ov2640子设备核心操作详细分析

简介: ov2640子设备核心操作详细分析

ov2640_subdev_core_ops核心操作

// ov2640子设备核心操作
static struct v4l2_subdev_core_ops ov2640_subdev_core_ops = {
#ifdef CONFIG_VIDEO_ADV_DEBUG
    .g_register    = ov2640_g_register, // 获取寄存器值
    .s_register    = ov2640_s_register, // 设置寄存器值
#endif
    .s_power    = ov2640_s_power, // 设置ov2640的电源
};

获取寄存器值ov2640_g_register

函数名为ov2640_g_register,接受一个v4l2_subdev结构体指针和一个v4l2_dbg_register结构体指针reg作为参数。

函数的主要功能如下:

从v4l2_subdev结构体中获取i2c_client结构体指针,该结构体用于表示I2C设备。

设置要读取的寄存器的大小为1字节,并检查要读取的寄存器地址是否有效,如果无效则返回-EINVAL表示无效参数。

使用i2c_smbus_read_byte_data函数通过I2C总线读取指定寄存器的值。

如果读取成功,将读取的值赋给reg->val字段,并返回0表示成功。

如果读取失败,直接返回读取函数的返回值。

该函数的作用是通过使用I2C总线读取指定寄存器的值,并将读取的值存储在reg->val字段中,以实现获取寄存器值的功能。

// 获取指定寄存器的值
static int ov2640_g_register(struct v4l2_subdev *sd,
                 struct v4l2_dbg_register *reg)
{
    // 获取i2c_client结构体
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    // 定义变量
    int ret;
    reg->size = 1;
    if (reg->reg > 0xff)
        return -EINVAL;
    // 通过I2C总线读取指定寄存器的值
    ret = i2c_smbus_read_byte_data(client, reg->reg);
    if (ret < 0)
        return ret;
    reg->val = ret;
    return 0;
}

函数名为 i2c_smbus_read_byte_data,接受一个 i2c_client 结构体指针 client 和一个 u8 类型的命令 command 作为参数。

函数的主要功能如下:

定义一个联合体变量 data,用于存储读取的数据。

定义一个整型变量 status,用于存储函数执行的状态。

调用 i2c_smbus_xfer 函数,传入适配器、地址、标志位,以及读取操作的参数,包括命令 command 和数据类型为字节型。

执行读取操作,并将读取的数据存储在 data 中。

返回执行结果,如果 status 小于 0,返回 status,否则返回 data.byte。

该函数的作用是通过使用 I2C 总线进行读取操作,读取指定地址的寄存器的字节数据,并返回读取的结果。

s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
{
    union i2c_smbus_data data; // 定义一个联合体变量data
    int status; // 定义一个整型变量status
    status = i2c_smbus_xfer(client->adapter, client->addr, client->flags, // 调用i2c_smbus_xfer函数,传入适配器、地址、标志位
                I2C_SMBUS_READ, command, // 读取操作,命令为command
                I2C_SMBUS_BYTE_DATA, &data); // 读取一个字节的数据,存储在data中
    return (status < 0) ? status : data.byte; // 如果status小于0,返回status,否则返回data.byte
}

设置寄存器值ov2640_s_register

这段代码用于设置指定寄存器的值。

函数名为ov2640_s_register,接受一个v4l2_subdev结构体指针和一个v4l2_dbg_register结构体指针reg作为参数。

函数的主要功能如下:

从v4l2_subdev结构体中获取i2c_client结构体指针,该结构体用于表示I2C设备。

检查要设置的寄存器地址和值是否超出有效范围,如果超出范围,则返回-EINVAL表示无效参数。

使用i2c_smbus_write_byte_data函数通过I2C总线向指定寄存器写入指定的值。

返回i2c_smbus_write_byte_data函数的返回值。

该函数的作用是通过使用I2C总线向指定的寄存器写入指定的值,以实现设置寄存器的功能。

