// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018 BayLibre, SAS * Author: Maxime Jourdan * * VDEC_1 is a video decoding block that allows decoding of * MPEG 1/2/4, H.263, H.264, MJPEG, VC1 */ #include #include #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, };