All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/7] drm/tegra: Add DSI and panel support
@ 2013-11-11 12:00 Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 1/7] drm: Add DSI bus infrastructure Thierry Reding
                   ` (6 more replies)
  0 siblings, 7 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

Hi,

This third version of the series reworks some aspects of DSI and panel
support. There is now a DSI bus type, which can be used to instantiate
devices that represent DSI peripherals and register controllers as DSI
hosts. This is currently somewhat DT-centric, but all hardware that I
have access to boots from DT, so I couldn't test any other code anyway
and decided to leave that up until someone actually needs it.

Patch 1 adds the DSI bus type, which provides functions to instantiate
DSI peripherals as devices that can be tied into Linux' driver model.
Similarly a DSI host can be created (or embedded within the private
structure of a DSI controller driver), which DSI peripherals can talk
to. It also exposes a DSI driver, which is essentially a very think
wrapper around the standard struct device_driver and allows to easily
match DSI drivers to DSI devices (again, only DT-style matching is
supported so far).

Patch 2 adds the DRM panel framework. Not much has changed since the
previous version. Mostly smaller fixes and a way to control the panel
brightness, which would usually be tied to a struct backlight_device
but doesn't necessarily have to be, so I provided separate accessors
that can be used by drivers as they see fit.

Patch 3 implements the simple panel driver. The most notable changes
since v2 are that the brightness accessors are now implemented if a
backlight device is associated with the panel. Furthermore the driver
now supports DSI devices using the new DSI infrastructure, which at
the same time sharing most of the code with the platform driver for
panels not connected via a bus, such as dumb RGB/LVDS panels.

An implementation of the panel support for Tegra is provided in patch 4.
Due to how DSI devices are instantiated (namely as children of the DSI
controller after it has been probed), panels are now hotpluggable at
runtime. This sounds less scary than it actually is. It merely means
that the code can deal with a panel not being present at probe time
(which will never be the case for DSI). Depending on load order the
panel might become available only until rather late, and there is code
to deal with that as well (by leveraging the hotplug detection mechanism
in DRM). I haven't been able to produce this case yet, so that that code
path is currently untested, but it should work...

Furthermore this patch exposes a "brightness" property to userspace that
can be used to control the backlight associated with a panel. The aim is
to provide a standard way for userspace to access the correct backlight
and not rely on some heuristic to determine the correct sysfs path to
use for backlight control.

Patch 5 implements a driver for the MIPI pad calibration hardware found
on Tegra SoCs. It isn't very relevant to the DSI patches but is included
here for the sake of completeness.

The DSI host implementation for Tegra SoCs is provided in patch 6. That
contains some code to configure the MIPI D-PHY which might be useful in
other drivers, but I've kept it in the Tegra driver for now. It is
nicely split into separate files so would be easy to move out if needed.

Patch 7 contains a work-in-progress implementation of DSI command mode
for Tegra. This isn't cleaned up yet and contains various hacks to make
it work. Also when run while the controller is in video mode and sending
image data, the switch back to video mode after the message has been
transferred doesn't work properly and causes the display to no longer
work. When run before switching to video mode, however, this seems to
work properly. Things like short writes respond with the proper trigger
message and such. The goal for this was to be able to identify panels
using their device descriptor block (DDB). Unfortunately the display
panel that I have reads back only zeros (the framing of the messages
looks correct, though), so I somewhat lost interest in making this work
correctly.

Eventually I could envision turning this into something much bigger. If
for instance somebody has access to a panel from which a proper DDB can
be read, generic helpers could be written around this and panels probed
not via DT but also directly by matching against the supplier and other
data found in the DDB. In the meantime I'll try to find out if I can get
access to such hardware so that I can finish this up properly.

Thierry

Thierry Reding (7):
  drm: Add DSI bus infrastructure
  drm: Add panel support
  drm/panel: Add simple panel support
  drm/tegra: Implement panel support
  gpu: host1x: Add MIPI pad calibration support
  drm/tegra: Add DSI support
  WIP: drm/tegra: Implement DSI transfers

 .../bindings/gpu/nvidia,tegra20-host1x.txt         |   16 +
 .../bindings/misc/nvidia,tegra114-mipi.txt         |   37 +
 .../devicetree/bindings/panel/auo,b101aw03.txt     |    7 +
 .../bindings/panel/chunghwa,claa101wb03.txt        |    7 +
 .../bindings/panel/panasonic,vvx10f004b00.txt      |    7 +
 .../devicetree/bindings/panel/simple-panel.txt     |   21 +
 drivers/gpu/drm/Kconfig                            |    6 +
 drivers/gpu/drm/Makefile                           |    4 +
 drivers/gpu/drm/drm_dsi.c                          |  306 ++++++
 drivers/gpu/drm/drm_panel.c                        |  100 ++
 drivers/gpu/drm/panel/Kconfig                      |   20 +
 drivers/gpu/drm/panel/Makefile                     |    1 +
 drivers/gpu/drm/panel/panel-simple.c               |  467 +++++++++
 drivers/gpu/drm/tegra/Kconfig                      |    2 +
 drivers/gpu/drm/tegra/Makefile                     |    2 +
 drivers/gpu/drm/tegra/dc.h                         |    2 +
 drivers/gpu/drm/tegra/drm.c                        |   10 +-
 drivers/gpu/drm/tegra/drm.h                        |    5 +
 drivers/gpu/drm/tegra/dsi.c                        | 1087 ++++++++++++++++++++
 drivers/gpu/drm/tegra/dsi.h                        |  134 +++
 drivers/gpu/drm/tegra/mipi-phy.c                   |  137 +++
 drivers/gpu/drm/tegra/mipi-phy.h                   |   65 ++
 drivers/gpu/drm/tegra/output.c                     |   95 +-
 drivers/gpu/host1x/Makefile                        |    1 +
 drivers/gpu/host1x/dev.c                           |   17 +-
 drivers/gpu/host1x/dev.h                           |    2 +
 drivers/gpu/host1x/mipi.c                          |  272 +++++
 include/drm/drm_dsi.h                              |  206 ++++
 include/drm/drm_panel.h                            |  114 ++
 include/linux/host1x.h                             |    6 +
 30 files changed, 3147 insertions(+), 9 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt
 create mode 100644 Documentation/devicetree/bindings/panel/auo,b101aw03.txt
 create mode 100644 Documentation/devicetree/bindings/panel/chunghwa,claa101wb03.txt
 create mode 100644 Documentation/devicetree/bindings/panel/panasonic,vvx10f004b00.txt
 create mode 100644 Documentation/devicetree/bindings/panel/simple-panel.txt
 create mode 100644 drivers/gpu/drm/drm_dsi.c
 create mode 100644 drivers/gpu/drm/drm_panel.c
 create mode 100644 drivers/gpu/drm/panel/Kconfig
 create mode 100644 drivers/gpu/drm/panel/Makefile
 create mode 100644 drivers/gpu/drm/panel/panel-simple.c
 create mode 100644 drivers/gpu/drm/tegra/dsi.c
 create mode 100644 drivers/gpu/drm/tegra/dsi.h
 create mode 100644 drivers/gpu/drm/tegra/mipi-phy.c
 create mode 100644 drivers/gpu/drm/tegra/mipi-phy.h
 create mode 100644 drivers/gpu/host1x/mipi.c
 create mode 100644 include/drm/drm_dsi.h
 create mode 100644 include/drm/drm_panel.h

-- 
1.8.4.2

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  2013-11-12 14:14   ` Andrzej Hajda
  2013-11-11 12:00 ` [PATCH v3 2/7] drm: Add panel support Thierry Reding
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

In order to support DSI peripherals, add a DSI bus type that devices and
drivers can be registered with.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 drivers/gpu/drm/Kconfig   |   4 +
 drivers/gpu/drm/Makefile  |   2 +
 drivers/gpu/drm/drm_dsi.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_dsi.h     | 206 +++++++++++++++++++++++++++++++
 4 files changed, 518 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_dsi.c
 create mode 100644 include/drm/drm_dsi.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index f86427591167..7faefcdd6854 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_DSI
+	bool
+	depends on DRM
+
 config DRM_USB
 	tristate
 	depends on DRM
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index cc08b845f965..eef34abc1e45 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-dsi-y   := drm_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_DSI)	+= drm_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_dsi.c b/drivers/gpu/drm/drm_dsi.c
new file mode 100644
index 000000000000..bead3cc0e9e3
--- /dev/null
+++ b/drivers/gpu/drm/drm_dsi.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#include <drm/drm_dsi.h>
+
+static int dsi_device_match(struct device *dev, struct device_driver *drv)
+{
+	if (of_driver_match_device(dev, drv))
+		return 1;
+
+	return 0;
+}
+
+static struct bus_type dsi_bus_type = {
+	.name = "dsi",
+	.match = dsi_device_match,
+};
+
+static void dsi_device_release(struct device *dev)
+{
+	struct dsi_device *dsi = to_dsi_device(dev);
+
+	of_node_put(dsi->dev.of_node);
+	dsi_host_put(dsi->host);
+	kfree(dsi);
+}
+
+static struct dsi_device *dsi_device_alloc(struct dsi_host *host)
+{
+	struct dsi_device *dsi;
+
+	if (!dsi_host_get(host))
+		return ERR_PTR(-EINVAL);
+
+	dsi = kzalloc(sizeof(*dsi), GFP_KERNEL);
+	if (!dsi) {
+		dsi_host_put(host);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	dsi->host = dsi_host_get(host);
+
+	dsi->dev.parent = host->dev;
+	dsi->dev.bus = &dsi_bus_type;
+	dsi->dev.release = dsi_device_release;
+
+	device_initialize(&dsi->dev);
+
+	return dsi;
+}
+
+static int dsi_device_add(struct dsi_device *dsi)
+{
+	struct dsi_host *host = dsi->host;
+	int err;
+
+	dev_set_name(&dsi->dev, "%s.%u", dev_name(host->dev), dsi->channel);
+
+	err = device_add(&dsi->dev);
+	if (err < 0) {
+		dsi_device_put(dsi);
+		return err;
+	}
+
+	return 0;
+}
+
+static int of_dsi_host_register(struct dsi_host *host)
+{
+	if (!host->dev->of_node)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int of_dsi_register_devices(struct dsi_host *host)
+{
+	struct device_node *np;
+
+	if (!host->dev->of_node)
+		return -ENODEV;
+
+	for_each_available_child_of_node(host->dev->of_node, np) {
+		struct dsi_device *dsi;
+		u32 value;
+		int err;
+
+		dsi = dsi_device_alloc(host);
+		if (IS_ERR(dsi)) {
+			dev_err(host->dev,
+				"failed to allocate DSI device for %s: %ld\n",
+				np->full_name, PTR_ERR(dsi));
+			continue;
+		}
+
+		dsi->dev.of_node = of_node_get(np);
+
+		err = of_property_read_u32(np, "reg", &value);
+		if (err) {
+			dev_err(host->dev,
+				"device %s has no valid 'reg' property: %d\n",
+				np->full_name, err);
+			dsi_device_put(dsi);
+			continue;
+		}
+
+		if (value > 3) {
+			dev_err(host->dev,
+				"device %s has invalid virtual channel %u\n",
+				np->full_name, value);
+			dsi_device_put(dsi);
+			continue;
+		}
+
+		dsi->channel = value;
+
+		err = dsi_device_add(dsi);
+		if (err < 0) {
+			dev_err(host->dev,
+				"failed to add DSI device for %s: %d\n",
+				np->full_name, err);
+			dsi_device_put(dsi);
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+int dsi_host_register(struct dsi_host *host)
+{
+	int err;
+
+	if (IS_ENABLED(CONFIG_OF)) {
+		err = of_dsi_host_register(host);
+		if (err < 0)
+			return err;
+	}
+
+	if (IS_ENABLED(CONFIG_OF)) {
+		err = of_dsi_register_devices(host);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(dsi_host_register);
+
+static int __dsi_device_unregister(struct device *dev, void *data)
+{
+	device_unregister(dev);
+	return 0;
+}
+
+/**
+ * dsi_host_unregister() - unregister a DSI host controller
+ * @host: a DSI host controller
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_host_unregister(struct dsi_host *host)
+{
+	device_for_each_child(host->dev, NULL, __dsi_device_unregister);
+
+	return 0;
+}
+EXPORT_SYMBOL(dsi_host_unregister);
+
+/**
+ * dsi_host_transfer() - transfer a DSI message between host and peripheral
+ * @host: DSI host
+ * @msg: DSI message to transfer
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+ssize_t dsi_host_transfer(struct dsi_host *host, struct dsi_msg *msg)
+{
+	if (host->ops && host->ops->transfer)
+		return host->ops->transfer(host, msg);
+
+	return -ENOSYS;
+}
+EXPORT_SYMBOL(dsi_host_transfer);
+
+/**
+ * dsi_device_attach() - attach a DSI peripheral to its DSI host
+ * @device: DSI peripheral
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_device_attach(struct dsi_device *device)
+{
+	if (device->host->ops && device->host->ops->attach)
+		return device->host->ops->attach(device->host, device);
+
+	return -ENOSYS;
+}
+EXPORT_SYMBOL(dsi_device_attach);
+
+/**
+ * dsi_device_detach() - detach a DSI peripheral from its DSI host
+ * @device: DSI peripheral
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_device_detach(struct dsi_device *device)
+{
+	if (device->host->ops && device->host->ops->detach)
+		return device->host->ops->detach(device->host, device);
+
+	return -ENOSYS;
+}
+EXPORT_SYMBOL(dsi_device_detach);
+
+static int dsi_driver_probe(struct device *dev)
+{
+	const struct dsi_driver *drv = to_dsi_driver(dev->driver);
+	struct dsi_device *dsi = to_dsi_device(dev);
+
+	return drv->probe(dsi);
+}
+
+static int dsi_driver_remove(struct device *dev)
+{
+	const struct dsi_driver *drv = to_dsi_driver(dev->driver);
+	struct dsi_device *dsi = to_dsi_device(dev);
+
+	return drv->remove(dsi);
+}
+
+static void dsi_driver_shutdown(struct device *dev)
+{
+	const struct dsi_driver *drv = to_dsi_driver(dev->driver);
+	struct dsi_device *dsi = to_dsi_device(dev);
+
+	drv->shutdown(dsi);
+}
+
+/**
+ * dsi_register_driver() - register a DSI driver
+ * @drv: DSI driver
+ *
+ * Returns 0 on success or a negative error-code on failure.
+ */
+int dsi_register_driver(struct dsi_driver *drv)
+{
+	drv->driver.bus = &dsi_bus_type;
+
+	if (drv->probe)
+		drv->driver.probe = dsi_driver_probe;
+
+	if (drv->remove)
+		drv->driver.remove = dsi_driver_remove;
+
+	if (drv->shutdown)
+		drv->driver.shutdown = dsi_driver_shutdown;
+
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL(dsi_register_driver);
+
+/**
+ * dsi_unregister_driver() - unregister a DSI driver
+ * @drv: DSI driver
+ */
+void dsi_unregister_driver(struct dsi_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL(dsi_unregister_driver);
+
+static int __init dsi_init(void)
+{
+	return bus_register(&dsi_bus_type);
+}
+postcore_initcall(dsi_init);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("DRM DSI infrastructure");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/include/drm/drm_dsi.h b/include/drm/drm_dsi.h
new file mode 100644
index 000000000000..0886160b9aa2
--- /dev/null
+++ b/include/drm/drm_dsi.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef _DRM_DSI_H_
+#define _DRM_DSI_H_
+
+#include <linux/types.h>
+
+struct dsi_device;
+struct dsi_host;
+
+/*
+ * DSI packet data types
+ */
+
+/* processor-sourced packets */
+#define DSI_CMD_VSYNC_START 0x01
+#define DSI_CMD_VSYNC_END 0x11
+#define DSI_CMD_HSYNC_START 0x21
+#define DSI_CMD_HSYNC_END 0x31
+#define DSI_CMD_EOT 0x08
+#define DSI_CMD_COLOR_MODE_OFF 0x02
+#define DSI_CMD_COLOR_MODE_ON 0x12
+#define DSI_CMD_SHUT_DOWN 0x22
+#define DSI_CMD_TURN_ON 0x32
+#define DSI_CMD_GEN_SHORT_WRITE_0 0x03
+#define DSI_CMD_GEN_SHORT_WRITE_1 0x13
+#define DSI_CMD_GEN_SHORT_WRITE_2 0x23
+#define DSI_CMD_GEN_SHORT_READ_0 0x04
+#define DSI_CMD_GEN_SHORT_READ_1 0x14
+#define DSI_CMD_GEN_SHORT_READ_2 0x24
+#define DSI_CMD_DCS_SHORT_WRITE_0 0x05
+#define DSI_CMD_DCS_SHORT_WRITE_1 0x15
+#define DSI_CMD_DCS_SHORT_READ 0x06
+#define DSI_CMD_SET_MAX_RETURN_PACKET_SIZE 0x37
+#define DSI_CMD_NULL 0x09
+#define DSI_CMD_BLANK 0x19
+#define DSI_CMD_GEN_LONG_WRITE 0x29
+#define DSI_CMD_DCS_LONG_WRITE 0x39
+#define DSI_CMD_YCbCr422_20 0x0c
+#define DSI_CMD_YCbCr422_24 0x1c
+#define DSI_CMD_YCbCr422_16 0x2c
+#define DSI_CMD_RGB30 0x0d
+#define DSI_CMD_RGB36 0x1d
+#define DSI_CMD_YCbCr420 0x3d
+#define DSI_CMD_RGB16 0x0e
+#define DSI_CMD_RGB18 0x1e
+#define DSI_CMD_RGB18NP 0x2e
+#define DSI_CMD_RGB24 0x3e
+
+/* peripheral-sourced */
+#define DSI_RSP_ACK_ERR 0x02
+#define DSI_RSP_EOT 0x08
+#define DSI_RSP_GEN_SHORT_READ_1 0x11
+#define DSI_RSP_GEN_SHORT_READ_2 0x12
+#define DSI_RSP_GEN_LONG_READ 0x1a
+#define DSI_RSP_DCS_LONG_READ 0x1c
+#define DSI_RSP_DCS_SHORT_READ_1 0x21
+#define DSI_RSP_DCS_SHORT_READ_2 0x22
+
+#define DSI_ACK 0x84
+#define DSI_ESC 0x87
+
+/**
+ * struct dsi_msg - DSI command message
+ * @channel: virtual channel to send the message to
+ * @type: data ID of the message
+ * @tx_len: length of transmission buffer
+ * @tx: transmission buffer
+ * @rx_len: length of reception buffer
+ * @rx: reception buffer
+ */
+struct dsi_msg {
+	u8 channel;
+	u8 type;
+
+	size_t tx_len;
+	void *tx;
+
+	size_t rx_len;
+	void *rx;
+};
+
+/**
+ * struct dsi_host_ops - DSI host operations
+ * @attach: called when a peripheral is attached to the host
+ * @detach: called when a peripheral is detached from the host
+ * @transfer: transfer a DSI command message to a peripheral
+ */
+struct dsi_host_ops {
+	int (*attach)(struct dsi_host *host, struct dsi_device *device);
+	int (*detach)(struct dsi_host *host, struct dsi_device *device);
+	ssize_t (*transfer)(struct dsi_host *host, struct dsi_msg *msg);
+};
+
+/**
+ * struct dsi_host - DSI host
+ * @dev: device providing the DSI host functionality
+ * @ops: pointer to DSI host operations
+ */
+struct dsi_host {
+	struct device *dev;
+
+	const struct dsi_host_ops *ops;
+};
+
+static inline struct dsi_host *dsi_host_get(struct dsi_host *host)
+{
+	if (!host || !get_device(host->dev))
+		return NULL;
+
+	return host;
+}
+
+static inline void dsi_host_put(struct dsi_host *host)
+{
+	if (host)
+		put_device(host->dev);
+}
+
+int dsi_host_register(struct dsi_host *host);
+int dsi_host_unregister(struct dsi_host *host);
+
+ssize_t dsi_host_transfer(struct dsi_host *host, struct dsi_msg *msg);
+
+/**
+ * struct dsi_device - DSI peripheral
+ * @host: DSI host that this peripheral is attached to
+ * @dev: device to tie the peripheral into the device tree
+ * @channel: virtual channel of the peripheral
+ */
+struct dsi_device {
+	struct dsi_host *host;
+	struct device dev;
+
+	unsigned int channel;
+};
+
+static inline struct dsi_device *to_dsi_device(struct device *dev)
+{
+	return dev ? container_of(dev, struct dsi_device, dev) : NULL;
+}
+
+static inline struct dsi_device *dsi_device_get(struct dsi_device *dsi)
+{
+	if (!dsi || !get_device(&dsi->dev))
+		return NULL;
+
+	return dsi;
+}
+
+static inline void dsi_device_put(struct dsi_device *dsi)
+{
+	if (dsi)
+		put_device(&dsi->dev);
+}
+
+int dsi_device_attach(struct dsi_device *device);
+int dsi_device_detach(struct dsi_device *device);
+
+/**
+ * struct dsi_driver - DSI driver
+ * @driver: device driver model driver
+ * @probe: callback for device binding
+ * @remove: callback for device unbinding
+ * @shutdown: callback for device shutdown
+ */
+struct dsi_driver {
+	struct device_driver driver;
+	int (*probe)(struct dsi_device *dsi);
+	int (*remove)(struct dsi_device *dsi);
+	void (*shutdown)(struct dsi_device *dsi);
+};
+
+static inline struct dsi_driver *to_dsi_driver(struct device_driver *drv)
+{
+	return drv ? container_of(drv, struct dsi_driver, driver) : NULL;
+}
+
+int dsi_register_driver(struct dsi_driver *drv);
+void dsi_unregister_driver(struct dsi_driver *drv);
+
+#define module_dsi_driver(__dsi_driver)				\
+	module_driver(__dsi_driver, dsi_register_driver,	\
+			dsi_unregister_driver)
+
+#endif
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 2/7] drm: Add panel support
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 1/7] drm: Add DSI bus infrastructure Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 3/7] drm/panel: Add simple " Thierry Reding
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

Add a very simple framework to register and lookup panels. Panel drivers
can initialize a DRM panel and register it with the framework, allowing
them to be retrieved and used by display drivers. Currently only support
for DPMS and obtaining panel modes is provided. However it should be
sufficient to enable a large number of panels. The framework should also
be easily extensible to support more sophisticated kinds of panels such
as DSI.

The framework hasn't been tied into the DRM core, even though it should
be easily possible to do so if that's what we want. In the current
implementation, display drivers can simple make use of it to retrieve a
panel, obtain its modes and control its DPMS mode.

Note that this is currently only tested on systems that boot from a
device tree. No glue code has been written yet for systems that use
platform data, but it should be easy to add.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- depend on DRM  (reported by Randy Dunlap)
- propagate errors from .enable() and .disable()
- add backlight brightness accessors
- hide DRM_PANEL Kconfig symbol

 drivers/gpu/drm/Kconfig       |   2 +
 drivers/gpu/drm/Makefile      |   1 +
 drivers/gpu/drm/drm_panel.c   | 100 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/panel/Kconfig |   5 ++
 include/drm/drm_panel.h       | 114 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 222 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_panel.c
 create mode 100644 drivers/gpu/drm/panel/Kconfig
 create mode 100644 include/drm/drm_panel.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 7faefcdd6854..28407af6e642 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -195,3 +195,5 @@ source "drivers/gpu/drm/qxl/Kconfig"
 source "drivers/gpu/drm/msm/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
+
+source "drivers/gpu/drm/panel/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index eef34abc1e45..f521f4a71fd2 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -18,6 +18,7 @@ drm-y       :=	drm_auth.o drm_buffer.o drm_bufs.o drm_cache.o \
 drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
 drm-$(CONFIG_PCI) += ati_pcigart.o
+drm-$(CONFIG_DRM_PANEL) += drm_panel.o
 
 drm-dsi-y   := drm_dsi.o
 drm-usb-y   := drm_usb.o
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
new file mode 100644
index 000000000000..2ef988e037b7
--- /dev/null
+++ b/drivers/gpu/drm/drm_panel.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013, NVIDIA Corporation.  All rights reserved.
+ *
+ * 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 AUTHORS OR COPYRIGHT HOLDERS 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 <linux/err.h>
+#include <linux/module.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_panel.h>
+
+static DEFINE_MUTEX(panel_lock);
+static LIST_HEAD(panel_list);
+
+void drm_panel_init(struct drm_panel *panel)
+{
+	INIT_LIST_HEAD(&panel->list);
+}
+EXPORT_SYMBOL(drm_panel_init);
+
+int drm_panel_add(struct drm_panel *panel)
+{
+	mutex_lock(&panel_lock);
+	list_add_tail(&panel->list, &panel_list);
+	mutex_unlock(&panel_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_panel_add);
+
+void drm_panel_remove(struct drm_panel *panel)
+{
+	mutex_lock(&panel_lock);
+	list_del_init(&panel->list);
+	mutex_unlock(&panel_lock);
+}
+EXPORT_SYMBOL(drm_panel_remove);
+
+int drm_panel_attach(struct drm_panel *panel, struct drm_connector *connector)
+{
+	if (panel->connector)
+		return -EBUSY;
+
+	panel->connector = connector;
+	panel->drm = connector->dev;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_panel_attach);
+
+int drm_panel_detach(struct drm_panel *panel)
+{
+	panel->connector = NULL;
+	panel->drm = NULL;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_panel_detach);
+
+#ifdef CONFIG_OF
+struct drm_panel *of_drm_find_panel(struct device_node *np)
+{
+	struct drm_panel *panel;
+
+	mutex_lock(&panel_lock);
+
+	list_for_each_entry(panel, &panel_list, list) {
+		if (panel->dev->of_node == np) {
+			mutex_unlock(&panel_lock);
+			return panel;
+		}
+	}
+
+	mutex_unlock(&panel_lock);
+	return NULL;
+}
+EXPORT_SYMBOL(of_drm_find_panel);
+#endif
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("DRM panel infrastructure");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
new file mode 100644
index 000000000000..d1db4ef626fd
--- /dev/null
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -0,0 +1,5 @@
+config DRM_PANEL
+	bool
+	depends on DRM
+	help
+	  Panel registration and lookup framework.
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
new file mode 100644
index 000000000000..898c4c4da552
--- /dev/null
+++ b/include/drm/drm_panel.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2013, NVIDIA Corporation.  All rights reserved.
+ *
+ * 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 AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef __DRM_PANEL_H__
+#define __DRM_PANEL_H__
+
+#include <linux/list.h>
+
+struct drm_connector;
+struct drm_device;
+struct drm_panel;
+
+struct drm_panel_funcs {
+	int (*disable)(struct drm_panel *panel);
+	int (*enable)(struct drm_panel *panel);
+	int (*get_modes)(struct drm_panel *panel);
+
+	int (*get_brightness_range)(struct drm_panel *panel, uint64_t *min,
+				    uint64_t *max);
+	int (*get_brightness)(struct drm_panel *panel, uint64_t *value);
+	int (*set_brightness)(struct drm_panel *panel, uint64_t value);
+};
+
+struct drm_panel {
+	struct drm_device *drm;
+	struct drm_connector *connector;
+	struct device *dev;
+
+	const struct drm_panel_funcs *funcs;
+
+	struct list_head list;
+};
+
+static inline int drm_panel_disable(struct drm_panel *panel)
+{
+	if (panel && panel->funcs && panel->funcs->disable)
+		return panel->funcs->disable(panel);
+
+	return panel ? -ENOSYS : -EINVAL;
+}
+
+static inline int drm_panel_enable(struct drm_panel *panel)
+{
+	if (panel && panel->funcs && panel->funcs->enable)
+		return panel->funcs->enable(panel);
+
+	return panel ? -ENOSYS : -EINVAL;
+}
+
+static inline int drm_panel_get_brightness_range(struct drm_panel *panel,
+						 uint64_t *min, uint64_t *max)
+{
+	if (panel && panel->funcs && panel->funcs->get_brightness_range)
+		return panel->funcs->get_brightness_range(panel, min, max);
+
+	return panel ? -ENOSYS : -EINVAL;
+}
+
+static inline int drm_panel_get_brightness(struct drm_panel *panel,
+					   uint64_t *value)
+{
+	if (panel && panel->funcs && panel->funcs->get_brightness)
+		return panel->funcs->get_brightness(panel, value);
+
+	return panel ? -ENOSYS : -EINVAL;
+}
+
+static inline int drm_panel_set_brightness(struct drm_panel *panel,
+					   uint64_t value)
+{
+	if (panel && panel->funcs && panel->funcs->set_brightness)
+		return panel->funcs->set_brightness(panel, value);
+
+	return panel ? -ENOSYS : -EINVAL;
+}
+
+void drm_panel_init(struct drm_panel *panel);
+
+int drm_panel_add(struct drm_panel *panel);
+void drm_panel_remove(struct drm_panel *panel);
+
+int drm_panel_attach(struct drm_panel *panel, struct drm_connector *connector);
+int drm_panel_detach(struct drm_panel *panel);
+
+#ifdef CONFIG_OF
+struct drm_panel *of_drm_find_panel(struct device_node *np);
+#else
+static inline struct drm_panel *of_drm_find_panel(struct device_node *np)
+{
+	return NULL;
+}
+#endif
+
+#endif
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 3/7] drm/panel: Add simple panel support
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 1/7] drm: Add DSI bus infrastructure Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 2/7] drm: Add panel support Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 4/7] drm/tegra: Implement " Thierry Reding
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

Add a driver for simple panels. Such panels can have a regulator that
provides the supply voltage and a separate GPIO to enable the panel.
Optionally the panels can have a backlight associated with them so it
can be enabled or disabled according to the panel's power management
mode.

Support is added for three panels: An AU Optronics 10.1" WSVGA, a
Chunghwa Picture Tubes 10.1" WXGA and a Panasonic 10.1 WUXGA TFT LCD
panel.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- propagate errors from .enable() and .disable()
- implement backlight brightness accessors
- allow building as a module
- add DSI device support
- fixup license string

Changes in v2:
- fixup pixel clock for Panasonic VVX10F004B00

 .../devicetree/bindings/panel/auo,b101aw03.txt     |   7 +
 .../bindings/panel/chunghwa,claa101wb03.txt        |   7 +
 .../bindings/panel/panasonic,vvx10f004b00.txt      |   7 +
 .../devicetree/bindings/panel/simple-panel.txt     |  21 +
 drivers/gpu/drm/Makefile                           |   1 +
 drivers/gpu/drm/panel/Kconfig                      |  15 +
 drivers/gpu/drm/panel/Makefile                     |   1 +
 drivers/gpu/drm/panel/panel-simple.c               | 467 +++++++++++++++++++++
 8 files changed, 526 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/panel/auo,b101aw03.txt
 create mode 100644 Documentation/devicetree/bindings/panel/chunghwa,claa101wb03.txt
 create mode 100644 Documentation/devicetree/bindings/panel/panasonic,vvx10f004b00.txt
 create mode 100644 Documentation/devicetree/bindings/panel/simple-panel.txt
 create mode 100644 drivers/gpu/drm/panel/Makefile
 create mode 100644 drivers/gpu/drm/panel/panel-simple.c

diff --git a/Documentation/devicetree/bindings/panel/auo,b101aw03.txt b/Documentation/devicetree/bindings/panel/auo,b101aw03.txt
new file mode 100644
index 000000000000..72e088a4fb3a
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/auo,b101aw03.txt
@@ -0,0 +1,7 @@
+AU Optronics Corporation 10.1" WSVGA TFT LCD panel
+
+Required properties:
+- compatible: should be "auo,b101aw03"
+
+This binding is compatible with the simple-panel binding, which is specified
+in simple-panel.txt in this directory.
diff --git a/Documentation/devicetree/bindings/panel/chunghwa,claa101wb03.txt b/Documentation/devicetree/bindings/panel/chunghwa,claa101wb03.txt
new file mode 100644
index 000000000000..0ab2c05a4c22
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/chunghwa,claa101wb03.txt
@@ -0,0 +1,7 @@
+Chunghwa Picture Tubes Ltd. 10.1" WXGA TFT LCD panel
+
+Required properties:
+- compatible: should be "chunghwa,claa101wb03"
+
+This binding is compatible with the simple-panel binding, which is specified
+in simple-panel.txt in this directory.
diff --git a/Documentation/devicetree/bindings/panel/panasonic,vvx10f004b00.txt b/Documentation/devicetree/bindings/panel/panasonic,vvx10f004b00.txt
new file mode 100644
index 000000000000..d328b0341bf4
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/panasonic,vvx10f004b00.txt
@@ -0,0 +1,7 @@
+Panasonic Corporation 10.1" WUXGA TFT LCD panel
+
+Required properties:
+- compatible: should be "panasonic,vvx10f004b00"
+
+This binding is compatible with the simple-panel binding, which is specified
+in simple-panel.txt in this directory.
diff --git a/Documentation/devicetree/bindings/panel/simple-panel.txt b/Documentation/devicetree/bindings/panel/simple-panel.txt
new file mode 100644
index 000000000000..1341bbf4aa3d
--- /dev/null
+++ b/Documentation/devicetree/bindings/panel/simple-panel.txt
@@ -0,0 +1,21 @@
+Simple display panel
+
+Required properties:
+- power-supply: regulator to provide the supply voltage
+
+Optional properties:
+- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
+- enable-gpios: GPIO pin to enable or disable the panel
+- backlight: phandle of the backlight device attached to the panel
+
+Example:
+
+	panel: panel {
+		compatible = "cptt,claa101wb01";
+		ddc-i2c-bus = <&panelddc>;
+
+		power-supply = <&vdd_pnl_reg>;
+		enable-gpios = <&gpio 90 0>;
+
+		backlight = <&backlight>;
+	};
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index f521f4a71fd2..38acfa0471da 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -62,3 +62,4 @@ obj-$(CONFIG_DRM_QXL) += qxl/
 obj-$(CONFIG_DRM_MSM) += msm/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-y			+= i2c/
+obj-y			+= panel/
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d1db4ef626fd..4f40d198f93d 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -3,3 +3,18 @@ config DRM_PANEL
 	depends on DRM
 	help
 	  Panel registration and lookup framework.
+
+menu "Display Panels"
+	depends on DRM_PANEL
+
+config DRM_PANEL_SIMPLE
+	tristate "support for simple panels"
+	depends on DRM && DRM_PANEL
+	depends on OF
+	help
+	  DRM panel driver for dumb panels that need at most a regulator and
+	  a GPIO to be powered up. Optionally a backlight can be attached so
+	  that it can be automatically turned off when the panel goes into a
+	  low power state.
+
+endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
new file mode 100644
index 000000000000..af9dfa235b94
--- /dev/null
+++ b/drivers/gpu/drm/panel/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
new file mode 100644
index 000000000000..af9aee387341
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2013, NVIDIA Corporation.  All rights reserved.
+ *
+ * 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 AUTHORS OR COPYRIGHT HOLDERS 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 <linux/backlight.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_dsi.h>
+#include <drm/drm_panel.h>
+
+struct panel_desc {
+	const struct drm_display_mode *modes;
+	unsigned int num_modes;
+
+	struct {
+		unsigned int width;
+		unsigned int height;
+	} size;
+};
+
+/* TODO: convert to gpiod_*() API once it's been merged */
+#define GPIO_ACTIVE_LOW	(1 << 0)
+
+struct panel_simple {
+	struct drm_panel base;
+	bool enabled;
+
+	const struct panel_desc *desc;
+
+	struct backlight_device *backlight;
+	struct regulator *supply;
+
+	unsigned long enable_gpio_flags;
+	int enable_gpio;
+};
+
+static inline struct panel_simple *to_panel_simple(struct drm_panel *panel)
+{
+	return container_of(panel, struct panel_simple, base);
+}
+
+static int panel_simple_disable(struct drm_panel *panel)
+{
+	struct panel_simple *p = to_panel_simple(panel);
+
+	if (!p->enabled)
+		return 0;
+
+	if (p->backlight) {
+		p->backlight->props.power = FB_BLANK_POWERDOWN;
+		backlight_update_status(p->backlight);
+	}
+
+	if (gpio_is_valid(p->enable_gpio)) {
+		if (p->enable_gpio_flags & GPIO_ACTIVE_LOW)
+			gpio_set_value(p->enable_gpio, 1);
+		else
+			gpio_set_value(p->enable_gpio, 0);
+	}
+
+	regulator_disable(p->supply);
+	p->enabled = false;
+
+	return 0;
+}
+
+static int panel_simple_enable(struct drm_panel *panel)
+{
+	struct panel_simple *p = to_panel_simple(panel);
+	int err;
+
+	if (p->enabled)
+		return 0;
+
+	err = regulator_enable(p->supply);
+	if (err < 0) {
+		dev_err(panel->dev, "failed to enable supply: %d\n", err);
+		return err;
+	}
+
+	if (gpio_is_valid(p->enable_gpio)) {
+		if (p->enable_gpio_flags & GPIO_ACTIVE_LOW)
+			gpio_set_value(p->enable_gpio, 0);
+		else
+			gpio_set_value(p->enable_gpio, 1);
+	}
+
+	if (p->backlight) {
+		p->backlight->props.power = FB_BLANK_UNBLANK;
+		backlight_update_status(p->backlight);
+	}
+
+	p->enabled = true;
+
+	return 0;
+}
+
+static int panel_simple_get_modes(struct drm_panel *panel)
+{
+	struct panel_simple *p = to_panel_simple(panel);
+	struct drm_display_mode *mode;
+	unsigned int i;
+
+	for (i = 0; i < p->desc->num_modes; i++) {
+		mode = drm_mode_duplicate(panel->drm, &p->desc->modes[i]);
+		if (!mode)
+			return -ENOMEM;
+
+		drm_mode_set_name(mode);
+
+		drm_mode_probed_add(panel->connector, mode);
+	}
+
+	return p->desc->num_modes;
+}
+
+static int panel_simple_get_brightness_range(struct drm_panel *panel,
+					     uint64_t *min, uint64_t *max)
+{
+	struct panel_simple *p = to_panel_simple(panel);
+
+	if (!p->backlight)
+		return -ENODEV;
+
+	*max = p->backlight->props.max_brightness;
+	*min = 0;
+
+	return 0;
+}
+
+static int panel_simple_get_brightness(struct drm_panel *panel,
+				       uint64_t *value)
+{
+	struct panel_simple *p = to_panel_simple(panel);
+
+	if (!p->backlight)
+		return -ENODEV;
+
+	*value = p->backlight->props.brightness;
+
+	return 0;
+}
+
+static int panel_simple_set_brightness(struct drm_panel *panel,
+				       uint64_t value)
+{
+	struct panel_simple *p = to_panel_simple(panel);
+
+	if (!p->backlight)
+		return -ENODEV;
+
+	p->backlight->props.brightness = value;
+	backlight_update_status(p->backlight);
+
+	return 0;
+}
+
+static const struct drm_panel_funcs panel_simple_funcs = {
+	.disable = panel_simple_disable,
+	.enable = panel_simple_enable,
+	.get_modes = panel_simple_get_modes,
+	.get_brightness_range = panel_simple_get_brightness_range,
+	.get_brightness = panel_simple_get_brightness,
+	.set_brightness = panel_simple_set_brightness,
+};
+
+static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
+{
+	struct device_node *backlight;
+	struct panel_simple *panel;
+	enum of_gpio_flags flags;
+	int err;
+
+	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+	if (!panel)
+		return -ENOMEM;
+
+	panel->enabled = false;
+	panel->desc = desc;
+
+	panel->supply = devm_regulator_get(dev, "power");
+	if (IS_ERR(panel->supply))
+		return PTR_ERR(panel->supply);
+
+	panel->enable_gpio = of_get_named_gpio_flags(dev->of_node,
+						     "enable-gpios", 0,
+						     &flags);
+	if (gpio_is_valid(panel->enable_gpio)) {
+		unsigned int value;
+
+		if (flags & OF_GPIO_ACTIVE_LOW)
+			panel->enable_gpio_flags |= GPIO_ACTIVE_LOW;
+
+		err = gpio_request(panel->enable_gpio, "enable");
+		if (err < 0) {
+			dev_err(dev, "failed to request GPIO#%u: %d\n",
+				panel->enable_gpio, err);
+			return err;
+		}
+
+		value = (panel->enable_gpio_flags & GPIO_ACTIVE_LOW) != 0;
+
+		err = gpio_direction_output(panel->enable_gpio, value);
+		if (err < 0) {
+			dev_err(dev, "failed to setup GPIO%u: %d\n",
+				panel->enable_gpio, err);
+			goto free_gpio;
+		}
+	}
+
+	backlight = of_parse_phandle(dev->of_node, "backlight", 0);
+	if (backlight) {
+		panel->backlight = of_find_backlight_by_node(backlight);
+		if (!panel->backlight) {
+			err = -EPROBE_DEFER;
+			goto free_gpio;
+		}
+
+		of_node_put(backlight);
+	}
+
+	drm_panel_init(&panel->base);
+	panel->base.dev = dev;
+	panel->base.funcs = &panel_simple_funcs;
+
+	err = drm_panel_add(&panel->base);
+	if (err < 0)
+		goto free_gpio;
+
+	dev_set_drvdata(dev, panel);
+
+	return 0;
+
+free_gpio:
+	if (gpio_is_valid(panel->enable_gpio))
+		gpio_free(panel->enable_gpio);
+
+	return err;
+}
+
+static int panel_simple_remove(struct device *dev)
+{
+	struct panel_simple *panel = dev_get_drvdata(dev);
+
+	drm_panel_detach(&panel->base);
+	drm_panel_remove(&panel->base);
+
+	panel_simple_disable(&panel->base);
+
+	if (panel->backlight)
+		put_device(&panel->backlight->dev);
+
+	if (gpio_is_valid(panel->enable_gpio))
+		gpio_free(panel->enable_gpio);
+
+	regulator_disable(panel->supply);
+
+	return 0;
+}
+
+static const struct drm_display_mode auo_b101aw03_mode = {
+	.clock = 51450,
+	.hdisplay = 1024,
+	.hsync_start = 1024 + 156,
+	.hsync_end = 1024 + 156 + 8,
+	.htotal = 1024 + 156 + 8 + 156,
+	.vdisplay = 600,
+	.vsync_start = 600 + 16,
+	.vsync_end = 600 + 16 + 6,
+	.vtotal = 600 + 16 + 6 + 16,
+	.vrefresh = 60,
+};
+
+static const struct panel_desc auo_b101aw03 = {
+	.modes = &auo_b101aw03_mode,
+	.num_modes = 1,
+	.size = {
+		.width = 223,
+		.height = 125,
+	},
+};
+
+static const struct drm_display_mode chunghwa_claa101wb01_mode = {
+	.clock = 69300,
+	.hdisplay = 1366,
+	.hsync_start = 1366 + 48,
+	.hsync_end = 1366 + 48 + 32,
+	.htotal = 1366 + 48 + 32 + 20,
+	.vdisplay = 768,
+	.vsync_start = 768 + 16,
+	.vsync_end = 768 + 16 + 8,
+	.vtotal = 768 + 16 + 8 + 16,
+	.vrefresh = 60,
+};
+
+static const struct panel_desc chunghwa_claa101wb01 = {
+	.modes = &chunghwa_claa101wb01_mode,
+	.num_modes = 1,
+	.size = {
+		.width = 223,
+		.height = 125,
+	},
+};
+
+static const struct of_device_id platform_of_match[] = {
+	{
+		.compatible = "auo,b101aw03",
+		.data = &auo_b101aw03,
+	}, {
+		.compatible = "chunghwa,claa101wb01",
+		.data = &chunghwa_claa101wb01
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, platform_of_match);
+
+static int panel_simple_platform_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *id;
+
+	id = of_match_node(platform_of_match, pdev->dev.of_node);
+	if (!id)
+		return -ENODEV;
+
+	return panel_simple_probe(&pdev->dev, id->data);
+}
+
+static int panel_simple_platform_remove(struct platform_device *pdev)
+{
+	return panel_simple_remove(&pdev->dev);
+}
+
+static struct platform_driver panel_simple_platform_driver = {
+	.driver = {
+		.name = "panel-simple",
+		.owner = THIS_MODULE,
+		.of_match_table = platform_of_match,
+	},
+	.probe = panel_simple_platform_probe,
+	.remove = panel_simple_platform_remove,
+};
+
+static const struct drm_display_mode panasonic_vvx10f004b00_mode = {
+	.clock = 157200,
+	.hdisplay = 1920,
+	.hsync_start = 1920 + 154,
+	.hsync_end = 1920 + 154 + 16,
+	.htotal = 1920 + 154 + 16 + 32,
+	.vdisplay = 1200,
+	.vsync_start = 1200 + 17,
+	.vsync_end = 1200 + 17 + 2,
+	.vtotal = 1200 + 17 + 2 + 16,
+	.vrefresh = 60,
+};
+
+static const struct panel_desc panasonic_vvx10f004b00 = {
+	.modes = &panasonic_vvx10f004b00_mode,
+	.num_modes = 1,
+	.size = {
+		.width = 217,
+		.height = 136,
+	},
+};
+
+static const struct of_device_id dsi_of_match[] = {
+	{
+		.compatible = "panasonic,vvx10f004b00",
+		.data = &panasonic_vvx10f004b00
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, dsi_of_match);
+
+static int panel_simple_dsi_probe(struct dsi_device *dsi)
+{
+	const struct of_device_id *id;
+	int err;
+
+	id = of_match_node(dsi_of_match, dsi->dev.of_node);
+	if (!id)
+		return -ENODEV;
+
+	err = panel_simple_probe(&dsi->dev, id->data);
+	if (err < 0)
+		return err;
+
+	return dsi_device_attach(dsi);
+}
+
+static int panel_simple_dsi_remove(struct dsi_device *dsi)
+{
+	int err;
+
+	err = dsi_device_detach(dsi);
+	if (err < 0)
+		dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err);
+
+	return panel_simple_remove(&dsi->dev);
+}
+
+static struct dsi_driver panel_simple_dsi_driver = {
+	.driver = {
+		.name = "panel-simple-dsi",
+		.owner = THIS_MODULE,
+		.of_match_table = dsi_of_match,
+	},
+	.probe = panel_simple_dsi_probe,
+	.remove = panel_simple_dsi_remove,
+};
+
+static int __init panel_simple_init(void)
+{
+	int err;
+
+	err = platform_driver_register(&panel_simple_platform_driver);
+	if (err < 0)
+		return err;
+
+	if (IS_ENABLED(CONFIG_DRM_DSI)) {
+		err = dsi_register_driver(&panel_simple_dsi_driver);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+module_init(panel_simple_init);
+
+static void __exit panel_simple_exit(void)
+{
+	if (IS_ENABLED(CONFIG_DRM_DSI))
+		dsi_unregister_driver(&panel_simple_dsi_driver);
+
+	platform_driver_unregister(&panel_simple_platform_driver);
+}
+module_exit(panel_simple_exit);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("DRM Driver for Simple Panels");
+MODULE_LICENSE("GPL and additional rights");
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 4/7] drm/tegra: Implement panel support
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
                   ` (2 preceding siblings ...)
  2013-11-11 12:00 ` [PATCH v3 3/7] drm/panel: Add simple " Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 5/7] gpu: host1x: Add MIPI pad calibration support Thierry Reding
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

Use the DRM panel framework to attach a panel to an output. If the panel
attached to a connector supports supports the backlight brightness
accessors, a property will be available to allow the brightness to be
modified from userspace.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- allow panels to be hotplugged
- add brightness property

 .../bindings/gpu/nvidia,tegra20-host1x.txt         |  2 +
 drivers/gpu/drm/tegra/Kconfig                      |  1 +
 drivers/gpu/drm/tegra/drm.h                        |  3 +
 drivers/gpu/drm/tegra/output.c                     | 90 +++++++++++++++++++++-
 4 files changed, 92 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
index b4fa934ae3a2..24f026e20154 100644
--- a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
+++ b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
@@ -67,6 +67,7 @@ of the following host1x client modules:
   - nvidia,ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
   - nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
   - nvidia,edid: supplies a binary EDID blob
+  - nvidia,panel: phandle of a display panel
 
 - hdmi: High Definition Multimedia Interface
 
@@ -81,6 +82,7 @@ of the following host1x client modules:
   - nvidia,ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
   - nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
   - nvidia,edid: supplies a binary EDID blob
+  - nvidia,panel: phandle of a display panel
 
 - tvo: TV encoder output
 
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
index 8961ba6a34b8..da34dd1b9ddb 100644
--- a/drivers/gpu/drm/tegra/Kconfig
+++ b/drivers/gpu/drm/tegra/Kconfig
@@ -5,6 +5,7 @@ config DRM_TEGRA
 	select TEGRA_HOST1X
 	select DRM_KMS_HELPER
 	select DRM_KMS_FB_HELPER
+	select DRM_PANEL
 	select FB_SYS_FILLRECT
 	select FB_SYS_COPYAREA
 	select FB_SYS_IMAGEBLIT
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h
index 7da0b923131f..5aa7f2065c02 100644
--- a/drivers/gpu/drm/tegra/drm.h
+++ b/drivers/gpu/drm/tegra/drm.h
@@ -183,6 +183,7 @@ struct tegra_output {
 	const struct tegra_output_ops *ops;
 	enum tegra_output_type type;
 
+	struct drm_panel *panel;
 	struct i2c_adapter *ddc;
 	const struct edid *edid;
 	unsigned int hpd_irq;
@@ -190,6 +191,8 @@ struct tegra_output {
 
 	struct drm_encoder encoder;
 	struct drm_connector connector;
+
+	struct drm_property *brightness;
 };
 
 static inline struct tegra_output *encoder_to_output(struct drm_encoder *e)
diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c
index 2cb0065e0578..c5e0a2a97bc8 100644
--- a/drivers/gpu/drm/tegra/output.c
+++ b/drivers/gpu/drm/tegra/output.c
@@ -9,6 +9,7 @@
 
 #include <linux/of_gpio.h>
 
+#include <drm/drm_panel.h>
 #include "drm.h"
 
 static int tegra_connector_get_modes(struct drm_connector *connector)
@@ -17,6 +18,9 @@ static int tegra_connector_get_modes(struct drm_connector *connector)
 	struct edid *edid = NULL;
 	int err = 0;
 
+	if (output->panel)
+		return output->panel->funcs->get_modes(output->panel);
+
 	if (output->edid)
 		edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL);
 	else if (output->ddc)
@@ -60,6 +64,39 @@ static const struct drm_connector_helper_funcs connector_helper_funcs = {
 	.best_encoder = tegra_connector_best_encoder,
 };
 
+static void tegra_output_add_panel_properties(struct tegra_output *output)
+{
+	struct drm_device *drm = output->connector.dev;
+	uint64_t min, max, value;
+	int err;
+
+	if (output->brightness)
+		return;
+
+	err = drm_panel_get_brightness_range(output->panel, &min, &max);
+	if (err < 0)
+		return;
+
+	err = drm_panel_get_brightness(output->panel, &value);
+	if (err < 0)
+		return;
+
+	output->brightness = drm_property_create_range(drm, 0, "brightness",
+						       min, max);
+	drm_object_attach_property(&output->connector.base, output->brightness,
+				   value);
+}
+
+static void tegra_output_remove_panel_properties(struct tegra_output *output)
+{
+	struct drm_device *drm = output->connector.dev;
+
+	if (output->brightness) {
+		drm_property_destroy(drm, output->brightness);
+		output->brightness = NULL;
+	}
+}
+
 static enum drm_connector_status
 tegra_connector_detect(struct drm_connector *connector, bool force)
 {
@@ -72,6 +109,14 @@ tegra_connector_detect(struct drm_connector *connector, bool force)
 		else
 			status = connector_status_connected;
 	} else {
+		if (!output->panel) {
+			tegra_output_remove_panel_properties(output);
+			status = connector_status_disconnected;
+		} else {
+			tegra_output_add_panel_properties(output);
+			status = connector_status_connected;
+		}
+
 		if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
 			status = connector_status_connected;
 	}
@@ -79,6 +124,24 @@ tegra_connector_detect(struct drm_connector *connector, bool force)
 	return status;
 }
 
+static int tegra_connector_set_property(struct drm_connector *connector,
+					struct drm_property *property,
+					uint64_t value)
+{
+	struct tegra_output *output = connector_to_output(connector);
+	int err;
+
+	if (output->panel) {
+		if (property == output->brightness) {
+			err = drm_panel_set_brightness(output->panel, value);
+			if (err < 0)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
 static void drm_connector_clear(struct drm_connector *connector)
 {
 	memset(connector, 0, sizeof(*connector));
@@ -95,6 +158,7 @@ static const struct drm_connector_funcs connector_funcs = {
 	.dpms = drm_helper_connector_dpms,
 	.detect = tegra_connector_detect,
 	.fill_modes = drm_helper_probe_single_connector_modes,
+	.set_property = tegra_connector_set_property,
 	.destroy = tegra_connector_destroy,
 };
 
@@ -115,6 +179,15 @@ static const struct drm_encoder_funcs encoder_funcs = {
 
 static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode)
 {
+	struct tegra_output *output = encoder_to_output(encoder);
+	struct drm_panel *panel = output->panel;
+
+	if (panel && panel->funcs) {
+		if (mode != DRM_MODE_DPMS_ON)
+			drm_panel_disable(panel);
+		else
+			drm_panel_enable(panel);
+	}
 }
 
 static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder,
@@ -163,14 +236,23 @@ static irqreturn_t hpd_irq(int irq, void *data)
 
 int tegra_output_probe(struct tegra_output *output)
 {
+	struct device_node *ddc, *panel;
 	enum of_gpio_flags flags;
-	struct device_node *ddc;
 	size_t size;
 	int err;
 
 	if (!output->of_node)
 		output->of_node = output->dev->of_node;
 
+	panel = of_parse_phandle(output->of_node, "nvidia,panel", 0);
+	if (panel) {
+		output->panel = of_drm_find_panel(panel);
+		if (!output->panel)
+			return -EPROBE_DEFER;
+
+		of_node_put(panel);
+	}
+
 	output->edid = of_get_property(output->of_node, "nvidia,edid", &size);
 
 	ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
@@ -185,9 +267,6 @@ int tegra_output_probe(struct tegra_output *output)
 		of_node_put(ddc);
 	}
 
-	if (!output->edid && !output->ddc)
-		return -ENODEV;
-
 	output->hpd_gpio = of_get_named_gpio_flags(output->of_node,
 						   "nvidia,hpd-gpio", 0,
 						   &flags);
@@ -267,6 +346,9 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
 	drm_connector_helper_add(&output->connector, &connector_helper_funcs);
 	output->connector.dpms = DRM_MODE_DPMS_OFF;
 
+	if (output->panel)
+		drm_panel_attach(output->panel, &output->connector);
+
 	drm_encoder_init(drm, &output->encoder, &encoder_funcs, encoder);
 	drm_encoder_helper_add(&output->encoder, &encoder_helper_funcs);
 
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 5/7] gpu: host1x: Add MIPI pad calibration support
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
                   ` (3 preceding siblings ...)
  2013-11-11 12:00 ` [PATCH v3 4/7] drm/tegra: Implement " Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 6/7] drm/tegra: Add DSI support Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 7/7] WIP: drm/tegra: Implement DSI transfers Thierry Reding
  6 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

This driver adds support to perform calibration of the MIPI pads for CSI
and DSI.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- rename calibrate and #calibrate-cells properties to nvidia,mipi-calibrate
  and #nvidia,mipi-calibrate-cells, respectively
- split API into tegra_mipi_{request,free,calibrate}() to allow DT to be
  parsed only once and to better support deferred probing

 .../bindings/misc/nvidia,tegra114-mipi.txt         |  37 +++
 drivers/gpu/host1x/Makefile                        |   1 +
 drivers/gpu/host1x/dev.c                           |  17 +-
 drivers/gpu/host1x/dev.h                           |   2 +
 drivers/gpu/host1x/mipi.c                          | 272 +++++++++++++++++++++
 include/linux/host1x.h                             |   6 +
 6 files changed, 331 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt
 create mode 100644 drivers/gpu/host1x/mipi.c

diff --git a/Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt b/Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt
new file mode 100644
index 000000000000..beb75ec7f6fc
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt
@@ -0,0 +1,37 @@
+NVIDIA Tegra MIPI pad calibration controller
+
+Required properties:
+- compatible: "nvidia,tegra<chip>-mipi"
+- reg: Physical base address and length of the controller's registers.
+- clocks: The clock consumed by the controller.
+- #nvidia,mipi-calibrate-cells: Should be 1. The cell is a bitmask of the pads
+  that need to be calibrated for a given device.
+
+User nodes need to contain an nvidia,mipi-calibrate property that has a
+phandle to refer to the calibration controller node and a bitmask of the pads
+that need to be calibrated.
+
+Example:
+
+	mipi: mipi@700e3000 {
+		compatible = "nvidia,tegra114-mipi";
+		reg = <0x700e3000 0x100>;
+		clocks = <&tegra_car TEGRA114_CLK_MIPI_CAL>;
+		#nvidia,mipi-calibrate-cells = <1>;
+	};
+
+	...
+
+	host1x@50000000 {
+		...
+
+		dsi@54300000 {
+			...
+
+			nvidia,mipi-calibrate = <&mipi 0x060>;
+
+			...
+		};
+
+		...
+	};
diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile
index afa1e9e4e512..de305c2d510e 100644
--- a/drivers/gpu/host1x/Makefile
+++ b/drivers/gpu/host1x/Makefile
@@ -7,6 +7,7 @@ host1x-y = \
 	channel.o \
 	job.o \
 	debug.o \
+	mipi.o \
 	hw/host1x01.o \
 	hw/host1x02.o
 
diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c
index 80da003d63de..646a333b069a 100644
--- a/drivers/gpu/host1x/dev.c
+++ b/drivers/gpu/host1x/dev.c
@@ -210,17 +210,26 @@ static int __init tegra_host1x_init(void)
 		return err;
 
 	err = platform_driver_register(&tegra_host1x_driver);
-	if (err < 0) {
-		host1x_bus_exit();
-		return err;
-	}
+	if (err < 0)
+		goto unregister_bus;
+
+	err = platform_driver_register(&tegra_mipi_driver);
+	if (err < 0)
+		goto unregister_host1x;
 
 	return 0;
+
+unregister_host1x:
+	platform_driver_unregister(&tegra_host1x_driver);
+unregister_bus:
+	host1x_bus_exit();
+	return err;
 }
 module_init(tegra_host1x_init);
 
 static void __exit tegra_host1x_exit(void)
 {
+	platform_driver_unregister(&tegra_mipi_driver);
 	platform_driver_unregister(&tegra_host1x_driver);
 	host1x_bus_exit();
 }
diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h
index a61a976e7a42..0b6e8e9629c5 100644
--- a/drivers/gpu/host1x/dev.h
+++ b/drivers/gpu/host1x/dev.h
@@ -306,4 +306,6 @@ static inline void host1x_hw_show_mlocks(struct host1x *host, struct output *o)
 	host->debug_op->show_mlocks(host, o);
 }
 
+extern struct platform_driver tegra_mipi_driver;
+
 #endif
diff --git a/drivers/gpu/host1x/mipi.c b/drivers/gpu/host1x/mipi.c
new file mode 100644
index 000000000000..78a74d69c8ee
--- /dev/null
+++ b/drivers/gpu/host1x/mipi.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define MIPI_CAL_CTRL			0x00
+#define MIPI_CAL_CTRL_START		(1 << 0)
+
+#define MIPI_CAL_AUTOCAL_CTRL		0x01
+
+#define MIPI_CAL_STATUS			0x02
+#define MIPI_CAL_STATUS_DONE		(1 << 16)
+#define MIPI_CAL_STATUS_ACTIVE		(1 <<  0)
+
+#define MIPI_CAL_CONFIG_CSIA		0x05
+#define MIPI_CAL_CONFIG_CSIB		0x06
+#define MIPI_CAL_CONFIG_CSIC		0x07
+#define MIPI_CAL_CONFIG_CSID		0x08
+#define MIPI_CAL_CONFIG_CSIE		0x09
+#define MIPI_CAL_CONFIG_DSIA		0x0e
+#define MIPI_CAL_CONFIG_DSIB		0x0f
+#define MIPI_CAL_CONFIG_DSIC		0x10
+#define MIPI_CAL_CONFIG_DSID		0x11
+
+#define MIPI_CAL_CONFIG_SELECT		(1 << 21)
+#define MIPI_CAL_CONFIG_HSPDOS(x)	(((x) & 0x1f) << 16)
+#define MIPI_CAL_CONFIG_HSPUOS(x)	(((x) & 0x1f) <<  8)
+#define MIPI_CAL_CONFIG_TERMOS(x)	(((x) & 0x1f) <<  0)
+
+#define MIPI_CAL_BIAS_PAD_CFG0		0x16
+#define MIPI_CAL_BIAS_PAD_PDVCLAMP	(1 << 1)
+#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF	(1 << 0)
+
+#define MIPI_CAL_BIAS_PAD_CFG1		0x17
+
+#define MIPI_CAL_BIAS_PAD_CFG2		0x18
+#define MIPI_CAL_BIAS_PAD_PDVREG	(1 << 1)
+
+static const struct module {
+	unsigned long reg;
+} modules[] = {
+	{ .reg = MIPI_CAL_CONFIG_CSIA },
+	{ .reg = MIPI_CAL_CONFIG_CSIB },
+	{ .reg = MIPI_CAL_CONFIG_CSIC },
+	{ .reg = MIPI_CAL_CONFIG_CSID },
+	{ .reg = MIPI_CAL_CONFIG_CSIE },
+	{ .reg = MIPI_CAL_CONFIG_DSIA },
+	{ .reg = MIPI_CAL_CONFIG_DSIB },
+	{ .reg = MIPI_CAL_CONFIG_DSIC },
+	{ .reg = MIPI_CAL_CONFIG_DSID },
+};
+
+struct tegra_mipi {
+	void __iomem *regs;
+	struct mutex lock;
+	struct clk *clk;
+};
+
+struct tegra_mipi_device {
+	struct platform_device *pdev;
+	struct tegra_mipi *mipi;
+	struct device *device;
+	unsigned long pads;
+};
+
+static inline unsigned long tegra_mipi_readl(struct tegra_mipi *mipi,
+					     unsigned long reg)
+{
+	return readl(mipi->regs + (reg << 2));
+}
+
+static inline void tegra_mipi_writel(struct tegra_mipi *mipi,
+				     unsigned long value, unsigned long reg)
+{
+	writel(value, mipi->regs + (reg << 2));
+}
+
+struct tegra_mipi_device *tegra_mipi_request(struct device *device)
+{
+	struct device_node *np = device->of_node;
+	struct tegra_mipi_device *dev;
+	struct of_phandle_args args;
+	int err;
+
+	err = of_parse_phandle_with_args(np, "nvidia,mipi-calibrate",
+					 "#nvidia,mipi-calibrate-cells", 0,
+					 &args);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		of_node_put(args.np);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	dev->pdev = of_find_device_by_node(args.np);
+	if (!dev->pdev) {
+		of_node_put(args.np);
+		err = -ENODEV;
+		goto free;
+	}
+
+	of_node_put(args.np);
+
+	dev->mipi = platform_get_drvdata(dev->pdev);
+	if (!dev->mipi) {
+		err = -EPROBE_DEFER;
+		goto pdev_put;
+	}
+
+	dev->pads = args.args[0];
+	dev->device = device;
+
+	return dev;
+
+pdev_put:
+	platform_device_put(dev->pdev);
+free:
+	kfree(dev);
+out:
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(tegra_mipi_request);
+
+void tegra_mipi_free(struct tegra_mipi_device *device)
+{
+	platform_device_put(device->pdev);
+	kfree(device);
+}
+EXPORT_SYMBOL_GPL(tegra_mipi_free);
+
+static int tegra_mipi_wait(struct tegra_mipi *mipi)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(250);
+	unsigned long value;
+
+	while (time_before(jiffies, timeout)) {
+		value = tegra_mipi_readl(mipi, MIPI_CAL_STATUS);
+		if ((value & MIPI_CAL_STATUS_ACTIVE) == 0 &&
+		    (value & MIPI_CAL_STATUS_DONE) != 0)
+			return 0;
+
+		usleep_range(10, 50);
+	}
+
+	return -ETIMEDOUT;
+}
+
+int tegra_mipi_calibrate(struct tegra_mipi_device *device)
+{
+	unsigned long value;
+	unsigned int i;
+	int err;
+
+	err = clk_enable(device->mipi->clk);
+	if (err < 0)
+		return err;
+
+	mutex_lock(&device->mipi->lock);
+
+	value = tegra_mipi_readl(device->mipi, MIPI_CAL_BIAS_PAD_CFG0);
+	value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP;
+	value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF;
+	tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG0);
+
+	value = tegra_mipi_readl(device->mipi, MIPI_CAL_BIAS_PAD_CFG2);
+	value &= ~MIPI_CAL_BIAS_PAD_PDVREG;
+	tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG2);
+
+	for (i = 0; i < ARRAY_SIZE(modules); i++) {
+		if (device->pads & BIT(i))
+			value = MIPI_CAL_CONFIG_SELECT |
+				MIPI_CAL_CONFIG_HSPDOS(0) |
+				MIPI_CAL_CONFIG_HSPUOS(4) |
+				MIPI_CAL_CONFIG_TERMOS(5);
+		else
+			value = 0;
+
+		tegra_mipi_writel(device->mipi, value, modules[i].reg);
+	}
+
+	tegra_mipi_writel(device->mipi, MIPI_CAL_CTRL_START, MIPI_CAL_CTRL);
+
+	err = tegra_mipi_wait(device->mipi);
+
+	mutex_unlock(&device->mipi->lock);
+	clk_disable(device->mipi->clk);
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(tegra_mipi_calibrate);
+
+static int tegra_mipi_probe(struct platform_device *pdev)
+{
+	struct tegra_mipi *mipi;
+	struct resource *res;
+	int err;
+
+	mipi = devm_kzalloc(&pdev->dev, sizeof(*mipi), GFP_KERNEL);
+	if (!mipi)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mipi->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(mipi->regs))
+		return PTR_ERR(mipi->regs);
+
+	mutex_init(&mipi->lock);
+
+	mipi->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(mipi->clk)) {
+		dev_err(&pdev->dev, "failed to get clock\n");
+		return PTR_ERR(mipi->clk);
+	}
+
+	err = clk_prepare(mipi->clk);
+	if (err < 0)
+		return err;
+
+	platform_set_drvdata(pdev, mipi);
+
+	return 0;
+}
+
+static int tegra_mipi_remove(struct platform_device *pdev)
+{
+	struct tegra_mipi *mipi = platform_get_drvdata(pdev);
+
+	clk_unprepare(mipi->clk);
+
+	return 0;
+}
+
+static struct of_device_id tegra_mipi_of_match[] = {
+	{ .compatible = "nvidia,tegra114-mipi", },
+	{ },
+};
+
+struct platform_driver tegra_mipi_driver = {
+	.driver = {
+		.name = "tegra-mipi",
+		.of_match_table = tegra_mipi_of_match,
+	},
+	.probe = tegra_mipi_probe,
+	.remove = tegra_mipi_remove,
+};
diff --git a/include/linux/host1x.h b/include/linux/host1x.h
index f5b9b87ac9a9..3af847273277 100644
--- a/include/linux/host1x.h
+++ b/include/linux/host1x.h
@@ -281,4 +281,10 @@ int host1x_device_exit(struct host1x_device *device);
 int host1x_client_register(struct host1x_client *client);
 int host1x_client_unregister(struct host1x_client *client);
 
+struct tegra_mipi_device;
+
+struct tegra_mipi_device *tegra_mipi_request(struct device *device);
+void tegra_mipi_free(struct tegra_mipi_device *device);
+int tegra_mipi_calibrate(struct tegra_mipi_device *device);
+
 #endif
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 6/7] drm/tegra: Add DSI support
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
                   ` (4 preceding siblings ...)
  2013-11-11 12:00 ` [PATCH v3 5/7] gpu: host1x: Add MIPI pad calibration support Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  2013-11-11 12:00 ` [PATCH v3 7/7] WIP: drm/tegra: Implement DSI transfers Thierry Reding
  6 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

