Linux I2C 驱动分析(一)

来源:互联网 发布:抽象工厂模式特点java 编辑:程序博客网 时间:2024/04/28 07:38


每学习一种驱动类型我都希望把我的学习经历记录下来。今天我就想利用am9开发板上的I2C设备AT24C02把I2C的驱动做一个深入的学习。


一. I2c体系总述

 Linux I2C驱动体系结构主要由3部分组成,即I2C核心、I2C总线驱动和I2C设备驱动。I2C核心是I2C总线驱动和I2C设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了I2C中设备与适配器的沟通。而I2C总线驱动属于平台驱动,它主要实现了提供给I2C设备驱动使用的通信方法(即I2C主控制如何实现对I2C设备的读写)。I2C设备驱动有两种一种是内核编写好的的通用型的I2C驱动,它给我们提供了一些对设备进行操作的的接口。我们并不需要再去别写I2C驱动,当是我们在应用程序当中需要涉及大量对硬件操作的内容(这种应用程序也叫用户态驱动)。另一种I2C驱动需要我们知己编写加载到内核当中(这一种驱动我们下一节再进行讲解)。


二 I2C总线驱动的分析

(1)

I2C总线驱动在I2C-S3C2410.c当中。I2C总线驱动属于平台总线驱动设备结构当中的驱动。以下代码主要完成了平台驱动的注册,和prole函数的填写。

I2C函数主要的工作都在proble当中完成。我们都知道只有平台设备与平台驱动匹配时proble函数才会被调用。那就有一个问题了,2C总线驱动的平台设备在在哪里呢?

static struct platform_driver s3c2410_i2c_driver = {        //平台驱动
.probe = s3c24xx_i2c_probe,                      //当在板级信息上注册了相应的平台设备后此proble函数被调用。
.remove = s3c24xx_i2c_remove,
.suspend_late = s3c24xx_i2c_suspend_late,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-i2c",
},
};


static int __init i2c_adap_s3c_init(void)
{
int ret;
ret = platform_driver_register(&s3c2410_i2c_driver);
if (ret == 0) {
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
}

return ret;
}

现在我们回答一下上面的问题。平台设备如同我们之前讲解过的SPI驱动一样在板级初始化代码中注册。代码如下:

struct platform_device s3c_device_i2c0 = {     // 平台设备
.name  = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id  = 0,
#else
.id  = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource  = s3c_i2c_resource,
};


static struct platform_device *smdk2440_devices[] __initdata = {    //将I2C的平台设备将如平台设备数组当中
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_rtc,
&s3c_device_dm9000,
&s3c_device_uda134x,
};


static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);

platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));    //将平台设备数组加入到内核链表在何时的时候将其全部注册
smdk_machine_init();
}


通过以上代码我们知道了I2C的平台设备设备何时注册。现在我们又重现回到I2C总线驱动的proble函数当中来。


static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;                                       //适配器指针
struct s3c2410_platform_i2c *pdata;                  //平台数据
struct resource *res;                                           //指向资源
int ret;
pdata = pdev->dev.platform_data;                     
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);    //分配适配器空间
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}

strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner   = THIS_MODULE;
i2c->adap.algo    = &s3c24xx_i2c_algorithm;      //给适配器一个通信方法(重点,proble函数主要的工作之一)
i2c->adap.retries = 2;                                           //两次总线仲裁尝试
i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup     = 50;

spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);

i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}

dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);

clk_enable(i2c->clk);

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    //获取适配器寄存器资源
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto err_clk;
}

i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,    //申请I/O内存
pdev->name);

if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto err_clk;
}

i2c->regs = ioremap(res->start, (res->end-res->start)+1);    //将内存地址映射到虚拟地址

if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto err_ioarea;
}

dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
i2c->regs, i2c->ioarea, res);

i2c->adap.algo_data = i2c;       //将内存地址映射到虚拟地址
i2c->adap.dev.parent = &pdev->dev;

ret = s3c24xx_i2c_init(i2c);   ////初始化I2C控制器(重点,proble函数主要的工作之一)
if (ret != 0)
goto err_iomap;

i2c->irq = ret = platform_get_irq(pdev, 0);  //获取平台设备的中断号
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
goto err_iomap;
}

ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,     ////注册中断处理函数(用来处理I2C设备数据的收发)
 dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
goto err_iomap;
}

ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
goto err_irq;
}

i2c->adap.nr = pdata->bus_num;

ret = i2c_add_numbered_adapter(&i2c->adap);  //向内核中添加适配器(重点,proble函数主要的工作之一)
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
}

platform_set_drvdata(pdev, i2c);

dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return 0;

 err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);

 err_irq:
free_irq(i2c->irq, i2c);

 err_iomap:
iounmap(i2c->regs);

 err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);

 err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);

 err_noclk:
kfree(i2c);
return ret;
}

嗯,总结一下以上proble函数主要做了这么几样工作。1.,申请一个I2C适配器结构体,注册了适配器的通信方法。2.申请中断处理函数。3.初始化I2C控制器.。
4.I2C适配器添加到内核中。好了我们接下来就详细的讲解一下这么几项东西。首先解析一下33.初始化I2C控制器。然后再通信方法里对1.和2..申请中断处理函数进行统一的讲解。最后在I2C设备驱动那一节结合4.I2C适配器添加到内核中.


