I2C驱动程序设计(2)—Linux下I2C子系统的介绍

1.I2C子系统架构

 

  • Linux下IIC的架构模型大概可以分为3层:
    • 第一层是I2C的从设备驱动,它包含图中的device driver和i2c-dev。device driver需要用户编写,i2c-dev由内核实现,包含了I2C设备的通用方法,但是用户不能直接使用这个驱动,需要编写一个用户层驱动,它们2个合起来才可以实现一个驱动程序。
    • 第二层总线驱动,它又叫做总线控制器驱动,比如说芯片内部的I2C控制器的使用需要实现一个驱动程序,比如所需要往I2C总线上传输数据它需要什么方法,需要实现那些函数等等。它包含图中的i2c-adapter和adapter-agio。
    • 第三层是i2c-core,I2C总线和I2C设备驱动的中间枢纽,它提供了I2C总线驱动和设备驱动的注册、注销方法等

2.I2C总线驱动

  • 图中我们可以知道编写i2c驱动有2种实现方法。
    • 第一种是用户自己完全写一个i2c的驱动程序。
    • 另一种方法是使用i2c的通用驱动i2c-dev,然后自己设计一个i2c用户模式驱动。
  • 对于第一种情况,应用程序读写i2c设备的流程是比较明了的。
  • 对于第二种情况的读写流程稍微有点复杂,我们先来介绍另一个比较重要的地方,就是i2c控制器驱动,i2c adapter/algorithm。它是直接操作i2c设备的。里面包含2部分的东西,一个是adapter,适配器的意思,另一个是algorithm,算法的意思。
  • 我们先来分析adapter,打开Linux源代码,搜索i2c_adapter:
/*
 * i2c_adapter is the structure used to identify a physical i2c bus along
 * with the access algorithms necessary to access it.
 */
struct i2c_adapter {
	struct module *owner;
	unsigned int id;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;
 
	/* data fields that are valid for all devices	*/
	u8 level; 			/* nesting level for lockdep */
	struct mutex bus_lock;
 
	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */
 
	int nr;
	char name[48];
	struct completion dev_released;
};
  • 在Linux中每一个i2c适配器或者说控制器都会有一个i2c_adapter来描述,里面有一个成员:
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
  • 看看它的定义:
/*
 * The following structs are for those who like to implement new bus drivers:
 * i2c_algorithm is the interface to a class of hardware solutions which can
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 * to name two of the most common.
 */
struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);
 
	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);
};
  • 这个结构里面主要是一些函数指针,最重要的要数master_xfer了。这个函数主要来实现需要在i2c总线上传输数据的方法。比如说CPU需要通过i2c总线往一个i2c设备发送一些数据,这时候就可以使用i2c控制器里面实现的传输方法,比如说xxx_transfer来实现对i2c设备的读写,这样就把i2c数据的传输给封装起来了。
  • 我们接下来分析一下2440上i2c控制器驱动的实现,它的实现在i2c-s3c2410.c文件中实现。
  • 先来分析一下模块初始化和退出函数:
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
 
 static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe,
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
	},
};
 
 
static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);
 
 
static void __exit i2c_adap_s3c_exit(void)
{
	platform_driver_unregister(&s3c24xx_i2c_driver);
}
  • 这里主要通过平台驱动注册了一个设备。设备名叫做s3c24xx_i2c_driver。上面是s3c24xx_i2c_driver的定义,先来分析一些s3c24xx_i2c_probe函数。里面的一些变量赋值和其他一些简单的函数就不分析
