/*
* Copyright (c) 2019, Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <drivers/video.h>
#define VIDEO_PATTERN_COLOR_BAR 0
#define VIDEO_PATTERN_FPS 30
struct video_sw_generator_data {
struct device *dev;
struct video_format fmt;
struct k_fifo fifo_in;
struct k_fifo fifo_out;
struct k_delayed_work buf_work;
int pattern;
struct k_poll_signal *signal;
bool ctrl_hflip;
bool ctrl_vflip;
};
static int video_sw_generator_set_fmt(struct device *dev,
enum video_endpoint_id ep,
struct video_format *fmt)
{
struct video_sw_generator_data *data = dev->driver_data;
if (ep != VIDEO_EP_OUT) {
return -EINVAL;
}
data->fmt = *fmt;
return 0;
}
static int video_sw_generator_get_fmt(struct device *dev,
enum video_endpoint_id ep,
struct video_format *fmt)
{
struct video_sw_generator_data *data = dev->driver_data;
if (ep != VIDEO_EP_OUT) {
return -EINVAL;
}
*fmt = data->fmt;
return 0;
}
static int video_sw_generator_stream_start(struct device *dev)
{
struct video_sw_generator_data *data = dev->driver_data;
return k_delayed_work_submit(&data->buf_work, K_MSEC(33));
}
static int video_sw_generator_stream_stop(struct device *dev)
{
struct video_sw_generator_data *data = dev->driver_data;
k_delayed_work_cancel(&data->buf_work);
return 0;
}
/* Black, Blue, Red, Purple, Green, Aqua, Yellow, White */
u16_t rgb565_colorbar_value[] = { 0x0000, 0x001F, 0xF800, 0xF81F,
0x07E0, 0x07FF, 0xFFE0, 0xFFFF };
static void __fill_buffer_colorbar(struct video_sw_generator_data *data,
struct video_buffer *vbuf)
{
int bw = data->fmt.width / 8;
int h, w, i = 0;
for (h = 0; h < data->fmt.height; h++) {
for (w = 0; w < data->fmt.width; w++) {
int color_idx = data->ctrl_vflip ? 7 - w / bw : w / bw;
if (data->fmt.pixelformat == VIDEO_PIX_FMT_RGB565) {
u16_t *pixel = (u16_t *)&vbuf->buffer[i];
*pixel = rgb565_colorbar_value[color_idx];
i += 2;
}
}
}
vbuf->timestamp = k_uptime_get_32();
vbuf->bytesused = i;
}
static void __buffer_work(struct k_work *work)
{
struct video_sw_generator_data *data;
struct video_buffer *vbuf;
data = CONTAINER_OF(work, struct video_sw_generator_data, buf_work);
k_delayed_work_submit(&data->buf_work, 1000 / VIDEO_PATTERN_FPS);
vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT);
if (vbuf == NULL) {
return;
}
switch (data->pattern) {
case VIDEO_PATTERN_COLOR_BAR:
__fill_buffer_colorbar(data, vbuf);
break;
}
k_fifo_put(&data->fifo_out, vbuf);
/* Event */
if (data->signal) {
k_poll_signal_raise(data->signal, VIDEO_BUF_DONE);
}
k_yield();
}
static int video_sw_generator_enqueue(struct device *dev,
enum video_endpoint_id ep,
struct video_buffer *vbuf)
{
struct video_sw_generator_data *data = dev->driver_data;
if (ep != VIDEO_EP_OUT) {
return -EINVAL;
}
k_fifo_put(&data->fifo_in, vbuf);
return 0;
}
static int video_sw_generator_dequeue(struct device *dev,
enum video_endpoint_id ep,
struct video_buffer **vbuf, u32_t timeout)
{
struct video_sw_generator_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 int video_sw_generator_flush(struct device *dev,
enum video_endpoint_id ep,
bool cancel)
{
struct video_sw_generator_data *data = dev->driver_data;
struct video_buffer *vbuf;
if (!cancel) {
/* wait for all buffer to be processed */
do {
k_sleep(K_MSEC(1));
} while (!k_fifo_is_empty(&data->fifo_in));
} else {
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 const struct video_format_cap fmts[] = {
{
.pixelformat = VIDEO_PIX_FMT_RGB565,
.width_min = 640,
.width_max = 640,
.height_min = 480,
.height_max = 480,
.width_step = 0,
.height_step = 0,
},
{ 0 }
};
static int video_sw_generator_get_caps(struct device *dev,
enum video_endpoint_id ep,
struct video_caps *caps)
{
caps->format_caps = fmts;
caps->min_vbuf_count = 0;
return 0;
}
static int video_sw_generator_set_signal(struct device *dev,
enum video_endpoint_id ep,
struct k_poll_signal *signal)
{
struct video_sw_generator_data *data = dev->driver_data;
if (data->signal && signal != NULL) {
return -EALREADY;
}
data->signal = signal;
return 0;
}
static inline int video_sw_generator_set_ctrl(struct device *dev,
unsigned int cid,
void *value)
{
struct video_sw_generator_data *data = dev->driver_data;
switch (cid) {
case VIDEO_CID_VFLIP:
data->ctrl_vflip = (bool)value;
break;
default:
return -ENOTSUP;
}
return 0;
}
static const struct video_driver_api video_sw_generator_driver_api = {
.set_format = video_sw_generator_set_fmt,
.get_format = video_sw_generator_get_fmt,
.stream_start = video_sw_generator_stream_start,
.stream_stop = video_sw_generator_stream_stop,
.flush = video_sw_generator_flush,
.enqueue = video_sw_generator_enqueue,
.dequeue = video_sw_generator_dequeue,
.get_caps = video_sw_generator_get_caps,
.set_ctrl = video_sw_generator_set_ctrl,
.set_signal = video_sw_generator_set_signal,
};
static struct video_sw_generator_data video_sw_generator_data_0 = {
.fmt.width = 320,
.fmt.height = 240,
.fmt.pitch = 640,
.fmt.pixelformat = VIDEO_PIX_FMT_RGB565,
};
static int video_sw_generator_init(struct device *dev)
{
struct video_sw_generator_data *data = dev->driver_data;
data->dev = dev;
k_fifo_init(&data->fifo_in);
k_fifo_init(&data->fifo_out);
k_delayed_work_init(&data->buf_work, __buffer_work);
return 0;
}
DEVICE_AND_API_INIT(video_sw_generator, "VIDEO_SW_GENERATOR",
&video_sw_generator_init, &video_sw_generator_data_0, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&video_sw_generator_driver_api);