From a400e381d0386c45af839b8058f6f8de542ba807 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Sat, 22 Oct 2016 02:15:10 +0800 Subject: [PATCH] msm: mdss: add support to change HDMI PLL PPM Add sysfs and ioctl to adjust HDMI clock rate by certain PPM. This function is required by clock recovery in broadcast in which HDMI PLL should be adjusted in order to reduce the clock drift in broadcast. CRs-Fixed: 1086894 Change-Id: I1df15dd6aec44ae3e78bd4f80dc70d0d04760687 Signed-off-by: Ray Zhang --- drivers/video/fbdev/msm/mdss_hdmi_tx.c | 137 +++++++++++++++++++-- drivers/video/fbdev/msm/mdss_mdp_overlay.c | 24 ++++ drivers/video/fbdev/msm/mdss_panel.h | 3 + include/uapi/linux/msm_mdp_ext.h | 6 + 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 @@ -30,6 +30,12 @@ #define MSMFB_MDP_SET_CFG _IOW(MDP_IOCTL_MAGIC, 130, \ 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 */