在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
20150424 adapter实现i2c驱动程序编写 2015-04-24 Lover雪儿 i2c的驱动程序可以由旧探测方法(adapt)和新探测方法(probe)这两种方法实现. 在i2c_driver中,adapt老方法使用的是attach_adapter作为探测函数,而在新方法中使用的是probe作为探测函数。 1 I2C设备驱动结构: 2 struct i2c_driver { 3 unsigned int class; 4 5 /* 添加和卸载I2C设备,老版本*/ 6 int (*attach_adapter)(struct i2c_adapter *) __deprecated; 7 int (*detach_adapter)(struct i2c_adapter *) __deprecated; 8 9 /* 添加和卸载I2C设备,新版本*/ 10 int (*probe)(struct i2c_client *, const struct i2c_device_id *); 11 int (*remove)(struct i2c_client *); 12 13 /*休眠和唤醒 */ 14 void (*shutdown)(struct i2c_client *); 15 int (*suspend)(struct i2c_client *, pm_message_t mesg); 16 int (*resume)(struct i2c_client *); 17 void (*alert)(struct i2c_client *, unsigned int data); 18 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); 19 struct device_driver driver; 20 const struct i2c_device_id *id_table; 21 22 /* Device detection callback for automatic device creation */ 23 int (*detect)(struct i2c_client *, struct i2c_board_info *); 24 const unsigned short *address_list; 25 struct list_head clients; 26 };
从简单开始学习,我们今天从旧探测方法adapt开始学习实现存储芯片at24cxx的i2c驱动程序,注意本试验的实现必须在linux-2.6.22.6或者更低版本的内核上编译,原因是linux-2.6.30以后的内核i2c.c文件中已经取消了统一的i2c_probe()这个函数了,所以本实验再高版本的内核中是无法实现的. 一、一个简单的I2C驱动 1.定义i2c_client_address_data结构体用于设置设备地址 i2c_client_address_data是在include/linux/i2c.h中定义或者自己定义的一个结构体(如上图所示)。它的一个最主要的作用就是定义设备地址。 在设备地址数组中,I2C_CLIENT_END是指设备地址结束,一旦系统检测到这个值,就会停止扫描。 如上图中所示: 在normal_addr[]数组中填入我们的设备地址(注意设备地址为7位),然后使用I2C_CLIENT_END结束。 如果自己未定义,则会使用i2c.h中默认定义的结构体。 1 struct i2c_client_address_data { 2 unsigned short *normal_i2c; 3 unsigned short *probe; 4 unsigned short *ignore; 5 unsigned short **forces; 6 }; 一般来说,采用默认定义的是不会工作的,因为i2c-core是不可能知道设备地址的,i2c-probe探测函数也是不可能找到设备地址,i2c-probe如果能传入地址的话,是很容易导致系统混乱的从而一起未知的错误。 2.定义i2c_driver结构体 如图所示: 在i2c_driver结构体中定义了i2c设备的名字,探测函数attach_adapter,卸载函数detach_adapter这三个属性。 3.完善探测函数等 如图所示:分别实现了前面i2c_driver结构体中定义的三个属性。 在探测函数中调用i2c.h(linux2.6.22内核以前)中的的i2c_probe函数进行匹配探测。 4.在入口函数中注册i2c驱动 I2C的设备驱动是通过i2c_add_driver(&at24cxx_i2c_driver)向i2c-core注册的,my_driver中的核心是detach和attach函数,在attach中通过i2c_probe探测到总线上的设备并把设备和驱动建立连接以完成设备的初始化。 static int my_attach(struct i2c_adapter *adapter){ return i2c_probe(adapter, &addr_data, my_probe); } 5.在出口函数中删除i2c-driver驱动。 6.在linux下编译结果
7.总结i2c程序的工作流程 简单的来说i2c的实现驱动就分为三个步骤:定义设备地址i2c_client_address_data、定义i2c_driver结构体实现探测、在入口函数中注册i2c_driver结构体。 接下来就细细讲述一下工作流程吧。 ①首先,在i2c_client_address_data的normal_i2c属性中定义好我们设备的设备地址。 ②接下来,i2c_driver就出场了,它的功能是定义i2c设备的名字,探测函数,卸载函数三个属性。 ③当程序在入口函数中注册i2c-driver驱动之后,系统就会根据我们第一步中定义的设备地址,调用attach_adapter函数进行匹配设备地址是否支持,在attach_adapter函数中主要的功能是在调用i2c_probe函数,当系统检测到设备地址匹配时,就会进入i2c_probe函数中干一些重要的事,接着就进入i2c-probe传入的at24cxx_detect函数中实现我们自己的事。 其实总结一下就下面一个流程:at24cxx_attach_adapter -> i2c_probe -> at24cxx_detect ④当我们卸载设备时,会自动调用i2c_driver中定义的卸载函数at24cxx_detach_adapter进行卸载设备。 ⑤最后一步自然就是在出口函数中卸载i2c-driver驱动拉。
附上驱动程序1: 1 #include <linux/kernel.h> 2 #include <linux/init.h> 3 #include <linux/module.h> 4 #include <linux/slab.h> 5 #include <linux/jiffies.h> 6 #include <linux/mutex.h> 7 #include <linux/i2c.h> 8 #include <linux/fs.h> 9 #include <asm/uaccess.h> 10 #include <linux/i2c-id.h> 11 #include <linux/device.h> /* for struct device */ 12 #include <linux/sched.h> /* for completion */ 13 14 #include <linux/ctype.h> 15 #include <linux/types.h> 16 #include <linux/delay.h> 17 18 19 20 static unsigned short ignore[] = {I2C_CLIENT_END}; 21 static unsigned short normal_addr[] = {0X50, I2C_CLIENT_END}; //设备地址:01010000(0x50) 七位 22 23 static struct i2c_client_address_data addr_data = { 24 .normal_i2c = normal_addr, //要发出地址信号,并且得到ACK信号,才能确定是否存在这个设备 25 .probe = ignore, 26 .ignore = ignore, 27 //.forces //强制认为存在这个设备 28 }; 29 30 31 static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind) 32 { 33 printk("enter at24cxx_detect \n"); 34 return 0; 35 } 36 37 //探测时调用函数 38 static int at24cxx_attach_adapter(struct i2c_adapter *adapter) 39 { 40 return i2c_probe(adapter, &addr_data, at24cxx_detect); 41 } 42 43 //卸载函数 44 static int at24cxx_detach_adapter(struct i2c_adapter *client) 45 { 46 printk("enter at24cxx_detach_adapter \n"); 47 return 0; 48 } 49 50 //定义i2c_driver结构体 51 static struct i2c_driver at24cxx_i2c_driver = { 52 .driver = { 53 .name = "at24cxx", 54 }, 55 .attach_adapter = at24cxx_attach_adapter, 56 .detach_adapter = at24cxx_detach_adapter, 57 }; 58 59 static int at24cxx_init(void) 60 { 61 /* 1.分配一个i2c_driver结构体 */ 62 /* 2.设置 */ 63 i2c_add_driver(&at24cxx_i2c_driver); //注册i2c驱动 64 65 return 0; 66 } 67 68 void at24cxx_exit(void) 69 { 70 i2c_del_driver(&at24cxx_i2c_driver); 71 } 72 73 module_init(at24cxx_init); 74 module_exit(at24cxx_exit); 75 MODULE_LICENSE("GPL"); 76 77 78 /* 79 80 1.在I2C中总共有两条线,SDA,SCL,分别是负责数据的发送,时钟脉冲线 81 起始信号,SCL变为低电平,SDA由低电平变为高电平 82 ------------------------------------------------------------------------------ 83 | 起始位S | 7位设备地址 | R/W | 8B数据 | ACK (注8b数据和ACK可以周而复始) | P | 84 ------------------------------------------------------------------------------ 85 设备地址最后一位表示的是读还是写 86 87 刚开始前8个clk里SDA由主机驱动,发送从机设备地址,当从机发现设备地址为自己的时, 88 便会在第9个时钟里SDA由从机驱动,I2C主机释放SDA,由从机驱动SDA 89 写数据:前8个CLK,SDA由主机驱动 90 第9个CLK,SDA由从机驱动 91 写数据:前8个CLK,SDA由从机驱动 92 第9个CLK,SDA由主机驱动 93 停止信号,SCL变为高电平,SDA由低电平变为高电平 94 95 2.i2c驱动框架 96 ---------------------------------------------------------------------- 97 APP: open,read,write 98 ---------------------------------------------------------------------- 99 I2c设备驱动程序:drv_open,drv_read,drv_write, 知道数据的含义 100 ---------------------------------------------------------------------- 101 I2c总线驱动程序:①识别②提供读写函数, 知道如何收发数据 102 ---------------------------------------------------------------------- 103 104 drivers/chip/ //设备驱动程序 105 drivers/i2c/busses/i2c-s3c2410.c //总线驱动程序 106 drivers/i2c/algos //算法 107 ---------------------------------------------------------------------------------------------- 108 BUS 109 i2c_add / \ i2c_add_diver 110 i2c_adapter / \ i2c_driver .id 表示能支持哪些设备 111 / 插槽(适配器) \设备驱动程序 .attach_adapter 接到适配器上去 112 ---------------------------------------------------------------------------------------------- 113 i2c总线驱动程序: 114 1.分配一个i2c_adapter 插槽 115 2.设置,algo结构体,核心算法,.master_xfer, 116 3.注册i2c_add_adapter .master_xfer:发I2C信号的函数 117 ---------------------------------------------------------------------------------------------- 118 i2c_add_adapter ①把结构体放入链表 119 ②从driver链表取出每一项,得到其ID(设备地址) 120 ③使用master_xfer函数发start信号,发设备地址 121 ④如果收到ACK则发现了一个设备client 122 --------------------------------------------------------------------------------------------- 123 i2c设备驱动程序: 124 i2c_add_driver: ①把I2C_driver放入链表 125 ②使用从adapter链表取出”适配器“,使用它的mater_xfer函数法start信号,发设备地址(.id里面) 126 如果能收到ACK信号,则表示发现了一个设备client 127 -------------------------------------------------------------------------------------------- 128 129 写I2C的driver驱动程序: 130 ①分配一个i2c_driver结构体 131 ②设置 132 attach_adapter //它直接调用i2c_probe(adap,设备地址,发现这个设备后要调用的函数) 133 detach_client //卸载驱动后,如果之前有能支持的,则调用它来清理 134 ③注册 135 136 137 138 4.测试1th 139 insmod at24cxx.ko 观察输出信息 140 修改normal_addr为0x60,加载观察输出信息。 141 142 143 */
二、增加I2C设备地址的force属性 不知道大家注意到没有,在i2c_client_address_data结构体中还有一个我们没有使用到的属性.forces,它是用于强制找到设备地址的,现在我们就在前面已经实现好的驱动程序中实现forces属性。 当我们设备不存在时,可以使用.froces属性强制系统认为存在设备,进入probe函数中进行匹配,从而调用at24cxx_detect函数。 编写forces数组 由于.forces属性对应的数据类型为**,即指针的指针,所以此处我们的forces结构体就必须定义为二维数组了, 如图所示,首先我们定义一个force_addr数组,数组中三个元素的意思分别为, ANY_I2C_BUS:适用于任何总线。 0x60:我们伪造的设备地址。 I2C_CLIENT_END:设备地址结束。 接下来,用unsigned shor类型的指针数组进行封装一下,赋值给.forces属性。 编译加载,可以发现我们的probe函数被正常调用了,说明系统已经强制认识这个设备了。
附上驱动程序2: 1 #include <linux/kernel.h> 2 #include <linux/init.h> 3 #include <linux/module.h> 4 #include <linux/slab.h> 5 #include <linux/jiffies.h> 6 #include <linux/mutex.h> 7 #include <linux/i2c.h> 8 #include <linux/fs.h> 9 #include <asm/uaccess.h> 10 11 12 static unsigned short ignore[] = {I2C_CLIENT_END}; 13 static unsigned short normal_addr[] = {0X50, I2C_CLIENT_END}; //设备地址:01010000(0x50) 七位 14 /* 改为0x60的话,由于不存在设备地址为0x60的设备,at24cxx_detect不被调用 */ 15 16 /*static unsigned short force_addr[2][3] = { {ANY_I2C_BUS, 0x60, I2C_CLIENT_END}, // 强制调用at24cxx_detect 17 {ANY_I2C_BUS, 0x60, I2C_CLIENT_END}, 18 };*/ 19 static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END}; 20 static unsigned short * forces[] = {force_addr,NULL}; 21 22 23 static struct i2c_client_address_data addr_data = { 24 .normal_i2c = normal_addr, //要发出地址信号,并且得到ACK信号,才能确定是否存在这个设备 25 .probe = ignore, 26 .ignore = ignore, 27 .forces = forces, //在probe函数中查找,强制认为存在这个设备,从会调用at24cxx_detect 28 }; 29 30 static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind) 31 { 32 printk("enter at24cxx_detect \n"); 33 return 0; 34 } 35 36 //探测时调用函数 37 static int at24cxx_attach_adapter(struct i2c_adapter *adapter) 38 { 39 return i2c_probe(adapter, &addr_data, at24cxx_detect); 40 } 41 42 //卸载函数 43 static int at24cxx_detach_adapter(struct i2c_client *client) 44 { 45 printk("enter at24cxx_detach_adapter \n"); 46 return 0; 47 } 48 49 //定义i2c_driver结构体 50 static struct i2c_driver at24cxx_i2c_driver = { 51 .driver = { 52 .name = "at24cxx", 53 }, 54 .attach_adapter = at24cxx_attach_adapter, 55 .detach_client = at24cxx_detach_adapter, 56 }; 57 58 static int at24cxx_init(void) 59 { 60 /* 1.分配一个i2c_driver结构体 */ 61 /* 2.设置 */ 62 i2c_add_driver(&at24cxx_i2c_driver); //注册i2c驱动 63 64 return 0; 65 } 66 67 void at24cxx_exit(void) 68 { 69 i2c_del_driver(&at24cxx_i2c_driver); 70 } 71 72 module_init(at24cxx_init); 73 module_exit(at24cxx_exit); 74 MODULE_LICENSE("GPL"); 75 76 77 /* 78 79 1.在I2C中总共有两条线,SDA,SCL,分别是负责数据的发送,时钟脉冲线 80 起始信号,SCL变为低电平,SDA由低电平变为高电平 81 ------------------------------------------------------------------------------ 82 | 起始位S | 7位设备地址 | R/W | 8B数据 | ACK (注8b数据和ACK可以周而复始) | P | 83 ------------------------------------------------------------------------------ 84 设备地址最后一位表示的是读还是写 85 86 刚开始前8个clk里SDA由主机驱动,发送从机设备地址,当从机发现设备地址为自己的时, 87 便会在第9个时钟里SDA由从机驱动,I2C主机释放SDA,由从机驱动SDA 88 写数据:前8个CLK,SDA由主机驱动 89 第9个CLK,SDA由从机驱动 90 写数据:前8个CLK,SDA由从机驱动 91 第9个CLK,SDA由主机驱动 92 停止信号,SCL变为高电平,SDA由低电平变为高电平 93 94 2.i2c驱动框架 95 ---------------------------------------------------------------------- 96 APP: open,read,write 97 ---------------------------------------------------------------------- 98 I2c设备驱动程序:drv_open,drv_read,drv_write, 知道数据的含义 99 ---------------------------------------------------------------------- 100 I2c总线驱动程序:①识别②提供读写函数, 知道如何收发数据 101 ---------------------------------------------------------------------- 102 103 drivers/chip/ //设备驱动程序 104 drivers/i2c/busses/i2c-s3c2410.c //总线驱动程序 105 drivers/i2c/algos //算法 106 ---------------------------------------------------------------------------------------------- 107 BUS 108 i2c_add / \ i2c_add_diver 109 i2c_adapter / \ i2c_driver .id 表示能支持哪些设备 110 / 插槽(适配器) \设备驱动程序 .attach_adapter 接到适配器上去 111 ---------------------------------------------------------------------------------------------- 112 i2c总线驱动程序: 113 1.分配一个i2c_adapter 插槽 114 2.设置,algo结构体,核心算法,.master_xfer, 115 3.注册i2c_add_adapter .master_xfer:发I2C信号的函数 116 ---------------------------------------------------------------------------------------------- 117 i2c_add_adapter ①把结构体放入链表 118 ②从driver链表取出每一项,得到其ID(设备地址) 119 ③使用master_xfer函数发start信号,发设备地址 120 ④如果收到ACK则发现了一个设备client 121 --------------------------------------------------------------------------------------------- 122 i2c设备驱动程序: 123 i2c_add_driver: ①把I2C_driver放入链表 124 ②使用从adapter链表取出”适配器“,使用它的mater_xfer函数法start信号,发设备地址(.id里面) 125 如果能收到ACK信号,则表示发现了一个设备client 126 -------------------------------------------------------------------------------------------- 127 128 写I2C的driver驱动程序: 129 ①分配一个i2c_driver结构体 130 ②设置 131 attach_adapter //它直接调用i2c_probe(adap,设备地址,发现这个设备后要调用的函数) 132 detach_client //卸载驱动后,如果之前有能支持的,则调用它来清理 133 ③注册 134 135 136 137 4.测试1th 138 insmod at24cxx.ko 观察输出信息 139 修改normal_addr为0x60,加载观察输出信息。 140 141 142 */
三、完善i2c_probe函数里的参数at24cxx_detect函数 在at24cxx_detect函数中,它的主要功能就是构建一个i2c_client结构体 1 struct i2c_client { 2 unsigned short flags; 3 unsigned short addr; 4 char name[I2C_NAME_SIZE]; 5 struct i2c_adapter *adapter; //定义adapter适配器 6 struct i2c_driver *driver; 7 struct device dev; 8 int irq; 9 char driver_name[KOBJ_NAME_LEN]; 10 struct list_head list; 11 struct completion released; 12 }; 13 14 struct i2c_client代表一个挂载到i2c总线上的i2c从设备,该设备所需要的数据结构,其中包括 15 该i2c从设备所依附的i2c主设备 struct i2c_adapter *adapter 16 该i2c从设备的驱动程序struct i2c_driver *driver 17 作为i2c从设备所通用的成员变量,比如addr, name等 18 该i2c从设备驱动所特有的数据,依附于dev->driver_data下 如图所示。 首先定义一个全局的i2c_client结构体,然后再at24cxx_detect函数中对其进行设置。 61行:分配一个i2c_client 62行:设置设备地址 63行:设置I2C适配器 64行:设置连接i2c_driver结构体 65行:设置client的名字 66行:加载client结构体,当要卸载驱动时,便会调用detach函数,如下图所示: 在卸载函数中,首先卸载client结构体,然后再释放client结构体的内存。 附上驱动程序3: 1 #include <linux/kernel.h> 2 #include <linux/init.h> 3 #include <linux/module.h> 4 #include <linux/slab.h> 5 #include <linux/jiffies.h> 6 #include <linux/mutex.h> 7 #include <linux/i2c.h> 8 9 10 static unsigned short ignore[] = {I2C_CLIENT_END}; 11 static unsigned short normal_addr[] = {0X50, I2C_CLIENT_END}; //设备地址:01010000(0x50) 七位 12 /* 改为0x60的话,由于不存在设备地址为0x60的设备,at24cxx_detect不被调用 */ 13 14 /*static unsigned short force_addr[2][3] = { {ANY_I2C_BUS, 0x60, I2C_CLIENT_END}, // 强制调用at24cxx_detect 15 全部评论
专题导读
热门推荐
热门话题
阅读排行榜
|
请发表评论