diff options
author | Maxime Jourdan <mjourdan@baylibre.com> | 2019-06-06 12:05:11 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab+samsung@kernel.org> | 2019-06-11 11:25:53 -0400 |
commit | 3e7f51bd96077acad6acd7b45668f65b44233c4e (patch) | |
tree | 83003b083b3c6848d5596468843e8836e29ef58a /drivers/staging | |
parent | a1ccca0e84243c8aa39f4700cecc200b36c6b50f (diff) |
media: meson: add v4l2 m2m video decoder driver
Amlogic SoCs feature a powerful video decoder unit able to
decode many formats, with a performance of usually up to 4k60.
This is a driver for this IP that is based around the v4l2 m2m framework.
It features decoding for:
- MPEG 1
- MPEG 2
Supported SoCs are: GXBB (S905), GXL (S905X/W/D), GXM (S912)
There is also a hardware bitstream parser (ESPARSER) that is handled here.
Signed-off-by: Maxime Jourdan <mjourdan@baylibre.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
Diffstat (limited to 'drivers/staging')
-rw-r--r-- | drivers/staging/media/Kconfig | 2 | ||||
-rw-r--r-- | drivers/staging/media/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/Kconfig | 11 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/Makefile | 8 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/TODO | 8 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/codec_mpeg12.c | 210 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/codec_mpeg12.h | 14 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/dos_regs.h | 98 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/esparser.c | 324 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/esparser.h | 32 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec.c | 1098 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec.h | 267 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec_1.c | 230 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec_1.h | 14 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec_helpers.c | 449 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec_helpers.h | 83 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec_platform.c | 101 | ||||
-rw-r--r-- | drivers/staging/media/meson/vdec/vdec_platform.h | 30 |
18 files changed, 2980 insertions, 0 deletions
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 710b085b782f..7212762035b4 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -28,6 +28,8 @@ source "drivers/staging/media/davinci_vpfe/Kconfig" source "drivers/staging/media/imx/Kconfig" +source "drivers/staging/media/meson/vdec/Kconfig" + source "drivers/staging/media/omap4iss/Kconfig" source "drivers/staging/media/rockchip/vpu/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index ea754f9acd53..4222584a9bcb 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro-dvt/ obj-$(CONFIG_I2C_BCM2048) += bcm2048/ obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/ obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ +obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/ obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ obj-$(CONFIG_VIDEO_SUNXI) += sunxi/ obj-$(CONFIG_TEGRA_VDE) += tegra-vde/ diff --git a/drivers/staging/media/meson/vdec/Kconfig b/drivers/staging/media/meson/vdec/Kconfig new file mode 100644 index 000000000000..9e1450193392 --- /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 && VIDEO_V4L2 && 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 000000000000..6bea129084b7 --- /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 +meson-vdec-objs += codec_mpeg12.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 000000000000..70ae990cf13b --- /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_mpeg12.c b/drivers/staging/media/meson/vdec/codec_mpeg12.c new file mode 100644 index 000000000000..5398fbf7ce20 --- /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 = sess->priv; + 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 000000000000..43cab5f39ca0 --- /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/dos_regs.h b/drivers/staging/media/meson/vdec/dos_regs.h new file mode 100644 index 000000000000..abd810542dbb --- /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 000000000000..3a21a8cec799 --- /dev/null +++ b/drivers/staging/media/meson/vdec/esparser.c @@ -0,0 +1,324 @@ +// 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/of_device.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 + +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; +} + +/* 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 vb2_buffer *vb) +{ + u32 payload_size = vb2_get_plane_payload(vb, 0); + u32 pad_size = 0; + u8 *vaddr = vb2_plane_vaddr(vb, 0) + payload_size; + + if (payload_size < ESPARSER_MIN_PACKET_SIZE) { + pad_size = ESPARSER_MIN_PACKET_SIZE - payload_size; + memset(vaddr, 0, pad_size); + } + + memset(vaddr + pad_size, 0, SEARCH_PATTERN_LEN); + vaddr[pad_size] = 0x00; + vaddr[pad_size + 1] = 0x00; + vaddr[pad_size + 2] = 0x01; + vaddr[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 num_dst_bufs = 0; + u32 payload_size = vb2_get_plane_payload(vb, 0); + dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0); + u32 offset; + u32 pad_size; + + 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); + + if (esparser_vififo_get_free_space(sess) < payload_size || + atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs) + return -EAGAIN; + + v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf); + + offset = esparser_get_offset(sess); + + amvdec_add_ts_reorder(sess, vb->timestamp, offset); + dev_dbg(core->dev, "esparser: ts = %llu pld_size = %u offset = %08X\n", + vb->timestamp, payload_size, offset); + + pad_size = esparser_pad_start_code(vb); + 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; + } + + /* We need to wait until we parse the first keyframe. + * All buffers prior to the first keyframe must be dropped. + */ + if (!sess->keyframe_found) + usleep_range(1000, 2000); + + if (sess->keyframe_found) + atomic_inc(&sess->esparser_queued_bufs); + else + amvdec_remove_ts(sess, vb->timestamp); + + vbuf->flags = 0; + vbuf->field = V4L2_FIELD_NONE; + 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) { + dev_err(dev, "Failed getting ESPARSER IRQ from dtb\n"); + 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 000000000000..ff51fe7fda66 --- /dev/null +++ b/drivers/staging/media/meson/vdec/esparser.h @@ -0,0 +1,32 @@ +/* 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 + */ +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 + */ +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/vdec.c b/drivers/staging/media/meson/vdec/vdec.c new file mode 100644 index 000000000000..4e4f9d614e41 --- /dev/null +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -0,0 +1,1098 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Maxime Jourdan <mjourdan@baylibre.com> + */ + +#include <linux/of_device.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 <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); + 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; + + 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] < output_size) + return -EINVAL; + break; + ca |