diff options
Diffstat (limited to 'drivers/video/tegra')
-rw-r--r-- | drivers/video/tegra/Kconfig | 51 | ||||
-rw-r--r-- | drivers/video/tegra/Makefile | 9 | ||||
-rw-r--r-- | drivers/video/tegra/TODO | 5 | ||||
-rw-r--r-- | drivers/video/tegra/dc-pwm-backlight.c | 150 | ||||
-rw-r--r-- | drivers/video/tegra/dc.c | 682 | ||||
-rw-r--r-- | drivers/video/tegra/dc.h | 40 | ||||
-rw-r--r-- | drivers/video/tegra/dsi.c | 1148 | ||||
-rw-r--r-- | drivers/video/tegra/dsi.h | 246 | ||||
-rw-r--r-- | drivers/video/tegra/hdmi.c | 623 | ||||
-rw-r--r-- | drivers/video/tegra/hdmi.h | 648 | ||||
-rw-r--r-- | drivers/video/tegra/host1x.c | 86 | ||||
-rw-r--r-- | drivers/video/tegra/mipi-phy.c | 133 | ||||
-rw-r--r-- | drivers/video/tegra/mipi-phy.h | 48 | ||||
-rw-r--r-- | drivers/video/tegra/mipi.c | 294 | ||||
-rw-r--r-- | drivers/video/tegra/tegra124/Makefile | 8 | ||||
-rw-r--r-- | drivers/video/tegra/tegra124/display.c | 504 | ||||
-rw-r--r-- | drivers/video/tegra/tegra124/displayport.h | 411 | ||||
-rw-r--r-- | drivers/video/tegra/tegra124/dp.c | 1624 | ||||
-rw-r--r-- | drivers/video/tegra/tegra124/sor.c | 1083 | ||||
-rw-r--r-- | drivers/video/tegra/tegra124/sor.h | 914 |
20 files changed, 8707 insertions, 0 deletions
diff --git a/drivers/video/tegra/Kconfig b/drivers/video/tegra/Kconfig new file mode 100644 index 00000000000..d3b8dbb2826 --- /dev/null +++ b/drivers/video/tegra/Kconfig @@ -0,0 +1,51 @@ +config HOST1X_TEGRA + bool "NVIDIA Tegra host1x BUS support" + depends on SIMPLE_BUS + +config VIDEO_TEGRA + bool "Enable Display Controller support on Tegra devices" + depends on OF_CONTROL + select HOST1X_TEGRA + help + Enable support for Display Controller found in Tegra SoC. The + Display Controller Complex integrates two independent display + controllers. Each display controller is capable of interfacing + to an external display device, which can be a parallel interface + or SPI LCD, DVI, an HDMI HDTV, RGB monitor or a MIPI DSI LCD. + Direct interface is supported directly to most LCD displays with + TFT or TFT-like interface. + +config VIDEO_DSI_TEGRA + bool "Enable DSI controller support on Tegra devices" + depends on VIDEO_BRIDGE && PANEL && DM_GPIO + select VIDEO_TEGRA + select VIDEO_MIPI_DSI + help + Enable support for the Display Serial Interface (DSI) found in + Tegra SoC. It is a MIPI standard serial bitstream, intended to + provide a low pin count interface to a display panel. + +config VIDEO_HDMI_TEGRA + bool "Enable HDMI support on Tegra devices" + depends on VIDEO_BRIDGE && DM_I2C + select I2C_EDID + select VIDEO_TEGRA + help + Enable support for the High-Definition Multimedia Interface (HDMI) + found in Tegra SoC. + +config TEGRA_BACKLIGHT_PWM + bool "Enable Tegra DC PWM backlight support" + depends on BACKLIGHT + select VIDEO_TEGRA + help + Enable support for the Display Controller dependent PWM backlight + found in the Tegra SoC and usually used with DSI panels. + +config VIDEO_TEGRA124 + bool "Enable video support on Tegra124" + help + Tegra124 supports many video output options including eDP and + HDMI. At present only eDP is supported by U-Boot. This option + enables this support which can be used on devices which + have an eDP display connected. diff --git a/drivers/video/tegra/Makefile b/drivers/video/tegra/Makefile new file mode 100644 index 00000000000..3c50a0ba3c3 --- /dev/null +++ b/drivers/video/tegra/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_HOST1X_TEGRA) += host1x.o +obj-$(CONFIG_VIDEO_TEGRA) += dc.o +obj-$(CONFIG_VIDEO_DSI_TEGRA) += dsi.o mipi.o mipi-phy.o +obj-$(CONFIG_VIDEO_HDMI_TEGRA) += hdmi.o +obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += dc-pwm-backlight.o + +obj-${CONFIG_VIDEO_TEGRA124} += tegra124/ diff --git a/drivers/video/tegra/TODO b/drivers/video/tegra/TODO new file mode 100644 index 00000000000..1c8c2389a18 --- /dev/null +++ b/drivers/video/tegra/TODO @@ -0,0 +1,5 @@ +Existence of separate Tegra124 video implementations is not an ideal solution +since generic video setup for Tegra already has Tegra124 support of some degree. +It is not possible at the time of this note is written to integrate T124 SOR +and DP without possible regressions. Tegra124 setup for SOR and DP should be +incorporated into existing setup once such opportunity occurs. diff --git a/drivers/video/tegra/dc-pwm-backlight.c b/drivers/video/tegra/dc-pwm-backlight.c new file mode 100644 index 00000000000..eff10b5563e --- /dev/null +++ b/drivers/video/tegra/dc-pwm-backlight.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2022 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#define LOG_CATEGORY UCLASS_PANEL_BACKLIGHT + +#include <backlight.h> +#include <dm.h> +#include <i2c.h> +#include <log.h> +#include <linux/delay.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/gpio.h> + +#include "dc.h" + +#define TEGRA_PWM_BL_MIN_BRIGHTNESS 0x10 +#define TEGRA_PWM_BL_MAX_BRIGHTNESS 0xFF + +#define TEGRA_PWM_BL_PERIOD 0xFF +#define TEGRA_PWM_BL_CLK_DIV 0x14 +#define TEGRA_PWM_BL_CLK_SELECT 0x00 + +#define PM_PERIOD_SHIFT 18 +#define PM_CLK_DIVIDER_SHIFT 4 + +#define TEGRA_PWM_PM0 0 +#define TEGRA_PWM_PM1 1 + +struct tegra_pwm_backlight_priv { + struct dc_ctlr *dc; /* Display controller regmap */ + + u32 pwm_source; + u32 period; + u32 clk_div; + u32 clk_select; + u32 dft_brightness; +}; + +static int tegra_pwm_backlight_set_brightness(struct udevice *dev, int percent) +{ + struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev); + struct dc_cmd_reg *cmd = &priv->dc->cmd; + struct dc_com_reg *com = &priv->dc->com; + unsigned int ctrl; + unsigned long out_sel; + unsigned long cmd_state; + + if (percent == BACKLIGHT_DEFAULT) + percent = priv->dft_brightness; + + if (percent < TEGRA_PWM_BL_MIN_BRIGHTNESS) + percent = TEGRA_PWM_BL_MIN_BRIGHTNESS; + + if (percent > TEGRA_PWM_BL_MAX_BRIGHTNESS) + percent = TEGRA_PWM_BL_MAX_BRIGHTNESS; + + ctrl = ((priv->period << PM_PERIOD_SHIFT) | + (priv->clk_div << PM_CLK_DIVIDER_SHIFT) | + priv->clk_select); + + /* The new value should be effected immediately */ + cmd_state = readl(&cmd->state_access); + writel((cmd_state | (1 << 2)), &cmd->state_access); + + switch (priv->pwm_source) { + case TEGRA_PWM_PM0: + /* Select the LM0 on PM0 */ + out_sel = readl(&com->pin_output_sel[5]); + out_sel &= ~(7 << 0); + out_sel |= (3 << 0); + writel(out_sel, &com->pin_output_sel[5]); + writel(ctrl, &com->pm0_ctrl); + writel(percent, &com->pm0_duty_cycle); + break; + case TEGRA_PWM_PM1: + /* Select the LM1 on PM1 */ + out_sel = readl(&com->pin_output_sel[5]); + out_sel &= ~(7 << 4); + out_sel |= (3 << 4); + writel(out_sel, &com->pin_output_sel[5]); + writel(ctrl, &com->pm1_ctrl); + writel(percent, &com->pm1_duty_cycle); + break; + default: + break; + } + + writel(cmd_state, &cmd->state_access); + return 0; +} + +static int tegra_pwm_backlight_enable(struct udevice *dev) +{ + struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev); + + return tegra_pwm_backlight_set_brightness(dev, priv->dft_brightness); +} + +static int tegra_pwm_backlight_probe(struct udevice *dev) +{ + struct tegra_pwm_backlight_priv *priv = dev_get_priv(dev); + ofnode dc = ofnode_get_parent(dev_ofnode(dev)); + + priv->dc = (struct dc_ctlr *)ofnode_get_addr(dc); + if (!priv->dc) { + log_err("%s: failed to get DC controller\n", __func__); + return -EINVAL; + } + + priv->pwm_source = + dev_read_u32_default(dev, "nvidia,pwm-source", + TEGRA_PWM_PM0); + priv->period = + dev_read_u32_default(dev, "nvidia,period", + TEGRA_PWM_BL_PERIOD); + priv->clk_div = + dev_read_u32_default(dev, "nvidia,clock-div", + TEGRA_PWM_BL_CLK_DIV); + priv->clk_select = + dev_read_u32_default(dev, "nvidia,clock-select", + TEGRA_PWM_BL_CLK_SELECT); + priv->dft_brightness = + dev_read_u32_default(dev, "nvidia,default-brightness", + TEGRA_PWM_BL_MAX_BRIGHTNESS); + + return 0; +} + +static const struct backlight_ops tegra_pwm_backlight_ops = { + .enable = tegra_pwm_backlight_enable, + .set_brightness = tegra_pwm_backlight_set_brightness, +}; + +static const struct udevice_id tegra_pwm_backlight_ids[] = { + { .compatible = "nvidia,tegra-pwm-backlight" }, + { } +}; + +U_BOOT_DRIVER(tegra_pwm_backlight) = { + .name = "tegra_pwm_backlight", + .id = UCLASS_PANEL_BACKLIGHT, + .of_match = tegra_pwm_backlight_ids, + .probe = tegra_pwm_backlight_probe, + .ops = &tegra_pwm_backlight_ops, + .priv_auto = sizeof(struct tegra_pwm_backlight_priv), +}; diff --git a/drivers/video/tegra/dc.c b/drivers/video/tegra/dc.c new file mode 100644 index 00000000000..f0e3d2c993f --- /dev/null +++ b/drivers/video/tegra/dc.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * Copyright (c) 2024 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#include <backlight.h> +#include <cpu_func.h> +#include <clk.h> +#include <dm.h> +#include <dm/ofnode_graph.h> +#include <fdtdec.h> +#include <log.h> +#include <panel.h> +#include <video.h> +#include <video_bridge.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/powergate.h> + +#include "dc.h" + +/* Holder of Tegra per-SOC DC differences */ +struct tegra_dc_soc_info { + bool has_timer; + bool has_rgb; + bool has_pgate; +}; + +/* Information about the display controller */ +struct tegra_lcd_priv { + int width; /* width in pixels */ + int height; /* height in pixels */ + enum video_log2_bpp log2_bpp; /* colour depth */ + struct display_timing timing; + struct udevice *panel; /* Panels attached to RGB */ + struct udevice *bridge; /* Bridge linked with DC */ + struct dc_ctlr *dc; /* Display controller regmap */ + const struct tegra_dc_soc_info *soc; + fdt_addr_t frame_buffer; /* Address of frame buffer */ + unsigned pixel_clock; /* Pixel clock in Hz */ + struct clk *clk; + struct clk *clk_parent; + ulong scdiv; /* Clock divider used by disp_clk_ctrl */ + bool rotation; /* 180 degree panel turn */ + int pipe; /* DC controller: 0 for A, 1 for B */ +}; + +enum { + /* Maximum LCD size we support */ + LCD_MAX_WIDTH = 2560, + LCD_MAX_HEIGHT = 1600, + LCD_MAX_LOG2_BPP = VIDEO_BPP16, +}; + +static void update_window(struct tegra_lcd_priv *priv, + struct disp_ctl_win *win) +{ + struct dc_ctlr *dc = priv->dc; + unsigned h_dda, v_dda; + unsigned long val; + + val = readl(&dc->cmd.disp_win_header); + val |= WINDOW_A_SELECT; + writel(val, &dc->cmd.disp_win_header); + + writel(win->fmt, &dc->win.color_depth); + + clrsetbits_le32(&dc->win.byte_swap, BYTE_SWAP_MASK, + BYTE_SWAP_NOSWAP << BYTE_SWAP_SHIFT); + + val = win->out_x << H_POSITION_SHIFT; + val |= win->out_y << V_POSITION_SHIFT; + writel(val, &dc->win.pos); + + val = win->out_w << H_SIZE_SHIFT; + val |= win->out_h << V_SIZE_SHIFT; + writel(val, &dc->win.size); + + val = (win->w * win->bpp / 8) << H_PRESCALED_SIZE_SHIFT; + val |= win->h << V_PRESCALED_SIZE_SHIFT; + writel(val, &dc->win.prescaled_size); + + writel(0, &dc->win.h_initial_dda); + writel(0, &dc->win.v_initial_dda); + + h_dda = (win->w * 0x1000) / max(win->out_w - 1, 1U); + v_dda = (win->h * 0x1000) / max(win->out_h - 1, 1U); + + val = h_dda << H_DDA_INC_SHIFT; + val |= v_dda << V_DDA_INC_SHIFT; + writel(val, &dc->win.dda_increment); + + writel(win->stride, &dc->win.line_stride); + writel(0, &dc->win.buf_stride); + + val = WIN_ENABLE; + if (win->bpp < 24) + val |= COLOR_EXPAND; + + if (priv->rotation) + val |= H_DIRECTION | V_DIRECTION; + + writel(val, &dc->win.win_opt); + + writel((unsigned long)win->phys_addr, &dc->winbuf.start_addr); + writel(win->x, &dc->winbuf.addr_h_offset); + writel(win->y, &dc->winbuf.addr_v_offset); + + writel(0xff00, &dc->win.blend_nokey); + writel(0xff00, &dc->win.blend_1win); + + val = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + val |= GENERAL_UPDATE | WIN_A_UPDATE; + writel(val, &dc->cmd.state_ctrl); +} + +static int update_display_mode(struct tegra_lcd_priv *priv) +{ + struct dc_disp_reg *disp = &priv->dc->disp; + struct display_timing *dt = &priv->timing; + unsigned long val; + + writel(0x0, &disp->disp_timing_opt); + + writel(1 | 1 << 16, &disp->ref_to_sync); + writel(dt->hsync_len.typ | dt->vsync_len.typ << 16, &disp->sync_width); + writel(dt->hback_porch.typ | dt->vback_porch.typ << 16, + &disp->back_porch); + writel((dt->hfront_porch.typ - 1) | (dt->vfront_porch.typ - 1) << 16, + &disp->front_porch); + writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active); + + if (priv->soc->has_rgb) { + val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT; + val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT; + writel(val, &disp->data_enable_opt); + + val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT; + val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT; + val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT; + writel(val, &disp->disp_interface_ctrl); + + writel(0x00010001, &disp->shift_clk_opt); + } + + val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT; + val |= priv->scdiv << SHIFT_CLK_DIVIDER_SHIFT; + writel(val, &disp->disp_clk_ctrl); + + return 0; +} + +/* Start up the display and turn on power to PWMs */ +static void basic_init(struct dc_cmd_reg *cmd) +{ + u32 val; + + writel(0x00000100, &cmd->gen_incr_syncpt_ctrl); + writel(0x0000011a, &cmd->cont_syncpt_vsync); + writel(0x00000000, &cmd->int_type); + writel(0x00000000, &cmd->int_polarity); + writel(0x00000000, &cmd->int_mask); + writel(0x00000000, &cmd->int_enb); + + val = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE; + val |= PW3_ENABLE | PW4_ENABLE | PM0_ENABLE; + val |= PM1_ENABLE; + writel(val, &cmd->disp_pow_ctrl); + + val = readl(&cmd->disp_cmd); + val &= ~CTRL_MODE_MASK; + val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT; + writel(val, &cmd->disp_cmd); +} + +static void basic_init_timer(struct dc_disp_reg *disp) +{ + writel(0x00000020, &disp->mem_high_pri); + writel(0x00000001, &disp->mem_high_pri_timer); +} + +static const u32 rgb_enb_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_polarity_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x01000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_data_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00210222, + 0x00002200, + 0x00020000, +}; + +static void rgb_enable(struct tegra_lcd_priv *priv) +{ + struct dc_com_reg *com = &priv->dc->com; + struct display_timing *dt = &priv->timing; + u32 value; + int i; + + for (i = 0; i < PIN_REG_COUNT; i++) { + writel(rgb_enb_tab[i], &com->pin_output_enb[i]); + writel(rgb_polarity_tab[i], &com->pin_output_polarity[i]); + writel(rgb_data_tab[i], &com->pin_output_data[i]); + } + + /* configure H- and V-sync signal polarities */ + value = readl(&com->pin_output_polarity[1]); + + if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) + value |= LHS_OUTPUT_POLARITY_LOW; + else + value &= ~LHS_OUTPUT_POLARITY_LOW; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW) + value |= LVS_OUTPUT_POLARITY_LOW; + else + value &= ~LVS_OUTPUT_POLARITY_LOW; + + writel(value, &com->pin_output_polarity[1]); + + for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++) + writel(rgb_sel_tab[i], &com->pin_output_sel[i]); +} + +static int setup_window(struct tegra_lcd_priv *priv, + struct disp_ctl_win *win) +{ + if (priv->rotation) { + win->x = priv->width * 2 - 1; + win->y = priv->height - 1; + } else { + win->x = 0; + win->y = 0; + } + + win->w = priv->width; + win->h = priv->height; + win->out_x = 0; + win->out_y = 0; + win->out_w = priv->width; + win->out_h = priv->height; + win->phys_addr = priv->frame_buffer; + win->stride = priv->width * (1 << priv->log2_bpp) / 8; + + log_debug("%s: depth = %d\n", __func__, priv->log2_bpp); + + switch (priv->log2_bpp) { + case VIDEO_BPP32: + win->fmt = COLOR_DEPTH_R8G8B8A8; + win->bpp = 32; + break; + case VIDEO_BPP16: + win->fmt = COLOR_DEPTH_B5G6R5; + win->bpp = 16; + break; + + default: + log_debug("Unsupported LCD bit depth\n"); + return -1; + } + + return 0; +} + +/** + * Register a new display based on device tree configuration. + * + * The frame buffer can be positioned by U-Boot or overridden by the fdt. + * You should pass in the U-Boot address here, and check the contents of + * struct tegra_lcd_priv to see what was actually chosen. + * + * @param priv Driver's private data + * @param default_lcd_base Default address of LCD frame buffer + * Return: 0 if ok, -1 on error (unsupported bits per pixel) + */ +static int tegra_display_probe(struct tegra_lcd_priv *priv, + void *default_lcd_base) +{ + struct disp_ctl_win window; + unsigned long rate = clk_get_rate(priv->clk_parent); + int ret; + + priv->frame_buffer = (u32)default_lcd_base; + + /* + * We halve the rate if DISP1 parent is PLLD, since actual parent + * is plld_out0 which is PLLD divided by 2. + */ + if (priv->clk_parent->id == CLOCK_ID_DISPLAY || + priv->clk_parent->id == CLOCK_ID_DISPLAY2) + rate /= 2; + + /* + * The pixel clock divider is in 7.1 format (where the bottom bit + * represents 0.5). Here we calculate the divider needed to get from + * the display clock (typically 600MHz) to the pixel clock. We round + * up or down as required. + */ + if (!priv->scdiv) + priv->scdiv = ((rate * 2 + priv->pixel_clock / 2) + / priv->pixel_clock) - 2; + log_debug("Display clock %lu, divider %lu\n", rate, priv->scdiv); + + clock_start_periph_pll(priv->clk->id, priv->clk_parent->id, + rate); + + basic_init(&priv->dc->cmd); + + if (priv->soc->has_timer) + basic_init_timer(&priv->dc->disp); + + if (priv->soc->has_rgb) + rgb_enable(priv); + + if (priv->pixel_clock) + update_display_mode(priv); + + ret = setup_window(priv, &window); + if (ret) + return ret; + + update_window(priv, &window); + + return 0; +} + +static int tegra_lcd_probe(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + struct tegra_lcd_priv *priv = dev_get_priv(dev); + int ret; + + /* Initialize the Tegra display controller */ + if (priv->soc->has_pgate) { + uint powergate; + + if (priv->pipe) + powergate = TEGRA_POWERGATE_DISB; + else + powergate = TEGRA_POWERGATE_DIS; + + ret = tegra_powergate_power_off(powergate); + if (ret < 0) { + log_debug("failed to power off DISP gate: %d", ret); + return ret; + } + + ret = tegra_powergate_sequence_power_up(powergate, + priv->clk->id); + if (ret < 0) { + log_debug("failed to power up DISP gate: %d", ret); + return ret; + } + } + + /* Get shift clock divider from Tegra DSI if used */ + if (priv->bridge) { + if (!strcmp(priv->bridge->driver->name, "tegra_dsi")) { + struct tegra_dc_plat *dc_plat = dev_get_plat(priv->bridge); + + priv->scdiv = dc_plat->scdiv; + } + } + + /* Clean the framebuffer area */ + memset((u8 *)plat->base, 0, plat->size); + flush_dcache_all(); + + ret = tegra_display_probe(priv, (void *)plat->base); + if (ret) { + log_debug("%s: Failed to probe display driver\n", __func__); + return ret; + } + + if (priv->panel) { + ret = panel_enable_backlight(priv->panel); + if (ret) { + log_debug("%s: Cannot enable backlight, ret=%d\n", __func__, ret); + return ret; + } + } + + if (priv->bridge) { + ret = video_bridge_attach(priv->bridge); + if (ret) { + log_debug("%s: Cannot attach bridge, ret=%d\n", __func__, ret); + return ret; + } + } + + mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size, + DCACHE_WRITETHROUGH); + + /* Enable flushing after LCD writes if requested */ + video_set_flush_dcache(dev, true); + + uc_priv->xsize = priv->width; + uc_priv->ysize = priv->height; + uc_priv->bpix = priv->log2_bpp; + log_debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer, + plat->size); + + if (priv->panel) { + ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); + if (ret) + return ret; + } + + if (priv->bridge) { + ret = video_bridge_set_backlight(priv->bridge, BACKLIGHT_DEFAULT); + if (ret) + return ret; + } + + return 0; +} + +static int tegra_lcd_configure_rgb(struct udevice *dev, ofnode rgb) +{ + struct tegra_lcd_priv *priv = dev_get_priv(dev); + ofnode remote; + int ret; + + /* DC can have only 1 port */ + remote = ofnode_graph_get_remote_node(rgb, -1, -1); + + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel); + if (!ret) + return 0; + + ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, remote, &priv->bridge); + if (!ret) + return 0; + + /* Try legacy method if graph did not work */ + remote = ofnode_parse_phandle(rgb, "nvidia,panel", 0); + if (!ofnode_valid(remote)) + return -EINVAL; + + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, remote, &priv->panel); + if (ret) { + log_debug("%s: Cannot find panel for '%s' (ret=%d)\n", + __func__, dev->name, ret); + + ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, remote, + &priv->bridge); + if (ret) { + log_err("%s: Cannot find panel or bridge for '%s' (ret=%d)\n", + __func__, dev->name, ret); + return ret; + } + } + + return 0; +} + +static int tegra_lcd_configure_internal(struct udevice *dev) +{ + struct tegra_lcd_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat; + ofnode host1x = ofnode_get_parent(dev_ofnode(dev)); + ofnode node; + int ret; + + switch (priv->pipe) { + case 0: /* DC0 is usually used for DSI */ + /* Check for ganged DSI configuration */ + ofnode_for_each_subnode(node, host1x) + if (ofnode_name_eq(node, "dsi") && ofnode_is_enabled(node) && + ofnode_read_bool(node, "nvidia,ganged-mode")) + goto exit; + + /* If no master DSI found loop for any active DSI */ + ofnode_for_each_subnode(node, host1x) + if (ofnode_name_eq(node, "dsi") && ofnode_is_enabled(node)) + goto exit; + + log_err("%s: failed to find DSI device for '%s'\n", + __func__, dev->name); + + return -ENODEV; + case 1: /* DC1 is usually used for HDMI */ + ofnode_for_each_subnode(node, host1x) + if (ofnode_name_eq(node, "hdmi")) + goto exit; + + log_err("%s: failed to find HDMI device for '%s'\n", + __func__, dev->name); + + return -ENODEV; + default: + log_debug("Unsupported DC selection\n"); + return -EINVAL; + } + +exit: + ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, node, &priv->bridge); + if (ret) { + log_err("%s: failed to get DSI/HDMI device for '%s' (ret %d)\n", + __func__, dev->name, ret); + return ret; + } + + priv->clk_parent = devm_clk_get(priv->bridge, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get DC clock parent from DSI/HDMI: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + dc_plat = dev_get_plat(priv->bridge); + + /* Fill the platform data for internal devices */ + dc_plat->dev = dev; + dc_plat->dc = priv->dc; + dc_plat->pipe = priv->pipe; + + return 0; +} + +static int tegra_lcd_of_to_plat(struct udevice *dev) +{ + struct tegra_lcd_priv *priv = dev_get_priv(dev); + struct display_timing *timing; + ofnode rgb; + int ret; + + priv->dc = (struct dc_ctlr *)dev_read_addr_ptr(dev); + if (!priv->dc) { + log_debug("%s: No display controller address\n", __func__); + return -EINVAL; + } + + priv->soc = (struct tegra_dc_soc_info *)dev_get_driver_data(dev); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + log_debug("%s: Could not get DC clock: %ld\n", + __func__, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + + priv->clk_parent = devm_clk_get(dev, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get DC clock parent: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + priv->rotation = dev_read_bool(dev, "nvidia,180-rotation"); + priv->pipe = dev_read_u32_default(dev, "nvidia,head", 0); + + /* + * Usual logic of Tegra video routing should be next: + * 1. Check rgb subnode for RGB/LVDS panels or bridges + * 2. If none found, then iterate through bridges bound, + * looking for DSIA or DSIB for DC0 and HDMI for DC1. + * If none of above is valid, then configuration is not + * valid. + */ + + rgb = dev_read_subnode(dev, "rgb"); + if (ofnode_valid(rgb) && ofnode_is_enabled(rgb)) { + /* RGB is available, use it */ + ret = tegra_lcd_configure_rgb(dev, rgb); + if (ret) + return ret; + } else { + /* RGB is not available, check for internal devices */ + ret = tegra_lcd_configure_internal(dev); + if (ret) + return ret; + } + + if (priv->panel) { + ret = panel_get_display_timing(priv->panel, &priv->timing); + if (ret) { + ret = ofnode_decode_display_timing(rgb, 0, &priv->timing); + if (ret) { + log_debug("%s: Cannot read display timing for '%s' (ret=%d)\n", + __func__, dev->name, ret); + return -EINVAL; + } + } + } + + if (priv->bridge) { + ret = video_bridge_get_display_timing(priv->bridge, &priv->timing); + if (ret) { + log_debug("%s: Cannot read display timing for '%s' (ret=%d)\n", + __func__, dev->name, ret); + return -EINVAL; + } + } + + timing = &priv->timing; + priv->width = timing->hactive.typ; + priv->height = timing->vactive.typ; + priv->pixel_clock = timing->pixelclock.typ; + priv->log2_bpp = VIDEO_BPP16; + + return 0; +} + +static int tegra_lcd_bind(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT * + (1 << LCD_MAX_LOG2_BPP) / 8; + + return dm_scan_fdt_dev(dev); +} + +static const struct tegra_dc_soc_info tegra20_dc_soc_info = { + .has_timer = true, + .has_rgb = true, + .has_pgate = false, +}; + +static const struct tegra_dc_soc_info tegra30_dc_soc_info = { + .has_timer = false, + .has_rgb = true, + .has_pgate = false, +}; + +static const struct tegra_dc_soc_info tegra114_dc_soc_info = { + .has_timer = false, + .has_rgb = false, + .has_pgate = true, +}; + +static const struct udevice_id tegra_lcd_ids[] = { + { + .compatible = "nvidia,tegra20-dc", + .data = (ulong)&tegra20_dc_soc_info + }, { + .compatible = "nvidia,tegra30-dc", + .data = (ulong)&tegra30_dc_soc_info + }, { + .compatible = "nvidia,tegra114-dc", + .data = (ulong)&tegra114_dc_soc_info + }, { + .compatible = "nvidia,tegra124-dc", + .data = (ulong)&tegra114_dc_soc_info + }, { + /* sentinel */ + } +}; + +U_BOOT_DRIVER(tegra_lcd) = { + .name = "tegra_lcd", + .id = UCLASS_VIDEO, + .of_match = tegra_lcd_ids, + .bind = tegra_lcd_bind, + .probe = tegra_lcd_probe, + .of_to_plat = tegra_lcd_of_to_plat, + .priv_auto = sizeof(struct tegra_lcd_priv), +}; diff --git a/drivers/video/tegra/dc.h b/drivers/video/tegra/dc.h new file mode 100644 index 00000000000..2a4013b3355 --- /dev/null +++ b/drivers/video/tegra/dc.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation <www.nvidia.com> + */ + +#ifndef _TEGRA_DC_H +#define _TEGRA_DC_H + +#ifndef __ASSEMBLY__ +#include <linux/bitops.h> +#endif + +/* arch-tegra/dc exists only because T124 uses it */ +#include <asm/arch-tegra/dc.h> + +struct tegra_dc_plat { + struct udevice *dev; /* Display controller device */ + struct dc_ctlr *dc; /* Display controller regmap */ + int pipe; /* DC number: 0 for A, 1 for B */ + ulong scdiv; /* Shift clock divider */ +}; + +/* This holds information about a window which can be displayed */ +struct disp_ctl_win { + enum win_color_depth_id fmt; /* Color depth/format */ + unsigned int bpp; /* Bits per pixel */ + phys_addr_t phys_addr; /* Physical address in memory */ + unsigned int x; /* Horizontal address offset (bytes) */ + unsigned int y; /* Veritical address offset (bytes) */ + unsigned int w; /* Width of source window */ + unsigned int h; /* Height of source window */ + unsigned int stride; /* Number of bytes per line */ + unsigned int out_x; /* Left edge of output window (col) */ + unsigned int out_y; /* Top edge of output window (row) */ + unsigned int out_w; /* Width of output window in pixels */ + unsigned int out_h; /* Height of output window in pixels */ +}; + +#endif /* _TEGRA_DC_H */ diff --git a/drivers/video/tegra/dsi.c b/drivers/video/tegra/dsi.c new file mode 100644 index 00000000000..bc308869f4e --- /dev/null +++ b/drivers/video/tegra/dsi.c @@ -0,0 +1,1148 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2022 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#include <dm.h> +#include <clk.h> +#include <log.h> +#include <misc.h> +#include <mipi_display.h> +#include <mipi_dsi.h> +#include <backlight.h> +#include <video_bridge.h> +#include <panel.h> +#include <reset.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> +#include <power/regulator.h> + +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch-tegra/clk_rst.h> + +#include "dc.h" +#include "dsi.h" +#include "mipi-phy.h" + +/* List of supported DSI bridges */ +enum { + DSI_V0, + DSI_V1, +}; + +struct tegra_dsi_priv { + struct mipi_dsi_host host; + struct mipi_dsi_device device; + struct mipi_dphy_timing dphy_timing; + + struct udevice *panel; + struct udevice *mipi; + struct display_timing timing; + + struct dsi_ctlr *dsi; + struct udevice *avdd; + + enum tegra_dsi_format format; + + struct clk *clk; + struct clk *clk_parent; + + int video_fifo_depth; + int host_fifo_depth; + + u32 calibration_pads; + u32 version; + + /* for ganged-mode support */ + struct udevice *master; + struct udevice *slave; +}; + +static void tegra_dc_enable_controller(struct udevice *dev) +{ + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct dc_ctlr *dc = dc_plat->dc; + u32 value; + + value = readl(&dc->disp.disp_win_opt); + value |= DSI_ENABLE; + writel(value, &dc->disp.disp_win_opt); + + writel(GENERAL_UPDATE, &dc->cmd.state_ctrl); + writel(GENERAL_ACT_REQ, &dc->cmd.state_ctrl); +} + +static const char * const error_report[16] = { + "SoT Error", + "SoT Sync Error", + "EoT Sync Error", + "Escape Mode Entry Command Error", + "Low-Power Transmit Sync Error", + "Peripheral Timeout Error", + "False Control Error", + "Contention Detected", + "ECC Error, single-bit", + "ECC Error, multi-bit", + "Checksum Error", + "DSI Data Type Not Recognized", + "DSI VC ID Invalid", + "Invalid Transmission Length", + "Reserved", + "DSI Protocol Violation", +}; + +static ssize_t tegra_dsi_read_response(struct dsi_misc_reg *misc, + const struct mipi_dsi_msg *msg, + size_t count) +{ + u8 *rx = msg->rx_buf; + unsigned int i, j, k; + size_t size = 0; + u16 errors; + u32 value; + + /* read and parse packet header */ + value = readl(&misc->dsi_rd_data); + + switch (value & 0x3f) { + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + errors = (value >> 8) & 0xffff; + printf("%s: Acknowledge and error report: %04x\n", + __func__, errors); + for (i = 0; i < ARRAY_SIZE(error_report); i++) + if (errors & BIT(i)) + printf("%s: %2u: %s\n", __func__, i, + error_report[i]); + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + rx[0] = (value >> 8) & 0xff; + size = 1; + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + rx[0] = (value >> 8) & 0xff; + rx[1] = (value >> 16) & 0xff; + size = 2; + break; + + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + default: + printf("%s: unhandled response type: %02x\n", + __func__, value & 0x3f); + return -EPROTO; + } + + size = min(size, msg->rx_len); + + if (msg->rx_buf && size > 0) { + for (i = 0, j = 0; i < count - 1; i++, j += 4) { + u8 *rx = msg->rx_buf + j; + + value = readl(&misc->dsi_rd_data); + + for (k = 0; k < 4 && (j + k) < msg->rx_len; k++) + rx[j + k] = (value >> (k << 3)) & 0xff; + } + } + + return size; +} + +static int tegra_dsi_transmit(struct dsi_misc_reg *misc, + unsigned long timeout) +{ + writel(DSI_TRIGGER_HOST, &misc->dsi_trigger); + + while (timeout--) { + u32 value = readl(&misc->dsi_trigger); + + if ((value & DSI_TRIGGER_HOST) == 0) + return 0; + + udelay(1000); + } + + debug("timeout waiting for transmission to complete\n"); + return -ETIMEDOUT; +} + +static int tegra_dsi_wait_for_response(struct dsi_misc_reg *misc, + unsigned long timeout) +{ + while (timeout--) { + u32 value = readl(&misc->dsi_status); + u8 count = value & 0x1f; + + if (count > 0) + return count; + + udelay(1000); + } + + debug("peripheral returned no data\n"); + return -ETIMEDOUT; +} + +static void tegra_dsi_writesl(struct dsi_misc_reg *misc, + const void *buffer, size_t size) +{ + const u8 *buf = buffer; + size_t i, j; + u32 value; + + for (j = 0; j < size; j += 4) { + value = 0; + + for (i = 0; i < 4 && j + i < size; i++) + value |= buf[j + i] << (i << 3); + + writel(value, &misc->dsi_wr_data); + } +} + +static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct udevice *dev = (struct udevice *)host->dev; + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_misc_reg *misc = &priv->dsi->misc; + struct mipi_dsi_packet packet; + const u8 *header; + size_t count; + ssize_t err; + u32 value; + + err = mipi_dsi_create_packet(&packet, msg); + if (err < 0) + return err; + + header = packet.header; + + /* maximum FIFO depth is 1920 words */ + if (packet.size > priv->video_fifo_depth * 4) + return -ENOSPC; + + /* reset underflow/overflow flags */ + value = readl(&misc->dsi_status); + if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) { + value = DSI_HOST_CONTROL_FIFO_RESET; + writel(value, &misc->host_dsi_ctrl); + udelay(10); + } + + value = readl(&misc->dsi_pwr_ctrl); + value |= DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + + mdelay(5); + + value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | + DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; + + if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0) + value |= DSI_HOST_CONTROL_HS; + + /* + * The host FIFO has a maximum of 64 words, so larger transmissions + * need to use the video FIFO. + */ + if (packet.size > priv->host_fifo_depth * 4) + value |= DSI_HOST_CONTROL_FIFO_SEL; + + writel(value, &misc->host_dsi_ctrl); + + /* + * For reads and messages with explicitly requested ACK, generate a + * BTA sequence after the transmission of the packet. + */ + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + value = readl(&misc->host_dsi_ctrl); + value |= DSI_HOST_CONTROL_PKT_BTA; + writel(value, &misc->host_dsi_ctrl); + } + + value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE; + writel(value, &misc->dsi_ctrl); + + /* write packet header, ECC is generated by hardware */ + value = header[2] << 16 | header[1] << 8 | header[0]; + writel(value, &misc->dsi_wr_data); + + /* write payload (if any) */ + if (packet.payload_length > 0) + tegra_dsi_writesl(misc, packet.payload, + packet.payload_length); + + err = tegra_dsi_transmit(misc, 250); + if (err < 0) + return err; + + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + err = tegra_dsi_wait_for_response(misc, 250); + if (err < 0) + return err; + + count = err; + + value = readl(&misc->dsi_rd_data); + switch (value) { + case 0x84: + debug("%s: ACK\n", __func__); + break; + + case 0x87: + debug("%s: ESCAPE\n", __func__); + break; + + default: + printf("%s: unknown status: %08x\n", __func__, value); + break; + } + + if (count > 1) { + err = tegra_dsi_read_response(misc, msg, count); + if (err < 0) { + printf("%s: failed to parse response: %zd\n", + __func__, err); + } else { + /* + * For read commands, return the number of + * bytes returned by the peripheral. + */ + count = err; + } + } + } else { + /* + * For write commands, we have transmitted the 4-byte header + * plus the variable-length payload. + */ + count = 4 + packet.payload_length; + } + + return count; +} + +struct mipi_dsi_host_ops tegra_dsi_bridge_host_ops = { + .transfer = tegra_dsi_host_transfer, +}; + +#define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9)) +#define PKT_LEN0(len) (((len) & 0x07) << 0) +#define PKT_ID1(id) ((((id) & 0x3f) << 13) | (1 << 19)) +#define PKT_LEN1(len) (((len) & 0x07) << 10) +#define PKT_ID2(id) ((((id) & 0x3f) << 23) | (1 << 29)) +#define PKT_LEN2(len) (((len) & 0x07) << 20) + +#define PKT_LP BIT(30) +#define NUM_PKT_SEQ 12 + +/* + * non-burst mode with sync pulses + */ +static const u32 pkt_seq_video_non_burst_sync_pulses[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_V_SYNC_END) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), +}; + +/* + * non-burst mode with sync events + */ +static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), +}; + +static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { + [ 0] = 0, + [ 1] = 0, + [ 2] = 0, + [ 3] = 0, + [ 4] = 0, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP, + [ 7] = 0, + [ 8] = 0, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP, + [11] = 0, +}; + +static void tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, + unsigned int *mulp, unsigned int *divp) +{ + switch (format) { + case MIPI_DSI_FMT_RGB666_PACKED: + case MIPI_DSI_FMT_RGB888: + *mulp = 3; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB565: + *mulp = 2; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB666: + *mulp = 9; + *divp = 4; + break; + + default: + break; + } +} + +static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, + enum tegra_dsi_format *fmt) +{ + switch (format) { + case MIPI_DSI_FMT_RGB888: + *fmt = TEGRA_DSI_FORMAT_24P; + break; + + case MIPI_DSI_FMT_RGB666: + *fmt = TEGRA_DSI_FORMAT_18NP; + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + *fmt = TEGRA_DSI_FORMAT_18P; + break; + + case MIPI_DSI_FMT_RGB565: + *fmt = TEGRA_DSI_FORMAT_16P; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void tegra_dsi_pad_calibrate(struct dsi_pad_ctrl_reg *pad) +{ + u32 value; + + /* start calibration */ + value = DSI_PAD_CONTROL_PAD_LPUPADJ(0x1) | + DSI_PAD_CONTROL_PAD_LPDNADJ(0x1) | + DSI_PAD_CONTROL_PAD_PREEMP_EN(0x1) | + DSI_PAD_CONTROL_PAD_SLEWDNADJ(0x6) | + DSI_PAD_CONTROL_PAD_SLEWUPADJ(0x6) | + DSI_PAD_CONTROL_PAD_PDIO(0) | + DSI_PAD_CONTROL_PAD_PDIO_CLK(0) | + DSI_PAD_CONTROL_PAD_PULLDN_ENAB(0); + writel(value, &pad->pad_ctrl); + + clock_enable(PERIPH_ID_VI); + clock_enable(PERIPH_ID_CSI); + udelay(2); + reset_set_enable(PERIPH_ID_VI, 0); + reset_set_enable(PERIPH_ID_CSI, 0); + + value = MIPI_CAL_TERMOSA(0x4); + writel(value, TEGRA_VI_BASE + (CSI_CILA_MIPI_CAL_CONFIG_0 << 2)); + + value = MIPI_CAL_TERMOSB(0x4); + writel(value, TEGRA_VI_BASE + (CSI_CILB_MIPI_CAL_CONFIG_0 << 2)); + + value = MIPI_CAL_HSPUOSD(0x3) | MIPI_CAL_HSPDOSD(0x4); + writel(value, TEGRA_VI_BASE + (CSI_DSI_MIPI_CAL_CONFIG << 2)); + + value = PAD_DRIV_DN_REF(0x5) | PAD_DRIV_UP_REF(0x7); + writel(value, TEGRA_VI_BASE + (CSI_MIPIBIAS_PAD_CONFIG << 2)); + + value = PAD_CIL_PDVREG(0x0); + writel(value, TEGRA_VI_BASE + (CSI_CIL_PAD_CONFIG << 2)); +} + +static void tegra_dsi_mipi_calibrate(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_pad_ctrl_reg *pad = &priv->dsi->pad; + u32 value; + int ret; + + ret = misc_set_enabled(priv->mipi, true); + if (ret) + log_debug("%s: failed to enable MIPI calibration: %d\n", + __func__, ret); + + writel(0, &pad->pad_ctrl); + writel(0, &pad->pad_ctrl_1); + writel(0, &pad->pad_ctrl_2); + writel(0, &pad->pad_ctrl_3); + writel(0, &pad->pad_ctrl_4); + + /* DSI pad enable */ + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + writel(value, &pad->pad_ctrl); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + writel(value, &pad->pad_ctrl_2); + + value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | + DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); + writel(value, &pad->pad_ctrl_3); + + ret = misc_write(priv->mipi, priv->calibration_pads, NULL, 0); + if (ret) + log_debug("%s: MIPI calibration failed %d\n", __func__, ret); + + if (priv->slave) + tegra_dsi_mipi_calibrate(priv->slave); +} + +static void tegra_dsi_set_timeout(struct udevice *dev, + unsigned long bclk, + unsigned int vrefresh) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_timeout_reg *rtimeout = &priv->dsi->timeout; + unsigned int timeout; + u32 value; + + /* one frame high-speed transmission timeout */ + timeout = (bclk / vrefresh) / 512; + value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); + writel(value, &rtimeout->dsi_timeout_0); + + /* 2 ms peripheral timeout for panel */ + timeout = 2 * bclk / 512 * 1000; + value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); + writel(value, &rtimeout->dsi_timeout_1); + + value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); + writel(value, &rtimeout->dsi_to_tally); + + if (priv->slave) + tegra_dsi_set_timeout(priv->slave, bclk, vrefresh); +} + +static void tegra_dsi_set_phy_timing(struct udevice *dev, + unsigned long period, + const struct mipi_dphy_timing *dphy_timing) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_timing_reg *ptiming = &priv->dsi->ptiming; + u32 value; + + value = DSI_TIMING_FIELD(dphy_timing->hsexit, period, 1) << 24 | + DSI_TIMING_FIELD(dphy_timing->hstrail, period, 0) << 16 | + DSI_TIMING_FIELD(dphy_timing->hszero, period, 3) << 8 | + DSI_TIMING_FIELD(dphy_timing->hsprepare, period, 1); + writel(value, &ptiming->dsi_phy_timing_0); + + value = DSI_TIMING_FIELD(dphy_timing->clktrail, period, 1) << 24 | + DSI_TIMING_FIELD(dphy_timing->clkpost, period, 1) << 16 | + DSI_TIMING_FIELD(dphy_timing->clkzero, period, 1) << 8 | + DSI_TIMING_FIELD(dphy_timing->lpx, period, 1); + writel(value, &ptiming->dsi_phy_timing_1); + + value = DSI_TIMING_FIELD(dphy_timing->clkprepare, period, 1) << 16 | + DSI_TIMING_FIELD(dphy_timing->clkpre, period, 1) << 8 | + DSI_TIMING_FIELD(0xff * period, period, 0) << 0; + writel(value, &ptiming->dsi_phy_timing_2); + + value = DSI_TIMING_FIELD(dphy_timing->taget, period, 1) << 16 | + DSI_TIMING_FIELD(dphy_timing->tasure, period, 1) << 8 | + DSI_TIMING_FIELD(dphy_timing->tago, period, 1); + writel(value, &ptiming->dsi_bta_timing); + + if (priv->slave) + tegra_dsi_set_phy_timing(priv->slave, period, dphy_timing); +} + +static u32 tegra_dsi_get_lanes(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + + if (priv->master) { + struct tegra_dsi_priv *mpriv = dev_get_priv(priv->master); + struct mipi_dsi_device *mdevice = &mpriv->device; + + return mdevice->lanes + device->lanes; + } + + if (priv->slave) { + struct tegra_dsi_priv *spriv = dev_get_priv(priv->slave); + struct mipi_dsi_device *sdevice = &spriv->device; + + return device->lanes + sdevice->lanes; + } + + return device->lanes; +} + +static void tegra_dsi_ganged_enable(struct udevice *dev, unsigned int start, + unsigned int size) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_ganged_mode_reg *ganged = &priv->dsi->ganged; + + writel(start, &ganged->ganged_mode_start); + writel(size << 16 | size, &ganged->ganged_mode_size); + writel(DSI_GANGED_MODE_CONTROL_ENABLE, &ganged->ganged_mode_ctrl); +} + +static void tegra_dsi_configure(struct udevice *dev, unsigned int pipe, + unsigned long mode_flags) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *timing = &priv->timing; + + struct dsi_misc_reg *misc = &priv->dsi->misc; + struct dsi_pkt_seq_reg *pkt = &priv->dsi->pkt; + struct dsi_pkt_len_reg *len = &priv->dsi->len; + + unsigned int hact, hsw, hbp, hfp, i, mul, div; + const u32 *pkt_seq; + u32 value; + + tegra_dsi_get_muldiv(device->format, &mul, &div); + + if (mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + printf("[DSI] Non-burst video mode with sync pulses\n"); + pkt_seq = pkt_seq_video_non_burst_sync_pulses; + } else if (mode_flags & MIPI_DSI_MODE_VIDEO) { + printf("[DSI] Non-burst video mode with sync events\n"); + pkt_seq = pkt_seq_video_non_burst_sync_events; + } else { + printf("[DSI] Command mode\n"); + pkt_seq = pkt_seq_command_mode; + } + + value = DSI_CONTROL_CHANNEL(0) | + DSI_CONTROL_FORMAT(priv->format) | + DSI_CONTROL_LANES(device->lanes - 1) | + DSI_CONTROL_SOURCE(pipe); + writel(value, &misc->dsi_ctrl); + + writel(priv->video_fifo_depth, &misc->dsi_max_threshold); + + value = DSI_HOST_CONTROL_HS; + writel(value, &misc->host_dsi_ctrl); + + value = readl(&misc->dsi_ctrl); + + if (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) + value |= DSI_CONTROL_HS_CLK_CTRL; + + value &= ~DSI_CONTROL_TX_TRIG(3); + + /* enable DCS commands for command mode */ + if (mode_flags & MIPI_DSI_MODE_VIDEO) + value &= ~DSI_CONTROL_DCS_ENABLE; + else + value |= DSI_CONTROL_DCS_ENABLE; + + value |= DSI_CONTROL_VIDEO_ENABLE; + value &= ~DSI_CONTROL_HOST_ENABLE; + writel(value, &misc->dsi_ctrl); + + for (i = 0; i < NUM_PKT_SEQ; i++) + writel(pkt_seq[i], &pkt->dsi_pkt_seq_0_lo + i); + + if (mode_flags & MIPI_DSI_MODE_VIDEO) { + /* horizontal active pixels */ + hact = timing->hactive.typ * mul / div; + + /* horizontal sync width */ + hsw = timing->hsync_len.typ * mul / div; + + /* horizontal back porch */ + hbp = timing->hback_porch.typ * mul / div; + + /* horizontal front porch */ + hfp = timing->hfront_porch.typ * mul / div; + + if (priv->master || priv->slave) { + hact /= 2; + hsw /= 2; + hbp = hbp / 2 - 1; + hfp /= 2; + } + + if ((mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) == 0) + hbp += hsw; + + /* subtract packet overhead */ + hsw -= 10; + hbp -= 14; + hfp -= 8; + + writel(hsw << 16 | 0, &len->dsi_pkt_len_0_1); + writel(hact << 16 | hbp, &len->dsi_pkt_len_2_3); + writel(hfp, &len->dsi_pkt_len_4_5); + writel(0x0f0f << 16, &len->dsi_pkt_len_6_7); + } else { + if (priv->master || priv->slave) { + /* + * For ganged mode, assume symmetric left-right mode. + */ + value = 1 + (timing->hactive.typ / 2) * mul / div; + } else { + /* 1 byte (DCS command) + pixel data */ + value = 1 + timing->hactive.typ * mul / div; + } + + writel(0, &len->dsi_pkt_len_0_1); + writel(value << 16, &len->dsi_pkt_len_2_3); + writel(value << 16, &len->dsi_pkt_len_4_5); + writel(0, &len->dsi_pkt_len_6_7); + + value = MIPI_DCS_WRITE_MEMORY_START << 8 | + MIPI_DCS_WRITE_MEMORY_CONTINUE; + writel(value, &len->dsi_dcs_cmds); + } + + /* set SOL delay */ + if (priv->master || priv->slave) { + unsigned long delay, bclk, bclk_ganged; + unsigned int lanes = tegra_dsi_get_lanes(dev); + unsigned long htotal = timing->hactive.typ + timing->hfront_porch.typ + + timing->hback_porch.typ + timing->hsync_len.typ; + + /* SOL to valid, valid to FIFO and FIFO write delay */ + delay = 4 + 4 + 2; + delay = DIV_ROUND_UP(delay * mul, div * lanes); + /* FIFO read delay */ + delay = delay + 6; + + bclk = DIV_ROUND_UP(htotal * mul, div * lanes); + bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes); + value = bclk - bclk_ganged + delay + 20; + } else { + /* set SOL delay (for non-burst mode only) */ + value = 8 * mul / div; + } + + writel(value, &misc->dsi_sol_delay); + + if (priv->slave) { + tegra_dsi_configure(priv->slave, pipe, mode_flags); + /* + * TODO: Support modes other than symmetrical left-right + * split. + */ + tegra_dsi_ganged_enable(dev, 0, timing->hactive.typ / 2); + tegra_dsi_ganged_enable(priv->slave, timing->hactive.typ / 2, + timing->hactive.typ / 2); + } +} + +static void tegra_dsi_enable(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct dsi_misc_reg *misc = &priv->dsi->misc; + u32 value; + + /* enable DSI controller */ + value = readl(&misc->dsi_pwr_ctrl); + value |= DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + + if (priv->slave) + tegra_dsi_enable(priv->slave); +} + +static int tegra_dsi_encoder_enable(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *timing = &priv->timing; + struct dsi_misc_reg *misc = &priv->dsi->misc; + unsigned int mul, div; + unsigned long bclk, plld, period; + u32 value, lanes; + int ret; + + /* If for some reasone DSI is enabled then it needs to + * be disabled in order for the panel initialization + * commands to be properly sent. + */ + value = readl(&misc->dsi_pwr_ctrl); + + if (value & DSI_POWER_CONTROL_ENABLE) { + value = readl(&misc->dsi_pwr_ctrl); + value &= ~DSI_POWER_CONTROL_ENABLE; + writel(value, &misc->dsi_pwr_ctrl); + } + + /* Disable interrupt */ + writel(0, &misc->int_enable); + + if (priv->version) + tegra_dsi_mipi_calibrate(dev); + else + tegra_dsi_pad_calibrate(&priv->dsi->pad); + + tegra_dsi_get_muldiv(device->format, &mul, &div); + + /* compute byte clock */ + lanes = tegra_dsi_get_lanes(dev); + bclk = (timing->pixelclock.typ * mul) / (div * lanes); + + tegra_dsi_set_timeout(dev, bclk, 60); + + /* + * Compute bit clock and round up to the next MHz. + */ + plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; + period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, plld); + + ret = mipi_dphy_timing_get_default(&priv->dphy_timing, period); + if (ret < 0) { + printf("%s: failed to get D-PHY timing: %d\n", __func__, ret); + return ret; + } + + ret = mipi_dphy_timing_validate(&priv->dphy_timing, period); + if (ret < 0) { + printf("%s: failed to validate D-PHY timing: %d\n", __func__, ret); + return ret; + } + + /* + * The D-PHY timing fields are expressed in byte-clock cycles, so + * multiply the period by 8. + */ + tegra_dsi_set_phy_timing(dev, period * 8, &priv->dphy_timing); + + /* Perform panel HW setup */ + ret = panel_enable_backlight(priv->panel); + if (ret) + return ret; + + tegra_dsi_configure(dev, dc_plat->pipe, device->mode_flags); + + tegra_dc_enable_controller(dev); + + tegra_dsi_enable(dev); + + return 0; +} + +static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + + /* Turn on/off backlight */ + return panel_set_backlight(priv->panel, percent); +} + +static int tegra_dsi_panel_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static void tegra_dsi_init_clocks(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct mipi_dsi_device *device = &priv->device; + unsigned int mul, div, lanes; + unsigned long bclk, plld; + + /* Switch parents of DSI clocks in case of not standard parent */ + if (priv->clk->id == PERIPH_ID_DSI && + priv->clk_parent->id == CLOCK_ID_DISPLAY2) { + /* Change DSIA clock parent to PLLD2 */ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + + /* DSIA_CLK_SRC */ + setbits_le32(&clkrst->crc_pll[CLOCK_ID_DISPLAY].pll_base, + BIT(25)); + } + + if (priv->clk->id == PERIPH_ID_DSIB && + priv->clk_parent->id == CLOCK_ID_DISPLAY) { + /* Change DSIB clock parent to match DSIA */ + struct clk_rst_ctlr *clkrst = + (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE; + + /* DSIB_CLK_SRC */ + clrbits_le32(&clkrst->plld2.pll_base, BIT(25)); + } + + tegra_dsi_get_muldiv(device->format, &mul, &div); + + lanes = tegra_dsi_get_lanes(dev); + bclk = (priv->timing.pixelclock.typ * mul) / (div * lanes); + + plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC); + + dc_plat->scdiv = ((plld * USEC_PER_SEC + + priv->timing.pixelclock.typ / 2) / + priv->timing.pixelclock.typ) - 2; + + /* + * BUG: If DISP1 is a PLLD/D2 child, it cannot go over 370MHz. The + * cause of this is not quite clear. This can be overcomed by + * halving the PLLD/D2 if the target rate is > 800MHz. This way + * DISP1 and DSI clocks will be equal. + */ + if (plld > 800) + plld /= 2; + + switch (clock_get_osc_freq()) { + case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */ + case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */ + clock_set_rate(priv->clk_parent->id, plld, 12, 0, 8); + break; + + case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */ + clock_set_rate(priv->clk_parent->id, plld, 26, 0, 8); + break; + + case CLOCK_OSC_FREQ_13_0: /* OSC is 13Mhz */ + case CLOCK_OSC_FREQ_16_8: /* OSC is 16.8Mhz */ + clock_set_rate(priv->clk_parent->id, plld, 13, 0, 8); + break; + + case CLOCK_OSC_FREQ_19_2: + case CLOCK_OSC_FREQ_38_4: + default: + /* + * These are not supported. + */ + break; + } + + clk_enable(priv->clk); +} + +static int tegra_dsi_ganged_probe(struct udevice *dev) +{ + struct tegra_dsi_priv *mpriv = dev_get_priv(dev); + struct udevice *gangster; + + uclass_get_device_by_phandle(UCLASS_VIDEO_BRIDGE, dev, + "nvidia,ganged-mode", &gangster); + if (gangster) { + /* Ganged mode is set */ + struct tegra_dsi_priv *spriv = dev_get_priv(gangster); + + mpriv->slave = gangster; + spriv->master = dev; + } + + return 0; +} + +static int tegra_dsi_bridge_probe(struct udevice *dev) +{ + struct tegra_dsi_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct mipi_dsi_panel_plat *mipi_plat; + struct reset_ctl reset_ctl; + int ret; + + priv->version = dev_get_driver_data(dev); + + priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->dsi) { + printf("%s: No display controller address\n", __func__); + return -EINVAL; + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + log_debug("%s: Could not get DSI clock: %ld\n", + __func__, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + + priv->clk_parent = devm_clk_get(dev, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get DSI clock parent: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + priv->video_fifo_depth = 1920; + priv->host_fifo_depth = 64; + + tegra_dsi_ganged_probe(dev); + + ret = reset_get_by_name(dev, "dsi", &reset_ctl); + if (ret) { + log_debug("%s: reset_get_by_name() failed: %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev, + "avdd-dsi-csi-supply", &priv->avdd); + if (ret) + debug("%s: Cannot get avdd-dsi-csi-supply: error %d\n", + __func__, ret); + + /* Check all DSI children */ + device_foreach_child(priv->panel, dev) { + if (device_get_uclass_id(priv->panel) == UCLASS_PANEL) + break; + } + + /* if loop exits without panel device return error */ + if (device_get_uclass_id(priv->panel) != UCLASS_PANEL) { + log_debug("%s: panel not found, ret %d\n", __func__, ret); + return -EINVAL; + } + + ret = uclass_get_device_by_ofnode(UCLASS_PANEL, dev_ofnode(priv->panel), + &priv->panel); + if (ret) { + log_debug("%s: Cannot get panel: error %d\n", __func__, ret); + return log_ret(ret); + } + + if (priv->version) { + ret = uclass_get_device_by_phandle(UCLASS_MISC, dev, + "nvidia,mipi-calibrate", + &priv->mipi); + if (ret) { + log_debug("%s: cannot get MIPI: error %d\n", __func__, ret); + return ret; + } + + ret = dev_read_u32_index(dev, "nvidia,mipi-calibrate", 1, + &priv->calibration_pads); + if (ret) { + log_debug("%s: cannot get calibration pads: error %d\n", + __func__, ret); + return ret; + } + } + + panel_get_display_timing(priv->panel, &priv->timing); + + mipi_plat = dev_get_plat(priv->panel); + mipi_plat->device = device; + + priv->host.dev = (struct device *)dev; + priv->host.ops = &tegra_dsi_bridge_host_ops; + + device->host = &priv->host; + device->lanes = mipi_plat->lanes; + device->format = mipi_plat->format; + device->mode_flags = mipi_plat->mode_flags; + + tegra_dsi_get_format(device->format, &priv->format); + + reset_assert(&reset_ctl); + + ret = regulator_set_enable_if_allowed(priv->avdd, true); + if (ret && ret != -ENOSYS) + return ret; + + tegra_dsi_init_clocks(dev); + + mdelay(2); + reset_deassert(&reset_ctl); + + return 0; +} + +static const struct video_bridge_ops tegra_dsi_bridge_ops = { + .attach = tegra_dsi_encoder_enable, + .set_backlight = tegra_dsi_bridge_set_panel, + .get_display_timing = tegra_dsi_panel_timings, +}; + +static const struct udevice_id tegra_dsi_bridge_ids[] = { + { .compatible = "nvidia,tegra20-dsi", .data = DSI_V0 }, + { .compatible = "nvidia,tegra30-dsi", .data = DSI_V0 }, + { .compatible = "nvidia,tegra114-dsi", .data = DSI_V1 }, + { .compatible = "nvidia,tegra124-dsi", .data = DSI_V1 }, + { } +}; + +U_BOOT_DRIVER(tegra_dsi) = { + .name = "tegra_dsi", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = tegra_dsi_bridge_ids, + .ops = &tegra_dsi_bridge_ops, + .bind = dm_scan_fdt_dev, + .probe = tegra_dsi_bridge_probe, + .plat_auto = sizeof(struct tegra_dc_plat), + .priv_auto = sizeof(struct tegra_dsi_priv), +}; diff --git a/drivers/video/tegra/dsi.h b/drivers/video/tegra/dsi.h new file mode 100644 index 00000000000..683c5e31a34 --- /dev/null +++ b/drivers/video/tegra/dsi.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation <www.nvidia.com> + */ + +#ifndef _TEGRA_DSI_H +#define _TEGRA_DSI_H + +#ifndef __ASSEMBLY__ +#include <linux/bitops.h> +#endif + +/* Register definitions for the Tegra display serial interface */ + +/* DSI syncpoint register 0x000 ~ 0x002 */ +struct dsi_syncpt_reg { + /* Address 0x000 ~ 0x002 */ + uint incr_syncpt; /* _INCR_SYNCPT_0 */ + uint incr_syncpt_ctrl; /* _INCR_SYNCPT_CNTRL_0 */ + uint incr_syncpt_err; /* _INCR_SYNCPT_ERROR_0 */ +}; + +/* DSI misc register 0x008 ~ 0x015 */ +struct dsi_misc_reg { + /* Address 0x008 ~ 0x015 */ + uint ctxsw; /* _CTXSW_0 */ + uint dsi_rd_data; /* _DSI_RD_DATA_0 */ + uint dsi_wr_data; /* _DSI_WR_DATA_0 */ + uint dsi_pwr_ctrl; /* _DSI_POWER_CONTROL_0 */ + uint int_enable; /* _INT_ENABLE_0 */ + uint int_status; /* _INT_STATUS_0 */ + uint int_mask; /* _INT_MASK_0 */ + uint host_dsi_ctrl; /* _HOST_DSI_CONTROL_0 */ + uint dsi_ctrl; /* _DSI_CONTROL_0 */ + uint dsi_sol_delay; /* _DSI_SOL_DELAY_0 */ + uint dsi_max_threshold; /* _DSI_MAX_THRESHOLD_0 */ + uint dsi_trigger; /* _DSI_TRIGGER_0 */ + uint dsi_tx_crc; /* _DSI_TX_CRC_0 */ + uint dsi_status; /* _DSI_STATUS_0 */ +}; + +/* DSI init sequence register 0x01a ~ 0x022 */ +struct dsi_init_seq_reg { + /* Address 0x01a ~ 0x022 */ + uint dsi_init_seq_ctrl; /* _DSI_INIT_SEQ_CONTROL_0 */ + uint dsi_init_seq_data_0; /* _DSI_INIT_SEQ_DATA_0_0 */ + uint dsi_init_seq_data_1; /* _DSI_INIT_SEQ_DATA_1_0 */ + uint dsi_init_seq_data_2; /* _DSI_INIT_SEQ_DATA_2_0 */ + uint dsi_init_seq_data_3; /* _DSI_INIT_SEQ_DATA_3_0 */ + uint dsi_init_seq_data_4; /* _DSI_INIT_SEQ_DATA_4_0 */ + uint dsi_init_seq_data_5; /* _DSI_INIT_SEQ_DATA_5_0 */ + uint dsi_init_seq_data_6; /* _DSI_INIT_SEQ_DATA_6_0 */ + uint dsi_init_seq_data_7; /* _DSI_INIT_SEQ_DATA_7_0 */ +}; + +/* DSI packet sequence register 0x023 ~ 0x02e */ +struct dsi_pkt_seq_reg { + /* Address 0x023 ~ 0x02e */ + uint dsi_pkt_seq_0_lo; /* _DSI_PKT_SEQ_0_LO_0 */ + uint dsi_pkt_seq_0_hi; /* _DSI_PKT_SEQ_0_HI_0 */ + uint dsi_pkt_seq_1_lo; /* _DSI_PKT_SEQ_1_LO_0 */ + uint dsi_pkt_seq_1_hi; /* _DSI_PKT_SEQ_1_HI_0 */ + uint dsi_pkt_seq_2_lo; /* _DSI_PKT_SEQ_2_LO_0 */ + uint dsi_pkt_seq_2_hi; /* _DSI_PKT_SEQ_2_HI_0 */ + uint dsi_pkt_seq_3_lo; /* _DSI_PKT_SEQ_3_LO_0 */ + uint dsi_pkt_seq_3_hi; /* _DSI_PKT_SEQ_3_HI_0 */ + uint dsi_pkt_seq_4_lo; /* _DSI_PKT_SEQ_4_LO_0 */ + uint dsi_pkt_seq_4_hi; /* _DSI_PKT_SEQ_4_HI_0 */ + uint dsi_pkt_seq_5_lo; /* _DSI_PKT_SEQ_5_LO_0 */ + uint dsi_pkt_seq_5_hi; /* _DSI_PKT_SEQ_5_HI_0 */ +}; + +/* DSI packet length register 0x033 ~ 0x037 */ +struct dsi_pkt_len_reg { + /* Address 0x033 ~ 0x037 */ + uint dsi_dcs_cmds; /* _DSI_DCS_CMDS_0 */ + uint dsi_pkt_len_0_1; /* _DSI_PKT_LEN_0_1_0 */ + uint dsi_pkt_len_2_3; /* _DSI_PKT_LEN_2_3_0 */ + uint dsi_pkt_len_4_5; /* _DSI_PKT_LEN_4_5_0 */ + uint dsi_pkt_len_6_7; /* _DSI_PKT_LEN_6_7_0 */ +}; + +/* DSI PHY timing register 0x03c ~ 0x03f */ +struct dsi_timing_reg { + /* Address 0x03c ~ 0x03f */ + uint dsi_phy_timing_0; /* _DSI_PHY_TIMING_0_0 */ + uint dsi_phy_timing_1; /* _DSI_PHY_TIMING_1_0 */ + uint dsi_phy_timing_2; /* _DSI_PHY_TIMING_2_0 */ + uint dsi_bta_timing; /* _DSI_BTA_TIMING_0 */ +}; + +/* DSI timeout register 0x044 ~ 0x046 */ +struct dsi_timeout_reg { + /* Address 0x044 ~ 0x046 */ + uint dsi_timeout_0; /* _DSI_TIMEOUT_0_0 */ + uint dsi_timeout_1; /* _DSI_TIMEOUT_1_0 */ + uint dsi_to_tally; /* _DSI_TO_TALLY_0 */ +}; + +/* DSI PAD control register 0x04b ~ 0x052 */ +struct dsi_pad_ctrl_reg { + /* Address 0x04b ~ 0x052 */ + uint pad_ctrl; /* _PAD_CONTROL_0 */ + uint pad_ctrl_cd; /* _PAD_CONTROL_CD_0 */ + uint pad_cd_status; /* _PAD_CD_STATUS_0 */ + uint dsi_vid_mode_control; /* _DSI_VID_MODE_CONTROL_0 */ + uint pad_ctrl_1; /* _PAD_CONTROL_1 */ + uint pad_ctrl_2; /* _PAD_CONTROL_2 */ + uint pad_ctrl_3; /* _PAD_CONTROL_3 */ + uint pad_ctrl_4; /* _PAD_CONTROL_4 */ +}; + +/* DSI ganged mode register 0x053 ~ 0x04e */ +struct dsi_ganged_mode_reg { + /* Address 0x053 ~ 0x055 */ + uint ganged_mode_ctrl; /* _DSI_GANGED_MODE_CONTROL_0 */ + uint ganged_mode_start; /* _DSI_GANGED_MODE_START_0 */ + uint ganged_mode_size; /* _DSI_GANGED_MODE_SIZE_0 */ +}; + +/* Display Serial Interface (DSI_) regs */ +struct dsi_ctlr { + struct dsi_syncpt_reg syncpt; /* SYNCPT register 0x000 ~ 0x002 */ + uint reserved0[5]; /* reserved_0[5] */ + + struct dsi_misc_reg misc; /* MISC register 0x008 ~ 0x015 */ + uint reserved1[4]; /* reserved_1[4] */ + + struct dsi_init_seq_reg init; /* INIT register 0x01a ~ 0x022 */ + struct dsi_pkt_seq_reg pkt; /* PKT register 0x023 ~ 0x02e */ + uint reserved2[4]; /* reserved_2[4] */ + + struct dsi_pkt_len_reg len; /* LEN registers 0x033 ~ 0x037 */ + uint reserved3[4]; /* reserved_3[4] */ + + struct dsi_timing_reg ptiming; /* TIMING registers 0x03c ~ 0x03f */ + uint reserved4[4]; /* reserved_4[4] */ + + struct dsi_timeout_reg timeout; /* TIMEOUT registers 0x044 ~ 0x046 */ + uint reserved5[4]; /* reserved_5[4] */ + + struct dsi_pad_ctrl_reg pad; /* PAD registers 0x04b ~ 0x04e */ + struct dsi_ganged_mode_reg ganged; /* GANGED registers 0x053 ~ 0x055 */ +}; + +#define DSI_POWER_CONTROL_ENABLE BIT(0) + +#define DSI_HOST_CONTROL_FIFO_RESET BIT(21) +#define DSI_HOST_CONTROL_CRC_RESET BIT(20) +#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12) +#define DSI_HOST_CONTROL_RAW BIT(6) +#define DSI_HOST_CONTROL_HS BIT(5) +#define DSI_HOST_CONTROL_FIFO_SEL BIT(4) +#define DSI_HOST_CONTROL_IMM_BTA BIT(3) +#define DSI_HOST_CONTROL_PKT_BTA BIT(2) +#define DSI_HOST_CONTROL_CS BIT(1) +#define DSI_HOST_CONTROL_ECC BIT(0) + +#define DSI_CONTROL_HS_CLK_CTRL BIT(20) +#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16) +#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12) +#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8) +#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4) +#define DSI_CONTROL_DCS_ENABLE BIT(3) +#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2) +#define DSI_CONTROL_VIDEO_ENABLE BIT(1) +#define DSI_CONTROL_HOST_ENABLE BIT(0) + +#define DSI_TRIGGER_HOST BIT(1) +#define DSI_TRIGGER_VIDEO BIT(0) + +#define DSI_STATUS_IDLE BIT(10) +#define DSI_STATUS_UNDERFLOW BIT(9) +#define DSI_STATUS_OVERFLOW BIT(8) + +#define DSI_TIMING_FIELD(value, period, hwinc) \ + ((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff) + +#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0) +#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0) + +#define DSI_TALLY_TA(x) (((x) & 0xff) << 16) +#define DSI_TALLY_LRX(x) (((x) & 0xff) << 8) +#define DSI_TALLY_HTX(x) (((x) & 0xff) << 0) + +#define DSI_PAD_CONTROL_PAD_PULLDN_ENAB(x) (((x) & 0x1) << 28) +#define DSI_PAD_CONTROL_PAD_SLEWUPADJ(x) (((x) & 0x7) << 24) +#define DSI_PAD_CONTROL_PAD_SLEWDNADJ(x) (((x) & 0x7) << 20) +#define DSI_PAD_CONTROL_PAD_PREEMP_EN(x) (((x) & 0x1) << 19) +#define DSI_PAD_CONTROL_PAD_PDIO_CLK(x) (((x) & 0x1) << 18) +#define DSI_PAD_CONTROL_PAD_PDIO(x) (((x) & 0x3) << 16) +#define DSI_PAD_CONTROL_PAD_LPUPADJ(x) (((x) & 0x3) << 14) +#define DSI_PAD_CONTROL_PAD_LPDNADJ(x) (((x) & 0x3) << 12) + +#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0) +#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16) + +#define DSI_PAD_OUT_CLK(x) (((x) & 0x7) << 0) +#define DSI_PAD_LP_DN(x) (((x) & 0x7) << 4) +#define DSI_PAD_LP_UP(x) (((x) & 0x7) << 8) +#define DSI_PAD_SLEW_DN(x) (((x) & 0x7) << 12) +#define DSI_PAD_SLEW_UP(x) (((x) & 0x7) << 16) + +#define DSI_PAD_PREEMP_PD_CLK(x) (((x) & 0x3) << 12) +#define DSI_PAD_PREEMP_PU_CLK(x) (((x) & 0x3) << 8) +#define DSI_PAD_PREEMP_PD(x) (((x) & 0x3) << 4) +#define DSI_PAD_PREEMP_PU(x) (((x) & 0x3) << 0) + +#define DSI_GANGED_MODE_CONTROL_ENABLE BIT(0) + +/* + * pixel format as used in the DSI_CONTROL_FORMAT field + */ +enum tegra_dsi_format { + TEGRA_DSI_FORMAT_16P, + TEGRA_DSI_FORMAT_18NP, + TEGRA_DSI_FORMAT_18P, + TEGRA_DSI_FORMAT_24P, +}; + +/* DSI calibration in VI region */ +#define TEGRA_VI_BASE 0x54080000 + +#define CSI_CILA_MIPI_CAL_CONFIG_0 0x22a +#define MIPI_CAL_TERMOSA(x) (((x) & 0x1f) << 0) + +#define CSI_CILB_MIPI_CAL_CONFIG_0 0x22b +#define MIPI_CAL_TERMOSB(x) (((x) & 0x1f) << 0) + +#define CSI_CIL_PAD_CONFIG 0x229 +#define PAD_CIL_PDVREG(x) (((x) & 0x01) << 1) + +#define CSI_DSI_MIPI_CAL_CONFIG 0x234 +#define MIPI_CAL_HSPDOSD(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_HSPUOSD(x) (((x) & 0x1f) << 8) + +#define CSI_MIPIBIAS_PAD_CONFIG 0x235 +#define PAD_DRIV_DN_REF(x) (((x) & 0x7) << 16) +#define PAD_DRIV_UP_REF(x) (((x) & 0x7) << 8) + +#endif /* _TEGRA_DSI_H */ diff --git a/drivers/video/tegra/hdmi.c b/drivers/video/tegra/hdmi.c new file mode 100644 index 00000000000..bfb48b25187 --- /dev/null +++ b/drivers/video/tegra/hdmi.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#include <clk.h> +#include <dm.h> +#include <edid.h> +#include <i2c.h> +#include <log.h> +#include <misc.h> +#include <panel.h> +#include <reset.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/time.h> +#include <power/regulator.h> +#include <video_bridge.h> + +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> + +#include "dc.h" +#include "hdmi.h" + +#define DDCCI_ENTRY_ADDR 0x37 +#define DDCCI_SOURSE_ADDR 0x51 +#define DDCCI_COMMAND_WRITE 0x03 +#define DDCCI_CTRL_BRIGHTNESS 0x10 + +#define HDMI_EDID_I2C_ADDR 0x50 +#define HDMI_REKEY_DEFAULT 56 + +static const char * const hdmi_supplies[] = { + "hdmi-supply", "pll-supply", "vdd-supply" +}; + +struct tmds_config { + unsigned int pclk; + u32 pll0; + u32 pll1; + u32 pe_current; + u32 drive_current; + u32 peak_current; +}; + +struct tegra_hdmi_config { + const struct tmds_config *tmds; + unsigned int num_tmds; + unsigned int max_pclk; + + /* to be filled */ +}; + +struct tegra_hdmi_priv { + struct hdmi_ctlr *hdmi_regmap; + + struct udevice *supplies[ARRAY_SIZE(hdmi_supplies)]; + struct udevice *hdmi_ddc; + + struct gpio_desc hpd; /* hotplug detection gpio */ + struct display_timing timing; + + struct clk *clk; + struct clk *clk_parent; + + int panel_bits_per_colourp; + const struct tegra_hdmi_config *config; +}; + +/* 1280x720p 60hz: EIA/CEA-861-B Format 4 */ +static struct display_timing default_720p_timing = { + .pixelclock.typ = 74250000, + .hactive.typ = 1280, + .hfront_porch.typ = 110, + .hback_porch.typ = 220, + .hsync_len.typ = 40, + .vactive.typ = 720, + .vfront_porch.typ = 5, + .vback_porch.typ = 20, + .vsync_len.typ = 5, + .flags = DISPLAY_FLAGS_HSYNC_HIGH | + DISPLAY_FLAGS_VSYNC_HIGH, +}; + +static const struct tmds_config tegra20_tmds_config[] = { + { /* slow pixel clock modes */ + .pclk = 27000000, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(0) | + SOR_PLL_TX_REG_LOAD(3), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE, + .pe_current = PE_CURRENT0(PE_CURRENT_0_0_mA) | + PE_CURRENT1(PE_CURRENT_0_0_mA) | + PE_CURRENT2(PE_CURRENT_0_0_mA) | + PE_CURRENT3(PE_CURRENT_0_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_7_125_mA), + }, + { /* high pixel clock modes */ + .pclk = UINT_MAX, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(1) | + SOR_PLL_TX_REG_LOAD(3), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, + .pe_current = PE_CURRENT0(PE_CURRENT_6_0_mA) | + PE_CURRENT1(PE_CURRENT_6_0_mA) | + PE_CURRENT2(PE_CURRENT_6_0_mA) | + PE_CURRENT3(PE_CURRENT_6_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_7_125_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_7_125_mA), + }, +}; + +static const struct tmds_config tegra30_tmds_config[] = { + { /* 480p modes */ + .pclk = 27000000, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(0) | + SOR_PLL_TX_REG_LOAD(0), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE, + .pe_current = PE_CURRENT0(PE_CURRENT_0_0_mA) | + PE_CURRENT1(PE_CURRENT_0_0_mA) | + PE_CURRENT2(PE_CURRENT_0_0_mA) | + PE_CURRENT3(PE_CURRENT_0_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), + }, { /* 720p modes */ + .pclk = 74250000, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(1) | + SOR_PLL_TX_REG_LOAD(0), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, + .pe_current = PE_CURRENT0(PE_CURRENT_5_0_mA) | + PE_CURRENT1(PE_CURRENT_5_0_mA) | + PE_CURRENT2(PE_CURRENT_5_0_mA) | + PE_CURRENT3(PE_CURRENT_5_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), + }, { /* 1080p modes */ + .pclk = UINT_MAX, + .pll0 = SOR_PLL_BG_V17_S(3) | SOR_PLL_ICHPMP(1) | + SOR_PLL_RESISTORSEL | SOR_PLL_VCOCAP(3) | + SOR_PLL_TX_REG_LOAD(0), + .pll1 = SOR_PLL_TMDS_TERM_ENABLE | SOR_PLL_PE_EN, + .pe_current = PE_CURRENT0(PE_CURRENT_5_0_mA) | + PE_CURRENT1(PE_CURRENT_5_0_mA) | + PE_CURRENT2(PE_CURRENT_5_0_mA) | + PE_CURRENT3(PE_CURRENT_5_0_mA), + .drive_current = DRIVE_CURRENT_LANE0(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE1(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE2(DRIVE_CURRENT_5_250_mA) | + DRIVE_CURRENT_LANE3(DRIVE_CURRENT_5_250_mA), + }, +}; + +static void tegra_dc_enable_controller(struct udevice *dev) +{ + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct dc_ctlr *dc = dc_plat->dc; + u32 value; + + value = readl(&dc->disp.disp_win_opt); + value |= HDMI_ENABLE; + writel(value, &dc->disp.disp_win_opt); + + writel(GENERAL_UPDATE, &dc->cmd.state_ctrl); + writel(GENERAL_ACT_REQ, &dc->cmd.state_ctrl); +} + +static void tegra_hdmi_setup_tmds(struct tegra_hdmi_priv *priv, + const struct tmds_config *tmds) +{ + struct hdmi_ctlr *hdmi = priv->hdmi_regmap; + u32 value; + + writel(tmds->pll0, &hdmi->nv_pdisp_sor_pll0); + writel(tmds->pll1, &hdmi->nv_pdisp_sor_pll1); + writel(tmds->pe_current, &hdmi->nv_pdisp_pe_current); + + writel(tmds->drive_current, &hdmi->nv_pdisp_sor_lane_drive_current); + + value = readl(&hdmi->nv_pdisp_sor_lane_drive_current); + value |= BIT(31); + writel(value, &hdmi->nv_pdisp_sor_lane_drive_current); +} + +static int tegra_hdmi_encoder_enable(struct udevice *dev) +{ + struct tegra_dc_plat *dc_plat = dev_get_plat(dev); + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct dc_ctlr *dc = dc_plat->dc; + struct display_timing *dt = &priv->timing; + struct hdmi_ctlr *hdmi = priv->hdmi_regmap; + unsigned long rate, div82; + unsigned int pulse_start, rekey; + int retries = 1000; + u32 value; + int i; + + /* power up sequence */ + value = readl(&hdmi->nv_pdisp_sor_pll0); + value &= ~SOR_PLL_PDBG; + writel(value, &hdmi->nv_pdisp_sor_pll0); + + udelay(20); + + value = readl(&hdmi->nv_pdisp_sor_pll0); + value &= ~SOR_PLL_PWR; + writel(value, &hdmi->nv_pdisp_sor_pll0); + + writel(VSYNC_H_POSITION(1), &dc->disp.disp_timing_opt); + writel(DITHER_CONTROL_DISABLE | BASE_COLOR_SIZE_888, + &dc->disp.disp_color_ctrl); + + /* video_preamble uses h_pulse2 */ + pulse_start = 1 + dt->hsync_len.typ + dt->hback_porch.typ - 10; + + writel(H_PULSE2_ENABLE, &dc->disp.disp_signal_opt0); + + value = PULSE_MODE_NORMAL | PULSE_POLARITY_HIGH | + PULSE_QUAL_VACTIVE | PULSE_LAST_END_A; + writel(value, &dc->disp.h_pulse[H_PULSE2].h_pulse_ctrl); + + value = PULSE_START(pulse_start) | PULSE_END(pulse_start + 8); + writel(value, &dc->disp.h_pulse[H_PULSE2].h_pulse_pos[H_PULSE0_POSITION_A]); + + value = VSYNC_WINDOW_END(0x210) | VSYNC_WINDOW_START(0x200) | + VSYNC_WINDOW_ENABLE; + writel(value, &hdmi->nv_pdisp_hdmi_vsync_window); + + if (dc_plat->pipe) + value = HDMI_SRC_DISPLAYB; + else + value = HDMI_SRC_DISPLAYA; + + if (dt->hactive.typ == 720 && (dt->vactive.typ == 480 || + dt->vactive.typ == 576)) + writel(value | ARM_VIDEO_RANGE_FULL, + &hdmi->nv_pdisp_input_control); + else + writel(value | ARM_VIDEO_RANGE_LIMITED, + &hdmi->nv_pdisp_input_control); + + rate = clock_get_periph_rate(priv->clk->id, priv->clk_parent->id); + div82 = rate / USEC_PER_SEC * 4; + value = SOR_REFCLK_DIV_INT(div82 >> 2) | SOR_REFCLK_DIV_FRAC(div82); + writel(value, &hdmi->nv_pdisp_sor_refclk); + + rekey = HDMI_REKEY_DEFAULT; + value = HDMI_CTRL_REKEY(rekey); + value |= HDMI_CTRL_MAX_AC_PACKET((dt->hsync_len.typ + dt->hback_porch.typ + + dt->hfront_porch.typ - rekey - 18) / 32); + writel(value, &hdmi->nv_pdisp_hdmi_ctrl); + + /* TMDS CONFIG */ + for (i = 0; i < priv->config->num_tmds; i++) { + if (dt->pixelclock.typ <= priv->config->tmds[i].pclk) { + tegra_hdmi_setup_tmds(priv, &priv->config->tmds[i]); + break; + } + } + + writel(SOR_SEQ_PU_PC(0) | SOR_SEQ_PU_PC_ALT(0) | SOR_SEQ_PD_PC(8) | + SOR_SEQ_PD_PC_ALT(8), &hdmi->nv_pdisp_sor_seq_ctl); + + value = SOR_SEQ_INST_WAIT_TIME(1) | SOR_SEQ_INST_WAIT_UNITS_VSYNC | + SOR_SEQ_INST_HALT | SOR_SEQ_INST_PIN_A_LOW | + SOR_SEQ_INST_PIN_B_LOW | SOR_SEQ_INST_DRIVE_PWM_OUT_LO; + + writel(value, &hdmi->nv_pdisp_sor_seq_inst0); + writel(value, &hdmi->nv_pdisp_sor_seq_inst8); + + value = readl(&hdmi->nv_pdisp_sor_cstm); + + value &= ~SOR_CSTM_ROTCLK(~0); + value |= SOR_CSTM_ROTCLK(2); + value |= SOR_CSTM_PLLDIV; + value &= ~SOR_CSTM_LVDS_ENABLE; + value &= ~SOR_CSTM_MODE_MASK; + value |= SOR_CSTM_MODE_TMDS; + + writel(value, &hdmi->nv_pdisp_sor_cstm); + + /* start SOR */ + writel(SOR_PWR_NORMAL_STATE_PU | SOR_PWR_NORMAL_START_NORMAL | + SOR_PWR_SAFE_STATE_PD | SOR_PWR_SETTING_NEW_TRIGGER, + &hdmi->nv_pdisp_sor_pwr); + writel(SOR_PWR_NORMAL_STATE_PU | SOR_PWR_NORMAL_START_NORMAL | + SOR_PWR_SAFE_STATE_PD | SOR_PWR_SETTING_NEW_DONE, + &hdmi->nv_pdisp_sor_pwr); + + do { + if (--retries < 0) + return -ETIME; + value = readl(&hdmi->nv_pdisp_sor_pwr); + } while (value & SOR_PWR_SETTING_NEW_PENDING); + + value = SOR_STATE_ASY_CRCMODE_COMPLETE | + SOR_STATE_ASY_OWNER_HEAD0 | + SOR_STATE_ASY_SUBOWNER_BOTH | + SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A | + SOR_STATE_ASY_DEPOL_POS; + + /* setup sync polarities */ + if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH) + value |= SOR_STATE_ASY_HSYNCPOL_POS; + + if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) + value |= SOR_STATE_ASY_HSYNCPOL_NEG; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH) + value |= SOR_STATE_ASY_VSYNCPOL_POS; + + if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW) + value |= SOR_STATE_ASY_VSYNCPOL_NEG; + + writel(value, &hdmi->nv_pdisp_sor_state2); + + value = SOR_STATE_ASY_HEAD_OPMODE_AWAKE | SOR_STATE_ASY_ORMODE_NORMAL; + writel(value, &hdmi->nv_pdisp_sor_state1); + + writel(0, &hdmi->nv_pdisp_sor_state0); + writel(SOR_STATE_UPDATE, &hdmi->nv_pdisp_sor_state0); + writel(value | SOR_STATE_ATTACHED, + &hdmi->nv_pdisp_sor_state1); + writel(0, &hdmi->nv_pdisp_sor_state0); + + tegra_dc_enable_controller(dev); + + return 0; +} + +/* DDC/CI backlight control */ +static int tegra_hdmi_set_connector(struct udevice *dev, int percent) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct udevice *ddc_entry; + struct i2c_msg msg[1]; + u8 checksum = DDCCI_ENTRY_ADDR << 1; + int i, ret; + + ret = dm_i2c_probe(priv->hdmi_ddc, DDCCI_ENTRY_ADDR, 0, &ddc_entry); + if (ret) { + log_debug("%s: cannot probe DDC/CI entry: error %d\n", + __func__, ret); + return 0; + } + + /* + * payload[1] is length: hithest bit OR last 4 bits indicate + * the number of following bytes (excluding checksum) + */ + u8 payload[7] = { DDCCI_SOURSE_ADDR, BIT(7) | (sizeof(payload) - 3), + DDCCI_COMMAND_WRITE, DDCCI_CTRL_BRIGHTNESS, + (u8)(percent & 0xff), (u8)(percent & 0xff), 0 }; + + /* DDC/CI checksum is a simple XOR of all preceding bytes */ + for (i = 0; i < (sizeof(payload) - 1); i++) + checksum ^= payload[i]; + + payload[6] = checksum; + + msg->addr = DDCCI_ENTRY_ADDR; + msg->flags = 0; + msg->len = sizeof(payload); + msg->buf = payload; + + dm_i2c_xfer(ddc_entry, msg, 1); + + return 0; +} + +static int tegra_hdmi_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static void tegra_hdmi_init_clocks(struct udevice *dev) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + u32 n = priv->timing.pixelclock.typ * 2 / USEC_PER_SEC; + + switch (clock_get_osc_freq()) { + case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */ + case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */ + clock_set_rate(priv->clk_parent->id, n, 12, 0, 8); + break; + + case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */ + clock_set_rate(priv->clk_parent->id, n, 26, 0, 8); + break; + + case CLOCK_OSC_FREQ_13_0: /* OSC is 13Mhz */ + case CLOCK_OSC_FREQ_16_8: /* OSC is 16.8Mhz */ + clock_set_rate(priv->clk_parent->id, n, 13, 0, 8); + break; + + case CLOCK_OSC_FREQ_19_2: + case CLOCK_OSC_FREQ_38_4: + default: + /* + * These are not supported. + */ + break; + } + + clock_start_periph_pll(priv->clk->id, priv->clk_parent->id, + priv->timing.pixelclock.typ); +} + +static bool tegra_hdmi_mode_valid(void *hdmi_priv, const struct display_timing *timing) +{ + struct tegra_hdmi_priv *priv = hdmi_priv; + + if (timing->pixelclock.typ > priv->config->max_pclk) + return false; + + return true; +} + +static int tegra_hdmi_decode_edid(struct udevice *dev) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct udevice *hdmi_edid; + uchar edid_buf[EDID_SIZE] = { 0 }; + int i, ret; + + /* Poll for 1 sec in case EDID is not ready right after hpd */ + for (i = 0; i < 10; i++) { + ret = dm_i2c_probe(priv->hdmi_ddc, HDMI_EDID_I2C_ADDR, 0, + &hdmi_edid); + if (!ret) + break; + + mdelay(100); + } + if (ret) { + log_debug("%s: cannot probe EDID: error %d\n", + __func__, ret); + return ret; + } + + ret = dm_i2c_read(hdmi_edid, 0, edid_buf, sizeof(edid_buf)); + if (ret) { + log_debug("%s: cannot dump EDID buffer: error %d\n", + __func__, ret); + return ret; + } + + ret = edid_get_timing_validate(edid_buf, sizeof(edid_buf), &priv->timing, + &priv->panel_bits_per_colourp, + tegra_hdmi_mode_valid, priv); + if (ret) { + log_debug("%s: cannot decode EDID info: error %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int tegra_hdmi_wait_hpd(struct tegra_hdmi_priv *priv) +{ + int i; + + /* Poll 1 second for HPD signal */ + for (i = 0; i < 10; i++) { + if (dm_gpio_get_value(&priv->hpd)) + return 0; + + mdelay(100); + } + + return -ETIMEDOUT; +} + +static int tegra_hdmi_probe(struct udevice *dev) +{ + struct tegra_hdmi_priv *priv = dev_get_priv(dev); + struct reset_ctl reset_ctl; + int i, ret; + + priv->hdmi_regmap = (struct hdmi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->hdmi_regmap) { + log_debug("%s: no display controller address\n", __func__); + return -EINVAL; + } + + priv->config = (struct tegra_hdmi_config *)dev_get_driver_data(dev); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + log_debug("%s: Could not get HDMI clock: %ld\n", + __func__, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + + priv->clk_parent = devm_clk_get(dev, "parent"); + if (IS_ERR(priv->clk_parent)) { + log_debug("%s: Could not get HDMI clock parent: %ld\n", + __func__, PTR_ERR(priv->clk_parent)); + return PTR_ERR(priv->clk_parent); + } + + for (i = 0; i < ARRAY_SIZE(hdmi_supplies); i++) { + ret = device_get_supply_regulator(dev, hdmi_supplies[i], + &priv->supplies[i]); + if (ret) { + log_debug("%s: cannot get %s %d\n", __func__, + hdmi_supplies[i], ret); + if (ret != -ENOENT) + return log_ret(ret); + } + + ret = regulator_set_enable_if_allowed(priv->supplies[i], true); + if (ret && ret != -ENOSYS) { + log_debug("%s: cannot enable %s: error %d\n", + __func__, hdmi_supplies[i], ret); + return ret; + } + } + + ret = reset_get_by_name(dev, "hdmi", &reset_ctl); + if (ret) { + log_debug("%s: reset_get_by_name() failed: %d\n", + __func__, ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_I2C, dev, + "nvidia,ddc-i2c-bus", + &priv->hdmi_ddc); + if (ret) { + log_debug("%s: cannot get hdmi ddc i2c bus: error %d\n", + __func__, ret); + return ret; + } + + ret = gpio_request_by_name(dev, "nvidia,hpd-gpio", 0, + &priv->hpd, GPIOD_IS_IN); + if (ret) { + log_debug("%s: Could not decode hpd-gpios (%d)\n", + __func__, ret); + return ret; + } + + /* wait for connector */ + ret = tegra_hdmi_wait_hpd(priv); + if (ret) { + /* HPD failed, use default timings */ + memcpy(&priv->timing, &default_720p_timing, + sizeof(default_720p_timing)); + } else { + ret = tegra_hdmi_decode_edid(dev); + if (ret) + memcpy(&priv->timing, &default_720p_timing, + sizeof(default_720p_timing)); + } + + reset_assert(&reset_ctl); + tegra_hdmi_init_clocks(dev); + + mdelay(2); + reset_deassert(&reset_ctl); + + return 0; +} + +static const struct tegra_hdmi_config tegra20_hdmi_config = { + .tmds = tegra20_tmds_config, + .num_tmds = ARRAY_SIZE(tegra20_tmds_config), + .max_pclk = 148500000, /* 1080p */ +}; + +static const struct tegra_hdmi_config tegra30_hdmi_config = { + .tmds = tegra30_tmds_config, + .num_tmds = ARRAY_SIZE(tegra30_tmds_config), + .max_pclk = 148500000, /* 1080p */ +}; + +static const struct video_bridge_ops tegra_hdmi_ops = { + .attach = tegra_hdmi_encoder_enable, + .set_backlight = tegra_hdmi_set_connector, + .get_display_timing = tegra_hdmi_timings, +}; + +static const struct udevice_id tegra_hdmi_ids[] = { + { + .compatible = "nvidia,tegra20-hdmi", + .data = (ulong)&tegra20_hdmi_config + }, { + .compatible = "nvidia,tegra30-hdmi", + .data = (ulong)&tegra30_hdmi_config + }, { + /* sentinel */ + } +}; + +U_BOOT_DRIVER(tegra_hdmi) = { + .name = "tegra_hdmi", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = tegra_hdmi_ids, + .ops = &tegra_hdmi_ops, + .probe = tegra_hdmi_probe, + .plat_auto = sizeof(struct tegra_dc_plat), + .priv_auto = sizeof(struct tegra_hdmi_priv), +}; diff --git a/drivers/video/tegra/hdmi.h b/drivers/video/tegra/hdmi.h new file mode 100644 index 00000000000..d17655973e3 --- /dev/null +++ b/drivers/video/tegra/hdmi.h @@ -0,0 +1,648 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2010 + * NVIDIA Corporation <www.nvidia.com> + */ + +#ifndef _TEGRA_HDMI_H +#define _TEGRA_HDMI_H + +#ifndef __ASSEMBLY__ +#include <linux/bitops.h> +#endif + +/* Register definitions for the Tegra high-definition multimedia interface */ + +/* High-Definition Multimedia Interface (HDMI_) regs */ +struct hdmi_ctlr { + /* Address 0x000 ~ 0x0d2 */ + uint ctxsw; /* _CTXSW */ /* 0x00 */ + + uint nv_pdisp_sor_state0; /* _NV_PDISP_SOR_STATE0 */ + uint nv_pdisp_sor_state1; /* _NV_PDISP_SOR_STATE1 */ + uint nv_pdisp_sor_state2; /* _NV_PDISP_SOR_STATE2 */ + + uint nv_pdisp_rg_hdcp_an_msb; /* _NV_PDISP_RG_HDCP_AN_MSB */ + uint nv_pdisp_rg_hdcp_an_lsb; /* _NV_PDISP_RG_HDCP_AN_LSB */ + uint nv_pdisp_rg_hdcp_cn_msb; /* _NV_PDISP_RG_HDCP_CN_MSB */ + uint nv_pdisp_rg_hdcp_cn_lsb; /* _NV_PDISP_RG_HDCP_CN_LSB */ + uint nv_pdisp_rg_hdcp_aksv_msb; /* _NV_PDISP_RG_HDCP_AKSV_MSB */ + uint nv_pdisp_rg_hdcp_aksv_lsb; /* _NV_PDISP_RG_HDCP_AKSV_LSB */ + uint nv_pdisp_rg_hdcp_bksv_msb; /* _NV_PDISP_RG_HDCP_BKSV_MSB */ + uint nv_pdisp_rg_hdcp_bksv_lsb; /* _NV_PDISP_RG_HDCP_BKSV_LSB */ + uint nv_pdisp_rg_hdcp_cksv_msb; /* _NV_PDISP_RG_HDCP_CKSV_MSB */ + uint nv_pdisp_rg_hdcp_cksv_lsb; /* _NV_PDISP_RG_HDCP_CKSV_LSB */ + uint nv_pdisp_rg_hdcp_dksv_msb; /* _NV_PDISP_RG_HDCP_DKSV_MSB */ + uint nv_pdisp_rg_hdcp_dksv_lsb; /* _NV_PDISP_RG_HDCP_DKSV_LSB */ + uint nv_pdisp_rg_hdcp_ctrl; /* _NV_PDISP_RG_HDCP_CTRL */ /* 0x10 */ + uint nv_pdisp_rg_hdcp_cmode; /* _NV_PDISP_RG_HDCP_CMODE */ + uint nv_pdisp_rg_hdcp_mprime_msb; /* _NV_PDISP_RG_HDCP_MPRIME_MSB */ + uint nv_pdisp_rg_hdcp_mprime_lsb; /* _NV_PDISP_RG_HDCP_MPRIME_LSB */ + uint nv_pdisp_rg_hdcp_sprime_msb; /* _NV_PDISP_RG_HDCP_SPRIME_MSB */ + uint nv_pdisp_rg_hdcp_sprime_lsb2; /* _NV_PDISP_RG_HDCP_SPRIME_LSB2 */ + uint nv_pdisp_rg_hdcp_sprime_lsb1; /* _NV_PDISP_RG_HDCP_SPRIME_LSB1 */ + uint nv_pdisp_rg_hdcp_ri; /* _NV_PDISP_RG_HDCP_RI */ + uint nv_pdisp_rg_hdcp_cs_msb; /* _NV_PDISP_RG_HDCP_CS_MSB */ + uint nv_pdisp_rg_hdcp_cs_lsb; /* _NV_PDISP_RG_HDCP_CS_LSB */ + + uint nv_pdisp_hdmi_audio_emu0; /* _NV_PDISP_HDMI_AUDIO_EMU0 */ + uint nv_pdisp_hdmi_audio_emu_rdata0; /* _NV_PDISP_HDMI_AUDIO_EMU_RDATA0 */ + uint nv_pdisp_hdmi_audio_emu1; /* _NV_PDISP_HDMI_AUDIO_EMU1 */ + uint nv_pdisp_hdmi_audio_emu2; /* _NV_PDISP_HDMI_AUDIO_EMU2 */ + uint nv_pdisp_hdmi_audio_infoframe_ctrl; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL */ + uint nv_pdisp_hdmi_audio_infoframe_status; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS */ + uint nv_pdisp_hdmi_audio_infoframe_header; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER */ /* 0x20 */ + uint nv_pdisp_hdmi_audio_infoframe_subpack0_low; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW */ + uint nv_pdisp_hdmi_audio_infoframe_subpack0_high; /* _NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH */ + + uint nv_pdisp_hdmi_avi_infoframe_ctrl; /* _NV_PDISP_HDMI_AVI_INFOFRAME_CTRL */ + uint nv_pdisp_hdmi_avi_infoframe_status; /* _NV_PDISP_HDMI_AVI_INFOFRAME_STATUS */ + uint nv_pdisp_hdmi_avi_infoframe_header; /* _NV_PDISP_HDMI_AVI_INFOFRAME_HEADER */ + uint nv_pdisp_hdmi_avi_infoframe_subpack0_low; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW */ + uint nv_pdisp_hdmi_avi_infoframe_subpack0_high; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH */ + uint nv_pdisp_hdmi_avi_infoframe_subpack1_low; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW */ + uint nv_pdisp_hdmi_avi_infoframe_subpack1_high; /* _NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH */ + + uint nv_pdisp_hdmi_generic_ctrl; /* _NV_PDISP_HDMI_GENERIC_CTRL */ + uint nv_pdisp_hdmi_generic_status; /* _NV_PDISP_HDMI_GENERIC_STATUS */ + uint nv_pdisp_hdmi_generic_header; /* _NV_PDISP_HDMI_GENERIC_HEADER */ + uint nv_pdisp_hdmi_generic_subpack0_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW */ + uint nv_pdisp_hdmi_generic_subpack0_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH */ + uint nv_pdisp_hdmi_generic_subpack1_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW */ + uint nv_pdisp_hdmi_generic_subpack1_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH */ + uint nv_pdisp_hdmi_generic_subpack2_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW */ + uint nv_pdisp_hdmi_generic_subpack2_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH */ + uint nv_pdisp_hdmi_generic_subpack3_low; /* _NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW */ + uint nv_pdisp_hdmi_generic_subpack3_high; /* _NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH */ + + uint nv_pdisp_hdmi_acr_ctrl; /* _NV_PDISP_HDMI_ACR_CTRL */ + uint nv_pdisp_hdmi_acr_0320_subpack_low; /* _NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0320_subpack_high; /* _NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0441_subpack_low; /* _NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0441_subpack_high; /* _NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0882_subpack_low; /* _NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0882_subpack_high; /* _NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_1764_subpack_low; /* _NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_1764_subpack_high; /* _NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0480_subpack_low; /* _NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0480_subpack_high; /* _NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_0960_subpack_low; /* _NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_0960_subpack_high; /* _NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH */ + uint nv_pdisp_hdmi_acr_1920_subpack_low; /* _NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW */ + uint nv_pdisp_hdmi_acr_1920_subpack_high; /* _NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH */ + + uint nv_pdisp_hdmi_ctrl; /* _NV_PDISP_HDMI_CTRL */ + uint nv_pdisp_hdmi_vsync_keepout; /* _NV_PDISP_HDMI_VSYNC_KEEPOUT */ + uint nv_pdisp_hdmi_vsync_window; /* _NV_PDISP_HDMI_VSYNC_WINDOW */ + uint nv_pdisp_hdmi_gcp_ctrl; /* _NV_PDISP_HDMI_GCP_CTRL */ + uint nv_pdisp_hdmi_gcp_status; /* _NV_PDISP_HDMI_GCP_STATUS */ + uint nv_pdisp_hdmi_gcp_subpack; /* _NV_PDISP_HDMI_GCP_SUBPACK */ + uint nv_pdisp_hdmi_channel_status1; /* _NV_PDISP_HDMI_CHANNEL_STATUS1 */ + uint nv_pdisp_hdmi_channel_status2; /* _NV_PDISP_HDMI_CHANNEL_STATUS2 */ + uint nv_pdisp_hdmi_emu0; /* _NV_PDISP_HDMI_EMU0 */ + uint nv_pdisp_hdmi_emu1; /* _NV_PDISP_HDMI_EMU1 */ + uint nv_pdisp_hdmi_emu1_rdata; /* _NV_PDISP_HDMI_EMU1_RDATA */ + uint nv_pdisp_hdmi_spare; /* _NV_PDISP_HDMI_SPARE */ + uint nv_pdisp_hdmi_spdif_chn_status1; /* _NV_PDISP_HDMI_SPDIF_CHN_STATUS1 */ + uint nv_pdisp_hdmi_spdif_chn_status2; /* _NV_PDISP_HDMI_SPDIF_CHN_STATUS2 */ + + uint nv_pdisp_hdcprif_rom_ctrl; /* _NV_PDISP_HDCPRIF_ROM_CTRL */ + + uint unused; + + uint nv_pdisp_sor_cap; /* _NV_PDISP_SOR_CAP */ + uint nv_pdisp_sor_pwr; /* _NV_PDISP_SOR_PWR */ + uint nv_pdisp_sor_test; /* _NV_PDISP_SOR_TEST */ + uint nv_pdisp_sor_pll0; /* _NV_PDISP_SOR_PLL0 */ + uint nv_pdisp_sor_pll1; /* _NV_PDISP_SOR_PLL1 */ + uint nv_pdisp_sor_pll2; /* _NV_PDISP_SOR_PLL2 */ + uint nv_pdisp_sor_cstm; /* _NV_PDISP_SOR_CSTM */ + uint nv_pdisp_sor_lvds; /* _NV_PDISP_SOR_LVDS */ + uint nv_pdisp_sor_crca; /* _NV_PDISP_SOR_CRCA */ + uint nv_pdisp_sor_crcb; /* _NV_PDISP_SOR_CRCB */ + uint nv_pdisp_sor_blank; /* _NV_PDISP_SOR_BLANK */ + + uint nv_pdisp_sor_seq_ctl; /* _NV_PDISP_SOR_SEQ_CTL */ + uint nv_pdisp_sor_seq_inst0; /* _NV_PDISP_SOR_SEQ_INST0 */ + uint nv_pdisp_sor_seq_inst1; /* _NV_PDISP_SOR_SEQ_INST1 */ + uint nv_pdisp_sor_seq_inst2; /* _NV_PDISP_SOR_SEQ_INST2 */ + uint nv_pdisp_sor_seq_inst3; /* _NV_PDISP_SOR_SEQ_INST3 */ + uint nv_pdisp_sor_seq_inst4; /* _NV_PDISP_SOR_SEQ_INST4 */ + uint nv_pdisp_sor_seq_inst5; /* _NV_PDISP_SOR_SEQ_INST5 */ + uint nv_pdisp_sor_seq_inst6; /* _NV_PDISP_SOR_SEQ_INST6 */ + uint nv_pdisp_sor_seq_inst7; /* _NV_PDISP_SOR_SEQ_INST7 */ + uint nv_pdisp_sor_seq_inst8; /* _NV_PDISP_SOR_SEQ_INST8 */ + uint nv_pdisp_sor_seq_inst9; /* _NV_PDISP_SOR_SEQ_INST9 */ + uint nv_pdisp_sor_seq_insta; /* _NV_PDISP_SOR_SEQ_INSTA */ + uint nv_pdisp_sor_seq_instb; /* _NV_PDISP_SOR_SEQ_INSTB */ + uint nv_pdisp_sor_seq_instc; /* _NV_PDISP_SOR_SEQ_INSTC */ + uint nv_pdisp_sor_seq_instd; /* _NV_PDISP_SOR_SEQ_INSTD */ + uint nv_pdisp_sor_seq_inste; /* _NV_PDISP_SOR_SEQ_INSTE */ + uint nv_pdisp_sor_seq_instf; /* _NV_PDISP_SOR_SEQ_INSTF */ + + uint unused1[2]; + + uint nv_pdisp_sor_vcrca0; /* _NV_PDISP_SOR_VCRCA0 */ + uint nv_pdisp_sor_vcrca1; /* _NV_PDISP_SOR_VCRCA1 */ + uint nv_pdisp_sor_ccrca0; /* _NV_PDISP_SOR_CCRCA0 */ + uint nv_pdisp_sor_ccrca1; /* _NV_PDISP_SOR_CCRCA1 */ + + uint nv_pdisp_sor_edataa0; /* _NV_PDISP_SOR_EDATAA0 */ + uint nv_pdisp_sor_edataa1; /* _NV_PDISP_SOR_EDATAA1 */ + + uint nv_pdisp_sor_counta0; /* _NV_PDISP_SOR_COUNTA0 */ + uint nv_pdisp_sor_counta1; /* _NV_PDISP_SOR_COUNTA1 */ + + uint nv_pdisp_sor_debuga0; /* _NV_PDISP_SOR_DEBUGA0 */ + uint nv_pdisp_sor_debuga1; /* _NV_PDISP_SOR_DEBUGA1 */ + + uint nv_pdisp_sor_trig; /* _NV_PDISP_SOR_TRIG */ + uint nv_pdisp_sor_mscheck; /* _NV_PDISP_SOR_MSCHECK */ + uint nv_pdisp_sor_lane_drive_current; /* _NV_PDISP_SOR_LANE_DRIVE_CURRENT */ + + uint nv_pdisp_audio_debug0; /* _NV_PDISP_AUDIO_DEBUG0 0x7f */ + uint nv_pdisp_audio_debug1; /* _NV_PDISP_AUDIO_DEBUG1 0x80 */ + uint nv_pdisp_audio_debug2; /* _NV_PDISP_AUDIO_DEBUG2 0x81 */ + + uint nv_pdisp_audio_fs1; /* _NV_PDISP_AUDIO_FS1 0x82 */ + uint nv_pdisp_audio_fs2; /* _NV_PDISP_AUDIO_FS2 */ + uint nv_pdisp_audio_fs3; /* _NV_PDISP_AUDIO_FS3 */ + uint nv_pdisp_audio_fs4; /* _NV_PDISP_AUDIO_FS4 */ + uint nv_pdisp_audio_fs5; /* _NV_PDISP_AUDIO_FS5 */ + uint nv_pdisp_audio_fs6; /* _NV_PDISP_AUDIO_FS6 */ + uint nv_pdisp_audio_fs7; /* _NV_PDISP_AUDIO_FS7 0x88 */ + + uint nv_pdisp_audio_pulse_width; /* _NV_PDISP_AUDIO_PULSE_WIDTH */ + uint nv_pdisp_audio_threshold; /* _NV_PDISP_AUDIO_THRESHOLD */ + uint nv_pdisp_audio_cntrl0; /* _NV_PDISP_AUDIO_CNTRL0 */ + uint nv_pdisp_audio_n; /* _NV_PDISP_AUDIO_N */ + uint nv_pdisp_audio_nval[7]; /* _NV_PDISP_AUDIO_NVAL */ + + uint nv_pdisp_hdcprif_rom_timing; /* _NV_PDISP_HDCPRIF_ROM_TIMING */ + uint nv_pdisp_sor_refclk; /* _NV_PDISP_SOR_REFCLK */ + uint nv_pdisp_crc_control; /* _NV_PDISP_CRC_CONTROL */ + uint nv_pdisp_input_control; /* _NV_PDISP_INPUT_CONTROL */ + uint nv_pdisp_scratch; /* _NV_PDISP_SCRATCH */ + uint nv_pdisp_pe_current; /* _NV_PDISP_PE_CURRENT */ + + uint nv_pdisp_key_ctrl; /* _NV_PDISP_KEY_CTRL */ + uint nv_pdisp_key_debug0; /* _NV_PDISP_KEY_DEBUG0 */ + uint nv_pdisp_key_debug1; /* _NV_PDISP_KEY_DEBUG1 */ + uint nv_pdisp_key_debug2; /* _NV_PDISP_KEY_DEBUG2 */ + uint nv_pdisp_key_hdcp_key_0; /* _NV_PDISP_KEY_HDCP_KEY_0 */ + uint nv_pdisp_key_hdcp_key_1; /* _NV_PDISP_KEY_HDCP_KEY_1 */ + uint nv_pdisp_key_hdcp_key_2; /* _NV_PDISP_KEY_HDCP_KEY_2 */ + uint nv_pdisp_key_hdcp_key_3; /* _NV_PDISP_KEY_HDCP_KEY_3 */ + uint nv_pdisp_key_hdcp_key_trig; /* _NV_PDISP_KEY_HDCP_KEY_3 */ + uint nv_pdisp_key_skey_index; /* _NV_PDISP_KEY_HDCP_KEY_3 */ /* 0xa3 */ + + uint unused2[8]; + + uint nv_pdisp_sor_audio_cntrl0; /* _NV_PDISP_SOR_AUDIO_CNTRL0 */ /* 0xac */ + uint nv_pdisp_sor_audio_debug; /* _NV_PDISP_SOR_AUDIO_DEBUG */ + uint nv_pdisp_sor_audio_spare0; /* _NV_PDISP_SOR_AUDIO_SPARE0 */ + uint nv_pdisp_sor_audio_nval[7]; /* _NV_PDISP_SOR_AUDIO_NVAL 0xaf ~ 0xb5 */ + uint nv_pdisp_sor_audio_hda_scratch[4]; /* _NV_PDISP_SOR_AUDIO_HDA_SCRATCH 0xb6 ~ 0xb9 */ + uint nv_pdisp_sor_audio_hda_codec_scratch[2]; /* _NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH 0xba ~ 0xbb */ + + uint nv_pdisp_sor_audio_hda_eld_bufwr; /* _NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR */ + uint nv_pdisp_sor_audio_hda_presense; /* _NV_PDISP_SOR_AUDIO_HDA_PRESENSE */ + uint nv_pdisp_sor_audio_hda_cp; /* _NV_PDISP_SOR_AUDIO_HDA_CP */ + uint nv_pdisp_sor_audio_aval[8]; /* _NV_PDISP_SOR_AUDIO_AVAL */ + uint nv_pdisp_sor_audio_gen_ctrl; /* _NV_PDISP_SOR_AUDIO_GEN_CTRL */ + + uint unused3[4]; + + uint nv_pdisp_int_status; /* _NV_PDISP_INT_STATUS */ + uint nv_pdisp_int_mask; /* _NV_PDISP_INT_MASK */ + uint nv_pdisp_int_enable; /* _NV_PDISP_INT_ENABLE */ + + uint unused4[2]; + + uint nv_pdisp_sor_io_peak_current; /* _NV_PDISP_SOR_IO_PEAK_CURRENT */ + uint nv_pdisp_sor_pad_ctls0; /* _NV_PDISP_SOR_PAD_CTLS0 */ +}; + +/* HDMI_NV_PDISP_SOR_STATE0 0x01 */ +#define SOR_STATE_UPDATE BIT(0) + +/* HDMI_NV_PDISP_SOR_STATE1 0x02 */ +#define SOR_STATE_ASY_HEAD_OPMODE_AWAKE BIT(1) +#define SOR_STATE_ASY_ORMODE_NORMAL BIT(2) +#define SOR_STATE_ATTACHED BIT(3) + +/* HDMI_NV_PDISP_SOR_STATE2 0x03 */ +#define SOR_STATE_ASY_OWNER_NONE (0 << 0) +#define SOR_STATE_ASY_OWNER_HEAD0 (1 << 0) +#define SOR_STATE_ASY_SUBOWNER_NONE (0 << 4) +#define SOR_STATE_ASY_SUBOWNER_SUBHEAD0 (1 << 4) +#define SOR_STATE_ASY_SUBOWNER_SUBHEAD1 (2 << 4) +#define SOR_STATE_ASY_SUBOWNER_BOTH (3 << 4) +#define SOR_STATE_ASY_CRCMODE_ACTIVE (0 << 6) +#define SOR_STATE_ASY_CRCMODE_COMPLETE (1 << 6) +#define SOR_STATE_ASY_CRCMODE_NON_ACTIVE (2 << 6) +#define SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A (1 << 8) +#define SOR_STATE_ASY_PROTOCOL_CUSTOM (15 << 8) +#define SOR_STATE_ASY_HSYNCPOL_POS (0 << 12) +#define SOR_STATE_ASY_HSYNCPOL_NEG (1 << 12) +#define SOR_STATE_ASY_VSYNCPOL_POS (0 << 13) +#define SOR_STATE_ASY_VSYNCPOL_NEG (1 << 13) +#define SOR_STATE_ASY_DEPOL_POS (0 << 14) +#define SOR_STATE_ASY_DEPOL_NEG (1 << 14) + +#define INFOFRAME_CTRL_ENABLE BIT(0) +#define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0) +#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8) +#define INFOFRAME_HEADER_LEN(x) (((x) & 0x0f) << 16) + +/* HDMI_NV_PDISP_HDMI_GENERIC_CTRL 0x2a */ +#define GENERIC_CTRL_ENABLE BIT(0) +#define GENERIC_CTRL_OTHER BIT(4) +#define GENERIC_CTRL_SINGLE BIT(8) +#define GENERIC_CTRL_HBLANK BIT(12) +#define GENERIC_CTRL_AUDIO BIT(16) + +/* HDMI_NV_PDISP_HDMI_ACR_* */ +#define ACR_SUBPACK_CTS(x) (((x) & 0xffffff) << 8) +#define ACR_SUBPACK_N(x) (((x) & 0xffffff) << 0) +#define ACR_ENABLE BIT(31) + +/* HDMI_NV_PDISP_HDMI_CTRL 0x44 */ +#define HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0) +#define HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16) +#define HDMI_CTRL_ENABLE BIT(30) + +/* HDMI_NV_PDISP_HDMI_VSYNC_* */ +#define VSYNC_WINDOW_END(x) (((x) & 0x3ff) << 0) +#define VSYNC_WINDOW_START(x) (((x) & 0x3ff) << 16) +#define VSYNC_WINDOW_ENABLE BIT(31) + +/* HDMI_NV_PDISP_HDMI_SPARE 0x4f */ +#define SPARE_HW_CTS BIT(0) +#define SPARE_FORCE_SW_CTS BIT(1) +#define SPARE_CTS_RESET_VAL(x) (((x) & 0x7) << 16) + +/* HDMI_NV_PDISP_SOR_PWR 0x55 */ +#define SOR_PWR_NORMAL_STATE_PD (0 << 0) +#define SOR_PWR_NORMAL_STATE_PU (1 << 0) +#define SOR_PWR_NORMAL_START_NORMAL (0 << 1) +#define SOR_PWR_NORMAL_START_ALT (1 << 1) +#define SOR_PWR_SAFE_STATE_PD (0 << 16) +#define SOR_PWR_SAFE_STATE_PU (1 << 16) +#define SOR_PWR_SETTING_NEW_DONE (0 << 31) +#define SOR_PWR_SETTING_NEW_PENDING (1 << 31) +#define SOR_PWR_SETTING_NEW_TRIGGER (1 << 31) + +/* HDMI_NV_PDISP_SOR_PLL0 0x57 */ +#define SOR_PLL_PWR BIT(0) +#define SOR_PLL_PDBG BIT(1) +#define SOR_PLL_VCAPD BIT(2) +#define SOR_PLL_PDPORT BIT(3) +#define SOR_PLL_RESISTORSEL BIT(4) +#define SOR_PLL_PULLDOWN BIT(5) +#define SOR_PLL_VCOCAP(x) (((x) & 0xf) << 8) +#define SOR_PLL_BG_V17_S(x) (((x) & 0xf) << 12) +#define SOR_PLL_FILTER(x) (((x) & 0xf) << 16) +#define SOR_PLL_ICHPMP(x) (((x) & 0xf) << 24) +#define SOR_PLL_TX_REG_LOAD(x) (((x) & 0xf) << 28) + +/* HDMI_NV_PDISP_SOR_PLL1 0x58 */ +#define SOR_PLL_TMDS_TERM_ENABLE BIT(8) +#define SOR_PLL_TMDS_TERMADJ(x) (((x) & 0xf) << 9) +#define SOR_PLL_LOADADJ(x) (((x) & 0xf) << 20) +#define SOR_PLL_PE_EN BIT(28) +#define SOR_PLL_HALF_FULL_PE BIT(29) +#define SOR_PLL_S_D_PIN_PE BIT(30) + +/* HDMI_NV_PDISP_SOR_CSTM 0x5a */ +#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24) +#define SOR_CSTM_PLLDIV BIT(21) +#define SOR_CSTM_LVDS_ENABLE BIT(16) +#define SOR_CSTM_MODE_LVDS (0 << 12) +#define SOR_CSTM_MODE_TMDS (1 << 12) +#define SOR_CSTM_MODE_MASK (3 << 12) + +/* HDMI_NV_PDISP_SOR_SEQ_CTL 0x5f */ +#define SOR_SEQ_PU_PC(x) (((x) & 0xf) << 0) +#define SOR_SEQ_PU_PC_ALT(x) (((x) & 0xf) << 4) +#define SOR_SEQ_PD_PC(x) (((x) & 0xf) << 8) +#define SOR_SEQ_PD_PC_ALT(x) (((x) & 0xf) << 12) +#define SOR_SEQ_PC(x) (((x) & 0xf) << 16) +#define SOR_SEQ_STATUS BIT(28) +#define SOR_SEQ_SWITCH BIT(30) + +/* HDMI_NV_PDISP_SOR_SEQ_INST(x) (0x60 + (x)) */ +#define SOR_SEQ_INST_WAIT_TIME(x) (((x) & 0x3ff) << 0) +#define SOR_SEQ_INST_WAIT_UNITS_VSYNC (2 << 12) +#define SOR_SEQ_INST_HALT (1 << 15) +#define SOR_SEQ_INST_PIN_A_LOW (0 << 21) +#define SOR_SEQ_INST_PIN_A_HIGH (1 << 21) +#define SOR_SEQ_INST_PIN_B_LOW (0 << 22) +#define SOR_SEQ_INST_PIN_B_HIGH (1 << 22) +#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23) + +/* HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT 0x7e */ +#define DRIVE_CURRENT_LANE0(x) (((x) & 0x3f) << 0) +#define DRIVE_CURRENT_LANE1(x) (((x) & 0x3f) << 8) +#define DRIVE_CURRENT_LANE2(x) (((x) & 0x3f) << 16) +#define DRIVE_CURRENT_LANE3(x) (((x) & 0x3f) << 24) +#define DRIVE_CURRENT_LANE0_T114(x) (((x) & 0x7f) << 0) +#define DRIVE_CURRENT_LANE1_T114(x) (((x) & 0x7f) << 8) +#define DRIVE_CURRENT_LANE2_T114(x) (((x) & 0x7f) << 16) +#define DRIVE_CURRENT_LANE3_T114(x) (((x) & 0x7f) << 24) + +/* Drive current list */ +enum { + DRIVE_CURRENT_1_500_mA, + DRIVE_CURRENT_1_875_mA, + DRIVE_CURRENT_2_250_mA, + DRIVE_CURRENT_2_625_mA, + DRIVE_CURRENT_3_000_mA, + DRIVE_CURRENT_3_375_mA, + DRIVE_CURRENT_3_750_mA, + DRIVE_CURRENT_4_125_mA, + DRIVE_CURRENT_4_500_mA, + DRIVE_CURRENT_4_875_mA, + DRIVE_CURRENT_5_250_mA, + DRIVE_CURRENT_5_625_mA, + DRIVE_CURRENT_6_000_mA, + DRIVE_CURRENT_6_375_mA, + DRIVE_CURRENT_6_750_mA, + DRIVE_CURRENT_7_125_mA, + DRIVE_CURRENT_7_500_mA, + DRIVE_CURRENT_7_875_mA, + DRIVE_CURRENT_8_250_mA, + DRIVE_CURRENT_8_625_mA, + DRIVE_CURRENT_9_000_mA, + DRIVE_CURRENT_9_375_mA, + DRIVE_CURRENT_9_750_mA, + DRIVE_CURRENT_10_125_mA, + DRIVE_CURRENT_10_500_mA, + DRIVE_CURRENT_10_875_mA, + DRIVE_CURRENT_11_250_mA, + DRIVE_CURRENT_11_625_mA, + DRIVE_CURRENT_12_000_mA, + DRIVE_CURRENT_12_375_mA, + DRIVE_CURRENT_12_750_mA, + DRIVE_CURRENT_13_125_mA, + DRIVE_CURRENT_13_500_mA, + DRIVE_CURRENT_13_875_mA, + DRIVE_CURRENT_14_250_mA, + DRIVE_CURRENT_14_625_mA, + DRIVE_CURRENT_15_000_mA, + DRIVE_CURRENT_15_375_mA, + DRIVE_CURRENT_15_750_mA, + DRIVE_CURRENT_16_125_mA, + DRIVE_CURRENT_16_500_mA, + DRIVE_CURRENT_16_875_mA, + DRIVE_CURRENT_17_250_mA, + DRIVE_CURRENT_17_625_mA, + DRIVE_CURRENT_18_000_mA, + DRIVE_CURRENT_18_375_mA, + DRIVE_CURRENT_18_750_mA, + DRIVE_CURRENT_19_125_mA, + DRIVE_CURRENT_19_500_mA, + DRIVE_CURRENT_19_875_mA, + DRIVE_CURRENT_20_250_mA, + DRIVE_CURRENT_20_625_mA, + DRIVE_CURRENT_21_000_mA, + DRIVE_CURRENT_21_375_mA, + DRIVE_CURRENT_21_750_mA, + DRIVE_CURRENT_22_125_mA, + DRIVE_CURRENT_22_500_mA, + DRIVE_CURRENT_22_875_mA, + DRIVE_CURRENT_23_250_mA, + DRIVE_CURRENT_23_625_mA, + DRIVE_CURRENT_24_000_mA, + DRIVE_CURRENT_24_375_mA, + DRIVE_CURRENT_24_750_mA, +}; + +/* Drive current list for T114 */ +enum { + DRIVE_CURRENT_0_000_mA_T114, + DRIVE_CURRENT_0_400_mA_T114, + DRIVE_CURRENT_0_800_mA_T114, + DRIVE_CURRENT_1_200_mA_T114, + DRIVE_CURRENT_1_600_mA_T114, + DRIVE_CURRENT_2_000_mA_T114, + DRIVE_CURRENT_2_400_mA_T114, + DRIVE_CURRENT_2_800_mA_T114, + DRIVE_CURRENT_3_200_mA_T114, + DRIVE_CURRENT_3_600_mA_T114, + DRIVE_CURRENT_4_000_mA_T114, + DRIVE_CURRENT_4_400_mA_T114, + DRIVE_CURRENT_4_800_mA_T114, + DRIVE_CURRENT_5_200_mA_T114, + DRIVE_CURRENT_5_600_mA_T114, + DRIVE_CURRENT_6_000_mA_T114, + DRIVE_CURRENT_6_400_mA_T114, + DRIVE_CURRENT_6_800_mA_T114, + DRIVE_CURRENT_7_200_mA_T114, + DRIVE_CURRENT_7_600_mA_T114, + DRIVE_CURRENT_8_000_mA_T114, + DRIVE_CURRENT_8_400_mA_T114, + DRIVE_CURRENT_8_800_mA_T114, + DRIVE_CURRENT_9_200_mA_T114, + DRIVE_CURRENT_9_600_mA_T114, + DRIVE_CURRENT_10_000_mA_T114, + DRIVE_CURRENT_10_400_mA_T114, + DRIVE_CURRENT_10_800_mA_T114, + DRIVE_CURRENT_11_200_mA_T114, + DRIVE_CURRENT_11_600_mA_T114, + DRIVE_CURRENT_12_000_mA_T114, + DRIVE_CURRENT_12_400_mA_T114, + DRIVE_CURRENT_12_800_mA_T114, + DRIVE_CURRENT_13_200_mA_T114, + DRIVE_CURRENT_13_600_mA_T114, + DRIVE_CURRENT_14_000_mA_T114, + DRIVE_CURRENT_14_400_mA_T114, + DRIVE_CURRENT_14_800_mA_T114, + DRIVE_CURRENT_15_200_mA_T114, + DRIVE_CURRENT_15_600_mA_T114, + DRIVE_CURRENT_16_000_mA_T114, + DRIVE_CURRENT_16_400_mA_T114, + DRIVE_CURRENT_16_800_mA_T114, + DRIVE_CURRENT_17_200_mA_T114, + DRIVE_CURRENT_17_600_mA_T114, + DRIVE_CURRENT_18_000_mA_T114, + DRIVE_CURRENT_18_400_mA_T114, + DRIVE_CURRENT_18_800_mA_T114, + DRIVE_CURRENT_19_200_mA_T114, + DRIVE_CURRENT_19_600_mA_T114, + DRIVE_CURRENT_20_000_mA_T114, + DRIVE_CURRENT_20_400_mA_T114, + DRIVE_CURRENT_20_800_mA_T114, + DRIVE_CURRENT_21_200_mA_T114, + DRIVE_CURRENT_21_600_mA_T114, + DRIVE_CURRENT_22_000_mA_T114, + DRIVE_CURRENT_22_400_mA_T114, + DRIVE_CURRENT_22_800_mA_T114, + DRIVE_CURRENT_23_200_mA_T114, + DRIVE_CURRENT_23_600_mA_T114, + DRIVE_CURRENT_24_000_mA_T114, + DRIVE_CURRENT_24_400_mA_T114, + DRIVE_CURRENT_24_800_mA_T114, + DRIVE_CURRENT_25_200_mA_T114, + DRIVE_CURRENT_25_400_mA_T114, + DRIVE_CURRENT_25_800_mA_T114, + DRIVE_CURRENT_26_200_mA_T114, + DRIVE_CURRENT_26_600_mA_T114, + DRIVE_CURRENT_27_000_mA_T114, + DRIVE_CURRENT_27_400_mA_T114, + DRIVE_CURRENT_27_800_mA_T114, + DRIVE_CURRENT_28_200_mA_T114, +}; + +/* HDMI_NV_PDISP_AUDIO_FS */ +#define AUDIO_FS_LOW(x) (((x) & 0xfff) << 0) +#define AUDIO_FS_HIGH(x) (((x) & 0xfff) << 16) + +/* HDMI_NV_PDISP_AUDIO_CNTRL0 0x8b */ +#define AUDIO_CNTRL0_ERROR_TOLERANCE(x) (((x) & 0xff) << 0) +#define AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20) +#define AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20) +#define AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20) +#define AUDIO_CNTRL0_FRAMES_PER_BLOCK(x) (((x) & 0xff) << 24) + +/* HDMI_NV_PDISP_AUDIO_N 0x8c */ +#define AUDIO_N_VALUE(x) (((x) & 0xfffff) << 0) +#define AUDIO_N_RESETF (1 << 20) +#define AUDIO_N_GENERATE_NORMAL (0 << 24) +#define AUDIO_N_GENERATE_ALTERNATE (1 << 24) + +/* HDMI_NV_PDISP_SOR_REFCLK 0x95 */ +#define SOR_REFCLK_DIV_INT(x) (((x) & 0xff) << 8) +#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x03) << 6) + +/* HDMI_NV_PDISP_INPUT_CONTROL 0x97 */ +#define HDMI_SRC_DISPLAYA (0 << 0) +#define HDMI_SRC_DISPLAYB (1 << 0) +#define ARM_VIDEO_RANGE_FULL (0 << 1) +#define ARM_VIDEO_RANGE_LIMITED (1 << 1) + +/* HDMI_NV_PDISP_PE_CURRENT 0x99 */ +#define PE_CURRENT0(x) (((x) & 0xf) << 0) +#define PE_CURRENT1(x) (((x) & 0xf) << 8) +#define PE_CURRENT2(x) (((x) & 0xf) << 16) +#define PE_CURRENT3(x) (((x) & 0xf) << 24) + +enum { + PE_CURRENT_0_0_mA, + PE_CURRENT_0_5_mA, + PE_CURRENT_1_0_mA, + PE_CURRENT_1_5_mA, + PE_CURRENT_2_0_mA, + PE_CURRENT_2_5_mA, + PE_CURRENT_3_0_mA, + PE_CURRENT_3_5_mA, + PE_CURRENT_4_0_mA, + PE_CURRENT_4_5_mA, + PE_CURRENT_5_0_mA, + PE_CURRENT_5_5_mA, + PE_CURRENT_6_0_mA, + PE_CURRENT_6_5_mA, + PE_CURRENT_7_0_mA, + PE_CURRENT_7_5_mA, +}; + +enum { + PE_CURRENT_0_mA_T114, + PE_CURRENT_1_mA_T114, + PE_CURRENT_2_mA_T114, + PE_CURRENT_3_mA_T114, + PE_CURRENT_4_mA_T114, + PE_CURRENT_5_mA_T114, + PE_CURRENT_6_mA_T114, + PE_CURRENT_7_mA_T114, + PE_CURRENT_8_mA_T114, + PE_CURRENT_9_mA_T114, + PE_CURRENT_10_mA_T114, + PE_CURRENT_11_mA_T114, + PE_CURRENT_12_mA_T114, + PE_CURRENT_13_mA_T114, + PE_CURRENT_14_mA_T114, + PE_CURRENT_15_mA_T114, +}; + +/* HDMI_NV_PDISP_SOR_AUDIO_CNTRL0 0xac */ +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20) +#define SOR_AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29) + +/* HDMI_NV_PDISP_SOR_AUDIO_SPARE0 0xae */ +#define SOR_AUDIO_SPARE0_HBR_ENABLE BIT(27) + +/* HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0 0xba */ +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID BIT(30) +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff + +/* HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE 0xbd */ +#define SOR_AUDIO_HDA_PRESENSE_VALID BIT(1) +#define SOR_AUDIO_HDA_PRESENSE_PRESENT BIT(0) + +/* HDMI_NV_PDISP_INT_STATUS 0xcc */ +#define INT_SCRATCH BIT(3) +#define INT_CP_REQUEST BIT(2) +#define INT_CODEC_SCRATCH1 BIT(1) +#define INT_CODEC_SCRATCH0 BIT(0) + +/* HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT 0xd1 */ +#define PEAK_CURRENT_LANE0(x) (((x) & 0x7f) << 0) +#define PEAK_CURRENT_LANE1(x) (((x) & 0x7f) << 8) +#define PEAK_CURRENT_LANE2(x) (((x) & 0x7f) << 16) +#define PEAK_CURRENT_LANE3(x) (((x) & 0x7f) << 24) + +enum { + PEAK_CURRENT_0_000_mA, + PEAK_CURRENT_0_200_mA, + PEAK_CURRENT_0_400_mA, + PEAK_CURRENT_0_600_mA, + PEAK_CURRENT_0_800_mA, + PEAK_CURRENT_1_000_mA, + PEAK_CURRENT_1_200_mA, + PEAK_CURRENT_1_400_mA, + PEAK_CURRENT_1_600_mA, + PEAK_CURRENT_1_800_mA, + PEAK_CURRENT_2_000_mA, + PEAK_CURRENT_2_200_mA, + PEAK_CURRENT_2_400_mA, + PEAK_CURRENT_2_600_mA, + PEAK_CURRENT_2_800_mA, + PEAK_CURRENT_3_000_mA, + PEAK_CURRENT_3_200_mA, + PEAK_CURRENT_3_400_mA, + PEAK_CURRENT_3_600_mA, + PEAK_CURRENT_3_800_mA, + PEAK_CURRENT_4_000_mA, + PEAK_CURRENT_4_200_mA, + PEAK_CURRENT_4_400_mA, + PEAK_CURRENT_4_600_mA, + PEAK_CURRENT_4_800_mA, + PEAK_CURRENT_5_000_mA, + PEAK_CURRENT_5_200_mA, + PEAK_CURRENT_5_400_mA, + PEAK_CURRENT_5_600_mA, + PEAK_CURRENT_5_800_mA, + PEAK_CURRENT_6_000_mA, + PEAK_CURRENT_6_200_mA, + PEAK_CURRENT_6_400_mA, + PEAK_CURRENT_6_600_mA, + PEAK_CURRENT_6_800_mA, + PEAK_CURRENT_7_000_mA, + PEAK_CURRENT_7_200_mA, + PEAK_CURRENT_7_400_mA, + PEAK_CURRENT_7_600_mA, + PEAK_CURRENT_7_800_mA, + PEAK_CURRENT_8_000_mA, + PEAK_CURRENT_8_200_mA, + PEAK_CURRENT_8_400_mA, + PEAK_CURRENT_8_600_mA, + PEAK_CURRENT_8_800_mA, + PEAK_CURRENT_9_000_mA, + PEAK_CURRENT_9_200_mA, + PEAK_CURRENT_9_400_mA, +}; + +#endif /* _TEGRA_HDMI_H */ diff --git a/drivers/video/tegra/host1x.c b/drivers/video/tegra/host1x.c new file mode 100644 index 00000000000..58ab871a3b4 --- /dev/null +++ b/drivers/video/tegra/host1x.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2025 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#include <dm.h> +#include <clk.h> +#include <log.h> +#include <reset.h> +#include <linux/delay.h> + +#include <asm/arch/clock.h> +#include <asm/arch-tegra/clk_rst.h> + +struct tegra_host1x_info { + u32 clk_parent; + u32 rate; +}; + +static int tegra_host1x_probe(struct udevice *dev) +{ + struct clk *clk; + struct reset_ctl reset_ctl; + const struct tegra_host1x_info *info; + int ret; + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) { + log_debug("%s: cannot get HOST1X clock: %ld\n", + __func__, PTR_ERR(clk)); + return PTR_ERR(clk); + } + + ret = reset_get_by_name(dev, "host1x", &reset_ctl); + if (ret) { + log_debug("%s: cannot get HOST1X reset: %d\n", + __func__, ret); + return ret; + } + + info = (struct tegra_host1x_info *)dev_get_driver_data(dev); + + reset_assert(&reset_ctl); + clock_start_periph_pll(clk->id, info->clk_parent, info->rate); + + mdelay(2); + reset_deassert(&reset_ctl); + + return 0; +} + +static const struct tegra_host1x_info tegra20_host1x_info = { + .clk_parent = CLOCK_ID_CGENERAL, + .rate = 150000000, /* 150 MHz */ +}; + +static const struct tegra_host1x_info tegra114_host1x_info = { + .clk_parent = CLOCK_ID_PERIPH, + .rate = 136000000, /* 136 MHz */ +}; + +static const struct udevice_id tegra_host1x_ids[] = { + { + .compatible = "nvidia,tegra20-host1x", + .data = (ulong)&tegra20_host1x_info + }, { + .compatible = "nvidia,tegra30-host1x", + .data = (ulong)&tegra20_host1x_info + }, { + .compatible = "nvidia,tegra114-host1x", + .data = (ulong)&tegra114_host1x_info + }, { + .compatible = "nvidia,tegra124-host1x", + .data = (ulong)&tegra114_host1x_info + }, { + /* sentinel */ + } +}; + +U_BOOT_DRIVER(tegra_host1x) = { + .name = "tegra_host1x", + .id = UCLASS_SIMPLE_BUS, + .of_match = tegra_host1x_ids, + .probe = tegra_host1x_probe, + .flags = DM_FLAG_PRE_RELOC, +}; diff --git a/drivers/video/tegra/mipi-phy.c b/drivers/video/tegra/mipi-phy.c new file mode 100644 index 00000000000..576262e405d --- /dev/null +++ b/drivers/video/tegra/mipi-phy.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 NVIDIA Corporation + */ + +#include <linux/err.h> + +#include "mipi-phy.h" + +/* + * Default D-PHY timings based on MIPI D-PHY specification. Derived from the + * valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY + * specification (v1.2) with minor adjustments. + */ +int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, + unsigned long period) +{ + timing->clkmiss = 0; + timing->clkpost = 70 + 52 * period; + timing->clkpre = 8; + timing->clkprepare = 65; + timing->clksettle = 95; + timing->clktermen = 0; + timing->clktrail = 80; + timing->clkzero = 260; + timing->dtermen = 0; + timing->eot = 0; + timing->hsexit = 120; + timing->hsprepare = 65 + 5 * period; + timing->hszero = 145 + 5 * period; + timing->hssettle = 85 + 6 * period; + timing->hsskip = 40; + + /* + * The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40) + * contains this formula as: + * + * T_HS-TRAIL = max(n * 8 * period, 60 + n * 4 * period) + * + * where n = 1 for forward-direction HS mode and n = 4 for reverse- + * direction HS mode. There's only one setting and this function does + * not parameterize on anything other that period, so this code will + * assumes that reverse-direction HS mode is supported and uses n = 4. + */ + timing->hstrail = max(4 * 8 * period, 60 + 4 * 4 * period); + + timing->init = 100000; + timing->lpx = 60; + timing->taget = 5 * timing->lpx; + timing->tago = 4 * timing->lpx; + timing->tasure = 2 * timing->lpx; + timing->wakeup = 1000000; + + return 0; +} + +/* + * Validate D-PHY timing according to MIPI D-PHY specification + * (v1.2, Section 6.9 "Global Operation Timing Parameters"). + */ +int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, + unsigned long period) +{ + if (timing->clkmiss > 60) + return -EINVAL; + + if (timing->clkpost < (60 + 52 * period)) + return -EINVAL; + + if (timing->clkpre < 8) + return -EINVAL; + + if (timing->clkprepare < 38 || timing->clkprepare > 95) + return -EINVAL; + + if (timing->clksettle < 95 || timing->clksettle > 300) + return -EINVAL; + + if (timing->clktermen > 38) + return -EINVAL; + + if (timing->clktrail < 60) + return -EINVAL; + + if (timing->clkprepare + timing->clkzero < 300) + return -EINVAL; + + if (timing->dtermen > 35 + 4 * period) + return -EINVAL; + + if (timing->eot > 105 + 12 * period) + return -EINVAL; + + if (timing->hsexit < 100) + return -EINVAL; + + if (timing->hsprepare < 40 + 4 * period || + timing->hsprepare > 85 + 6 * period) + return -EINVAL; + + if (timing->hsprepare + timing->hszero < 145 + 10 * period) + return -EINVAL; + + if ((timing->hssettle < 85 + 6 * period) || + (timing->hssettle > 145 + 10 * period)) + return -EINVAL; + + if (timing->hsskip < 40 || timing->hsskip > 55 + 4 * period) + return -EINVAL; + + if (timing->hstrail < max(8 * period, 60 + 4 * period)) + return -EINVAL; + + if (timing->init < 100000) + return -EINVAL; + + if (timing->lpx < 50) + return -EINVAL; + + if (timing->taget != 5 * timing->lpx) + return -EINVAL; + + if (timing->tago != 4 * timing->lpx) + return -EINVAL; + + if (timing->tasure < timing->lpx || timing->tasure > 2 * timing->lpx) + return -EINVAL; + + if (timing->wakeup < 1000000) + return -EINVAL; + + return 0; +} diff --git a/drivers/video/tegra/mipi-phy.h b/drivers/video/tegra/mipi-phy.h new file mode 100644 index 00000000000..41889a75035 --- /dev/null +++ b/drivers/video/tegra/mipi-phy.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 NVIDIA Corporation + */ + +#ifndef DRM_TEGRA_MIPI_PHY_H +#define DRM_TEGRA_MIPI_PHY_H + +/* + * D-PHY timing parameters + * + * A detailed description of these parameters can be found in the MIPI + * Alliance Specification for D-PHY, Section 5.9 "Global Operation Timing + * Parameters". + * + * All parameters are specified in nanoseconds. + */ +struct mipi_dphy_timing { + unsigned int clkmiss; + unsigned int clkpost; + unsigned int clkpre; + unsigned int clkprepare; + unsigned int clksettle; + unsigned int clktermen; + unsigned int clktrail; + unsigned int clkzero; + unsigned int dtermen; + unsigned int eot; + unsigned int hsexit; + unsigned int hsprepare; + unsigned int hszero; + unsigned int hssettle; + unsigned int hsskip; + unsigned int hstrail; + unsigned int init; + unsigned int lpx; + unsigned int taget; + unsigned int tago; + unsigned int tasure; + unsigned int wakeup; +}; + +int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, + unsigned long period); +int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, + unsigned long period); + +#endif diff --git a/drivers/video/tegra/mipi.c b/drivers/video/tegra/mipi.c new file mode 100644 index 00000000000..a4f4343d008 --- /dev/null +++ b/drivers/video/tegra/mipi.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com> + */ + +#include <dm.h> +#include <clk.h> +#include <misc.h> +#include <linux/delay.h> +#include <linux/iopoll.h> + +#include <asm/arch/clock.h> +#include <asm/io.h> + +/* MIPI control registers 0x00 ~ 0x74 */ +struct mipi_ctlr { + uint mipi_cal_ctrl; + uint mipi_cal_autocal_ctrl; + uint mipi_cal_status; + + uint unused1[2]; + + uint mipi_cal_config_csia; + uint mipi_cal_config_csib; + uint mipi_cal_config_csic; + uint mipi_cal_config_csid; + uint mipi_cal_config_csie; + + uint unused2[4]; + + uint mipi_cal_config_dsia; + uint mipi_cal_config_dsib; + uint mipi_cal_config_dsic; + uint mipi_cal_config_dsid; + + uint unused3[4]; + + uint mipi_cal_bias_pad_cfg0; + uint mipi_cal_bias_pad_cfg1; + uint mipi_cal_bias_pad_cfg2; + + uint mipi_cal_dsia_config_2; + uint mipi_cal_dsib_config_2; + uint mipi_cal_cilc_config_2; + uint mipi_cal_cild_config_2; + uint mipi_cal_csie_config_2; +}; + +#define MIPI_DSIA_PADS 0x60 +#define MIPI_DSIB_PADS 0x180 + +#define MIPI_CAL_CTRL_NOISE_FILTER(x) (((x) & 0xf) << 26) +#define MIPI_CAL_CTRL_PRESCALE(x) (((x) & 0x3) << 24) +#define MIPI_CAL_CTRL_CLKEN_OVR BIT(4) +#define MIPI_CAL_CTRL_START BIT(0) + +#define MIPI_CAL_STATUS_DONE BIT(16) +#define MIPI_CAL_STATUS_ACTIVE BIT(0) + +#define MIPI_CAL_OVERIDE(x) (((x) & 0x1) << 30) +#define MIPI_CAL_SEL(x) (((x) & 0x1) << 21) +#define MIPI_CAL_HSPDOS(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_HSPUOS(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_TERMOS(x) (((x) & 0x1f) << 0) + +#define MIPI_CAL_BIAS_PAD_PDVCLAMP BIT(1) +#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF BIT(0) + +#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16) +#define MIPI_CAL_BIAS_PAD_DRV_UP_REF(x) (((x) & 0x7) << 8) + +#define MIPI_CAL_BIAS_PAD_VCLAMP(x) (((x) & 0x7) << 16) +#define MIPI_CAL_BIAS_PAD_VAUXP(x) (((x) & 0x7) << 4) +#define MIPI_CAL_BIAS_PAD_PDVREG BIT(1) + +#define MIPI_CAL_HSCLKPDOSDSI(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_HSCLKPUOSDSI(x) (((x) & 0x1f) << 0) + +struct tegra_mipi_priv { + struct mipi_ctlr *mipi; + struct clk *mipi_cal; + u32 version; +}; + +enum { + T114, + T124, +}; + +static void tegra114_mipi_pads_cal(struct tegra_mipi_priv *priv, + int calibration_pads) +{ + u32 value; + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x4) | + MIPI_CAL_TERMOS(0x5); + writel(value, &priv->mipi->mipi_cal_config_dsia); + writel(value, &priv->mipi->mipi_cal_config_dsib); + + /* Deselect PAD C */ + value = readl(&priv->mipi->mipi_cal_config_dsic); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_config_dsic); + + /* Deselect PAD D */ + value = readl(&priv->mipi->mipi_cal_config_dsid); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_config_dsid); +} + +static void tegra124_mipi_pads_cal(struct tegra_mipi_priv *priv, + int calibration_pads) +{ + u32 value; + + /* Calibrate DSI-A */ + if (calibration_pads == MIPI_DSIA_PADS) { + printf("Calibrating DSI-A pads\n"); + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x0) | + MIPI_CAL_TERMOS(0x0); + writel(value, &priv->mipi->mipi_cal_config_dsia); + writel(value, &priv->mipi->mipi_cal_config_dsib); + + value = MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSCLKPDOSDSI(0x1) | + MIPI_CAL_HSCLKPUOSDSI(0x2); + writel(value, &priv->mipi->mipi_cal_dsia_config_2); + writel(value, &priv->mipi->mipi_cal_dsib_config_2); + + /* Deselect PAD C */ + value = readl(&priv->mipi->mipi_cal_cilc_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_cilc_config_2); + + /* Deselect PAD D */ + value = readl(&priv->mipi->mipi_cal_cild_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_cild_config_2); + } + + /* Calibrate DSI-B */ + if (calibration_pads == MIPI_DSIB_PADS) { + printf("Calibrating DSI-B pads\n"); + + value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x0) | + MIPI_CAL_TERMOS(0x0); + writel(value, &priv->mipi->mipi_cal_config_csic); + writel(value, &priv->mipi->mipi_cal_config_csid); + + value = MIPI_CAL_SEL(0x1) | + MIPI_CAL_HSCLKPDOSDSI(0x1) | + MIPI_CAL_HSCLKPUOSDSI(0x2); + writel(value, &priv->mipi->mipi_cal_cilc_config_2); + writel(value, &priv->mipi->mipi_cal_cild_config_2); + + /* Deselect PAD A */ + value = readl(&priv->mipi->mipi_cal_dsia_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_dsia_config_2); + + /* Deselect PAD B */ + value = readl(&priv->mipi->mipi_cal_dsib_config_2); + value &= ~(MIPI_CAL_SEL(0x1)); + writel(value, &priv->mipi->mipi_cal_dsib_config_2); + } +} + +static int tegra_mipi_calibrate(struct udevice *dev, int offset, const void *buf, + int size) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + u32 value; + + value = MIPI_CAL_BIAS_PAD_DRV_DN_REF(0x2) | + MIPI_CAL_BIAS_PAD_DRV_UP_REF(0x0); + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg1); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2); + value &= ~MIPI_CAL_BIAS_PAD_VCLAMP(0x7); + value &= ~MIPI_CAL_BIAS_PAD_VAUXP(0x7); + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2); + + switch (priv->version) { + case T114: + tegra114_mipi_pads_cal(priv, offset); + break; + + case T124: + tegra124_mipi_pads_cal(priv, offset); + break; + + default: + return -EINVAL; + } + + value = readl(&priv->mipi->mipi_cal_ctrl); + value &= ~MIPI_CAL_CTRL_NOISE_FILTER(0xf); + value &= ~MIPI_CAL_CTRL_PRESCALE(0x3); + value |= MIPI_CAL_CTRL_NOISE_FILTER(0xa) | + MIPI_CAL_CTRL_PRESCALE(0x2) | + MIPI_CAL_CTRL_CLKEN_OVR; + writel(value, &priv->mipi->mipi_cal_ctrl); + + /* clear any pending status bits */ + value = readl(&priv->mipi->mipi_cal_status); + writel(value, &priv->mipi->mipi_cal_status); + + value = readl(&priv->mipi->mipi_cal_ctrl); + value |= MIPI_CAL_CTRL_START; + writel(value, &priv->mipi->mipi_cal_ctrl); + + /* + * Wait for min 72uS to let calibration logic finish calibration + * sequence codes before waiting for pads idle state to apply the + * results. + */ + udelay(80); + + return readl_poll_sleep_timeout(&priv->mipi->mipi_cal_status, value, + !(value & MIPI_CAL_STATUS_ACTIVE) && + (value & MIPI_CAL_STATUS_DONE), 100, + 250000); +} + +static int tegra_mipi_enable(struct udevice *dev, bool val) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + u32 value; + + reset_set_enable(priv->mipi_cal->id, 1); + mdelay(100); + reset_set_enable(priv->mipi_cal->id, 0); + mdelay(1); + + clk_enable(priv->mipi_cal); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg0); + value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP; + value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF; + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg0); + + value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2); + value &= ~MIPI_CAL_BIAS_PAD_PDVREG; + writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2); + + return 0; +} + +static const struct misc_ops tegra_mipi_ops = { + .write = tegra_mipi_calibrate, + .set_enabled = tegra_mipi_enable, +}; + +static int tegra_mipi_probe(struct udevice *dev) +{ + struct tegra_mipi_priv *priv = dev_get_priv(dev); + + priv->version = dev_get_driver_data(dev); + + priv->mipi = (struct mipi_ctlr *)dev_read_addr_ptr(dev); + if (!priv->mipi) { + log_debug("%s: no MIPI controller address\n", __func__); + return -EINVAL; + } + + priv->mipi_cal = devm_clk_get(dev, NULL); + if (IS_ERR(priv->mipi_cal)) { + log_debug("%s: Could not get MIPI clock: %ld\n", + __func__, PTR_ERR(priv->mipi_cal)); + return PTR_ERR(priv->mipi_cal); + } + + return 0; +} + +static const struct udevice_id tegra_mipi_ids[] = { + { .compatible = "nvidia,tegra114-mipi", .data = T114 }, + { .compatible = "nvidia,tegra124-mipi", .data = T124 }, + { } +}; + +U_BOOT_DRIVER(tegra_mipi) = { + .name = "tegra_mipi", + .id = UCLASS_MISC, + .ops = &tegra_mipi_ops, + .of_match = tegra_mipi_ids, + .probe = tegra_mipi_probe, + .priv_auto = sizeof(struct tegra_mipi_priv), +}; diff --git a/drivers/video/tegra/tegra124/Makefile b/drivers/video/tegra/tegra124/Makefile new file mode 100644 index 00000000000..a378382628c --- /dev/null +++ b/drivers/video/tegra/tegra124/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) 2014 Google, Inc +# + +obj-y += display.o +obj-y += dp.o +obj-y += sor.o diff --git a/drivers/video/tegra/tegra124/display.c b/drivers/video/tegra/tegra124/display.c new file mode 100644 index 00000000000..abe31e27d84 --- /dev/null +++ b/drivers/video/tegra/tegra124/display.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2014 Google Inc. + * + * Extracted from Chromium coreboot commit 3f59b13d + */ + +#include <bootstage.h> +#include <dm.h> +#include <edid.h> +#include <errno.h> +#include <display.h> +#include <edid.h> +#include <log.h> +#include <part.h> +#include <time.h> +#include <video.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/pwm.h> +#include <asm/arch-tegra/dc.h> +#include <dm/uclass-internal.h> +#include <linux/delay.h> +#include "displayport.h" + +/* return in 1000ths of a Hertz */ +static int tegra_dc_calc_refresh(const struct display_timing *timing) +{ + int h_total, v_total, refresh; + int pclk = timing->pixelclock.typ; + + h_total = timing->hactive.typ + timing->hfront_porch.typ + + timing->hback_porch.typ + timing->hsync_len.typ; + v_total = timing->vactive.typ + timing->vfront_porch.typ + + timing->vback_porch.typ + timing->vsync_len.typ; + if (!pclk || !h_total || !v_total) + return 0; + refresh = pclk / h_total; + refresh *= 1000; + refresh /= v_total; + + return refresh; +} + +static void print_mode(const struct display_timing *timing) +{ + int refresh = tegra_dc_calc_refresh(timing); + + debug("MODE:%dx%d@%d.%03uHz pclk=%d\n", + timing->hactive.typ, timing->vactive.typ, refresh / 1000, + refresh % 1000, timing->pixelclock.typ); +} + +static int update_display_mode(struct dc_ctlr *disp_ctrl, + const struct display_timing *timing, + int href_to_sync, int vref_to_sync) +{ + print_mode(timing); + + writel(0x1, &disp_ctrl->disp.disp_timing_opt); + + writel(vref_to_sync << 16 | href_to_sync, + &disp_ctrl->disp.ref_to_sync); + + writel(timing->vsync_len.typ << 16 | timing->hsync_len.typ, + &disp_ctrl->disp.sync_width); + + writel(((timing->vback_porch.typ - vref_to_sync) << 16) | + timing->hback_porch.typ, &disp_ctrl->disp.back_porch); + + writel(((timing->vfront_porch.typ + vref_to_sync) << 16) | + timing->hfront_porch.typ, &disp_ctrl->disp.front_porch); + + writel(timing->hactive.typ | (timing->vactive.typ << 16), + &disp_ctrl->disp.disp_active); + + /** + * We want to use PLLD_out0, which is PLLD / 2: + * PixelClock = (PLLD / 2) / ShiftClockDiv / PixelClockDiv. + * + * Currently most panels work inside clock range 50MHz~100MHz, and PLLD + * has some requirements to have VCO in range 500MHz~1000MHz (see + * clock.c for more detail). To simplify calculation, we set + * PixelClockDiv to 1 and ShiftClockDiv to 1. In future these values + * may be calculated by clock_display, to allow wider frequency range. + * + * Note ShiftClockDiv is a 7.1 format value. + */ + const u32 shift_clock_div = 1; + writel((PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT) | + ((shift_clock_div - 1) * 2) << SHIFT_CLK_DIVIDER_SHIFT, + &disp_ctrl->disp.disp_clk_ctrl); + debug("%s: PixelClock=%u, ShiftClockDiv=%u\n", __func__, + timing->pixelclock.typ, shift_clock_div); + return 0; +} + +static u32 tegra_dc_poll_register(void *reg, + u32 mask, u32 exp_val, u32 poll_interval_us, u32 timeout_us) +{ + u32 temp = timeout_us; + u32 reg_val = 0; + + do { + udelay(poll_interval_us); + reg_val = readl(reg); + if (timeout_us > poll_interval_us) + timeout_us -= poll_interval_us; + else + break; + } while ((reg_val & mask) != exp_val); + + if ((reg_val & mask) == exp_val) + return 0; /* success */ + + return temp; +} + +int tegra_dc_sor_general_act(struct dc_ctlr *disp_ctrl) +{ + writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl); + + if (tegra_dc_poll_register(&disp_ctrl->cmd.state_ctrl, + GENERAL_ACT_REQ, 0, 100, + DC_POLL_TIMEOUT_MS * 1000)) { + debug("dc timeout waiting for DC to stop\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static struct display_timing min_mode = { + .hsync_len = { .typ = 1 }, + .vsync_len = { .typ = 1 }, + .hback_porch = { .typ = 20 }, + .vback_porch = { .typ = 0 }, + .hactive = { .typ = 16 }, + .vactive = { .typ = 16 }, + .hfront_porch = { .typ = 1 }, + .vfront_porch = { .typ = 2 }, +}; + +/* Disable windows and set minimum raster timings */ +void tegra_dc_sor_disable_win_short_raster(struct dc_ctlr *disp_ctrl, + int *dc_reg_ctx) +{ + const int href_to_sync = 0, vref_to_sync = 1; + int selected_windows, i; + + selected_windows = readl(&disp_ctrl->cmd.disp_win_header); + + /* Store and clear window options */ + for (i = 0; i < DC_N_WINDOWS; ++i) { + writel(WINDOW_A_SELECT << i, &disp_ctrl->cmd.disp_win_header); + dc_reg_ctx[i] = readl(&disp_ctrl->win.win_opt); + writel(0, &disp_ctrl->win.win_opt); + writel(WIN_A_ACT_REQ << i, &disp_ctrl->cmd.state_ctrl); + } + + writel(selected_windows, &disp_ctrl->cmd.disp_win_header); + + /* Store current raster timings and set minimum timings */ + dc_reg_ctx[i++] = readl(&disp_ctrl->disp.ref_to_sync); + writel(href_to_sync | (vref_to_sync << 16), + &disp_ctrl->disp.ref_to_sync); + + dc_reg_ctx[i++] = readl(&disp_ctrl->disp.sync_width); + writel(min_mode.hsync_len.typ | (min_mode.vsync_len.typ << 16), + &disp_ctrl->disp.sync_width); + + dc_reg_ctx[i++] = readl(&disp_ctrl->disp.back_porch); + writel(min_mode.hback_porch.typ | (min_mode.vback_porch.typ << 16), + &disp_ctrl->disp.back_porch); + + dc_reg_ctx[i++] = readl(&disp_ctrl->disp.front_porch); + writel(min_mode.hfront_porch.typ | (min_mode.vfront_porch.typ << 16), + &disp_ctrl->disp.front_porch); + + dc_reg_ctx[i++] = readl(&disp_ctrl->disp.disp_active); + writel(min_mode.hactive.typ | (min_mode.vactive.typ << 16), + &disp_ctrl->disp.disp_active); + + writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl); +} + +/* Restore previous windows status and raster timings */ +void tegra_dc_sor_restore_win_and_raster(struct dc_ctlr *disp_ctrl, + int *dc_reg_ctx) +{ + int selected_windows, i; + + selected_windows = readl(&disp_ctrl->cmd.disp_win_header); + + for (i = 0; i < DC_N_WINDOWS; ++i) { + writel(WINDOW_A_SELECT << i, &disp_ctrl->cmd.disp_win_header); + writel(dc_reg_ctx[i], &disp_ctrl->win.win_opt); + writel(WIN_A_ACT_REQ << i, &disp_ctrl->cmd.state_ctrl); + } + + writel(selected_windows, &disp_ctrl->cmd.disp_win_header); + + writel(dc_reg_ctx[i++], &disp_ctrl->disp.ref_to_sync); + writel(dc_reg_ctx[i++], &disp_ctrl->disp.sync_width); + writel(dc_reg_ctx[i++], &disp_ctrl->disp.back_porch); + writel(dc_reg_ctx[i++], &disp_ctrl->disp.front_porch); + writel(dc_reg_ctx[i++], &disp_ctrl->disp.disp_active); + + writel(GENERAL_UPDATE, &disp_ctrl->cmd.state_ctrl); +} + +static int tegra_depth_for_bpp(int bpp) +{ + switch (bpp) { + case 32: + return COLOR_DEPTH_R8G8B8A8; + case 16: + return COLOR_DEPTH_B5G6R5; + default: + debug("Unsupported LCD bit depth"); + return -1; + } +} + +static int update_window(struct dc_ctlr *disp_ctrl, + u32 frame_buffer, int fb_bits_per_pixel, + const struct display_timing *timing) +{ + const u32 colour_white = 0xffffff; + int colour_depth; + u32 val; + + writel(WINDOW_A_SELECT, &disp_ctrl->cmd.disp_win_header); + + writel(((timing->vactive.typ << 16) | timing->hactive.typ), + &disp_ctrl->win.size); + writel(((timing->vactive.typ << 16) | + (timing->hactive.typ * fb_bits_per_pixel / 8)), + &disp_ctrl->win.prescaled_size); + writel(((timing->hactive.typ * fb_bits_per_pixel / 8 + 31) / + 32 * 32), &disp_ctrl->win.line_stride); + + colour_depth = tegra_depth_for_bpp(fb_bits_per_pixel); + if (colour_depth == -1) + return -EINVAL; + + writel(colour_depth, &disp_ctrl->win.color_depth); + + writel(frame_buffer, &disp_ctrl->winbuf.start_addr); + writel(0x1000 << V_DDA_INC_SHIFT | 0x1000 << H_DDA_INC_SHIFT, + &disp_ctrl->win.dda_increment); + + writel(colour_white, &disp_ctrl->disp.blend_background_color); + writel(CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT, + &disp_ctrl->cmd.disp_cmd); + + writel(WRITE_MUX_ACTIVE, &disp_ctrl->cmd.state_access); + + val = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + val |= GENERAL_UPDATE | WIN_A_UPDATE; + writel(val, &disp_ctrl->cmd.state_ctrl); + + /* Enable win_a */ + val = readl(&disp_ctrl->win.win_opt); + writel(val | WIN_ENABLE, &disp_ctrl->win.win_opt); + + return 0; +} + +static int tegra_dc_init(struct dc_ctlr *disp_ctrl) +{ + /* do not accept interrupts during initialization */ + writel(0x00000000, &disp_ctrl->cmd.int_mask); + writel(WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, + &disp_ctrl->cmd.state_access); + writel(WINDOW_A_SELECT, &disp_ctrl->cmd.disp_win_header); + writel(0x00000000, &disp_ctrl->win.win_opt); + writel(0x00000000, &disp_ctrl->win.byte_swap); + writel(0x00000000, &disp_ctrl->win.buffer_ctrl); + + writel(0x00000000, &disp_ctrl->win.pos); + writel(0x00000000, &disp_ctrl->win.h_initial_dda); + writel(0x00000000, &disp_ctrl->win.v_initial_dda); + writel(0x00000000, &disp_ctrl->win.dda_increment); + writel(0x00000000, &disp_ctrl->win.dv_ctrl); + + writel(0x01000000, &disp_ctrl->win.blend_layer_ctrl); + writel(0x00000000, &disp_ctrl->win.blend_match_select); + writel(0x00000000, &disp_ctrl->win.blend_nomatch_select); + writel(0x00000000, &disp_ctrl->win.blend_alpha_1bit); + + writel(0x00000000, &disp_ctrl->winbuf.start_addr_hi); + writel(0x00000000, &disp_ctrl->winbuf.addr_h_offset); + writel(0x00000000, &disp_ctrl->winbuf.addr_v_offset); + + writel(0x00000000, &disp_ctrl->com.crc_checksum); + writel(0x00000000, &disp_ctrl->com.pin_output_enb[0]); + writel(0x00000000, &disp_ctrl->com.pin_output_enb[1]); + writel(0x00000000, &disp_ctrl->com.pin_output_enb[2]); + writel(0x00000000, &disp_ctrl->com.pin_output_enb[3]); + writel(0x00000000, &disp_ctrl->disp.disp_signal_opt0); + + return 0; +} + +static void dump_config(int panel_bpp, struct display_timing *timing) +{ + printf("timing->hactive.typ = %d\n", timing->hactive.typ); + printf("timing->vactive.typ = %d\n", timing->vactive.typ); + printf("timing->pixelclock.typ = %d\n", timing->pixelclock.typ); + + printf("timing->hfront_porch.typ = %d\n", timing->hfront_porch.typ); + printf("timing->hsync_len.typ = %d\n", timing->hsync_len.typ); + printf("timing->hback_porch.typ = %d\n", timing->hback_porch.typ); + + printf("timing->vfront_porch.typ %d\n", timing->vfront_porch.typ); + printf("timing->vsync_len.typ = %d\n", timing->vsync_len.typ); + printf("timing->vback_porch.typ = %d\n", timing->vback_porch.typ); + + printf("panel_bits_per_pixel = %d\n", panel_bpp); +} + +static int display_update_config_from_edid(struct udevice *dp_dev, + int *panel_bppp, + struct display_timing *timing) +{ + return display_read_timing(dp_dev, timing); +} + +static int display_init(struct udevice *dev, void *lcdbase, + int fb_bits_per_pixel, struct display_timing *timing) +{ + struct display_plat *disp_uc_plat; + struct dc_ctlr *dc_ctlr; + struct udevice *dp_dev; + const int href_to_sync = 1, vref_to_sync = 1; + int panel_bpp = 18; /* default 18 bits per pixel */ + u32 plld_rate; + int ret; + + /* + * Before we probe the display device (eDP), tell it that this device + * is the source of the display data. + */ + ret = uclass_find_first_device(UCLASS_DISPLAY, &dp_dev); + if (ret) { + debug("%s: device '%s' display not found (ret=%d)\n", __func__, + dev->name, ret); + return ret; + } + + disp_uc_plat = dev_get_uclass_plat(dp_dev); + debug("Found device '%s', disp_uc_priv=%p\n", dp_dev->name, + disp_uc_plat); + disp_uc_plat->src_dev = dev; + + ret = uclass_get_device(UCLASS_DISPLAY, 0, &dp_dev); + if (ret) { + debug("%s: Failed to probe eDP, ret=%d\n", __func__, ret); + return ret; + } + + dc_ctlr = dev_read_addr_ptr(dev); + if (ofnode_decode_display_timing(dev_ofnode(dev), 0, timing)) { + debug("%s: Failed to decode display timing\n", __func__); + return -EINVAL; + } + + ret = display_update_config_from_edid(dp_dev, &panel_bpp, timing); + if (ret) { + debug("%s: Failed to decode EDID, using defaults\n", __func__); + dump_config(panel_bpp, timing); + } + + /* + * The plld is programmed with the assumption of the SHIFT_CLK_DIVIDER + * and PIXEL_CLK_DIVIDER are zero (divide by 1). See the + * update_display_mode() for detail. + */ + plld_rate = clock_set_display_rate(timing->pixelclock.typ * 2); + if (plld_rate == 0) { + printf("dc: clock init failed\n"); + return -EIO; + } else if (plld_rate != timing->pixelclock.typ * 2) { + debug("dc: plld rounded to %u\n", plld_rate); + timing->pixelclock.typ = plld_rate / 2; + } + + /* Init dc */ + ret = tegra_dc_init(dc_ctlr); + if (ret) { + debug("dc: init failed\n"); + return ret; + } + + /* Configure dc mode */ + ret = update_display_mode(dc_ctlr, timing, href_to_sync, vref_to_sync); + if (ret) { + debug("dc: failed to configure display mode\n"); + return ret; + } + + /* Enable dp */ + ret = display_enable(dp_dev, panel_bpp, timing); + if (ret) { + debug("dc: failed to enable display: ret=%d\n", ret); + return ret; + } + + ret = update_window(dc_ctlr, (ulong)lcdbase, fb_bits_per_pixel, timing); + if (ret) { + debug("dc: failed to update window\n"); + return ret; + } + debug("%s: ready\n", __func__); + + return 0; +} + +enum { + /* Maximum LCD size we support */ + LCD_MAX_WIDTH = 1920, + LCD_MAX_HEIGHT = 1200, + LCD_MAX_LOG2_BPP = 4, /* 2^4 = 16 bpp */ +}; + +static int tegra124_lcd_init(struct udevice *dev, void *lcdbase, + enum video_log2_bpp l2bpp) +{ + struct video_priv *uc_priv = dev_get_uclass_priv(dev); + struct display_timing timing; + int ret; + + clock_set_up_plldp(); + clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH, 408000000); + + clock_enable(PERIPH_ID_HOST1X); + clock_enable(PERIPH_ID_DISP1); + clock_enable(PERIPH_ID_PWM); + clock_enable(PERIPH_ID_DPAUX); + clock_enable(PERIPH_ID_SOR0); + udelay(2); + + reset_set_enable(PERIPH_ID_HOST1X, 0); + reset_set_enable(PERIPH_ID_DISP1, 0); + reset_set_enable(PERIPH_ID_PWM, 0); + reset_set_enable(PERIPH_ID_DPAUX, 0); + reset_set_enable(PERIPH_ID_SOR0, 0); + + ret = display_init(dev, lcdbase, 1 << l2bpp, &timing); + if (ret) + return ret; + + uc_priv->xsize = roundup(timing.hactive.typ, 16); + uc_priv->ysize = timing.vactive.typ; + uc_priv->bpix = l2bpp; + + video_set_flush_dcache(dev, 1); + debug("%s: done\n", __func__); + + return 0; +} + +static int tegra124_lcd_probe(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + ulong start; + int ret; + + start = get_timer(0); + bootstage_start(BOOTSTAGE_ID_ACCUM_LCD, "lcd"); + ret = tegra124_lcd_init(dev, (void *)plat->base, VIDEO_BPP16); + bootstage_accum(BOOTSTAGE_ID_ACCUM_LCD); + debug("LCD init took %lu ms\n", get_timer(start)); + if (ret) + printf("%s: Error %d\n", __func__, ret); + + return 0; +} + +static int tegra124_lcd_bind(struct udevice *dev) +{ + struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev); + + uc_plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT * + (1 << VIDEO_BPP16) / 8; + debug("%s: Frame buffer size %x\n", __func__, uc_plat->size); + + return 0; +} + +static const struct udevice_id tegra124_lcd_ids[] = { + { .compatible = "nvidia,tegra124-dc" }, + { } +}; + +U_BOOT_DRIVER(tegra124_dc) = { + .name = "tegra124-dc", + .id = UCLASS_VIDEO, + .of_match = tegra124_lcd_ids, + .bind = tegra124_lcd_bind, + .probe = tegra124_lcd_probe, +}; diff --git a/drivers/video/tegra/tegra124/displayport.h b/drivers/video/tegra/tegra124/displayport.h new file mode 100644 index 00000000000..a3044475aeb --- /dev/null +++ b/drivers/video/tegra/tegra124/displayport.h @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2014, NVIDIA Corporation. + */ + +#ifndef _TEGRA_DISPLAYPORT_H +#define _TEGRA_DISPLAYPORT_H + +#include <linux/drm_dp_helper.h> + +struct dpaux_ctlr { + u32 reserved0; + u32 intr_en_aux; + u32 reserved2_4; + u32 intr_aux; +}; + +#define DPAUX_INTR_EN_AUX 0x1 +#define DPAUX_INTR_AUX 0x5 +#define DPAUX_DP_AUXDATA_WRITE_W(i) (0x9 + 4 * (i)) +#define DPAUX_DP_AUXDATA_READ_W(i) (0x19 + 4 * (i)) +#define DPAUX_DP_AUXADDR 0x29 +#define DPAUX_DP_AUXCTL 0x2d +#define DPAUX_DP_AUXCTL_CMDLEN_SHIFT 0 +#define DPAUX_DP_AUXCTL_CMDLEN_FIELD 0xff +#define DPAUX_DP_AUXCTL_CMD_SHIFT 12 +#define DPAUX_DP_AUXCTL_CMD_MASK (0xf << 12) +#define DPAUX_DP_AUXCTL_CMD_I2CWR (0 << 12) +#define DPAUX_DP_AUXCTL_CMD_I2CRD (1 << 12) +#define DPAUX_DP_AUXCTL_CMD_I2CREQWSTAT (2 << 12) +#define DPAUX_DP_AUXCTL_CMD_MOTWR (4 << 12) +#define DPAUX_DP_AUXCTL_CMD_MOTRD (5 << 12) +#define DPAUX_DP_AUXCTL_CMD_MOTREQWSTAT (6 << 12) +#define DPAUX_DP_AUXCTL_CMD_AUXWR (8 << 12) +#define DPAUX_DP_AUXCTL_CMD_AUXRD (9 << 12) +#define DPAUX_DP_AUXCTL_TRANSACTREQ_SHIFT 16 +#define DPAUX_DP_AUXCTL_TRANSACTREQ_MASK (0x1 << 16) +#define DPAUX_DP_AUXCTL_TRANSACTREQ_DONE (0 << 16) +#define DPAUX_DP_AUXCTL_TRANSACTREQ_PENDING (1 << 16) +#define DPAUX_DP_AUXCTL_RST_SHIFT 31 +#define DPAUX_DP_AUXCTL_RST_DEASSERT (0 << 31) +#define DPAUX_DP_AUXCTL_RST_ASSERT (1 << 31) +#define DPAUX_DP_AUXSTAT 0x31 +#define DPAUX_DP_AUXSTAT_HPD_STATUS_SHIFT 28 +#define DPAUX_DP_AUXSTAT_HPD_STATUS_UNPLUG (0 << 28) +#define DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED (1 << 28) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_SHIFT 20 +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_MASK (0xf << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_IDLE (0 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_SYNC (1 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_START1 (2 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_COMMAND (3 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_ADDRESS (4 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_LENGTH (5 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_WRITE1 (6 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_READ1 (7 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_GET_M (8 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_STOP1 (9 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_STOP2 (10 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_REPLY (11 << 20) +#define DPAUX_DP_AUXSTAT_AUXCTL_STATE_CLEANUP (12 << 20) +#define DPAUX_DP_AUXSTAT_REPLYTYPE_SHIFT 16 +#define DPAUX_DP_AUXSTAT_REPLYTYPE_MASK (0xf << 16) +#define DPAUX_DP_AUXSTAT_REPLYTYPE_ACK (0 << 16) +#define DPAUX_DP_AUXSTAT_REPLYTYPE_NACK (1 << 16) +#define DPAUX_DP_AUXSTAT_REPLYTYPE_DEFER (2 << 16) +#define DPAUX_DP_AUXSTAT_REPLYTYPE_I2CNACK (4 << 16) +#define DPAUX_DP_AUXSTAT_REPLYTYPE_I2CDEFER (8 << 16) +#define DPAUX_DP_AUXSTAT_NO_STOP_ERROR_SHIFT 11 +#define DPAUX_DP_AUXSTAT_NO_STOP_ERROR_NOT_PENDING (0 << 11) +#define DPAUX_DP_AUXSTAT_NO_STOP_ERROR_PENDING (1 << 11) +#define DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_SHIFT 10 +#define DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_NOT_PENDING (0 << 10) +#define DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_PENDING (1 << 10) +#define DPAUX_DP_AUXSTAT_RX_ERROR_SHIFT 9 +#define DPAUX_DP_AUXSTAT_RX_ERROR_NOT_PENDING (0 << 9) +#define DPAUX_DP_AUXSTAT_RX_ERROR_PENDING (1 << 9) +#define DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_SHIFT 8 +#define DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_NOT_PENDING (0 << 8) +#define DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_PENDING (1 << 8) +#define DPAUX_DP_AUXSTAT_REPLY_M_SHIFT 0 +#define DPAUX_DP_AUXSTAT_REPLY_M_MASK (0xff << 0) +#define DPAUX_HPD_CONFIG (0x3d) +#define DPAUX_HPD_IRQ_CONFIG 0x41 +#define DPAUX_DP_AUX_CONFIG 0x45 +#define DPAUX_HYBRID_PADCTL 0x49 +#define DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV_SHIFT 15 +#define DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV_DISABLE (0 << 15) +#define DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV_ENABLE (1 << 15) +#define DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV_SHIFT 14 +#define DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV_DISABLE (0 << 14) +#define DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV_ENABLE (1 << 14) +#define DPAUX_HYBRID_PADCTL_AUX_CMH_SHIFT 12 +#define DPAUX_HYBRID_PADCTL_AUX_CMH_DEFAULT_MASK (0x3 << 12) +#define DPAUX_HYBRID_PADCTL_AUX_CMH_V0_60 (0 << 12) +#define DPAUX_HYBRID_PADCTL_AUX_CMH_V0_64 (1 << 12) +#define DPAUX_HYBRID_PADCTL_AUX_CMH_V0_70 (2 << 12) +#define DPAUX_HYBRID_PADCTL_AUX_CMH_V0_56 (3 << 12) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_SHIFT 8 +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_DEFAULT_MASK (0x7 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_78 (0 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_60 (1 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_54 (2 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_45 (3 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_50 (4 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_42 (5 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_39 (6 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_34 (7 << 8) +#define DPAUX_HYBRID_PADCTL_AUX_DRVI_SHIFT 2 +#define DPAUX_HYBRID_PADCTL_AUX_DRVI_DEFAULT_MASK (0x3f << 2) +#define DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV_SHIFT 1 +#define DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV_DISABLE (0 << 1) +#define DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV_ENABLE (1 << 1) +#define DPAUX_HYBRID_PADCTL_MODE_SHIFT 0 +#define DPAUX_HYBRID_PADCTL_MODE_AUX 0 +#define DPAUX_HYBRID_PADCTL_MODE_I2C 1 +#define DPAUX_HYBRID_SPARE 0x4d +#define DPAUX_HYBRID_SPARE_PAD_PWR_POWERUP 0 +#define DPAUX_HYBRID_SPARE_PAD_PWR_POWERDOWN 1 + +#define DP_AUX_DEFER_MAX_TRIES 7 +#define DP_AUX_TIMEOUT_MAX_TRIES 2 +#define DP_POWER_ON_MAX_TRIES 3 + +#define DP_AUX_MAX_BYTES 16 + +#define DP_AUX_TIMEOUT_MS 40 +#define DP_DPCP_RETRY_SLEEP_NS 400 + +static const u32 tegra_dp_vs_regs[][4][4] = { + /* postcursor2 L0 */ + { + /* pre-emphasis: L0, L1, L2, L3 */ + {0x13, 0x19, 0x1e, 0x28}, /* voltage swing: L0 */ + {0x1e, 0x25, 0x2d}, /* L1 */ + {0x28, 0x32}, /* L2 */ + {0x3c}, /* L3 */ + }, + + /* postcursor2 L1 */ + { + {0x12, 0x17, 0x1b, 0x25}, + {0x1c, 0x23, 0x2a}, + {0x25, 0x2f}, + {0x39}, + }, + + /* postcursor2 L2 */ + { + {0x12, 0x16, 0x1a, 0x22}, + {0x1b, 0x20, 0x27}, + {0x24, 0x2d}, + {0x36}, + }, + + /* postcursor2 L3 */ + { + {0x11, 0x14, 0x17, 0x1f}, + {0x19, 0x1e, 0x24}, + {0x22, 0x2a}, + {0x32}, + }, +}; + +static const u32 tegra_dp_pe_regs[][4][4] = { + /* postcursor2 L0 */ + { + /* pre-emphasis: L0, L1, L2, L3 */ + {0x00, 0x09, 0x13, 0x25}, /* voltage swing: L0 */ + {0x00, 0x0f, 0x1e}, /* L1 */ + {0x00, 0x14}, /* L2 */ + {0x00}, /* L3 */ + }, + + /* postcursor2 L1 */ + { + {0x00, 0x0a, 0x14, 0x28}, + {0x00, 0x0f, 0x1e}, + {0x00, 0x14}, + {0x00}, + }, + + /* postcursor2 L2 */ + { + {0x00, 0x0a, 0x14, 0x28}, + {0x00, 0x0f, 0x1e}, + {0x00, 0x14}, + {0x00}, + }, + + /* postcursor2 L3 */ + { + {0x00, 0x0a, 0x14, 0x28}, + {0x00, 0x0f, 0x1e}, + {0x00, 0x14}, + {0x00}, + }, +}; + +static const u32 tegra_dp_pc_regs[][4][4] = { + /* postcursor2 L0 */ + { + /* pre-emphasis: L0, L1, L2, L3 */ + {0x00, 0x00, 0x00, 0x00}, /* voltage swing: L0 */ + {0x00, 0x00, 0x00}, /* L1 */ + {0x00, 0x00}, /* L2 */ + {0x00}, /* L3 */ + }, + + /* postcursor2 L1 */ + { + {0x02, 0x02, 0x04, 0x05}, + {0x02, 0x04, 0x05}, + {0x04, 0x05}, + {0x05}, + }, + + /* postcursor2 L2 */ + { + {0x04, 0x05, 0x08, 0x0b}, + {0x05, 0x09, 0x0b}, + {0x08, 0x0a}, + {0x0b}, + }, + + /* postcursor2 L3 */ + { + {0x05, 0x09, 0x0b, 0x12}, + {0x09, 0x0d, 0x12}, + {0x0b, 0x0f}, + {0x12}, + }, +}; + +static const u32 tegra_dp_tx_pu[][4][4] = { + /* postcursor2 L0 */ + { + /* pre-emphasis: L0, L1, L2, L3 */ + {0x20, 0x30, 0x40, 0x60}, /* voltage swing: L0 */ + {0x30, 0x40, 0x60}, /* L1 */ + {0x40, 0x60}, /* L2 */ + {0x60}, /* L3 */ + }, + + /* postcursor2 L1 */ + { + {0x20, 0x20, 0x30, 0x50}, + {0x30, 0x40, 0x50}, + {0x40, 0x50}, + {0x60}, + }, + + /* postcursor2 L2 */ + { + {0x20, 0x20, 0x30, 0x40}, + {0x30, 0x30, 0x40}, + {0x40, 0x50}, + {0x60}, + }, + + /* postcursor2 L3 */ + { + {0x20, 0x20, 0x20, 0x40}, + {0x30, 0x30, 0x40}, + {0x40, 0x40}, + {0x60}, + }, +}; + +enum { + DRIVECURRENT_LEVEL0 = 0, + DRIVECURRENT_LEVEL1 = 1, + DRIVECURRENT_LEVEL2 = 2, + DRIVECURRENT_LEVEL3 = 3, +}; + +enum { + PREEMPHASIS_DISABLED = 0, + PREEMPHASIS_LEVEL1 = 1, + PREEMPHASIS_LEVEL2 = 2, + PREEMPHASIS_LEVEL3 = 3, +}; + +enum { + POSTCURSOR2_LEVEL0 = 0, + POSTCURSOR2_LEVEL1 = 1, + POSTCURSOR2_LEVEL2 = 2, + POSTCURSOR2_LEVEL3 = 3, + POSTCURSOR2_SUPPORTED +}; + +static inline int tegra_dp_is_max_vs(u32 pe, u32 vs) +{ + return (vs < (DRIVECURRENT_LEVEL3 - pe)) ? 0 : 1; +} + +static inline int tegra_dp_is_max_pe(u32 pe, u32 vs) +{ + return (pe < (PREEMPHASIS_LEVEL3 - vs)) ? 0 : 1; +} + +static inline int tegra_dp_is_max_pc(u32 pc) +{ + return (pc < POSTCURSOR2_LEVEL3) ? 0 : 1; +} + +/* DPCD definitions which are not defined in drm_dp_helper.h */ +#define DP_DPCD_REV_MAJOR_SHIFT 4 +#define DP_DPCD_REV_MAJOR_MASK (0xf << 4) +#define DP_DPCD_REV_MINOR_SHIFT 0 +#define DP_DPCD_REV_MINOR_MASK 0xf + +#define DP_MAX_LINK_RATE_VAL_1_62_GPBS 0x6 +#define DP_MAX_LINK_RATE_VAL_2_70_GPBS 0xa +#define DP_MAX_LINK_RATE_VAL_5_40_GPBS 0x4 + +#define DP_MAX_LANE_COUNT_LANE_1 0x1 +#define DP_MAX_LANE_COUNT_LANE_2 0x2 +#define DP_MAX_LANE_COUNT_LANE_4 0x4 +#define DP_MAX_LANE_COUNT_TPS3_SUPPORTED_YES (1 << 6) +#define DP_MAX_LANE_COUNT_ENHANCED_FRAMING_YES (1 << 7) + +#define NV_DPCD_TRAINING_LANEX_SET_DC_SHIFT 0 +#define NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_T (0x00000001 << 2) +#define NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F (0x00000000 << 2) +#define NV_DPCD_TRAINING_LANEX_SET_PE_SHIFT 3 +#define NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_T (0x00000001 << 5) +#define NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_F (0x00000000 << 5) + +#define DP_MAX_DOWNSPREAD_VAL_NONE 0 +#define DP_MAX_DOWNSPREAD_VAL_0_5_PCT 1 +#define DP_MAX_DOWNSPREAD_NO_AUX_HANDSHAKE_LT_T (1 << 6) + +#define DP_EDP_CONFIGURATION_CAP_ASC_RESET_YES 1 +#define DP_EDP_CONFIGURATION_CAP_FRAMING_CHANGE_YES (1 << 1) + +#define DP_LANE_COUNT_SET_ENHANCEDFRAMING_T (1 << 7) + +#define DP_TRAINING_PATTERN_SET_SC_DISABLED_T (1 << 5) +#define NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F (0x00000000 << 5) +#define NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T (0x00000001 << 5) + +#define DP_MAIN_LINK_CHANNEL_CODING_SET_ASC_RESET_DISABLE 0 +#define DP_MAIN_LINK_CHANNEL_CODING_SET_ASC_RESET_ENABLE 1 + +#define NV_DPCD_TRAINING_LANE0_1_SET2 0x10f +#define NV_DPCD_TRAINING_LANE2_3_SET2 0x110 +#define NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_T (1 << 2) +#define NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_F (0 << 2) +#define NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_T (1 << 6) +#define NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_F (0 << 6) +#define NV_DPCD_LANEX_SET2_PC2_SHIFT 0 +#define NV_DPCD_LANEXPLUS1_SET2_PC2_SHIFT 4 + +#define NV_DPCD_STATUS_LANEX_CR_DONE_SHIFT 0 +#define NV_DPCD_STATUS_LANEX_CR_DONE_NO (0x00000000) +#define NV_DPCD_STATUS_LANEX_CR_DONE_YES (0x00000001) +#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT 1 +#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_NO (0x00000000 << 1) +#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_YES (0x00000001 << 1) +#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT 2 +#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_NO (0x00000000 << 2) +#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_YES (0x00000001 << 2) +#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_SHIFT 4 +#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_NO (0x00000000 << 4) +#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES (0x00000001 << 4) +#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT 5 +#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_NO (0x00000000 << 5) +#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_YES (0x00000001 << 5) +#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT 6 +#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_NO (0x00000000 << 6) +#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES (0x00000001 << 6) + +#define NV_DPCD_LANE_ALIGN_STATUS_UPDATED (0x00000204) +#define NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_NO (0x00000000) +#define NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_YES (0x00000001) + +#define NV_DPCD_STATUS_LANEX_CR_DONE_SHIFT 0 +#define NV_DPCD_STATUS_LANEX_CR_DONE_NO (0x00000000) +#define NV_DPCD_STATUS_LANEX_CR_DONE_YES (0x00000001) +#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT 1 +#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_NO (0x00000000 << 1) +#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_YES (0x00000001 << 1) +#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT 2 +#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_NO (0x00000000 << 2) +#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_YES (0x00000001 << 2) +#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_SHIFT 4 +#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_NO (0x00000000 << 4) +#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES (0x00000001 << 4) +#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT 5 +#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_NO (0x00000000 << 5) +#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_YES (0x00000001 << 5) +#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT 6 +#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_NO (0x00000000 << 6) +#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES (0x00000001 << 6) + +#define NV_DPCD_ADJUST_REQ_LANEX_DC_SHIFT 0 +#define NV_DPCD_ADJUST_REQ_LANEX_DC_MASK 0x3 +#define NV_DPCD_ADJUST_REQ_LANEX_PE_SHIFT 2 +#define NV_DPCD_ADJUST_REQ_LANEX_PE_MASK (0x3 << 2) +#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_SHIFT 4 +#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK (0x3 << 4) +#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_SHIFT 6 +#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_MASK (0x3 << 6) +#define NV_DPCD_ADJUST_REQ_POST_CURSOR2 (0x0000020C) +#define NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_MASK 0x3 +#define NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_SHIFT(i) (i*2) + +#define NV_DPCD_TRAINING_AUX_RD_INTERVAL (0x0000000E) +#define NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F (0x00000000 << 2) +#endif diff --git a/drivers/video/tegra/tegra124/dp.c b/drivers/video/tegra/tegra124/dp.c new file mode 100644 index 00000000000..b95b14da77d --- /dev/null +++ b/drivers/video/tegra/tegra124/dp.c @@ -0,0 +1,1624 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2013, NVIDIA Corporation. + * Copyright 2014 Google Inc. + */ + +#include <display.h> +#include <dm.h> +#include <div64.h> +#include <errno.h> +#include <log.h> +#include <time.h> +#include <video_bridge.h> +#include <asm/io.h> +#include <asm/arch-tegra/dc.h> +#include <linux/delay.h> +#include "display.h" +#include "edid.h" +#include "sor.h" +#include "displayport.h" + +#define DO_FAST_LINK_TRAINING 1 + +struct tegra_dp_plat { + ulong base; +}; + +/** + * struct tegra_dp_priv - private displayport driver info + * + * @dc_dev: Display controller device that is sending the video feed + */ +struct tegra_dp_priv { + struct udevice *sor; + struct udevice *dc_dev; + struct dpaux_ctlr *regs; + u8 revision; + int enabled; +}; + +struct tegra_dp_priv dp_data; + +static inline u32 tegra_dpaux_readl(struct tegra_dp_priv *dp, u32 reg) +{ + return readl((u32 *)dp->regs + reg); +} + +static inline void tegra_dpaux_writel(struct tegra_dp_priv *dp, u32 reg, + u32 val) +{ + writel(val, (u32 *)dp->regs + reg); +} + +static inline u32 tegra_dc_dpaux_poll_register(struct tegra_dp_priv *dp, + u32 reg, u32 mask, u32 exp_val, + u32 poll_interval_us, + u32 timeout_us) +{ + u32 reg_val = 0; + u32 temp = timeout_us; + + do { + udelay(poll_interval_us); + reg_val = tegra_dpaux_readl(dp, reg); + if (timeout_us > poll_interval_us) + timeout_us -= poll_interval_us; + else + break; + } while ((reg_val & mask) != exp_val); + + if ((reg_val & mask) == exp_val) + return 0; /* success */ + debug("dpaux_poll_register 0x%x: timeout: (reg_val)0x%08x & (mask)0x%08x != (exp_val)0x%08x\n", + reg, reg_val, mask, exp_val); + return temp; +} + +static inline int tegra_dpaux_wait_transaction(struct tegra_dp_priv *dp) +{ + /* According to DP spec, each aux transaction needs to finish + within 40ms. */ + if (tegra_dc_dpaux_poll_register(dp, DPAUX_DP_AUXCTL, + DPAUX_DP_AUXCTL_TRANSACTREQ_MASK, + DPAUX_DP_AUXCTL_TRANSACTREQ_DONE, + 100, DP_AUX_TIMEOUT_MS * 1000) != 0) { + debug("dp: DPAUX transaction timeout\n"); + return -1; + } + return 0; +} + +static int tegra_dc_dpaux_write_chunk(struct tegra_dp_priv *dp, u32 cmd, + u32 addr, u8 *data, u32 *size, + u32 *aux_stat) +{ + int i; + u32 reg_val; + u32 timeout_retries = DP_AUX_TIMEOUT_MAX_TRIES; + u32 defer_retries = DP_AUX_DEFER_MAX_TRIES; + u32 temp_data; + + if (*size > DP_AUX_MAX_BYTES) + return -1; /* only write one chunk of data */ + + /* Make sure the command is write command */ + switch (cmd) { + case DPAUX_DP_AUXCTL_CMD_I2CWR: + case DPAUX_DP_AUXCTL_CMD_MOTWR: + case DPAUX_DP_AUXCTL_CMD_AUXWR: + break; + default: + debug("dp: aux write cmd 0x%x is invalid\n", cmd); + return -EINVAL; + } + + tegra_dpaux_writel(dp, DPAUX_DP_AUXADDR, addr); + for (i = 0; i < DP_AUX_MAX_BYTES / 4; ++i) { + memcpy(&temp_data, data, 4); + tegra_dpaux_writel(dp, DPAUX_DP_AUXDATA_WRITE_W(i), temp_data); + data += 4; + } + + reg_val = tegra_dpaux_readl(dp, DPAUX_DP_AUXCTL); + reg_val &= ~DPAUX_DP_AUXCTL_CMD_MASK; + reg_val |= cmd; + reg_val &= ~DPAUX_DP_AUXCTL_CMDLEN_FIELD; + reg_val |= ((*size - 1) << DPAUX_DP_AUXCTL_CMDLEN_SHIFT); + + while ((timeout_retries > 0) && (defer_retries > 0)) { + if ((timeout_retries != DP_AUX_TIMEOUT_MAX_TRIES) || + (defer_retries != DP_AUX_DEFER_MAX_TRIES)) + udelay(1); + + reg_val |= DPAUX_DP_AUXCTL_TRANSACTREQ_PENDING; + tegra_dpaux_writel(dp, DPAUX_DP_AUXCTL, reg_val); + + if (tegra_dpaux_wait_transaction(dp)) + debug("dp: aux write transaction timeout\n"); + + *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); + + if ((*aux_stat & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_PENDING) || + (*aux_stat & DPAUX_DP_AUXSTAT_RX_ERROR_PENDING) || + (*aux_stat & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_PENDING) || + (*aux_stat & DPAUX_DP_AUXSTAT_NO_STOP_ERROR_PENDING)) { + if (timeout_retries-- > 0) { + debug("dp: aux write retry (0x%x) -- %d\n", + *aux_stat, timeout_retries); + /* clear the error bits */ + tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, + *aux_stat); + continue; + } else { + debug("dp: aux write got error (0x%x)\n", + *aux_stat); + return -ETIMEDOUT; + } + } + + if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_I2CDEFER) || + (*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_DEFER)) { + if (defer_retries-- > 0) { + debug("dp: aux write defer (0x%x) -- %d\n", + *aux_stat, defer_retries); + /* clear the error bits */ + tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, + *aux_stat); + continue; + } else { + debug("dp: aux write defer exceeds max retries (0x%x)\n", + *aux_stat); + return -ETIMEDOUT; + } + } + + if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_MASK) == + DPAUX_DP_AUXSTAT_REPLYTYPE_ACK) { + *size = ((*aux_stat) & DPAUX_DP_AUXSTAT_REPLY_M_MASK); + return 0; + } else { + debug("dp: aux write failed (0x%x)\n", *aux_stat); + return -EIO; + } + } + /* Should never come to here */ + return -EIO; +} + +static int tegra_dc_dpaux_read_chunk(struct tegra_dp_priv *dp, u32 cmd, + u32 addr, u8 *data, u32 *size, + u32 *aux_stat) +{ + u32 reg_val; + u32 timeout_retries = DP_AUX_TIMEOUT_MAX_TRIES; + u32 defer_retries = DP_AUX_DEFER_MAX_TRIES; + + if (*size > DP_AUX_MAX_BYTES) { + debug("only read one chunk\n"); + return -EIO; /* only read one chunk */ + } + + /* Check to make sure the command is read command */ + switch (cmd) { + case DPAUX_DP_AUXCTL_CMD_I2CRD: + case DPAUX_DP_AUXCTL_CMD_I2CREQWSTAT: + case DPAUX_DP_AUXCTL_CMD_MOTRD: + case DPAUX_DP_AUXCTL_CMD_AUXRD: + break; + default: + debug("dp: aux read cmd 0x%x is invalid\n", cmd); + return -EIO; + } + + *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); + if (!(*aux_stat & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) { + debug("dp: HPD is not detected\n"); + return -EIO; + } + + tegra_dpaux_writel(dp, DPAUX_DP_AUXADDR, addr); + + reg_val = tegra_dpaux_readl(dp, DPAUX_DP_AUXCTL); + reg_val &= ~DPAUX_DP_AUXCTL_CMD_MASK; + reg_val |= cmd; + reg_val &= ~DPAUX_DP_AUXCTL_CMDLEN_FIELD; + reg_val |= ((*size - 1) << DPAUX_DP_AUXCTL_CMDLEN_SHIFT); + while ((timeout_retries > 0) && (defer_retries > 0)) { + if ((timeout_retries != DP_AUX_TIMEOUT_MAX_TRIES) || + (defer_retries != DP_AUX_DEFER_MAX_TRIES)) + udelay(DP_DPCP_RETRY_SLEEP_NS * 2); + + reg_val |= DPAUX_DP_AUXCTL_TRANSACTREQ_PENDING; + tegra_dpaux_writel(dp, DPAUX_DP_AUXCTL, reg_val); + + if (tegra_dpaux_wait_transaction(dp)) + debug("dp: aux read transaction timeout\n"); + + *aux_stat = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); + + if ((*aux_stat & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR_PENDING) || + (*aux_stat & DPAUX_DP_AUXSTAT_RX_ERROR_PENDING) || + (*aux_stat & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR_PENDING) || + (*aux_stat & DPAUX_DP_AUXSTAT_NO_STOP_ERROR_PENDING)) { + if (timeout_retries-- > 0) { + debug("dp: aux read retry (0x%x) -- %d\n", + *aux_stat, timeout_retries); + /* clear the error bits */ + tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, + *aux_stat); + continue; /* retry */ + } else { + debug("dp: aux read got error (0x%x)\n", + *aux_stat); + return -ETIMEDOUT; + } + } + + if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_I2CDEFER) || + (*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_DEFER)) { + if (defer_retries-- > 0) { + debug("dp: aux read defer (0x%x) -- %d\n", + *aux_stat, defer_retries); + /* clear the error bits */ + tegra_dpaux_writel(dp, DPAUX_DP_AUXSTAT, + *aux_stat); + continue; + } else { + debug("dp: aux read defer exceeds max retries (0x%x)\n", + *aux_stat); + return -ETIMEDOUT; + } + } + + if ((*aux_stat & DPAUX_DP_AUXSTAT_REPLYTYPE_MASK) == + DPAUX_DP_AUXSTAT_REPLYTYPE_ACK) { + int i; + u32 temp_data[4]; + + for (i = 0; i < DP_AUX_MAX_BYTES / 4; ++i) + temp_data[i] = tegra_dpaux_readl(dp, + DPAUX_DP_AUXDATA_READ_W(i)); + + *size = ((*aux_stat) & DPAUX_DP_AUXSTAT_REPLY_M_MASK); + memcpy(data, temp_data, *size); + + return 0; + } else { + debug("dp: aux read failed (0x%x\n", *aux_stat); + return -EIO; + } + } + /* Should never come to here */ + debug("%s: can't\n", __func__); + + return -EIO; +} + +static int tegra_dc_dpaux_read(struct tegra_dp_priv *dp, u32 cmd, u32 addr, + u8 *data, u32 *size, u32 *aux_stat) +{ + u32 finished = 0; + u32 cur_size; + int ret = 0; + + do { + cur_size = *size - finished; + if (cur_size > DP_AUX_MAX_BYTES) + cur_size = DP_AUX_MAX_BYTES; + + ret = tegra_dc_dpaux_read_chunk(dp, cmd, addr, + data, &cur_size, aux_stat); + if (ret) + break; + + /* cur_size should be the real size returned */ + addr += cur_size; + data += cur_size; + finished += cur_size; + + } while (*size > finished); + *size = finished; + + return ret; +} + +static int tegra_dc_dp_dpcd_read(struct tegra_dp_priv *dp, u32 cmd, + u8 *data_ptr) +{ + u32 size = 1; + u32 status = 0; + int ret; + + ret = tegra_dc_dpaux_read_chunk(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, + cmd, data_ptr, &size, &status); + if (ret) { + debug("dp: Failed to read DPCD data. CMD 0x%x, Status 0x%x\n", + cmd, status); + } + + return ret; +} + +static int tegra_dc_dp_dpcd_write(struct tegra_dp_priv *dp, u32 cmd, + u8 data) +{ + u32 size = 1; + u32 status = 0; + int ret; + + ret = tegra_dc_dpaux_write_chunk(dp, DPAUX_DP_AUXCTL_CMD_AUXWR, + cmd, &data, &size, &status); + if (ret) { + debug("dp: Failed to write DPCD data. CMD 0x%x, Status 0x%x\n", + cmd, status); + } + + return ret; +} + +static int tegra_dc_i2c_aux_read(struct tegra_dp_priv *dp, u32 i2c_addr, + u8 addr, u8 *data, u32 size, u32 *aux_stat) +{ + u32 finished = 0; + int ret = 0; + + do { + u32 cur_size = min((u32)DP_AUX_MAX_BYTES, size - finished); + + u32 len = 1; + ret = tegra_dc_dpaux_write_chunk( + dp, DPAUX_DP_AUXCTL_CMD_MOTWR, i2c_addr, + &addr, &len, aux_stat); + if (ret) { + debug("%s: error sending address to read.\n", + __func__); + return ret; + } + + ret = tegra_dc_dpaux_read_chunk( + dp, DPAUX_DP_AUXCTL_CMD_I2CRD, i2c_addr, + data, &cur_size, aux_stat); + if (ret) { + debug("%s: error reading data.\n", __func__); + return ret; + } + + /* cur_size should be the real size returned */ + addr += cur_size; + data += cur_size; + finished += cur_size; + } while (size > finished); + + return finished; +} + +static void tegra_dc_dpaux_enable(struct tegra_dp_priv *dp) +{ + /* clear interrupt */ + tegra_dpaux_writel(dp, DPAUX_INTR_AUX, 0xffffffff); + /* do not enable interrupt for now. Enable them when Isr in place */ + tegra_dpaux_writel(dp, DPAUX_INTR_EN_AUX, 0x0); + + tegra_dpaux_writel(dp, DPAUX_HYBRID_PADCTL, + DPAUX_HYBRID_PADCTL_AUX_DRVZ_OHM_50 | + DPAUX_HYBRID_PADCTL_AUX_CMH_V0_70 | + 0x18 << DPAUX_HYBRID_PADCTL_AUX_DRVI_SHIFT | + DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV_ENABLE); + + tegra_dpaux_writel(dp, DPAUX_HYBRID_SPARE, + DPAUX_HYBRID_SPARE_PAD_PWR_POWERUP); +} + +#ifdef DEBUG +static void tegra_dc_dp_dump_link_cfg(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *link_cfg) +{ + debug("DP config: cfg_name cfg_value\n"); + debug(" Lane Count %d\n", + link_cfg->max_lane_count); + debug(" SupportEnhancedFraming %s\n", + link_cfg->support_enhanced_framing ? "Y" : "N"); + debug(" Bandwidth %d\n", + link_cfg->max_link_bw); + debug(" bpp %d\n", + link_cfg->bits_per_pixel); + debug(" EnhancedFraming %s\n", + link_cfg->enhanced_framing ? "Y" : "N"); + debug(" Scramble_enabled %s\n", + link_cfg->scramble_ena ? "Y" : "N"); + debug(" LinkBW %d\n", + link_cfg->link_bw); + debug(" lane_count %d\n", + link_cfg->lane_count); + debug(" activespolarity %d\n", + link_cfg->activepolarity); + debug(" active_count %d\n", + link_cfg->active_count); + debug(" tu_size %d\n", + link_cfg->tu_size); + debug(" active_frac %d\n", + link_cfg->active_frac); + debug(" watermark %d\n", + link_cfg->watermark); + debug(" hblank_sym %d\n", + link_cfg->hblank_sym); + debug(" vblank_sym %d\n", + link_cfg->vblank_sym); +} +#endif + +static int _tegra_dp_lower_link_config(struct tegra_dp_priv *dp, + struct tegra_dp_link_config *cfg) +{ + switch (cfg->link_bw) { + case SOR_LINK_SPEED_G1_62: + if (cfg->max_link_bw > SOR_LINK_SPEED_G1_62) + cfg->link_bw = SOR_LINK_SPEED_G2_7; + cfg->lane_count /= 2; + break; + case SOR_LINK_SPEED_G2_7: + cfg->link_bw = SOR_LINK_SPEED_G1_62; + break; + case SOR_LINK_SPEED_G5_4: + if (cfg->lane_count == 1) { + cfg->link_bw = SOR_LINK_SPEED_G2_7; + cfg->lane_count = cfg->max_lane_count; + } else { + cfg->lane_count /= 2; + } + break; + default: + debug("dp: Error link rate %d\n", cfg->link_bw); + return -ENOLINK; + } + + return (cfg->lane_count > 0) ? 0 : -ENOLINK; +} + +/* + * Calcuate if given cfg can meet the mode request. + * Return 0 if mode is possible, -1 otherwise + */ +static int tegra_dc_dp_calc_config(struct tegra_dp_priv *dp, + const struct display_timing *timing, + struct tegra_dp_link_config *link_cfg) +{ + const u32 link_rate = 27 * link_cfg->link_bw * 1000 * 1000; + const u64 f = 100000; /* precision factor */ + u32 num_linkclk_line; /* Number of link clocks per line */ + u64 ratio_f; /* Ratio of incoming to outgoing data rate */ + u64 frac_f; + u64 activesym_f; /* Activesym per TU */ + u64 activecount_f; + u32 activecount; + u32 activepolarity; + u64 approx_value_f; + u32 activefrac = 0; + u64 accumulated_error_f = 0; + u32 lowest_neg_activecount = 0; + u32 lowest_neg_activepolarity = 0; + u32 lowest_neg_tusize = 64; + u32 num_symbols_per_line; + u64 lowest_neg_activefrac = 0; + u64 lowest_neg_error_f = 64 * f; + u64 watermark_f; + int i; + int neg; + + if (!link_rate || !link_cfg->lane_count || !timing->pixelclock.typ || + !link_cfg->bits_per_pixel) + return -1; + + if ((u64)timing->pixelclock.typ * link_cfg->bits_per_pixel >= + (u64)link_rate * 8 * link_cfg->lane_count) + return -1; + + num_linkclk_line = (u32)(lldiv(link_rate * timing->hactive.typ, + timing->pixelclock.typ)); + + ratio_f = (u64)timing->pixelclock.typ * link_cfg->bits_per_pixel * f; + ratio_f /= 8; + do_div(ratio_f, link_rate * link_cfg->lane_count); + + for (i = 64; i >= 32; --i) { + activesym_f = ratio_f * i; + activecount_f = lldiv(activesym_f, (u32)f) * f; + frac_f = activesym_f - activecount_f; + activecount = (u32)(lldiv(activecount_f, (u32)f)); + + if (frac_f < (lldiv(f, 2))) /* fraction < 0.5 */ + activepolarity = 0; + else { + activepolarity = 1; + frac_f = f - frac_f; + } + + if (frac_f != 0) { + /* warning: frac_f should be 64-bit */ + frac_f = lldiv(f * f, frac_f); /* 1 / fraction */ + if (frac_f > (15 * f)) + activefrac = activepolarity ? 1 : 15; + else + activefrac = activepolarity ? + (u32)lldiv(frac_f, (u32)f) + 1 : + (u32)lldiv(frac_f, (u32)f); + } + + if (activefrac == 1) + activepolarity = 0; + + if (activepolarity == 1) + approx_value_f = activefrac ? lldiv( + (activecount_f + (activefrac * f - f) * f), + (activefrac * f)) : + activecount_f + f; + else + approx_value_f = activefrac ? + activecount_f + lldiv(f, activefrac) : + activecount_f; + + if (activesym_f < approx_value_f) { + accumulated_error_f = num_linkclk_line * + lldiv(approx_value_f - activesym_f, i); + neg = 1; + } else { + accumulated_error_f = num_linkclk_line * + lldiv(activesym_f - approx_value_f, i); + neg = 0; + } + + if ((neg && (lowest_neg_error_f > accumulated_error_f)) || + (accumulated_error_f == 0)) { + lowest_neg_error_f = accumulated_error_f; + lowest_neg_tusize = i; + lowest_neg_activecount = activecount; + lowest_neg_activepolarity = activepolarity; + lowest_neg_activefrac = activefrac; + + if (accumulated_error_f == 0) + break; + } + } + + if (lowest_neg_activefrac == 0) { + link_cfg->activepolarity = 0; + link_cfg->active_count = lowest_neg_activepolarity ? + lowest_neg_activecount : lowest_neg_activecount - 1; + link_cfg->tu_size = lowest_neg_tusize; + link_cfg->active_frac = 1; + } else { + link_cfg->activepolarity = lowest_neg_activepolarity; + link_cfg->active_count = (u32)lowest_neg_activecount; + link_cfg->tu_size = lowest_neg_tusize; + link_cfg->active_frac = (u32)lowest_neg_activefrac; + } + + watermark_f = lldiv(ratio_f * link_cfg->tu_size * (f - ratio_f), f); + link_cfg->watermark = (u32)(lldiv(watermark_f + lowest_neg_error_f, + f)) + link_cfg->bits_per_pixel / 4 - 1; + num_symbols_per_line = (timing->hactive.typ * + link_cfg->bits_per_pixel) / + (8 * link_cfg->lane_count); + + if (link_cfg->watermark > 30) { + debug("dp: sor setting: unable to get a good tusize, force watermark to 30\n"); + link_cfg->watermark = 30; + return -1; + } else if (link_cfg->watermark > num_symbols_per_line) { + debug("dp: sor setting: force watermark to the number of symbols in the line\n"); + link_cfg->watermark = num_symbols_per_line; + return -1; + } + + /* + * Refer to dev_disp.ref for more information. + * # symbols/hblank = ((SetRasterBlankEnd.X + SetRasterSize.Width - + * SetRasterBlankStart.X - 7) * link_clk / pclk) + * - 3 * enhanced_framing - Y + * where Y = (# lanes == 4) 3 : (# lanes == 2) ? 6 : 12 + */ + link_cfg->hblank_sym = (int)lldiv(((uint64_t)timing->hback_porch.typ + + timing->hfront_porch.typ + timing->hsync_len.typ - 7) * + link_rate, timing->pixelclock.typ) - + 3 * link_cfg->enhanced_framing - + (12 / link_cfg->lane_count); + + if (link_cfg->hblank_sym < 0) + link_cfg->hblank_sym = 0; + + /* + * Refer to dev_disp.ref for more information. + * # symbols/vblank = ((SetRasterBlankStart.X - + * SetRasterBlankEen.X - 25) * link_clk / pclk) + * - Y - 1; + * where Y = (# lanes == 4) 12 : (# lanes == 2) ? 21 : 39 + */ + link_cfg->vblank_sym = (int)lldiv(((uint64_t)timing->hactive.typ - 25) + * link_rate, timing->pixelclock.typ) - (36 / + link_cfg->lane_count) - 4; + + if (link_cfg->vblank_sym < 0) + link_cfg->vblank_sym = 0; + + link_cfg->is_valid = 1; +#ifdef DEBUG + tegra_dc_dp_dump_link_cfg(dp, link_cfg); +#endif + + return 0; +} + +static int tegra_dc_dp_init_max_link_cfg( + const struct display_timing *timing, + struct tegra_dp_priv *dp, + struct tegra_dp_link_config *link_cfg) +{ + const int drive_current = 0x40404040; + const int preemphasis = 0x0f0f0f0f; + const int postcursor = 0; + u8 dpcd_data; + int ret; + + ret = tegra_dc_dp_dpcd_read(dp, DP_MAX_LANE_COUNT, &dpcd_data); + if (ret) + return ret; + link_cfg->max_lane_count = dpcd_data & DP_MAX_LANE_COUNT_MASK; + link_cfg->tps3_supported = (dpcd_data & + DP_MAX_LANE_COUNT_TPS3_SUPPORTED_YES) ? 1 : 0; + + link_cfg->support_enhanced_framing = + (dpcd_data & DP_MAX_LANE_COUNT_ENHANCED_FRAMING_YES) ? + 1 : 0; + + ret = tegra_dc_dp_dpcd_read(dp, DP_MAX_DOWNSPREAD, &dpcd_data); + if (ret) + return ret; + link_cfg->downspread = (dpcd_data & DP_MAX_DOWNSPREAD_VAL_0_5_PCT) ? + 1 : 0; + + ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_TRAINING_AUX_RD_INTERVAL, + &link_cfg->aux_rd_interval); + if (ret) + return ret; + ret = tegra_dc_dp_dpcd_read(dp, DP_MAX_LINK_RATE, + &link_cfg->max_link_bw); + if (ret) + return ret; + + /* + * Set to a high value for link training and attach. + * Will be re-programmed when dp is enabled. + */ + link_cfg->drive_current = drive_current; + link_cfg->preemphasis = preemphasis; + link_cfg->postcursor = postcursor; + + ret = tegra_dc_dp_dpcd_read(dp, DP_EDP_CONFIGURATION_CAP, &dpcd_data); + if (ret) + return ret; + + link_cfg->alt_scramber_reset_cap = + (dpcd_data & DP_EDP_CONFIGURATION_CAP_ASC_RESET_YES) ? + 1 : 0; + link_cfg->only_enhanced_framing = + (dpcd_data & DP_EDP_CONFIGURATION_CAP_FRAMING_CHANGE_YES) ? + 1 : 0; + + link_cfg->lane_count = link_cfg->max_lane_count; + link_cfg->link_bw = link_cfg->max_link_bw; + link_cfg->enhanced_framing = link_cfg->support_enhanced_framing; + link_cfg->frame_in_ms = (1000 / 60) + 1; + + tegra_dc_dp_calc_config(dp, timing, link_cfg); + return 0; +} + +static int tegra_dc_dp_set_assr(struct tegra_dp_priv *priv, + struct udevice *sor, int ena) +{ + int ret; + + u8 dpcd_data = ena ? + DP_MAIN_LINK_CHANNEL_CODING_SET_ASC_RESET_ENABLE : + DP_MAIN_LINK_CHANNEL_CODING_SET_ASC_RESET_DISABLE; + + ret = tegra_dc_dp_dpcd_write(priv, DP_EDP_CONFIGURATION_SET, + dpcd_data); + if (ret) + return ret; + + /* Also reset the scrambler to 0xfffe */ + tegra_dc_sor_set_internal_panel(sor, ena); + return 0; +} + +static int tegra_dp_set_link_bandwidth(struct tegra_dp_priv *dp, + struct udevice *sor, + u8 link_bw) +{ + tegra_dc_sor_set_link_bandwidth(sor, link_bw); + + /* Sink side */ + return tegra_dc_dp_dpcd_write(dp, DP_LINK_BW_SET, link_bw); +} + +static int tegra_dp_set_lane_count(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *link_cfg, + struct udevice *sor) +{ + u8 dpcd_data; + int ret; + + /* check if panel support enhanched_framing */ + dpcd_data = link_cfg->lane_count; + if (link_cfg->enhanced_framing) + dpcd_data |= DP_LANE_COUNT_SET_ENHANCEDFRAMING_T; + ret = tegra_dc_dp_dpcd_write(dp, DP_LANE_COUNT_SET, dpcd_data); + if (ret) + return ret; + + tegra_dc_sor_set_lane_count(sor, link_cfg->lane_count); + + /* Also power down lanes that will not be used */ + return 0; +} + +static int tegra_dc_dp_link_trained(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *cfg) +{ + u32 lane; + u8 mask; + u8 data; + int ret; + + for (lane = 0; lane < cfg->lane_count; ++lane) { + ret = tegra_dc_dp_dpcd_read(dp, (lane / 2) ? + DP_LANE2_3_STATUS : DP_LANE0_1_STATUS, + &data); + if (ret) + return ret; + mask = (lane & 1) ? + NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES | + NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_YES | + NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES : + DP_LANE_CR_DONE | + DP_LANE_CHANNEL_EQ_DONE | + DP_LANE_SYMBOL_LOCKED; + if ((data & mask) != mask) + return -1; + } + return 0; +} + +static int tegra_dp_channel_eq_status(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *cfg) +{ + u32 cnt; + u32 n_lanes = cfg->lane_count; + u8 data; + u8 ce_done = 1; + int ret; + + for (cnt = 0; cnt < n_lanes / 2; cnt++) { + ret = tegra_dc_dp_dpcd_read(dp, DP_LANE0_1_STATUS + cnt, &data); + if (ret) + return ret; + + if (n_lanes == 1) { + ce_done = (data & (0x1 << + NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) && + (data & (0x1 << + NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)); + break; + } else if (!(data & (0x1 << + NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) || + !(data & (0x1 << + NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)) || + !(data & (0x1 << + NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT)) || + !(data & (0x1 << + NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT))) + return -EIO; + } + + if (ce_done) { + ret = tegra_dc_dp_dpcd_read(dp, + DP_LANE_ALIGN_STATUS_UPDATED, + &data); + if (ret) + return ret; + if (!(data & NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_YES)) + ce_done = 0; + } + + return ce_done ? 0 : -EIO; +} + +static int tegra_dp_clock_recovery_status(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *cfg) +{ + u32 cnt; + u32 n_lanes = cfg->lane_count; + u8 data_ptr; + int ret; + + for (cnt = 0; cnt < n_lanes / 2; cnt++) { + ret = tegra_dc_dp_dpcd_read(dp, (DP_LANE0_1_STATUS + cnt), + &data_ptr); + if (ret) + return ret; + + if (n_lanes == 1) + return (data_ptr & NV_DPCD_STATUS_LANEX_CR_DONE_YES) ? + 1 : 0; + else if (!(data_ptr & NV_DPCD_STATUS_LANEX_CR_DONE_YES) || + !(data_ptr & (NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES))) + return 0; + } + + return 1; +} + +static int tegra_dp_lt_adjust(struct tegra_dp_priv *dp, u32 pe[4], u32 vs[4], + u32 pc[4], u8 pc_supported, + const struct tegra_dp_link_config *cfg) +{ + size_t cnt; + u8 data_ptr; + u32 n_lanes = cfg->lane_count; + int ret; + + for (cnt = 0; cnt < n_lanes / 2; cnt++) { + ret = tegra_dc_dp_dpcd_read(dp, DP_ADJUST_REQUEST_LANE0_1 + cnt, + &data_ptr); + if (ret) + return ret; + pe[2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEX_PE_MASK) >> + NV_DPCD_ADJUST_REQ_LANEX_PE_SHIFT; + vs[2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEX_DC_MASK) >> + NV_DPCD_ADJUST_REQ_LANEX_DC_SHIFT; + pe[1 + 2 * cnt] = + (data_ptr & NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_MASK) >> + NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_SHIFT; + vs[1 + 2 * cnt] = + (data_ptr & NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK) >> + NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_SHIFT; + } + if (pc_supported) { + ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_ADJUST_REQ_POST_CURSOR2, + &data_ptr); + if (ret) + return ret; + for (cnt = 0; cnt < n_lanes; cnt++) { + pc[cnt] = (data_ptr >> + NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_SHIFT(cnt)) & + NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_MASK; + } + } + + return 0; +} + +static void tegra_dp_wait_aux_training(struct tegra_dp_priv *dp, + bool is_clk_recovery, + const struct tegra_dp_link_config *cfg) +{ + if (!cfg->aux_rd_interval) + udelay(is_clk_recovery ? 200 : 500); + else + mdelay(cfg->aux_rd_interval * 4); +} + +static void tegra_dp_tpg(struct tegra_dp_priv *dp, u32 tp, u32 n_lanes, + const struct tegra_dp_link_config *cfg) +{ + u8 data = (tp == training_pattern_disabled) + ? (tp | NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F) + : (tp | NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T); + + tegra_dc_sor_set_dp_linkctl(dp->sor, 1, tp, cfg); + tegra_dc_dp_dpcd_write(dp, DP_TRAINING_PATTERN_SET, data); +} + +static int tegra_dp_link_config(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *link_cfg) +{ + u8 dpcd_data; + u32 retry; + int ret; + + if (link_cfg->lane_count == 0) { + debug("dp: error: lane count is 0. Can not set link config.\n"); + return -ENOLINK; + } + + /* Set power state if it is not in normal level */ + ret = tegra_dc_dp_dpcd_read(dp, DP_SET_POWER, &dpcd_data); + if (ret) + return ret; + + if (dpcd_data == DP_SET_POWER_D3) { + dpcd_data = DP_SET_POWER_D0; + + /* DP spec requires 3 retries */ + for (retry = 3; retry > 0; --retry) { + ret = tegra_dc_dp_dpcd_write(dp, DP_SET_POWER, + dpcd_data); + if (!ret) + break; + if (retry == 1) { + debug("dp: Failed to set DP panel power\n"); + return ret; + } + } + } + + /* Enable ASSR if possible */ + if (link_cfg->alt_scramber_reset_cap) { + ret = tegra_dc_dp_set_assr(dp, dp->sor, 1); + if (ret) + return ret; + } + + ret = tegra_dp_set_link_bandwidth(dp, dp->sor, link_cfg->link_bw); + if (ret) { + debug("dp: Failed to set link bandwidth\n"); + return ret; + } + ret = tegra_dp_set_lane_count(dp, link_cfg, dp->sor); + if (ret) { + debug("dp: Failed to set lane count\n"); + return ret; + } + tegra_dc_sor_set_dp_linkctl(dp->sor, 1, training_pattern_none, + link_cfg); + + return 0; +} + +static int tegra_dp_lower_link_config(struct tegra_dp_priv *dp, + const struct display_timing *timing, + struct tegra_dp_link_config *cfg) +{ + struct tegra_dp_link_config tmp_cfg; + int ret; + + tmp_cfg = *cfg; + cfg->is_valid = 0; + + ret = _tegra_dp_lower_link_config(dp, cfg); + if (!ret) + ret = tegra_dc_dp_calc_config(dp, timing, cfg); + if (!ret) + ret = tegra_dp_link_config(dp, cfg); + if (ret) + goto fail; + + return 0; + +fail: + *cfg = tmp_cfg; + tegra_dp_link_config(dp, &tmp_cfg); + return ret; +} + +static int tegra_dp_lt_config(struct tegra_dp_priv *dp, u32 pe[4], u32 vs[4], + u32 pc[4], const struct tegra_dp_link_config *cfg) +{ + struct udevice *sor = dp->sor; + u32 n_lanes = cfg->lane_count; + u8 pc_supported = cfg->tps3_supported; + u32 cnt; + u32 val; + + for (cnt = 0; cnt < n_lanes; cnt++) { + u32 mask = 0; + u32 pe_reg, vs_reg, pc_reg; + u32 shift = 0; + + switch (cnt) { + case 0: + mask = PR_LANE2_DP_LANE0_MASK; + shift = PR_LANE2_DP_LANE0_SHIFT; + break; + case 1: + mask = PR_LANE1_DP_LANE1_MASK; + shift = PR_LANE1_DP_LANE1_SHIFT; + break; + case 2: + mask = PR_LANE0_DP_LANE2_MASK; + shift = PR_LANE0_DP_LANE2_SHIFT; + break; + case 3: + mask = PR_LANE3_DP_LANE3_MASK; + shift = PR_LANE3_DP_LANE3_SHIFT; + break; + default: + debug("dp: incorrect lane cnt\n"); + return -EINVAL; + } + + pe_reg = tegra_dp_pe_regs[pc[cnt]][vs[cnt]][pe[cnt]]; + vs_reg = tegra_dp_vs_regs[pc[cnt]][vs[cnt]][pe[cnt]]; + pc_reg = tegra_dp_pc_regs[pc[cnt]][vs[cnt]][pe[cnt]]; + + tegra_dp_set_pe_vs_pc(sor, mask, pe_reg << shift, + vs_reg << shift, pc_reg << shift, + pc_supported); + } + + tegra_dp_disable_tx_pu(dp->sor); + udelay(20); + + for (cnt = 0; cnt < n_lanes; cnt++) { + u32 max_vs_flag = tegra_dp_is_max_vs(pe[cnt], vs[cnt]); + u32 max_pe_flag = tegra_dp_is_max_pe(pe[cnt], vs[cnt]); + + val = (vs[cnt] << NV_DPCD_TRAINING_LANEX_SET_DC_SHIFT) | + (max_vs_flag ? + NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_T : + NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F) | + (pe[cnt] << NV_DPCD_TRAINING_LANEX_SET_PE_SHIFT) | + (max_pe_flag ? + NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_T : + NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_F); + tegra_dc_dp_dpcd_write(dp, (DP_TRAINING_LANE0_SET + cnt), val); + } + + if (pc_supported) { + for (cnt = 0; cnt < n_lanes / 2; cnt++) { + u32 max_pc_flag0 = tegra_dp_is_max_pc(pc[cnt]); + u32 max_pc_flag1 = tegra_dp_is_max_pc(pc[cnt + 1]); + val = (pc[cnt] << NV_DPCD_LANEX_SET2_PC2_SHIFT) | + (max_pc_flag0 ? + NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_T : + NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_F) | + (pc[cnt + 1] << + NV_DPCD_LANEXPLUS1_SET2_PC2_SHIFT) | + (max_pc_flag1 ? + NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_T : + NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_F); + tegra_dc_dp_dpcd_write(dp, + NV_DPCD_TRAINING_LANE0_1_SET2 + + cnt, val); + } + } + + return 0; +} + +static int _tegra_dp_channel_eq(struct tegra_dp_priv *dp, u32 pe[4], + u32 vs[4], u32 pc[4], u8 pc_supported, + u32 n_lanes, + const struct tegra_dp_link_config *cfg) +{ + u32 retry_cnt; + + for (retry_cnt = 0; retry_cnt < 4; retry_cnt++) { + int ret; + + if (retry_cnt) { + ret = tegra_dp_lt_adjust(dp, pe, vs, pc, pc_supported, + cfg); + if (ret) + return ret; + tegra_dp_lt_config(dp, pe, vs, pc, cfg); + } + + tegra_dp_wait_aux_training(dp, false, cfg); + + if (!tegra_dp_clock_recovery_status(dp, cfg)) { + debug("dp: CR failed in channel EQ sequence!\n"); + break; + } + + if (!tegra_dp_channel_eq_status(dp, cfg)) + return 0; + } + + return -EIO; +} + +static int tegra_dp_channel_eq(struct tegra_dp_priv *dp, u32 pe[4], u32 vs[4], + u32 pc[4], + const struct tegra_dp_link_config *cfg) +{ + u32 n_lanes = cfg->lane_count; + u8 pc_supported = cfg->tps3_supported; + int ret; + u32 tp_src = training_pattern_2; + + if (pc_supported) + tp_src = training_pattern_3; + + tegra_dp_tpg(dp, tp_src, n_lanes, cfg); + + ret = _tegra_dp_channel_eq(dp, pe, vs, pc, pc_supported, n_lanes, cfg); + + tegra_dp_tpg(dp, training_pattern_disabled, n_lanes, cfg); + + return ret; +} + +static int _tegra_dp_clk_recovery(struct tegra_dp_priv *dp, u32 pe[4], + u32 vs[4], u32 pc[4], u8 pc_supported, + u32 n_lanes, + const struct tegra_dp_link_config *cfg) +{ + u32 vs_temp[4]; + u32 retry_cnt = 0; + + do { + tegra_dp_lt_config(dp, pe, vs, pc, cfg); + tegra_dp_wait_aux_training(dp, true, cfg); + + if (tegra_dp_clock_recovery_status(dp, cfg)) + return 0; + + memcpy(vs_temp, vs, sizeof(vs_temp)); + tegra_dp_lt_adjust(dp, pe, vs, pc, pc_supported, cfg); + + if (memcmp(vs_temp, vs, sizeof(vs_temp))) + retry_cnt = 0; + else + ++retry_cnt; + } while (retry_cnt < 5); + + return -EIO; +} + +static int tegra_dp_clk_recovery(struct tegra_dp_priv *dp, u32 pe[4], + u32 vs[4], u32 pc[4], + const struct tegra_dp_link_config *cfg) +{ + u32 n_lanes = cfg->lane_count; + u8 pc_supported = cfg->tps3_supported; + int err; + + tegra_dp_tpg(dp, training_pattern_1, n_lanes, cfg); + + err = _tegra_dp_clk_recovery(dp, pe, vs, pc, pc_supported, n_lanes, + cfg); + if (err < 0) + tegra_dp_tpg(dp, training_pattern_disabled, n_lanes, cfg); + + return err; +} + +static int tegra_dc_dp_full_link_training(struct tegra_dp_priv *dp, + const struct display_timing *timing, + struct tegra_dp_link_config *cfg) +{ + struct udevice *sor = dp->sor; + int err; + u32 pe[4], vs[4], pc[4]; + + tegra_sor_precharge_lanes(sor, cfg); + +retry_cr: + memset(pe, PREEMPHASIS_DISABLED, sizeof(pe)); + memset(vs, DRIVECURRENT_LEVEL0, sizeof(vs)); + memset(pc, POSTCURSOR2_LEVEL0, sizeof(pc)); + + err = tegra_dp_clk_recovery(dp, pe, vs, pc, cfg); + if (err) { + if (!tegra_dp_lower_link_config(dp, timing, cfg)) + goto retry_cr; + + debug("dp: clk recovery failed\n"); + goto fail; + } + + err = tegra_dp_channel_eq(dp, pe, vs, pc, cfg); + if (err) { + if (!tegra_dp_lower_link_config(dp, timing, cfg)) + goto retry_cr; + + debug("dp: channel equalization failed\n"); + goto fail; + } +#ifdef DEBUG + tegra_dc_dp_dump_link_cfg(dp, cfg); +#endif + return 0; + +fail: + return err; +} + +/* + * All link training functions are ported from kernel dc driver. + * See more details at drivers/video/tegra/dc/dp.c + */ +static int tegra_dc_dp_fast_link_training(struct tegra_dp_priv *dp, + const struct tegra_dp_link_config *link_cfg, + struct udevice *sor) +{ + u8 link_bw; + u8 lane_count; + u16 data16; + u32 data32; + u32 size; + u32 status; + int j; + u32 mask = 0xffff >> ((4 - link_cfg->lane_count) * 4); + + tegra_dc_sor_set_lane_parm(sor, link_cfg); + tegra_dc_dp_dpcd_write(dp, DP_MAIN_LINK_CHANNEL_CODING_SET, + DP_SET_ANSI_8B10B); + + /* Send TP1 */ + tegra_dc_sor_set_dp_linkctl(sor, 1, training_pattern_1, link_cfg); + tegra_dc_dp_dpcd_write(dp, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_1); + + for (j = 0; j < link_cfg->lane_count; ++j) + tegra_dc_dp_dpcd_write(dp, DP_TRAINING_LANE0_SET + j, 0x24); + udelay(520); + + size = sizeof(data16); + tegra_dc_dpaux_read(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, + DP_LANE0_1_STATUS, (u8 *)&data16, &size, &status); + status = mask & 0x1111; + if ((data16 & status) != status) { + debug("dp: Link training error for TP1 (%#x, status %#x)\n", + data16, status); + return -EFAULT; + } + + /* enable ASSR */ + tegra_dc_dp_set_assr(dp, sor, link_cfg->scramble_ena); + tegra_dc_sor_set_dp_linkctl(sor, 1, training_pattern_3, link_cfg); + + tegra_dc_dp_dpcd_write(dp, DP_TRAINING_PATTERN_SET, + link_cfg->link_bw == 20 ? 0x23 : 0x22); + for (j = 0; j < link_cfg->lane_count; ++j) + tegra_dc_dp_dpcd_write(dp, DP_TRAINING_LANE0_SET + j, 0x24); + udelay(520); + + size = sizeof(data32); + tegra_dc_dpaux_read(dp, DPAUX_DP_AUXCTL_CMD_AUXRD, DP_LANE0_1_STATUS, + (u8 *)&data32, &size, &status); + if ((data32 & mask) != (0x7777 & mask)) { + debug("dp: Link training error for TP2/3 (0x%x)\n", data32); + return -EFAULT; + } + + tegra_dc_sor_set_dp_linkctl(sor, 1, training_pattern_disabled, + link_cfg); + tegra_dc_dp_dpcd_write(dp, DP_TRAINING_PATTERN_SET, 0); + + if (tegra_dc_dp_link_trained(dp, link_cfg)) { + tegra_dc_sor_read_link_config(sor, &link_bw, &lane_count); + debug("Fast link training failed, link bw %d, lane # %d\n", + link_bw, lane_count); + return -EFAULT; + } + + debug("Fast link training succeeded, link bw %d, lane %d\n", + link_cfg->link_bw, link_cfg->lane_count); + + return 0; +} + +static int tegra_dp_do_link_training(struct tegra_dp_priv *dp, + struct tegra_dp_link_config *link_cfg, + const struct display_timing *timing, + struct udevice *sor) +{ + u8 link_bw; + u8 lane_count; + int ret; + + if (DO_FAST_LINK_TRAINING) { + ret = tegra_dc_dp_fast_link_training(dp, link_cfg, sor); + if (ret) { + debug("dp: fast link training failed\n"); + } else { + /* + * set to a known-good drive setting if fast link + * succeeded. Ignore any error. + */ + ret = tegra_dc_sor_set_voltage_swing(dp->sor, link_cfg); + if (ret) + debug("Failed to set voltage swing\n"); + } + } else { + ret = -ENOSYS; + } + if (ret) { + /* Try full link training then */ + ret = tegra_dc_dp_full_link_training(dp, timing, link_cfg); + if (ret) { + debug("dp: full link training failed\n"); + return ret; + } + } + + /* Everything is good; double check the link config */ + tegra_dc_sor_read_link_config(sor, &link_bw, &lane_count); + + if ((link_cfg->link_bw == link_bw) && + (link_cfg->lane_count == lane_count)) + return 0; + else + return -EFAULT; +} + +static int tegra_dc_dp_explore_link_cfg(struct tegra_dp_priv *dp, + struct tegra_dp_link_config *link_cfg, + struct udevice *sor, + const struct display_timing *timing) +{ + struct tegra_dp_link_config temp_cfg; + + if (!timing->pixelclock.typ || !timing->hactive.typ || + !timing->vactive.typ) { + debug("dp: error mode configuration"); + return -EINVAL; + } + if (!link_cfg->max_link_bw || !link_cfg->max_lane_count) { + debug("dp: error link configuration"); + return -EINVAL; + } + + link_cfg->is_valid = 0; + + memcpy(&temp_cfg, link_cfg, sizeof(temp_cfg)); + + temp_cfg.link_bw = temp_cfg.max_link_bw; + temp_cfg.lane_count = temp_cfg.max_lane_count; + + /* + * set to max link config + */ + if ((!tegra_dc_dp_calc_config(dp, timing, &temp_cfg)) && + (!tegra_dp_link_config(dp, &temp_cfg)) && + (!tegra_dp_do_link_training(dp, &temp_cfg, timing, sor))) + /* the max link cfg is doable */ + memcpy(link_cfg, &temp_cfg, sizeof(temp_cfg)); + + return link_cfg->is_valid ? 0 : -EFAULT; +} + +static int tegra_dp_hpd_plug(struct tegra_dp_priv *dp) +{ + const int vdd_to_hpd_delay_ms = 200; + u32 val; + ulong start; + + start = get_timer(0); + do { + val = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT); + if (val & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED) + return 0; + udelay(100); + } while (get_timer(start) < vdd_to_hpd_delay_ms); + + return -EIO; +} + +static int tegra_dc_dp_sink_out_of_sync(struct tegra_dp_priv *dp, u32 delay_ms) +{ + u8 dpcd_data; + int out_of_sync; + int ret; + + debug("%s: delay=%d\n", __func__, delay_ms); + mdelay(delay_ms); + ret = tegra_dc_dp_dpcd_read(dp, DP_SINK_STATUS, &dpcd_data); + if (ret) + return ret; + + out_of_sync = !(dpcd_data & DP_SINK_STATUS_PORT0_IN_SYNC); + if (out_of_sync) + debug("SINK receive port 0 out of sync, data=%x\n", dpcd_data); + else + debug("SINK is in synchronization\n"); + + return out_of_sync; +} + +static int tegra_dc_dp_check_sink(struct tegra_dp_priv *dp, + struct tegra_dp_link_config *link_cfg, + const struct display_timing *timing) +{ + const int max_retry = 5; + int delay_frame; + int retries; + + /* + * DP TCON may skip some main stream frames, thus we need to wait + * some delay before reading the DPCD SINK STATUS register, starting + * from 5 + */ + delay_frame = 5; + + retries = max_retry; + do { + int ret; + + if (!tegra_dc_dp_sink_out_of_sync(dp, link_cfg->frame_in_ms * + delay_frame)) + return 0; + + debug("%s: retries left %d\n", __func__, retries); + if (!retries--) { + printf("DP: Out of sync after %d retries\n", max_retry); + return -EIO; + } + ret = tegra_dc_sor_detach(dp->dc_dev, dp->sor); + if (ret) + return ret; + if (tegra_dc_dp_explore_link_cfg(dp, link_cfg, dp->sor, + timing)) { + debug("dp: %s: error to configure link\n", __func__); + continue; + } + + tegra_dc_sor_set_power_state(dp->sor, 1); + tegra_dc_sor_attach(dp->dc_dev, dp->sor, link_cfg, timing); + + /* Increase delay_frame for next try in case the sink is + skipping more frames */ + delay_frame += 10; + } while (1); +} + +int tegra_dp_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *timing) +{ + struct tegra_dp_priv *priv = dev_get_priv(dev); + struct tegra_dp_link_config slink_cfg, *link_cfg = &slink_cfg; + struct udevice *sor; + int data; + int retry; + int ret; + + memset(link_cfg, '\0', sizeof(*link_cfg)); + link_cfg->is_valid = 0; + link_cfg->scramble_ena = 1; + + tegra_dc_dpaux_enable(priv); + + if (tegra_dp_hpd_plug(priv) < 0) { + debug("dp: hpd plug failed\n"); + return -EIO; + } + + link_cfg->bits_per_pixel = panel_bpp; + if (tegra_dc_dp_init_max_link_cfg(timing, priv, link_cfg)) { + debug("dp: failed to init link configuration\n"); + return -ENOLINK; + } + + ret = uclass_first_device_err(UCLASS_VIDEO_BRIDGE, &sor); + if (ret) { + debug("dp: failed to find SOR device: ret=%d\n", ret); + return ret; + } + priv->sor = sor; + ret = tegra_dc_sor_enable_dp(sor, link_cfg); + if (ret) + return ret; + + tegra_dc_sor_set_panel_power(sor, 1); + + /* Write power on to DPCD */ + data = DP_SET_POWER_D0; + retry = 0; + do { + ret = tegra_dc_dp_dpcd_write(priv, DP_SET_POWER, data); + } while ((retry++ < DP_POWER_ON_MAX_TRIES) && ret); + + if (ret || retry >= DP_POWER_ON_MAX_TRIES) { + debug("dp: failed to power on panel (0x%x)\n", ret); + return -ENETUNREACH; + goto error_enable; + } + + /* Confirm DP plugging status */ + if (!(tegra_dpaux_readl(priv, DPAUX_DP_AUXSTAT) & + DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED)) { + debug("dp: could not detect HPD\n"); + return -ENXIO; + } + + /* Check DP version */ + if (tegra_dc_dp_dpcd_read(priv, DP_DPCD_REV, &priv->revision)) { + debug("dp: failed to read the revision number from sink\n"); + return -EIO; + } + + if (tegra_dc_dp_explore_link_cfg(priv, link_cfg, sor, timing)) { + debug("dp: error configuring link\n"); + return -ENOMEDIUM; + } + + tegra_dc_sor_set_power_state(sor, 1); + ret = tegra_dc_sor_attach(priv->dc_dev, sor, link_cfg, timing); + if (ret && ret != -EEXIST) + return ret; + + /* + * This takes a long time, but can apparently resolve a failure to + * bring up the display correctly. + */ + if (0) { + ret = tegra_dc_dp_check_sink(priv, link_cfg, timing); + if (ret) + return ret; + } + + /* Power down the unused lanes to save power - a few hundred mW */ + tegra_dc_sor_power_down_unused_lanes(sor, link_cfg); + + ret = video_bridge_set_backlight(sor, 80); + if (ret) { + debug("dp: failed to set backlight\n"); + return ret; + } + + priv->enabled = true; +error_enable: + return 0; +} + +static int tegra_dp_of_to_plat(struct udevice *dev) +{ + struct tegra_dp_plat *plat = dev_get_plat(dev); + + plat->base = dev_read_addr(dev); + + return 0; +} + +static int tegra_dp_read_edid(struct udevice *dev, u8 *buf, int buf_size) +{ + struct tegra_dp_priv *priv = dev_get_priv(dev); + const int tegra_edid_i2c_address = 0x50; + u32 aux_stat = 0; + + tegra_dc_dpaux_enable(priv); + + return tegra_dc_i2c_aux_read(priv, tegra_edid_i2c_address, 0, buf, + buf_size, &aux_stat); +} + +static const struct dm_display_ops dp_tegra_ops = { + .read_edid = tegra_dp_read_edid, + .enable = tegra_dp_enable, +}; + +static int dp_tegra_probe(struct udevice *dev) +{ + struct tegra_dp_plat *plat = dev_get_plat(dev); + struct tegra_dp_priv *priv = dev_get_priv(dev); + struct display_plat *disp_uc_plat = dev_get_uclass_plat(dev); + + priv->regs = (struct dpaux_ctlr *)plat->base; + priv->enabled = false; + + /* Remember the display controller that is sending us video */ + priv->dc_dev = disp_uc_plat->src_dev; + + return 0; +} + +static const struct udevice_id tegra_dp_ids[] = { + { .compatible = "nvidia,tegra124-dpaux" }, + { .compatible = "nvidia,tegra210-dpaux" }, + { } +}; + +U_BOOT_DRIVER(dp_tegra) = { + .name = "dpaux_tegra", + .id = UCLASS_DISPLAY, + .of_match = tegra_dp_ids, + .of_to_plat = tegra_dp_of_to_plat, + .probe = dp_tegra_probe, + .ops = &dp_tegra_ops, + .priv_auto = sizeof(struct tegra_dp_priv), + .plat_auto = sizeof(struct tegra_dp_plat), +}; diff --git a/drivers/video/tegra/tegra124/sor.c b/drivers/video/tegra/tegra124/sor.c new file mode 100644 index 00000000000..1ce5330c6bc --- /dev/null +++ b/drivers/video/tegra/tegra124/sor.c @@ -0,0 +1,1083 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2013, NVIDIA Corporation. + */ + +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <panel.h> +#include <syscon.h> +#include <video_bridge.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch-tegra/dc.h> +#include <linux/delay.h> +#include <linux/printk.h> +#include "displayport.h" +#include "sor.h" +#include <linux/err.h> + +#define DEBUG_SOR 0 + +#define APBDEV_PMC_DPD_SAMPLE 0x20 +#define APBDEV_PMC_DPD_SAMPLE_ON_DISABLE 0 +#define APBDEV_PMC_DPD_SAMPLE_ON_ENABLE 1 +#define APBDEV_PMC_SEL_DPD_TIM 0x1c8 +#define APBDEV_PMC_SEL_DPD_TIM_SEL_DPD_TIM_DEFAULT 0x7f +#define APBDEV_PMC_IO_DPD2_REQ 0x1c0 +#define APBDEV_PMC_IO_DPD2_REQ_LVDS_SHIFT 25 +#define APBDEV_PMC_IO_DPD2_REQ_LVDS_OFF (0 << 25) +#define APBDEV_PMC_IO_DPD2_REQ_LVDS_ON (1 << 25) +#define APBDEV_PMC_IO_DPD2_REQ_CODE_SHIFT 30 +#define APBDEV_PMC_IO_DPD2_REQ_CODE_DEFAULT_MASK (0x3 << 30) +#define APBDEV_PMC_IO_DPD2_REQ_CODE_IDLE (0 << 30) +#define APBDEV_PMC_IO_DPD2_REQ_CODE_DPD_OFF (1 << 30) +#define APBDEV_PMC_IO_DPD2_REQ_CODE_DPD_ON (2 << 30) +#define APBDEV_PMC_IO_DPD2_STATUS 0x1c4 +#define APBDEV_PMC_IO_DPD2_STATUS_LVDS_SHIFT 25 +#define APBDEV_PMC_IO_DPD2_STATUS_LVDS_OFF (0 << 25) +#define APBDEV_PMC_IO_DPD2_STATUS_LVDS_ON (1 << 25) + +struct tegra_dc_sor_data { + void *base; + void *pmc_base; + u8 portnum; /* 0 or 1 */ + int power_is_up; + struct udevice *panel; +}; + +static inline u32 tegra_sor_readl(struct tegra_dc_sor_data *sor, u32 reg) +{ + return readl((u32 *)sor->base + reg); +} + +static inline void tegra_sor_writel(struct tegra_dc_sor_data *sor, u32 reg, + u32 val) +{ + writel(val, (u32 *)sor->base + reg); +} + +static inline void tegra_sor_write_field(struct tegra_dc_sor_data *sor, + u32 reg, u32 mask, u32 val) +{ + u32 reg_val = tegra_sor_readl(sor, reg); + reg_val &= ~mask; + reg_val |= val; + tegra_sor_writel(sor, reg, reg_val); +} + +void tegra_dp_disable_tx_pu(struct udevice *dev) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), + DP_PADCTL_TX_PU_MASK, DP_PADCTL_TX_PU_DISABLE); +} + +void tegra_dp_set_pe_vs_pc(struct udevice *dev, u32 mask, u32 pe_reg, + u32 vs_reg, u32 pc_reg, u8 pc_supported) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + + tegra_sor_write_field(sor, PR(sor->portnum), mask, pe_reg); + tegra_sor_write_field(sor, DC(sor->portnum), mask, vs_reg); + if (pc_supported) { + tegra_sor_write_field(sor, POSTCURSOR(sor->portnum), mask, + pc_reg); + } +} + +static int tegra_dc_sor_poll_register(struct tegra_dc_sor_data *sor, u32 reg, + u32 mask, u32 exp_val, + int poll_interval_us, int timeout_ms) +{ + u32 reg_val = 0; + ulong start; + + start = get_timer(0); + do { + reg_val = tegra_sor_readl(sor, reg); + if (((reg_val & mask) == exp_val)) + return 0; + udelay(poll_interval_us); + } while (get_timer(start) < timeout_ms); + + debug("sor_poll_register 0x%x: timeout, (reg_val)0x%08x & (mask)0x%08x != (exp_val)0x%08x\n", + reg, reg_val, mask, exp_val); + + return -ETIMEDOUT; +} + +int tegra_dc_sor_set_power_state(struct udevice *dev, int pu_pd) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + u32 orig_val; + + orig_val = tegra_sor_readl(sor, PWR); + + reg_val = pu_pd ? PWR_NORMAL_STATE_PU : + PWR_NORMAL_STATE_PD; /* normal state only */ + + if (reg_val == orig_val) + return 0; /* No update needed */ + + reg_val |= PWR_SETTING_NEW_TRIGGER; + tegra_sor_writel(sor, PWR, reg_val); + + /* Poll to confirm it is done */ + if (tegra_dc_sor_poll_register(sor, PWR, + PWR_SETTING_NEW_DEFAULT_MASK, + PWR_SETTING_NEW_DONE, + 100, TEGRA_SOR_TIMEOUT_MS)) { + debug("dc timeout waiting for SOR_PWR = NEW_DONE\n"); + return -EFAULT; + } + + return 0; +} + +void tegra_dc_sor_set_dp_linkctl(struct udevice *dev, int ena, + u8 training_pattern, + const struct tegra_dp_link_config *link_cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + reg_val = tegra_sor_readl(sor, DP_LINKCTL(sor->portnum)); + + if (ena) + reg_val |= DP_LINKCTL_ENABLE_YES; + else + reg_val &= DP_LINKCTL_ENABLE_NO; + + reg_val &= ~DP_LINKCTL_TUSIZE_MASK; + reg_val |= (link_cfg->tu_size << DP_LINKCTL_TUSIZE_SHIFT); + + if (link_cfg->enhanced_framing) + reg_val |= DP_LINKCTL_ENHANCEDFRAME_ENABLE; + + tegra_sor_writel(sor, DP_LINKCTL(sor->portnum), reg_val); + + switch (training_pattern) { + case training_pattern_1: + tegra_sor_writel(sor, DP_TPG, 0x41414141); + break; + case training_pattern_2: + case training_pattern_3: + reg_val = (link_cfg->link_bw == SOR_LINK_SPEED_G5_4) ? + 0x43434343 : 0x42424242; + tegra_sor_writel(sor, DP_TPG, reg_val); + break; + default: + tegra_sor_writel(sor, DP_TPG, 0x50505050); + break; + } +} + +static int tegra_dc_sor_enable_lane_sequencer(struct tegra_dc_sor_data *sor, + int pu, int is_lvds) +{ + u32 reg_val; + + /* SOR lane sequencer */ + if (pu) { + reg_val = LANE_SEQ_CTL_SETTING_NEW_TRIGGER | + LANE_SEQ_CTL_SEQUENCE_DOWN | + LANE_SEQ_CTL_NEW_POWER_STATE_PU; + } else { + reg_val = LANE_SEQ_CTL_SETTING_NEW_TRIGGER | + LANE_SEQ_CTL_SEQUENCE_UP | + LANE_SEQ_CTL_NEW_POWER_STATE_PD; + } + + if (is_lvds) + reg_val |= 15 << LANE_SEQ_CTL_DELAY_SHIFT; + else + reg_val |= 1 << LANE_SEQ_CTL_DELAY_SHIFT; + + tegra_sor_writel(sor, LANE_SEQ_CTL, reg_val); + + if (tegra_dc_sor_poll_register(sor, LANE_SEQ_CTL, + LANE_SEQ_CTL_SETTING_MASK, + LANE_SEQ_CTL_SETTING_NEW_DONE, + 100, TEGRA_SOR_TIMEOUT_MS)) { + debug("dp: timeout while waiting for SOR lane sequencer to power down lanes\n"); + return -1; + } + + return 0; +} + +static int tegra_dc_sor_power_dplanes(struct udevice *dev, + u32 lane_count, int pu) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + reg_val = tegra_sor_readl(sor, DP_PADCTL(sor->portnum)); + + if (pu) { + switch (lane_count) { + case 4: + reg_val |= (DP_PADCTL_PD_TXD_3_NO | + DP_PADCTL_PD_TXD_2_NO); + /* fall through */ + case 2: + reg_val |= DP_PADCTL_PD_TXD_1_NO; + case 1: + reg_val |= DP_PADCTL_PD_TXD_0_NO; + break; + default: + debug("dp: invalid lane number %d\n", lane_count); + return -1; + } + + tegra_sor_writel(sor, DP_PADCTL(sor->portnum), reg_val); + tegra_dc_sor_set_lane_count(dev, lane_count); + } + + return tegra_dc_sor_enable_lane_sequencer(sor, pu, 0); +} + +void tegra_dc_sor_set_panel_power(struct udevice *dev, int power_up) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + reg_val = tegra_sor_readl(sor, DP_PADCTL(sor->portnum)); + + if (power_up) + reg_val |= DP_PADCTL_PAD_CAL_PD_POWERUP; + else + reg_val &= ~DP_PADCTL_PAD_CAL_PD_POWERUP; + + tegra_sor_writel(sor, DP_PADCTL(sor->portnum), reg_val); +} + +static void tegra_dc_sor_config_pwm(struct tegra_dc_sor_data *sor, u32 pwm_div, + u32 pwm_dutycycle) +{ + tegra_sor_writel(sor, PWM_DIV, pwm_div); + tegra_sor_writel(sor, PWM_CTL, + (pwm_dutycycle & PWM_CTL_DUTY_CYCLE_MASK) | + PWM_CTL_SETTING_NEW_TRIGGER); + + if (tegra_dc_sor_poll_register(sor, PWM_CTL, + PWM_CTL_SETTING_NEW_SHIFT, + PWM_CTL_SETTING_NEW_DONE, + 100, TEGRA_SOR_TIMEOUT_MS)) { + debug("dp: timeout while waiting for SOR PWM setting\n"); + } +} + +static void tegra_dc_sor_set_dp_mode(struct udevice *dev, + const struct tegra_dp_link_config *link_cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + tegra_dc_sor_set_link_bandwidth(dev, link_cfg->link_bw); + + tegra_dc_sor_set_dp_linkctl(dev, 1, training_pattern_none, link_cfg); + reg_val = tegra_sor_readl(sor, DP_CONFIG(sor->portnum)); + reg_val &= ~DP_CONFIG_WATERMARK_MASK; + reg_val |= link_cfg->watermark; + reg_val &= ~DP_CONFIG_ACTIVESYM_COUNT_MASK; + reg_val |= (link_cfg->active_count << + DP_CONFIG_ACTIVESYM_COUNT_SHIFT); + reg_val &= ~DP_CONFIG_ACTIVESYM_FRAC_MASK; + reg_val |= (link_cfg->active_frac << + DP_CONFIG_ACTIVESYM_FRAC_SHIFT); + if (link_cfg->activepolarity) + reg_val |= DP_CONFIG_ACTIVESYM_POLARITY_POSITIVE; + else + reg_val &= ~DP_CONFIG_ACTIVESYM_POLARITY_POSITIVE; + reg_val |= (DP_CONFIG_ACTIVESYM_CNTL_ENABLE | + DP_CONFIG_RD_RESET_VAL_NEGATIVE); + + tegra_sor_writel(sor, DP_CONFIG(sor->portnum), reg_val); + + /* program h/vblank sym */ + tegra_sor_write_field(sor, DP_AUDIO_HBLANK_SYMBOLS, + DP_AUDIO_HBLANK_SYMBOLS_MASK, + link_cfg->hblank_sym); + + tegra_sor_write_field(sor, DP_AUDIO_VBLANK_SYMBOLS, + DP_AUDIO_VBLANK_SYMBOLS_MASK, + link_cfg->vblank_sym); +} + +static inline void tegra_dc_sor_super_update(struct tegra_dc_sor_data *sor) +{ + tegra_sor_writel(sor, SUPER_STATE0, 0); + tegra_sor_writel(sor, SUPER_STATE0, 1); + tegra_sor_writel(sor, SUPER_STATE0, 0); +} + +static inline void tegra_dc_sor_update(struct tegra_dc_sor_data *sor) +{ + tegra_sor_writel(sor, STATE0, 0); + tegra_sor_writel(sor, STATE0, 1); + tegra_sor_writel(sor, STATE0, 0); +} + +static int tegra_dc_sor_io_set_dpd(struct tegra_dc_sor_data *sor, int up) +{ + u32 reg_val; + void *pmc_base = sor->pmc_base; + + if (up) { + writel(APBDEV_PMC_DPD_SAMPLE_ON_ENABLE, + pmc_base + APBDEV_PMC_DPD_SAMPLE); + writel(10, pmc_base + APBDEV_PMC_SEL_DPD_TIM); + } + + reg_val = readl(pmc_base + APBDEV_PMC_IO_DPD2_REQ); + reg_val &= ~(APBDEV_PMC_IO_DPD2_REQ_LVDS_ON || + APBDEV_PMC_IO_DPD2_REQ_CODE_DEFAULT_MASK); + + reg_val = up ? APBDEV_PMC_IO_DPD2_REQ_LVDS_ON | + APBDEV_PMC_IO_DPD2_REQ_CODE_DPD_OFF : + APBDEV_PMC_IO_DPD2_REQ_LVDS_OFF | + APBDEV_PMC_IO_DPD2_REQ_CODE_DPD_ON; + + writel(reg_val, pmc_base + APBDEV_PMC_IO_DPD2_REQ); + + /* Polling */ + u32 temp = 10 * 1000; + do { + udelay(20); + reg_val = readl(pmc_base + APBDEV_PMC_IO_DPD2_STATUS); + if (temp > 20) + temp -= 20; + else + break; + } while ((reg_val & APBDEV_PMC_IO_DPD2_STATUS_LVDS_ON) != 0); + + if ((reg_val & APBDEV_PMC_IO_DPD2_STATUS_LVDS_ON) != 0) { + debug("PMC_IO_DPD2 polling failed (0x%x)\n", reg_val); + return -EIO; + } + + if (up) { + writel(APBDEV_PMC_DPD_SAMPLE_ON_DISABLE, + pmc_base + APBDEV_PMC_DPD_SAMPLE); + } + + return 0; +} + +void tegra_dc_sor_set_internal_panel(struct udevice *dev, int is_int) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + reg_val = tegra_sor_readl(sor, DP_SPARE(sor->portnum)); + if (is_int) + reg_val |= DP_SPARE_PANEL_INTERNAL; + else + reg_val &= ~DP_SPARE_PANEL_INTERNAL; + + reg_val |= DP_SPARE_SOR_CLK_SEL_MACRO_SORCLK | + DP_SPARE_SEQ_ENABLE_YES; + tegra_sor_writel(sor, DP_SPARE(sor->portnum), reg_val); +} + +void tegra_dc_sor_read_link_config(struct udevice *dev, u8 *link_bw, + u8 *lane_count) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + reg_val = tegra_sor_readl(sor, CLK_CNTRL); + *link_bw = (reg_val & CLK_CNTRL_DP_LINK_SPEED_MASK) + >> CLK_CNTRL_DP_LINK_SPEED_SHIFT; + reg_val = tegra_sor_readl(sor, + DP_LINKCTL(sor->portnum)); + + switch (reg_val & DP_LINKCTL_LANECOUNT_MASK) { + case DP_LINKCTL_LANECOUNT_ZERO: + *lane_count = 0; + break; + case DP_LINKCTL_LANECOUNT_ONE: + *lane_count = 1; + break; + case DP_LINKCTL_LANECOUNT_TWO: + *lane_count = 2; + break; + case DP_LINKCTL_LANECOUNT_FOUR: + *lane_count = 4; + break; + default: + printf("Unknown lane count\n"); + } +} + +void tegra_dc_sor_set_link_bandwidth(struct udevice *dev, u8 link_bw) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + + tegra_sor_write_field(sor, CLK_CNTRL, + CLK_CNTRL_DP_LINK_SPEED_MASK, + link_bw << CLK_CNTRL_DP_LINK_SPEED_SHIFT); +} + +void tegra_dc_sor_set_lane_count(struct udevice *dev, u8 lane_count) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg_val; + + reg_val = tegra_sor_readl(sor, DP_LINKCTL(sor->portnum)); + reg_val &= ~DP_LINKCTL_LANECOUNT_MASK; + switch (lane_count) { + case 0: + break; + case 1: + reg_val |= DP_LINKCTL_LANECOUNT_ONE; + break; + case 2: + reg_val |= DP_LINKCTL_LANECOUNT_TWO; + break; + case 4: + reg_val |= DP_LINKCTL_LANECOUNT_FOUR; + break; + default: + /* 0 should be handled earlier. */ + printf("dp: Invalid lane count %d\n", lane_count); + return; + } + tegra_sor_writel(sor, DP_LINKCTL(sor->portnum), reg_val); +} + +/* + * The SOR power sequencer does not work for t124 so SW has to + * go through the power sequence manually + * Power up steps from spec: + * STEP PDPORT PDPLL PDBG PLLVCOD PLLCAPD E_DPD PDCAL + * 1 1 1 1 1 1 1 1 + * 2 1 1 1 1 1 0 1 + * 3 1 1 0 1 1 0 1 + * 4 1 0 0 0 0 0 1 + * 5 0 0 0 0 0 0 1 + */ +static int tegra_dc_sor_power_up(struct udevice *dev, int is_lvds) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 reg; + int ret; + + if (sor->power_is_up) + return 0; + + /* + * If for some reason it is already powered up, don't do it again. + * This can happen if U-Boot is the secondary boot loader. + */ + reg = tegra_sor_readl(sor, DP_PADCTL(sor->portnum)); + if (reg & DP_PADCTL_PD_TXD_0_NO) + return 0; + + /* Set link bw */ + tegra_dc_sor_set_link_bandwidth(dev, is_lvds ? + CLK_CNTRL_DP_LINK_SPEED_LVDS : + CLK_CNTRL_DP_LINK_SPEED_G1_62); + + /* step 1 */ + tegra_sor_write_field(sor, PLL2, + PLL2_AUX7_PORT_POWERDOWN_MASK | /* PDPORT */ + PLL2_AUX6_BANDGAP_POWERDOWN_MASK | /* PDBG */ + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, /* PLLCAPD */ + PLL2_AUX7_PORT_POWERDOWN_ENABLE | + PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE | + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_ENABLE); + tegra_sor_write_field(sor, PLL0, PLL0_PWR_MASK | /* PDPLL */ + PLL0_VCOPD_MASK, /* PLLVCOPD */ + PLL0_PWR_OFF | PLL0_VCOPD_ASSERT); + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), + DP_PADCTL_PAD_CAL_PD_POWERDOWN, /* PDCAL */ + DP_PADCTL_PAD_CAL_PD_POWERDOWN); + + /* step 2 */ + ret = tegra_dc_sor_io_set_dpd(sor, 1); + if (ret) + return ret; + udelay(15); + + /* step 3 */ + tegra_sor_write_field(sor, PLL2, + PLL2_AUX6_BANDGAP_POWERDOWN_MASK, + PLL2_AUX6_BANDGAP_POWERDOWN_DISABLE); + udelay(25); + + /* step 4 */ + tegra_sor_write_field(sor, PLL0, + PLL0_PWR_MASK | /* PDPLL */ + PLL0_VCOPD_MASK, /* PLLVCOPD */ + PLL0_PWR_ON | PLL0_VCOPD_RESCIND); + /* PLLCAPD */ + tegra_sor_write_field(sor, PLL2, + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_DISABLE); + udelay(225); + + /* step 5 PDPORT */ + tegra_sor_write_field(sor, PLL2, + PLL2_AUX7_PORT_POWERDOWN_MASK, + PLL2_AUX7_PORT_POWERDOWN_DISABLE); + + sor->power_is_up = 1; + + return 0; +} + +#if DEBUG_SOR +static void dump_sor_reg(struct tegra_dc_sor_data *sor) +{ +#define DUMP_REG(a) printk(BIOS_INFO, \ + "%-32s %03x %08x\n", \ + #a, a, tegra_sor_readl(sor, a)); + + DUMP_REG(SUPER_STATE0); + DUMP_REG(SUPER_STATE1); + DUMP_REG(STATE0); + DUMP_REG(STATE1); + DUMP_REG(NV_HEAD_STATE0(0)); + DUMP_REG(NV_HEAD_STATE0(1)); + DUMP_REG(NV_HEAD_STATE1(0)); + DUMP_REG(NV_HEAD_STATE1(1)); + DUMP_REG(NV_HEAD_STATE2(0)); + DUMP_REG(NV_HEAD_STATE2(1)); + DUMP_REG(NV_HEAD_STATE3(0)); + DUMP_REG(NV_HEAD_STATE3(1)); + DUMP_REG(NV_HEAD_STATE4(0)); + DUMP_REG(NV_HEAD_STATE4(1)); + DUMP_REG(NV_HEAD_STATE5(0)); + DUMP_REG(NV_HEAD_STATE5(1)); + DUMP_REG(CRC_CNTRL); + DUMP_REG(CLK_CNTRL); + DUMP_REG(CAP); + DUMP_REG(PWR); + DUMP_REG(TEST); + DUMP_REG(PLL0); + DUMP_REG(PLL1); + DUMP_REG(PLL2); + DUMP_REG(PLL3); + DUMP_REG(CSTM); + DUMP_REG(LVDS); + DUMP_REG(CRCA); + DUMP_REG(CRCB); + DUMP_REG(SEQ_CTL); + DUMP_REG(LANE_SEQ_CTL); + DUMP_REG(SEQ_INST(0)); + DUMP_REG(SEQ_INST(1)); + DUMP_REG(SEQ_INST(2)); + DUMP_REG(SEQ_INST(3)); + DUMP_REG(SEQ_INST(4)); + DUMP_REG(SEQ_INST(5)); + DUMP_REG(SEQ_INST(6)); + DUMP_REG(SEQ_INST(7)); + DUMP_REG(SEQ_INST(8)); + DUMP_REG(PWM_DIV); + DUMP_REG(PWM_CTL); + DUMP_REG(MSCHECK); + DUMP_REG(XBAR_CTRL); + DUMP_REG(DP_LINKCTL(0)); + DUMP_REG(DP_LINKCTL(1)); + DUMP_REG(DC(0)); + DUMP_REG(DC(1)); + DUMP_REG(LANE_DRIVE_CURRENT(0)); + DUMP_REG(PR(0)); + DUMP_REG(LANE4_PREEMPHASIS(0)); + DUMP_REG(POSTCURSOR(0)); + DUMP_REG(DP_CONFIG(0)); + DUMP_REG(DP_CONFIG(1)); + DUMP_REG(DP_MN(0)); + DUMP_REG(DP_MN(1)); + DUMP_REG(DP_PADCTL(0)); + DUMP_REG(DP_PADCTL(1)); + DUMP_REG(DP_DEBUG(0)); + DUMP_REG(DP_DEBUG(1)); + DUMP_REG(DP_SPARE(0)); + DUMP_REG(DP_SPARE(1)); + DUMP_REG(DP_TPG); + + return; +} +#endif + +static void tegra_dc_sor_config_panel(struct tegra_dc_sor_data *sor, + int is_lvds, + const struct tegra_dp_link_config *link_cfg, + const struct display_timing *timing) +{ + const int head_num = 0; + u32 reg_val = STATE1_ASY_OWNER_HEAD0 << head_num; + u32 vtotal, htotal; + u32 vsync_end, hsync_end; + u32 vblank_end, hblank_end; + u32 vblank_start, hblank_start; + + reg_val |= is_lvds ? STATE1_ASY_PROTOCOL_LVDS_CUSTOM : + STATE1_ASY_PROTOCOL_DP_A; + reg_val |= STATE1_ASY_SUBOWNER_NONE | + STATE1_ASY_CRCMODE_COMPLETE_RASTER; + + reg_val |= STATE1_ASY_HSYNCPOL_NEGATIVE_TRUE; + reg_val |= STATE1_ASY_VSYNCPOL_NEGATIVE_TRUE; + reg_val |= (link_cfg->bits_per_pixel > 18) ? + STATE1_ASY_PIXELDEPTH_BPP_24_444 : + STATE1_ASY_PIXELDEPTH_BPP_18_444; + + tegra_sor_writel(sor, STATE1, reg_val); + + /* + * Skipping programming NV_HEAD_STATE0, assuming: + * interlacing: PROGRESSIVE, dynamic range: VESA, colorspace: RGB + */ + vtotal = timing->vsync_len.typ + timing->vback_porch.typ + + timing->vactive.typ + timing->vfront_porch.typ; + htotal = timing->hsync_len.typ + timing->hback_porch.typ + + timing->hactive.typ + timing->hfront_porch.typ; + + tegra_sor_writel(sor, NV_HEAD_STATE1(head_num), + vtotal << NV_HEAD_STATE1_VTOTAL_SHIFT | + htotal << NV_HEAD_STATE1_HTOTAL_SHIFT); + + vsync_end = timing->vsync_len.typ - 1; + hsync_end = timing->hsync_len.typ - 1; + tegra_sor_writel(sor, NV_HEAD_STATE2(head_num), + vsync_end << NV_HEAD_STATE2_VSYNC_END_SHIFT | + hsync_end << NV_HEAD_STATE2_HSYNC_END_SHIFT); + + vblank_end = vsync_end + timing->vback_porch.typ; + hblank_end = hsync_end + timing->hback_porch.typ; + tegra_sor_writel(sor, NV_HEAD_STATE3(head_num), + vblank_end << NV_HEAD_STATE3_VBLANK_END_SHIFT | + hblank_end << NV_HEAD_STATE3_HBLANK_END_SHIFT); + + vblank_start = vblank_end + timing->vactive.typ; + hblank_start = hblank_end + timing->hactive.typ; + tegra_sor_writel(sor, NV_HEAD_STATE4(head_num), + vblank_start << NV_HEAD_STATE4_VBLANK_START_SHIFT | + hblank_start << NV_HEAD_STATE4_HBLANK_START_SHIFT); + + /* TODO: adding interlace mode support */ + tegra_sor_writel(sor, NV_HEAD_STATE5(head_num), 0x1); + + tegra_sor_write_field(sor, CSTM, + CSTM_ROTCLK_DEFAULT_MASK | + CSTM_LVDS_EN_ENABLE, + 2 << CSTM_ROTCLK_SHIFT | + (is_lvds ? CSTM_LVDS_EN_ENABLE : + CSTM_LVDS_EN_DISABLE)); + + tegra_dc_sor_config_pwm(sor, 1024, 1024); +} + +static void tegra_dc_sor_enable_dc(struct dc_ctlr *disp_ctrl) +{ + u32 reg_val = readl(&disp_ctrl->cmd.state_access); + + writel(reg_val | WRITE_MUX_ACTIVE, &disp_ctrl->cmd.state_access); + writel(VSYNC_H_POSITION(1), &disp_ctrl->disp.disp_timing_opt); + + /* Enable DC now - otherwise pure text console may not show. */ + writel(CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT, + &disp_ctrl->cmd.disp_cmd); + writel(reg_val, &disp_ctrl->cmd.state_access); +} + +int tegra_dc_sor_enable_dp(struct udevice *dev, + const struct tegra_dp_link_config *link_cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + int ret; + + tegra_sor_write_field(sor, CLK_CNTRL, + CLK_CNTRL_DP_CLK_SEL_MASK, + CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK); + + tegra_sor_write_field(sor, PLL2, + PLL2_AUX6_BANDGAP_POWERDOWN_MASK, + PLL2_AUX6_BANDGAP_POWERDOWN_DISABLE); + udelay(25); + + tegra_sor_write_field(sor, PLL3, + PLL3_PLLVDD_MODE_MASK, + PLL3_PLLVDD_MODE_V3_3); + tegra_sor_writel(sor, PLL0, + 0xf << PLL0_ICHPMP_SHFIT | + 0x3 << PLL0_VCOCAP_SHIFT | + PLL0_PLLREG_LEVEL_V45 | + PLL0_RESISTORSEL_EXT | + PLL0_PWR_ON | PLL0_VCOPD_RESCIND); + tegra_sor_write_field(sor, PLL2, + PLL2_AUX1_SEQ_MASK | + PLL2_AUX9_LVDSEN_OVERRIDE | + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, + PLL2_AUX1_SEQ_PLLCAPPD_OVERRIDE | + PLL2_AUX9_LVDSEN_OVERRIDE | + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_DISABLE); + tegra_sor_writel(sor, PLL1, PLL1_TERM_COMPOUT_HIGH | + PLL1_TMDS_TERM_ENABLE); + + if (tegra_dc_sor_poll_register(sor, PLL2, + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, + PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_DISABLE, + 100, TEGRA_SOR_TIMEOUT_MS)) { + printf("DP failed to lock PLL\n"); + return -EIO; + } + + tegra_sor_write_field(sor, PLL2, PLL2_AUX2_MASK | + PLL2_AUX7_PORT_POWERDOWN_MASK, + PLL2_AUX2_OVERRIDE_POWERDOWN | + PLL2_AUX7_PORT_POWERDOWN_DISABLE); + + ret = tegra_dc_sor_power_up(dev, 0); + if (ret) { + debug("DP failed to power up\n"); + return ret; + } + + /* re-enable SOR clock */ + clock_sor_enable_edp_clock(); + + /* Power up lanes */ + tegra_dc_sor_power_dplanes(dev, link_cfg->lane_count, 1); + + tegra_dc_sor_set_dp_mode(dev, link_cfg); + debug("%s ret\n", __func__); + + return 0; +} + +int tegra_dc_sor_attach(struct udevice *dc_dev, struct udevice *dev, + const struct tegra_dp_link_config *link_cfg, + const struct display_timing *timing) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + struct dc_ctlr *disp_ctrl; + u32 reg_val; + + /* Use the first display controller */ + debug("%s\n", __func__); + disp_ctrl = dev_read_addr_ptr(dc_dev); + + tegra_dc_sor_enable_dc(disp_ctrl); + tegra_dc_sor_config_panel(sor, 0, link_cfg, timing); + + writel(0x9f00, &disp_ctrl->cmd.state_ctrl); + writel(0x9f, &disp_ctrl->cmd.state_ctrl); + + writel(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE, + &disp_ctrl->cmd.disp_pow_ctrl); + + reg_val = tegra_sor_readl(sor, TEST); + if (reg_val & TEST_ATTACHED_TRUE) + return -EEXIST; + + tegra_sor_writel(sor, SUPER_STATE1, + SUPER_STATE1_ATTACHED_NO); + + /* + * Enable display2sor clock at least 2 cycles before DC start, + * to clear sor internal valid signal. + */ + writel(SOR_ENABLE, &disp_ctrl->disp.disp_win_opt); + writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl); + writel(0, &disp_ctrl->disp.disp_win_opt); + writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl); + + /* Attach head */ + tegra_dc_sor_update(sor); + tegra_sor_writel(sor, SUPER_STATE1, + SUPER_STATE1_ATTACHED_YES); + tegra_sor_writel(sor, SUPER_STATE1, + SUPER_STATE1_ATTACHED_YES | + SUPER_STATE1_ASY_HEAD_OP_AWAKE | + SUPER_STATE1_ASY_ORMODE_NORMAL); + tegra_dc_sor_super_update(sor); + + /* Enable dc */ + reg_val = readl(&disp_ctrl->cmd.state_access); + writel(reg_val | WRITE_MUX_ACTIVE, &disp_ctrl->cmd.state_access); + writel(CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT, + &disp_ctrl->cmd.disp_cmd); + writel(SOR_ENABLE, &disp_ctrl->disp.disp_win_opt); + writel(reg_val, &disp_ctrl->cmd.state_access); + + if (tegra_dc_sor_poll_register(sor, TEST, + TEST_ACT_HEAD_OPMODE_DEFAULT_MASK, + TEST_ACT_HEAD_OPMODE_AWAKE, + 100, + TEGRA_SOR_ATTACH_TIMEOUT_MS)) { + printf("dc timeout waiting for OPMOD = AWAKE\n"); + return -ETIMEDOUT; + } else { + debug("%s: sor is attached\n", __func__); + } + +#if DEBUG_SOR + dump_sor_reg(sor); +#endif + debug("%s: ret=%d\n", __func__, 0); + + return 0; +} + +void tegra_dc_sor_set_lane_parm(struct udevice *dev, + const struct tegra_dp_link_config *link_cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + + tegra_sor_writel(sor, LANE_DRIVE_CURRENT(sor->portnum), + link_cfg->drive_current); + tegra_sor_writel(sor, PR(sor->portnum), + link_cfg->preemphasis); + tegra_sor_writel(sor, POSTCURSOR(sor->portnum), + link_cfg->postcursor); + tegra_sor_writel(sor, LVDS, 0); + + tegra_dc_sor_set_link_bandwidth(dev, link_cfg->link_bw); + tegra_dc_sor_set_lane_count(dev, link_cfg->lane_count); + + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), + DP_PADCTL_TX_PU_ENABLE | + DP_PADCTL_TX_PU_VALUE_DEFAULT_MASK, + DP_PADCTL_TX_PU_ENABLE | + 2 << DP_PADCTL_TX_PU_VALUE_SHIFT); + + /* Precharge */ + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), 0xf0, 0xf0); + udelay(20); + + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), 0xf0, 0x0); +} + +int tegra_dc_sor_set_voltage_swing(struct udevice *dev, + const struct tegra_dp_link_config *link_cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 drive_current = 0; + u32 pre_emphasis = 0; + + /* Set to a known-good pre-calibrated setting */ + switch (link_cfg->link_bw) { + case SOR_LINK_SPEED_G1_62: + case SOR_LINK_SPEED_G2_7: + drive_current = 0x13131313; + pre_emphasis = 0; + break; + case SOR_LINK_SPEED_G5_4: + debug("T124 does not support 5.4G link clock.\n"); + default: + debug("Invalid sor link bandwidth: %d\n", link_cfg->link_bw); + return -ENOLINK; + } + + tegra_sor_writel(sor, LANE_DRIVE_CURRENT(sor->portnum), drive_current); + tegra_sor_writel(sor, PR(sor->portnum), pre_emphasis); + + return 0; +} + +void tegra_dc_sor_power_down_unused_lanes(struct udevice *dev, + const struct tegra_dp_link_config *link_cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 pad_ctrl = 0; + int err = 0; + + switch (link_cfg->lane_count) { + case 4: + pad_ctrl = DP_PADCTL_PD_TXD_0_NO | + DP_PADCTL_PD_TXD_1_NO | + DP_PADCTL_PD_TXD_2_NO | + DP_PADCTL_PD_TXD_3_NO; + break; + case 2: + pad_ctrl = DP_PADCTL_PD_TXD_0_NO | + DP_PADCTL_PD_TXD_1_NO | + DP_PADCTL_PD_TXD_2_YES | + DP_PADCTL_PD_TXD_3_YES; + break; + case 1: + pad_ctrl = DP_PADCTL_PD_TXD_0_NO | + DP_PADCTL_PD_TXD_1_YES | + DP_PADCTL_PD_TXD_2_YES | + DP_PADCTL_PD_TXD_3_YES; + break; + default: + printf("Invalid sor lane count: %u\n", link_cfg->lane_count); + return; + } + + pad_ctrl |= DP_PADCTL_PAD_CAL_PD_POWERDOWN; + tegra_sor_writel(sor, DP_PADCTL(sor->portnum), pad_ctrl); + + err = tegra_dc_sor_enable_lane_sequencer(sor, 0, 0); + if (err) { + debug("Wait for lane power down failed: %d\n", err); + return; + } +} + +int tegra_sor_precharge_lanes(struct udevice *dev, + const struct tegra_dp_link_config *cfg) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + u32 val = 0; + + switch (cfg->lane_count) { + case 4: + val |= (DP_PADCTL_PD_TXD_3_NO | + DP_PADCTL_PD_TXD_2_NO); + /* fall through */ + case 2: + val |= DP_PADCTL_PD_TXD_1_NO; + /* fall through */ + case 1: + val |= DP_PADCTL_PD_TXD_0_NO; + break; + default: + debug("dp: invalid lane number %d\n", cfg->lane_count); + return -EINVAL; + } + + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), + (0xf << DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT), + (val << DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT)); + udelay(100); + tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), + (0xf << DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT), + 0); + + return 0; +} + +static void tegra_dc_sor_enable_sor(struct dc_ctlr *disp_ctrl, bool enable) +{ + u32 reg_val = readl(&disp_ctrl->disp.disp_win_opt); + + reg_val = enable ? reg_val | SOR_ENABLE : reg_val & ~SOR_ENABLE; + writel(reg_val, &disp_ctrl->disp.disp_win_opt); +} + +int tegra_dc_sor_detach(struct udevice *dc_dev, struct udevice *dev) +{ + struct tegra_dc_sor_data *sor = dev_get_priv(dev); + int dc_reg_ctx[DC_REG_SAVE_SPACE]; + struct dc_ctlr *disp_ctrl; + unsigned long dc_int_mask; + int ret; + + debug("%s\n", __func__); + /* Use the first display controller */ + disp_ctrl = dev_read_addr_ptr(dev); + + /* Sleep mode */ + tegra_sor_writel(sor, SUPER_STATE1, SUPER_STATE1_ASY_HEAD_OP_SLEEP | + SUPER_STATE1_ASY_ORMODE_SAFE | + SUPER_STATE1_ATTACHED_YES); + tegra_dc_sor_super_update(sor); + + tegra_dc_sor_disable_win_short_raster(disp_ctrl, dc_reg_ctx); + + if (tegra_dc_sor_poll_register(sor, TEST, + TEST_ACT_HEAD_OPMODE_DEFAULT_MASK, + TEST_ACT_HEAD_OPMODE_SLEEP, 100, + TEGRA_SOR_ATTACH_TIMEOUT_MS)) { + debug("dc timeout waiting for OPMOD = SLEEP\n"); + ret = -ETIMEDOUT; + goto err; + } + + tegra_sor_writel(sor, SUPER_STATE1, SUPER_STATE1_ASY_HEAD_OP_SLEEP | + SUPER_STATE1_ASY_ORMODE_SAFE | + SUPER_STATE1_ATTACHED_NO); + + /* Mask DC interrupts during the 2 dummy frames required for detach */ + dc_int_mask = readl(&disp_ctrl->cmd.int_mask); + writel(0, &disp_ctrl->cmd.int_mask); + + /* Stop DC->SOR path */ + tegra_dc_sor_enable_sor(disp_ctrl, false); + ret = tegra_dc_sor_general_act(disp_ctrl); + if (ret) + goto err; + + /* Stop DC */ + writel(CTRL_MODE_STOP << CTRL_MODE_SHIFT, &disp_ctrl->cmd.disp_cmd); + ret = tegra_dc_sor_general_act(disp_ctrl); + if (ret) + goto err; + + tegra_dc_sor_restore_win_and_raster(disp_ctrl, dc_reg_ctx); + + writel(dc_int_mask, &disp_ctrl->cmd.int_mask); + + return 0; +err: + debug("%s: ret=%d\n", __func__, ret); + + return ret; +} + +static int tegra_sor_set_backlight(struct udevice *dev, int percent) +{ + struct tegra_dc_sor_data *priv = dev_get_priv(dev); + int ret; + + ret = panel_enable_backlight(priv->panel); + if (ret) { + debug("sor: Cannot enable panel backlight\n"); + return ret; + } + + return 0; +} + +static int tegra_sor_of_to_plat(struct udevice *dev) +{ + struct tegra_dc_sor_data *priv = dev_get_priv(dev); + int ret; + + priv->base = dev_read_addr_ptr(dev); + + priv->pmc_base = (void *)syscon_get_first_range(TEGRA_SYSCON_PMC); + if (IS_ERR(priv->pmc_base)) + return PTR_ERR(priv->pmc_base); + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "nvidia,panel", + &priv->panel); + if (ret) { + debug("%s: Cannot find panel for '%s' (ret=%d)\n", __func__, + dev->name, ret); + return ret; + } + + return 0; +} + +static const struct video_bridge_ops tegra_sor_ops = { + .set_backlight = tegra_sor_set_backlight, +}; + +static const struct udevice_id tegra_sor_ids[] = { + { .compatible = "nvidia,tegra124-sor" }, + { } +}; + +U_BOOT_DRIVER(sor_tegra) = { + .name = "sor_tegra", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = tegra_sor_ids, + .of_to_plat = tegra_sor_of_to_plat, + .ops = &tegra_sor_ops, + .priv_auto = sizeof(struct tegra_dc_sor_data), +}; diff --git a/drivers/video/tegra/tegra124/sor.h b/drivers/video/tegra/tegra124/sor.h new file mode 100644 index 00000000000..2fc9a38267d --- /dev/null +++ b/drivers/video/tegra/tegra124/sor.h @@ -0,0 +1,914 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2011-2013, NVIDIA Corporation. + */ + +#ifndef _VIDEO_TEGRA124_SOR_H +#define _VIDEO_TEGRA124_SOR_H + +#define SUPER_STATE0 0x1 +#define SUPER_STATE0_UPDATE_SHIFT 0 +#define SUPER_STATE0_UPDATE_DEFAULT_MASK 0x1 +#define SUPER_STATE1 0x2 +#define SUPER_STATE1_ATTACHED_SHIFT 3 +#define SUPER_STATE1_ATTACHED_NO (0 << 3) +#define SUPER_STATE1_ATTACHED_YES (1 << 3) +#define SUPER_STATE1_ASY_ORMODE_SHIFT 2 +#define SUPER_STATE1_ASY_ORMODE_SAFE (0 << 2) +#define SUPER_STATE1_ASY_ORMODE_NORMAL (1 << 2) +#define SUPER_STATE1_ASY_HEAD_OP_SHIFT 0 +#define SUPER_STATE1_ASY_HEAD_OP_DEFAULT_MASK 0x3 +#define SUPER_STATE1_ASY_HEAD_OP_SLEEP 0 +#define SUPER_STATE1_ASY_HEAD_OP_SNOOZE 1 +#define SUPER_STATE1_ASY_HEAD_OP_AWAKE 2 +#define STATE0 0x3 +#define STATE0_UPDATE_SHIFT 0 +#define STATE0_UPDATE_DEFAULT_MASK 0x1 +#define STATE1 0x4 +#define STATE1_ASY_PIXELDEPTH_SHIFT 17 +#define STATE1_ASY_PIXELDEPTH_DEFAULT_MASK (0xf << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_16_422 (1 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_18_444 (2 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_20_422 (3 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_24_422 (4 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_24_444 (5 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_30_444 (6 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_32_422 (7 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_36_444 (8 << 17) +#define STATE1_ASY_PIXELDEPTH_BPP_48_444 (9 << 17) +#define STATE1_ASY_REPLICATE_SHIFT 15 +#define STATE1_ASY_REPLICATE_DEFAULT_MASK (3 << 15) +#define STATE1_ASY_REPLICATE_OFF (0 << 15) +#define STATE1_ASY_REPLICATE_X2 (1 << 15) +#define STATE1_ASY_REPLICATE_X4 (2 << 15) +#define STATE1_ASY_DEPOL_SHIFT 14 +#define STATE1_ASY_DEPOL_DEFAULT_MASK (1 << 14) +#define STATE1_ASY_DEPOL_POSITIVE_TRUE (0 << 14) +#define STATE1_ASY_DEPOL_NEGATIVE_TRUE (1 << 14) +#define STATE1_ASY_VSYNCPOL_SHIFT 13 +#define STATE1_ASY_VSYNCPOL_DEFAULT_MASK (1 << 13) +#define STATE1_ASY_VSYNCPOL_POSITIVE_TRUE (0 << 13) +#define STATE1_ASY_VSYNCPOL_NEGATIVE_TRUE (1 << 13) +#define STATE1_ASY_HSYNCPOL_SHIFT 12 +#define STATE1_ASY_HSYNCPOL_DEFAULT_MASK (1 << 12) +#define STATE1_ASY_HSYNCPOL_POSITIVE_TRUE (0 << 12) +#define STATE1_ASY_HSYNCPOL_NEGATIVE_TRUE (1 << 12) +#define STATE1_ASY_PROTOCOL_SHIFT 8 +#define STATE1_ASY_PROTOCOL_DEFAULT_MASK (0xf << 8) +#define STATE1_ASY_PROTOCOL_LVDS_CUSTOM (0 << 8) +#define STATE1_ASY_PROTOCOL_DP_A (8 << 8) +#define STATE1_ASY_PROTOCOL_DP_B (9 << 8) +#define STATE1_ASY_PROTOCOL_CUSTOM (15 << 8) +#define STATE1_ASY_CRCMODE_SHIFT 6 +#define STATE1_ASY_CRCMODE_DEFAULT_MASK (3 << 6) +#define STATE1_ASY_CRCMODE_ACTIVE_RASTER (0 << 6) +#define STATE1_ASY_CRCMODE_COMPLETE_RASTER (1 << 6) +#define STATE1_ASY_CRCMODE_NON_ACTIVE_RASTER (2 << 6) +#define STATE1_ASY_SUBOWNER_SHIFT 4 +#define STATE1_ASY_SUBOWNER_DEFAULT_MASK (3 << 4) +#define STATE1_ASY_SUBOWNER_NONE (0 << 4) +#define STATE1_ASY_SUBOWNER_SUBHEAD0 (1 << 4) +#define STATE1_ASY_SUBOWNER_SUBHEAD1 (2 << 4) +#define STATE1_ASY_SUBOWNER_BOTH (3 << 4) +#define STATE1_ASY_OWNER_SHIFT 0 +#define STATE1_ASY_OWNER_DEFAULT_MASK 0xf +#define STATE1_ASY_OWNER_NONE 0 +#define STATE1_ASY_OWNER_HEAD0 1 +#define STATE1_ASY_OWNER_HEAD1 2 +#define NV_HEAD_STATE0(i) 0x5 +#define NV_HEAD_STATE0_INTERLACED_SHIFT 4 +#define NV_HEAD_STATE0_INTERLACED_DEFAULT_MASK (3 << 4) +#define NV_HEAD_STATE0_INTERLACED_PROGRESSIVE (0 << 4) +#define NV_HEAD_STATE0_INTERLACED_INTERLACED (1 << 4) +#define NV_HEAD_STATE0_RANGECOMPRESS_SHIFT 3 +#define NV_HEAD_STATE0_RANGECOMPRESS_DEFAULT_MASK (1 << 3) +#define NV_HEAD_STATE0_RANGECOMPRESS_DISABLE (0 << 3) +#define NV_HEAD_STATE0_RANGECOMPRESS_ENABLE (1 << 3) +#define NV_HEAD_STATE0_DYNRANGE_SHIFT 2 +#define NV_HEAD_STATE0_DYNRANGE_DEFAULT_MASK (1 << 2) +#define NV_HEAD_STATE0_DYNRANGE_VESA (0 << 2) +#define NV_HEAD_STATE0_DYNRANGE_CEA (1 << 2) +#define NV_HEAD_STATE0_COLORSPACE_SHIFT 0 +#define NV_HEAD_STATE0_COLORSPACE_DEFAULT_MASK 0x3 +#define NV_HEAD_STATE0_COLORSPACE_RGB 0 +#define NV_HEAD_STATE0_COLORSPACE_YUV_601 1 +#define NV_HEAD_STATE0_COLORSPACE_YUV_709 2 +#define NV_HEAD_STATE1(i) (7 + i) +#define NV_HEAD_STATE1_VTOTAL_SHIFT 16 +#define NV_HEAD_STATE1_VTOTAL_DEFAULT_MASK (0x7fff << 16) +#define NV_HEAD_STATE1_HTOTAL_SHIFT 0 +#define NV_HEAD_STATE1_HTOTAL_DEFAULT_MASK 0x7fff +#define NV_HEAD_STATE2(i) (9 + i) +#define NV_HEAD_STATE2_VSYNC_END_SHIFT 16 +#define NV_HEAD_STATE2_VSYNC_END_DEFAULT_MASK (0x7fff << 16) +#define NV_HEAD_STATE2_HSYNC_END_SHIFT 0 +#define NV_HEAD_STATE2_HSYNC_END_DEFAULT_MASK 0x7fff +#define NV_HEAD_STATE3(i) (0xb + i) +#define NV_HEAD_STATE3_VBLANK_END_SHIFT 16 +#define NV_HEAD_STATE3_VBLANK_END_DEFAULT_MASK (0x7fff << 16) +#define NV_HEAD_STATE3_HBLANK_END_SHIFT 0 +#define NV_HEAD_STATE3_HBLANK_END_DEFAULT_MASK 0x7fff +#define NV_HEAD_STATE4(i) (0xd + i) +#define NV_HEAD_STATE4_VBLANK_START_SHIFT 16 +#define NV_HEAD_STATE4_VBLANK_START_DEFAULT_MASK (0x7fff << 16) +#define NV_HEAD_STATE4_HBLANK_START_SHIFT 0 +#define NV_HEAD_STATE4_HBLANK_START_DEFAULT_MASK 0x7fff +#define NV_HEAD_STATE5(i) (0xf + i) +#define CRC_CNTRL 0x11 +#define CRC_CNTRL_ARM_CRC_ENABLE_SHIFT 0 +#define CRC_CNTRL_ARM_CRC_ENABLE_NO 0 +#define CRC_CNTRL_ARM_CRC_ENABLE_YES 1 +#define CRC_CNTRL_ARM_CRC_ENABLE_DIS 0 +#define CRC_CNTRL_ARM_CRC_ENABLE_EN 1 +#define CLK_CNTRL 0x13 +#define CLK_CNTRL_DP_CLK_SEL_SHIFT 0 +#define CLK_CNTRL_DP_CLK_SEL_MASK 0x3 +#define CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK 0 +#define CLK_CNTRL_DP_CLK_SEL_DIFF_PCLK 1 +#define CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK 2 +#define CLK_CNTRL_DP_CLK_SEL_DIFF_DPCLK 3 +#define CLK_CNTRL_DP_LINK_SPEED_SHIFT 2 +#define CLK_CNTRL_DP_LINK_SPEED_MASK (0x1f << 2) +#define CLK_CNTRL_DP_LINK_SPEED_G1_62 (6 << 2) +#define CLK_CNTRL_DP_LINK_SPEED_G2_7 (10 << 2) +#define CLK_CNTRL_DP_LINK_SPEED_LVDS (7 << 2) +#define CAP 0x14 +#define CAP_DP_A_SHIFT 24 +#define CAP_DP_A_DEFAULT_MASK (1 << 24) +#define CAP_DP_A_FALSE (0 << 24) +#define CAP_DP_A_TRUE (1 << 24) +#define CAP_DP_B_SHIFT 25 +#define CAP_DP_B_DEFAULT_MASK (1 << 24) +#define CAP_DP_B_FALSE (0 << 24) +#define CAP_DP_B_TRUE (1 << 24) +#define PWR 0x15 +#define PWR_SETTING_NEW_SHIFT 31 +#define PWR_SETTING_NEW_DEFAULT_MASK (1 << 31) +#define PWR_SETTING_NEW_DONE (0 << 31) +#define PWR_SETTING_NEW_PENDING (1 << 31) +#define PWR_SETTING_NEW_TRIGGER (1 << 31) +#define PWR_MODE_SHIFT 28 +#define PWR_MODE_DEFAULT_MASK (1 << 28) +#define PWR_MODE_NORMAL (0 << 28) +#define PWR_MODE_SAFE (1 << 28) +#define PWR_HALT_DELAY_SHIFT 24 +#define PWR_HALT_DELAY_DEFAULT_MASK (1 << 24) +#define PWR_HALT_DELAY_DONE (0 << 24) +#define PWR_HALT_DELAY_ACTIVE (1 << 24) +#define PWR_SAFE_START_SHIFT 17 +#define PWR_SAFE_START_DEFAULT_MASK (1 << 17) +#define PWR_SAFE_START_NORMAL (0 << 17) +#define PWR_SAFE_START_ALT (1 << 17) +#define PWR_SAFE_STATE_SHIFT 16 +#define PWR_SAFE_STATE_DEFAULT_MASK (1 << 16) +#define PWR_SAFE_STATE_PD (0 << 16) +#define PWR_SAFE_STATE_PU (1 << 16) +#define PWR_NORMAL_START_SHIFT 1 +#define PWR_NORMAL_START_DEFAULT_MASK (1 << 1) +#define PWR_NORMAL_START_NORMAL (0 << 16) +#define PWR_NORMAL_START_ALT (1 << 16) +#define PWR_NORMAL_STATE_SHIFT 0 +#define PWR_NORMAL_STATE_DEFAULT_MASK 0x1 +#define PWR_NORMAL_STATE_PD 0 +#define PWR_NORMAL_STATE_PU 1 +#define TEST 0x16 +#define TEST_TESTMUX_SHIFT 24 +#define TEST_TESTMUX_DEFAULT_MASK (0xff << 24) +#define TEST_TESTMUX_AVSS (0 << 24) +#define TEST_TESTMUX_CLOCKIN (2 << 24) +#define TEST_TESTMUX_PLL_VOL (4 << 24) +#define TEST_TESTMUX_SLOWCLKINT (8 << 24) +#define TEST_TESTMUX_AVDD (16 << 24) +#define TEST_TESTMUX_VDDREG (32 << 24) +#define TEST_TESTMUX_REGREF_VDDREG (64 << 24) +#define TEST_TESTMUX_REGREF_AVDD (128 << 24) +#define TEST_CRC_SHIFT 23 +#define TEST_CRC_PRE_SERIALIZE (0 << 23) +#define TEST_CRC_POST_DESERIALIZE (1 << 23) +#define TEST_TPAT_SHIFT 20 +#define TEST_TPAT_DEFAULT_MASK (7 << 20) +#define TEST_TPAT_LO (0 << 20) +#define TEST_TPAT_TDAT (1 << 20) +#define TEST_TPAT_RAMP (2 << 20) +#define TEST_TPAT_WALK (3 << 20) +#define TEST_TPAT_MAXSTEP (4 << 20) +#define TEST_TPAT_MINSTEP (5 << 20) +#define TEST_DSRC_SHIFT 16 +#define TEST_DSRC_DEFAULT_MASK (3 << 16) +#define TEST_DSRC_NORMAL (0 << 16) +#define TEST_DSRC_DEBUG (1 << 16) +#define TEST_DSRC_TGEN (2 << 16) +#define TEST_HEAD_NUMBER_SHIFT 12 +#define TEST_HEAD_NUMBER_DEFAULT_MASK (3 << 12) +#define TEST_HEAD_NUMBER_NONE (0 << 12) +#define TEST_HEAD_NUMBER_HEAD0 (1 << 12) +#define TEST_HEAD_NUMBER_HEAD1 (2 << 12) +#define TEST_ATTACHED_SHIFT 10 +#define TEST_ATTACHED_DEFAULT_MASK (1 << 10) +#define TEST_ATTACHED_FALSE (0 << 10) +#define TEST_ATTACHED_TRUE (1 << 10) +#define TEST_ACT_HEAD_OPMODE_SHIFT 8 +#define TEST_ACT_HEAD_OPMODE_DEFAULT_MASK (3 << 8) +#define TEST_ACT_HEAD_OPMODE_SLEEP (0 << 8) +#define TEST_ACT_HEAD_OPMODE_SNOOZE (1 << 8) +#define TEST_ACT_HEAD_OPMODE_AWAKE (2 << 8) +#define TEST_INVD_SHIFT 6 +#define TEST_INVD_DISABLE (0 << 6) +#define TEST_INVD_ENABLE (1 << 6) +#define TEST_TEST_ENABLE_SHIFT 1 +#define TEST_TEST_ENABLE_DISABLE (0 << 1) +#define TEST_TEST_ENABLE_ENABLE (1 << 1) +#define PLL0 0x17 +#define PLL0_ICHPMP_SHFIT 24 +#define PLL0_ICHPMP_DEFAULT_MASK (0xf << 24) +#define PLL0_VCOCAP_SHIFT 8 +#define PLL0_VCOCAP_DEFAULT_MASK (0xf << 8) +#define PLL0_PLLREG_LEVEL_SHIFT 6 +#define PLL0_PLLREG_LEVEL_DEFAULT_MASK (3 << 6) +#define PLL0_PLLREG_LEVEL_V25 (0 << 6) +#define PLL0_PLLREG_LEVEL_V15 (1 << 6) +#define PLL0_PLLREG_LEVEL_V35 (2 << 6) +#define PLL0_PLLREG_LEVEL_V45 (3 << 6) +#define PLL0_PULLDOWN_SHIFT 5 +#define PLL0_PULLDOWN_DEFAULT_MASK (1 << 5) +#define PLL0_PULLDOWN_DISABLE (0 << 5) +#define PLL0_PULLDOWN_ENABLE (1 << 5) +#define PLL0_RESISTORSEL_SHIFT 4 +#define PLL0_RESISTORSEL_DEFAULT_MASK (1 << 4) +#define PLL0_RESISTORSEL_INT (0 << 4) +#define PLL0_RESISTORSEL_EXT (1 << 4) +#define PLL0_VCOPD_SHIFT 2 +#define PLL0_VCOPD_MASK (1 << 2) +#define PLL0_VCOPD_RESCIND (0 << 2) +#define PLL0_VCOPD_ASSERT (1 << 2) +#define PLL0_PWR_SHIFT 0 +#define PLL0_PWR_MASK 1 +#define PLL0_PWR_ON 0 +#define PLL0_PWR_OFF 1 +#define PLL1_TMDS_TERM_SHIFT 8 +#define PLL1_TMDS_TERM_DISABLE (0 << 8) +#define PLL1_TMDS_TERM_ENABLE (1 << 8) +#define PLL1 0x18 +#define PLL1_TERM_COMPOUT_SHIFT 15 +#define PLL1_TERM_COMPOUT_LOW (0 << 15) +#define PLL1_TERM_COMPOUT_HIGH (1 << 15) +#define PLL2 0x19 +#define PLL2_DCIR_PLL_RESET_SHIFT 0 +#define PLL2_DCIR_PLL_RESET_OVERRIDE (0 << 0) +#define PLL2_DCIR_PLL_RESET_ALLOW (1 << 0) +#define PLL2_AUX1_SHIFT 17 +#define PLL2_AUX1_SEQ_MASK (1 << 17) +#define PLL2_AUX1_SEQ_PLLCAPPD_ALLOW (0 << 17) +#define PLL2_AUX1_SEQ_PLLCAPPD_OVERRIDE (1 << 17) +#define PLL2_AUX2_SHIFT 18 +#define PLL2_AUX2_MASK (1 << 18) +#define PLL2_AUX2_OVERRIDE_POWERDOWN (0 << 18) +#define PLL2_AUX2_ALLOW_POWERDOWN (1 << 18) +#define PLL2_AUX6_SHIFT 22 +#define PLL2_AUX6_BANDGAP_POWERDOWN_MASK (1 << 22) +#define PLL2_AUX6_BANDGAP_POWERDOWN_DISABLE (0 << 22) +#define PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE (1 << 22) +#define PLL2_AUX7_SHIFT 23 +#define PLL2_AUX7_PORT_POWERDOWN_MASK (1 << 23) +#define PLL2_AUX7_PORT_POWERDOWN_DISABLE (0 << 23) +#define PLL2_AUX7_PORT_POWERDOWN_ENABLE (1 << 23) +#define PLL2_AUX8_SHIFT 24 +#define PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK (1 << 24) +#define PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_DISABLE (0 << 24) +#define PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_ENABLE (1 << 24) +#define PLL2_AUX9_SHIFT 25 +#define PLL2_AUX9_LVDSEN_ALLOW (0 << 25) +#define PLL2_AUX9_LVDSEN_OVERRIDE (1 << 25) +#define PLL3 0x1a +#define PLL3_PLLVDD_MODE_SHIFT 13 +#define PLL3_PLLVDD_MODE_MASK (1 << 13) +#define PLL3_PLLVDD_MODE_V1_8 (0 << 13) +#define PLL3_PLLVDD_MODE_V3_3 (1 << 13) +#define CSTM 0x1b +#define CSTM_ROTDAT_SHIFT 28 +#define CSTM_ROTDAT_DEFAULT_MASK (7 << 28) +#define CSTM_ROTCLK_SHIFT 24 +#define CSTM_ROTCLK_DEFAULT_MASK (0xf << 24) +#define CSTM_LVDS_EN_SHIFT 16 +#define CSTM_LVDS_EN_DISABLE (0 << 16) +#define CSTM_LVDS_EN_ENABLE (1 << 16) +#define CSTM_LINKACTB_SHIFT 15 +#define CSTM_LINKACTB_DISABLE (0 << 15) +#define CSTM_LINKACTB_ENABLE (1 << 15) +#define CSTM_LINKACTA_SHIFT 14 +#define CSTM_LINKACTA_DISABLE (0 << 14) +#define CSTM_LINKACTA_ENABLE (1 << 14) +#define LVDS 0x1c +#define LVDS_ROTDAT_SHIFT 28 +#define LVDS_ROTDAT_DEFAULT_MASK (7 << 28) +#define LVDS_ROTDAT_RST (0 << 28) +#define LVDS_ROTCLK_SHIFT 24 +#define LVDS_ROTCLK_DEFAULT_MASK (0xf << 24) +#define LVDS_ROTCLK_RST (0 << 24) +#define LVDS_PLLDIV_SHIFT 21 +#define LVDS_PLLDIV_DEFAULT_MASK (1 << 21) +#define LVDS_PLLDIV_BY_7 (0 << 21) +#define LVDS_BALANCED_SHIFT 19 +#define LVDS_BALANCED_DEFAULT_MASK (1 << 19) +#define LVDS_BALANCED_DISABLE (0 << 19) +#define LVDS_BALANCED_ENABLE (1 << 19) +#define LVDS_NEW_MODE_SHIFT 18 +#define LVDS_NEW_MODE_DEFAULT_MASK (1 << 18) +#define LVDS_NEW_MODE_DISABLE (0 << 18) +#define LVDS_NEW_MODE_ENABLE (1 << 18) +#define LVDS_DUP_SYNC_SHIFT 17 +#define LVDS_DUP_SYNC_DEFAULT_MASK (1 << 17) +#define LVDS_DUP_SYNC_DISABLE (0 << 17) +#define LVDS_DUP_SYNC_ENABLE (1 << 17) +#define LVDS_LVDS_EN_SHIFT 16 +#define LVDS_LVDS_EN_DEFAULT_MASK (1 << 16) +#define LVDS_LVDS_EN_ENABLE (1 << 16) +#define LVDS_LINKACTB_SHIFT 15 +#define LVDS_LINKACTB_DEFAULT_MASK (1 << 15) +#define LVDS_LINKACTB_DISABLE (0 << 15) +#define LVDS_LINKACTB_ENABLE (1 << 15) +#define LVDS_LINKACTA_SHIFT 14 +#define LVDS_LINKACTA_DEFAULT_MASK (1 << 14) +#define LVDS_LINKACTA_ENABLE (1 << 14) +#define LVDS_MODE_SHIFT 12 +#define LVDS_MODE_DEFAULT_MASK (3 << 12) +#define LVDS_MODE_LVDS (0 << 12) +#define LVDS_UPPER_SHIFT 11 +#define LVDS_UPPER_DEFAULT_MASK (1 << 11) +#define LVDS_UPPER_FALSE (0 << 11) +#define LVDS_UPPER_TRUE (1 << 11) +#define LVDS_PD_TXCB_SHIFT 9 +#define LVDS_PD_TXCB_DEFAULT_MASK (1 << 9) +#define LVDS_PD_TXCB_ENABLE (0 << 9) +#define LVDS_PD_TXCB_DISABLE (1 << 9) +#define LVDS_PD_TXCA_SHIFT 8 +#define LVDS_PD_TXCA_DEFAULT_MASK (1 << 8) +#define LVDS_PD_TXCA_ENABLE (0 << 8) +#define LVDS_PD_TXDB_3_SHIFT 7 +#define LVDS_PD_TXDB_3_DEFAULT_MASK (1 << 7) +#define LVDS_PD_TXDB_3_ENABLE (0 << 7) +#define LVDS_PD_TXDB_3_DISABLE (1 << 7) +#define LVDS_PD_TXDB_2_SHIFT 6 +#define LVDS_PD_TXDB_2_DEFAULT_MASK (1 << 6) +#define LVDS_PD_TXDB_2_ENABLE (0 << 6) +#define LVDS_PD_TXDB_2_DISABLE (1 << 6) +#define LVDS_PD_TXDB_1_SHIFT 5 +#define LVDS_PD_TXDB_1_DEFAULT_MASK (1 << 5) +#define LVDS_PD_TXDB_1_ENABLE (0 << 5) +#define LVDS_PD_TXDB_1_DISABLE (1 << 5) +#define LVDS_PD_TXDB_0_SHIFT 4 +#define LVDS_PD_TXDB_0_DEFAULT_MASK (1 << 4) +#define LVDS_PD_TXDB_0_ENABLE (0 << 4) +#define LVDS_PD_TXDB_0_DISABLE (1 << 4) +#define LVDS_PD_TXDA_3_SHIFT 3 +#define LVDS_PD_TXDA_3_DEFAULT_MASK (1 << 3) +#define LVDS_PD_TXDA_3_ENABLE (0 << 3) +#define LVDS_PD_TXDA_3_DISABLE (1 << 3) +#define LVDS_PD_TXDA_2_SHIFT 2 +#define LVDS_PD_TXDA_2_DEFAULT_MASK (1 << 2) +#define LVDS_PD_TXDA_2_ENABLE (0 << 2) +#define LVDS_PD_TXDA_1_SHIFT 1 +#define LVDS_PD_TXDA_1_DEFAULT_MASK (1 << 1) +#define LVDS_PD_TXDA_1_ENABLE (0 << 1) +#define LVDS_PD_TXDA_0_SHIFT 0 +#define LVDS_PD_TXDA_0_DEFAULT_MASK 0x1 +#define LVDS_PD_TXDA_0_ENABLE 0 +#define CRCA 0x1d +#define CRCA_VALID_FALSE 0 +#define CRCA_VALID_TRUE 1 +#define CRCA_VALID_RST 1 +#define CRCB 0x1e +#define CRCB_CRC_DEFAULT_MASK 0xffffffff +#define SEQ_CTL 0x20 +#define SEQ_CTL_SWITCH_SHIFT 30 +#define SEQ_CTL_SWITCH_MASK (1 << 30) +#define SEQ_CTL_SWITCH_WAIT (0 << 30) +#define SEQ_CTL_SWITCH_FORCE (1 << 30) +#define SEQ_CTL_STATUS_SHIFT 28 +#define SEQ_CTL_STATUS_MASK (1 << 28) +#define SEQ_CTL_STATUS_STOPPED (0 << 28) +#define SEQ_CTL_STATUS_RUNNING (1 << 28) +#define SEQ_CTL_PC_SHIFT 16 +#define SEQ_CTL_PC_MASK (0xf << 16) +#define SEQ_CTL_PD_PC_ALT_SHIFT 12 +#define SEQ_CTL_PD_PC_ALT_MASK (0xf << 12) +#define SEQ_CTL_PD_PC_SHIFT 8 +#define SEQ_CTL_PD_PC_MASK (0xf << 8) +#define SEQ_CTL_PU_PC_ALT_SHIFT 4 +#define SEQ_CTL_PU_PC_ALT_MASK (0xf << 4) +#define SEQ_CTL_PU_PC_SHIFT 0 +#define SEQ_CTL_PU_PC_MASK 0xf +#define LANE_SEQ_CTL 0x21 +#define LANE_SEQ_CTL_SETTING_NEW_SHIFT 31 +#define LANE_SEQ_CTL_SETTING_MASK (1 << 31) +#define LANE_SEQ_CTL_SETTING_NEW_DONE (0 << 31) +#define LANE_SEQ_CTL_SETTING_NEW_PENDING (1 << 31) +#define LANE_SEQ_CTL_SETTING_NEW_TRIGGER (1 << 31) +#define LANE_SEQ_CTL_SEQ_STATE_SHIFT 28 +#define LANE_SEQ_CTL_SEQ_STATE_IDLE (0 << 28) +#define LANE_SEQ_CTL_SEQ_STATE_BUSY (1 << 28) +#define LANE_SEQ_CTL_SEQUENCE_SHIFT 20 +#define LANE_SEQ_CTL_SEQUENCE_UP (0 << 20) +#define LANE_SEQ_CTL_SEQUENCE_DOWN (1 << 20) +#define LANE_SEQ_CTL_NEW_POWER_STATE_SHIFT 16 +#define LANE_SEQ_CTL_NEW_POWER_STATE_PU (0 << 16) +#define LANE_SEQ_CTL_NEW_POWER_STATE_PD (1 << 16) +#define LANE_SEQ_CTL_DELAY_SHIFT 12 +#define LANE_SEQ_CTL_DELAY_DEFAULT_MASK (0xf << 12) +#define LANE_SEQ_CTL_LANE9_STATE_SHIFT 9 +#define LANE_SEQ_CTL_LANE9_STATE_POWERUP (0 << 9) +#define LANE_SEQ_CTL_LANE9_STATE_POWERDOWN (1 << 9) +#define LANE_SEQ_CTL_LANE8_STATE_SHIFT 8 +#define LANE_SEQ_CTL_LANE8_STATE_POWERUP (0 << 8) +#define LANE_SEQ_CTL_LANE8_STATE_POWERDOWN (1 << 8) +#define LANE_SEQ_CTL_LANE7_STATE_SHIFT 7 +#define LANE_SEQ_CTL_LANE7_STATE_POWERUP (0 << 7) +#define LANE_SEQ_CTL_LANE7_STATE_POWERDOWN (1 << 7) +#define LANE_SEQ_CTL_LANE6_STATE_SHIFT 6 +#define LANE_SEQ_CTL_LANE6_STATE_POWERUP (0 << 6) +#define LANE_SEQ_CTL_LANE6_STATE_POWERDOWN (1 << 6) +#define LANE_SEQ_CTL_LANE5_STATE_SHIFT 5 +#define LANE_SEQ_CTL_LANE5_STATE_POWERUP (0 << 5) +#define LANE_SEQ_CTL_LANE5_STATE_POWERDOWN (1 << 5) +#define LANE_SEQ_CTL_LANE4_STATE_SHIFT 4 +#define LANE_SEQ_CTL_LANE4_STATE_POWERUP (0 << 4) +#define LANE_SEQ_CTL_LANE4_STATE_POWERDOWN (1 << 4) +#define LANE_SEQ_CTL_LANE3_STATE_SHIFT 3 +#define LANE_SEQ_CTL_LANE3_STATE_POWERUP (0 << 3) +#define LANE_SEQ_CTL_LANE3_STATE_POWERDOWN (1 << 3) +#define LANE_SEQ_CTL_LANE2_STATE_SHIFT 2 +#define LANE_SEQ_CTL_LANE2_STATE_POWERUP (0 << 2) +#define LANE_SEQ_CTL_LANE2_STATE_POWERDOWN (1 << 2) +#define LANE_SEQ_CTL_LANE1_STATE_SHIFT 1 +#define LANE_SEQ_CTL_LANE1_STATE_POWERUP (0 << 1) +#define LANE_SEQ_CTL_LANE1_STATE_POWERDOWN (1 << 1) +#define LANE_SEQ_CTL_LANE0_STATE_SHIFT 0 +#define LANE_SEQ_CTL_LANE0_STATE_POWERUP 0 +#define LANE_SEQ_CTL_LANE0_STATE_POWERDOWN 1 +#define SEQ_INST(i) (0x22 + i) +#define SEQ_INST_PLL_PULLDOWN_SHIFT 31 +#define SEQ_INST_PLL_PULLDOWN_DISABLE (0 << 31) +#define SEQ_INST_PLL_PULLDOWN_ENABLE (1 << 31) +#define SEQ_INST_POWERDOWN_MACRO_SHIFT 30 +#define SEQ_INST_POWERDOWN_MACRO_NORMAL (0 << 30) +#define SEQ_INST_POWERDOWN_MACRO_POWERDOWN (1 << 30) +#define SEQ_INST_ASSERT_PLL_RESET_SHIFT 29 +#define SEQ_INST_ASSERT_PLL_RESET_NORMAL (0 << 29) +#define SEQ_INST_ASSERT_PLL_RESET_RST (1 << 29) +#define SEQ_INST_BLANK_V_SHIFT 28 +#define SEQ_INST_BLANK_V_NORMAL (0 << 28) +#define SEQ_INST_BLANK_V_INACTIVE (1 << 28) +#define SEQ_INST_BLANK_H_SHIFT 27 +#define SEQ_INST_BLANK_H_NORMAL (0 << 27) +#define SEQ_INST_BLANK_H_INACTIVE (1 << 27) +#define SEQ_INST_BLANK_DE_SHIFT 26 +#define SEQ_INST_BLANK_DE_NORMAL (0 << 26) +#define SEQ_INST_BLANK_DE_INACTIVE (1 << 26) +#define SEQ_INST_BLACK_DATA_SHIFT 25 +#define SEQ_INST_BLACK_DATA_NORMAL (0 << 25) +#define SEQ_INST_BLACK_DATA_BLACK (1 << 25) +#define SEQ_INST_TRISTATE_IOS_SHIFT 24 +#define SEQ_INST_TRISTATE_IOS_ENABLE_PINS (0 << 24) +#define SEQ_INST_TRISTATE_IOS_TRISTATE (1 << 24) +#define SEQ_INST_DRIVE_PWM_OUT_LO_SHIFT 23 +#define SEQ_INST_DRIVE_PWM_OUT_LO_FALSE (0 << 23) +#define SEQ_INST_DRIVE_PWM_OUT_LO_TRUE (1 << 23) +#define SEQ_INST_PIN_B_SHIFT 22 +#define SEQ_INST_PIN_B_LOW (0 << 22) +#define SEQ_INST_PIN_B_HIGH (1 << 22) +#define SEQ_INST_PIN_A_SHIFT 21 +#define SEQ_INST_PIN_A_LOW (0 << 21) +#define SEQ_INST_PIN_A_HIGH (1 << 21) +#define SEQ_INST_SEQUENCE_SHIFT 19 +#define SEQ_INST_SEQUENCE_UP (0 << 19) +#define SEQ_INST_SEQUENCE_DOWN (1 << 19) +#define SEQ_INST_LANE_SEQ_SHIFT 18 +#define SEQ_INST_LANE_SEQ_STOP (0 << 18) +#define SEQ_INST_LANE_SEQ_RUN (1 << 18) +#define SEQ_INST_PDPORT_SHIFT 17 +#define SEQ_INST_PDPORT_NO (0 << 17) +#define SEQ_INST_PDPORT_YES (1 << 17) +#define SEQ_INST_PDPLL_SHIFT 16 +#define SEQ_INST_PDPLL_NO (0 << 16) +#define SEQ_INST_PDPLL_YES (1 << 16) +#define SEQ_INST_HALT_SHIFT 15 +#define SEQ_INST_HALT_FALSE (0 << 15) +#define SEQ_INST_HALT_TRUE (1 << 15) +#define SEQ_INST_WAIT_UNITS_SHIFT 12 +#define SEQ_INST_WAIT_UNITS_DEFAULT_MASK (3 << 12) +#define SEQ_INST_WAIT_UNITS_US (0 << 12) +#define SEQ_INST_WAIT_UNITS_MS (1 << 12) +#define SEQ_INST_WAIT_UNITS_VSYNC (2 << 12) +#define SEQ_INST_WAIT_TIME_SHIFT 0 +#define SEQ_INST_WAIT_TIME_DEFAULT_MASK 0x3ff +#define PWM_DIV 0x32 +#define PWM_DIV_DIVIDE_DEFAULT_MASK 0xffffff +#define PWM_CTL 0x33 +#define PWM_CTL_SETTING_NEW_SHIFT 31 +#define PWM_CTL_SETTING_NEW_DONE (0 << 31) +#define PWM_CTL_SETTING_NEW_PENDING (1 << 31) +#define PWM_CTL_SETTING_NEW_TRIGGER (1 << 31) +#define PWM_CTL_CLKSEL_SHIFT 30 +#define PWM_CTL_CLKSEL_PCLK (0 << 30) +#define PWM_CTL_CLKSEL_XTAL (1 << 30) +#define PWM_CTL_DUTY_CYCLE_SHIFT 0 +#define PWM_CTL_DUTY_CYCLE_MASK 0xffffff +#define MSCHECK 0x49 +#define MSCHECK_CTL_SHIFT 31 +#define MSCHECK_CTL_CLEAR (0 << 31) +#define MSCHECK_CTL_RUN (1 << 31) +#define XBAR_CTRL 0x4a +#define DP_LINKCTL(i) (0x4c + (i)) +#define DP_LINKCTL_FORCE_IDLEPTTRN_SHIFT 31 +#define DP_LINKCTL_FORCE_IDLEPTTRN_NO (0 << 31) +#define DP_LINKCTL_FORCE_IDLEPTTRN_YES (1 << 31) +#define DP_LINKCTL_COMPLIANCEPTTRN_SHIFT 28 +#define DP_LINKCTL_COMPLIANCEPTTRN_NOPATTERN (0 << 28) +#define DP_LINKCTL_COMPLIANCEPTTRN_COLORSQARE (1 << 28) +#define DP_LINKCTL_LANECOUNT_SHIFT 16 +#define DP_LINKCTL_LANECOUNT_MASK (0x1f << 16) +#define DP_LINKCTL_LANECOUNT_ZERO (0 << 16) +#define DP_LINKCTL_LANECOUNT_ONE (1 << 16) +#define DP_LINKCTL_LANECOUNT_TWO (3 << 16) +#define DP_LINKCTL_LANECOUNT_FOUR (15 << 16) +#define DP_LINKCTL_ENHANCEDFRAME_SHIFT 14 +#define DP_LINKCTL_ENHANCEDFRAME_DISABLE (0 << 14) +#define DP_LINKCTL_ENHANCEDFRAME_ENABLE (1 << 14) +#define DP_LINKCTL_SYNCMODE_SHIFT 10 +#define DP_LINKCTL_SYNCMODE_DISABLE (0 << 10) +#define DP_LINKCTL_SYNCMODE_ENABLE (1 << 10) +#define DP_LINKCTL_TUSIZE_SHIFT 2 +#define DP_LINKCTL_TUSIZE_MASK (0x7f << 2) +#define DP_LINKCTL_ENABLE_SHIFT 0 +#define DP_LINKCTL_ENABLE_NO 0 +#define DP_LINKCTL_ENABLE_YES 1 +#define DC(i) (0x4e + (i)) +#define DC_LANE3_DP_LANE3_SHIFT 24 +#define DC_LANE3_DP_LANE3_MASK (0xff << 24) +#define DC_LANE3_DP_LANE3_P0_LEVEL0 (17 << 24) +#define DC_LANE3_DP_LANE3_P1_LEVEL0 (21 << 24) +#define DC_LANE3_DP_LANE3_P2_LEVEL0 (26 << 24) +#define DC_LANE3_DP_LANE3_P3_LEVEL0 (34 << 24) +#define DC_LANE3_DP_LANE3_P0_LEVEL1 (26 << 24) +#define DC_LANE3_DP_LANE3_P1_LEVEL1 (32 << 24) +#define DC_LANE3_DP_LANE3_P2_LEVEL1 (39 << 24) +#define DC_LANE3_DP_LANE3_P0_LEVEL2 (34 << 24) +#define DC_LANE3_DP_LANE3_P1_LEVEL2 (43 << 24) +#define DC_LANE3_DP_LANE3_P0_LEVEL3 (51 << 24) +#define DC_LANE2_DP_LANE0_SHIFT 16 +#define DC_LANE2_DP_LANE0_MASK (0xff << 16) +#define DC_LANE2_DP_LANE0_P0_LEVEL0 (17 << 16) +#define DC_LANE2_DP_LANE0_P1_LEVEL0 (21 << 16) +#define DC_LANE2_DP_LANE0_P2_LEVEL0 (26 << 16) +#define DC_LANE2_DP_LANE0_P3_LEVEL0 (34 << 16) +#define DC_LANE2_DP_LANE0_P0_LEVEL1 (26 << 16) +#define DC_LANE2_DP_LANE0_P1_LEVEL1 (32 << 16) +#define DC_LANE2_DP_LANE0_P2_LEVEL1 (39 << 16) +#define DC_LANE2_DP_LANE0_P0_LEVEL2 (34 << 16) +#define DC_LANE2_DP_LANE0_P1_LEVEL2 (43 << 16) +#define DC_LANE2_DP_LANE0_P0_LEVEL3 (51 << 16) +#define DC_LANE1_DP_LANE1_SHIFT 8 +#define DC_LANE1_DP_LANE1_MASK (0xff << 8) +#define DC_LANE1_DP_LANE1_P0_LEVEL0 (17 << 8) +#define DC_LANE1_DP_LANE1_P1_LEVEL0 (21 << 8) +#define DC_LANE1_DP_LANE1_P2_LEVEL0 (26 << 8) +#define DC_LANE1_DP_LANE1_P3_LEVEL0 (34 << 8) +#define DC_LANE1_DP_LANE1_P0_LEVEL1 (26 << 8) +#define DC_LANE1_DP_LANE1_P1_LEVEL1 (32 << 8) +#define DC_LANE1_DP_LANE1_P2_LEVEL1 (39 << 8) +#define DC_LANE1_DP_LANE1_P0_LEVEL2 (34 << 8) +#define DC_LANE1_DP_LANE1_P1_LEVEL2 (43 << 8) +#define DC_LANE1_DP_LANE1_P0_LEVEL3 (51 << 8) +#define DC_LANE0_DP_LANE2_SHIFT 0 +#define DC_LANE0_DP_LANE2_MASK 0xff +#define DC_LANE0_DP_LANE2_P0_LEVEL0 17 +#define DC_LANE0_DP_LANE2_P1_LEVEL0 21 +#define DC_LANE0_DP_LANE2_P2_LEVEL0 26 +#define DC_LANE0_DP_LANE2_P3_LEVEL0 34 +#define DC_LANE0_DP_LANE2_P0_LEVEL1 26 +#define DC_LANE0_DP_LANE2_P1_LEVEL1 32 +#define DC_LANE0_DP_LANE2_P2_LEVEL1 39 +#define DC_LANE0_DP_LANE2_P0_LEVEL2 34 +#define DC_LANE0_DP_LANE2_P1_LEVEL2 43 +#define DC_LANE0_DP_LANE2_P0_LEVEL3 51 +#define LANE_DRIVE_CURRENT(i) (0x4e + (i)) +#define PR(i) (0x52 + (i)) +#define PR_LANE3_DP_LANE3_SHIFT 24 +#define PR_LANE3_DP_LANE3_MASK (0xff << 24) +#define PR_LANE3_DP_LANE3_D0_LEVEL0 (0 << 24) +#define PR_LANE3_DP_LANE3_D1_LEVEL0 (0 << 24) +#define PR_LANE3_DP_LANE3_D2_LEVEL0 (0 << 24) +#define PR_LANE3_DP_LANE3_D3_LEVEL0 (0 << 24) +#define PR_LANE3_DP_LANE3_D0_LEVEL1 (4 << 24) +#define PR_LANE3_DP_LANE3_D1_LEVEL1 (6 << 24) +#define PR_LANE3_DP_LANE3_D2_LEVEL1 (17 << 24) +#define PR_LANE3_DP_LANE3_D0_LEVEL2 (8 << 24) +#define PR_LANE3_DP_LANE3_D1_LEVEL2 (13 << 24) +#define PR_LANE3_DP_LANE3_D0_LEVEL3 (17 << 24) +#define PR_LANE2_DP_LANE0_SHIFT 16 +#define PR_LANE2_DP_LANE0_MASK (0xff << 16) +#define PR_LANE2_DP_LANE0_D0_LEVEL0 (0 << 16) +#define PR_LANE2_DP_LANE0_D1_LEVEL0 (0 << 16) +#define PR_LANE2_DP_LANE0_D2_LEVEL0 (0 << 16) +#define PR_LANE2_DP_LANE0_D3_LEVEL0 (0 << 16) +#define PR_LANE2_DP_LANE0_D0_LEVEL1 (4 << 16) +#define PR_LANE2_DP_LANE0_D1_LEVEL1 (6 << 16) +#define PR_LANE2_DP_LANE0_D2_LEVEL1 (17 << 16) +#define PR_LANE2_DP_LANE0_D0_LEVEL2 (8 << 16) +#define PR_LANE2_DP_LANE0_D1_LEVEL2 (13 << 16) +#define PR_LANE2_DP_LANE0_D0_LEVEL3 (17 << 16) +#define PR_LANE1_DP_LANE1_SHIFT 8 +#define PR_LANE1_DP_LANE1_MASK (0xff >> 8) +#define PR_LANE1_DP_LANE1_D0_LEVEL0 (0 >> 8) +#define PR_LANE1_DP_LANE1_D1_LEVEL0 (0 >> 8) +#define PR_LANE1_DP_LANE1_D2_LEVEL0 (0 >> 8) +#define PR_LANE1_DP_LANE1_D3_LEVEL0 (0 >> 8) +#define PR_LANE1_DP_LANE1_D0_LEVEL1 (4 >> 8) +#define PR_LANE1_DP_LANE1_D1_LEVEL1 (6 >> 8) +#define PR_LANE1_DP_LANE1_D2_LEVEL1 (17 >> 8) +#define PR_LANE1_DP_LANE1_D0_LEVEL2 (8 >> 8) +#define PR_LANE1_DP_LANE1_D1_LEVEL2 (13 >> 8) +#define PR_LANE1_DP_LANE1_D0_LEVEL3 (17 >> 8) +#define PR_LANE0_DP_LANE2_SHIFT 0 +#define PR_LANE0_DP_LANE2_MASK 0xff +#define PR_LANE0_DP_LANE2_D0_LEVEL0 0 +#define PR_LANE0_DP_LANE2_D1_LEVEL0 0 +#define PR_LANE0_DP_LANE2_D2_LEVEL0 0 +#define PR_LANE0_DP_LANE2_D3_LEVEL0 0 +#define PR_LANE0_DP_LANE2_D0_LEVEL1 4 +#define PR_LANE0_DP_LANE2_D1_LEVEL1 6 +#define PR_LANE0_DP_LANE2_D2_LEVEL1 17 +#define PR_LANE0_DP_LANE2_D0_LEVEL2 8 +#define PR_LANE0_DP_LANE2_D1_LEVEL2 13 +#define PR_LANE0_DP_LANE2_D0_LEVEL3 17 +#define LANE4_PREEMPHASIS(i) (0x54 + (i)) +#define POSTCURSOR(i) (0x56 + (i)) +#define DP_CONFIG(i) (0x58 + (i)) +#define DP_CONFIG_RD_RESET_VAL_SHIFT 31 +#define DP_CONFIG_RD_RESET_VAL_POSITIVE (0 << 31) +#define DP_CONFIG_RD_RESET_VAL_NEGATIVE (1 << 31) +#define DP_CONFIG_IDLE_BEFORE_ATTACH_SHIFT 28 +#define DP_CONFIG_IDLE_BEFORE_ATTACH_DISABLE (0 << 28) +#define DP_CONFIG_IDLE_BEFORE_ATTACH_ENABLE (1 << 28) +#define DP_CONFIG_ACTIVESYM_CNTL_SHIFT 26 +#define DP_CONFIG_ACTIVESYM_CNTL_DISABLE (0 << 26) +#define DP_CONFIG_ACTIVESYM_CNTL_ENABLE (1 << 26) +#define DP_CONFIG_ACTIVESYM_POLARITY_SHIFT 24 +#define DP_CONFIG_ACTIVESYM_POLARITY_NEGATIVE (0 << 24) +#define DP_CONFIG_ACTIVESYM_POLARITY_POSITIVE (1 << 24) +#define DP_CONFIG_ACTIVESYM_FRAC_SHIFT 16 +#define DP_CONFIG_ACTIVESYM_FRAC_MASK (0xf << 16) +#define DP_CONFIG_ACTIVESYM_COUNT_SHIFT 8 +#define DP_CONFIG_ACTIVESYM_COUNT_MASK (0x7f << 8) +#define DP_CONFIG_WATERMARK_SHIFT 0 +#define DP_CONFIG_WATERMARK_MASK 0x3f +#define DP_MN(i) (0x5a + i) +#define DP_MN_M_MOD_SHIFT 30 +#define DP_MN_M_MOD_DEFAULT_MASK (3 << 30) +#define DP_MN_M_MOD_NONE (0 << 30) +#define DP_MN_M_MOD_INC (1 << 30) +#define DP_MN_M_MOD_DEC (2 << 30) +#define DP_MN_M_DELTA_SHIFT 24 +#define DP_MN_M_DELTA_DEFAULT_MASK (0xf << 24) +#define DP_MN_N_VAL_SHIFT 0 +#define DP_MN_N_VAL_DEFAULT_MASK 0xffffff +#define DP_PADCTL(i) (0x5c + (i)) +#define DP_PADCTL_SPARE_SHIFT 25 +#define DP_PADCTL_SPARE_DEFAULT_MASK (0x7f << 25) +#define DP_PADCTL_VCO_2X_SHIFT 24 +#define DP_PADCTL_VCO_2X_DISABLE (0 << 24) +#define DP_PADCTL_VCO_2X_ENABLE (1 << 24) +#define DP_PADCTL_PAD_CAL_PD_SHIFT 23 +#define DP_PADCTL_PAD_CAL_PD_POWERUP (0 << 23) +#define DP_PADCTL_PAD_CAL_PD_POWERDOWN (1 << 23) +#define DP_PADCTL_TX_PU_SHIFT 22 +#define DP_PADCTL_TX_PU_DISABLE (0 << 22) +#define DP_PADCTL_TX_PU_ENABLE (1 << 22) +#define DP_PADCTL_TX_PU_MASK (1 << 22) +#define DP_PADCTL_REG_CTRL_SHIFT 20 +#define DP_PADCTL_REG_CTRL_DEFAULT_MASK (3 << 20) +#define DP_PADCTL_VCMMODE_SHIFT 16 +#define DP_PADCTL_VCMMODE_DEFAULT_MASK (0xf << 16) +#define DP_PADCTL_VCMMODE_TRISTATE (0 << 16) +#define DP_PADCTL_VCMMODE_TEST_MUX (1 << 16) +#define DP_PADCTL_VCMMODE_WEAK_PULLDOWN (2 << 16) +#define DP_PADCTL_VCMMODE_STRONG_PULLDOWN (4 << 16) +#define DP_PADCTL_TX_PU_VALUE_SHIFT 8 +#define DP_PADCTL_TX_PU_VALUE_DEFAULT_MASK (0xff << 8) +#define DP_PADCTL_COMODE_TXD_3_DP_TXD_3_SHIFT 7 +#define DP_PADCTL_COMODE_TXD_3_DP_TXD_3_DISABLE (0 << 7) +#define DP_PADCTL_COMODE_TXD_3_DP_TXD_3_ENABLE (1 << 7) +#define DP_PADCTL_COMODE_TXD_2_DP_TXD_0_SHIFT 6 +#define DP_PADCTL_COMODE_TXD_2_DP_TXD_0_DISABLE (0 << 6) +#define DP_PADCTL_COMODE_TXD_2_DP_TXD_0_ENABLE (1 << 6) +#define DP_PADCTL_COMODE_TXD_1_DP_TXD_1_SHIFT 5 +#define DP_PADCTL_COMODE_TXD_1_DP_TXD_1_DISABLE (0 << 5) +#define DP_PADCTL_COMODE_TXD_1_DP_TXD_1_ENABLE (1 << 5) +#define DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT 4 +#define DP_PADCTL_COMODE_TXD_0_DP_TXD_2_DISABLE (0 << 4) +#define DP_PADCTL_COMODE_TXD_0_DP_TXD_2_ENABLE (1 << 4) +#define DP_PADCTL_PD_TXD_3_SHIFT 3 +#define DP_PADCTL_PD_TXD_3_YES (0 << 3) +#define DP_PADCTL_PD_TXD_3_NO (1 << 3) +#define DP_PADCTL_PD_TXD_0_SHIFT 2 +#define DP_PADCTL_PD_TXD_0_YES (0 << 2) +#define DP_PADCTL_PD_TXD_0_NO (1 << 2) +#define DP_PADCTL_PD_TXD_1_SHIFT 1 +#define DP_PADCTL_PD_TXD_1_YES (0 << 1) +#define DP_PADCTL_PD_TXD_1_NO (1 << 1) +#define DP_PADCTL_PD_TXD_2_SHIFT 0 +#define DP_PADCTL_PD_TXD_2_YES 0 +#define DP_PADCTL_PD_TXD_2_NO 1 +#define DP_DEBUG(i) (0x5e + i) +#define DP_SPARE(i) (0x60 + (i)) +#define DP_SPARE_REG_SHIFT 3 +#define DP_SPARE_REG_DEFAULT_MASK (0x1fffffff << 3) +#define DP_SPARE_SOR_CLK_SEL_SHIFT 2 +#define DP_SPARE_SOR_CLK_SEL_DEFAULT_MASK (1 << 2) +#define DP_SPARE_SOR_CLK_SEL_SAFE_SORCLK (0 << 2) +#define DP_SPARE_SOR_CLK_SEL_MACRO_SORCLK (1 << 2) +#define DP_SPARE_PANEL_SHIFT 1 +#define DP_SPARE_PANEL_EXTERNAL (0 << 1) +#define DP_SPARE_PANEL_INTERNAL (1 << 1) +#define DP_SPARE_SEQ_ENABLE_SHIFT 0 +#define DP_SPARE_SEQ_ENABLE_NO 0 +#define DP_SPARE_SEQ_ENABLE_YES 1 +#define DP_AUDIO_CTRL 0x62 +#define DP_AUDIO_HBLANK_SYMBOLS 0x63 +#define DP_AUDIO_HBLANK_SYMBOLS_MASK 0x1ffff +#define DP_AUDIO_HBLANK_SYMBOLS_VALUE_SHIFT 0 +#define DP_AUDIO_VBLANK_SYMBOLS 0x64 +#define DP_AUDIO_VBLANK_SYMBOLS_MASK 0x1ffff +#define DP_AUDIO_VBLANK_SYMBOLS_SHIFT 0 +#define DP_GENERIC_INFOFRAME_HEADER 0x65 +#define DP_GENERIC_INFOFRAME_SUBPACK(i) (0x66 + (i)) +#define DP_TPG 0x6d +#define DP_TPG_LANE3_CHANNELCODING_SHIFT 30 +#define DP_TPG_LANE3_CHANNELCODING_DISABLE (0 << 30) +#define DP_TPG_LANE3_CHANNELCODING_ENABLE (1 << 30) +#define DP_TPG_LANE3_SCRAMBLEREN_SHIFT 28 +#define DP_TPG_LANE3_SCRAMBLEREN_ENABLE_GALIOS (1 << 28) +#define DP_TPG_LANE3_SCRAMBLEREN_ENABLE_FIBONACCI (2 << 28) +#define DP_TPG_LANE3_PATTERN_SHIFT 24 +#define DP_TPG_LANE3_PATTERN_DEFAULT_MASK (0xf << 24) +#define DP_TPG_LANE3_PATTERN_NOPATTERN (0 << 24) +#define DP_TPG_LANE3_PATTERN_TRAINING1 (1 << 24) +#define DP_TPG_LANE3_PATTERN_TRAINING2 (2 << 24) +#define DP_TPG_LANE3_PATTERN_TRAINING3 (3 << 24) +#define DP_TPG_LANE3_PATTERN_D102 (4 << 24) +#define DP_TPG_LANE3_PATTERN_SBLERRRATE (5 << 24) +#define DP_TPG_LANE3_PATTERN_PRBS7 (6 << 24) +#define DP_TPG_LANE3_PATTERN_CSTM (7 << 24) +#define DP_TPG_LANE3_PATTERN_HBR2_COMPLIANCE (8 << 24) +#define DP_TPG_LANE2_CHANNELCODING_SHIFT 22 +#define DP_TPG_LANE2_CHANNELCODING_DISABLE (0 << 22) +#define DP_TPG_LANE2_CHANNELCODING_ENABLE (1 << 22) +#define DP_TPG_LANE2_SCRAMBLEREN_SHIFT 20 +#define DP_TPG_LANE2_SCRAMBLEREN_DEFAULT_MASK (3 << 20) +#define DP_TPG_LANE2_SCRAMBLEREN_DISABLE (0 << 20) +#define DP_TPG_LANE2_SCRAMBLEREN_ENABLE_GALIOS (1 << 20) +#define DP_TPG_LANE2_SCRAMBLEREN_ENABLE_FIBONACCI (2 << 20) +#define DP_TPG_LANE2_PATTERN_SHIFT 16 +#define DP_TPG_LANE2_PATTERN_DEFAULT_MASK (0xf << 16) +#define DP_TPG_LANE2_PATTERN_NOPATTERN (0 << 16) +#define DP_TPG_LANE2_PATTERN_TRAINING1 (1 << 16) +#define DP_TPG_LANE2_PATTERN_TRAINING2 (2 << 16) +#define DP_TPG_LANE2_PATTERN_TRAINING3 (3 << 16) +#define DP_TPG_LANE2_PATTERN_D102 (4 << 16) +#define DP_TPG_LANE2_PATTERN_SBLERRRATE (5 << 16) +#define DP_TPG_LANE2_PATTERN_PRBS7 (6 << 16) +#define DP_TPG_LANE2_PATTERN_CSTM (7 << 16) +#define DP_TPG_LANE2_PATTERN_HBR2_COMPLIANCE (8 << 16) +#define DP_TPG_LANE1_CHANNELCODING_SHIFT 14 +#define DP_TPG_LANE1_CHANNELCODING_DISABLE (0 << 14) +#define DP_TPG_LANE1_CHANNELCODING_ENABLE (1 << 14) +#define DP_TPG_LANE1_SCRAMBLEREN_SHIFT 12 +#define DP_TPG_LANE1_SCRAMBLEREN_DEFAULT_MASK (3 << 12) +#define DP_TPG_LANE1_SCRAMBLEREN_DISABLE (0 << 12) +#define DP_TPG_LANE1_SCRAMBLEREN_ENABLE_GALIOS (1 << 12) +#define DP_TPG_LANE1_SCRAMBLEREN_ENABLE_FIBONACCI (2 << 12) +#define DP_TPG_LANE1_PATTERN_SHIFT 8 +#define DP_TPG_LANE1_PATTERN_DEFAULT_MASK (0xf << 8) +#define DP_TPG_LANE1_PATTERN_NOPATTERN (0 << 8) +#define DP_TPG_LANE1_PATTERN_TRAINING1 (1 << 8) +#define DP_TPG_LANE1_PATTERN_TRAINING2 (2 << 8) +#define DP_TPG_LANE1_PATTERN_TRAINING3 (3 << 8) +#define DP_TPG_LANE1_PATTERN_D102 (4 << 8) +#define DP_TPG_LANE1_PATTERN_SBLERRRATE (5 << 8) +#define DP_TPG_LANE1_PATTERN_PRBS7 (6 << 8) +#define DP_TPG_LANE1_PATTERN_CSTM (7 << 8) +#define DP_TPG_LANE1_PATTERN_HBR2_COMPLIANCE (8 << 8) +#define DP_TPG_LANE0_CHANNELCODING_SHIFT 6 +#define DP_TPG_LANE0_CHANNELCODING_DISABLE (0 << 6) +#define DP_TPG_LANE0_CHANNELCODING_ENABLE (1 << 6) +#define DP_TPG_LANE0_SCRAMBLEREN_SHIFT 4 +#define DP_TPG_LANE0_SCRAMBLEREN_DEFAULT_MASK (3 << 4) +#define DP_TPG_LANE0_SCRAMBLEREN_DISABLE (0 << 4) +#define DP_TPG_LANE0_SCRAMBLEREN_ENABLE_GALIOS (1 << 4) +#define DP_TPG_LANE0_SCRAMBLEREN_ENABLE_FIBONACCI (2 << 4) +#define DP_TPG_LANE0_PATTERN_SHIFT 0 +#define DP_TPG_LANE0_PATTERN_DEFAULT_MASK 0xf +#define DP_TPG_LANE0_PATTERN_NOPATTERN 0 +#define DP_TPG_LANE0_PATTERN_TRAINING1 1 +#define DP_TPG_LANE0_PATTERN_TRAINING2 2 +#define DP_TPG_LANE0_PATTERN_TRAINING3 3 +#define DP_TPG_LANE0_PATTERN_D102 4 +#define DP_TPG_LANE0_PATTERN_SBLERRRATE 5 +#define DP_TPG_LANE0_PATTERN_PRBS7 6 +#define DP_TPG_LANE0_PATTERN_CSTM 7 +#define DP_TPG_LANE0_PATTERN_HBR2_COMPLIANCE 8 + +enum { + training_pattern_disabled = 0, + training_pattern_1 = 1, + training_pattern_2 = 2, + training_pattern_3 = 3, + training_pattern_none = 0xff +}; + +enum tegra_dc_sor_protocol { + SOR_DP, + SOR_LVDS, +}; + +#define SOR_LINK_SPEED_G1_62 6 +#define SOR_LINK_SPEED_G2_7 10 +#define SOR_LINK_SPEED_G5_4 20 +#define SOR_LINK_SPEED_LVDS 7 + +struct tegra_dp_link_config { + int is_valid; + + /* Supported configuration */ + u8 max_link_bw; + u8 max_lane_count; + int downspread; + int support_enhanced_framing; + u32 bits_per_pixel; + int alt_scramber_reset_cap; /* true for eDP */ + int only_enhanced_framing; /* enhanced_frame_en ignored */ + int frame_in_ms; + + /* Actual configuration */ + u8 link_bw; + u8 lane_count; + int enhanced_framing; + int scramble_ena; + + u32 activepolarity; + u32 active_count; + u32 tu_size; + u32 active_frac; + u32 watermark; + + s32 hblank_sym; + s32 vblank_sym; + + /* Training data */ + u32 drive_current; + u32 preemphasis; + u32 postcursor; + u8 aux_rd_interval; + u8 tps3_supported; +}; + +#define TEGRA_SOR_TIMEOUT_MS 1000 +#define TEGRA_SOR_ATTACH_TIMEOUT_MS 1000 + +int tegra_dc_sor_enable_dp(struct udevice *sor, + const struct tegra_dp_link_config *link_cfg); +int tegra_dc_sor_set_power_state(struct udevice *sor, int pu_pd); +void tegra_dc_sor_set_dp_linkctl(struct udevice *dev, int ena, + u8 training_pattern, const struct tegra_dp_link_config *link_cfg); +void tegra_dc_sor_set_link_bandwidth(struct udevice *dev, u8 link_bw); +void tegra_dc_sor_set_lane_count(struct udevice *dev, u8 lane_count); +void tegra_dc_sor_set_panel_power(struct udevice *sor, + int power_up); +void tegra_dc_sor_set_internal_panel(struct udevice *dev, int is_int); +void tegra_dc_sor_read_link_config(struct udevice *dev, u8 *link_bw, + u8 *lane_count); +void tegra_dc_sor_set_lane_parm(struct udevice *dev, + const struct tegra_dp_link_config *link_cfg); +void tegra_dc_sor_power_down_unused_lanes(struct udevice *sor, + const struct tegra_dp_link_config *link_cfg); +int tegra_dc_sor_set_voltage_swing(struct udevice *sor, + const struct tegra_dp_link_config *link_cfg); +int tegra_sor_precharge_lanes(struct udevice *dev, + const struct tegra_dp_link_config *cfg); +void tegra_dp_disable_tx_pu(struct udevice *sor); +void tegra_dp_set_pe_vs_pc(struct udevice *dev, u32 mask, u32 pe_reg, + u32 vs_reg, u32 pc_reg, u8 pc_supported); + +int tegra_dc_sor_attach(struct udevice *dc_dev, struct udevice *sor, + const struct tegra_dp_link_config *link_cfg, + const struct display_timing *timing); +int tegra_dc_sor_detach(struct udevice *dc_dev, struct udevice *sor); + +void tegra_dc_sor_disable_win_short_raster(struct dc_ctlr *disp_ctrl, + int *dc_reg_ctx); +int tegra_dc_sor_general_act(struct dc_ctlr *disp_ctrl); +void tegra_dc_sor_restore_win_and_raster(struct dc_ctlr *disp_ctrl, + int *dc_reg_ctx); + +int tegra_dc_sor_init(struct udevice **sorp); +#endif |