This commit adds support for both DSI outputs found on Tegra. Only very
minimal functionality is implemented, so advanced features like ganged
mode won't work.

Due to the lack of other test hardware, some sections of the driver are
hardcoded to work with Dalmore.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- select DRM_DSI symbol to pull in DSI bus infrastructure
- implement DSI host .attach() and .detach() callbacks
- move register definitions to separate file
- move MIPI D-PHY code to separate file
- add support for low-power clocks

Changes in v2:
- cope with modes that have a 0 Hz refresh rate

 .../bindings/gpu/nvidia,tegra20-host1x.txt         |  14 +
 drivers/gpu/drm/tegra/Kconfig                      |   1 +
 drivers/gpu/drm/tegra/Makefile                     |   2 +
 drivers/gpu/drm/tegra/dc.h                         |   2 +
 drivers/gpu/drm/tegra/drm.c                        |  10 +-
 drivers/gpu/drm/tegra/drm.h                        |   2 +
 drivers/gpu/drm/tegra/dsi.c                        | 940 +++++++++++++++++++++
 drivers/gpu/drm/tegra/dsi.h                        | 134 +++
 drivers/gpu/drm/tegra/mipi-phy.c                   | 137 +++
 drivers/gpu/drm/tegra/mipi-phy.h                   |  65 ++
 drivers/gpu/drm/tegra/output.c                     |   5 +
 11 files changed, 1311 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/tegra/dsi.c
 create mode 100644 drivers/gpu/drm/tegra/dsi.h
 create mode 100644 drivers/gpu/drm/tegra/mipi-phy.c
 create mode 100644 drivers/gpu/drm/tegra/mipi-phy.h

