【linux iic子系统】i2c设备的添加方法(四)

编程入门 行业动态 更新时间:2024-10-28 15:32:47

文章目录

前言一、静态注册二、动态注册三、用户空间注册四、i2c驱动扫描注册


前言

I2C设备的4种添加方法:
1)静态注册
2)动态注册
3)用户空间注册
4)i2c驱动扫描注册


一、静态注册

静态注册就是在架构板级文件或初始化文件中添加i2c设备信息,并注册到特定位置(__i2c_board_list链表)上就可以了,如arm架构下board-xxx-yyy.c文件,x86架构下xxx-yyy-init-zzz.c文件。当系统静态注册i2c控制器(adapter)时,将会去查找这个链表,并实例化i2c设备添加到i2c总线上。注意:一定要赶在i2c控制器注册前将i2c设备信息添加到链表上。

1)定义一个 i2c_board_info 结构体

必须要有名字和设备地址,其他如中断号、私有数据非必须。

static struct i2c_board_info my_tmp75_info = {I2C_BOARD_INFO("my_tmp75", 0x48),
};
@my_tmp75是设备名字,用于匹配i2c驱动。
@0x48是i2c设备的基地址。

如果有多个设备,可以定义成结构数组,一次添加多个设备信息。

2)注册设备

使用 i2c_register_board_info 函数将i2c设备信息添加到特定链表,函数原型如下

i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n)
{devinfo->busnum = busnum; /* 组装i2c总线 */devinfo->board_info = *info; /* 绑定设备信息 */list_add_tail(&devinfo->list, &__i2c_board_list); /* 将设备信息添加进链表中 */
}
@busnum:哪一条总线,也就是选择哪一个i2c控制器(adapter)
@info:i2c设备信息,就是上面的结构体
@n:info中有几个设备

将在 i2c_register_adapter 函数中使用到

static int i2c_register_adapter(struct i2c_adapter *adap)
{…if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);…
}static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{struct i2c_devinfo        *devinfo;down_read(&__i2c_board_lock);list_for_each_entry(devinfo, &__i2c_board_list, list) {if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter, &devinfo->board_info))dev_err(&adapter->dev,"Can't create device at 0x%02x\n",devinfo->board_info.addr);}up_read(&__i2c_board_lock);
}

而调用 i2c_register_adapter 函数的有两个地方,分别是 i2c_add_adapter 函数和i2c_add_numbered_adapter 函数,但 i2c_add_adapter 函数中是动态分配的总线号,adap->nr一定比__i2c_first_dynamic_bus_num变量大,因此不会进入到i2c_scan_static_board_info函数,所以只有i2c_add_numbered_adapter 最终使用到,而这个函数是i2c控制器静态注册时调用的,因此静态注册i2c设备必须赶在i2c控制器注册前添加。


二、动态注册

动态注册i2c设备可以使用两个函数,分别为 i2c_new_device 函数与 i2c_new_probed_device 函数,它们两区别是:

i2c_new_device:不管i2c设备是否真的存在,都实例化 i2c_client。i2c_new_probed_device:调用probe函数去探测i2c地址是否有回应,存在则实例化i2c_client。如果自己不提供probe函数的话,使用默认的i2c_default_probe函数。

1)使用 i2c_new_device 注册设备

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>static struct i2c_board_info my_tmp75_info = {I2C_BOARD_INFO("my_tmp75", 0x48),//这个名字很重要,用于匹配 I2C 驱动
};static struct i2c_client *my_tmp75_client;static int my_tmp75_init(void)
{struct i2c_adapter *i2c_adapt;int ret = 0;i2c_adapt = i2c_get_adapter(6);if (i2c_adapt == NULL){printk("get adapter fail!\n");ret = -ENODEV;}my_tmp75_client = i2c_new_device(i2c_adapt, &my_tmp75_info);if (my_tmp75_client == NULL){printk("i2c new fail!\n");ret = -ENODEV;}i2c_put_adapter(i2c_adapt);return ret;
}static void my_tmp75_exit(void)
{i2c_unregister_device(my_tmp75_client);
}module_init(my_tmp75_init);
module_exit(my_tmp75_exit);MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This my i2c device for tmp75");

