drm/tegra: dsi: Add ganged mode support
[cascardo/linux.git] / drivers / gpu / drm / tegra / dsi.c
index 584b771..6681610 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/host1x.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/reset.h>
 
@@ -54,6 +55,10 @@ struct tegra_dsi {
 
        unsigned int video_fifo_depth;
        unsigned int host_fifo_depth;
+
+       /* for ganged-mode support */
+       struct tegra_dsi *master;
+       struct tegra_dsi *slave;
 };
 
 static inline struct tegra_dsi *
@@ -318,6 +323,21 @@ static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = {
        [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 int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
 {
        struct mipi_dphy_timing timing;
@@ -426,26 +446,59 @@ static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format,
        return 0;
 }
 
-static int tegra_output_dsi_enable(struct tegra_output *output)
+static void tegra_dsi_ganged_enable(struct tegra_dsi *dsi, unsigned int start,
+                                   unsigned int size)
+{
+       u32 value;
+
+       tegra_dsi_writel(dsi, start, DSI_GANGED_MODE_START);
+       tegra_dsi_writel(dsi, size << 16 | size, DSI_GANGED_MODE_SIZE);
+
+       value = DSI_GANGED_MODE_CONTROL_ENABLE;
+       tegra_dsi_writel(dsi, value, DSI_GANGED_MODE_CONTROL);
+}
+
+static void tegra_dsi_enable(struct tegra_dsi *dsi)
+{
+       u32 value;
+
+       value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
+       value |= DSI_POWER_CONTROL_ENABLE;
+       tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+
+       if (dsi->slave)
+               tegra_dsi_enable(dsi->slave);
+}
+
+static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi)
+{
+       if (dsi->master)
+               return dsi->master->lanes + dsi->lanes;
+
+       if (dsi->slave)
+               return dsi->lanes + dsi->slave->lanes;
+
+       return dsi->lanes;
+}
+
+static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe,
+                              const struct drm_display_mode *mode)
 {
-       struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
-       struct drm_display_mode *mode = &dc->base.mode;
        unsigned int hact, hsw, hbp, hfp, i, mul, div;
-       struct tegra_dsi *dsi = to_dsi(output);
        enum tegra_dsi_format format;
-       unsigned long value;
        const u32 *pkt_seq;
+       u32 value;
        int err;
 
-       if (dsi->enabled)
-               return 0;
-
        if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
                DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n");
                pkt_seq = pkt_seq_video_non_burst_sync_pulses;
-       } else {
+       } else if (dsi->flags & MIPI_DSI_MODE_VIDEO) {
                DRM_DEBUG_KMS("Non-burst video mode with sync events\n");
                pkt_seq = pkt_seq_video_non_burst_sync_events;
+       } else {
+               DRM_DEBUG_KMS("Command mode\n");
+               pkt_seq = pkt_seq_command_mode;
        }
 
        err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
@@ -456,28 +509,29 @@ static int tegra_output_dsi_enable(struct tegra_output *output)
        if (err < 0)
                return err;
 
-       err = clk_enable(dsi->clk);
-       if (err < 0)
-               return err;
-
-       reset_control_deassert(dsi->rst);
-
        value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) |
                DSI_CONTROL_LANES(dsi->lanes - 1) |
-               DSI_CONTROL_SOURCE(dc->pipe);
+               DSI_CONTROL_SOURCE(pipe);
        tegra_dsi_writel(dsi, value, DSI_CONTROL);
 
        tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD);
 
-       value = DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS |
-               DSI_HOST_CONTROL_ECC;
+       value = DSI_HOST_CONTROL_HS;
        tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
 
        value = tegra_dsi_readl(dsi, DSI_CONTROL);
