|  | /* | 
|  | * Copyright (c) 2024 Charles Dias <charlesdias.cd@outlook.com> | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT st_stm32_dcmi | 
|  |  | 
|  | #include <errno.h> | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/irq.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/drivers/video.h> | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  | #include <zephyr/drivers/clock_control/stm32_clock_control.h> | 
|  | #include <zephyr/drivers/clock_control.h> | 
|  | #include <zephyr/drivers/dma.h> | 
|  | #include <zephyr/drivers/dma/dma_stm32.h> | 
|  |  | 
|  | #include <stm32_ll_dma.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(video_stm32_dcmi, CONFIG_VIDEO_LOG_LEVEL); | 
|  |  | 
|  | #if CONFIG_VIDEO_BUFFER_POOL_NUM_MAX < 2 | 
|  | #error "The minimum required number of buffers for video_stm32 is 2" | 
|  | #endif | 
|  |  | 
|  | typedef void (*irq_config_func_t)(const struct device *dev); | 
|  |  | 
|  | struct stream { | 
|  | DMA_TypeDef *reg; | 
|  | const struct device *dma_dev; | 
|  | uint32_t channel; | 
|  | struct dma_config cfg; | 
|  | }; | 
|  |  | 
|  | struct video_stm32_dcmi_data { | 
|  | const struct device *dev; | 
|  | DCMI_HandleTypeDef hdcmi; | 
|  | struct video_format fmt; | 
|  | struct k_fifo fifo_in; | 
|  | struct k_fifo fifo_out; | 
|  | uint32_t pixel_format; | 
|  | uint32_t height; | 
|  | uint32_t width; | 
|  | uint32_t pitch; | 
|  | struct video_buffer *vbuf; | 
|  | }; | 
|  |  | 
|  | struct video_stm32_dcmi_config { | 
|  | struct stm32_pclken pclken; | 
|  | irq_config_func_t irq_config; | 
|  | const struct pinctrl_dev_config *pctrl; | 
|  | const struct device *sensor_dev; | 
|  | const struct stream dma; | 
|  | }; | 
|  |  | 
|  | void HAL_DCMI_ErrorCallback(DCMI_HandleTypeDef *hdcmi) | 
|  | { | 
|  | LOG_WRN("%s", __func__); | 
|  | } | 
|  |  | 
|  | void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) | 
|  | { | 
|  | struct video_stm32_dcmi_data *dev_data = | 
|  | CONTAINER_OF(hdcmi, struct video_stm32_dcmi_data, hdcmi); | 
|  | struct video_buffer *vbuf; | 
|  |  | 
|  | HAL_DCMI_Suspend(hdcmi); | 
|  |  | 
|  | vbuf = k_fifo_get(&dev_data->fifo_in, K_NO_WAIT); | 
|  |  | 
|  | if (vbuf == NULL) { | 
|  | LOG_DBG("Failed to get buffer from fifo"); | 
|  | goto resume; | 
|  | } | 
|  |  | 
|  | vbuf->timestamp = k_uptime_get_32(); | 
|  | memcpy(vbuf->buffer, dev_data->vbuf->buffer, vbuf->bytesused); | 
|  |  | 
|  | k_fifo_put(&dev_data->fifo_out, vbuf); | 
|  |  | 
|  | resume: | 
|  | HAL_DCMI_Resume(hdcmi); | 
|  | } | 
|  |  | 
|  | static void stm32_dcmi_isr(const struct device *dev) | 
|  | { | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  |  | 
|  | HAL_DCMI_IRQHandler(&data->hdcmi); | 
|  | } | 
|  |  | 
|  | static void dmci_dma_callback(const struct device *dev, void *arg, | 
|  | uint32_t channel, int status) | 
|  | { | 
|  | DMA_HandleTypeDef *hdma = arg; | 
|  |  | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | if (status < 0) { | 
|  | LOG_ERR("DMA callback error with channel %d.", channel); | 
|  | } | 
|  |  | 
|  | HAL_DMA_IRQHandler(hdma); | 
|  | } | 
|  |  | 
|  | void HAL_DMA_ErrorCallback(DMA_HandleTypeDef *hdma) | 
|  | { | 
|  | LOG_WRN("%s", __func__); | 
|  | } | 
|  |  | 
|  | static int stm32_dma_init(const struct device *dev) | 
|  | { | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | int ret; | 
|  |  | 
|  | /* Check if the DMA device is ready */ | 
|  | if (!device_is_ready(config->dma.dma_dev)) { | 
|  | LOG_ERR("%s DMA device not ready", config->dma.dma_dev->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * DMA configuration | 
|  | * Due to use of QSPI HAL API in current driver, | 
|  | * both HAL and Zephyr DMA drivers should be configured. | 
|  | * The required configuration for Zephyr DMA driver should only provide | 
|  | * the minimum information to inform the DMA slot will be in used and | 
|  | * how to route callbacks. | 
|  | */ | 
|  | struct dma_config dma_cfg = config->dma.cfg; | 
|  | static DMA_HandleTypeDef hdma; | 
|  |  | 
|  | /* Proceed to the minimum Zephyr DMA driver init */ | 
|  | dma_cfg.user_data = &hdma; | 
|  | /* HACK: This field is used to inform driver that it is overridden */ | 
|  | dma_cfg.linked_channel = STM32_DMA_HAL_OVERRIDE; | 
|  | /* Because of the STREAM OFFSET, the DMA channel given here is from 1 - 8 */ | 
|  | ret = dma_config(config->dma.dma_dev, | 
|  | config->dma.channel + STM32_DMA_STREAM_OFFSET, &dma_cfg); | 
|  | if (ret != 0) { | 
|  | LOG_ERR("Failed to configure DMA channel %d", | 
|  | config->dma.channel + STM32_DMA_STREAM_OFFSET); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /*** Configure the DMA ***/ | 
|  | /* Set the parameters to be configured */ | 
|  | hdma.Init.Request		= DMA_REQUEST_DCMI; | 
|  | hdma.Init.Direction		= DMA_PERIPH_TO_MEMORY; | 
|  | hdma.Init.PeriphInc		= DMA_PINC_DISABLE; | 
|  | hdma.Init.MemInc		= DMA_MINC_ENABLE; | 
|  | hdma.Init.PeriphDataAlignment	= DMA_PDATAALIGN_WORD; | 
|  | hdma.Init.MemDataAlignment	= DMA_MDATAALIGN_WORD; | 
|  | hdma.Init.Mode			= DMA_CIRCULAR; | 
|  | hdma.Init.Priority		= DMA_PRIORITY_HIGH; | 
|  | hdma.Init.FIFOMode		= DMA_FIFOMODE_DISABLE; | 
|  |  | 
|  | hdma.Instance = __LL_DMA_GET_STREAM_INSTANCE(config->dma.reg, | 
|  | config->dma.channel); | 
|  |  | 
|  | /* Initialize DMA HAL */ | 
|  | __HAL_LINKDMA(&data->hdcmi, DMA_Handle, hdma); | 
|  |  | 
|  | if (HAL_DMA_Init(&hdma) != HAL_OK) { | 
|  | LOG_ERR("DCMI DMA Init failed"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int stm32_dcmi_enable_clock(const struct device *dev) | 
|  | { | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | const struct device *dcmi_clock = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); | 
|  | int err; | 
|  |  | 
|  | if (!device_is_ready(dcmi_clock)) { | 
|  | LOG_ERR("clock control device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* Turn on DCMI peripheral clock */ | 
|  | err = clock_control_on(dcmi_clock, (clock_control_subsys_t *) &config->pclken); | 
|  | if (err < 0) { | 
|  | LOG_ERR("Failed to enable DCMI clock. Error %d", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int video_stm32_dcmi_set_fmt(const struct device *dev, | 
|  | enum video_endpoint_id ep, | 
|  | struct video_format *fmt) | 
|  | { | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  | unsigned int bpp = video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; | 
|  |  | 
|  | if (bpp == 0 || (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if ((fmt->pitch * fmt->height) > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | data->pixel_format = fmt->pixelformat; | 
|  | data->pitch = fmt->pitch; | 
|  | data->height = fmt->height; | 
|  | data->width = fmt->width; | 
|  |  | 
|  | if (video_set_format(config->sensor_dev, ep, fmt)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int video_stm32_dcmi_get_fmt(const struct device *dev, | 
|  | enum video_endpoint_id ep, | 
|  | struct video_format *fmt) | 
|  | { | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  |  | 
|  | if (fmt == NULL || (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (!video_get_format(config->sensor_dev, ep, fmt)) { | 
|  | /* align DCMI with sensor fmt */ | 
|  | return video_stm32_dcmi_set_fmt(dev, ep, fmt); | 
|  | } | 
|  |  | 
|  | fmt->pixelformat = data->pixel_format; | 
|  | fmt->height = data->height; | 
|  | fmt->width = data->width; | 
|  | fmt->pitch = data->pitch; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int video_stm32_dcmi_set_stream(const struct device *dev, bool enable) | 
|  | { | 
|  | int err; | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  |  | 
|  | if (!enable) { | 
|  | if (video_stream_stop(config->sensor_dev)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | err = HAL_DCMI_Stop(&data->hdcmi); | 
|  | if (err != HAL_OK) { | 
|  | LOG_ERR("Failed to stop DCMI"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Release the video buffer allocated when start streaming */ | 
|  | k_fifo_put(&data->fifo_in, data->vbuf); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | data->vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT); | 
|  |  | 
|  | if (data->vbuf == NULL) { | 
|  | LOG_ERR("Failed to dequeue a DCMI buffer."); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | err = HAL_DCMI_Start_DMA(&data->hdcmi, DCMI_MODE_CONTINUOUS, | 
|  | (uint32_t)data->vbuf->buffer, data->vbuf->bytesused / 4); | 
|  | if (err != HAL_OK) { | 
|  | LOG_ERR("Failed to start DCMI DMA"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (video_stream_start(config->sensor_dev)) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int video_stm32_dcmi_enqueue(const struct device *dev, | 
|  | enum video_endpoint_id ep, | 
|  | struct video_buffer *vbuf) | 
|  | { | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  | const uint32_t buffer_size = data->pitch * data->height; | 
|  |  | 
|  | if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (buffer_size > vbuf->size) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | vbuf->bytesused = buffer_size; | 
|  | vbuf->line_offset = 0; | 
|  |  | 
|  | k_fifo_put(&data->fifo_in, vbuf); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int video_stm32_dcmi_dequeue(const struct device *dev, | 
|  | enum video_endpoint_id ep, | 
|  | struct video_buffer **vbuf, | 
|  | k_timeout_t timeout) | 
|  | { | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  |  | 
|  | if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | *vbuf = k_fifo_get(&data->fifo_out, timeout); | 
|  | if (*vbuf == NULL) { | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int video_stm32_dcmi_get_caps(const struct device *dev, | 
|  | enum video_endpoint_id ep, | 
|  | struct video_caps *caps) | 
|  | { | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | int ret = -ENODEV; | 
|  |  | 
|  | if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* DCMI produces full frames */ | 
|  | caps->min_line_count = caps->max_line_count = LINE_COUNT_HEIGHT; | 
|  |  | 
|  | /* Forward the message to the sensor device */ | 
|  | ret = video_get_caps(config->sensor_dev, ep, caps); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static inline int video_stm32_dcmi_set_ctrl(const struct device *dev, unsigned int cid, void *value) | 
|  | { | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | int ret; | 
|  |  | 
|  | /* Forward to source dev if any */ | 
|  | ret = video_set_ctrl(config->sensor_dev, cid, value); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static inline int video_stm32_dcmi_get_ctrl(const struct device *dev, unsigned int cid, void *value) | 
|  | { | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | int ret; | 
|  |  | 
|  | /* Forward to source dev if any */ | 
|  | ret = video_get_ctrl(config->sensor_dev, cid, value); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(video, video_stm32_dcmi_driver_api) = { | 
|  | .set_format = video_stm32_dcmi_set_fmt, | 
|  | .get_format = video_stm32_dcmi_get_fmt, | 
|  | .set_stream = video_stm32_dcmi_set_stream, | 
|  | .enqueue = video_stm32_dcmi_enqueue, | 
|  | .dequeue = video_stm32_dcmi_dequeue, | 
|  | .get_caps = video_stm32_dcmi_get_caps, | 
|  | .set_ctrl = video_stm32_dcmi_set_ctrl, | 
|  | .get_ctrl = video_stm32_dcmi_get_ctrl, | 
|  | }; | 
|  |  | 
|  | static void video_stm32_dcmi_irq_config_func(const struct device *dev) | 
|  | { | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), | 
|  | stm32_dcmi_isr, DEVICE_DT_INST_GET(0), 0); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  | } | 
|  |  | 
|  | #define DCMI_DMA_CHANNEL_INIT(index, src_dev, dest_dev)					\ | 
|  | .dma_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_IDX(index, 0)),			\ | 
|  | .channel = DT_INST_DMAS_CELL_BY_IDX(index, 0, channel),				\ | 
|  | .reg = (DMA_TypeDef *)DT_REG_ADDR(						\ | 
|  | DT_PHANDLE_BY_IDX(DT_DRV_INST(0), dmas, 0)),		\ | 
|  | .cfg = {									\ | 
|  | .dma_slot = STM32_DMA_SLOT_BY_IDX(index, 0, slot),			\ | 
|  | .channel_direction = STM32_DMA_CONFIG_DIRECTION(			\ | 
|  | STM32_DMA_CHANNEL_CONFIG_BY_IDX(index, 0)),			\ | 
|  | .source_data_size = STM32_DMA_CONFIG_##src_dev##_DATA_SIZE(		\ | 
|  | STM32_DMA_CHANNEL_CONFIG_BY_IDX(index, 0)),			\ | 
|  | .dest_data_size = STM32_DMA_CONFIG_##dest_dev##_DATA_SIZE(		\ | 
|  | STM32_DMA_CHANNEL_CONFIG_BY_IDX(index, 0)),			\ | 
|  | .source_burst_length = 1,       /* SINGLE transfer */			\ | 
|  | .dest_burst_length = 1,         /* SINGLE transfer */			\ | 
|  | .channel_priority = STM32_DMA_CONFIG_PRIORITY(				\ | 
|  | STM32_DMA_CHANNEL_CONFIG_BY_IDX(index, 0)),			\ | 
|  | .dma_callback = dmci_dma_callback,					\ | 
|  | },										\ | 
|  |  | 
|  | PINCTRL_DT_INST_DEFINE(0); | 
|  |  | 
|  | #define STM32_DCMI_GET_CAPTURE_RATE(capture_rate)					\ | 
|  | ((capture_rate) == 1 ? DCMI_CR_ALL_FRAME :					\ | 
|  | (capture_rate) == 2 ? DCMI_CR_ALTERNATE_2_FRAME :				\ | 
|  | (capture_rate) == 4 ? DCMI_CR_ALTERNATE_4_FRAME :				\ | 
|  | DCMI_CR_ALL_FRAME) | 
|  |  | 
|  | #define STM32_DCMI_GET_BUS_WIDTH(bus_width)						\ | 
|  | ((bus_width) == 8 ? DCMI_EXTEND_DATA_8B :					\ | 
|  | (bus_width) == 10 ? DCMI_EXTEND_DATA_10B :					\ | 
|  | (bus_width) == 12 ? DCMI_EXTEND_DATA_12B :					\ | 
|  | (bus_width) == 14 ? DCMI_EXTEND_DATA_14B :					\ | 
|  | DCMI_EXTEND_DATA_8B) | 
|  |  | 
|  | #define DCMI_DMA_CHANNEL(id, src, dest)							\ | 
|  | .dma = {									\ | 
|  | COND_CODE_1(DT_INST_DMAS_HAS_IDX(id, 0),				\ | 
|  | (DCMI_DMA_CHANNEL_INIT(id, src, dest)),				\ | 
|  | (NULL))								\ | 
|  | }, | 
|  |  | 
|  | static struct video_stm32_dcmi_data video_stm32_dcmi_data_0 = { | 
|  | .hdcmi = { | 
|  | .Instance = (DCMI_TypeDef *) DT_INST_REG_ADDR(0), | 
|  | .Init = { | 
|  | .SynchroMode = DCMI_SYNCHRO_HARDWARE, | 
|  | .PCKPolarity = (DT_INST_PROP(0, pixelclk_active) ? | 
|  | DCMI_PCKPOLARITY_RISING : DCMI_PCKPOLARITY_FALLING), | 
|  | .HSPolarity = (DT_INST_PROP(0, hsync_active) ? | 
|  | DCMI_HSPOLARITY_HIGH : DCMI_HSPOLARITY_LOW), | 
|  | .VSPolarity = (DT_INST_PROP(0, vsync_active) ? | 
|  | DCMI_VSPOLARITY_HIGH : DCMI_VSPOLARITY_LOW), | 
|  | .CaptureRate = STM32_DCMI_GET_CAPTURE_RATE( | 
|  | DT_INST_PROP(0, capture_rate)), | 
|  | .ExtendedDataMode = STM32_DCMI_GET_BUS_WIDTH( | 
|  | DT_INST_PROP(0, bus_width)), | 
|  | .JPEGMode = DCMI_JPEG_DISABLE, | 
|  | .ByteSelectMode = DCMI_BSM_ALL, | 
|  | .ByteSelectStart = DCMI_OEBS_ODD, | 
|  | .LineSelectMode = DCMI_LSM_ALL, | 
|  | .LineSelectStart = DCMI_OELS_ODD, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct video_stm32_dcmi_config video_stm32_dcmi_config_0 = { | 
|  | .pclken = { | 
|  | .enr = DT_INST_CLOCKS_CELL(0, bits), | 
|  | .bus = DT_INST_CLOCKS_CELL(0, bus) | 
|  | }, | 
|  | .irq_config = video_stm32_dcmi_irq_config_func, | 
|  | .pctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(0), | 
|  | .sensor_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, sensor)), | 
|  | DCMI_DMA_CHANNEL(0, PERIPHERAL, MEMORY) | 
|  | }; | 
|  |  | 
|  | static int video_stm32_dcmi_init(const struct device *dev) | 
|  | { | 
|  | const struct video_stm32_dcmi_config *config = dev->config; | 
|  | struct video_stm32_dcmi_data *data = dev->data; | 
|  | int err; | 
|  |  | 
|  | /* Configure DT provided pins */ | 
|  | err = pinctrl_apply_state(config->pctrl, PINCTRL_STATE_DEFAULT); | 
|  | if (err < 0) { | 
|  | LOG_ERR("pinctrl setup failed. Error %d.", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Initialize DMA peripheral */ | 
|  | err = stm32_dma_init(dev); | 
|  | if (err < 0) { | 
|  | LOG_ERR("DMA initialization failed."); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Enable DCMI clock */ | 
|  | err = stm32_dcmi_enable_clock(dev); | 
|  | if (err < 0) { | 
|  | LOG_ERR("Clock enabling failed."); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | data->dev = dev; | 
|  | k_fifo_init(&data->fifo_in); | 
|  | k_fifo_init(&data->fifo_out); | 
|  |  | 
|  | /* Run IRQ init */ | 
|  | config->irq_config(dev); | 
|  |  | 
|  | /* Initialize DCMI peripheral */ | 
|  | err = HAL_DCMI_Init(&data->hdcmi); | 
|  | if (err != HAL_OK) { | 
|  | LOG_ERR("DCMI initialization failed."); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | k_sleep(K_MSEC(100)); | 
|  | LOG_DBG("%s inited", dev->name); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, &video_stm32_dcmi_init, | 
|  | NULL, &video_stm32_dcmi_data_0, | 
|  | &video_stm32_dcmi_config_0, | 
|  | POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, | 
|  | &video_stm32_dcmi_driver_api); |