前言
在正式介紹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