// 设置指定寄存器的值
static int ov2640_s_register(struct v4l2_subdev *sd,
                 const struct v4l2_dbg_register *reg)
{
    // 获取i2c_client结构体
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    if (reg->reg > 0xff ||
        reg->val > 0xff)
        return -EINVAL;
    // 通过I2C总线向指定寄存器写入指定值
    return i2c_smbus_write_byte_data(client, reg->reg, reg->val);
}

/driver/i2c/i2c-core.c

函数名为 i2c_smbus_write_byte_data,接受一个 i2c_client 结构体指针 client、一个 u8 类型的命令 command,以及一个 u8 类型的值 value 作为参数。

函数的主要功能如下:

定义一个联合体变量 data,用于存储要写入的数据。

将参数 value 的值存储在 data 中。

调用 i2c_smbus_xfer 函数,传入适配器、地址、标志位,以及写入操作的参数,包括命令 command 和数据类型为字节型,以及存储要写入数据的地址 &data。

执行写入操作,并将结果返回。

该函数的作用是通过使用 I2C 总线进行写入操作,将指定地址的寄存器写入一个字节的数据,返回写入操作的结果。

s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)

{

union i2c_smbus_data data; // 定义一个联合体变量data

data.byte = value; // 将value存储在data中

return i2c_smbus_xfer(client->adapter, client->addr, client->flags, // 调用i2c_smbus_xfer函数,传入适配器、地址、标志位

I2C_SMBUS_WRITE, command, // 写入操作,命令为command

I2C_SMBUS_BYTE_DATA, &data); // 写入一个字节的数据,存储在data中

}

i2c_smbus_xfer

Linux内核驱动程序:使用模拟I2C
Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c
Linux-5.4\drivers\i2c\busses\i2c-gpio.c
Linux内核真正的I2C控制器驱动程序
IMX6ULL: Linux-4.9.88\drivers\i2c\busses\i2c-imx.c

ov2640_s_register->i2c_smbus_write_byte_data->i2c_smbus_xfer

函数名为 i2c_smbus_xfer,接受一个 i2c_adapter 结构体指针 adapter、一个 u16 类型的地址 addr、一个 unsigned short 类型的标志位 flags、一个 char 类型的读写操作 read_write、一个 u8 类型的命令 command、一个 int 类型的协议 protocol,以及一个 union i2c_smbus_data 联合体指针 data 作为参数。

函数的主要功能如下:

执行跟踪点追踪,记录有关 SMBus 写入和读取操作的信息。

对标志位进行处理,只保留 I2C_M_TEN、I2C_CLIENT_PEC 和 I2C_CLIENT_SCCB 三个标志位。

如果适配器的算法中实现了 smbus_xfer 函数,则使用该函数进行 SMBus 传输。在仲裁丢失的情况下自动重试,直到达到最大重试次数或超时。

如果适配器的算法没有实现 smbus_xfer 函数或返回 -EOPNOTSUPP,则回退到使用 i2c_smbus_xfer_emulated 函数进行传输。

执行跟踪点追踪,记录有关 SMBus 回复和结果的信息。

返回传输的结果。

该函数的作用是通过使用 I2C 总线进行 SMBus 传输。它根据适配器的支持情况选择合适的传输方法,并处理重试和回退逻辑,最终返回传输的结果。

s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
           char read_write, u8 command, int protocol,
           union i2c_smbus_data *data)
{
    unsigned long orig_jiffies;
    int try;
    s32 res;
    /* 如果启用,则以下两个跟踪点取决于读写和协议。*/
    trace_smbus_write(adapter, addr, flags, read_write,
              command, protocol, data);
    trace_smbus_read(adapter, addr, flags, read_write,
             command, protocol);
    /* 仅保留I2C_M_TEN、I2C_CLIENT_PEC和I2C_CLIENT_SCCB标志 */
    flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB;
    if (adapter->algo->smbus_xfer) {
        i2c_lock_adapter(adapter);
        /* 在仲裁丢失的情况下自动重试 */
        orig_jiffies = jiffies;
        for (res = 0, try = 0; try <= adapter->retries; try++) {
            res = adapter->algo->smbus_xfer(adapter, addr, flags,
                            read_write, command,
                            protocol, data);
            if (res != -EAGAIN)
                break;
            if (time_after(jiffies,
                       orig_jiffies + adapter->timeout))
                break;
        }
        i2c_unlock_adapter(adapter);
        if (res != -EOPNOTSUPP || !adapter->algo->master_xfer)
            goto trace;
        /*
         * 如果适配器没有实现SMBus操作的本地支持,则回退到i2c_smbus_xfer_emulated。
         */
    }
    res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
                      command, protocol, data);
