All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/7] drm: Add support for tiny LCD displays
@ 2017-02-11 18:48 ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

drm: Add support for tiny LCD displays

This is an attempt at providing a DRM version of drivers/staging/fbtft.

The tinydrm library provides a very simplified view of DRM in particular
for tiny displays that has onboard video memory and is connected through
a slow bus like SPI/I2C.

Jani pointed out that the backlight dependency was broken, so since it's
so small and most tinydrm drivers will need it, I just selected it.

The DT rotation property has been moved to display/panel/panel.txt.


Noralf.

Changes since version 3:
- Rebase on new drm_fbdev_cma_init_with_funcs() signature
- Fix backlight dependency by selecting it
- Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate
- Changed display/display.txt -> display/panel/panel.txt

Changes since version 2:
- Remove fbdev after drm unregister, not before.
- Added Documentation/devicetree/bindings/display/display.txt

Changes since version 1:
- Add tinydrm.rst
- Set tdev->fbdev_cma=NULL on unregister (lastclose is called after that).
- Remove some DRM_DEBUG*()
- Write-combined memory has uncached reads, so speed up by copying/buffering
  one pixel line before conversion.

Changes since RFC v2:
- Rebased on new core helpers
- Don't use drm_panel
- Flush when the framebuffer is changed on the plane
- Add devm_tinydrm_init()
- Fix PRIME support, set vaddr
- Use atomic helpers in suspend/resume
- Add a tinydrm_connector with one display mode
- Set mode_config.preferred_depth and use it for fbdev
- Subclass tinydrm_device in drivers instead of bloating the structure
- The PiTFT display uses a MI0283QT panel, write driver for that instead.
- Drop homegrown lcdreg module, it ended up as a collection of special
  cases.
- Add more documentation

Changes since RFC v1:
- Add fb_deferred_io support to drm_fb_helper and drm_fb_cma_helper,
  and use drm_fb_cma_helper instead.
- Move display pipeline code to drm_simple_kms_helper.
- Don't use (struct drm_driver *)->load().
- Make tinydrm more like a library, exporting the internals.
- Move the struct drm_driver definition from the tinydrm module to the
  driver using a helper macro: TINYDRM_DRM_DRIVER.
- Remove dirtyfb() async code.
- Added support for partial display updates.


Noralf Trønnes (7):
  drm: Add DRM support for tiny LCD displays
  drm/tinydrm: Add helper functions
  drm/tinydrm: Add MIPI DBI support
  of: Add vendor prefix for Multi-Inno
  dt-bindings: display/panel: Add common rotation property
  dt-bindings: Add Multi-Inno MI0283QT binding
  drm/tinydrm: Add support for Multi-Inno MI0283QT display

 .../bindings/display/multi-inno,mi0283qt.txt       |   27 +
 .../devicetree/bindings/display/panel/panel.txt    |    4 +
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 Documentation/gpu/index.rst                        |    1 +
 Documentation/gpu/tinydrm.rst                      |   42 +
 MAINTAINERS                                        |    6 +
 drivers/gpu/drm/Kconfig                            |    2 +
 drivers/gpu/drm/Makefile                           |    1 +
 drivers/gpu/drm/tinydrm/Kconfig                    |   21 +
 drivers/gpu/drm/tinydrm/Makefile                   |    7 +
 drivers/gpu/drm/tinydrm/core/Makefile              |    3 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c        |  376 ++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  460 +++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c        |  234 +++++
 drivers/gpu/drm/tinydrm/mi0283qt.c                 |  279 ++++++
 drivers/gpu/drm/tinydrm/mipi-dbi.c                 | 1005 ++++++++++++++++++++
 include/drm/tinydrm/ili9341.h                      |   54 ++
 include/drm/tinydrm/mipi-dbi.h                     |  107 +++
 include/drm/tinydrm/tinydrm-helpers.h              |  100 ++
 include/drm/tinydrm/tinydrm.h                      |  115 +++
 20 files changed, 2845 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
 create mode 100644 Documentation/devicetree/bindings/display/panel/panel.txt
 create mode 100644 Documentation/gpu/tinydrm.rst
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
 create mode 100644 drivers/gpu/drm/tinydrm/mi0283qt.c
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/ili9341.h
 create mode 100644 include/drm/tinydrm/mipi-dbi.h
 create mode 100644 include/drm/tinydrm/tinydrm-helpers.h
 create mode 100644 include/drm/tinydrm/tinydrm.h

--
2.10.2

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

* [PATCH v4 0/7] drm: Add support for tiny LCD displays
@ 2017-02-11 18:48 ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

drm: Add support for tiny LCD displays

This is an attempt at providing a DRM version of drivers/staging/fbtft.

The tinydrm library provides a very simplified view of DRM in particular
for tiny displays that has onboard video memory and is connected through
a slow bus like SPI/I2C.

Jani pointed out that the backlight dependency was broken, so since it's
so small and most tinydrm drivers will need it, I just selected it.

The DT rotation property has been moved to display/panel/panel.txt.


Noralf.

Changes since version 3:
- Rebase on new drm_fbdev_cma_init_with_funcs() signature
- Fix backlight dependency by selecting it
- Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate
- Changed display/display.txt -> display/panel/panel.txt

Changes since version 2:
- Remove fbdev after drm unregister, not before.
- Added Documentation/devicetree/bindings/display/display.txt

Changes since version 1:
- Add tinydrm.rst
- Set tdev->fbdev_cma=NULL on unregister (lastclose is called after that).
- Remove some DRM_DEBUG*()
- Write-combined memory has uncached reads, so speed up by copying/buffering
  one pixel line before conversion.

Changes since RFC v2:
- Rebased on new core helpers
- Don't use drm_panel
- Flush when the framebuffer is changed on the plane
- Add devm_tinydrm_init()
- Fix PRIME support, set vaddr
- Use atomic helpers in suspend/resume
- Add a tinydrm_connector with one display mode
- Set mode_config.preferred_depth and use it for fbdev
- Subclass tinydrm_device in drivers instead of bloating the structure
- The PiTFT display uses a MI0283QT panel, write driver for that instead.
- Drop homegrown lcdreg module, it ended up as a collection of special
  cases.
- Add more documentation

Changes since RFC v1:
- Add fb_deferred_io support to drm_fb_helper and drm_fb_cma_helper,
  and use drm_fb_cma_helper instead.
- Move display pipeline code to drm_simple_kms_helper.
- Don't use (struct drm_driver *)->load().
- Make tinydrm more like a library, exporting the internals.
- Move the struct drm_driver definition from the tinydrm module to the
  driver using a helper macro: TINYDRM_DRM_DRIVER.
- Remove dirtyfb() async code.
- Added support for partial display updates.


Noralf Trønnes (7):
  drm: Add DRM support for tiny LCD displays
  drm/tinydrm: Add helper functions
  drm/tinydrm: Add MIPI DBI support
  of: Add vendor prefix for Multi-Inno
  dt-bindings: display/panel: Add common rotation property
  dt-bindings: Add Multi-Inno MI0283QT binding
  drm/tinydrm: Add support for Multi-Inno MI0283QT display

 .../bindings/display/multi-inno,mi0283qt.txt       |   27 +
 .../devicetree/bindings/display/panel/panel.txt    |    4 +
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 Documentation/gpu/index.rst                        |    1 +
 Documentation/gpu/tinydrm.rst                      |   42 +
 MAINTAINERS                                        |    6 +
 drivers/gpu/drm/Kconfig                            |    2 +
 drivers/gpu/drm/Makefile                           |    1 +
 drivers/gpu/drm/tinydrm/Kconfig                    |   21 +
 drivers/gpu/drm/tinydrm/Makefile                   |    7 +
 drivers/gpu/drm/tinydrm/core/Makefile              |    3 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c        |  376 ++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  460 +++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c        |  234 +++++
 drivers/gpu/drm/tinydrm/mi0283qt.c                 |  279 ++++++
 drivers/gpu/drm/tinydrm/mipi-dbi.c                 | 1005 ++++++++++++++++++++
 include/drm/tinydrm/ili9341.h                      |   54 ++
 include/drm/tinydrm/mipi-dbi.h                     |  107 +++
 include/drm/tinydrm/tinydrm-helpers.h              |  100 ++
 include/drm/tinydrm/tinydrm.h                      |  115 +++
 20 files changed, 2845 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
 create mode 100644 Documentation/devicetree/bindings/display/panel/panel.txt
 create mode 100644 Documentation/gpu/tinydrm.rst
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
 create mode 100644 drivers/gpu/drm/tinydrm/mi0283qt.c
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/ili9341.h
 create mode 100644 include/drm/tinydrm/mipi-dbi.h
 create mode 100644 include/drm/tinydrm/tinydrm-helpers.h
 create mode 100644 include/drm/tinydrm/tinydrm.h

--
2.10.2

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

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

