/* * Copyright (c) 2024 espros photonics Co. * Copyright (c) 2024 Espressif Systems (Shanghai) CO LTD. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT espressif_esp32_lcd_cam #include #include #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(video_esp32_lcd_cam, CONFIG_VIDEO_LOG_LEVEL); #define VIDEO_ESP32_DMA_BUFFER_MAX_SIZE 4095 #define VIDEO_ESP32_VSYNC_MASK 0x04 #ifdef CONFIG_POLL #define VIDEO_ESP32_RAISE_OUT_SIG_IF_ENABLED(result) \ if (data->signal_out) { \ k_poll_signal_raise(data->signal_out, result); \ } #else #define VIDEO_ESP32_RAISE_OUT_SIG_IF_ENABLED(result) #endif enum video_esp32_cam_clk_sel_values { VIDEO_ESP32_CAM_CLK_SEL_NONE = 0, VIDEO_ESP32_CAM_CLK_SEL_XTAL = 1, VIDEO_ESP32_CAM_CLK_SEL_PLL_DIV2 = 2, VIDEO_ESP32_CAM_CLK_SEL_PLL_F160M = 3, }; struct video_esp32_config { const struct pinctrl_dev_config *pcfg; const struct device *clock_dev; const clock_control_subsys_t clock_subsys; const struct device *dma_dev; const struct device *source_dev; uint32_t cam_clk; uint8_t rx_dma_channel; uint8_t data_width; uint8_t invert_de; uint8_t invert_byte_order; uint8_t invert_bit_order; uint8_t invert_pclk; uint8_t invert_hsync; uint8_t invert_vsync; }; struct video_esp32_data { cam_hal_context_t hal; const struct video_esp32_config *config; struct video_format video_format; struct video_buffer *active_vbuf; bool is_streaming; struct k_fifo fifo_in; struct k_fifo fifo_out; struct dma_block_config dma_blocks[CONFIG_DMA_ESP32_MAX_DESCRIPTOR_NUM]; #ifdef CONFIG_POLL struct k_poll_signal *signal_out; #endif }; static int video_esp32_reload_dma(struct video_esp32_data *data) { const struct video_esp32_config *cfg = data->config; int ret = 0; if (data->active_vbuf == NULL) { LOG_ERR("No video buffer available. Enqueue some buffers first."); return -EAGAIN; } ret = dma_reload(cfg->dma_dev, cfg->rx_dma_channel, 0, (uint32_t)data->active_vbuf->buffer, data->active_vbuf->bytesused); if (ret) { LOG_ERR("Unable to reload DMA (%d)", ret); return ret; } ret = dma_start(cfg->dma_dev, cfg->rx_dma_channel); if (ret) { LOG_ERR("Unable to start DMA (%d)", ret); return ret; } return 0; } void video_esp32_dma_rx_done(const struct device *dev, void *user_data, uint32_t channel, int status) { struct video_esp32_data *data = user_data; if (status == DMA_STATUS_BLOCK) { LOG_DBG("received block"); return; } if (status != DMA_STATUS_COMPLETE) { VIDEO_ESP32_RAISE_OUT_SIG_IF_ENABLED(VIDEO_BUF_ERROR) LOG_ERR("DMA error: %d", status); return; } if (data->active_vbuf == NULL) { VIDEO_ESP32_RAISE_OUT_SIG_IF_ENABLED(VIDEO_BUF_ERROR) LOG_ERR("No video buffer available. Enque some buffers first."); return; } k_fifo_put(&data->fifo_out, data->active_vbuf); VIDEO_ESP32_RAISE_OUT_SIG_IF_ENABLED(VIDEO_BUF_DONE) data->active_vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT); if (data->active_vbuf == NULL) { LOG_WRN("Frame dropped. No buffer available"); VIDEO_ESP32_RAISE_OUT_SIG_IF_ENABLED(VIDEO_BUF_ERROR) return; } video_esp32_reload_dma(data); } static int video_esp32_stream_start(const struct device *dev) { const struct video_esp32_config *cfg = dev->config; struct video_esp32_data *data = dev->data; struct dma_status dma_status = {0}; struct dma_config dma_cfg = {0}; struct dma_block_config *dma_block_iter = data->dma_blocks; uint32_t buffer_size = 0; int error = 0; if (data->is_streaming) { return -EBUSY; } LOG_DBG("Start streaming"); error = dma_get_status(cfg->dma_dev, cfg->rx_dma_channel, &dma_status); if (error) { LOG_ERR("Unable to get Rx status (%d)", error); return error; } if (dma_status.busy) { LOG_ERR("Rx DMA Channel %d is busy", cfg->rx_dma_channel); return -EBUSY; } data->active_vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT); if (!data->active_vbuf) { LOG_ERR("No enqueued video buffers available."); return -EAGAIN; } buffer_size = data->active_vbuf->bytesused; memset(data->dma_blocks, 0, sizeof(data->dma_blocks)); for (int i = 0; i < CONFIG_DMA_ESP32_MAX_DESCRIPTOR_NUM; ++i) { dma_block_iter->dest_address = (uint32_t)data->active_vbuf->buffer + (i * VIDEO_ESP32_DMA_BUFFER_MAX_SIZE); if (buffer_size < VIDEO_ESP32_DMA_BUFFER_MAX_SIZE) { dma_block_iter->block_size = buffer_size; dma_block_iter->next_block = NULL; dma_cfg.block_count = i + 1; break; } dma_block_iter->block_size = VIDEO_ESP32_DMA_BUFFER_MAX_SIZE; dma_block_iter->next_block = dma_block_iter + 1; dma_block_iter++; buffer_size -= VIDEO_ESP32_DMA_BUFFER_MAX_SIZE; } if (dma_block_iter->next_block) { LOG_ERR("Not enough descriptors available. Increase " "CONFIG_DMA_ESP32_MAX_DESCRIPTOR_NUM"); return -ENOBUFS; } dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY; dma_cfg.dma_callback = video_esp32_dma_rx_done; dma_cfg.user_data = data; dma_cfg.dma_slot = SOC_GDMA_TRIG_PERIPH_CAM0; dma_cfg.complete_callback_en = 1; dma_cfg.head_block = &data->dma_blocks[0]; error = dma_config(cfg->dma_dev, cfg->rx_dma_channel, &dma_cfg); if (error) { LOG_ERR("Unable to configure DMA (%d)", error); return error; } error = dma_start(cfg->dma_dev, cfg->rx_dma_channel); if (error) { LOG_ERR("Unable to start DMA (%d)", error); return error; } cam_hal_start_streaming(&data->hal); if (video_stream_start(cfg->source_dev)) { return -EIO; } data->is_streaming = true; return 0; } static int video_esp32_stream_stop(const struct device *dev) { const struct video_esp32_config *cfg = dev->config; struct video_esp32_data *data = dev->data; int ret = 0; LOG_DBG("Stop streaming"); if (video_stream_stop(cfg->source_dev)) { return -EIO; } data->is_streaming = false; ret = dma_stop(cfg->dma_dev, cfg->rx_dma_channel); if (ret) { LOG_ERR("Unable to stop DMA (%d)", ret); return ret; } cam_hal_stop_streaming(&data->hal); return 0; } static int video_esp32_get_caps(const struct device *dev, enum video_endpoint_id ep, struct video_caps *caps) { const struct video_esp32_config *config = dev->config; if (ep != VIDEO_EP_OUT) { return -EINVAL; } /* ESP32 produces full frames */ caps->min_line_count = caps->max_line_count = LINE_COUNT_HEIGHT; /* Forward the message to the source device */ return video_get_caps(config->source_dev, ep, caps); } static int video_esp32_get_fmt(const struct device *dev, enum video_endpoint_id ep, struct video_format *fmt) { const struct video_esp32_config *cfg = dev->config; int ret = 0; LOG_DBG("Get format"); if (fmt == NULL || ep != VIDEO_EP_OUT) { return -EINVAL; } ret = video_get_format(cfg->source_dev, ep, fmt); if (ret) { LOG_ERR("Failed to get format from source"); return ret; } return 0; } static int video_esp32_set_fmt(const struct device *dev, enum video_endpoint_id ep, struct video_format *fmt) { const struct video_esp32_config *cfg = dev->config; struct video_esp32_data *data = dev->data; if (fmt == NULL || ep != VIDEO_EP_OUT) { return -EINVAL; } data->video_format = *fmt; return video_set_format(cfg->source_dev, ep, fmt); } static int video_esp32_enqueue(const struct device *dev, enum video_endpoint_id ep, struct video_buffer *vbuf) { struct video_esp32_data *data = dev->data; if (ep != VIDEO_EP_OUT) { return -EINVAL; } vbuf->bytesused = data->video_format.pitch * data->video_format.height; vbuf->line_offset = 0; k_fifo_put(&data->fifo_in, vbuf); return 0; } static int video_esp32_dequeue(const struct device *dev, enum video_endpoint_id ep, struct video_buffer **vbuf, k_timeout_t timeout) { struct video_esp32_data *data = dev->data; if (ep != VIDEO_EP_OUT) { return -EINVAL; } *vbuf = k_fifo_get(&data->fifo_out, timeout); LOG_DBG("Dequeue done, vbuf = %p", *vbuf); if (*vbuf == NULL) { return -EAGAIN; } return 0; } static int video_esp32_set_ctrl(const struct device *dev, unsigned int cid, void *value) { const struct video_esp32_config *cfg = dev->config; return video_set_ctrl(cfg->source_dev, cid, value); } static int video_esp32_get_ctrl(const struct device *dev, unsigned int cid, void *value) { const struct video_esp32_config *cfg = dev->config; return video_get_ctrl(cfg->source_dev, cid, value); } #ifdef CONFIG_POLL int video_esp32_set_signal(const struct device *dev, enum video_endpoint_id ep, struct k_poll_signal *sig) { struct video_esp32_data *data = dev->data; if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) { LOG_ERR("Invalid endpoint id"); return -EINVAL; } data->signal_out = sig; return 0; } #endif static void video_esp32_cam_ctrl_init(const struct device *dev) { const struct video_esp32_config *cfg = dev->config; struct video_esp32_data *data = dev->data; const cam_hal_config_t hal_cfg = { .port = 0, .byte_swap_en = cfg->invert_byte_order, }; cam_hal_init(&data->hal, &hal_cfg); cam_ll_reverse_dma_data_bit_order(data->hal.hw, cfg->invert_bit_order); cam_ll_enable_invert_pclk(data->hal.hw, cfg->invert_pclk); cam_ll_set_input_data_width(data->hal.hw, cfg->data_width); cam_ll_enable_invert_de(data->hal.hw, cfg->invert_de); cam_ll_enable_invert_vsync(data->hal.hw, cfg->invert_vsync); cam_ll_enable_invert_hsync(data->hal.hw, cfg->invert_hsync); } static int video_esp32_init(const struct device *dev) { const struct video_esp32_config *cfg = dev->config; struct video_esp32_data *data = dev->data; k_fifo_init(&data->fifo_in); k_fifo_init(&data->fifo_out); data->config = cfg; video_esp32_cam_ctrl_init(dev); if (!device_is_ready(cfg->dma_dev)) { LOG_ERR("DMA device not ready"); return -ENODEV; } return 0; } static const struct video_driver_api esp32_driver_api = { /* mandatory callbacks */ .set_format = video_esp32_set_fmt, .get_format = video_esp32_get_fmt, .stream_start = video_esp32_stream_start, .stream_stop = video_esp32_stream_stop, .get_caps = video_esp32_get_caps, /* optional callbacks */ .enqueue = video_esp32_enqueue, .dequeue = video_esp32_dequeue, .flush = NULL, .set_ctrl = video_esp32_set_ctrl, .get_ctrl = video_esp32_get_ctrl, #ifdef CONFIG_POLL .set_signal = video_esp32_set_signal, #endif }; PINCTRL_DT_INST_DEFINE(0); static const struct video_esp32_config esp32_config = { .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), .source_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, source)), .dma_dev = ESP32_DT_INST_DMA_CTLR(0, rx), .rx_dma_channel = DT_INST_DMAS_CELL_BY_NAME(0, rx, channel), .data_width = DT_INST_PROP_OR(0, data_width, 8), .invert_bit_order = DT_INST_PROP(0, invert_bit_order), .invert_byte_order = DT_INST_PROP(0, invert_byte_order), .invert_pclk = DT_INST_PROP(0, invert_pclk), .invert_de = DT_INST_PROP(0, invert_de), .invert_hsync = DT_INST_PROP(0, invert_hsync), .invert_vsync = DT_INST_PROP(0, invert_vsync), .cam_clk = DT_INST_PROP_OR(0, cam_clk, 0), .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(0)), .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(0, offset), }; static struct video_esp32_data esp32_data = {0}; DEVICE_DT_INST_DEFINE(0, video_esp32_init, NULL, &esp32_data, &esp32_config, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &esp32_driver_api); static int video_esp32_cam_init_master_clock(void) { int ret = 0; ret = pinctrl_apply_state(esp32_config.pcfg, PINCTRL_STATE_DEFAULT); if (ret < 0) { printk("video pinctrl setup failed (%d)", ret); return ret; } /* Enable peripheral */ if (!device_is_ready(esp32_config.clock_dev)) { return -ENODEV; } clock_control_on(esp32_config.clock_dev, esp32_config.clock_subsys); if (!esp32_config.cam_clk) { printk("No cam_clk specified\n"); return -EINVAL; } if (ESP32_CLK_CPU_PLL_160M % esp32_config.cam_clk) { printk("Invalid cam_clk value. It must be a divisor of 160M\n"); return -EINVAL; } /* Enable camera master clock output */ cam_ll_select_clk_src(0, LCD_CLK_SRC_PLL160M); cam_ll_set_group_clock_coeff(0, ESP32_CLK_CPU_PLL_160M / esp32_config.cam_clk, 0, 0); return 0; } SYS_INIT(video_esp32_cam_init_master_clock, PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);