2)使用 i2c_new_probed_device 注册设备

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>static struct i2c_client *my_tmp75_client;static const unsigned short addr_list[] = { 0x46, 0x48, I2C_CLIENT_END };//必须以I2C_CLIENT_END宏结尾static int my_i2c_dev_init(void)
{struct i2c_adapter *i2c_adap;struct i2c_board_info my_i2c_dev_info;memset(&my_i2c_dev_info, 0, sizeof(struct i2c_board_info));   strlcpy(my_i2c_dev_info.type, "my_tmp75", I2C_NAME_SIZE);i2c_adap = i2c_get_adapter(0);my_tmp75_client = i2c_new_probed_device(i2c_adap, &my_i2c_dev_info, addr_list, NULL);//只会匹配到 0x48 地址i2c_put_adapter(i2c_adap);if (my_tmp75_client)return 0;elsereturn -ENODEV;
}static void my_i2c_dev_exit(void)
{i2c_unregister_device(my_tmp75_client);
}module_init(my_i2c_dev_init);
module_exit(my_i2c_dev_exit);MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This my i2c device for tmp75");

三、用户空间注册

1)创建 i2c 设备

echo i2c_test 0x48 > /sys/bus/i2c/devices/i2c-6/new_device

使用这种方法创建的 i2c 设备会挂在 i2c_adapter 的链表上,为了方便用户空间删除 i2c 设备!

2)删除设备

echo 0x48 > /sys/bus/i2c/devices/i2c-6/delete_device

删除设备只能删除在用户空间创建的 i2c 设备!

在 i2c 控制器注册时,会在/sys/bus/i2c/devices/目录下创建 i2c-x 设备文件,并且设置它的属性,而 new_device 和 delete_device 均是它的属性。

写new_device时会调用i2c_sysfs_new_device 函数,内部再调用 i2c_new_device 函数。

写delete_device时会调用i2c_sysfs_delete_device函数,内部再调用i2c_unregister_device函数。

/** Let users instantiate I2C devices through sysfs. This can be used when* platform initialization code doesn't contain the proper data for* whatever reason. Also useful for drivers that do device detection and* detection fails, either because the device uses an unexpected address,* or this is a patible device with different ID register values.** Parameter checking may look overzealous, but we really don't want* the user to provide incorrect parameters.*/
/*让用户通过 sysfs 实例化 I2C 设备。 当平台初始化代码由于某种原因不包含正确的数据时,可以使用它。 对于进行设备检测和检测失败的驱动程序也很有用,因为设备使用了意外的地址,或者这是具有不同 ID 寄存器值的兼容设备。*参数检查可能看起来过于热心,但我们真的不希望用户提供不正确的参数。*/
static ssize_t
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{struct i2c_adapter *adap = to_i2c_adapter(dev);struct i2c_board_info info;struct i2c_client *client;char *blank, end;int res;memset(&info, 0, sizeof(struct i2c_board_info));blank = strchr(buf, ' ');if (!blank) {dev_err(dev, "%s: Missing parameters\n", "new_device");return -EINVAL;}if (blank - buf > I2C_NAME_SIZE - 1) {dev_err(dev, "%s: Invalid device name\n", "new_device");return -EINVAL;}memcpy(info.type, buf, blank - buf);/* Parse remaining parameters, reject extra parameters */res = sscanf(++blank, "%hi%c", &info.addr, &end);if (res < 1) {dev_err(dev, "%s: Can't parse I2C address\n", "new_device");return -EINVAL;}if (res > 1  && end != '\n') {dev_err(dev, "%s: Extra parameters\n", "new_device");return -EINVAL;}client = i2c_new_device(adap, &info);if (!client)return -EINVAL;/* Keep track of the added device */mutex_lock(&adap->userspace_clients_lock);list_add_tail(&client->detected, &adap->userspace_clients);mutex_unlock(&adap->userspace_clients_lock);dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",info.type, info.addr);return count;
}/** And of course let the users delete the devices they instantiated, if* they got it wrong. This interface can only be used to delete devices* instantiated by i2c_sysfs_new_device above. This guarantees that we* don't delete devices to which some kernel code still has references.** Parameter checking may look overzealous, but we really don't want* the user to delete the wrong device.*/
/*
* 当然,如果用户弄错了,让他们删除他们实例化的设备。 该接口只能用于删除
上面 i2c_sysfs_new_device 实例化的设备。 这保证了我们不会删除某些内核
代码仍然引用的设备。
*
参数检查可能看起来过于热心,但我们真的不希望用户删除错误的设备。
*/
static ssize_t
i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{struct i2c_adapter *adap = to_i2c_adapter(dev);struct i2c_client *client, *next;unsigned short addr;char end;int res;/* Parse parameters, reject extra parameters */res = sscanf(buf, "%hi%c", &addr, &end);if (res < 1) {dev_err(dev, "%s: Can't parse I2C address\n", "delete_device");return -EINVAL;}if (res > 1  && end != '\n') {dev_err(dev, "%s: Extra parameters\n", "delete_device");return -EINVAL;}/* Make sure the device was added through sysfs */res = -ENOENT;mutex_lock(&adap->userspace_clients_lock);list_for_each_entry_safe(client, next, &adap->userspace_clients,detected) {if (client->addr == addr) {dev_info(dev, "%s: Deleting device %s at 0x%02hx\n","delete_device", client->name, client->addr);list_del(&client->detected);i2c_unregister_device(client);res = count;break;}}mutex_unlock(&adap->userspace_clients_lock);if (res < 0)dev_err(dev, "%s: Can't find device in list\n","delete_device");return res;
}