* [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
  2017-02-11 18:48 ` Noralf Trønnes
@ 2017-02-11 18:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

tinydrm provides helpers for very simple displays that can use
CMA backed framebuffers and need flushing on changes.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Rebase on new drm_fbdev_cma_init_with_funcs() signature

 Documentation/gpu/index.rst                 |   1 +
 Documentation/gpu/tinydrm.rst               |  21 ++
 drivers/gpu/drm/Kconfig                     |   2 +
 drivers/gpu/drm/Makefile                    |   1 +
 drivers/gpu/drm/tinydrm/Kconfig             |   8 +
 drivers/gpu/drm/tinydrm/Makefile            |   1 +
 drivers/gpu/drm/tinydrm/core/Makefile       |   3 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 376 ++++++++++++++++++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 234 +++++++++++++++++
 include/drm/tinydrm/tinydrm.h               | 115 +++++++++
 10 files changed, 762 insertions(+)
 create mode 100644 Documentation/gpu/tinydrm.rst
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
 create mode 100644 include/drm/tinydrm/tinydrm.h

diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst
index 367d7c3..f81278a 100644
--- a/Documentation/gpu/index.rst
+++ b/Documentation/gpu/index.rst
@@ -11,6 +11,7 @@ Linux GPU Driver Developer's Guide
    drm-kms-helpers
    drm-uapi
    i915
+   tinydrm
    vga-switcheroo
    vgaarbiter

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
new file mode 100644
index 0000000..ec4a20d
--- /dev/null
+++ b/Documentation/gpu/tinydrm.rst
@@ -0,0 +1,21 @@
+==========================
+drm/tinydrm Driver library
+==========================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :doc: overview
+
+Core functionality
+==================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :doc: core
+
+.. kernel-doc:: include/drm/tinydrm/tinydrm.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :export:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
+   :export:
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 90bc65d..88e01e08e 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -263,6 +263,8 @@ source "drivers/gpu/drm/mxsfb/Kconfig"

 source "drivers/gpu/drm/meson/Kconfig"

+source "drivers/gpu/drm/tinydrm/Kconfig"
+
 # Keep legacy drivers last

 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 92de399..3ee9579 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -94,3 +94,4 @@ obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
 obj-$(CONFIG_DRM_ZTE)	+= zte/
 obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
+obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
new file mode 100644
index 0000000..ffb873f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -0,0 +1,8 @@
+menuconfig DRM_TINYDRM
+	tristate "Support for simple displays"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	help
+	  Choose this option if you have a tinydrm supported display.
+	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
new file mode 100644
index 0000000..7476ed1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DRM_TINYDRM)		+= core/
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
new file mode 100644
index 0000000..4f14a0f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -0,0 +1,3 @@
+tinydrm-y := tinydrm-core.o tinydrm-pipe.o
+
+obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
new file mode 100644
index 0000000..6a257dd
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/device.h>
+#include <linux/dma-buf.h>
+
+/**
+ * DOC: overview
+ *
+ * This library provides driver helpers for very simple display hardware.
+ *
+ * It is based on &drm_simple_display_pipe coupled with a &drm_connector which
+ * has only one fixed &drm_display_mode. The framebuffers are backed by the
+ * cma helper and have support for framebuffer flushing (dirty).
+ * fbdev support is also included.
+ *
+ */
+
+/**
+ * DOC: core
+ *
+ * The driver allocates &tinydrm_device, initializes it using
+ * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init()
+ * and registers the DRM device using devm_tinydrm_register().
+ */
+
+/**
+ * tinydrm_lastclose - DRM lastclose helper
+ * @drm: DRM device
+ *
+ * This function ensures that fbdev is restored when drm_lastclose() is called
+ * on the last drm_release(). Drivers can use this as their
+ * &drm_driver->lastclose callback.
+ */
+void tinydrm_lastclose(struct drm_device *drm)
+{
+	struct tinydrm_device *tdev = drm->dev_private;
+
+	DRM_DEBUG_KMS("\n");
+	drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
+}
+EXPORT_SYMBOL(tinydrm_lastclose);
+
+/**
+ * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from
+ *     another driver's scatter/gather table of pinned pages
+ * @drm: DRM device to import into
+ * @attach: DMA-BUF attachment
+ * @sgt: Scatter/gather table of pinned pages
+ *
+ * This function imports a scatter/gather table exported via DMA-BUF by
+ * another driver using drm_gem_cma_prime_import_sg_table(). It sets the
+ * kernel virtual address on the CMA object. Drivers should use this as their
+ * &drm_driver->gem_prime_import_sg_table callback if they need the virtual
+ * address. tinydrm_gem_cma_free_object() should be used in combination with
+ * this function.
+ *
+ * Returns:
+ * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
+ * error code on failure.
+ */
+struct drm_gem_object *
+tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt)
+{
+	struct drm_gem_cma_object *cma_obj;
+	struct drm_gem_object *obj;
+	void *vaddr;
+
+	vaddr = dma_buf_vmap(attach->dmabuf);
+	if (!vaddr) {
+		DRM_ERROR("Failed to vmap PRIME buffer\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt);
+	if (IS_ERR(obj)) {
+		dma_buf_vunmap(attach->dmabuf, vaddr);
+		return obj;
+	}
+
+	cma_obj = to_drm_gem_cma_obj(obj);
+	cma_obj->vaddr = vaddr;
+
+	return obj;
+}
+EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table);
+
+/**
+ * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
+ *                               object
+ * @gem_obj: GEM object to free
+ *
+ * This function frees the backing memory of the CMA GEM object, cleans up the
+ * GEM object state and frees the memory used to store the object itself using
+ * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel
+ * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers
+ * can use this as their &drm_driver->gem_free_object callback.
+ */
+void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj)
+{
+	if (gem_obj->import_attach) {
+		struct drm_gem_cma_object *cma_obj;
+
+		cma_obj = to_drm_gem_cma_obj(gem_obj);
+		dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr);
+		cma_obj->vaddr = NULL;
+	}
+
+	drm_gem_cma_free_object(gem_obj);
+}
+EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object);
+
+const struct file_operations tinydrm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= drm_compat_ioctl,
+#endif
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+EXPORT_SYMBOL(tinydrm_fops);
+
+static struct drm_framebuffer *
+tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+		  const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct tinydrm_device *tdev = drm->dev_private;
+
+	return drm_fb_cma_create_with_funcs(drm, file_priv, mode_cmd,
+					    tdev->fb_funcs);
+}
+
+static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
+	.fb_create = tinydrm_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+			const struct drm_framebuffer_funcs *fb_funcs,
+			struct drm_driver *driver)
+{
+	struct drm_device *drm;
+
+	mutex_init(&tdev->dirty_lock);
+	tdev->fb_funcs = fb_funcs;
+
+	/*
+	 * We don't embed drm_device, because that prevent us from using
+	 * devm_kzalloc() to allocate tinydrm_device in the driver since
+	 * drm_dev_unref() frees the structure. The devm_ functions provide
+	 * for easy error handling.
+	 */
+	drm = drm_dev_alloc(driver, parent);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+
+	tdev->drm = drm;
+	drm->dev_private = tdev;
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &tinydrm_mode_config_funcs;
+
+	return 0;
+}
+
+static void tinydrm_fini(struct tinydrm_device *tdev)
+{
+	drm_mode_config_cleanup(tdev->drm);
+	mutex_destroy(&tdev->dirty_lock);
+	tdev->drm->dev_private = NULL;
+	drm_dev_unref(tdev->drm);
+}
+
+static void devm_tinydrm_release(void *data)
+{
+	tinydrm_fini(data);
+}
+
+/**
+ * devm_tinydrm_init - Initialize tinydrm device
+ * @parent: Parent device object
+ * @tdev: tinydrm device
+ * @fb_funcs: Framebuffer functions
+ * @driver: DRM driver
+ *
+ * This function initializes @tdev, the underlying DRM device and it's
+ * mode_config. Resources will be automatically freed on driver detach (devres)
+ * using drm_mode_config_cleanup() and drm_dev_unref().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+		      const struct drm_framebuffer_funcs *fb_funcs,
+		      struct drm_driver *driver)
+{
+	int ret;
+
+	ret = tinydrm_init(parent, tdev, fb_funcs, driver);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action(parent, devm_tinydrm_release, tdev);
+	if (ret)
+		tinydrm_fini(tdev);
+
+	return ret;
+}
+EXPORT_SYMBOL(devm_tinydrm_init);
+
+static int tinydrm_register(struct tinydrm_device *tdev)
+{
+	struct drm_device *drm = tdev->drm;
+	int bpp = drm->mode_config.preferred_depth;
+	struct drm_fbdev_cma *fbdev;
+	int ret;
+
+	ret = drm_dev_register(tdev->drm, 0);
+	if (ret)
+		return ret;
+
+	fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
+					      drm->mode_config.num_connector,
+					      tdev->fb_funcs);
+	if (IS_ERR(fbdev))
+		DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
+	else
+		tdev->fbdev_cma = fbdev;
+
+	return 0;
+}
+
+static void tinydrm_unregister(struct tinydrm_device *tdev)
+{
+	struct drm_fbdev_cma *fbdev_cma = tdev->fbdev_cma;
+
+	drm_crtc_force_disable_all(tdev->drm);
+	/* don't restore fbdev in lastclose, keep pipeline disabled */
+	tdev->fbdev_cma = NULL;
+	drm_dev_unregister(tdev->drm);
+	if (fbdev_cma)
+		drm_fbdev_cma_fini(fbdev_cma);
+}
+
+static void devm_tinydrm_register_release(void *data)
+{
+	tinydrm_unregister(data);
+}
+
+/**
+ * devm_tinydrm_register - Register tinydrm device
+ * @tdev: tinydrm device
+ *
+ * This function registers the underlying DRM device and fbdev.
+ * These resources will be automatically unregistered on driver detach (devres)
+ * and the display pipeline will be disabled.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int devm_tinydrm_register(struct tinydrm_device *tdev)
+{
+	struct device *dev = tdev->drm->dev;
+	int ret;
+
+	ret = tinydrm_register(tdev);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action(dev, devm_tinydrm_register_release, tdev);
+	if (ret)
+		tinydrm_unregister(tdev);
+
+	return ret;
+}
+EXPORT_SYMBOL(devm_tinydrm_register);
+
+/**
+ * tinydrm_shutdown - Shutdown tinydrm
+ * @tdev: tinydrm device
+ *
+ * This function makes sure that the display pipeline is disabled.
+ * Used by drivers in their shutdown callback to turn off the display
+ * on machine shutdown and reboot.
+ */
+void tinydrm_shutdown(struct tinydrm_device *tdev)
+{
+	drm_crtc_force_disable_all(tdev->drm);
+}
+EXPORT_SYMBOL(tinydrm_shutdown);
+
+/**
+ * tinydrm_suspend - Suspend tinydrm
+ * @tdev: tinydrm device
+ *
+ * Used in driver PM operations to suspend tinydrm.
+ * Suspends fbdev and DRM.
+ * Resume with tinydrm_resume().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_suspend(struct tinydrm_device *tdev)
+{
+	struct drm_atomic_state *state;
+
+	if (tdev->suspend_state) {
+		DRM_ERROR("Failed to suspend: state already set\n");
+		return -EINVAL;
+	}
+
+	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 1);
+	state = drm_atomic_helper_suspend(tdev->drm);
+	if (IS_ERR(state)) {
+		drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
+		return PTR_ERR(state);
+	}
+
+	tdev->suspend_state = state;
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_suspend);
+
+/**
+ * tinydrm_resume - Resume tinydrm
+ * @tdev: tinydrm device
+ *
+ * Used in driver PM operations to resume tinydrm.
+ * Suspend with tinydrm_suspend().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_resume(struct tinydrm_device *tdev)
+{
+	struct drm_atomic_state *state = tdev->suspend_state;
+	int ret;
+
+	if (!state) {
+		DRM_ERROR("Failed to resume: state is not set\n");
+		return -EINVAL;
+	}
+
+	tdev->suspend_state = NULL;
+
+	ret = drm_atomic_helper_resume(tdev->drm, state);
+	if (ret) {
+		DRM_ERROR("Error resuming state: %d\n", ret);
+		return ret;
+	}
+
+	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_resume);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
new file mode 100644
index 0000000..ec43fb7
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modes.h>
+#include <drm/tinydrm/tinydrm.h>
+
+struct tinydrm_connector {
+	struct drm_connector base;
+	const struct drm_display_mode *mode;
+};
+
+static inline struct tinydrm_connector *
+to_tinydrm_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct tinydrm_connector, base);
+}
+
+static int tinydrm_connector_get_modes(struct drm_connector *connector)
+{
+	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, tconn->mode);
+	if (!mode) {
+		DRM_ERROR("Failed to duplicate mode\n");
+		return 0;
+	}
+
+	if (mode->name[0] == '\0')
+		drm_mode_set_name(mode);
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	if (mode->width_mm) {
+		connector->display_info.width_mm = mode->width_mm;
+		connector->display_info.height_mm = mode->height_mm;
+	}
+
+	return 1;
+}
+
+static const struct drm_connector_helper_funcs tinydrm_connector_hfuncs = {
+	.get_modes = tinydrm_connector_get_modes,
+	.best_encoder = drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status
+tinydrm_connector_detect(struct drm_connector *connector, bool force)
+{
+	if (drm_device_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector->status;
+}
+
+static void tinydrm_connector_destroy(struct drm_connector *connector)
+{
+	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
+
+	drm_connector_cleanup(connector);
+	kfree(tconn);
+}
+
+static const struct drm_connector_funcs tinydrm_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = tinydrm_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = tinydrm_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+struct drm_connector *
+tinydrm_connector_create(struct drm_device *drm,
+			 const struct drm_display_mode *mode,
+			 int connector_type)
+{
+	struct tinydrm_connector *tconn;
+	struct drm_connector *connector;
+	int ret;
+
+	tconn = kzalloc(sizeof(*tconn), GFP_KERNEL);
+	if (!tconn)
+		return ERR_PTR(-ENOMEM);
+
+	tconn->mode = mode;
+	connector = &tconn->base;
+
+	drm_connector_helper_add(connector, &tinydrm_connector_hfuncs);
+	ret = drm_connector_init(drm, connector, &tinydrm_connector_funcs,
+				 connector_type);
+	if (ret) {
+		kfree(tconn);
+		return ERR_PTR(ret);
+	}
+
+	connector->status = connector_status_connected;
+
+	return connector;
+}
+
+/**
+ * tinydrm_display_pipe_update - Display pipe update helper
+ * @pipe: Simple display pipe
+ * @old_state: Old plane state
+ *
+ * This function does a full framebuffer flush if the plane framebuffer
+ * has changed. It also handles vblank events. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->update callback.
+ */
+void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct drm_crtc *crtc = &tdev->pipe.crtc;
+
+	if (fb && (fb != old_state->fb)) {
+		pipe->plane.fb = fb;
+		if (fb->funcs->dirty)
+			fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+	}
+
+	if (crtc->state->event) {
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		crtc->state->event = NULL;
+	}
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_update);
+
+/**
+ * tinydrm_display_pipe_prepare_fb - Display pipe prepare_fb helper
+ * @pipe: Simple display pipe
+ * @plane_state: Plane state
+ *
+ * This function uses drm_fb_cma_prepare_fb() to check if the plane FB has an
+ * dma-buf attached, extracts the exclusive fence and attaches it to plane
+ * state for the atomic helper to wait on. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->prepare_fb callback.
+ */
+int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state)
+{
+	return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_prepare_fb);
+
+static int tinydrm_rotate_mode(struct drm_display_mode *mode,
+			       unsigned int rotation)
+{
+	if (rotation == 0 || rotation == 180) {
+		return 0;
+	} else if (rotation == 90 || rotation == 270) {
+		swap(mode->hdisplay, mode->vdisplay);
+		swap(mode->hsync_start, mode->vsync_start);
+		swap(mode->hsync_end, mode->vsync_end);
+		swap(mode->htotal, mode->vtotal);
+		swap(mode->width_mm, mode->height_mm);
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+
+/**
+ * tinydrm_display_pipe_init - Initialize display pipe
+ * @tdev: tinydrm device
+ * @funcs: Display pipe functions
+ * @connector_type: Connector type
+ * @formats: Array of supported formats (DRM_FORMAT\_\*)
+ * @format_count: Number of elements in @formats
+ * @mode: Supported mode
+ * @rotation: Initial @mode rotation in degrees Counter Clock Wise
+ *
+ * This function sets up a &drm_simple_display_pipe with a &drm_connector that
+ * has one fixed &drm_display_mode which is rotated according to @rotation.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int
+tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			  const struct drm_simple_display_pipe_funcs *funcs,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count,
+			  const struct drm_display_mode *mode,
+			  unsigned int rotation)
+{
+	struct drm_device *drm = tdev->drm;
+	struct drm_display_mode *mode_copy;
+	struct drm_connector *connector;
+	int ret;
+
+	mode_copy = devm_kmalloc(drm->dev, sizeof(*mode_copy), GFP_KERNEL);
+	if (!mode_copy)
+		return -ENOMEM;
+
+	*mode_copy = *mode;
+	ret = tinydrm_rotate_mode(mode_copy, rotation);
+	if (ret) {
+		DRM_ERROR("Illegal rotation value %u\n", rotation);
+		return -EINVAL;
+	}
+
+	drm->mode_config.min_width = mode_copy->hdisplay;
+	drm->mode_config.max_width = mode_copy->hdisplay;
+	drm->mode_config.min_height = mode_copy->vdisplay;
+	drm->mode_config.max_height = mode_copy->vdisplay;
+
+	connector = tinydrm_connector_create(drm, mode_copy, connector_type);
+	if (IS_ERR(connector))
+		return PTR_ERR(connector);
+
+	ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
+					   format_count, connector);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_init);
diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
new file mode 100644
index 0000000..cf9ca20
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_TINYDRM_H
+#define __LINUX_TINYDRM_H
+
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+/**
+ * struct tinydrm_device - tinydrm device
+ * @drm: DRM device
+ * @pipe: Display pipe structure
+ * @dirty_lock: Serializes framebuffer flushing
+ * @fbdev_cma: CMA fbdev structure
+ * @suspend_state: Atomic state when suspended
+ * @fb_funcs: Framebuffer functions used when creating framebuffers
+ */
+struct tinydrm_device {
+	struct drm_device *drm;
+	struct drm_simple_display_pipe pipe;
+	struct mutex dirty_lock;
+	struct drm_fbdev_cma *fbdev_cma;
+	struct drm_atomic_state *suspend_state;
+	const struct drm_framebuffer_funcs *fb_funcs;
+};
+
+static inline struct tinydrm_device *
+pipe_to_tinydrm(struct drm_simple_display_pipe *pipe)
+{
+	return container_of(pipe, struct tinydrm_device, pipe);
+}
+
+/**
+ * TINYDRM_GEM_DRIVER_OPS - default tinydrm gem operations
+ *
+ * This macro provides a shortcut for setting the tinydrm GEM operations in
+ * the &drm_driver structure.
+ */
+#define TINYDRM_GEM_DRIVER_OPS \
+	.gem_free_object	= tinydrm_gem_cma_free_object, \
+	.gem_vm_ops		= &drm_gem_cma_vm_ops, \
+	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd, \
+	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle, \
+	.gem_prime_import	= drm_gem_prime_import, \
+	.gem_prime_export	= drm_gem_prime_export, \
+	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table, \
+	.gem_prime_import_sg_table = tinydrm_gem_cma_prime_import_sg_table, \
+	.gem_prime_vmap		= drm_gem_cma_prime_vmap, \
+	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap, \
+	.gem_prime_mmap		= drm_gem_cma_prime_mmap, \
+	.dumb_create		= drm_gem_cma_dumb_create, \
+	.dumb_map_offset	= drm_gem_cma_dumb_map_offset, \
+	.dumb_destroy		= drm_gem_dumb_destroy, \
+	.fops			= &tinydrm_fops
+
+/**
+ * TINYDRM_MODE - tinydrm display mode
+ * @hd: Horizontal resolution, width
+ * @vd: Vertical resolution, height
+ * @hd_mm: Display width in millimeters
+ * @vd_mm: Display height in millimeters
+ *
+ * This macro creates a &drm_display_mode for use with tinydrm.
+ */
+#define TINYDRM_MODE(hd, vd, hd_mm, vd_mm) \
+	.hdisplay = (hd), \
+	.hsync_start = (hd), \
+	.hsync_end = (hd), \
+	.htotal = (hd), \
+	.vdisplay = (vd), \
+	.vsync_start = (vd), \
+	.vsync_end = (vd), \
+	.vtotal = (vd), \
+	.width_mm = (hd_mm), \
+	.height_mm = (vd_mm), \
+	.type = DRM_MODE_TYPE_DRIVER, \
+	.clock = 1 /* pass validation */
+
+extern const struct file_operations tinydrm_fops;
+void tinydrm_lastclose(struct drm_device *drm);
+void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj);
+struct drm_gem_object *
+tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt);
+int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+		      const struct drm_framebuffer_funcs *fb_funcs,
+		      struct drm_driver *driver);
+int devm_tinydrm_register(struct tinydrm_device *tdev);
+void tinydrm_shutdown(struct tinydrm_device *tdev);
+int tinydrm_suspend(struct tinydrm_device *tdev);
+int tinydrm_resume(struct tinydrm_device *tdev);
+
+void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state);
+int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state);
+int
+tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			  const struct drm_simple_display_pipe_funcs *funcs,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count,
+			  const struct drm_display_mode *mode,
+			  unsigned int rotation);
+
+#endif /* __LINUX_TINYDRM_H */
--
2.10.2

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

* [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

tinydrm provides helpers for very simple displays that can use
CMA backed framebuffers and need flushing on changes.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Rebase on new drm_fbdev_cma_init_with_funcs() signature

 Documentation/gpu/index.rst                 |   1 +
 Documentation/gpu/tinydrm.rst               |  21 ++
 drivers/gpu/drm/Kconfig                     |   2 +
 drivers/gpu/drm/Makefile                    |   1 +
 drivers/gpu/drm/tinydrm/Kconfig             |   8 +
 drivers/gpu/drm/tinydrm/Makefile            |   1 +
 drivers/gpu/drm/tinydrm/core/Makefile       |   3 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 376 ++++++++++++++++++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 234 +++++++++++++++++
 include/drm/tinydrm/tinydrm.h               | 115 +++++++++
 10 files changed, 762 insertions(+)
 create mode 100644 Documentation/gpu/tinydrm.rst
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
 create mode 100644 include/drm/tinydrm/tinydrm.h

diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst
index 367d7c3..f81278a 100644
--- a/Documentation/gpu/index.rst
+++ b/Documentation/gpu/index.rst
@@ -11,6 +11,7 @@ Linux GPU Driver Developer's Guide
    drm-kms-helpers
    drm-uapi
    i915
+   tinydrm
    vga-switcheroo
    vgaarbiter

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
new file mode 100644
index 0000000..ec4a20d
--- /dev/null
+++ b/Documentation/gpu/tinydrm.rst
@@ -0,0 +1,21 @@
+==========================
+drm/tinydrm Driver library
+==========================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :doc: overview
+
+Core functionality
+==================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :doc: core
+
+.. kernel-doc:: include/drm/tinydrm/tinydrm.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+   :export:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
+   :export:
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 90bc65d..88e01e08e 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -263,6 +263,8 @@ source "drivers/gpu/drm/mxsfb/Kconfig"

 source "drivers/gpu/drm/meson/Kconfig"

+source "drivers/gpu/drm/tinydrm/Kconfig"
+
 # Keep legacy drivers last

 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 92de399..3ee9579 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -94,3 +94,4 @@ obj-$(CONFIG_DRM_ARCPGU)+= arc/
 obj-y			+= hisilicon/
 obj-$(CONFIG_DRM_ZTE)	+= zte/
 obj-$(CONFIG_DRM_MXSFB)	+= mxsfb/
+obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
new file mode 100644
index 0000000..ffb873f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -0,0 +1,8 @@
+menuconfig DRM_TINYDRM
+	tristate "Support for simple displays"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	help
+	  Choose this option if you have a tinydrm supported display.
+	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
new file mode 100644
index 0000000..7476ed1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DRM_TINYDRM)		+= core/
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
new file mode 100644
index 0000000..4f14a0f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -0,0 +1,3 @@
+tinydrm-y := tinydrm-core.o tinydrm-pipe.o
+
+obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
new file mode 100644
index 0000000..6a257dd
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/device.h>
+#include <linux/dma-buf.h>
+
+/**
+ * DOC: overview
+ *
+ * This library provides driver helpers for very simple display hardware.
+ *
+ * It is based on &drm_simple_display_pipe coupled with a &drm_connector which
+ * has only one fixed &drm_display_mode. The framebuffers are backed by the
+ * cma helper and have support for framebuffer flushing (dirty).
+ * fbdev support is also included.
+ *
+ */
+
+/**
+ * DOC: core
+ *
+ * The driver allocates &tinydrm_device, initializes it using
+ * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init()
+ * and registers the DRM device using devm_tinydrm_register().
+ */
+
+/**
+ * tinydrm_lastclose - DRM lastclose helper
+ * @drm: DRM device
+ *
+ * This function ensures that fbdev is restored when drm_lastclose() is called
+ * on the last drm_release(). Drivers can use this as their
+ * &drm_driver->lastclose callback.
+ */
+void tinydrm_lastclose(struct drm_device *drm)
+{
+	struct tinydrm_device *tdev = drm->dev_private;
+
+	DRM_DEBUG_KMS("\n");
+	drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
+}
+EXPORT_SYMBOL(tinydrm_lastclose);
+
+/**
+ * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from
+ *     another driver's scatter/gather table of pinned pages
+ * @drm: DRM device to import into
+ * @attach: DMA-BUF attachment
+ * @sgt: Scatter/gather table of pinned pages
+ *
+ * This function imports a scatter/gather table exported via DMA-BUF by
+ * another driver using drm_gem_cma_prime_import_sg_table(). It sets the
+ * kernel virtual address on the CMA object. Drivers should use this as their
+ * &drm_driver->gem_prime_import_sg_table callback if they need the virtual
+ * address. tinydrm_gem_cma_free_object() should be used in combination with
+ * this function.
+ *
+ * Returns:
+ * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
+ * error code on failure.
+ */
+struct drm_gem_object *
+tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt)
+{
+	struct drm_gem_cma_object *cma_obj;
+	struct drm_gem_object *obj;
+	void *vaddr;
+
+	vaddr = dma_buf_vmap(attach->dmabuf);
+	if (!vaddr) {
+		DRM_ERROR("Failed to vmap PRIME buffer\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt);
+	if (IS_ERR(obj)) {
+		dma_buf_vunmap(attach->dmabuf, vaddr);
+		return obj;
+	}
+
+	cma_obj = to_drm_gem_cma_obj(obj);
+	cma_obj->vaddr = vaddr;
+
+	return obj;
+}
+EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table);
+
+/**
+ * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
+ *                               object
+ * @gem_obj: GEM object to free
+ *
+ * This function frees the backing memory of the CMA GEM object, cleans up the
+ * GEM object state and frees the memory used to store the object itself using
+ * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel
+ * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers
+ * can use this as their &drm_driver->gem_free_object callback.
+ */
+void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj)
+{
+	if (gem_obj->import_attach) {
+		struct drm_gem_cma_object *cma_obj;
+
+		cma_obj = to_drm_gem_cma_obj(gem_obj);
+		dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr);
+		cma_obj->vaddr = NULL;
+	}
+
+	drm_gem_cma_free_object(gem_obj);
+}
+EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object);
+
+const struct file_operations tinydrm_fops = {
+	.owner		= THIS_MODULE,
+	.open		= drm_open,
+	.release	= drm_release,
+	.unlocked_ioctl	= drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= drm_compat_ioctl,
+#endif
+	.poll		= drm_poll,
+	.read		= drm_read,
+	.llseek		= no_llseek,
+	.mmap		= drm_gem_cma_mmap,
+};
+EXPORT_SYMBOL(tinydrm_fops);
+
+static struct drm_framebuffer *
+tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
+		  const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct tinydrm_device *tdev = drm->dev_private;
+
+	return drm_fb_cma_create_with_funcs(drm, file_priv, mode_cmd,
+					    tdev->fb_funcs);
+}
+
+static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
+	.fb_create = tinydrm_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+			const struct drm_framebuffer_funcs *fb_funcs,
+			struct drm_driver *driver)
+{
+	struct drm_device *drm;
+
+	mutex_init(&tdev->dirty_lock);
+	tdev->fb_funcs = fb_funcs;
+
+	/*
+	 * We don't embed drm_device, because that prevent us from using
+	 * devm_kzalloc() to allocate tinydrm_device in the driver since
+	 * drm_dev_unref() frees the structure. The devm_ functions provide
+	 * for easy error handling.
+	 */
+	drm = drm_dev_alloc(driver, parent);
+	if (IS_ERR(drm))
+		return PTR_ERR(drm);
+
+	tdev->drm = drm;
+	drm->dev_private = tdev;
+	drm_mode_config_init(drm);
+	drm->mode_config.funcs = &tinydrm_mode_config_funcs;
+
+	return 0;
+}
+
+static void tinydrm_fini(struct tinydrm_device *tdev)
+{
+	drm_mode_config_cleanup(tdev->drm);
+	mutex_destroy(&tdev->dirty_lock);
+	tdev->drm->dev_private = NULL;
+	drm_dev_unref(tdev->drm);
+}
+
+static void devm_tinydrm_release(void *data)
+{
+	tinydrm_fini(data);
+}
+
+/**
+ * devm_tinydrm_init - Initialize tinydrm device
+ * @parent: Parent device object
+ * @tdev: tinydrm device
+ * @fb_funcs: Framebuffer functions
+ * @driver: DRM driver
+ *
+ * This function initializes @tdev, the underlying DRM device and it's
+ * mode_config. Resources will be automatically freed on driver detach (devres)
+ * using drm_mode_config_cleanup() and drm_dev_unref().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+		      const struct drm_framebuffer_funcs *fb_funcs,
+		      struct drm_driver *driver)
+{
+	int ret;
+
+	ret = tinydrm_init(parent, tdev, fb_funcs, driver);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action(parent, devm_tinydrm_release, tdev);
+	if (ret)
+		tinydrm_fini(tdev);
+
+	return ret;
+}
+EXPORT_SYMBOL(devm_tinydrm_init);
+
+static int tinydrm_register(struct tinydrm_device *tdev)
+{
+	struct drm_device *drm = tdev->drm;
+	int bpp = drm->mode_config.preferred_depth;
+	struct drm_fbdev_cma *fbdev;
+	int ret;
+
+	ret = drm_dev_register(tdev->drm, 0);
+	if (ret)
+		return ret;
+
+	fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
+					      drm->mode_config.num_connector,
+					      tdev->fb_funcs);
+	if (IS_ERR(fbdev))
+		DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
+	else
+		tdev->fbdev_cma = fbdev;
+
+	return 0;
+}
+
+static void tinydrm_unregister(struct tinydrm_device *tdev)
+{
+	struct drm_fbdev_cma *fbdev_cma = tdev->fbdev_cma;
+
+	drm_crtc_force_disable_all(tdev->drm);
+	/* don't restore fbdev in lastclose, keep pipeline disabled */
+	tdev->fbdev_cma = NULL;
+	drm_dev_unregister(tdev->drm);
+	if (fbdev_cma)
+		drm_fbdev_cma_fini(fbdev_cma);
+}
+
+static void devm_tinydrm_register_release(void *data)
+{
+	tinydrm_unregister(data);
+}
+
+/**
+ * devm_tinydrm_register - Register tinydrm device
+ * @tdev: tinydrm device
+ *
+ * This function registers the underlying DRM device and fbdev.
+ * These resources will be automatically unregistered on driver detach (devres)
+ * and the display pipeline will be disabled.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int devm_tinydrm_register(struct tinydrm_device *tdev)
+{
+	struct device *dev = tdev->drm->dev;
+	int ret;
+
+	ret = tinydrm_register(tdev);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action(dev, devm_tinydrm_register_release, tdev);
+	if (ret)
+		tinydrm_unregister(tdev);
+
+	return ret;
+}
+EXPORT_SYMBOL(devm_tinydrm_register);
+
+/**
+ * tinydrm_shutdown - Shutdown tinydrm
+ * @tdev: tinydrm device
+ *
+ * This function makes sure that the display pipeline is disabled.
+ * Used by drivers in their shutdown callback to turn off the display
+ * on machine shutdown and reboot.
+ */
+void tinydrm_shutdown(struct tinydrm_device *tdev)
+{
+	drm_crtc_force_disable_all(tdev->drm);
+}
+EXPORT_SYMBOL(tinydrm_shutdown);
+
+/**
+ * tinydrm_suspend - Suspend tinydrm
+ * @tdev: tinydrm device
+ *
+ * Used in driver PM operations to suspend tinydrm.
+ * Suspends fbdev and DRM.
+ * Resume with tinydrm_resume().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_suspend(struct tinydrm_device *tdev)
+{
+	struct drm_atomic_state *state;
+
+	if (tdev->suspend_state) {
+		DRM_ERROR("Failed to suspend: state already set\n");
+		return -EINVAL;
+	}
+
+	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 1);
+	state = drm_atomic_helper_suspend(tdev->drm);
+	if (IS_ERR(state)) {
+		drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
+		return PTR_ERR(state);
+	}
+
+	tdev->suspend_state = state;
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_suspend);
+
+/**
+ * tinydrm_resume - Resume tinydrm
+ * @tdev: tinydrm device
+ *
+ * Used in driver PM operations to resume tinydrm.
+ * Suspend with tinydrm_suspend().
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_resume(struct tinydrm_device *tdev)
+{
+	struct drm_atomic_state *state = tdev->suspend_state;
+	int ret;
+
+	if (!state) {
+		DRM_ERROR("Failed to resume: state is not set\n");
+		return -EINVAL;
+	}
+
+	tdev->suspend_state = NULL;
+
+	ret = drm_atomic_helper_resume(tdev->drm, state);
+	if (ret) {
+		DRM_ERROR("Error resuming state: %d\n", ret);
+		return ret;
+	}
+
+	drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_resume);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
new file mode 100644
index 0000000..ec43fb7
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modes.h>
+#include <drm/tinydrm/tinydrm.h>
+
+struct tinydrm_connector {
+	struct drm_connector base;
+	const struct drm_display_mode *mode;
+};
+
+static inline struct tinydrm_connector *
+to_tinydrm_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct tinydrm_connector, base);
+}
+
+static int tinydrm_connector_get_modes(struct drm_connector *connector)
+{
+	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, tconn->mode);
+	if (!mode) {
+		DRM_ERROR("Failed to duplicate mode\n");
+		return 0;
+	}
+
+	if (mode->name[0] == '\0')
+		drm_mode_set_name(mode);
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	if (mode->width_mm) {
+		connector->display_info.width_mm = mode->width_mm;
+		connector->display_info.height_mm = mode->height_mm;
+	}
+
+	return 1;
+}
+
+static const struct drm_connector_helper_funcs tinydrm_connector_hfuncs = {
+	.get_modes = tinydrm_connector_get_modes,
+	.best_encoder = drm_atomic_helper_best_encoder,
+};
+
+static enum drm_connector_status
+tinydrm_connector_detect(struct drm_connector *connector, bool force)
+{
+	if (drm_device_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector->status;
+}
+
+static void tinydrm_connector_destroy(struct drm_connector *connector)
+{
+	struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
+
+	drm_connector_cleanup(connector);
+	kfree(tconn);
+}
+
+static const struct drm_connector_funcs tinydrm_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = tinydrm_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = tinydrm_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+struct drm_connector *
+tinydrm_connector_create(struct drm_device *drm,
+			 const struct drm_display_mode *mode,
+			 int connector_type)
+{
+	struct tinydrm_connector *tconn;
+	struct drm_connector *connector;
+	int ret;
+
+	tconn = kzalloc(sizeof(*tconn), GFP_KERNEL);
+	if (!tconn)
+		return ERR_PTR(-ENOMEM);
+
+	tconn->mode = mode;
+	connector = &tconn->base;
+
+	drm_connector_helper_add(connector, &tinydrm_connector_hfuncs);
+	ret = drm_connector_init(drm, connector, &tinydrm_connector_funcs,
+				 connector_type);
+	if (ret) {
+		kfree(tconn);
+		return ERR_PTR(ret);
+	}
+
+	connector->status = connector_status_connected;
+
+	return connector;
+}
+
+/**
+ * tinydrm_display_pipe_update - Display pipe update helper
+ * @pipe: Simple display pipe
+ * @old_state: Old plane state
+ *
+ * This function does a full framebuffer flush if the plane framebuffer
+ * has changed. It also handles vblank events. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->update callback.
+ */
+void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct drm_crtc *crtc = &tdev->pipe.crtc;
+
+	if (fb && (fb != old_state->fb)) {
+		pipe->plane.fb = fb;
+		if (fb->funcs->dirty)
+			fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+	}
+
+	if (crtc->state->event) {
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+		crtc->state->event = NULL;
+	}
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_update);
+
+/**
+ * tinydrm_display_pipe_prepare_fb - Display pipe prepare_fb helper
+ * @pipe: Simple display pipe
+ * @plane_state: Plane state
+ *
+ * This function uses drm_fb_cma_prepare_fb() to check if the plane FB has an
+ * dma-buf attached, extracts the exclusive fence and attaches it to plane
+ * state for the atomic helper to wait on. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->prepare_fb callback.
+ */
+int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state)
+{
+	return drm_fb_cma_prepare_fb(&pipe->plane, plane_state);
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_prepare_fb);
+
+static int tinydrm_rotate_mode(struct drm_display_mode *mode,
+			       unsigned int rotation)
+{
+	if (rotation == 0 || rotation == 180) {
+		return 0;
+	} else if (rotation == 90 || rotation == 270) {
+		swap(mode->hdisplay, mode->vdisplay);
+		swap(mode->hsync_start, mode->vsync_start);
+		swap(mode->hsync_end, mode->vsync_end);
+		swap(mode->htotal, mode->vtotal);
+		swap(mode->width_mm, mode->height_mm);
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+
+/**
+ * tinydrm_display_pipe_init - Initialize display pipe
+ * @tdev: tinydrm device
+ * @funcs: Display pipe functions
+ * @connector_type: Connector type
+ * @formats: Array of supported formats (DRM_FORMAT\_\*)
+ * @format_count: Number of elements in @formats
+ * @mode: Supported mode
+ * @rotation: Initial @mode rotation in degrees Counter Clock Wise
+ *
+ * This function sets up a &drm_simple_display_pipe with a &drm_connector that
+ * has one fixed &drm_display_mode which is rotated according to @rotation.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int
+tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			  const struct drm_simple_display_pipe_funcs *funcs,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count,
+			  const struct drm_display_mode *mode,
+			  unsigned int rotation)
+{
+	struct drm_device *drm = tdev->drm;
+	struct drm_display_mode *mode_copy;
+	struct drm_connector *connector;
+	int ret;
+
+	mode_copy = devm_kmalloc(drm->dev, sizeof(*mode_copy), GFP_KERNEL);
+	if (!mode_copy)
+		return -ENOMEM;
+
+	*mode_copy = *mode;
+	ret = tinydrm_rotate_mode(mode_copy, rotation);
+	if (ret) {
+		DRM_ERROR("Illegal rotation value %u\n", rotation);
+		return -EINVAL;
+	}
+
+	drm->mode_config.min_width = mode_copy->hdisplay;
+	drm->mode_config.max_width = mode_copy->hdisplay;
+	drm->mode_config.min_height = mode_copy->vdisplay;
+	drm->mode_config.max_height = mode_copy->vdisplay;
+
+	connector = tinydrm_connector_create(drm, mode_copy, connector_type);
+	if (IS_ERR(connector))
+		return PTR_ERR(connector);
+
+	ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
+					   format_count, connector);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_init);
diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
new file mode 100644
index 0000000..cf9ca20
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_TINYDRM_H
+#define __LINUX_TINYDRM_H
+
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+/**
+ * struct tinydrm_device - tinydrm device
+ * @drm: DRM device
+ * @pipe: Display pipe structure
+ * @dirty_lock: Serializes framebuffer flushing
+ * @fbdev_cma: CMA fbdev structure
+ * @suspend_state: Atomic state when suspended
+ * @fb_funcs: Framebuffer functions used when creating framebuffers
+ */
+struct tinydrm_device {
+	struct drm_device *drm;
+	struct drm_simple_display_pipe pipe;
+	struct mutex dirty_lock;
+	struct drm_fbdev_cma *fbdev_cma;
+	struct drm_atomic_state *suspend_state;
+	const struct drm_framebuffer_funcs *fb_funcs;
+};
+
+static inline struct tinydrm_device *
+pipe_to_tinydrm(struct drm_simple_display_pipe *pipe)
+{
+	return container_of(pipe, struct tinydrm_device, pipe);
+}
+
+/**
+ * TINYDRM_GEM_DRIVER_OPS - default tinydrm gem operations
+ *
+ * This macro provides a shortcut for setting the tinydrm GEM operations in
+ * the &drm_driver structure.
+ */
+#define TINYDRM_GEM_DRIVER_OPS \
+	.gem_free_object	= tinydrm_gem_cma_free_object, \
+	.gem_vm_ops		= &drm_gem_cma_vm_ops, \
+	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd, \
+	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle, \
+	.gem_prime_import	= drm_gem_prime_import, \
+	.gem_prime_export	= drm_gem_prime_export, \
+	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table, \
+	.gem_prime_import_sg_table = tinydrm_gem_cma_prime_import_sg_table, \
+	.gem_prime_vmap		= drm_gem_cma_prime_vmap, \
+	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap, \
+	.gem_prime_mmap		= drm_gem_cma_prime_mmap, \
+	.dumb_create		= drm_gem_cma_dumb_create, \
+	.dumb_map_offset	= drm_gem_cma_dumb_map_offset, \
+	.dumb_destroy		= drm_gem_dumb_destroy, \
+	.fops			= &tinydrm_fops
+
+/**
+ * TINYDRM_MODE - tinydrm display mode
+ * @hd: Horizontal resolution, width
+ * @vd: Vertical resolution, height
+ * @hd_mm: Display width in millimeters
+ * @vd_mm: Display height in millimeters
+ *
+ * This macro creates a &drm_display_mode for use with tinydrm.
+ */
+#define TINYDRM_MODE(hd, vd, hd_mm, vd_mm) \
+	.hdisplay = (hd), \
+	.hsync_start = (hd), \
+	.hsync_end = (hd), \
+	.htotal = (hd), \
+	.vdisplay = (vd), \
+	.vsync_start = (vd), \
+	.vsync_end = (vd), \
+	.vtotal = (vd), \
+	.width_mm = (hd_mm), \
+	.height_mm = (vd_mm), \
+	.type = DRM_MODE_TYPE_DRIVER, \
+	.clock = 1 /* pass validation */
+
+extern const struct file_operations tinydrm_fops;
+void tinydrm_lastclose(struct drm_device *drm);
+void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj);
+struct drm_gem_object *
+tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
+				      struct dma_buf_attachment *attach,
+				      struct sg_table *sgt);
+int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
+		      const struct drm_framebuffer_funcs *fb_funcs,
+		      struct drm_driver *driver);
+int devm_tinydrm_register(struct tinydrm_device *tdev);
+void tinydrm_shutdown(struct tinydrm_device *tdev);
+int tinydrm_suspend(struct tinydrm_device *tdev);
+int tinydrm_resume(struct tinydrm_device *tdev);
+
+void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe,
+				 struct drm_plane_state *old_state);
+int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe,
+				    struct drm_plane_state *plane_state);
+int
+tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			  const struct drm_simple_display_pipe_funcs *funcs,
+			  int connector_type,
+			  const uint32_t *formats,
+			  unsigned int format_count,
+			  const struct drm_display_mode *mode,
+			  unsigned int rotation);
+
+#endif /* __LINUX_TINYDRM_H */
--
2.10.2

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

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

* [PATCH v4 2/7] drm/tinydrm: Add helper functions
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

Add common functionality needed by many tinydrm drivers.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Fix backlight dependency by selecting it
- Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate

 Documentation/gpu/tinydrm.rst                  |   9 +
 drivers/gpu/drm/tinydrm/Kconfig                |   2 +
 drivers/gpu/drm/tinydrm/core/Makefile          |   2 +-
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c | 460 +++++++++++++++++++++++++
 include/drm/tinydrm/tinydrm-helpers.h          | 100 ++++++
 5 files changed, 572 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
 create mode 100644 include/drm/tinydrm/tinydrm-helpers.h

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
index ec4a20d..fb256d2 100644
--- a/Documentation/gpu/tinydrm.rst
+++ b/Documentation/gpu/tinydrm.rst
@@ -19,3 +19,12 @@ Core functionality

 .. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
    :export:
+
+Additional helpers
+==================
+
+.. kernel-doc:: include/drm/tinydrm/tinydrm-helpers.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
+   :export:
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index ffb873f..adf3626 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -3,6 +3,8 @@ menuconfig DRM_TINYDRM
 	depends on DRM
 	select DRM_KMS_HELPER
 	select DRM_KMS_CMA_HELPER
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
index 4f14a0f..fb221e6 100644
--- a/drivers/gpu/drm/tinydrm/core/Makefile
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -1,3 +1,3 @@
-tinydrm-y := tinydrm-core.o tinydrm-pipe.o
+tinydrm-y := tinydrm-core.o tinydrm-pipe.o tinydrm-helpers.o

 obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
new file mode 100644
index 0000000..3ccda6c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/tinydrm/tinydrm.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/backlight.h>
+#include <linux/pm.h>
+#include <linux/spi/spi.h>
+#include <linux/swab.h>
+
+static unsigned int spi_max;
+module_param(spi_max, uint, 0400);
+MODULE_PARM_DESC(spi_max, "Set a lower SPI max transfer size");
+
+/**
+ * tinydrm_merge_clips - Merge clip rectangles
+ * @dst: Destination clip rectangle
+ * @src: Source clip rectangle(s)
+ * @num_clips: Number of @src clip rectangles
+ * @flags: Dirty fb ioctl flags
+ * @max_width: Maximum width of @dst
+ * @max_height: Maximum height of @dst
+ *
+ * This function merges @src clip rectangle(s) into @dst. If @src is NULL,
+ * @max_width and @min_width is used to set a full @dst clip rectangle.
+ *
+ * Returns:
+ * true if it's a full clip, false otherwise
+ */
+bool tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned int num_clips,
+			 unsigned int flags, u32 max_width, u32 max_height)
+{
+	unsigned int i;
+
+	if (!src || !num_clips) {
+		dst->x1 = 0;
+		dst->x2 = max_width;
+		dst->y1 = 0;
+		dst->y2 = max_height;
+		return true;
+	}
+
+	dst->x1 = ~0;
+	dst->y1 = ~0;
+	dst->x2 = 0;
+	dst->y2 = 0;
+
+	for (i = 0; i < num_clips; i++) {
+		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY)
+			i++;
+		dst->x1 = min(dst->x1, src[i].x1);
+		dst->x2 = max(dst->x2, src[i].x2);
+		dst->y1 = min(dst->y1, src[i].y1);
+		dst->y2 = max(dst->y2, src[i].y2);
+	}
+
+	if (dst->x2 > max_width || dst->y2 > max_height ||
+	    dst->x1 >= dst->x2 || dst->y1 >= dst->y2) {
+		DRM_DEBUG_KMS("Illegal clip: x1=%u, x2=%u, y1=%u, y2=%u\n",
+			      dst->x1, dst->x2, dst->y1, dst->y2);
+		dst->x1 = 0;
+		dst->y1 = 0;
+		dst->x2 = max_width;
+		dst->y2 = max_height;
+	}
+
+	return (dst->x2 - dst->x1) == max_width &&
+	       (dst->y2 - dst->y1) == max_height;
+}
+EXPORT_SYMBOL(tinydrm_merge_clips);
+
+/**
+ * tinydrm_memcpy - Copy clip buffer
+ * @dst: Destination buffer
+ * @vaddr: Source buffer
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ */
+void tinydrm_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip)
+{
+	unsigned int cpp = drm_format_plane_cpp(fb->format->format, 0);
+	unsigned int pitch = fb->pitches[0];
+	void *src = vaddr + (clip->y1 * pitch) + (clip->x1 * cpp);
+	size_t len = (clip->x2 - clip->x1) * cpp;
+	unsigned int y;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		memcpy(dst, src, len);
+		src += pitch;
+		dst += len;
+	}
+}
+EXPORT_SYMBOL(tinydrm_memcpy);
+
+/**
+ * tinydrm_swab16 - Swap bytes into clip buffer
+ * @dst: RGB565 destination buffer
+ * @vaddr: RGB565 source buffer
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ */
+void tinydrm_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip)
+{
+	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
+	unsigned int x, y;
+	u16 *src, *buf;
+
+	/*
+	 * The cma memory is write-combined so reads are uncached.
+	 * Speed up by fetching one line at a time.
+	 */
+	buf = kmalloc(len, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		src = vaddr + (y * fb->pitches[0]);
+		src += clip->x1;
+		memcpy(buf, src, len);
+		src = buf;
+		for (x = clip->x1; x < clip->x2; x++)
+			*dst++ = swab16(*src++);
+	}
+
+	kfree(buf);
+}
+EXPORT_SYMBOL(tinydrm_swab16);
+
+/**
+ * tinydrm_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer
+ * @dst: RGB565 destination buffer
+ * @vaddr: XRGB8888 source buffer
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ * @swap: Swap bytes
+ *
+ * Drivers can use this function for RGB565 devices that don't natively
+ * support XRGB8888.
+ */
+void tinydrm_xrgb8888_to_rgb565(u16 *dst, void *vaddr,
+				struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap)
+{
+	size_t len = (clip->x2 - clip->x1) * sizeof(u32);
+	unsigned int x, y;
+	u32 *src, *buf;
+	u16 val16;
+
+	buf = kmalloc(len, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		src = vaddr + (y * fb->pitches[0]);
+		src += clip->x1;
+		memcpy(buf, src, len);
+		src = buf;
+		for (x = clip->x1; x < clip->x2; x++) {
+			val16 = ((*src & 0x00F80000) >> 8) |
+				((*src & 0x0000FC00) >> 5) |
+				((*src & 0x000000F8) >> 3);
+			src++;
+			if (swap)
+				*dst++ = swab16(val16);
+			else
+				*dst++ = val16;
+		}
+	}
+
+	kfree(buf);
+}
+EXPORT_SYMBOL(tinydrm_xrgb8888_to_rgb565);
+
+/**
+ * tinydrm_of_find_backlight - Find backlight device in device-tree
+ * @dev: Device
+ *
+ * This function looks for a DT node pointed to by a property named 'backlight'
+ * and uses of_find_backlight_by_node() to get the backlight device.
+ * Additionally if the brightness property is zero, it is set to
+ * max_brightness.
+ *
+ * Returns:
+ * NULL if there's no backlight property.
+ * Error pointer -EPROBE_DEFER if the DT node is found, but no backlight device
+ * is found.
+ * If the backlight device is found, a pointer to the structure is returned.
+ */
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev)
+{
+	struct backlight_device *backlight;
+	struct device_node *np;
+
+	np = of_parse_phandle(dev->of_node, "backlight", 0);
+	if (!np)
+		return NULL;
+
+	backlight = of_find_backlight_by_node(np);
+	of_node_put(np);
+
+	if (!backlight)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	if (!backlight->props.brightness) {
+		backlight->props.brightness = backlight->props.max_brightness;
+		DRM_DEBUG_KMS("Backlight brightness set to %d\n",
+			      backlight->props.brightness);
+	}
+
+	return backlight;
+}
+EXPORT_SYMBOL(tinydrm_of_find_backlight);
+
+/**
+ * tinydrm_enable_backlight - Enable backlight helper
+ * @backlight: Backlight device
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_enable_backlight(struct backlight_device *backlight)
+{
+	unsigned int old_state;
+	int ret;
+
+	if (!backlight)
+		return 0;
+
+	old_state = backlight->props.state;
+	backlight->props.state &= ~BL_CORE_FBBLANK;
+	DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
+		      backlight->props.state);
+
+	ret = backlight_update_status(backlight);
+	if (ret)
+		DRM_ERROR("Failed to enable backlight %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_enable_backlight);
+
+/**
+ * tinydrm_disable_backlight - Disable backlight helper
+ * @backlight: Backlight device
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_disable_backlight(struct backlight_device *backlight)
+{
+	unsigned int old_state;
+	int ret;
+
+	if (!backlight)
+		return 0;
+
+	old_state = backlight->props.state;
+	backlight->props.state |= BL_CORE_FBBLANK;
+	DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
+		      backlight->props.state);
+	ret = backlight_update_status(backlight);
+	if (ret)
+		DRM_ERROR("Failed to disable backlight %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_disable_backlight);
+
+#if IS_ENABLED(CONFIG_SPI)
+
+/**
+ * tinydrm_spi_max_transfer_size - Determine max SPI transfer size
+ * @spi: SPI device
+ * @max_len: Maximum buffer size needed (optional)
+ *
+ * This function returns the maximum size to use for SPI transfers. It checks
+ * the SPI master, the optional @max_len and the module parameter spi_max and
+ * returns the smallest.
+ *
+ * Returns:
+ * Maximum size for SPI transfers
+ */
+size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len)
+{
+	size_t ret;
+
+	ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len);
+	if (max_len)
+		ret = min(ret, max_len);
+	if (spi_max)
+		ret = min_t(size_t, ret, spi_max);
+	ret &= ~0x3;
+	if (ret < 4)
+		ret = 4;
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_spi_max_transfer_size);
+
+/**
+ * tinydrm_spi_bpw_supported - Check if bits per word is supported
+ * @spi: SPI device
+ * @bpw: Bits per word
+ *
+ * This function checks to see if the SPI master driver supports @bpw.
+ *
+ * Returns:
+ * True if @bpw is supported, false otherwise.
+ */
+bool tinydrm_spi_bpw_supported(struct spi_device *spi, u8 bpw)
+{
+	u32 bpw_mask = spi->master->bits_per_word_mask;
+
+	if (bpw == 8)
+		return true;
+
+	if (!bpw_mask) {
+		dev_warn_once(&spi->dev,
+			      "bits_per_word_mask not set, assume 8-bit only\n");
+		return false;
+	}
+
+	if (bpw_mask & SPI_BPW_MASK(bpw))
+		return true;
+
+	return false;
+}
+EXPORT_SYMBOL(tinydrm_spi_bpw_supported);
+
+static void
+tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr,
+		      const void *buf, int idx, bool tx)
+{
+	u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz;
+	char linebuf[3 * 32];
+
+	hex_dump_to_buffer(buf, tr->len, 16,
+			   DIV_ROUND_UP(tr->bits_per_word, 8),
+			   linebuf, sizeof(linebuf), false);
+
+	printk(KERN_DEBUG
+	       "    tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx,
+	       speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000,
+	       speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len,
+	       tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : "");
+}
+
+/* called through tinydrm_dbg_spi_message() */
+void _tinydrm_dbg_spi_message(struct spi_device *spi, struct spi_message *m)
+{
+	struct spi_transfer *tmp;
+	struct list_head *pos;
+	int i = 0;
+
+	list_for_each(pos, &m->transfers) {
+		tmp = list_entry(pos, struct spi_transfer, transfer_list);
+
+		if (tmp->tx_buf)
+			tinydrm_dbg_spi_print(spi, tmp, tmp->tx_buf, i, true);
+		if (tmp->rx_buf)
+			tinydrm_dbg_spi_print(spi, tmp, tmp->rx_buf, i, false);
+		i++;
+	}
+}
+EXPORT_SYMBOL(_tinydrm_dbg_spi_message);
+
+/**
+ * tinydrm_spi_transfer - SPI transfer helper
+ * @spi: SPI device
+ * @speed_hz: Override speed (optional)
+ * @header: Optional header transfer
+ * @bpw: Bits per word
+ * @buf: Buffer to transfer
+ * @len: Buffer length
+ *
+ * This SPI transfer helper breaks up the transfer of @buf into chunks which
+ * the SPI master driver can handle. If the machine is Little Endian and the
+ * SPI master driver doesn't support 16 bits per word, it swaps the bytes and
+ * does a 8-bit transfer.
+ * If @header is set, it is prepended to each SPI message.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
+			 struct spi_transfer *header, u8 bpw, const void *buf,
+			 size_t len)
+{
+	struct spi_transfer tr = {
+		.bits_per_word = bpw,
+		.speed_hz = speed_hz,
+	};
+	struct spi_message m;
+	u16 *swap_buf = NULL;
+	size_t max_chunk;
+	size_t chunk;
+	int ret = 0;
+
+	if (WARN_ON_ONCE(bpw != 8 && bpw != 16))
+		return -EINVAL;
+
+	max_chunk = tinydrm_spi_max_transfer_size(spi, 0);
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n",
+			 __func__, bpw, max_chunk);
+
+	if (bpw == 16 && !tinydrm_spi_bpw_supported(spi, 16)) {
+		tr.bits_per_word = 8;
+		if (tinydrm_machine_little_endian()) {
+			swap_buf = kmalloc(min(len, max_chunk), GFP_KERNEL);
+			if (!swap_buf)
+				return -ENOMEM;
+		}
+	}
+
+	spi_message_init(&m);
+	if (header)
+		spi_message_add_tail(header, &m);
+	spi_message_add_tail(&tr, &m);
+
+	while (len) {
+		chunk = min(len, max_chunk);
+
+		tr.tx_buf = buf;
+		tr.len = chunk;
+
+		if (swap_buf) {
+			const u16 *buf16 = buf;
+			unsigned int i;
+
+			for (i = 0; i < chunk / 2; i++)
+				swap_buf[i] = swab16(buf16[i]);
+
+			tr.tx_buf = swap_buf;
+		}
+
+		buf += chunk;
+		len -= chunk;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_spi_transfer);
+
+#endif /* CONFIG_SPI */
diff --git a/include/drm/tinydrm/tinydrm-helpers.h b/include/drm/tinydrm/tinydrm-helpers.h
new file mode 100644
index 0000000..78175fe
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm-helpers.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_TINYDRM_HELPERS_H
+#define __LINUX_TINYDRM_HELPERS_H
+
+struct backlight_device;
+struct tinydrm_device;
+struct drm_clip_rect;
+struct spi_transfer;
+struct spi_message;
+struct spi_device;
+struct device;
+
+/**
+ * tinydrm_machine_little_endian - Machine is little endian
+ *
+ * Returns:
+ * true if *defined(__LITTLE_ENDIAN)*, false otherwise
+ */
+static inline bool tinydrm_machine_little_endian(void)
+{
+#if defined(__LITTLE_ENDIAN)
+	return true;
+#else
+	return false;
+#endif
+}
+
+bool tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned int num_clips,
+			 unsigned int flags, u32 max_width, u32 max_height);
+void tinydrm_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip);
+void tinydrm_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip);
+void tinydrm_xrgb8888_to_rgb565(u16 *dst, void *vaddr,
+				struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap);
+
+#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev);
+int tinydrm_enable_backlight(struct backlight_device *backlight);
+int tinydrm_disable_backlight(struct backlight_device *backlight);
+#else
+static inline struct backlight_device *
+tinydrm_of_find_backlight(struct device *dev)
+{
+	return NULL;
+}
+
+static inline int tinydrm_enable_backlight(struct backlight_device *backlight)
+{
+	return 0;
+}
+
+static inline int
+tinydrm_disable_backlight(struct backlight_device *backlight)
+{
+	return 0;
+}
+#endif
+
+size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len);
+bool tinydrm_spi_bpw_supported(struct spi_device *spi, u8 bpw);
+int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
+			 struct spi_transfer *header, u8 bpw, const void *buf,
+			 size_t len);
+void _tinydrm_dbg_spi_message(struct spi_device *spi, struct spi_message *m);
+
+#ifdef DEBUG
+/**
+ * tinydrm_dbg_spi_message - Dump SPI message
+ * @spi: SPI device
+ * @m: SPI message
+ *
+ * Dumps info about the transfers in a SPI message including buffer content.
+ * DEBUG has to be defined for this function to be enabled alongside setting
+ * the DRM_UT_DRIVER bit of &drm_debug.
+ */
+static inline void tinydrm_dbg_spi_message(struct spi_device *spi,
+					   struct spi_message *m)
+{
+	if (drm_debug & DRM_UT_DRIVER)
+		_tinydrm_dbg_spi_message(spi, m);
+}
+#else
+static inline void tinydrm_dbg_spi_message(struct spi_device *spi,
+					   struct spi_message *m)
+{
+}
+#endif /* DEBUG */
+
+#endif /* __LINUX_TINYDRM_HELPERS_H */
--
2.10.2

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

* [PATCH v4 2/7] drm/tinydrm: Add helper functions
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: robh-DgEjT+Ai2ygdnm+yROfE0A,
	thomas.petazzoni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Noralf Trønnes

Add common functionality needed by many tinydrm drivers.

Signed-off-by: Noralf Trønnes <noralf-L59+Z2yzLopAfugRpC6u6w@public.gmane.org>
Acked-by: Daniel Vetter <daniel.vetter-/w4YWyX8dFk@public.gmane.org>
Acked-by: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
---

Changes since version 3:
- Fix backlight dependency by selecting it
- Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate

 Documentation/gpu/tinydrm.rst                  |   9 +
 drivers/gpu/drm/tinydrm/Kconfig                |   2 +
 drivers/gpu/drm/tinydrm/core/Makefile          |   2 +-
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c | 460 +++++++++++++++++++++++++
 include/drm/tinydrm/tinydrm-helpers.h          | 100 ++++++
 5 files changed, 572 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
 create mode 100644 include/drm/tinydrm/tinydrm-helpers.h

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
index ec4a20d..fb256d2 100644
--- a/Documentation/gpu/tinydrm.rst
+++ b/Documentation/gpu/tinydrm.rst
@@ -19,3 +19,12 @@ Core functionality

 .. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
    :export:
+
+Additional helpers
+==================
+
+.. kernel-doc:: include/drm/tinydrm/tinydrm-helpers.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
+   :export:
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index ffb873f..adf3626 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -3,6 +3,8 @@ menuconfig DRM_TINYDRM
 	depends on DRM
 	select DRM_KMS_HELPER
 	select DRM_KMS_CMA_HELPER
+	select BACKLIGHT_LCD_SUPPORT
+	select BACKLIGHT_CLASS_DEVICE
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile
index 4f14a0f..fb221e6 100644
--- a/drivers/gpu/drm/tinydrm/core/Makefile
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -1,3 +1,3 @@
-tinydrm-y := tinydrm-core.o tinydrm-pipe.o
+tinydrm-y := tinydrm-core.o tinydrm-pipe.o tinydrm-helpers.o

 obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
new file mode 100644
index 0000000..3ccda6c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/tinydrm/tinydrm.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/backlight.h>
+#include <linux/pm.h>
+#include <linux/spi/spi.h>
+#include <linux/swab.h>
+
+static unsigned int spi_max;
+module_param(spi_max, uint, 0400);
+MODULE_PARM_DESC(spi_max, "Set a lower SPI max transfer size");
+
+/**
+ * tinydrm_merge_clips - Merge clip rectangles
+ * @dst: Destination clip rectangle
+ * @src: Source clip rectangle(s)
+ * @num_clips: Number of @src clip rectangles
+ * @flags: Dirty fb ioctl flags
+ * @max_width: Maximum width of @dst
+ * @max_height: Maximum height of @dst
+ *
+ * This function merges @src clip rectangle(s) into @dst. If @src is NULL,
+ * @max_width and @min_width is used to set a full @dst clip rectangle.
+ *
+ * Returns:
+ * true if it's a full clip, false otherwise
+ */
+bool tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned int num_clips,
+			 unsigned int flags, u32 max_width, u32 max_height)
+{
+	unsigned int i;
+
+	if (!src || !num_clips) {
+		dst->x1 = 0;
+		dst->x2 = max_width;
+		dst->y1 = 0;
+		dst->y2 = max_height;
+		return true;
+	}
+
+	dst->x1 = ~0;
+	dst->y1 = ~0;
+	dst->x2 = 0;
+	dst->y2 = 0;
+
+	for (i = 0; i < num_clips; i++) {
+		if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY)
+			i++;
+		dst->x1 = min(dst->x1, src[i].x1);
+		dst->x2 = max(dst->x2, src[i].x2);
+		dst->y1 = min(dst->y1, src[i].y1);
+		dst->y2 = max(dst->y2, src[i].y2);
+	}
+
+	if (dst->x2 > max_width || dst->y2 > max_height ||
+	    dst->x1 >= dst->x2 || dst->y1 >= dst->y2) {
+		DRM_DEBUG_KMS("Illegal clip: x1=%u, x2=%u, y1=%u, y2=%u\n",
+			      dst->x1, dst->x2, dst->y1, dst->y2);
+		dst->x1 = 0;
+		dst->y1 = 0;
+		dst->x2 = max_width;
+		dst->y2 = max_height;
+	}
+
+	return (dst->x2 - dst->x1) == max_width &&
+	       (dst->y2 - dst->y1) == max_height;
+}
+EXPORT_SYMBOL(tinydrm_merge_clips);
+
+/**
+ * tinydrm_memcpy - Copy clip buffer
+ * @dst: Destination buffer
+ * @vaddr: Source buffer
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ */
+void tinydrm_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip)
+{
+	unsigned int cpp = drm_format_plane_cpp(fb->format->format, 0);
+	unsigned int pitch = fb->pitches[0];
+	void *src = vaddr + (clip->y1 * pitch) + (clip->x1 * cpp);
+	size_t len = (clip->x2 - clip->x1) * cpp;
+	unsigned int y;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		memcpy(dst, src, len);
+		src += pitch;
+		dst += len;
+	}
+}
+EXPORT_SYMBOL(tinydrm_memcpy);
+
+/**
+ * tinydrm_swab16 - Swap bytes into clip buffer
+ * @dst: RGB565 destination buffer
+ * @vaddr: RGB565 source buffer
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ */
+void tinydrm_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip)
+{
+	size_t len = (clip->x2 - clip->x1) * sizeof(u16);
+	unsigned int x, y;
+	u16 *src, *buf;
+
+	/*
+	 * The cma memory is write-combined so reads are uncached.
+	 * Speed up by fetching one line at a time.
+	 */
+	buf = kmalloc(len, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		src = vaddr + (y * fb->pitches[0]);
+		src += clip->x1;
+		memcpy(buf, src, len);
+		src = buf;
+		for (x = clip->x1; x < clip->x2; x++)
+			*dst++ = swab16(*src++);
+	}
+
+	kfree(buf);
+}
+EXPORT_SYMBOL(tinydrm_swab16);
+
+/**
+ * tinydrm_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer
+ * @dst: RGB565 destination buffer
+ * @vaddr: XRGB8888 source buffer
+ * @fb: DRM framebuffer
+ * @clip: Clip rectangle area to copy
+ * @swap: Swap bytes
+ *
+ * Drivers can use this function for RGB565 devices that don't natively
+ * support XRGB8888.
+ */
+void tinydrm_xrgb8888_to_rgb565(u16 *dst, void *vaddr,
+				struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap)
+{
+	size_t len = (clip->x2 - clip->x1) * sizeof(u32);
+	unsigned int x, y;
+	u32 *src, *buf;
+	u16 val16;
+
+	buf = kmalloc(len, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		src = vaddr + (y * fb->pitches[0]);
+		src += clip->x1;
+		memcpy(buf, src, len);
+		src = buf;
+		for (x = clip->x1; x < clip->x2; x++) {
+			val16 = ((*src & 0x00F80000) >> 8) |
+				((*src & 0x0000FC00) >> 5) |
+				((*src & 0x000000F8) >> 3);
+			src++;
+			if (swap)
+				*dst++ = swab16(val16);
+			else
+				*dst++ = val16;
+		}
+	}
+
+	kfree(buf);
+}
+EXPORT_SYMBOL(tinydrm_xrgb8888_to_rgb565);
+
+/**
+ * tinydrm_of_find_backlight - Find backlight device in device-tree
+ * @dev: Device
+ *
+ * This function looks for a DT node pointed to by a property named 'backlight'
+ * and uses of_find_backlight_by_node() to get the backlight device.
+ * Additionally if the brightness property is zero, it is set to
+ * max_brightness.
+ *
+ * Returns:
+ * NULL if there's no backlight property.
+ * Error pointer -EPROBE_DEFER if the DT node is found, but no backlight device
+ * is found.
+ * If the backlight device is found, a pointer to the structure is returned.
+ */
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev)
+{
+	struct backlight_device *backlight;
+	struct device_node *np;
+
+	np = of_parse_phandle(dev->of_node, "backlight", 0);
+	if (!np)
+		return NULL;
+
+	backlight = of_find_backlight_by_node(np);
+	of_node_put(np);
+
+	if (!backlight)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	if (!backlight->props.brightness) {
+		backlight->props.brightness = backlight->props.max_brightness;
+		DRM_DEBUG_KMS("Backlight brightness set to %d\n",
+			      backlight->props.brightness);
+	}
+
+	return backlight;
+}
+EXPORT_SYMBOL(tinydrm_of_find_backlight);
+
+/**
+ * tinydrm_enable_backlight - Enable backlight helper
+ * @backlight: Backlight device
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_enable_backlight(struct backlight_device *backlight)
+{
+	unsigned int old_state;
+	int ret;
+
+	if (!backlight)
+		return 0;
+
+	old_state = backlight->props.state;
+	backlight->props.state &= ~BL_CORE_FBBLANK;
+	DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
+		      backlight->props.state);
+
+	ret = backlight_update_status(backlight);
+	if (ret)
+		DRM_ERROR("Failed to enable backlight %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_enable_backlight);
+
+/**
+ * tinydrm_disable_backlight - Disable backlight helper
+ * @backlight: Backlight device
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_disable_backlight(struct backlight_device *backlight)
+{
+	unsigned int old_state;
+	int ret;
+
+	if (!backlight)
+		return 0;
+
+	old_state = backlight->props.state;
+	backlight->props.state |= BL_CORE_FBBLANK;
+	DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
+		      backlight->props.state);
+	ret = backlight_update_status(backlight);
+	if (ret)
+		DRM_ERROR("Failed to disable backlight %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_disable_backlight);
+
+#if IS_ENABLED(CONFIG_SPI)
+
+/**
+ * tinydrm_spi_max_transfer_size - Determine max SPI transfer size
+ * @spi: SPI device
+ * @max_len: Maximum buffer size needed (optional)
+ *
+ * This function returns the maximum size to use for SPI transfers. It checks
+ * the SPI master, the optional @max_len and the module parameter spi_max and
+ * returns the smallest.
+ *
+ * Returns:
+ * Maximum size for SPI transfers
+ */
+size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len)
+{
+	size_t ret;
+
+	ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len);
+	if (max_len)
+		ret = min(ret, max_len);
+	if (spi_max)
+		ret = min_t(size_t, ret, spi_max);
+	ret &= ~0x3;
+	if (ret < 4)
+		ret = 4;
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_spi_max_transfer_size);
+
+/**
+ * tinydrm_spi_bpw_supported - Check if bits per word is supported
+ * @spi: SPI device
+ * @bpw: Bits per word
+ *
+ * This function checks to see if the SPI master driver supports @bpw.
+ *
+ * Returns:
+ * True if @bpw is supported, false otherwise.
+ */
+bool tinydrm_spi_bpw_supported(struct spi_device *spi, u8 bpw)
+{
+	u32 bpw_mask = spi->master->bits_per_word_mask;
+
+	if (bpw == 8)
+		return true;
+
+	if (!bpw_mask) {
+		dev_warn_once(&spi->dev,
+			      "bits_per_word_mask not set, assume 8-bit only\n");
+		return false;
+	}
+
+	if (bpw_mask & SPI_BPW_MASK(bpw))
+		return true;
+
+	return false;
+}
+EXPORT_SYMBOL(tinydrm_spi_bpw_supported);
+
+static void
+tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr,
+		      const void *buf, int idx, bool tx)
+{
+	u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz;
+	char linebuf[3 * 32];
+
+	hex_dump_to_buffer(buf, tr->len, 16,
+			   DIV_ROUND_UP(tr->bits_per_word, 8),
+			   linebuf, sizeof(linebuf), false);
+
+	printk(KERN_DEBUG
+	       "    tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx,
+	       speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000,
+	       speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len,
+	       tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : "");
+}
+
+/* called through tinydrm_dbg_spi_message() */
+void _tinydrm_dbg_spi_message(struct spi_device *spi, struct spi_message *m)
+{
+	struct spi_transfer *tmp;
+	struct list_head *pos;
+	int i = 0;
+
+	list_for_each(pos, &m->transfers) {
+		tmp = list_entry(pos, struct spi_transfer, transfer_list);
+
+		if (tmp->tx_buf)
+			tinydrm_dbg_spi_print(spi, tmp, tmp->tx_buf, i, true);
+		if (tmp->rx_buf)
+			tinydrm_dbg_spi_print(spi, tmp, tmp->rx_buf, i, false);
+		i++;
+	}
+}
+EXPORT_SYMBOL(_tinydrm_dbg_spi_message);
+
+/**
+ * tinydrm_spi_transfer - SPI transfer helper
+ * @spi: SPI device
+ * @speed_hz: Override speed (optional)
+ * @header: Optional header transfer
+ * @bpw: Bits per word
+ * @buf: Buffer to transfer
+ * @len: Buffer length
+ *
+ * This SPI transfer helper breaks up the transfer of @buf into chunks which
+ * the SPI master driver can handle. If the machine is Little Endian and the
+ * SPI master driver doesn't support 16 bits per word, it swaps the bytes and
+ * does a 8-bit transfer.
+ * If @header is set, it is prepended to each SPI message.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
+			 struct spi_transfer *header, u8 bpw, const void *buf,
+			 size_t len)
+{
+	struct spi_transfer tr = {
+		.bits_per_word = bpw,
+		.speed_hz = speed_hz,
+	};
+	struct spi_message m;
+	u16 *swap_buf = NULL;
+	size_t max_chunk;
+	size_t chunk;
+	int ret = 0;
+
+	if (WARN_ON_ONCE(bpw != 8 && bpw != 16))
+		return -EINVAL;
+
+	max_chunk = tinydrm_spi_max_transfer_size(spi, 0);
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n",
+			 __func__, bpw, max_chunk);
+
+	if (bpw == 16 && !tinydrm_spi_bpw_supported(spi, 16)) {
+		tr.bits_per_word = 8;
+		if (tinydrm_machine_little_endian()) {
+			swap_buf = kmalloc(min(len, max_chunk), GFP_KERNEL);
+			if (!swap_buf)
+				return -ENOMEM;
+		}
+	}
+
+	spi_message_init(&m);
+	if (header)
+		spi_message_add_tail(header, &m);
+	spi_message_add_tail(&tr, &m);
+
+	while (len) {
+		chunk = min(len, max_chunk);
+
+		tr.tx_buf = buf;
+		tr.len = chunk;
+
+		if (swap_buf) {
+			const u16 *buf16 = buf;
+			unsigned int i;
+
+			for (i = 0; i < chunk / 2; i++)
+				swap_buf[i] = swab16(buf16[i]);
+
+			tr.tx_buf = swap_buf;
+		}
+
+		buf += chunk;
+		len -= chunk;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_spi_transfer);
+
+#endif /* CONFIG_SPI */
diff --git a/include/drm/tinydrm/tinydrm-helpers.h b/include/drm/tinydrm/tinydrm-helpers.h
new file mode 100644
index 0000000..78175fe
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm-helpers.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_TINYDRM_HELPERS_H
+#define __LINUX_TINYDRM_HELPERS_H
+
+struct backlight_device;
+struct tinydrm_device;
+struct drm_clip_rect;
+struct spi_transfer;
+struct spi_message;
+struct spi_device;
+struct device;
+
+/**
+ * tinydrm_machine_little_endian - Machine is little endian
+ *
+ * Returns:
+ * true if *defined(__LITTLE_ENDIAN)*, false otherwise
+ */
+static inline bool tinydrm_machine_little_endian(void)
+{
+#if defined(__LITTLE_ENDIAN)
+	return true;
+#else
+	return false;
+#endif
+}
+
+bool tinydrm_merge_clips(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned int num_clips,
+			 unsigned int flags, u32 max_width, u32 max_height);
+void tinydrm_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip);
+void tinydrm_swab16(u16 *dst, void *vaddr, struct drm_framebuffer *fb,
+		    struct drm_clip_rect *clip);
+void tinydrm_xrgb8888_to_rgb565(u16 *dst, void *vaddr,
+				struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap);
+
+#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev);
+int tinydrm_enable_backlight(struct backlight_device *backlight);
+int tinydrm_disable_backlight(struct backlight_device *backlight);
+#else
+static inline struct backlight_device *
+tinydrm_of_find_backlight(struct device *dev)
+{
+	return NULL;
+}
+
+static inline int tinydrm_enable_backlight(struct backlight_device *backlight)
+{
+	return 0;
+}
+
+static inline int
+tinydrm_disable_backlight(struct backlight_device *backlight)
+{
+	return 0;
+}
+#endif
+
+size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len);
+bool tinydrm_spi_bpw_supported(struct spi_device *spi, u8 bpw);
+int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
+			 struct spi_transfer *header, u8 bpw, const void *buf,
+			 size_t len);
+void _tinydrm_dbg_spi_message(struct spi_device *spi, struct spi_message *m);
+
+#ifdef DEBUG
+/**
+ * tinydrm_dbg_spi_message - Dump SPI message
+ * @spi: SPI device
+ * @m: SPI message
+ *
+ * Dumps info about the transfers in a SPI message including buffer content.
+ * DEBUG has to be defined for this function to be enabled alongside setting
+ * the DRM_UT_DRIVER bit of &drm_debug.
+ */
+static inline void tinydrm_dbg_spi_message(struct spi_device *spi,
+					   struct spi_message *m)
+{
+	if (drm_debug & DRM_UT_DRIVER)
+		_tinydrm_dbg_spi_message(spi, m);
+}
+#else
+static inline void tinydrm_dbg_spi_message(struct spi_device *spi,
+					   struct spi_message *m)
+{
+}
+#endif /* DEBUG */
+
+#endif /* __LINUX_TINYDRM_HELPERS_H */
--
2.10.2

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

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

* [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support
  2017-02-11 18:48 ` Noralf Trønnes
@ 2017-02-11 18:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

Add support for MIPI DBI compatible controllers.
Interface type C option 1 and 3 are supported (SPI).

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate

 Documentation/gpu/tinydrm.rst      |   12 +
 drivers/gpu/drm/tinydrm/Kconfig    |    3 +
 drivers/gpu/drm/tinydrm/Makefile   |    3 +
 drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/mipi-dbi.h     |  107 ++++
 5 files changed, 1130 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/mipi-dbi.h

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
index fb256d2..a913644 100644
--- a/Documentation/gpu/tinydrm.rst
+++ b/Documentation/gpu/tinydrm.rst
@@ -28,3 +28,15 @@ Additional helpers

 .. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    :export:
+
+MIPI DBI Compatible Controllers
+===============================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/mipi-dbi.c
+   :doc: overview
+
+.. kernel-doc:: include/drm/tinydrm/mipi-dbi.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/mipi-dbi.c
+   :export:
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index adf3626..e00bcfc 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -8,3 +8,6 @@ menuconfig DRM_TINYDRM
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
+
+config TINYDRM_MIPI_DBI
+	tristate
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 7476ed1..fe5d4c6 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1 +1,4 @@
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
+
+# Controllers
+obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
new file mode 100644
index 0000000..07d49ba
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
@@ -0,0 +1,1005 @@
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/debugfs.h>
+#include <linux/dma-buf.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
+
+#define DCS_POWER_MODE_DISPLAY			BIT(2)
+#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
+#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
+#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
+#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
+#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
+
+/**
+ * DOC: overview
+ *
+ * This library provides helpers for MIPI Display Bus Interface (DBI)
+ * compatible display controllers.
+ *
+ * Many controllers for tiny lcd displays are MIPI compliant and can use this
+ * library. If a controller uses registers 0x2A and 0x2B to set the area to
+ * update and uses register 0x2C to write to frame memory, it is most likely
+ * MIPI compliant.
+ *
+ * Only MIPI Type 1 displays are supported since a full frame memory is needed.
+ *
+ * There are 3 MIPI DBI implementation types:
+ *
+ * A. Motorola 6800 type parallel bus
+ *
+ * B. Intel 8080 type parallel bus
+ *
+ * C. SPI type with 3 options:
+ *
+ *    1. 9-bit with the Data/Command signal as the ninth bit
+ *    2. Same as above except it's sent as 16 bits
+ *    3. 8-bit with the Data/Command signal as a separate D/CX pin
+ *
+ * Currently mipi_dbi only supports Type C options 1 and 3 with
+ * mipi_dbi_spi_init().
+ */
+
+#define MIPI_DBI_DEBUG_COMMAND(cmd, data, len) \
+({ \
+	if (!len) \
+		DRM_DEBUG_DRIVER("cmd=%02x\n", cmd); \
+	else if (len <= 32) \
+		DRM_DEBUG_DRIVER("cmd=%02x, par=%*ph\n", cmd, len, data); \
+	else \
+		DRM_DEBUG_DRIVER("cmd=%02x, len=%zu\n", cmd, len); \
+})
+
+static const u8 mipi_dbi_dcs_read_commands[] = {
+	MIPI_DCS_GET_DISPLAY_ID,
+	MIPI_DCS_GET_RED_CHANNEL,
+	MIPI_DCS_GET_GREEN_CHANNEL,
+	MIPI_DCS_GET_BLUE_CHANNEL,
+	MIPI_DCS_GET_DISPLAY_STATUS,
+	MIPI_DCS_GET_POWER_MODE,
+	MIPI_DCS_GET_ADDRESS_MODE,
+	MIPI_DCS_GET_PIXEL_FORMAT,
+	MIPI_DCS_GET_DISPLAY_MODE,
+	MIPI_DCS_GET_SIGNAL_MODE,
+	MIPI_DCS_GET_DIAGNOSTIC_RESULT,
+	MIPI_DCS_READ_MEMORY_START,
+	MIPI_DCS_READ_MEMORY_CONTINUE,
+	MIPI_DCS_GET_SCANLINE,
+	MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+	MIPI_DCS_GET_CONTROL_DISPLAY,
+	MIPI_DCS_GET_POWER_SAVE,
+	MIPI_DCS_GET_CABC_MIN_BRIGHTNESS,
+	MIPI_DCS_READ_DDB_START,
+	MIPI_DCS_READ_DDB_CONTINUE,
+	0, /* sentinel */
+};
+
+static bool mipi_dbi_command_is_read(struct mipi_dbi *mipi, u8 cmd)
+{
+	unsigned int i;
+
+	if (!mipi->read_commands)
+		return false;
+
+	for (i = 0; i < 0xff; i++) {
+		if (!mipi->read_commands[i])
+			return false;
+		if (cmd == mipi->read_commands[i])
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * mipi_dbi_command_read - MIPI DCS read command
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @val: Value read
+ *
+ * Send MIPI DCS read command to the controller.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val)
+{
+	if (!mipi->read_commands)
+		return -EACCES;
+
+	if (!mipi_dbi_command_is_read(mipi, cmd))
+		return -EINVAL;
+
+	return mipi_dbi_command_buf(mipi, cmd, val, 1);
+}
+EXPORT_SYMBOL(mipi_dbi_command_read);
+
+/**
+ * mipi_dbi_command_buf - MIPI DCS command with parameter(s) in an array
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @data: Parameter buffer
+ * @len: Buffer length
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len)
+{
+	int ret;
+
+	mutex_lock(&mipi->cmdlock);
+	ret = mipi->command(mipi, cmd, data, len);
+	mutex_unlock(&mipi->cmdlock);
+
+	return ret;
+}
+EXPORT_SYMBOL(mipi_dbi_command_buf);
+
+static int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
+	struct drm_format_name_buf format_name;
+	void *src = cma_obj->vaddr;
+	int ret = 0;
+
+	if (import_attach) {
+		ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
+					       DMA_FROM_DEVICE);
+		if (ret)
+			return ret;
+	}
+
+	switch (fb->format->format) {
+	case DRM_FORMAT_RGB565:
+		if (swap)
+			tinydrm_swab16(dst, src, fb, clip);
+		else
+			tinydrm_memcpy(dst, src, fb, clip);
+		break;
+	case DRM_FORMAT_XRGB8888:
+		tinydrm_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
+		break;
+	default:
+		dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
+			     drm_get_format_name(fb->format->format,
+						 &format_name));
+		return -EINVAL;
+	}
+
+	if (import_attach)
+		ret = dma_buf_end_cpu_access(import_attach->dmabuf,
+					     DMA_FROM_DEVICE);
+	return ret;
+}
+
+static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
+			     struct drm_file *file_priv,
+			     unsigned int flags, unsigned int color,
+			     struct drm_clip_rect *clips,
+			     unsigned int num_clips)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	bool swap = mipi->swap_bytes;
+	struct drm_clip_rect clip;
+	int ret = 0;
+	bool full;
+	void *tr;
+
+	mutex_lock(&tdev->dirty_lock);
+
+	if (!mipi->enabled)
+		goto out_unlock;
+
+	/* fbdev can flush even when we're not interested */
+	if (tdev->pipe.plane.fb != fb)
+		goto out_unlock;
+
+	full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
+				   fb->width, fb->height);
+
+	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
+		  clip.x1, clip.x2, clip.y1, clip.y2);
+
+	if (!mipi->dc || !full || swap ||
+	    fb->format->format == DRM_FORMAT_XRGB8888) {
+		tr = mipi->tx_buf;
+		ret = mipi_dbi_buf_copy(mipi->tx_buf, fb, &clip, swap);
+		if (ret)
+			goto out_unlock;
+	} else {
+		tr = cma_obj->vaddr;
+	}
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS,
+			 (clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF,
+			 (clip.x2 >> 8) & 0xFF, (clip.x2 - 1) & 0xFF);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS,
+			 (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF,
+			 (clip.y2 >> 8) & 0xFF, (clip.y2 - 1) & 0xFF);
+
+	ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, tr,
+				(clip.x2 - clip.x1) * (clip.y2 - clip.y1) * 2);
+
+out_unlock:
+	mutex_unlock(&tdev->dirty_lock);
+
+	if (ret)
+		dev_err_once(fb->dev->dev, "Failed to update display %d\n",
+			     ret);
+
+	return ret;
+}
+
+static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
+	.destroy	= drm_fb_cma_destroy,
+	.create_handle	= drm_fb_cma_create_handle,
+	.dirty		= mipi_dbi_fb_dirty,
+};
+
+/**
+ * mipi_dbi_pipe_enable - MIPI DBI pipe enable helper
+ * @pipe: Display pipe
+ * @crtc_state: CRTC state
+ *
+ * This function enables backlight. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->enable callback.
+ */
+void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			  struct drm_crtc_state *crtc_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	struct drm_framebuffer *fb = pipe->plane.fb;
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi->enabled = true;
+	if (fb)
+		fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+
+	tinydrm_enable_backlight(mipi->backlight);
+}
+EXPORT_SYMBOL(mipi_dbi_pipe_enable);
+
+static void mipi_dbi_blank(struct mipi_dbi *mipi)
+{
+	struct drm_device *drm = mipi->tinydrm.drm;
+	u16 height = drm->mode_config.min_height;
+	u16 width = drm->mode_config.min_width;
+	size_t len = width * height * 2;
+
+	memset(mipi->tx_buf, 0, len);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0,
+			 (width >> 8) & 0xFF, (width - 1) & 0xFF);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0,
+			 (height >> 8) & 0xFF, (height - 1) & 0xFF);
+	mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START,
+			     (u8 *)mipi->tx_buf, len);
+}
+
+/**
+ * mipi_dbi_pipe_disable - MIPI DBI pipe disable helper
+ * @pipe: Display pipe
+ *
+ * This function disables backlight if present or if not the
+ * display memory is blanked. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->disable callback.
+ */
+void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi->enabled = false;
+
+	if (mipi->backlight)
+		tinydrm_disable_backlight(mipi->backlight);
+	else
+		mipi_dbi_blank(mipi);
+}
+EXPORT_SYMBOL(mipi_dbi_pipe_disable);
+
+static const uint32_t mipi_dbi_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_XRGB8888,
+};
+
+/**
+ * mipi_dbi_init - MIPI DBI initialization
+ * @dev: Parent device
+ * @mipi: &mipi_dbi structure to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ * @rotation: Initial rotation in degrees Counter Clock Wise
+ *
+ * This function initializes a &mipi_dbi structure and it's underlying
+ * @tinydrm_device. It also sets up the display pipeline.
+ *
+ * Supported formats: Native RGB565 and emulated XRGB8888.
+ *
+ * Objects created by this function will be automatically freed on driver
+ * detach (devres).
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
+		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		  struct drm_driver *driver,
+		  const struct drm_display_mode *mode, unsigned int rotation)
+{
+	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
+	struct tinydrm_device *tdev = &mipi->tinydrm;
+	int ret;
+
+	if (!mipi->command)
+		return -EINVAL;
+
+	mutex_init(&mipi->cmdlock);
+
+	mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
+	if (!mipi->tx_buf)
+		return -ENOMEM;
+
+	ret = devm_tinydrm_init(dev, tdev, &mipi_dbi_fb_funcs, driver);
+	if (ret)
+		return ret;
+
+	/* TODO: Maybe add DRM_MODE_CONNECTOR_SPI */
+	ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
+					DRM_MODE_CONNECTOR_VIRTUAL,
+					mipi_dbi_formats,
+					ARRAY_SIZE(mipi_dbi_formats), mode,
+					rotation);
+	if (ret)
+		return ret;
+
+	tdev->drm->mode_config.preferred_depth = 16;
+	mipi->rotation = rotation;
+
+	drm_mode_config_reset(tdev->drm);
+
+	DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n",
+		      tdev->drm->mode_config.preferred_depth, rotation);
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_init);
+
+/**
+ * mipi_dbi_hw_reset - Hardware reset of controller
+ * @mipi: MIPI DBI structure
+ *
+ * Reset controller if the &mipi_dbi->reset gpio is set.
+ */
+void mipi_dbi_hw_reset(struct mipi_dbi *mipi)
+{
+	if (!mipi->reset)
+		return;
+
+	gpiod_set_value_cansleep(mipi->reset, 0);
+	msleep(20);
+	gpiod_set_value_cansleep(mipi->reset, 1);
+	msleep(120);
+}
+EXPORT_SYMBOL(mipi_dbi_hw_reset);
+
+/**
+ * mipi_dbi_display_is_on - Check if display is on
+ * @mipi: MIPI DBI structure
+ *
+ * This function checks the Power Mode register (if readable) to see if
+ * display output is turned on. This can be used to see if the bootloader
+ * has already turned on the display avoiding flicker when the pipeline is
+ * enabled.
+ *
+ * Returns:
+ * true if the display can be verified to be on, false otherwise.
+ */
+bool mipi_dbi_display_is_on(struct mipi_dbi *mipi)
+{
+	u8 val;
+
+	if (mipi_dbi_command_read(mipi, MIPI_DCS_GET_POWER_MODE, &val))
+		return false;
+
+	val &= ~DCS_POWER_MODE_RESERVED_MASK;
+
+	if (val != (DCS_POWER_MODE_DISPLAY |
+	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
+		return false;
+
+	DRM_DEBUG_DRIVER("Display is ON\n");
+
+	return true;
+}
+EXPORT_SYMBOL(mipi_dbi_display_is_on);
+
+#if IS_ENABLED(CONFIG_SPI)
+
+/*
+ * Many controllers have a max speed of 10MHz, but can be pushed way beyond
+ * that. Increase reliability by running pixel data at max speed and the rest
+ * at 10MHz, preventing transfer glitches from messing up the init settings.
+ */
+static u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len)
+{
+	if (len > 64)
+		return 0; /* use default */
+
+	return min_t(u32, 10000000, spi->max_speed_hz);
+}
+
+/*
+ * MIPI DBI Type C Option 1
+ *
+ * If the SPI controller doesn't have 9 bits per word support,
+ * use blocks of 9 bytes to send 8x 9-bit words using a 8-bit SPI transfer.
+ * Pad partial blocks with MIPI_DCS_NOP (zero).
+ * This is how the D/C bit (x) is added:
+ *     x7654321
+ *     0x765432
+ *     10x76543
+ *     210x7654
+ *     3210x765
+ *     43210x76
+ *     543210x7
+ *     6543210x
+ *     76543210
+ */
+
+static int mipi_dbi_spi1e_transfer(struct mipi_dbi *mipi, int dc,
+				   const void *buf, size_t len,
+				   unsigned int bpw)
+{
+	bool swap_bytes = (bpw == 16 && tinydrm_machine_little_endian());
+	size_t chunk, max_chunk = mipi->tx_buf9_len;
+	struct spi_device *spi = mipi->spi;
+	struct spi_transfer tr = {
+		.tx_buf = mipi->tx_buf9,
+		.bits_per_word = 8,
+	};
+	struct spi_message m;
+	const u8 *src = buf;
+	int i, ret;
+	u8 *dst;
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
+			 __func__, dc, max_chunk);
+
+	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
+	spi_message_init_with_transfers(&m, &tr, 1);
+
+	if (!dc) {
+		if (WARN_ON_ONCE(len != 1))
+			return -EINVAL;
+
+		/* Command: pad no-op's (zeroes) at beginning of block */
+		dst = mipi->tx_buf9;
+		memset(dst, 0, 9);
+		dst[8] = *src;
+		tr.len = 9;
+
+		tinydrm_dbg_spi_message(spi, &m);
+
+		return spi_sync(spi, &m);
+	}
+
+	/* max with room for adding one bit per byte */
+	max_chunk = max_chunk / 9 * 8;
+	/* but no bigger than len */
+	max_chunk = min(max_chunk, len);
+	/* 8 byte blocks */
+	max_chunk = max_t(size_t, 8, max_chunk & ~0x7);
+
+	while (len) {
+		size_t added = 0;
+
+		chunk = min(len, max_chunk);
+		len -= chunk;
+		dst = mipi->tx_buf9;
+
+		if (chunk < 8) {
+			u8 val, carry = 0;
+
+			/* Data: pad no-op's (zeroes) at end of block */
+			memset(dst, 0, 9);
+
+			if (swap_bytes) {
+				for (i = 1; i < (chunk + 1); i++) {
+					val = src[1];
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+					i++;
+					val = src[0];
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+					src += 2;
+				}
+				*dst++ = carry;
+			} else {
+				for (i = 1; i < (chunk + 1); i++) {
+					val = *src++;
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+				}
+				*dst++ = carry;
+			}
+
+			chunk = 8;
+			added = 1;
+		} else {
+			for (i = 0; i < chunk; i += 8) {
+				if (swap_bytes) {
+					*dst++ =                 BIT(7) | (src[1] >> 1);
+					*dst++ = (src[1] << 7) | BIT(6) | (src[0] >> 2);
+					*dst++ = (src[0] << 6) | BIT(5) | (src[3] >> 3);
+					*dst++ = (src[3] << 5) | BIT(4) | (src[2] >> 4);
+					*dst++ = (src[2] << 4) | BIT(3) | (src[5] >> 5);
+					*dst++ = (src[5] << 3) | BIT(2) | (src[4] >> 6);
+					*dst++ = (src[4] << 2) | BIT(1) | (src[7] >> 7);
+					*dst++ = (src[7] << 1) | BIT(0);
+					*dst++ = src[6];
+				} else {
+					*dst++ =                 BIT(7) | (src[0] >> 1);
+					*dst++ = (src[0] << 7) | BIT(6) | (src[1] >> 2);
+					*dst++ = (src[1] << 6) | BIT(5) | (src[2] >> 3);
+					*dst++ = (src[2] << 5) | BIT(4) | (src[3] >> 4);
+					*dst++ = (src[3] << 4) | BIT(3) | (src[4] >> 5);
+					*dst++ = (src[4] << 3) | BIT(2) | (src[5] >> 6);
+					*dst++ = (src[5] << 2) | BIT(1) | (src[6] >> 7);
+					*dst++ = (src[6] << 1) | BIT(0);
+					*dst++ = src[7];
+				}
+
+				src += 8;
+				added++;
+			}
+		}
+
+		tr.len = chunk + added;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+
+static int mipi_dbi_spi1_transfer(struct mipi_dbi *mipi, int dc,
+				  const void *buf, size_t len,
+				  unsigned int bpw)
+{
+	struct spi_device *spi = mipi->spi;
+	struct spi_transfer tr = {
+		.bits_per_word = 9,
+	};
+	const u16 *src16 = buf;
+	const u8 *src8 = buf;
+	struct spi_message m;
+	size_t max_chunk;
+	u16 *dst16;
+	int ret;
+
+	if (!tinydrm_spi_bpw_supported(spi, 9))
+		return mipi_dbi_spi1e_transfer(mipi, dc, buf, len, bpw);
+
+	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
+	max_chunk = mipi->tx_buf9_len;
+	dst16 = mipi->tx_buf9;
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
+			 __func__, dc, max_chunk);
+
+	max_chunk = min(max_chunk / 2, len);
+
+	spi_message_init_with_transfers(&m, &tr, 1);
+	tr.tx_buf = dst16;
+
+	while (len) {
+		size_t chunk = min(len, max_chunk);
+		unsigned int i;
+
+		if (bpw == 16 && tinydrm_machine_little_endian()) {
+			for (i = 0; i < (chunk * 2); i += 2) {
+				dst16[i]     = *src16 >> 8;
+				dst16[i + 1] = *src16++ & 0xFF;
+				if (dc) {
+					dst16[i]     |= 0x0100;
+					dst16[i + 1] |= 0x0100;
+				}
+			}
+		} else {
+			for (i = 0; i < chunk; i++) {
+				dst16[i] = *src8++;
+				if (dc)
+					dst16[i] |= 0x0100;
+			}
+		}
+
+		tr.len = chunk;
+		len -= chunk;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+
+static int mipi_dbi_typec1_command(struct mipi_dbi *mipi, u8 cmd,
+				   u8 *parameters, size_t num)
+{
+	unsigned int bpw = (cmd == MIPI_DCS_WRITE_MEMORY_START) ? 16 : 8;
+	int ret;
+
+	if (mipi_dbi_command_is_read(mipi, cmd))
+		return -ENOTSUPP;
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, parameters, num);
+
+	ret = mipi_dbi_spi1_transfer(mipi, 0, &cmd, 1, 8);
+	if (ret || !num)
+		return ret;
+
+	return mipi_dbi_spi1_transfer(mipi, 1, parameters, num, bpw);
+}
+
+/* MIPI DBI Type C Option 3 */
+
+static int mipi_dbi_typec3_command_read(struct mipi_dbi *mipi, u8 cmd,
+					u8 *data, size_t len)
+{
+	struct spi_device *spi = mipi->spi;
+	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED,
+			     spi->max_speed_hz / 2);
+	struct spi_transfer tr[2] = {
+		{
+			.speed_hz = speed_hz,
+			.tx_buf = &cmd,
+			.len = 1,
+		}, {
+			.speed_hz = speed_hz,
+			.len = len,
+		},
+	};
+	struct spi_message m;
+	u8 *buf;
+	int ret;
+
+	if (!len)
+		return -EINVAL;
+
+	/*
+	 * Support non-standard 24-bit and 32-bit Nokia read commands which
+	 * start with a dummy clock, so we need to read an extra byte.
+	 */
+	if (cmd == MIPI_DCS_GET_DISPLAY_ID ||
+	    cmd == MIPI_DCS_GET_DISPLAY_STATUS) {
+		if (!(len == 3 || len == 4))
+			return -EINVAL;
+
+		tr[1].len = len + 1;
+	}
+
+	buf = kmalloc(tr[1].len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tr[1].rx_buf = buf;
+	gpiod_set_value_cansleep(mipi->dc, 0);
+
+	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr));
+	ret = spi_sync(spi, &m);
+	if (ret)
+		goto err_free;
+
+	tinydrm_dbg_spi_message(spi, &m);
+
+	if (tr[1].len == len) {
+		memcpy(data, buf, len);
+	} else {
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			data[i] = (buf[i] << 1) | !!(buf[i + 1] & BIT(7));
+	}
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, data, len);
+
+err_free:
+	kfree(buf);
+
+	return ret;
+}
+
+static int mipi_dbi_typec3_command(struct mipi_dbi *mipi, u8 cmd,
+				   u8 *par, size_t num)
+{
+	struct spi_device *spi = mipi->spi;
+	unsigned int bpw = 8;
+	u32 speed_hz;
+	int ret;
+
+	if (mipi_dbi_command_is_read(mipi, cmd))
+		return mipi_dbi_typec3_command_read(mipi, cmd, par, num);
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, par, num);
+
+	gpiod_set_value_cansleep(mipi->dc, 0);
+	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
+	ret = tinydrm_spi_transfer(spi, speed_hz, NULL, 8, &cmd, 1);
+	if (ret || !num)
+		return ret;
+
+	if (cmd == MIPI_DCS_WRITE_MEMORY_START && !mipi->swap_bytes)
+		bpw = 16;
+
+	gpiod_set_value_cansleep(mipi->dc, 1);
+	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
+
+	return tinydrm_spi_transfer(spi, speed_hz, NULL, bpw, par, num);
+}
+
+/**
+ * mipi_dbi_spi_init - Initialize MIPI DBI SPI interfaced controller
+ * @spi: SPI device
+ * @dc: D/C gpio (optional)
+ * @mipi: &mipi_dbi structure to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ * @rotation: Initial rotation in degrees Counter Clock Wise
+ *
+ * This function sets &mipi_dbi->command, enables &mipi->read_commands for the
+ * usual read commands and initializes @mipi using mipi_dbi_init().
+ *
+ * If @dc is set, a Type C Option 3 interface is assumed, if not
+ * Type C Option 1.
+ *
+ * If the SPI master driver doesn't support the necessary bits per word,
+ * the following transformation is used:
+ *
+ * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
+ * - 16-bit: if big endian send as 8-bit, if little endian swap bytes
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
+		      struct gpio_desc *dc,
+		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		      struct drm_driver *driver,
+		      const struct drm_display_mode *mode,
+		      unsigned int rotation)
+{
+	size_t tx_size = tinydrm_spi_max_transfer_size(spi, 0);
+	struct device *dev = &spi->dev;
+	int ret;
+
+	if (tx_size < 16) {
+		DRM_ERROR("SPI transmit buffer too small: %zu\n", tx_size);
+		return -EINVAL;
+	}
+
+	/*
+	 * Even though it's not the SPI device that does DMA (the master does),
+	 * the dma mask is necessary for the dma_alloc_wc() in
+	 * drm_gem_cma_create(). The dma_addr returned will be a physical
+	 * adddress which might be different from the bus address, but this is
+	 * not a problem since the address will not be used.
+	 * The virtual address is used in the transfer and the SPI core
+	 * re-maps it on the SPI master device using the DMA streaming API
+	 * (spi_map_buf()).
+	 */
+	if (!dev->coherent_dma_mask) {
+		ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+		if (ret) {
+			dev_warn(dev, "Failed to set dma mask %d\n", ret);
+			return ret;
+		}
+	}
+
+	mipi->spi = spi;
+	mipi->read_commands = mipi_dbi_dcs_read_commands;
+
+	if (dc) {
+		mipi->command = mipi_dbi_typec3_command;
+		mipi->dc = dc;
+		if (tinydrm_machine_little_endian() &&
+		    !tinydrm_spi_bpw_supported(spi, 16))
+			mipi->swap_bytes = true;
+	} else {
+		mipi->command = mipi_dbi_typec1_command;
+		mipi->tx_buf9_len = tx_size;
+		mipi->tx_buf9 = devm_kmalloc(dev, tx_size, GFP_KERNEL);
+		if (!mipi->tx_buf9)
+			return -ENOMEM;
+	}
+
+	return mipi_dbi_init(dev, mipi, pipe_funcs, driver, mode, rotation);
+}
+EXPORT_SYMBOL(mipi_dbi_spi_init);
+
+#endif /* CONFIG_SPI */
+
+#ifdef CONFIG_DEBUG_FS
+
+static ssize_t mipi_dbi_debugfs_command_write(struct file *file,
+					      const char __user *ubuf,
+					      size_t count, loff_t *ppos)
+{
+	struct seq_file *m = file->private_data;
+	struct mipi_dbi *mipi = m->private;
+	u8 val, cmd, parameters[64];
+	char *buf, *pos, *token;
+	unsigned int i;
+	int ret;
+
+	buf = memdup_user_nul(ubuf, count);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	/* strip trailing whitespace */
+	for (i = count - 1; i > 0; i--)
+		if (isspace(buf[i]))
+			buf[i] = '\0';
+		else
+			break;
+	i = 0;
+	pos = buf;
+	while (pos) {
+		token = strsep(&pos, " ");
+		if (!token) {
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		ret = kstrtou8(token, 16, &val);
+		if (ret < 0)
+			goto err_free;
+
+		if (token == buf)
+			cmd = val;
+		else
+			parameters[i++] = val;
+
+		if (i == 64) {
+			ret = -E2BIG;
+			goto err_free;
+		}
+	}
+
+	ret = mipi_dbi_command_buf(mipi, cmd, parameters, i);
+
+err_free:
+	kfree(buf);
+
+	return ret < 0 ? ret : count;
+}
+
+static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused)
+{
+	struct mipi_dbi *mipi = m->private;
+	u8 cmd, val[4];
+	size_t len, i;
+	int ret;
+
+	for (cmd = 0; cmd < 255; cmd++) {
+		if (!mipi_dbi_command_is_read(mipi, cmd))
+			continue;
+
+		switch (cmd) {
+		case MIPI_DCS_READ_MEMORY_START:
+		case MIPI_DCS_READ_MEMORY_CONTINUE:
+			len = 2;
+			break;
+		case MIPI_DCS_GET_DISPLAY_ID:
+			len = 3;
+			break;
+		case MIPI_DCS_GET_DISPLAY_STATUS:
+			len = 4;
+			break;
+		default:
+			len = 1;
+			break;
+		}
+
+		seq_printf(m, "%02x: ", cmd);
+		ret = mipi_dbi_command_buf(mipi, cmd, val, len);
+		if (ret) {
+			seq_puts(m, "XX\n");
+			continue;
+		}
+
+		for (i = 0; i < len; i++)
+			seq_printf(m, "%02x", val[i]);
+		seq_puts(m, "\n");
+	}
+
+	return 0;
+}
+
+static int mipi_dbi_debugfs_command_open(struct inode *inode,
+					 struct file *file)
+{
+	return single_open(file, mipi_dbi_debugfs_command_show,
+			   inode->i_private);
+}
+
+static const struct file_operations mipi_dbi_debugfs_command_fops = {
+	.owner = THIS_MODULE,
+	.open = mipi_dbi_debugfs_command_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.write = mipi_dbi_debugfs_command_write,
+};
+
+static const struct drm_info_list mipi_dbi_debugfs_list[] = {
+	{ "fb",   drm_fb_cma_debugfs_show, 0 },
+};
+
+/**
+ * mipi_dbi_debugfs_init - Create debugfs entries
+ * @minor: DRM minor
+ *
+ * This function creates a 'command' debugfs file for sending commands to the
+ * controller or getting the read command values.
+ * Drivers can use this as their &drm_driver->debugfs_init callback.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_debugfs_init(struct drm_minor *minor)
+{
+	struct tinydrm_device *tdev = minor->dev->dev_private;
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	umode_t mode = S_IFREG | S_IWUSR;
+
+	if (mipi->read_commands)
+		mode |= S_IRUGO;
+	debugfs_create_file("command", mode, minor->debugfs_root, mipi,
+			    &mipi_dbi_debugfs_command_fops);
+
+	return drm_debugfs_create_files(mipi_dbi_debugfs_list,
+					ARRAY_SIZE(mipi_dbi_debugfs_list),
+					minor->debugfs_root, minor);
+}
+EXPORT_SYMBOL(mipi_dbi_debugfs_init);
+
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h
new file mode 100644
index 0000000..d137b16
--- /dev/null
+++ b/include/drm/tinydrm/mipi-dbi.h
@@ -0,0 +1,107 @@
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_MIPI_DBI_H
+#define __LINUX_MIPI_DBI_H
+
+#include <drm/tinydrm/tinydrm.h>
+
+struct spi_device;
+struct gpio_desc;
+struct regulator;
+
+/**
+ * struct mipi_dbi - MIPI DBI controller
+ * @tinydrm: tinydrm base
+ * @spi: SPI device
+ * @enabled: Pipeline is enabled
+ * @cmdlock: Command lock
+ * @command: Bus specific callback executing commands.
+ * @read_commands: Array of read commands terminated by a zero entry.
+ *                 Reading is disabled if this is NULL.
+ * @dc: Optional D/C gpio.
+ * @tx_buf: Buffer used for transfer (copy clip rect area)
+ * @tx_buf9: Buffer used for Option 1 9-bit conversion
+ * @tx_buf9_len: Size of tx_buf9.
+ * @swap_bytes: Swap bytes in buffer before transfer
+ * @reset: Optional reset gpio
+ * @rotation: initial rotation in degrees Counter Clock Wise
+ * @backlight: backlight device (optional)
+ * @regulator: power regulator (optional)
+ */
+struct mipi_dbi {
+	struct tinydrm_device tinydrm;
+	struct spi_device *spi;
+	bool enabled;
+	struct mutex cmdlock;
+	int (*command)(struct mipi_dbi *mipi, u8 cmd, u8 *param, size_t num);
+	const u8 *read_commands;
+	struct gpio_desc *dc;
+	u16 *tx_buf;
+	void *tx_buf9;
+	size_t tx_buf9_len;
+	bool swap_bytes;
+	struct gpio_desc *reset;
+	unsigned int rotation;
+	struct backlight_device *backlight;
+	struct regulator *regulator;
+};
+
+static inline struct mipi_dbi *
+mipi_dbi_from_tinydrm(struct tinydrm_device *tdev)
+{
+	return container_of(tdev, struct mipi_dbi, tinydrm);
+}
+
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
+		      struct gpio_desc *dc,
+		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		      struct drm_driver *driver,
+		      const struct drm_display_mode *mode,
+		      unsigned int rotation);
+int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
+		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		  struct drm_driver *driver,
+		  const struct drm_display_mode *mode, unsigned int rotation);
+void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			  struct drm_crtc_state *crtc_state);
+void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe);
+void mipi_dbi_hw_reset(struct mipi_dbi *mipi);
+bool mipi_dbi_display_is_on(struct mipi_dbi *mipi);
+
+int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val);
+int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len);
+
+/**
+ * mipi_dbi_command - MIPI DCS command with optional parameter(s)
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @seq...: Optional parameter(s)
+ *
+ * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
+ * get/read.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+#define mipi_dbi_command(mipi, cmd, seq...) \
+({ \
+	u8 d[] = { seq }; \
+	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
+})
+
+#ifdef CONFIG_DEBUG_FS
+int mipi_dbi_debugfs_init(struct drm_minor *minor);
+#else
+#define mipi_dbi_debugfs_init		NULL
+#endif
+
+#endif /* __LINUX_MIPI_DBI_H */
--
2.10.2

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

