| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. |
| * Author: James.Qian.Wang <james.qian.wang@arm.com> |
| * |
| */ |
| #include "d71_dev.h" |
| #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) |
| { |
| u32 id = BLOCK_INFO_BLK_ID(hw_id); |
| u32 pipe = id; |
| |
| switch (BLOCK_INFO_BLK_TYPE(hw_id)) { |
| case D71_BLK_TYPE_LPU_WB_LAYER: |
| id = KOMEDA_COMPONENT_WB_LAYER; |
| break; |
| case D71_BLK_TYPE_CU_SPLITTER: |
| id = KOMEDA_COMPONENT_SPLITTER; |
| break; |
| case D71_BLK_TYPE_CU_SCALER: |
| pipe = id / D71_PIPELINE_MAX_SCALERS; |
| id %= D71_PIPELINE_MAX_SCALERS; |
| id += KOMEDA_COMPONENT_SCALER0; |
| break; |
| case D71_BLK_TYPE_CU: |
| id += KOMEDA_COMPONENT_COMPIZ0; |
| break; |
| case D71_BLK_TYPE_LPU_LAYER: |
| pipe = id / D71_PIPELINE_MAX_LAYERS; |
| id %= D71_PIPELINE_MAX_LAYERS; |
| id += KOMEDA_COMPONENT_LAYER0; |
| break; |
| case D71_BLK_TYPE_DOU_IPS: |
| id += KOMEDA_COMPONENT_IPS0; |
| break; |
| case D71_BLK_TYPE_CU_MERGER: |
| id = KOMEDA_COMPONENT_MERGER; |
| break; |
| case D71_BLK_TYPE_DOU: |
| id = KOMEDA_COMPONENT_TIMING_CTRLR; |
| break; |
| default: |
| id = 0xFFFFFFFF; |
| } |
| |
| if (comp_id) |
| *comp_id = id; |
| |
| if (pipe_id) |
| *pipe_id = pipe; |
| } |
| |
| static u32 get_valid_inputs(struct block_header *blk) |
| { |
| u32 valid_inputs = 0, comp_id; |
| int i; |
| |
| for (i = 0; i < PIPELINE_INFO_N_VALID_INPUTS(blk->pipeline_info); i++) { |
| get_resources_id(blk->input_ids[i], NULL, &comp_id); |
| if (comp_id == 0xFFFFFFFF) |
| continue; |
| valid_inputs |= BIT(comp_id); |
| } |
| |
| return valid_inputs; |
| } |
| |
| static void get_values_from_reg(void __iomem *reg, u32 offset, |
| u32 count, u32 *val) |
| { |
| u32 i, addr; |
| |
| for (i = 0; i < count; i++) { |
| addr = offset + (i << 2); |
| /* 0xA4 is WO register */ |
| if (addr != 0xA4) |
| val[i] = malidp_read32(reg, addr); |
| else |
| val[i] = 0xDEADDEAD; |
| } |
| } |
| |
| static void dump_block_header(struct seq_file *sf, void __iomem *reg) |
| { |
| struct block_header hdr; |
| u32 i, n_input, n_output; |
| |
| d71_read_block_header(reg, &hdr); |
| seq_printf(sf, "BLOCK_INFO:\t\t0x%X\n", hdr.block_info); |
| seq_printf(sf, "PIPELINE_INFO:\t\t0x%X\n", hdr.pipeline_info); |
| |
| n_output = PIPELINE_INFO_N_OUTPUTS(hdr.pipeline_info); |
| n_input = PIPELINE_INFO_N_VALID_INPUTS(hdr.pipeline_info); |
| |
| for (i = 0; i < n_input; i++) |
| seq_printf(sf, "VALID_INPUT_ID%u:\t0x%X\n", |
| i, hdr.input_ids[i]); |
| |
| for (i = 0; i < n_output; i++) |
| seq_printf(sf, "OUTPUT_ID%u:\t\t0x%X\n", |
| i, hdr.output_ids[i]); |
| } |
| |
| /* On D71, we are using the global line size. From D32, every component have |
| * a line size register to indicate the fifo size. |
| */ |
| static u32 __get_blk_line_size(struct d71_dev *d71, u32 __iomem *reg, |
| u32 max_default) |
| { |
| if (!d71->periph_addr) |
| max_default = malidp_read32(reg, BLK_MAX_LINE_SIZE); |
| |
| return max_default; |
| } |
| |
| static u32 get_blk_line_size(struct d71_dev *d71, u32 __iomem *reg) |
| { |
| return __get_blk_line_size(d71, reg, d71->max_line_size); |
| } |
| |
| static u32 to_rot_ctrl(u32 rot) |
| { |
| u32 lr_ctrl = 0; |
| |
| switch (rot & DRM_MODE_ROTATE_MASK) { |
| case DRM_MODE_ROTATE_0: |
| lr_ctrl |= L_ROT(L_ROT_R0); |
| break; |
| case DRM_MODE_ROTATE_90: |
| lr_ctrl |= L_ROT(L_ROT_R90); |
| break; |
| case DRM_MODE_ROTATE_180: |
| lr_ctrl |= L_ROT(L_ROT_R180); |
| break; |
| case DRM_MODE_ROTATE_270: |
| lr_ctrl |= L_ROT(L_ROT_R270); |
| break; |
| } |
| |
| if (rot & DRM_MODE_REFLECT_X) |
| lr_ctrl |= L_HFLIP; |
| if (rot & DRM_MODE_REFLECT_Y) |
| lr_ctrl |= L_VFLIP; |
| |
| return lr_ctrl; |
| } |
| |
| static u32 to_ad_ctrl(u64 modifier) |
| { |
| u32 afbc_ctrl = AD_AEN; |
| |
| 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) |
| { |
| malidp_write32_mask(c->reg, BLK_CONTROL, L_EN, 0); |
| } |
| |
| static void d71_layer_update(struct komeda_component *c, |
| struct komeda_component_state *state) |
| { |
| struct komeda_layer_state *st = to_layer_st(state); |
| struct drm_plane_state *plane_st = state->plane->state; |
| struct drm_framebuffer *fb = plane_st->fb; |
| struct komeda_fb *kfb = to_kfb(fb); |
| 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); |
| |
| 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, 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, 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); |
| } |
| |
| static void d71_layer_dump(struct komeda_component *c, struct seq_file *sf) |
| { |
| u32 v[15], i; |
| bool rich, rgb2rgb; |
| char *prefix; |
| |
| get_values_from_reg(c->reg, LAYER_INFO, 1, &v[14]); |
| if (v[14] & 0x1) { |
| rich = true; |
| prefix = "LR_"; |
| } else { |
| rich = false; |
| prefix = "LS_"; |
| } |
| |
| rgb2rgb = !!(v[14] & L_INFO_CM); |
| |
| dump_block_header(sf, c->reg); |
| |
| seq_printf(sf, "%sLAYER_INFO:\t\t0x%X\n", prefix, v[14]); |
| |
| get_values_from_reg(c->reg, 0xD0, 1, v); |
| seq_printf(sf, "%sCONTROL:\t\t0x%X\n", prefix, v[0]); |
| if (rich) { |
| get_values_from_reg(c->reg, 0xD4, 1, v); |
| seq_printf(sf, "LR_RICH_CONTROL:\t0x%X\n", v[0]); |
| } |
| get_values_from_reg(c->reg, 0xD8, 4, v); |
| seq_printf(sf, "%sFORMAT:\t\t0x%X\n", prefix, v[0]); |
| seq_printf(sf, "%sIT_COEFFTAB:\t\t0x%X\n", prefix, v[1]); |
| seq_printf(sf, "%sIN_SIZE:\t\t0x%X\n", prefix, v[2]); |
| seq_printf(sf, "%sPALPHA:\t\t0x%X\n", prefix, v[3]); |
| |
| get_values_from_reg(c->reg, 0x100, 3, v); |
| seq_printf(sf, "%sP0_PTR_LOW:\t\t0x%X\n", prefix, v[0]); |
| seq_printf(sf, "%sP0_PTR_HIGH:\t\t0x%X\n", prefix, v[1]); |
| seq_printf(sf, "%sP0_STRIDE:\t\t0x%X\n", prefix, v[2]); |
| |
| get_values_from_reg(c->reg, 0x110, 2, v); |
| seq_printf(sf, "%sP1_PTR_LOW:\t\t0x%X\n", prefix, v[0]); |
| seq_printf(sf, "%sP1_PTR_HIGH:\t\t0x%X\n", prefix, v[1]); |
| if (rich) { |
| get_values_from_reg(c->reg, 0x118, 1, v); |
| seq_printf(sf, "LR_P1_STRIDE:\t\t0x%X\n", v[0]); |
| |
| get_values_from_reg(c->reg, 0x120, 2, v); |
| seq_printf(sf, "LR_P2_PTR_LOW:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "LR_P2_PTR_HIGH:\t\t0x%X\n", v[1]); |
| |
| get_values_from_reg(c->reg, 0x130, 12, v); |
| for (i = 0; i < 12; i++) |
| seq_printf(sf, "LR_YUV_RGB_COEFF%u:\t0x%X\n", i, v[i]); |
| } |
| |
| if (rgb2rgb) { |
| get_values_from_reg(c->reg, LAYER_RGB_RGB_COEFF0, 12, v); |
| for (i = 0; i < 12; i++) |
| seq_printf(sf, "LS_RGB_RGB_COEFF%u:\t0x%X\n", i, v[i]); |
| } |
| |
| get_values_from_reg(c->reg, 0x160, 3, v); |
| seq_printf(sf, "%sAD_CONTROL:\t\t0x%X\n", prefix, v[0]); |
| seq_printf(sf, "%sAD_H_CROP:\t\t0x%X\n", prefix, v[1]); |
| seq_printf(sf, "%sAD_V_CROP:\t\t0x%X\n", prefix, v[2]); |
| } |
| |
| static int d71_layer_validate(struct komeda_component *c, |
| struct komeda_component_state *state) |
| { |
| struct komeda_layer_state *st = to_layer_st(state); |
| struct komeda_layer *layer = to_layer(c); |
| struct drm_plane_state *plane_st; |
| struct drm_framebuffer *fb; |
| u32 fourcc, line_sz, max_line_sz; |
| |
| plane_st = drm_atomic_get_new_plane_state(state->obj.state, |
| state->plane); |
| fb = plane_st->fb; |
| fourcc = fb->format->format; |
| |
| if (drm_rotation_90_or_270(st->rot)) |
| line_sz = st->vsize - st->afbc_crop_t - st->afbc_crop_b; |
| else |
| line_sz = st->hsize - st->afbc_crop_l - st->afbc_crop_r; |
| |
| if (fb->modifier) { |
| if ((fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) == |
| AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) |
| max_line_sz = layer->line_sz; |
| else |
| max_line_sz = layer->line_sz / 2; |
| |
| if (line_sz > max_line_sz) { |
| DRM_DEBUG_ATOMIC("afbc request line_sz: %d exceed the max afbc line_sz: %d.\n", |
| line_sz, max_line_sz); |
| return -EINVAL; |
| } |
| } |
| |
| if (fourcc == DRM_FORMAT_YUV420_10BIT && line_sz > 2046 && (st->afbc_crop_l % 4)) { |
| DRM_DEBUG_ATOMIC("YUV420_10BIT input_hsize: %d exceed the max size 2046.\n", |
| line_sz); |
| return -EINVAL; |
| } |
| |
| if (fourcc == DRM_FORMAT_X0L2 && line_sz > 2046 && (st->addr[0] % 16)) { |
| DRM_DEBUG_ATOMIC("X0L2 input_hsize: %d exceed the max size 2046.\n", |
| line_sz); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct komeda_component_funcs d71_layer_funcs = { |
| .validate = d71_layer_validate, |
| .update = d71_layer_update, |
| .disable = d71_layer_disable, |
| .dump_register = d71_layer_dump, |
| }; |
| |
| static int d71_layer_init(struct d71_dev *d71, |
| struct block_header *blk, u32 __iomem *reg) |
| { |
| struct komeda_component *c; |
| struct komeda_layer *layer; |
| u32 pipe_id, layer_id, layer_info; |
| |
| get_resources_id(blk->block_info, &pipe_id, &layer_id); |
| c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*layer), |
| layer_id, |
| BLOCK_INFO_INPUT_ID(blk->block_info), |
| &d71_layer_funcs, 0, |
| get_valid_inputs(blk), |
| 1, reg, "LPU%d_LAYER%d", pipe_id, layer_id); |
| if (IS_ERR(c)) { |
| DRM_ERROR("Failed to add layer component\n"); |
| return PTR_ERR(c); |
| } |
| |
| layer = to_layer(c); |
| layer_info = malidp_read32(reg, LAYER_INFO); |
| |
| if (layer_info & L_INFO_RF) |
| layer->layer_type = KOMEDA_FMT_RICH_LAYER; |
| else |
| layer->layer_type = KOMEDA_FMT_SIMPLE_LAYER; |
| |
| if (!d71->periph_addr) { |
| /* D32 or newer product */ |
| layer->line_sz = malidp_read32(reg, BLK_MAX_LINE_SIZE); |
| layer->yuv_line_sz = L_INFO_YUV_MAX_LINESZ(layer_info); |
| } else if (d71->max_line_size > 2048) { |
| /* D71 4K */ |
| layer->line_sz = d71->max_line_size; |
| layer->yuv_line_sz = layer->line_sz / 2; |
| } else { |
| /* D71 2K */ |
| if (layer->layer_type == KOMEDA_FMT_RICH_LAYER) { |
| /* rich layer is 4K configuration */ |
| layer->line_sz = d71->max_line_size * 2; |
| layer->yuv_line_sz = layer->line_sz / 2; |
| } else { |
| layer->line_sz = d71->max_line_size; |
| layer->yuv_line_sz = 0; |
| } |
| } |
| |
| set_range(&layer->hsize_in, 4, layer->line_sz); |
| |
| set_range(&layer->vsize_in, 4, d71->max_vsize); |
| |
| malidp_write32(reg, LAYER_PALPHA, D71_PALPHA_DEF_MAP); |
| |
| layer->supported_rots = DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK; |
| |
| 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) |
| { |
| 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; |
| wb_layer->line_sz = get_blk_line_size(d71, reg); |
| wb_layer->yuv_line_sz = wb_layer->line_sz; |
| |
| set_range(&wb_layer->hsize_in, 64, wb_layer->line_sz); |
| set_range(&wb_layer->vsize_in, 64, d71->max_vsize); |
| |
| return 0; |
| } |
| |
| static void d71_component_disable(struct komeda_component *c) |
| { |
| u32 __iomem *reg = c->reg; |
| u32 i; |
| |
| malidp_write32(reg, BLK_CONTROL, 0); |
| |
| 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, |
| u32 __iomem *cfg_reg, |
| u32 input_hw_id, |
| struct komeda_compiz_input_cfg *cin) |
| { |
| u32 ctrl = CU_INPUT_CTRL_EN; |
| u8 blend = cin->pixel_blend_mode; |
| |
| if (blend == DRM_MODE_BLEND_PIXEL_NONE) |
| ctrl |= CU_INPUT_CTRL_PAD; |
| else if (blend == DRM_MODE_BLEND_PREMULTI) |
| ctrl |= CU_INPUT_CTRL_PMUL; |
| |
| ctrl |= CU_INPUT_CTRL_ALPHA(cin->layer_alpha); |
| |
| malidp_write32(id_reg, BLK_INPUT_ID0, input_hw_id); |
| |
| malidp_write32(cfg_reg, CU_INPUT0_SIZE, |
| HV_SIZE(cin->hsize, cin->vsize)); |
| malidp_write32(cfg_reg, CU_INPUT0_OFFSET, |
| HV_OFFSET(cin->hoffset, cin->voffset)); |
| malidp_write32(cfg_reg, CU_INPUT0_CONTROL, ctrl); |
| } |
| |
| static void d71_compiz_update(struct komeda_component *c, |
| struct komeda_component_state *state) |
| { |
| struct komeda_compiz_state *st = to_compiz_st(state); |
| u32 __iomem *reg = c->reg; |
| u32 __iomem *id_reg, *cfg_reg; |
| u32 index; |
| |
| for_each_changed_input(state, index) { |
| id_reg = reg + index; |
| cfg_reg = reg + index * CU_PER_INPUT_REGS; |
| if (state->active_inputs & BIT(index)) { |
| compiz_enable_input(id_reg, cfg_reg, |
| 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); |
| } |
| } |
| |
| malidp_write32(reg, BLK_SIZE, HV_SIZE(st->hsize, st->vsize)); |
| } |
| |
| static void d71_compiz_dump(struct komeda_component *c, struct seq_file *sf) |
| { |
| u32 v[8], i; |
| |
| dump_block_header(sf, c->reg); |
| |
| get_values_from_reg(c->reg, 0x80, 5, v); |
| for (i = 0; i < 5; i++) |
| seq_printf(sf, "CU_INPUT_ID%u:\t\t0x%X\n", i, v[i]); |
| |
| get_values_from_reg(c->reg, 0xA0, 5, v); |
| seq_printf(sf, "CU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); |
| seq_printf(sf, "CU_IRQ_CLEAR:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "CU_IRQ_MASK:\t\t0x%X\n", v[2]); |
| seq_printf(sf, "CU_IRQ_STATUS:\t\t0x%X\n", v[3]); |
| seq_printf(sf, "CU_STATUS:\t\t0x%X\n", v[4]); |
| |
| get_values_from_reg(c->reg, 0xD0, 2, v); |
| seq_printf(sf, "CU_CONTROL:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "CU_SIZE:\t\t0x%X\n", v[1]); |
| |
| get_values_from_reg(c->reg, 0xDC, 1, v); |
| seq_printf(sf, "CU_BG_COLOR:\t\t0x%X\n", v[0]); |
| |
| for (i = 0, v[4] = 0xE0; i < 5; i++, v[4] += 0x10) { |
| get_values_from_reg(c->reg, v[4], 3, v); |
| seq_printf(sf, "CU_INPUT%u_SIZE:\t\t0x%X\n", i, v[0]); |
| seq_printf(sf, "CU_INPUT%u_OFFSET:\t0x%X\n", i, v[1]); |
| seq_printf(sf, "CU_INPUT%u_CONTROL:\t0x%X\n", i, v[2]); |
| } |
| |
| get_values_from_reg(c->reg, 0x130, 2, v); |
| seq_printf(sf, "CU_USER_LOW:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "CU_USER_HIGH:\t\t0x%X\n", v[1]); |
| } |
| |
| static const struct komeda_component_funcs d71_compiz_funcs = { |
| .update = d71_compiz_update, |
| .disable = d71_component_disable, |
| .dump_register = d71_compiz_dump, |
| }; |
| |
| static int d71_compiz_init(struct d71_dev *d71, |
| struct block_header *blk, u32 __iomem *reg) |
| { |
| struct komeda_component *c; |
| struct komeda_compiz *compiz; |
| 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(*compiz), |
| comp_id, |
| BLOCK_INFO_INPUT_ID(blk->block_info), |
| &d71_compiz_funcs, |
| CU_NUM_INPUT_IDS, get_valid_inputs(blk), |
| CU_NUM_OUTPUT_IDS, reg, |
| "CU%d", pipe_id); |
| if (IS_ERR(c)) |
| return PTR_ERR(c); |
| |
| compiz = to_compiz(c); |
| |
| set_range(&compiz->hsize, 64, get_blk_line_size(d71, reg)); |
| set_range(&compiz->vsize, 64, d71->max_vsize); |
| |
| 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[10]; |
| |
| 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]); |
| |
| get_values_from_reg(c->reg, 0x130, 10, v); |
| seq_printf(sf, "SC_ENH_LIMITS:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "SC_ENH_COEFF0:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "SC_ENH_COEFF1:\t\t0x%X\n", v[2]); |
| seq_printf(sf, "SC_ENH_COEFF2:\t\t0x%X\n", v[3]); |
| seq_printf(sf, "SC_ENH_COEFF3:\t\t0x%X\n", v[4]); |
| seq_printf(sf, "SC_ENH_COEFF4:\t\t0x%X\n", v[5]); |
| seq_printf(sf, "SC_ENH_COEFF5:\t\t0x%X\n", v[6]); |
| seq_printf(sf, "SC_ENH_COEFF6:\t\t0x%X\n", v[7]); |
| seq_printf(sf, "SC_ENH_COEFF7:\t\t0x%X\n", v[8]); |
| seq_printf(sf, "SC_ENH_COEFF8:\t\t0x%X\n", v[9]); |
| } |
| |
| 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, __get_blk_line_size(d71, reg, 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->crtc_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, get_blk_line_size(d71, reg)); |
| 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, |
| __get_blk_line_size(d71, reg, 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 drm_crtc_state *crtc_st = state->crtc->state; |
| struct komeda_improc_state *st = to_improc_st(state); |
| struct d71_pipeline *pipe = to_d71_pipeline(c->pipeline); |
| u32 __iomem *reg = c->reg; |
| u32 index, mask = 0, ctrl = 0; |
| |
| 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)); |
| malidp_write32(reg, IPS_DEPTH, st->color_depth); |
| |
| if (crtc_st->color_mgmt_changed) { |
| mask |= IPS_CTRL_FT | IPS_CTRL_RGB; |
| |
| if (crtc_st->gamma_lut) { |
| malidp_write_group(pipe->dou_ft_coeff_addr, FT_COEFF0, |
| KOMEDA_N_GAMMA_COEFFS, |
| st->fgamma_coeffs); |
| ctrl |= IPS_CTRL_FT; /* enable gamma */ |
| } |
| |
| if (crtc_st->ctm) { |
| malidp_write_group(reg, IPS_RGB_RGB_COEFF0, |
| KOMEDA_N_CTM_COEFFS, |
| st->ctm_coeffs); |
| ctrl |= IPS_CTRL_RGB; /* enable gamut */ |
| } |
| } |
| |
| mask |= IPS_CTRL_YUV | IPS_CTRL_CHD422 | IPS_CTRL_CHD420; |
| |
| /* config color format */ |
| if (st->color_format == DRM_COLOR_FORMAT_YCRCB420) |
| ctrl |= IPS_CTRL_YUV | IPS_CTRL_CHD422 | IPS_CTRL_CHD420; |
| else if (st->color_format == DRM_COLOR_FORMAT_YCRCB422) |
| ctrl |= IPS_CTRL_YUV | IPS_CTRL_CHD422; |
| else if (st->color_format == DRM_COLOR_FORMAT_YCRCB444) |
| ctrl |= IPS_CTRL_YUV; |
| |
| malidp_write32_mask(reg, BLK_CONTROL, mask, ctrl); |
| } |
| |
| static void d71_improc_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, 2, v); |
| seq_printf(sf, "IPS_INPUT_ID0:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "IPS_INPUT_ID1:\t\t0x%X\n", v[1]); |
| |
| get_values_from_reg(c->reg, 0xC0, 1, v); |
| seq_printf(sf, "IPS_INFO:\t\t0x%X\n", v[0]); |
| |
| get_values_from_reg(c->reg, 0xD0, 3, v); |
| seq_printf(sf, "IPS_CONTROL:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "IPS_SIZE:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "IPS_DEPTH:\t\t0x%X\n", v[2]); |
| |
| get_values_from_reg(c->reg, 0x130, 12, v); |
| for (i = 0; i < 12; i++) |
| seq_printf(sf, "IPS_RGB_RGB_COEFF%u:\t0x%X\n", i, v[i]); |
| |
| get_values_from_reg(c->reg, 0x170, 12, v); |
| for (i = 0; i < 12; i++) |
| seq_printf(sf, "IPS_RGB_YUV_COEFF%u:\t0x%X\n", i, v[i]); |
| } |
| |
| static const struct komeda_component_funcs d71_improc_funcs = { |
| .update = d71_improc_update, |
| .disable = d71_component_disable, |
| .dump_register = d71_improc_dump, |
| }; |
| |
| static int d71_improc_init(struct d71_dev *d71, |
| struct block_header *blk, u32 __iomem *reg) |
| { |
| struct komeda_component *c; |
| struct komeda_improc *improc; |
| u32 pipe_id, comp_id, value; |
| |
| get_resources_id(blk->block_info, &pipe_id, &comp_id); |
| |
| c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*improc), |
| comp_id, |
| BLOCK_INFO_INPUT_ID(blk->block_info), |
| &d71_improc_funcs, IPS_NUM_INPUT_IDS, |
| get_valid_inputs(blk), |
| IPS_NUM_OUTPUT_IDS, reg, "DOU%d_IPS", pipe_id); |
| if (IS_ERR(c)) { |
| DRM_ERROR("Failed to add improc component\n"); |
| return PTR_ERR(c); |
| } |
| |
| improc = to_improc(c); |
| improc->supported_color_depths = BIT(8) | BIT(10); |
| improc->supported_color_formats = DRM_COLOR_FORMAT_RGB444 | |
| DRM_COLOR_FORMAT_YCRCB444 | |
| DRM_COLOR_FORMAT_YCRCB422; |
| value = malidp_read32(reg, BLK_INFO); |
| if (value & IPS_INFO_CHD420) |
| improc->supported_color_formats |= DRM_COLOR_FORMAT_YCRCB420; |
| |
| improc->supports_csc = true; |
| improc->supports_gamma = true; |
| |
| return 0; |
| } |
| |
| static void d71_timing_ctrlr_disable(struct komeda_component *c) |
| { |
| malidp_write32_mask(c->reg, BLK_CONTROL, BS_CTRL_EN, 0); |
| } |
| |
| static void d71_timing_ctrlr_update(struct komeda_component *c, |
| struct komeda_component_state *state) |
| { |
| struct drm_crtc_state *crtc_st = state->crtc->state; |
| struct drm_display_mode *mode = &crtc_st->adjusted_mode; |
| u32 __iomem *reg = c->reg; |
| u32 hactive, hfront_porch, hback_porch, hsync_len; |
| u32 vactive, vfront_porch, vback_porch, vsync_len; |
| u32 value; |
| |
| hactive = mode->crtc_hdisplay; |
| hfront_porch = mode->crtc_hsync_start - mode->crtc_hdisplay; |
| hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; |
| hback_porch = mode->crtc_htotal - mode->crtc_hsync_end; |
| |
| vactive = mode->crtc_vdisplay; |
| vfront_porch = mode->crtc_vsync_start - mode->crtc_vdisplay; |
| vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; |
| vback_porch = mode->crtc_vtotal - mode->crtc_vsync_end; |
| |
| malidp_write32(reg, BS_ACTIVESIZE, HV_SIZE(hactive, vactive)); |
| malidp_write32(reg, BS_HINTERVALS, BS_H_INTVALS(hfront_porch, |
| hback_porch)); |
| malidp_write32(reg, BS_VINTERVALS, BS_V_INTVALS(vfront_porch, |
| vback_porch)); |
| |
| value = BS_SYNC_VSW(vsync_len) | BS_SYNC_HSW(hsync_len); |
| value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? BS_SYNC_VSP : 0; |
| value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? BS_SYNC_HSP : 0; |
| malidp_write32(reg, BS_SYNC, value); |
| |
| malidp_write32(reg, BS_PROG_LINE, D71_DEFAULT_PREPRETCH_LINE - 1); |
| malidp_write32(reg, BS_PREFETCH_LINE, D71_DEFAULT_PREPRETCH_LINE); |
| |
| /* configure bs control register */ |
| value = BS_CTRL_EN | BS_CTRL_VM; |
| if (c->pipeline->dual_link) { |
| malidp_write32(reg, BS_DRIFT_TO, hfront_porch + 16); |
| value |= BS_CTRL_DL; |
| } |
| |
| malidp_write32(reg, BLK_CONTROL, value); |
| } |
| |
| static void d71_timing_ctrlr_dump(struct komeda_component *c, |
| struct seq_file *sf) |
| { |
| u32 v[8], i; |
| |
| dump_block_header(sf, c->reg); |
| |
| get_values_from_reg(c->reg, 0xC0, 1, v); |
| seq_printf(sf, "BS_INFO:\t\t0x%X\n", v[0]); |
| |
| get_values_from_reg(c->reg, 0xD0, 8, v); |
| seq_printf(sf, "BS_CONTROL:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "BS_PROG_LINE:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "BS_PREFETCH_LINE:\t0x%X\n", v[2]); |
| seq_printf(sf, "BS_BG_COLOR:\t\t0x%X\n", v[3]); |
| seq_printf(sf, "BS_ACTIVESIZE:\t\t0x%X\n", v[4]); |
| seq_printf(sf, "BS_HINTERVALS:\t\t0x%X\n", v[5]); |
| seq_printf(sf, "BS_VINTERVALS:\t\t0x%X\n", v[6]); |
| seq_printf(sf, "BS_SYNC:\t\t0x%X\n", v[7]); |
| |
| get_values_from_reg(c->reg, 0x100, 3, v); |
| seq_printf(sf, "BS_DRIFT_TO:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "BS_FRAME_TO:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "BS_TE_TO:\t\t0x%X\n", v[2]); |
| |
| get_values_from_reg(c->reg, 0x110, 3, v); |
| for (i = 0; i < 3; i++) |
| seq_printf(sf, "BS_T%u_INTERVAL:\t\t0x%X\n", i, v[i]); |
| |
| get_values_from_reg(c->reg, 0x120, 5, v); |
| for (i = 0; i < 2; i++) { |
| seq_printf(sf, "BS_CRC%u_LOW:\t\t0x%X\n", i, v[i << 1]); |
| seq_printf(sf, "BS_CRC%u_HIGH:\t\t0x%X\n", i, v[(i << 1) + 1]); |
| } |
| seq_printf(sf, "BS_USER:\t\t0x%X\n", v[4]); |
| } |
| |
| static const struct komeda_component_funcs d71_timing_ctrlr_funcs = { |
| .update = d71_timing_ctrlr_update, |
| .disable = d71_timing_ctrlr_disable, |
| .dump_register = d71_timing_ctrlr_dump, |
| }; |
| |
| static int d71_timing_ctrlr_init(struct d71_dev *d71, |
| struct block_header *blk, u32 __iomem *reg) |
| { |
| struct komeda_component *c; |
| struct komeda_timing_ctrlr *ctrlr; |
| 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(*ctrlr), |
| KOMEDA_COMPONENT_TIMING_CTRLR, |
| BLOCK_INFO_INPUT_ID(blk->block_info), |
| &d71_timing_ctrlr_funcs, |
| 1, BIT(KOMEDA_COMPONENT_IPS0 + pipe_id), |
| BS_NUM_OUTPUT_IDS, reg, "DOU%d_BS", pipe_id); |
| if (IS_ERR(c)) { |
| DRM_ERROR("Failed to add display_ctrl component\n"); |
| return PTR_ERR(c); |
| } |
| |
| ctrlr = to_ctrlr(c); |
| |
| ctrlr->supports_dual_link = d71->supports_dual_link; |
| |
| return 0; |
| } |
| |
| int d71_probe_block(struct d71_dev *d71, |
| struct block_header *blk, u32 __iomem *reg) |
| { |
| struct d71_pipeline *pipe; |
| int blk_id = BLOCK_INFO_BLK_ID(blk->block_info); |
| |
| int err = 0; |
| |
| switch (BLOCK_INFO_BLK_TYPE(blk->block_info)) { |
| case D71_BLK_TYPE_GCU: |
| break; |
| |
| case D71_BLK_TYPE_LPU: |
| pipe = d71->pipes[blk_id]; |
| pipe->lpu_addr = reg; |
| break; |
| |
| case D71_BLK_TYPE_LPU_LAYER: |
| err = d71_layer_init(d71, blk, reg); |
| break; |
| |
| case D71_BLK_TYPE_LPU_WB_LAYER: |
| err = d71_wb_layer_init(d71, blk, reg); |
| break; |
| |
| case D71_BLK_TYPE_CU: |
| pipe = d71->pipes[blk_id]; |
| pipe->cu_addr = reg; |
| err = d71_compiz_init(d71, blk, reg); |
| break; |
| |
| 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: |
| pipe = d71->pipes[blk_id]; |
| pipe->dou_addr = reg; |
| break; |
| |
| case D71_BLK_TYPE_DOU_IPS: |
| err = d71_improc_init(d71, blk, reg); |
| break; |
| |
| case D71_BLK_TYPE_DOU_FT_COEFF: |
| pipe = d71->pipes[blk_id]; |
| pipe->dou_ft_coeff_addr = reg; |
| break; |
| |
| case D71_BLK_TYPE_DOU_BS: |
| err = d71_timing_ctrlr_init(d71, blk, reg); |
| break; |
| |
| case D71_BLK_TYPE_GLB_LT_COEFF: |
| break; |
| |
| case D71_BLK_TYPE_GLB_SCL_COEFF: |
| d71->glb_scl_coeff_addr[blk_id] = reg; |
| break; |
| |
| default: |
| DRM_ERROR("Unknown block (block_info: 0x%x) is found\n", |
| blk->block_info); |
| err = -EINVAL; |
| break; |
| } |
| |
| return err; |
| } |
| |
| static void d71_gcu_dump(struct d71_dev *d71, struct seq_file *sf) |
| { |
| u32 v[5]; |
| |
| seq_puts(sf, "\n------ GCU ------\n"); |
| |
| get_values_from_reg(d71->gcu_addr, 0, 3, v); |
| seq_printf(sf, "GLB_ARCH_ID:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "GLB_CORE_ID:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "GLB_CORE_INFO:\t\t0x%X\n", v[2]); |
| |
| get_values_from_reg(d71->gcu_addr, 0x10, 1, v); |
| seq_printf(sf, "GLB_IRQ_STATUS:\t\t0x%X\n", v[0]); |
| |
| get_values_from_reg(d71->gcu_addr, 0xA0, 5, v); |
| seq_printf(sf, "GCU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); |
| seq_printf(sf, "GCU_IRQ_CLEAR:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "GCU_IRQ_MASK:\t\t0x%X\n", v[2]); |
| seq_printf(sf, "GCU_IRQ_STATUS:\t\t0x%X\n", v[3]); |
| seq_printf(sf, "GCU_STATUS:\t\t0x%X\n", v[4]); |
| |
| get_values_from_reg(d71->gcu_addr, 0xD0, 3, v); |
| seq_printf(sf, "GCU_CONTROL:\t\t0x%X\n", v[0]); |
| seq_printf(sf, "GCU_CONFIG_VALID0:\t0x%X\n", v[1]); |
| seq_printf(sf, "GCU_CONFIG_VALID1:\t0x%X\n", v[2]); |
| } |
| |
| static void d71_lpu_dump(struct d71_pipeline *pipe, struct seq_file *sf) |
| { |
| u32 v[6]; |
| |
| seq_printf(sf, "\n------ LPU%d ------\n", pipe->base.id); |
| |
| dump_block_header(sf, pipe->lpu_addr); |
| |
| get_values_from_reg(pipe->lpu_addr, 0xA0, 6, v); |
| seq_printf(sf, "LPU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); |
| seq_printf(sf, "LPU_IRQ_CLEAR:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "LPU_IRQ_MASK:\t\t0x%X\n", v[2]); |
| seq_printf(sf, "LPU_IRQ_STATUS:\t\t0x%X\n", v[3]); |
| seq_printf(sf, "LPU_STATUS:\t\t0x%X\n", v[4]); |
| seq_printf(sf, "LPU_TBU_STATUS:\t\t0x%X\n", v[5]); |
| |
| get_values_from_reg(pipe->lpu_addr, 0xC0, 1, v); |
| seq_printf(sf, "LPU_INFO:\t\t0x%X\n", v[0]); |
| |
| get_values_from_reg(pipe->lpu_addr, 0xD0, 3, v); |
| seq_printf(sf, "LPU_RAXI_CONTROL:\t0x%X\n", v[0]); |
| seq_printf(sf, "LPU_WAXI_CONTROL:\t0x%X\n", v[1]); |
| seq_printf(sf, "LPU_TBU_CONTROL:\t0x%X\n", v[2]); |
| } |
| |
| static void d71_dou_dump(struct d71_pipeline *pipe, struct seq_file *sf) |
| { |
| u32 v[5]; |
| |
| seq_printf(sf, "\n------ DOU%d ------\n", pipe->base.id); |
| |
| dump_block_header(sf, pipe->dou_addr); |
| |
| get_values_from_reg(pipe->dou_addr, 0xA0, 5, v); |
| seq_printf(sf, "DOU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); |
| seq_printf(sf, "DOU_IRQ_CLEAR:\t\t0x%X\n", v[1]); |
| seq_printf(sf, "DOU_IRQ_MASK:\t\t0x%X\n", v[2]); |
| seq_printf(sf, "DOU_IRQ_STATUS:\t\t0x%X\n", v[3]); |
| seq_printf(sf, "DOU_STATUS:\t\t0x%X\n", v[4]); |
| } |
| |
| static void d71_pipeline_dump(struct komeda_pipeline *pipe, struct seq_file *sf) |
| { |
| struct d71_pipeline *d71_pipe = to_d71_pipeline(pipe); |
| |
| d71_lpu_dump(d71_pipe, sf); |
| d71_dou_dump(d71_pipe, sf); |
| } |
| |
| const struct komeda_pipeline_funcs d71_pipeline_funcs = { |
| .downscaling_clk_check = d71_downscaling_clk_check, |
| .dump_register = d71_pipeline_dump, |
| }; |
| |
| void d71_dump(struct komeda_dev *mdev, struct seq_file *sf) |
| { |
| struct d71_dev *d71 = mdev->chip_data; |
| |
| d71_gcu_dump(d71, sf); |
| } |