diff --git a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
index 24f026e20154..3f14e81c55bd 100644
--- a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
+++ b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt
@@ -96,6 +96,20 @@ of the following host1x client modules:
   Required properties:
   - compatible: "nvidia,tegra<chip>-dsi"
   - reg: Physical base address and length of the controller's registers.
+  - clocks: Should contain phandle and clock specifiers for two clocks:
+    the DSI controller clock and the parent clock of the controller.
+  - clock-names: A list of strings containing the name for each clock in
+    the clocks property. Must contain the following two entries:
+    - "dsi": the DSI controller clock
+    - "parent": parent of the DSI controller clock
+  - calibrate: Should contain a phandle and a specifier specifying which
+    pads are used by this DSI output and need to be calibrated.
+
+  Optional properties:
+  - nvidia,ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
+  - nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
+  - nvidia,edid: supplies a binary EDID blob
+  - nvidia,panel: phandle of a display panel
 
 Example:
 
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
index da34dd1b9ddb..032e2c2b21ef 100644
--- a/drivers/gpu/drm/tegra/Kconfig
+++ b/drivers/gpu/drm/tegra/Kconfig
@@ -3,6 +3,7 @@ config DRM_TEGRA
 	depends on ARCH_TEGRA || ARCH_MULTIPLATFORM
 	depends on DRM
 	select TEGRA_HOST1X
