On Mon, Nov 3, 2014 at 4:27 AM, Thierry Reding thierry.reding@gmail.com wrote:
From: Thierry Reding treding@nvidia.com
Implement ganged mode support for the Tegra DSI driver. The DSI host controller to gang up with is specified via a phandle in the device tree and the resolved DSI host controller used for the programming of the ganged-mode registers.
There's a lot in here that is not specifically ganging-support, such as adding the transfer callback and command mode, as well as pulling out functionality into helper functions. It might make things a little clearer to split this up into a few patches. I'll leave that up to you.
At any rate, aside from the tiny nit I picked below (which you can feel free to ignore),
Reviewed-by: Sean Paul seanpaul@chromium.org
Signed-off-by: Thierry Reding treding@nvidia.com
Changes in v2:
- keep track of the number of bytes transferred to/from peripheral
- use newly introduced mipi_dsi_create_packet()
- extract FIFO write into separate function
.../bindings/gpu/nvidia,tegra20-host1x.txt | 2 + drivers/gpu/drm/tegra/dsi.c | 792 ++++++++++++++++++--- drivers/gpu/drm/tegra/dsi.h | 14 +- 3 files changed, 691 insertions(+), 117 deletions(-)
diff --git a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt index b48f4ef31d93..4c32ef0b7db8 100644 --- a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt +++ b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt @@ -191,6 +191,8 @@ of the following host1x client modules:
- nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
- nvidia,edid: supplies a binary EDID blob
- nvidia,panel: phandle of a display panel
- nvidia,ganged-mode: contains a phandle to a second DSI controller to gang
- up with in order to support up to 8 data lanes
- sor: serial output resource
diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 584b771d8b2f..8940360ccc9c 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -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; @@ -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)
@@ -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);
You might want to add "size = size & 0xFFFF;" before performing this write.
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 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;
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 (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);
}
/* horizontal front porch */
hfp = (mode->hsync_start - mode->hdisplay) * mul / div;
hfp -= 8;
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;
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,39 +759,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 +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,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 +1323,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 +1335,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; @@ -846,10 +1372,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 +1395,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 +1479,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 +1490,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 +1528,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);
diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h index 5ce610d08d77..bad1006a5150 100644 --- a/drivers/gpu/drm/tegra/dsi.h +++ b/drivers/gpu/drm/tegra/dsi.h @@ -21,9 +21,16 @@ #define DSI_INT_STATUS 0x0d #define DSI_INT_MASK 0x0e #define DSI_HOST_CONTROL 0x0f +#define DSI_HOST_CONTROL_FIFO_RESET (1 << 21) +#define DSI_HOST_CONTROL_CRC_RESET (1 << 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 (1 << 6) #define DSI_HOST_CONTROL_HS (1 << 5) -#define DSI_HOST_CONTROL_BTA (1 << 2) +#define DSI_HOST_CONTROL_FIFO_SEL (1 << 4) +#define DSI_HOST_CONTROL_IMM_BTA (1 << 3) +#define DSI_HOST_CONTROL_PKT_BTA (1 << 2) #define DSI_HOST_CONTROL_CS (1 << 1) #define DSI_HOST_CONTROL_ECC (1 << 0) #define DSI_CONTROL 0x10 @@ -39,9 +46,13 @@ #define DSI_SOL_DELAY 0x11 #define DSI_MAX_THRESHOLD 0x12 #define DSI_TRIGGER 0x13 +#define DSI_TRIGGER_HOST (1 << 1) +#define DSI_TRIGGER_VIDEO (1 << 0) #define DSI_TX_CRC 0x14 #define DSI_STATUS 0x15 #define DSI_STATUS_IDLE (1 << 10) +#define DSI_STATUS_UNDERFLOW (1 << 9) +#define DSI_STATUS_OVERFLOW (1 << 8) #define DSI_INIT_SEQ_CONTROL 0x1a #define DSI_INIT_SEQ_DATA_0 0x1b #define DSI_INIT_SEQ_DATA_1 0x1c @@ -104,6 +115,7 @@ #define DSI_PAD_CONTROL_3 0x51 #define DSI_PAD_CONTROL_4 0x52 #define DSI_GANGED_MODE_CONTROL 0x53 +#define DSI_GANGED_MODE_CONTROL_ENABLE (1 << 0) #define DSI_GANGED_MODE_START 0x54 #define DSI_GANGED_MODE_SIZE 0x55
#define DSI_RAW_DATA_BYTE_COUNT 0x56
2.1.2