diff options
| -rw-r--r-- | drivers/video/fbdev/msm/mdss_hdmi_tx.c | 137 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdss_mdp_overlay.c | 24 | ||||
| -rw-r--r-- | drivers/video/fbdev/msm/mdss_panel.h | 3 | ||||
| -rw-r--r-- | 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 @@ -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 |
