drivers: video: Add MCUX CSI video driver
Add support for CMOS Sensor Interface video driver. Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
This commit is contained in:
parent
32169886ca
commit
43e58aed34
7 changed files with 516 additions and 0 deletions
|
@ -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)
|
||||
|
|
|
@ -89,4 +89,6 @@ source "drivers/ps2/Kconfig"
|
|||
|
||||
source "drivers/kscan/Kconfig"
|
||||
|
||||
source "drivers/video/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
7
drivers/video/CMakeLists.txt
Normal file
7
drivers/video/CMakeLists.txt
Normal file
|
@ -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)
|
33
drivers/video/Kconfig
Normal file
33
drivers/video/Kconfig
Normal file
|
@ -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
|
11
drivers/video/Kconfig.mcux_csi
Normal file
11
drivers/video/Kconfig.mcux_csi
Normal file
|
@ -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
|
51
drivers/video/video_common.c
Normal file
51
drivers/video/video_common.c
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2019, Linaro Limited
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <zephyr.h>
|
||||
|
||||
#include <drivers/video.h>
|
||||
|
||||
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;
|
||||
}
|
411
drivers/video/video_mcux_csi.c
Normal file
411
drivers/video/video_mcux_csi.c
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Copyright (c) 2019, Linaro Limited
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <zephyr.h>
|
||||
|
||||
#include <fsl_csi.h>
|
||||
|
||||
#ifdef CONFIG_HAS_MCUX_CACHE
|
||||
#include <fsl_cache.h>
|
||||
#endif
|
||||
|
||||
#include <drivers/video.h>
|
||||
|
||||
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
|
Loading…
Add table
Add a link
Reference in a new issue