diff mbox

[RFC,1/7] drm: add DPI bus support

Message ID 1412609873-1894-2-git-send-email-boris.brezillon@free-electrons.com (mailing list archive)
State New, archived
Headers show

Commit Message

Boris BREZILLON Oct. 6, 2014, 3:37 p.m. UTC
The DPI bus is a parallel bus used to interface with video components.
This bus provide some control signals (HSYNC, VSYNC) and a parallel data
bus used to transfer content to slave devices (panels, encoders, ...)

This DPI layer is providing a way to negotiate a video format suiting all
the activated DPI devices on the bus.

As usual with busses, there are two ends:
- the host: this is the device aggregating the devices on a DPI bus.
            It is also responsible for choosing the best format given the
            devices connected on its bus.
- the device: it's describing a specific hardware (a panel, an encoder, or
              any other DRM device), and as such is attached to a driver.
              This driver will typically create a DRM encoder (and
              possibly the associated connector) and attach it to the DRM
              device embedding the DPI bus.

This approach will ease the work of display controllers, and
hopefully provide a standard way to bind slave DRM devices to DRM devices
(assuming your device is connected on DPI bus).

The term MIPI DPI or even DPI might not be the appropriate one as MIPI DPI
is a standard specify more things than just the video bus format being
used on the bus.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/gpu/drm/Kconfig        |   4 +
 drivers/gpu/drm/Makefile       |   1 +
 drivers/gpu/drm/drm_mipi_dpi.c | 369 +++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_mipi_dpi.h     | 169 +++++++++++++++++++
 4 files changed, 543 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_mipi_dpi.c
 create mode 100644 include/drm/drm_mipi_dpi.h
diff mbox

Patch

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 2d97f7e..8ad8983 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_DPI
+	bool
+	depends on DRM
+
 config DRM_MIPI_DSI
 	bool
 	depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index abb4f29..a77b2bc 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -35,6 +35,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_DPI) += drm_mipi_dpi.o
 obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
 obj-$(CONFIG_DRM_USB)   += drm_usb.o
 obj-$(CONFIG_DRM_TTM)	+= ttm/
diff --git a/drivers/gpu/drm/drm_mipi_dpi.c b/drivers/gpu/drm/drm_mipi_dpi.c
new file mode 100644
index 0000000..403922c
--- /dev/null
+++ b/drivers/gpu/drm/drm_mipi_dpi.c
@@ -0,0 +1,369 @@ 
+/*
+ * MIPI DPI Bus
+ *
+ * Copyright (C) 2014, Atmel
+ * Copyright (C) 2014, Free Electrons
+ *
+ * Author: Boris Brezillon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <drm/drm_mipi_dpi.h>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <video/mipi_display.h>
+
+static int mipi_dpi_device_match(struct device *dev, struct device_driver *drv)
+{
+	return of_driver_match_device(dev, drv);
+}
+
+static const struct dev_pm_ops mipi_dpi_device_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_dpi_bus_type = {
+	.name = "mipi-dpi",
+	.match = mipi_dpi_device_match,
+	.pm = &mipi_dpi_device_pm_ops,
+};
+
+static void mipi_dpi_dev_release(struct device *dev)
+{
+	struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev);
+
+	of_node_put(dev->of_node);
+	kfree(dpi);
+}
+
+static const struct device_type mipi_dpi_device_type = {
+	.release = mipi_dpi_dev_release,
+};
+
+static struct mipi_dpi_device *mipi_dpi_device_alloc(struct mipi_dpi_host *host)
+{
+	struct mipi_dpi_device *dpi;
+
+	dpi = kzalloc(sizeof(*dpi), GFP_KERNEL);
+	if (!dpi)
+		return ERR_PTR(-ENOMEM);
+
+	dpi->host = host;
+	dpi->dev.bus = &mipi_dpi_bus_type;
+	dpi->dev.parent = host->dev;
+	dpi->dev.type = &mipi_dpi_device_type;
+
+	device_initialize(&dpi->dev);
+
+	return dpi;
+}
+
+static int mipi_dpi_device_add(struct mipi_dpi_device *dpi)
+{
+	struct mipi_dpi_host *host = dpi->host;
+
+	dev_set_name(&dpi->dev, "%s.%d", dev_name(host->dev),  dpi->id);
+
+	return device_add(&dpi->dev);
+}
+
+static struct mipi_dpi_device *
+of_mipi_dpi_device_add(struct mipi_dpi_host *host, struct device_node *node)
+{
+	struct mipi_dpi_device *dpi;
+	struct device *dev = host->dev;
+	int ret;
+	u32 reg;
+
+	ret = of_property_read_u32(node, "reg", &reg);
+	if (ret) {
+		dev_err(dev, "device node %s has no valid reg property: %d\n",
+			node->full_name, ret);
+		return ERR_PTR(-EINVAL);
+	}
+
+	dpi = mipi_dpi_device_alloc(host);
+	if (IS_ERR(dpi)) {
+		dev_err(dev, "failed to allocate DPI device %s: %ld\n",
+			node->full_name, PTR_ERR(dpi));
+		return dpi;
+	}
+
+	dpi->dev.of_node = of_node_get(node);
+	dpi->id = reg;
+
+	ret = mipi_dpi_device_add(dpi);
+	if (ret) {
+		dev_err(dev, "failed to add DPI device %s: %d\n",
+			node->full_name, ret);
+		kfree(dpi);
+		return ERR_PTR(ret);
+	}
+
+	return dpi;
+}
+
+void mipi_dpi_host_init(struct mipi_dpi_host *host)
+{
+	mutex_init(&host->lock);
+	INIT_LIST_HEAD(&host->devices);
+}
+EXPORT_SYMBOL(mipi_dpi_host_init);
+
+int mipi_dpi_host_register(struct mipi_dpi_host *host)
+{
+	struct device_node *bus_node;
+	struct device_node *node;
+
+	if (!host->dev)
+		return -EINVAL;
+
+	bus_node = host->of_node ?: host->dev->of_node;
+	if (!bus_node)
+		return -EINVAL;
+
+	for_each_available_child_of_node(bus_node, node) {
+		pr_info("%s:%i\n", __func__, __LINE__);
+		/* skip nodes without reg property */
+		if (!of_find_property(node, "reg", NULL))
+			continue;
+		pr_info("%s:%i\n", __func__, __LINE__);
+		of_mipi_dpi_device_add(host, node);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dpi_host_register);
+
+static int mipi_dpi_remove_device_fn(struct device *dev, void *priv)
+{
+	struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev);
+
+	device_unregister(&dpi->dev);
+
+	return 0;
+}
+
+void mipi_dpi_host_unregister(struct mipi_dpi_host *host)
+{
+	device_for_each_child(host->dev, NULL, mipi_dpi_remove_device_fn);
+}
+EXPORT_SYMBOL(mipi_dpi_host_unregister);
+
+int mipi_dpi_host_apply_format(struct mipi_dpi_host *host)
+{
+	enum video_bus_format bus_fmt;
+	struct mipi_dpi_device *dpi;
+	int ret;
+
+	mutex_lock(&host->lock);
+
+	ret = host->ops->best_format(host, &bus_fmt);
+	if (ret)
+		goto err_unlock;
+
+	ret = host->ops->set_format(host, bus_fmt);
+	if (ret)
+		goto err_unlock;
+
+	list_for_each_entry(dpi, &host->devices, node) {
+		if (!dpi->enabled)
+			continue;
+
+		if (dpi->ops && dpi->ops->set_format) {
+			ret = dpi->ops->set_format(dpi, dpi->next_format);
+			if (ret)
+				goto err_restore_formats;
+		}
+	}
+
+	list_for_each_entry(dpi, &host->devices, node) {
+		dpi->current_format = dpi->next_format;
+	}
+
+	host->current_format = bus_fmt;
+
+	mutex_unlock(&host->lock);
+
+	return 0;
+
+err_restore_formats:
+	list_for_each_entry(dpi, &host->devices, node) {
+		if (!dpi->enabled)
+			continue;
+
+		if (dpi->ops && dpi->ops->set_format)
+			dpi->ops->set_format(dpi, dpi->current_format);
+	}
+
+	host->ops->set_format(host, host->current_format);
+
+err_unlock:
+	mutex_unlock(&host->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(mipi_dpi_host_apply_format);
+
+/**
+ * mipi_dpi_attach - attach a DPI device to its DPI host
+ * @dpi: DPI peripheral
+ */
+int mipi_dpi_attach(struct mipi_dpi_device *dpi)
+{
+	const struct mipi_dpi_host_ops *ops = dpi->host->ops;
+	bool agreed = false;
+	int ret;
+	int i;
+
+	if (!ops || !ops->attach)
+		return -ENOSYS;
+
+	if (!dpi->num_supported_formats)
+		return -EINVAL;
+
+	if (dpi->num_supported_formats == 1)
+		dpi->current_format = dpi->supported_formats[0];
+	else if (!dpi->ops || !dpi->ops->set_format)
+		return -EINVAL;
+
+	for (i = 0; !agreed && i < dpi->host->num_supported_formats; i++) {
+		enum video_bus_format hfmt = dpi->host->supported_formats[i];
+		int j;
+
+		for (j = 0; j < dpi->num_supported_formats; j++) {
+			if (hfmt != dpi->host->supported_formats[i])
+				continue;
+
+			agreed = true;
+			break;
+		}
+	}
+
+	if (!agreed)
+		return -ENOTSUPP;
+
+	mutex_lock(&dpi->host->lock);
+	ret = ops->attach(dpi->host, dpi);
+	if (!ret)
+		list_add_tail(&dpi->node, &dpi->host->devices);
+	mutex_unlock(&dpi->host->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(mipi_dpi_attach);
+
+/**
+ * mipi_dpi_detach - detach a DPI device from its DPI host
+ * @dpi: DPI peripheral
+ */
+int mipi_dpi_detach(struct mipi_dpi_device *dpi)
+{
+	const struct mipi_dpi_host_ops *ops = dpi->host->ops;
+	int ret;
+
+	if (!ops || !ops->detach)
+		return -ENOSYS;
+
+	mutex_lock(&dpi->host->lock);
+	ret = ops->detach(dpi->host, dpi);
+	if (!ret)
+		list_del(&dpi->node);
+	mutex_unlock(&dpi->host->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(mipi_dpi_detach);
+
+static int mipi_dpi_drv_probe(struct device *dev)
+{
+	struct mipi_dpi_driver *drv = to_mipi_dpi_driver(dev->driver);
+	struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev);
+
+	return drv->probe(dpi);
+}
+
+static int mipi_dpi_drv_remove(struct device *dev)
+{
+	struct mipi_dpi_driver *drv = to_mipi_dpi_driver(dev->driver);
+	struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev);
+
+	return drv->remove(dpi);
+}
+
+static void mipi_dpi_drv_shutdown(struct device *dev)
+{
+	struct mipi_dpi_driver *drv = to_mipi_dpi_driver(dev->driver);
+	struct mipi_dpi_device *dpi = to_mipi_dpi_device(dev);
+
+	drv->shutdown(dpi);
+}
+
+/**
+ * mipi_dpi_driver_register - register a driver for DPI devices
+ * @drv: DPI driver structure
+ */
+int mipi_dpi_driver_register(struct mipi_dpi_driver *drv)
+{
+	drv->driver.bus = &mipi_dpi_bus_type;
+	if (drv->probe)
+		drv->driver.probe = mipi_dpi_drv_probe;
+	if (drv->remove)
+		drv->driver.remove = mipi_dpi_drv_remove;
+	if (drv->shutdown)
+		drv->driver.shutdown = mipi_dpi_drv_shutdown;
+
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL(mipi_dpi_driver_register);
+
+/**
+ * mipi_dpi_driver_unregister - unregister a driver for DPI devices
+ * @drv: DPI driver structure
+ */
+void mipi_dpi_driver_unregister(struct mipi_dpi_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL(mipi_dpi_driver_unregister);
+
+static int __init mipi_dpi_bus_init(void)
+{
+	return bus_register(&mipi_dpi_bus_type);
+}
+postcore_initcall(mipi_dpi_bus_init);
+
+MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
+MODULE_DESCRIPTION("MIPI DPI Bus");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/include/drm/drm_mipi_dpi.h b/include/drm/drm_mipi_dpi.h
new file mode 100644
index 0000000..6388c3f
--- /dev/null
+++ b/include/drm/drm_mipi_dpi.h
@@ -0,0 +1,169 @@ 
+/*
+ * MIPI DPI Bus
+ *
+ * Copyright (C) 2014, Atmel
+ * Copyright (C) 2014, Free Electrons
+ *
+ * Author: Boris Brezillon <boris.brezillon@free-electrons.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_DPI_H__
+#define __DRM_MIPI_DPI_H__
+
+#include <linux/device.h>
+#include <linux/video-bus-format.h>
+
+struct mipi_dpi_host;
+struct mipi_dpi_device;
+
+/**
+ * struct mipi_dpi_host_ops - DPI bus operations
+ * @attach: attach DPI device to DPI host
+ * @detach: detach DPI device from DPI host
+ * @best_format: choose best video bus format depending on the connected
+ *		 devices.
+ * @set_format: configure the DPI bus to use a specific video bus format
+ */
+struct mipi_dpi_host_ops {
+	int (*attach)(struct mipi_dpi_host *host,
+		      struct mipi_dpi_device *dpi);
+	int (*detach)(struct mipi_dpi_host *host,
+		      struct mipi_dpi_device *dpi);
+	int (*best_format)(struct mipi_dpi_host *host,
+			   enum video_bus_format *fmt);
+	int (*set_format)(struct mipi_dpi_host *host,
+			  enum video_bus_format fmt);
+};
+
+/**
+ * struct mipi_dpi_host - DPI host device
+ * @dev: device exposing the DPI host
+ * @ddev: drm device, typically used by dpi devices to register new
+ *	  encoders/connectors
+ * @of_node: DPI host device node
+ * @possible_crtcs: the crtc this dpi host is attached to, typically
+ *		    used by dpi devices to register new encoders/connectors
+ * @current_format: the current video bus format
+ * @supported_formats: a array of supported formats
+ * @num_supported_formats: the number of formats contained in the
+ *			   supported_formats array
+ * @devices: list of DPI devices attached to this host
+ * @lock: lock to access DPI devices
+ * @ops: DPI host operations
+ */
+struct mipi_dpi_host {
+	struct device *dev;
+	struct drm_device *ddev;
+	struct device_node *of_node;
+	u32 possible_crtcs;
+	enum video_bus_format current_format;
+	const enum video_bus_format *supported_formats;
+	int num_supported_formats;
+	struct list_head devices;
+	struct mutex lock;
+	const struct mipi_dpi_host_ops *ops;
+};
+
+void mipi_dpi_host_init(struct mipi_dpi_host *host);
+int mipi_dpi_host_register(struct mipi_dpi_host *host);
+void mipi_dpi_host_unregister(struct mipi_dpi_host *host);
+int mipi_dpi_host_apply_format(struct mipi_dpi_host *host);
+
+/**
+ * struct mipi_dpi_device_ops - DPI device operations
+ * @set_format: configure the video bus format to be used on this device
+ */
+struct mipi_dpi_device_ops {
+	int (*set_format)(struct mipi_dpi_device *dev,
+			  enum video_bus_format format);
+};
+
+/**
+ * struct mipi_dpi_device - DPI peripheral device
+ * @node: DPI device list entry
+ * @host: DPI host for this peripheral
+ * @dev: driver model device node for this peripheral
+ * @id: the DPI device id on the bus
+ * @supported_formats: a array of supported formats
+ * @num_supported_formats: the number of formats contained in the
+ *			   supported_formats array
+ * @current_format: the current video bus format
+ * @next_format: the next video bus format to be set when
+ *		 mipi_dpi_host_apply_format is called
+ * @enabled: set to true is the DPI device is enabled (slave device is
+ *	     active)
+ * @ops: DPI device operations
+ */
+struct mipi_dpi_device {
+	struct list_head node;
+	struct mipi_dpi_host *host;
+	struct device dev;
+	unsigned int id;
+	const enum video_bus_format *supported_formats;
+	int num_supported_formats;
+	enum video_bus_format current_format;
+	enum video_bus_format next_format;
+	bool enabled;
+	const struct mipi_dpi_device_ops *ops;
+};
+
+static inline struct mipi_dpi_device *to_mipi_dpi_device(struct device *dev)
+{
+	return container_of(dev, struct mipi_dpi_device, dev);
+}
+
+int mipi_dpi_attach(struct mipi_dpi_device *dpi);
+int mipi_dpi_detach(struct mipi_dpi_device *dpi);
+
+static inline void mipi_dpi_enable(struct mipi_dpi_device *dpi)
+{
+	dpi->enabled = true;
+}
+
+static inline void mipi_dpi_disable(struct mipi_dpi_device *dpi)
+{
+	dpi->enabled = false;
+}
+
+/**
+ * struct mipi_dpi_driver - DPI driver
+ * @driver: device driver model driver
+ * @probe: callback for device binding
+ * @remove: callback for device unbinding
+ * @shutdown: called at shutdown time to quiesce the device
+ */
+struct mipi_dpi_driver {
+	struct device_driver driver;
+	int(*probe)(struct mipi_dpi_device *dpi);
+	int(*remove)(struct mipi_dpi_device *dpi);
+	void (*shutdown)(struct mipi_dpi_device *dpi);
+};
+
+static inline struct mipi_dpi_driver *
+to_mipi_dpi_driver(struct device_driver *driver)
+{
+	return container_of(driver, struct mipi_dpi_driver, driver);
+}
+
+static inline void *mipi_dpi_get_drvdata(const struct mipi_dpi_device *dpi)
+{
+	return dev_get_drvdata(&dpi->dev);
+}
+
+static inline void mipi_dpi_set_drvdata(struct mipi_dpi_device *dpi, void *data)
+{
+	dev_set_drvdata(&dpi->dev, data);
+}
+
+int mipi_dpi_driver_register(struct mipi_dpi_driver *driver);
+void mipi_dpi_driver_unregister(struct mipi_dpi_driver *driver);
+
+#define module_mipi_dpi_driver(__mipi_dpi_driver) \
+	module_driver(__mipi_dpi_driver, mipi_dpi_driver_register, \
+			mipi_dpi_driver_unregister)
+
+#endif /* __DRM_MIPI_DPI__ */