summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/video/fbdev/msm/mdss_hdmi_tx.c137
-rw-r--r--drivers/video/fbdev/msm/mdss_mdp_overlay.c24
-rw-r--r--drivers/video/fbdev/msm/mdss_panel.h3
-rw-r--r--include/uapi/linux/msm_mdp_ext.h6
4 files changed, 162 insertions, 8 deletions
diff --git a/drivers/video/fbdev/msm/mdss_hdmi_tx.c b/drivers/video/fbdev/msm/mdss_hdmi_tx.c
index ff44d0ae4ac5..e26a6dbb5d99 100644
--- a/drivers/video/fbdev/msm/mdss_hdmi_tx.c
+++ b/drivers/video/fbdev/msm/mdss_hdmi_tx.c
@@ -71,6 +71,7 @@
#define HDMI_TX_MIN_FPS 20000
#define HDMI_TX_MAX_FPS 120000
+#define HDMI_KHZ_TO_HZ 1000
#define HDMI_TX_VERSION_403 0x40000003 /* msm8998 */
#define HDMI_GET_MSB(x) (x >> 8)
@@ -114,6 +115,7 @@ static int hdmi_tx_audio_info_setup(struct platform_device *pdev,
static int hdmi_tx_get_audio_edid_blk(struct platform_device *pdev,
struct msm_ext_disp_audio_edid_blk *blk);
static int hdmi_tx_get_cable_status(struct platform_device *pdev, u32 vote);
+static int hdmi_tx_update_ppm(struct hdmi_tx_ctrl *hdmi_ctrl, s32 ppm);
static struct mdss_hw hdmi_tx_hw = {
.hw_ndx = MDSS_HW_HDMI,
@@ -648,10 +650,11 @@ static int hdmi_tx_update_pixel_clk(struct hdmi_tx_ctrl *hdmi_ctrl)
{
struct dss_module_power *power_data = NULL;
struct mdss_panel_info *pinfo;
+ u32 new_clk_rate = 0;
int rc = 0;
if (!hdmi_ctrl) {
- DEV_ERR("%s: invalid input\n", __func__);
+ pr_err("invalid input\n");
rc = -EINVAL;
goto end;
}
@@ -660,21 +663,25 @@ static int hdmi_tx_update_pixel_clk(struct hdmi_tx_ctrl *hdmi_ctrl)
power_data = &hdmi_ctrl->pdata.power_data[HDMI_TX_CORE_PM];
if (!power_data) {
- DEV_ERR("%s: Error: invalid power data\n", __func__);
+ pr_err("Error: invalid power data\n");
rc = -EINVAL;
goto end;
}
- if (power_data->clk_config->rate == pinfo->clk_rate) {
- rc = -EINVAL;
+ new_clk_rate = hdmi_tx_setup_tmds_clk_rate(pinfo->clk_rate,
+ pinfo->out_format, hdmi_ctrl->panel.dc_enable);
+
+ if (power_data->clk_config->rate == new_clk_rate)
goto end;
- }
- power_data->clk_config->rate = pinfo->clk_rate;
+ power_data->clk_config->rate = new_clk_rate;
- DEV_DBG("%s: rate %ld\n", __func__, power_data->clk_config->rate);
+ pr_debug("rate %ld\n", power_data->clk_config->rate);
- msm_dss_clk_set_rate(power_data->clk_config, power_data->num_clk);
+ rc = msm_dss_clk_set_rate(power_data->clk_config, power_data->num_clk);
+ if (rc < 0)
+ pr_err("failed to set clock rate %lu\n",
+ power_data->clk_config->rate);
end:
return rc;
}
@@ -1316,6 +1323,35 @@ end:
return ret;
}
+static ssize_t hdmi_tx_sysfs_wta_hdmi_ppm(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int ret, ppm;
+ struct hdmi_tx_ctrl *hdmi_ctrl
+ = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
+
+ if (!hdmi_ctrl) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&hdmi_ctrl->tx_lock);
+
+ ret = kstrtoint(buf, 10, &ppm);
+ if (ret) {
+ pr_err("kstrtoint failed. rc=%d\n", ret);
+ goto end;
+ }
+
+ hdmi_tx_update_ppm(hdmi_ctrl, ppm);
+
+ ret = strnlen(buf, PAGE_SIZE);
+ pr_debug("write ppm %d\n", ppm);
+end:
+ mutex_unlock(&hdmi_ctrl->tx_lock);
+ return ret;
+}
+
static DEVICE_ATTR(connected, S_IRUGO, hdmi_tx_sysfs_rda_connected, NULL);
static DEVICE_ATTR(hot_plug, S_IWUSR, NULL, hdmi_tx_sysfs_wta_hot_plug);
static DEVICE_ATTR(sim_mode, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_sim_mode,
@@ -1336,6 +1372,8 @@ static DEVICE_ATTR(s3d_mode, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_s3d_mode,
hdmi_tx_sysfs_wta_s3d_mode);
static DEVICE_ATTR(5v, S_IWUSR, NULL, hdmi_tx_sysfs_wta_5v);
static DEVICE_ATTR(hdr_stream, S_IWUSR, NULL, hdmi_tx_sysfs_wta_hdr_stream);
+static DEVICE_ATTR(hdmi_ppm, S_IRUGO | S_IWUSR, NULL,
+ hdmi_tx_sysfs_wta_hdmi_ppm);
static struct attribute *hdmi_tx_fs_attrs[] = {
&dev_attr_connected.attr,
@@ -1351,6 +1389,7 @@ static struct attribute *hdmi_tx_fs_attrs[] = {
&dev_attr_s3d_mode.attr,
&dev_attr_5v.attr,
&dev_attr_hdr_stream.attr,
+ &dev_attr_hdmi_ppm.attr,
NULL,
};
static struct attribute_group hdmi_tx_fs_attrs_group = {
@@ -3566,6 +3605,80 @@ static void hdmi_tx_fps_work(struct work_struct *work)
hdmi_tx_update_fps(hdmi_ctrl);
}
+static u64 hdmi_tx_clip_valid_pclk(struct hdmi_tx_ctrl *hdmi_ctrl, u64 pclk_in)
+{
+ struct msm_hdmi_mode_timing_info timing = {0};
+ u32 pclk_delta, pclk;
+ u64 pclk_clip = pclk_in;
+
+ hdmi_get_supported_mode(&timing,
+ &hdmi_ctrl->ds_data, hdmi_ctrl->vic);
+
+ /* as per standard, 0.5% of deviation is allowed */
+ pclk = timing.pixel_freq * HDMI_KHZ_TO_HZ;
+ pclk_delta = pclk * 5 / 1000;
+
+ if (pclk_in < (pclk - pclk_delta))
+ pclk_clip = pclk - pclk_delta;
+ else if (pclk_in > (pclk + pclk_delta))
+ pclk_clip = pclk + pclk_delta;
+
+ if (pclk_in != pclk_clip)
+ pr_debug("the deviation is too big, so clip pclk from %lld to %lld\n",
+ pclk_in, pclk_clip);
+
+ return pclk_clip;
+}
+
+/**
+ * hdmi_tx_update_ppm() - Update the HDMI pixel clock as per the input ppm
+ *
+ * @ppm: ppm is parts per million multiplied by 1000.
+ * return: 0 on success, non-zero in case of failure.
+ */
+static int hdmi_tx_update_ppm(struct hdmi_tx_ctrl *hdmi_ctrl, s32 ppm)
+{
+ struct mdss_panel_info *pinfo = NULL;
+ u64 cur_pclk, dst_pclk;
+ u64 clip_pclk;
+ int rc = 0;
+
+ if (!hdmi_ctrl) {
+ pr_err("invalid hdmi_ctrl\n");
+ return -EINVAL;
+ }
+
+ pinfo = &hdmi_ctrl->panel_data.panel_info;
+
+ /* only available in case HDMI is up */
+ if (!hdmi_tx_is_panel_on(hdmi_ctrl)) {
+ pr_err("hdmi is not on\n");
+ return -EINVAL;
+ }
+
+ /* get current pclk */
+ cur_pclk = pinfo->clk_rate;
+ /* get desired pclk */
+ dst_pclk = cur_pclk * (1000000000 + ppm);
+ do_div(dst_pclk, 1000000000);
+
+ clip_pclk = hdmi_tx_clip_valid_pclk(hdmi_ctrl, dst_pclk);
+
+ /* update pclk */
+ if (clip_pclk != cur_pclk) {
+ pr_debug("pclk changes from %llu to %llu when ppm is %d\n",
+ cur_pclk, clip_pclk, ppm);
+ pinfo->clk_rate = clip_pclk;
+ rc = hdmi_tx_update_pixel_clk(hdmi_ctrl);
+ if (rc < 0) {
+ pr_err("PPM update failed, reset clock rate\n");
+ pinfo->clk_rate = cur_pclk;
+ }
+ }
+
+ return rc;
+}
+
static int hdmi_tx_evt_handle_register(struct hdmi_tx_ctrl *hdmi_ctrl)
{
int rc = 0;
@@ -3796,6 +3909,13 @@ static int hdmi_tx_evt_handle_deep_color(struct hdmi_tx_ctrl *hdmi_ctrl)
return 0;
}
+static int hdmi_tx_evt_handle_hdmi_ppm(struct hdmi_tx_ctrl *hdmi_ctrl)
+{
+ s32 ppm = (s32) (unsigned long)hdmi_ctrl->evt_arg;
+
+ return hdmi_tx_update_ppm(hdmi_ctrl, ppm);
+}
+
static int hdmi_tx_event_handler(struct mdss_panel_data *panel_data,
int event, void *arg)
{
@@ -4497,6 +4617,7 @@ static int hdmi_tx_init_event_handler(struct hdmi_tx_ctrl *hdmi_ctrl)
handler[MDSS_EVENT_PANEL_OFF] = hdmi_tx_evt_handle_panel_off;
handler[MDSS_EVENT_CLOSE] = hdmi_tx_evt_handle_close;
handler[MDSS_EVENT_DEEP_COLOR] = hdmi_tx_evt_handle_deep_color;
+ handler[MDSS_EVENT_UPDATE_PANEL_PPM] = hdmi_tx_evt_handle_hdmi_ppm;
return 0;
}
diff --git a/drivers/video/fbdev/msm/mdss_mdp_overlay.c b/drivers/video/fbdev/msm/mdss_mdp_overlay.c
index 6ba5828479f5..5d2a4bcfd8bf 100644
--- a/drivers/video/fbdev/msm/mdss_mdp_overlay.c
+++ b/drivers/video/fbdev/msm/mdss_mdp_overlay.c
@@ -4614,6 +4614,20 @@ static int mdss_fb_set_metadata(struct msm_fb_data_type *mfd,
return ret;
}
+static int mdss_fb_set_panel_ppm(struct msm_fb_data_type *mfd, s32 ppm)
+{
+ struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
+ int ret = 0;
+
+ if (!ctl)
+ return -EPERM;
+
+ ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UPDATE_PANEL_PPM,
+ (void *) (unsigned long) ppm,
+ CTL_INTF_EVENT_FLAG_DEFAULT);
+ return ret;
+}
+
static int mdss_fb_get_hw_caps(struct msm_fb_data_type *mfd,
struct mdss_hw_caps *caps)
{
@@ -5160,6 +5174,16 @@ static int mdss_mdp_overlay_ioctl_handler(struct msm_fb_data_type *mfd,
}
ret = mdss_mdp_set_cfg(mfd, &cfg);
break;
+ case MSMFB_MDP_SET_PANEL_PPM:
+ ret = copy_from_user(&val, argp, sizeof(val));
+ if (ret) {
+ pr_err("copy failed MSMFB_MDP_SET_PANEL_PPM ret %d\n",
+ ret);
+ ret = -EFAULT;
+ break;
+ }
+ ret = mdss_fb_set_panel_ppm(mfd, val);
+ break;
default:
break;
diff --git a/drivers/video/fbdev/msm/mdss_panel.h b/drivers/video/fbdev/msm/mdss_panel.h
index 0483e3d42873..1235934ccbb4 100644
--- a/drivers/video/fbdev/msm/mdss_panel.h
+++ b/drivers/video/fbdev/msm/mdss_panel.h
@@ -255,6 +255,8 @@ struct mdss_intf_recovery {
* Argument provided is new panel timing.
* @MDSS_EVENT_DEEP_COLOR: Set deep color.
* Argument provided is bits per pixel (8/10/12)
+ * @MDSS_EVENT_UPDATE_PANEL_PPM: update pixel clock by input PPM.
+ * Argument provided is parts per million.
*/
enum mdss_intf_events {
MDSS_EVENT_RESET = 1,
@@ -287,6 +289,7 @@ enum mdss_intf_events {
MDSS_EVENT_PANEL_TIMING_SWITCH,
MDSS_EVENT_DEEP_COLOR,
MDSS_EVENT_DISABLE_PANEL,
+ MDSS_EVENT_UPDATE_PANEL_PPM,
MDSS_EVENT_MAX,
};
diff --git a/include/uapi/linux/msm_mdp_ext.h b/include/uapi/linux/msm_mdp_ext.h
index 1a71e860ba48..3a049c1ba69a 100644
--- a/include/uapi/linux/msm_mdp_ext.h
+++ b/include/uapi/linux/msm_mdp_ext.h
@@ -31,6 +31,12 @@
struct mdp_set_cfg)
/*
+ * Ioctl for setting the PLL PPM.
+ * PLL PPM is passed by the user space using this IOCTL.
+ */
+#define MSMFB_MDP_SET_PANEL_PPM _IOW(MDP_IOCTL_MAGIC, 131, int)
+
+/*
* To allow proper structure padding for 64bit/32bit target
*/
#ifdef __LP64