* [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

Add support for MIPI DBI compatible controllers.
Interface type C option 1 and 3 are supported (SPI).

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Use #if IS_ENABLED(CONFIG_SPI) instead of #ifdef to handle tristate

 Documentation/gpu/tinydrm.rst      |   12 +
 drivers/gpu/drm/tinydrm/Kconfig    |    3 +
 drivers/gpu/drm/tinydrm/Makefile   |    3 +
 drivers/gpu/drm/tinydrm/mipi-dbi.c | 1005 ++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/mipi-dbi.h     |  107 ++++
 5 files changed, 1130 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/mipi-dbi.h

diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst
index fb256d2..a913644 100644
--- a/Documentation/gpu/tinydrm.rst
+++ b/Documentation/gpu/tinydrm.rst
@@ -28,3 +28,15 @@ Additional helpers

 .. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
    :export:
+
+MIPI DBI Compatible Controllers
+===============================
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/mipi-dbi.c
+   :doc: overview
+
+.. kernel-doc:: include/drm/tinydrm/mipi-dbi.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/tinydrm/mipi-dbi.c
+   :export:
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index adf3626..e00bcfc 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -8,3 +8,6 @@ menuconfig DRM_TINYDRM
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
+
+config TINYDRM_MIPI_DBI
+	tristate
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 7476ed1..fe5d4c6 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1 +1,4 @@
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
+
+# Controllers
+obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c
new file mode 100644
index 0000000..07d49ba
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
@@ -0,0 +1,1005 @@
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/debugfs.h>
+#include <linux/dma-buf.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+#define MIPI_DBI_MAX_SPI_READ_SPEED 2000000 /* 2MHz */
+
+#define DCS_POWER_MODE_DISPLAY			BIT(2)
+#define DCS_POWER_MODE_DISPLAY_NORMAL_MODE	BIT(3)
+#define DCS_POWER_MODE_SLEEP_MODE		BIT(4)
+#define DCS_POWER_MODE_PARTIAL_MODE		BIT(5)
+#define DCS_POWER_MODE_IDLE_MODE		BIT(6)
+#define DCS_POWER_MODE_RESERVED_MASK		(BIT(0) | BIT(1) | BIT(7))
+
+/**
+ * DOC: overview
+ *
+ * This library provides helpers for MIPI Display Bus Interface (DBI)
+ * compatible display controllers.
+ *
+ * Many controllers for tiny lcd displays are MIPI compliant and can use this
+ * library. If a controller uses registers 0x2A and 0x2B to set the area to
+ * update and uses register 0x2C to write to frame memory, it is most likely
+ * MIPI compliant.
+ *
+ * Only MIPI Type 1 displays are supported since a full frame memory is needed.
+ *
+ * There are 3 MIPI DBI implementation types:
+ *
+ * A. Motorola 6800 type parallel bus
+ *
+ * B. Intel 8080 type parallel bus
+ *
+ * C. SPI type with 3 options:
+ *
+ *    1. 9-bit with the Data/Command signal as the ninth bit
+ *    2. Same as above except it's sent as 16 bits
+ *    3. 8-bit with the Data/Command signal as a separate D/CX pin
+ *
+ * Currently mipi_dbi only supports Type C options 1 and 3 with
+ * mipi_dbi_spi_init().
+ */
+
+#define MIPI_DBI_DEBUG_COMMAND(cmd, data, len) \
+({ \
+	if (!len) \
+		DRM_DEBUG_DRIVER("cmd=%02x\n", cmd); \
+	else if (len <= 32) \
+		DRM_DEBUG_DRIVER("cmd=%02x, par=%*ph\n", cmd, len, data); \
+	else \
+		DRM_DEBUG_DRIVER("cmd=%02x, len=%zu\n", cmd, len); \
+})
+
+static const u8 mipi_dbi_dcs_read_commands[] = {
+	MIPI_DCS_GET_DISPLAY_ID,
+	MIPI_DCS_GET_RED_CHANNEL,
+	MIPI_DCS_GET_GREEN_CHANNEL,
+	MIPI_DCS_GET_BLUE_CHANNEL,
+	MIPI_DCS_GET_DISPLAY_STATUS,
+	MIPI_DCS_GET_POWER_MODE,
+	MIPI_DCS_GET_ADDRESS_MODE,
+	MIPI_DCS_GET_PIXEL_FORMAT,
+	MIPI_DCS_GET_DISPLAY_MODE,
+	MIPI_DCS_GET_SIGNAL_MODE,
+	MIPI_DCS_GET_DIAGNOSTIC_RESULT,
+	MIPI_DCS_READ_MEMORY_START,
+	MIPI_DCS_READ_MEMORY_CONTINUE,
+	MIPI_DCS_GET_SCANLINE,
+	MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
+	MIPI_DCS_GET_CONTROL_DISPLAY,
+	MIPI_DCS_GET_POWER_SAVE,
+	MIPI_DCS_GET_CABC_MIN_BRIGHTNESS,
+	MIPI_DCS_READ_DDB_START,
+	MIPI_DCS_READ_DDB_CONTINUE,
+	0, /* sentinel */
+};
+
+static bool mipi_dbi_command_is_read(struct mipi_dbi *mipi, u8 cmd)
+{
+	unsigned int i;
+
+	if (!mipi->read_commands)
+		return false;
+
+	for (i = 0; i < 0xff; i++) {
+		if (!mipi->read_commands[i])
+			return false;
+		if (cmd == mipi->read_commands[i])
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * mipi_dbi_command_read - MIPI DCS read command
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @val: Value read
+ *
+ * Send MIPI DCS read command to the controller.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val)
+{
+	if (!mipi->read_commands)
+		return -EACCES;
+
+	if (!mipi_dbi_command_is_read(mipi, cmd))
+		return -EINVAL;
+
+	return mipi_dbi_command_buf(mipi, cmd, val, 1);
+}
+EXPORT_SYMBOL(mipi_dbi_command_read);
+
+/**
+ * mipi_dbi_command_buf - MIPI DCS command with parameter(s) in an array
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @data: Parameter buffer
+ * @len: Buffer length
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len)
+{
+	int ret;
+
+	mutex_lock(&mipi->cmdlock);
+	ret = mipi->command(mipi, cmd, data, len);
+	mutex_unlock(&mipi->cmdlock);
+
+	return ret;
+}
+EXPORT_SYMBOL(mipi_dbi_command_buf);
+
+static int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
+				struct drm_clip_rect *clip, bool swap)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
+	struct drm_format_name_buf format_name;
+	void *src = cma_obj->vaddr;
+	int ret = 0;
+
+	if (import_attach) {
+		ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
+					       DMA_FROM_DEVICE);
+		if (ret)
+			return ret;
+	}
+
+	switch (fb->format->format) {
+	case DRM_FORMAT_RGB565:
+		if (swap)
+			tinydrm_swab16(dst, src, fb, clip);
+		else
+			tinydrm_memcpy(dst, src, fb, clip);
+		break;
+	case DRM_FORMAT_XRGB8888:
+		tinydrm_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
+		break;
+	default:
+		dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
+			     drm_get_format_name(fb->format->format,
+						 &format_name));
+		return -EINVAL;
+	}
+
+	if (import_attach)
+		ret = dma_buf_end_cpu_access(import_attach->dmabuf,
+					     DMA_FROM_DEVICE);
+	return ret;
+}
+
+static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
+			     struct drm_file *file_priv,
+			     unsigned int flags, unsigned int color,
+			     struct drm_clip_rect *clips,
+			     unsigned int num_clips)
+{
+	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	bool swap = mipi->swap_bytes;
+	struct drm_clip_rect clip;
+	int ret = 0;
+	bool full;
+	void *tr;
+
+	mutex_lock(&tdev->dirty_lock);
+
+	if (!mipi->enabled)
+		goto out_unlock;
+
+	/* fbdev can flush even when we're not interested */
+	if (tdev->pipe.plane.fb != fb)
+		goto out_unlock;
+
+	full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
+				   fb->width, fb->height);
+
+	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
+		  clip.x1, clip.x2, clip.y1, clip.y2);
+
+	if (!mipi->dc || !full || swap ||
+	    fb->format->format == DRM_FORMAT_XRGB8888) {
+		tr = mipi->tx_buf;
+		ret = mipi_dbi_buf_copy(mipi->tx_buf, fb, &clip, swap);
+		if (ret)
+			goto out_unlock;
+	} else {
+		tr = cma_obj->vaddr;
+	}
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS,
+			 (clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF,
+			 (clip.x2 >> 8) & 0xFF, (clip.x2 - 1) & 0xFF);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS,
+			 (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF,
+			 (clip.y2 >> 8) & 0xFF, (clip.y2 - 1) & 0xFF);
+
+	ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, tr,
+				(clip.x2 - clip.x1) * (clip.y2 - clip.y1) * 2);
+
+out_unlock:
+	mutex_unlock(&tdev->dirty_lock);
+
+	if (ret)
+		dev_err_once(fb->dev->dev, "Failed to update display %d\n",
+			     ret);
+
+	return ret;
+}
+
+static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
+	.destroy	= drm_fb_cma_destroy,
+	.create_handle	= drm_fb_cma_create_handle,
+	.dirty		= mipi_dbi_fb_dirty,
+};
+
+/**
+ * mipi_dbi_pipe_enable - MIPI DBI pipe enable helper
+ * @pipe: Display pipe
+ * @crtc_state: CRTC state
+ *
+ * This function enables backlight. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->enable callback.
+ */
+void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			  struct drm_crtc_state *crtc_state)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	struct drm_framebuffer *fb = pipe->plane.fb;
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi->enabled = true;
+	if (fb)
+		fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
+
+	tinydrm_enable_backlight(mipi->backlight);
+}
+EXPORT_SYMBOL(mipi_dbi_pipe_enable);
+
+static void mipi_dbi_blank(struct mipi_dbi *mipi)
+{
+	struct drm_device *drm = mipi->tinydrm.drm;
+	u16 height = drm->mode_config.min_height;
+	u16 width = drm->mode_config.min_width;
+	size_t len = width * height * 2;
+
+	memset(mipi->tx_buf, 0, len);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0,
+			 (width >> 8) & 0xFF, (width - 1) & 0xFF);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0,
+			 (height >> 8) & 0xFF, (height - 1) & 0xFF);
+	mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START,
+			     (u8 *)mipi->tx_buf, len);
+}
+
+/**
+ * mipi_dbi_pipe_disable - MIPI DBI pipe disable helper
+ * @pipe: Display pipe
+ *
+ * This function disables backlight if present or if not the
+ * display memory is blanked. Drivers can use this as their
+ * &drm_simple_display_pipe_funcs->disable callback.
+ */
+void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+
+	DRM_DEBUG_KMS("\n");
+
+	mipi->enabled = false;
+
+	if (mipi->backlight)
+		tinydrm_disable_backlight(mipi->backlight);
+	else
+		mipi_dbi_blank(mipi);
+}
+EXPORT_SYMBOL(mipi_dbi_pipe_disable);
+
+static const uint32_t mipi_dbi_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_XRGB8888,
+};
+
+/**
+ * mipi_dbi_init - MIPI DBI initialization
+ * @dev: Parent device
+ * @mipi: &mipi_dbi structure to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ * @rotation: Initial rotation in degrees Counter Clock Wise
+ *
+ * This function initializes a &mipi_dbi structure and it's underlying
+ * @tinydrm_device. It also sets up the display pipeline.
+ *
+ * Supported formats: Native RGB565 and emulated XRGB8888.
+ *
+ * Objects created by this function will be automatically freed on driver
+ * detach (devres).
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
+		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		  struct drm_driver *driver,
+		  const struct drm_display_mode *mode, unsigned int rotation)
+{
+	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
+	struct tinydrm_device *tdev = &mipi->tinydrm;
+	int ret;
+
+	if (!mipi->command)
+		return -EINVAL;
+
+	mutex_init(&mipi->cmdlock);
+
+	mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
+	if (!mipi->tx_buf)
+		return -ENOMEM;
+
+	ret = devm_tinydrm_init(dev, tdev, &mipi_dbi_fb_funcs, driver);
+	if (ret)
+		return ret;
+
+	/* TODO: Maybe add DRM_MODE_CONNECTOR_SPI */
+	ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
+					DRM_MODE_CONNECTOR_VIRTUAL,
+					mipi_dbi_formats,
+					ARRAY_SIZE(mipi_dbi_formats), mode,
+					rotation);
+	if (ret)
+		return ret;
+
+	tdev->drm->mode_config.preferred_depth = 16;
+	mipi->rotation = rotation;
+
+	drm_mode_config_reset(tdev->drm);
+
+	DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n",
+		      tdev->drm->mode_config.preferred_depth, rotation);
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_init);
+
+/**
+ * mipi_dbi_hw_reset - Hardware reset of controller
+ * @mipi: MIPI DBI structure
+ *
+ * Reset controller if the &mipi_dbi->reset gpio is set.
+ */
+void mipi_dbi_hw_reset(struct mipi_dbi *mipi)
+{
+	if (!mipi->reset)
+		return;
+
+	gpiod_set_value_cansleep(mipi->reset, 0);
+	msleep(20);
+	gpiod_set_value_cansleep(mipi->reset, 1);
+	msleep(120);
+}
+EXPORT_SYMBOL(mipi_dbi_hw_reset);
+
+/**
+ * mipi_dbi_display_is_on - Check if display is on
+ * @mipi: MIPI DBI structure
+ *
+ * This function checks the Power Mode register (if readable) to see if
+ * display output is turned on. This can be used to see if the bootloader
+ * has already turned on the display avoiding flicker when the pipeline is
+ * enabled.
+ *
+ * Returns:
+ * true if the display can be verified to be on, false otherwise.
+ */
+bool mipi_dbi_display_is_on(struct mipi_dbi *mipi)
+{
+	u8 val;
+
+	if (mipi_dbi_command_read(mipi, MIPI_DCS_GET_POWER_MODE, &val))
+		return false;
+
+	val &= ~DCS_POWER_MODE_RESERVED_MASK;
+
+	if (val != (DCS_POWER_MODE_DISPLAY |
+	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
+		return false;
+
+	DRM_DEBUG_DRIVER("Display is ON\n");
+
+	return true;
+}
+EXPORT_SYMBOL(mipi_dbi_display_is_on);
+
+#if IS_ENABLED(CONFIG_SPI)
+
+/*
+ * Many controllers have a max speed of 10MHz, but can be pushed way beyond
+ * that. Increase reliability by running pixel data at max speed and the rest
+ * at 10MHz, preventing transfer glitches from messing up the init settings.
+ */
+static u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len)
+{
+	if (len > 64)
+		return 0; /* use default */
+
+	return min_t(u32, 10000000, spi->max_speed_hz);
+}
+
+/*
+ * MIPI DBI Type C Option 1
+ *
+ * If the SPI controller doesn't have 9 bits per word support,
+ * use blocks of 9 bytes to send 8x 9-bit words using a 8-bit SPI transfer.
+ * Pad partial blocks with MIPI_DCS_NOP (zero).
+ * This is how the D/C bit (x) is added:
+ *     x7654321
+ *     0x765432
+ *     10x76543
+ *     210x7654
+ *     3210x765
+ *     43210x76
+ *     543210x7
+ *     6543210x
+ *     76543210
+ */
+
+static int mipi_dbi_spi1e_transfer(struct mipi_dbi *mipi, int dc,
+				   const void *buf, size_t len,
+				   unsigned int bpw)
+{
+	bool swap_bytes = (bpw == 16 && tinydrm_machine_little_endian());
+	size_t chunk, max_chunk = mipi->tx_buf9_len;
+	struct spi_device *spi = mipi->spi;
+	struct spi_transfer tr = {
+		.tx_buf = mipi->tx_buf9,
+		.bits_per_word = 8,
+	};
+	struct spi_message m;
+	const u8 *src = buf;
+	int i, ret;
+	u8 *dst;
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
+			 __func__, dc, max_chunk);
+
+	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
+	spi_message_init_with_transfers(&m, &tr, 1);
+
+	if (!dc) {
+		if (WARN_ON_ONCE(len != 1))
+			return -EINVAL;
+
+		/* Command: pad no-op's (zeroes) at beginning of block */
+		dst = mipi->tx_buf9;
+		memset(dst, 0, 9);
+		dst[8] = *src;
+		tr.len = 9;
+
+		tinydrm_dbg_spi_message(spi, &m);
+
+		return spi_sync(spi, &m);
+	}
+
+	/* max with room for adding one bit per byte */
+	max_chunk = max_chunk / 9 * 8;
+	/* but no bigger than len */
+	max_chunk = min(max_chunk, len);
+	/* 8 byte blocks */
+	max_chunk = max_t(size_t, 8, max_chunk & ~0x7);
+
+	while (len) {
+		size_t added = 0;
+
+		chunk = min(len, max_chunk);
+		len -= chunk;
+		dst = mipi->tx_buf9;
+
+		if (chunk < 8) {
+			u8 val, carry = 0;
+
+			/* Data: pad no-op's (zeroes) at end of block */
+			memset(dst, 0, 9);
+
+			if (swap_bytes) {
+				for (i = 1; i < (chunk + 1); i++) {
+					val = src[1];
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+					i++;
+					val = src[0];
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+					src += 2;
+				}
+				*dst++ = carry;
+			} else {
+				for (i = 1; i < (chunk + 1); i++) {
+					val = *src++;
+					*dst++ = carry | BIT(8 - i) | (val >> i);
+					carry = val << (8 - i);
+				}
+				*dst++ = carry;
+			}
+
+			chunk = 8;
+			added = 1;
+		} else {
+			for (i = 0; i < chunk; i += 8) {
+				if (swap_bytes) {
+					*dst++ =                 BIT(7) | (src[1] >> 1);
+					*dst++ = (src[1] << 7) | BIT(6) | (src[0] >> 2);
+					*dst++ = (src[0] << 6) | BIT(5) | (src[3] >> 3);
+					*dst++ = (src[3] << 5) | BIT(4) | (src[2] >> 4);
+					*dst++ = (src[2] << 4) | BIT(3) | (src[5] >> 5);
+					*dst++ = (src[5] << 3) | BIT(2) | (src[4] >> 6);
+					*dst++ = (src[4] << 2) | BIT(1) | (src[7] >> 7);
+					*dst++ = (src[7] << 1) | BIT(0);
+					*dst++ = src[6];
+				} else {
+					*dst++ =                 BIT(7) | (src[0] >> 1);
+					*dst++ = (src[0] << 7) | BIT(6) | (src[1] >> 2);
+					*dst++ = (src[1] << 6) | BIT(5) | (src[2] >> 3);
+					*dst++ = (src[2] << 5) | BIT(4) | (src[3] >> 4);
+					*dst++ = (src[3] << 4) | BIT(3) | (src[4] >> 5);
+					*dst++ = (src[4] << 3) | BIT(2) | (src[5] >> 6);
+					*dst++ = (src[5] << 2) | BIT(1) | (src[6] >> 7);
+					*dst++ = (src[6] << 1) | BIT(0);
+					*dst++ = src[7];
+				}
+
+				src += 8;
+				added++;
+			}
+		}
+
+		tr.len = chunk + added;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+
+static int mipi_dbi_spi1_transfer(struct mipi_dbi *mipi, int dc,
+				  const void *buf, size_t len,
+				  unsigned int bpw)
+{
+	struct spi_device *spi = mipi->spi;
+	struct spi_transfer tr = {
+		.bits_per_word = 9,
+	};
+	const u16 *src16 = buf;
+	const u8 *src8 = buf;
+	struct spi_message m;
+	size_t max_chunk;
+	u16 *dst16;
+	int ret;
+
+	if (!tinydrm_spi_bpw_supported(spi, 9))
+		return mipi_dbi_spi1e_transfer(mipi, dc, buf, len, bpw);
+
+	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
+	max_chunk = mipi->tx_buf9_len;
+	dst16 = mipi->tx_buf9;
+
+	if (drm_debug & DRM_UT_DRIVER)
+		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
+			 __func__, dc, max_chunk);
+
+	max_chunk = min(max_chunk / 2, len);
+
+	spi_message_init_with_transfers(&m, &tr, 1);
+	tr.tx_buf = dst16;
+
+	while (len) {
+		size_t chunk = min(len, max_chunk);
+		unsigned int i;
+
+		if (bpw == 16 && tinydrm_machine_little_endian()) {
+			for (i = 0; i < (chunk * 2); i += 2) {
+				dst16[i]     = *src16 >> 8;
+				dst16[i + 1] = *src16++ & 0xFF;
+				if (dc) {
+					dst16[i]     |= 0x0100;
+					dst16[i + 1] |= 0x0100;
+				}
+			}
+		} else {
+			for (i = 0; i < chunk; i++) {
+				dst16[i] = *src8++;
+				if (dc)
+					dst16[i] |= 0x0100;
+			}
+		}
+
+		tr.len = chunk;
+		len -= chunk;
+
+		tinydrm_dbg_spi_message(spi, &m);
+		ret = spi_sync(spi, &m);
+		if (ret)
+			return ret;
+	};
+
+	return 0;
+}
+
+static int mipi_dbi_typec1_command(struct mipi_dbi *mipi, u8 cmd,
+				   u8 *parameters, size_t num)
+{
+	unsigned int bpw = (cmd == MIPI_DCS_WRITE_MEMORY_START) ? 16 : 8;
+	int ret;
+
+	if (mipi_dbi_command_is_read(mipi, cmd))
+		return -ENOTSUPP;
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, parameters, num);
+
+	ret = mipi_dbi_spi1_transfer(mipi, 0, &cmd, 1, 8);
+	if (ret || !num)
+		return ret;
+
+	return mipi_dbi_spi1_transfer(mipi, 1, parameters, num, bpw);
+}
+
+/* MIPI DBI Type C Option 3 */
+
+static int mipi_dbi_typec3_command_read(struct mipi_dbi *mipi, u8 cmd,
+					u8 *data, size_t len)
+{
+	struct spi_device *spi = mipi->spi;
+	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED,
+			     spi->max_speed_hz / 2);
+	struct spi_transfer tr[2] = {
+		{
+			.speed_hz = speed_hz,
+			.tx_buf = &cmd,
+			.len = 1,
+		}, {
+			.speed_hz = speed_hz,
+			.len = len,
+		},
+	};
+	struct spi_message m;
+	u8 *buf;
+	int ret;
+
+	if (!len)
+		return -EINVAL;
+
+	/*
+	 * Support non-standard 24-bit and 32-bit Nokia read commands which
+	 * start with a dummy clock, so we need to read an extra byte.
+	 */
+	if (cmd == MIPI_DCS_GET_DISPLAY_ID ||
+	    cmd == MIPI_DCS_GET_DISPLAY_STATUS) {
+		if (!(len == 3 || len == 4))
+			return -EINVAL;
+
+		tr[1].len = len + 1;
+	}
+
+	buf = kmalloc(tr[1].len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	tr[1].rx_buf = buf;
+	gpiod_set_value_cansleep(mipi->dc, 0);
+
+	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr));
+	ret = spi_sync(spi, &m);
+	if (ret)
+		goto err_free;
+
+	tinydrm_dbg_spi_message(spi, &m);
+
+	if (tr[1].len == len) {
+		memcpy(data, buf, len);
+	} else {
+		unsigned int i;
+
+		for (i = 0; i < len; i++)
+			data[i] = (buf[i] << 1) | !!(buf[i + 1] & BIT(7));
+	}
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, data, len);
+
+err_free:
+	kfree(buf);
+
+	return ret;
+}
+
+static int mipi_dbi_typec3_command(struct mipi_dbi *mipi, u8 cmd,
+				   u8 *par, size_t num)
+{
+	struct spi_device *spi = mipi->spi;
+	unsigned int bpw = 8;
+	u32 speed_hz;
+	int ret;
+
+	if (mipi_dbi_command_is_read(mipi, cmd))
+		return mipi_dbi_typec3_command_read(mipi, cmd, par, num);
+
+	MIPI_DBI_DEBUG_COMMAND(cmd, par, num);
+
+	gpiod_set_value_cansleep(mipi->dc, 0);
+	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
+	ret = tinydrm_spi_transfer(spi, speed_hz, NULL, 8, &cmd, 1);
+	if (ret || !num)
+		return ret;
+
+	if (cmd == MIPI_DCS_WRITE_MEMORY_START && !mipi->swap_bytes)
+		bpw = 16;
+
+	gpiod_set_value_cansleep(mipi->dc, 1);
+	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
+
+	return tinydrm_spi_transfer(spi, speed_hz, NULL, bpw, par, num);
+}
+
+/**
+ * mipi_dbi_spi_init - Initialize MIPI DBI SPI interfaced controller
+ * @spi: SPI device
+ * @dc: D/C gpio (optional)
+ * @mipi: &mipi_dbi structure to initialize
+ * @pipe_funcs: Display pipe functions
+ * @driver: DRM driver
+ * @mode: Display mode
+ * @rotation: Initial rotation in degrees Counter Clock Wise
+ *
+ * This function sets &mipi_dbi->command, enables &mipi->read_commands for the
+ * usual read commands and initializes @mipi using mipi_dbi_init().
+ *
+ * If @dc is set, a Type C Option 3 interface is assumed, if not
+ * Type C Option 1.
+ *
+ * If the SPI master driver doesn't support the necessary bits per word,
+ * the following transformation is used:
+ *
+ * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
+ * - 16-bit: if big endian send as 8-bit, if little endian swap bytes
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
+		      struct gpio_desc *dc,
+		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		      struct drm_driver *driver,
+		      const struct drm_display_mode *mode,
+		      unsigned int rotation)
+{
+	size_t tx_size = tinydrm_spi_max_transfer_size(spi, 0);
+	struct device *dev = &spi->dev;
+	int ret;
+
+	if (tx_size < 16) {
+		DRM_ERROR("SPI transmit buffer too small: %zu\n", tx_size);
+		return -EINVAL;
+	}
+
+	/*
+	 * Even though it's not the SPI device that does DMA (the master does),
+	 * the dma mask is necessary for the dma_alloc_wc() in
+	 * drm_gem_cma_create(). The dma_addr returned will be a physical
+	 * adddress which might be different from the bus address, but this is
+	 * not a problem since the address will not be used.
+	 * The virtual address is used in the transfer and the SPI core
+	 * re-maps it on the SPI master device using the DMA streaming API
+	 * (spi_map_buf()).
+	 */
+	if (!dev->coherent_dma_mask) {
+		ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+		if (ret) {
+			dev_warn(dev, "Failed to set dma mask %d\n", ret);
+			return ret;
+		}
+	}
+
+	mipi->spi = spi;
+	mipi->read_commands = mipi_dbi_dcs_read_commands;
+
+	if (dc) {
+		mipi->command = mipi_dbi_typec3_command;
+		mipi->dc = dc;
+		if (tinydrm_machine_little_endian() &&
+		    !tinydrm_spi_bpw_supported(spi, 16))
+			mipi->swap_bytes = true;
+	} else {
+		mipi->command = mipi_dbi_typec1_command;
+		mipi->tx_buf9_len = tx_size;
+		mipi->tx_buf9 = devm_kmalloc(dev, tx_size, GFP_KERNEL);
+		if (!mipi->tx_buf9)
+			return -ENOMEM;
+	}
+
+	return mipi_dbi_init(dev, mipi, pipe_funcs, driver, mode, rotation);
+}
+EXPORT_SYMBOL(mipi_dbi_spi_init);
+
+#endif /* CONFIG_SPI */
+
+#ifdef CONFIG_DEBUG_FS
+
+static ssize_t mipi_dbi_debugfs_command_write(struct file *file,
+					      const char __user *ubuf,
+					      size_t count, loff_t *ppos)
+{
+	struct seq_file *m = file->private_data;
+	struct mipi_dbi *mipi = m->private;
+	u8 val, cmd, parameters[64];
+	char *buf, *pos, *token;
+	unsigned int i;
+	int ret;
+
+	buf = memdup_user_nul(ubuf, count);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	/* strip trailing whitespace */
+	for (i = count - 1; i > 0; i--)
+		if (isspace(buf[i]))
+			buf[i] = '\0';
+		else
+			break;
+	i = 0;
+	pos = buf;
+	while (pos) {
+		token = strsep(&pos, " ");
+		if (!token) {
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		ret = kstrtou8(token, 16, &val);
+		if (ret < 0)
+			goto err_free;
+
+		if (token == buf)
+			cmd = val;
+		else
+			parameters[i++] = val;
+
+		if (i == 64) {
+			ret = -E2BIG;
+			goto err_free;
+		}
+	}
+
+	ret = mipi_dbi_command_buf(mipi, cmd, parameters, i);
+
+err_free:
+	kfree(buf);
+
+	return ret < 0 ? ret : count;
+}
+
+static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused)
+{
+	struct mipi_dbi *mipi = m->private;
+	u8 cmd, val[4];
+	size_t len, i;
+	int ret;
+
+	for (cmd = 0; cmd < 255; cmd++) {
+		if (!mipi_dbi_command_is_read(mipi, cmd))
+			continue;
+
+		switch (cmd) {
+		case MIPI_DCS_READ_MEMORY_START:
+		case MIPI_DCS_READ_MEMORY_CONTINUE:
+			len = 2;
+			break;
+		case MIPI_DCS_GET_DISPLAY_ID:
+			len = 3;
+			break;
+		case MIPI_DCS_GET_DISPLAY_STATUS:
+			len = 4;
+			break;
+		default:
+			len = 1;
+			break;
+		}
+
+		seq_printf(m, "%02x: ", cmd);
+		ret = mipi_dbi_command_buf(mipi, cmd, val, len);
+		if (ret) {
+			seq_puts(m, "XX\n");
+			continue;
+		}
+
+		for (i = 0; i < len; i++)
+			seq_printf(m, "%02x", val[i]);
+		seq_puts(m, "\n");
+	}
+
+	return 0;
+}
+
+static int mipi_dbi_debugfs_command_open(struct inode *inode,
+					 struct file *file)
+{
+	return single_open(file, mipi_dbi_debugfs_command_show,
+			   inode->i_private);
+}
+
+static const struct file_operations mipi_dbi_debugfs_command_fops = {
+	.owner = THIS_MODULE,
+	.open = mipi_dbi_debugfs_command_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+	.write = mipi_dbi_debugfs_command_write,
+};
+
+static const struct drm_info_list mipi_dbi_debugfs_list[] = {
+	{ "fb",   drm_fb_cma_debugfs_show, 0 },
+};
+
+/**
+ * mipi_dbi_debugfs_init - Create debugfs entries
+ * @minor: DRM minor
+ *
+ * This function creates a 'command' debugfs file for sending commands to the
+ * controller or getting the read command values.
+ * Drivers can use this as their &drm_driver->debugfs_init callback.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int mipi_dbi_debugfs_init(struct drm_minor *minor)
+{
+	struct tinydrm_device *tdev = minor->dev->dev_private;
+	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
+	umode_t mode = S_IFREG | S_IWUSR;
+
+	if (mipi->read_commands)
+		mode |= S_IRUGO;
+	debugfs_create_file("command", mode, minor->debugfs_root, mipi,
+			    &mipi_dbi_debugfs_command_fops);
+
+	return drm_debugfs_create_files(mipi_dbi_debugfs_list,
+					ARRAY_SIZE(mipi_dbi_debugfs_list),
+					minor->debugfs_root, minor);
+}
+EXPORT_SYMBOL(mipi_dbi_debugfs_init);
+
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h
new file mode 100644
index 0000000..d137b16
--- /dev/null
+++ b/include/drm/tinydrm/mipi-dbi.h
@@ -0,0 +1,107 @@
+/*
+ * MIPI Display Bus Interface (DBI) LCD controller support
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_MIPI_DBI_H
+#define __LINUX_MIPI_DBI_H
+
+#include <drm/tinydrm/tinydrm.h>
+
+struct spi_device;
+struct gpio_desc;
+struct regulator;
+
+/**
+ * struct mipi_dbi - MIPI DBI controller
+ * @tinydrm: tinydrm base
+ * @spi: SPI device
+ * @enabled: Pipeline is enabled
+ * @cmdlock: Command lock
+ * @command: Bus specific callback executing commands.
+ * @read_commands: Array of read commands terminated by a zero entry.
+ *                 Reading is disabled if this is NULL.
+ * @dc: Optional D/C gpio.
+ * @tx_buf: Buffer used for transfer (copy clip rect area)
+ * @tx_buf9: Buffer used for Option 1 9-bit conversion
+ * @tx_buf9_len: Size of tx_buf9.
+ * @swap_bytes: Swap bytes in buffer before transfer
+ * @reset: Optional reset gpio
+ * @rotation: initial rotation in degrees Counter Clock Wise
+ * @backlight: backlight device (optional)
+ * @regulator: power regulator (optional)
+ */
+struct mipi_dbi {
+	struct tinydrm_device tinydrm;
+	struct spi_device *spi;
+	bool enabled;
+	struct mutex cmdlock;
+	int (*command)(struct mipi_dbi *mipi, u8 cmd, u8 *param, size_t num);
+	const u8 *read_commands;
+	struct gpio_desc *dc;
+	u16 *tx_buf;
+	void *tx_buf9;
+	size_t tx_buf9_len;
+	bool swap_bytes;
+	struct gpio_desc *reset;
+	unsigned int rotation;
+	struct backlight_device *backlight;
+	struct regulator *regulator;
+};
+
+static inline struct mipi_dbi *
+mipi_dbi_from_tinydrm(struct tinydrm_device *tdev)
+{
+	return container_of(tdev, struct mipi_dbi, tinydrm);
+}
+
+int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
+		      struct gpio_desc *dc,
+		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		      struct drm_driver *driver,
+		      const struct drm_display_mode *mode,
+		      unsigned int rotation);
+int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
+		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
+		  struct drm_driver *driver,
+		  const struct drm_display_mode *mode, unsigned int rotation);
+void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			  struct drm_crtc_state *crtc_state);
+void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe);
+void mipi_dbi_hw_reset(struct mipi_dbi *mipi);
+bool mipi_dbi_display_is_on(struct mipi_dbi *mipi);
+
+int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val);
+int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len);
+
+/**
+ * mipi_dbi_command - MIPI DCS command with optional parameter(s)
+ * @mipi: MIPI structure
+ * @cmd: Command
+ * @seq...: Optional parameter(s)
+ *
+ * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
+ * get/read.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+#define mipi_dbi_command(mipi, cmd, seq...) \
+({ \
+	u8 d[] = { seq }; \
+	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
+})
+
+#ifdef CONFIG_DEBUG_FS
+int mipi_dbi_debugfs_init(struct drm_minor *minor);
+#else
+#define mipi_dbi_debugfs_init		NULL
+#endif
+
+#endif /* __LINUX_MIPI_DBI_H */
--
2.10.2

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

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

* [PATCH v4 4/7] of: Add vendor prefix for Multi-Inno
  2017-02-11 18:48 ` Noralf Trønnes
@ 2017-02-11 18:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

Multi-Inno Technology Co.,Ltd is a Hong Kong based company offering
LCD, LCD module products and complete panel solutions.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 1e4e84e..a0c3d2b 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -194,6 +194,7 @@ mpl	MPL AG
 mqmaker	mqmaker Inc.
 msi	Micro-Star International Co. Ltd.
 mti	Imagination Technologies Ltd. (formerly MIPS Technologies Inc.)
+multi-inno	Multi-Inno Technology Co.,Ltd
 mundoreader	Mundo Reader S.L.
 murata	Murata Manufacturing Co., Ltd.
 mxicy	Macronix International Co., Ltd.
-- 
2.10.2

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

* [PATCH v4 4/7] of: Add vendor prefix for Multi-Inno
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

Multi-Inno Technology Co.,Ltd is a Hong Kong based company offering
LCD, LCD module products and complete panel solutions.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 1e4e84e..a0c3d2b 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -194,6 +194,7 @@ mpl	MPL AG
 mqmaker	mqmaker Inc.
 msi	Micro-Star International Co. Ltd.
 mti	Imagination Technologies Ltd. (formerly MIPS Technologies Inc.)
+multi-inno	Multi-Inno Technology Co.,Ltd
 mundoreader	Mundo Reader S.L.
 murata	Murata Manufacturing Co., Ltd.
 mxicy	Macronix International Co., Ltd.
-- 
2.10.2

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

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

* [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
  2017-02-11 18:48 ` Noralf Trønnes
@ 2017-02-11 18:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