四、i2c驱动扫描注册

在《i2c设备与驱动匹配过程》中说到,i2c 驱动注册时会使用两种匹配方法去寻找i2c设备,代码如下:

struct bus_type i2c_bus_type = {.name		= "i2c",.match		= i2c_device_match,.probe		= i2c_device_probe,.remove		= i2c_device_remove,.shutdown	= i2c_device_shutdown,.pm		= &i2c_device_pm_ops,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{driver->driver.bus = &i2c_bus_type;//添加总线res = driver_register(&driver->driver);//驱动注册核心函数,注意只传入了driver成员/* 遍历所有挂在总线上的iic适配器,用它们去探测driver中指定的iic设备地址列表 */i2c_for_each_dev(driver, __process_new_driver);
}

driver_register 函数已将讲解过,现在来分析 i2c_for_each_dev 函数,

int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
{int res;mutex_lock(&core_lock);res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);mutex_unlock(&core_lock);return res;
}int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{struct klist_iter i;struct device *dev;int error = 0;if (!bus || !bus->p)return -EINVAL;klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));while (!error && (dev = next_device(&i)))error = fn(dev, data);klist_iter_exit(&i);return error;
}

最终调用 __process_new_driver 函数,使用 i2c 总线上所有 i2c 适配器去探测i2c驱动中的设备地址数组!

static struct device_type i2c_client_type = {.groups		= i2c_dev_attr_groups,.uevent		= i2c_device_uevent,.release	= i2c_client_dev_release,
};
struct device_type i2c_adapter_type = {.groups		= i2c_adapter_attr_groups,.release	= i2c_adapter_dev_release,
};
static int __process_new_driver(struct device *dev, void *data)
{if (dev->type != &i2c_adapter_type)return 0;return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

入口先判断传入的设备是不是i2c适配器(i2c控制器),因为在《i2c设备与驱动匹配过程》中说到,i2c 适配器和 i2c 设备一样,都会挂在 i2c 总线上,它们是通过 dev->type 项区分的。

static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{/* Detect supported devices on that bus, and instantiate them */i2c_detect(adap, driver);…
}

最终调用i2c_detect函数,函数简化后如下:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{int adap_id = i2c_adapter_id(adapter);address_list = driver->address_list;if (!driver->detect || !address_list)return 0;temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);itemp_client->adapter = adapter;for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1){temp_client->addr = address_list[i];err = i2c_detect_address(temp_client, driver);if (unlikely(err))break;}
}

