drm/msm/dp: add support for dp controller
Add support for DisplayPort's hardware controller module.
Provide the functionalities to other modules to program
the DP controller. DP controller modules can power on/off
the controller, send different test patterns, do the link
training and interact with other DP modules.
Change-Id: I42c6a5e30b04728f5120252b85fee7ee6717ec21
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
Signed-off-by: Padmanabhan Komanduru <pkomandu@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
new file mode 100644
index 0000000..7102d8a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -0,0 +1,1396 @@
+/*
+ * Copyright (c) 2012-2017, 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) "[drm-dp] %s: " fmt, __func__
+
+#include <linux/types.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+
+#include "dp_ctrl.h"
+
+#define DP_LINK_RATE_MULTIPLIER 27000000
+#define DP_KHZ_TO_HZ 1000
+#define DP_CRYPTO_CLK_RATE_KHZ 180000
+
+/* sink power state */
+#define SINK_POWER_ON 1
+#define SINK_POWER_OFF 2
+
+#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0)
+#define DP_CTRL_INTR_IDLE_PATTERN_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)
+
+struct dp_vc_tu_mapping_table {
+ u32 vic;
+ u8 lanes;
+ u8 lrate; /* DP_LINK_RATE -> 162(6), 270(10), 540(20), 810 (30) */
+ u8 bpp;
+ u8 valid_boundary_link;
+ u16 delay_start_link;
+ bool boundary_moderation_en;
+ u8 valid_lower_boundary_link;
+ u8 upper_boundary_count;
+ u8 lower_boundary_count;
+ u8 tu_size_minus1;
+};
+
+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;
+ struct completion irq_comp;
+
+ bool hpd_irq_on;
+ bool power_on;
+ bool sink_info_read;
+ bool cont_splash;
+ bool psm_enabled;
+ bool initialized;
+ bool orientation;
+
+ u32 pixel_rate;
+ u32 vic;
+};
+
+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_set_sink_power_state(struct dp_ctrl_private *ctrl,
+ u8 power_state)
+{
+ const int len = 1;
+
+ ctrl->aux->write(ctrl->aux, 0x600, len, AUX_NATIVE, &power_state);
+}
+
+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 *dp_ctrl)
+{
+ int const idle_pattern_completion_timeout_ms = 3 * HZ / 100;
+ 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);
+
+ dp_ctrl_set_sink_power_state(ctrl, SINK_POWER_OFF);
+
+ reinit_completion(&ctrl->idle_comp);
+ dp_ctrl_state_ctrl(ctrl, ST_PUSH_IDLE);
+
+ if (!wait_for_completion_timeout(&ctrl->idle_comp,
+ idle_pattern_completion_timeout_ms))
+ pr_warn("PUSH_IDLE pattern timedout\n");
+
+ pr_debug("mainlink off done\n");
+}
+
+static void dp_ctrl_config_ctrl(struct dp_ctrl_private *ctrl)
+{
+ u32 config = 0, tbd;
+
+ config |= (2 << 13); /* Default-> LSCLK DIV: 1/4 LCLK */
+ config |= (0 << 11); /* RGB */
+
+ /* Scrambler reset enable */
+ if (ctrl->panel->dpcd.scrambler_reset)
+ config |= (1 << 10);
+
+ tbd = ctrl->link->get_test_bits_depth(ctrl->link,
+ ctrl->panel->pinfo.bpp);
+ config |= tbd << 8;
+
+ /* Num of Lanes */
+ config |= ((ctrl->link->lane_count - 1) << 4);
+
+ if (ctrl->panel->dpcd.enhanced_frame)
+ config |= 0x40;
+
+ config |= 0x04; /* progressive video */
+
+ config |= 0x03; /* sycn clock & static Mvid */
+
+ ctrl->catalog->config_ctrl(ctrl->catalog, config);
+}
+
+/**
+ * dp_ctrl_configure_source_params() - configures DP transmitter source params
+ * @ctrl: Display Port Driver data
+ *
+ * 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_params(struct dp_ctrl_private *ctrl)
+{
+ u32 cc, tb;
+
+ ctrl->catalog->lane_mapping(ctrl->catalog);
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, true);
+
+ dp_ctrl_config_ctrl(ctrl);
+
+ tb = ctrl->link->get_test_bits_depth(ctrl->link,
+ ctrl->panel->pinfo.bpp);
+ cc = ctrl->link->get_colorimetry_config(ctrl->link);
+ ctrl->catalog->config_misc(ctrl->catalog, cc, tb);
+
+ ctrl->catalog->config_msa(ctrl->catalog);
+
+ ctrl->panel->timing_cfg(ctrl->panel);
+}
+
+static void dp_ctrl_get_extra_req_bytes(u64 result_valid,
+ int valid_bdary_link,
+ u64 value1, u64 value2,
+ bool *negative, u64 *result,
+ u64 compare)
+{
+ *negative = false;
+ if (result_valid >= compare) {
+ if (valid_bdary_link
+ >= compare)
+ *result = value1 + value2;
+ else {
+ if (value1 < value2)
+ *negative = true;
+ *result = (value1 >= value2) ?
+ (value1 - value2) : (value2 - value1);
+ }
+ } else {
+ if (valid_bdary_link
+ >= compare) {
+ if (value1 >= value2)
+ *negative = true;
+ *result = (value1 >= value2) ?
+ (value1 - value2) : (value2 - value1);
+ } else {
+ *result = value1 + value2;
+ *negative = true;
+ }
+ }
+}
+
+static u64 roundup_u64(u64 x, u64 y)
+{
+ x += (y - 1);
+ return (div64_ul(x, y) * y);
+}
+
+static u64 rounddown_u64(u64 x, u64 y)
+{
+ u64 rem;
+
+ div64_u64_rem(x, y, &rem);
+ return (x - rem);
+}
+
+static void dp_ctrl_calc_tu_parameters(struct dp_ctrl_private *ctrl,
+ struct dp_vc_tu_mapping_table *tu_table)
+{
+ u32 const multiplier = 1000000;
+ u64 pclk, lclk;
+ u8 bpp, ln_cnt, link_rate;
+ int run_idx = 0;
+ u32 lwidth, h_blank;
+ u32 fifo_empty = 0;
+ u32 ratio_scale = 1001;
+ u64 temp, ratio, original_ratio;
+ u64 temp2, reminder;
+ u64 temp3, temp4, result = 0;
+
+ u64 err = multiplier;
+ u64 n_err = 0, n_n_err = 0;
+ bool n_err_neg, nn_err_neg;
+ u8 hblank_margin = 16;
+
+ u8 tu_size, tu_size_desired = 0, tu_size_minus1;
+ int valid_boundary_link;
+ u64 resulting_valid;
+ u64 total_valid;
+ u64 effective_valid;
+ u64 effective_valid_recorded;
+ int n_tus;
+ int n_tus_per_lane;
+ int paired_tus;
+ int remainder_tus;
+ int remainder_tus_upper, remainder_tus_lower;
+ int extra_bytes;
+ int filler_size;
+ int delay_start_link;
+ int boundary_moderation_en = 0;
+ int upper_bdry_cnt = 0;
+ int lower_bdry_cnt = 0;
+ int i_upper_bdry_cnt = 0;
+ int i_lower_bdry_cnt = 0;
+ int valid_lower_boundary_link = 0;
+ int even_distribution_bf = 0;
+ int even_distribution_legacy = 0;
+ int even_distribution = 0;
+ int min_hblank = 0;
+ int extra_pclk_cycles;
+ u8 extra_pclk_cycle_delay = 4;
+ int extra_pclk_cycles_in_link_clk;
+ u64 ratio_by_tu;
+ u64 average_valid2;
+ u64 extra_buffer_margin;
+ int new_valid_boundary_link;
+
+ u64 resulting_valid_tmp;
+ u64 ratio_by_tu_tmp;
+ int n_tus_tmp;
+ int extra_pclk_cycles_tmp;
+ int extra_pclk_cycles_in_lclk_tmp;
+ int extra_req_bytes_new_tmp;
+ int filler_size_tmp;
+ int lower_filler_size_tmp;
+ int delay_start_link_tmp;
+ int min_hblank_tmp = 0;
+ bool extra_req_bytes_is_neg = false;
+ struct dp_panel_info *pinfo = &ctrl->panel->pinfo;
+
+ u8 dp_brute_force = 1;
+ u64 brute_force_threshold = 10;
+ u64 diff_abs;
+
+ link_rate = ctrl->link->link_rate;
+ ln_cnt = ctrl->link->lane_count;
+
+ bpp = pinfo->bpp;
+ lwidth = pinfo->h_active;
+ h_blank = pinfo->h_back_porch + pinfo->h_front_porch +
+ pinfo->h_sync_width;
+ pclk = pinfo->pixel_clk_khz * 1000;
+
+ boundary_moderation_en = 0;
+ upper_bdry_cnt = 0;
+ lower_bdry_cnt = 0;
+ i_upper_bdry_cnt = 0;
+ i_lower_bdry_cnt = 0;
+ valid_lower_boundary_link = 0;
+ even_distribution_bf = 0;
+ even_distribution_legacy = 0;
+ even_distribution = 0;
+ min_hblank = 0;
+
+ lclk = link_rate * DP_LINK_RATE_MULTIPLIER;
+
+ pr_debug("pclk=%lld, active_width=%d, h_blank=%d\n",
+ pclk, lwidth, h_blank);
+ pr_debug("lclk = %lld, ln_cnt = %d\n", lclk, ln_cnt);
+ ratio = div64_u64_rem(pclk * bpp * multiplier,
+ 8 * ln_cnt * lclk, &reminder);
+ ratio = div64_u64((pclk * bpp * multiplier), (8 * ln_cnt * lclk));
+ original_ratio = ratio;
+
+ extra_buffer_margin = roundup_u64(div64_u64(extra_pclk_cycle_delay
+ * lclk * multiplier, pclk), multiplier);
+ extra_buffer_margin = div64_u64(extra_buffer_margin, multiplier);
+
+ /* To deal with cases where lines are not distributable */
+ if (((lwidth % ln_cnt) != 0) && ratio < multiplier) {
+ ratio = ratio * ratio_scale;
+ ratio = ratio < (1000 * multiplier)
+ ? ratio : (1000 * multiplier);
+ }
+ pr_debug("ratio = %lld\n", ratio);
+
+ for (tu_size = 32; tu_size <= 64; tu_size++) {
+ temp = ratio * tu_size;
+ temp2 = ((temp / multiplier) + 1) * multiplier;
+ n_err = roundup_u64(temp, multiplier) - temp;
+
+ if (n_err < err) {
+ err = n_err;
+ tu_size_desired = tu_size;
+ }
+ }
+ pr_debug("Info: tu_size_desired = %d\n", tu_size_desired);
+
+ tu_size_minus1 = tu_size_desired - 1;
+
+ valid_boundary_link = roundup_u64(ratio * tu_size_desired, multiplier);
+ valid_boundary_link /= multiplier;
+ n_tus = rounddown((lwidth * bpp * multiplier)
+ / (8 * valid_boundary_link), multiplier) / multiplier;
+ even_distribution_legacy = n_tus % ln_cnt == 0 ? 1 : 0;
+ pr_debug("Info: n_symbol_per_tu=%d, number_of_tus=%d\n",
+ valid_boundary_link, n_tus);
+
+ extra_bytes = roundup_u64((n_tus + 1)
+ * ((valid_boundary_link * multiplier)
+ - (original_ratio * tu_size_desired)), multiplier);
+ extra_bytes /= multiplier;
+ extra_pclk_cycles = roundup(extra_bytes * 8 * multiplier / bpp,
+ multiplier);
+ extra_pclk_cycles /= multiplier;
+ extra_pclk_cycles_in_link_clk = roundup_u64(div64_u64(extra_pclk_cycles
+ * lclk * multiplier, pclk), multiplier);
+ extra_pclk_cycles_in_link_clk /= multiplier;
+ filler_size = roundup_u64((tu_size_desired - valid_boundary_link)
+ * multiplier, multiplier);
+ filler_size /= multiplier;
+ ratio_by_tu = div64_u64(ratio * tu_size_desired, multiplier);
+
+ pr_debug("extra_pclk_cycles_in_link_clk=%d, extra_bytes=%d\n",
+ extra_pclk_cycles_in_link_clk, extra_bytes);
+ pr_debug("extra_pclk_cycles_in_link_clk=%d\n",
+ extra_pclk_cycles_in_link_clk);
+ pr_debug("filler_size=%d, extra_buffer_margin=%lld\n",
+ filler_size, extra_buffer_margin);
+
+ delay_start_link = ((extra_bytes > extra_pclk_cycles_in_link_clk)
+ ? extra_bytes
+ : extra_pclk_cycles_in_link_clk)
+ + filler_size + extra_buffer_margin;
+ resulting_valid = valid_boundary_link;
+ pr_debug("Info: delay_start_link=%d, filler_size=%d\n",
+ delay_start_link, filler_size);
+ pr_debug("valid_boundary_link=%d ratio_by_tu=%lld\n",
+ valid_boundary_link, ratio_by_tu);
+
+ diff_abs = (resulting_valid >= ratio_by_tu)
+ ? (resulting_valid - ratio_by_tu)
+ : (ratio_by_tu - resulting_valid);
+
+ if (err != 0 && ((diff_abs > brute_force_threshold)
+ || (even_distribution_legacy == 0)
+ || (dp_brute_force == 1))) {
+ err = multiplier;
+ for (tu_size = 32; tu_size <= 64; tu_size++) {
+ for (i_upper_bdry_cnt = 1; i_upper_bdry_cnt <= 15;
+ i_upper_bdry_cnt++) {
+ for (i_lower_bdry_cnt = 1;
+ i_lower_bdry_cnt <= 15;
+ i_lower_bdry_cnt++) {
+ new_valid_boundary_link =
+ roundup_u64(ratio
+ * tu_size, multiplier);
+ average_valid2 = (i_upper_bdry_cnt
+ * new_valid_boundary_link
+ + i_lower_bdry_cnt
+ * (new_valid_boundary_link
+ - multiplier))
+ / (i_upper_bdry_cnt
+ + i_lower_bdry_cnt);
+ n_tus = rounddown_u64(div64_u64(lwidth
+ * multiplier * multiplier
+ * (bpp / 8), average_valid2),
+ multiplier);
+ n_tus /= multiplier;
+ n_tus_per_lane
+ = rounddown(n_tus
+ * multiplier
+ / ln_cnt, multiplier);
+ n_tus_per_lane /= multiplier;
+ paired_tus =
+ rounddown((n_tus_per_lane)
+ * multiplier
+ / (i_upper_bdry_cnt
+ + i_lower_bdry_cnt),
+ multiplier);
+ paired_tus /= multiplier;
+ remainder_tus = n_tus_per_lane
+ - paired_tus
+ * (i_upper_bdry_cnt
+ + i_lower_bdry_cnt);
+ if ((remainder_tus
+ - i_upper_bdry_cnt) > 0) {
+ remainder_tus_upper
+ = i_upper_bdry_cnt;
+ remainder_tus_lower =
+ remainder_tus
+ - i_upper_bdry_cnt;
+ } else {
+ remainder_tus_upper
+ = remainder_tus;
+ remainder_tus_lower = 0;
+ }
+ total_valid = paired_tus
+ * (i_upper_bdry_cnt
+ * new_valid_boundary_link
+ + i_lower_bdry_cnt
+ * (new_valid_boundary_link
+ - multiplier))
+ + (remainder_tus_upper
+ * new_valid_boundary_link)
+ + (remainder_tus_lower
+ * (new_valid_boundary_link
+ - multiplier));
+ n_err_neg = nn_err_neg = false;
+ effective_valid
+ = div_u64(total_valid,
+ n_tus_per_lane);
+ n_n_err = (effective_valid
+ >= (ratio * tu_size))
+ ? (effective_valid
+ - (ratio * tu_size))
+ : ((ratio * tu_size)
+ - effective_valid);
+ if (effective_valid < (ratio * tu_size))
+ nn_err_neg = true;
+ n_err = (average_valid2
+ >= (ratio * tu_size))
+ ? (average_valid2
+ - (ratio * tu_size))
+ : ((ratio * tu_size)
+ - average_valid2);
+ if (average_valid2 < (ratio * tu_size))
+ n_err_neg = true;
+ even_distribution =
+ n_tus % ln_cnt == 0 ? 1 : 0;
+ diff_abs =
+ resulting_valid >= ratio_by_tu
+ ? (resulting_valid
+ - ratio_by_tu)
+ : (ratio_by_tu
+ - resulting_valid);
+
+ resulting_valid_tmp = div64_u64(
+ (i_upper_bdry_cnt
+ * new_valid_boundary_link
+ + i_lower_bdry_cnt
+ * (new_valid_boundary_link
+ - multiplier)),
+ (i_upper_bdry_cnt
+ + i_lower_bdry_cnt));
+ ratio_by_tu_tmp =
+ original_ratio * tu_size;
+ ratio_by_tu_tmp /= multiplier;
+ n_tus_tmp = rounddown_u64(
+ div64_u64(lwidth
+ * multiplier * multiplier
+ * bpp / 8,
+ resulting_valid_tmp),
+ multiplier);
+ n_tus_tmp /= multiplier;
+
+ temp3 = (resulting_valid_tmp
+ >= (original_ratio * tu_size))
+ ? (resulting_valid_tmp
+ - original_ratio * tu_size)
+ : (original_ratio * tu_size)
+ - resulting_valid_tmp;
+ temp3 = (n_tus_tmp + 1) * temp3;
+ temp4 = (new_valid_boundary_link
+ >= (original_ratio * tu_size))
+ ? (new_valid_boundary_link
+ - original_ratio
+ * tu_size)
+ : (original_ratio * tu_size)
+ - new_valid_boundary_link;
+ temp4 = (i_upper_bdry_cnt
+ * ln_cnt * temp4);
+
+ temp3 = roundup_u64(temp3, multiplier);
+ temp4 = roundup_u64(temp4, multiplier);
+ dp_ctrl_get_extra_req_bytes
+ (resulting_valid_tmp,
+ new_valid_boundary_link,
+ temp3, temp4,
+ &extra_req_bytes_is_neg,
+ &result,
+ (original_ratio * tu_size));
+ extra_req_bytes_new_tmp
+ = div64_ul(result, multiplier);
+ if ((extra_req_bytes_is_neg)
+ && (extra_req_bytes_new_tmp
+ > 1))
+ extra_req_bytes_new_tmp
+ = extra_req_bytes_new_tmp - 1;
+ if (extra_req_bytes_new_tmp == 0)
+ extra_req_bytes_new_tmp = 1;
+ extra_pclk_cycles_tmp =
+ (u64)(extra_req_bytes_new_tmp
+ * 8 * multiplier) / bpp;
+ extra_pclk_cycles_tmp /= multiplier;
+
+ if (extra_pclk_cycles_tmp <= 0)
+ extra_pclk_cycles_tmp = 1;
+ extra_pclk_cycles_in_lclk_tmp =
+ roundup_u64(div64_u64(
+ extra_pclk_cycles_tmp
+ * lclk * multiplier,
+ pclk), multiplier);
+ extra_pclk_cycles_in_lclk_tmp
+ /= multiplier;
+ filler_size_tmp = roundup_u64(
+ (tu_size * multiplier *
+ new_valid_boundary_link),
+ multiplier);
+ filler_size_tmp /= multiplier;
+ lower_filler_size_tmp =
+ filler_size_tmp + 1;
+ if (extra_req_bytes_is_neg)
+ temp3 = (extra_req_bytes_new_tmp
+ > extra_pclk_cycles_in_lclk_tmp
+ ? extra_pclk_cycles_in_lclk_tmp
+ : extra_req_bytes_new_tmp);
+ else
+ temp3 = (extra_req_bytes_new_tmp
+ > extra_pclk_cycles_in_lclk_tmp
+ ? extra_req_bytes_new_tmp :
+ extra_pclk_cycles_in_lclk_tmp);
+
+ temp4 = lower_filler_size_tmp
+ + extra_buffer_margin;
+ if (extra_req_bytes_is_neg)
+ delay_start_link_tmp
+ = (temp3 >= temp4)
+ ? (temp3 - temp4)
+ : (temp4 - temp3);
+ else
+ delay_start_link_tmp
+ = temp3 + temp4;
+
+ min_hblank_tmp = (int)div64_u64(
+ roundup_u64(
+ div64_u64(delay_start_link_tmp
+ * pclk * multiplier, lclk),
+ multiplier), multiplier)
+ + hblank_margin;
+
+ if (((even_distribution == 1)
+ || ((even_distribution_bf == 0)
+ && (even_distribution_legacy
+ == 0)))
+ && !n_err_neg && !nn_err_neg
+ && n_n_err < err
+ && (n_n_err < diff_abs
+ || (dp_brute_force == 1))
+ && (new_valid_boundary_link
+ - 1) > 0
+ && (h_blank >=
+ (u32)min_hblank_tmp)) {
+ upper_bdry_cnt =
+ i_upper_bdry_cnt;
+ lower_bdry_cnt =
+ i_lower_bdry_cnt;
+ err = n_n_err;
+ boundary_moderation_en = 1;
+ tu_size_desired = tu_size;
+ valid_boundary_link =
+ new_valid_boundary_link;
+ effective_valid_recorded
+ = effective_valid;
+ delay_start_link
+ = delay_start_link_tmp;
+ filler_size = filler_size_tmp;
+ min_hblank = min_hblank_tmp;
+ n_tus = n_tus_tmp;
+ even_distribution_bf = 1;
+
+ pr_debug("upper_bdry_cnt=%d, lower_boundary_cnt=%d, err=%lld, tu_size_desired=%d, valid_boundary_link=%d, effective_valid=%lld\n",
+ upper_bdry_cnt,
+ lower_bdry_cnt, err,
+ tu_size_desired,
+ valid_boundary_link,
+ effective_valid);
+ }
+ }
+ }
+ }
+
+ if (boundary_moderation_en == 1) {
+ resulting_valid = (u64)(upper_bdry_cnt
+ *valid_boundary_link + lower_bdry_cnt
+ * (valid_boundary_link - 1))
+ / (upper_bdry_cnt + lower_bdry_cnt);
+ ratio_by_tu = original_ratio * tu_size_desired;
+ valid_lower_boundary_link =
+ (valid_boundary_link / multiplier) - 1;
+
+ tu_size_minus1 = tu_size_desired - 1;
+ even_distribution_bf = 1;
+ valid_boundary_link /= multiplier;
+ pr_debug("Info: Boundary_moderation enabled\n");
+ }
+ }
+
+ min_hblank = ((int) roundup_u64(div64_u64(delay_start_link * pclk
+ * multiplier, lclk), multiplier))
+ / multiplier + hblank_margin;
+ if (h_blank < (u32)min_hblank) {
+ pr_debug(" WARNING: run_idx=%d Programmed h_blank %d is smaller than the min_hblank %d supported.\n",
+ run_idx, h_blank, min_hblank);
+ }
+
+ if (fifo_empty) {
+ tu_size_minus1 = 31;
+ valid_boundary_link = 32;
+ delay_start_link = 0;
+ boundary_moderation_en = 0;
+ }
+
+ pr_debug("tu_size_minus1=%d valid_boundary_link=%d delay_start_link=%d boundary_moderation_en=%d\n upper_boundary_cnt=%d lower_boundary_cnt=%d valid_lower_boundary_link=%d min_hblank=%d\n",
+ tu_size_minus1, valid_boundary_link, delay_start_link,
+ boundary_moderation_en, upper_bdry_cnt, lower_bdry_cnt,
+ valid_lower_boundary_link, min_hblank);
+
+ tu_table->valid_boundary_link = valid_boundary_link;
+ tu_table->delay_start_link = delay_start_link;
+ tu_table->boundary_moderation_en = boundary_moderation_en;
+ tu_table->valid_lower_boundary_link = valid_lower_boundary_link;
+ tu_table->upper_boundary_count = upper_bdry_cnt;
+ tu_table->lower_boundary_count = lower_bdry_cnt;
+ tu_table->tu_size_minus1 = tu_size_minus1;
+}
+
+static void dp_ctrl_setup_tr_unit(struct dp_ctrl_private *ctrl)
+{
+ u32 dp_tu = 0x0;
+ u32 valid_boundary = 0x0;
+ u32 valid_boundary2 = 0x0;
+ struct dp_vc_tu_mapping_table tu_calc_table;
+
+ dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table);
+
+ dp_tu |= tu_calc_table.tu_size_minus1;
+ valid_boundary |= tu_calc_table.valid_boundary_link;
+ valid_boundary |= (tu_calc_table.delay_start_link << 16);
+
+ valid_boundary2 |= (tu_calc_table.valid_lower_boundary_link << 1);
+ valid_boundary2 |= (tu_calc_table.upper_boundary_count << 16);
+ valid_boundary2 |= (tu_calc_table.lower_boundary_count << 20);
+
+ if (tu_calc_table.boundary_moderation_en)
+ valid_boundary2 |= BIT(0);
+
+ pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
+ dp_tu, valid_boundary, valid_boundary2);
+
+ ctrl->catalog->dp_tu = dp_tu;
+ ctrl->catalog->valid_boundary = valid_boundary;
+ ctrl->catalog->valid_boundary2 = valid_boundary2;
+
+ ctrl->catalog->update_transfer_unit(ctrl->catalog);
+}
+
+static int dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ if (ctrl->cont_splash)
+ return ret;
+
+ ret = wait_for_completion_timeout(&ctrl->video_comp, HZ / 2);
+ if (ret <= 0) {
+ pr_err("Link Train timedout\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+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);
+ }
+
+ pr_debug("max_level_reached = 0x%x\n", max_level_reached);
+
+ pre_emphasis_level <<= 3;
+
+ for (i = 0; i < 4; i++)
+ buf[i] = voltage_level | pre_emphasis_level | max_level_reached;
+
+ pr_debug("p|v=0x%x\n", voltage_level | pre_emphasis_level);
+ return ctrl->aux->write(ctrl->aux, 0x103, 4, AUX_NATIVE, buf);
+}
+
+static void dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl)
+{
+ struct dp_link *link = ctrl->link;
+
+ pr_debug("v=%d p=%d\n", link->v_level, link->p_level);
+
+ ctrl->catalog->update_vx_px(ctrl->catalog,
+ link->v_level, link->p_level);
+
+ dp_ctrl_update_sink_vx_px(ctrl, link->v_level, link->p_level);
+}
+
+static void dp_ctrl_cap_lane_rate_set(struct dp_ctrl_private *ctrl)
+{
+ u8 buf[4];
+ struct dp_panel_dpcd *cap;
+
+ cap = &ctrl->panel->dpcd;
+
+ pr_debug("bw=%x lane=%d\n", ctrl->link->link_rate,
+ ctrl->link->lane_count);
+
+ buf[0] = ctrl->link->link_rate;
+ buf[1] = ctrl->link->lane_count;
+
+ if (cap->enhanced_frame)
+ buf[1] |= 0x80;
+
+ ctrl->aux->write(ctrl->aux, 0x100, 2, AUX_NATIVE, buf);
+}
+
+static void dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl,
+ u8 pattern)
+{
+ u8 buf[4];
+
+ pr_debug("pattern=%x\n", pattern);
+
+ buf[0] = pattern;
+ ctrl->aux->write(ctrl->aux, 0x102, 1, AUX_NATIVE, buf);
+}
+
+static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl)
+{
+ int tries, old_v_level;
+ int ret = 0;
+ int usleep_time;
+ int const maximum_retries = 5;
+
+ 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);
+ dp_ctrl_cap_lane_rate_set(ctrl);
+ dp_ctrl_train_pattern_set(ctrl, 0x21); /* train_1 */
+ dp_ctrl_update_vx_px(ctrl);
+
+ tries = 0;
+ old_v_level = ctrl->link->v_level;
+ while (1) {
+ usleep_time = ctrl->panel->dpcd.training_read_interval;
+ usleep_range(usleep_time, usleep_time * 2);
+
+ if (ctrl->link->clock_recovery(ctrl->link)) {
+ ret = 0;
+ break;
+ }
+
+ if (ctrl->link->v_level == DP_LINK_VOLTAGE_MAX) {
+ ret = -1;
+ break; /* quit */
+ }
+
+ if (old_v_level == ctrl->link->v_level) {
+ tries++;
+ if (tries >= maximum_retries) {
+ ret = -1;
+ break; /* quit */
+ }
+ } else {
+ tries = 0;
+ old_v_level = ctrl->link->v_level;
+ }
+
+ ctrl->link->adjust_levels(ctrl->link);
+
+ dp_ctrl_update_vx_px(ctrl);
+ }
+
+ 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_rate) {
+ case DP_LINK_RATE_810:
+ ctrl->link->link_rate = DP_LINK_RATE_540;
+ break;
+ case DP_LINK_RATE_540:
+ ctrl->link->link_rate = DP_LINK_RATE_270;
+ break;
+ case DP_LINK_RATE_270:
+ ctrl->link->link_rate = DP_LINK_RATE_162;
+ break;
+ case DP_LINK_RATE_162:
+ default:
+ ret = -EINVAL;
+ break;
+ };
+
+ pr_debug("new rate=%d\n", ctrl->link->link_rate);
+
+ return ret;
+}
+
+static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl)
+{
+ int usleep_time;
+
+ dp_ctrl_train_pattern_set(ctrl, 0);
+
+ usleep_time = ctrl->panel->dpcd.training_read_interval;
+ usleep_range(usleep_time, usleep_time * 2);
+}
+
+static int dp_ctrl_link_training_2(struct dp_ctrl_private *ctrl)
+{
+ int tries = 0;
+ int ret = 0;
+ int usleep_time;
+ char pattern;
+ int const maximum_retries = 5;
+
+ if (ctrl->panel->dpcd.flags & DPCD_TPS3)
+ pattern = 0x03;
+ else
+ pattern = 0x02;
+
+ dp_ctrl_update_vx_px(ctrl);
+ ctrl->catalog->set_pattern(ctrl->catalog, pattern);
+ dp_ctrl_train_pattern_set(ctrl, pattern | 0x20);
+
+ do {
+ usleep_time = ctrl->panel->dpcd.training_read_interval;
+ usleep_range(usleep_time, usleep_time * 2);
+
+ if (ctrl->link->channel_equalization(ctrl->link)) {
+ ret = 0;
+ break;
+ }
+
+ if (tries > maximum_retries) {
+ ret = -1;
+ break;
+ }
+ tries++;
+
+ ctrl->link->adjust_levels(ctrl->link);
+
+ dp_ctrl_update_vx_px(ctrl);
+ } while (1);
+
+ return ret;
+}
+
+static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ ret = ctrl->aux->ready(ctrl->aux);
+ if (!ret) {
+ pr_err("aux chan NOT ready\n");
+ return ret;
+ }
+
+ ctrl->link->p_level = 0;
+ ctrl->link->v_level = 0;
+
+ dp_ctrl_config_ctrl(ctrl);
+ dp_ctrl_state_ctrl(ctrl, 0);
+
+ ret = dp_ctrl_link_train_1(ctrl);
+ if (ret < 0) {
+ if (!dp_ctrl_link_rate_down_shift(ctrl)) {
+ pr_debug("retry with lower rate\n");
+
+ dp_ctrl_clear_training_pattern(ctrl);
+ return -EAGAIN;
+ }
+
+ pr_err("Training 1 failed\n");
+ ret = -EINVAL;
+ goto clear;
+ }
+
+ pr_debug("Training 1 completed successfully\n");
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+ ret = dp_ctrl_link_training_2(ctrl);
+ if (ret < 0) {
+ if (!dp_ctrl_link_rate_down_shift(ctrl)) {
+ pr_debug("retry with lower rate\n");
+
+ dp_ctrl_clear_training_pattern(ctrl);
+ return -EAGAIN;
+ }
+
+ pr_err("Training 2 failed\n");
+ ret = -EINVAL;
+ goto clear;
+ }
+
+ pr_debug("Training 2 completed successfully\n");
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+clear:
+ dp_ctrl_clear_training_pattern(ctrl);
+ return ret;
+}
+
+static int dp_ctrl_setup_main_link(struct dp_ctrl_private *ctrl, bool train)
+{
+ bool mainlink_ready = false;
+ int ret = 0;
+
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, true);
+
+ dp_ctrl_set_sink_power_state(ctrl, SINK_POWER_ON);
+
+ if (ctrl->link->phy_pattern_requested(ctrl->link))
+ 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.
+ */
+ ctrl->catalog->reset(ctrl->catalog);
+
+ ret = dp_ctrl_link_train(ctrl);
+ if (ret)
+ goto end;
+
+send_video:
+ /*
+ * Set up transfer unit values and set controller state to send
+ * video.
+ */
+ dp_ctrl_setup_tr_unit(ctrl);
+ ctrl->catalog->state_ctrl(ctrl->catalog, ST_SEND_VIDEO);
+
+ dp_ctrl_wait4video_ready(ctrl);
+ mainlink_ready = ctrl->catalog->mainlink_ready(ctrl->catalog);
+ pr_debug("mainlink %s\n", mainlink_ready ? "READY" : "NOT READY");
+end:
+ return ret;
+}
+
+static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl,
+ char *name, u32 rate)
+{
+ u32 num = ctrl->parser->mp[DP_CTRL_PM].num_clk;
+ struct dss_clk *cfg = ctrl->parser->mp[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);
+}
+
+static int dp_ctrl_enable_mainlink_clocks(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ ctrl->power->set_pixel_clk_parent(ctrl->power);
+
+ dp_ctrl_set_clock_rate(ctrl, "ctrl_link_clk",
+ (ctrl->link->link_rate * DP_LINK_RATE_MULTIPLIER) /
+ DP_KHZ_TO_HZ);
+
+ dp_ctrl_set_clock_rate(ctrl, "ctrl_crypto_clk", DP_CRYPTO_CLK_RATE_KHZ);
+
+ dp_ctrl_set_clock_rate(ctrl, "ctrl_pixel_clk", ctrl->pixel_rate);
+
+ ret = ctrl->power->clk_enable(ctrl->power, DP_CTRL_PM, true);
+ if (ret) {
+ pr_err("Unabled to start link clocks\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int dp_ctrl_disable_mainlink_clocks(struct dp_ctrl_private *ctrl)
+{
+ return ctrl->power->clk_enable(ctrl->power, DP_CTRL_PM, false);
+}
+
+static int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip)
+{
+ 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);
+
+ if (ctrl->initialized) {
+ pr_debug("host init done already\n");
+ return 0;
+ }
+
+ ctrl->orientation = flip;
+ catalog = ctrl->catalog;
+
+ catalog->reset(ctrl->catalog);
+ catalog->phy_reset(ctrl->catalog);
+ catalog->enable_irq(ctrl->catalog, true);
+
+ ctrl->initialized = true;
+
+ 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);
+
+ if (!ctrl->initialized) {
+ pr_debug("host deinit done already\n");
+ return;
+ }
+
+ ctrl->catalog->enable_irq(ctrl->catalog, false);
+ ctrl->catalog->reset(ctrl->catalog);
+
+ /* Make sure DP is disabled before clk disable */
+ wmb();
+
+ dp_ctrl_disable_mainlink_clocks(ctrl);
+
+ ctrl->initialized = false;
+ pr_debug("Host deinitialized successfully\n");
+}
+
+static int dp_ctrl_on_irq(struct dp_ctrl_private *ctrl, bool lt_needed)
+{
+ int ret = 0;
+
+ do {
+ if (ret == -EAGAIN)
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+
+ ctrl->catalog->phy_lane_cfg(ctrl->catalog,
+ ctrl->orientation, ctrl->link->lane_count);
+
+ 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 (!ctrl->link->phy_pattern_requested(
+ ctrl->link))
+ dp_ctrl_disable_mainlink_clocks(ctrl);
+
+ ret = dp_ctrl_enable_mainlink_clocks(ctrl);
+ if (ret)
+ continue;
+ }
+
+ dp_ctrl_configure_source_params(ctrl);
+
+ reinit_completion(&ctrl->idle_comp);
+
+ ctrl->power_on = true;
+
+ if (ctrl->psm_enabled) {
+ ret = ctrl->link->send_psm_request(ctrl->link, false);
+ if (ret) {
+ pr_err("failed to exit low power mode, rc=%d\n",
+ ret);
+ continue;
+ }
+ }
+
+ ret = dp_ctrl_setup_main_link(ctrl, lt_needed);
+ } while (ret == -EAGAIN);
+
+ return ret;
+}
+
+static int dp_ctrl_on_hpd(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ if (ctrl->cont_splash)
+ goto link_training;
+
+ ctrl->power->clk_enable(ctrl->power, DP_CORE_PM, true);
+ ctrl->catalog->hpd_config(ctrl->catalog, true);
+
+ ctrl->link->link_rate = ctrl->panel->get_link_rate(ctrl->panel);
+ ctrl->link->lane_count = ctrl->panel->dpcd.max_lane_count;
+ ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz;
+
+ pr_debug("link_rate=%d, lane_count=%d, pixel_rate=%d\n",
+ ctrl->link->link_rate, ctrl->link->lane_count,
+ ctrl->pixel_rate);
+
+ ctrl->catalog->phy_lane_cfg(ctrl->catalog,
+ ctrl->orientation, ctrl->link->lane_count);
+
+ ret = dp_ctrl_enable_mainlink_clocks(ctrl);
+ if (ret)
+ goto exit;
+
+ reinit_completion(&ctrl->idle_comp);
+
+ dp_ctrl_configure_source_params(ctrl);
+
+ if (ctrl->psm_enabled)
+ ret = ctrl->link->send_psm_request(ctrl->link, false);
+link_training:
+ ctrl->power_on = true;
+
+ while (-EAGAIN == dp_ctrl_setup_main_link(ctrl, true))
+ pr_debug("MAIN LINK TRAINING RETRY\n");
+
+ ctrl->cont_splash = 0;
+
+ ctrl->power_on = true;
+ pr_debug("End-\n");
+
+exit:
+ return ret;
+}
+
+static int dp_ctrl_off_irq(struct dp_ctrl_private *ctrl)
+{
+ if (!ctrl->power_on) {
+ pr_debug("ctrl already powered off\n");
+ return 0;
+ }
+
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+
+ /* Make sure DP mainlink and audio engines are disabled */
+ wmb();
+
+ complete_all(&ctrl->irq_comp);
+ pr_debug("end\n");
+
+ return 0;
+}
+
+static int dp_ctrl_off_hpd(struct dp_ctrl_private *ctrl)
+{
+ if (!ctrl->power_on) {
+ pr_debug("panel already powered off\n");
+ return 0;
+ }
+
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+
+ ctrl->power_on = false;
+ ctrl->sink_info_read = false;
+
+ pr_debug("DP off done\n");
+
+ return 0;
+}
+
+static int dp_ctrl_on(struct dp_ctrl *dp_ctrl)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ rc = -EINVAL;
+ goto end;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (ctrl->hpd_irq_on)
+ rc = dp_ctrl_on_irq(ctrl, false);
+ else
+ rc = dp_ctrl_on_hpd(ctrl);
+end:
+ return rc;
+}
+
+static int dp_ctrl_off(struct dp_ctrl *dp_ctrl)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ rc = -EINVAL;
+ goto end;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (ctrl->hpd_irq_on)
+ rc = dp_ctrl_off_irq(ctrl);
+ else
+ rc = dp_ctrl_off_hpd(ctrl);
+end:
+ return rc;
+}
+
+static void dp_ctrl_isr(struct dp_ctrl *dp_ctrl, u32 irq)
+{
+ u32 isr;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ isr = ctrl->catalog->get_interrupt(ctrl->catalog);
+
+ if (isr & DP_CTRL_INTR_READY_FOR_VIDEO)
+ dp_ctrl_video_ready(ctrl);
+
+ if (isr & DP_CTRL_INTR_IDLE_PATTERN_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);
+ init_completion(&ctrl->irq_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;
+
+ 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->push_idle = dp_ctrl_push_idle;
+ dp_ctrl->isr = dp_ctrl_isr;
+
+ 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);
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
new file mode 100644
index 0000000..c9cf7f8
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012-2017, 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.
+ *
+ */
+
+#ifndef _DP_CTRL_H_
+#define _DP_CTRL_H_
+
+#include "dp_aux.h"
+#include "dp_panel.h"
+#include "dp_link.h"
+#include "dp_parser.h"
+#include "dp_power.h"
+#include "dp_catalog.h"
+
+struct dp_ctrl {
+ int (*init)(struct dp_ctrl *dp_ctrl, bool flip);
+ void (*deinit)(struct dp_ctrl *dp_ctrl);
+ int (*on)(struct dp_ctrl *dp_ctrl);
+ int (*off)(struct dp_ctrl *dp_ctrl);
+ void (*push_idle)(struct dp_ctrl *dp_ctrl);
+ void (*isr)(struct dp_ctrl *dp_ctrl, u32 isr);
+};
+
+struct dp_ctrl_in {
+ struct device *dev;
+ struct dp_panel *panel;
+ struct dp_aux *aux;
+ struct dp_link *link;
+ struct dp_parser *parser;
+ struct dp_power *power;
+ struct dp_catalog_ctrl *catalog;
+};
+
+struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in);
+void dp_ctrl_put(struct dp_ctrl *dp_ctrl);
+
+#endif /* _DP_CTRL_H_ */