Display panels can be oriented many ways, especially in the embedded
world. The rotation property is a way to describe this orientation.
The counter clockwise direction is chosen because that's what fbdev
and drm use.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Changed display/display.txt -> display/panel/panel.txt

 Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/panel.txt

diff --git a/Documentation/devicetree/bindings/display/panel/panel.txt b/Documentation/devicetree/bindings/display/panel/panel.txt
new file mode 100644
index 0000000..e2e6867
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/panel.txt
@@ -0,0 +1,4 @@
+Common display properties
+-------------------------
+
+- rotation:	Display rotation in degrees counter clockwise (0,90,180,270)
--
2.10.2

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

* [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

Display panels can be oriented many ways, especially in the embedded
world. The rotation property is a way to describe this orientation.
The counter clockwise direction is chosen because that's what fbdev
and drm use.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Thierry Reding <treding@nvidia.com>
---

Changes since version 3:
- Changed display/display.txt -> display/panel/panel.txt

 Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/panel.txt

diff --git a/Documentation/devicetree/bindings/display/panel/panel.txt b/Documentation/devicetree/bindings/display/panel/panel.txt
new file mode 100644
index 0000000..e2e6867
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/panel.txt
@@ -0,0 +1,4 @@
+Common display properties
+-------------------------
+
+- rotation:	Display rotation in degrees counter clockwise (0,90,180,270)
--
2.10.2

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

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

* [PATCH v4 6/7] dt-bindings: Add Multi-Inno MI0283QT binding
  2017-02-11 18:48 ` Noralf Trønnes
@ 2017-02-11 18:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

Add device-tree binding documentation for the MI0283QT display panel.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../bindings/display/multi-inno,mi0283qt.txt       | 27 ++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt

diff --git a/Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt b/Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
new file mode 100644
index 0000000..eed48c3
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
@@ -0,0 +1,27 @@
+Multi-Inno MI0283QT display panel
+
+Required properties:
+- compatible:	"multi-inno,mi0283qt".
+
+The node for this driver must be a child node of a SPI controller, hence
+all mandatory properties described in ../spi/spi-bus.txt must be specified.
+
+Optional properties:
+- dc-gpios:	D/C pin. The presence/absence of this GPIO determines
+		the panel interface mode (IM[3:0] pins):
+		- present: IM=x110 4-wire 8-bit data serial interface
+		- absent:  IM=x101 3-wire 9-bit data serial interface
+- reset-gpios:	Reset pin
+- power-supply:	A regulator node for the supply voltage.
+- backlight:	phandle of the backlight device attached to the panel
+- rotation:	panel rotation in degrees counter clockwise (0,90,180,270)
+
+Example:
+	mi0283qt@0{
+		compatible = "multi-inno,mi0283qt";
+		reg = <0>;
+		spi-max-frequency = <32000000>;
+		rotation = <90>;
+		dc-gpios = <&gpio 25 0>;
+		backlight = <&backlight>;
+	};
-- 
2.10.2

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

* [PATCH v4 6/7] dt-bindings: Add Multi-Inno MI0283QT binding
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

Add device-tree binding documentation for the MI0283QT display panel.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Rob Herring <robh@kernel.org>
---
 .../bindings/display/multi-inno,mi0283qt.txt       | 27 ++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt

diff --git a/Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt b/Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
new file mode 100644
index 0000000..eed48c3
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
@@ -0,0 +1,27 @@
+Multi-Inno MI0283QT display panel
+
+Required properties:
+- compatible:	"multi-inno,mi0283qt".
+
+The node for this driver must be a child node of a SPI controller, hence
+all mandatory properties described in ../spi/spi-bus.txt must be specified.
+
+Optional properties:
+- dc-gpios:	D/C pin. The presence/absence of this GPIO determines
+		the panel interface mode (IM[3:0] pins):
+		- present: IM=x110 4-wire 8-bit data serial interface
+		- absent:  IM=x101 3-wire 9-bit data serial interface
+- reset-gpios:	Reset pin
+- power-supply:	A regulator node for the supply voltage.
+- backlight:	phandle of the backlight device attached to the panel
+- rotation:	panel rotation in degrees counter clockwise (0,90,180,270)
+
+Example:
+	mi0283qt@0{
+		compatible = "multi-inno,mi0283qt";
+		reg = <0>;
+		spi-max-frequency = <32000000>;
+		rotation = <90>;
+		dc-gpios = <&gpio 25 0>;
+		backlight = <&backlight>;
+	};
-- 
2.10.2

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

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

* [PATCH v4 7/7] drm/tinydrm: Add support for Multi-Inno MI0283QT display
  2017-02-11 18:48 ` Noralf Trønnes
@ 2017-02-11 18:48   ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree
  Cc: robh, thomas.petazzoni, linux-kernel, Noralf Trønnes

Add driver to support the Multi-Inno MI0283QT display panel.
It has an ILI9341 MIPI DBI compatible display controller.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Thierry Reding <treding@nvidia.com>
---
 MAINTAINERS                        |   6 +
 drivers/gpu/drm/tinydrm/Kconfig    |   8 ++
 drivers/gpu/drm/tinydrm/Makefile   |   3 +
 drivers/gpu/drm/tinydrm/mi0283qt.c | 279 +++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/ili9341.h      |  54 +++++++
 5 files changed, 350 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mi0283qt.c
 create mode 100644 include/drm/tinydrm/ili9341.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 20b87eb..bc502ae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4297,6 +4297,12 @@ S:	Supported
 F:	drivers/gpu/drm/mediatek/
 F:	Documentation/devicetree/bindings/display/mediatek/
 
+DRM DRIVER FOR MI0283QT
+M:	Noralf Trønnes <noralf@tronnes.org>
+S:	Maintained
+F:	drivers/gpu/drm/tinydrm/mi0283qt.c
+F:	Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
+
 DRM DRIVER FOR MSM ADRENO GPU
 M:	Rob Clark <robdclark@gmail.com>
 L:	linux-arm-msm@vger.kernel.org
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index e00bcfc..3504c53 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -11,3 +11,11 @@ menuconfig DRM_TINYDRM
 
 config TINYDRM_MIPI_DBI
 	tristate
+
+config TINYDRM_MI0283QT
+	tristate "DRM support for MI0283QT"
+	depends on DRM_TINYDRM && SPI
+	select TINYDRM_MIPI_DBI
+	help
+	  DRM driver for the Multi-Inno MI0283QT display panel
+	  If M is selected the module will be called mi0283qt.
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index fe5d4c6..7a3604c 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -2,3 +2,6 @@ obj-$(CONFIG_DRM_TINYDRM)		+= core/
 
 # Controllers
 obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
+
+# Displays
+obj-$(CONFIG_TINYDRM_MI0283QT)		+= mi0283qt.o
diff --git a/drivers/gpu/drm/tinydrm/mi0283qt.c b/drivers/gpu/drm/tinydrm/mi0283qt.c
new file mode 100644
index 0000000..b29fe86
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mi0283qt.c
@@ -0,0 +1,279 @@
+/*
+ * DRM driver for Multi-Inno MI0283QT panels
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/tinydrm/ili9341.h>
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+static int mi0283qt_init(struct mipi_dbi *mipi)
+{
+	struct tinydrm_device *tdev = &mipi->tinydrm;
+	struct device *dev = tdev->drm->dev;
+	u8 addr_mode;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+
+	ret = regulator_enable(mipi->regulator);
+	if (ret) {
+		dev_err(dev, "Failed to enable regulator %d\n", ret);
+		return ret;
+	}
+
+	/* Avoid flicker by skipping setup if the bootloader has done it */
+	if (mipi_dbi_display_is_on(mipi))
+		return 0;
+
+	mipi_dbi_hw_reset(mipi);
+	ret = mipi_dbi_command(mipi, MIPI_DCS_SOFT_RESET);
+	if (ret) {
+		dev_err(dev, "Error sending command %d\n", ret);
+		regulator_disable(mipi->regulator);
+		return ret;
+	}
+
+	msleep(20);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF);
+
+	mipi_dbi_command(mipi, ILI9341_PWCTRLB, 0x00, 0x83, 0x30);
+	mipi_dbi_command(mipi, ILI9341_PWRSEQ, 0x64, 0x03, 0x12, 0x81);
+	mipi_dbi_command(mipi, ILI9341_DTCTRLA, 0x85, 0x01, 0x79);
+	mipi_dbi_command(mipi, ILI9341_PWCTRLA, 0x39, 0x2c, 0x00, 0x34, 0x02);
+	mipi_dbi_command(mipi, ILI9341_PUMPCTRL, 0x20);
+	mipi_dbi_command(mipi, ILI9341_DTCTRLB, 0x00, 0x00);
+
+	/* Power Control */
+	mipi_dbi_command(mipi, ILI9341_PWCTRL1, 0x26);
+	mipi_dbi_command(mipi, ILI9341_PWCTRL2, 0x11);
+	/* VCOM */
+	mipi_dbi_command(mipi, ILI9341_VMCTRL1, 0x35, 0x3e);
+	mipi_dbi_command(mipi, ILI9341_VMCTRL2, 0xbe);
+
+	/* Memory Access Control */
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+
+	switch (mipi->rotation) {
+	default:
+		addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY |
+			    ILI9341_MADCTL_MX;
+		break;
+	case 90:
+		addr_mode = ILI9341_MADCTL_MY;
+		break;
+	case 180:
+		addr_mode = ILI9341_MADCTL_MV;
+		break;
+	case 270:
+		addr_mode = ILI9341_MADCTL_MX;
+		break;
+	}
+	addr_mode |= ILI9341_MADCTL_BGR;
+	mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+
+	/* Frame Rate */
+	mipi_dbi_command(mipi, ILI9341_FRMCTR1, 0x00, 0x1b);
+
+	/* Gamma */
+	mipi_dbi_command(mipi, ILI9341_EN3GAM, 0x08);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
+	mipi_dbi_command(mipi, ILI9341_PGAMCTRL,
+		       0x1f, 0x1a, 0x18, 0x0a, 0x0f, 0x06, 0x45, 0x87,
+		       0x32, 0x0a, 0x07, 0x02, 0x07, 0x05, 0x00);
+	mipi_dbi_command(mipi, ILI9341_NGAMCTRL,
+		       0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3a, 0x78,
+		       0x4d, 0x05, 0x18, 0x0d, 0x38, 0x3a, 0x1f);
+
+	/* DDRAM */
+	mipi_dbi_command(mipi, ILI9341_ETMOD, 0x07);
+
+	/* Display */
+	mipi_dbi_command(mipi, ILI9341_DISCTRL, 0x0a, 0x82, 0x27, 0x00);
+	mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(100);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON);
+	msleep(100);
+
+	return 0;
+}
+
+static void mi0283qt_fini(void *data)
+{
+	struct mipi_dbi *mipi = data;
+
+	DRM_DEBUG_KMS("\n");
+	regulator_disable(mipi->regulator);
+}
+
+static const struct drm_simple_display_pipe_funcs mi0283qt_pipe_funcs = {
+	.enable = mipi_dbi_pipe_enable,
+	.disable = mipi_dbi_pipe_disable,
+	.update = tinydrm_display_pipe_update,
+	.prepare_fb = tinydrm_display_pipe_prepare_fb,
+};
+
+static const struct drm_display_mode mi0283qt_mode = {
+	TINYDRM_MODE(320, 240, 58, 43),
+};
+
+static struct drm_driver mi0283qt_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+				  DRIVER_ATOMIC,
+	TINYDRM_GEM_DRIVER_OPS,
+	.lastclose		= tinydrm_lastclose,
+	.debugfs_init		= mipi_dbi_debugfs_init,
+	.name			= "mi0283qt",
+	.desc			= "Multi-Inno MI0283QT",
+	.date			= "20160614",
+	.major			= 1,
+	.minor			= 0,
+};
+
+static const struct of_device_id mi0283qt_of_match[] = {
+	{ .compatible = "multi-inno,mi0283qt" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mi0283qt_of_match);
+
+static const struct spi_device_id mi0283qt_id[] = {
+	{ "mi0283qt", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, mi0283qt_id);
+
+static int mi0283qt_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct tinydrm_device *tdev;
+	struct mipi_dbi *mipi;
+	struct gpio_desc *dc;
+	u32 rotation = 0;
+	int ret;
+
+	mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
+	if (!mipi)
+		return -ENOMEM;
+
+	mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(mipi->reset)) {
+		dev_err(dev, "Failed to get gpio 'reset'\n");
+		return PTR_ERR(mipi->reset);
+	}
+
+	dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
+	if (IS_ERR(dc)) {
+		dev_err(dev, "Failed to get gpio 'dc'\n");
+		return PTR_ERR(dc);
+	}
+
+	mipi->regulator = devm_regulator_get(dev, "power");
+	if (IS_ERR(mipi->regulator))
+		return PTR_ERR(mipi->regulator);
+
+	mipi->backlight = tinydrm_of_find_backlight(dev);
+	if (IS_ERR(mipi->backlight))
+		return PTR_ERR(mipi->backlight);
+
+	device_property_read_u32(dev, "rotation", &rotation);
+
+	ret = mipi_dbi_spi_init(spi, mipi, dc, &mi0283qt_pipe_funcs,
+				&mi0283qt_driver, &mi0283qt_mode, rotation);
+	if (ret)
+		return ret;
+
+	ret = mi0283qt_init(mipi);
+	if (ret)
+		return ret;
+
+	/* use devres to fini after drm unregister (drv->remove is before) */
+	ret = devm_add_action(dev, mi0283qt_fini, mipi);
+	if (ret) {
+		mi0283qt_fini(mipi);
+		return ret;
+	}
+
+	tdev = &mipi->tinydrm;
+
+	ret = devm_tinydrm_register(tdev);
+	if (ret)
+		return ret;
+
+	spi_set_drvdata(spi, mipi);
+
+	DRM_DEBUG_DRIVER("Initialized %s:%s @%uMHz on minor %d\n",
+			 tdev->drm->driver->name, dev_name(dev),
+			 spi->max_speed_hz / 1000000,
+			 tdev->drm->primary->index);
+
+	return 0;
+}
+
+static void mi0283qt_shutdown(struct spi_device *spi)
+{
+	struct mipi_dbi *mipi = spi_get_drvdata(spi);
+
+	tinydrm_shutdown(&mipi->tinydrm);
+}
+
+static int __maybe_unused mi0283qt_pm_suspend(struct device *dev)
+{
+	struct mipi_dbi *mipi = dev_get_drvdata(dev);
+	int ret;
+
+	ret = tinydrm_suspend(&mipi->tinydrm);
+	if (ret)
+		return ret;
+
+	mi0283qt_fini(mipi);
+
+	return 0;
+}
+
+static int __maybe_unused mi0283qt_pm_resume(struct device *dev)
+{
+	struct mipi_dbi *mipi = dev_get_drvdata(dev);
+	int ret;
+
+	ret = mi0283qt_init(mipi);
+	if (ret)
+		return ret;
+
+	return tinydrm_resume(&mipi->tinydrm);
+}
+
+static const struct dev_pm_ops mi0283qt_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(mi0283qt_pm_suspend, mi0283qt_pm_resume)
+};
+
+static struct spi_driver mi0283qt_spi_driver = {
+	.driver = {
+		.name = "mi0283qt",
+		.owner = THIS_MODULE,
+		.of_match_table = mi0283qt_of_match,
+		.pm = &mi0283qt_pm_ops,
+	},
+	.id_table = mi0283qt_id,
+	.probe = mi0283qt_probe,
+	.shutdown = mi0283qt_shutdown,
+};
+module_spi_driver(mi0283qt_spi_driver);
+
+MODULE_DESCRIPTION("Multi-Inno MI0283QT DRM driver");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/ili9341.h b/include/drm/tinydrm/ili9341.h
new file mode 100644
index 0000000..807a09f
--- /dev/null
+++ b/include/drm/tinydrm/ili9341.h
@@ -0,0 +1,54 @@
+/*
+ * ILI9341 LCD controller
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_ILI9341_H
+#define __LINUX_ILI9341_H
+
+#define ILI9341_FRMCTR1    0xb1
+#define ILI9341_FRMCTR2    0xb2
+#define ILI9341_FRMCTR3    0xb3
+#define ILI9341_INVTR      0xb4
+#define ILI9341_PRCTR      0xb5
+#define ILI9341_DISCTRL    0xb6
+#define ILI9341_ETMOD      0xb7
+
+#define ILI9341_PWCTRL1    0xc0
+#define ILI9341_PWCTRL2    0xc1
+#define ILI9341_VMCTRL1    0xc5
+#define ILI9341_VMCTRL2    0xc7
+#define ILI9341_PWCTRLA    0xcb
+#define ILI9341_PWCTRLB    0xcf
+
+#define ILI9341_RDID1      0xda
+#define ILI9341_RDID2      0xdb
+#define ILI9341_RDID3      0xdc
+#define ILI9341_RDID4      0xd3
+
+#define ILI9341_PGAMCTRL   0xe0
+#define ILI9341_NGAMCTRL   0xe1
+#define ILI9341_DGAMCTRL1  0xe2
+#define ILI9341_DGAMCTRL2  0xe3
+#define ILI9341_DTCTRLA    0xe8
+#define ILI9341_DTCTRLB    0xea
+#define ILI9341_PWRSEQ     0xed
+
+#define ILI9341_EN3GAM     0xf2
+#define ILI9341_IFCTRL     0xf6
+#define ILI9341_PUMPCTRL   0xf7
+
+#define ILI9341_MADCTL_MH  BIT(2)
+#define ILI9341_MADCTL_BGR BIT(3)
+#define ILI9341_MADCTL_ML  BIT(4)
+#define ILI9341_MADCTL_MV  BIT(5)
+#define ILI9341_MADCTL_MX  BIT(6)
+#define ILI9341_MADCTL_MY  BIT(7)
+
+#endif /* __LINUX_ILI9341_H */
-- 
2.10.2

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

* [PATCH v4 7/7] drm/tinydrm: Add support for Multi-Inno MI0283QT display
@ 2017-02-11 18:48   ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-11 18:48 UTC (permalink / raw)
  To: dri-devel, devicetree; +Cc: thomas.petazzoni, linux-kernel

Add driver to support the Multi-Inno MI0283QT display panel.
It has an ILI9341 MIPI DBI compatible display controller.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Thierry Reding <treding@nvidia.com>
---
 MAINTAINERS                        |   6 +
 drivers/gpu/drm/tinydrm/Kconfig    |   8 ++
 drivers/gpu/drm/tinydrm/Makefile   |   3 +
 drivers/gpu/drm/tinydrm/mi0283qt.c | 279 +++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/ili9341.h      |  54 +++++++
 5 files changed, 350 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mi0283qt.c
 create mode 100644 include/drm/tinydrm/ili9341.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 20b87eb..bc502ae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4297,6 +4297,12 @@ S:	Supported
 F:	drivers/gpu/drm/mediatek/
 F:	Documentation/devicetree/bindings/display/mediatek/
 
+DRM DRIVER FOR MI0283QT
+M:	Noralf Trønnes <noralf@tronnes.org>
+S:	Maintained
+F:	drivers/gpu/drm/tinydrm/mi0283qt.c
+F:	Documentation/devicetree/bindings/display/multi-inno,mi0283qt.txt
+
 DRM DRIVER FOR MSM ADRENO GPU
 M:	Rob Clark <robdclark@gmail.com>
 L:	linux-arm-msm@vger.kernel.org
diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index e00bcfc..3504c53 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -11,3 +11,11 @@ menuconfig DRM_TINYDRM
 
 config TINYDRM_MIPI_DBI
 	tristate
+
+config TINYDRM_MI0283QT
+	tristate "DRM support for MI0283QT"
+	depends on DRM_TINYDRM && SPI
+	select TINYDRM_MIPI_DBI
+	help
+	  DRM driver for the Multi-Inno MI0283QT display panel
+	  If M is selected the module will be called mi0283qt.
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index fe5d4c6..7a3604c 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -2,3 +2,6 @@ obj-$(CONFIG_DRM_TINYDRM)		+= core/
 
 # Controllers
 obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
+
+# Displays
+obj-$(CONFIG_TINYDRM_MI0283QT)		+= mi0283qt.o
diff --git a/drivers/gpu/drm/tinydrm/mi0283qt.c b/drivers/gpu/drm/tinydrm/mi0283qt.c
new file mode 100644
index 0000000..b29fe86
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mi0283qt.c
@@ -0,0 +1,279 @@
+/*
+ * DRM driver for Multi-Inno MI0283QT panels
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/tinydrm/ili9341.h>
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm-helpers.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+static int mi0283qt_init(struct mipi_dbi *mipi)
+{
+	struct tinydrm_device *tdev = &mipi->tinydrm;
+	struct device *dev = tdev->drm->dev;
+	u8 addr_mode;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+
+	ret = regulator_enable(mipi->regulator);
+	if (ret) {
+		dev_err(dev, "Failed to enable regulator %d\n", ret);
+		return ret;
+	}
+
+	/* Avoid flicker by skipping setup if the bootloader has done it */
+	if (mipi_dbi_display_is_on(mipi))
+		return 0;
+
+	mipi_dbi_hw_reset(mipi);
+	ret = mipi_dbi_command(mipi, MIPI_DCS_SOFT_RESET);
+	if (ret) {
+		dev_err(dev, "Error sending command %d\n", ret);
+		regulator_disable(mipi->regulator);
+		return ret;
+	}
+
+	msleep(20);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_OFF);
+
+	mipi_dbi_command(mipi, ILI9341_PWCTRLB, 0x00, 0x83, 0x30);
+	mipi_dbi_command(mipi, ILI9341_PWRSEQ, 0x64, 0x03, 0x12, 0x81);
+	mipi_dbi_command(mipi, ILI9341_DTCTRLA, 0x85, 0x01, 0x79);
+	mipi_dbi_command(mipi, ILI9341_PWCTRLA, 0x39, 0x2c, 0x00, 0x34, 0x02);
+	mipi_dbi_command(mipi, ILI9341_PUMPCTRL, 0x20);
+	mipi_dbi_command(mipi, ILI9341_DTCTRLB, 0x00, 0x00);
+
+	/* Power Control */
+	mipi_dbi_command(mipi, ILI9341_PWCTRL1, 0x26);
+	mipi_dbi_command(mipi, ILI9341_PWCTRL2, 0x11);
+	/* VCOM */
+	mipi_dbi_command(mipi, ILI9341_VMCTRL1, 0x35, 0x3e);
+	mipi_dbi_command(mipi, ILI9341_VMCTRL2, 0xbe);
+
+	/* Memory Access Control */
+	mipi_dbi_command(mipi, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+
+	switch (mipi->rotation) {
+	default:
+		addr_mode = ILI9341_MADCTL_MV | ILI9341_MADCTL_MY |
+			    ILI9341_MADCTL_MX;
+		break;
+	case 90:
+		addr_mode = ILI9341_MADCTL_MY;
+		break;
+	case 180:
+		addr_mode = ILI9341_MADCTL_MV;
+		break;
+	case 270:
+		addr_mode = ILI9341_MADCTL_MX;
+		break;
+	}
+	addr_mode |= ILI9341_MADCTL_BGR;
+	mipi_dbi_command(mipi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+
+	/* Frame Rate */
+	mipi_dbi_command(mipi, ILI9341_FRMCTR1, 0x00, 0x1b);
+
+	/* Gamma */
+	mipi_dbi_command(mipi, ILI9341_EN3GAM, 0x08);
+	mipi_dbi_command(mipi, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
+	mipi_dbi_command(mipi, ILI9341_PGAMCTRL,
+		       0x1f, 0x1a, 0x18, 0x0a, 0x0f, 0x06, 0x45, 0x87,
+		       0x32, 0x0a, 0x07, 0x02, 0x07, 0x05, 0x00);
+	mipi_dbi_command(mipi, ILI9341_NGAMCTRL,
+		       0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3a, 0x78,
+		       0x4d, 0x05, 0x18, 0x0d, 0x38, 0x3a, 0x1f);
+
+	/* DDRAM */
+	mipi_dbi_command(mipi, ILI9341_ETMOD, 0x07);
+
+	/* Display */
+	mipi_dbi_command(mipi, ILI9341_DISCTRL, 0x0a, 0x82, 0x27, 0x00);
+	mipi_dbi_command(mipi, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(100);
+
+	mipi_dbi_command(mipi, MIPI_DCS_SET_DISPLAY_ON);
+	msleep(100);
+
+	return 0;
+}
+
+static void mi0283qt_fini(void *data)
+{
+	struct mipi_dbi *mipi = data;
+
+	DRM_DEBUG_KMS("\n");
+	regulator_disable(mipi->regulator);
+}
+
+static const struct drm_simple_display_pipe_funcs mi0283qt_pipe_funcs = {
+	.enable = mipi_dbi_pipe_enable,
+	.disable = mipi_dbi_pipe_disable,
+	.update = tinydrm_display_pipe_update,
+	.prepare_fb = tinydrm_display_pipe_prepare_fb,
+};
+
+static const struct drm_display_mode mi0283qt_mode = {
+	TINYDRM_MODE(320, 240, 58, 43),
+};
+
+static struct drm_driver mi0283qt_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
+				  DRIVER_ATOMIC,
+	TINYDRM_GEM_DRIVER_OPS,
+	.lastclose		= tinydrm_lastclose,
+	.debugfs_init		= mipi_dbi_debugfs_init,
+	.name			= "mi0283qt",
+	.desc			= "Multi-Inno MI0283QT",
+	.date			= "20160614",
+	.major			= 1,
+	.minor			= 0,
+};
+
+static const struct of_device_id mi0283qt_of_match[] = {
+	{ .compatible = "multi-inno,mi0283qt" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mi0283qt_of_match);
+
+static const struct spi_device_id mi0283qt_id[] = {
+	{ "mi0283qt", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, mi0283qt_id);
+
+static int mi0283qt_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct tinydrm_device *tdev;
+	struct mipi_dbi *mipi;
+	struct gpio_desc *dc;
+	u32 rotation = 0;
+	int ret;
+
+	mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
+	if (!mipi)
+		return -ENOMEM;
+
+	mipi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(mipi->reset)) {
+		dev_err(dev, "Failed to get gpio 'reset'\n");
+		return PTR_ERR(mipi->reset);
+	}
+
+	dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
+	if (IS_ERR(dc)) {
+		dev_err(dev, "Failed to get gpio 'dc'\n");
+		return PTR_ERR(dc);
+	}
+
+	mipi->regulator = devm_regulator_get(dev, "power");
+	if (IS_ERR(mipi->regulator))
+		return PTR_ERR(mipi->regulator);
+
+	mipi->backlight = tinydrm_of_find_backlight(dev);
+	if (IS_ERR(mipi->backlight))
+		return PTR_ERR(mipi->backlight);
+
+	device_property_read_u32(dev, "rotation", &rotation);
+
+	ret = mipi_dbi_spi_init(spi, mipi, dc, &mi0283qt_pipe_funcs,
+				&mi0283qt_driver, &mi0283qt_mode, rotation);
+	if (ret)
+		return ret;
+
+	ret = mi0283qt_init(mipi);
+	if (ret)
+		return ret;
+
+	/* use devres to fini after drm unregister (drv->remove is before) */
+	ret = devm_add_action(dev, mi0283qt_fini, mipi);
+	if (ret) {
+		mi0283qt_fini(mipi);
+		return ret;
+	}
+
+	tdev = &mipi->tinydrm;
+
+	ret = devm_tinydrm_register(tdev);
+	if (ret)
+		return ret;
+
+	spi_set_drvdata(spi, mipi);
+
+	DRM_DEBUG_DRIVER("Initialized %s:%s @%uMHz on minor %d\n",
+			 tdev->drm->driver->name, dev_name(dev),
+			 spi->max_speed_hz / 1000000,
+			 tdev->drm->primary->index);
+
+	return 0;
+}
+
+static void mi0283qt_shutdown(struct spi_device *spi)
+{
+	struct mipi_dbi *mipi = spi_get_drvdata(spi);
+
+	tinydrm_shutdown(&mipi->tinydrm);
+}
+
+static int __maybe_unused mi0283qt_pm_suspend(struct device *dev)
+{
+	struct mipi_dbi *mipi = dev_get_drvdata(dev);
+	int ret;
+
+	ret = tinydrm_suspend(&mipi->tinydrm);
+	if (ret)
+		return ret;
+
+	mi0283qt_fini(mipi);
+
+	return 0;
+}
+
+static int __maybe_unused mi0283qt_pm_resume(struct device *dev)
+{
+	struct mipi_dbi *mipi = dev_get_drvdata(dev);
+	int ret;
+
+	ret = mi0283qt_init(mipi);
+	if (ret)
+		return ret;
+
+	return tinydrm_resume(&mipi->tinydrm);
+}
+
+static const struct dev_pm_ops mi0283qt_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(mi0283qt_pm_suspend, mi0283qt_pm_resume)
+};
+
+static struct spi_driver mi0283qt_spi_driver = {
+	.driver = {
+		.name = "mi0283qt",
+		.owner = THIS_MODULE,
+		.of_match_table = mi0283qt_of_match,
+		.pm = &mi0283qt_pm_ops,
+	},
+	.id_table = mi0283qt_id,
+	.probe = mi0283qt_probe,
+	.shutdown = mi0283qt_shutdown,
+};
+module_spi_driver(mi0283qt_spi_driver);
+
+MODULE_DESCRIPTION("Multi-Inno MI0283QT DRM driver");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/ili9341.h b/include/drm/tinydrm/ili9341.h
new file mode 100644
index 0000000..807a09f
--- /dev/null
+++ b/include/drm/tinydrm/ili9341.h
@@ -0,0 +1,54 @@
+/*
+ * ILI9341 LCD controller
+ *
+ * Copyright 2016 Noralf Trønnes
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __LINUX_ILI9341_H
+#define __LINUX_ILI9341_H
+
+#define ILI9341_FRMCTR1    0xb1
+#define ILI9341_FRMCTR2    0xb2
+#define ILI9341_FRMCTR3    0xb3
+#define ILI9341_INVTR      0xb4
+#define ILI9341_PRCTR      0xb5
+#define ILI9341_DISCTRL    0xb6
+#define ILI9341_ETMOD      0xb7
+
+#define ILI9341_PWCTRL1    0xc0
+#define ILI9341_PWCTRL2    0xc1
+#define ILI9341_VMCTRL1    0xc5
+#define ILI9341_VMCTRL2    0xc7
+#define ILI9341_PWCTRLA    0xcb
+#define ILI9341_PWCTRLB    0xcf
+
+#define ILI9341_RDID1      0xda
+#define ILI9341_RDID2      0xdb
+#define ILI9341_RDID3      0xdc
+#define ILI9341_RDID4      0xd3
+
+#define ILI9341_PGAMCTRL   0xe0
+#define ILI9341_NGAMCTRL   0xe1
+#define ILI9341_DGAMCTRL1  0xe2
+#define ILI9341_DGAMCTRL2  0xe3
+#define ILI9341_DTCTRLA    0xe8
+#define ILI9341_DTCTRLB    0xea
+#define ILI9341_PWRSEQ     0xed
+
+#define ILI9341_EN3GAM     0xf2
+#define ILI9341_IFCTRL     0xf6
+#define ILI9341_PUMPCTRL   0xf7
+
+#define ILI9341_MADCTL_MH  BIT(2)
+#define ILI9341_MADCTL_BGR BIT(3)
+#define ILI9341_MADCTL_ML  BIT(4)
+#define ILI9341_MADCTL_MV  BIT(5)
+#define ILI9341_MADCTL_MX  BIT(6)
+#define ILI9341_MADCTL_MY  BIT(7)
+
+#endif /* __LINUX_ILI9341_H */
-- 
2.10.2

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

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-02-12 11:05     ` Andy Shevchenko
  0 siblings, 0 replies; 40+ messages in thread
From: Andy Shevchenko @ 2017-02-12 11:05 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel, devicetree, robh, Thomas Petazzoni, linux-kernel

On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> tinydrm provides helpers for very simple displays that can use
> CMA backed framebuffers and need flushing on changes.

> +/**

> + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
> + *                               object

Keep on one line?

> +const struct file_operations tinydrm_fops = {

> +       .owner          = THIS_MODULE,

Do we still need this?

> +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
> +                       const struct drm_framebuffer_funcs *fb_funcs,
> +                       struct drm_driver *driver)
> +{
> +       struct drm_device *drm;
> +
> +       mutex_init(&tdev->dirty_lock);
> +       tdev->fb_funcs = fb_funcs;
> +
> +       /*
> +        * We don't embed drm_device, because that prevent us from using
> +        * devm_kzalloc() to allocate tinydrm_device in the driver since

> +        * drm_dev_unref() frees the structure. The devm_ functions provide

"devm_ functions" -> "devm_kzalloc()" ?

Otherwise what else do you refer to and why here?

> +        * for easy error handling.
> +        */

> +static int tinydrm_register(struct tinydrm_device *tdev)
> +{
> +       struct drm_device *drm = tdev->drm;
> +       int bpp = drm->mode_config.preferred_depth;
> +       struct drm_fbdev_cma *fbdev;
> +       int ret;
> +
> +       ret = drm_dev_register(tdev->drm, 0);
> +       if (ret)
> +               return ret;
> +
> +       fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
> +                                             drm->mode_config.num_connector,
> +                                             tdev->fb_funcs);

> +       if (IS_ERR(fbdev))
> +               DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
> +       else
> +               tdev->fbdev_cma = fbdev;

Apparently I missed previous round of reviews, can you just put in
short words why error is not propagated here to the caller?

> +
> +       return 0;
> +}

> +/**
> + * tinydrm_display_pipe_init - Initialize display pipe
> + * @tdev: tinydrm device
> + * @funcs: Display pipe functions
> + * @connector_type: Connector type

> + * @formats: Array of supported formats (DRM_FORMAT\_\*)

DRM_FORMAT_* ?

> + * @format_count: Number of elements in @formats
> + * @mode: Supported mode
> + * @rotation: Initial @mode rotation in degrees Counter Clock Wise
> + *
> + * This function sets up a &drm_simple_display_pipe with a &drm_connector that
> + * has one fixed &drm_display_mode which is rotated according to @rotation.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */

> +{
> +       struct drm_device *drm = tdev->drm;
> +       struct drm_display_mode *mode_copy;
> +       struct drm_connector *connector;
> +       int ret;

> +       connector = tinydrm_connector_create(drm, mode_copy, connector_type);
> +       if (IS_ERR(connector))
> +               return PTR_ERR(connector);
> +

> +       ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
> +                                          format_count, connector);
> +       if (ret)
> +               return ret;
> +
> +       return 0;

return drm_... ?

> +}
> +EXPORT_SYMBOL(tinydrm_display_pipe_init);

-- 
With Best Regards,
Andy Shevchenko

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-02-12 11:05     ` Andy Shevchenko
  0 siblings, 0 replies; 40+ messages in thread
From: Andy Shevchenko @ 2017-02-12 11:05 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, devicetree,
	robh-DgEjT+Ai2ygdnm+yROfE0A, Thomas Petazzoni,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf-L59+Z2yzLopAfugRpC6u6w@public.gmane.org> wrote:
> tinydrm provides helpers for very simple displays that can use
> CMA backed framebuffers and need flushing on changes.

> +/**

> + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
> + *                               object

Keep on one line?

> +const struct file_operations tinydrm_fops = {

> +       .owner          = THIS_MODULE,

Do we still need this?

> +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
> +                       const struct drm_framebuffer_funcs *fb_funcs,
> +                       struct drm_driver *driver)
> +{
> +       struct drm_device *drm;
> +
> +       mutex_init(&tdev->dirty_lock);
> +       tdev->fb_funcs = fb_funcs;
> +
> +       /*
> +        * We don't embed drm_device, because that prevent us from using
> +        * devm_kzalloc() to allocate tinydrm_device in the driver since

> +        * drm_dev_unref() frees the structure. The devm_ functions provide

"devm_ functions" -> "devm_kzalloc()" ?

Otherwise what else do you refer to and why here?

> +        * for easy error handling.
> +        */

> +static int tinydrm_register(struct tinydrm_device *tdev)
> +{
> +       struct drm_device *drm = tdev->drm;
> +       int bpp = drm->mode_config.preferred_depth;
> +       struct drm_fbdev_cma *fbdev;
> +       int ret;
> +
> +       ret = drm_dev_register(tdev->drm, 0);
> +       if (ret)
> +               return ret;
> +
> +       fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
> +                                             drm->mode_config.num_connector,
> +                                             tdev->fb_funcs);

> +       if (IS_ERR(fbdev))
> +               DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
> +       else
> +               tdev->fbdev_cma = fbdev;

Apparently I missed previous round of reviews, can you just put in
short words why error is not propagated here to the caller?

> +
> +       return 0;
> +}

> +/**
> + * tinydrm_display_pipe_init - Initialize display pipe
> + * @tdev: tinydrm device
> + * @funcs: Display pipe functions
> + * @connector_type: Connector type

> + * @formats: Array of supported formats (DRM_FORMAT\_\*)

DRM_FORMAT_* ?

> + * @format_count: Number of elements in @formats
> + * @mode: Supported mode
> + * @rotation: Initial @mode rotation in degrees Counter Clock Wise
> + *
> + * This function sets up a &drm_simple_display_pipe with a &drm_connector that
> + * has one fixed &drm_display_mode which is rotated according to @rotation.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */

> +{
> +       struct drm_device *drm = tdev->drm;
> +       struct drm_display_mode *mode_copy;
> +       struct drm_connector *connector;
> +       int ret;

> +       connector = tinydrm_connector_create(drm, mode_copy, connector_type);
> +       if (IS_ERR(connector))
> +               return PTR_ERR(connector);
> +

> +       ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
> +                                          format_count, connector);
> +       if (ret)
> +               return ret;
> +
> +       return 0;

return drm_... ?

> +}
> +EXPORT_SYMBOL(tinydrm_display_pipe_init);

-- 
With Best Regards,
Andy Shevchenko
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 2/7] drm/tinydrm: Add helper functions
  2017-02-11 18:48   ` Noralf Trønnes
@ 2017-02-12 11:50     ` Andy Shevchenko
  -1 siblings, 0 replies; 40+ messages in thread
From: Andy Shevchenko @ 2017-02-12 11:50 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel, devicetree, robh, Thomas Petazzoni, linux-kernel

On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> Add common functionality needed by many tinydrm drivers.

> +int tinydrm_enable_backlight(struct backlight_device *backlight)
> +{
> +       unsigned int old_state;
> +       int ret;
> +
> +       if (!backlight)
> +               return 0;
> +
> +       old_state = backlight->props.state;
> +       backlight->props.state &= ~BL_CORE_FBBLANK;
> +       DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
> +                     backlight->props.state);

"%#x" ?
(And elsewhere)

> +#if IS_ENABLED(CONFIG_SPI)

> +size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len)
> +{
> +       size_t ret;
> +

> +       ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len);

I don't get why DMA constrain somehow affects this framework?

What if max_dma_len is zero (imagine SPI master that works only by PIO
by some reason)?

> +       if (max_len)
> +               ret = min(ret, max_len);
> +       if (spi_max)
> +               ret = min_t(size_t, ret, spi_max);

> +       ret &= ~0x3;

