[RFC v2 PATCH] mipi-dsi-bus: add MIPI DSI bus support
Andrzej Hajda
a.hajda at samsung.com
Mon Nov 18 08:25:23 PST 2013
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@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
More information about the dri-devel
mailing list