IMX6ULL的I2C驱动详细分析

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: IMX6ULL的I2C驱动详细分析

i2c_imx_driver 的平台驱动注册

static struct platform_driver i2c_imx_driver = {
    .probe = i2c_imx_probe, // 注册函数
    .remove = i2c_imx_remove, // 注销函数
    .driver    = {
        .name = DRIVER_NAME, // 驱动名
        .owner = THIS_MODULE, // 模块拥有者
        .of_device_id = i2c_imx_dt_ids, // 设备树匹配表
        .pm = IMX_I2C_PM, // 电源管理
    },
    .id_table    = imx_i2c_devtype, // 设备ID表
};
static int __init i2c_adap_imx_init(void)
{
    return platform_driver_register(&i2c_imx_driver); // 注册平台驱动
}
subsys_initcall(i2c_adap_imx_init); // 子系统初始化
static void __exit i2c_adap_imx_exit(void)
{
    platform_driver_unregister(&i2c_imx_driver); // 注销平台驱动
}
module_exit(i2c_adap_imx_exit); // 模块退出

这段代码定义了一个名为 i2c_imx_driver 的平台驱动结构体,并实现了两个函数:i2c_adap_imx_init 和 i2c_adap_imx_exit。

首先,i2c_imx_driver 结构体中定义了以下字段:

probe:注册函数,用于在设备匹配成功时调用以进行设备初始化。

remove:注销函数,用于在设备被移除时调用以进行资源释放。

driver:驱动结构体,其中包含驱动的名称、模块拥有者、设备树匹配表和电源管理等信息。

id_table:设备ID表,用于指定支持的设备ID。

接下来,在 i2c_adap_imx_init 函数中,通过调用 platform_driver_register 函数来注册平台驱动 i2c_imx_driver。这将使得驱动在系统初始化期间被加载并可用。函数返回注册结果。

然后,在 i2c_adap_imx_exit 函数中,通过调用 platform_driver_unregister 函数来注销平台驱动 i2c_imx_driver。这将在模块退出时执行,用于释放已注册的平台驱动。函数没有返回值。

最后,使用 module_exit 宏将 i2c_adap_imx_exit 函数注册为模块的退出函数。这将在模块卸载时调用以执行平台驱动的注销操作。

这段代码的作用是实现了一个基于平台的 I2C 驱动,并提供了初始化和退出函数来注册和注销该驱动。驱动注册时将根据设备树的匹配信息进行初始化,并在模块退出时进行注销,以确保平台驱动的正确加载和释放。

i2c_imx_probe注册函数

这段代码是 I2C 设备探测函数的实现,它被用作 i2c_imx_driver 的 probe 成员。

函数的主要功能是在设备匹配成功时进行设备初始化。下面是该函数的主要步骤:

获取设备树匹配信息,用于判断是否有设备树匹配数据。

获取中断号和资源信息,并对 I2C 控制器的基地址进行映射。

分配并初始化 i2c_imx_struct 结构体,用于存储与该 I2C 设备相关的信息。

设置 I2C 适配器的名称、拥有者、算法等成员变量。

获取并使能 I2C 时钟。

请求中断,并设置中断处理函数。

初始化等待队列和适配器数据。

设置时钟分频和芯片寄存器的默认值。

添加 I2C 适配器。

设置平台驱动数据。

注销时钟,并打印调试信息。

如果支持 DMA,初始化 DMA 配置。

最后,函数返回0表示成功初始化设备,返回其他错误代码表示初始化失败。

该函数的作用是在 I2C 设备探测阶段完成必要的初始化操作,以确保 I2C 设备正常工作。