+
        if (dsi->flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
                value |= DSI_CONTROL_HS_CLK_CTRL;
+
        value &= ~DSI_CONTROL_TX_TRIG(3);
-       value &= ~DSI_CONTROL_DCS_ENABLE;
+
+       /* enable DCS commands for command mode */
+       if (dsi->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;
        tegra_dsi_writel(dsi, value, DSI_CONTROL);
@@ -489,28 +543,106 @@ static int tegra_output_dsi_enable(struct tegra_output *output)
        for (i = 0; i < NUM_PKT_SEQ; i++)
                tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i);
 
-       /* horizontal active pixels */
-       hact = mode->hdisplay * mul / div;
+       if (dsi->flags & MIPI_DSI_MODE_VIDEO) {
+               /* horizontal active pixels */
+               hact = mode->hdisplay * mul / div;
+
+               /* horizontal sync width */
+               hsw = (mode->hsync_end - mode->hsync_start) * mul / div;
+               hsw -= 10;
+
+               /* horizontal back porch */
+               hbp = (mode->htotal - mode->hsync_end) * mul / div;
+               hbp -= 14;
+
+               /* horizontal front porch */
+               hfp = (mode->hsync_start - mode->hdisplay) * mul / div;
+               hfp -= 8;
 
-       /* horizontal sync width */
-       hsw = (mode->hsync_end - mode->hsync_start) * mul / div;
-       hsw -= 10;
+               tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1);
+               tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3);
+               tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5);
+               tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7);
 
-       /* horizontal back porch */
-       hbp = (mode->htotal - mode->hsync_end) * mul / div;
-       hbp -= 14;
+               /* set SOL delay (for non-burst mode only) */
+               tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY);
+
+               /* TODO: implement ganged mode */
+       } else {
+               u16 bytes;
+
+               if (dsi->master || dsi->slave) {
+                       /*
+                        * For ganged mode, assume symmetric left-right mode.
+                        */
+                       bytes = 1 + (mode->hdisplay / 2) * mul / div;
+               } else {
+                       /* 1 byte (DCS command) + pixel data */
+                       bytes = 1 + mode->hdisplay * mul / div;
+               }
+
+               tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1);
+               tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3);
+               tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_4_5);
+               tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_6_7);
+
+               value = MIPI_DCS_WRITE_MEMORY_START << 8 |
+                       MIPI_DCS_WRITE_MEMORY_CONTINUE;
+               tegra_dsi_writel(dsi, value, DSI_DCS_CMDS);
+
+               /* set SOL delay */
+               if (dsi->master || dsi->slave) {
+                       unsigned int lanes = tegra_dsi_get_lanes(dsi);
+                       unsigned long delay, bclk, bclk_ganged;
+
+                       /* 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(mode->htotal * mul, div * lanes);
+                       bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes);
+                       value = bclk - bclk_ganged + delay + 20;
+               } else {
+                       /* TODO: revisit for non-ganged mode */
+                       value = 8 * mul / div;
+               }
+
+               tegra_dsi_writel(dsi, value, DSI_SOL_DELAY);
+       }
+
+       if (dsi->slave) {
+               err = tegra_dsi_configure(dsi->slave, pipe, mode);
+               if (err < 0)
+                       return err;
+
+               /*
+                * TODO: Support modes other than symmetrical left-right
+                * split.
+                */
+               tegra_dsi_ganged_enable(dsi, 0, mode->hdisplay / 2);
+               tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2,
+                                       mode->hdisplay / 2);
+       }
+
+       return 0;
+}
 
-       /* horizontal front porch */
-       hfp = (mode->hsync_start  - mode->hdisplay) * mul / div;
-       hfp -= 8;
+static int tegra_output_dsi_enable(struct tegra_output *output)
+{
+       struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
+       const struct drm_display_mode *mode = &dc->base.mode;
+       struct tegra_dsi *dsi = to_dsi(output);
+       u32 value;
+       int err;
 
-       tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1);
-       tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3);
-       tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5);
-       tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7);
+       if (dsi->enabled)
+               return 0;
 
-       /* set SOL delay */
-       tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY);
+       err = tegra_dsi_configure(dsi, dc->pipe, mode);
+       if (err < 0)
+               return err;
 
        /* enable display controller */
        value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
