从零写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

0 0
原创粉丝点击