/* s3c24xx_i2c_init
 *
 * initialise the controller, set the IO lines and frequency
*/
 
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
	unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
	struct s3c2410_platform_i2c *pdata;
	unsigned int freq;
 
	/* get the plafrom data */
 
	pdata = i2c->dev->platform_data;
 
	/* inititalise the gpio */
 
	if (pdata->cfg_gpio)
		pdata->cfg_gpio(to_platform_device(i2c->dev));
 
	/* write slave address */
 
	writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
 
	dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
 
	writel(iicon, i2c->regs + S3C2410_IICCON);
 
	/* we need to work out the divisors for the clock... */
 
	if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
		writel(0, i2c->regs + S3C2410_IICCON);
		dev_err(i2c->dev, "cannot meet bus frequency required\n");
		return -EINVAL;
	}
 
	/* todo - check that the i2c lines aren't being dragged anywhere */
 
	dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
	dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
 
	return 0;
}
  • 我们可以看到这个函数主要完成了一下的功能:
    • 1、unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;使能中断和应答
    • 2、pdata->cfg_gpio(to_platform_device(i2c->dev));初始化GPIO
    • 3、writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);写从地址
    • 4、if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) ;初始化时钟。
  • 可以发现,这里的初始化代码和裸机的代码也是非常类似的,初始化之后还调用了i2c_add_numbered_adapter函数来注册一个i2c控制器。
  • prob函数中有一个重要的赋值语句i2c->adap.algo    = &s3c24xx_i2c_algorithm;之前说过algorithm是用来实现i2c设备读写方法的。这里面包含2个函数,一个是s3c24xx_i2c_xfer,这个函数用来实现i2c设备的读写,他们依次调用s3c24xx_i2c_doxfer->s3c24xx_i2c_message_start,最终实现设备的读写,这个函数如下:
/* s3c24xx_i2c_message_start
 *
 * put the start of a message onto the bus
*/
 
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
				      struct i2c_msg *msg)
{
	unsigned int addr = (msg->addr & 0x7f) << 1;
	unsigned long stat;
	unsigned long iiccon;
 
	stat = 0;
	stat |=  S3C2410_IICSTAT_TXRXEN;
 
	if (msg->flags & I2C_M_RD) {
		stat |= S3C2410_IICSTAT_MASTER_RX;
		addr |= 1;
	} else
		stat |= S3C2410_IICSTAT_MASTER_TX;
 
	if (msg->flags & I2C_M_REV_DIR_ADDR)
		addr ^= 1;
 
	/* todo - check for wether ack wanted or not */
	s3c24xx_i2c_enable_ack(i2c);
 
	iiccon = readl(i2c->regs + S3C2410_IICCON);
	writel(stat, i2c->regs + S3C2410_IICSTAT);
 
	dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
	writeb(addr, i2c->regs + S3C2410_IICDS);
 
	/* delay here to ensure the data byte has gotten onto the bus
	 * before the transaction is started */
 
	ndelay(i2c->tx_setup);
 
	dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
	writel(iiccon, i2c->regs + S3C2410_IICCON);
 
	stat |= S3C2410_IICSTAT_START;
	writel(stat, i2c->regs + S3C2410_IICSTAT);
}
  • 我们对比2440数据手册中的IIC读写时序:

  •  可以发现读写的流程和数据手册是一致的(肯定是一致的,不然就读写失败了)。
  • 不过后面从ACK period开始就属于中断的内容了,我们需要找到i2c的中断处理函数,肯定是在prob函数里面找的,发现了这么一段话:
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
			  dev_name(&pdev->dev), i2c);
  • 这里面注册了一个中断,s3c24xx_i2c_irq。中断处理程序如下:
/* s3c24xx_i2c_irq
 *
 * top level IRQ servicing routine
*/
 
static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)
{
	struct s3c24xx_i2c *i2c = dev_id;
	unsigned long status;
	unsigned long tmp;
 
	status = readl(i2c->regs + S3C2410_IICSTAT);
 
	if (status & S3C2410_IICSTAT_ARBITR) {
		/* deal with arbitration loss */
		dev_err(i2c->dev, "deal with arbitration loss\n");
	}
 
	if (i2c->state == STATE_IDLE) {
		dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");
 
		tmp = readl(i2c->regs + S3C2410_IICCON);
		tmp &= ~S3C2410_IICCON_IRQPEND;
		writel(tmp, i2c->regs +  S3C2410_IICCON);
		goto out;
	}
 
	/* pretty much this leaves us with the fact that we've
	 * transmitted or received whatever byte we last sent */
 
	i2s_s3c_irq_nextbyte(i2c, status);
 
 out:
	return IRQ_HANDLED;
}

 

发布了222 篇原创文章 · 获赞 226 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章