I2C核心(i2c_core)
I2C核心維護了i2c_bus結構體,提供了I2C總線驅動和設備驅動的注冊、注銷方法,維護了I2C總線的驅動、設備鏈表,實現了設備、驅動的匹配探測。此部分代碼由Linux內核提供。
I2C總線驅動
I2C總線驅動維護了I2C適配器數據結構(i2c_adapter)和適配器的通信方法數據結構(i2c_algorithm)。所以I2C總線驅動可控制I2C適配器產生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。
I2C設備驅動
I2C設備驅動主要維護兩個結構體:i2c_driver和i2c_client,實現和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅動開發者需要完成的。
第二:Linux內核中描述I2C的四個核心結構體
1)i2c_client—掛在I2C總線上的I2C從設備
每一個i2c從設備都需要用一個i2c_client結構體來描述,i2c_client對應真實的i2c物理設備device。
struct i2c_client { unsigned short flags; //標志位 (讀寫) unsigned short addr; //7位的設備地址(低7位) char name[I2C_NAME_SIZE]; //設備的名字,用來和i2c_driver匹配 struct i2c_adapter *adapter; //依附的適配器(adapter),適配器指明所屬的總線(i2c0/1/2_bus) struct device dev; //繼承的設備結構體 int irq; //設備申請的中斷號 struct list_head detected; //已經被發現的設備鏈表 };
但是i2c_client不是我們自己寫程序去創建的,而是通過以下常用的方式自動創建的:
方法一: 分配、設置、注冊i2c_board_info
方法二: 獲取adapter調用i2c_new_device
方法三: 通過設備樹(devicetree)創建
方法1和方法2通過platform創建,這兩種方法在內核3.0版本以前使用所以在這不詳細介紹;**方法3是最新的方法,**3.0版本之后的內核都是通過這種方式創建的,文章后面的案例就按方法3。
2)i2c_adapter
I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對I2C總線都對應一個適配器來控制它。在Linux內核代碼中,每一個adapter提供了一個描述它的結構(struct i2c_adapter),再通過i2c core層將i2c設備與i2c adapter關聯起來。主要用來完成i2c總線控制器相關的數據通信,此結構體在芯片廠商提供的代碼中維護。
struct i2c_adapter { struct module *owner; unsigned int class; //允許匹配的設備的類型 const struct i2c_algorithm *algo; //指向適配器的驅動程序,實現發送數據的算法 struct device dev; //指向適配器的設備結構體 char name[48]; //適配器的名字 };
3)i2c_algorithm
I2C總線數據通信算法,通過管理I2C總線控制器,實現對I2C總線上數據的發送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對應的驅動程序,每一個適配器對應一個驅動程序,用來描述適配器和設備之間的通信方法,由芯片廠商去實現的。
struct i2c_algorithm { //傳輸函數指針,指向實現IIC總線通信協議的函數 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); };
4)i2c_driver
用于管理I2C的驅動程序和i2c設備(client)的匹配探測,實現與應用層交互的文件操作集合fops、cdev等。
struct i2c_driver { int (*probe)(struct i2c_client *, const struct i2c_device_id *); //設備匹配成功調用的函數 int (*remove)(struct i2c_client *); //設備移除之后調用的函數 struct device_driver driver; //設備驅動結構體 const struct i2c_device_id *id_table; //設備的ID表,匹配用platform創建的client };
第三:應用實例,實現mpu6050驅動,讀取溫度
在設備樹中描述I2C設備信息
@i2c-0 {//表示這個i2c_client所依附的adapter是i2c-0 //對應i2c_client的name = "invensense,mpu6050" compatible = "invensense,mpu6050"; //對應i2c_client的addr = 0x69 -- 從機設備的地址 reg = <0x69>; //對應i2c_client的irq interrupts = <70>; };
最終內核會將這個設備樹的節點解析為一個i2c_client結構體與i2c_driver結構體進行匹配。
第四:編寫驅動代碼
分配、設置、注冊i2c_driver結構體
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總線驅動模型屬于設備模型中的一類,同樣struct i2c_driver結構體繼承于struct driver,匹配方法和設備模型中講的一樣,這里要去匹配設備樹,所以必須實現i2c_driver結構體中的driver成員中的of_match_table成員:
/* 用來匹配mpu6050的設備樹 */ static struct of_device_id mpu6050_of_match[] = { {.compatible = "invensense,mpu6050"}, {}, };
如果和設備樹匹配成功,那么就好調用probe函數
/* 匹配函數,設備樹中的mpu6050結點對應轉換為一個client結構體 */ static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id) { int ret; printk("mpu6050 match ok! "); mpu6050_dev.client = client; /* 注冊設備號 */ 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; }
實現文件操作集合
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; }
如何實現對i2c從設備的讀寫操作?
/* 讀取mpu6050中一個字節的數據,將讀取的數據的地址返回 */ static int mpu6050_read_byte(struct i2c_client * client, unsigned char reg_add) { int ret; /* 要讀取的那個寄存器的地址 */ char txbuf = reg_add; /* 用來接收讀到的數據 */ char rxbuf[1]; /* i2c_msg指明要操作的從機地址,方向,緩沖區 */ struct i2c_msg msg[] = { {client->addr, 0, 1, &txbuf}, //0表示寫,向往從機寫要操作的寄存器的地址 {client->addr, I2C_M_RD, 1, rxbuf}, //讀數據 }; /* 通過i2c_transfer函數操作msg */ ret = i2c_transfer(client->adapter, msg, 2); //執行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; /* 要寫的那個寄存器的地址和要寫的數據 */ char txbuf[] = {reg_addr, data}; /* 1個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; }
在實現讀寫操作的時候,使用了一個重要的函數i2c_transfer(),這個函數是i2c核心提供給設備驅動的,通過它發送的數據需要被打包成i2c_msg結構,這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器。
struct i2c_msg { __u16 addr; /* slave address */ __u16 flags; /* 1 - 讀 0 - 寫 */ __u16 len; /* msg length */ __u8 *buf; /* 要發送的數據 */ };
以上是我對Linux中I2C驅動框架的分析及實際案例分析,如有不足歡迎指出。
審核編輯:劉清
-
控制器
+關注
關注
112文章
16361瀏覽量
178028 -
內核
+關注
關注
3文章
1372瀏覽量
40289 -
適配器
+關注
關注
8文章
1952瀏覽量
68024 -
Linux
+關注
關注
87文章
11304瀏覽量
209476 -
I2C
+關注
關注
28文章
1487瀏覽量
123740 -
I2C總線
+關注
關注
8文章
391瀏覽量
60936 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21650 -
MPU6050
+關注
關注
39文章
307瀏覽量
71403
原文標題:Linux系統中I2C子系統基本分析
文章出處:【微信號:嵌入式開發愛好者,微信公眾號:嵌入式開發愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論