trace:
    /* 如果启用,则回复跟踪点取决于读写。*/
    trace_smbus_reply(adapter, addr, flags, read_write,
              command, protocol, data);
    trace_smbus_result(adapter, addr, flags, read_write,
               command, protocol, res);
    return res;
}

i2c_imx_xfer

smbus_xfer进行 SMBus 传输

/driver/i2c/busses/i2c-imx.c

这个函数是用于在i.MX平台的I2C适配器上执行数据传输操作。它是i.MX系列芯片的I2C驱动程序中的一部分。

该函数的作用如下:

开始I2C传输:调用i2c_imx_start函数启动I2C传输。它会发送起始条件(START)信号。

读写数据:对于每个消息(msgs数组中的每个元素),根据消息的属性进行读写操作。如果消息的flags标志指示要读取数据,则调用i2c_imx_read函数进行读取;否则,根据DMA的可用性调用适当的写入函数进行写入操作,可以选择使用DMA写入或普通写入。

停止I2C传输:调用i2c_imx_stop函数停止I2C传输。它会发送停止条件(STOP)信号。

返回结果:根据传输的成功与否,返回相应的结果。如果传输失败,则返回负数表示错误;否则,返回传输的消息数量。

该函数在I2C传输过程中还输出一些调试信息,例如控制寄存器(I2CR)和状态寄存器(I2SR)的值,以及传输消息的序号和结果状态。

总体而言,这个函数负责管理i.MX平台上的I2C传输过程,处理起始条件、读写数据以及停止条件,以实现与I2C设备的通信。

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_smbus_xfer_emulated

ov2640_s_register->i2c_smbus_write_byte_data->i2c_smbus_xfer->i2c_smbus_xfer_emulated

函数名为 i2c_smbus_xfer,接受一个 i2c_adapter 结构体指针 adapter、一个 u16 类型的地址 addr、一个 unsigned short 类型的标志位 flags、一个 char 类型的读写操作 read_write、一个 u8 类型的命令 command、一个 int 类型的协议 protocol,以及一个 union i2c_smbus_data 联合体指针 data 作为参数。

函数的主要功能如下:

执行跟踪点追踪,记录有关 SMBus 写入和读取操作的信息。

对标志位进行处理,只保留 I2C_M_TEN、I2C_CLIENT_PEC 和 I2C_CLIENT_SCCB 三个标志位。

如果适配器的算法中实现了 smbus_xfer 函数,则使用该函数进行 SMBus 传输。在仲裁丢失的情况下自动重试,直到达到最大重试次数或超时。

如果适配器的算法没有实现 smbus_xfer 函数或返回 -EOPNOTSUPP,则回退到使用 i2c_smbus_xfer_emulated 函数进行传输。

执行跟踪点追踪,记录有关 SMBus 回复和结果的信息。

返回传输的结果。

