drm/i915/dsi: add drm mipi dsi host support
authorJani Nikula <jani.nikula@intel.com>
Fri, 16 Jan 2015 12:27:23 +0000 (14:27 +0200)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Thu, 29 Jan 2015 15:51:39 +0000 (16:51 +0100)
Add basic support for using the drm mipi dsi framework for DSI. We don't
use device tree which is pretty much required by mipi_dsi_host_register
and friends, and we don't have the kind of device model the functions
expect either. So we cheat and use it as a library to abstract what we
need: a nice, clean interface for DSI transfers. This means we will have
to be careful with what functions we call, as the driver model devices
in mipi_dsi_host and mipi_dsi_device will *not* be initialized.

Signed-off-by: Jani Nikula <jani.nikula@intel.com>
Reviewed-By: Shobhit Kumar <shobhit.kumar@intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/i915/Kconfig
drivers/gpu/drm/i915/intel_dsi.c
drivers/gpu/drm/i915/intel_dsi.h
drivers/gpu/drm/i915/intel_dsi_panel_vbt.c

index da196cd07263a01380e3eacb339b3a3b705bc682..74acca9bcd9dc5211fc7f552eee4bb2199f28f08 100644 (file)
@@ -12,6 +12,7 @@ config DRM_I915
        select TMPFS
        select DRM_KMS_HELPER
        select DRM_PANEL
+       select DRM_MIPI_DSI
        # i915 depends on ACPI_VIDEO when ACPI is enabled
        # but for select to work, need to select ACPI_VIDEO's dependencies, ick
        select BACKLIGHT_LCD_SUPPORT if ACPI
index 1e151e00a614778f9fe3650254c9566464d2d5ac..803e87a5a22f8782da986f44af3e4bf466a5c4c0 100644 (file)
@@ -29,6 +29,7 @@
 #include <drm/drm_edid.h>
 #include <drm/i915_drm.h>
 #include <drm/drm_panel.h>
+#include <drm/drm_mipi_dsi.h>
 #include <linux/slab.h>
 #include "i915_drv.h"
 #include "intel_drv.h"
@@ -59,6 +60,149 @@ static void wait_for_dsi_fifo_empty(struct intel_dsi *intel_dsi, enum port port)
                DRM_ERROR("DPI FIFOs are not empty\n");
 }
 
