• 【转】Linux I2C设备驱动编写(三)-实例分析AM3359


    原文网址:http://www.cnblogs.com/biglucky/p/4059586.html

    TI-AM3359 I2C适配器实例分析

    I2C Spec简述

    特性:
    • 兼容飞利浦I2C 2.1版本规格
    • 支持标准模式(100K bits/s)和快速模式(400K bits/s)
    • 多路接收、发送模式
    • 支持7bit、10bit设备地址模式
    • 32字节FIFO缓冲区
    • 可编程时钟发生器
    • 双DMA通道,一条中断线
    • 三个I2C模块实例I2C0I2C1I2C2
    • 时钟信号能够达到最高48MHz,来自PRCM
    不支持
    • SCCB协议
    • 高速模式(3.4MBPS)
    管脚
    管脚类型描述
    I2Cx_SCL I/OD I2C 串行时钟
    I2Cx_SDA I/OD I2C 串行数据
    I2C重置
    • 通过系统重置PIRSTNA=0,所有寄存器都会被重置到上电状态
    • 软重置,置位I2C_SYSC寄存器的SRST位。
    • I2C_CON寄存器的I2C_EN位可以让I2C模块重置。当PIRSTNA=1,I2C_EN=0会让I2C模块功能部分重置,所有寄存器数据会被暂存(不会恢复上电状态)
    数据有效性
    • SDA在SCL高电平期间必须保持稳定,而只有在SCL低电平期间数据线(SDA)才可以进行高低电平切换
    开始位&停止位

    当I2C模块被设置为主控制时会产生START和STOP:

    • START开始位是SCL高电平期间SDA HIGH->LOW
    SCL   _____         _______
                      \____/
    SDA   __
                 \____________
    • STOP停止位是SCL高电平期间SDA LOW->HIGH
    SCL    _____         _______
                       \____/
    SDA        ___________
              __/
    • 在START信号后总线就会被认为是busy忙状态,而在STOP后其会被视为空闲状态
    串行数据格式

    8位数据格式,每个放在SDA线上的都是1个字节即8位长,总共有多少个字节要发送/接收是需要写在DCOUNT寄存器中的。数据是高位先传输,如果I2C模块处于接收模式中,那么一个应答位后跟着一个字节的数据。I2C模块支持两种数据格式:

    • 7bit/10bit地址格式
    • 带有多个开始位的7bit/10bit地址格式

    FIFO控制

    I2C模块有两个内部的32字节FIFO,FIFO的深度可以通过控制I2C_IRQSTATUS_RAW.FIFODEPTH寄存器修改。

    如何编程I2C

    1. 使能模块前先设置
    • 使分频器产生约12MHz的I2C模块时钟(设置I2C_PSC=x,x的值需要根据系统时钟频率进行计算)
    • 使I2C时钟产生100Kpbs(Standard Mode)或400Kbps(Fast Mode)(SCLL = x 及 SCLH = x,这些值也是需要根据系统时钟频率进行计算)
    • 如果是FS模式,则配置自己的地址(I2C_OA = x)
    • 重置I2C模块(I2C_CON:I2C_EN=1)
    2. 初始化程序
    • 设置I2C工作模式寄存器(I2C_CON)
    • 若想用传输数据中断则使能中断掩码(I2C_IRQENABLE_SET)
    • 如果在FS模式中,使用DMA传输数据的话,使能DMA(I2C_BUF及I2C_DMA/RX/TX/ENABLE_SET)且配置DMA控制器
    3. 设置从地址和数据计数器

    在主动模式中,设置从地址(I2C_SA = x),设置传输需要的字节数(I2C_CNT = x)

    4. 初始化一次传输

    在FS模式中。查询一下I2C状态寄存器(I2C_IRQSTATUS_RAW)中总线状态(BB),如果是0则说明总线不是忙状态,设置START/STOP(I2C_CON:STT/STP)初始化一次传输。

    5. 接收数据

    检查I2C状态寄存器(I2C_IRQSTATUS_RAW)中代表接收数据是否准备好的中断位(RRDY),用这个RRDY中断(I2C_IRQENABLE_SET.RRDY_IE置位)或使用DMA_RX(I2C_BUF.RDMA_EN置位且I2C_DMARXENABLE_SET置位)去数据接收寄存器(I2C_DATA)中去读接收到的数据。

    6. 发送数据

    查询代表传输数据是否准备好的中断位(XRDY)(还是在状态寄存器I2C_IRQSTATUS_RAW中),用XRDY中断(I2C_IRQENABLE_SET.XRDY_IE置位)或DMA_TX(I2C_BUF.XDMA_EN与I2C_DMATXENABLE_SET置位)去将数据写入到I2C_DATA寄存器中。

    I2C寄存器

    由于寄存器众多,这里只将上述提到过的几个拿出来(不包含DMA相关)。

    偏移量寄存器名概述
    00h I2C_REVNB_LO 只读,存储着硬烧写的此模块的版本号
    04h I2C_REVNB_HI 只读,存储功能和SCHEME信息
    24h I2C_IRQSTATUS_RAW 读写,提供相关中断信息,是否使能等
    2Ch I2C_IRQENABLE_SET 读写,使能中断
    98h I2C_CNT 读写,设置I2C数据承载量(多少字节),在STT设1和接到ARDY间不能改动此寄存器
    9Ch I2C_DATA 读写,8位,本地数据读写到FIFO寄存器
    A4h I2C_CON 读写,在传输期间不要修改(STT为1到接收到ARDY间),I2C控制设置
    A8h I2C_OA 读写,8位,传输期间不能修改。设置自身I2C地址7bit/10bit
    ACh I2C_SA 读写,10位,设置从地址7bit/10bit
    B0h I2C_PSC 读写,8位,分频器设置,使能I2C前可修改
    B4h I2C_SCLL 读写,8位,使能I2C前可修改,占空比低电平时间
    B8h I2C_SCLH 读写,8位,使能I2C前可修改,占空比高电平时间

    适配器代码解读

    在Linux内核驱动中,此适配器驱动存在于drivers/i2c/busses/i2c-omap.c。根据前几节对适配器i2c_adapter的理解,在写I2C适配器驱动时,主要集中在对传输、设备初始化、电源管理这几点。

    平台设备注册
    static struct platform_driver omap_i2c_driver = {
        .probe        = omap_i2c_probe,
        .remove        = omap_i2c_remove,
        .driver        = {
            .name    = "omap_i2c",
            .owner    = THIS_MODULE,
            .pm    = OMAP_I2C_PM_OPS,
            .of_match_table = of_match_ptr(omap_i2c_of_match),
        },
    };

    可以看到,此适配器的匹配是通过dts(Device Tree)进行匹配的,omap_i2c_of_match为:

    static const struct of_device_id omap_i2c_of_match[] = {
        {
            .compatible = "ti,omap4-i2c",
            .data = &omap4_pdata,
        },
        {
            .compatible = "ti,omap3-i2c",
            .data = &omap3_pdata,
        },
        { },
    };

    通过在查阅相关dts,不难发现有这样的设备节点存在:

         i2c0: i2c@44e0b000 {
             compatible = "ti,omap4-i2c";
             #address-cells = <1>;
             #size-cells = <0>;
             ti,hwmods = "i2c1"; /* TODO: Fix hwmod */
             reg = <0x44e0b000 0x1000>;
             interrupts = <70>;
             status = "disabled";
         };  
    
         i2c1: i2c@4802a000 {
             compatible = "ti,omap4-i2c";
             #address-cells = <1>;
             #size-cells = <0>;
             ti,hwmods = "i2c2"; /* TODO: Fix hwmod */
             reg = <0x4802a000 0x1000>;
             interrupts = <71>;
             status = "disabled";
         };  
    
         i2c2: i2c@4819c000 {
             compatible = "ti,omap4-i2c";
             #address-cells = <1>;
             #size-cells = <0>;
             ti,hwmods = "i2c3"; /* TODO: Fix hwmod */
             reg = <0x4819c000 0x1000>;
             interrupts = <30>;
             status = "disabled";
         };

    通过查阅AM3359手册168页的内存映射表可以发现,这个dts所描述的3个I2C总线节点是与AM3359完全对应的,而名称(即compatible)也与驱动中所指定的列表项能够匹配。至于中断号的确定可通过手册的212页TABLE 6-1. ARM Cortex-A8 Interrupts得到,这里不再贴图,关于DTS的相关知识也非本问涉及,不做介绍。

    下面重点分析此驱动的probe及电源管理。

    匹配动作probe

    由于DTS的存在,一旦内核检测到匹配的Device Tree节点就会触发probe匹配动作(因为DTS节省了对原本platform_device在板级代码中的存在)。由于probe函数内容较多,此处部分节选:

    static int
    omap_i2c_probe(struct platform_device *pdev)
    {
        struct omap_i2c_dev    *dev;
        struct i2c_adapter    *adap;
        struct resource        *mem;
        const struct omap_i2c_bus_platform_data *pdata =
            pdev->dev.platform_data;
        struct device_node    *node = pdev->dev.of_node;
        const struct of_device_id *match;
        int irq;
        int r;
        u32 rev;
        u16 minor, major, scheme;
        struct pinctrl *pinctrl;
    
        /* NOTE: driver uses the static register mapping */
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);  //对应DTS中reg
        if (!mem) {
            dev_err(&pdev->dev, "no mem resource?
    ");
            return -ENODEV;
        }
    
        irq = platform_get_irq(pdev, 0);  //对应DTS中interrupts
        if (irq < 0) {
            dev_err(&pdev->dev, "no irq resource?
    ");
            return irq;
        }
    
        dev = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
        if (!dev) {
            dev_err(&pdev->dev, "Menory allocation failed
    ");
            return -ENOMEM;
        }
    
        dev->base = devm_request_and_ioremap(&pdev->dev, mem);  //做内存和IO映射
        if (!dev->base) {
            dev_err(&pdev->dev, "I2C region already claimed
    ");
            return -ENOMEM;
        }
    
        match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev);  //通过DTS进行匹配
        if (match) {
            u32 freq = 100000; /* default to 100000 Hz */
    
            pdata = match->data;
            dev->flags = pdata->flags;
    
            of_property_read_u32(node, "clock-frequency", &freq);  
            /* convert DT freq value in Hz into kHz for speed */
            dev->speed = freq / 1000;  //若成功匹配则设置I2C总线适配器速度为clock-frequency的数值
        } else if (pdata != NULL) {
            dev->speed = pdata->clkrate;  //若没匹配成功,而又有pdata(即通过传统方式注册platform_device)
            dev->flags = pdata->flags;
            dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
        }
    
    rev = __raw_readw(dev->base + 0x04);  //读取I2C_REVNB_HI寄存器
    
    /*
    * #define OMAP_I2C_SCHEME(rev)        ((rev & 0xc000) >> 14)
    * 对应spec中描述:4244页,15-14位SCHEME,只读。
    */
        scheme = OMAP_I2C_SCHEME(rev);  
        switch (scheme) {
        case OMAP_I2C_SCHEME_0:
            dev->regs = (u8 *)reg_map_ip_v1;
            dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG);
            minor = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
            major = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
            break;
        case OMAP_I2C_SCHEME_1:
            /* FALLTHROUGH */
        default:
            dev->regs = (u8 *)reg_map_ip_v2;
            rev = (rev << 16) |
                omap_i2c_read_reg(dev, OMAP_I2C_IP_V2_REVNB_LO);
            minor = OMAP_I2C_REV_SCHEME_1_MINOR(rev);
            major = OMAP_I2C_REV_SCHEME_1_MAJOR(rev);
            dev->rev = rev;
        }

    上述代码为版本判断,根据不同版本确定不同的寄存器地图。根据spec能够确定,实际AM3359的I2C总线适配器应该是OMAP_I2C_SCHEME_1类型,其寄存器地图为reg_map_ip_v2:

    static const u8 reg_map_ip_v2[] = {
        [OMAP_I2C_REV_REG] = 0x04,
        [OMAP_I2C_IE_REG] = 0x2c,
        [OMAP_I2C_STAT_REG] = 0x28,
        [OMAP_I2C_IV_REG] = 0x34,
        [OMAP_I2C_WE_REG] = 0x34,
        [OMAP_I2C_SYSS_REG] = 0x90,
        [OMAP_I2C_BUF_REG] = 0x94,
        [OMAP_I2C_CNT_REG] = 0x98,
        [OMAP_I2C_DATA_REG] = 0x9c,
        [OMAP_I2C_SYSC_REG] = 0x10,
        [OMAP_I2C_CON_REG] = 0xa4,
        [OMAP_I2C_OA_REG] = 0xa8,
        [OMAP_I2C_SA_REG] = 0xac,
        [OMAP_I2C_PSC_REG] = 0xb0,
        [OMAP_I2C_SCLL_REG] = 0xb4,
        [OMAP_I2C_SCLH_REG] = 0xb8,
        [OMAP_I2C_SYSTEST_REG] = 0xbC,
        [OMAP_I2C_BUFSTAT_REG] = 0xc0,
        [OMAP_I2C_IP_V2_REVNB_LO] = 0x00,
        [OMAP_I2C_IP_V2_REVNB_HI] = 0x04,
        [OMAP_I2C_IP_V2_IRQSTATUS_RAW] = 0x24,
        [OMAP_I2C_IP_V2_IRQENABLE_SET] = 0x2c,
        [OMAP_I2C_IP_V2_IRQENABLE_CLR] = 0x30,
    };

    与spec能够对应上。不过这个列表不是根据寄存器地址排序的,是根据:

    enum {
        OMAP_I2C_REV_REG = 0,
        OMAP_I2C_IE_REG,
        OMAP_I2C_STAT_REG,
        OMAP_I2C_IV_REG,
        OMAP_I2C_WE_REG,
        OMAP_I2C_SYSS_REG,
        OMAP_I2C_BUF_REG,
        OMAP_I2C_CNT_REG,
        OMAP_I2C_DATA_REG,
        OMAP_I2C_SYSC_REG,
        OMAP_I2C_CON_REG,
        OMAP_I2C_OA_REG,
        OMAP_I2C_SA_REG,
        OMAP_I2C_PSC_REG,
        OMAP_I2C_SCLL_REG,
        OMAP_I2C_SCLH_REG,
        OMAP_I2C_SYSTEST_REG,
        OMAP_I2C_BUFSTAT_REG,
        /* only on OMAP4430 */
        OMAP_I2C_IP_V2_REVNB_LO,
        OMAP_I2C_IP_V2_REVNB_HI,
        OMAP_I2C_IP_V2_IRQSTATUS_RAW,
        OMAP_I2C_IP_V2_IRQENABLE_SET,
        OMAP_I2C_IP_V2_IRQENABLE_CLR,
    };

    共计23个寄存器。接下来是获取FIFO信息:

    if (!(dev->flags & OMAP_I2C_FLAG_NO_FIFO)) {
    u16 s;
    
        /*
        * OMAP_I2C_BUFSTAT_REG对应寄存器地图中的寄存器0xc0,即I2C_BUFSTAT寄存器。
        * 其第14~15位代表FIFO大小:0x0-8字节,0x1-16字节,0x2-32字节,0x3-64字节,只读寄存器。
        * 改变RX/TX FIFO可通过改写I2C_BUF 0x94寄存器
        */
            s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
            dev->fifo_size = 0x8 << s;
            dev->fifo_size = (dev->fifo_size / 2);  //折半是为了处理潜在事件    
        }

    接下来是对I2C适配器的初始化:

    /* reset ASAP, clearing any IRQs */ //尽快重置,清除所有中断位
    omap_i2c_init(dev);

    进入此函数后在对具体硬件操作前还进行了时钟的相关计算,由于代码比较冗长,这里直接根据实际情况提炼出部分代码进行分析:

    static int omap_i2c_init(struct omap_i2c_dev *dev)
    {
    u16 psc = 0, scll = 0, sclh = 0;
    u16 fsscll = 0, fssclh = 0, hsscll = 0, hssclh = 0;
    unsigned long fclk_rate = 12000000;  //12MHz
    unsigned long internal_clk = 0;
    struct clk *fclk;
    if (!(dev->flags & OMAP_I2C_FLAG_SIMPLE_CLOCK)) {
    //上边的代码中表示过,默认为100KHz。即标准模式,而此I2C适配器只能支持标准和快速,对于高速模式并不支持
            internal_clk = 4000;
        fclk = clk_get(dev->dev, "fck");
        fclk_rate = clk_get_rate(fclk) / 1000;
        clk_put(fclk);
    
        /* Compute prescaler divisor */
        psc = fclk_rate / internal_clk;  //计算分频器系数,0~0xff表示1倍到256倍
        psc = psc - 1;
    /*
    * SCLL为SCL低电平设置,持续时间tROW = (SCLL + 7) * ICLK,即SCLL = tROW / ICLK - 7
    * SCLH为SCL高电平设置,持续时间tHIGH= (SCLH + 5) * ICLK,即SCLH = tHIGH/ ICLK - 5
    */
            /* Standard mode */
            fsscll = internal_clk / (dev->speed * 2) - 7;
            fssclh = internal_clk / (dev->speed * 2) - 5;
    
        scll = (hsscll << OMAP_I2C_SCLL_HSSCLL) | fsscll;
        sclh = (hssclh << OMAP_I2C_SCLH_HSSCLH) | fssclh;
    }
    dev->iestate = (OMAP_I2C_IE_XRDY | OMAP_I2C_IE_RRDY |
            OMAP_I2C_IE_ARDY | OMAP_I2C_IE_NACK |
            OMAP_I2C_IE_AL)  | ((dev->fifo_size) ?
                (OMAP_I2C_IE_RDR | OMAP_I2C_IE_XDR) : 0);  //设置传输数据相关中断位
    
    dev->pscstate = psc;
    dev->scllstate = scll;
    dev->sclhstate = sclh;
    
    __omap_i2c_init(dev);
    
    return 0;
    }

    对一些最后的必要参数计算或匹配完后,通过最终的__omap_i2c_init(dev)进行最后的写入:

    static void __omap_i2c_init(struct omap_i2c_dev *dev)
    {
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);  //重置控制器
    
        /* Setup clock prescaler to obtain approx 12MHz I2C module clock: */
        omap_i2c_write_reg(dev, OMAP_I2C_PSC_REG, dev->pscstate);  //设置分频器参数
    
        /* SCL low and high time values */
        omap_i2c_write_reg(dev, OMAP_I2C_SCLL_REG, dev->scllstate); //设置SCL高低电平参数
        omap_i2c_write_reg(dev, OMAP_I2C_SCLH_REG, dev->sclhstate);
        if (dev->rev >= OMAP_I2C_REV_ON_3430_3530)
            omap_i2c_write_reg(dev, OMAP_I2C_WE_REG, dev->westate);
    
        /* Take the I2C module out of reset: */
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN);  //使能I2C适配器
    
        /*
         * Don't write to this register if the IE state is 0 as it can
         * cause deadlock.
         */
        if (dev->iestate)
            omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate);  //设置中断使能位
    }

    到这里硬件模块的初始化工作就全部完成了。接下来继续,包含了中断处理程序注册、适配器注册等。

    r = devm_request_threaded_irq(&pdev->dev, dev->irq,
                omap_i2c_isr, omap_i2c_isr_thread,
                IRQF_NO_SUSPEND | IRQF_ONESHOT,
                pdev->name, dev);
    //申请中断,并安装相应的handle及中断工作线程(主要包含传输工作)
    
    if (r) {
        dev_err(dev->dev, "failure requesting irq %i
    ", dev->irq);
        goto err_unuse_clocks;
    }
    
    adap = &dev->adapter; //开始准备适配器的注册工作
    i2c_set_adapdata(adap, dev);  //之前设置、计算的那些参数不能丢掉,要保存在adapter的dev->p->driver_data中。
    adap->owner = THIS_MODULE;
    adap->class = I2C_CLASS_HWMON;
    strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
    adap->algo = &omap_i2c_algo;  //此适配器的通讯算法
    adap->dev.parent = &pdev->dev;
    adap->dev.of_node = pdev->dev.of_node;
    
    /* i2c device drivers may be active on return from add_adapter() */
    adap->nr = pdev->id;  //指定总线号
    r = i2c_add_numbered_adapter(adap);  //注册适配器
    
    of_i2c_register_devices(adap); //注册在DTS中声明的I2C设备

    至此此I2C适配器成功注册,属于他的I2C设备也即将通过注册。稍做休息,然后分析最最重要的adapter->algo成员。

    static const struct i2c_algorithm omap_i2c_algo = {
        .master_xfer    = omap_i2c_xfer,
        .functionality    = omap_i2c_func,
    };

    先看简单的功能查询接口函数:

    static u32
    omap_i2c_func(struct i2c_adapter *adap)
    {
        return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
               I2C_FUNC_PROTOCOL_MANGLING;
    }

    支持I2C、支持仿真SMBUS但不支持快速协议、支持协议编码(自定义协议)。在分析master_xfer成员前先熟悉一下i2c_msg的数据结构:

    struct i2c_msg {
        __u16 addr;    /* slave address            */
        __u16 flags;
    #define I2C_M_TEN        0x0010    /* this is a ten bit chip address */  //10bit从地址
    #define I2C_M_RD        0x0001    /* read data, from slave to master */  //读数据
    /*
    * 相关资料 https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
    */
    #define I2C_M_STOP        0x8000    /* if I2C_FUNC_PROTOCOL_MANGLING */  //每个消息后都会带有一个STOP位
    #define I2C_M_NOSTART        0x4000    /* if I2C_FUNC_NOSTART */ //多消息传输,在第二个消息前设置此位
    #define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */ //切换读写标志位
    #define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */ //no ACK位会被视为ACK
    #define I2C_M_NO_RD_ACK        0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */  //读消息时候,主设备的ACK/no ACK位会被忽略
    #define I2C_M_RECV_LEN        0x0400    /* length will be first received byte */
        __u16 len;        /* msg length                */
        __u8 *buf;        /* pointer to msg data            */
    };
    • addr即从设备地址
    • flags可以控制数据、协议格式等
    • len代表消息产股的
    • buf是指向所传输数据的指针

    下面介绍AM3359 I2C适配器的传输机制:

    static int
    omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
    {
        struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
        int i;
        int r;
    
        r = pm_runtime_get_sync(dev->dev);
        if (IS_ERR_VALUE(r))
            goto out;
    
        r = omap_i2c_wait_for_bb(dev);  //通过读取寄存器I2C_IRQSTATUS的12位BB查询总线状态,等待总线空闲
        if (r < 0)
            goto out;
    
        if (dev->set_mpu_wkup_lat != NULL)
            dev->set_mpu_wkup_lat(dev->dev, dev->latency);
    
        for (i = 0; i < num; i++) {
            r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));  //传输消息,最后一条消息接STOP位
            if (r != 0)
                break;
        }
    
        if (r == 0)
            r = num;
    
        omap_i2c_wait_for_bb(dev);    
    out:
        pm_runtime_mark_last_busy(dev->dev);
        pm_runtime_put_autosuspend(dev->dev);
        return r;
    }

    omap_i2c_xfer_msg比较长,让我们慢慢分析:

    static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
                     struct i2c_msg *msg, int stop)
    {
        struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
        unsigned long timeout;
        u16 w;
    
        dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d
    ",
            msg->addr, msg->len, msg->flags, stop);
    
        if (msg->len == 0)  //无效长度检测
            return -EINVAL;
    
        dev->receiver = !!(msg->flags & I2C_M_RD);  //判断是否为读取数据,若是则为receiver模式
        omap_i2c_resize_fifo(dev, msg->len, dev->receiver);  //根据所需发送/接收数据调整并清空对应FIFO,操作I2C_BUF寄存器0x94
    //14位,清除接收FIFO,13~8位设置接收FIFO大小,最大64字节
    //6位,清除发送FIFO,0~5位设置发送FIFO大小,最大64字节
    
        omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr);  //写入从地址
    
        /* REVISIT: Could the STB bit of I2C_CON be used with probing? */
        dev->buf = msg->buf; //组装消息
        dev->buf_len = msg->len;
    
        /* make sure writes to dev->buf_len are ordered */
        barrier();
    
        omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len);  //写入消息数量
    
        /* Clear the FIFO Buffers */
        w = omap_i2c_read_reg(dev, OMAP_I2C_BUF_REG);
        w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR;
        omap_i2c_write_reg(dev, OMAP_I2C_BUF_REG, w);  //依然是清除FIFO,在omap_i2c_resize_fifo中只清除了RX/TX之一,由dev->receiver决定
    
        INIT_COMPLETION(dev->cmd_complete);  //初始化等待量,是为中断处理线程准备的
        dev->cmd_err = 0;  //清空错误码
    
        w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT;  //使能I2C适配器,并设置master模式,产生开始位。即S-A-D
    /* S开始位,A从地址,D数据,P停止位。在I2C适配器发送数据时的序列为:
    * S-A-D-(n)-P
    * 而即便是I2C适配器从从设备中读取数据,其协议头也是一样的,之后后续发生改变:
    * S-A-D-S-A-D-P 关于读写方向,一包含在A中。所以无论是读还是写,第一个S-A-D都会有的。
    */
        /* High speed configuration */
        if (dev->speed > 400)
            w |= OMAP_I2C_CON_OPMODE_HS;
    
        if (msg->flags & I2C_M_STOP)
            stop = 1;
        if (msg->flags & I2C_M_TEN) //10bit从地址扩展
            w |= OMAP_I2C_CON_XA;
        if (!(msg->flags & I2C_M_RD))
            w |= OMAP_I2C_CON_TRX;  //设置是发送、接收模式
    
        if (!dev->b_hw && stop)  //在传输最后生成一个STOP位,若flags设置了I2C_M_STOP则每一个消息后都要跟一个STOP位(真的有这样的从设备需求)
            w |= OMAP_I2C_CON_STP;
    
        omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);  //通过设置I2C_CON寄存器初始化一次传输,此处后进入中断程序
    
        /*
         * Don't write stt and stp together on some hardware.
         */
        if (dev->b_hw && stop) {
            unsigned long delay = jiffies + OMAP_I2C_TIMEOUT;
            u16 con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
            while (con & OMAP_I2C_CON_STT) {
                con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
    
                /* Let the user know if i2c is in a bad state */
                if (time_after(jiffies, delay)) {
                    dev_err(dev->dev, "controller timed out "
                    "waiting for start condition to finish
    ");
                    return -ETIMEDOUT;
                }
                cpu_relax();
            }
    
            w |= OMAP_I2C_CON_STP;
            w &= ~OMAP_I2C_CON_STT;
            omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);  //写停止位
        }
    
        /*
         * REVISIT: We should abort the transfer on signals, but the bus goes
         * into arbitration and we're currently unable to recover from it.
         */
        timeout = wait_for_completion_timeout(&dev->cmd_complete,
                            OMAP_I2C_TIMEOUT); //等待中断处理完成
        if (timeout == 0) {
            dev_err(dev->dev, "controller timed out
    ");
            omap_i2c_reset(dev);
            __omap_i2c_init(dev);
            return -ETIMEDOUT;
        }
    
        if (likely(!dev->cmd_err))  //下边是一些错误处理,错误码会在中断处理中出错的时候配置上
            return 0;
    
        /* We have an error */
        if (dev->cmd_err & (OMAP_I2C_STAT_AL | OMAP_I2C_STAT_ROVR |
                    OMAP_I2C_STAT_XUDF)) {
            omap_i2c_reset(dev);
            __omap_i2c_init(dev);
            return -EIO;
        }
    
        if (dev->cmd_err & OMAP_I2C_STAT_NACK) {
            if (msg->flags & I2C_M_IGNORE_NAK)
                return 0;
            if (stop) {
                w = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
                w |= OMAP_I2C_CON_STP;
                omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);
            }
            return -EREMOTEIO;
        }
        return -EIO;
    }

    可见,这里只是对消息的发送、接收做了前期的初始化以及扫尾工作,关键在于中断如何处理:

    static irqreturn_t
    omap_i2c_isr(int irq, void *dev_id)
    {
        struct omap_i2c_dev *dev = dev_id;
        irqreturn_t ret = IRQ_HANDLED;
        u16 mask;
        u16 stat;
    
        spin_lock(&dev->lock);
        mask = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
        stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
    
        if (stat & mask)  //检验中断是否有效,若有效则开启中断线程
            ret = IRQ_WAKE_THREAD;
    
        spin_unlock(&dev->lock);
    
        return ret;
    }

    接下来进入I2C适配器的中断处理线程:

    static irqreturn_t
    omap_i2c_isr_thread(int this_irq, void *dev_id)
    {
        struct omap_i2c_dev *dev = dev_id;
        unsigned long flags;
        u16 bits;
        u16 stat;
        int err = 0, count = 0;
    
        spin_lock_irqsave(&dev->lock, flags);
        do {
            bits = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
            stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
            stat &= bits;  //IRQ status和使能寄存器基本是一一对应的(除部分保留位)
    
            /* If we're in receiver mode, ignore XDR/XRDY */ //根据不同模式自动忽略对应寄存器
            if (dev->receiver)
                stat &= ~(OMAP_I2C_STAT_XDR | OMAP_I2C_STAT_XRDY);
            else
                stat &= ~(OMAP_I2C_STAT_RDR | OMAP_I2C_STAT_RRDY);
    
            if (!stat) {
                /* my work here is done */
                goto out;
            }  //过滤一圈下来发现白扯了~Orz
    
            dev_dbg(dev->dev, "IRQ (ISR = 0x%04x)
    ", stat);
            if (count++ == 100) {  //一次中断可能带有多个事件,如事件过多(100个)直接放弃……
                dev_warn(dev->dev, "Too much work in one IRQ
    ");
                break;
            }
    
            if (stat & OMAP_I2C_STAT_NACK) {  //收到NO ACK位
                err |= OMAP_I2C_STAT_NACK;
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_NACK);  //记录错误码,清空此位
                break;
            }
    
            if (stat & OMAP_I2C_STAT_AL) { //在发送模式中,丢失Arbitration后自动置位
                dev_err(dev->dev, "Arbitration lost
    ");
                err |= OMAP_I2C_STAT_AL;
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_AL);
                break;
            }
    
            /*
             * ProDB0017052: Clear ARDY bit twice
             */
            if (stat & (OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK |
                        OMAP_I2C_STAT_AL)) {
                omap_i2c_ack_stat(dev, (OMAP_I2C_STAT_RRDY |
                            OMAP_I2C_STAT_RDR |
                            OMAP_I2C_STAT_XRDY |
                            OMAP_I2C_STAT_XDR |
                            OMAP_I2C_STAT_ARDY));
                break;
            }
        //接收数据,不过我没太弄懂RDR和RRDY的关系,应该是一个是FIFO中的数据,一个不是。有高手请帮解读下,不胜感激。
            if (stat & OMAP_I2C_STAT_RDR) {  //RDR有效
                u8 num_bytes = 1;
    
                if (dev->fifo_size)
                    num_bytes = dev->buf_len;
    
                omap_i2c_receive_data(dev, num_bytes, true); //从I2C_DATA寄存器中读取接收到的数据
    
                if (dev->errata & I2C_OMAP_ERRATA_I207)
                    i2c_omap_errata_i207(dev, stat);
    
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RDR);
                continue;
            }
    
            if (stat & OMAP_I2C_STAT_RRDY) {  //有新消息待读
                u8 num_bytes = 1;
    
                if (dev->threshold)
                    num_bytes = dev->threshold;
    
                omap_i2c_receive_data(dev, num_bytes, false);  //接收数据
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RRDY);
                continue;
            }
        //发送数据相关
            if (stat & OMAP_I2C_STAT_XDR) {
                u8 num_bytes = 1;
                int ret;
    
                if (dev->fifo_size)
                    num_bytes = dev->buf_len;
    
                ret = omap_i2c_transmit_data(dev, num_bytes, true);  //将数据写入I2C_DATA寄存器
                if (ret < 0)
                    break;
    
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XDR);
                continue;
            }
    
            if (stat & OMAP_I2C_STAT_XRDY) {
                u8 num_bytes = 1;
                int ret;
    
                if (dev->threshold)
                    num_bytes = dev->threshold;
    
                ret = omap_i2c_transmit_data(dev, num_bytes, false);
                if (ret < 0)
                        break;
    
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XRDY);
                continue;
            }
    
            if (stat & OMAP_I2C_STAT_ROVR) { //接收溢出
                dev_err(dev->dev, "Receive overrun
    ");
                err |= OMAP_I2C_STAT_ROVR;
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_ROVR);
                break;
            }
    
            if (stat & OMAP_I2C_STAT_XUDF) { //发送溢出
                dev_err(dev->dev, "Transmit underflow
    ");
                err |= OMAP_I2C_STAT_XUDF;
                omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XUDF);
                break;
            }
        } while (stat);
    
        omap_i2c_complete_cmd(dev, err);  //通知传输函数完成(可以写STOP位了),并带回错误码
    
    out:
        spin_unlock_irqrestore(&dev->lock, flags);
    
        return IRQ_HANDLED;
    }

    到这里就分析完AM3359的I2C总线适配器的消息传输算法了。关于RDR/RRDY和XDR/XRDY的困惑之后我会去自己分辨,如果有了新的理解会及时更新。若有大牛路过,也希望对此给予指点一二。

    总结:

    通过对AM3359集成的I2C总线适配器的驱动分析,可以看到对于适配器驱动来说,需要包含一下几点:

    • 电源管理
    • 初始化(时钟、中断等参数设置)
    • 消息传输算法实现

    其中最复杂,也最重要的模块就是传输算法的实现,虽然模式中主要就是两种(master/slave),但是对中断状态的检测尤为重要,而且其中还要有必要的判错防御代码来保证在出现异常的情况下I2C适配器能够自矫正进而继续正常工作。

  • 相关阅读:
    Centos7下部署两套python版本并存环境的操作记录
    JSON格式化输出和解析工具
    利用阿里云的源yum方式安装Mongodb
    Ansible配置及常用模块总结
    VMware/KVM/OpenStack虚拟化之网络模式总结
    Mac下通过VMware Fusion安装centos虚拟机操作记录
    Supervisor (进程管理利器) 使用说明
    zabbix中配置当memory剩余不足20%时触发报警
    分布式监控系统Zabbix-3.0.3--短信报警设置
    linux下用户操作记录审计环境的部署记录
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4476248.html
Copyright © 2020-2023  润新知