历史上的今天
今天是:2024年11月24日(星期日)
2021年11月24日 | i2c驱动程序全面分析,从adapter驱动程序到设备驱动程序
2021-11-24 来源:eefocus
开发板 :mini2440
内核版本:linux2.6.32.2
驱动程序参考:韦东山老师毕业班i2c
内容概括:
1、adapter client 简介
2、adapter 驱动框架
2.1 设备侧
2.2 驱动侧
2.2.1 probe 函数
2.2.1.1 注册adapter
new_device del_device
board_info
i2c_detect
i2c_new_device
3、i2c 设备驱动框架
3.1 i2c_bus_type
3.2 i2c_driver
3.3 i2c_device
4、写设备驱动程序
5、写adapter驱动程序
1、adapter client 简介
在内核里,i2c 驱动框架大概分为两层,adapter 驱动 和 设备驱动,adapter 英文翻译过来为 “适配器”,适配器并不恰当,根据我的理解,adapter 指的是我们 mcu 里的 i2c 控制模块,就是那堆寄存器,因为一个 mcu 里的i2c控制模块是固定的(寄存器参数、以及收发数据的方法),因此大多数情况下,它们都有芯片厂商写好了,然而我们学习的过程中自己动手写一写也并不困难。对于s3c2440仅仅有一个i2c_adapter,但是别的Mcu可能有多个。至于Client,它对应于muc外围的I2c设备,每一个i2c设备都由一个唯一的client来描述。

struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
};
简单扫一眼,i2c_adapter 封装了 struct device ,因此它是作为一个设备注册到内核中去的(稍后我们会知道,它是注册到i2c_bus_type里),此外非常重要的一个成员struct i2c_algorithm *algo ,这就是我们上边提到的 i2c 控制器收发数据的方法。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
master_xfer 对应于i2c协议子集 smbus ,有些设备只支持这个协议
smbus_xfer 对应于普通的 i2c 传输协议
functionality 用来描述,adapter 所具有的功能,比如是否支持 smbus
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;
};
i2c_client 本质上是一个 i2c_"dev", 它包含了与它配对的 driver ,以及它所在的 adapter(i2c设备在物理连接上,连接到了哪个adapter),后面分析时会看到,它也是作为设备注册到i2c_bus_type
2、adapter 驱动框架
在我所使用的这个内核里,2440的i2c_adapter框架是基于 platform_bus_type 的,关于 platform_bus_type 别的文章已经分析过了,这里不做赘述,只简单提一下,当设备或驱动注册到 platform_bus_type 时,首先会查找驱动是否有id_table,如果有根据id_table进行匹配(就是看id_table里有无设备的名字),否则匹配设备名字和驱动名字。匹配成功则调用驱动里的probe函数。
2.1 设备侧
根据设备总线驱动模型的分层思想,将一个驱动程序分为 device 和 driver 两层,那么 device 里提供底层的硬件资源,在 driver 中取出这些资源进行使用。那么我们就可以猜测到 i2c_adapter 驱动的设备侧 至少应该含有哪些资源?
1、存器地址必须有吧,因为我们要使用这些寄存器,不然怎么传输。
2、中断必须有吧,i2c传输过程中可是离不开中断的。
下面,我们就开详细的看一看,i2c_adapter 驱动的设备侧提供了哪些设备资源。
mach-smdk2410.c (archarmmach-s3c2410) 中定义了个指针数组,这里面有我们想要的 s3c_device_i2c0
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
};
dev-i2c0.c (archarmplat-s3c)
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC1,
.end = S3C_PA_IIC1 + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC1,
.end = IRQ_IIC1,
.flags = IORESOURCE_IRQ,
},
};
是不是正如我们所料,在资源文件中提供了 物理寄存器 以及 中断资源。
Mach-smdk2410.c (archarmmach-s3c2410),将 s3c_device_i2c0 注册到 平台设备总线上去
static void __init smdk2410_init(void)
{
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
smdk_machine_init();
}
dev-i2c0.c (archarmplat-s3c)
static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
.flags = 0,
.slave_addr = 0x10,
.frequency = 100*1000,
.sda_delay = 100,
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd)
pd = &default_i2c_data0;
npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform datan", __func__);
else if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;
s3c_device_i2c0.dev.platform_data = npd;
}
setup-i2c.c (archarmplat-s3c24xx)
void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);
s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);
}
S3c244x.c (archarmplat-s3c24xx)
void __init s3c244x_map_io(void)
{
/* register our io-tables */
iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));
/* rename any peripherals used differing from the s3c2410 */
s3c_device_sdi.name = "s3c2440-sdi";
s3c_device_i2c0.name = "s3c2440-i2c";
s3c_device_nand.name = "s3c2440-nand";
s3c_device_usbgadget.name = "s3c2440-usbgadget";
}
在将 s3c_device_i2c0 注册到 平台设备总线上去之前,还提供了以上的其它信息,包括i2c控制器作为从机的默认slave_addr等,以及引脚的配置函数。注意,s3c_device_i2c0.name = "s3c2440-i2c";
2.2 驱动侧
驱动侧的工作大概是取出设备侧的资源进行利用,比如Ioremap,配置寄存器,注册中断等等
i2c-s3c2410.c (driversi2cbusses)
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,
},
};
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data = TYPE_S3C2440,
}, { },
};
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);
我们在分析platform总线模型的时候,我们知道platform_bus_type->match函数是首先根据 driver->id_table 来进行匹配device的,前面讲了s3c_device_i2c0.name = "s3c2440-i2c",因此,匹配成功会调用 s3c24xx_i2c_driver->probe 函数,也就是 s3c24xx_i2c_probe ,它是个重点。
2.3 probe 函数分析
i2c-s3c2410.c (driversi2cbusses)
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata;
struct resource *res;
int ret;
// 还记得,我们在 device 里放的platform_data么,是时候取出来使用了
pdata = pdev->dev.platform_data;
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
/* 1、使能 i2c 时钟 */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
clk_enable(i2c->clk);
/* 2、io内存映射 */
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));
/* 3、设置adap的相关信息 */
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; // i2c控制器的收发函数
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* 4、初始化 i2c controller */
ret = s3c24xx_i2c_init(i2c);
i2c->irq = ret = platform_get_irq(pdev, 0);
/* 5、注册中断 */
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
ret = s3c24xx_i2c_register_cpufreq(i2c);
/* 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.
*/
/* 6、适配器编号 */
i2c->adap.nr = pdata->bus_num; //阅读上面的英文,大概意思就是device侧pdata中没设置bus_num,那么就默认为0,显然这里是0
/* 7、注册 adapter */
ret = i2c_add_numbered_adapter(&i2c->adap); // i2c_register_adapter(&i2c->adap);
platform_set_drvdata(pdev, i2c);
return 0;
}
i2c-core.c (driversi2c)
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0, dummy;
mutex_init(&adap->bus_lock);
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
/* 设置 adap->dev.kobj.name 为 i2c-0 ,它将出现在 sysfs 中 */
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
/* 设置它所属的总线 i2c_bus_type */
adap->dev.bus = &i2c_bus_type;
/* 重点: 设置属性,用户空间创建 device 就靠它了 */
adap->dev.type = &i2c_adapter_type;
/* 将 adap->dev注册到 i2c_bus_type */
res = device_register(&adap->dev);
/* 大概是创建 devices 目录 到class 目录的符号连接 */
#ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class linkn");
#endif
/* 重点: 扫描 __i2c_board_list 链表里的设备信息,自动创建 client ,并注册到 i2c_bus_type */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
/* 重点: 遍历 i2c_bus_type的driver 链表,取出每一个driver 调用 i2c_do_add_adapter */
mutex_lock(&core_lock);
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
i2c_do_add_adapter);
史海拾趣
|
深圳赛格导航 深圳市赛格导航科技股份有限公司是国内GPS车载监控产品应用领域的开拓者,从 94年开始研究和应用GPS 技术,具有资深的行业经验。产品与服务包括面向国内和海外的汽车GPS定位监控系统设备、物流GPS监控、车 ...… 查看全部问答> |
|
为什么GPIO中有的Pin要设为Pull_Up/Pull_Down? 如题,其中有的pin为输入pin,有的pin为输出pin,但是为什么要设置一些pin的属性为pull up或者为pull down,pull up/pull down到底是干吗用的?根据什么来设的呢,聆听各位大虾的教诲!!… 查看全部问答> |
|
为什么VS2005开发Win CE程序连接Access数据库,可是却没有System.Data.OleDb类库? 我用VS2005开发Win CE程序,连接Access数据库,可是却没有System.Data.OleDb类库,只有System.Data.Common System.Data.SqlTypes 这两个类库,怎么回事啊???? 只好用System.Data.Common.DbConnection连接,可是出现一下问题: DbConnection myCo ...… 查看全部问答> |
|
本帖最后由 dontium 于 2015-1-23 13:19 编辑 我准备使用UCC39002与三个D/C D/C模块实现均流,可是TI官方给出的PDF中没有的实际应用,只是告诉参考资料为TI文献NO.SLUA270中UCC39002与PH-100S4 模块,可是PH-100S4又没法网络上查 ...… 查看全部问答> |
|
也学“下载到FLASH”里------------F28035 C2000 我的F28035在RAM中运行正常,下到FLASH里就不运行了。 慢慢再看它的资料吧 [ 本帖最后由 dontium 于 2012-2-14 16:26 编辑 ]… 查看全部问答> |
|
看《零死角玩转STM32》的初级篇中的流水灯,根据他的步骤写完程序,烧进去,结果灯全亮,不闪,后来干脆不高延时,改成如下所示还是全亮; LED1( ON ); // 亮 LED1 ...… 查看全部问答> |




