|
@@ -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>
|
|
|
|
|
@@ -26,9 +27,6 @@
|
|
|
#include "dsi.h"
|
|
|
#include "mipi-phy.h"
|
|
|
|
|
|
-#define DSI_VIDEO_FIFO_DEPTH (1920 / 4)
|
|
|
-#define DSI_HOST_FIFO_DEPTH 64
|
|
|
-
|
|
|
struct tegra_dsi {
|
|
|
struct host1x_client client;
|
|
|
struct tegra_output output;
|
|
@@ -54,6 +52,13 @@ struct tegra_dsi {
|
|
|
|
|
|
struct regulator *vdd;
|
|
|
bool enabled;
|
|
|
+
|
|
|
+ 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;
|
|
@@ -329,7 +349,7 @@ static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
|
|
|
if (rate < 0)
|
|
|
return rate;
|
|
|
|
|
|
- period = DIV_ROUND_CLOSEST(1000000000UL, rate * 2);
|
|
|
+ period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2);
|
|
|
|
|
|
err = mipi_dphy_timing_get_default(&timing, period);
|
|
|
if (err < 0)
|
|
@@ -369,6 +389,9 @@ static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
|
|
|
DSI_TIMING_FIELD(timing.tago, period, 1);
|
|
|
tegra_dsi_writel(dsi, value, DSI_BTA_TIMING);
|
|
|
|
|
|
+ if (dsi->slave)
|
|
|
+ return tegra_dsi_set_phy_timing(dsi->slave);
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -426,26 +449,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,61 +512,136 @@ 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);
|
|
|
+ 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);
|
|
|
|
|
|
- err = tegra_dsi_set_phy_timing(dsi);
|
|
|
- if (err < 0)
|
|
|
- return err;
|
|
|
-
|
|
|
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 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 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 front porch */
|
|
|
+ hfp = (mode->hsync_start - mode->hdisplay) * mul / div;
|
|
|
+ hfp -= 8;
|
|
|
|
|
|
- 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);
|
|
|
+ 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);
|
|
|
|
|
|
- /* set SOL delay */
|
|
|
- tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY);
|
|
|
+ /* 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;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+
|
|
|
+ if (dsi->enabled)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ 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 +662,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,39 +758,68 @@ 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.
|
|
|
*/
|
|
|
- plld = DIV_ROUND_UP(bclk * 8, 1000000) * 1000000;
|
|
|
+ plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC;
|
|
|
|
|
|
/*
|
|
|
* We divide the frequency by two here, but we make up for that by
|
|
@@ -640,25 +851,17 @@ 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?
|
|
|
*/
|
|
|
+ tegra_dsi_set_timeout(dsi, bclk, vrefresh);
|
|
|
|
|
|
- /* 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);
|
|
|
+ err = tegra_dsi_set_phy_timing(dsi);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -695,7 +898,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 +923,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 +942,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 +956,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 +996,324 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+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 tegra_dsi *dsi,
|
|
|
+ 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 = tegra_dsi_readl(dsi, DSI_RD_DATA);
|
|
|
+
|
|
|
+ switch (value & 0x3f) {
|
|
|
+ case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
|
|
|
+ errors = (value >> 8) & 0xffff;
|
|
|
+ dev_dbg(dsi->dev, "Acknowledge and error report: %04x\n",
|
|
|
+ errors);
|
|
|
+ for (i = 0; i < ARRAY_SIZE(error_report); i++)
|
|
|
+ if (errors & BIT(i))
|
|
|
+ dev_dbg(dsi->dev, " %2u: %s\n", 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:
|
|
|
+ dev_err(dsi->dev, "unhandled response type: %02x\n",
|
|
|
+ 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 = tegra_dsi_readl(dsi, 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 tegra_dsi *dsi, unsigned long timeout)
|
|
|
+{
|
|
|
+ tegra_dsi_writel(dsi, DSI_TRIGGER_HOST, DSI_TRIGGER);
|
|
|
+
|
|
|
+ timeout = jiffies + msecs_to_jiffies(timeout);
|
|
|
+
|
|
|
+ while (time_before(jiffies, timeout)) {
|
|
|
+ u32 value = tegra_dsi_readl(dsi, DSI_TRIGGER);
|
|
|
+ if ((value & DSI_TRIGGER_HOST) == 0)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ usleep_range(1000, 2000);
|
|
|
+ }
|
|
|
+
|
|
|
+ DRM_DEBUG_KMS("timeout waiting for transmission to complete\n");
|
|
|
+ return -ETIMEDOUT;
|
|
|
+}
|
|
|
+
|
|
|
+static int tegra_dsi_wait_for_response(struct tegra_dsi *dsi,
|
|
|
+ unsigned long timeout)
|
|
|
+{
|
|
|
+ timeout = jiffies + msecs_to_jiffies(250);
|
|
|
+
|
|
|
+ while (time_before(jiffies, timeout)) {
|
|
|
+ u32 value = tegra_dsi_readl(dsi, DSI_STATUS);
|
|
|
+ u8 count = value & 0x1f;
|
|
|
+
|
|
|
+ if (count > 0)
|
|
|
+ return count;
|
|
|
+
|
|
|
+ usleep_range(1000, 2000);
|
|
|
+ }
|
|
|
+
|
|
|
+ DRM_DEBUG_KMS("peripheral returned no data\n");
|
|
|
+ return -ETIMEDOUT;
|
|
|
+}
|
|
|
+
|
|
|
+static void tegra_dsi_writesl(struct tegra_dsi *dsi, unsigned long offset,
|
|
|
+ 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);
|
|
|
+
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_WR_DATA);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host,
|
|
|
+ const struct mipi_dsi_msg *msg)
|
|
|
+{
|
|
|
+ struct tegra_dsi *dsi = host_to_tegra(host);
|
|
|
+ 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 > dsi->video_fifo_depth * 4)
|
|
|
+ return -ENOSPC;
|
|
|
+
|
|
|
+ /* reset underflow/overflow flags */
|
|
|
+ value = tegra_dsi_readl(dsi, DSI_STATUS);
|
|
|
+ if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) {
|
|
|
+ value = DSI_HOST_CONTROL_FIFO_RESET;
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
|
+ usleep_range(10, 20);
|
|
|
+ }
|
|
|
+
|
|
|
+ value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
|
|
|
+ value |= DSI_POWER_CONTROL_ENABLE;
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
|
|
|
+
|
|
|
+ usleep_range(5000, 10000);
|
|
|
+
|
|
|
+ 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 > dsi->host_fifo_depth * 4)
|
|
|
+ value |= DSI_HOST_CONTROL_FIFO_SEL;
|
|
|
+
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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 = tegra_dsi_readl(dsi, DSI_HOST_CONTROL);
|
|
|
+ value |= DSI_HOST_CONTROL_PKT_BTA;
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
|
|
|
+ }
|
|
|
+
|
|
|
+ value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE;
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_CONTROL);
|
|
|
+
|
|
|
+ /* write packet header, ECC is generated by hardware */
|
|
|
+ value = header[2] << 16 | header[1] << 8 | header[0];
|
|
|
+ tegra_dsi_writel(dsi, value, DSI_WR_DATA);
|
|
|
+
|
|
|
+ /* write payload (if any) */
|
|
|
+ if (packet.payload_length > 0)
|
|
|
+ tegra_dsi_writesl(dsi, DSI_WR_DATA, packet.payload,
|
|
|
+ packet.payload_length);
|
|
|
+
|
|
|
+ err = tegra_dsi_transmit(dsi, 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(dsi, 250);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ count = err;
|
|
|
+
|
|
|
+ value = tegra_dsi_readl(dsi, DSI_RD_DATA);
|
|
|
+ switch (value) {
|
|
|
+ case 0x84:
|
|
|
+ /*
|
|
|
+ dev_dbg(dsi->dev, "ACK\n");
|
|
|
+ */
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 0x87:
|
|
|
+ /*
|
|
|
+ dev_dbg(dsi->dev, "ESCAPE\n");
|
|
|
+ */
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ dev_err(dsi->dev, "unknown status: %08x\n", value);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (count > 1) {
|
|
|
+ err = tegra_dsi_read_response(dsi, msg, count);
|
|
|
+ if (err < 0)
|
|
|
+ dev_err(dsi->dev,
|
|
|
+ "failed to parse response: %zd\n",
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+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 +1326,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;
|
|
@@ -830,8 +1338,29 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host,
|
|
|
static const struct mipi_dsi_host_ops tegra_dsi_host_ops = {
|
|
|
.attach = tegra_dsi_host_attach,
|
|
|
.detach = tegra_dsi_host_detach,
|
|
|
+ .transfer = tegra_dsi_host_transfer,
|
|
|
};
|
|
|
|
|
|
+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;
|
|
@@ -843,11 +1372,19 @@ static int tegra_dsi_probe(struct platform_device *pdev)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
dsi->output.dev = dsi->dev = &pdev->dev;
|
|
|
+ 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
|
|
@@ -861,68 +1398,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;
|
|
@@ -930,7 +1482,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);
|
|
@@ -941,12 +1493,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)
|
|
@@ -965,7 +1531,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);
|