MIPI DSI bus allows to model DSI hosts
and DSI devices using Linux bus.

Signed-off-by: Andrzej Hajda <a.hajda at samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park at samsung.com>
---
Hi,

This is my implementation of mipi dsi bus.
The first time it was posted as a part of CDF infrastructure [1],
but if it can be merged independently I will be glad.

I have not addressed yet Bert's comments, but I think it should
be easy, once we have agreement how to implement it.

There are also working drivers which uses this bus:
- Exynos DSI master,
- s6e8aa0 panel.
I will post them later.

[1] http://www.mail-archive.com/dri-devel at lists.freedesktop.org/msg45252.html

Changes:
v2:
    - set_power callback removed (its role is passed to RUNTIME_PM),
    - changed transfer ops parameters to struct,
    - changed source location,
    - minor fixes

Regards
Andrzej

---
 drivers/gpu/drm/Kconfig        |   4 +
 drivers/gpu/drm/Makefile       |   2 +
 drivers/gpu/drm/drm_mipi_dsi.c | 344 +++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_mipi_dsi.h     | 154 ++++++++++++++++++
 4 files changed, 504 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_mipi_dsi.c
 create mode 100644 include/drm/drm_mipi_dsi.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index f864275..58a603d 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_MIPI_DSI
+       tristate
+       default y
+
 config DRM_USB
        tristate
        depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index cc08b84..6bab99c 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-mipi-dsi-y := drm_mipi_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_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