+	select DRM_DSI
 	select DRM_KMS_HELPER
 	select DRM_KMS_FB_HELPER
 	select DRM_PANEL
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
index edc76abd58bb..8d220afbd85f 100644
--- a/drivers/gpu/drm/tegra/Makefile
+++ b/drivers/gpu/drm/tegra/Makefile
@@ -9,6 +9,8 @@ tegra-drm-y := \
 	output.o \
 	rgb.o \
 	hdmi.o \
+	mipi-phy.o \
+	dsi.o \
 	gr2d.o \
 	gr3d.o
 
diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
index 91bbda291470..788627a060d7 100644
--- a/drivers/gpu/drm/tegra/dc.h
+++ b/drivers/gpu/drm/tegra/dc.h
@@ -28,6 +28,7 @@
 #define DISP_CTRL_MODE_STOP (0 << 5)
 #define DISP_CTRL_MODE_C_DISPLAY (1 << 5)
 #define DISP_CTRL_MODE_NC_DISPLAY (2 << 5)
+#define DISP_CTRL_MODE_MASK (3 << 5)
 #define DC_CMD_SIGNAL_RAISE			0x033
 #define DC_CMD_DISPLAY_POWER_CONTROL		0x036
 #define PW0_ENABLE (1 <<  0)
@@ -116,6 +117,7 @@
 
 #define DC_DISP_DISP_WIN_OPTIONS		0x402
 #define HDMI_ENABLE (1 << 30)
+#define DSI_ENABLE  (1 << 29)
 
 #define DC_DISP_DISP_MEM_HIGH_PRIORITY		0x403
 #define CURSOR_THRESHOLD(x)   (((x) & 0x03) << 24)
diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c
index 28e178137718..ed86f2b3c0d7 100644
--- a/drivers/gpu/drm/tegra/drm.c
+++ b/drivers/gpu/drm/tegra/drm.c
@@ -649,6 +649,7 @@ static const struct of_device_id host1x_drm_subdevs[] = {
 	{ .compatible = "nvidia,tegra30-hdmi", },
 	{ .compatible = "nvidia,tegra30-gr2d", },
 	{ .compatible = "nvidia,tegra30-gr3d", },
+	{ .compatible = "nvidia,tegra114-dsi", },
 	{ .compatible = "nvidia,tegra114-hdmi", },
 	{ .compatible = "nvidia,tegra114-gr3d", },
 	{ /* sentinel */ }
@@ -673,10 +674,14 @@ static int __init host1x_drm_init(void)
 	if (err < 0)
 		goto unregister_host1x;
 
-	err = platform_driver_register(&tegra_hdmi_driver);
+	err = platform_driver_register(&tegra_dsi_driver);
 	if (err < 0)
 		goto unregister_dc;
 
+	err = platform_driver_register(&tegra_hdmi_driver);
+	if (err < 0)
+		goto unregister_dsi;
+
 	err = platform_driver_register(&tegra_gr2d_driver);
 	if (err < 0)
 		goto unregister_hdmi;
@@ -691,6 +696,8 @@ unregister_gr2d:
 	platform_driver_unregister(&tegra_gr2d_driver);
 unregister_hdmi:
 	platform_driver_unregister(&tegra_hdmi_driver);
+unregister_dsi:
+	platform_driver_unregister(&tegra_dsi_driver);
 unregister_dc:
 	platform_driver_unregister(&tegra_dc_driver);
 unregister_host1x:
@@ -704,6 +711,7 @@ static void __exit host1x_drm_exit(void)
 	platform_driver_unregister(&tegra_gr3d_driver);
 	platform_driver_unregister(&tegra_gr2d_driver);
 	platform_driver_unregister(&tegra_hdmi_driver);
+	platform_driver_unregister(&tegra_dsi_driver);
 	platform_driver_unregister(&tegra_dc_driver);
 	host1x_driver_unregister(&host1x_drm_driver);
 }
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h
index 5aa7f2065c02..439c1b3bd920 100644
--- a/drivers/gpu/drm/tegra/drm.h
+++ b/drivers/gpu/drm/tegra/drm.h
@@ -174,6 +174,7 @@ struct tegra_output_ops {
 enum tegra_output_type {
 	TEGRA_OUTPUT_RGB,
 	TEGRA_OUTPUT_HDMI,
+	TEGRA_OUTPUT_DSI,
 };
 
 struct tegra_output {
@@ -266,6 +267,7 @@ extern void tegra_drm_fb_exit(struct drm_device *drm);
 extern void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev);
 
 extern struct platform_driver tegra_dc_driver;
+extern struct platform_driver tegra_dsi_driver;
 extern struct platform_driver tegra_hdmi_driver;
 extern struct platform_driver tegra_gr2d_driver;
 extern struct platform_driver tegra_gr3d_driver;
diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c
new file mode 100644
index 000000000000..effd0b5a5554
--- /dev/null
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk/tegra.h>
+#include <linux/debugfs.h>
+#include <linux/host1x.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_dsi.h>
+#include <drm/drm_panel.h>
+
+#include "dc.h"
+#include "drm.h"
+#include "dsi.h"
+#include "mipi-phy.h"
+
+#define DSI_VIDEO_FIFO_DEPTH (1920 / 4)
+#define DSI_HOST_FIFO_DEPTH 64
+
+enum dsi_format {
+	DSI_FORMAT_16P,
+	DSI_FORMAT_18NP,
+	DSI_FORMAT_18P,
+	DSI_FORMAT_24P,
+};
+
+struct tegra_dsi {
+	struct host1x_client client;
+	struct tegra_output output;
+	struct device *dev;
+
+	void __iomem *regs;
+
+	struct clk *clk_parent;
+	struct clk *clk_lp;
+	struct clk *clk;
+
+	struct drm_info_list *debugfs_files;
+	struct drm_minor *minor;
+	struct dentry *debugfs;
+
+	enum dsi_format format;
+	unsigned int lanes;
+
+	struct tegra_mipi_device *mipi;
+	struct dsi_host host;
+};
+
+static inline struct tegra_dsi *
+host1x_client_to_dsi(struct host1x_client *client)
+{
+	return container_of(client, struct tegra_dsi, client);
+}
+
+static inline struct tegra_dsi *dsi_host_to_tegra(struct dsi_host *host)
+{
+	return container_of(host, struct tegra_dsi, host);
+}
+
+static inline struct tegra_dsi *to_dsi(struct tegra_output *output)
+{
+	return container_of(output, struct tegra_dsi, output);
+}
+
+static inline unsigned long tegra_dsi_readl(struct tegra_dsi *dsi,
+					    unsigned long reg)
+{
+	return readl(dsi->regs + (reg << 2));
+}
+
+static inline void tegra_dsi_writel(struct tegra_dsi *dsi, unsigned long value,
+				    unsigned long reg)
+{
+	writel(value, dsi->regs + (reg << 2));
+}
+
+static int tegra_dsi_show_regs(struct seq_file *s, void *data)
+{
+	struct drm_info_node *node = s->private;
+	struct tegra_dsi *dsi = node->info_ent->data;
+
+#define DUMP_REG(name)						\
+	seq_printf(s, "%-32s %#05x %08lx\n", #name, name,	\
+		   tegra_dsi_readl(dsi, name))
+
+	DUMP_REG(DSI_INCR_SYNCPT);
+	DUMP_REG(DSI_INCR_SYNCPT_CONTROL);
+	DUMP_REG(DSI_INCR_SYNCPT_ERROR);
+	DUMP_REG(DSI_CTXSW);
+	DUMP_REG(DSI_RD_DATA);
+	DUMP_REG(DSI_WR_DATA);
+	DUMP_REG(DSI_POWER_CONTROL);
+	DUMP_REG(DSI_INT_ENABLE);
+	DUMP_REG(DSI_INT_STATUS);
+	DUMP_REG(DSI_INT_MASK);
+	DUMP_REG(DSI_HOST_CONTROL);
+	DUMP_REG(DSI_CONTROL);
+	DUMP_REG(DSI_SOL_DELAY);
+	DUMP_REG(DSI_MAX_THRESHOLD);
+	DUMP_REG(DSI_TRIGGER);
+	DUMP_REG(DSI_TX_CRC);
+	DUMP_REG(DSI_STATUS);
+
+	DUMP_REG(DSI_INIT_SEQ_CONTROL);
+	DUMP_REG(DSI_INIT_SEQ_DATA_0);
+	DUMP_REG(DSI_INIT_SEQ_DATA_1);
+	DUMP_REG(DSI_INIT_SEQ_DATA_2);
+	DUMP_REG(DSI_INIT_SEQ_DATA_3);
+	DUMP_REG(DSI_INIT_SEQ_DATA_4);
+	DUMP_REG(DSI_INIT_SEQ_DATA_5);
+	DUMP_REG(DSI_INIT_SEQ_DATA_6);
+	DUMP_REG(DSI_INIT_SEQ_DATA_7);
+
+	DUMP_REG(DSI_PKT_SEQ_0_LO);
+	DUMP_REG(DSI_PKT_SEQ_0_HI);
+	DUMP_REG(DSI_PKT_SEQ_1_LO);
+	DUMP_REG(DSI_PKT_SEQ_1_HI);
+	DUMP_REG(DSI_PKT_SEQ_2_LO);
+	DUMP_REG(DSI_PKT_SEQ_2_HI);
+	DUMP_REG(DSI_PKT_SEQ_3_LO);
+	DUMP_REG(DSI_PKT_SEQ_3_HI);
+	DUMP_REG(DSI_PKT_SEQ_4_LO);
+	DUMP_REG(DSI_PKT_SEQ_4_HI);
+	DUMP_REG(DSI_PKT_SEQ_5_LO);
+	DUMP_REG(DSI_PKT_SEQ_5_HI);
+
+	DUMP_REG(DSI_DCS_CMDS);
+
+	DUMP_REG(DSI_PKT_LEN_0_1);
+	DUMP_REG(DSI_PKT_LEN_2_3);
+	DUMP_REG(DSI_PKT_LEN_4_5);
+	DUMP_REG(DSI_PKT_LEN_6_7);
+
+	DUMP_REG(DSI_PHY_TIMING_0);
+	DUMP_REG(DSI_PHY_TIMING_1);
+	DUMP_REG(DSI_PHY_TIMING_2);
+	DUMP_REG(DSI_BTA_TIMING);
+
+	DUMP_REG(DSI_TIMEOUT_0);
+	DUMP_REG(DSI_TIMEOUT_1);
+	DUMP_REG(DSI_TO_TALLY);
+
+	DUMP_REG(DSI_PAD_CONTROL_0);
+	DUMP_REG(DSI_PAD_CONTROL_CD);
+	DUMP_REG(DSI_PAD_CD_STATUS);
+	DUMP_REG(DSI_VIDEO_MODE_CONTROL);
+	DUMP_REG(DSI_PAD_CONTROL_1);
+	DUMP_REG(DSI_PAD_CONTROL_2);
+	DUMP_REG(DSI_PAD_CONTROL_3);
+	DUMP_REG(DSI_PAD_CONTROL_4);
+
+	DUMP_REG(DSI_GANGED_MODE_CONTROL);
+	DUMP_REG(DSI_GANGED_MODE_START);
+	DUMP_REG(DSI_GANGED_MODE_SIZE);
+
+	DUMP_REG(DSI_RAW_DATA_BYTE_COUNT);
+	DUMP_REG(DSI_ULTRA_LOW_POWER_CONTROL);
+
+	DUMP_REG(DSI_INIT_SEQ_DATA_8);
+	DUMP_REG(DSI_INIT_SEQ_DATA_9);
+	DUMP_REG(DSI_INIT_SEQ_DATA_10);
+	DUMP_REG(DSI_INIT_SEQ_DATA_11);
+	DUMP_REG(DSI_INIT_SEQ_DATA_12);
+	DUMP_REG(DSI_INIT_SEQ_DATA_13);
+	DUMP_REG(DSI_INIT_SEQ_DATA_14);
+	DUMP_REG(DSI_INIT_SEQ_DATA_15);
+
+#undef DUMP_REG
+
+	return 0;
+}
+
+static struct drm_info_list debugfs_files[] = {
+	{ "regs", tegra_dsi_show_regs, 0, NULL },
+};
+
+static int tegra_dsi_debugfs_init(struct tegra_dsi *dsi,
+				  struct drm_minor *minor)
+{
+	const char *name = dev_name(dsi->dev);
+	unsigned int i;
+	int err;
+
+	dsi->debugfs = debugfs_create_dir(name, minor->debugfs_root);
+	if (!dsi->debugfs)
+		return -ENOMEM;
+
+	dsi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files),
+				     GFP_KERNEL);
+	if (!dsi->debugfs_files) {
+		err = -ENOMEM;
+		goto remove;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(debugfs_files); i++)
+		dsi->debugfs_files[i].data = dsi;
+
+	err = drm_debugfs_create_files(dsi->debugfs_files,
+				       ARRAY_SIZE(debugfs_files),
+				       dsi->debugfs, minor);
+	if (err < 0)
+		goto free;
+
+	dsi->minor = minor;
+
+	return 0;
+
+free:
+	kfree(dsi->debugfs_files);
+	dsi->debugfs_files = NULL;
+remove:
+	debugfs_remove(dsi->debugfs);
+	dsi->debugfs = NULL;
+
+	return err;
+}
+
+static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi)
+{
+	drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files),
+				 dsi->minor);
+	dsi->minor = NULL;
+
+	kfree(dsi->debugfs_files);
+	dsi->debugfs_files = NULL;
+
+	debugfs_remove(dsi->debugfs);
+	dsi->debugfs = NULL;
+
+	return 0;
+}
+
+#define PKT_ID0(id)	((((id) & 0x3f) <<  3) | (1 <<  9))
+#define PKT_LEN0(len)	(((len) & 0x07) <<  0)
+#define PKT_ID1(id)	((((id) & 0x3f) << 13) | (1 << 19))
+#define PKT_LEN1(len)	(((len) & 0x07) << 10)
+#define PKT_ID2(id)	((((id) & 0x3f) << 23) | (1 << 29))
+#define PKT_LEN2(len)	(((len) & 0x07) << 20)
+
+#define PKT_LP		(1 << 30)
+#define NUM_PKT_SEQ	12
+
+/* non-burst mode with sync-end */
+static const u32 pkt_seq_vnb_syne[NUM_PKT_SEQ] = {
+	[ 0] = PKT_ID0(DSI_CMD_VSYNC_START) | PKT_LEN0(0) |
+	       PKT_ID1(DSI_CMD_BLANK) | PKT_LEN1(1) |
+	       PKT_ID2(DSI_CMD_HSYNC_END) | PKT_LEN2(0) |
+	       PKT_LP,
+	[ 1] = 0,
+	[ 2] = PKT_ID0(DSI_CMD_VSYNC_END) | PKT_LEN0(0) |
+	       PKT_ID1(DSI_CMD_BLANK) | PKT_LEN1(1) |
+	       PKT_ID2(DSI_CMD_HSYNC_END) | PKT_LEN2(0) |
+	       PKT_LP,
+	[ 3] = 0,
+	[ 4] = PKT_ID0(DSI_CMD_HSYNC_START) | PKT_LEN0(0) |
+	       PKT_ID1(DSI_CMD_BLANK) | PKT_LEN1(1) |
+	       PKT_ID2(DSI_CMD_HSYNC_END) | PKT_LEN2(0) |
+	       PKT_LP,
+	[ 5] = 0,
+	[ 6] = PKT_ID0(DSI_CMD_HSYNC_START) | PKT_LEN0(0) |
+	       PKT_ID1(DSI_CMD_BLANK) | PKT_LEN1(1) |
+	       PKT_ID2(DSI_CMD_HSYNC_END) | PKT_LEN2(0),
+	[ 7] = PKT_ID0(DSI_CMD_BLANK) | PKT_LEN0(2) |
+	       PKT_ID1(DSI_CMD_RGB24) | PKT_LEN1(3) |
+	       PKT_ID2(DSI_CMD_BLANK) | PKT_LEN2(4),
+	[ 8] = PKT_ID0(DSI_CMD_HSYNC_START) | PKT_LEN0(0) |
+	       PKT_ID1(DSI_CMD_BLANK) | PKT_LEN1(1) |
+	       PKT_ID2(DSI_CMD_HSYNC_END) | PKT_LEN2(0) |
+	       PKT_LP,
+	[ 9] = 0,
+	[10] = PKT_ID0(DSI_CMD_HSYNC_START) | PKT_LEN0(0) |
+	       PKT_ID1(DSI_CMD_BLANK) | PKT_LEN1(1) |
+	       PKT_ID2(DSI_CMD_HSYNC_END) | PKT_LEN2(0),
+	[11] = PKT_ID0(DSI_CMD_BLANK) | PKT_LEN0(2) |
+	       PKT_ID1(DSI_CMD_RGB24) | PKT_LEN1(3) |
+	       PKT_ID2(DSI_CMD_BLANK) | PKT_LEN2(4),
+};
+
+static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi)
+{
+	struct mipi_dphy_timing timing;
+	unsigned long value, period;
+	long rate;
+	int err;
+
+	rate = clk_get_rate(dsi->clk);
+	if (rate < 0)
+		return rate;
+
+	period = DIV_ROUND_CLOSEST(1000000000UL, rate * 2);
+
+	err = mipi_dphy_timing_get_default(&timing, period);
+	if (err < 0)
+		return err;
+
+	err = mipi_dphy_timing_validate(&timing, period);
+	if (err < 0) {
+		dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err);
+		return err;
+	}
+
+	/*
+	 * The D-PHY timing fields below are expressed in byte-clock cycles,
+	 * so multiply the period by 8.
+	 */
+	period *= 8;
+
+	value = DSI_TIMING_FIELD(timing.hsexit, period, 1) << 24 |
+		DSI_TIMING_FIELD(timing.hstrail, period, 0) << 16 |
+		DSI_TIMING_FIELD(timing.hszero, period, 3) << 8 |
+		DSI_TIMING_FIELD(timing.hsprepare, period, 1);
+	tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0);
+
+	value = DSI_TIMING_FIELD(timing.clktrail, period, 1) << 24 |
+		DSI_TIMING_FIELD(timing.clkpost, period, 1) << 16 |
+		DSI_TIMING_FIELD(timing.clkzero, period, 1) << 8 |
+		DSI_TIMING_FIELD(timing.lpx, period, 1);
+	tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1);
+
+	value = DSI_TIMING_FIELD(timing.clkprepare, period, 1) << 16 |
+		DSI_TIMING_FIELD(timing.clkpre, period, 1) << 8 |
+		DSI_TIMING_FIELD(0xff * period, period, 0) << 0;
+	tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2);
+
+	value = DSI_TIMING_FIELD(timing.taget, period, 1) << 16 |
+		DSI_TIMING_FIELD(timing.tasure, period, 1) << 8 |
+		DSI_TIMING_FIELD(timing.tago, period, 1);
+	tegra_dsi_writel(dsi, value, DSI_BTA_TIMING);
+
+	return 0;
+}
+
+static int tegra_dsi_get_muldiv(enum dsi_format format, unsigned int *mulp,
+				unsigned int *divp)
+{
+	switch (format) {
+	case DSI_FORMAT_16P:
+		*mulp = 2;
+		*divp = 1;
+		break;
+
+	case DSI_FORMAT_18NP:
+		*mulp = 9;
+		*divp = 4;
+		break;
+
+	case DSI_FORMAT_18P:
+	case DSI_FORMAT_24P:
+		*mulp = 3;
+		*divp = 1;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int tegra_output_dsi_enable(struct tegra_output *output)
+{
+	struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
+	struct drm_display_mode *mode = &dc->base.mode;
+	unsigned int hact, hsw, hbp, hfp, i, mul, div;
+	struct tegra_dsi *dsi = to_dsi(output);
+	/* FIXME: don't hardcode this */
+	const u32 *pkt_seq = pkt_seq_vnb_syne;
+	unsigned long value;
+	int err;
+
+	err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
+	if (err < 0)
+		return err;
+
+	err = clk_enable(dsi->clk);
+	if (err < 0)
+		return err;
+
+	tegra_periph_reset_deassert(dsi->clk);
+
+	value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(dsi->format) |
+		DSI_CONTROL_LANES(dsi->lanes - 1) |
+		DSI_CONTROL_SOURCE(dc->pipe);
+	tegra_dsi_writel(dsi, value, DSI_CONTROL);
+
+	tegra_dsi_writel(dsi, DSI_VIDEO_FIFO_DEPTH, DSI_MAX_THRESHOLD);
+
+	value = DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS |
+		DSI_HOST_CONTROL_ECC;
+	tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
+
+	value = tegra_dsi_readl(dsi, DSI_CONTROL);
+	value |= DSI_CONTROL_HS_CLK_CTRL;
+	value &= ~DSI_CONTROL_TX_TRIG(3);
+	value &= ~DSI_CONTROL_DCS_ENABLE;
+	value |= DSI_CONTROL_VIDEO_ENABLE;
+	value &= ~DSI_CONTROL_HOST_ENABLE;
+	tegra_dsi_writel(dsi, value, DSI_CONTROL);
+
+	err = tegra_dsi_set_phy_timing(dsi);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < NUM_PKT_SEQ; i++)
+		tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i);
+
+	/* horizontal active pixels */
+	hact = mode->hdisplay * mul / div;
+
+	/* horizontal sync width */
+	hsw = (mode->hsync_end - mode->hsync_start) * mul / div;
+	hsw -= 10;
+
+	/* horizontal back porch */
+	hbp = (mode->htotal - mode->hsync_end) * mul / div;
+	hbp -= 14;
+
+	/* horizontal front porch */
+	hfp = (mode->hsync_start  - mode->hdisplay) * mul / div;
+	hfp -= 8;
+
+	tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1);
+	tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3);
+	tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5);
+	tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7);
+
+	/* set SOL delay */
+	tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY);
+
+	/* enable display controller */
+	value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
+	value |= DSI_ENABLE;
+	tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
+
+	value = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
+		PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
+	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
+
+	value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
+	value &= ~DISP_CTRL_MODE_MASK;
+	value |= DISP_CTRL_MODE_C_DISPLAY;
+	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
+
+	tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
+	tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
+
+	/* enable DSI controller */
+	value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
+	value |= DSI_POWER_CONTROL_ENABLE;
+	tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+
+	return 0;
+}
+
+static int tegra_output_dsi_disable(struct tegra_output *output)
+{
+	struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
+	struct tegra_dsi *dsi = to_dsi(output);
+	unsigned long value;
+
+	/* disable DSI controller */
+	value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
+	value &= DSI_POWER_CONTROL_ENABLE;
+	tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+
+	/*
+	 * FIXME: The output isn't attached to any CRTC when it's being
+	 * disabled, so the following will never be executed.
+	 */
+	if (dc) {
+		/* disable display controller */
+		value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
+		value &= ~DISP_CTRL_MODE_MASK;
+		tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
+
+		value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
+		value &= ~DSI_ENABLE;
+		tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
+	}
+
+	clk_disable(dsi->clk);
+
+	return 0;
+}
+
+static int tegra_output_dsi_setup_clock(struct tegra_output *output,
+					struct clk *clk, unsigned long pclk)
+{
+	struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc);
+	struct drm_display_mode *mode = &dc->base.mode;
+	unsigned int timeout, mul, div, vrefresh;
+	struct tegra_dsi *dsi = to_dsi(output);
+	unsigned long bclk, plld, value;
+	struct clk *base;
+	int err;
+
+	err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
+	if (err < 0)
+		return err;
+
+	vrefresh = drm_mode_vrefresh(mode);
+
+	pclk = mode->htotal * mode->vtotal * vrefresh;
+	bclk = (pclk * mul) / (div * dsi->lanes);
+	plld = DIV_ROUND_UP(bclk * 8, 1000000);
+	pclk = (plld * 1000000) / 2;
+
+	err = clk_set_parent(clk, dsi->clk_parent);
+	if (err < 0) {
+		dev_err(dsi->dev, "failed to set parent clock: %d\n", err);
+		return err;
+	}
+
+	base = clk_get_parent(dsi->clk_parent);
+
+	/*
+	 * This assumes that the parent clock is pll_d_out0 or pll_d2_out
+	 * respectively, each of which divides the base pll_d by 2.
+	 */
+	err = clk_set_rate(base, pclk * 2);
+	if (err < 0) {
+		dev_err(dsi->dev, "failed to set base clock rate to %lu Hz\n",
+			pclk * 2);
+		return err;
+	}
+
+	/* one frame high-speed transmission timeout */
+	timeout = (bclk / vrefresh) / 512;
+	value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
+	tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
+
+	/* 2 ms peripheral timeout for panel */
+	timeout = 2 * bclk / 512 * 1000;
+	value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
+	tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
+
+	value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
+	tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
+
+	return 0;
+}
+
+static int tegra_output_dsi_check_mode(struct tegra_output *output,
+				       struct drm_display_mode *mode,
+				       enum drm_mode_status *status)
+{
+	/*
+	 * FIXME: For now, always assume that the mode is okay.
+	 */
+
+	*status = MODE_OK;
+
+	return 0;
+}
+
+static const struct tegra_output_ops dsi_ops = {
+	.enable = tegra_output_dsi_enable,
+	.disable = tegra_output_dsi_disable,
+	.setup_clock = tegra_output_dsi_setup_clock,
+	.check_mode = tegra_output_dsi_check_mode,
+};
+
+static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
+{
+	unsigned long value;
+
+	value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
+	tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
+
+	return 0;
+}
+
+static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
+{
+	unsigned long value;
+
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
+
+	/* start calibration */
+	tegra_dsi_pad_enable(dsi);
+
+	value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
+		DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
+		DSI_PAD_OUT_CLK(0x0);
+	tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
+
+	return tegra_mipi_calibrate(dsi->mipi);
+}
+
+static int tegra_dsi_init(struct host1x_client *client)
+{
+	struct tegra_drm *tegra = dev_get_drvdata(client->parent);
+	struct tegra_dsi *dsi = host1x_client_to_dsi(client);
+	unsigned long value, i;
+	int err;
+
+	dsi->output.type = TEGRA_OUTPUT_DSI;
+	dsi->output.dev = client->dev;
+	dsi->output.ops = &dsi_ops;
+
+	err = tegra_output_init(tegra->drm, &dsi->output);
+	if (err < 0) {
+		dev_err(client->dev, "output setup failed: %d\n", err);
+		return err;
+	}
+
+	if (IS_ENABLED(CONFIG_DEBUG_FS)) {
+		err = tegra_dsi_debugfs_init(dsi, tegra->drm->primary);
+		if (err < 0)
+			dev_err(dsi->dev, "debugfs setup failed: %d\n", err);
+	}
+
+	/*
+	 * enable high-speed mode, checksum generation, ECC generation and
+	 * disable raw mode
+	 */
+	value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL);
+	value |= DSI_HOST_CONTROL_ECC | DSI_HOST_CONTROL_CS |
+		 DSI_HOST_CONTROL_HS;
+	value &= ~DSI_HOST_CONTROL_RAW;
+	tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
+
+	tegra_dsi_writel(dsi, 0, DSI_SOL_DELAY);
+	tegra_dsi_writel(dsi, 0, DSI_MAX_THRESHOLD);
+
+	tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL);
+
+	for (i = 0; i < 8; i++) {
+		tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + i);
+		tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_8 + i);
+	}
+
+	for (i = 0; i < 12; i++)
+		tegra_dsi_writel(dsi, 0, DSI_PKT_SEQ_0_LO + i);
+
+	tegra_dsi_writel(dsi, 0, DSI_DCS_CMDS);
+
+	err = tegra_dsi_pad_calibrate(dsi);
+	if (err < 0) {
+		dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
+		return err;
+	}
+
+	tegra_dsi_writel(dsi, DSI_POWER_CONTROL_ENABLE, DSI_POWER_CONTROL);
+	usleep_range(300, 1000);
+
+	return 0;
+}
+
+static int tegra_dsi_exit(struct host1x_client *client)
+{
+	struct tegra_dsi *dsi = host1x_client_to_dsi(client);
+	int err;
+
+	if (IS_ENABLED(CONFIG_DEBUG_FS)) {
+		err = tegra_dsi_debugfs_exit(dsi);
+		if (err < 0)
+			dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err);
+	}
+
+	err = tegra_output_disable(&dsi->output);
+	if (err < 0) {
+		dev_err(client->dev, "output failed to disable: %d\n", err);
+		return err;
+	}
+
+	err = tegra_output_exit(&dsi->output);
+	if (err < 0) {
+		dev_err(client->dev, "output cleanup failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static const struct host1x_client_ops dsi_client_ops = {
+	.init = tegra_dsi_init,
+	.exit = tegra_dsi_exit,
+};
+
+static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi)
+{
+	struct clk *parent;
+	int err;
+
+	parent = clk_get_parent(dsi->clk);
+	if (!parent)
+		return -EINVAL;
+
+	err = clk_set_parent(parent, dsi->clk_parent);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static void tegra_dsi_initialize(struct tegra_dsi *dsi)
+{
+	unsigned int i;
+
+	tegra_dsi_writel(dsi, 0, DSI_POWER_CONTROL);
+
+	tegra_dsi_writel(dsi, 0, DSI_INT_ENABLE);
+	tegra_dsi_writel(dsi, 0, DSI_INT_STATUS);
+	tegra_dsi_writel(dsi, 0, DSI_INT_MASK);
+
+	tegra_dsi_writel(dsi, 0, DSI_HOST_CONTROL);
+	tegra_dsi_writel(dsi, 0, DSI_CONTROL);
+
+	tegra_dsi_writel(dsi, 0, DSI_SOL_DELAY);
+	tegra_dsi_writel(dsi, 0, DSI_MAX_THRESHOLD);
+
+	tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL);
+
+	for (i = 0; i < 8; i++) {
+		tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + i);
+		tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_8 + i);
+	}
+
+	for (i = 0; i < 12; i++)
+		tegra_dsi_writel(dsi, 0, DSI_PKT_SEQ_0_LO + i);
+
+	tegra_dsi_writel(dsi, 0, DSI_DCS_CMDS);
+
+	for (i = 0; i < 4; i++)
+		tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1 + i);
+
+	tegra_dsi_writel(dsi, 0x00000000, DSI_PHY_TIMING_0);
+	tegra_dsi_writel(dsi, 0x00000000, DSI_PHY_TIMING_1);
+	tegra_dsi_writel(dsi, 0x000000ff, DSI_PHY_TIMING_2);
+	tegra_dsi_writel(dsi, 0x00000000, DSI_BTA_TIMING);
+
+	tegra_dsi_writel(dsi, 0, DSI_TIMEOUT_0);
+	tegra_dsi_writel(dsi, 0, DSI_TIMEOUT_1);
+	tegra_dsi_writel(dsi, 0, DSI_TO_TALLY);
+
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_CD);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CD_STATUS);
+	tegra_dsi_writel(dsi, 0, DSI_VIDEO_MODE_CONTROL);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
+	tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
+
+	tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
+	tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START);
+	tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE);
+}
+
+static int tegra_dsi_host_attach(struct dsi_host *host,
+				 struct dsi_device *device)
+{
+	struct tegra_dsi *dsi = dsi_host_to_tegra(host);
+	struct tegra_output *output = &dsi->output;
+
+	output->panel = of_drm_find_panel(device->dev.of_node);
+	if (output->panel) {
+		if (output->connector.dev)
+			drm_helper_hpd_irq_event(output->connector.dev);
+	}
+
+	return 0;
+}
+
+static int tegra_dsi_host_detach(struct dsi_host *host,
+				 struct dsi_device *device)
+{
+	struct tegra_dsi *dsi = dsi_host_to_tegra(host);
+	struct tegra_output *output = &dsi->output;
+
+	if (output->panel && &device->dev == output->panel->dev) {
+		if (output->connector.dev)
+			drm_helper_hpd_irq_event(output->connector.dev);
+
+		output->panel = NULL;
+	}
+
+	return 0;
+}
+
+static const struct dsi_host_ops tegra_dsi_host_ops = {
+	.attach = tegra_dsi_host_attach,
+	.detach = tegra_dsi_host_detach,
+};
+
+static int tegra_dsi_probe(struct platform_device *pdev)
+{
+	struct tegra_dsi *dsi;
+	struct resource *regs;
+	int err;
+
+	dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->output.dev = dsi->dev = &pdev->dev;
+
+	err = tegra_output_probe(&dsi->output);
+	if (err < 0)
+		return err;
+
+	/*
+	 * FIXME: Don't hardcode these. Perhaps they should be queried from
+	 *        the panel or from the DSI interface's DT node.
+	 */
+	dsi->format = DSI_FORMAT_24P;
+	dsi->lanes = 4;
+
+	dsi->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(dsi->clk))
+		return PTR_ERR(dsi->clk);
+
+	err = clk_prepare_enable(dsi->clk);
+	if (err < 0)
+		return err;
+
+	dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
+	if (IS_ERR(dsi->clk_lp))
+		return PTR_ERR(dsi->clk_lp);
+
+	err = clk_prepare_enable(dsi->clk_lp);
+	if (err < 0)
+		return err;
+
+	dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
+	if (IS_ERR(dsi->clk_parent))
+		return PTR_ERR(dsi->clk_parent);
+
+	err = clk_prepare_enable(dsi->clk_parent);
+	if (err < 0)
+		return err;
+
+	err = tegra_dsi_setup_clocks(dsi);
+	if (err < 0)
+		return err;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dsi->regs = devm_ioremap_resource(&pdev->dev, regs);
+	if (!dsi->regs)
+		return -EADDRNOTAVAIL;
+
+	tegra_dsi_initialize(dsi);
+
+	dsi->mipi = tegra_mipi_request(&pdev->dev);
+	if (IS_ERR(dsi->mipi))
+		return PTR_ERR(dsi->mipi);
+
+	dsi->host.ops = &tegra_dsi_host_ops;
+	dsi->host.dev = &pdev->dev;
+
+	err = dsi_host_register(&dsi->host);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to register DSI host: %d\n", err);
+		return err;
+	}
+
+	INIT_LIST_HEAD(&dsi->client.list);
+	dsi->client.ops = &dsi_client_ops;
+	dsi->client.dev = &pdev->dev;
+
+	err = host1x_client_register(&dsi->client);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to register host1x client: %d\n",
+			err);
+		return err;
+	}
+
+	platform_set_drvdata(pdev, dsi);
+
+	return 0;
+}
+
+static int tegra_dsi_remove(struct platform_device *pdev)
+{
+	struct tegra_dsi *dsi = platform_get_drvdata(pdev);
+	int err;
+
+	err = host1x_client_unregister(&dsi->client);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
+			err);
+		return err;
+	}
+
+	dsi_host_unregister(&dsi->host);
+	tegra_mipi_free(dsi->mipi);
+
+	clk_disable_unprepare(dsi->clk_parent);
+	clk_disable_unprepare(dsi->clk_lp);
+	clk_disable_unprepare(dsi->clk);
+
+	err = tegra_output_remove(&dsi->output);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to remove output: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id tegra_dsi_of_match[] = {
+	{ .compatible = "nvidia,tegra114-dsi", },
+	{ },
+};
+
+struct platform_driver tegra_dsi_driver = {
+	.driver = {
+		.name = "tegra-dsi",
+		.of_match_table = tegra_dsi_of_match,
+	},
+	.probe = tegra_dsi_probe,
+	.remove = tegra_dsi_remove,
+};
diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h
new file mode 100644
index 000000000000..00e79c1f448c
--- /dev/null
+++ b/drivers/gpu/drm/tegra/dsi.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef DRM_TEGRA_DSI_H
+#define DRM_TEGRA_DSI_H
+
+#define DSI_INCR_SYNCPT			0x00
+#define DSI_INCR_SYNCPT_CONTROL		0x01
+#define DSI_INCR_SYNCPT_ERROR		0x02
+#define DSI_CTXSW			0x08
+#define DSI_RD_DATA			0x09
+#define DSI_WR_DATA			0x0a
+#define DSI_POWER_CONTROL		0x0b
+#define DSI_POWER_CONTROL_ENABLE	(1 << 0)
+#define DSI_INT_ENABLE			0x0c
+#define DSI_INT_STATUS			0x0d
+#define DSI_INT_MASK			0x0e
+#define DSI_HOST_CONTROL		0x0f
+#define DSI_HOST_CONTROL_RAW		(1 << 6)
+#define DSI_HOST_CONTROL_HS		(1 << 5)
+#define DSI_HOST_CONTROL_BTA		(1 << 2)
+#define DSI_HOST_CONTROL_CS		(1 << 1)
+#define DSI_HOST_CONTROL_ECC		(1 << 0)
+#define DSI_CONTROL			0x10
+#define DSI_CONTROL_HS_CLK_CTRL		(1 << 20)
+#define DSI_CONTROL_CHANNEL(c)		(((c) & 0x3) << 16)
+#define DSI_CONTROL_FORMAT(f)		(((f) & 0x3) << 12)
+#define DSI_CONTROL_TX_TRIG(x)		(((x) & 0x3) <<  8)
+#define DSI_CONTROL_LANES(n)		(((n) & 0x3) <<  4)
+#define DSI_CONTROL_DCS_ENABLE		(1 << 3)
+#define DSI_CONTROL_SOURCE(s)		(((s) & 0x1) <<  2)
+#define DSI_CONTROL_VIDEO_ENABLE	(1 << 1)
+#define DSI_CONTROL_HOST_ENABLE		(1 << 0)
+#define DSI_SOL_DELAY			0x11
+#define DSI_MAX_THRESHOLD		0x12
+#define DSI_TRIGGER			0x13
+#define DSI_TX_CRC			0x14
+#define DSI_STATUS			0x15
+#define DSI_STATUS_IDLE			(1 << 10)
+#define DSI_INIT_SEQ_CONTROL		0x1a
+#define DSI_INIT_SEQ_DATA_0		0x1b
+#define DSI_INIT_SEQ_DATA_1		0x1c
+#define DSI_INIT_SEQ_DATA_2		0x1d
+#define DSI_INIT_SEQ_DATA_3		0x1e
+#define DSI_INIT_SEQ_DATA_4		0x1f
+#define DSI_INIT_SEQ_DATA_5		0x20
+#define DSI_INIT_SEQ_DATA_6		0x21
+#define DSI_INIT_SEQ_DATA_7		0x22
+#define DSI_PKT_SEQ_0_LO		0x23
+#define DSI_PKT_SEQ_0_HI		0x24
+#define DSI_PKT_SEQ_1_LO		0x25
+#define DSI_PKT_SEQ_1_HI		0x26
+#define DSI_PKT_SEQ_2_LO		0x27
+#define DSI_PKT_SEQ_2_HI		0x28
+#define DSI_PKT_SEQ_3_LO		0x29
+#define DSI_PKT_SEQ_3_HI		0x2a
+#define DSI_PKT_SEQ_4_LO		0x2b
+#define DSI_PKT_SEQ_4_HI		0x2c
+#define DSI_PKT_SEQ_5_LO		0x2d
+#define DSI_PKT_SEQ_5_HI		0x2e
+#define DSI_DCS_CMDS			0x33
+#define DSI_PKT_LEN_0_1			0x34
+#define DSI_PKT_LEN_2_3			0x35
+#define DSI_PKT_LEN_4_5			0x36
+#define DSI_PKT_LEN_6_7			0x37
+#define DSI_PHY_TIMING_0		0x3c
+#define DSI_PHY_TIMING_1		0x3d
+#define DSI_PHY_TIMING_2		0x3e
+#define DSI_BTA_TIMING			0x3f
+
+#define DSI_TIMING_FIELD(value, period, hwinc) \
+	((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff)
+
+#define DSI_TIMEOUT_0			0x44
+#define DSI_TIMEOUT_LRX(x)		(((x) & 0xffff) << 16)
+#define DSI_TIMEOUT_HTX(x)		(((x) & 0xffff) <<  0)
+#define DSI_TIMEOUT_1			0x45
+#define DSI_TIMEOUT_PR(x)		(((x) & 0xffff) << 16)
+#define DSI_TIMEOUT_TA(x)		(((x) & 0xffff) <<  0)
+#define DSI_TO_TALLY			0x46
+#define DSI_TALLY_TA(x)			(((x) & 0xff) << 16)
+#define DSI_TALLY_LRX(x)		(((x) & 0xff) <<  8)
+#define DSI_TALLY_HTX(x)		(((x) & 0xff) <<  0)
+#define DSI_PAD_CONTROL_0		0x4b
+#define DSI_PAD_CONTROL_VS1_PDIO(x)	(((x) & 0xf) <<  0)
+#define DSI_PAD_CONTROL_VS1_PDIO_CLK	(1 <<  8)
+#define DSI_PAD_CONTROL_VS1_PULLDN(x)	(((x) & 0xf) << 16)
+#define DSI_PAD_CONTROL_VS1_PULLDN_CLK	(1 << 24)
+#define DSI_PAD_CONTROL_CD		0x4c
+#define DSI_PAD_CD_STATUS		0x4d
+#define DSI_VIDEO_MODE_CONTROL		0x4e
+#define DSI_PAD_CONTROL_1		0x4f
+#define DSI_PAD_CONTROL_2		0x50
+#define DSI_PAD_OUT_CLK(x)		(((x) & 0x7) <<  0)
+#define DSI_PAD_LP_DN(x)		(((x) & 0x7) <<  4)
+#define DSI_PAD_LP_UP(x)		(((x) & 0x7) <<  8)
+#define DSI_PAD_SLEW_DN(x)		(((x) & 0x7) << 12)
+#define DSI_PAD_SLEW_UP(x)		(((x) & 0x7) << 16)
+#define DSI_PAD_CONTROL_3		0x51
+#define DSI_PAD_CONTROL_4		0x52
+#define DSI_GANGED_MODE_CONTROL		0x53
+#define DSI_GANGED_MODE_START		0x54
+#define DSI_GANGED_MODE_SIZE		0x55
+#define DSI_RAW_DATA_BYTE_COUNT		0x56
+#define DSI_ULTRA_LOW_POWER_CONTROL	0x57
+#define DSI_INIT_SEQ_DATA_8		0x58
+#define DSI_INIT_SEQ_DATA_9		0x59
+#define DSI_INIT_SEQ_DATA_10		0x5a
+#define DSI_INIT_SEQ_DATA_11		0x5b
+#define DSI_INIT_SEQ_DATA_12		0x5c
+#define DSI_INIT_SEQ_DATA_13		0x5d
+#define DSI_INIT_SEQ_DATA_14		0x5e
+#define DSI_INIT_SEQ_DATA_15		0x5f
+
+#endif
diff --git a/drivers/gpu/drm/tegra/mipi-phy.c b/drivers/gpu/drm/tegra/mipi-phy.c
new file mode 100644
index 000000000000..1d2ad267cfce
--- /dev/null
+++ b/drivers/gpu/drm/tegra/mipi-phy.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+
+#include "mipi-phy.h"
+
+/*
+ * Default D-PHY timings based on MIPI D-PHY specification. Derived from
+ * the valid ranges specified in Section 5.9 of the D-PHY specification
+ * with minor adjustments.
+ */
+int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
+				 unsigned long period)
+{
+	timing->clkmiss = 0;
+	timing->clkpost = 70 + 52 * period;
+	timing->clkpre = 8;
+	timing->clkprepare = 65;
+	timing->clksettle = 95;
+	timing->clktermen = 0;
+	timing->clktrail = 80;
+	timing->clkzero = 260;
+	timing->dtermen = 0;
+	timing->eot = 0;
+	timing->hsexit = 120;
+	timing->hsprepare = 65 + 5 * period;
+	timing->hszero = 145 + 5 * period;
+	timing->hssettle = 85 + 6 * period;
+	timing->hsskip = 40;
+	timing->hstrail = max(8 * period, 60 + 4 * period);
+	timing->init = 100000;
+	timing->lpx = 60;
+	timing->taget = 5 * timing->lpx;
+	timing->tago = 4 * timing->lpx;
+	timing->tasure = 2 * timing->lpx;
+	timing->wakeup = 1000000;
+
+	return 0;
+}
+
+/*
+ * Validate D-PHY timing according to MIPI Alliance Specification for D-PHY,
+ * Section 5.9 "Global Operation Timing Parameters".
+ */
+int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
+			      unsigned long period)
+{
+	if (timing->clkmiss > 60)
+		return -EINVAL;
+
+	if (timing->clkpost < (60 + 52 * period))
+		return -EINVAL;
+
+	if (timing->clkpre < 8)
+		return -EINVAL;
+
+	if (timing->clkprepare < 38 || timing->clkprepare > 95)
+		return -EINVAL;
+
+	if (timing->clksettle < 95 || timing->clksettle > 300)
+		return -EINVAL;
+
+	if (timing->clktermen > 38)
+		return -EINVAL;
+
+	if (timing->clktrail < 60)
+		return -EINVAL;
+
+	if (timing->clkprepare + timing->clkzero < 300)
+		return -EINVAL;
+
+	if (timing->dtermen > 35 + 4 * period)
+		return -EINVAL;
+
+	if (timing->eot > 105 + 12 * period)
+		return -EINVAL;
+
+	if (timing->hsexit < 100)
+		return -EINVAL;
+
+	if (timing->hsprepare < 40 + 4 * period ||
+	    timing->hsprepare > 85 + 6 * period)
+		return -EINVAL;
+
+	if (timing->hsprepare + timing->hszero < 145 + 10 * period)
+		return -EINVAL;
+
+	if ((timing->hssettle < 85 + 6 * period) ||
+	    (timing->hssettle > 145 + 10 * period))
+		return -EINVAL;
+
+	if (timing->hsskip < 40 || timing->hsskip > 55 + 4 * period)
+		return -EINVAL;
+
+	if (timing->hstrail < max(8 * period, 60 + 4 * period))
+		return -EINVAL;
+
+	if (timing->init < 100000)
+		return -EINVAL;
+
+	if (timing->lpx < 50)
+		return -EINVAL;
+
+	if (timing->taget != 5 * timing->lpx)
+		return -EINVAL;
+
+	if (timing->tago != 4 * timing->lpx)
+		return -EINVAL;
+
+	if (timing->tasure < timing->lpx || timing->tasure > 2 * timing->lpx)
+		return -EINVAL;
+
+	if (timing->wakeup < 1000000)
+		return -EINVAL;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/tegra/mipi-phy.h b/drivers/gpu/drm/tegra/mipi-phy.h
new file mode 100644
index 000000000000..d3591694432d
--- /dev/null
+++ b/drivers/gpu/drm/tegra/mipi-phy.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef DRM_TEGRA_MIPI_PHY_H
+#define DRM_TEGRA_MIPI_PHY_H
+
+/*
+ * D-PHY timing parameters
+ *
+ * A detailed description of these parameters can be found in the  MIPI
+ * Alliance Specification for D-PHY, Section 5.9 "Global Operation Timing
+ * Parameters".
+ *
+ * All parameters are specified in nanoseconds.
+ */
+struct mipi_dphy_timing {
+	unsigned int clkmiss;
+	unsigned int clkpost;
+	unsigned int clkpre;
+	unsigned int clkprepare;
+	unsigned int clksettle;
+	unsigned int clktermen;
+	unsigned int clktrail;
+	unsigned int clkzero;
+	unsigned int dtermen;
+	unsigned int eot;
+	unsigned int hsexit;
+	unsigned int hsprepare;
+	unsigned int hszero;
+	unsigned int hssettle;
+	unsigned int hsskip;
+	unsigned int hstrail;
+	unsigned int init;
+	unsigned int lpx;
+	unsigned int taget;
+	unsigned int tago;
+	unsigned int tasure;
+	unsigned int wakeup;
+};
+
+int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
+				 unsigned long period);
+int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
+			      unsigned long period);
+
+#endif
diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c
index c5e0a2a97bc8..f4689a74a9a5 100644
--- a/drivers/gpu/drm/tegra/output.c
+++ b/drivers/gpu/drm/tegra/output.c
@@ -335,6 +335,11 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
 		encoder = DRM_MODE_ENCODER_TMDS;
 		break;
 
