美文网首页BB-black开发板[Linux arm-v8]
Linux i2c子系统源码分析--Apple的学习笔记

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

作者: applecai | 来源:发表于2020-12-15 22:03 被阅读0次

    一,前言

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

    相关文章

      网友评论

        本文标题:Linux i2c子系统源码分析--Apple的学习笔记

        本文链接:https://www.haomeiwen.com/subject/uxrggktx.html