// I2C设备探测函数
static int i2c_imx_probe(struct platform_device *pdev)
{
    // 获取设备树匹配信息
    const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
                               &pdev->dev);
    // 定义I2C设备结构体指针
    struct imx_i2c_struct *i2c_imx;
    // 定义资源结构体指针
    struct resource *res;
    // 定义I2C平台数据结构体指针
    struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
    // 定义I2C控制器基地址指针
    void __iomem *base;
    // 定义中断号
    int irq, ret;
    // 定义DMA物理地址
    dma_addr_t phy_addr;
    // 打印调试信息
    dev_dbg(&pdev->dev, "<%s>\n", __func__);
    // 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "can't get irq number\n");
        return irq;
    }
    // 获取资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    // 映射I2C控制器基地址
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);
    // 获取DMA物理地址
    phy_addr = (dma_addr_t)res->start;
    // 分配I2C设备结构体内存
    i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
    if (!i2c_imx)
        return -ENOMEM;
    // 判断是否有设备树匹配信息
    if (of_id)
        i2c_imx->hwdata = of_id->data;
    else
        i2c_imx->hwdata = (struct imx_i2c_hwdata *)
                platform_get_device_id(pdev)->driver_data;
    /* Setup i2c_imx driver structure */
    // 复制设备名到I2C适配器结构体中
    strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
    // 设置I2C适配器结构体中的成员变量
    i2c_imx->adapter.owner        = THIS_MODULE;
    i2c_imx->adapter.algo        = &i2c_imx_algo;
    i2c_imx->adapter.dev.parent    = &pdev->dev;
    i2c_imx->adapter.nr        = pdev->id;
    i2c_imx->adapter.dev.of_node    = pdev->dev.of_node;
    i2c_imx->base            = base;
    /* 获取I2C时钟 */
    i2c_imx->clk = devm_clk_get(&pdev->dev, NULL); // 获取I2C时钟
    if (IS_ERR(i2c_imx->clk)) {
        dev_err(&pdev->dev, "can't get I2C clock\n"); // 获取时钟失败
        return PTR_ERR(i2c_imx->clk);
    }
    ret = clk_prepare_enable(i2c_imx->clk); // 使能I2C时钟
    if (ret) {
        dev_err(&pdev->dev, "can't enable I2C clock\n"); // 使能时钟失败
        return ret;
    }
    /* 请求中断 */
    ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
                   IRQF_NO_SUSPEND, pdev->name, i2c_imx); // 请求中断
    if (ret) {
        dev_err(&pdev->dev, "can't claim irq %d\n", irq); // 请求中断失败
        goto clk_disable;
    }
    /* 初始化队列 */
    init_waitqueue_head(&i2c_imx->queue); // 初始化等待队列
    /* 设置适配器数据 */
    i2c_set_adapdata(&i2c_imx->adapter, i2c_imx); // 设置适配器数据
    /* 设置时钟分频 */
    i2c_imx->bitrate = IMX_I2C_BIT_RATE;
    ret = of_property_read_u32(pdev->dev.of_node,
                   "clock-frequency", &i2c_imx->bitrate);
    if (ret < 0 && pdata && pdata->bitrate)
        i2c_imx->bitrate = pdata->bitrate;
    /* 设置芯片寄存器为默认值 */
    imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
            i2c_imx, IMX_I2C_I2CR);
    imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
    /* 添加I2C适配器 */
    ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
    if (ret < 0) {
        dev_err(&pdev->dev, "registration failed\n");
        goto clk_disable;
    }
    /* 设置平台驱动数据 */
    platform_set_drvdata(pdev, i2c_imx);
    clk_disable_unprepare(i2c_imx->clk);
    dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
    dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
    dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
        i2c_imx->adapter.name);
    dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");
    /* 如果支持DMA,则初始化DMA配置 */
    i2c_imx_dma_request(i2c_imx, phy_addr);
    return 0;   /* 返回OK */
clk_disable:
    clk_disable_unprepare(i2c_imx->clk);
    return ret;
}

i2c_imx_algoI2C算法结构体

函数 i2c_imx_xfer 是一个 I2C 传输函数,用于执行一系列的 I2C 数据传输操作。以下是该函数的主要步骤:

执行 I2C 传输的准备工作,包括启动 I2C 传输。

针对每个传输消息进行读写操作。根据消息的标志位,如果是读操作,则调用 i2c_imx_read 函数进行读取;如果是写操作,则根据数据长度决定是否使用 DMA 进行写操作,或者使用普通的写操作函数 i2c_imx_write。

如果发生错误,跳转到 fail0 标签处进行错误处理。

完成所有消息的传输后,调用 i2c_imx_stop 函数停止 I2C 传输。

打印调试信息,然后根据传输结果返回传输的消息数或错误代码。

函数 i2c_imx_func 是用于返回 I2C 总线所支持的功能的函数。它指定了 I2C 总线支持的功能,包括基本的 I2C 功能、SMBus 模拟功能以及读取块数据的功能。

这两个函数一起实现了在 I2C 总线上进行数据传输的功能,并提供了对 I2C 总线所支持功能的查询。

