android_kernel_oneplus_msm8998/drivers/video/fbdev/msm/mdss_dp.c
Ajay Singh Parmar 0659e58553 msm: ext_display: update hpd and notify logic
Use "hpd" method to notify external display module about
cable status change and "notify" as an acknowledgment to
power on or off. This makes hpd method as a blocking
call completed by notify call.

Change-Id: I5ef7cf5c95d46a695f20a51214a2afabd6feb4b6
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
2016-11-01 11:17:46 -07:00

2851 lines
66 KiB
C

/* Copyright (c) 2012-2016 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/regulator/consumer.h>
#include <linux/qpnp/pwm.h>
#include <linux/clk.h>
#include <linux/spinlock_types.h>
#include <linux/kthread.h>
#include <linux/msm_ext_display.h>
#include "mdss.h"
#include "mdss_dp.h"
#include "mdss_dp_util.h"
#include "mdss_hdmi_panel.h"
#include <linux/hdcp_qseecom.h>
#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 DEFAULT_VIDEO_RESOLUTION HDMI_VFRMT_640x480p60_4_3
static u32 supported_modes[] = {
HDMI_VFRMT_640x480p60_4_3,
HDMI_VFRMT_720x480p60_4_3, HDMI_VFRMT_720x480p60_16_9,
HDMI_VFRMT_1280x720p60_16_9,
HDMI_VFRMT_1920x1080p60_16_9,
HDMI_VFRMT_3840x2160p24_16_9, HDMI_VFRMT_3840x2160p30_16_9,
HDMI_VFRMT_3840x2160p60_16_9,
HDMI_VFRMT_4096x2160p24_256_135, HDMI_VFRMT_4096x2160p30_256_135,
HDMI_VFRMT_4096x2160p60_256_135, HDMI_EVFRMT_4096x2160p24_16_9
};
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_retraining(struct mdss_dp_drv_pdata *dp);
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_clk_prefix(const char *clk_prefix, const char *clk_name)
{
return !strncmp(clk_name, clk_prefix, strlen(clk_prefix));
}
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;
const char *core_clk = "core";
const char *ctrl_clk = "ctrl";
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_clk_prefix(core_clk, clk_name))
core_clk_count++;
if (mdss_dp_is_clk_prefix(ctrl_clk, clk_name))
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;
const char *core_clk = "core";
const char *ctrl_clk = "ctrl";
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_clk_prefix(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_clk_prefix(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"))
clk->type = DSS_CLK_PCLK;
else if (!strcmp(clk_name, "ctrl_pixel_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;
}
} else {
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;
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, 0);
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;
u32 data = 0;
timing = &dp->edid.timing[0];
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);
if (dp->edid.color_depth != 6)
data |= 0x100; /* Default: 8 bits */
/* 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);
}
int mdss_dp_wait4train(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;
}
pr_debug("End--\n");
return ret;
}
static void mdss_dp_update_cable_status(struct mdss_dp_drv_pdata *dp,
bool connected)
{
mutex_lock(&dp->pd_msg_mutex);
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->pd_msg_mutex);
}
static int dp_get_cable_status(struct platform_device *pdev, u32 vote)
{
struct mdss_dp_drv_pdata *dp_ctrl = platform_get_drvdata(pdev);
u32 hpd;
if (!dp_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return -ENODEV;
}
mutex_lock(&dp_ctrl->pd_msg_mutex);
hpd = dp_ctrl->cable_connected;
mutex_unlock(&dp_ctrl->pd_msg_mutex);
return hpd;
}
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 = platform_get_drvdata(pdev);
if (!dp_ctrl || !params) {
DEV_ERR("%s: invalid input\n", __func__);
return -ENODEV;
}
mdss_dp_audio_setup_sdps(&dp_ctrl->ctrl_io);
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);
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 = platform_get_drvdata(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 int mdss_dp_init_ext_disp(struct mdss_dp_drv_pdata *dp)
{
int ret = 0;
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;
}
dp->ext_audio_data.type = EXT_DISPLAY_TYPE_DP;
dp->ext_audio_data.kobj = dp->kobj;
dp->ext_audio_data.pdev = dp->pdev;
dp->ext_audio_data.codec_ops.audio_info_setup =
dp_audio_info_setup;
dp->ext_audio_data.codec_ops.get_audio_edid_blk =
dp_get_audio_edid_blk;
dp->ext_audio_data.codec_ops.cable_status =
dp_get_cable_status;
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,
&dp->ext_audio_data);
if (ret)
pr_err("%s: failed to register disp\n", __func__);
end:
return ret;
}
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;
}
ret = hdmi_get_supported_mode(&timing, &dp_drv->ds_data, vic);
pinfo = &dp_drv->panel_data.panel_info;
if (ret || !timing.supported || !pinfo) {
DEV_ERR("%s: invalid timing data\n", __func__);
return -EINVAL;
}
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.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->type = DP_PANEL;
pinfo->pdest = DISPLAY_4;
pinfo->wait_cycle = 0;
pinfo->bpp = 24;
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;
dp_drv->bpp = pinfo->bpp;
pr_debug("update res. vic= %d, pclk_rate = %llu\n",
dp_drv->vic, pinfo->clk_rate);
return 0;
} /* dp_init_panel_info */
static inline void mdss_dp_ack_state(struct mdss_dp_drv_pdata *dp, int val)
{
if (dp && dp->ext_audio_data.intf_ops.notify)
dp->ext_audio_data.intf_ops.notify(dp->ext_pdev, val);
}
/**
* 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,
struct lane_mapping *lane_map)
{
int ret = 0;
pr_debug("enter: orientation = %d\n", orientation);
if (!lane_map) {
pr_err("invalid lane map input");
ret = -EINVAL;
goto exit;
}
/* Set the default lane mapping */
lane_map->lane0 = 2;
lane_map->lane1 = 3;
lane_map->lane2 = 1;
lane_map->lane3 = 0;
if (orientation == ORIENTATION_CC2) {
lane_map->lane0 = 1;
lane_map->lane1 = 0;
lane_map->lane2 = 2;
lane_map->lane3 = 3;
if (gpio_is_valid(dp->usbplug_cc_gpio)) {
gpio_set_value(dp->usbplug_cc_gpio, 1);
pr_debug("Configured cc gpio for new Orientation\n");
}
}
pr_debug("lane0 = %d, lane1 = %d, lane2 =%d, lane3 =%d\n",
lane_map->lane0, lane_map->lane1, lane_map->lane2,
lane_map->lane3);
exit:
return ret;
}
/**
* 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;
dp->power_data[DP_CTRL_PM].clk_config[0].rate =
((dp->link_rate * DP_LINK_RATE_MULTIPLIER) / 1000);/* KHz */
dp->pixel_rate = dp->panel_data.panel_info.clk_rate;
dp->power_data[DP_CTRL_PM].clk_config[3].rate =
(dp->pixel_rate / 1000);/* KHz */
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)
{
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,
struct lane_mapping *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_sw_mvid_nvid(&dp->ctrl_io);
mdss_dp_timing_cfg(&dp->ctrl_io, &dp->panel_data.panel_info);
}
/**
* mdss_dp_train_main_link() - initiates training of DP main link
* @dp: Display Port Driver data
*
* 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_train_main_link(struct mdss_dp_drv_pdata *dp)
{
int ret = 0;
int ready = 0;
pr_debug("enter\n");
ret = mdss_dp_link_train(dp);
if (ret)
goto end;
mdss_dp_wait4train(dp);
ready = mdss_dp_mainlink_ready(dp, BIT(0));
pr_debug("main link %s\n", ready ? "READY" : "NOT READY");
end:
return ret;
}
static int mdss_dp_on_irq(struct mdss_dp_drv_pdata *dp_drv)
{
int ret = 0;
enum plug_orientation orientation = ORIENTATION_NONE;
struct lane_mapping ln_map;
/* wait until link training is completed */
pr_debug("enter\n");
do {
if (ret == -EAGAIN) {
mdss_dp_mainlink_push_idle(&dp_drv->panel_data);
mdss_dp_off_irq(dp_drv);
}
mutex_lock(&dp_drv->train_mutex);
orientation = usbpd_get_plug_orientation(dp_drv->pd);
pr_debug("plug orientation = %d\n", orientation);
ret = mdss_dp_get_lane_mapping(dp_drv, orientation, &ln_map);
if (ret)
goto exit;
mdss_dp_phy_share_lane_config(&dp_drv->phy_io,
orientation, dp_drv->dpcd.max_lane_count);
ret = mdss_dp_enable_mainlink_clocks(dp_drv);
if (ret)
goto exit;
mdss_dp_mainlink_reset(&dp_drv->ctrl_io);
reinit_completion(&dp_drv->idle_comp);
mdss_dp_configure_source_params(dp_drv, &ln_map);
dp_drv->power_on = true;
ret = mdss_dp_train_main_link(dp_drv);
mutex_unlock(&dp_drv->train_mutex);
} while (ret == -EAGAIN);
pr_debug("end\n");
exit:
mutex_unlock(&dp_drv->train_mutex);
return ret;
}
int mdss_dp_on_hpd(struct mdss_dp_drv_pdata *dp_drv)
{
int ret = 0;
enum plug_orientation orientation = ORIENTATION_NONE;
struct lane_mapping ln_map;
/* 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);
orientation = usbpd_get_plug_orientation(dp_drv->pd);
pr_debug("plug Orientation = %d\n", orientation);
ret = mdss_dp_get_lane_mapping(dp_drv, orientation, &ln_map);
if (ret)
goto exit;
if (dp_drv->new_vic && (dp_drv->new_vic != dp_drv->vic))
dp_init_panel_info(dp_drv, dp_drv->new_vic);
dp_drv->link_rate =
mdss_dp_gen_link_clk(&dp_drv->panel_data.panel_info,
dp_drv->dpcd.max_lane_count);
pr_debug("link_rate=0x%x, Max rate supported by sink=0x%x\n",
dp_drv->link_rate, dp_drv->dpcd.max_link_rate);
if (!dp_drv->link_rate) {
pr_err("Unable to configure required link rate\n");
ret = -EINVAL;
goto exit;
}
mdss_dp_phy_share_lane_config(&dp_drv->phy_io,
orientation, dp_drv->dpcd.max_lane_count);
pr_debug("link_rate = 0x%x\n", dp_drv->link_rate);
ret = mdss_dp_enable_mainlink_clocks(dp_drv);
if (ret)
goto exit;
mdss_dp_mainlink_reset(&dp_drv->ctrl_io);
reinit_completion(&dp_drv->idle_comp);
mdss_dp_configure_source_params(dp_drv, &ln_map);
link_training:
dp_drv->power_on = true;
if (-EAGAIN == mdss_dp_train_main_link(dp_drv)) {
mutex_unlock(&dp_drv->train_mutex);
mdss_dp_link_retraining(dp_drv);
return 0;
}
dp_drv->cont_splash = 0;
dp_drv->power_on = true;
mdss_dp_ack_state(dp_drv, true);
pr_debug("End-\n");
exit:
mutex_unlock(&dp_drv->train_mutex);
return ret;
}
int mdss_dp_on(struct mdss_panel_data *pdata)
{
struct mdss_dp_drv_pdata *dp_drv = NULL;
if (!pdata) {
pr_err("Invalid input data\n");
return -EINVAL;
}
dp_drv = container_of(pdata, struct mdss_dp_drv_pdata,
panel_data);
return mdss_dp_on_hpd(dp_drv);
}
static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp)
{
dp->test_data = (const struct dpcd_test_request){ 0 };
}
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_link_training_requested(
struct mdss_dp_drv_pdata *dp)
{
return (dp->test_data.test_requested == TEST_LINK_TRAINING);
}
static inline bool mdss_dp_soft_hpd_reset(struct mdss_dp_drv_pdata *dp)
{
return mdss_dp_is_link_training_requested(dp) &&
dp->alt_mode.dp_status.hpd_irq;
}
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 the DP main link is disabled before clk disable */
wmb();
mdss_dp_disable_mainlink_clocks(dp_drv);
dp_drv->power_on = false;
mutex_unlock(&dp_drv->train_mutex);
complete_all(&dp_drv->irq_comp);
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_irq_disable(dp_drv);
mdss_dp_config_gpios(dp_drv, false);
mdss_dp_pinctrl_set_state(dp_drv, 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_drv->ctrl_io);
/* Make sure DP is disabled before clk disable */
wmb();
mdss_dp_disable_mainlink_clocks(dp_drv);
mdss_dp_clk_ctrl(dp_drv, DP_CORE_PM, false);
mdss_dp_regulator_ctrl(dp_drv, false);
dp_drv->dp_initialized = false;
dp_drv->power_on = false;
mdss_dp_ack_state(dp_drv, false);
mutex_unlock(&dp_drv->train_mutex);
pr_debug("DP off done\n");
return 0;
}
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 (mdss_dp_soft_hpd_reset(dp))
return mdss_dp_off_irq(dp);
else
return mdss_dp_off_hpd(dp);
}
static int mdss_dp_send_cable_notification(
struct mdss_dp_drv_pdata *dp, int val)
{
int ret = 0;
if (!dp) {
DEV_ERR("%s: invalid input\n", __func__);
ret = -EINVAL;
goto end;
}
if (dp && dp->ext_audio_data.intf_ops.hpd)
ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev,
dp->ext_audio_data.type, val);
end:
return ret;
}
static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp, bool enable)
{
return mdss_dp_send_cable_notification(dp, enable);
}
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 = {0};
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);
dp_drv->ds_data.ds_registered = true;
dp_drv->ds_data.modes_num = ARRAY_SIZE(supported_modes);
dp_drv->ds_data.modes = supported_modes;
dp_drv->max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ;
edid_init_data.kobj = dp_drv->kobj;
edid_init_data.ds_data = dp_drv->ds_data;
edid_init_data.max_pclk_khz = dp_drv->max_pclk_khz;
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;
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_err("%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;
}
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_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));
pr_debug("plug Orientation = %d\n",
usbpd_get_plug_orientation(dp_drv->pd));
mdss_dp_phy_aux_setup(&dp_drv->phy_io);
mdss_dp_irq_enable(dp_drv);
pr_debug("irq enabled\n");
mdss_dp_dpcd_cap_read(dp_drv);
ret = mdss_dp_edid_read(dp_drv);
if (ret)
goto edid_error;
pr_debug("edid_read success. buf_size=%d\n",
dp_drv->edid_buf_size);
ret = hdmi_edid_parser(dp_drv->panel_data.panel_info.edid_data);
if (ret) {
DEV_ERR("%s: edid parse failed\n", __func__);
goto edid_error;
}
mdss_dp_notify_clients(dp_drv, true);
dp_drv->dp_initialized = true;
return ret;
edid_error:
mdss_dp_clk_ctrl(dp_drv, DP_CORE_PM, false);
clk_error:
mdss_dp_regulator_ctrl(dp_drv, false);
mdss_dp_config_gpios(dp_drv, false);
vreg_error:
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, &dp->ds_data);
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 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;
int rc = 0;
dp = container_of(dw, struct mdss_dp_drv_pdata, hdcp_cb_work);
ops = dp->hdcp.ops;
switch (dp->hdcp_status) {
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->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;
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 = {0};
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;
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);
if (!dp)
return -EINVAL;
ret = snprintf(buf, PAGE_SIZE, "%d\n", dp->cable_connected);
pr_debug("%d\n", dp->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 DEVICE_ATTR(connected, S_IRUGO, mdss_dp_rda_connected, NULL);
static DEVICE_ATTR(s3d_mode, S_IRUGO | S_IWUSR, mdss_dp_sysfs_rda_s3d_mode,
mdss_dp_sysfs_wta_s3d_mode);
static struct attribute *mdss_dp_fs_attrs[] = {
&dev_attr_connected.attr,
&dev_attr_s3d_mode.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)
{
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);
mdss_dp_aux_set_sink_power_state(dp_drv, SINK_POWER_OFF);
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;
}
/* 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;
if (!dp->hdcp.hdcp2_present) {
dp->hdcp.hdcp1_present = hdcp1_check_if_supported_load_app();
if (dp->hdcp.hdcp1_present) {
fd = dp->hdcp.hdcp1;
ops = hdcp_1x_start(fd);
}
}
/* update internal data about hdcp */
dp->hdcp.data = fd;
dp->hdcp.ops = ops;
}
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 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:
mdss_dp_update_hdcp_info(dp);
if (dp->hdcp.ops && dp->hdcp.ops->authenticate)
rc = dp->hdcp.ops->authenticate(dp->hdcp.data);
break;
case MDSS_EVENT_PANEL_OFF:
rc = mdss_dp_off(pdata);
break;
case MDSS_EVENT_BLANK:
if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->off) {
flush_delayed_work(&dp->hdcp_cb_work);
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;
default:
pr_debug("unhandled event=%d\n", event);
break;
}
return rc;
}
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);
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 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 = 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 = 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->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=%p size=%x\n",
dp_drv->base, dp_drv->base_size);
mdss_debug_register_base("dp",
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 void mdss_dp_event_work(struct work_struct *work)
{
struct mdss_dp_drv_pdata *dp = NULL;
unsigned long flag;
u32 todo = 0, config;
if (!work) {
pr_err("invalid work structure\n");
return;
}
dp = container_of(work, struct mdss_dp_drv_pdata, work);
spin_lock_irqsave(&dp->event_lock, flag);
todo = dp->current_event;
dp->current_event = 0;
spin_unlock_irqrestore(&dp->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_DISCOVER_MODES:
usbpd_send_svdm(dp->pd, USB_C_DP_SID, USBPD_SVDM_DISCOVER_MODES,
SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 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, 0x0, 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, 0x0, 0x0);
break;
case EV_USBPD_DP_STATUS:
usbpd_send_svdm(dp->pd, USB_C_DP_SID, DP_VDM_STATUS,
SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0);
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);
}
}
static void dp_send_events(struct mdss_dp_drv_pdata *dp, u32 events)
{
spin_lock(&dp->event_lock);
dp->current_event = events;
queue_work(dp->workq, &dp->work);
spin_unlock(&dp->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);
mask1 = isr1 & dp->mask1;
isr1 &= ~mask1; /* remove masks bit */
pr_debug("isr=%x mask=%x isr2=%x\n",
isr1, mask1, isr2);
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)
dp_send_events(dp, EV_VIDEO_READY);
if (isr2 & EDP_INTR_IDLE_PATTERNs_SENT)
dp_send_events(dp, EV_IDLE_PATTERNS_SENT);
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->hdcp.ops && dp->hdcp.ops->isr) {
if (dp->hdcp.ops->isr(dp->hdcp.data))
pr_err("dp_hdcp_isr failed\n");
}
return IRQ_HANDLED;
}
static int mdss_dp_event_setup(struct mdss_dp_drv_pdata *dp)
{
spin_lock_init(&dp->event_lock);
dp->workq = create_workqueue("mdss_dp_hpd");
if (!dp->workq) {
pr_err("%s: Error creating workqueue\n", __func__);
return -EPERM;
}
INIT_WORK(&dp->work, mdss_dp_event_work);
INIT_DELAYED_WORK(&dp->hdcp_cb_work, mdss_dp_hdcp_cb_work);
return 0;
}
static void usbpd_connect_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;
}
mdss_dp_update_cable_status(dp_drv, true);
dp_send_events(dp_drv, EV_USBPD_DISCOVER_MODES);
pr_debug("discover_mode event sent\n");
}
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_notify_clients(dp_drv, false);
}
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_hpd_irq_notify_clients() - notifies DP clients of HPD IRQ tear down
* @dp: Display Port Driver data
*
* This function will send a notification to display/audio clients of DP tear
* down during an HPD IRQ. This happens only if HPD IRQ is toggled,
* in which case the user space proceeds with shutdown of DP driver, including
* mainlink disable, and pushing the controller into idle state.
*/
static int mdss_dp_hpd_irq_notify_clients(struct mdss_dp_drv_pdata *dp)
{
const int irq_comp_timeout = HZ * 2;
int ret = 0;
if (dp->hpd_irq_toggled) {
dp->hpd_irq_clients_notified = true;
ret = mdss_dp_notify_clients(dp, false);
if (!IS_ERR_VALUE(ret) && ret) {
reinit_completion(&dp->irq_comp);
ret = wait_for_completion_timeout(&dp->irq_comp,
irq_comp_timeout);
if (ret <= 0) {
pr_warn("irq_comp timed out\n");
ret = -EINVAL;
} else {
ret = 0;
}
}
}
return 0;
}
/**
* mdss_dp_link_retraining() - initiates link retraining
* @dp: Display Port Driver data
*
* This function will initiate link retraining by first notifying
* DP clients and triggering DP shutdown, and then enabling DP after
* notification is done successfully.
*/
static inline void mdss_dp_link_retraining(struct mdss_dp_drv_pdata *dp)
{
if (mdss_dp_hpd_irq_notify_clients(dp))
return;
mdss_dp_on_irq(dp);
}
/**
* 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_retraining(dp);
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_retraining(dp);
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)
{
if (!mdss_dp_is_downstream_port_status_changed(dp))
return -EINVAL;
return mdss_dp_edid_read(dp);
}
/**
* 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;
dp->hpd_irq_on = true;
mdss_dp_aux_parse_sink_status_field(dp);
ret = mdss_dp_process_link_training_request(dp);
if (!ret)
goto exit;
ret = mdss_dp_process_link_status_update(dp);
if (!ret)
goto exit;
ret = mdss_dp_process_downstream_port_status_change(dp);
if (!ret)
goto exit;
pr_debug("done\n");
exit:
mdss_dp_reset_test_data(dp);
return ret;
}
/**
* mdss_dp_process_hpd_irq_low() - handle HPD IRQ transition to LOW
* @dp: Display Port Driver data
*
* This function will handle the HPD IRQ state transitions from HIGH to LOW,
* indicating the end of a test request.
*/
static int mdss_dp_process_hpd_irq_low(struct mdss_dp_drv_pdata *dp)
{
if (!dp->hpd_irq_clients_notified)
return -EINVAL;
pr_debug("enter: HPD IRQ low\n");
dp->hpd_irq_on = false;
dp->hpd_irq_clients_notified = false;
mdss_dp_update_cable_status(dp, false);
mdss_dp_mainlink_push_idle(&dp->panel_data);
mdss_dp_off_hpd(dp);
mdss_dp_reset_test_data(dp);
pr_debug("done\n");
return 0;
}
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;
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;
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:
dp_drv->alt_mode.dp_status.response = *vdos;
mdss_dp_usbpd_ext_dp_status(&dp_drv->alt_mode.dp_status);
dp_drv->hpd_irq_toggled = dp_drv->hpd_irq_on !=
dp_drv->alt_mode.dp_status.hpd_irq;
if (dp_drv->alt_mode.dp_status.hpd_irq) {
pr_debug("Attention: hpd_irq high\n");
if (dp_drv->power_on && dp_drv->hdcp.ops &&
dp_drv->hdcp.ops->cp_irq)
dp_drv->hdcp.ops->cp_irq(dp_drv->hdcp.data);
if (!mdss_dp_process_hpd_irq_high(dp_drv))
break;
} else if (dp_drv->hpd_irq_toggled) {
if (!mdss_dp_process_hpd_irq_low(dp_drv))
break;
}
if (!dp_drv->alt_mode.dp_status.hpd_high) {
pr_debug("Attention: HPD low\n");
mdss_dp_update_cable_status(dp_drv, false);
mdss_dp_notify_clients(dp_drv, false);
pr_debug("Attention: Notified clients\n");
break;
}
pr_debug("Attention: HPD high\n");
mdss_dp_update_cable_status(dp_drv, true);
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);
else
dp_send_events(dp_drv, EV_USBPD_DP_CONFIGURE);
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;
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");
if (dp_drv->alt_mode.dp_status.hpd_high)
mdss_dp_host_init(&dp_drv->panel_data);
break;
default:
pr_err("unknown cmd: %d\n", cmd);
break;
}
}
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->pd_msg_mutex);
mutex_init(&dp_drv->hdcp_mutex);
spin_lock_init(&dp_drv->lock);
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_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;
mdss_dp_reset_test_data(dp_drv);
init_completion(&dp_drv->irq_comp);
pr_debug("done\n");
dp_send_events(dp_drv, EV_USBPD_DISCOVER_MODES);
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_drv = NULL;
if (!dev) {
pr_err("%s:Invalid input\n", __func__);
return NULL;
}
dp_drv = dev_get_drvdata(dev);
if (!dp_drv) {
pr_err("%s:Invalid dp driver\n", __func__);
return NULL;
}
return dp_drv->hdcp.data;
}
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 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");