Skip to content

drivers: video: sw_generator: add more formats and try to simplify #85968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 217 additions & 74 deletions drivers/video/video_sw_generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

#define DT_DRV_COMPAT zephyr_sw_generator

#include <zephyr/kernel.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/drivers/video.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>

#include "video_ctrls.h"
#include "video_device.h"
Expand All @@ -26,6 +28,7 @@ LOG_MODULE_REGISTER(video_sw_generator, CONFIG_VIDEO_LOG_LEVEL);
* format. 60 fps is therefore chosen as a common value in practice.
*/
#define MAX_FRAME_RATE 60
#define MIN_FRAME_RATE 1

struct sw_ctrls {
struct video_ctrl hflip;
Expand All @@ -37,52 +40,48 @@ struct video_sw_generator_data {
struct video_format fmt;
struct k_fifo fifo_in;
struct k_fifo fifo_out;
struct k_work_delayable buf_work;
struct k_work_sync work_sync;
struct k_work_delayable work;
int pattern;
struct k_poll_signal *sig;
uint32_t frame_rate;
};

static const struct video_format_cap fmts[] = {{
.pixelformat = VIDEO_PIX_FMT_RGB565,
.width_min = 64,
.width_max = 1920,
.height_min = 64,
.height_max = 1080,
.width_step = 1,
.height_step = 1,
}, {
.pixelformat = VIDEO_PIX_FMT_XRGB32,
.width_min = 64,
.width_max = 1920,
.height_min = 64,
.height_max = 1080,
.width_step = 1,
.height_step = 1,
},
{0}};
#define VIDEO_SW_GENERATOR_FORMAT_CAP(pixfmt) \
{ \
.pixelformat = pixfmt, \
.width_min = 64, \
.width_max = 1920, \
.height_min = 64, \
.height_max = 1080, \
.width_step = 1, \
.height_step = 1, \
}

static const struct video_format_cap fmts[] = {
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_RGB24),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_YUYV),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_RGB565),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_XRGB32),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SRGGB8),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SGRBG8),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SBGGR8),
VIDEO_SW_GENERATOR_FORMAT_CAP(VIDEO_PIX_FMT_SGBRG8),
{0},
};

