diff --git a/msm/Makefile b/msm/Makefile index 626487f5ce..6c6acffdea 100644 --- a/msm/Makefile +++ b/msm/Makefile @@ -24,6 +24,8 @@ msm_drm-$(CONFIG_DRM_MSM_DP) += dp/dp_usbpd.o \ dp/dp_hdcp2p2.o \ sde_hdcp_1x.o \ sde_hdcp_2x.o \ + dp/dp_pll.o \ + dp/dp_pll_5nm.o \ msm_drm-$(CONFIG_DRM_MSM_DP_MST) += dp/dp_mst_drm.o \ diff --git a/msm/dp/dp_aux.h b/msm/dp/dp_aux.h index 0f41b89a25..e7339fc35b 100644 --- a/msm/dp/dp_aux.h +++ b/msm/dp/dp_aux.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_AUX_H_ @@ -22,6 +22,7 @@ #define DP_STATE_LINK_MAINTENANCE_COMPLETED BIT(10) #define DP_STATE_LINK_MAINTENANCE_FAILED BIT(11) #define DP_STATE_AUX_TIMEOUT BIT(12) +#define DP_STATE_PLL_LOCKED BIT(13) enum dp_aux_error { DP_AUX_ERR_NONE = 0, diff --git a/msm/dp/dp_debug.c b/msm/dp/dp_debug.c index e9d1a1f5e5..90472b5dc3 100644 --- a/msm/dp/dp_debug.c +++ b/msm/dp/dp_debug.c @@ -13,6 +13,7 @@ #include "drm_connector.h" #include "sde_connector.h" #include "dp_display.h" +#include "dp_pll.h" #define DEBUG_NAME "drm_dp" @@ -40,6 +41,7 @@ struct dp_debug_private { struct dp_debug dp_debug; struct dp_parser *parser; struct dp_ctrl *ctrl; + struct dp_pll *pll; struct mutex lock; }; @@ -785,35 +787,6 @@ static ssize_t dp_debug_mst_sideband_mode_write(struct file *file, return count; } -static ssize_t dp_debug_widebus_mode_write(struct file *file, - const char __user *user_buff, size_t count, loff_t *ppos) -{ - struct dp_debug_private *debug = file->private_data; - char buf[SZ_8]; - size_t len = 0; - u32 widebus_mode = 0; - - if (!debug || !debug->parser) - return -ENODEV; - - if (*ppos) - return 0; - - len = min_t(size_t, count, SZ_8 - 1); - if (copy_from_user(buf, user_buff, len)) - return -EFAULT; - - buf[len] = '\0'; - - if (kstrtoint(buf, 10, &widebus_mode) != 0) - return -EINVAL; - - debug->parser->has_widebus = widebus_mode ? true : false; - DP_DEBUG("widebus_enable: %d\n", widebus_mode); - - return len; -} - static ssize_t dp_debug_tpg_write(struct file *file, const char __user *user_buff, size_t count, loff_t *ppos) { @@ -1900,17 +1873,391 @@ static const struct file_operations hdcp_fops = { .read = dp_debug_read_hdcp, }; -static const struct file_operations widebus_mode_fops = { - .open = simple_open, - .write = dp_debug_widebus_mode_write, -}; +static int dp_debug_init_mst(struct dp_debug_private *debug, struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("mst_con_id", 0644, dir, + debug, &mst_con_id_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs create mst_con_id failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("mst_con_info", 0644, dir, + debug, &mst_conn_info_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs create mst_conn_info failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("mst_con_add", 0644, dir, + debug, &mst_con_add_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DRM_ERROR("[%s] debugfs create mst_con_add failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("mst_con_remove", 0644, dir, + debug, &mst_con_remove_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DRM_ERROR("[%s] debugfs create mst_con_remove failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("mst_mode", 0644, dir, + debug, &mst_mode_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs mst_mode failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("mst_sideband_mode", 0644, dir, + debug, &mst_sideband_mode_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs mst_sideband_mode failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_link(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("max_bw_code", 0644, dir, + debug, &bw_code_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs max_bw_code failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("max_pclk_khz", 0644, dir, + debug, &max_pclk_khz_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs max_pclk_khz failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_u32("max_lclk_khz", 0644, dir, + &debug->parser->max_lclk_khz); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs max_lclk_khz failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_u32("lane_count", 0644, dir, + &debug->panel->lane_count); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs lane_count failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_u32("link_bw_code", 0644, dir, + &debug->panel->link_bw_code); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs link_bw_code failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_hdcp(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_bool("hdcp_wait_sink_sync", 0644, dir, + &debug->dp_debug.hdcp_wait_sink_sync); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs hdcp_wait_sink_sync failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_bool("force_encryption", 0644, dir, + &debug->dp_debug.force_encryption); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs force_encryption failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_sink_caps(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("edid_modes", 0644, dir, + debug, &edid_modes_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs create edid_modes failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("edid_modes_mst", 0644, dir, + debug, &edid_modes_mst_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs create edid_modes_mst failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("edid", 0644, dir, + debug, &edid_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs edid failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("dpcd", 0644, dir, + debug, &dpcd_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs dpcd failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_status(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("dp_debug", 0444, dir, + debug, &dp_debug_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs create file failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("connected", 0444, dir, + debug, &connected_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs connected failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("hdr", 0400, dir, debug, &hdr_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs hdr failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("hdr_mst", 0400, dir, debug, &hdr_mst_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs hdr_mst failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("hdcp", 0644, dir, debug, &hdcp_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs hdcp failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_sim(struct dp_debug_private *debug, struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("hpd", 0644, dir, debug, &hpd_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs hpd failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("sim", 0644, dir, debug, &sim_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs sim failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("attention", 0644, dir, + debug, &attention_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs attention failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_bool("skip_uevent", 0644, dir, + &debug->dp_debug.skip_uevent); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs skip_uevent failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_dsc_fec(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_bool("dsc_feature_enable", 0644, dir, + &debug->parser->dsc_feature_enable); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs dsc_feature failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_bool("fec_feature_enable", 0644, dir, + &debug->parser->fec_feature_enable); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs fec_feature_enable failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_tpg(struct dp_debug_private *debug, struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("tpg_ctrl", 0644, dir, + debug, &tpg_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs tpg failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_reg_dump(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_file("exe_mode", 0644, dir, + debug, &exe_mode_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs register failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_file("dump", 0644, dir, + debug, &dump_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs dump failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} + +static int dp_debug_init_feature_toggle(struct dp_debug_private *debug, + struct dentry *dir) +{ + int rc = 0; + struct dentry *file; + + file = debugfs_create_bool("ssc_enable", 0644, dir, + &debug->pll->ssc_en); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs ssc_enable failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + file = debugfs_create_bool("widebus_mode", 0644, dir, + &debug->parser->has_widebus); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DP_ERR("[%s] debugfs widebus_mode failed, rc=%d\n", + DEBUG_NAME, rc); + return rc; + } + + return rc; +} static int dp_debug_init(struct dp_debug *dp_debug) { int rc = 0; struct dp_debug_private *debug = container_of(dp_debug, struct dp_debug_private, dp_debug); - struct dentry *dir, *file; + struct dentry *dir; dir = debugfs_create_dir(DEBUG_NAME, NULL); if (IS_ERR_OR_NULL(dir)) { @@ -1925,272 +2272,49 @@ static int dp_debug_init(struct dp_debug *dp_debug) debug->root = dir; - file = debugfs_create_file("dp_debug", 0444, dir, - debug, &dp_debug_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs create file failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_status(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("edid_modes", 0644, dir, - debug, &edid_modes_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs create edid_modes failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_sink_caps(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("edid_modes_mst", 0644, dir, - debug, &edid_modes_mst_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs create edid_modes_mst failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_mst(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("mst_con_id", 0644, dir, - debug, &mst_con_id_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs create mst_con_id failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_link(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("mst_con_info", 0644, dir, - debug, &mst_conn_info_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs create mst_conn_info failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_hdcp(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("mst_con_add", 0644, dir, - debug, &mst_con_add_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DRM_ERROR("[%s] debugfs create mst_con_add failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_sim(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("mst_con_remove", 0644, dir, - debug, &mst_con_remove_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DRM_ERROR("[%s] debugfs create mst_con_remove failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_dsc_fec(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("hpd", 0644, dir, - debug, &hpd_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs hpd failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_tpg(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("connected", 0444, dir, - debug, &connected_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs connected failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_reg_dump(debug, dir); + if (rc) goto error_remove_dir; - } - file = debugfs_create_file("max_bw_code", 0644, dir, - debug, &bw_code_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs max_bw_code failed, rc=%d\n", - DEBUG_NAME, rc); - } - - file = debugfs_create_file("exe_mode", 0644, dir, - debug, &exe_mode_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs register failed, rc=%d\n", - DEBUG_NAME, rc); - } - - file = debugfs_create_file("edid", 0644, dir, - debug, &edid_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs edid failed, rc=%d\n", - DEBUG_NAME, rc); + rc = dp_debug_init_feature_toggle(debug, dir); + if (rc) goto error_remove_dir; - } - - file = debugfs_create_file("dpcd", 0644, dir, - debug, &dpcd_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs dpcd failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("tpg_ctrl", 0644, dir, - debug, &tpg_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs tpg failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("hdr", 0400, dir, - debug, &hdr_fops); - - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs hdr failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("hdr_mst", 0400, dir, - debug, &hdr_mst_fops); - - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs hdr_mst failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("sim", 0644, dir, - debug, &sim_fops); - - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs sim failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("attention", 0644, dir, - debug, &attention_fops); - - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs attention failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("dump", 0644, dir, - debug, &dump_fops); - - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs dump failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("mst_mode", 0644, dir, - debug, &mst_mode_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs max_bw_code failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("mst_sideband_mode", 0644, dir, - debug, &mst_sideband_mode_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs max_bw_code failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("max_pclk_khz", 0644, dir, - debug, &max_pclk_khz_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs max_pclk_khz failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_bool("force_encryption", 0644, dir, - &debug->dp_debug.force_encryption); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs force_encryption failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_file("hdcp", 0644, dir, - debug, &hdcp_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs hdcp failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_bool("hdcp_wait_sink_sync", 0644, dir, - &debug->dp_debug.hdcp_wait_sink_sync); - - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs hdcp_wait_sink_sync failed, rc=%d\n", - DEBUG_NAME, rc); - goto error_remove_dir; - } - - file = debugfs_create_bool("dsc_feature_enable", 0644, dir, - &debug->parser->dsc_feature_enable); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs dsc_feature failed, rc=%d\n", - DEBUG_NAME, rc); - } - - file = debugfs_create_bool("fec_feature_enable", 0644, dir, - &debug->parser->fec_feature_enable); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs fec_feature_enable failed, rc=%d\n", - DEBUG_NAME, rc); - } - - file = debugfs_create_file("widebus_mode", 0644, dir, - debug, &widebus_mode_fops); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs widebus failed, rc=%d\n", - DEBUG_NAME, rc); - } - - file = debugfs_create_u32("max_lclk_khz", 0644, dir, - &debug->parser->max_lclk_khz); - if (IS_ERR_OR_NULL(file)) { - rc = PTR_ERR(file); - DP_ERR("[%s] debugfs max_lclk_khz failed, rc=%d\n", - DEBUG_NAME, rc); - } return 0; error_remove_dir: - if (!file) - rc = -EINVAL; debugfs_remove_recursive(dir); error: return rc; @@ -2229,7 +2353,7 @@ struct dp_debug *dp_debug_get(struct dp_debug_in *in) struct dp_debug *dp_debug; if (!in->dev || !in->panel || !in->hpd || !in->link || - !in->catalog || !in->ctrl) { + !in->catalog || !in->ctrl || !in->pll) { DP_ERR("invalid input\n"); rc = -EINVAL; goto error; @@ -2251,6 +2375,7 @@ struct dp_debug *dp_debug_get(struct dp_debug_in *in) debug->catalog = in->catalog; debug->parser = in->parser; debug->ctrl = in->ctrl; + debug->pll = in->pll; dp_debug = &debug->dp_debug; dp_debug->vdisplay = 0; diff --git a/msm/dp/dp_debug.h b/msm/dp/dp_debug.h index 931152ad13..e49e018aeb 100644 --- a/msm/dp/dp_debug.h +++ b/msm/dp/dp_debug.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_DEBUG_H_ @@ -12,6 +12,7 @@ #include "dp_usbpd.h" #include "dp_aux.h" #include "dp_display.h" +#include "dp_pll.h" #define DP_WARN(fmt, ...) DRM_WARN("[msm-dp-warn] "fmt, ##__VA_ARGS__) #define DP_ERR(fmt, ...) DRM_DEV_ERROR(NULL, "[msm-dp-error]" fmt, \ @@ -35,6 +36,7 @@ * @tpg_state: specifies whether tpg feature is enabled * @max_pclk_khz: max pclk supported * @force_encryption: enable/disable forced encryption for HDCP 2.2 + * @skip_uevent: skip hotplug uevent to the user space * @hdcp_status: string holding hdcp status information * @dp_mst_connector_list: list containing all dp mst connectors * @mst_hpd_sim: specifies whether simulated hpd enabled @@ -56,6 +58,7 @@ struct dp_debug { bool tpg_state; u32 max_pclk_khz; bool force_encryption; + bool skip_uevent; char hdcp_status[SZ_128]; struct dp_mst_connector dp_mst_connector_list; bool mst_hpd_sim; @@ -78,6 +81,8 @@ struct dp_debug { * @connector: double pointer to display connector * @catalog: instance of catalog module * @parser: instance of parser module + * @ctrl: instance of controller module + * @pll: instance of pll module */ struct dp_debug_in { struct device *dev; @@ -89,6 +94,7 @@ struct dp_debug_in { struct dp_catalog *catalog; struct dp_parser *parser; struct dp_ctrl *ctrl; + struct dp_pll *pll; }; /** diff --git a/msm/dp/dp_display.c b/msm/dp/dp_display.c index e26e03ea28..17ed7d3948 100644 --- a/msm/dp/dp_display.c +++ b/msm/dp/dp_display.c @@ -28,6 +28,7 @@ #include "dp_display.h" #include "sde_hdcp.h" #include "dp_debug.h" +#include "dp_pll.h" #define DP_MST_DEBUG(fmt, ...) DP_DEBUG(fmt, ##__VA_ARGS__) @@ -152,6 +153,7 @@ struct dp_display_private { struct dp_panel *panel; struct dp_ctrl *ctrl; struct dp_debug *debug; + struct dp_pll *pll; struct dp_panel *active_panels[DP_STREAM_MAX]; struct dp_hdcp hdcp; @@ -493,7 +495,8 @@ static void dp_display_deinitialize_hdcp(struct dp_display_private *dp) return; } - sde_dp_hdcp2p2_deinit(dp->hdcp.data); + sde_hdcp_1x_deinit(dp->hdcp.dev[HDCP_VERSION_1X].fd); + sde_dp_hdcp2p2_deinit(dp->hdcp.dev[HDCP_VERSION_2P2].fd); } static int dp_display_initialize_hdcp(struct dp_display_private *dp) @@ -527,19 +530,18 @@ static int dp_display_initialize_hdcp(struct dp_display_private *dp) fd = sde_hdcp_1x_init(&hdcp_init_data); if (IS_ERR_OR_NULL(fd)) { - DP_ERR("Error initializing HDCP 1.x\n"); - rc = -EINVAL; - goto error; + DP_DEBUG("Error initializing HDCP 1.x\n"); + return -EINVAL; } dp->hdcp.dev[HDCP_VERSION_1X].fd = fd; dp->hdcp.dev[HDCP_VERSION_1X].ops = sde_hdcp_1x_get(fd); dp->hdcp.dev[HDCP_VERSION_1X].ver = HDCP_VERSION_1X; - DP_DEBUG("HDCP 1.3 initialized\n"); + DP_INFO("HDCP 1.3 initialized\n"); fd = sde_dp_hdcp2p2_init(&hdcp_init_data); if (IS_ERR_OR_NULL(fd)) { - DP_ERR("Error initializing HDCP 2.x\n"); + DP_DEBUG("Error initializing HDCP 2.x\n"); rc = -EINVAL; goto error; } @@ -547,11 +549,11 @@ static int dp_display_initialize_hdcp(struct dp_display_private *dp) dp->hdcp.dev[HDCP_VERSION_2P2].fd = fd; dp->hdcp.dev[HDCP_VERSION_2P2].ops = sde_dp_hdcp2p2_get(fd); dp->hdcp.dev[HDCP_VERSION_2P2].ver = HDCP_VERSION_2P2; - DP_DEBUG("HDCP 2.2 initialized\n"); + DP_INFO("HDCP 2.2 initialized\n"); return 0; error: - dp_display_deinitialize_hdcp(dp); + sde_hdcp_1x_deinit(dp->hdcp.dev[HDCP_VERSION_1X].fd); return rc; } @@ -661,8 +663,9 @@ static void dp_display_send_hpd_event(struct dp_display_private *dp) envp[2] = bpp; envp[3] = pattern; envp[4] = NULL; - kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, - envp); + if (!dp->debug->skip_uevent) + kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, + envp); if (connector->status == connector_status_connected) { dp_display_state_add(DP_STATE_CONNECT_NOTIFIED); @@ -1346,17 +1349,18 @@ static int dp_display_get_usb_extcon(struct dp_display_private *dp) static void dp_display_deinit_sub_modules(struct dp_display_private *dp) { + dp_debug_put(dp->debug); + dp_hpd_put(dp->hpd); dp_audio_put(dp->panel->audio); dp_ctrl_put(dp->ctrl); - dp_link_put(dp->link); dp_panel_put(dp->panel); - dp_aux_put(dp->aux); + dp_link_put(dp->link); dp_power_put(dp->power); + dp_pll_put(dp->pll); + dp_aux_put(dp->aux); dp_catalog_put(dp->catalog); dp_parser_put(dp->parser); - dp_hpd_put(dp->hpd); mutex_destroy(&dp->session_lock); - dp_debug_put(dp->debug); } static int dp_init_sub_modules(struct dp_display_private *dp) @@ -1374,6 +1378,9 @@ static int dp_init_sub_modules(struct dp_display_private *dp) struct dp_debug_in debug_in = { .dev = dev, }; + struct dp_pll_in pll_in = { + .pdev = dp->pdev, + }; mutex_init(&dp->session_lock); @@ -1401,21 +1408,6 @@ static int dp_init_sub_modules(struct dp_display_private *dp) goto error_catalog; } - dp->power = dp_power_get(dp->parser); - if (IS_ERR(dp->power)) { - rc = PTR_ERR(dp->power); - DP_ERR("failed to initialize power, rc = %d\n", rc); - dp->power = NULL; - goto error_power; - } - - rc = dp->power->power_client_init(dp->power, &dp->priv->phandle, - dp->dp_display.drm_dev); - if (rc) { - DP_ERR("Power client create failed\n"); - goto error_aux; - } - dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser, dp->aux_switch_node); if (IS_ERR(dp->aux)) { @@ -1428,6 +1420,32 @@ static int dp_init_sub_modules(struct dp_display_private *dp) rc = dp->aux->drm_aux_register(dp->aux); if (rc) { DP_ERR("DRM DP AUX register failed\n"); + goto error_pll; + } + + pll_in.aux = dp->aux; + pll_in.parser = dp->parser; + + dp->pll = dp_pll_get(&pll_in); + if (IS_ERR(dp->pll)) { + rc = PTR_ERR(dp->pll); + DP_ERR("failed to initialize pll, rc = %d\n", rc); + dp->pll = NULL; + goto error_pll; + } + + dp->power = dp_power_get(dp->parser, dp->pll); + if (IS_ERR(dp->power)) { + rc = PTR_ERR(dp->power); + DP_ERR("failed to initialize power, rc = %d\n", rc); + dp->power = NULL; + goto error_power; + } + + rc = dp->power->power_client_init(dp->power, &dp->priv->phandle, + dp->dp_display.drm_dev); + if (rc) { + DP_ERR("Power client create failed\n"); goto error_link; } @@ -1503,6 +1521,7 @@ static int dp_init_sub_modules(struct dp_display_private *dp) debug_in.catalog = dp->catalog; debug_in.parser = dp->parser; debug_in.ctrl = dp->ctrl; + debug_in.pll = dp->pll; dp->debug = dp_debug_get(&debug_in); if (IS_ERR(dp->debug)) { @@ -1540,10 +1559,12 @@ error_ctrl: error_panel: dp_link_put(dp->link); error_link: - dp_aux_put(dp->aux); -error_aux: dp_power_put(dp->power); error_power: + dp_pll_put(dp->pll); +error_pll: + dp_aux_put(dp->aux); +error_aux: dp_catalog_put(dp->catalog); error_catalog: dp_parser_put(dp->parser); diff --git a/msm/dp/dp_panel.c b/msm/dp/dp_panel.c index 3f4fe47f58..b9ee5cb9a3 100644 --- a/msm/dp/dp_panel.c +++ b/msm/dp/dp_panel.c @@ -1528,6 +1528,18 @@ skip_dpcd_read: link_info->num_lanes = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK; + if (is_link_rate_valid(panel->dp_panel.link_bw_code)) { + DP_DEBUG("debug link bandwidth code: 0x%x\n", + panel->dp_panel.link_bw_code); + link_info->rate = drm_dp_bw_code_to_link_rate( + panel->dp_panel.link_bw_code); + } + + if (is_lane_count_valid(panel->dp_panel.lane_count)) { + DP_DEBUG("debug lane count: %d\n", panel->dp_panel.lane_count); + link_info->num_lanes = panel->dp_panel.lane_count; + } + if (multi_func) link_info->num_lanes = min_t(unsigned int, link_info->num_lanes, 2); @@ -2272,11 +2284,6 @@ static int dp_panel_deinit_panel_info(struct dp_panel *dp_panel, u32 flags) struct drm_connector *connector; struct sde_connector_state *c_state; - if (!dp_panel) { - DP_ERR("invalid input\n"); - return -EINVAL; - } - if (flags & DP_PANEL_SRC_INITIATED_POWER_DOWN) { DP_DEBUG("retain states in src initiated power down request\n"); return 0; @@ -2318,6 +2325,9 @@ static int dp_panel_deinit_panel_info(struct dp_panel *dp_panel, u32 flags) memset(&c_state->hdr_meta, 0, sizeof(c_state->hdr_meta)); memset(&c_state->dyn_hdr_meta, 0, sizeof(c_state->dyn_hdr_meta)); + dp_panel->link_bw_code = 0; + dp_panel->lane_count = 0; + return rc; } @@ -3001,6 +3011,8 @@ struct dp_panel *dp_panel_get(struct dp_panel_in *in) dp_panel = &panel->dp_panel; dp_panel->max_bw_code = DP_LINK_BW_8_1; dp_panel->spd_enabled = true; + dp_panel->link_bw_code = 0; + dp_panel->lane_count = 0; memcpy(panel->spd_vendor_name, vendor_name, (sizeof(u8) * 8)); memcpy(panel->spd_product_description, product_desc, (sizeof(u8) * 16)); dp_panel->connector = in->connector; diff --git a/msm/dp/dp_panel.h b/msm/dp/dp_panel.h index f211639b0a..fc10758c30 100644 --- a/msm/dp/dp_panel.h +++ b/msm/dp/dp_panel.h @@ -108,6 +108,8 @@ struct dp_panel { /* debug */ u32 max_bw_code; + u32 lane_count; + u32 link_bw_code; /* By default, stream_id is assigned to DP_INVALID_STREAM. * Client sets the stream id value using set_stream_id interface. diff --git a/msm/dp/dp_parser.c b/msm/dp/dp_parser.c index 2d63863613..21ac2562b9 100644 --- a/msm/dp/dp_parser.c +++ b/msm/dp/dp_parser.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #include @@ -297,6 +297,7 @@ static const char *dp_parser_supply_node_name(enum dp_pm_type module) case DP_CORE_PM: return "qcom,core-supply-entries"; case DP_CTRL_PM: return "qcom,ctrl-supply-entries"; case DP_PHY_PM: return "qcom,phy-supply-entries"; + case DP_PLL_PM: return "qcom,pll-supply-entries"; default: return "???"; } } diff --git a/msm/dp/dp_parser.h b/msm/dp/dp_parser.h index 9caa1a7ddf..c5d0b186a5 100644 --- a/msm/dp/dp_parser.h +++ b/msm/dp/dp_parser.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_PARSER_H_ @@ -21,6 +21,7 @@ enum dp_pm_type { DP_STREAM0_PM, DP_STREAM1_PM, DP_LINK_PM, + DP_PLL_PM, DP_MAX_PM }; @@ -33,6 +34,7 @@ static inline const char *dp_parser_pm_name(enum dp_pm_type module) case DP_STREAM0_PM: return "DP_STREAM0_PM"; case DP_STREAM1_PM: return "DP_STREAM1_PM"; case DP_LINK_PM: return "DP_LINK_PM"; + case DP_PLL_PM: return "DP_PLL_PM"; default: return "???"; } } diff --git a/msm/dp/dp_pll.c b/msm/dp/dp_pll.c new file mode 100644 index 0000000000..31f91c84ce --- /dev/null +++ b/msm/dp/dp_pll.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include "dp_debug.h" +#include "dp_pll.h" + +static int dp_pll_fill_io(struct dp_pll *pll) +{ + struct dp_parser *parser = pll->parser; + + pll->io.dp_phy = parser->get_io(parser, "dp_phy"); + if (!pll->io.dp_phy) { + DP_ERR("Invalid dp_phy resource\n"); + return -ENOMEM; + } + + pll->io.dp_pll = parser->get_io(parser, "dp_pll"); + if (!pll->io.dp_pll) { + DP_ERR("Invalid dp_pll resource\n"); + return -ENOMEM; + } + + pll->io.dp_ln_tx0 = parser->get_io(parser, "dp_ln_tx0"); + if (!pll->io.dp_ln_tx0) { + DP_ERR("Invalid dp_ln_tx1 resource\n"); + return -ENOMEM; + } + + pll->io.dp_ln_tx1 = parser->get_io(parser, "dp_ln_tx1"); + if (!pll->io.dp_ln_tx1) { + DP_ERR("Invalid dp_ln_tx1 resource\n"); + return -ENOMEM; + } + + pll->io.gdsc = parser->get_io(parser, "gdsc"); + if (!pll->io.gdsc) { + DP_ERR("Invalid gdsc resource\n"); + return -ENOMEM; + } + + return 0; +} + +static int dp_pll_clock_register(struct dp_pll *pll) +{ + int rc; + + switch (pll->revision) { + case DP_PLL_5NM_V1: + case DP_PLL_5NM_V2: + rc = dp_pll_clock_register_5nm(pll); + break; + default: + rc = -ENOTSUPP; + break; + } + + return rc; +} + +static void dp_pll_clock_unregister(struct dp_pll *pll) +{ + switch (pll->revision) { + case DP_PLL_5NM_V1: + case DP_PLL_5NM_V2: + dp_pll_clock_unregister_5nm(pll); + break; + default: + break; + } +} + +struct dp_pll *dp_pll_get(struct dp_pll_in *in) +{ + int rc = 0; + struct dp_pll *pll; + struct dp_parser *parser; + const char *label = NULL; + struct platform_device *pdev; + + if (!in || !in->pdev || !in->pdev->dev.of_node || !in->parser) { + DP_ERR("Invalid resource pointers\n"); + return ERR_PTR(-EINVAL); + } + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + pll->pdev = in->pdev; + pll->parser = in->parser; + pll->aux = in->aux; + parser = pll->parser; + pdev = pll->pdev; + + label = of_get_property(pdev->dev.of_node, "qcom,pll-revision", NULL); + if (label) { + if (!strcmp(label, "5nm-v1")) { + pll->revision = DP_PLL_5NM_V1; + } else if (!strcmp(label, "5nm-v2")) { + pll->revision = DP_PLL_5NM_V2; + } else { + DP_ERR("Unsupported pll revision\n"); + rc = -ENOTSUPP; + goto error; + } + } else { + DP_ERR("pll revision not specified\n"); + rc = -EINVAL; + goto error; + } + + pll->ssc_en = of_property_read_bool(pdev->dev.of_node, + "qcom,ssc-feature-enable"); + pll->bonding_en = of_property_read_bool(pdev->dev.of_node, + "qcom,bonding-feature-enable"); + + rc = dp_pll_fill_io(pll); + if (rc) + goto error; + + rc = dp_pll_clock_register(pll); + if (rc) + goto error; + + DP_INFO("revision=%s, ssc_en=%d, bonding_en=%d\n", + dp_pll_get_revision(pll->revision), pll->ssc_en, + pll->bonding_en); + + return pll; +error: + kfree(pll); + return ERR_PTR(rc); +} + +void dp_pll_put(struct dp_pll *pll) +{ + dp_pll_clock_unregister(pll); + kfree(pll); +} diff --git a/msm/dp/dp_pll.h b/msm/dp/dp_pll.h new file mode 100644 index 0000000000..87f7619066 --- /dev/null +++ b/msm/dp/dp_pll.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __DP_PLL_H +#define __DP_PLL_H + +#include +#include +#include +#include "dp_parser.h" +#include "sde_dbg.h" + +#define DP_VCO_HSCLK_RATE_1620MHZDIV1000 1620000UL +#define DP_VCO_HSCLK_RATE_2700MHZDIV1000 2700000UL +#define DP_VCO_HSCLK_RATE_5400MHZDIV1000 5400000UL +#define DP_VCO_HSCLK_RATE_8100MHZDIV1000 8100000UL + +#define dp_pll_get_base(x) pll->io.x->io.base + +#define dp_pll_read(x, offset) ({ \ + readl_relaxed((dp_pll_get_base(x)) + (offset)); \ +}) + +#define dp_pll_write(x, offset, data) ({ \ + DP_DEBUG(#offset", addr=0x%x, val=0x%x\n", \ + (dp_pll_get_base(x)) + (offset), (data)); \ + SDE_EVT32_VERBOSE((dp_pll_get_base(x)) + (offset), (data)); \ + writel_relaxed((data), (dp_pll_get_base(x)) + (offset)); \ +}) + +enum dp_pll_revision { + DP_PLL_UNKNOWN, + DP_PLL_5NM_V1, + DP_PLL_5NM_V2, +}; + +static inline const char *dp_pll_get_revision(enum dp_pll_revision rev) +{ + switch (rev) { + case DP_PLL_UNKNOWN: return "DP_PLL_UNKNOWN"; + case DP_PLL_5NM_V1: return "DP_PLL_5NM_V1"; + case DP_PLL_5NM_V2: return "DP_PLL_5NM_V2"; + default: return "???"; + } +} + +struct dp_pll_io { + struct dp_io_data *dp_phy; + struct dp_io_data *dp_pll; + struct dp_io_data *dp_ln_tx0; + struct dp_io_data *dp_ln_tx1; + struct dp_io_data *gdsc; +}; + +struct dp_pll_vco_clk { + struct clk_hw hw; + unsigned long rate; /* current vco rate */ + u64 min_rate; /* min vco rate */ + u64 max_rate; /* max vco rate */ + void *priv; +}; + +struct dp_pll { + /* + * target pll revision information + */ + u32 revision; + + /* + * Certain plls needs to update the same vco rate after resume in + * suspend/resume scenario. Cached the vco rate for such plls. + */ + unsigned long vco_cached_rate; + + /* + * PLL index if multiple index are available. Eg. in case of + * DSI we have 2 plls. + */ + uint32_t index; + + bool ssc_en; + bool bonding_en; + + void *priv; + struct platform_device *pdev; + struct dp_parser *parser; + struct dp_power *power; + struct dp_aux *aux; + struct dp_pll_io io; + struct clk_onecell_data *clk_data; +}; + +struct dp_pll_db { + struct dp_pll *pll; + + /* lane and orientation settings */ + u8 lane_cnt; + u8 orientation; + + /* COM PHY settings */ + u32 hsclk_sel; + u32 dec_start_mode0; + u32 div_frac_start1_mode0; + u32 div_frac_start2_mode0; + u32 div_frac_start3_mode0; + u32 integloop_gain0_mode0; + u32 integloop_gain1_mode0; + u32 lock_cmp1_mode0; + u32 lock_cmp2_mode0; + u32 lock_cmp_en; + u32 ssc_step_size1_mode0; + u32 ssc_step_size2_mode0; + + /* PHY vco divider */ + u32 phy_vco_div; +}; + + +static inline struct dp_pll_vco_clk *to_dp_vco_hw(struct clk_hw *hw) +{ + return container_of(hw, struct dp_pll_vco_clk, hw); +} + +static inline bool is_gdsc_disabled(struct dp_pll *pll) +{ + return (dp_pll_read(gdsc, 0x0) & BIT(31)) ? false : true; +} + +int dp_pll_clock_register_5nm(struct dp_pll *pll); +void dp_pll_clock_unregister_5nm(struct dp_pll *pll); + +struct dp_pll_in { + struct platform_device *pdev; + struct dp_aux *aux; + struct dp_parser *parser; +}; + +struct dp_pll *dp_pll_get(struct dp_pll_in *in); +void dp_pll_put(struct dp_pll *pll); +#endif /* __DP_PLL_H */ diff --git a/msm/dp/dp_pll_5nm.c b/msm/dp/dp_pll_5nm.c new file mode 100644 index 0000000000..84792f5730 --- /dev/null +++ b/msm/dp/dp_pll_5nm.c @@ -0,0 +1,1038 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +/* + * Display Port PLL driver block diagram for branch clocks + * + * +------------------------------+ + * | DP_VCO_CLK | + * | | + * | +-------------------+ | + * | | (DP PLL/VCO) | | + * | +---------+---------+ | + * | v | + * | +----------+-----------+ | + * | | hsclk_divsel_clk_src | | + * | +----------+-----------+ | + * +------------------------------+ + * | + * +------------<---------v------------>----------+ + * | | + * +-----v------------+ | + * | dp_link_clk_src | | + * | divsel_ten | | + * +---------+--------+ | + * | | + * | | + * v v + * Input to DISPCC block | + * for link clk, crypto clk | + * and interface clock | + * | + * | + * +--------<------------+-----------------+---<---+ + * | | | + * +-------v------+ +--------v-----+ +--------v------+ + * | vco_divided | | vco_divided | | vco_divided | + * | _clk_src | | _clk_src | | _clk_src | + * | | | | | | + * |divsel_six | | divsel_two | | divsel_four | + * +-------+------+ +-----+--------+ +--------+------+ + * | | | + * v------->----------v-------------<------v + * | + * +----------+---------+ + * | vco_divided_clk | + * | _src_mux | + * +---------+----------+ + * | + * v + * Input to DISPCC block + * for DP pixel clock + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "clk-regmap-mux.h" +#include "dp_hpd.h" +#include "dp_debug.h" +#include "dp_pll.h" + +#define DP_PHY_CFG 0x0010 +#define DP_PHY_CFG_1 0x0014 +#define DP_PHY_PD_CTL 0x0018 +#define DP_PHY_MODE 0x001C + +#define DP_PHY_AUX_CFG1 0x0024 +#define DP_PHY_AUX_CFG2 0x0028 + +#define DP_PHY_VCO_DIV 0x0070 +#define DP_PHY_TX0_TX1_LANE_CTL 0x0078 +#define DP_PHY_TX2_TX3_LANE_CTL 0x009C + +#define DP_PHY_SPARE0 0x00C8 +#define DP_PHY_STATUS 0x00DC + +/* Tx registers */ +#define TXn_CLKBUF_ENABLE 0x0008 +#define TXn_TX_EMP_POST1_LVL 0x000C + +#define TXn_TX_DRV_LVL 0x0014 + +#define TXn_RESET_TSYNC_EN 0x001C +#define TXn_PRE_STALL_LDO_BOOST_EN 0x0020 +#define TXn_TX_BAND 0x0024 +#define TXn_INTERFACE_SELECT 0x002C + +#define TXn_RES_CODE_LANE_OFFSET_TX 0x003C +#define TXn_RES_CODE_LANE_OFFSET_RX 0x0040 + +#define TXn_TRANSCEIVER_BIAS_EN 0x0054 +#define TXn_HIGHZ_DRVR_EN 0x0058 +#define TXn_TX_POL_INV 0x005C +#define TXn_PARRATE_REC_DETECT_IDLE_EN 0x0060 + +/* PLL register offset */ +#define QSERDES_COM_BG_TIMER 0x000C +#define QSERDES_COM_SSC_EN_CENTER 0x0010 +#define QSERDES_COM_SSC_ADJ_PER1 0x0014 +#define QSERDES_COM_SSC_PER1 0x001C +#define QSERDES_COM_SSC_PER2 0x0020 +#define QSERDES_COM_SSC_STEP_SIZE1_MODE0 0x0024 +#define QSERDES_COM_SSC_STEP_SIZE2_MODE0 0X0028 +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x0044 +#define QSERDES_COM_CLK_ENABLE1 0x0048 +#define QSERDES_COM_SYS_CLK_CTRL 0x004C +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x0050 +#define QSERDES_COM_PLL_IVCO 0x0058 + +#define QSERDES_COM_CP_CTRL_MODE0 0x0074 +#define QSERDES_COM_PLL_RCTRL_MODE0 0x007C +#define QSERDES_COM_PLL_CCTRL_MODE0 0x0084 +#define QSERDES_COM_SYSCLK_EN_SEL 0x0094 +#define QSERDES_COM_RESETSM_CNTRL 0x009C +#define QSERDES_COM_LOCK_CMP_EN 0x00A4 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x00AC +#define QSERDES_COM_LOCK_CMP2_MODE0 0x00B0 + +#define QSERDES_COM_DEC_START_MODE0 0x00BC +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x00CC +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x00D0 +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x00D4 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x00EC +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x00F0 +#define QSERDES_COM_VCO_TUNE_CTRL 0x0108 +#define QSERDES_COM_VCO_TUNE_MAP 0x010C + +#define QSERDES_COM_CMN_STATUS 0x0140 +#define QSERDES_COM_CLK_SEL 0x0154 +#define QSERDES_COM_HSCLK_SEL 0x0158 + +#define QSERDES_COM_CORECLK_DIV_MODE0 0x0168 + +#define QSERDES_COM_CORE_CLK_EN 0x0174 +#define QSERDES_COM_C_READY_STATUS 0x0178 +#define QSERDES_COM_CMN_CONFIG 0x017C + +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x0184 + +/* Tx tran offsets */ +#define DP_TRAN_DRVR_EMP_EN 0x00C0 +#define DP_TX_INTERFACE_MODE 0x00C4 + +/* Tx VMODE offsets */ +#define DP_VMODE_CTRL1 0x00C8 + +#define DP_PHY_PLL_POLL_SLEEP_US 500 +#define DP_PHY_PLL_POLL_TIMEOUT_US 10000 + +#define DP_VCO_RATE_8100MHZDIV1000 8100000UL +#define DP_VCO_RATE_9720MHZDIV1000 9720000UL +#define DP_VCO_RATE_10800MHZDIV1000 10800000UL + +#define DP_5NM_C_READY BIT(0) +#define DP_5NM_FREQ_DONE BIT(0) +#define DP_5NM_PLL_LOCKED BIT(1) +#define DP_5NM_PHY_READY BIT(1) +#define DP_5NM_TSYNC_DONE BIT(0) + +static int dp_vco_pll_init_db_5nm(struct dp_pll_db *pdb, + unsigned long rate) +{ + struct dp_pll *pll = pdb->pll; + u32 spare_value = 0; + + spare_value = dp_pll_read(dp_phy, DP_PHY_SPARE0); + pdb->lane_cnt = spare_value & 0x0F; + pdb->orientation = (spare_value & 0xF0) >> 4; + + DP_DEBUG("spare_value=0x%x, ln_cnt=0x%x, orientation=0x%x\n", + spare_value, pdb->lane_cnt, pdb->orientation); + + pdb->div_frac_start1_mode0 = 0x00; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + + switch (rate) { + case DP_VCO_HSCLK_RATE_1620MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_9720MHZDIV1000); + pdb->hsclk_sel = 0x05; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->lock_cmp1_mode0 = 0x6f; + pdb->lock_cmp2_mode0 = 0x08; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0x45; + pdb->ssc_step_size2_mode0 = 0x06; + break; + case DP_VCO_HSCLK_RATE_2700MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x03; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->lock_cmp1_mode0 = 0x0f; + pdb->lock_cmp2_mode0 = 0x0e; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x08; + pdb->ssc_step_size1_mode0 = 0x45; + pdb->ssc_step_size2_mode0 = 0x06; + break; + case DP_VCO_HSCLK_RATE_5400MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x01; + pdb->dec_start_mode0 = 0x8c; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x0a; + pdb->lock_cmp1_mode0 = 0x1f; + pdb->lock_cmp2_mode0 = 0x1c; + pdb->phy_vco_div = 0x2; + pdb->lock_cmp_en = 0x08; + pdb->ssc_step_size1_mode0 = 0x5c; + pdb->ssc_step_size2_mode0 = 0x08; + break; + case DP_VCO_HSCLK_RATE_8100MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_8100MHZDIV1000); + pdb->hsclk_sel = 0x00; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->lock_cmp1_mode0 = 0x2f; + pdb->lock_cmp2_mode0 = 0x2a; + pdb->phy_vco_div = 0x0; + pdb->lock_cmp_en = 0x08; + pdb->ssc_step_size1_mode0 = 0x45; + pdb->ssc_step_size2_mode0 = 0x06; + break; + default: + DP_ERR("unsupported rate %ld\n", rate); + return -EINVAL; + } + return 0; +} + +static int dp_config_vco_rate_5nm(struct dp_pll_vco_clk *vco, + unsigned long rate) +{ + int res = 0; + struct dp_pll *pll = vco->priv; + struct dp_pll_db *pdb = (struct dp_pll_db *)pll->priv; + + res = dp_vco_pll_init_db_5nm(pdb, rate); + if (res) { + DP_ERR("VCO Init DB failed\n"); + return res; + } + + dp_pll_write(dp_phy, DP_PHY_CFG_1, 0x0F); + + if (pdb->lane_cnt != 4) { + if (pdb->orientation == ORIENTATION_CC2) + dp_pll_write(dp_phy, DP_PHY_PD_CTL, 0x6d); + else + dp_pll_write(dp_phy, DP_PHY_PD_CTL, 0x75); + } else { + dp_pll_write(dp_phy, DP_PHY_PD_CTL, 0x7d); + } + + /* Make sure the PHY register writes are done */ + wmb(); + + dp_pll_write(dp_pll, QSERDES_COM_SVS_MODE_CLK_SEL, 0x05); + dp_pll_write(dp_pll, QSERDES_COM_SYSCLK_EN_SEL, 0x3b); + dp_pll_write(dp_pll, QSERDES_COM_SYS_CLK_CTRL, 0x02); + dp_pll_write(dp_pll, QSERDES_COM_CLK_ENABLE1, 0x0c); + dp_pll_write(dp_pll, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x06); + dp_pll_write(dp_pll, QSERDES_COM_CLK_SEL, 0x30); + /* Make sure the PHY register writes are done */ + wmb(); + + /* PLL Optimization */ + dp_pll_write(dp_pll, QSERDES_COM_PLL_IVCO, 0x0f); + dp_pll_write(dp_pll, QSERDES_COM_PLL_CCTRL_MODE0, 0x36); + dp_pll_write(dp_pll, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); + dp_pll_write(dp_pll, QSERDES_COM_CP_CTRL_MODE0, 0x06); + /* Make sure the PLL register writes are done */ + wmb(); + + /* link rate dependent params */ + dp_pll_write(dp_pll, QSERDES_COM_HSCLK_SEL, pdb->hsclk_sel); + dp_pll_write(dp_pll, QSERDES_COM_DEC_START_MODE0, pdb->dec_start_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_DIV_FRAC_START1_MODE0, pdb->div_frac_start1_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_DIV_FRAC_START2_MODE0, pdb->div_frac_start2_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_DIV_FRAC_START3_MODE0, pdb->div_frac_start3_mode0); + dp_pll_write(dp_pll, QSERDES_COM_LOCK_CMP1_MODE0, pdb->lock_cmp1_mode0); + dp_pll_write(dp_pll, QSERDES_COM_LOCK_CMP2_MODE0, pdb->lock_cmp2_mode0); + dp_pll_write(dp_pll, QSERDES_COM_LOCK_CMP_EN, pdb->lock_cmp_en); + dp_pll_write(dp_phy, DP_PHY_VCO_DIV, pdb->phy_vco_div); + /* Make sure the PLL register writes are done */ + wmb(); + + dp_pll_write(dp_pll, QSERDES_COM_CMN_CONFIG, 0x02); + dp_pll_write(dp_pll, QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x3f); + dp_pll_write(dp_pll, QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00); + dp_pll_write(dp_pll, QSERDES_COM_VCO_TUNE_MAP, 0x00); + /* Make sure the PHY register writes are done */ + wmb(); + + dp_pll_write(dp_pll, QSERDES_COM_BG_TIMER, 0x0a); + dp_pll_write(dp_pll, QSERDES_COM_CORECLK_DIV_MODE0, 0x0a); + dp_pll_write(dp_pll, QSERDES_COM_VCO_TUNE_CTRL, 0x00); + if (pll->bonding_en) + dp_pll_write(dp_pll, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1f); + else + dp_pll_write(dp_pll, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x17); + dp_pll_write(dp_pll, QSERDES_COM_CORE_CLK_EN, 0x1f); + /* Make sure the PHY register writes are done */ + wmb(); + + if (pll->ssc_en) { + dp_pll_write(dp_pll, QSERDES_COM_SSC_EN_CENTER, 0x01); + dp_pll_write(dp_pll, QSERDES_COM_SSC_ADJ_PER1, 0x00); + dp_pll_write(dp_pll, QSERDES_COM_SSC_PER1, 0x36); + dp_pll_write(dp_pll, QSERDES_COM_SSC_PER2, 0x01); + dp_pll_write(dp_pll, QSERDES_COM_SSC_STEP_SIZE1_MODE0, + pdb->ssc_step_size1_mode0); + dp_pll_write(dp_pll, QSERDES_COM_SSC_STEP_SIZE2_MODE0, + pdb->ssc_step_size2_mode0); + } + + if (pdb->orientation == ORIENTATION_CC2) + dp_pll_write(dp_phy, DP_PHY_MODE, 0x4c); + else + dp_pll_write(dp_phy, DP_PHY_MODE, 0x5c); + + dp_pll_write(dp_phy, DP_PHY_AUX_CFG1, 0x13); + dp_pll_write(dp_phy, DP_PHY_AUX_CFG2, 0xA4); + /* Make sure the PLL register writes are done */ + wmb(); + + /* TX-0 register configuration */ + dp_pll_write(dp_phy, DP_PHY_TX0_TX1_LANE_CTL, 0x05); + dp_pll_write(dp_ln_tx0, DP_VMODE_CTRL1, 0x40); + dp_pll_write(dp_ln_tx0, TXn_PRE_STALL_LDO_BOOST_EN, 0x30); + dp_pll_write(dp_ln_tx0, TXn_INTERFACE_SELECT, 0x3b); + dp_pll_write(dp_ln_tx0, TXn_CLKBUF_ENABLE, 0x0f); + dp_pll_write(dp_ln_tx0, TXn_RESET_TSYNC_EN, 0x03); + dp_pll_write(dp_ln_tx0, DP_TRAN_DRVR_EMP_EN, 0xf); + dp_pll_write(dp_ln_tx0, TXn_PARRATE_REC_DETECT_IDLE_EN, 0x00); + dp_pll_write(dp_ln_tx0, DP_TX_INTERFACE_MODE, 0x00); + dp_pll_write(dp_ln_tx0, TXn_RES_CODE_LANE_OFFSET_TX, 0x11); + dp_pll_write(dp_ln_tx0, TXn_RES_CODE_LANE_OFFSET_RX, 0x11); + dp_pll_write(dp_ln_tx0, TXn_TX_BAND, 0x04); + /* Make sure the PLL register writes are done */ + wmb(); + + /* TX-1 register configuration */ + dp_pll_write(dp_phy, DP_PHY_TX2_TX3_LANE_CTL, 0x05); + dp_pll_write(dp_ln_tx1, DP_VMODE_CTRL1, 0x40); + dp_pll_write(dp_ln_tx1, TXn_PRE_STALL_LDO_BOOST_EN, 0x30); + dp_pll_write(dp_ln_tx1, TXn_INTERFACE_SELECT, 0x3b); + dp_pll_write(dp_ln_tx1, TXn_CLKBUF_ENABLE, 0x0f); + dp_pll_write(dp_ln_tx1, TXn_RESET_TSYNC_EN, 0x03); + dp_pll_write(dp_ln_tx1, DP_TRAN_DRVR_EMP_EN, 0xf); + dp_pll_write(dp_ln_tx1, TXn_PARRATE_REC_DETECT_IDLE_EN, 0x00); + dp_pll_write(dp_ln_tx1, DP_TX_INTERFACE_MODE, 0x00); + dp_pll_write(dp_ln_tx1, TXn_RES_CODE_LANE_OFFSET_TX, 0x11); + dp_pll_write(dp_ln_tx1, TXn_RES_CODE_LANE_OFFSET_RX, 0x11); + dp_pll_write(dp_ln_tx1, TXn_TX_BAND, 0x04); + /* Make sure the PHY register writes are done */ + wmb(); + + return res; +} + +enum dp_5nm_pll_status { + C_READY, + FREQ_DONE, + PLL_LOCKED, + PHY_READY, + TSYNC_DONE, +}; + +char *dp_5nm_pll_get_status_name(enum dp_5nm_pll_status status) +{ + switch (status) { + case C_READY: + return "C_READY"; + case FREQ_DONE: + return "FREQ_DONE"; + case PLL_LOCKED: + return "PLL_LOCKED"; + case PHY_READY: + return "PHY_READY"; + case TSYNC_DONE: + return "TSYNC_DONE"; + default: + return "unknown"; + } +} + +static bool dp_5nm_pll_get_status(struct dp_pll *pll, + enum dp_5nm_pll_status status) +{ + u32 reg, state, bit; + void __iomem *base; + bool success = true; + + switch (status) { + case C_READY: + base = dp_pll_get_base(dp_pll); + reg = QSERDES_COM_C_READY_STATUS; + bit = DP_5NM_C_READY; + break; + case FREQ_DONE: + base = dp_pll_get_base(dp_pll); + reg = QSERDES_COM_CMN_STATUS; + bit = DP_5NM_FREQ_DONE; + break; + case PLL_LOCKED: + base = dp_pll_get_base(dp_pll); + reg = QSERDES_COM_CMN_STATUS; + bit = DP_5NM_PLL_LOCKED; + break; + case PHY_READY: + base = dp_pll_get_base(dp_phy); + reg = DP_PHY_STATUS; + bit = DP_5NM_PHY_READY; + break; + case TSYNC_DONE: + base = dp_pll_get_base(dp_phy); + reg = DP_PHY_STATUS; + bit = DP_5NM_TSYNC_DONE; + break; + default: + return false; + } + + if (readl_poll_timeout_atomic((base + reg), state, + ((state & bit) > 0), + DP_PHY_PLL_POLL_SLEEP_US, + DP_PHY_PLL_POLL_TIMEOUT_US)) { + DP_ERR("%s failed, status=%x\n", + dp_5nm_pll_get_status_name(status), state); + + success = false; + } + + return success; +} + +static int dp_pll_enable_5nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct dp_pll *pll = vco->priv; + + pll->aux->state &= ~DP_STATE_PLL_LOCKED; + + dp_pll_write(dp_phy, DP_PHY_CFG, 0x01); + dp_pll_write(dp_phy, DP_PHY_CFG, 0x05); + dp_pll_write(dp_phy, DP_PHY_CFG, 0x01); + dp_pll_write(dp_phy, DP_PHY_CFG, 0x09); + dp_pll_write(dp_pll, QSERDES_COM_RESETSM_CNTRL, 0x20); + wmb(); /* Make sure the PLL register writes are done */ + + if (!dp_5nm_pll_get_status(pll, C_READY)) { + rc = -EINVAL; + goto lock_err; + } + + if (!dp_5nm_pll_get_status(pll, FREQ_DONE)) { + rc = -EINVAL; + goto lock_err; + } + + if (!dp_5nm_pll_get_status(pll, PLL_LOCKED)) { + rc = -EINVAL; + goto lock_err; + } + + dp_pll_write(dp_phy, DP_PHY_CFG, 0x19); + /* Make sure the PHY register writes are done */ + wmb(); + + if (!dp_5nm_pll_get_status(pll, TSYNC_DONE)) { + rc = -EINVAL; + goto lock_err; + } + + if (!dp_5nm_pll_get_status(pll, PHY_READY)) { + rc = -EINVAL; + goto lock_err; + } + + pll->aux->state |= DP_STATE_PLL_LOCKED; + DP_DEBUG("PLL is locked\n"); +lock_err: + return rc; +} + +static int dp_pll_disable_5nm(struct clk_hw *hw) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct dp_pll *pll = vco->priv; + + /* Assert DP PHY power down */ + dp_pll_write(dp_phy, DP_PHY_PD_CTL, 0x2); + /* + * Make sure all the register writes to disable PLL are + * completed before doing any other operation + */ + wmb(); + + return 0; +} + +static struct clk_ops mux_clk_ops; +static struct regmap_config dp_pll_5nm_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x910, +}; + +int dp_mux_set_parent_5nm(void *context, unsigned int reg, unsigned int val) +{ + struct dp_pll *pll = context; + u32 auxclk_div; + + if (!context) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + auxclk_div = dp_pll_read(dp_phy, DP_PHY_VCO_DIV); + auxclk_div &= ~0x03; + + if (val == 0) + auxclk_div |= 1; + else if (val == 1) + auxclk_div |= 2; + else if (val == 2) + auxclk_div |= 0; + + dp_pll_write(dp_phy, DP_PHY_VCO_DIV, auxclk_div); + /* Make sure the PHY registers writes are done */ + wmb(); + DP_DEBUG("mux=%d auxclk_div=%x\n", val, auxclk_div); + + return 0; +} + +int dp_mux_get_parent_5nm(void *context, unsigned int reg, unsigned int *val) +{ + u32 auxclk_div = 0; + struct dp_pll *pll = context; + + if (!context || !val) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + if (is_gdsc_disabled(pll)) + return 0; + + auxclk_div = dp_pll_read(dp_phy, DP_PHY_VCO_DIV); + auxclk_div &= 0x03; + + if (auxclk_div == 1) /* Default divider */ + *val = 0; + else if (auxclk_div == 2) + *val = 1; + else if (auxclk_div == 0) + *val = 2; + + DP_DEBUG("auxclk_div=%d, val=%d\n", auxclk_div, *val); + + return 0; +} + +static struct regmap_bus dp_pixel_mux_regmap_ops = { + .reg_write = dp_mux_set_parent_5nm, + .reg_read = dp_mux_get_parent_5nm, +}; + +static int dp_vco_set_rate_5nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct dp_pll_vco_clk *vco; + int rc; + struct dp_pll *pll; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + vco = to_dp_vco_hw(hw); + pll = vco->priv; + + DP_DEBUG("DP lane CLK rate=%ld\n", rate); + + rc = dp_config_vco_rate_5nm(vco, rate); + if (rc) + DP_ERR("Failed to set clk rate\n"); + + vco->rate = rate; + + return 0; +} + +static int dp_regulator_enable_5nm(struct dp_parser *parser, + enum dp_pm_type pm_type, bool enable) +{ + int rc = 0; + struct dss_module_power mp; + + if (pm_type < DP_CORE_PM || pm_type >= DP_MAX_PM) { + DP_ERR("invalid resource: %d %s\n", pm_type, + dp_parser_pm_name(pm_type)); + return -EINVAL; + } + + mp = parser->mp[pm_type]; + rc = msm_dss_enable_vreg(mp.vreg_config, mp.num_vreg, enable); + if (rc) { + DP_ERR("failed to '%s' vregs for %s\n", + enable ? "enable" : "disable", + dp_parser_pm_name(pm_type)); + return rc; + } + + DP_DEBUG("success: '%s' vregs for %s\n", enable ? "enable" : "disable", + dp_parser_pm_name(pm_type)); + return rc; +} + +static int dp_vco_prepare_5nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco; + struct dp_pll *pll; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + vco = to_dp_vco_hw(hw); + pll = vco->priv; + + DP_DEBUG("rate=%ld\n", vco->rate); + + /* + * Enable DP_PM_PLL regulator if the PLL revision is 5nm-V1 and the + * link rate is 8.1Gbps. This will result in voting to place Mx rail in + * turbo as required for V1 hardware PLL functionality. + */ + if (pll->revision == DP_PLL_5NM_V1 && + vco->rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000) + dp_regulator_enable_5nm(pll->parser, DP_PLL_PM, true); + + if ((pll->vco_cached_rate != 0) + && (pll->vco_cached_rate == vco->rate)) { + rc = dp_vco_set_rate_5nm(hw, pll->vco_cached_rate, + pll->vco_cached_rate); + if (rc) { + DP_ERR("index=%d vco_set_rate failed. rc=%d\n", + rc, pll->index); + goto error; + } + } + + rc = dp_pll_enable_5nm(hw); + if (rc) { + DP_ERR("ndx=%d failed to enable dp pll\n", pll->index); + goto error; + } + +error: + return rc; +} + +static void dp_vco_unprepare_5nm(struct clk_hw *hw) +{ + struct dp_pll_vco_clk *vco; + struct dp_pll *pll; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return; + } + + vco = to_dp_vco_hw(hw); + pll = vco->priv; + + if (!pll) { + DP_ERR("invalid input parameter\n"); + return; + } + + if (pll->revision == DP_PLL_5NM_V1 && + vco->rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000) + dp_regulator_enable_5nm(pll->parser, DP_PLL_PM, false); + + pll->vco_cached_rate = vco->rate; + dp_pll_disable_5nm(hw); +} + +static unsigned long dp_vco_recalc_rate_5nm(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dp_pll_vco_clk *vco; + u32 hsclk_sel, link_clk_divsel, hsclk_div, link_clk_div = 0; + unsigned long vco_rate; + struct dp_pll *pll; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return 0; + } + + vco = to_dp_vco_hw(hw); + pll = vco->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + DP_DEBUG("input rates: parent=%lu, vco=%lu\n", parent_rate, vco->rate); + + hsclk_sel = dp_pll_read(dp_pll, QSERDES_COM_HSCLK_SEL); + hsclk_sel &= 0x0f; + + if (hsclk_sel == 5) + hsclk_div = 5; + else if (hsclk_sel == 3) + hsclk_div = 3; + else if (hsclk_sel == 1) + hsclk_div = 2; + else if (hsclk_sel == 0) + hsclk_div = 1; + else { + DP_DEBUG("unknown divider. forcing to default\n"); + hsclk_div = 5; + } + + link_clk_divsel = dp_pll_read(dp_phy, DP_PHY_AUX_CFG2); + link_clk_divsel >>= 2; + link_clk_divsel &= 0x3; + + if (link_clk_divsel == 0) + link_clk_div = 5; + else if (link_clk_divsel == 1) + link_clk_div = 10; + else if (link_clk_divsel == 2) + link_clk_div = 20; + else + DP_ERR("unsupported div. Phy_mode: %d\n", link_clk_divsel); + + if (link_clk_div == 20) { + vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + } else { + if (hsclk_div == 5) + vco_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000; + else if (hsclk_div == 3) + vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (hsclk_div == 2) + vco_rate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + vco_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000; + } + + DP_DEBUG("hsclk: sel=0x%x, div=0x%x; lclk: sel=%u, div=%u, rate=%lu\n", + hsclk_sel, hsclk_div, link_clk_divsel, link_clk_div, vco_rate); + + pll->vco_cached_rate = vco->rate = vco_rate; + + return vco_rate; +} + +static long dp_vco_round_rate_5nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dp_pll_vco_clk *vco; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return 0; + } + + vco = to_dp_vco_hw(hw); + if (rate <= vco->min_rate) + rrate = vco->min_rate; + else if (rate <= DP_VCO_HSCLK_RATE_2700MHZDIV1000) + rrate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (rate <= DP_VCO_HSCLK_RATE_5400MHZDIV1000) + rrate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + rrate = vco->max_rate; + + DP_DEBUG("rrate=%ld\n", rrate); + + if (parent_rate) + *parent_rate = rrate; + return rrate; +} + +/* Op structures */ +static const struct clk_ops dp_5nm_vco_clk_ops = { + .recalc_rate = dp_vco_recalc_rate_5nm, + .set_rate = dp_vco_set_rate_5nm, + .round_rate = dp_vco_round_rate_5nm, + .prepare = dp_vco_prepare_5nm, + .unprepare = dp_vco_unprepare_5nm, +}; + +static struct dp_pll_vco_clk dp_vco_clk = { + .min_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000, + .max_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000, + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_clk", + .parent_names = (const char *[]){ "bi_tcxo" }, + .num_parents = 1, + .ops = &dp_5nm_vco_clk_ops, + }, +}; + +static struct clk_fixed_factor dp_phy_pll_link_clk = { + .div = 10, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_phy_pll_link_clk", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_two_clk_src = { + .div = 2, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_two_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_four_clk_src = { + .div = 4, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_four_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_six_clk_src = { + .div = 6, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_six_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dp_phy_pll_vco_div_clk = { + .reg = 0x64, + .shift = 0, + .width = 2, + + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dp_phy_pll_vco_div_clk", + .parent_names = + (const char *[]){"dp_vco_divsel_two_clk_src", + "dp_vco_divsel_four_clk_src", + "dp_vco_divsel_six_clk_src"}, + .num_parents = 3, + .ops = &mux_clk_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static int clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + int ret = 0; + + if (!hw || !req) { + DP_ERR("Invalid input parameters\n"); + return -EINVAL; + } + + ret = __clk_mux_determine_rate_closest(hw, req); + if (ret) + return ret; + + /* Set the new parent of mux if there is a new valid parent */ + if (hw->clk && req->best_parent_hw->clk) + clk_set_parent(hw->clk, req->best_parent_hw->clk); + + return 0; +} + +static unsigned long mux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk *div_clk = NULL, *vco_clk = NULL; + struct dp_pll_vco_clk *vco = NULL; + + if (!hw) { + DP_ERR("Invalid input parameter\n"); + return 0; + } + + div_clk = clk_get_parent(hw->clk); + if (!div_clk) + return 0; + + vco_clk = clk_get_parent(div_clk); + if (!vco_clk) + return 0; + + vco = to_dp_vco_hw(__clk_get_hw(vco_clk)); + if (!vco) + return 0; + + if (vco->rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000) + return (vco->rate / 6); + else if (vco->rate == DP_VCO_HSCLK_RATE_5400MHZDIV1000) + return (vco->rate / 4); + else + return (vco->rate / 2); +} + +static struct clk_hw *mdss_dp_pllcc_5nm[] = { + [DP_VCO_CLK] = &dp_vco_clk.hw, + [DP_LINK_CLK_DIVSEL_TEN] = &dp_phy_pll_link_clk.hw, + [DP_VCO_DIVIDED_TWO_CLK_SRC] = &dp_vco_divsel_two_clk_src.hw, + [DP_VCO_DIVIDED_FOUR_CLK_SRC] = &dp_vco_divsel_four_clk_src.hw, + [DP_VCO_DIVIDED_SIX_CLK_SRC] = &dp_vco_divsel_six_clk_src.hw, + [DP_PHY_PLL_VCO_DIV_CLK] = &dp_phy_pll_vco_div_clk.clkr.hw, +}; + +static struct dp_pll_db dp_pdb; + +int dp_pll_clock_register_5nm(struct dp_pll *pll) +{ + int rc = -ENOTSUPP, i = 0; + struct platform_device *pdev; + struct clk *clk; + struct regmap *regmap; + int num_clks = ARRAY_SIZE(mdss_dp_pllcc_5nm); + + if (!pll) { + DP_ERR("pll data not initialized\n"); + return -EINVAL; + } + pdev = pll->pdev; + + pll->clk_data = kzalloc(sizeof(*pll->clk_data), GFP_KERNEL); + if (!pll->clk_data) + return -ENOMEM; + + pll->clk_data->clks = kcalloc(num_clks, sizeof(struct clk *), + GFP_KERNEL); + if (!pll->clk_data->clks) { + kfree(pll->clk_data); + return -ENOMEM; + } + + pll->clk_data->clk_num = num_clks; + + pll->priv = &dp_pdb; + dp_pdb.pll = pll; + + /* Set client data for vco, mux and div clocks */ + regmap = regmap_init(&pdev->dev, &dp_pixel_mux_regmap_ops, + pll, &dp_pll_5nm_cfg); + mux_clk_ops = clk_regmap_mux_closest_ops; + mux_clk_ops.determine_rate = clk_mux_determine_rate; + mux_clk_ops.recalc_rate = mux_recalc_rate; + + dp_vco_clk.priv = pll; + dp_phy_pll_vco_div_clk.clkr.regmap = regmap; + + for (i = DP_VCO_CLK; i <= DP_PHY_PLL_VCO_DIV_CLK; i++) { + DP_DEBUG("reg clk: %d index: %d\n", i, pll->index); + clk = clk_register(&pdev->dev, mdss_dp_pllcc_5nm[i]); + if (IS_ERR(clk)) { + DP_ERR("clk registration failed for DP: %d\n", + pll->index); + rc = -EINVAL; + goto clk_reg_fail; + } + pll->clk_data->clks[i] = clk; + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, pll->clk_data); + if (rc) { + DP_ERR("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + goto clk_reg_fail; + } else { + DP_DEBUG("success\n"); + } + return rc; +clk_reg_fail: + dp_pll_clock_unregister_5nm(pll); + return rc; +} + +void dp_pll_clock_unregister_5nm(struct dp_pll *pll) +{ + kfree(pll->clk_data->clks); + kfree(pll->clk_data); +} diff --git a/msm/dp/dp_power.c b/msm/dp/dp_power.c index e14488f43f..403d5cf69d 100644 --- a/msm/dp/dp_power.c +++ b/msm/dp/dp_power.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #include @@ -9,16 +9,17 @@ #include "dp_power.h" #include "dp_catalog.h" #include "dp_debug.h" +#include "dp_pll.h" #define DP_CLIENT_NAME_SIZE 20 struct dp_power_private { struct dp_parser *parser; + struct dp_pll *pll; struct platform_device *pdev; struct clk *pixel_clk_rcg; struct clk *pixel_parent; struct clk *pixel1_clk_rcg; - struct clk *pixel1_parent; struct dp_power dp_power; @@ -84,6 +85,17 @@ static int dp_power_regulator_ctrl(struct dp_power_private *power, bool enable) parser = power->parser; for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { + /* + * The DP_PLL_PM regulator is controlled by dp_display based + * on the link configuration. + */ + if (i == DP_PLL_PM) { + DP_DEBUG("skipping: '%s' vregs for %s\n", + enable ? "enable" : "disable", + dp_parser_pm_name(i)); + continue; + } + rc = msm_dss_enable_vreg( parser->mp[i].vreg_config, parser->mp[i].num_vreg, enable); @@ -151,6 +163,20 @@ static int dp_power_pinctrl_set(struct dp_power_private *power, bool active) return rc; } +static void dp_power_clk_put(struct dp_power_private *power) +{ + enum dp_pm_type module; + + for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { + struct dss_module_power *pm = &power->parser->mp[module]; + + if (!pm->num_clk) + continue; + + msm_dss_put_clk(pm->clk_config, pm->num_clk); + } +} + static int dp_power_clk_init(struct dp_power_private *power, bool enable) { int rc = 0; @@ -175,52 +201,53 @@ static int dp_power_clk_init(struct dp_power_private *power, bool enable) } } - power->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg"); + power->pixel_clk_rcg = clk_get(dev, "pixel_clk_rcg"); if (IS_ERR(power->pixel_clk_rcg)) { - DP_DEBUG("Unable to get DP pixel clk RCG\n"); + DP_ERR("Unable to get DP pixel clk RCG: %d\n", + PTR_ERR(power->pixel_clk_rcg)); + rc = PTR_ERR(power->pixel_clk_rcg); power->pixel_clk_rcg = NULL; + goto err_pixel_clk_rcg; } - power->pixel_parent = devm_clk_get(dev, "pixel_parent"); + power->pixel_parent = clk_get(dev, "pixel_parent"); if (IS_ERR(power->pixel_parent)) { - DP_DEBUG("Unable to get DP pixel RCG parent\n"); + DP_DEBUG("Unable to get DP pixel RCG parent: %d\n", + PTR_ERR(power->pixel_parent)); + rc = PTR_ERR(power->pixel_parent); power->pixel_parent = NULL; + goto err_pixel_parent; } - power->pixel1_clk_rcg = devm_clk_get(dev, "pixel1_clk_rcg"); + power->pixel1_clk_rcg = clk_get(dev, "pixel1_clk_rcg"); if (IS_ERR(power->pixel1_clk_rcg)) { - DP_DEBUG("Unable to get DP pixel1 clk RCG\n"); + DP_DEBUG("Unable to get DP pixel1 clk RCG: %d\n", + PTR_ERR(power->pixel1_clk_rcg)); + rc = PTR_ERR(power->pixel1_clk_rcg); power->pixel1_clk_rcg = NULL; - } - - power->pixel1_parent = devm_clk_get(dev, "pixel1_parent"); - if (IS_ERR(power->pixel1_parent)) { - DP_DEBUG("Unable to get DP pixel1 RCG parent\n"); - power->pixel1_parent = NULL; + goto err_pixel1_clk_rcg; } } else { + if (power->pixel1_clk_rcg) + clk_put(power->pixel1_clk_rcg); + if (power->pixel_parent) - devm_clk_put(dev, power->pixel_parent); + clk_put(power->pixel_parent); if (power->pixel_clk_rcg) - devm_clk_put(dev, power->pixel_clk_rcg); + clk_put(power->pixel_clk_rcg); - if (power->pixel1_parent) - devm_clk_put(dev, power->pixel1_parent); - - if (power->pixel1_clk_rcg) - devm_clk_put(dev, power->pixel1_clk_rcg); - - for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { - struct dss_module_power *pm = - &power->parser->mp[module]; - - if (!pm->num_clk) - continue; - - msm_dss_put_clk(pm->clk_config, pm->num_clk); - } + dp_power_clk_put(power); } + + return rc; + +err_pixel1_clk_rcg: + clk_put(power->pixel_parent); +err_pixel_parent: + clk_put(power->pixel_clk_rcg); +err_pixel_clk_rcg: + dp_power_clk_put(power); exit: return rc; } @@ -373,7 +400,7 @@ static int dp_power_request_gpios(struct dp_power_private *power) unsigned int gpio = mp->gpio_config[i].gpio; if (gpio_is_valid(gpio)) { - rc = devm_gpio_request(dev, gpio, gpio_names[i]); + rc = gpio_request(gpio, gpio_names[i]); if (rc) { DP_ERR("request %s gpio failed, rc=%d\n", gpio_names[i], rc); @@ -521,13 +548,20 @@ static int dp_power_set_pixel_clk_parent(struct dp_power *dp_power, u32 strm_id) if (strm_id == DP_STREAM_0) { if (power->pixel_clk_rcg && power->pixel_parent) - clk_set_parent(power->pixel_clk_rcg, + rc = clk_set_parent(power->pixel_clk_rcg, power->pixel_parent); + else + DP_WARN("skipped for strm_id=%d\n", strm_id); } else if (strm_id == DP_STREAM_1) { - if (power->pixel1_clk_rcg && power->pixel1_parent) - clk_set_parent(power->pixel1_clk_rcg, - power->pixel1_parent); + if (power->pixel1_clk_rcg && power->pixel_parent) + rc = clk_set_parent(power->pixel1_clk_rcg, + power->pixel_parent); + else + DP_WARN("skipped for strm_id=%d\n", strm_id); } + + if (rc) + DP_ERR("failed. strm_id=%d, rc=%d\n", strm_id, rc); exit: return rc; } @@ -653,25 +687,26 @@ exit: return rc; } -struct dp_power *dp_power_get(struct dp_parser *parser) +struct dp_power *dp_power_get(struct dp_parser *parser, struct dp_pll *pll) { int rc = 0; struct dp_power_private *power; struct dp_power *dp_power; - if (!parser) { + if (!parser || !pll) { DP_ERR("invalid input\n"); rc = -EINVAL; goto error; } - power = devm_kzalloc(&parser->pdev->dev, sizeof(*power), GFP_KERNEL); + power = kzalloc(sizeof(*power), GFP_KERNEL); if (!power) { rc = -ENOMEM; goto error; } power->parser = parser; + power->pll = pll; power->pdev = parser->pdev; dp_power = &power->dp_power; @@ -698,5 +733,5 @@ void dp_power_put(struct dp_power *dp_power) power = container_of(dp_power, struct dp_power_private, dp_power); - devm_kfree(&power->pdev->dev, power); + kfree(power); } diff --git a/msm/dp/dp_power.h b/msm/dp/dp_power.h index a5e5f5d93e..7a0302356d 100644 --- a/msm/dp/dp_power.h +++ b/msm/dp/dp_power.h @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_POWER_H_ #define _DP_POWER_H_ #include "dp_parser.h" +#include "dp_pll.h" #include "sde_power_handle.h" /** @@ -17,6 +18,8 @@ * @clk_enable: enable/disable the DP clocks * @set_pixel_clk_parent: set the parent of DP pixel clock * @clk_get_rate: get the current rate for provided clk_name + * @power_client_init: configures clocks and regulators + * @power_client_deinit: frees clock and regulator resources */ struct dp_power { struct drm_device *drm_dev; @@ -37,13 +40,14 @@ struct dp_power { * dp_power_get() - configure and get the DisplayPort power module data * * @parser: instance of parser module + * @pll: instance of pll module * return: pointer to allocated power module data * * This API will configure the DisplayPort's power module and provides * methods to be called by the client to configure the power related * modueles. */ -struct dp_power *dp_power_get(struct dp_parser *parser); +struct dp_power *dp_power_get(struct dp_parser *parser, struct dp_pll *pll); /** * dp_power_put() - release the power related resources diff --git a/msm/dp/dp_reg.h b/msm/dp/dp_reg.h index 50ef169f93..6291bac4fe 100644 --- a/msm/dp/dp_reg.h +++ b/msm/dp/dp_reg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_REG_H_ @@ -387,7 +387,7 @@ #define TXn_HIGHZ_DRVR_EN_V420 (0x0058) #define TXn_TX_POL_INV_V420 (0x005C) -#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x004) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x044) /* DP MMSS_CC registers */ #define MMSS_DP_LINK_CMD_RCGR (0x0138)