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 {