// I2C算法结构体
static struct i2c_algorithm i2c_imx_algo = {
    .master_xfer    = i2c_imx_xfer, // 主机传输函数
    .functionality    = i2c_imx_func, // 返回总线支持的功能
};
static int i2c_imx_xfer(struct i2c_adapter *adapter,
                        struct i2c_msg *msgs, int num)
{
    unsigned int i, temp;
    int result;
    bool is_lastmsg = false;
    struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
    dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
    /* Start I2C transfer */
    result = i2c_imx_start(i2c_imx); // 开始I2C传输
    if (result)
        goto fail0;
    /* read/write data */
    for (i = 0; i < num; i++) { // 读写数据
        if (i == num - 1)
            is_lastmsg = true;
        if (i) {
            dev_dbg(&i2c_imx->adapter.dev,
                "<%s> repeated start\n", __func__);
            temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
            temp |= I2CR_RSTA; // 重复启动
            imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
            result =  i2c_imx_bus_busy(i2c_imx, 1);
            if (result)
                goto fail0;
        }
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> transfer message: %d\n", __func__, i);
        /* write/read data */
#ifdef CONFIG_I2C_DEBUG_BUS
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> CONTROL: IEN=%d, IIEN=%d, MSTA=%d, MTX=%d, TXAK=%d, RSTA=%d\n",
            __func__,
            (temp & I2CR_IEN ? 1 : 0), (temp & I2CR_IIEN ? 1 : 0),
            (temp & I2CR_MSTA ? 1 : 0), (temp & I2CR_MTX ? 1 : 0),
            (temp & I2CR_TXAK ? 1 : 0), (temp & I2CR_RSTA ? 1 : 0));
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> STATUS: ICF=%d, IAAS=%d, IBB=%d, IAL=%d, SRW=%d, IIF=%d, RXAK=%d\n",
            __func__,
            (temp & I2SR_ICF ? 1 : 0), (temp & I2SR_IAAS ? 1 : 0),
            (temp & I2SR_IBB ? 1 : 0), (temp & I2SR_IAL ? 1 : 0),
            (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),
            (temp & I2SR_RXAK ? 1 : 0));
#endif
        if (msgs[i].flags & I2C_M_RD)
            result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg); // 读取
        else {
            if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
                result = i2c_imx_dma_write(i2c_imx, &msgs[i]); // DMA写
            else
                result = i2c_imx_write(i2c_imx, &msgs[i]); // 写
        }
        if (result)
            goto fail0;
    }
fail0:
    /* Stop I2C transfer */
    i2c_imx_stop(i2c_imx); // 停止I2C传输
    dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
        (result < 0) ? "error" : "success msg",
            (result < 0) ? result : num);
    return (result < 0) ? result : num;
}
// 返回I2C总线支持的功能
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
        | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}

i2c_imx_start开始I2C

函数 i2c_imx_start 用于开始一次 I2C 事务。以下是该函数的主要步骤:

设置 I2C 时钟并准备使能该时钟。

将分频值写入相应寄存器。

启用 I2C 控制器,清除相关寄存器的状态。

等待控制器稳定,延时一段时间。

设置控制器为主机模式,并检查总线是否繁忙。

将控制器的配置设置为发送模式,并开启中断和发送应答信号。

返回执行结果。

函数的主要目的是初始化和配置 I2C 控制器,以准备开始一次 I2C 事务。它确保时钟和寄存器的设置正确,并将控制器设置为主机模式。函数执行成功后,即可开始进行 I2C 数据传输。

// 开始I2C事务
static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
{
    unsigned int temp = 0;
    int result;
    dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
    // 设置I2C时钟
    i2c_imx_set_clk(i2c_imx);
    // 准备并使能I2C时钟
    result = clk_prepare_enable(i2c_imx->clk);
    if (result)
        return result;
    // 写入分频值
    imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
    /* 启用I2C控制器 */
    imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
    imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR);
    /* 等待控制器稳定 */
    udelay(50);
    /* 开始I2C事务 */
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
    temp |= I2CR_MSTA;
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    result = i2c_imx_bus_busy(i2c_imx, 1);
    if (result)
        return result;
    i2c_imx->stopped = 0;
    temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK;
    temp &= ~I2CR_DMAEN;
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    return result;
}

i2c_imx_stop停止I2C

函数 i2c_imx_stop 用于停止当前的 I2C 事务。以下是该函数的主要步骤:

检查是否已停止当前的 I2C 事务。如果是,则直接返回。

如果尚未停止,则执行以下操作:

清除控制器的主机模式和发送模式标志位,如果使用 DMA,则还会清除 DMA 使能标志位。