static int video_sw_generator_set_fmt(const struct device *dev, struct video_format *fmt)
{
struct video_sw_generator_data *data = dev->data;
int i = 0;

for (i = 0; i < ARRAY_SIZE(fmts); ++i) {
if (fmt->pixelformat == fmts[i].pixelformat && fmt->width >= fmts[i].width_min &&
fmt->width <= fmts[i].width_max && fmt->height >= fmts[i].height_min &&
fmt->height <= fmts[i].height_max) {
break;
}
}
size_t idx;
int ret;

if (i == ARRAY_SIZE(fmts)) {
ret = video_format_caps_index(fmts, fmt, &idx);
if (ret < 0) {
LOG_ERR("Unsupported pixel format or resolution");
return -ENOTSUP;
return ret;
}

data->fmt = *fmt;

return 0;
}

Expand All @@ -99,56 +98,201 @@ static int video_sw_generator_set_stream(const struct device *dev, bool enable,
enum video_buf_type type)
{
struct video_sw_generator_data *data = dev->data;
struct k_work_sync work_sync = {0};

if (enable) {
k_work_schedule(&data->buf_work, K_MSEC(1000 / data->frame_rate));
k_work_schedule(&data->work, K_MSEC(1000 / data->frame_rate));
} else {
k_work_cancel_delayable_sync(&data->buf_work, &data->work_sync);
k_work_cancel_delayable_sync(&data->work, &work_sync);
}

return 0;
}

/* Black, Blue, Red, Purple, Green, Aqua, Yellow, White */
uint16_t rgb565_colorbar_value[] = {0x0000, 0x001F, 0xF800, 0xF81F, 0x07E0, 0x07FF, 0xFFE0, 0xFFFF};
static const uint8_t pattern_rggb_idx[] = {0, 1, 1, 2};
static const uint8_t pattern_bggr_idx[] = {2, 1, 1, 0};
static const uint8_t pattern_gbrg_idx[] = {1, 2, 0, 1};
static const uint8_t pattern_grbg_idx[] = {1, 0, 2, 1};

/* White, Yellow, Cyan, Green, Magenta, Red, Blue, Black */

static const uint16_t pattern_8bars_yuv_bt709[8][3] = {
{0xFE, 0x80, 0x7F}, {0xEC, 0x00, 0x8B}, {0xC8, 0x9D, 0x00}, {0xB6, 0x1D, 0x0C},
{0x48, 0xE2, 0xF3}, {0x36, 0x62, 0xFF}, {0x12, 0xFF, 0x74}, {0x00, 0x80, 0x80},
};

uint32_t xrgb32_colorbar_value[] = {0xFF000000, 0xFF0000FF, 0xFFFF0000, 0xFFFF00FF,
0xFF00FF00, 0xFF00FFFF, 0xFFFFFF00, 0xFFFFFFFF};
static const uint16_t pattern_8bars_rgb[8][3] = {
{0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0x00}, {0x00, 0xFF, 0xFF}, {0x00, 0xFF, 0x00},
{0xFF, 0x00, 0xFF}, {0xFF, 0x00, 0x00}, {0x00, 0x00, 0xFF}, {0x00, 0x00, 0x00},
};

static void __fill_buffer_colorbar(struct video_sw_generator_data *data, struct video_buffer *vbuf)
static inline int video_sw_generator_get_color_idx(uint16_t w, uint16_t width, bool hflip)
{
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->ctrls.hflip.val ? 7 - w / bw : w / bw;
if (data->fmt.pixelformat == VIDEO_PIX_FMT_RGB565) {
uint16_t *pixel = (uint16_t *)&vbuf->buffer[i];
*pixel = rgb565_colorbar_value[color_idx];
i += 2;
} else if (data->fmt.pixelformat == VIDEO_PIX_FMT_XRGB32) {
uint32_t *pixel = (uint32_t *)&vbuf->buffer[i];
*pixel = xrgb32_colorbar_value[color_idx];
i += 4;
}
/* If hflip is on, start from the right instead */
w = (hflip) ? (width - w - 1) : (w);

/* Downscale from w/width to #/8 */
return 8 * w / width;
}

static uint16_t video_sw_generator_fill_yuyv(uint8_t *buffer, uint16_t width, bool hflip)
{
if (width % 2 != 0) {
LOG_ERR("YUYV pixels always go by pairs");
return 0;
}

for (size_t w = 0; w < width; w += 2) {
int color_idx = video_sw_generator_get_color_idx(w, width, hflip);

buffer[w * 2 + 0] = pattern_8bars_yuv_bt709[color_idx][0];
buffer[w * 2 + 1] = pattern_8bars_yuv_bt709[color_idx][1];
buffer[w * 2 + 2] = pattern_8bars_yuv_bt709[color_idx][0];
buffer[w * 2 + 3] = pattern_8bars_yuv_bt709[color_idx][2];
}
return 1;
}

static uint16_t video_sw_generator_fill_xrgb32(uint8_t *buffer, uint16_t width, bool hflip)
{
for (size_t w = 0; w < width; w++) {
int color_idx = video_sw_generator_get_color_idx(w, width, hflip);

buffer[w * 4 + 0] = 0xff;
buffer[w * 4 + 1] = pattern_8bars_rgb[color_idx][0];
buffer[w * 4 + 2] = pattern_8bars_rgb[color_idx][1];
buffer[w * 4 + 3] = pattern_8bars_rgb[color_idx][2];
}
return 1;
}

static uint16_t video_sw_generator_fill_rgb24(uint8_t *buffer, uint16_t width, bool hflip)
{
for (size_t w = 0; w < width; w++) {
int color_idx = video_sw_generator_get_color_idx(w, width, hflip);

buffer[w * 3 + 0] = pattern_8bars_rgb[color_idx][0];
buffer[w * 3 + 1] = pattern_8bars_rgb[color_idx][1];
buffer[w * 3 + 2] = pattern_8bars_rgb[color_idx][2];
}
return 1;
}

static uint16_t video_sw_generator_fill_rgb565(uint8_t *buffer, uint16_t width, bool hflip)
{
for (size_t w = 0; w < width; w++) {
int color_idx = video_sw_generator_get_color_idx(w, width, hflip);
uint8_t r = pattern_8bars_rgb[color_idx][0] >> (8 - 5);
uint8_t g = pattern_8bars_rgb[color_idx][1] >> (8 - 6);
uint8_t b = pattern_8bars_rgb[color_idx][2] >> (8 - 5);

((uint16_t *)buffer)[w] = sys_cpu_to_le16((r << 11) | (g << 6) | (b << 0));
}
return 1;
}

static uint16_t video_sw_generator_fill_bayer8(uint8_t *buffer, uint16_t width, bool hflip,
const uint8_t *bayer_idx)
{
uint8_t *row0 = buffer + 0;
uint8_t *row1 = buffer + width;

if (width % 2 != 0) {
LOG_ERR("Bayer pixels always go by pairs (vertically and horizontally)");
return 0;
}

for (size_t w = 0; w < width; w += 2) {
int color_idx = video_sw_generator_get_color_idx(w, width, hflip);

row0[w + 0] = pattern_8bars_rgb[color_idx][bayer_idx[0]];
row0[w + 1] = pattern_8bars_rgb[color_idx][bayer_idx[1]];
row1[w + 0] = pattern_8bars_rgb[color_idx][bayer_idx[2]];
row1[w + 1] = pattern_8bars_rgb[color_idx][bayer_idx[3]];
}
return 2;
}

static int video_sw_generator_fill(const struct device *const dev, struct video_buffer *vbuf)
{
struct video_sw_generator_data *data = dev->data;
struct video_format *fmt = &data->fmt;
size_t pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE;
bool hflip = data->ctrls.hflip.val;
uint16_t lines = 0;

if (vbuf->size < pitch * 2) {
LOG_ERR("At least 2 lines needed for bayer formats support");
return -EINVAL;
}

/* Fill the first row of the emulated framebuffer */
switch (data->fmt.pixelformat) {
case VIDEO_PIX_FMT_YUYV:
lines = video_sw_generator_fill_yuyv(vbuf->buffer, fmt->width, hflip);
break;
case VIDEO_PIX_FMT_XRGB32:
lines = video_sw_generator_fill_xrgb32(vbuf->buffer, fmt->width, hflip);
break;
case VIDEO_PIX_FMT_RGB24:
lines = video_sw_generator_fill_rgb24(vbuf->buffer, fmt->width, hflip);
break;
case VIDEO_PIX_FMT_RGB565:
lines = video_sw_generator_fill_rgb565(vbuf->buffer, fmt->width, hflip);
break;
case VIDEO_PIX_FMT_SRGGB8:
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip,
pattern_rggb_idx);
break;
case VIDEO_PIX_FMT_SGBRG8:
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip,
pattern_gbrg_idx);
break;
case VIDEO_PIX_FMT_SBGGR8:
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip,
pattern_bggr_idx);
break;
case VIDEO_PIX_FMT_SGRBG8:
lines = video_sw_generator_fill_bayer8(vbuf->buffer, fmt->width, hflip,
pattern_grbg_idx);
break;
default:
CODE_UNREACHABLE;
break;
}