index 0000000..ead35da
--- /dev/null
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -0,0 +1,344 @@
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012, Samsung Electronics, Co., Ltd.
+ * Andrzej Hajda <a.hajda at 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.
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <video/mipi_display.h>
+#include <drm/drm_mipi_dsi.h>
+
+/* 
-----------------------------------------------------------------------------
+ * Bus operations
+ */
+
+int mipi_dsi_init(struct mipi_dsi_device *dev)
+{
+       const struct mipi_dsi_bus_ops *ops = dev->bus->ops;
+
+       if (!ops->init)
+               return -ENOSYS;
+
+       return ops->init(dev->bus, dev);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_init);
+
+int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on)
+{
+       const struct mipi_dsi_bus_ops *ops = dev->bus->ops;
+
+       if (!ops->set_stream)
+               return -ENOSYS;
+
+       return ops->set_stream(dev->bus, dev, on);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_set_stream);
+
+int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 
*data,
+                      size_t len)
+{
+       const struct mipi_dsi_bus_ops *ops = dev->bus->ops;
+       struct mipi_dsi_msg msg = {
+               .channel = channel,
+               .tx_buf = data,
+               .tx_len = len
+       };
+
+       if (!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;
+       }
+
+       return ops->transfer(dev->bus, dev, &msg);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_dcs_write);
+
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd,
+                         u8 *data, size_t len)
+{
+       const struct mipi_dsi_bus_ops *ops = dev->bus->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->transfer)
+               return -ENOSYS;
+
+       return ops->transfer(dev->bus, dev, &msg);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_dcs_read);
+
+/* 
-----------------------------------------------------------------------------
+ * Bus type
+ */
+
+static const struct mipi_dsi_device_id *
+mipi_dsi_match_id(const struct mipi_dsi_device_id *id,
+                 struct mipi_dsi_device *dev)
+{
+       while (id->name[0]) {
+               if (strcmp(dev->name, id->name) == 0) {
+                       dev->id_entry = id;
+                       return id;
+               }
+               id++;
+       }
+       return NULL;
+}
+
+static int mipi_dsi_match(struct device *_dev, struct device_driver *_drv)
+{
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+       struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_drv);
+
+       if (of_driver_match_device(_dev, _drv))
+               return 1;
+
+       if (drv->id_table)
+               return mipi_dsi_match_id(drv->id_table, dev) != NULL;
+
+       return (strcmp(dev->name, _drv->name) == 0);
+}
+
+static ssize_t modalias_show(struct device *_dev, struct device_attribute *a,
+                            char *buf)
+{
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+       int len = snprintf(buf, PAGE_SIZE, MIPI_DSI_MODULE_PREFIX "%s\n",
+                          dev->name);
+
+       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+}
+
+static struct device_attribute mipi_dsi_dev_attrs[] = {
+       __ATTR_RO(modalias),
+       __ATTR_NULL,
+};
+
+static int mipi_dsi_uevent(struct device *_dev, struct kobj_uevent_env *env)
+{
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+
+       add_uevent_var(env, "MODALIAS=%s%s", MIPI_DSI_MODULE_PREFIX,
+                      dev->name);
+       return 0;
+}
+
+static const struct dev_pm_ops mipi_dsi_dev_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",
+       .dev_attrs      = mipi_dsi_dev_attrs,
+       .match          = mipi_dsi_match,
+       .uevent         = mipi_dsi_uevent,
+       .pm             = &mipi_dsi_dev_pm_ops,
+};
+
+void mipi_dsi_dev_release(struct device *_dev)
+{
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+
+       kfree(dev);
+}
+
+static struct device_type mipi_dsi_dev_type = {
+       .release        = mipi_dsi_dev_release,
+};
+
+
+/* 
-----------------------------------------------------------------------------
+ * Device and driver (un)registration
+ */
+
+/**
+ * mipi_dsi_device_register - register a DSI device
+ * @dev: DSI device we're registering
+ */
+int mipi_dsi_device_register(struct mipi_dsi_device *dev,
+                             struct mipi_dsi_bus *bus)
+{
+       device_initialize(&dev->dev);
+
+       dev->bus = bus;
+       dev->dev.bus = &mipi_dsi_bus_type;
+       dev->dev.parent = bus->dev;
+       dev->dev.type = &mipi_dsi_dev_type;
+
+       if (dev->id != -1)
+               dev_set_name(&dev->dev, "%s.%d", dev->name,  dev->id);
+       else
+               dev_set_name(&dev->dev, "%s", dev->name);
+
+       return device_add(&dev->dev);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_device_register);
+
+/**
+ * mipi_dsi_device_unregister - unregister a DSI device
+ * @dev: DSI device we're unregistering
+ */
+void mipi_dsi_device_unregister(struct mipi_dsi_device *dev)
+{
+       device_del(&dev->dev);
+       put_device(&dev->dev);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_device_unregister);
+
+static int mipi_dsi_drv_probe(struct device *_dev)
+{
+       struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_dev->driver);
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+
+       return drv->probe(dev);
+}
+
+static int mipi_dsi_drv_remove(struct device *_dev)
+{
+       struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_dev->driver);
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+
+       return drv->remove(dev);
+}
+
+/**
+ * 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_GPL(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_GPL(mipi_dsi_driver_unregister);
+
+struct mipi_dsi_device *of_mipi_dsi_register_device(struct mipi_dsi_bus *bus,
+                                                   struct device_node *node)
+{
+       struct mipi_dsi_device *d = NULL;
+       int ret;
+
+       d = kzalloc(sizeof(*d), GFP_KERNEL);
+       if (!d)
+               return ERR_PTR(-ENOMEM);
+
+       ret = of_modalias_node(node, d->name, sizeof(d->name));
+       if (ret) {
+               dev_err(bus->dev, "modalias failure on %s\n", node->full_name);
+               goto err_free;
+       }
+
+       d->dev.of_node = of_node_get(node);
+       if (!d->dev.of_node)
+               goto err_free;
+
+       ret = mipi_dsi_device_register(d, bus);
+
+       if (!ret)
+               return d;
+
+       of_node_put(node);
+err_free:
+       kfree(d);
+
+       return ERR_PTR(ret);
+}
+
+int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus)
+{
+       struct device_node *n;
+
+       for_each_child_of_node(bus->dev->of_node, n)
+               of_mipi_dsi_register_device(bus, n);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(of_mipi_dsi_register_devices);
+
+static int mipi_dsi_remove_device_fn(struct device *_dev, void *priv)
+{
+       struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev);
+
+       mipi_dsi_device_unregister(dev);
+
+       return 0;
+}
+
+void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus)
+{
+       device_for_each_child(bus->dev, bus, mipi_dsi_remove_device_fn);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_unregister_devices);
+/* 
-----------------------------------------------------------------------------
+ * Init/exit
+ */
+
+static int __init mipi_dsi_bus_init(void)
+{
+       return bus_register(&mipi_dsi_bus_type);
+}
+
+static void __exit mipi_dsi_bus_exit(void)
+{
+       bus_unregister(&mipi_dsi_bus_type);
+}
+
+module_init(mipi_dsi_bus_init);
+module_exit(mipi_dsi_bus_exit)
+
+MODULE_AUTHOR("Andrzej Hajda <a.hajda at samsung.com>");
+MODULE_DESCRIPTION("MIPI DSI Bus");
+MODULE_LICENSE("GPL v2");
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
new file mode 100644
index 0000000..cdde75e
--- /dev/null
+++ b/include/drm/drm_mipi_dsi.h
@@ -0,0 +1,154 @@
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2013, Samsung Electronics, Co., Ltd.
+ * Andrzej Hajda <a.hajda at 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>
+#include <video/videomode.h>
+
+struct mipi_dsi_bus;
+struct mipi_dsi_device;
+
+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_bus_ops {
+       int (*init)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev);
+       int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev,
+                         bool on);
+       ssize_t (*transfer)(struct mipi_dsi_bus *bus,
+                           struct mipi_dsi_device *dev,
+                           struct mipi_dsi_msg *msg);
+};
+
+#define DSI_MODE_VIDEO                 (1 << 0)
+#define DSI_MODE_VIDEO_BURST           (1 << 1)
+#define DSI_MODE_VIDEO_SYNC_PULSE      (1 << 2)
+#define DSI_MODE_VIDEO_AUTO_VERT       (1 << 3)
+#define DSI_MODE_VIDEO_HSE             (1 << 4)
+#define DSI_MODE_VIDEO_HFP             (1 << 5)
+#define DSI_MODE_VIDEO_HBP             (1 << 6)
+#define DSI_MODE_VIDEO_HSA             (1 << 7)
+#define DSI_MODE_VSYNC_FLUSH           (1 << 8)
+#define DSI_MODE_EOT_PACKET            (1 << 9)
+
+enum mipi_dsi_pixel_format {
+       DSI_FMT_RGB888,
+       DSI_FMT_RGB666,
+       DSI_FMT_RGB666_PACKED,
+       DSI_FMT_RGB565,
+};
+
+struct mipi_dsi_interface_params {
+       enum mipi_dsi_pixel_format format;
+       unsigned long mode;
+       unsigned long hs_clk_freq;
+       unsigned long esc_clk_freq;
+       unsigned char data_lanes;
+       unsigned char cmd_allow;
+};
+
+struct mipi_dsi_bus {
+       struct device *dev;
+       const struct mipi_dsi_bus_ops *ops;
+};
+
+#define MIPI_DSI_MODULE_PREFIX         "mipi-dsi:"
+#define MIPI_DSI_NAME_SIZE             32
+
+struct mipi_dsi_device_id {
+       char name[MIPI_DSI_NAME_SIZE];
+       __kernel_ulong_t driver_data    /* Data private to the driver */
+                       __aligned(sizeof(__kernel_ulong_t));
+};
+
+struct mipi_dsi_device {
+       char name[MIPI_DSI_NAME_SIZE];
+       int id;
+       struct device dev;
+
+       const struct mipi_dsi_device_id *id_entry;
+       struct mipi_dsi_bus *bus;
+       struct videomode vm;
+       struct mipi_dsi_interface_params params;
+};
+
+#define to_mipi_dsi_device(d)  container_of(d, struct mipi_dsi_device, dev)
+
+int mipi_dsi_device_register(struct mipi_dsi_device *dev,
+                            struct mipi_dsi_bus *bus);
+void mipi_dsi_device_unregister(struct mipi_dsi_device *dev);
+
+struct mipi_dsi_driver {
+       int(*probe)(struct mipi_dsi_device *);
+       int(*remove)(struct mipi_dsi_device *);
+       struct device_driver driver;
+       const struct mipi_dsi_device_id *id_table;
+};
+
+#define to_mipi_dsi_driver(d)  container_of(d, struct mipi_dsi_driver, driver)
+
+int mipi_dsi_driver_register(struct mipi_dsi_driver *drv);
+void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv);
+
+static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device *dev)
+{
+       return dev_get_drvdata(&dev->dev);
+}
+
+static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dev,
+                                       void *data)
+{
+       dev_set_drvdata(&dev->dev, data);
+}
+
+int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus);
+void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus);
+
+/* module_mipi_dsi_driver() - Helper macro for drivers that don't do
+ * anything special in module init/exit.  This eliminates a lot of
+ * boilerplate.  Each module may only use this macro once, and
+ * calling it replaces module_init() and module_exit()
+ */
+#define module_mipi_dsi_driver(__mipi_dsi_driver) \
+       module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \
+                       mipi_dsi_driver_unregister)
+
+int mipi_dsi_init(struct mipi_dsi_device *dev);
+int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on);
+int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 
*data,
+                      size_t len);
+ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd,
+                         u8 *data, size_t len);
+
+#define mipi_dsi_dcs_write_seq(dev, channel, seq...) \
+({\
+       const u8 d[] = { seq };\
+       BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too long for 
stack");\
+       mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\
+})
+
+#define mipi_dsi_dcs_write_static_seq(dev, channel, seq...) \
+({\
+       static const u8 d[] = { seq };\
+       mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\
+})
+
+#endif /* __DRM_MIPI_DSI__ */
-- 
1.8.3.2

Reply via email to