在某些 i.MXL 硬件错误的情况下,添加延迟以确保生成 “STOP” 位。

标记总线非繁忙,并将 stopped 标志设置为 1,表示 I2C 事务已停止。

禁用 I2C 控制器并释放相关资源。

函数的主要目的是完成 I2C 事务的停止过程。它会清除控制器的相关标志位,并释放使用的资源,以便下次进行新的 I2C 事务。

/*
 * 停止I2C事务
 */
static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
{
    unsigned int temp = 0;
    if (!i2c_imx->stopped) {
        /* 停止I2C事务 */
        dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
        temp &= ~(I2CR_MSTA | I2CR_MTX);
        if (i2c_imx->dma)
            temp &= ~I2CR_DMAEN;
        imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    }
    if (is_imx1_i2c(i2c_imx)) {
        /*
         * 这个延迟是由于i.MXL硬件错误引起的。
         * 如果没有(或太短的)延迟,将不会生成“STOP”位。
         */
        udelay(i2c_imx->disable_delay);
    }
    if (!i2c_imx->stopped) {
        i2c_imx_bus_busy(i2c_imx, 0);
        i2c_imx->stopped = 1;
    }
    /* 禁用I2C控制器 */
    temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    clk_disable_unprepare(i2c_imx->clk);
}

i2c_imx_isr中断服务函数

该函数是 I2C 控制器的中断服务例程。

当 I2C 控制器产生中断时,会调用该函数来处理中断。

函数首先读取状态寄存器 I2SR,并检查中断标志位 IIF 是否被设置。

如果中断标志位已经被设置,则保存状态寄存器的值,并清除中断标志位。

然后,函数会根据硬件数据表中的操作码,将清除中断标志位的操作码写入状态寄存器。

最后,函数会唤醒等待队列,以通知等待中的线程中断已经处理完毕。

如果中断标志位未被设置,函数返回 IRQ_NONE,表示未处理中断。

如果中断标志位已被设置并成功处理,函数返回 IRQ_HANDLED,表示中断已被处理。

static irqreturn_t i2c_imx_isr(int irq, void *dev_id)
{
    struct imx_i2c_struct *i2c_imx = dev_id;
    unsigned int temp;
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
    if (temp & I2SR_IIF) {
        /* 保存状态寄存器 */
        i2c_imx->i2csr = temp;
        temp &= ~I2SR_IIF;
        temp |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF);
        imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2SR);
        wake_up(&i2c_imx->queue); // 唤醒等待队列
        return IRQ_HANDLED;
    }
    return IRQ_NONE;
}

i2c_imx_dma_writeDMA 进行写操作的 I2C 传输

以下是函数 i2c_imx_dma_write 的概要总结:该函数用于使用 DMA 进行写操作的 I2C 传输。

首先,函数对 DMA 相关参数进行设置,包括选择发送通道、传输方向、数据方向和传输长度。

然后,函数调用

i2c_imx_dma_xfer 函数执行 DMA 传输,将消息数据传输到设备。

接下来,函数开启 DMA 传输,将 I2C 控制器的

I2CR 寄存器中的 DMA 使能位置为1。

函数将从设备地址写入到

I2DR 寄存器,其中第一个字节必须由 CPU 传输。

函数使用等待完成的方式等待传输完成,同时检查是否超时。

一旦传输完成,函数关闭 DMA 传输,将 I2C 控制器的 I2CR 寄存器中的 DMA 使能位置为0。

最后,函数将最后一个数据字节由 CPU 传输到 I2DR 寄存器,并检查传输的完成情况。

如果传输过程中发生错误,函数将返回相应的错误代码。

如果传输成功完成,则调用

i2c_imx_acked 函数以确认从设备的应答状态,并返回结果。

