summaryrefslogtreecommitdiffstats
path: root/drivers/video/exynos
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/exynos')
-rw-r--r--drivers/video/exynos/Makefile12
-rw-r--r--drivers/video/exynos/exynos_dp.c961
-rw-r--r--drivers/video/exynos/exynos_dp_lowlevel.c1257
-rw-r--r--drivers/video/exynos/exynos_dp_lowlevel.h68
-rw-r--r--drivers/video/exynos/exynos_fb.c330
-rw-r--r--drivers/video/exynos/exynos_fb.h41
-rw-r--r--drivers/video/exynos/exynos_fimd.c409
-rw-r--r--drivers/video/exynos/exynos_mipi_dsi.c337
-rw-r--r--drivers/video/exynos/exynos_mipi_dsi_common.c620
-rw-r--r--drivers/video/exynos/exynos_mipi_dsi_common.h35
-rw-r--r--drivers/video/exynos/exynos_mipi_dsi_lowlevel.c639
-rw-r--r--drivers/video/exynos/exynos_mipi_dsi_lowlevel.h98
-rw-r--r--drivers/video/exynos/exynos_pwm_bl.c45
13 files changed, 4852 insertions, 0 deletions
diff --git a/drivers/video/exynos/Makefile b/drivers/video/exynos/Makefile
new file mode 100644
index 0000000000..d4bdf3230d
--- /dev/null
+++ b/drivers/video/exynos/Makefile
@@ -0,0 +1,12 @@
+#
+# (C) Copyright 2000-2007
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
+
+obj-$(CONFIG_EXYNOS_DP) += exynos_dp.o exynos_dp_lowlevel.o
+obj-$(CONFIG_EXYNOS_FB) += exynos_fb.o exynos_fimd.o
+obj-$(CONFIG_EXYNOS_MIPI_DSIM) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \
+ exynos_mipi_dsi_lowlevel.o
+obj-$(CONFIG_EXYNOS_PWM_BL) += exynos_pwm_bl.o
diff --git a/drivers/video/exynos/exynos_dp.c b/drivers/video/exynos/exynos_dp.c
new file mode 100644
index 0000000000..0d5d090d0e
--- /dev/null
+++ b/drivers/video/exynos/exynos_dp.c
@@ -0,0 +1,961 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+#include <common.h>
+#include <malloc.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 <fdtdec.h>
+#include <libfdt.h>
+
+#include "exynos_dp_lowlevel.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+void __exynos_set_dp_phy(unsigned int onoff)
+{
+}
+void exynos_set_dp_phy(unsigned int onoff)
+ __attribute__((weak, alias("__exynos_set_dp_phy")));
+
+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(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;
+
+ /*
+ * 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(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(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(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(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 {
+ debug("DP EDID data does not include any extensions.\n");
+
+ /* 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) {
+ 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(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);
+ }
+ }
+
+ debug("DP EDID Read success!\n");
+
+ 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, 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(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)
+ edp_info->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)
+ edp_info->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 (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 {
+ printf("DP Wrong MAX LANE COUNT : %x\n", temp);
+ return -EINVAL;
+ }
+
+ ret = exynos_dp_read_edid();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("DP exynos_dp_read_edid() failed\n");
+ return -EINVAL;
+ }
+
+ 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 = 0;
+
+ debug("DP: %s was called\n", __func__);
+
+ 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) {
+ printf("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) {
+ printf("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) {
+ printf("DP write_dpcd_byte failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_training_pattern_dis(void)
+{
+ unsigned int ret = EXYNOS_DP_SUCCESS;
+
+ 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) {
+ printf("DP request_link_training_req failed\n");
+ return -EAGAIN;
+ }
+
+ return ret;
+}
+
+static unsigned int exynos_dp_enable_rx_to_enhanced_mode(unsigned char enable)
+{
+ unsigned char data;
+ unsigned int ret = EXYNOS_DP_SUCCESS;
+
+ ret = exynos_dp_read_byte_from_dpcd(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(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(unsigned char enhance_mode)
+{
+ unsigned int ret = EXYNOS_DP_SUCCESS;
+
+ ret = exynos_dp_enable_rx_to_enhanced_mode(enhance_mode);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("DP rx_enhance_mode failed\n");
+ return -EAGAIN;
+ }
+
+ 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,};
+ 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(DPCD_LANE0_1_STATUS, 2, buf);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("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]) {
+ printf("Wrong lane status\n");
+ return -EINVAL;
+ }
+ }
+
+ *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)
+{
+ unsigned int ret = EXYNOS_DP_SUCCESS;
+ 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(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 edp_device_info *edp_info)
+{
+ int ret;
+
+ ret = exynos_dp_training_pattern_dis();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("DP training_pattern_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) {
+ printf("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;
+ printf("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)
+ printf("DP training_patter_disable() failed\n");
+
+ ret = exynos_dp_set_enhanced_mode(edp_info->dpcd_efc);
+ if (ret != EXYNOS_DP_SUCCESS)
+ printf("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 = EXYNOS_DP_SUCCESS;
+ 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(edp_info, &lane_stat);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("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) {
+ 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) {
+ printf("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) {
+ printf("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) {
+ printf("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 = EXYNOS_DP_SUCCESS;
+ 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) {
+ printf("DP read lane status failed\n");
+ edp_info->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(DPCD_LN_ALIGN_UPDATED,
+ &sink_stat);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ edp_info->lt_info.lt_status = DP_LT_FAIL;
+
+ return ret;
+ }
+
+ interlane_aligned = (sink_stat & DPCD_INTERLANE_ALIGN_DONE);
+
+ 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) {
+ printf("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)) {
+ debug("DP Equalizer training succeed\n");
+
+ f_bw = exynos_dp_get_link_bandwidth();
+ f_lane_cnt = exynos_dp_get_lane_count();
+
+ debug("DP final BandWidth : %x\n", f_bw);
+ 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) {
+ printf("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)
+{
+ 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) {
+ printf("DP LT:link start failed\n");
+ return ret;
+ }
+ break;
+ case DP_LT_CR:
+ ret = exynos_dp_process_clock_recovery(edp_info);
+ 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(edp_info);
+ 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 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)
+ printf("DP dp_sw_link_training() 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) {
+ printf("DP does not support master mode\n");
+ return -ENODEV;
+ } else {
+ /* debug slave */
+ exynos_dp_config_video_slave_mode(&edp_info->video_info);
+ }
+
+ exynos_dp_set_video_color_format(&edp_info->video_info);
+
+ if (edp_info->video_info.bist_mode) {
+ if (exynos_dp_config_video_bist(edp_info) != 0)
+ return -1;
+ }
+
+ ret = exynos_dp_get_pll_lock_status();
+ if (ret != PLL_LOCKED) {
+ printf("DP PLL is not locked yet\n");
+ return -EIO;
+ }
+
+ 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) {
+ 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(CALCULATED_M, 0, 0);
+
+ /* For video bist, Video timing must be generated by register */
+ exynos_dp_set_video_timing_mode(VIDEO_TIMING_FROM_CAPTURE);
+
+ /* Enable video bist */
+ if (edp_info->video_info.bist_pattern != COLOR_RAMP &&
+ edp_info->video_info.bist_pattern != BALCK_WHITE_V_LINES &&
+ edp_info->video_info.bist_pattern != COLOR_SQUARE)
+ exynos_dp_enable_video_bist(edp_info->video_info.bist_mode);
+ else
+ exynos_dp_enable_video_bist(DP_DISABLE);
+
+ /* 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 = 100;
+ while (retry_cnt) {
+ ret = exynos_dp_is_video_stream_on();
+ 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;
+}
+
+int exynos_dp_parse_dt(const void *blob, struct edp_device_info *edp_info)
+{
+ unsigned int node = fdtdec_next_compatible(blob, 0,
+ COMPAT_SAMSUNG_EXYNOS5_DP);
+ if (node <= 0) {
+ debug("exynos_dp: Can't get device node for dp\n");
+ return -ENODEV;
+ }
+
+ edp_info->disp_info.h_res = fdtdec_get_int(blob, node,
+ "samsung,h-res", 0);
+ edp_info->disp_info.h_sync_width = fdtdec_get_int(blob, node,
+ "samsung,h-sync-width", 0);
+ edp_info->disp_info.h_back_porch = fdtdec_get_int(blob, node,
+ "samsung,h-back-porch", 0);
+ edp_info->disp_info.h_front_porch = fdtdec_get_int(blob, node,
+ "samsung,h-front-porch", 0);
+ edp_info->disp_info.v_res = fdtdec_get_int(blob, node,
+ "samsung,v-res", 0);
+ edp_info->disp_info.v_sync_width = fdtdec_get_int(blob, node,
+ "samsung,v-sync-width", 0);
+ edp_info->disp_info.v_back_porch = fdtdec_get_int(blob, node,
+ "samsung,v-back-porch", 0);
+ edp_info->disp_info.v_front_porch = fdtdec_get_int(blob, node,
+ "samsung,v-front-porch", 0);
+ edp_info->disp_info.v_sync_rate = fdtdec_get_int(blob, node,
+ "samsung,v-sync-rate", 0);
+
+ edp_info->lt_info.lt_status = fdtdec_get_int(blob, node,
+ "samsung,lt-status", 0);
+
+ edp_info->video_info.master_mode = fdtdec_get_int(blob, node,
+ "samsung,master-mode", 0);
+ edp_info->video_info.bist_mode = fdtdec_get_int(blob, node,
+ "samsung,bist-mode", 0);
+ edp_info->video_info.bist_pattern = fdtdec_get_int(blob, node,
+ "samsung,bist-pattern", 0);
+ edp_info->video_info.h_sync_polarity = fdtdec_get_int(blob, node,
+ "samsung,h-sync-polarity", 0);
+ edp_info->video_info.v_sync_polarity = fdtdec_get_int(blob, node,
+ "samsung,v-sync-polarity", 0);
+ edp_info->video_info.interlaced = fdtdec_get_int(blob, node,
+ "samsung,interlaced", 0);
+ edp_info->video_info.color_space = fdtdec_get_int(blob, node,
+ "samsung,color-space", 0);
+ edp_info->video_info.dynamic_range = fdtdec_get_int(blob, node,
+ "samsung,dynamic-range", 0);
+ edp_info->video_info.ycbcr_coeff = fdtdec_get_int(blob, node,
+ "samsung,ycbcr-coeff", 0);
+ edp_info->video_info.color_depth = fdtdec_get_int(blob, node,
+ "samsung,color-depth", 0);
+ return 0;
+}
+
+unsigned int exynos_init_dp(void)
+{
+ unsigned int ret;
+ struct edp_device_info *edp_info;
+
+ edp_info = kzalloc(sizeof(struct edp_device_info), GFP_KERNEL);
+ if (!edp_info) {
+ debug("failed to allocate edp device object.\n");
+ return -EFAULT;
+ }
+
+ if (exynos_dp_parse_dt(gd->fdt_blob, edp_info))
+ debug("unable to parse DP DT node\n");
+
+ exynos_dp_set_base_addr();
+
+ exynos_dp_disp_info(&edp_info->disp_info);
+
+ exynos_set_dp_phy(1);
+
+ ret = exynos_dp_init_dp();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("DP exynos_dp_init_dp() failed\n");
+ return ret;
+ }
+
+ ret = exynos_dp_handle_edid(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("EDP handle_edid fail\n");
+ return ret;
+ }
+
+ ret = exynos_dp_set_link_train(edp_info);
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("DP link training fail\n");
+ return ret;
+ }
+
+ 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) {
+ printf("Exynos DP init failed\n");
+ return ret;
+ }
+
+ debug("Exynos DP init done\n");
+
+ return ret;
+}
diff --git a/drivers/video/exynos/exynos_dp_lowlevel.c b/drivers/video/exynos/exynos_dp_lowlevel.c
new file mode 100644
index 0000000000..acb5bc8eb7
--- /dev/null
+++ b/drivers/video/exynos/exynos_dp_lowlevel.c
@@ -0,0 +1,1257 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+#include <common.h>
+#include <linux/err.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/dp_info.h>
+#include <asm/arch/dp.h>
+#include <fdtdec.h>
+#include <libfdt.h>
+
+/* Declare global data pointer */
+DECLARE_GLOBAL_DATA_PTR;
+
+struct exynos_dp *dp_regs;
+
+void exynos_dp_set_base_addr(void)
+{
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+ unsigned int node = fdtdec_next_compatible(gd->fdt_blob,
+ 0, COMPAT_SAMSUNG_EXYNOS5_DP);
+ if (node <= 0)
+ debug("exynos_dp: Can't get device node for dp\n");
+
+ dp_regs = (struct exynos_dp *)fdtdec_get_addr(gd->fdt_blob,
+ node, "reg");
+ if (dp_regs == NULL)
+ debug("Can't get the DP base address\n");
+#else
+ dp_regs = (struct exynos_dp *)samsung_get_base_dp();
+#endif
+}
+
+static void exynos_dp_enable_video_input(unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->video_ctl1);
+ reg &= ~VIDEO_EN_MASK;
+
+ /* enable video input */
+ if (enable)
+ reg |= VIDEO_EN_MASK;
+
+ writel(reg, &dp_regs->video_ctl1);
+
+ return;
+}
+
+void exynos_dp_enable_video_bist(unsigned int enable)
+{
+ /* enable video bist */
+ unsigned int reg;
+
+ reg = readl(&dp_regs->video_ctl4);
+ reg &= ~VIDEO_BIST_MASK;
+
+ /* enable video bist */
+ if (enable)
+ reg |= VIDEO_BIST_MASK;
+
+ writel(reg, &dp_regs->video_ctl4);
+
+ return;
+}
+
+void exynos_dp_enable_video_mute(unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->video_ctl1);
+ reg &= ~(VIDEO_MUTE_MASK);
+ if (enable)
+ reg |= VIDEO_MUTE_MASK;
+
+ writel(reg, &dp_regs->video_ctl1);
+
+ return;
+}
+
+
+static void exynos_dp_init_analog_param(void)
+{
+ unsigned int reg;
+
+ /*
+ * Set termination
+ * Normal bandgap, Normal swing, Tx terminal registor 61 ohm
+ * 24M Phy clock, TX digital logic power is 100:1.0625V
+ */
+ reg = SEL_BG_NEW_BANDGAP | TX_TERMINAL_CTRL_61_OHM |
+ SWING_A_30PER_G_NORMAL;
+ writel(reg, &dp_regs->analog_ctl1);
+
+ reg = SEL_24M | TX_DVDD_BIT_1_0625V;
+ writel(reg, &dp_regs->analog_ctl2);
+
+ /*
+ * Set power source for internal clk driver to 1.0625v.
+ * Select current reference of TX driver current to 00:Ipp/2+Ic/2.
+ * Set VCO range of PLL +- 0uA
+ */
+ reg = DRIVE_DVDD_BIT_1_0625V | SEL_CURRENT_DEFAULT | VCO_BIT_000_MICRO;
+ writel(reg, &dp_regs->analog_ctl3);
+
+ /*
+ * Set AUX TX terminal resistor to 102 ohm
+ * Set AUX channel amplitude control
+ */
+ reg = PD_RING_OSC | AUX_TERMINAL_CTRL_52_OHM | TX_CUR1_2X | TX_CUR_4_MA;
+ writel(reg, &dp_regs->pll_filter_ctl1);
+
+ /*
+ * PLL loop filter bandwidth
+ * For 2.7Gbps: 175KHz, For 1.62Gbps: 234KHz
+ * PLL digital power select: 1.2500V
+ */
+ reg = CH3_AMP_0_MV | CH2_AMP_0_MV | CH1_AMP_0_MV | CH0_AMP_0_MV;
+
+ writel(reg, &dp_regs->amp_tuning_ctl);
+
+ /*
+ * PLL loop filter bandwidth
+ * For 2.7Gbps: 175KHz, For 1.62Gbps: 234KHz
+ * PLL digital power select: 1.1250V
+ */
+ reg = DP_PLL_LOOP_BIT_DEFAULT | DP_PLL_REF_BIT_1_1250V;
+ writel(reg, &dp_regs->pll_ctl);
+}
+
+static void exynos_dp_init_interrupt(void)
+{
+ /* Set interrupt registers to initial states */
+
+ /*
+ * Disable interrupt
+ * INT pin assertion polarity. It must be configured
+ * correctly according to ICU setting.
+ * 1 = assert high, 0 = assert low
+ */
+ writel(INT_POL, &dp_regs->int_ctl);
+
+ /* Clear pending registers */
+ writel(0xff, &dp_regs->common_int_sta1);
+ writel(0xff, &dp_regs->common_int_sta2);
+ writel(0xff, &dp_regs->common_int_sta3);
+ writel(0xff, &dp_regs->common_int_sta4);
+ writel(0xff, &dp_regs->int_sta);
+
+ /* 0:mask,1: unmask */
+ writel(0x00, &dp_regs->int_sta_mask1);
+ writel(0x00, &dp_regs->int_sta_mask2);
+ writel(0x00, &dp_regs->int_sta_mask3);
+ writel(0x00, &dp_regs->int_sta_mask4);
+ writel(0x00, &dp_regs->int_sta_mask);
+}
+
+void exynos_dp_reset(void)
+{
+ unsigned int reg_func_1;
+
+ /* dp tx sw reset */
+ writel(RESET_DP_TX, &dp_regs->tx_sw_reset);
+
+ exynos_dp_enable_video_input(DP_DISABLE);
+ exynos_dp_enable_video_bist(DP_DISABLE);
+ exynos_dp_enable_video_mute(DP_DISABLE);
+
+ /* software reset */
+ reg_func_1 = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N |
+ AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N |
+ HDCP_FUNC_EN_N | SW_FUNC_EN_N;
+
+ writel(reg_func_1, &dp_regs->func_en1);
+ writel(reg_func_1, &dp_regs->func_en2);
+
+ mdelay(1);
+
+ exynos_dp_init_analog_param();
+ exynos_dp_init_interrupt();
+
+ return;
+}
+
+void exynos_dp_enable_sw_func(unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->func_en1);
+ reg &= ~(SW_FUNC_EN_N);
+
+ if (!enable)
+ reg |= SW_FUNC_EN_N;
+
+ writel(reg, &dp_regs->func_en1);
+
+ return;
+}
+
+unsigned int exynos_dp_set_analog_power_down(unsigned int block, u32 enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->phy_pd);
+ switch (block) {
+ case AUX_BLOCK:
+ reg &= ~(AUX_PD);
+ if (enable)
+ reg |= AUX_PD;
+ break;
+ case CH0_BLOCK:
+ reg &= ~(CH0_PD);
+ if (enable)
+ reg |= CH0_PD;
+ break;
+ case CH1_BLOCK:
+ reg &= ~(CH1_PD);
+ if (enable)
+ reg |= CH1_PD;
+ break;
+ case CH2_BLOCK:
+ reg &= ~(CH2_PD);
+ if (enable)
+ reg |= CH2_PD;
+ break;
+ case CH3_BLOCK:
+ reg &= ~(CH3_PD);
+ if (enable)
+ reg |= CH3_PD;
+ break;
+ case ANALOG_TOTAL:
+ reg &= ~PHY_PD;
+ if (enable)
+ reg |= PHY_PD;
+ break;
+ case POWER_ALL:
+ reg &= ~(PHY_PD | AUX_PD | CH0_PD | CH1_PD | CH2_PD |
+ CH3_PD);
+ if (enable)
+ reg |= (PHY_PD | AUX_PD | CH0_PD | CH1_PD |
+ CH2_PD | CH3_PD);
+ break;
+ default:
+ printf("DP undefined block number : %d\n", block);
+ return -1;
+ }
+
+ writel(reg, &dp_regs->phy_pd);
+
+ return 0;
+}
+
+unsigned int exynos_dp_get_pll_lock_status(void)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->debug_ctl);
+
+ if (reg & PLL_LOCK)
+ return PLL_LOCKED;
+ else
+ return PLL_UNLOCKED;
+}
+
+static void exynos_dp_set_pll_power(unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->pll_ctl);
+ reg &= ~(DP_PLL_PD);
+
+ if (!enable)
+ reg |= DP_PLL_PD;
+
+ writel(reg, &dp_regs->pll_ctl);
+}
+
+int exynos_dp_init_analog_func(void)
+{
+ int ret = EXYNOS_DP_SUCCESS;
+ unsigned int retry_cnt = 10;
+ unsigned int reg;
+
+ /* Power On All Analog block */
+ exynos_dp_set_analog_power_down(POWER_ALL, DP_DISABLE);
+
+ reg = PLL_LOCK_CHG;
+ writel(reg, &dp_regs->common_int_sta1);
+
+ reg = readl(&dp_regs->debug_ctl);
+ reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL);
+ writel(reg, &dp_regs->debug_ctl);
+
+ /* Assert DP PLL Reset */
+ reg = readl(&dp_regs->pll_ctl);
+ reg |= DP_PLL_RESET;
+ writel(reg, &dp_regs->pll_ctl);
+
+ mdelay(1);
+
+ /* Deassert DP PLL Reset */
+ reg = readl(&dp_regs->pll_ctl);
+ reg &= ~(DP_PLL_RESET);
+ writel(reg, &dp_regs->pll_ctl);
+
+ exynos_dp_set_pll_power(DP_ENABLE);
+
+ while (exynos_dp_get_pll_lock_status() == PLL_UNLOCKED) {
+ mdelay(1);
+ retry_cnt--;
+ if (retry_cnt == 0) {
+ printf("DP dp's pll lock failed : retry : %d\n",
+ retry_cnt);
+ return -EINVAL;
+ }
+ }
+
+ debug("dp's pll lock success(%d)\n", retry_cnt);
+
+ /* Enable Serdes FIFO function and Link symbol clock domain module */
+ reg = readl(&dp_regs->func_en2);
+ reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N
+ | AUX_FUNC_EN_N);
+ writel(reg, &dp_regs->func_en2);
+
+ return ret;
+}
+
+void exynos_dp_init_hpd(void)
+{
+ unsigned int reg;
+
+ /* Clear interrupts related to Hot Plug Detect */
+ reg = HOTPLUG_CHG | HPD_LOST | PLUG;
+ writel(reg, &dp_regs->common_int_sta4);
+
+ reg = INT_HPD;
+ writel(reg, &dp_regs->int_sta);
+
+ reg = readl(&dp_regs->sys_ctl3);
+ reg &= ~(F_HPD | HPD_CTRL);
+ writel(reg, &dp_regs->sys_ctl3);
+
+ return;
+}
+
+static inline void exynos_dp_reset_aux(void)
+{
+ unsigned int reg;
+
+ /* Disable AUX channel module */
+ reg = readl(&dp_regs->func_en2);
+ reg |= AUX_FUNC_EN_N;
+ writel(reg, &dp_regs->func_en2);
+
+ return;
+}
+
+void exynos_dp_init_aux(void)
+{
+ unsigned int reg;
+
+ /* Clear interrupts related to AUX channel */
+ reg = RPLY_RECEIV | AUX_ERR;
+ writel(reg, &dp_regs->int_sta);
+
+ exynos_dp_reset_aux();
+
+ /* Disable AUX transaction H/W retry */
+ reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3) | AUX_HW_RETRY_COUNT_SEL(3)|
+ AUX_HW_RETRY_INTERVAL_600_MICROSECONDS;
+ writel(reg, &dp_regs->aux_hw_retry_ctl);
+
+ /* Receive AUX Channel DEFER commands equal to DEFER_COUNT*64 */
+ reg = DEFER_CTRL_EN | DEFER_COUNT(1);
+ writel(reg, &dp_regs->aux_ch_defer_ctl);
+
+ /* Enable AUX channel module */
+ reg = readl(&dp_regs->func_en2);
+ reg &= ~AUX_FUNC_EN_N;
+ writel(reg, &dp_regs->func_en2);
+
+ return;
+}
+
+void exynos_dp_config_interrupt(void)
+{
+ unsigned int reg;
+
+ /* 0: mask, 1: unmask */
+ reg = COMMON_INT_MASK_1;
+ writel(reg, &dp_regs->common_int_mask1);
+
+ reg = COMMON_INT_MASK_2;
+ writel(reg, &dp_regs->common_int_mask2);
+
+ reg = COMMON_INT_MASK_3;
+ writel(reg, &dp_regs->common_int_mask3);
+
+ reg = COMMON_INT_MASK_4;
+ writel(reg, &dp_regs->common_int_mask4);
+
+ reg = INT_STA_MASK;
+ writel(reg, &dp_regs->int_sta_mask);
+
+ return;
+}
+
+unsigned int exynos_dp_get_plug_in_status(void)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->sys_ctl3);
+ if (reg & HPD_STATUS)
+ return 0;
+
+ return -1;
+}
+
+unsigned int exynos_dp_detect_hpd(void)
+{
+ int timeout_loop = DP_TIMEOUT_LOOP_COUNT;
+
+ mdelay(2);
+
+ while (exynos_dp_get_plug_in_status() != 0) {
+ if (timeout_loop == 0)
+ return -EINVAL;
+ mdelay(10);
+ timeout_loop--;
+ }
+
+ return EXYNOS_DP_SUCCESS;
+}
+
+unsigned int exynos_dp_start_aux_transaction(void)
+{
+ unsigned int reg;
+ unsigned int ret = 0;
+ unsigned int retry_cnt;
+
+ /* Enable AUX CH operation */
+ reg = readl(&dp_regs->aux_ch_ctl2);
+ reg |= AUX_EN;
+ writel(reg, &dp_regs->aux_ch_ctl2);
+
+ retry_cnt = 10;
+ while (retry_cnt) {
+ reg = readl(&dp_regs->int_sta);
+ if (!(reg & RPLY_RECEIV)) {
+ if (retry_cnt == 0) {
+ printf("DP Reply Timeout!!\n");
+ ret = -EAGAIN;
+ return ret;
+ }
+ mdelay(1);
+ retry_cnt--;
+ } else
+ break;
+ }
+
+ /* Clear interrupt source for AUX CH command reply */
+ writel(reg, &dp_regs->int_sta);
+
+ /* Clear interrupt source for AUX CH access error */
+ reg = readl(&dp_regs->int_sta);
+ if (reg & AUX_ERR) {
+ printf("DP Aux Access Error\n");
+ writel(AUX_ERR, &dp_regs->int_sta);
+ ret = -EAGAIN;
+ return ret;
+ }
+
+ /* Check AUX CH error access status */
+ reg = readl(&dp_regs->aux_ch_sta);
+ if ((reg & AUX_STATUS_MASK) != 0) {
+ debug("DP AUX CH error happens: %x\n", reg & AUX_STATUS_MASK);
+ ret = -EAGAIN;
+ return ret;
+ }
+
+ return EXYNOS_DP_SUCCESS;
+}
+
+unsigned int exynos_dp_write_byte_to_dpcd(unsigned int reg_addr,
+ unsigned char data)
+{
+ unsigned int reg, ret;
+
+ /* Clear AUX CH data buffer */
+ reg = BUF_CLR;
+ writel(reg, &dp_regs->buffer_data_ctl);
+
+ /* Select DPCD device address */
+ reg = AUX_ADDR_7_0(reg_addr);
+ writel(reg, &dp_regs->aux_addr_7_0);
+ reg = AUX_ADDR_15_8(reg_addr);
+ writel(reg, &dp_regs->aux_addr_15_8);
+ reg = AUX_ADDR_19_16(reg_addr);
+ writel(reg, &dp_regs->aux_addr_19_16);
+
+ /* Write data buffer */
+ reg = (unsigned int)data;
+ writel(reg, &dp_regs->buf_data0);
+
+ /*
+ * Set DisplayPort transaction and write 1 byte
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ ret = exynos_dp_start_aux_transaction();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ printf("DP Aux transaction failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+unsigned int exynos_dp_read_byte_from_dpcd(unsigned int reg_addr,
+ unsigned char *data)
+{
+ unsigned int reg;
+ int retval;
+
+ /* Clear AUX CH data buffer */
+ reg = BUF_CLR;
+ writel(reg, &dp_regs->buffer_data_ctl);
+
+ /* Select DPCD device address */
+ reg = AUX_ADDR_7_0(reg_addr);
+ writel(reg, &dp_regs->aux_addr_7_0);
+ reg = AUX_ADDR_15_8(reg_addr);
+ writel(reg, &dp_regs->aux_addr_15_8);
+ reg = AUX_ADDR_19_16(reg_addr);
+ writel(reg, &dp_regs->aux_addr_19_16);
+
+ /*
+ * Set DisplayPort transaction and read 1 byte
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ retval = exynos_dp_start_aux_transaction();
+ if (!retval)
+ debug("DP Aux Transaction fail!\n");
+
+ /* Read data buffer */
+ reg = readl(&dp_regs->buf_data0);
+ *data = (unsigned char)(reg & 0xff);
+
+ return retval;
+}
+
+unsigned int exynos_dp_write_bytes_to_dpcd(unsigned int reg_addr,
+ unsigned int count,
+ unsigned char data[])
+{
+ unsigned int reg;
+ unsigned int start_offset;
+ unsigned int cur_data_count;
+ unsigned int cur_data_idx;
+ unsigned int retry_cnt;
+ unsigned int ret = 0;
+
+ /* Clear AUX CH data buffer */
+ reg = BUF_CLR;
+ writel(reg, &dp_regs->buffer_data_ctl);
+
+ start_offset = 0;
+ while (start_offset < count) {
+ /* Buffer size of AUX CH is 16 * 4bytes */
+ if ((count - start_offset) > 16)
+ cur_data_count = 16;
+ else
+ cur_data_count = count - start_offset;
+
+ retry_cnt = 5;
+ while (retry_cnt) {
+ /* Select DPCD device address */
+ reg = AUX_ADDR_7_0(reg_addr + start_offset);
+ writel(reg, &dp_regs->aux_addr_7_0);
+ reg = AUX_ADDR_15_8(reg_addr + start_offset);
+ writel(reg, &dp_regs->aux_addr_15_8);
+ reg = AUX_ADDR_19_16(reg_addr + start_offset);
+ writel(reg, &dp_regs->aux_addr_19_16);
+
+ for (cur_data_idx = 0; cur_data_idx < cur_data_count;
+ cur_data_idx++) {
+ reg = data[start_offset + cur_data_idx];
+ writel(reg, (unsigned int)&dp_regs->buf_data0 +
+ (4 * cur_data_idx));
+ }
+ /*
+ * Set DisplayPort transaction and write
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_LENGTH(cur_data_count) |
+ AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ ret = exynos_dp_start_aux_transaction();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ if (retry_cnt == 0) {
+ printf("DP Aux Transaction failed\n");
+ return ret;
+ }
+ retry_cnt--;
+ } else
+ break;
+ }
+ start_offset += cur_data_count;
+ }
+
+ return ret;
+}
+
+unsigned int exynos_dp_read_bytes_from_dpcd(unsigned int reg_addr,
+ unsigned int count,
+ unsigned char data[])
+{
+ unsigned int reg;
+ unsigned int start_offset;
+ unsigned int cur_data_count;
+ unsigned int cur_data_idx;
+ unsigned int retry_cnt;
+ unsigned int ret = 0;
+
+ /* Clear AUX CH data buffer */
+ reg = BUF_CLR;
+ writel(reg, &dp_regs->buffer_data_ctl);
+
+ start_offset = 0;
+ while (start_offset < count) {
+ /* Buffer size of AUX CH is 16 * 4bytes */
+ if ((count - start_offset) > 16)
+ cur_data_count = 16;
+ else
+ cur_data_count = count - start_offset;
+
+ retry_cnt = 5;
+ while (retry_cnt) {
+ /* Select DPCD device address */
+ reg = AUX_ADDR_7_0(reg_addr + start_offset);
+ writel(reg, &dp_regs->aux_addr_7_0);
+ reg = AUX_ADDR_15_8(reg_addr + start_offset);
+ writel(reg, &dp_regs->aux_addr_15_8);
+ reg = AUX_ADDR_19_16(reg_addr + start_offset);
+ writel(reg, &dp_regs->aux_addr_19_16);
+ /*
+ * Set DisplayPort transaction and read
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_LENGTH(cur_data_count) |
+ AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ ret = exynos_dp_start_aux_transaction();
+ if (ret != EXYNOS_DP_SUCCESS) {
+ if (retry_cnt == 0) {
+ printf("DP Aux Transaction failed\n");
+ return ret;
+ }
+ retry_cnt--;
+ } else
+ break;
+ }
+
+ for (cur_data_idx = 0; cur_data_idx < cur_data_count;
+ cur_data_idx++) {
+ reg = readl((unsigned int)&dp_regs->buf_data0 +
+ 4 * cur_data_idx);
+ data[start_offset + cur_data_idx] = (unsigned char)reg;
+ }
+
+ start_offset += cur_data_count;
+ }
+
+ return ret;
+}
+
+int exynos_dp_select_i2c_device(unsigned int device_addr,
+ unsigned int reg_addr)
+{
+ unsigned int reg;
+ int retval;
+
+ /* Set EDID device address */
+ reg = device_addr;
+ writel(reg, &dp_regs->aux_addr_7_0);
+ writel(0x0, &dp_regs->aux_addr_15_8);
+ writel(0x0, &dp_regs->aux_addr_19_16);
+
+ /* Set offset from base address of EDID device */
+ writel(reg_addr, &dp_regs->buf_data0);
+
+ /*
+ * Set I2C transaction and write address
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT |
+ AUX_TX_COMM_WRITE;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ retval = exynos_dp_start_aux_transaction();
+ if (retval != 0)
+ printf("%s: DP Aux Transaction fail!\n", __func__);
+
+ return retval;
+}
+
+int exynos_dp_read_byte_from_i2c(unsigned int device_addr,
+ unsigned int reg_addr,
+ unsigned int *data)
+{
+ unsigned int reg;
+ int i;
+ int retval;
+
+ for (i = 0; i < 10; i++) {
+ /* Clear AUX CH data buffer */
+ reg = BUF_CLR;
+ writel(reg, &dp_regs->buffer_data_ctl);
+
+ /* Select EDID device */
+ retval = exynos_dp_select_i2c_device(device_addr, reg_addr);
+ if (retval != 0) {
+ printf("DP Select EDID device fail. retry !\n");
+ continue;
+ }
+
+ /*
+ * Set I2C transaction and read data
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_TX_COMM_I2C_TRANSACTION |
+ AUX_TX_COMM_READ;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ retval = exynos_dp_start_aux_transaction();
+ if (retval != EXYNOS_DP_SUCCESS)
+ printf("%s: DP Aux Transaction fail!\n", __func__);
+ }
+
+ /* Read data */
+ if (retval == 0)
+ *data = readl(&dp_regs->buf_data0);
+
+ return retval;
+}
+
+int exynos_dp_read_bytes_from_i2c(unsigned int device_addr,
+ unsigned int reg_addr, unsigned int count, unsigned char edid[])
+{
+ unsigned int reg;
+ unsigned int i, j;
+ unsigned int cur_data_idx;
+ unsigned int defer = 0;
+ int retval = 0;
+
+ for (i = 0; i < count; i += 16) { /* use 16 burst */
+ for (j = 0; j < 100; j++) {
+ /* Clear AUX CH data buffer */
+ reg = BUF_CLR;
+ writel(reg, &dp_regs->buffer_data_ctl);
+
+ /* Set normal AUX CH command */
+ reg = readl(&dp_regs->aux_ch_ctl2);
+ reg &= ~ADDR_ONLY;
+ writel(reg, &dp_regs->aux_ch_ctl2);
+
+ /*
+ * If Rx sends defer, Tx sends only reads
+ * request without sending addres
+ */
+ if (!defer)
+ retval =
+ exynos_dp_select_i2c_device(device_addr,
+ reg_addr + i);
+ else
+ defer = 0;
+
+ if (retval == EXYNOS_DP_SUCCESS) {
+ /*
+ * Set I2C transaction and write data
+ * If bit 3 is 1, DisplayPort transaction.
+ * If Bit 3 is 0, I2C transaction.
+ */
+ reg = AUX_LENGTH(16) |
+ AUX_TX_COMM_I2C_TRANSACTION |
+ AUX_TX_COMM_READ;
+ writel(reg, &dp_regs->aux_ch_ctl1);
+
+ /* Start AUX transaction */
+ retval = exynos_dp_start_aux_transaction();
+ if (retval == 0)
+ break;
+ else
+ printf("DP Aux Transaction fail!\n");
+ }
+ /* Check if Rx sends defer */
+ reg = readl(&dp_regs->aux_rx_comm);
+ if (reg == AUX_RX_COMM_AUX_DEFER ||
+ reg == AUX_RX_COMM_I2C_DEFER) {
+ printf("DP Defer: %d\n", reg);
+ defer = 1;
+ }
+ }
+
+ for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) {
+ reg = readl((unsigned int)&dp_regs->buf_data0
+ + 4 * cur_data_idx);
+ edid[i + cur_data_idx] = (unsigned char)reg;
+ }
+ }
+
+ return retval;
+}
+
+void exynos_dp_reset_macro(void)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->phy_test);
+ reg |= MACRO_RST;
+ writel(reg, &dp_regs->phy_test);
+
+ /* 10 us is the minimum Macro reset time. */
+ mdelay(1);
+
+ reg &= ~MACRO_RST;
+ writel(reg, &dp_regs->phy_test);
+}
+
+void exynos_dp_set_link_bandwidth(unsigned char bwtype)
+{
+ unsigned int reg;
+
+ reg = (unsigned int)bwtype;
+
+ /* Set bandwidth to 2.7G or 1.62G */
+ if ((bwtype == DP_LANE_BW_1_62) || (bwtype == DP_LANE_BW_2_70))
+ writel(reg, &dp_regs->link_bw_set);
+}
+
+unsigned char exynos_dp_get_link_bandwidth(void)
+{
+ unsigned char ret;
+ unsigned int reg;
+
+ reg = readl(&dp_regs->link_bw_set);
+ ret = (unsigned char)reg;
+
+ return ret;
+}
+
+void exynos_dp_set_lane_count(unsigned char count)
+{
+ unsigned int reg;
+
+ reg = (unsigned int)count;
+
+ if ((count == DP_LANE_CNT_1) || (count == DP_LANE_CNT_2) ||
+ (count == DP_LANE_CNT_4))
+ writel(reg, &dp_regs->lane_count_set);
+}
+
+unsigned int exynos_dp_get_lane_count(void)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->lane_count_set);
+
+ return reg;
+}
+
+unsigned char exynos_dp_get_lanex_pre_emphasis(unsigned char lanecnt)
+{
+ unsigned int reg_list[DP_LANE_CNT_4] = {
+ (unsigned int)&dp_regs->ln0_link_training_ctl,
+ (unsigned int)&dp_regs->ln1_link_training_ctl,
+ (unsigned int)&dp_regs->ln2_link_training_ctl,
+ (unsigned int)&dp_regs->ln3_link_training_ctl,
+ };
+
+ return readl(reg_list[lanecnt]);
+}
+
+void exynos_dp_set_lanex_pre_emphasis(unsigned char request_val,
+ unsigned char lanecnt)
+{
+ unsigned int reg_list[DP_LANE_CNT_4] = {
+ (unsigned int)&dp_regs->ln0_link_training_ctl,
+ (unsigned int)&dp_regs->ln1_link_training_ctl,
+ (unsigned int)&dp_regs->ln2_link_training_ctl,
+ (unsigned int)&dp_regs->ln3_link_training_ctl,
+ };
+
+ writel(request_val, reg_list[lanecnt]);
+}
+
+void exynos_dp_set_lane_pre_emphasis(unsigned int level, unsigned char lanecnt)
+{
+ unsigned char i;
+ unsigned int reg;
+ unsigned int reg_list[DP_LANE_CNT_4] = {
+ (unsigned int)&dp_regs->ln0_link_training_ctl,
+ (unsigned int)&dp_regs->ln1_link_training_ctl,
+ (unsigned int)&dp_regs->ln2_link_training_ctl,
+ (unsigned int)&dp_regs->ln3_link_training_ctl,
+ };
+ unsigned int reg_shift[DP_LANE_CNT_4] = {
+ PRE_EMPHASIS_SET_0_SHIFT,
+ PRE_EMPHASIS_SET_1_SHIFT,
+ PRE_EMPHASIS_SET_2_SHIFT,
+ PRE_EMPHASIS_SET_3_SHIFT
+ };
+
+ for (i = 0; i < lanecnt; i++) {
+ reg = level << reg_shift[i];
+ writel(reg, reg_list[i]);
+ }
+}
+
+void exynos_dp_set_training_pattern(unsigned int pattern)
+{
+ unsigned int reg = 0;
+
+ switch (pattern) {
+ case PRBS7:
+ reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7;
+ break;
+ case D10_2:
+ reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2;
+ break;
+ case TRAINING_PTN1:
+ reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1;
+ break;
+ case TRAINING_PTN2:
+ reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2;
+ break;
+ case DP_NONE:
+ reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_DISABLE |
+ SW_TRAINING_PATTERN_SET_NORMAL;
+ break;
+ default:
+ break;
+ }
+
+ writel(reg, &dp_regs->training_ptn_set);
+}
+
+void exynos_dp_enable_enhanced_mode(unsigned char enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->sys_ctl4);
+ reg &= ~ENHANCED;
+
+ if (enable)
+ reg |= ENHANCED;
+
+ writel(reg, &dp_regs->sys_ctl4);
+}
+
+void exynos_dp_enable_scrambling(unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->training_ptn_set);
+ reg &= ~(SCRAMBLING_DISABLE);
+
+ if (!enable)
+ reg |= SCRAMBLING_DISABLE;
+
+ writel(reg, &dp_regs->training_ptn_set);
+}
+
+int exynos_dp_init_video(void)
+{
+ unsigned int reg;
+
+ /* Clear VID_CLK_CHG[1] and VID_FORMAT_CHG[3] and VSYNC_DET[7] */
+ reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG;
+ writel(reg, &dp_regs->common_int_sta1);
+
+ /* I_STRM__CLK detect : DE_CTL : Auto detect */
+ reg &= ~DET_CTRL;
+ writel(reg, &dp_regs->sys_ctl1);
+
+ return 0;
+}
+
+void exynos_dp_config_video_slave_mode(struct edp_video_info *video_info)
+{
+ unsigned int reg;
+
+ /* Video Slave mode setting */
+ reg = readl(&dp_regs->func_en1);
+ reg &= ~(MASTER_VID_FUNC_EN_N|SLAVE_VID_FUNC_EN_N);
+ reg |= MASTER_VID_FUNC_EN_N;
+ writel(reg, &dp_regs->func_en1);
+
+ /* Configure Interlaced for slave mode video */
+ reg = readl(&dp_regs->video_ctl10);
+ reg &= ~INTERACE_SCAN_CFG;
+ reg |= (video_info->interlaced << INTERACE_SCAN_CFG_SHIFT);
+ writel(reg, &dp_regs->video_ctl10);
+
+ /* Configure V sync polarity for slave mode video */
+ reg = readl(&dp_regs->video_ctl10);
+ reg &= ~VSYNC_POLARITY_CFG;
+ reg |= (video_info->v_sync_polarity << V_S_POLARITY_CFG_SHIFT);
+ writel(reg, &dp_regs->video_ctl10);
+
+ /* Configure H sync polarity for slave mode video */
+ reg = readl(&dp_regs->video_ctl10);
+ reg &= ~HSYNC_POLARITY_CFG;
+ reg |= (video_info->h_sync_polarity << H_S_POLARITY_CFG_SHIFT);
+ writel(reg, &dp_regs->video_ctl10);
+
+ /* Set video mode to slave mode */
+ reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE;
+ writel(reg, &dp_regs->soc_general_ctl);
+}
+
+void exynos_dp_set_video_color_format(struct edp_video_info *video_info)
+{
+ unsigned int reg;
+
+ /* Configure the input color depth, color space, dynamic range */
+ reg = (video_info->dynamic_range << IN_D_RANGE_SHIFT) |
+ (video_info->color_depth << IN_BPC_SHIFT) |
+ (video_info->color_space << IN_COLOR_F_SHIFT);
+ writel(reg, &dp_regs->video_ctl2);
+
+ /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */
+ reg = readl(&dp_regs->video_ctl3);
+ reg &= ~IN_YC_COEFFI_MASK;
+ if (video_info->ycbcr_coeff)
+ reg |= IN_YC_COEFFI_ITU709;
+ else
+ reg |= IN_YC_COEFFI_ITU601;
+ writel(reg, &dp_regs->video_ctl3);
+}
+
+int exynos_dp_config_video_bist(struct edp_device_info *edp_info)
+{
+ unsigned int reg;
+ unsigned int bist_type = 0;
+ struct edp_video_info video_info = edp_info->video_info;
+
+ /* For master mode, you don't need to set the video format */
+ if (video_info.master_mode == 0) {
+ writel(TOTAL_LINE_CFG_L(edp_info->disp_info.v_total),
+ &dp_regs->total_ln_cfg_l);
+ writel(TOTAL_LINE_CFG_H(edp_info->disp_info.v_total),
+ &dp_regs->total_ln_cfg_h);
+ writel(ACTIVE_LINE_CFG_L(edp_info->disp_info.v_res),
+ &dp_regs->active_ln_cfg_l);
+ writel(ACTIVE_LINE_CFG_H(edp_info->disp_info.v_res),
+ &dp_regs->active_ln_cfg_h);
+ writel(edp_info->disp_info.v_sync_width,
+ &dp_regs->vsw_cfg);
+ writel(edp_info->disp_info.v_back_porch,
+ &dp_regs->vbp_cfg);
+ writel(edp_info->disp_info.v_front_porch,
+ &dp_regs->vfp_cfg);
+
+ writel(TOTAL_PIXEL_CFG_L(edp_info->disp_info.h_total),
+ &dp_regs->total_pix_cfg_l);
+ writel(TOTAL_PIXEL_CFG_H(edp_info->disp_info.h_total),
+ &dp_regs->total_pix_cfg_h);
+ writel(ACTIVE_PIXEL_CFG_L(edp_info->disp_info.h_res),
+ &dp_regs->active_pix_cfg_l);
+ writel(ACTIVE_PIXEL_CFG_H(edp_info->disp_info.h_res),
+ &dp_regs->active_pix_cfg_h);
+ writel(H_F_PORCH_CFG_L(edp_info->disp_info.h_front_porch),
+ &dp_regs->hfp_cfg_l);
+ writel(H_F_PORCH_CFG_H(edp_info->disp_info.h_front_porch),
+ &dp_regs->hfp_cfg_h);
+ writel(H_SYNC_PORCH_CFG_L(edp_info->disp_info.h_sync_width),
+ &dp_regs->hsw_cfg_l);
+ writel(H_SYNC_PORCH_CFG_H(edp_info->disp_info.h_sync_width),
+ &dp_regs->hsw_cfg_h);
+ writel(H_B_PORCH_CFG_L(edp_info->disp_info.h_back_porch),
+ &dp_regs->hbp_cfg_l);
+ writel(H_B_PORCH_CFG_H(edp_info->disp_info.h_back_porch),
+ &dp_regs->hbp_cfg_h);
+
+ /*
+ * Set SLAVE_I_SCAN_CFG[2], VSYNC_P_CFG[1],
+ * HSYNC_P_CFG[0] properly
+ */
+ reg = (video_info.interlaced << INTERACE_SCAN_CFG_SHIFT |
+ video_info.v_sync_polarity << V_S_POLARITY_CFG_SHIFT |
+ video_info.h_sync_polarity << H_S_POLARITY_CFG_SHIFT);
+ writel(reg, &dp_regs->video_ctl10);
+ }
+
+ /* BIST color bar width set--set to each bar is 32 pixel width */
+ switch (video_info.bist_pattern) {
+ case COLORBAR_32:
+ bist_type = BIST_WIDTH_BAR_32_PIXEL |
+ BIST_TYPE_COLOR_BAR;
+ break;
+ case COLORBAR_64:
+ bist_type = BIST_WIDTH_BAR_64_PIXEL |
+ BIST_TYPE_COLOR_BAR;
+ break;
+ case WHITE_GRAY_BALCKBAR_32:
+ bist_type = BIST_WIDTH_BAR_32_PIXEL |
+ BIST_TYPE_WHITE_GRAY_BLACK_BAR;
+ break;
+ case WHITE_GRAY_BALCKBAR_64:
+ bist_type = BIST_WIDTH_BAR_64_PIXEL |
+ BIST_TYPE_WHITE_GRAY_BLACK_BAR;
+ break;
+ case MOBILE_WHITEBAR_32:
+ bist_type = BIST_WIDTH_BAR_32_PIXEL |
+ BIST_TYPE_MOBILE_WHITE_BAR;
+ break;
+ case MOBILE_WHITEBAR_64:
+ bist_type = BIST_WIDTH_BAR_64_PIXEL |
+ BIST_TYPE_MOBILE_WHITE_BAR;
+ break;
+ default:
+ return -1;
+ }
+
+ reg = bist_type;
+ writel(reg, &dp_regs->video_ctl4);
+
+ return 0;
+}
+
+unsigned int exynos_dp_is_slave_video_stream_clock_on(void)
+{
+ unsigned int reg;
+
+ /* Update Video stream clk detect status */
+ reg = readl(&dp_regs->sys_ctl1);
+ writel(reg, &dp_regs->sys_ctl1);
+
+ reg = readl(&dp_regs->sys_ctl1);
+
+ if (!(reg & DET_STA)) {
+ debug("DP Input stream clock not detected.\n");
+ return -EIO;
+ }
+
+ return EXYNOS_DP_SUCCESS;
+}
+
+void exynos_dp_set_video_cr_mn(unsigned int type, unsigned int m_value,
+ unsigned int n_value)
+{
+ unsigned int reg;
+
+ if (type == REGISTER_M) {
+ reg = readl(&dp_regs->sys_ctl4);
+ reg |= FIX_M_VID;
+ writel(reg, &dp_regs->sys_ctl4);
+ reg = M_VID0_CFG(m_value);
+ writel(reg, &dp_regs->m_vid0);
+ reg = M_VID1_CFG(m_value);
+ writel(reg, &dp_regs->m_vid1);
+ reg = M_VID2_CFG(m_value);
+ writel(reg, &dp_regs->m_vid2);
+
+ reg = N_VID0_CFG(n_value);
+ writel(reg, &dp_regs->n_vid0);
+ reg = N_VID1_CFG(n_value);
+ writel(reg, &dp_regs->n_vid1);
+ reg = N_VID2_CFG(n_value);
+ writel(reg, &dp_regs->n_vid2);
+ } else {
+ reg = readl(&dp_regs->sys_ctl4);
+ reg &= ~FIX_M_VID;
+ writel(reg, &dp_regs->sys_ctl4);
+ }
+}
+
+void exynos_dp_set_video_timing_mode(unsigned int type)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->video_ctl10);
+ reg &= ~FORMAT_SEL;
+
+ if (type != VIDEO_TIMING_FROM_CAPTURE)
+ reg |= FORMAT_SEL;
+
+ writel(reg, &dp_regs->video_ctl10);
+}
+
+void exynos_dp_enable_video_master(unsigned int enable)
+{
+ unsigned int reg;
+
+ reg = readl(&dp_regs->soc_general_ctl);
+ if (enable) {
+ reg &= ~VIDEO_MODE_MASK;
+ reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE;
+ } else {
+ reg &= ~VIDEO_MODE_MASK;
+ reg |= VIDEO_MODE_SLAVE_MODE;
+ }
+
+ writel(reg, &dp_regs->soc_general_ctl);
+}
+
+void exynos_dp_start_video(void)
+{
+ unsigned int reg;
+
+ /* Enable Video input and disable Mute */
+ reg = readl(&dp_regs->video_ctl1);
+ reg |= VIDEO_EN;
+ writel(reg, &dp_regs->video_ctl1);
+}
+
+unsigned int exynos_dp_is_video_stream_on(void)
+{
+ unsigned int reg;
+
+ /* Update STRM_VALID */
+ reg = readl(&dp_regs->sys_ctl3);
+ writel(reg, &dp_regs->sys_ctl3);
+
+ reg = readl(&dp_regs->sys_ctl3);
+ if (!(reg & STRM_VALID))
+ return -EIO;
+
+ return EXYNOS_DP_SUCCESS;
+}
diff --git a/drivers/video/exynos/exynos_dp_lowlevel.h b/drivers/video/exynos/exynos_dp_lowlevel.h
new file mode 100644
index 0000000000..8651681520
--- /dev/null
+++ b/drivers/video/exynos/exynos_dp_lowlevel.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef _EXYNOS_EDP_LOWLEVEL_H
+#define _EXYNOS_EDP_LOWLEVEL_H
+
+void exynos_dp_enable_video_bist(unsigned int enable);
+void exynos_dp_enable_video_mute(unsigned int enable);
+void exynos_dp_reset(void);
+void exynos_dp_enable_sw_func(unsigned int enable);
+unsigned int exynos_dp_set_analog_power_down(unsigned int block, u32 enable);
+unsigned int exynos_dp_get_pll_lock_status(void);
+int exynos_dp_init_analog_func(void);
+void exynos_dp_init_hpd(void);
+void exynos_dp_init_aux(void);
+void exynos_dp_config_interrupt(void);
+unsigned int exynos_dp_get_plug_in_status(void);
+unsigned int exynos_dp_detect_hpd(void);
+unsigned int exynos_dp_start_aux_transaction(void);
+unsigned int exynos_dp_write_byte_to_dpcd(unsigned int reg_addr,
+ unsigned char data);
+unsigned int exynos_dp_read_byte_from_dpcd(unsigned int reg_addr,
+ unsigned char *data);
+unsigned int exynos_dp_write_bytes_to_dpcd(unsigned int reg_addr,
+ unsigned int count,
+ unsigned char data[]);
+unsigned int exynos_dp_read_bytes_from_dpcd( unsigned int reg_addr,
+ unsigned int count,
+ unsigned char data[]);
+int exynos_dp_select_i2c_device( unsigned int device_addr,
+ unsigned int reg_addr);
+int exynos_dp_read_byte_from_i2c(unsigned int device_addr,
+ unsigned int reg_addr, unsigned int *data);
+int exynos_dp_read_bytes_from_i2c(unsigned int device_addr,
+ unsigned int reg_addr, unsigned int count,
+ unsigned char edid[]);
+void exynos_dp_reset_macro(void);
+void exynos_dp_set_link_bandwidth(unsigned char bwtype);
+unsigned char exynos_dp_get_link_bandwidth(void);
+void exynos_dp_set_lane_count(unsigned char count);
+unsigned int exynos_dp_get_lane_count(void);
+unsigned char exynos_dp_get_lanex_pre_emphasis(unsigned char lanecnt);
+void exynos_dp_set_lane_pre_emphasis(unsigned int level,
+ unsigned char lanecnt);
+void exynos_dp_set_lanex_pre_emphasis(unsigned char request_val,
+ unsigned char lanecnt);
+void exynos_dp_set_training_pattern(unsigned int pattern);
+void exynos_dp_enable_enhanced_mode(unsigned char enable);
+void exynos_dp_enable_scrambling(unsigned int enable);
+int exynos_dp_init_video(void);
+void exynos_dp_config_video_slave_mode(struct edp_video_info *video_info);
+void exynos_dp_set_video_color_format(struct edp_video_info *video_info);
+int exynos_dp_config_video_bist(struct edp_device_info *edp_info);
+unsigned int exynos_dp_is_slave_video_stream_clock_on(void);
+void exynos_dp_set_video_cr_mn(unsigned int type, unsigned int m_value,
+ unsigned int n_value);
+void exynos_dp_set_video_timing_mode(unsigned int type);
+void exynos_dp_enable_video_master(unsigned int enable);
+void exynos_dp_start_video(void);
+unsigned int exynos_dp_is_video_stream_on(void);
+void exynos_dp_set_base_addr(void);
+
+#endif /* _EXYNOS_DP_LOWLEVEL_H */
diff --git a/drivers/video/exynos/exynos_fb.c b/drivers/video/exynos/exynos_fb.c
new file mode 100644
index 0000000000..69edc3a3a4
--- /dev/null
+++ b/drivers/video/exynos/exynos_fb.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+#include <common.h>
+#include <lcd.h>
+#include <fdtdec.h>
+#include <libfdt.h>
+#include <asm/io.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/clk.h>
+#include <asm/arch/mipi_dsim.h>
+#include <asm/arch/dp_info.h>
+#include <asm/arch/system.h>
+#include <asm/gpio.h>
+#include <asm-generic/errno.h>
+
+#include "exynos_fb.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static unsigned int panel_width, panel_height;
+
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+vidinfo_t panel_info = {
+ /*
+ * Insert a value here so that we don't end up in the BSS
+ * Reference: drivers/video/tegra.c
+ */
+ .vl_col = -1,
+};
+#endif
+
+ushort *configuration_get_cmap(void)
+{
+#if defined(CONFIG_LCD_LOGO)
+ return bmp_logo_palette;
+#else
+ return NULL;
+#endif
+}
+
+static void exynos_lcd_init_mem(void *lcdbase, vidinfo_t *vid)
+{
+ unsigned long palette_size;
+ unsigned int fb_size;
+
+ fb_size = vid->vl_row * vid->vl_col * (NBITS(vid->vl_bpix) >> 3);
+
+ palette_size = NBITS(vid->vl_bpix) == 8 ? 256 : 16;
+
+ exynos_fimd_lcd_init_mem((unsigned long)lcdbase,
+ (unsigned long)fb_size, palette_size);
+}
+
+static void exynos_lcd_init(vidinfo_t *vid)
+{
+ exynos_fimd_lcd_init(vid);
+
+ /* Enable flushing after LCD writes if requested */
+ lcd_set_flush_dcache(1);
+}
+
+__weak void exynos_cfg_lcd_gpio(void)
+{
+}
+
+__weak void exynos_backlight_on(unsigned int onoff)
+{
+}
+
+__weak void exynos_reset_lcd(void)
+{
+}
+
+__weak void exynos_lcd_power_on(void)
+{
+}
+
+__weak void exynos_cfg_ldo(void)
+{
+}
+
+__weak void exynos_enable_ldo(unsigned int onoff)
+{
+}
+
+__weak void exynos_backlight_reset(void)
+{
+}
+
+__weak int exynos_lcd_misc_init(vidinfo_t *vid)
+{
+ return 0;
+}
+
+static void lcd_panel_on(vidinfo_t *vid)
+{
+ struct gpio_desc pwm_out_gpio;
+ struct gpio_desc bl_en_gpio;
+ unsigned int node;
+
+ udelay(vid->init_delay);
+
+ exynos_backlight_reset();
+
+ exynos_cfg_lcd_gpio();
+
+ exynos_lcd_power_on();
+
+ udelay(vid->power_on_delay);
+
+ if (vid->dp_enabled)
+ exynos_init_dp();
+
+ exynos_reset_lcd();
+
+ udelay(vid->reset_delay);
+
+ exynos_backlight_on(1);
+
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+ node = fdtdec_next_compatible(gd->fdt_blob, 0,
+ COMPAT_SAMSUNG_EXYNOS_FIMD);
+ if (node <= 0) {
+ debug("FIMD: Can't get device node for FIMD\n");
+ return;
+ }
+ gpio_request_by_name_nodev(gd->fdt_blob, node, "samsung,pwm-out-gpio",
+ 0, &pwm_out_gpio,
+ GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
+
+ gpio_request_by_name_nodev(gd->fdt_blob, node, "samsung,bl-en-gpio", 0,
+ &bl_en_gpio,
+ GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
+
+#endif
+ exynos_cfg_ldo();
+
+ exynos_enable_ldo(1);
+
+ if (vid->mipi_enabled)
+ exynos_mipi_dsi_init();
+}
+
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+int exynos_lcd_early_init(const void *blob)
+{
+ unsigned int node;
+ node = fdtdec_next_compatible(blob, 0, COMPAT_SAMSUNG_EXYNOS_FIMD);
+ if (node <= 0) {
+ debug("exynos_fb: Can't get device node for fimd\n");
+ return -ENODEV;
+ }
+
+ panel_info.vl_col = fdtdec_get_int(blob, node, "samsung,vl-col", 0);
+ if (panel_info.vl_col == 0) {
+ debug("Can't get XRES\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_row = fdtdec_get_int(blob, node, "samsung,vl-row", 0);
+ if (panel_info.vl_row == 0) {
+ debug("Can't get YRES\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_width = fdtdec_get_int(blob, node,
+ "samsung,vl-width", 0);
+
+ panel_info.vl_height = fdtdec_get_int(blob, node,
+ "samsung,vl-height", 0);
+
+ panel_info.vl_freq = fdtdec_get_int(blob, node, "samsung,vl-freq", 0);
+ if (panel_info.vl_freq == 0) {
+ debug("Can't get refresh rate\n");
+ return -ENXIO;
+ }
+
+ if (fdtdec_get_bool(blob, node, "samsung,vl-clkp"))
+ panel_info.vl_clkp = CONFIG_SYS_LOW;
+
+ if (fdtdec_get_bool(blob, node, "samsung,vl-oep"))
+ panel_info.vl_oep = CONFIG_SYS_LOW;
+
+ if (fdtdec_get_bool(blob, node, "samsung,vl-hsp"))
+ panel_info.vl_hsp = CONFIG_SYS_LOW;
+
+ if (fdtdec_get_bool(blob, node, "samsung,vl-vsp"))
+ panel_info.vl_vsp = CONFIG_SYS_LOW;
+
+ if (fdtdec_get_bool(blob, node, "samsung,vl-dp"))
+ panel_info.vl_dp = CONFIG_SYS_LOW;
+
+ panel_info.vl_bpix = fdtdec_get_int(blob, node, "samsung,vl-bpix", 0);
+ if (panel_info.vl_bpix == 0) {
+ debug("Can't get bits per pixel\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_hspw = fdtdec_get_int(blob, node, "samsung,vl-hspw", 0);
+ if (panel_info.vl_hspw == 0) {
+ debug("Can't get hsync width\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_hfpd = fdtdec_get_int(blob, node, "samsung,vl-hfpd", 0);
+ if (panel_info.vl_hfpd == 0) {
+ debug("Can't get right margin\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_hbpd = (u_char)fdtdec_get_int(blob, node,
+ "samsung,vl-hbpd", 0);
+ if (panel_info.vl_hbpd == 0) {
+ debug("Can't get left margin\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_vspw = (u_char)fdtdec_get_int(blob, node,
+ "samsung,vl-vspw", 0);
+ if (panel_info.vl_vspw == 0) {
+ debug("Can't get vsync width\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_vfpd = fdtdec_get_int(blob, node,
+ "samsung,vl-vfpd", 0);
+ if (panel_info.vl_vfpd == 0) {
+ debug("Can't get lower margin\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_vbpd = fdtdec_get_int(blob, node, "samsung,vl-vbpd", 0);
+ if (panel_info.vl_vbpd == 0) {
+ debug("Can't get upper margin\n");
+ return -ENXIO;
+ }
+
+ panel_info.vl_cmd_allow_len = fdtdec_get_int(blob, node,
+ "samsung,vl-cmd-allow-len", 0);
+
+ panel_info.win_id = fdtdec_get_int(blob, node, "samsung,winid", 0);
+ panel_info.init_delay = fdtdec_get_int(blob, node,
+ "samsung,init-delay", 0);
+ panel_info.power_on_delay = fdtdec_get_int(blob, node,
+ "samsung,power-on-delay", 0);
+ panel_info.reset_delay = fdtdec_get_int(blob, node,
+ "samsung,reset-delay", 0);
+ panel_info.interface_mode = fdtdec_get_int(blob, node,
+ "samsung,interface-mode", 0);
+ panel_info.mipi_enabled = fdtdec_get_int(blob, node,
+ "samsung,mipi-enabled", 0);
+ panel_info.dp_enabled = fdtdec_get_int(blob, node,
+ "samsung,dp-enabled", 0);
+ panel_info.cs_setup = fdtdec_get_int(blob, node,
+ "samsung,cs-setup", 0);
+ panel_info.wr_setup = fdtdec_get_int(blob, node,
+ "samsung,wr-setup", 0);
+ panel_info.wr_act = fdtdec_get_int(blob, node, "samsung,wr-act", 0);
+ panel_info.wr_hold = fdtdec_get_int(blob, node, "samsung,wr-hold", 0);
+
+ panel_info.logo_on = fdtdec_get_int(blob, node, "samsung,logo-on", 0);
+ if (panel_info.logo_on) {
+ panel_info.logo_width = fdtdec_get_int(blob, node,
+ "samsung,logo-width", 0);
+ panel_info.logo_height = fdtdec_get_int(blob, node,
+ "samsung,logo-height", 0);
+ panel_info.logo_addr = fdtdec_get_int(blob, node,
+ "samsung,logo-addr", 0);
+ }
+
+ panel_info.rgb_mode = fdtdec_get_int(blob, node,
+ "samsung,rgb-mode", 0);
+ panel_info.pclk_name = fdtdec_get_int(blob, node,
+ "samsung,pclk-name", 0);
+ panel_info.sclk_div = fdtdec_get_int(blob, node,
+ "samsung,sclk-div", 0);
+ panel_info.dual_lcd_enabled = fdtdec_get_int(blob, node,
+ "samsung,dual-lcd-enabled", 0);
+
+ return 0;
+}
+#endif
+
+void lcd_ctrl_init(void *lcdbase)
+{
+ set_system_display_ctrl();
+ set_lcd_clk();
+
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+#ifdef CONFIG_EXYNOS_MIPI_DSIM
+ exynos_init_dsim_platform_data(&panel_info);
+#endif
+ exynos_lcd_misc_init(&panel_info);
+#else
+ /* initialize parameters which is specific to panel. */
+ init_panel_info(&panel_info);
+#endif
+
+ panel_width = panel_info.vl_width;
+ panel_height = panel_info.vl_height;
+
+ exynos_lcd_init_mem(lcdbase, &panel_info);
+
+ exynos_lcd_init(&panel_info);
+}
+
+void lcd_enable(void)
+{
+ if (panel_info.logo_on) {
+ memset((void *) gd->fb_base, 0, panel_width * panel_height *
+ (NBITS(panel_info.vl_bpix) >> 3));
+ }
+
+ lcd_panel_on(&panel_info);
+}
+
+/* dummy function */
+void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue)
+{
+ return;
+}
diff --git a/drivers/video/exynos/exynos_fb.h b/drivers/video/exynos/exynos_fb.h
new file mode 100644
index 0000000000..2c2f94bd03
--- /dev/null
+++ b/drivers/video/exynos/exynos_fb.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef _EXYNOS_FB_H_
+#define _EXYNOS_FB_H_
+
+#include <asm/arch/fb.h>
+
+#define MAX_CLOCK (86 * 1000000)
+
+enum exynos_cpu_auto_cmd_rate {
+ DISABLE_AUTO_FRM,
+ PER_TWO_FRM,
+ PER_FOUR_FRM,
+ PER_SIX_FRM,
+ PER_EIGHT_FRM,
+ PER_TEN_FRM,
+ PER_TWELVE_FRM,
+ PER_FOURTEEN_FRM,
+ PER_SIXTEEN_FRM,
+ PER_EIGHTEEN_FRM,
+ PER_TWENTY_FRM,
+ PER_TWENTY_TWO_FRM,
+ PER_TWENTY_FOUR_FRM,
+ PER_TWENTY_SIX_FRM,
+ PER_TWENTY_EIGHT_FRM,
+ PER_THIRTY_FRM,
+};
+
+void exynos_fimd_lcd_init_mem(unsigned long screen_base, unsigned long fb_size,
+ unsigned long palette_size);
+void exynos_fimd_lcd_init(vidinfo_t *vid);
+unsigned long exynos_fimd_calc_fbsize(void);
+
+#endif
diff --git a/drivers/video/exynos/exynos_fimd.c b/drivers/video/exynos/exynos_fimd.c
new file mode 100644
index 0000000000..ac001a801e
--- /dev/null
+++ b/drivers/video/exynos/exynos_fimd.c
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+#include <common.h>
+#include <asm/io.h>
+#include <lcd.h>
+#include <div64.h>
+#include <fdtdec.h>
+#include <libfdt.h>
+#include <asm/arch/clk.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/cpu.h>
+#include "exynos_fb.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static unsigned long *lcd_base_addr;
+static vidinfo_t *pvid;
+static struct exynos_fb *fimd_ctrl;
+
+void exynos_fimd_lcd_init_mem(u_long screen_base, u_long fb_size,
+ u_long palette_size)
+{
+ lcd_base_addr = (unsigned long *)screen_base;
+}
+
+static void exynos_fimd_set_dualrgb(unsigned int enabled)
+{
+ unsigned int cfg = 0;
+
+ if (enabled) {
+ cfg = EXYNOS_DUALRGB_BYPASS_DUAL | EXYNOS_DUALRGB_LINESPLIT |
+ EXYNOS_DUALRGB_VDEN_EN_ENABLE;
+
+ /* in case of Line Split mode, MAIN_CNT doesn't neet to set. */
+ cfg |= EXYNOS_DUALRGB_SUB_CNT(pvid->vl_col / 2) |
+ EXYNOS_DUALRGB_MAIN_CNT(0);
+ }
+
+ writel(cfg, &fimd_ctrl->dualrgb);
+}
+
+static void exynos_fimd_set_dp_clkcon(unsigned int enabled)
+{
+ unsigned int cfg = 0;
+
+ if (enabled)
+ cfg = EXYNOS_DP_CLK_ENABLE;
+
+ writel(cfg, &fimd_ctrl->dp_mie_clkcon);
+}
+
+static void exynos_fimd_set_par(unsigned int win_id)
+{
+ unsigned int cfg = 0;
+
+ /* set window control */
+ cfg = readl((unsigned int)&fimd_ctrl->wincon0 +
+ EXYNOS_WINCON(win_id));
+
+ cfg &= ~(EXYNOS_WINCON_BITSWP_ENABLE | EXYNOS_WINCON_BYTESWP_ENABLE |
+ EXYNOS_WINCON_HAWSWP_ENABLE | EXYNOS_WINCON_WSWP_ENABLE |
+ EXYNOS_WINCON_BURSTLEN_MASK | EXYNOS_WINCON_BPPMODE_MASK |
+ EXYNOS_WINCON_INRGB_MASK | EXYNOS_WINCON_DATAPATH_MASK);
+
+ /* DATAPATH is DMA */
+ cfg |= EXYNOS_WINCON_DATAPATH_DMA;
+
+ cfg |= EXYNOS_WINCON_HAWSWP_ENABLE;
+
+ /* dma burst is 16 */
+ cfg |= EXYNOS_WINCON_BURSTLEN_16WORD;
+
+ switch (pvid->vl_bpix) {
+ case 4:
+ cfg |= EXYNOS_WINCON_BPPMODE_16BPP_565;
+ break;
+ default:
+ cfg |= EXYNOS_WINCON_BPPMODE_24BPP_888;
+ break;
+ }
+
+ writel(cfg, (unsigned int)&fimd_ctrl->wincon0 +
+ EXYNOS_WINCON(win_id));
+
+ /* set window position to x=0, y=0*/
+ cfg = EXYNOS_VIDOSD_LEFT_X(0) | EXYNOS_VIDOSD_TOP_Y(0);
+ writel(cfg, (unsigned int)&fimd_ctrl->vidosd0a +
+ EXYNOS_VIDOSD(win_id));
+
+ cfg = EXYNOS_VIDOSD_RIGHT_X(pvid->vl_col - 1) |
+ EXYNOS_VIDOSD_BOTTOM_Y(pvid->vl_row - 1) |
+ EXYNOS_VIDOSD_RIGHT_X_E(1) |
+ EXYNOS_VIDOSD_BOTTOM_Y_E(0);
+
+ writel(cfg, (unsigned int)&fimd_ctrl->vidosd0b +
+ EXYNOS_VIDOSD(win_id));
+
+ /* set window size for window0*/
+ cfg = EXYNOS_VIDOSD_SIZE(pvid->vl_col * pvid->vl_row);
+ writel(cfg, (unsigned int)&fimd_ctrl->vidosd0c +
+ EXYNOS_VIDOSD(win_id));
+}
+
+static void exynos_fimd_set_buffer_address(unsigned int win_id)
+{
+ unsigned long start_addr, end_addr;
+
+ start_addr = (unsigned long)lcd_base_addr;
+ end_addr = start_addr + ((pvid->vl_col * (NBITS(pvid->vl_bpix) / 8)) *
+ pvid->vl_row);
+
+ writel(start_addr, (unsigned int)&fimd_ctrl->vidw00add0b0 +
+ EXYNOS_BUFFER_OFFSET(win_id));
+ writel(end_addr, (unsigned int)&fimd_ctrl->vidw00add1b0 +
+ EXYNOS_BUFFER_OFFSET(win_id));
+}
+
+static void exynos_fimd_set_clock(vidinfo_t *pvid)
+{
+ unsigned int cfg = 0, div = 0, remainder, remainder_div;
+ unsigned long pixel_clock;
+ unsigned long long src_clock;
+
+ if (pvid->dual_lcd_enabled) {
+ pixel_clock = pvid->vl_freq *
+ (pvid->vl_hspw + pvid->vl_hfpd +
+ pvid->vl_hbpd + pvid->vl_col / 2) *
+ (pvid->vl_vspw + pvid->vl_vfpd +
+ pvid->vl_vbpd + pvid->vl_row);
+ } else if (pvid->interface_mode == FIMD_CPU_INTERFACE) {
+ pixel_clock = pvid->vl_freq *
+ pvid->vl_width * pvid->vl_height *
+ (pvid->cs_setup + pvid->wr_setup +
+ pvid->wr_act + pvid->wr_hold + 1);
+ } else {
+ pixel_clock = pvid->vl_freq *
+ (pvid->vl_hspw + pvid->vl_hfpd +
+ pvid->vl_hbpd + pvid->vl_col) *
+ (pvid->vl_vspw + pvid->vl_vfpd +
+ pvid->vl_vbpd + pvid->vl_row);
+ }
+
+ cfg = readl(&fimd_ctrl->vidcon0);
+ cfg &= ~(EXYNOS_VIDCON0_CLKSEL_MASK | EXYNOS_VIDCON0_CLKVALUP_MASK |
+ EXYNOS_VIDCON0_CLKVAL_F(0xFF) | EXYNOS_VIDCON0_VCLKEN_MASK |
+ EXYNOS_VIDCON0_CLKDIR_MASK);
+ cfg |= (EXYNOS_VIDCON0_CLKSEL_SCLK | EXYNOS_VIDCON0_CLKVALUP_ALWAYS |
+ EXYNOS_VIDCON0_VCLKEN_NORMAL | EXYNOS_VIDCON0_CLKDIR_DIVIDED);
+
+ src_clock = (unsigned long long) get_lcd_clk();
+
+ /* get quotient and remainder. */
+ remainder = do_div(src_clock, pixel_clock);
+ div = src_clock;
+
+ remainder *= 10;
+ remainder_div = remainder / pixel_clock;
+
+ /* round about one places of decimals. */
+ if (remainder_div >= 5)
+ div++;
+
+ /* in case of dual lcd mode. */
+ if (pvid->dual_lcd_enabled)
+ div--;
+
+ cfg |= EXYNOS_VIDCON0_CLKVAL_F(div - 1);
+ writel(cfg, &fimd_ctrl->vidcon0);
+}
+
+void exynos_set_trigger(void)
+{
+ unsigned int cfg = 0;
+
+ cfg = readl(&fimd_ctrl->trigcon);
+
+ cfg |= (EXYNOS_I80SOFT_TRIG_EN | EXYNOS_I80START_TRIG);
+
+ writel(cfg, &fimd_ctrl->trigcon);
+}
+
+int exynos_is_i80_frame_done(void)
+{
+ unsigned int cfg = 0;
+ int status;
+
+ cfg = readl(&fimd_ctrl->trigcon);
+
+ /* frame done func is valid only when TRIMODE[0] is set to 1. */
+ status = (cfg & EXYNOS_I80STATUS_TRIG_DONE) ==
+ EXYNOS_I80STATUS_TRIG_DONE;
+
+ return status;
+}
+
+static void exynos_fimd_lcd_on(void)
+{
+ unsigned int cfg = 0;
+
+ /* display on */
+ cfg = readl(&fimd_ctrl->vidcon0);
+ cfg |= (EXYNOS_VIDCON0_ENVID_ENABLE | EXYNOS_VIDCON0_ENVID_F_ENABLE);
+ writel(cfg, &fimd_ctrl->vidcon0);
+}
+
+static void exynos_fimd_window_on(unsigned int win_id)
+{
+ unsigned int cfg = 0;
+
+ /* enable window */
+ cfg = readl((unsigned int)&fimd_ctrl->wincon0 +
+ EXYNOS_WINCON(win_id));
+ cfg |= EXYNOS_WINCON_ENWIN_ENABLE;
+ writel(cfg, (unsigned int)&fimd_ctrl->wincon0 +
+ EXYNOS_WINCON(win_id));
+
+ cfg = readl(&fimd_ctrl->winshmap);
+ cfg |= EXYNOS_WINSHMAP_CH_ENABLE(win_id);
+ writel(cfg, &fimd_ctrl->winshmap);
+}
+
+void exynos_fimd_lcd_off(void)
+{
+ unsigned int cfg = 0;
+
+ cfg = readl(&fimd_ctrl->vidcon0);
+ cfg &= (EXYNOS_VIDCON0_ENVID_DISABLE | EXYNOS_VIDCON0_ENVID_F_DISABLE);
+ writel(cfg, &fimd_ctrl->vidcon0);
+}
+
+void exynos_fimd_window_off(unsigned int win_id)
+{
+ unsigned int cfg = 0;
+
+ cfg = readl((unsigned int)&fimd_ctrl->wincon0 +
+ EXYNOS_WINCON(win_id));
+ cfg &= EXYNOS_WINCON_ENWIN_DISABLE;
+ writel(cfg, (unsigned int)&fimd_ctrl->wincon0 +
+ EXYNOS_WINCON(win_id));
+
+ cfg = readl(&fimd_ctrl->winshmap);
+ cfg &= ~EXYNOS_WINSHMAP_CH_DISABLE(win_id);
+ writel(cfg, &fimd_ctrl->winshmap);
+}
+
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+/*
+* The reset value for FIMD SYSMMU register MMU_CTRL is 3
+* on Exynos5420 and newer versions.
+* This means FIMD SYSMMU is on by default on Exynos5420
+* and newer versions.
+* Since in u-boot we don't use SYSMMU, we should disable
+* those FIMD SYSMMU.
+* Note that there are 2 SYSMMU for FIMD: m0 and m1.
+* m0 handles windows 0 and 4, and m1 handles windows 1, 2 and 3.
+* We disable both of them here.
+*/
+void exynos_fimd_disable_sysmmu(void)
+{
+ u32 *sysmmufimd;
+ unsigned int node;
+ int node_list[2];
+ int count;
+ int i;
+
+ count = fdtdec_find_aliases_for_id(gd->fdt_blob, "fimd",
+ COMPAT_SAMSUNG_EXYNOS_SYSMMU, node_list, 2);
+ for (i = 0; i < count; i++) {
+ node = node_list[i];
+ if (node <= 0) {
+ debug("Can't get device node for fimd sysmmu\n");
+ return;
+ }
+
+ sysmmufimd = (u32 *)fdtdec_get_addr(gd->fdt_blob, node, "reg");
+ if (!sysmmufimd) {
+ debug("Can't get base address for sysmmu fimdm0");
+ return;
+ }
+
+ writel(0x0, sysmmufimd);
+ }
+}
+#endif
+
+void exynos_fimd_lcd_init(vidinfo_t *vid)
+{
+ unsigned int cfg = 0, rgb_mode;
+ unsigned int offset;
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+ unsigned int node;
+
+ node = fdtdec_next_compatible(gd->fdt_blob,
+ 0, COMPAT_SAMSUNG_EXYNOS_FIMD);
+ if (node <= 0)
+ debug("exynos_fb: Can't get device node for fimd\n");
+
+ fimd_ctrl = (struct exynos_fb *)fdtdec_get_addr(gd->fdt_blob,
+ node, "reg");
+ if (fimd_ctrl == NULL)
+ debug("Can't get the FIMD base address\n");
+
+ if (fdtdec_get_bool(gd->fdt_blob, node, "samsung,disable-sysmmu"))
+ exynos_fimd_disable_sysmmu();
+
+#else
+ fimd_ctrl = (struct exynos_fb *)samsung_get_base_fimd();
+#endif
+
+ offset = exynos_fimd_get_base_offset();
+
+ /* store panel info to global variable */
+ pvid = vid;
+
+ rgb_mode = vid->rgb_mode;
+
+ if (vid->interface_mode == FIMD_RGB_INTERFACE) {
+ cfg |= EXYNOS_VIDCON0_VIDOUT_RGB;
+ writel(cfg, &fimd_ctrl->vidcon0);
+
+ cfg = readl(&fimd_ctrl->vidcon2);
+ cfg &= ~(EXYNOS_VIDCON2_WB_MASK |
+ EXYNOS_VIDCON2_TVFORMATSEL_MASK |
+ EXYNOS_VIDCON2_TVFORMATSEL_YUV_MASK);
+ cfg |= EXYNOS_VIDCON2_WB_DISABLE;
+ writel(cfg, &fimd_ctrl->vidcon2);
+
+ /* set polarity */
+ cfg = 0;
+ if (!pvid->vl_clkp)
+ cfg |= EXYNOS_VIDCON1_IVCLK_RISING_EDGE;
+ if (!pvid->vl_hsp)
+ cfg |= EXYNOS_VIDCON1_IHSYNC_INVERT;
+ if (!pvid->vl_vsp)
+ cfg |= EXYNOS_VIDCON1_IVSYNC_INVERT;
+ if (!pvid->vl_dp)
+ cfg |= EXYNOS_VIDCON1_IVDEN_INVERT;
+
+ writel(cfg, (unsigned int)&fimd_ctrl->vidcon1 + offset);
+
+ /* set timing */
+ cfg = EXYNOS_VIDTCON0_VFPD(pvid->vl_vfpd - 1);
+ cfg |= EXYNOS_VIDTCON0_VBPD(pvid->vl_vbpd - 1);
+ cfg |= EXYNOS_VIDTCON0_VSPW(pvid->vl_vspw - 1);
+ writel(cfg, (unsigned int)&fimd_ctrl->vidtcon0 + offset);
+
+ cfg = EXYNOS_VIDTCON1_HFPD(pvid->vl_hfpd - 1);
+ cfg |= EXYNOS_VIDTCON1_HBPD(pvid->vl_hbpd - 1);
+ cfg |= EXYNOS_VIDTCON1_HSPW(pvid->vl_hspw - 1);
+
+ writel(cfg, (unsigned int)&fimd_ctrl->vidtcon1 + offset);
+
+ /* set lcd size */
+ cfg = EXYNOS_VIDTCON2_HOZVAL(pvid->vl_col - 1) |
+ EXYNOS_VIDTCON2_LINEVAL(pvid->vl_row - 1) |
+ EXYNOS_VIDTCON2_HOZVAL_E(pvid->vl_col - 1) |
+ EXYNOS_VIDTCON2_LINEVAL_E(pvid->vl_row - 1);
+
+ writel(cfg, (unsigned int)&fimd_ctrl->vidtcon2 + offset);
+ }
+
+ /* set display mode */
+ cfg = readl(&fimd_ctrl->vidcon0);
+ cfg &= ~EXYNOS_VIDCON0_PNRMODE_MASK;
+ cfg |= (rgb_mode << EXYNOS_VIDCON0_PNRMODE_SHIFT);
+ writel(cfg, &fimd_ctrl->vidcon0);
+
+ /* set par */
+ exynos_fimd_set_par(pvid->win_id);
+
+ /* set memory address */
+ exynos_fimd_set_buffer_address(pvid->win_id);
+
+ /* set buffer size */
+ cfg = EXYNOS_VIDADDR_PAGEWIDTH(pvid->vl_col * NBITS(pvid->vl_bpix) / 8) |
+ EXYNOS_VIDADDR_PAGEWIDTH_E(pvid->vl_col * NBITS(pvid->vl_bpix) / 8) |
+ EXYNOS_VIDADDR_OFFSIZE(0) |
+ EXYNOS_VIDADDR_OFFSIZE_E(0);
+
+ writel(cfg, (unsigned int)&fimd_ctrl->vidw00add2 +
+ EXYNOS_BUFFER_SIZE(pvid->win_id));
+
+ /* set clock */
+ exynos_fimd_set_clock(pvid);
+
+ /* set rgb mode to dual lcd. */
+ exynos_fimd_set_dualrgb(pvid->dual_lcd_enabled);
+
+ /* display on */
+ exynos_fimd_lcd_on();
+
+ /* window on */
+ exynos_fimd_window_on(pvid->win_id);
+
+ exynos_fimd_set_dp_clkcon(pvid->dp_enabled);
+}
+
+unsigned long exynos_fimd_calc_fbsize(void)
+{
+ return pvid->vl_col * pvid->vl_row * (NBITS(pvid->vl_bpix) / 8);
+}
diff --git a/drivers/video/exynos/exynos_mipi_dsi.c b/drivers/video/exynos/exynos_mipi_dsi.c
new file mode 100644
index 0000000000..b597accf3d
--- /dev/null
+++ b/drivers/video/exynos/exynos_mipi_dsi.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <fdtdec.h>
+#include <libfdt.h>
+#include <linux/compat.h>
+#include <linux/err.h>
+#include <asm/arch/dsim.h>
+#include <asm/arch/mipi_dsim.h>
+#include <asm/arch/power.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/clk.h>
+
+#include "exynos_mipi_dsi_lowlevel.h"
+#include "exynos_mipi_dsi_common.h"
+
+#define master_to_driver(a) (a->dsim_lcd_drv)
+#define master_to_device(a) (a->dsim_lcd_dev)
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static struct exynos_platform_mipi_dsim *dsim_pd;
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+static struct mipi_dsim_config dsim_config_dt;
+static struct exynos_platform_mipi_dsim dsim_platform_data_dt;
+static struct mipi_dsim_lcd_device mipi_lcd_device_dt;
+#endif
+
+struct mipi_dsim_ddi {
+ int bus_id;
+ struct list_head list;
+ struct mipi_dsim_lcd_device *dsim_lcd_dev;
+ struct mipi_dsim_lcd_driver *dsim_lcd_drv;
+};
+
+static LIST_HEAD(dsim_ddi_list);
+static LIST_HEAD(dsim_lcd_dev_list);
+
+int exynos_mipi_dsi_register_lcd_device(struct mipi_dsim_lcd_device *lcd_dev)
+{
+ struct mipi_dsim_ddi *dsim_ddi;
+
+ if (!lcd_dev) {
+ debug("mipi_dsim_lcd_device is NULL.\n");
+ return -EFAULT;
+ }
+
+ if (!lcd_dev->name) {
+ debug("dsim_lcd_device name is NULL.\n");
+ return -EFAULT;
+ }
+
+ dsim_ddi = kzalloc(sizeof(struct mipi_dsim_ddi), GFP_KERNEL);
+ if (!dsim_ddi) {
+ debug("failed to allocate dsim_ddi object.\n");
+ return -EFAULT;
+ }
+
+ dsim_ddi->dsim_lcd_dev = lcd_dev;
+
+ list_add_tail(&dsim_ddi->list, &dsim_ddi_list);
+
+ return 0;
+}
+
+struct mipi_dsim_ddi
+ *exynos_mipi_dsi_find_lcd_device(struct mipi_dsim_lcd_driver *lcd_drv)
+{
+ struct mipi_dsim_ddi *dsim_ddi;
+ struct mipi_dsim_lcd_device *lcd_dev;
+
+ list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) {
+ lcd_dev = dsim_ddi->dsim_lcd_dev;
+ if (!lcd_dev)
+ continue;
+
+ if (lcd_drv->id >= 0) {
+ if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0 &&
+ lcd_drv->id == lcd_dev->id) {
+ /**
+ * bus_id would be used to identify
+ * connected bus.
+ */
+ dsim_ddi->bus_id = lcd_dev->bus_id;
+
+ return dsim_ddi;
+ }
+ } else {
+ if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0) {
+ /**
+ * bus_id would be used to identify
+ * connected bus.
+ */
+ dsim_ddi->bus_id = lcd_dev->bus_id;
+
+ return dsim_ddi;
+ }
+ }
+
+ kfree(dsim_ddi);
+ list_del(&dsim_ddi_list);
+ }
+
+ return NULL;
+}
+
+int exynos_mipi_dsi_register_lcd_driver(struct mipi_dsim_lcd_driver *lcd_drv)
+{
+ struct mipi_dsim_ddi *dsim_ddi;
+
+ if (!lcd_drv) {
+ debug("mipi_dsim_lcd_driver is NULL.\n");
+ return -EFAULT;
+ }
+
+ if (!lcd_drv->name) {
+ debug("dsim_lcd_driver name is NULL.\n");
+ return -EFAULT;
+ }
+
+ dsim_ddi = exynos_mipi_dsi_find_lcd_device(lcd_drv);
+ if (!dsim_ddi) {
+ debug("mipi_dsim_ddi object not found.\n");
+ return -EFAULT;
+ }
+
+ dsim_ddi->dsim_lcd_drv = lcd_drv;
+
+ debug("registered panel driver(%s) to mipi-dsi driver.\n",
+ lcd_drv->name);
+
+ return 0;
+
+}
+
+struct mipi_dsim_ddi
+ *exynos_mipi_dsi_bind_lcd_ddi(struct mipi_dsim_device *dsim,
+ const char *name)
+{
+ struct mipi_dsim_ddi *dsim_ddi;
+ struct mipi_dsim_lcd_driver *lcd_drv;
+ struct mipi_dsim_lcd_device *lcd_dev;
+
+ list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) {
+ lcd_drv = dsim_ddi->dsim_lcd_drv;
+ lcd_dev = dsim_ddi->dsim_lcd_dev;
+ if (!lcd_drv || !lcd_dev)
+ continue;
+
+ debug("lcd_drv->id = %d, lcd_dev->id = %d\n",
+ lcd_drv->id, lcd_dev->id);
+
+ if ((strcmp(lcd_drv->name, name) == 0)) {
+ lcd_dev->master = dsim;
+
+ dsim->dsim_lcd_dev = lcd_dev;
+ dsim->dsim_lcd_drv = lcd_drv;
+
+ return dsim_ddi;
+ }
+ }
+
+ return NULL;
+}
+
+/* define MIPI-DSI Master operations. */
+static struct mipi_dsim_master_ops master_ops = {
+ .cmd_write = exynos_mipi_dsi_wr_data,
+ .get_dsim_frame_done = exynos_mipi_dsi_get_frame_done_status,
+ .clear_dsim_frame_done = exynos_mipi_dsi_clear_frame_done,
+};
+
+int exynos_mipi_dsi_init(void)
+{
+ struct mipi_dsim_device *dsim;
+ struct mipi_dsim_config *dsim_config;
+ struct mipi_dsim_ddi *dsim_ddi;
+
+ dsim = kzalloc(sizeof(struct mipi_dsim_device), GFP_KERNEL);
+ if (!dsim) {
+ debug("failed to allocate dsim object.\n");
+ return -EFAULT;
+ }
+
+ /* get mipi_dsim_config. */
+ dsim_config = dsim_pd->dsim_config;
+ if (dsim_config == NULL) {
+ debug("failed to get dsim config data.\n");
+ return -EFAULT;
+ }
+
+ dsim->pd = dsim_pd;
+ dsim->dsim_config = dsim_config;
+ dsim->master_ops = &master_ops;
+
+ /* bind lcd ddi matched with panel name. */
+ dsim_ddi = exynos_mipi_dsi_bind_lcd_ddi(dsim, dsim_pd->lcd_panel_name);
+ if (!dsim_ddi) {
+ debug("mipi_dsim_ddi object not found.\n");
+ return -ENOSYS;
+ }
+ if (dsim_pd->lcd_power)
+ dsim_pd->lcd_power();
+
+ if (dsim_pd->mipi_power)
+ dsim_pd->mipi_power();
+
+ /* phy_enable(unsigned int dev_index, unsigned int enable) */
+ if (dsim_pd->phy_enable)
+ dsim_pd->phy_enable(0, 1);
+
+ set_mipi_clk();
+
+ exynos_mipi_dsi_init_dsim(dsim);
+ exynos_mipi_dsi_init_link(dsim);
+ exynos_mipi_dsi_set_hs_enable(dsim);
+
+ /* set display timing. */
+ exynos_mipi_dsi_set_display_mode(dsim, dsim->dsim_config);
+
+ /* initialize mipi-dsi client(lcd panel). */
+ if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->mipi_panel_init) {
+ dsim_ddi->dsim_lcd_drv->mipi_panel_init(dsim);
+ dsim_ddi->dsim_lcd_drv->mipi_display_on(dsim);
+ }
+
+ debug("mipi-dsi driver(%s mode) has been probed.\n",
+ (dsim_config->e_interface == DSIM_COMMAND) ?
+ "CPU" : "RGB");
+
+ return 0;
+}
+
+void exynos_set_dsim_platform_data(struct exynos_platform_mipi_dsim *pd)
+{
+ if (pd == NULL) {
+ debug("pd is NULL\n");
+ return;
+ }
+
+ dsim_pd = pd;
+}
+
+#if CONFIG_IS_ENABLED(OF_CONTROL)
+int exynos_dsim_config_parse_dt(const void *blob)
+{
+ int node;
+
+ node = fdtdec_next_compatible(blob, 0, COMPAT_SAMSUNG_EXYNOS_MIPI_DSI);
+ if (node <= 0) {
+ printf("exynos_mipi_dsi: Can't get device node for mipi dsi\n");
+ return -ENODEV;
+ }
+
+ dsim_config_dt.e_interface = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-e-interface", 0);
+
+ dsim_config_dt.e_virtual_ch = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-e-virtual-ch", 0);
+
+ dsim_config_dt.e_pixel_format = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-e-pixel-format", 0);
+
+ dsim_config_dt.e_burst_mode = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-e-burst-mode", 0);
+
+ dsim_config_dt.e_no_data_lane = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-e-no-data-lane", 0);
+
+ dsim_config_dt.e_byte_clk = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-e-byte-clk", 0);
+
+ dsim_config_dt.hfp = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-hfp", 0);
+
+ dsim_config_dt.p = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-p", 0);
+ dsim_config_dt.m = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-m", 0);
+ dsim_config_dt.s = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-s", 0);
+
+ dsim_config_dt.pll_stable_time = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-pll-stable-time", 0);
+
+ dsim_config_dt.esc_clk = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-esc-clk", 0);
+
+ dsim_config_dt.stop_holding_cnt = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-stop-holding-cnt", 0);
+
+ dsim_config_dt.bta_timeout = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-bta-timeout", 0);
+
+ dsim_config_dt.rx_timeout = fdtdec_get_int(blob, node,
+ "samsung,dsim-config-rx-timeout", 0);
+
+ mipi_lcd_device_dt.name = fdtdec_get_config_string(blob,
+ "samsung,dsim-device-name");
+
+ mipi_lcd_device_dt.id = fdtdec_get_int(blob, node,
+ "samsung,dsim-device-id", 0);
+
+ mipi_lcd_device_dt.bus_id = fdtdec_get_int(blob, node,
+ "samsung,dsim-device-bus_id", 0);
+
+ mipi_lcd_device_dt.reverse_panel = fdtdec_get_int(blob, node,
+ "samsung,dsim-device-reverse-panel", 0);
+
+ return 0;
+}
+
+void exynos_init_dsim_platform_data(vidinfo_t *vid)
+{
+ if (exynos_dsim_config_parse_dt(gd->fdt_blob))
+ debug("Can't get proper dsim config.\n");
+
+ strcpy(dsim_platform_data_dt.lcd_panel_name, mipi_lcd_device_dt.name);
+ dsim_platform_data_dt.dsim_config = &dsim_config_dt;
+ dsim_platform_data_dt.mipi_power = mipi_power;
+ dsim_platform_data_dt.phy_enable = set_mipi_phy_ctrl;
+ dsim_platform_data_dt.lcd_panel_info = (void *)vid;
+
+ mipi_lcd_device_dt.platform_data = (void *)&dsim_platform_data_dt;
+ exynos_mipi_dsi_register_lcd_device(&mipi_lcd_device_dt);
+
+ dsim_pd = &dsim_platform_data_dt;
+}
+#endif
diff --git a/drivers/video/exynos/exynos_mipi_dsi_common.c b/drivers/video/exynos/exynos_mipi_dsi_common.c
new file mode 100644
index 0000000000..925d51500a
--- /dev/null
+++ b/drivers/video/exynos/exynos_mipi_dsi_common.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <lcd.h>
+#include <linux/err.h>
+#include <asm/arch/dsim.h>
+#include <asm/arch/mipi_dsim.h>
+
+#include "exynos_mipi_dsi_lowlevel.h"
+
+#define MHZ (1000 * 1000)
+#define FIN_HZ (24 * MHZ)
+
+#define DFIN_PLL_MIN_HZ (6 * MHZ)
+#define DFIN_PLL_MAX_HZ (12 * MHZ)
+
+#define DFVCO_MIN_HZ (500 * MHZ)
+#define DFVCO_MAX_HZ (1000 * MHZ)
+
+#define TRY_GET_FIFO_TIMEOUT (5000 * 2)
+
+/* MIPI-DSIM status types. */
+enum {
+ DSIM_STATE_INIT, /* should be initialized. */
+ DSIM_STATE_STOP, /* CPU and LCDC are LP mode. */
+ DSIM_STATE_HSCLKEN, /* HS clock was enabled. */
+ DSIM_STATE_ULPS
+};
+
+/* define DSI lane types. */
+enum {
+ DSIM_LANE_CLOCK = (1 << 0),
+ DSIM_LANE_DATA0 = (1 << 1),
+ DSIM_LANE_DATA1 = (1 << 2),
+ DSIM_LANE_DATA2 = (1 << 3),
+ DSIM_LANE_DATA3 = (1 << 4)
+};
+
+static unsigned int dpll_table[15] = {
+ 100, 120, 170, 220, 270,
+ 320, 390, 450, 510, 560,
+ 640, 690, 770, 870, 950
+};
+
+static void exynos_mipi_dsi_long_data_wr(struct mipi_dsim_device *dsim,
+ const unsigned char *data0, unsigned int data1)
+{
+ unsigned int data_cnt = 0, payload = 0;
+
+ /* in case that data count is more then 4 */
+ for (data_cnt = 0; data_cnt < data1; data_cnt += 4) {
+ /*
+ * after sending 4bytes per one time,
+ * send remainder data less then 4.
+ */
+ if ((data1 - data_cnt) < 4) {
+ if ((data1 - data_cnt) == 3) {
+ payload = data0[data_cnt] |
+ data0[data_cnt + 1] << 8 |
+ data0[data_cnt + 2] << 16;
+ debug("count = 3 payload = %x, %x %x %x\n",
+ payload, data0[data_cnt],
+ data0[data_cnt + 1],
+ data0[data_cnt + 2]);
+ } else if ((data1 - data_cnt) == 2) {
+ payload = data0[data_cnt] |
+ data0[data_cnt + 1] << 8;
+ debug("count = 2 payload = %x, %x %x\n", payload,
+ data0[data_cnt], data0[data_cnt + 1]);
+ } else if ((data1 - data_cnt) == 1) {
+ payload = data0[data_cnt];
+ }
+ } else {
+ /* send 4bytes per one time. */
+ payload = data0[data_cnt] |
+ data0[data_cnt + 1] << 8 |
+ data0[data_cnt + 2] << 16 |
+ data0[data_cnt + 3] << 24;
+
+ debug("count = 4 payload = %x, %x %x %x %x\n",
+ payload, *(u8 *)(data0 + data_cnt),
+ data0[data_cnt + 1],
+ data0[data_cnt + 2],
+ data0[data_cnt + 3]);
+ }
+ exynos_mipi_dsi_wr_tx_data(dsim, payload);
+ }
+}
+
+int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id,
+ const unsigned char *data0, unsigned int data1)
+{
+ unsigned int timeout = TRY_GET_FIFO_TIMEOUT;
+ unsigned long delay_val, delay;
+ unsigned int check_rx_ack = 0;
+
+ if (dsim->state == DSIM_STATE_ULPS) {
+ debug("state is ULPS.\n");
+
+ return -EINVAL;
+ }
+
+ delay_val = MHZ / dsim->dsim_config->esc_clk;
+ delay = 10 * delay_val;
+
+ mdelay(delay);
+
+ /* only if transfer mode is LPDT, wait SFR becomes empty. */
+ if (dsim->state == DSIM_STATE_STOP) {
+ while (!(exynos_mipi_dsi_get_fifo_state(dsim) &
+ SFR_HEADER_EMPTY)) {
+ if ((timeout--) > 0)
+ mdelay(1);
+ else {
+ debug("SRF header fifo is not empty.\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ switch (data_id) {
+ /* short packet types of packet types for command. */
+ case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
+ case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
+ case MIPI_DSI_DCS_SHORT_WRITE:
+ case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
+ case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
+ debug("data0 = %x data1 = %x\n",
+ data0[0], data0[1]);
+ exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]);
+ if (check_rx_ack) {
+ /* process response func should be implemented */
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+
+ /* general command */
+ case MIPI_DSI_COLOR_MODE_OFF:
+ case MIPI_DSI_COLOR_MODE_ON:
+ case MIPI_DSI_SHUTDOWN_PERIPHERAL:
+ case MIPI_DSI_TURN_ON_PERIPHERAL:
+ exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]);
+ if (check_rx_ack) {
+ /* process response func should be implemented. */
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+
+ /* packet types for video data */
+ case MIPI_DSI_V_SYNC_START:
+ case MIPI_DSI_V_SYNC_END:
+ case MIPI_DSI_H_SYNC_START:
+ case MIPI_DSI_H_SYNC_END:
+ case MIPI_DSI_END_OF_TRANSMISSION:
+ return 0;
+
+ /* short and response packet types for command */
+ case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
+ case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
+ case MIPI_DSI_DCS_READ:
+ exynos_mipi_dsi_clear_all_interrupt(dsim);
+ exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]);
+ /* process response func should be implemented. */
+ return 0;
+
+ /* long packet type and null packet */
+ case MIPI_DSI_NULL_PACKET:
+ case MIPI_DSI_BLANKING_PACKET:
+ return 0;
+ case MIPI_DSI_GENERIC_LONG_WRITE:
+ case MIPI_DSI_DCS_LONG_WRITE:
+ {
+ unsigned int payload = 0;
+
+ /* if data count is less then 4, then send 3bytes data. */
+ if (data1 < 4) {
+ payload = data0[0] |
+ data0[1] << 8 |
+ data0[2] << 16;
+
+ exynos_mipi_dsi_wr_tx_data(dsim, payload);
+
+ debug("count = %d payload = %x,%x %x %x\n",
+ data1, payload, data0[0],
+ data0[1], data0[2]);
+ } else {
+ /* in case that data count is more then 4 */
+ exynos_mipi_dsi_long_data_wr(dsim, data0, data1);
+ }
+
+ /* put data into header fifo */
+ exynos_mipi_dsi_wr_tx_header(dsim, data_id, data1 & 0xff,
+ (data1 & 0xff00) >> 8);
+
+ }
+ if (check_rx_ack)
+ /* process response func should be implemented. */
+ return 0;
+ else
+ return -EINVAL;
+
+ /* packet typo for video data */
+ case MIPI_DSI_PACKED_PIXEL_STREAM_16:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_18:
+ case MIPI_DSI_PIXEL_STREAM_3BYTE_18:
+ case MIPI_DSI_PACKED_PIXEL_STREAM_24:
+ if (check_rx_ack) {
+ /* process response func should be implemented. */
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+ default:
+ debug("data id %x is not supported current DSI spec.\n",
+ data_id);
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int exynos_mipi_dsi_pll_on(struct mipi_dsim_device *dsim, unsigned int enable)
+{
+ int sw_timeout;
+
+ if (enable) {
+ sw_timeout = 1000;
+
+ exynos_mipi_dsi_clear_interrupt(dsim);
+ exynos_mipi_dsi_enable_pll(dsim, 1);
+ while (1) {
+ sw_timeout--;
+ if (exynos_mipi_dsi_is_pll_stable(dsim))
+ return 0;
+ if (sw_timeout == 0)
+ return -EINVAL;
+ }
+ } else
+ exynos_mipi_dsi_enable_pll(dsim, 0);
+
+ return 0;
+}
+
+unsigned long exynos_mipi_dsi_change_pll(struct mipi_dsim_device *dsim,
+ unsigned int pre_divider, unsigned int main_divider,
+ unsigned int scaler)
+{
+ unsigned long dfin_pll, dfvco, dpll_out;
+ unsigned int i, freq_band = 0xf;
+
+ dfin_pll = (FIN_HZ / pre_divider);
+
+ /******************************************************
+ * Serial Clock(=ByteClk X 8) FreqBand[3:0] *
+ ******************************************************
+ * ~ 99.99 MHz 0000
+ * 100 ~ 119.99 MHz 0001
+ * 120 ~ 159.99 MHz 0010
+ * 160 ~ 199.99 MHz 0011
+ * 200 ~ 239.99 MHz 0100
+ * 140 ~ 319.99 MHz 0101
+ * 320 ~ 389.99 MHz 0110
+ * 390 ~ 449.99 MHz 0111
+ * 450 ~ 509.99 MHz 1000
+ * 510 ~ 559.99 MHz 1001
+ * 560 ~ 639.99 MHz 1010
+ * 640 ~ 689.99 MHz 1011
+ * 690 ~ 769.99 MHz 1100
+ * 770 ~ 869.99 MHz 1101
+ * 870 ~ 949.99 MHz 1110
+ * 950 ~ 1000 MHz 1111
+ ******************************************************/
+ if (dfin_pll < DFIN_PLL_MIN_HZ || dfin_pll > DFIN_PLL_MAX_HZ) {
+ debug("fin_pll range should be 6MHz ~ 12MHz\n");
+ exynos_mipi_dsi_enable_afc(dsim, 0, 0);
+ } else {
+ if (dfin_pll < 7 * MHZ)
+ exynos_mipi_dsi_enable_afc(dsim, 1, 0x1);
+ else if (dfin_pll < 8 * MHZ)
+ exynos_mipi_dsi_enable_afc(dsim, 1, 0x0);
+ else if (dfin_pll < 9 * MHZ)
+ exynos_mipi_dsi_enable_afc(dsim, 1, 0x3);
+ else if (dfin_pll < 10 * MHZ)
+ exynos_mipi_dsi_enable_afc(dsim, 1, 0x2);
+ else if (dfin_pll < 11 * MHZ)
+ exynos_mipi_dsi_enable_afc(dsim, 1, 0x5);
+ else
+ exynos_mipi_dsi_enable_afc(dsim, 1, 0x4);
+ }
+
+ dfvco = dfin_pll * main_divider;
+ debug("dfvco = %lu, dfin_pll = %lu, main_divider = %d\n",
+ dfvco, dfin_pll, main_divider);
+ if (dfvco < DFVCO_MIN_HZ || dfvco > DFVCO_MAX_HZ)
+ debug("fvco range should be 500MHz ~ 1000MHz\n");
+
+ dpll_out = dfvco / (1 << scaler);
+ debug("dpll_out = %lu, dfvco = %lu, scaler = %d\n",
+ dpll_out, dfvco, scaler);
+
+ for (i = 0; i < ARRAY_SIZE(dpll_table); i++) {
+ if (dpll_out < dpll_table[i] * MHZ) {
+ freq_band = i;
+ break;
+ }
+ }
+
+ debug("freq_band = %d\n", freq_band);
+
+ exynos_mipi_dsi_pll_freq(dsim, pre_divider, main_divider, scaler);
+
+ exynos_mipi_dsi_hs_zero_ctrl(dsim, 0);
+ exynos_mipi_dsi_prep_ctrl(dsim, 0);
+
+ /* Freq Band */
+ exynos_mipi_dsi_pll_freq_band(dsim, freq_band);
+
+ /* Stable time */
+ exynos_mipi_dsi_pll_stable_time(dsim,
+ dsim->dsim_config->pll_stable_time);
+
+ /* Enable PLL */
+ debug("FOUT of mipi dphy pll is %luMHz\n",
+ (dpll_out / MHZ));
+
+ return dpll_out;
+}
+
+int exynos_mipi_dsi_set_clock(struct mipi_dsim_device *dsim,
+ unsigned int byte_clk_sel, unsigned int enable)
+{
+ unsigned int esc_div;
+ unsigned long esc_clk_error_rate;
+ unsigned long hs_clk = 0, byte_clk = 0, escape_clk = 0;
+
+ if (enable) {
+ dsim->e_clk_src = byte_clk_sel;
+
+ /* Escape mode clock and byte clock source */
+ exynos_mipi_dsi_set_byte_clock_src(dsim, byte_clk_sel);
+
+ /* DPHY, DSIM Link : D-PHY clock out */
+ if (byte_clk_sel == DSIM_PLL_OUT_DIV8) {
+ hs_clk = exynos_mipi_dsi_change_pll(dsim,
+ dsim->dsim_config->p, dsim->dsim_config->m,
+ dsim->dsim_config->s);
+ if (hs_clk == 0) {
+ debug("failed to get hs clock.\n");
+ return -EINVAL;
+ }
+
+ byte_clk = hs_clk / 8;
+ exynos_mipi_dsi_enable_pll_bypass(dsim, 0);
+ exynos_mipi_dsi_pll_on(dsim, 1);
+ /* DPHY : D-PHY clock out, DSIM link : external clock out */
+ } else if (byte_clk_sel == DSIM_EXT_CLK_DIV8)
+ debug("not support EXT CLK source for MIPI DSIM\n");
+ else if (byte_clk_sel == DSIM_EXT_CLK_BYPASS)
+ debug("not support EXT CLK source for MIPI DSIM\n");
+
+ /* escape clock divider */
+ esc_div = byte_clk / (dsim->dsim_config->esc_clk);
+ debug("esc_div = %d, byte_clk = %lu, esc_clk = %lu\n",
+ esc_div, byte_clk, dsim->dsim_config->esc_clk);
+ if ((byte_clk / esc_div) >= (20 * MHZ) ||
+ (byte_clk / esc_div) > dsim->dsim_config->esc_clk)
+ esc_div += 1;
+
+ escape_clk = byte_clk / esc_div;
+ debug("escape_clk = %lu, byte_clk = %lu, esc_div = %d\n",
+ escape_clk, byte_clk, esc_div);
+
+ /* enable escape clock. */
+ exynos_mipi_dsi_enable_byte_clock(dsim, 1);
+
+ /* enable byte clk and escape clock */
+ exynos_mipi_dsi_set_esc_clk_prs(dsim, 1, esc_div);
+ /* escape clock on lane */
+ exynos_mipi_dsi_enable_esc_clk_on_lane(dsim,
+ (DSIM_LANE_CLOCK | dsim->data_lane), 1);
+
+ debug("byte clock is %luMHz\n",
+ (byte_clk / MHZ));
+ debug("escape clock that user's need is %lu\n",
+ (dsim->dsim_config->esc_clk / MHZ));
+ debug("escape clock divider is %x\n", esc_div);
+ debug("escape clock is %luMHz\n",
+ ((byte_clk / esc_div) / MHZ));
+
+ if ((byte_clk / esc_div) > escape_clk) {
+ esc_clk_error_rate = escape_clk /
+ (byte_clk / esc_div);
+ debug("error rate is %lu over.\n",
+ (esc_clk_error_rate / 100));
+ } else if ((byte_clk / esc_div) < (escape_clk)) {
+ esc_clk_error_rate = (byte_clk / esc_div) /
+ escape_clk;
+ debug("error rate is %lu under.\n",
+ (esc_clk_error_rate / 100));
+ }
+ } else {
+ exynos_mipi_dsi_enable_esc_clk_on_lane(dsim,
+ (DSIM_LANE_CLOCK | dsim->data_lane), 0);
+ exynos_mipi_dsi_set_esc_clk_prs(dsim, 0, 0);
+
+ /* disable escape clock. */
+ exynos_mipi_dsi_enable_byte_clock(dsim, 0);
+
+ if (byte_clk_sel == DSIM_PLL_OUT_DIV8)
+ exynos_mipi_dsi_pll_on(dsim, 0);
+ }
+
+ return 0;
+}
+
+int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim)
+{
+ dsim->state = DSIM_STATE_INIT;
+
+ switch (dsim->dsim_config->e_no_data_lane) {
+ case DSIM_DATA_LANE_1:
+ dsim->data_lane = DSIM_LANE_DATA0;
+ break;
+ case DSIM_DATA_LANE_2:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1;
+ break;
+ case DSIM_DATA_LANE_3:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 |
+ DSIM_LANE_DATA2;
+ break;
+ case DSIM_DATA_LANE_4:
+ dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 |
+ DSIM_LANE_DATA2 | DSIM_LANE_DATA3;
+ break;
+ default:
+ debug("data lane is invalid.\n");
+ return -EINVAL;
+ };
+
+ exynos_mipi_dsi_sw_reset(dsim);
+ exynos_mipi_dsi_dp_dn_swap(dsim, 0);
+
+ return 0;
+}
+
+int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim,
+ unsigned int enable)
+{
+ /* enable only frame done interrupt */
+ exynos_mipi_dsi_set_interrupt_mask(dsim, INTMSK_FRAME_DONE, enable);
+
+ return 0;
+}
+
+static void convert_to_fb_videomode(struct fb_videomode *mode1,
+ vidinfo_t *mode2)
+{
+ mode1->xres = mode2->vl_width;
+ mode1->yres = mode2->vl_height;
+ mode1->upper_margin = mode2->vl_vfpd;
+ mode1->lower_margin = mode2->vl_vbpd;
+ mode1->left_margin = mode2->vl_hfpd;
+ mode1->right_margin = mode2->vl_hbpd;
+ mode1->vsync_len = mode2->vl_vspw;
+ mode1->hsync_len = mode2->vl_hspw;
+}
+
+int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim,
+ struct mipi_dsim_config *dsim_config)
+{
+ struct exynos_platform_mipi_dsim *dsim_pd;
+ struct fb_videomode lcd_video;
+ vidinfo_t *vid;
+
+ dsim_pd = (struct exynos_platform_mipi_dsim *)dsim->pd;
+ vid = (vidinfo_t *)dsim_pd->lcd_panel_info;
+
+ convert_to_fb_videomode(&lcd_video, vid);
+
+ /* in case of VIDEO MODE (RGB INTERFACE), it sets polarities. */
+ if (dsim->dsim_config->e_interface == (u32) DSIM_VIDEO) {
+ if (dsim->dsim_config->auto_vertical_cnt == 0) {
+ exynos_mipi_dsi_set_main_disp_vporch(dsim,
+ vid->vl_cmd_allow_len,
+ lcd_video.upper_margin,
+ lcd_video.lower_margin);
+ exynos_mipi_dsi_set_main_disp_hporch(dsim,
+ lcd_video.left_margin,
+ lcd_video.right_margin);
+ exynos_mipi_dsi_set_main_disp_sync_area(dsim,
+ lcd_video.vsync_len,
+ lcd_video.hsync_len);
+ }
+ }
+
+ exynos_mipi_dsi_set_main_disp_resol(dsim, lcd_video.xres,
+ lcd_video.yres);
+
+ exynos_mipi_dsi_display_config(dsim, dsim->dsim_config);
+
+ debug("lcd panel ==> width = %d, height = %d\n",
+ lcd_video.xres, lcd_video.yres);
+
+ return 0;
+}
+
+int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim)
+{
+ unsigned int time_out = 100;
+
+ switch (dsim->state) {
+ case DSIM_STATE_INIT:
+ exynos_mipi_dsi_init_fifo_pointer(dsim, 0x1f);
+
+ /* dsi configuration */
+ exynos_mipi_dsi_init_config(dsim);
+ exynos_mipi_dsi_enable_lane(dsim, DSIM_LANE_CLOCK, 1);
+ exynos_mipi_dsi_enable_lane(dsim, dsim->data_lane, 1);
+
+ /* set clock configuration */
+ exynos_mipi_dsi_set_clock(dsim,
+ dsim->dsim_config->e_byte_clk, 1);
+
+ /* check clock and data lane state are stop state */
+ while (!(exynos_mipi_dsi_is_lane_state(dsim))) {
+ time_out--;
+ if (time_out == 0) {
+ debug("DSI Master is not stop state.\n");
+ debug("Check initialization process\n");
+
+ return -EINVAL;
+ }
+ }
+
+ dsim->state = DSIM_STATE_STOP;
+
+ /* BTA sequence counters */
+ exynos_mipi_dsi_set_stop_state_counter(dsim,
+ dsim->dsim_config->stop_holding_cnt);
+ exynos_mipi_dsi_set_bta_timeout(dsim,
+ dsim->dsim_config->bta_timeout);
+ exynos_mipi_dsi_set_lpdr_timeout(dsim,
+ dsim->dsim_config->rx_timeout);
+
+ return 0;
+ default:
+ debug("DSI Master is already init.\n");
+ return 0;
+ }
+
+ return 0;
+}
+
+int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim)
+{
+ if (dsim->state == DSIM_STATE_STOP) {
+ if (dsim->e_clk_src != DSIM_EXT_CLK_BYPASS) {
+ dsim->state = DSIM_STATE_HSCLKEN;
+
+ /* set LCDC and CPU transfer mode to HS. */
+ exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0);
+ exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0);
+
+ exynos_mipi_dsi_enable_hs_clock(dsim, 1);
+
+ return 0;
+ } else
+ debug("clock source is external bypass.\n");
+ } else
+ debug("DSIM is not stop state.\n");
+
+ return 0;
+}
+
+int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim,
+ unsigned int mode)
+{
+ if (mode) {
+ if (dsim->state != DSIM_STATE_HSCLKEN) {
+ debug("HS Clock lane is not enabled.\n");
+ return -EINVAL;
+ }
+
+ exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0);
+ } else {
+ if (dsim->state == DSIM_STATE_INIT || dsim->state ==
+ DSIM_STATE_ULPS) {
+ debug("DSI Master is not STOP or HSDT state.\n");
+ return -EINVAL;
+ }
+
+ exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0);
+ }
+
+ return 0;
+}
+
+int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim)
+{
+ return _exynos_mipi_dsi_get_frame_done_status(dsim);
+}
+
+int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim)
+{
+ _exynos_mipi_dsi_clear_frame_done(dsim);
+
+ return 0;
+}
diff --git a/drivers/video/exynos/exynos_mipi_dsi_common.h b/drivers/video/exynos/exynos_mipi_dsi_common.h
new file mode 100644
index 0000000000..98eb78e5f0
--- /dev/null
+++ b/drivers/video/exynos/exynos_mipi_dsi_common.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <linux/fb.h>
+
+#ifndef _EXYNOS_MIPI_DSI_COMMON_H
+#define _EXYNOS_MIPI_DSI_COMMON_H
+
+int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id,
+ const unsigned char *data0, unsigned int data1);
+int exynos_mipi_dsi_pll_on(struct mipi_dsim_device *dsim, unsigned int enable);
+unsigned long exynos_mipi_dsi_change_pll(struct mipi_dsim_device *dsim,
+ unsigned int pre_divider, unsigned int main_divider,
+ unsigned int scaler);
+int exynos_mipi_dsi_set_clock(struct mipi_dsim_device *dsim,
+ unsigned int byte_clk_sel, unsigned int enable);
+int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim);
+int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim,
+ struct mipi_dsim_config *dsim_info);
+int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim);
+int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim);
+int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim,
+ unsigned int mode);
+int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim,
+ unsigned int enable);
+int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim);
+int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim);
+
+#endif /* _EXYNOS_MIPI_DSI_COMMON_H */
diff --git a/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c
new file mode 100644
index 0000000000..fcfdc8d120
--- /dev/null
+++ b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <asm/arch/dsim.h>
+#include <asm/arch/mipi_dsim.h>
+#include <asm/arch/power.h>
+#include <asm/arch/cpu.h>
+
+#include "exynos_mipi_dsi_lowlevel.h"
+#include "exynos_mipi_dsi_common.h"
+
+void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim)
+{
+ unsigned int reg;
+
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = readl(&mipi_dsim->swrst);
+
+ reg |= DSIM_FUNCRST;
+
+ writel(reg, &mipi_dsim->swrst);
+}
+
+void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim)
+{
+ unsigned int reg = 0;
+
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = readl(&mipi_dsim->swrst);
+
+ reg |= DSIM_SWRST;
+ reg |= DSIM_FUNCRST;
+
+ writel(reg, &mipi_dsim->swrst);
+}
+
+void exynos_mipi_dsi_sw_release(struct mipi_dsim_device *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->intsrc);
+
+ reg |= INTSRC_SWRST_RELEASE;
+
+ writel(reg, &mipi_dsim->intsrc);
+}
+
+void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim,
+ unsigned int mode, unsigned int mask)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->intmsk);
+
+ if (mask)
+ reg |= mode;
+ else
+ reg &= ~mode;
+
+ writel(reg, &mipi_dsim->intmsk);
+}
+
+void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim,
+ unsigned int cfg)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = readl(&mipi_dsim->fifoctrl);
+
+ writel(reg & ~(cfg), &mipi_dsim->fifoctrl);
+ udelay(10 * 1000);
+ reg |= cfg;
+
+ writel(reg, &mipi_dsim->fifoctrl);
+}
+
+/*
+ * this function set PLL P, M and S value in D-PHY
+ */
+void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim,
+ unsigned int value)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ writel(DSIM_AFC_CTL(value), &mipi_dsim->phyacchr);
+}
+
+void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim,
+ unsigned int width_resol, unsigned int height_resol)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ /* standby should be set after configuration so set to not ready*/
+ reg = (readl(&mipi_dsim->mdresol)) & ~(DSIM_MAIN_STAND_BY);
+ writel(reg, &mipi_dsim->mdresol);
+
+ /* reset resolution */
+ reg &= ~(DSIM_MAIN_VRESOL(0x7ff) | DSIM_MAIN_HRESOL(0x7ff));
+ reg |= DSIM_MAIN_VRESOL(height_resol) | DSIM_MAIN_HRESOL(width_resol);
+
+ reg |= DSIM_MAIN_STAND_BY;
+ writel(reg, &mipi_dsim->mdresol);
+}
+
+void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim,
+ unsigned int cmd_allow, unsigned int vfront, unsigned int vback)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = (readl(&mipi_dsim->mvporch)) &
+ ~((DSIM_CMD_ALLOW_MASK) | (DSIM_STABLE_VFP_MASK) |
+ (DSIM_MAIN_VBP_MASK));
+
+ reg |= ((cmd_allow & 0xf) << DSIM_CMD_ALLOW_SHIFT) |
+ ((vfront & 0x7ff) << DSIM_STABLE_VFP_SHIFT) |
+ ((vback & 0x7ff) << DSIM_MAIN_VBP_SHIFT);
+
+ writel(reg, &mipi_dsim->mvporch);
+}
+
+void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim,
+ unsigned int front, unsigned int back)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = (readl(&mipi_dsim->mhporch)) &
+ ~((DSIM_MAIN_HFP_MASK) | (DSIM_MAIN_HBP_MASK));
+
+ reg |= (front << DSIM_MAIN_HFP_SHIFT) | (back << DSIM_MAIN_HBP_SHIFT);
+
+ writel(reg, &mipi_dsim->mhporch);
+}
+
+void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim,
+ unsigned int vert, unsigned int hori)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = (readl(&mipi_dsim->msync)) &
+ ~((DSIM_MAIN_VSA_MASK) | (DSIM_MAIN_HSA_MASK));
+
+ reg |= ((vert & 0x3ff) << DSIM_MAIN_VSA_SHIFT) |
+ (hori << DSIM_MAIN_HSA_SHIFT);
+
+ writel(reg, &mipi_dsim->msync);
+}
+
+void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim,
+ unsigned int vert, unsigned int hori)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = (readl(&mipi_dsim->sdresol)) &
+ ~(DSIM_SUB_STANDY_MASK);
+
+ writel(reg, &mipi_dsim->sdresol);
+
+ reg &= ~(DSIM_SUB_VRESOL_MASK) | ~(DSIM_SUB_HRESOL_MASK);
+ reg |= ((vert & 0x7ff) << DSIM_SUB_VRESOL_SHIFT) |
+ ((hori & 0x7ff) << DSIM_SUB_HRESOL_SHIFT);
+ writel(reg, &mipi_dsim->sdresol);
+
+ /* DSIM STANDBY */
+ reg |= (1 << DSIM_SUB_STANDY_SHIFT);
+ writel(reg, &mipi_dsim->sdresol);
+}
+
+void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim)
+{
+ struct mipi_dsim_config *dsim_config = dsim->dsim_config;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int cfg = (readl(&mipi_dsim->config)) &
+ ~((1 << DSIM_EOT_PACKET_SHIFT) |
+ (0x1f << DSIM_HSA_MODE_SHIFT) |
+ (0x3 << DSIM_NUM_OF_DATALANE_SHIFT));
+
+ cfg |= (dsim_config->auto_flush << DSIM_AUTO_FLUSH_SHIFT) |
+ (dsim_config->eot_disable << DSIM_EOT_PACKET_SHIFT) |
+ (dsim_config->auto_vertical_cnt << DSIM_AUTO_MODE_SHIFT) |
+ (dsim_config->hse << DSIM_HSE_MODE_SHIFT) |
+ (dsim_config->hfp << DSIM_HFP_MODE_SHIFT) |
+ (dsim_config->hbp << DSIM_HBP_MODE_SHIFT) |
+ (dsim_config->hsa << DSIM_HSA_MODE_SHIFT) |
+ (dsim_config->e_no_data_lane << DSIM_NUM_OF_DATALANE_SHIFT);
+
+ writel(cfg, &mipi_dsim->config);
+}
+
+void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim,
+ struct mipi_dsim_config *dsim_config)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ u32 reg = (readl(&mipi_dsim->config)) &
+ ~((0x3 << DSIM_BURST_MODE_SHIFT) | (1 << DSIM_VIDEO_MODE_SHIFT)
+ | (0x3 << DSIM_MAINVC_SHIFT) | (0x7 << DSIM_MAINPIX_SHIFT)
+ | (0x3 << DSIM_SUBVC_SHIFT) | (0x7 << DSIM_SUBPIX_SHIFT));
+
+ if (dsim_config->e_interface == DSIM_VIDEO)
+ reg |= (1 << DSIM_VIDEO_MODE_SHIFT);
+ else if (dsim_config->e_interface == DSIM_COMMAND)
+ reg &= ~(1 << DSIM_VIDEO_MODE_SHIFT);
+ else {
+ printf("unknown lcd type.\n");
+ return;
+ }
+
+ /* main lcd */
+ reg |= ((u8) (dsim_config->e_burst_mode) & 0x3) << DSIM_BURST_MODE_SHIFT
+ | ((u8) (dsim_config->e_virtual_ch) & 0x3) << DSIM_MAINVC_SHIFT
+ | ((u8) (dsim_config->e_pixel_format) & 0x7) << DSIM_MAINPIX_SHIFT;
+
+ writel(reg, &mipi_dsim->config);
+}
+
+void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim,
+ unsigned int lane, unsigned int enable)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = readl(&mipi_dsim->config);
+
+ if (enable)
+ reg |= DSIM_LANE_ENx(lane);
+ else
+ reg &= ~DSIM_LANE_ENx(lane);
+
+ writel(reg, &mipi_dsim->config);
+}
+
+void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim,
+ unsigned int count)
+{
+ unsigned int cfg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ /* get the data lane number. */
+ cfg = DSIM_NUM_OF_DATA_LANE(count);
+
+ writel(cfg, &mipi_dsim->config);
+}
+
+void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim,
+ unsigned int enable, unsigned int afc_code)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->phyacchr);
+
+ reg = 0;
+
+ if (enable) {
+ reg |= DSIM_AFC_EN;
+ reg &= ~(0x7 << DSIM_AFC_CTL_SHIFT);
+ reg |= DSIM_AFC_CTL(afc_code);
+ } else
+ reg &= ~DSIM_AFC_EN;
+
+ writel(reg, &mipi_dsim->phyacchr);
+}
+
+void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim,
+ unsigned int enable)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->clkctrl)) &
+ ~(DSIM_PLL_BYPASS_EXTERNAL);
+
+ reg |= enable << DSIM_PLL_BYPASS_SHIFT;
+
+ writel(reg, &mipi_dsim->clkctrl);
+}
+
+void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim,
+ unsigned int freq_band)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->pllctrl)) &
+ ~(0x1f << DSIM_FREQ_BAND_SHIFT);
+
+ reg |= ((freq_band & 0x1f) << DSIM_FREQ_BAND_SHIFT);
+
+ writel(reg, &mipi_dsim->pllctrl);
+}
+
+void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim,
+ unsigned int pre_divider, unsigned int main_divider,
+ unsigned int scaler)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->pllctrl)) &
+ ~(0x7ffff << 1);
+
+ reg |= ((pre_divider & 0x3f) << DSIM_PREDIV_SHIFT) |
+ ((main_divider & 0x1ff) << DSIM_MAIN_SHIFT) |
+ ((scaler & 0x7) << DSIM_SCALER_SHIFT);
+
+ writel(reg, &mipi_dsim->pllctrl);
+}
+
+void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim,
+ unsigned int lock_time)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ writel(lock_time, &mipi_dsim->plltmr);
+}
+
+void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim,
+ unsigned int enable)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->pllctrl)) &
+ ~(0x1 << DSIM_PLL_EN_SHIFT);
+
+ reg |= ((enable & 0x1) << DSIM_PLL_EN_SHIFT);
+
+ writel(reg, &mipi_dsim->pllctrl);
+}
+
+void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim,
+ unsigned int src)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->clkctrl)) &
+ ~(0x3 << DSIM_BYTE_CLK_SRC_SHIFT);
+
+ reg |= ((unsigned int) src) << DSIM_BYTE_CLK_SRC_SHIFT;
+
+ writel(reg, &mipi_dsim->clkctrl);
+}
+
+void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim,
+ unsigned int enable)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->clkctrl)) &
+ ~(1 << DSIM_BYTE_CLKEN_SHIFT);
+
+ reg |= enable << DSIM_BYTE_CLKEN_SHIFT;
+
+ writel(reg, &mipi_dsim->clkctrl);
+}
+
+void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim,
+ unsigned int enable, unsigned int prs_val)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->clkctrl)) &
+ ~((1 << DSIM_ESC_CLKEN_SHIFT) | (0xffff));
+
+ reg |= enable << DSIM_ESC_CLKEN_SHIFT;
+ if (enable)
+ reg |= prs_val;
+
+ writel(reg, &mipi_dsim->clkctrl);
+}
+
+void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim,
+ unsigned int lane_sel, unsigned int enable)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->clkctrl);
+
+ if (enable)
+ reg |= DSIM_LANE_ESC_CLKEN(lane_sel);
+ else
+ reg &= ~DSIM_LANE_ESC_CLKEN(lane_sel);
+
+ writel(reg, &mipi_dsim->clkctrl);
+}
+
+void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim,
+ unsigned int enable)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->escmode)) &
+ ~(0x1 << DSIM_FORCE_STOP_STATE_SHIFT);
+
+ reg |= ((enable & 0x1) << DSIM_FORCE_STOP_STATE_SHIFT);
+
+ writel(reg, &mipi_dsim->escmode);
+}
+
+unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->status);
+
+ /**
+ * check clock and data lane states.
+ * if MIPI-DSI controller was enabled at bootloader then
+ * TX_READY_HS_CLK is enabled otherwise STOP_STATE_CLK.
+ * so it should be checked for two case.
+ */
+ if ((reg & DSIM_STOP_STATE_DAT(0xf)) &&
+ ((reg & DSIM_STOP_STATE_CLK) ||
+ (reg & DSIM_TX_READY_HS_CLK)))
+ return 1;
+ else
+ return 0;
+}
+
+void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim,
+ unsigned int cnt_val)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->escmode)) &
+ ~(0x7ff << DSIM_STOP_STATE_CNT_SHIFT);
+
+ reg |= ((cnt_val & 0x7ff) << DSIM_STOP_STATE_CNT_SHIFT);
+
+ writel(reg, &mipi_dsim->escmode);
+}
+
+void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim,
+ unsigned int timeout)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->timeout)) &
+ ~(0xff << DSIM_BTA_TOUT_SHIFT);
+
+ reg |= (timeout << DSIM_BTA_TOUT_SHIFT);
+
+ writel(reg, &mipi_dsim->timeout);
+}
+
+void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim,
+ unsigned int timeout)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->timeout)) &
+ ~(0xffff << DSIM_LPDR_TOUT_SHIFT);
+
+ reg |= (timeout << DSIM_LPDR_TOUT_SHIFT);
+
+ writel(reg, &mipi_dsim->timeout);
+}
+
+void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim,
+ unsigned int lp)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->escmode);
+
+ reg &= ~DSIM_CMD_LPDT_LP;
+
+ if (lp)
+ reg |= DSIM_CMD_LPDT_LP;
+
+ writel(reg, &mipi_dsim->escmode);
+}
+
+void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim,
+ unsigned int lp)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->escmode);
+
+ reg &= ~DSIM_TX_LPDT_LP;
+
+ if (lp)
+ reg |= DSIM_TX_LPDT_LP;
+
+ writel(reg, &mipi_dsim->escmode);
+}
+
+void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim,
+ unsigned int enable)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->clkctrl)) &
+ ~(1 << DSIM_TX_REQUEST_HSCLK_SHIFT);
+
+ reg |= enable << DSIM_TX_REQUEST_HSCLK_SHIFT;
+
+ writel(reg, &mipi_dsim->clkctrl);
+}
+
+void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim,
+ unsigned int swap_en)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->phyacchr1);
+
+ reg &= ~(0x3 << DSIM_DPDN_SWAP_DATA_SHIFT);
+ reg |= (swap_en & 0x3) << DSIM_DPDN_SWAP_DATA_SHIFT;
+
+ writel(reg, &mipi_dsim->phyacchr1);
+}
+
+void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim,
+ unsigned int hs_zero)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->pllctrl)) &
+ ~(0xf << DSIM_ZEROCTRL_SHIFT);
+
+ reg |= ((hs_zero & 0xf) << DSIM_ZEROCTRL_SHIFT);
+
+ writel(reg, &mipi_dsim->pllctrl);
+}
+
+void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, unsigned int prep)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (readl(&mipi_dsim->pllctrl)) &
+ ~(0x7 << DSIM_PRECTRL_SHIFT);
+
+ reg |= ((prep & 0x7) << DSIM_PRECTRL_SHIFT);
+
+ writel(reg, &mipi_dsim->pllctrl);
+}
+
+void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->intsrc);
+
+ reg |= INTSRC_PLL_STABLE;
+
+ writel(reg, &mipi_dsim->intsrc);
+}
+
+void exynos_mipi_dsi_clear_all_interrupt(struct mipi_dsim_device *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ writel(0xffffffff, &mipi_dsim->intsrc);
+}
+
+unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim)
+{
+ unsigned int reg;
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ reg = readl(&mipi_dsim->status);
+
+ return reg & DSIM_PLL_STABLE ? 1 : 0;
+}
+
+unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ return readl(&mipi_dsim->fifoctrl) & ~(0x1f);
+}
+
+void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim,
+ unsigned int di, const unsigned char data0, const unsigned char data1)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = (DSIM_PKTHDR_DAT1(data1) | DSIM_PKTHDR_DAT0(data0) |
+ DSIM_PKTHDR_DI(di));
+
+ writel(reg, &mipi_dsim->pkthdr);
+}
+
+unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device
+ *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->intsrc);
+
+ return (reg & INTSRC_FRAME_DONE) ? 1 : 0;
+}
+
+void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+ unsigned int reg = readl(&mipi_dsim->intsrc);
+
+ writel(reg | INTSRC_FRAME_DONE, &mipi_dsim->intsrc);
+}
+
+void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim,
+ unsigned int tx_data)
+{
+ struct exynos_mipi_dsim *mipi_dsim =
+ (struct exynos_mipi_dsim *)samsung_get_base_mipi_dsim();
+
+ writel(tx_data, &mipi_dsim->payload);
+}
diff --git a/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h
new file mode 100644
index 0000000000..0bede25e4d
--- /dev/null
+++ b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Author: InKi Dae <inki.dae@samsung.com>
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef _EXYNOS_MIPI_DSI_LOWLEVEL_H
+#define _EXYNOS_MIPI_DSI_LOWLEVEL_H
+
+void exynos_mipi_dsi_register(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_sw_release(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim,
+ unsigned int mode, unsigned int mask);
+void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim,
+ unsigned int count);
+void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim,
+ unsigned int cfg);
+void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim,
+ unsigned int value);
+void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim,
+ unsigned int value);
+void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim,
+ unsigned int width_resol, unsigned int height_resol);
+void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim,
+ unsigned int cmd_allow, unsigned int vfront, unsigned int vback);
+void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim,
+ unsigned int front, unsigned int back);
+void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim,
+ unsigned int vert, unsigned int hori);
+void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim,
+ unsigned int vert, unsigned int hori);
+void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim,
+ struct mipi_dsim_config *dsim_config);
+void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim,
+ unsigned int count);
+void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim,
+ unsigned int lane, unsigned int enable);
+void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim,
+ unsigned int enable, unsigned int afc_code);
+void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim,
+ unsigned int enable);
+void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim,
+ unsigned int freq_band);
+void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim,
+ unsigned int pre_divider, unsigned int main_divider,
+ unsigned int scaler);
+void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim,
+ unsigned int lock_time);
+void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim,
+ unsigned int enable);
+void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim,
+ unsigned int src);
+void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim,
+ unsigned int enable);
+void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim,
+ unsigned int enable, unsigned int prs_val);
+void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim,
+ unsigned int lane_sel, unsigned int enable);
+void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim,
+ unsigned int enable);
+unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim,
+ unsigned int cnt_val);
+void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim,
+ unsigned int timeout);
+void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim,
+ unsigned int timeout);
+void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim,
+ unsigned int lp);
+void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim,
+ unsigned int lp);
+void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim,
+ unsigned int enable);
+void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim,
+ unsigned int swap_en);
+void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim,
+ unsigned int hs_zero);
+void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim,
+ unsigned int prep);
+void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_clear_all_interrupt(struct mipi_dsim_device *dsim);
+unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim);
+unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim);
+unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device
+ *dsim);
+void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim);
+void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim,
+ unsigned int di, const unsigned char data0, const unsigned char data1);
+void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim,
+ unsigned int tx_data);
+
+#endif /* _EXYNOS_MIPI_DSI_LOWLEVEL_H */
diff --git a/drivers/video/exynos/exynos_pwm_bl.c b/drivers/video/exynos/exynos_pwm_bl.c
new file mode 100644
index 0000000000..a6890daf20
--- /dev/null
+++ b/drivers/video/exynos/exynos_pwm_bl.c
@@ -0,0 +1,45 @@
+/*
+ * PWM BACKLIGHT driver for Board based on EXYNOS.
+ *
+ * Author: Donghwa Lee <dh09.lee@samsung.com>
+ *
+ * Derived from linux/drivers/video/backlight/pwm_backlight.c
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <pwm.h>
+#include <linux/types.h>
+#include <asm/io.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/pwm.h>
+#include <asm/arch/pwm_backlight.h>
+
+static struct pwm_backlight_data *pwm;
+
+static int exynos_pwm_backlight_update_status(void)
+{
+ int brightness = pwm->brightness;
+ int max = pwm->max_brightness;
+
+ if (brightness == 0) {
+ pwm_config(pwm->pwm_id, 0, pwm->period);
+ pwm_disable(pwm->pwm_id);
+ } else {
+ pwm_config(pwm->pwm_id,
+ brightness * pwm->period / max, pwm->period);
+ pwm_enable(pwm->pwm_id);
+ }
+ return 0;
+}
+
+int exynos_pwm_backlight_init(struct pwm_backlight_data *pd)
+{
+ pwm = pd;
+
+ exynos_pwm_backlight_update_status();
+
+ return 0;
+}
OpenPOWER on IntegriCloud