前言
在正式介绍I2C驱动框架之前,我们先了解一些基础知识。内核有两种i2c驱动程序的编写方式。分别称这两种方式为“Adapter方式(LEGACY)”和“Probe方式(new style)”。但是legacy的方式已经过时了,较新的内核版本已经无法编译通过,之前也写过legacy方式的驱动,可以参考下面的连接:I2C驱动程序。本文是介绍new style的编写方式。
正文
1、struct i2c_driver
static const struct i2c_device_id stk3420_ids[] = {
{IR_STK_DEV_BASENAME, 0},
{/*end of list*/}
};
MODULE_DEVICE_TABLE(i2c, stk3420_ids); /* 第一个参数和xxx_device_id相关 */
static struct of_device_id stk3420_dt_ids[] = {
{ .compatible = "sensortek,stk3420" },
{ }
};
static struct i2c_driver stk3420_i2c_driver = {
.driver = {
.name = STK3420_DRV_NAME,
#ifdef CONFIG_PM
.pm = &stk3420_i2c_pm,
#endif
.of_match_table = of_match_ptr(stk3420_dt_ids),
},
.probe = stk3420_i2c_probe,
.remove = stk3420_i2c_remove,
.id_table = stk3420_ids,
};
首先定义一个i2c_driver结构体,包含几个重要的点:
(1).driver
1).name:定义了这个驱动的名称,在驱动注册进总线的阶段会用到,如果有同名的驱动就不会重复注册了
2).pm:电源管理相关的,主要定义了suspend和resume函数,后面我们再详细介绍
3).of_match_table:用于匹配对应的device,也就是匹配dts中的节点
(2).probe
如果找到匹配的device,就会调用probe函数了
(3).remove
卸载驱动后会调用的函数
(4).id_table
在匹配device时,首先会通过of_match_table来检测,如果不匹配或者不存在of_match_table的时候,才会通过id_table来匹配,对应的流程如下,通过调用bus的match函数来匹配:
代码路径:drivers\i2c\i2c-core.c
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv)) /* 通过of_match_table来匹配device */
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table) /* 通过id_table来匹配device */
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
2、i2c_add_driver
static int __init stk3420_init(void)
{
return i2c_add_driver(&stk3420_i2c_driver);
}
定义完struct i2c_driver结构体后,调用i2c_add_driver函数将其注册到I2C总线。i2c_add_driver函数其实也是driver_register的封装,和其他的函数比如platform_driver_register是类似的,这里就不展开了。
3、stk3420_i2c_probe
找到匹配的device后,就会调用driver的probe函数了。
static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, stk3420_show_enable, stk3420_store_enable); /* 省略stk3420_show_enable和stk3420_store_enable函数 */
static struct device_attribute *stk3420_attr_list[] = {
&dev_attr_enable,
};
static int stk3420_i2c_probe(struct i2c_client *client, const struct i2c_device_id *did)
{
struct stk3420_dev_data *dev_data;
dev_data = kzalloc(sizeof(struct stk3420_dev_data), GFP_KERNEL);
if (dev_data == NULL) {
pr_err("Failed to malloc stk3420_dev_data\n");
return -ENOMEM;
}
/*
* 在probe函数中具体做什么,一般看具体实现什么功能,这里我给出一些大部分驱动都要实现的步骤
*/
/*
* 1、解析dts文件中的节点
*/
struct device_node *np = client->dev.of_node;
if (!np) {
dev_err(&client->dev, "no device tree\n");
return -EINVAL;
}
gpio = of_get_named_gpio_flags(np, "power-gpio", 0, &pwr_flags);
if (gpio_is_valid(gpio)) {
dev_data->pwr_pin = of_get_named_gpio_flags(np, "power-gpio", 0, &pwr_flags);
dev_data->pwr_en_level = (pwr_flags == GPIO_ACTIVE_HIGH) ? 1 : 0;
} else {
dev_data->pwr_pin = -1;
}
/*
* 2、一般都需要创建一些节点,方便应用层去操作驱动,那么我们就需要在/sys/class/下创建相关节点
*/
struct class *dev_class;
struct device *ctl_dev;
dev_class = class_create(THIS_MODULE, "gesture");
ctl_dev = device_create(dev_class, NULL, 0, NULL, "control");
if (IS_ERR(ctl_dev)) {
dev_err(ctl_dev, "Failed to create bt char device\n");
ret = PTR_ERR(ctl_dev);
goto err_create_dev;
}
device_create_file(ctl_dev, stk3420_attr_list[0]); /* 这步之后就创建了/sys/class/gesture/control/enable */
/*
* 3、有可能需要对I2C设备做一些初始化的动作,这时候就会涉及到和I2C从设备的数据读写操作
*/
/* 3.1、读操作,读取地址STK3420_STATE_REG的值到buf中 */
u8 buf[1];
stk3420_reg_read(client, STK3420_STATE_REG, buf);
/* 3.2、写操作,将0x00写到寄存器STK3420_STATE_REG中 */
stk3420_reg_write(client, STK3420_STATE_REG, 0x00);
/*
* 其他的操作就根据自己需要的功能进行定制了,比如设置中断函数等等
*/
}
(1)I2C设备具体的读写函数
static int stk3420_reg_read(const struct i2c_client *client, u8 addr, u8 *val)
{
int ret;
struct i2c_msg msg[2];
/*数据传输3要素:源,目的,长度*/
/*在读取从设备的数据前,需要将存储数据的空间的地址告诉它*/
msg[0].addr = client->addr; /* 目的 */
msg[0].flags = client->flags & I2C_M_TEN; /* I2C_M_TEN:this is a ten bit chip address */
msg[0].len = 1; /* 地址 = 1bytes */
msg[0].buf = &addr; /* 源 */
/*然后启动读操作*/
msg[1].addr = client->addr; /* 源 */
msg[1].flags = client->flags & I2C_M_TEN;
msg[1].flags |= I2C_M_RD; /* 读操作 */
msg[1].len = 1; /* 读取的字节数,实际就是val的大小 */
msg[1].buf = val; /* 存储读取到的数据 */
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0)
dev_err(&client->dev, "%s:i2c read error\n", __func__);
return ret;
}
static int stk3420_reg_write(const struct i2c_client *client,
u8 addr, u8 val)
{
int ret;
struct i2c_msg msg;
u8 send[2];
send[0] = addr;
send[1] = val;
/*数据传输3要素:源,目的,长度*/
msg.addr = client->addr; /* 目的 */
msg.flags = client->flags & I2C_M_TEN;
msg.len = 2; /* 地址+数据 = 2bytes,也就是send数组的大小了,如果想传送更多的数据,可以数组的大小可以增加 */
msg.buf = send; /* 源 */
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
dev_err(&client->dev, "%s:i2c write error\n", __func__);
return ret;
}
4、stk3420_i2c_remove
static int stk3420_i2c_remove(struct i2c_client *client)
{
/*
* 销毁函数就根据probe函数中调用了什么,这里就销毁什么,比如我调用了kzalloc,这里就需要kfree
*/
struct stk3420_dev_data *dev_data = i2c_get_clientdata(client);
kfree(dev_data);
return 0;
}
5、stk3420_i2c_pm
电源管理相关的函数,主要是在suspend和resume的时候一些相关的操作
int stk3420_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct stk3420_dev_data *dev_data = i2c_get_clientdata(client);
/* 自定义一些需要在suspend时候的操作 */
return 0;
}
int stk3420_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct stk3420_dev_data *dev_data = i2c_get_clientdata(client);
/* 自定义一些需要在resume时候的操作 */
return 0;
}
static SIMPLE_DEV_PM_OPS(stk3420_i2c_pm, stk3420_suspend, stk3420_resume);
结语
整个框架比较粗糙,省略了许多细节,但是能大概了解I2C设备驱动的框架,再读一份完整的驱动代码时也能有思路。而且展示了I2C设备的读写函数的含义,这里对于数据的传输通过i2c_transfer函数,也就是master_xfer的方式。在一些驱动中我们还会看到通过i2c_smbus_read_byte_data函数来传输数据的,也就是smbus_xfer的方式。这是目前适配器主要支持两种传输方法:smbus_xfer和master_xfer。一般来说,如果设配器支持了master_xfer那么它也可以模拟支持smbus的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。后面我再出一篇文章讲解通过smbus_xfer方式的驱动框架。
参考连接:
https://www.cnblogs.com/simonshi/archive/2011/02/24/1963426.html