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