+	case TEGRA_OUTPUT_DSI:
+		connector = DRM_MODE_CONNECTOR_DSI;
+		encoder = DRM_MODE_ENCODER_DSI;
+		break;
+
 	default:
 		connector = DRM_MODE_CONNECTOR_Unknown;
 		encoder = DRM_MODE_ENCODER_NONE;
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 7/7] WIP: drm/tegra: Implement DSI transfers
  2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
                   ` (5 preceding siblings ...)
  2013-11-11 12:00 ` [PATCH v3 6/7] drm/tegra: Add DSI support Thierry Reding
@ 2013-11-11 12:00 ` Thierry Reding
  6 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-11 12:00 UTC (permalink / raw)
  To: dri-devel

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 drivers/gpu/drm/tegra/dsi.c | 147 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 147 insertions(+)

diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c
index effd0b5a5554..a100719b3d0c 100644
--- a/drivers/gpu/drm/tegra/dsi.c
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -774,6 +774,131 @@ static void tegra_dsi_initialize(struct tegra_dsi *dsi)
 	tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE);
 }
 
+static int tegra_dsi_set_lp_clk(struct tegra_dsi *dsi, unsigned long pclk)
+{
+	unsigned long bclk, timeout, value;
+	struct clk *clk, *parent, *base;
+	unsigned int mul, div;
+	int err;
+
+	err = tegra_dsi_get_muldiv(dsi->format, &mul, &div);
+	if (err < 0)
+		return err;
+
+	bclk = pclk * 8;
+
+	clk = clk_get_parent(dsi->clk);
+	parent = clk_get_parent(clk);
+	base = clk_get_parent(parent);
+
+	err = clk_set_rate(base, pclk * 2);
+	if (err < 0)
+		return err;
+
+	/* one frame high-speed transmission timeout */
+	timeout = bclk / 512;
+	value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout);
+	tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0);
+
+	/* 2 ms peripheral timeout for panel */
+	timeout = 2 * bclk / 512 * 1000;
+	value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000);
+	tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1);
+
+	value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0);
+	tegra_dsi_writel(dsi, value, DSI_TO_TALLY);
+
+	return 0;
+}
+
+static ssize_t tegra_dsi_host_transfer(struct dsi_host *host,
+				       struct dsi_msg *msg)
+{
+	struct tegra_dsi *dsi = dsi_host_to_tegra(host);
+	unsigned long value, timeout;
+	unsigned int count = 0, i;
+	const u8 *tx = msg->tx;
+	int err;
+
+	dev_info(dsi->dev, "> %s(host=%p, msg=%p)\n", __func__, host, msg);
+
+	/* XXX */
+	drm_panel_enable(dsi->output.panel);
+
+	value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL);
+	value |= DSI_POWER_CONTROL_ENABLE;
+	tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL);
+	usleep_range(300, 1000);
+
+	err = tegra_dsi_set_lp_clk(dsi, 10000000);
+	if (err < 0) {
+		dev_err(dsi->dev, "failed to setup low-power clock: %d\n", err);
+		return err;
+	}
+
+	tegra_dsi_writel(dsi, DSI_HOST_FIFO_DEPTH, DSI_MAX_THRESHOLD);
+
+	value = tegra_dsi_readl(dsi, DSI_CONTROL);
+	value = 0x00003031;
+	tegra_dsi_writel(dsi, value, DSI_CONTROL);
+
+	value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL);
+	value = 0x00102003;
+	tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
+
+	if (msg->rx && msg->rx_len > 0) {
+		value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL);
+		value |= DSI_HOST_CONTROL_BTA;
+		tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL);
+	}
+
+	value = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f);
+
+	value |= tx[0] <<  8;
+	value |= tx[1] << 16;
+
+	tegra_dsi_writel(dsi, value, DSI_WR_DATA);
+
+	tegra_dsi_writel(dsi, 0x00000002, DSI_TRIGGER);
+
+	timeout = jiffies + msecs_to_jiffies(250);
+
+	while (true) {
+		value = tegra_dsi_readl(dsi, DSI_TRIGGER);
+		if ((value & 0x00000002) == 0)
+			break;
+
+		if (time_after(jiffies, timeout))
+			return -ETIMEDOUT;
+
+		usleep_range(25, 100);
+	}
+
+	timeout = jiffies + msecs_to_jiffies(250);
+
+	while (true) {
+		usleep_range(1000, 2000);
+
+		value = tegra_dsi_readl(dsi, DSI_STATUS);
+		count = value & 0x1f;
+
+		if (count > 0)
+			break;
+
+		if (time_after(jiffies, timeout))
+			return -ETIMEDOUT;
+	}
+
+	for (i = 0; i < count; i++)
+		tegra_dsi_readl(dsi, DSI_RD_DATA);
+
+	/* XXX */
+	drm_panel_disable(dsi->output.panel);
+
+	dev_info(dsi->dev, "< %s()\n", __func__);
+	return 0;
+}
+
 static int tegra_dsi_host_attach(struct dsi_host *host,
 				 struct dsi_device *device)
 {
@@ -784,6 +909,27 @@ static int tegra_dsi_host_attach(struct dsi_host *host,
 	if (output->panel) {
 		if (output->connector.dev)
 			drm_helper_hpd_irq_event(output->connector.dev);
+
+		if (0) {
+			struct dsi_msg msg;
+			u8 tx[2], rx[2];
+			ssize_t err;
+
+			rx[0] = rx[1] = 0;
+			tx[0] = tx[1] = 0;
+
+			memset(&msg, 0, sizeof(msg));
+			msg.channel = 0;
+			msg.type = 0x05;
+			msg.tx_len = 2;
+			msg.tx = tx;
+			msg.rx_len = 2;
+			msg.rx = rx;
+
+			err = dsi_host_transfer(&dsi->host, &msg);
+			if (err < 0)
+				dev_err(dsi->dev, "dsi_host_transfer() failed: %zd\n", err);
+		}
 	}
 
 	return 0;
@@ -808,6 +954,7 @@ static int tegra_dsi_host_detach(struct dsi_host *host,
 static const struct dsi_host_ops tegra_dsi_host_ops = {
 	.attach = tegra_dsi_host_attach,
 	.detach = tegra_dsi_host_detach,
+	.transfer = tegra_dsi_host_transfer,
 };
 
 static int tegra_dsi_probe(struct platform_device *pdev)
-- 
1.8.4.2

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-11 12:00 ` [PATCH v3 1/7] drm: Add DSI bus infrastructure Thierry Reding
@ 2013-11-12 14:14   ` Andrzej Hajda
  2013-11-13 21:38     ` Thierry Reding
  0 siblings, 1 reply; 17+ messages in thread