该函数的作用是通过使用 I2C 总线进行 SMBus 传输。它根据适配器的支持情况选择合适的传输方法,并处理重试和回退逻辑,最终返回传输的结果。

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
                   unsigned short flags,
                   char read_write, u8 command, int size,
                   union i2c_smbus_data *data)
{
    /* 需要生成一系列的消息。在写入的情况下,我们只需要使用一个消息;在读取时,我们需要两个消息。我们使用合理的默认值初始化大多数内容,以使下面的代码更简单。 */
    unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; // 第一个消息的缓冲区
    unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; // 第二个消息的缓冲区
    int num = read_write == I2C_SMBUS_READ ? 2 : 1; // 消息数量
    int i;
    u8 partial_pec = 0; // 部分PEC
    int status;
    struct i2c_msg msg[2] = {
        {
            .addr = addr, // 地址
            .flags = flags, // 标志
            .len = 1, // 长度
            .buf = msgbuf0, // 缓冲区
        }, {
            .addr = addr, // 地址
            .flags = flags | I2C_M_RD, // 标志
            .len = 0, // 长度
            .buf = msgbuf1, // 缓冲区
        },
    };
    msgbuf0[0] = command; // 命令
    switch (size) { // 根据size的值进行判断
    case I2C_SMBUS_QUICK: // 快速模式
        msg[0].len = 0; // 长度为0
        /* 特殊情况:读写字段用作数据 */
        msg[0].flags = flags | (read_write == I2C_SMBUS_READ ?
                    I2C_M_RD : 0); // 标志
        num = 1; // 消息数量为1
        break;
    case I2C_SMBUS_BYTE: // 字节模式
        if (read_write == I2C_SMBUS_READ) {
            /* 特殊情况:只有读! */
            msg[0].flags = I2C_M_RD | flags; // 标志
            num = 1; // 消息数量为1
        }
        break;
    case I2C_SMBUS_BYTE_DATA: // 字节数据模式
        if (read_write == I2C_SMBUS_READ)
            msg[1].len = 1; // 长度为1
        else {
            msg[0].len = 2; // 长度为2
            msgbuf0[1] = data->byte; // 数据
        }
        break;
    case I2C_SMBUS_WORD_DATA: // 字数据模式
        if (read_write == I2C_SMBUS_READ)
            msg[1].len = 2; // 长度为2
        else {
            msg[0].len = 3; // 长度为3
            msgbuf0[1] = data->word & 0xff; // 数据低位
            msgbuf0[2] = data->word >> 8; // 数据高位
        }
        break;
    case I2C_SMBUS_PROC_CALL: // 处理调用
        num = 2; /* 特殊情况 */
        read_write = I2C_SMBUS_READ; // 读操作
        msg[0].len = 3; // 长度为3
        msg[1].len = 2; // 长度为2
        msgbuf0[1] = data->word & 0xff; // 数据低位
        msgbuf0[2] = data->word >> 8; // 数据高位
        break;
    case I2C_SMBUS_BLOCK_DATA: // 块数据
        if (read_write == I2C_SMBUS_READ) {
            msg[1].flags |= I2C_M_RECV_LEN; // 接收长度
            msg[1].len = 1; /* 块长度将由底层总线驱动程序添加 */
        } else {
            msg[0].len = data->block[0] + 2; // 长度为块长度+2
            if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) { // 如果长度大于最大块长度+2
                dev_err(&adapter->dev,
                    "Invalid block write size %d\n", // 输出错误信息
                    data->block[0]);
                return -EINVAL; // 返回错误
            }
            for (i = 1; i < msg[0].len; i++) // 遍历块
                msgbuf0[i] = data->block[i-1]; // 将块中的数据存入缓冲区
        }
        break;
    case I2C_SMBUS_BLOCK_PROC_CALL: // 块处理调用
        num = 2; /* 另一种特殊情况 */
        read_write = I2C_SMBUS_READ; // 读操作
        if (data->block[0] > I2C_SMBUS_BLOCK_MAX) { // 如果块长度大于最大块长度
            dev_err(&adapter->dev,
                "Invalid block write size %d\n", // 输出错误信息
                data->block[0]);
            return -EINVAL; // 返回错误
        }
        msg[0].len = data->block[0] + 2; // 长度为块长度+2
        for (i = 1; i < msg[0].len; i++) // 遍历块
            msgbuf0[i] = data->block[i-1]; // 将块中的数据存入缓冲区
        msg[1].flags |= I2C_M_RECV_LEN; // 接收长度
        msg[1].len = 1; /* 块长度将由底层总线驱动程序添加 */
        break;
    case I2C_SMBUS_I2C_BLOCK_DATA: // I2C块数据
        if (read_write == I2C_SMBUS_READ) {
            msg[1].len = data->block[0]; // 长度为块长度
        } else {
            msg[0].len = data->block[0] + 1; // 长度为块长度+1
            if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) { // 如果长度大于最大块长度+1
                dev_err(&adapter->dev,
                    "Invalid block write size %d\n", // 输出错误信息
                    data->block[0]);
                return -EINVAL; // 返回错误
            }
            for (i = 1; i <= data->block[0]; i++) // 遍历块
                msgbuf0[i] = data->block[i]; // 将块中的数据存入缓冲区
        }
        break;
    default:
        dev_err(&adapter->dev, "Unsupported transaction %d\n", size); // 输出错误信息
        return -EOPNOTSUPP; // 返回错误
    }
    // 判断是否需要计算PEC
    i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
                      && size != I2C_SMBUS_I2C_BLOCK_DATA);
    if (i) {
        /* 如果第一个消息是写,则计算PEC */
        if (!(msg[0].flags & I2C_M_RD)) {
            if (num == 1) /* 只有写操作 */
                i2c_smbus_add_pec(&msg[0]);
            else /* 先写后读 */
                partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
        }
        /* 如果最后一个消息是读,则请求PEC */
        if (msg[num-1].flags & I2C_M_RD)
            msg[num-1].len++;
    }
    status = i2c_transfer(adapter, msg, num); // 发送消息
    if (status < 0)
        return status; // 发送失败,返回错误
    /* 如果最后一个消息是读,则检查PEC */
    if (i && (msg[num-1].flags & I2C_M_RD)) {
        status = i2c_smbus_check_pec(partial_pec, &msg[num-1]);
        if (status < 0)
            return status;
    }
    // 根据读写类型进行操作
    if (read_write == I2C_SMBUS_READ)
        switch (size) {
        case I2C_SMBUS_BYTE:
            data->byte = msgbuf0[0]; // 将缓冲区中的数据存入data中
            break;
        case I2C_SMBUS_BYTE_DATA:
            data->byte = msgbuf1[0]; // 将缓冲区中的数据存入data中
            break;
        case I2C_SMBUS_WORD_DATA:
        case I2C_SMBUS_PROC_CALL:
            data->word = msgbuf1[0] | (msgbuf1[1] << 8); // 将缓冲区中的数据存入data中
            break;
        case I2C_SMBUS_I2C_BLOCK_DATA:
            for (i = 0; i < data->block[0]; i++)
                data->block[i+1] = msgbuf1[i]; // 将缓冲区中的数据存入data中
            break;
        case I2C_SMBUS_BLOCK_DATA:
        case I2C_SMBUS_BLOCK_PROC_CALL:
            for (i = 0; i < msgbuf1[0] + 1; i++)
                data->block[i] = msgbuf1[i]; // 将缓冲区中的数据存入data中
            break;
        }
    return 0;
}

