/* * Copyright (c) 2024 Charles Dias * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT st_stm32_dcmi #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(video_stm32_dcmi, CONFIG_VIDEO_LOG_LEVEL); K_HEAP_DEFINE(video_stm32_buffer_pool, CONFIG_VIDEO_BUFFER_POOL_SZ_MAX); 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; uint8_t *buffer; }; 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->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_pix_fmt_bpp(fmt->pixelformat); if (bpp == 0 || (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL)) { 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_stream_start(const struct device *dev) { struct video_stm32_dcmi_data *data = dev->data; const struct video_stm32_dcmi_config *config = dev->config; size_t buffer_size = data->pitch * data->height; data->buffer = k_heap_alloc(&video_stm32_buffer_pool, buffer_size, K_NO_WAIT); if (data->buffer == NULL) { LOG_ERR("Failed to allocate DCMI buffer for image. Size %d bytes", buffer_size); return -ENOMEM; } int err = HAL_DCMI_Start_DMA(&data->hdcmi, DCMI_MODE_CONTINUOUS, (uint32_t)data->buffer, buffer_size / 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_stream_stop(const struct device *dev) { struct video_stm32_dcmi_data *data = dev->data; const struct video_stm32_dcmi_config *config = dev->config; int err; if (video_stream_stop(config->sensor_dev)) { return -EIO; } /* Release the buffer allocated in stream_start */ k_heap_free(&video_stm32_buffer_pool, data->buffer); err = HAL_DCMI_Stop(&data->hdcmi); if (err != HAL_OK) { LOG_ERR("Failed to stop DCMI"); 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 const struct video_driver_api video_stm32_dcmi_driver_api = { .set_format = video_stm32_dcmi_set_fmt, .get_format = video_stm32_dcmi_get_fmt, .stream_start = video_stm32_dcmi_stream_start, .stream_stop = video_stm32_dcmi_stream_stop, .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);