从零写iic bus 总线驱动 (s5vp210)

一 步骤:

根据上两篇分析,总结下写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_t	iic_bus_wait;


struct  s5pv210_i2c_xfer_data{ 
	struct i2c_msg		*msg;
	unsigned int		msg_num;
	unsigned int		msg_idx;
	unsigned int		byte_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 mode
	if (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;
		else
			s5pv210_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++;  // 下个 msg

			s5pv210_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");
	else
		dev_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 
	else
		idno = 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 0
data: , 17, 0x11




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值