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) //如果是读那进行跳转,注此case无break!
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设备驱动。
- Linux I2C驱动完全分析(一)
- Linux I2C驱动源码分析(一)
- Linux I2C驱动源码分析(一)
- Linux I2C驱动源码分析(一)
- Linux I2C驱动完全分析(一)
- Linux I2C驱动完全分析(一)
- Linux I2C驱动完全分析(一)
- Linux I2C驱动完全分析(一)
- Linux I2C 驱动分析(一)
- Linux下I2C驱动分析(一)
- Linux I2C驱动完全分析(一)
- Linux I2C驱动完全分析(一)
- Linux I2C驱动分析(一)----I2C架构和总线驱动
- Linux I2C驱动分析(一)----I2C架构和总线驱动
- Linux I2C驱动分析(一)----I2C架构和总线驱动
- Linux I2C驱动分析(一)----I2C架构和总线驱动
- Linux I2C驱动分析(一)----I2C架构和总线驱动
- linux内核I2C驱动子系统分析(一)
- 大数据处理Hadoop学习文章
- hdu 5468 Puzzled Elena(前缀性质+dfs序+容斥)
- 使用Ant打包java程序(讲解详细)
- 剑指Offer面试题目:有序二维数组的查找
- Apriori算法详解之【一、相关概念和核心步骤】
- Linux I2C 驱动分析(一)
- Linux下socket优化
- Linux上扩展磁盘空间
- 黑马程序员—static和extern及文件操作和字符读写
- CString与LPCWSTR、LPSTR、char*、LPWSTR等类型的转换
- Android 的ThreadLocal 详解
- 玩转CPU Topology
- 字符串转换工具
- GLSL语言基础