static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx,
                    struct i2c_msg *msgs)
{
    int result;
    unsigned long time_left;
    unsigned int temp = 0;
    unsigned long orig_jiffies = jiffies;
    struct imx_i2c_dma *dma = i2c_imx->dma;
    struct device *dev = &i2c_imx->adapter.dev;
    // 设置DMA通道为发送通道
    dma->chan_using = dma->chan_tx;
    // DMA传输方向为从内存到设备
    dma->dma_transfer_dir = DMA_MEM_TO_DEV;
    // DMA数据方向为发送
    dma->dma_data_dir = DMA_TO_DEVICE;
    // DMA传输长度为消息长度减1
    dma->dma_len = msgs->len - 1;
    result = i2c_imx_dma_xfer(i2c_imx, msgs);
    if (result)
        return result;
    // 开启DMA传输
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
    temp |= I2CR_DMAEN;
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    /*
     * 写从设备地址。
     * 第一个字节必须由CPU传输。
     */
    imx_i2c_write_reg(msgs->addr << 1, i2c_imx, IMX_I2C_I2DR);
    reinit_completion(&i2c_imx->dma->cmd_complete);
    time_left = wait_for_completion_timeout(
                &i2c_imx->dma->cmd_complete,
                msecs_to_jiffies(DMA_TIMEOUT));
    if (time_left == 0) {
        dmaengine_terminate_all(dma->chan_using);
        return -ETIMEDOUT;
    }
    /* 等待传输完成 */
    while (1) {
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);
        if (temp & I2SR_ICF) // 如果传输完成
            break;
        if (time_after(jiffies, orig_jiffies +
                msecs_to_jiffies(DMA_TIMEOUT))) { // 如果超时
            dev_dbg(dev, "<%s> Timeout\n", __func__);
            return -ETIMEDOUT;
        }
        schedule(); // 等待
    }
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
    temp &= ~I2CR_DMAEN; // 关闭DMA传输
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    /* 最后一个数据字节必须由CPU传输 */
    imx_i2c_write_reg(msgs->buf[msgs->len-1],
                i2c_imx, IMX_I2C_I2DR);
    result = i2c_imx_trx_complete(i2c_imx);
    if (result)
        return result;
    return i2c_imx_acked(
i2c_imx);
}

2c_imx_dma_read

函数i2c_imx_dma_read是用于在i.MX系列芯片上进行I2C数据读取的函数。它使用DMA进行数据传输,并处理了传输完成、超时等情况。

函数的主要步骤如下:

开启DMA传输:设置I2C控制寄存器的DMAEN位,启用DMA传输。

设置DMA通道为接收通道:将DMA通道设置为接收通道,并指定数据传输方向为从设备到内存。

执行DMA传输:调用i2c_imx_dma_xfer函数执行DMA传输。

等待DMA传输完成:使用等待完成机制等待DMA传输完成,如果超时则终止DMA传输并返回超时错误。

等待数据传输完成:循环检查I2C状态寄存器,直到传输完成或超时。如果超时,则返回超时错误。

关闭DMA传输:将I2C控制寄存器的DMAEN位清除,关闭DMA传输。

读取数据:从I2C数据寄存器读取n-1个字节的数据,并调用i2c_imx_trx_complete函数完成传输。

处理最后一个消息:如果是最后一个消息,清除主模式和传输模式位,将总线设置为空闲状态,并标记传输已停止。否则,设置传输模式为主模式接收,并读取最后一个字节的数据。

返回0:传输完成,返回0表示成功。

总体而言,函数i2c_imx_dma_read使用DMA进行I2C数据读取,处理了传输中断、超时和最后一个消息的情况,并最终返回传输结果。

