diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index a3b163443628..a111d0e2cc52 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -50,6 +50,14 @@ config VIDEO_BUFFER_SMH_ATTRIBUTE 1: SMH_REG_ATTR_NON_CACHEABLE 2: SMH_REG_ATTR_EXTERNAL +config VIDEO_I2C_RETRY_NUM + int "Number of retries after a failed I2C communication" + default 0 + help + If set to 0, only a single write attempt will be done with no retry. + The default is to not retry. Board configuration files or user project can then + use the number of retries that matches their situation. + source "drivers/video/Kconfig.esp32_dvp" source "drivers/video/Kconfig.mcux_csi" diff --git a/drivers/video/video_common.c b/drivers/video/video_common.c index 7ae75932ccdf..9c92ed09b028 100644 --- a/drivers/video/video_common.c +++ b/drivers/video/video_common.c @@ -1,14 +1,23 @@ /* * Copyright (c) 2019, Linaro Limited - * Copyright (c) 2024, tinyVision.ai Inc. + * Copyright (c) 2024-2025, tinyVision.ai Inc. * * SPDX-License-Identifier: Apache-2.0 */ #include -#include +#include +#include #include +#include +#include +#include +#include + +#include "video_common.h" + +LOG_MODULE_REGISTER(video_common, CONFIG_VIDEO_LOG_LEVEL); #if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP) #include @@ -166,3 +175,193 @@ void video_closest_frmival(const struct device *dev, struct video_frmival_enum * } } } + +static int video_read_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size_w, + uint8_t *buf_r, size_t size_r) +{ + int ret; + + for (int i = 0;; i++) { + ret = i2c_write_read_dt(i2c, buf_w, size_w, buf_r, size_r); + if (ret == 0) { + break; + } + if (i == CONFIG_VIDEO_I2C_RETRY_NUM) { + LOG_HEXDUMP_ERR(buf_w, size_w, "failed to write-read to I2C register"); + return ret; + } + + k_sleep(K_MSEC(1)); + } + + return 0; +} + +int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_data) +{ + size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr); + size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr); + bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr); + uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr); + uint8_t buf_w[sizeof(uint16_t)] = {0}; + uint8_t *data_ptr; + int ret; + + __ASSERT(addr_size > 0, "The address must have a address size flag"); + __ASSERT(data_size > 0, "The address must have a data size flag"); + + *reg_data = 0; + + if (big_endian) { + /* Casting between data sizes in big-endian requires re-aligning */ + data_ptr = (uint8_t *)reg_data + sizeof(*reg_data) - data_size; + } else { + /* Casting between data sizes in little-endian is a no-op */ + data_ptr = (uint8_t *)reg_data; + } + + for (int i = 0; i < data_size; i++) { + if (addr_size == 1) { + buf_w[0] = addr + i; + } else { + sys_put_be16(addr + i, &buf_w[0]); + } + + ret = video_read_reg_retry(i2c, buf_w, addr_size, &data_ptr[i], 1); + if (ret < 0) { + LOG_ERR("Failed to read from register 0x%x", addr + i); + return ret; + } + + LOG_HEXDUMP_DBG(buf_w, addr_size, "Data written to the I2C device..."); + LOG_HEXDUMP_DBG(&data_ptr[i], 1, "... data read back from the I2C device"); + } + + *reg_data = big_endian ? sys_be32_to_cpu(*reg_data) : sys_le32_to_cpu(*reg_data); + + return 0; +} + +static int video_write_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size) +{ + int ret; + + for (int i = 0;; i++) { + ret = i2c_write_dt(i2c, buf_w, size); + if (ret == 0) { + break; + } + if (i == CONFIG_VIDEO_I2C_RETRY_NUM) { + LOG_HEXDUMP_ERR(buf_w, size, "failed to write to I2C register"); + return ret; + } + + k_sleep(K_MSEC(1)); + } + + return 0; +} + +int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_data) +{ + size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr); + size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr); + bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr); + uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr); + uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; + uint8_t *data_ptr; + int ret; + + __ASSERT(addr_size > 0, "The address must have a address size flag"); + __ASSERT(data_size > 0, "The address must have a data size flag"); + + if (big_endian) { + /* Casting between data sizes in big-endian requires re-aligning */ + reg_data = sys_cpu_to_be32(reg_data); + data_ptr = (uint8_t *)®_data + sizeof(reg_data) - data_size; + } else { + /* Casting between data sizes in little-endian is a no-op */ + reg_data = sys_cpu_to_le32(reg_data); + data_ptr = (uint8_t *)®_data; + } + + for (int i = 0; i < data_size; i++) { + /* The address is always big-endian as per CCI standard */ + if (addr_size == 1) { + buf_w[0] = addr + i; + } else { + sys_put_be16(addr + i, &buf_w[0]); + } + + buf_w[addr_size] = data_ptr[i]; + + LOG_HEXDUMP_DBG(buf_w, addr_size + 1, "Data written to the I2C device"); + + ret = video_write_reg_retry(i2c, buf_w, addr_size + 1); + if (ret < 0) { + LOG_ERR("Failed to write to register 0x%x", addr + i); + return ret; + } + } + + return 0; +} + +int video_modify_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask, + uint32_t field_value) +{ + uint32_t reg; + int ret; + + ret = video_read_cci_reg(i2c, reg_addr, ®); + if (ret < 0) { + return ret; + } + + return video_write_cci_reg(i2c, reg_addr, (reg & ~field_mask) | field_value); +} + +int video_write_cci_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs, + size_t num_regs) +{ + int ret; + + for (int i = 0; i < num_regs; i++) { + ret = video_write_cci_reg(i2c, regs[i].addr, regs[i].data); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +int video_write_cci_multiregs8(const struct i2c_dt_spec *i2c, const struct video_reg8 *regs, + size_t num_regs) +{ + int ret; + + for (int i = 0; i < num_regs; i++) { + ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR8_DATA8, regs[i].data); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +int video_write_cci_multiregs16(const struct i2c_dt_spec *i2c, const struct video_reg16 *regs, + size_t num_regs) +{ + int ret; + + for (int i = 0; i < num_regs; i++) { + ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR16_DATA8, regs[i].data); + if (ret < 0) { + return ret; + } + } + + return 0; +} diff --git a/drivers/video/video_common.h b/drivers/video/video_common.h new file mode 100644 index 000000000000..5a691321aa54 --- /dev/null +++ b/drivers/video/video_common.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_VIDEO_COMMON_H_ +#define ZEPHYR_DRIVERS_VIDEO_COMMON_H_ + +#include + +#include +#include +#include +#include +#include + +/** + * @brief Register for building tables supporting 8/16 bit address and 8/16/24/32 bit value sizes. + * + * A flag in the address indicates the size of the register address, and the size and endianness of + * the value. + * + * If willing to save space on large register tables, a more compact version @ref video_reg8 and + * @ref video_reg16 can be used, however only supporting 8-bit values . + * + * The data field is in CPU-native endianness, and the library functions will perform the + * endianness swap as needed while transmitting data. + */ +struct video_reg { + /** Address of the register, and other flags if used with the @ref video_cci API. */ + uint32_t addr; + /** Value to write to this address */ + uint32_t data; +}; + +/** Register for building tables supporting 8-bit addresses and 8-bit value sizes */ +struct video_reg8 { + /** Address of the register */ + uint8_t addr; + /** Value to write to this address */ + uint8_t data; +}; + +/** Register for building tables supporting 16-bit addresses and 8-bit value sizes */ +struct video_reg16 { + /** Address of the register */ + uint16_t addr; + /** Value to write to this address */ + uint8_t data; +}; + +/** + * @defgroup video_cci Video Camera Control Interface (CCI) handling + * + * The Camera Control Interface (CCI) is an I2C communication scheme part of MIPI-CSI. + * It defines how register addresses and register values are packed into I2C messages. + * + * After the I2C device address, I2C messages payload contain: + * + * 1. The 8-bit or 16-bit of the address in big-endian, written to the device. + * 2. The 8-bit of the register data either read or written. + * + * To write to registers larger than 8-bit, multiple read/writes messages are issued. + * Endianness and segmentation of larger registers are defined on a per-sensor basis. + * + * @{ + */ + +/** @cond INTERNAL_HIDDEN */ +#define VIDEO_REG_ENDIANNESS_MASK (uint32_t)(GENMASK(24, 24)) +#define VIDEO_REG_ADDR_SIZE_MASK (uint32_t)(GENMASK(23, 20)) +#define VIDEO_REG_DATA_SIZE_MASK (uint32_t)(GENMASK(19, 16)) +#define VIDEO_REG_ADDR_MASK (uint32_t)(GENMASK(15, 0)) + +#define VIDEO_REG(addr_size, data_size, endianness) \ + (FIELD_PREP(VIDEO_REG_ADDR_SIZE_MASK, (addr_size)) | \ + FIELD_PREP(VIDEO_REG_DATA_SIZE_MASK, (data_size)) | \ + FIELD_PREP(VIDEO_REG_ENDIANNESS_MASK, (endianness))) +/** @endcond */ + +/** + * @defgroup video_cci_reg_flags Flags describing a Video CCI register + * + * For a given drivers, register sizes are not expected to change, so macros can be used when + * defining registers: + * + * @code{.c} + * #define SENSORNAME_REG8(addr) ((uint32_t)(addr) | VIDEO_REG_ADDR16_DATA8) + * #define SENSORNAME_REG16(addr) ((uint32_t)(addr) | VIDEO_REG_ADDR16_DATA16_LE) + * #define SENSORNAME_REG24(addr) ((uint32_t)(addr) | VIDEO_REG_ADDR16_DATA24_LE) + * @endcode + * + * Then the macros can be used directly in register definitions: + * + * @code{.c} + * #define SENSORNAME_REG_EXPOSURE_LEVEL SENSORNAME_REG16(0x3060) + * #define SENSORNAME_REG_AN_GAIN_LEVEL SENSORNAME_REG8(0x3062) + * ... + * video_write_cci_reg(&cfg->i2c, SENSORNAME_REG_EXPOSURE_LEVEL, 3000); + * video_write_cci_reg(&cfg->i2c, SENSORNAME_REG_AN_GAIN_LEVEL, 220); + * @endcode + * + * Or used directly inline: + * @code{.c} + * video_write_cci_reg(&cfg->i2c, SENSORNAME_REG16(0x3060), 3000); + * video_write_cci_reg(&cfg->i2c, SENSORNAME_REG8(0x3062), 220); + * @endcode + * + * As well as in register tables: + * + * @code{.c} + * struct video_reg init_regs[] = { + * {SENSORNAME_REG_EXPOSURE_LEVEL, 2000}, + * {SENSORNAME_REG_AN_GAIN_LEVEL, 180}, + * ... + * {0}, + * }; + * @endcode + * + * @{ + */ +/** Flag a register as 8-bit address size, 8-bit data size */ +#define VIDEO_REG_ADDR8_DATA8 VIDEO_REG(1, 1, false) +/** Flag a register as 8-bit address size, 16-bit data size, little-endian */ +#define VIDEO_REG_ADDR8_DATA16_LE VIDEO_REG(1, 2, false) +/** Flag a register as 8-bit address size, 16-bit data size, big-endian */ +#define VIDEO_REG_ADDR8_DATA16_BE VIDEO_REG(1, 2, true) +/** Flag a register as 8-bit address size, 24-bit data size, little-endian */ +#define VIDEO_REG_ADDR8_DATA24_LE VIDEO_REG(1, 3, false) +/** Flag a register as 8-bit address size, 24-bit data size, big-endian */ +#define VIDEO_REG_ADDR8_DATA24_BE VIDEO_REG(1, 3, true) +/** Flag a register as 8-bit address size, 32-bit data size, little-endian */ +#define VIDEO_REG_ADDR8_DATA32_LE VIDEO_REG(1, 4, false) +/** Flag a register as 8-bit address size, 32-bit data size, big-endian */ +#define VIDEO_REG_ADDR8_DATA32_BE VIDEO_REG(1, 4, true) +/** Flag a register as 16-bit address size, 8-bit data size */ +#define VIDEO_REG_ADDR16_DATA8 VIDEO_REG(2, 1, false) +/** Flag a register as 16-bit address size, 16-bit data size, little-endian */ +#define VIDEO_REG_ADDR16_DATA16_LE VIDEO_REG(2, 2, false) +/** Flag a register as 16-bit address size, 16-bit data size, big-endian */ +#define VIDEO_REG_ADDR16_DATA16_BE VIDEO_REG(2, 2, true) +/** Flag a register as 16-bit address size, 24-bit data size, little-endian */ +#define VIDEO_REG_ADDR16_DATA24_LE VIDEO_REG(2, 3, false) +/** Flag a register as 16-bit address size, 24-bit data size, big-endian */ +#define VIDEO_REG_ADDR16_DATA24_BE VIDEO_REG(2, 3, true) +/** Flag a register as 16-bit address size, 32-bit data size, little-endian */ +#define VIDEO_REG_ADDR16_DATA32_LE VIDEO_REG(2, 4, false) +/** Flag a register as 16-bit address size, 32-bit data size, big-endian */ +#define VIDEO_REG_ADDR16_DATA32_BE VIDEO_REG(2, 4, true) +/** @} */ + +/** + * @brief Write a Camera Control Interface register value with the specified address and size. + * + * The size of the register address and register data passed as flags in the high bits of + * @p reg_addr in the unused bits of the address. + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief reg_addr Address of the register to fill with @p reg_data along with size information. + * @brief reg_data Value to write at this address, the size to write is encoded in the address. + */ +int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_data); + + +/** + * @brief Perform a read-modify-write operation on a register given an address, mask and value. + * + * The size of the register address and register data passed as flags in the high bits of + * @p reg_addr in the unused bits of the address. + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief reg_addr Address of the register to fill with @p reg_data along with size information. + * @brief field_mask Mask of the field to insert into the existing value. + * @brief field_value Value to write at this address, the size to write is encoded in the address. + */ +int video_modify_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask, + uint32_t field_value); + +/** + * @brief Read a Camera Control Interace register value from the specified address and size. + * + * The size of the register address and register data passed as flags in the high bits of + * @p reg_addr in the unused bits of the address. + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief reg_addr Address of the register to fill with @p reg_data along with size information. + * @brief reg_data Value to write at this address, the size to write is encoded in the address. + * This is a 32-bit integer pointer even when reading 8-bit or 16 bits value. + */ +int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_data); + +/** + * @brief Write a complete table of registers to a device one by one. + * + * The address present in the registers need to be encoding the size information using the macros + * such as @ref VIDEO_REG_ADDR16_DATA8. The last element must be empty (@c {0}) to mark the end of + * the table. + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief regs Array of address/value pairs to write to the device sequentially. + * @brief num_regs Number of registers entries in the table. + */ +int video_write_cci_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs, + size_t num_regs); + +/** + * @brief Write a complete table of registers to a device one by one. + * + * The registers address are 8-bit wide and values are 8-bit wide. + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief regs Array of address/value pairs to write to the device sequentially. + * @brief num_regs Number of registers entries in the table. + */ +int video_write_cci_multiregs8(const struct i2c_dt_spec *i2c, const struct video_reg8 *regs, + size_t num_regs); + +/** + * @brief Write a complete table of registers to a device one by one. + * + * The registers address are 16-bit wide and values are 8-bit wide. + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief regs Array of address/value pairs to write to the device sequentially. + * @brief num_regs Number of registers entries in the table. + */ +int video_write_cci_multiregs16(const struct i2c_dt_spec *i2c, const struct video_reg16 *regs, + size_t num_regs); + +/** @} */ + +#endif /* ZEPHYR_DRIVERS_VIDEO_COMMON_H_ */