i2c總線是一種十分常見的板級總線,本文以linux3.14.0爲參考, 討論Linux中的i2c驅動模型並利用這個模型寫一個mpu6050的驅動, 最後在應用層將mpu6050中的原始數據讀取出來html
下圖就是我理解的i2c驅動框架示意圖, 相似中斷子系統, i2c子系統中也使用一個對象來描述一個物理實體, 設備對象與驅動分離, 驅動結合設備對象對硬件設備的描述才能夠驅動一個具體的物理設備, 體現了分離的設計思想, 實現了代碼的複用, 好比:node
事實上, 對於任何一種總線, 內核都有一個bus_type類型的對象與之對應, 可是platform_bus_type並無對應的實際的物理總線, 這也就是platform總線也叫虛擬總線的緣由.linux
除了分離,i2c子系統也體現的軟件分層的設計思想, 整個i2c子系統由3層構成:設備驅動層--i2c核心--控制器驅動算法
除了經典的分層與分離模型,咱們也能夠看到一個有意思的現象——Linux 的應用程序不但能夠經過設備驅動來訪問i2c從設備,還能夠經過一個並無直接掛接到i2c_bus_type的i2c_cleint來找到主機控制器進而訪問任意一個i2c設備, 這是怎麼回事呢? 我會在下一篇說^-^網絡
我首先說i2c_adapter, 並非編寫一個i2c設備驅動須要它, 一般咱們在配置內核的時候已經將i2c控制器的設備信息和驅動已經編譯進內核了, 就是這個adapter對象已經建立好了, 可是瞭解其中的成員對於理解i2c驅動框架很是重要, 全部的設備驅動都要通過這個對象的處理才能和物理設備通訊框架
//include/linux/i2c.h 425 struct i2c_adapter { 426 struct module *owner; 427 unsigned int class; /* classes to allow probing for */ 428 const struct i2c_algorithm *algo; /* the algorithm to access the bus */ 429 void *algo_data; 430 431 /* data fields that are valid for all devices */ 432 struct rt_mutex bus_lock; 433 434 int timeout; /* in jiffies */ 435 int retries; 436 struct device dev; /* the adapter device */ 437 438 int nr; 439 char name[48]; 440 struct completion dev_released; 441 442 struct mutex userspace_clients_lock; 443 struct list_head userspace_clients; 444 445 struct i2c_bus_recovery_info *bus_recovery_info; 446 };
struct i2c_adapter
--428-->這個i2c控制器須要的控制算法, 其中最重要的成員是master_xfer()接口, 這個接口是硬件相關的, 裏面的操做都是基於具體的SoCi2c寄存器的, 它將完成將數據發送到物理i2c控制器的"最後一千米"
--436-->表示這個一個device, 會掛接到內核中的鏈表中來管理, 其中的
--443-->這個節點將一個i2c_adapter對象和它所屬的i2c_client對象以及相應的i2c_driver對象鏈接到一塊兒函數
下面是2個i2c-core.c提供的i2c_adapter直接相關的操做API, 一般也不須要設備驅動開發中使用,測試
這個API能夠將一個i2c_adapter類型的對象註冊到內核中, 源碼我就不貼了, 下面是他們的調用關係,咱們能夠從中看到一個adapter對象和系統中的i2c_driver對象以及i2c_client對象的匹配流程。
首先,咱們在驅動中構造一個i2c_adapter對象的時候,對其中的相關域進行初始化,這裏咱們最關心它的父設備this
//drivers/i2c/buses/i2c-s3c2410.c 1072 static int s3c24xx_i2c_probe(struct platform_device *pdev) 1073 { 1140 i2c->adap.dev.parent = &pdev->dev; 1210 }
獲得了這樣一個i2c_adapter對象,咱們就能夠調用這個API將它註冊到內核,調用關係以下:spa
i2c_add_adapter()
1 └──i2c_register_adapter(adapter)
2 ├──adap->dev.bus = &i2c_bus_type;
3 ├──adap->dev.type = &i2c_adapter_type;
4 │ └──i2c_adapter_attr_groups
5 │ └── i2c_adapter_attr_group
6 │ └── i2c_adapter_attrs
7 │ └── &dev_attr_new_device.attr
8 │ └──DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
9 │ └──i2c_sysfs_new_device()
10 │ └──list_add_tail(&client->detected, &adap->userspace_clients);
11 └──device_register(&adap-dev);
12 ├──device_initialize(dev);
13 │ ├──/* /sys/devices/ /
14 │ ├──struct kset devices_kset;
15 │ ├──dev->kobj.kset = devices_kset;
16 │ ├──kobject_init(&dev->kobj, &device_ktype);
17 │ └──set_dev_node(dev, -1);
18 └──device_add(dev);
19 ├──parent=get_device(dev->parent);
20 ├──kobj = get_device_parent(dev, parent);
21 │ └──return &parent->kobj;
22 ├──dev->kobj.parent = kobj;
23 ├──set_dev_node(dev, dev_to_node(parent));
24 ├──kobject_add(&dev->kobj, dev->kobj.parent, NULL);
25 │ ├──kobject_add_varg(kobj, parent, fmt, args);
26 │ ├──kobj->parent = parent;
27 │ ├──kobject_add_internal(kobj);
28 │ ├──parent = kobject_get(kobj->parent);
29 │ ├──kobj_kset_join(kobj);
30 │ │ ├──kset_get(kobj->kset)
31 │ │ └──list_add_tail(&kobj->entry, &kobj->kset->list);
32 │ ├──kobj->parent = parent;
33 │ └──create_dir(kobj);
34 ├──device_create_file(dev, &dev_attr_uevent);
35 ├──device_create_sys_dev_entry(dev);
36 ├──devtmpfs_create_node(dev);
37 ├──device_add_class_symlinks(dev);
38 ├──device_add_attrs(dev);
39 ├──bus_add_device(dev);
40 ├──bus_probe_device(dev);
41 ├──klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);
42 └──klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);
調用關係就是這樣了,下面我簡單解釋一下這個樹
i2c_add_adapter()
--10-->將i2c_adapter對象中的userspace_clients與匹配到的client對象中detected鏈接到一塊兒
--15-->將/sys/devices的kset賦值給i2c_adapter->device->kobject->parent,即創建i2c_adapter對象和/sys/devices的父子關係, 參見"Linux設備管理(一)_kobject, kset,ktype分析"
--20-->獲取device->parent的kobject對象
--22-->將device->parent的kobject對象做爲device->kobject->parent, 造成device->kobject->parent == device->parent->kobject
--31-->將這個device->kobject掛接到device->kset->list鏈表中, 因而可知, kobject->kset指向的kset對象和kobject->entry掛接到的kset對象能夠不是一個, 與"Linux設備管理(一)_kobject, kset,ktype分析"那種狀況不一樣.
從內核中刪除一個adapter
在i2c設備端,驅動開發的主要工做和平臺總線同樣:構建設備對象和驅動對象,我用的開發板上的i2c總線上掛接的設備是mpu6050,接下來我就以個人板子爲例,討論如何編寫i2c設備端的驅動。
一樣這裏的設備對象也可使用三種方式構建:平臺文件,模塊和設備樹。
本文采用設備樹的方式構建設備對象,咱們能夠參考內核文檔"Documentations/devicetree/bindings/i2c/i2c-s3c2410.txt"以及設備樹中的樣板來編寫咱們的設備樹節點,** 咱們在設備樹中可不會寫mpu6050內部寄存器的地址,由於這些寄存器地址SoC看不到**。
/{
109 i2c@138B0000 {
110 #address-cells = <1>;
111 #size-cells = <0>;
112 samsung,i2c-sda-delay = <100>;
113 samsung,i2c-max-bus-freq = <20000>;
114 pinctrl-0 =<&i2c5_bus>;
115 pinctrl-names="default";
116 status="okay";
117 mpu6050@68{
118 compatible="invensense,mpu6050";
119 reg=<0x68>;
120 };
121 };
/
--109-->即咱們SoC上的i2c控制器的地址
--116-->這個必定要okay,實際上是對"./arch/arm/boot/dts/exynos4.dtsi +387"處的status = "disabled"的重寫,相同的節點的不一樣屬性信息都會被合併,相同節點的相同的屬性會被重寫
--117-->設備子節點,/表示板子,它的子節點node1表示SoC上的某個控制器,控制器中的子節點node2表示掛接在這個控制器上的設備(們)。68便是設備地址。
--118-->這個屬性就是咱們和驅動匹配的鑰匙,一個字符都不能錯
--119-->這個屬性是從設備的地址,咱們能夠經過查閱手冊"MPU-6050_DataSheet_V3_4"獲得
寫了這個設備節點,內核就會爲咱們在內核中構造一個i2c_client對象並掛接到i2c總線對象的設備鏈表中以待匹配,這個設備類以下
//include/linux/i2c.h 217 struct i2c_client { 218 unsigned short flags; /* div., see below */ 219 unsigned short addr; /* chip address - NOTE: 7bit */ 220 /* addresses are stored in the */ 221 /* _LOWER_ 7 bits */ 222 char name[I2C_NAME_SIZE]; 223 struct i2c_adapter *adapter; /* the adapter we sit on */ 224 struct device dev; /* the device structure */ 225 int irq; /* irq issued by device */ 226 struct list_head detected; 227 };
--219-->設備地址
--223-->表示這個client從屬的i2c主機對應的adapter對象,驅動方法中使用這個指針發送數據
--224-->表示這是一個device
--225-->中斷使用的中斷號
--226-->將全部i2c_client連在一塊兒的節點
和平臺總線相似,i2c驅動對象使用i2c_driver結構來描述,因此,編寫一個i2c驅動的本質工做就是構造一個i2c_driver對象並將其註冊到內核。咱們先來認識一下這個對象
//include/linux/i2c.h 161 struct i2c_driver { 162 unsigned int class; 167 int (*attach_adapter)(struct i2c_adapter *) __deprecated; 170 int (*probe)(struct i2c_client *, const struct i2c_device_id *); 171 int (*remove)(struct i2c_client *); 174 void (*shutdown)(struct i2c_client *); 175 int (*suspend)(struct i2c_client *, pm_message_t mesg); 176 int (*resume)(struct i2c_client *); 183 void (*alert)(struct i2c_client *, unsigned int data); 188 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); 190 struct device_driver driver; 191 const struct i2c_device_id *id_table; 194 int (*detect)(struct i2c_client *, struct i2c_board_info *); 195 const unsigned short *address_list; 196 struct list_head clients; 197 };
struct i2c_driver
--170-->探測函數,匹配成功以後執行,會將匹配到的i2c_client對象傳入,完成申請資源,初始化,提供接口等工做。
--171-->移除函數,設備消失時會調用,驅動模塊被rmmod時也會先被調用,完成和probe相反的操做。
--174-->這三個方法都是電源管理相關的接口
--190-->代表這是一個設備的驅動類,和platform同樣,用於匹配設備樹的of_match_table域在這裏
--191-->用於使用平臺文件或模塊編寫設備信息時進行匹配使用,至關於platform_driver中的id_table。
--197-->用於將全部i2c_driver聯繫到一塊兒的節點
那麼接下來就是填充對象了,咱們這裏使用的是設備樹匹配,因此of_match_table被填充以下。
struct of_device_id mpu6050_dt_match[] = { {.compatible = "invensense,mpu6050"}, {}, }; struct i2c_device_id mpu6050_dev_match[] = {};
而後將這兩個成員填充到i2c_driver對象以下,這個階段咱們能夠在mpu6050_probe中只填寫prink來測試咱們的驅動方法對象是否有問題。
struct i2c_driver mpu6050_driver = { .probe = mpu6050_probe, .remove = mpu6050_remove, .driver = { .owner = THIS_MODULE, .name = "mpu6050drv", .of_match_table = of_match_ptr(mpu6050_dt_match), }, .id_table = mpu6050_dev_match, };
使用下述API註冊/註銷驅動對象,這個宏和module_platform_driver同樣是內核提供給咱們一個用於快速實現註冊註銷接口的快捷方式,寫了這句以及模塊受權,咱們就能夠靜待各類信息被打印了
module_i2c_driver(mpu6050_driver);
若是測試經過,咱們就要研究如何找到adapter以及如何經過找到的adapter將數據發送出去。沒錯,我說的i2c_msg
68 struct i2c_msg { 69 __u16 addr; /* slave address */ 70 __u16 flags; 71 #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ 72 #define I2C_M_RD 0x0001 /* read data, from slave to master */ 73 #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 74 #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ 75 #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 76 #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ 77 #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ 78 #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ 79 __u16 len; /* msg length */ 80 __u8 *buf; /* pointer to msg data */ 81 };
struct i2c_msg
--69-->從機地址
--70-->操做標誌,I2C_M_RD爲讀(0),寫爲1
--79-->有效數據長度
--80-->裝載有效數據的頭指針
咱們知道,i2c總線上傳入數據是以字節爲單位的,而咱們的通訊類別分爲兩種:讀and寫,對於寫,一般按照下面的時序
Mater | S | I2CAddr+WriteBit | InternalRegisterAddr | DATA | DATA | P | ||||
---|---|---|---|---|---|---|---|---|---|---|
Slave | ACK | ACK | ACK | ACK |
對於讀,一般是按照下面的時序
Mater | S | I2CAddr+WriteBit | InternalRegisterAddr | S | I2CAddr+ReadBit | ACK | NACK | P | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Slave | ACK | ACK | ACK | DATA | DATA |
i2c子系統爲了實現這種通訊方法,爲咱們封裝了i2c_msg結構,對於每個START信號,都對應一個i2c_msg對象,實際操做中咱們會將全部的請求封裝成一個struct i2c_msg[],一次性將全部的請求經過i2c_transfer()發送給匹配到的client的從屬的adapter,由adapter根據相應的algo域以及master_xfer域經過主機驅動來將這些請求發送給硬件上的設備
這是一個經過i2c總線來訪問mpu6050的驅動
//mpu6050_common.h #define MPU6050_MAGIC 'K' union mpu6050_data { struct { short x; short y; short z; }accel; struct { short x; short y; short z; }gyro; unsigned short temp; }; #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data) #define GET_GYRO _IOR(MPU6050_MAGIC, 1, union mpu6050_data) #define GET_TEMP _IOR(MPU6050_MAGIC, 2, union mpu6050_data)
//mpu6050_drv.h #define SMPLRT_DIV 0x19 //陀螺儀採樣率,典型值:0x07(125Hz) #define CONFIG 0x1A //低通濾波頻率,典型值:0x06(5Hz) #define GYRO_CONFIG 0x1B //陀螺儀自檢及測量範圍,典型值:0x18(不自檢,2000deg/s) #define ACCEL_CONFIG 0x1C //加速計自檢、測量範圍及高通濾波,典型值:0x18(不自檢,2G,5Hz) #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 //陀螺儀z軸角速度數據寄存器(高位) #define GYRO_ZOUT_L 0x48 //陀螺儀z軸角速度數據寄存器(低位) #define PWR_MGMT_1 0x6B //電源管理,典型值:0x00(正常啓用) #define WHO_AM_I 0x75 //IIC地址寄存器(默認數值0x68,只讀) #define SlaveAddress 0x68 //MPU6050-I2C地址寄存器 #define W_FLG 0 #define R_FLG 1
//mpu6050.c struct mpu6050_pri { struct cdev dev; struct i2c_client *client; }; struct mpu6050_pri dev; static void mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val) { char txbuf[2] = {reg,val}; struct i2c_msg msg[2] = { [0] = { .addr = client->addr, .flags= W_FLG, .len = sizeof(txbuf), .buf = txbuf, }, }; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); } static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg) { char txbuf[1] = {reg}; char rxbuf[1] = {0}; struct i2c_msg msg[2] = { [0] = { .addr = client->addr, .flags = W_FLG, .len = sizeof(txbuf), .buf = txbuf, }, [1] = { .addr = client->addr, .flags = I2C_M_RD, .len = sizeof(rxbuf), .buf = rxbuf, }, }; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); return rxbuf[0]; } static int dev_open(struct inode *ip, struct file *fp) { return 0; } static int dev_release(struct inode *ip, struct file *fp) { return 0; } static long dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { int res = 0; union mpu6050_data data = {{0}}; switch(cmd){ case GET_ACCEL: data.accel.x = mpu6050_read_byte(dev.client,ACCEL_XOUT_L); data.accel.x|= mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8; data.accel.y = mpu6050_read_byte(dev.client,ACCEL_YOUT_L); data.accel.y|= mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8; data.accel.z = mpu6050_read_byte(dev.client,ACCEL_ZOUT_L); data.accel.z|= mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8; break; case GET_GYRO: data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L); data.gyro.x|= mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8; data.gyro.y = mpu6050_read_byte(dev.client,GYRO_YOUT_L); data.gyro.y|= mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8; data.gyro.z = mpu6050_read_byte(dev.client,GYRO_ZOUT_L); data.gyro.z|= mpu6050_read_byte(dev.client,GYRO_ZOUT_H)<<8; printk("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z); break; case GET_TEMP: data.temp = mpu6050_read_byte(dev.client,TEMP_OUT_L); data.temp|= mpu6050_read_byte(dev.client,TEMP_OUT_H)<<8; printk("temp: %d\n",data.temp); break; default: printk(KERN_INFO "invalid cmd"); break; } printk("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z); res = copy_to_user((void *)arg,&data,sizeof(data)); return sizeof(data); } struct file_operations fops = { .open = dev_open, .release = dev_release, .unlocked_ioctl = dev_ioctl, }; #define DEV_CNT 1 #define DEV_MI 0 #define DEV_MAME "mpu6050" struct class *cls; dev_t dev_no ; static void mpu6050_init(struct i2c_client *client) { mpu6050_write_byte(client, PWR_MGMT_1, 0x00); mpu6050_write_byte(client, SMPLRT_DIV, 0x07); mpu6050_write_byte(client, CONFIG, 0x06); mpu6050_write_byte(client, GYRO_CONFIG, 0x18); mpu6050_write_byte(client, ACCEL_CONFIG, 0x0); } static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id) { dev.client = client; printk(KERN_INFO "xj_match ok\n"); cdev_init(&dev.dev,&fops); alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME); cdev_add(&dev.dev,dev_no,DEV_CNT); mpu6050_init(client); /*自動建立設備文件*/ cls = class_create(THIS_MODULE,DEV_MAME); device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI); printk(KERN_INFO "probe\n"); return 0; } static int mpu6050_remove(struct i2c_client * client) { device_destroy(cls,dev_no); class_destroy(cls); unregister_chrdev_region(dev_no,DEV_CNT); return 0; } struct of_device_id mpu6050_dt_match[] = { {.compatible = "invensense,mpu6050"}, {}, }; struct i2c_device_id mpu6050_dev_match[] = {}; struct i2c_driver mpu6050_driver = { .probe = mpu6050_probe, .remove = mpu6050_remove, .driver = { .owner = THIS_MODULE, .name = "mpu6050drv", .of_match_table = of_match_ptr(mpu6050_dt_match), }, .id_table = mpu6050_dev_match, }; module_i2c_driver(mpu6050_driver); MODULE_LICENSE("GPL");
經過上面的驅動, 咱們能夠在應用層操做設備文件從mpu6050寄存器中讀取原始數據, 應用層以下
int main(int argc, char * const argv[]) { int fd = open(argv[1],O_RDWR); if(-1== fd){ perror("open"); return -1; } union mpu6050_data data = {{0}}; while(1){ ioctl(fd,GET_ACCEL,&data); printf("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z); ioctl(fd,GET_GYRO,&data); printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z); ioctl(fd,GET_TEMP,&data); printf("temp: %d\n",data.temp); sleep(1); } return 0; }
最終能夠獲取傳感器的原始數據以下