summaryrefslogtreecommitdiff
path: root/src/cpu/samsung/exynos5-common/exynos-fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cpu/samsung/exynos5-common/exynos-fb.c')
-rw-r--r--src/cpu/samsung/exynos5-common/exynos-fb.c594
1 files changed, 594 insertions, 0 deletions
diff --git a/src/cpu/samsung/exynos5-common/exynos-fb.c b/src/cpu/samsung/exynos5-common/exynos-fb.c
new file mode 100644
index 0000000000..30d0767923
--- /dev/null
+++ b/src/cpu/samsung/exynos5-common/exynos-fb.c
@@ -0,0 +1,594 @@
+/*
+ * LCD driver for Exynos
+ *
+ * Copyright 2013 Google Inc.
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <arch/io.h>
+#include <stdlib.h>
+#include <string.h>
+#include <console/console.h>
+#include <cpu/samsung/exynos5250/cpu.h>
+#include <cpu/samsung/exynos5250/power.h>
+#include <cpu/samsung/exynos5250/sysreg.h>
+#include <drivers/maxim/max77686/max77686.h>
+
+#include "device/i2c.h"
+#include "cpu/samsung/exynos5-common/i2c.h"
+#include "cpu/samsung/exynos5250/dsim.h"
+#include "cpu/samsung/exynos5250/fimd.h"
+
+#include "cpu/samsung/exynos5250/s5p-dp.h"
+#include "s5p-dp-core.h"
+
+/* To help debug any init errors here, define a list of possible errors */
+enum {
+ ERR_PLL_NOT_UNLOCKED = 2,
+ ERR_VIDEO_CLOCK_BAD,
+ ERR_VIDEO_STREAM_BAD,
+ ERR_DPCD_READ_ERROR1, /* 5 */
+
+ ERR_DPCD_WRITE_ERROR1,
+ ERR_DPCD_READ_ERROR2,
+ ERR_DPCD_WRITE_ERROR2,
+ ERR_INVALID_LANE,
+ ERR_PLL_NOT_LOCKED, /* 10 */
+
+ ERR_PRE_EMPHASIS_LEVELS,
+ ERR_LINK_RATE_ABNORMAL,
+ ERR_MAX_LANE_COUNT_ABNORMAL,
+ ERR_LINK_TRAINING_FAILURE,
+ ERR_MISSING_DP_BASE, /* 15 */
+
+ ERR_NO_FDT_NODE,
+};
+/* ok, this is stupid, but we're going to leave the variables in here until we know it works.
+ * one cleanup task at a time.
+ */
+enum stage_t {
+ STAGE_START = 0,
+ STAGE_LCD_VDD,
+ STAGE_BRIDGE_SETUP,
+ STAGE_BRIDGE_INIT,
+ STAGE_BRIDGE_RESET,
+ STAGE_HOTPLUG,
+ STAGE_DP_CONTROLLER,
+ STAGE_BACKLIGHT_VDD,
+ STAGE_BACKLIGHT_PWM,
+ STAGE_BACKLIGHT_EN,
+ STAGE_DONE,
+};
+
+int lcd_line_length;
+int lcd_color_fg;
+int lcd_color_bg;
+
+void *lcd_base; /* Start of framebuffer memory */
+void *lcd_console_address; /* Start of console buffer */
+
+short console_col;
+short console_row;
+
+
+#ifdef CONFIG_EXYNOS_DISPLAYPORT
+static struct s5p_dp_device dp_device;
+
+#endif
+
+/* Bypass FIMD of DISP1_BLK */
+static void fimd_bypass(void)
+{
+ struct exynos5_sysreg *sysreg = samsung_get_base_sysreg();
+
+ /*setbits_le32(&sysreg->disp1blk_cfg, FIMDBYPASS_DISP1);*/
+ sysreg->disp1blk_cfg &= ~FIMDBYPASS_DISP1;
+}
+
+/* Calculate the size of Framebuffer from the resolution */
+static u32 calc_fbsize(vidinfo_t *panel_info)
+{
+ /* They had PAGE_SIZE here instead of 4096.
+ * but that's a totally arbitrary number -- everything nowadays
+ * has lots of page sizes.
+ * So keep it obvious.
+ */
+ return ALIGN((panel_info->vl_col * panel_info->vl_row *
+ ((1<<panel_info->vl_bpix) / 8)), 4096);
+}
+
+/*
+ * Initialize display controller.
+ *
+ * @param lcdbase pointer to the base address of framebuffer.
+ * @pd pointer to the main panel_data structure
+ */
+void fb_init(vidinfo_t *panel_info, void *lcdbase, struct exynos5_fimd_panel *pd)
+{
+ unsigned int val;
+ u32 fbsize;
+ struct exynos5_fimd *fimd =
+ samsung_get_base_fimd();
+ struct exynos5_disp_ctrl *disp_ctrl =
+ samsung_get_base_disp_ctrl();
+
+ writel(pd->ivclk | pd->fixvclk, &disp_ctrl->vidcon1);
+ val = ENVID_ON | ENVID_F_ON | (pd->clkval_f << CLKVAL_F_OFFSET);
+ writel(val, &fimd->vidcon0);
+
+ val = (pd->vsync << VSYNC_PULSE_WIDTH_OFFSET) |
+ (pd->lower_margin << V_FRONT_PORCH_OFFSET) |
+ (pd->upper_margin << V_BACK_PORCH_OFFSET);
+ writel(val, &disp_ctrl->vidtcon0);
+
+ val = (pd->hsync << HSYNC_PULSE_WIDTH_OFFSET) |
+ (pd->right_margin << H_FRONT_PORCH_OFFSET) |
+ (pd->left_margin << H_BACK_PORCH_OFFSET);
+ writel(val, &disp_ctrl->vidtcon1);
+
+ val = ((pd->xres - 1) << HOZVAL_OFFSET) |
+ ((pd->yres - 1) << LINEVAL_OFFSET);
+ writel(val, &disp_ctrl->vidtcon2);
+
+ writel((unsigned int)lcd_base, &fimd->vidw00add0b0);
+
+ fbsize = calc_fbsize(panel_info);
+ writel((unsigned int)lcd_base + fbsize, &fimd->vidw00add1b0);
+
+ writel(pd->xres * 2, &fimd->vidw00add2);
+
+ val = ((pd->xres - 1) << OSD_RIGHTBOTX_F_OFFSET);
+ val |= ((pd->yres - 1) << OSD_RIGHTBOTY_F_OFFSET);
+ writel(val, &fimd->vidosd0b);
+ writel(pd->xres * pd->yres, &fimd->vidosd0c);
+
+ setbits_le32(&fimd->shadowcon, CHANNEL0_EN);
+
+ val = BPPMODE_F_RGB_16BIT_565 << BPPMODE_F_OFFSET;
+ val |= ENWIN_F_ENABLE | HALF_WORD_SWAP_EN;
+ writel(val, &fimd->wincon0);
+
+ /* DPCLKCON_ENABLE */
+ writel(1 << 1, &fimd->dpclkcon);
+}
+
+void exynos_fimd_disable(void);
+void exynos_fimd_disable(void)
+{
+ struct exynos5_fimd *fimd = samsung_get_base_fimd();
+
+ writel(0, &fimd->wincon0);
+ clrbits_le32(&fimd->shadowcon, CHANNEL0_EN);
+}
+
+/*
+ * Configure DP in slave mode and wait for video stream.
+ *
+ * param dp pointer to main s5p-dp structure
+ * param video_info pointer to main video_info structure.
+ * return status
+ */
+static int s5p_dp_config_video(struct s5p_dp_device *dp,
+ struct video_info *video_info)
+{
+ int timeout = 0;
+ u32 start;
+ struct exynos5_dp *base = dp->base;
+
+ s5p_dp_config_video_slave_mode(dp, video_info);
+
+ s5p_dp_set_video_color_format(dp, video_info->color_depth,
+ video_info->color_space,
+ video_info->dynamic_range,
+ video_info->ycbcr_coeff);
+
+ if (s5p_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) {
+ debug("PLL is not locked yet.\n");
+ return -ERR_PLL_NOT_UNLOCKED;
+ }
+
+ start = get_timer(0);
+ do {
+ if (s5p_dp_is_slave_video_stream_clock_on(dp) == 0) {
+ timeout++;
+ break;
+ }
+ } while (get_timer(start) <= STREAM_ON_TIMEOUT);
+
+ if (!timeout) {
+ debug("Video Clock Not ok\n");
+ return -ERR_VIDEO_CLOCK_BAD;
+ }
+
+ /* Set to use the register calculated M/N video */
+ s5p_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0);
+
+ clrbits_le32(&base->video_ctl_10, FORMAT_SEL);
+
+ /* Disable video mute */
+ clrbits_le32(&base->video_ctl_1, HDCP_VIDEO_MUTE);
+
+ /* Configure video slave mode */
+ s5p_dp_enable_video_master(dp);
+
+ /* Enable video */
+ setbits_le32(&base->video_ctl_1, VIDEO_EN);
+ timeout = s5p_dp_is_video_stream_on(dp);
+
+ if (timeout) {
+ debug("Video Stream Not on\n");
+ return -ERR_VIDEO_STREAM_BAD;
+ }
+
+ return 0;
+}
+
+/*
+ * Set DP to enhanced mode. We use this for EVT1
+ * param dp pointer to main s5p-dp structure
+ * return status
+ */
+static int s5p_dp_enable_rx_to_enhanced_mode(struct s5p_dp_device *dp)
+{
+ u8 data;
+
+ if (s5p_dp_read_byte_from_dpcd(dp, DPCD_ADDR_LANE_COUNT_SET, &data)) {
+ debug("DPCD read error\n");
+ return -ERR_DPCD_READ_ERROR1;
+ }
+
+ if (s5p_dp_write_byte_to_dpcd(dp, DPCD_ADDR_LANE_COUNT_SET,
+ DPCD_ENHANCED_FRAME_EN |
+ (data & DPCD_LANE_COUNT_SET_MASK))) {
+ debug("DPCD write error\n");
+ return -ERR_DPCD_WRITE_ERROR1;
+ }
+
+ return 0;
+}
+
+/*
+ * Enable scrambles mode. We use this for EVT1
+ * param dp pointer to main s5p-dp structure
+ * return status
+ */
+static int s5p_dp_enable_scramble(struct s5p_dp_device *dp)
+{
+ u8 data;
+ struct exynos5_dp *base = dp->base;
+
+ clrbits_le32(&base->dp_training_ptn_set, SCRAMBLING_DISABLE);
+
+ if (s5p_dp_read_byte_from_dpcd(dp, DPCD_ADDR_TRAINING_PATTERN_SET,
+ &data)) {
+ debug("DPCD read error\n");
+ return -ERR_DPCD_READ_ERROR2;
+ }
+
+ if (s5p_dp_write_byte_to_dpcd(dp, DPCD_ADDR_TRAINING_PATTERN_SET,
+ (u8)(data & ~DPCD_SCRAMBLING_DISABLED))) {
+ debug("DPCD write error\n");
+ return -ERR_DPCD_WRITE_ERROR2;
+ }
+
+ return 0;
+}
+
+/*
+ * Reset DP and prepare DP for init training
+ * param dp pointer to main s5p-dp structure
+ */
+static int s5p_dp_init_dp(struct s5p_dp_device *dp)
+{
+ int ret, i;
+ struct exynos5_dp *base = dp->base;
+
+ for (i = 0; i < DP_INIT_TRIES; i++) {
+ s5p_dp_reset(dp);
+
+ /* SW defined function Normal operation */
+ clrbits_le32(&base->func_en_1, SW_FUNC_EN_N);
+
+ ret = s5p_dp_init_analog_func(dp);
+ if (!ret)
+ break;
+
+ udelay(5000);
+ debug("LCD retry init, attempt=%d ret=%d\n", i, ret);
+ }
+ if (i == DP_INIT_TRIES) {
+ debug("LCD initialization failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ s5p_dp_init_aux(dp);
+
+ return ret;
+}
+
+/*
+ * Set pre-emphasis level
+ * param dp pointer to main s5p-dp structure
+ * param pre_emphasis pre-emphasis level
+ * param lane lane number(0 - 3)
+ * return status
+ */
+static int s5p_dp_set_lane_lane_pre_emphasis(struct s5p_dp_device *dp,
+ int pre_emphasis, int lane)
+{
+ u32 reg;
+ struct exynos5_dp *base = dp->base;
+
+ reg = pre_emphasis << PRE_EMPHASIS_SET_SHIFT;
+ switch (lane) {
+ case 0:
+ writel(reg, &base->ln0_link_trn_ctl);
+ break;
+ case 1:
+ writel(reg, &base->ln1_link_trn_ctl);
+ break;
+
+ case 2:
+ writel(reg, &base->ln2_link_trn_ctl);
+ break;
+
+ case 3:
+ writel(reg, &base->ln3_link_trn_ctl);
+ break;
+ default:
+ debug("%s: Invalid lane %d\n", __func__, lane);
+ return -ERR_INVALID_LANE;
+ }
+ return 0;
+}
+
+/*
+ * Read supported bandwidth type
+ * param dp pointer to main s5p-dp structure
+ * param bandwidth pointer to variable holding bandwidth type
+ */
+static void s5p_dp_get_max_rx_bandwidth(struct s5p_dp_device *dp,
+ u8 *bandwidth)
+{
+ u8 data;
+
+ /*
+ * For DP rev.1.1, Maximum link rate of Main Link lanes
+ * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps
+ */
+ s5p_dp_read_byte_from_dpcd(dp, DPCD_ADDR_MAX_LINK_RATE, &data);
+ *bandwidth = data;
+}
+
+/*
+ * Reset DP and prepare DP for init training
+ * param dp pointer to main s5p-dp structure
+ * param lane_count pointer to variable holding no of lanes
+ */
+static void s5p_dp_get_max_rx_lane_count(struct s5p_dp_device *dp,
+ u8 *lane_count)
+{
+ u8 data;
+
+ /*
+ * For DP rev.1.1, Maximum number of Main Link lanes
+ * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes
+ */
+ s5p_dp_read_byte_from_dpcd(dp, DPCD_ADDR_MAX_LANE_COUNT, &data);
+ *lane_count = data & DPCD_MAX_LANE_COUNT_MASK;
+}
+
+/*
+ * DP H/w Link Training. Set DPCD link rate and bandwidth.
+ * param dp pointer to main s5p-dp structure
+ * param max_lane No of lanes
+ * param max_rate bandwidth
+ * return status
+ */
+static int s5p_dp_hw_link_training(struct s5p_dp_device *dp,
+ unsigned int max_lane,
+ unsigned int max_rate)
+{
+ u32 data;
+ u32 start;
+ int lane;
+ struct exynos5_dp *base = dp->base;
+
+ /* Stop Video */
+ clrbits_le32(&base->video_ctl_1, VIDEO_EN);
+
+ start = get_timer(0);
+ while (s5p_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) {
+ if (get_timer(start) > PLL_LOCK_TIMEOUT) {
+ /* Ignore this error, and try to continue */
+ printk(BIOS_ERR, "PLL is not locked yet.\n");
+ break;
+ }
+ }
+
+ /* Reset Macro */
+ setbits_le32(&base->dp_phy_test, MACRO_RST);
+
+ /* 10 us is the minimum reset time. */
+ udelay(10);
+
+ clrbits_le32(&base->dp_phy_test, MACRO_RST);
+
+ /* Set TX pre-emphasis to minimum */
+ for (lane = 0; lane < max_lane; lane++)
+ if (s5p_dp_set_lane_lane_pre_emphasis(dp,
+ PRE_EMPHASIS_LEVEL_0, lane)) {
+ debug("Unable to set pre emphasis level\n");
+ return -ERR_PRE_EMPHASIS_LEVELS;
+ }
+
+ /* All DP analog module power up */
+ writel(0x00, &base->dp_phy_pd);
+
+ /* Initialize by reading RX's DPCD */
+ s5p_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate);
+ s5p_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count);
+
+ if ((dp->link_train.link_rate != LINK_RATE_1_62GBPS) &&
+ (dp->link_train.link_rate != LINK_RATE_2_70GBPS)) {
+ debug("Rx Max Link Rate is abnormal :%x !\n",
+ dp->link_train.link_rate);
+ /* Not Retrying */
+ return -ERR_LINK_RATE_ABNORMAL;
+ }
+
+ if (dp->link_train.lane_count == 0) {
+ debug("Rx Max Lane count is abnormal :%x !\n",
+ dp->link_train.lane_count);
+ /* Not retrying */
+ return -ERR_MAX_LANE_COUNT_ABNORMAL;
+ }
+
+ /* Setup TX lane count & rate */
+ if (dp->link_train.lane_count > max_lane)
+ dp->link_train.lane_count = max_lane;
+ if (dp->link_train.link_rate > max_rate)
+ dp->link_train.link_rate = max_rate;
+
+ /* Set link rate and count as you want to establish*/
+ writel(dp->link_train.lane_count, &base->lane_count_set);
+ writel(dp->link_train.link_rate, &base->link_bw_set);
+
+ /* Set sink to D0 (Sink Not Ready) mode. */
+ s5p_dp_write_byte_to_dpcd(dp, DPCD_ADDR_SINK_POWER_STATE,
+ DPCD_SET_POWER_STATE_D0);
+
+ /* Start HW link training */
+ writel(HW_TRAINING_EN, &base->dp_hw_link_training);
+
+ /* Wait unitl HW link training done */
+ s5p_dp_wait_hw_link_training_done(dp);
+
+ /* Get hardware link training status */
+ data = readl(&base->dp_hw_link_training);
+ if (data != 0) {
+ debug(" H/W link training failure: 0x%x\n", data);
+ return -ERR_LINK_TRAINING_FAILURE;
+ }
+
+ /* Get Link Bandwidth */
+ data = readl(&base->link_bw_set);
+
+ dp->link_train.link_rate = data;
+
+ data = readl(&base->lane_count_set);
+ dp->link_train.lane_count = data;
+
+ return 0;
+}
+
+/*
+ * Initialize DP display
+ */
+int dp_controller_init(struct s5p_dp_device *dp_device, unsigned *wait_ms)
+{
+ int ret;
+ struct s5p_dp_device *dp = dp_device;
+ struct exynos5_dp *base;
+
+ //dp->base = (struct exynos5_dp *)addr;
+ /* yes. we're a snow. Yet somehow our config is from a development kit?
+ * This Must Change */
+ //dp->video_info = &smdk5250_dp_config;
+
+ clock_init_dp_clock();
+
+ power_enable_dp_phy();
+ ret = s5p_dp_init_dp(dp);
+ if (ret) {
+ debug("%s: Could not initialize dp\n", __func__);
+ return ret;
+ }
+
+ ret = s5p_dp_hw_link_training(dp, dp->video_info->lane_count,
+ dp->video_info->link_rate);
+ if (ret) {
+ debug("unable to do link train\n");
+ return ret;
+ }
+ /* Minimum delay after H/w Link training */
+ udelay(1000);
+
+ ret = s5p_dp_enable_scramble(dp);
+ if (ret) {
+ debug("unable to set scramble mode\n");
+ return ret;
+ }
+
+ ret = s5p_dp_enable_rx_to_enhanced_mode(dp);
+ if (ret) {
+ debug("unable to set enhanced mode\n");
+ return ret;
+ }
+
+
+ base = dp->base;
+ /* Enable enhanced mode */
+ setbits_le32(&base->sys_ctl_4, ENHANCED);
+
+ writel(dp->link_train.lane_count, &base->lane_count_set);
+ writel(dp->link_train.link_rate, &base->link_bw_set);
+
+ s5p_dp_init_video(dp);
+ ret = s5p_dp_config_video(dp, dp->video_info);
+ if (ret) {
+ debug("unable to config video\n");
+ return ret;
+ }
+
+ /*
+ * This delay is T3 in the LCD timing spec (defined as >200ms). We set
+ * this down to 60ms since that's the approximate maximum amount of time
+ * it'll take a bridge to start outputting LVDS data. The delay of
+ * >200ms is just a conservative value to avoid turning on the backlight
+ * when there's random LCD data on the screen. Shaving 140ms off the
+ * boot is an acceptable trade-off.
+ */
+ *wait_ms = 60;
+ return 0;
+}
+
+/**
+ * Init the LCD controller
+ *
+ * @param lcdbase Base address of LCD frame buffer
+ * @return 0 if ok, -ve error code on error
+ */
+int lcd_ctrl_init(vidinfo_t *panel_info, struct exynos5_fimd_panel *panel_data, void *lcdbase)
+{
+ int ret = 0;
+
+ //vi->res = panel_info->vl_col;
+ //vi->yres = panel_info->vl_row;
+
+ fimd_bypass();
+ fb_init(panel_info, lcdbase, panel_data);
+
+ /* Enable flushing after LCD writes if requested */
+ // forget it. lcd_set_flush_dcache(1);
+ return ret;
+}