如果 i2c 驱动的设备地址数组为空或 detect 函数不存在,则结束返回,否则临时实例化一个 temp_client 设备,赋值 adapter 为当前 i2c 控制器,然后在使用该 i2c 控制器去探测 i2c 驱动设备地址数组中的所有地址,关键函数是 i2c_detect_address 如下(简化后):

static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{struct i2c_board_info info;struct i2c_adapter *adapter = temp_client->adapter;int addr = temp_client->addr;int err;err = i2c_check_7bit_addr_validity_strict(addr);//检查地址是否有效,即7位有效地址if (err) {return err;}if (i2c_check_addr_busy(adapter, addr))//跳过已经使用的i2c设备return 0;if (!i2c_default_probe(adapter, addr))//检查这个地址是否有回应return 0;memset(&info, 0, sizeof(struct i2c_board_info));info.addr = addr;err = driver->detect(temp_client, &info);if (err) {return err == -ENODEV ? 0 : err;}if (info.type[0] == '\0'){}else{struct i2c_client *client;client = i2c_new_device(adapter, &info);if (client)list_add_tail(&client->detected, &driver->clients);}
}

首先检查有效性、是否有设备回应、是否被使用,之后初始化了i2c_board_info 结构,注意只初始化了地址(实例化设备必须还要名字),然后调用了 i2c 驱动中的 detect 函数,如果成功则调用 i2c_new_device函数真正实例化i2c设备,并且将i2c设备挂在i2c驱动的链表上!注意:只有这种方式添加的i2c设备才会挂在驱动的链表上!

仔细思考上面就能发现,i2c驱动中的detect函数必须要填写i2c_board_info结构体中name,i2c_new_device才能实例化i2c设备。

所以,使用i2c驱动扫描注册设备时,需要按如下格式编写驱动!

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>static int __devinit my_i2c_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{return 0;
}static int __devexit my_i2c_drv_remove(struct i2c_client *client)
{return 0;
}static const struct i2c_device_id my_dev_id_table[] = {{ "my_i2c_dev", 0 },{}
};//这里的名字很重要,驱动第一种匹配设备的方式要用到static int my_i2c_drv_detect(struct i2c_client *client, struct i2c_board_info *info)
{/* 能运行到这里, 表示该addr的设备是存在的* 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)* 还需要进一步读写I2C设备来分辨是哪款芯片,自己写方法* detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type,也就是设备名字*/printk("my_i2c_drv_detect: addr = 0x%x\n", client->addr);/* 进一步判断是哪一款 */strlcpy(info->type, "my_i2c_dev", I2C_NAME_SIZE);return 0;
}static const unsigned short addr_list[] = { 0x46, 0x48, I2C_CLIENT_END };//必须使用I2C_CLIENT_END宏结尾/* 1. 分配/设置i2c_driver */
static struct i2c_driver my_i2c_driver = {.class  = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备,不是对应类将不会调用匹配 */.driver        = {.name        = "my_i2c_dev",.owner        = THIS_MODULE,},.probe                = my_i2c_drv_probe,.remove        = __devexit_p(my_i2c_drv_remove),.id_table        = my_dev_id_table,.detect     = my_i2c_drv_detect,  /* 用这个函数来检测设备确实存在 ,并填充设备名字*/.address_list        = addr_list,   /* 这些设备的地址 */
};static int my_i2c_drv_init(void)
{/* 2. 注册i2c_driver */i2c_add_driver(&my_i2c_driver);return 0;
}static void my_i2c_drv_exit(void)
{i2c_del_driver(&my_i2_driver);
}module_init(my_i2c_drv_init);
module_exit(my_i2c_drv_exit);
MODULE_LICENSE("GPL");

参考链接

更多推荐

子系统,方法,设备,linux,iic

本文发布于:2023-05-20 23:58:11,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/156776.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:子系统   方法   设备   linux   iic

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!