IMX6ULL的I2C驱动详细分析

news2024/11/19 10:47:25

IMX6ULL的I2C驱动详细分析


文章目录

  • IMX6ULL的I2C驱动详细分析
  • i2c_imx_driver 的平台驱动注册
  • i2c_imx_probe注册函数
  • i2c_imx_algoI2C算法结构体
  • i2c_imx_start开始I2C
  • i2c_imx_stop停止I2C
  • i2c_imx_isr中断服务函数
  • i2c_imx_dma_writeDMA 进行写操作的 I2C 传输
  • 2c_imx_dma_read
  • i2c_imx_write
  • i2c_imx_read


在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/562876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动图怎么拆分成静图?简单快速分解gif的方法

怎么把gif动图变成静态图片&#xff1f;常见的gif动图画面生动丰富&#xff0c;是由一帧一帧静态的图片合成的&#xff0c;当我们想要把gif动图拆分成多张图片的时候要怎么操作呢&#xff1f;有没有什么简单方便的工具吗&#xff1f; 一、有没有简单方便的gif工具呢&#xff1…

Linux内存简介

Linux内存简介 概述 为何MemTotal小于RAM容量 [rootiZbp1dphe2bpv39op1g123Z ~]# dmesg | grep Memory [ 1.391064] Memory: 131604168K/134217136K available (14346K kernel code, 9546K rwdata, 9084K rodata, 2660K init, 7556K bss, 2612708K reserved, 0K cma-reserved) …

云原生改造- istio

istio 官网有bookinfo的案例&#xff0c; 但是这个案例过于繁琐&#xff0c;直接就可以运行&#xff0c;但是有些原理不是很清楚。 本教程是k8s改造成istio一个案例。spring-petclinic-msa是网上找到的一个k8s的一个java案例&#xff0c;通过spring-petclinic-msa改造成istio&a…

盘点团队在线帮助文档怎么做?

团队在线帮助文档是一个非常重要的工具&#xff0c;它可以帮助团队成员更好地协作和沟通&#xff0c;提高工作效率&#xff0c;并减少沟通成本。在本文中&#xff0c;我们将会盘点团队在线帮助文档的各个方面&#xff0c;以帮助您更好地了解如何创建一个高效的在线帮助文档。 …

6. python的for循环

文章目录 一、for循环1.1、for循环分析1.2、注意事项 二、遍历数值列表2.1、range()函数的使用2.2、 创建数值列表2.3、对数值列表进行简单统计 一、for循环 有时&#xff0c;我们需要对列表内的所有元素逐一进行相同的操作&#xff0c;为避免出现大量重复的代码&#xff0c;p…

js一行代码就能完成的事情,为什么要写两行

三元运算符 用三元运算符代替简单的if else if (age < 18) { me 小姐姐; } else { me 老阿姨; }改用三元运算符,一行就能搞定 me age < 18 ? 小姐姐 : 老阿姨;复杂的判断三元运算符就有点不简单易懂了 const you "董员外" const your "菜…