static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
            struct i2c_msg *msgs, bool is_lastmsg)
{
    int result;
    unsigned long time_left;
    unsigned int temp;
    unsigned long orig_jiffies = jiffies;
    struct imx_i2c_dma *dma = i2c_imx->dma;
    struct device *dev = &i2c_imx->adapter.dev;
    // 开启DMA传输
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
    temp |= I2CR_DMAEN;
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
    // 设置DMA通道为接收通道
    dma->chan_using = dma->chan_rx;
    // DMA传输方向为从设备到内存
    dma->dma_transfer_dir = DMA_DEV_TO_MEM;
    // DMA数据方向为接收
    dma->dma_data_dir = DMA_FROM_DEVICE;
    // 最后两个数据字节必须由CPU传输
    dma->dma_len = msgs->len - 2;
    result = i2c_imx_dma_xfer(i2c_imx, msgs);
    if (result)
        return result;
    // 等待DMA传输完成
    reinit_completion(&i2c_imx->dma->cmd_complete);
    time_left = wait_for_completion_timeout(
                &i2c_imx->dma->cmd_complete,
                msecs_to_jiffies(DMA_TIMEOUT));
    if (time_left == 0) {
        dmaengine_terminate_all(dma->chan_using);
        return -ETIMEDOUT;
    }
    /* 等待传输完成 */
    while (1) {
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); // 读取I2SR寄存器
        if (temp & I2SR_ICF) // 如果传输完成
            break;
        if (time_after(jiffies, orig_jiffies +
                msecs_to_jiffies(DMA_TIMEOUT))) { // 如果超时
            dev_dbg(dev, "<%s> Timeout\n", __func__); // 调试信息
            return -ETIMEDOUT;
        }
        schedule(); // 等待
    }
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); // 读取I2CR寄存器
    temp &= ~I2CR_DMAEN; // 关闭DMA传输
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); // 写入I2CR寄存器
    /* 读取n-1个字节的数据 */
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); // 读取I2CR寄存器
    temp |= I2CR_TXAK; // 设置NACK
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); // 写入I2CR寄存器
    msgs->buf[msgs->len-2] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); // 读取n-1个字节的数据
    /* 读取n个字节的数据 */
    result = i2c_imx_trx_complete(i2c_imx); // 传输完成
    if (result)
        return result;
    if (is_lastmsg) { // 如果是最后一个消息
        /*
         * 在读取I2DR之前,必须生成STOP以防止控制器生成另一个时钟周期
         */
        dev_dbg(dev, "<%s> clear MSTA\n", __func__); // 调试信息
        temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); // 读取I2CR寄存器
        temp &= ~(I2CR_MSTA | I2CR_MTX); // 清除MSTA和MTX位
        imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); // 写入I2CR寄存器
        i2c_imx_bus_busy(i2c_imx, 0); // 总线空闲
        i2c_imx->stopped = 1; // 停止
    } else {
        /*
         * 对于I2C主接收器,重复重启操作如下:
         * 读取->重复MSTA->读/写
         * 在第一个读操作中,在读取最后一个字节之前,控制器必须设置MTX,否则第一个读操作会多花费一个时钟周期。
         */
        temp = readb(i2c_imx->base + IMX_I2C_I2CR); // 读取I2CR寄存器
        temp |= I2CR_MTX; // 设置MTX位
        writeb(temp, i2c_imx->base + IMX_I2C_I2CR); // 写入I2CR寄存器
    }
    msgs->buf[msgs->len-1] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); // 读取最后一个字节的数据
    return 0; // 返回0
}

i2c_imx_write

函数i2c_imx_write是用于在i.MX系列芯片上进行I2C数据写入的函数。

函数的主要步骤如下:

输出调试信息:打印从设备地址。

写入从设备地址:将从设备地址左移一位并写入I2C数据寄存器。

等待传输完成:调用i2c_imx_trx_complete函数等待传输完成。

检查ACK:调用i2c_imx_acked函数检查是否收到ACK。

输出调试信息:打印写入数据的调试信息。

写入数据:使用循环将数据逐个字节写入I2C数据寄存器。

等待传输完成:调用i2c_imx_trx_complete函数等待传输完成。

检查ACK:调用i2c_imx_acked函数检查是否收到ACK。

返回0:传输完成,返回0表示成功。

总体而言,函数i2c_imx_write用于通过I2C总线向从设备写入数据。它将从设备地址和数据逐个字节写入I2C数据寄存器,并在每个字节写入后等待传输完成和检查ACK。最后,函数返回传输结果。

static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
{
    int i, result;
    dev_dbg(&i2c_imx->adapter.dev, "<%s> write slave address: addr=0x%x\n",
        __func__, msgs->addr << 1); // 输出调试信息,写从设备地址
    /* write slave address */
    imx_i2c_write_reg(msgs->addr << 1, i2c_imx, IMX_I2C_I2DR); // 写从设备地址
    result = i2c_imx_trx_complete(i2c_imx); // 传输完成
    if (result)
        return result;
    result = i2c_imx_acked(i2c_imx); // 是否收到ACK
    if (result)
        return result;
    dev_dbg(&i2c_imx->adapter.dev, "<%s> write data\n", __func__); // 输出调试信息,写数据
    /* write data */
    for (i = 0; i < msgs->len; i++) {
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> write byte: B%d=0x%X\n",
            __func__, i, msgs->buf[i]); // 输出调试信息,写字节
        imx_i2c_write_reg(msgs->buf[i], i2c_imx, IMX_I2C_I2DR); // 写字节
        result = i2c_imx_trx_complete(i2c_imx); // 传输完成
        if (result)
            return result;
        result = i2c_imx_acked(i2c_imx); // 是否收到ACK
        if (result)
            return result;
    }
    return 0; // 返回0
}

i2c_imx_read

函数i2c_imx_read是用于在i.MX系列芯片上进行I2C数据读取的函数。