+static void write_data(struct drm_i915_private *dev_priv, u32 reg,
+                      const u8 *data, u32 len)
+{
+       u32 i, j;
+
+       for (i = 0; i < len; i += 4) {
+               u32 val = 0;
+
+               for (j = 0; j < min_t(u32, len - i, 4); j++)
+                       val |= *data++ << 8 * j;
+
+               I915_WRITE(reg, val);
+       }
+}
+
+static void read_data(struct drm_i915_private *dev_priv, u32 reg,
+                     u8 *data, u32 len)
+{
+       u32 i, j;
+
+       for (i = 0; i < len; i += 4) {
+               u32 val = I915_READ(reg);
+
+               for (j = 0; j < min_t(u32, len - i, 4); j++)
+                       *data++ = val >> 8 * j;
+       }
+}
+
+static ssize_t intel_dsi_host_transfer(struct mipi_dsi_host *host,
+                                      const struct mipi_dsi_msg *msg)
+{
+       struct intel_dsi_host *intel_dsi_host = to_intel_dsi_host(host);
+       struct drm_device *dev = intel_dsi_host->intel_dsi->base.base.dev;
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       enum port port = intel_dsi_host->port;
+       struct mipi_dsi_packet packet;
+       ssize_t ret;
+       const u8 *header, *data;
+       u32 data_reg, data_mask, ctrl_reg, ctrl_mask;
+
+       ret = mipi_dsi_create_packet(&packet, msg);
+       if (ret < 0)
+               return ret;
+
+       header = packet.header;
+       data = packet.payload;
+
+       if (msg->flags & MIPI_DSI_MSG_USE_LPM) {
+               data_reg = MIPI_LP_GEN_DATA(port);
+               data_mask = LP_DATA_FIFO_FULL;
+               ctrl_reg = MIPI_LP_GEN_CTRL(port);
+               ctrl_mask = LP_CTRL_FIFO_FULL;
+       } else {
+               data_reg = MIPI_HS_GEN_DATA(port);
+               data_mask = HS_DATA_FIFO_FULL;
+               ctrl_reg = MIPI_HS_GEN_CTRL(port);
+               ctrl_mask = HS_CTRL_FIFO_FULL;
+       }
+
+       /* note: this is never true for reads */
+       if (packet.payload_length) {
+
+               if (wait_for((I915_READ(MIPI_GEN_FIFO_STAT(port)) & data_mask) == 0, 50))
+                       DRM_ERROR("Timeout waiting for HS/LP DATA FIFO !full\n");
+
+               write_data(dev_priv, data_reg, packet.payload,
+                          packet.payload_length);
+       }
+
+       if (msg->rx_len) {
+               I915_WRITE(MIPI_INTR_STAT(port), GEN_READ_DATA_AVAIL);
+       }
+
+       if (wait_for((I915_READ(MIPI_GEN_FIFO_STAT(port)) & ctrl_mask) == 0, 50)) {
+               DRM_ERROR("Timeout waiting for HS/LP CTRL FIFO !full\n");
+       }
+
+       I915_WRITE(ctrl_reg, header[2] << 16 | header[1] << 8 | header[0]);
+
+       /* ->rx_len is set only for reads */
+       if (msg->rx_len) {
+               data_mask = GEN_READ_DATA_AVAIL;
+               if (wait_for((I915_READ(MIPI_INTR_STAT(port)) & data_mask) == data_mask, 50))
+                       DRM_ERROR("Timeout waiting for read data.\n");
+
+               read_data(dev_priv, data_reg, msg->rx_buf, msg->rx_len);
+       }
+
+       /* XXX: fix for reads and writes */
+       return 4 + packet.payload_length;
+}
+
+static int intel_dsi_host_attach(struct mipi_dsi_host *host,
+                                struct mipi_dsi_device *dsi)
+{
+       return 0;
+}
+
+static int intel_dsi_host_detach(struct mipi_dsi_host *host,
+                                struct mipi_dsi_device *dsi)
+{
+       return 0;
+}
+
+static const struct mipi_dsi_host_ops intel_dsi_host_ops = {
+       .attach = intel_dsi_host_attach,
+       .detach = intel_dsi_host_detach,
+       .transfer = intel_dsi_host_transfer,
+};
+
+static struct intel_dsi_host *intel_dsi_host_init(struct intel_dsi *intel_dsi,
+                                                 enum port port)
+{
+       struct intel_dsi_host *host;
+       struct mipi_dsi_device *device;
+
+       host = kzalloc(sizeof(*host), GFP_KERNEL);
+       if (!host)
+               return NULL;
+
+       host->base.ops = &intel_dsi_host_ops;
+       host->intel_dsi = intel_dsi;
+       host->port = port;
+
+       /*
+        * We should call mipi_dsi_host_register(&host->base) here, but we don't
+        * have a host->dev, and we don't have OF stuff either. So just use the
+        * dsi framework as a library and hope for the best. Create the dsi
+        * devices by ourselves here too. Need to be careful though, because we
+        * don't initialize any of the driver model devices here.
+        */
+       device = kzalloc(sizeof(*device), GFP_KERNEL);
+       if (!device) {
+               kfree(host);
+               return NULL;
+       }
+
+       device->host = &host->base;
+       host->device = device;
+
+       return host;
+}
+
 static void band_gap_reset(struct drm_i915_private *dev_priv)
 {
        mutex_lock(&dev_priv->dpio_lock);
@@ -809,6 +953,7 @@ void intel_dsi_init(struct drm_device *dev)
        struct drm_connector *connector;
        struct drm_display_mode *scan, *fixed_mode = NULL;
        struct drm_i915_private *dev_priv = dev->dev_private;
+       enum port port;
        unsigned int i;
 
        DRM_DEBUG_KMS("\n");
@@ -857,7 +1002,11 @@ void intel_dsi_init(struct drm_device *dev)
        intel_connector->unregister = intel_connector_unregister;
 
        /* Pipe A maps to MIPI DSI port A, pipe B maps to MIPI DSI port C */
-       if (dev_priv->vbt.dsi.port == DVO_PORT_MIPIA) {
+       if (dev_priv->vbt.dsi.config->dual_link) {
+               /* XXX: does dual link work on either pipe? */
+               intel_encoder->crtc_mask = (1 << PIPE_A);
+               intel_dsi->ports = ((1 << PORT_A) | (1 << PORT_C));
+       } else if (dev_priv->vbt.dsi.port == DVO_PORT_MIPIA) {
                intel_encoder->crtc_mask = (1 << PIPE_A);
                intel_dsi->ports = (1 << PORT_A);
        } else if (dev_priv->vbt.dsi.port == DVO_PORT_MIPIC) {
@@ -865,6 +1014,17 @@ void intel_dsi_init(struct drm_device *dev)
                intel_dsi->ports = (1 << PORT_C);
        }
 
+       /* Create a DSI host (and a device) for each port. */
+       for_each_dsi_port(port, intel_dsi->ports) {
+               struct intel_dsi_host *host;
+
+               host = intel_dsi_host_init(intel_dsi, port);
+               if (!host)
+                       goto err;
+
+               intel_dsi->dsi_hosts[port] = host;
+       }
+
        for (i = 0; i < ARRAY_SIZE(intel_dsi_drivers); i++) {
                intel_dsi->panel = intel_dsi_drivers[i].init(intel_dsi,
                                                             intel_dsi_drivers[i].panel_id);
index fc0b2b8d90f1b8b0bca280aa12c9e83bf6a480ed..2784ac442368a6a37e2f3d516406a38a63570e8d 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
+#include <drm/drm_mipi_dsi.h>
 #include "intel_drv.h"
 
 /* Dual Link support */
 #define DSI_DUAL_LINK_FRONT_BACK       1
 #define DSI_DUAL_LINK_PIXEL_ALT                2
 
+struct intel_dsi_host;
+
 struct intel_dsi {
        struct intel_encoder base;
 
        struct drm_panel *panel;
+       struct intel_dsi_host *dsi_hosts[I915_MAX_PORTS];
 
        struct intel_connector *attached_connector;
 
@@ -94,6 +98,20 @@ struct intel_dsi {
        u16 panel_pwr_cycle_delay;
 };
 
+struct intel_dsi_host {
+       struct mipi_dsi_host base;
+       struct intel_dsi *intel_dsi;
+       enum port port;
+
+       /* our little hack */
+       struct mipi_dsi_device *device;
+};
+
+static inline struct intel_dsi_host *to_intel_dsi_host(struct mipi_dsi_host *h)
+{
+       return container_of(h, struct intel_dsi_host, base);
+}
+
 #define for_each_dsi_port(__port, __ports_mask) \
        for ((__port) = PORT_A; (__port) < I915_MAX_PORTS; (__port)++)  \
                if ((__ports_mask) & (1 << (__port)))
index ac7a24dcf7f7c7c06c2754c33afd61a4eb408d30..576c730664e6134d1e14c7d2676539a09a2372b6 100644 (file)
@@ -399,9 +399,6 @@ struct drm_panel *vbt_panel_init(struct intel_dsi *intel_dsi, u16 panel_id)
        intel_dsi->dual_link = mipi_config->dual_link;
        intel_dsi->pixel_overlap = mipi_config->pixel_overlap;
 
-       if (intel_dsi->dual_link)
-               intel_dsi->ports = ((1 << PORT_A) | (1 << PORT_C));
-
        if (intel_dsi->pixel_format == VID_MODE_FORMAT_RGB666)
                bits_per_pixel = 18;
        else if (intel_dsi->pixel_format == VID_MODE_FORMAT_RGB565)