S3C2440内核IIC设备驱动建立流程:
由内核打印信息,追索驱动流程:
参考的内核版本为: linux-2.6.22.6
----------------------------------------
内核注册流程:
由打印信息:
1. 先注册 i2c_driver:i2cdev_driver --- \drivers\i2c\i2c-dev.c
前面分析过,内核启动初始化会执行函数:i2c_dev_init
i2c_dev_init(void)
register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); // 注册字符设备,主设备号为:I2C_MAJOR,应用层统一的操作接口为 i2cdev_fops
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); // 构建一个class:i2c_dev_class
i2c_add_driver(&i2cdev_driver);
i2c_register_driver(THIS_MODULE, driver);
driver_register(&driver->driver);
list_add_tail(&driver->list,&drivers); // 这里将 i2cdev_driver 挂接到 drivers 链表上
// 遍历adapters链表中的adapter,调用函数 i2cdev_attach_adapter(adapter)
if (driver->attach_adapter) { // 这里 driver->attach_adapter = i2cdev_attach_adapter
struct i2c_adapter *adapter;
// 在系统初始化的时候,adapters链表上,还没有初始化adapter元素,所以找不到adapter
list_for_each_entry(adapter, &adapters, list) {
driver->attach_adapter(adapter); // 初始化的时候,找不到adapter,所以该句不会执行。
}
}
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
.id = I2C_DRIVERID_I2CDEV,
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
.detach_client = i2cdev_detach_client,
};
注:
这个代码是linux内核做好的框架,内核启动,就会注册一个i2c字符设备以及类,i2c-driver链表。
并且为这个i2c字符设备提供了统一的应用层的操作接口函数。
打印信息:
i2c /dev entries driver
i2c_dev_init, i2c_add_driver: i2cdev_driver
-------------------------------------------------
2. 然后注册i2c_adapter: s3c24xx_i2c.adap --- \drivers\i2c\busses\i2c-s3c2410.c
注:这里就是针对不同的Soc注册i2c adapter,adapter是针对硬件i2c设备来说的。
在S3C2440中,使用平台总线驱动来注册I2C设备的。
2.1 初始化注册i2c平台设备资源:
linux-2.6.22.6\arch\arm\plat-s3c24xx\devs.c
在这个文件中定义了i2c设备资源:
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
linux-2.6.22.6\arch\arm\mach-s3c2440\mach-smdk2440.c
在这个文件中注册i2c平台设备:
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&s3c2440_device_sdi,
};
// 初始化函数
static void __init smdk2440_machine_init(void)
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
platform_device_register(devs[i]); // 这里真正注册 s3c_device_i2c 设备
2.2 初始化注册i2c平台驱动:
linux-2.6.22.6\drivers\i2c\busses\i2c-s3c2410.c
// 注册i2c平台驱动:
static int __init i2c_adap_s3c_init(void)
platform_driver_register(&s3c2440_i2c_driver);
// s3c2440的i2c平台设备驱动结构体:
static struct platform_driver s3c2440_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2440-i2c",
},
};
而这里要问:那么 i2c_adapter 在哪里设置的呢?
回答:
当初始化i2c平台驱动程序时,就会去找与平台驱动同名的平台设备,如果找到匹配的设备,则会调用
平台驱动中定义的probe函数。
在这个probe函数中,将会获取该SoC的i2c设备的相关资源(也就是前面定义的i2c平台设备资源),并且构造一个
i2c_adapter 结构体,设置填充,最后调用函数i2c_add_adapter(该i2c_adapter结构体),向系统注册。
// 平台设备驱动:注册driver的时候,当找到匹配的设备时,调用driver里面的probe函数
s3c24xx_i2c_probe(struct platform_device *pdev)
struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
s3c24xx_i2c_init(i2c); // 初始化IIC控制器
i2c_add_adapter(&i2c->adap);
i2c_register_adapter(adapter);
list_add_tail(&adap->list, &adapters); // 将该adapter挂接到adapters链表上
// 遍历drivers链表中的i2c_driver,调用函数:driver->attach_adapter
list_for_each(item,&drivers) {
driver = list_entry(item, struct i2c_driver, list);
if (driver->attach_adapter)
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap); // 这里由于前面内核启动的时候,初始化了一个 i2cdev_driver 结构体
// 所以这里将执行: i2cdev_attach_adapter(adap)
i2cdev_attach_adapter(注册的adapter结构体)
// 在类 i2c_dev_class 下面注册一个device: i2c_dev->dev,
// 在应用层的文件为: /dev/i2c-0,/dev/i2c-1,... /dev/i2c-n
// 这个类: i2c_dev_class 也是在内核启动的时候注册的
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr),
"i2c-%d", adap->nr);
}
// 结构体:i2c_adapter,这里用结构体 struct s3c24xx_i2c 做了一层封装:
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
// 这里才是真正的 i2c_adapter
.adap = {
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm, // 这里定义了i2c设备通过I2C接口如何发送,接收数据,
// 至于数据啥意思,它不管。
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};
打印信息:
cnt: 1, i2c_adapter name: s3c2410-i2c
-------------------------------------------
3. 进一步解释:
当注册 i2c_adapter: s3c24xx_i2c.adap 的时候,就会遍历drivers链表,把drivers链表中的 i2c_driver
一个一个取出来,然后调用 i2c_driver.attach_adapter(adapt)函数。
因为前面只注册了一个 i2c_driver结构体: i2cdev_driver,所以就将调用函数:
i2cdev_driver.attach_adapter(adapter) = i2cdev_attach_adapter(s3c24xx_i2c.adap)
i2cdev_attach_adapter(s3c24xx_i2c.adap)
device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), "i2c-%d", adap->nr);
这个函数前面已经分析了,将会创建设备文件:"/dev/i2c-0"
所以当内核启动后,可以查看是否建立的设备文件:
# ls /dev/i2c* -l
crw-rw---- 1 0 0 89, 0 Jan 1 00:00 /dev/i2c-0
可以看到,主设备号为89,次设备号为0,建立了设备文件: "/dev/i2c-0"
内核打印信息:
cnt: 1, i2cdev_attach_adapter,adap.name:s3c2410-i2c
cnt: 1, i2c_register_driver, i2c_driver->name: dev_driver
4. 问题:上面只是生成了一个/dev/i2c-0设备文件,那真正的IIC从设备如何注册驱动呢?
回答:
可以参考例子程序:linux-2.6.22.6\drivers\i2c\chips\ds1374.c
static int __init ds1374_init(void)
i2c_add_driver(&ds1374_driver)
i2c_register_driver(THIS_MODULE, driver);
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
driver_register(&driver->driver);
// 将注册的 i2c_driver 挂接到drivers链表上
list_add_tail(&driver->list,&drivers);
// 遍历adapters链表下的 adapter 结构体,调用新注册的 i2c_driver 下面的attach_adapter函数
if (driver->attach_adapter) {
struct i2c_adapter *adapter;
list_for_each_entry(adapter, &adapters, list) {
driver->attach_adapter(adapter); // 这里函数 attach_adapter = ds1374_attach
// adapter 就是之前初始化注册的 s3c24xx_i2c.adap
// 相当于执行函数:
ds1374_attach(s3c24xx_i2c.adap)
i2c_probe(s3c24xx_i2c.adap, &addr_data, ds1374_probe);
// 在这里才真正的和底层设备扯上关系了
i2c_probe_address(s3c24xx_i2c.adap, 从设备的IIC地址, -1, 传递进来的函数 ds1374_probe );
i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
// 1. 探测是否真正存在该地址的IIC从设备
i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
// 2. 如果存在该IIC从设备,则调用真正的IIC从设备probe函数,
// 这个函数是传递进来的: ds1374_probe
found_proc(adapter, addr, kind);
ds1374_probe(struct i2c_adapter *adap, int addr, int kind)
// 2.1 建立一个i2c_client结构体,这个就表示一个外部的IIC从设备
// 可以看到这个client结构体才是真正的操作源头。
struct i2c_client *client;
client->addr = addr;
client->adapter = adap;
client->driver = &ds1374_driver;
i2c_attach_client(client);
// 2.2 将这个i2c_client结构体,挂接到该 i2c_adapter 的clients链表下面
list_add_tail(&client->list,&adapter->clients);
client->dev.bus = &i2c_bus_type;
device_register(&client->dev);
}
}
static struct i2c_driver ds1374_driver = {
.driver = {
.name = DS1374_DRV_NAME,
},
.id = I2C_DRIVERID_DS1374,
.attach_adapter = ds1374_attach,
.detach_client = ds1374_detach,
};
总结:
当注册 i2c_driver 到内核时,
(1)会从adapters链表下取出一个个adapter,
(2)然后调用新注册i2c_driver结构体总定义的 attach_adapter 函数,
(3)在这个 attach_adapter 函数中,会调用内核定义的统一的接口函数: i2c_probe
i2c_probe函数原型:
int i2c_probe(struct i2c_adapter *adapter, struct i2c_client_address_data *address_data,
int (*found_proc) (struct i2c_adapter *, int, int))
(4)在i2c_probe函数中,做两件事:
a. 探测该IIC从设备是否真的存在,这是有内核定义的框架来自动probe的;
b. 传递给 i2c_probe 函数的参数中带有一个真正的 IIC从设备的probe函数,在这一步得到调用
在这个真正的IIC从设备probe函数中,将建立一个I2c_client结构体,这个结构体就是IIC从设备
和外界进行IIC通信的载体(各种不同的操作:比如读写IIC从设备)。
5. 总结如何编写i2c驱动
5.1 构造一个i2c_driver结构体
该结构体包括函数:
attach_adapter
detach_client
5.2 注册i2c_driver结构体
当向内核注册i2c_driver,将会调用该结构体下面的attach_adapter函数
5.3 所以当内核注册i2c_driver,会先探测IIC设备时候存在,如果存在,则调用attach_adapter函数来probe设备,来建立某种联系:
5.3.1 在attach_adapter函数中,调用统一的函数接口:i2c_probe(adapter, 设备地址, "设备真正的probe函数");
5.3.2 在"设备真正的probe函数"中,构造一个:struct i2c_client结构体:
该结构体一端连着"adapter",一端连着"i2c_driver",并保存IIC设备地址;最后调用函数 i2c_attach_client
将该client注册到内核的 i2c_bus_type 上。
5.3.3 运行到这里,表示已经找到IIC设备,并且已经利用client建立了某种联系,就可以创建与IIC设备对应的字符
设备文件,这个创建过程和前面讲的建立字符设备驱动一样。以后IIC设备文件的读写等操作,都是借助于client
这个桥梁。
注:
5.3.3 这一步在有的driver里面并没有做。注意在源代码中查看。
阅读(1357) | 评论(0) | 转发(0) |