mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-16 09:48:16 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
1083 lines
27 KiB
C
1083 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2012 Samsung Electronics
|
|
*
|
|
* Author: Donghwa Lee <dh09.lee@samsung.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <common.h>
|
|
#include <display.h>
|
|
#include <fdtdec.h>
|
|
#include <linux/libfdt.h>
|
|
#include <malloc.h>
|
|
#include <video_bridge.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/err.h>
|
|
#include <asm/arch/clk.h>
|
|
#include <asm/arch/cpu.h>
|
|
#include <asm/arch/dp_info.h>
|
|
#include <asm/arch/dp.h>
|
|
#include <asm/arch/pinmux.h>
|
|
#include <asm/arch/power.h>
|
|
|
|
#include "exynos_dp_lowlevel.h"
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
static void exynos_dp_disp_info(struct edp_disp_info *disp_info)
|
|
{
|
|
disp_info->h_total = disp_info->h_res + disp_info->h_sync_width +
|
|
disp_info->h_back_porch + disp_info->h_front_porch;
|
|
disp_info->v_total = disp_info->v_res + disp_info->v_sync_width +
|
|
disp_info->v_back_porch + disp_info->v_front_porch;
|
|
|
|
return;
|
|
}
|
|
|
|
static int exynos_dp_init_dp(struct exynos_dp *regs)
|
|
{
|
|
int ret;
|
|
exynos_dp_reset(regs);
|
|
|
|
/* SW defined function Normal operation */
|
|
exynos_dp_enable_sw_func(regs, DP_ENABLE);
|
|
|
|
ret = exynos_dp_init_analog_func(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS)
|
|
return ret;
|
|
|
|
exynos_dp_init_hpd(regs);
|
|
exynos_dp_init_aux(regs);
|
|
|
|
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(struct exynos_dp *regs)
|
|
{
|
|
unsigned char edid[EDID_BLOCK_LENGTH * 2];
|
|
unsigned int extend_block = 0;
|
|
unsigned char sum;
|
|
unsigned char test_vector;
|
|
int retval;
|
|
|
|
/*
|
|
* 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 */
|
|
exynos_dp_read_byte_from_i2c(regs, I2C_EDID_DEVICE_ADDR,
|
|
EDID_EXTENSION_FLAG, &extend_block);
|
|
|
|
if (extend_block > 0) {
|
|
printf("DP EDID data includes a single extension!\n");
|
|
|
|
/* Read EDID data */
|
|
retval = exynos_dp_read_bytes_from_i2c(regs,
|
|
I2C_EDID_DEVICE_ADDR,
|
|
EDID_HEADER_PATTERN,
|
|
EDID_BLOCK_LENGTH,
|
|
&edid[EDID_HEADER_PATTERN]);
|
|
if (retval != 0) {
|
|
printf("DP EDID Read failed!\n");
|
|
return -1;
|
|
}
|
|
sum = exynos_dp_calc_edid_check_sum(edid);
|
|
if (sum != 0) {
|
|
printf("DP EDID bad checksum!\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Read additional EDID data */
|
|
retval = exynos_dp_read_bytes_from_i2c(regs,
|
|
I2C_EDID_DEVICE_ADDR,
|
|
EDID_BLOCK_LENGTH,
|
|
EDID_BLOCK_LENGTH,
|
|
&edid[EDID_BLOCK_LENGTH]);
|
|
if (retval != 0) {
|
|
printf("DP EDID Read failed!\n");
|
|
return -1;
|
|
}
|
|
sum = exynos_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]);
|
|
if (sum != 0) {
|
|
printf("DP EDID bad checksum!\n");
|
|
return -1;
|
|
}
|
|
|
|
exynos_dp_read_byte_from_dpcd(regs, DPCD_TEST_REQUEST,
|
|
&test_vector);
|
|
if (test_vector & DPCD_TEST_EDID_READ) {
|
|
exynos_dp_write_byte_to_dpcd(regs,
|
|
DPCD_TEST_EDID_CHECKSUM,
|
|
edid[EDID_BLOCK_LENGTH + EDID_CHECKSUM]);
|
|
exynos_dp_write_byte_to_dpcd(regs,
|
|
DPCD_TEST_RESPONSE,
|
|
DPCD_TEST_EDID_CHECKSUM_WRITE);
|
|
}
|
|
} else {
|
|
debug("DP EDID data does not include any extensions.\n");
|
|
|
|
/* Read EDID data */
|
|
retval = exynos_dp_read_bytes_from_i2c(regs,
|
|
I2C_EDID_DEVICE_ADDR,
|
|
EDID_HEADER_PATTERN,
|
|
EDID_BLOCK_LENGTH,
|
|
&edid[EDID_HEADER_PATTERN]);
|
|
|
|
if (retval != 0) {
|
|
printf("DP EDID Read failed!\n");
|
|
return -1;
|
|
}
|
|
sum = exynos_dp_calc_edid_check_sum(edid);
|
|
if (sum != 0) {
|
|
printf("DP EDID bad checksum!\n");
|
|
return -1;
|
|
}
|
|
|
|
exynos_dp_read_byte_from_dpcd(regs, DPCD_TEST_REQUEST,
|
|
&test_vector);
|
|
if (test_vector & DPCD_TEST_EDID_READ) {
|
|
exynos_dp_write_byte_to_dpcd(regs,
|
|
DPCD_TEST_EDID_CHECKSUM, edid[EDID_CHECKSUM]);
|
|
exynos_dp_write_byte_to_dpcd(regs,
|
|
DPCD_TEST_RESPONSE,
|
|
DPCD_TEST_EDID_CHECKSUM_WRITE);
|
|
}
|
|
}
|
|
|
|
debug("DP EDID Read success!\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int exynos_dp_handle_edid(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
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, 16);
|
|
memset(lane_bw, 0, 16);
|
|
memset(lane_cnt, 0, 16);
|
|
memset(buf, 0, 12);
|
|
|
|
retry_cnt = 5;
|
|
while (retry_cnt) {
|
|
/* Read DPCD 0x0000-0x000b */
|
|
ret = exynos_dp_read_bytes_from_dpcd(regs, DPCD_DPCD_REV, 12,
|
|
buf);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
if (retry_cnt == 0) {
|
|
printf("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)
|
|
priv->dpcd_rev = temp;
|
|
else {
|
|
printf("DP Wrong DPCD Rev : %x\n", temp);
|
|
return -ENODEV;
|
|
}
|
|
|
|
temp = buf[DPCD_MAX_LINK_RATE];
|
|
if (temp == DP_LANE_BW_1_62 || temp == DP_LANE_BW_2_70)
|
|
priv->lane_bw = temp;
|
|
else {
|
|
printf("DP Wrong MAX LINK RATE : %x\n", temp);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Refer VESA Display Port Standard Ver1.1a Page 120 */
|
|
if (priv->dpcd_rev == DP_DPCD_REV_11) {
|
|
temp = buf[DPCD_MAX_LANE_COUNT] & 0x1f;
|
|
if (buf[DPCD_MAX_LANE_COUNT] & 0x80)
|
|
priv->dpcd_efc = 1;
|
|
else
|
|
priv->dpcd_efc = 0;
|
|
} else {
|
|
temp = buf[DPCD_MAX_LANE_COUNT];
|
|
priv->dpcd_efc = 0;
|
|
}
|
|
|
|
if (temp == DP_LANE_CNT_1 || temp == DP_LANE_CNT_2 ||
|
|
temp == DP_LANE_CNT_4) {
|
|
priv->lane_cnt = temp;
|
|
} else {
|
|
printf("DP Wrong MAX LANE COUNT : %x\n", temp);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = exynos_dp_read_edid(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP exynos_dp_read_edid() failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void exynos_dp_init_training(struct exynos_dp *regs)
|
|
{
|
|
/*
|
|
* 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(regs);
|
|
|
|
/* All DP analog module power up */
|
|
exynos_dp_set_analog_power_down(regs, POWER_ALL, 0);
|
|
}
|
|
|
|
static unsigned int exynos_dp_link_start(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
unsigned char buf[5];
|
|
unsigned int ret = 0;
|
|
|
|
debug("DP: %s was called\n", __func__);
|
|
|
|
priv->lt_info.lt_status = DP_LT_CR;
|
|
priv->lt_info.ep_loop = 0;
|
|
priv->lt_info.cr_loop[0] = 0;
|
|
priv->lt_info.cr_loop[1] = 0;
|
|
priv->lt_info.cr_loop[2] = 0;
|
|
priv->lt_info.cr_loop[3] = 0;
|
|
|
|
/* Set sink to D0 (Sink Not Ready) mode. */
|
|
ret = exynos_dp_write_byte_to_dpcd(regs, DPCD_SINK_POWER_STATE,
|
|
DPCD_SET_POWER_STATE_D0);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP write_dpcd_byte failed\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Set link rate and count as you want to establish */
|
|
exynos_dp_set_link_bandwidth(regs, priv->lane_bw);
|
|
exynos_dp_set_lane_count(regs, priv->lane_cnt);
|
|
|
|
/* Setup RX configuration */
|
|
buf[0] = priv->lane_bw;
|
|
buf[1] = priv->lane_cnt;
|
|
|
|
ret = exynos_dp_write_bytes_to_dpcd(regs, DPCD_LINK_BW_SET, 2, buf);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP write_dpcd_byte failed\n");
|
|
return ret;
|
|
}
|
|
|
|
exynos_dp_set_lane_pre_emphasis(regs, PRE_EMPHASIS_LEVEL_0,
|
|
priv->lane_cnt);
|
|
|
|
/* Set training pattern 1 */
|
|
exynos_dp_set_training_pattern(regs, 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(regs, DPCD_TRAINING_PATTERN_SET,
|
|
5, buf);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP write_dpcd_byte failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_training_pattern_dis(struct exynos_dp *regs)
|
|
{
|
|
unsigned int ret;
|
|
|
|
exynos_dp_set_training_pattern(regs, DP_NONE);
|
|
|
|
ret = exynos_dp_write_byte_to_dpcd(regs, DPCD_TRAINING_PATTERN_SET,
|
|
DPCD_TRAINING_PATTERN_DISABLED);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP request_link_training_req failed\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_enable_rx_to_enhanced_mode(
|
|
struct exynos_dp *regs, unsigned char enable)
|
|
{
|
|
unsigned char data;
|
|
unsigned int ret;
|
|
|
|
ret = exynos_dp_read_byte_from_dpcd(regs, DPCD_LANE_COUNT_SET,
|
|
&data);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read_from_dpcd failed\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
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(regs, DPCD_LANE_COUNT_SET, data);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP write_to_dpcd failed\n");
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_set_enhanced_mode(struct exynos_dp *regs,
|
|
unsigned char enhance_mode)
|
|
{
|
|
unsigned int ret;
|
|
|
|
ret = exynos_dp_enable_rx_to_enhanced_mode(regs, enhance_mode);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP rx_enhance_mode failed\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
exynos_dp_enable_enhanced_mode(regs, enhance_mode);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_dp_read_dpcd_lane_stat(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv,
|
|
unsigned char *status)
|
|
{
|
|
unsigned int ret, i;
|
|
unsigned char buf[2];
|
|
unsigned char lane_stat[DP_LANE_CNT_4] = {0,};
|
|
unsigned char shift_val[DP_LANE_CNT_4] = {0,};
|
|
|
|
shift_val[0] = 0;
|
|
shift_val[1] = 4;
|
|
shift_val[2] = 0;
|
|
shift_val[3] = 4;
|
|
|
|
ret = exynos_dp_read_bytes_from_dpcd(regs, DPCD_LANE0_1_STATUS, 2,
|
|
buf);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read lane status failed\n");
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < priv->lane_cnt; i++) {
|
|
lane_stat[i] = (buf[(i / 2)] >> shift_val[i]) & 0x0f;
|
|
if (lane_stat[0] != lane_stat[i]) {
|
|
printf("Wrong lane status\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
*status = lane_stat[0];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_read_dpcd_adj_req(struct exynos_dp *regs,
|
|
unsigned char lane_num, unsigned char *sw, unsigned char *em)
|
|
{
|
|
unsigned int ret;
|
|
unsigned char buf;
|
|
unsigned int dpcd_addr;
|
|
unsigned char shift_val[DP_LANE_CNT_4] = {0, 4, 0, 4};
|
|
|
|
/* lane_num value is used as array index, so this range 0 ~ 3 */
|
|
dpcd_addr = DPCD_ADJUST_REQUEST_LANE0_1 + (lane_num / 2);
|
|
|
|
ret = exynos_dp_read_byte_from_dpcd(regs, dpcd_addr, &buf);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read adjust request failed\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
*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 exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
int ret;
|
|
|
|
ret = exynos_dp_training_pattern_dis(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP training_pattern_disable() failed\n");
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
}
|
|
|
|
ret = exynos_dp_set_enhanced_mode(regs, priv->dpcd_efc);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP set_enhanced_mode() failed\n");
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_dp_reduce_link_rate(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
int ret;
|
|
|
|
if (priv->lane_bw == DP_LANE_BW_2_70) {
|
|
priv->lane_bw = DP_LANE_BW_1_62;
|
|
printf("DP Change lane bw to 1.62Gbps\n");
|
|
priv->lt_info.lt_status = DP_LT_START;
|
|
ret = EXYNOS_DP_SUCCESS;
|
|
} else {
|
|
ret = exynos_dp_training_pattern_dis(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS)
|
|
printf("DP training_patter_disable() failed\n");
|
|
|
|
ret = exynos_dp_set_enhanced_mode(regs, priv->dpcd_efc);
|
|
if (ret != EXYNOS_DP_SUCCESS)
|
|
printf("DP set_enhanced_mode() failed\n");
|
|
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_process_clock_recovery(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
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];
|
|
|
|
debug("DP: %s was called\n", __func__);
|
|
mdelay(1);
|
|
|
|
ret = exynos_dp_read_dpcd_lane_stat(regs, priv, &lane_stat);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read lane status failed\n");
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
return ret;
|
|
}
|
|
|
|
if (lane_stat & DP_LANE_STAT_CR_DONE) {
|
|
debug("DP clock Recovery training succeed\n");
|
|
exynos_dp_set_training_pattern(regs, TRAINING_PTN2);
|
|
|
|
for (i = 0; i < priv->lane_cnt; i++) {
|
|
ret = exynos_dp_read_dpcd_adj_req(regs, i,
|
|
&adj_req_sw, &adj_req_em);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
priv->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(regs,
|
|
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(regs,
|
|
DPCD_TRAINING_PATTERN_SET, 5, buf);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP write training pattern1 failed\n");
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
return ret;
|
|
} else
|
|
priv->lt_info.lt_status = DP_LT_ET;
|
|
} else {
|
|
for (i = 0; i < priv->lane_cnt; i++) {
|
|
lt_ctl_val[i] = exynos_dp_get_lanex_pre_emphasis(
|
|
regs, i);
|
|
ret = exynos_dp_read_dpcd_adj_req(regs, i,
|
|
&adj_req_sw, &adj_req_em);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read adj req failed\n");
|
|
priv->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(regs,
|
|
priv);
|
|
|
|
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)) {
|
|
priv->lt_info.cr_loop[i]++;
|
|
if (priv->lt_info.cr_loop[i] == MAX_CR_LOOP)
|
|
ret = exynos_dp_reduce_link_rate(
|
|
regs, priv);
|
|
}
|
|
|
|
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(regs,
|
|
lt_ctl_val[i], i);
|
|
}
|
|
|
|
ret = exynos_dp_write_bytes_to_dpcd(regs,
|
|
DPCD_TRAINING_LANE0_SET, 4, lt_ctl_val);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP write training pattern2 failed\n");
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_process_equalizer_training(
|
|
struct exynos_dp *regs, struct exynos_dp_priv *priv)
|
|
{
|
|
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(regs, priv, &lane_stat);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read lane status failed\n");
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
return ret;
|
|
}
|
|
|
|
debug("DP lane stat : %x\n", lane_stat);
|
|
|
|
if (lane_stat & DP_LANE_STAT_CR_DONE) {
|
|
ret = exynos_dp_read_byte_from_dpcd(regs,
|
|
DPCD_LN_ALIGN_UPDATED,
|
|
&sink_stat);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
interlane_aligned = (sink_stat & DPCD_INTERLANE_ALIGN_DONE);
|
|
|
|
for (i = 0; i < priv->lane_cnt; i++) {
|
|
ret = exynos_dp_read_dpcd_adj_req(regs, i,
|
|
&adj_req_sw, &adj_req_em);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP read adj req 1 failed\n");
|
|
priv->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)) {
|
|
debug("DP Equalizer training succeed\n");
|
|
|
|
f_bw = exynos_dp_get_link_bandwidth(regs);
|
|
f_lane_cnt = exynos_dp_get_lane_count(regs);
|
|
|
|
debug("DP final BandWidth : %x\n", f_bw);
|
|
debug("DP final Lane Count : %x\n", f_lane_cnt);
|
|
|
|
priv->lt_info.lt_status = DP_LT_FINISHED;
|
|
|
|
exynos_dp_equalizer_err_link(regs, priv);
|
|
|
|
} else {
|
|
priv->lt_info.ep_loop++;
|
|
|
|
if (priv->lt_info.ep_loop > MAX_EQ_LOOP) {
|
|
if (priv->lane_bw == DP_LANE_BW_2_70) {
|
|
ret = exynos_dp_reduce_link_rate(
|
|
regs, priv);
|
|
} else {
|
|
priv->lt_info.lt_status =
|
|
DP_LT_FAIL;
|
|
exynos_dp_equalizer_err_link(regs,
|
|
priv);
|
|
}
|
|
} else {
|
|
for (i = 0; i < priv->lane_cnt; i++)
|
|
exynos_dp_set_lanex_pre_emphasis(
|
|
regs, lt_ctl_val[i], i);
|
|
|
|
ret = exynos_dp_write_bytes_to_dpcd(regs,
|
|
DPCD_TRAINING_LANE0_SET,
|
|
4, lt_ctl_val);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP set lt pattern failed\n");
|
|
priv->lt_info.lt_status =
|
|
DP_LT_FAIL;
|
|
exynos_dp_equalizer_err_link(regs,
|
|
priv);
|
|
}
|
|
}
|
|
}
|
|
} else if (priv->lane_bw == DP_LANE_BW_2_70) {
|
|
ret = exynos_dp_reduce_link_rate(regs, priv);
|
|
} else {
|
|
priv->lt_info.lt_status = DP_LT_FAIL;
|
|
exynos_dp_equalizer_err_link(regs, priv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_sw_link_training(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
unsigned int ret = 0;
|
|
int training_finished;
|
|
|
|
/* Turn off unnecessary lane */
|
|
if (priv->lane_cnt == 1)
|
|
exynos_dp_set_analog_power_down(regs, CH1_BLOCK, 1);
|
|
|
|
training_finished = 0;
|
|
|
|
priv->lt_info.lt_status = DP_LT_START;
|
|
|
|
/* Process here */
|
|
while (!training_finished) {
|
|
switch (priv->lt_info.lt_status) {
|
|
case DP_LT_START:
|
|
ret = exynos_dp_link_start(regs, priv);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP LT:link start failed\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case DP_LT_CR:
|
|
ret = exynos_dp_process_clock_recovery(regs,
|
|
priv);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP LT:clock recovery failed\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case DP_LT_ET:
|
|
ret = exynos_dp_process_equalizer_training(regs,
|
|
priv);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP LT:equalizer training failed\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case DP_LT_FINISHED:
|
|
training_finished = 1;
|
|
break;
|
|
case DP_LT_FAIL:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int exynos_dp_set_link_train(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
unsigned int ret;
|
|
|
|
exynos_dp_init_training(regs);
|
|
|
|
ret = exynos_dp_sw_link_training(regs, priv);
|
|
if (ret != EXYNOS_DP_SUCCESS)
|
|
printf("DP dp_sw_link_training() failed\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void exynos_dp_enable_scramble(struct exynos_dp *regs,
|
|
unsigned int enable)
|
|
{
|
|
unsigned char data;
|
|
|
|
if (enable) {
|
|
exynos_dp_enable_scrambling(regs, DP_ENABLE);
|
|
|
|
exynos_dp_read_byte_from_dpcd(regs,
|
|
DPCD_TRAINING_PATTERN_SET, &data);
|
|
exynos_dp_write_byte_to_dpcd(regs, DPCD_TRAINING_PATTERN_SET,
|
|
(u8)(data & ~DPCD_SCRAMBLING_DISABLED));
|
|
} else {
|
|
exynos_dp_enable_scrambling(regs, DP_DISABLE);
|
|
exynos_dp_read_byte_from_dpcd(regs,
|
|
DPCD_TRAINING_PATTERN_SET, &data);
|
|
exynos_dp_write_byte_to_dpcd(regs, DPCD_TRAINING_PATTERN_SET,
|
|
(u8)(data | DPCD_SCRAMBLING_DISABLED));
|
|
}
|
|
}
|
|
|
|
static unsigned int exynos_dp_config_video(struct exynos_dp *regs,
|
|
struct exynos_dp_priv *priv)
|
|
{
|
|
unsigned int ret = 0;
|
|
unsigned int retry_cnt;
|
|
|
|
mdelay(1);
|
|
|
|
if (priv->video_info.master_mode) {
|
|
printf("DP does not support master mode\n");
|
|
return -ENODEV;
|
|
} else {
|
|
/* debug slave */
|
|
exynos_dp_config_video_slave_mode(regs,
|
|
&priv->video_info);
|
|
}
|
|
|
|
exynos_dp_set_video_color_format(regs, &priv->video_info);
|
|
|
|
if (priv->video_info.bist_mode) {
|
|
if (exynos_dp_config_video_bist(regs, priv) != 0)
|
|
return -1;
|
|
}
|
|
|
|
ret = exynos_dp_get_pll_lock_status(regs);
|
|
if (ret != PLL_LOCKED) {
|
|
printf("DP PLL is not locked yet\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (priv->video_info.master_mode == 0) {
|
|
retry_cnt = 10;
|
|
while (retry_cnt) {
|
|
ret = exynos_dp_is_slave_video_stream_clock_on(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
if (retry_cnt == 0) {
|
|
printf("DP stream_clock_on failed\n");
|
|
return ret;
|
|
}
|
|
retry_cnt--;
|
|
mdelay(1);
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set to use the register calculated M/N video */
|
|
exynos_dp_set_video_cr_mn(regs, CALCULATED_M, 0, 0);
|
|
|
|
/* For video bist, Video timing must be generated by register */
|
|
exynos_dp_set_video_timing_mode(regs, VIDEO_TIMING_FROM_CAPTURE);
|
|
|
|
/* Enable video bist */
|
|
if (priv->video_info.bist_pattern != COLOR_RAMP &&
|
|
priv->video_info.bist_pattern != BALCK_WHITE_V_LINES &&
|
|
priv->video_info.bist_pattern != COLOR_SQUARE)
|
|
exynos_dp_enable_video_bist(regs,
|
|
priv->video_info.bist_mode);
|
|
else
|
|
exynos_dp_enable_video_bist(regs, DP_DISABLE);
|
|
|
|
/* Disable video mute */
|
|
exynos_dp_enable_video_mute(regs, DP_DISABLE);
|
|
|
|
/* Configure video Master or Slave mode */
|
|
exynos_dp_enable_video_master(regs,
|
|
priv->video_info.master_mode);
|
|
|
|
/* Enable video */
|
|
exynos_dp_start_video(regs);
|
|
|
|
if (priv->video_info.master_mode == 0) {
|
|
retry_cnt = 100;
|
|
while (retry_cnt) {
|
|
ret = exynos_dp_is_video_stream_on(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
if (retry_cnt == 0) {
|
|
printf("DP Timeout of video stream\n");
|
|
return ret;
|
|
}
|
|
retry_cnt--;
|
|
mdelay(5);
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_dp_ofdata_to_platdata(struct udevice *dev)
|
|
{
|
|
struct exynos_dp_priv *priv = dev_get_priv(dev);
|
|
const void *blob = gd->fdt_blob;
|
|
unsigned int node = dev_of_offset(dev);
|
|
fdt_addr_t addr;
|
|
|
|
addr = devfdt_get_addr(dev);
|
|
if (addr == FDT_ADDR_T_NONE) {
|
|
debug("Can't get the DP base address\n");
|
|
return -EINVAL;
|
|
}
|
|
priv->regs = (struct exynos_dp *)addr;
|
|
priv->disp_info.h_res = fdtdec_get_int(blob, node,
|
|
"samsung,h-res", 0);
|
|
priv->disp_info.h_sync_width = fdtdec_get_int(blob, node,
|
|
"samsung,h-sync-width", 0);
|
|
priv->disp_info.h_back_porch = fdtdec_get_int(blob, node,
|
|
"samsung,h-back-porch", 0);
|
|
priv->disp_info.h_front_porch = fdtdec_get_int(blob, node,
|
|
"samsung,h-front-porch", 0);
|
|
priv->disp_info.v_res = fdtdec_get_int(blob, node,
|
|
"samsung,v-res", 0);
|
|
priv->disp_info.v_sync_width = fdtdec_get_int(blob, node,
|
|
"samsung,v-sync-width", 0);
|
|
priv->disp_info.v_back_porch = fdtdec_get_int(blob, node,
|
|
"samsung,v-back-porch", 0);
|
|
priv->disp_info.v_front_porch = fdtdec_get_int(blob, node,
|
|
"samsung,v-front-porch", 0);
|
|
priv->disp_info.v_sync_rate = fdtdec_get_int(blob, node,
|
|
"samsung,v-sync-rate", 0);
|
|
|
|
priv->lt_info.lt_status = fdtdec_get_int(blob, node,
|
|
"samsung,lt-status", 0);
|
|
|
|
priv->video_info.master_mode = fdtdec_get_int(blob, node,
|
|
"samsung,master-mode", 0);
|
|
priv->video_info.bist_mode = fdtdec_get_int(blob, node,
|
|
"samsung,bist-mode", 0);
|
|
priv->video_info.bist_pattern = fdtdec_get_int(blob, node,
|
|
"samsung,bist-pattern", 0);
|
|
priv->video_info.h_sync_polarity = fdtdec_get_int(blob, node,
|
|
"samsung,h-sync-polarity", 0);
|
|
priv->video_info.v_sync_polarity = fdtdec_get_int(blob, node,
|
|
"samsung,v-sync-polarity", 0);
|
|
priv->video_info.interlaced = fdtdec_get_int(blob, node,
|
|
"samsung,interlaced", 0);
|
|
priv->video_info.color_space = fdtdec_get_int(blob, node,
|
|
"samsung,color-space", 0);
|
|
priv->video_info.dynamic_range = fdtdec_get_int(blob, node,
|
|
"samsung,dynamic-range", 0);
|
|
priv->video_info.ycbcr_coeff = fdtdec_get_int(blob, node,
|
|
"samsung,ycbcr-coeff", 0);
|
|
priv->video_info.color_depth = fdtdec_get_int(blob, node,
|
|
"samsung,color-depth", 0);
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_dp_bridge_init(struct udevice *dev)
|
|
{
|
|
const int max_tries = 10;
|
|
int num_tries;
|
|
int ret;
|
|
|
|
debug("%s\n", __func__);
|
|
ret = video_bridge_attach(dev);
|
|
if (ret) {
|
|
debug("video bridge init failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We need to wait for 90ms after bringing up the bridge since there
|
|
* is a phantom "high" on the HPD chip during its bootup. The phantom
|
|
* high comes within 7ms of de-asserting PD and persists for at least
|
|
* 15ms. The real high comes roughly 50ms after PD is de-asserted. The
|
|
* phantom high makes it hard for us to know when the NXP chip is up.
|
|
*/
|
|
mdelay(90);
|
|
|
|
for (num_tries = 0; num_tries < max_tries; num_tries++) {
|
|
/* Check HPD. If it's high, or we don't have it, all is well */
|
|
ret = video_bridge_check_attached(dev);
|
|
if (!ret || ret == -ENOENT)
|
|
return 0;
|
|
|
|
debug("%s: eDP bridge failed to come up; try %d of %d\n",
|
|
__func__, num_tries, max_tries);
|
|
}
|
|
|
|
/* Immediately go into bridge reset if the hp line is not high */
|
|
return -EIO;
|
|
}
|
|
|
|
static int exynos_dp_bridge_setup(const void *blob)
|
|
{
|
|
const int max_tries = 2;
|
|
int num_tries;
|
|
struct udevice *dev;
|
|
int ret;
|
|
|
|
/* Configure I2C registers for Parade bridge */
|
|
ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &dev);
|
|
if (ret) {
|
|
debug("video bridge init failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (strncmp(dev->driver->name, "parade", 6)) {
|
|
/* Mux HPHPD to the special hotplug detect mode */
|
|
exynos_pinmux_config(PERIPH_ID_DPHPD, 0);
|
|
}
|
|
|
|
for (num_tries = 0; num_tries < max_tries; num_tries++) {
|
|
ret = exynos_dp_bridge_init(dev);
|
|
if (!ret)
|
|
return 0;
|
|
if (num_tries == max_tries - 1)
|
|
break;
|
|
|
|
/*
|
|
* If we're here, the bridge chip failed to initialise.
|
|
* Power down the bridge in an attempt to reset.
|
|
*/
|
|
video_bridge_set_active(dev, false);
|
|
|
|
/*
|
|
* Arbitrarily wait 300ms here with DP_N low. Don't know for
|
|
* sure how long we should wait, but we're being paranoid.
|
|
*/
|
|
mdelay(300);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
int exynos_dp_enable(struct udevice *dev, int panel_bpp,
|
|
const struct display_timing *timing)
|
|
{
|
|
struct exynos_dp_priv *priv = dev_get_priv(dev);
|
|
struct exynos_dp *regs = priv->regs;
|
|
unsigned int ret;
|
|
|
|
debug("%s: start\n", __func__);
|
|
exynos_dp_disp_info(&priv->disp_info);
|
|
|
|
ret = exynos_dp_bridge_setup(gd->fdt_blob);
|
|
if (ret && ret != -ENODEV)
|
|
printf("LCD bridge failed to enable: %d\n", ret);
|
|
|
|
exynos_dp_phy_ctrl(1);
|
|
|
|
ret = exynos_dp_init_dp(regs);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP exynos_dp_init_dp() failed\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = exynos_dp_handle_edid(regs, priv);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("EDP handle_edid fail\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = exynos_dp_set_link_train(regs, priv);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("DP link training fail\n");
|
|
return ret;
|
|
}
|
|
|
|
exynos_dp_enable_scramble(regs, DP_ENABLE);
|
|
exynos_dp_enable_rx_to_enhanced_mode(regs, DP_ENABLE);
|
|
exynos_dp_enable_enhanced_mode(regs, DP_ENABLE);
|
|
|
|
exynos_dp_set_link_bandwidth(regs, priv->lane_bw);
|
|
exynos_dp_set_lane_count(regs, priv->lane_cnt);
|
|
|
|
exynos_dp_init_video(regs);
|
|
ret = exynos_dp_config_video(regs, priv);
|
|
if (ret != EXYNOS_DP_SUCCESS) {
|
|
printf("Exynos DP init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
debug("Exynos DP init done\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const struct dm_display_ops exynos_dp_ops = {
|
|
.enable = exynos_dp_enable,
|
|
};
|
|
|
|
static const struct udevice_id exynos_dp_ids[] = {
|
|
{ .compatible = "samsung,exynos5-dp" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(exynos_dp) = {
|
|
.name = "exynos_dp",
|
|
.id = UCLASS_DISPLAY,
|
|
.of_match = exynos_dp_ids,
|
|
.ops = &exynos_dp_ops,
|
|
.ofdata_to_platdata = exynos_dp_ofdata_to_platdata,
|
|
.priv_auto_alloc_size = sizeof(struct exynos_dp_priv),
|
|
};
|