Why alignment is that? Why do we need it? Isn't a busyness of SPI
framework to cope with it?

> +       if (ret < 4)

It's effectively check for 0.

> +               ret = 4;
> +
> +       return ret;

> +}
> +EXPORT_SYMBOL(tinydrm_spi_max_transfer_size);

> +static void
> +tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr,
> +                     const void *buf, int idx, bool tx)
> +{
> +       u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz;
> +       char linebuf[3 * 32];
> +

> +       hex_dump_to_buffer(buf, tr->len, 16,
> +                          DIV_ROUND_UP(tr->bits_per_word, 8),
> +                          linebuf, sizeof(linebuf), false);

> +
> +       printk(KERN_DEBUG
> +              "    tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx,
> +              speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000,
> +              speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len,
> +              tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : "");

I hope at some point we will have some extension to print speeds,
sizes and so on based on algo in string_get_size().

> +}

> +int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
> +                        struct spi_transfer *header, u8 bpw, const void *buf,
> +                        size_t len)
> +{
> +       struct spi_transfer tr = {
> +               .bits_per_word = bpw,
> +               .speed_hz = speed_hz,
> +       };
> +       struct spi_message m;
> +       u16 *swap_buf = NULL;
> +       size_t max_chunk;
> +       size_t chunk;
> +       int ret = 0;
> +
> +       if (WARN_ON_ONCE(bpw != 8 && bpw != 16))
> +               return -EINVAL;
> +
> +       max_chunk = tinydrm_spi_max_transfer_size(spi, 0);
> +

> +       if (drm_debug & DRM_UT_DRIVER)
> +               pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n",
> +                        __func__, bpw, max_chunk);

For all of your dev_dbg() / pr_debug() __func__ argument might be
redundant. Dynamic Debug may include this by request from user.

> +/**
> + * tinydrm_machine_little_endian - Machine is little endian
> + *
> + * Returns:
> + * true if *defined(__LITTLE_ENDIAN)*, false otherwise
> + */
> +static inline bool tinydrm_machine_little_endian(void)
> +{
> +#if defined(__LITTLE_ENDIAN)
> +       return true;
> +#else
> +       return false;
> +#endif
> +}

Hmm... What is the typical code of a caller for this?

-- 
With Best Regards,
Andy Shevchenko

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

* Re: [PATCH v4 2/7] drm/tinydrm: Add helper functions
@ 2017-02-12 11:50     ` Andy Shevchenko
  0 siblings, 0 replies; 40+ messages in thread
From: Andy Shevchenko @ 2017-02-12 11:50 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Thomas Petazzoni, devicetree, linux-kernel, dri-devel

On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> Add common functionality needed by many tinydrm drivers.

> +int tinydrm_enable_backlight(struct backlight_device *backlight)
> +{
> +       unsigned int old_state;
> +       int ret;
> +
> +       if (!backlight)
> +               return 0;
> +
> +       old_state = backlight->props.state;
> +       backlight->props.state &= ~BL_CORE_FBBLANK;
> +       DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
> +                     backlight->props.state);

"%#x" ?
(And elsewhere)

> +#if IS_ENABLED(CONFIG_SPI)

> +size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len)
> +{
> +       size_t ret;
> +

> +       ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len);

I don't get why DMA constrain somehow affects this framework?

What if max_dma_len is zero (imagine SPI master that works only by PIO
by some reason)?

> +       if (max_len)
> +               ret = min(ret, max_len);
> +       if (spi_max)
> +               ret = min_t(size_t, ret, spi_max);

> +       ret &= ~0x3;

Why alignment is that? Why do we need it? Isn't a busyness of SPI
framework to cope with it?

> +       if (ret < 4)

It's effectively check for 0.

> +               ret = 4;
> +
> +       return ret;

> +}
> +EXPORT_SYMBOL(tinydrm_spi_max_transfer_size);

> +static void
> +tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr,
> +                     const void *buf, int idx, bool tx)
> +{
> +       u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz;
> +       char linebuf[3 * 32];
> +

> +       hex_dump_to_buffer(buf, tr->len, 16,
> +                          DIV_ROUND_UP(tr->bits_per_word, 8),
> +                          linebuf, sizeof(linebuf), false);

> +
> +       printk(KERN_DEBUG
> +              "    tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx,
> +              speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000,
> +              speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len,
> +              tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : "");

I hope at some point we will have some extension to print speeds,
sizes and so on based on algo in string_get_size().

> +}

> +int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
> +                        struct spi_transfer *header, u8 bpw, const void *buf,
> +                        size_t len)
> +{
> +       struct spi_transfer tr = {
> +               .bits_per_word = bpw,
> +               .speed_hz = speed_hz,
> +       };
> +       struct spi_message m;
> +       u16 *swap_buf = NULL;
> +       size_t max_chunk;
> +       size_t chunk;
> +       int ret = 0;
> +
> +       if (WARN_ON_ONCE(bpw != 8 && bpw != 16))
> +               return -EINVAL;
> +
> +       max_chunk = tinydrm_spi_max_transfer_size(spi, 0);
> +

> +       if (drm_debug & DRM_UT_DRIVER)
> +               pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n",
> +                        __func__, bpw, max_chunk);

For all of your dev_dbg() / pr_debug() __func__ argument might be
redundant. Dynamic Debug may include this by request from user.

> +/**
> + * tinydrm_machine_little_endian - Machine is little endian
> + *
> + * Returns:
> + * true if *defined(__LITTLE_ENDIAN)*, false otherwise
> + */
> +static inline bool tinydrm_machine_little_endian(void)
> +{
> +#if defined(__LITTLE_ENDIAN)
> +       return true;
> +#else
> +       return false;
> +#endif
> +}

Hmm... What is the typical code of a caller for this?

-- 
With Best Regards,
Andy Shevchenko
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
  2017-02-12 11:05     ` Andy Shevchenko
@ 2017-02-12 13:57       ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-12 13:57 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: dri-devel, devicetree, robh, Thomas Petazzoni, linux-kernel


Den 12.02.2017 12.05, skrev Andy Shevchenko:
> On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
>> tinydrm provides helpers for very simple displays that can use
>> CMA backed framebuffers and need flushing on changes.
>> +/**
>> + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
>> + *                               object
> Keep on one line?

That means 81 columns and checkpatch complaint.
This in one of the annoying things about Linux
kernel coding. Always trying
to avoid
breaking up lines.
Because for me it hinders redability.

>> +const struct file_operations tinydrm_fops = {
>> +       .owner          = THIS_MODULE,
> Do we still need this?

Looks like it, see drm_stub_open()

>> +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
>> +                       const struct drm_framebuffer_funcs *fb_funcs,
>> +                       struct drm_driver *driver)
>> +{
>> +       struct drm_device *drm;
>> +
>> +       mutex_init(&tdev->dirty_lock);
>> +       tdev->fb_funcs = fb_funcs;
>> +
>> +       /*
>> +        * We don't embed drm_device, because that prevent us from using
>> +        * devm_kzalloc() to allocate tinydrm_device in the driver since
>> +        * drm_dev_unref() frees the structure. The devm_ functions provide
> "devm_ functions" -> "devm_kzalloc()" ?
>
> Otherwise what else do you refer to and why here?

yes, that last sentence can be dropped.

>> +        * for easy error handling.
>> +        */
>> +static int tinydrm_register(struct tinydrm_device *tdev)
>> +{
>> +       struct drm_device *drm = tdev->drm;
>> +       int bpp = drm->mode_config.preferred_depth;
>> +       struct drm_fbdev_cma *fbdev;
>> +       int ret;
>> +
>> +       ret = drm_dev_register(tdev->drm, 0);
>> +       if (ret)
>> +               return ret;
>> +
>> +       fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
>> +                                             drm->mode_config.num_connector,
>> +                                             tdev->fb_funcs);
>> +       if (IS_ERR(fbdev))
>> +               DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
>> +       else
>> +               tdev->fbdev_cma = fbdev;
> Apparently I missed previous round of reviews, can you just put in
> short words why error is not propagated here to the caller?

A comment might have helped here:
/* fbdev is of minor importance, don't let it stop us */

>> +
>> +       return 0;
>> +}
>> +/**
>> + * tinydrm_display_pipe_init - Initialize display pipe
>> + * @tdev: tinydrm device
>> + * @funcs: Display pipe functions
>> + * @connector_type: Connector type
>> + * @formats: Array of supported formats (DRM_FORMAT\_\*)
> DRM_FORMAT_* ?

sphinx wants it that way.

>> + * @format_count: Number of elements in @formats
>> + * @mode: Supported mode
>> + * @rotation: Initial @mode rotation in degrees Counter Clock Wise
>> + *
>> + * This function sets up a &drm_simple_display_pipe with a &drm_connector that
>> + * has one fixed &drm_display_mode which is rotated according to @rotation.
>> + *
>> + * Returns:
>> + * Zero on success, negative error code on failure.
>> + */
>> +{
>> +       struct drm_device *drm = tdev->drm;
>> +       struct drm_display_mode *mode_copy;
>> +       struct drm_connector *connector;
>> +       int ret;
>> +       connector = tinydrm_connector_create(drm, mode_copy, connector_type);
>> +       if (IS_ERR(connector))
>> +               return PTR_ERR(connector);
>> +
>> +       ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
>> +                                          format_count, connector);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
> return drm_... ?

Yep.


tinydrm will be merged the way it is now, unless someone points to
something that is broken. But I collect your comments for a later
cleanup patchset. It takes a lot of effort for me as an amateur to
keeps this code up-to-date out-of-tree for months. It's not even
sure that I've hit the mark with this, so there will most likely be
changes when I start converting fbtft drivers, and I'll implement the
remaining bits and pieces as I make changes. The core of tinydrm won't
change because of unforseen fbtft quirks, but other parts might.


Noralf.

>> +}
>> +EXPORT_SYMBOL(tinydrm_display_pipe_init);

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-02-12 13:57       ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-12 13:57 UTC (permalink / raw)
  To: Andy Shevchenko; +Cc: Thomas Petazzoni, devicetree, linux-kernel, dri-devel


Den 12.02.2017 12.05, skrev Andy Shevchenko:
> On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
>> tinydrm provides helpers for very simple displays that can use
>> CMA backed framebuffers and need flushing on changes.
>> +/**
>> + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
>> + *                               object
> Keep on one line?

That means 81 columns and checkpatch complaint.
This in one of the annoying things about Linux
kernel coding. Always trying
to avoid
breaking up lines.
Because for me it hinders redability.

>> +const struct file_operations tinydrm_fops = {
>> +       .owner          = THIS_MODULE,
> Do we still need this?

Looks like it, see drm_stub_open()

>> +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
>> +                       const struct drm_framebuffer_funcs *fb_funcs,
>> +                       struct drm_driver *driver)
>> +{
>> +       struct drm_device *drm;
>> +
>> +       mutex_init(&tdev->dirty_lock);
>> +       tdev->fb_funcs = fb_funcs;
>> +
>> +       /*
>> +        * We don't embed drm_device, because that prevent us from using
>> +        * devm_kzalloc() to allocate tinydrm_device in the driver since
>> +        * drm_dev_unref() frees the structure. The devm_ functions provide
> "devm_ functions" -> "devm_kzalloc()" ?
>
> Otherwise what else do you refer to and why here?

yes, that last sentence can be dropped.

>> +        * for easy error handling.
>> +        */
>> +static int tinydrm_register(struct tinydrm_device *tdev)
>> +{
>> +       struct drm_device *drm = tdev->drm;
>> +       int bpp = drm->mode_config.preferred_depth;
>> +       struct drm_fbdev_cma *fbdev;
>> +       int ret;
>> +
>> +       ret = drm_dev_register(tdev->drm, 0);
>> +       if (ret)
>> +               return ret;
>> +
>> +       fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
>> +                                             drm->mode_config.num_connector,
>> +                                             tdev->fb_funcs);
>> +       if (IS_ERR(fbdev))
>> +               DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
>> +       else
>> +               tdev->fbdev_cma = fbdev;
> Apparently I missed previous round of reviews, can you just put in
> short words why error is not propagated here to the caller?

A comment might have helped here:
/* fbdev is of minor importance, don't let it stop us */

>> +
>> +       return 0;
>> +}
>> +/**
>> + * tinydrm_display_pipe_init - Initialize display pipe
>> + * @tdev: tinydrm device
>> + * @funcs: Display pipe functions
>> + * @connector_type: Connector type
>> + * @formats: Array of supported formats (DRM_FORMAT\_\*)
> DRM_FORMAT_* ?

sphinx wants it that way.

>> + * @format_count: Number of elements in @formats
>> + * @mode: Supported mode
>> + * @rotation: Initial @mode rotation in degrees Counter Clock Wise
>> + *
>> + * This function sets up a &drm_simple_display_pipe with a &drm_connector that
>> + * has one fixed &drm_display_mode which is rotated according to @rotation.
>> + *
>> + * Returns:
>> + * Zero on success, negative error code on failure.
>> + */
>> +{
>> +       struct drm_device *drm = tdev->drm;
>> +       struct drm_display_mode *mode_copy;
>> +       struct drm_connector *connector;
>> +       int ret;
>> +       connector = tinydrm_connector_create(drm, mode_copy, connector_type);
>> +       if (IS_ERR(connector))
>> +               return PTR_ERR(connector);
>> +
>> +       ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats,
>> +                                          format_count, connector);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return 0;
> return drm_... ?

Yep.


tinydrm will be merged the way it is now, unless someone points to
something that is broken. But I collect your comments for a later
cleanup patchset. It takes a lot of effort for me as an amateur to
keeps this code up-to-date out-of-tree for months. It's not even
sure that I've hit the mark with this, so there will most likely be
changes when I start converting fbtft drivers, and I'll implement the
remaining bits and pieces as I make changes. The core of tinydrm won't
change because of unforseen fbtft quirks, but other parts might.


Noralf.

>> +}
>> +EXPORT_SYMBOL(tinydrm_display_pipe_init);

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

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

* Re: [PATCH v4 2/7] drm/tinydrm: Add helper functions
@ 2017-02-12 15:33       ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-12 15:33 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: dri-devel, devicetree, robh, Thomas Petazzoni, linux-kernel


Den 12.02.2017 12.50, skrev Andy Shevchenko:
> On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
>> Add common functionality needed by many tinydrm drivers.
>> +int tinydrm_enable_backlight(struct backlight_device *backlight)
>> +{
>> +       unsigned int old_state;
>> +       int ret;
>> +
>> +       if (!backlight)
>> +               return 0;
>> +
>> +       old_state = backlight->props.state;
>> +       backlight->props.state &= ~BL_CORE_FBBLANK;
>> +       DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
>> +                     backlight->props.state);
> "%#x" ?
> (And elsewhere)
>
>> +#if IS_ENABLED(CONFIG_SPI)
>> +size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len)
>> +{
>> +       size_t ret;
>> +
>> +       ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len);
> I don't get why DMA constrain somehow affects this framework?

The reason is that spi-bcm2835 reverts to PIO on larger buffers.
Looking at __spi_map_msg() and spi_map_buf() it becomes clear that the
core breaks up the buffer into manageable parts. So this must be a bug
in spi-bcm2835 (and spi-pxa2xx) since no other drivers have a upper
limit in the .can_dma() callback.
So you're rightly confused, with drivers fixed, this limit can be lifted.

> What if max_dma_len is zero (imagine SPI master that works only by PIO
> by some reason)?

It can't be zero:

int spi_register_master(struct spi_master *master)
{
...
     if (!master->max_dma_len)
         master->max_dma_len = INT_MAX;

>> +       if (max_len)
>> +               ret = min(ret, max_len);
>> +       if (spi_max)
>> +               ret = min_t(size_t, ret, spi_max);
>> +       ret &= ~0x3;
> Why alignment is that? Why do we need it? Isn't a busyness of SPI
> framework to cope with it?

This minimum capping doesn't look good. I should probably put >16
instead, that would cover the minimum for the 9-bit emulation code.

The reason I let the user change the transfer size, is that some usb
audio card on Raspberry Pi stuttered with 4k fbtft transfers.

>> +       if (ret < 4)
> It's effectively check for 0.
>
>> +               ret = 4;
>> +
>> +       return ret;
>> +}
>> +EXPORT_SYMBOL(tinydrm_spi_max_transfer_size);
>> +static void
>> +tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr,
>> +                     const void *buf, int idx, bool tx)
>> +{
>> +       u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz;
>> +       char linebuf[3 * 32];
>> +
>> +       hex_dump_to_buffer(buf, tr->len, 16,
>> +                          DIV_ROUND_UP(tr->bits_per_word, 8),
>> +                          linebuf, sizeof(linebuf), false);
>> +
>> +       printk(KERN_DEBUG
>> +              "    tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx,
>> +              speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000,
>> +              speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len,
>> +              tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : "");
> I hope at some point we will have some extension to print speeds,
> sizes and so on based on algo in string_get_size().
>
>> +}
>> +int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
>> +                        struct spi_transfer *header, u8 bpw, const void *buf,
>> +                        size_t len)
>> +{
>> +       struct spi_transfer tr = {
>> +               .bits_per_word = bpw,
>> +               .speed_hz = speed_hz,
>> +       };
>> +       struct spi_message m;
>> +       u16 *swap_buf = NULL;
>> +       size_t max_chunk;
>> +       size_t chunk;
>> +       int ret = 0;
>> +
>> +       if (WARN_ON_ONCE(bpw != 8 && bpw != 16))
>> +               return -EINVAL;
>> +
>> +       max_chunk = tinydrm_spi_max_transfer_size(spi, 0);
>> +
>> +       if (drm_debug & DRM_UT_DRIVER)
>> +               pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n",
>> +                        __func__, bpw, max_chunk);
> For all of your dev_dbg() / pr_debug() __func__ argument might be
> redundant. Dynamic Debug may include this by request from user.
>
>> +/**
>> + * tinydrm_machine_little_endian - Machine is little endian
>> + *
>> + * Returns:
>> + * true if *defined(__LITTLE_ENDIAN)*, false otherwise
>> + */
>> +static inline bool tinydrm_machine_little_endian(void)
>> +{
>> +#if defined(__LITTLE_ENDIAN)
>> +       return true;
>> +#else
>> +       return false;
>> +#endif
>> +}
> Hmm... What is the typical code of a caller for this?