@@ -531,28 +663,79 @@ static int tegra_output_dsi_enable(struct tegra_output *output)
        tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
 
        /* enable DSI controller */
-       value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
-       value |= DSI_POWER_CONTROL_ENABLE;
-       tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+       tegra_dsi_enable(dsi);
 
        dsi->enabled = true;
 
        return 0;
 }
 
+static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout)
+{
+       u32 value;
+
+       timeout = jiffies + msecs_to_jiffies(timeout);
+
+       while (time_before(jiffies, timeout)) {
+               value = tegra_dsi_readl(dsi, DSI_STATUS);
+               if (value & DSI_STATUS_IDLE)
+                       return 0;
+
+               usleep_range(1000, 2000);
+       }
+
+       return -ETIMEDOUT;
+}
+
+static void tegra_dsi_video_disable(struct tegra_dsi *dsi)
+{
+       u32 value;
+
+       value = tegra_dsi_readl(dsi, DSI_CONTROL);
+       value &= ~DSI_CONTROL_VIDEO_ENABLE;
+       tegra_dsi_writel(dsi, value, DSI_CONTROL);
+
+       if (dsi->slave)
+               tegra_dsi_video_disable(dsi->slave);
+}
+
+static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
+{
+       tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START);
+       tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE);
+       tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
+}
+
+static void tegra_dsi_disable(struct tegra_dsi *dsi)
+{
+       u32 value;
+
+       if (dsi->slave) {
+               tegra_dsi_ganged_disable(dsi->slave);
+               tegra_dsi_ganged_disable(dsi);
+       }
+
+       value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
+       value &= ~DSI_POWER_CONTROL_ENABLE;
+       tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+
+       if (dsi->slave)
+               tegra_dsi_disable(dsi->slave);
+
+       usleep_range(5000, 10000);
+}
+
 static int tegra_output_dsi_disable(struct tegra_output *output)
 {
        struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
        struct tegra_dsi *dsi = to_dsi(output);
        unsigned long value;
+       int err;
 
        if (!dsi->enabled)
                return 0;
 
-       /* disable DSI controller */
-       value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
-       value &= ~DSI_POWER_CONTROL_ENABLE;
-       tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+       tegra_dsi_video_disable(dsi);
 
        /*
         * The following accesses registers of the display controller, so make
@@ -576,34 +759,63 @@ static int tegra_output_dsi_disable(struct tegra_output *output)
                tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
        }
 
-       clk_disable(dsi->clk);
+       err = tegra_dsi_wait_idle(dsi, 100);
+       if (err < 0)
+               dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err);
+
+       tegra_dsi_disable(dsi);
 
        dsi->enabled = false;
 
        return 0;
 }
 
+static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, 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);
+       tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
+
+       /* 2 ms peripheral timeout for panel */
+       timeout = 2 * bclk / 512 * 1000;
+       value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
+       tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
+
+       value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
+       tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
+
+       if (dsi->slave)
+               tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh);
+}
+
 static int tegra_output_dsi_setup_clock(struct tegra_output *output,
                                        struct clk *clk, unsigned long pclk,
                                        unsigned int *divp)
 {
        struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
        struct drm_display_mode *mode = &dc->base.mode;
-       unsigned int timeout, mul, div, vrefresh;
        struct tegra_dsi *dsi = to_dsi(output);
-       unsigned long bclk, plld, value;
+       unsigned int mul, div, vrefresh, lanes;
+       unsigned long bclk, plld;
        int err;
 
+       lanes = tegra_dsi_get_lanes(dsi);
+
        err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
        if (err < 0)
                return err;
 
-       DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, dsi->lanes);
+       DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, lanes);
        vrefresh = drm_mode_vrefresh(mode);
        DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh);
 
        /* compute byte clock */
-       bclk = (pclk * mul) / (div * dsi->lanes);
+       bclk = (pclk * mul) / (div * lanes);
 
        /*
         * Compute bit clock and round up to the next MHz.
@@ -640,25 +852,13 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output,
         * not working properly otherwise. Perhaps the PLLs cannot generate
         * frequencies sufficiently high.
         */
