// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2019 BayLibre, SAS * Author: Neil Armstrong <narmstrong@baylibre.com> */ #include <linux/bitfield.h> #include <drm/drm_print.h> #include <drm/drm_fourcc.h> #include "meson_drv.h" #include "meson_registers.h" #include "meson_viu.h" #include "meson_rdma.h" #include "meson_osd_afbcd.h" /* * DOC: Driver for the ARM FrameBuffer Compression Decoders * * The Amlogic GXM and G12A SoC families embeds an AFBC Decoder, * to decode compressed buffers generated by the ARM Mali GPU. * * For the GXM Family, Amlogic designed their own Decoder, named in * the vendor source as "MESON_AFBC", and a single decoder is available * for the 2 OSD planes. * This decoder is compatible with the AFBC 1.0 specifications and the * Mali T820 GPU capabilities. * It supports : * - basic AFBC buffer for RGB32 only, thus YTR feature is mandatory * - SPARSE layout and SPLIT layout * - only 16x16 superblock * * The decoder reads the data from the SDRAM, decodes and sends the * decoded pixel stream to the OSD1 Plane pixel composer. * * For the G12A Family, Amlogic integrated an ARM AFBC Decoder, named * in the vendor source as "MALI_AFBC", and the decoder can decode up * to 4 surfaces, one for each of the 4 available OSDs. * This decoder is compatible with the AFBC 1.2 specifications for the * Mali G31 and G52 GPUs. * Is supports : * - basic AFBC buffer for multiple RGB and YUV pixel formats * - SPARSE layout and SPLIT layout * - 16x16 and 32x8 "wideblk" superblocks * - Tiled header * * The ARM AFBC Decoder independent from the VPU Pixel Pipeline, so * the ARM AFBC Decoder reads the data from the SDRAM then decodes * into a private internal physical address where the OSD1 Plane pixel * composer unpacks the decoded data. */ /* Amlogic AFBC Decoder for GXM Family */ #define OSD1_AFBCD_RGB32 0x15 static int meson_gxm_afbcd_pixel_fmt(u64 modifier, uint32_t format) { switch (format) { case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: return OSD1_AFBCD_RGB32; /* TOFIX support mode formats */ default: DRM_DEBUG("unsupported afbc format[%08x]\n", format); return -EINVAL; } } static bool meson_gxm_afbcd_supported_fmt(u64 modifier, uint32_t format) { if (modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) return false; if (!(modifier & AFBC_FORMAT_MOD_YTR)) return false; return meson_gxm_afbcd_pixel_fmt(modifier, format) >= 0; } static int meson_gxm_afbcd_reset(struct meson_drm *priv) { writel_relaxed(VIU_SW_RESET_OSD1_AFBCD, priv->io_base + _REG(VIU_SW_RESET)); writel_relaxed(0, priv->io_base + _REG(VIU_SW_RESET)); return 0; } static int meson_gxm_afbcd_init(struct meson_drm *priv) { return 0; } static void meson_gxm_afbcd_exit(struct meson_drm *priv) { meson_gxm_afbcd_reset(priv); } static int meson_gxm_afbcd_enable(struct meson_drm *priv) { writel_relaxed(FIELD_PREP(OSD1_AFBCD_ID_FIFO_THRD, 0x40) | OSD1_AFBCD_DEC_ENABLE, priv->io_base + _REG(OSD1_AFBCD_ENABLE)); return 0; } static int meson_gxm_afbcd_disable(struct meson_drm *priv) { writel_bits_relaxed(OSD1_AFBCD_DEC_ENABLE, 0, priv->io_base + _REG(OSD1_AFBCD_ENABLE)); return 0; } static int meson_gxm_afbcd_setup(struct meson_drm *priv) { u32 conv_lbuf_len; u32 mode = FIELD_PREP(OSD1_AFBCD_MIF_URGENT, 3) | FIELD_PREP(OSD1_AFBCD_HOLD_LINE_NUM, 4) | FIELD_PREP(OSD1_AFBCD_RGBA_EXCHAN_CTRL, 0x34) | meson_gxm_afbcd_pixel_fmt(priv->afbcd.modifier, priv->afbcd.format); if (priv->afbcd.modifier & AFBC_FORMAT_MOD_SPARSE) mode |= OSD1_AFBCD_HREG_HALF_BLOCK; if (priv->afbcd.modifier & AFBC_FORMAT_MOD_SPLIT) mode |= OSD1_AFBCD_HREG_BLOCK_SPLIT; writel_relaxed(mode, priv->io_base + _REG(OSD1_AFBCD_MODE)); writel_relaxed(FIELD_PREP(OSD1_AFBCD_HREG_VSIZE_IN, priv->viu.osd1_width) | FIELD_PREP(OSD1_AFBCD_HREG_HSIZE_IN, priv->viu.osd1_height), priv->io_base + _REG(OSD1_AFBCD_SIZE_IN)); writel_relaxed(priv->viu.osd1_addr >> 4, priv->io_base + _REG(OSD1_AFBCD_HDR_PTR)); writel_relaxed(priv->viu.osd1_addr >> 4, priv->io_base + _REG(OSD1_AFBCD_FRAME_PTR)); /* TOFIX: bits 31:24 are not documented, nor the meaning of 0xe4 */ writel_relaxed((0xe4 << 24) | (priv->viu.osd1_addr & 0xffffff), priv->io_base + _REG(OSD1_AFBCD_CHROMA_PTR)); if (priv->viu.osd1_width <= 128) conv_lbuf_len = 32; else if (priv->viu.osd1_width <= 256) conv_lbuf_len = 64; else if (priv->viu.osd1_width <= 512) conv_lbuf_len = 128; else if (priv->viu.osd1_width <= 1024) conv_lbuf_len = 256; else if (priv->viu.osd1_width <= 2048) conv_lbuf_len = 512; else conv_lbuf_len = 1024; writel_relaxed(conv_lbuf_len, priv->io_base + _REG(OSD1_AFBCD_CONV_CTRL)); writel_relaxed(FIELD_PREP(OSD1_AFBCD_DEC_PIXEL_BGN_H, 0) | FIELD_PREP(OSD1_AFBCD_DEC_PIXEL_END_H, priv->viu.osd1_width - 1), priv->io_base + _REG(OSD1_AFBCD_PIXEL_HSCOPE)); writel_relaxed(FIELD_PREP(OSD1_AFBCD_DEC_PIXEL_BGN_V, 0) | FIELD_PREP(OSD1_AFBCD_DEC_PIXEL_END_V, priv->viu.osd1_height - 1), priv->io_base + _REG(OSD1_AFBCD_PIXEL_VSCOPE)); return 0; } struct meson_afbcd_ops meson_afbcd_gxm_ops = { .init = meson_gxm_afbcd_init, .exit = meson_gxm_afbcd_exit, .reset = meson_gxm_afbcd_reset, .enable = meson_gxm_afbcd_enable, .disable = meson_gxm_afbcd_disable, .setup = meson_gxm_afbcd_setup, .supported_fmt = meson_gxm_afbcd_supported_fmt, }; /* ARM AFBC Decoder for G12A Family */ /* Amlogic G12A Mali AFBC Decoder supported formats */ enum { MAFBC_FMT_RGB565 = 0, MAFBC_FMT_RGBA5551, MAFBC_FMT_RGBA1010102, MAFBC_FMT_YUV420_10B, MAFBC_FMT_RGB888, MAFBC_FMT_RGBA8888, MAFBC_FMT_RGBA4444, MAFBC_FMT_R8, MAFBC_FMT_RG88, MAFBC_FMT_YUV420_8B, MAFBC_FMT_YUV422_8B = 11, MAFBC_FMT_YUV422_10B = 14, }; static int meson_g12a_afbcd_pixel_fmt(u64 modifier, uint32_t format) { switch (format) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: /* YTR is forbidden for non XBGR formats */ if (modifier & AFBC_FORMAT_MOD_YTR) return -EINVAL; fallthrough; case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: return MAFBC_FMT_RGBA8888; case DRM_FORMAT_RGB888: /* YTR is forbidden for non XBGR formats */ if (modifier & AFBC_FORMAT_MOD_YTR) return -EINVAL; return MAFBC_FMT_RGB888; case DRM_FORMAT_RGB565: /* YTR is forbidden for non XBGR formats */ if (modifier & AFBC_FORMAT_MOD_YTR) return -EINVAL; return MAFBC_FMT_RGB565; /* TOFIX support mode formats */ default: DRM_DEBUG("unsupported afbc format[%08x]\n", format); return -EINVAL; } } static int meson_g12a_afbcd_bpp(uint32_t format) { switch (format) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: return 32; case DRM_FORMAT_RGB888: return 24; case DRM_FORMAT_RGB565: return 16; /* TOFIX support mode formats */ default: DRM_ERROR("unsupported afbc format[%08x]\n", format); return 0; } } static int meson_g12a_afbcd_fmt_to_blk_mode(u64 modifier, uint32_t format) { switch (format) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: return OSD_MALI_COLOR_MODE_RGBA8888; case DRM_FORMAT_RGB888: return OSD_MALI_COLOR_MODE_RGB888; case DRM_FORMAT_RGB565: return OSD_MALI_COLOR_MODE_RGB565; /* TOFIX support mode formats */ default: DRM_DEBUG("unsupported afbc format[%08x]\n", format); return -EINVAL; } } static bool meson_g12a_afbcd_supported_fmt(u64 modifier, uint32_t format) { return meson_g12a_afbcd_pixel_fmt(modifier, format) >= 0; } static int meson_g12a_afbcd_reset(struct meson_drm *priv) { meson_rdma_reset(priv); meson_rdma_writel_sync(priv, VIU_SW_RESET_G12A_AFBC_ARB | VIU_SW_RESET_G12A_OSD1_AFBCD, VIU_SW_RESET); meson_rdma_writel_sync(priv, 0, VIU_SW_RESET); return 0; } static int meson_g12a_afbcd_init(struct meson_drm *priv) { int ret; ret = meson_rdma_init(priv); if (ret) return ret; meson_rdma_setup(priv); /* Handle AFBC Decoder reset manually */ writel_bits_relaxed(MALI_AFBCD_MANUAL_RESET, MALI_AFBCD_MANUAL_RESET, priv->io_base + _REG(MALI_AFBCD_TOP_CTRL)); return 0; } static void meson_g12a_afbcd_exit(struct meson_drm *priv) { meson_g12a_afbcd_reset(priv); meson_rdma_free(priv); } static int meson_g12a_afbcd_enable(struct meson_drm *priv) { meson_rdma_writel_sync(priv, VPU_MAFBC_IRQ_SURFACES_COMPLETED | VPU_MAFBC_IRQ_CONFIGURATION_SWAPPED | VPU_MAFBC_IRQ_DECODE_ERROR | VPU_MAFBC_IRQ_DETILING_ERROR, VPU_MAFBC_IRQ_MASK); meson_rdma_writel_sync(priv, VPU_MAFBC_S0_ENABLE, VPU_MAFBC_SURFACE_CFG); meson_rdma_writel_sync(priv, VPU_MAFBC_DIRECT_SWAP, VPU_MAFBC_COMMAND); /* This will enable the RDMA replaying the register writes on vsync */ meson_rdma_flush(priv); return 0; } static int meson_g12a_afbcd_disable(struct meson_drm *priv) { writel_bits_relaxed(VPU_MAFBC_S0_ENABLE, 0, priv->io_base + _REG(VPU_MAFBC_SURFACE_CFG)); return 0; } static int meson_g12a_afbcd_setup(struct meson_drm *priv) { u32 format = meson_g12a_afbcd_pixel_fmt(priv->afbcd.modifier, priv->afbcd.format); if (priv->afbcd.modifier & AFBC_FORMAT_MOD_YTR) format |= VPU_MAFBC_YUV_TRANSFORM; if (priv->afbcd.modifier & AFBC_FORMAT_MOD_SPLIT) format |= VPU_MAFBC_BLOCK_SPLIT; if (priv->afbcd.modifier & AFBC_FORMAT_MOD_TILED) format |= VPU_MAFBC_TILED_HEADER_EN; if ((priv->afbcd.modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) == AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) format |= FIELD_PREP(VPU_MAFBC_SUPER_BLOCK_ASPECT, 1); meson_rdma_writel_sync(priv, format, VPU_MAFBC_FORMAT_SPECIFIER_S0); meson_rdma_writel_sync(priv, priv->viu.osd1_addr, VPU_MAFBC_HEADER_BUF_ADDR_LOW_S0); meson_rdma_writel_sync(priv, 0, VPU_MAFBC_HEADER_BUF_ADDR_HIGH_S0); meson_rdma_writel_sync(priv, priv->viu.osd1_width, VPU_MAFBC_BUFFER_WIDTH_S0); meson_rdma_writel_sync(priv, ALIGN(priv->viu.osd1_height, 32), VPU_MAFBC_BUFFER_HEIGHT_S0); meson_rdma_writel_sync(priv, 0, VPU_MAFBC_BOUNDING_BOX_X_START_S0); meson_rdma_writel_sync(priv, priv->viu.osd1_width - 1, VPU_MAFBC_BOUNDING_BOX_X_END_S0); meson_rdma_writel_sync(priv, 0, VPU_MAFBC_BOUNDING_BOX_Y_START_S0); meson_rdma_writel_sync(priv, priv->viu.osd1_height - 1, VPU_MAFBC_BOUNDING_BOX_Y_END_S0); meson_rdma_writel_sync(priv, MESON_G12A_AFBCD_OUT_ADDR, VPU_MAFBC_OUTPUT_BUF_ADDR_LOW_S0); meson_rdma_writel_sync(priv, 0, VPU_MAFBC_OUTPUT_BUF_ADDR_HIGH_S0); meson_rdma_writel_sync(priv, priv->viu.osd1_width * (meson_g12a_afbcd_bpp(priv->afbcd.format) / 8), VPU_MAFBC_OUTPUT_BUF_STRIDE_S0); return 0; } struct meson_afbcd_ops meson_afbcd_g12a_ops = { .init = meson_g12a_afbcd_init, .exit = meson_g12a_afbcd_exit, .reset = meson_g12a_afbcd_reset, .enable = meson_g12a_afbcd_enable, .disable = meson_g12a_afbcd_disable, .setup = meson_g12a_afbcd_setup, .fmt_to_blk_mode = meson_g12a_afbcd_fmt_to_blk_mode, .supported_fmt = meson_g12a_afbcd_supported_fmt, };