Linux I2C核心、总线与设备驱动(二)
從上面的分析可知,雖然I2C硬件體系結構比較簡單,但是I2C體系結構在Linux中的實現卻相當復雜。當工程師拿到實際的電路板,面對復雜的 Linux I2C子系統,應該如何下手寫驅動呢?究竟有哪些是需要親自做的,哪些是內核已經提供的呢?理清這個問題非常有意義,可以使我們面對具體問題時迅速地抓住重點。
??? 一方面,適配器驅動可能是Linux內核本身還不包含的。另一方面,掛接在適配器上的具體設備驅動可能也是Linux不存在的。即便上述設備驅動都存在于Linux內核中,其基于的平臺也可能與我們的電路板不一樣。因此,工程師要實現的主要工作將包括:
?? 提供I2C適配器的硬件驅動,探測、初始化I2C適配器(如申請I2C的I/O地址和中斷號)、驅動CPU控制的I2C適配器從硬件上產生各種信號以及處理I2C中斷等。
?? 提供I2C適配器的algorithm,用具體適配器的xxx_xfer()函數填充i2c_algorithm的master_xfer指針,并把i2c_algorithm指針賦值給i2c_adapter的algo指針。
?? 實現I2C設備驅動與i2c_driver接口,用具體設備yyy的yyy_attach_adapter()函數指針、 yyy_detach_client()函數指針和yyy_command()函數指針的賦值給i2c_driver的attach_adapter、 detach_adapter和detach_client指針。
?? 實現I2C設備驅動的文件操作接口,即實現具體設備yyy的yyy_read()、yyy_write()和yyy_ioctl()函數等。
上述工作中1、2屬于I2C總線驅動,3、4屬于I2C設備驅動,做完這些工作,系統會增加兩個內核模塊。本章第3~4節將詳細分析這些工作的實施方法,給出設計模板,而5~6節將給出兩個具體的實例。
15.2 Linux I2C核心
I2C核心(drivers/i2c/i2c-core.c)中提供了一組不依賴于硬件平臺的接口函數,這個文件一般不需要被工程師修改,但是理解其中的主要函數非常關鍵,因為I2C總線驅動和設備驅動之間依賴于I2C核心作為紐帶。I2C核心中的主要函數包括:
?? 增加/刪除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);
?? 增加/刪除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
?? i2c_client依附/脫離
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);
當一個具體的client被偵測到并被關聯的時候,設備和sysfs文件將被注冊。相反地,在client被取消關聯的時候,sysfs文件和設備也被注銷,如代碼清單15.6。
代碼清單15.6 I2C核心client attach/detach函數
1? int i2c_attach_client(struct i2c_client *client)
2? {
3??? ...
4? ?device_register(&client->dev);
5? ?device_create_file(&client->dev, &dev_attr_client_name);
6? ?
7? ?return 0;
8? }
9?
10 int i2c_detach_client(struct i2c_client *client)
11 {
12?? ...
13 ?device_remove_file(&client->dev, &dev_attr_client_name);
14 ?device_unregister(&client->dev);
15?? ...
16 }
(4)i2c傳輸、發送和接收
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
i2c_transfer ()函數用于進行I2C適配器和I2C設備之間的一組消息交互,i2c_master_send()函數和i2c_master_recv()函數內部會調用i2c_transfer()函數分別完成一條寫消息和一條讀消息,如代碼清單15.7、15.8。
代碼清單15.7 I2C核心i2c_master_send函數
1? int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
2? {
3? ?int ret;
4? ?struct i2c_adapter *adap=client->adapter;
5? ?struct i2c_msg msg;
6??? /*構造一個寫消息*/
7? ?msg.addr = client->addr;
8? ?msg.flags = client->flags & I2C_M_TEN;
9? ?msg.len = count;
10 ?msg.buf = (char *)buf;
11 ?/*傳輸消息*/
12 ?ret = i2c_transfer(adap, &msg, 1);
13
14 ?return (ret == 1) ? count : ret;
15 }
代碼清單15.8 I2C核心i 2c_master_recv函數
1? int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
2? {
3? ?struct i2c_adapter *adap=client->adapter;
4? ?struct i2c_msg msg;
5? ?int ret;
6?? /*構造一個讀消息*/
7? ?msg.addr = client->addr;
8? ?msg.flags = client->flags & I2C_M_TEN;
9? ?msg.flags |= I2C_M_RD;
10 ?msg.len = count;
11 ?msg.buf = buf;
12? /*傳輸消息*/
13 ?ret = i2c_transfer(adap, &msg, 1);
14
15 ?/* 成功(1條消息被處理), 返回讀的字節數 */
16 ?return (ret == 1) ? count : ret;
17 }
i2c_transfer()函數本身不具備驅動適配器物理硬件完成消息交互的能力,它只是尋找到i2c_adapter對應的i2c_algorithm,并使用i2c_algorithm的master_xfer()函數真正驅動硬件流程,如代碼清單15.9。
代碼清單15.9 I2C核心i 2c_transfer函數
1? int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
2? {
3? ?int ret;
4?
5? ?if (adap->algo->master_xfer) {
6? ??down(&adap->bus_lock);
7? ??ret = adap->algo->master_xfer(adap,msgs,num); /* 消息傳輸 */
8? ??up(&adap->bus_lock);
9? ??return ret;
10 ?} else {
11 ??dev_dbg(&adap->dev, "I2C level transfers not supported\n");
12 ??return -ENOSYS;
13 ?}
14 }
(5)I2C控制命令分派
下面函數有助于將發給I2C適配器設備文件ioctl的命令分派給對應適配器的algorithm的algo_control()函數或i2c_driver的command()函數:
int i2c_control(struct i2c_client *client,?unsigned int cmd, unsigned long arg);
void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg);
15.3 Linux I2C總線驅動
15.3.1 I2C適配器驅動加載與卸載
I2C總線驅動模塊的加載函數要完成兩個工作:
?? 初始化I2C適配器所使用的硬件資源,申請I/O地址、中斷號等。
?? 通過i2c_add_adapter()添加i2c_adapter的數據結構,當然這個i2c_adapter數據結構的成員已經被xxx適配器的相應函數指針所初始化。
I2C總線驅動模塊的卸載函數要完成的工作與加載函數的相反:
?? 釋放I2C適配器所使用的硬件資源,釋放I/O地址、中斷號等。
?? 通過i2c_del_adapter()刪除i2c_adapter的數據結構。
代碼清單15.10給出了I2C適配器驅動模塊加載和卸載函數的模板。
代碼清單15.10 I2C總線驅動模塊加載和卸載函數模板
1? static int __init i2c_adapter_xxx_init(void)
2? {
3??? xxx_adpater_hw_init();
4??? i2c_add_adapter(&xxx_adapter);
5? }
6?
7? static void __exit i2c_adapter_xxx_exit(void)
8? {
9??? xxx_adpater_hw_free();
10?? i2c_del_adapter(&xxx_adapter);
11 }
上述代碼中xxx_adpater_hw_init()和xxx_adpater_hw_free()函數的實現都與具體的CPU和I2C設備硬件直接相關。
15.3.2 I2C總線通信方法
我們需要為特定的I2C適配器實現其通信方法,主要實現i2c_algorithm的master_xfer()函數和functionality()函數。
functionality ()函數非常簡單,用于返回algorithm所支持的通信協議,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、 I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。
master_xfer()函數在I2C適配器上完成傳遞給它的i2c_msg數組中的每個I2C消息,代碼清單15.11給出了xxx設備的master_xfer()函數模板。
代碼清單15.11 I2C總線驅動master_xfer函數模板
1? static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
2??? int num)
3? {
4??? ...
5??? for (i = 0; i < num; i++)
6??? {
7????? i2c_adapter_xxx_start(); /*產生開始位*/
8????? /*是讀消息*/
9????? if (msgs[i]->flags &I2C_M_RD)
10???? {
11?????? i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*發送從設備讀地址*/
12?????? i2c_adapter_xxx_wait_ack(); /*獲得從設備的ack*/
13?????? i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /*讀取msgs[i]
14???????? ->len長的數據到msgs[i]->buf*/
15???? }
16???? else
17????? /*是寫消息*/
18???? {
19?????? i2c_adapter_xxx_setaddr(msg->addr << 1); /*發送從設備寫地址*/
20?????? i2c_adapter_xxx_wait_ack(); /*獲得從設備的ack*/
21?????? i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /*讀取msgs[i]
22???????? ->len長的數據到msgs[i]->buf*/
23???? }
24?? }
25?? i2c_adapter_xxx_stop(); /*產生停止位*/
26 }
上 述代碼實際上給出了一個master_xfer()函數處理I2C消息數組的流程,對于數組中的每個消息,判斷消息類型,若為讀消息,則賦從設備地址為 (msg->addr << 1) | 1,否則為msg->addr << 1。對每個消息產生1個開始位,緊接著傳送從設備地址,然后開始數據的發送或接收,對最后的消息還需產生1個停止位。圖15.3描述了整個 master_xfer()完成的時序。
圖15.3 algorithm中master_xfer的時序
master_xfer()函 數模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、 i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、 i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函數用于完成適配器的底層硬件操作,與I2C 適配器和CPU的具體硬件直接相關,需要由工程師根據芯片的數據手冊來實現。
i2c_adapter_xxx_readbytes()用于從從設備上接收一串數據,i2c_adapter_xxx_writebytes()用于向從設備寫入一串數據,這兩個函數的內部也會涉及到I2C總線協議中的ACK應答。
master_xfer ()函數的實現在形式上會很多樣,即便是Linux內核源代碼中已經給出的一些I2C總線驅動的master_xfer()函數,由于由不同的組織或個人 完成,風格上的差別也非常大,不一定能與模板完全對應,如master_xfer()函數模板給出的消息處理是順序進行的,而有的驅動以中斷方式來完成這 個流程(第5節的實例即是如此)。不管具體怎么實施,流程的本質都是不變的。因為這個流程不以驅動工程師的意志為轉移,最終由I2C總線硬件上的通信協議 決定。
多數I2C總線驅動會定義一個xxx_i2c結構體,作為i2c_adapter的algo_data(類似“私有數據”),其中包含 I2C消息數組指針、數組索引及I2C適配器algorithm訪問控制用的自旋鎖、等待隊列等,而master_xfer()函數完成消息數組中消息的 處理也可通過對xxx_i2c結構體相關成員的訪問來控制。代碼清單15.12給出了xxx_i2c結構體的定義,與圖15.2中的xxx_i2c是對應 的。
代碼清單15.12 xxx_i2c結構體模板
1? struct xxx_i2c
2? {
3? ?spinlock_t??lock;
4? ?wait_queue_head_t?wait;?
5? ?struct i2c_msg??*msg;
6? ?unsigned int??msg_num;
7? ?unsigned int??msg_idx;
8? ?unsigned int??msg_ptr;
9?? ...
10 ?struct i2c_adapter?adap;
11 };
15.4 Linux I2C設備驅動
I2C 設備驅動要使用i2c_driver和i2c_client數據結構并填充其中的成員函數。i2c_client一般被包含在設備的私有信息結構體 yyy_data中,而i2c_driver則適宜被定義為全局變量并初始化,代碼清單15.13顯示了被初始化的i2c_driver。
代碼清單15.13 初始化的i2c_driver
1? static struct i2c_driver yyy_driver =
2? {
3??? .driver =
4??? {
5????? .name = "yyy",
6??? } ,
7??? .attach_adapter = yyy_attach_adapter,
8??? .detach_client =? yyy_detach_client,
9??? .command = yyy_command,
10 };
15.4.1 Linux I2C設備驅動模塊加載與卸載
I2C設備驅動模塊加載函數通用的方法是在I2C設備驅動模塊加載函數中完成兩件事:
?? 通過register_chrdev()函數將I2C設備注冊為一個字符設備。
?? 通過I2C核心的i2c_add_driver()函數添加i2c_driver。
在模塊卸載函數中需要做相反的兩件事:
?? 通過I2C核心的i2c_del_driver()函數刪除i2c_driver。
?? 通過unregister_chrdev()函數注銷字符設備。
代碼清單15.14給出了I2C設備驅動加載與卸載函數的模板。
代碼清單15.14 I2C設備驅動模塊加載與卸載函數模板
1? static int __init yyy_init(void)
2? {
3??? int res;
4??? /*注冊字符設備*/
5??? res = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老內核接口
6??? if (res)
7????? goto out;
8??? /*添加i2c_driver*/
9??? res = i2c_add_driver(&yyy_driver);
10?? if (res)
11???? goto out_unreg_class;
12?? return 0;
13
14?? out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c");
15?? out: printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
16?? return res;
17 }
18
19 static void __exit yyy_exit(void)
20 {
21?? i2c_del_driver(&i2cdev_driver);
22?? unregister_chrdev(YYY_MAJOR, "yyy");
23 }
第5行代碼說明注冊“yyy”這個字符設備時,使用的file_operations結構體為yyy_fops,15.4.3節將講解這個結構體中成員函數的實現。
15.4.2 Linux I2C設備驅動i2c_driver成員函數
i2c_add_driver (&yyy_driver)的執行會引發i2c_driver結構體中yyy_attach_adapter()函數的執行,我們可以在 yyy_attach_adapter()函數里探測物理設備。為了實現探測,yyy_attach_adapter()函數里面也只需簡單地調用I2C 核心的i2c_probe()函數,如代碼清單15.15。
代碼清單15.15 I2C設備驅動i2c_attach_adapter函數模板
1 static int yyy_attach_adapter(struct i2c_adapter *adapter)
2 {
3 ?return i2c_probe(adapter, &addr_data, yyy_detect);
4 }
代碼第3行傳遞給i2c_probe()函數的第1個參數是i2c_adapter指針,第2個參數是要探測的地址數據,第3個參數是具體的探測函數。要探測的地址實際列表在一個16位無符號整型數組中,這個數組以I2C_CLIENT_END為最后一個元素。
i2c_probe()函數會引發yyy_detect()函數的調用,可以在yyy_detect()函數中初始化i2c_client,如代碼清單15.16。
代碼清單15.16 I2C設備驅動detect函數模板
1? static int yyy_detect(struct i2c_adapter *adapter, int address, int kind)
2? {
3?? struct i2c_client *new_client;
4?? struct yyy_data *data;
5?? int err = 0;
6
7?? if (!i2c_check_functionality(adapter, I2C_FUNC_XXX)
8???? goto exit;
9
10? if (!(data = kzalloc(sizeof(struct yyy_data), GFP_KERNEL)))
11? {
12??? err =? - ENOMEM;
13??? goto exit;
14? }
15
16? new_client = &data->client;
17? new_client->addr = address;
18? new_client->adapter = adapter;
19? new_client->driver = &yyy_driver;
20? new_client->flags = 0;
21
22? /* 新的client將依附于adapter */
23? if ((err = i2c_attach_client(new_client)))
24??? goto exit_kfree;
25?
26? yyy_init_client(new_client);
27? return 0;
28? exit_kfree: kfree(data);
29? exit: return err;
30}
代 碼第10行分配私有信息結構體的內存,i2c_client也被創建。第16~20行對新創建的i2c_client進行初始化。第23行調用內核的 i2c_attach_client()知會I2C核心系統中包含了一個新的I2C設備。第26行代碼初始化i2c_client對應的I2C設備,這個 函數是硬件相關的。
圖15.4描述了當I2C設備驅動的模塊加載函數被調用的時候引發的連鎖反應的流程。
圖15.4 I2C設備驅動模塊加載連鎖反應
I2C 設備驅動卸載函數進行i2c_del_driver(&yyy_driver)調用后,會引發與yyy_driver關聯的每個 i2c_client與之解除關聯,即yyy_detach_client()函數將因此而被調用,代碼清單15.17給出了函數 yyy_detach_client()的設計。
代碼清單15.17 I2C設備驅動i2c_detach_client函數模板
1? static int yyy_detach_client(struct i2c_client *client)
2? {
3? ?int err;
4? ?struct yyy_data *data = i2c_get_clientdata(client);
5?
6? ?if ((err = i2c_detach_client(client)))
7? ??return err;
8?
9? ?kfree(data);
10 ?return 0;
11 }
上 述函數中第4行的i2c_get_clientdata()函數用于從yyy_data私有信息結構中的i2c_client的指針獲取yyy_data 的指針。第6行調用I2C核心函數i2c_detach_client(),這個函數會引發i2c_adapter的client_unregister ()函數被調用。第9行代碼釋放yyy_data的內存。
圖15.5描述了當I2C設備驅動的模塊卸載函數被調用的時候引發的連鎖反應的流程。
圖15.5 I2C設備驅動模塊卸載連鎖反應
下面開始分析i2c_driver中重要函數yyy_command()的實現,它實現了針對設備的控制命令。具體的控制命令是設備相關的,如對于實時鐘而言,命令將是設置時間和獲取時間,而對于視頻AD設備而言,命令會是設置采樣方式、選擇通道等。
假設yyy設備接受兩類命令YYY_CMD1、YYY_CMD2,而處理這兩個命令的函數分別為yyy_cmd1()、yyy_cmd2(),代碼清單15.18給出了yyy_command()函數的設計。
代碼清單15.18 I2C設備驅動command函數模板
1? static int yyy_command(struct i2c_client *client, unsigned int cmd, void
2??? *arg)
3? {
4??? switch (cmd)
5??? {
6????? case YYY_CMD1:
7??????? return yyy_cmd1(client, arg);?
8????? case YYY_CMD2:
9??????? return yyy_cmd2(client, arg);
10???? default:
11?????? return? - EINVAL;
12?? }
13 }
具體命令的實現是通過組件i2c_msg消息數組,并調用I2C核心的傳輸、發送和接收函數,由I2C核心的傳輸、發送和接收函數調用I2C適配器對應的algorithm相關函數來完成的。代碼清單15.19給出了一個yyy_cmd1()的例子。
代碼清單15.19 I2C設備具體命令處理函數模板
1? static int yyy_cmd1(struct i2c_client *client, struct rtc_time *dt)
2? {
3? ?struct i2c_msg msg[2];
4?? /*第一條消息是寫消息*/
5? ?msg[0].addr = client->addr;
6? ?msg[0].flags = 0;
7? ?msg[0].len = 1;
8? ?msg[0].buf = &offs;
9? ?/*第二條消息是讀消息*/
10 ?msg[1].addr = client->addr;
11 ?msg[1].flags = I2C_M_RD;
12 ?msg[1].len = sizeof(buf);
13 ?msg[1].buf = &buf[0];
14 ?
15 ?i2c_transfer(client->adapter, msg, 2);
16?? ...
17 }
15.4.3 Linux I2C設備驅動文件操作接口
作 為一種字符類設備,Linux I2C設備驅動文件操作接口與普通的設備驅動是完全一致的,但是在其中要使用i2c_client、i2c_driver、i2c_adapter和 i2c_algorithm結構體和I2C核心,并且對設備的讀寫和控制需要借助體系結構中各組成部分的協同合作。代碼清單15.20給出一個I2C設備 寫函數的例子。
代碼清單15.20 I2C設備文件接口寫函數范例
1? static ssize_t yyy_write(struct file *file, char *buf, size_t count, loff_t off)
2? {
3??? struct i2c_client *client = (struct i2c_client*)file->private_data;
4??? i2c_msg msg[1];
5??? char *tmp;
6??? int ret;
7?
8??? tmp = kmalloc(count, GFP_KERNEL);
9??? if (tmp == NULL)
10???? return? - ENOMEM;
11?? if (copy_from_user(tmp, buf, count))
12?? {
13???? kfree(tmp);
14???? return? - EFAULT;
15?? }
16
17?? msg[0].addr = client->addr;//地址
18?? msg[0].flags = 0;?????? //0為寫
19?? msg[0].len = count;???? //要寫的字節數
20?? msg[0].buf = tmp;????? //要寫的數據
21?? ret = i2c_transfer(client->adapter, msg, 1);? //傳輸i2c消息
22?? return (ret == 1) ? count : ret;
23 }
上述程序給出的僅僅是一個寫函數的例子,具體的寫操作與設備密切相關。我們通過這個例來仔細分析I2C設備讀寫過程中數據的流向和函數的調用關系。I2C設備的寫操作經歷了如下幾個步驟:
① 從用戶空間到字符設備驅動寫函數接口,寫函數構造I2C消息數組。
② 寫函數把構造的I2C消息數組傳遞給I2C核心的傳輸函數i2c_transfer()。
③ I2C核心的傳輸函數i2c_transfer()找到對應適配器algorithm的通信方法函數master_xfer()去最終完成I2C消息的處理。
圖15.6描述了從用戶空間發起讀寫操作到algorithm進行消息傳輸的流程。
圖15.6 I2C設備讀寫完整流程
總結
以上是生活随笔為你收集整理的Linux I2C核心、总线与设备驱动(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows 11 2023年重大更新
- 下一篇: 嵌入式Linux操作系统的版本查询