diff --git a/boards/shields/raspberry_pi_camera_module_2/Kconfig.shield b/boards/shields/raspberry_pi_camera_module_2/Kconfig.shield new file mode 100644 index 000000000000..a489dcf205ef --- /dev/null +++ b/boards/shields/raspberry_pi_camera_module_2/Kconfig.shield @@ -0,0 +1,5 @@ +# Copyright (c) 2025 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config SHIELD_RASPBERRY_PI_CAMERA_MODULE_2 + def_bool $(shields_list_contains,raspberry_pi_camera_module_2) diff --git a/boards/shields/raspberry_pi_camera_module_2/boards/stm32n6570_dk.conf b/boards/shields/raspberry_pi_camera_module_2/boards/stm32n6570_dk.conf new file mode 100644 index 000000000000..c9ddd9321fca --- /dev/null +++ b/boards/shields/raspberry_pi_camera_module_2/boards/stm32n6570_dk.conf @@ -0,0 +1,3 @@ +CONFIG_VIDEO_STM32_DCMIPP_SENSOR_PIXEL_FORMAT="pBAA" +CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH=3280 +CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT=2464 diff --git a/boards/shields/raspberry_pi_camera_module_2/boards/stm32n6570_dk_stm32n657xx_sb.conf b/boards/shields/raspberry_pi_camera_module_2/boards/stm32n6570_dk_stm32n657xx_sb.conf new file mode 100644 index 000000000000..c9ddd9321fca --- /dev/null +++ b/boards/shields/raspberry_pi_camera_module_2/boards/stm32n6570_dk_stm32n657xx_sb.conf @@ -0,0 +1,3 @@ +CONFIG_VIDEO_STM32_DCMIPP_SENSOR_PIXEL_FORMAT="pBAA" +CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH=3280 +CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT=2464 diff --git a/boards/shields/raspberry_pi_camera_module_2/doc/index.rst b/boards/shields/raspberry_pi_camera_module_2/doc/index.rst new file mode 100644 index 000000000000..a505e7ab4ffd --- /dev/null +++ b/boards/shields/raspberry_pi_camera_module_2/doc/index.rst @@ -0,0 +1,55 @@ +.. _raspberry_pi_camera_module_2: + +Raspberry Pi Camera Module 2 +############################ + +Overview +******** + +The Raspberry Pi camera module 2 provides a Sony IMX219 rolling shutter in a module featuring a +15-pin FFC connector popularized by Raspberry Pi, present on a wide range of boards. + +The NoIR variant is the same as the normal variant, except with the infra-red filter removed. + +.. figure:: rpi_cam_v2_normal.jpg + :width: 500px + :align: center + :alt: Raspberry Pi Camera Module 2 + + Raspberry Pi Camera Module 2, normal variant (Credit: Raspberry Pi.) + +.. figure:: rpi_cam_v2_noir.jpg + :width: 500px + :align: center + :alt: Raspberry Pi Camera Module 2 NoIR + + Raspberry Pi Camera Module 2, NoIR variant (Credit: Raspberry Pi.) + +Requirements +************ + +The camera module is compatible with all boards featuring a 15 pins FFC connector and the necessary +devicetree definitions for a :dtcompatible:`raspberrypi,csi-connector`. + +Usage +***** + +The shield can be used in any application by setting ``SHIELD`` to +``raspberry_pi_camera_module_2`` for boards with the necessary device tree node labels: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/video/capture + :board: stm32n6570_dk + :shield: raspberry_pi_camera_module_2 + :goals: build + +References +********** + +- `Product page `_ + +- `Product page (NoIR) `_ + +- `Datasheet `_ + +- `Mechanical drawing `_ diff --git a/boards/shields/raspberry_pi_camera_module_2/doc/rpi_cam_v2_noir.jpg b/boards/shields/raspberry_pi_camera_module_2/doc/rpi_cam_v2_noir.jpg new file mode 100644 index 000000000000..265a0f190b64 Binary files /dev/null and b/boards/shields/raspberry_pi_camera_module_2/doc/rpi_cam_v2_noir.jpg differ diff --git a/boards/shields/raspberry_pi_camera_module_2/doc/rpi_cam_v2_normal.jpg b/boards/shields/raspberry_pi_camera_module_2/doc/rpi_cam_v2_normal.jpg new file mode 100644 index 000000000000..f32656e6d755 Binary files /dev/null and b/boards/shields/raspberry_pi_camera_module_2/doc/rpi_cam_v2_normal.jpg differ diff --git a/boards/shields/raspberry_pi_camera_module_2/raspberry_pi_camera_module_2.overlay b/boards/shields/raspberry_pi_camera_module_2/raspberry_pi_camera_module_2.overlay new file mode 100644 index 000000000000..7f8de9d8d1eb --- /dev/null +++ b/boards/shields/raspberry_pi_camera_module_2/raspberry_pi_camera_module_2.overlay @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/ { + chosen { + zephyr,camera = &csi_capture_port; + }; + + imx219_input_clock: imx219-input-clock { + compatible = "fixed-clock"; + clock-frequency = <24000000>; + #clock-cells = <0>; + }; +}; + +&csi_interface { + status = "okay"; +}; + +&csi_ep_in { + remote-endpoint-label = "imx219_ep_out"; + bus-type = ; + data-lanes = <1 2>; +}; + +&csi_i2c { + imx219: imx219@10 { + compatible = "sony,imx219"; + clocks = <&imx219_input_clock>; + reg = <0x10>; + + port { + imx219_ep_out: endpoint { + remote-endpoint-label = "csi_ep_in"; + bus-type = ; + data-lanes = <1 2>; + }; + }; + }; +}; diff --git a/boards/shields/raspberry_pi_camera_module_2/shield.yml b/boards/shields/raspberry_pi_camera_module_2/shield.yml new file mode 100644 index 000000000000..4559ed02cea9 --- /dev/null +++ b/boards/shields/raspberry_pi_camera_module_2/shield.yml @@ -0,0 +1,6 @@ +shield: + name: raspberry_pi_camera_module_2 + full_name: Raspberry Pi Camera Module 2 + vendor: Raspberry Pi + supported_features: + - video diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 083df586b003..6e0dc94918b8 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -6,24 +6,27 @@ zephyr_library_sources(video_common.c) zephyr_library_sources(video_ctrls.c) zephyr_library_sources(video_device.c) -zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c) +# zephyr-keep-sorted-start +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_ESP32 video_esp32_dvp.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_GC2145 gc2145.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_IMX219 imx219.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_IMX335 imx335.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_MIPI_CSI2RX video_mcux_mipi_csi2rx.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_SDMA video_mcux_smartdma.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_MT9M114 mt9m114.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640 ov2640.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_OV7725 ov7725.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_OV9655 ov9655.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_SHELL video_shell.c) -zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_GENERATOR video_sw_generator.c) -zephyr_library_sources_ifdef(CONFIG_VIDEO_MT9M114 mt9m114.c) -zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7725 ov7725.c) -zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640 ov2640.c) -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_OV9655 ov9655.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) -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_library_sources_ifdef(CONFIG_VIDEO_STM32_DCMIPP video_stm32_dcmipp.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_STM32_DCMI video_stm32_dcmi.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_STM32_DCMIPP video_stm32_dcmipp.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_ST_MIPID02 video_st_mipid02.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_GENERATOR video_sw_generator.c) +# zephyr-keep-sorted-stop zephyr_linker_sources(DATA_SECTIONS video.ld) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 924e2156273c..f0e323953d2d 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -58,42 +58,27 @@ config VIDEO_I2C_RETRY_NUM The default is to not retry. Board configuration files or user project can then use the number of retries that matches their situation. +# zephyr-keep-sorted-start +source "drivers/video/Kconfig.emul_imager" +source "drivers/video/Kconfig.emul_rx" source "drivers/video/Kconfig.esp32_dvp" - +source "drivers/video/Kconfig.gc2145" +source "drivers/video/Kconfig.imx219" +source "drivers/video/Kconfig.imx335" source "drivers/video/Kconfig.mcux_csi" - source "drivers/video/Kconfig.mcux_mipi_csi2rx" - -source "drivers/video/Kconfig.shell" - -source "drivers/video/Kconfig.sw_generator" - +source "drivers/video/Kconfig.mcux_sdma" source "drivers/video/Kconfig.mt9m114" - -source "drivers/video/Kconfig.ov7725" - source "drivers/video/Kconfig.ov2640" - -source "drivers/video/Kconfig.stm32_dcmi" - source "drivers/video/Kconfig.ov5640" - source "drivers/video/Kconfig.ov7670" - +source "drivers/video/Kconfig.ov7725" source "drivers/video/Kconfig.ov9655" - -source "drivers/video/Kconfig.gc2145" - -source "drivers/video/Kconfig.mcux_sdma" - -source "drivers/video/Kconfig.emul_imager" - -source "drivers/video/Kconfig.emul_rx" - -source "drivers/video/Kconfig.imx335" - +source "drivers/video/Kconfig.shell" source "drivers/video/Kconfig.st_mipid02" - +source "drivers/video/Kconfig.stm32_dcmi" source "drivers/video/Kconfig.stm32_dcmipp" +source "drivers/video/Kconfig.sw_generator" +# zephyr-keep-sorted-stop endif # VIDEO diff --git a/drivers/video/Kconfig.imx219 b/drivers/video/Kconfig.imx219 new file mode 100644 index 000000000000..d6523ca7a75c --- /dev/null +++ b/drivers/video/Kconfig.imx219 @@ -0,0 +1,10 @@ +# Copyright (c) 2024 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_IMX219 + bool "IMX219 8 Mega-Pixel CMOS image sensor" + select I2C + depends on DT_HAS_SONY_IMX219_ENABLED + default y + help + Enable driver for 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..7089fb683e9d --- /dev/null +++ b/drivers/video/imx219.c @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2022-2023 Circuit Valley + * 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_common.h" +#include "video_ctrls.h" +#include "video_device.h" + +LOG_MODULE_REGISTER(imx219, CONFIG_VIDEO_LOG_LEVEL); + +#define IMX219_FULL_WIDTH 3280 +#define IMX219_FULL_HEIGHT 2464 +#define IMX219_CHIP_ID 0x0219 + +#define IMX219_REG8(addr) ((addr) | VIDEO_REG_ADDR16_DATA8) +#define IMX219_REG16(addr) ((addr) | VIDEO_REG_ADDR16_DATA16_BE) + +#define IMX219_CCI_CHIP_ID IMX219_REG16(0x0000) +#define IMX219_CCI_SOFTWARE_RESET IMX219_REG8(0x0103) +#define IMX219_CCI_MODE_SELECT IMX219_REG8(0x0100) +#define IMX219_MODE_SELECT_STANDBY 0x00 +#define IMX219_MODE_SELECT_STREAMING 0x01 +#define IMX219_CCI_ANALOG_GAIN IMX219_REG8(0x0157) +#define IMX219_ANALOG_GAIN_DEFAULT 240 +#define IMX219_CCI_DIGITAL_GAIN IMX219_REG16(0x0158) +#define IMX219_DIGITAL_GAIN_DEFAULT 5000 +#define IMX219_CCI_INTEGRATION_TIME IMX219_REG16(0x015A) +#define IMX219_INTEGRATION_TIME_DEFAULT 100 +#define IMX219_CCI_TESTPATTERN IMX219_REG16(0x0600) +#define IMX219_CCI_TP_WINDOW_WIDTH IMX219_REG16(0x0624) +#define IMX219_CCI_TP_WINDOW_HEIGHT IMX219_REG16(0x0626) +#define IMX219_CCI_CSI_LANE_MODE IMX219_REG8(0x0114) +#define IMX219_CCI_DPHY_CTRL IMX219_REG8(0x0128) +#define IMX219_CCI_EXCK_FREQ IMX219_REG16(0x012a) +#define IMX219_CCI_PREPLLCK_VT_DIV IMX219_REG8(0x0304) +#define IMX219_CCI_PREPLLCK_OP_DIV IMX219_REG8(0x0305) +#define IMX219_CCI_OPPXCK_DIV IMX219_REG8(0x0309) +#define IMX219_CCI_VTPXCK_DIV IMX219_REG8(0x0301) +#define IMX219_CCI_VTSYCK_DIV IMX219_REG8(0x0303) +#define IMX219_CCI_OPSYCK_DIV IMX219_REG8(0x030b) +#define IMX219_CCI_PLL_VT_MPY IMX219_REG16(0x0306) +#define IMX219_CCI_PLL_OP_MPY IMX219_REG16(0x030c) +#define IMX219_CCI_LINE_LENGTH_A IMX219_REG16(0x0162) +#define IMX219_REG_CSI_DATA_FORMAT_A0 0x018c +#define IMX219_REG_CSI_DATA_FORMAT_A1 0x018d +#define IMX219_CCI_BINNING_MODE_H IMX219_REG8(0x0174) +#define IMX219_CCI_BINNING_MODE_V IMX219_REG8(0x0175) +#define IMX219_CCI_ORIENTATION IMX219_REG8(0x0172) +#define IMX219_CCI_FRM_LENGTH_A IMX219_REG16(0x0160) +#define IMX219_CCI_X_ADD_STA_A IMX219_REG16(0x0164) +#define IMX219_CCI_X_ADD_END_A IMX219_REG16(0x0166) +#define IMX219_CCI_Y_ADD_STA_A IMX219_REG16(0x0168) +#define IMX219_CCI_Y_ADD_END_A IMX219_REG16(0x016a) +#define IMX219_CCI_X_OUTPUT_SIZE IMX219_REG16(0x016c) +#define IMX219_CCI_Y_OUTPUT_SIZE IMX219_REG16(0x016e) +#define IMX219_CCI_X_ODD_INC_A IMX219_REG8(0x0170) +#define IMX219_CCI_Y_ODD_INC_A IMX219_REG8(0x0171) +#define IMX219_CCI_DT_PEDESTAL IMX219_REG16(0xD1EA) +#define IMX219_DT_PEDESTAL_DEFAULT 40 + +#define IMX219_2DL_LINK_FREQ 456000000 +const int64_t imx219_2dl_link_frequency[] = { + IMX219_2DL_LINK_FREQ, +}; + +struct imx219_ctrls { + struct video_ctrl exposure; + struct video_ctrl brightness; + struct video_ctrl analogue_gain; + struct video_ctrl digital_gain; + struct video_ctrl linkfreq; + struct video_ctrl test_pattern; +}; + +struct imx219_data { + struct imx219_ctrls ctrls; + struct video_format fmt; + int fps; +}; + +struct imx219_config { + struct i2c_dt_spec i2c; + uint32_t input_clk_hz; +}; + +/* Undocumented registers */ +static const struct video_reg16 imx219_vendor_regs[] = { + /* Enable access to registers from 0x3000 to 0x5fff */ + {0x30eb, 0x05}, + {0x30eb, 0x0c}, + {0x300a, 0xff}, + {0x300b, 0xff}, + {0x30eb, 0x05}, + {0x30eb, 0x09}, + + /* Extra undocumented registers */ + {0x455e, 0x00}, + {0x471e, 0x4b}, + {0x4767, 0x0f}, + {0x4750, 0x14}, + {0x4540, 0x00}, + {0x47b4, 0x14}, + {0x4713, 0x30}, + {0x478b, 0x10}, + {0x478f, 0x10}, + {0x4793, 0x10}, + {0x4797, 0x0e}, + {0x479b, 0x0e}, +}; + +/* Registers to crop down a resolution to a centered width and height */ +static const struct video_reg imx219_init_regs[] = { + /* MIPI configuration registers */ + {IMX219_CCI_CSI_LANE_MODE, 0x01}, /* 2 Lanes */ + {IMX219_CCI_DPHY_CTRL, 0x00}, /* Timing auto */ + + /* Timing and format registers */ + {IMX219_CCI_LINE_LENGTH_A, 3448}, + {IMX219_CCI_X_ODD_INC_A, 1}, + {IMX219_CCI_Y_ODD_INC_A, 1}, + + /* Custom defaults */ + {IMX219_CCI_BINNING_MODE_H, 0x00}, /* No binning */ + {IMX219_CCI_BINNING_MODE_V, 0x00} /* No binning */, + {IMX219_CCI_DIGITAL_GAIN, IMX219_DIGITAL_GAIN_DEFAULT}, + {IMX219_CCI_ANALOG_GAIN, IMX219_ANALOG_GAIN_DEFAULT}, + {IMX219_CCI_INTEGRATION_TIME, IMX219_INTEGRATION_TIME_DEFAULT}, + {IMX219_CCI_ORIENTATION, 0x03}, +}; + +static const struct video_reg16 imx219_fmt_raw8_regs[] = { + {IMX219_REG_CSI_DATA_FORMAT_A0, 8}, + {IMX219_REG_CSI_DATA_FORMAT_A1, 8}, +}; + +static const struct video_reg16 imx219_fmt_raw10_regs[] = { + {IMX219_REG_CSI_DATA_FORMAT_A0, 10}, + {IMX219_REG_CSI_DATA_FORMAT_A1, 10}, +}; + +/* TODO the FPS registers are currently tuned for 1920x1080 cropped resolution */ + +static const struct video_reg imx219_fps_30_regs[] = { + {IMX219_CCI_PREPLLCK_VT_DIV, 0x03}, /* Auto */ + {IMX219_CCI_PREPLLCK_OP_DIV, 0x03}, /* Auto */ + {IMX219_CCI_VTPXCK_DIV, 4}, /* Video Timing clock multiplier */ + {IMX219_CCI_VTSYCK_DIV, 1}, + {IMX219_CCI_OPPXCK_DIV, 10}, /* Output pixel clock divider */ + {IMX219_CCI_OPSYCK_DIV, 1}, + {IMX219_CCI_PLL_VT_MPY, 30}, /* Video Timing clock multiplier */ + {IMX219_CCI_PLL_OP_MPY, 50}, /* Output clock multiplier */ +}; + +static const struct video_reg imx219_fps_15_regs[] = { + {IMX219_CCI_PREPLLCK_VT_DIV, 0x03}, /* Auto */ + {IMX219_CCI_PREPLLCK_OP_DIV, 0x03}, /* Auto */ + {IMX219_CCI_VTPXCK_DIV, 4}, /* Video Timing clock multiplier */ + {IMX219_CCI_VTSYCK_DIV, 1}, + {IMX219_CCI_OPPXCK_DIV, 10}, /* Output pixel clock divider */ + {IMX219_CCI_OPSYCK_DIV, 1}, + {IMX219_CCI_PLL_VT_MPY, 15}, /* Video Timing clock multiplier */ + {IMX219_CCI_PLL_OP_MPY, 50}, /* Output clock multiplier */ +}; + +enum { + IMX219_RAW8_FULL_FRAME, + IMX219_RAW10_FULL_FRAME, +}; + +/* TODO switch to video_set_selection() API for cropping instead */ +static const struct video_format_cap imx219_fmts[] = { + [IMX219_RAW8_FULL_FRAME] = { + .pixelformat = VIDEO_PIX_FMT_SBGGR8, + .width_min = 4, .width_max = IMX219_FULL_WIDTH, .width_step = 4, + .height_min = 4, .height_max = IMX219_FULL_HEIGHT, .height_step = 4, + }, + [IMX219_RAW10_FULL_FRAME] = { + .pixelformat = VIDEO_PIX_FMT_SBGGR10P, + .width_min = 4, .width_max = IMX219_FULL_WIDTH, .width_step = 4, + .height_min = 4, .height_max = IMX219_FULL_HEIGHT, .height_step = 4, + }, + {0}, +}; + +static int imx219_set_fmt(const struct device *dev, struct video_format *fmt) +{ + const struct imx219_config *cfg = dev->config; + struct imx219_data *drv_data = dev->data; + struct video_reg crop_regs[] = { + /* Image crop size */ + {IMX219_CCI_X_ADD_STA_A, (IMX219_FULL_WIDTH - fmt->width) / 2}, + {IMX219_CCI_X_ADD_END_A, (IMX219_FULL_WIDTH + fmt->width) / 2 - 1}, + {IMX219_CCI_Y_ADD_STA_A, (IMX219_FULL_HEIGHT - fmt->height) / 2}, + {IMX219_CCI_Y_ADD_END_A, (IMX219_FULL_HEIGHT + fmt->height) / 2 - 1}, + + /* Update the output resolution */ + {IMX219_CCI_X_OUTPUT_SIZE, fmt->width}, + {IMX219_CCI_Y_OUTPUT_SIZE, fmt->height}, + + /* Make sure the mipi line is long enough for the new output size */ + {IMX219_CCI_FRM_LENGTH_A, fmt->height + 20}, + + /* Test pattern size */ + {IMX219_CCI_TP_WINDOW_WIDTH, fmt->width}, + {IMX219_CCI_TP_WINDOW_HEIGHT, fmt->height} + }; + size_t idx; + int ret; + + ret = video_format_caps_index(imx219_fmts, fmt, &idx); + if (ret < 0) { + LOG_ERR("Format requested '%s' %ux%u not supported", + VIDEO_FOURCC_TO_STR(fmt->pixelformat), fmt->width, fmt->height); + return -ENOTSUP; + } + + if (fmt->width != imx219_fmts[idx].width_min && + (fmt->width - imx219_fmts[idx].width_min) % imx219_fmts[idx].width_step != 0) { + LOG_ERR("Unsupported width %u requested", fmt->width); + return -EINVAL; + } + + if (fmt->height != imx219_fmts[idx].height_min && + (fmt->height - imx219_fmts[idx].height_min) % imx219_fmts[idx].height_step != 0) { + LOG_ERR("Unsupported height %u requested", fmt->height); + return -EINVAL; + } + + /* Select the base resolution and imaging mode */ + switch (idx) { + case IMX219_RAW8_FULL_FRAME: + ret = video_write_cci_multiregs16(&cfg->i2c, imx219_fmt_raw8_regs, + ARRAY_SIZE(imx219_fmt_raw8_regs)); + break; + case IMX219_RAW10_FULL_FRAME: + ret = video_write_cci_multiregs16(&cfg->i2c, imx219_fmt_raw10_regs, + ARRAY_SIZE(imx219_fmt_raw10_regs)); + break; + default: + CODE_UNREACHABLE; + return -EINVAL; + } + if (ret < 0) { + return ret; + } + + /* Apply a crop window on top of it */ + ret = video_write_cci_multiregs(&cfg->i2c, crop_regs, ARRAY_SIZE(crop_regs)); + if (ret < 0) { + return ret; + } + + drv_data->fmt = *fmt; + + return 0; +} + +static int imx219_get_fmt(const struct device *dev, struct video_format *fmt) +{ + struct imx219_data *drv_data = dev->data; + + *fmt = drv_data->fmt; + + return 0; +} + +static int imx219_get_caps(const struct device *dev, struct video_caps *caps) +{ + if (caps->type != VIDEO_BUF_TYPE_OUTPUT) { + LOG_ERR("Only output buffers supported"); + return -EINVAL; + } + + caps->min_line_count = LINE_COUNT_HEIGHT; + caps->max_line_count = LINE_COUNT_HEIGHT; + caps->format_caps = imx219_fmts; + + return 0; +} + +enum { + IMX219_FRMIVAL_30FPS, + IMX219_FRMIVAL_15FPS, +}; + +static int imx219_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) +{ + fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE; + + switch (fie->index) { + case IMX219_FRMIVAL_30FPS: + fie->discrete.numerator = 1; + fie->discrete.denominator = 30; + break; + case IMX219_FRMIVAL_15FPS: + fie->discrete.numerator = 1; + fie->discrete.denominator = 15; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int imx219_set_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct imx219_config *cfg = dev->config; + struct imx219_data *drv_data = dev->data; + struct video_frmival_enum fie = { + .discrete = *frmival, + .type = VIDEO_FRMIVAL_TYPE_DISCRETE, + .format = &drv_data->fmt, + }; + int ret; + + video_closest_frmival(dev, &fie); + + switch (fie.index) { + case IMX219_FRMIVAL_30FPS: + ret = video_write_cci_multiregs(&cfg->i2c, imx219_fps_30_regs, + ARRAY_SIZE(imx219_fps_30_regs)); + frmival->numerator = 1; + frmival->denominator = drv_data->fps = 30; + break; + case IMX219_FRMIVAL_15FPS: + ret = video_write_cci_multiregs(&cfg->i2c, imx219_fps_15_regs, + ARRAY_SIZE(imx219_fps_15_regs)); + frmival->numerator = 1; + frmival->denominator = drv_data->fps = 15; + break; + default: + CODE_UNREACHABLE; + return -EINVAL; + } + + return ret; +} + +static int imx219_get_frmival(const struct device *dev, struct video_frmival *frmival) +{ + struct imx219_data *drv_data = dev->data; + + frmival->numerator = 1; + frmival->denominator = drv_data->fps; + + return 0; +} + +static int imx219_set_stream(const struct device *dev, bool on, enum video_buf_type type) +{ + const struct imx219_config *cfg = dev->config; + + if (type != VIDEO_BUF_TYPE_OUTPUT) { + LOG_ERR("Only output buffers supported"); + return -EINVAL; + } + + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_MODE_SELECT, + on ? IMX219_MODE_SELECT_STREAMING : IMX219_MODE_SELECT_STANDBY); +} + +static int imx219_set_ctrl(const struct device *dev, unsigned int cid) +{ + const struct imx219_config *cfg = dev->config; + struct imx219_data *drv_data = dev->data; + struct imx219_ctrls *ctrls = &drv_data->ctrls; + + switch (cid) { + case VIDEO_CID_EXPOSURE: + /* Values for normal frame rate, different range for low frame rate mode */ + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_INTEGRATION_TIME, + ctrls->exposure.val); + case VIDEO_CID_ANALOGUE_GAIN: + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_ANALOG_GAIN, + ctrls->analogue_gain.val); + case VIDEO_CID_GAIN: + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_DIGITAL_GAIN, + ctrls->digital_gain.val); + case VIDEO_CID_BRIGHTNESS: + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_DT_PEDESTAL, + ctrls->brightness.val); + case VIDEO_CID_TEST_PATTERN: + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_TESTPATTERN, + ctrls->test_pattern.val); + default: + LOG_WRN("Control not supported"); + return -ENOTSUP; + } +} + +static const DEVICE_API(video, imx219_driver_api) = { + .set_stream = imx219_set_stream, + .set_ctrl = imx219_set_ctrl, + .set_format = imx219_set_fmt, + .get_format = imx219_get_fmt, + .get_caps = imx219_get_caps, + .set_frmival = imx219_set_frmival, + .get_frmival = imx219_get_frmival, + .enum_frmival = imx219_enum_frmival, +}; + +static const char *const imx219_test_pattern_menu[] = { + "Off", + "Solid color", + "100% color bars", + "Fade to grey color bar", + "PN9", + "16 split color bar", + "16 split inverted color bar", + "Column counter", + "Inverted column counter", + "PN31", + NULL, +}; + +static int imx219_init_ctrls(const struct device *dev) +{ + struct imx219_data *drv_data = dev->data; + struct imx219_ctrls *ctrls = &drv_data->ctrls; + int ret; + + ret = video_init_ctrl( + &ctrls->exposure, dev, VIDEO_CID_EXPOSURE, + (struct video_ctrl_range){.min = 0x0000, .max = 0xffff, .step = 1, + .def = IMX219_INTEGRATION_TIME_DEFAULT}); + if (ret < 0) { + return ret; + } + + ret = video_init_ctrl( + &ctrls->brightness, dev, VIDEO_CID_BRIGHTNESS, + (struct video_ctrl_range){.min = 0x00, .max = 0x3ff, .step = 1, + .def = IMX219_DT_PEDESTAL_DEFAULT}); + if (ret < 0) { + return ret; + } + + ret = video_init_ctrl( + &ctrls->analogue_gain, dev, VIDEO_CID_ANALOGUE_GAIN, + (struct video_ctrl_range){.min = 0x00, .max = 0xff, .step = 1, + .def = IMX219_ANALOG_GAIN_DEFAULT}); + if (ret < 0) { + return ret; + } + + ret = video_init_ctrl( + &ctrls->digital_gain, dev, VIDEO_CID_DIGITAL_GAIN, + (struct video_ctrl_range){.min = 0x000, .max = 0xfff, .step = 1, + .def = IMX219_DIGITAL_GAIN_DEFAULT}); + if (ret < 0) { + return ret; + } + + ret = video_init_int_menu_ctrl(&ctrls->linkfreq, dev, VIDEO_CID_LINK_FREQ, + 0, imx219_2dl_link_frequency, + ARRAY_SIZE(imx219_2dl_link_frequency)); + if (ret < 0) { + return ret; + } + + ctrls->linkfreq.flags |= VIDEO_CTRL_FLAG_READ_ONLY; + + return video_init_menu_ctrl(&ctrls->test_pattern, dev, VIDEO_CID_TEST_PATTERN, 0, + imx219_test_pattern_menu); +} + +static int imx219_set_input_clk(const struct device *dev, uint32_t rate_hz) +{ + const struct imx219_config *cfg = dev->config; + + if (rate_hz < MHZ(6) || rate_hz > MHZ(27) || rate_hz % MHZ(1) != 0) { + LOG_ERR("Unsupported INCK freq (%d Hz)\n", rate_hz); + return -EINVAL; + } + + return video_write_cci_reg(&cfg->i2c, IMX219_CCI_EXCK_FREQ, (rate_hz / MHZ(1)) << 8); +} + +static int imx219_init(const struct device *dev) +{ + const struct imx219_config *cfg = dev->config; + struct video_format fmt = { + .width = imx219_fmts[0].width_min, + .height = imx219_fmts[0].height_min, + .pixelformat = imx219_fmts[0].pixelformat, + }; + struct video_frmival frmival = { + .numerator = 1, + .denominator = 15, + }; + uint32_t reg; + int ret; + + if (!device_is_ready(cfg->i2c.bus)) { + LOG_ERR("I2C device %s is not ready", cfg->i2c.bus->name); + return -ENODEV; + } + + k_sleep(K_MSEC(1)); + + ret = video_write_cci_reg(&cfg->i2c, IMX219_CCI_SOFTWARE_RESET, 1); + if (ret < 0) { + return ret; + } + + k_sleep(K_MSEC(6)); /* t5 */ + + ret = video_read_cci_reg(&cfg->i2c, IMX219_CCI_CHIP_ID, ®); + if (ret < 0) { + return ret; + } + + if (reg != IMX219_CHIP_ID) { + LOG_ERR("Wrong chip ID 0x%04x instead of 0x%04x", reg, IMX219_CHIP_ID); + return -ENODEV; + } + + ret = imx219_set_input_clk(dev, cfg->input_clk_hz); + if (ret < 0) { + return ret; + } + + ret = video_write_cci_multiregs16(&cfg->i2c, imx219_vendor_regs, + ARRAY_SIZE(imx219_vendor_regs)); + if (ret < 0) { + return ret; + } + + ret = video_write_cci_multiregs(&cfg->i2c, imx219_init_regs, + ARRAY_SIZE(imx219_init_regs)); + if (ret < 0) { + return ret; + } + + ret = imx219_set_fmt(dev, &fmt); + if (ret < 0) { + return ret; + } + + ret = imx219_set_frmival(dev, &frmival); + if (ret < 0) { + return ret; + } + + return imx219_init_ctrls(dev); +} + +#define IMX219_INIT(n) \ + static struct imx219_data imx219_data_##n; \ + \ + static const struct imx219_config imx219_cfg_##n = { \ + .i2c = I2C_DT_SPEC_INST_GET(n), \ + .input_clk_hz = DT_INST_PROP_BY_PHANDLE(n, clocks, clock_frequency), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &imx219_init, NULL, &imx219_data_##n, &imx219_cfg_##n, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &imx219_driver_api); \ + \ + VIDEO_DEVICE_DEFINE(imx219_##n, DEVICE_DT_INST_GET(n), NULL); + +DT_INST_FOREACH_STATUS_OKAY(IMX219_INIT) diff --git a/drivers/video/video_ctrls.c b/drivers/video/video_ctrls.c index cba92100967c..54999678bd76 100644 --- a/drivers/video/video_ctrls.c +++ b/drivers/video/video_ctrls.c @@ -544,6 +544,8 @@ static inline const char *video_get_ctrl_name(uint32_t id) return "Test Pattern"; case VIDEO_CID_LINK_FREQ: return "Link Frequency"; + case VIDEO_CID_DIGITAL_GAIN: + return "Digital Gain"; default: return NULL; } diff --git a/dts/bindings/video/sony,imx219.yaml b/dts/bindings/video/sony,imx219.yaml new file mode 100644 index 000000000000..f0145200df87 --- /dev/null +++ b/dts/bindings/video/sony,imx219.yaml @@ -0,0 +1,9 @@ +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-controls.h b/include/zephyr/drivers/video-controls.h index 21c173c45980..997bc2c8173b 100644 --- a/include/zephyr/drivers/video-controls.h +++ b/include/zephyr/drivers/video-controls.h @@ -349,7 +349,10 @@ enum video_camera_orientation { */ #define VIDEO_CID_IMAGE_SOURCE_CLASS_BASE 0x009e0900 -/** Analogue gain control. */ +/** Analogue gain is gain affecting all colour components in the pixel + * matrix. The gain operation is performed in the analogue domain + * before A/D conversion. + */ #define VIDEO_CID_ANALOGUE_GAIN (VIDEO_CID_IMAGE_SOURCE_CLASS_BASE + 3) /** @@ -371,6 +374,14 @@ enum video_camera_orientation { /** Selection of the type of test pattern to represent */ #define VIDEO_CID_TEST_PATTERN (VIDEO_CID_IMAGE_PROC_CLASS_BASE + 3) +/** Digital gain is the value by which all colour components + * are multiplied by. Typically the digital gain applied is the + * control value divided by e.g. 0x100, meaning that to get no + * digital gain the control value needs to be 0x100. The no-gain + * configuration is also typically the default. + */ +#define VIDEO_CID_DIGITAL_GAIN (VIDEO_CID_IMAGE_PROC_CLASS_BASE + 5) + /** * @} */ diff --git a/tests/drivers/build_all/video/app.overlay b/tests/drivers/build_all/video/app.overlay index 2d15d8fcbfc7..29dc3c3a7c10 100644 --- a/tests/drivers/build_all/video/app.overlay +++ b/tests/drivers/build_all/video/app.overlay @@ -17,6 +17,12 @@ #clock-cells = <0>; }; + imx219_input_clock: imx219-input-clock { + compatible = "fixed-clock"; + clock-frequency = <24000000>; + #clock-cells = <0>; + }; + test { #address-cells = <1>; #size-cells = <1>; @@ -127,11 +133,17 @@ }; }; - test_i2c_ov9655: ov9655@8 { + test_i2c_ov9655: ov9655@9 { compatible = "ovti,ov9655"; - reg = <0x8>; + reg = <0x9>; reset-gpios = <&test_gpio 0 0>; }; + + test_i2c_imx219: imx219@a { + compatible = "sony,imx219"; + reg = <0xa>; + clocks = <&imx219_input_clock>; + }; }; test_video_emul_rx: video_emul_rx@10003000 {