I2C(三) linux3.4(内核分析)
title: I2C(三) linux3.4((内核分析))
date: 2019/1/28 19:18:42
toc: true
I2C(三) linux3.4(内核分析)
(一)总线流程
可以看下总线的匹配函数
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,
};
bus.probe
这里插入一下bus
的probe
,简单的搜索probe
,可以在drivers\base\bus.c
搜索到一些东西,继续查看,可以看到有如下函数,也就是先执行总线的probe
,再执行具体驱动的probe
driver_probe_device
> really_probe
> dev->bus->probe(dev)
> drv->probe(dev)
那么谁来调用这个driver_probe_device
,搜索可以看到有以下函数调用,具体的device_attach
和driver_attach
就不深入了,水平还不够
static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind);
>driver_bind
>driver_probe_device(drv, dev)
device_attach
>__device_attach
>driver_match_device 先匹配
>driver_probe_device(drv, dev)
driver_attach
>__driver_attach
>driver_match_device 先匹配
>driver_probe_device(drv, dev)
int driver_probe_device(struct device_driver *drv, struct device *dev)
>really_probe(dev, drv);
{
dev->bus->probe(dev)
drv->probe(dev)
}
match
这里匹配的是dev
的母体结构client
的成员name
与driver
的id_table
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
//先找到 dev 的母结构 i2c_client的地址
struct i2c_client *client = i2c_verify_client(dev);
//通过 driver 找到 i2c_driver
driver = to_i2c_driver(drv);
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
> if (strcmp(client->name, id->name) == 0)
}
i2c_device_probe
匹配之后,会调用这个函数,这个函数会这里会将client
绑定具体的driver
,再调用实际驱动的probe
,
client->driver = driver;
driver->probe(client, i2c_match_id(driver->id_table, client));
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
// 这里会将client 绑定具体的driver
client->driver = driver;
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n");
status = driver->probe(client, i2c_match_id(driver->id_table, client));
if (status) {
client->driver = NULL;
i2c_set_clientdata(client, NULL);
}
return status;
}
(二)client注册
这里的client,指的是能够挂接到总线上的设备,或者是说你强制认为总线上就有这个设备.具体的结构如下:
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev)
struct i2c_adapter *adapter
指的是挂载的具体的哪个总线struct i2c_driver *driver
指的是我们用什么驱动,这里指的是自己的字符设备或者块设备等struct device dev
这个结构比较关键,这个就是总线设备模型dev---driver
部分中左侧dev
部分- 当我们挂载到bus上的时候,通过bus.probe就会将对应的
driver
附到client.driver
上了
方式(一)静态加载
i2c_register_board_info
这种方式是编译到内核,注册信息
i2c_register_board_info
我们一般使用如下方式
//arch\arm\mach-s3c24xx\mach-mini2440.c
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
{
I2C_BOARD_INFO("24c08", 0x50),
.platform_data = &at24c08,
},
};
static void __init mini2440_init(void)
{
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
}
具体是怎么注册的?实际上是将具体的这个硬件信息先构造成i2c_devinfo
结构,再放入到一个全局的链表中
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
devinfo->busnum = busnum;
devinfo->board_info = *info;
//这是一个全局的链表,也就是先分配一个i2c_devinfo.board_info 使用的就是单板传递的信息
list_add_tail(&devinfo->list, &__i2c_board_list);
}
return status;
}
这个全局的链表内容是这样的
struct i2c_devinfo {
struct list_head list;
int busnum; //链表上的序号
struct i2c_board_info board_info;
{
char type[I2C_NAME_SIZE]; //设备名,
unsigned short flags; //将来传递给client
unsigned short addr; //设备地址
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
int irq;
};
};
i2c_scan_static_board_info
谁会来调用这个链表呢?也就是根据这个信息添加client到总线上,很显然,是我们在注册总线的时候,来去遍历这个信息,也就是函数i2c_scan_static_board_info
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
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);
}
}
这个函数根据这个链表,通过i2c_new_device
来操作,提前透露一下,这个函数是构造client
,加入到总线设备的dev链表中去
我们从 if (devinfo->busnum == adapter->nr
这里可以看出来适配器的选择,也就是
devinfo->busnum
也指的是总线的编号adapter->nr
应该是适配器的编号,也就是总线的编号
i2c_new_device
这里我们根据i2c_board_info >构造链表__i2c_board_list
来创建一个实际的client
,然后将这个client
的元素dev
挂载到总线设备模型的dev上
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL); 先分配结构体
// 指定具体的adapt适配器
client->adapter = adap;
// 指定挂载在总线设备平台上的 platform_data ,挂载在总线平台上的是dev 而不是 client
client->dev.platform_data = info->platform_data;
// 这个数据属于架构的数据
if (info->archdata)
client->dev.archdata = *info->archdata;
//具体硬件信息
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
//地址合法性检测
/* Check for address validity */
status = i2c_check_client_addr_validity(client);
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
/* Check for address business */ 7位地址和10位地址判断
status = i2c_check_addr_busy(adap, client->addr);
if (status)
goto out_err;
//设置具体总线平台的类型,节点,设置名字
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
//kobject.name =%d-%04x 适配器.nr- client->addr(10位地址还要|0xa0000)
/* For 10-bit clients, add an arbitrary offset to avoid collisions */
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr | ((client->flags & I2C_CLIENT_TEN)
? 0xa000 : 0));
//注册client->dev
status = device_register(&client->dev);
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
}
client->dev.parent = &client->adapter->dev;
这里将总线设备的这个元素只想来适配器的dev
i2c_register_adapter
那么谁来调用这个静态扫描的函数呢?很显然是在注册适配器的时候,也就是注册I2C控制器
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
小结
单板信息最终会加入到这个链表,注册adapt
的时候会扫描这个链表 生成client
,将这个client.dev
挂载到 总线平台设备的 dev
链表上,那么我们如何去找到这个client,在这里有一个宏
#define to_i2c_client(d) container_of(d, struct i2c_client, dev)
这里的i2c_board_info.type==i2c_client.name
用作总线设备的匹配
方式(二)指定设备
这个方式实际上就是跳过通过单板信息构造链表,直接调用i2c_new_device
来构造client
,加入到总线设备模型的dev
链表中,i2c_new_probed_device
则是先判断下设备地址是否存在再来创建,这个函数还能指定自定义的地址检测方式
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
i2c_new_probed_device(struct i2c_adapter *adap,
struct i2c_board_info *info,
unsigned short const *addr_list,
int (*probe)(struct i2c_adapter *, unsigned short addr))
{
if (!probe)
probe = i2c_default_probe;
if (i2c_check_addr_busy(adap, addr_list[i]))
...
probe(adap, addr_list[i])
return i2c_new_device(adap, info);
}
具体的实例代码如下
static struct i2c_board_info at24cxx_info = {
I2C_BOARD_INFO("at24c08", 0x50), //设置tyep 也就是后来赋值给client的name
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
};
static struct i2c_client *at24cxx_client;
static int at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0);
at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
i2c_put_adapter(i2c_adap);
return 0;
}
/// 检测地址
static struct i2c_client *at24cxx_client;
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
static int at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap;
struct i2c_board_info at24cxx_info;
memset(&at24cxx_info, 0, sizeof(struct i2c_board_info));
strlcpy(at24cxx_info.type, "at24c08", I2C_NAME_SIZE);
i2c_adap = i2c_get_adapter(0);
at24cxx_client = i2c_new_probed_device(i2c_adap, &at24cxx_info, addr_list, NULL);
i2c_put_adapter(i2c_adap);
if (at24cxx_client)
return 0;
else
return -ENODEV;
}
方式(三)用户空间
直接在shell
下操作文件
# 创建
echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device
# 删除
echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device
搜索下new_device
,实际最终调用函数i2c_sysfs_new_device
,这个函数内部也是调用i2c_new_device
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);
方式(四)遍历适配器
前面三个都是指定适配器去挂接设备,那么假设不知道适配器,我们如何去遍历适配器,自动识别出在那个适配器上然后去挂接上client
呢?这里使用i2c_add_driver
注意
实际上我们的适配器也是挂载到设备平台dev
链表中,通过type
来区分
简介
使用
i2c_add_driver
注册用户驱动匹配已有的挂载
client
,调用probe
遍历左侧的
dev
链表,挑选adapt
,来识别驱动自身指定的address_list
,如果识别到设备存在调用驱动的
detect
来做进一步检测,设置i2c_board_info.type
,赋值client
做匹配使用
i2c_new_device
来挂载这个client
,这里会和i2c_driver
的id_table
比较,执行驱动的probe
这里一定会匹配上的,因为client.name就是
i2c_board_info.type
也就是id_table.name
代码注释(可以看下后面的设备驱动章节)
i2c_add_driver
i2c_register_driver
a. at24cxx_driver放入i2c_bus_type的drv链表
并且从dev链表里取出能匹配的i2c_client并调用probe
driver_register
b. 对于每一个适配器,调用__process_new_driver
对于每一个适配器,调用它的函数确定address_list里的设备是否存在
如果存在,再调用detect进一步确定、设置,然后i2c_new_device
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
//下面这个函数是总线设备的通用函数 ,注释上的意思应该就是
// 在某类设备总线上,从 start的dev开始遍历,执行以fn为函数的,data为参数的回调i2c_driver
// 这里也就是对于 i2c_bus_type 上的dev 执行 __process_new_driver 参数是driver也就是 i2c_driver
//int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
>bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
//可以先看一下__process_new_driver ,按照之前的注释 data是i2c_driver ,也就是 参数是 __process_new_driver
__process_new_driver
//这里的dev 是指的适配器,实际上适配器和设备都是挂载在一个链表上的,根据type分辨
// 确保遍历的dev 是适配器类型
if (dev->type != &i2c_adapter_type)
return 0;
i2c_do_add_adapter
/* Detect supported devices on that bus, and instantiate them */
i2c_detect(adap, driver);
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
err = i2c_detect_address(temp_client, driver);
/* 判断这个设备是否存在:简单的发出S信号确定有ACK */
if (!i2c_default_probe(adapter, addr))
return 0;
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
//这里是我们自己的检测函数
// 设置info.type
err = driver->detect(temp_client, &info);
i2c_new_device
代码举例
static struct i2c_driver at24cxx_driver = {
.class = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备 */
.driver = {
.name = "100ask",
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = __devexit_p(at24cxx_remove),
.id_table = at24cxx_id_table,
.detect = at24cxx_detect, /* 用这个函数来检测设备确实存在 */
.address_list = addr_list, /* 这些设备的地址 */
};
//用作 dev 和 driver 的匹配,这里如果能识别,肯定赋值个 client.name
static const struct i2c_device_id at24cxx_id_table[] = {
{ "at24c08", 0 },
{}
};
static const unsigned short addr_list[] = { 0x60, 0x50, I2C_CLIENT_END };
去"class表示的这一类"I2C适配器,用"detect函数"来确定能否找到"address_list里的设备",
如果能找到就调用i2c_new_device来注册i2c_client, 这会和i2c_driver的id_table比较,
如果匹配,调用probe
(三)适配器
drivers\i2c\busses\i2c-s3c2410.c
注册适配器最终会调用i2c_driver.detect
来设置具体的匹配的参数i2c_client.type
,,挂载client
到链表
然后调用驱动i2c_driver.probe
注册字符设备驱动等
引入
驱动首先从入口开始看起,可以看到是platform
框架,也就是驱动入口执行函数是driver.probe
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table = s3c24xx_i2c_match,
},
};
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
s3c24xx_i2c_probe
- 设置具体的
adapt
结构 - 设置中断
- 使用
i2c_add_numbered_adapter
或者i2c_add_adapter
来注册这个adapt
- 使用
of_i2c_register_devices
会调用i2c_new_device
添加client
到链表
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
struct resource *res;
int ret;
if (!pdev->dev.of_node) {
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (pdata)
memcpy(i2c->pdata, pdata, sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
//设置adapt 的参数
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
// adap.algo 这是具体的硬件传输的函数
i2c->adap.algo = &s3c24xx_i2c_algorithm;
//数据传输重试次数
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
spin_lock_init(&i2c->lock);
//初始化等待队列,这个在后续的休眠中使用,触发中断后休眠
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
clk_enable(i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->ioarea = request_mem_region(res->start, resource_size(res),pdev->name);
i2c->regs = ioremap(res->start, resource_size(res));
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
// 初始化I2c 寄存器 gpio复用
/* initialise the i2c controller */
ret = s3c24xx_i2c_init(i2c);
// 设置中断函数,真正的数据传输
i2c->irq = ret = platform_get_irq(pdev, 0);
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, 0,dev_name(&pdev->dev), i2c);
ret = s3c24xx_i2c_register_cpufreq(i2c);
// 这里使用指定 adapt 编号的方式,也可以使用 i2c_add_adapter 来注册这个adapt
/* Note, previous versions of the driver used i2c_add_adapter()
* to add the bus at any number. We now pass the bus number via
* the platform data, so if unset it will now default to always
* being bus 0.
*/
i2c->adap.nr = i2c->pdata->bus_num;
i2c->adap.dev.of_node = pdev->dev.of_node;
ret = i2c_add_numbered_adapter(&i2c->adap);
//这个会挂接client 到链表
of_i2c_register_devices(&i2c->adap);
> i2c_new_device
platform_set_drvdata(pdev, i2c);
pm_runtime_enable(&pdev->dev);
pm_runtime_enable(&i2c->adap.dev);
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
clk_disable(i2c->clk);
return 0;
}
注册适配器
这里使用i2c_add_adapter
或者i2c_add_numbered_adapter
来注册适配器,最终都是调用i2c_register_adapter(adap)
来注册适配器的
i2c_register_adapter
这里的具体看代码注释,最终会调用__process_new_adapter
,应该是要完成实际的驱动注册
static int i2c_register_adapter(struct i2c_adapter *adap)
{
rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
//设置 adapt的dev.type,后续能够根据这个和client区别
// 注册adap->dev 到总线平台的左侧
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
// 在系统初始化的时候,我们手动添加了一些外设信息,期望构造client加入到dev链表
// 如果没有合适的adapt,我们并不能将其构造为client加入,而是保留在链表中
// 我们新注册了一个adapt,理所当然需要使用这个新的adapt去尝试构造一个client
/* create pre-declared device nodes */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
// 针对链表上的所有 driver,调用adapt的硬件操作,执行__process_new_adapter
// 执行__process_new_adapter 应该是要调用驱动driver 的detect 来创建字符设备等驱动
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
}
__process_new_adapter
这个函数是我们遍历总线的driver,执行这个函数,也就是遍历我们右侧的的
i2c_driver
这个函数从名字看就和
__process_new_driver
很像,从bus_for_each_drv
的注释看出来data
是作为回调的参数,也就是说最终是i2c_do_add_adapter(to_i2c_driver(d), adapt);
,可以看到i2c_do_add_adapter
的原型的第二个参数确实是adapt
/**
* bus_for_each_drv - driver iterator
* @bus: bus we're dealing with.
* @start: driver to start iterating on.
* @data: data to pass to the callback.
* @fn: function to call for each driver.
*
*/
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
static int __process_new_adapter(struct device_driver *d, void *data)
{
return i2c_do_add_adapter(to_i2c_driver(d), data);
}
//上面的data也就是bus_for_each_drv的data 也就是最开始要注册的adapt
static int i2c_do_add_adapter(struct i2c_driver *driver,
struct i2c_adapter *adap)
i2c_do_add_adapter
这里函数的关键是i2c_detect
,3.4内核的driver->attach_adapter
可以忽略了
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);
// 下面这个driver->attach_adapter 是2.6上的匹配到硬件地址后,
// 用来创建字符设备驱动等操作的函数,3.4内核上使用上面的 i2c_detect 来实现这个功能
// 一般不需要使用,可以看到结构定义是 __deprecated ,也就是不推荐的了
/* Let legacy drivers scan this bus for matching devices */
if (driver->attach_adapter) {
dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
driver->driver.name);
dev_warn(&adap->dev, "Please use another way to instantiate "
"your i2c_client\n");
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap);
}
return 0;
}
i2c_detect
这里先判断下adapt
和driver
类型,然后构造一个临时的client
,地址是driver->address_list
中的列表,依次尝试i2c_detect_address
来检测
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
const unsigned short *address_list;
struct i2c_client *temp_client;
int i, err = 0;
int adap_id = i2c_adapter_id(adapter);
address_list = driver->address_list;
if (!driver->detect || !address_list)
return 0;
/* Stop here if the classes do not match */
if (!(adapter->class & driver->class))
return 0;
/* Set up a temporary client to help detect callback */
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!temp_client)
return -ENOMEM;
temp_client->adapter = adapter;
// 创建一个临时的 client,使用 i2c_detect_address来尝试操作
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
"addr 0x%02x\n", adap_id, address_list[i]);
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver);
if (unlikely(err))
break;
}
kfree(temp_client);
return err;
}
i2c_detect_address
- 这里会使用
i2c_smbus_xfer
来实际通信确保硬件存在 - 调用最后的 用户驱动
i2c_driver.detect
- 成功后使用
i2c_new_device
安装client
到dev
链表,i2c_new_device
在上述章节回顾
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;
/* Make sure the address is valid */
err = i2c_check_addr_validity(addr);
if (err) {
dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
addr);
return err;
}
/* Skip if already in use */
if (i2c_check_addr_busy(adapter, addr))
return 0;
// 这里会使用 i2c_smbus_xfer 来实际通信确保硬件存在
/* Make sure there is something at this address */
if (!i2c_default_probe(adapter, addr))
return 0;
// 调用最后的 用户驱动 i2c_driver.detect
/* Finally call the custom detection function */
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
err = driver->detect(temp_client, &info);
if (err) {
/* -ENODEV is returned if the detection fails. We catch it
here as this isn't an error. */
return err == -ENODEV ? 0 : err;
}
// i2c_new_device 安装client 到dev 链表
/* Consistency check */
if (info.type[0] == '\0') {
dev_err(&adapter->dev, "%s detection function provided "
"no name for 0x%x\n", driver->driver.name,
addr);
} else {
struct i2c_client *client;
/* Detection succeeded, instantiate the device */
dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",
info.type, info.addr);
client = i2c_new_device(adapter, &info);
if (client)
list_add_tail(&client->detected, &driver->clients);
else
dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
info.type, info.addr);
}
return 0;
}
i2c_driver.detect
接下去就是要分析用户的设备驱动i2c_driver.detect
,设置匹配参数
- 用来判断那些可能设备地址相同的不同类型的设备.
- 设置具体的
i2c_board_info.type
,这个参数会在i2c_new_device
赋值给client
,用作i2c
总线平台的匹配参数 - 这会触发
i2c_driver
的probe
接下去执行驱动probe
这里就是执行字符设备驱动的注册等了
(四)设备驱动i2c_driver
drivers\misc\eeprom\eeprom.c
drivers\misc\eeprom\at24.c
misc 是杂项的意思
引入
从入口看起
static int __init at24_init(void)
{
return i2c_add_driver(&at24_driver);
}
module_init(at24_init);
注册设备驱动
i2c_add_driver
/* use a define to avoid include chaining to get THIS_MODULE */
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
i2c_register_driver
- 注册驱动程序,智力应该就会去执行
probe
函数了 - 遍历这个驱动
driver
,执行__process_new_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
// 注册驱动程序,智力应该就会去执行probe 函数了
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
// 遍历这个驱动,执行__process_new_driver
INIT_LIST_HEAD(&driver->clients);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
probe
综合来看这两个例子,probe
里面就是创建字符设备驱动类似的操作,at24
里面的比较复杂,但都是一些具体的操作
__process_new_driver
这个函数最终调用了i2c_do_add_adapter
,也就是和注册适配器后的操作一致,挂接client,绑定client,driver,adapt
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));
}
这个函数理论上需要驱动实现detect
,但是我在at24.c
里面没有找到,在eeprom.c
里面是有的,所以这个驱动应该是先有设备驱动和适配器,然后去手动构造client
,这样就是另外的路线了
i2c_do_add_adapter
i2c_detect
i2c_detect_address
i2c_driver.detect
接下去执行驱动probe
这几个函数都和注册适配器的是一样的,回去看上面的就可以了
构造设备驱动
方式(一) APP>驱动
参考内核文档 Documentation\i2c\smbus-protocol
这里就是在i2c_driver
的probe
中注册我们真实的驱动程序,比如构造字符设备驱动程序,提供读写的接口即可
在EEPROM
的普通读写,可以使用以下简单的函数,参考Documentation\i2c\smbus-protocol
,smbus
是i2c
的一个子集
//读
i2c_smbus_read_byte_data(at24cxx_client, addr)
//写
i2c_smbus_write_byte_data(at24cxx_client, addr, data)
方式(二)使用i2c-dev
使用通用的一个驱动程序i2c-dev
来控制总线,去读写设备.其实我们可以看到内核源码目录下有个i2c-dev.c
,就是这个文件,它类似一个通用的驱动.它实现了一个字符设备驱动,次设备号表示总线编号
内核配置
Device Drivers
I2C support
<*> I2C device interface
//i2c_dev->adap->nr 指定的adapt
static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
{
struct i2c_dev *i2c_dev;
list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
if (i2c_dev->adap->nr == index)
goto found;
}
i2c_dev = NULL;
return i2c_dev;
}
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
可以看下源码,实际上最终也是调用类似的函数,注意这里面的i2cdev_read/i2cdev_write
只是单纯时序上的读写,我们操作EE
的读操作实际上是需要先读再写地址的,所以不能用这个接口,应该用i2cdev_ioctl
i2c_get_functionality
i2c_smbus_xfer
这里可以参考
Linux设备驱动开发详解第2版-宋宝华.pdf > 15.4.3 Linux 的 i2c-dev.c 文件分析
关于设备驱动detect
我们在分析注册设备驱动以及注册适配器驱动的时候,最终都会调用i2c_driver.detect
,这个函数会设置具体的i2c_board_info.type
,这个参数会在i2c_new_device
赋值给client
,用作i2c
总线平台的匹配参数这会触发i2c_driver
的probe
.
但是我们在刚开始做的设置client的前三种方法,i2c_driver
并没有设置detect
,也就是说在函数中不会执行到这个过程,我们从公共的入口i2c_detect
来看下,加入调试打印看看实际有没有执行
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
const unsigned short *address_list;
struct i2c_client *temp_client;
int i, err = 0;
int adap_id = i2c_adapter_id(adapter);
address_list = driver->address_list;
if (!driver->detect || !address_list)
{
// 在这里加入打印,看看实际驱动的detect 有没有默认值
printk("no detect\n");
return 0;
}
else
{
//打印实际的detect
printk("detect is %p \n",driver->detect);
}
/* Stop here if the classes do not match */
if (!(adapter->class & driver->class))
return 0;
/* Set up a temporary client to help detect callback */
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!temp_client)
return -ENOMEM;
temp_client->adapter = adapter;
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
"addr 0x%02x\n", adap_id, address_list[i]);
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver);
if (unlikely(err))
break;
}
kfree(temp_client);
return err;
}
测试下代码1th
,运行结果如下
/mnt/iic/1th # insmod at24cxx_drv.ko
no detect
/mnt/iic/1th # insmod at24cxx_dev.ko
/home/book/stu/iic/1th/at24cxx_drv.c at24cxx_probe 13
------------------------------------------------------------
/mnt/iic/1th # insmod at24cxx_dev.ko
/mnt/iic/1th # insmod at24cxx_drv.ko
/home/book/stu/iic/1th/at24cxx_drv.c at24cxx_probe 13
no detect
可以看出来,不论是先加载dev
还是先加载driver
,都不会有detect
,那为啥能正常工作执行probe
呢?流程应该是这样的:
- 我们的
i2c_detect
实际上只是将client
挂接到dev
链表上,i2c_client
上的driver
驱动的依附是在bus.probe=i2c_device_probe
中实现的 dev
与driver
匹配上就会执行驱动的probe,也就是执行打印
所以:
- 先加载
drv
时,运行到i2c_detect
时直接打印no detect
后退出,然后加载dev
后匹配执行驱动的probe
- 先加载
dev
时,没有匹配不动作,加载drv
时,先匹配上了执行驱动的probe
,再去执行i2c_detect
,打印no detect
补充:
- 可以看到
i2c_detect
最终目的是为了挂接client
,而我们手动创建client
使用了i2c_new_device
,跳过了这个步骤,所以也能运行. - 这里要区分两个步骤
- 挂接
client
到dev
链表,使用i2c_new_device
- 匹配
client
与driver
,使用的是bus.probe
- 挂接
- 也就是说如果我们不使用
i2c_new_device
来手动创建设备挂接到dev
链表,就需要detect
来辅助内核挂接client
挂接
系统信息查看
我们可以通过查看文件信息来查看具体的i2c
设备
可以在
/sys/bus/i2c/
下查看设备文件,这里看出来适配器驱动与client
都属于device
,这里的50
是设备地址.0
是总线序号/mnt/iic/1th # ls /sys/bus/i2c/
devices drivers_autoprobe uevent
drivers drivers_probe
/mnt/iic/1th # ls /sys/bus/i2c/devices/
0-0050 i2c-0
/mnt/iic/1th # ls /sys/bus/i2c/drivers/
100ask at24 dummy
查看已有的
i2c
总线#ls /sys/class/i2c-adapter/
i2c-0
创建设备节点的
new_device
和deleted_device
位置#ls /sys/class/i2c-adapter/i2c-0/
delete_device name power uevent
device new_device subsystem
这个文件夹实际的链接
/sys/devices/platform/s3c2440-i2c/i2c-0
/sys/class/i2c-adapter/i2c-0 这是一个链接文件 /sys/devices/platform/s3c2440-i2c/i2c-0
查看我们的
client
/sys/devices/platform # ls
alarmtimer s3c2440-nand s3c24xx_led.1 soc-audio
power s3c2440-uart.0 s3c24xx_led.2 uevent
s3c2410-lcd s3c2440-uart.1 s3c24xx_led.3 wm8976-codec
s3c2410-ohci s3c2440-uart.2 s3c24xx_wm8976.0
s3c2410-wdt s3c24xx-iis samsung-audio
s3c2440-i2c s3c24xx_led.0 snd-soc-dummy /sys/devices/platform/s3c2440-i2c # ls
driver i2c-0 modalias power subsystem uevent /sys/devices/platform/s3c2440-i2c/i2c-0 # ls
0-0051 device new_device subsystem
delete_device name power uevent 这里的 0-0051 实际上就是我们挂载的clinet设备了
内核配置
如果要使用i2c_dev.c
这个通用的驱动,可以查看同目录下的Makefile
,需要配置obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
────────────────────────────────────────────────────── Search Results ──────────────────────────────────────────────────────┐
│ Symbol: I2C_CHARDEV [=m] │
│ Type : tristate │
│ Prompt: I2C device interface │
│ Defined at drivers/i2c/Kconfig:39 │
│ Depends on: I2C [=y] │
│ Location: │
│ -> Device Drivers │
│ -> I2C support (I2C [=y])
关于适配器驱动,看下drivers\i2c\busses\Makefile
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
也就是找到配置,去除这个
│ Symbol: I2C_S3C2410 [=y] │
│ Type : tristate │
│ Prompt: S3C2410 I2C Driver │
│ Defined at drivers/i2c/busses/Kconfig:601 │
│ Depends on: I2C [=y] && HAVE_S3C2410_I2C [=y] │
│ Location: │
│ -> Device Drivers │
│ -> I2C support (I2C [=y]) │
│ -> I2C Hardware Bus support
I2C(三) linux3.4(内核分析)的更多相关文章
- 《Linux内核分析》第三周 构建一个简单的Linux系统MenuOS
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK THREE ...
- Linux内核分析第三周学习总结:构造一个简单的Linux系统MenuOS
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.Linux内 ...
- Linux内核及分析 第三周 Linux内核的启动过程
实验过程: 打开shell终端,执行以下命令: cd LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootf ...
- linux内核分析第三周
20135103王海宁 linux内核分析第三周 http://mooc.study.163.com/course/USTC-1000029000 按照课堂提供的方法,命令行一行行敲上去,我是手机缓 ...
- 《Linux内核分析》第三周学习报告
<Linux内核分析>第三周学习报告 ——构造一个简单的Linux系统MenuOS 姓名:王玮怡 学号:201351 ...
- “Linux内核分析”实验三报告
构造一个简单的Linux系统 张文俊+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10000290 ...
- 《Linux内核分析》第三周学习小结 构造一个简单的Linux系统OS
郝智宇 无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第三周 构造一个简单的Linux系统Me ...
- Linux内核分析 实验三:跟踪分析Linux内核的启动过程
贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...
- 【MOOC EXP】Linux内核分析实验三报告
程涵 原创博客 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 [跟踪分析Linux内核的启动过程] ...
随机推荐
- Android 跳转权限设置界面的终极方案
转载请标明出处,维权必究:https://www.cnblogs.com/tangZH/p/10338377.html 有时候APP需要获取某些权限,要让用户手动打开,那么问题来了,若是直接提醒用户出 ...
- ThinkPad 安装 Ubuntu 18.10 系统 -- 高分屏各项配置与Nvdia独显驱动
索引: 目录索引 一.机器概述 1.屏幕:14'' 2.分辨率:1920*1080 3.显卡:Intel 核显 & Nvidia GeForce 940MX 独显 ,双显卡 4.其它硬件 ...
- ILRuntime官方Demo笔记
调用/执行 热更中的方法 调用热更代码中方法,写在AppDomain中,记录一下主要几个方法: AppDomain.LoadAssembly 加载热更dll 执行热更代码的方法,有两种方式: appd ...
- SSIS中xml的输入输出
输出为XML的两种方法 1.用数据流, 将平面文件作为DES输出 在SQL里将要输出的数据查询成为单列的字符串: SELECT (SELECT * FROM A FOR XML ROOT('A'),E ...
- mysql的进阶
老师的博客:http://www.cnblogs.com/wupeiqi/articles/5713323.html 总结 导出与导入 导出:mysqldump -u root -p 数据库 > ...
- 进行Spark,Kafka针对Kerberos相关配置
1. 提交任务的命令 spark-submit \--class <classname> \--master yarn \--deploy-mode client \--executor- ...
- Hybrid APP之Native和H5页面交互原理
Hybrid APP之Native和H5页面交互原理 Hybrid APP的关键是原生页面与H5页面直接的交互,如下图,痛过JSBridge,H5页面可以调用Native的api,Native也可调用 ...
- Django学习笔记(3)--模板
模板 在实际的页面大多是带样式的HTML代码,而模板是一种带有特殊语法的html文件,这个html文件可以被django编译,可以传递参数进去, 实现数据动态化.在编译完成后,生成一个普通的html文 ...
- df、du命令
EXT3 最多只能支持32TB的文件系统和2TB的文件,实际只能容纳2TB的文件系统和16GB的文件 Ext3目前只支持32000个子目录 Ext3文件系统使用32位空间记录块数量和i-节点数量 ...
- RabbitMQ的一些有用教程
最近学习了一些RabbitMQ的知识,现在对所阅读过的一些非常优秀的教程进行总结,感谢各位博主和大神的无私奉献. 一.原理篇 https://blog.51cto.com/lookingdream/2 ...