]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
video: tegra20: add DSI controller driver
authorSvyatoslav Ryhel <clamor95@gmail.com>
Mon, 27 Mar 2023 08:11:48 +0000 (11:11 +0300)
committerAnatolij Gustschin <agust@denx.de>
Fri, 7 Apr 2023 17:52:54 +0000 (19:52 +0200)
Adds support for both DSI outputs found on Tegra. Only very
minimal functionality is implemented, so advanced features
like ganged mode won't work. Driver is heavily based on
mainline Tegra DSI and re-uses much of its features.

Only T30 is supported for now but T20 support can be added
if any supported devices will be found.

Driver is wrapped as panel driver since Tegra DC driver supports
only panel drivers calls.

Tested-by: Andreas Westman Dorcsak <hedmoo@yahoo.com> # ASUS TF600T T30
Tested-by: Svyatoslav Ryhel <clamor95@gmail.com> # HTC One X T30
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
arch/arm/include/asm/arch-tegra30/dsi.h [new file with mode: 0644]
drivers/video/tegra20/Kconfig
drivers/video/tegra20/Makefile
drivers/video/tegra20/mipi-phy.c [new file with mode: 0644]
drivers/video/tegra20/mipi-phy.h [new file with mode: 0644]
drivers/video/tegra20/tegra-dsi.c [new file with mode: 0644]

diff --git a/arch/arm/include/asm/arch-tegra30/dsi.h b/arch/arm/include/asm/arch-tegra30/dsi.h
new file mode 100644 (file)
index 0000000..7ade132
--- /dev/null
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ *  (C) Copyright 2010
+ *  NVIDIA Corporation <www.nvidia.com>
+ */
+
+#ifndef __ASM_ARCH_TEGRA_DSI_H
+#define __ASM_ARCH_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 ~ 0x04e */
+struct dsi_pad_ctrl_reg {
+       /* Address 0x04b ~ 0x04e */
+       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 */
+};
+
+/* 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 */
+};
+
+#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)
+
+/*
+ * 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 /* __ASM_ARCH_TEGRA_DSI_H */
index 2a4036b8988d64e1c3d01b76a30c29f28adbe439..5b1dfbfbbed5bca0ea95ddd241112b207a732f33 100644 (file)
@@ -6,3 +6,12 @@ config VIDEO_TEGRA20
           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 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.
index 4517923025fc17f04f2344f74fb9c855c623169a..e82ee96962fa4a1338450945817412623c537935 100644 (file)
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0+
 
 obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o
+obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o mipi-phy.o
diff --git a/drivers/video/tegra20/mipi-phy.c b/drivers/video/tegra20/mipi-phy.c
new file mode 100644 (file)
index 0000000..c3ebc40
--- /dev/null
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ */
+
+#include <common.h>
+#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/tegra20/mipi-phy.h b/drivers/video/tegra20/mipi-phy.h
new file mode 100644 (file)
index 0000000..41889a7
--- /dev/null
@@ -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/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c
new file mode 100644 (file)
index 0000000..8c3404e
--- /dev/null
@@ -0,0 +1,864 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2022 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <misc.h>
+#include <mipi_display.h>
+#include <mipi_dsi.h>
+#include <backlight.h>
+#include <panel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <power/regulator.h>
+
+#include <asm/gpio.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/display.h>
+#include <asm/arch-tegra30/dsi.h>
+
+#include "mipi-phy.h"
+
+#define USEC_PER_SEC   1000000L
+#define NSEC_PER_SEC   1000000000L
+
+struct tegra_dsi_priv {
+       struct mipi_dsi_host host;
+       struct mipi_dsi_device device;
+       struct mipi_dphy_timing dphy_timing;
+
+       struct udevice *panel;
+       struct display_timing timing;
+
+       struct dsi_ctlr *dsi;
+       struct udevice *avdd;
+
+       enum tegra_dsi_format format;
+
+       int dsi_clk;
+       int video_fifo_depth;
+       int host_fifo_depth;
+};
+
+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;
+
+       /*
+        * 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_set_timeout(struct dsi_timeout_reg *rtimeout,
+                                 unsigned long bclk,
+                                 unsigned int vrefresh)
+{
+       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);
+}
+
+static void tegra_dsi_set_phy_timing(struct dsi_timing_reg *ptiming,
+                                    unsigned long period,
+                                    const struct mipi_dphy_timing *dphy_timing)
+{
+       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);
+}
+
+static void tegra_dsi_configure(struct udevice *dev,
+                               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(0);
+       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;
+
+               if ((mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) == 0)
+                       hbp += hsw;
+
+               /* horizontal front porch */
+               hfp = timing->hfront_porch.typ * mul / div;
+
+               /* 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 {
+               /* 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 (for non-burst mode only) */
+       writel(8 * mul / div, &misc->dsi_sol_delay);
+}
+
+static int tegra_dsi_encoder_enable(struct udevice *dev)
+{
+       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;
+       unsigned int mul, div;
+       unsigned long bclk, plld, period;
+       u32 value;
+       int ret;
+
+       /* Disable interrupt */
+       writel(0, &misc->int_enable);
+
+       tegra_dsi_pad_calibrate(&priv->dsi->pad);
+
+       tegra_dsi_get_muldiv(device->format, &mul, &div);
+
+       /* compute byte clock */
+       bclk = (timing->pixelclock.typ * mul) / (div * device->lanes);
+
+       tegra_dsi_set_timeout(&priv->dsi->timeout, 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(&priv->dsi->ptiming,
+                                period * 8, &priv->dphy_timing);
+
+       /* Perform panel HW setup */
+       ret = panel_enable_backlight(priv->panel);
+       if (ret)
+               return ret;
+
+       tegra_dsi_configure(dev, 0);
+
+       ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
+       if (ret)
+               return ret;
+
+       tegra_dsi_configure(dev, device->mode_flags);
+
+       tegra_dc_enable_controller(dev);
+
+       /* enable DSI controller */
+       value = readl(&misc->dsi_pwr_ctrl);
+       value |= DSI_POWER_CONTROL_ENABLE;
+       writel(value, &misc->dsi_pwr_ctrl);
+
+       return 0;
+}
+
+static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent)
+{
+       /* Is not used in tegra dc */
+       return 0;
+}
+
+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 mipi_dsi_device *device = &priv->device;
+       unsigned int mul, div;
+       unsigned long bclk, plld;
+
+       tegra_dsi_get_muldiv(device->format, &mul, &div);
+
+       bclk = (priv->timing.pixelclock.typ * mul) /
+                                       (div * device->lanes);
+
+       plld = DIV_ROUND_UP(bclk * 8, 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(CLOCK_ID_DISPLAY, plld, 12, 0, 8);
+               break;
+
+       case CLOCK_OSC_FREQ_26_0: /* OSC is 26Mhz */
+               clock_set_rate(CLOCK_ID_DISPLAY, 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(CLOCK_ID_DISPLAY, plld, 13, 0, 8);
+               break;
+
+       case CLOCK_OSC_FREQ_19_2:
+       case CLOCK_OSC_FREQ_38_4:
+       default:
+               /*
+                * These are not supported.
+                */
+               break;
+       }
+
+       priv->dsi_clk = clock_decode_periph_id(dev);
+
+       clock_enable(priv->dsi_clk);
+       udelay(2);
+       reset_set_enable(priv->dsi_clk, 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;
+       int ret;
+
+       priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev);
+       if (!priv->dsi) {
+               printf("%s: No display controller address\n", __func__);
+               return -EINVAL;
+       }
+
+       priv->video_fifo_depth = 480;
+       priv->host_fifo_depth = 64;
+
+       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);
+
+       ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev,
+                                          "panel", &priv->panel);
+       if (ret) {
+               printf("%s: Cannot get panel: error %d\n", __func__, ret);
+               return log_ret(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);
+
+       if (priv->avdd) {
+               ret = regulator_set_enable(priv->avdd, true);
+               if (ret)
+                       return ret;
+       }
+
+       tegra_dsi_init_clocks(dev);
+
+       return 0;
+}
+
+static const struct panel_ops tegra_dsi_bridge_ops = {
+       .enable_backlight       = 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,tegra30-dsi" },
+       { }
+};
+
+U_BOOT_DRIVER(tegra_dsi) = {
+       .name           = "tegra_dsi",
+       .id             = UCLASS_PANEL,
+       .of_match       = tegra_dsi_bridge_ids,
+       .ops            = &tegra_dsi_bridge_ops,
+       .probe          = tegra_dsi_bridge_probe,
+       .plat_auto      = sizeof(struct tegra_dc_plat),
+       .priv_auto      = sizeof(struct tegra_dsi_priv),
+};