summaryrefslogtreecommitdiff
path: root/drivers/video/tegra
diff options
context:
space:
mode:
authorSvyatoslav Ryhel <[email protected]>2025-03-29 16:49:53 +0200
committerSvyatoslav Ryhel <[email protected]>2025-04-12 09:42:36 +0300
commit901f249fdb6b0ab287ac23dbdcada44c4c697dca (patch)
treeba682147bb768803a3a18abaf724ccc143ec52d6 /drivers/video/tegra
parente69eeffb59c9cc338dcc518450f370aebe5a7374 (diff)
video: rename tegra20 to tegra
Since this set of drivers suports four Tegra SoC generations, lets name it just 'tegra'. Signed-off-by: Svyatoslav Ryhel <[email protected]> Reviewed-by: Peter Robinson <[email protected]>
Diffstat (limited to 'drivers/video/tegra')
-rw-r--r--drivers/video/tegra/Kconfig38
-rw-r--r--drivers/video/tegra/Makefile7
-rw-r--r--drivers/video/tegra/mipi-phy.c133
-rw-r--r--drivers/video/tegra/mipi-phy.h48
-rw-r--r--drivers/video/tegra/tegra-dc.c682
-rw-r--r--drivers/video/tegra/tegra-dc.h40
-rw-r--r--drivers/video/tegra/tegra-dsi.c1148
-rw-r--r--drivers/video/tegra/tegra-dsi.h246
-rw-r--r--drivers/video/tegra/tegra-hdmi.c623
-rw-r--r--drivers/video/tegra/tegra-hdmi.h648
-rw-r--r--drivers/video/tegra/tegra-host1x.c86
-rw-r--r--drivers/video/tegra/tegra-mipi.c294
-rw-r--r--drivers/video/tegra/tegra-pwm-backlight.c150
13 files changed, 4143 insertions, 0 deletions
diff --git a/drivers/video/tegra/Kconfig b/drivers/video/tegra/Kconfig
new file mode 100644
index 00000000000..598f9ea1f21
--- /dev/null
+++ b/drivers/video/tegra/Kconfig
@@ -0,0 +1,38 @@
+config HOST1X_TEGRA
+ bool "NVIDIA Tegra host1x BUS support"
+ depends on SIMPLE_BUS
+
+config VIDEO_TEGRA20
+ bool "Enable Display Controller support on Tegra20 and Tegra 30"
+ depends on OF_CONTROL
+ select HOST1X_TEGRA
+ help
+ T20/T30 support video output to an attached LCD panel as well as
+ other options such as HDMI. Only the LCD is supported in U-Boot.
+ This option enables this support which can be used on devices which
+ have an LCD display connected.
+
+config VIDEO_DSI_TEGRA30
+ bool "Enable Tegra 30 DSI support"
+ depends on VIDEO_BRIDGE && PANEL && DM_GPIO
+ select VIDEO_TEGRA20
+ select VIDEO_MIPI_DSI
+ help
+ T30 has native support for DSI panels. This option enables support
+ for such panels which can be used on endeavoru and tf600t.
+
+config VIDEO_HDMI_TEGRA
+ bool "Enable Tegra HDMI support"
+ depends on VIDEO_BRIDGE && DM_I2C
+ select I2C_EDID
+ select VIDEO_TEGRA20
+ help
+ Tegra has native support for HDMI. This option enables support
+ for such connection and can be used for any supported device.
+
+config TEGRA_BACKLIGHT_PWM
+ bool "Enable Tegra DC PWM backlight support"
+ depends on BACKLIGHT
+ select VIDEO_TEGRA20
+ help
+ Tegra DC dependent backlight.
diff --git a/drivers/video/tegra/Makefile b/drivers/video/tegra/Makefile
new file mode 100644
index 00000000000..78521405749
--- /dev/null
+++ b/drivers/video/tegra/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-$(CONFIG_HOST1X_TEGRA) += tegra-host1x.o
+obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o
+obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o tegra-mipi.o mipi-phy.o
+obj-$(CONFIG_VIDEO_HDMI_TEGRA) += tegra-hdmi.o
+obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += tegra-pwm-backlight.o
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/tegra-dc.c b/drivers/video/tegra/tegra-dc.c
new file mode 100644
index 00000000000..1f43153ff27
--- /dev/null
+++ b/drivers/video/tegra/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 <[email protected]>
+ */
+
+#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 "tegra-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/tegra-dc.h b/drivers/video/tegra/tegra-dc.h
new file mode 100644
index 00000000000..2a4013b3355
--- /dev/null
+++ b/drivers/video/tegra/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/tegra-dsi.c b/drivers/video/tegra/tegra-dsi.c
new file mode 100644
index 00000000000..a96fba01ee4
--- /dev/null
+++ b/drivers/video/tegra/tegra-dsi.c
@@ -0,0 +1,1148 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2022 Svyatoslav Ryhel <[email protected]>
+ */
+
+#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 "tegra-dc.h"
+#include "tegra-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/tegra-dsi.h b/drivers/video/tegra/tegra-dsi.h
new file mode 100644
index 00000000000..683c5e31a34
--- /dev/null
+++ b/drivers/video/tegra/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/tegra-hdmi.c b/drivers/video/tegra/tegra-hdmi.c
new file mode 100644
index 00000000000..bda69919d92
--- /dev/null
+++ b/drivers/video/tegra/tegra-hdmi.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2023 Svyatoslav Ryhel <[email protected]>
+ */
+
+#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 "tegra-dc.h"
+#include "tegra-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/tegra-hdmi.h b/drivers/video/tegra/tegra-hdmi.h
new file mode 100644
index 00000000000..d17655973e3
--- /dev/null
+++ b/drivers/video/tegra/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/tegra-host1x.c b/drivers/video/tegra/tegra-host1x.c
new file mode 100644
index 00000000000..58ab871a3b4
--- /dev/null
+++ b/drivers/video/tegra/tegra-host1x.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2025 Svyatoslav Ryhel <[email protected]>
+ */
+
+#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/tegra-mipi.c b/drivers/video/tegra/tegra-mipi.c
new file mode 100644
index 00000000000..a4f4343d008
--- /dev/null
+++ b/drivers/video/tegra/tegra-mipi.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2023 Svyatoslav Ryhel <[email protected]>
+ */
+
+#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/tegra-pwm-backlight.c b/drivers/video/tegra/tegra-pwm-backlight.c
new file mode 100644
index 00000000000..998f0df1991
--- /dev/null
+++ b/drivers/video/tegra/tegra-pwm-backlight.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2022 Svyatoslav Ryhel <[email protected]>
+ */
+
+#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 "tegra-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),
+};