summaryrefslogtreecommitdiff
path: root/msm/dp/dp_ctrl.c
diff options
context:
space:
mode:
authorNarendra Muppalla <NarendraM@codeaurora.org>2019-04-02 14:23:55 -0700
committerNarendra Muppalla <NarendraM@codeaurora.org>2019-04-14 22:20:59 -0700
commit37098534569a62094344632c265171981957e012 (patch)
tree6135925b0706ad2165e0ba6a5ae8612f885e425a /msm/dp/dp_ctrl.c
parentda3538d49ce4568701176ca3651272a5d99b1e4d (diff)
downloaddisplay-drivers-37098534569a62094344632c265171981957e012.tar.gz
Display drivers kernel project initial snapshot
This change brings msm display driver including sde, dp, dsi, rotator, dsi pll and dp pll from base 4.19 kernel project. It is first source code snapshot from base kernel project. Change-Id: Iec864c064ce5ea04e170f24414c728684002f284 Signed-off-by: Narendra Muppalla <NarendraM@codeaurora.org>
Diffstat (limited to 'msm/dp/dp_ctrl.c')
-rw-r--r--msm/dp/dp_ctrl.c1317
1 files changed, 1317 insertions, 0 deletions
diff --git a/msm/dp/dp_ctrl.c b/msm/dp/dp_ctrl.c
new file mode 100644
index 00000000..0d1b061a
--- /dev/null
+++ b/msm/dp/dp_ctrl.c
@@ -0,0 +1,1317 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
+
+#include <linux/types.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <drm/drm_fixed.h>
+
+#include "dp_ctrl.h"
+
+#define DP_MST_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__)
+
+#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0)
+#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3)
+
+#define DP_CTRL_INTR_MST_DP0_VCPF_SENT BIT(0)
+#define DP_CTRL_INTR_MST_DP1_VCPF_SENT BIT(3)
+
+/* dp state ctrl */
+#define ST_TRAIN_PATTERN_1 BIT(0)
+#define ST_TRAIN_PATTERN_2 BIT(1)
+#define ST_TRAIN_PATTERN_3 BIT(2)
+#define ST_TRAIN_PATTERN_4 BIT(3)
+#define ST_SYMBOL_ERR_RATE_MEASUREMENT BIT(4)
+#define ST_PRBS7 BIT(5)
+#define ST_CUSTOM_80_BIT_PATTERN BIT(6)
+#define ST_SEND_VIDEO BIT(7)
+#define ST_PUSH_IDLE BIT(8)
+#define MST_DP0_PUSH_VCPF BIT(12)
+#define MST_DP0_FORCE_VCPF BIT(13)
+#define MST_DP1_PUSH_VCPF BIT(14)
+#define MST_DP1_FORCE_VCPF BIT(15)
+
+#define MR_LINK_TRAINING1 0x8
+#define MR_LINK_SYMBOL_ERM 0x80
+#define MR_LINK_PRBS7 0x100
+#define MR_LINK_CUSTOM80 0x200
+#define MR_LINK_TRAINING4 0x40
+
+struct dp_mst_ch_slot_info {
+ u32 start_slot;
+ u32 tot_slots;
+};
+
+struct dp_mst_channel_info {
+ struct dp_mst_ch_slot_info slot_info[DP_STREAM_MAX];
+};
+
+struct dp_ctrl_private {
+ struct dp_ctrl dp_ctrl;
+
+ struct device *dev;
+ struct dp_aux *aux;
+ struct dp_panel *panel;
+ struct dp_link *link;
+ struct dp_power *power;
+ struct dp_parser *parser;
+ struct dp_catalog_ctrl *catalog;
+
+ struct completion idle_comp;
+ struct completion video_comp;
+
+ bool orientation;
+ bool power_on;
+ bool mst_mode;
+ bool fec_mode;
+
+ atomic_t aborted;
+
+ u32 vic;
+ u32 stream_count;
+ struct dp_mst_channel_info mst_ch_info;
+};
+
+enum notification_status {
+ NOTIFY_UNKNOWN,
+ NOTIFY_CONNECT,
+ NOTIFY_DISCONNECT,
+ NOTIFY_CONNECT_IRQ_HPD,
+ NOTIFY_DISCONNECT_IRQ_HPD,
+};
+
+static void dp_ctrl_idle_patterns_sent(struct dp_ctrl_private *ctrl)
+{
+ pr_debug("idle_patterns_sent\n");
+ complete(&ctrl->idle_comp);
+}
+
+static void dp_ctrl_video_ready(struct dp_ctrl_private *ctrl)
+{
+ pr_debug("dp_video_ready\n");
+ complete(&ctrl->video_comp);
+}
+
+static void dp_ctrl_abort(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ atomic_set(&ctrl->aborted, 1);
+}
+
+static void dp_ctrl_state_ctrl(struct dp_ctrl_private *ctrl, u32 state)
+{
+ ctrl->catalog->state_ctrl(ctrl->catalog, state);
+}
+
+static void dp_ctrl_push_idle(struct dp_ctrl_private *ctrl,
+ enum dp_stream_id strm)
+{
+ int const idle_pattern_completion_timeout_ms = HZ / 10;
+ u32 state = 0x0;
+
+ if (!ctrl->power_on)
+ return;
+
+ if (!ctrl->mst_mode) {
+ state = ST_PUSH_IDLE;
+ goto trigger_idle;
+ }
+
+ if (strm >= DP_STREAM_MAX) {
+ pr_err("mst push idle, invalid stream:%d\n", strm);
+ return;
+ }
+
+ state |= (strm == DP_STREAM_0) ? MST_DP0_PUSH_VCPF : MST_DP1_PUSH_VCPF;
+
+trigger_idle:
+ reinit_completion(&ctrl->idle_comp);
+ dp_ctrl_state_ctrl(ctrl, state);
+
+ if (!wait_for_completion_timeout(&ctrl->idle_comp,
+ idle_pattern_completion_timeout_ms))
+ pr_warn("time out\n");
+ else
+ pr_debug("mainlink off done\n");
+}
+
+/**
+ * dp_ctrl_configure_source_link_params() - configures DP TX source params
+ * @ctrl: Display Port Driver data
+ * @enable: enable or disable DP transmitter
+ *
+ * Configures the DP transmitter source params including details such as lane
+ * configuration, output format and sink/panel timing information.
+ */
+static void dp_ctrl_configure_source_link_params(struct dp_ctrl_private *ctrl,
+ bool enable)
+{
+ if (enable) {
+ ctrl->catalog->lane_mapping(ctrl->catalog, ctrl->orientation,
+ ctrl->parser->l_map);
+ ctrl->catalog->lane_pnswap(ctrl->catalog,
+ ctrl->parser->l_pnswap);
+ ctrl->catalog->mst_config(ctrl->catalog, ctrl->mst_mode);
+ ctrl->catalog->config_ctrl(ctrl->catalog,
+ ctrl->link->link_params.lane_count);
+ ctrl->catalog->mainlink_levels(ctrl->catalog,
+ ctrl->link->link_params.lane_count);
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, true);
+ } else {
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+ }
+}
+
+static void dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl)
+{
+ if (!wait_for_completion_timeout(&ctrl->video_comp, HZ / 2))
+ pr_warn("SEND_VIDEO time out\n");
+}
+
+static int dp_ctrl_update_sink_vx_px(struct dp_ctrl_private *ctrl,
+ u32 voltage_level, u32 pre_emphasis_level)
+{
+ int i;
+ u8 buf[4];
+ u32 max_level_reached = 0;
+
+ if (voltage_level == DP_LINK_VOLTAGE_MAX) {
+ pr_debug("max. voltage swing level reached %d\n",
+ voltage_level);
+ max_level_reached |= BIT(2);
+ }
+
+ if (pre_emphasis_level == DP_LINK_PRE_EMPHASIS_MAX) {
+ pr_debug("max. pre-emphasis level reached %d\n",
+ pre_emphasis_level);
+ max_level_reached |= BIT(5);
+ }
+
+ pre_emphasis_level <<= 3;
+
+ for (i = 0; i < 4; i++)
+ buf[i] = voltage_level | pre_emphasis_level | max_level_reached;
+
+ pr_debug("sink: p|v=0x%x\n", voltage_level | pre_emphasis_level);
+ return drm_dp_dpcd_write(ctrl->aux->drm_aux, 0x103, buf, 4);
+}
+
+static int dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl)
+{
+ struct dp_link *link = ctrl->link;
+
+ ctrl->catalog->update_vx_px(ctrl->catalog,
+ link->phy_params.v_level, link->phy_params.p_level);
+
+ return dp_ctrl_update_sink_vx_px(ctrl, link->phy_params.v_level,
+ link->phy_params.p_level);
+}
+
+static int dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl,
+ u8 pattern)
+{
+ u8 buf[4];
+
+ pr_debug("sink: pattern=%x\n", pattern);
+
+ buf[0] = pattern;
+ return drm_dp_dpcd_write(ctrl->aux->drm_aux,
+ DP_TRAINING_PATTERN_SET, buf, 1);
+}
+
+static int dp_ctrl_read_link_status(struct dp_ctrl_private *ctrl,
+ u8 *link_status)
+{
+ int ret = 0, len;
+ u32 const offset = DP_LANE_ALIGN_STATUS_UPDATED - DP_LANE0_1_STATUS;
+ u32 link_status_read_max_retries = 100;
+
+ while (--link_status_read_max_retries) {
+ len = drm_dp_dpcd_read_link_status(ctrl->aux->drm_aux,
+ link_status);
+ if (len != DP_LINK_STATUS_SIZE) {
+ pr_err("DP link status read failed, err: %d\n", len);
+ ret = len;
+ break;
+ }
+
+ if (!(link_status[offset] & DP_LINK_STATUS_UPDATED))
+ break;
+ }
+
+ return ret;
+}
+
+static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl)
+{
+ int tries, old_v_level, ret = 0;
+ u8 link_status[DP_LINK_STATUS_SIZE];
+ int const maximum_retries = 5;
+
+ ctrl->aux->state &= ~DP_STATE_TRAIN_1_FAILED;
+ ctrl->aux->state &= ~DP_STATE_TRAIN_1_SUCCEEDED;
+ ctrl->aux->state |= DP_STATE_TRAIN_1_STARTED;
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+ ctrl->catalog->set_pattern(ctrl->catalog, 0x01);
+ ret = dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 |
+ DP_LINK_SCRAMBLING_DISABLE); /* train_1 */
+ if (ret <= 0) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ ret = dp_ctrl_update_vx_px(ctrl);
+ if (ret <= 0) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ tries = 0;
+ old_v_level = ctrl->link->phy_params.v_level;
+ while (1) {
+ if (atomic_read(&ctrl->aborted)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ drm_dp_link_train_clock_recovery_delay(ctrl->panel->dpcd);
+
+ ret = dp_ctrl_read_link_status(ctrl, link_status);
+ if (ret)
+ break;
+
+ if (drm_dp_clock_recovery_ok(link_status,
+ ctrl->link->link_params.lane_count)) {
+ break;
+ }
+
+ if (ctrl->link->phy_params.v_level == DP_LINK_VOLTAGE_MAX) {
+ pr_err_ratelimited("max v_level reached\n");
+ ret = -EAGAIN;
+ break;
+ }
+
+ if (old_v_level == ctrl->link->phy_params.v_level) {
+ tries++;
+ if (tries >= maximum_retries) {
+ pr_err("max tries reached\n");
+ ret = -ETIMEDOUT;
+ break;
+ }
+ } else {
+ tries = 0;
+ old_v_level = ctrl->link->phy_params.v_level;
+ }
+
+ pr_debug("clock recovery not done, adjusting vx px\n");
+
+ ctrl->link->adjust_levels(ctrl->link, link_status);
+ ret = dp_ctrl_update_vx_px(ctrl);
+ if (ret <= 0) {
+ ret = -EINVAL;
+ break;
+ }
+ }
+end:
+ ctrl->aux->state &= ~DP_STATE_TRAIN_1_STARTED;
+
+ if (ret)
+ ctrl->aux->state |= DP_STATE_TRAIN_1_FAILED;
+ else
+ ctrl->aux->state |= DP_STATE_TRAIN_1_SUCCEEDED;
+
+ return ret;
+}
+
+static int dp_ctrl_link_rate_down_shift(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ if (!ctrl)
+ return -EINVAL;
+
+ switch (ctrl->link->link_params.bw_code) {
+ case DP_LINK_BW_8_1:
+ ctrl->link->link_params.bw_code = DP_LINK_BW_5_4;
+ break;
+ case DP_LINK_BW_5_4:
+ ctrl->link->link_params.bw_code = DP_LINK_BW_2_7;
+ break;
+ case DP_LINK_BW_2_7:
+ case DP_LINK_BW_1_62:
+ default:
+ ctrl->link->link_params.bw_code = DP_LINK_BW_1_62;
+ break;
+ }
+
+ pr_debug("new bw code=0x%x\n", ctrl->link->link_params.bw_code);
+
+ return ret;
+}
+
+static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl)
+{
+ dp_ctrl_train_pattern_set(ctrl, 0);
+ drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd);
+}
+
+static int dp_ctrl_link_training_2(struct dp_ctrl_private *ctrl)
+{
+ int tries = 0, ret = 0;
+ char pattern;
+ int const maximum_retries = 5;
+ u8 link_status[DP_LINK_STATUS_SIZE];
+
+ ctrl->aux->state &= ~DP_STATE_TRAIN_2_FAILED;
+ ctrl->aux->state &= ~DP_STATE_TRAIN_2_SUCCEEDED;
+ ctrl->aux->state |= DP_STATE_TRAIN_2_STARTED;
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+ if (drm_dp_tps3_supported(ctrl->panel->dpcd))
+ pattern = DP_TRAINING_PATTERN_3;
+ else
+ pattern = DP_TRAINING_PATTERN_2;
+
+ ret = dp_ctrl_update_vx_px(ctrl);
+ if (ret <= 0) {
+ ret = -EINVAL;
+ goto end;
+ }
+ ctrl->catalog->set_pattern(ctrl->catalog, pattern);
+ ret = dp_ctrl_train_pattern_set(ctrl,
+ pattern | DP_RECOVERED_CLOCK_OUT_EN);
+ if (ret <= 0) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ do {
+ if (atomic_read(&ctrl->aborted)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd);
+
+ ret = dp_ctrl_read_link_status(ctrl, link_status);
+ if (ret)
+ break;
+
+ if (drm_dp_channel_eq_ok(link_status,
+ ctrl->link->link_params.lane_count))
+ break;
+
+ if (tries > maximum_retries) {
+ ret = -ETIMEDOUT;
+ break;
+ }
+ tries++;
+
+ ctrl->link->adjust_levels(ctrl->link, link_status);
+ ret = dp_ctrl_update_vx_px(ctrl);
+ if (ret <= 0) {
+ ret = -EINVAL;
+ break;
+ }
+ } while (1);
+end:
+ ctrl->aux->state &= ~DP_STATE_TRAIN_2_STARTED;
+
+ if (ret)
+ ctrl->aux->state |= DP_STATE_TRAIN_2_FAILED;
+ else
+ ctrl->aux->state |= DP_STATE_TRAIN_2_SUCCEEDED;
+ return ret;
+}
+
+static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+ u8 encoding = 0x1;
+ struct drm_dp_link link_info = {0};
+
+ ctrl->link->phy_params.p_level = 0;
+ ctrl->link->phy_params.v_level = 0;
+
+ link_info.num_lanes = ctrl->link->link_params.lane_count;
+ link_info.rate = drm_dp_bw_code_to_link_rate(
+ ctrl->link->link_params.bw_code);
+ link_info.capabilities = ctrl->panel->link_info.capabilities;
+
+ ret = drm_dp_link_configure(ctrl->aux->drm_aux, &link_info);
+ if (ret)
+ goto end;
+
+ ret = drm_dp_dpcd_write(ctrl->aux->drm_aux,
+ DP_MAIN_LINK_CHANNEL_CODING_SET, &encoding, 1);
+ if (ret <= 0) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ ret = dp_ctrl_link_train_1(ctrl);
+ if (ret) {
+ pr_err("link training #1 failed\n");
+ goto end;
+ }
+
+ /* print success info as this is a result of user initiated action */
+ pr_info("link training #1 successful\n");
+
+ ret = dp_ctrl_link_training_2(ctrl);
+ if (ret) {
+ pr_err("link training #2 failed\n");
+ goto end;
+ }
+
+ /* print success info as this is a result of user initiated action */
+ pr_info("link training #2 successful\n");
+
+end:
+ dp_ctrl_state_ctrl(ctrl, 0);
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+ dp_ctrl_clear_training_pattern(ctrl);
+ return ret;
+}
+
+static int dp_ctrl_setup_main_link(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+ const unsigned int fec_cfg_dpcd = 0x120;
+
+ if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN)
+ goto end;
+
+ /*
+ * 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.
+ */
+ ctrl->catalog->reset(ctrl->catalog);
+
+ if (ctrl->fec_mode)
+ drm_dp_dpcd_writeb(ctrl->aux->drm_aux, fec_cfg_dpcd, 0x01);
+
+ ret = dp_ctrl_link_train(ctrl);
+
+end:
+ return ret;
+}
+
+static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl,
+ char *name, enum dp_pm_type clk_type, u32 rate)
+{
+ u32 num = ctrl->parser->mp[clk_type].num_clk;
+ struct dss_clk *cfg = ctrl->parser->mp[clk_type].clk_config;
+
+ while (num && strcmp(cfg->clk_name, name)) {
+ num--;
+ cfg++;
+ }
+
+ pr_debug("setting rate=%d on clk=%s\n", rate, name);
+
+ if (num)
+ cfg->rate = rate;
+ else
+ pr_err("%s clock could not be set with rate %d\n", name, rate);
+}
+
+static int dp_ctrl_enable_link_clock(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+ u32 rate = drm_dp_bw_code_to_link_rate(ctrl->link->link_params.bw_code);
+ enum dp_pm_type type = DP_LINK_PM;
+
+ pr_debug("rate=%d\n", rate);
+
+ dp_ctrl_set_clock_rate(ctrl, "link_clk", type, rate);
+
+ ret = ctrl->power->clk_enable(ctrl->power, type, true);
+ if (ret) {
+ pr_err("Unabled to start link clocks\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void dp_ctrl_disable_link_clock(struct dp_ctrl_private *ctrl)
+{
+ ctrl->power->clk_enable(ctrl->power, DP_LINK_PM, false);
+}
+
+static int dp_ctrl_link_setup(struct dp_ctrl_private *ctrl, bool shallow)
+{
+ int rc = -EINVAL;
+ u32 link_train_max_retries = 100;
+ struct dp_catalog_ctrl *catalog;
+ struct dp_link_params *link_params;
+
+ catalog = ctrl->catalog;
+ link_params = &ctrl->link->link_params;
+
+ catalog->phy_lane_cfg(catalog, ctrl->orientation,
+ link_params->lane_count);
+
+ while (1) {
+ pr_debug("bw_code=%d, lane_count=%d\n",
+ link_params->bw_code, link_params->lane_count);
+
+ rc = dp_ctrl_enable_link_clock(ctrl);
+ if (rc)
+ break;
+
+ dp_ctrl_configure_source_link_params(ctrl, true);
+
+ rc = dp_ctrl_setup_main_link(ctrl);
+ if (!rc)
+ break;
+
+ /*
+ * Shallow means link training failure is not important.
+ * If it fails, we still keep the link clocks on.
+ * In this mode, the system expects DP to be up
+ * even though the cable is removed. Disconnect interrupt
+ * will eventually trigger and shutdown DP.
+ */
+ if (shallow) {
+ rc = 0;
+ break;
+ }
+
+ if (!link_train_max_retries-- || atomic_read(&ctrl->aborted))
+ break;
+
+ dp_ctrl_link_rate_down_shift(ctrl);
+
+ dp_ctrl_configure_source_link_params(ctrl, false);
+ dp_ctrl_disable_link_clock(ctrl);
+
+ /* hw recommended delays before retrying link training */
+ msleep(20);
+ }
+
+ return rc;
+}
+
+static int dp_ctrl_enable_stream_clocks(struct dp_ctrl_private *ctrl,
+ struct dp_panel *dp_panel)
+{
+ int ret = 0;
+ u32 pclk;
+ enum dp_pm_type clk_type;
+ char clk_name[32] = "";
+
+ ret = ctrl->power->set_pixel_clk_parent(ctrl->power,
+ dp_panel->stream_id);
+
+ if (ret)
+ return ret;
+
+ if (dp_panel->stream_id == DP_STREAM_0) {
+ clk_type = DP_STREAM0_PM;
+ strlcpy(clk_name, "strm0_pixel_clk", 32);
+ } else if (dp_panel->stream_id == DP_STREAM_1) {
+ clk_type = DP_STREAM1_PM;
+ strlcpy(clk_name, "strm1_pixel_clk", 32);
+ } else {
+ pr_err("Invalid stream:%d for clk enable\n",
+ dp_panel->stream_id);
+ return -EINVAL;
+ }
+
+ pclk = dp_panel->pinfo.widebus_en ?
+ (dp_panel->pinfo.pixel_clk_khz >> 1) :
+ (dp_panel->pinfo.pixel_clk_khz);
+
+ dp_ctrl_set_clock_rate(ctrl, clk_name, clk_type, pclk);
+
+ ret = ctrl->power->clk_enable(ctrl->power, clk_type, true);
+ if (ret) {
+ pr_err("Unabled to start stream:%d clocks\n",
+ dp_panel->stream_id);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int dp_ctrl_disable_stream_clocks(struct dp_ctrl_private *ctrl,
+ struct dp_panel *dp_panel)
+{
+ int ret = 0;
+
+ if (dp_panel->stream_id == DP_STREAM_0) {
+ return ctrl->power->clk_enable(ctrl->power,
+ DP_STREAM0_PM, false);
+ } else if (dp_panel->stream_id == DP_STREAM_1) {
+ return ctrl->power->clk_enable(ctrl->power,
+ DP_STREAM1_PM, false);
+ } else {
+ pr_err("Invalid stream:%d for clk disable\n",
+ dp_panel->stream_id);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+static int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip, bool reset)
+{
+ struct dp_ctrl_private *ctrl;
+ struct dp_catalog_ctrl *catalog;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return -EINVAL;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ ctrl->orientation = flip;
+ catalog = ctrl->catalog;
+
+ if (reset) {
+ catalog->usb_reset(ctrl->catalog, flip);
+ catalog->phy_reset(ctrl->catalog);
+ }
+ catalog->enable_irq(ctrl->catalog, true);
+ atomic_set(&ctrl->aborted, 0);
+
+ return 0;
+}
+
+/**
+ * dp_ctrl_host_deinit() - Uninitialize DP controller
+ * @ctrl: Display Port Driver data
+ *
+ * Perform required steps to uninitialize DP controller
+ * and its resources.
+ */
+static void dp_ctrl_host_deinit(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ ctrl->catalog->enable_irq(ctrl->catalog, false);
+
+ pr_debug("Host deinitialized successfully\n");
+}
+
+static void dp_ctrl_send_video(struct dp_ctrl_private *ctrl)
+{
+ ctrl->catalog->state_ctrl(ctrl->catalog, ST_SEND_VIDEO);
+}
+
+static int dp_ctrl_link_maintenance(struct dp_ctrl *dp_ctrl)
+{
+ int ret = 0;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return -EINVAL;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ ctrl->aux->state &= ~DP_STATE_LINK_MAINTENANCE_COMPLETED;
+ ctrl->aux->state &= ~DP_STATE_LINK_MAINTENANCE_FAILED;
+
+ if (!ctrl->power_on) {
+ pr_err("ctrl off\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ if (atomic_read(&ctrl->aborted))
+ goto end;
+
+ ctrl->aux->state |= DP_STATE_LINK_MAINTENANCE_STARTED;
+ ret = dp_ctrl_setup_main_link(ctrl);
+ ctrl->aux->state &= ~DP_STATE_LINK_MAINTENANCE_STARTED;
+
+ if (ret) {
+ ctrl->aux->state |= DP_STATE_LINK_MAINTENANCE_FAILED;
+ goto end;
+ }
+
+ ctrl->aux->state |= DP_STATE_LINK_MAINTENANCE_COMPLETED;
+
+ if (ctrl->stream_count) {
+ dp_ctrl_send_video(ctrl);
+ dp_ctrl_wait4video_ready(ctrl);
+ }
+end:
+ return ret;
+}
+
+static void dp_ctrl_process_phy_test_request(struct dp_ctrl *dp_ctrl)
+{
+ int ret = 0;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (!ctrl->link->phy_params.phy_test_pattern_sel) {
+ pr_debug("no test pattern selected by sink\n");
+ return;
+ }
+
+ pr_debug("start\n");
+
+ /*
+ * 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.
+ */
+ ctrl->catalog->reset(ctrl->catalog);
+ ctrl->dp_ctrl.stream_pre_off(&ctrl->dp_ctrl, ctrl->panel);
+ ctrl->dp_ctrl.stream_off(&ctrl->dp_ctrl, ctrl->panel);
+ ctrl->dp_ctrl.off(&ctrl->dp_ctrl);
+
+ ctrl->aux->init(ctrl->aux, ctrl->parser->aux_cfg);
+
+ ret = ctrl->dp_ctrl.on(&ctrl->dp_ctrl, ctrl->mst_mode,
+ ctrl->fec_mode, false);
+ if (ret)
+ pr_err("failed to enable DP controller\n");
+
+ ctrl->dp_ctrl.stream_on(&ctrl->dp_ctrl, ctrl->panel);
+ pr_debug("end\n");
+}
+
+static void dp_ctrl_send_phy_test_pattern(struct dp_ctrl_private *ctrl)
+{
+ bool success = false;
+ u32 pattern_sent = 0x0;
+ u32 pattern_requested = ctrl->link->phy_params.phy_test_pattern_sel;
+
+ dp_ctrl_update_vx_px(ctrl);
+ ctrl->catalog->send_phy_pattern(ctrl->catalog, pattern_requested);
+ ctrl->link->send_test_response(ctrl->link);
+
+ pattern_sent = ctrl->catalog->read_phy_pattern(ctrl->catalog);
+ pr_debug("pattern_request: %s. pattern_sent: 0x%x\n",
+ dp_link_get_phy_test_pattern(pattern_requested),
+ pattern_sent);
+
+ switch (pattern_sent) {
+ case MR_LINK_TRAINING1:
+ if (pattern_requested ==
+ DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING)
+ success = true;
+ break;
+ case MR_LINK_SYMBOL_ERM:
+ if ((pattern_requested ==
+ DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT)
+ || (pattern_requested ==
+ DP_TEST_PHY_PATTERN_CP2520_PATTERN_1))
+ success = true;
+ break;
+ case MR_LINK_PRBS7:
+ if (pattern_requested == DP_TEST_PHY_PATTERN_PRBS7)
+ success = true;
+ break;
+ case MR_LINK_CUSTOM80:
+ if (pattern_requested ==
+ DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN)
+ success = true;
+ break;
+ case MR_LINK_TRAINING4:
+ if (pattern_requested ==
+ DP_TEST_PHY_PATTERN_CP2520_PATTERN_3)
+ success = true;
+ break;
+ default:
+ success = false;
+ break;
+ }
+
+ pr_debug("%s: %s\n", success ? "success" : "failed",
+ dp_link_get_phy_test_pattern(pattern_requested));
+}
+
+static void dp_ctrl_mst_calculate_rg(struct dp_ctrl_private *ctrl,
+ struct dp_panel *panel, u32 *p_x_int, u32 *p_y_frac_enum)
+{
+ u64 min_slot_cnt, max_slot_cnt;
+ u64 raw_target_sc, target_sc_fixp;
+ u64 ts_denom, ts_enum, ts_int;
+ u64 pclk = panel->pinfo.pixel_clk_khz;
+ u64 lclk = panel->link_info.rate;
+ u64 lanes = panel->link_info.num_lanes;
+ u64 bpp = panel->pinfo.bpp;
+ u64 pbn = panel->pbn;
+ u64 numerator, denominator, temp, temp1, temp2;
+ u32 x_int = 0, y_frac_enum = 0;
+ u64 target_strm_sym, ts_int_fixp, ts_frac_fixp, y_frac_enum_fixp;
+
+ if (panel->pinfo.comp_info.comp_ratio)
+ bpp = panel->pinfo.comp_info.dsc_info.bpp;
+
+ /* min_slot_cnt */
+ numerator = pclk * bpp * 64 * 1000;
+ denominator = lclk * lanes * 8 * 1000;
+ min_slot_cnt = drm_fixp_from_fraction(numerator, denominator);
+
+ /* max_slot_cnt */
+ numerator = pbn * 54 * 1000;
+ denominator = lclk * lanes;
+ max_slot_cnt = drm_fixp_from_fraction(numerator, denominator);
+
+ /* raw_target_sc */
+ numerator = max_slot_cnt + min_slot_cnt;
+ denominator = drm_fixp_from_fraction(2, 1);
+ raw_target_sc = drm_fixp_div(numerator, denominator);
+
+ pr_debug("raw_target_sc before overhead:0x%llx\n", raw_target_sc);
+ pr_debug("dsc_overhead_fp:0x%llx\n", panel->pinfo.dsc_overhead_fp);
+
+ /* apply fec and dsc overhead factor */
+ if (panel->pinfo.dsc_overhead_fp)
+ raw_target_sc = drm_fixp_mul(raw_target_sc,
+ panel->pinfo.dsc_overhead_fp);
+
+ if (panel->fec_overhead_fp)
+ raw_target_sc = drm_fixp_mul(raw_target_sc,
+ panel->fec_overhead_fp);
+
+ pr_debug("raw_target_sc after overhead:0x%llx\n", raw_target_sc);
+
+ /* target_sc */
+ temp = drm_fixp_from_fraction(256 * lanes, 1);
+ numerator = drm_fixp_mul(raw_target_sc, temp);
+ denominator = drm_fixp_from_fraction(256 * lanes, 1);
+ target_sc_fixp = drm_fixp_div(numerator, denominator);
+
+ ts_enum = 256 * lanes;
+ ts_denom = drm_fixp_from_fraction(256 * lanes, 1);
+ ts_int = drm_fixp2int(target_sc_fixp);
+
+ temp = drm_fixp2int_ceil(raw_target_sc);
+ if (temp != ts_int) {
+ temp = drm_fixp_from_fraction(ts_int, 1);
+ temp1 = raw_target_sc - temp;
+ temp2 = drm_fixp_mul(temp1, ts_denom);
+ ts_enum = drm_fixp2int(temp2);
+ }
+
+ /* target_strm_sym */
+ ts_int_fixp = drm_fixp_from_fraction(ts_int, 1);
+ ts_frac_fixp = drm_fixp_from_fraction(ts_enum, drm_fixp2int(ts_denom));
+ temp = ts_int_fixp + ts_frac_fixp;
+ temp1 = drm_fixp_from_fraction(lanes, 1);
+ target_strm_sym = drm_fixp_mul(temp, temp1);
+
+ /* x_int */
+ x_int = drm_fixp2int(target_strm_sym);
+
+ /* y_enum_frac */
+ temp = drm_fixp_from_fraction(x_int, 1);
+ temp1 = target_strm_sym - temp;
+ temp2 = drm_fixp_from_fraction(256, 1);
+ y_frac_enum_fixp = drm_fixp_mul(temp1, temp2);
+
+ temp1 = drm_fixp2int(y_frac_enum_fixp);
+ temp2 = drm_fixp2int_ceil(y_frac_enum_fixp);
+
+ y_frac_enum = (u32)((temp1 == temp2) ? temp1 : temp1 + 1);
+
+ panel->mst_target_sc = raw_target_sc;
+ *p_x_int = x_int;
+ *p_y_frac_enum = y_frac_enum;
+
+ pr_debug("x_int: %d, y_frac_enum: %d\n", x_int, y_frac_enum);
+}
+
+static int dp_ctrl_mst_send_act(struct dp_ctrl_private *ctrl)
+{
+ bool act_complete;
+
+ if (!ctrl->mst_mode)
+ return 0;
+
+ ctrl->catalog->trigger_act(ctrl->catalog);
+ msleep(20); /* needs 1 frame time */
+
+ ctrl->catalog->read_act_complete_sts(ctrl->catalog, &act_complete);
+
+ if (!act_complete)
+ pr_err("mst act trigger complete failed\n");
+ else
+ DP_MST_DEBUG("mst ACT trigger complete SUCCESS\n");
+
+ return 0;
+}
+
+static void dp_ctrl_mst_stream_setup(struct dp_ctrl_private *ctrl,
+ struct dp_panel *panel)
+{
+ u32 x_int, y_frac_enum, lanes, bw_code;
+ int i;
+
+ if (!ctrl->mst_mode)
+ return;
+
+ DP_MST_DEBUG("mst stream channel allocation\n");
+
+ for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
+ ctrl->catalog->channel_alloc(ctrl->catalog,
+ i,
+ ctrl->mst_ch_info.slot_info[i].start_slot,
+ ctrl->mst_ch_info.slot_info[i].tot_slots);
+ }
+
+ lanes = ctrl->link->link_params.lane_count;
+ bw_code = ctrl->link->link_params.bw_code;
+
+ dp_ctrl_mst_calculate_rg(ctrl, panel, &x_int, &y_frac_enum);
+
+ ctrl->catalog->update_rg(ctrl->catalog, panel->stream_id,
+ x_int, y_frac_enum);
+
+ DP_MST_DEBUG("mst stream:%d, start_slot:%d, tot_slots:%d\n",
+ panel->stream_id,
+ panel->channel_start_slot, panel->channel_total_slots);
+
+ DP_MST_DEBUG("mst lane_cnt:%d, bw:%d, x_int:%d, y_frac:%d\n",
+ lanes, bw_code, x_int, y_frac_enum);
+}
+
+static void dp_ctrl_fec_dsc_setup(struct dp_ctrl_private *ctrl)
+{
+ u8 fec_sts = 0;
+ int rlen;
+ u32 dsc_enable;
+ const unsigned int fec_sts_dpcd = 0x280;
+
+ if (ctrl->stream_count || !ctrl->fec_mode)
+ return;
+
+ ctrl->catalog->fec_config(ctrl->catalog, ctrl->fec_mode);
+
+ /* wait for controller to start fec sequence */
+ usleep_range(900, 1000);
+ drm_dp_dpcd_readb(ctrl->aux->drm_aux, fec_sts_dpcd, &fec_sts);
+ pr_debug("sink fec status:%d\n", fec_sts);
+
+ dsc_enable = ctrl->fec_mode ? 1 : 0;
+ rlen = drm_dp_dpcd_writeb(ctrl->aux->drm_aux, DP_DSC_ENABLE,
+ dsc_enable);
+ if (rlen < 1)
+ pr_debug("failed to enable sink dsc\n");
+}
+
+static int dp_ctrl_stream_on(struct dp_ctrl *dp_ctrl, struct dp_panel *panel)
+{
+ int rc = 0;
+ bool link_ready = false;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl || !panel)
+ return -EINVAL;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ rc = dp_ctrl_enable_stream_clocks(ctrl, panel);
+ if (rc) {
+ pr_err("failure on stream clock enable\n");
+ return rc;
+ }
+
+ rc = panel->hw_cfg(panel, true);
+ if (rc)
+ return rc;
+
+ if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) {
+ dp_ctrl_send_phy_test_pattern(ctrl);
+ return 0;
+ }
+
+ dp_ctrl_mst_stream_setup(ctrl, panel);
+
+ dp_ctrl_send_video(ctrl);
+
+ dp_ctrl_mst_send_act(ctrl);
+
+ dp_ctrl_wait4video_ready(ctrl);
+
+ dp_ctrl_fec_dsc_setup(ctrl);
+
+ ctrl->stream_count++;
+
+ link_ready = ctrl->catalog->mainlink_ready(ctrl->catalog);
+ pr_debug("mainlink %s\n", link_ready ? "READY" : "NOT READY");
+
+ return rc;
+}
+
+static void dp_ctrl_mst_stream_pre_off(struct dp_ctrl *dp_ctrl,
+ struct dp_panel *panel)
+{
+ struct dp_ctrl_private *ctrl;
+ bool act_complete;
+ int i;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (!ctrl->mst_mode)
+ return;
+
+ for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) {
+ ctrl->catalog->channel_alloc(ctrl->catalog,
+ i,
+ ctrl->mst_ch_info.slot_info[i].start_slot,
+ ctrl->mst_ch_info.slot_info[i].tot_slots);
+ }
+
+ ctrl->catalog->trigger_act(ctrl->catalog);
+ msleep(20); /* needs 1 frame time */
+ ctrl->catalog->read_act_complete_sts(ctrl->catalog, &act_complete);
+
+ if (!act_complete)
+ pr_err("mst stream_off act trigger complete failed\n");
+ else
+ DP_MST_DEBUG("mst stream_off ACT trigger complete SUCCESS\n");
+}
+
+static void dp_ctrl_stream_pre_off(struct dp_ctrl *dp_ctrl,
+ struct dp_panel *panel)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl || !panel) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ dp_ctrl_push_idle(ctrl, panel->stream_id);
+
+ dp_ctrl_mst_stream_pre_off(dp_ctrl, panel);
+}
+
+static void dp_ctrl_stream_off(struct dp_ctrl *dp_ctrl, struct dp_panel *panel)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl || !panel)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (!ctrl->power_on)
+ return;
+
+ panel->hw_cfg(panel, false);
+
+ dp_ctrl_disable_stream_clocks(ctrl, panel);
+ ctrl->stream_count--;
+}
+
+static int dp_ctrl_on(struct dp_ctrl *dp_ctrl, bool mst_mode,
+ bool fec_mode, bool shallow)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+ u32 rate = 0;
+
+ if (!dp_ctrl) {
+ rc = -EINVAL;
+ goto end;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (ctrl->power_on)
+ goto end;
+
+ ctrl->mst_mode = mst_mode;
+ ctrl->fec_mode = fec_mode;
+ rate = ctrl->panel->link_info.rate;
+
+ if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) {
+ pr_debug("using phy test link parameters\n");
+ } else {
+ ctrl->link->link_params.bw_code =
+ drm_dp_link_rate_to_bw_code(rate);
+ ctrl->link->link_params.lane_count =
+ ctrl->panel->link_info.num_lanes;
+ }
+
+ pr_debug("bw_code=%d, lane_count=%d\n",
+ ctrl->link->link_params.bw_code,
+ ctrl->link->link_params.lane_count);
+
+ rc = dp_ctrl_link_setup(ctrl, shallow);
+ ctrl->power_on = true;
+end:
+ return rc;
+}
+
+static void dp_ctrl_off(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (!ctrl->power_on)
+ return;
+
+ dp_ctrl_configure_source_link_params(ctrl, false);
+ ctrl->catalog->reset(ctrl->catalog);
+
+ /* Make sure DP is disabled before clk disable */
+ wmb();
+
+ dp_ctrl_disable_link_clock(ctrl);
+
+ ctrl->mst_mode = false;
+ ctrl->fec_mode = false;
+ ctrl->power_on = false;
+ memset(&ctrl->mst_ch_info, 0, sizeof(ctrl->mst_ch_info));
+ pr_debug("DP off done\n");
+}
+
+static void dp_ctrl_set_mst_channel_info(struct dp_ctrl *dp_ctrl,
+ enum dp_stream_id strm,
+ u32 start_slot, u32 tot_slots)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl || strm >= DP_STREAM_MAX) {
+ pr_err("invalid input\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ ctrl->mst_ch_info.slot_info[strm].start_slot = start_slot;
+ ctrl->mst_ch_info.slot_info[strm].tot_slots = tot_slots;
+}
+
+static void dp_ctrl_isr(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ ctrl->catalog->get_interrupt(ctrl->catalog);
+
+ if (ctrl->catalog->isr & DP_CTRL_INTR_READY_FOR_VIDEO)
+ dp_ctrl_video_ready(ctrl);
+
+ if (ctrl->catalog->isr & DP_CTRL_INTR_IDLE_PATTERN_SENT)
+ dp_ctrl_idle_patterns_sent(ctrl);
+
+ if (ctrl->catalog->isr5 & DP_CTRL_INTR_MST_DP0_VCPF_SENT)
+ dp_ctrl_idle_patterns_sent(ctrl);
+
+ if (ctrl->catalog->isr5 & DP_CTRL_INTR_MST_DP1_VCPF_SENT)
+ dp_ctrl_idle_patterns_sent(ctrl);
+}
+
+struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+ struct dp_ctrl *dp_ctrl;
+
+ if (!in->dev || !in->panel || !in->aux ||
+ !in->link || !in->catalog) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ ctrl = devm_kzalloc(in->dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ init_completion(&ctrl->idle_comp);
+ init_completion(&ctrl->video_comp);
+
+ /* in parameters */
+ ctrl->parser = in->parser;
+ ctrl->panel = in->panel;
+ ctrl->power = in->power;
+ ctrl->aux = in->aux;
+ ctrl->link = in->link;
+ ctrl->catalog = in->catalog;
+ ctrl->dev = in->dev;
+ ctrl->mst_mode = false;
+ ctrl->fec_mode = false;
+
+ dp_ctrl = &ctrl->dp_ctrl;
+
+ /* out parameters */
+ dp_ctrl->init = dp_ctrl_host_init;
+ dp_ctrl->deinit = dp_ctrl_host_deinit;
+ dp_ctrl->on = dp_ctrl_on;
+ dp_ctrl->off = dp_ctrl_off;
+ dp_ctrl->abort = dp_ctrl_abort;
+ dp_ctrl->isr = dp_ctrl_isr;
+ dp_ctrl->link_maintenance = dp_ctrl_link_maintenance;
+ dp_ctrl->process_phy_test_request = dp_ctrl_process_phy_test_request;
+ dp_ctrl->stream_on = dp_ctrl_stream_on;
+ dp_ctrl->stream_off = dp_ctrl_stream_off;
+ dp_ctrl->stream_pre_off = dp_ctrl_stream_pre_off;
+ dp_ctrl->set_mst_channel_info = dp_ctrl_set_mst_channel_info;
+
+ return dp_ctrl;
+error:
+ return ERR_PTR(rc);
+}
+
+void dp_ctrl_put(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ devm_kfree(ctrl->dev, ctrl);
+}