In order to support DSI peripherals, add a DSI bus type that devices and
drivers can be registered with.

Signed-off-by: Thierry Reding <treding at nvidia.com>
---
 drivers/gpu/drm/Kconfig   |   4 +
 drivers/gpu/drm/Makefile  |   2 +
 drivers/gpu/drm/drm_dsi.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_dsi.h     | 206 +++++++++++++++++++++++++++++++
 4 files changed, 518 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_dsi.c
 create mode 100644 include/drm/drm_dsi.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index f86427591167..7faefcdd6854 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -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_DSI
+       bool
+       depends on DRM
+
 config DRM_USB
        tristate
        depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index cc08b845f965..eef34abc1e45 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -19,6 +19,7 @@ drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
 drm-$(CONFIG_PCI) += ati_pcigart.o

+drm-dsi-y   := drm_dsi.o
 drm-usb-y   := drm_usb.o

 drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o
@@ -31,6 +32,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_DSI)  += drm_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_dsi.c b/drivers/gpu/drm/drm_dsi.c
new file mode 100644
index 000000000000..bead3cc0e9e3
--- /dev/null
+++ b/drivers/gpu/drm/drm_dsi.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#include <drm/drm_dsi.h>
+
+static int dsi_device_match(struct device *dev, struct device_driver *drv)
+{
+       if (of_driver_match_device(dev, drv))
+               return 1;
+
+       return 0;
+}
+
+static struct bus_type dsi_bus_type = {
+       .name = "dsi",
+       .match = dsi_device_match,
+};
+
+static void dsi_device_release(struct device *dev)
+{
+       struct dsi_device *dsi = to_dsi_device(dev);
+
+       of_node_put(dsi->dev.of_node);
+       dsi_host_put(dsi->host);
+       kfree(dsi);
+}
+
+static struct dsi_device *dsi_device_alloc(struct dsi_host *host)
+{
+       struct dsi_device *dsi;
+
+       if (!dsi_host_get(host))
+               return ERR_PTR(-EINVAL);
+
+       dsi = kzalloc(sizeof(*dsi), GFP_KERNEL);
+       if (!dsi) {
+               dsi_host_put(host);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       dsi->host = dsi_host_get(host);
+
+       dsi->dev.parent = host->dev;
+       dsi->dev.bus = &dsi_bus_type;
+       dsi->dev.release = dsi_device_release;
+
+       device_initialize(&dsi->dev);
+
+       return dsi;
+}
+
+static int dsi_device_add(struct dsi_device *dsi)
+{
+       struct dsi_host *host = dsi->host;
+       int err;
+
+       dev_set_name(&dsi->dev, "%s.%u", dev_name(host->dev), dsi->channel);
+
+       err = device_add(&dsi->dev);
+       if (err < 0) {
+               dsi_device_put(dsi);
+               return err;
+       }
+
+       return 0;
+}
+
+static int of_dsi_host_register(struct dsi_host *host)
+{
+       if (!host->dev->of_node)
+               return -ENODEV;
+
+       return 0;
+}
+
+static int of_dsi_register_devices(struct dsi_host *host)
+{
+       struct device_node *np;
+
+       if (!host->dev->of_node)
+               return -ENODEV;
+
+       for_each_available_child_of_node(host->dev->of_node, np) {
+               struct dsi_device *dsi;
+               u32 value;
+               int err;
+
+               dsi = dsi_device_alloc(host);
+               if (IS_ERR(dsi)) {
+                       dev_err(host->dev,
+                               "failed to allocate DSI device for %s: %ld\n",
+                               np->full_name, PTR_ERR(dsi));
+                       continue;
+               }
+
+               dsi->dev.of_node = of_node_get(np);
+
+               err = of_property_read_u32(np, "reg", &value);
+               if (err) {
+                       dev_err(host->dev,
+                               "device %s has no valid 'reg' property: %d\n",
+                               np->full_name, err);
+                       dsi_device_put(dsi);
+                       continue;
+               }
+
+               if (value > 3) {
+                       dev_err(host->dev,
+                               "device %s has invalid virtual channel %u\n",
+                               np->full_name, value);
+                       dsi_device_put(dsi);
+                       continue;
+               }
+
+               dsi->channel = value;
+
+               err = dsi_device_add(dsi);
+               if (err < 0) {
+                       dev_err(host->dev,
+                               "failed to add DSI device for %s: %d\n",
+                               np->full_name, err);
+                       dsi_device_put(dsi);
+                       continue;
+               }
+       }
+
+       return 0;
+}
+
+int dsi_host_register(struct dsi_host *host)
+{
+       int err;
+
+       if (IS_ENABLED(CONFIG_OF)) {
+               err = of_dsi_host_register(host);
+               if (err < 0)
+                       return err;
+       }
+
+       if (IS_ENABLED(CONFIG_OF)) {
+               err = of_dsi_register_devices(host);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(dsi_host_register);
+
+static int __dsi_device_unregister(struct device *dev, void *data)
+{
+       device_unregister(dev);
+       return 0;
+}
+
+/**
+ * dsi_host_unregister() - unregister a DSI host controller
+ * @host: a DSI host controller
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_host_unregister(struct dsi_host *host)
+{
+       device_for_each_child(host->dev, NULL, __dsi_device_unregister);
+
+       return 0;
+}
+EXPORT_SYMBOL(dsi_host_unregister);
+
+/**
+ * dsi_host_transfer() - transfer a DSI message between host and peripheral
+ * @host: DSI host
+ * @msg: DSI message to transfer
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+ssize_t dsi_host_transfer(struct dsi_host *host, struct dsi_msg *msg)
+{
+       if (host->ops && host->ops->transfer)
+               return host->ops->transfer(host, msg);
+
+       return -ENOSYS;
+}
+EXPORT_SYMBOL(dsi_host_transfer);
+
+/**
+ * dsi_device_attach() - attach a DSI peripheral to its DSI host
+ * @device: DSI peripheral
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_device_attach(struct dsi_device *device)
+{
+       if (device->host->ops && device->host->ops->attach)
+               return device->host->ops->attach(device->host, device);
+
+       return -ENOSYS;
+}
+EXPORT_SYMBOL(dsi_device_attach);
+
+/**
+ * dsi_device_detach() - detach a DSI peripheral from its DSI host
+ * @device: DSI peripheral
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_device_detach(struct dsi_device *device)
+{
+       if (device->host->ops && device->host->ops->detach)
+               return device->host->ops->detach(device->host, device);
+
+       return -ENOSYS;
+}
+EXPORT_SYMBOL(dsi_device_detach);
+
+static int dsi_driver_probe(struct device *dev)
+{
+       const struct dsi_driver *drv = to_dsi_driver(dev->driver);
+       struct dsi_device *dsi = to_dsi_device(dev);
+
+       return drv->probe(dsi);
+}
+
+static int dsi_driver_remove(struct device *dev)
+{
+       const struct dsi_driver *drv = to_dsi_driver(dev->driver);
+       struct dsi_device *dsi = to_dsi_device(dev);
+
+       return drv->remove(dsi);
+}
+
+static void dsi_driver_shutdown(struct device *dev)
+{
+       const struct dsi_driver *drv = to_dsi_driver(dev->driver);
+       struct dsi_device *dsi = to_dsi_device(dev);
+
+       drv->shutdown(dsi);
+}
+
+/**
+ * dsi_register_driver() - register a DSI driver
+ * @drv: DSI driver
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_register_driver(struct dsi_driver *drv)
+{
+       drv->driver.bus = &dsi_bus_type;
+
+       if (drv->probe)
+               drv->driver.probe = dsi_driver_probe;
+
+       if (drv->remove)
+               drv->driver.remove = dsi_driver_remove;
+
+       if (drv->shutdown)
+               drv->driver.shutdown = dsi_driver_shutdown;
+
+       return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL(dsi_register_driver);
+
+/**
+ * dsi_unregister_driver() - unregister a DSI driver
+ * @drv: DSI driver
+ */
+void dsi_unregister_driver(struct dsi_driver *drv)
+{
+       driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL(dsi_unregister_driver);
+
+static int __init dsi_init(void)
+{
+       return bus_register(&dsi_bus_type);
+}
+postcore_initcall(dsi_init);
+
+MODULE_AUTHOR("Thierry Reding <treding at nvidia.com>");
+MODULE_DESCRIPTION("DRM DSI infrastructure");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/include/drm/drm_dsi.h b/include/drm/drm_dsi.h
new file mode 100644
index 000000000000..0886160b9aa2
--- /dev/null
+++ b/include/drm/drm_dsi.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef _DRM_DSI_H_
+#define _DRM_DSI_H_
+
+#include <linux/types.h>
+
+struct dsi_device;
+struct dsi_host;
+
+/*
+ * DSI packet data types
+ */
+
+/* processor-sourced packets */
+#define DSI_CMD_VSYNC_START 0x01
+#define DSI_CMD_VSYNC_END 0x11
+#define DSI_CMD_HSYNC_START 0x21
+#define DSI_CMD_HSYNC_END 0x31
+#define DSI_CMD_EOT 0x08
+#define DSI_CMD_COLOR_MODE_OFF 0x02
+#define DSI_CMD_COLOR_MODE_ON 0x12
+#define DSI_CMD_SHUT_DOWN 0x22
+#define DSI_CMD_TURN_ON 0x32
+#define DSI_CMD_GEN_SHORT_WRITE_0 0x03
+#define DSI_CMD_GEN_SHORT_WRITE_1 0x13
+#define DSI_CMD_GEN_SHORT_WRITE_2 0x23
+#define DSI_CMD_GEN_SHORT_READ_0 0x04
+#define DSI_CMD_GEN_SHORT_READ_1 0x14
+#define DSI_CMD_GEN_SHORT_READ_2 0x24
+#define DSI_CMD_DCS_SHORT_WRITE_0 0x05
+#define DSI_CMD_DCS_SHORT_WRITE_1 0x15
+#define DSI_CMD_DCS_SHORT_READ 0x06
+#define DSI_CMD_SET_MAX_RETURN_PACKET_SIZE 0x37
+#define DSI_CMD_NULL 0x09
+#define DSI_CMD_BLANK 0x19
+#define DSI_CMD_GEN_LONG_WRITE 0x29
+#define DSI_CMD_DCS_LONG_WRITE 0x39
+#define DSI_CMD_YCbCr422_20 0x0c
+#define DSI_CMD_YCbCr422_24 0x1c
+#define DSI_CMD_YCbCr422_16 0x2c
+#define DSI_CMD_RGB30 0x0d
+#define DSI_CMD_RGB36 0x1d
+#define DSI_CMD_YCbCr420 0x3d
+#define DSI_CMD_RGB16 0x0e
+#define DSI_CMD_RGB18 0x1e
+#define DSI_CMD_RGB18NP 0x2e
+#define DSI_CMD_RGB24 0x3e
+
+/* peripheral-sourced */
+#define DSI_RSP_ACK_ERR 0x02
+#define DSI_RSP_EOT 0x08
+#define DSI_RSP_GEN_SHORT_READ_1 0x11
+#define DSI_RSP_GEN_SHORT_READ_2 0x12
+#define DSI_RSP_GEN_LONG_READ 0x1a
+#define DSI_RSP_DCS_LONG_READ 0x1c
+#define DSI_RSP_DCS_SHORT_READ_1 0x21
+#define DSI_RSP_DCS_SHORT_READ_2 0x22
+
+#define DSI_ACK 0x84
+#define DSI_ESC 0x87
+
+/**
+ * struct dsi_msg - DSI command message
+ * @channel: virtual channel to send the message to
+ * @type: data ID of the message
+ * @tx_len: length of transmission buffer
+ * @tx: transmission buffer
+ * @rx_len: length of reception buffer
+ * @rx: reception buffer
+ */
+struct dsi_msg {
+       u8 channel;
+       u8 type;
+
+       size_t tx_len;
+       void *tx;
+
+       size_t rx_len;
+       void *rx;
+};
+
+/**
+ * struct dsi_host_ops - DSI host operations
+ * @attach: called when a peripheral is attached to the host
+ * @detach: called when a peripheral is detached from the host
+ * @transfer: transfer a DSI command message to a peripheral
+ */
+struct dsi_host_ops {
+       int (*attach)(struct dsi_host *host, struct dsi_device *device);
+       int (*detach)(struct dsi_host *host, struct dsi_device *device);
+       ssize_t (*transfer)(struct dsi_host *host, struct dsi_msg *msg);
+};
+
+/**
+ * struct dsi_host - DSI host
+ * @dev: device providing the DSI host functionality
+ * @ops: pointer to DSI host operations
+ */
+struct dsi_host {
+       struct device *dev;
+
+       const struct dsi_host_ops *ops;
+};
+
+static inline struct dsi_host *dsi_host_get(struct dsi_host *host)
+{
+       if (!host || !get_device(host->dev))
+               return NULL;
+
+       return host;
+}
+
+static inline void dsi_host_put(struct dsi_host *host)
+{
+       if (host)
+               put_device(host->dev);
+}
+
+int dsi_host_register(struct dsi_host *host);
+int dsi_host_unregister(struct dsi_host *host);
+
+ssize_t dsi_host_transfer(struct dsi_host *host, struct dsi_msg *msg);
+
+/**
+ * struct dsi_device - DSI peripheral
+ * @host: DSI host that this peripheral is attached to
+ * @dev: device to tie the peripheral into the device tree
+ * @channel: virtual channel of the peripheral
+ */
+struct dsi_device {
+       struct dsi_host *host;
+       struct device dev;
+
+       unsigned int channel;
+};
+
+static inline struct dsi_device *to_dsi_device(struct device *dev)
+{
+       return dev ? container_of(dev, struct dsi_device, dev) : NULL;
+}
+
+static inline struct dsi_device *dsi_device_get(struct dsi_device *dsi)
+{
+       if (!dsi || !get_device(&dsi->dev))
+               return NULL;
+
+       return dsi;
+}
+
+static inline void dsi_device_put(struct dsi_device *dsi)
+{
+       if (dsi)
+               put_device(&dsi->dev);
+}
+
+int dsi_device_attach(struct dsi_device *device);
+int dsi_device_detach(struct dsi_device *device);
+
+/**
+ * struct dsi_driver - DSI driver
+ * @driver: device driver model driver
+ * @probe: callback for device binding
+ * @remove: callback for device unbinding
+ * @shutdown: callback for device shutdown
+ */
+struct dsi_driver {
+       struct device_driver driver;
+       int (*probe)(struct dsi_device *dsi);
+       int (*remove)(struct dsi_device *dsi);
+       void (*shutdown)(struct dsi_device *dsi);
+};
+
+static inline struct dsi_driver *to_dsi_driver(struct device_driver *drv)
+{
+       return drv ? container_of(drv, struct dsi_driver, driver) : NULL;
+}
+
+int dsi_register_driver(struct dsi_driver *drv);
+void dsi_unregister_driver(struct dsi_driver *drv);
+
+#define module_dsi_driver(__dsi_driver)                                \
+       module_driver(__dsi_driver, dsi_register_driver,        \
+                       dsi_unregister_driver)
+
+#endif
-- 
1.8.4.2

Reply via email to