Linux i2c子系统源码分析--Apple的学习笔记
一,前言
MPU6500六轴陀螺仪linux驱动(spi&i2c合并)--Apple的学习笔记已经完成了对i2c和spi总线及input子系统的linux驱动框架理解及API使用,然后进行进一步的理论升级,也就是去看源码,这样当遇到问题的情况下,可以高效的去解决和定位问题,而不是当做黑盒处理。便于将来进行内核裁剪。我当前使用的kernel内核版本为5.4.61。
之前也看过框架及做过练习
i2c子系统及eeprom驱动--Apple的学习笔记
动手写i2c总线设备驱动--Apple的学习笔记
二,分析的切入点
正常来说从初始化或者使用的函数,但是我想看看有哪些是内核释放给我们用的API,所以我首先从i2c.h中找extern函数,随意看,然后发现传输函数client->addr,我就好奇设备树中的addr是如何传入的,首先当然是找i2c文件夹中的core函数。比如找到了i2c-core-base.c和i2c-core-of.c函数等,由于我用设备树,当然在of函数中找。于是找到关键字info->addr。然后我就索性一路相关函数流都看下吧!我用的是am335x芯片所以看的是i2c-omap.c
三,源码数据流分析
- 关于dev下i2c设备文件的创建分析。
一定是通过device_register来创建的。然后我开始倒推。
i2c_add_numbered_adapter(nr已经在设备树中定义了,静态分配主要用到of_alias_get_id来获取id,否则动态分配)
-->i2c_add_adapter
---->i2c_register_adapter
------>of_i2c_register_devices(进一步填充adap对象信息)
-------->of_i2c_register_device
---------->of_i2c_get_board_info(通过adap->dev从设备树获取信息到i2c_board_info结构体对象info中)
---------->i2c_new_device
------------>i2c_new_client_device(将copy到i2c_client结构体对象)
-------------->device_register(创建i2c设备)
- am335x(omap)芯片平台中i2c总线probe函数
omap_i2c_probe一开始也是通过of函数从设备树中获取相关属性值来填充omap_i2c_dev对象。
-->omap_i2c_init将填充后的对象赋值给对应omap寄存器进行i2c初始化。
-->devm_request_threaded_irq里面通过判断不同的芯片版本来选择irq方式。omap_i2c_isr和omap_i2c_isr_thread是我关注的,因为它和i2c的接收和发送有关。
-->i2c_add_numbered_adapter把omap_i2c_dev中的adap对象进行填充。
关注adap->algo = &omap_i2c_algo,这个算法是传输方式,每个芯片都不同。每个芯片的i2c支持的操作函数可能都不同,是通过algo算法来挂载到具体的操作。设计方法为有共性的内容进行合并提取,特殊的进行指针动态挂载,这和我设计的i2c及spi总线都支持的MPU6500中的tf->read等函数是一样的思路,i2c就是挂到i2c的read函数,spi总线就是挂到spi的read函数。
static const struct i2c_algorithm omap_i2c_algo = {
.master_xfer = omap_i2c_xfer_irq,
.master_xfer_atomic = omap_i2c_xfer_polling,
.functionality = omap_i2c_func,
};
至此,关于i2c设备文件的注册创建已经完成。可以理解为omap_i2c_probe会调用i2c_add_numbered_adapter最后会调用device_register来创建i2c设备到dev文件夹下。然后adapter就是一个代理商,可以理解为一个适配器,大多数芯片的i2c probe函数都会通过调用i2c_add_numbered_adapter来注册i2c设备的。
- i2c_master_send的数据流
此函数为发送函数,也是比较重要的函数,一路跟踪下去还是比较简单的。
-->i2c_transfer_buffer_flags
---->i2c_transfer里面的第一个参数就是client->adapter
------>__i2c_transfer里面有些检查保护措施
-------->adap->algo->master_xfer就是调用不同芯片中的特殊函数了。
---------->omap_i2c_xfer_common
------------>omap_i2c_xfer_msg
omap_i2c_xfer_msg传入的polling参数为false说明是中断,而不是轮询等待。里面先设置寄存器值w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT;
I2c使能master并且start激活,然后为通过omap_i2c_write_reg(omap, OMAP_I2C_CON_REG, w);
为寄存器赋值进行发送i2c波形
后面是timeout = wait_for_completion_timeout(&omap->cmd_complete,OMAP_I2C_TIMEOUT);
等待中断中给他设置完成标志。
然后调试了下中断,主要思路就是判断stat然后进行相关处理,这个处理思路和单片机中i2c中断是一样的处理NACK,Read ready和Write ready等。
omap_i2c_transmit_data就是i2c数据填充到芯片的寄存器中 其中omap->buf = msg->buf
以及omap->buf_len = msg->len
是在上层omap_i2c_xfer_msg函数中赋值的。omap_i2c_receive_data是底层的i2c读。在中断处理thread中对每个stat的标志处理好后就会重新写1,等于clear此标志,比如omap_i2c_ack_stat(omap, OMAP_I2C_STAT_RRDY);
static int omap_i2c_transmit_data(struct omap_i2c_dev *omap, u8 num_bytes,
bool is_xdr)
{
u16 w;
while (num_bytes--) {
w = *omap->buf++;
omap->buf_len--;
/*
* Data reg in 2430, omap3 and
* omap4 is 8 bit wide
*/
if (omap->flags & OMAP_I2C_FLAG_16BIT_DATA_REG) {
w |= *omap->buf++ << 8;
omap->buf_len--;
}
if (omap->errata & I2C_OMAP_ERRATA_I462) {
int ret;
ret = errata_omap3_i462(omap);
if (ret < 0)
return ret;
}
omap_i2c_write_reg(omap, OMAP_I2C_DATA_REG, w);
}
return 0;
}
如下发送函数是主要进行发送寄存器设置的OMAP_I2C_CNT_REG为设置发送长度,发送完成后按照config的配置会自动发送STOP或不发。
w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR;
没用dma,用的fifo,首先是清除rx和tx中断。
wait_for_completion_timeout
就是等待中断处理thread完毕。
static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
struct i2c_msg *msg, int stop, bool polling)
{
struct omap_i2c_dev *omap = i2c_get_adapdata(adap);
unsigned long timeout;
u16 w;
int ret;
dev_dbg(omap->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n",
msg->addr, msg->len, msg->flags, stop);
omap->receiver = !!(msg->flags & I2C_M_RD);
omap_i2c_resize_fifo(omap, msg->len, omap->receiver);
omap_i2c_write_reg(omap, OMAP_I2C_SA_REG, msg->addr);
/* REVISIT: Could the STB bit of I2C_CON be used with probing? */
omap->buf = msg->buf;
omap->buf_len = msg->len;
/* make sure writes to omap->buf_len are ordered */
barrier();
omap_i2c_write_reg(omap, OMAP_I2C_CNT_REG, omap->buf_len);
/* Clear the FIFO Buffers */
w = omap_i2c_read_reg(omap, OMAP_I2C_BUF_REG);
w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR;
omap_i2c_write_reg(omap, OMAP_I2C_BUF_REG, w);
......
if (!polling) {
timeout = wait_for_completion_timeout(&omap->cmd_complete,
OMAP_I2C_TIMEOUT);
......
}
omap_i2c_xfer_data中将收发中断处理完后,就退出while循环,返回'-EAGAIN'后调用omap_i2c_complete_cmd
if (!stat) {
/* my work here is done */
err = -EAGAIN;
break;
}
static irqreturn_t
omap_i2c_isr_thread(int this_irq, void *dev_id)
{
int ret;
struct omap_i2c_dev *omap = dev_id;
ret = omap_i2c_xfer_data(omap);
if (ret != -EAGAIN)
omap_i2c_complete_cmd(omap, ret);
return IRQ_HANDLED;
}
把i2c-omap.c添加#define DEBUG 1后可以看到如下打印信息,把中断中的状态信息打印出来。
[ 48.648002] omap_i2c 4819c000.i2c: addr: 0x0068, len: 1, flags: 0x0, stop: 0
[ 48.655101] isr=16
[ 48.655154] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0010)
[ 48.662395] isr=4
[ 48.662450] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0004)
[ 48.670210] omap_i2c 4819c000.i2c: addr: 0x0068, len: 14, flags: 0x201, stop: 1
[ 48.678927] isr=8
[ 48.678968] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0008)
[ 48.685983] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0004)
ACC x=0.37 y=0.50 z=0.79
Gyro x=-0.01 y=0.00 z=0.01
temp=15.85
[ 48.897995] omap_i2c 4819c000.i2c: addr: 0x0068, len: 1, flags: 0x0, stop: 0
[ 48.905100] isr=16
[ 48.905152] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0010)
[ 48.912397] isr=4
[ 48.912447] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0004)
[ 48.920223] omap_i2c 4819c000.i2c: addr: 0x0068, len: 14, flags: 0x201, stop: 1
[ 48.928941] isr=8
[ 48.928984] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0008)
[ 48.936002] omap_i2c 4819c000.i2c: IRQ (ISR = 0x0004)
ACC x=0.38 y=0.47 z=0.80
Gyro x=-0.03 y=0.00 z=0.02
temp=15.85
四,把smbus改成i2c的API测试通过
static int apple6500_read_i2c(struct device *dev, unsigned off)
{
struct i2c_client *c = to_i2c_client(dev);
u8 buffer[1]={0};
struct i2c_msg msgs[] = {
{
.addr = c->addr,
.flags = 0,
.len = 1,
.buf = (unsigned char*)&off,
},
{
.addr = c->addr,
.flags = I2C_M_RD,
.len = 1,
.buf = buffer,
},
};
int ret;
ret = i2c_transfer(c->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return ret < 0 ? ret : -EIO;
return buffer[0];
}
static int apple6500_write_i2c(struct device *dev, unsigned off, unsigned char v)
{
struct i2c_client *c = to_i2c_client(dev);
char buf[2];
int ret;
buf[0] = off;
memcpy(&buf[1], &v, 1);
ret = i2c_master_send(c, buf, 2);
if (ret != 2)
return ret < 0 ? ret : -EIO;
return 0;
}
static int apple6500_read_block_i2c(struct device *dev, unsigned off,unsigned char *buf, size_t size)
{
struct i2c_client *c = to_i2c_client(dev);
struct i2c_msg msgs[] = {
{
.addr = c->addr,
.flags = 0,
.len = 1,
.buf = (unsigned char*)&off,
},
{
.addr = c->addr,
.flags = I2C_M_RD,
.len = size,
.buf = buf,
},
};
int ret;
ret = i2c_transfer(c->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return ret < 0 ? ret : -EIO;
return 0;
}