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: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
e94236cde4
commit
0fffdf6ca9
|
@ -993,6 +993,272 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi)
|
||||||
return 0;
|
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)
|
static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi)
|
||||||
{
|
{
|
||||||
struct clk *parent;
|
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 = {
|
static const struct mipi_dsi_host_ops tegra_dsi_host_ops = {
|
||||||
.attach = tegra_dsi_host_attach,
|
.attach = tegra_dsi_host_attach,
|
||||||
.detach = tegra_dsi_host_detach,
|
.detach = tegra_dsi_host_detach,
|
||||||
|
.transfer = tegra_dsi_host_transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi)
|
static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi)
|
||||||
|
|
|
@ -21,9 +21,16 @@
|
||||||
#define DSI_INT_STATUS 0x0d
|
#define DSI_INT_STATUS 0x0d
|
||||||
#define DSI_INT_MASK 0x0e
|
#define DSI_INT_MASK 0x0e
|
||||||
#define DSI_HOST_CONTROL 0x0f
|
#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_RAW (1 << 6)
|
||||||
#define DSI_HOST_CONTROL_HS (1 << 5)
|
#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_CS (1 << 1)
|
||||||
#define DSI_HOST_CONTROL_ECC (1 << 0)
|
#define DSI_HOST_CONTROL_ECC (1 << 0)
|
||||||
#define DSI_CONTROL 0x10
|
#define DSI_CONTROL 0x10
|
||||||
|
@ -39,9 +46,13 @@
|
||||||
#define DSI_SOL_DELAY 0x11
|
#define DSI_SOL_DELAY 0x11
|
||||||
#define DSI_MAX_THRESHOLD 0x12
|
#define DSI_MAX_THRESHOLD 0x12
|
||||||
#define DSI_TRIGGER 0x13
|
#define DSI_TRIGGER 0x13
|
||||||
|
#define DSI_TRIGGER_HOST (1 << 1)
|
||||||
|
#define DSI_TRIGGER_VIDEO (1 << 0)
|
||||||
#define DSI_TX_CRC 0x14
|
#define DSI_TX_CRC 0x14
|
||||||
#define DSI_STATUS 0x15
|
#define DSI_STATUS 0x15
|
||||||
#define DSI_STATUS_IDLE (1 << 10)
|
#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_CONTROL 0x1a
|
||||||
#define DSI_INIT_SEQ_DATA_0 0x1b
|
#define DSI_INIT_SEQ_DATA_0 0x1b
|
||||||
#define DSI_INIT_SEQ_DATA_1 0x1c
|
#define DSI_INIT_SEQ_DATA_1 0x1c
|
||||||
|
|
Loading…
Reference in New Issue