(2)初始化I2C控制器分析。

好了现在我们进入s3c24xx_i2c_init(i2c)函数,看看里面做了什么工作。(这一部分我们可以结合S3C2440技术手册关于一起来看。)

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;

     /*我们在板级初始化函数当中添加了I2C总线的平台设备,同时也设置了 平台设备的私有数据*/
pdata = i2c->dev->platform_data;

if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));//   初始化GPIO口。将GPE14和GPE15设置为IICSDA和IICSCL模式。

writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);//如果SC2440要作为I2C的从设备。那么将从设备地址写入IICADD寄存器。

dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);

  /*将IICCON寄存器的第5位和第7位置1.即使能中断和允许S3C2440收到数据后产生ACK应答*/
writel(iicon, i2c->regs + S3C2410_IICCON);

if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {   /设置时钟源和时钟频率(此函数主要对IICCON寄存器的0到3位进行设置I2C总线上数据的传输速率)
writel(0, i2c->regs + S3C2410_IICCON);  //失败则设置为0
dev_err(i2c->dev, "cannot meet bus frequency required\n");
return -EINVAL;
}

dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);

if (s3c24xx_i2c_is2440(i2c))
writel(0x0, i2c->regs + S3C2440_IICLC);
return 0;
}

这里我们完成了对I2C适配器的初始化。主要如下1.设置GPIO。2.使能中断和允许ACK.3.设置适配器数据传输的速率。建议此节结合S3C2440手册II2C初始化那一段一起看。它们是高度匹配的。


三  I2C总线驱动传输方法分析

在总线驱动的proble函数内核已经设置了数据的传输方法。代码如下:


i2c->adap.algo    = &s3c24xx_i2c_algorithm;


好,现在追寻一下s3c24xx_i2c_algorithm函数。


static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality= s3c24xx_i2c_func,
};


s3c24xx_i2c_xfe函数才实现了总线数据的传输。现在继续追踪s3c24xx_i2c_xfe函数。

static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret;
for (retry = 0; retry < adap->retries; retry++) {

ret = s3c24xx_i2c_doxfer(i2c, msgs, num); //实现数据传输
if (ret != -EAGAIN)
return ret;
dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

udelay(100);
}

return -EREMOTEIO;
}

以上代码但当正真气作用的是s3c24xx_i2c_doxfer(i2c, msgs, num)。我们追踪这个函数。


static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
     struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;

if (i2c->suspended)
return -EIO;

ret = s3c24xx_i2c_set_master(i2c);
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
ret = -EAGAIN;
goto out;
}

spin_lock_irq(&i2c->lock);
        /*记录传输数据的信息*/
i2c->msg     = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state   = STATE_START;   //设置传输状态(重要,在 i2s_s3c_irq_nextbyte函数中会用到

s3c24xx_i2c_enable_irq(i2c);// 将IICCON寄存器的第5位置1,使能中断。
s3c24xx_i2c_message_start(i2c, msgs);  //启动本次传输。
spin_unlock_irq(&i2c->lock);

timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
ret = i2c->msg_idx;

if (timeout == 0)
dev_dbg(i2c->dev, "timeout\n");
else if (ret != num)
dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

/* ensure the stop has been through the bus */
msleep(1);
 out:
return ret;
}

s3c24xx_i2c_doxfe()传输数据的大致流程是:记录下待传输数据的信息,调用s3c24xx_i2c_enable_irq()使能中断。设置I2C传输的状态为STATE_START。然后调用s3c24xx_i2c_message_start()启动本次传输。在启动传输后,调用进程会在等待队列i2c->wait挂起,接下来的数据传输由i2c中断完成。 接下来我们跟踪一下s3c24xx_i2c_message_start()。然后解析一下i2c中断函数

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
     struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;    //取从设备地址低7位,并前移1
unsigned long stat;
unsigned long iiccon; 
        stat = 0;
stat |=  S3C2410_IICSTAT_TXRXEN;                //使能发送接收功能,为写地址到IICDS

if (msg->flags & I2C_M_RD) {                          //如果读,则主机接收,地址位D0=1
stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1; 
} else                                                             /如果写,则主机发送,地址位D0=0
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);                    //使能ACK响应信号

iiccon = readl(i2c->regs + S3C2410_IICCON);
writel(stat, i2c->regs + S3C2410_IICSTAT);                //设置ICSTAT寄存器为I2c主机发送或接受。


dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
writeb(addr, i2c->regs + S3C2410_IICDS);              //写地址到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);   //发送S信号,IICDS寄存器中数据自动发出
}