一周吃透Java面试八股文(2023最新整理

Java就业大环境仍然根基稳定&#xff0c;市场上有很多机会&#xff0c;技术好的人前景就好&#xff0c;就看你有多大本事了。小编得到了一份很不错的资源&#xff0c;建议大家可以认真地来看看以下的资料&#xff0c;来提升一下自己的核心竞争力&#xff0c;在面试中轻松应对面…

如何在WordPress页面上显示或隐藏小部件?

您想在 WordPress 网站的特定页面上显示或隐藏小部件吗&#xff1f; 默认情况下&#xff0c;当您将小部件添加到您的网站时&#xff0c;它们将显示在所有页面上。但是&#xff0c;有时您可能只想在某些页面上显示选定的小部件可以帮助您更好地自定义您的网站并提供个性化的用户…

如何在 Fedora 37 上安装 FileZilla?

FileZilla 是一款流行的开源 FTP&#xff08;文件传输协议&#xff09;客户端&#xff0c;它提供了一个直观的界面来管理和传输文件。本文将详细介绍如何在 Fedora 37 上安装 FileZilla。以下是安装过程的详细步骤&#xff1a; 步骤一&#xff1a;更新系统 在安装任何软件之前…

【大数据之Hive】一、Hive概念及框架原理

1 Hive概念 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能&#xff0c;主要完成海量数据的分析和计算。 优点&#xff1a;简化数据开发流程及提高了效率。 2 Hive本质 Hive是一个Hadoop客户端&#xff…

【golang】2、http client、爬图

文章目录 一、http client爬取并存储 jpg 用 golang 可以很方便的爬图&#xff08;http 下载图片&#xff0c;存储为 jpg 格式&#xff09;。 一、http client http client 有如下最佳实践&#xff1a; 尽量用 default http client&#xff1a;默认的 http client 设置了很多…

排序算法——堆排序

把上面的序列变成一个完全二叉树&#xff0c;要想实现大顶堆&#xff08;大顶堆&#xff1a;叶子节点不考虑&#xff0c;每个节点都要比他的两个孩子节点要大&#xff09;&#xff0c;就要进行如下操作&#xff0c;你会发现len/2就是最后一个非叶子节点 第一步是从下往上调 9和…

Apache Pulsar部署搭建

1.部署规划 部署 Pulsar 集群包括以下步骤(按顺序)&#xff1a; 1.部署一个 ZooKeeper 集群&#xff0c;初始化 Pulsar 集群元数据。2.部署一个 Bookeeper 集群。3.部署一个或多个 Pulsar brokers。4.部署 Pulsar manager&#xff08;可选&#xff09;。 2.节点规划 主机名…

【C++】函数重载 - 给代码增添多彩的魔法

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、函数重载概述 3、函数重载注意事项 4、总结 1、缘起 函数重载&#xff0c;是编程世界中的一抹迷人色彩&#xff0c…

IMX6ULL平台I2C数据结构分析

IMX6ULL平台I2C数据结构分析 文章目录 IMX6ULL平台I2C数据结构分析i2c_clienti2c_adapterimx_i2c_structimx_i2c_hwdataimx_i2c_dma 在 i.MX 平台的 I2C 驱动中&#xff0c;存在多个相关的结构体&#xff0c;它们之间的联系和在内核中的作用如下&#xff1a; struct i2c_client…

CentOS 7 上安装 Anaconda

一、在 CentOS 7 上安装 Anaconda 的步骤如下&#xff1a; 在官网下载 Anaconda 的最新版本&#xff0c;链接&#xff1a;https://www.anaconda.com/products/distribution 打开终端&#xff0c;进入下载目录&#xff0c;使用以下命令来安装 Anaconda&#xff1a;# 也可直接在…

【JavaScript】线程和进程,JavaScript线程,事件队列,事件循环 ,微任务、宏任务

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 进程和线程JavaScript线程事件队列、事件循环微任务、宏任务面试题1面试题2 进程和线程 进程&a…

有符号定点小数的31bit问题

背景 定点小数就是小数位恒定的小数&#xff0c;在信号处理等领域应用广泛&#xff0c;它的表示格式类似于S1.7&#xff08;有符号&#xff0c;整数部分1bit&#xff0c;小数部分7bit&#xff09;、U0.8&#xff08;无符号&#xff0c;没有整数部分&#xff0c;小数部分8bit&a…

基于java的班级综合测评管理系统的设计与实现

背景 本系统的主要目的在于加速信息化进程&#xff0c;充分利用计算机技术和现代通讯的手段面向学校的服务。建立信息交流平台&#xff0c;方便信息资源的共享&#xff0c;加强各个部门之间的交流。提高整体的办公效率&#xff0c;为管理员以及教师提供辅助的班级综合测评管理…

Java如何远程调试线上项目

远程调试java项目 [Remote Debug JVM] 一、前提二、配置IntelliJ IDEA2.1、打开 IntelliJ IDEA 并打开您的 Java 项目2.2、单击 "Run" 菜单&#xff0c;然后选择 "Edit Configurations..."2.3、在 "Run/Debug Configurations" 对话框中&#xff…