i2c_transfer

ov2640_s_register->i2c_smbus_write_byte_data->i2c_smbus_xfer->i2c_smbus_xfer_emulated->i2c_transfer

这段代码实现了在I2C总线上进行传输的函数i2c_transfer。它是一个通用的I2C传输函数,用于向I2C设备发送一系列消息,并返回传输的结果。

函数首先检查适配器是否支持master_xfer函数,该函数用于执行实际的传输操作。如果适配器支持该函数,将进行以下步骤:

如果当前在原子上下文中或者中断被禁止,函数尝试获取适配器的锁定。如果无法获取锁定,表示有其他I2C活动正在进行中,函数返回-EAGAIN表示稍后重试。

如果当前不在原子上下文中且中断未被禁止,函数获取适配器的锁定。

调用__i2c_transfer函数执行实际的传输操作,传递适配器、消息数组和消息数量作为参数。

释放适配器的锁定。

返回传输的结果。

如果适配器不支持master_xfer函数,则函数打印一条调试信息并返回-EOPNOTSUPP表示不支持I2C级别的传输操作。

需要注意的是,这段代码中存在一些注释提到了错误报告模型的弱点,包括在从设备接收字节后出现错误时无法报告接收到的字节数量,以及在向从设备传输字节后收到NAK时无法报告已传输的字节数量。这些弱点需要在实际使用中进行进一步的考虑和处理。

