Linux2.6.37 I2C驱动框架分析(五)
来源:互联网 发布:淘宝卖家怎么进入 编辑:程序博客网 时间:2024/06/08 02:05
/driver/i2c/busses/i2c-s3c2410.c:
入口:
i2c_adap_s3c_init(void)
platform_driver_register(&s3c24xx_i2c_driver); 注册平台驱动s3c24xx_i2c_driver
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,
},
};
当找到匹配的平台设备时,平台驱动的probe方法就会被调用,直接进入s3c24xx_i2c_probe函数的分析。
s3c24xx_i2c_probe(struct platform_device *pdev)
pdata = pdev->dev.platform_data; 获取平台设备的平台数据
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); 分配一个s3c24xx_i2c空间,s3c24xx_i2c是对i2c_adapter结构进一步的封装
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); 设置适配器的名字
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; 设置适配器的总线通信方法,核心
i2c->adap.retries = 2; 适配器重复的次数,一般用于没反映的情况
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50; 建立时间
i2c->clk = clk_get(&pdev->dev, "i2c"); 获取I2C模块的时钟
clk_enable(i2c->clk); 使能该模块的时钟
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 获取平台设备I/O内存资源,就是一些寄存器
i2c->ioarea = request_mem_region(res->start, resource_size(res),pdev->name); 对该I/O内存进行检查是否可用
i2c->regs = ioremap(res->start, resource_size(res)); 将I/O内存映射到内核空间,就可以操作这些寄存器了
ret = s3c24xx_i2c_init(i2c); 使能i2c硬件模块
i2c->irq = ret = platform_get_irq(pdev, 0); 获取平台设备中的中断资源,中断号
request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,dev_name(&pdev->dev), i2c); 注册中断处理函数
ret = s3c24xx_i2c_register_cpufreq(i2c); 设置i2c模块的工作时钟
i2c->adap.nr = pdata->bus_num; 看来总线号是在平台设备中写死的哦
ret = i2c_add_numbered_adapter(&i2c->adap); 注册该适配器
这里就没有细致的去分析每个函数的实现了,如果你有Datesheet的话应该一步一步就能看懂,没什么大不了的,probe工作:
1.从平台设备中获取平台资源,对于寄存器资源将其进行映射,对于中断资源,注册相应的中断的处理函数
2.获取时钟,使能时钟,设置工作时钟
3.向系统注册这个i2c_adapter
通过前面的学习,我们知道一个i2c总线驱动最重要的就是总线驱动提供的通信方法,对于S3C2440的总线通信方法在s3c24xx_i2c_algorithm中实现,还有需要关注的就是中断处理函数了。
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer, 总线的通信方法
.functionality = s3c24xx_i2c_func, 总线支持的功能
};
重点分析s3c24xx_i2c_xfer和s3c24xx_i2c_irq。
s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 都是以i2c消息的形式进行传输的
s3c24xx_i2c_doxfer(i2c, msgs, num); 最终还是调用了该函数啊
ret = s3c24xx_i2c_set_master(i2c); 设置I2C模块为主机模式,貌似Linux I2C适配器驱动不支持从机模式哦
i2c->msg = msgs; 指向i2c_mgs
i2c->msg_num = num; 消息的个数
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START; 适配器的状态为发送S信号的状态
s3c24xx_i2c_enable_irq(i2c); 使能I2C模块的中断
timeout = s3c24xx_i2c_message_start(i2c, msgs); 开始发送这些消息了,这个函数比较重要,等下分析
wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); 接下来就在这睡眠了,醒来的条件要么是超时了,要么是消息发送完了在中断程序中被唤醒,如果是超时了,醒来后会打印一些信息,不是重点。
接下来看看重点:
s3c24xx_i2c_message_start(i2c, msgs);
unsigned int addr = (msg->addr & 0x7f) << 1; 计算出设备的7位地址
unsigned long stat;
unsigned long iiccon;
stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN; 设置发送接收使能
if (msg->flags & I2C_M_RD) { 根据flags设置主机接收模式还是发送模式
stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1; 第8为1表示读操作
} else
stat |= S3C2410_IICSTAT_MASTER_TX; 发送时,第8位为0
if (msg->flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
s3c24xx_i2c_enable_ack(i2c); 使能ACK,当主机接收到数据后会发出ACK信号
iiccon = readl(i2c->regs + S3C2410_IICCON); 读取IICCON,就设置这些信息写回寄存器中生效
writel(stat, i2c->regs + S3C2410_IICSTAT);
writeb(addr, i2c->regs + S3C2410_IICDS); 将设备的地址写入
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT); 发出S信号,发出S信号后就会自动的把设备的地址发送出去,从机能响应时,会在SCL的第9个时钟周期发出ACK信号,这个信号会产生中断。而该函数的使命就完成了,剩下的一切一切都是中断处理函数中完了。
中断处理函数:s3c24xx_i2c_irq(int irqno, void *dev_id)
s3c24xx_i2c_irq(int irqno, void *dev_id)
status = readl(i2c->regs + S3C2410_IICSTAT); 读取状态寄存器,来查看是哪类时间产生了
if (status & S3C2410_IICSTAT_ARBITR) { 如果是仲裁总线失败了,啥也干不了只能打印一些出错信息了
dev_err(i2c->dev, "deal with arbitration loss\n");
}
if (i2c->state == STATE_IDLE) { 处于空闲状态,接触中断挂起就OK,不能会持续不断的中断哦
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); 其他事件产生的中断,都交给你去处理了,这个函数有点复杂哦
i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) 该函数是根据处理器的所处的状态来进行处理的
switch (i2c->state) {
case STATE_IDLE: 都处于空闲状态了,肯定啥事不干了
dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
goto out;
break;
case STATE_STOP: 处于发送STOP信号的状态了,直接禁止中断就OK
dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
s3c24xx_i2c_disable_irq(i2c);
goto out_ack;
case STATE_START: 处于发送S信号的阶段,一开始传输不就是线发出了S信号吗?
if (i2c->msg->flags & I2C_M_RD) 会根据消息的标志位来设置适配器接下来处于哪种状态
i2c->state = STATE_READ; 接下来处理器就处于读数据的状态了
else
i2c->state = STATE_WRITE; 接下来处理器就处于写数据的状态了
if (is_lastmsg(i2c) && i2c->msg->len == 0) { 如果是最后一个消息且消息发送完了
s3c24xx_i2c_stop(i2c, 0); 直接停止了i2c
goto out_ack; 貌似这种情况应该是验证一个设备是否存在的时候有用
}
if (i2c->state == STATE_READ) 如果是进行读操作,就跳转到标号prepare_read去了
goto prepare_read; 是写的话直接往下执行就OK了,没加break哦
case STATE_WRITE:
if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) { 如果不忽略ACK信号的话,此时应该接收到ACK信号
if (iicstat & S3C2410_IICSTAT_LASTBIT) { 没接到ACK信号,完了出错了
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); 继续写就是了
ndelay(i2c->tx_setup);
} else if (!is_lastmsg(i2c)) { 不是当前消息的最后一个字节,也不是最后一个消息
dev_dbg(i2c->dev, "WRITE: Next Message\n");
i2c->msg_ptr = 0; 消息内部数据指针清0,
i2c->msg_idx++; 消息索引加1
i2c->msg++; 指向下一个消息
if (i2c->msg->flags & I2C_M_NOSTART) { 标志设置诶不需要重复的START信号
if (i2c->msg->flags & I2C_M_RD) { 如果是读操作
s3c24xx_i2c_stop(i2c, -EINVAL); 停止
}
goto retry_write; 进行重复写
} else { 如果需要如果发送完一个消息需要重新发送START信号
s3c24xx_i2c_message_start(i2c, i2c->msg); 重新开始传输
i2c->state = STATE_START; 适配器为START状态
}
} else { 消息传完了,停止传输
s3c24xx_i2c_stop(i2c, 0);
}
break;
case STATE_READ: 读操作
byte = readb(i2c->regs + S3C2410_IICDS); 从寄存器中读取数据
i2c->msg->buf[i2c->msg_ptr++] = byte; 将消息数据放到buf中
prepare_read:
if (is_msglast(i2c)) { 如果是最后一个消息
if (is_lastmsg(i2c)) 最后一个消息的最后一个字节
s3c24xx_i2c_disable_ack(i2c); 停止传输
} else if (is_msgend(i2c)) { 最后一个消息的最后一个字节
if (is_lastmsg(i2c)) { 停止传输
dev_dbg(i2c->dev, "READ: Send Stop\n");
s3c24xx_i2c_stop(i2c, 0);
} else { 接收下一个消息
dev_dbg(i2c->dev, "READ: Next Transfer\n");
i2c->msg_ptr = 0;
i2c->msg_idx++;
i2c->msg++;
}
}
break;
}
适配器驱动的代码就分析到这了,相信此时你会适配器驱动已经不再感到神秘,接下来一章分析及编写I2C设备驱动及最终的总结
- Linux2.6.37 I2C驱动框架分析(五)
- Linux2.6.37 I2C驱动框架分析(一)
- Linux2.6.37 I2C驱动框架分析(二)
- Linux2.6.37 I2C驱动框架分析(三)
- Linux2.6.37 I2C驱动框架分析(六)
- Linux2.6.37 I2C驱动框架(四)
- i2c 驱动举例框架分析
- PowerPC + Linux2.6.25平台下的I2C驱动架构分析
- PowerPC + Linux2.6.25平台下的I2C驱动架构分析
- linux2.6.22.6驱动源码框架分析
- I2c-dev.c 参考代码 TP驱动分析(五)
- Linux USB驱动框架分析(五)
- Linux USB驱动框架分析(五)
- I2C驱动的框架实现分析
- Linux2.6 I2C子系统分析
- i2c驱动--驱动框架
- I2C设备驱动(三)--linux i2c驱动框架
- i2c驱动架构(史上最全) davinc dm368 i2c驱动分析
- Linux2.6.37 I2C驱动框架(四)
- 构造函数中调用虚函数?
- 为myeclipse添加多个tomcat
- Oracle Timezone
- dispatch_once 只执行一次的方法
- Linux2.6.37 I2C驱动框架分析(五)
- Struts框架面试题
- samba源代码安装
- android开源社区
- pix501恢复出厂默认设置实战
- 16---3填空学指针
- Java虚拟机学习 - 对象访问
- PHP的一些面试题
- Ubutu12.04下下载与编译Android系统