BB-black开发板[Linux arm-v8]

Linux i2c子系统源码分析--Apple的学习笔记

2020-12-15  本文已影响0人  applecai

一,前言

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

三,源码数据流分析

  1. 关于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设备)

  1. 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设备的。

  1. 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;
}
上一篇下一篇

猜你喜欢

热点阅读