总结下我们这个3c24xx_i2c_message_start函数吧,这个函数主要做了两件事,第一使能ACK信号。第二,将从机地址和读写方式控制字写入待IICDS中,由IICSTAT发送S信号,启动发送从机地址。其实分析到现在我们发现s3c24xx_i2c_doxfer调用3c24xx_i2c_message_start只是发送了一个从机的地址。真正的数据传输在哪里呢?其实真正的数据传输我们放在了中断处理函数中实现的。当数据准备好发送时,将产生中断,并调用事先注册的中断处理函数s3c24xx_i2c_irq进行数据传输。中断的产生其实有3种情况,第一,总线仲裁失败。第二,当总线还处于空闲状态,因为一些错误操作等原因,意外进入了中断处理函数。第三,收发完一个字节的数据,或者当收发到的I2C设备地址信息吻合。行,那我们先来看看s3c24xx_i2c_irq到底怎么来传输数据的吧。(s3c24xx_i2c_irq在总线驱动的proble函数中完成注册。代码:ret equest_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
 dev_name(&pdev->dev), i2c);


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;
}

i2s_s3c_irq_nextbyte(i2c, status);      //实现了数据的发送
 out:
return IRQ_HANDLED;
}

中断函数真正起到数据传输的是 i2s_s3c_irq_nextbyte()。接着我们追踪这个函数


static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
{
unsigned long tmp;
unsigned char byte;
int ret = 0;
switch (i2c->state) {

case STATE_IDLE:       //总线上没有数据传输,则立即返回
dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
goto out;
break;

case STATE_STOP:     //发出停止信号P
dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
s3c24xx_i2c_disable_irq(i2c);
goto out_ack;

case STATE_START:     //发出开始信号S


  //当没有接收到ACK应答信号,说明I2C设备不存在,应停止总线工作

if (iicstat & S3C2410_IICSTAT_LASTBIT &&
   !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
/* ack was not received... */

dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -ENXIO);
goto out_ack;
}

if (i2c->msg->flags & I2C_M_RD)   //根据消息表示来设置读还是写。
i2c->state = STATE_READ;       //一个读消息
else
i2c->state = STATE_WRITE;    //一个写消息


              // is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1
if (is_lastmsg(i2c) && i2c->msg->len == 0) {
s3c24xx_i2c_stop(i2c, 0);
goto out_ack;
}

if (i2c->state == STATE_READ)    //如果是读那进行跳转,注此casebreak
goto prepare_read;

/* fall through to the write state, as we will need to
* send a byte as well */

case STATE_WRITE:

if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
if (iicstat & S3C2410_IICSTAT_LASTBIT) {
dev_dbg(i2c->dev, "WRITE: No Ack\n");

s3c24xx_i2c_stop(i2c, -ECONNREFUSED);
goto out_ack;
}
}

 retry_write:

if (!is_msgend(i2c)) {
byte = i2c->msg->buf[i2c->msg_ptr++];         //读取待传输数据
writeb(byte, i2c->regs + S3C2410_IICDS);      //将待传输数据写入IICDS
ndelay(i2c->tx_setup);                                    /延时50ms,等待发送到总线上

} else if (!is_lastmsg(i2c)) {                                    //当前信息传输完,还有信息要传输情况下
/* we need to go to the next i2c message */

dev_dbg(i2c->dev, "WRITE: Next Message\n");

i2c->msg_ptr = 0;                                        //下一条消息字符串的首地址置0
i2c->msg_idx++;
i2c->msg++;

/* check to see if we need to do another message */
if (i2c->msg->flags & I2C_M_NOSTART) {

if (i2c->msg->flags & I2C_M_RD) {

s3c24xx_i2c_stop(i2c, -EINVAL);
}

goto retry_write;
} else {
/* send the new start */
s3c24xx_i2c_message_start(i2c, i2c->msg);
i2c->state = STATE_START;
}

} else {

s3c24xx_i2c_stop(i2c, 0);
}
break;

case STATE_READ:

byte = readb(i2c->regs + S3C2410_IICDS);   //IICDS读取数据
i2c->msg->buf[i2c->msg_ptr++] = byte;      //将读取到的数据放入缓存区, msg_ptr++直到当前消息传输完毕

 prepare_read:
if (is_msglast(i2c)) {                        // is_msglast()判断如果是消息的最后一个字节,如果是返回1
/* last byte of buffer */

if (is_lastmsg(i2c))                     // is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1
s3c24xx_i2c_disable_ack(i2c);

} else if (is_msgend(i2c)) {                 // is_msgend(0判断当前消息是否已经传输完所有字节,如果是返回1
if (is_lastmsg(i2c)) {                    // is_lastmsg()判断是当前处理的消息是否是最后一个消息,如果是返回1
/* last message, send stop and complete */
dev_dbg(i2c->dev, "READ: Send Stop\n");

s3c24xx_i2c_stop(i2c, 0);
} else {
/* go to the next transfer */
dev_dbg(i2c->dev, "READ: Next Transfer\n");

i2c->msg_ptr = 0;     //下一条消息字符串的首地址置0
i2c->msg_idx++;
i2c->msg++;         //表示准备传输下一条消息
}
}

break;
}

/* acknowlegde the IRQ and get back on with the work */

 out_ack:
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;               //清除中断,否则会重复执行中断处理函数
writel(tmp, i2c->regs + S3C2410_IICCON);
 out:
return ret;
}

好了I2C的总线驱动基本分析完毕。下一节分析I2C设备驱动。

0 0