summaryrefslogtreecommitdiff
path: root/msm/dp/dp_ctrl.c
diff options
context:
space:
mode:
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);
+}