From: Andrzej Hajda @ 2013-11-12 14:14 UTC (permalink / raw)
  To: Thierry Reding, dri-devel

Hi Thierry,

I have already sent patch with DSI bus implementation [1].
It was posted as the first step of CDF implementation attempt,
but in fact it do not depend on CDF.

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

One comment below.

On 11/11/2013 01:00 PM, Thierry Reding wrote:
> In order to support DSI peripherals, add a DSI bus type that devices and
> drivers can be registered with.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> ---

[snip]
> +
> +/*
> + * DSI packet data types
> + */
> +
> +/* processor-sourced packets */
> +#define DSI_CMD_VSYNC_START 0x01
> +#define DSI_CMD_VSYNC_END 0x11
> +#define DSI_CMD_HSYNC_START 0x21
> +#define DSI_CMD_HSYNC_END 0x31
> +#define DSI_CMD_EOT 0x08
> +#define DSI_CMD_COLOR_MODE_OFF 0x02
> +#define DSI_CMD_COLOR_MODE_ON 0x12
> +#define DSI_CMD_SHUT_DOWN 0x22
> +#define DSI_CMD_TURN_ON 0x32
> +#define DSI_CMD_GEN_SHORT_WRITE_0 0x03
> +#define DSI_CMD_GEN_SHORT_WRITE_1 0x13
> +#define DSI_CMD_GEN_SHORT_WRITE_2 0x23
> +#define DSI_CMD_GEN_SHORT_READ_0 0x04
> +#define DSI_CMD_GEN_SHORT_READ_1 0x14
> +#define DSI_CMD_GEN_SHORT_READ_2 0x24
> +#define DSI_CMD_DCS_SHORT_WRITE_0 0x05
> +#define DSI_CMD_DCS_SHORT_WRITE_1 0x15
> +#define DSI_CMD_DCS_SHORT_READ 0x06
> +#define DSI_CMD_SET_MAX_RETURN_PACKET_SIZE 0x37
> +#define DSI_CMD_NULL 0x09
> +#define DSI_CMD_BLANK 0x19
> +#define DSI_CMD_GEN_LONG_WRITE 0x29
> +#define DSI_CMD_DCS_LONG_WRITE 0x39
> +#define DSI_CMD_YCbCr422_20 0x0c
> +#define DSI_CMD_YCbCr422_24 0x1c
> +#define DSI_CMD_YCbCr422_16 0x2c
> +#define DSI_CMD_RGB30 0x0d
> +#define DSI_CMD_RGB36 0x1d
> +#define DSI_CMD_YCbCr420 0x3d
> +#define DSI_CMD_RGB16 0x0e
> +#define DSI_CMD_RGB18 0x1e
> +#define DSI_CMD_RGB18NP 0x2e
> +#define DSI_CMD_RGB24 0x3e
> +
> +/* peripheral-sourced */
> +#define DSI_RSP_ACK_ERR 0x02
> +#define DSI_RSP_EOT 0x08
> +#define DSI_RSP_GEN_SHORT_READ_1 0x11
> +#define DSI_RSP_GEN_SHORT_READ_2 0x12
> +#define DSI_RSP_GEN_LONG_READ 0x1a
> +#define DSI_RSP_DCS_LONG_READ 0x1c
> +#define DSI_RSP_DCS_SHORT_READ_1 0x21
> +#define DSI_RSP_DCS_SHORT_READ_2 0x22
> +

Those macros are already defined in include/video/mipi_display.h

Regards
Andrzej

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-12 14:14   ` Andrzej Hajda
@ 2013-11-13 21:38     ` Thierry Reding
  2013-11-14 14:15       ` Andrzej Hajda
  0 siblings, 1 reply; 17+ messages in thread
From: Thierry Reding @ 2013-11-13 21:38 UTC (permalink / raw)
  To: Andrzej Hajda; +Cc: dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 3175 bytes --]

On Tue, Nov 12, 2013 at 03:14:22PM +0100, Andrzej Hajda wrote:
> Hi Thierry,
> 
> I have already sent patch with DSI bus implementation [1].
> It was posted as the first step of CDF implementation attempt,
> but in fact it do not depend on CDF.
> 
> [1]
> http://www.mail-archive.com/dri-devel@lists.freedesktop.org/msg45252.html

Seems like that patchset was never merged. I guess probably because it
was posted as part of CDF work.

Do you have any plans on continuing work on that? If not I could extract
the DSI bus patch from the series, it's largely similar to the patch I
proposed here, and rework it somewhat. I'd very much like to avoid
putting the code in drivers/video, though, since that's considered
obsolete. Furthermore I think if we kept the transfer function proposed
in my patch should make it easier to address Bert's comments from your
posting.

> One comment below.
> 
> On 11/11/2013 01:00 PM, Thierry Reding wrote:
> > In order to support DSI peripherals, add a DSI bus type that devices and
> > drivers can be registered with.
> > 
> > Signed-off-by: Thierry Reding <treding@nvidia.com>
> > ---
> 
> [snip]
> > +
> > +/*
> > + * DSI packet data types
> > + */
> > +
> > +/* processor-sourced packets */
> > +#define DSI_CMD_VSYNC_START 0x01
> > +#define DSI_CMD_VSYNC_END 0x11
> > +#define DSI_CMD_HSYNC_START 0x21
> > +#define DSI_CMD_HSYNC_END 0x31
> > +#define DSI_CMD_EOT 0x08
> > +#define DSI_CMD_COLOR_MODE_OFF 0x02
> > +#define DSI_CMD_COLOR_MODE_ON 0x12
> > +#define DSI_CMD_SHUT_DOWN 0x22
> > +#define DSI_CMD_TURN_ON 0x32
> > +#define DSI_CMD_GEN_SHORT_WRITE_0 0x03
> > +#define DSI_CMD_GEN_SHORT_WRITE_1 0x13
> > +#define DSI_CMD_GEN_SHORT_WRITE_2 0x23
> > +#define DSI_CMD_GEN_SHORT_READ_0 0x04
> > +#define DSI_CMD_GEN_SHORT_READ_1 0x14
> > +#define DSI_CMD_GEN_SHORT_READ_2 0x24
> > +#define DSI_CMD_DCS_SHORT_WRITE_0 0x05
> > +#define DSI_CMD_DCS_SHORT_WRITE_1 0x15
> > +#define DSI_CMD_DCS_SHORT_READ 0x06
> > +#define DSI_CMD_SET_MAX_RETURN_PACKET_SIZE 0x37
> > +#define DSI_CMD_NULL 0x09
> > +#define DSI_CMD_BLANK 0x19
> > +#define DSI_CMD_GEN_LONG_WRITE 0x29
> > +#define DSI_CMD_DCS_LONG_WRITE 0x39
> > +#define DSI_CMD_YCbCr422_20 0x0c
> > +#define DSI_CMD_YCbCr422_24 0x1c
> > +#define DSI_CMD_YCbCr422_16 0x2c
> > +#define DSI_CMD_RGB30 0x0d
> > +#define DSI_CMD_RGB36 0x1d
> > +#define DSI_CMD_YCbCr420 0x3d
> > +#define DSI_CMD_RGB16 0x0e
> > +#define DSI_CMD_RGB18 0x1e
> > +#define DSI_CMD_RGB18NP 0x2e
> > +#define DSI_CMD_RGB24 0x3e
> > +
> > +/* peripheral-sourced */
> > +#define DSI_RSP_ACK_ERR 0x02
> > +#define DSI_RSP_EOT 0x08
> > +#define DSI_RSP_GEN_SHORT_READ_1 0x11
> > +#define DSI_RSP_GEN_SHORT_READ_2 0x12
> > +#define DSI_RSP_GEN_LONG_READ 0x1a
> > +#define DSI_RSP_DCS_LONG_READ 0x1c
> > +#define DSI_RSP_DCS_SHORT_READ_1 0x21
> > +#define DSI_RSP_DCS_SHORT_READ_2 0x22
> > +
> 
> Those macros are already defined in include/video/mipi_display.h

I wasn't aware, thanks for the pointer. It's somewhat unfortunate that
that's in include/video, but judging by the git log, it has been there
for quite a while.

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 836 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-13 21:38     ` Thierry Reding
@ 2013-11-14 14:15       ` Andrzej Hajda
  2013-11-14 15:04         ` Bert Kenward
  2013-11-18 12:57         ` Thierry Reding
  0 siblings, 2 replies; 17+ messages in thread
From: Andrzej Hajda @ 2013-11-14 14:15 UTC (permalink / raw)
  To: Thierry Reding; +Cc: dri-devel

Hi Thierry,

On 11/13/2013 10:38 PM, Thierry Reding wrote:
> On Tue, Nov 12, 2013 at 03:14:22PM +0100, Andrzej Hajda wrote:
>> Hi Thierry,
>>
>> I have already sent patch with DSI bus implementation [1].
>> It was posted as the first step of CDF implementation attempt,
>> but in fact it do not depend on CDF.
>>
>> [1]
>> http://www.mail-archive.com/dri-devel@lists.freedesktop.org/msg45252.html
> Seems like that patchset was never merged. I guess probably because it
> was posted as part of CDF work.
>
> Do you have any plans on continuing work on that?If not I could extract
> the DSI bus patch from the series, it's largely similar to the patch I
> proposed here, and rework it somewhat.
I will soon sent new patch with the current version of the bus.
It could be a better base to your rework.
> I'd very much like to avoid
> putting the code in drivers/video, though, since that's considered
> obsolete.
I have followed convention proposed by Laurent in his DBI bus.
It seems to me OK - DSI bus is more related to video than to drm.
I know that drivers/video is mostly occupied by FB drivers,
but according to Kconfig it is not only for FB.
Of course this is only my suggestion.

>  Furthermore I think if we kept the transfer function proposed
> in my patch should make it easier to address Bert's comments from your
> posting.
I am not sure which part of Barts comment you are addressing.
Anyway I also prefer passing struct and returning ssize_t.
I am not sure about splitting type and channel but this seems to be a
minor detail.

Regards
Andrzej

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-14 14:15       ` Andrzej Hajda
@ 2013-11-14 15:04         ` Bert Kenward
  2013-11-18 11:22           ` Thierry Reding
  2013-11-18 12:57         ` Thierry Reding
  1 sibling, 1 reply; 17+ messages in thread
From: Bert Kenward @ 2013-11-14 15:04 UTC (permalink / raw)
  To: Andrzej Hajda, Thierry Reding; +Cc: dri-devel

On 11/14/2013 14:16, Andrzej Hajda wrote:
> On 11/13/2013 10:38 PM, Thierry Reding wrote:
> >  Furthermore I think if we kept the transfer function proposed
> > in my patch should make it easier to address Bert's comments from your
> > posting.
> I am not sure which part of Barts comment you are addressing.
> Anyway I also prefer passing struct and returning ssize_t.
> I am not sure about splitting type and channel but this seems to be a
> minor detail.

Most of the comments I made about Andrzej's patch from last month apply here, and would result in extensions to struct dsi_msg. Some means of choosing whether the transfer should be in HS or LP mode is necessary. For video mode panels some means of specifying a period (VFP, VBP, etc) for sending the transfer is needed. Perhaps struct dsi_msg should include:

#define DSI_WINDOW_VFP  (1 << 0)
#define DSI_WINDOW_ACT  (1 << 1)
#define DSI_WINDOW_VBP  (1 << 2)
#define DSI_WINDOW_VSY  (1 << 3)

/**
 * struct dsi_msg - DSI command message
 * @channel: virtual channel to send the message to
 * @type: data ID of the message
 * @lp_mode: send in LP mode if non-zero
 * @window: video period when transfer is allowed - bitmask of DSI_WINDOW_*
 * @tx_len: length of transmission buffer
 * @tx: transmission buffer
 * @rx_len: length of reception buffer
 * @rx: reception buffer
 */
struct dsi_msg {
	u8 channel;
	u8 type;
	u8 lp_mode;
	u8 window;

	size_t tx_len;
	void *tx;

	size_t rx_len;
	void *rx;
};

The ability to specify synchronisation with a tearing effect control signal for a command mode panel is obviously important. It's somewhat analogous to the "window" option for video mode.

They're little used, but a mechanism for sending triggers should be included. They're probably sufficiently different that it should be a different op.

All the best,

Bert.

-- 
Bert Kenward
Software Engineer
Broadcom Mobile Platform Solutions
Cambridge, UK

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-14 15:04         ` Bert Kenward
@ 2013-11-18 11:22           ` Thierry Reding
  2013-11-18 11:59             ` Bert Kenward
  0 siblings, 1 reply; 17+ messages in thread
From: Thierry Reding @ 2013-11-18 11:22 UTC (permalink / raw)
  To: Bert Kenward; +Cc: Andrzej Hajda, dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 2643 bytes --]

On Thu, Nov 14, 2013 at 03:04:19PM +0000, Bert Kenward wrote:
> On 11/14/2013 14:16, Andrzej Hajda wrote:
> > On 11/13/2013 10:38 PM, Thierry Reding wrote:
> > >  Furthermore I think if we kept the transfer function proposed
> > > in my patch should make it easier to address Bert's comments from your
> > > posting.
> > I am not sure which part of Barts comment you are addressing.
> > Anyway I also prefer passing struct and returning ssize_t.
> > I am not sure about splitting type and channel but this seems to be a
> > minor detail.
> 
> Most of the comments I made about Andrzej's patch from last month
> apply here, and would result in extensions to struct dsi_msg. Some
> means of choosing whether the transfer should be in HS or LP mode is
> necessary. For video mode panels some means of specifying a period
> (VFP, VBP, etc) for sending the transfer is needed. Perhaps struct
> dsi_msg should include:
> 
> #define DSI_WINDOW_VFP  (1 << 0)
> #define DSI_WINDOW_ACT  (1 << 1)
> #define DSI_WINDOW_VBP  (1 << 2)
> #define DSI_WINDOW_VSY  (1 << 3)
> 
> /**
>  * struct dsi_msg - DSI command message
>  * @channel: virtual channel to send the message to
>  * @type: data ID of the message
>  * @lp_mode: send in LP mode if non-zero

Perhaps a flags field would be more flexible here. I can easily imagine
other I can imagine that other flags may be needed eventually.

>  * @window: video period when transfer is allowed - bitmask of DSI_WINDOW_*

I'm not sure if this is the right interface. What will happen for
instance if the hardware doesn't support any of the bits in that mask?
Perhaps a slightly better approach might be to expose the capabilities
of the DSI host, so that the DSI core knows up front which windows can
be used.

>  * @tx_len: length of transmission buffer
>  * @tx: transmission buffer
>  * @rx_len: length of reception buffer
>  * @rx: reception buffer
>  */
> struct dsi_msg {
> 	u8 channel;
> 	u8 type;
> 	u8 lp_mode;
> 	u8 window;
> 
> 	size_t tx_len;
> 	void *tx;
> 
> 	size_t rx_len;
> 	void *rx;
> };
> 
> The ability to specify synchronisation with a tearing effect control
> signal for a command mode panel is obviously important. It's somewhat
> analogous to the "window" option for video mode.
> 
> They're little used, but a mechanism for sending triggers should be
> included. They're probably sufficiently different that it should be a
> different op.

I agree. While I currently have no use-case where this is required, I
think having a struct dsi_msg makes it easier to extend the featureset
on an as-needed basis.

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 836 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-18 11:22           ` Thierry Reding
@ 2013-11-18 11:59             ` Bert Kenward
  2013-11-18 12:49               ` Thierry Reding
  0 siblings, 1 reply; 17+ messages in thread
From: Bert Kenward @ 2013-11-18 11:59 UTC (permalink / raw)
  To: Thierry Reding; +Cc: Andrzej Hajda, dri-devel

On 11/18/2013 11:22, Thierry Reding wrote:
> On Thu, Nov 14, 2013 at 03:04:19PM +0000, Bert Kenward wrote:
> > #define DSI_WINDOW_VFP  (1 << 0)
> > #define DSI_WINDOW_ACT  (1 << 1)
> > #define DSI_WINDOW_VBP  (1 << 2)
> > #define DSI_WINDOW_VSY  (1 << 3)
> >
> > /**
> >  * struct dsi_msg - DSI command message
> >  * @channel: virtual channel to send the message to
> >  * @type: data ID of the message
> >  * @lp_mode: send in LP mode if non-zero
> 
> Perhaps a flags field would be more flexible here. I can easily imagine
> other I can imagine that other flags may be needed eventually.

Agreed. "TE synchronised" would be one such extra flag, for supporting command mode updates.

> >  * @window: video period when transfer is allowed - bitmask of
> DSI_WINDOW_*
> 
> I'm not sure if this is the right interface. What will happen for
> instance if the hardware doesn't support any of the bits in that mask?
> Perhaps a slightly better approach might be to expose the capabilities
> of the DSI host, so that the DSI core knows up front which windows can
> be used.

Exposing the capabilities seems like the smart thing to do, certainly - but you'd still need a way to specify which of those capabilities you want to use for each transfer.

I'd suggest that hardware would ignore bits that it couldn't support - in the limit, hardware that has no way to choose when to send a command during video would ignore this completely. I realise that could well cause confusion when trying to work out why a particularly display is misbehaving when you think you're sending commands at the right time.

Bert.

-- 
Bert Kenward
Software Engineer
Broadcom Mobile Platform Solutions
Cambridge, UK

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-18 11:59             ` Bert Kenward
@ 2013-11-18 12:49               ` Thierry Reding
  0 siblings, 0 replies; 17+ messages in thread
From: Thierry Reding @ 2013-11-18 12:49 UTC (permalink / raw)
  To: Bert Kenward; +Cc: Andrzej Hajda, dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 2956 bytes --]

On Mon, Nov 18, 2013 at 11:59:23AM +0000, Bert Kenward wrote:
> On 11/18/2013 11:22, Thierry Reding wrote:
> > On Thu, Nov 14, 2013 at 03:04:19PM +0000, Bert Kenward wrote:
> > > #define DSI_WINDOW_VFP  (1 << 0)
> > > #define DSI_WINDOW_ACT  (1 << 1)
> > > #define DSI_WINDOW_VBP  (1 << 2)
> > > #define DSI_WINDOW_VSY  (1 << 3)
> > >
> > > /**
> > >  * struct dsi_msg - DSI command message
> > >  * @channel: virtual channel to send the message to
> > >  * @type: data ID of the message
> > >  * @lp_mode: send in LP mode if non-zero
> > 
> > Perhaps a flags field would be more flexible here. I can easily imagine
> > other I can imagine that other flags may be needed eventually.
> 
> Agreed. "TE synchronised" would be one such extra flag, for supporting command mode updates.
> 
> > >  * @window: video period when transfer is allowed - bitmask of
> > DSI_WINDOW_*
> > 
> > I'm not sure if this is the right interface. What will happen for
> > instance if the hardware doesn't support any of the bits in that mask?
> > Perhaps a slightly better approach might be to expose the capabilities
> > of the DSI host, so that the DSI core knows up front which windows can
> > be used.
> 
> Exposing the capabilities seems like the smart thing to do, certainly
> - but you'd still need a way to specify which of those capabilities
> you want to use for each transfer.

Yes. I think we'll still need to have that. It's just that for some
transfers it doesn't matter during which window they are executed.
Although I guess in those cases the caller could just specify all bits
to signal that it doesn't care.

> I'd suggest that hardware would ignore bits that it couldn't support -
> in the limit, hardware that has no way to choose when to send a
> command during video would ignore this completely. I realise that
> could well cause confusion when trying to work out why a particularly
> display is misbehaving when you think you're sending commands at the
> right time.

I think at the very least if there's no match between the requested set
of windows and the ones that a particular DSI host supports, then the
driver should report an error.

The reason why I thought that exposing the capabilities might be useful
is that the caller could be smart about how to transfer a message, or
perhaps use different messages to the same effect. But perhaps that's
not even possible and maybe not worth considering.

A smart thing to do in my opinion will be to not try to overengineer
this from the beginning. That's why I'm thinking of splitting out the
whose dsi_msg support in a first step, so that the bus infrastructure
can be merged without having discussed all possible cases. And even so
dsi_msg doesn't have to be perfect from the start. It's an in-kernel
API, therefore can change easily if needed. The less we require of it
now the easier it will be to extend when the need arises.

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 836 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-14 14:15       ` Andrzej Hajda
  2013-11-14 15:04         ` Bert Kenward
@ 2013-11-18 12:57         ` Thierry Reding
  2013-11-18 16:42           ` Andrzej Hajda
  1 sibling, 1 reply; 17+ messages in thread
From: Thierry Reding @ 2013-11-18 12:57 UTC (permalink / raw)
  To: Andrzej Hajda; +Cc: dri-devel


[-- Attachment #1.1: Type: text/plain, Size: 2854 bytes --]

On Thu, Nov 14, 2013 at 03:15:57PM +0100, Andrzej Hajda wrote:
> Hi Thierry,
> 
> On 11/13/2013 10:38 PM, Thierry Reding wrote:
> > On Tue, Nov 12, 2013 at 03:14:22PM +0100, Andrzej Hajda wrote:
> >> Hi Thierry,
> >>
> >> I have already sent patch with DSI bus implementation [1].
> >> It was posted as the first step of CDF implementation attempt,
> >> but in fact it do not depend on CDF.
> >>
> >> [1]
> >> http://www.mail-archive.com/dri-devel@lists.freedesktop.org/msg45252.html
> > Seems like that patchset was never merged. I guess probably because it
> > was posted as part of CDF work.
> >
> > Do you have any plans on continuing work on that?If not I could extract
> > the DSI bus patch from the series, it's largely similar to the patch I
> > proposed here, and rework it somewhat.
> I will soon sent new patch with the current version of the bus.
> It could be a better base to your rework.

Any estimate on when this will be? I want to get started on this as soon
as possible so we can get this in good shape for 3.14. I suspect that if
I start based on the current patch, I won't have to redo everything when
updating for the next version. Quite a bit should remain similar, right?

> > I'd very much like to avoid
> > putting the code in drivers/video, though, since that's considered
> > obsolete.
> I have followed convention proposed by Laurent in his DBI bus.
> It seems to me OK - DSI bus is more related to video than to drm.
> I know that drivers/video is mostly occupied by FB drivers,
> but according to Kconfig it is not only for FB.
> Of course this is only my suggestion.

I've had an IRC discussion with Dave and Rob (added on Cc), and they
both agreed that implementing it as part of DRM would be preferred.

The reason, as I've said before, is primarily that drivers/video is
considered obsolete and new drivers and features should be implemented
within the context of DRM/KMS. Additionally this has the benefit of
being able to reuse the native DRM data structures and types, as well as
helper functions, without having to go through an extra layer of
compatibility.

> >  Furthermore I think if we kept the transfer function proposed
> > in my patch should make it easier to address Bert's comments from your
> > posting.
> I am not sure which part of Barts comment you are addressing.
> Anyway I also prefer passing struct and returning ssize_t.
> I am not sure about splitting type and channel but this seems to be a
> minor detail.

I find that to be a perfectly natural split. A DSI peripheral will have
an associated virtual channel number anyway, and having a separate field
will allow drivers to just copy that field into the dsi_msg's
equivalent.

The alternative would be to require each driver to properly encode the
channel and data type.

Thierry

[-- Attachment #1.2: Type: application/pgp-signature, Size: 836 bytes --]

[-- Attachment #2: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v3 1/7] drm: Add DSI bus infrastructure
  2013-11-18 12:57         ` Thierry Reding
@ 2013-11-18 16:42           ` Andrzej Hajda
  0 siblings, 0 replies; 17+ messages in thread
From: Andrzej Hajda @ 2013-11-18 16:42 UTC (permalink / raw)
  To: Thierry Reding; +Cc: dri-devel

On 11/18/2013 01:57 PM, Thierry Reding wrote:
> On Thu, Nov 14, 2013 at 03:15:57PM +0100, Andrzej Hajda wrote:
>> Hi Thierry,
>>
>> On 11/13/2013 10:38 PM, Thierry Reding wrote:
>>> On Tue, Nov 12, 2013 at 03:14:22PM +0100, Andrzej Hajda wrote:
>>>> Hi Thierry,
>>>>
>>>> I have already sent patch with DSI bus implementation [1].
>>>> It was posted as the first step of CDF implementation attempt,
>>>> but in fact it do not depend on CDF.
>>>>
>>>> [1]
>>>> http://www.mail-archive.com/dri-devel@lists.freedesktop.org/msg45252.html
>>> Seems like that patchset was never merged. I guess probably because it
>>> was posted as part of CDF work.
>>>
>>> Do you have any plans on continuing work on that?If not I could extract
>>> the DSI bus patch from the series, it's largely similar to the patch I
>>> proposed here, and rework it somewhat.
>> I will soon sent new patch with the current version of the bus.
>> It could be a better base to your rework.
> Any estimate on when this will be? I want to get started on this as soon
> as possible so we can get this in good shape for 3.14. I suspect that if
> I start based on the current patch, I won't have to redo everything when
> updating for the next version. Quite a bit should remain similar, right?
Patch sent.
>
>>> I'd very much like to avoid
>>> putting the code in drivers/video, though, since that's considered
>>> obsolete.
>> I have followed convention proposed by Laurent in his DBI bus.
>> It seems to me OK - DSI bus is more related to video than to drm.
>> I know that drivers/video is mostly occupied by FB drivers,
>> but according to Kconfig it is not only for FB.
>> Of course this is only my suggestion.
> I've had an IRC discussion with Dave and Rob (added on Cc), and they
> both agreed that implementing it as part of DRM would be preferred.
>
> The reason, as I've said before, is primarily that drivers/video is
> considered obsolete and new drivers and features should be implemented
> within the context of DRM/KMS. Additionally this has the benefit of
> being able to reuse the native DRM data structures and types, as well as
> helper functions, without having to go through an extra layer of
> compatibility.
>
>>>  Furthermore I think if we kept the transfer function proposed
>>> in my patch should make it easier to address Bert's comments from your
>>> posting.
>> I am not sure which part of Barts comment you are addressing.
>> Anyway I also prefer passing struct and returning ssize_t.
>> I am not sure about splitting type and channel but this seems to be a
>> minor detail.
> I find that to be a perfectly natural split. A DSI peripheral will have
> an associated virtual channel number anyway, and having a separate field
> will allow drivers to just copy that field into the dsi_msg's
> equivalent.
>
> The alternative would be to require each driver to properly encode the
> channel and data type.
Other alternatives:
- use helpers functions to call transfer op, encoding could be done by them,
- fill channel by DSI master, based on DSI slave(seems to be little
over-engineering)

But I see no big difference between them.

Regards
Andrzej
>
> Thierry

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2013-11-18 16:42 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-11-11 12:00 [PATCH v3 0/7] drm/tegra: Add DSI and panel support Thierry Reding
2013-11-11 12:00 ` [PATCH v3 1/7] drm: Add DSI bus infrastructure Thierry Reding
2013-11-12 14:14   ` Andrzej Hajda
2013-11-13 21:38     ` Thierry Reding
2013-11-14 14:15       ` Andrzej Hajda
2013-11-14 15:04         ` Bert Kenward
2013-11-18 11:22           ` Thierry Reding
2013-11-18 11:59             ` Bert Kenward
2013-11-18 12:49               ` Thierry Reding
2013-11-18 12:57         ` Thierry Reding
2013-11-18 16:42           ` Andrzej Hajda
2013-11-11 12:00 ` [PATCH v3 2/7] drm: Add panel support Thierry Reding
2013-11-11 12:00 ` [PATCH v3 3/7] drm/panel: Add simple " Thierry Reding
2013-11-11 12:00 ` [PATCH v3 4/7] drm/tegra: Implement " Thierry Reding
2013-11-11 12:00 ` [PATCH v3 5/7] gpu: host1x: Add MIPI pad calibration support Thierry Reding
2013-11-11 12:00 ` [PATCH v3 6/7] drm/tegra: Add DSI support Thierry Reding
2013-11-11 12:00 ` [PATCH v3 7/7] WIP: drm/tegra: Implement DSI transfers Thierry Reding

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.