From cd8dccf211218c53fc6da7ee7ef8ad353ab1fdd0 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Thu, 24 Apr 2025 14:05:19 +0200 Subject: [PATCH] drivers: video: introduction of the stm32 DCMIPP driver The STM32 Digital Camera Memory Interface Pixel Processor (DCMIPP) is a multi-pipeline camera interface allowing to capture and process frames from parallel or CSI interfaces depending on its version. Signed-off-by: Alain Volmat --- drivers/video/CMakeLists.txt | 1 + drivers/video/Kconfig | 2 + drivers/video/Kconfig.stm32_dcmipp | 36 + drivers/video/video_stm32_dcmipp.c | 1321 +++++++++++++++++++ tests/drivers/build_all/video/testcase.yaml | 5 + 5 files changed, 1365 insertions(+) create mode 100644 drivers/video/Kconfig.stm32_dcmipp create mode 100644 drivers/video/video_stm32_dcmipp.c diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 16915aad340..083df586b00 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -24,5 +24,6 @@ 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_linker_sources(DATA_SECTIONS video.ld) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 22d755afc3a..924e2156273 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -94,4 +94,6 @@ source "drivers/video/Kconfig.imx335" source "drivers/video/Kconfig.st_mipid02" +source "drivers/video/Kconfig.stm32_dcmipp" + endif # VIDEO diff --git a/drivers/video/Kconfig.stm32_dcmipp b/drivers/video/Kconfig.stm32_dcmipp new file mode 100644 index 00000000000..329babde6e6 --- /dev/null +++ b/drivers/video/Kconfig.stm32_dcmipp @@ -0,0 +1,36 @@ +# STM32 DCMIPP driver configuration options + +# Copyright (c) 2025 STMicroelectronics. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_STM32_DCMIPP + bool "STM32 Digital Camera Memory Interface Pixel Processor (DCMIPP) driver" + default y + depends on DT_HAS_ST_STM32_DCMIPP_ENABLED + select USE_STM32_HAL_DCMIPP + select USE_STM32_HAL_RIF if SOC_SERIES_STM32N6X + help + Enable driver for STM32 Digital Camera Memory Interface Pixel Processor + (DCMIPP) peripheral + +if VIDEO_STM32_DCMIPP + +config VIDEO_STM32_DCMIPP_SENSOR_WIDTH + int "Width of the sensor frame" + default 2592 + help + Width of the sensor video frame. + +config VIDEO_STM32_DCMIPP_SENSOR_HEIGHT + int "Height of the sensor frame" + default 1944 + help + Height of the sensor video frame. + +config VIDEO_STM32_DCMIPP_SENSOR_PIXEL_FORMAT + string "Pixel format of the sensor frame" + default "RG12" + help + Pixel format of the sensor video frame. + +endif diff --git a/drivers/video/video_stm32_dcmipp.c b/drivers/video/video_stm32_dcmipp.c new file mode 100644 index 00000000000..e04723fdb1e --- /dev/null +++ b/drivers/video/video_stm32_dcmipp.c @@ -0,0 +1,1321 @@ +/* + * Copyright (c) 2025 STMicroelectronics. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "video_ctrls.h" +#include "video_device.h" + +#define DT_DRV_COMPAT st_stm32_dcmipp + +/* On STM32MP13X HAL, below two functions have different names */ +#if defined(CONFIG_SOC_SERIES_STM32MP13X) +#define HAL_DCMIPP_PIPE_SetConfig HAL_DCMIPP_PIPE_Config +#define HAL_DCMIPP_PARALLEL_SetConfig HAL_DCMIPP_SetParallelConfig +#endif + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32n6_dcmipp) +#define STM32_DCMIPP_HAS_CSI +#define STM32_DCMIPP_HAS_PIXEL_PIPES +#endif + +LOG_MODULE_REGISTER(stm32_dcmipp, CONFIG_VIDEO_LOG_LEVEL); + +typedef void (*irq_config_func_t)(const struct device *dev); + +enum stm32_dcmipp_state { + STM32_DCMIPP_STOPPED = 0, + STM32_DCMIPP_WAIT_FOR_BUFFER, + STM32_DCMIPP_RUNNING, +}; + +#define STM32_DCMIPP_MAX_PIPE_NB 3 + +#define STM32_DCMIPP_MAX_ISP_DEC_FACTOR 8 +#define STM32_DCMIPP_MAX_PIPE_DEC_FACTOR 8 +#define STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR 8 +#define STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR (STM32_DCMIPP_MAX_PIPE_DEC_FACTOR * \ + STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR) +struct stm32_dcmipp_pipe_data { + const struct device *dev; + uint32_t id; + struct stm32_dcmipp_data *dcmipp; + struct k_mutex lock; + struct video_format fmt; + struct k_fifo fifo_in; + struct k_fifo fifo_out; + struct video_buffer *next; + struct video_buffer *active; + enum stm32_dcmipp_state state; + bool is_streaming; +}; + +struct stm32_dcmipp_data { + const struct device *dev; + DCMIPP_HandleTypeDef hdcmipp; + + /* FIXME - there should be a mutex to protect enabled_pipe */ + unsigned int enabled_pipe; + struct video_format source_fmt; + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + /* Store the ISP decimation block ratio */ + int32_t isp_dec_hratio; + int32_t isp_dec_vratio; +#endif + + struct stm32_dcmipp_pipe_data *pipe[STM32_DCMIPP_MAX_PIPE_NB]; +}; + +struct stm32_dcmipp_config { + const struct stm32_pclken dcmipp_pclken; + const struct stm32_pclken dcmipp_pclken_ker; + irq_config_func_t irq_config; + const struct pinctrl_dev_config *pctrl; + const struct device *source_dev; + const struct reset_dt_spec reset_dcmipp; + int bus_type; +#if defined(STM32_DCMIPP_HAS_CSI) + const struct stm32_pclken csi_pclken; + const struct reset_dt_spec reset_csi; + struct { + uint32_t nb_lanes; + uint8_t lanes[2]; + } csi; +#endif + struct { + uint32_t vs_polarity; + uint32_t hs_polarity; + uint32_t pck_polarity; + } parallel; +}; + +#define STM32_DCMIPP_WIDTH_MIN 16 +#define STM32_DCMIPP_HEIGHT_MIN 2 +#define STM32_DCMIPP_WIDTH_MAX 4094 +#define STM32_DCMIPP_HEIGHT_MAX 4094 + +/* Callback getting called for each frame written into memory */ +void HAL_DCMIPP_PIPE_FrameEventCallback(DCMIPP_HandleTypeDef *hdcmipp, uint32_t Pipe) +{ + struct stm32_dcmipp_data *dcmipp = + CONTAINER_OF(hdcmipp, struct stm32_dcmipp_data, hdcmipp); + struct stm32_dcmipp_pipe_data *pipe = dcmipp->pipe[Pipe]; + uint32_t bytesused; + int ret; + + __ASSERT(pipe->active, "Unexpected behavior, active_buf must not be NULL"); + + /* Counter is only available on Pipe0 */ + if (Pipe == DCMIPP_PIPE0) { + ret = HAL_DCMIPP_PIPE_GetDataCounter(hdcmipp, Pipe, &bytesused); + if (ret != HAL_OK) { + LOG_WRN("Failed to read counter - buffer in error"); + pipe->active->bytesused = 0; + } else { + pipe->active->bytesused = bytesused; + } + } else { + pipe->active->bytesused = pipe->fmt.height * pipe->fmt.pitch; + } + + pipe->active->timestamp = k_uptime_get_32(); + pipe->active->line_offset = 0; + + k_fifo_put(&pipe->fifo_out, pipe->active); + pipe->active = NULL; +} + +/* Callback getting called for each vsync */ +void HAL_DCMIPP_PIPE_VsyncEventCallback(DCMIPP_HandleTypeDef *hdcmipp, uint32_t Pipe) +{ + struct stm32_dcmipp_data *dcmipp = + CONTAINER_OF(hdcmipp, struct stm32_dcmipp_data, hdcmipp); + struct stm32_dcmipp_pipe_data *pipe = dcmipp->pipe[Pipe]; + int ret; + + if (pipe->state != STM32_DCMIPP_RUNNING) { + return; + } + + pipe->active = pipe->next; + + pipe->next = k_fifo_get(&pipe->fifo_in, K_NO_WAIT); + if (pipe->next == NULL) { + LOG_DBG("No buffer available, pause streaming"); + /* + * Disable Capture Request + * Use direct register access here since we don't want to wait until + * getting CPTACT down here since we still need to handle other stuff + */ + pipe->state = STM32_DCMIPP_WAIT_FOR_BUFFER; + if (Pipe == DCMIPP_PIPE0) { + CLEAR_BIT(hdcmipp->Instance->P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + } +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + else if (Pipe == DCMIPP_PIPE1) { + CLEAR_BIT(hdcmipp->Instance->P1FCTCR, DCMIPP_P1FCTCR_CPTREQ); + } else if (Pipe == DCMIPP_PIPE2) { + CLEAR_BIT(hdcmipp->Instance->P2FCTCR, DCMIPP_P2FCTCR_CPTREQ); + } +#endif + return; + } + + /* + * TODO - we only support 1 buffer formats for the time being, setting of + * MEMORY_ADDRESS_1 and MEMORY_ADDRESS_2 required depending on the pixelformat + * for Pipe1 + */ + ret = HAL_DCMIPP_PIPE_SetMemoryAddress(&dcmipp->hdcmipp, Pipe, DCMIPP_MEMORY_ADDRESS_0, + (uint32_t)pipe->next->buffer); + if (ret != HAL_OK) { + LOG_ERR("Failed to update memory address"); + return; + } +} + +#if defined(STM32_DCMIPP_HAS_CSI) +#define INPUT_FMT(fmt, dcmipp_fmt, bpp, dt) \ + { \ + .pixelformat = VIDEO_PIX_FMT_##fmt, \ + .dcmipp_format = DCMIPP_FORMAT_##dcmipp_fmt, \ + .dcmipp_csi_bpp = DCMIPP_CSI_DT_BPP##bpp, \ + .dcmipp_csi_dt = DCMIPP_DT_##dt, \ + } +#else +#define INPUT_FMT(fmt, dcmipp_fmt, bpp, dt) \ + { \ + .pixelformat = VIDEO_PIX_FMT_##fmt, \ + .dcmipp_format = DCMIPP_FORMAT_##dcmipp_fmt, \ + } +#endif + +/* DCMIPP input format descriptions */ +static const struct stm32_dcmipp_input_fmt { + uint32_t pixelformat; + uint32_t dcmipp_format; +#if defined(STM32_DCMIPP_HAS_CSI) + uint32_t dcmipp_csi_bpp; + uint32_t dcmipp_csi_dt; +#endif +} stm32_dcmipp_input_fmt_desc[] = { + INPUT_FMT(SBGGR8, RAW8, 8, RAW8), INPUT_FMT(SGBRG8, RAW8, 8, RAW8), + INPUT_FMT(SGRBG8, RAW8, 8, RAW8), INPUT_FMT(SRGGB8, RAW8, 8, RAW8), + INPUT_FMT(SBGGR10P, RAW10, 10, RAW10), INPUT_FMT(SGBRG10P, RAW10, 10, RAW10), + INPUT_FMT(SGRBG10P, RAW10, 10, RAW10), INPUT_FMT(SRGGB10P, RAW10, 10, RAW10), + INPUT_FMT(SBGGR12P, RAW12, 12, RAW12), INPUT_FMT(SGBRG12P, RAW12, 12, RAW12), + INPUT_FMT(SGRBG12P, RAW12, 12, RAW12), INPUT_FMT(SRGGB12P, RAW12, 12, RAW12), + INPUT_FMT(SBGGR14P, RAW14, 14, RAW14), INPUT_FMT(SGBRG14P, RAW14, 14, RAW14), + INPUT_FMT(SGRBG14P, RAW14, 14, RAW14), INPUT_FMT(SRGGB14P, RAW14, 14, RAW14), + INPUT_FMT(RGB565, RGB565, 8, RGB565), + INPUT_FMT(YUYV, YUV422, 8, YUV422_8), +}; + +static const struct stm32_dcmipp_input_fmt *stm32_dcmipp_get_input_info(uint32_t pixelformat) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(stm32_dcmipp_input_fmt_desc); i++) { + if (pixelformat == stm32_dcmipp_input_fmt_desc[i].pixelformat) { + return &stm32_dcmipp_input_fmt_desc[i]; + } + } + + return NULL; +} + +static int stm32_dcmipp_conf_parallel(const struct device *dev, + const struct stm32_dcmipp_input_fmt *input_fmt) +{ + struct stm32_dcmipp_data *dcmipp = dev->data; + const struct stm32_dcmipp_config *config = dev->config; + DCMIPP_ParallelConfTypeDef parallel_cfg = { 0 }; + int ret; + + parallel_cfg.Format = input_fmt->dcmipp_format; + parallel_cfg.SwapCycles = DCMIPP_SWAPCYCLES_DISABLE; + parallel_cfg.VSPolarity = config->parallel.vs_polarity; + parallel_cfg.HSPolarity = config->parallel.hs_polarity; + parallel_cfg.PCKPolarity = config->parallel.pck_polarity; + parallel_cfg.ExtendedDataMode = DCMIPP_INTERFACE_8BITS; + parallel_cfg.SynchroMode = DCMIPP_SYNCHRO_HARDWARE; + + ret = HAL_DCMIPP_PARALLEL_SetConfig(&dcmipp->hdcmipp, ¶llel_cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to configure DCMIPP Parallel interface"); + return -EIO; + } + + return 0; +} + +#if defined(STM32_DCMIPP_HAS_CSI) +#define DCMIPP_PHY_BITRATE(val) \ + { \ + .rate = val, \ + .PHYBitrate = DCMIPP_CSI_PHY_BT_##val, \ + } +static const struct { + uint16_t rate; + uint16_t PHYBitrate; +} stm32_dcmipp_bitrate[] = { + DCMIPP_PHY_BITRATE(80), DCMIPP_PHY_BITRATE(90), DCMIPP_PHY_BITRATE(100), + DCMIPP_PHY_BITRATE(110), DCMIPP_PHY_BITRATE(120), DCMIPP_PHY_BITRATE(130), + DCMIPP_PHY_BITRATE(140), DCMIPP_PHY_BITRATE(150), DCMIPP_PHY_BITRATE(160), + DCMIPP_PHY_BITRATE(170), DCMIPP_PHY_BITRATE(180), DCMIPP_PHY_BITRATE(190), + DCMIPP_PHY_BITRATE(205), DCMIPP_PHY_BITRATE(220), DCMIPP_PHY_BITRATE(235), + DCMIPP_PHY_BITRATE(250), DCMIPP_PHY_BITRATE(275), DCMIPP_PHY_BITRATE(300), + DCMIPP_PHY_BITRATE(325), DCMIPP_PHY_BITRATE(350), DCMIPP_PHY_BITRATE(400), + DCMIPP_PHY_BITRATE(450), DCMIPP_PHY_BITRATE(500), DCMIPP_PHY_BITRATE(550), + DCMIPP_PHY_BITRATE(600), DCMIPP_PHY_BITRATE(650), DCMIPP_PHY_BITRATE(700), + DCMIPP_PHY_BITRATE(750), DCMIPP_PHY_BITRATE(800), DCMIPP_PHY_BITRATE(850), + DCMIPP_PHY_BITRATE(900), DCMIPP_PHY_BITRATE(950), DCMIPP_PHY_BITRATE(1000), + DCMIPP_PHY_BITRATE(1050), DCMIPP_PHY_BITRATE(1100), DCMIPP_PHY_BITRATE(1150), + DCMIPP_PHY_BITRATE(1200), DCMIPP_PHY_BITRATE(1250), DCMIPP_PHY_BITRATE(1300), + DCMIPP_PHY_BITRATE(1350), DCMIPP_PHY_BITRATE(1400), DCMIPP_PHY_BITRATE(1450), + DCMIPP_PHY_BITRATE(1500), DCMIPP_PHY_BITRATE(1550), DCMIPP_PHY_BITRATE(1600), + DCMIPP_PHY_BITRATE(1650), DCMIPP_PHY_BITRATE(1700), DCMIPP_PHY_BITRATE(1750), + DCMIPP_PHY_BITRATE(1800), DCMIPP_PHY_BITRATE(1850), DCMIPP_PHY_BITRATE(1900), + DCMIPP_PHY_BITRATE(1950), DCMIPP_PHY_BITRATE(2000), DCMIPP_PHY_BITRATE(2050), + DCMIPP_PHY_BITRATE(2100), DCMIPP_PHY_BITRATE(2150), DCMIPP_PHY_BITRATE(2200), + DCMIPP_PHY_BITRATE(2250), DCMIPP_PHY_BITRATE(2300), DCMIPP_PHY_BITRATE(2350), + DCMIPP_PHY_BITRATE(2400), DCMIPP_PHY_BITRATE(2450), DCMIPP_PHY_BITRATE(2500), +}; + +static int stm32_dcmipp_conf_csi(const struct device *dev, uint32_t dcmipp_csi_bpp) +{ + const struct stm32_dcmipp_config *config = dev->config; + struct stm32_dcmipp_data *dcmipp = dev->data; + DCMIPP_CSI_ConfTypeDef csiconf = { 0 }; + uint64_t phy_bitrate; + struct video_control ctrl = { + .id = VIDEO_CID_PIXEL_RATE, + }; + int err, i; + + csiconf.NumberOfLanes = config->csi.nb_lanes == 2 ? DCMIPP_CSI_TWO_DATA_LANES : + DCMIPP_CSI_ONE_DATA_LANE; + csiconf.DataLaneMapping = config->csi.lanes[0] == 1 ? DCMIPP_CSI_PHYSICAL_DATA_LANES : + DCMIPP_CSI_INVERTED_DATA_LANES; + + /* Get the pixel_rate from the source to guess the bitrate */ + err = video_get_ctrl(config->source_dev, &ctrl); + if (err < 0) { + LOG_ERR("Can not get source_dev pixel rate"); + return err; + } + phy_bitrate = ctrl.val64 * video_bits_per_pixel(dcmipp->source_fmt.pixelformat) / + (2 * config->csi.nb_lanes); + phy_bitrate /= MHZ(1); + LOG_DBG("Sensor PixelRate = %lld, PHY Bitrate computed = %lld MHz", + ctrl.val64, phy_bitrate); + + for (i = 0; i < ARRAY_SIZE(stm32_dcmipp_bitrate); i++) { + if (stm32_dcmipp_bitrate[i].rate >= phy_bitrate) { + break; + } + } + if (i == ARRAY_SIZE(stm32_dcmipp_bitrate)) { + LOG_ERR("Couldn't find proper CSI PHY settings for bitrate %lld", phy_bitrate); + return -EINVAL; + } + csiconf.PHYBitrate = stm32_dcmipp_bitrate[i].PHYBitrate; + + err = HAL_DCMIPP_CSI_SetConfig(&dcmipp->hdcmipp, &csiconf); + if (err != HAL_OK) { + LOG_ERR("Failed to configure DCMIPP CSI"); + return -EIO; + } + + /* Set Virtual Channel config */ + /* TODO - need to be able to use an alternate VC, info coming from the source */ + err = HAL_DCMIPP_CSI_SetVCConfig(&dcmipp->hdcmipp, DCMIPP_VIRTUAL_CHANNEL0, + dcmipp_csi_bpp); + if (err != HAL_OK) { + LOG_ERR("Failed to set CSI configuration"); + return -EIO; + } + + return 0; +} +#endif + +/* Description of DCMIPP inputs */ +#define DUMP_PIPE_FMT(fmt, input_fmt) \ + { \ + .pixelformat = VIDEO_PIX_FMT_##fmt, \ + .pipes = BIT(0), \ + .dump.input_pixelformat = VIDEO_PIX_FMT_##input_fmt, \ + } +#define RAW_BAYER_UNPACKED(size) \ + DUMP_PIPE_FMT(SBGGR##size, SBGGR##size), \ + DUMP_PIPE_FMT(SGBRG##size, SGBRG##size), \ + DUMP_PIPE_FMT(SGRBG##size, SGRBG##size), \ + DUMP_PIPE_FMT(SRGGB##size, SRGGB##size) + +#define RAW_BAYER_PACKED(size) \ + DUMP_PIPE_FMT(SBGGR##size##P, SBGGR##size), \ + DUMP_PIPE_FMT(SGBRG##size##P, SGBRG##size), \ + DUMP_PIPE_FMT(SGRBG##size##P, SGRBG##size), \ + DUMP_PIPE_FMT(SRGGB##size##P, SRGGB##size) + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) +/* Description of Pixel Pipes formats */ +#define PIXEL_PIPE_FMT(fmt, dcmipp, swap, valid_pipes) \ + { \ + .pixelformat = VIDEO_PIX_FMT_##fmt, \ + .pipes = valid_pipes, \ + .pixels.dcmipp_format = DCMIPP_PIXEL_PACKER_FORMAT_##dcmipp, \ + .pixels.swap_uv = swap, \ + } +#endif +static const struct stm32_dcmipp_mapping { + uint32_t pixelformat; + uint32_t pipes; + union { + struct { + uint32_t input_pixelformat; + } dump; + struct { + uint32_t dcmipp_format; + uint32_t swap_uv; + } pixels; + }; +} stm32_dcmipp_format_support[] = { + /* Dump pipe format descriptions */ + RAW_BAYER_UNPACKED(8), + RAW_BAYER_PACKED(10), RAW_BAYER_PACKED(12), RAW_BAYER_PACKED(14), + DUMP_PIPE_FMT(RGB565, RGB565), + DUMP_PIPE_FMT(YUYV, YUYV), +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + /* Pixel pipes format descriptions */ + PIXEL_PIPE_FMT(RGB565, RGB565_1, 0, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(YUYV, YUV422_1, 0, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(YVYU, YUV422_1, 1, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(GREY, MONO_Y8_G8_1, 0, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(RGB24, RGB888_YUV444_1, 1, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(BGR24, RGB888_YUV444_1, 0, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(ARGB32, RGBA888, 1, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(ABGR32, ARGB8888, 0, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(RGBA32, ARGB8888, 1, (BIT(1) | BIT(2))), + PIXEL_PIPE_FMT(BGRA32, RGBA888, 0, (BIT(1) | BIT(2))), + /* TODO - need to add the semiplanar & planar formats */ +#endif +}; + +/* + * FIXME: Helper to know colorspace of a format + * This below to the video_common part and should be moved there + */ +#define VIDEO_COLORSPACE_RAW 0 +#define VIDEO_COLORSPACE_RGB 1 +#define VIDEO_COLORSPACE_YUV 2 +#define VIDEO_COLORSPACE(fmt) \ + (((fmt) == VIDEO_PIX_FMT_RGB565X || (fmt) == VIDEO_PIX_FMT_RGB565 || \ + (fmt) == VIDEO_PIX_FMT_BGR24 || (fmt) == VIDEO_PIX_FMT_RGB24 || \ + (fmt) == VIDEO_PIX_FMT_ARGB32 || (fmt) == VIDEO_PIX_FMT_ABGR32 || \ + (fmt) == VIDEO_PIX_FMT_RGBA32 || (fmt) == VIDEO_PIX_FMT_BGRA32 || \ + (fmt) == VIDEO_PIX_FMT_XRGB32) ? VIDEO_COLORSPACE_RGB : \ + \ + ((fmt) == VIDEO_PIX_FMT_GREY || \ + (fmt) == VIDEO_PIX_FMT_YUYV || (fmt) == VIDEO_PIX_FMT_YVYU || \ + (fmt) == VIDEO_PIX_FMT_VYUY || (fmt) == VIDEO_PIX_FMT_UYVY || \ + (fmt) == VIDEO_PIX_FMT_XYUV32) ? VIDEO_COLORSPACE_YUV : \ + \ + VIDEO_COLORSPACE_RAW) + +static const struct stm32_dcmipp_mapping *stm32_dcmipp_get_mapping(uint32_t pixelformat, + uint32_t pipe) +{ + for (int i = 0; i < ARRAY_SIZE(stm32_dcmipp_format_support); i++) { + if (pixelformat == stm32_dcmipp_format_support[i].pixelformat && + BIT(pipe) & stm32_dcmipp_format_support[i].pipes) { + return &stm32_dcmipp_format_support[i]; + } + } + + return NULL; +} + +static int stm32_dcmipp_set_fmt(const struct device *dev, struct video_format *fmt) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + const struct stm32_dcmipp_mapping *mapping; + int ret = 0; + + /* Sanitize format given */ + mapping = stm32_dcmipp_get_mapping(fmt->pixelformat, pipe->id); + if (mapping == NULL) { + LOG_ERR("Pixelformat %s/0x%x is not supported", + VIDEO_FOURCC_TO_STR(fmt->pixelformat), fmt->pixelformat); + return -EINVAL; + } + + /* In case of DUMP pipe, verify that requested format matched with input format */ + if (pipe->id == DCMIPP_PIPE0 && + mapping->dump.input_pixelformat != dcmipp->source_fmt.pixelformat) { + LOG_ERR("Dump pipe output format (%s) incompatible with source format (%s)", + VIDEO_FOURCC_TO_STR(fmt->pixelformat), + VIDEO_FOURCC_TO_STR(dcmipp->source_fmt.pixelformat)); + return -EINVAL; + } + + if (!IN_RANGE(fmt->width, STM32_DCMIPP_WIDTH_MIN, STM32_DCMIPP_WIDTH_MAX) || + !IN_RANGE(fmt->height, STM32_DCMIPP_HEIGHT_MIN, STM32_DCMIPP_HEIGHT_MAX)) { + LOG_ERR("Format width/height (%d x %d) does not match caps", + fmt->width, fmt->height); + return -EINVAL; + } + + fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + /* On Pipe1 and Pipe2, the pitch must be multiple of 16 bytes */ + fmt->pitch = ROUND_UP(fmt->pitch, 16); + } +#endif + + k_mutex_lock(&pipe->lock, K_FOREVER); + + if (pipe->is_streaming) { + ret = -EBUSY; + goto out; + } + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + uint32_t post_isp_decimate_width = dcmipp->source_fmt.width / + dcmipp->isp_dec_hratio; + uint32_t post_isp_decimate_height = dcmipp->source_fmt.height / + dcmipp->isp_dec_vratio; + + if (!IN_RANGE(fmt->width, + post_isp_decimate_width / STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR, + post_isp_decimate_width) || + !IN_RANGE(fmt->height, + post_isp_decimate_height / STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR, + post_isp_decimate_height)) { + LOG_ERR("Requested resolution cannot be achieved"); + ret = -EINVAL; + goto out; + } + } +#endif + + pipe->fmt = *fmt; + +out: + k_mutex_unlock(&pipe->lock); + + return ret; +} + +static int stm32_dcmipp_get_fmt(const struct device *dev, struct video_format *fmt) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + + *fmt = pipe->fmt; + + return 0; +} + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) +#define STM32_DCMIPP_DSIZE_HVRATIO_CONS 8192 +#define STM32_DCMIPP_DSIZE_HVRATIO_MAX 65535 +#define STM32_DCMIPP_DSIZE_HVDIV_CONS 1024 +#define STM32_DCMIPP_DSIZE_HVDIV_MAX 1023 +static int stm32_dcmipp_set_downscale(struct stm32_dcmipp_pipe_data *pipe) +{ + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + uint32_t post_decimate_width = dcmipp->source_fmt.width / dcmipp->isp_dec_hratio; + uint32_t post_decimate_height = dcmipp->source_fmt.height / dcmipp->isp_dec_vratio; + DCMIPP_DecimationConfTypeDef dec_cfg; + DCMIPP_DownsizeTypeDef downsize_cfg; + struct video_format *fmt = &pipe->fmt; + uint32_t hdec = 1, vdec = 1; + int ret; + + if (fmt->width == post_decimate_width && fmt->height == post_decimate_height) { + ret = HAL_DCMIPP_PIPE_DisableDecimation(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable the pipe decimation"); + return -EIO; + } + + ret = HAL_DCMIPP_PIPE_DisableDownsize(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable the pipe downsize"); + return -EIO; + } + + return 0; + } + + /* Compute decimation factors (HDEC/VDEC) */ + while (fmt->width * STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR < post_decimate_width) { + hdec *= 2; + post_decimate_width /= 2; + } + while (fmt->height * STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR < post_decimate_height) { + vdec *= 2; + post_decimate_height /= 2; + } + + if (hdec == 1 && vdec == 1) { + ret = HAL_DCMIPP_PIPE_DisableDecimation(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable the pipe decimation"); + return -EIO; + } + } else { + /* Use same decimation factor in width / height to keep aspect ratio */ + dec_cfg.HRatio = __builtin_ctz(hdec) << DCMIPP_P1DECR_HDEC_Pos; + dec_cfg.VRatio = __builtin_ctz(vdec) << DCMIPP_P1DECR_VDEC_Pos; + + ret = HAL_DCMIPP_PIPE_SetDecimationConfig(&dcmipp->hdcmipp, pipe->id, &dec_cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable the pipe decimation"); + return -EIO; + } + ret = HAL_DCMIPP_PIPE_EnableDecimation(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable the pipe decimation"); + return -EIO; + } + } + + /* Compute downsize factor */ + downsize_cfg.HRatio = post_decimate_width * STM32_DCMIPP_DSIZE_HVRATIO_CONS / fmt->width; + if (downsize_cfg.HRatio > STM32_DCMIPP_DSIZE_HVRATIO_MAX) { + downsize_cfg.HRatio = STM32_DCMIPP_DSIZE_HVRATIO_MAX; + } + downsize_cfg.VRatio = post_decimate_height * STM32_DCMIPP_DSIZE_HVRATIO_CONS / fmt->height; + if (downsize_cfg.VRatio > STM32_DCMIPP_DSIZE_HVRATIO_MAX) { + downsize_cfg.VRatio = STM32_DCMIPP_DSIZE_HVRATIO_MAX; + } + downsize_cfg.HDivFactor = STM32_DCMIPP_DSIZE_HVDIV_CONS * fmt->width / post_decimate_width; + if (downsize_cfg.HDivFactor > STM32_DCMIPP_DSIZE_HVDIV_MAX) { + downsize_cfg.HDivFactor = STM32_DCMIPP_DSIZE_HVDIV_MAX; + } + downsize_cfg.VDivFactor = + STM32_DCMIPP_DSIZE_HVDIV_CONS * fmt->height / post_decimate_height; + if (downsize_cfg.VDivFactor > STM32_DCMIPP_DSIZE_HVDIV_MAX) { + downsize_cfg.VDivFactor = STM32_DCMIPP_DSIZE_HVDIV_MAX; + } + downsize_cfg.HSize = fmt->width; + downsize_cfg.VSize = fmt->height; + + ret = HAL_DCMIPP_PIPE_SetDownsizeConfig(&dcmipp->hdcmipp, pipe->id, &downsize_cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to configure the pipe downsize"); + return -EIO; + } + + ret = HAL_DCMIPP_PIPE_EnableDownsize(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable the pipe downsize"); + return -EIO; + } + + return 0; +} + +/* No clamping enabled */ +/* RGB Full to YUV 601 Full */ +const DCMIPP_ColorConversionConfTypeDef stm32_dcmipp_rgb_to_yuv = { + .RR = 131, .RG = -110, .RB = -21, .RA = 128, + .GR = 77, .GG = 150, .GB = 29, .GA = 0, + .BR = -44, .BG = -87, .BB = 131, .BA = 128, +}; + +/* YUV 601 Full to RGB Full */ +const DCMIPP_ColorConversionConfTypeDef stm32_dcmipp_yuv_to_rgb = { + .RR = 351, .RG = 256, .RB = 0, .RA = -175, + .GR = -179, .GG = 256, .GB = -86, .GA = 132, + .BR = 0, .BG = 256, .BB = 443, .BA = -222, +}; + +static int stm32_dcmipp_set_yuv_conversion(struct stm32_dcmipp_pipe_data *pipe, + uint32_t source_colorspace, + uint32_t output_colorspace) +{ + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + const DCMIPP_ColorConversionConfTypeDef *cfg = NULL; + int ret; + + /* No YUV conversion on pipe 2 */ + if (pipe->id == DCMIPP_PIPE2) { + return 0; + } + + /* Perform YUV conversion if necessary and possible */ + if ((source_colorspace == VIDEO_COLORSPACE_RAW || + source_colorspace == VIDEO_COLORSPACE_RGB) && + output_colorspace == VIDEO_COLORSPACE_YUV) { + /* Need to perform RGB to YUV conversion */ + cfg = &stm32_dcmipp_rgb_to_yuv; + } else if (source_colorspace == VIDEO_COLORSPACE_YUV && + output_colorspace == VIDEO_COLORSPACE_RGB) { + /* Need to perform YUV to RGB conversion */ + cfg = &stm32_dcmipp_yuv_to_rgb; + } else { + ret = HAL_DCMIPP_PIPE_DisableYUVConversion(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable YUV conversion"); + return -EIO; + } + + return 0; + } + + ret = HAL_DCMIPP_PIPE_SetYUVConversionConfig(&dcmipp->hdcmipp, pipe->id, cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to setup YUV conversion"); + return -EIO; + } + + ret = HAL_DCMIPP_PIPE_EnableYUVConversion(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable YUV conversion"); + return -EIO; + } + + return 0; +} +#endif + +static int stm32_dcmipp_stream_enable(const struct device *dev) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + const struct stm32_dcmipp_config *config = dev->config; + struct video_format *fmt = &pipe->fmt; + const struct stm32_dcmipp_mapping *mapping; + const struct stm32_dcmipp_input_fmt *input_fmt; +#if defined(STM32_DCMIPP_HAS_CSI) + DCMIPP_CSI_PIPE_ConfTypeDef csi_pipe_cfg = { 0 }; +#endif + DCMIPP_PipeConfTypeDef pipe_cfg = { 0 }; + int ret; + + k_mutex_lock(&pipe->lock, K_FOREVER); + + if (pipe->is_streaming) { + ret = -EALREADY; + goto out; + } + + input_fmt = stm32_dcmipp_get_input_info(dcmipp->source_fmt.pixelformat); + if (input_fmt == NULL) { + LOG_ERR("Unsupported input format"); + ret = -EINVAL; + goto out; + } + + /* Configure the source if nothing is already running */ + if (dcmipp->enabled_pipe == 0) { + ret = video_set_format(config->source_dev, &dcmipp->source_fmt); + if (ret < 0) { + goto out; + } + + if (config->bus_type == VIDEO_BUS_TYPE_PARALLEL) { + ret = stm32_dcmipp_conf_parallel(dcmipp->dev, input_fmt); + } +#if defined(STM32_DCMIPP_HAS_CSI) + else if (config->bus_type == VIDEO_BUS_TYPE_CSI2_DPHY) { + ret = stm32_dcmipp_conf_csi(dcmipp->dev, input_fmt->dcmipp_csi_bpp); + } +#endif + else { + LOG_ERR("Invalid bus_type"); + ret = -EINVAL; + goto out; + } + if (ret < 0) { + LOG_ERR("Failed to configure input stage"); + goto out; + } + } + + pipe->active = NULL; + pipe->next = k_fifo_get(&pipe->fifo_in, K_NO_WAIT); + if (pipe->next == NULL) { + LOG_ERR("No buffer available to start streaming"); + ret = -ENOMEM; + goto out; + } + +#if defined(STM32_DCMIPP_HAS_CSI) + /* Configure the Pipe input */ + csi_pipe_cfg.DataTypeMode = DCMIPP_DTMODE_DTIDA; + csi_pipe_cfg.DataTypeIDA = input_fmt->dcmipp_csi_dt; + ret = HAL_DCMIPP_CSI_PIPE_SetConfig(&dcmipp->hdcmipp, + pipe->id == DCMIPP_PIPE2 ? DCMIPP_PIPE1 : pipe->id, + &csi_pipe_cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to configure pipe #%d input", pipe->id); + ret = -EIO; + goto out; + } +#endif + + /* Configure Pipe */ + mapping = stm32_dcmipp_get_mapping(fmt->pixelformat, pipe->id); + if (mapping == NULL) { + LOG_ERR("Failed to find pipe format mapping"); + ret = -EIO; + goto out; + } + + pipe_cfg.FrameRate = DCMIPP_FRAME_RATE_ALL; +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + pipe_cfg.PixelPipePitch = fmt->pitch; + pipe_cfg.PixelPackerFormat = mapping->pixels.dcmipp_format; + } +#endif + ret = HAL_DCMIPP_PIPE_SetConfig(&dcmipp->hdcmipp, pipe->id, &pipe_cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to configure pipe #%d", pipe->id); + ret = -EIO; + goto out; + } + + if (pipe->id == DCMIPP_PIPE0) { + /* Only the PIPE0 has a limiter */ + /* Set Limiter to avoid buffer overflow, in number of 32 bits words */ + ret = HAL_DCMIPP_PIPE_EnableLimitEvent(&dcmipp->hdcmipp, DCMIPP_PIPE0, + (fmt->pitch * fmt->height) / 4); + if (ret != HAL_OK) { + LOG_ERR("Failed to set limiter"); + ret = -EIO; + goto out; + } + } +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + else if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + uint32_t source_colorspace = VIDEO_COLORSPACE(dcmipp->source_fmt.pixelformat); + uint32_t output_colorspace = VIDEO_COLORSPACE(fmt->pixelformat); + + /* Enable / disable SWAPRB if necessary */ + if (mapping->pixels.swap_uv) { + ret = HAL_DCMIPP_PIPE_EnableRedBlueSwap(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable Red-Blue swap"); + ret = -EIO; + goto out; + } + } else { + ret = HAL_DCMIPP_PIPE_DisableRedBlueSwap(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable Red-Blue swap"); + ret = -EIO; + goto out; + } + } + + if (source_colorspace == VIDEO_COLORSPACE_RAW) { + /* Enable demosaicing if input format is Bayer */ + ret = HAL_DCMIPP_PIPE_EnableISPRawBayer2RGB(&dcmipp->hdcmipp, DCMIPP_PIPE1); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable demosaicing"); + ret = -EIO; + goto out; + } + } else { + /* Disable demosaicing */ + ret = HAL_DCMIPP_PIPE_DisableISPRawBayer2RGB(&dcmipp->hdcmipp, + DCMIPP_PIPE1); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable demosaicing"); + ret = -EIO; + goto out; + } + } + + /* Configure the Pipe decimation / downsize */ + ret = stm32_dcmipp_set_downscale(pipe); + if (ret < 0) { + goto out; + } + + /* Configure YUV conversion */ + ret = stm32_dcmipp_set_yuv_conversion(pipe, source_colorspace, output_colorspace); + if (ret < 0) { + goto out; + } + } +#endif + + /* Enable the DCMIPP Pipeline */ + if (config->bus_type == VIDEO_BUS_TYPE_PARALLEL) { + ret = HAL_DCMIPP_PIPE_Start(&dcmipp->hdcmipp, pipe->id, + (uint32_t)pipe->next->buffer, DCMIPP_MODE_CONTINUOUS); + } +#if defined(STM32_DCMIPP_HAS_CSI) + else if (config->bus_type == VIDEO_BUS_TYPE_CSI2_DPHY) { + ret = HAL_DCMIPP_CSI_PIPE_Start(&dcmipp->hdcmipp, pipe->id, DCMIPP_VIRTUAL_CHANNEL0, + (uint32_t)pipe->next->buffer, + DCMIPP_MODE_CONTINUOUS); + } +#endif + else { + LOG_ERR("Invalid bus_type"); + ret = -EINVAL; + goto out; + } + if (ret != HAL_OK) { + LOG_ERR("Failed to start the pipeline"); + ret = -EIO; + goto out; + } + + /* It is necessary to start the source as well if nothing else is running */ + if (dcmipp->enabled_pipe == 0) { + ret = video_stream_start(config->source_dev, VIDEO_BUF_TYPE_OUTPUT); + if (ret < 0) { + LOG_ERR("Failed to start the source"); + if (config->bus_type == VIDEO_BUS_TYPE_PARALLEL) { + HAL_DCMIPP_PIPE_Stop(&dcmipp->hdcmipp, pipe->id); + } +#if defined(STM32_DCMIPP_HAS_CSI) + else if (config->bus_type == VIDEO_BUS_TYPE_CSI2_DPHY) { + HAL_DCMIPP_CSI_PIPE_Stop(&dcmipp->hdcmipp, pipe->id, + DCMIPP_VIRTUAL_CHANNEL0); + } +#endif + else { + LOG_ERR("Invalid bus_type"); + } + ret = -EIO; + goto out; + } + } + + pipe->state = STM32_DCMIPP_RUNNING; + pipe->is_streaming = true; + dcmipp->enabled_pipe++; + +out: + k_mutex_unlock(&pipe->lock); + + return ret; +} + +static int stm32_dcmipp_stream_disable(const struct device *dev) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + const struct stm32_dcmipp_config *config = dev->config; + int ret; + + k_mutex_lock(&pipe->lock, K_FOREVER); + + if (!pipe->is_streaming) { + ret = -EINVAL; + goto out; + } + + /* Disable the DCMIPP Pipeline */ + if (config->bus_type == VIDEO_BUS_TYPE_PARALLEL) { + ret = HAL_DCMIPP_PIPE_Stop(&dcmipp->hdcmipp, pipe->id); + } +#if defined(STM32_DCMIPP_HAS_CSI) + else if (config->bus_type == VIDEO_BUS_TYPE_CSI2_DPHY) { + ret = HAL_DCMIPP_CSI_PIPE_Stop(&dcmipp->hdcmipp, pipe->id, DCMIPP_VIRTUAL_CHANNEL0); + } +#endif + else { + LOG_ERR("Invalid bus_type"); + ret = -EIO; + goto out; + } + if (ret != HAL_OK) { + LOG_ERR("Failed to stop the pipeline"); + ret = -EIO; + goto out; + } + + /* Turn off the source nobody else is using the DCMIPP */ + if (dcmipp->enabled_pipe == 1) { + ret = video_stream_stop(config->source_dev, VIDEO_BUF_TYPE_OUTPUT); + if (ret < 0) { + LOG_ERR("Failed to stop the source"); + ret = -EIO; + goto out; + } + } + + /* Release the video buffer allocated when start streaming */ + if (pipe->next != NULL) { + k_fifo_put(&pipe->fifo_in, pipe->next); + } + if (pipe->active != NULL) { + k_fifo_put(&pipe->fifo_in, pipe->active); + } + + pipe->state = STM32_DCMIPP_STOPPED; + pipe->is_streaming = false; + dcmipp->enabled_pipe--; + +out: + k_mutex_unlock(&pipe->lock); + + return ret; +} + +static int stm32_dcmipp_set_stream(const struct device *dev, bool enable, enum video_buf_type type) +{ + return enable ? stm32_dcmipp_stream_enable(dev) : stm32_dcmipp_stream_disable(dev); +} + +static int stm32_dcmipp_enqueue(const struct device *dev, struct video_buffer *vbuf) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + int ret; + + k_mutex_lock(&pipe->lock, K_FOREVER); + + if (pipe->fmt.pitch * pipe->fmt.height > vbuf->size) { + return -EINVAL; + } + + if (pipe->state == STM32_DCMIPP_WAIT_FOR_BUFFER) { + LOG_DBG("Restart CPTREQ after wait for buffer"); + pipe->next = vbuf; + ret = HAL_DCMIPP_PIPE_SetMemoryAddress(&dcmipp->hdcmipp, pipe->id, + DCMIPP_MEMORY_ADDRESS_0, + (uint32_t)pipe->next->buffer); + if (ret != HAL_OK) { + LOG_ERR("Failed to update memory address"); + return -EIO; + } + if (pipe->id == DCMIPP_PIPE0) { + SET_BIT(dcmipp->hdcmipp.Instance->P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + } +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + else if (pipe->id == DCMIPP_PIPE1) { + SET_BIT(dcmipp->hdcmipp.Instance->P1FCTCR, DCMIPP_P1FCTCR_CPTREQ); + } else if (pipe->id == DCMIPP_PIPE2) { + SET_BIT(dcmipp->hdcmipp.Instance->P2FCTCR, DCMIPP_P2FCTCR_CPTREQ); + } +#endif + pipe->state = STM32_DCMIPP_RUNNING; + } else { + k_fifo_put(&pipe->fifo_in, vbuf); + } + + k_mutex_unlock(&pipe->lock); + + return 0; +} + +static int stm32_dcmipp_dequeue(const struct device *dev, struct video_buffer **vbuf, + k_timeout_t timeout) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + + *vbuf = k_fifo_get(&pipe->fifo_out, timeout); + if (*vbuf == NULL) { + return -EAGAIN; + } + + return 0; +} + +/* + * TODO: caps aren't yet handled hence give back straight the caps given by the + * source. Normally this should be the intersection of what the source produces + * vs what the DCMIPP can input (for pipe0) and, for pipe 1 and 2, for a given + * input format, generate caps based on capabilities, color conversion, decimation + * etc + */ +static int stm32_dcmipp_get_caps(const struct device *dev, struct video_caps *caps) +{ + const struct stm32_dcmipp_config *config = dev->config; + int ret; + + ret = video_get_caps(config->source_dev, caps); + + caps->min_vbuf_count = 1; + caps->min_line_count = LINE_COUNT_HEIGHT; + caps->max_line_count = LINE_COUNT_HEIGHT; + + return ret; +} + +static int stm32_dcmipp_get_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct stm32_dcmipp_config *config = dev->config; + + return video_get_frmival(config->source_dev, frmival); +} + +static int stm32_dcmipp_set_frmival(const struct device *dev, struct video_frmival *frmival) +{ + const struct stm32_dcmipp_config *config = dev->config; + + return video_set_frmival(config->source_dev, frmival); +} + +static int stm32_dcmipp_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) +{ + const struct stm32_dcmipp_config *config = dev->config; + + return video_enum_frmival(config->source_dev, fie); +} + +static DEVICE_API(video, stm32_dcmipp_driver_api) = { + .set_format = stm32_dcmipp_set_fmt, + .get_format = stm32_dcmipp_get_fmt, + .set_stream = stm32_dcmipp_set_stream, + .enqueue = stm32_dcmipp_enqueue, + .dequeue = stm32_dcmipp_dequeue, + .get_caps = stm32_dcmipp_get_caps, + .get_frmival = stm32_dcmipp_get_frmival, + .set_frmival = stm32_dcmipp_set_frmival, + .enum_frmival = stm32_dcmipp_enum_frmival, +}; + +static int stm32_dcmipp_enable_clock(const struct device *dev) +{ + const struct stm32_dcmipp_config *config = dev->config; + const struct device *cc_node = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); + int err; + + if (!device_is_ready(cc_node)) { + LOG_ERR("clock control device not ready"); + return -ENODEV; + } + + /* Turn on DCMIPP peripheral clock */ + err = clock_control_configure(cc_node, (clock_control_subsys_t)&config->dcmipp_pclken_ker, + NULL); + if (err < 0) { + LOG_ERR("Failed to configure DCMIPP clock. Error %d", err); + return err; + } + + err = clock_control_on(cc_node, (clock_control_subsys_t *)&config->dcmipp_pclken); + if (err < 0) { + LOG_ERR("Failed to enable DCMIPP clock. Error %d", err); + return err; + } + +#if defined(STM32_DCMIPP_HAS_CSI) + /* Turn on CSI peripheral clock */ + err = clock_control_on(cc_node, (clock_control_subsys_t *)&config->csi_pclken); + if (err < 0) { + LOG_ERR("Failed to enable CSI clock. Error %d", err); + return err; + } +#endif + + return 0; +} + +static int stm32_dcmipp_init(const struct device *dev) +{ + const struct stm32_dcmipp_config *cfg = dev->config; + struct stm32_dcmipp_data *dcmipp = dev->data; + + int err; + +#if defined(CONFIG_SOC_SERIES_STM32N6X) + RIMC_MasterConfig_t rimc = {0}; +#endif + + dcmipp->enabled_pipe = 0; + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + dcmipp->isp_dec_hratio = 1; + dcmipp->isp_dec_vratio = 1; +#endif + + /* Configure DT provided pins */ + if (cfg->bus_type == VIDEO_BUS_TYPE_PARALLEL) { + err = pinctrl_apply_state(cfg->pctrl, PINCTRL_STATE_DEFAULT); + if (err < 0) { + LOG_ERR("pinctrl setup failed. Error %d.", err); + return err; + } + } + + /* Enable DCMIPP / CSI clocks */ + err = stm32_dcmipp_enable_clock(dev); + if (err < 0) { + LOG_ERR("Clock enabling failed."); + return err; + } + + /* Reset DCMIPP & CSI */ + if (!device_is_ready(cfg->reset_dcmipp.dev)) { + LOG_ERR("reset controller not ready"); + return -ENODEV; + } + reset_line_toggle_dt(&cfg->reset_dcmipp); +#if defined(STM32_DCMIPP_HAS_CSI) + reset_line_toggle_dt(&cfg->reset_csi); +#endif + + dcmipp->dev = dev; + + /* Run IRQ init */ + cfg->irq_config(dev); + +#if defined(CONFIG_SOC_SERIES_STM32N6X) + rimc.MasterCID = RIF_CID_1; + rimc.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV; + HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_DCMIPP, &rimc); + HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_DCMIPP, + RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); +#endif + + /* Initialize DCMI peripheral */ + err = HAL_DCMIPP_Init(&dcmipp->hdcmipp); + if (err != HAL_OK) { + LOG_ERR("DCMIPP initialization failed."); + return -EIO; + } + + LOG_DBG("%s initialized", dev->name); + + return 0; +} + +static int stm32_dcmipp_pipe_init(const struct device *dev) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + + k_mutex_init(&pipe->lock); + k_fifo_init(&pipe->fifo_in); + k_fifo_init(&pipe->fifo_out); + + /* TODO - need to init formats to valid values */ + + /* Store the pipe data pointer into dcmipp data structure */ + dcmipp->pipe[pipe->id] = pipe; + + return 0; +} + +static void stm32_dcmipp_isr(const struct device *dev) +{ + struct stm32_dcmipp_data *dcmipp = dev->data; + + HAL_DCMIPP_IRQHandler(&dcmipp->hdcmipp); +} + +#define DCMIPP_PIPE_INIT_DEFINE(node_id, inst) \ + static struct stm32_dcmipp_pipe_data stm32_dcmipp_pipe_##node_id = { \ + .id = DT_NODE_CHILD_IDX(node_id), \ + .dcmipp = &stm32_dcmipp_data_##inst, \ + }; \ + \ + DEVICE_DT_DEFINE(node_id, &stm32_dcmipp_pipe_init, NULL, \ + &stm32_dcmipp_pipe_##node_id, \ + &stm32_dcmipp_config_##inst, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ + &stm32_dcmipp_driver_api); + +#define SOURCE_DEV(inst) DEVICE_DT_GET(DT_NODE_REMOTE_DEVICE(DT_INST_ENDPOINT_BY_ID(inst, 0, 0))) + +#if defined(STM32_DCMIPP_HAS_CSI) +#define STM32_DCMIPP_CSI_DT_PARAMS(inst) \ + .csi_pclken = \ + {.bus = DT_CLOCKS_CELL_BY_NAME(DT_DRV_INST(inst), csi, bus), \ + .enr = DT_CLOCKS_CELL_BY_NAME(DT_DRV_INST(inst), csi, bits)}, \ + .reset_csi = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 1), \ + .csi.nb_lanes = DT_PROP_LEN(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), data_lanes), \ + .csi.lanes[0] = DT_PROP_BY_IDX(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), \ + data_lanes, 0), \ + .csi.lanes[1] = COND_CODE_1(DT_PROP_LEN(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), \ + data_lanes), \ + (0), \ + (DT_PROP_BY_IDX(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), \ + data_lanes, 1))), +#else +#define STM32_DCMIPP_CSI_DT_PARAMS(inst) +#endif + +#define STM32_DCMIPP_INIT(inst) \ + static void stm32_dcmipp_irq_config_##inst(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), \ + stm32_dcmipp_isr, DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQN(inst)); \ + } \ + \ + static struct stm32_dcmipp_data stm32_dcmipp_data_##inst = { \ + .hdcmipp = { \ + .Instance = (DCMIPP_TypeDef *)DT_INST_REG_ADDR(inst), \ + }, \ + .source_fmt = { \ + .pixelformat = \ + VIDEO_FOURCC_FROM_STR( \ + CONFIG_VIDEO_STM32_DCMIPP_SENSOR_PIXEL_FORMAT), \ + .width = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH, \ + .height = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT, \ + }, \ + }; \ + \ + PINCTRL_DT_INST_DEFINE(inst); \ + \ + static const struct stm32_dcmipp_config stm32_dcmipp_config_##inst = { \ + .dcmipp_pclken = \ + {.bus = DT_CLOCKS_CELL_BY_NAME(DT_DRV_INST(inst), dcmipp, bus), \ + .enr = DT_CLOCKS_CELL_BY_NAME(DT_DRV_INST(inst), dcmipp, bits)}, \ + .dcmipp_pclken_ker = \ + {.bus = DT_CLOCKS_CELL_BY_NAME(DT_DRV_INST(inst), dcmipp_ker, bus), \ + .enr = DT_CLOCKS_CELL_BY_NAME(DT_DRV_INST(inst), dcmipp_ker, bits)}, \ + .irq_config = stm32_dcmipp_irq_config_##inst, \ + .pctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .source_dev = SOURCE_DEV(inst), \ + .reset_dcmipp = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 0), \ + .bus_type = DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), bus_type, \ + VIDEO_BUS_TYPE_PARALLEL), \ + STM32_DCMIPP_CSI_DT_PARAMS(inst) \ + .parallel.vs_polarity = DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), \ + vsync_active, 0) ? \ + DCMIPP_VSPOLARITY_HIGH : \ + DCMIPP_VSPOLARITY_LOW, \ + .parallel.hs_polarity = DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \ + hsync_active, 0) ? \ + DCMIPP_HSPOLARITY_HIGH : \ + DCMIPP_HSPOLARITY_LOW, \ + .parallel.pck_polarity = DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(inst, 0, 0), \ + pclk_sample, 0) ? \ + DCMIPP_PCKPOLARITY_RISING : \ + DCMIPP_PCKPOLARITY_FALLING, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, &stm32_dcmipp_init, \ + NULL, &stm32_dcmipp_data_##inst, \ + &stm32_dcmipp_config_##inst, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \ + NULL); \ + \ + DT_FOREACH_CHILD_VARGS(DT_INST_PORT_BY_ID(inst, 1), DCMIPP_PIPE_INIT_DEFINE, inst); \ + \ + VIDEO_DEVICE_DEFINE(dcmipp_##inst, DEVICE_DT_INST_GET(inst), SOURCE_DEV(inst)); + +DT_INST_FOREACH_STATUS_OKAY(STM32_DCMIPP_INIT) diff --git a/tests/drivers/build_all/video/testcase.yaml b/tests/drivers/build_all/video/testcase.yaml index 519966a846a..c7a5e07161e 100644 --- a/tests/drivers/build_all/video/testcase.yaml +++ b/tests/drivers/build_all/video/testcase.yaml @@ -31,3 +31,8 @@ tests: drivers.video.esp32_dvp.build: platform_allow: - esp32s3_eye/esp32s3/procpu + drivers.video.stm32_dcmipp.build: + platform_allow: + - stm32n6570_dk/stm32n657xx/sb + extra_args: + - platform:stm32n6570_dk/stm32n657xx/sb:SHIELD=st_b_cams_imx_mb1854