IMX6ULL的I2C驱动详细分析

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,同步至SelectDB 1个月
简介: 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;
}

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


相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
存储 Linux C语言
【IMX6ULL项目】IMX6ULL上Linux系统实现产测工具框架(一)
【IMX6ULL项目】IMX6ULL上Linux系统实现产测工具框架(一)
316 0
|
Linux 芯片 开发者
Linux 驱动开发基础知识——内核对设备树的处理与使用(十)
Linux 驱动开发基础知识——内核对设备树的处理与使用(十)
1404 0
Linux 驱动开发基础知识——内核对设备树的处理与使用(十)
|
Ubuntu Linux 开发工具
升级openssh前安装zlib报异常configure aborting
【8月更文挑战第22天】当升级OpenSSH前安装zlib遇到“configure aborting”异常,可尝试:1) 检查并确保所需依赖项(如gcc、make)已安装且版本兼容;2) 核实环境变量(如`PATH`, `LD_LIBRARY_PATH`)正确无误;3) 确认zlib版本与系统架构及OS版本匹配;4) 手动配置编译参数(如`--prefix`, `--with-pic`);5) 仔细审查configure脚本输出的错误信息;6) 在相关社区寻求帮助或查阅官方文档。遵循上述步骤有助于解决安装问题,顺利完成OpenSSH升级。
378 8
数据库系统工程师考点笔记
数据库系统工程师考点笔记
1293 0
|
Linux 网络安全 芯片
linux系统中详解u-boot之网络移植与调试
linux系统中详解u-boot之网络移植与调试
1812 0
|
芯片
IMX6ULL平台的I2C
IMX6ULL平台的I2C
303 0
IMX6ULL平台的I2C
|
Ubuntu 安全 网络协议
|
Linux 开发者 iOS开发
Python常用打包工具比较
以上是常用的四种打包工具比较。各自有着自己的优缺点,开发者可以根据自己的需求来选择合适的工具。如果你只需要在 Windows 平台上运行应用程序,可以选择 py2exe。如果你需要跨平台支持,并且希望打包过程简单,可以选择 Briefcase。如果你需要支持多个平台,并且打包过程比较复杂,可以选择 cx_Freeze。如果你需要支持多个平台,并且对第三方库的兼容性有较高的要求,可以选择 PyInstaller。
1074 4
|
存储 API C++
【 QString接口大全】 Qt QString类使用示例
【 QString接口大全】 Qt QString类使用示例
472 1
|
Linux 程序员
Linux驱动入门(6.2)按键驱动和LED驱动 --- 将逻辑电平与物理电平分离
Linux驱动入门(6.2)按键驱动和LED驱动 --- 将逻辑电平与物理电平分离
1046 0