/* Copyright (c) 2012-2017, 2020, 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 * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdss_hdcp.h" #include "mdss.h" #include "mdss_dp.h" #include "mdss_dp_util.h" #include "mdss_hdmi_panel.h" #include #include "mdss_hdcp.h" #include "mdss_debug.h" #define RGB_COMPONENTS 3 #define VDDA_MIN_UV 1800000 /* uV units */ #define VDDA_MAX_UV 1800000 /* uV units */ #define VDDA_UA_ON_LOAD 100000 /* uA units */ #define VDDA_UA_OFF_LOAD 100 /* uA units */ #define DP_CRYPTO_CLK_RATE_KHZ 337500 #define DP_STRING_SIZE 30 struct mdss_dp_attention_node { u32 vdo; struct list_head list; }; #define DEFAULT_VIDEO_RESOLUTION HDMI_VFRMT_640x480p60_4_3 static int mdss_dp_host_init(struct mdss_panel_data *pdata); static int mdss_dp_host_deinit(struct mdss_dp_drv_pdata *dp); static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv); static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata); static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp, bool lt_needed); static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp_drv); static void dp_send_events(struct mdss_dp_drv_pdata *dp, u32 events); static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, enum notification_status status); static int mdss_dp_process_phy_test_pattern_request( struct mdss_dp_drv_pdata *dp); static struct mdss_dp_drv_pdata *dp_get_data( struct platform_device *pdev); static int mdss_dp_send_audio_notification( struct mdss_dp_drv_pdata *dp, u32 state); static void mdss_dp_reset_sw_state(struct mdss_dp_drv_pdata *dp); static inline void mdss_dp_reset_sink_count(struct mdss_dp_drv_pdata *dp) { memset(&dp->sink_count, 0, sizeof(dp->sink_count)); } static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp) { dp->test_data = (const struct dpcd_test_request){ 0 }; dp->test_data.test_bit_depth = DP_TEST_BIT_DEPTH_UNKNOWN; hdmi_edid_config_override(dp->panel_data.panel_info.edid_data, false, NULL); } static inline bool mdss_dp_is_link_status_updated(struct mdss_dp_drv_pdata *dp) { return dp->link_status.link_status_updated; } static inline bool mdss_dp_is_downstream_port_status_changed( struct mdss_dp_drv_pdata *dp) { return dp->link_status.downstream_port_status_changed; } static inline bool mdss_dp_is_audio_pattern_requested( struct mdss_dp_drv_pdata *dp) { return (dp->test_data.test_requested & TEST_AUDIO_PATTERN); } static inline bool mdss_dp_is_link_training_requested( struct mdss_dp_drv_pdata *dp) { return (dp->test_data.test_requested == TEST_LINK_TRAINING); } static inline bool mdss_dp_is_video_pattern_requested( struct mdss_dp_drv_pdata *dp) { return (dp->test_data.test_requested & TEST_VIDEO_PATTERN) && !(dp->test_data.test_requested & TEST_AUDIO_DISABLED_VIDEO); } static inline bool mdss_dp_is_phy_test_pattern_requested( struct mdss_dp_drv_pdata *dp) { return (dp->test_data.test_requested == PHY_TEST_PATTERN); } static void mdss_dp_put_dt_clk_data(struct device *dev, struct dss_module_power *module_power) { if (!module_power) { DEV_ERR("%s: invalid input\n", __func__); return; } if (module_power->clk_config) { devm_kfree(dev, module_power->clk_config); module_power->clk_config = NULL; } module_power->num_clk = 0; } /* mdss_dp_put_dt_clk_data */ static int mdss_dp_is_ctrl_clk(const char *clk_name) { bool is_ctrl_clk = false; pr_debug("%s+: clk-name=%s\n", __func__, clk_name); if (!strcmp(clk_name, "ctrl_link_clk")) is_ctrl_clk = true; else if (!strcmp(clk_name, "ctrl_link_iface_clk")) is_ctrl_clk = true; else if (!strcmp(clk_name, "ctrl_crypto_clk")) is_ctrl_clk = true; else if (!strcmp(clk_name, "ctrl_pixel_clk")) is_ctrl_clk = true; else is_ctrl_clk = false; pr_debug("%s-: is_ctrl_clk = %d", __func__, is_ctrl_clk); return is_ctrl_clk; } static int mdss_dp_is_core_clk(const char *clk_name) { bool is_core_clk = false; pr_debug("%s+: clk-name=%s\n", __func__, clk_name); if (!strcmp(clk_name, "core_mnoc_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_iface_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_bus_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_mdp_core_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_alt_iface_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_aux_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_ref_clk")) is_core_clk = true; else if (!strcmp(clk_name, "core_ref_clk_src")) is_core_clk = true; else if (!strcmp(clk_name, "core_ahb_phy_clk")) is_core_clk = true; else is_core_clk = false; pr_debug("%s-: is_core_clk = %d", __func__, is_core_clk); return is_core_clk; } static void mdss_dp_reset_phy_config_indices(struct mdss_dp_drv_pdata *dp) { int i = 0; for (i = 0; i < PHY_AUX_CFG_MAX; i++) dp->aux_cfg[i].current_index = 0; } static void mdss_dp_phy_aux_cfg_reset(struct mdss_dp_drv_pdata *dp) { int i = 0; for (i = 0; i < PHY_AUX_CFG_MAX; i++) dp->aux_cfg[i] = (const struct mdss_dp_phy_cfg){ 0 }; } static int mdss_dp_parse_aux_cfg(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp) { int len = 0, i = 0, j = 0, config_count = 0; const char *data; int const minimum_config_count = 1; for (i = 0; i < PHY_AUX_CFG_MAX; i++) { const char *property = mdss_dp_get_phy_aux_config_property(i); data = of_get_property(pdev->dev.of_node, property, &len); if (!data) { pr_err("Unable to read %s\n", property); goto error; } config_count = len - 1; if ((config_count < minimum_config_count) || (config_count > MDSS_DP_MAX_PHY_CFG_VALUE_CNT)) { pr_err("Invalid config count (%d) configs for %s\n", config_count, property); goto error; } dp->aux_cfg[i].offset = data[0]; dp->aux_cfg[i].cfg_cnt = config_count; pr_debug("%s offset=0x%x, cfg_cnt=%d\n", property, dp->aux_cfg[i].offset, dp->aux_cfg[i].cfg_cnt); for (j = 1; j < len; j++) { dp->aux_cfg[i].lut[j - 1] = data[j]; pr_debug("%s lut[%d]=0x%x\n", property, i, dp->aux_cfg[i].lut[j - 1]); } } return 0; error: mdss_dp_phy_aux_cfg_reset(dp); return -EINVAL; } static int mdss_dp_parse_prop(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) { int len = 0, i = 0, rc = 0; const char *data; rc = mdss_dp_parse_aux_cfg(pdev, dp_drv); if (rc) return rc; data = of_get_property(pdev->dev.of_node, "qcom,logical2physical-lane-map", &len); if ((!data) || (len != DP_MAX_PHY_LN)) { pr_debug("%s:%d, lane mapping not defined, use default", __func__, __LINE__); dp_drv->l_map[DP_PHY_LN0] = DP_ML0; dp_drv->l_map[DP_PHY_LN1] = DP_ML1; dp_drv->l_map[DP_PHY_LN2] = DP_ML2; dp_drv->l_map[DP_PHY_LN3] = DP_ML3; } else { for (i = 0; i < len; i++) dp_drv->l_map[i] = data[i]; } rc = of_property_read_u32(pdev->dev.of_node, "qcom,max-pclk-frequency-khz", &dp_drv->max_pclk_khz); if (rc) dp_drv->max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ; return 0; } static int mdss_dp_init_clk_power_data(struct device *dev, struct mdss_dp_drv_pdata *pdata) { int num_clk = 0, i = 0, rc = 0; int core_clk_count = 0, ctrl_clk_count = 0; struct dss_module_power *core_power_data = NULL; struct dss_module_power *ctrl_power_data = NULL; const char *clk_name; num_clk = of_property_count_strings(dev->of_node, "clock-names"); if (num_clk <= 0) { pr_err("no clocks are defined\n"); rc = -EINVAL; goto exit; } core_power_data = &pdata->power_data[DP_CORE_PM]; ctrl_power_data = &pdata->power_data[DP_CTRL_PM]; for (i = 0; i < num_clk; i++) { of_property_read_string_index(dev->of_node, "clock-names", i, &clk_name); if (mdss_dp_is_core_clk(clk_name)) core_clk_count++; if (mdss_dp_is_ctrl_clk(clk_name)) ctrl_clk_count++; } pr_debug("%s: core_clk_count = %d ctrl_clk_count=%d\n", __func__, core_clk_count, ctrl_clk_count); /* Initialize the CORE power module */ if (core_clk_count <= 0) { pr_err("no core clocks are defined\n"); rc = -EINVAL; goto exit; } core_power_data->num_clk = core_clk_count; core_power_data->clk_config = devm_kzalloc(dev, sizeof(struct dss_clk) * core_power_data->num_clk, GFP_KERNEL); if (!core_power_data->clk_config) { rc = -EINVAL; goto exit; } /* Initialize the CTRL power module */ if (ctrl_clk_count <= 0) { pr_err("no ctrl clocks are defined\n"); rc = -EINVAL; goto ctrl_clock_error; } ctrl_power_data->num_clk = ctrl_clk_count; ctrl_power_data->clk_config = devm_kzalloc(dev, sizeof(struct dss_clk) * ctrl_power_data->num_clk, GFP_KERNEL); if (!ctrl_power_data->clk_config) { ctrl_power_data->num_clk = 0; rc = -EINVAL; goto ctrl_clock_error; } return rc; ctrl_clock_error: mdss_dp_put_dt_clk_data(dev, core_power_data); exit: return rc; } static int mdss_dp_get_dt_clk_data(struct device *dev, struct mdss_dp_drv_pdata *pdata) { int rc = 0, i = 0; const char *clk_name; int num_clk = 0; int core_clk_index = 0, ctrl_clk_index = 0; int core_clk_count = 0, ctrl_clk_count = 0; struct dss_module_power *core_power_data = NULL; struct dss_module_power *ctrl_power_data = NULL; if (!dev || !pdata) { pr_err("invalid input\n"); rc = -EINVAL; goto exit; } rc = mdss_dp_init_clk_power_data(dev, pdata); if (rc) { pr_err("failed to initialize power data\n"); rc = -EINVAL; goto exit; } core_power_data = &pdata->power_data[DP_CORE_PM]; core_clk_count = core_power_data->num_clk; ctrl_power_data = &pdata->power_data[DP_CTRL_PM]; ctrl_clk_count = ctrl_power_data->num_clk; num_clk = core_clk_count + ctrl_clk_count; for (i = 0; i < num_clk; i++) { of_property_read_string_index(dev->of_node, "clock-names", i, &clk_name); if (mdss_dp_is_core_clk(clk_name) && core_clk_index < core_clk_count) { struct dss_clk *clk = &core_power_data->clk_config[core_clk_index]; strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); clk->type = DSS_CLK_AHB; core_clk_index++; } else if (mdss_dp_is_ctrl_clk(clk_name) && ctrl_clk_index < ctrl_clk_count) { struct dss_clk *clk = &ctrl_power_data->clk_config[ctrl_clk_index]; strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); ctrl_clk_index++; if (!strcmp(clk_name, "ctrl_link_clk") || !strcmp(clk_name, "ctrl_pixel_clk") || !strcmp(clk_name, "ctrl_crypto_clk")) clk->type = DSS_CLK_PCLK; else clk->type = DSS_CLK_AHB; } } pr_debug("Display-port clock parsing successful\n"); exit: return rc; } /* mdss_dp_get_dt_clk_data */ static int mdss_dp_clk_init(struct mdss_dp_drv_pdata *dp_drv, struct device *dev, bool initialize) { struct dss_module_power *core_power_data = NULL; struct dss_module_power *ctrl_power_data = NULL; int rc = 0; if (!dp_drv || !dev) { pr_err("invalid input\n"); rc = -EINVAL; goto exit; } core_power_data = &dp_drv->power_data[DP_CORE_PM]; ctrl_power_data = &dp_drv->power_data[DP_CTRL_PM]; if (!core_power_data || !ctrl_power_data) { pr_err("invalid power_data\n"); rc = -EINVAL; goto exit; } if (initialize) { rc = msm_dss_get_clk(dev, core_power_data->clk_config, core_power_data->num_clk); if (rc) { DEV_ERR("Failed to get %s clk. Err=%d\n", __mdss_dp_pm_name(DP_CORE_PM), rc); goto exit; } rc = msm_dss_get_clk(dev, ctrl_power_data->clk_config, ctrl_power_data->num_clk); if (rc) { DEV_ERR("Failed to get %s clk. Err=%d\n", __mdss_dp_pm_name(DP_CTRL_PM), rc); goto ctrl_get_error; } dp_drv->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg"); if (IS_ERR(dp_drv->pixel_clk_rcg)) { pr_debug("%s: Unable to get DP pixel clk RCG\n", __func__); dp_drv->pixel_clk_rcg = NULL; } dp_drv->pixel_parent = devm_clk_get(dev, "pixel_parent"); if (IS_ERR(dp_drv->pixel_parent)) { pr_debug("%s: Unable to get DP pixel RCG parent\n", __func__); dp_drv->pixel_parent = NULL; } dp_drv->pixel_clk_two_div = devm_clk_get(dev, "pixel_clk_two_div"); if (IS_ERR(dp_drv->pixel_clk_two_div)) { pr_debug("%s: Unable to get DP pixel two div clk\n", __func__); dp_drv->pixel_clk_two_div = NULL; } dp_drv->pixel_clk_four_div = devm_clk_get(dev, "pixel_clk_four_div"); if (IS_ERR(dp_drv->pixel_clk_four_div)) { pr_debug("%s: Unable to get DP pixel four div clk\n", __func__); dp_drv->pixel_clk_four_div = NULL; } } else { if (dp_drv->pixel_parent) devm_clk_put(dev, dp_drv->pixel_parent); if (dp_drv->pixel_clk_rcg) devm_clk_put(dev, dp_drv->pixel_clk_rcg); msm_dss_put_clk(ctrl_power_data->clk_config, ctrl_power_data->num_clk); msm_dss_put_clk(core_power_data->clk_config, core_power_data->num_clk); } return rc; ctrl_get_error: msm_dss_put_clk(core_power_data->clk_config, core_power_data->num_clk); exit: return rc; } static int mdss_dp_clk_set_rate_enable( struct dss_module_power *power_data, bool enable) { int ret = 0; if (enable) { ret = msm_dss_clk_set_rate( power_data->clk_config, power_data->num_clk); if (ret) { pr_err("failed to set clks rate.\n"); goto exit; } ret = msm_dss_enable_clk( power_data->clk_config, power_data->num_clk, 1); if (ret) { pr_err("failed to enable clks\n"); goto exit; } } else { ret = msm_dss_enable_clk( power_data->clk_config, power_data->num_clk, 0); if (ret) { pr_err("failed to disable clks\n"); goto exit; } } exit: return ret; } /* * This clock control function supports enabling/disabling * of core and ctrl power module clocks */ static int mdss_dp_clk_ctrl(struct mdss_dp_drv_pdata *dp_drv, int pm_type, bool enable) { int ret = 0; if ((pm_type != DP_CORE_PM) && (pm_type != DP_CTRL_PM)) { pr_err("unsupported power module: %s\n", __mdss_dp_pm_name(pm_type)); return -EINVAL; } if (enable) { if ((pm_type == DP_CORE_PM) && (dp_drv->core_clks_on)) { pr_debug("core clks already enabled\n"); return 0; } if ((pm_type == DP_CTRL_PM) && (dp_drv->link_clks_on)) { pr_debug("links clks already enabled\n"); return 0; } if ((pm_type == DP_CTRL_PM) && (!dp_drv->core_clks_on)) { pr_debug("Need to enable core clks before link clks\n"); ret = mdss_dp_clk_set_rate_enable( &dp_drv->power_data[DP_CORE_PM], enable); if (ret) { pr_err("failed to enable clks: %s. err=%d\n", __mdss_dp_pm_name(DP_CORE_PM), ret); goto error; } else { dp_drv->core_clks_on = true; } } } ret = mdss_dp_clk_set_rate_enable( &dp_drv->power_data[pm_type], enable); if (ret) { pr_err("failed to '%s' clks for: %s. err=%d\n", enable ? "enable" : "disable", __mdss_dp_pm_name(pm_type), ret); goto error; } if (pm_type == DP_CORE_PM) dp_drv->core_clks_on = enable; else dp_drv->link_clks_on = enable; pr_debug("%s clocks for %s\n", enable ? "enable" : "disable", __mdss_dp_pm_name(pm_type)); pr_debug("link_clks:%s core_clks:%s\n", dp_drv->link_clks_on ? "on" : "off", dp_drv->core_clks_on ? "on" : "off"); error: return ret; } static int mdss_dp_regulator_ctrl(struct mdss_dp_drv_pdata *dp_drv, bool enable) { int ret = 0, i = 0, j = 0; if (dp_drv->core_power == enable) { pr_debug("regulators already %s\n", enable ? "enabled" : "disabled"); return 0; } for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { ret = msm_dss_enable_vreg( dp_drv->power_data[i].vreg_config, dp_drv->power_data[i].num_vreg, enable); if (ret) { pr_err("failed to '%s' vregs for %s\n", enable ? "enable" : "disable", __mdss_dp_pm_name(i)); if (enable) { /* Disabling the enabled vregs */ for (j = i-1; j >= DP_CORE_PM; j--) { msm_dss_enable_vreg( dp_drv->power_data[j].vreg_config, dp_drv->power_data[j].num_vreg, 0); } } goto error; } } dp_drv->core_power = enable; error: return ret; } static void mdss_dp_put_dt_vreg_data(struct device *dev, struct dss_module_power *module_power) { if (!module_power) { DEV_ERR("invalid input\n"); return; } if (module_power->vreg_config) { devm_kfree(dev, module_power->vreg_config); module_power->vreg_config = NULL; } module_power->num_vreg = 0; } /* mdss_dp_put_dt_vreg_data */ static int mdss_dp_get_dt_vreg_data(struct device *dev, struct device_node *of_node, struct dss_module_power *mp, enum dp_pm_type module) { int i = 0, rc = 0; u32 tmp = 0; struct device_node *supply_node = NULL; const char *pm_supply_name = NULL; struct device_node *supply_root_node = NULL; if (!dev || !mp) { pr_err("invalid input\n"); rc = -EINVAL; return rc; } mp->num_vreg = 0; pm_supply_name = __mdss_dp_pm_supply_node_name(module); supply_root_node = of_get_child_by_name(of_node, pm_supply_name); if (!supply_root_node) { pr_err("no supply entry present: %s\n", pm_supply_name); goto novreg; } mp->num_vreg = of_get_available_child_count(supply_root_node); if (mp->num_vreg == 0) { pr_debug("no vreg\n"); goto novreg; } else { pr_debug("vreg found. count=%d\n", mp->num_vreg); } mp->vreg_config = devm_kzalloc(dev, sizeof(struct dss_vreg) * mp->num_vreg, GFP_KERNEL); if (!mp->vreg_config) { rc = -ENOMEM; goto error; } for_each_child_of_node(supply_root_node, supply_node) { const char *st = NULL; /* vreg-name */ rc = of_property_read_string(supply_node, "qcom,supply-name", &st); if (rc) { pr_err("error reading name. rc=%d\n", rc); goto error; } snprintf(mp->vreg_config[i].vreg_name, ARRAY_SIZE((mp->vreg_config[i].vreg_name)), "%s", st); /* vreg-min-voltage */ rc = of_property_read_u32(supply_node, "qcom,supply-min-voltage", &tmp); if (rc) { pr_err("error reading min volt. rc=%d\n", rc); goto error; } mp->vreg_config[i].min_voltage = tmp; /* vreg-max-voltage */ rc = of_property_read_u32(supply_node, "qcom,supply-max-voltage", &tmp); if (rc) { pr_err("error reading max volt. rc=%d\n", rc); goto error; } mp->vreg_config[i].max_voltage = tmp; /* enable-load */ rc = of_property_read_u32(supply_node, "qcom,supply-enable-load", &tmp); if (rc) { pr_err("error reading enable load. rc=%d\n", rc); goto error; } mp->vreg_config[i].enable_load = tmp; /* disable-load */ rc = of_property_read_u32(supply_node, "qcom,supply-disable-load", &tmp); if (rc) { pr_err("error reading disable load. rc=%d\n", rc); goto error; } mp->vreg_config[i].disable_load = tmp; pr_debug("%s min=%d, max=%d, enable=%d, disable=%d\n", mp->vreg_config[i].vreg_name, mp->vreg_config[i].min_voltage, mp->vreg_config[i].max_voltage, mp->vreg_config[i].enable_load, mp->vreg_config[i].disable_load ); ++i; } return rc; error: if (mp->vreg_config) { devm_kfree(dev, mp->vreg_config); mp->vreg_config = NULL; } novreg: mp->num_vreg = 0; return rc; } /* mdss_dp_get_dt_vreg_data */ static int mdss_dp_regulator_init(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) { int rc = 0, i = 0, j = 0; if (!pdev || !dp_drv) { pr_err("invalid input\n"); return -EINVAL; } for (i = DP_CORE_PM; !rc && (i < DP_MAX_PM); i++) { rc = msm_dss_config_vreg(&pdev->dev, dp_drv->power_data[i].vreg_config, dp_drv->power_data[i].num_vreg, 1); if (rc) { pr_err("failed to init vregs for %s\n", __mdss_dp_pm_name(i)); for (j = i-1; j >= DP_CORE_PM; j--) { msm_dss_config_vreg(&pdev->dev, dp_drv->power_data[j].vreg_config, dp_drv->power_data[j].num_vreg, 0); } } } return rc; } static int mdss_dp_pinctrl_set_state( struct mdss_dp_drv_pdata *dp, bool active) { struct pinctrl_state *pin_state; int rc = -EFAULT; if (IS_ERR_OR_NULL(dp->pin_res.pinctrl)) return PTR_ERR(dp->pin_res.pinctrl); pin_state = active ? dp->pin_res.state_active : dp->pin_res.state_suspend; if (!IS_ERR_OR_NULL(pin_state)) { rc = pinctrl_select_state(dp->pin_res.pinctrl, pin_state); if (rc) pr_err("can not set %s pins\n", active ? "mdss_dp_active" : "mdss_dp_sleep"); } else { pr_err("invalid '%s' pinstate\n", active ? "mdss_dp_active" : "mdss_dp_sleep"); } return rc; } static int mdss_dp_pinctrl_init(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp) { dp->pin_res.pinctrl = devm_pinctrl_get(&pdev->dev); if (IS_ERR_OR_NULL(dp->pin_res.pinctrl)) { pr_err("failed to get pinctrl\n"); return PTR_ERR(dp->pin_res.pinctrl); } dp->pin_res.state_active = pinctrl_lookup_state(dp->pin_res.pinctrl, "mdss_dp_active"); if (IS_ERR_OR_NULL(dp->pin_res.state_active)) { pr_err("can not get dp active pinstate\n"); return PTR_ERR(dp->pin_res.state_active); } dp->pin_res.state_suspend = pinctrl_lookup_state(dp->pin_res.pinctrl, "mdss_dp_sleep"); if (IS_ERR_OR_NULL(dp->pin_res.state_suspend)) { pr_err("can not get dp sleep pinstate\n"); return PTR_ERR(dp->pin_res.state_suspend); } return 0; } static int mdss_dp_request_gpios(struct mdss_dp_drv_pdata *dp) { int rc = 0; struct device *dev = NULL; if (!dp) { pr_err("invalid input\n"); return -EINVAL; } dev = &dp->pdev->dev; if (gpio_is_valid(dp->aux_en_gpio)) { rc = devm_gpio_request(dev, dp->aux_en_gpio, "aux_enable"); if (rc) { pr_err("request aux_en gpio failed, rc=%d\n", rc); goto aux_en_gpio_err; } } if (gpio_is_valid(dp->aux_sel_gpio)) { rc = devm_gpio_request(dev, dp->aux_sel_gpio, "aux_sel"); if (rc) { pr_err("request aux_sel gpio failed, rc=%d\n", rc); goto aux_sel_gpio_err; } } if (gpio_is_valid(dp->usbplug_cc_gpio)) { rc = devm_gpio_request(dev, dp->usbplug_cc_gpio, "usbplug_cc"); if (rc) { pr_err("request usbplug_cc gpio failed, rc=%d\n", rc); goto usbplug_cc_gpio_err; } } if (gpio_is_valid(dp->hpd_gpio)) { rc = devm_gpio_request(dev, dp->hpd_gpio, "hpd"); if (rc) { pr_err("request hpd gpio failed, rc=%d\n", rc); goto hpd_gpio_err; } } return rc; hpd_gpio_err: if (gpio_is_valid(dp->usbplug_cc_gpio)) gpio_free(dp->usbplug_cc_gpio); usbplug_cc_gpio_err: if (gpio_is_valid(dp->aux_sel_gpio)) gpio_free(dp->aux_sel_gpio); aux_sel_gpio_err: if (gpio_is_valid(dp->aux_en_gpio)) gpio_free(dp->aux_en_gpio); aux_en_gpio_err: return rc; } static int mdss_dp_config_gpios(struct mdss_dp_drv_pdata *dp, bool enable) { int rc = 0; if (enable == true) { rc = mdss_dp_request_gpios(dp); if (rc) { pr_err("gpio request failed\n"); return rc; } if (gpio_is_valid(dp->aux_en_gpio)) { rc = gpio_direction_output( dp->aux_en_gpio, 0); if (rc) pr_err("unable to set dir for aux_en gpio\n"); } if (gpio_is_valid(dp->aux_sel_gpio)) { rc = gpio_direction_output( dp->aux_sel_gpio, dp->aux_sel_gpio_output); if (rc) pr_err("unable to set dir for aux_sel gpio\n"); } if (gpio_is_valid(dp->usbplug_cc_gpio)) { gpio_set_value( dp->usbplug_cc_gpio, 0); } if (gpio_is_valid(dp->hpd_gpio)) { gpio_set_value( dp->hpd_gpio, 1); } } else { if (gpio_is_valid(dp->aux_en_gpio)) { gpio_set_value((dp->aux_en_gpio), 0); gpio_free(dp->aux_en_gpio); } if (gpio_is_valid(dp->aux_sel_gpio)) { gpio_set_value((dp->aux_sel_gpio), 0); gpio_free(dp->aux_sel_gpio); } if (gpio_is_valid(dp->usbplug_cc_gpio)) { gpio_set_value((dp->usbplug_cc_gpio), 0); gpio_free(dp->usbplug_cc_gpio); } if (gpio_is_valid(dp->hpd_gpio)) { gpio_set_value((dp->hpd_gpio), 0); gpio_free(dp->hpd_gpio); } } return 0; } static int mdss_dp_parse_gpio_params(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp) { dp->aux_en_gpio = of_get_named_gpio( pdev->dev.of_node, "qcom,aux-en-gpio", 0); if (!gpio_is_valid(dp->aux_en_gpio)) { pr_err("%d, Aux_en gpio not specified\n", __LINE__); return -EINVAL; } dp->aux_sel_gpio = of_get_named_gpio( pdev->dev.of_node, "qcom,aux-sel-gpio", 0); if (!gpio_is_valid(dp->aux_sel_gpio)) { pr_err("%d, Aux_sel gpio not specified\n", __LINE__); return -EINVAL; } dp->usbplug_cc_gpio = of_get_named_gpio( pdev->dev.of_node, "qcom,usbplug-cc-gpio", 0); if (!gpio_is_valid(dp->usbplug_cc_gpio)) { pr_err("%d,usbplug_cc gpio not specified\n", __LINE__); return -EINVAL; } dp->hpd_gpio = of_get_named_gpio( pdev->dev.of_node, "qcom,hpd-gpio", 0); if (!gpio_is_valid(dp->hpd_gpio)) { pr_info("%d,hpd gpio not specified\n", __LINE__); } return 0; } void mdss_dp_phy_initialize(struct mdss_dp_drv_pdata *dp) { /* * To siwtch the usb3_phy to operate in DP mode, the phy and PLL * should have the reset lines asserted */ mdss_dp_assert_phy_reset(&dp->ctrl_io, true); /* Delay to make sure the assert is propagated */ udelay(2000); mdss_dp_switch_usb3_phy_to_dp_mode(&dp->tcsr_reg_io); wmb(); /* ensure that the register write is successful */ mdss_dp_assert_phy_reset(&dp->ctrl_io, false); } void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp) { struct dpcd_cap *cap; struct display_timing_desc *timing; struct mdss_panel_info *pinfo; u32 data = 0; timing = &dp->edid.timing[0]; pinfo = &dp->panel_data.panel_info; cap = &dp->dpcd; data |= (2 << 13); /* Default-> LSCLK DIV: 1/4 LCLK */ /* Color Format */ switch (dp->panel_data.panel_info.out_format) { case MDP_Y_CBCR_H2V2: data |= (1 << 11); /* YUV420 */ break; case MDP_Y_CBCR_H2V1: data |= (2 << 11); /* YUV422 */ break; default: data |= (0 << 11); /* RGB */ break; } /* Scrambler reset enable */ if (cap->scrambler_reset) data |= (1 << 10); /* Bits per components */ data |= (mdss_dp_bpp_to_test_bit_depth(pinfo->bpp) << 8); /* Num of Lanes */ data |= ((dp->lane_cnt - 1) << 4); if (cap->enhanced_frame) data |= 0x40; if (!timing->interlaced) /* progressive */ data |= 0x04; data |= 0x03; /* sycn clock & static Mvid */ mdss_dp_configuration_ctrl(&dp->ctrl_io, data); } static int mdss_dp_wait4video_ready(struct mdss_dp_drv_pdata *dp_drv) { int ret = 0; if (dp_drv->cont_splash) return ret; ret = wait_for_completion_timeout(&dp_drv->video_comp, 30); if (ret <= 0) { pr_err("Link Train timedout\n"); ret = -EINVAL; } else { ret = 0; /* * The audio subsystem should only be notified once the DP * controller is in SEND_VIDEO state. This will ensure that * the DP audio engine is able to acknowledge the audio unmute * request, which will result in the AFE port being configured * correctly. */ mdss_dp_send_audio_notification(dp_drv, true); } pr_debug("End--\n"); return ret; } static void mdss_dp_update_cable_status(struct mdss_dp_drv_pdata *dp, bool connected) { mutex_lock(&dp->attention_lock); pr_debug("cable_connected to %d\n", connected); if (dp->cable_connected != connected) dp->cable_connected = connected; else pr_debug("no change in cable status\n"); mutex_unlock(&dp->attention_lock); } static int dp_get_cable_status(struct platform_device *pdev, u32 vote) { struct mdss_dp_drv_pdata *dp = dp_get_data(pdev); u32 hpd; if (!dp) { DEV_ERR("%s: invalid input\n", __func__); return -ENODEV; } mutex_lock(&dp->attention_lock); hpd = dp->cable_connected; mutex_unlock(&dp->attention_lock); return hpd; } static bool mdss_dp_sink_audio_supp(struct mdss_dp_drv_pdata *dp) { return hdmi_edid_is_audio_supported( dp->panel_data.panel_info.edid_data); } static int dp_audio_info_setup(struct platform_device *pdev, struct msm_ext_disp_audio_setup_params *params) { int rc = 0; struct mdss_dp_drv_pdata *dp_ctrl = dp_get_data(pdev); if (!dp_ctrl || !params) { pr_err("invalid input\n"); rc = -ENODEV; goto end; } if (!dp_ctrl->power_on) { pr_debug("DP is already power off\n"); goto end; } mdss_dp_audio_setup_sdps(&dp_ctrl->ctrl_io, params->num_of_channels); mdss_dp_config_audio_acr_ctrl(&dp_ctrl->ctrl_io, dp_ctrl->link_rate); mdss_dp_set_safe_to_exit_level(&dp_ctrl->ctrl_io, dp_ctrl->lane_cnt); mdss_dp_audio_enable(&dp_ctrl->ctrl_io, true); end: return rc; } /* dp_audio_info_setup */ static int dp_get_audio_edid_blk(struct platform_device *pdev, struct msm_ext_disp_audio_edid_blk *blk) { struct mdss_dp_drv_pdata *dp = dp_get_data(pdev); int rc = 0; if (!dp) { DEV_ERR("%s: invalid input\n", __func__); return -ENODEV; } rc = hdmi_edid_get_audio_blk (dp->panel_data.panel_info.edid_data, blk); if (rc) DEV_ERR("%s:edid_get_audio_blk failed\n", __func__); return rc; } /* dp_get_audio_edid_blk */ static void dp_audio_teardown_done(struct platform_device *pdev) { struct mdss_dp_drv_pdata *dp = dp_get_data(pdev); if (!dp) { pr_err("invalid input\n"); return; } if (!dp->power_on) { pr_err("DP is already power off\n"); return; } mdss_dp_audio_enable(&dp->ctrl_io, false); /* Make sure the DP audio engine is disabled */ wmb(); pr_debug("audio engine disabled\n"); } /* dp_audio_teardown_done */ static int dp_get_intf_id(struct platform_device *pdev) { struct mdss_dp_drv_pdata *dp = dp_get_data(pdev); if (!dp) { pr_err("%s: invalid DP data\n", __func__); return -ENODEV; } return EXT_DISPLAY_TYPE_DP; } static int dp_audio_ack_done(struct platform_device *pdev, u32 ack) { int rc = 0; struct mdss_dp_drv_pdata *dp = dp_get_data(pdev); if (!dp) { pr_err("%s: invalid dp data\n", __func__); return -ENODEV; } pr_debug("acknowledging audio (%d)\n", ack); return rc; } static int dp_audio_codec_ready(struct platform_device *pdev) { int rc = 0; struct mdss_dp_drv_pdata *dp = dp_get_data(pdev); if (!dp) { pr_err("%s: invalid dp data\n", __func__); return -ENODEV; } return rc; } static int mdss_dp_init_ext_disp(struct mdss_dp_drv_pdata *dp) { int ret = 0; struct msm_ext_disp_init_data *ext; struct msm_ext_disp_audio_codec_ops *ops; struct device_node *pd_np; const char *phandle = "qcom,msm_ext_disp"; if (!dp) { pr_err("%s: invalid input\n", __func__); ret = -ENODEV; goto end; } ext = &dp->ext_audio_data; ops = &ext->codec_ops; ext->codec.type = EXT_DISPLAY_TYPE_DP; ext->codec.ctrl_id = 0; ext->codec.stream_id = 0; ext->pdev = dp->pdev; ext->intf_data = &dp->audio_data; ops->audio_info_setup = dp_audio_info_setup; ops->get_audio_edid_blk = dp_get_audio_edid_blk; ops->cable_status = dp_get_cable_status; ops->get_intf_id = dp_get_intf_id; ops->teardown_done = dp_audio_teardown_done; ops->acknowledge = dp_audio_ack_done; ops->ready = dp_audio_codec_ready; if (!dp->pdev->dev.of_node) { pr_err("%s cannot find dp dev.of_node\n", __func__); ret = -ENODEV; goto end; } pd_np = of_parse_phandle(dp->pdev->dev.of_node, phandle, 0); if (!pd_np) { pr_err("%s cannot find %s dev\n", __func__, phandle); ret = -ENODEV; goto end; } dp->ext_pdev = of_find_device_by_node(pd_np); if (!dp->ext_pdev) { pr_err("%s cannot find %s pdev\n", __func__, phandle); ret = -ENODEV; goto end; } ret = msm_ext_disp_register_intf(dp->ext_pdev, ext); if (ret) pr_err("%s: failed to register disp\n", __func__); end: return ret; } static u32 mdss_dp_get_bpp(struct mdss_dp_drv_pdata *dp) { u32 bpp; u32 bit_depth; /* * Set bpp value based on whether a test video pattern is requested. * For test pattern, the test data has the bit depth per color * component. Otherwise, set it based on EDID. */ if (dp->override_config || mdss_dp_is_video_pattern_requested(dp)) bit_depth = dp->test_data.test_bit_depth; else bit_depth = dp->edid.color_depth; if (!mdss_dp_is_test_bit_depth_valid(bit_depth)) { pr_debug("invalid bit_depth=%d. fall back to default\n", bit_depth); bit_depth = DP_TEST_BIT_DEPTH_8; /* default to 24bpp */ } bpp = mdss_dp_test_bit_depth_to_bpp(bit_depth); return bpp; } static u32 mdss_dp_get_colorimetry_config(struct mdss_dp_drv_pdata *dp) { u32 cc; enum dynamic_range dr; /* unless a video pattern CTS test is ongoing, use CEA_VESA */ if (mdss_dp_is_video_pattern_requested(dp)) dr = dp->test_data.test_dyn_range; else dr = DP_DYNAMIC_RANGE_RGB_VESA; /* Only RGB_VESA nd RGB_CEA supported for now */ switch (dr) { case DP_DYNAMIC_RANGE_RGB_CEA: cc = BIT(3); break; case DP_DYNAMIC_RANGE_RGB_VESA: default: cc = 0; } return cc; } static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic) { struct mdss_panel_info *pinfo; struct msm_hdmi_mode_timing_info timing = {0}; u32 ret; if (!dp_drv) { DEV_ERR("invalid input\n"); return -EINVAL; } pinfo = &dp_drv->panel_data.panel_info; if (vic != HDMI_VFRMT_UNKNOWN) { ret = hdmi_get_supported_mode(&timing, NULL, vic); if (ret || !timing.supported || !pinfo) { DEV_ERR("%s: invalid timing data\n", __func__); return -EINVAL; } } else { pr_debug("reset panel info to zeroes\n"); } dp_drv->vic = vic; pinfo->xres = timing.active_h; pinfo->yres = timing.active_v; pinfo->clk_rate = timing.pixel_freq * 1000; pinfo->lcdc.h_back_porch = timing.back_porch_h; pinfo->lcdc.h_front_porch = timing.front_porch_h; pinfo->lcdc.h_pulse_width = timing.pulse_width_h; pinfo->lcdc.h_active_low = timing.active_low_h; pinfo->lcdc.v_back_porch = timing.back_porch_v; pinfo->lcdc.v_front_porch = timing.front_porch_v; pinfo->lcdc.v_pulse_width = timing.pulse_width_v; pinfo->lcdc.v_active_low = timing.active_low_v; pinfo->type = DP_PANEL; pinfo->pdest = DISPLAY_4; pinfo->wait_cycle = 0; pinfo->fb_num = 1; pinfo->lcdc.border_clr = 0; /* blk */ pinfo->lcdc.underflow_clr = 0xff; /* blue */ pinfo->lcdc.hsync_skew = 0; pinfo->is_pluggable = true; pinfo->bpp = mdss_dp_get_bpp(dp_drv); pr_debug("bpp=%d\n", pinfo->bpp); pr_debug("update res. vic= %d, pclk_rate = %llu\n", dp_drv->vic, pinfo->clk_rate); return 0; } /* dp_init_panel_info */ /** * mdss_dp_get_lane_mapping() - returns lane mapping based on given orientation * @orientation: usb plug orientation * @lane_map: the configured lane mapping * * Returns 0 when the lane mapping is successfully determined based on the * given usb plug orientation. */ static int mdss_dp_get_lane_mapping(struct mdss_dp_drv_pdata *dp, enum plug_orientation orientation, char *lane_map) { int ret = 0, i = 0, j = 0; pr_debug("enter: orientation = %d\n", orientation); if (!lane_map) { pr_err("invalid lane map input"); ret = -EINVAL; goto exit; } /* For flip case, swap phy lanes with ML0 and ML3, ML1 and ML2 */ if (orientation == ORIENTATION_CC2) { for (i = 0; i < DP_MAX_PHY_LN; i++) { if (dp->l_map[i] == DP_ML0) { for (j = 0; j < DP_MAX_PHY_LN; j++) { if (dp->l_map[j] == DP_ML3) { lane_map[i] = DP_ML3; lane_map[j] = DP_ML0; break; } } } else if (dp->l_map[i] == DP_ML1) { for (j = 0; j < DP_MAX_PHY_LN; j++) { if (dp->l_map[j] == DP_ML2) { lane_map[i] = DP_ML2; lane_map[j] = DP_ML1; break; } } } } } else { /* Normal orientation */ for (i = 0; i < DP_MAX_PHY_LN; i++) lane_map[i] = dp->l_map[i]; } pr_debug("lane0 = %d, lane1 = %d, lane2 =%d, lane3 =%d\n", lane_map[0], lane_map[1], lane_map[2], lane_map[3]); exit: return ret; } static u32 mdss_dp_calc_max_pclk_rate(struct mdss_dp_drv_pdata *dp) { u32 bpp = mdss_dp_get_bpp(dp); u32 max_link_rate_khz = dp->dpcd.max_link_rate * (DP_LINK_RATE_MULTIPLIER / 100); u32 max_data_rate_khz = dp->dpcd.max_lane_count * max_link_rate_khz * 8 / 10; u32 max_pclk_rate_khz = max_data_rate_khz / bpp; pr_debug("bpp=%d, max_lane_cnt=%d, max_link_rate=%dKHz\n", bpp, dp->dpcd.max_lane_count, max_link_rate_khz); pr_debug("max_data_rate=%dKHz, max_pclk_rate=%dKHz\n", max_data_rate_khz, max_pclk_rate_khz); return max_pclk_rate_khz; } static void mdss_dp_set_clock_rate(struct mdss_dp_drv_pdata *dp, char *name, u32 rate) { u32 num = dp->power_data[DP_CTRL_PM].num_clk; struct dss_clk *cfg = dp->power_data[DP_CTRL_PM].clk_config; while (num && strcmp(cfg->clk_name, name)) { num--; cfg++; } if (num) cfg->rate = rate; else pr_err("%s clock could not be set with rate %d\n", name, rate); } /** * mdss_dp_enable_mainlink_clocks() - enables Display Port main link clocks * @dp: Display Port Driver data * * Returns 0 when the main link clocks are successfully enabled. */ static int mdss_dp_enable_mainlink_clocks(struct mdss_dp_drv_pdata *dp) { int ret = 0; if (dp->pixel_clk_rcg && dp->pixel_parent) clk_set_parent(dp->pixel_clk_rcg, dp->pixel_parent); if (dp->link_clks_on) { pr_debug("link clocks already on\n"); return ret; } if (dp->pixel_parent && dp->pixel_clk_two_div && dp->pixel_clk_four_div) { if (dp->link_rate == DP_LINK_RATE_540) clk_set_parent(dp->pixel_parent, dp->pixel_clk_four_div); else clk_set_parent(dp->pixel_parent, dp->pixel_clk_two_div); } mdss_dp_set_clock_rate(dp, "ctrl_link_clk", (dp->link_rate * DP_LINK_RATE_MULTIPLIER) / DP_KHZ_TO_HZ); mdss_dp_set_clock_rate(dp, "ctrl_crypto_clk", DP_CRYPTO_CLK_RATE_KHZ); dp->pixel_rate = dp->panel_data.panel_info.clk_rate; mdss_dp_set_clock_rate(dp, "ctrl_pixel_clk", (dp->pixel_rate / DP_KHZ_TO_HZ)); ret = mdss_dp_clk_ctrl(dp, DP_CTRL_PM, true); if (ret) { pr_err("Unabled to start link clocks\n"); ret = -EINVAL; } return ret; } /** * mdss_dp_disable_mainlink_clocks() - disables Display Port main link clocks * @dp: Display Port Driver data */ static void mdss_dp_disable_mainlink_clocks(struct mdss_dp_drv_pdata *dp_drv) { if (!dp_drv->link_clks_on) { pr_debug("link clocks already off\n"); return; } mdss_dp_clk_ctrl(dp_drv, DP_CTRL_PM, false); } /** * mdss_dp_configure_source_params() - configures DP transmitter source params * @dp: Display Port Driver data * @lane_map: usb port lane mapping * * Configures the DP transmitter source params including details such as lane * configuration, output format and sink/panel timing information. */ static void mdss_dp_configure_source_params(struct mdss_dp_drv_pdata *dp, char *lane_map) { mdss_dp_ctrl_lane_mapping(&dp->ctrl_io, lane_map); mdss_dp_fill_link_cfg(dp); mdss_dp_mainlink_ctrl(&dp->ctrl_io, true); mdss_dp_config_ctrl(dp); mdss_dp_config_misc(dp, mdss_dp_bpp_to_test_bit_depth(mdss_dp_get_bpp(dp)), mdss_dp_get_colorimetry_config(dp)); mdss_dp_sw_config_msa(dp); mdss_dp_timing_cfg(&dp->ctrl_io, &dp->panel_data.panel_info); } /** * mdss_dp_setup_main_link() - initiates training of DP main link * @dp: Display Port Driver data * @train: specify if link training should be done or not * * Initiates training of the DP main link and checks the state of the main * link after the training is complete. * * Return: error code. -EINVAL if any invalid data or -EAGAIN if retraining * is required. */ static int mdss_dp_setup_main_link(struct mdss_dp_drv_pdata *dp, bool train) { int ret = 0; bool mainlink_ready = false; pr_debug("enter\n"); mdss_dp_mainlink_ctrl(&dp->ctrl_io, true); ret = mdss_dp_aux_send_psm_request(dp, false); if (ret) { pr_err("Failed to exit low power mode, rc=%d\n", ret); goto end; } reinit_completion(&dp->video_comp); if (mdss_dp_is_phy_test_pattern_requested(dp)) goto end; if (!train) goto send_video; /* * As part of previous calls, DP controller state might have * transitioned to PUSH_IDLE. In order to start transmitting a link * training pattern, we have to first to a DP software reset. */ mdss_dp_ctrl_reset(&dp->ctrl_io); ret = mdss_dp_link_train(dp); if (ret) goto end; /* * Skip the transfer unit setup and the routine to wait for the * video ready interrupt as link training tests do not require * video frames to be sent. */ if (mdss_dp_is_link_training_requested(dp)) goto end; send_video: /* * Set up transfer unit values and set controller state to send * video. */ mdss_dp_setup_tr_unit(&dp->ctrl_io, dp->link_rate, dp->lane_cnt, dp->vic, &dp->panel_data.panel_info); mdss_dp_state_ctrl(&dp->ctrl_io, ST_SEND_VIDEO); mdss_dp_wait4video_ready(dp); mainlink_ready = mdss_dp_mainlink_ready(dp); pr_debug("mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); end: return ret; } static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv, bool lt_needed) { int ret = 0; char ln_map[4]; bool connected; mutex_lock(&dp_drv->attention_lock); connected = dp_drv->cable_connected; mutex_unlock(&dp_drv->attention_lock); /* * If DP cable disconnected, Avoid link training or turning on DP Path */ if (!connected) { pr_err("DP sink not connected\n"); return -EINVAL; } /* wait until link training is completed */ pr_debug("enter, lt_needed=%s\n", lt_needed ? "true" : "false"); do { if (ret == -EAGAIN) mdss_dp_mainlink_ctrl(&dp_drv->ctrl_io, false); mutex_lock(&dp_drv->train_mutex); dp_init_panel_info(dp_drv, dp_drv->vic); ret = mdss_dp_get_lane_mapping(dp_drv, dp_drv->orientation, ln_map); if (ret) goto exit_loop; mdss_dp_phy_share_lane_config(&dp_drv->phy_io, dp_drv->orientation, dp_drv->dpcd.max_lane_count, dp_drv->phy_reg_offset); if (lt_needed) { /* * Diasable and re-enable the mainlink clock since the * link clock might have been adjusted as part of the * link maintenance. */ if (!mdss_dp_is_phy_test_pattern_requested(dp_drv)) mdss_dp_disable_mainlink_clocks(dp_drv); ret = mdss_dp_enable_mainlink_clocks(dp_drv); if (ret) goto exit_loop; } mdss_dp_configure_source_params(dp_drv, ln_map); reinit_completion(&dp_drv->idle_comp); dp_drv->power_on = true; ret = mdss_dp_setup_main_link(dp_drv, lt_needed); if (ret) { if (ret == -ENODEV || ret == -EINVAL) { pr_err("main link setup failed\n"); mutex_unlock(&dp_drv->train_mutex); return ret; } } exit_loop: mutex_unlock(&dp_drv->train_mutex); } while (ret == -EAGAIN); pr_debug("end\n"); /* * Send a connect notification to clients except when processing link * training and electrical compliance tests. There is no need to send * a notification in these testing use cases as there is no * expectation of receiving a video signal as part of the test. */ if (!mdss_dp_is_phy_test_pattern_requested(dp_drv) && !mdss_dp_is_link_training_requested(dp_drv)) mdss_dp_notify_clients(dp_drv, NOTIFY_CONNECT_IRQ_HPD); return ret; } static int mdss_dp_on_hpd(struct mdss_dp_drv_pdata *dp_drv) { int ret = 0; char ln_map[4]; /* wait until link training is completed */ mutex_lock(&dp_drv->train_mutex); pr_debug("Enter++ cont_splash=%d\n", dp_drv->cont_splash); if (dp_drv->cont_splash) { mdss_dp_aux_ctrl(&dp_drv->ctrl_io, true); goto link_training; } ret = mdss_dp_clk_ctrl(dp_drv, DP_CORE_PM, true); if (ret) { pr_err("Unabled to start core clocks\n"); goto exit; } mdss_dp_hpd_configure(&dp_drv->ctrl_io, true); ret = mdss_dp_get_lane_mapping(dp_drv, dp_drv->orientation, ln_map); if (ret) goto exit; dp_init_panel_info(dp_drv, dp_drv->new_vic); dp_drv->link_rate = mdss_dp_gen_link_clk(dp_drv); if (!dp_drv->link_rate) { pr_err("Unable to configure required link rate\n"); mdss_dp_clk_ctrl(dp_drv, DP_CORE_PM, false); ret = -EINVAL; goto exit; } mdss_dp_phy_share_lane_config(&dp_drv->phy_io, dp_drv->orientation, dp_drv->dpcd.max_lane_count, dp_drv->phy_reg_offset); ret = mdss_dp_enable_mainlink_clocks(dp_drv); if (ret) goto exit; reinit_completion(&dp_drv->idle_comp); mdss_dp_configure_source_params(dp_drv, ln_map); link_training: dp_drv->power_on = true; while (-EAGAIN == mdss_dp_setup_main_link(dp_drv, true)) { pr_debug("MAIN LINK TRAINING RETRY\n"); mdss_dp_mainlink_ctrl(&dp_drv->ctrl_io, false); /* Disable DP mainlink clocks */ mdss_dp_disable_mainlink_clocks(dp_drv); /* Enable DP mainlink clocks with reduced link rate */ mdss_dp_enable_mainlink_clocks(dp_drv); } dp_drv->cont_splash = 0; dp_drv->power_on = true; pr_debug("End-\n"); exit: mutex_unlock(&dp_drv->train_mutex); return ret; } static int mdss_dp_on(struct mdss_panel_data *pdata) { struct mdss_dp_drv_pdata *dp_drv = NULL; bool hpd; int rc = 0; if (!pdata) { pr_err("Invalid input data\n"); return -EINVAL; } dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); mutex_lock(&dp_drv->attention_lock); hpd = dp_drv->cable_connected; mutex_unlock(&dp_drv->attention_lock); /* In case of device coming out of PM_SUSPEND, there can be * a corner case where the sink is turned off or the DP cable * is disconnected almost at the same time as userspace triggering * unblank. This can cause the UNBLANK call to be still triggered * before the disconnect event is notified to the userspace. * Avoid turning ON DP path in such cases. */ if (!hpd || !dp_drv->alt_mode.dp_status.hpd_high) { pr_err("DP sink not connected\n"); return -EINVAL; } /* * If the link already active, then nothing needs to be done here. * However, it is possible that the the power_on flag could be * set to true but we would still need to initialize the DP host. * An example of this use-case is when a multiport dongle is connected * and subsequently the downstream sink is disconnected. This would * only go through the IRQ HPD path where we tear down the link but * the power_on flag remains set to true. When the downstream sink * is subsequently connected again, we need to re-initialize DP * host */ if (dp_drv->power_on && (dp_drv->new_vic && (dp_drv->new_vic == dp_drv->vic))) { pr_debug("Link already setup, return\n"); return 0; } /* * During device suspend, host_deinit() is called * to release DP resources. PM_RESUME can be * called for any module wake-up. To avoid multiple host * init/deinit during unrelated resume/suspend events, * add host initialization call before DP power-on. */ if (!dp_drv->dp_initialized) { rc = mdss_dp_host_init(pdata); if (rc < 0) return rc; } return mdss_dp_on_hpd(dp_drv); } static bool mdss_dp_is_ds_bridge(struct mdss_dp_drv_pdata *dp) { return dp->dpcd.downstream_port.dsp_present; } static bool mdss_dp_is_ds_bridge_sink_count_zero(struct mdss_dp_drv_pdata *dp) { return (mdss_dp_is_ds_bridge(dp) && (dp->sink_count.count == 0)); } static bool mdss_dp_is_ds_bridge_no_local_edid(struct mdss_dp_drv_pdata *dp) { return (mdss_dp_is_ds_bridge_sink_count_zero(dp) && !(dp->dpcd.flags & DPCD_PORT_0_EDID_PRESENTED)); } static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv) { if (!dp_drv->power_on) { pr_debug("panel already powered off\n"); return 0; } /* wait until link training is completed */ mutex_lock(&dp_drv->train_mutex); pr_debug("start\n"); mdss_dp_mainlink_ctrl(&dp_drv->ctrl_io, false); mdss_dp_audio_enable(&dp_drv->ctrl_io, false); /* Make sure DP mainlink and audio engines are disabled */ wmb(); /* * If downstream device is a brige which no longer has any * downstream devices connected to it, then we should reset * the current panel info */ if (mdss_dp_is_ds_bridge_sink_count_zero(dp_drv)) dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); mutex_unlock(&dp_drv->train_mutex); pr_debug("end\n"); return 0; } static int mdss_dp_off_hpd(struct mdss_dp_drv_pdata *dp_drv) { if (!dp_drv->power_on) { pr_debug("panel already powered off\n"); return 0; } /* wait until link training is completed */ mutex_lock(&dp_drv->train_mutex); pr_debug("Entered++, cont_splash=%d\n", dp_drv->cont_splash); mdss_dp_mainlink_ctrl(&dp_drv->ctrl_io, false); mdss_dp_aux_ctrl(&dp_drv->ctrl_io, false); mdss_dp_audio_enable(&dp_drv->ctrl_io, false); mdss_dp_host_deinit(dp_drv); dp_drv->power_on = false; dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN); mdss_dp_reset_test_data(dp_drv); mdss_dp_reset_sink_count(dp_drv); dp_drv->prev_sink_count = dp_drv->sink_count; mutex_unlock(&dp_drv->train_mutex); pr_debug("DP off done\n"); return 0; } static int mdss_dp_off(struct mdss_panel_data *pdata) { struct mdss_dp_drv_pdata *dp = NULL; dp = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); if (!dp) { pr_err("Invalid input data\n"); return -EINVAL; } if (dp->hpd_irq_on) return mdss_dp_off_irq(dp); else return mdss_dp_off_hpd(dp); } static struct mdss_dp_drv_pdata *dp_get_data(struct platform_device *pdev) { struct msm_ext_disp_data *ext_data; void *audio_data; if (!pdev) { pr_err("invalid input\n"); return ERR_PTR(-ENODEV); } ext_data = platform_get_drvdata(pdev); if (!ext_data) { pr_err("invalid ext disp data\n"); return ERR_PTR(-EINVAL); } audio_data = ext_data->intf_data; if (!audio_data) { pr_err("invalid intf data\n"); return ERR_PTR(-EINVAL); } return container_of(audio_data, struct mdss_dp_drv_pdata, audio_data); } static int mdss_dp_send_audio_notification( struct mdss_dp_drv_pdata *dp, u32 state) { int ret = 0; u32 flags = 0; struct msm_ext_disp_init_data *ext; if (!dp) { pr_err("invalid input\n"); ret = -EINVAL; goto end; } ext = &dp->ext_audio_data; if (!ext || !ext->intf_ops.audio_config || !ext->intf_ops.audio_notify) { pr_err("invalid ext audio ops\n"); return -EINVAL; } if (mdss_dp_sink_audio_supp(dp) || dp->audio_test_req) { dp->audio_test_req = false; flags |= MSM_EXT_DISP_HPD_AUDIO; pr_debug("sending audio notification = %d, flags = %d\n", state, flags); if (state == EXT_DISPLAY_CABLE_CONNECT) { ext->intf_ops.audio_config(dp->ext_pdev, &ext->codec, state); ret = ext->intf_ops.audio_notify( dp->ext_pdev, &ext->codec, state); } else if (state == EXT_DISPLAY_CABLE_DISCONNECT) { ret = ext->intf_ops.audio_notify( dp->ext_pdev, &ext->codec, state); ext->intf_ops.audio_config(dp->ext_pdev, &ext->codec, state); } } else { pr_debug("sink does not support audio\n"); } end: return ret; } static int mdss_dp_send_video_notification( struct mdss_dp_drv_pdata *dp, int val) { char name[DP_STRING_SIZE], status[DP_STRING_SIZE]; char *envp[3]; int ret; if (!dp) { DEV_ERR("%s: invalid dp data\n", __func__); return -EINVAL; } if (dp->hpd_notify_state == !!val) { pr_debug("%s: no change in HPD state\n", __func__); return -EINVAL; } dp->hpd_notify_state = !!val; snprintf(name, DP_STRING_SIZE, "name=%s", "DP"); snprintf(status, DP_STRING_SIZE, "STATUS=%d", val ? 1 : 0); pr_debug("[%s]:[%s]\n", name, status); pr_debug("sending video notification = %d\n", val); envp[0] = name; envp[1] = status; envp[2] = NULL; ret = kobject_uevent_env(dp->kobj, KOBJ_CHANGE, envp); return ret; } static void mdss_dp_set_default_resolution(struct mdss_dp_drv_pdata *dp) { hdmi_edid_set_video_resolution(dp->panel_data.panel_info.edid_data, DEFAULT_VIDEO_RESOLUTION, true); } static void mdss_dp_set_default_link_parameters(struct mdss_dp_drv_pdata *dp) { const int default_max_link_rate = 0x6; const int default_max_lane_count = 1; dp->dpcd.max_lane_count = default_max_lane_count; dp->dpcd.max_link_rate = default_max_link_rate; pr_debug("max_link_rate = 0x%x, max_lane_count= 0x%x\n", dp->dpcd.max_link_rate, dp->dpcd.max_lane_count); } static int mdss_dp_edid_init(struct mdss_panel_data *pdata) { struct mdss_dp_drv_pdata *dp_drv = NULL; struct hdmi_edid_init_data edid_init_data = {NULL}; void *edid_data; if (!pdata) { pr_err("Invalid input data\n"); return -EINVAL; } dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); edid_init_data.kobj = dp_drv->kobj; edid_init_data.max_pclk_khz = dp_drv->max_pclk_khz; edid_init_data.yc420_support = false; edid_data = hdmi_edid_init(&edid_init_data); if (!edid_data) { DEV_ERR("%s: edid init failed\n", __func__); return -ENODEV; } dp_drv->panel_data.panel_info.edid_data = edid_data; /* initialize EDID buffer pointers */ dp_drv->edid_buf = edid_init_data.buf; dp_drv->edid_buf_size = edid_init_data.buf_size; mdss_dp_set_default_resolution(dp_drv); return 0; } static int mdss_dp_host_init(struct mdss_panel_data *pdata) { struct mdss_dp_drv_pdata *dp_drv = NULL; int ret = 0; if (!pdata) { pr_err("Invalid input data\n"); return -EINVAL; } dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); if (dp_drv->dp_initialized) { pr_debug("%s: host init done already\n", __func__); return 0; } ret = mdss_dp_regulator_ctrl(dp_drv, true); if (ret) { pr_err("failed to enable regulators\n"); goto vreg_error; } dp_drv->orientation = usbpd_get_plug_orientation(dp_drv->pd); if (dp_drv->orientation == ORIENTATION_NONE) { pr_err("DP cable might be disconnected\n"); ret = -EINVAL; goto orientation_error; } dp_drv->aux_sel_gpio_output = 0; if (dp_drv->orientation == ORIENTATION_CC2) dp_drv->aux_sel_gpio_output = 1; pr_debug("orientation = %d, aux_sel_gpio_output = %d\n", dp_drv->orientation, dp_drv->aux_sel_gpio_output); mdss_dp_pinctrl_set_state(dp_drv, true); mdss_dp_config_gpios(dp_drv, true); ret = mdss_dp_clk_ctrl(dp_drv, DP_CORE_PM, true); if (ret) { pr_err("Unabled to start core clocks\n"); goto clk_error; } mdss_dp_aux_init(dp_drv); mdss_dp_phy_initialize(dp_drv); mdss_dp_ctrl_reset(&dp_drv->ctrl_io); mdss_dp_phy_reset(&dp_drv->ctrl_io); mdss_dp_aux_reset(&dp_drv->ctrl_io); mdss_dp_aux_set_limits(&dp_drv->ctrl_io); mdss_dp_aux_ctrl(&dp_drv->ctrl_io, true); pr_debug("Ctrl_hw_rev =0x%x, phy hw_rev =0x%x\n", mdss_dp_get_ctrl_hw_version(&dp_drv->ctrl_io), mdss_dp_get_phy_hw_version(&dp_drv->phy_io)); mdss_dp_reset_phy_config_indices(dp_drv); mdss_dp_phy_aux_setup(dp_drv); mdss_dp_irq_enable(dp_drv); dp_drv->dp_initialized = true; return 0; clk_error: mdss_dp_config_gpios(dp_drv, false); orientation_error: mdss_dp_regulator_ctrl(dp_drv, false); vreg_error: return ret; } /** * mdss_dp_host_deinit() - Uninitialize DP controller * @dp: Display Port Driver data * * Perform required steps to uninitialize DP controller * and its resources. */ static int mdss_dp_host_deinit(struct mdss_dp_drv_pdata *dp) { if (!dp) { pr_err("Invalid input data\n"); return -EINVAL; } if (!dp->dp_initialized) { pr_debug("%s: host deinit done already\n", __func__); return 0; } mdss_dp_irq_disable(dp); mdss_dp_config_gpios(dp, false); mdss_dp_pinctrl_set_state(dp, false); /* * The global reset will need DP link ralated clocks to be * running. Add the global reset just before disabling the * link clocks and core clocks. */ mdss_dp_ctrl_reset(&dp->ctrl_io); /* Make sure DP is disabled before clk disable */ wmb(); mdss_dp_disable_mainlink_clocks(dp); mdss_dp_clk_ctrl(dp, DP_CORE_PM, false); mdss_dp_regulator_ctrl(dp, false); dp->dp_initialized = false; pr_debug("Host deinitialized successfully\n"); return 0; } /** * mdss_dp_notify_clients() - notifies DP clients of cable connection * @dp: Display Port Driver data * @status: HPD notification status requested * * This function will send a notification to display/audio clients of change * in DP connection status. */ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, enum notification_status status) { int ret = 0; bool notify = false; bool connect; pr_debug("beginning notification\n"); if (status == dp->hpd_notification_status) { pr_debug("No change in status %s --> %s\n", mdss_dp_notification_status_to_string(status), mdss_dp_notification_status_to_string( dp->hpd_notification_status)); goto end; } switch (status) { case NOTIFY_CONNECT_IRQ_HPD: if (dp->hpd_notification_status != NOTIFY_DISCONNECT_IRQ_HPD) goto invalid_request; notify = true; connect = true; break; case NOTIFY_CONNECT: if (dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) goto invalid_request; notify = true; connect = true; break; case NOTIFY_DISCONNECT: /* * Userspace triggers a disconnect event on boot up, this must * not be processed as there was no previously connected sink * device. */ if (dp->hpd_notification_status == NOTIFY_UNKNOWN) goto invalid_request; if (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD) { /* * Just in case if NOTIFY_DISCONNECT_IRQ_HPD is timedout */ if (dp->power_on) mdss_dp_state_ctrl(&dp->ctrl_io, ST_PUSH_IDLE); /* * user modules already turned off. Need to explicitly * turn off DP core here. */ mdss_dp_off_hpd(dp); } else { notify = true; connect = false; } break; case NOTIFY_DISCONNECT_IRQ_HPD: if (dp->hpd_notification_status == NOTIFY_DISCONNECT) goto invalid_request; notify = true; connect = false; break; default: pr_err("Invalid notification status = %d\n", status); ret = -EINVAL; break; } goto notify; invalid_request: pr_err("Invalid request %s --> %s\n", mdss_dp_notification_status_to_string( dp->hpd_notification_status), mdss_dp_notification_status_to_string(status)); ret = -EINVAL; notify: if (ret || !notify) { pr_debug("not sending notification\n"); goto end; } atomic_set(&dp->notification_pending, 1); if (connect) { mdss_dp_host_init(&dp->panel_data); ret = mdss_dp_send_video_notification(dp, true); } else { mdss_dp_send_audio_notification(dp, false); ret = mdss_dp_send_video_notification(dp, false); } if (!ret) { pr_debug("Successfully sent notification %s --> %s\n", mdss_dp_notification_status_to_string( dp->hpd_notification_status), mdss_dp_notification_status_to_string(status)); } else { pr_err("%s Notification failed\n", mdss_dp_notification_status_to_string(status)); atomic_set(&dp->notification_pending, 0); } end: dp->hpd_notification_status = status; return ret; } static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp) { int ret; u32 max_pclk_khz; pr_debug("start\n"); ret = mdss_dp_dpcd_cap_read(dp); if (ret || !mdss_dp_aux_is_link_rate_valid(dp->dpcd.max_link_rate) || !mdss_dp_aux_is_lane_count_valid(dp->dpcd.max_lane_count)) { if ((ret == -ENODEV) || (ret == EDP_AUX_ERR_TOUT)) { pr_err("DPCD read timedout, skip connect notification\n"); goto end; } /* * If there is an error in parsing DPCD or if DPCD reports * unsupported link parameters then set the default link * parameters and continue to read EDID. */ pr_err("dpcd read failed, set failsafe parameters\n"); mdss_dp_set_default_link_parameters(dp); goto read_edid; } /* * When connected to a multiport adaptor which does not have a * local EDID present, do not attempt to read the EDID. * When connected to a multiport adaptor with no downstream device * connected to it, do not attempt to read the EDID. It is possible * that the adaptor may advertise the presence of local EDID, but it * is not guaranteed to work. */ if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) { if (mdss_dp_is_ds_bridge_no_local_edid(dp)) pr_debug("No local EDID present on DS branch device\n"); pr_info("no downstream devices, skip client notification\n"); goto end; } read_edid: ret = mdss_dp_edid_read(dp); if (ret) { if (ret == -ENODEV) goto end; pr_err("edid read error, setting default resolution\n"); goto notify; } max_pclk_khz = mdss_dp_calc_max_pclk_rate(dp); hdmi_edid_set_max_pclk_rate(dp->panel_data.panel_info.edid_data, min(dp->max_pclk_khz, max_pclk_khz)); if (dp->dpcd_read_required) { pr_debug("reading DPCD with updated AUX config\n"); mdss_dp_dpcd_cap_read(dp); dp->dpcd_read_required = false; } ret = hdmi_edid_parser(dp->panel_data.panel_info.edid_data); if (ret) { pr_err("edid parse failed, setting default resolution\n"); goto notify; } notify: if (ret) { /* set failsafe parameters */ pr_info("falling back to failsafe mode\n"); mdss_dp_set_default_resolution(dp); mdss_dp_set_default_link_parameters(dp); } /* Check if there is a PHY_TEST_PATTERN request when we get HPD high. * Update the DP driver with the test parameters including link rate, * lane count, voltage level, and pre-emphasis level. Do not notify * the userspace of the connection, just power on the DP controller * and mainlink with the new settings. */ if (mdss_dp_is_phy_test_pattern_requested(dp)) { pr_info("PHY_TEST_PATTERN requested by sink\n"); mdss_dp_process_phy_test_pattern_request(dp); pr_info("skip client notification\n"); goto end; } mdss_dp_notify_clients(dp, NOTIFY_CONNECT); end: pr_debug("end\n"); return ret; } static int mdss_dp_check_params(struct mdss_dp_drv_pdata *dp, void *arg) { struct mdss_panel_info *var_pinfo, *pinfo; int rc = 0; int new_vic = -1; if (!dp || !arg) return 0; pinfo = &dp->panel_data.panel_info; var_pinfo = (struct mdss_panel_info *)arg; pr_debug("reconfig xres: %d yres: %d, current xres: %d yres: %d\n", var_pinfo->xres, var_pinfo->yres, pinfo->xres, pinfo->yres); new_vic = hdmi_panel_get_vic(var_pinfo, NULL); if ((new_vic < 0) || (new_vic > HDMI_VFRMT_MAX)) { DEV_ERR("%s: invalid or not supported vic\n", __func__); goto end; } /* * return value of 1 lets mdss know that panel * needs a reconfig due to new resolution and * it will issue close and open subsequently. */ if (new_vic != dp->vic) { rc = 1; DEV_ERR("%s: res change %d ==> %d\n", __func__, dp->vic, new_vic); } dp->new_vic = new_vic; end: return rc; } static inline bool dp_is_hdcp_enabled(struct mdss_dp_drv_pdata *dp_drv) { return dp_drv->hdcp.feature_enabled && (dp_drv->hdcp.hdcp1_present || dp_drv->hdcp.hdcp2_present) && dp_drv->hdcp.ops; } static void mdss_dp_hdcp_cb_work(struct work_struct *work) { struct mdss_dp_drv_pdata *dp; struct delayed_work *dw = to_delayed_work(work); struct hdcp_ops *ops; unsigned char *base; int rc = 0; u32 hdcp_auth_state; dp = container_of(dw, struct mdss_dp_drv_pdata, hdcp_cb_work); base = dp->base; if (dp->hdcp_status == HDCP_STATE_AUTHENTICATING && mdss_dp_is_audio_pattern_requested(dp)) { pr_debug("no hdcp for audio tests\n"); return; } hdcp_auth_state = (dp_read(base + DP_HDCP_STATUS) >> 20) & 0x3; pr_debug("hdcp auth state %d\n", hdcp_auth_state); ops = dp->hdcp.ops; switch (dp->hdcp_status) { case HDCP_STATE_AUTHENTICATING: pr_debug("start authenticaton\n"); dp->dpcd_version = dp->dpcd.minor | (dp->dpcd.major << 8); if (dp->hdcp.ops && dp->hdcp.ops->authenticate) rc = dp->hdcp.ops->authenticate(dp->hdcp.data); break; case HDCP_STATE_AUTHENTICATED: pr_debug("hdcp authenticated\n"); dp->hdcp.auth_state = true; break; case HDCP_STATE_AUTH_FAIL: dp->hdcp.auth_state = false; if (dp->alt_mode.dp_status.hpd_high && dp->power_on) { pr_debug("Reauthenticating\n"); if (ops && ops->reauthenticate) { rc = ops->reauthenticate(dp->hdcp.data); if (rc) pr_err("reauth failed rc=%d\n", rc); } } else { pr_debug("not reauthenticating, cable disconnected\n"); } break; default: break; } } static void mdss_dp_hdcp_cb(void *ptr, enum hdcp_states status) { struct mdss_dp_drv_pdata *dp = ptr; if (!dp) { pr_err("invalid input\n"); return; } dp->hdcp_status = status; if (dp->alt_mode.dp_status.hpd_high) queue_delayed_work(dp->workq, &dp->hdcp_cb_work, HZ/4); } static int mdss_dp_hdcp_init(struct mdss_panel_data *pdata) { struct hdcp_init_data hdcp_init_data = {NULL}; struct mdss_dp_drv_pdata *dp_drv = NULL; struct resource *res; int rc = 0; if (!pdata) { pr_err("Invalid input data\n"); goto error; } dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); res = platform_get_resource_byname(dp_drv->pdev, IORESOURCE_MEM, "dp_ctrl"); if (!res) { pr_err("Error getting dp ctrl resource\n"); rc = -EINVAL; goto error; } hdcp_init_data.phy_addr = res->start; hdcp_init_data.core_io = &dp_drv->ctrl_io; hdcp_init_data.qfprom_io = &dp_drv->qfprom_io; hdcp_init_data.hdcp_io = &dp_drv->hdcp_io; hdcp_init_data.mutex = &dp_drv->hdcp_mutex; hdcp_init_data.sysfs_kobj = dp_drv->kobj; hdcp_init_data.workq = dp_drv->workq; hdcp_init_data.notify_status = mdss_dp_hdcp_cb; hdcp_init_data.cb_data = (void *)dp_drv; hdcp_init_data.sec_access = true; hdcp_init_data.client_id = HDCP_CLIENT_DP; hdcp_init_data.version = &dp_drv->dpcd_version; dp_drv->hdcp.hdcp1 = hdcp_1x_init(&hdcp_init_data); if (IS_ERR_OR_NULL(dp_drv->hdcp.hdcp1)) { pr_err("Error hdcp init\n"); rc = -EINVAL; goto error; } dp_drv->panel_data.panel_info.hdcp_1x_data = dp_drv->hdcp.hdcp1; pr_debug("HDCP 1.3 initialized\n"); dp_drv->hdcp.hdcp2 = dp_hdcp2p2_init(&hdcp_init_data); if (!IS_ERR_OR_NULL(dp_drv->hdcp.hdcp2)) pr_debug("HDCP 2.2 initialized\n"); dp_drv->hdcp.feature_enabled = true; return 0; error: return rc; } static struct mdss_dp_drv_pdata *mdss_dp_get_drvdata(struct device *device) { struct msm_fb_data_type *mfd; struct mdss_panel_data *pd; struct mdss_dp_drv_pdata *dp = NULL; struct fb_info *fbi = dev_get_drvdata(device); if (fbi) { mfd = (struct msm_fb_data_type *)fbi->par; pd = dev_get_platdata(&mfd->pdev->dev); dp = container_of(pd, struct mdss_dp_drv_pdata, panel_data); } return dp; } static ssize_t mdss_dp_rda_connected(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); bool cable_connected; if (!dp) return -EINVAL; mutex_lock(&dp->attention_lock); cable_connected = dp->cable_connected; mutex_unlock(&dp->attention_lock); ret = snprintf(buf, PAGE_SIZE, "%d\n", cable_connected); pr_debug("%d\n", cable_connected); return ret; } static ssize_t mdss_dp_sysfs_wta_s3d_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret, s3d_mode; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { DEV_ERR("%s: invalid input\n", __func__); return -EINVAL; } ret = kstrtoint(buf, 10, &s3d_mode); if (ret) { DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, ret); goto end; } dp->s3d_mode = s3d_mode; ret = strnlen(buf, PAGE_SIZE); DEV_DBG("%s: %d\n", __func__, dp->s3d_mode); end: return ret; } static ssize_t mdss_dp_sysfs_rda_s3d_mode(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { DEV_ERR("%s: invalid input\n", __func__); return -EINVAL; } ret = snprintf(buf, PAGE_SIZE, "%d\n", dp->s3d_mode); DEV_DBG("%s: '%d'\n", __func__, dp->s3d_mode); return ret; } static bool mdss_dp_is_test_ongoing(struct mdss_dp_drv_pdata *dp) { return (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD); } /** * mdss_dp_psm_config() - Downstream device uPacket RX Power Management * @dp: Display Port Driver data * * Perform required steps to configure the uPacket RX of a downstream * connected device in a power-save mode. */ static int mdss_dp_psm_config(struct mdss_dp_drv_pdata *dp, bool enable) { int ret = 0; if (!dp) { pr_err("invalid data\n"); return -EINVAL; } if (dp->psm_enabled == enable) { pr_debug("No change in psm requested\n"); goto end; } pr_debug("Power save mode %s requested\n", enable ? "entry" : "exit"); if (enable) { ret = mdss_dp_aux_send_psm_request(dp, true); if (ret) goto end; /* * If this configuration is requested as part of an * automated test, then HPD notification has already been * sent out. Just disable the main-link and turn off DP Tx. * * Otherwise, trigger a complete shutdown of the pipeline. */ if (mdss_dp_is_test_ongoing(dp)) { mdss_dp_mainlink_push_idle(&dp->panel_data); mdss_dp_off_irq(dp); } else { mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT); } } else { /* * If this configuration is requested as part of an * automated test, then just perform a link retraining. * * Otherwise, re-initialize the host and setup the complete * pipeline from scratch by sending a connection notification * to user modules. */ if (mdss_dp_is_test_ongoing(dp)) { mdss_dp_link_maintenance(dp, true); } else { mdss_dp_host_init(&dp->panel_data); mdss_dp_notify_clients(dp, NOTIFY_CONNECT); } } end: pr_debug("Power save mode %s %s\n", dp->psm_enabled ? "entry" : "exit", ret ? "failed" : "successful"); return ret; } static ssize_t mdss_dp_wta_psm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int psm; int rc; ssize_t ret = strnlen(buf, PAGE_SIZE); struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid data\n"); ret = -EINVAL; goto end; } rc = kstrtoint(buf, 10, &psm); if (rc) { pr_err("kstrtoint failed. ret=%d\n", (int)ret); goto end; } rc = mdss_dp_psm_config(dp, psm ? true : false); if (rc) { pr_err("failed to config Power Save Mode\n"); goto end; } end: return ret; } static ssize_t mdss_dp_rda_psm(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid input\n"); return -EINVAL; } ret = snprintf(buf, PAGE_SIZE, "%d\n", dp->psm_enabled ? 1 : 0); pr_debug("psm: %s\n", dp->psm_enabled ? "enabled" : "disabled"); return ret; } static ssize_t mdss_dp_wta_hpd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int hpd, rc; ssize_t ret = strnlen(buf, PAGE_SIZE); struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); bool cable_connected; if (!dp) { pr_err("invalid data\n"); ret = -EINVAL; goto end; } rc = kstrtoint(buf, 10, &hpd); if (rc) { pr_err("kstrtoint failed. ret=%d\n", rc); ret = rc; goto end; } dp->hpd = !!hpd; mutex_lock(&dp->attention_lock); cable_connected = dp->cable_connected; mutex_unlock(&dp->attention_lock); pr_debug("hpd=%d cable_connected=%s\n", dp->hpd, cable_connected ? "true" : "false"); if (dp->hpd && cable_connected) { if (dp->alt_mode.current_state & DP_CONFIGURE_DONE) { mdss_dp_host_init(&dp->panel_data); mdss_dp_process_hpd_high(dp); } else { dp_send_events(dp, EV_USBPD_DISCOVER_MODES); } } else if (!dp->hpd) { mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT); } end: return ret; } static ssize_t mdss_dp_rda_hpd(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid input\n"); return -EINVAL; } ret = snprintf(buf, PAGE_SIZE, "%d\n", dp->hpd); pr_debug("hpd: %d\n", dp->hpd); return ret; } static int mdss_dp_parse_config_value(char const *buf, char const *name, u32 *val) { int ret = 0; char *buf1; char *token; buf1 = strnstr(buf, name, PAGE_SIZE); if (buf1) { buf1 = buf1 + strlen(name); token = strsep(&buf1, " "); if (!token) { pr_err("strsep failed\n"); ret = -EINVAL; goto end; } ret = kstrtou32(token, 10, val); if (ret) { pr_err("kstrtoint failed. ret=%d\n", (int)ret); goto end; } pr_debug("parsed %s(%d)\n", name, *val); } else { ret = -EINVAL; } end: return ret; } static ssize_t mdss_dp_wta_config(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { u32 val; u32 bit_depth; int ret; char const *bpp_key = "bpp="; char const *pattern_type_key = "pattern_type="; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid data\n"); ret = -EINVAL; goto end; } ret = mdss_dp_parse_config_value(buf, bpp_key, &val); if (ret) { pr_debug("%s config not found\n", bpp_key); goto pattern_type; } bit_depth = mdss_dp_bpp_to_test_bit_depth(val); if (!mdss_dp_is_test_bit_depth_valid(bit_depth)) { pr_err("invalid bpp = %d\n", val); } else { dp->test_data.test_bit_depth = bit_depth; if (val != 0) dp->override_config = true; else dp->override_config = false; pr_debug("bpp=%d, test_bit_depth=%d\n", val, dp->test_data.test_bit_depth); } pattern_type: ret = mdss_dp_parse_config_value(buf, pattern_type_key, &val); if (ret) { pr_debug("%s config not found\n", pattern_type_key); goto end; } if (!mdss_dp_is_test_video_pattern_valid(val)) { pr_err("invalid test video pattern = %d\n", val); } else { dp->test_data.test_video_pattern = val; pr_debug("test_video_pattern=%d (%s)\n", dp->test_data.test_video_pattern, mdss_dp_test_video_pattern_to_string( dp->test_data.test_video_pattern)); } end: return count; } static ssize_t mdss_dp_rda_config(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; u32 bpp; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid input\n"); return -EINVAL; } bpp = mdss_dp_get_bpp(dp); ret = snprintf(buf, PAGE_SIZE, "bpp=%d\npattern_type=%d\n", bpp, dp->test_data.test_video_pattern); pr_debug("bpp: %d pattern_type=%d (%s)\n", bpp, dp->test_data.test_video_pattern, mdss_dp_test_video_pattern_to_string( dp->test_data.test_video_pattern)); return ret; } static ssize_t mdss_dp_wta_frame_crc(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; u32 val; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); char const *ctl_crc_key = "ctl_crc_en="; char const *sink_crc_key = "sink_crc_en="; bool ctl_crc_en, sink_crc_en; if (!dp) { pr_err("invalid data\n"); goto end; } if (!dp->power_on) { pr_err("DP controller not powered on\n"); goto end; } ret = mdss_dp_parse_config_value(buf, ctl_crc_key, &val); if (ret) { pr_debug("%s config not found\n", ctl_crc_key); goto sink_crc; } ctl_crc_en = val ? true : false; mdss_dp_config_ctl_frame_crc(dp, ctl_crc_en); sink_crc: ret = mdss_dp_parse_config_value(buf, sink_crc_key, &val); if (ret) { pr_debug("%s config not found\n", sink_crc_key); goto end; } sink_crc_en = val ? true : false; mdss_dp_aux_config_sink_frame_crc(dp, sink_crc_en); end: return count; } static ssize_t mdss_dp_print_crc_values(struct mdss_dp_drv_pdata *dp, char *buf, ssize_t len) { char line[] = "------------------------------"; mdss_dp_read_ctl_frame_crc(dp); mdss_dp_aux_read_sink_frame_crc(dp); return snprintf(buf, PAGE_SIZE, "\t\t|R_Cr\t\t|G_y\t\t|B_Cb\n%s%s\nctl(%s)\t|0x%08x\t|0x%08x\t|0x%08x\nsink(%s)\t|0x%08x\t|0x%08x\t|0x%08x\n", line, line, dp->ctl_crc.en ? "enabled" : "disabled", dp->ctl_crc.r_cr, dp->ctl_crc.g_y, dp->ctl_crc.b_cb, dp->sink_crc.en ? "enabled" : "disabled", dp->sink_crc.r_cr, dp->sink_crc.g_y, dp->sink_crc.b_cb); } static ssize_t mdss_dp_rda_frame_crc(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid input\n"); return -EINVAL; } if (!dp->power_on) { pr_err("DP controller not powered on\n"); return 0; } ret = mdss_dp_print_crc_values(dp, buf, PAGE_SIZE); return ret; } static ssize_t mdss_dp_wta_hdcp_feature(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { u32 hdcp; int rc; ssize_t ret = strnlen(buf, PAGE_SIZE); struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid data\n"); ret = -EINVAL; goto end; } rc = kstrtoint(buf, 10, &hdcp); if (rc) { pr_err("kstrtoint failed. ret=%d\n", rc); ret = rc; goto end; } dp->hdcp.feature_enabled = !!hdcp; pr_debug("hdcp=%d\n", dp->hdcp.feature_enabled); end: return ret; } static ssize_t mdss_dp_rda_hdcp_feature(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t ret; struct mdss_dp_drv_pdata *dp = mdss_dp_get_drvdata(dev); if (!dp) { pr_err("invalid input\n"); return -EINVAL; } ret = snprintf(buf, PAGE_SIZE, "%d\n", dp->hdcp.feature_enabled); pr_debug("hdcp: %d\n", dp->hdcp.feature_enabled); return ret; } static DEVICE_ATTR(connected, 0444, mdss_dp_rda_connected, NULL); static DEVICE_ATTR(s3d_mode, 0644, mdss_dp_sysfs_rda_s3d_mode, mdss_dp_sysfs_wta_s3d_mode); static DEVICE_ATTR(hpd, 0644, mdss_dp_rda_hpd, mdss_dp_wta_hpd); static DEVICE_ATTR(psm, 0644, mdss_dp_rda_psm, mdss_dp_wta_psm); static DEVICE_ATTR(config, 0644, mdss_dp_rda_config, mdss_dp_wta_config); static DEVICE_ATTR(frame_crc, 0644, mdss_dp_rda_frame_crc, mdss_dp_wta_frame_crc); static DEVICE_ATTR(hdcp_feature, 0644, mdss_dp_rda_hdcp_feature, mdss_dp_wta_hdcp_feature); static struct attribute *mdss_dp_fs_attrs[] = { &dev_attr_connected.attr, &dev_attr_s3d_mode.attr, &dev_attr_hpd.attr, &dev_attr_psm.attr, &dev_attr_config.attr, &dev_attr_frame_crc.attr, &dev_attr_hdcp_feature.attr, NULL, }; static struct attribute_group mdss_dp_fs_attrs_group = { .attrs = mdss_dp_fs_attrs, }; static int mdss_dp_sysfs_create(struct mdss_dp_drv_pdata *dp, struct fb_info *fbi) { int rc; if (!dp || !fbi) { pr_err("ivalid input\n"); return -ENODEV; } rc = sysfs_create_group(&fbi->dev->kobj, &mdss_dp_fs_attrs_group); if (rc) { pr_err("failed, rc=%d\n", rc); return rc; } pr_debug("sysfs ceated\n"); return 0; } static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata) { bool cable_connected; struct mdss_dp_drv_pdata *dp_drv = NULL; const int idle_pattern_completion_timeout_ms = 3 * HZ / 100; dp_drv = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); if (!dp_drv) { pr_err("Invalid input data\n"); return; } pr_debug("Entered++\n"); /* wait until link training is completed */ mutex_lock(&dp_drv->train_mutex); if (!dp_drv->power_on) { pr_err("DP Controller not powered on\n"); mutex_unlock(&dp_drv->train_mutex); return; } /* power down the sink if cable is still connected */ mutex_lock(&dp_drv->attention_lock); cable_connected = dp_drv->cable_connected; mutex_unlock(&dp_drv->attention_lock); if (cable_connected && dp_drv->alt_mode.dp_status.hpd_high) { if (mdss_dp_aux_send_psm_request(dp_drv, true)) pr_err("Failed to enter low power mode\n"); } reinit_completion(&dp_drv->idle_comp); mdss_dp_state_ctrl(&dp_drv->ctrl_io, ST_PUSH_IDLE); if (!wait_for_completion_timeout(&dp_drv->idle_comp, idle_pattern_completion_timeout_ms)) pr_warn("PUSH_IDLE pattern timedout\n"); mutex_unlock(&dp_drv->train_mutex); pr_debug("mainlink off done\n"); } static void mdss_dp_update_hdcp_info(struct mdss_dp_drv_pdata *dp) { void *fd = NULL; struct hdcp_ops *ops = NULL; if (!dp) { pr_err("invalid input\n"); return; } if (!dp->hdcp.feature_enabled) { pr_debug("feature not enabled\n"); return; } /* check first if hdcp2p2 is supported */ fd = dp->hdcp.hdcp2; if (fd) ops = dp_hdcp2p2_start(fd); if (ops && ops->feature_supported) dp->hdcp.hdcp2_present = ops->feature_supported(fd); else dp->hdcp.hdcp2_present = false; //hdcp1_check_if_supported_load_app(); if (!dp->hdcp.hdcp2_present) { dp->hdcp.hdcp1_present = false; if (dp->hdcp.hdcp1_present) { fd = dp->hdcp.hdcp1; ops = hdcp_1x_start(fd); } } /* update internal data about hdcp */ if (dp->hdcp.hdcp2_present || dp->hdcp.hdcp1_present) { dp->hdcp.data = fd; dp->hdcp.ops = ops; } else { dp->hdcp.data = NULL; dp->hdcp.ops = NULL; } } /** * mdss_dp_reset_panel_info() - reset the panel_info data * @dp: Display Port Driver data * * This function will reset the panel resolution to * HDMI_VFRMT_UNKNOWN if the sink device is not connected. This will help * to reconfigure the panel resolution during cable connect event. */ static void mdss_dp_reset_panel_info(struct mdss_dp_drv_pdata *dp) { if (dp->suspend_vic != HDMI_VFRMT_UNKNOWN) { dp->suspend_vic = HDMI_VFRMT_UNKNOWN; dp_init_panel_info(dp, dp->suspend_vic); } } static int mdss_dp_event_handler(struct mdss_panel_data *pdata, int event, void *arg) { int rc = 0; struct fb_info *fbi; struct mdss_dp_drv_pdata *dp = NULL; if (!pdata) { pr_err("%s: Invalid input data\n", __func__); return -EINVAL; } pr_debug("event=%s\n", mdss_panel_intf_event_to_string(event)); dp = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); switch (event) { case MDSS_EVENT_UNBLANK: rc = mdss_dp_on(pdata); break; case MDSS_EVENT_PANEL_ON: if (!dp->power_on) { pr_err("DP Controller not powered on\n"); break; } mdss_dp_update_hdcp_info(dp); if (dp_is_hdcp_enabled(dp)) { cancel_delayed_work_sync(&dp->hdcp_cb_work); dp->hdcp_status = HDCP_STATE_AUTHENTICATING; queue_delayed_work(dp->workq, &dp->hdcp_cb_work, HZ / 2); } break; case MDSS_EVENT_POST_PANEL_ON: atomic_set(&dp->notification_pending, 0); complete_all(&dp->notification_comp); break; case MDSS_EVENT_PANEL_OFF: rc = mdss_dp_off(pdata); atomic_set(&dp->notification_pending, 0); complete_all(&dp->notification_comp); break; case MDSS_EVENT_BLANK: if (!dp->power_on) { pr_err("DP Controller not powered on\n"); break; } if (!atomic_read(&dp->notification_pending)) { pr_debug("blank when cable is connected\n"); kthread_park(dp->ev_thread); } if (dp_is_hdcp_enabled(dp)) { dp->hdcp_status = HDCP_STATE_INACTIVE; cancel_delayed_work_sync(&dp->hdcp_cb_work); if (dp->hdcp.ops->off) dp->hdcp.ops->off(dp->hdcp.data); } mdss_dp_mainlink_push_idle(pdata); break; case MDSS_EVENT_FB_REGISTERED: fbi = (struct fb_info *)arg; if (!fbi || !fbi->dev) break; dp->kobj = &fbi->dev->kobj; dp->fb_node = fbi->node; mdss_dp_sysfs_create(dp, fbi); mdss_dp_edid_init(pdata); mdss_dp_hdcp_init(pdata); rc = mdss_dp_init_ext_disp(dp); if (rc) pr_err("failed to initialize ext disp data, ret=%d\n", rc); break; case MDSS_EVENT_CHECK_PARAMS: rc = mdss_dp_check_params(dp, arg); break; case MDSS_EVENT_SUSPEND: /* * Make sure DP host_deinit is called * when DP host is initialized but not * powered ON. * For example, this scenerio happens * when you connect DP sink while the * device is in suspend state. */ if ((!dp->power_on) && (dp->dp_initialized)) { rc = mdss_dp_host_deinit(dp); kthread_park(dp->ev_thread); } /* * For DP suspend/resume use case, CHECK_PARAMS is * not called if the cable status is not changed. * Store the sink resolution in suspend and configure * the resolution during DP resume path. */ if (dp->power_on) dp->suspend_vic = dp->vic; break; case MDSS_EVENT_RESUME: if (dp->suspend_vic != HDMI_VFRMT_UNKNOWN) { dp_init_panel_info(dp, dp->suspend_vic); mdss_dp_reset_sw_state(dp); kthread_unpark(dp->ev_thread); } break; default: pr_debug("unhandled event=%d\n", event); break; } return rc; } static int mdss_dp_device_register(struct mdss_dp_drv_pdata *dp_drv) { int ret; ret = dp_init_panel_info(dp_drv, DEFAULT_VIDEO_RESOLUTION); if (ret) { DEV_ERR("%s: dp_init_panel_info failed\n", __func__); return ret; } dp_drv->panel_data.event_handler = mdss_dp_event_handler; dp_drv->panel_data.panel_info.cont_splash_enabled = dp_drv->cont_splash; ret = mdss_register_panel(dp_drv->pdev, &dp_drv->panel_data); if (ret) { dev_err(&(dp_drv->pdev->dev), "unable to register dp\n"); return ret; } pr_info("dp initialized\n"); return 0; } /* * Retrieve dp Resources */ static int mdss_retrieve_dp_ctrl_resources(struct platform_device *pdev, struct mdss_dp_drv_pdata *dp_drv) { int rc = 0; u32 index; rc = of_property_read_u32(pdev->dev.of_node, "cell-index", &index); if (rc) { dev_err(&pdev->dev, "Cell-index not specified, rc=%d\n", rc); return rc; } rc = msm_dss_ioremap_byname(pdev, &dp_drv->ctrl_io, "dp_ctrl"); if (rc) { pr_err("%d unable to remap dp ctrl resources\n", __LINE__); return rc; } dp_drv->base = (unsigned char *) dp_drv->ctrl_io.base; dp_drv->base_size = dp_drv->ctrl_io.len; rc = msm_dss_ioremap_byname(pdev, &dp_drv->phy_io, "dp_phy"); if (rc) { pr_err("%d unable to remap dp PHY resources\n", __LINE__); return rc; } rc = of_property_read_u32(pdev->dev.of_node, "qcom,phy-register-offset", &dp_drv->phy_reg_offset); if (rc) dp_drv->phy_reg_offset = 0; rc = msm_dss_ioremap_byname(pdev, &dp_drv->tcsr_reg_io, "tcsr_regs"); if (rc) { pr_err("%d unable to remap dp tcsr_reg resources\n", __LINE__); return rc; } if (msm_dss_ioremap_byname(pdev, &dp_drv->dp_cc_io, "dp_mmss_cc")) { pr_err("%d unable to remap dp MMSS_CC resources\n", __LINE__); return rc; } if (msm_dss_ioremap_byname(pdev, &dp_drv->qfprom_io, "qfprom_physical")) pr_warn("unable to remap dp qfprom resources\n"); if (msm_dss_ioremap_byname(pdev, &dp_drv->hdcp_io, "hdcp_physical")) pr_warn("unable to remap dp hdcp resources\n"); pr_debug("DP Driver base=%pK size=%x\n", dp_drv->base, dp_drv->base_size); mdss_debug_register_base("dp", (void *) dp_drv->base, dp_drv->base_size, NULL); return 0; } static void mdss_dp_video_ready(struct mdss_dp_drv_pdata *dp) { pr_debug("dp_video_ready\n"); complete(&dp->video_comp); } static void mdss_dp_idle_patterns_sent(struct mdss_dp_drv_pdata *dp) { pr_debug("idle_patterns_sent\n"); complete(&dp->idle_comp); } static void mdss_dp_do_link_train(struct mdss_dp_drv_pdata *dp) { if (dp->cont_splash) return; mdss_dp_link_train(dp); } static int mdss_dp_event_thread(void *data) { unsigned long flag; u32 todo = 0, config; struct mdss_dp_event_data *ev_data; struct mdss_dp_event *ev; struct mdss_dp_drv_pdata *dp = NULL; if (!data) return -EINVAL; ev_data = (struct mdss_dp_event_data *)data; pr_debug("starting\n"); while (!kthread_should_stop()) { wait_event(ev_data->event_q, (ev_data->pndx != ev_data->gndx) || kthread_should_stop() || kthread_should_park()); if (kthread_should_stop()) return 0; if (kthread_should_park()) { pr_debug("parking event thread\n"); kthread_parkme(); continue; } spin_lock_irqsave(&ev_data->event_lock, flag); ev = &(ev_data->event_list[ev_data->gndx++]); todo = ev->id; dp = ev->dp; ev->id = 0; ev_data->gndx %= MDSS_DP_EVENT_Q_MAX; spin_unlock_irqrestore(&ev_data->event_lock, flag); pr_debug("todo=%s\n", mdss_dp_ev_event_to_string(todo)); switch (todo) { case EV_EDID_READ: mdss_dp_edid_read(dp); break; case EV_DPCD_CAP_READ: mdss_dp_dpcd_cap_read(dp); break; case EV_DPCD_STATUS_READ: mdss_dp_dpcd_status_read(dp); break; case EV_LINK_TRAIN: mdss_dp_do_link_train(dp); break; case EV_VIDEO_READY: mdss_dp_video_ready(dp); break; case EV_IDLE_PATTERNS_SENT: mdss_dp_idle_patterns_sent(dp); break; case EV_USBPD_ATTENTION: mdss_dp_handle_attention(dp); break; case EV_USBPD_DISCOVER_MODES: usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_DISCOVER_MODES, SVDM_CMD_TYPE_INITIATOR, 0x0, NULL, 0x0); break; case EV_USBPD_ENTER_MODE: usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_ENTER_MODE, SVDM_CMD_TYPE_INITIATOR, 0x1, NULL, 0x0); break; case EV_USBPD_EXIT_MODE: usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_EXIT_MODE, SVDM_CMD_TYPE_INITIATOR, 0x1, NULL, 0x0); break; case EV_USBPD_DP_STATUS: config = 0x1; /* DFP_D connected */ usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_STATUS, SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); break; case EV_USBPD_DP_CONFIGURE: config = mdss_dp_usbpd_gen_config_pkt(dp); usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_CONFIGURE, SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); break; default: pr_err("Unknown event:%d\n", todo); } } return 0; } static void dp_send_events(struct mdss_dp_drv_pdata *dp, u32 event) { struct mdss_dp_event *ev; struct mdss_dp_event_data *ev_data = &dp->dp_event; pr_debug("event=%s\n", mdss_dp_ev_event_to_string(event)); spin_lock(&ev_data->event_lock); ev = &ev_data->event_list[ev_data->pndx++]; ev->id = event; ev->dp = dp; ev_data->pndx %= MDSS_DP_EVENT_Q_MAX; wake_up(&ev_data->event_q); spin_unlock(&ev_data->event_lock); } irqreturn_t dp_isr(int irq, void *ptr) { struct mdss_dp_drv_pdata *dp = (struct mdss_dp_drv_pdata *)ptr; unsigned char *base = dp->base; u32 isr1, isr2, mask1; u32 ack; spin_lock(&dp->lock); isr1 = dp_read(base + DP_INTR_STATUS); isr2 = dp_read(base + DP_INTR_STATUS2); pr_debug("isr1=0x%08x, isr2=0x%08x\n", isr1, isr2); mask1 = isr1 & dp->mask1; isr1 &= ~mask1; /* remove masks bit */ ack = isr1 & EDP_INTR_STATUS1; ack <<= 1; /* ack bits */ ack |= mask1; dp_write(base + DP_INTR_STATUS, ack); ack = isr2 & EDP_INTR_STATUS2; ack <<= 1; /* ack bits */ ack |= isr2; dp_write(base + DP_INTR_STATUS2, ack); spin_unlock(&dp->lock); if (isr1 & EDP_INTR_HPD) { isr1 &= ~EDP_INTR_HPD; /* clear */ mdss_dp_host_init(&dp->panel_data); dp_send_events(dp, EV_LINK_TRAIN); } if (isr2 & EDP_INTR_READY_FOR_VIDEO) mdss_dp_video_ready(dp); if (isr2 & EDP_INTR_IDLE_PATTERNs_SENT) mdss_dp_idle_patterns_sent(dp); if (isr1 && dp->aux_cmd_busy) { /* clear DP_AUX_TRANS_CTRL */ dp_write(base + DP_AUX_TRANS_CTRL, 0); /* read DP_INTERRUPT_TRANS_NUM */ dp->aux_trans_num = dp_read(base + DP_INTERRUPT_TRANS_NUM); if (dp->aux_cmd_i2c) dp_aux_i2c_handler(dp, isr1); else dp_aux_native_handler(dp, isr1); } if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->isr) { if (dp->hdcp.ops->isr(dp->hdcp.data)) pr_err("dp_hdcp_isr failed\n"); } return IRQ_HANDLED; } static void mdss_dp_event_cleanup(struct mdss_dp_drv_pdata *dp) { destroy_workqueue(dp->workq); if (dp->ev_thread == current) return; kthread_stop(dp->ev_thread); } static int mdss_dp_event_setup(struct mdss_dp_drv_pdata *dp) { init_waitqueue_head(&dp->dp_event.event_q); spin_lock_init(&dp->dp_event.event_lock); dp->ev_thread = kthread_run(mdss_dp_event_thread, (void *)&dp->dp_event, "mdss_dp_event"); if (IS_ERR(dp->ev_thread)) { pr_err("unable to start event thread\n"); return PTR_ERR(dp->ev_thread); } dp->workq = create_workqueue("mdss_dp_hpd"); if (!dp->workq) { pr_err("%s: Error creating workqueue\n", __func__); return -EPERM; } INIT_DELAYED_WORK(&dp->hdcp_cb_work, mdss_dp_hdcp_cb_work); INIT_LIST_HEAD(&dp->attention_head); return 0; } static void mdss_dp_reset_event_list(struct mdss_dp_drv_pdata *dp) { struct mdss_dp_event_data *ev_data = &dp->dp_event; spin_lock(&ev_data->event_lock); ev_data->pndx = ev_data->gndx = 0; spin_unlock(&ev_data->event_lock); mutex_lock(&dp->attention_lock); INIT_LIST_HEAD(&dp->attention_head); mutex_unlock(&dp->attention_lock); } static void mdss_dp_reset_sw_state(struct mdss_dp_drv_pdata *dp) { int ret = 0; pr_debug("enter\n"); mdss_dp_reset_event_list(dp); /* * IRQ_HPD attention event handler first turns on DP path and then * notifies CONNECT_IRQ_HPD and waits for userspace to trigger UNBLANK. * In such cases, before UNBLANK call, if cable is disconnected, if * DISCONNECT is notified immediately, userspace might not sense any * change in connection status, leaving DP controller ON. * * To avoid such cases, wait for the connection event to complete before * sending disconnection event */ if (atomic_read(&dp->notification_pending)) { pr_debug("waiting for the pending notitfication\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_err("%s timed out\n", mdss_dp_notification_status_to_string( dp->hpd_notification_status)); } } atomic_set(&dp->notification_pending, 0); /* complete any waiting completions */ complete_all(&dp->notification_comp); } static void usbpd_connect_callback(struct usbpd_svid_handler *hdlr, bool supports_usb_comm) { struct mdss_dp_drv_pdata *dp_drv; dp_drv = container_of(hdlr, struct mdss_dp_drv_pdata, svid_handler); if (!dp_drv->pd) { pr_err("get_usbpd phandle failed\n"); return; } mdss_dp_update_cable_status(dp_drv, true); if (dp_drv->ev_thread) kthread_unpark(dp_drv->ev_thread); if (dp_drv->hpd) dp_send_events(dp_drv, EV_USBPD_DISCOVER_MODES); } static void usbpd_disconnect_callback(struct usbpd_svid_handler *hdlr) { struct mdss_dp_drv_pdata *dp_drv; dp_drv = container_of(hdlr, struct mdss_dp_drv_pdata, svid_handler); if (!dp_drv->pd) { pr_err("get_usbpd phandle failed\n"); return; } pr_debug("cable disconnected\n"); mdss_dp_update_cable_status(dp_drv, false); dp_drv->alt_mode.current_state = UNKNOWN_STATE; mdss_dp_reset_sw_state(dp_drv); kthread_park(dp_drv->ev_thread); /** * Manually turn off the DP controller if we are in PHY * testing mode. */ if (mdss_dp_is_phy_test_pattern_requested(dp_drv)) { pr_info("turning off DP controller for PHY testing\n"); mdss_dp_mainlink_push_idle(&dp_drv->panel_data); mdss_dp_off_hpd(dp_drv); } else { mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); } /* * If cable is disconnected during device suspend, * reset the panel resolution to HDMI_VFRMT_UNKNOWN * so that new resolution is configured during * cable connect event */ if ((!dp_drv->power_on) && (!dp_drv->dp_initialized)) mdss_dp_reset_panel_info(dp_drv); /* * If a cable/dongle is connected to the TX device but * no sink device is connected, we call host * initialization where orientation settings are * configured. When the cable/dongle is disconnect, * call host de-initialization to make sure * we re-configure the orientation settings during * the next connect event. */ if ((!dp_drv->power_on) && (dp_drv->dp_initialized)) mdss_dp_host_deinit(dp_drv); } static int mdss_dp_validate_callback(u8 cmd, enum usbpd_svdm_cmd_type cmd_type, int num_vdos) { int ret = 0; if (cmd_type == SVDM_CMD_TYPE_RESP_NAK) { pr_err("error: NACK\n"); ret = -EINVAL; goto end; } if (cmd_type == SVDM_CMD_TYPE_RESP_BUSY) { pr_err("error: BUSY\n"); ret = -EBUSY; goto end; } if (cmd == USBPD_SVDM_ATTENTION) { if (cmd_type != SVDM_CMD_TYPE_INITIATOR) { pr_err("error: invalid cmd type for attention\n"); ret = -EINVAL; goto end; } if (!num_vdos) { pr_err("error: no vdo provided\n"); ret = -EINVAL; goto end; } } else { if (cmd_type != SVDM_CMD_TYPE_RESP_ACK) { pr_err("error: invalid cmd type\n"); ret = -EINVAL; } } end: return ret; } /** * mdss_dp_send_test_response() - sends the test response to the sink * @dp: Display Port Driver data * * This function will send the test response to the sink but only after * any previous link training has been completed. */ static inline void mdss_dp_send_test_response(struct mdss_dp_drv_pdata *dp) { mutex_lock(&dp->train_mutex); mdss_dp_aux_send_test_response(dp); mutex_unlock(&dp->train_mutex); } /** * mdss_dp_link_maintenance() - initiates link maintenanace * @dp: Display Port Driver data * @lt_needed: link retraining needed * * This function will perform link maintenance by first notifying * DP clients and triggering DP shutdown, and then enabling DP after * notification is done successfully. It will perform link retraining * if specified. */ static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp, bool lt_needed) { if (mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD)) return; if (atomic_read(&dp->notification_pending)) { int ret; pr_debug("waiting for the disconnect to finish\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n"); return; } } mdss_dp_on_irq(dp, lt_needed); } /** * mdss_dp_process_link_status_update() - processes link status updates * @dp: Display Port Driver data * * This function will check for changes in the link status, e.g. clock * recovery done on all lanes, and trigger link training if there is a * failure/error on the link. * * The function will return 0 if the a link status update has been processed, * otherwise it will return -EINVAL. */ static int mdss_dp_process_link_status_update(struct mdss_dp_drv_pdata *dp) { if (!mdss_dp_is_link_status_updated(dp) || (mdss_dp_aux_channel_eq_done(dp) && mdss_dp_aux_clock_recovery_done(dp))) return -EINVAL; pr_info("channel_eq_done = %d, clock_recovery_done = %d\n", mdss_dp_aux_channel_eq_done(dp), mdss_dp_aux_clock_recovery_done(dp)); mdss_dp_link_maintenance(dp, true); return 0; } /** * mdss_dp_process_link_training_request() - processes new training requests * @dp: Display Port Driver data * * This function will handle new link training requests that are initiated by * the sink. In particular, it will update the requested lane count and link * link rate, and then trigger the link retraining procedure. * * The function will return 0 if a link training request has been processed, * otherwise it will return -EINVAL. */ static int mdss_dp_process_link_training_request(struct mdss_dp_drv_pdata *dp) { if (!mdss_dp_is_link_training_requested(dp)) return -EINVAL; mdss_dp_send_test_response(dp); pr_info("%s link rate = 0x%x, lane count = 0x%x\n", mdss_dp_get_test_name(TEST_LINK_TRAINING), dp->test_data.test_link_rate, dp->test_data.test_lane_count); dp->dpcd.max_lane_count = dp->test_data.test_lane_count; dp->link_rate = dp->test_data.test_link_rate; mdss_dp_link_maintenance(dp, true); return 0; } /** * mdss_dp_process_phy_test_pattern_request() - process new phy test requests * @dp: Display Port Driver data * * This function will handle new phy test pattern requests that are initiated * by the sink. The function will return 0 if a phy test pattern has been * processed, otherwise it will return -EINVAL. */ static int mdss_dp_process_phy_test_pattern_request( struct mdss_dp_drv_pdata *dp) { u32 test_link_rate = 0, test_lane_count = 0; if (!mdss_dp_is_phy_test_pattern_requested(dp)) return -EINVAL; test_link_rate = dp->test_data.test_link_rate; test_lane_count = dp->test_data.test_lane_count; if (!mdss_dp_aux_is_link_rate_valid(test_link_rate) || !mdss_dp_aux_is_lane_count_valid(test_lane_count)) { pr_info("Invalid params: link rate = 0x%x, lane count = 0x%x\n", test_link_rate, test_lane_count); return -EINVAL; } pr_debug("start\n"); if (dp->power_on) { pr_info("turning off DP controller for PHY testing\n"); mdss_dp_mainlink_push_idle(&dp->panel_data); /* * The global reset will need DP link ralated clocks to be * running. Add the global reset just before disabling the * link clocks and core clocks. */ mdss_dp_ctrl_reset(&dp->ctrl_io); mdss_dp_off_irq(dp); } /** * Set the timing information to 1920x1080p60. This resolution will be * used when enabling the pixel clock. */ dp_init_panel_info(dp, HDMI_VFRMT_1920x1080p60_16_9); pr_info("Current: link rate = 0x%x, lane count = 0x%x\n", dp->dpcd.max_lane_count, dp->link_rate); pr_info("Requested: link rate = 0x%x, lane count = 0x%x\n", dp->test_data.test_link_rate, dp->test_data.test_lane_count); dp->dpcd.max_lane_count = dp->test_data.test_lane_count; dp->link_rate = dp->test_data.test_link_rate; mdss_dp_on_irq(dp, true); /** * Read the updated values for voltage and pre-emphasis levels and * then program the DP controller PHY accordingly. */ mdss_dp_aux_parse_vx_px(dp); mdss_dp_aux_update_voltage_and_pre_emphasis_lvl(dp); mdss_dp_phy_send_test_pattern(dp); mdss_dp_send_test_response(dp); pr_debug("end\n"); return 0; } /** * mdss_dp_process_audio_pattern_request() - process new audio pattern request * @dp: Display Port Driver data * * This function will handle a new audio pattern request that is initiated by * the sink. This is acheieved by sending the necessary secondary data packets * to the sink. It is expected that any simulatenous requests for video * patterns will be handled before the audio pattern is sent to the sink. */ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp) { struct msm_ext_disp_init_data *ext = &dp->ext_audio_data; if (!mdss_dp_is_audio_pattern_requested(dp)) return -EINVAL; if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->off) { cancel_delayed_work_sync(&dp->hdcp_cb_work); dp->hdcp.ops->off(dp->hdcp.data); } pr_debug("sampling_rate=%s, channel_count=%d, pattern_type=%s\n", mdss_dp_get_audio_sample_rate( dp->test_data.test_audio_sampling_rate), dp->test_data.test_audio_channel_count, mdss_dp_get_audio_test_pattern( dp->test_data.test_audio_pattern_type)); pr_debug("audio_period: ch1=0x%x, ch2=0x%x, ch3=0x%x, ch4=0x%x\n", dp->test_data.test_audio_period_ch_1, dp->test_data.test_audio_period_ch_2, dp->test_data.test_audio_period_ch_3, dp->test_data.test_audio_period_ch_4); pr_debug("audio_period: ch5=0x%x, ch6=0x%x, ch7=0x%x, ch8=0x%x\n", dp->test_data.test_audio_period_ch_5, dp->test_data.test_audio_period_ch_6, dp->test_data.test_audio_period_ch_7, dp->test_data.test_audio_period_ch_8); if (dp->ext_audio_data.intf_ops.audio_notify) dp->ext_audio_data.intf_ops.audio_notify(dp->ext_pdev, &ext->codec, 1); dp->audio_test_req = true; return 0; } /** * mdss_dp_process_downstream_port_status_change() - process port status changes * @dp: Display Port Driver data * * This function will handle downstream port updates that are initiated by * the sink. If the downstream port status has changed, the EDID is read via * AUX. * * The function will return 0 if a downstream port update has been * processed, otherwise it will return -EINVAL. */ static int mdss_dp_process_downstream_port_status_change( struct mdss_dp_drv_pdata *dp) { bool ds_status_changed = false; if (mdss_dp_is_downstream_port_status_changed(dp)) { pr_debug("downstream port status changed\n"); ds_status_changed = true; } /* * Ideally sink should update the downstream port status changed * whenever it updates the downstream sink count. However, it is * possible that only the sink count is updated without setting * the downstream port status changed bit. */ if (dp->sink_count.count != dp->prev_sink_count.count) { pr_debug("downstream sink count changed from %d --> %d\n", dp->prev_sink_count.count, dp->sink_count.count); ds_status_changed = true; } if (!ds_status_changed) return -EINVAL; mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD); if (atomic_read(&dp->notification_pending)) { int ret; pr_debug("waiting for the disconnect to finish\n"); ret = wait_for_completion_timeout(&dp->notification_comp, HZ); if (ret <= 0) { pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n"); return -ETIMEDOUT; } } if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) { pr_debug("sink count is zero, nothing to do\n"); return 0; } return mdss_dp_process_hpd_high(dp); } static bool mdss_dp_video_pattern_test_lt_needed(struct mdss_dp_drv_pdata *dp) { char new_link_rate; /* * Link re-training for video format change is only needed if: * 1. Link rate changes * 2. Lane count changes * For now, assume that lane count is not going to change */ new_link_rate = mdss_dp_gen_link_clk(dp); pr_debug("new link rate = 0x%x, current link rate = 0x%x\n", new_link_rate, dp->link_rate); if (new_link_rate != dp->link_rate) { dp->link_rate = new_link_rate; return true; } return false; } /** * mdss_dp_process_video_pattern_request() - process new video pattern request * @dp: Display Port Driver data * * This function will handle a new video pattern request that are initiated by * the sink. This is acheieved by first sending a disconnect notification to * the sink followed by a subsequent connect notification to the user modules, * where it is expected that the user modules would draw the required test * pattern. */ static int mdss_dp_process_video_pattern_request(struct mdss_dp_drv_pdata *dp) { bool lt_needed; struct hdmi_edid_override_data ov_data = {0, 0, 1, HDMI_VFRMT_640x480p59_4_3}; bool ov_res = false; if (!mdss_dp_is_video_pattern_requested(dp)) goto end; pr_info("%s: bit depth=%d(%d bpp) pattern=%s\n", mdss_dp_get_test_name(TEST_VIDEO_PATTERN), dp->test_data.test_bit_depth, mdss_dp_test_bit_depth_to_bpp(dp->test_data.test_bit_depth), mdss_dp_test_video_pattern_to_string( dp->test_data.test_video_pattern)); if (dp->test_data.test_h_width == 640) { pr_debug("Set resolution to 640x480p59"); if (dp->vic != HDMI_VFRMT_640x480p59_4_3) { ov_res = true; dp->vic = HDMI_VFRMT_640x480p59_4_3; } hdmi_edid_config_override(dp->panel_data.panel_info.edid_data, true, &ov_data); } dp_init_panel_info(dp, dp->vic); lt_needed = ov_res | mdss_dp_video_pattern_test_lt_needed(dp); pr_debug("Link training needed: %s", lt_needed ? "yes" : "no"); mdss_dp_link_maintenance(dp, lt_needed); if (mdss_dp_is_audio_pattern_requested(dp)) goto end; mdss_dp_send_test_response(dp); return 0; end: return -EINVAL; } /** * mdss_dp_process_hpd_irq_high() - handle HPD IRQ transition to HIGH * @dp: Display Port Driver data * * This function will handle the HPD IRQ state transitions from LOW to HIGH * (including cases when there are back to back HPD IRQ HIGH) indicating * the start of a new link training request or sink status update. */ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp) { int ret = 0; pr_debug("start\n"); dp->hpd_irq_on = true; mdss_dp_reset_test_data(dp); mdss_dp_aux_parse_sink_status_field(dp); ret = mdss_dp_process_downstream_port_status_change(dp); if (!ret) goto exit; ret = mdss_dp_process_link_training_request(dp); if (!ret) goto exit; ret = mdss_dp_process_phy_test_pattern_request(dp); if (!ret) goto exit; ret = mdss_dp_process_link_status_update(dp); if (!ret) goto exit; ret = mdss_dp_process_video_pattern_request(dp); if (!ret) goto exit; ret = mdss_dp_process_audio_pattern_request(dp); if (!ret) goto exit; pr_debug("done\n"); exit: dp->hpd_irq_on = false; return ret; } static void usbpd_response_callback(struct usbpd_svid_handler *hdlr, u8 cmd, enum usbpd_svdm_cmd_type cmd_type, const u32 *vdos, int num_vdos) { struct mdss_dp_drv_pdata *dp_drv; struct mdss_dp_attention_node *node; dp_drv = container_of(hdlr, struct mdss_dp_drv_pdata, svid_handler); if (!dp_drv->pd) { pr_err("get_usbpd phandle failed\n"); return; } pr_debug("callback -> cmd: 0x%x, *vdos = 0x%x, num_vdos = %d\n", cmd, *vdos, num_vdos); if (mdss_dp_validate_callback(cmd, cmd_type, num_vdos)) { pr_debug("invalid callback received\n"); return; } switch (cmd) { case USBPD_SVDM_DISCOVER_MODES: dp_drv->alt_mode.dp_cap.response = *vdos; mdss_dp_usbpd_ext_capabilities(&dp_drv->alt_mode.dp_cap); dp_drv->alt_mode.current_state |= DISCOVER_MODES_DONE; if (dp_drv->alt_mode.dp_cap.s_port & BIT(0)) dp_send_events(dp_drv, EV_USBPD_ENTER_MODE); break; case USBPD_SVDM_ENTER_MODE: dp_drv->alt_mode.current_state |= ENTER_MODE_DONE; dp_send_events(dp_drv, EV_USBPD_DP_STATUS); break; case USBPD_SVDM_ATTENTION: node = kzalloc(sizeof(*node), GFP_KERNEL); if (!node) return; node->vdo = *vdos; mutex_lock(&dp_drv->attention_lock); if (dp_drv->cable_connected) list_add_tail(&node->list, &dp_drv->attention_head); mutex_unlock(&dp_drv->attention_lock); dp_send_events(dp_drv, EV_USBPD_ATTENTION); break; case DP_VDM_STATUS: dp_drv->alt_mode.dp_status.response = *vdos; mdss_dp_usbpd_ext_dp_status(&dp_drv->alt_mode.dp_status); if (!(dp_drv->alt_mode.current_state & DP_CONFIGURE_DONE)) { dp_drv->alt_mode.current_state |= DP_STATUS_DONE; if (dp_drv->alt_mode.dp_status.c_port & BIT(1)) dp_send_events(dp_drv, EV_USBPD_DP_CONFIGURE); } break; case DP_VDM_CONFIGURE: dp_drv->alt_mode.current_state |= DP_CONFIGURE_DONE; pr_debug("Configure: config USBPD to DP done\n"); mdss_dp_usbpd_ext_dp_status(&dp_drv->alt_mode.dp_status); mdss_dp_host_init(&dp_drv->panel_data); if (dp_drv->alt_mode.dp_status.hpd_high) mdss_dp_process_hpd_high(dp_drv); break; default: pr_err("unknown cmd: %d\n", cmd); break; } } static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv) { if (dp_drv->alt_mode.dp_status.hpd_irq) { pr_debug("Attention: hpd_irq high\n"); /* In case of HPD_IRQ events without DP link being * turned on such as adb shell stop, skip handling * hpd_irq event. */ if (!dp_drv->dp_initialized) { pr_err("DP not initialized yet\n"); return; } if (dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.ops->cp_irq) { if (!dp_drv->hdcp.ops->cp_irq(dp_drv->hdcp.data)) return; } if (!mdss_dp_process_hpd_irq_high(dp_drv)) return; } if (!dp_drv->alt_mode.dp_status.hpd_high) { pr_debug("Attention: HPD low\n"); if (!dp_drv->power_on) { pr_debug("HPD already low\n"); return; } if (dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.ops->off) { cancel_delayed_work_sync(&dp_drv->hdcp_cb_work); dp_drv->hdcp.ops->off(dp_drv->hdcp.data); } /* * Reset the sink count before nofifying clients since HPD Low * indicates that the downstream device has been disconnected. */ mdss_dp_reset_sink_count(dp_drv); mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT); pr_debug("Attention: Notified clients\n"); /* * When a DP adaptor is connected and if sink is * disconnected during device suspend, * reset the panel resolution to HDMI_VFRMT_UNKNOWN * so that new resolution is configured during * connect event. */ if ((!dp_drv->power_on) && (!dp_drv->dp_initialized)) mdss_dp_reset_panel_info(dp_drv); /** * Manually turn off the DP controller if we are in PHY * testing mode. */ if (mdss_dp_is_phy_test_pattern_requested(dp_drv)) { pr_info("turning off DP controller for PHY testing\n"); mdss_dp_mainlink_push_idle(&dp_drv->panel_data); mdss_dp_off_hpd(dp_drv); } return; } pr_debug("Attention: HPD high\n"); if (dp_drv->power_on) { pr_debug("HPD high processed already\n"); return; } dp_drv->alt_mode.current_state |= DP_STATUS_DONE; if (dp_drv->alt_mode.current_state & DP_CONFIGURE_DONE) { mdss_dp_host_init(&dp_drv->panel_data); mdss_dp_process_hpd_high(dp_drv); } else { dp_send_events(dp_drv, EV_USBPD_DP_CONFIGURE); } } static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp) { int i = 0; pr_debug("start\n"); while (!list_empty_careful(&dp->attention_head)) { struct mdss_dp_attention_node *node; u32 vdo; pr_debug("processing item %d in the list\n", ++i); reinit_completion(&dp->notification_comp); mutex_lock(&dp->attention_lock); if (!dp->cable_connected) { pr_debug("cable disconnected, returning\n"); mutex_unlock(&dp->attention_lock); goto exit; } node = list_first_entry(&dp->attention_head, struct mdss_dp_attention_node, list); vdo = node->vdo; list_del(&node->list); mutex_unlock(&dp->attention_lock); kzfree(node); dp->alt_mode.dp_status.response = vdo; mdss_dp_usbpd_ext_dp_status(&dp->alt_mode.dp_status); mdss_dp_process_attention(dp); if (atomic_read(&dp->notification_pending)) { pr_debug("waiting for the attention event to finish\n"); /* * This wait is intentionally implemented without a * timeout since this is happens only in possible error * conditions e.g. if the display framework does not * power off/on the DisplayPort device in time. Other * events might already be queued from the sink at this * point and they cannot be processed until the power * off/on is complete otherwise we might have problems * with interleaving of these events e.g. un-clocked * register access. */ wait_for_completion(&dp->notification_comp); } pr_debug("done processing item %d in the list\n", i); }; exit: pr_debug("exit\n"); } static int mdss_dp_usbpd_setup(struct mdss_dp_drv_pdata *dp_drv) { int ret = 0; const char *pd_phandle = "qcom,dp-usbpd-detection"; dp_drv->pd = devm_usbpd_get_by_phandle(&dp_drv->pdev->dev, pd_phandle); if (IS_ERR(dp_drv->pd)) { pr_err("get_usbpd phandle failed (%ld)\n", PTR_ERR(dp_drv->pd)); return PTR_ERR(dp_drv->pd); } dp_drv->svid_handler.svid = USB_C_DP_SID; dp_drv->svid_handler.vdm_received = NULL; dp_drv->svid_handler.connect = &usbpd_connect_callback; dp_drv->svid_handler.svdm_received = &usbpd_response_callback; dp_drv->svid_handler.disconnect = &usbpd_disconnect_callback; ret = usbpd_register_svid(dp_drv->pd, &dp_drv->svid_handler); if (ret) { pr_err("usbpd registration failed\n"); return -ENODEV; } return ret; } static int mdss_dp_probe(struct platform_device *pdev) { int ret, i; struct mdss_dp_drv_pdata *dp_drv; struct mdss_panel_cfg *pan_cfg = NULL; struct mdss_util_intf *util; util = mdss_get_util_intf(); if (!util) { pr_err("Failed to get mdss utility functions\n"); return -ENODEV; } if (!util->mdp_probe_done) { pr_err("MDP not probed yet!\n"); return -EPROBE_DEFER; } if (!pdev || !pdev->dev.of_node) { pr_err("pdev not found for DP controller\n"); return -ENODEV; } pan_cfg = mdss_panel_intf_type(MDSS_PANEL_INTF_EDP); if (IS_ERR(pan_cfg)) { return PTR_ERR(pan_cfg); } else if (pan_cfg) { pr_debug("DP as prim not supported\n"); return -ENODEV; } dp_drv = devm_kzalloc(&pdev->dev, sizeof(*dp_drv), GFP_KERNEL); if (dp_drv == NULL) return -ENOMEM; dp_drv->pdev = pdev; dp_drv->pdev->id = 1; dp_drv->mdss_util = util; dp_drv->clk_on = 0; dp_drv->aux_rate = 19200000; dp_drv->mask1 = EDP_INTR_MASK1; dp_drv->mask2 = EDP_INTR_MASK2; mutex_init(&dp_drv->emutex); mutex_init(&dp_drv->attention_lock); mutex_init(&dp_drv->hdcp_mutex); spin_lock_init(&dp_drv->lock); mutex_init(&dp_drv->aux_mutex); mutex_init(&dp_drv->train_mutex); init_completion(&dp_drv->aux_comp); init_completion(&dp_drv->idle_comp); init_completion(&dp_drv->video_comp); if (mdss_dp_usbpd_setup(dp_drv)) { pr_err("Error usbpd setup!\n"); devm_kfree(&pdev->dev, dp_drv); dp_drv = NULL; return -EPROBE_DEFER; } ret = mdss_retrieve_dp_ctrl_resources(pdev, dp_drv); if (ret) goto probe_err; /* Parse the regulator information */ for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { ret = mdss_dp_get_dt_vreg_data(&pdev->dev, pdev->dev.of_node, &dp_drv->power_data[i], i); if (ret) { pr_err("get_dt_vreg_data failed for %s. rc=%d\n", __mdss_dp_pm_name(i), ret); i--; for (; i >= DP_CORE_PM; i--) mdss_dp_put_dt_vreg_data(&pdev->dev, &dp_drv->power_data[i]); goto probe_err; } } ret = mdss_dp_get_dt_clk_data(&pdev->dev, dp_drv); if (ret) { DEV_ERR("get_dt_clk_data failed.ret=%d\n", ret); goto probe_err; } ret = mdss_dp_regulator_init(pdev, dp_drv); if (ret) goto probe_err; ret = mdss_dp_clk_init(dp_drv, &pdev->dev, true); if (ret) { DEV_ERR("clk_init failed.ret=%d\n", ret); goto probe_err; } ret = mdss_dp_parse_prop(pdev, dp_drv); if (ret) { DEV_ERR("DP properties parsing failed.ret=%d\n", ret); goto probe_err; } ret = mdss_dp_irq_setup(dp_drv); if (ret) goto probe_err; ret = mdss_dp_event_setup(dp_drv); if (ret) goto probe_err; dp_drv->cont_splash = dp_drv->mdss_util->panel_intf_status(DISPLAY_1, MDSS_PANEL_INTF_EDP) ? true : false; platform_set_drvdata(pdev, dp_drv); ret = mdss_dp_pinctrl_init(pdev, dp_drv); if (ret) { pr_err("pinctrl init failed, ret=%d\n", ret); goto probe_err; } ret = mdss_dp_parse_gpio_params(pdev, dp_drv); if (ret) { pr_err("failed to parse gpio params, ret=%d\n", ret); goto probe_err; } mdss_dp_device_register(dp_drv); dp_drv->inited = true; dp_drv->hpd_irq_on = false; atomic_set(&dp_drv->notification_pending, 0); mdss_dp_reset_test_data(dp_drv); init_completion(&dp_drv->notification_comp); dp_drv->suspend_vic = HDMI_VFRMT_UNKNOWN; pr_debug("done\n"); return 0; probe_err: iounmap(dp_drv->ctrl_io.base); iounmap(dp_drv->phy_io.base); if (dp_drv) { if (dp_drv->pd) usbpd_unregister_svid(dp_drv->pd, &dp_drv->svid_handler); devm_kfree(&pdev->dev, dp_drv); } return ret; } void *mdss_dp_get_hdcp_data(struct device *dev) { struct mdss_dp_drv_pdata *dp; struct msm_fb_data_type *mfd; struct mdss_panel_data *pd; struct fb_info *fbi = dev_get_drvdata(dev); if (!fbi) { pr_err("invalid fbi\n"); goto error; } mfd = (struct msm_fb_data_type *)fbi->par; if (!mfd) { pr_err("invalid mfd\n"); goto error; } pd = dev_get_platdata(&mfd->pdev->dev); if (!pd) { pr_err("invalid panel_data\n"); goto error; } dp = container_of(pd, struct mdss_dp_drv_pdata, panel_data); return dp->hdcp.data; error: return NULL; } static inline bool dp_is_stream_shareable(struct mdss_dp_drv_pdata *dp_drv) { bool ret = 0; switch (dp_drv->hdcp.enc_lvl) { case HDCP_STATE_AUTH_ENC_NONE: ret = true; break; case HDCP_STATE_AUTH_ENC_1X: ret = dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.auth_state; break; case HDCP_STATE_AUTH_ENC_2P2: ret = dp_drv->hdcp.feature_enabled && dp_drv->hdcp.hdcp2_present && dp_drv->hdcp.auth_state; break; default: ret = false; } return ret; } static int mdss_dp_remove(struct platform_device *pdev) { struct mdss_dp_drv_pdata *dp_drv = NULL; dp_drv = platform_get_drvdata(pdev); //dp_hdcp2p2_deinit(dp_drv->hdcp.data); mdss_dp_event_cleanup(dp_drv); iounmap(dp_drv->ctrl_io.base); dp_drv->ctrl_io.base = NULL; iounmap(dp_drv->phy_io.base); dp_drv->phy_io.base = NULL; return 0; } static const struct of_device_id msm_mdss_dp_dt_match[] = { {.compatible = "qcom,mdss-dp"}, {} }; MODULE_DEVICE_TABLE(of, msm_mdss_dp_dt_match); static struct platform_driver mdss_dp_driver = { .probe = mdss_dp_probe, .remove = mdss_dp_remove, .shutdown = NULL, .driver = { .name = "mdss_dp", .of_match_table = msm_mdss_dp_dt_match, }, }; static int __init mdss_dp_init(void) { int ret; ret = platform_driver_register(&mdss_dp_driver); if (ret) { pr_err("driver register failed"); return ret; } return ret; } module_init(mdss_dp_init); static void __exit mdss_dp_driver_cleanup(void) { platform_driver_unregister(&mdss_dp_driver); } module_exit(mdss_dp_driver_cleanup); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("DP controller driver");