aboutsummaryrefslogtreecommitdiff
path: root/src/soc/samsung/exynos5420/dp.c
diff options
context:
space:
mode:
authorHung-Te Lin <hungte@chromium.org>2013-09-27 12:45:45 +0800
committerIsaac Christensen <isaac.christensen@se-eng.com>2014-08-26 17:55:18 +0200
commit22d0ca0ceb802675cdcab1472b8477066f729373 (patch)
tree79e2e38a2c6b34125f48b05cfd7f9ef3c88c833d /src/soc/samsung/exynos5420/dp.c
parentb123e0d3345554d7e93361bb4511a53bc95d41a1 (diff)
armv7: Move Exynos from 'cpu' to 'soc'.
The Exynos family and most ARM products are SoC, not just CPU. We used to put ARM code in src/cpu to avoid polluting the code base for what was essentially an experiment at the time. Now that it's past the experimental phase and we're going to see more SoCs (including intel/baytrail) in coreboot. Change-Id: I5ea1f822664244edf5f77087bc8018d7c535f81c Reviewed-on: https://chromium-review.googlesource.com/170891 Tested-by: Hung-Te Lin <hungte@chromium.org> Reviewed-by: Aaron Durbin <adurbin@chromium.org> Reviewed-by: Ronald Minnich <rminnich@chromium.org> Commit-Queue: Hung-Te Lin <hungte@chromium.org> (cherry picked from commit c8bb8fe0b20be37465f93c738d80e7e43033670a) Signed-off-by: Isaac Christensen <isaac.christensen@se-eng.com> Reviewed-on: http://review.coreboot.org/6739 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Diffstat (limited to 'src/soc/samsung/exynos5420/dp.c')
-rw-r--r--src/soc/samsung/exynos5420/dp.c910
1 files changed, 910 insertions, 0 deletions
diff --git a/src/soc/samsung/exynos5420/dp.c b/src/soc/samsung/exynos5420/dp.c
new file mode 100644
index 0000000000..b147035b64
--- /dev/null
+++ b/src/soc/samsung/exynos5420/dp.c
@@ -0,0 +1,910 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <arch/io.h>
+#include <stdlib.h>
+#include <string.h>
+#include <timer.h>
+#include <delay.h>
+#include <console/console.h>
+#include <lib.h>
+#include "timer.h"
+#include "power.h"
+#include "sysreg.h"
+
+#include "dp.h"
+#include "fimd.h"
+#include "i2c.h"
+
+/*
+ * Here is the rough outline of how we bring up the display:
+ * 1. Upon power-on Sink generates a hot plug detection pulse thru HPD
+ * 2. Source determines video mode by reading DPCD receiver capability field
+ * (DPCD 00000h to 0000Dh) including eDP CP capability register (DPCD
+ * 0000Dh).
+ * 3. Sink replies DPCD receiver capability field.
+ * 4. Source starts EDID read thru I2C-over-AUX.
+ * 5. Sink replies EDID thru I2C-over-AUX.
+ * 6. Source determines link configuration, such as MAX_LINK_RATE and
+ * MAX_LANE_COUNT. Source also determines which type of eDP Authentication
+ * method to use and writes DPCD link configuration field (DPCD 00100h to
+ * 0010Ah) including eDP configuration set (DPCD 0010Ah).
+ * 7. Source starts link training. Sink does clock recovery and equalization.
+ * 8. Source reads DPCD link status field (DPCD 00200h to 0020Bh).
+ * 9. Sink replies DPCD link status field. If main link is not stable, Source
+ * repeats Step 7.
+ * 10. Source sends MSA (Main Stream Attribute) data. Sink extracts video
+ * parameters and recovers stream clock.
+ * 11. Source sends video data.
+ */
+
+
+static int exynos_dp_init_dp(void)
+{
+ int ret;
+ exynos_dp_reset();
+
+ /* SW defined function Normal operation */
+ exynos_dp_enable_sw_func(DP_ENABLE);
+
+ ret = exynos_dp_init_analog_func();
+ if (ret != EXYNOS_DP_SUCCESS)
+ return ret;
+
+ exynos_dp_init_hpd();
+ exynos_dp_init_aux();
+
+ return ret;
+}
+
+static unsigned char exynos_dp_calc_edid_check_sum(unsigned char *edid_data)
+{
+ int i;
+ unsigned char sum = 0;
+
+ for (i = 0; i < EDID_BLOCK_LENGTH; i++)
+ sum = sum + edid_data[i];
+
+ return sum;
+}
+
+static unsigned int exynos_dp_read_edid(void)
+{
+ unsigned char edid[EDID_BLOCK_LENGTH * 2];
+ unsigned int extend_block = 0;
+ unsigned char sum;
+ unsigned char test_vector;
+ int retval = 0;
+
+ /*
+ * EDID device address is 0x50.
+ * However, if necessary, you must have set upper address
+ * into E-EDID in I2C device, 0x30.
+ */
+
+ /* Read Extension Flag, Number of 128-byte EDID extension blocks */
+ if (exynos_dp_read_byte_from_i2c
+ (I2C_EDID_DEVICE_ADDR, EDID_EXTENSION_FLAG, &extend_block))
+ return -1;
+
+ if (extend_block > 0) {
+ /* Read EDID data */
+ retval = exynos_dp_read_bytes_from_i2c(I2C_EDID_DEVICE_ADDR,
+ EDID_HEADER_PATTERN,
+ EDID_BLOCK_LENGTH,
+ &edid[EDID_HEADER_PATTERN]);
+
+ if (retval != 0) {
+ printk(BIOS_ERR, "DP EDID Read failed!\n");
+ return -1;
+ }
+ sum = exynos_dp_calc_edid_check_sum(edid);
+ if (sum != 0) {
+ printk(BIOS_ERR, "DP EDID bad checksum!\n");
+ return -1;
+ }
+ /* Read additional EDID data */
+ retval = exynos_dp_read_bytes_from_i2c(I2C_EDID_DEVICE_ADDR,
+ EDID_BLOCK_LENGTH,
+ EDID_BLOCK_LENGTH,
+ &edid[EDID_BLOCK_LENGTH]);
+ if (retval != 0) {
+ printk(BIOS_ERR, "DP EDID Read failed!\n");
+ return -1;
+ }
+ sum = exynos_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]);
+ if (sum != 0) {
+ printk(BIOS_ERR, "DP EDID bad checksum!\n");
+ return -1;
+ }
+ exynos_dp_read_byte_from_dpcd(DPCD_TEST_REQUEST,
+ &test_vector);
+ if (test_vector & DPCD_TEST_EDID_READ) {
+ exynos_dp_write_byte_to_dpcd(DPCD_TEST_EDID_CHECKSUM,
+ edid[EDID_BLOCK_LENGTH + EDID_CHECKSUM]);
+ exynos_dp_write_byte_to_dpcd(DPCD_TEST_RESPONSE,
+ DPCD_TEST_EDID_CHECKSUM_WRITE);
+ }
+ } else {
+ /* Read EDID data */
+ retval = exynos_dp_read_bytes_from_i2c(I2C_EDID_DEVICE_ADDR,
+ EDID_HEADER_PATTERN,
+ EDID_BLOCK_LENGTH,
+ &edid[EDID_HEADER_PATTERN]);
+
+ if (retval != 0) {
+ printk(BIOS_ERR, "DP EDID Read failed!\n");
+ return -1;
+ }
+ sum = exynos_dp_calc_edid_check_sum(edid);
+ if (sum != 0) {
+ printk(BIOS_ERR, "DP EDID bad checksum!\n");
+ return -1;
+ }
+
+ exynos_dp_read_byte_from_dpcd(DPCD_TEST_REQUEST,
+ &test_vector);
+ if (test_vector & DPCD_TEST_EDID_READ) {
+ exynos_dp_write_byte_to_dpcd(DPCD_TEST_EDID_CHECKSUM,
+ edid[EDID_CHECKSUM]);
+ exynos_dp_write_byte_to_dpcd(DPCD_TEST_RESPONSE,
+ DPCD_TEST_EDID_CHECKSUM_WRITE);
+ }
+
+ }
+
+ return 0;
+}
+
+static unsigned int exynos_dp_handle_edid(struct edp_device_info *edp_info)
+{
+ unsigned char buf[12];
+ unsigned int ret;
+ unsigned char temp;
+ unsigned char retry_cnt;
+ unsigned char dpcd_rev[16];
+ unsigned char lane_bw[16];
+ unsigned char lane_cnt[16];
+
+ memset(dpcd_rev, 0, sizeof(dpcd_rev));
+ memset(lane_bw, 0, sizeof(lane_bw));
+ memset(lane_cnt, 0, sizeof(lane_cnt));
+ memset(buf, 0, sizeof(buf));
+
+ retry_cnt = 5;
+ while (retry_cnt) {
+ /* Read DPCD 0x0000-0x000b */
+ ret = exynos_dp_read_bytes_from_dpcd(DPCD_DPCD_REV, 12,
+ buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ if (retry_cnt == 0) {
+ printk(BIOS_ERR, "DP read_byte_from_dpcd() failed\n");
+ return ret;
+ }
+ retry_cnt--;
+ } else
+ break;
+ }
+ /* */
+ temp = buf[DPCD_DPCD_REV];
+ if (temp == DP_DPCD_REV_10 || temp == DP_DPCD_REV_11)
+ edp_info->dpcd_rev = temp;
+ else {
+ printk(BIOS_ERR, "DP Wrong DPCD Rev : %x\n", temp);
+ return -1;
+ }
+ temp = buf[DPCD_MAX_LINK_RATE];
+ if (temp == DP_LANE_BW_1_62 || temp == DP_LANE_BW_2_70)
+ edp_info->lane_bw = temp;
+ else {
+ printk(BIOS_ERR, "DP Wrong MAX LINK RATE : %x\n", temp);
+ return -1;
+ }
+ /*Refer VESA Display Port Stnadard Ver1.1a Page 120 */
+ if (edp_info->dpcd_rev == DP_DPCD_REV_11) {
+ temp = buf[DPCD_MAX_LANE_COUNT] & 0x1f;
+ if (buf[DPCD_MAX_LANE_COUNT] & 0x80)
+ edp_info->dpcd_efc = 1;
+ else
+ edp_info->dpcd_efc = 0;
+ } else {
+ temp = buf[DPCD_MAX_LANE_COUNT];
+ edp_info->dpcd_efc = 0;
+ }
+
+ if (temp == DP_LANE_CNT_1 || temp == DP_LANE_CNT_2 ||
+ temp == DP_LANE_CNT_4) {
+ edp_info->lane_cnt = temp;
+ } else {
+ printk(BIOS_ERR, "DP Wrong MAX LANE COUNT : %x\n", temp);
+ return -1;
+ }
+
+ if (edp_info->raw_edid){
+ ret = EXYNOS_DP_SUCCESS;
+ printk(BIOS_SPEW, "EDID compiled in, skipping read\n");
+ } else {
+ ret = exynos_dp_read_edid();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP exynos_dp_read_edid() failed\n");
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+static void exynos_dp_init_training(void)
+{
+ /*
+ * MACRO_RST must be applied after the PLL_LOCK to avoid
+ * the DP inter pair skew issue for at least 10 us
+ */
+ exynos_dp_reset_macro();
+
+ /* All DP analog module power up */
+ exynos_dp_set_analog_power_down(POWER_ALL, 0);
+}
+
+static unsigned int exynos_dp_link_start(struct edp_device_info *edp_info)
+{
+ unsigned char buf[5];
+ unsigned int ret;
+
+ edp_info->lt_info.lt_status = DP_LT_CR;
+ edp_info->lt_info.ep_loop = 0;
+ edp_info->lt_info.cr_loop[0] = 0;
+ edp_info->lt_info.cr_loop[1] = 0;
+ edp_info->lt_info.cr_loop[2] = 0;
+ edp_info->lt_info.cr_loop[3] = 0;
+
+ /* Set sink to D0 (Sink Not Ready) mode. */
+ ret = exynos_dp_write_byte_to_dpcd(DPCD_SINK_POWER_STATE,
+ DPCD_SET_POWER_STATE_D0);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP write_dpcd_byte failed\n");
+ return ret;
+ }
+
+ /* Set link rate and count as you want to establish*/
+ exynos_dp_set_link_bandwidth(edp_info->lane_bw);
+ exynos_dp_set_lane_count(edp_info->lane_cnt);
+
+ /* Setup RX configuration */
+ buf[0] = edp_info->lane_bw;
+ buf[1] = edp_info->lane_cnt;
+
+ ret = exynos_dp_write_bytes_to_dpcd(DPCD_LINK_BW_SET, 2,
+ buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP write_dpcd_byte failed\n");
+ return ret;
+ }
+
+ exynos_dp_set_lane_pre_emphasis(PRE_EMPHASIS_LEVEL_0,
+ edp_info->lane_cnt);
+
+ /* Set training pattern 1 */
+ exynos_dp_set_training_pattern(TRAINING_PTN1);
+
+ /* Set RX training pattern */
+ buf[0] = DPCD_SCRAMBLING_DISABLED | DPCD_TRAINING_PATTERN_1;
+
+ buf[1] = DPCD_PRE_EMPHASIS_SET_PATTERN_2_LEVEL_0 |
+ DPCD_VOLTAGE_SWING_SET_PATTERN_1_LEVEL_0;
+ buf[2] = DPCD_PRE_EMPHASIS_SET_PATTERN_2_LEVEL_0 |
+ DPCD_VOLTAGE_SWING_SET_PATTERN_1_LEVEL_0;
+ buf[3] = DPCD_PRE_EMPHASIS_SET_PATTERN_2_LEVEL_0 |
+ DPCD_VOLTAGE_SWING_SET_PATTERN_1_LEVEL_0;
+ buf[4] = DPCD_PRE_EMPHASIS_SET_PATTERN_2_LEVEL_0 |
+ DPCD_VOLTAGE_SWING_SET_PATTERN_1_LEVEL_0;
+
+ ret = exynos_dp_write_bytes_to_dpcd(DPCD_TRAINING_PATTERN_SET,
+ 5, buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP write_dpcd_byte failed\n");
+ return ret;
+ }
+ return ret;
+}
+
+static unsigned int exynos_dp_training_pattern_dis(void)
+{
+ unsigned int ret;
+
+ exynos_dp_set_training_pattern(DP_NONE);
+
+ ret = exynos_dp_write_byte_to_dpcd(DPCD_TRAINING_PATTERN_SET,
+ DPCD_TRAINING_PATTERN_DISABLED);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP requst_link_traninig_req failed\n");
+ return -1;
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_enable_rx_to_enhanced_mode(unsigned char enable)
+{
+ unsigned char data;
+ unsigned int ret;
+
+ ret = exynos_dp_read_byte_from_dpcd(DPCD_LANE_COUNT_SET,
+ &data);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read_from_dpcd failed\n");
+ return -1;
+ }
+
+ if (enable)
+ data = DPCD_ENHANCED_FRAME_EN | DPCD_LN_COUNT_SET(data);
+ else
+ data = DPCD_LN_COUNT_SET(data);
+
+ ret = exynos_dp_write_byte_to_dpcd(DPCD_LANE_COUNT_SET,
+ data);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP write_to_dpcd failed\n");
+ return -1;
+
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_set_enhanced_mode(unsigned char enhance_mode)
+{
+ unsigned int ret;
+
+ ret = exynos_dp_enable_rx_to_enhanced_mode(enhance_mode);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP rx_enhance_mode failed\n");
+ return -1;
+ }
+
+ exynos_dp_enable_enhanced_mode(enhance_mode);
+
+ return ret;
+}
+
+static int exynos_dp_read_dpcd_lane_stat(struct edp_device_info *edp_info,
+ unsigned char *status)
+{
+ unsigned int ret, i;
+ unsigned char buf[2];
+ unsigned char lane_stat[DP_LANE_CNT_4] = {0,};
+ const unsigned char shift_val[] = {0, 4, 0, 4};
+
+ ret = exynos_dp_read_bytes_from_dpcd(DPCD_LANE0_1_STATUS, 2, buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read lane status failed\n");
+ return ret;
+ }
+
+ for (i = 0; i < edp_info->lane_cnt; i++) {
+ lane_stat[i] = (buf[(i / 2)] >> shift_val[i]) & 0x0f;
+ if (lane_stat[0] != lane_stat[i]) {
+ printk(BIOS_ERR, "Wrong lane status\n");
+ return -1;
+ }
+ }
+
+ *status = lane_stat[0];
+
+ return ret;
+}
+
+static unsigned int exynos_dp_read_dpcd_adj_req(unsigned char lane_num,
+ unsigned char *sw, unsigned char *em)
+{
+ const unsigned char shift_val[] = {0, 4, 0, 4};
+ unsigned int ret;
+ unsigned char buf;
+ unsigned int dpcd_addr;
+
+ /*lane_num value is used as arry index, so this range 0 ~ 3 */
+ dpcd_addr = DPCD_ADJUST_REQUEST_LANE0_1 + (lane_num / 2);
+
+ ret = exynos_dp_read_byte_from_dpcd(dpcd_addr, &buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read adjust request failed\n");
+ return -1;
+ }
+
+ *sw = ((buf >> shift_val[lane_num]) & 0x03);
+ *em = ((buf >> shift_val[lane_num]) & 0x0c) >> 2;
+
+ return ret;
+}
+
+static int exynos_dp_equalizer_err_link(struct edp_device_info *edp_info)
+{
+ int ret;
+
+ ret = exynos_dp_training_pattern_dis();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP training_patter_disable() failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ }
+
+ ret = exynos_dp_set_enhanced_mode(edp_info->dpcd_efc);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP set_enhanced_mode() failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ }
+
+ return ret;
+}
+
+static int exynos_dp_reduce_link_rate(struct edp_device_info *edp_info)
+{
+ int ret;
+
+ if (edp_info->lane_bw == DP_LANE_BW_2_70) {
+ edp_info->lane_bw = DP_LANE_BW_1_62;
+ printk(BIOS_ERR, "DP Change lane bw to 1.62Gbps\n");
+ edp_info->lt_info.lt_status = DP_LT_START;
+ ret = EXYNOS_DP_SUCCESS;
+ } else {
+ ret = exynos_dp_training_pattern_dis();
+ if (ret != EXYNOS_DP_SUCCESS)
+ printk(BIOS_ERR, "DP training_patter_disable() failed\n");
+
+ ret = exynos_dp_set_enhanced_mode(edp_info->dpcd_efc);
+ if (ret != EXYNOS_DP_SUCCESS)
+ printk(BIOS_ERR, "DP set_enhanced_mode() failed\n");
+
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_process_clock_recovery(struct edp_device_info
+ *edp_info)
+{
+ unsigned int ret;
+ unsigned char lane_stat;
+ unsigned char lt_ctl_val[DP_LANE_CNT_4] = {0, };
+ unsigned int i;
+ unsigned char adj_req_sw;
+ unsigned char adj_req_em;
+ unsigned char buf[5];
+
+ mdelay(1);
+
+ ret = exynos_dp_read_dpcd_lane_stat(edp_info, &lane_stat);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read lane status failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ return ret;
+ }
+
+ if (lane_stat & DP_LANE_STAT_CR_DONE) {
+ printk(BIOS_DEBUG,"DP clock Recovery training succeed\n");
+ exynos_dp_set_training_pattern(TRAINING_PTN2);
+
+ for (i = 0; i < edp_info->lane_cnt; i++) {
+ ret = exynos_dp_read_dpcd_adj_req(i, &adj_req_sw,
+ &adj_req_em);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ return ret;
+ }
+
+ lt_ctl_val[i] = 0;
+ lt_ctl_val[i] = adj_req_em << 3 | adj_req_sw;
+
+ if ((adj_req_sw == VOLTAGE_LEVEL_3)
+ || (adj_req_em == PRE_EMPHASIS_LEVEL_3)) {
+ lt_ctl_val[i] |= MAX_DRIVE_CURRENT_REACH_3 |
+ MAX_PRE_EMPHASIS_REACH_3;
+ }
+ exynos_dp_set_lanex_pre_emphasis(lt_ctl_val[i], i);
+ }
+
+ buf[0] = DPCD_SCRAMBLING_DISABLED | DPCD_TRAINING_PATTERN_2;
+ buf[1] = lt_ctl_val[0];
+ buf[2] = lt_ctl_val[1];
+ buf[3] = lt_ctl_val[2];
+ buf[4] = lt_ctl_val[3];
+
+ ret = exynos_dp_write_bytes_to_dpcd(
+ DPCD_TRAINING_PATTERN_SET, 5, buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP write training pattern1 failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ return ret;
+ } else
+ edp_info->lt_info.lt_status = DP_LT_ET;
+ } else {
+ for (i = 0; i < edp_info->lane_cnt; i++) {
+ lt_ctl_val[i] = exynos_dp_get_lanex_pre_emphasis(i);
+ ret = exynos_dp_read_dpcd_adj_req(i,
+ &adj_req_sw, &adj_req_em);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read adj req failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ return ret;
+ }
+
+ if ((adj_req_sw == VOLTAGE_LEVEL_3) ||
+ (adj_req_em == PRE_EMPHASIS_LEVEL_3))
+ ret = exynos_dp_reduce_link_rate(edp_info);
+
+ if ((DRIVE_CURRENT_SET_0_GET(lt_ctl_val[i]) ==
+ adj_req_sw) &&
+ (PRE_EMPHASIS_SET_0_GET(lt_ctl_val[i]) ==
+ adj_req_em)) {
+ edp_info->lt_info.cr_loop[i]++;
+ if (edp_info->lt_info.cr_loop[i] == MAX_CR_LOOP)
+ ret = exynos_dp_reduce_link_rate(
+ edp_info);
+ }
+
+ lt_ctl_val[i] = 0;
+ lt_ctl_val[i] = adj_req_em << 3 | adj_req_sw;
+
+ if ((adj_req_sw == VOLTAGE_LEVEL_3) ||
+ (adj_req_em == PRE_EMPHASIS_LEVEL_3)) {
+ lt_ctl_val[i] |= MAX_DRIVE_CURRENT_REACH_3 |
+ MAX_PRE_EMPHASIS_REACH_3;
+ }
+ exynos_dp_set_lanex_pre_emphasis(lt_ctl_val[i], i);
+ }
+
+ ret = exynos_dp_write_bytes_to_dpcd(
+ DPCD_TRAINING_LANE0_SET, 4, lt_ctl_val);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP write training pattern2 failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_process_equalizer_training(struct edp_device_info
+ *edp_info)
+{
+ unsigned int ret;
+ unsigned char lane_stat, adj_req_sw, adj_req_em, i;
+ unsigned char lt_ctl_val[DP_LANE_CNT_4] = {0,};
+ unsigned char interlane_aligned = 0;
+ unsigned char f_bw;
+ unsigned char f_lane_cnt;
+ unsigned char sink_stat;
+
+ mdelay(1);
+
+ ret = exynos_dp_read_dpcd_lane_stat(edp_info, &lane_stat);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read lane status failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ return ret;
+ }
+
+ printk(BIOS_DEBUG,"DP lane stat : %x\n", lane_stat);
+
+ if (lane_stat & DP_LANE_STAT_CR_DONE) {
+ printk(BIOS_DEBUG, "DP_LANE_STAT_CR_DONE ok\n");
+ ret = exynos_dp_read_byte_from_dpcd(DPCD_LN_ALIGN_UPDATED,
+ &sink_stat);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ printk(BIOS_ERR, "DP read DPCD_LN_ALIGN_UPDATED failed\n");
+ return ret;
+ }
+
+ interlane_aligned = (sink_stat & DPCD_INTERLANE_ALIGN_DONE);
+ printk(BIOS_DEBUG, "interlane_aligned: %d\n", interlane_aligned);
+ printk(BIOS_DEBUG, "Check %d lanes\n", edp_info->lane_cnt);
+
+ for (i = 0; i < edp_info->lane_cnt; i++) {
+ ret = exynos_dp_read_dpcd_adj_req(i,
+ &adj_req_sw, &adj_req_em);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP read adj req 1 failed\n");
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+
+ return ret;
+ }
+
+ lt_ctl_val[i] = 0;
+ lt_ctl_val[i] = adj_req_em << 3 | adj_req_sw;
+
+ if ((adj_req_sw == VOLTAGE_LEVEL_3) ||
+ (adj_req_em == PRE_EMPHASIS_LEVEL_3)) {
+ lt_ctl_val[i] |= MAX_DRIVE_CURRENT_REACH_3;
+ lt_ctl_val[i] |= MAX_PRE_EMPHASIS_REACH_3;
+ }
+ }
+
+ if (((lane_stat&DP_LANE_STAT_CE_DONE) &&
+ (lane_stat&DP_LANE_STAT_SYM_LOCK))
+ && (interlane_aligned == DPCD_INTERLANE_ALIGN_DONE)) {
+ printk(BIOS_DEBUG,"DP Equalizer training succeed\n");
+
+ f_bw = exynos_dp_get_link_bandwidth();
+ f_lane_cnt = exynos_dp_get_lane_count();
+
+ printk(BIOS_DEBUG,"DP final BandWidth : %x\n", f_bw);
+ printk(BIOS_DEBUG,"DP final Lane Count : %x\n", f_lane_cnt);
+
+ edp_info->lt_info.lt_status = DP_LT_FINISHED;
+
+ exynos_dp_equalizer_err_link(edp_info);
+
+ } else {
+ edp_info->lt_info.ep_loop++;
+
+ if (edp_info->lt_info.ep_loop > MAX_EQ_LOOP) {
+ if (edp_info->lane_bw == DP_LANE_BW_2_70) {
+ ret = exynos_dp_reduce_link_rate(
+ edp_info);
+ } else {
+ edp_info->lt_info.lt_status =
+ DP_LT_FAIL;
+ exynos_dp_equalizer_err_link(edp_info);
+ }
+ } else {
+ for (i = 0; i < edp_info->lane_cnt; i++)
+ exynos_dp_set_lanex_pre_emphasis(
+ lt_ctl_val[i], i);
+
+ ret = exynos_dp_write_bytes_to_dpcd(
+ DPCD_TRAINING_LANE0_SET,
+ 4, lt_ctl_val);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP set lt pattern failed\n");
+ edp_info->lt_info.lt_status =
+ DP_LT_FAIL;
+ exynos_dp_equalizer_err_link(edp_info);
+ }
+ }
+ }
+ } else if (edp_info->lane_bw == DP_LANE_BW_2_70) {
+ ret = exynos_dp_reduce_link_rate(edp_info);
+ } else {
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+ exynos_dp_equalizer_err_link(edp_info);
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_sw_link_training(struct edp_device_info *edp_info)
+{
+ /* the C compiler is almost smart enough to know this gets set.
+ * But not quite.
+ */
+ unsigned int ret = 0;
+ int training_finished;
+
+ /* Turn off unnecessary lane */
+ if (edp_info->lane_cnt == 1)
+ exynos_dp_set_analog_power_down(CH1_BLOCK, 1);
+
+ training_finished = 0;
+
+ edp_info->lt_info.lt_status = DP_LT_START;
+
+ /* Process here */
+ while (!training_finished) {
+ switch (edp_info->lt_info.lt_status) {
+ case DP_LT_START:
+ ret = exynos_dp_link_start(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP LT:link start failed\n");
+ training_finished = 1;
+ }
+ break;
+ case DP_LT_CR:
+ ret = exynos_dp_process_clock_recovery(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP LT:clock recovery failed\n");
+ training_finished = 1;
+ }
+ break;
+ case DP_LT_ET:
+ ret = exynos_dp_process_equalizer_training(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP LT:equalizer training failed\n");
+ training_finished = 1;
+ }
+ break;
+ case DP_LT_FINISHED:
+ training_finished = 1;
+ break;
+ case DP_LT_FAIL:
+ printk(BIOS_ERR,"DP: %s: DP_LT_FAIL: failed\n", __func__);
+ training_finished = 1;
+ ret = -1;
+ }
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_set_link_train(struct edp_device_info *edp_info)
+{
+ unsigned int ret;
+
+ exynos_dp_init_training();
+
+ ret = exynos_dp_sw_link_training(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS)
+ printk(BIOS_ERR, "DP dp_sw_link_traning() failed\n");
+
+ return ret;
+}
+
+static void exynos_dp_enable_scramble(unsigned int enable)
+{
+ unsigned char data;
+
+ if (enable) {
+ exynos_dp_enable_scrambling(DP_ENABLE);
+
+ exynos_dp_read_byte_from_dpcd(DPCD_TRAINING_PATTERN_SET,
+ &data);
+ exynos_dp_write_byte_to_dpcd(DPCD_TRAINING_PATTERN_SET,
+ (u8)(data & ~DPCD_SCRAMBLING_DISABLED));
+ } else {
+ exynos_dp_enable_scrambling(DP_DISABLE);
+ exynos_dp_read_byte_from_dpcd(DPCD_TRAINING_PATTERN_SET,
+ &data);
+ exynos_dp_write_byte_to_dpcd(DPCD_TRAINING_PATTERN_SET,
+ (u8)(data | DPCD_SCRAMBLING_DISABLED));
+ }
+}
+
+static unsigned int exynos_dp_config_video(struct edp_device_info *edp_info)
+{
+ unsigned int ret = 0;
+ unsigned int retry_cnt;
+
+ mdelay(1);
+
+ if (edp_info->video_info.master_mode) {
+ printk(BIOS_ERR,
+ "DP does not support master mode: bailing out\n");
+ return -1;
+ } else {
+ /* debug slave */
+ exynos_dp_config_video_slave_mode(&edp_info->video_info);
+ }
+
+ exynos_dp_set_video_color_format(&edp_info->video_info);
+
+ ret = exynos_dp_get_pll_lock_status();
+ if (ret != PLL_LOCKED) {
+ printk(BIOS_ERR, "DP PLL is not locked yet\n");
+ return -1;
+ }
+
+ if (edp_info->video_info.master_mode == 0) {
+ retry_cnt = 10;
+ while (retry_cnt) {
+ ret = exynos_dp_is_slave_video_stream_clock_on();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ if (retry_cnt == 0) {
+ printk(BIOS_ERR, "DP stream_clock_on failed\n");
+ return ret;
+ }
+ retry_cnt--;
+ mdelay(1);
+ } else {
+ printk(BIOS_DEBUG, "DP stream_clock succeeds\n");
+ break;
+ }
+ }
+ }
+
+ /* Set to use the register calculated M/N video */
+ exynos_dp_set_video_cr_mn(CALCULATED_M, 0, 0);
+
+ /* For video bist, Video timing must be generated by register
+ * not clear if we still need this. We could take it out and it
+ * might appear to work, then fail strangely.
+ */
+ exynos_dp_set_video_timing_mode(VIDEO_TIMING_FROM_CAPTURE);
+
+ /* we need to be sure this is off. */
+ exynos_dp_disable_video_bist();
+
+ /* Disable video mute */
+ exynos_dp_enable_video_mute(DP_DISABLE);
+
+ /* Configure video Master or Slave mode */
+ exynos_dp_enable_video_master(edp_info->video_info.master_mode);
+
+ /* Enable video */
+ exynos_dp_start_video();
+
+ if (edp_info->video_info.master_mode == 0) {
+ retry_cnt = 500;
+ while (retry_cnt) {
+ ret = exynos_dp_is_video_stream_on();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ retry_cnt--;
+ if (retry_cnt == 0) {
+ printk(BIOS_ERR, "DP Timeout of video stream\n");
+ }
+ } else {
+ printk(BIOS_DEBUG, "DP video stream is on\n");
+ break;
+ }
+ /* this is a cheap operation, involving some register
+ * reads, and no AUX channel IO. A ms. delay is fine.
+ */
+ mdelay(1);
+ }
+ }
+
+ return ret;
+}
+
+int exynos_init_dp(struct edp_device_info *edp_info)
+{
+ unsigned int ret;
+
+
+ dp_phy_control(1);
+
+ ret = exynos_dp_init_dp();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP exynos_dp_init_dp() failed\n");
+ return ret;
+ }
+
+ ret = exynos_dp_handle_edid(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "EDP handle_edid fail\n");
+ return ret;
+ }
+
+ ret = exynos_dp_set_link_train(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "DP link training fail\n");
+ return ret;
+ }
+ printk(BIOS_DEBUG, "EDP link training ok\n");
+
+ exynos_dp_enable_scramble(DP_ENABLE);
+ exynos_dp_enable_rx_to_enhanced_mode(DP_ENABLE);
+ exynos_dp_enable_enhanced_mode(DP_ENABLE);
+
+ exynos_dp_set_link_bandwidth(edp_info->lane_bw);
+ exynos_dp_set_lane_count(edp_info->lane_cnt);
+
+ exynos_dp_init_video();
+ ret = exynos_dp_config_video(edp_info);
+
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printk(BIOS_ERR, "Exynos DP init failed\n");
+ return ret;
+ }
+ printk(BIOS_DEBUG, "Exynos DP init done\n");
+
+ return ret;
+}