On Fri, 2020-03-06 at 13:57 -0800, Tim Harvey wrote: > On Fri, Mar 6, 2020 at 1:00 PM Adrian Ratiu <adrian.ratiu@xxxxxxxxxxxxx> wrote: > > Hi > > > > On Fri, 06 Mar 2020, Tim Harvey <tharvey@xxxxxxxxxxxxx> wrote: > > > On Wed, Nov 13, 2019 at 7:06 AM Philipp Zabel > > > <p.zabel@xxxxxxxxxxxxxx> wrote: > > > > This patch adds JPEG decoding support for CODA960, handling the > > > > JPEG hardware directly. A separate JPEG decoder video device is > > > > created due to the separate hardware unit and different > > > > supported pixel formats. While the hardware can not change > > > > subsampling on the fly, it can decode 4:2:2 subsampled JPEG > > > > images into YUV422P. > > > > > > > > Signed-off-by: Philipp Zabel <p.zabel@xxxxxxxxxxxxxx> > > > > [m.felsch@xxxxxxxxxxxxxx: fix qsequence counting by explicitly > > > > checking for the !use_bit case] > > > > Signed-off-by: Marco Felsch <m.felsch@xxxxxxxxxxxxxx> --- > > > > drivers/media/platform/Kconfig | 1 + > > > > drivers/media/platform/coda/coda-common.c | 124 ++++- > > > > drivers/media/platform/coda/coda-jpeg.c | 551 > > > > ++++++++++++++++++++++ drivers/media/platform/coda/coda.h > > > > | 11 +- 4 files changed, 683 insertions(+), 4 deletions(-) > > > > > > > > diff --git a/drivers/media/platform/Kconfig > > > > b/drivers/media/platform/Kconfig index > > > > e84f35d3a68e..c989a2a45c60 100644 --- > > > > a/drivers/media/platform/Kconfig +++ > > > > b/drivers/media/platform/Kconfig @@ -180,6 +180,7 @@ config > > > > VIDEO_CODA > > > > select SRAM select VIDEOBUF2_DMA_CONTIG select > > > > VIDEOBUF2_VMALLOC > > > > + select V4L2_JPEG_HELPER > > > > select V4L2_MEM2MEM_DEV select GENERIC_ALLOCATOR help > > > > diff --git a/drivers/media/platform/coda/coda-common.c > > > > b/drivers/media/platform/coda/coda-common.c index > > > > 9aa5aa837c4e..d8f988b207b1 100644 --- > > > > a/drivers/media/platform/coda/coda-common.c +++ > > > > b/drivers/media/platform/coda/coda-common.c @@ -159,6 +159,7 @@ > > > > static const struct coda_codec coda9_codecs[] = { > > > > CODA_CODEC(CODA9_MODE_DECODE_H264, V4L2_PIX_FMT_H264, > > > > V4L2_PIX_FMT_YUV420, 1920, 1088), > > > > CODA_CODEC(CODA9_MODE_DECODE_MP2, V4L2_PIX_FMT_MPEG2, > > > > V4L2_PIX_FMT_YUV420, 1920, 1088), > > > > CODA_CODEC(CODA9_MODE_DECODE_MP4, V4L2_PIX_FMT_MPEG4, > > > > V4L2_PIX_FMT_YUV420, 1920, 1088), > > > > + CODA_CODEC(CODA9_MODE_DECODE_MJPG, V4L2_PIX_FMT_JPEG, > > > > V4L2_PIX_FMT_YUV420, 8192, 8192), > > > > }; > > > > > > > > struct coda_video_device { > > > > @@ -252,6 +253,22 @@ static const struct coda_video_device > > > > coda9_jpeg_encoder = { > > > > }, > > > > }; > > > > > > > > +static const struct coda_video_device coda9_jpeg_decoder = { + > > > > .name = "coda-jpeg-decoder", + .type = CODA_INST_DECODER, > > > > + .ops = &coda9_jpeg_decode_ops, + .direct = true, > > > > + .src_formats = { + V4L2_PIX_FMT_JPEG, + > > > > }, + .dst_formats = { + V4L2_PIX_FMT_NV12, > > > > + V4L2_PIX_FMT_YUV420, + > > > > V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUV422P, + > > > > }, +}; + > > > > static const struct coda_video_device *codadx6_video_devices[] > > > > = { > > > > &coda_bit_encoder, > > > > }; > > > > @@ -270,6 +287,7 @@ static const struct coda_video_device > > > > *coda7_video_devices[] = { > > > > > > > > static const struct coda_video_device *coda9_video_devices[] = > > > > { > > > > &coda9_jpeg_encoder, > > > > + &coda9_jpeg_decoder, > > > > &coda_bit_encoder, &coda_bit_decoder, > > > > }; > > > > @@ -411,6 +429,12 @@ static int coda_querycap(struct file > > > > *file, void *priv, > > > > return 0; > > > > } > > > > > > > > +static const u32 coda_formats_420[CODA_MAX_FORMATS] = { + > > > > V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + > > > > V4L2_PIX_FMT_YVU420, +}; + > > > > static int coda_enum_fmt(struct file *file, void *priv, > > > > struct v4l2_fmtdesc *f) > > > > { > > > > @@ -421,10 +445,31 @@ static int coda_enum_fmt(struct file > > > > *file, void *priv, > > > > > > > > if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) > > > > formats = cvd->src_formats; > > > > - else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + > > > > else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + > > > > struct coda_q_data *q_data_src; + struct > > > > vb2_queue *src_vq; + > > > > formats = cvd->dst_formats; > > > > - else + + /* + * If the > > > > source format is already fixed, only allow the same + > > > > * chroma subsampling. + */ + > > > > q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + > > > > src_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + > > > > V4L2_BUF_TYPE_VIDEO_OUTPUT); + if > > > > (q_data_src->fourcc == V4L2_PIX_FMT_JPEG && + > > > > vb2_is_streaming(src_vq)) { + if > > > > (ctx->params.jpeg_format == 0) { + > > > > formats = coda_formats_420; + } else if > > > > (ctx->params.jpeg_format == 1) { + > > > > f->pixelformat = V4L2_PIX_FMT_YUV422P; + > > > > return f->index ? -EINVAL : 0; + } + > > > > } + } else { > > > > return -EINVAL; > > > > + } > > > > > > > > if (f->index >= CODA_MAX_FORMATS || formats[f->index] > > > > == 0) > > > > return -EINVAL; > > > > @@ -614,12 +659,21 @@ static int coda_try_fmt_vid_cap(struct > > > > file *file, void *priv, > > > > > > > > /* > > > > * If the source format is already fixed, only allow > > > > the same output > > > > - * resolution + * resolution. When decoding JPEG > > > > images, we also have to make sure to + * use the same > > > > chroma subsampling. > > > > */ > > > > src_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, > > > > V4L2_BUF_TYPE_VIDEO_OUTPUT); if > > > > (vb2_is_streaming(src_vq)) { > > > > f->fmt.pix.width = q_data_src->width; > > > > f->fmt.pix.height = q_data_src->height; > > > > + + if (q_data_src->fourcc == V4L2_PIX_FMT_JPEG) > > > > { + if (ctx->params.jpeg_format == 0 && + > > > > f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + > > > > f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + > > > > else if (ctx->params.jpeg_format == 1) + > > > > f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + > > > > } > > > > } > > > > > > > > f->fmt.pix.colorspace = ctx->colorspace; > > > > @@ -747,6 +801,7 @@ static int coda_s_fmt(struct coda_ctx *ctx, > > > > struct v4l2_format *f, > > > > /* else fall through */ > > > > case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: > > > > + case V4L2_PIX_FMT_YUV422P: > > > > ctx->tiled_map_type = GDI_LINEAR_FRAME_MAP; > > > > break; > > > > default: > > > > @@ -1894,6 +1949,45 @@ static int coda_start_streaming(struct > > > > vb2_queue *q, unsigned int count) > > > > } > > > > } > > > > > > > > + /* + * Check the first input > > > > JPEG buffer to determine chroma + * subsampling. > > > > + */ + if (q_data_src->fourcc == > > > > V4L2_PIX_FMT_JPEG) { + buf = > > > > v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + > > > > ret = coda_jpeg_decode_header(ctx, &buf->vb2_buf); + > > > > if (ret < 0) { + > > > > v4l2_err(v4l2_dev, + > > > > "failed to decode JPEG header: %d\n", + > > > > ret); + goto err; + > > > > } + + q_data_dst = get_q_data(ctx, + > > > > V4L2_BUF_TYPE_VIDEO_CAPTURE); + > > > > q_data_dst->width = round_up(q_data_src->width, 16); + > > > > q_data_dst->bytesperline = q_data_dst->width; + > > > > if (ctx->params.jpeg_format == 0) { + > > > > q_data_dst->height = + > > > > round_up(q_data_src->height, 16); + > > > > q_data_dst->sizeimage = + > > > > q_data_dst->bytesperline * + > > > > q_data_dst->height * 3 / 2; + if > > > > (q_data_dst->fourcc != V4L2_PIX_FMT_YUV420) + > > > > q_data_dst->fourcc = V4L2_PIX_FMT_NV12; + > > > > } else { + q_data_dst->height = + > > > > round_up(q_data_src->height, 8); + > > > > q_data_dst->sizeimage = + > > > > q_data_dst->bytesperline * + > > > > q_data_dst->height * 2; + > > > > q_data_dst->fourcc = V4L2_PIX_FMT_YUV422P; + > > > > } + q_data_dst->rect.left = 0; + > > > > q_data_dst->rect.top = 0; + > > > > q_data_dst->rect.width = q_data_src->width; + > > > > q_data_dst->rect.height = q_data_src->height; + } > > > > ctx->streamon_out = 1; > > > > } else { > > > > ctx->streamon_cap = 1; > > > > @@ -2132,6 +2226,30 @@ static int coda_s_ctrl(struct v4l2_ctrl > > > > *ctrl) > > > > case V4L2_CID_JPEG_RESTART_INTERVAL: > > > > ctx->params.jpeg_restart_interval = ctrl->val; > > > > break; > > > > + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + > > > > switch (ctrl->val) { + case > > > > V4L2_JPEG_CHROMA_SUBSAMPLING_444: + > > > > ctx->params.jpeg_chroma_subsampling[0] = 0x11; + > > > > ctx->params.jpeg_chroma_subsampling[1] = 0x11; + > > > > ctx->params.jpeg_chroma_subsampling[2] = 0x11; + > > > > break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + > > > > ctx->params.jpeg_chroma_subsampling[0] = 0x21; + > > > > ctx->params.jpeg_chroma_subsampling[1] = 0x11; + > > > > ctx->params.jpeg_chroma_subsampling[2] = 0x11; + > > > > break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + > > > > ctx->params.jpeg_chroma_subsampling[0] = 0x22; + > > > > ctx->params.jpeg_chroma_subsampling[1] = 0x11; + > > > > ctx->params.jpeg_chroma_subsampling[2] = 0x11; + > > > > break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY: > > > > + ctx->params.jpeg_chroma_subsampling[0] > > > > = 0x21; + > > > > ctx->params.jpeg_chroma_subsampling[1] = 0x00; + > > > > ctx->params.jpeg_chroma_subsampling[2] = 0x00; + > > > > break; + } + break; > > > > case V4L2_CID_MPEG_VIDEO_VBV_DELAY: > > > > ctx->params.vbv_delay = ctrl->val; break; > > > > diff --git a/drivers/media/platform/coda/coda-jpeg.c > > > > b/drivers/media/platform/coda/coda-jpeg.c index > > > > c5cfa985c829..3a6aa027c82b 100644 --- > > > > a/drivers/media/platform/coda/coda-jpeg.c +++ > > > > b/drivers/media/platform/coda/coda-jpeg.c @@ -15,6 +15,7 @@ > > > > > > > > #include <media/v4l2-common.h> #include <media/v4l2-fh.h> > > > > +#include <media/v4l2-jpeg.h> > > > > #include <media/v4l2-mem2mem.h> #include > > > > <media/videobuf2-core.h> #include > > > > <media/videobuf2-dma-contig.h> > > > > @@ -37,6 +38,18 @@ enum { > > > > CODA9_JPEG_FORMAT_400, > > > > }; > > > > > > > > +struct coda_huff_tab { + u8 dc_bits[2][16]; + > > > > u8 dc_values[2][12 + 4]; /* padded to 32-bit */ + u8 > > > > ac_bits[2][16]; + u8 ac_values[2][162 + 2]; /* > > > > padded to 32-bit */ + + /* DC Luma, DC Chroma, AC Luma, > > > > AC Chroma */ + s16 min[4 * 16]; + s16 max[4 > > > > * 16]; + s8 ptr[4 * 16]; +}; + > > > > /* > > > > * Typical Huffman tables for 8-bit precision luminance and * > > > > chrominance from JPEG ITU-T.81 (ISO/IEC 10918-1) Annex K.3 > > > > @@ -245,6 +258,273 @@ bool coda_jpeg_check_buffer(struct > > > > coda_ctx *ctx, struct vb2_buffer *vb) > > > > return false; > > > > } > > > > > > > > +static int coda9_jpeg_gen_dec_huff_tab(struct coda_ctx *ctx, > > > > int tab_num); + +int coda_jpeg_decode_header(struct coda_ctx > > > > *ctx, struct vb2_buffer *vb) +{ + struct coda_dev *dev = > > > > ctx->dev; + u8 *buf = vb2_plane_vaddr(vb, 0); + > > > > size_t len = vb2_get_plane_payload(vb, 0); + struct > > > > v4l2_jpeg_scan_header scan_header; + struct > > > > v4l2_jpeg_reference quantization_tables[4] = { 0 }; + > > > > struct v4l2_jpeg_reference huffman_tables[4] = { 0 }; + > > > > struct v4l2_jpeg_header header = { + .scan = > > > > &scan_header, + .quantization_tables = > > > > quantization_tables, + .huffman_tables = > > > > huffman_tables, + }; + struct coda_q_data > > > > *q_data_src; + struct coda_huff_tab *huff_tab; + > > > > int i, j, ret; + + ret = v4l2_jpeg_parse_header(buf, len, > > > > &header); + if (ret < 0) { + > > > > v4l2_err(&dev->v4l2_dev, "failed to parse header\n"); + > > > > return ret; + } + + > > > > ctx->params.jpeg_restart_interval = header.restart_interval; + > > > > + /* check frame header */ + if > > > > (header.frame.height > ctx->codec->max_h || + > > > > header.frame.width > ctx->codec->max_w) { + > > > > v4l2_err(&dev->v4l2_dev, "invalid dimensions: %dx%d\n", + > > > > header.frame.width, header.frame.height); + > > > > return -EINVAL; + } + + q_data_src = > > > > get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if > > > > (header.frame.height != q_data_src->height || + > > > > header.frame.width != q_data_src->width) { + > > > > v4l2_err(&dev->v4l2_dev, + "dimensions > > > > don't match format: %dx%d\n", + > > > > header.frame.width, header.frame.height); + > > > > return -EINVAL; + } + + /* install quantization > > > > tables */ + if (quantization_tables[3].start) { + > > > > v4l2_err(&dev->v4l2_dev, + "only 3 > > > > quantization tables supported\n"); + return > > > > -EINVAL; + } + for (i = 0; i < 3; i++) { + > > > > if (!quantization_tables[i].start) + > > > > continue; + if (!ctx->params.jpeg_qmat_tab[i]) + > > > > ctx->params.jpeg_qmat_tab[i] = kmalloc(64, GFP_KERNEL); + > > > > memcpy(ctx->params.jpeg_qmat_tab[i], + > > > > quantization_tables[i].start, 64); + } + + /* > > > > install Huffman tables */ + for (i = 0; i < 4; i++) { + > > > > if (!huffman_tables[i].start) { + > > > > v4l2_err(&dev->v4l2_dev, "missing Huffman table\n"); + > > > > return -EINVAL; + } + if > > > > (huffman_tables[i].length != ((i & 2) ? 178 : 28)) { + > > > > v4l2_err(&dev->v4l2_dev, + > > > > "invalid Huffman table %d length: %zu\n", i, + > > > > huffman_tables[i].length); + return > > > > -EINVAL; + } + } + huff_tab = > > > > ctx->params.jpeg_huff_tab; + if (!huff_tab) { + > > > > huff_tab = kzalloc(sizeof(*huff_tab), GFP_KERNEL); + > > > > if (!huff_tab) + return -ENOMEM; + > > > > ctx->params.jpeg_huff_tab = huff_tab; + } + > > > > memcpy(huff_tab->dc_bits[0], huffman_tables[0].start, 16); + > > > > memcpy(huff_tab->dc_values[0], huffman_tables[0].start + 16, > > > > 12); + memcpy(huff_tab->dc_bits[1], > > > > huffman_tables[1].start, 16); + > > > > memcpy(huff_tab->dc_values[1], huffman_tables[1].start + 16, > > > > 12); + memcpy(huff_tab->ac_bits[0], > > > > huffman_tables[2].start, 16); + > > > > memcpy(huff_tab->ac_values[0], huffman_tables[2].start + 16, > > > > 162); + memcpy(huff_tab->ac_bits[1], > > > > huffman_tables[3].start, 16); + > > > > memcpy(huff_tab->ac_values[1], huffman_tables[3].start + 16, > > > > 162); + + /* check scan header */ + for (i = 0; i < > > > > scan_header.num_components; i++) { + struct > > > > v4l2_jpeg_scan_component_spec *scan_component; + + > > > > scan_component = &scan_header.component[i]; + for > > > > (j = 0; j < header.frame.num_components; j++) { + > > > > if (header.frame.component[j].component_identifier == + > > > > scan_component->component_selector) + > > > > break; + } + if (j == > > > > header.frame.num_components) + continue; > > > > + + ctx->params.jpeg_huff_dc_index[j] = + > > > > scan_component->dc_entropy_coding_table_selector; + > > > > ctx->params.jpeg_huff_ac_index[j] = + > > > > scan_component->ac_entropy_coding_table_selector; + } + + > > > > /* Generate Huffman table information */ + for (i = 0; i > > > > < 4; i++) + coda9_jpeg_gen_dec_huff_tab(ctx, i); > > > > + + /* start of entropy coded segment */ + > > > > ctx->jpeg_ecs_offset = header.ecs_offset; + + if > > > > (header.frame.subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) > > > > + ctx->params.jpeg_format = 1; + + return > > > > 0; +} + +static inline void coda9_jpeg_write_huff_values(struct > > > > coda_dev *dev, u8 *bits, + > > > > s8 *values, int num_values) +{ + int huff_length, i; + + > > > > for (huff_length = 0, i = 0; i < 16; i++) + > > > > huff_length += bits[i]; + for (i = huff_length; i < > > > > num_values; i++) + values[i] = -1; + for (i > > > > = 0; i < num_values; i++) + coda_write(dev, > > > > (s32)values[i], CODA9_REG_JPEG_HUFF_DATA); +} + +static int > > > > coda9_jpeg_dec_huff_setup(struct coda_ctx *ctx) +{ + > > > > struct coda_huff_tab *huff_tab = ctx->params.jpeg_huff_tab; + > > > > struct coda_dev *dev = ctx->dev; + s16 *huff_min = > > > > huff_tab->min; + s16 *huff_max = huff_tab->max; + > > > > s8 *huff_ptr = huff_tab->ptr; + int i; + + /* MIN > > > > Tables */ + coda_write(dev, 0x003, > > > > CODA9_REG_JPEG_HUFF_CTRL); + coda_write(dev, 0x000, > > > > CODA9_REG_JPEG_HUFF_ADDR); + for (i = 0; i < 4 * 16; i++) > > > > + coda_write(dev, (s32)huff_min[i], > > > > CODA9_REG_JPEG_HUFF_DATA); + + /* MAX Tables */ + > > > > coda_write(dev, 0x403, CODA9_REG_JPEG_HUFF_CTRL); + > > > > coda_write(dev, 0x440, CODA9_REG_JPEG_HUFF_ADDR); + for > > > > (i = 0; i < 4 * 16; i++) + coda_write(dev, > > > > (s32)huff_max[i], CODA9_REG_JPEG_HUFF_DATA); + + /* PTR > > > > Tables */ + coda_write(dev, 0x803, > > > > CODA9_REG_JPEG_HUFF_CTRL); + coda_write(dev, 0x880, > > > > CODA9_REG_JPEG_HUFF_ADDR); + for (i = 0; i < 4 * 16; i++) > > > > + coda_write(dev, (s32)huff_ptr[i], > > > > CODA9_REG_JPEG_HUFF_DATA); + + /* VAL Tables: DC Luma, DC > > > > Chroma, AC Luma, AC Chroma */ + coda_write(dev, 0xc03, > > > > CODA9_REG_JPEG_HUFF_CTRL); + > > > > coda9_jpeg_write_huff_values(dev, huff_tab->dc_bits[0], + > > > > huff_tab->dc_values[0], 12); + > > > > coda9_jpeg_write_huff_values(dev, huff_tab->dc_bits[1], + > > > > huff_tab->dc_values[1], 12); + > > > > coda9_jpeg_write_huff_values(dev, huff_tab->ac_bits[0], + > > > > huff_tab->ac_values[0], 162); + > > > > coda9_jpeg_write_huff_values(dev, huff_tab->ac_bits[1], + > > > > huff_tab->ac_values[1], 162); + coda_write(dev, 0x000, > > > > CODA9_REG_JPEG_HUFF_CTRL); + return 0; +} + +static > > > > inline void coda9_jpeg_write_qmat_tab(struct coda_dev *dev, + > > > > u8 *qmat, int index) +{ + int i; + + > > > > coda_write(dev, index | 0x3, CODA9_REG_JPEG_QMAT_CTRL); + > > > > for (i = 0; i < 64; i++) + coda_write(dev, > > > > qmat[i], CODA9_REG_JPEG_QMAT_DATA); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_QMAT_CTRL); +} + +static void > > > > coda9_jpeg_qmat_setup(struct coda_ctx *ctx) +{ + struct > > > > coda_dev *dev = ctx->dev; + int *qmat_index = > > > > ctx->params.jpeg_qmat_index; + u8 **qmat_tab = > > > > ctx->params.jpeg_qmat_tab; + + > > > > coda9_jpeg_write_qmat_tab(dev, qmat_tab[qmat_index[0]], 0x00); > > > > + coda9_jpeg_write_qmat_tab(dev, qmat_tab[qmat_index[1]], > > > > 0x40); + coda9_jpeg_write_qmat_tab(dev, > > > > qmat_tab[qmat_index[2]], 0x80); +} + +static void > > > > coda9_jpeg_dec_bbc_gbu_setup(struct coda_ctx *ctx, + > > > > struct vb2_buffer *buf, u32 ecs_offset) +{ + struct > > > > coda_dev *dev = ctx->dev; + int page_ptr, word_ptr, > > > > bit_ptr; + u32 bbc_base_addr, end_addr; + int > > > > bbc_cur_pos; + int ret, val; + + bbc_base_addr = > > > > vb2_dma_contig_plane_dma_addr(buf, 0); + end_addr = > > > > bbc_base_addr + vb2_get_plane_payload(buf, 0); + + > > > > page_ptr = ecs_offset / 256; + word_ptr = (ecs_offset % > > > > 256) / 4; + if (page_ptr & 1) + word_ptr += > > > > 64; + bit_ptr = (ecs_offset % 4) * 8; + if > > > > (word_ptr & 1) + bit_ptr += 32; + word_ptr > > > > &= ~0x1; + + coda_write(dev, end_addr, > > > > CODA9_REG_JPEG_BBC_WR_PTR); + coda_write(dev, > > > > bbc_base_addr, CODA9_REG_JPEG_BBC_BAS_ADDR); + + /* Leave > > > > 3 256-byte page margin to avoid a BBC interrupt */ + > > > > coda_write(dev, end_addr + 256 * 3 + 256, > > > > CODA9_REG_JPEG_BBC_END_ADDR); + val = > > > > DIV_ROUND_UP(vb2_plane_size(buf, 0), 256) + 3; + > > > > coda_write(dev, BIT(31) | val, CODA9_REG_JPEG_BBC_STRM_CTRL); + > > > > + bbc_cur_pos = page_ptr; + coda_write(dev, > > > > bbc_cur_pos, CODA9_REG_JPEG_BBC_CUR_POS); + > > > > coda_write(dev, bbc_base_addr + (bbc_cur_pos << 8), + > > > > CODA9_REG_JPEG_BBC_EXT_ADDR); + coda_write(dev, > > > > (bbc_cur_pos & 1) << 6, CODA9_REG_JPEG_BBC_INT_ADDR); + > > > > coda_write(dev, 64, CODA9_REG_JPEG_BBC_DATA_CNT); + > > > > coda_write(dev, 0, CODA9_REG_JPEG_BBC_COMMAND); + do { + > > > > ret = coda_read(dev, CODA9_REG_JPEG_BBC_BUSY); + } while > > > > (ret == 1); + + bbc_cur_pos++; + coda_write(dev, > > > > bbc_cur_pos, CODA9_REG_JPEG_BBC_CUR_POS); + > > > > coda_write(dev, bbc_base_addr + (bbc_cur_pos << 8), + > > > > CODA9_REG_JPEG_BBC_EXT_ADDR); + coda_write(dev, > > > > (bbc_cur_pos & 1) << 6, CODA9_REG_JPEG_BBC_INT_ADDR); + > > > > coda_write(dev, 64, CODA9_REG_JPEG_BBC_DATA_CNT); + > > > > coda_write(dev, 0, CODA9_REG_JPEG_BBC_COMMAND); + do { + > > > > ret = coda_read(dev, CODA9_REG_JPEG_BBC_BUSY); + } while > > > > (ret == 1); + + bbc_cur_pos++; + coda_write(dev, > > > > bbc_cur_pos, CODA9_REG_JPEG_BBC_CUR_POS); + > > > > coda_write(dev, 1, CODA9_REG_JPEG_BBC_CTRL); + + > > > > coda_write(dev, 0, CODA9_REG_JPEG_GBU_TT_CNT); + > > > > coda_write(dev, word_ptr, CODA9_REG_JPEG_GBU_WD_PTR); + > > > > coda_write(dev, 0, CODA9_REG_JPEG_GBU_BBSR); + > > > > coda_write(dev, 127, CODA9_REG_JPEG_GBU_BBER); + if > > > > (page_ptr & 1) { + coda_write(dev, 0, > > > > CODA9_REG_JPEG_GBU_BBIR); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_GBU_BBHR); + } else { + > > > > coda_write(dev, 64, CODA9_REG_JPEG_GBU_BBIR); + > > > > coda_write(dev, 64, CODA9_REG_JPEG_GBU_BBHR); + } + > > > > coda_write(dev, 4, CODA9_REG_JPEG_GBU_CTRL); + > > > > coda_write(dev, bit_ptr, CODA9_REG_JPEG_GBU_FF_RPTR); + > > > > coda_write(dev, 3, CODA9_REG_JPEG_GBU_CTRL); +} + > > > > static const int bus_req_num[] = { > > > > [CODA9_JPEG_FORMAT_420] = 2, [CODA9_JPEG_FORMAT_422] = > > > > 3, > > > > @@ -341,6 +621,71 @@ static int > > > > coda9_jpeg_gen_enc_huff_tab(struct coda_ctx *ctx, int tab_num, > > > > #define DC_TABLE_INDEX1 2 #define > > > > AC_TABLE_INDEX1 3 > > > > > > > > +static u8 *coda9_jpeg_get_huff_bits(struct coda_ctx *ctx, int > > > > tab_num) +{ + struct coda_huff_tab *huff_tab = > > > > ctx->params.jpeg_huff_tab; + + if (!huff_tab) + > > > > return NULL; + + switch (tab_num) { + case > > > > DC_TABLE_INDEX0: return huff_tab->dc_bits[0]; + case > > > > AC_TABLE_INDEX0: return huff_tab->ac_bits[0]; + case > > > > DC_TABLE_INDEX1: return huff_tab->dc_bits[1]; + case > > > > AC_TABLE_INDEX1: return huff_tab->ac_bits[1]; + } + + > > > > return NULL; +} + +static int > > > > coda9_jpeg_gen_dec_huff_tab(struct coda_ctx *ctx, int tab_num) > > > > +{ + int ptr_cnt = 0, huff_code = 0, zero_flag = 0, > > > > data_flag = 0; + u8 *huff_bits; + s16 *huff_max; + > > > > s16 *huff_min; + s8 *huff_ptr; + int ofs; + > > > > int i; + + huff_bits = coda9_jpeg_get_huff_bits(ctx, > > > > tab_num); + if (!huff_bits) + return > > > > -EINVAL; + + /* DC/AC Luma, DC/AC Chroma -> DC > > > > Luma/Chroma, AC Luma/Chroma */ + ofs = ((tab_num & 1) << > > > > 1) | ((tab_num >> 1) & 1); + ofs *= 16; + + > > > > huff_ptr = ctx->params.jpeg_huff_tab->ptr + ofs; + > > > > huff_max = ctx->params.jpeg_huff_tab->max + ofs; + > > > > huff_min = ctx->params.jpeg_huff_tab->min + ofs; + + for > > > > (i = 0; i < 16; i++) { + if (huff_bits[i]) { + > > > > huff_ptr[i] = ptr_cnt; + ptr_cnt += > > > > huff_bits[i]; + huff_min[i] = huff_code; > > > > + huff_max[i] = huff_code + (huff_bits[i] > > > > - 1); + data_flag = 1; + > > > > zero_flag = 0; + } else { + > > > > huff_ptr[i] = -1; + huff_min[i] = -1; + > > > > huff_max[i] = -1; + zero_flag = 1; + > > > > } + + if (data_flag == 1) { + > > > > if (zero_flag == 1) + huff_code > > > > <<= 1; + else + > > > > huff_code = (huff_max[i] + 1) << 1; + } + } > > > > + + return 0; +} + > > > > static int coda9_jpeg_load_huff_tab(struct coda_ctx *ctx) { > > > > struct coda_dev *dev = ctx->dev; > > > > @@ -401,6 +746,8 @@ static inline void > > > > coda9_jpeg_write_qmat_quotients(struct coda_dev *dev, > > > > coda_write(dev, index, CODA9_REG_JPEG_QMAT_CTRL); > > > > } > > > > > > > > +static void coda_scale_quant_table(u8 *q_tab, int scale); + > > > > static int coda9_jpeg_load_qmat_tab(struct coda_ctx *ctx) { > > > > struct coda_dev *dev = ctx->dev; > > > > @@ -860,6 +1207,13 @@ static void > > > > coda9_jpeg_finish_encode(struct coda_ctx *ctx) > > > > coda_dbg(1, ctx, "job finished: encoded frame > > > > (%u)%s\n", > > > > dst_buf->sequence, (dst_buf->flags & > > > > V4L2_BUF_FLAG_LAST) ? " (last)" : ""); > > > > + + /* + * Reset JPEG processing unit after each > > > > encode run to work + * around hangups when switching > > > > context between encoder and + * decoder. + */ + > > > > coda_hw_reset(ctx); > > > > } > > > > > > > > static void coda9_jpeg_release(struct coda_ctx *ctx) > > > > @@ -872,6 +1226,7 @@ static void coda9_jpeg_release(struct > > > > coda_ctx *ctx) > > > > ctx->params.jpeg_qmat_tab[1] = NULL; > > > > for (i = 0; i < 3; i++) > > > > kfree(ctx->params.jpeg_qmat_tab[i]); > > > > + kfree(ctx->params.jpeg_huff_tab); > > > > } > > > > > > > > const struct coda_context_ops coda9_jpeg_encode_ops = { > > > > @@ -882,6 +1237,202 @@ const struct coda_context_ops > > > > coda9_jpeg_encode_ops = { > > > > .release = coda9_jpeg_release, > > > > }; > > > > > > > > +/* + * Decoder context operations + */ + +static int > > > > coda9_jpeg_start_decoding(struct coda_ctx *ctx) +{ + > > > > ctx->params.jpeg_qmat_index[0] = 0; + > > > > ctx->params.jpeg_qmat_index[1] = 1; + > > > > ctx->params.jpeg_qmat_index[2] = 1; + > > > > ctx->params.jpeg_qmat_tab[0] = luma_q; + > > > > ctx->params.jpeg_qmat_tab[1] = chroma_q; + /* nothing > > > > more to do here */ + + /* TODO: we could already scan the > > > > first header to get the chroma + * format. + */ > > > > + + return 0; +} + +static int > > > > coda9_jpeg_prepare_decode(struct coda_ctx *ctx) +{ + > > > > struct coda_dev *dev = ctx->dev; + int aligned_width, > > > > aligned_height; + int chroma_format; + int ret; + > > > > u32 val, dst_fourcc; + struct coda_q_data *q_data_dst; + > > > > struct vb2_v4l2_buffer *src_buf, *dst_buf; + int > > > > chroma_interleave; + + src_buf = > > > > v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = > > > > v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + q_data_dst = > > > > get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + > > > > dst_fourcc = q_data_dst->fourcc; + + if > > > > (vb2_get_plane_payload(&src_buf->vb2_buf, 0) == 0) + > > > > vb2_set_plane_payload(&src_buf->vb2_buf, 0, + > > > > vb2_plane_size(&src_buf->vb2_buf, 0)); + + chroma_format > > > > = coda9_jpeg_chroma_format(q_data_dst->fourcc); + if > > > > (chroma_format < 0) { + > > > > v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); + > > > > return chroma_format; + } + + /* Round image > > > > dimensions to multiple of MCU size */ + aligned_width = > > > > round_up(q_data_dst->width, width_align[chroma_format]); + > > > > aligned_height = round_up(q_data_dst->height, + > > > > height_align[chroma_format]); + if (aligned_width != > > > > q_data_dst->bytesperline) { + > > > > v4l2_err(&dev->v4l2_dev, "stride mismatch: %d != %d\n", + > > > > aligned_width, q_data_dst->bytesperline); + } + + > > > > coda_set_gdi_regs(ctx); + + ret = > > > > coda_jpeg_decode_header(ctx, &src_buf->vb2_buf); + if > > > > (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to > > > > decode JPEG header: %d\n", + ret); + + > > > > src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + > > > > dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + > > > > v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + > > > > v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + + > > > > v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); + > > > > return ret; + } + + val = > > > > ctx->params.jpeg_huff_ac_index[0] << 12 | + > > > > ctx->params.jpeg_huff_ac_index[1] << 11 | + > > > > ctx->params.jpeg_huff_ac_index[2] << 10 | + > > > > ctx->params.jpeg_huff_dc_index[0] << 9 | + > > > > ctx->params.jpeg_huff_dc_index[1] << 8 | + > > > > ctx->params.jpeg_huff_dc_index[2] << 7; + if > > > > (ctx->params.jpeg_huff_tab) + val |= > > > > CODA9_JPEG_PIC_CTRL_USER_HUFFMAN_EN; + coda_write(dev, > > > > val, CODA9_REG_JPEG_PIC_CTRL); + + coda_write(dev, > > > > aligned_width << 16 | aligned_height, + > > > > CODA9_REG_JPEG_PIC_SIZE); + + chroma_interleave = > > > > (dst_fourcc == V4L2_PIX_FMT_NV12); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_ROT_INFO); + coda_write(dev, > > > > bus_req_num[chroma_format], CODA9_REG_JPEG_OP_INFO); + > > > > coda_write(dev, mcu_info[chroma_format], > > > > CODA9_REG_JPEG_MCU_INFO); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_SCL_INFO); + coda_write(dev, > > > > chroma_interleave, CODA9_REG_JPEG_DPB_CONFIG); + > > > > coda_write(dev, ctx->params.jpeg_restart_interval, + > > > > CODA9_REG_JPEG_RST_INTVAL); + + if > > > > (ctx->params.jpeg_huff_tab) { + ret = > > > > coda9_jpeg_dec_huff_setup(ctx); + if (ret < 0) { > > > > + v4l2_err(&dev->v4l2_dev, + > > > > "failed to set up Huffman tables: %d\n", ret); + > > > > v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); + > > > > return ret; + } + } + + > > > > coda9_jpeg_qmat_setup(ctx); + + > > > > coda9_jpeg_dec_bbc_gbu_setup(ctx, &src_buf->vb2_buf, + > > > > ctx->jpeg_ecs_offset); + + coda_write(dev, 0, > > > > CODA9_REG_JPEG_RST_INDEX); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_RST_COUNT); + + coda_write(dev, 0, > > > > CODA9_REG_JPEG_DPCM_DIFF_Y); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_DPCM_DIFF_CB); + coda_write(dev, 0, > > > > CODA9_REG_JPEG_DPCM_DIFF_CR); + + coda_write(dev, 0, > > > > CODA9_REG_JPEG_ROT_INFO); + + coda_write(dev, 1, > > > > CODA9_GDI_CONTROL); + do { + ret = > > > > coda_read(dev, CODA9_GDI_STATUS); + } while (!ret); + + > > > > val = (chroma_format << 17) | (chroma_interleave << 16) | + > > > > q_data_dst->bytesperline; + if (ctx->tiled_map_type == > > > > GDI_TILED_FRAME_MB_RASTER_MAP) + val |= 3 << 20; > > > > + coda_write(dev, val, CODA9_GDI_INFO_CONTROL); + + > > > > coda_write(dev, aligned_width << 16 | aligned_height, + > > > > CODA9_GDI_INFO_PIC_SIZE); + + coda_write_base(ctx, > > > > q_data_dst, dst_buf, CODA9_GDI_INFO_BASE_Y); + + > > > > coda_write(dev, 0, CODA9_REG_JPEG_DPB_BASE00); + > > > > coda_write(dev, 0, CODA9_GDI_CONTROL); + coda_write(dev, > > > > 1, CODA9_GDI_PIC_INIT_HOST); + + trace_coda_jpeg_run(ctx, > > > > src_buf); + + coda_write(dev, 1, > > > > CODA9_REG_JPEG_PIC_START); + + return 0; +} + +static > > > > void coda9_jpeg_finish_decode(struct coda_ctx *ctx) +{ + > > > > struct coda_dev *dev = ctx->dev; + struct vb2_v4l2_buffer > > > > *dst_buf, *src_buf; + struct coda_q_data *q_data_dst; + > > > > u32 err_mb; + + err_mb = coda_read(dev, > > > > CODA9_REG_JPEG_PIC_ERRMB); + if (err_mb) + > > > > v4l2_err(&dev->v4l2_dev, "ERRMB: 0x%x\n", err_mb); + + > > > > coda_write(dev, 0, CODA9_REG_JPEG_BBC_FLUSH_CMD); + + > > > > src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + > > > > dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + > > > > dst_buf->sequence = ctx->osequence++; + + > > > > trace_coda_jpeg_done(ctx, dst_buf); + + dst_buf->flags &= > > > > ~(V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_LAST); + > > > > dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + > > > > dst_buf->flags |= src_buf->flags & V4L2_BUF_FLAG_LAST; + + > > > > v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, false); + + > > > > q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + > > > > vb2_set_plane_payload(&dst_buf->vb2_buf, 0, > > > > q_data_dst->sizeimage); + + v4l2_m2m_buf_done(src_buf, > > > > VB2_BUF_STATE_DONE); + coda_m2m_buf_done(ctx, dst_buf, > > > > err_mb ? VB2_BUF_STATE_ERROR : + > > > > VB2_BUF_STATE_DONE); + + coda_dbg(1, ctx, "job finished: > > > > decoded frame (%u)%s\n", + dst_buf->sequence, + > > > > (dst_buf->flags & V4L2_BUF_FLAG_LAST) ? " (last)" : ""); + + > > > > /* + * Reset JPEG processing unit after each decode run > > > > to work + * around hangups when switching context > > > > between encoder and + * decoder. + */ + > > > > coda_hw_reset(ctx); +} + +const struct coda_context_ops > > > > coda9_jpeg_decode_ops = { + .queue_init = > > > > coda_encoder_queue_init, /* non-bitstream operation */ + > > > > .start_streaming = coda9_jpeg_start_decoding, + > > > > .prepare_run = coda9_jpeg_prepare_decode, + .finish_run = > > > > coda9_jpeg_finish_decode, + .release = > > > > coda9_jpeg_release, +}; + > > > > irqreturn_t coda9_jpeg_irq_handler(int irq, void *data) { > > > > struct coda_dev *dev = data; > > > > diff --git a/drivers/media/platform/coda/coda.h > > > > b/drivers/media/platform/coda/coda.h index > > > > 98af53d11c1b..80a2cc1126bd 100644 --- > > > > a/drivers/media/platform/coda/coda.h +++ > > > > b/drivers/media/platform/coda/coda.h @@ -69,7 +69,7 @@ struct > > > > coda_aux_buf { > > > > > > > > struct coda_dev { > > > > struct v4l2_device v4l2_dev; > > > > - struct video_device vfd[5]; + struct > > > > video_device vfd[6]; > > > > struct device *dev; const struct coda_devtype > > > > *devtype; int firmware; > > > > @@ -123,9 +123,15 @@ struct coda_params { > > > > u8 mpeg4_inter_qp; u8 > > > > gop_size; int intra_refresh; > > > > + u8 jpeg_format; > > > > u8 jpeg_quality; u8 > > > > jpeg_restart_interval; u8 > > > > *jpeg_qmat_tab[3]; > > > > + int jpeg_qmat_index[3]; + int > > > > jpeg_huff_dc_index[3]; + int > > > > jpeg_huff_ac_index[3]; + struct coda_huff_tab > > > > *jpeg_huff_tab; + u8 > > > > jpeg_chroma_subsampling[3]; > > > > int codec_mode; int > > > > codec_mode_aux; enum v4l2_mpeg_video_multi_slice_mode > > > > slice_mode; > > > > @@ -237,6 +243,7 @@ struct coda_ctx { > > > > struct v4l2_fh fh; int > > > > gopcounter; int runcounter; > > > > + int jpeg_ecs_offset; > > > > char vpu_header[3][64]; int > > > > vpu_header_size[3]; struct kfifo > > > > bitstream_fifo; > > > > @@ -361,12 +368,14 @@ void > > > > coda_update_profile_level_ctrls(struct coda_ctx *ctx, u8 > > > > profile_idc, > > > > u8 level_idc); > > > > > > > > bool coda_jpeg_check_buffer(struct coda_ctx *ctx, struct > > > > vb2_buffer *vb); > > > > +int coda_jpeg_decode_header(struct coda_ctx *ctx, struct > > > > vb2_buffer *vb); > > > > int coda_jpeg_write_tables(struct coda_ctx *ctx); void > > > > coda_set_jpeg_compression_quality(struct coda_ctx *ctx, int > > > > quality); > > > > > > > > extern const struct coda_context_ops coda_bit_encode_ops; > > > > extern const struct coda_context_ops coda_bit_decode_ops; > > > > extern const struct coda_context_ops coda9_jpeg_encode_ops; > > > > +extern const struct coda_context_ops coda9_jpeg_decode_ops; > > > > > > > > irqreturn_t coda_irq_handler(int irq, void *data); irqreturn_t > > > > coda9_jpeg_irq_handler(int irq, void *data); > > > > -- 2.20.1 > > > > > > > > > > Hi Philipp, > > > > > > Thank you for the continuing improvements to the coda driver! > > > > > > I've applied 'media: add v4l2 JPEG helpers' and this on top of > > > 5.6-rc3 and it does decode but the colorspace looks all wrong > > > (looks like a planar issue). I'm testing this on an IMX6Q board > > > with 'gst-launch-1.0 videotestsrc ! jpegenc ! v4l2jpegdec ! > > > autovideosink'. > > > > > > Any ideas what could be happening here? Do you have plans to > > > re-submit this and 'media: add v4l2 JPEG helpers'? > > > > So from my testing the decoder patches work really well, (thanks a > > lot Philipp, much appreciated!) but there is still one bug you > > might be hitting which I also encountered. > > > > I've tested on an imx6dl. > > > > Looks like v4l2jpegdec is chosing the wrong format for some > > reason, the pipeline tries to use I420 but it defaults to NV12. > > > > You can validate your pipeline with the software decoder 'jpegdec' > > with something like this (just an example of what I use): > > > > gst-launch-1.0 -v filesrc location=/test420.jpeg ! jpegparse ! > > jpegdec ! kmssink > > > > You can see, due to the -v arg, that the format is correctly > > negotiated as I420. If you change to v4l2jpegdec you'll see > > autonegotiation try to use NV12 which leads to a colorspace > > conversion bug because the planes are swapped between formats. > > > > My workaround is to explicitely set the format in the pipeline, > > like: > > > > gst-launch-1.0 -v filesrc location=/test420.jpeg ! jpegparse ! > > v4l2jpegdec ! video/x-raw,format=I420 ! kmssink > > > > Please tell me if this helps, it would be good to have > > confirmation I'm not the only one :) > > > > Adrian, > > Indeed this is the issue and the workaround works for me as well on > IMX6Q on 5.6-rc3 with 'media: coda: jpeg: add CODA960 JPEG decoder > support' and 'media: add v4l2 JPEG helpers' > gst-launch-1.0 videotestsrc ! jpegenc ! jpegdec ! kmssink # works > fine; jpegdec outputs I420 > gst-launch-1.0 videotestsrc ! jpegenc ! v4l2jpegdec ! kmssink # > invalid image; v4l2jpegdec outputs NV12 > gst-launch-1.0 videotestsrc ! jpegenc ! v4l2jpegdec ! > video/x-raw,format=I420 ! kmssink # works fine; v4l2jpegdec outputs > I420 > Hey guys, Note that the decoder seems to support NV12: +static const struct coda_video_device coda9_jpeg_decoder = { + .name = "coda-jpeg-decoder", + .type = CODA_INST_DECODER, + .ops = &coda9_jpeg_decode_ops, + .direct = true, + .src_formats = { + V4L2_PIX_FMT_JPEG, + }, + .dst_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUV422P, + }, +}; I don't think there is anything wrong with the negotiation. The jpegdec element supports I420 (see gst-inspect-1.0 jpegdec), so naturally this pipeline negotiates I420: gst-launch-1.0 videotestsrc ! jpegenc ! jpegdec ! kmssink On the other hand, since the hardware decoder supports NV12 as well as I420, gstreamer is free to negotiate to whatever it prefers, so: gst-launch-1.0 videotestsrc ! jpegenc ! v4l2jpegdec ! kmssink will happily negotiate to NV12, unless you explicitly ask for a format. So the bug would not be a wrong negotiation, but the driver seemingly not producing correct NV12. Regards, Ezequiel