I2C核心(i2c_core)
I2C核心維護(hù)了i2c_bus結(jié)構(gòu)體,提供了I2C總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)的注冊(cè)、注銷方法,維護(hù)了I2C總線的驅(qū)動(dòng)、設(shè)備鏈表,實(shí)現(xiàn)了設(shè)備、驅(qū)動(dòng)的匹配探測(cè)。此部分代碼由Linux內(nèi)核提供。
I2C總線驅(qū)動(dòng)
I2C總線驅(qū)動(dòng)維護(hù)了I2C適配器數(shù)據(jù)結(jié)構(gòu)(i2c_adapter)和適配器的通信方法數(shù)據(jù)結(jié)構(gòu)(i2c_algorithm)。所以I2C總線驅(qū)動(dòng)可控制I2C適配器產(chǎn)生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。
I2C設(shè)備驅(qū)動(dòng)
I2C設(shè)備驅(qū)動(dòng)主要維護(hù)兩個(gè)結(jié)構(gòu)體:i2c_driver和i2c_client,實(shí)現(xiàn)和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅(qū)動(dòng)開發(fā)者需要完成的。
第二:Linux內(nèi)核中描述I2C的四個(gè)核心結(jié)構(gòu)體
1)i2c_client—掛在I2C總線上的I2C從設(shè)備
每一個(gè)i2c從設(shè)備都需要用一個(gè)i2c_client結(jié)構(gòu)體來描述,i2c_client對(duì)應(yīng)真實(shí)的i2c物理設(shè)備device。
struct i2c_client { unsigned short flags; //標(biāo)志位 (讀寫) unsigned short addr; //7位的設(shè)備地址(低7位) char name[I2C_NAME_SIZE]; //設(shè)備的名字,用來和i2c_driver匹配 struct i2c_adapter *adapter; //依附的適配器(adapter),適配器指明所屬的總線(i2c0/1/2_bus) struct device dev; //繼承的設(shè)備結(jié)構(gòu)體 int irq; //設(shè)備申請(qǐng)的中斷號(hào) struct list_head detected; //已經(jīng)被發(fā)現(xiàn)的設(shè)備鏈表 };
但是i2c_client不是我們自己寫程序去創(chuàng)建的,而是通過以下常用的方式自動(dòng)創(chuàng)建的:
方法一: 分配、設(shè)置、注冊(cè)i2c_board_info
方法二: 獲取adapter調(diào)用i2c_new_device
方法三: 通過設(shè)備樹(devicetree)創(chuàng)建
方法1和方法2通過platform創(chuàng)建,這兩種方法在內(nèi)核3.0版本以前使用所以在這不詳細(xì)介紹;**方法3是最新的方法,**3.0版本之后的內(nèi)核都是通過這種方式創(chuàng)建的,文章后面的案例就按方法3。
2)i2c_adapter
I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對(duì)I2C總線都對(duì)應(yīng)一個(gè)適配器來控制它。在Linux內(nèi)核代碼中,每一個(gè)adapter提供了一個(gè)描述它的結(jié)構(gòu)(struct i2c_adapter),再通過i2c core層將i2c設(shè)備與i2c adapter關(guān)聯(lián)起來。主要用來完成i2c總線控制器相關(guān)的數(shù)據(jù)通信,此結(jié)構(gòu)體在芯片廠商提供的代碼中維護(hù)。
struct i2c_adapter { struct module *owner; unsigned int class; //允許匹配的設(shè)備的類型 const struct i2c_algorithm *algo; //指向適配器的驅(qū)動(dòng)程序,實(shí)現(xiàn)發(fā)送數(shù)據(jù)的算法 struct device dev; //指向適配器的設(shè)備結(jié)構(gòu)體 char name[48]; //適配器的名字 };
3)i2c_algorithm
I2C總線數(shù)據(jù)通信算法,通過管理I2C總線控制器,實(shí)現(xiàn)對(duì)I2C總線上數(shù)據(jù)的發(fā)送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對(duì)應(yīng)的驅(qū)動(dòng)程序,每一個(gè)適配器對(duì)應(yīng)一個(gè)驅(qū)動(dòng)程序,用來描述適配器和設(shè)備之間的通信方法,由芯片廠商去實(shí)現(xiàn)的。
struct i2c_algorithm { //傳輸函數(shù)指針,指向?qū)崿F(xiàn)IIC總線通信協(xié)議的函數(shù) int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); };
4)i2c_driver
用于管理I2C的驅(qū)動(dòng)程序和i2c設(shè)備(client)的匹配探測(cè),實(shí)現(xiàn)與應(yīng)用層交互的文件操作集合fops、cdev等。
struct i2c_driver { int (*probe)(struct i2c_client *, const struct i2c_device_id *); //設(shè)備匹配成功調(diào)用的函數(shù) int (*remove)(struct i2c_client *); //設(shè)備移除之后調(diào)用的函數(shù) struct device_driver driver; //設(shè)備驅(qū)動(dòng)結(jié)構(gòu)體 const struct i2c_device_id *id_table; //設(shè)備的ID表,匹配用platform創(chuàng)建的client };
第三:應(yīng)用實(shí)例,實(shí)現(xiàn)mpu6050驅(qū)動(dòng),讀取溫度
在設(shè)備樹中描述I2C設(shè)備信息
@i2c-0 {//表示這個(gè)i2c_client所依附的adapter是i2c-0 //對(duì)應(yīng)i2c_client的name = "invensense,mpu6050" compatible = "invensense,mpu6050"; //對(duì)應(yīng)i2c_client的addr = 0x69 -- 從機(jī)設(shè)備的地址 reg = <0x69>; //對(duì)應(yīng)i2c_client的irq interrupts = <70>; };
最終內(nèi)核會(huì)將這個(gè)設(shè)備樹的節(jié)點(diǎn)解析為一個(gè)i2c_client結(jié)構(gòu)體與i2c_driver結(jié)構(gòu)體進(jìn)行匹配。
第四:編寫驅(qū)動(dòng)代碼
分配、設(shè)置、注冊(cè)i2c_driver結(jié)構(gòu)體
struct i2c_driver mpu6050_driver = { . driver = { .name = "mpu6050", .owner = THIS_MODULE, .of_match_table = of_match_ptr(mpu6050_of_match), }, .probe = mpu6050_probe, .remove = mpu6050_remove, }; static int mpu6050_init(void) { printk("%s called ", __func__); i2c_add_driver(&mpu6050_driver); return 0; }
i2c總線驅(qū)動(dòng)模型屬于設(shè)備模型中的一類,同樣struct i2c_driver結(jié)構(gòu)體繼承于struct driver,匹配方法和設(shè)備模型中講的一樣,這里要去匹配設(shè)備樹,所以必須實(shí)現(xiàn)i2c_driver結(jié)構(gòu)體中的driver成員中的of_match_table成員:
/* 用來匹配mpu6050的設(shè)備樹 */ static struct of_device_id mpu6050_of_match[] = { {.compatible = "invensense,mpu6050"}, {}, };
如果和設(shè)備樹匹配成功,那么就好調(diào)用probe函數(shù)
/* 匹配函數(shù),設(shè)備樹中的mpu6050結(jié)點(diǎn)對(duì)應(yīng)轉(zhuǎn)換為一個(gè)client結(jié)構(gòu)體 */ static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id) { int ret; printk("mpu6050 match ok! "); mpu6050_dev.client = client; /* 注冊(cè)設(shè)備號(hào) */ mpu6050_dev.devno = MKDEV(MAJOR, MINOR); ret = register_chrdev_region(mpu6050_dev.devno, 1, "mpu6050"); if (ret < 0) goto err1; cdev_init(&mpu6050_dev.cdev, &mpu6050_fops); mpu6050_dev.cdev.owner = THIS_MODULE; ret = cdev_add(&mpu6050_dev.cdev, mpu6050_dev.devno, 1); if (ret < 0) goto err2; return 0; err2: unregister_chrdev_region(mpu6050_dev.devno, 1); err1: return -1; }
實(shí)現(xiàn)文件操作集合
struct file_operations mpu6050_fops = { .owner = THIS_MODULE, .open = mpu6050_open, .release = mpu6050_release, .unlocked_ioctl = mpu6050_ioctl, }; static int mpu6050_open(struct inode * inodep, struct file * filep) { printk("%s called ", __func__); mpu6050_write_byte(mpu6050_dev.client, PWR_MGMT_1, 0x00); mpu6050_write_byte(mpu6050_dev.client, SMPLRT_DIV, 0x07); mpu6050_write_byte(mpu6050_dev.client, CONFIG, 0x06); mpu6050_write_byte(mpu6050_dev.client, GYRO_CONFIG, 0xF8); mpu6050_write_byte(mpu6050_dev.client, ACCEL_CONFIG, 0x19); return 0; } static int mpu6050_release(struct inode * inodep, struct file * filep) { printk("%s called ", __func__); return 0; } void get_temp(union mpu6050_data * data) { data->temp = mpu6050_read_byte(mpu6050_dev.client, TEMP_OUT_L); data->temp |= mpu6050_read_byte(mpu6050_dev.client, TEMP_OUT_H) << 8; } static long mpu6050_ioctl(struct file * filep, unsigned int cmd, unsigned long arg) { union mpu6050_data data; switch (cmd) { case GET_TEMP: get_temp(&data); break; default: break; } if (copy_to_user((unsigned int *)arg, &data, sizeof(data))) return -1; return 0; }
如何實(shí)現(xiàn)對(duì)i2c從設(shè)備的讀寫操作?
/* 讀取mpu6050中一個(gè)字節(jié)的數(shù)據(jù),將讀取的數(shù)據(jù)的地址返回 */ static int mpu6050_read_byte(struct i2c_client * client, unsigned char reg_add) { int ret; /* 要讀取的那個(gè)寄存器的地址 */ char txbuf = reg_add; /* 用來接收讀到的數(shù)據(jù) */ char rxbuf[1]; /* i2c_msg指明要操作的從機(jī)地址,方向,緩沖區(qū) */ struct i2c_msg msg[] = { {client->addr, 0, 1, &txbuf}, //0表示寫,向往從機(jī)寫要操作的寄存器的地址 {client->addr, I2C_M_RD, 1, rxbuf}, //讀數(shù)據(jù) }; /* 通過i2c_transfer函數(shù)操作msg */ ret = i2c_transfer(client->adapter, msg, 2); //執(zhí)行2條msg if (ret < 0) { printk("i2c_transfer read err "); return -1; } return rxbuf[0]; } static int mpu6050_write_byte(struct i2c_client * client, unsigned char reg_addr, unsigned char data) { int ret; /* 要寫的那個(gè)寄存器的地址和要寫的數(shù)據(jù) */ char txbuf[] = {reg_addr, data}; /* 1個(gè)msg,寫兩次 */ struct i2c_msg msg[] = { {client->addr, 0, 2, txbuf} }; ret = i2c_transfer(client->adapter, msg, 1); if (ret < 0) { printk("i2c_transfer write err "); return -1; } return 0; }
在實(shí)現(xiàn)讀寫操作的時(shí)候,使用了一個(gè)重要的函數(shù)i2c_transfer(),這個(gè)函數(shù)是i2c核心提供給設(shè)備驅(qū)動(dòng)的,通過它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg結(jié)構(gòu),這個(gè)函數(shù)最終會(huì)回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對(duì)象發(fā)送到i2c物理控制器。
struct i2c_msg { __u16 addr; /* slave address */ __u16 flags; /* 1 - 讀 0 - 寫 */ __u16 len; /* msg length */ __u8 *buf; /* 要發(fā)送的數(shù)據(jù) */ };
以上是我對(duì)Linux中I2C驅(qū)動(dòng)框架的分析及實(shí)際案例分析,如有不足歡迎指出。
審核編輯:劉清
-
控制器
+關(guān)注
關(guān)注
112文章
16412瀏覽量
178705 -
內(nèi)核
+關(guān)注
關(guān)注
3文章
1377瀏覽量
40338 -
適配器
+關(guān)注
關(guān)注
8文章
1961瀏覽量
68114 -
Linux
+關(guān)注
關(guān)注
87文章
11324瀏覽量
209938 -
I2C
+關(guān)注
關(guān)注
28文章
1490瀏覽量
124080 -
I2C總線
+關(guān)注
關(guān)注
8文章
391瀏覽量
61052 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21686 -
MPU6050
+關(guān)注
關(guān)注
39文章
307瀏覽量
71512
原文標(biāo)題:Linux系統(tǒng)中I2C子系統(tǒng)基本分析
文章出處:【微信號(hào):嵌入式開發(fā)愛好者,微信公眾號(hào):嵌入式開發(fā)愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論