Files
android_kernel_eebbk_sm6150/drivers/video/fbdev/msm/mdss_dp.c
T

4776 lines
116 KiB
C
Raw Normal View History

/* 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 <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/clk.h>
#include <linux/pwm.h>
#include <linux/spinlock_types.h>
#include <linux/kthread.h>
#include <linux/msm_ext_display.h>
#include "mdss_hdcp.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 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");