| // 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, |
| }; |