If the bus can't do 16-bit natively, it will have to swap the bytes on
little endian machines before transfer as 8-bit (Raspberry Pi can't do
16-bit SPI with it's DMA capable controller).
So this function is to avoid #ifdef's elsewhere.

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

* Re: [PATCH v4 2/7] drm/tinydrm: Add helper functions
@ 2017-02-12 15:33       ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-02-12 15:33 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, devicetree,
	robh-DgEjT+Ai2ygdnm+yROfE0A, Thomas Petazzoni,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA


Den 12.02.2017 12.50, skrev Andy Shevchenko:
> On Sat, Feb 11, 2017 at 8:48 PM, Noralf Trønnes <noralf-L59+Z2yzLopAfugRpC6u6w@public.gmane.org> wrote:
>> Add common functionality needed by many tinydrm drivers.
>> +int tinydrm_enable_backlight(struct backlight_device *backlight)
>> +{
>> +       unsigned int old_state;
>> +       int ret;
>> +
>> +       if (!backlight)
>> +               return 0;
>> +
>> +       old_state = backlight->props.state;
>> +       backlight->props.state &= ~BL_CORE_FBBLANK;
>> +       DRM_DEBUG_KMS("Backlight state: 0x%x -> 0x%x\n", old_state,
>> +                     backlight->props.state);
> "%#x" ?
> (And elsewhere)
>
>> +#if IS_ENABLED(CONFIG_SPI)
>> +size_t tinydrm_spi_max_transfer_size(struct spi_device *spi, size_t max_len)
>> +{
>> +       size_t ret;
>> +
>> +       ret = min(spi_max_transfer_size(spi), spi->master->max_dma_len);
> I don't get why DMA constrain somehow affects this framework?

The reason is that spi-bcm2835 reverts to PIO on larger buffers.
Looking at __spi_map_msg() and spi_map_buf() it becomes clear that the
core breaks up the buffer into manageable parts. So this must be a bug
in spi-bcm2835 (and spi-pxa2xx) since no other drivers have a upper
limit in the .can_dma() callback.
So you're rightly confused, with drivers fixed, this limit can be lifted.

> What if max_dma_len is zero (imagine SPI master that works only by PIO
> by some reason)?

It can't be zero:

int spi_register_master(struct spi_master *master)
{
...
     if (!master->max_dma_len)
         master->max_dma_len = INT_MAX;

>> +       if (max_len)
>> +               ret = min(ret, max_len);
>> +       if (spi_max)
>> +               ret = min_t(size_t, ret, spi_max);
>> +       ret &= ~0x3;
> Why alignment is that? Why do we need it? Isn't a busyness of SPI
> framework to cope with it?

This minimum capping doesn't look good. I should probably put >16
instead, that would cover the minimum for the 9-bit emulation code.

The reason I let the user change the transfer size, is that some usb
audio card on Raspberry Pi stuttered with 4k fbtft transfers.

>> +       if (ret < 4)
> It's effectively check for 0.
>
>> +               ret = 4;
>> +
>> +       return ret;
>> +}
>> +EXPORT_SYMBOL(tinydrm_spi_max_transfer_size);
>> +static void
>> +tinydrm_dbg_spi_print(struct spi_device *spi, struct spi_transfer *tr,
>> +                     const void *buf, int idx, bool tx)
>> +{
>> +       u32 speed_hz = tr->speed_hz ? tr->speed_hz : spi->max_speed_hz;
>> +       char linebuf[3 * 32];
>> +
>> +       hex_dump_to_buffer(buf, tr->len, 16,
>> +                          DIV_ROUND_UP(tr->bits_per_word, 8),
>> +                          linebuf, sizeof(linebuf), false);
>> +
>> +       printk(KERN_DEBUG
>> +              "    tr(%i): speed=%u%s, bpw=%i, len=%u, %s_buf=[%s%s]\n", idx,
>> +              speed_hz > 1000000 ? speed_hz / 1000000 : speed_hz / 1000,
>> +              speed_hz > 1000000 ? "MHz" : "kHz", tr->bits_per_word, tr->len,
>> +              tx ? "tx" : "rx", linebuf, tr->len > 16 ? " ..." : "");
> I hope at some point we will have some extension to print speeds,
> sizes and so on based on algo in string_get_size().
>
>> +}
>> +int tinydrm_spi_transfer(struct spi_device *spi, u32 speed_hz,
>> +                        struct spi_transfer *header, u8 bpw, const void *buf,
>> +                        size_t len)
>> +{
>> +       struct spi_transfer tr = {
>> +               .bits_per_word = bpw,
>> +               .speed_hz = speed_hz,
>> +       };
>> +       struct spi_message m;
>> +       u16 *swap_buf = NULL;
>> +       size_t max_chunk;
>> +       size_t chunk;
>> +       int ret = 0;
>> +
>> +       if (WARN_ON_ONCE(bpw != 8 && bpw != 16))
>> +               return -EINVAL;
>> +
>> +       max_chunk = tinydrm_spi_max_transfer_size(spi, 0);
>> +
>> +       if (drm_debug & DRM_UT_DRIVER)
>> +               pr_debug("[drm:%s] bpw=%u, max_chunk=%zu, transfers:\n",
>> +                        __func__, bpw, max_chunk);
> For all of your dev_dbg() / pr_debug() __func__ argument might be
> redundant. Dynamic Debug may include this by request from user.
>
>> +/**
>> + * tinydrm_machine_little_endian - Machine is little endian
>> + *
>> + * Returns:
>> + * true if *defined(__LITTLE_ENDIAN)*, false otherwise
>> + */
>> +static inline bool tinydrm_machine_little_endian(void)
>> +{
>> +#if defined(__LITTLE_ENDIAN)
>> +       return true;
>> +#else
>> +       return false;
>> +#endif
>> +}
> Hmm... What is the typical code of a caller for this?

If the bus can't do 16-bit natively, it will have to swap the bytes on
little endian machines before transfer as 8-bit (Raspberry Pi can't do
16-bit SPI with it's DMA capable controller).
So this function is to avoid #ifdef's elsewhere.

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

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

* Re: [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
  2017-02-11 18:48   ` Noralf Trønnes
@ 2017-02-14 16:19     ` Rob Herring
  -1 siblings, 0 replies; 40+ messages in thread
From: Rob Herring @ 2017-02-14 16:19 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, devicetree, Thomas Petazzoni, linux-kernel

On Sat, Feb 11, 2017 at 12:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> Display panels can be oriented many ways, especially in the embedded
> world. The rotation property is a way to describe this orientation.
> The counter clockwise direction is chosen because that's what fbdev
> and drm use.
>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Acked-by: Thierry Reding <treding@nvidia.com>
> ---
>
> Changes since version 3:
> - Changed display/display.txt -> display/panel/panel.txt
>
>  Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++
>  1 file changed, 4 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/panel/panel.txt

Acked-by: Rob Herring <robh@kernel.org>

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

* Re: [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
@ 2017-02-14 16:19     ` Rob Herring
  0 siblings, 0 replies; 40+ messages in thread
From: Rob Herring @ 2017-02-14 16:19 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Thomas Petazzoni, devicetree, linux-kernel, dri-devel

On Sat, Feb 11, 2017 at 12:48 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> Display panels can be oriented many ways, especially in the embedded
> world. The rotation property is a way to describe this orientation.
> The counter clockwise direction is chosen because that's what fbdev
> and drm use.
>
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Acked-by: Thierry Reding <treding@nvidia.com>
> ---
>
> Changes since version 3:
> - Changed display/display.txt -> display/panel/panel.txt
>
>  Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++
>  1 file changed, 4 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/panel/panel.txt

Acked-by: Rob Herring <robh@kernel.org>
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
  2017-02-12 13:57       ` Noralf Trønnes
@ 2017-02-14 19:54         ` Daniel Vetter
  -1 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-02-14 19:54 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: Andy Shevchenko, Thomas Petazzoni, devicetree, linux-kernel, dri-devel

On Sun, Feb 12, 2017 at 2:57 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> tinydrm will be merged the way it is now, unless someone points to
> something that is broken. But I collect your comments for a later
> cleanup patchset. It takes a lot of effort for me as an amateur to
> keeps this code up-to-date out-of-tree for months. It's not even
> sure that I've hit the mark with this, so there will most likely be
> changes when I start converting fbtft drivers, and I'll implement the
> remaining bits and pieces as I make changes. The core of tinydrm won't
> change because of unforseen fbtft quirks, but other parts might.

+1, pls send pull request to Dave Airlie. DT-acks are all there, and
Thierry confirmed on irc that his reply on the last thread was indeed
meant as a full ack for going ahead and polishing later. I also
chatted with Dave directly, and he's perfectly happy to take this in
as-is. If you do this soon, should still easily get into 4.11 (since
the 4.10 release is delayed).

Cheers, Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-02-14 19:54         ` Daniel Vetter
  0 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-02-14 19:54 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: Thomas Petazzoni, devicetree, Andy Shevchenko, linux-kernel, dri-devel

On Sun, Feb 12, 2017 at 2:57 PM, Noralf Trønnes <noralf@tronnes.org> wrote:
> tinydrm will be merged the way it is now, unless someone points to
> something that is broken. But I collect your comments for a later
> cleanup patchset. It takes a lot of effort for me as an amateur to
> keeps this code up-to-date out-of-tree for months. It's not even
> sure that I've hit the mark with this, so there will most likely be
> changes when I start converting fbtft drivers, and I'll implement the
> remaining bits and pieces as I make changes. The core of tinydrm won't
> change because of unforseen fbtft quirks, but other parts might.

+1, pls send pull request to Dave Airlie. DT-acks are all there, and
Thierry confirmed on irc that his reply on the last thread was indeed
meant as a full ack for going ahead and polishing later. I also
chatted with Dave directly, and he's perfectly happy to take this in
as-is. If you do this soon, should still easily get into 4.11 (since
the 4.10 release is delayed).

Cheers, Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
  2017-02-11 18:48   ` Noralf Trønnes
@ 2017-03-07 22:21     ` Daniel Vetter
  -1 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-03-07 22:21 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, devicetree, thomas.petazzoni, linux-kernel

On Sat, Feb 11, 2017 at 07:48:52PM +0100, Noralf Trønnes wrote:
> +const struct file_operations tinydrm_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= drm_open,
> +	.release	= drm_release,
> +	.unlocked_ioctl	= drm_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl	= drm_compat_ioctl,
> +#endif
> +	.poll		= drm_poll,
> +	.read		= drm_read,
> +	.llseek		= no_llseek,
> +	.mmap		= drm_gem_cma_mmap,
> +};
> +EXPORT_SYMBOL(tinydrm_fops);

Just spotted this while doing a drive-by refactoring, this doesn't really
work. The THIS_MODULE must be in the source code for your driver, and
can't be in some helper library module like tinydrm.ko.

I'm working on a macro to make this easier, and I guess you could try to
integrate that into your driver macro somehow (probably simplest to
generate the entire structure and just pass name+desc to that macro,
everything else is boilerplate anyway).

I'll cc you on that patch.
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-03-07 22:21     ` Daniel Vetter
  0 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-03-07 22:21 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, devicetree, linux-kernel, dri-devel

On Sat, Feb 11, 2017 at 07:48:52PM +0100, Noralf Trønnes wrote:
> +const struct file_operations tinydrm_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= drm_open,
> +	.release	= drm_release,
> +	.unlocked_ioctl	= drm_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl	= drm_compat_ioctl,
> +#endif
> +	.poll		= drm_poll,
> +	.read		= drm_read,
> +	.llseek		= no_llseek,
> +	.mmap		= drm_gem_cma_mmap,
> +};
> +EXPORT_SYMBOL(tinydrm_fops);

Just spotted this while doing a drive-by refactoring, this doesn't really
work. The THIS_MODULE must be in the source code for your driver, and
can't be in some helper library module like tinydrm.ko.

I'm working on a macro to make this easier, and I guess you could try to
integrate that into your driver macro somehow (probably simplest to
generate the entire structure and just pass name+desc to that macro,
everything else is boilerplate anyway).

I'll cc you on that patch.
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
  2017-03-07 22:21     ` Daniel Vetter
@ 2017-03-08 12:23       ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-03-08 12:23 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, devicetree, thomas.petazzoni, linux-kernel


Den 07.03.2017 23.21, skrev Daniel Vetter:
> On Sat, Feb 11, 2017 at 07:48:52PM +0100, Noralf Trønnes wrote:
>> +const struct file_operations tinydrm_fops = {
>> +	.owner		= THIS_MODULE,
>> +	.open		= drm_open,
>> +	.release	= drm_release,
>> +	.unlocked_ioctl	= drm_ioctl,
>> +#ifdef CONFIG_COMPAT
>> +	.compat_ioctl	= drm_compat_ioctl,
>> +#endif
>> +	.poll		= drm_poll,
>> +	.read		= drm_read,
>> +	.llseek		= no_llseek,
>> +	.mmap		= drm_gem_cma_mmap,
>> +};
>> +EXPORT_SYMBOL(tinydrm_fops);
> Just spotted this while doing a drive-by refactoring, this doesn't really
> work. The THIS_MODULE must be in the source code for your driver, and
> can't be in some helper library module like tinydrm.ko.

Yeah, you're right.

> I'm working on a macro to make this easier, and I guess you could try to
> integrate that into your driver macro somehow (probably simplest to
> generate the entire structure and just pass name+desc to that macro,
> everything else is boilerplate anyway).
>
> I'll cc you on that patch.

Thanks.

Noralf.

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

* Re: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays
@ 2017-03-08 12:23       ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-03-08 12:23 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, devicetree, linux-kernel, dri-devel


Den 07.03.2017 23.21, skrev Daniel Vetter:
> On Sat, Feb 11, 2017 at 07:48:52PM +0100, Noralf Trønnes wrote:
>> +const struct file_operations tinydrm_fops = {
>> +	.owner		= THIS_MODULE,
>> +	.open		= drm_open,
>> +	.release	= drm_release,
>> +	.unlocked_ioctl	= drm_ioctl,
>> +#ifdef CONFIG_COMPAT
>> +	.compat_ioctl	= drm_compat_ioctl,
>> +#endif
>> +	.poll		= drm_poll,
>> +	.read		= drm_read,
>> +	.llseek		= no_llseek,
>> +	.mmap		= drm_gem_cma_mmap,
>> +};
>> +EXPORT_SYMBOL(tinydrm_fops);
> Just spotted this while doing a drive-by refactoring, this doesn't really
> work. The THIS_MODULE must be in the source code for your driver, and
> can't be in some helper library module like tinydrm.ko.

Yeah, you're right.

> I'm working on a macro to make this easier, and I guess you could try to
> integrate that into your driver macro somehow (probably simplest to
> generate the entire structure and just pass name+desc to that macro,
> everything else is boilerplate anyway).
>
> I'll cc you on that patch.

Thanks.

Noralf.

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

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

* Re: [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support
@ 2017-03-12 18:42     ` Daniel Vetter
  0 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-03-12 18:42 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: dri-devel, devicetree, thomas.petazzoni, linux-kernel

On Sat, Feb 11, 2017 at 07:48:54PM +0100, Noralf Trønnes wrote:
> +static int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
> +				struct drm_clip_rect *clip, bool swap)
> +{
> +	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
> +	struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
> +	struct drm_format_name_buf format_name;
> +	void *src = cma_obj->vaddr;
> +	int ret = 0;
> +
> +	if (import_attach) {
> +		ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
> +					       DMA_FROM_DEVICE);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	switch (fb->format->format) {
> +	case DRM_FORMAT_RGB565:
> +		if (swap)
> +			tinydrm_swab16(dst, src, fb, clip);
> +		else
> +			tinydrm_memcpy(dst, src, fb, clip);
> +		break;
> +	case DRM_FORMAT_XRGB8888:
> +		tinydrm_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
> +		break;

Already mentioned in my other mail that this here is kinda not cool. At
least I think we'll digg ourselves into a big hole if we start to convert
xrgb8888 into whatever the device actually supports in drm drivers. This
works for devices behind a slow upload link (because the cpu is likely
much faster), but for anything else userspace really must support the
native pixel format. And rgb565 is a rather common format, there should be
no reason to duct-tape around it.

> +	default:
> +		dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
> +			     drm_get_format_name(fb->format->format,
> +						 &format_name));
> +		return -EINVAL;
> +	}
> +
> +	if (import_attach)
> +		ret = dma_buf_end_cpu_access(import_attach->dmabuf,
> +					     DMA_FROM_DEVICE);
> +	return ret;
> +}
> +
> +static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
> +			     struct drm_file *file_priv,
> +			     unsigned int flags, unsigned int color,
> +			     struct drm_clip_rect *clips,
> +			     unsigned int num_clips)
> +{
> +	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
> +	struct tinydrm_device *tdev = fb->dev->dev_private;
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +	bool swap = mipi->swap_bytes;
> +	struct drm_clip_rect clip;
> +	int ret = 0;
> +	bool full;
> +	void *tr;
> +
> +	mutex_lock(&tdev->dirty_lock);
> +
> +	if (!mipi->enabled)
> +		goto out_unlock;

Here's the real reason I replied: I think long term-ish we need to rework
what you use as the main upload function in tinydrm. I realize just using
fb->dirty is convenient, because simple_pipe->upload doesn't take a dirty
rectangle. But long term I think we need to fix that, because otherwise
we'll reinvent the same few lines of boilerplate (like here and in the
other tinydrm drivers you have queued).

Rough sketch of a plan:

- Add a struct drm_rect dirty_clip to drm_crtc_state. When duplicating the
  crtc state, clear that to the max values, x/y = 0 and w/h = MAX_INT, in
  __drm_atomic_helper_crtc_duplicate_state().

> +
> +	/* fbdev can flush even when we're not interested */
> +	if (tdev->pipe.plane.fb != fb)
> +		goto out_unlock;
> +
> +	full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
> +				   fb->width, fb->height);

- Move tinydrm_merge_clips into drm_framebuffer.c, dropping the tinydrm_
  prefix ofc and using drm_fb_. drm_framebuffer.c makes sense since this
  is a function useful to implement the fb->dirty function.

- Create a new drm_fb_dirty function which does essentially what you do
  here, but generic. You can use e.g. drm_atomic_helper_update_plane as
  the template. But instead of doing a simple full-screen plane update,
  this new helper also sets crtc_state->dirty_clip to the right
  coordinates. And of course it needs to check whether the fb is actually
  active (and maybe where), so there's some book-keeping involved. There's
  also some good fun involved in scaling things appropriately. For that
  case we might simply give up and declare the entire area covered by the
  plane as dirty.

Tinydrm drivers (and really, anything using drm_simple_pipe) then just
implement the ->update hook, obeying the dirty_clip rectangle, and you can
remove tinydrm_display_pipe_update().

The upshot of all this is two-fold:

- You get rid of a tiny bit of book-keeping in each tinydrm driver (the
  merge_clips above, plus the "is this the current fb on our hw" check).

- There's lots more manual upload devices than just tinydrm, or
  drm_simple_pipe, this would benefit any atomic driver.

- Long-term we want to expose the dirty_clip as properties to userspace,
  since the compositor already computes this anyway for each atomic update
  (at least good compositors do this). Test-driving these new properties
  within the kernel would help in catching issues.

The only downside is that we won't have a clip rect list anymore, but no
one (except for vmwgfx) seems to use that anyway.
-Daniel

> +
> +	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
> +		  clip.x1, clip.x2, clip.y1, clip.y2);
> +
> +	if (!mipi->dc || !full || swap ||
> +	    fb->format->format == DRM_FORMAT_XRGB8888) {
> +		tr = mipi->tx_buf;
> +		ret = mipi_dbi_buf_copy(mipi->tx_buf, fb, &clip, swap);
> +		if (ret)
> +			goto out_unlock;
> +	} else {
> +		tr = cma_obj->vaddr;
> +	}
> +
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS,
> +			 (clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF,
> +			 (clip.x2 >> 8) & 0xFF, (clip.x2 - 1) & 0xFF);
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS,
> +			 (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF,
> +			 (clip.y2 >> 8) & 0xFF, (clip.y2 - 1) & 0xFF);
> +
> +	ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, tr,
> +				(clip.x2 - clip.x1) * (clip.y2 - clip.y1) * 2);
> +
> +out_unlock:
> +	mutex_unlock(&tdev->dirty_lock);
> +
> +	if (ret)
> +		dev_err_once(fb->dev->dev, "Failed to update display %d\n",
> +			     ret);
> +
> +	return ret;
> +}
> +
> +static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
> +	.destroy	= drm_fb_cma_destroy,
> +	.create_handle	= drm_fb_cma_create_handle,
> +	.dirty		= mipi_dbi_fb_dirty,
> +};
> +
> +/**
> + * mipi_dbi_pipe_enable - MIPI DBI pipe enable helper
> + * @pipe: Display pipe
> + * @crtc_state: CRTC state
> + *
> + * This function enables backlight. Drivers can use this as their
> + * &drm_simple_display_pipe_funcs->enable callback.
> + */
> +void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
> +			  struct drm_crtc_state *crtc_state)
> +{
> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +	struct drm_framebuffer *fb = pipe->plane.fb;
> +
> +	DRM_DEBUG_KMS("\n");
> +
> +	mipi->enabled = true;
> +	if (fb)
> +		fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
> +
> +	tinydrm_enable_backlight(mipi->backlight);
> +}
> +EXPORT_SYMBOL(mipi_dbi_pipe_enable);
> +
> +static void mipi_dbi_blank(struct mipi_dbi *mipi)
> +{
> +	struct drm_device *drm = mipi->tinydrm.drm;
> +	u16 height = drm->mode_config.min_height;
> +	u16 width = drm->mode_config.min_width;
> +	size_t len = width * height * 2;
> +
> +	memset(mipi->tx_buf, 0, len);
> +
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0,
> +			 (width >> 8) & 0xFF, (width - 1) & 0xFF);
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0,
> +			 (height >> 8) & 0xFF, (height - 1) & 0xFF);
> +	mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START,
> +			     (u8 *)mipi->tx_buf, len);
> +}
> +
> +/**
> + * mipi_dbi_pipe_disable - MIPI DBI pipe disable helper
> + * @pipe: Display pipe
> + *
> + * This function disables backlight if present or if not the
> + * display memory is blanked. Drivers can use this as their
> + * &drm_simple_display_pipe_funcs->disable callback.
> + */
> +void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe)
> +{
> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +
> +	DRM_DEBUG_KMS("\n");
> +
> +	mipi->enabled = false;
> +
> +	if (mipi->backlight)
> +		tinydrm_disable_backlight(mipi->backlight);
> +	else
> +		mipi_dbi_blank(mipi);
> +}
> +EXPORT_SYMBOL(mipi_dbi_pipe_disable);
> +
> +static const uint32_t mipi_dbi_formats[] = {
> +	DRM_FORMAT_RGB565,
> +	DRM_FORMAT_XRGB8888,
> +};
> +
> +/**
> + * mipi_dbi_init - MIPI DBI initialization
> + * @dev: Parent device
> + * @mipi: &mipi_dbi structure to initialize
> + * @pipe_funcs: Display pipe functions
> + * @driver: DRM driver
> + * @mode: Display mode
> + * @rotation: Initial rotation in degrees Counter Clock Wise
> + *
> + * This function initializes a &mipi_dbi structure and it's underlying
> + * @tinydrm_device. It also sets up the display pipeline.
> + *
> + * Supported formats: Native RGB565 and emulated XRGB8888.
> + *
> + * Objects created by this function will be automatically freed on driver
> + * detach (devres).
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
> +		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		  struct drm_driver *driver,
> +		  const struct drm_display_mode *mode, unsigned int rotation)
> +{
> +	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
> +	struct tinydrm_device *tdev = &mipi->tinydrm;
> +	int ret;
> +
> +	if (!mipi->command)
> +		return -EINVAL;
> +
> +	mutex_init(&mipi->cmdlock);
> +
> +	mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
> +	if (!mipi->tx_buf)
> +		return -ENOMEM;
> +
> +	ret = devm_tinydrm_init(dev, tdev, &mipi_dbi_fb_funcs, driver);
> +	if (ret)
> +		return ret;
> +
> +	/* TODO: Maybe add DRM_MODE_CONNECTOR_SPI */
> +	ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
> +					DRM_MODE_CONNECTOR_VIRTUAL,
> +					mipi_dbi_formats,
> +					ARRAY_SIZE(mipi_dbi_formats), mode,
> +					rotation);
> +	if (ret)
> +		return ret;
> +
> +	tdev->drm->mode_config.preferred_depth = 16;
> +	mipi->rotation = rotation;
> +
> +	drm_mode_config_reset(tdev->drm);
> +
> +	DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n",
> +		      tdev->drm->mode_config.preferred_depth, rotation);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(mipi_dbi_init);
> +
> +/**
> + * mipi_dbi_hw_reset - Hardware reset of controller
> + * @mipi: MIPI DBI structure
> + *
> + * Reset controller if the &mipi_dbi->reset gpio is set.
> + */
> +void mipi_dbi_hw_reset(struct mipi_dbi *mipi)
> +{
> +	if (!mipi->reset)
> +		return;
> +
> +	gpiod_set_value_cansleep(mipi->reset, 0);
> +	msleep(20);
> +	gpiod_set_value_cansleep(mipi->reset, 1);
> +	msleep(120);
> +}
> +EXPORT_SYMBOL(mipi_dbi_hw_reset);
> +
> +/**
> + * mipi_dbi_display_is_on - Check if display is on
> + * @mipi: MIPI DBI structure
> + *
> + * This function checks the Power Mode register (if readable) to see if
> + * display output is turned on. This can be used to see if the bootloader
> + * has already turned on the display avoiding flicker when the pipeline is
> + * enabled.
> + *
> + * Returns:
> + * true if the display can be verified to be on, false otherwise.
> + */
> +bool mipi_dbi_display_is_on(struct mipi_dbi *mipi)
> +{
> +	u8 val;
> +
> +	if (mipi_dbi_command_read(mipi, MIPI_DCS_GET_POWER_MODE, &val))
> +		return false;
> +
> +	val &= ~DCS_POWER_MODE_RESERVED_MASK;
> +
> +	if (val != (DCS_POWER_MODE_DISPLAY |
> +	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
> +		return false;
> +
> +	DRM_DEBUG_DRIVER("Display is ON\n");
> +
> +	return true;
> +}
> +EXPORT_SYMBOL(mipi_dbi_display_is_on);
> +
> +#if IS_ENABLED(CONFIG_SPI)
> +
> +/*
> + * Many controllers have a max speed of 10MHz, but can be pushed way beyond
> + * that. Increase reliability by running pixel data at max speed and the rest
> + * at 10MHz, preventing transfer glitches from messing up the init settings.
> + */
> +static u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len)
> +{
> +	if (len > 64)
> +		return 0; /* use default */
> +
> +	return min_t(u32, 10000000, spi->max_speed_hz);
> +}
> +
> +/*
> + * MIPI DBI Type C Option 1
> + *
> + * If the SPI controller doesn't have 9 bits per word support,
> + * use blocks of 9 bytes to send 8x 9-bit words using a 8-bit SPI transfer.
> + * Pad partial blocks with MIPI_DCS_NOP (zero).
> + * This is how the D/C bit (x) is added:
> + *     x7654321
> + *     0x765432
> + *     10x76543
> + *     210x7654
> + *     3210x765
> + *     43210x76
> + *     543210x7
> + *     6543210x
> + *     76543210
> + */
> +
> +static int mipi_dbi_spi1e_transfer(struct mipi_dbi *mipi, int dc,
> +				   const void *buf, size_t len,
> +				   unsigned int bpw)
> +{
> +	bool swap_bytes = (bpw == 16 && tinydrm_machine_little_endian());
> +	size_t chunk, max_chunk = mipi->tx_buf9_len;
> +	struct spi_device *spi = mipi->spi;
> +	struct spi_transfer tr = {
> +		.tx_buf = mipi->tx_buf9,
> +		.bits_per_word = 8,
> +	};
> +	struct spi_message m;
> +	const u8 *src = buf;
> +	int i, ret;
> +	u8 *dst;
> +
> +	if (drm_debug & DRM_UT_DRIVER)
> +		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
> +			 __func__, dc, max_chunk);
> +
> +	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
> +	spi_message_init_with_transfers(&m, &tr, 1);
> +
> +	if (!dc) {
> +		if (WARN_ON_ONCE(len != 1))
> +			return -EINVAL;
> +
> +		/* Command: pad no-op's (zeroes) at beginning of block */
> +		dst = mipi->tx_buf9;
> +		memset(dst, 0, 9);
> +		dst[8] = *src;
> +		tr.len = 9;
> +
> +		tinydrm_dbg_spi_message(spi, &m);
> +
> +		return spi_sync(spi, &m);
> +	}
> +
> +	/* max with room for adding one bit per byte */
> +	max_chunk = max_chunk / 9 * 8;
> +	/* but no bigger than len */
> +	max_chunk = min(max_chunk, len);
> +	/* 8 byte blocks */
> +	max_chunk = max_t(size_t, 8, max_chunk & ~0x7);
> +
> +	while (len) {
> +		size_t added = 0;
> +
> +		chunk = min(len, max_chunk);
> +		len -= chunk;
> +		dst = mipi->tx_buf9;
> +
> +		if (chunk < 8) {
> +			u8 val, carry = 0;
> +
> +			/* Data: pad no-op's (zeroes) at end of block */
> +			memset(dst, 0, 9);
> +
> +			if (swap_bytes) {
> +				for (i = 1; i < (chunk + 1); i++) {
> +					val = src[1];
> +					*dst++ = carry | BIT(8 - i) | (val >> i);
> +					carry = val << (8 - i);
> +					i++;
> +					val = src[0];
> +					*dst++ = carry | BIT(8 - i) | (val >> i);
> +					carry = val << (8 - i);
> +					src += 2;
> +				}
> +				*dst++ = carry;
> +			} else {
> +				for (i = 1; i < (chunk + 1); i++) {
> +					val = *src++;
> +					*dst++ = carry | BIT(8 - i) | (val >> i);
> +					carry = val << (8 - i);
> +				}
> +				*dst++ = carry;
> +			}
> +
> +			chunk = 8;
> +			added = 1;
> +		} else {
> +			for (i = 0; i < chunk; i += 8) {
> +				if (swap_bytes) {
> +					*dst++ =                 BIT(7) | (src[1] >> 1);
> +					*dst++ = (src[1] << 7) | BIT(6) | (src[0] >> 2);
> +					*dst++ = (src[0] << 6) | BIT(5) | (src[3] >> 3);
> +					*dst++ = (src[3] << 5) | BIT(4) | (src[2] >> 4);
> +					*dst++ = (src[2] << 4) | BIT(3) | (src[5] >> 5);
> +					*dst++ = (src[5] << 3) | BIT(2) | (src[4] >> 6);
> +					*dst++ = (src[4] << 2) | BIT(1) | (src[7] >> 7);
> +					*dst++ = (src[7] << 1) | BIT(0);
> +					*dst++ = src[6];
> +				} else {
> +					*dst++ =                 BIT(7) | (src[0] >> 1);
> +					*dst++ = (src[0] << 7) | BIT(6) | (src[1] >> 2);
> +					*dst++ = (src[1] << 6) | BIT(5) | (src[2] >> 3);
> +					*dst++ = (src[2] << 5) | BIT(4) | (src[3] >> 4);
> +					*dst++ = (src[3] << 4) | BIT(3) | (src[4] >> 5);
> +					*dst++ = (src[4] << 3) | BIT(2) | (src[5] >> 6);
> +					*dst++ = (src[5] << 2) | BIT(1) | (src[6] >> 7);
> +					*dst++ = (src[6] << 1) | BIT(0);
> +					*dst++ = src[7];
> +				}
> +
> +				src += 8;
> +				added++;
> +			}
> +		}
> +
> +		tr.len = chunk + added;
> +
> +		tinydrm_dbg_spi_message(spi, &m);
> +		ret = spi_sync(spi, &m);
> +		if (ret)
> +			return ret;
> +	};
> +
> +	return 0;
> +}
> +
> +static int mipi_dbi_spi1_transfer(struct mipi_dbi *mipi, int dc,
> +				  const void *buf, size_t len,
> +				  unsigned int bpw)
> +{
> +	struct spi_device *spi = mipi->spi;
> +	struct spi_transfer tr = {
> +		.bits_per_word = 9,
> +	};
> +	const u16 *src16 = buf;
> +	const u8 *src8 = buf;
> +	struct spi_message m;
> +	size_t max_chunk;
> +	u16 *dst16;
> +	int ret;
> +
> +	if (!tinydrm_spi_bpw_supported(spi, 9))
> +		return mipi_dbi_spi1e_transfer(mipi, dc, buf, len, bpw);
> +
> +	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
> +	max_chunk = mipi->tx_buf9_len;
> +	dst16 = mipi->tx_buf9;
> +
> +	if (drm_debug & DRM_UT_DRIVER)
> +		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
> +			 __func__, dc, max_chunk);
> +
> +	max_chunk = min(max_chunk / 2, len);
> +
> +	spi_message_init_with_transfers(&m, &tr, 1);
> +	tr.tx_buf = dst16;
> +
> +	while (len) {
> +		size_t chunk = min(len, max_chunk);
> +		unsigned int i;
> +
> +		if (bpw == 16 && tinydrm_machine_little_endian()) {
> +			for (i = 0; i < (chunk * 2); i += 2) {
> +				dst16[i]     = *src16 >> 8;
> +				dst16[i + 1] = *src16++ & 0xFF;
> +				if (dc) {
> +					dst16[i]     |= 0x0100;
> +					dst16[i + 1] |= 0x0100;
> +				}
> +			}
> +		} else {
> +			for (i = 0; i < chunk; i++) {
> +				dst16[i] = *src8++;
> +				if (dc)
> +					dst16[i] |= 0x0100;
> +			}
> +		}
> +
> +		tr.len = chunk;
> +		len -= chunk;
> +
> +		tinydrm_dbg_spi_message(spi, &m);
> +		ret = spi_sync(spi, &m);
> +		if (ret)
> +			return ret;
> +	};
> +
> +	return 0;
> +}
> +
> +static int mipi_dbi_typec1_command(struct mipi_dbi *mipi, u8 cmd,
> +				   u8 *parameters, size_t num)
> +{
> +	unsigned int bpw = (cmd == MIPI_DCS_WRITE_MEMORY_START) ? 16 : 8;
> +	int ret;
> +
> +	if (mipi_dbi_command_is_read(mipi, cmd))
> +		return -ENOTSUPP;
> +
> +	MIPI_DBI_DEBUG_COMMAND(cmd, parameters, num);
> +
> +	ret = mipi_dbi_spi1_transfer(mipi, 0, &cmd, 1, 8);
> +	if (ret || !num)
> +		return ret;
> +
> +	return mipi_dbi_spi1_transfer(mipi, 1, parameters, num, bpw);
> +}
> +
> +/* MIPI DBI Type C Option 3 */
> +
> +static int mipi_dbi_typec3_command_read(struct mipi_dbi *mipi, u8 cmd,
> +					u8 *data, size_t len)
> +{
> +	struct spi_device *spi = mipi->spi;
> +	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED,
> +			     spi->max_speed_hz / 2);
> +	struct spi_transfer tr[2] = {
> +		{
> +			.speed_hz = speed_hz,
> +			.tx_buf = &cmd,
> +			.len = 1,
> +		}, {
> +			.speed_hz = speed_hz,
> +			.len = len,
> +		},
> +	};
> +	struct spi_message m;
> +	u8 *buf;
> +	int ret;
> +
> +	if (!len)
> +		return -EINVAL;
> +
> +	/*
> +	 * Support non-standard 24-bit and 32-bit Nokia read commands which
> +	 * start with a dummy clock, so we need to read an extra byte.
> +	 */
> +	if (cmd == MIPI_DCS_GET_DISPLAY_ID ||
> +	    cmd == MIPI_DCS_GET_DISPLAY_STATUS) {
> +		if (!(len == 3 || len == 4))
> +			return -EINVAL;
> +
> +		tr[1].len = len + 1;
> +	}
> +
> +	buf = kmalloc(tr[1].len, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	tr[1].rx_buf = buf;
> +	gpiod_set_value_cansleep(mipi->dc, 0);
> +
> +	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr));
> +	ret = spi_sync(spi, &m);
> +	if (ret)
> +		goto err_free;
> +
> +	tinydrm_dbg_spi_message(spi, &m);
> +
> +	if (tr[1].len == len) {
> +		memcpy(data, buf, len);
> +	} else {
> +		unsigned int i;
> +
> +		for (i = 0; i < len; i++)
> +			data[i] = (buf[i] << 1) | !!(buf[i + 1] & BIT(7));
> +	}
> +
> +	MIPI_DBI_DEBUG_COMMAND(cmd, data, len);
> +
> +err_free:
> +	kfree(buf);
> +
> +	return ret;
> +}
> +
> +static int mipi_dbi_typec3_command(struct mipi_dbi *mipi, u8 cmd,
> +				   u8 *par, size_t num)
> +{
> +	struct spi_device *spi = mipi->spi;
> +	unsigned int bpw = 8;
> +	u32 speed_hz;
> +	int ret;
> +
> +	if (mipi_dbi_command_is_read(mipi, cmd))
> +		return mipi_dbi_typec3_command_read(mipi, cmd, par, num);
> +
> +	MIPI_DBI_DEBUG_COMMAND(cmd, par, num);
> +
> +	gpiod_set_value_cansleep(mipi->dc, 0);
> +	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
> +	ret = tinydrm_spi_transfer(spi, speed_hz, NULL, 8, &cmd, 1);
> +	if (ret || !num)
> +		return ret;
> +
> +	if (cmd == MIPI_DCS_WRITE_MEMORY_START && !mipi->swap_bytes)
> +		bpw = 16;
> +
> +	gpiod_set_value_cansleep(mipi->dc, 1);
> +	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
> +
> +	return tinydrm_spi_transfer(spi, speed_hz, NULL, bpw, par, num);
> +}
> +
> +/**
> + * mipi_dbi_spi_init - Initialize MIPI DBI SPI interfaced controller
> + * @spi: SPI device
> + * @dc: D/C gpio (optional)
> + * @mipi: &mipi_dbi structure to initialize
> + * @pipe_funcs: Display pipe functions
> + * @driver: DRM driver
> + * @mode: Display mode
> + * @rotation: Initial rotation in degrees Counter Clock Wise
> + *
> + * This function sets &mipi_dbi->command, enables &mipi->read_commands for the
> + * usual read commands and initializes @mipi using mipi_dbi_init().
> + *
> + * If @dc is set, a Type C Option 3 interface is assumed, if not
> + * Type C Option 1.
> + *
> + * If the SPI master driver doesn't support the necessary bits per word,
> + * the following transformation is used:
> + *
> + * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
> + * - 16-bit: if big endian send as 8-bit, if little endian swap bytes
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
> +		      struct gpio_desc *dc,
> +		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		      struct drm_driver *driver,
> +		      const struct drm_display_mode *mode,
> +		      unsigned int rotation)
> +{
> +	size_t tx_size = tinydrm_spi_max_transfer_size(spi, 0);
> +	struct device *dev = &spi->dev;
> +	int ret;
> +
> +	if (tx_size < 16) {
> +		DRM_ERROR("SPI transmit buffer too small: %zu\n", tx_size);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Even though it's not the SPI device that does DMA (the master does),
> +	 * the dma mask is necessary for the dma_alloc_wc() in
> +	 * drm_gem_cma_create(). The dma_addr returned will be a physical
> +	 * adddress which might be different from the bus address, but this is
> +	 * not a problem since the address will not be used.
> +	 * The virtual address is used in the transfer and the SPI core
> +	 * re-maps it on the SPI master device using the DMA streaming API
> +	 * (spi_map_buf()).
> +	 */
> +	if (!dev->coherent_dma_mask) {
> +		ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
> +		if (ret) {
> +			dev_warn(dev, "Failed to set dma mask %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	mipi->spi = spi;
> +	mipi->read_commands = mipi_dbi_dcs_read_commands;
> +
> +	if (dc) {
> +		mipi->command = mipi_dbi_typec3_command;
> +		mipi->dc = dc;
> +		if (tinydrm_machine_little_endian() &&
> +		    !tinydrm_spi_bpw_supported(spi, 16))
> +			mipi->swap_bytes = true;
> +	} else {
> +		mipi->command = mipi_dbi_typec1_command;
> +		mipi->tx_buf9_len = tx_size;
> +		mipi->tx_buf9 = devm_kmalloc(dev, tx_size, GFP_KERNEL);
> +		if (!mipi->tx_buf9)
> +			return -ENOMEM;
> +	}
> +
> +	return mipi_dbi_init(dev, mipi, pipe_funcs, driver, mode, rotation);
> +}
> +EXPORT_SYMBOL(mipi_dbi_spi_init);
> +
> +#endif /* CONFIG_SPI */
> +
> +#ifdef CONFIG_DEBUG_FS
> +
> +static ssize_t mipi_dbi_debugfs_command_write(struct file *file,
> +					      const char __user *ubuf,
> +					      size_t count, loff_t *ppos)
> +{
> +	struct seq_file *m = file->private_data;
> +	struct mipi_dbi *mipi = m->private;
> +	u8 val, cmd, parameters[64];
> +	char *buf, *pos, *token;
> +	unsigned int i;
> +	int ret;
> +
> +	buf = memdup_user_nul(ubuf, count);
> +	if (IS_ERR(buf))
> +		return PTR_ERR(buf);
> +
> +	/* strip trailing whitespace */
> +	for (i = count - 1; i > 0; i--)
> +		if (isspace(buf[i]))
> +			buf[i] = '\0';
> +		else
> +			break;
> +	i = 0;
> +	pos = buf;
> +	while (pos) {
> +		token = strsep(&pos, " ");
> +		if (!token) {
> +			ret = -EINVAL;
> +			goto err_free;
> +		}
> +
> +		ret = kstrtou8(token, 16, &val);
> +		if (ret < 0)
> +			goto err_free;
> +
> +		if (token == buf)
> +			cmd = val;
> +		else
> +			parameters[i++] = val;
> +
> +		if (i == 64) {
> +			ret = -E2BIG;
> +			goto err_free;
> +		}
> +	}
> +
> +	ret = mipi_dbi_command_buf(mipi, cmd, parameters, i);
> +
> +err_free:
> +	kfree(buf);
> +
> +	return ret < 0 ? ret : count;
> +}
> +
> +static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused)
> +{
> +	struct mipi_dbi *mipi = m->private;
> +	u8 cmd, val[4];
> +	size_t len, i;
> +	int ret;
> +
> +	for (cmd = 0; cmd < 255; cmd++) {
> +		if (!mipi_dbi_command_is_read(mipi, cmd))
> +			continue;
> +
> +		switch (cmd) {
> +		case MIPI_DCS_READ_MEMORY_START:
> +		case MIPI_DCS_READ_MEMORY_CONTINUE:
> +			len = 2;
> +			break;
> +		case MIPI_DCS_GET_DISPLAY_ID:
> +			len = 3;
> +			break;
> +		case MIPI_DCS_GET_DISPLAY_STATUS:
> +			len = 4;
> +			break;
> +		default:
> +			len = 1;
> +			break;
> +		}
> +
> +		seq_printf(m, "%02x: ", cmd);
> +		ret = mipi_dbi_command_buf(mipi, cmd, val, len);
> +		if (ret) {
> +			seq_puts(m, "XX\n");
> +			continue;
> +		}
> +
> +		for (i = 0; i < len; i++)
> +			seq_printf(m, "%02x", val[i]);
> +		seq_puts(m, "\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static int mipi_dbi_debugfs_command_open(struct inode *inode,
> +					 struct file *file)
> +{
> +	return single_open(file, mipi_dbi_debugfs_command_show,
> +			   inode->i_private);
> +}
> +
> +static const struct file_operations mipi_dbi_debugfs_command_fops = {
> +	.owner = THIS_MODULE,
> +	.open = mipi_dbi_debugfs_command_open,
> +	.read = seq_read,
> +	.llseek = seq_lseek,
> +	.release = single_release,
> +	.write = mipi_dbi_debugfs_command_write,
> +};
> +
> +static const struct drm_info_list mipi_dbi_debugfs_list[] = {
> +	{ "fb",   drm_fb_cma_debugfs_show, 0 },
> +};
> +
> +/**
> + * mipi_dbi_debugfs_init - Create debugfs entries
> + * @minor: DRM minor
> + *
> + * This function creates a 'command' debugfs file for sending commands to the
> + * controller or getting the read command values.
> + * Drivers can use this as their &drm_driver->debugfs_init callback.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int mipi_dbi_debugfs_init(struct drm_minor *minor)
> +{
> +	struct tinydrm_device *tdev = minor->dev->dev_private;
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +	umode_t mode = S_IFREG | S_IWUSR;
> +
> +	if (mipi->read_commands)
> +		mode |= S_IRUGO;
> +	debugfs_create_file("command", mode, minor->debugfs_root, mipi,
> +			    &mipi_dbi_debugfs_command_fops);
> +
> +	return drm_debugfs_create_files(mipi_dbi_debugfs_list,
> +					ARRAY_SIZE(mipi_dbi_debugfs_list),
> +					minor->debugfs_root, minor);
> +}
> +EXPORT_SYMBOL(mipi_dbi_debugfs_init);
> +
> +#endif
> +
> +MODULE_LICENSE("GPL");
> diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h
> new file mode 100644
> index 0000000..d137b16
> --- /dev/null
> +++ b/include/drm/tinydrm/mipi-dbi.h
> @@ -0,0 +1,107 @@
> +/*
> + * MIPI Display Bus Interface (DBI) LCD controller support
> + *
> + * Copyright 2016 Noralf Trønnes
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef __LINUX_MIPI_DBI_H
> +#define __LINUX_MIPI_DBI_H
> +
> +#include <drm/tinydrm/tinydrm.h>
> +
> +struct spi_device;
> +struct gpio_desc;
> +struct regulator;
> +
> +/**
> + * struct mipi_dbi - MIPI DBI controller
> + * @tinydrm: tinydrm base
> + * @spi: SPI device
> + * @enabled: Pipeline is enabled
> + * @cmdlock: Command lock
> + * @command: Bus specific callback executing commands.
> + * @read_commands: Array of read commands terminated by a zero entry.
> + *                 Reading is disabled if this is NULL.
> + * @dc: Optional D/C gpio.
> + * @tx_buf: Buffer used for transfer (copy clip rect area)
> + * @tx_buf9: Buffer used for Option 1 9-bit conversion
> + * @tx_buf9_len: Size of tx_buf9.
> + * @swap_bytes: Swap bytes in buffer before transfer
> + * @reset: Optional reset gpio
> + * @rotation: initial rotation in degrees Counter Clock Wise
> + * @backlight: backlight device (optional)
> + * @regulator: power regulator (optional)
> + */
> +struct mipi_dbi {
> +	struct tinydrm_device tinydrm;
> +	struct spi_device *spi;
> +	bool enabled;
> +	struct mutex cmdlock;
> +	int (*command)(struct mipi_dbi *mipi, u8 cmd, u8 *param, size_t num);
> +	const u8 *read_commands;
> +	struct gpio_desc *dc;
> +	u16 *tx_buf;
> +	void *tx_buf9;
> +	size_t tx_buf9_len;
> +	bool swap_bytes;
> +	struct gpio_desc *reset;
> +	unsigned int rotation;
> +	struct backlight_device *backlight;
> +	struct regulator *regulator;
> +};
> +
> +static inline struct mipi_dbi *
> +mipi_dbi_from_tinydrm(struct tinydrm_device *tdev)
> +{
> +	return container_of(tdev, struct mipi_dbi, tinydrm);
> +}
> +
> +int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
> +		      struct gpio_desc *dc,
> +		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		      struct drm_driver *driver,
> +		      const struct drm_display_mode *mode,
> +		      unsigned int rotation);
> +int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
> +		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		  struct drm_driver *driver,
> +		  const struct drm_display_mode *mode, unsigned int rotation);
> +void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
> +			  struct drm_crtc_state *crtc_state);
> +void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe);
> +void mipi_dbi_hw_reset(struct mipi_dbi *mipi);
> +bool mipi_dbi_display_is_on(struct mipi_dbi *mipi);
> +
> +int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val);
> +int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len);
> +
> +/**
> + * mipi_dbi_command - MIPI DCS command with optional parameter(s)
> + * @mipi: MIPI structure
> + * @cmd: Command
> + * @seq...: Optional parameter(s)
> + *
> + * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
> + * get/read.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +#define mipi_dbi_command(mipi, cmd, seq...) \
> +({ \
> +	u8 d[] = { seq }; \
> +	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
> +})
> +
> +#ifdef CONFIG_DEBUG_FS
> +int mipi_dbi_debugfs_init(struct drm_minor *minor);
> +#else
> +#define mipi_dbi_debugfs_init		NULL
> +#endif
> +
> +#endif /* __LINUX_MIPI_DBI_H */
> --
> 2.10.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch

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

* Re: [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support
@ 2017-03-12 18:42     ` Daniel Vetter
  0 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-03-12 18:42 UTC (permalink / raw)
  To: Noralf Trønnes
  Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	thomas.petazzoni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Sat, Feb 11, 2017 at 07:48:54PM +0100, Noralf Trønnes wrote:
> +static int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
> +				struct drm_clip_rect *clip, bool swap)
> +{
> +	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
> +	struct dma_buf_attachment *import_attach = cma_obj->base.import_attach;
> +	struct drm_format_name_buf format_name;
> +	void *src = cma_obj->vaddr;
> +	int ret = 0;
> +
> +	if (import_attach) {
> +		ret = dma_buf_begin_cpu_access(import_attach->dmabuf,
> +					       DMA_FROM_DEVICE);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	switch (fb->format->format) {
> +	case DRM_FORMAT_RGB565:
> +		if (swap)
> +			tinydrm_swab16(dst, src, fb, clip);
> +		else
> +			tinydrm_memcpy(dst, src, fb, clip);
> +		break;
> +	case DRM_FORMAT_XRGB8888:
> +		tinydrm_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
> +		break;

Already mentioned in my other mail that this here is kinda not cool. At
least I think we'll digg ourselves into a big hole if we start to convert
xrgb8888 into whatever the device actually supports in drm drivers. This
works for devices behind a slow upload link (because the cpu is likely
much faster), but for anything else userspace really must support the
native pixel format. And rgb565 is a rather common format, there should be
no reason to duct-tape around it.

> +	default:
> +		dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
> +			     drm_get_format_name(fb->format->format,
> +						 &format_name));
> +		return -EINVAL;
> +	}
> +
> +	if (import_attach)
> +		ret = dma_buf_end_cpu_access(import_attach->dmabuf,
> +					     DMA_FROM_DEVICE);
> +	return ret;
> +}
> +
> +static int mipi_dbi_fb_dirty(struct drm_framebuffer *fb,
> +			     struct drm_file *file_priv,
> +			     unsigned int flags, unsigned int color,
> +			     struct drm_clip_rect *clips,
> +			     unsigned int num_clips)
> +{
> +	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
> +	struct tinydrm_device *tdev = fb->dev->dev_private;
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +	bool swap = mipi->swap_bytes;
> +	struct drm_clip_rect clip;
> +	int ret = 0;
> +	bool full;
> +	void *tr;
> +
> +	mutex_lock(&tdev->dirty_lock);
> +
> +	if (!mipi->enabled)
> +		goto out_unlock;

Here's the real reason I replied: I think long term-ish we need to rework
what you use as the main upload function in tinydrm. I realize just using
fb->dirty is convenient, because simple_pipe->upload doesn't take a dirty
rectangle. But long term I think we need to fix that, because otherwise
we'll reinvent the same few lines of boilerplate (like here and in the
other tinydrm drivers you have queued).

Rough sketch of a plan:

- Add a struct drm_rect dirty_clip to drm_crtc_state. When duplicating the
  crtc state, clear that to the max values, x/y = 0 and w/h = MAX_INT, in
  __drm_atomic_helper_crtc_duplicate_state().

> +
> +	/* fbdev can flush even when we're not interested */
> +	if (tdev->pipe.plane.fb != fb)
> +		goto out_unlock;
> +
> +	full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
> +				   fb->width, fb->height);

- Move tinydrm_merge_clips into drm_framebuffer.c, dropping the tinydrm_
  prefix ofc and using drm_fb_. drm_framebuffer.c makes sense since this
  is a function useful to implement the fb->dirty function.

- Create a new drm_fb_dirty function which does essentially what you do
  here, but generic. You can use e.g. drm_atomic_helper_update_plane as
  the template. But instead of doing a simple full-screen plane update,
  this new helper also sets crtc_state->dirty_clip to the right
  coordinates. And of course it needs to check whether the fb is actually
  active (and maybe where), so there's some book-keeping involved. There's
  also some good fun involved in scaling things appropriately. For that
  case we might simply give up and declare the entire area covered by the
  plane as dirty.

Tinydrm drivers (and really, anything using drm_simple_pipe) then just
implement the ->update hook, obeying the dirty_clip rectangle, and you can
remove tinydrm_display_pipe_update().

The upshot of all this is two-fold:

- You get rid of a tiny bit of book-keeping in each tinydrm driver (the
  merge_clips above, plus the "is this the current fb on our hw" check).

- There's lots more manual upload devices than just tinydrm, or
  drm_simple_pipe, this would benefit any atomic driver.

- Long-term we want to expose the dirty_clip as properties to userspace,
  since the compositor already computes this anyway for each atomic update
  (at least good compositors do this). Test-driving these new properties
  within the kernel would help in catching issues.

The only downside is that we won't have a clip rect list anymore, but no
one (except for vmwgfx) seems to use that anyway.
-Daniel

> +
> +	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
> +		  clip.x1, clip.x2, clip.y1, clip.y2);
> +
> +	if (!mipi->dc || !full || swap ||
> +	    fb->format->format == DRM_FORMAT_XRGB8888) {
> +		tr = mipi->tx_buf;
> +		ret = mipi_dbi_buf_copy(mipi->tx_buf, fb, &clip, swap);
> +		if (ret)
> +			goto out_unlock;
> +	} else {
> +		tr = cma_obj->vaddr;
> +	}
> +
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS,
> +			 (clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF,
> +			 (clip.x2 >> 8) & 0xFF, (clip.x2 - 1) & 0xFF);
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS,
> +			 (clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF,
> +			 (clip.y2 >> 8) & 0xFF, (clip.y2 - 1) & 0xFF);
> +
> +	ret = mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START, tr,
> +				(clip.x2 - clip.x1) * (clip.y2 - clip.y1) * 2);
> +
> +out_unlock:
> +	mutex_unlock(&tdev->dirty_lock);
> +
> +	if (ret)
> +		dev_err_once(fb->dev->dev, "Failed to update display %d\n",
> +			     ret);
> +
> +	return ret;
> +}
> +
> +static const struct drm_framebuffer_funcs mipi_dbi_fb_funcs = {
> +	.destroy	= drm_fb_cma_destroy,
> +	.create_handle	= drm_fb_cma_create_handle,
> +	.dirty		= mipi_dbi_fb_dirty,
> +};
> +
> +/**
> + * mipi_dbi_pipe_enable - MIPI DBI pipe enable helper
> + * @pipe: Display pipe
> + * @crtc_state: CRTC state
> + *
> + * This function enables backlight. Drivers can use this as their
> + * &drm_simple_display_pipe_funcs->enable callback.
> + */
> +void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
> +			  struct drm_crtc_state *crtc_state)
> +{
> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +	struct drm_framebuffer *fb = pipe->plane.fb;
> +
> +	DRM_DEBUG_KMS("\n");
> +
> +	mipi->enabled = true;
> +	if (fb)
> +		fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0);
> +
> +	tinydrm_enable_backlight(mipi->backlight);
> +}
> +EXPORT_SYMBOL(mipi_dbi_pipe_enable);
> +
> +static void mipi_dbi_blank(struct mipi_dbi *mipi)
> +{
> +	struct drm_device *drm = mipi->tinydrm.drm;
> +	u16 height = drm->mode_config.min_height;
> +	u16 width = drm->mode_config.min_width;
> +	size_t len = width * height * 2;
> +
> +	memset(mipi->tx_buf, 0, len);
> +
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_COLUMN_ADDRESS, 0, 0,
> +			 (width >> 8) & 0xFF, (width - 1) & 0xFF);
> +	mipi_dbi_command(mipi, MIPI_DCS_SET_PAGE_ADDRESS, 0, 0,
> +			 (height >> 8) & 0xFF, (height - 1) & 0xFF);
> +	mipi_dbi_command_buf(mipi, MIPI_DCS_WRITE_MEMORY_START,
> +			     (u8 *)mipi->tx_buf, len);
> +}
> +
> +/**
> + * mipi_dbi_pipe_disable - MIPI DBI pipe disable helper
> + * @pipe: Display pipe
> + *
> + * This function disables backlight if present or if not the
> + * display memory is blanked. Drivers can use this as their
> + * &drm_simple_display_pipe_funcs->disable callback.
> + */
> +void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe)
> +{
> +	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +
> +	DRM_DEBUG_KMS("\n");
> +
> +	mipi->enabled = false;
> +
> +	if (mipi->backlight)
> +		tinydrm_disable_backlight(mipi->backlight);
> +	else
> +		mipi_dbi_blank(mipi);
> +}
> +EXPORT_SYMBOL(mipi_dbi_pipe_disable);
> +
> +static const uint32_t mipi_dbi_formats[] = {
> +	DRM_FORMAT_RGB565,
> +	DRM_FORMAT_XRGB8888,
> +};
> +
> +/**
> + * mipi_dbi_init - MIPI DBI initialization
> + * @dev: Parent device
> + * @mipi: &mipi_dbi structure to initialize
> + * @pipe_funcs: Display pipe functions
> + * @driver: DRM driver
> + * @mode: Display mode
> + * @rotation: Initial rotation in degrees Counter Clock Wise
> + *
> + * This function initializes a &mipi_dbi structure and it's underlying
> + * @tinydrm_device. It also sets up the display pipeline.
> + *
> + * Supported formats: Native RGB565 and emulated XRGB8888.
> + *
> + * Objects created by this function will be automatically freed on driver
> + * detach (devres).
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
> +		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		  struct drm_driver *driver,
> +		  const struct drm_display_mode *mode, unsigned int rotation)
> +{
> +	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
> +	struct tinydrm_device *tdev = &mipi->tinydrm;
> +	int ret;
> +
> +	if (!mipi->command)
> +		return -EINVAL;
> +
> +	mutex_init(&mipi->cmdlock);
> +
> +	mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
> +	if (!mipi->tx_buf)
> +		return -ENOMEM;
> +
> +	ret = devm_tinydrm_init(dev, tdev, &mipi_dbi_fb_funcs, driver);
> +	if (ret)
> +		return ret;
> +
> +	/* TODO: Maybe add DRM_MODE_CONNECTOR_SPI */
> +	ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
> +					DRM_MODE_CONNECTOR_VIRTUAL,
> +					mipi_dbi_formats,
> +					ARRAY_SIZE(mipi_dbi_formats), mode,
> +					rotation);
> +	if (ret)
> +		return ret;
> +
> +	tdev->drm->mode_config.preferred_depth = 16;
> +	mipi->rotation = rotation;
> +
> +	drm_mode_config_reset(tdev->drm);
> +
> +	DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n",
> +		      tdev->drm->mode_config.preferred_depth, rotation);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(mipi_dbi_init);
> +
> +/**
> + * mipi_dbi_hw_reset - Hardware reset of controller
> + * @mipi: MIPI DBI structure
> + *
> + * Reset controller if the &mipi_dbi->reset gpio is set.
> + */
> +void mipi_dbi_hw_reset(struct mipi_dbi *mipi)
> +{
> +	if (!mipi->reset)
> +		return;
> +
> +	gpiod_set_value_cansleep(mipi->reset, 0);
> +	msleep(20);
> +	gpiod_set_value_cansleep(mipi->reset, 1);
> +	msleep(120);
> +}
> +EXPORT_SYMBOL(mipi_dbi_hw_reset);
> +
> +/**
> + * mipi_dbi_display_is_on - Check if display is on
> + * @mipi: MIPI DBI structure
> + *
> + * This function checks the Power Mode register (if readable) to see if
> + * display output is turned on. This can be used to see if the bootloader
> + * has already turned on the display avoiding flicker when the pipeline is
> + * enabled.
> + *
> + * Returns:
> + * true if the display can be verified to be on, false otherwise.
> + */
> +bool mipi_dbi_display_is_on(struct mipi_dbi *mipi)
> +{
> +	u8 val;
> +
> +	if (mipi_dbi_command_read(mipi, MIPI_DCS_GET_POWER_MODE, &val))
> +		return false;
> +
> +	val &= ~DCS_POWER_MODE_RESERVED_MASK;
> +
> +	if (val != (DCS_POWER_MODE_DISPLAY |
> +	    DCS_POWER_MODE_DISPLAY_NORMAL_MODE | DCS_POWER_MODE_SLEEP_MODE))
> +		return false;
> +
> +	DRM_DEBUG_DRIVER("Display is ON\n");
> +
> +	return true;
> +}
> +EXPORT_SYMBOL(mipi_dbi_display_is_on);
> +
> +#if IS_ENABLED(CONFIG_SPI)
> +
> +/*
> + * Many controllers have a max speed of 10MHz, but can be pushed way beyond
> + * that. Increase reliability by running pixel data at max speed and the rest
> + * at 10MHz, preventing transfer glitches from messing up the init settings.
> + */
> +static u32 mipi_dbi_spi_cmd_max_speed(struct spi_device *spi, size_t len)
> +{
> +	if (len > 64)
> +		return 0; /* use default */
> +
> +	return min_t(u32, 10000000, spi->max_speed_hz);
> +}
> +
> +/*
> + * MIPI DBI Type C Option 1
> + *
> + * If the SPI controller doesn't have 9 bits per word support,
> + * use blocks of 9 bytes to send 8x 9-bit words using a 8-bit SPI transfer.
> + * Pad partial blocks with MIPI_DCS_NOP (zero).
> + * This is how the D/C bit (x) is added:
> + *     x7654321
> + *     0x765432
> + *     10x76543
> + *     210x7654
> + *     3210x765
> + *     43210x76
> + *     543210x7
> + *     6543210x
> + *     76543210
> + */
> +
> +static int mipi_dbi_spi1e_transfer(struct mipi_dbi *mipi, int dc,
> +				   const void *buf, size_t len,
> +				   unsigned int bpw)
> +{
> +	bool swap_bytes = (bpw == 16 && tinydrm_machine_little_endian());
> +	size_t chunk, max_chunk = mipi->tx_buf9_len;
> +	struct spi_device *spi = mipi->spi;
> +	struct spi_transfer tr = {
> +		.tx_buf = mipi->tx_buf9,
> +		.bits_per_word = 8,
> +	};
> +	struct spi_message m;
> +	const u8 *src = buf;
> +	int i, ret;
> +	u8 *dst;
> +
> +	if (drm_debug & DRM_UT_DRIVER)
> +		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
> +			 __func__, dc, max_chunk);
> +
> +	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
> +	spi_message_init_with_transfers(&m, &tr, 1);
> +
> +	if (!dc) {
> +		if (WARN_ON_ONCE(len != 1))
> +			return -EINVAL;
> +
> +		/* Command: pad no-op's (zeroes) at beginning of block */
> +		dst = mipi->tx_buf9;
> +		memset(dst, 0, 9);
> +		dst[8] = *src;
> +		tr.len = 9;
> +
> +		tinydrm_dbg_spi_message(spi, &m);
> +
> +		return spi_sync(spi, &m);
> +	}
> +
> +	/* max with room for adding one bit per byte */
> +	max_chunk = max_chunk / 9 * 8;
> +	/* but no bigger than len */
> +	max_chunk = min(max_chunk, len);
> +	/* 8 byte blocks */
> +	max_chunk = max_t(size_t, 8, max_chunk & ~0x7);
> +
> +	while (len) {
> +		size_t added = 0;
> +
> +		chunk = min(len, max_chunk);
> +		len -= chunk;
> +		dst = mipi->tx_buf9;
> +
> +		if (chunk < 8) {
> +			u8 val, carry = 0;
> +
> +			/* Data: pad no-op's (zeroes) at end of block */
> +			memset(dst, 0, 9);
> +
> +			if (swap_bytes) {
> +				for (i = 1; i < (chunk + 1); i++) {
> +					val = src[1];
> +					*dst++ = carry | BIT(8 - i) | (val >> i);
> +					carry = val << (8 - i);
> +					i++;
> +					val = src[0];
> +					*dst++ = carry | BIT(8 - i) | (val >> i);
> +					carry = val << (8 - i);
> +					src += 2;
> +				}
> +				*dst++ = carry;
> +			} else {
> +				for (i = 1; i < (chunk + 1); i++) {
> +					val = *src++;
> +					*dst++ = carry | BIT(8 - i) | (val >> i);
> +					carry = val << (8 - i);
> +				}
> +				*dst++ = carry;
> +			}
> +
> +			chunk = 8;
> +			added = 1;
> +		} else {
> +			for (i = 0; i < chunk; i += 8) {
> +				if (swap_bytes) {
> +					*dst++ =                 BIT(7) | (src[1] >> 1);
> +					*dst++ = (src[1] << 7) | BIT(6) | (src[0] >> 2);
> +					*dst++ = (src[0] << 6) | BIT(5) | (src[3] >> 3);
> +					*dst++ = (src[3] << 5) | BIT(4) | (src[2] >> 4);
> +					*dst++ = (src[2] << 4) | BIT(3) | (src[5] >> 5);
> +					*dst++ = (src[5] << 3) | BIT(2) | (src[4] >> 6);
> +					*dst++ = (src[4] << 2) | BIT(1) | (src[7] >> 7);
> +					*dst++ = (src[7] << 1) | BIT(0);
> +					*dst++ = src[6];
> +				} else {
> +					*dst++ =                 BIT(7) | (src[0] >> 1);
> +					*dst++ = (src[0] << 7) | BIT(6) | (src[1] >> 2);
> +					*dst++ = (src[1] << 6) | BIT(5) | (src[2] >> 3);
> +					*dst++ = (src[2] << 5) | BIT(4) | (src[3] >> 4);
> +					*dst++ = (src[3] << 4) | BIT(3) | (src[4] >> 5);
> +					*dst++ = (src[4] << 3) | BIT(2) | (src[5] >> 6);
> +					*dst++ = (src[5] << 2) | BIT(1) | (src[6] >> 7);
> +					*dst++ = (src[6] << 1) | BIT(0);
> +					*dst++ = src[7];
> +				}
> +
> +				src += 8;
> +				added++;
> +			}
> +		}
> +
> +		tr.len = chunk + added;
> +
> +		tinydrm_dbg_spi_message(spi, &m);
> +		ret = spi_sync(spi, &m);
> +		if (ret)
> +			return ret;
> +	};
> +
> +	return 0;
> +}
> +
> +static int mipi_dbi_spi1_transfer(struct mipi_dbi *mipi, int dc,
> +				  const void *buf, size_t len,
> +				  unsigned int bpw)
> +{
> +	struct spi_device *spi = mipi->spi;
> +	struct spi_transfer tr = {
> +		.bits_per_word = 9,
> +	};
> +	const u16 *src16 = buf;
> +	const u8 *src8 = buf;
> +	struct spi_message m;
> +	size_t max_chunk;
> +	u16 *dst16;
> +	int ret;
> +
> +	if (!tinydrm_spi_bpw_supported(spi, 9))
> +		return mipi_dbi_spi1e_transfer(mipi, dc, buf, len, bpw);
> +
> +	tr.speed_hz = mipi_dbi_spi_cmd_max_speed(spi, len);
> +	max_chunk = mipi->tx_buf9_len;
> +	dst16 = mipi->tx_buf9;
> +
> +	if (drm_debug & DRM_UT_DRIVER)
> +		pr_debug("[drm:%s] dc=%d, max_chunk=%zu, transfers:\n",
> +			 __func__, dc, max_chunk);
> +
> +	max_chunk = min(max_chunk / 2, len);
> +
> +	spi_message_init_with_transfers(&m, &tr, 1);
> +	tr.tx_buf = dst16;
> +
> +	while (len) {
> +		size_t chunk = min(len, max_chunk);
> +		unsigned int i;
> +
> +		if (bpw == 16 && tinydrm_machine_little_endian()) {
> +			for (i = 0; i < (chunk * 2); i += 2) {
> +				dst16[i]     = *src16 >> 8;
> +				dst16[i + 1] = *src16++ & 0xFF;
> +				if (dc) {
> +					dst16[i]     |= 0x0100;
> +					dst16[i + 1] |= 0x0100;
> +				}
> +			}
> +		} else {
> +			for (i = 0; i < chunk; i++) {
> +				dst16[i] = *src8++;
> +				if (dc)
> +					dst16[i] |= 0x0100;
> +			}
> +		}
> +
> +		tr.len = chunk;
> +		len -= chunk;
> +
> +		tinydrm_dbg_spi_message(spi, &m);
> +		ret = spi_sync(spi, &m);
> +		if (ret)
> +			return ret;
> +	};
> +
> +	return 0;
> +}
> +
> +static int mipi_dbi_typec1_command(struct mipi_dbi *mipi, u8 cmd,
> +				   u8 *parameters, size_t num)
> +{
> +	unsigned int bpw = (cmd == MIPI_DCS_WRITE_MEMORY_START) ? 16 : 8;
> +	int ret;
> +
> +	if (mipi_dbi_command_is_read(mipi, cmd))
> +		return -ENOTSUPP;
> +
> +	MIPI_DBI_DEBUG_COMMAND(cmd, parameters, num);
> +
> +	ret = mipi_dbi_spi1_transfer(mipi, 0, &cmd, 1, 8);
> +	if (ret || !num)
> +		return ret;
> +
> +	return mipi_dbi_spi1_transfer(mipi, 1, parameters, num, bpw);
> +}
> +
> +/* MIPI DBI Type C Option 3 */
> +
> +static int mipi_dbi_typec3_command_read(struct mipi_dbi *mipi, u8 cmd,
> +					u8 *data, size_t len)
> +{
> +	struct spi_device *spi = mipi->spi;
> +	u32 speed_hz = min_t(u32, MIPI_DBI_MAX_SPI_READ_SPEED,
> +			     spi->max_speed_hz / 2);
> +	struct spi_transfer tr[2] = {
> +		{
> +			.speed_hz = speed_hz,
> +			.tx_buf = &cmd,
> +			.len = 1,
> +		}, {
> +			.speed_hz = speed_hz,
> +			.len = len,
> +		},
> +	};
> +	struct spi_message m;
> +	u8 *buf;
> +	int ret;
> +
> +	if (!len)
> +		return -EINVAL;
> +
> +	/*
> +	 * Support non-standard 24-bit and 32-bit Nokia read commands which
> +	 * start with a dummy clock, so we need to read an extra byte.
> +	 */
> +	if (cmd == MIPI_DCS_GET_DISPLAY_ID ||
> +	    cmd == MIPI_DCS_GET_DISPLAY_STATUS) {
> +		if (!(len == 3 || len == 4))
> +			return -EINVAL;
> +
> +		tr[1].len = len + 1;
> +	}
> +
> +	buf = kmalloc(tr[1].len, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	tr[1].rx_buf = buf;
> +	gpiod_set_value_cansleep(mipi->dc, 0);
> +
> +	spi_message_init_with_transfers(&m, tr, ARRAY_SIZE(tr));
> +	ret = spi_sync(spi, &m);
> +	if (ret)
> +		goto err_free;
> +
> +	tinydrm_dbg_spi_message(spi, &m);
> +
> +	if (tr[1].len == len) {
> +		memcpy(data, buf, len);
> +	} else {
> +		unsigned int i;
> +
> +		for (i = 0; i < len; i++)
> +			data[i] = (buf[i] << 1) | !!(buf[i + 1] & BIT(7));
> +	}
> +
> +	MIPI_DBI_DEBUG_COMMAND(cmd, data, len);
> +
> +err_free:
> +	kfree(buf);
> +
> +	return ret;
> +}
> +
> +static int mipi_dbi_typec3_command(struct mipi_dbi *mipi, u8 cmd,
> +				   u8 *par, size_t num)
> +{
> +	struct spi_device *spi = mipi->spi;
> +	unsigned int bpw = 8;
> +	u32 speed_hz;
> +	int ret;
> +
> +	if (mipi_dbi_command_is_read(mipi, cmd))
> +		return mipi_dbi_typec3_command_read(mipi, cmd, par, num);
> +
> +	MIPI_DBI_DEBUG_COMMAND(cmd, par, num);
> +
> +	gpiod_set_value_cansleep(mipi->dc, 0);
> +	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
> +	ret = tinydrm_spi_transfer(spi, speed_hz, NULL, 8, &cmd, 1);
> +	if (ret || !num)
> +		return ret;
> +
> +	if (cmd == MIPI_DCS_WRITE_MEMORY_START && !mipi->swap_bytes)
> +		bpw = 16;
> +
> +	gpiod_set_value_cansleep(mipi->dc, 1);
> +	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
> +
> +	return tinydrm_spi_transfer(spi, speed_hz, NULL, bpw, par, num);
> +}
> +
> +/**
> + * mipi_dbi_spi_init - Initialize MIPI DBI SPI interfaced controller
> + * @spi: SPI device
> + * @dc: D/C gpio (optional)
> + * @mipi: &mipi_dbi structure to initialize
> + * @pipe_funcs: Display pipe functions
> + * @driver: DRM driver
> + * @mode: Display mode
> + * @rotation: Initial rotation in degrees Counter Clock Wise
> + *
> + * This function sets &mipi_dbi->command, enables &mipi->read_commands for the
> + * usual read commands and initializes @mipi using mipi_dbi_init().
> + *
> + * If @dc is set, a Type C Option 3 interface is assumed, if not
> + * Type C Option 1.
> + *
> + * If the SPI master driver doesn't support the necessary bits per word,
> + * the following transformation is used:
> + *
> + * - 9-bit: reorder buffer as 9x 8-bit words, padded with no-op command.
> + * - 16-bit: if big endian send as 8-bit, if little endian swap bytes
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
> +		      struct gpio_desc *dc,
> +		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		      struct drm_driver *driver,
> +		      const struct drm_display_mode *mode,
> +		      unsigned int rotation)
> +{
> +	size_t tx_size = tinydrm_spi_max_transfer_size(spi, 0);
> +	struct device *dev = &spi->dev;
> +	int ret;
> +
> +	if (tx_size < 16) {
> +		DRM_ERROR("SPI transmit buffer too small: %zu\n", tx_size);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Even though it's not the SPI device that does DMA (the master does),
> +	 * the dma mask is necessary for the dma_alloc_wc() in
> +	 * drm_gem_cma_create(). The dma_addr returned will be a physical
> +	 * adddress which might be different from the bus address, but this is
> +	 * not a problem since the address will not be used.
> +	 * The virtual address is used in the transfer and the SPI core
> +	 * re-maps it on the SPI master device using the DMA streaming API
> +	 * (spi_map_buf()).
> +	 */
> +	if (!dev->coherent_dma_mask) {
> +		ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
> +		if (ret) {
> +			dev_warn(dev, "Failed to set dma mask %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	mipi->spi = spi;
> +	mipi->read_commands = mipi_dbi_dcs_read_commands;
> +
> +	if (dc) {
> +		mipi->command = mipi_dbi_typec3_command;
> +		mipi->dc = dc;
> +		if (tinydrm_machine_little_endian() &&
> +		    !tinydrm_spi_bpw_supported(spi, 16))
> +			mipi->swap_bytes = true;
> +	} else {
> +		mipi->command = mipi_dbi_typec1_command;
> +		mipi->tx_buf9_len = tx_size;
> +		mipi->tx_buf9 = devm_kmalloc(dev, tx_size, GFP_KERNEL);
> +		if (!mipi->tx_buf9)
> +			return -ENOMEM;
> +	}
> +
> +	return mipi_dbi_init(dev, mipi, pipe_funcs, driver, mode, rotation);
> +}
> +EXPORT_SYMBOL(mipi_dbi_spi_init);
> +
> +#endif /* CONFIG_SPI */
> +
> +#ifdef CONFIG_DEBUG_FS
> +
> +static ssize_t mipi_dbi_debugfs_command_write(struct file *file,
> +					      const char __user *ubuf,
> +					      size_t count, loff_t *ppos)
> +{
> +	struct seq_file *m = file->private_data;
> +	struct mipi_dbi *mipi = m->private;
> +	u8 val, cmd, parameters[64];
> +	char *buf, *pos, *token;
> +	unsigned int i;
> +	int ret;
> +
> +	buf = memdup_user_nul(ubuf, count);
> +	if (IS_ERR(buf))
> +		return PTR_ERR(buf);
> +
> +	/* strip trailing whitespace */
> +	for (i = count - 1; i > 0; i--)
> +		if (isspace(buf[i]))
> +			buf[i] = '\0';
> +		else
> +			break;
> +	i = 0;
> +	pos = buf;
> +	while (pos) {
> +		token = strsep(&pos, " ");
> +		if (!token) {
> +			ret = -EINVAL;
> +			goto err_free;
> +		}
> +
> +		ret = kstrtou8(token, 16, &val);
> +		if (ret < 0)
> +			goto err_free;
> +
> +		if (token == buf)
> +			cmd = val;
> +		else
> +			parameters[i++] = val;
> +
> +		if (i == 64) {
> +			ret = -E2BIG;
> +			goto err_free;
> +		}
> +	}
> +
> +	ret = mipi_dbi_command_buf(mipi, cmd, parameters, i);
> +
> +err_free:
> +	kfree(buf);
> +
> +	return ret < 0 ? ret : count;
> +}
> +
> +static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused)
> +{
> +	struct mipi_dbi *mipi = m->private;
> +	u8 cmd, val[4];
> +	size_t len, i;
> +	int ret;
> +
> +	for (cmd = 0; cmd < 255; cmd++) {
> +		if (!mipi_dbi_command_is_read(mipi, cmd))
> +			continue;
> +
> +		switch (cmd) {
> +		case MIPI_DCS_READ_MEMORY_START:
> +		case MIPI_DCS_READ_MEMORY_CONTINUE:
> +			len = 2;
> +			break;
> +		case MIPI_DCS_GET_DISPLAY_ID:
> +			len = 3;
> +			break;
> +		case MIPI_DCS_GET_DISPLAY_STATUS:
> +			len = 4;
> +			break;
> +		default:
> +			len = 1;
> +			break;
> +		}
> +
> +		seq_printf(m, "%02x: ", cmd);
> +		ret = mipi_dbi_command_buf(mipi, cmd, val, len);
> +		if (ret) {
> +			seq_puts(m, "XX\n");
> +			continue;
> +		}
> +
> +		for (i = 0; i < len; i++)
> +			seq_printf(m, "%02x", val[i]);
> +		seq_puts(m, "\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static int mipi_dbi_debugfs_command_open(struct inode *inode,
> +					 struct file *file)
> +{
> +	return single_open(file, mipi_dbi_debugfs_command_show,
> +			   inode->i_private);
> +}
> +
> +static const struct file_operations mipi_dbi_debugfs_command_fops = {
> +	.owner = THIS_MODULE,
> +	.open = mipi_dbi_debugfs_command_open,
> +	.read = seq_read,
> +	.llseek = seq_lseek,
> +	.release = single_release,
> +	.write = mipi_dbi_debugfs_command_write,
> +};
> +
> +static const struct drm_info_list mipi_dbi_debugfs_list[] = {
> +	{ "fb",   drm_fb_cma_debugfs_show, 0 },
> +};
> +
> +/**
> + * mipi_dbi_debugfs_init - Create debugfs entries
> + * @minor: DRM minor
> + *
> + * This function creates a 'command' debugfs file for sending commands to the
> + * controller or getting the read command values.
> + * Drivers can use this as their &drm_driver->debugfs_init callback.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +int mipi_dbi_debugfs_init(struct drm_minor *minor)
> +{
> +	struct tinydrm_device *tdev = minor->dev->dev_private;
> +	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
> +	umode_t mode = S_IFREG | S_IWUSR;
> +
> +	if (mipi->read_commands)
> +		mode |= S_IRUGO;
> +	debugfs_create_file("command", mode, minor->debugfs_root, mipi,
> +			    &mipi_dbi_debugfs_command_fops);
> +
> +	return drm_debugfs_create_files(mipi_dbi_debugfs_list,
> +					ARRAY_SIZE(mipi_dbi_debugfs_list),
> +					minor->debugfs_root, minor);
> +}
> +EXPORT_SYMBOL(mipi_dbi_debugfs_init);
> +
> +#endif
> +
> +MODULE_LICENSE("GPL");
> diff --git a/include/drm/tinydrm/mipi-dbi.h b/include/drm/tinydrm/mipi-dbi.h
> new file mode 100644
> index 0000000..d137b16
> --- /dev/null
> +++ b/include/drm/tinydrm/mipi-dbi.h
> @@ -0,0 +1,107 @@
> +/*
> + * MIPI Display Bus Interface (DBI) LCD controller support
> + *
> + * Copyright 2016 Noralf Trønnes
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef __LINUX_MIPI_DBI_H
> +#define __LINUX_MIPI_DBI_H
> +
> +#include <drm/tinydrm/tinydrm.h>
> +
> +struct spi_device;
> +struct gpio_desc;
> +struct regulator;
> +
> +/**
> + * struct mipi_dbi - MIPI DBI controller
> + * @tinydrm: tinydrm base
> + * @spi: SPI device
> + * @enabled: Pipeline is enabled
> + * @cmdlock: Command lock
> + * @command: Bus specific callback executing commands.
> + * @read_commands: Array of read commands terminated by a zero entry.
> + *                 Reading is disabled if this is NULL.
> + * @dc: Optional D/C gpio.
> + * @tx_buf: Buffer used for transfer (copy clip rect area)
> + * @tx_buf9: Buffer used for Option 1 9-bit conversion
> + * @tx_buf9_len: Size of tx_buf9.
> + * @swap_bytes: Swap bytes in buffer before transfer
> + * @reset: Optional reset gpio
> + * @rotation: initial rotation in degrees Counter Clock Wise
> + * @backlight: backlight device (optional)
> + * @regulator: power regulator (optional)
> + */
> +struct mipi_dbi {
> +	struct tinydrm_device tinydrm;
> +	struct spi_device *spi;
> +	bool enabled;
> +	struct mutex cmdlock;
> +	int (*command)(struct mipi_dbi *mipi, u8 cmd, u8 *param, size_t num);
> +	const u8 *read_commands;
> +	struct gpio_desc *dc;
> +	u16 *tx_buf;
> +	void *tx_buf9;
> +	size_t tx_buf9_len;
> +	bool swap_bytes;
> +	struct gpio_desc *reset;
> +	unsigned int rotation;
> +	struct backlight_device *backlight;
> +	struct regulator *regulator;
> +};
> +
> +static inline struct mipi_dbi *
> +mipi_dbi_from_tinydrm(struct tinydrm_device *tdev)
> +{
> +	return container_of(tdev, struct mipi_dbi, tinydrm);
> +}
> +
> +int mipi_dbi_spi_init(struct spi_device *spi, struct mipi_dbi *mipi,
> +		      struct gpio_desc *dc,
> +		      const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		      struct drm_driver *driver,
> +		      const struct drm_display_mode *mode,
> +		      unsigned int rotation);
> +int mipi_dbi_init(struct device *dev, struct mipi_dbi *mipi,
> +		  const struct drm_simple_display_pipe_funcs *pipe_funcs,
> +		  struct drm_driver *driver,
> +		  const struct drm_display_mode *mode, unsigned int rotation);
> +void mipi_dbi_pipe_enable(struct drm_simple_display_pipe *pipe,
> +			  struct drm_crtc_state *crtc_state);
> +void mipi_dbi_pipe_disable(struct drm_simple_display_pipe *pipe);
> +void mipi_dbi_hw_reset(struct mipi_dbi *mipi);
> +bool mipi_dbi_display_is_on(struct mipi_dbi *mipi);
> +
> +int mipi_dbi_command_read(struct mipi_dbi *mipi, u8 cmd, u8 *val);
> +int mipi_dbi_command_buf(struct mipi_dbi *mipi, u8 cmd, u8 *data, size_t len);
> +
> +/**
> + * mipi_dbi_command - MIPI DCS command with optional parameter(s)
> + * @mipi: MIPI structure
> + * @cmd: Command
> + * @seq...: Optional parameter(s)
> + *
> + * Send MIPI DCS command to the controller. Use mipi_dbi_command_read() for
> + * get/read.
> + *
> + * Returns:
> + * Zero on success, negative error code on failure.
> + */
> +#define mipi_dbi_command(mipi, cmd, seq...) \
> +({ \
> +	u8 d[] = { seq }; \
> +	mipi_dbi_command_buf(mipi, cmd, d, ARRAY_SIZE(d)); \
> +})
> +
> +#ifdef CONFIG_DEBUG_FS
> +int mipi_dbi_debugfs_init(struct drm_minor *minor);
> +#else
> +#define mipi_dbi_debugfs_init		NULL
> +#endif
> +
> +#endif /* __LINUX_MIPI_DBI_H */
> --
> 2.10.2
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support
@ 2017-03-12 19:02       ` Daniel Vetter
  0 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-03-12 19:02 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel, devicetree, Thomas Petazzoni,
	Linux Kernel Mailing List

On Sun, Mar 12, 2017 at 7:42 PM, Daniel Vetter <daniel@ffwll.ch> wrote:
>
> The upshot of all this is two-fold:
>
> - You get rid of a tiny bit of book-keeping in each tinydrm driver (the
>   merge_clips above, plus the "is this the current fb on our hw" check).
>
> - There's lots more manual upload devices than just tinydrm, or
>   drm_simple_pipe, this would benefit any atomic driver.
>
> - Long-term we want to expose the dirty_clip as properties to userspace,
>   since the compositor already computes this anyway for each atomic update
>   (at least good compositors do this). Test-driving these new properties
>   within the kernel would help in catching issues.

- Correct locking: taking the ->dirty_lock only protects against
concurrent other ->dirty or simple_pipe->update operations, it doesn't
protect against concurrent modeset changes (i.e. someone could disable
the panel while you try to upload stuff). Moving the locking out of
panel drivers into this one single core helper removes the need for
any locking in drivers. And no need for locking == no way to screw it
up :-)
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

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

* Re: [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support
@ 2017-03-12 19:02       ` Daniel Vetter
  0 siblings, 0 replies; 40+ messages in thread
From: Daniel Vetter @ 2017-03-12 19:02 UTC (permalink / raw)
  To: Noralf Trønnes, dri-devel,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Thomas Petazzoni,
	Linux Kernel Mailing List

On Sun, Mar 12, 2017 at 7:42 PM, Daniel Vetter <daniel-/w4YWyX8dFk@public.gmane.org> wrote:
>
> The upshot of all this is two-fold:
>
> - You get rid of a tiny bit of book-keeping in each tinydrm driver (the
>   merge_clips above, plus the "is this the current fb on our hw" check).
>
> - There's lots more manual upload devices than just tinydrm, or
>   drm_simple_pipe, this would benefit any atomic driver.
>
> - Long-term we want to expose the dirty_clip as properties to userspace,
>   since the compositor already computes this anyway for each atomic update
>   (at least good compositors do this). Test-driving these new properties
>   within the kernel would help in catching issues.

- Correct locking: taking the ->dirty_lock only protects against
concurrent other ->dirty or simple_pipe->update operations, it doesn't
protect against concurrent modeset changes (i.e. someone could disable
the panel while you try to upload stuff). Moving the locking out of
panel drivers into this one single core helper removes the need for
any locking in drivers. And no need for locking == no way to screw it
up :-)
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
@ 2017-04-11  5:29     ` Laurent Pinchart
  0 siblings, 0 replies; 40+ messages in thread
From: Laurent Pinchart @ 2017-04-11  5:29 UTC (permalink / raw)
  To: dri-devel; +Cc: Noralf Trønnes, devicetree, thomas.petazzoni, linux-kernel

Hi Noralf,

On Saturday 11 Feb 2017 19:48:56 Noralf Trønnes wrote:
> Display panels can be oriented many ways, especially in the embedded
> world. The rotation property is a way to describe this orientation.
> The counter clockwise direction is chosen because that's what fbdev
> and drm use.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> Acked-by: Thierry Reding <treding@nvidia.com>
> ---
> 
> Changes since version 3:
> - Changed display/display.txt -> display/panel/panel.txt
> 
>  Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++

We now have both Documentation/devicetree/bindings/display/panel/panel.txt and 
Documentation/devicetree/bindings/display/panel/panel-common.txt as they have 
been merged concurrently without being aware of each other. Would you mind if 
I moved this property to panel-common.txt ?

I would also like to document the property in a bit more details to avoid 
confusion about the rotation direction, as "display rotation" could be 
interpreted as the angle by which the display output has to be rotated to 
obtain an upside-up image. What would you think of the following, am I 
overdoing it, or is it even more confusing ?

- rotation: Panels are commonly mounted rotated, with their native orientation 
not aligned with the device's orientation. The rotation property specifies the 
angle in degrees between the panel's orientation and the device's orientation 
(corresponding to a counter-clockwise rotation of the panel). Valid values are 
0, 90, 180 and 270.


>  1 file changed, 4 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/display/panel/panel.txt
> 
> diff --git a/Documentation/devicetree/bindings/display/panel/panel.txt
> b/Documentation/devicetree/bindings/display/panel/panel.txt new file mode
> 100644
> index 0000000..e2e6867
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/panel.txt
> @@ -0,0 +1,4 @@
> +Common display properties
> +-------------------------
> +
> +- rotation:	Display rotation in degrees counter clockwise (0,90,180,270)

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
@ 2017-04-11  5:29     ` Laurent Pinchart
  0 siblings, 0 replies; 40+ messages in thread
From: Laurent Pinchart @ 2017-04-11  5:29 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: Noralf Trønnes, devicetree-u79uwXL29TY76Z2rM5mHXA,
	thomas.petazzoni-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

Hi Noralf,

On Saturday 11 Feb 2017 19:48:56 Noralf Trønnes wrote:
> Display panels can be oriented many ways, especially in the embedded
> world. The rotation property is a way to describe this orientation.
> The counter clockwise direction is chosen because that's what fbdev
> and drm use.
> 
> Signed-off-by: Noralf Trønnes <noralf-L59+Z2yzLopAfugRpC6u6w@public.gmane.org>
> Acked-by: Thierry Reding <treding-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
> ---
> 
> Changes since version 3:
> - Changed display/display.txt -> display/panel/panel.txt
> 
>  Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++

We now have both Documentation/devicetree/bindings/display/panel/panel.txt and 
Documentation/devicetree/bindings/display/panel/panel-common.txt as they have 
been merged concurrently without being aware of each other. Would you mind if 
I moved this property to panel-common.txt ?

I would also like to document the property in a bit more details to avoid 
confusion about the rotation direction, as "display rotation" could be 
interpreted as the angle by which the display output has to be rotated to 
obtain an upside-up image. What would you think of the following, am I 
overdoing it, or is it even more confusing ?

- rotation: Panels are commonly mounted rotated, with their native orientation 
not aligned with the device's orientation. The rotation property specifies the 
angle in degrees between the panel's orientation and the device's orientation 
(corresponding to a counter-clockwise rotation of the panel). Valid values are 
0, 90, 180 and 270.


>  1 file changed, 4 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/display/panel/panel.txt
> 
> diff --git a/Documentation/devicetree/bindings/display/panel/panel.txt
> b/Documentation/devicetree/bindings/display/panel/panel.txt new file mode
> 100644
> index 0000000..e2e6867
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/panel.txt
> @@ -0,0 +1,4 @@
> +Common display properties
> +-------------------------
> +
> +- rotation:	Display rotation in degrees counter clockwise (0,90,180,270)

-- 
Regards,

Laurent Pinchart

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

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

* Re: [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
  2017-04-11  5:29     ` Laurent Pinchart
@ 2017-04-12 11:01       ` Noralf Trønnes
  -1 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-04-12 11:01 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: devicetree, thomas.petazzoni, linux-kernel


Den 11.04.2017 07.29, skrev Laurent Pinchart:
> Hi Noralf,
>
> On Saturday 11 Feb 2017 19:48:56 Noralf Trønnes wrote:
>> Display panels can be oriented many ways, especially in the embedded
>> world. The rotation property is a way to describe this orientation.
>> The counter clockwise direction is chosen because that's what fbdev
>> and drm use.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> Acked-by: Thierry Reding <treding@nvidia.com>
>> ---
>>
>> Changes since version 3:
>> - Changed display/display.txt -> display/panel/panel.txt
>>
>>   Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++
> We now have both Documentation/devicetree/bindings/display/panel/panel.txt and
> Documentation/devicetree/bindings/display/panel/panel-common.txt as they have
> been merged concurrently without being aware of each other. Would you mind if
> I moved this property to panel-common.txt ?

Not at all, please do.

> I would also like to document the property in a bit more details to avoid
> confusion about the rotation direction, as "display rotation" could be
> interpreted as the angle by which the display output has to be rotated to
> obtain an upside-up image. What would you think of the following, am I
> overdoing it, or is it even more confusing ?

I was more confused at least :-)
It's not important to me what the actual wording is, but I see that I
have used the word 'Display' which should have been 'Panel'.

Noralf.

> - rotation: Panels are commonly mounted rotated, with their native orientation
> not aligned with the device's orientation. The rotation property specifies the
> angle in degrees between the panel's orientation and the device's orientation
> (corresponding to a counter-clockwise rotation of the panel). Valid values are
> 0, 90, 180 and 270.
>
>
>>   1 file changed, 4 insertions(+)
>>   create mode 100644
>> Documentation/devicetree/bindings/display/panel/panel.txt
>>
>> diff --git a/Documentation/devicetree/bindings/display/panel/panel.txt
>> b/Documentation/devicetree/bindings/display/panel/panel.txt new file mode
>> 100644
>> index 0000000..e2e6867
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/display/panel/panel.txt
>> @@ -0,0 +1,4 @@
>> +Common display properties
>> +-------------------------
>> +
>> +- rotation:	Display rotation in degrees counter clockwise (0,90,180,270)

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

* Re: [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property
@ 2017-04-12 11:01       ` Noralf Trønnes
  0 siblings, 0 replies; 40+ messages in thread
From: Noralf Trønnes @ 2017-04-12 11:01 UTC (permalink / raw)
  To: Laurent Pinchart, dri-devel; +Cc: thomas.petazzoni, devicetree, linux-kernel


Den 11.04.2017 07.29, skrev Laurent Pinchart:
> Hi Noralf,
>
> On Saturday 11 Feb 2017 19:48:56 Noralf Trønnes wrote:
>> Display panels can be oriented many ways, especially in the embedded
>> world. The rotation property is a way to describe this orientation.
>> The counter clockwise direction is chosen because that's what fbdev
>> and drm use.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>> Acked-by: Thierry Reding <treding@nvidia.com>
>> ---
>>
>> Changes since version 3:
>> - Changed display/display.txt -> display/panel/panel.txt
>>
>>   Documentation/devicetree/bindings/display/panel/panel.txt | 4 ++++
> We now have both Documentation/devicetree/bindings/display/panel/panel.txt and
> Documentation/devicetree/bindings/display/panel/panel-common.txt as they have
> been merged concurrently without being aware of each other. Would you mind if
> I moved this property to panel-common.txt ?

Not at all, please do.

> I would also like to document the property in a bit more details to avoid
> confusion about the rotation direction, as "display rotation" could be
> interpreted as the angle by which the display output has to be rotated to
> obtain an upside-up image. What would you think of the following, am I
> overdoing it, or is it even more confusing ?

I was more confused at least :-)
It's not important to me what the actual wording is, but I see that I
have used the word 'Display' which should have been 'Panel'.

Noralf.

> - rotation: Panels are commonly mounted rotated, with their native orientation
> not aligned with the device's orientation. The rotation property specifies the
> angle in degrees between the panel's orientation and the device's orientation
> (corresponding to a counter-clockwise rotation of the panel). Valid values are
> 0, 90, 180 and 270.
>
>
>>   1 file changed, 4 insertions(+)
>>   create mode 100644
>> Documentation/devicetree/bindings/display/panel/panel.txt
>>
>> diff --git a/Documentation/devicetree/bindings/display/panel/panel.txt
>> b/Documentation/devicetree/bindings/display/panel/panel.txt new file mode
>> 100644
>> index 0000000..e2e6867
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/display/panel/panel.txt
>> @@ -0,0 +1,4 @@
>> +Common display properties
>> +-------------------------
>> +
>> +- rotation:	Display rotation in degrees counter clockwise (0,90,180,270)

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

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

end of thread, other threads:[~2017-04-12 11:01 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-11 18:48 [PATCH v4 0/7] drm: Add support for tiny LCD displays Noralf Trønnes
2017-02-11 18:48 ` Noralf Trønnes
2017-02-11 18:48 ` [PATCH v4 1/7] drm: Add DRM " Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes
2017-02-12 11:05   ` Andy Shevchenko
2017-02-12 11:05     ` Andy Shevchenko
2017-02-12 13:57     ` Noralf Trønnes
2017-02-12 13:57       ` Noralf Trønnes
2017-02-14 19:54       ` Daniel Vetter
2017-02-14 19:54         ` Daniel Vetter
2017-03-07 22:21   ` Daniel Vetter
2017-03-07 22:21     ` Daniel Vetter
2017-03-08 12:23     ` Noralf Trønnes
2017-03-08 12:23       ` Noralf Trønnes
2017-02-11 18:48 ` [PATCH v4 2/7] drm/tinydrm: Add helper functions Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes
2017-02-12 11:50   ` Andy Shevchenko
2017-02-12 11:50     ` Andy Shevchenko
2017-02-12 15:33     ` Noralf Trønnes
2017-02-12 15:33       ` Noralf Trønnes
2017-02-11 18:48 ` [PATCH v4 3/7] drm/tinydrm: Add MIPI DBI support Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes
2017-03-12 18:42   ` Daniel Vetter
2017-03-12 18:42     ` Daniel Vetter
2017-03-12 19:02     ` Daniel Vetter
2017-03-12 19:02       ` Daniel Vetter
2017-02-11 18:48 ` [PATCH v4 4/7] of: Add vendor prefix for Multi-Inno Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes
2017-02-11 18:48 ` [PATCH v4 5/7] dt-bindings: display/panel: Add common rotation property Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes
2017-02-14 16:19   ` Rob Herring
2017-02-14 16:19     ` Rob Herring
2017-04-11  5:29   ` Laurent Pinchart
2017-04-11  5:29     ` Laurent Pinchart
2017-04-12 11:01     ` Noralf Trønnes
2017-04-12 11:01       ` Noralf Trønnes
2017-02-11 18:48 ` [PATCH v4 6/7] dt-bindings: Add Multi-Inno MI0283QT binding Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes
2017-02-11 18:48 ` [PATCH v4 7/7] drm/tinydrm: Add support for Multi-Inno MI0283QT display Noralf Trønnes
2017-02-11 18:48   ` Noralf Trønnes

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.