MIPI DSI is a high-speed serial interface to transmit
data from/to host to display module.

Signed-off-by: Andrzej Hajda <a.ha...@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.p...@samsung.com>
---
 drivers/video/display/Kconfig        |   4 +
 drivers/video/display/Makefile       |   1 +
 drivers/video/display/mipi-dsi-bus.c | 332 +++++++++++++++++++++++++++++++++++
 include/video/display.h              |   3 +
 include/video/mipi-dsi-bus.h         | 144 +++++++++++++++
 5 files changed, 484 insertions(+)
 create mode 100644 drivers/video/display/mipi-dsi-bus.c
 create mode 100644 include/video/mipi-dsi-bus.h

diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig
index 9b482a8..619b05d 100644
--- a/drivers/video/display/Kconfig
+++ b/drivers/video/display/Kconfig
@@ -20,6 +20,10 @@ config DISPLAY_MIPI_DBI
        tristate
        default n
 
+config DISPLAY_MIPI_DSI
+       tristate
+       default n
+
 config DISPLAY_PANEL_DPI
        tristate "DPI (Parallel) Display Panels"
        ---help---
diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile
index d03c64a..b323fd4 100644
--- a/drivers/video/display/Makefile
+++ b/drivers/video/display/Makefile
@@ -3,6 +3,7 @@ display-y                                       := 
display-core.o \
 obj-$(CONFIG_DISPLAY_CORE)                     += display.o
 obj-$(CONFIG_DISPLAY_CONNECTOR_VGA)            += con-vga.o
 obj-$(CONFIG_DISPLAY_MIPI_DBI)                 += mipi-dbi-bus.o
+obj-$(CONFIG_DISPLAY_MIPI_DSI)                 += mipi-dsi-bus.o
 obj-$(CONFIG_DISPLAY_PANEL_DPI)                        += panel-dpi.o
 obj-$(CONFIG_DISPLAY_PANEL_R61505)             += panel-r61505.o
 obj-$(CONFIG_DISPLAY_PANEL_R61517)             += panel-r61517.o
diff --git a/drivers/video/display/mipi-dsi-bus.c 
b/drivers/video/display/mipi-dsi-bus.c
new file mode 100644
index 0000000..a194d92
--- /dev/null
+++ b/drivers/video/display/mipi-dsi-bus.c
@@ -0,0 +1,332 @@
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2012, Samsung Electronics, Co., Ltd.
+ * Andrzej Hajda <a.ha...@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 <video/mipi-dsi-bus.h>
+
+/* 
-----------------------------------------------------------------------------
+ * Bus operations
+ */
+
+int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on)
+{
+       const struct mipi_dsi_bus_ops *ops = dev->bus->ops;
+
+       if (!ops->set_power)
+               return 0;
+
+       return ops->set_power(dev->bus, dev, on);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_set_power);
+
+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 0;
+
+       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;
+       u8 type = channel << 6;
+
+       if (!ops->transfer)
+               return -EINVAL;
+
+       switch (len) {
+       case 0:
+               return -EINVAL;
+       case 1:
+               type |= MIPI_DSI_DCS_SHORT_WRITE;
+               break;
+       case 2:
+               type |= MIPI_DSI_DCS_SHORT_WRITE_PARAM;
+               break;
+       default:
+               type |= MIPI_DSI_DCS_LONG_WRITE;
+       }
+
+       return ops->transfer(dev->bus, dev, type, data, len, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(mipi_dsi_dcs_write);
+
+int 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;
+       u8 type = MIPI_DSI_DCS_READ | (channel << 6);
+
+       if (!ops->transfer)
+               return -EINVAL;
+
+       return ops->transfer(dev->bus, dev, type, &cmd, 1, data, len);
+}
+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_init(void)
+{
+       return bus_register(&mipi_dsi_bus_type);
+}
+
+static void __exit mipi_dsi_exit(void)
+{
+       bus_unregister(&mipi_dsi_bus_type);
+}
+
+module_init(mipi_dsi_init);
+module_exit(mipi_dsi_exit)
+
+MODULE_AUTHOR("Andrzej Hajda <a.ha...@samsung.com>");
+MODULE_DESCRIPTION("MIPI DSI Bus");
+MODULE_LICENSE("GPL v2");
diff --git a/include/video/display.h b/include/video/display.h
index 3138401..7faca0f 100644
--- a/include/video/display.h
+++ b/include/video/display.h
@@ -18,6 +18,7 @@
 #include <linux/module.h>
 #include <media/media-entity.h>
 #include <video/mipi-dbi-bus.h>
+#include <video/mipi-dsi-bus.h>
 
 #define DISPLAY_PIXEL_CODING(option, type, from, to, variant) \
        (((option) << 17) | ((type) << 13) | ((variant) << 10) | \
@@ -184,6 +185,7 @@ enum display_entity_stream_state {
 enum display_entity_interface_type {
        DISPLAY_ENTITY_INTERFACE_DPI,
        DISPLAY_ENTITY_INTERFACE_DBI,
+       DISPLAY_ENTITY_INTERFACE_DSI,
        DISPLAY_ENTITY_INTERFACE_LVDS,
        DISPLAY_ENTITY_INTERFACE_VGA,
 };
@@ -192,6 +194,7 @@ struct display_entity_interface_params {
        enum display_entity_interface_type type;
        union {
                struct mipi_dbi_interface_params dbi;
+               struct mipi_dsi_interface_params dsi;
        } p;
 };
 
diff --git a/include/video/mipi-dsi-bus.h b/include/video/mipi-dsi-bus.h
new file mode 100644
index 0000000..a78792d
--- /dev/null
+++ b/include/video/mipi-dsi-bus.h
@@ -0,0 +1,144 @@
+/*
+ * MIPI DSI Bus
+ *
+ * Copyright (C) 2013, Samsung Electronics, Co., Ltd.
+ * Andrzej Hajda <a.ha...@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 __MIPI_DSI_BUS_H__
+#define __MIPI_DSI_BUS_H__
+
+#include <linux/device.h>
+#include <video/videomode.h>
+
+struct mipi_dsi_bus;
+struct mipi_dsi_device;
+
+struct mipi_dsi_bus_ops {
+       int (*set_power)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev,
+                        bool on);
+       int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev,
+                         bool on);
+       int (*transfer)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev,
+                       u8 type, const u8 *tx_buf, size_t tx_len, u8 *rx_buf,
+                       size_t rx_len);
+};
+
+#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_set_power(struct mipi_dsi_device *dev, bool on);
+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);
+int 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 /* __MIPI_DSI_BUS__ */
-- 
1.8.1.2

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to