diff --git a/Documentation/devicetree/bindings/display/arm,komeda.txt b/Documentation/devicetree/bindings/display/arm,komeda.txt index 02b226532ebdd70529150626f0ae7b57a15acbe5..8513695ee47fe96e7899d764e5ee98e3d4d2a8ba 100644 --- a/Documentation/devicetree/bindings/display/arm,komeda.txt +++ b/Documentation/devicetree/bindings/display/arm,komeda.txt @@ -7,10 +7,13 @@ Required properties: - clocks: A list of phandle + clock-specifier pairs, one for each entry in 'clock-names' - clock-names: A list of clock names. It should contain: - - "mclk": for the main processor clock - - "pclk": for the APB interface clock + - "aclk": for the main processor clock - #address-cells: Must be 1 - #size-cells: Must be 0 +- iommus: configure the stream id to IOMMU, Must be configured if want to + enable iommu in display. for how to configure this node please reference + devicetree/bindings/iommu/arm,smmu-v3.txt, + devicetree/bindings/iommu/iommu.txt Required properties for sub-node: pipeline@nq Each device contains one or two pipeline sub-nodes (at least one), each @@ -20,7 +23,6 @@ pipeline node should provide properties: in 'clock-names' - clock-names: should contain: - "pxclk": pixel clock - - "aclk": AXI interface clock - port: each pipeline connect to an encoder input port. The connection is modeled using the OF graph bindings specified in @@ -42,12 +44,15 @@ Example: compatible = "arm,mali-d71"; reg = <0xc00000 0x20000>; interrupts = <0 168 4>; - clocks = <&dpu_mclk>, <&dpu_aclk>; - clock-names = "mclk", "pclk"; + clocks = <&dpu_aclk>; + clock-names = "aclk"; + iommus = <&smmu 0>, <&smmu 1>, <&smmu 2>, <&smmu 3>, + <&smmu 4>, <&smmu 5>, <&smmu 6>, <&smmu 7>, + <&smmu 8>, <&smmu 9>; dp0_pipe0: pipeline@0 { - clocks = <&fpgaosc2>, <&dpu_aclk>; - clock-names = "pxclk", "aclk"; + clocks = <&fpgaosc2>; + clock-names = "pxclk"; reg = <0>; port { @@ -58,8 +63,8 @@ Example: }; dp0_pipe1: pipeline@1 { - clocks = <&fpgaosc2>, <&dpu_aclk>; - clock-names = "pxclk", "aclk"; + clocks = <&fpgaosc2>; + clock-names = "pxclk"; reg = <1>; port { diff --git a/drivers/gpu/drm/arm/display/include/malidp_io.h b/drivers/gpu/drm/arm/display/include/malidp_io.h index 4fb3caf864ce7b3fa18cf841c91f502fea207900..9440dff94212082df6f68d1916ae8fa192ce7b24 100644 --- a/drivers/gpu/drm/arm/display/include/malidp_io.h +++ b/drivers/gpu/drm/arm/display/include/malidp_io.h @@ -21,6 +21,13 @@ malidp_write32(u32 __iomem *base, u32 offset, u32 v) writel(v, (base + (offset >> 2))); } +static inline void +malidp_write64(u32 __iomem *base, u32 offset, u64 v) +{ + writel(lower_32_bits(v), (base + (offset >> 2))); + writel(upper_32_bits(v), (base + (offset >> 2) + 1)); +} + static inline void malidp_write32_mask(u32 __iomem *base, u32 offset, u32 m, u32 v) { diff --git a/drivers/gpu/drm/arm/display/include/malidp_utils.h b/drivers/gpu/drm/arm/display/include/malidp_utils.h index 8cfd91196e1540ce9c193ee47e804463d4049226..3bc383d5bf73d13ebf38ffe358c7e17c30411f5e 100644 --- a/drivers/gpu/drm/arm/display/include/malidp_utils.h +++ b/drivers/gpu/drm/arm/display/include/malidp_utils.h @@ -8,6 +8,7 @@ #define _MALIDP_UTILS_ #include +#include #define has_bit(nr, mask) (BIT(nr) & (mask)) #define has_bits(bits, mask) (((bits) & (mask)) == (bits)) @@ -20,11 +21,9 @@ int num_tries = __tries; \ while (!__cond && (num_tries > 0)) { \ usleep_range(__min_range, __max_range); \ - if (__cond) \ - break; \ num_tries--; \ } \ - num_tries; \ + (__cond) ? 0 : -ETIMEDOUT; \ }) /* the restriction of range is [start, end] */ diff --git a/drivers/gpu/drm/arm/display/komeda/Makefile b/drivers/gpu/drm/arm/display/komeda/Makefile index 412eeba8c39f07faa8d8346ff6ee192c8e2490c4..5c3900c2e764feac9a6ea5d932a0af2f0821c00d 100644 --- a/drivers/gpu/drm/arm/display/komeda/Makefile +++ b/drivers/gpu/drm/arm/display/komeda/Makefile @@ -8,12 +8,14 @@ komeda-y := \ komeda_drv.o \ komeda_dev.o \ komeda_format_caps.o \ + komeda_color_mgmt.o \ komeda_pipeline.o \ komeda_pipeline_state.o \ komeda_framebuffer.o \ komeda_kms.o \ komeda_crtc.o \ komeda_plane.o \ + komeda_wb_connector.o \ komeda_private_obj.o komeda-y += \ diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c index 6bab816ed8e73257daf72eacf3c3f798943074b9..4073a452e24ab26c1e51f9d2d2f0a88b76348e22 100644 --- a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c @@ -10,6 +10,7 @@ #include "komeda_kms.h" #include "malidp_io.h" #include "komeda_framebuffer.h" +#include "komeda_color_mgmt.h" static void get_resources_id(u32 hw_id, u32 *pipe_id, u32 *comp_id) { @@ -134,11 +135,60 @@ static u32 to_rot_ctrl(u32 rot) return lr_ctrl; } -static inline u32 to_d71_input_id(struct komeda_component_output *output) +static u32 to_ad_ctrl(u64 modifier) { - struct komeda_component *comp = output->component; + u32 afbc_ctrl = AD_AEN; - return comp ? (comp->hw_id + output->output_port) : 0; + if (!modifier) + return 0; + + if ((modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) == + AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) + afbc_ctrl |= AD_WB; + + if (modifier & AFBC_FORMAT_MOD_YTR) + afbc_ctrl |= AD_YT; + if (modifier & AFBC_FORMAT_MOD_SPLIT) + afbc_ctrl |= AD_BS; + if (modifier & AFBC_FORMAT_MOD_TILED) + afbc_ctrl |= AD_TH; + + return afbc_ctrl; +} + +static inline u32 to_d71_input_id(struct komeda_component_state *st, int idx) +{ + struct komeda_component_output *input = &st->inputs[idx]; + + /* if input is not active, set hw input_id(0) to disable it */ + if (has_bit(idx, st->active_inputs)) + return input->component->hw_id + input->output_port; + else + return 0; +} + +static void d71_layer_update_fb(struct komeda_component *c, + struct komeda_fb *kfb, + dma_addr_t *addr) +{ + struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + u32 __iomem *reg = c->reg; + int block_h; + + if (info->num_planes > 2) + malidp_write64(reg, BLK_P2_PTR_LOW, addr[2]); + + if (info->num_planes > 1) { + block_h = drm_format_info_block_height(info, 1); + malidp_write32(reg, BLK_P1_STRIDE, fb->pitches[1] * block_h); + malidp_write64(reg, BLK_P1_PTR_LOW, addr[1]); + } + + block_h = drm_format_info_block_height(info, 0); + malidp_write32(reg, BLK_P0_STRIDE, fb->pitches[0] * block_h); + malidp_write64(reg, BLK_P0_PTR_LOW, addr[0]); + malidp_write32(reg, LAYER_FMT, kfb->format_caps->hw_id); } static void d71_layer_disable(struct komeda_component *c) @@ -156,26 +206,65 @@ static void d71_layer_update(struct komeda_component *c, u32 __iomem *reg = c->reg; u32 ctrl_mask = L_EN | L_ROT(L_ROT_R270) | L_HFLIP | L_VFLIP | L_TBU_EN; u32 ctrl = L_EN | to_rot_ctrl(st->rot); - int i; - for (i = 0; i < fb->format->num_planes; i++) { - malidp_write32(reg, - BLK_P0_PTR_LOW + i * LAYER_PER_PLANE_REGS * 4, - lower_32_bits(st->addr[i])); - malidp_write32(reg, - BLK_P0_PTR_HIGH + i * LAYER_PER_PLANE_REGS * 4, - upper_32_bits(st->addr[i])); - if (i >= 2) + d71_layer_update_fb(c, kfb, st->addr); + + malidp_write32(reg, AD_CONTROL, to_ad_ctrl(fb->modifier)); + if (fb->modifier) { + u64 addr; + + malidp_write32(reg, LAYER_AD_H_CROP, HV_CROP(st->afbc_crop_l, + st->afbc_crop_r)); + malidp_write32(reg, LAYER_AD_V_CROP, HV_CROP(st->afbc_crop_t, + st->afbc_crop_b)); + /* afbc 1.2 wants payload, afbc 1.0/1.1 wants end_addr */ + if (fb->modifier & AFBC_FORMAT_MOD_TILED) + addr = st->addr[0] + kfb->offset_payload; + else + addr = st->addr[0] + kfb->afbc_size - 1; + + malidp_write32(reg, BLK_P1_PTR_LOW, lower_32_bits(addr)); + malidp_write32(reg, BLK_P1_PTR_HIGH, upper_32_bits(addr)); + } + + if (fb->format->is_yuv) { + u32 upsampling = 0; + + switch (kfb->format_caps->fourcc) { + case DRM_FORMAT_YUYV: + upsampling = fb->modifier ? LR_CHI422_BILINEAR : + LR_CHI422_REPLICATION; + break; + case DRM_FORMAT_UYVY: + upsampling = LR_CHI422_REPLICATION; break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_YUV420_8BIT: + case DRM_FORMAT_YUV420_10BIT: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_P010: + /* these fmt support MPGE/JPEG both, here perfer JPEG*/ + upsampling = LR_CHI420_JPEG; + break; + case DRM_FORMAT_X0L2: + upsampling = LR_CHI420_JPEG; + break; + default: + break; + } - malidp_write32(reg, - BLK_P0_STRIDE + i * LAYER_PER_PLANE_REGS * 4, - fb->pitches[i] & 0xFFFF); + malidp_write32(reg, LAYER_R_CONTROL, upsampling); + malidp_write_group(reg, LAYER_YUV_RGB_COEFF0, + KOMEDA_N_YUV2RGB_COEFFS, + komeda_select_yuv2rgb_coeffs( + plane_st->color_encoding, + plane_st->color_range)); } - malidp_write32(reg, LAYER_FMT, kfb->format_caps->hw_id); malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize, st->vsize)); + if (kfb->is_va) + ctrl |= L_TBU_EN; malidp_write32_mask(reg, BLK_CONTROL, ctrl_mask, ctrl); } @@ -288,10 +377,90 @@ static int d71_layer_init(struct d71_dev *d71, return 0; } +static void d71_wb_layer_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_layer_state *st = to_layer_st(state); + struct drm_connector_state *conn_st = state->wb_conn->state; + struct komeda_fb *kfb = to_kfb(conn_st->writeback_job->fb); + u32 ctrl = L_EN | LW_OFM, mask = L_EN | LW_OFM | LW_TBU_EN; + u32 __iomem *reg = c->reg; + + d71_layer_update_fb(c, kfb, st->addr); + + if (kfb->is_va) + ctrl |= LW_TBU_EN; + + malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize, st->vsize)); + malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(state, 0)); + malidp_write32_mask(reg, BLK_CONTROL, mask, ctrl); +} + +static void d71_wb_layer_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[12], i; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0x80, 1, v); + seq_printf(sf, "LW_INPUT_ID0:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xD0, 3, v); + seq_printf(sf, "LW_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "LW_PROG_LINE:\t\t0x%X\n", v[1]); + seq_printf(sf, "LW_FORMAT:\t\t0x%X\n", v[2]); + + get_values_from_reg(c->reg, 0xE0, 1, v); + seq_printf(sf, "LW_IN_SIZE:\t\t0x%X\n", v[0]); + + for (i = 0; i < 2; i++) { + get_values_from_reg(c->reg, 0x100 + i * 0x10, 3, v); + seq_printf(sf, "LW_P%u_PTR_LOW:\t\t0x%X\n", i, v[0]); + seq_printf(sf, "LW_P%u_PTR_HIGH:\t\t0x%X\n", i, v[1]); + seq_printf(sf, "LW_P%u_STRIDE:\t\t0x%X\n", i, v[2]); + } + + get_values_from_reg(c->reg, 0x130, 12, v); + for (i = 0; i < 12; i++) + seq_printf(sf, "LW_RGB_YUV_COEFF%u:\t0x%X\n", i, v[i]); +} + +static void d71_wb_layer_disable(struct komeda_component *c) +{ + malidp_write32(c->reg, BLK_INPUT_ID0, 0); + malidp_write32_mask(c->reg, BLK_CONTROL, L_EN, 0); +} + +static const struct komeda_component_funcs d71_wb_layer_funcs = { + .update = d71_wb_layer_update, + .disable = d71_wb_layer_disable, + .dump_register = d71_wb_layer_dump, +}; + static int d71_wb_layer_init(struct d71_dev *d71, struct block_header *blk, u32 __iomem *reg) { - DRM_DEBUG("Detect D71_Wb_Layer.\n"); + struct komeda_component *c; + struct komeda_layer *wb_layer; + u32 pipe_id, layer_id; + + get_resources_id(blk->block_info, &pipe_id, &layer_id); + + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*wb_layer), + layer_id, BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_wb_layer_funcs, + 1, get_valid_inputs(blk), 0, reg, + "LPU%d_LAYER_WR", pipe_id); + if (IS_ERR(c)) { + DRM_ERROR("Failed to add wb_layer component\n"); + return PTR_ERR(c); + } + + wb_layer = to_layer(c); + wb_layer->layer_type = KOMEDA_FMT_WB_LAYER; + + set_range(&wb_layer->hsize_in, D71_MIN_LINE_SIZE, d71->max_line_size); + set_range(&wb_layer->vsize_in, D71_MIN_VERTICAL_SIZE, d71->max_vsize); return 0; } @@ -303,8 +472,18 @@ static void d71_component_disable(struct komeda_component *c) malidp_write32(reg, BLK_CONTROL, 0); - for (i = 0; i < c->max_active_inputs; i++) + for (i = 0; i < c->max_active_inputs; i++) { malidp_write32(reg, BLK_INPUT_ID0 + (i << 2), 0); + + /* Besides clearing the input ID to zero, D71 compiz also has + * input enable bit in CU_INPUTx_CONTROL which need to be + * cleared. + */ + if (has_bit(c->id, KOMEDA_PIPELINE_COMPIZS)) + malidp_write32(reg, CU_INPUT0_CONTROL + + i * CU_PER_INPUT_REGS * 4, + CU_INPUT_CTRL_ALPHA(0xFF)); + } } static void compiz_enable_input(u32 __iomem *id_reg, @@ -337,15 +516,15 @@ static void d71_compiz_update(struct komeda_component *c, struct komeda_compiz_state *st = to_compiz_st(state); u32 __iomem *reg = c->reg; u32 __iomem *id_reg, *cfg_reg; - u32 index, input_hw_id; + u32 index; for_each_changed_input(state, index) { id_reg = reg + index; cfg_reg = reg + index * CU_PER_INPUT_REGS; - input_hw_id = to_d71_input_id(&state->inputs[index]); if (state->active_inputs & BIT(index)) { compiz_enable_input(id_reg, cfg_reg, - input_hw_id, &st->cins[index]); + to_d71_input_id(state, index), + &st->cins[index]); } else { malidp_write32(id_reg, BLK_INPUT_ID0, 0); malidp_write32(cfg_reg, CU_INPUT0_CONTROL, 0); @@ -424,18 +603,354 @@ static int d71_compiz_init(struct d71_dev *d71, return 0; } +static void d71_scaler_update_filter_lut(u32 __iomem *reg, u32 hsize_in, + u32 vsize_in, u32 hsize_out, + u32 vsize_out) +{ + u32 val = 0; + + if (hsize_in <= hsize_out) + val |= 0x62; + else if (hsize_in <= (hsize_out + hsize_out / 2)) + val |= 0x63; + else if (hsize_in <= hsize_out * 2) + val |= 0x64; + else if (hsize_in <= hsize_out * 2 + (hsize_out * 3) / 4) + val |= 0x65; + else + val |= 0x66; + + if (vsize_in <= vsize_out) + val |= SC_VTSEL(0x6A); + else if (vsize_in <= (vsize_out + vsize_out / 2)) + val |= SC_VTSEL(0x6B); + else if (vsize_in <= vsize_out * 2) + val |= SC_VTSEL(0x6C); + else if (vsize_in <= vsize_out * 2 + vsize_out * 3 / 4) + val |= SC_VTSEL(0x6D); + else + val |= SC_VTSEL(0x6E); + + malidp_write32(reg, SC_COEFFTAB, val); +} + +static void d71_scaler_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_scaler_state *st = to_scaler_st(state); + u32 __iomem *reg = c->reg; + u32 init_ph, delta_ph, ctrl; + + d71_scaler_update_filter_lut(reg, st->hsize_in, st->vsize_in, + st->hsize_out, st->vsize_out); + + malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize_in, st->vsize_in)); + malidp_write32(reg, SC_OUT_SIZE, HV_SIZE(st->hsize_out, st->vsize_out)); + malidp_write32(reg, SC_H_CROP, HV_CROP(st->left_crop, st->right_crop)); + + /* for right part, HW only sample the valid pixel which means the pixels + * in left_crop will be jumpped, and the first sample pixel is: + * + * dst_a = st->total_hsize_out - st->hsize_out + st->left_crop + 0.5; + * + * Then the corresponding texel in src is: + * + * h_delta_phase = st->total_hsize_in / st->total_hsize_out; + * src_a = dst_A * h_delta_phase; + * + * and h_init_phase is src_a deduct the real source start src_S; + * + * src_S = st->total_hsize_in - st->hsize_in; + * h_init_phase = src_a - src_S; + * + * And HW precision for the initial/delta_phase is 16:16 fixed point, + * the following is the simplified formula + */ + if (st->right_part) { + u32 dst_a = st->total_hsize_out - st->hsize_out + st->left_crop; + + if (st->en_img_enhancement) + dst_a -= 1; + + init_ph = ((st->total_hsize_in * (2 * dst_a + 1) - + 2 * st->total_hsize_out * (st->total_hsize_in - + st->hsize_in)) << 15) / st->total_hsize_out; + } else { + init_ph = (st->total_hsize_in << 15) / st->total_hsize_out; + } + + malidp_write32(reg, SC_H_INIT_PH, init_ph); + + delta_ph = (st->total_hsize_in << 16) / st->total_hsize_out; + malidp_write32(reg, SC_H_DELTA_PH, delta_ph); + + init_ph = (st->total_vsize_in << 15) / st->vsize_out; + malidp_write32(reg, SC_V_INIT_PH, init_ph); + + delta_ph = (st->total_vsize_in << 16) / st->vsize_out; + malidp_write32(reg, SC_V_DELTA_PH, delta_ph); + + ctrl = 0; + ctrl |= st->en_scaling ? SC_CTRL_SCL : 0; + ctrl |= st->en_alpha ? SC_CTRL_AP : 0; + ctrl |= st->en_img_enhancement ? SC_CTRL_IENH : 0; + /* If we use the hardware splitter we shouldn't set SC_CTRL_LS */ + if (st->en_split && + state->inputs[0].component->id != KOMEDA_COMPONENT_SPLITTER) + ctrl |= SC_CTRL_LS; + + malidp_write32(reg, BLK_CONTROL, ctrl); + malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(state, 0)); +} + +static void d71_scaler_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[9]; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0x80, 1, v); + seq_printf(sf, "SC_INPUT_ID0:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xD0, 1, v); + seq_printf(sf, "SC_CONTROL:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xDC, 9, v); + seq_printf(sf, "SC_COEFFTAB:\t\t0x%X\n", v[0]); + seq_printf(sf, "SC_IN_SIZE:\t\t0x%X\n", v[1]); + seq_printf(sf, "SC_OUT_SIZE:\t\t0x%X\n", v[2]); + seq_printf(sf, "SC_H_CROP:\t\t0x%X\n", v[3]); + seq_printf(sf, "SC_V_CROP:\t\t0x%X\n", v[4]); + seq_printf(sf, "SC_H_INIT_PH:\t\t0x%X\n", v[5]); + seq_printf(sf, "SC_H_DELTA_PH:\t\t0x%X\n", v[6]); + seq_printf(sf, "SC_V_INIT_PH:\t\t0x%X\n", v[7]); + seq_printf(sf, "SC_V_DELTA_PH:\t\t0x%X\n", v[8]); +} + +static const struct komeda_component_funcs d71_scaler_funcs = { + .update = d71_scaler_update, + .disable = d71_component_disable, + .dump_register = d71_scaler_dump, +}; + +static int d71_scaler_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_scaler *scaler; + u32 pipe_id, comp_id; + + get_resources_id(blk->block_info, &pipe_id, &comp_id); + + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*scaler), + comp_id, BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_scaler_funcs, + 1, get_valid_inputs(blk), 1, reg, + "CU%d_SCALER%d", + pipe_id, BLOCK_INFO_BLK_ID(blk->block_info)); + + if (IS_ERR(c)) { + DRM_ERROR("Failed to initialize scaler"); + return PTR_ERR(c); + } + + scaler = to_scaler(c); + set_range(&scaler->hsize, 4, 2048); + set_range(&scaler->vsize, 4, 4096); + scaler->max_downscaling = 6; + scaler->max_upscaling = 64; + scaler->scaling_split_overlap = 8; + scaler->enh_split_overlap = 1; + + malidp_write32(c->reg, BLK_CONTROL, 0); + + return 0; +} + +static int d71_downscaling_clk_check(struct komeda_pipeline *pipe, + struct drm_display_mode *mode, + unsigned long aclk_rate, + struct komeda_data_flow_cfg *dflow) +{ + u32 h_in = dflow->in_w; + u32 v_in = dflow->in_h; + u32 v_out = dflow->out_h; + u64 fraction, denominator; + + /* D71 downscaling must satisfy the following equation + * + * ACLK h_in * v_in + * ------- >= --------------------------------------------- + * PXLCLK (h_total - (1 + 2 * v_in / v_out)) * v_out + * + * In only horizontal downscaling situation, the right side should be + * multiplied by (h_total - 3) / (h_active - 3), then equation becomes + * + * ACLK h_in + * ------- >= ---------------- + * PXLCLK (h_active - 3) + * + * To avoid precision lost the equation 1 will be convert to: + * + * ACLK h_in * v_in + * ------- >= ----------------------------------- + * PXLCLK (h_total -1 ) * v_out - 2 * v_in + */ + if (v_in == v_out) { + fraction = h_in; + denominator = mode->hdisplay - 3; + } else { + fraction = h_in * v_in; + denominator = (mode->htotal - 1) * v_out - 2 * v_in; + } + + return aclk_rate * denominator >= mode->clock * 1000 * fraction ? + 0 : -EINVAL; +} + +static void d71_splitter_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_splitter_state *st = to_splitter_st(state); + u32 __iomem *reg = c->reg; + + malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(state, 0)); + malidp_write32(reg, BLK_SIZE, HV_SIZE(st->hsize, st->vsize)); + malidp_write32(reg, SP_OVERLAP_SIZE, st->overlap & 0x1FFF); + malidp_write32(reg, BLK_CONTROL, BLK_CTRL_EN); +} + +static void d71_splitter_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[3]; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, BLK_INPUT_ID0, 1, v); + seq_printf(sf, "SP_INPUT_ID0:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, BLK_CONTROL, 3, v); + seq_printf(sf, "SP_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "SP_SIZE:\t\t0x%X\n", v[1]); + seq_printf(sf, "SP_OVERLAP_SIZE:\t0x%X\n", v[2]); +} + +static const struct komeda_component_funcs d71_splitter_funcs = { + .update = d71_splitter_update, + .disable = d71_component_disable, + .dump_register = d71_splitter_dump, +}; + +static int d71_splitter_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_splitter *splitter; + u32 pipe_id, comp_id; + + get_resources_id(blk->block_info, &pipe_id, &comp_id); + + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*splitter), + comp_id, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_splitter_funcs, + 1, get_valid_inputs(blk), 2, reg, + "CU%d_SPLITTER", pipe_id); + + if (IS_ERR(c)) { + DRM_ERROR("Failed to initialize splitter"); + return -1; + } + + splitter = to_splitter(c); + + set_range(&splitter->hsize, 4, d71->max_line_size); + set_range(&splitter->vsize, 4, d71->max_vsize); + + return 0; +} + +static void d71_merger_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_merger_state *st = to_merger_st(state); + u32 __iomem *reg = c->reg; + u32 index; + + for_each_changed_input(state, index) + malidp_write32(reg, MG_INPUT_ID0 + index * 4, + to_d71_input_id(state, index)); + + malidp_write32(reg, MG_SIZE, HV_SIZE(st->hsize_merged, + st->vsize_merged)); + malidp_write32(reg, BLK_CONTROL, BLK_CTRL_EN); +} + +static void d71_merger_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, MG_INPUT_ID0, 1, &v); + seq_printf(sf, "MG_INPUT_ID0:\t\t0x%X\n", v); + + get_values_from_reg(c->reg, MG_INPUT_ID1, 1, &v); + seq_printf(sf, "MG_INPUT_ID1:\t\t0x%X\n", v); + + get_values_from_reg(c->reg, BLK_CONTROL, 1, &v); + seq_printf(sf, "MG_CONTROL:\t\t0x%X\n", v); + + get_values_from_reg(c->reg, MG_SIZE, 1, &v); + seq_printf(sf, "MG_SIZE:\t\t0x%X\n", v); +} + +static const struct komeda_component_funcs d71_merger_funcs = { + .update = d71_merger_update, + .disable = d71_component_disable, + .dump_register = d71_merger_dump, +}; + +static int d71_merger_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_merger *merger; + u32 pipe_id, comp_id; + + get_resources_id(blk->block_info, &pipe_id, &comp_id); + + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*merger), + comp_id, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_merger_funcs, + MG_NUM_INPUTS_IDS, get_valid_inputs(blk), + MG_NUM_OUTPUTS_IDS, reg, + "CU%d_MERGER", pipe_id); + + if (IS_ERR(c)) { + DRM_ERROR("Failed to initialize merger.\n"); + return PTR_ERR(c); + } + + merger = to_merger(c); + + set_range(&merger->hsize_merged, 4, 4032); + set_range(&merger->vsize_merged, 4, 4096); + + return 0; +} + static void d71_improc_update(struct komeda_component *c, struct komeda_component_state *state) { struct komeda_improc_state *st = to_improc_st(state); u32 __iomem *reg = c->reg; - u32 index, input_hw_id; + u32 index; - for_each_changed_input(state, index) { - input_hw_id = state->active_inputs & BIT(index) ? - to_d71_input_id(&state->inputs[index]) : 0; - malidp_write32(reg, BLK_INPUT_ID0 + index * 4, input_hw_id); - } + for_each_changed_input(state, index) + malidp_write32(reg, BLK_INPUT_ID0 + index * 4, + to_d71_input_id(state, index)); malidp_write32(reg, BLK_SIZE, HV_SIZE(st->hsize, st->vsize)); } @@ -644,9 +1159,16 @@ int d71_probe_block(struct d71_dev *d71, err = d71_compiz_init(d71, blk, reg); break; - case D71_BLK_TYPE_CU_SPLITTER: case D71_BLK_TYPE_CU_SCALER: + err = d71_scaler_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_CU_SPLITTER: + err = d71_splitter_init(d71, blk, reg); + break; + case D71_BLK_TYPE_CU_MERGER: + err = d71_merger_init(d71, blk, reg); break; case D71_BLK_TYPE_DOU: @@ -683,3 +1205,7 @@ int d71_probe_block(struct d71_dev *d71, return err; } + +const struct komeda_pipeline_funcs d71_pipeline_funcs = { + .downscaling_clk_check = d71_downscaling_clk_check, +}; diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c index 3a7248d42376c8ef9a5aaf0d088eafd4db69cbbd..d567ab7ed314e28d029184cf4b5e3752bed0f336 100644 --- a/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c @@ -280,7 +280,7 @@ static int d71_change_opmode(struct komeda_dev *mdev, int new_mode) ret = dp_wait_cond(((malidp_read32(d71->gcu_addr, BLK_CONTROL) & 0x7) == opmode), 100, 1000, 10000); - return ret > 0 ? 0 : -ETIMEDOUT; + return ret; } static void d71_flush(struct komeda_dev *mdev, @@ -304,7 +304,7 @@ static int d71_reset(struct d71_dev *d71) ret = dp_wait_cond(!(malidp_read32(gcu, BLK_CONTROL) & GCU_CONTROL_SRST), 100, 1000, 10000); - return ret > 0 ? 0 : -ETIMEDOUT; + return ret; } void d71_read_block_header(u32 __iomem *reg, struct block_header *blk) @@ -390,7 +390,7 @@ static int d71_enum_resources(struct komeda_dev *mdev) for (i = 0; i < d71->num_pipelines; i++) { pipe = komeda_pipeline_add(mdev, sizeof(struct d71_pipeline), - NULL); + &d71_pipeline_funcs); if (IS_ERR(pipe)) { err = PTR_ERR(pipe); goto err_cleanup; @@ -447,61 +447,119 @@ static int d71_enum_resources(struct komeda_dev *mdev) #define AFB_TH_SC_YTR_BS AFBC(_TILED | _SC | _SPARSE | _YTR | _SPLIT) static struct komeda_format_caps d71_format_caps_table[] = { - /* HW_ID | fourcc | tile_sz | layer_types | rots | afbc_layouts | afbc_features */ + /* HW_ID | fourcc | layer_types | rots | afbc_layouts | afbc_features */ /* ABGR_2101010*/ - {__HW_ID(0, 0), DRM_FORMAT_ARGB2101010, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ - {__HW_ID(0, 2), DRM_FORMAT_RGBA1010102, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(0, 3), DRM_FORMAT_BGRA1010102, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 0), DRM_FORMAT_ARGB2101010, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ + {__HW_ID(0, 2), DRM_FORMAT_RGBA1010102, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 3), DRM_FORMAT_BGRA1010102, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, /* ABGR_8888*/ - {__HW_ID(1, 0), DRM_FORMAT_ARGB8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(1, 1), DRM_FORMAT_ABGR8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(1, 1), DRM_FORMAT_ABGR8888, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ - {__HW_ID(1, 2), DRM_FORMAT_RGBA8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(1, 3), DRM_FORMAT_BGRA8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 0), DRM_FORMAT_ARGB8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 1), DRM_FORMAT_ABGR8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 1), DRM_FORMAT_ABGR8888, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ + {__HW_ID(1, 2), DRM_FORMAT_RGBA8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 3), DRM_FORMAT_BGRA8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, /* XBGB_8888 */ - {__HW_ID(2, 0), DRM_FORMAT_XRGB8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(2, 1), DRM_FORMAT_XBGR8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(2, 2), DRM_FORMAT_RGBX8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, - {__HW_ID(2, 3), DRM_FORMAT_BGRX8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 0), DRM_FORMAT_XRGB8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 1), DRM_FORMAT_XBGR8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 2), DRM_FORMAT_RGBX8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 3), DRM_FORMAT_BGRX8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, /* BGR_888 */ /* none-afbc RGB888 doesn't support rotation and flip */ - {__HW_ID(3, 0), DRM_FORMAT_RGB888, 1, RICH_SIMPLE_WB, Rot_0, 0, 0}, - {__HW_ID(3, 1), DRM_FORMAT_BGR888, 1, RICH_SIMPLE_WB, Rot_0, 0, 0}, - {__HW_ID(3, 1), DRM_FORMAT_BGR888, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ + {__HW_ID(3, 0), DRM_FORMAT_RGB888, RICH_SIMPLE_WB, Rot_0, 0, 0}, + {__HW_ID(3, 1), DRM_FORMAT_BGR888, RICH_SIMPLE_WB, Rot_0, 0, 0}, + {__HW_ID(3, 1), DRM_FORMAT_BGR888, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ /* BGR 16bpp */ - {__HW_ID(4, 0), DRM_FORMAT_RGBA5551, 1, RICH_SIMPLE, Flip_H_V, 0, 0}, - {__HW_ID(4, 1), DRM_FORMAT_ABGR1555, 1, RICH_SIMPLE, Flip_H_V, 0, 0}, - {__HW_ID(4, 1), DRM_FORMAT_ABGR1555, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */ - {__HW_ID(4, 2), DRM_FORMAT_RGB565, 1, RICH_SIMPLE, Flip_H_V, 0, 0}, - {__HW_ID(4, 3), DRM_FORMAT_BGR565, 1, RICH_SIMPLE, Flip_H_V, 0, 0}, - {__HW_ID(4, 3), DRM_FORMAT_BGR565, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */ - {__HW_ID(4, 4), DRM_FORMAT_R8, 1, SIMPLE, Rot_0, 0, 0}, + {__HW_ID(4, 0), DRM_FORMAT_RGBA5551, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 1), DRM_FORMAT_ABGR1555, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 1), DRM_FORMAT_ABGR1555, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */ + {__HW_ID(4, 2), DRM_FORMAT_RGB565, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 3), DRM_FORMAT_BGR565, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 3), DRM_FORMAT_BGR565, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */ + {__HW_ID(4, 4), DRM_FORMAT_R8, SIMPLE, Rot_0, 0, 0}, /* YUV 444/422/420 8bit */ - {__HW_ID(5, 0), 0 /*XYUV8888*/, 1, 0, 0, 0, 0}, - /* XYUV unsupported*/ - {__HW_ID(5, 1), DRM_FORMAT_YUYV, 1, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */ - {__HW_ID(5, 2), DRM_FORMAT_YUYV, 1, RICH, Flip_H_V, 0, 0}, - {__HW_ID(5, 3), DRM_FORMAT_UYVY, 1, RICH, Flip_H_V, 0, 0}, - {__HW_ID(5, 4), 0, /*X0L0 */ 2, 0, 0, 0}, /* Y0L0 unsupported */ - {__HW_ID(5, 6), DRM_FORMAT_NV12, 1, RICH, Flip_H_V, 0, 0}, - {__HW_ID(5, 6), 0/*DRM_FORMAT_YUV420_8BIT*/, 1, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */ - {__HW_ID(5, 7), DRM_FORMAT_YUV420, 1, RICH, Flip_H_V, 0, 0}, + {__HW_ID(5, 1), DRM_FORMAT_YUYV, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */ + {__HW_ID(5, 2), DRM_FORMAT_YUYV, RICH, Flip_H_V, 0, 0}, + {__HW_ID(5, 3), DRM_FORMAT_UYVY, RICH, Flip_H_V, 0, 0}, + {__HW_ID(5, 6), DRM_FORMAT_NV12, RICH, Flip_H_V, 0, 0}, + {__HW_ID(5, 6), DRM_FORMAT_YUV420_8BIT, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */ + {__HW_ID(5, 7), DRM_FORMAT_YUV420, RICH, Flip_H_V, 0, 0}, /* YUV 10bit*/ - {__HW_ID(6, 0), 0,/*XVYU2101010*/ 1, 0, 0, 0, 0},/* VYV30 unsupported */ - {__HW_ID(6, 6), 0/*DRM_FORMAT_X0L2*/, 2, RICH, Flip_H_V, 0, 0}, - {__HW_ID(6, 7), 0/*DRM_FORMAT_P010*/, 1, RICH, Flip_H_V, 0, 0}, - {__HW_ID(6, 7), 0/*DRM_FORMAT_YUV420_10BIT*/, 1, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, + {__HW_ID(6, 6), DRM_FORMAT_X0L2, RICH, Flip_H_V, 0, 0}, + {__HW_ID(6, 7), DRM_FORMAT_P010, RICH, Flip_H_V, 0, 0}, + {__HW_ID(6, 7), DRM_FORMAT_YUV420_10BIT, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, }; +static bool d71_format_mod_supported(const struct komeda_format_caps *caps, + u32 layer_type, u64 modifier, u32 rot) +{ + uint64_t layout = modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK; + + if ((layout == AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) && + drm_rotation_90_or_270(rot)) { + DRM_DEBUG_ATOMIC("D71 doesn't support ROT90 for WB-AFBC.\n"); + return false; + } + + return true; +} + static void d71_init_fmt_tbl(struct komeda_dev *mdev) { struct komeda_format_caps_table *table = &mdev->fmt_tbl; table->format_caps = d71_format_caps_table; + table->format_mod_supported = d71_format_mod_supported; table->n_formats = ARRAY_SIZE(d71_format_caps_table); } +static int d71_connect_iommu(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 __iomem *reg = d71->gcu_addr; + u32 check_bits = (d71->num_pipelines == 2) ? + GCU_STATUS_TCS0 | GCU_STATUS_TCS1 : GCU_STATUS_TCS0; + int i, ret; + + if (!d71->integrates_tbu) + return -1; + + malidp_write32_mask(reg, BLK_CONTROL, 0x7, TBU_CONNECT_MODE); + + ret = dp_wait_cond(has_bits(check_bits, malidp_read32(reg, BLK_STATUS)), + 100, 1000, 1000); + if (ret < 0) { + DRM_ERROR("timed out connecting to TCU!\n"); + malidp_write32_mask(reg, BLK_CONTROL, 0x7, INACTIVE_MODE); + return ret; + } + + for (i = 0; i < d71->num_pipelines; i++) + malidp_write32_mask(d71->pipes[i]->lpu_addr, LPU_TBU_CONTROL, + LPU_TBU_CTRL_TLBPEN, LPU_TBU_CTRL_TLBPEN); + return 0; +} + +static int d71_disconnect_iommu(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 __iomem *reg = d71->gcu_addr; + u32 check_bits = (d71->num_pipelines == 2) ? + GCU_STATUS_TCS0 | GCU_STATUS_TCS1 : GCU_STATUS_TCS0; + int ret; + + malidp_write32_mask(reg, BLK_CONTROL, 0x7, TBU_DISCONNECT_MODE); + + ret = dp_wait_cond(((malidp_read32(reg, BLK_STATUS) & check_bits) == 0), + 100, 1000, 1000); + if (ret < 0) { + DRM_ERROR("timed out disconnecting from TCU!\n"); + malidp_write32_mask(reg, BLK_CONTROL, 0x7, INACTIVE_MODE); + } + + return ret; +} + static const struct komeda_dev_funcs d71_chip_funcs = { .init_format_table = d71_init_fmt_tbl, .enum_resources = d71_enum_resources, @@ -512,6 +570,8 @@ static const struct komeda_dev_funcs d71_chip_funcs = { .on_off_vblank = d71_on_off_vblank, .change_opmode = d71_change_opmode, .flush = d71_flush, + .connect_iommu = d71_connect_iommu, + .disconnect_iommu = d71_disconnect_iommu, }; const struct komeda_dev_funcs * diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h index 7465c57d97745911865fb5ef45f07f88707475a4..84f1878b647d6f4d4683f7020da9f84f57fa0ef8 100644 --- a/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h @@ -43,6 +43,8 @@ struct d71_dev { #define to_d71_pipeline(x) container_of(x, struct d71_pipeline, base) +extern const struct komeda_pipeline_funcs d71_pipeline_funcs; + int d71_probe_block(struct d71_dev *d71, struct block_header *blk, u32 __iomem *reg); void d71_read_block_header(u32 __iomem *reg, struct block_header *blk); diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c new file mode 100644 index 0000000000000000000000000000000000000000..9d14a92dbb17a3a84c9ed8d57b830a8f5a603e77 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#include "komeda_color_mgmt.h" + +/* 10bit precision YUV2RGB matrix */ +static const s32 yuv2rgb_bt601_narrow[KOMEDA_N_YUV2RGB_COEFFS] = { + 1192, 0, 1634, + 1192, -401, -832, + 1192, 2066, 0, + 64, 512, 512 +}; + +static const s32 yuv2rgb_bt601_wide[KOMEDA_N_YUV2RGB_COEFFS] = { + 1024, 0, 1436, + 1024, -352, -731, + 1024, 1815, 0, + 0, 512, 512 +}; + +static const s32 yuv2rgb_bt709_narrow[KOMEDA_N_YUV2RGB_COEFFS] = { + 1192, 0, 1836, + 1192, -218, -546, + 1192, 2163, 0, + 64, 512, 512 +}; + +static const s32 yuv2rgb_bt709_wide[KOMEDA_N_YUV2RGB_COEFFS] = { + 1024, 0, 1613, + 1024, -192, -479, + 1024, 1900, 0, + 0, 512, 512 +}; + +static const s32 yuv2rgb_bt2020[KOMEDA_N_YUV2RGB_COEFFS] = { + 1024, 0, 1476, + 1024, -165, -572, + 1024, 1884, 0, + 0, 512, 512 +}; + +const s32 *komeda_select_yuv2rgb_coeffs(u32 color_encoding, u32 color_range) +{ + bool narrow = color_range == DRM_COLOR_YCBCR_LIMITED_RANGE; + const s32 *coeffs; + + switch (color_encoding) { + case DRM_COLOR_YCBCR_BT709: + coeffs = narrow ? yuv2rgb_bt709_narrow : yuv2rgb_bt709_wide; + break; + case DRM_COLOR_YCBCR_BT601: + coeffs = narrow ? yuv2rgb_bt601_narrow : yuv2rgb_bt601_wide; + break; + case DRM_COLOR_YCBCR_BT2020: + coeffs = yuv2rgb_bt2020; + break; + default: + coeffs = NULL; + break; + } + + return coeffs; +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h new file mode 100644 index 0000000000000000000000000000000000000000..a2df218f58e79c8b4045e6d8e6f59aa144e12a27 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#ifndef _KOMEDA_COLOR_MGMT_H_ +#define _KOMEDA_COLOR_MGMT_H_ + +#include + +#define KOMEDA_N_YUV2RGB_COEFFS 12 + +const s32 *komeda_select_yuv2rgb_coeffs(u32 color_encoding, u32 color_range); + +#endif diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c b/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c index 284ce079d8c49d26cc0aa97d10d1ff2b6553f845..3f222f464eb2f2d15b37f52488cb8aadd0cea5f5 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c @@ -18,6 +18,21 @@ #include "komeda_dev.h" #include "komeda_kms.h" +static void komeda_crtc_update_clock_ratio(struct komeda_crtc_state *kcrtc_st) +{ + u64 pxlclk, aclk; + + if (!kcrtc_st->base.active) { + kcrtc_st->clock_ratio = 0; + return; + } + + pxlclk = kcrtc_st->base.adjusted_mode.clock * 1000; + aclk = komeda_calc_aclk(kcrtc_st); + + kcrtc_st->clock_ratio = div64_u64(aclk << 32, pxlclk); +} + /** * komeda_crtc_atomic_check - build display output data flow * @crtc: DRM crtc @@ -38,6 +53,9 @@ komeda_crtc_atomic_check(struct drm_crtc *crtc, struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(state); int err; + if (drm_atomic_crtc_needs_modeset(state)) + komeda_crtc_update_clock_ratio(kcrtc_st); + if (state->active) { err = komeda_build_display_data_flow(kcrtc, kcrtc_st); if (err) @@ -45,6 +63,10 @@ komeda_crtc_atomic_check(struct drm_crtc *crtc, } /* release unclaimed pipeline resources */ + err = komeda_release_unclaimed_resources(kcrtc->slave, kcrtc_st); + if (err) + return err; + err = komeda_release_unclaimed_resources(kcrtc->master, kcrtc_st); if (err) return err; @@ -52,11 +74,12 @@ komeda_crtc_atomic_check(struct drm_crtc *crtc, return 0; } -static u32 komeda_calc_mclk(struct komeda_crtc_state *kcrtc_st) +unsigned long komeda_calc_aclk(struct komeda_crtc_state *kcrtc_st) { - unsigned long mclk = kcrtc_st->base.adjusted_mode.clock * 1000; + struct komeda_dev *mdev = kcrtc_st->base.crtc->dev->dev_private; + unsigned long pxlclk = kcrtc_st->base.adjusted_mode.clock; - return mclk; + return clk_round_rate(mdev->aclk, pxlclk * 1000); } /* For active a crtc, mainly need two parts of preparation @@ -89,23 +112,20 @@ komeda_crtc_prepare(struct komeda_crtc *kcrtc) } mdev->dpmode = new_mode; - /* Only need to enable mclk on single display mode, but no need to - * enable mclk it on dual display mode, since the dual mode always - * switch from single display mode, the mclk already enabled, no need + /* Only need to enable aclk on single display mode, but no need to + * enable aclk it on dual display mode, since the dual mode always + * switch from single display mode, the aclk already enabled, no need * to enable it again. */ if (new_mode != KOMEDA_MODE_DUAL_DISP) { - err = clk_set_rate(mdev->mclk, komeda_calc_mclk(kcrtc_st)); + err = clk_set_rate(mdev->aclk, komeda_calc_aclk(kcrtc_st)); if (err) - DRM_ERROR("failed to set mclk.\n"); - err = clk_prepare_enable(mdev->mclk); + DRM_ERROR("failed to set aclk.\n"); + err = clk_prepare_enable(mdev->aclk); if (err) - DRM_ERROR("failed to enable mclk.\n"); + DRM_ERROR("failed to enable aclk.\n"); } - err = clk_prepare_enable(master->aclk); - if (err) - DRM_ERROR("failed to enable axi clk for pipe%d.\n", master->id); err = clk_set_rate(master->pxlclk, pxlclk_rate); if (err) DRM_ERROR("failed to set pxlclk for pipe%d\n", master->id); @@ -146,9 +166,8 @@ komeda_crtc_unprepare(struct komeda_crtc *kcrtc) mdev->dpmode = new_mode; clk_disable_unprepare(master->pxlclk); - clk_disable_unprepare(master->aclk); if (new_mode == KOMEDA_MODE_INACTIVE) - clk_disable_unprepare(mdev->mclk); + clk_disable_unprepare(mdev->aclk); unlock: mutex_unlock(&mdev->lock); @@ -165,6 +184,15 @@ void komeda_crtc_handle_event(struct komeda_crtc *kcrtc, if (events & KOMEDA_EVENT_VSYNC) drm_crtc_handle_vblank(crtc); + if (events & KOMEDA_EVENT_EOW) { + struct komeda_wb_connector *wb_conn = kcrtc->wb_conn; + + if (wb_conn) + drm_writeback_signal_completion(&wb_conn->base, 0); + else + DRM_WARN("CRTC[%d]: EOW happen but no wb_connector.\n", + drm_crtc_index(&kcrtc->base)); + } /* will handle it together with the write back support */ if (events & KOMEDA_EVENT_EOW) DRM_DEBUG("EOW.\n"); @@ -201,6 +229,9 @@ komeda_crtc_do_flush(struct drm_crtc *crtc, struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(crtc->state); struct komeda_dev *mdev = kcrtc->base.dev->dev_private; struct komeda_pipeline *master = kcrtc->master; + struct komeda_pipeline *slave = kcrtc->slave; + struct komeda_wb_connector *wb_conn = kcrtc->wb_conn; + struct drm_connector_state *conn_st; DRM_DEBUG_ATOMIC("CRTC%d_FLUSH: active_pipes: 0x%x, affected: 0x%x.\n", drm_crtc_index(crtc), @@ -210,6 +241,13 @@ komeda_crtc_do_flush(struct drm_crtc *crtc, if (has_bit(master->id, kcrtc_st->affected_pipes)) komeda_pipeline_update(master, old->state); + if (slave && has_bit(slave->id, kcrtc_st->affected_pipes)) + komeda_pipeline_update(slave, old->state); + + conn_st = wb_conn ? wb_conn->base.base.state : NULL; + if (conn_st && conn_st->writeback_job) + drm_writeback_queue_job(&wb_conn->base, conn_st); + /* step 2: notify the HW to kickoff the update */ mdev->funcs->flush(mdev, master->id, kcrtc_st->active_pipes); } @@ -231,6 +269,7 @@ komeda_crtc_atomic_disable(struct drm_crtc *crtc, struct komeda_crtc_state *old_st = to_kcrtc_st(old); struct komeda_dev *mdev = crtc->dev->dev_private; struct komeda_pipeline *master = kcrtc->master; + struct komeda_pipeline *slave = kcrtc->slave; struct completion *disable_done = &crtc->state->commit->flip_done; struct completion temp; int timeout; @@ -239,6 +278,9 @@ komeda_crtc_atomic_disable(struct drm_crtc *crtc, drm_crtc_index(crtc), old_st->active_pipes, old_st->affected_pipes); + if (slave && has_bit(slave->id, old_st->active_pipes)) + komeda_pipeline_disable(slave, old->state); + if (has_bit(master->id, old_st->active_pipes)) komeda_pipeline_disable(master, old->state); @@ -311,7 +353,6 @@ komeda_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *m) if (m->flags & DRM_MODE_FLAG_INTERLACE) return MODE_NO_INTERLACE; - /* main clock/AXI clk must be faster than pxlclk*/ mode_clk = m->clock * 1000; pxlclk = clk_round_rate(master->pxlclk, mode_clk); if (pxlclk != mode_clk) { @@ -320,15 +361,9 @@ komeda_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *m) return MODE_NOCLOCK; } - if (clk_round_rate(mdev->mclk, mode_clk) < pxlclk) { - DRM_DEBUG_ATOMIC("mclk can't satisfy the requirement of %s-clk: %ld.\n", - m->name, pxlclk); - - return MODE_CLOCK_HIGH; - } - - if (clk_round_rate(master->aclk, mode_clk) < pxlclk) { - DRM_DEBUG_ATOMIC("aclk can't satisfy the requirement of %s-clk: %ld.\n", + /* main engine clock must be faster than pxlclk*/ + if (clk_round_rate(mdev->aclk, mode_clk) < pxlclk) { + DRM_DEBUG_ATOMIC("engine clk can't satisfy the requirement of %s-clk: %ld.\n", m->name, pxlclk); return MODE_CLOCK_HIGH; @@ -389,6 +424,8 @@ komeda_crtc_atomic_duplicate_state(struct drm_crtc *crtc) __drm_atomic_helper_crtc_duplicate_state(crtc, &new->base); new->affected_pipes = old->active_pipes; + new->clock_ratio = old->clock_ratio; + new->max_slave_zorder = old->max_slave_zorder; return &new->base; } @@ -417,6 +454,24 @@ static void komeda_crtc_vblank_disable(struct drm_crtc *crtc) mdev->funcs->on_off_vblank(mdev, kcrtc->master->id, false); } +static int +komeda_crtc_atomic_get_property(struct drm_crtc *crtc, + const struct drm_crtc_state *state, + struct drm_property *property, uint64_t *val) +{ + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(state); + + if (property == kcrtc->clock_ratio_property) { + *val = kcrtc_st->clock_ratio; + } else { + DRM_DEBUG_DRIVER("Unknown property %s\n", property->name); + return -EINVAL; + } + + return 0; +} + static const struct drm_crtc_funcs komeda_crtc_funcs = { .gamma_set = drm_atomic_helper_legacy_gamma_set, .destroy = drm_crtc_cleanup, @@ -427,6 +482,7 @@ static const struct drm_crtc_funcs komeda_crtc_funcs = { .atomic_destroy_state = komeda_crtc_atomic_destroy_state, .enable_vblank = komeda_crtc_vblank_enable, .disable_vblank = komeda_crtc_vblank_disable, + .atomic_get_property = komeda_crtc_atomic_get_property, }; int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms, @@ -444,7 +500,7 @@ int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms, master = mdev->pipelines[i]; crtc->master = master; - crtc->slave = NULL; + crtc->slave = komeda_pipeline_get_slave(master); if (crtc->slave) sprintf(str, "pipe-%d", crtc->slave->id); @@ -462,6 +518,42 @@ int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms, return 0; } +static int komeda_crtc_create_clock_ratio_property(struct komeda_crtc *kcrtc) +{ + struct drm_crtc *crtc = &kcrtc->base; + struct drm_property *prop; + + prop = drm_property_create_range(crtc->dev, DRM_MODE_PROP_ATOMIC, + "CLOCK_RATIO", 0, U64_MAX); + if (!prop) + return -ENOMEM; + + drm_object_attach_property(&crtc->base, prop, 0); + kcrtc->clock_ratio_property = prop; + + return 0; +} + +static int komeda_crtc_create_slave_planes_property(struct komeda_crtc *kcrtc) +{ + struct drm_crtc *crtc = &kcrtc->base; + struct drm_property *prop; + + if (kcrtc->slave_planes == 0) + return 0; + + prop = drm_property_create_range(crtc->dev, DRM_MODE_PROP_IMMUTABLE, + "slave_planes", 0, U32_MAX); + if (!prop) + return -ENOMEM; + + drm_object_attach_property(&crtc->base, prop, kcrtc->slave_planes); + + kcrtc->slave_planes_property = prop; + + return 0; +} + static struct drm_plane * get_crtc_primary(struct komeda_kms_dev *kms, struct komeda_crtc *crtc) { @@ -498,7 +590,15 @@ static int komeda_crtc_add(struct komeda_kms_dev *kms, crtc->port = kcrtc->master->of_output_port; - return 0; + err = komeda_crtc_create_clock_ratio_property(kcrtc); + if (err) + return err; + + err = komeda_crtc_create_slave_planes_property(kcrtc); + if (err) + return err; + + return err; } int komeda_kms_add_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev) diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_dev.c b/drivers/gpu/drm/arm/display/komeda/komeda_dev.c index b67030a9f05684d120b9458a22a75cd8a322be49..5a118984de33d558f082855e08f2c01df0abcb3d 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_dev.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_dev.c @@ -5,6 +5,7 @@ * */ #include +#include #include #include #include @@ -52,9 +53,6 @@ static void komeda_debugfs_init(struct komeda_dev *mdev) return; mdev->debugfs_root = debugfs_create_dir("komeda", NULL); - if (IS_ERR_OR_NULL(mdev->debugfs_root)) - return; - debugfs_create_file("register", 0444, mdev->debugfs_root, mdev, &komeda_register_fops); } @@ -115,13 +113,6 @@ static int komeda_parse_pipe_dt(struct komeda_dev *mdev, struct device_node *np) pipe = mdev->pipelines[pipe_id]; - clk = of_clk_get_by_name(np, "aclk"); - if (IS_ERR(clk)) { - DRM_ERROR("get aclk for pipeline %d failed!\n", pipe_id); - return PTR_ERR(clk); - } - pipe->aclk = clk; - clk = of_clk_get_by_name(np, "pxclk"); if (IS_ERR(clk)) { DRM_ERROR("get pxclk for pipeline %d failed!\n", pipe_id); @@ -144,14 +135,8 @@ static int komeda_parse_dt(struct device *dev, struct komeda_dev *mdev) { struct platform_device *pdev = to_platform_device(dev); struct device_node *child, *np = dev->of_node; - struct clk *clk; int ret; - clk = devm_clk_get(dev, "mclk"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - mdev->mclk = clk; mdev->irq = platform_get_irq(pdev, 0); if (mdev->irq < 0) { DRM_ERROR("could not get IRQ number.\n"); @@ -205,16 +190,15 @@ struct komeda_dev *komeda_dev_create(struct device *dev) goto err_cleanup; } - mdev->pclk = devm_clk_get(dev, "pclk"); - if (IS_ERR(mdev->pclk)) { - DRM_ERROR("Get APB clk failed.\n"); - err = PTR_ERR(mdev->pclk); - mdev->pclk = NULL; + mdev->aclk = devm_clk_get(dev, "aclk"); + if (IS_ERR(mdev->aclk)) { + DRM_ERROR("Get engine clk failed.\n"); + err = PTR_ERR(mdev->aclk); + mdev->aclk = NULL; goto err_cleanup; } - /* Enable APB clock to access the registers */ - clk_prepare_enable(mdev->pclk); + clk_prepare_enable(mdev->aclk); mdev->funcs = product->identify(mdev->reg_base, &mdev->chip); if (!komeda_product_match(mdev, product->product_id)) { @@ -253,6 +237,18 @@ struct komeda_dev *komeda_dev_create(struct device *dev) dev->dma_parms = &mdev->dma_parms; dma_set_max_seg_size(dev, DMA_BIT_MASK(32)); + mdev->iommu = iommu_get_domain_for_dev(mdev->dev); + if (!mdev->iommu) + DRM_INFO("continue without IOMMU support!\n"); + + if (mdev->iommu && mdev->funcs->connect_iommu) { + err = mdev->funcs->connect_iommu(mdev); + if (err) { + mdev->iommu = NULL; + goto err_cleanup; + } + } + err = sysfs_create_group(&dev->kobj, &komeda_sysfs_attr_group); if (err) { DRM_ERROR("create sysfs group failed.\n"); @@ -282,6 +278,10 @@ void komeda_dev_destroy(struct komeda_dev *mdev) debugfs_remove_recursive(mdev->debugfs_root); #endif + if (mdev->iommu && mdev->funcs->disconnect_iommu) + mdev->funcs->disconnect_iommu(mdev); + mdev->iommu = NULL; + for (i = 0; i < mdev->n_pipelines; i++) { komeda_pipeline_destroy(mdev, mdev->pipelines[i]); mdev->pipelines[i] = NULL; @@ -297,15 +297,10 @@ void komeda_dev_destroy(struct komeda_dev *mdev) mdev->reg_base = NULL; } - if (mdev->mclk) { - devm_clk_put(dev, mdev->mclk); - mdev->mclk = NULL; - } - - if (mdev->pclk) { - clk_disable_unprepare(mdev->pclk); - devm_clk_put(dev, mdev->pclk); - mdev->pclk = NULL; + if (mdev->aclk) { + clk_disable_unprepare(mdev->aclk); + devm_clk_put(dev, mdev->aclk); + mdev->aclk = NULL; } devm_kfree(dev, mdev); diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_dev.h b/drivers/gpu/drm/arm/display/komeda/komeda_dev.h index 973fd5e0eb98ef5fbd897a8a398410673d8b345a..d1c86b6174c808a109b9718e79575d9f6096fb92 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_dev.h +++ b/drivers/gpu/drm/arm/display/komeda/komeda_dev.h @@ -92,6 +92,10 @@ struct komeda_dev_funcs { int (*enum_resources)(struct komeda_dev *mdev); /** @cleanup: call to chip to cleanup komeda_dev->chip data */ void (*cleanup)(struct komeda_dev *mdev); + /** @connect_iommu: Optional, connect to external iommu */ + int (*connect_iommu)(struct komeda_dev *mdev); + /** @disconnect_iommu: Optional, disconnect to external iommu */ + int (*disconnect_iommu)(struct komeda_dev *mdev); /** * @irq_handler: * @@ -156,10 +160,8 @@ struct komeda_dev { struct komeda_chip_info chip; /** @fmt_tbl: initialized by &komeda_dev_funcs->init_format_table */ struct komeda_format_caps_table fmt_tbl; - /** @pclk: APB clock for register access */ - struct clk *pclk; - /** @mclk: HW main engine clk */ - struct clk *mclk; + /** @aclk: HW main engine clk */ + struct clk *aclk; /** @irq: irq number */ int irq; @@ -184,6 +186,9 @@ struct komeda_dev { */ void *chip_data; + /** @iommu: iommu domain */ + struct iommu_domain *iommu; + /** @debugfs_root: root directory of komeda debugfs */ struct dentry *debugfs_root; }; diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c index 1e17bd6107a46cd5d7c82b7bfe1819a22c7e521c..cd4d9f53ddef280340898e0c35ff6abdefdcf4a1 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c @@ -35,6 +35,64 @@ komeda_get_format_caps(struct komeda_format_caps_table *table, return NULL; } +/* Two assumptions + * 1. RGB always has YTR + * 2. Tiled RGB always has SC + */ +u64 komeda_supported_modifiers[] = { + /* AFBC_16x16 + features: YUV+RGB both */ + AFBC_16x16(0), + /* SPARSE */ + AFBC_16x16(_SPARSE), + /* YTR + (SPARSE) */ + AFBC_16x16(_YTR | _SPARSE), + AFBC_16x16(_YTR), + /* SPLIT + SPARSE + YTR RGB only */ + /* split mode is only allowed for sparse mode */ + AFBC_16x16(_SPLIT | _SPARSE | _YTR), + /* TILED + (SPARSE) */ + /* TILED YUV format only */ + AFBC_16x16(_TILED | _SPARSE), + AFBC_16x16(_TILED), + /* TILED + SC + (SPLIT+SPARSE | SPARSE) + (YTR) */ + AFBC_16x16(_TILED | _SC | _SPLIT | _SPARSE | _YTR), + AFBC_16x16(_TILED | _SC | _SPARSE | _YTR), + AFBC_16x16(_TILED | _SC | _YTR), + /* AFBC_32x8 + features: which are RGB formats only */ + /* YTR + (SPARSE) */ + AFBC_32x8(_YTR | _SPARSE), + AFBC_32x8(_YTR), + /* SPLIT + SPARSE + (YTR) */ + /* split mode is only allowed for sparse mode */ + AFBC_32x8(_SPLIT | _SPARSE | _YTR), + /* TILED + SC + (SPLIT+SPARSE | SPARSE) + YTR */ + AFBC_32x8(_TILED | _SC | _SPLIT | _SPARSE | _YTR), + AFBC_32x8(_TILED | _SC | _SPARSE | _YTR), + AFBC_32x8(_TILED | _SC | _YTR), + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +bool komeda_format_mod_supported(struct komeda_format_caps_table *table, + u32 layer_type, u32 fourcc, u64 modifier, + u32 rot) +{ + const struct komeda_format_caps *caps; + + caps = komeda_get_format_caps(table, fourcc, modifier); + if (!caps) + return false; + + if (!(caps->supported_layer_types & layer_type)) + return false; + + if (table->format_mod_supported) + return table->format_mod_supported(caps, layer_type, modifier, + rot); + + return true; +} + u32 *komeda_get_layer_fourcc_list(struct komeda_format_caps_table *table, u32 layer_type, u32 *n_fmts) { diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h index 60f39e77b098589474797ef2db53486238089a13..3631910d33b5e4a5e83db4140921cf432265c4ef 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h +++ b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h @@ -50,7 +50,6 @@ * * @hw_id: hw format id, hw specific value. * @fourcc: drm fourcc format. - * @tile_size: format tiled size, used by ARM format X0L0/X0L2 * @supported_layer_types: indicate which layer supports this format * @supported_rots: allowed rotations for this format * @supported_afbc_layouts: supported afbc layerout @@ -59,7 +58,6 @@ struct komeda_format_caps { u32 hw_id; u32 fourcc; - u32 tile_size; u32 supported_layer_types; u32 supported_rots; u32 supported_afbc_layouts; @@ -71,12 +69,30 @@ struct komeda_format_caps { * * @n_formats: the size of format_caps list. * @format_caps: format_caps list. + * @format_mod_supported: Optional. Some HW may have special requirements or + * limitations which can not be described by format_caps, this func supply HW + * the ability to do the further HW specific check. */ struct komeda_format_caps_table { u32 n_formats; const struct komeda_format_caps *format_caps; + bool (*format_mod_supported)(const struct komeda_format_caps *caps, + u32 layer_type, u64 modifier, u32 rot); }; +extern u64 komeda_supported_modifiers[]; + +static inline const char *komeda_get_format_name(u32 fourcc, u64 modifier) +{ + struct drm_format_name_buf buf; + static char name[64]; + + snprintf(name, sizeof(name), "%s with modifier: 0x%llx.", + drm_get_format_name(fourcc, &buf), modifier); + + return name; +} + const struct komeda_format_caps * komeda_get_format_caps(struct komeda_format_caps_table *table, u32 fourcc, u64 modifier); @@ -86,4 +102,8 @@ u32 *komeda_get_layer_fourcc_list(struct komeda_format_caps_table *table, void komeda_put_fourcc_list(u32 *fourcc_list); +bool komeda_format_mod_supported(struct komeda_format_caps_table *table, + u32 layer_type, u32 fourcc, u64 modifier, + u32 rot); + #endif diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c index 9cc9935024f7713a345f6b8d0fc87dc948f2261e..3b0a70ed6aa0fb078748f82b69e44edc4d843860 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c @@ -36,52 +36,112 @@ static const struct drm_framebuffer_funcs komeda_fb_funcs = { .create_handle = komeda_fb_create_handle, }; +static int +komeda_fb_afbc_size_check(struct komeda_fb *kfb, struct drm_file *file, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + struct drm_gem_object *obj; + u32 alignment_w = 0, alignment_h = 0, alignment_header, n_blocks; + u64 min_size; + + obj = drm_gem_object_lookup(file, mode_cmd->handles[0]); + if (!obj) { + DRM_DEBUG_KMS("Failed to lookup GEM object\n"); + return -ENOENT; + } + + switch (fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) { + case AFBC_FORMAT_MOD_BLOCK_SIZE_32x8: + alignment_w = 32; + alignment_h = 8; + break; + case AFBC_FORMAT_MOD_BLOCK_SIZE_16x16: + alignment_w = 16; + alignment_h = 16; + break; + default: + WARN(1, "Invalid AFBC_FORMAT_MOD_BLOCK_SIZE: %lld.\n", + fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK); + break; + } + + /* tiled header afbc */ + if (fb->modifier & AFBC_FORMAT_MOD_TILED) { + alignment_w *= AFBC_TH_LAYOUT_ALIGNMENT; + alignment_h *= AFBC_TH_LAYOUT_ALIGNMENT; + alignment_header = AFBC_TH_BODY_START_ALIGNMENT; + } else { + alignment_header = AFBC_BODY_START_ALIGNMENT; + } + + kfb->aligned_w = ALIGN(fb->width, alignment_w); + kfb->aligned_h = ALIGN(fb->height, alignment_h); + + if (fb->offsets[0] % alignment_header) { + DRM_DEBUG_KMS("afbc offset alignment check failed.\n"); + goto check_failed; + } + + n_blocks = (kfb->aligned_w * kfb->aligned_h) / AFBC_SUPERBLK_PIXELS; + kfb->offset_payload = ALIGN(n_blocks * AFBC_HEADER_SIZE, + alignment_header); + + kfb->afbc_size = kfb->offset_payload + n_blocks * + ALIGN(info->cpp[0] * AFBC_SUPERBLK_PIXELS, + AFBC_SUPERBLK_ALIGNMENT); + min_size = kfb->afbc_size + fb->offsets[0]; + if (min_size > obj->size) { + DRM_DEBUG_KMS("afbc size check failed, obj_size: 0x%zx. min_size 0x%llx.\n", + obj->size, min_size); + goto check_failed; + } + + fb->obj[0] = obj; + return 0; + +check_failed: + drm_gem_object_put_unlocked(obj); + return -EINVAL; +} + static int komeda_fb_none_afbc_size_check(struct komeda_dev *mdev, struct komeda_fb *kfb, struct drm_file *file, const struct drm_mode_fb_cmd2 *mode_cmd) { struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; struct drm_gem_object *obj; - u32 min_size = 0; - u32 i; + u32 i, block_h; + u64 min_size; + + if (komeda_fb_check_src_coords(kfb, 0, 0, fb->width, fb->height)) + return -EINVAL; - for (i = 0; i < fb->format->num_planes; i++) { + for (i = 0; i < info->num_planes; i++) { obj = drm_gem_object_lookup(file, mode_cmd->handles[i]); if (!obj) { DRM_DEBUG_KMS("Failed to lookup GEM object\n"); - fb->obj[i] = NULL; - return -ENOENT; } + fb->obj[i] = obj; - kfb->aligned_w = fb->width / (i ? fb->format->hsub : 1); - kfb->aligned_h = fb->height / (i ? fb->format->vsub : 1); - - if (fb->pitches[i] % mdev->chip.bus_width) { + block_h = drm_format_info_block_height(info, i); + if ((fb->pitches[i] * block_h) % mdev->chip.bus_width) { DRM_DEBUG_KMS("Pitch[%d]: 0x%x doesn't align to 0x%x\n", i, fb->pitches[i], mdev->chip.bus_width); - drm_gem_object_put_unlocked(obj); - fb->obj[i] = NULL; - return -EINVAL; } - min_size = ((kfb->aligned_h / kfb->format_caps->tile_size - 1) - * fb->pitches[i]) - + (kfb->aligned_w * fb->format->cpp[i] - * kfb->format_caps->tile_size) - + fb->offsets[i]; - + min_size = komeda_fb_get_pixel_addr(kfb, 0, fb->height, i) + - to_drm_gem_cma_obj(obj)->paddr; if (obj->size < min_size) { - DRM_DEBUG_KMS("Fail to check none afbc fb size.\n"); - drm_gem_object_put_unlocked(obj); - fb->obj[i] = NULL; - + DRM_DEBUG_KMS("The fb->obj[%d] size: 0x%zx lower than the minimum requirement: 0x%llx.\n", + i, obj->size, min_size); return -EINVAL; } - - fb->obj[i] = obj; } if (fb->format->num_planes == 3) { @@ -118,7 +178,10 @@ komeda_fb_create(struct drm_device *dev, struct drm_file *file, drm_helper_mode_fill_fb_struct(dev, &kfb->base, mode_cmd); - ret = komeda_fb_none_afbc_size_check(mdev, kfb, file, mode_cmd); + if (kfb->base.modifier) + ret = komeda_fb_afbc_size_check(kfb, file, mode_cmd); + else + ret = komeda_fb_none_afbc_size_check(mdev, kfb, file, mode_cmd); if (ret < 0) goto err_cleanup; @@ -129,6 +192,8 @@ komeda_fb_create(struct drm_device *dev, struct drm_file *file, goto err_cleanup; } + kfb->is_va = mdev->iommu ? true : false; + return &kfb->base; err_cleanup: @@ -139,12 +204,42 @@ komeda_fb_create(struct drm_device *dev, struct drm_file *file, return ERR_PTR(ret); } +int komeda_fb_check_src_coords(const struct komeda_fb *kfb, + u32 src_x, u32 src_y, u32 src_w, u32 src_h) +{ + const struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + u32 block_w = drm_format_info_block_width(fb->format, 0); + u32 block_h = drm_format_info_block_height(fb->format, 0); + + if ((src_x + src_w > fb->width) || (src_y + src_h > fb->height)) { + DRM_DEBUG_ATOMIC("Invalid source coordinate.\n"); + return -EINVAL; + } + + if ((src_x % info->hsub) || (src_w % info->hsub) || + (src_y % info->vsub) || (src_h % info->vsub)) { + DRM_DEBUG_ATOMIC("Wrong subsampling dimension x:%d, y:%d, w:%d, h:%d for format: %x.\n", + src_x, src_y, src_w, src_h, info->format); + return -EINVAL; + } + + if ((src_x % block_w) || (src_w % block_w) || + (src_y % block_h) || (src_h % block_h)) { + DRM_DEBUG_ATOMIC("x:%d, y:%d, w:%d, h:%d should be multiple of block_w/h for format: %x.\n", + src_x, src_y, src_w, src_h, info->format); + return -EINVAL; + } + + return 0; +} + dma_addr_t komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane) { struct drm_framebuffer *fb = &kfb->base; const struct drm_gem_cma_object *obj; - u32 plane_x, plane_y, cpp, pitch, offset; + u32 offset, plane_x, plane_y, block_w, block_sz; if (plane >= fb->format->num_planes) { DRM_DEBUG_KMS("Out of max plane num.\n"); @@ -155,13 +250,33 @@ komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane) offset = fb->offsets[plane]; if (!fb->modifier) { + block_w = drm_format_info_block_width(fb->format, plane); + block_sz = fb->format->char_per_block[plane]; plane_x = x / (plane ? fb->format->hsub : 1); plane_y = y / (plane ? fb->format->vsub : 1); - cpp = fb->format->cpp[plane]; - pitch = fb->pitches[plane]; - offset += plane_x * cpp * kfb->format_caps->tile_size + - (plane_y * pitch) / kfb->format_caps->tile_size; + + offset += (plane_x / block_w) * block_sz + + plane_y * fb->pitches[plane]; } return obj->paddr + offset; } + +/* if the fb can be supported by a specific layer */ +bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type, + u32 rot) +{ + struct drm_framebuffer *fb = &kfb->base; + struct komeda_dev *mdev = fb->dev->dev_private; + u32 fourcc = fb->format->format; + u64 modifier = fb->modifier; + bool supported; + + supported = komeda_format_mod_supported(&mdev->fmt_tbl, layer_type, + fourcc, modifier, rot); + if (!supported) + DRM_DEBUG_ATOMIC("Layer TYPE: %d doesn't support fb FMT: %s.\n", + layer_type, komeda_get_format_name(fourcc, modifier)); + + return supported; +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h index ea2fe190c1e341e773cbdb1a2bc1a767c27c9a10..c61ca98a3a6370d8c4591b98c9774ff756f7f0a3 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h +++ b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h @@ -21,19 +21,28 @@ struct komeda_fb { * extends drm_format_info for komeda specific information */ const struct komeda_format_caps *format_caps; + /** @is_va: if smmu is enabled, it will be true */ + bool is_va; /** @aligned_w: aligned frame buffer width */ u32 aligned_w; /** @aligned_h: aligned frame buffer height */ u32 aligned_h; + /** @afbc_size: minimum size of afbc */ + u32 afbc_size; + /** @offset_payload: start of afbc body buffer */ + u32 offset_payload; }; #define to_kfb(dfb) container_of(dfb, struct komeda_fb, base) struct drm_framebuffer * komeda_fb_create(struct drm_device *dev, struct drm_file *file, - const struct drm_mode_fb_cmd2 *mode_cmd); + const struct drm_mode_fb_cmd2 *mode_cmd); +int komeda_fb_check_src_coords(const struct komeda_fb *kfb, + u32 src_x, u32 src_y, u32 src_w, u32 src_h); dma_addr_t komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane); -bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type); +bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type, + u32 rot); #endif diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_kms.c b/drivers/gpu/drm/arm/display/komeda/komeda_kms.c index 86f6542afb40d130ca61c0d79b83ac0e4a1142e2..419a8b0e5de8f2fe96be3d9e022e73405eb406f2 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_kms.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_kms.c @@ -58,7 +58,6 @@ static struct drm_driver komeda_kms_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_PRIME | DRIVER_HAVE_IRQ, .lastclose = drm_fb_helper_lastclose, - .irq_handler = komeda_kms_irq_handler, .gem_free_object_unlocked = drm_gem_cma_free_object, .gem_vm_ops = &drm_gem_cma_vm_ops, .dumb_create = komeda_gem_cma_dumb_create, @@ -100,6 +99,108 @@ static const struct drm_mode_config_helper_funcs komeda_mode_config_helpers = { .atomic_commit_tail = komeda_kms_commit_tail, }; +static int komeda_plane_state_list_add(struct drm_plane_state *plane_st, + struct list_head *zorder_list) +{ + struct komeda_plane_state *new = to_kplane_st(plane_st); + struct komeda_plane_state *node, *last; + + last = list_empty(zorder_list) ? + NULL : list_last_entry(zorder_list, typeof(*last), zlist_node); + + /* Considering the list sequence is zpos increasing, so if list is empty + * or the zpos of new node bigger than the last node in list, no need + * loop and just insert the new one to the tail of the list. + */ + if (!last || (new->base.zpos > last->base.zpos)) { + list_add_tail(&new->zlist_node, zorder_list); + return 0; + } + + /* Build the list by zpos increasing */ + list_for_each_entry(node, zorder_list, zlist_node) { + if (new->base.zpos < node->base.zpos) { + list_add_tail(&new->zlist_node, &node->zlist_node); + break; + } else if (node->base.zpos == new->base.zpos) { + struct drm_plane *a = node->base.plane; + struct drm_plane *b = new->base.plane; + + /* Komeda doesn't support setting a same zpos for + * different planes. + */ + DRM_DEBUG_ATOMIC("PLANE: %s and PLANE: %s are configured same zpos: %d.\n", + a->name, b->name, node->base.zpos); + return -EINVAL; + } + } + + return 0; +} + +static int komeda_crtc_normalize_zpos(struct drm_crtc *crtc, + struct drm_crtc_state *crtc_st) +{ + struct drm_atomic_state *state = crtc_st->state; + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(crtc_st); + struct komeda_plane_state *kplane_st; + struct drm_plane_state *plane_st; + struct drm_framebuffer *fb; + struct drm_plane *plane; + struct list_head zorder_list; + int order = 0, err; + + DRM_DEBUG_ATOMIC("[CRTC:%d:%s] calculating normalized zpos values\n", + crtc->base.id, crtc->name); + + INIT_LIST_HEAD(&zorder_list); + + /* This loop also added all effected planes into the new state */ + drm_for_each_plane_mask(plane, crtc->dev, crtc_st->plane_mask) { + plane_st = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_st)) + return PTR_ERR(plane_st); + + /* Build a list by zpos increasing */ + err = komeda_plane_state_list_add(plane_st, &zorder_list); + if (err) + return err; + } + + kcrtc_st->max_slave_zorder = 0; + + list_for_each_entry(kplane_st, &zorder_list, zlist_node) { + plane_st = &kplane_st->base; + fb = plane_st->fb; + plane = plane_st->plane; + + plane_st->normalized_zpos = order++; + /* When layer_split has been enabled, one plane will be handled + * by two separated komeda layers (left/right), which may needs + * two zorders. + * - zorder: for left_layer for left display part. + * - zorder + 1: will be reserved for right layer. + */ + if (to_kplane_st(plane_st)->layer_split) + order++; + + DRM_DEBUG_ATOMIC("[PLANE:%d:%s] zpos:%d, normalized zpos: %d\n", + plane->base.id, plane->name, + plane_st->zpos, plane_st->normalized_zpos); + + /* calculate max slave zorder */ + if (has_bit(drm_plane_index(plane), kcrtc->slave_planes)) + kcrtc_st->max_slave_zorder = + max(plane_st->normalized_zpos, + kcrtc_st->max_slave_zorder); + } + + crtc_st->zpos_changed = true; + + return 0; +} + static int komeda_kms_check(struct drm_device *dev, struct drm_atomic_state *state) { @@ -111,7 +212,7 @@ static int komeda_kms_check(struct drm_device *dev, if (err) return err; - /* komeda need to re-calculate resource assumption in every commit + /* Komeda need to re-calculate resource assumption in every commit * so need to add all affected_planes (even unchanged) to * drm_atomic_state. */ @@ -119,6 +220,10 @@ static int komeda_kms_check(struct drm_device *dev, err = drm_atomic_add_affected_planes(state, crtc); if (err) return err; + + err = komeda_crtc_normalize_zpos(crtc, new_crtc_st); + if (err) + return err; } err = drm_atomic_helper_check_planes(dev, state); @@ -148,7 +253,7 @@ static void komeda_kms_mode_config_init(struct komeda_kms_dev *kms, config->min_height = 0; config->max_width = 4096; config->max_height = 4096; - config->allow_fb_modifiers = false; + config->allow_fb_modifiers = true; config->funcs = &komeda_mode_config_funcs; config->helper_private = &komeda_mode_config_helpers; @@ -188,29 +293,36 @@ struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev) if (err) goto cleanup_mode_config; + err = komeda_kms_add_wb_connectors(kms, mdev); + if (err) + goto cleanup_mode_config; + err = component_bind_all(mdev->dev, kms); if (err) goto cleanup_mode_config; drm_mode_config_reset(drm); - err = drm_irq_install(drm, mdev->irq); + err = devm_request_irq(drm->dev, mdev->irq, + komeda_kms_irq_handler, IRQF_SHARED, + drm->driver->name, drm); if (err) goto cleanup_mode_config; err = mdev->funcs->enable_irq(mdev); if (err) - goto uninstall_irq; + goto cleanup_mode_config; + + drm->irq_enabled = true; err = drm_dev_register(drm, 0); if (err) - goto uninstall_irq; + goto cleanup_mode_config; return kms; -uninstall_irq: - drm_irq_uninstall(drm); cleanup_mode_config: + drm->irq_enabled = false; drm_mode_config_cleanup(drm); komeda_kms_cleanup_private_objs(kms); free_kms: @@ -223,9 +335,9 @@ void komeda_kms_detach(struct komeda_kms_dev *kms) struct drm_device *drm = &kms->base; struct komeda_dev *mdev = drm->dev_private; + drm->irq_enabled = false; mdev->funcs->disable_irq(mdev); drm_dev_unregister(drm); - drm_irq_uninstall(drm); component_unbind_all(mdev->dev, drm); komeda_kms_cleanup_private_objs(kms); drm_mode_config_cleanup(drm); diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_kms.h b/drivers/gpu/drm/arm/display/komeda/komeda_kms.h index ac3d9209b4d92fc30369c1cc23581db4200fa291..219fa3f0c3363636a465c40da3227447e1616cd8 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_kms.h +++ b/drivers/gpu/drm/arm/display/komeda/komeda_kms.h @@ -7,11 +7,13 @@ #ifndef _KOMEDA_KMS_H_ #define _KOMEDA_KMS_H_ +#include #include #include #include #include #include +#include #include