summaryrefslogtreecommitdiff
path: root/drivers/video
diff options
context:
space:
mode:
authorNaseer Ahmed <naseer@codeaurora.org>2016-04-05 15:19:25 -0400
committerKyle Yan <kyan@codeaurora.org>2016-05-25 14:20:24 -0700
commit687db9c75e3ca8c5ccb649032e9fa8e90fb9485d (patch)
tree60d3ae038c448d573f28273a7ec85a0d152f18f1 /drivers/video
parente215162bb29af2325151d6148f2601b9f9a7f916 (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.h4
-rw-r--r--drivers/video/fbdev/msm/mdss_fb.c124
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp.c67
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp.h104
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_ctl.c83
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_hwio.h4
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_layer.c241
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_pp.c148
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);