From b2f2c4d53ef354a3ee548056aec10e08e74e7631 Mon Sep 17 00:00:00 2001 From: Ray Zhang Date: Tue, 7 Mar 2017 13:00:07 +0800 Subject: drm/msm: Add PLL_DELTA property to HDMI connector Clock recovery needs to update HDMI clock in order to compensate the clock drift, so add a connector property named as PLL_DELTA to support it. Meanwhile add a node in debugfs to expose this functionality. CRs-Fixed: 2015827 Change-Id: Ifdc7134b33102f112a8e3c659fae6a017ff11461 Signed-off-by: Ray Zhang --- drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c | 137 ++++++++++++++++++++- drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h | 28 +++++ drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c | 2 - drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c | 11 ++ drivers/gpu/drm/msm/msm_drv.h | 1 + drivers/gpu/drm/msm/msm_prop.c | 67 +++++++++- drivers/gpu/drm/msm/msm_prop.h | 41 +++++- drivers/gpu/drm/msm/sde/sde_connector.c | 4 + drivers/gpu/drm/msm/sde/sde_kms.c | 1 + 9 files changed, 286 insertions(+), 6 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c index 6a6d02c5444d..1ff3ee2bdca6 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c @@ -401,12 +401,111 @@ static const struct file_operations edid_vendor_name_fops = { .read = _sde_hdmi_edid_vendor_name_read, }; +static u64 _sde_hdmi_clip_valid_pclk(struct drm_display_mode *mode, u64 pclk_in) +{ + u32 pclk_delta, pclk; + u64 pclk_clip = pclk_in; + + /* as per standard, 0.5% of deviation is allowed */ + pclk = mode->clock * 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_warn("clip pclk from %lld to %lld\n", pclk_in, pclk_clip); + + return pclk_clip; +} + +/** + * _sde_hdmi_update_pll_delta() - Update the HDMI pixel clock as per input ppm + * + * @ppm: ppm is parts per million multiplied by 1000. + * return: 0 on success, non-zero in case of failure. + * + * The input ppm will be clipped if it's more than or less than 5% of the TMDS + * clock rate defined by HDMI spec. + */ +static int _sde_hdmi_update_pll_delta(struct sde_hdmi *display, s32 ppm) +{ + struct hdmi *hdmi = display->ctrl.ctrl; + struct drm_display_mode *current_mode = &display->mode; + u64 cur_pclk, dst_pclk; + u64 clip_pclk; + int rc = 0; + + if (!hdmi->power_on || !display->connected) { + SDE_ERROR("HDMI display is not ready\n"); + return -EINVAL; + } + + /* get current pclk */ + cur_pclk = hdmi->pixclock; + /* get desired pclk */ + dst_pclk = cur_pclk * (1000000000 + ppm); + do_div(dst_pclk, 1000000000); + + clip_pclk = _sde_hdmi_clip_valid_pclk(current_mode, dst_pclk); + + /* update pclk */ + if (clip_pclk != cur_pclk) { + SDE_DEBUG("PCLK changes from %llu to %llu when delta is %d\n", + cur_pclk, clip_pclk, ppm); + + rc = clk_set_rate(hdmi->pwr_clks[0], clip_pclk); + if (rc < 0) { + SDE_ERROR("PLL update failed, reset clock rate\n"); + return rc; + } + + hdmi->pixclock = clip_pclk; + } + + return rc; +} + +static ssize_t _sde_hdmi_debugfs_pll_delta_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct sde_hdmi *display = file->private_data; + char buf[10]; + int ppm = 0; + + if (!display) + return -ENODEV; + + if (count >= sizeof(buf)) + return -EFAULT; + + if (copy_from_user(buf, user_buf, count)) + return -EFAULT; + + buf[count] = 0; /* end of string */ + + if (kstrtoint(buf, 0, &ppm)) + return -EFAULT; + + if (ppm) + _sde_hdmi_update_pll_delta(display, ppm); + + return count; +} + +static const struct file_operations pll_delta_fops = { + .open = simple_open, + .write = _sde_hdmi_debugfs_pll_delta_write, +}; + static int _sde_hdmi_debugfs_init(struct sde_hdmi *display) { int rc = 0; struct dentry *dir, *dump_file, *edid_modes; struct dentry *edid_vsdb_info, *edid_hdr_info, *edid_hfvsdb_info; - struct dentry *edid_vcdb_info, *edid_vendor_name; + struct dentry *edid_vcdb_info, *edid_vendor_name, *pll_file; dir = debugfs_create_dir(display->name, NULL); if (!dir) { @@ -423,7 +522,19 @@ static int _sde_hdmi_debugfs_init(struct sde_hdmi *display) &dump_info_fops); if (IS_ERR_OR_NULL(dump_file)) { rc = PTR_ERR(dump_file); - SDE_ERROR("[%s]debugfs create file failed, rc=%d\n", + SDE_ERROR("[%s]debugfs create dump_info file failed, rc=%d\n", + display->name, rc); + goto error_remove_dir; + } + + pll_file = debugfs_create_file("pll_delta", + 0644, + dir, + display, + &pll_delta_fops); + if (IS_ERR_OR_NULL(pll_file)) { + rc = PTR_ERR(pll_file); + SDE_ERROR("[%s]debugfs create pll_delta file failed, rc=%d\n", display->name, rc); goto error_remove_dir; } @@ -1324,6 +1435,28 @@ int sde_hdmi_get_info(struct msm_display_info *info, return rc; } +int sde_hdmi_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + int property_index, + uint64_t value, + void *display) +{ + int rc = 0; + + if (!connector || !display) { + SDE_ERROR("connector=%pK or display=%pK is NULL\n", + connector, display); + return 0; + } + + SDE_DEBUG("\n"); + + if (property_index == CONNECTOR_PROP_PLL_DELTA) + rc = _sde_hdmi_update_pll_delta(display, value); + + return rc; +} + u32 sde_hdmi_get_num_of_displays(void) { u32 count = 0; diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h index bb3061a6ed00..ecdace10d0c3 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h @@ -80,6 +80,7 @@ struct sde_hdmi_ctrl { * @non_pluggable: If HDMI display is non pluggable * @num_of_modes: Number of modes supported by display if non pluggable. * @mode_list: Mode list if non pluggable. + * @mode: Current display mode. * @connected: If HDMI display is connected. * @is_tpg_enabled: TPG state. * @hpd_work: HPD work structure. @@ -103,6 +104,7 @@ struct sde_hdmi { bool non_pluggable; u32 num_of_modes; struct list_head mode_list; + struct drm_display_mode mode; bool connected; bool is_tpg_enabled; @@ -269,6 +271,22 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display); int sde_hdmi_get_info(struct msm_display_info *info, void *display); +/** + * sde_hdmi_set_property() - set the connector properties + * @connector: Handle to the connector. + * @state: Handle to the connector state. + * @property_index: property index. + * @value: property value. + * @display: Handle to the display. + * + * Return: error code. + */ +int sde_hdmi_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + int property_index, + uint64_t value, + void *display); + /** * sde_hdmi_bridge_init() - init sde hdmi bridge * @hdmi: Handle to the hdmi. @@ -453,5 +471,15 @@ static inline int sde_hdmi_get_info(struct msm_display_info *info, { return 0; } + +static inline int sde_hdmi_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + int property_index, + uint64_t value, + void *display) +{ + return 0; +} + #endif /*#else of CONFIG_DRM_SDE_HDMI*/ #endif /* _SDE_HDMI_H_ */ diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c index 13ea49cfa42d..48a3a9316a41 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c @@ -30,8 +30,6 @@ #define HDMI_AUDIO_INFO_FRAME_PACKET_VERSION 0x1 #define HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH 0x0A -#define HDMI_KHZ_TO_HZ 1000 -#define HDMI_MHZ_TO_HZ 1000000 #define HDMI_ACR_N_MULTIPLIER 128 #define DEFAULT_AUDIO_SAMPLE_RATE_HZ 48000 diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c index c76e42c67885..6c82c3d4826d 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c @@ -568,6 +568,15 @@ static void _sde_hdmi_bridge_set_spd_infoframe(struct hdmi *hdmi, hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, packet_control); } +static inline void _sde_hdmi_save_mode(struct hdmi *hdmi, + struct drm_display_mode *mode) +{ + struct sde_connector *c_conn = to_sde_connector(hdmi->connector); + struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display; + + drm_mode_copy(&display->mode, mode); +} + static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) @@ -640,6 +649,8 @@ static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge, DRM_DEBUG("hdmi setup info frame\n"); } _sde_hdmi_bridge_setup_scrambler(hdmi, mode); + + _sde_hdmi_save_mode(hdmi, mode); } static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = { diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index d2d118cf7e07..ef44beb1a948 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -148,6 +148,7 @@ enum msm_mdp_conn_property { CONNECTOR_PROP_DST_Y, CONNECTOR_PROP_DST_W, CONNECTOR_PROP_DST_H, + CONNECTOR_PROP_PLL_DELTA, /* enum/bitmask properties */ CONNECTOR_PROP_TOPOLOGY_NAME, diff --git a/drivers/gpu/drm/msm/msm_prop.c b/drivers/gpu/drm/msm/msm_prop.c index 5a9e472ea59b..5f3d1b6356aa 100644 --- a/drivers/gpu/drm/msm/msm_prop.c +++ b/drivers/gpu/drm/msm/msm_prop.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -183,6 +183,55 @@ static void _msm_property_install_integer(struct msm_property_info *info, } } +/** + * _msm_property_install_integer - install signed drm range property + * @info: Pointer to property info container struct + * @name: Property name + * @flags: Other property type flags, e.g. DRM_MODE_PROP_IMMUTABLE + * @min: Min property value + * @max: Max property value + * @init: Default Property value + * @property_idx: Property index + * @force_dirty: Whether or not to filter 'dirty' status on unchanged values + */ +static void _msm_property_install_signed_integer(struct msm_property_info *info, + const char *name, int flags, int64_t min, int64_t max, + int64_t init, uint32_t property_idx, bool force_dirty) +{ + struct drm_property **prop; + + if (!info) + return; + + ++info->install_request; + + if (!name || (property_idx >= info->property_count)) { + DRM_ERROR("invalid argument(s), %s\n", name ? name : "null"); + } else { + prop = &info->property_array[property_idx]; + /* + * Properties need to be attached to each drm object that + * uses them, but only need to be created once + */ + if (*prop == 0) { + *prop = drm_property_create_signed_range(info->dev, + flags, name, min, max); + if (*prop == 0) + DRM_ERROR("create %s property failed\n", name); + } + + /* save init value for later */ + info->property_data[property_idx].default_value = I642U64(init); + info->property_data[property_idx].force_dirty = force_dirty; + + /* always attach property, if created */ + if (*prop) { + drm_object_attach_property(info->base, *prop, init); + ++info->install_count; + } + } +} + void msm_property_install_range(struct msm_property_info *info, const char *name, int flags, uint64_t min, uint64_t max, uint64_t init, uint32_t property_idx) @@ -199,6 +248,22 @@ void msm_property_install_volatile_range(struct msm_property_info *info, min, max, init, property_idx, true); } +void msm_property_install_signed_range(struct msm_property_info *info, + const char *name, int flags, int64_t min, int64_t max, + int64_t init, uint32_t property_idx) +{ + _msm_property_install_signed_integer(info, name, flags, + min, max, init, property_idx, false); +} + +void msm_property_install_volatile_signed_range(struct msm_property_info *info, + const char *name, int flags, int64_t min, int64_t max, + int64_t init, uint32_t property_idx) +{ + _msm_property_install_signed_integer(info, name, flags, + min, max, init, property_idx, true); +} + void msm_property_install_rotation(struct msm_property_info *info, unsigned int supported_rotations, uint32_t property_idx) { diff --git a/drivers/gpu/drm/msm/msm_prop.h b/drivers/gpu/drm/msm/msm_prop.h index dbe28bdf5638..1430551700c7 100644 --- a/drivers/gpu/drm/msm/msm_prop.h +++ b/drivers/gpu/drm/msm/msm_prop.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -207,6 +207,45 @@ void msm_property_install_volatile_range(struct msm_property_info *info, uint64_t init, uint32_t property_idx); +/** + * msm_property_install_signed_range - install signed drm range property + * @info: Pointer to property info container struct + * @name: Property name + * @flags: Other property type flags, e.g. DRM_MODE_PROP_IMMUTABLE + * @min: Min property value + * @max: Max property value + * @init: Default Property value + * @property_idx: Property index + */ +void msm_property_install_signed_range(struct msm_property_info *info, + const char *name, + int flags, + int64_t min, + int64_t max, + int64_t init, + uint32_t property_idx); + +/** + * msm_property_install_volatile_signed_range - install signed range property + * This function is similar to msm_property_install_range, but assumes + * that the property is meant for holding user pointers or descriptors + * that may reference volatile data without having an updated value. + * @info: Pointer to property info container struct + * @name: Property name + * @flags: Other property type flags, e.g. DRM_MODE_PROP_IMMUTABLE + * @min: Min property value + * @max: Max property value + * @init: Default Property value + * @property_idx: Property index + */ +void msm_property_install_volatile_signed_range(struct msm_property_info *info, + const char *name, + int flags, + int64_t min, + int64_t max, + int64_t init, + uint32_t property_idx); + /** * msm_property_install_rotation - install standard drm rotation property * @info: Pointer to property info container struct diff --git a/drivers/gpu/drm/msm/sde/sde_connector.c b/drivers/gpu/drm/msm/sde/sde_connector.c index 31cf25ab5691..98eb50a04cb1 100644 --- a/drivers/gpu/drm/msm/sde/sde_connector.c +++ b/drivers/gpu/drm/msm/sde/sde_connector.c @@ -586,6 +586,10 @@ struct drm_connector *sde_connector_init(struct drm_device *dev, msm_property_install_range(&c_conn->property_info, "RETIRE_FENCE", 0x0, 0, INR_OPEN_MAX, 0, CONNECTOR_PROP_RETIRE_FENCE); + msm_property_install_volatile_signed_range(&c_conn->property_info, + "PLL_DELTA", 0x0, INT_MIN, INT_MAX, 0, + CONNECTOR_PROP_PLL_DELTA); + /* enum/bitmask properties */ msm_property_install_enum(&c_conn->property_info, "topology_name", DRM_MODE_PROP_IMMUTABLE, 0, e_topology_name, diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c index 581918da183f..826c1e905c04 100644 --- a/drivers/gpu/drm/msm/sde/sde_kms.c +++ b/drivers/gpu/drm/msm/sde/sde_kms.c @@ -598,6 +598,7 @@ static int _sde_kms_setup_displays(struct drm_device *dev, .get_modes = sde_hdmi_connector_get_modes, .mode_valid = sde_hdmi_mode_valid, .get_info = sde_hdmi_get_info, + .set_property = sde_hdmi_set_property, }; struct msm_display_info info = {0}; struct drm_encoder *encoder; -- cgit v1.2.3