总体而言,这段代码提供了一个通用的I2C传输函数,可以在适配器支持master_xfer函数的情况下进行传输操作,并返回传输的结果。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    int ret;
    /* REVISIT the fault reporting model here is weak:
     * 
     *  - 当我们从从设备接收N个字节后出现错误时,没有办法报告“N”。
     * 
     *  - 当我们向从设备传输N个字节后收到NAK时,没有办法报告“N”...或者让主设备继续执行此组合消息的其余部分,如果这是适当的响应。
     * 
     *  - 例如,“num”为2,我们成功完成第一个消息,但在第二个消息的部分中出现错误,不清楚是否应将其报告为一个(丢弃第二个消息的状态)或errno(丢弃第一个消息的状态)。
     */
    if (adap->algo->master_xfer) {
#ifdef DEBUG
        for (ret = 0; ret < num; ret++) {
            dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
                "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
                ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
        }
#endif
        if (in_atomic() || irqs_disabled()) {
            ret = i2c_trylock_adapter(adap);
            if (!ret)
                /* I2C活动正在进行中。 */
                return -EAGAIN;
        } else {
            i2c_lock_adapter(adap);
        }
        ret = __i2c_transfer(adap, msgs, num);
        i2c_unlock_adapter(adap);
        return ret;
    } else {
        dev_dbg(&adap->dev, "I2C level transfers not supported\n");
        return -EOPNOTSUPP;
    }
}

__i2c_transfer

ov2640_s_register->i2c_smbus_write_byte_data->i2c_smbus_xfer->i2c_smbus_xfer_emulated->i2c_transfer->__i2c_transfer

这个函数用于在给定的I2C适配器上执行一系列I2C消息的传输操作。

函数的主要步骤如下:

检查适配器的特殊特性:如果适配器具有特殊的quirks(特性),则调用i2c_check_for_quirks函数检查传入的消息是否需要特殊处理。如果需要特殊处理,返回错误码-EOPNOTSUPP表示不支持。

启用跟踪:根据跟踪选项的状态,决定是否启用消息跟踪。如果启用了消息跟踪,使用trace_i2c_read和trace_i2c_write函数跟踪每个读取或写入消息的详细信息。

重试机制:在给定的重试次数范围内,循环执行适配器的master_xfer函数来执行I2C传输。如果返回值不是-EAGAIN(表示仲裁丢失),或者超过了指定的超时时间,跳出循环。

结果跟踪:根据跟踪选项的状态,决定是否启用结果跟踪。如果启用了结果跟踪,使用trace_i2c_reply和trace_i2c_result函数跟踪每个读取消息的响应以及整体传输结果。

返回结果:返回传输操作的结果。