if (lines == 0) {
return -EINVAL;
}

/* How much was filled in so far */
vbuf->bytesused = data->fmt.pitch * lines;

/* Duplicate the first line(s) all over the buffer */
for (int h = lines; h < data->fmt.height; h += lines) {
if (vbuf->size < vbuf->bytesused + pitch * lines) {
LOG_WRN("Generation stopped early: buffer too small");
break;
}
memcpy(vbuf->buffer + h * pitch, vbuf->buffer, pitch * lines);
vbuf->bytesused += pitch * lines;
}

vbuf->timestamp = k_uptime_get_32();
vbuf->bytesused = i;
vbuf->line_offset = 0;

return 0;
}

static void __buffer_work(struct k_work *work)
static void video_sw_generator_worker(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct video_sw_generator_data *data;
struct video_buffer *vbuf;

data = CONTAINER_OF(dwork, struct video_sw_generator_data, buf_work);
data = CONTAINER_OF(dwork, struct video_sw_generator_data, work);

k_work_reschedule(&data->buf_work, K_MSEC(1000 / data->frame_rate));
k_work_reschedule(&data->work, K_MSEC(1000 / data->frame_rate));

vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT);
if (vbuf == NULL) {
Expand All @@ -157,7 +301,7 @@ static void __buffer_work(struct k_work *work)

switch (data->pattern) {
case VIDEO_PATTERN_COLOR_BAR:
__fill_buffer_colorbar(data, vbuf);
video_sw_generator_fill(data->dev, vbuf);
break;
}

Expand Down Expand Up @@ -244,13 +388,12 @@ static int video_sw_generator_set_frmival(const struct device *dev, struct video
{
struct video_sw_generator_data *data = dev->data;

if (frmival->denominator && frmival->numerator) {
data->frame_rate = MIN(DIV_ROUND_CLOSEST(frmival->denominator, frmival->numerator),
MAX_FRAME_RATE);
} else {
if (frmival->denominator == 0 || frmival->numerator == 0) {
return -EINVAL;
}

data->frame_rate = CLAMP(DIV_ROUND_CLOSEST(frmival->denominator, frmival->numerator),
MIN_FRAME_RATE, MAX_FRAME_RATE);
frmival->numerator = 1;
frmival->denominator = data->frame_rate;

Expand All @@ -269,17 +412,17 @@ static int video_sw_generator_get_frmival(const struct device *dev, struct video

static int video_sw_generator_enum_frmival(const struct device *dev, struct video_frmival_enum *fie)
{
int i = 0;
size_t idx;
int ret;

while (fmts[i].pixelformat && (fmts[i].pixelformat != fie->format->pixelformat)) {
i++;
if (fie->index >= 1) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very minor point, should match this line with the commit log which mentions fie.index > 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

return -ERANGE;
}

if ((i == ARRAY_SIZE(fmts)) || (fie->format->width > fmts[i].width_max) ||
(fie->format->width < fmts[i].width_min) ||
(fie->format->height > fmts[i].height_max) ||
(fie->format->height < fmts[i].height_min)) {
return -EINVAL;
ret = video_format_caps_index(fmts, fie->format, &idx);
if (ret < 0) {
LOG_ERR("Unsupported pixel format or resolution");
return ret;
}

fie->type = VIDEO_FRMIVAL_TYPE_STEPWISE;
Expand Down Expand Up @@ -333,7 +476,7 @@ static int video_sw_generator_init(const struct device *dev)
data->dev = dev;
k_fifo_init(&data->fifo_in);
k_fifo_init(&data->fifo_out);
k_work_init_delayable(&data->buf_work, __buffer_work);
k_work_init_delayable(&data->work, video_sw_generator_worker);

return video_sw_generator_init_controls(dev);
}
Expand Down
Loading