diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index c74d186e03aa..1e672a9bbfb5 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -22,5 +22,6 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_SDMA video_mcux_smartdma.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_IMAGER video_emul_imager.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_EMUL_RX video_emul_rx.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_IMX335 imx335.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_ST_MIPID02 video_st_mipid02.c) zephyr_linker_sources(DATA_SECTIONS video.ld) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index e0ec36e2c90e..17b251d102c8 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -90,4 +90,6 @@ source "drivers/video/Kconfig.emul_rx" source "drivers/video/Kconfig.imx335" +source "drivers/video/Kconfig.st_mipid02" + endif # VIDEO diff --git a/drivers/video/Kconfig.st_mipid02 b/drivers/video/Kconfig.st_mipid02 new file mode 100644 index 000000000000..564d4c2deda0 --- /dev/null +++ b/drivers/video/Kconfig.st_mipid02 @@ -0,0 +1,24 @@ +# Copyright (c) 2025 ST Microelectronics +# SPDX-License-Identifier: Apache-2.0 + +menuconfig VIDEO_ST_MIPID02 + bool "ST Microelectronics MIPID02 CSI to DVP bridge" + select I2C + depends on DT_HAS_ST_MIPID02_ENABLED + default y + help + Enable driver for ST MIPID02 CSI to DVP bridge. + The ST MIPID02 is a dual lane CSI-2 deserializer allowing + to output up to 200MHz 12-bit parallel. + +if VIDEO_ST_MIPID02 + +config VIDEO_ST_MIPID02_CAPS_HEAP_SIZE + int "Size of a heap reserved to handle caps" + default 512 + help + Heap size reserved in order to handle caps. This could be + reduced or increased depending on the amount of formats + supported by the ST_MIPID02 source device. + +endif # VIDEO_ST_MIPID02 diff --git a/drivers/video/video_common.c b/drivers/video/video_common.c index 9c92ed09b028..95b3ad6b0384 100644 --- a/drivers/video/video_common.c +++ b/drivers/video/video_common.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -365,3 +366,42 @@ int video_write_cci_multiregs16(const struct i2c_dt_spec *i2c, const struct vide return 0; } + +int64_t video_get_csi_link_freq(const struct device *dev, uint8_t bpp, uint8_t lane_nb) +{ + struct video_control ctrl = { + .id = VIDEO_CID_LINK_FREQ, + }; + struct video_ctrl_query ctrl_query = { + .id = VIDEO_CID_LINK_FREQ, + }; + int ret; + + /* Try to get the LINK_FREQ value from the source device */ + ret = video_get_ctrl(dev, &ctrl); + if (ret < 0) { + goto fallback; + } + + ret = video_query_ctrl(dev, &ctrl_query); + if (ret < 0) { + return ret; + } + + if (!IN_RANGE(ctrl.val, ctrl_query.range.min, ctrl_query.range.max)) { + return -ERANGE; + } + + return (int64_t)ctrl_query.int_menu[ctrl.val]; + +fallback: + /* If VIDEO_CID_LINK_FREQ is not available, approximate from VIDEO_CID_PIXEL_RATE */ + ctrl.id = VIDEO_CID_PIXEL_RATE; + ret = video_get_ctrl(dev, &ctrl); + if (ret < 0) { + return ret; + } + + /* CSI D-PHY is using a DDR data bus so bitrate is twice the frequency */ + return ctrl.val64 * bpp / (2 * lane_nb); +} diff --git a/drivers/video/video_ctrls.c b/drivers/video/video_ctrls.c index dcab6582eef7..c213dcbb4790 100644 --- a/drivers/video/video_ctrls.c +++ b/drivers/video/video_ctrls.c @@ -70,6 +70,7 @@ static inline int check_range(enum video_ctrl_type type, struct video_ctrl_range } return 0; case VIDEO_CTRL_TYPE_MENU: + case VIDEO_CTRL_TYPE_INTEGER_MENU: if (!IN_RANGE(range.min, 0, range.max) || !IN_RANGE(range.def, range.min, range.max)) { return -ERANGE; @@ -107,6 +108,9 @@ static inline void set_type_flag(uint32_t id, enum video_ctrl_type *type, uint32 *type = VIDEO_CTRL_TYPE_INTEGER64; *flags |= VIDEO_CTRL_FLAG_READ_ONLY; break; + case VIDEO_CID_LINK_FREQ: + *type = VIDEO_CTRL_TYPE_INTEGER_MENU; + break; default: *type = VIDEO_CTRL_TYPE_INTEGER; break; @@ -196,6 +200,28 @@ int video_init_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint return 0; } +int video_init_int_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id, + uint8_t def, const int64_t menu[], size_t menu_len) +{ + int ret; + + if (!menu) { + return -EINVAL; + } + + ret = video_init_ctrl( + ctrl, dev, id, + (struct video_ctrl_range){.min = 0, .max = menu_len - 1, .step = 1, .def = def}); + + if (ret) { + return ret; + } + + ctrl->int_menu = menu; + + return 0; +} + /* By definition, the cluster is in manual mode if the master control value is 0 */ static inline bool is_cluster_manual(const struct video_ctrl *master) { @@ -505,6 +531,8 @@ static inline const char *video_get_ctrl_name(uint32_t id) return "Pixel Rate"; case VIDEO_CID_TEST_PATTERN: return "Test Pattern"; + case VIDEO_CID_LINK_FREQ: + return "Link Frequency"; default: return NULL; } @@ -540,7 +568,11 @@ int video_query_ctrl(const struct device *dev, struct video_ctrl_query *cq) cq->type = ctrl->type; cq->flags = ctrl->flags; cq->range = ctrl->range; - cq->menu = ctrl->menu; + if (cq->type == VIDEO_CTRL_TYPE_MENU) { + cq->menu = ctrl->menu; + } else if (cq->type == VIDEO_CTRL_TYPE_INTEGER_MENU) { + cq->int_menu = ctrl->int_menu; + } cq->name = video_get_ctrl_name(cq->id); return 0; @@ -568,6 +600,9 @@ void video_print_ctrl(const struct device *const dev, const struct video_ctrl_qu case VIDEO_CTRL_TYPE_MENU: type = "menu"; break; + case VIDEO_CTRL_TYPE_INTEGER_MENU: + type = "integer menu"; + break; case VIDEO_CTRL_TYPE_STRING: type = "string"; break; @@ -594,10 +629,15 @@ void video_print_ctrl(const struct device *const dev, const struct video_ctrl_qu cq->range.step, cq->range.def, vc.val); } - if (cq->menu) { + if (cq->type == VIDEO_CTRL_TYPE_MENU && cq->menu) { while (cq->menu[i]) { LOG_INF("%*s %u: %s", 32, "", i, cq->menu[i]); i++; } + } else if (cq->type == VIDEO_CTRL_TYPE_INTEGER_MENU && cq->int_menu) { + while (cq->int_menu[i]) { + LOG_INF("%*s %u: %lld", 12, "", i, cq->int_menu[i]); + i++; + } } } diff --git a/drivers/video/video_ctrls.h b/drivers/video/video_ctrls.h index 07c6c5575f3a..485eacc4a3ce 100644 --- a/drivers/video/video_ctrls.h +++ b/drivers/video/video_ctrls.h @@ -27,10 +27,12 @@ enum video_ctrl_type { VIDEO_CTRL_TYPE_INTEGER = 2, /** 64-bit integer type */ VIDEO_CTRL_TYPE_INTEGER64 = 3, - /** Menu type, standard or driver-defined menu */ + /** Menu string type, standard or driver-defined menu */ VIDEO_CTRL_TYPE_MENU = 4, /** String type */ VIDEO_CTRL_TYPE_STRING = 5, + /** Menu integer type, standard or driver-defined menu */ + VIDEO_CTRL_TYPE_INTEGER_MENU = 6, }; struct video_device; @@ -54,7 +56,10 @@ struct video_ctrl { int32_t val; int64_t val64; }; - const char *const *menu; + union { + const char *const *menu; + const int64_t *int_menu; + }; sys_dnode_t node; }; @@ -64,6 +69,9 @@ int video_init_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t int video_init_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id, uint8_t def, const char *const menu[]); +int video_init_int_menu_ctrl(struct video_ctrl *ctrl, const struct device *dev, uint32_t id, + uint8_t def, const int64_t menu[], size_t menu_len); + void video_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz); void video_auto_cluster_ctrl(struct video_ctrl *ctrls, uint8_t sz, bool set_volatile); diff --git a/drivers/video/video_st_mipid02.c b/drivers/video/video_st_mipid02.c new file mode 100644 index 000000000000..88d9aabb1582 --- /dev/null +++ b/drivers/video/video_st_mipid02.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2025 STMicroelectronics. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT st_mipid02 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "video_common.h" +#include "video_device.h" + +LOG_MODULE_REGISTER(video_st_mipid02, CONFIG_VIDEO_LOG_LEVEL); + +#define MIPID02_MAX_DATA_LANE_NB 2 +struct mipid02_config { + struct i2c_dt_spec i2c; + struct gpio_dt_spec reset_gpio; + const struct device *source_dev; + struct { + uint32_t nb_lanes; + uint8_t lanes[MIPID02_MAX_DATA_LANE_NB]; + } csi; + uint8_t mode_reg2; +}; + +struct mipid02_data { + struct video_format fmt; + const struct mipid02_format_desc *desc; + struct video_format_cap *caps; +}; + +K_HEAP_DEFINE(mipid02_video_caps_heap, CONFIG_VIDEO_ST_MIPID02_CAPS_HEAP_SIZE); + +#define MIPID02_REG8(addr) ((uint32_t)(addr) | VIDEO_REG_ADDR16_DATA8) + +#define MIPID02_CLK_LANE_REG1 MIPID02_REG8(0x0002) +#define MIPID02_CLK_LANE_REG1_EN BIT(0) +#define MIPID02_CLK_LANE_REG1_UI_MSK 0xfc + +#define MIPID02_CLK_LANE_REG3 MIPID02_REG8(0x0004) +#define MIPID02_CLK_LANE_REG3_CSI BIT(1) + +#define MIPID02_DATA_LANE0_REG1 MIPID02_REG8(0x0005) +#define MIPID02_DATA_LANE0_REG1_EN BIT(0) +#define MIPID02_DATA_LANE0_REG1_NORMAL BIT(1) + +#define MIPID02_DATA_LANE0_REG2 MIPID02_REG8(0x0006) +#define MIPID02_DATA_LANE0_REG2_CSI BIT(0) + +#define MIPID02_DATA_LANE1_REG1 MIPID02_REG8(0x0009) +#define MIPID02_DATA_LANE1_REG1_EN BIT(0) + +#define MIPID02_DATA_LANE1_REG2 MIPID02_REG8(0x000a) +#define MIPID02_DATA_LANE1_REG2_CSI BIT(0) + +#define MIPID02_MODE_REG1 MIPID02_REG8(0x0014) +#define MIPID02_MODE_REG1_2LANES BIT(1) +#define MIPID02_MODE_REG1_SWAP BIT(2) +#define MIPID02_MODE_REG1_NO_BYPASS BIT(6) + +#define MIPID02_MODE_REG2 MIPID02_REG8(0x0015) +#define MIPID02_MODE_REG2_HSYNC_INV BIT(1) +#define MIPID02_MODE_REG2_VSYNC_INV BIT(2) +#define MIPID02_MODE_REG2_PCLK_INV BIT(3) + +#define MIPID02_DATA_ID_REG MIPID02_REG8(0x0017) + +#define MIPID02_DATA_SEL_CTRL_REG MIPID02_REG8(0x0019) +#define MIPID02_DATA_SEL_CTRL_REG_R_MODE BIT(2) + +#define MIPID02_FMT(fmt, csi_fmt, dt) \ + { \ + .pixelformat = VIDEO_PIX_FMT_##fmt, \ + .csi_pixelformat = VIDEO_PIX_FMT_##csi_fmt, \ + .csi_datatype = dt, \ + } + +struct mipid02_format_desc { + uint32_t pixelformat; + uint32_t csi_pixelformat; + uint8_t csi_datatype; +}; + +static const struct mipid02_format_desc mipid02_supported_formats[] = { + /* Raw 8 bit */ + MIPID02_FMT(SBGGR8, SBGGR8, VIDEO_MIPI_CSI2_DT_RAW8), + MIPID02_FMT(SGBRG8, SGBRG8, VIDEO_MIPI_CSI2_DT_RAW8), + MIPID02_FMT(SGRBG8, SGRBG8, VIDEO_MIPI_CSI2_DT_RAW8), + MIPID02_FMT(SRGGB8, SRGGB8, VIDEO_MIPI_CSI2_DT_RAW8), + /* Raw 10 bit */ + MIPID02_FMT(SBGGR10, SBGGR10P, VIDEO_MIPI_CSI2_DT_RAW10), + MIPID02_FMT(SGBRG10, SGBRG10P, VIDEO_MIPI_CSI2_DT_RAW10), + MIPID02_FMT(SGRBG10, SGRBG10P, VIDEO_MIPI_CSI2_DT_RAW10), + MIPID02_FMT(SRGGB10, SRGGB10P, VIDEO_MIPI_CSI2_DT_RAW10), + /* Raw 12 bit */ + MIPID02_FMT(SBGGR12, SBGGR12P, VIDEO_MIPI_CSI2_DT_RAW12), + MIPID02_FMT(SGBRG12, SGBRG12P, VIDEO_MIPI_CSI2_DT_RAW12), + MIPID02_FMT(SGRBG12, SGRBG12P, VIDEO_MIPI_CSI2_DT_RAW12), + MIPID02_FMT(SRGGB12, SRGGB12P, VIDEO_MIPI_CSI2_DT_RAW12), + /* Yxx */ + MIPID02_FMT(GREY, GREY, VIDEO_MIPI_CSI2_DT_RAW8), + /* RGB */ + MIPID02_FMT(RGB565, RGB565, VIDEO_MIPI_CSI2_DT_RGB565), + /* YUV */ + MIPID02_FMT(YUYV, YUYV, VIDEO_MIPI_CSI2_DT_YUV422_8), + /* JPEG */ + MIPID02_FMT(JPEG, JPEG, 0), +}; + +static const struct mipid02_format_desc *mipid02_get_format_desc(uint32_t pixelformat) +{ + for (int i = 0; i < ARRAY_SIZE(mipid02_supported_formats); i++) { + if (pixelformat == mipid02_supported_formats[i].pixelformat || + pixelformat == mipid02_supported_formats[i].csi_pixelformat) { + return &mipid02_supported_formats[i]; + } + } + + return NULL; +} + +static int mipid02_set_fmt(const struct device *dev, struct video_format *fmt) +{ + struct mipid02_data *drv_data = dev->data; + const struct mipid02_config *cfg = dev->config; + const struct mipid02_format_desc *desc; + struct video_format sensor_fmt = *fmt; + int ret; + + /* Check that format is supported */ + desc = mipid02_get_format_desc(fmt->pixelformat); + if (!desc) { + return -EINVAL; + } + + /* Configure the DT and perform set_fmt on sensor */ + if (desc->csi_datatype != 0) { + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_ID_REG, + desc->csi_datatype); + if (ret < 0) { + return ret; + } + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_SEL_CTRL_REG, + MIPID02_DATA_SEL_CTRL_REG_R_MODE); + if (ret < 0) { + return ret; + } + } else { + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_SEL_CTRL_REG, 0); + if (ret < 0) { + return ret; + } + } + + /* Call video_set_format to the sensor */ + sensor_fmt.pixelformat = desc->csi_pixelformat; + + ret = video_set_format(cfg->source_dev, &sensor_fmt); + if (ret < 0) { + return ret; + } + + drv_data->fmt = *fmt; + drv_data->desc = desc; + + return 0; +} + +static int mipid02_get_fmt(const struct device *dev, struct video_format *fmt) +{ + const struct mipid02_config *cfg = dev->config; + struct mipid02_data *drv_data = dev->data; + const struct mipid02_format_desc *desc; + int ret; + + if (!drv_data->desc) { + ret = video_get_format(cfg->source_dev, fmt); + if (ret < 0) { + return ret; + } + + desc = mipid02_get_format_desc(fmt->pixelformat); + if (desc) { + LOG_ERR("Sensor format not supported by the ST-MIPID02"); + return -EIO; + } + + /* Override the pixelformat */ + fmt->pixelformat = desc->pixelformat; + + drv_data->fmt = *fmt; + drv_data->desc = desc; + } + + *fmt = drv_data->fmt; + + return 0; +} + +static int mipid02_get_caps(const struct device *dev, struct video_caps *caps) +{ + const struct mipid02_config *cfg = dev->config; + struct mipid02_data *drv_data = dev->data; + int ind = 0, caps_nb = 0; + int ret; + + ret = video_get_caps(cfg->source_dev, caps); + if (ret < 0) { + return ret; + } + + while (caps->format_caps[ind].pixelformat) { + if (mipid02_get_format_desc(caps->format_caps[ind].pixelformat)) { + caps_nb++; + } + ind++; + } + + k_heap_free(&mipid02_video_caps_heap, drv_data->caps); + drv_data->caps = k_heap_alloc(&mipid02_video_caps_heap, + (caps_nb + 1) * sizeof(struct video_format_cap), K_FOREVER); + if (!drv_data->caps) { + return -ENOMEM; + } + + ind = 0; + for (int i = 0; caps->format_caps[i].pixelformat; i++) { + const struct mipid02_format_desc *desc = + mipid02_get_format_desc(caps->format_caps[i].pixelformat); + + if (!desc) { + continue; + } + + drv_data->caps[ind] = caps->format_caps[i]; + drv_data->caps[ind].pixelformat = desc->pixelformat; + ind++; + + if (ind == caps_nb) { + memset(&drv_data->caps[ind], 0, sizeof(drv_data->caps[ind])); + break; + } + } + + caps->format_caps = drv_data->caps; + caps->min_vbuf_count = 1; + caps->min_line_count = LINE_COUNT_HEIGHT; + caps->max_line_count = LINE_COUNT_HEIGHT; + + return 0; +} + +static int mipid02_set_stream(const struct device *dev, bool enable, enum video_buf_type type) +{ + const struct mipid02_config *cfg = dev->config; + struct mipid02_data *drv_data = dev->data; + const struct mipid02_format_desc *desc; + int32_t link_freq; + int ret; + + if (!enable) { + ret = video_stream_stop(cfg->source_dev, type); + if (ret < 0) { + return ret; + } + + /* Disable lanes */ + ret = video_write_cci_reg(&cfg->i2c, MIPID02_CLK_LANE_REG1, 0); + if (ret < 0) { + return ret; + } + + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE0_REG1, 0); + if (ret < 0) { + return ret; + } + + if (cfg->csi.nb_lanes == 2) { + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE1_REG1, 0); + if (ret < 0) { + return ret; + } + } + + return 0; + } + + desc = mipid02_get_format_desc(drv_data->fmt.pixelformat); + if (!desc) { + LOG_ERR("No valid format desc available, should get/set_fmt prior to set_stream"); + return -EIO; + } + + /* Get link-frequency information from source */ + link_freq = video_get_csi_link_freq(cfg->source_dev, + video_bits_per_pixel(desc->csi_pixelformat), + cfg->csi.nb_lanes); + if (link_freq < 0) { + LOG_ERR("Failed to retrieve source link-frequency"); + return -EIO; + } + + /* Enable lanes */ + ret = video_write_cci_reg(&cfg->i2c, MIPID02_CLK_LANE_REG1, + FIELD_PREP(MIPID02_CLK_LANE_REG1_UI_MSK, 2000000000 / link_freq) | + MIPID02_CLK_LANE_REG1_EN); + if (ret < 0) { + return ret; + } + + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE0_REG1, + MIPID02_DATA_LANE0_REG1_EN | MIPID02_DATA_LANE0_REG1_NORMAL); + if (ret < 0) { + return ret; + } + + if (cfg->csi.nb_lanes == 2) { + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE1_REG1, + MIPID02_DATA_LANE1_REG1_EN); + if (ret < 0) { + return ret; + } + } + + return video_stream_start(cfg->source_dev, type); +} + +static int mipid02_set_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct mipid02_config *cfg = dev->config; + + return video_set_frmival(cfg->source_dev, frmival); +} + +static int mipid02_get_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct mipid02_config *cfg = dev->config; + + return video_get_frmival(cfg->source_dev, frmival); +} + +static int mipid02_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) +{ + const struct mipid02_config *cfg = dev->config; + + return video_enum_frmival(cfg->source_dev, fie); +} + +static DEVICE_API(video, mipid02_driver_api) = { + .set_format = mipid02_set_fmt, + .get_format = mipid02_get_fmt, + .get_caps = mipid02_get_caps, + .set_stream = mipid02_set_stream, + .set_frmival = mipid02_set_frmival, + .get_frmival = mipid02_get_frmival, + .enum_frmival = mipid02_enum_frmival, +}; + +static int mipid02_init(const struct device *dev) +{ + const struct mipid02_config *cfg = dev->config; + int ret; + + if (!device_is_ready(cfg->i2c.bus)) { + LOG_ERR("Bus device is not ready"); + return -ENODEV; + } + + if (!gpio_is_ready_dt(&cfg->reset_gpio)) { + LOG_ERR("%s: device %s is not ready", dev->name, cfg->reset_gpio.port->name); + return -ENODEV; + } + + /* Power up sequence */ + if (cfg->reset_gpio.port) { + ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + return ret; + } + } + + k_sleep(K_MSEC(10)); + + if (cfg->reset_gpio.port) { + gpio_pin_set_dt(&cfg->reset_gpio, 0); + } + + /* Set CSI mode */ + ret = video_write_cci_reg(&cfg->i2c, MIPID02_CLK_LANE_REG3, MIPID02_CLK_LANE_REG3_CSI); + if (ret < 0) { + return ret; + } + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE0_REG2, MIPID02_DATA_LANE0_REG2_CSI); + if (ret < 0) { + return ret; + } + if (cfg->csi.nb_lanes == 2) { + ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE1_REG2, + MIPID02_DATA_LANE1_REG2_CSI); + if (ret < 0) { + return ret; + } + } + + /* Configure parallel port synchro signals */ + ret = video_write_cci_reg(&cfg->i2c, MIPID02_MODE_REG2, cfg->mode_reg2); + if (ret < 0) { + return ret; + } + + /* Perform the lanes configuration */ + return video_write_cci_reg(&cfg->i2c, MIPID02_MODE_REG1, + MIPID02_MODE_REG1_NO_BYPASS | + (cfg->csi.nb_lanes == 2 ? MIPID02_MODE_REG1_2LANES : 0) | + (cfg->csi.lanes[0] == 1 ? 0 : MIPID02_MODE_REG1_SWAP)); +} + +#define SOURCE_DEV(n) DEVICE_DT_GET(DT_NODE_REMOTE_DEVICE(DT_INST_ENDPOINT_BY_ID(n, 0, 0))) + +#define MIPID02_INIT(n) \ + static struct mipid02_data mipid02_data_##n; \ + \ + static const struct mipid02_config mipid02_cfg_##n = { \ + .i2c = I2C_DT_SPEC_INST_GET(n), \ + .source_dev = SOURCE_DEV(n), \ + .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \ + .csi.nb_lanes = DT_PROP_LEN(DT_INST_ENDPOINT_BY_ID(n, 0, 0), data_lanes), \ + .csi.lanes[0] = DT_PROP_BY_IDX(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \ + data_lanes, 0), \ + .csi.lanes[1] = COND_CODE_1(DT_PROP_LEN(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \ + data_lanes), \ + (0), \ + (DT_PROP_BY_IDX(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \ + data_lanes, 1))), \ + .mode_reg2 = (DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 2, 0), hsync_active, 0) ? \ + MIPID02_MODE_REG2_HSYNC_INV : 0) | \ + (DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 2, 0), vsync_active, 0) ? \ + MIPID02_MODE_REG2_VSYNC_INV : 0) | \ + (DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 2, 0), pclk_sample, 0) ? \ + MIPID02_MODE_REG2_PCLK_INV : 0), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &mipid02_init, NULL, &mipid02_data_##n, &mipid02_cfg_##n, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &mipid02_driver_api); \ + \ + VIDEO_DEVICE_DEFINE(mipid02_##n, DEVICE_DT_INST_GET(n), NULL); + +DT_INST_FOREACH_STATUS_OKAY(MIPID02_INIT) diff --git a/dts/bindings/video/st,mipid02.yaml b/dts/bindings/video/st,mipid02.yaml new file mode 100644 index 000000000000..b78d0df0c2b5 --- /dev/null +++ b/dts/bindings/video/st,mipid02.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2025 STMicroelectronics. +# SPDX-License-Identifier: Apache-2.0 + +description: MIPID02 CSI to DVP interface bridge + +compatible: "st,mipid02" + +include: i2c-device.yaml + +properties: + reset-gpios: + type: phandle-array + description: | + The XCLR pin is asserted to cause a hard reset. The sensor + receives this as an active-low signal. + +child-binding: + child-binding: + child-binding: + include: video-interfaces.yaml diff --git a/include/zephyr/drivers/video-controls.h b/include/zephyr/drivers/video-controls.h index 79141eb430d8..314c9f520396 100644 --- a/include/zephyr/drivers/video-controls.h +++ b/include/zephyr/drivers/video-controls.h @@ -362,6 +362,9 @@ enum video_camera_orientation { */ #define VIDEO_CID_IMAGE_PROC_CLASS_BASE 0x009f0900 +/** Link frequency, applicable for the CSI2 based devices */ +#define VIDEO_CID_LINK_FREQ (VIDEO_CID_IMAGE_PROC_CLASS_BASE + 1) + /** Pixel rate (pixels/second) in the device's pixel array. This control is read-only. */ #define VIDEO_CID_PIXEL_RATE (VIDEO_CID_IMAGE_PROC_CLASS_BASE + 2) @@ -463,7 +466,10 @@ struct video_ctrl_query { /** control range */ struct video_ctrl_range range; /** menu if control is of menu type */ - const char *const *menu; + union { + const char *const *menu; + const int64_t *int_menu; + }; }; /** diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index b5a706752589..8b4c13fd3a0f 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -804,6 +804,23 @@ void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwis */ void video_closest_frmival(const struct device *dev, struct video_frmival_enum *match); +/** + * @brief Return the link-frequency advertised by a device + * + * Device exposing a CSI link should advertise at least one of the following two controls: + * - @ref VIDEO_CID_LINK_FREQ + * - @ref VIDEO_CID_PIXEL_RATE + * + * At first the helper will try read the @ref VIDEO_CID_LINK_FREQ and if not available will + * approximate the link-frequency from the @ref VIDEO_CID_PIXEL_RATE value, taking into + * consideration the bits per pixel of the format and the number of lanes. + * + * @param dev Video device to query. + * @param bpp Amount of bits per pixel of the pixel format produced by the device + * @param lane_nb Number of CSI-2 lanes used + */ +int64_t video_get_csi_link_freq(const struct device *dev, uint8_t bpp, uint8_t lane_nb); + /** * @defgroup video_pixel_formats Video pixel formats * The '|' characters separate the pixels or logical blocks, and spaces separate the bytes. @@ -1418,6 +1435,39 @@ static inline unsigned int video_bits_per_pixel(uint32_t pixfmt) } } +/** + * @} + */ + +/** + * @name MIPI CSI2 Data-types + * + * @{ + */ +#define VIDEO_MIPI_CSI2_DT_NULL 0x10 +#define VIDEO_MIPI_CSI2_DT_BLANKING 0x11 +#define VIDEO_MIPI_CSI2_DT_EMBEDDED_8 0x12 +#define VIDEO_MIPI_CSI2_DT_YUV420_8 0x18 +#define VIDEO_MIPI_CSI2_DT_YUV420_10 0x19 +#define VIDEO_MIPI_CSI2_DT_YUV420_CSPS_8 0x1c +#define VIDEO_MIPI_CSI2_DT_YUV420_CSPS_10 0x1d +#define VIDEO_MIPI_CSI2_DT_YUV422_8 0x1e +#define VIDEO_MIPI_CSI2_DT_YUV422_10 0x1f +#define VIDEO_MIPI_CSI2_DT_RGB444 0x20 +#define VIDEO_MIPI_CSI2_DT_RGB555 0x21 +#define VIDEO_MIPI_CSI2_DT_RGB565 0x22 +#define VIDEO_MIPI_CSI2_DT_RGB666 0x23 +#define VIDEO_MIPI_CSI2_DT_RGB888 0x24 +#define VIDEO_MIPI_CSI2_DT_RAW6 0x28 +#define VIDEO_MIPI_CSI2_DT_RAW7 0x29 +#define VIDEO_MIPI_CSI2_DT_RAW8 0x2a +#define VIDEO_MIPI_CSI2_DT_RAW10 0x2b +#define VIDEO_MIPI_CSI2_DT_RAW12 0x2c +#define VIDEO_MIPI_CSI2_DT_RAW14 0x2d + +/* User-defined Data-Type range from 0x30 to 0x37 */ +#define VIDEO_MIPI_CSI2_DT_USER(n) (0x30 + (n)) + /** * @} */ diff --git a/tests/drivers/build_all/video/app.overlay b/tests/drivers/build_all/video/app.overlay index a533d9433115..926977b8e447 100644 --- a/tests/drivers/build_all/video/app.overlay +++ b/tests/drivers/build_all/video/app.overlay @@ -88,6 +88,43 @@ reg = <0x7>; clocks = <&imx335_input_clock>; reset-gpios = <&test_gpio 0 0>; + + port { + test_imx335_ep: endpoint { + remote-endpoint-label = "test_mipid02_0"; + }; + }; + }; + + test_i2c_mipid02: bridge@8 { + compatible = "st,mipid02"; + reg = <0x8>; + reset-gpios = <&test_gpio 0 0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + test_mipid02_0: endpoint { + data-lanes = <1 2>; + remote-endpoint-label = "test_imx335_ep"; + }; + }; + + port@2 { + reg = <2>; + + test_mipid02_2: endpoint { + hsync-active = <0>; + vsync-active = <0>; + pclk-sample = <0>; + remote-endpoint-label = "parallel_receiver"; + }; + }; + }; }; };