-       *divp = ((8 * mul) / (div * dsi->lanes)) - 2;
+       *divp = ((8 * mul) / (div * lanes)) - 2;
 
        /*
         * XXX: Move the below somewhere else so that we don't need to have
         * access to the vrefresh in this function?
         */
-
-       /* one frame high-speed transmission timeout */
-       timeout = (bclk / vrefresh) / 512;
-       value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
-       tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
-
-       /* 2 ms peripheral timeout for panel */
-       timeout = 2 * bclk / 512 * 1000;
-       value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
-       tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
-
-       value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
-       tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
+       tegra_dsi_set_timeout(dsi, bclk, vrefresh);
 
        return 0;
 }
@@ -695,7 +895,7 @@ static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
 
 static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
 {
-       unsigned long value;
+       u32 value;
 
        tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
        tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
@@ -720,14 +920,17 @@ static int tegra_dsi_init(struct host1x_client *client)
        struct tegra_dsi *dsi = host1x_client_to_dsi(client);
        int err;
 
-       dsi->output.type = TEGRA_OUTPUT_DSI;
-       dsi->output.dev = client->dev;
-       dsi->output.ops = &dsi_ops;
-
-       err = tegra_output_init(drm, &dsi->output);
-       if (err < 0) {
-               dev_err(client->dev, "output setup failed: %d\n", err);
-               return err;
+       /* Gangsters must not register their own outputs. */
+       if (!dsi->master) {
+               dsi->output.type = TEGRA_OUTPUT_DSI;
+               dsi->output.dev = client->dev;
+               dsi->output.ops = &dsi_ops;
+
+               err = tegra_output_init(drm, &dsi->output);
+               if (err < 0) {
+                       dev_err(client->dev, "output setup failed: %d\n", err);
+                       return err;
+               }
        }
 
        if (IS_ENABLED(CONFIG_DEBUG_FS)) {
@@ -736,12 +939,6 @@ static int tegra_dsi_init(struct host1x_client *client)
                        dev_err(dsi->dev, "debugfs setup failed: %d\n", err);
        }
 
-       err = tegra_dsi_pad_calibrate(dsi);
-       if (err < 0) {
-               dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
-               return err;
-       }
-
        return 0;
 }
 
@@ -756,16 +953,20 @@ static int tegra_dsi_exit(struct host1x_client *client)
                        dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err);
        }
 
-       err = tegra_output_disable(&dsi->output);
-       if (err < 0) {
-               dev_err(client->dev, "output failed to disable: %d\n", err);
-               return err;
-       }
-
-       err = tegra_output_exit(&dsi->output);
-       if (err < 0) {
-               dev_err(client->dev, "output cleanup failed: %d\n", err);
-               return err;
+       if (!dsi->master) {
+               err = tegra_output_disable(&dsi->output);
+               if (err < 0) {
+                       dev_err(client->dev, "output failed to disable: %d\n",
+                               err);
+                       return err;
+               }
+
+               err = tegra_output_exit(&dsi->output);
+               if (err < 0) {
+                       dev_err(client->dev, "output cleanup failed: %d\n",
+                               err);
+                       return err;
+               }
        }
 
        return 0;
@@ -792,20 +993,58 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi)
        return 0;
 }
 
+static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi)
+{
+       struct clk *parent;
+       int err;
+
+       /* make sure both DSI controllers share the same PLL */
+       parent = clk_get_parent(dsi->slave->clk);
+       if (!parent)
+               return -EINVAL;
+
+       err = clk_set_parent(parent, dsi->clk_parent);
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
 static int tegra_dsi_host_attach(struct mipi_dsi_host *host,
                                 struct mipi_dsi_device *device)
 {
        struct tegra_dsi *dsi = host_to_tegra(host);
-       struct tegra_output *output = &dsi->output;
 
        dsi->flags = device->mode_flags;
        dsi->format = device->format;
        dsi->lanes = device->lanes;
 
-       output->panel = of_drm_find_panel(device->dev.of_node);
-       if (output->panel) {
-               if (output->connector.dev)
+       if (dsi->slave) {
+               int err;
+
+               dev_dbg(dsi->dev, "attaching dual-channel device %s\n",
+                       dev_name(&device->dev));
+
+               err = tegra_dsi_ganged_setup(dsi);
+               if (err < 0) {
+                       dev_err(dsi->dev, "failed to set up ganged mode: %d\n",
+                               err);
+                       return err;
+               }
+       }
+
+       /*
+        * Slaves don't have a panel associated with them, so they provide
+        * merely the second channel.
+        */
+       if (!dsi->master) {
+               struct tegra_output *output = &dsi->output;
+
+               output->panel = of_drm_find_panel(device->dev.of_node);
+               if (output->panel && output->connector.dev) {
+                       drm_panel_attach(output->panel, &output->connector);
                        drm_helper_hpd_irq_event(output->connector.dev);
+               }
        }
 
        return 0;
@@ -818,10 +1057,10 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host,
        struct tegra_output *output = &dsi->output;
 
        if (output->panel && &device->dev == output->panel->dev) {
+               output->panel = NULL;
+
                if (output->connector.dev)
                        drm_helper_hpd_irq_event(output->connector.dev);
-
-               output->panel = NULL;
        }
 
        return 0;
@@ -832,6 +1071,26 @@ static const struct mipi_dsi_host_ops tegra_dsi_host_ops = {
        .detach = tegra_dsi_host_detach,
 };
 
+static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi)
+{
+       struct device_node *np;
+
+       np = of_parse_phandle(dsi->dev->of_node, "nvidia,ganged-mode", 0);
+       if (np) {
+               struct platform_device *gangster = of_find_device_by_node(np);
+
+               dsi->slave = platform_get_drvdata(gangster);
+               of_node_put(np);
+
+               if (!dsi->slave)
+                       return -EPROBE_DEFER;
+
+               dsi->slave->master = dsi;
+       }
+
+       return 0;
+}
+
 static int tegra_dsi_probe(struct platform_device *pdev)
 {
        struct tegra_dsi *dsi;
@@ -846,10 +1105,16 @@ static int tegra_dsi_probe(struct platform_device *pdev)
        dsi->video_fifo_depth = 1920;
        dsi->host_fifo_depth = 64;
 
+       err = tegra_dsi_ganged_probe(dsi);
+       if (err < 0)
+               return err;
+
        err = tegra_output_probe(&dsi->output);
        if (err < 0)
                return err;
 
+       dsi->output.connector.polled = DRM_CONNECTOR_POLL_HPD;
+
        /*
         * Assume these values by default. When a DSI peripheral driver
         * attaches to the DSI host, the parameters will be taken from
@@ -863,68 +1128,83 @@ static int tegra_dsi_probe(struct platform_device *pdev)
        if (IS_ERR(dsi->rst))
                return PTR_ERR(dsi->rst);
 
+       err = reset_control_deassert(dsi->rst);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed to bring DSI out of reset: %d\n",
+                       err);
+               return err;
+       }
+
        dsi->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(dsi->clk)) {
                dev_err(&pdev->dev, "cannot get DSI clock\n");
-               return PTR_ERR(dsi->clk);
+               err = PTR_ERR(dsi->clk);
+               goto reset;
        }
 
        err = clk_prepare_enable(dsi->clk);
        if (err < 0) {
                dev_err(&pdev->dev, "cannot enable DSI clock\n");
-               return err;
+               goto reset;
        }
 
        dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
        if (IS_ERR(dsi->clk_lp)) {
                dev_err(&pdev->dev, "cannot get low-power clock\n");
-               return PTR_ERR(dsi->clk_lp);
+               err = PTR_ERR(dsi->clk_lp);
+               goto disable_clk;
        }
 
        err = clk_prepare_enable(dsi->clk_lp);
        if (err < 0) {
                dev_err(&pdev->dev, "cannot enable low-power clock\n");
-               return err;
+               goto disable_clk;
        }
 
        dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
        if (IS_ERR(dsi->clk_parent)) {
                dev_err(&pdev->dev, "cannot get parent clock\n");
-               return PTR_ERR(dsi->clk_parent);
-       }
-
-       err = clk_prepare_enable(dsi->clk_parent);
-       if (err < 0) {
-               dev_err(&pdev->dev, "cannot enable parent clock\n");
-               return err;
+               err = PTR_ERR(dsi->clk_parent);
+               goto disable_clk_lp;
        }
 
        dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
        if (IS_ERR(dsi->vdd)) {
                dev_err(&pdev->dev, "cannot get VDD supply\n");
-               return PTR_ERR(dsi->vdd);
+               err = PTR_ERR(dsi->vdd);
+               goto disable_clk_lp;
        }
 
        err = regulator_enable(dsi->vdd);
        if (err < 0) {
                dev_err(&pdev->dev, "cannot enable VDD supply\n");
-               return err;
+               goto disable_clk_lp;
        }
 
        err = tegra_dsi_setup_clocks(dsi);
        if (err < 0) {
                dev_err(&pdev->dev, "cannot setup clocks\n");
-               return err;
+               goto disable_vdd;
        }
 
        regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        dsi->regs = devm_ioremap_resource(&pdev->dev, regs);
-       if (IS_ERR(dsi->regs))
-               return PTR_ERR(dsi->regs);
+       if (IS_ERR(dsi->regs)) {
+               err = PTR_ERR(dsi->regs);
+               goto disable_vdd;
+       }
 
        dsi->mipi = tegra_mipi_request(&pdev->dev);
-       if (IS_ERR(dsi->mipi))
-               return PTR_ERR(dsi->mipi);
+       if (IS_ERR(dsi->mipi)) {
+               err = PTR_ERR(dsi->mipi);
+               goto disable_vdd;
+       }
+
+       err = tegra_dsi_pad_calibrate(dsi);
+       if (err < 0) {
+               dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
+               goto mipi_free;
+       }
 
        dsi->host.ops = &tegra_dsi_host_ops;
        dsi->host.dev = &pdev->dev;
@@ -932,7 +1212,7 @@ static int tegra_dsi_probe(struct platform_device *pdev)
        err = mipi_dsi_host_register(&dsi->host);
        if (err < 0) {
                dev_err(&pdev->dev, "failed to register DSI host: %d\n", err);
-               return err;
+               goto mipi_free;
        }
 
        INIT_LIST_HEAD(&dsi->client.list);
@@ -943,12 +1223,26 @@ static int tegra_dsi_probe(struct platform_device *pdev)
        if (err < 0) {
                dev_err(&pdev->dev, "failed to register host1x client: %d\n",
                        err);
-               return err;
+               goto unregister;
        }
 
        platform_set_drvdata(pdev, dsi);
 
        return 0;
+
+unregister:
+       mipi_dsi_host_unregister(&dsi->host);
+mipi_free:
+       tegra_mipi_free(dsi->mipi);
+disable_vdd:
+       regulator_disable(dsi->vdd);
+disable_clk_lp:
+       clk_disable_unprepare(dsi->clk_lp);
+disable_clk:
+       clk_disable_unprepare(dsi->clk);
+reset:
+       reset_control_assert(dsi->rst);
+       return err;
 }
 
 static int tegra_dsi_remove(struct platform_device *pdev)
@@ -967,7 +1261,6 @@ static int tegra_dsi_remove(struct platform_device *pdev)
        tegra_mipi_free(dsi->mipi);
 
        regulator_disable(dsi->vdd);
-       clk_disable_unprepare(dsi->clk_parent);
        clk_disable_unprepare(dsi->clk_lp);
        clk_disable_unprepare(dsi->clk);
        reset_control_assert(dsi->rst);