总体而言,该函数负责管理I2C传输过程中的重试机制和结果跟踪,并调用适配器的master_xfer函数执行实际的传输操作。它还提供了消息跟踪的选项,以便在需要时记录传输的详细信息。

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    unsigned long orig_jiffies;
    int ret, try;
    if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
        return -EOPNOTSUPP;
    /* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets
     * enabled.  This is an efficient way of keeping the for-loop from
     * being executed when not needed.
     */
    if (static_key_false(&i2c_trace_msg)) {
        int i;
        for (i = 0; i < num; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_read(adap, &msgs[i], i);
            else
                trace_i2c_write(adap, &msgs[i], i);
    }
    /* Retry automatically on arbitration loss */
    orig_jiffies = jiffies;
    for (ret = 0, try = 0; try <= adap->retries; try++) {
        ret = adap->algo->master_xfer(adap, msgs, num);
        if (ret != -EAGAIN)
            break;
        if (time_after(jiffies, orig_jiffies + adap->timeout))
            break;
    }
    if (static_key_false(&i2c_trace_msg)) {
        int i;
        for (i = 0; i < ret; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_reply(adap, &msgs[i], i);
        trace_i2c_result(adap, i, ret);
    }
    return ret;
}

设置ov2640的电源ov2640_s_power

这段代码是用于设置ov2640相机设备的电源状态的函数。

函数名为ov2640_s_power,接受一个v4l2_subdev结构体指针和一个整数on作为参数。

函数的主要功能如下:

从v4l2_subdev结构体中获取i2c_client结构体指针,该结构体用于表示I2C设备。

使用i2c_client结构体指针获取soc_camera_subdev_desc结构体指针,该结构体用于描述相机子设备。

从i2c_client结构体指针中获取ov2640_priv结构体指针,该结构体是ov2640相机设备的私有数据。

调用soc_camera_set_power函数,将dev(设备)、ssdd(相机子设备描述)、priv->clk(时钟)和on(电源状态)作为参数,以设置ov2640相机设备的电源状态。

返回soc_camera_set_power函数的返回值。

该函数的作用是通过调用soc_camera_set_power函数来设置ov2640相机设备的电源状态。

// 设置ov2640的电源状态
static int ov2640_s_power(struct v4l2_subdev *sd, int on)
{
    // 获取i2c_client结构体
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    // 获取soc_camera_subdev_desc结构体
    struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
    // 获取ov2640_priv结构体
    struct ov2640_priv *priv = to_ov2640(client);
    // 设置ov2640的电源状态
    return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
}
/include/media/soc_camera.h
// 设置摄像头电源状态
static inline int soc_camera_set_power(struct device *dev,
        struct soc_camera_subdev_desc *ssdd, struct v4l2_clk *clk, bool on)
{
    return on ? soc_camera_power_on(dev, ssdd, clk) // 打开电源
          : soc_camera_power_off(dev, ssdd, clk); // 关闭电源
}

/driver/media/platform/soc_camera.c

这两个函数用于相机电源的开启和关闭操作。下面是对这两个函数的概括总结:

soc_camera_power_on函数用于打开相机的电源。

如果提供了时钟,并且未平衡电源或时钟状态未设置,则使能时钟。

使能寄存器。

如果存在电源回调函数,则调用电源回调函数使能相机电源。

如果上述操作都成功,则返回0。

如果有任何步骤失败,则执行相应的错误处理操作,包括禁用寄存器和时钟,并返回错误码。

soc_camera_power_off函数用于关闭相机的电源。

如果存在电源回调函数,则调用电源回调函数关闭相机电源。

禁用寄存器。

如果提供了时钟,并且未平衡电源或时钟状态已设置,则禁用时钟。

返回执行过程中的任何错误码。

这两个函数在相机驱动中用于管理相机设备的电源控制,包括使能和禁用电源和相关的时钟和寄存器。

int soc_camera_power_on(struct device *dev, struct soc_camera_subdev_desc *ssdd,
            struct v4l2_clk *clk)
{
    int ret;
    bool clock_toggle;
    if (clk && (!ssdd->unbalanced_power ||
            !test_and_set_bit(0, &ssdd->clock_state))) { // 如果有时钟并且未平衡电源或时钟状态未设置
        ret = v4l2_clk_enable(clk); // 使能时钟
        if (ret < 0) {
            dev_err(dev, "Cannot enable clock: %d\n", ret); // 不能使能时钟
            return ret;
        }
        clock_toggle = true; // 时钟切换为真
    } else {
        clock_toggle = false; // 时钟切换为假
    }
    ret = regulator_bulk_enable(ssdd->sd_pdata.num_regulators,
                    ssdd->sd_pdata.regulators); // 使能寄存器
    if (ret < 0) {
        dev_err(dev, "Cannot enable regulators\n"); // 不能使能寄存器
        goto eregenable;
    }
    if (ssdd->power) { // 如果有电源
        ret = ssdd->power(dev, 1); // 使能电源
        if (ret < 0) {
            dev_err(dev,
                "Platform failed to power-on the camera.\n"); // 不能使能相机电源
            goto epwron;
        }
    }
    return 0;
epwron:
    regulator_bulk_disable(ssdd->sd_pdata.num_regulators,
                   ssdd->sd_pdata.regulators); // 禁用寄存器
eregenable:
    if (clock_toggle)
        v4l2_clk_disable(clk); // 禁用时钟
    return ret;
}
EXPORT_SYMBOL(soc_camera_power_on);
/**
 * soc_camera_power_off() - 关闭相机电源
 * @dev: 设备
 * @ssdd: 相机子设备描述
 * @clk: 时钟
 * @return: 返回值
 */
int soc_camera_power_off(struct device *dev, struct soc_camera_subdev_desc *ssdd,
             struct v4l2_clk *clk)
{
    int ret = 0;
    int err;
    if (ssdd->power) { // 如果有电源
        err = ssdd->power(dev, 0); // 关闭电源
        if (err < 0) {
            dev_err(dev,
                "Platform failed to power-off the camera.\n"); // 不能关闭相机电源
            ret = err;
        }
    }
    err = regulator_bulk_disable(ssdd->sd_pdata.num_regulators,
                     ssdd->sd_pdata.regulators); // 禁用寄存器
    if (err < 0) {
        dev_err(dev, "Cannot disable regulators\n"); // 不能禁用寄存器
        ret = ret ? : err;
    }
    if (clk && (!ssdd->unbalanced_power || test_and_clear_bit(0, &ssdd->clock_state)))
        v4l2_clk_disable(clk); // 禁用时钟
    return ret;
}

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


目录
相关文章
|
4月前
|
传感器 Linux API
如何实现 MCU软件中多个模块初始化函数的优雅调用
如何实现 MCU软件中多个模块初始化函数的优雅调用
|
6月前
|
异构计算
【FPGA】基本实验步骤演示 | Verilog编码 | 运行合成 | 设备/引脚分配 | 综合/实施 | 设备配置 | 以最简单的逻辑非为例
【FPGA】基本实验步骤演示 | Verilog编码 | 运行合成 | 设备/引脚分配 | 综合/实施 | 设备配置 | 以最简单的逻辑非为例
50 0
|
7月前
|
前端开发 Python
【前端验证】通用型顺序比对的uvm scoreboard组件编写
【前端验证】通用型顺序比对的uvm scoreboard组件编写
|
10月前
|
存储 传感器 索引
ov2640子设备视频操作详细分析
ov2640子设备视频操作详细分析
229 0
ov2640子设备视频操作详细分析
|
5月前
|
Windows
5.1 Windows驱动开发:判断驱动加载状态
在驱动开发中我们有时需要得到驱动自身是否被加载成功的状态,这个功能看似没啥用实际上在某些特殊场景中还是需要的,如下代码实现了判断当前驱动是否加载成功,如果加载成功, 则输出该驱动的详细路径信息。该功能实现的核心函数是`NtQuerySystemInformation`这是一个微软未公开的函数,也没有文档化,不过我们仍然可以通过动态指针的方式调用到它,该函数可以查询到很多系统信息状态,首先需要定义一个指针。
40 0
5.1 Windows驱动开发:判断驱动加载状态
|
8月前
GB28181控制、传输流程和协议接口之注册|注销和技术实现
注册和注销基本要求 SIP客户端、网关、SIP设备、联网系统等 SIP代理(SIP UA)使用IETFRFC3261中定义的方法 15 GB/T28181—2016Register进行注册和注销。
|
Linux
Linux驱动开发 数据的传输和辅助信息的作用
Linux驱动开发 数据的传输和辅助信息的作用
129 0
|
监控 安全 网络架构
用于同步光网络 (SONET) 和同步数字体系 (SDH) 控制的通用多协议标签交换 (GMPLS) 扩展
本文档提供了特定于同步光网络 (SONET)/同步数字体系结构 (SDH) 的详细信息。根据 [RFC3471],SONET/SDH 特定参数在信令协议中携带在流量参数特定对象中。
353 0
用于同步光网络 (SONET) 和同步数字体系 (SDH) 控制的通用多协议标签交换 (GMPLS) 扩展
|
Android开发
RK3399平台开发系列讲解(系统修改记录篇)1.13、安卓系统hal层修改最大支持摄像头数量
RK3399平台开发系列讲解(系统修改记录篇)1.13、安卓系统hal层修改最大支持摄像头数量
132 0
|
传感器 物联网 开发者
Zigbee自定义任务初始化|学习笔记
快速学习Zigbee自定义任务初始化
100 0
Zigbee自定义任务初始化|学习笔记