Commit 0fffdf6c authored by Thierry Reding's avatar Thierry Reding

drm/tegra: dsi: Implement host transfers

Add support for sending MIPI DSI command packets from the host to a
peripheral. This is required for panels that need configuration before
they accept video data.
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent e94236cd
......@@ -993,6 +993,272 @@ 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;
......@@ -1069,6 +1335,7 @@ 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)
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment