All of lore.kernel.org
 help / color / mirror / Atom feed
From: Andrzej Hajda <a.hajda@samsung.com>
To: Thierry Reding <treding@nvidia.com>
Cc: dri-devel@lists.freedesktop.org,
	Andrzej Hajda <a.hajda@samsung.com>,
	Kyungmin Park <kyungmin.park@samsung.com>,
	tomi.valkeinen@ti.com
Subject: [RFC v2 PATCH] mipi-dsi-bus: add MIPI DSI bus support
Date: Mon, 18 Nov 2013 17:25:23 +0100	[thread overview]
Message-ID: <1384791923-11424-1-git-send-email-a.hajda@samsung.com> (raw)

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

Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@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@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@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@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	other threads:[~2013-11-18 16:25 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-11-18 16:25 Andrzej Hajda [this message]
2013-11-22 17:41 ` [RFC v2 PATCH] mipi-dsi-bus: add MIPI DSI bus support Thierry Reding
2013-11-25 15:05   ` Andrzej Hajda
2013-11-27 10:54     ` Thierry Reding
2013-11-29 15:28       ` Andrzej Hajda
2013-12-02 10:22         ` Thierry Reding
2013-12-05 14:37       ` Tomi Valkeinen
2013-12-06 12:54         ` Thierry Reding
2013-12-09 11:34           ` Tomi Valkeinen
2013-12-09 13:10             ` Thierry Reding
2013-12-09 15:05               ` Tomi Valkeinen
2013-12-09 16:10                 ` Thierry Reding
2013-12-10  9:19                   ` Tomi Valkeinen
2013-12-12 12:19                     ` Thierry Reding
2013-12-13 11:21                       ` Tomi Valkeinen
2013-12-13 11:22                       ` Andrzej Hajda
2013-12-13 11:29                         ` Tomi Valkeinen
2013-12-13 12:06                           ` Thierry Reding
2013-12-13 12:23                             ` Tomi Valkeinen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1384791923-11424-1-git-send-email-a.hajda@samsung.com \
    --to=a.hajda@samsung.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=kyungmin.park@samsung.com \
    --cc=tomi.valkeinen@ti.com \
    --cc=treding@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.