函数的主要步骤如下:

输出调试信息:打印从设备地址。

写入从设备地址:将从设备地址左移一位,并将最低位设置为1,表示读取操作。

等待传输完成:调用i2c_imx_trx_complete函数等待传输完成。

检查ACK:调用i2c_imx_acked函数检查是否收到ACK。

输出调试信息:打印设置总线的调试信息。

设置总线以读取数据:读取I2CR寄存器的值,并清除MTX位,表示将进行读取操作。

重置I2CR_TXAK标志位:对于SMBus块读或读取长度大于1的情况,重置I2CR_TXAK标志位。

写入I2CR寄存器:将修改后的值写入I2CR寄存器。

读取I2DR寄存器:执行虚拟读取,读取I2DR寄存器的值(此读取不会返回实际的数据)。

输出调试信息:打印读取数据的调试信息。

如果使用DMA传输且满足使用DMA传输的条件,则调用i2c_imx_dma_read函数进行数据读取。

循环读取数据:使用循环读取数据。

等待传输完成:调用i2c_imx_trx_complete函数等待传输完成。

对于SMBus块读的第一个字节,读取长度并添加到msgs->len中。

如果是最后一个消息:

如果is_lastmsg为true,生成STOP信号并清除MSTA和MTX标志位,以防止控制器生成另一个时钟周期。

如果is_lastmsg为false,进行重复重新启动操作。

对于倒数第二个字节,设置I2CR_TXAK标志位。

如果是SMBus块读的第一个字节,将长度存储在msgs->buf[0]中。

否则,将读取的字节存储在msgs->buf[i]中。

输出调试信息:打印读取的字节信息。

返回0:传输完成,返回0表示成功。

总体而言,函数i2c_imx_read用于通过I2C总线从从设备读取数据。它发送从设备地址,并根据读取长度和是否使用DMA传输等条件进行数据读取。在循环读取数据时,它会检查是否是SMBus块读的第一个字节,并根据是否是最后一个消息进行相应的操作。最后,函数返回传输结果。

static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool is_lastmsg)
{
    int i, result;
    unsigned int temp;
    int block_data = msgs->flags & I2C_M_RECV_LEN; // 判断是否为SMBus块读
    dev_dbg(&i2c_imx->adapter.dev,
        "<%s> write slave address: addr=0x%x\n",
        __func__, (msgs->addr << 1) | 0x01); // 输出调试信息,写从设备地址
    /* write slave address */
    imx_i2c_write_reg((msgs->addr << 1) | 0x01, i2c_imx, IMX_I2C_I2DR); // 写从设备地址
    result = i2c_imx_trx_complete(i2c_imx); // 传输完成
    if (result)
        return result;
    result = i2c_imx_acked(i2c_imx); // 是否收到ACK
    if (result)
        return result;
    dev_dbg(&i2c_imx->adapter.dev, "<%s> setup bus\n", __func__); // 输出调试信息,设置总线
    /* setup bus to read data */
    temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); // 读取I2CR寄存器
    temp &= ~I2CR_MTX; // 清除MTX位
    /*
     * Reset the I2CR_TXAK flag initially for SMBus block read since the
     * length is unknown
     */
    if ((msgs->len - 1) || block_data)
        temp &= ~I2CR_TXAK; // 如果是SMBus块读,则重置I2CR_TXAK标志位
    imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); // 写入I2CR寄存器
    imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */ // 读取I2DR寄存器
    dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__); // 输出调试信息,读取数据
    if (i2c_imx->dma && msgs->len >= DMA_THRESHOLD && !block_data) // 如果使用DMA传输
        return i2c_imx_dma_read(i2c_imx, msgs, is_lastmsg); // 使用DMA传输
    /* read data */
    for (i = 0; i < msgs->len; i++) { // 循环读取数据
        u8 len = 0; // 初始化长度为0
        result = i2c_imx_trx_complete(i2c_imx); // 传输完成
        if (result)
            return result;
        /*
         * First byte is the length of remaining packet
         * in the SMBus block data read. Add it to
         * msgs->len.
         */
        if ((!i) && block_data) { // 如果是SMBus块读
            len = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); // 读取长度
            if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) // 如果长度为0或者大于最大长度
                return -EPROTO; // 返回错误
            dev_dbg(&i2c_imx->adapter.dev,
                "<%s> read length: 0x%X\n",
                __func__, len); // 输出调试信息,读取长度
            msgs->len += len; // 将长度加入到msgs->len中
        }
        // 如果是最后一个消息
        if (i == (msgs->len - 1)) {
            if (is_lastmsg) {
                /*
                 * 在读取I2DR之前,必须生成STOP,以防止控制器生成另一个时钟周期
                 */
                dev_dbg(&i2c_imx->adapter.dev,
                    "<%s> clear MSTA\n", __func__);
                temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
                temp &= ~(I2CR_MSTA | I2CR_MTX);
                imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
                i2c_imx_bus_busy(i2c_imx, 0);
                i2c_imx->stopped = 1;
            } else {
                /*
                 * 对于i2c主接收器,重复重新启动操作如下:
                 * 读取->重复MSTA->读/写
                 * 在第一个读操作中,在读取最后一个字节之前,控制器必须设置MTX,否则第一个读操作会多花费一个时钟周期。
                 */
                temp = readb(i2c_imx->base + IMX_I2C_I2CR);
                temp |= I2CR_MTX;
                writeb(temp, i2c_imx->base + IMX_I2C_I2CR);
            }
        } else if (i == (msgs->len - 2)) {
            dev_dbg(&i2c_imx->adapter.dev,
                "<%s> set TXAK\n", __func__);
            temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
            temp |= I2CR_TXAK;
            imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
        }
        if ((!i) && block_data)
            msgs->buf[0] = len;
        else
            msgs->buf[i] =  imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
        dev_dbg(&i2c_imx->adapter.dev,
            "<%s> read byte: B%d=0x%X\n",
            __func__, i, msgs->buf[i]); // 输出调试信息,读取字节
    }
    return 0;
}

