引言
I2C裝置驅動是I2C架構中最接近應用層的,其上接應用層,下接I2C核心。也是驅動開發人員需要實作的代碼,在此驅動中我們隻需負責以下步驟(以ap3216c為例):
a. 新增硬體資訊(裝置樹)
b. 搭建驅動架構
c. 建構i2c_driver,并注冊到linux i2c中
d. 注冊字元裝置
e. 向應用層提供i2c裝置操作接口
f. 登出i2c裝置
本篇文章會按照以上六個階段展開解析。
流程解析
a. 新增硬體資訊裝置樹(裝置樹)
首先觀察硬體i2c裝置挂載到哪個i2c總線上,然後在裝置樹檔案找到該總線的裝置節點,在節點下建立子節點描述i2c裝置硬體資訊即可。
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "100ask,ap3216c";
reg = <0x1e>;
};
};
複制
b. 搭建驅動架構
所謂搭建驅動架構,無非就是字元驅動将驅動入口、出口、以及對應用層的接口實作。與其他字元驅動的搭建是一樣的。
c. 建構i2c_driver,并注冊到linux
首先先看需要建構的i2c_driver結構體原型:
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
複制
注: i2c_driver類似于platform_driver,在i2c_driver注冊到核心且名稱與裝置樹比對一緻就會進入probe中,在要解除安裝該驅動時會進入remove中。是以要填充i2c_driver的入口函數probe、出口函數(remove)和用于比對的硬體資訊driver。
static struct i2c_driver ap3216c_device_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.name = PLATFORM_NAME,
.owner = THIS_MODULE,
.of_match_table = ap3216c_table,
},
.id_table = ap3216c_id,
};
複制
d. 注冊i2c裝置
static int __init ap3216c_init(void)
{
int ret = 0;
printk("%s:%d: Entry %s \r\n", __FILE__, __LINE__, __func__);
ret = i2c_add_driver(&ap3216c_device_driver);
return ret;
}
複制
注冊i2c裝置很簡單,隻需要在初始化中調用Linux提供的宏i2c_add_driver 即可。但是i2c_add_driver具體如何實作,有必要了解一下:
首先,這個宏調用了i2c_register_driver:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
複制
再大緻了解i2c_register_driver函數流程:
--- drivers --- i2c_core.c --- i2c_register_driver( --- driver->driver.bus = &i2c_bus_type
| struct module *owner, |- INIT_LIST_HEAD(&driver->clients)
| struct i2c_driver *driver) |- driver_register(&driver->driver)
| |- i2c_for_each_dev(driver, __process_new_driver)
|- i2c_for_each_dev( --- bus_for_each_dev(&i2c_bus_type, NULL, data, fn)
| void *data,
| int (*fn)(struct device *, void *))
|- __process_new_driver( --- if (dev->type != &i2c_adapter_type)
struct device *dev, | return 0;
void *data) |- i2c_do_add_adapter(data, to_i2c_adapter(dev));
複制
小結:
從上圖流程來看,在将i2c結構體注冊進核心時,調用了屬于i2c核心的i2c_register_driver。進入i2c核心中,會将i2c結構體添加到i2c連結清單中,并實作i2c_client與i2c_driver的比對,比對成功會進入i2c_driver 結構體的probe函數中。(具體實作放在I2C核心文章分析)
e. 向應用層提供i2c裝置操作接口
成功進入probe函數後,就說明i2c驅動配置基本成功。接下來在probe中需要實作字元驅動的注冊,以及實作對外的讀寫接口。字元驅動的注冊代碼,與其他字元驅動是一緻的,浏覽代碼實作即可。主要分析對外接口的讀寫i2c裝置操作:
在單片機的程式中,實作對i2c裝置的讀寫,需要手動實作讀寫i2c寄存器,或者通過GPIO模拟i2c時序與i2c裝置通信。而在Linux中,如何與i2c裝置的具體通信已經被封裝成固定的API,在程式中填充這些API的資料參數調用即可,列舉讀寫單個位元組的實作:
static int ap3216c_read_regs(struct sap3216c_dev *dev, unsigned char reg,
void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = val;
msg[1].len = len;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret ==2 ) {
ret = 0;
} else {
printk("%s %d: i2c transfer error!\n ", __func__, __LINE__);
ret = -EREMOTEIO;
}
return ret;
}
static int ap3216c_write_regs(struct sap3216c_dev *dev, unsigned char reg,
unsigned char *buf, unsigned char len)
{
unsigned char temp_buf[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
temp_buf[0] = reg;
memcpy(&temp_buf[1], buf, len);
msg.addr = client->addr;
msg.flags = 0;
msg.buf = temp_buf;
msg.len = len+1;
return i2c_transfer(client->adapter, &msg, 1);
}
複制
小結:
由以上代碼發現,在與i2c裝置的讀寫通信中,都是通過調用i2c_transfer實作。
i2c_transfer三個參數意義 :
(1) client->adapter: 該i2c裝置連接配接的i2c總線擴充卡;
(2) msg:需要發送的資料;
(3) 1:需要發送的msg個數。
通過以上讀寫的實作,與上一篇文章 《I2C總線架構 之 I2C協定》 讀寫時序是對應的:
(1) 寫操作隻需要一個msg結構體:
- 起始位 + 寫操作(msg[0]) + 停止位。
(2) 讀操作需要兩個msg結構體 :
- 起始位+ 寫操作(寫入位址 msg[0])+ 起始位 + 讀操作(存入msg[1]) + 停止位。
f. 登出i2c裝置
登出操作:在字元驅動出口函數中,解除安裝掉注冊的i2c裝置。這裡調用i2c_del_driver即可實作,與i2c_add_driver是對應的。
static void __exit ap3216c_exit(void)
{
printk("%s:%d: Entry %s \r\n", __FILE__, __LINE__, __func__);
i2c_del_driver(&ap3216c_device_driver);
}
複制
總結
到這裡本篇文章對i2c裝置驅動的具體分析基本完成。本篇以ap3216c光敏傳感器代碼為例,從入口到出口代碼走向展開分析。通讀文章大緻了解,會發現本篇i2c裝置驅動與虛拟總線platform架構類似。不同的是platform是軟體實作的虛拟總線,在soc上并不存在;而i2c總線,在soc上是實際存在的。相同的是兩者實作将驅動分層為硬體參數和驅動抽象,在注冊時周遊比對,然後進入正文probe中!
由于Linux内部的實作較為複雜,本篇主要以裝置驅動的角度來分析整個驅動的代碼走向,涉及到内部API的實作,本篇隻大概介紹其功能,剩餘部分會放在i2c核心繼續分析。
參考:
《Linux裝置驅動開發詳解》
《【正點原子】I.MX6U嵌入式Linux驅動開發指南V1.4》
https://blog.csdn.net/Egean/article/details/81085077