From 223c09b84742d45cf5e273c5633492530dc68a18 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 8 Jul 2021 17:50:25 +0200 Subject: [PATCH 2/3] WIP: JPEG driver Signed-off-by: Emmanuel Gil Peyrot --- drivers/staging/media/sunxi/cedrus/Kconfig | 1 + drivers/staging/media/sunxi/cedrus/Makefile | 2 +- drivers/staging/media/sunxi/cedrus/cedrus.h | 5 + .../staging/media/sunxi/cedrus/cedrus_dec.c | 4 + .../staging/media/sunxi/cedrus/cedrus_hw.c | 4 + .../staging/media/sunxi/cedrus/cedrus_jpeg.c | 236 ++++++++++++++++++ .../staging/media/sunxi/cedrus/cedrus_regs.h | 8 + .../staging/media/sunxi/cedrus/cedrus_video.c | 9 + 8 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 drivers/staging/media/sunxi/cedrus/cedrus_jpeg.c diff --git a/drivers/staging/media/sunxi/cedrus/Kconfig b/drivers/staging/media/sunxi/cedrus/Kconfig index 621944f9907a..4144f8a687d4 100644 --- a/drivers/staging/media/sunxi/cedrus/Kconfig +++ b/drivers/staging/media/sunxi/cedrus/Kconfig @@ -10,6 +10,7 @@ config VIDEO_SUNXI_CEDRUS select SUNXI_SRAM select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV + select V4L2_JPEG_HELPER help Support for the VPU found in Allwinner SoCs, also known as the Cedar video engine. diff --git a/drivers/staging/media/sunxi/cedrus/Makefile b/drivers/staging/media/sunxi/cedrus/Makefile index a647b3690bf8..fa3e949e0788 100644 --- a/drivers/staging/media/sunxi/cedrus/Makefile +++ b/drivers/staging/media/sunxi/cedrus/Makefile @@ -3,4 +3,4 @@ obj-$(CONFIG_VIDEO_SUNXI_CEDRUS) += sunxi-cedrus.o sunxi-cedrus-y = cedrus.o cedrus_video.o cedrus_hw.o cedrus_dec.o \ cedrus_mpeg2.o cedrus_h264.o cedrus_h265.o \ - cedrus_vp8.o + cedrus_vp8.o cedrus_jpeg.o diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h index 522c184e2afc..555f8d124d47 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus.h +++ b/drivers/staging/media/sunxi/cedrus/cedrus.h @@ -34,6 +34,7 @@ #define CEDRUS_CAPABILITY_MPEG2_DEC BIT(3) #define CEDRUS_CAPABILITY_VP8_DEC BIT(4) #define CEDRUS_CAPABILITY_H265_10_DEC BIT(5) +#define CEDRUS_CAPABILITY_JPEG_DEC BIT(6) enum cedrus_irq_status { CEDRUS_IRQ_NONE, @@ -152,6 +153,9 @@ struct cedrus_ctx { u8 *entropy_probs_buf; dma_addr_t entropy_probs_buf_dma; } vp8; + struct { + unsigned int subsampling; + } jpeg; } codec; }; @@ -201,6 +205,7 @@ extern struct cedrus_dec_ops cedrus_dec_ops_mpeg2; extern struct cedrus_dec_ops cedrus_dec_ops_h264; extern struct cedrus_dec_ops cedrus_dec_ops_h265; extern struct cedrus_dec_ops cedrus_dec_ops_vp8; +extern struct cedrus_dec_ops cedrus_dec_ops_jpeg; static inline void cedrus_write(struct cedrus_dev *dev, u32 reg, u32 val) { diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_dec.c b/drivers/staging/media/sunxi/cedrus/cedrus_dec.c index fbbf9e6f0f50..1fc99a6b65bd 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_dec.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_dec.c @@ -86,6 +86,10 @@ void cedrus_device_run(void *priv) V4L2_CID_STATELESS_VP8_FRAME); break; + case V4L2_PIX_FMT_JPEG: + /* TODO: do we need to do anything here? */ + break; + default: break; } diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c index fa86a658fdc6..b49bfe198d62 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c @@ -43,7 +43,9 @@ int cedrus_engine_enable(struct cedrus_ctx *ctx) reg |= VE_MODE_DDR_MODE_BW_128; switch (ctx->src_fmt.pixelformat) { + /* MPEG2 and JPEG both use the same decoding mode bit. */ case V4L2_PIX_FMT_MPEG2_SLICE: + case V4L2_PIX_FMT_JPEG: reg |= VE_MODE_DEC_MPEG; break; @@ -144,6 +146,8 @@ static irqreturn_t cedrus_irq(int irq, void *data) else state = VB2_BUF_STATE_DONE; + printk("cedrus_irq: %d (error=%d done=%d)\n", state, VB2_BUF_STATE_ERROR, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx, state); diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_jpeg.c b/drivers/staging/media/sunxi/cedrus/cedrus_jpeg.c new file mode 100644 index 000000000000..2e4ebdb9fa63 --- /dev/null +++ b/drivers/staging/media/sunxi/cedrus/cedrus_jpeg.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cedrus VPU driver + * + * Copyright (C) 2022 Emmanuel Gil Peyrot + */ + +#include +#include + +#include "cedrus.h" +#include "cedrus_hw.h" +#include "cedrus_regs.h" + +static enum cedrus_irq_status cedrus_jpeg_irq_status(struct cedrus_ctx *ctx) +{ + struct cedrus_dev *dev = ctx->dev; + u32 reg; + + reg = cedrus_read(dev, VE_DEC_MPEG_STATUS); + reg &= VE_DEC_MPEG_STATUS_CHECK_MASK; + + if (!reg) + return CEDRUS_IRQ_NONE; + + if (reg & VE_DEC_MPEG_STATUS_CHECK_ERROR || + !(reg & VE_DEC_MPEG_STATUS_SUCCESS)) + return CEDRUS_IRQ_ERROR; + + return CEDRUS_IRQ_OK; +} + +static void cedrus_jpeg_irq_clear(struct cedrus_ctx *ctx) +{ + struct cedrus_dev *dev = ctx->dev; + + cedrus_write(dev, VE_DEC_MPEG_STATUS, VE_DEC_MPEG_STATUS_CHECK_MASK); +} + +static void cedrus_jpeg_irq_disable(struct cedrus_ctx *ctx) +{ + struct cedrus_dev *dev = ctx->dev; + u32 reg = cedrus_read(dev, VE_DEC_MPEG_CTRL); + + reg &= ~VE_DEC_MPEG_CTRL_IRQ_MASK; + + cedrus_write(dev, VE_DEC_MPEG_CTRL, reg); +} + +static int cedrus_jpeg_setup(struct cedrus_ctx *ctx, struct cedrus_run *run) +{ + struct v4l2_jpeg_scan_header scan_header; + struct v4l2_jpeg_reference quantization_tables[4] = { }; + struct v4l2_jpeg_reference huffman_tables[4] = { }; + struct v4l2_jpeg_header header = { + .scan = &scan_header, + .quantization_tables = quantization_tables, + .huffman_tables = huffman_tables, + }; + dma_addr_t src_buf_addr, dst_luma_addr, dst_chroma_addr; + struct cedrus_dev *dev = ctx->dev; + struct vb2_buffer *src_buf = &run->src->vb2_buf; + const u8 *matrix; + int ret; + unsigned int i, j; + u32 reg; + u8 buffer[256]; + int sum, sums[4], last; + struct v4l2_jpeg_reference *table; + + u8 *a = vb2_plane_vaddr(src_buf, 0); + u32 b = vb2_get_plane_payload(src_buf, 0); + printk("Hello world! vaddr=%p len=%u\n", a, b); + + ret = v4l2_jpeg_parse_header(vb2_plane_vaddr(src_buf, 0), vb2_get_plane_payload(src_buf, 0), &header); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to parse JPEG header\n"); + return -EINVAL; + } + + switch ((header.frame.component[0].horizontal_sampling_factor << 4) + | header.frame.component[0].vertical_sampling_factor) { + case 0x22: + ctx->codec.jpeg.subsampling = VE_DEC_MPEG_TRIGGER_CHROMA_FMT_420; + break; + case 0x21: + ctx->codec.jpeg.subsampling = VE_DEC_MPEG_TRIGGER_CHROMA_FMT_422; + break; + case 0x11: + ctx->codec.jpeg.subsampling = VE_DEC_MPEG_TRIGGER_CHROMA_FMT_444; + break; + case 0x12: + ctx->codec.jpeg.subsampling = VE_DEC_MPEG_TRIGGER_CHROMA_FMT_422T; + break; + default: + v4l2_err(&dev->v4l2_dev, "unsupported subsampling\n"); + return -EINVAL; + } + + /* Activate JPEG engine. */ + cedrus_engine_enable(ctx); + + /* Set restart interval. */ + cedrus_write(dev, VE_DEC_MPEG_JPEG_RES_INT, header.restart_interval); + + /* Set resolution in blocks. */ + reg = (header.frame.width - 1) / (8 * header.frame.component[0].horizontal_sampling_factor); + reg |= ((header.frame.height - 1) / (8 * header.frame.component[0].vertical_sampling_factor)) << 16; + cedrus_write(dev, VE_DEC_MPEG_JPEG_SIZE, reg); + + /* Set intra quantisation matrix. */ + matrix = quantization_tables[0].start; + for (i = 0; i < 64; i++) { + reg = VE_DEC_MPEG_IQMINPUT_WEIGHT(i, matrix[i]); + reg |= VE_DEC_MPEG_IQMINPUT_FLAG_INTRA; + + cedrus_write(dev, VE_DEC_MPEG_IQMINPUT, reg); + } + + /* Set non-intra quantisation matrix. */ + matrix = quantization_tables[1].start; + for (i = 0; i < 64; i++) { + reg = VE_DEC_MPEG_IQMINPUT_WEIGHT(i, matrix[i]); + reg |= VE_DEC_MPEG_IQMINPUT_FLAG_NON_INTRA; + + cedrus_write(dev, VE_DEC_MPEG_IQMINPUT, reg); + } + + /* Set Huffman tables. */ + cedrus_write(dev, VE_DEC_MPEG_SRAM_RW_OFFSET, 0); + for (i = 0; i < 4; i++) { + table = &huffman_tables[i]; + memset(buffer, 0xff, 32); + memset(&buffer[32], 0, 32); + if (table) { + if (table->length < 16) { + v4l2_err(&dev->v4l2_dev, "huffman table too short\n"); + return -EINVAL; + } + sum = 0; + last = 0; + for (j = 0; j < 16; j++) { + buffer[32 + j] = sum; + sum += table->start[j]; + if (table->start[j] != 0) + last = j; + } + if (table->length - 16 != sum) { + v4l2_err(&dev->v4l2_dev, "huffman table too short\n"); + return -EINVAL; + } + sums[i] = sum; + sum = 0; + for (j = 0; j <= last; j++) { + ((u16*)buffer)[j] = sum; + sum += table->start[j]; + sum *= 2; + } + } + for (j = 0; j < 16; j++) + cedrus_write(dev, VE_DEC_MPEG_SRAM_RW_DATA, ((u32*)buffer)[j]); + } + for (i = 0; i < 256 - 64; i++) + cedrus_write(dev, VE_DEC_MPEG_SRAM_RW_DATA, 0); + for (i = 0; i < 4; i++) { + table = &huffman_tables[i]; + memset(buffer, 0, 256); + if (table) + memcpy(buffer, &table->start[16], sums[i]); + for (j = 0; j < 64; j++) + cedrus_write(dev, VE_DEC_MPEG_SRAM_RW_DATA, ((u32*)buffer)[j]); + } + + /* Destination luma and chroma buffers. */ + + dst_luma_addr = cedrus_dst_buf_addr(ctx, &run->dst->vb2_buf, 0); + dst_chroma_addr = cedrus_dst_buf_addr(ctx, &run->dst->vb2_buf, 1); + + cedrus_write(dev, VE_DEC_MPEG_REC_LUMA, dst_luma_addr); + cedrus_write(dev, VE_DEC_MPEG_REC_CHROMA, dst_chroma_addr); + + /* Source offset and length in bits. */ + + cedrus_write(dev, VE_DEC_MPEG_VLD_OFFSET, 8 * header.ecs_offset); + + reg = vb2_get_plane_payload(src_buf, 0) * 8; + cedrus_write(dev, VE_DEC_MPEG_VLD_LEN, reg - 8 * header.ecs_offset); + + /* Source beginning and end addresses. */ + + src_buf_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); + + reg = VE_DEC_MPEG_VLD_ADDR_BASE(src_buf_addr); + reg |= VE_DEC_MPEG_VLD_ADDR_VALID_PIC_DATA; + reg |= VE_DEC_MPEG_VLD_ADDR_LAST_PIC_DATA; + reg |= VE_DEC_MPEG_VLD_ADDR_FIRST_PIC_DATA; + + cedrus_write(dev, VE_DEC_MPEG_VLD_ADDR, reg); + + reg = src_buf_addr + vb2_get_plane_payload(src_buf, 0); + cedrus_write(dev, VE_DEC_MPEG_VLD_END_ADDR, reg); + + /* Clear previous errors. */ + cedrus_write(dev, VE_DEC_MPEG_ERROR, 0); + + /* Enable appropriate interruptions and components. */ + + reg = VE_DEC_MPEG_CTRL_IRQ_MASK | VE_DEC_MPEG_CTRL_MC_NO_WRITEBACK | + VE_DEC_MPEG_CTRL_MC_CACHE_EN; + + cedrus_write(dev, VE_DEC_MPEG_CTRL, reg); + + printk("Yay!\n"); + return 0; +} + +static void cedrus_jpeg_trigger(struct cedrus_ctx *ctx) +{ + struct cedrus_dev *dev = ctx->dev; + u32 reg; + + /* Trigger JPEG engine. */ + reg = VE_DEC_MPEG_TRIGGER_HW_JPEG_VLD | VE_DEC_MPEG_TRIGGER_JPEG; + reg |= ctx->codec.jpeg.subsampling; + + cedrus_write(dev, VE_DEC_MPEG_TRIGGER, reg); + printk("Trigger!\n"); +} + +struct cedrus_dec_ops cedrus_dec_ops_jpeg = { + .irq_clear = cedrus_jpeg_irq_clear, + .irq_disable = cedrus_jpeg_irq_disable, + .irq_status = cedrus_jpeg_irq_status, + .setup = cedrus_jpeg_setup, + .trigger = cedrus_jpeg_trigger, +}; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h index 05e6cbc548ab..d9bc8eab25dd 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h +++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h @@ -250,11 +250,19 @@ #define VE_DEC_MPEG_IQMINPUT_WEIGHT(i, v) \ (SHIFT_AND_MASK_BITS(i, 13, 8) | SHIFT_AND_MASK_BITS(v, 7, 0)) +#define VE_DEC_MPEG_JPEG_SIZE (VE_ENGINE_DEC_MPEG + 0xb8) +#define VE_DEC_MPEG_JPEG_MCU (VE_ENGINE_DEC_MPEG + 0xbc) +#define VE_DEC_MPEG_JPEG_RES_INT (VE_ENGINE_DEC_MPEG + 0xc0) #define VE_DEC_MPEG_ERROR (VE_ENGINE_DEC_MPEG + 0xc4) #define VE_DEC_MPEG_CRTMBADDR (VE_ENGINE_DEC_MPEG + 0xc8) #define VE_DEC_MPEG_ROT_LUMA (VE_ENGINE_DEC_MPEG + 0xcc) #define VE_DEC_MPEG_ROT_CHROMA (VE_ENGINE_DEC_MPEG + 0xd0) +#define VE_DEC_MPEG_JPEG_MCU_START (VE_ENGINE_DEC_MPEG + 0xd8) +#define VE_DEC_MPEG_JPEG_MCU_END (VE_ENGINE_DEC_MPEG + 0xdc) +#define VE_DEC_MPEG_SRAM_RW_OFFSET (VE_ENGINE_DEC_MPEG + 0xe0) +#define VE_DEC_MPEG_SRAM_RW_DATA (VE_ENGINE_DEC_MPEG + 0xe4) + #define VE_DEC_H265_DEC_NAL_HDR (VE_ENGINE_DEC_H265 + 0x00) #define VE_DEC_H265_DEC_NAL_HDR_NUH_TEMPORAL_ID_PLUS1(v) \ diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c index b00feaf4072c..7205c2315bc5 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c @@ -55,6 +55,11 @@ static struct cedrus_format cedrus_formats[] = { .directions = CEDRUS_DECODE_SRC, .capabilities = CEDRUS_CAPABILITY_VP8_DEC, }, + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .directions = CEDRUS_DECODE_SRC, + .capabilities = CEDRUS_CAPABILITY_JPEG_DEC, + }, { .pixelformat = V4L2_PIX_FMT_NV12, .directions = CEDRUS_DECODE_DST, @@ -118,6 +123,7 @@ void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt) case V4L2_PIX_FMT_H264_SLICE: case V4L2_PIX_FMT_HEVC_SLICE: case V4L2_PIX_FMT_VP8_FRAME: + case V4L2_PIX_FMT_JPEG: /* Zero bytes per line for encoded source. */ bytesperline = 0; /* Choose some minimum size since this can't be 0 */ @@ -350,6 +356,9 @@ static int cedrus_s_fmt_vid_out_p(struct cedrus_ctx *ctx, case V4L2_PIX_FMT_VP8_FRAME: ctx->current_codec = &cedrus_dec_ops_vp8; break; + case V4L2_PIX_FMT_JPEG: + ctx->current_codec = &cedrus_dec_ops_jpeg; + break; } /* Propagate format information to capture. */ -- 2.40.0