diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/staging/media/meson | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/staging/media/meson')
25 files changed, 6929 insertions, 0 deletions
diff --git a/drivers/staging/media/meson/vdec/Kconfig b/drivers/staging/media/meson/vdec/Kconfig new file mode 100644 index 0000000000..19ffea987b --- /dev/null +++ b/drivers/staging/media/meson/vdec/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 + +config VIDEO_MESON_VDEC + tristate "Amlogic video decoder driver" + depends on VIDEO_DEV && HAS_DMA + depends on ARCH_MESON || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select MESON_CANVAS + help + Support for the video decoder found in gxbb/gxl/gxm chips. diff --git a/drivers/staging/media/meson/vdec/Makefile b/drivers/staging/media/meson/vdec/Makefile new file mode 100644 index 0000000000..6e726af84a --- /dev/null +++ b/drivers/staging/media/meson/vdec/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Amlogic meson video decoder driver + +meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o +meson-vdec-objs += vdec_1.o vdec_hevc.o +meson-vdec-objs += codec_mpeg12.o codec_h264.o codec_hevc_common.o codec_vp9.o + +obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o diff --git a/drivers/staging/media/meson/vdec/TODO b/drivers/staging/media/meson/vdec/TODO new file mode 100644 index 0000000000..70ae990cf1 --- /dev/null +++ b/drivers/staging/media/meson/vdec/TODO @@ -0,0 +1,8 @@ +This driver is in staging until the V4L2 documentation about stateful video +decoders is finalized, as well as the corresponding compliance tests. + +It is at the moment not guaranteed to work properly with a userspace +stack that follows the latest version of the specification, especially +with compression standards like MPEG1/2 where the driver does not support +dynamic resolution switching, including the first one used to determine coded +resolution. diff --git a/drivers/staging/media/meson/vdec/codec_h264.c b/drivers/staging/media/meson/vdec/codec_h264.c new file mode 100644 index 0000000000..c61128fc4b --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_h264.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "vdec_helpers.h" +#include "dos_regs.h" +#include "codec_h264.h" + +#define SIZE_EXT_FW (20 * SZ_1K) +#define SIZE_WORKSPACE 0x1ee000 +#define SIZE_SEI (8 * SZ_1K) + +/* + * Offset added by the firmware which must be substracted + * from the workspace phyaddr + */ +#define WORKSPACE_BUF_OFFSET 0x1000000 + +/* ISR status */ +#define CMD_MASK GENMASK(7, 0) +#define CMD_SRC_CHANGE 1 +#define CMD_FRAMES_READY 2 +#define CMD_FATAL_ERROR 6 +#define CMD_BAD_WIDTH 7 +#define CMD_BAD_HEIGHT 8 + +#define SEI_DATA_READY BIT(15) + +/* Picture type */ +#define PIC_TOP_BOT 5 +#define PIC_BOT_TOP 6 + +/* Size of Motion Vector per macroblock */ +#define MB_MV_SIZE 96 + +/* Frame status data */ +#define PIC_STRUCT_BIT 5 +#define PIC_STRUCT_MASK GENMASK(2, 0) +#define BUF_IDX_MASK GENMASK(4, 0) +#define ERROR_FLAG BIT(9) +#define OFFSET_BIT 16 +#define OFFSET_MASK GENMASK(15, 0) + +/* Bitstream parsed data */ +#define MB_TOTAL_BIT 8 +#define MB_TOTAL_MASK GENMASK(15, 0) +#define MB_WIDTH_MASK GENMASK(7, 0) +#define MAX_REF_BIT 24 +#define MAX_REF_MASK GENMASK(6, 0) +#define AR_IDC_BIT 16 +#define AR_IDC_MASK GENMASK(7, 0) +#define AR_PRESENT_FLAG BIT(0) +#define AR_EXTEND 0xff + +/* + * Buffer to send to the ESPARSER to signal End Of Stream for H.264. + * This is a 16x16 encoded picture that will trigger drain firmware-side. + * There is no known alternative. + */ +static const u8 eos_sequence[SZ_4K] = { + 0x00, 0x00, 0x00, 0x01, 0x06, 0x05, 0xff, 0xe4, 0xdc, 0x45, 0xe9, 0xbd, + 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, + 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20, + 0x36, 0x37, 0x20, 0x72, 0x31, 0x31, 0x33, 0x30, 0x20, 0x38, 0x34, 0x37, + 0x35, 0x39, 0x37, 0x37, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34, + 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, + 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, + 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30, + 0x30, 0x39, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, + 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e, + 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, + 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65, + 0x66, 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, + 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, + 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20, + 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, + 0x3d, 0x36, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, + 0x30, 0x3a, 0x30, 0x2e, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, + 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, + 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, + 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, + 0x73, 0x3d, 0x30, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30, + 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, + 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x63, 0x68, + 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, + 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x6d, 0x62, 0x61, 0x66, + 0x66, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, + 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, + 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, + 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, + 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x61, 0x74, + 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, + 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, + 0x6e, 0x3d, 0x31, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x35, + 0x31, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69, + 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, + 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0a, 0x9a, 0x74, 0xf4, 0x20, + 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x51, 0xe2, 0x44, 0xd4, + 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x32, 0xc8, 0x00, 0x00, 0x00, 0x01, + 0x65, 0x88, 0x80, 0x20, 0x00, 0x08, 0x7f, 0xea, 0x6a, 0xe2, 0x99, 0xb6, + 0x57, 0xae, 0x49, 0x30, 0xf5, 0xfe, 0x5e, 0x46, 0x0b, 0x72, 0x44, 0xc4, + 0xe1, 0xfc, 0x62, 0xda, 0xf1, 0xfb, 0xa2, 0xdb, 0xd6, 0xbe, 0x5c, 0xd7, + 0x24, 0xa3, 0xf5, 0xb9, 0x2f, 0x57, 0x16, 0x49, 0x75, 0x47, 0x77, 0x09, + 0x5c, 0xa1, 0xb4, 0xc3, 0x4f, 0x60, 0x2b, 0xb0, 0x0c, 0xc8, 0xd6, 0x66, + 0xba, 0x9b, 0x82, 0x29, 0x33, 0x92, 0x26, 0x99, 0x31, 0x1c, 0x7f, 0x9b, + 0x00, 0x00, 0x01, 0x0ff, +}; + +static const u8 *codec_h264_eos_sequence(u32 *len) +{ + *len = ARRAY_SIZE(eos_sequence); + return eos_sequence; +} + +struct codec_h264 { + /* H.264 decoder requires an extended firmware */ + void *ext_fw_vaddr; + dma_addr_t ext_fw_paddr; + + /* Buffer for the H.264 Workspace */ + void *workspace_vaddr; + dma_addr_t workspace_paddr; + + /* Buffer for the H.264 references MV */ + void *ref_vaddr; + dma_addr_t ref_paddr; + u32 ref_size; + + /* Buffer for parsed SEI data */ + void *sei_vaddr; + dma_addr_t sei_paddr; + + u32 mb_width; + u32 mb_height; + u32 max_refs; +}; + +static int codec_h264_can_recycle(struct amvdec_core *core) +{ + return !amvdec_read_dos(core, AV_SCRATCH_7) || + !amvdec_read_dos(core, AV_SCRATCH_8); +} + +static void codec_h264_recycle(struct amvdec_core *core, u32 buf_idx) +{ + /* + * Tell the firmware it can recycle this buffer. + * AV_SCRATCH_8 serves the same purpose. + */ + if (!amvdec_read_dos(core, AV_SCRATCH_7)) + amvdec_write_dos(core, AV_SCRATCH_7, buf_idx + 1); + else + amvdec_write_dos(core, AV_SCRATCH_8, buf_idx + 1); +} + +static int codec_h264_start(struct amvdec_session *sess) +{ + u32 workspace_offset; + struct amvdec_core *core = sess->core; + struct codec_h264 *h264 = sess->priv; + + /* Allocate some memory for the H.264 decoder's state */ + h264->workspace_vaddr = + dma_alloc_coherent(core->dev, SIZE_WORKSPACE, + &h264->workspace_paddr, GFP_KERNEL); + if (!h264->workspace_vaddr) + return -ENOMEM; + + /* Allocate some memory for the H.264 SEI dump */ + h264->sei_vaddr = dma_alloc_coherent(core->dev, SIZE_SEI, + &h264->sei_paddr, GFP_KERNEL); + if (!h264->sei_vaddr) + return -ENOMEM; + + amvdec_write_dos_bits(core, POWER_CTL_VLD, BIT(9) | BIT(6)); + + workspace_offset = h264->workspace_paddr - WORKSPACE_BUF_OFFSET; + amvdec_write_dos(core, AV_SCRATCH_1, workspace_offset); + amvdec_write_dos(core, AV_SCRATCH_G, h264->ext_fw_paddr); + amvdec_write_dos(core, AV_SCRATCH_I, h264->sei_paddr - + workspace_offset); + + /* Enable "error correction" */ + amvdec_write_dos(core, AV_SCRATCH_F, + (amvdec_read_dos(core, AV_SCRATCH_F) & 0xffffffc3) | + BIT(4) | BIT(7)); + + amvdec_write_dos(core, MDEC_PIC_DC_THRESH, 0x404038aa); + + return 0; +} + +static int codec_h264_stop(struct amvdec_session *sess) +{ + struct codec_h264 *h264 = sess->priv; + struct amvdec_core *core = sess->core; + + if (h264->ext_fw_vaddr) + dma_free_coherent(core->dev, SIZE_EXT_FW, + h264->ext_fw_vaddr, h264->ext_fw_paddr); + + if (h264->workspace_vaddr) + dma_free_coherent(core->dev, SIZE_WORKSPACE, + h264->workspace_vaddr, h264->workspace_paddr); + + if (h264->ref_vaddr) + dma_free_coherent(core->dev, h264->ref_size, + h264->ref_vaddr, h264->ref_paddr); + + if (h264->sei_vaddr) + dma_free_coherent(core->dev, SIZE_SEI, + h264->sei_vaddr, h264->sei_paddr); + + return 0; +} + +static int codec_h264_load_extended_firmware(struct amvdec_session *sess, + const u8 *data, u32 len) +{ + struct codec_h264 *h264; + struct amvdec_core *core = sess->core; + + if (len < SIZE_EXT_FW) + return -EINVAL; + + h264 = kzalloc(sizeof(*h264), GFP_KERNEL); + if (!h264) + return -ENOMEM; + + h264->ext_fw_vaddr = dma_alloc_coherent(core->dev, SIZE_EXT_FW, + &h264->ext_fw_paddr, + GFP_KERNEL); + if (!h264->ext_fw_vaddr) { + kfree(h264); + return -ENOMEM; + } + + memcpy(h264->ext_fw_vaddr, data, SIZE_EXT_FW); + sess->priv = h264; + + return 0; +} + +static const struct v4l2_fract par_table[] = { + { 1, 1 }, { 1, 1 }, { 12, 11 }, { 10, 11 }, + { 16, 11 }, { 40, 33 }, { 24, 11 }, { 20, 11 }, + { 32, 11 }, { 80, 33 }, { 18, 11 }, { 15, 11 }, + { 64, 33 }, { 160, 99 }, { 4, 3 }, { 3, 2 }, + { 2, 1 } +}; + +static void codec_h264_set_par(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 seq_info = amvdec_read_dos(core, AV_SCRATCH_2); + u32 ar_idc = (seq_info >> AR_IDC_BIT) & AR_IDC_MASK; + + if (!(seq_info & AR_PRESENT_FLAG)) + return; + + if (ar_idc == AR_EXTEND) { + u32 ar_info = amvdec_read_dos(core, AV_SCRATCH_3); + + sess->pixelaspect.numerator = ar_info & 0xffff; + sess->pixelaspect.denominator = (ar_info >> 16) & 0xffff; + return; + } + + if (ar_idc >= ARRAY_SIZE(par_table)) + return; + + sess->pixelaspect = par_table[ar_idc]; +} + +static void codec_h264_resume(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_h264 *h264 = sess->priv; + u32 mb_width, mb_height, mb_total; + + amvdec_set_canvases(sess, + (u32[]){ ANC0_CANVAS_ADDR, 0 }, + (u32[]){ 24, 0 }); + + dev_dbg(core->dev, "max_refs = %u; actual_dpb_size = %u\n", + h264->max_refs, sess->num_dst_bufs); + + /* Align to a multiple of 4 macroblocks */ + mb_width = ALIGN(h264->mb_width, 4); + mb_height = ALIGN(h264->mb_height, 4); + mb_total = mb_width * mb_height; + + h264->ref_size = mb_total * MB_MV_SIZE * h264->max_refs; + h264->ref_vaddr = dma_alloc_coherent(core->dev, h264->ref_size, + &h264->ref_paddr, GFP_KERNEL); + if (!h264->ref_vaddr) { + amvdec_abort(sess); + return; + } + + /* Address to store the references' MVs */ + amvdec_write_dos(core, AV_SCRATCH_1, h264->ref_paddr); + /* End of ref MV */ + amvdec_write_dos(core, AV_SCRATCH_4, h264->ref_paddr + h264->ref_size); + + amvdec_write_dos(core, AV_SCRATCH_0, (h264->max_refs << 24) | + (sess->num_dst_bufs << 16) | + ((h264->max_refs - 1) << 8)); +} + +/* + * Configure the H.264 decoder when the parser detected a parameter set change + */ +static void codec_h264_src_change(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_h264 *h264 = sess->priv; + u32 parsed_info, mb_total; + u32 crop_infor, crop_bottom, crop_right; + u32 frame_width, frame_height; + + sess->keyframe_found = 1; + + parsed_info = amvdec_read_dos(core, AV_SCRATCH_1); + + /* Total number of 16x16 macroblocks */ + mb_total = (parsed_info >> MB_TOTAL_BIT) & MB_TOTAL_MASK; + /* Number of macroblocks per line */ + h264->mb_width = parsed_info & MB_WIDTH_MASK; + /* Number of macroblock lines */ + h264->mb_height = mb_total / h264->mb_width; + + h264->max_refs = ((parsed_info >> MAX_REF_BIT) & MAX_REF_MASK) + 1; + + crop_infor = amvdec_read_dos(core, AV_SCRATCH_6); + crop_bottom = (crop_infor & 0xff); + crop_right = (crop_infor >> 16) & 0xff; + + frame_width = h264->mb_width * 16 - crop_right; + frame_height = h264->mb_height * 16 - crop_bottom; + + dev_dbg(core->dev, "frame: %ux%u; crop: %u %u\n", + frame_width, frame_height, crop_right, crop_bottom); + + codec_h264_set_par(sess); + amvdec_src_change(sess, frame_width, frame_height, h264->max_refs + 5); +} + +/* + * The bitstream offset is split in half in 2 different registers. + * Fetch its MSB here, which location depends on the frame number. + */ +static u32 get_offset_msb(struct amvdec_core *core, int frame_num) +{ + int take_msb = frame_num % 2; + int reg_offset = (frame_num / 2) * 4; + u32 offset_msb = amvdec_read_dos(core, AV_SCRATCH_A + reg_offset); + + if (take_msb) + return offset_msb & 0xffff0000; + + return (offset_msb & 0x0000ffff) << 16; +} + +static void codec_h264_frames_ready(struct amvdec_session *sess, u32 status) +{ + struct amvdec_core *core = sess->core; + int error_count; + int num_frames; + int i; + + error_count = amvdec_read_dos(core, AV_SCRATCH_D); + num_frames = (status >> 8) & 0xff; + if (error_count) { + dev_warn(core->dev, + "decoder error(s) happened, count %d\n", error_count); + amvdec_write_dos(core, AV_SCRATCH_D, 0); + } + + for (i = 0; i < num_frames; i++) { + u32 frame_status = amvdec_read_dos(core, AV_SCRATCH_1 + i * 4); + u32 buffer_index = frame_status & BUF_IDX_MASK; + u32 pic_struct = (frame_status >> PIC_STRUCT_BIT) & + PIC_STRUCT_MASK; + u32 offset = (frame_status >> OFFSET_BIT) & OFFSET_MASK; + u32 field = V4L2_FIELD_NONE; + + /* + * A buffer decode error means it was decoded, + * but part of the picture will have artifacts. + * Typical reason is a temporarily corrupted bitstream + */ + if (frame_status & ERROR_FLAG) + dev_dbg(core->dev, "Buffer %d decode error\n", + buffer_index); + + if (pic_struct == PIC_TOP_BOT) + field = V4L2_FIELD_INTERLACED_TB; + else if (pic_struct == PIC_BOT_TOP) + field = V4L2_FIELD_INTERLACED_BT; + + offset |= get_offset_msb(core, i); + amvdec_dst_buf_done_idx(sess, buffer_index, offset, field); + } +} + +static irqreturn_t codec_h264_threaded_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 status; + u32 size; + u8 cmd; + + status = amvdec_read_dos(core, AV_SCRATCH_0); + cmd = status & CMD_MASK; + + switch (cmd) { + case CMD_SRC_CHANGE: + codec_h264_src_change(sess); + break; + case CMD_FRAMES_READY: + codec_h264_frames_ready(sess, status); + break; + case CMD_FATAL_ERROR: + dev_err(core->dev, "H.264 decoder fatal error\n"); + goto abort; + case CMD_BAD_WIDTH: + size = (amvdec_read_dos(core, AV_SCRATCH_1) + 1) * 16; + dev_err(core->dev, "Unsupported video width: %u\n", size); + goto abort; + case CMD_BAD_HEIGHT: + size = (amvdec_read_dos(core, AV_SCRATCH_1) + 1) * 16; + dev_err(core->dev, "Unsupported video height: %u\n", size); + goto abort; + case 0: /* Unused but not worth printing for */ + case 9: + break; + default: + dev_info(core->dev, "Unexpected H264 ISR: %08X\n", cmd); + break; + } + + if (cmd && cmd != CMD_SRC_CHANGE) + amvdec_write_dos(core, AV_SCRATCH_0, 0); + + /* Decoder has some SEI data for us ; ignore */ + if (amvdec_read_dos(core, AV_SCRATCH_J) & SEI_DATA_READY) + amvdec_write_dos(core, AV_SCRATCH_J, 0); + + return IRQ_HANDLED; +abort: + amvdec_abort(sess); + return IRQ_HANDLED; +} + +static irqreturn_t codec_h264_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); + + return IRQ_WAKE_THREAD; +} + +struct amvdec_codec_ops codec_h264_ops = { + .start = codec_h264_start, + .stop = codec_h264_stop, + .load_extended_firmware = codec_h264_load_extended_firmware, + .isr = codec_h264_isr, + .threaded_isr = codec_h264_threaded_isr, + .can_recycle = codec_h264_can_recycle, + .recycle = codec_h264_recycle, + .eos_sequence = codec_h264_eos_sequence, + .resume = codec_h264_resume, +}; diff --git a/drivers/staging/media/meson/vdec/codec_h264.h b/drivers/staging/media/meson/vdec/codec_h264.h new file mode 100644 index 0000000000..7cb4fb86ff --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_h264.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_CODEC_H264_H_ +#define __MESON_VDEC_CODEC_H264_H_ + +#include "vdec.h" + +extern struct amvdec_codec_ops codec_h264_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/codec_hevc_common.c b/drivers/staging/media/meson/vdec/codec_hevc_common.c new file mode 100644 index 0000000000..0315cc0911 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_hevc_common.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "codec_hevc_common.h" +#include "vdec_helpers.h" +#include "hevc_regs.h" + +#define MMU_COMPRESS_HEADER_SIZE 0x48000 +#define MMU_MAP_SIZE 0x4800 + +const u16 vdec_hevc_parser_cmd[] = { + 0x0401, 0x8401, 0x0800, 0x0402, + 0x9002, 0x1423, 0x8CC3, 0x1423, + 0x8804, 0x9825, 0x0800, 0x04FE, + 0x8406, 0x8411, 0x1800, 0x8408, + 0x8409, 0x8C2A, 0x9C2B, 0x1C00, + 0x840F, 0x8407, 0x8000, 0x8408, + 0x2000, 0xA800, 0x8410, 0x04DE, + 0x840C, 0x840D, 0xAC00, 0xA000, + 0x08C0, 0x08E0, 0xA40E, 0xFC00, + 0x7C00 +}; + +/* Configure decode head read mode */ +void codec_hevc_setup_decode_head(struct amvdec_session *sess, int is_10bit) +{ + struct amvdec_core *core = sess->core; + u32 body_size = amvdec_am21c_body_size(sess->width, sess->height); + u32 head_size = amvdec_am21c_head_size(sess->width, sess->height); + + if (!codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) { + /* Enable 2-plane reference read mode */ + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, BIT(31)); + return; + } + + if (codec_hevc_use_mmu(core->platform->revision, + sess->pixfmt_cap, is_10bit)) + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, BIT(4)); + else + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL1, 0); + + if (core->platform->revision < VDEC_REVISION_SM1) + amvdec_write_dos(core, HEVCD_MPP_DECOMP_CTL2, body_size / 32); + amvdec_write_dos(core, HEVC_CM_BODY_LENGTH, body_size); + amvdec_write_dos(core, HEVC_CM_HEADER_OFFSET, body_size); + amvdec_write_dos(core, HEVC_CM_HEADER_LENGTH, head_size); +} +EXPORT_SYMBOL_GPL(codec_hevc_setup_decode_head); + +static void codec_hevc_setup_buffers_gxbb(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + struct v4l2_m2m_buffer *buf; + u32 buf_num = v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); + dma_addr_t buf_y_paddr = 0; + dma_addr_t buf_uv_paddr = 0; + u32 idx = 0; + u32 val; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 0); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + struct vb2_buffer *vb = &buf->vb.vb2_buf; + + idx = vb->index; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, is_10bit)) + buf_y_paddr = comm->fbc_buffer_paddr[idx]; + else + buf_y_paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) { + val = buf_y_paddr | (idx << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + } else { + buf_uv_paddr = vb2_dma_contig_plane_dma_addr(vb, 1); + val = buf_y_paddr | ((idx * 2) << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + val = buf_uv_paddr | ((idx * 2 + 1) << 8) | 1; + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, + val); + } + } + + if (codec_hevc_use_fbc(sess->pixfmt_cap, is_10bit)) + val = buf_y_paddr | (idx << 8) | 1; + else + val = buf_y_paddr | ((idx * 2) << 8) | 1; + + /* Fill the remaining unused slots with the last buffer's Y addr */ + for (i = buf_num; i < MAX_REF_PIC_NUM; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR, val); + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + for (i = 0; i < 32; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, 0); +} + +static void codec_hevc_setup_buffers_gxl(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + struct v4l2_m2m_buffer *buf; + u32 revision = core->platform->revision; + u32 pixfmt_cap = sess->pixfmt_cap; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, + BIT(2) | BIT(1)); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + struct vb2_buffer *vb = &buf->vb.vb2_buf; + dma_addr_t buf_y_paddr = 0; + dma_addr_t buf_uv_paddr = 0; + u32 idx = vb->index; + + if (codec_hevc_use_mmu(revision, pixfmt_cap, is_10bit)) + buf_y_paddr = comm->mmu_header_paddr[idx]; + else if (codec_hevc_use_downsample(pixfmt_cap, is_10bit)) + buf_y_paddr = comm->fbc_buffer_paddr[idx]; + else + buf_y_paddr = vb2_dma_contig_plane_dma_addr(vb, 0); + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_DATA, + buf_y_paddr >> 5); + + if (!codec_hevc_use_fbc(pixfmt_cap, is_10bit)) { + buf_uv_paddr = vb2_dma_contig_plane_dma_addr(vb, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_DATA, + buf_uv_paddr >> 5); + } + } + + amvdec_write_dos(core, HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR, 1); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + for (i = 0; i < 32; ++i) + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, 0); +} + +void codec_hevc_free_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + u32 am21_size = amvdec_am21c_size(sess->width, sess->height); + int i; + + for (i = 0; i < MAX_REF_PIC_NUM; ++i) { + if (comm->fbc_buffer_vaddr[i]) { + dma_free_coherent(dev, am21_size, + comm->fbc_buffer_vaddr[i], + comm->fbc_buffer_paddr[i]); + comm->fbc_buffer_vaddr[i] = NULL; + } + } +} +EXPORT_SYMBOL_GPL(codec_hevc_free_fbc_buffers); + +static int codec_hevc_alloc_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + struct v4l2_m2m_buffer *buf; + u32 am21_size = amvdec_am21c_size(sess->width, sess->height); + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + u32 idx = buf->vb.vb2_buf.index; + dma_addr_t paddr; + void *vaddr = dma_alloc_coherent(dev, am21_size, &paddr, + GFP_KERNEL); + if (!vaddr) { + codec_hevc_free_fbc_buffers(sess, comm); + return -ENOMEM; + } + + comm->fbc_buffer_vaddr[idx] = vaddr; + comm->fbc_buffer_paddr[idx] = paddr; + } + + return 0; +} + +void codec_hevc_free_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + int i; + + for (i = 0; i < MAX_REF_PIC_NUM; ++i) { + if (comm->mmu_header_vaddr[i]) { + dma_free_coherent(dev, MMU_COMPRESS_HEADER_SIZE, + comm->mmu_header_vaddr[i], + comm->mmu_header_paddr[i]); + comm->mmu_header_vaddr[i] = NULL; + } + } + + if (comm->mmu_map_vaddr) { + dma_free_coherent(dev, MMU_MAP_SIZE, + comm->mmu_map_vaddr, + comm->mmu_map_paddr); + comm->mmu_map_vaddr = NULL; + } +} +EXPORT_SYMBOL_GPL(codec_hevc_free_mmu_headers); + +static int codec_hevc_alloc_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm) +{ + struct device *dev = sess->core->dev; + struct v4l2_m2m_buffer *buf; + + comm->mmu_map_vaddr = dma_alloc_coherent(dev, MMU_MAP_SIZE, + &comm->mmu_map_paddr, + GFP_KERNEL); + if (!comm->mmu_map_vaddr) + return -ENOMEM; + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + u32 idx = buf->vb.vb2_buf.index; + dma_addr_t paddr; + void *vaddr = dma_alloc_coherent(dev, MMU_COMPRESS_HEADER_SIZE, + &paddr, GFP_KERNEL); + if (!vaddr) { + codec_hevc_free_mmu_headers(sess, comm); + return -ENOMEM; + } + + comm->mmu_header_vaddr[idx] = vaddr; + comm->mmu_header_paddr[idx] = paddr; + } + + return 0; +} + +int codec_hevc_setup_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit) +{ + struct amvdec_core *core = sess->core; + int ret; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, is_10bit)) { + ret = codec_hevc_alloc_fbc_buffers(sess, comm); + if (ret) + return ret; + } + + if (codec_hevc_use_mmu(core->platform->revision, + sess->pixfmt_cap, is_10bit)) { + ret = codec_hevc_alloc_mmu_headers(sess, comm); + if (ret) { + codec_hevc_free_fbc_buffers(sess, comm); + return ret; + } + } + + if (core->platform->revision == VDEC_REVISION_GXBB) + codec_hevc_setup_buffers_gxbb(sess, comm, is_10bit); + else + codec_hevc_setup_buffers_gxl(sess, comm, is_10bit); + + return 0; +} +EXPORT_SYMBOL_GPL(codec_hevc_setup_buffers); + +void codec_hevc_fill_mmu_map(struct amvdec_session *sess, + struct codec_hevc_common *comm, + struct vb2_buffer *vb) +{ + u32 size = amvdec_am21c_size(sess->width, sess->height); + u32 nb_pages = size / PAGE_SIZE; + u32 *mmu_map = comm->mmu_map_vaddr; + u32 first_page; + u32 i; + + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) + first_page = comm->fbc_buffer_paddr[vb->index] >> PAGE_SHIFT; + else + first_page = vb2_dma_contig_plane_dma_addr(vb, 0) >> PAGE_SHIFT; + + for (i = 0; i < nb_pages; ++i) + mmu_map[i] = first_page + i; +} +EXPORT_SYMBOL_GPL(codec_hevc_fill_mmu_map); diff --git a/drivers/staging/media/meson/vdec/codec_hevc_common.h b/drivers/staging/media/meson/vdec/codec_hevc_common.h new file mode 100644 index 0000000000..cf072b8a9d --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_hevc_common.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_HEVC_COMMON_H_ +#define __MESON_VDEC_HEVC_COMMON_H_ + +#include "vdec.h" + +#define PARSER_CMD_SKIP_CFG_0 0x0000090b +#define PARSER_CMD_SKIP_CFG_1 0x1b14140f +#define PARSER_CMD_SKIP_CFG_2 0x001b1910 + +#define VDEC_HEVC_PARSER_CMD_LEN 37 +extern const u16 vdec_hevc_parser_cmd[VDEC_HEVC_PARSER_CMD_LEN]; + +#define MAX_REF_PIC_NUM 24 + +struct codec_hevc_common { + void *fbc_buffer_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t fbc_buffer_paddr[MAX_REF_PIC_NUM]; + + void *mmu_header_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t mmu_header_paddr[MAX_REF_PIC_NUM]; + + void *mmu_map_vaddr; + dma_addr_t mmu_map_paddr; +}; + +/* Returns 1 if we must use framebuffer compression */ +static inline int codec_hevc_use_fbc(u32 pixfmt, int is_10bit) +{ + /* TOFIX: Handle Amlogic Compressed buffer for 8bit also */ + return is_10bit; +} + +/* Returns 1 if we are decoding 10-bit but outputting 8-bit NV12 */ +static inline int codec_hevc_use_downsample(u32 pixfmt, int is_10bit) +{ + return is_10bit; +} + +/* Returns 1 if we are decoding using the IOMMU */ +static inline int codec_hevc_use_mmu(u32 revision, u32 pixfmt, int is_10bit) +{ + return revision >= VDEC_REVISION_G12A && + codec_hevc_use_fbc(pixfmt, is_10bit); +} + +/* Configure decode head read mode */ +void codec_hevc_setup_decode_head(struct amvdec_session *sess, int is_10bit); + +void codec_hevc_free_fbc_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm); + +void codec_hevc_free_mmu_headers(struct amvdec_session *sess, + struct codec_hevc_common *comm); + +int codec_hevc_setup_buffers(struct amvdec_session *sess, + struct codec_hevc_common *comm, + int is_10bit); + +void codec_hevc_fill_mmu_map(struct amvdec_session *sess, + struct codec_hevc_common *comm, + struct vb2_buffer *vb); + +#endif diff --git a/drivers/staging/media/meson/vdec/codec_mpeg12.c b/drivers/staging/media/meson/vdec/codec_mpeg12.c new file mode 100644 index 0000000000..48869cc3d9 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_mpeg12.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "codec_mpeg12.h" +#include "dos_regs.h" +#include "vdec_helpers.h" + +#define SIZE_WORKSPACE SZ_128K +/* Offset substracted by the firmware from the workspace paddr */ +#define WORKSPACE_OFFSET (5 * SZ_1K) + +/* map firmware registers to known MPEG1/2 functions */ +#define MREG_SEQ_INFO AV_SCRATCH_4 + #define MPEG2_SEQ_DAR_MASK GENMASK(3, 0) + #define MPEG2_DAR_4_3 2 + #define MPEG2_DAR_16_9 3 + #define MPEG2_DAR_221_100 4 +#define MREG_PIC_INFO AV_SCRATCH_5 +#define MREG_PIC_WIDTH AV_SCRATCH_6 +#define MREG_PIC_HEIGHT AV_SCRATCH_7 +#define MREG_BUFFERIN AV_SCRATCH_8 +#define MREG_BUFFEROUT AV_SCRATCH_9 +#define MREG_CMD AV_SCRATCH_A +#define MREG_CO_MV_START AV_SCRATCH_B +#define MREG_ERROR_COUNT AV_SCRATCH_C +#define MREG_FRAME_OFFSET AV_SCRATCH_D +#define MREG_WAIT_BUFFER AV_SCRATCH_E +#define MREG_FATAL_ERROR AV_SCRATCH_F + +#define PICINFO_PROG 0x00008000 +#define PICINFO_TOP_FIRST 0x00002000 + +struct codec_mpeg12 { + /* Buffer for the MPEG1/2 Workspace */ + void *workspace_vaddr; + dma_addr_t workspace_paddr; +}; + +static const u8 eos_sequence[SZ_1K] = { 0x00, 0x00, 0x01, 0xB7 }; + +static const u8 *codec_mpeg12_eos_sequence(u32 *len) +{ + *len = ARRAY_SIZE(eos_sequence); + return eos_sequence; +} + +static int codec_mpeg12_can_recycle(struct amvdec_core *core) +{ + return !amvdec_read_dos(core, MREG_BUFFERIN); +} + +static void codec_mpeg12_recycle(struct amvdec_core *core, u32 buf_idx) +{ + amvdec_write_dos(core, MREG_BUFFERIN, buf_idx + 1); +} + +static int codec_mpeg12_start(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_mpeg12 *mpeg12; + int ret; + + mpeg12 = kzalloc(sizeof(*mpeg12), GFP_KERNEL); + if (!mpeg12) + return -ENOMEM; + + /* Allocate some memory for the MPEG1/2 decoder's state */ + mpeg12->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE, + &mpeg12->workspace_paddr, + GFP_KERNEL); + if (!mpeg12->workspace_vaddr) { + dev_err(core->dev, "Failed to request MPEG 1/2 Workspace\n"); + ret = -ENOMEM; + goto free_mpeg12; + } + + ret = amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_0, 0 }, + (u32[]){ 8, 0 }); + if (ret) + goto free_workspace; + + amvdec_write_dos(core, POWER_CTL_VLD, BIT(4)); + amvdec_write_dos(core, MREG_CO_MV_START, + mpeg12->workspace_paddr + WORKSPACE_OFFSET); + + amvdec_write_dos(core, MPEG1_2_REG, 0); + amvdec_write_dos(core, PSCALE_CTRL, 0); + amvdec_write_dos(core, PIC_HEAD_INFO, 0x380); + amvdec_write_dos(core, M4_CONTROL_REG, 0); + amvdec_write_dos(core, MREG_BUFFERIN, 0); + amvdec_write_dos(core, MREG_BUFFEROUT, 0); + amvdec_write_dos(core, MREG_CMD, (sess->width << 16) | sess->height); + amvdec_write_dos(core, MREG_ERROR_COUNT, 0); + amvdec_write_dos(core, MREG_FATAL_ERROR, 0); + amvdec_write_dos(core, MREG_WAIT_BUFFER, 0); + + sess->keyframe_found = 1; + sess->priv = mpeg12; + + return 0; + +free_workspace: + dma_free_coherent(core->dev, SIZE_WORKSPACE, mpeg12->workspace_vaddr, + mpeg12->workspace_paddr); +free_mpeg12: + kfree(mpeg12); + + return ret; +} + +static int codec_mpeg12_stop(struct amvdec_session *sess) +{ + struct codec_mpeg12 *mpeg12 = sess->priv; + struct amvdec_core *core = sess->core; + + if (mpeg12->workspace_vaddr) + dma_free_coherent(core->dev, SIZE_WORKSPACE, + mpeg12->workspace_vaddr, + mpeg12->workspace_paddr); + + return 0; +} + +static void codec_mpeg12_update_dar(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 seq = amvdec_read_dos(core, MREG_SEQ_INFO); + u32 ar = seq & MPEG2_SEQ_DAR_MASK; + + switch (ar) { + case MPEG2_DAR_4_3: + amvdec_set_par_from_dar(sess, 4, 3); + break; + case MPEG2_DAR_16_9: + amvdec_set_par_from_dar(sess, 16, 9); + break; + case MPEG2_DAR_221_100: + amvdec_set_par_from_dar(sess, 221, 100); + break; + default: + sess->pixelaspect.numerator = 1; + sess->pixelaspect.denominator = 1; + break; + } +} + +static irqreturn_t codec_mpeg12_threaded_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 reg; + u32 pic_info; + u32 is_progressive; + u32 buffer_index; + u32 field = V4L2_FIELD_NONE; + u32 offset; + + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); + reg = amvdec_read_dos(core, MREG_FATAL_ERROR); + if (reg == 1) { + dev_err(core->dev, "MPEG1/2 fatal error\n"); + amvdec_abort(sess); + return IRQ_HANDLED; + } + + reg = amvdec_read_dos(core, MREG_BUFFEROUT); + if (!reg) + return IRQ_HANDLED; + + /* Unclear what this means */ + if ((reg & GENMASK(23, 17)) == GENMASK(23, 17)) + goto end; + + pic_info = amvdec_read_dos(core, MREG_PIC_INFO); + is_progressive = pic_info & PICINFO_PROG; + + if (!is_progressive) + field = (pic_info & PICINFO_TOP_FIRST) ? + V4L2_FIELD_INTERLACED_TB : + V4L2_FIELD_INTERLACED_BT; + + codec_mpeg12_update_dar(sess); + buffer_index = ((reg & 0xf) - 1) & 7; + offset = amvdec_read_dos(core, MREG_FRAME_OFFSET); + amvdec_dst_buf_done_idx(sess, buffer_index, offset, field); + +end: + amvdec_write_dos(core, MREG_BUFFEROUT, 0); + return IRQ_HANDLED; +} + +static irqreturn_t codec_mpeg12_isr(struct amvdec_session *sess) +{ + return IRQ_WAKE_THREAD; +} + +struct amvdec_codec_ops codec_mpeg12_ops = { + .start = codec_mpeg12_start, + .stop = codec_mpeg12_stop, + .isr = codec_mpeg12_isr, + .threaded_isr = codec_mpeg12_threaded_isr, + .can_recycle = codec_mpeg12_can_recycle, + .recycle = codec_mpeg12_recycle, + .eos_sequence = codec_mpeg12_eos_sequence, +}; diff --git a/drivers/staging/media/meson/vdec/codec_mpeg12.h b/drivers/staging/media/meson/vdec/codec_mpeg12.h new file mode 100644 index 0000000000..43cab5f39c --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_mpeg12.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_CODEC_MPEG12_H_ +#define __MESON_VDEC_CODEC_MPEG12_H_ + +#include "vdec.h" + +extern struct amvdec_codec_ops codec_mpeg12_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/codec_vp9.c b/drivers/staging/media/meson/vdec/codec_vp9.c new file mode 100644 index 0000000000..394df57615 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_vp9.c @@ -0,0 +1,2170 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan <mjourdan@baylibre.com> + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-contig.h> + +#include "dos_regs.h" +#include "hevc_regs.h" +#include "codec_vp9.h" +#include "vdec_helpers.h" +#include "codec_hevc_common.h" + +/* HEVC reg mapping */ +#define VP9_DEC_STATUS_REG HEVC_ASSIST_SCRATCH_0 + #define VP9_10B_DECODE_SLICE 5 + #define VP9_HEAD_PARSER_DONE 0xf0 +#define VP9_RPM_BUFFER HEVC_ASSIST_SCRATCH_1 +#define VP9_SHORT_TERM_RPS HEVC_ASSIST_SCRATCH_2 +#define VP9_ADAPT_PROB_REG HEVC_ASSIST_SCRATCH_3 +#define VP9_MMU_MAP_BUFFER HEVC_ASSIST_SCRATCH_4 +#define VP9_PPS_BUFFER HEVC_ASSIST_SCRATCH_5 +#define VP9_SAO_UP HEVC_ASSIST_SCRATCH_6 +#define VP9_STREAM_SWAP_BUFFER HEVC_ASSIST_SCRATCH_7 +#define VP9_STREAM_SWAP_BUFFER2 HEVC_ASSIST_SCRATCH_8 +#define VP9_PROB_SWAP_BUFFER HEVC_ASSIST_SCRATCH_9 +#define VP9_COUNT_SWAP_BUFFER HEVC_ASSIST_SCRATCH_A +#define VP9_SEG_MAP_BUFFER HEVC_ASSIST_SCRATCH_B +#define VP9_SCALELUT HEVC_ASSIST_SCRATCH_D +#define VP9_WAIT_FLAG HEVC_ASSIST_SCRATCH_E +#define LMEM_DUMP_ADR HEVC_ASSIST_SCRATCH_F +#define NAL_SEARCH_CTL HEVC_ASSIST_SCRATCH_I +#define VP9_DECODE_MODE HEVC_ASSIST_SCRATCH_J + #define DECODE_MODE_SINGLE 0 +#define DECODE_STOP_POS HEVC_ASSIST_SCRATCH_K +#define HEVC_DECODE_COUNT HEVC_ASSIST_SCRATCH_M +#define HEVC_DECODE_SIZE HEVC_ASSIST_SCRATCH_N + +/* VP9 Constants */ +#define LCU_SIZE 64 +#define MAX_REF_PIC_NUM 24 +#define REFS_PER_FRAME 3 +#define REF_FRAMES 8 +#define MV_MEM_UNIT 0x240 +#define ADAPT_PROB_SIZE 0xf80 + +enum FRAME_TYPE { + KEY_FRAME = 0, + INTER_FRAME = 1, + FRAME_TYPES, +}; + +/* VP9 Workspace layout */ +#define MPRED_MV_BUF_SIZE 0x120000 + +#define IPP_SIZE 0x4000 +#define SAO_ABV_SIZE 0x30000 +#define SAO_VB_SIZE 0x30000 +#define SH_TM_RPS_SIZE 0x800 +#define VPS_SIZE 0x800 +#define SPS_SIZE 0x800 +#define PPS_SIZE 0x2000 +#define SAO_UP_SIZE 0x2800 +#define SWAP_BUF_SIZE 0x800 +#define SWAP_BUF2_SIZE 0x800 +#define SCALELUT_SIZE 0x8000 +#define DBLK_PARA_SIZE 0x80000 +#define DBLK_DATA_SIZE 0x80000 +#define SEG_MAP_SIZE 0xd800 +#define PROB_SIZE 0x5000 +#define COUNT_SIZE 0x3000 +#define MMU_VBH_SIZE 0x5000 +#define MPRED_ABV_SIZE 0x10000 +#define MPRED_MV_SIZE (MPRED_MV_BUF_SIZE * MAX_REF_PIC_NUM) +#define RPM_BUF_SIZE 0x100 +#define LMEM_SIZE 0x800 + +#define IPP_OFFSET 0x00 +#define SAO_ABV_OFFSET (IPP_OFFSET + IPP_SIZE) +#define SAO_VB_OFFSET (SAO_ABV_OFFSET + SAO_ABV_SIZE) +#define SH_TM_RPS_OFFSET (SAO_VB_OFFSET + SAO_VB_SIZE) +#define VPS_OFFSET (SH_TM_RPS_OFFSET + SH_TM_RPS_SIZE) +#define SPS_OFFSET (VPS_OFFSET + VPS_SIZE) +#define PPS_OFFSET (SPS_OFFSET + SPS_SIZE) +#define SAO_UP_OFFSET (PPS_OFFSET + PPS_SIZE) +#define SWAP_BUF_OFFSET (SAO_UP_OFFSET + SAO_UP_SIZE) +#define SWAP_BUF2_OFFSET (SWAP_BUF_OFFSET + SWAP_BUF_SIZE) +#define SCALELUT_OFFSET (SWAP_BUF2_OFFSET + SWAP_BUF2_SIZE) +#define DBLK_PARA_OFFSET (SCALELUT_OFFSET + SCALELUT_SIZE) +#define DBLK_DATA_OFFSET (DBLK_PARA_OFFSET + DBLK_PARA_SIZE) +#define SEG_MAP_OFFSET (DBLK_DATA_OFFSET + DBLK_DATA_SIZE) +#define PROB_OFFSET (SEG_MAP_OFFSET + SEG_MAP_SIZE) +#define COUNT_OFFSET (PROB_OFFSET + PROB_SIZE) +#define MMU_VBH_OFFSET (COUNT_OFFSET + COUNT_SIZE) +#define MPRED_ABV_OFFSET (MMU_VBH_OFFSET + MMU_VBH_SIZE) +#define MPRED_MV_OFFSET (MPRED_ABV_OFFSET + MPRED_ABV_SIZE) +#define RPM_OFFSET (MPRED_MV_OFFSET + MPRED_MV_SIZE) +#define LMEM_OFFSET (RPM_OFFSET + RPM_BUF_SIZE) + +#define SIZE_WORKSPACE ALIGN(LMEM_OFFSET + LMEM_SIZE, 64 * SZ_1K) + +#define NONE -1 +#define INTRA_FRAME 0 +#define LAST_FRAME 1 +#define GOLDEN_FRAME 2 +#define ALTREF_FRAME 3 +#define MAX_REF_FRAMES 4 + +/* + * Defines, declarations, sub-functions for vp9 de-block loop + filter Thr/Lvl table update + * - struct segmentation is for loop filter only (removed something) + * - function "vp9_loop_filter_init" and "vp9_loop_filter_frame_init" will + be instantiated in C_Entry + * - vp9_loop_filter_init run once before decoding start + * - vp9_loop_filter_frame_init run before every frame decoding start + * - set video format to VP9 is in vp9_loop_filter_init + */ +#define MAX_LOOP_FILTER 63 +#define MAX_REF_LF_DELTAS 4 +#define MAX_MODE_LF_DELTAS 2 +#define SEGMENT_DELTADATA 0 +#define SEGMENT_ABSDATA 1 +#define MAX_SEGMENTS 8 + +/* VP9 PROB processing defines */ +#define VP9_PARTITION_START 0 +#define VP9_PARTITION_SIZE_STEP (3 * 4) +#define VP9_PARTITION_ONE_SIZE (4 * VP9_PARTITION_SIZE_STEP) +#define VP9_PARTITION_KEY_START 0 +#define VP9_PARTITION_P_START VP9_PARTITION_ONE_SIZE +#define VP9_PARTITION_SIZE (2 * VP9_PARTITION_ONE_SIZE) +#define VP9_SKIP_START (VP9_PARTITION_START + VP9_PARTITION_SIZE) +#define VP9_SKIP_SIZE 4 /* only use 3*/ +#define VP9_TX_MODE_START (VP9_SKIP_START + VP9_SKIP_SIZE) +#define VP9_TX_MODE_8_0_OFFSET 0 +#define VP9_TX_MODE_8_1_OFFSET 1 +#define VP9_TX_MODE_16_0_OFFSET 2 +#define VP9_TX_MODE_16_1_OFFSET 4 +#define VP9_TX_MODE_32_0_OFFSET 6 +#define VP9_TX_MODE_32_1_OFFSET 9 +#define VP9_TX_MODE_SIZE 12 +#define VP9_COEF_START (VP9_TX_MODE_START + VP9_TX_MODE_SIZE) +#define VP9_COEF_BAND_0_OFFSET 0 +#define VP9_COEF_BAND_1_OFFSET (VP9_COEF_BAND_0_OFFSET + 3 * 3 + 1) +#define VP9_COEF_BAND_2_OFFSET (VP9_COEF_BAND_1_OFFSET + 6 * 3) +#define VP9_COEF_BAND_3_OFFSET (VP9_COEF_BAND_2_OFFSET + 6 * 3) +#define VP9_COEF_BAND_4_OFFSET (VP9_COEF_BAND_3_OFFSET + 6 * 3) +#define VP9_COEF_BAND_5_OFFSET (VP9_COEF_BAND_4_OFFSET + 6 * 3) +#define VP9_COEF_SIZE_ONE_SET 100 /* ((3 + 5 * 6) * 3 + 1 padding)*/ +#define VP9_COEF_4X4_START (VP9_COEF_START + 0 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_8X8_START (VP9_COEF_START + 4 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_16X16_START (VP9_COEF_START + 8 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_32X32_START (VP9_COEF_START + 12 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_SIZE_PLANE (2 * VP9_COEF_SIZE_ONE_SET) +#define VP9_COEF_SIZE (4 * 2 * 2 * VP9_COEF_SIZE_ONE_SET) +#define VP9_INTER_MODE_START (VP9_COEF_START + VP9_COEF_SIZE) +#define VP9_INTER_MODE_SIZE 24 /* only use 21 (# * 7)*/ +#define VP9_INTERP_START (VP9_INTER_MODE_START + VP9_INTER_MODE_SIZE) +#define VP9_INTERP_SIZE 8 +#define VP9_INTRA_INTER_START (VP9_INTERP_START + VP9_INTERP_SIZE) +#define VP9_INTRA_INTER_SIZE 4 +#define VP9_INTERP_INTRA_INTER_START VP9_INTERP_START +#define VP9_INTERP_INTRA_INTER_SIZE (VP9_INTERP_SIZE + VP9_INTRA_INTER_SIZE) +#define VP9_COMP_INTER_START \ + (VP9_INTERP_INTRA_INTER_START + VP9_INTERP_INTRA_INTER_SIZE) +#define VP9_COMP_INTER_SIZE 5 +#define VP9_COMP_REF_START (VP9_COMP_INTER_START + VP9_COMP_INTER_SIZE) +#define VP9_COMP_REF_SIZE 5 +#define VP9_SINGLE_REF_START (VP9_COMP_REF_START + VP9_COMP_REF_SIZE) +#define VP9_SINGLE_REF_SIZE 10 +#define VP9_REF_MODE_START VP9_COMP_INTER_START +#define VP9_REF_MODE_SIZE \ + (VP9_COMP_INTER_SIZE + VP9_COMP_REF_SIZE + VP9_SINGLE_REF_SIZE) +#define VP9_IF_Y_MODE_START (VP9_REF_MODE_START + VP9_REF_MODE_SIZE) +#define VP9_IF_Y_MODE_SIZE 36 +#define VP9_IF_UV_MODE_START (VP9_IF_Y_MODE_START + VP9_IF_Y_MODE_SIZE) +#define VP9_IF_UV_MODE_SIZE 92 /* only use 90*/ +#define VP9_MV_JOINTS_START (VP9_IF_UV_MODE_START + VP9_IF_UV_MODE_SIZE) +#define VP9_MV_JOINTS_SIZE 3 +#define VP9_MV_SIGN_0_START (VP9_MV_JOINTS_START + VP9_MV_JOINTS_SIZE) +#define VP9_MV_SIGN_0_SIZE 1 +#define VP9_MV_CLASSES_0_START (VP9_MV_SIGN_0_START + VP9_MV_SIGN_0_SIZE) +#define VP9_MV_CLASSES_0_SIZE 10 +#define VP9_MV_CLASS0_0_START \ + (VP9_MV_CLASSES_0_START + VP9_MV_CLASSES_0_SIZE) +#define VP9_MV_CLASS0_0_SIZE 1 +#define VP9_MV_BITS_0_START (VP9_MV_CLASS0_0_START + VP9_MV_CLASS0_0_SIZE) +#define VP9_MV_BITS_0_SIZE 10 +#define VP9_MV_SIGN_1_START (VP9_MV_BITS_0_START + VP9_MV_BITS_0_SIZE) +#define VP9_MV_SIGN_1_SIZE 1 +#define VP9_MV_CLASSES_1_START \ + (VP9_MV_SIGN_1_START + VP9_MV_SIGN_1_SIZE) +#define VP9_MV_CLASSES_1_SIZE 10 +#define VP9_MV_CLASS0_1_START \ + (VP9_MV_CLASSES_1_START + VP9_MV_CLASSES_1_SIZE) +#define VP9_MV_CLASS0_1_SIZE 1 +#define VP9_MV_BITS_1_START \ + (VP9_MV_CLASS0_1_START + VP9_MV_CLASS0_1_SIZE) +#define VP9_MV_BITS_1_SIZE 10 +#define VP9_MV_CLASS0_FP_0_START \ + (VP9_MV_BITS_1_START + VP9_MV_BITS_1_SIZE) +#define VP9_MV_CLASS0_FP_0_SIZE 9 +#define VP9_MV_CLASS0_FP_1_START \ + (VP9_MV_CLASS0_FP_0_START + VP9_MV_CLASS0_FP_0_SIZE) +#define VP9_MV_CLASS0_FP_1_SIZE 9 +#define VP9_MV_CLASS0_HP_0_START \ + (VP9_MV_CLASS0_FP_1_START + VP9_MV_CLASS0_FP_1_SIZE) +#define VP9_MV_CLASS0_HP_0_SIZE 2 +#define VP9_MV_CLASS0_HP_1_START \ + (VP9_MV_CLASS0_HP_0_START + VP9_MV_CLASS0_HP_0_SIZE) +#define VP9_MV_CLASS0_HP_1_SIZE 2 +#define VP9_MV_START VP9_MV_JOINTS_START +#define VP9_MV_SIZE 72 /*only use 69*/ + +#define VP9_TOTAL_SIZE (VP9_MV_START + VP9_MV_SIZE) + +/* VP9 COUNT mem processing defines */ +#define VP9_COEF_COUNT_START 0 +#define VP9_COEF_COUNT_BAND_0_OFFSET 0 +#define VP9_COEF_COUNT_BAND_1_OFFSET \ + (VP9_COEF_COUNT_BAND_0_OFFSET + 3 * 5) +#define VP9_COEF_COUNT_BAND_2_OFFSET \ + (VP9_COEF_COUNT_BAND_1_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_BAND_3_OFFSET \ + (VP9_COEF_COUNT_BAND_2_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_BAND_4_OFFSET \ + (VP9_COEF_COUNT_BAND_3_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_BAND_5_OFFSET \ + (VP9_COEF_COUNT_BAND_4_OFFSET + 6 * 5) +#define VP9_COEF_COUNT_SIZE_ONE_SET 165 /* ((3 + 5 * 6) * 5 */ +#define VP9_COEF_COUNT_4X4_START \ + (VP9_COEF_COUNT_START + 0 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_8X8_START \ + (VP9_COEF_COUNT_START + 4 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_16X16_START \ + (VP9_COEF_COUNT_START + 8 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_32X32_START \ + (VP9_COEF_COUNT_START + 12 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_SIZE_PLANE (2 * VP9_COEF_COUNT_SIZE_ONE_SET) +#define VP9_COEF_COUNT_SIZE (4 * 2 * 2 * VP9_COEF_COUNT_SIZE_ONE_SET) + +#define VP9_INTRA_INTER_COUNT_START \ + (VP9_COEF_COUNT_START + VP9_COEF_COUNT_SIZE) +#define VP9_INTRA_INTER_COUNT_SIZE (4 * 2) +#define VP9_COMP_INTER_COUNT_START \ + (VP9_INTRA_INTER_COUNT_START + VP9_INTRA_INTER_COUNT_SIZE) +#define VP9_COMP_INTER_COUNT_SIZE (5 * 2) +#define VP9_COMP_REF_COUNT_START \ + (VP9_COMP_INTER_COUNT_START + VP9_COMP_INTER_COUNT_SIZE) +#define VP9_COMP_REF_COUNT_SIZE (5 * 2) +#define VP9_SINGLE_REF_COUNT_START \ + (VP9_COMP_REF_COUNT_START + VP9_COMP_REF_COUNT_SIZE) +#define VP9_SINGLE_REF_COUNT_SIZE (10 * 2) +#define VP9_TX_MODE_COUNT_START \ + (VP9_SINGLE_REF_COUNT_START + VP9_SINGLE_REF_COUNT_SIZE) +#define VP9_TX_MODE_COUNT_SIZE (12 * 2) +#define VP9_SKIP_COUNT_START \ + (VP9_TX_MODE_COUNT_START + VP9_TX_MODE_COUNT_SIZE) +#define VP9_SKIP_COUNT_SIZE (3 * 2) +#define VP9_MV_SIGN_0_COUNT_START \ + (VP9_SKIP_COUNT_START + VP9_SKIP_COUNT_SIZE) +#define VP9_MV_SIGN_0_COUNT_SIZE (1 * 2) +#define VP9_MV_SIGN_1_COUNT_START \ + (VP9_MV_SIGN_0_COUNT_START + VP9_MV_SIGN_0_COUNT_SIZE) +#define VP9_MV_SIGN_1_COUNT_SIZE (1 * 2) +#define VP9_MV_BITS_0_COUNT_START \ + (VP9_MV_SIGN_1_COUNT_START + VP9_MV_SIGN_1_COUNT_SIZE) +#define VP9_MV_BITS_0_COUNT_SIZE (10 * 2) +#define VP9_MV_BITS_1_COUNT_START \ + (VP9_MV_BITS_0_COUNT_START + VP9_MV_BITS_0_COUNT_SIZE) +#define VP9_MV_BITS_1_COUNT_SIZE (10 * 2) +#define VP9_MV_CLASS0_HP_0_COUNT_START \ + (VP9_MV_BITS_1_COUNT_START + VP9_MV_BITS_1_COUNT_SIZE) +#define VP9_MV_CLASS0_HP_0_COUNT_SIZE (2 * 2) +#define VP9_MV_CLASS0_HP_1_COUNT_START \ + (VP9_MV_CLASS0_HP_0_COUNT_START + VP9_MV_CLASS0_HP_0_COUNT_SIZE) +#define VP9_MV_CLASS0_HP_1_COUNT_SIZE (2 * 2) + +/* Start merge_tree */ +#define VP9_INTER_MODE_COUNT_START \ + (VP9_MV_CLASS0_HP_1_COUNT_START + VP9_MV_CLASS0_HP_1_COUNT_SIZE) +#define VP9_INTER_MODE_COUNT_SIZE (7 * 4) +#define VP9_IF_Y_MODE_COUNT_START \ + (VP9_INTER_MODE_COUNT_START + VP9_INTER_MODE_COUNT_SIZE) +#define VP9_IF_Y_MODE_COUNT_SIZE (10 * 4) +#define VP9_IF_UV_MODE_COUNT_START \ + (VP9_IF_Y_MODE_COUNT_START + VP9_IF_Y_MODE_COUNT_SIZE) +#define VP9_IF_UV_MODE_COUNT_SIZE (10 * 10) +#define VP9_PARTITION_P_COUNT_START \ + (VP9_IF_UV_MODE_COUNT_START + VP9_IF_UV_MODE_COUNT_SIZE) +#define VP9_PARTITION_P_COUNT_SIZE (4 * 4 * 4) +#define VP9_INTERP_COUNT_START \ + (VP9_PARTITION_P_COUNT_START + VP9_PARTITION_P_COUNT_SIZE) +#define VP9_INTERP_COUNT_SIZE (4 * 3) +#define VP9_MV_JOINTS_COUNT_START \ + (VP9_INTERP_COUNT_START + VP9_INTERP_COUNT_SIZE) +#define VP9_MV_JOINTS_COUNT_SIZE (1 * 4) +#define VP9_MV_CLASSES_0_COUNT_START \ + (VP9_MV_JOINTS_COUNT_START + VP9_MV_JOINTS_COUNT_SIZE) +#define VP9_MV_CLASSES_0_COUNT_SIZE (1 * 11) +#define VP9_MV_CLASS0_0_COUNT_START \ + (VP9_MV_CLASSES_0_COUNT_START + VP9_MV_CLASSES_0_COUNT_SIZE) +#define VP9_MV_CLASS0_0_COUNT_SIZE (1 * 2) +#define VP9_MV_CLASSES_1_COUNT_START \ + (VP9_MV_CLASS0_0_COUNT_START + VP9_MV_CLASS0_0_COUNT_SIZE) +#define VP9_MV_CLASSES_1_COUNT_SIZE (1 * 11) +#define VP9_MV_CLASS0_1_COUNT_START \ + (VP9_MV_CLASSES_1_COUNT_START + VP9_MV_CLASSES_1_COUNT_SIZE) +#define VP9_MV_CLASS0_1_COUNT_SIZE (1 * 2) +#define VP9_MV_CLASS0_FP_0_COUNT_START \ + (VP9_MV_CLASS0_1_COUNT_START + VP9_MV_CLASS0_1_COUNT_SIZE) +#define VP9_MV_CLASS0_FP_0_COUNT_SIZE (3 * 4) +#define VP9_MV_CLASS0_FP_1_COUNT_START \ + (VP9_MV_CLASS0_FP_0_COUNT_START + VP9_MV_CLASS0_FP_0_COUNT_SIZE) +#define VP9_MV_CLASS0_FP_1_COUNT_SIZE (3 * 4) + +#define DC_PRED 0 /* Average of above and left pixels */ +#define V_PRED 1 /* Vertical */ +#define H_PRED 2 /* Horizontal */ +#define D45_PRED 3 /* Directional 45 deg = round(arctan(1/1) * 180/pi) */ +#define D135_PRED 4 /* Directional 135 deg = 180 - 45 */ +#define D117_PRED 5 /* Directional 117 deg = 180 - 63 */ +#define D153_PRED 6 /* Directional 153 deg = 180 - 27 */ +#define D207_PRED 7 /* Directional 207 deg = 180 + 27 */ +#define D63_PRED 8 /* Directional 63 deg = round(arctan(2/1) * 180/pi) */ +#define TM_PRED 9 /* True-motion */ + +/* Use a static inline to avoid possible side effect from num being reused */ +static inline int round_power_of_two(int value, int num) +{ + return (value + (1 << (num - 1))) >> num; +} + +#define MODE_MV_COUNT_SAT 20 +static const int count_to_update_factor[MODE_MV_COUNT_SAT + 1] = { + 0, 6, 12, 19, 25, 32, 38, 44, 51, 57, 64, + 70, 76, 83, 89, 96, 102, 108, 115, 121, 128 +}; + +union rpm_param { + struct { + u16 data[RPM_BUF_SIZE]; + } l; + struct { + u16 profile; + u16 show_existing_frame; + u16 frame_to_show_idx; + u16 frame_type; /*1 bit*/ + u16 show_frame; /*1 bit*/ + u16 error_resilient_mode; /*1 bit*/ + u16 intra_only; /*1 bit*/ + u16 display_size_present; /*1 bit*/ + u16 reset_frame_context; + u16 refresh_frame_flags; + u16 width; + u16 height; + u16 display_width; + u16 display_height; + u16 ref_info; + u16 same_frame_size; + u16 mode_ref_delta_enabled; + u16 ref_deltas[4]; + u16 mode_deltas[2]; + u16 filter_level; + u16 sharpness_level; + u16 bit_depth; + u16 seg_quant_info[8]; + u16 seg_enabled; + u16 seg_abs_delta; + /* bit 15: feature enabled; bit 8, sign; bit[5:0], data */ + u16 seg_lf_info[8]; + } p; +}; + +enum SEG_LVL_FEATURES { + SEG_LVL_ALT_Q = 0, /* Use alternate Quantizer */ + SEG_LVL_ALT_LF = 1, /* Use alternate loop filter value */ + SEG_LVL_REF_FRAME = 2, /* Optional Segment reference frame */ + SEG_LVL_SKIP = 3, /* Optional Segment (0,0) + skip mode */ + SEG_LVL_MAX = 4 /* Number of features supported */ +}; + +struct segmentation { + u8 enabled; + u8 update_map; + u8 update_data; + u8 abs_delta; + u8 temporal_update; + s16 feature_data[MAX_SEGMENTS][SEG_LVL_MAX]; + unsigned int feature_mask[MAX_SEGMENTS]; +}; + +struct loop_filter_thresh { + u8 mblim; + u8 lim; + u8 hev_thr; +}; + +struct loop_filter_info_n { + struct loop_filter_thresh lfthr[MAX_LOOP_FILTER + 1]; + u8 lvl[MAX_SEGMENTS][MAX_REF_FRAMES][MAX_MODE_LF_DELTAS]; +}; + +struct loopfilter { + int filter_level; + + int sharpness_level; + int last_sharpness_level; + + u8 mode_ref_delta_enabled; + u8 mode_ref_delta_update; + + /*0 = Intra, Last, GF, ARF*/ + signed char ref_deltas[MAX_REF_LF_DELTAS]; + signed char last_ref_deltas[MAX_REF_LF_DELTAS]; + + /*0 = ZERO_MV, MV*/ + signed char mode_deltas[MAX_MODE_LF_DELTAS]; + signed char last_mode_deltas[MAX_MODE_LF_DELTAS]; +}; + +struct vp9_frame { + struct list_head list; + struct vb2_v4l2_buffer *vbuf; + int index; + int intra_only; + int show; + int type; + int done; + unsigned int width; + unsigned int height; +}; + +struct codec_vp9 { + /* VP9 context lock */ + struct mutex lock; + + /* Common part with the HEVC decoder */ + struct codec_hevc_common common; + + /* Buffer for the VP9 Workspace */ + void *workspace_vaddr; + dma_addr_t workspace_paddr; + + /* Contains many information parsed from the bitstream */ + union rpm_param rpm_param; + + /* Whether we detected the bitstream as 10-bit */ + int is_10bit; + + /* Coded resolution reported by the hardware */ + u32 width, height; + + /* All ref frames used by the HW at a given time */ + struct list_head ref_frames_list; + u32 frames_num; + + /* In case of downsampling (decoding with FBC but outputting in NV12M), + * we need to allocate additional buffers for FBC. + */ + void *fbc_buffer_vaddr[MAX_REF_PIC_NUM]; + dma_addr_t fbc_buffer_paddr[MAX_REF_PIC_NUM]; + + int ref_frame_map[REF_FRAMES]; + int next_ref_frame_map[REF_FRAMES]; + struct vp9_frame *frame_refs[REFS_PER_FRAME]; + + u32 lcu_total; + + /* loop filter */ + int default_filt_lvl; + struct loop_filter_info_n lfi; + struct loopfilter lf; + struct segmentation seg_4lf; + + struct vp9_frame *cur_frame; + struct vp9_frame *prev_frame; +}; + +static int div_r32(s64 m, int n) +{ + s64 qu = div_s64(m, n); + + return (int)qu; +} + +static int clip_prob(int p) +{ + return clamp_val(p, 1, 255); +} + +static int segfeature_active(struct segmentation *seg, int segment_id, + enum SEG_LVL_FEATURES feature_id) +{ + return seg->enabled && + (seg->feature_mask[segment_id] & (1 << feature_id)); +} + +static int get_segdata(struct segmentation *seg, int segment_id, + enum SEG_LVL_FEATURES feature_id) +{ + return seg->feature_data[segment_id][feature_id]; +} + +static void vp9_update_sharpness(struct loop_filter_info_n *lfi, + int sharpness_lvl) +{ + int lvl; + + /* For each possible value for the loop filter fill out limits*/ + for (lvl = 0; lvl <= MAX_LOOP_FILTER; lvl++) { + /* Set loop filter parameters that control sharpness.*/ + int block_inside_limit = lvl >> ((sharpness_lvl > 0) + + (sharpness_lvl > 4)); + + if (sharpness_lvl > 0) { + if (block_inside_limit > (9 - sharpness_lvl)) + block_inside_limit = (9 - sharpness_lvl); + } + + if (block_inside_limit < 1) + block_inside_limit = 1; + + lfi->lfthr[lvl].lim = (u8)block_inside_limit; + lfi->lfthr[lvl].mblim = (u8)(2 * (lvl + 2) + + block_inside_limit); + } +} + +/* Instantiate this function once when decode is started */ +static void +vp9_loop_filter_init(struct amvdec_core *core, struct codec_vp9 *vp9) +{ + struct loop_filter_info_n *lfi = &vp9->lfi; + struct loopfilter *lf = &vp9->lf; + struct segmentation *seg_4lf = &vp9->seg_4lf; + int i; + + memset(lfi, 0, sizeof(struct loop_filter_info_n)); + memset(lf, 0, sizeof(struct loopfilter)); + memset(seg_4lf, 0, sizeof(struct segmentation)); + lf->sharpness_level = 0; + vp9_update_sharpness(lfi, lf->sharpness_level); + lf->last_sharpness_level = lf->sharpness_level; + + for (i = 0; i < 32; i++) { + unsigned int thr; + + thr = ((lfi->lfthr[i * 2 + 1].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2 + 1].mblim & 0xff); + thr = (thr << 16) | ((lfi->lfthr[i * 2].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2].mblim & 0xff); + + amvdec_write_dos(core, HEVC_DBLK_CFG9, thr); + } + + if (core->platform->revision >= VDEC_REVISION_SM1) + amvdec_write_dos(core, HEVC_DBLK_CFGB, + (0x3 << 14) | /* dw fifo thres r and b */ + (0x3 << 12) | /* dw fifo thres r or b */ + (0x3 << 10) | /* dw fifo thres not r/b */ + BIT(0)); /* VP9 video format */ + else if (core->platform->revision >= VDEC_REVISION_G12A) + /* VP9 video format */ + amvdec_write_dos(core, HEVC_DBLK_CFGB, (0x54 << 8) | BIT(0)); + else + amvdec_write_dos(core, HEVC_DBLK_CFGB, 0x40400001); +} + +static void +vp9_loop_filter_frame_init(struct amvdec_core *core, struct segmentation *seg, + struct loop_filter_info_n *lfi, + struct loopfilter *lf, int default_filt_lvl) +{ + int i; + int seg_id; + + /* + * n_shift is the multiplier for lf_deltas + * the multiplier is: + * - 1 for when filter_lvl is between 0 and 31 + * - 2 when filter_lvl is between 32 and 63 + */ + const int scale = 1 << (default_filt_lvl >> 5); + + /* update limits if sharpness has changed */ + if (lf->last_sharpness_level != lf->sharpness_level) { + vp9_update_sharpness(lfi, lf->sharpness_level); + lf->last_sharpness_level = lf->sharpness_level; + + /* Write to register */ + for (i = 0; i < 32; i++) { + unsigned int thr; + + thr = ((lfi->lfthr[i * 2 + 1].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2 + 1].mblim & 0xff); + thr = (thr << 16) | + ((lfi->lfthr[i * 2].lim & 0x3f) << 8) | + (lfi->lfthr[i * 2].mblim & 0xff); + + amvdec_write_dos(core, HEVC_DBLK_CFG9, thr); + } + } + + for (seg_id = 0; seg_id < MAX_SEGMENTS; seg_id++) { + int lvl_seg = default_filt_lvl; + + if (segfeature_active(seg, seg_id, SEG_LVL_ALT_LF)) { + const int data = get_segdata(seg, seg_id, + SEG_LVL_ALT_LF); + lvl_seg = clamp_t(int, + seg->abs_delta == SEGMENT_ABSDATA ? + data : default_filt_lvl + data, + 0, MAX_LOOP_FILTER); + } + + if (!lf->mode_ref_delta_enabled) { + /* + * We could get rid of this if we assume that deltas + * are set to zero when not in use. + * encoder always uses deltas + */ + memset(lfi->lvl[seg_id], lvl_seg, + sizeof(lfi->lvl[seg_id])); + } else { + int ref, mode; + const int intra_lvl = + lvl_seg + lf->ref_deltas[INTRA_FRAME] * scale; + lfi->lvl[seg_id][INTRA_FRAME][0] = + clamp_val(intra_lvl, 0, MAX_LOOP_FILTER); + + for (ref = LAST_FRAME; ref < MAX_REF_FRAMES; ++ref) { + for (mode = 0; mode < MAX_MODE_LF_DELTAS; + ++mode) { + const int inter_lvl = + lvl_seg + + lf->ref_deltas[ref] * scale + + lf->mode_deltas[mode] * scale; + lfi->lvl[seg_id][ref][mode] = + clamp_val(inter_lvl, 0, + MAX_LOOP_FILTER); + } + } + } + } + + for (i = 0; i < 16; i++) { + unsigned int level; + + level = ((lfi->lvl[i >> 1][3][i & 1] & 0x3f) << 24) | + ((lfi->lvl[i >> 1][2][i & 1] & 0x3f) << 16) | + ((lfi->lvl[i >> 1][1][i & 1] & 0x3f) << 8) | + (lfi->lvl[i >> 1][0][i & 1] & 0x3f); + if (!default_filt_lvl) + level = 0; + + amvdec_write_dos(core, HEVC_DBLK_CFGA, level); + } +} + +static void codec_vp9_flush_output(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + struct vp9_frame *tmp, *n; + + mutex_lock(&vp9->lock); + list_for_each_entry_safe(tmp, n, &vp9->ref_frames_list, list) { + if (!tmp->done) { + if (tmp->show) + amvdec_dst_buf_done(sess, tmp->vbuf, + V4L2_FIELD_NONE); + else + v4l2_m2m_buf_queue(sess->m2m_ctx, tmp->vbuf); + + vp9->frames_num--; + } + + list_del(&tmp->list); + kfree(tmp); + } + mutex_unlock(&vp9->lock); +} + +static u32 codec_vp9_num_pending_bufs(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + + if (!vp9) + return 0; + + return vp9->frames_num; +} + +static int codec_vp9_alloc_workspace(struct amvdec_core *core, + struct codec_vp9 *vp9) +{ + /* Allocate some memory for the VP9 decoder's state */ + vp9->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE, + &vp9->workspace_paddr, + GFP_KERNEL); + if (!vp9->workspace_vaddr) { + dev_err(core->dev, "Failed to allocate VP9 Workspace\n"); + return -ENOMEM; + } + + return 0; +} + +static void codec_vp9_setup_workspace(struct amvdec_session *sess, + struct codec_vp9 *vp9) +{ + struct amvdec_core *core = sess->core; + u32 revision = core->platform->revision; + dma_addr_t wkaddr = vp9->workspace_paddr; + + amvdec_write_dos(core, HEVCD_IPP_LINEBUFF_BASE, wkaddr + IPP_OFFSET); + amvdec_write_dos(core, VP9_RPM_BUFFER, wkaddr + RPM_OFFSET); + amvdec_write_dos(core, VP9_SHORT_TERM_RPS, wkaddr + SH_TM_RPS_OFFSET); + amvdec_write_dos(core, VP9_PPS_BUFFER, wkaddr + PPS_OFFSET); + amvdec_write_dos(core, VP9_SAO_UP, wkaddr + SAO_UP_OFFSET); + + amvdec_write_dos(core, VP9_STREAM_SWAP_BUFFER, + wkaddr + SWAP_BUF_OFFSET); + amvdec_write_dos(core, VP9_STREAM_SWAP_BUFFER2, + wkaddr + SWAP_BUF2_OFFSET); + amvdec_write_dos(core, VP9_SCALELUT, wkaddr + SCALELUT_OFFSET); + + if (core->platform->revision >= VDEC_REVISION_G12A) + amvdec_write_dos(core, HEVC_DBLK_CFGE, + wkaddr + DBLK_PARA_OFFSET); + + amvdec_write_dos(core, HEVC_DBLK_CFG4, wkaddr + DBLK_PARA_OFFSET); + amvdec_write_dos(core, HEVC_DBLK_CFG5, wkaddr + DBLK_DATA_OFFSET); + amvdec_write_dos(core, VP9_SEG_MAP_BUFFER, wkaddr + SEG_MAP_OFFSET); + amvdec_write_dos(core, VP9_PROB_SWAP_BUFFER, wkaddr + PROB_OFFSET); + amvdec_write_dos(core, VP9_COUNT_SWAP_BUFFER, wkaddr + COUNT_OFFSET); + amvdec_write_dos(core, LMEM_DUMP_ADR, wkaddr + LMEM_OFFSET); + + if (codec_hevc_use_mmu(revision, sess->pixfmt_cap, vp9->is_10bit)) { + amvdec_write_dos(core, HEVC_SAO_MMU_VH0_ADDR, + wkaddr + MMU_VBH_OFFSET); + amvdec_write_dos(core, HEVC_SAO_MMU_VH1_ADDR, + wkaddr + MMU_VBH_OFFSET + (MMU_VBH_SIZE / 2)); + + if (revision >= VDEC_REVISION_G12A) + amvdec_write_dos(core, HEVC_ASSIST_MMU_MAP_ADDR, + vp9->common.mmu_map_paddr); + else + amvdec_write_dos(core, VP9_MMU_MAP_BUFFER, + vp9->common.mmu_map_paddr); + } +} + +static int codec_vp9_start(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9; + u32 val; + int i; + int ret; + + vp9 = kzalloc(sizeof(*vp9), GFP_KERNEL); + if (!vp9) + return -ENOMEM; + + ret = codec_vp9_alloc_workspace(core, vp9); + if (ret) + goto free_vp9; + + codec_vp9_setup_workspace(sess, vp9); + amvdec_write_dos_bits(core, HEVC_STREAM_CONTROL, BIT(0)); + /* stream_fifo_hole */ + if (core->platform->revision >= VDEC_REVISION_G12A) + amvdec_write_dos_bits(core, HEVC_STREAM_FIFO_CTL, BIT(29)); + + val = amvdec_read_dos(core, HEVC_PARSER_INT_CONTROL) & 0x7fffffff; + val |= (3 << 29) | BIT(24) | BIT(22) | BIT(7) | BIT(4) | BIT(0); + amvdec_write_dos(core, HEVC_PARSER_INT_CONTROL, val); + amvdec_write_dos_bits(core, HEVC_SHIFT_STATUS, BIT(0)); + amvdec_write_dos(core, HEVC_SHIFT_CONTROL, BIT(10) | BIT(9) | + (3 << 6) | BIT(5) | BIT(2) | BIT(1) | BIT(0)); + amvdec_write_dos(core, HEVC_CABAC_CONTROL, BIT(0)); + amvdec_write_dos(core, HEVC_PARSER_CORE_CONTROL, BIT(0)); + amvdec_write_dos(core, HEVC_SHIFT_STARTCODE, 0x00000001); + + amvdec_write_dos(core, VP9_DEC_STATUS_REG, 0); + + amvdec_write_dos(core, HEVC_PARSER_CMD_WRITE, BIT(16)); + for (i = 0; i < ARRAY_SIZE(vdec_hevc_parser_cmd); ++i) + amvdec_write_dos(core, HEVC_PARSER_CMD_WRITE, + vdec_hevc_parser_cmd[i]); + + amvdec_write_dos(core, HEVC_PARSER_CMD_SKIP_0, PARSER_CMD_SKIP_CFG_0); + amvdec_write_dos(core, HEVC_PARSER_CMD_SKIP_1, PARSER_CMD_SKIP_CFG_1); + amvdec_write_dos(core, HEVC_PARSER_CMD_SKIP_2, PARSER_CMD_SKIP_CFG_2); + amvdec_write_dos(core, HEVC_PARSER_IF_CONTROL, + BIT(5) | BIT(2) | BIT(0)); + + amvdec_write_dos(core, HEVCD_IPP_TOP_CNTL, BIT(0)); + amvdec_write_dos(core, HEVCD_IPP_TOP_CNTL, BIT(1)); + + amvdec_write_dos(core, VP9_WAIT_FLAG, 1); + + /* clear mailbox interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_CLR_REG, 1); + /* enable mailbox interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_MASK, 1); + /* disable PSCALE for hardware sharing */ + amvdec_write_dos(core, HEVC_PSCALE_CTRL, 0); + /* Let the uCode do all the parsing */ + amvdec_write_dos(core, NAL_SEARCH_CTL, 0x8); + + amvdec_write_dos(core, DECODE_STOP_POS, 0); + amvdec_write_dos(core, VP9_DECODE_MODE, DECODE_MODE_SINGLE); + + pr_debug("decode_count: %u; decode_size: %u\n", + amvdec_read_dos(core, HEVC_DECODE_COUNT), + amvdec_read_dos(core, HEVC_DECODE_SIZE)); + + vp9_loop_filter_init(core, vp9); + + INIT_LIST_HEAD(&vp9->ref_frames_list); + mutex_init(&vp9->lock); + memset(&vp9->ref_frame_map, -1, sizeof(vp9->ref_frame_map)); + memset(&vp9->next_ref_frame_map, -1, sizeof(vp9->next_ref_frame_map)); + for (i = 0; i < REFS_PER_FRAME; ++i) + vp9->frame_refs[i] = NULL; + sess->priv = vp9; + + return 0; + +free_vp9: + kfree(vp9); + return ret; +} + +static int codec_vp9_stop(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + + mutex_lock(&vp9->lock); + if (vp9->workspace_vaddr) + dma_free_coherent(core->dev, SIZE_WORKSPACE, + vp9->workspace_vaddr, + vp9->workspace_paddr); + + codec_hevc_free_fbc_buffers(sess, &vp9->common); + mutex_unlock(&vp9->lock); + + return 0; +} + +/* + * Program LAST & GOLDEN frames into the motion compensation reference cache + * controller + */ +static void codec_vp9_set_mcrcc(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + u32 val; + + /* Reset mcrcc */ + amvdec_write_dos(core, HEVCD_MCRCC_CTL1, 0x2); + /* Disable on I-frame */ + if (vp9->cur_frame->type == KEY_FRAME || vp9->cur_frame->intra_only) { + amvdec_write_dos(core, HEVCD_MCRCC_CTL1, 0x0); + return; + } + + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, BIT(1)); + val = amvdec_read_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR) & 0xffff; + val |= (val << 16); + amvdec_write_dos(core, HEVCD_MCRCC_CTL2, val); + val = amvdec_read_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR) & 0xffff; + val |= (val << 16); + amvdec_write_dos(core, HEVCD_MCRCC_CTL3, val); + + /* Enable mcrcc progressive-mode */ + amvdec_write_dos(core, HEVCD_MCRCC_CTL1, 0xff0); +} + +static void codec_vp9_set_sao(struct amvdec_session *sess, + struct vb2_buffer *vb) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + + dma_addr_t buf_y_paddr; + dma_addr_t buf_u_v_paddr; + u32 val; + + if (codec_hevc_use_downsample(sess->pixfmt_cap, vp9->is_10bit)) + buf_y_paddr = + vp9->common.fbc_buffer_paddr[vb->index]; + else + buf_y_paddr = + vb2_dma_contig_plane_dma_addr(vb, 0); + + if (codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) { + val = amvdec_read_dos(core, HEVC_SAO_CTRL5) & ~0xff0200; + amvdec_write_dos(core, HEVC_SAO_CTRL5, val); + amvdec_write_dos(core, HEVC_CM_BODY_START_ADDR, buf_y_paddr); + } + + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) { + buf_y_paddr = + vb2_dma_contig_plane_dma_addr(vb, 0); + buf_u_v_paddr = + vb2_dma_contig_plane_dma_addr(vb, 1); + amvdec_write_dos(core, HEVC_SAO_Y_START_ADDR, buf_y_paddr); + amvdec_write_dos(core, HEVC_SAO_C_START_ADDR, buf_u_v_paddr); + amvdec_write_dos(core, HEVC_SAO_Y_WPTR, buf_y_paddr); + amvdec_write_dos(core, HEVC_SAO_C_WPTR, buf_u_v_paddr); + } + + if (codec_hevc_use_mmu(core->platform->revision, sess->pixfmt_cap, + vp9->is_10bit)) { + amvdec_write_dos(core, HEVC_CM_HEADER_START_ADDR, + vp9->common.mmu_header_paddr[vb->index]); + /* use HEVC_CM_HEADER_START_ADDR */ + amvdec_write_dos_bits(core, HEVC_SAO_CTRL5, BIT(10)); + } + + amvdec_write_dos(core, HEVC_SAO_Y_LENGTH, + amvdec_get_output_size(sess)); + amvdec_write_dos(core, HEVC_SAO_C_LENGTH, + (amvdec_get_output_size(sess) / 2)); + + if (core->platform->revision >= VDEC_REVISION_G12A) { + amvdec_clear_dos_bits(core, HEVC_DBLK_CFGB, + BIT(4) | BIT(5) | BIT(8) | BIT(9)); + /* enable first, compressed write */ + if (codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) + amvdec_write_dos_bits(core, HEVC_DBLK_CFGB, BIT(8)); + + /* enable second, uncompressed write */ + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) + amvdec_write_dos_bits(core, HEVC_DBLK_CFGB, BIT(9)); + + /* dblk pipeline mode=1 for performance */ + if (sess->width >= 1280) + amvdec_write_dos_bits(core, HEVC_DBLK_CFGB, BIT(4)); + + pr_debug("HEVC_DBLK_CFGB: %08X\n", + amvdec_read_dos(core, HEVC_DBLK_CFGB)); + } + + val = amvdec_read_dos(core, HEVC_SAO_CTRL1) & ~0x3ff0; + val |= 0xff0; /* Set endianness for 2-bytes swaps (nv12) */ + if (core->platform->revision < VDEC_REVISION_G12A) { + val &= ~0x3; + if (!codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) + val |= BIT(0); /* disable cm compression */ + /* TOFIX: Handle Amlogic Framebuffer compression */ + } + + amvdec_write_dos(core, HEVC_SAO_CTRL1, val); + pr_debug("HEVC_SAO_CTRL1: %08X\n", val); + + /* no downscale for NV12 */ + val = amvdec_read_dos(core, HEVC_SAO_CTRL5) & ~0xff0000; + amvdec_write_dos(core, HEVC_SAO_CTRL5, val); + + val = amvdec_read_dos(core, HEVCD_IPP_AXIIF_CONFIG) & ~0x30; + val |= 0xf; + val &= ~BIT(12); /* NV12 */ + amvdec_write_dos(core, HEVCD_IPP_AXIIF_CONFIG, val); +} + +static dma_addr_t codec_vp9_get_frame_mv_paddr(struct codec_vp9 *vp9, + struct vp9_frame *frame) +{ + return vp9->workspace_paddr + MPRED_MV_OFFSET + + (frame->index * MPRED_MV_BUF_SIZE); +} + +static void codec_vp9_set_mpred_mv(struct amvdec_core *core, + struct codec_vp9 *vp9) +{ + int mpred_mv_rd_end_addr; + int use_prev_frame_mvs = vp9->prev_frame->width == + vp9->cur_frame->width && + vp9->prev_frame->height == + vp9->cur_frame->height && + !vp9->prev_frame->intra_only && + vp9->prev_frame->show && + vp9->prev_frame->type != KEY_FRAME; + + amvdec_write_dos(core, HEVC_MPRED_CTRL3, 0x24122412); + amvdec_write_dos(core, HEVC_MPRED_ABV_START_ADDR, + vp9->workspace_paddr + MPRED_ABV_OFFSET); + + amvdec_clear_dos_bits(core, HEVC_MPRED_CTRL4, BIT(6)); + if (use_prev_frame_mvs) + amvdec_write_dos_bits(core, HEVC_MPRED_CTRL4, BIT(6)); + + amvdec_write_dos(core, HEVC_MPRED_MV_WR_START_ADDR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->cur_frame)); + amvdec_write_dos(core, HEVC_MPRED_MV_WPTR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->cur_frame)); + + amvdec_write_dos(core, HEVC_MPRED_MV_RD_START_ADDR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->prev_frame)); + amvdec_write_dos(core, HEVC_MPRED_MV_RPTR, + codec_vp9_get_frame_mv_paddr(vp9, vp9->prev_frame)); + + mpred_mv_rd_end_addr = + codec_vp9_get_frame_mv_paddr(vp9, vp9->prev_frame) + + (vp9->lcu_total * MV_MEM_UNIT); + amvdec_write_dos(core, HEVC_MPRED_MV_RD_END_ADDR, mpred_mv_rd_end_addr); +} + +static void codec_vp9_update_next_ref(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + u32 buf_idx = vp9->cur_frame->index; + int ref_index = 0; + int refresh_frame_flags; + int mask; + + refresh_frame_flags = vp9->cur_frame->type == KEY_FRAME ? + 0xff : param->p.refresh_frame_flags; + + for (mask = refresh_frame_flags; mask; mask >>= 1) { + pr_debug("mask=%08X; ref_index=%d\n", mask, ref_index); + if (mask & 1) + vp9->next_ref_frame_map[ref_index] = buf_idx; + else + vp9->next_ref_frame_map[ref_index] = + vp9->ref_frame_map[ref_index]; + + ++ref_index; + } + + for (; ref_index < REF_FRAMES; ++ref_index) + vp9->next_ref_frame_map[ref_index] = + vp9->ref_frame_map[ref_index]; +} + +static void codec_vp9_save_refs(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int i; + + for (i = 0; i < REFS_PER_FRAME; ++i) { + const int ref = (param->p.ref_info >> + (((REFS_PER_FRAME - i - 1) * 4) + 1)) & 0x7; + + if (vp9->ref_frame_map[ref] < 0) + continue; + + pr_warn("%s: FIXME, would need to save ref %d\n", + __func__, vp9->ref_frame_map[ref]); + } +} + +static void codec_vp9_update_ref(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int ref_index = 0; + int mask; + int refresh_frame_flags; + + if (!vp9->cur_frame) + return; + + refresh_frame_flags = vp9->cur_frame->type == KEY_FRAME ? + 0xff : param->p.refresh_frame_flags; + + for (mask = refresh_frame_flags; mask; mask >>= 1) { + vp9->ref_frame_map[ref_index] = + vp9->next_ref_frame_map[ref_index]; + ++ref_index; + } + + if (param->p.show_existing_frame) + return; + + for (; ref_index < REF_FRAMES; ++ref_index) + vp9->ref_frame_map[ref_index] = + vp9->next_ref_frame_map[ref_index]; +} + +static struct vp9_frame *codec_vp9_get_frame_by_idx(struct codec_vp9 *vp9, + int idx) +{ + struct vp9_frame *frame; + + list_for_each_entry(frame, &vp9->ref_frames_list, list) { + if (frame->index == idx) + return frame; + } + + return NULL; +} + +static void codec_vp9_sync_ref(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int i; + + for (i = 0; i < REFS_PER_FRAME; ++i) { + const int ref = (param->p.ref_info >> + (((REFS_PER_FRAME - i - 1) * 4) + 1)) & 0x7; + const int idx = vp9->ref_frame_map[ref]; + + vp9->frame_refs[i] = codec_vp9_get_frame_by_idx(vp9, idx); + if (!vp9->frame_refs[i]) + pr_warn("%s: couldn't find VP9 ref %d\n", __func__, + idx); + } +} + +static void codec_vp9_set_refs(struct amvdec_session *sess, + struct codec_vp9 *vp9) +{ + struct amvdec_core *core = sess->core; + int i; + + for (i = 0; i < REFS_PER_FRAME; ++i) { + struct vp9_frame *frame = vp9->frame_refs[i]; + int id_y; + int id_u_v; + + if (!frame) + continue; + + if (codec_hevc_use_fbc(sess->pixfmt_cap, vp9->is_10bit)) { + id_y = frame->index; + id_u_v = id_y; + } else { + id_y = frame->index * 2; + id_u_v = id_y + 1; + } + + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_DATA_ADDR, + (id_u_v << 16) | (id_u_v << 8) | id_y); + } +} + +static void codec_vp9_set_mc(struct amvdec_session *sess, + struct codec_vp9 *vp9) +{ + struct amvdec_core *core = sess->core; + u32 scale = 0; + u32 sz; + int i; + + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, 1); + codec_vp9_set_refs(sess, vp9); + amvdec_write_dos(core, HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR, + (16 << 8) | 1); + codec_vp9_set_refs(sess, vp9); + + amvdec_write_dos(core, VP9D_MPP_REFINFO_TBL_ACCCONFIG, BIT(2)); + for (i = 0; i < REFS_PER_FRAME; ++i) { + if (!vp9->frame_refs[i]) + continue; + + if (vp9->frame_refs[i]->width != vp9->width || + vp9->frame_refs[i]->height != vp9->height) + scale = 1; + + sz = amvdec_am21c_body_size(vp9->frame_refs[i]->width, + vp9->frame_refs[i]->height); + + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + vp9->frame_refs[i]->width); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + vp9->frame_refs[i]->height); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + (vp9->frame_refs[i]->width << 14) / + vp9->width); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, + (vp9->frame_refs[i]->height << 14) / + vp9->height); + amvdec_write_dos(core, VP9D_MPP_REFINFO_DATA, sz >> 5); + } + + amvdec_write_dos(core, VP9D_MPP_REF_SCALE_ENBL, scale); +} + +static struct vp9_frame *codec_vp9_get_new_frame(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + union rpm_param *param = &vp9->rpm_param; + struct vb2_v4l2_buffer *vbuf; + struct vp9_frame *new_frame; + + new_frame = kzalloc(sizeof(*new_frame), GFP_KERNEL); + if (!new_frame) + return NULL; + + vbuf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx); + if (!vbuf) { + dev_err(sess->core->dev, "No dst buffer available\n"); + kfree(new_frame); + return NULL; + } + + while (codec_vp9_get_frame_by_idx(vp9, vbuf->vb2_buf.index)) { + struct vb2_v4l2_buffer *old_vbuf = vbuf; + + vbuf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx); + v4l2_m2m_buf_queue(sess->m2m_ctx, old_vbuf); + if (!vbuf) { + dev_err(sess->core->dev, "No dst buffer available\n"); + kfree(new_frame); + return NULL; + } + } + + new_frame->vbuf = vbuf; + new_frame->index = vbuf->vb2_buf.index; + new_frame->intra_only = param->p.intra_only; + new_frame->show = param->p.show_frame; + new_frame->type = param->p.frame_type; + new_frame->width = vp9->width; + new_frame->height = vp9->height; + list_add_tail(&new_frame->list, &vp9->ref_frames_list); + vp9->frames_num++; + + return new_frame; +} + +static void codec_vp9_show_existing_frame(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + + if (!param->p.show_existing_frame) + return; + + pr_debug("showing frame %u\n", param->p.frame_to_show_idx); +} + +static void codec_vp9_rm_noshow_frame(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + struct vp9_frame *tmp; + + list_for_each_entry(tmp, &vp9->ref_frames_list, list) { + if (tmp->show) + continue; + + pr_debug("rm noshow: %u\n", tmp->index); + v4l2_m2m_buf_queue(sess->m2m_ctx, tmp->vbuf); + list_del(&tmp->list); + kfree(tmp); + vp9->frames_num--; + return; + } +} + +static void codec_vp9_process_frame(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + union rpm_param *param = &vp9->rpm_param; + int intra_only; + + if (!param->p.show_frame) + codec_vp9_rm_noshow_frame(sess); + + vp9->cur_frame = codec_vp9_get_new_frame(sess); + if (!vp9->cur_frame) + return; + + pr_debug("frame %d: type: %08X; show_exist: %u; show: %u, intra_only: %u\n", + vp9->cur_frame->index, + param->p.frame_type, param->p.show_existing_frame, + param->p.show_frame, param->p.intra_only); + + if (param->p.frame_type != KEY_FRAME) + codec_vp9_sync_ref(vp9); + codec_vp9_update_next_ref(vp9); + codec_vp9_show_existing_frame(vp9); + + if (codec_hevc_use_mmu(core->platform->revision, sess->pixfmt_cap, + vp9->is_10bit)) + codec_hevc_fill_mmu_map(sess, &vp9->common, + &vp9->cur_frame->vbuf->vb2_buf); + + intra_only = param->p.show_frame ? 0 : param->p.intra_only; + + /* clear mpred (for keyframe only) */ + if (param->p.frame_type != KEY_FRAME && !intra_only) { + codec_vp9_set_mc(sess, vp9); + codec_vp9_set_mpred_mv(core, vp9); + } else { + amvdec_clear_dos_bits(core, HEVC_MPRED_CTRL4, BIT(6)); + } + + amvdec_write_dos(core, HEVC_PARSER_PICTURE_SIZE, + (vp9->height << 16) | vp9->width); + codec_vp9_set_mcrcc(sess); + codec_vp9_set_sao(sess, &vp9->cur_frame->vbuf->vb2_buf); + + vp9_loop_filter_frame_init(core, &vp9->seg_4lf, + &vp9->lfi, &vp9->lf, + vp9->default_filt_lvl); + + /* ask uCode to start decoding */ + amvdec_write_dos(core, VP9_DEC_STATUS_REG, VP9_10B_DECODE_SLICE); +} + +static void codec_vp9_process_lf(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int i; + + vp9->lf.mode_ref_delta_enabled = param->p.mode_ref_delta_enabled; + vp9->lf.sharpness_level = param->p.sharpness_level; + vp9->default_filt_lvl = param->p.filter_level; + vp9->seg_4lf.enabled = param->p.seg_enabled; + vp9->seg_4lf.abs_delta = param->p.seg_abs_delta; + + for (i = 0; i < 4; i++) + vp9->lf.ref_deltas[i] = param->p.ref_deltas[i]; + + for (i = 0; i < 2; i++) + vp9->lf.mode_deltas[i] = param->p.mode_deltas[i]; + + for (i = 0; i < MAX_SEGMENTS; i++) + vp9->seg_4lf.feature_mask[i] = + (param->p.seg_lf_info[i] & 0x8000) ? + (1 << SEG_LVL_ALT_LF) : 0; + + for (i = 0; i < MAX_SEGMENTS; i++) + vp9->seg_4lf.feature_data[i][SEG_LVL_ALT_LF] = + (param->p.seg_lf_info[i] & 0x100) ? + -(param->p.seg_lf_info[i] & 0x3f) + : (param->p.seg_lf_info[i] & 0x3f); +} + +static void codec_vp9_resume(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + + mutex_lock(&vp9->lock); + if (codec_hevc_setup_buffers(sess, &vp9->common, vp9->is_10bit)) { + mutex_unlock(&vp9->lock); + amvdec_abort(sess); + return; + } + + codec_vp9_setup_workspace(sess, vp9); + codec_hevc_setup_decode_head(sess, vp9->is_10bit); + codec_vp9_process_lf(vp9); + codec_vp9_process_frame(sess); + + mutex_unlock(&vp9->lock); +} + +/* + * The RPM section within the workspace contains + * many information regarding the parsed bitstream + */ +static void codec_vp9_fetch_rpm(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + u16 *rpm_vaddr = vp9->workspace_vaddr + RPM_OFFSET; + int i, j; + + for (i = 0; i < RPM_BUF_SIZE; i += 4) + for (j = 0; j < 4; j++) + vp9->rpm_param.l.data[i + j] = rpm_vaddr[i + 3 - j]; +} + +static int codec_vp9_process_rpm(struct codec_vp9 *vp9) +{ + union rpm_param *param = &vp9->rpm_param; + int src_changed = 0; + int is_10bit = 0; + int pic_width_64 = ALIGN(param->p.width, 64); + int pic_height_32 = ALIGN(param->p.height, 32); + int pic_width_lcu = (pic_width_64 % LCU_SIZE) ? + pic_width_64 / LCU_SIZE + 1 + : pic_width_64 / LCU_SIZE; + int pic_height_lcu = (pic_height_32 % LCU_SIZE) ? + pic_height_32 / LCU_SIZE + 1 + : pic_height_32 / LCU_SIZE; + vp9->lcu_total = pic_width_lcu * pic_height_lcu; + + if (param->p.bit_depth == 10) + is_10bit = 1; + + if (vp9->width != param->p.width || vp9->height != param->p.height || + vp9->is_10bit != is_10bit) + src_changed = 1; + + vp9->width = param->p.width; + vp9->height = param->p.height; + vp9->is_10bit = is_10bit; + + pr_debug("width: %u; height: %u; is_10bit: %d; src_changed: %d\n", + vp9->width, vp9->height, is_10bit, src_changed); + + return src_changed; +} + +static bool codec_vp9_is_ref(struct codec_vp9 *vp9, struct vp9_frame *frame) +{ + int i; + + for (i = 0; i < REF_FRAMES; ++i) + if (vp9->ref_frame_map[i] == frame->index) + return true; + + return false; +} + +static void codec_vp9_show_frame(struct amvdec_session *sess) +{ + struct codec_vp9 *vp9 = sess->priv; + struct vp9_frame *tmp, *n; + + list_for_each_entry_safe(tmp, n, &vp9->ref_frames_list, list) { + if (!tmp->show || tmp == vp9->cur_frame) + continue; + + if (!tmp->done) { + pr_debug("Doning %u\n", tmp->index); + amvdec_dst_buf_done(sess, tmp->vbuf, V4L2_FIELD_NONE); + tmp->done = 1; + vp9->frames_num--; + } + + if (codec_vp9_is_ref(vp9, tmp) || tmp == vp9->prev_frame) + continue; + + pr_debug("deleting %d\n", tmp->index); + list_del(&tmp->list); + kfree(tmp); + } +} + +static void vp9_tree_merge_probs(unsigned int *prev_prob, + unsigned int *cur_prob, + int coef_node_start, int tree_left, + int tree_right, + int tree_i, int node) +{ + int prob_32, prob_res, prob_shift; + int pre_prob, new_prob; + int den, m_count, get_prob, factor; + + prob_32 = prev_prob[coef_node_start / 4 * 2]; + prob_res = coef_node_start & 3; + prob_shift = prob_res * 8; + pre_prob = (prob_32 >> prob_shift) & 0xff; + + den = tree_left + tree_right; + + if (den == 0) { + new_prob = pre_prob; + } else { + m_count = min(den, MODE_MV_COUNT_SAT); + get_prob = + clip_prob(div_r32(((int64_t)tree_left * 256 + + (den >> 1)), + den)); + + /* weighted_prob */ + factor = count_to_update_factor[m_count]; + new_prob = round_power_of_two(pre_prob * (256 - factor) + + get_prob * factor, 8); + } + + cur_prob[coef_node_start / 4 * 2] = + (cur_prob[coef_node_start / 4 * 2] & (~(0xff << prob_shift))) | + (new_prob << prob_shift); +} + +static void adapt_coef_probs_cxt(unsigned int *prev_prob, + unsigned int *cur_prob, + unsigned int *count, + int update_factor, + int cxt_num, + int coef_cxt_start, + int coef_count_cxt_start) +{ + int prob_32, prob_res, prob_shift; + int pre_prob, new_prob; + int num, den, m_count, get_prob, factor; + int node, coef_node_start; + int count_sat = 24; + int cxt; + + for (cxt = 0; cxt < cxt_num; cxt++) { + const int n0 = count[coef_count_cxt_start]; + const int n1 = count[coef_count_cxt_start + 1]; + const int n2 = count[coef_count_cxt_start + 2]; + const int neob = count[coef_count_cxt_start + 3]; + const int nneob = count[coef_count_cxt_start + 4]; + const unsigned int branch_ct[3][2] = { + { neob, nneob }, + { n0, n1 + n2 }, + { n1, n2 } + }; + + coef_node_start = coef_cxt_start; + for (node = 0 ; node < 3 ; node++) { + prob_32 = prev_prob[coef_node_start / 4 * 2]; + prob_res = coef_node_start & 3; + prob_shift = prob_res * 8; + pre_prob = (prob_32 >> prob_shift) & 0xff; + + /* get binary prob */ + num = branch_ct[node][0]; + den = branch_ct[node][0] + branch_ct[node][1]; + m_count = min(den, count_sat); + + get_prob = (den == 0) ? + 128u : + clip_prob(div_r32(((int64_t)num * 256 + + (den >> 1)), den)); + + factor = update_factor * m_count / count_sat; + new_prob = + round_power_of_two(pre_prob * (256 - factor) + + get_prob * factor, 8); + + cur_prob[coef_node_start / 4 * 2] = + (cur_prob[coef_node_start / 4 * 2] & + (~(0xff << prob_shift))) | + (new_prob << prob_shift); + + coef_node_start += 1; + } + + coef_cxt_start = coef_cxt_start + 3; + coef_count_cxt_start = coef_count_cxt_start + 5; + } +} + +static void adapt_coef_probs(int prev_kf, int cur_kf, int pre_fc, + unsigned int *prev_prob, unsigned int *cur_prob, + unsigned int *count) +{ + int tx_size, coef_tx_size_start, coef_count_tx_size_start; + int plane, coef_plane_start, coef_count_plane_start; + int type, coef_type_start, coef_count_type_start; + int band, coef_band_start, coef_count_band_start; + int cxt_num; + int coef_cxt_start, coef_count_cxt_start; + int node, coef_node_start, coef_count_node_start; + + int tree_i, tree_left, tree_right; + int mvd_i; + + int update_factor = cur_kf ? 112 : (prev_kf ? 128 : 112); + + int prob_32; + int prob_res; + int prob_shift; + int pre_prob; + + int den; + int get_prob; + int m_count; + int factor; + + int new_prob; + + for (tx_size = 0 ; tx_size < 4 ; tx_size++) { + coef_tx_size_start = VP9_COEF_START + + tx_size * 4 * VP9_COEF_SIZE_ONE_SET; + coef_count_tx_size_start = VP9_COEF_COUNT_START + + tx_size * 4 * VP9_COEF_COUNT_SIZE_ONE_SET; + coef_plane_start = coef_tx_size_start; + coef_count_plane_start = coef_count_tx_size_start; + + for (plane = 0 ; plane < 2 ; plane++) { + coef_type_start = coef_plane_start; + coef_count_type_start = coef_count_plane_start; + + for (type = 0 ; type < 2 ; type++) { + coef_band_start = coef_type_start; + coef_count_band_start = coef_count_type_start; + + for (band = 0 ; band < 6 ; band++) { + if (band == 0) + cxt_num = 3; + else + cxt_num = 6; + coef_cxt_start = coef_band_start; + coef_count_cxt_start = + coef_count_band_start; + + adapt_coef_probs_cxt(prev_prob, + cur_prob, + count, + update_factor, + cxt_num, + coef_cxt_start, + coef_count_cxt_start); + + if (band == 0) { + coef_band_start += 10; + coef_count_band_start += 15; + } else { + coef_band_start += 18; + coef_count_band_start += 30; + } + } + coef_type_start += VP9_COEF_SIZE_ONE_SET; + coef_count_type_start += + VP9_COEF_COUNT_SIZE_ONE_SET; + } + + coef_plane_start += 2 * VP9_COEF_SIZE_ONE_SET; + coef_count_plane_start += + 2 * VP9_COEF_COUNT_SIZE_ONE_SET; + } + } + + if (cur_kf == 0) { + /* mode_mv_merge_probs - merge_intra_inter_prob */ + for (coef_count_node_start = VP9_INTRA_INTER_COUNT_START; + coef_count_node_start < (VP9_MV_CLASS0_HP_1_COUNT_START + + VP9_MV_CLASS0_HP_1_COUNT_SIZE); + coef_count_node_start += 2) { + if (coef_count_node_start == + VP9_INTRA_INTER_COUNT_START) + coef_node_start = VP9_INTRA_INTER_START; + else if (coef_count_node_start == + VP9_COMP_INTER_COUNT_START) + coef_node_start = VP9_COMP_INTER_START; + else if (coef_count_node_start == + VP9_TX_MODE_COUNT_START) + coef_node_start = VP9_TX_MODE_START; + else if (coef_count_node_start == + VP9_SKIP_COUNT_START) + coef_node_start = VP9_SKIP_START; + else if (coef_count_node_start == + VP9_MV_SIGN_0_COUNT_START) + coef_node_start = VP9_MV_SIGN_0_START; + else if (coef_count_node_start == + VP9_MV_SIGN_1_COUNT_START) + coef_node_start = VP9_MV_SIGN_1_START; + else if (coef_count_node_start == + VP9_MV_BITS_0_COUNT_START) + coef_node_start = VP9_MV_BITS_0_START; + else if (coef_count_node_start == + VP9_MV_BITS_1_COUNT_START) + coef_node_start = VP9_MV_BITS_1_START; + else /* node_start == VP9_MV_CLASS0_HP_0_COUNT_START */ + coef_node_start = VP9_MV_CLASS0_HP_0_START; + + den = count[coef_count_node_start] + + count[coef_count_node_start + 1]; + + prob_32 = prev_prob[coef_node_start / 4 * 2]; + prob_res = coef_node_start & 3; + prob_shift = prob_res * 8; + pre_prob = (prob_32 >> prob_shift) & 0xff; + + if (den == 0) { + new_prob = pre_prob; + } else { + m_count = min(den, MODE_MV_COUNT_SAT); + get_prob = + clip_prob(div_r32(((int64_t) + count[coef_count_node_start] * 256 + + (den >> 1)), + den)); + + /* weighted prob */ + factor = count_to_update_factor[m_count]; + new_prob = + round_power_of_two(pre_prob * + (256 - factor) + + get_prob * factor, + 8); + } + + cur_prob[coef_node_start / 4 * 2] = + (cur_prob[coef_node_start / 4 * 2] & + (~(0xff << prob_shift))) | + (new_prob << prob_shift); + + coef_node_start = coef_node_start + 1; + } + + coef_node_start = VP9_INTER_MODE_START; + coef_count_node_start = VP9_INTER_MODE_COUNT_START; + for (tree_i = 0 ; tree_i < 7 ; tree_i++) { + for (node = 0 ; node < 3 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 2: + tree_left = count[start + 1]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 3]; + break; + default: + tree_left = count[start + 2]; + tree_right = count[start + 0] + + count[start + 1] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + + coef_count_node_start = coef_count_node_start + 4; + } + + coef_node_start = VP9_IF_Y_MODE_START; + coef_count_node_start = VP9_IF_Y_MODE_COUNT_START; + for (tree_i = 0 ; tree_i < 14 ; tree_i++) { + for (node = 0 ; node < 9 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 8: + tree_left = + count[start + D153_PRED]; + tree_right = + count[start + D207_PRED]; + break; + case 7: + tree_left = + count[start + D63_PRED]; + tree_right = + count[start + D207_PRED] + + count[start + D153_PRED]; + break; + case 6: + tree_left = + count[start + D45_PRED]; + tree_right = + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + case 5: + tree_left = + count[start + D135_PRED]; + tree_right = + count[start + D117_PRED]; + break; + case 4: + tree_left = + count[start + H_PRED]; + tree_right = + count[start + D117_PRED] + + count[start + D135_PRED]; + break; + case 3: + tree_left = + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED]; + tree_right = + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + case 2: + tree_left = + count[start + V_PRED]; + tree_right = + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED] + + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + case 1: + tree_left = + count[start + TM_PRED]; + tree_right = + count[start + V_PRED] + + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED] + + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + default: + tree_left = + count[start + DC_PRED]; + tree_right = + count[start + TM_PRED] + + count[start + V_PRED] + + count[start + H_PRED] + + count[start + D117_PRED] + + count[start + D135_PRED] + + count[start + D45_PRED] + + count[start + D207_PRED] + + count[start + D153_PRED] + + count[start + D63_PRED]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = coef_count_node_start + 10; + } + + coef_node_start = VP9_PARTITION_P_START; + coef_count_node_start = VP9_PARTITION_P_COUNT_START; + for (tree_i = 0 ; tree_i < 16 ; tree_i++) { + for (node = 0 ; node < 3 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 2: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + + coef_count_node_start = coef_count_node_start + 4; + } + + coef_node_start = VP9_INTERP_START; + coef_count_node_start = VP9_INTERP_COUNT_START; + for (tree_i = 0 ; tree_i < 4 ; tree_i++) { + for (node = 0 ; node < 2 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = coef_count_node_start + 3; + } + + coef_node_start = VP9_MV_JOINTS_START; + coef_count_node_start = VP9_MV_JOINTS_COUNT_START; + for (tree_i = 0 ; tree_i < 1 ; tree_i++) { + for (node = 0 ; node < 3 ; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 2: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = coef_count_node_start + 4; + } + + for (mvd_i = 0 ; mvd_i < 2 ; mvd_i++) { + coef_node_start = mvd_i ? VP9_MV_CLASSES_1_START : + VP9_MV_CLASSES_0_START; + coef_count_node_start = mvd_i ? + VP9_MV_CLASSES_1_COUNT_START : + VP9_MV_CLASSES_0_COUNT_START; + tree_i = 0; + for (node = 0; node < 10; node++) { + unsigned int start = coef_count_node_start; + + switch (node) { + case 9: + tree_left = count[start + 9]; + tree_right = count[start + 10]; + break; + case 8: + tree_left = count[start + 7]; + tree_right = count[start + 8]; + break; + case 7: + tree_left = count[start + 7] + + count[start + 8]; + tree_right = count[start + 9] + + count[start + 10]; + break; + case 6: + tree_left = count[start + 6]; + tree_right = count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + case 5: + tree_left = count[start + 4]; + tree_right = count[start + 5]; + break; + case 4: + tree_left = count[start + 4] + + count[start + 5]; + tree_right = count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + case 3: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 2: + tree_left = count[start + 2] + + count[start + 3]; + tree_right = count[start + 4] + + count[start + 5] + + count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3] + + count[start + 4] + + count[start + 5] + + count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3] + + count[start + 4] + + count[start + 5] + + count[start + 6] + + count[start + 7] + + count[start + 8] + + count[start + 9] + + count[start + 10]; + break; + } + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + + coef_node_start = mvd_i ? VP9_MV_CLASS0_1_START : + VP9_MV_CLASS0_0_START; + coef_count_node_start = mvd_i ? + VP9_MV_CLASS0_1_COUNT_START : + VP9_MV_CLASS0_0_COUNT_START; + tree_i = 0; + node = 0; + tree_left = count[coef_count_node_start + 0]; + tree_right = count[coef_count_node_start + 1]; + + vp9_tree_merge_probs(prev_prob, cur_prob, + coef_node_start, + tree_left, tree_right, + tree_i, node); + coef_node_start = mvd_i ? VP9_MV_CLASS0_FP_1_START : + VP9_MV_CLASS0_FP_0_START; + coef_count_node_start = mvd_i ? + VP9_MV_CLASS0_FP_1_COUNT_START : + VP9_MV_CLASS0_FP_0_COUNT_START; + + for (tree_i = 0; tree_i < 3; tree_i++) { + for (node = 0; node < 3; node++) { + unsigned int start = + coef_count_node_start; + switch (node) { + case 2: + tree_left = count[start + 2]; + tree_right = count[start + 3]; + break; + case 1: + tree_left = count[start + 1]; + tree_right = count[start + 2] + + count[start + 3]; + break; + default: + tree_left = count[start + 0]; + tree_right = count[start + 1] + + count[start + 2] + + count[start + 3]; + break; + } + + vp9_tree_merge_probs(prev_prob, + cur_prob, + coef_node_start, + tree_left, + tree_right, + tree_i, node); + + coef_node_start = coef_node_start + 1; + } + coef_count_node_start = + coef_count_node_start + 4; + } + } + } +} + +static irqreturn_t codec_vp9_threaded_isr(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct codec_vp9 *vp9 = sess->priv; + u32 dec_status = amvdec_read_dos(core, VP9_DEC_STATUS_REG); + u32 prob_status = amvdec_read_dos(core, VP9_ADAPT_PROB_REG); + int i; + + if (!vp9) + return IRQ_HANDLED; + + mutex_lock(&vp9->lock); + if (dec_status != VP9_HEAD_PARSER_DONE) { + dev_err(core->dev_dec, "Unrecognized dec_status: %08X\n", + dec_status); + amvdec_abort(sess); + goto unlock; + } + + pr_debug("ISR: %08X;%08X\n", dec_status, prob_status); + sess->keyframe_found = 1; + + if ((prob_status & 0xff) == 0xfd && vp9->cur_frame) { + /* VP9_REQ_ADAPT_PROB */ + u8 *prev_prob_b = ((u8 *)vp9->workspace_vaddr + + PROB_OFFSET) + + ((prob_status >> 8) * 0x1000); + u8 *cur_prob_b = ((u8 *)vp9->workspace_vaddr + + PROB_OFFSET) + 0x4000; + u8 *count_b = (u8 *)vp9->workspace_vaddr + + COUNT_OFFSET; + int last_frame_type = vp9->prev_frame ? + vp9->prev_frame->type : + KEY_FRAME; + + adapt_coef_probs(last_frame_type == KEY_FRAME, + vp9->cur_frame->type == KEY_FRAME ? 1 : 0, + prob_status >> 8, + (unsigned int *)prev_prob_b, + (unsigned int *)cur_prob_b, + (unsigned int *)count_b); + + memcpy(prev_prob_b, cur_prob_b, ADAPT_PROB_SIZE); + amvdec_write_dos(core, VP9_ADAPT_PROB_REG, 0); + } + + /* Invalidate first 3 refs */ + for (i = 0; i < REFS_PER_FRAME ; ++i) + vp9->frame_refs[i] = NULL; + + vp9->prev_frame = vp9->cur_frame; + codec_vp9_update_ref(vp9); + + codec_vp9_fetch_rpm(sess); + if (codec_vp9_process_rpm(vp9)) { + amvdec_src_change(sess, vp9->width, vp9->height, 16); + + /* No frame is actually processed */ + vp9->cur_frame = NULL; + + /* Show the remaining frame */ + codec_vp9_show_frame(sess); + + /* FIXME: Save refs for resized frame */ + if (vp9->frames_num) + codec_vp9_save_refs(vp9); + + goto unlock; + } + + codec_vp9_process_lf(vp9); + codec_vp9_process_frame(sess); + codec_vp9_show_frame(sess); + +unlock: + mutex_unlock(&vp9->lock); + return IRQ_HANDLED; +} + +static irqreturn_t codec_vp9_isr(struct amvdec_session *sess) +{ + return IRQ_WAKE_THREAD; +} + +struct amvdec_codec_ops codec_vp9_ops = { + .start = codec_vp9_start, + .stop = codec_vp9_stop, + .isr = codec_vp9_isr, + .threaded_isr = codec_vp9_threaded_isr, + .num_pending_bufs = codec_vp9_num_pending_bufs, + .drain = codec_vp9_flush_output, + .resume = codec_vp9_resume, +}; diff --git a/drivers/staging/media/meson/vdec/codec_vp9.h b/drivers/staging/media/meson/vdec/codec_vp9.h new file mode 100644 index 0000000000..62db65a2b9 --- /dev/null +++ b/drivers/staging/media/meson/vdec/codec_vp9.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Maxime Jourdan <maxi.jourdan@wanadoo.fr> + */ + +#ifndef __MESON_VDEC_CODEC_VP9_H_ +#define __MESON_VDEC_CODEC_VP9_H_ + +#include "vdec.h" + +extern struct amvdec_codec_ops codec_vp9_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/dos_regs.h b/drivers/staging/media/meson/vdec/dos_regs.h new file mode 100644 index 0000000000..abd810542d --- /dev/null +++ b/drivers/staging/media/meson/vdec/dos_regs.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_DOS_REGS_H_ +#define __MESON_VDEC_DOS_REGS_H_ + +/* DOS registers */ +#define VDEC_ASSIST_AMR1_INT8 0x00b4 + +#define ASSIST_MBOX1_CLR_REG 0x01d4 +#define ASSIST_MBOX1_MASK 0x01d8 + +#define MPSR 0x0c04 +#define MCPU_INTR_MSK 0x0c10 +#define CPSR 0x0c84 + +#define IMEM_DMA_CTRL 0x0d00 +#define IMEM_DMA_ADR 0x0d04 +#define IMEM_DMA_COUNT 0x0d08 +#define LMEM_DMA_CTRL 0x0d40 + +#define MC_STATUS0 0x2424 +#define MC_CTRL1 0x242c + +#define PSCALE_RST 0x2440 +#define PSCALE_CTRL 0x2444 +#define PSCALE_BMEM_ADDR 0x247c +#define PSCALE_BMEM_DAT 0x2480 + +#define DBLK_CTRL 0x2544 +#define DBLK_STATUS 0x254c + +#define GCLK_EN 0x260c +#define MDEC_PIC_DC_CTRL 0x2638 +#define MDEC_PIC_DC_STATUS 0x263c +#define ANC0_CANVAS_ADDR 0x2640 +#define MDEC_PIC_DC_THRESH 0x26e0 + +/* Firmware interface registers */ +#define AV_SCRATCH_0 0x2700 +#define AV_SCRATCH_1 0x2704 +#define AV_SCRATCH_2 0x2708 +#define AV_SCRATCH_3 0x270c +#define AV_SCRATCH_4 0x2710 +#define AV_SCRATCH_5 0x2714 +#define AV_SCRATCH_6 0x2718 +#define AV_SCRATCH_7 0x271c +#define AV_SCRATCH_8 0x2720 +#define AV_SCRATCH_9 0x2724 +#define AV_SCRATCH_A 0x2728 +#define AV_SCRATCH_B 0x272c +#define AV_SCRATCH_C 0x2730 +#define AV_SCRATCH_D 0x2734 +#define AV_SCRATCH_E 0x2738 +#define AV_SCRATCH_F 0x273c +#define AV_SCRATCH_G 0x2740 +#define AV_SCRATCH_H 0x2744 +#define AV_SCRATCH_I 0x2748 +#define AV_SCRATCH_J 0x274c +#define AV_SCRATCH_K 0x2750 +#define AV_SCRATCH_L 0x2754 + +#define MPEG1_2_REG 0x3004 +#define PIC_HEAD_INFO 0x300c +#define POWER_CTL_VLD 0x3020 +#define M4_CONTROL_REG 0x30a4 + +/* Stream Buffer (stbuf) regs */ +#define VLD_MEM_VIFIFO_START_PTR 0x3100 +#define VLD_MEM_VIFIFO_CURR_PTR 0x3104 +#define VLD_MEM_VIFIFO_END_PTR 0x3108 +#define VLD_MEM_VIFIFO_CONTROL 0x3110 + #define MEM_FIFO_CNT_BIT 16 + #define MEM_FILL_ON_LEVEL BIT(10) + #define MEM_CTRL_EMPTY_EN BIT(2) + #define MEM_CTRL_FILL_EN BIT(1) +#define VLD_MEM_VIFIFO_WP 0x3114 +#define VLD_MEM_VIFIFO_RP 0x3118 +#define VLD_MEM_VIFIFO_LEVEL 0x311c +#define VLD_MEM_VIFIFO_BUF_CNTL 0x3120 + #define MEM_BUFCTRL_MANUAL BIT(1) +#define VLD_MEM_VIFIFO_WRAP_COUNT 0x3144 + +#define DCAC_DMA_CTRL 0x3848 + +#define DOS_SW_RESET0 0xfc00 +#define DOS_GCLK_EN0 0xfc04 +#define DOS_GEN_CTRL0 0xfc08 +#define DOS_MEM_PD_VDEC 0xfcc0 +#define DOS_MEM_PD_HEVC 0xfccc +#define DOS_SW_RESET3 0xfcd0 +#define DOS_GCLK_EN3 0xfcd4 +#define DOS_VDEC_MCRCC_STALL_CTRL 0xfd00 + +#endif diff --git a/drivers/staging/media/meson/vdec/esparser.c b/drivers/staging/media/meson/vdec/esparser.c new file mode 100644 index 0000000000..4632346f04 --- /dev/null +++ b/drivers/staging/media/meson/vdec/esparser.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + * + * The Elementary Stream Parser is a HW bitstream parser. + * It reads bitstream buffers and feeds them to the VIFIFO + */ + +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/reset.h> +#include <linux/interrupt.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-mem2mem.h> + +#include "dos_regs.h" +#include "esparser.h" +#include "vdec_helpers.h" + +/* PARSER REGS (CBUS) */ +#define PARSER_CONTROL 0x00 + #define ES_PACK_SIZE_BIT 8 + #define ES_WRITE BIT(5) + #define ES_SEARCH BIT(1) + #define ES_PARSER_START BIT(0) +#define PARSER_FETCH_ADDR 0x4 +#define PARSER_FETCH_CMD 0x8 +#define PARSER_CONFIG 0x14 + #define PS_CFG_MAX_FETCH_CYCLE_BIT 0 + #define PS_CFG_STARTCODE_WID_24_BIT 10 + #define PS_CFG_MAX_ES_WR_CYCLE_BIT 12 + #define PS_CFG_PFIFO_EMPTY_CNT_BIT 16 +#define PFIFO_WR_PTR 0x18 +#define PFIFO_RD_PTR 0x1c +#define PARSER_SEARCH_PATTERN 0x24 + #define ES_START_CODE_PATTERN 0x00000100 +#define PARSER_SEARCH_MASK 0x28 + #define ES_START_CODE_MASK 0xffffff00 + #define FETCH_ENDIAN_BIT 27 +#define PARSER_INT_ENABLE 0x2c + #define PARSER_INT_HOST_EN_BIT 8 +#define PARSER_INT_STATUS 0x30 + #define PARSER_INTSTAT_SC_FOUND 1 +#define PARSER_ES_CONTROL 0x5c +#define PARSER_VIDEO_START_PTR 0x80 +#define PARSER_VIDEO_END_PTR 0x84 +#define PARSER_VIDEO_WP 0x88 +#define PARSER_VIDEO_HOLE 0x90 + +#define SEARCH_PATTERN_LEN 512 +#define VP9_HEADER_SIZE 16 + +static DECLARE_WAIT_QUEUE_HEAD(wq); +static int search_done; + +static irqreturn_t esparser_isr(int irq, void *dev) +{ + int int_status; + struct amvdec_core *core = dev; + + int_status = amvdec_read_parser(core, PARSER_INT_STATUS); + amvdec_write_parser(core, PARSER_INT_STATUS, int_status); + + if (int_status & PARSER_INTSTAT_SC_FOUND) { + amvdec_write_parser(core, PFIFO_RD_PTR, 0); + amvdec_write_parser(core, PFIFO_WR_PTR, 0); + search_done = 1; + wake_up_interruptible(&wq); + } + + return IRQ_HANDLED; +} + +/* + * VP9 frame headers need to be appended by a 16-byte long + * Amlogic custom header + */ +static int vp9_update_header(struct amvdec_core *core, struct vb2_buffer *buf) +{ + u8 *dp; + u8 marker; + int dsize; + int num_frames, cur_frame; + int cur_mag, mag, mag_ptr; + int frame_size[8], tot_frame_size[8]; + int total_datasize = 0; + int new_frame_size; + unsigned char *old_header = NULL; + + dp = (uint8_t *)vb2_plane_vaddr(buf, 0); + dsize = vb2_get_plane_payload(buf, 0); + + if (dsize == vb2_plane_size(buf, 0)) { + dev_warn(core->dev, "%s: unable to update header\n", __func__); + return 0; + } + + marker = dp[dsize - 1]; + if ((marker & 0xe0) == 0xc0) { + num_frames = (marker & 0x7) + 1; + mag = ((marker >> 3) & 0x3) + 1; + mag_ptr = dsize - mag * num_frames - 2; + if (dp[mag_ptr] != marker) + return 0; + + mag_ptr++; + for (cur_frame = 0; cur_frame < num_frames; cur_frame++) { + frame_size[cur_frame] = 0; + for (cur_mag = 0; cur_mag < mag; cur_mag++) { + frame_size[cur_frame] |= + (dp[mag_ptr] << (cur_mag * 8)); + mag_ptr++; + } + if (cur_frame == 0) + tot_frame_size[cur_frame] = + frame_size[cur_frame]; + else + tot_frame_size[cur_frame] = + tot_frame_size[cur_frame - 1] + + frame_size[cur_frame]; + total_datasize += frame_size[cur_frame]; + } + } else { + num_frames = 1; + frame_size[0] = dsize; + tot_frame_size[0] = dsize; + total_datasize = dsize; + } + + new_frame_size = total_datasize + num_frames * VP9_HEADER_SIZE; + + if (new_frame_size >= vb2_plane_size(buf, 0)) { + dev_warn(core->dev, "%s: unable to update header\n", __func__); + return 0; + } + + for (cur_frame = num_frames - 1; cur_frame >= 0; cur_frame--) { + int framesize = frame_size[cur_frame]; + int framesize_header = framesize + 4; + int oldframeoff = tot_frame_size[cur_frame] - framesize; + int outheaderoff = oldframeoff + cur_frame * VP9_HEADER_SIZE; + u8 *fdata = dp + outheaderoff; + u8 *old_framedata = dp + oldframeoff; + + memmove(fdata + VP9_HEADER_SIZE, old_framedata, framesize); + + fdata[0] = (framesize_header >> 24) & 0xff; + fdata[1] = (framesize_header >> 16) & 0xff; + fdata[2] = (framesize_header >> 8) & 0xff; + fdata[3] = (framesize_header >> 0) & 0xff; + fdata[4] = ((framesize_header >> 24) & 0xff) ^ 0xff; + fdata[5] = ((framesize_header >> 16) & 0xff) ^ 0xff; + fdata[6] = ((framesize_header >> 8) & 0xff) ^ 0xff; + fdata[7] = ((framesize_header >> 0) & 0xff) ^ 0xff; + fdata[8] = 0; + fdata[9] = 0; + fdata[10] = 0; + fdata[11] = 1; + fdata[12] = 'A'; + fdata[13] = 'M'; + fdata[14] = 'L'; + fdata[15] = 'V'; + + if (!old_header) { + /* nothing */ + } else if (old_header > fdata + 16 + framesize) { + dev_dbg(core->dev, "%s: data has gaps, setting to 0\n", + __func__); + memset(fdata + 16 + framesize, 0, + (old_header - fdata + 16 + framesize)); + } else if (old_header < fdata + 16 + framesize) { + dev_err(core->dev, "%s: data overwritten\n", __func__); + } + old_header = fdata; + } + + return new_frame_size; +} + +/* Pad the packet to at least 4KiB bytes otherwise the VDEC unit won't trigger + * ISRs. + * Also append a start code 000001ff at the end to trigger + * the ESPARSER interrupt. + */ +static u32 esparser_pad_start_code(struct amvdec_core *core, + struct vb2_buffer *vb, + u32 payload_size) +{ + u32 pad_size = 0; + u8 *vaddr = vb2_plane_vaddr(vb, 0); + + if (payload_size < ESPARSER_MIN_PACKET_SIZE) { + pad_size = ESPARSER_MIN_PACKET_SIZE - payload_size; + memset(vaddr + payload_size, 0, pad_size); + } + + if ((payload_size + pad_size + SEARCH_PATTERN_LEN) > + vb2_plane_size(vb, 0)) { + dev_warn(core->dev, "%s: unable to pad start code\n", __func__); + return pad_size; + } + + memset(vaddr + payload_size + pad_size, 0, SEARCH_PATTERN_LEN); + vaddr[payload_size + pad_size] = 0x00; + vaddr[payload_size + pad_size + 1] = 0x00; + vaddr[payload_size + pad_size + 2] = 0x01; + vaddr[payload_size + pad_size + 3] = 0xff; + + return pad_size; +} + +static int +esparser_write_data(struct amvdec_core *core, dma_addr_t addr, u32 size) +{ + amvdec_write_parser(core, PFIFO_RD_PTR, 0); + amvdec_write_parser(core, PFIFO_WR_PTR, 0); + amvdec_write_parser(core, PARSER_CONTROL, + ES_WRITE | + ES_PARSER_START | + ES_SEARCH | + (size << ES_PACK_SIZE_BIT)); + + amvdec_write_parser(core, PARSER_FETCH_ADDR, addr); + amvdec_write_parser(core, PARSER_FETCH_CMD, + (7 << FETCH_ENDIAN_BIT) | + (size + SEARCH_PATTERN_LEN)); + + search_done = 0; + return wait_event_interruptible_timeout(wq, search_done, (HZ / 5)); +} + +static u32 esparser_vififo_get_free_space(struct amvdec_session *sess) +{ + u32 vififo_usage; + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; + struct amvdec_core *core = sess->core; + + vififo_usage = vdec_ops->vififo_level(sess); + vififo_usage += amvdec_read_parser(core, PARSER_VIDEO_HOLE); + vififo_usage += (6 * SZ_1K); // 6 KiB internal fifo + + if (vififo_usage > sess->vififo_size) { + dev_warn(sess->core->dev, + "VIFIFO usage (%u) > VIFIFO size (%u)\n", + vififo_usage, sess->vififo_size); + return 0; + } + + return sess->vififo_size - vififo_usage; +} + +int esparser_queue_eos(struct amvdec_core *core, const u8 *data, u32 len) +{ + struct device *dev = core->dev; + void *eos_vaddr; + dma_addr_t eos_paddr; + int ret; + + eos_vaddr = dma_alloc_coherent(dev, len + SEARCH_PATTERN_LEN, + &eos_paddr, GFP_KERNEL); + if (!eos_vaddr) + return -ENOMEM; + + memcpy(eos_vaddr, data, len); + ret = esparser_write_data(core, eos_paddr, len); + dma_free_coherent(dev, len + SEARCH_PATTERN_LEN, + eos_vaddr, eos_paddr); + + return ret; +} + +static u32 esparser_get_offset(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + u32 offset = amvdec_read_parser(core, PARSER_VIDEO_WP) - + sess->vififo_paddr; + + if (offset < sess->last_offset) + sess->wrap_count++; + + sess->last_offset = offset; + offset += (sess->wrap_count * sess->vififo_size); + + return offset; +} + +static int +esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf) +{ + int ret; + struct vb2_buffer *vb = &vbuf->vb2_buf; + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + u32 payload_size = vb2_get_plane_payload(vb, 0); + dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0); + u32 num_dst_bufs = 0; + u32 offset; + u32 pad_size; + + /* + * When max ref frame is held by VP9, this should be -= 3 to prevent a + * shortage of CAPTURE buffers on the decoder side. + * For the future, a good enhancement of the way this is handled could + * be to notify new capture buffers to the decoding modules, so that + * they could pause when there is no capture buffer available and + * resume on this notification. + */ + if (sess->fmt_out->pixfmt == V4L2_PIX_FMT_VP9) { + if (codec_ops->num_pending_bufs) + num_dst_bufs = codec_ops->num_pending_bufs(sess); + + num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx); + num_dst_bufs -= 3; + + if (esparser_vififo_get_free_space(sess) < payload_size || + atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs) + return -EAGAIN; + } else if (esparser_vififo_get_free_space(sess) < payload_size) { + return -EAGAIN; + } + + v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf); + + offset = esparser_get_offset(sess); + + ret = amvdec_add_ts(sess, vb->timestamp, vbuf->timecode, offset, vbuf->flags); + if (ret) { + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + return ret; + } + + dev_dbg(core->dev, "esparser: ts = %llu pld_size = %u offset = %08X flags = %08X\n", + vb->timestamp, payload_size, offset, vbuf->flags); + + vbuf->flags = 0; + vbuf->field = V4L2_FIELD_NONE; + vbuf->sequence = sess->sequence_out++; + + if (sess->fmt_out->pixfmt == V4L2_PIX_FMT_VP9) { + payload_size = vp9_update_header(core, vb); + + /* If unable to alter buffer to add headers */ + if (payload_size == 0) { + amvdec_remove_ts(sess, vb->timestamp); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + + return 0; + } + } + + pad_size = esparser_pad_start_code(core, vb, payload_size); + ret = esparser_write_data(core, phy, payload_size + pad_size); + + if (ret <= 0) { + dev_warn(core->dev, "esparser: input parsing error\n"); + amvdec_remove_ts(sess, vb->timestamp); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + amvdec_write_parser(core, PARSER_FETCH_CMD, 0); + + return 0; + } + + atomic_inc(&sess->esparser_queued_bufs); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); + + return 0; +} + +void esparser_queue_all_src(struct work_struct *work) +{ + struct v4l2_m2m_buffer *buf, *n; + struct amvdec_session *sess = + container_of(work, struct amvdec_session, esparser_queue_work); + + mutex_lock(&sess->lock); + v4l2_m2m_for_each_src_buf_safe(sess->m2m_ctx, buf, n) { + if (sess->should_stop) + break; + + if (esparser_queue(sess, &buf->vb) < 0) + break; + } + mutex_unlock(&sess->lock); +} + +int esparser_power_up(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; + + reset_control_reset(core->esparser_reset); + amvdec_write_parser(core, PARSER_CONFIG, + (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) | + (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) | + (16 << PS_CFG_MAX_FETCH_CYCLE_BIT)); + + amvdec_write_parser(core, PFIFO_RD_PTR, 0); + amvdec_write_parser(core, PFIFO_WR_PTR, 0); + + amvdec_write_parser(core, PARSER_SEARCH_PATTERN, + ES_START_CODE_PATTERN); + amvdec_write_parser(core, PARSER_SEARCH_MASK, ES_START_CODE_MASK); + + amvdec_write_parser(core, PARSER_CONFIG, + (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) | + (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) | + (16 << PS_CFG_MAX_FETCH_CYCLE_BIT) | + (2 << PS_CFG_STARTCODE_WID_24_BIT)); + + amvdec_write_parser(core, PARSER_CONTROL, + (ES_SEARCH | ES_PARSER_START)); + + amvdec_write_parser(core, PARSER_VIDEO_START_PTR, sess->vififo_paddr); + amvdec_write_parser(core, PARSER_VIDEO_END_PTR, + sess->vififo_paddr + sess->vififo_size - 8); + amvdec_write_parser(core, PARSER_ES_CONTROL, + amvdec_read_parser(core, PARSER_ES_CONTROL) & ~1); + + if (vdec_ops->conf_esparser) + vdec_ops->conf_esparser(sess); + + amvdec_write_parser(core, PARSER_INT_STATUS, 0xffff); + amvdec_write_parser(core, PARSER_INT_ENABLE, + BIT(PARSER_INT_HOST_EN_BIT)); + + return 0; +} + +int esparser_init(struct platform_device *pdev, struct amvdec_core *core) +{ + struct device *dev = &pdev->dev; + int ret; + int irq; + + irq = platform_get_irq_byname(pdev, "esparser"); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, esparser_isr, IRQF_SHARED, + "esparserirq", core); + if (ret) { + dev_err(dev, "Failed requesting ESPARSER IRQ\n"); + return ret; + } + + core->esparser_reset = + devm_reset_control_get_exclusive(dev, "esparser"); + if (IS_ERR(core->esparser_reset)) { + dev_err(dev, "Failed to get esparser_reset\n"); + return PTR_ERR(core->esparser_reset); + } + + return 0; +} diff --git a/drivers/staging/media/meson/vdec/esparser.h b/drivers/staging/media/meson/vdec/esparser.h new file mode 100644 index 0000000000..9351e62c70 --- /dev/null +++ b/drivers/staging/media/meson/vdec/esparser.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_ESPARSER_H_ +#define __MESON_VDEC_ESPARSER_H_ + +#include <linux/platform_device.h> + +#include "vdec.h" + +int esparser_init(struct platform_device *pdev, struct amvdec_core *core); +int esparser_power_up(struct amvdec_session *sess); + +/** + * esparser_queue_eos() - write End Of Stream sequence to the ESPARSER + * + * @core: vdec core struct + * @data: EOS sequence + * @len: length of EOS sequence + */ +int esparser_queue_eos(struct amvdec_core *core, const u8 *data, u32 len); + +/** + * esparser_queue_all_src() - work handler that writes as many src buffers + * as possible to the ESPARSER + * + * @work: work struct + */ +void esparser_queue_all_src(struct work_struct *work); + +#define ESPARSER_MIN_PACKET_SIZE SZ_4K + +#endif diff --git a/drivers/staging/media/meson/vdec/hevc_regs.h b/drivers/staging/media/meson/vdec/hevc_regs.h new file mode 100644 index 0000000000..0392f41a1e --- /dev/null +++ b/drivers/staging/media/meson/vdec/hevc_regs.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#ifndef __MESON_VDEC_HEVC_REGS_H_ +#define __MESON_VDEC_HEVC_REGS_H_ + +#define HEVC_ASSIST_MMU_MAP_ADDR 0xc024 + +#define HEVC_ASSIST_MBOX1_CLR_REG 0xc1d4 +#define HEVC_ASSIST_MBOX1_MASK 0xc1d8 + +#define HEVC_ASSIST_SCRATCH_0 0xc300 +#define HEVC_ASSIST_SCRATCH_1 0xc304 +#define HEVC_ASSIST_SCRATCH_2 0xc308 +#define HEVC_ASSIST_SCRATCH_3 0xc30c +#define HEVC_ASSIST_SCRATCH_4 0xc310 +#define HEVC_ASSIST_SCRATCH_5 0xc314 +#define HEVC_ASSIST_SCRATCH_6 0xc318 +#define HEVC_ASSIST_SCRATCH_7 0xc31c +#define HEVC_ASSIST_SCRATCH_8 0xc320 +#define HEVC_ASSIST_SCRATCH_9 0xc324 +#define HEVC_ASSIST_SCRATCH_A 0xc328 +#define HEVC_ASSIST_SCRATCH_B 0xc32c +#define HEVC_ASSIST_SCRATCH_C 0xc330 +#define HEVC_ASSIST_SCRATCH_D 0xc334 +#define HEVC_ASSIST_SCRATCH_E 0xc338 +#define HEVC_ASSIST_SCRATCH_F 0xc33c +#define HEVC_ASSIST_SCRATCH_G 0xc340 +#define HEVC_ASSIST_SCRATCH_H 0xc344 +#define HEVC_ASSIST_SCRATCH_I 0xc348 +#define HEVC_ASSIST_SCRATCH_J 0xc34c +#define HEVC_ASSIST_SCRATCH_K 0xc350 +#define HEVC_ASSIST_SCRATCH_L 0xc354 +#define HEVC_ASSIST_SCRATCH_M 0xc358 +#define HEVC_ASSIST_SCRATCH_N 0xc35c + +#define HEVC_PARSER_VERSION 0xc400 +#define HEVC_STREAM_CONTROL 0xc404 +#define HEVC_STREAM_START_ADDR 0xc408 +#define HEVC_STREAM_END_ADDR 0xc40c +#define HEVC_STREAM_WR_PTR 0xc410 +#define HEVC_STREAM_RD_PTR 0xc414 +#define HEVC_STREAM_LEVEL 0xc418 +#define HEVC_STREAM_FIFO_CTL 0xc41c +#define HEVC_SHIFT_CONTROL 0xc420 +#define HEVC_SHIFT_STARTCODE 0xc424 +#define HEVC_SHIFT_EMULATECODE 0xc428 +#define HEVC_SHIFT_STATUS 0xc42c +#define HEVC_SHIFTED_DATA 0xc430 +#define HEVC_SHIFT_BYTE_COUNT 0xc434 +#define HEVC_SHIFT_COMMAND 0xc438 +#define HEVC_ELEMENT_RESULT 0xc43c +#define HEVC_CABAC_CONTROL 0xc440 +#define HEVC_PARSER_SLICE_INFO 0xc444 +#define HEVC_PARSER_CMD_WRITE 0xc448 +#define HEVC_PARSER_CORE_CONTROL 0xc44c +#define HEVC_PARSER_CMD_FETCH 0xc450 +#define HEVC_PARSER_CMD_STATUS 0xc454 +#define HEVC_PARSER_LCU_INFO 0xc458 +#define HEVC_PARSER_HEADER_INFO 0xc45c +#define HEVC_PARSER_INT_CONTROL 0xc480 +#define HEVC_PARSER_INT_STATUS 0xc484 +#define HEVC_PARSER_IF_CONTROL 0xc488 +#define HEVC_PARSER_PICTURE_SIZE 0xc48c +#define HEVC_PARSER_LCU_START 0xc490 +#define HEVC_PARSER_HEADER_INFO2 0xc494 +#define HEVC_PARSER_QUANT_READ 0xc498 +#define HEVC_PARSER_RESERVED_27 0xc49c +#define HEVC_PARSER_CMD_SKIP_0 0xc4a0 +#define HEVC_PARSER_CMD_SKIP_1 0xc4a4 +#define HEVC_PARSER_CMD_SKIP_2 0xc4a8 +#define HEVC_SAO_IF_STATUS 0xc4c0 +#define HEVC_SAO_IF_DATA_Y 0xc4c4 +#define HEVC_SAO_IF_DATA_U 0xc4c8 +#define HEVC_SAO_IF_DATA_V 0xc4cc +#define HEVC_STREAM_SWAP_ADDR 0xc4d0 +#define HEVC_STREAM_SWAP_CTRL 0xc4d4 +#define HEVC_IQIT_IF_WAIT_CNT 0xc4d8 +#define HEVC_MPRED_IF_WAIT_CNT 0xc4dc +#define HEVC_SAO_IF_WAIT_CNT 0xc4e0 + +#define HEVC_MPRED_VERSION 0xc800 +#define HEVC_MPRED_CTRL0 0xc804 + #define MPRED_CTRL0_NEW_PIC BIT(2) + #define MPRED_CTRL0_NEW_TILE BIT(3) + #define MPRED_CTRL0_NEW_SLI_SEG BIT(4) + #define MPRED_CTRL0_TMVP BIT(5) + #define MPRED_CTRL0_LDC BIT(6) + #define MPRED_CTRL0_COL_FROM_L0 BIT(7) + #define MPRED_CTRL0_ABOVE_EN BIT(9) + #define MPRED_CTRL0_MV_WR_EN BIT(10) + #define MPRED_CTRL0_MV_RD_EN BIT(11) + #define MPRED_CTRL0_BUF_LINEAR BIT(13) +#define HEVC_MPRED_CTRL1 0xc808 +#define HEVC_MPRED_INT_EN 0xc80c +#define HEVC_MPRED_INT_STATUS 0xc810 +#define HEVC_MPRED_PIC_SIZE 0xc814 +#define HEVC_MPRED_PIC_SIZE_LCU 0xc818 +#define HEVC_MPRED_TILE_START 0xc81c +#define HEVC_MPRED_TILE_SIZE_LCU 0xc820 +#define HEVC_MPRED_REF_NUM 0xc824 +#define HEVC_MPRED_REF_EN_L0 0xc830 +#define HEVC_MPRED_REF_EN_L1 0xc834 +#define HEVC_MPRED_COLREF_EN_L0 0xc838 +#define HEVC_MPRED_COLREF_EN_L1 0xc83c +#define HEVC_MPRED_AXI_WCTRL 0xc840 +#define HEVC_MPRED_AXI_RCTRL 0xc844 +#define HEVC_MPRED_ABV_START_ADDR 0xc848 +#define HEVC_MPRED_MV_WR_START_ADDR 0xc84c +#define HEVC_MPRED_MV_RD_START_ADDR 0xc850 +#define HEVC_MPRED_MV_WPTR 0xc854 +#define HEVC_MPRED_MV_RPTR 0xc858 +#define HEVC_MPRED_MV_WR_ROW_JUMP 0xc85c +#define HEVC_MPRED_MV_RD_ROW_JUMP 0xc860 +#define HEVC_MPRED_CURR_LCU 0xc864 +#define HEVC_MPRED_ABV_WPTR 0xc868 +#define HEVC_MPRED_ABV_RPTR 0xc86c +#define HEVC_MPRED_CTRL2 0xc870 +#define HEVC_MPRED_CTRL3 0xc874 +#define HEVC_MPRED_L0_REF00_POC 0xc880 +#define HEVC_MPRED_L1_REF00_POC 0xc8c0 + +#define HEVC_MPRED_CTRL4 0xc930 + +#define HEVC_MPRED_CUR_POC 0xc980 +#define HEVC_MPRED_COL_POC 0xc984 +#define HEVC_MPRED_MV_RD_END_ADDR 0xc988 + +#define HEVC_MSP 0xcc00 +#define HEVC_MPSR 0xcc04 +#define HEVC_MCPU_INTR_MSK 0xcc10 +#define HEVC_MCPU_INTR_REQ 0xcc14 +#define HEVC_CPSR 0xcc84 + +#define HEVC_IMEM_DMA_CTRL 0xcd00 +#define HEVC_IMEM_DMA_ADR 0xcd04 +#define HEVC_IMEM_DMA_COUNT 0xcd08 + +#define HEVCD_IPP_TOP_CNTL 0xd000 +#define HEVCD_IPP_LINEBUFF_BASE 0xd024 +#define HEVCD_IPP_AXIIF_CONFIG 0xd02c + +#define VP9D_MPP_REF_SCALE_ENBL 0xd104 +#define VP9D_MPP_REFINFO_TBL_ACCCONFIG 0xd108 +#define VP9D_MPP_REFINFO_DATA 0xd10c + +#define HEVCD_MPP_ANC2AXI_TBL_CONF_ADDR 0xd180 +#define HEVCD_MPP_ANC2AXI_TBL_CMD_ADDR 0xd184 +#define HEVCD_MPP_ANC2AXI_TBL_DATA 0xd190 + +#define HEVCD_MPP_ANC_CANVAS_ACCCONFIG_ADDR 0xd300 +#define HEVCD_MPP_ANC_CANVAS_DATA_ADDR 0xd304 +#define HEVCD_MPP_DECOMP_CTL1 0xd308 +#define HEVCD_MPP_DECOMP_CTL2 0xd30c +#define HEVCD_MCRCC_CTL1 0xd3c0 +#define HEVCD_MCRCC_CTL2 0xd3c4 +#define HEVCD_MCRCC_CTL3 0xd3c8 + +#define HEVC_DBLK_CFG0 0xd400 +#define HEVC_DBLK_CFG1 0xd404 +#define HEVC_DBLK_CFG2 0xd408 +#define HEVC_DBLK_CFG3 0xd40c +#define HEVC_DBLK_CFG4 0xd410 +#define HEVC_DBLK_CFG5 0xd414 +#define HEVC_DBLK_CFG6 0xd418 +#define HEVC_DBLK_CFG7 0xd41c +#define HEVC_DBLK_CFG8 0xd420 +#define HEVC_DBLK_CFG9 0xd424 +#define HEVC_DBLK_CFGA 0xd428 +#define HEVC_DBLK_STS0 0xd42c +#define HEVC_DBLK_CFGB 0xd42c +#define HEVC_DBLK_STS1 0xd430 +#define HEVC_DBLK_CFGE 0xd438 + +#define HEVC_SAO_VERSION 0xd800 +#define HEVC_SAO_CTRL0 0xd804 +#define HEVC_SAO_CTRL1 0xd808 +#define HEVC_SAO_PIC_SIZE 0xd814 +#define HEVC_SAO_PIC_SIZE_LCU 0xd818 +#define HEVC_SAO_TILE_START 0xd81c +#define HEVC_SAO_TILE_SIZE_LCU 0xd820 +#define HEVC_SAO_Y_START_ADDR 0xd82c +#define HEVC_SAO_Y_LENGTH 0xd830 +#define HEVC_SAO_C_START_ADDR 0xd834 +#define HEVC_SAO_C_LENGTH 0xd838 +#define HEVC_SAO_Y_WPTR 0xd83c +#define HEVC_SAO_C_WPTR 0xd840 +#define HEVC_SAO_ABV_START_ADDR 0xd844 +#define HEVC_SAO_VB_WR_START_ADDR 0xd848 +#define HEVC_SAO_VB_RD_START_ADDR 0xd84c +#define HEVC_SAO_ABV_WPTR 0xd850 +#define HEVC_SAO_ABV_RPTR 0xd854 +#define HEVC_SAO_VB_WPTR 0xd858 +#define HEVC_SAO_VB_RPTR 0xd85c +#define HEVC_SAO_CTRL2 0xd880 +#define HEVC_SAO_CTRL3 0xd884 +#define HEVC_SAO_CTRL4 0xd888 +#define HEVC_SAO_CTRL5 0xd88c +#define HEVC_SAO_CTRL6 0xd890 +#define HEVC_SAO_CTRL7 0xd894 +#define HEVC_CM_BODY_START_ADDR 0xd898 +#define HEVC_CM_BODY_LENGTH 0xd89c +#define HEVC_CM_HEADER_START_ADDR 0xd8a0 +#define HEVC_CM_HEADER_LENGTH 0xd8a4 +#define HEVC_CM_HEADER_OFFSET 0xd8ac +#define HEVC_SAO_MMU_VH0_ADDR 0xd8e8 +#define HEVC_SAO_MMU_VH1_ADDR 0xd8ec + +#define HEVC_IQIT_CLK_RST_CTRL 0xdc00 +#define HEVC_IQIT_SCALELUT_WR_ADDR 0xdc08 +#define HEVC_IQIT_SCALELUT_RD_ADDR 0xdc0c +#define HEVC_IQIT_SCALELUT_DATA 0xdc10 + +#define HEVC_PSCALE_CTRL 0xe444 + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c new file mode 100644 index 0000000000..219185aaa5 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -0,0 +1,1129 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/syscon.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-dev.h> +#include <media/videobuf2-dma-contig.h> + +#include "vdec.h" +#include "esparser.h" +#include "vdec_helpers.h" + +struct dummy_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +/* 16 MiB for parsed bitstream swap exchange */ +#define SIZE_VIFIFO SZ_16M + +static u32 get_output_size(u32 width, u32 height) +{ + return ALIGN(width * height, SZ_64K); +} + +u32 amvdec_get_output_size(struct amvdec_session *sess) +{ + return get_output_size(sess->width, sess->height); +} +EXPORT_SYMBOL_GPL(amvdec_get_output_size); + +static int vdec_codec_needs_recycle(struct amvdec_session *sess) +{ + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + return codec_ops->can_recycle && codec_ops->recycle; +} + +static int vdec_recycle_thread(void *data) +{ + struct amvdec_session *sess = data; + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + struct amvdec_buffer *tmp, *n; + + while (!kthread_should_stop()) { + mutex_lock(&sess->bufs_recycle_lock); + list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) { + if (!codec_ops->can_recycle(core)) + break; + + codec_ops->recycle(core, tmp->vb->index); + list_del(&tmp->list); + kfree(tmp); + } + mutex_unlock(&sess->bufs_recycle_lock); + + usleep_range(5000, 10000); + } + + return 0; +} + +static int vdec_poweron(struct amvdec_session *sess) +{ + int ret; + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; + + ret = clk_prepare_enable(sess->core->dos_parser_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(sess->core->dos_clk); + if (ret) + goto disable_dos_parser; + + ret = vdec_ops->start(sess); + if (ret) + goto disable_dos; + + esparser_power_up(sess); + + return 0; + +disable_dos: + clk_disable_unprepare(sess->core->dos_clk); +disable_dos_parser: + clk_disable_unprepare(sess->core->dos_parser_clk); + + return ret; +} + +static void vdec_wait_inactive(struct amvdec_session *sess) +{ + /* We consider 50ms with no IRQ to be inactive. */ + while (time_is_after_jiffies64(sess->last_irq_jiffies + + msecs_to_jiffies(50))) + msleep(25); +} + +static void vdec_poweroff(struct amvdec_session *sess) +{ + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + sess->should_stop = 1; + vdec_wait_inactive(sess); + if (codec_ops->drain) + codec_ops->drain(sess); + + vdec_ops->stop(sess); + clk_disable_unprepare(sess->core->dos_clk); + clk_disable_unprepare(sess->core->dos_parser_clk); +} + +static void +vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb) +{ + struct amvdec_buffer *new_buf; + + new_buf = kmalloc(sizeof(*new_buf), GFP_KERNEL); + if (!new_buf) + return; + new_buf->vb = vb; + + mutex_lock(&sess->bufs_recycle_lock); + list_add_tail(&new_buf->list, &sess->bufs_recycle); + mutex_unlock(&sess->bufs_recycle_lock); +} + +static void vdec_m2m_device_run(void *priv) +{ + struct amvdec_session *sess = priv; + + schedule_work(&sess->esparser_queue_work); +} + +static void vdec_m2m_job_abort(void *priv) +{ + struct amvdec_session *sess = priv; + + v4l2_m2m_job_finish(sess->m2m_dev, sess->m2m_ctx); +} + +static const struct v4l2_m2m_ops vdec_m2m_ops = { + .device_run = vdec_m2m_device_run, + .job_abort = vdec_m2m_job_abort, +}; + +static void process_num_buffers(struct vb2_queue *q, + struct amvdec_session *sess, + unsigned int *num_buffers, + bool is_reqbufs) +{ + const struct amvdec_format *fmt_out = sess->fmt_out; + unsigned int buffers_total = q->num_buffers + *num_buffers; + u32 min_buf_capture = v4l2_ctrl_g_ctrl(sess->ctrl_min_buf_capture); + + if (q->num_buffers + *num_buffers < min_buf_capture) + *num_buffers = min_buf_capture - q->num_buffers; + if (is_reqbufs && buffers_total < fmt_out->min_buffers) + *num_buffers = fmt_out->min_buffers - q->num_buffers; + if (buffers_total > fmt_out->max_buffers) + *num_buffers = fmt_out->max_buffers - q->num_buffers; + + /* We need to program the complete CAPTURE buffer list + * in registers during start_streaming, and the firmwares + * are free to choose any of them to write frames to. As such, + * we need all of them to be queued into the driver + */ + sess->num_dst_bufs = q->num_buffers + *num_buffers; + q->min_buffers_needed = max(fmt_out->min_buffers, sess->num_dst_bufs); +} + +static int vdec_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct amvdec_session *sess = vb2_get_drv_priv(q); + u32 output_size = amvdec_get_output_size(sess); + + if (*num_planes) { + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (*num_planes != 1 || + sizes[0] < sess->src_buffer_size) + return -EINVAL; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + switch (sess->pixfmt_cap) { + case V4L2_PIX_FMT_NV12M: + if (*num_planes != 2 || + sizes[0] < output_size || + sizes[1] < output_size / 2) + return -EINVAL; + break; + case V4L2_PIX_FMT_YUV420M: + if (*num_planes != 3 || + sizes[0] < output_size || + sizes[1] < output_size / 4 || + sizes[2] < output_size / 4) + return -EINVAL; + break; + default: + return -EINVAL; + } + + process_num_buffers(q, sess, num_buffers, false); + break; + } + + return 0; + } + + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + sizes[0] = sess->src_buffer_size; + *num_planes = 1; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + switch (sess->pixfmt_cap) { + case V4L2_PIX_FMT_NV12M: + sizes[0] = output_size; + sizes[1] = output_size / 2; + *num_planes = 2; + break; + case V4L2_PIX_FMT_YUV420M: + sizes[0] = output_size; + sizes[1] = output_size / 4; + sizes[2] = output_size / 4; + *num_planes = 3; + break; + default: + return -EINVAL; + } + + process_num_buffers(q, sess, num_buffers, true); + break; + default: + return -EINVAL; + } + + sess->changed_format = 1; + return 0; +} + +static void vdec_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct amvdec_session *sess = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_m2m_ctx *m2m_ctx = sess->m2m_ctx; + + v4l2_m2m_buf_queue(m2m_ctx, vbuf); + + if (!sess->streamon_out) + return; + + if (sess->streamon_cap && + vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + vdec_codec_needs_recycle(sess)) + vdec_queue_recycle(sess, vb); + + schedule_work(&sess->esparser_queue_work); +} + +static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct amvdec_session *sess = vb2_get_drv_priv(q); + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + struct amvdec_core *core = sess->core; + struct vb2_v4l2_buffer *buf; + int ret; + + if (core->cur_sess && core->cur_sess != sess) { + ret = -EBUSY; + goto bufs_done; + } + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + sess->streamon_out = 1; + else + sess->streamon_cap = 1; + + if (!sess->streamon_out) + return 0; + + if (sess->status == STATUS_NEEDS_RESUME && + q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + sess->changed_format) { + codec_ops->resume(sess); + sess->status = STATUS_RUNNING; + return 0; + } + + if (sess->status == STATUS_RUNNING || + sess->status == STATUS_NEEDS_RESUME || + sess->status == STATUS_INIT) + return 0; + + sess->vififo_size = SIZE_VIFIFO; + sess->vififo_vaddr = + dma_alloc_coherent(sess->core->dev, sess->vififo_size, + &sess->vififo_paddr, GFP_KERNEL); + if (!sess->vififo_vaddr) { + dev_err(sess->core->dev, "Failed to request VIFIFO buffer\n"); + ret = -ENOMEM; + goto bufs_done; + } + + sess->should_stop = 0; + sess->keyframe_found = 0; + sess->last_offset = 0; + sess->wrap_count = 0; + sess->pixelaspect.numerator = 1; + sess->pixelaspect.denominator = 1; + atomic_set(&sess->esparser_queued_bufs, 0); + v4l2_ctrl_s_ctrl(sess->ctrl_min_buf_capture, 1); + + ret = vdec_poweron(sess); + if (ret) + goto vififo_free; + + sess->sequence_cap = 0; + sess->sequence_out = 0; + if (vdec_codec_needs_recycle(sess)) + sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, + "vdec_recycle"); + + sess->status = STATUS_INIT; + core->cur_sess = sess; + schedule_work(&sess->esparser_queue_work); + return 0; + +vififo_free: + dma_free_coherent(sess->core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); +bufs_done: + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + sess->streamon_out = 0; + else + sess->streamon_cap = 0; + + return ret; +} + +static void vdec_free_canvas(struct amvdec_session *sess) +{ + int i; + + for (i = 0; i < sess->canvas_num; ++i) + meson_canvas_free(sess->core->canvas, sess->canvas_alloc[i]); + + sess->canvas_num = 0; +} + +static void vdec_reset_timestamps(struct amvdec_session *sess) +{ + struct amvdec_timestamp *tmp, *n; + + list_for_each_entry_safe(tmp, n, &sess->timestamps, list) { + list_del(&tmp->list); + kfree(tmp); + } +} + +static void vdec_reset_bufs_recycle(struct amvdec_session *sess) +{ + struct amvdec_buffer *tmp, *n; + + list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) { + list_del(&tmp->list); + kfree(tmp); + } +} + +static void vdec_stop_streaming(struct vb2_queue *q) +{ + struct amvdec_session *sess = vb2_get_drv_priv(q); + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + struct amvdec_core *core = sess->core; + struct vb2_v4l2_buffer *buf; + + if (sess->status == STATUS_RUNNING || + sess->status == STATUS_INIT || + (sess->status == STATUS_NEEDS_RESUME && + (!sess->streamon_out || !sess->streamon_cap))) { + if (vdec_codec_needs_recycle(sess)) + kthread_stop(sess->recycle_thread); + + vdec_poweroff(sess); + vdec_free_canvas(sess); + dma_free_coherent(sess->core->dev, sess->vififo_size, + sess->vififo_vaddr, sess->vififo_paddr); + vdec_reset_timestamps(sess); + vdec_reset_bufs_recycle(sess); + kfree(sess->priv); + sess->priv = NULL; + core->cur_sess = NULL; + sess->status = STATUS_STOPPED; + } + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); + + sess->streamon_out = 0; + } else { + /* Drain remaining refs if was still running */ + if (sess->status >= STATUS_RUNNING && codec_ops->drain) + codec_ops->drain(sess); + + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); + + sess->streamon_cap = 0; + } +} + +static int vdec_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static const struct vb2_ops vdec_vb2_ops = { + .queue_setup = vdec_queue_setup, + .start_streaming = vdec_start_streaming, + .stop_streaming = vdec_stop_streaming, + .buf_queue = vdec_vb2_buf_queue, + .buf_prepare = vdec_vb2_buf_prepare, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strscpy(cap->driver, "meson-vdec", sizeof(cap->driver)); + strscpy(cap->card, "Amlogic Video Decoder", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:meson-vdec", sizeof(cap->bus_info)); + + return 0; +} + +static const struct amvdec_format * +find_format(const struct amvdec_format *fmts, u32 size, u32 pixfmt) +{ + unsigned int i; + + for (i = 0; i < size; i++) { + if (fmts[i].pixfmt == pixfmt) + return &fmts[i]; + } + + return NULL; +} + +static unsigned int +vdec_supports_pixfmt_cap(const struct amvdec_format *fmt_out, u32 pixfmt_cap) +{ + int i; + + for (i = 0; fmt_out->pixfmts_cap[i]; i++) + if (fmt_out->pixfmts_cap[i] == pixfmt_cap) + return 1; + + return 0; +} + +static const struct amvdec_format * +vdec_try_fmt_common(struct amvdec_session *sess, u32 size, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; + const struct amvdec_format *fmts = sess->core->platform->formats; + const struct amvdec_format *fmt_out = NULL; + u32 output_size = 0; + + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + fmt_out = find_format(fmts, size, pixmp->pixelformat); + if (!fmt_out) { + pixmp->pixelformat = V4L2_PIX_FMT_MPEG2; + fmt_out = find_format(fmts, size, pixmp->pixelformat); + } + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + fmt_out = sess->fmt_out; + break; + default: + return NULL; + } + + pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width); + pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height); + output_size = get_output_size(pixmp->width, pixmp->height); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (!pfmt[0].sizeimage) + pfmt[0].sizeimage = sess->src_buffer_size; + pfmt[0].bytesperline = 0; + pixmp->num_planes = 1; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt_out = sess->fmt_out; + if (!vdec_supports_pixfmt_cap(fmt_out, pixmp->pixelformat)) + pixmp->pixelformat = fmt_out->pixfmts_cap[0]; + + memset(pfmt[1].reserved, 0, sizeof(pfmt[1].reserved)); + if (pixmp->pixelformat == V4L2_PIX_FMT_NV12M) { + pfmt[0].sizeimage = output_size; + pfmt[0].bytesperline = ALIGN(pixmp->width, 32); + + pfmt[1].sizeimage = output_size / 2; + pfmt[1].bytesperline = ALIGN(pixmp->width, 32); + pixmp->num_planes = 2; + } else if (pixmp->pixelformat == V4L2_PIX_FMT_YUV420M) { + pfmt[0].sizeimage = output_size; + pfmt[0].bytesperline = ALIGN(pixmp->width, 32); + + pfmt[1].sizeimage = output_size / 4; + pfmt[1].bytesperline = ALIGN(pixmp->width, 32) / 2; + + pfmt[2].sizeimage = output_size / 2; + pfmt[2].bytesperline = ALIGN(pixmp->width, 32) / 2; + pixmp->num_planes = 3; + } + } + + if (pixmp->field == V4L2_FIELD_ANY) + pixmp->field = V4L2_FIELD_NONE; + + return fmt_out; +} + +static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + + vdec_try_fmt_common(sess, sess->core->platform->num_formats, f); + + return 0; +} + +static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->pixelformat = sess->pixfmt_cap; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + pixmp->pixelformat = sess->fmt_out->pixfmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixmp->width = sess->width; + pixmp->height = sess->height; + pixmp->colorspace = sess->colorspace; + pixmp->ycbcr_enc = sess->ycbcr_enc; + pixmp->quantization = sess->quantization; + pixmp->xfer_func = sess->xfer_func; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixmp->width = sess->width; + pixmp->height = sess->height; + } + + vdec_try_fmt_common(sess, sess->core->platform->num_formats, f); + + return 0; +} + +static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + u32 num_formats = sess->core->platform->num_formats; + const struct amvdec_format *fmt_out; + struct v4l2_pix_format_mplane orig_pixmp; + struct v4l2_format format; + u32 pixfmt_out = 0, pixfmt_cap = 0; + + orig_pixmp = *pixmp; + + fmt_out = vdec_try_fmt_common(sess, num_formats, f); + if (!fmt_out) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixfmt_out = pixmp->pixelformat; + pixfmt_cap = sess->pixfmt_cap; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixfmt_cap = pixmp->pixelformat; + pixfmt_out = sess->fmt_out->pixfmt; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_out; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + vdec_try_fmt_common(sess, num_formats, &format); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + sess->width = format.fmt.pix_mp.width; + sess->height = format.fmt.pix_mp.height; + sess->colorspace = pixmp->colorspace; + sess->ycbcr_enc = pixmp->ycbcr_enc; + sess->quantization = pixmp->quantization; + sess->xfer_func = pixmp->xfer_func; + sess->src_buffer_size = pixmp->plane_fmt[0].sizeimage; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_cap; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + vdec_try_fmt_common(sess, num_formats, &format); + + sess->width = format.fmt.pix_mp.width; + sess->height = format.fmt.pix_mp.height; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + sess->fmt_out = fmt_out; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + sess->pixfmt_cap = format.fmt.pix_mp.pixelformat; + + return 0; +} + +static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + const struct vdec_platform *platform = sess->core->platform; + const struct amvdec_format *fmt_out; + + memset(f->reserved, 0, sizeof(f->reserved)); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (f->index >= platform->num_formats) + return -EINVAL; + + fmt_out = &platform->formats[f->index]; + f->pixelformat = fmt_out->pixfmt; + f->flags = fmt_out->flags; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt_out = sess->fmt_out; + if (f->index >= 4 || !fmt_out->pixfmts_cap[f->index]) + return -EINVAL; + + f->pixelformat = fmt_out->pixfmts_cap[f->index]; + } else { + return -EINVAL; + } + + return 0; +} + +static int vdec_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + const struct amvdec_format *formats = sess->core->platform->formats; + const struct amvdec_format *fmt; + u32 num_formats = sess->core->platform->num_formats; + + fmt = find_format(formats, num_formats, fsize->pixel_format); + if (!fmt || fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + + fsize->stepwise.min_width = 256; + fsize->stepwise.max_width = fmt->max_width; + fsize->stepwise.step_width = 1; + fsize->stepwise.min_height = 144; + fsize->stepwise.max_height = fmt->max_height; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int +vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + struct device *dev = sess->core->dev; + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); + if (ret) + return ret; + + if (!(sess->streamon_out & sess->streamon_cap)) + return 0; + + if (cmd->cmd == V4L2_DEC_CMD_START) { + v4l2_m2m_clear_state(sess->m2m_ctx); + sess->should_stop = 0; + return 0; + } + + /* Should not happen */ + if (cmd->cmd != V4L2_DEC_CMD_STOP) + return -EINVAL; + + dev_dbg(dev, "Received V4L2_DEC_CMD_STOP\n"); + + sess->should_stop = 1; + + v4l2_m2m_mark_stopped(sess->m2m_ctx); + + if (codec_ops->drain) { + vdec_wait_inactive(sess); + codec_ops->drain(sess); + } else if (codec_ops->eos_sequence) { + u32 len; + const u8 *data = codec_ops->eos_sequence(&len); + + esparser_queue_eos(sess->core, data, len); + vdec_wait_inactive(sess); + } + + return ret; +} + +static int vdec_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static int vdec_g_pixelaspect(struct file *file, void *fh, int type, + struct v4l2_fract *f) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + *f = sess->pixelaspect; + return 0; +} + +static const struct v4l2_ioctl_ops vdec_ioctl_ops = { + .vidioc_querycap = vdec_querycap, + .vidioc_enum_fmt_vid_cap = vdec_enum_fmt, + .vidioc_enum_fmt_vid_out = vdec_enum_fmt, + .vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vdec_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vdec_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vdec_try_fmt, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_enum_framesizes = vdec_enum_framesizes, + .vidioc_subscribe_event = vdec_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = vdec_decoder_cmd, + .vidioc_g_pixelaspect = vdec_g_pixelaspect, +}; + +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct amvdec_session *sess = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &vdec_vb2_ops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = sess; + src_vq->buf_struct_size = sizeof(struct dummy_buf); + src_vq->min_buffers_needed = 1; + src_vq->dev = sess->core->dev; + src_vq->lock = &sess->lock; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &vdec_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->drv_priv = sess; + dst_vq->buf_struct_size = sizeof(struct dummy_buf); + dst_vq->min_buffers_needed = 1; + dst_vq->dev = sess->core->dev; + dst_vq->lock = &sess->lock; + return vb2_queue_init(dst_vq); +} + +static int vdec_init_ctrls(struct amvdec_session *sess) +{ + struct v4l2_ctrl_handler *ctrl_handler = &sess->ctrl_handler; + int ret; + + ret = v4l2_ctrl_handler_init(ctrl_handler, 1); + if (ret) + return ret; + + sess->ctrl_min_buf_capture = + v4l2_ctrl_new_std(ctrl_handler, NULL, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, + 1); + + ret = ctrl_handler->error; + if (ret) { + v4l2_ctrl_handler_free(ctrl_handler); + return ret; + } + + return 0; +} + +static int vdec_open(struct file *file) +{ + struct amvdec_core *core = video_drvdata(file); + struct device *dev = core->dev; + const struct amvdec_format *formats = core->platform->formats; + struct amvdec_session *sess; + int ret; + + sess = kzalloc(sizeof(*sess), GFP_KERNEL); + if (!sess) + return -ENOMEM; + + sess->core = core; + + sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); + if (IS_ERR(sess->m2m_dev)) { + dev_err(dev, "Fail to v4l2_m2m_init\n"); + ret = PTR_ERR(sess->m2m_dev); + goto err_free_sess; + } + + sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init); + if (IS_ERR(sess->m2m_ctx)) { + dev_err(dev, "Fail to v4l2_m2m_ctx_init\n"); + ret = PTR_ERR(sess->m2m_ctx); + goto err_m2m_release; + } + + ret = vdec_init_ctrls(sess); + if (ret) + goto err_m2m_release; + + sess->pixfmt_cap = formats[0].pixfmts_cap[0]; + sess->fmt_out = &formats[0]; + sess->width = 1280; + sess->height = 720; + sess->pixelaspect.numerator = 1; + sess->pixelaspect.denominator = 1; + sess->src_buffer_size = SZ_1M; + + INIT_LIST_HEAD(&sess->timestamps); + INIT_LIST_HEAD(&sess->bufs_recycle); + INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src); + mutex_init(&sess->lock); + mutex_init(&sess->bufs_recycle_lock); + spin_lock_init(&sess->ts_spinlock); + + v4l2_fh_init(&sess->fh, core->vdev_dec); + sess->fh.ctrl_handler = &sess->ctrl_handler; + v4l2_fh_add(&sess->fh); + sess->fh.m2m_ctx = sess->m2m_ctx; + file->private_data = &sess->fh; + + return 0; + +err_m2m_release: + v4l2_m2m_release(sess->m2m_dev); +err_free_sess: + kfree(sess); + return ret; +} + +static int vdec_close(struct file *file) +{ + struct amvdec_session *sess = + container_of(file->private_data, struct amvdec_session, fh); + + v4l2_m2m_ctx_release(sess->m2m_ctx); + v4l2_m2m_release(sess->m2m_dev); + v4l2_fh_del(&sess->fh); + v4l2_fh_exit(&sess->fh); + + mutex_destroy(&sess->lock); + mutex_destroy(&sess->bufs_recycle_lock); + + kfree(sess); + + return 0; +} + +static const struct v4l2_file_operations vdec_fops = { + .owner = THIS_MODULE, + .open = vdec_open, + .release = vdec_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +static irqreturn_t vdec_isr(int irq, void *data) +{ + struct amvdec_core *core = data; + struct amvdec_session *sess = core->cur_sess; + + sess->last_irq_jiffies = get_jiffies_64(); + + return sess->fmt_out->codec_ops->isr(sess); +} + +static irqreturn_t vdec_threaded_isr(int irq, void *data) +{ + struct amvdec_core *core = data; + struct amvdec_session *sess = core->cur_sess; + + return sess->fmt_out->codec_ops->threaded_isr(sess); +} + +static const struct of_device_id vdec_dt_match[] = { + { .compatible = "amlogic,gxbb-vdec", + .data = &vdec_platform_gxbb }, + { .compatible = "amlogic,gxm-vdec", + .data = &vdec_platform_gxm }, + { .compatible = "amlogic,gxl-vdec", + .data = &vdec_platform_gxl }, + { .compatible = "amlogic,g12a-vdec", + .data = &vdec_platform_g12a }, + { .compatible = "amlogic,sm1-vdec", + .data = &vdec_platform_sm1 }, + {} +}; +MODULE_DEVICE_TABLE(of, vdec_dt_match); + +static int vdec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct video_device *vdev; + struct amvdec_core *core; + const struct of_device_id *of_id; + int irq; + int ret; + + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->dev = dev; + platform_set_drvdata(pdev, core); + + core->dos_base = devm_platform_ioremap_resource_byname(pdev, "dos"); + if (IS_ERR(core->dos_base)) + return PTR_ERR(core->dos_base); + + core->esparser_base = devm_platform_ioremap_resource_byname(pdev, "esparser"); + if (IS_ERR(core->esparser_base)) + return PTR_ERR(core->esparser_base); + + core->regmap_ao = + syscon_regmap_lookup_by_phandle(dev->of_node, + "amlogic,ao-sysctrl"); + if (IS_ERR(core->regmap_ao)) { + dev_err(dev, "Couldn't regmap AO sysctrl\n"); + return PTR_ERR(core->regmap_ao); + } + + core->canvas = meson_canvas_get(dev); + if (IS_ERR(core->canvas)) + return PTR_ERR(core->canvas); + + of_id = of_match_node(vdec_dt_match, dev->of_node); + core->platform = of_id->data; + + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) { + core->vdec_hevcf_clk = devm_clk_get(dev, "vdec_hevcf"); + if (IS_ERR(core->vdec_hevcf_clk)) + return -EPROBE_DEFER; + } + + core->dos_parser_clk = devm_clk_get(dev, "dos_parser"); + if (IS_ERR(core->dos_parser_clk)) + return -EPROBE_DEFER; + + core->dos_clk = devm_clk_get(dev, "dos"); + if (IS_ERR(core->dos_clk)) + return -EPROBE_DEFER; + + core->vdec_1_clk = devm_clk_get(dev, "vdec_1"); + if (IS_ERR(core->vdec_1_clk)) + return -EPROBE_DEFER; + + core->vdec_hevc_clk = devm_clk_get(dev, "vdec_hevc"); + if (IS_ERR(core->vdec_hevc_clk)) + return -EPROBE_DEFER; + + irq = platform_get_irq_byname(pdev, "vdec"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(core->dev, irq, vdec_isr, + vdec_threaded_isr, IRQF_ONESHOT, + "vdec", core); + if (ret) + return ret; + + ret = esparser_init(pdev, core); + if (ret) + return ret; + + ret = v4l2_device_register(dev, &core->v4l2_dev); + if (ret) { + dev_err(dev, "Couldn't register v4l2 device\n"); + return -ENOMEM; + } + + vdev = video_device_alloc(); + if (!vdev) { + ret = -ENOMEM; + goto err_vdev_release; + } + + core->vdev_dec = vdev; + core->dev_dec = dev; + mutex_init(&core->lock); + + strscpy(vdev->name, "meson-video-decoder", sizeof(vdev->name)); + vdev->release = video_device_release; + vdev->fops = &vdec_fops; + vdev->ioctl_ops = &vdec_ioctl_ops; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->v4l2_dev = &core->v4l2_dev; + vdev->lock = &core->lock; + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + + video_set_drvdata(vdev, core); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dev, "Failed registering video device\n"); + goto err_vdev_release; + } + + return 0; + +err_vdev_release: + video_device_release(vdev); + v4l2_device_unregister(&core->v4l2_dev); + return ret; +} + +static void vdec_remove(struct platform_device *pdev) +{ + struct amvdec_core *core = platform_get_drvdata(pdev); + + video_unregister_device(core->vdev_dec); + v4l2_device_unregister(&core->v4l2_dev); +} + +static struct platform_driver meson_vdec_driver = { + .probe = vdec_probe, + .remove_new = vdec_remove, + .driver = { + .name = "meson-vdec", + .of_match_table = vdec_dt_match, + }, +}; +module_platform_driver(meson_vdec_driver); + +MODULE_DESCRIPTION("Meson video decoder driver for GXBB/GXL/GXM/G12/SM1"); +MODULE_AUTHOR("Maxime Jourdan <mjourdan@baylibre.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/meson/vdec/vdec.h b/drivers/staging/media/meson/vdec/vdec.h new file mode 100644 index 0000000000..0906b8fb5c --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec.h @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_CORE_H_ +#define __MESON_VDEC_CORE_H_ + +#include <linux/irqreturn.h> +#include <linux/regmap.h> +#include <linux/list.h> +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <linux/soc/amlogic/meson-canvas.h> + +#include "vdec_platform.h" + +/* 32 buffers in 3-plane YUV420 */ +#define MAX_CANVAS (32 * 3) + +struct amvdec_buffer { + struct list_head list; + struct vb2_buffer *vb; +}; + +/** + * struct amvdec_timestamp - stores a src timestamp along with a VIFIFO offset + * + * @list: used to make lists out of this struct + * @tc: timecode from the v4l2 buffer + * @ts: timestamp from the VB2 buffer + * @offset: offset in the VIFIFO where the associated packet was written + * @flags: flags from the v4l2 buffer + * @used_count: times this timestamp was checked for a match with a dst buffer + */ +struct amvdec_timestamp { + struct list_head list; + struct v4l2_timecode tc; + u64 ts; + u32 offset; + u32 flags; + u32 used_count; +}; + +struct amvdec_session; + +/** + * struct amvdec_core - device parameters, singleton + * + * @dos_base: DOS memory base address + * @esparser_base: PARSER memory base address + * @regmap_ao: regmap for the AO bus + * @dev: core device + * @dev_dec: decoder device + * @platform: platform-specific data + * @canvas: canvas provider reference + * @dos_parser_clk: DOS_PARSER clock + * @dos_clk: DOS clock + * @vdec_1_clk: VDEC_1 clock + * @vdec_hevc_clk: VDEC_HEVC clock + * @vdec_hevcf_clk: VDEC_HEVCF clock + * @esparser_reset: RESET for the PARSER + * @vdev_dec: video device for the decoder + * @v4l2_dev: v4l2 device + * @cur_sess: current decoding session + * @lock: video device lock + */ +struct amvdec_core { + void __iomem *dos_base; + void __iomem *esparser_base; + struct regmap *regmap_ao; + + struct device *dev; + struct device *dev_dec; + const struct vdec_platform *platform; + + struct meson_canvas *canvas; + + struct clk *dos_parser_clk; + struct clk *dos_clk; + struct clk *vdec_1_clk; + struct clk *vdec_hevc_clk; + struct clk *vdec_hevcf_clk; + + struct reset_control *esparser_reset; + + struct video_device *vdev_dec; + struct v4l2_device v4l2_dev; + + struct amvdec_session *cur_sess; + struct mutex lock; +}; + +/** + * struct amvdec_ops - vdec operations + * + * @start: mandatory call when the vdec needs to initialize + * @stop: mandatory call when the vdec needs to stop + * @conf_esparser: mandatory call to let the vdec configure the ESPARSER + * @vififo_level: mandatory call to get the current amount of data + * in the VIFIFO + * @use_offsets: mandatory call. Returns 1 if the VDEC supports vififo offsets + */ +struct amvdec_ops { + int (*start)(struct amvdec_session *sess); + int (*stop)(struct amvdec_session *sess); + void (*conf_esparser)(struct amvdec_session *sess); + u32 (*vififo_level)(struct amvdec_session *sess); +}; + +/** + * struct amvdec_codec_ops - codec operations + * + * @start: mandatory call when the codec needs to initialize + * @stop: mandatory call when the codec needs to stop + * @load_extended_firmware: optional call to load additional firmware bits + * @num_pending_bufs: optional call to get the number of dst buffers on hold + * @can_recycle: optional call to know if the codec is ready to recycle + * a dst buffer + * @recycle: optional call to tell the codec to recycle a dst buffer. Must go + * in pair with @can_recycle + * @drain: optional call if the codec has a custom way of draining + * @resume: optional call to resume after a resolution change + * @eos_sequence: optional call to get an end sequence to send to esparser + * for flush. Mutually exclusive with @drain. + * @isr: mandatory call when the ISR triggers + * @threaded_isr: mandatory call for the threaded ISR + */ +struct amvdec_codec_ops { + int (*start)(struct amvdec_session *sess); + int (*stop)(struct amvdec_session *sess); + int (*load_extended_firmware)(struct amvdec_session *sess, + const u8 *data, u32 len); + u32 (*num_pending_bufs)(struct amvdec_session *sess); + int (*can_recycle)(struct amvdec_core *core); + void (*recycle)(struct amvdec_core *core, u32 buf_idx); + void (*drain)(struct amvdec_session *sess); + void (*resume)(struct amvdec_session *sess); + const u8 * (*eos_sequence)(u32 *len); + irqreturn_t (*isr)(struct amvdec_session *sess); + irqreturn_t (*threaded_isr)(struct amvdec_session *sess); +}; + +/** + * struct amvdec_format - describes one of the OUTPUT (src) format supported + * + * @pixfmt: V4L2 pixel format + * @min_buffers: minimum amount of CAPTURE (dst) buffers + * @max_buffers: maximum amount of CAPTURE (dst) buffers + * @max_width: maximum picture width supported + * @max_height: maximum picture height supported + * @flags: enum flags associated with this pixfmt + * @vdec_ops: the VDEC operations that support this format + * @codec_ops: the codec operations that support this format + * @firmware_path: Path to the firmware that supports this format + * @pixfmts_cap: list of CAPTURE pixel formats available with pixfmt + */ +struct amvdec_format { + u32 pixfmt; + u32 min_buffers; + u32 max_buffers; + u32 max_width; + u32 max_height; + u32 flags; + + struct amvdec_ops *vdec_ops; + struct amvdec_codec_ops *codec_ops; + + char *firmware_path; + u32 pixfmts_cap[4]; +}; + +enum amvdec_status { + STATUS_STOPPED, + STATUS_INIT, + STATUS_RUNNING, + STATUS_NEEDS_RESUME, +}; + +/** + * struct amvdec_session - decoding session parameters + * + * @core: reference to the vdec core struct + * @fh: v4l2 file handle + * @m2m_dev: v4l2 m2m device + * @m2m_ctx: v4l2 m2m context + * @ctrl_handler: V4L2 control handler + * @ctrl_min_buf_capture: V4L2 control V4L2_CID_MIN_BUFFERS_FOR_CAPTURE + * @lock: cap & out queues lock + * @fmt_out: vdec pixel format for the OUTPUT queue + * @pixfmt_cap: V4L2 pixel format for the CAPTURE queue + * @src_buffer_size: size in bytes of the OUTPUT buffers' only plane + * @width: current picture width + * @height: current picture height + * @colorspace: current colorspace + * @ycbcr_enc: current ycbcr_enc + * @quantization: current quantization + * @xfer_func: current transfer function + * @pixelaspect: Pixel Aspect Ratio reported by the decoder + * @esparser_queued_bufs: number of buffers currently queued into ESPARSER + * @esparser_queue_work: work struct for the ESPARSER to process src buffers + * @streamon_cap: stream on flag for capture queue + * @streamon_out: stream on flag for output queue + * @sequence_cap: capture sequence counter + * @sequence_out: output sequence counter + * @should_stop: flag set if userspace signaled EOS via command + * or empty buffer + * @keyframe_found: flag set once a keyframe has been parsed + * @num_dst_bufs: number of destination buffers + * @changed_format: the format changed + * @canvas_alloc: array of all the canvas IDs allocated + * @canvas_num: number of canvas IDs allocated + * @vififo_vaddr: virtual address for the VIFIFO + * @vififo_paddr: physical address for the VIFIFO + * @vififo_size: size of the VIFIFO dma alloc + * @bufs_recycle: list of buffers that need to be recycled + * @bufs_recycle_lock: lock for the bufs_recycle list + * @recycle_thread: task struct for the recycling thread + * @timestamps: chronological list of src timestamps + * @ts_spinlock: spinlock for the timestamps list + * @last_irq_jiffies: tracks last time the vdec triggered an IRQ + * @last_offset: tracks last offset of vififo + * @wrap_count: number of times the vififo wrapped around + * @fw_idx_to_vb2_idx: firmware buffer index to vb2 buffer index + * @status: current decoding status + * @priv: codec private data + */ +struct amvdec_session { + struct amvdec_core *core; + + struct v4l2_fh fh; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *ctrl_min_buf_capture; + struct mutex lock; + + const struct amvdec_format *fmt_out; + u32 pixfmt_cap; + u32 src_buffer_size; + + u32 width; + u32 height; + u32 colorspace; + u8 ycbcr_enc; + u8 quantization; + u8 xfer_func; + + struct v4l2_fract pixelaspect; + + atomic_t esparser_queued_bufs; + struct work_struct esparser_queue_work; + + unsigned int streamon_cap, streamon_out; + unsigned int sequence_cap, sequence_out; + unsigned int should_stop; + unsigned int keyframe_found; + unsigned int num_dst_bufs; + unsigned int changed_format; + + u8 canvas_alloc[MAX_CANVAS]; + u32 canvas_num; + + void *vififo_vaddr; + dma_addr_t vififo_paddr; + u32 vififo_size; + + struct list_head bufs_recycle; + struct mutex bufs_recycle_lock; /* bufs_recycle list lock */ + struct task_struct *recycle_thread; + + struct list_head timestamps; + spinlock_t ts_spinlock; /* timestamp list lock */ + + u64 last_irq_jiffies; + u32 last_offset; + u32 wrap_count; + u32 fw_idx_to_vb2_idx[32]; + + enum amvdec_status status; + void *priv; +}; + +u32 amvdec_get_output_size(struct amvdec_session *sess); + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec_1.c b/drivers/staging/media/meson/vdec/vdec_1.c new file mode 100644 index 0000000000..3fe2de0c93 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_1.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + * + * VDEC_1 is a video decoding block that allows decoding of + * MPEG 1/2/4, H.263, H.264, MJPEG, VC1 + */ + +#include <linux/firmware.h> +#include <linux/clk.h> + +#include "vdec_1.h" +#include "vdec_helpers.h" +#include "dos_regs.h" + +/* AO Registers */ +#define AO_RTI_GEN_PWR_SLEEP0 0xe8 +#define AO_RTI_GEN_PWR_ISO0 0xec + #define GEN_PWR_VDEC_1 (BIT(3) | BIT(2)) + #define GEN_PWR_VDEC_1_SM1 (BIT(1)) + +#define MC_SIZE (4096 * 4) + +static int +vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname) +{ + const struct firmware *fw; + struct amvdec_core *core = sess->core; + struct device *dev = core->dev_dec; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + static void *mc_addr; + static dma_addr_t mc_addr_map; + int ret; + u32 i = 1000; + + ret = request_firmware(&fw, fwname, dev); + if (ret < 0) + return -EINVAL; + + if (fw->size < MC_SIZE) { + dev_err(dev, "Firmware size %zu is too small. Expected %u.\n", + fw->size, MC_SIZE); + ret = -EINVAL; + goto release_firmware; + } + + mc_addr = dma_alloc_coherent(core->dev, MC_SIZE, + &mc_addr_map, GFP_KERNEL); + if (!mc_addr) { + ret = -ENOMEM; + goto release_firmware; + } + + memcpy(mc_addr, fw->data, MC_SIZE); + + amvdec_write_dos(core, MPSR, 0); + amvdec_write_dos(core, CPSR, 0); + + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31)); + + amvdec_write_dos(core, IMEM_DMA_ADR, mc_addr_map); + amvdec_write_dos(core, IMEM_DMA_COUNT, MC_SIZE / 4); + amvdec_write_dos(core, IMEM_DMA_CTRL, (0x8000 | (7 << 16))); + + while (--i && amvdec_read_dos(core, IMEM_DMA_CTRL) & 0x8000); + + if (i == 0) { + dev_err(dev, "Firmware load fail (DMA hang?)\n"); + ret = -EINVAL; + goto free_mc; + } + + if (codec_ops->load_extended_firmware) + ret = codec_ops->load_extended_firmware(sess, + fw->data + MC_SIZE, + fw->size - MC_SIZE); + +free_mc: + dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map); +release_firmware: + release_firmware(fw); + return ret; +} + +static int vdec_1_stbuf_power_up(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + amvdec_write_dos(core, VLD_MEM_VIFIFO_CONTROL, 0); + amvdec_write_dos(core, VLD_MEM_VIFIFO_WRAP_COUNT, 0); + amvdec_write_dos(core, POWER_CTL_VLD, BIT(4)); + + amvdec_write_dos(core, VLD_MEM_VIFIFO_START_PTR, sess->vififo_paddr); + amvdec_write_dos(core, VLD_MEM_VIFIFO_CURR_PTR, sess->vififo_paddr); + amvdec_write_dos(core, VLD_MEM_VIFIFO_END_PTR, + sess->vififo_paddr + sess->vififo_size - 8); + + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1); + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1); + + amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, MEM_BUFCTRL_MANUAL); + amvdec_write_dos(core, VLD_MEM_VIFIFO_WP, sess->vififo_paddr); + + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); + + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, + (0x11 << MEM_FIFO_CNT_BIT) | MEM_FILL_ON_LEVEL | + MEM_CTRL_FILL_EN | MEM_CTRL_EMPTY_EN); + + return 0; +} + +static void vdec_1_conf_esparser(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + /* VDEC_1 specific ESPARSER stuff */ + amvdec_write_dos(core, DOS_GEN_CTRL0, 0); + amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1); +} + +static u32 vdec_1_vififo_level(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + return amvdec_read_dos(core, VLD_MEM_VIFIFO_LEVEL); +} + +static int vdec_1_stop(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + amvdec_write_dos(core, MPSR, 0); + amvdec_write_dos(core, CPSR, 0); + amvdec_write_dos(core, ASSIST_MBOX1_MASK, 0); + + amvdec_write_dos(core, DOS_SW_RESET0, BIT(12) | BIT(11)); + amvdec_write_dos(core, DOS_SW_RESET0, 0); + amvdec_read_dos(core, DOS_SW_RESET0); + + /* enable vdec1 isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_1_SM1, GEN_PWR_VDEC_1_SM1); + else + regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0xc0); + /* power off vdec1 memories */ + amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0xffffffff); + /* power off vdec1 */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_1_SM1, GEN_PWR_VDEC_1_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_1, GEN_PWR_VDEC_1); + + clk_disable_unprepare(core->vdec_1_clk); + + if (sess->priv) + codec_ops->stop(sess); + + return 0; +} + +static int vdec_1_start(struct amvdec_session *sess) +{ + int ret; + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + /* Configure the vdec clk to the maximum available */ + clk_set_rate(core->vdec_1_clk, 666666666); + ret = clk_prepare_enable(core->vdec_1_clk); + if (ret) + return ret; + + /* Enable power for VDEC_1 */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_1_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_1, 0); + usleep_range(10, 20); + + /* Reset VDEC1 */ + amvdec_write_dos(core, DOS_SW_RESET0, 0xfffffffc); + amvdec_write_dos(core, DOS_SW_RESET0, 0x00000000); + + amvdec_write_dos(core, DOS_GCLK_EN0, 0x3ff); + + /* enable VDEC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0); + /* Remove VDEC1 Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_1_SM1, 0); + else + regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0); + /* Reset DOS top registers */ + amvdec_write_dos(core, DOS_VDEC_MCRCC_STALL_CTRL, 0); + + amvdec_write_dos(core, GCLK_EN, 0x3ff); + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31)); + + vdec_1_stbuf_power_up(sess); + + ret = vdec_1_load_firmware(sess, sess->fmt_out->firmware_path); + if (ret) + goto stop; + + ret = codec_ops->start(sess); + if (ret) + goto stop; + + /* Enable IRQ */ + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1); + amvdec_write_dos(core, ASSIST_MBOX1_MASK, 1); + + /* Enable 2-plane output */ + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M) + amvdec_write_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17)); + else + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17)); + + /* Enable firmware processor */ + amvdec_write_dos(core, MPSR, 1); + /* Let the firmware settle */ + usleep_range(10, 20); + + return 0; + +stop: + vdec_1_stop(sess); + return ret; +} + +struct amvdec_ops vdec_1_ops = { + .start = vdec_1_start, + .stop = vdec_1_stop, + .conf_esparser = vdec_1_conf_esparser, + .vififo_level = vdec_1_vififo_level, +}; diff --git a/drivers/staging/media/meson/vdec/vdec_1.h b/drivers/staging/media/meson/vdec/vdec_1.h new file mode 100644 index 0000000000..042d930c40 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_1.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_VDEC_1_H_ +#define __MESON_VDEC_VDEC_1_H_ + +#include "vdec.h" + +extern struct amvdec_ops vdec_1_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec_helpers.c b/drivers/staging/media/meson/vdec/vdec_helpers.c new file mode 100644 index 0000000000..7d2a756532 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_helpers.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <linux/gcd.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> + +#include "vdec_helpers.h" + +#define NUM_CANVAS_NV12 2 +#define NUM_CANVAS_YUV420 3 + +u32 amvdec_read_dos(struct amvdec_core *core, u32 reg) +{ + return readl_relaxed(core->dos_base + reg); +} +EXPORT_SYMBOL_GPL(amvdec_read_dos); + +void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val) +{ + writel_relaxed(val, core->dos_base + reg); +} +EXPORT_SYMBOL_GPL(amvdec_write_dos); + +void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val) +{ + amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) | val); +} +EXPORT_SYMBOL_GPL(amvdec_write_dos_bits); + +void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val) +{ + amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) & ~val); +} +EXPORT_SYMBOL_GPL(amvdec_clear_dos_bits); + +u32 amvdec_read_parser(struct amvdec_core *core, u32 reg) +{ + return readl_relaxed(core->esparser_base + reg); +} +EXPORT_SYMBOL_GPL(amvdec_read_parser); + +void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val) +{ + writel_relaxed(val, core->esparser_base + reg); +} +EXPORT_SYMBOL_GPL(amvdec_write_parser); + +/* 4 KiB per 64x32 block */ +u32 amvdec_am21c_body_size(u32 width, u32 height) +{ + u32 width_64 = ALIGN(width, 64) / 64; + u32 height_32 = ALIGN(height, 32) / 32; + + return SZ_4K * width_64 * height_32; +} +EXPORT_SYMBOL_GPL(amvdec_am21c_body_size); + +/* 32 bytes per 128x64 block */ +u32 amvdec_am21c_head_size(u32 width, u32 height) +{ + u32 width_128 = ALIGN(width, 128) / 128; + u32 height_64 = ALIGN(height, 64) / 64; + + return 32 * width_128 * height_64; +} +EXPORT_SYMBOL_GPL(amvdec_am21c_head_size); + +u32 amvdec_am21c_size(u32 width, u32 height) +{ + return ALIGN(amvdec_am21c_body_size(width, height) + + amvdec_am21c_head_size(width, height), SZ_64K); +} +EXPORT_SYMBOL_GPL(amvdec_am21c_size); + +static int canvas_alloc(struct amvdec_session *sess, u8 *canvas_id) +{ + int ret; + + if (sess->canvas_num >= MAX_CANVAS) { + dev_err(sess->core->dev, "Reached max number of canvas\n"); + return -ENOMEM; + } + + ret = meson_canvas_alloc(sess->core->canvas, canvas_id); + if (ret) + return ret; + + sess->canvas_alloc[sess->canvas_num++] = *canvas_id; + return 0; +} + +static int set_canvas_yuv420m(struct amvdec_session *sess, + struct vb2_buffer *vb, u32 width, + u32 height, u32 reg) +{ + struct amvdec_core *core = sess->core; + u8 canvas_id[NUM_CANVAS_YUV420]; /* Y U V */ + dma_addr_t buf_paddr[NUM_CANVAS_YUV420]; /* Y U V */ + int ret, i; + + for (i = 0; i < NUM_CANVAS_YUV420; ++i) { + ret = canvas_alloc(sess, &canvas_id[i]); + if (ret) + return ret; + + buf_paddr[i] = + vb2_dma_contig_plane_dma_addr(vb, i); + } + + /* Y plane */ + meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0], + width, height, MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + + /* U plane */ + meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1], + width / 2, height / 2, MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + + /* V plane */ + meson_canvas_config(core->canvas, canvas_id[2], buf_paddr[2], + width / 2, height / 2, MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + + amvdec_write_dos(core, reg, + ((canvas_id[2]) << 16) | + ((canvas_id[1]) << 8) | + (canvas_id[0])); + + return 0; +} + +static int set_canvas_nv12m(struct amvdec_session *sess, + struct vb2_buffer *vb, u32 width, + u32 height, u32 reg) +{ + struct amvdec_core *core = sess->core; + u8 canvas_id[NUM_CANVAS_NV12]; /* Y U/V */ + dma_addr_t buf_paddr[NUM_CANVAS_NV12]; /* Y U/V */ + int ret, i; + + for (i = 0; i < NUM_CANVAS_NV12; ++i) { + ret = canvas_alloc(sess, &canvas_id[i]); + if (ret) + return ret; + + buf_paddr[i] = + vb2_dma_contig_plane_dma_addr(vb, i); + } + + /* Y plane */ + meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0], + width, height, MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + + /* U/V plane */ + meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1], + width, height / 2, MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + + amvdec_write_dos(core, reg, + ((canvas_id[1]) << 16) | + ((canvas_id[1]) << 8) | + (canvas_id[0])); + + return 0; +} + +int amvdec_set_canvases(struct amvdec_session *sess, + u32 reg_base[], u32 reg_num[]) +{ + struct v4l2_m2m_buffer *buf; + u32 pixfmt = sess->pixfmt_cap; + u32 width = ALIGN(sess->width, 32); + u32 height = ALIGN(sess->height, 32); + u32 reg_cur; + u32 reg_num_cur = 0; + u32 reg_base_cur = 0; + int i = 0; + int ret; + + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) { + if (!reg_base[reg_base_cur]) + return -EINVAL; + + reg_cur = reg_base[reg_base_cur] + reg_num_cur * 4; + + switch (pixfmt) { + case V4L2_PIX_FMT_NV12M: + ret = set_canvas_nv12m(sess, &buf->vb.vb2_buf, width, + height, reg_cur); + if (ret) + return ret; + break; + case V4L2_PIX_FMT_YUV420M: + ret = set_canvas_yuv420m(sess, &buf->vb.vb2_buf, width, + height, reg_cur); + if (ret) + return ret; + break; + default: + dev_err(sess->core->dev, "Unsupported pixfmt %08X\n", + pixfmt); + return -EINVAL; + } + + reg_num_cur++; + if (reg_num_cur >= reg_num[reg_base_cur]) { + reg_base_cur++; + reg_num_cur = 0; + } + + sess->fw_idx_to_vb2_idx[i++] = buf->vb.vb2_buf.index; + } + + return 0; +} +EXPORT_SYMBOL_GPL(amvdec_set_canvases); + +int amvdec_add_ts(struct amvdec_session *sess, u64 ts, + struct v4l2_timecode tc, u32 offset, u32 vbuf_flags) +{ + struct amvdec_timestamp *new_ts; + unsigned long flags; + + new_ts = kzalloc(sizeof(*new_ts), GFP_KERNEL); + if (!new_ts) + return -ENOMEM; + + new_ts->ts = ts; + new_ts->tc = tc; + new_ts->offset = offset; + new_ts->flags = vbuf_flags; + + spin_lock_irqsave(&sess->ts_spinlock, flags); + list_add_tail(&new_ts->list, &sess->timestamps); + spin_unlock_irqrestore(&sess->ts_spinlock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(amvdec_add_ts); + +void amvdec_remove_ts(struct amvdec_session *sess, u64 ts) +{ + struct amvdec_timestamp *tmp; + unsigned long flags; + + spin_lock_irqsave(&sess->ts_spinlock, flags); + list_for_each_entry(tmp, &sess->timestamps, list) { + if (tmp->ts == ts) { + list_del(&tmp->list); + kfree(tmp); + goto unlock; + } + } + dev_warn(sess->core->dev_dec, + "Couldn't remove buffer with timestamp %llu from list\n", ts); + +unlock: + spin_unlock_irqrestore(&sess->ts_spinlock, flags); +} +EXPORT_SYMBOL_GPL(amvdec_remove_ts); + +static void dst_buf_done(struct amvdec_session *sess, + struct vb2_v4l2_buffer *vbuf, + u32 field, u64 timestamp, + struct v4l2_timecode timecode, u32 flags) +{ + struct device *dev = sess->core->dev_dec; + u32 output_size = amvdec_get_output_size(sess); + + switch (sess->pixfmt_cap) { + case V4L2_PIX_FMT_NV12M: + vb2_set_plane_payload(&vbuf->vb2_buf, 0, output_size); + vb2_set_plane_payload(&vbuf->vb2_buf, 1, output_size / 2); + break; + case V4L2_PIX_FMT_YUV420M: + vb2_set_plane_payload(&vbuf->vb2_buf, 0, output_size); + vb2_set_plane_payload(&vbuf->vb2_buf, 1, output_size / 4); + vb2_set_plane_payload(&vbuf->vb2_buf, 2, output_size / 4); + break; + } + + vbuf->vb2_buf.timestamp = timestamp; + vbuf->sequence = sess->sequence_cap++; + vbuf->flags = flags; + vbuf->timecode = timecode; + + if (sess->should_stop && + atomic_read(&sess->esparser_queued_bufs) <= 1) { + const struct v4l2_event ev = { .type = V4L2_EVENT_EOS }; + + dev_dbg(dev, "Signaling EOS, sequence_cap = %u\n", + sess->sequence_cap - 1); + v4l2_event_queue_fh(&sess->fh, &ev); + vbuf->flags |= V4L2_BUF_FLAG_LAST; + } else if (sess->status == STATUS_NEEDS_RESUME) { + /* Mark LAST for drained show frames during a source change */ + vbuf->flags |= V4L2_BUF_FLAG_LAST; + sess->sequence_cap = 0; + } else if (sess->should_stop) + dev_dbg(dev, "should_stop, %u bufs remain\n", + atomic_read(&sess->esparser_queued_bufs)); + + dev_dbg(dev, "Buffer %u done, ts = %llu, flags = %08X\n", + vbuf->vb2_buf.index, timestamp, flags); + vbuf->field = field; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); + + /* Buffer done probably means the vififo got freed */ + schedule_work(&sess->esparser_queue_work); +} + +void amvdec_dst_buf_done(struct amvdec_session *sess, + struct vb2_v4l2_buffer *vbuf, u32 field) +{ + struct device *dev = sess->core->dev_dec; + struct amvdec_timestamp *tmp; + struct list_head *timestamps = &sess->timestamps; + struct v4l2_timecode timecode; + u64 timestamp; + u32 vbuf_flags; + unsigned long flags; + + spin_lock_irqsave(&sess->ts_spinlock, flags); + if (list_empty(timestamps)) { + dev_err(dev, "Buffer %u done but list is empty\n", + vbuf->vb2_buf.index); + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&sess->ts_spinlock, flags); + return; + } + + tmp = list_first_entry(timestamps, struct amvdec_timestamp, list); + timestamp = tmp->ts; + timecode = tmp->tc; + vbuf_flags = tmp->flags; + list_del(&tmp->list); + kfree(tmp); + spin_unlock_irqrestore(&sess->ts_spinlock, flags); + + dst_buf_done(sess, vbuf, field, timestamp, timecode, vbuf_flags); + atomic_dec(&sess->esparser_queued_bufs); +} +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done); + +void amvdec_dst_buf_done_offset(struct amvdec_session *sess, + struct vb2_v4l2_buffer *vbuf, + u32 offset, u32 field, bool allow_drop) +{ + struct device *dev = sess->core->dev_dec; + struct amvdec_timestamp *match = NULL; + struct amvdec_timestamp *tmp, *n; + struct v4l2_timecode timecode = { 0 }; + u64 timestamp = 0; + u32 vbuf_flags = 0; + unsigned long flags; + + spin_lock_irqsave(&sess->ts_spinlock, flags); + + /* Look for our vififo offset to get the corresponding timestamp. */ + list_for_each_entry_safe(tmp, n, &sess->timestamps, list) { + if (tmp->offset > offset) { + /* + * Delete any record that remained unused for 32 match + * checks + */ + if (tmp->used_count++ >= 32) { + list_del(&tmp->list); + kfree(tmp); + } + break; + } + + match = tmp; + } + + if (!match) { + dev_err(dev, "Buffer %u done but can't match offset (%08X)\n", + vbuf->vb2_buf.index, offset); + } else { + timestamp = match->ts; + timecode = match->tc; + vbuf_flags = match->flags; + list_del(&match->list); + kfree(match); + } + spin_unlock_irqrestore(&sess->ts_spinlock, flags); + + dst_buf_done(sess, vbuf, field, timestamp, timecode, vbuf_flags); + if (match) + atomic_dec(&sess->esparser_queued_bufs); +} +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done_offset); + +void amvdec_dst_buf_done_idx(struct amvdec_session *sess, + u32 buf_idx, u32 offset, u32 field) +{ + struct vb2_v4l2_buffer *vbuf; + struct device *dev = sess->core->dev_dec; + + vbuf = v4l2_m2m_dst_buf_remove_by_idx(sess->m2m_ctx, + sess->fw_idx_to_vb2_idx[buf_idx]); + + if (!vbuf) { + dev_err(dev, + "Buffer %u done but it doesn't exist in m2m_ctx\n", + buf_idx); + return; + } + + if (offset != -1) + amvdec_dst_buf_done_offset(sess, vbuf, offset, field, true); + else + amvdec_dst_buf_done(sess, vbuf, field); +} +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done_idx); + +void amvdec_set_par_from_dar(struct amvdec_session *sess, + u32 dar_num, u32 dar_den) +{ + u32 div; + + sess->pixelaspect.numerator = sess->height * dar_num; + sess->pixelaspect.denominator = sess->width * dar_den; + div = gcd(sess->pixelaspect.numerator, sess->pixelaspect.denominator); + sess->pixelaspect.numerator /= div; + sess->pixelaspect.denominator /= div; +} +EXPORT_SYMBOL_GPL(amvdec_set_par_from_dar); + +void amvdec_src_change(struct amvdec_session *sess, u32 width, + u32 height, u32 dpb_size) +{ + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION }; + + v4l2_ctrl_s_ctrl(sess->ctrl_min_buf_capture, dpb_size); + + /* + * Check if the capture queue is already configured well for our + * usecase. If so, keep decoding with it and do not send the event + */ + if (sess->streamon_cap && + sess->width == width && + sess->height == height && + dpb_size <= sess->num_dst_bufs) { + sess->fmt_out->codec_ops->resume(sess); + return; + } + + sess->changed_format = 0; + sess->width = width; + sess->height = height; + sess->status = STATUS_NEEDS_RESUME; + + dev_dbg(sess->core->dev, "Res. changed (%ux%u), DPB size %u\n", + width, height, dpb_size); + v4l2_event_queue_fh(&sess->fh, &ev); +} +EXPORT_SYMBOL_GPL(amvdec_src_change); + +void amvdec_abort(struct amvdec_session *sess) +{ + dev_info(sess->core->dev, "Aborting decoding session!\n"); + vb2_queue_error(&sess->m2m_ctx->cap_q_ctx.q); + vb2_queue_error(&sess->m2m_ctx->out_q_ctx.q); +} +EXPORT_SYMBOL_GPL(amvdec_abort); diff --git a/drivers/staging/media/meson/vdec/vdec_helpers.h b/drivers/staging/media/meson/vdec/vdec_helpers.h new file mode 100644 index 0000000000..4bf3e61d08 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_helpers.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_HELPERS_H_ +#define __MESON_VDEC_HELPERS_H_ + +#include "vdec.h" + +/** + * amvdec_set_canvases() - Map VB2 buffers to canvases + * + * @sess: current session + * @reg_base: Registry bases of where to write the canvas indexes + * @reg_num: number of contiguous registers after each reg_base (including it) + */ +int amvdec_set_canvases(struct amvdec_session *sess, + u32 reg_base[], u32 reg_num[]); + +/* Helpers to read/write to the various IPs (DOS, PARSER) */ +u32 amvdec_read_dos(struct amvdec_core *core, u32 reg); +void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val); +void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val); +void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val); +u32 amvdec_read_parser(struct amvdec_core *core, u32 reg); +void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val); + +u32 amvdec_am21c_body_size(u32 width, u32 height); +u32 amvdec_am21c_head_size(u32 width, u32 height); +u32 amvdec_am21c_size(u32 width, u32 height); + +/** + * amvdec_dst_buf_done_idx() - Signal that a buffer is done decoding + * + * @sess: current session + * @buf_idx: hardware buffer index + * @offset: VIFIFO bitstream offset corresponding to the buffer + * @field: V4L2 interlaced field + */ +void amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx, + u32 offset, u32 field); +void amvdec_dst_buf_done(struct amvdec_session *sess, + struct vb2_v4l2_buffer *vbuf, u32 field); +void amvdec_dst_buf_done_offset(struct amvdec_session *sess, + struct vb2_v4l2_buffer *vbuf, + u32 offset, u32 field, bool allow_drop); + +/** + * amvdec_add_ts() - Add a timestamp to the list + * + * @sess: current session + * @ts: timestamp to add + * @tc: timecode to add + * @offset: offset in the VIFIFO where the associated packet was written + * @flags: the vb2_v4l2_buffer flags + */ +int amvdec_add_ts(struct amvdec_session *sess, u64 ts, + struct v4l2_timecode tc, u32 offset, u32 flags); +void amvdec_remove_ts(struct amvdec_session *sess, u64 ts); + +/** + * amvdec_set_par_from_dar() - Set Pixel Aspect Ratio from Display Aspect Ratio + * + * @sess: current session + * @dar_num: numerator of the DAR + * @dar_den: denominator of the DAR + */ +void amvdec_set_par_from_dar(struct amvdec_session *sess, + u32 dar_num, u32 dar_den); + +/** + * amvdec_src_change() - Notify new resolution/DPB size to the core + * + * @sess: current session + * @width: picture width detected by the hardware + * @height: picture height detected by the hardware + * @dpb_size: Decoded Picture Buffer size (= amount of buffers for decoding) + */ +void amvdec_src_change(struct amvdec_session *sess, u32 width, + u32 height, u32 dpb_size); + +/** + * amvdec_abort() - Abort the current decoding session + * + * @sess: current session + */ +void amvdec_abort(struct amvdec_session *sess); +#endif diff --git a/drivers/staging/media/meson/vdec/vdec_hevc.c b/drivers/staging/media/meson/vdec/vdec_hevc.c new file mode 100644 index 0000000000..afced435c9 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_hevc.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Maxime Jourdan <maxi.jourdan@wanadoo.fr> + * + * VDEC_HEVC is a video decoding block that allows decoding of + * HEVC, VP9 + */ + +#include <linux/firmware.h> +#include <linux/clk.h> + +#include "vdec_1.h" +#include "vdec_helpers.h" +#include "vdec_hevc.h" +#include "hevc_regs.h" +#include "dos_regs.h" + +/* AO Registers */ +#define AO_RTI_GEN_PWR_SLEEP0 0xe8 +#define AO_RTI_GEN_PWR_ISO0 0xec + #define GEN_PWR_VDEC_HEVC (BIT(7) | BIT(6)) + #define GEN_PWR_VDEC_HEVC_SM1 (BIT(2)) + +#define MC_SIZE (4096 * 4) + +static int vdec_hevc_load_firmware(struct amvdec_session *sess, + const char *fwname) +{ + struct amvdec_core *core = sess->core; + struct device *dev = core->dev_dec; + const struct firmware *fw; + static void *mc_addr; + static dma_addr_t mc_addr_map; + int ret; + u32 i = 100; + + ret = request_firmware(&fw, fwname, dev); + if (ret < 0) { + dev_err(dev, "Unable to request firmware %s\n", fwname); + return ret; + } + + if (fw->size < MC_SIZE) { + dev_err(dev, "Firmware size %zu is too small. Expected %u.\n", + fw->size, MC_SIZE); + ret = -EINVAL; + goto release_firmware; + } + + mc_addr = dma_alloc_coherent(core->dev, MC_SIZE, &mc_addr_map, + GFP_KERNEL); + if (!mc_addr) { + ret = -ENOMEM; + goto release_firmware; + } + + memcpy(mc_addr, fw->data, MC_SIZE); + + amvdec_write_dos(core, HEVC_MPSR, 0); + amvdec_write_dos(core, HEVC_CPSR, 0); + + amvdec_write_dos(core, HEVC_IMEM_DMA_ADR, mc_addr_map); + amvdec_write_dos(core, HEVC_IMEM_DMA_COUNT, MC_SIZE / 4); + amvdec_write_dos(core, HEVC_IMEM_DMA_CTRL, (0x8000 | (7 << 16))); + + while (i && (readl(core->dos_base + HEVC_IMEM_DMA_CTRL) & 0x8000)) + i--; + + if (i == 0) { + dev_err(dev, "Firmware load fail (DMA hang?)\n"); + ret = -ENODEV; + } + + dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map); +release_firmware: + release_firmware(fw); + return ret; +} + +static void vdec_hevc_stbuf_init(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) & ~1); + amvdec_write_dos(core, HEVC_STREAM_START_ADDR, sess->vififo_paddr); + amvdec_write_dos(core, HEVC_STREAM_END_ADDR, + sess->vififo_paddr + sess->vififo_size); + amvdec_write_dos(core, HEVC_STREAM_RD_PTR, sess->vififo_paddr); + amvdec_write_dos(core, HEVC_STREAM_WR_PTR, sess->vififo_paddr); +} + +/* VDEC_HEVC specific ESPARSER configuration */ +static void vdec_hevc_conf_esparser(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + + /* set vififo_vbuf_rp_sel=>vdec_hevc */ + amvdec_write_dos(core, DOS_GEN_CTRL0, 3 << 1); + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) | BIT(3)); + amvdec_write_dos(core, HEVC_STREAM_CONTROL, + amvdec_read_dos(core, HEVC_STREAM_CONTROL) | 1); + amvdec_write_dos(core, HEVC_STREAM_FIFO_CTL, + amvdec_read_dos(core, HEVC_STREAM_FIFO_CTL) | BIT(29)); +} + +static u32 vdec_hevc_vififo_level(struct amvdec_session *sess) +{ + return readl_relaxed(sess->core->dos_base + HEVC_STREAM_LEVEL); +} + +static int vdec_hevc_stop(struct amvdec_session *sess) +{ + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + /* Disable interrupt */ + amvdec_write_dos(core, HEVC_ASSIST_MBOX1_MASK, 0); + /* Disable firmware processor */ + amvdec_write_dos(core, HEVC_MPSR, 0); + + if (sess->priv) + codec_ops->stop(sess); + + /* Enable VDEC_HEVC Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_HEVC_SM1, + GEN_PWR_VDEC_HEVC_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + 0xc00, 0xc00); + + /* VDEC_HEVC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_HEVC, 0xffffffffUL); + + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC_SM1, + GEN_PWR_VDEC_HEVC_SM1); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC, GEN_PWR_VDEC_HEVC); + + clk_disable_unprepare(core->vdec_hevc_clk); + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) + clk_disable_unprepare(core->vdec_hevcf_clk); + + return 0; +} + +static int vdec_hevc_start(struct amvdec_session *sess) +{ + int ret; + struct amvdec_core *core = sess->core; + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; + + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) { + clk_set_rate(core->vdec_hevcf_clk, 666666666); + ret = clk_prepare_enable(core->vdec_hevcf_clk); + if (ret) + return ret; + } + + clk_set_rate(core->vdec_hevc_clk, 666666666); + ret = clk_prepare_enable(core->vdec_hevc_clk); + if (ret) { + if (core->platform->revision == VDEC_REVISION_G12A || + core->platform->revision == VDEC_REVISION_SM1) + clk_disable_unprepare(core->vdec_hevcf_clk); + return ret; + } + + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VDEC_HEVC, 0); + usleep_range(10, 20); + + /* Reset VDEC_HEVC*/ + amvdec_write_dos(core, DOS_SW_RESET3, 0xffffffff); + amvdec_write_dos(core, DOS_SW_RESET3, 0x00000000); + + amvdec_write_dos(core, DOS_GCLK_EN3, 0xffffffff); + + /* VDEC_HEVC Memories */ + amvdec_write_dos(core, DOS_MEM_PD_HEVC, 0x00000000); + + /* Remove VDEC_HEVC Isolation */ + if (core->platform->revision == VDEC_REVISION_SM1) + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + GEN_PWR_VDEC_HEVC_SM1, 0); + else + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, + 0xc00, 0); + + amvdec_write_dos(core, DOS_SW_RESET3, 0xffffffff); + amvdec_write_dos(core, DOS_SW_RESET3, 0x00000000); + + vdec_hevc_stbuf_init(sess); + + ret = vdec_hevc_load_firmware(sess, sess->fmt_out->firmware_path); + if (ret) + goto stop; + + ret = codec_ops->start(sess); + if (ret) + goto stop; + + amvdec_write_dos(core, DOS_SW_RESET3, BIT(12) | BIT(11)); + amvdec_write_dos(core, DOS_SW_RESET3, 0); + amvdec_read_dos(core, DOS_SW_RESET3); + + amvdec_write_dos(core, HEVC_MPSR, 1); + /* Let the firmware settle */ + usleep_range(10, 20); + + return 0; + +stop: + vdec_hevc_stop(sess); + return ret; +} + +struct amvdec_ops vdec_hevc_ops = { + .start = vdec_hevc_start, + .stop = vdec_hevc_stop, + .conf_esparser = vdec_hevc_conf_esparser, + .vififo_level = vdec_hevc_vififo_level, +}; diff --git a/drivers/staging/media/meson/vdec/vdec_hevc.h b/drivers/staging/media/meson/vdec/vdec_hevc.h new file mode 100644 index 0000000000..cd576a73a9 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_hevc.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Maxime Jourdan <maxi.jourdan@wanadoo.fr> + */ + +#ifndef __MESON_VDEC_VDEC_HEVC_H_ +#define __MESON_VDEC_VDEC_HEVC_H_ + +#include "vdec.h" + +extern struct amvdec_ops vdec_hevc_ops; + +#endif diff --git a/drivers/staging/media/meson/vdec/vdec_platform.c b/drivers/staging/media/meson/vdec/vdec_platform.c new file mode 100644 index 0000000000..70c9fd7c8b --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_platform.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include "vdec_platform.h" +#include "vdec.h" + +#include "vdec_1.h" +#include "vdec_hevc.h" +#include "codec_mpeg12.h" +#include "codec_h264.h" +#include "codec_vp9.h" + +static const struct amvdec_format vdec_formats_gxbb[] = { + { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/gxbb_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, +}; + +static const struct amvdec_format vdec_formats_gxl[] = { + { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/gxl_vp9.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/gxl_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, +}; + +static const struct amvdec_format vdec_formats_gxm[] = { + { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/gxl_vp9.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/gxm_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, +}; + +static const struct amvdec_format vdec_formats_g12a[] = { + { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/g12a_vp9.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/g12a_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, +}; + +static const struct amvdec_format vdec_formats_sm1[] = { + { + .pixfmt = V4L2_PIX_FMT_VP9, + .min_buffers = 16, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_hevc_ops, + .codec_ops = &codec_vp9_ops, + .firmware_path = "meson/vdec/sm1_vp9_mmu.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .min_buffers = 2, + .max_buffers = 24, + .max_width = 3840, + .max_height = 2160, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_h264_ops, + .firmware_path = "meson/vdec/g12a_h264.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED | + V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG1, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .min_buffers = 8, + .max_buffers = 8, + .max_width = 1920, + .max_height = 1080, + .vdec_ops = &vdec_1_ops, + .codec_ops = &codec_mpeg12_ops, + .firmware_path = "meson/vdec/gxl_mpeg12.bin", + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 }, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }, +}; + +const struct vdec_platform vdec_platform_gxbb = { + .formats = vdec_formats_gxbb, + .num_formats = ARRAY_SIZE(vdec_formats_gxbb), + .revision = VDEC_REVISION_GXBB, +}; + +const struct vdec_platform vdec_platform_gxl = { + .formats = vdec_formats_gxl, + .num_formats = ARRAY_SIZE(vdec_formats_gxl), + .revision = VDEC_REVISION_GXL, +}; + +const struct vdec_platform vdec_platform_gxm = { + .formats = vdec_formats_gxm, + .num_formats = ARRAY_SIZE(vdec_formats_gxm), + .revision = VDEC_REVISION_GXM, +}; + +const struct vdec_platform vdec_platform_g12a = { + .formats = vdec_formats_g12a, + .num_formats = ARRAY_SIZE(vdec_formats_g12a), + .revision = VDEC_REVISION_G12A, +}; + +const struct vdec_platform vdec_platform_sm1 = { + .formats = vdec_formats_sm1, + .num_formats = ARRAY_SIZE(vdec_formats_sm1), + .revision = VDEC_REVISION_SM1, +}; + +MODULE_FIRMWARE("meson/vdec/g12a_h264.bin"); +MODULE_FIRMWARE("meson/vdec/g12a_vp9.bin"); +MODULE_FIRMWARE("meson/vdec/gxbb_h264.bin"); +MODULE_FIRMWARE("meson/vdec/gxl_h264.bin"); +MODULE_FIRMWARE("meson/vdec/gxl_mpeg12.bin"); +MODULE_FIRMWARE("meson/vdec/gxl_vp9.bin"); +MODULE_FIRMWARE("meson/vdec/gxm_h264.bin"); +MODULE_FIRMWARE("meson/vdec/sm1_vp9_mmu.bin"); diff --git a/drivers/staging/media/meson/vdec/vdec_platform.h b/drivers/staging/media/meson/vdec/vdec_platform.h new file mode 100644 index 0000000000..731877a771 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec_platform.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#ifndef __MESON_VDEC_PLATFORM_H_ +#define __MESON_VDEC_PLATFORM_H_ + +#include "vdec.h" + +struct amvdec_format; + +enum vdec_revision { + VDEC_REVISION_GXBB, + VDEC_REVISION_GXL, + VDEC_REVISION_GXM, + VDEC_REVISION_G12A, + VDEC_REVISION_SM1, +}; + +struct vdec_platform { + const struct amvdec_format *formats; + const u32 num_formats; + enum vdec_revision revision; +}; + +extern const struct vdec_platform vdec_platform_gxbb; +extern const struct vdec_platform vdec_platform_gxm; +extern const struct vdec_platform vdec_platform_gxl; +extern const struct vdec_platform vdec_platform_g12a; +extern const struct vdec_platform vdec_platform_sm1; + +#endif |