I2C 驱动
💙
写在前面
-
i2c_client:连接到i2c硬件总线上的设备
-
i2c_adapter:每一个i2c控制器都有一个adapter和一个i2c_client之对应
-
内核中的注册与注销:就是把i2c_adapter_info结构体从链表中删除和添加
-
struct i2c_client介绍
-
i2c_client->addr:从设备唯一的器件地址,一般是7位的地址最后一位为读写位,0写/1读
-
i2c_client->flag:决定设备的器件地址addr是7位的还是10位的
-
i2c_client->name:设备的名字,可以在/sys/bus/i2c/device/找到,或者在/dev/下找到
I2c设备模型:
user-speace(会暴露出来/dev/i2c-x) <------------app
input block net misc cdev
----->probe----->|
struct i2c_client struct i2c_driver
struct bus_type
|
i2c-core(i2c核心)
|
struct i2c_adapter ----> i2c_algorithm
| | | |
devices(连接很多设备) master_xfer(是个函数指针能够对i2c控制器中的数据寄存器,进行读写的方法)
通信方法一般芯片厂商已经实现好啦
|
---------------------------------------------------------------
i2c控制器驱动:
1、ack 2、波特率 3、中断 4、地址 5、读写
|
i2c控制器(数据寄存器)
-----E2PROM----MMA7660----FT5X06—GSL680----
i2c0控制器(寄存器数据)->i2c_adapter
i2c1控制器(寄存器数据)->i2c_adapter
i2c2控制器(寄存器数据)->i2c_adapter
提示:在硬件上有多少个i2c设备,在内核里面就有多少个i2c_adaper结构体。实际运用上,当设备被挂到i2c总线上了,要用i2c-client和i2c-driver。[假如]想看门狗这类在芯片内部的设备就可以用platform总线即可
i2c_adapter : i2c控制器 --> 为每一个i2c-client分配一个i2c_adapter
369 /*
370 * i2c_adapter is the structure used to identify a physical i2c bus along
371 * with the access algorithms necessary to access it.
372 */
373 struct i2c_adapter {
374 struct module *owner;
375 unsigned int class; /* classes to allow probing for */
376 const struct i2c_algorithm *algo; /* the algorithm to access the bus */
377 void *algo_data;
378
379 /* data fields that are valid for all devices */
380 struct rt_mutex bus_lock;
381
382 int timeout; /* in jiffies */
383 int retries;
384 struct device dev; /* the adapter device */
385
386 int nr;
387 char name[48];//i2c 控制器的名字
388 struct completion dev_released;
389
390 struct mutex userspace_clients_lock;
391 struct list_head userspace_clients;
392 };
393 #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
394
i2c_algorithm:i2c用来传输信息的结构体 --> 里面有收发数据的函数指针master_xfer —>i2c_tansfer()
master_xfer:飞利浦提出的i2c总线(一般我们只用这个方式)
smbus_xfer:inter简化飞利浦后的总线
352 struct i2c_algorithm {
353 /* If an adapter algorithm can't do I2C-level access, set master_xfer
354 to NULL. If an adapter algorithm can do SMBus access, set
355 smbus_xfer. If set to NULL, the SMBus protocol is simulated
356 using common I2C messages */
357 /* master_xfer should return the number of messages successfully
358 processed, or a negative value on error */
359 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
360 int num);//这个是个函数指针 --->i2c_tansfer()
361 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
362 unsigned short flags, char read_write,
363 u8 command, int size, union i2c_smbus_data *data);
364
365 /* To determine what the adapter supports */
366 u32 (*functionality) (struct i2c_adapter *);
367 };
i2c_client 内核会为每一个i2c设备创建一个设备结构体,并指向一个adapter控制器
eg:一个EEPROM设备结构体会指向一个adapter控制器
220 struct i2c_client {
221 unsigned short flags; /* 用来表示这个地址是7位的,还是10位的 */
222 unsigned short addr; /* chip address - NOTE: 7bit */
223 /* addresses are stored in the */
224 /* _LOWER_ 7 bits */
225 char name[I2C_NAME_SIZE]; /* i2c设备的名字/sys/bus/i2c/device/xxxxx */
226 struct i2c_adapter *adapter; /* the adapter we sit on 表示当前这个client属于哪一个adapter(控制器) */
227 struct i2c_driver *driver; /* and our access routines 表示当前这个client与哪一个driver 去 probe */
228 struct device dev; /* the device structure */
229 int irq; /* irq issued by device i2c中断线产生中断 */
230 struct list_head detected;
231 };
其他I2c驱动实现方式:
242 //从dev中提取出来数据
static inline void *i2c_get_clientdata(const struct i2c_client *dev )
243 {
244 return dev_get_drvdata(&dev->dev);
245 }
246
//把data数据保存到dev中
247 static inline void i2c_set_clientdata(struct i2c_client *dev, void *data)
248 {
249 dev_set_drvdata(&dev->dev, data);
250 }
当我们去写i2c驱动的时候,不会去创建i2c_client,而是去创建一个i2c_board_info, struct i2c_client是内核创建的
这个结构体一般在板级目录下,像我的就在eg: xxx/arch/arm/plat-s5p6818/x6818/device.c
395 #if defined(CONFIG_TOUCHSCREEN_GSLX680)
396 #include <linux/i2c.h>
397 #define GSLX680_I2C_BUS (1)
398
399 static struct i2c_board_info __initdata gslX680_i2c_bdi = {
400 .type = "gslX680",
401 .addr = (0x40),
402 .irq = PB_PIO_IRQ(CFG_IO_TOUCH_PENDOWN_DETECT),
403 };
404 #endif
273 struct i2c_board_info {
274 char type[I2C_NAME_SIZE]; //kernel会把这个数据存放到i2c_client中name里
275 unsigned short flags; //kernel会把这个数据存放到i2c_client中flags里
276 unsigned short addr; //kernel会把这个数据存放到i2c_client中addr里
277 void *platform_data; //保存你自己想存入的一些数据
278 struct dev_archdata *archdata;
279 struct device_node *of_node;
280 int irq;//kernel会把这个数据存放到i2c_client中irq里
/*irq中断线:这个设备连的是哪个GPIO,然后把这个GPIO编号转化为中断线 -->GPIO_TO_IRQ()/PB_PIO_IRQ()*/
281 };
当我们创建完i2c_board_info后,内核会自动为我们创建i2c_client.内核会把我们写到i2c_board_info中的信息填入到i2c_client,eg:name\flag\addr\irq,继续往下看就知道为什么会这样
22 struct i2c_devinfo {
23 struct list_head list;//表示这个结构体是一个节点
24 int busnum;
25 struct i2c_board_info board_info;
26 };
-
i2c_devinfo表示在链表中的一个节点,每一个i2c_devinfo结构体都包含一个i2c_board_info,当内核启动时会遍历这个链表,把所有i2c_devinfo拿出来,再把board_info拿出来,把board_info的信息传入到i2c_client中,然后所有的i2c_client都创建出来了
node node node i2c_board_list:链---------(i2c_devinfo)------------(i2c_devinfo)-----------(i2c_devinfo)----------表 | | | i2c_board_info i2c_board_info i2c_board_info
i2c设备要在内核启动的时候就给注册了,要是写成模块是不好用的
/把i2c_board_info注册到i2c_devinfo的list中,把你写的i2c_board_info结构体,写到内核启动就注册/
注册i2c_board_info
*i2c_register_board_info(int busnum, struct i2c_board_info const info, unsigned int len)
#busnum:在第几个i2c总线上面,比如i2c总线0-1-2-3-4…
#info:把你要注册的i2c_board_info写进来,可以传进去一个数组进去
#len:这个代表数组元素的个数
64 int __init
65 i2c_register_board_info(int busnum,
66 struct i2c_board_info const *info, unsigned len)
67 {
68 int status;
69
70 down_write(&__i2c_board_lock); //共享资源,加读写锁
71
72 /* dynamic bus numbers will be assigned after the last static one */
73 if (busnum >= __i2c_first_dynamic_bus_num)
74 __i2c_first_dynamic_bus_num = busnum + 1;
75 //遍历这个数组,把每一个board_info提取出来,依次放入到i2c_board_list上的每一个devinfo节点上,如上图所示
76 for (status = 0; len; len--, info++) {
77 struct i2c_devinfo *devinfo;
78
79 devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
80 if (!devinfo) {
81 pr_debug("i2c-core: can't register boardinfo!\n");
82 status = -ENOMEM;
83 break;
84 }
85
86 devinfo->busnum = busnum;
87 devinfo->board_info = *info;
88 list_add_tail(&devinfo->list, &__i2c_board_list);
89 }
90
91 up_write(&__i2c_board_lock);
92
93 return status;
94 }
多说一句:
添加adapter:内核启动会自动注册每一个adapter
883 /**
884 * i2c_add_adapter - declare i2c adapter, use dynamic bus number
885 * @adapter: the adapter to add
886 * Context: can sleep
887 *
888 * This routine is used to declare an I2C adapter when its bus number
889 * doesn't matter. Examples: for I2C adapters dynamically added by
890 * USB links or PCI plugin cards.
891 *
892 * When this returns zero, a new bus number was allocated and stored
893 * in adap->nr, and the specified adapter became available for clients.
894 * Otherwise, a negative errno value is returned.
895 */
896 int i2c_add_adapter(struct i2c_adapter *adapter)
897 {
898 int id, res = 0;
899
900 retry:
901 if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
902 return -ENOMEM;
903
904 mutex_lock(&core_lock);
905 /* "above" here means "above or equal to", sigh */
906 res = idr_get_new_above(&i2c_adapter_idr, adapter,
907 __i2c_first_dynamic_bus_num, &id);
908 mutex_unlock(&core_lock);
909
910 if (res < 0) {
911 if (res == -EAGAIN)
912 goto retry;
913 return res;
914 }
915
916 adapter->nr = id; //i2c总线的编号
917 return i2c_register_adapter(adapter);
918 }
919 EXPORT_SYMBOL(i2c_add_adapter);
总结在应用大致分为两步:
1、写一个i2c_board_info结构体 2、把它注册到内核启动中
–/实际运用:/–
写一个我这边的触摸屏驱动:gslx680触摸屏硬件
重点要知道怎么去移植,这个驱动只是部分开源。
/GSLX680首先要知道:/
1、硬件位与那个i2c总线上面
2、irq 因为我们需要去申请中断线
3、wake 用于休眠唤醒的
399 static struct i2c_board_info __initdata gslX680_i2c_bdi = {
400 .type = "gslX680",
401 .addr = (0x40), //设备地址,手册或厂家会告知
402 .irq = PB_PIO_IRQ(CFG_IO_TOUCH_PENDOWN_DETECT), //GPIOB30
403 };
/*注册i2c_board_info结构体*/
/*上面有对这个结构体做说明 参数1:位与第几个i2c总线 参数2:一个装有i2c_board_info的数组,参数3:数组里面一共有几个结构体*/
/*当然这就说明在同一个i2c总线上面可以连接多个i2c设备*/
i2c_register_board_info(GLSX680_I2C_BUS, &glsX680_i2c_bdi, 1);
/*****************************构造多个设备*****************************/
static struct i2c_board_info __initdata gslX680_i2c_bdi[] = {
{
.type = "gslX680",
.addr = (0x40), //设备地址,手册或厂家会告知
.irq = PB_PIO_IRQ(CFG_IO_TOUCH_PENDOWN_DETECT), //GPIOB30
},
{
.type = "EEPROM",
.addr = (0x32), //设备地址,手册或厂家会告知
//假如不能参生中断
},
};