diff options
author | Naseer Ahmed <naseer@codeaurora.org> | 2016-04-05 15:19:25 -0400 |
---|---|---|
committer | Kyle Yan <kyan@codeaurora.org> | 2016-05-25 14:20:24 -0700 |
commit | 687db9c75e3ca8c5ccb649032e9fa8e90fb9485d (patch) | |
tree | 60d3ae038c448d573f28273a7ec85a0d152f18f1 /drivers/video | |
parent | e215162bb29af2325151d6148f2601b9f9a7f916 (diff) |
msm: mdss: Adding support for destination scaler
Destination scaling is a new hardware feature in MSM mdss 3xx hw. When user
mode enabled the destination upscaling, framebuffer will get upscaled
and then transmitted to the primary panel.
CRs-Fixed: 988990
Change-Id: I19aa5983316bec4a87811c8aa8b54f770001c45f
Signed-off-by: Benjamin Chan <bkchan@codeaurora.org>
Signed-off-by: Naseer Ahmed <naseer@codeaurora.org>
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/fbdev/msm/mdss.h | 4 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_fb.c | 124 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp.c | 67 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp.h | 104 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp_ctl.c | 83 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp_hwio.h | 4 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp_layer.c | 241 | ||||
-rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp_pp.c | 148 |
8 files changed, 707 insertions, 68 deletions
diff --git a/drivers/video/fbdev/msm/mdss.h b/drivers/video/fbdev/msm/mdss.h index f957ee082514..1f7c627b781e 100644 --- a/drivers/video/fbdev/msm/mdss.h +++ b/drivers/video/fbdev/msm/mdss.h @@ -503,6 +503,10 @@ struct mdss_data_type { u32 bcolor1; u32 bcolor2; struct mdss_scaler_block *scaler_off; + + u32 max_dest_scaler_input_width; + u32 max_dest_scaler_output_width; + struct mdss_mdp_destination_scaler *ds; }; extern struct mdss_data_type *mdss_res; diff --git a/drivers/video/fbdev/msm/mdss_fb.c b/drivers/video/fbdev/msm/mdss_fb.c index 0e26de90900c..82a5ee27a07f 100644 --- a/drivers/video/fbdev/msm/mdss_fb.c +++ b/drivers/video/fbdev/msm/mdss_fb.c @@ -116,6 +116,17 @@ static int mdss_fb_send_panel_event(struct msm_fb_data_type *mfd, int event, void *arg); static void mdss_fb_set_mdp_sync_pt_threshold(struct msm_fb_data_type *mfd, int type); + +static inline void __user *to_user_ptr(uint64_t address) +{ + return (void __user *)(uintptr_t)address; +} + +static inline uint64_t __user to_user_u64(void *ptr) +{ + return (uint64_t)((uintptr_t)ptr); +} + void mdss_fb_no_update_notify_timer_cb(unsigned long data) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data; @@ -4269,6 +4280,101 @@ err: return ret; } +static int __mdss_fb_copy_destscaler_data(struct fb_info *info, + struct mdp_layer_commit *commit) +{ + int i; + int ret = 0; + u32 data_size; + struct mdp_destination_scaler_data __user *ds_data_user; + struct mdp_destination_scaler_data *ds_data = NULL; + void __user *scale_data_user; + struct mdp_scale_data_v2 *scale_data = NULL; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct mdss_data_type *mdata; + + if (!mfd || !mfd->mdp.private1) { + pr_err("mfd is NULL or operation not permitted\n"); + ret = -EINVAL; + goto err; + } + + mdata = mfd_to_mdata(mfd); + if (!mdata) { + pr_err("mdata is NULL or not initialized\n"); + ret = -EINVAL; + goto err; + } + + if (commit->commit_v1.dest_scaler_cnt > + mdata->scaler_off->ndest_scalers) { + pr_err("Commit destination scaler cnt larger than HW setting, commit cnt=%d\n", + commit->commit_v1.dest_scaler_cnt); + ret = -EINVAL; + goto err; + } + + ds_data_user = (struct mdp_destination_scaler_data *) + commit->commit_v1.dest_scaler; + data_size = commit->commit_v1.dest_scaler_cnt * + sizeof(struct mdp_destination_scaler_data); + ds_data = kzalloc(data_size, GFP_KERNEL); + if (!ds_data) { + ret = -ENOMEM; + goto err; + } + + ret = copy_from_user(ds_data, ds_data_user, data_size); + if (ret) { + pr_err("dest scaler data copy from user failed\n"); + goto err; + } + + commit->commit_v1.dest_scaler = ds_data; + + for (i = 0; i < commit->commit_v1.dest_scaler_cnt; i++) { + scale_data = NULL; + + if (ds_data[i].scale) { + scale_data_user = to_user_ptr(ds_data[i].scale); + data_size = sizeof(struct mdp_scale_data_v2); + + scale_data = kzalloc(data_size, GFP_KERNEL); + if (!scale_data) { + ds_data[i].scale = 0; + ret = -ENOMEM; + goto err; + } + + ds_data[i].scale = to_user_u64(scale_data); + } + + if (scale_data && (ds_data[i].flags & + (MDP_DESTSCALER_SCALE_UPDATE | + MDP_DESTSCALER_ENHANCER_UPDATE))) { + ret = copy_from_user(scale_data, scale_data_user, + data_size); + if (ret) { + pr_err("scale data copy from user failed\n"); + goto err; + } + } + } + + return ret; + +err: + if (ds_data) { + for (i = 0; i < commit->commit_v1.dest_scaler_cnt; i++) { + scale_data = to_user_ptr(ds_data[i].scale); + kfree(scale_data); + } + kfree(ds_data); + } + + return ret; +} + static int mdss_fb_atomic_commit_ioctl(struct fb_info *info, unsigned long *argp, struct file *file) { @@ -4279,6 +4385,8 @@ static int mdss_fb_atomic_commit_ioctl(struct fb_info *info, struct mdp_input_layer __user *input_layer_list; struct mdp_output_layer *output_layer = NULL; struct mdp_output_layer __user *output_layer_user; + struct mdp_destination_scaler_data *ds_data = NULL; + struct mdp_destination_scaler_data __user *ds_data_user; ret = copy_from_user(&commit, argp, sizeof(struct mdp_layer_commit)); if (ret) { @@ -4356,6 +4464,16 @@ static int mdss_fb_atomic_commit_ioctl(struct fb_info *info, } } + ds_data_user = commit.commit_v1.dest_scaler; + if (ds_data_user) { + ret = __mdss_fb_copy_destscaler_data(info, &commit); + if (ret) { + pr_err("copy dest scaler failed\n"); + goto err; + } + ds_data = commit.commit_v1.dest_scaler; + } + ATRACE_BEGIN("ATOMIC_COMMIT"); ret = mdss_fb_atomic_commit(info, &commit, file); if (ret) @@ -4372,6 +4490,7 @@ static int mdss_fb_atomic_commit_ioctl(struct fb_info *info, commit.commit_v1.input_layers = input_layer_list; commit.commit_v1.output_layer = output_layer_user; + commit.commit_v1.dest_scaler = ds_data_user; rc = copy_to_user(argp, &commit, sizeof(struct mdp_layer_commit)); if (rc) { @@ -4397,6 +4516,11 @@ err: } kfree(layer_list); kfree(output_layer); + if (ds_data) { + for (i = 0; i < commit.commit_v1.dest_scaler_cnt; i++) + kfree(to_user_ptr(ds_data[i].scale)); + kfree(ds_data); + } return ret; } diff --git a/drivers/video/fbdev/msm/mdss_mdp.c b/drivers/video/fbdev/msm/mdss_mdp.c index 6ca1883da1bb..ff58514ef63a 100644 --- a/drivers/video/fbdev/msm/mdss_mdp.c +++ b/drivers/video/fbdev/msm/mdss_mdp.c @@ -1940,9 +1940,10 @@ static u32 mdss_mdp_res_init(struct mdss_data_type *mdata) static u32 mdss_mdp_scaler_init(struct mdss_data_type *mdata, struct device *dev) { - int ret; + int ret = -EINVAL; struct device_node *node; u32 prop_val; + int len = 0; if (!dev) return -EPERM; @@ -1978,8 +1979,7 @@ static u32 mdss_mdp_scaler_init(struct mdss_data_type *mdata, } mdata->scaler_off->vig_scaler_lut_off = prop_val; mdata->scaler_off->has_dest_scaler = - of_property_read_bool(mdata->pdev->dev.of_node, - "qcom,mdss-has-dest-scaler"); + of_property_read_bool(node, "qcom,mdss-has-dest-scaler"); if (mdata->scaler_off->has_dest_scaler) { ret = of_property_read_u32(node, "qcom,mdss-dest-block-off", @@ -1991,40 +1991,65 @@ static u32 mdss_mdp_scaler_init(struct mdss_data_type *mdata, } mdata->scaler_off->dest_base = mdata->mdss_io.base + prop_val; - mdata->scaler_off->ndest_scalers = - mdss_mdp_parse_dt_prop_len(mdata->pdev, - "qcom,mdss-dest-scalers-off"); + + if (!of_find_property(node, "qcom,mdss-dest-scaler-off", &len) + || (len < 1)) { + pr_err("find property %s failed ret %d\n", + "qcom,mdss-dest-scaler-off", ret); + return -EINVAL; + } + mdata->scaler_off->ndest_scalers = len/sizeof(u32); + mdata->scaler_off->dest_scaler_off = - devm_kzalloc(&mdata->pdev->dev, sizeof(u32) * + devm_kzalloc(dev, sizeof(u32) * mdata->scaler_off->ndest_scalers, GFP_KERNEL); if (!mdata->scaler_off->dest_scaler_off) { - kfree(mdata->scaler_off->dest_scaler_off); return -ENOMEM; } - ret = mdss_mdp_parse_dt_handler(mdata->pdev, + ret = of_property_read_u32_array(node, "qcom,mdss-dest-scaler-off", mdata->scaler_off->dest_scaler_off, mdata->scaler_off->ndest_scalers); if (ret) - return -EINVAL; + return ret; + mdata->scaler_off->dest_scaler_lut_off = - devm_kzalloc(&mdata->pdev->dev, sizeof(u32) * + devm_kzalloc(dev, sizeof(u32) * mdata->scaler_off->ndest_scalers, GFP_KERNEL); if (!mdata->scaler_off->dest_scaler_lut_off) { - kfree(mdata->scaler_off->dest_scaler_lut_off); return -ENOMEM; } - ret = mdss_mdp_parse_dt_handler(mdata->pdev, - "qcom,mdss-dest-scalers-lut-off", + ret = of_property_read_u32_array(node, + "qcom,mdss-dest-scaler-lut-off", mdata->scaler_off->dest_scaler_lut_off, mdata->scaler_off->ndest_scalers); if (ret) - return -EINVAL; + return ret; + + ret = of_property_read_u32(dev->of_node, + "qcom,max-dest-scaler-input-width", + &mdata->max_dest_scaler_input_width); + if (ret) { + pr_debug("read property %s failed ret %d\n", + "qcom,max-dest-scaler-input-width", + ret); + } + + ret = of_property_read_u32(dev->of_node, + "qcom,max-dest-scaler-output-width", + &mdata->max_dest_scaler_output_width); + if (ret) { + pr_debug("read property %s failed ret %d\n", + "qcom,max-dest-scaler-output-width", + ret); + } + + ret = mdss_mdp_ds_addr_setup(mdata); } - return 0; + return ret; } /** @@ -2322,6 +2347,16 @@ ssize_t mdss_mdp_show_capabilities(struct device *dev, if (mdata->clk_factor.numer) SPRINT("clk_fudge_factor=%u,%u\n", mdata->clk_factor.numer, mdata->clk_factor.denom); + if (test_bit(MDSS_CAPS_DEST_SCALER, mdata->mdss_caps_map)) { + SPRINT("max_dest_scaler_input_width=%u\n", + mdata->max_dest_scaler_input_width); + SPRINT("max_dest_scaler_output_width=%u\n", + mdata->max_dest_scaler_output_width); + SPRINT("dest_scaler_count=%u\n", + mdata->scaler_off->ndest_scalers); + SPRINT("max_dest_scale_up=%u\n", MAX_UPSCALE_RATIO); + } + SPRINT("features="); if (mdata->has_bwc) SPRINT(" bwc"); diff --git a/drivers/video/fbdev/msm/mdss_mdp.h b/drivers/video/fbdev/msm/mdss_mdp.h index bedbc10714c1..ac53af52c913 100644 --- a/drivers/video/fbdev/msm/mdss_mdp.h +++ b/drivers/video/fbdev/msm/mdss_mdp.h @@ -86,6 +86,36 @@ #define MAX_LAYER_COUNT 0xC +/** + * Destination Scaler control flags setting + * + * @DS_ENABLE: Setting the bit indicates Destination Scaler is enabled. Unset + * the bit indicates Destination Scaler is disable. + * @DS_DUAL_MODE: Setting the bit indicates Left and Right Destination Scaler + * are operated in Dual mode. + * @DS_LEFT: Setting the bit indicates current Destination Scaler is assigned + * with the Left LM. DS_LEFT and DS_DUAL_MODE can be used + * together. + * @DS_RIGHT: Setting the bit indicates current Destination Scaler is assigned + * with the Right LM. DS_RIGHT and DS_DUAL_MODE can be used + * together. + * @DS_SCALE_UPDATE: Setting the bit indicates current Destination Scaler + * QSEED3 parameters needs to be updated. + * @DS_ENHANCER_UPDATE: Setting this bit indicates current Desitnation Scaler + * QSEED3 Detial enhancer parameters need to be updated. + */ +#define DS_ENABLE BIT(0) +#define DS_DUAL_MODE BIT(1) +#define DS_LEFT BIT(2) +#define DS_RIGHT BIT(3) +#define DS_SCALE_UPDATE BIT(4) +#define DS_ENHANCER_UPDATE BIT(5) + +/** + * Destination Scaler DUAL mode overfetch pixel count + */ +#define MDSS_MDP_DS_OVERFETCH_SIZE 5 + /* hw cursor can only be setup in highest mixer stage */ #define HW_CURSOR_STAGE(mdata) \ (((mdata)->max_target_zorder + MDSS_MDP_STAGE_0) - 1) @@ -310,6 +340,25 @@ struct mdss_mdp_writeback { u8 supported_output_formats[BITS_TO_BYTES(MDP_IMGTYPE_LIMIT1)]; }; +/* + * Destination scaler info + * destination scaler is hard wired to DSPP0/1 and LM0/1 + * Input dimension is always matching to LM output dimension + * Output dimension is the Panel/WB dimension + * In bypass mode (off), input and output dimension is the same + */ +struct mdss_mdp_destination_scaler { + u32 num; + char __iomem *ds_base; + char __iomem *scaler_base; + char __iomem *lut_base; + u16 src_width; + u16 src_height; + u32 flags; + struct mdp_scale_data_v2 scaler; +}; + + struct mdss_mdp_ctl_intfs_ops { int (*start_fnc)(struct mdss_mdp_ctl *ctl); int (*stop_fnc)(struct mdss_mdp_ctl *ctl, int panel_power_state); @@ -462,6 +511,8 @@ struct mdss_mdp_mixer { char __iomem *base; char __iomem *dspp_base; char __iomem *pingpong_base; + /* Destination Scaler is hard wired to each mixer */ + struct mdss_mdp_destination_scaler *ds; u8 type; u8 params_changed; u16 width; @@ -1049,12 +1100,63 @@ static inline int mdss_mdp_line_buffer_width(void) return MAX_LINE_BUFFER_WIDTH; } +static inline int is_dest_scaling_enable(struct mdss_mdp_mixer *mixer) +{ + return (test_bit(MDSS_CAPS_DEST_SCALER, mdss_res->mdss_caps_map) && + mixer && mixer->ds && (mixer->ds->flags & DS_ENABLE)); +} + +static inline u32 get_ds_input_width(struct mdss_mdp_mixer *mixer) +{ + struct mdss_mdp_destination_scaler *ds; + + ds = mixer->ds; + if (ds) + return ds->src_width; + + return 0; +} + +static inline u32 get_ds_input_height(struct mdss_mdp_mixer *mixer) +{ + struct mdss_mdp_destination_scaler *ds; + + ds = mixer->ds; + if (ds) + return ds->src_height; + + return 0; +} + +static inline u32 get_ds_output_width(struct mdss_mdp_mixer *mixer) +{ + struct mdss_mdp_destination_scaler *ds; + + ds = mixer->ds; + if (ds) + return ds->scaler.dst_width; + + return 0; +} + +static inline u32 get_ds_output_height(struct mdss_mdp_mixer *mixer) +{ + struct mdss_mdp_destination_scaler *ds; + + ds = mixer->ds; + if (ds) + return ds->scaler.dst_height; + + return 0; +} + static inline u32 get_panel_yres(struct mdss_panel_info *pinfo) { u32 yres; yres = pinfo->yres + pinfo->lcdc.border_top + pinfo->lcdc.border_bottom; + return yres; } @@ -1064,6 +1166,7 @@ static inline u32 get_panel_xres(struct mdss_panel_info *pinfo) xres = pinfo->xres + pinfo->lcdc.border_left + pinfo->lcdc.border_right; + return xres; } @@ -1653,6 +1756,7 @@ int mdss_mdp_ctl_addr_setup(struct mdss_data_type *mdata, u32 *ctl_offsets, u32 len); int mdss_mdp_wb_addr_setup(struct mdss_data_type *mdata, u32 num_wb, u32 num_intf_wb); +int mdss_mdp_ds_addr_setup(struct mdss_data_type *mdata); void mdss_mdp_pipe_clk_force_off(struct mdss_mdp_pipe *pipe); int mdss_mdp_pipe_fetch_halt(struct mdss_mdp_pipe *pipe, bool is_recovery); diff --git a/drivers/video/fbdev/msm/mdss_mdp_ctl.c b/drivers/video/fbdev/msm/mdss_mdp_ctl.c index 6626c7eb2326..bb963079cc76 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_ctl.c +++ b/drivers/video/fbdev/msm/mdss_mdp_ctl.c @@ -1205,6 +1205,7 @@ static void mdss_mdp_perf_calc_mixer(struct mdss_mdp_mixer *mixer, struct mdss_panel_info *pinfo = NULL; int fps = DEFAULT_FRAME_RATE; u32 v_total = 0, bpp = MDSS_MDP_WB_OUTPUT_BPP; + u32 h_total = 0; int i; u32 max_clk_rate = 0; u64 bw_overlap_max = 0; @@ -1235,6 +1236,10 @@ static void mdss_mdp_perf_calc_mixer(struct mdss_mdp_mixer *mixer, fps = mdss_panel_get_framerate(pinfo); v_total = mdss_panel_get_vtotal(pinfo); } + if (is_dest_scaling_enable(mixer)) + h_total = get_ds_output_width(mixer); + else + h_total = mixer->width; } else { v_total = mixer->height; } @@ -1248,7 +1253,11 @@ static void mdss_mdp_perf_calc_mixer(struct mdss_mdp_mixer *mixer, pinfo = NULL; } - perf->mdp_clk_rate = mixer->width * v_total * fps; + /* + * with destination scaling, the increase of clock + * calculation should depends on output of size of DS setting. + */ + perf->mdp_clk_rate = h_total * v_total * fps; perf->mdp_clk_rate = mdss_mdp_clk_fudge_factor(mixer, perf->mdp_clk_rate); @@ -3429,8 +3438,15 @@ int mdss_mdp_ctl_setup(struct mdss_mdp_ctl *ctl) split_ctl = mdss_mdp_get_split_ctl(ctl); - width = get_panel_width(ctl); - height = get_panel_yres(pinfo); + if (is_dest_scaling_enable(ctl->mixer_left)) { + width = get_ds_input_width(ctl->mixer_left); + height = get_ds_input_height(ctl->mixer_left); + if (ctl->panel_data->next && is_pingpong_split(ctl->mfd)) + width *= 2; + } else { + width = get_panel_width(ctl); + height = get_panel_yres(pinfo); + } max_mixer_width = ctl->mdata->max_mixer_width; @@ -3593,8 +3609,13 @@ int mdss_mdp_ctl_reconfig(struct mdss_mdp_ctl *ctl, ctl->opmode |= (ctl->intf_num << 4); skip_intf_reconfig: - ctl->width = get_panel_xres(&pdata->panel_info); - ctl->height = get_panel_yres(&pdata->panel_info); + if (is_dest_scaling_enable(ctl->mixer_left)) { + ctl->width = get_ds_input_width(ctl->mixer_left); + ctl->height = get_ds_input_height(ctl->mixer_left); + } else { + ctl->width = get_panel_xres(&pdata->panel_info); + ctl->height = get_panel_yres(&pdata->panel_info); + } if (ctl->mixer_left) { ctl->mixer_left->width = ctl->width; ctl->mixer_left->height = ctl->height; @@ -3741,11 +3762,6 @@ int mdss_mdp_ctl_split_display_setup(struct mdss_mdp_ctl *ctl, return -ENODEV; } - sctl->width = get_panel_xres(&pdata->panel_info); - sctl->height = get_panel_yres(&pdata->panel_info); - - sctl->roi = (struct mdss_rect){0, 0, sctl->width, sctl->height}; - if (!ctl->mixer_left) { ctl->mixer_left = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, @@ -3764,6 +3780,16 @@ int mdss_mdp_ctl_split_display_setup(struct mdss_mdp_ctl *ctl, return -ENOMEM; } + if (is_dest_scaling_enable(mixer)) { + sctl->width = get_ds_input_width(mixer); + sctl->height = get_ds_input_height(mixer); + } else { + sctl->width = get_panel_xres(&pdata->panel_info); + sctl->height = get_panel_yres(&pdata->panel_info); + } + + sctl->roi = (struct mdss_rect){0, 0, sctl->width, sctl->height}; + mixer->is_right_mixer = true; mixer->width = sctl->width; mixer->height = sctl->height; @@ -4942,6 +4968,43 @@ int mdss_mdp_wb_addr_setup(struct mdss_data_type *mdata, return 0; } +int mdss_mdp_ds_addr_setup(struct mdss_data_type *mdata) +{ + struct mdss_mdp_destination_scaler *ds; + struct mdss_mdp_mixer *mixer = mdata->mixer_intf; + u32 num_ds_block; + int i; + + num_ds_block = mdata->scaler_off->ndest_scalers; + ds = devm_kcalloc(&mdata->pdev->dev, num_ds_block, + sizeof(struct mdss_mdp_destination_scaler), + GFP_KERNEL); + if (!ds) { + pr_err("unable to setup ds: kzalloc failed\n"); + return -ENOMEM; + } + + for (i = 0; i < num_ds_block; i++) { + ds[i].num = i; + ds[i].ds_base = mdata->scaler_off->dest_base; + ds[i].scaler_base = mdata->scaler_off->dest_base + + mdata->scaler_off->dest_scaler_off[i]; + ds[i].lut_base = mdata->scaler_off->dest_base + + mdata->scaler_off->dest_scaler_lut_off[i]; + + /* + * Assigning destination scaler to each LM. There is no dynamic + * assignment because destination scaler and LM are hard wired. + */ + if (i < mdata->nmixers_intf) + mixer[i].ds = &ds[i]; + } + + mdata->ds = ds; + + return 0; +} + struct mdss_mdp_mixer *mdss_mdp_mixer_get(struct mdss_mdp_ctl *ctl, int mux) { struct mdss_mdp_mixer *mixer = NULL; diff --git a/drivers/video/fbdev/msm/mdss_mdp_hwio.h b/drivers/video/fbdev/msm/mdss_mdp_hwio.h index 47a7740e0c09..74ab902f6e8e 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_hwio.h +++ b/drivers/video/fbdev/msm/mdss_mdp_hwio.h @@ -346,6 +346,10 @@ enum mdss_mdp_sspp_chroma_samp_type { #define MDSS_MDP_REG_SCALER_MISR_SIGNATURE_0 0x74 #define MDSS_MDP_REG_SCALER_MISR_SIGNATURE_1 0x78 +/* Destination scaler TOP registers */ +#define MDSS_MDP_REG_DEST_SCALER_OP_MODE 0x00 +#define MDSS_MDP_REG_DEST_SCALER_HW_VERSION 0x10 + #define SCALER_EN BIT(0) #define SCALER_DIR_EN BIT(4) #define SCALER_DE_EN BIT(8) diff --git a/drivers/video/fbdev/msm/mdss_mdp_layer.c b/drivers/video/fbdev/msm/mdss_mdp_layer.c index 8da8840f30ec..91b91dcc7960 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_layer.c +++ b/drivers/video/fbdev/msm/mdss_mdp_layer.c @@ -62,6 +62,208 @@ struct mdss_mdp_validate_info_t { struct mdss_mdp_pipe_multirect_params multirect; }; +static inline void *u64_to_ptr(uint64_t address) +{ + return (void *)(uintptr_t)address; +} + +static int __dest_scaler_data_setup(struct mdp_destination_scaler_data *ds_data, + struct mdss_mdp_destination_scaler *ds, + u32 max_input_width, u32 max_output_width) +{ + struct mdp_scale_data_v2 *scale; + + ds->flags = (ds_data->flags & MDP_DESTSCALER_ENABLE) ? DS_ENABLE : 0; + + if (ds_data->flags & (MDP_DESTSCALER_SCALE_UPDATE | + MDP_DESTSCALER_ENHANCER_UPDATE)) { + if (!ds_data->scale) { + pr_err("NULL scale data\n"); + return -EFAULT; + } + scale = u64_to_ptr(ds_data->scale); + + if (scale->src_width[0] > max_input_width) { + pr_err("Exceed max input width for dest scaler-%d: %d\n", + ds_data->dest_scaler_ndx, + scale->src_width[0]); + return -EINVAL; + } + if (scale->dst_width > max_output_width) { + pr_err("Exceed max output width for dest scaler-%d: %d\n", + ds_data->dest_scaler_ndx, + scale->dst_width); + return -EINVAL; + } + + memcpy(&ds->scaler, scale, sizeof(*scale)); + if (ds_data->flags & MDP_DESTSCALER_SCALE_UPDATE) + ds->flags |= DS_SCALE_UPDATE; + if (ds_data->flags & MDP_DESTSCALER_ENHANCER_UPDATE) + ds->flags |= DS_ENHANCER_UPDATE; + ds->src_width = scale->src_width[0]; + ds->src_height = scale->src_height[0]; + } + + if (ds_data->flags == 0) { + pr_debug("Disabling destination scaler-%d\n", + ds_data->dest_scaler_ndx); + } + + return 0; +} + +static int mdss_mdp_destination_scaler_pre_validate(struct mdss_mdp_ctl *ctl, + struct mdp_destination_scaler_data *ds_data) +{ + struct mdss_data_type *mdata; + struct mdss_panel_info *pinfo; + + mdata = ctl->mdata; + + /* + * we need to quickly check for any scale update, and adjust the mixer + * width and height accordingly. Otherwise, layer validate will fail + * when we switch between scaling factor or disabling scaling. + */ + if (test_bit(MDSS_CAPS_DEST_SCALER, mdata->mdss_caps_map) && ds_data) { + if (ctl->mixer_left) { + /* + * Any scale update from usermode, we will update the + * mixer width and height with the given LM width and + * height. + */ + pinfo = &ctl->panel_data->panel_info; + if ((ds_data->lm_width > get_panel_xres(pinfo)) || + (ds_data->lm_height > get_panel_yres(pinfo)) || + (ds_data->lm_width == 0) || + (ds_data->lm_height == 0)) { + pr_err("Invalid LM width / height setting\n"); + return -EINVAL; + } + + ctl->width = ds_data->lm_width; + ctl->height = ds_data->lm_height; + + ctl->mixer_left->width = ds_data->lm_width; + ctl->mixer_left->height = ds_data->lm_height; + pr_debug("Update mixer-left width/height: %dx%d\n", + ds_data->lm_width, ds_data->lm_width); + + } + + if (ctl->mixer_right) { + /* + * Split display both left and right should have the + * same width and height + */ + ctl->mixer_right->width = ds_data->lm_width; + ctl->mixer_right->height = ds_data->lm_height; + pr_info("Update mixer-right width/height: %dx%d\n", + ds_data->lm_width, ds_data->lm_height); + + /* + * For split display, CTL width should be equal to + * whole panel size + */ + ctl->width += ds_data->lm_width; + } + + pr_debug("Updated CTL width:%d, height:%d\n", + ctl->width, ctl->height); + } + + return 0; +} + +static int mdss_mdp_validate_destination_scaler(struct msm_fb_data_type *mfd, + struct mdp_destination_scaler_data *ds_data, + u32 ds_mode) +{ + int ret = 0; + struct mdss_data_type *mdata; + struct mdss_mdp_ctl *ctl; + struct mdss_mdp_destination_scaler *ds_left = NULL; + struct mdss_mdp_destination_scaler *ds_right = NULL; + + if (ds_data) { + mdata = mfd_to_mdata(mfd); + ctl = mfd_to_ctl(mfd); + + if (ctl->mixer_left) + ds_left = ctl->mixer_left->ds; + + if (ctl->mixer_right) + ds_right = ctl->mixer_right->ds; + + switch (ds_mode) { + case DS_DUAL_MODE: + if (!ds_left || !ds_right) { + pr_err("Cannot support DUAL mode dest scaling\n"); + return -EINVAL; + } + + ret = __dest_scaler_data_setup(&ds_data[0], ds_left, + mdata->max_dest_scaler_input_width - + MDSS_MDP_DS_OVERFETCH_SIZE, + mdata->max_dest_scaler_output_width); + if (ret) + return ret; + + ret = __dest_scaler_data_setup(&ds_data[1], ds_right, + mdata->max_dest_scaler_input_width - + MDSS_MDP_DS_OVERFETCH_SIZE, + mdata->max_dest_scaler_output_width); + if (ret) + return ret; + + ds_left->flags &= ~(DS_LEFT|DS_RIGHT); + ds_left->flags |= DS_DUAL_MODE; + ds_right->flags &= ~(DS_LEFT|DS_RIGHT); + ds_right->flags |= DS_DUAL_MODE; + break; + + case DS_LEFT: + if (!ds_left) { + pr_err("LM in ctl does not support Destination Scaler\n"); + return -EINVAL; + } + ds_left->flags &= ~(DS_DUAL_MODE|DS_RIGHT); + ds_left->flags |= DS_LEFT; + + ret = __dest_scaler_data_setup(&ds_data[0], ds_left, + mdata->max_dest_scaler_input_width, + mdata->max_dest_scaler_output_width); + break; + + case DS_RIGHT: + if (!ds_right) { + pr_err("Cannot setup DS_RIGHT because only single DS assigned to ctl\n"); + return -EINVAL; + } + + ds_right->flags &= ~(DS_DUAL_MODE|DS_LEFT); + ds_right->flags |= DS_RIGHT; + + ret = __dest_scaler_data_setup(&ds_data[0], ds_right, + mdata->max_dest_scaler_input_width, + mdata->max_dest_scaler_output_width); + break; + } + + } else { + pr_err("NULL destionation scaler data\n"); + return -EFAULT; + } + + if (ds_left) + pr_debug("DS_LEFT: flags=0x%X\n", ds_left->flags); + if (ds_right) + pr_debug("DS_RIGHT: flags=0x%X\n", ds_right->flags); + + return ret; +} + /* * __layer_needs_src_split() - check needs source split configuration * @layer: input layer @@ -1605,11 +1807,13 @@ static int __validate_layers(struct msm_fb_data_type *mfd, u32 left_lm_w = left_lm_w_from_mfd(mfd); u32 mixer_mux, dst_x; int layer_count = commit->input_layer_cnt; + u32 ds_mode = 0; struct mdss_mdp_pipe *pipe, *tmp, *left_blend_pipe; struct mdss_mdp_pipe *right_plist[MAX_PIPES_PER_LM] = {0}; struct mdss_mdp_pipe *left_plist[MAX_PIPES_PER_LM] = {0}; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); + struct mdss_data_type *mdata = mfd_to_mdata(mfd); struct mdss_mdp_mixer *mixer = NULL; struct mdp_input_layer *layer, *prev_layer, *layer_list; @@ -1863,6 +2067,37 @@ static int __validate_layers(struct msm_fb_data_type *mfd, layer->z_order -= MDSS_MDP_STAGE_0; } + if (test_bit(MDSS_CAPS_DEST_SCALER, mdata->mdss_caps_map) && + commit->dest_scaler) { + /* + * Find out which DS block to use based on LM assignment + */ + if ((left_cnt > 0) && (right_cnt > 0) && + (commit->dest_scaler_cnt == 2)) + ds_mode = DS_DUAL_MODE; + else if ((left_cnt > 0) && (right_cnt == 0) && + (commit->dest_scaler_cnt == 1)) + ds_mode = DS_LEFT; + else if ((left_cnt == 0) && (right_cnt > 0) && + (commit->dest_scaler_cnt == 1)) + ds_mode = DS_RIGHT; + else { + pr_err("Commit destination scaler count not matching with LM assignment, DS-cnt:%d\n", + commit->dest_scaler_cnt); + ret = -EINVAL; + goto validate_exit; + } + + ret = mdss_mdp_validate_destination_scaler(mfd, + commit->dest_scaler, + ds_mode); + if (ret) { + pr_err("fail to validate destination scaler\n"); + layer->error_code = ret; + goto validate_exit; + } + } + ret = mdss_mdp_perf_bw_check(mdp5_data->ctl, left_plist, left_cnt, right_plist, right_cnt); if (ret) { @@ -2130,6 +2365,12 @@ int mdss_mdp_layer_atomic_validate(struct msm_fb_data_type *mfd, } } + if (mdss_mdp_destination_scaler_pre_validate(mdp5_data->ctl, + commit->dest_scaler)) { + pr_err("Destination scaler pre-validate failed\n"); + return -EINVAL; + } + return __validate_layers(mfd, file, commit); } diff --git a/drivers/video/fbdev/msm/mdss_mdp_pp.c b/drivers/video/fbdev/msm/mdss_mdp_pp.c index 715f4428e81a..f92d4bb9ed1d 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_pp.c +++ b/drivers/video/fbdev/msm/mdss_mdp_pp.c @@ -221,6 +221,26 @@ struct mdp_csc_cfg mdp_csc_10bit_convert[MDSS_MDP_MAX_CSC] = { }, }; +static struct mdss_mdp_format_params dest_scaler_fmt = { + .format = MDP_XBGR_2101010, + .flag = 0, + .fetch_planes = MDSS_MDP_PLANE_INTERLEAVED, + .unpack_tight = 1, + .unpack_align_msb = 0, + .alpha_enable = 0, + .unpack_count = 4, + .bpp = 4, + .fetch_mode = MDSS_MDP_FETCH_LINEAR, + .element = { C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr }, + .bits = { + [C3_ALPHA] = 3, + [C2_R_Cr] = 3, + [C0_G_Y] = 3, + [C1_B_Cb] = 3, + }, + .unpack_dx_format = 1, +}; + #define CSC_MV_OFF 0x0 #define CSC_BV_OFF 0x2C #define CSC_LV_OFF 0x14 @@ -1589,48 +1609,15 @@ static void mdss_mdp_scaler_detail_enhance_cfg( } } -int mdss_mdp_qseed3_setup(struct mdss_mdp_pipe *pipe, - int location, int id) +int mdss_mdp_qseed3_setup(struct mdp_scale_data_v2 *scaler, + char __iomem *offset, + char __iomem *lut_offset, + struct mdss_mdp_format_params *fmt) { int rc = 0; - struct mdp_scale_data_v2 *scaler; - struct mdss_data_type *mdata; - char __iomem *offset, *lut_offset; - struct mdss_mdp_format_params *fmt; uint32_t op_mode = 0; uint32_t phase_init, preload, src_y_rgb, src_uv, dst; - mdata = mdss_mdp_get_mdata(); - /* SRC pipe QSEED3 Configuration */ - if (location == SSPP_VIG) { - scaler = &pipe->scaler; - offset = pipe->base + mdata->scaler_off->vig_scaler_off; - lut_offset = pipe->base + mdata->scaler_off->vig_scaler_lut_off; - fmt = pipe->src_fmt; - } else if (location == DSPP) { - /* Destination scaler QSEED3 Configuration */ - if ((mdata->scaler_off->has_dest_scaler) && - (id < mdata->scaler_off->ndest_scalers)) { - /* TODO :point to the destination params */ - scaler = NULL; - offset = mdata->scaler_off->dest_base + - mdata->scaler_off->dest_scaler_off[id]; - lut_offset = mdata->scaler_off->dest_base + - mdata->scaler_off->dest_scaler_lut_off[id]; - /*TODO : set pixel fmt to RGB101010 */ - return -ENOSYS; - } else { - return -EINVAL; - } - } else { - return -EINVAL; - } - - if (!scaler) { - pr_debug("scaler pointer is NULL\n"); - return 0; - } - pr_debug("scaler->enable=%d", scaler->enable); if (scaler->enable) { @@ -1650,8 +1637,6 @@ int mdss_mdp_qseed3_setup(struct mdss_mdp_pipe *pipe, ALPHA_FILTER_CFG; } - /* TODO:if src_fmt is 10 bits program the bitwidth - * accordingly */ if (!fmt->unpack_dx_format) op_mode |= 0x1 << SCALER_BIT_WIDTH; @@ -1750,12 +1735,24 @@ static int mdss_mdp_scale_setup(struct mdss_mdp_pipe *pipe, { struct mdss_data_type *mdata; int rc = 0; + char __iomem *offset, *lut_offset; mdata = mdss_mdp_get_mdata(); - if (test_bit(MDSS_CAPS_QSEED3, mdata->mdss_caps_map)) - rc = mdss_mdp_qseed3_setup(pipe, pp_blk, 0); - else + + if (test_bit(MDSS_CAPS_QSEED3, mdata->mdss_caps_map)) { + if (pp_blk == SSPP_VIG) { + offset = pipe->base + mdata->scaler_off->vig_scaler_off; + lut_offset = pipe->base + + mdata->scaler_off->vig_scaler_lut_off; + + rc = mdss_mdp_qseed3_setup(&pipe->scaler, offset, + lut_offset, pipe->src_fmt); + } else { + rc = -EINVAL; + } + } else { rc = mdss_mdp_qseed2_setup(pipe); + } if (rc) pr_err("scale setup on pipe %d type %d failed ret %d\n", @@ -2432,6 +2429,71 @@ dspp_exit: return ret; } +static int pp_dest_scaler_setup(struct mdss_mdp_mixer *mixer) +{ + struct mdss_mdp_ctl *ctl; + struct mdss_data_type *mdata; + struct mdss_mdp_destination_scaler *ds; + int ret = 0; + u32 op_mode; + u32 mask; + char *ds_offset; + + if (!mixer || !mixer->ctl || !mixer->ctl->mdata) + return -EINVAL; + + ctl = mixer->ctl; + mdata = ctl->mdata; + ds = mixer->ds; + + if (!test_bit(MDSS_CAPS_DEST_SCALER, mdata->mdss_caps_map) || !ds) + return 0; + + ds_offset = ds->ds_base; + op_mode = readl_relaxed(MDSS_MDP_REG_DEST_SCALER_OP_MODE + + ds_offset); + + mask = BIT(ds->num); + if (ds->flags & DS_ENABLE) + op_mode |= mask; + else + op_mode &= ~mask; + + if (ds->flags & DS_DUAL_MODE) + op_mode |= BIT(16); + else + op_mode &= ~BIT(16); + + writel_relaxed(op_mode, MDSS_MDP_REG_DEST_SCALER_OP_MODE + ds_offset); + + if (ds->flags & DS_SCALE_UPDATE) { + ret = mdss_mdp_qseed3_setup(&ds->scaler, + ds->scaler_base, ds->lut_base, + &dest_scaler_fmt); + if (ret) { + pr_err("Failed setup destination scaler\n"); + return ret; + } + /* + * Clearing the flag because we don't need to program the block + * for each commit if there is no change. + */ + ds->flags &= ~DS_SCALE_UPDATE; + } + + if (ds->flags & DS_ENHANCER_UPDATE) { + mdss_mdp_scaler_detail_enhance_cfg(&ds->scaler.detail_enhance, + ds->scaler_base); + ds->flags &= ~DS_ENHANCER_UPDATE; + } + + /* Destinations scaler shared the flush with DSPP in control */ + if (ds->flags & DS_ENABLE) + ctl->flush_bits |= BIT(13 + ds->num); + + return 0; +} + int mdss_mdp_pp_setup(struct mdss_mdp_ctl *ctl) { int ret = 0; @@ -2521,11 +2583,13 @@ int mdss_mdp_pp_setup_locked(struct mdss_mdp_ctl *ctl) } if (ctl->mixer_left) { + pp_dest_scaler_setup(ctl->mixer_left); pp_mixer_setup(ctl->mixer_left); pp_dspp_setup(disp_num, ctl->mixer_left); pp_ppb_setup(ctl->mixer_left); } if (ctl->mixer_right) { + pp_dest_scaler_setup(ctl->mixer_right); pp_mixer_setup(ctl->mixer_right); pp_dspp_setup(disp_num, ctl->mixer_right); pp_ppb_setup(ctl->mixer_right); |