drm: Add MIPI DSI bus support
authorAndrzej Hajda <a.hajda@samsung.com>
Wed, 4 Dec 2013 15:35:12 +0000 (16:35 +0100)
committerThierry Reding <treding@nvidia.com>
Tue, 17 Dec 2013 17:09:43 +0000 (18:09 +0100)
MIPI DSI bus allows to model DSI hosts and DSI peripherals using the
Linux driver model. DSI hosts are registered by the DSI host drivers.
During registration DSI peripherals will be created from the children
of the DSI host's device tree node. Support for registration from
board-setup code will be added later when needed.

DSI hosts expose operations which can be used by DSI peripheral drivers
to access associated devices.

Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/drm_mipi_dsi.c [new file with mode: 0644]
include/drm/drm_mipi_dsi.h [new file with mode: 0644]

index f864275..0b8f8fd 100644 (file)
@@ -20,6 +20,10 @@ menuconfig DRM
          details.  You should also select and configure AGP
          (/dev/agpgart) support if it is available for your platform.
 
+config DRM_MIPI_DSI
+       bool
+       depends on DRM
+
 config DRM_USB
        tristate
        depends on DRM
index cc08b84..b46239a 100644 (file)
@@ -31,6 +31,7 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
 CFLAGS_drm_trace_points.o := -I$(src)
 
 obj-$(CONFIG_DRM)      += drm.o
+obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
 obj-$(CONFIG_DRM_USB)   += drm_usb.o
 obj-$(CONFIG_DRM_TTM)  += ttm/
 obj-$(CONFIG_DRM_TDFX) += tdfx/
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
new file mode 100644 (file)
index 0000000..b155ee2
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <drm/drm_mipi_dsi.h>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <video/mipi_display.h>
+
+static int mipi_dsi_device_match(struct device *dev, struct device_driver *drv)
+{
+       return of_driver_match_device(dev, drv);
+}
+
+static const struct dev_pm_ops mipi_dsi_device_pm_ops = {
+       .runtime_suspend = pm_generic_runtime_suspend,
+       .runtime_resume = pm_generic_runtime_resume,
+       .suspend = pm_generic_suspend,
+       .resume = pm_generic_resume,
+       .freeze = pm_generic_freeze,
+       .thaw = pm_generic_thaw,
+       .poweroff = pm_generic_poweroff,
+       .restore = pm_generic_restore,
+};
+
+static struct bus_type mipi_dsi_bus_type = {
+       .name = "mipi-dsi",
+       .match = mipi_dsi_device_match,
+       .pm = &mipi_dsi_device_pm_ops,
+};
+
+static void mipi_dsi_dev_release(struct device *dev)
+{
+       struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+       of_node_put(dev->of_node);
+       kfree(dsi);
+}
+
+static const struct device_type mipi_dsi_device_type = {
+       .release = mipi_dsi_dev_release,
+};
+
+static struct mipi_dsi_device *mipi_dsi_device_alloc(struct mipi_dsi_host *host)
+{
+       struct mipi_dsi_device *dsi;
+
+       dsi = kzalloc(sizeof(*dsi), GFP_KERNEL);
+       if (!dsi)
+               return ERR_PTR(-ENOMEM);
+
+       dsi->host = host;
+       dsi->dev.bus = &mipi_dsi_bus_type;
+       dsi->dev.parent = host->dev;
+       dsi->dev.type = &mipi_dsi_device_type;
+
+       device_initialize(&dsi->dev);
+
+       return dsi;
+}
+
+static int mipi_dsi_device_add(struct mipi_dsi_device *dsi)
+{
+       struct mipi_dsi_host *host = dsi->host;
+
+       dev_set_name(&dsi->dev, "%s.%d", dev_name(host->dev),  dsi->channel);
+
+       return device_add(&dsi->dev);
+}
+
+static struct mipi_dsi_device *
+of_mipi_dsi_device_add(struct mipi_dsi_host *host, struct device_node *node)
+{
+       struct mipi_dsi_device *dsi;
+       struct device *dev = host->dev;
+       int ret;
+       u32 reg;
+
+       ret = of_property_read_u32(node, "reg", &reg);
+       if (ret) {
+               dev_err(dev, "device node %s has no valid reg property: %d\n",
+                       node->full_name, ret);
+               return ERR_PTR(-EINVAL);
+       }
+
+       if (reg > 3) {
+               dev_err(dev, "device node %s has invalid reg property: %u\n",
+                       node->full_name, reg);
+               return ERR_PTR(-EINVAL);
+       }
+
+       dsi = mipi_dsi_device_alloc(host);
+       if (IS_ERR(dsi)) {
+               dev_err(dev, "failed to allocate DSI device %s: %ld\n",
+                       node->full_name, PTR_ERR(dsi));
+               return dsi;
+       }
+
+       dsi->dev.of_node = of_node_get(node);
+       dsi->channel = reg;
+
+       ret = mipi_dsi_device_add(dsi);
+       if (ret) {
+               dev_err(dev, "failed to add DSI device %s: %d\n",
+                       node->full_name, ret);
+               kfree(dsi);
+               return ERR_PTR(ret);
+       }
+
+       return dsi;
+}
+
+int mipi_dsi_host_register(struct mipi_dsi_host *host)
+{
+       struct device_node *node;
+
+       for_each_available_child_of_node(host->dev->of_node, node)
+               of_mipi_dsi_device_add(host, node);
+
+       return 0;
+}
+EXPORT_SYMBOL(mipi_dsi_host_register);
+
+static int mipi_dsi_remove_device_fn(struct device *dev, void *priv)
+{
+       struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+       device_unregister(&dsi->dev);
+
+       return 0;
+}
+
+void mipi_dsi_host_unregister(struct mipi_dsi_host *host)
+{
+       device_for_each_child(host->dev, NULL, mipi_dsi_remove_device_fn);
+}
+EXPORT_SYMBOL(mipi_dsi_host_unregister);
+
+/**
+ * mipi_dsi_attach - attach a DSI device to its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_attach(struct mipi_dsi_device *dsi)
+{
+       const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+       if (!ops || !ops->attach)
+               return -ENOSYS;
+
+       return ops->attach(dsi->host, dsi);
+}
+EXPORT_SYMBOL(mipi_dsi_attach);
+
+/**
+ * mipi_dsi_detach - detach a DSI device from its DSI host
+ * @dsi: DSI peripheral
+ */
+int mipi_dsi_detach(struct mipi_dsi_device *dsi)
+{
+       const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+
+       if (!ops || !ops->detach)
+               return -ENOSYS;
+
+       return ops->detach(dsi->host, dsi);
+}
+EXPORT_SYMBOL(mipi_dsi_detach);
+
+/**
+ * mipi_dsi_dcs_write - send DCS write command
+ * @dsi: DSI device
+ * @channel: virtual channel
+ * @data: pointer to the command followed by parameters
+ * @len: length of @data
+ */
+int mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, unsigned int channel,
+                      const void *data, size_t len)
+{
+       const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+       struct mipi_dsi_msg msg = {
+               .channel = channel,
+               .tx_buf = data,
+               .tx_len = len
+       };
+
+       if (!ops || !ops->transfer)
+               return -ENOSYS;
+
+       switch (len) {
+       case 0:
+               return -EINVAL;
+       case 1:
+               msg.type = MIPI_DSI_DCS_SHORT_WRITE;
+               break;
+       case 2:
+               msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+               break;
+       default:
+               msg.type = MIPI_DSI_DCS_LONG_WRITE;
+               break;
+       }
+
+       return ops->transfer(dsi->host, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write);
+
+/**
+ * mipi_dsi_dcs_read - send DCS read request command
+ * @dsi: DSI device
+ * @channel: virtual channel
+ * @cmd: DCS read command
+ * @data: pointer to read buffer
+ * @len: length of @data
+ *
+ * Function returns number of read bytes or error code.
+ */
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, unsigned int channel,
+                         u8 cmd, void *data, size_t len)
+{
+       const struct mipi_dsi_host_ops *ops = dsi->host->ops;
+       struct mipi_dsi_msg msg = {
+               .channel = channel,
+               .type = MIPI_DSI_DCS_READ,
+               .tx_buf = &cmd,
+               .tx_len = 1,
+               .rx_buf = data,
+               .rx_len = len
+       };
+
+       if (!ops || !ops->transfer)
+               return -ENOSYS;
+
+       return ops->transfer(dsi->host, &msg);
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_read);
+
+static int mipi_dsi_drv_probe(struct device *dev)
+{
+       struct mipi_dsi_driver *drv = to_mipi_dsi_driver(dev->driver);
+       struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+       return drv->probe(dsi);
+}
+
+static int mipi_dsi_drv_remove(struct device *dev)
+{
+       struct mipi_dsi_driver *drv = to_mipi_dsi_driver(dev->driver);
+       struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+
+       return drv->remove(dsi);
+}
+
+/**
+ * mipi_dsi_driver_register - register a driver for DSI devices
+ * @drv: DSI driver structure
+ */
+int mipi_dsi_driver_register(struct mipi_dsi_driver *drv)
+{
+       drv->driver.bus = &mipi_dsi_bus_type;
+       if (drv->probe)
+               drv->driver.probe = mipi_dsi_drv_probe;
+       if (drv->remove)
+               drv->driver.remove = mipi_dsi_drv_remove;
+
+       return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL(mipi_dsi_driver_register);
+
+/**
+ * mipi_dsi_driver_unregister - unregister a driver for DSI devices
+ * @drv: DSI driver structure
+ */
+void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv)
+{
+       driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL(mipi_dsi_driver_unregister);
+
+static int __init mipi_dsi_bus_init(void)
+{
+       return bus_register(&mipi_dsi_bus_type);
+}
+postcore_initcall(mipi_dsi_bus_init);
+
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("MIPI DSI Bus");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
new file mode 100644 (file)
index 0000000..d32628a
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __DRM_MIPI_DSI_H__
+#define __DRM_MIPI_DSI_H__
+
+#include <linux/device.h>
+
+struct mipi_dsi_host;
+struct mipi_dsi_device;
+
+/**
+ * struct mipi_dsi_msg - read/write DSI buffer
+ * @channel: virtual channel id
+ * @type: payload data type
+ * @tx_len: length of @tx_buf
+ * @tx_buf: data to be written
+ * @rx_len: length of @rx_buf
+ * @rx_buf: data to be read, or NULL
+ */
+struct mipi_dsi_msg {
+       u8 channel;
+       u8 type;
+
+       size_t tx_len;
+       const void *tx_buf;
+
+       size_t rx_len;
+       void *rx_buf;
+};
+
+/**
+ * struct mipi_dsi_host_ops - DSI bus operations
+ * @attach: attach DSI device to DSI host
+ * @detach: detach DSI device from DSI host
+ * @transfer: send and/or receive DSI packet, return number of received bytes,
+ *           or error
+ */
+struct mipi_dsi_host_ops {
+       int (*attach)(struct mipi_dsi_host *host,
+                     struct mipi_dsi_device *dsi);
+       int (*detach)(struct mipi_dsi_host *host,
+                     struct mipi_dsi_device *dsi);
+       ssize_t (*transfer)(struct mipi_dsi_host *host,
+                           struct mipi_dsi_msg *msg);
+};
+
+/**
+ * struct mipi_dsi_host - DSI host device
+ * @dev: driver model device node for this DSI host
+ * @ops: DSI host operations
+ */
+struct mipi_dsi_host {
+       struct device *dev;
+       const struct mipi_dsi_host_ops *ops;
+};
+
+int mipi_dsi_host_register(struct mipi_dsi_host *host);
+void mipi_dsi_host_unregister(struct mipi_dsi_host *host);
+
+/* DSI mode flags */
+
+/* video mode */
+#define MIPI_DSI_MODE_VIDEO            BIT(0)
+/* video burst mode */
+#define MIPI_DSI_MODE_VIDEO_BURST      BIT(1)
+/* video pulse mode */
+#define MIPI_DSI_MODE_VIDEO_SYNC_PULSE BIT(2)
+/* enable auto vertical count mode */
+#define MIPI_DSI_MODE_VIDEO_AUTO_VERT  BIT(3)
+/* enable hsync-end packets in vsync-pulse and v-porch area */
+#define MIPI_DSI_MODE_VIDEO_HSE                BIT(4)
+/* disable hfront-porch area */
+#define MIPI_DSI_MODE_VIDEO_HFP                BIT(5)
+/* disable hback-porch area */
+#define MIPI_DSI_MODE_VIDEO_HBP                BIT(6)
+/* disable hsync-active area */
+#define MIPI_DSI_MODE_VIDEO_HSA                BIT(7)
+/* flush display FIFO on vsync pulse */
+#define MIPI_DSI_MODE_VSYNC_FLUSH      BIT(8)
+/* disable EoT packets in HS mode */
+#define MIPI_DSI_MODE_EOT_PACKET       BIT(9)
+
+enum mipi_dsi_pixel_format {
+       MIPI_DSI_FMT_RGB888,
+       MIPI_DSI_FMT_RGB666,
+       MIPI_DSI_FMT_RGB666_PACKED,
+       MIPI_DSI_FMT_RGB565,
+};
+
+/**
+ * struct mipi_dsi_device - DSI peripheral device
+ * @host: DSI host for this peripheral
+ * @dev: driver model device node for this peripheral
+ * @channel: virtual channel assigned to the peripheral
+ * @format: pixel format for video mode
+ * @lanes: number of active data lanes
+ * @mode_flags: DSI operation mode related flags
+ */
+struct mipi_dsi_device {
+       struct mipi_dsi_host *host;
+       struct device dev;
+
+       unsigned int channel;
+       unsigned int lanes;
+       enum mipi_dsi_pixel_format format;
+       unsigned long mode_flags;
+};
+
+#define to_mipi_dsi_device(d) container_of(d, struct mipi_dsi_device, dev)
+
+int mipi_dsi_attach(struct mipi_dsi_device *dsi);
+int mipi_dsi_detach(struct mipi_dsi_device *dsi);
+int mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, unsigned int channel,
+                      const void *data, size_t len);
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, unsigned int channel,
+                         u8 cmd, void *data, size_t len);
+
+/**
+ * struct mipi_dsi_driver - DSI driver
+ * @driver: device driver model driver
+ * @probe: callback for device binding
+ * @remove: callback for device unbinding
+ */
+struct mipi_dsi_driver {
+       struct device_driver driver;
+       int(*probe)(struct mipi_dsi_device *dsi);
+       int(*remove)(struct mipi_dsi_device *dsi);
+};
+
+#define to_mipi_dsi_driver(d) container_of(d, struct mipi_dsi_driver, driver)
+
+static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device *dsi)
+{
+       return dev_get_drvdata(&dsi->dev);
+}
+
+static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dsi, void *data)
+{
+       dev_set_drvdata(&dsi->dev, data);
+}
+
+int mipi_dsi_driver_register(struct mipi_dsi_driver *driver);
+void mipi_dsi_driver_unregister(struct mipi_dsi_driver *driver);
+
+#define module_mipi_dsi_driver(__mipi_dsi_driver) \
+       module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \
+                       mipi_dsi_driver_unregister)
+
+#endif /* __DRM_MIPI_DSI__ */