| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. |
| * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved |
| */ |
| |
| #include <drm/drm_managed.h> |
| |
| #include <drm/display/drm_dsc_helper.h> |
| |
| #include "dpu_kms.h" |
| #include "dpu_hw_catalog.h" |
| #include "dpu_hwio.h" |
| #include "dpu_hw_mdss.h" |
| #include "dpu_hw_dsc.h" |
| |
| #define DSC_CMN_MAIN_CNF 0x00 |
| |
| /* DPU_DSC_ENC register offsets */ |
| #define ENC_DF_CTRL 0x00 |
| #define ENC_GENERAL_STATUS 0x04 |
| #define ENC_HSLICE_STATUS 0x08 |
| #define ENC_OUT_STATUS 0x0C |
| #define ENC_INT_STAT 0x10 |
| #define ENC_INT_CLR 0x14 |
| #define ENC_INT_MASK 0x18 |
| #define DSC_MAIN_CONF 0x30 |
| #define DSC_PICTURE_SIZE 0x34 |
| #define DSC_SLICE_SIZE 0x38 |
| #define DSC_MISC_SIZE 0x3C |
| #define DSC_HRD_DELAYS 0x40 |
| #define DSC_RC_SCALE 0x44 |
| #define DSC_RC_SCALE_INC_DEC 0x48 |
| #define DSC_RC_OFFSETS_1 0x4C |
| #define DSC_RC_OFFSETS_2 0x50 |
| #define DSC_RC_OFFSETS_3 0x54 |
| #define DSC_RC_OFFSETS_4 0x58 |
| #define DSC_FLATNESS_QP 0x5C |
| #define DSC_RC_MODEL_SIZE 0x60 |
| #define DSC_RC_CONFIG 0x64 |
| #define DSC_RC_BUF_THRESH_0 0x68 |
| #define DSC_RC_BUF_THRESH_1 0x6C |
| #define DSC_RC_BUF_THRESH_2 0x70 |
| #define DSC_RC_BUF_THRESH_3 0x74 |
| #define DSC_RC_MIN_QP_0 0x78 |
| #define DSC_RC_MIN_QP_1 0x7C |
| #define DSC_RC_MIN_QP_2 0x80 |
| #define DSC_RC_MAX_QP_0 0x84 |
| #define DSC_RC_MAX_QP_1 0x88 |
| #define DSC_RC_MAX_QP_2 0x8C |
| #define DSC_RC_RANGE_BPG_OFFSETS_0 0x90 |
| #define DSC_RC_RANGE_BPG_OFFSETS_1 0x94 |
| #define DSC_RC_RANGE_BPG_OFFSETS_2 0x98 |
| |
| /* DPU_DSC_CTL register offsets */ |
| #define DSC_CTL 0x00 |
| #define DSC_CFG 0x04 |
| #define DSC_DATA_IN_SWAP 0x08 |
| #define DSC_CLK_CTRL 0x0C |
| |
| static int _dsc_calc_output_buf_max_addr(struct dpu_hw_dsc *hw_dsc, int num_softslice) |
| { |
| int max_addr = 2400 / num_softslice; |
| |
| if (hw_dsc->caps->features & BIT(DPU_DSC_NATIVE_42x_EN)) |
| max_addr /= 2; |
| |
| return max_addr - 1; |
| }; |
| |
| static void dpu_hw_dsc_disable_1_2(struct dpu_hw_dsc *hw_dsc) |
| { |
| struct dpu_hw_blk_reg_map *hw; |
| const struct dpu_dsc_sub_blks *sblk; |
| |
| if (!hw_dsc) |
| return; |
| |
| hw = &hw_dsc->hw; |
| sblk = hw_dsc->caps->sblk; |
| DPU_REG_WRITE(hw, sblk->ctl.base + DSC_CFG, 0); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + ENC_DF_CTRL, 0); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_MAIN_CONF, 0); |
| } |
| |
| static void dpu_hw_dsc_config_1_2(struct dpu_hw_dsc *hw_dsc, |
| struct drm_dsc_config *dsc, |
| u32 mode, |
| u32 initial_lines) |
| { |
| struct dpu_hw_blk_reg_map *hw; |
| const struct dpu_dsc_sub_blks *sblk; |
| u32 data = 0; |
| u32 det_thresh_flatness; |
| u32 num_active_slice_per_enc; |
| u32 bpp; |
| |
| if (!hw_dsc || !dsc) |
| return; |
| |
| hw = &hw_dsc->hw; |
| |
| sblk = hw_dsc->caps->sblk; |
| |
| if (mode & DSC_MODE_SPLIT_PANEL) |
| data |= BIT(0); |
| |
| if (mode & DSC_MODE_MULTIPLEX) |
| data |= BIT(1); |
| |
| num_active_slice_per_enc = dsc->slice_count; |
| if (mode & DSC_MODE_MULTIPLEX) |
| num_active_slice_per_enc = dsc->slice_count / 2; |
| |
| data |= (num_active_slice_per_enc & 0x3) << 7; |
| |
| DPU_REG_WRITE(hw, DSC_CMN_MAIN_CNF, data); |
| |
| data = (initial_lines & 0xff); |
| |
| if (mode & DSC_MODE_VIDEO) |
| data |= BIT(9); |
| |
| data |= (_dsc_calc_output_buf_max_addr(hw_dsc, num_active_slice_per_enc) << 18); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + ENC_DF_CTRL, data); |
| |
| data = (dsc->dsc_version_minor & 0xf) << 28; |
| if (dsc->dsc_version_minor == 0x2) { |
| if (dsc->native_422) |
| data |= BIT(22); |
| if (dsc->native_420) |
| data |= BIT(21); |
| } |
| |
| bpp = dsc->bits_per_pixel; |
| /* as per hw requirement bpp should be programmed |
| * twice the actual value in case of 420 or 422 encoding |
| */ |
| if (dsc->native_422 || dsc->native_420) |
| bpp = 2 * bpp; |
| |
| data |= bpp << 10; |
| |
| if (dsc->block_pred_enable) |
| data |= BIT(20); |
| |
| if (dsc->convert_rgb) |
| data |= BIT(4); |
| |
| data |= (dsc->line_buf_depth & 0xf) << 6; |
| data |= dsc->bits_per_component & 0xf; |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_MAIN_CONF, data); |
| |
| data = (dsc->pic_width & 0xffff) | |
| ((dsc->pic_height & 0xffff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_PICTURE_SIZE, data); |
| |
| data = (dsc->slice_width & 0xffff) | |
| ((dsc->slice_height & 0xffff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_SLICE_SIZE, data); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_MISC_SIZE, |
| (dsc->slice_chunk_size) & 0xffff); |
| |
| data = (dsc->initial_xmit_delay & 0xffff) | |
| ((dsc->initial_dec_delay & 0xffff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_HRD_DELAYS, data); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_SCALE, |
| dsc->initial_scale_value & 0x3f); |
| |
| data = (dsc->scale_increment_interval & 0xffff) | |
| ((dsc->scale_decrement_interval & 0x7ff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_SCALE_INC_DEC, data); |
| |
| data = (dsc->first_line_bpg_offset & 0x1f) | |
| ((dsc->second_line_bpg_offset & 0x1f) << 5); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_OFFSETS_1, data); |
| |
| data = (dsc->nfl_bpg_offset & 0xffff) | |
| ((dsc->slice_bpg_offset & 0xffff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_OFFSETS_2, data); |
| |
| data = (dsc->initial_offset & 0xffff) | |
| ((dsc->final_offset & 0xffff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_OFFSETS_3, data); |
| |
| data = (dsc->nsl_bpg_offset & 0xffff) | |
| ((dsc->second_line_offset_adj & 0xffff) << 16); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_OFFSETS_4, data); |
| |
| det_thresh_flatness = drm_dsc_flatness_det_thresh(dsc); |
| data = (dsc->flatness_min_qp & 0x1f) | |
| ((dsc->flatness_max_qp & 0x1f) << 5) | |
| ((det_thresh_flatness & 0xff) << 10); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_FLATNESS_QP, data); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MODEL_SIZE, |
| (dsc->rc_model_size) & 0xffff); |
| |
| data = dsc->rc_edge_factor & 0xf; |
| data |= (dsc->rc_quant_incr_limit0 & 0x1f) << 8; |
| data |= (dsc->rc_quant_incr_limit1 & 0x1f) << 13; |
| data |= (dsc->rc_tgt_offset_high & 0xf) << 20; |
| data |= (dsc->rc_tgt_offset_low & 0xf) << 24; |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_CONFIG, data); |
| |
| /* program the dsc wrapper */ |
| data = BIT(0); /* encoder enable */ |
| if (dsc->native_422) |
| data |= BIT(8); |
| else if (dsc->native_420) |
| data |= BIT(9); |
| if (!dsc->convert_rgb) |
| data |= BIT(10); |
| if (dsc->bits_per_component == 8) |
| data |= BIT(11); |
| if (mode & DSC_MODE_SPLIT_PANEL) |
| data |= BIT(12); |
| if (mode & DSC_MODE_MULTIPLEX) |
| data |= BIT(13); |
| if (!(mode & DSC_MODE_VIDEO)) |
| data |= BIT(17); |
| |
| DPU_REG_WRITE(hw, sblk->ctl.base + DSC_CFG, data); |
| } |
| |
| static void dpu_hw_dsc_config_thresh_1_2(struct dpu_hw_dsc *hw_dsc, |
| struct drm_dsc_config *dsc) |
| { |
| struct dpu_hw_blk_reg_map *hw; |
| const struct dpu_dsc_sub_blks *sblk; |
| struct drm_dsc_rc_range_parameters *rc; |
| |
| if (!hw_dsc || !dsc) |
| return; |
| |
| hw = &hw_dsc->hw; |
| |
| sblk = hw_dsc->caps->sblk; |
| |
| rc = dsc->rc_range_params; |
| |
| /* |
| * With BUF_THRESH -- 14 in total |
| * each register contains 4 thresh values with the last register |
| * containing only 2 thresh values |
| */ |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_BUF_THRESH_0, |
| (dsc->rc_buf_thresh[0] << 0) | |
| (dsc->rc_buf_thresh[1] << 8) | |
| (dsc->rc_buf_thresh[2] << 16) | |
| (dsc->rc_buf_thresh[3] << 24)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_BUF_THRESH_1, |
| (dsc->rc_buf_thresh[4] << 0) | |
| (dsc->rc_buf_thresh[5] << 8) | |
| (dsc->rc_buf_thresh[6] << 16) | |
| (dsc->rc_buf_thresh[7] << 24)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_BUF_THRESH_2, |
| (dsc->rc_buf_thresh[8] << 0) | |
| (dsc->rc_buf_thresh[9] << 8) | |
| (dsc->rc_buf_thresh[10] << 16) | |
| (dsc->rc_buf_thresh[11] << 24)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_BUF_THRESH_3, |
| (dsc->rc_buf_thresh[12] << 0) | |
| (dsc->rc_buf_thresh[13] << 8)); |
| |
| /* |
| * with min/max_QP -- 5 bits |
| * each register contains 5 min_qp or max_qp for total of 15 |
| * |
| * With BPG_OFFSET -- 6 bits |
| * each register contains 5 BPG_offset for total of 15 |
| */ |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MIN_QP_0, |
| (rc[0].range_min_qp << 0) | |
| (rc[1].range_min_qp << 5) | |
| (rc[2].range_min_qp << 10) | |
| (rc[3].range_min_qp << 15) | |
| (rc[4].range_min_qp << 20)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MAX_QP_0, |
| (rc[0].range_max_qp << 0) | |
| (rc[1].range_max_qp << 5) | |
| (rc[2].range_max_qp << 10) | |
| (rc[3].range_max_qp << 15) | |
| (rc[4].range_max_qp << 20)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_RANGE_BPG_OFFSETS_0, |
| (rc[0].range_bpg_offset << 0) | |
| (rc[1].range_bpg_offset << 6) | |
| (rc[2].range_bpg_offset << 12) | |
| (rc[3].range_bpg_offset << 18) | |
| (rc[4].range_bpg_offset << 24)); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MIN_QP_1, |
| (rc[5].range_min_qp << 0) | |
| (rc[6].range_min_qp << 5) | |
| (rc[7].range_min_qp << 10) | |
| (rc[8].range_min_qp << 15) | |
| (rc[9].range_min_qp << 20)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MAX_QP_1, |
| (rc[5].range_max_qp << 0) | |
| (rc[6].range_max_qp << 5) | |
| (rc[7].range_max_qp << 10) | |
| (rc[8].range_max_qp << 15) | |
| (rc[9].range_max_qp << 20)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_RANGE_BPG_OFFSETS_1, |
| (rc[5].range_bpg_offset << 0) | |
| (rc[6].range_bpg_offset << 6) | |
| (rc[7].range_bpg_offset << 12) | |
| (rc[8].range_bpg_offset << 18) | |
| (rc[9].range_bpg_offset << 24)); |
| |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MIN_QP_2, |
| (rc[10].range_min_qp << 0) | |
| (rc[11].range_min_qp << 5) | |
| (rc[12].range_min_qp << 10) | |
| (rc[13].range_min_qp << 15) | |
| (rc[14].range_min_qp << 20)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_MAX_QP_2, |
| (rc[10].range_max_qp << 0) | |
| (rc[11].range_max_qp << 5) | |
| (rc[12].range_max_qp << 10) | |
| (rc[13].range_max_qp << 15) | |
| (rc[14].range_max_qp << 20)); |
| DPU_REG_WRITE(hw, sblk->enc.base + DSC_RC_RANGE_BPG_OFFSETS_2, |
| (rc[10].range_bpg_offset << 0) | |
| (rc[11].range_bpg_offset << 6) | |
| (rc[12].range_bpg_offset << 12) | |
| (rc[13].range_bpg_offset << 18) | |
| (rc[14].range_bpg_offset << 24)); |
| } |
| |
| static void dpu_hw_dsc_bind_pingpong_blk_1_2(struct dpu_hw_dsc *hw_dsc, |
| const enum dpu_pingpong pp) |
| { |
| struct dpu_hw_blk_reg_map *hw; |
| const struct dpu_dsc_sub_blks *sblk; |
| int mux_cfg = 0xf; /* Disabled */ |
| |
| hw = &hw_dsc->hw; |
| |
| sblk = hw_dsc->caps->sblk; |
| |
| if (pp) |
| mux_cfg = (pp - PINGPONG_0) & 0x7; |
| |
| DPU_REG_WRITE(hw, sblk->ctl.base + DSC_CTL, mux_cfg); |
| } |
| |
| static void _setup_dcs_ops_1_2(struct dpu_hw_dsc_ops *ops, |
| const unsigned long features) |
| { |
| ops->dsc_disable = dpu_hw_dsc_disable_1_2; |
| ops->dsc_config = dpu_hw_dsc_config_1_2; |
| ops->dsc_config_thresh = dpu_hw_dsc_config_thresh_1_2; |
| ops->dsc_bind_pingpong_blk = dpu_hw_dsc_bind_pingpong_blk_1_2; |
| } |
| |
| struct dpu_hw_dsc *dpu_hw_dsc_init_1_2(struct drm_device *dev, |
| const struct dpu_dsc_cfg *cfg, |
| void __iomem *addr) |
| { |
| struct dpu_hw_dsc *c; |
| |
| c = drmm_kzalloc(dev, sizeof(*c), GFP_KERNEL); |
| if (!c) |
| return ERR_PTR(-ENOMEM); |
| |
| c->hw.blk_addr = addr + cfg->base; |
| c->hw.log_mask = DPU_DBG_MASK_DSC; |
| |
| c->idx = cfg->id; |
| c->caps = cfg; |
| _setup_dcs_ops_1_2(&c->ops, c->caps->features); |
| |
| return c; |
| } |