Linux I2C驅動學習
博客說明
撰寫日期 | 2019.11.20 |
---|---|
完稿日期 | 2019.11.21 |
最近維護 | 暫無 |
本文作者 | multimicro |
聯繫方式 | [email protected] |
資料鏈接 | 本文無附件資料 |
GitHub | https://github.com/wifialan/drivers |
原文鏈接 | https://blog.csdn.net/multimicro/article/details/103164746 |
開發環境
環境說明 | 詳細信息 | 備註信息 |
---|---|---|
操作系統 | Ubunut 18.04 | |
開發板 | JZ2440-V3 | |
u-boot | uboot-2012.04.01 | |
busybox | busybox-1.22.1 | |
u-boot和busybox編譯器 | arm-linux-gcc (4.4.3) | |
Linux內核 | linux-4.19-rc3 | |
Linux內核編譯器 | arm-linux-gnueabi-gcc (4.9.4) |
1. Linux I2C 體系結構
i2c的編程應用流程請參考我的上一篇文章Linux 設備樹學習——基於i2c總線分析,這篇文章我是基於i2c總線而寫的,裏面包含註冊和匹配方式的介紹,和相應的模板。
參考宋寶華 《Linux設備驅動開發詳解》 第15章內容。
Linux的I2C體系結構分爲3個組成部分:
- I2C核心
- I2C總線驅動(適配器驅動)
- I2C設備驅動
1.1 Linux I2C核心
借鑑 宋寶華 《Linux設備驅動開發詳解》 第15章第2節內容。
I2C核心(drivers/i2c/i2c-core-base.c)中提供了一組不依賴於硬件平臺的接口函數,這個文件一般不需要被工程師修改,但是理解其中的主要函數非常關鍵,因爲I2C總線驅動和設備驅動之間以I2C核心作爲紐帶。I2C核心中的主要函數如下。
1.1.1 增加 / 刪除 i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);
在有設備樹版本中,內部都封裝了從設備樹中獲取i2c
設備並註冊爲i2c client
的方法,如下:
/i2c節點一般表示i2c控制器, 它會被轉換爲platform_device, 在內核中有對應的platform_driver;
platform_driver的probe函數中會調用i2c_add_numbered_adapter:
i2c_add_numbered_adapter// drivers/i2c/i2c-core-base.c
__i2c_add_numbered_adapter
i2c_register_adapter
of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c
for_each_available_child_of_node(bus, node) {
client = of_i2c_register_device(adap, node);
client = i2c_new_device(adap, &info); // 設備樹中的i2c子節點被轉換爲i2c_client
}
上述第一個函數i2c_add_numbered_adapter
內容如下,通過Source Insight軟件,可以一步一步定位分析,可以很好的幫助理解設備樹彙總的i2c子設備節點是如何轉換爲i2c client
的。
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
return __i2c_add_numbered_adapter(adap);
}
1.1.2 增加 / 刪除 i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
這些函數一般用在編寫i2c設備driver中,如
drivers/char/at24c256.c
static int __init at24_init(void)
{
return i2c_add_driver(&at24c256_driver);
}
static void __exit at24_exit(void)
{
i2c_del_driver(&at24c256_driver);
printk(DRV_NAME "\tRemove i2c driver success\n");
}
module_init(at24_init);
module_exit(at24_exit);
1.1.3 I2C傳輸、發送和接收
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
這三個函數的區別在於:
i2c_transfer
一次可以傳輸多個i2c_msg
i2c_master_send
一次只能發送一個i2c_msg
i2c_master_recv
一次只能接收一個i2c_msg
在開發比較複雜的i2c時序時,採用最多的是i2c_transfer
。參考 Code 1
Code 1
int ret;
struct i2c_msg msg[2];
char buffer_data[100];
memset(buffer_data,0,sizeof(buffer_data));
buffer_data[0] = (char)0x00;
buffer_data[1] = (char)0x00;
buffer_data[2] = (char)0xAA;
buffer_data[3] = (char)0xAB;
buffer_data[4] = (char)0xAC;
/* 1st write data */
msg[0].addr = at24_dev->client->addr | 0x01; //這個是i2c從設備的地址
msg[0].flags = at24_dev->client->flags & 0; //這個是對i2c從設備的讀寫標誌位,和上面的addr組成i2c的第一個8bit數據
msg[0].buf = &buffer_data[0]; //這個是i2c從設備地址後的數據,數組中,每一個數據都是一個8bit數據
msg[0].len = 5; //指定發送除地址位外的多少個數據,上述buffer_data中,前兩個是at24c256的從設備地址,後3個是寫入的數據
/* 2nd write data */
msg[1].addr = at24_dev->client->addr | 0x01;
msg[1].flags = I2C_M_RD;
msg[1].buf = &buffer_data[5]; //指定接受的數據存放位置,這裏從buffer_data中的第6個位置中開始存儲讀到的數據,
msg[1].len = 3; //指定讀的數據個數
//開始i2c傳輸,發送成功後,會返回發送的次數,由最後一個參數爲2可以,需要開啓兩次傳輸,那麼發送成功後就會返回2
ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 2);
tmp = (ret == 2) ? msg[1].len : ret;
i2c_transfer
發送失敗會返回特定的負數,詳情參考linux i2c 的通信函數i2c_transfer在什麼情況下出現錯誤
1.2 Linux I2C適配器驅動
1.2.1 I2C適配器驅動的註冊與註銷
借鑑 宋寶華 《Linux設備驅動開發詳解》 第15章第3節內容。
由於I2C總線控制器通常是在內存上的,所以它本身也連接在platform總線上,要通過platform_driver和platform_device的匹配來執行。因此儘管I2C適配器給別人提供了總線,它自己也被認爲是接在platform總線上的一個客戶。Linux的總線、設備和驅動模型實際上是一個樹形結構,每個節點雖然可能成爲別的總線控制器,但是自己也被認爲是從上一級總線枚舉出來的。
也就是說,I2C適配器的初始化,需要在platform_driver
的probe()
函數中完成。這部分大多由芯片廠商提供好了,自己不需要編寫。
1.2.2 I2C總線的通信方法
只有I2C適配器驅動是沒有靈魂的,需要給它注入一個通信算法i2c_algorithm
,才能然I2C運作起來。
可以說I2C適配器驅動
是來配置I2C硬件
,而i2c_algorithm
是來操作I2C硬件
產生對應的I2C時序波形,完成基於I2C總線的數據傳輸。
我在開發時,沒有自己編寫通信方法,但是也可以實現通信,猜測這個也是芯片廠商給寫好的,直接調用接口
1.3 Linux I2C設備驅動
這方面主要是完成I2C設備驅動模塊的加載與卸載,並實現數據傳輸,模塊的加載與卸載就是經常編寫的驅動開發流程,詳情參考附錄中github代碼。
在編寫i2c設備driver中的i2c設備讀寫函數中,如下,我將AT24C256
這個i2c設備註冊成了字符設備,調用了file_operations
結構體裏面的讀寫方法。參考 Code 2 和 Code 3
drivers/char/at24c256.c
Code 2
static ssize_t at24_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
{
int ret,tmp;
char i;
struct i2c_msg msg;
char buffer_data[100];
copy_from_user(buffer_data, buffer, 10);
memset(buffer_data,0,sizeof(buffer_data));
buffer_data[0] = (char)0x00;
buffer_data[1] = (char)0x00;
for (i = 0; i < 64; ++i)
{
buffer_data[i+2] = i;
}
msg.addr = at24_dev->client->addr | 0x01; //這個是i2c從設備的地址
msg.flags = at24_dev->client->flags & 0;
msg.buf = &buffer_data[0]; //這個是i2c從設備地址後的數據
msg.len = 66;
ret = i2c_transfer(at24_dev->client->adapter,&msg,1);
tmp = (ret == 1) ? msg.len : ret;
printk("i2c code: %d return code: %d addr: 0x%02x%02x ",\
ret,tmp,buffer_data[0],buffer_data[1]);
for (i = 0; i < 64; ++i)
{
printk("Write Data: 0x%02x",buffer_data[i+2]);
}
return 0;
}
Code 3
static ssize_t at24_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
{
int ret,tmp;
unsigned long i;
struct i2c_msg msg[3];
char buffer_data[100];
memset(buffer_data,0,sizeof(buffer_data));
msg[0].addr = at24_dev->client->addr | 0x01;
msg[0].flags = I2C_M_RD;
msg[0].buf = &buffer_data[2];
msg[0].len = 64;
ret = i2c_transfer(at24_dev->client->adapter, &msg[0], 1);
tmp = (ret == 1) ? msg[0].len : ret;
printk("i2c code: %d return code: %d addr: 0x%02x%02x ",\
ret,tmp,buffer_data[0],buffer_data[1]);
for (i = 0; i < 64; ++i)
{
printk("Read Data: 0x%02x",buffer_data[i+2]);
}
return 0;
}
附錄
- Github代碼:at24c256.c