From 43e58aed34e6838fc32e59a56ab35434600bb3dd Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 1 Jul 2019 14:41:19 +0200 Subject: [PATCH] drivers: video: Add MCUX CSI video driver Add support for CMOS Sensor Interface video driver. Signed-off-by: Loic Poulain --- drivers/CMakeLists.txt | 1 + drivers/Kconfig | 2 + drivers/video/CMakeLists.txt | 7 + drivers/video/Kconfig | 33 +++ drivers/video/Kconfig.mcux_csi | 11 + drivers/video/video_common.c | 51 ++++ drivers/video/video_mcux_csi.c | 411 +++++++++++++++++++++++++++++++++ 7 files changed, 516 insertions(+) create mode 100644 drivers/video/CMakeLists.txt create mode 100644 drivers/video/Kconfig create mode 100644 drivers/video/Kconfig.mcux_csi create mode 100644 drivers/video/video_common.c create mode 100644 drivers/video/video_mcux_csi.c diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index d966c65da31..c28be9257a8 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory_if_kconfig(hwinfo) add_subdirectory_if_kconfig(espi) add_subdirectory_if_kconfig(ps2) add_subdirectory_if_kconfig(kscan) +add_subdirectory_if_kconfig(video) add_subdirectory_ifdef(CONFIG_FLASH_HAS_DRIVER_ENABLED flash) add_subdirectory_ifdef(CONFIG_SERIAL_HAS_DRIVER serial) diff --git a/drivers/Kconfig b/drivers/Kconfig index 81282bb87f6..3ed97cb9be6 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -89,4 +89,6 @@ source "drivers/ps2/Kconfig" source "drivers/kscan/Kconfig" +source "drivers/video/Kconfig" + endmenu diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt new file mode 100644 index 00000000000..a950ba75ef1 --- /dev/null +++ b/drivers/video/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(video_common.c) + +zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig new file mode 100644 index 00000000000..370eeef1bdd --- /dev/null +++ b/drivers/video/Kconfig @@ -0,0 +1,33 @@ +# Kconfig - VIDEO driver configuration options + +# +# Copyright (c) 2019 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# VIDEO Drivers +# +menuconfig VIDEO + bool "VIDEO hardware support" + help + Enable support for the VIDEO. + +if VIDEO + +config VIDEO_BUFFER_POOL_SZ_MAX + int "Size of the largest buffer in the video pool" + default 1048576 + +config VIDEO_BUFFER_POOL_NUM_MAX + int "Number of maximum sized buffer in the video pool" + default 2 + +config VIDEO_BUFFER_POOL_ALIGN + int "Alignment of the video pool’s buffer" + default 64 + +source "drivers/video/Kconfig.mcux_csi" + +endif # VIDEO diff --git a/drivers/video/Kconfig.mcux_csi b/drivers/video/Kconfig.mcux_csi new file mode 100644 index 00000000000..1ffda5ed308 --- /dev/null +++ b/drivers/video/Kconfig.mcux_csi @@ -0,0 +1,11 @@ +# Kconfig - NXP MCUX CSI driver configuration options + +# +# Copyright (c) 2019, Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig VIDEO_MCUX_CSI + bool "NXP MCUX CMOS Sensor Interface (CSI) driver" + depends on HAS_MCUX_CSI diff --git a/drivers/video/video_common.c b/drivers/video/video_common.c new file mode 100644 index 00000000000..c02cc4a6f3f --- /dev/null +++ b/drivers/video/video_common.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019, Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include + +K_MEM_POOL_DEFINE(video_buffer_pool, + CONFIG_VIDEO_BUFFER_POOL_ALIGN, + CONFIG_VIDEO_BUFFER_POOL_SZ_MAX, + CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, + CONFIG_VIDEO_BUFFER_POOL_ALIGN); + +static struct video_buffer video_buf[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX]; + +struct video_buffer *video_buffer_alloc(size_t size) +{ + struct video_buffer *vbuf = NULL; + int i; + + /* find available video buffer */ + for (i = 0; i < ARRAY_SIZE(video_buf); i++) { + if (video_buf[i].buffer == NULL) { + vbuf = &video_buf[i]; + break; + } + } + + if (vbuf == NULL) { + return NULL; + } + + /* Alloc buffer memory */ + vbuf->buffer = k_mem_pool_malloc(&video_buffer_pool, size); + if (vbuf->buffer == NULL) { + return NULL; + } + + vbuf->size = size; + vbuf->bytesused = 0; + + return vbuf; +} + +void video_buffer_release(struct video_buffer *vbuf) +{ + k_free(vbuf->buffer); + vbuf->buffer = NULL; +} diff --git a/drivers/video/video_mcux_csi.c b/drivers/video/video_mcux_csi.c new file mode 100644 index 00000000000..0dd9a2e43d7 --- /dev/null +++ b/drivers/video/video_mcux_csi.c @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2019, Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include + +#ifdef CONFIG_HAS_MCUX_CACHE +#include +#endif + +#include + +struct video_mcux_csi_config { + CSI_Type *base; + char *sensor_label; +}; + +struct video_mcux_csi_data { + struct device *sensor_dev; + csi_config_t csi_config; + csi_handle_t csi_handle; + struct k_fifo fifo_in; + struct k_fifo fifo_out; + u32_t pixelformat; + struct k_poll_signal *signal; +}; + +static inline unsigned int video_pix_fmt_bpp(u32_t pixelformat) +{ + switch (pixelformat) { + case VIDEO_PIX_FMT_BGGR8: + case VIDEO_PIX_FMT_GBRG8: + case VIDEO_PIX_FMT_GRBG8: + case VIDEO_PIX_FMT_RGGB8: + return 1; + case VIDEO_PIX_FMT_RGB565: + return 2; + default: + return 0; + } +} + +static void __frame_done_cb(CSI_Type *base, csi_handle_t *handle, + status_t status, void *user_data) +{ + struct device *dev = user_data; + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + enum video_signal_result result = VIDEO_BUF_DONE; + struct video_buffer *vbuf; + u32_t buffer_addr; + + /* IRQ context */ + + if (status != kStatus_CSI_FrameDone) { + return; + } + + status = CSI_TransferGetFullBuffer(config->base, &(data->csi_handle), + &buffer_addr); + if (status != kStatus_Success) { + result = VIDEO_BUF_ERROR; + goto done; + } + + /* Get matching vbuf by addr */ + while ((vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT))) { + if ((u32_t)vbuf->buffer == buffer_addr) { + break; + } + + /* should never happen on ordered stream, requeue and break */ + k_fifo_put(&data->fifo_in, vbuf); + vbuf = NULL; + break; + } + + if (vbuf == NULL) { + result = VIDEO_BUF_ERROR; + goto done; + } + + vbuf->timestamp = k_uptime_get_32(); + +#ifdef CONFIG_HAS_MCUX_CACHE + DCACHE_InvalidateByRange(buffer_addr, vbuf->bytesused); +#endif + + k_fifo_put(&data->fifo_out, vbuf); + +done: + /* Trigger Event */ + if (data->signal) { + k_poll_signal_raise(data->signal, result); + } +} + +static int video_mcux_csi_set_fmt(struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + unsigned int bpp = video_pix_fmt_bpp(fmt->pixelformat); + status_t ret; + + if (!bpp || ep != VIDEO_EP_OUT) { + return -EINVAL; + } + + data->pixelformat = fmt->pixelformat; + data->csi_config.bytesPerPixel = bpp; + data->csi_config.linePitch_Bytes = fmt->pitch; + data->csi_config.polarityFlags = kCSI_HsyncActiveHigh | kCSI_DataLatchOnRisingEdge; + data->csi_config.workMode = kCSI_GatedClockMode; /* use VSYNC, HSYNC, and PIXCLK */ + data->csi_config.dataBus = kCSI_DataBus8Bit; + data->csi_config.useExtVsync = true; + data->csi_config.height = fmt->height; + data->csi_config.width = fmt->width; + + ret = CSI_Init(config->base, &data->csi_config); + if (ret != kStatus_Success) { + return -EIO; + } + + ret = CSI_TransferCreateHandle(config->base, &data->csi_handle, + __frame_done_cb, dev); + if (ret != kStatus_Success) { + return -EIO; + } + + if (data->sensor_dev && video_set_format(data->sensor_dev, ep, fmt)) { + return -EIO; + } + + return 0; +} + +static int video_mcux_csi_get_fmt(struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct video_mcux_csi_data *data = dev->driver_data; + + if (fmt == NULL || ep != VIDEO_EP_OUT) { + return -EINVAL; + } + + if (data->sensor_dev && !video_get_format(data->sensor_dev, ep, fmt)) { + /* align CSI with sensor fmt */ + return video_mcux_csi_set_fmt(dev, ep, fmt); + } + + fmt->pixelformat = data->pixelformat; + fmt->height = data->csi_config.height; + fmt->width = data->csi_config.width; + fmt->pitch = data->csi_config.linePitch_Bytes; + + return 0; +} + +static int video_mcux_csi_stream_start(struct device *dev) +{ + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + status_t ret; + + ret = CSI_TransferStart(config->base, &data->csi_handle); + if (ret != kStatus_Success) { + return -EIO; + } + + if (data->sensor_dev && video_stream_start(data->sensor_dev)) { + return -EIO; + } + + return 0; +} + +static int video_mcux_csi_stream_stop(struct device *dev) +{ + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + status_t ret; + + if (data->sensor_dev && video_stream_stop(data->sensor_dev)) { + return -EIO; + } + + ret = CSI_TransferStop(config->base, &data->csi_handle); + if (ret != kStatus_Success) { + return -EIO; + } + + return 0; +} + + +static int video_mcux_csi_flush(struct device *dev, enum video_endpoint_id ep, + bool cancel) +{ + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + struct video_buf *vbuf; + u32_t buffer_addr; + status_t ret; + + if (!cancel) { + /* wait for all buffer to be processed */ + do { + k_sleep(1); + } while (!k_fifo_is_empty(&data->fifo_in)); + } else { + /* Flush driver ouput queue */ + do { + ret = CSI_TransferGetFullBuffer(config->base, + &(data->csi_handle), + &buffer_addr); + } while (ret == kStatus_Success); + + while ((vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT))) { + k_fifo_put(&data->fifo_out, vbuf); + if (data->signal) { + k_poll_signal_raise(data->signal, + VIDEO_BUF_ABORTED); + } + } + } + + return 0; +} + +static int video_mcux_csi_enqueue(struct device *dev, enum video_endpoint_id ep, + struct video_buffer *vbuf) +{ + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + unsigned int to_read; + status_t ret; + + if (ep != VIDEO_EP_OUT) { + return -EINVAL; + } + + to_read = data->csi_config.linePitch_Bytes * data->csi_config.height; + vbuf->bytesused = to_read; + + ret = CSI_TransferSubmitEmptyBuffer(config->base, &data->csi_handle, + (u32_t)vbuf->buffer); + if (ret != kStatus_Success) { + return -EIO; + } + + k_fifo_put(&data->fifo_in, vbuf); + + return 0; +} + +static int video_mcux_csi_dequeue(struct device *dev, enum video_endpoint_id ep, + struct video_buffer **vbuf, + u32_t timeout) +{ + struct video_mcux_csi_data *data = dev->driver_data; + + if (ep != VIDEO_EP_OUT) { + return -EINVAL; + } + + *vbuf = k_fifo_get(&data->fifo_out, timeout); + if (*vbuf == NULL) { + return -EAGAIN; + } + + return 0; +} + +static inline int video_mcux_csi_set_ctrl(struct device *dev, unsigned int cid, + void *value) +{ + struct video_mcux_csi_data *data = dev->driver_data; + int ret = -ENOTSUP; + + /* Forward to sensor dev if any */ + if (data->sensor_dev) { + ret = video_set_ctrl(data->sensor_dev, cid, value); + } + + return ret; +} + +static inline int video_mcux_csi_get_ctrl(struct device *dev, unsigned int cid, + void *value) +{ + struct video_mcux_csi_data *data = dev->driver_data; + int ret = -ENOTSUP; + + /* Forward to sensor dev if any */ + if (data->sensor_dev) { + ret = video_get_ctrl(data->sensor_dev, cid, value); + } + + return ret; +} + +static int video_mcux_csi_get_caps(struct device *dev, + enum video_endpoint_id ep, + struct video_caps *caps) +{ + struct video_mcux_csi_data *data = dev->driver_data; + int err = -ENODEV; + + if (ep != VIDEO_EP_OUT) { + return -EINVAL; + } + + /* Just forward to sensor dev for now */ + if (data->sensor_dev) { + err = video_get_caps(data->sensor_dev, ep, caps); + } + + /* NXP MCUX CSI request at least 2 buffer before starting */ + caps->min_vbuf_count = 2; + + /* no sensor dev */ + return err; +} + +extern void CSI_DriverIRQHandler(void); +static void video_mcux_csi_isr(void *p) +{ + ARG_UNUSED(p); + CSI_DriverIRQHandler(); +} + +static int video_mcux_csi_init(struct device *dev) +{ + const struct video_mcux_csi_config *config = dev->config->config_info; + struct video_mcux_csi_data *data = dev->driver_data; + + k_fifo_init(&data->fifo_in); + k_fifo_init(&data->fifo_out); + + CSI_GetDefaultConfig(&data->csi_config); + + /* check if there is any sensor device (video ctrl device) */ + if (config->sensor_label) { + data->sensor_dev = device_get_binding(config->sensor_label); + if (data->sensor_dev == NULL) { + return -ENODEV; + } + } + + return 0; +} + +static int video_mcux_csi_set_signal(struct device *dev, + enum video_endpoint_id ep, + struct k_poll_signal *signal) +{ + struct video_mcux_csi_data *data = dev->driver_data; + + if (data->signal && signal != NULL) { + return -EALREADY; + } + + data->signal = signal; + + return 0; +} + +static const struct video_driver_api video_mcux_csi_driver_api = { + .set_format = video_mcux_csi_set_fmt, + .get_format = video_mcux_csi_get_fmt, + .stream_start = video_mcux_csi_stream_start, + .stream_stop = video_mcux_csi_stream_stop, + .flush = video_mcux_csi_flush, + .enqueue = video_mcux_csi_enqueue, + .dequeue = video_mcux_csi_dequeue, + .set_ctrl = video_mcux_csi_set_ctrl, + .get_ctrl = video_mcux_csi_get_ctrl, + .get_caps = video_mcux_csi_get_caps, + .set_signal = video_mcux_csi_set_signal, +}; + +#if 1 /* Unique Instance */ +static const struct video_mcux_csi_config video_mcux_csi_config_0 = { + .base = (CSI_Type *)DT_VIDEO_MCUX_CSI_BASE_ADDRESS, + .sensor_label = DT_VIDEO_MCUX_CSI_SENSOR_NAME, +}; + +static struct video_mcux_csi_data video_mcux_csi_data_0; + +static int video_mcux_csi_init_0(struct device *dev) +{ + IRQ_CONNECT(DT_VIDEO_MCUX_CSI_IRQ, DT_VIDEO_MCUX_CSI_IRQ_PRI, + video_mcux_csi_isr, NULL, 0); + + irq_enable(DT_VIDEO_MCUX_CSI_IRQ); + + video_mcux_csi_init(dev); + + return 0; +} + +DEVICE_AND_API_INIT(video_mcux_csi, DT_VIDEO_MCUX_CSI_NAME, + &video_mcux_csi_init_0, &video_mcux_csi_data_0, + &video_mcux_csi_config_0, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &video_mcux_csi_driver_api); +#endif