root/drivers/staging/media/meson/vdec/codec_mpeg12.c
// 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_obj(*mpeg12);
        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,
};