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.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/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/drivers/video/video_common.c b/drivers/video/video_common.c index 5028a5f67a1a..e69dc6781cf1 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 @@ -135,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; @@ -156,11 +161,79 @@ 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)); + match->index = fie.index; + } + } +} + +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; +} - /* 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; +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/drivers/video/video_emul_imager.c b/drivers/video/video_emul_imager.c index c85c821472fa..e5ad9d91d1f3 100644 --- a/drivers/video/video_emul_imager.c +++ b/drivers/video/video_emul_imager.c @@ -15,21 +15,22 @@ #include #include #include +#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 @@ -37,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}, @@ -83,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, }; @@ -137,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; } @@ -216,103 +139,24 @@ 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; -} - -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; - fie->index++; - - return mode->fps == 0; + *(int *)value = reg; } /* White, Yellow, Cyan, Green, Magenta, Red, Blue, Black */ @@ -324,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 */ @@ -353,139 +197,80 @@ 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) { - struct video_format fmt; - uint8_t sensor_id; - int ret; + return video_imager_init(dev, emul_imager_init_regs, RGB565_64x20); +} - if (/* !i2c_is_ready_dt(&cfg->i2c) */ false) { - /* LOG_ERR("Bus %s is not ready", cfg->i2c.bus->name); */ - return -ENODEV; - } +#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); - 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; - } +DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE) - 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; - } +/* Emulation API, absent from real drvice drivers */ - fmt.pixelformat = fmts[0].pixelformat; - fmt.width = fmts[0].width_min; - fmt.height = fmts[0].height_min; - fmt.pitch = fmt.width * 2; +static int emul_imager_transfer_i2c(const struct emul *target, struct i2c_msg msgs[], int num_msgs, + int addr) +{ + return 0; +} - 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); - } +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}, \ - }; \ - \ - 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, NULL, &emul_imager_api_i2c, \ + &emul_imager_api); DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE) 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_ */ 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/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); + /** * @} */ 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>; 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 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)