drm/tegra: dsi: Implement host transfers
authorThierry Reding <treding@nvidia.com>
Fri, 7 Nov 2014 16:25:26 +0000 (17:25 +0100)
committerThierry Reding <treding@nvidia.com>
Thu, 13 Nov 2014 15:12:36 +0000 (16:12 +0100)
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>
drivers/gpu/drm/tegra/dsi.c
drivers/gpu/drm/tegra/dsi.h

index 66816104ba722f36e3654bb1353e2faf23718713..6646fa2adc38255190e3e2009479842b245528ad 100644 (file)
@@ -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)
index 1f6ca68108d85d7149e2af2911c497f74f4bfa88..bad1006a51509da2868d83115245cb742ec29385 100644 (file)
 #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
 #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