From 24e69de15ce8691785b8a9eaa2a023b3bff330d3 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Mar 2025 14:46:05 +0100 Subject: [PATCH 1/6] drivers: video: common: introduce CCI utilities The Camera Common Interface part of the MIPI CSI protocol defines methods to configure a camera device over I2C, such as the size of the register address and data. This permits to write wrapper software like it is done on Linux, in order to simplify the image sensor driver development. Signed-off-by: Josuah Demangeon --- drivers/video/video_common.c | 79 +++++++++++++++++++++++++- include/zephyr/drivers/video.h | 101 +++++++++++++++++++++++++++++++-- 2 files changed, 173 insertions(+), 7 deletions(-) diff --git a/drivers/video/video_common.c b/drivers/video/video_common.c index 5028a5f67a1a..ecb8bfe35d83 100644 --- a/drivers/video/video_common.c +++ b/drivers/video/video_common.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2019, Linaro Limited - * Copyright (c) 2024, tinyVision.ai Inc. + * Copyright (c) 2024-2025, tinyVision.ai Inc. * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,7 +8,12 @@ #include #include +#include #include +#include +#include + +LOG_MODULE_REGISTER(video_common, CONFIG_VIDEO_LOG_LEVEL); #if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP) #include @@ -164,3 +169,75 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, } } } + +int video_cci_read_reg(struct i2c_dt_spec *i2c, uint32_t addr, uint32_t *data) +{ + size_t addr_size = FIELD_GET(VIDEO_CCI_ADDR_SIZE_MASK, addr); + size_t data_size = FIELD_GET(VIDEO_CCI_DATA_SIZE_MASK, addr); + uint8_t *data_ptr = (uint8_t *)data + sizeof(uint32_t) - data_size; + uint8_t buf_w[sizeof(uint16_t)] = {0}; + int ret; + + *data = 0; + + for (int i = 0; i < data_size; i++, addr += 1) { + if (addr_size == 1) { + buf_w[0] = addr; + } else { + sys_put_be16(addr, &buf_w[0]); + } + + ret = i2c_write_read_dt(i2c, buf_w, addr_size, &data_ptr[i], 1); + if (ret != 0) { + return ret; + } + } + + *data = sys_be32_to_cpu(*data); + + return 0; +} + +int video_cci_write_reg(struct i2c_dt_spec *i2c, uint32_t addr, uint32_t data) +{ + size_t addr_size = FIELD_GET(VIDEO_CCI_ADDR_SIZE_MASK, addr); + size_t data_size = FIELD_GET(VIDEO_CCI_DATA_SIZE_MASK, addr); + uint8_t *data_ptr = (uint8_t *)&data + sizeof(uint32_t) - data_size; + uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; + int ret; + + data = sys_cpu_to_be32(data); + + for (int i = 0; i < data_size; i++, addr += 1) { + if (addr_size == 1) { + buf_w[0] = addr; + } else { + sys_put_be16(addr, &buf_w[0]); + } + + buf_w[addr_size] = data_ptr[i]; + + ret = i2c_write_dt(i2c, buf_w, addr_size + 1); + if (ret != 0) { + return ret; + } + } + + return 0; +} + +int video_cci_write_multi(struct i2c_dt_spec *i2c, const struct video_cci_reg *regs) +{ + int ret; + + for (int i = 0; regs[i].addr != 0; i++) { + ret = video_cci_write_reg(i2c, regs[i].addr, regs[i].data); + if (ret != 0) { + LOG_ERR("Failed to write 0x%04x to register 0x%02x", + regs[i].data, regs[i].addr); + return ret; + } + } + + return 0; +} diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 3788c9bad47d..21bc6ccf2bc3 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -21,16 +21,17 @@ * @{ */ -#include -#include -#include - -#include - #ifdef __cplusplus extern "C" { #endif +#include + +#include +#include +#include +#include + /* * Flag used by @ref video_caps structure to indicate endpoint operates on * buffers the size of the video frame @@ -1007,6 +1008,94 @@ static inline unsigned int video_bits_per_pixel(uint32_t pixfmt) } } +/** + * @} + */ + +/** + * Type used by register tables that have either the address or value 16-bit wide. + */ +struct video_cci_reg { + /** Address of the register to write to as well as */ + uint32_t addr; + /** Value to write in this address */ + uint32_t data; +}; + +/** + * @defgroup video_cci Video Camera Control Interface (CCI) + * + * The Camera Control Interface (CCI) is an I2C communication scheme part of MIPI-CSI. + * It allows register addresses 8-bit or 16-bit wide, and register data 8-bit wide only. + * Larger registers data size can be split across multiple write operations. + * + * Together, macros to define metadata in register addresses, and functions that can use this + * information are provided to facilitate drivers development. + * + * @{ + */ + +/** + * @brief Encode the size of the register into the unused bits of the address. + * + * Encode the register address and data size information as extra flags, used by + * @ref video_cci_read_reg(), @ref video_cci_write_reg() and @ref video_cci_write_multi(). + * + * Example usage for a 32-bit address field: + * + * @code{.c} + * #define IMX219_BINNING_MODE_H VIDEO_CCI(0x0174, 2, 32) + * @endcode + * + * @param addr The address to which add the size information. + * @param addr_size Number of address bytes. Possible values: 1, 2. + * @param data_size Number of data bytes following the address. Possible values: 1, 2, 3, 4 + */ +#define VIDEO_CCI_REG(addr, addr_size, data_size) \ + (FIELD_PREP(VIDEO_CCI_ADDR_SIZE_MASK, (addr_size)) | \ + FIELD_PREP(VIDEO_CCI_DATA_SIZE_MASK, (data_size)) | (addr)) + +#define VIDEO_CCI_ADDR_SIZE_MASK GENMASK(21, 20) +#define VIDEO_CCI_DATA_SIZE_MASK GENMASK(19, 16) +#define VIDEO_CCI_ADDR_MASK GENMASK(15, 0) + +/** + * @brief Write a register value to the specified register address and size. + * + * The size of the address and value are both encoded in the unused bits of the address by macros + * such as @ref VIDEO_CCI_REG(). + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief reg_addr Address of the register to fill with @reg_value along with size information. + * @brief reg_value Value to write at this address, the size to write is encoded in the address. + */ +int video_cci_write_reg(struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_value); + +/** + * @brief Read a register value from the specified register address and size. + * + * The size of the address and value are both encoded in the unused bits of the address by macros + * such as @ref VIDEO_ADDR16_REG8(). + * + * @brief i2c Reference to the video device on an I2C bus. + * @brief reg_addr Address of the register to fill with @reg_value along with size information. + * @brief reg_value 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_cci_read_reg(struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_value); + +/** + * @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_ADDR16_REG8(). 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. + */ +int video_cci_write_multi(struct i2c_dt_spec *i2c, const struct video_cci_reg *regs); + /** * @} */ From 3bd3829a43552732aaf5b6aaf16ba7b8a15fcaab Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Mar 2025 14:58:48 +0100 Subject: [PATCH 2/6] drivers: video: common: fix a misuse of video_enum_frmival() Fix an API misuse leading to off-by-one and inaccurate results. The API introduced by 0cbaea0d7ee does not expect fie.index to be incremented by the drver. Signed-off-by: Josuah Demangeon --- drivers/video/video_common.c | 8 ++------ drivers/video/video_emul_imager.c | 1 - tests/drivers/video/api/src/video_emul.c | 6 +++--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/video/video_common.c b/drivers/video/video_common.c index ecb8bfe35d83..e69dc6781cf1 100644 --- a/drivers/video/video_common.c +++ b/drivers/video/video_common.c @@ -140,7 +140,7 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, __ASSERT(match->type != VIDEO_FRMIVAL_TYPE_STEPWISE, "cannot find range matching the range, only a value matching the range"); - while (video_enum_frmival(dev, ep, &fie) == 0) { + for (fie.index = 0; video_enum_frmival(dev, ep, &fie) == 0; fie.index++) { struct video_frmival tmp = {0}; uint64_t diff_nsec = 0, a, b; @@ -161,11 +161,7 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep, if (diff_nsec < best_diff_nsec) { best_diff_nsec = diff_nsec; memcpy(&match->discrete, &tmp, sizeof(tmp)); - - /* The video_enum_frmival() function will increment fie.index every time. - * Compensate for it to get the current index, not the next index. - */ - match->index = fie.index - 1; + match->index = fie.index; } } } diff --git a/drivers/video/video_emul_imager.c b/drivers/video/video_emul_imager.c index c85c821472fa..62a45377d5ff 100644 --- a/drivers/video/video_emul_imager.c +++ b/drivers/video/video_emul_imager.c @@ -310,7 +310,6 @@ static int emul_imager_enum_frmival(const struct device *dev, enum video_endpoin fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE; fie->discrete.numerator = 1; fie->discrete.denominator = mode->fps; - fie->index++; return mode->fps == 0; } diff --git a/tests/drivers/video/api/src/video_emul.c b/tests/drivers/video/api/src/video_emul.c index 5ce07350f857..f4494c4c40a4 100644 --- a/tests/drivers/video/api/src/video_emul.c +++ b/tests/drivers/video/api/src/video_emul.c @@ -83,10 +83,10 @@ ZTEST(video_common, test_video_frmival) /* Do a first enumeration of frame intervals, expected to work */ zexpect_ok(video_enum_frmival(imager_dev, VIDEO_EP_OUT, &fie)); - zexpect_equal(fie.index, 1, "fie's index should increment by one at every iteration"); + zexpect_equal(fie.index, 0, "fie's index should stay the same and not increment"); /* Test that every value of the frame interval enumerator can be applied */ - do { + for (fie.index = 0; video_enum_frmival(imager_dev, VIDEO_EP_OUT, &fie) == 0; fie.index++) { struct video_frmival q, a; uint32_t min, max, step; @@ -122,7 +122,7 @@ ZTEST(video_common, test_video_frmival) zexpect_equal(video_frmival_nsec(&fie.discrete), video_frmival_nsec(&a)); break; } - } while (video_enum_frmival(imager_dev, VIDEO_EP_OUT, &fie) == 0); + } } ZTEST(video_common, test_video_ctrl) From dfec8f416eda394c5a25d90a29793fbd47218479 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Mar 2025 15:21:33 +0100 Subject: [PATCH 3/6] drivers: video: imager: introduce common helpers for imagers Imagers, also called image sensors, are implemented using the generic video API. This makes image sensor require boilerplate code which can be avoided by providing a common implementation that will fit most image sensors. Signed-off-by: Josuah Demangeon --- drivers/video/video_imager.c | 207 +++++++++++++++++++++++++++++++++++ drivers/video/video_imager.h | 84 ++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 drivers/video/video_imager.c create mode 100644 drivers/video/video_imager.h diff --git a/drivers/video/video_imager.c b/drivers/video/video_imager.c new file mode 100644 index 000000000000..5d160ac46e3d --- /dev/null +++ b/drivers/video/video_imager.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "video_imager.h" + +LOG_MODULE_REGISTER(video_imager, CONFIG_VIDEO_LOG_LEVEL); + +int video_imager_set_mode(const struct device *dev, const struct video_imager_mode *mode) +{ + struct video_imager_data *data = dev->data; + int ret; + + if (data->mode == mode) { + LOG_DBG("%s is arlready in the mode requested", dev->name); + return 0; + } + + /* Write each register table to the device */ + for (int i = 0; i < ARRAY_SIZE(mode->regs) && mode->regs[i] != NULL; i++) { + ret = video_cci_write_multi(&data->i2c, mode->regs[i]); + if (ret != 0) { + LOG_ERR("Could not set %s to mode %p, %u FPS", mode, mode->fps, dev->name); + return ret; + } + } + + data->mode = mode; + + return 0; +} + +int video_imager_set_frmival(const struct device *dev, enum video_endpoint_id ep, + struct video_frmival *frmival) +{ + struct video_imager_data *data = dev->data; + struct video_frmival_enum fie = {.format = &data->fmt, .discrete = *frmival}; + + if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + video_closest_frmival(dev, ep, &fie); + + return video_imager_set_mode(dev, &data->modes[data->fmt_id][fie.index]); +} + +int video_imager_get_frmival(const struct device *dev, enum video_endpoint_id ep, + struct video_frmival *frmival) +{ + struct video_imager_data *data = dev->data; + + if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + frmival->numerator = 1; + frmival->denominator = data->mode->fps; + + return 0; +} + +int video_imager_enum_frmival(const struct device *dev, enum video_endpoint_id ep, + struct video_frmival_enum *fie) +{ + struct video_imager_data *data = dev->data; + const struct video_imager_mode *modes; + size_t fmt_id = 0; + int ret; + + if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + ret = video_format_caps_index(data->fmts, fie->format, &fmt_id); + if (ret != 0) { + LOG_ERR("Format '%s' %ux%u not found for %s", + VIDEO_FOURCC_TO_STR(fie->format->pixelformat), + fie->format->width, fie->format->height, dev->name); + return ret; + } + + modes = data->modes[fmt_id]; + + for (int i = 0;; i++) { + if (modes[i].fps == 0) { + return -EINVAL; + } + + if (i == fie->index) { + fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE; + fie->discrete.numerator = 1; + fie->discrete.denominator = modes[i].fps; + break; + } + } + + return 0; +} + +int video_imager_set_fmt(const struct device *const dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_imager_data *data = dev->data; + size_t fmt_id; + int ret; + + if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + LOG_ERR("Only the output endpoint is supported for %s", dev->name); + return -EINVAL; + } + + ret = video_format_caps_index(data->fmts, fmt, &fmt_id); + if (ret != 0) { + LOG_ERR("Format '%s' %ux%u not found for device %s", + VIDEO_FOURCC_TO_STR(fmt->pixelformat), fmt->width, fmt->height, dev->name); + return ret; + } + + ret = video_imager_set_mode(dev, &data->modes[fmt_id][0]); + if (ret != 0) { + return ret; + } + + data->fmt_id = fmt_id; + data->fmt = *fmt; + + return 0; +} + +int video_imager_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_imager_data *data = dev->data; + + if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + *fmt = data->fmt; + + return 0; +} + +int video_imager_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps) +{ + struct video_imager_data *data = dev->data; + + if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { + return -EINVAL; + } + + caps->format_caps = data->fmts; + + return 0; +} + +int video_imager_init(const struct device *dev, const struct video_cci_reg *init_regs, + int default_fmt_idx) +{ + struct video_imager_data *data = dev->data; + struct video_format fmt; + int ret; + + __ASSERT_NO_MSG(data->modes != NULL); + __ASSERT_NO_MSG(data->fmts != NULL); + + if (!device_is_ready(data->i2c.bus)) { + LOG_ERR("I2C bus device %s is not ready", data->i2c.bus->name); + return -ENODEV; + } + + if (init_regs != NULL) { + ret = video_cci_write_multi(&data->i2c, init_regs); + if (ret != 0) { + LOG_ERR("Could not set %s initial registers", dev->name); + return ret; + } + } + + fmt.pixelformat = data->fmts[default_fmt_idx].pixelformat; + fmt.width = data->fmts[default_fmt_idx].width_max; + fmt.height = data->fmts[default_fmt_idx].height_max; + fmt.pitch = fmt.width * video_bits_per_pixel(fmt.pixelformat) / BITS_PER_BYTE; + + ret = video_set_format(dev, VIDEO_EP_OUT, &fmt); + if (ret != 0) { + LOG_ERR("Failed to set %s to default format '%s' %ux%u", + dev->name, VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); + return ret; + } + + return 0; +} diff --git a/drivers/video/video_imager.h b/drivers/video/video_imager.h new file mode 100644 index 000000000000..cbd01ff37473 --- /dev/null +++ b/drivers/video/video_imager.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_VIDEO_IMAGER_H_ +#define ZEPHYR_DRIVERS_VIDEO_IMAGER_H_ + +#include +#include + +/* + * Table entry for an "imaging mode" that can be applied to an imager to configure a particular + * framerate, resolution, and pixel format. The index of the table of modes is meant to match + * the index of the table of formats. + */ +struct video_imager_mode { + /* FPS for this mode */ + uint16_t fps; + /* Multiple lists of registers to allow sharing common sets of registers across modes. */ + const struct video_cci_reg *regs[3]; +}; + +/* + * A video imager device is expected to have dev->data point to this structure. + * In order to support custom data structure, it is possible to store an extra pointer + * in the dev->config struct. See existing drivers for an example. + */ +struct video_imager_data { + /* Index of the currently active format in both modes[] and fmts[] */ + int fmt_id; + /* Currently active video format */ + struct video_format fmt; + /* List of all formats supported by this sensor */ + const struct video_format_cap *fmts; + /* Currently active operating mode as defined above */ + const struct video_imager_mode *mode; + /* Array of modes tables, one table per format cap lislted by "fmts" */ + const struct video_imager_mode **modes; + /* I2C device to write the registers to */ + struct i2c_dt_spec i2c; +}; + +/* + * Initialize one row of a @struct video_format_cap with fixed width and height + * The minimum and maximum are the same for both width and height fields. + */ +#define VIDEO_IMAGER_FORMAT_CAP(pixfmt, width, height) \ + { \ + .width_min = (width), .width_max = (width), .width_step = 0, \ + .height_min = (height), .height_max = (height), .height_step = 0, \ + .pixelformat = (pixfmt), \ + } + +/* + * Function which will set the operating mode (as defined above) of the imager. + */ +int video_imager_set_mode(const struct device *dev, const struct video_imager_mode *mode); + +/* + * Default implementations that can be passed directly to the struct video_api, or called by the + * driver implementation. + */ +int video_imager_set_frmival(const struct device *dev, enum video_endpoint_id ep, + struct video_frmival *frmival); +int video_imager_get_frmival(const struct device *dev, enum video_endpoint_id ep, + struct video_frmival *frmival); +int video_imager_enum_frmival(const struct device *dev, enum video_endpoint_id ep, + struct video_frmival_enum *fie); +int video_imager_set_fmt(const struct device *const dev, enum video_endpoint_id ep, + struct video_format *fmt); +int video_imager_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt); +int video_imager_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps); + +/* + * Initialize an imager by loading init_regs onto the device, and setting the default format. + */ +int video_imager_init(const struct device *dev, const struct video_cci_reg *init_regs, + int default_fmt_idx); + +#endif /* ZEPHYR_DRIVERS_VIDEO_IMAGER_H_ */ From d4ad194249cfe4c3d51854cdaa5e04ca29b2cf55 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Mar 2025 15:32:01 +0100 Subject: [PATCH 4/6] drivers: video: imx219: new image sensor from Sony Add an IMX219 image sensor driver, using the newly introduced "CCI" and "imager" utilities to facilitate image sensor implementation. Signed-off-by: Josuah Demangeon --- drivers/video/CMakeLists.txt | 1 + drivers/video/Kconfig | 2 + drivers/video/Kconfig.imx219 | 10 + drivers/video/imx219.c | 270 ++++++++++++++++++++++ dts/bindings/video/sony,imx219.yaml | 12 + tests/drivers/build_all/video/app.overlay | 5 + 6 files changed, 300 insertions(+) create mode 100644 drivers/video/Kconfig.imx219 create mode 100644 drivers/video/imx219.c create mode 100644 dts/bindings/video/sony,imx219.yaml diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 6210f8d881d0..f7a724d49e7c 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -14,6 +14,7 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_GC2145 gc2145.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_STM32_DCMI video_stm32_dcmi.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV5640 ov5640.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7670 ov7670.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_IMX219 imx219.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_ESP32 video_esp32_dvp.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_SDMA video_mcux_smartdma.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_IMAGER video_emul_imager.c) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index a3b163443628..4ff5e870bcc8 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -58,6 +58,8 @@ source "drivers/video/Kconfig.mcux_mipi_csi2rx" source "drivers/video/Kconfig.sw_generator" +source "drivers/video/Kconfig.imx219" + source "drivers/video/Kconfig.mt9m114" source "drivers/video/Kconfig.ov7725" diff --git a/drivers/video/Kconfig.imx219 b/drivers/video/Kconfig.imx219 new file mode 100644 index 000000000000..6d6abd039646 --- /dev/null +++ b/drivers/video/Kconfig.imx219 @@ -0,0 +1,10 @@ +# Copyright (c) 2024-2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_IMX219 + bool "IMX219 8 MP CMOS image sensor" + select I2C + depends on DT_HAS_SONY_IMX219_ENABLED + default y + help + Enable the driver for the Sony IMX219 8 Mega-Pixel CMOS image sensor diff --git a/drivers/video/imx219.c b/drivers/video/imx219.c new file mode 100644 index 000000000000..2741ceac7bf3 --- /dev/null +++ b/drivers/video/imx219.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2024-2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT sony_imx219 + +#include +#include +#include +#include +#include +#include +#include + +#include "video_imager.h" + +LOG_MODULE_REGISTER(imx219, CONFIG_VIDEO_LOG_LEVEL); + +#define IMX219_FULL_WIDTH 3280 +#define IMX219_FULL_HEIGHT 2464 + +#define IMX219_REG_CHIP_ID VIDEO_CCI(0x0000, 2, 2) +#define IMX219_REG_SOFTWARE_RESET VIDEO_CCI(0x0103, 2, 1) +#define IMX219_REG_MODE_SELECT VIDEO_CCI(0x0100, 2, 1) +#define IMX219_REG_ANALOG_GAIN VIDEO_CCI(0x0157, 2, 1) +#define IMX219_REG_DIGITAL_GAIN VIDEO_CCI(0x0158, 2, 2) +#define IMX219_REG_INTEGRATION_TIME VIDEO_CCI(0x015A, 2, 2) +#define IMX219_REG_TESTPATTERN VIDEO_CCI(0x0600, 2, 2) +#define IMX219_REG_MODE_SELECT VIDEO_CCI(0x0100, 2, 1) +#define IMX219_REG_CSI_LANE_MODE VIDEO_CCI(0x0114, 2, 1) +#define IMX219_REG_DPHY_CTRL VIDEO_CCI(0x0128, 2, 1) +#define IMX219_REG_EXCK_FREQ VIDEO_CCI(0x012a, 2, 2) +#define IMX219_REG_PREPLLCK_VT_DIV VIDEO_CCI(0x0304, 2, 1) +#define IMX219_REG_PREPLLCK_OP_DIV VIDEO_CCI(0x0305, 2, 1) +#define IMX219_REG_OPPXCK_DIV VIDEO_CCI(0x0309, 2, 1) +#define IMX219_REG_VTPXCK_DIV VIDEO_CCI(0x0301, 2, 1) +#define IMX219_REG_VTSYCK_DIV VIDEO_CCI(0x0303, 2, 1) +#define IMX219_REG_OPSYCK_DIV VIDEO_CCI(0x030b, 2, 1) +#define IMX219_REG_PLL_VT_MPY VIDEO_CCI(0x0306, 2, 2) +#define IMX219_REG_PLL_OP_MPY VIDEO_CCI(0x030c, 2, 2) +#define IMX219_REG_LINE_LENGTH_A VIDEO_CCI(0x0162, 2, 2) +#define IMX219_REG_CSI_DATA_FORMAT_A VIDEO_CCI(0x018c, 2, 2) +#define IMX219_REG_BINNING_MODE_H VIDEO_CCI(0x0174, 2, 1) +#define IMX219_REG_BINNING_MODE_V VIDEO_CCI(0x0175, 2, 1) +#define IMX219_REG_ORIENTATION VIDEO_CCI(0x0172, 2, 1) +#define IMX219_REG_FRM_LENGTH_A VIDEO_CCI(0x0160, 2, 2) +#define IMX219_REG_FRM_LENGTH_A VIDEO_CCI(0x0160, 2, 2) +#define IMX219_REG_X_ADD_STA_A VIDEO_CCI(0x0164, 2, 2) +#define IMX219_REG_X_ADD_END_A VIDEO_CCI(0x0166, 2, 2) +#define IMX219_REG_Y_ADD_STA_A VIDEO_CCI(0x0168, 2, 2) +#define IMX219_REG_Y_ADD_END_A VIDEO_CCI(0x016a, 2, 2) +#define IMX219_REG_X_OUTPUT_SIZE VIDEO_CCI(0x016c, 2, 2) +#define IMX219_REG_Y_OUTPUT_SIZE VIDEO_CCI(0x016e, 2, 2) +#define IMX219_REG_X_ODD_INC_A VIDEO_CCI(0x0170, 2, 1) +#define IMX219_REG_Y_ODD_INC_A VIDEO_CCI(0x0171, 2, 1) + +/* Registers to crop down a resolution to a centered width and height */ +#define IMX219_REGS_CROP(width, height) \ + {IMX219_REG_X_ADD_STA_A, (IMX219_FULL_WIDTH - (width)) / 2}, \ + {IMX219_REG_X_ADD_END_A, (IMX219_FULL_WIDTH + (width)) / 2 - 1}, \ + {IMX219_REG_Y_ADD_STA_A, (IMX219_FULL_HEIGHT - (height)) / 2}, \ + {IMX219_REG_Y_ADD_END_A, (IMX219_FULL_HEIGHT + (height)) / 2 - 1} + +static const struct video_reg init_regs[] = { + {IMX219_REG_MODE_SELECT, 0x00}, /* Standby */ + + /* Enable access to registers from 0x3000 to 0x5fff */ + {VIDEO_CCI(0x30eb, 2, 1), 0x05}, + {VIDEO_CCI(0x30eb, 2, 1), 0x0c}, + {VIDEO_CCI(0x300a, 2, 1), 0xff}, + {VIDEO_CCI(0x300b, 2, 1), 0xff}, + {VIDEO_CCI(0x30eb, 2, 1), 0x05}, + {VIDEO_CCI(0x30eb, 2, 1), 0x09}, + + /* MIPI configuration registers */ + {IMX219_REG_CSI_LANE_MODE, 0x01}, /* 2 Lanes */ + {IMX219_REG_DPHY_CTRL, 0x00}, /* Timing auto */ + + /* Clock configuration registers */ + {IMX219_REG_EXCK_FREQ, 24 << 8}, /* 24 MHz */ + {IMX219_REG_PREPLLCK_VT_DIV, 0x03}, /* Auto */ + {IMX219_REG_PREPLLCK_OP_DIV, 0x03}, /* Auto */ + {IMX219_REG_OPPXCK_DIV, 10}, /* 10-bits per pixel */ + {IMX219_REG_VTPXCK_DIV, 5}, + {IMX219_REG_VTSYCK_DIV, 1}, + {IMX219_REG_OPSYCK_DIV, 1}, + {IMX219_REG_PLL_VT_MPY, 32}, /* Pixel/Sys clock multiplier */ + {IMX219_REG_PLL_OP_MPY, 50}, /* Output clock multiplier */ + + /* Undocumented registers */ + {VIDEO_CCI(0x455e, 2, 1), 0x00}, + {VIDEO_CCI(0x471e, 2, 1), 0x4b}, + {VIDEO_CCI(0x4767, 2, 1), 0x0f}, + {VIDEO_CCI(0x4750, 2, 1), 0x14}, + {VIDEO_CCI(0x4540, 2, 1), 0x00}, + {VIDEO_CCI(0x47b4, 2, 1), 0x14}, + {VIDEO_CCI(0x4713, 2, 1), 0x30}, + {VIDEO_CCI(0x478b, 2, 1), 0x10}, + {VIDEO_CCI(0x478f, 2, 1), 0x10}, + {VIDEO_CCI(0x4793, 2, 1), 0x10}, + {VIDEO_CCI(0x4797, 2, 1), 0x0e}, + {VIDEO_CCI(0x479b, 2, 1), 0x0e}, + + /* Timing and format registers */ + {IMX219_REG_LINE_LENGTH_A, 3448}, + {IMX219_REG_X_ODD_INC_A, 1}, + {IMX219_REG_Y_ODD_INC_A, 1}, + {IMX219_REG_CSI_DATA_FORMAT_A, (10 << 8) | 10}, /* 10-bits per pixels */ + + /* Custom defaults */ + {IMX219_REG_BINNING_MODE_H, 0x00}, + {IMX219_REG_BINNING_MODE_V, 0x00}, + {IMX219_REG_DIGITAL_GAIN, 5000}, + {IMX219_REG_ANALOG_GAIN, 240}, + {IMX219_REG_INTEGRATION_TIME, 1600}, + {IMX219_REG_ORIENTATION, 0x03}, + + {0}, +}; + +static const struct video_reg size_1920x1080[] = { + IMX219_REGS_CROP(1920, 1080), + {IMX219_REG_X_OUTPUT_SIZE, 1920}, + {IMX219_REG_Y_OUTPUT_SIZE, 1080}, + {IMX219_REG_FRM_LENGTH_A, 1080 + 120}, + {0}, +}; + +static const struct video_reg size_640x480[] = { + IMX219_REGS_CROP(640, 480), + {IMX219_REG_X_OUTPUT_SIZE, 640}, + {IMX219_REG_Y_OUTPUT_SIZE, 480}, + {IMX219_REG_FRM_LENGTH_A, 480 + 120}, + {0}, +}; + +static const struct video_imager_mode modes_1920x1080[] = { + {.fps = 30, .regs = {size_1920x1080}}, + {0}, +}; + +static const struct video_imager_mode modes_640x480[] = { + {.fps = 30, .regs = {size_640x480}}, + {0}, +}; + +static const struct video_imager_mode *modes[] = { + modes_1920x1080, + modes_640x480, + NULL, +}; + +static const struct video_format_cap fmts[] = { + VIDEO_IMAGER_FORMAT_CAP(VIDEO_PIX_FMT_BGGR8, 1920, 1080), + VIDEO_IMAGER_FORMAT_CAP(VIDEO_PIX_FMT_BGGR8, 640, 480), + {0}, +}; + +static int imx219_set_stream(const struct device *dev, bool on) +{ + struct video_imager_data *data = dev->data; + + return video_cci_write_reg(data->i2c, IMX219_REG_MODE_SELECT, on ? 0x01 : 0x00); +} + +static int imx219_set_ctrl(const struct device *dev, unsigned int cid, void *value) +{ + struct video_imager_data *data = dev->data; + + switch (cid) { + case VIDEO_CID_EXPOSURE: + return video_cci_write_reg(data->i2c, IMX219_REG_INTEGRATION_TIME, (int)value); + case VIDEO_CID_GAIN: + return video_cci_write_reg(data->i2c, IMX219_REG_ANALOG_GAIN, (int)value); + case VIDEO_CID_TEST_PATTERN: + return video_cci_write_reg(data->i2c, IMX219_REG_TESTPATTERN, (int)value); + default: + LOG_WRN("Control not supported"); + return -ENOTSUP; + } +} + +static int imx219_get_ctrl(const struct device *dev, unsigned int cid, void *value) +{ + struct video_imager_data *data = dev->data; + uint32_t reg; + int ret; + + switch (cid) { + case VIDEO_CID_EXPOSURE: + /* Values for normal frame rate, different range for low frame rate mode */ + ret = video_read_reg(data->i2c, IMX219_REG_INTEGRATION_TIME, ®); + *(uint32_t *)value = reg; + return ret; + case VIDEO_CID_GAIN: + ret = video_read_reg(data->i2c, IMX219_REG_ANALOG_GAIN, ®); + *(uint32_t *)value = reg; + return ret; + default: + LOG_WRN("Control not supported"); + return -ENOTSUP; + } +} + +static const DEVICE_API(video, imx219_driver_api) = { + /* Implementation common to all sensors */ + .set_format = video_imager_set_fmt, + .get_format = video_imager_get_fmt, + .get_caps = video_imager_get_caps, + .set_frmival = video_imager_set_frmival, + .get_frmival = video_imager_get_frmival, + .enum_frmival = video_imager_enum_frmival, + /* Implementation specific o this sensor */ + .set_stream = imx219_set_stream, + .set_ctrl = imx219_set_ctrl, + .get_ctrl = imx219_get_ctrl, +}; + +static int imx219_init(const struct device *dev) +{ + struct video_imager_data *data = dev->data; + uint32_t reg; + int ret; + + if (!device_is_ready(data->i2c->bus)) { + LOG_ERR("I2C device %s is not ready", data->i2c->bus->name); + return -ENODEV; + } + + k_sleep(K_MSEC(1)); + + ret = video_cci_write_reg(data->i2c, IMX219_REG_SOFTWARE_RESET, 1); + if (ret != 0) { + goto err; + } + + /* Initializing time of silicon (t5): 32000 clock cycles, 5.3 msec for 6 MHz */ + k_sleep(K_MSEC(6)); + + ret = video_cci_read_reg(data->i2c, IMX219_REG_CHIP_ID, ®); + if (ret != 0) { + goto err; + } + + if (reg != 0x0219) { + LOG_ERR("Wrong chip ID %04x", reg); + return -ENODEV; + } + + return video_imager_init(dev, init_regs, 0); +err: + LOG_ERR("Error during %s initialization: %s", dev->name, strerror(-ret)); + return ret; + +} + +#define IMX219_INIT(n) \ + static struct i2c_dt_spec i2c_##n = I2C_DT_SPEC_INST_GET(n); \ + static struct video_imager_data data_##n = { \ + .i2c = &i2c_##n, \ + .fmts = fmts, \ + .modes = modes, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &imx219_init, NULL, &data_##n, NULL, POST_KERNEL, \ + CONFIG_VIDEO_INIT_PRIORITY, &imx219_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(IMX219_INIT) diff --git a/dts/bindings/video/sony,imx219.yaml b/dts/bindings/video/sony,imx219.yaml new file mode 100644 index 000000000000..cff166871e9e --- /dev/null +++ b/dts/bindings/video/sony,imx219.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2025, tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: IMX219 8 Mega-Pixel CMOS image sensor + +compatible: "sony,imx219" + +include: i2c-device.yaml + +child-binding: + child-binding: + include: video-interfaces.yaml diff --git a/tests/drivers/build_all/video/app.overlay b/tests/drivers/build_all/video/app.overlay index 84678f7ee069..005fb6f94d9c 100644 --- a/tests/drivers/build_all/video/app.overlay +++ b/tests/drivers/build_all/video/app.overlay @@ -66,6 +66,11 @@ reset-gpios = <&test_gpio 0 0>; }; + test_i2c_imx219: imx219@6 { + compatible = "sony,imx219"; + reg = <0x6>; + }; + test_i2c_video_emul_imager: video_emul_imager@6 { compatible = "zephyr,video-emul-imager"; reg = <0x6>; From 3e1b15c75b26ef533b21fcb1816bdea8d4d8fa90 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Mar 2025 16:46:07 +0100 Subject: [PATCH 5/6] drivers: video: emul-imager: use the emulated I2C bus Replace the ad-hoc register emulation by the dedicated I2C emulator, making it usable with the same APIs as every other image sensors. Signed-off-by: Josuah Demangeon --- drivers/video/Kconfig.emul_imager | 1 + drivers/video/video_emul_imager.c | 31 +++++++++++++++++++---- tests/drivers/video/api/app.overlay | 39 ++++++++++++----------------- tests/drivers/video/api/prj.conf | 2 ++ 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/drivers/video/Kconfig.emul_imager b/drivers/video/Kconfig.emul_imager index d2752fdd53b4..b8aee5cd1296 100644 --- a/drivers/video/Kconfig.emul_imager +++ b/drivers/video/Kconfig.emul_imager @@ -5,6 +5,7 @@ config VIDEO_EMUL_IMAGER bool "Software implementation of an imager" depends on DT_HAS_ZEPHYR_VIDEO_EMUL_IMAGER_ENABLED default y + depends on EMUL help Enable driver for the emulated Imager. diff --git a/drivers/video/video_emul_imager.c b/drivers/video/video_emul_imager.c index 62a45377d5ff..e693e0ddc422 100644 --- a/drivers/video/video_emul_imager.c +++ b/drivers/video/video_emul_imager.c @@ -15,6 +15,7 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(video_emul_imager, CONFIG_VIDEO_LOG_LEVEL); @@ -313,7 +314,6 @@ static int emul_imager_enum_frmival(const struct device *dev, enum video_endpoin return mode->fps == 0; } - /* White, Yellow, Cyan, Green, Magenta, Red, Blue, Black */ static const uint16_t pattern_8bars_yuv[8][3] = { {0xFF, 0x7F, 0x7F}, {0xFF, 0x00, 0xFF}, {0xFF, 0xFF, 0x00}, {0x7F, 0x00, 0x00}, @@ -441,12 +441,13 @@ static DEVICE_API(video, emul_imager_driver_api) = { int emul_imager_init(const struct device *dev) { + const struct emul_imager_config *cfg = dev->config; struct video_format fmt; uint8_t sensor_id; int ret; - if (/* !i2c_is_ready_dt(&cfg->i2c) */ false) { - /* LOG_ERR("Bus %s is not ready", cfg->i2c.bus->name); */ + if (!i2c_is_ready_dt(&cfg->i2c)) { + LOG_ERR("Bus %s is not ready", cfg->i2c.bus->name); return -ENODEV; } @@ -476,15 +477,35 @@ int emul_imager_init(const struct device *dev) return 0; } +/* Simulated I2C bus: do nothing out of the data */ + +static int emul_imager_transfer_i2c(const struct emul *target, struct i2c_msg msgs[], int num_msgs, + int addr) +{ + return 0; +} + +static const struct i2c_emul_api emul_imager_api_i2c = { + .transfer = emul_imager_transfer_i2c, +}; + +static int emul_imager_init_i2c(const struct emul *target, const struct device *dev) +{ + return 0; +} + #define EMUL_IMAGER_DEFINE(inst) \ static struct emul_imager_data emul_imager_data_##inst; \ \ static const struct emul_imager_config emul_imager_cfg_##inst = { \ - .i2c = /* I2C_DT_SPEC_INST_GET(inst) */ {0}, \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ }; \ \ DEVICE_DT_INST_DEFINE(inst, &emul_imager_init, NULL, &emul_imager_data_##inst, \ &emul_imager_cfg_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ - &emul_imager_driver_api); + &emul_imager_driver_api); \ + \ + EMUL_DT_INST_DEFINE(inst, emul_imager_init_i2c, NULL, &emul_imager_cfg_##inst, NULL, \ + &emul_imager_api_i2c); DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE) diff --git a/tests/drivers/video/api/app.overlay b/tests/drivers/video/api/app.overlay index 3e1e02e6a95a..8e6cce51748a 100644 --- a/tests/drivers/video/api/app.overlay +++ b/tests/drivers/video/api/app.overlay @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 tinyVision.ai Inc. + * Copyright (c) 2024-2025 tinyVision.ai Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -8,31 +8,9 @@ #address-cells = <1>; #size-cells = <1>; - test_i2c: i2c@10002000 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "vnd,i2c"; - reg = <0x10002000 0x1000>; - clock-frequency = <100000>; - status = "okay"; - - test_video_emul_imager: video_emul_imager@6 { - compatible = "zephyr,video-emul-imager"; - status = "okay"; - reg = <0x6>; - - port { - test_video_emul_imager_ep_out: endpoint { - remote-endpoint-label = "test_video_emul_rx_ep_in"; - }; - }; - }; - }; - test_video_emul_rx: video_emul_rx@10003000 { compatible = "zephyr,video-emul-rx"; reg = <0x10003000 0x1000>; - status = "okay"; port { #address-cells = <1>; @@ -51,3 +29,18 @@ }; }; }; + +&i2c0 { + status = "okay"; + + test_video_emul_imager: video_emul_imager@6 { + compatible = "zephyr,video-emul-imager"; + reg = <0x6>; + + port { + test_video_emul_imager_ep_out: endpoint { + remote-endpoint-label = "test_video_emul_rx_ep_in"; + }; + }; + }; +}; diff --git a/tests/drivers/video/api/prj.conf b/tests/drivers/video/api/prj.conf index 5e2cc7c828bf..8b0bd6781ee6 100644 --- a/tests/drivers/video/api/prj.conf +++ b/tests/drivers/video/api/prj.conf @@ -3,3 +3,5 @@ CONFIG_VIDEO=y CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=16384 CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=1 CONFIG_VIDEO_LOG_LEVEL_DBG=y +CONFIG_EMUL=y +CONFIG_I2C=y From ef733ccb95ead80bbb9bde4d23ce78984e4d1225 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 12 Mar 2025 18:06:47 +0000 Subject: [PATCH 6/6] drivers: video: emul_imager: use the shared sensor infrastructure Now that the emulated I2C is supported, it is possible to use the same APIs as actual image sensors, permitting to cover the image sensor driver logics with unit tests. Signed-off-by: Josuah Demangeon --- drivers/video/video_emul_imager.c | 393 ++++++------------------------ 1 file changed, 79 insertions(+), 314 deletions(-) diff --git a/drivers/video/video_emul_imager.c b/drivers/video/video_emul_imager.c index e693e0ddc422..e5ad9d91d1f3 100644 --- a/drivers/video/video_emul_imager.c +++ b/drivers/video/video_emul_imager.c @@ -18,19 +18,19 @@ #include #include +#include "video_imager.h" + LOG_MODULE_REGISTER(video_emul_imager, CONFIG_VIDEO_LOG_LEVEL); -#define EMUL_IMAGER_REG_SENSOR_ID 0x0000 -#define EMUL_IMAGER_SENSOR_ID 0x99 -#define EMUL_IMAGER_REG_CTRL 0x0001 -#define EMUL_IMAGER_REG_INIT1 0x0002 -#define EMUL_IMAGER_REG_INIT2 0x0003 -#define EMUL_IMAGER_REG_TIMING1 0x0004 -#define EMUL_IMAGER_REG_TIMING2 0x0005 -#define EMUL_IMAGER_REG_TIMING3 0x0006 -#define EMUL_IMAGER_REG_EXPOSURE 0x0007 -#define EMUL_IMAGER_REG_GAIN 0x0008 -#define EMUL_IMAGER_REG_PATTERN 0x0009 +#define EMUL_IMAGER_REG_CTRL VIDEO_CCI_REG(0x0001, 2, 1) +#define EMUL_IMAGER_REG_INIT1 VIDEO_CCI_REG(0x0002, 2, 1) +#define EMUL_IMAGER_REG_INIT2 VIDEO_CCI_REG(0x0003, 2, 1) +#define EMUL_IMAGER_REG_TIMING1 VIDEO_CCI_REG(0x0004, 2, 1) +#define EMUL_IMAGER_REG_TIMING2 VIDEO_CCI_REG(0x0005, 2, 1) +#define EMUL_IMAGER_REG_TIMING3 VIDEO_CCI_REG(0x0006, 2, 1) +#define EMUL_IMAGER_REG_EXPOSURE VIDEO_CCI_REG(0x0007, 2, 2) +#define EMUL_IMAGER_REG_GAIN VIDEO_CCI_REG(0x0008, 2, 1) +#define EMUL_IMAGER_REG_PATTERN VIDEO_CCI_REG(0x0009, 2, 1) #define EMUL_IMAGER_PATTERN_OFF 0x00 #define EMUL_IMAGER_PATTERN_BARS1 0x01 #define EMUL_IMAGER_PATTERN_BARS2 0x02 @@ -38,41 +38,13 @@ LOG_MODULE_REGISTER(video_emul_imager, CONFIG_VIDEO_LOG_LEVEL); /* Emulated register bank */ uint8_t emul_imager_fake_regs[10]; -enum emul_imager_fmt_id { - RGB565_64x20, - YUYV_64x20, -}; - -struct emul_imager_reg { - uint16_t addr; - uint8_t value; -}; - -struct emul_imager_mode { - uint8_t fps; - /* List of registers lists to configure the various properties of the sensor. - * This permits to deduplicate the list of registers in case some lare sections - * are repeated across modes, such as the resolution for different FPS. - */ - const struct emul_imager_reg *regs[2]; - /* More fields can be added according to the needs of the sensor driver */ -}; - struct emul_imager_config { - struct i2c_dt_spec i2c; -}; - -struct emul_imager_data { - /* First field is a framebuffer for I/O emulation purpose */ - uint8_t framebuffer[CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE]; - /* Other fields are shared with real hardware drivers */ - const struct emul_imager_mode *mode; - enum emul_imager_fmt_id fmt_id; - struct video_format fmt; + /* A framebuffer for I/O emulation purpose */ + uint8_t *framebuffer; }; /* Initial parameters of the sensors common to all modes. */ -static const struct emul_imager_reg emul_imager_init_regs[] = { +static const struct video_cci_reg emul_imager_init_regs[] = { {EMUL_IMAGER_REG_CTRL, 0x00}, /* Example comment about REG_INIT1 */ {EMUL_IMAGER_REG_INIT1, 0x10}, @@ -84,53 +56,58 @@ static const struct emul_imager_reg emul_imager_init_regs[] = { * to set the timing parameters and other mode-dependent configuration. */ -static const struct emul_imager_reg emul_imager_rgb565_64x20[] = { +static const struct video_cci_reg emul_imager_rgb565_64x20[] = { {EMUL_IMAGER_REG_TIMING1, 0x64}, {EMUL_IMAGER_REG_TIMING2, 0x20}, {0}, }; -static const struct emul_imager_reg emul_imager_rgb565_64x20_15fps[] = { +static const struct video_cci_reg emul_imager_rgb565_64x20_15fps[] = { {EMUL_IMAGER_REG_TIMING3, 15}, {0}, }; -static const struct emul_imager_reg emul_imager_rgb565_64x20_30fps[] = { +static const struct video_cci_reg emul_imager_rgb565_64x20_30fps[] = { {EMUL_IMAGER_REG_TIMING3, 30}, {0}, }; -static const struct emul_imager_reg emul_imager_rgb565_64x20_60fps[] = { +static const struct video_cci_reg emul_imager_rgb565_64x20_60fps[] = { {EMUL_IMAGER_REG_TIMING3, 60}, {0}, }; -struct emul_imager_mode emul_imager_rgb565_64x20_modes[] = { +static const struct video_imager_mode emul_imager_rgb565_64x20_modes[] = { {.fps = 15, .regs = {emul_imager_rgb565_64x20, emul_imager_rgb565_64x20_15fps}}, {.fps = 30, .regs = {emul_imager_rgb565_64x20, emul_imager_rgb565_64x20_30fps}}, {.fps = 60, .regs = {emul_imager_rgb565_64x20, emul_imager_rgb565_64x20_60fps}}, {0}, }; -static const struct emul_imager_reg emul_imager_yuyv_64x20[] = { +static const struct video_cci_reg emul_imager_yuyv_64x20[] = { {EMUL_IMAGER_REG_TIMING1, 0x64}, {EMUL_IMAGER_REG_TIMING2, 0x20}, {0}, }; -static const struct emul_imager_reg emul_imager_yuyv_64x20_15fps[] = { +static const struct video_cci_reg emul_imager_yuyv_64x20_15fps[] = { {EMUL_IMAGER_REG_TIMING3, 15}, {0}, }; -static const struct emul_imager_reg emul_imager_yuyv_64x20_30fps[] = { +static const struct video_cci_reg emul_imager_yuyv_64x20_30fps[] = { {EMUL_IMAGER_REG_TIMING3, 30}, {0}, }; -struct emul_imager_mode emul_imager_yuyv_64x20_modes[] = { +static const struct video_imager_mode emul_imager_yuyv_64x20_modes[] = { {.fps = 15, .regs = {emul_imager_yuyv_64x20, emul_imager_yuyv_64x20_15fps}}, {.fps = 30, .regs = {emul_imager_yuyv_64x20, emul_imager_yuyv_64x20_30fps}}, {0}, }; +enum emul_imager_fmt_id { + RGB565_64x20, + YUYV_64x20, +}; + /* Summary of all the modes of all the frame formats, with the format ID as * index, matching fmts[]. */ -static const struct emul_imager_mode *emul_imager_modes[] = { +static const struct video_imager_mode *modes[] = { [RGB565_64x20] = emul_imager_rgb565_64x20_modes, [YUYV_64x20] = emul_imager_yuyv_64x20_modes, }; @@ -138,78 +115,23 @@ static const struct emul_imager_mode *emul_imager_modes[] = { /* Video device capabilities where the supported resolutions and pixel formats are listed. * The format ID is used as index to fetch the matching mode from the list above. */ -#define EMUL_IMAGER_VIDEO_FORMAT_CAP(width, height, format) \ - { \ - .pixelformat = (format), \ - .width_min = (width), \ - .width_max = (width), \ - .height_min = (height), \ - .height_max = (height), \ - .width_step = 0, \ - .height_step = 0, \ - } static const struct video_format_cap fmts[] = { - [RGB565_64x20] = EMUL_IMAGER_VIDEO_FORMAT_CAP(64, 20, VIDEO_PIX_FMT_RGB565), - [YUYV_64x20] = EMUL_IMAGER_VIDEO_FORMAT_CAP(64, 20, VIDEO_PIX_FMT_YUYV), + [RGB565_64x20] = VIDEO_IMAGER_FORMAT_CAP(64, 20, VIDEO_PIX_FMT_RGB565), + [YUYV_64x20] = VIDEO_IMAGER_FORMAT_CAP(64, 20, VIDEO_PIX_FMT_YUYV), {0}, }; -/* Emulated I2C register interface, to replace with actual I2C calls for real hardware */ -static int emul_imager_read_reg(const struct device *const dev, uint8_t reg_addr, uint8_t *value) -{ - LOG_DBG("%s placeholder for I2C read from 0x%02x", dev->name, reg_addr); - switch (reg_addr) { - case EMUL_IMAGER_REG_SENSOR_ID: - *value = EMUL_IMAGER_SENSOR_ID; - break; - default: - *value = emul_imager_fake_regs[reg_addr]; - } - return 0; -} - -/* Helper to read a full integer directly from a register */ -static int emul_imager_read_int(const struct device *const dev, uint8_t reg_addr, int *value) -{ - uint8_t val8; - int ret; - - ret = emul_imager_read_reg(dev, reg_addr, &val8); - *value = val8; - return ret; -} - -/* Some sensors will need reg8 or reg16 variants. */ -static int emul_imager_write_reg(const struct device *const dev, uint8_t reg_addr, uint8_t value) -{ - LOG_DBG("%s placeholder for I2C write 0x%08x to 0x%02x", dev->name, value, reg_addr); - emul_imager_fake_regs[reg_addr] = value; - return 0; -} - -static int emul_imager_write_multi(const struct device *const dev, - const struct emul_imager_reg *regs) -{ - int ret; - - for (int i = 0; regs[i].addr != 0; i++) { - ret = emul_imager_write_reg(dev, regs[i].addr, regs[i].value); - if (ret < 0) { - return ret; - } - } - return 0; -} - static int emul_imager_set_ctrl(const struct device *dev, unsigned int cid, void *value) { + struct video_imager_data *data = dev->data; + switch (cid) { case VIDEO_CID_EXPOSURE: - return emul_imager_write_reg(dev, EMUL_IMAGER_REG_EXPOSURE, (int)value); + return video_cci_write_reg(&data->i2c, EMUL_IMAGER_REG_EXPOSURE, (int)value); case VIDEO_CID_GAIN: - return emul_imager_write_reg(dev, EMUL_IMAGER_REG_GAIN, (int)value); + return video_cci_write_reg(&data->i2c, EMUL_IMAGER_REG_GAIN, (int)value); case VIDEO_CID_TEST_PATTERN: - return emul_imager_write_reg(dev, EMUL_IMAGER_REG_PATTERN, (int)value); + return video_cci_write_reg(&data->i2c, EMUL_IMAGER_REG_PATTERN, (int)value); default: return -ENOTSUP; } @@ -217,103 +139,26 @@ static int emul_imager_set_ctrl(const struct device *dev, unsigned int cid, void static int emul_imager_get_ctrl(const struct device *dev, unsigned int cid, void *value) { - struct emul_imager_data *data = dev->data; + struct video_imager_data *data = dev->data; + uint32_t reg; switch (cid) { case VIDEO_CID_EXPOSURE: - return emul_imager_read_int(dev, EMUL_IMAGER_REG_EXPOSURE, value); + video_cci_read_reg(&data->i2c, EMUL_IMAGER_REG_EXPOSURE, ®); case VIDEO_CID_GAIN: - return emul_imager_read_int(dev, EMUL_IMAGER_REG_GAIN, value); + video_cci_read_reg(&data->i2c, EMUL_IMAGER_REG_GAIN, ®); case VIDEO_CID_TEST_PATTERN: - return emul_imager_read_int(dev, EMUL_IMAGER_REG_PATTERN, value); + video_cci_read_reg(&data->i2c, EMUL_IMAGER_REG_PATTERN, ®); case VIDEO_CID_PIXEL_RATE: *(int64_t *)value = (int64_t)data->fmt.width * data->fmt.pitch * data->mode->fps; return 0; default: return -ENOTSUP; } -} - -/* Customize this function according to your "struct emul_imager_mode". */ -static int emul_imager_set_mode(const struct device *dev, const struct emul_imager_mode *mode) -{ - struct emul_imager_data *data = dev->data; - int ret; - - if (data->mode == mode) { - return 0; - } - - LOG_DBG("Applying mode %p at %d FPS", mode, mode->fps); - - /* Apply all the configuration registers for that mode */ - for (int i = 0; i < 2; i++) { - ret = emul_imager_write_multi(dev, mode->regs[i]); - if (ret < 0) { - goto err; - } - } - data->mode = mode; - return 0; -err: - LOG_ERR("Could not apply %s mode %p (%u FPS)", dev->name, mode, mode->fps); - return ret; + *(int *)value = reg; } -static int emul_imager_set_frmival(const struct device *dev, enum video_endpoint_id ep, - struct video_frmival *frmival) -{ - struct emul_imager_data *data = dev->data; - struct video_frmival_enum fie = {.format = &data->fmt, .discrete = *frmival}; - - if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { - return -EINVAL; - } - - video_closest_frmival(dev, ep, &fie); - LOG_DBG("Applying frame interval number %u", fie.index); - return emul_imager_set_mode(dev, &emul_imager_modes[data->fmt_id][fie.index]); -} - -static int emul_imager_get_frmival(const struct device *dev, enum video_endpoint_id ep, - struct video_frmival *frmival) -{ - struct emul_imager_data *data = dev->data; - - if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { - return -EINVAL; - } - - frmival->numerator = 1; - frmival->denominator = data->mode->fps; - return 0; -} - -static int emul_imager_enum_frmival(const struct device *dev, enum video_endpoint_id ep, - struct video_frmival_enum *fie) -{ - const struct emul_imager_mode *mode; - size_t fmt_id; - int ret; - - if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { - return -EINVAL; - } - - ret = video_format_caps_index(fmts, fie->format, &fmt_id); - if (ret < 0) { - return ret; - } - - mode = &emul_imager_modes[fmt_id][fie->index]; - - fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE; - fie->discrete.numerator = 1; - fie->discrete.denominator = mode->fps; - - return mode->fps == 0; -} /* White, Yellow, Cyan, Green, Magenta, Red, Blue, Black */ static const uint16_t pattern_8bars_yuv[8][3] = { {0xFF, 0x7F, 0x7F}, {0xFF, 0x00, 0xFF}, {0xFF, 0xFF, 0x00}, {0x7F, 0x00, 0x00}, @@ -323,8 +168,8 @@ static const uint16_t pattern_8bars_rgb[8][3] = { {0xFF, 0x00, 0xFF}, {0xFF, 0x00, 0x00}, {0x00, 0x00, 0xFF}, {0x00, 0x00, 0x00}}; static void emul_imager_fill_framebuffer(const struct device *const dev, struct video_format *fmt) { - struct emul_imager_data *data = dev->data; - uint16_t *fb16 = (uint16_t *)data->framebuffer; + const struct emul_imager_config *cfg = dev->config; + uint16_t *fb16 = (uint16_t *)cfg->framebuffer; uint16_t r, g, b, y, uv; /* Fill the first row of the emulated framebuffer */ @@ -352,132 +197,62 @@ static void emul_imager_fill_framebuffer(const struct device *const dev, struct /* Duplicate the first row over the whole frame */ for (size_t i = 1; i < fmt->height; i++) { - memcpy(data->framebuffer + fmt->pitch * i, data->framebuffer, fmt->pitch); + memcpy(cfg->framebuffer + fmt->pitch * i, cfg->framebuffer, fmt->pitch); } } -static int emul_imager_set_fmt(const struct device *const dev, enum video_endpoint_id ep, +static int emul_imager_set_fmt(const struct device *dev, enum video_endpoint_id ep, struct video_format *fmt) { - struct emul_imager_data *data = dev->data; - size_t fmt_id; - int ret; - - if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { - return -EINVAL; - } - - if (fmt->pitch * fmt->height > CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE) { - LOG_ERR("%s has %u bytes of memory, unable to support %x %ux%u (%u bytes)", - dev->name, CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE, fmt->pixelformat, - fmt->width, fmt->height, fmt->pitch * fmt->height); - return -ENOMEM; - } - - if (memcmp(&data->fmt, fmt, sizeof(data->fmt)) == 0) { - return 0; - } - - ret = video_format_caps_index(fmts, fmt, &fmt_id); - if (ret < 0) { - LOG_ERR("Format %x %ux%u not found for %s", fmt->pixelformat, fmt->width, - fmt->height, dev->name); - return ret; - } - - ret = emul_imager_set_mode(dev, &emul_imager_modes[fmt_id][0]); - if (ret < 0) { - return ret; - } - - /* Change the image pattern on the framebuffer */ emul_imager_fill_framebuffer(dev, fmt); - data->fmt_id = fmt_id; - data->fmt = *fmt; - return 0; -} - -static int emul_imager_get_fmt(const struct device *dev, enum video_endpoint_id ep, - struct video_format *fmt) -{ - struct emul_imager_data *data = dev->data; - - if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { - return -EINVAL; - } - - *fmt = data->fmt; - return 0; -} - -static int emul_imager_get_caps(const struct device *dev, enum video_endpoint_id ep, - struct video_caps *caps) -{ - if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { - return -EINVAL; - } - - caps->format_caps = fmts; - return 0; + return video_imager_set_fmt(dev, ep, fmt); } static int emul_imager_set_stream(const struct device *dev, bool enable) { - return emul_imager_write_reg(dev, EMUL_IMAGER_REG_CTRL, enable ? 1 : 0); + struct video_imager_data *data = dev->data; + + return video_cci_write_reg(&data->i2c, EMUL_IMAGER_REG_CTRL, enable ? 1 : 0); } -static DEVICE_API(video, emul_imager_driver_api) = { +static DEVICE_API(video, emul_imager_api) = { + .set_stream = emul_imager_set_stream, .set_ctrl = emul_imager_set_ctrl, .get_ctrl = emul_imager_get_ctrl, - .set_frmival = emul_imager_set_frmival, - .get_frmival = emul_imager_get_frmival, - .enum_frmival = emul_imager_enum_frmival, + .set_frmival = video_imager_set_frmival, + .get_frmival = video_imager_get_frmival, + .enum_frmival = video_imager_enum_frmival, .set_format = emul_imager_set_fmt, - .get_format = emul_imager_get_fmt, - .get_caps = emul_imager_get_caps, - .set_stream = emul_imager_set_stream, + .get_format = video_imager_get_fmt, + .get_caps = video_imager_get_caps, }; int emul_imager_init(const struct device *dev) { - const struct emul_imager_config *cfg = dev->config; - struct video_format fmt; - uint8_t sensor_id; - int ret; - - if (!i2c_is_ready_dt(&cfg->i2c)) { - LOG_ERR("Bus %s is not ready", cfg->i2c.bus->name); - return -ENODEV; - } - - ret = emul_imager_read_reg(dev, EMUL_IMAGER_REG_SENSOR_ID, &sensor_id); - if (ret < 0 || sensor_id != EMUL_IMAGER_SENSOR_ID) { - LOG_ERR("Failed to get %s correct sensor ID (0x%x", dev->name, sensor_id); - return ret; - } - - ret = emul_imager_write_multi(dev, emul_imager_init_regs); - if (ret < 0) { - LOG_ERR("Could not set %s initial registers", dev->name); - return ret; - } - - fmt.pixelformat = fmts[0].pixelformat; - fmt.width = fmts[0].width_min; - fmt.height = fmts[0].height_min; - fmt.pitch = fmt.width * 2; + return video_imager_init(dev, emul_imager_init_regs, RGB565_64x20); +} - ret = emul_imager_set_fmt(dev, VIDEO_EP_OUT, &fmt); - if (ret < 0) { - LOG_ERR("Failed to set %s to default format %x %ux%u", dev->name, fmt.pixelformat, - fmt.width, fmt.height); - } +#define EMUL_IMAGER_DEFINE(inst) \ + uint8_t emul_imager_framebuffer_##inst[CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE]; \ + \ + static const struct emul_imager_config emul_imager_cfg_##inst = { \ + .framebuffer = emul_imager_framebuffer_##inst, \ + }; \ + \ + static struct video_imager_data emul_imager_data_##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .fmts = fmts, \ + .modes = modes, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, &emul_imager_init, NULL, &emul_imager_data_##inst, \ + &emul_imager_cfg_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ + &emul_imager_api); - return 0; -} +DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE) -/* Simulated I2C bus: do nothing out of the data */ +/* Emulation API, absent from real drvice drivers */ static int emul_imager_transfer_i2c(const struct emul *target, struct i2c_msg msgs[], int num_msgs, int addr) @@ -495,17 +270,7 @@ static int emul_imager_init_i2c(const struct emul *target, const struct device * } #define EMUL_IMAGER_DEFINE(inst) \ - static struct emul_imager_data emul_imager_data_##inst; \ - \ - static const struct emul_imager_config emul_imager_cfg_##inst = { \ - .i2c = I2C_DT_SPEC_INST_GET(inst), \ - }; \ - \ - DEVICE_DT_INST_DEFINE(inst, &emul_imager_init, NULL, &emul_imager_data_##inst, \ - &emul_imager_cfg_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ - &emul_imager_driver_api); \ - \ - EMUL_DT_INST_DEFINE(inst, emul_imager_init_i2c, NULL, &emul_imager_cfg_##inst, NULL, \ - &emul_imager_api_i2c); + EMUL_DT_INST_DEFINE(inst, emul_imager_init_i2c, NULL, NULL, &emul_imager_api_i2c, \ + &emul_imager_api); DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE)