I2C設(shè)備驅(qū)動(dòng)的編寫(xiě)(二)
作者:曹老師,華清遠(yuǎn)見(jiàn)嵌入式學(xué)院講師。
前面我們說(shuō)了如何I2C用戶模式驅(qū)動(dòng),這種驅(qū)動(dòng)基于I2C子系統(tǒng),但是他對(duì)于應(yīng)用程序開(kāi)發(fā)人員的要求較高,需要應(yīng)用程序開(kāi)發(fā)人員了解硬件的一些東西,比如時(shí)序,地址等等,而多數(shù)時(shí)候應(yīng)用程序開(kāi)發(fā)人員是按照操作文件的方法操作設(shè)備,所以我們更希望用一些更簡(jiǎn)單的接口去訪問(wèn)。也就是我們今天的內(nèi)容——基于I2C子系統(tǒng)的字符驅(qū)動(dòng)。
I2C子系統(tǒng)的代碼分為三部分如圖:
Host:主機(jī)控制器驅(qū)動(dòng)
Device:設(shè)備驅(qū)動(dòng)代碼
Core: 核心代碼,提供設(shè)備與控制器的接口
一、主機(jī)控制器驅(qū)動(dòng)
Linux下主機(jī)控制器驅(qū)動(dòng),大多數(shù)是BSP提供的,這里不多說(shuō),簡(jiǎn)單說(shuō)下它主要干的活。I2C主機(jī)控制器在內(nèi)核里又叫適配器,用結(jié)構(gòu)i2c_adapter描述。
struct i2c_adapter { struct module *owner; unsigned int class; /* classes to allow probing for */ const struct i2c_algorithm *algo; /* the algorithm to access the bus */ …… };
struct i2c_algorithm 提供設(shè)備訪問(wèn)控制器的接口,定義如下:
struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data); u32 (*functionality) (struct i2c_adapter *); };
其中master_xfer就是我們給設(shè)備端提供的接口,這部分內(nèi)容按照芯片手冊(cè)中寄存器的操作實(shí)現(xiàn)數(shù)據(jù)的收發(fā)。
最終我們將i2c_adapter注冊(cè)到系統(tǒng)中,使用如下函數(shù): int i2c_add_numbered_adapter(struct i2c_adapter *);
二、核心代碼
這部分就不說(shuō)了,剛才我們介紹的函數(shù)全部是核心代碼提供,它主要是提供標(biāo)準(zhǔn)的統(tǒng)一的接口。
三、設(shè)備代碼
基于I2C的字符驅(qū)動(dòng)的編寫(xiě)首先我們需要了解幾個(gè)特定的結(jié)構(gòu)。
1、i2c_bus_type
i2c總線結(jié)構(gòu)定義了一些總線相關(guān)的方法,這里我們關(guān)系的是i2c_driver和i2c_client的配備規(guī)則,為什么匹配呢,i2c_client攜帶硬件信息,而i2c_driver只負(fù)責(zé)操作設(shè)備而不管操作的是那個(gè)設(shè)備它需要的硬件信息有i2c_client提供,所以需要i2c_client和i2c_driver協(xié)同操作,而一個(gè)系統(tǒng)中i2c_driver和i2c_client都可能有多個(gè),如何得到自己的另一半就是我所說(shuō)的匹配規(guī)則,i2c_bus_type的匹配規(guī)則定義如下:
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm = &i2c_device_pm_ops, };
static int i2c_device_match(struct device *dev, struct device_driver *drv) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; if (!client) return 0; /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) return 1; driver = to_i2c_driver(drv); /* match on an id table if there is one */ if (driver->id_table) return i2c_match_id(driver->id_table, client) != NULL; return 0; }
我們發(fā)現(xiàn)i2c總線的匹配規(guī)則是id或name兩種,id優(yōu)先級(jí)高。
2、板級(jí)結(jié)構(gòu):
struct i2c_board_info { char type[I2C_NAME_SIZE]; //芯片類(lèi)型,其實(shí)也就是名字,用來(lái)匹配 unsigned short flags; //標(biāo)志位,一些特定的標(biāo)志 unsigned short addr; //地址,從設(shè)備地址,不包括讀寫(xiě)位 void *platform_data; //用來(lái)傳遞一些私有數(shù)據(jù) struct dev_archdata *archdata; //同上 struct device_node *of_node; int irq; };
板子上沒(méi)有一個(gè)I2C的設(shè)備,我們就要注冊(cè)一個(gè)這樣的結(jié)構(gòu)體,到內(nèi)核里邊,這部分代碼一般添加在平臺(tái)代碼里邊,注冊(cè)函數(shù)如下:
i2c_register_board_info(int busnum, struct i2c_board_info const *info,unsigned n); busnum 現(xiàn)在很多CPU有多條I2C總線,這個(gè)參數(shù)表示第幾條總線 info 是一個(gè)結(jié)構(gòu)體數(shù)據(jù),表示我們要注冊(cè)的I2C設(shè)備 n 表示我們注冊(cè)了幾個(gè)I2C設(shè)備
通過(guò)上面函數(shù)就能把設(shè)備注冊(cè)到系統(tǒng)中。結(jié)構(gòu)如圖:
3、i2c_client
這個(gè)結(jié)構(gòu)我們不需要操作,是操作系統(tǒng)即核心代碼自動(dòng)完成,這個(gè)過(guò)程其實(shí)是在注冊(cè)i2c_adapter的時(shí)候完成的。即在函數(shù)i2c_add_numbered_adapter中完成,最終i2c_client攜帶者i2c_board_info和i2c_adapter的信息。
4、i2c_driver
這部分代碼主要負(fù)責(zé)注冊(cè)i2c_driver和匹配相應(yīng)的i2c_client。I2c_driver定義如下:
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; …… };
注冊(cè)函數(shù)如下: int i2c_add_driver(struct i2c_driver *driver);
這個(gè)函數(shù)負(fù)責(zé)注冊(cè)i2c_driver并匹配i2c_client,當(dāng)匹配到了對(duì)于的i2c_client,probe函數(shù)被執(zhí)行,并且i2c_client被以參數(shù)的形式傳遞過(guò)來(lái)。我們可以通過(guò)i2c_client提供的硬件信息和操作接口操作我們想要的設(shè)備。
5、數(shù)據(jù)傳輸
數(shù)據(jù)傳輸結(jié)構(gòu):
struct i2c_msg { __u16 addr; /* slave address */ __u16 flags; #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ #define I2C_M_RD 0x0001 /* read data, from slave to master */ #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */ };
消息的封裝與上節(jié)用戶模式驅(qū)動(dòng)相似,封裝好消息使用如下函數(shù)提交給核心代碼,最終通過(guò)控制器驅(qū)動(dòng)發(fā)送給具體的設(shè)備。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); adap 適配器,由client->adapter獲得。 msgs 消息 num 消息個(gè)數(shù)
通過(guò)上面內(nèi)容我們就可以構(gòu)建我們的基于linux下i2c子系統(tǒng)的設(shè)備驅(qū)動(dòng)了,例程如下:
平臺(tái)代碼添加:
static struct i2c_board_info i2c_devs0[] __initdata = { {I2C_BOARD_INFO("lm75", 0x48),}, }; i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
驅(qū)動(dòng)代碼:
#include < linux/module.h> #include < linux/kernel.h> #include < linux/init.h> #include < linux/fs.h> #include < linux/cdev.h> #include < linux/i2c.h> #include < linux/slab.h> #include < asm/uaccess.h> MODULE_LICENSE ("GPL"); #define LM75_REG_CONF 0x01 static const u8 LM75_REG_TEMP[3] = { 0x00, /* input */ 0x03, /* max */ 0x02, /* hyst */ }; struct lm75_data { u16 temp[3]; /* Register values, 0 = input 1 = max 2 = hyst */ }; static int lm75_major = 250; static int lm75_minor = 0; static int number_of_devices = 1; static dev_t devno = 0; static struct cdev cdev; static struct i2c_client *new_client; struct lm75_data *data; static int lm75_read_value(struct i2c_client *client) { struct i2c_msg msgs[2]; int status; char buf1[2]; char buf2[2]; msgs[0].len = 1; msgs[0].addr = client->addr; // lm75 設(shè)備地址 msgs[0].flags = 0;//write msgs[0].buf = buf1; msgs[0].buf[0] = LM75_REG_TEMP[0]; msgs[1].len = 2;//讀出的數(shù)據(jù) msgs[1].addr = client->addr;// lm75 設(shè)備地址 msgs[1].flags = I2C_M_RD;//read msgs[1].buf = buf2;//存放返回值的地址。 status = i2c_transfer(client->adapter, msgs, 2); if(status < 0) return status; printk("1 = %2x %2x\n", buf2[0], buf2[1]); return (buf2[0] << 8) | buf2[1]; } static ssize_t lm75_read(struct file *file, char __user *buff, size_t count, loff_t *offset) { int status; status = lm75_read_value(new_client); if(status < 0) { return status; } printk("status = %x\n", status); if(copy_to_user(buff, (char *)&status, sizeof(status))) return -EFAULT; return 0; } static int lm75_open(struct inode *inode, struct file *file) { return 0; } static int lm75_release(struct inode *inode, struct file *file) { return 0; } static struct file_operations lm75_fops = { .owner = THIS_MODULE, .read = lm75_read, .open = lm75_open, .release = lm75_release, }; static int lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret = 0; new_client = client; devno = MKDEV(lm75_major, lm75_minor); ret = register_chrdev_region(devno, number_of_devices, "lm75"); if(ret) { printk("failed to register device number\n"); goto err_register_chrdev_region; } cdev_init(&cdev, &lm75_fops); cdev.owner = THIS_MODULE; ret = cdev_add(&cdev, devno, number_of_devices); if(ret) { printk("failed to add device\n"); goto err_cdev_add; } return 0; err_cdev_add: unregister_chrdev_region(devno, number_of_devices); err_register_chrdev_region: kfree(data); return ret; } static int lm75_remove(struct i2c_client *client) { cdev_del(&cdev); unregister_chrdev_region(devno, number_of_devices); return 0; } enum lm75_type { /* keep sorted in alphabetical order */ lm75, lm75a, }; static const struct i2c_device_id lm75_ids[] = { { "lm75", lm75, }, { "lm75a", lm75a, }, { /* LIST END */ } }; static struct i2c_driver lm75_driver = { .driver = { .name = "lm75", }, .probe = lm75_probe, .remove = lm75_remove, .id_table = lm75_ids, }; static int __init s5pc100_lm75_init(void) { return i2c_add_driver(&lm75_driver); } static void __exit s5pc100_lm75_exit(void) { i2c_del_driver(&lm75_driver); } module_init(s5pc100_lm75_init); module_exit(s5pc100_lm75_exit);
|