如果文章对您有帮助,点赞👍支持,感谢🤝


相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
目录
相关文章
|
Linux 测试技术 调度
Linux 驱动之gpio-key驱动分析
Linux内核中的gpio-keys.c(driver/input/keyboard/gpio-keys.c)统一了所有关于按键的驱动实现方式。其良好的代码架构可以兼容几乎所有平台的关于按键的处理流程。如果需要在目标平台实现关于按键的驱动程序,完全可以直接使用该驱动,几乎不用自己实现任何代码。
1404 0
|
1月前
|
Ubuntu Linux
编译替换内核_设备树_驱动_IMX6ULL
编译替换内核_设备树_驱动_IMX6ULL
编译替换内核_设备树_驱动_IMX6ULL
|
1月前
|
Ubuntu Linux 开发工具
Linux下的IMX6ULL——开发板的第一个APP和驱动实验(三)
Linux下的IMX6ULL——开发板的第一个APP和驱动实验(三)
107 0
Linux下的IMX6ULL——开发板的第一个APP和驱动实验(三)
|
9月前
|
Linux Android开发
Linux misc子系统框架驱动4412蜂鸣器
Linux misc子系统框架驱动4412蜂鸣器
79 0
Linux misc子系统框架驱动4412蜂鸣器
|
Linux API
Linux驱动分析之LCD驱动架构
在Linux设备中,LCD显示采用了帧缓冲(framebuffer)技术,所以LCD驱动也叫Framebuffer驱动,所以LCD驱动框架就是围绕帧缓冲展开工作。帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象出来,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域写入颜色值,对应的颜色会自动在屏幕上显示。帧缓冲为标准字符设备, 主设备号为29,对应于/dev/fbn。
|
Linux Android开发 芯片
Linux驱动分析之Framebuffer驱动
前面我们了解了LCD的基本架构《Linux驱动分析之LCD驱动架构》,接下来我们拿个具体的实例来分析分析。这样可以了解其大概是如何使用和工作的。
|
缓存 Linux API
Linux驱动分析之Uart驱动架构
UART设备驱动可以使用tty驱动的框架来实现,但是因为串口之间有共性,所以Linux在tty接口上封装了一层(serial core)。后面我们再拿一篇文章来解释tty驱动,tty其实就是各种终端设备,串口其实也是终端设备。
Linux驱动分析之Uart驱动架构
|
芯片
最简单的LED驱动程序编写流程--基于IMX6ULL
最简单的LED驱动程序编写流程--基于IMX6ULL
171 0
|
编解码 Linux 芯片
linux LCD 驱动框架分析
linux LCD 驱动框架分析
176 0
|
缓存 Linux 芯片
Linux驱动分析之Uart驱动
之前对Uart驱动的整体架构做了介绍,现在来分析具体的驱动程序。我们以NXP 的 IMX6来进行分析。