从零写iic bus 总线驱动 (s5vp210)
来源:互联网 发布:中国新闻周刊 知乎 编辑:程序博客网 时间:2024/06/09 14:05
一 步骤:
根据上两篇分析,总结下写iic bus 驱动的步骤 :
1.probe:
软件方面:
分配,设置,注册 i2c_adapter结构体。
初始化一些辅助变量。
硬件方面:
获得和使能时钟
获得和映射相关寄存器
初始化iic总线控制器
注册中断
2.实现iic 数据传输的算法
master_xfer
二 代码
由于前面两篇博客分析过,现在直接贴代码:
#include <linux/kernel.h>#include <linux/module.h>//#define DEBUG 1 // 打开调试log#include <linux/i2c.h>#include <linux/init.h>#include <linux/time.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/err.h>#include <linux/platform_device.h>#include <linux/clk.h>#include <linux/cpufreq.h>#include <linux/slab.h>#include <linux/io.h>#include <asm/irq.h>#include <plat/regs-iic.h>#include <plat/iic.h>#include <linux/gpio.h>#include <plat/gpio-cfg.h>static struct clk*clk;static struct resource*ioarea;static spinlock_t iic_bus_lock;static wait_queue_head_tiic_bus_wait;struct s5pv210_i2c_xfer_data{ struct i2c_msg*msg;unsigned intmsg_num;unsigned intmsg_idx;unsigned intbyte_ptr;int state;};struct s5pv210_i2c_xfer_data s5pv210_i2c_xfer_data;struct s5pv210_i2c_regs {unsigned int iiccon;unsigned int iicstat;unsigned int iicadd;unsigned int iicds;unsigned int iiclc;};/* i2c controller state */enum s5pv210_i2c_state {STATE_IDLE,STATE_START,STATE_READ,STATE_WRITE,STATE_STOP};static struct s5pv210_i2c_regs *s5pv210_i2c_regs;static inline void s5pv210_i2c_stop(int ret){printk("neo: STOP\n");/* stop the transfer */s5pv210_i2c_regs->iicstat &= ~(1<<5) ; /* 重新设置状态 */s5pv210_i2c_xfer_data.state = STATE_STOP;s5pv210_i2c_xfer_data.byte_ptr = 0;s5pv210_i2c_xfer_data.msg = NULL;s5pv210_i2c_xfer_data.msg_idx++;s5pv210_i2c_xfer_data.msg_num = 0;if (ret)s5pv210_i2c_xfer_data.msg_idx = ret;printk("neo: master_complete %d\n", ret);/* 唤醒应用程序 */wake_up(&iic_bus_wait);/* disable irq */s5pv210_i2c_regs->iiccon &= ~((1<<5));}static void s5pv210_i2c_message_start(struct i2c_msg *msg){unsigned int addr = (msg->addr & 0x7f) << 1; // 获得地址printk("neo: START\n");// Master receive modeif (msg->flags & I2C_M_RD){addr= (addr | 1);s5pv210_i2c_regs->iicstat = 0x90;}else// Master transmit mode{s5pv210_i2c_regs->iicstat = 0xd0;}s5pv210_i2c_regs->iicds = addr;printk("neo: iicadd = 0x%08x,iiccon = 0x%08x,iicstat =0x%08x,%s,%d\n" ,s5pv210_i2c_regs->iicds ,s5pv210_i2c_regs->iiccon,s5pv210_i2c_regs->iicstat,__FUNCTION__, __LINE__ );ndelay(50); // 为了使地址能传到 sda线上/* 重新启动传输 这么写是因为iic 发出stop后会清楚中断 ack使能 所以必须重新启动iic总线 * 另外iiccon 的bit4也必须注意,在中断退出时已经清了中断,此时不需要重新设置该位,否则就会出错*/s5pv210_i2c_regs->iiccon |= (1<<7) | (0<<6) | (1<<5) | (11-1); s5pv210_i2c_regs->iicstat |= (1<<5); printk("neo: iicadd = 0x%08x,iiccon = 0x%08x,iicstat =0x%08x,%s,%d\n" ,s5pv210_i2c_regs->iicds ,s5pv210_i2c_regs->iiccon,s5pv210_i2c_regs->iicstat,__FUNCTION__, __LINE__ );}static inline int is_lastmsg(void){return s5pv210_i2c_xfer_data.msg_idx >= (s5pv210_i2c_xfer_data.msg_num - 1);}static inline int is_byteend(void){return s5pv210_i2c_xfer_data.byte_ptr >= s5pv210_i2c_xfer_data.msg->len;}static inline int is_bytelast(void){return s5pv210_i2c_xfer_data.byte_ptr == s5pv210_i2c_xfer_data.msg->len-1;}static irqreturn_t s5pv210_i2c_irq(int irqno, void *dev_id){printk("neo: state%d\n" ,s5pv210_i2c_xfer_data.state );printk("neo: %s enter,%d\n" ,__FUNCTION__, __LINE__ );if (s5pv210_i2c_regs->iicstat & S3C2410_IICSTAT_ARBITR) { printk("neo:deal with arbitration loss\n");return -ENODATA ;}switch (s5pv210_i2c_xfer_data.state){printk("neo: state%d\n" ,s5pv210_i2c_xfer_data.state );case STATE_IDLE:printk("%s: called in STATE_IDLE\n", __func__);return ENODEV; case STATE_START:/* 没有ack 返回 错误 */printk("neo: STATE_START \n" );if (s5pv210_i2c_regs->iicstat & S3C2410_IICSTAT_LASTBIT) { printk("neo:ack was not received\n");s5pv210_i2c_stop(-ENXIO);break;}/* just i2c probe to find devices. */if (is_lastmsg() && s5pv210_i2c_xfer_data.msg->len == 0) {printk("neo:ack was received\n");s5pv210_i2c_stop(0);break;}/* 下一个状态 */if (s5pv210_i2c_xfer_data.msg->flags & I2C_M_RD)s5pv210_i2c_xfer_data.state = STATE_READ;elses5pv210_i2c_xfer_data.state = STATE_WRITE;if (s5pv210_i2c_xfer_data.state == STATE_READ)goto prepare_read;case STATE_WRITE:printk("neo: WRITE START\n");/* 如果这个消息中还有数据,那么就把该数据写入iic 总线 */if (!is_byteend()) { printk("neo: WRITE: Next Byte\n");s5pv210_i2c_regs->iicds = s5pv210_i2c_xfer_data.msg->buf[s5pv210_i2c_xfer_data.byte_ptr++];ndelay(50);break;}/* 还有消息 */else if (!is_lastmsg()) { printk("neo: WRITE: Next Message\n");s5pv210_i2c_xfer_data.byte_ptr = 0;s5pv210_i2c_xfer_data.msg_idx++;s5pv210_i2c_xfer_data.msg++; // 下个 msgs5pv210_i2c_message_start(s5pv210_i2c_xfer_data.msg);s5pv210_i2c_xfer_data.state = STATE_START;break;}/* 没有消息且消息中没有数据,则停止 */else{printk("neo: WRITE: NO Message NO Byte\n");s5pv210_i2c_stop(0);break;}case STATE_READ:// 读出数据s5pv210_i2c_xfer_data.msg->buf[s5pv210_i2c_xfer_data.byte_ptr++] = s5pv210_i2c_regs->iicds;// 第一次start时 发完地址后并没有数据需要读取 prepare_read: /* 消息的最后一个字节 */if (is_bytelast()) {printk("neo: READ: Last Byte\n");/* 并且是最后一个消息 此时不发送ack*/if (is_lastmsg()) printk("neo: READ: Last Message\n");s5pv210_i2c_regs->iiccon &= ~(1<<7); } /* 否则该消息中没有数据了 */else if (is_byteend()) {printk("neo: READ: NO Byte\n");/* 并且是最后一个消息 此时停止*/if (is_lastmsg()) { printk("neo: READ: Last Message NO Byte\n");printk("neo: READ: Send Stop\n");s5pv210_i2c_stop(0);} /* 并且此时还有消息 */else {printk("neo: READ: Next Transfer\n");s5pv210_i2c_xfer_data.byte_ptr = 0;s5pv210_i2c_xfer_data.msg_idx++;s5pv210_i2c_xfer_data.msg++;}}break;}// 清中断s5pv210_i2c_regs->iiccon &= ~(S3C2410_IICCON_IRQPEND);return IRQ_HANDLED;}static int s5pv210_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num){static int cnt=0;int ret,timeout;printk("neo: s5pv210_i2c_xfer cnt = %d\n" , ++cnt); spin_lock_irq(&iic_bus_lock);/*初始化 msg *//* 应用程序会调用算法函数,并且会发来几个msg,驱动需要将这些msg读进来 或者发出去*/s5pv210_i2c_xfer_data.msg = msgs;s5pv210_i2c_xfer_data.msg_num = num;s5pv210_i2c_xfer_data.byte_ptr = 0;s5pv210_i2c_xfer_data.msg_idx = 0;s5pv210_i2c_xfer_data.state = STATE_START;s5pv210_i2c_message_start(msgs);spin_unlock_irq(&iic_bus_lock);timeout = wait_event_timeout(iic_bus_wait, s5pv210_i2c_xfer_data.msg_num == 0, HZ * 5);ret = s5pv210_i2c_xfer_data.msg_idx ;if (timeout == 0){dev_dbg(NULL, "neo:timeout\n");return -ETIMEDOUT; }else if (ret != num)dev_dbg(NULL, "neo:incomplete xfer \n");elsedev_dbg(NULL, "neo:complete xfer \n");/* ensure the stop has been through the bus */udelay(10);return ret; }static u32 s5pv210_i2c_func(struct i2c_adapter *adap){return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;}static const struct i2c_algorithm s5pv210_i2c_algo = {.master_xfer= s5pv210_i2c_xfer,.functionality= s5pv210_i2c_func,};/* 1. 分配/设置i2c_adapter*/static struct i2c_adapter s5v210_i2c_adapter = {.name = "s5pv210_i2c",.algo = &s5pv210_i2c_algo,.owner = THIS_MODULE,.class = I2C_CLASS_HWMON | I2C_CLASS_SPD,};static void s5pv210_i2c_init(void){/* 3.3.1 配置iic相关gpio */// 将gpd0 gpd1 两个gpio 配置为 scl sda ,并且不需要上拉使能s3c_gpio_cfgall_range(S5PV210_GPD1(0), 2, S3C_GPIO_SFN(2), S3C_GPIO_PULL_NONE); /* 3.3.2 初始化iic相关寄存器 *//* bit[7] = 1, 使能ACK * bit[6] = 0, IICCLK = PCLK/16 * bit[5] = 1, 使能中断 * bit[3:0] = (11-1), Tx clock = IICCLK/16 * PCLK = 66700kHz, IICCLK = 4168kHz, Tx Clock = 378khz */s5pv210_i2c_regs->iiccon = (1<<7) | (0<<6) | (1<<5) | (11-1); s5pv210_i2c_regs->iicadd= 0x10;s5pv210_i2c_regs->iiclc = (3 << 0) | (1<<2) ; s5pv210_i2c_regs->iicstat = (1<<4); // I2C串行输出使能(Rx/Tx)}static int neo_i2c_bus_s5pv210_init(void){int ret ;/*2. 辅助变量操作 */spin_lock_init(&iic_bus_lock);init_waitqueue_head(&iic_bus_wait);/* 3. 相关硬件操作 *//* 3.1 使能相关时钟 *///clk = clk_get(NULL, "i2c");clk = clk_get(NULL, "i2c");printk("neo: clock source %p\n", clk);clk_enable(clk);/* 3.2 映射相关寄存器 *///ioarea = request_mem_region(0xE1800000,sizeof(struct s5pv210_i2c_regs), "s5pv210-i2c");s5pv210_i2c_regs = ioremap(0xE1800000 , sizeof(struct s5pv210_i2c_regs)); // 这里只实现 iic控制器 0 的驱动printk( "neo:registers %p \n",s5pv210_i2c_regs);/* 3.3 初始化iic */s5pv210_i2c_init();/* 3.4 注册中断 */ret = request_irq(IRQ_IIC, s5pv210_i2c_irq, IRQF_DISABLED,"s5pv210-iic", NULL);if(ret < 0){dev_err(NULL, "cannot claim IRQ %d\n", IRQ_IIC);free_irq(IRQ_IIC, NULL);}/* 4 注册i2c_adapter */i2c_add_numbered_adapter(&s5v210_i2c_adapter);printk("neo: init over\n");return 0 ;}static void neo_i2c_bus_s5pv210_exit(void){i2c_del_adapter(&s5v210_i2c_adapter);free_irq(IRQ_IIC, NULL);iounmap(s5pv210_i2c_regs);clk_disable(clk);clk_put(clk);}module_init(neo_i2c_bus_s5pv210_init);module_exit(neo_i2c_bus_s5pv210_exit);MODULE_LICENSE("GPL");
三 调试
1.ioremap 相关寄存器之后无法写进寄存器。
一开始做实验时出现中断无法进入,后来就打印寄存器的值,发现都是0(即是reset value)。 后来就百度了下,发现百度有人说寄存器值写不进去可能跟没有获得clk 有关,我当时就纳闷了,检查了一遍代码 clk = clk_get(NULL, "i2c"); clk_enable(clk); 尼玛没问题啊??? 后来就硬着头皮加了句 printk("neo: clock source %p\n", clk);果然,打印出来的值为0xfffffffe ,一看就有问题,后来就跟进去看了下clk_get的源码:
查到了原因:
原来clk_get函数是定义在arch\arm\plat-samsung\Clock.c中的,这个文件应该是三星自己添加的,和内核发布的不一样,当clk_get 的第一个参数是NULL时会返回-1,这样就会得到错误的时钟源,此时呢我就将计就计,将//idno = -1; 改为idno = 0;这样就能获得正确的时钟源了!当获得正确的时钟源之后,寄存器就能正常读写,此时就可以进入中断了。
struct clk *clk_get(struct device *dev, const char *id){struct clk *p;struct clk *clk = ERR_PTR(-ENOENT);int idno;if (dev == NULL || !dev_is_platform_device(dev))//idno = -1; idno = 0; // 这边改下 改为 0 elseidno = to_platform_device(dev)->id;spin_lock(&clocks_lock);list_for_each_entry(p, &clocks, list) {if (p->id == idno && strcmp(id, p->name) == 0 && try_module_get(p->owner)) {clk = p;break;}}/* check for the case where a device was supplied, but the * clock that was being searched for is not device specific */if (IS_ERR(clk)) {list_for_each_entry(p, &clocks, list) {if (p->id == -1 && strcmp(id, p->name) == 0 && try_module_get(p->owner)) {clk = p;break;}}}spin_unlock(&clocks_lock);return clk;}
2. ./i2c_usr_test /dev/i2c/0 0x50 w 0 0x11 和 ./i2c_usr_test /dev/i2c/0 0x50 r 0
用应用程序写0地址的值,再读0 地址的值,两个值不一样:
利用内核自带的I2c-dev.c 的驱动测试, 先向at24cxx的0地址写一个字节的数据,再从0地址读出一个字节,发现读出来的值有误,通过log 看到iic发出读命令时只发出了一个start信号,不应该啊,应该有两次才对,通过内核自带的i2c-s3c2410.c 打印出第二次start信号下为 iicds = 0x000000a1,iiccon = 0x000000ba,iicstat =0x000000b0,很明显iiccon 控制器需要在s5pv210_i2c_message_start函数中再重新设置一下,因为在s5pv210_i2c_stop中会disable ack和传输,所以必须重新设置。另外中断位由于在中断传输结束后会清0,所以在s5pv210_i2c_message_start中设置iiccon时就不用重新设置中断位了,否则会出错。
3. 最后附上打印log
[root@FriendlyARM /]# ./i2c_usr_test /dev/i2c/0 0x50 w 0 0x11[ 30.517035] neo: s5pv210_i2c_xfer cnt = 32[ 30.517078] neo: START[ 30.517105] neo: iicadd = 0x000000a0,iiccon = 0x0000000a,iicstat =0x000000d1,s5pv210_i2c_message_start,105[ 30.517186] neo: iicadd = 0x000000a0,iiccon = 0x000000aa,iicstat =0x000000f1,s5pv210_i2c_message_start,110[ 30.520517] neo: state1[ 30.522940] neo: s5pv210_i2c_irq enter,132[ 30.527013] neo: STATE_START [ 30.529959] neo: WRITE START[ 30.532818] neo: WRITE: Next Byte[ 30.536138] neo: state3[ 30.538539] neo: s5pv210_i2c_irq enter,132[ 30.542610] neo: WRITE START[ 30.545470] neo: WRITE: Next Byte[ 30.548898] neo: state3[ 30.551190] neo: s5pv210_i2c_irq enter,132[ 30.555262] neo: WRITE START[ 30.558122] neo: WRITE: NO Message NO Byte[ 30.562194] neo: STOP[ 30.564448] neo: master_complete 0[root@FriendlyARM /]# ./i2c_usr_test /dev/i2c/0 0x50 r 0 [ 89.173515] neo: s5pv210_i2c_xfer cnt = 33[ 89.173557] neo: START[ 89.173584] neo: iicadd = 0x000000a0,iiccon = 0x0000008a,iicstat =0x000000d0,s5pv210_i2c_message_start,105[ 89.173666] neo: iicadd = 0x000000a0,iiccon = 0x000000aa,iicstat =0x000000f0,s5pv210_i2c_message_start,110[ 89.177016] neo: state1[ 89.179420] neo: s5pv210_i2c_irq enter,132[ 89.183492] neo: STATE_START [ 89.186439] neo: WRITE START[ 89.189298] neo: WRITE: Next Byte[ 89.192618] neo: state3[ 89.195018] neo: s5pv210_i2c_irq enter,132[ 89.199090] neo: WRITE START[ 89.201950] neo: WRITE: Next Message[ 89.205503] neo: START[ 89.207845] neo: iicadd = 0x000000a1,iiccon = 0x000000ba,iicstat =0x000000b0,s5pv210_i2c_message_start,105[ 89.217465] neo: iicadd = 0x000000a1,iiccon = 0x000000ba,iicstat =0x000000b0,s5pv210_i2c_message_start,110[ 89.227110] neo: state1[ 89.229508] neo: s5pv210_i2c_irq enter,132[ 89.233580] neo: STATE_START [ 89.236553] neo: state2[ 89.238953] neo: s5pv210_i2c_irq enter,132[ 89.243026] neo: READ: Last Byte[ 89.246232] neo: READ: Last Message[ 89.249724] neo: state2[ 89.252125] neo: s5pv210_i2c_irq enter,132[ 89.256197] neo: READ: NO Byte[ 89.259230] neo: READ: Last Message NO Byte[ 89.263476] neo: READ: Send Stop[ 89.266682] neo: STOP[ 89.268936] neo: master_complete 0data: , 17, 0x11
- 从零写iic bus 总线驱动 (s5vp210)
- IIC总线从零梳理(结合STM32平台)
- IIC总线驱动架构
- IIC总线驱动基础知识
- IIC总线驱动基础知识
- iic总线驱动框架
- Linux-IIC驱动(1)-IIC总线介绍
- ARM2410 IIC总线驱动基础知识
- IIC总线及其驱动代码
- 如何从零写linux,lcd驱动
- iic总线驱动(适配器驱动)详解
- 写Linux应用读写IIC 总线上的24c02 ,验证驱动是否正确
- Linux下IIC总线驱动 备忘
- Linux中IIC总线驱动分析
- Linux中IIC总线驱动分析
- IIC总线和外设驱动(一)
- 单片机——iic总线驱动
- /sys/bus一个总线设备驱动
- 114啦开源真的能让草根站长受益?_0
- Oracle 高水位(HWM: High Water Mark) 说明
- jdbc
- 长征路上的益友——好的名人博客地址
- poj1753--Flip Game
- 从零写iic bus 总线驱动 (s5vp210)
- 关于集合
- 关于数组类型的外部变量的声明
- Redis 数据结构之ziplist
- ognl.MethodFailedException:
- Apache 性能调优
- oracle所有对象汇总
- IBM CEO罗睿兰:预计硬件业务在2015年实现增长
- Adnroid ActionBar 各种用法