All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v2 0/8] drm: Add support for tiny LCD displays
@ 2016-04-08 17:05 Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
                   ` (8 more replies)
  0 siblings, 9 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

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

I'm sending this early before cleaning up the code hoping to get some
feedback in case there are better ways to structure this.

The tinydrm module provides a very simplified view of DRM for displays that
has onboard video memory and is connected through a slow bus like SPI/I2C.
A driver using tinydrm has to provide a function that will be called from
the dirtyfb ioctl (or fbdev deferred io) and optional drm_panel functions
which can be used to set up the display controller and control backlight.

Display controllers that have a similar register interface as the MIPI DBI/DCS
controllers can use the lcdreg module for register access.

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 (8):
  drm/fb-helper: Add fb_deferred_io support
  drm/fb-cma-helper: Add fb_deferred_io support
  drm: Add helper for simple kms drivers
  drm: Add DRM support for tiny LCD displays
  drm/tinydrm: Add lcd register abstraction
  drm/tinydrm/lcdreg: Add SPI support
  drm/tinydrm: Add mipi-dbi support
  drm/tinydrm: Add support for several Adafruit TFT displays

 drivers/gpu/drm/Kconfig                            |   9 +
 drivers/gpu/drm/Makefile                           |   2 +
 drivers/gpu/drm/drm_fb_cma_helper.c                | 149 ++++-
 drivers/gpu/drm/drm_fb_helper.c                    | 189 +++++-
 drivers/gpu/drm/drm_simple_kms_helper.c            | 262 ++++++++
 drivers/gpu/drm/tinydrm/Kconfig                    |  25 +
 drivers/gpu/drm/tinydrm/Makefile                   |   8 +
 drivers/gpu/drm/tinydrm/adafruit-tft.c             | 257 ++++++++
 drivers/gpu/drm/tinydrm/core/Makefile              |   6 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c        | 155 +++++
 .../gpu/drm/tinydrm/core/tinydrm-display-pipe.c    |  82 +++
 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c       |  94 +++
 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c |  99 +++
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  95 +++
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig             |   8 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile            |   5 +
 drivers/gpu/drm/tinydrm/lcdreg/internal.h          |   8 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c       | 190 ++++++
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c    | 281 ++++++++
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c        | 720 +++++++++++++++++++++
 drivers/gpu/drm/tinydrm/mipi-dbi.c                 | 253 ++++++++
 include/drm/drm_fb_cma_helper.h                    |  14 +
 include/drm/drm_fb_helper.h                        |  15 +
 include/drm/drm_simple_kms_helper.h                |  44 ++
 include/drm/tinydrm/ili9340.h                      |  47 ++
 include/drm/tinydrm/lcdreg-spi.h                   |  63 ++
 include/drm/tinydrm/lcdreg.h                       | 126 ++++
 include/drm/tinydrm/mipi-dbi.h                     |  24 +
 include/drm/tinydrm/tinydrm.h                      | 143 ++++
 29 files changed, 3360 insertions(+), 13 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_simple_kms_helper.c
 create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/adafruit-tft.c
 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-display-pipe.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/drm_simple_kms_helper.h
 create mode 100644 include/drm/tinydrm/ili9340.h
 create mode 100644 include/drm/tinydrm/lcdreg-spi.h
 create mode 100644 include/drm/tinydrm/lcdreg.h
 create mode 100644 include/drm/tinydrm/mipi-dbi.h
 create mode 100644 include/drm/tinydrm/tinydrm.h

--
2.2.2

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

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

* [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-13 10:57   ` Daniel Vetter
  2016-04-13 11:09   ` Daniel Vetter
  2016-04-08 17:05 ` [RFC v2 2/8] drm/fb-cma-helper: " Noralf Trønnes
                   ` (7 subsequent siblings)
  8 siblings, 2 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
Accumulated fbdev framebuffer changes are signaled using the callback
(struct drm_framebuffer_funcs *)->dirty()

The drm_fb_helper_sys_*() functions will accumulate changes and
schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
This worker is used by the deferred io mmap code to signal that it
has been collecting page faults. The page faults and/or other changes
are then merged into a drm_clip_rect and passed to the framebuffer
dirty() function.

The driver is responsible for setting up the fb_info.fbdefio structure
and calling fb_deferred_io_init() using the provided callback:
(struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_fb_helper.c | 189 +++++++++++++++++++++++++++++++++++++++-
 include/drm/drm_fb_helper.h     |  15 ++++
 2 files changed, 203 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 1e103c4..30f3dfd 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -48,6 +48,100 @@ MODULE_PARM_DESC(fbdev_emulation,
 
 static LIST_HEAD(kernel_fb_helper_list);
 
+/*
+ * Where should I put these drm_clip_rect functions?
+ */
+
+/**
+ * drm_clip_rect_reset - Reset clip
+ * @clip: clip rectangle
+ *
+ * Sets clip to {0,0,0,0}.
+ */
+static inline void drm_clip_rect_reset(struct drm_clip_rect *clip)
+{
+	clip->x1 = 0;
+	clip->x2 = 0;
+	clip->y1 = 0;
+	clip->y2 = 0;
+}
+
+/**
+ * drm_clip_rect_is_empty - Is clip empty?
+ * @clip: clip rectangle
+ *
+ * Returns true if clip is {0,0,0,0}.
+ */
+static inline bool drm_clip_rect_is_empty(struct drm_clip_rect *clip)
+{
+	return (!clip->x1 && !clip->x2 && !clip->y1 && !clip->y2);
+}
+
+/**
+ * drm_clip_rect_sanetize - Make sure clip rectangle has sane values
+ * @clip: clip rectangle
+ * @width: maximum width of rectangle
+ * @height: maximum height of rectangle
+ *
+ * Makes sure the clip doesn't exceed the specified width and height and that
+ * x1 <= x2 and y1 <= y2.
+ */
+void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height)
+{
+	if (clip->x1 > clip->x2)
+		swap(clip->x1, clip->x2);
+	if (clip->y1 > clip->y2)
+		swap(clip->y1, clip->y2);
+
+	clip->x1 = min_t(u32, clip->x1, width - 1);
+	clip->x2 = min_t(u32, clip->x2, width - 1);
+	clip->y1 = min_t(u32, clip->y1, height - 1);
+	clip->y2 = min_t(u32, clip->y2, height - 1);
+}
+EXPORT_SYMBOL(drm_clip_rect_sanetize);
+
+/**
+ * drm_clip_rect_merge - Merge clip rectangles
+ * @dst: destination clip rectangle
+ * @src: source clip rectangle(s), can be NULL
+ * @num_clips: number of source clip rectangles
+ * @width: width of rectangle if @src is NULL
+ * @height: height of rectangle if @src is NULL
+ *
+ * The dirtyfb ioctl allows for a NULL clip to be passed in,
+ * so if @src is NULL, width and height is used to set a full clip.
+ * @dst takes part in the merge unless it is empty {0,0,0,0}.
+ */
+void drm_clip_rect_merge(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned num_clips,
+			 unsigned flags, u32 width, u32 height)
+{
+	int i;
+
+	if (!src || !num_clips) {
+		dst->x1 = 0;
+		dst->x2 = width - 1;
+		dst->y1 = 0;
+		dst->y2 = height - 1;
+		return;
+	}
+
+	if (drm_clip_rect_is_empty(dst)) {
+		dst->x1 = ~0;
+		dst->y1 = ~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);
+	}
+}
+EXPORT_SYMBOL(drm_clip_rect_merge);
+
 /**
  * DOC: fbdev helpers
  *
@@ -410,6 +504,13 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
 
 	drm_warn_on_modeset_not_all_locked(dev);
 
+#ifdef CONFIG_FB_DEFERRED_IO
+	spin_lock(&fb_helper->dirty_lock);
+	drm_clip_rect_merge(&fb_helper->dirty_clip, NULL, 0, 0,
+			    fb_helper->fbdev->var.xres,
+			    fb_helper->fbdev->var.yres);
+	spin_unlock(&fb_helper->dirty_lock);
+#endif
 	if (fb_helper->atomic)
 		return restore_fbdev_mode_atomic(fb_helper);
 
@@ -654,6 +755,9 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
 			   const struct drm_fb_helper_funcs *funcs)
 {
 	INIT_LIST_HEAD(&helper->kernel_fb_list);
+#ifdef CONFIG_FB_DEFERRED_IO
+	spin_lock_init(&helper->dirty_lock);
+#endif
 	helper->funcs = funcs;
 	helper->dev = dev;
 }
@@ -838,6 +942,76 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
 }
 EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
 
+#ifdef CONFIG_FB_DEFERRED_IO
+void drm_fb_helper_deferred_io(struct fb_info *info,
+			       struct list_head *pagelist)
+{
+	struct drm_fb_helper *helper = info->par;
+	unsigned long start, end, min, max;
+	struct drm_clip_rect clip;
+	struct page *page;
+
+	if (!helper->fb->funcs->dirty)
+		return;
+
+	spin_lock(&helper->dirty_lock);
+	clip = helper->dirty_clip;
+	drm_clip_rect_reset(&helper->dirty_clip);
+	spin_unlock(&helper->dirty_lock);
+
+	min = ULONG_MAX;
+	max = 0;
+	list_for_each_entry(page, pagelist, lru) {
+		start = page->index << PAGE_SHIFT;
+		end = start + PAGE_SIZE - 1;
+		min = min(min, start);
+		max = max(max, end);
+	}
+
+	if (min < max) {
+		clip.x1 = 0;
+		clip.x2 = info->var.xres - 1;
+		clip.y1 = min / info->fix.line_length;
+		clip.y2 = min_t(u32, max / info->fix.line_length,
+				    info->var.yres - 1);
+	} else if (drm_clip_rect_is_empty(&clip)) {
+		clip.x1 = 0;
+		clip.x2 = info->var.xres - 1;
+		clip.y1 = 0;
+		clip.y2 = info->var.yres - 1;
+	}
+
+	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
+}
+EXPORT_SYMBOL(drm_fb_helper_deferred_io);
+
+static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
+				       u32 width, u32 height)
+{
+	struct drm_fb_helper *helper = info->par;
+	struct drm_clip_rect clip;
+
+	if (!info->fbdefio)
+		return;
+
+	clip.x1 = x;
+	clip.x2 = x + width - 1;
+	clip.y1 = y;
+	clip.y2 = y + height - 1;
+
+	spin_lock(&helper->dirty_lock);
+	drm_clip_rect_merge(&helper->dirty_clip, &clip, 1, 0, 0, 0);
+	spin_unlock(&helper->dirty_lock);
+
+	schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
+}
+#else
+static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
+					      u32 width, u32 height)
+{
+}
+#endif
+
 /**
  * drm_fb_helper_sys_read - wrapper around fb_sys_read
  * @info: fb_info struct pointer
@@ -866,7 +1040,14 @@ EXPORT_SYMBOL(drm_fb_helper_sys_read);
 ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
 				size_t count, loff_t *ppos)
 {
-	return fb_sys_write(info, buf, count, ppos);
+	ssize_t ret;
+
+	ret = fb_sys_write(info, buf, count, ppos);
+	if (ret > 0)
+		drm_fb_helper_sys_deferred(info, 0, 0,
+					   info->var.xres, info->var.yres);
+
+	return ret;
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_write);
 
@@ -881,6 +1062,8 @@ void drm_fb_helper_sys_fillrect(struct fb_info *info,
 				const struct fb_fillrect *rect)
 {
 	sys_fillrect(info, rect);
+	drm_fb_helper_sys_deferred(info, rect->dx, rect->dy,
+				   rect->width, rect->height);
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
 
@@ -895,6 +1078,8 @@ void drm_fb_helper_sys_copyarea(struct fb_info *info,
 				const struct fb_copyarea *area)
 {
 	sys_copyarea(info, area);
+	drm_fb_helper_sys_deferred(info, area->dx, area->dy,
+				   area->width, area->height);
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
 
@@ -909,6 +1094,8 @@ void drm_fb_helper_sys_imageblit(struct fb_info *info,
 				 const struct fb_image *image)
 {
 	sys_imageblit(info, image);
+	drm_fb_helper_sys_deferred(info, image->dx, image->dy,
+				   image->width, image->height);
 }
 EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
 
diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
index d8a40df..1daadc7 100644
--- a/include/drm/drm_fb_helper.h
+++ b/include/drm/drm_fb_helper.h
@@ -172,6 +172,9 @@ struct drm_fb_helper_connector {
  * @funcs: driver callbacks for fb helper
  * @fbdev: emulated fbdev device info struct
  * @pseudo_palette: fake palette of 16 colors
+ * @dirty_clip: clip rectangle used with deferred_io to accumulate damage to
+ *              the screen buffer
+ * @dirty_lock: spinlock protecting @dirty_clip
  *
  * This is the main structure used by the fbdev helpers. Drivers supporting
  * fbdev emulation should embedded this into their overall driver structure.
@@ -189,6 +192,10 @@ struct drm_fb_helper {
 	const struct drm_fb_helper_funcs *funcs;
 	struct fb_info *fbdev;
 	u32 pseudo_palette[17];
+#ifdef CONFIG_FB_DEFERRED_IO
+	struct drm_clip_rect dirty_clip;
+	spinlock_t dirty_lock;
+#endif
 
 	/**
 	 * @kernel_fb_list:
@@ -244,6 +251,9 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
 
 void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
 
+void drm_fb_helper_deferred_io(struct fb_info *info,
+			       struct list_head *pagelist);
+
 ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
 			       size_t count, loff_t *ppos);
 ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
@@ -362,6 +372,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
 {
 }
 
+static inline void drm_fb_helper_deferred_io(struct fb_info *info,
+					     struct list_head *pagelist)
+{
+}
+
 static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info,
 					     char __user *buf, size_t count,
 					     loff_t *ppos)
-- 
2.2.2

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

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

* [RFC v2 2/8] drm/fb-cma-helper: Add fb_deferred_io support
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-13 11:19   ` Daniel Vetter
  2016-04-08 17:05 ` [RFC v2 3/8] drm: Add helper for simple kms drivers Noralf Trønnes
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

This adds fbdev deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
The driver has to provide a (struct drm_framebuffer_funcs *)->dirty()
callback to get notification of fbdev framebuffer changes.
If the dirty() hook is set, then fb_deferred_io is set up automatically
by the helper.

Two functions have been added so that the driver can provide a dirty()
function:
- drm_fbdev_cma_init_with_funcs()
  This makes it possible for the driver to provided a custom
  (struct drm_fb_helper_funcs *)->fb_probe() function.
- drm_fbdev_cma_create_with_funcs()
  This is used by the .fb_probe hook to set a driver provided
  (struct drm_framebuffer_funcs *)->dirty() function.

Example driver code:

static int driver_fbdev_fb_dirty(struct drm_framebuffer *fb,
				 struct drm_file *file_priv,
				 unsigned flags, unsigned color,
				 struct drm_clip_rect *clips,
				 unsigned num_clips)
{
	struct drm_gem_cma_object *cma = drm_fb_cma_get_gem_obj(fb, 0);

	return 0;
}

static struct drm_framebuffer_funcs driver_fbdev_fb_funcs = {
	.destroy	= drm_fb_cma_destroy,
	.create_handle	= drm_fb_cma_create_handle,
	.dirty		= driver_fbdev_fb_dirty,
};

static int driver_fbdev_create(struct drm_fb_helper *helper,
	struct drm_fb_helper_surface_size *sizes)
{
	return drm_fbdev_cma_create_with_funcs(helper, sizes,
					&driver_fbdev_fb_funcs);
}

static const struct drm_fb_helper_funcs driver_fb_helper_funcs = {
	.fb_probe = driver_fbdev_create,
};

Driver probe:
	fbdev = drm_fbdev_cma_init_with_funcs(dev, 16,
					dev->mode_config.num_crtc,
					dev->mode_config.num_connector,
					&driver_fb_helper_funcs);

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/drm_fb_cma_helper.c | 149 +++++++++++++++++++++++++++++++++---
 include/drm/drm_fb_cma_helper.h     |  14 ++++
 2 files changed, 151 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c
index c895b6f..b347ddd 100644
--- a/drivers/gpu/drm/drm_fb_cma_helper.c
+++ b/drivers/gpu/drm/drm_fb_cma_helper.c
@@ -25,6 +25,8 @@
 #include <drm/drm_fb_cma_helper.h>
 #include <linux/module.h>
 
+#define DEFAULT_FBDEFIO_DELAY_MS 50
+
 struct drm_fb_cma {
 	struct drm_framebuffer		fb;
 	struct drm_gem_cma_object	*obj[4];
@@ -45,7 +47,7 @@ static inline struct drm_fb_cma *to_fb_cma(struct drm_framebuffer *fb)
 	return container_of(fb, struct drm_fb_cma, fb);
 }
 
-static void drm_fb_cma_destroy(struct drm_framebuffer *fb)
+void drm_fb_cma_destroy(struct drm_framebuffer *fb)
 {
 	struct drm_fb_cma *fb_cma = to_fb_cma(fb);
 	int i;
@@ -58,8 +60,9 @@ static void drm_fb_cma_destroy(struct drm_framebuffer *fb)
 	drm_framebuffer_cleanup(fb);
 	kfree(fb_cma);
 }
+EXPORT_SYMBOL(drm_fb_cma_destroy);
 
-static int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
+int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
 	struct drm_file *file_priv, unsigned int *handle)
 {
 	struct drm_fb_cma *fb_cma = to_fb_cma(fb);
@@ -67,6 +70,7 @@ static int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
 	return drm_gem_handle_create(file_priv,
 			&fb_cma->obj[0]->base, handle);
 }
+EXPORT_SYMBOL(drm_fb_cma_create_handle);
 
 static struct drm_framebuffer_funcs drm_fb_cma_funcs = {
 	.destroy	= drm_fb_cma_destroy,
@@ -75,7 +79,7 @@ static struct drm_framebuffer_funcs drm_fb_cma_funcs = {
 
 static struct drm_fb_cma *drm_fb_cma_alloc(struct drm_device *dev,
 	const const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_cma_object **obj,
-	unsigned int num_planes)
+	unsigned int num_planes, struct drm_framebuffer_funcs *funcs)
 {
 	struct drm_fb_cma *fb_cma;
 	int ret;
@@ -90,7 +94,7 @@ static struct drm_fb_cma *drm_fb_cma_alloc(struct drm_device *dev,
 	for (i = 0; i < num_planes; i++)
 		fb_cma->obj[i] = obj[i];
 
-	ret = drm_framebuffer_init(dev, &fb_cma->fb, &drm_fb_cma_funcs);
+	ret = drm_framebuffer_init(dev, &fb_cma->fb, funcs);
 	if (ret) {
 		dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret);
 		kfree(fb_cma);
@@ -144,7 +148,7 @@ struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
 		objs[i] = to_drm_gem_cma_obj(obj);
 	}
 
-	fb_cma = drm_fb_cma_alloc(dev, mode_cmd, objs, i);
+	fb_cma = drm_fb_cma_alloc(dev, mode_cmd, objs, i, &drm_fb_cma_funcs);
 	if (IS_ERR(fb_cma)) {
 		ret = PTR_ERR(fb_cma);
 		goto err_gem_object_unreference;
@@ -232,8 +236,93 @@ static struct fb_ops drm_fbdev_cma_ops = {
 	.fb_setcmap	= drm_fb_helper_setcmap,
 };
 
-static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
-	struct drm_fb_helper_surface_size *sizes)
+#ifdef CONFIG_FB_DEFERRED_IO
+/*
+ * HACK
+ * fb_deferred_io_mmap() is not exported so I use this hack until it's clear
+ * that this patch will be used.
+ */
+static int (*fb_deferred_io_mmap)(struct fb_info *info,
+				  struct vm_area_struct *vma);
+/*
+ * I sent a question about my need to set vm_page_prot to the fbdev ML, but
+ * I haven't heard back. So exporting fb_deferred_io_mmap() is probably
+ * the best solution.
+ */
+static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info,
+					  struct vm_area_struct *vma)
+{
+	fb_deferred_io_mmap(info, vma);
+	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+	return 0;
+}
+
+static int drm_fbdev_cma_defio_init(struct fb_info *fbi,
+				    struct drm_gem_cma_object *cma_obj)
+{
+	struct fb_deferred_io *fbdefio;
+	struct fb_ops *fbops;
+
+	/*
+	 * Per device structures are needed because:
+	 * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap
+	 * fbdefio: individual delays
+	 */
+	fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
+	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+	if (!fbdefio || !fbops) {
+		kfree(fbdefio);
+		return -ENOMEM;
+	}
+
+	/* can't be offset from vaddr since dirty() uses cma_obj */
+	fbi->screen_buffer = cma_obj->vaddr;
+	/* fb_deferred_io_fault() needs a physical address */
+	fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer));
+
+	*fbops = *fbi->fbops;
+	fbi->fbops = fbops;
+
+	fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS);
+	fbdefio->deferred_io = drm_fb_helper_deferred_io;
+	fbi->fbdefio = fbdefio;
+	fb_deferred_io_init(fbi);
+
+	if (!fb_deferred_io_mmap)
+		fb_deferred_io_mmap = fbi->fbops->fb_mmap;
+	fbi->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap;
+
+	return 0;
+}
+
+static void drm_fbdev_cma_defio_fini(struct fb_info *fbi)
+{
+	if (!fbi->fbdefio)
+		return;
+
+	fb_deferred_io_cleanup(fbi);
+	kfree(fbi->fbdefio);
+	kfree(fbi->fbops);
+}
+#else
+static inline int drm_fbdev_cma_defio_init(struct fb_info *fbi)
+{
+	return 0;
+}
+
+static inline void drm_fbdev_cma_defio_fini(struct fb_info *fbi)
+{
+}
+#endif /* CONFIG_FB_DEFERRED_IO */
+
+/*
+ * For use in a (struct drm_fb_helper_funcs *)->fb_probe callback function that
+ * needs custom struct drm_framebuffer_funcs, like dirty() for deferred_io use.
+ */
+int drm_fbdev_cma_create_with_funcs(struct drm_fb_helper *helper,
+	struct drm_fb_helper_surface_size *sizes,
+	struct drm_framebuffer_funcs *funcs)
 {
 	struct drm_fbdev_cma *fbdev_cma = to_fbdev_cma(helper);
 	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
@@ -269,7 +358,7 @@ static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
 		goto err_gem_free_object;
 	}
 
-	fbdev_cma->fb = drm_fb_cma_alloc(dev, &mode_cmd, &obj, 1);
+	fbdev_cma->fb = drm_fb_cma_alloc(dev, &mode_cmd, &obj, 1, funcs);
 	if (IS_ERR(fbdev_cma->fb)) {
 		dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
 		ret = PTR_ERR(fbdev_cma->fb);
@@ -295,31 +384,48 @@ static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
 	fbi->screen_size = size;
 	fbi->fix.smem_len = size;
 
+	if (funcs->dirty) {
+		ret = drm_fbdev_cma_defio_init(fbi, obj);
+		if (ret)
+			goto err_cma_destroy;
+	}
+
 	return 0;
 
+err_cma_destroy:
+	drm_framebuffer_unregister_private(&fbdev_cma->fb->fb);
+	drm_fb_cma_destroy(&fbdev_cma->fb->fb);
 err_fb_info_destroy:
 	drm_fb_helper_release_fbi(helper);
 err_gem_free_object:
 	dev->driver->gem_free_object(&obj->base);
 	return ret;
 }
+EXPORT_SYMBOL(drm_fbdev_cma_create_with_funcs);
+
+static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
+	struct drm_fb_helper_surface_size *sizes)
+{
+	return drm_fbdev_cma_create_with_funcs(helper, sizes, &drm_fb_cma_funcs);
+}
 
 static const struct drm_fb_helper_funcs drm_fb_cma_helper_funcs = {
 	.fb_probe = drm_fbdev_cma_create,
 };
 
 /**
- * drm_fbdev_cma_init() - Allocate and initializes a drm_fbdev_cma struct
+ * drm_fbdev_cma_init_with_funcs() - Allocate and initializes a drm_fbdev_cma struct
  * @dev: DRM device
  * @preferred_bpp: Preferred bits per pixel for the device
  * @num_crtc: Number of CRTCs
  * @max_conn_count: Maximum number of connectors
+ * @funcs: fb helper functions, in particular fb_probe()
  *
  * Returns a newly allocated drm_fbdev_cma struct or a ERR_PTR.
  */
-struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
+struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
 	unsigned int preferred_bpp, unsigned int num_crtc,
-	unsigned int max_conn_count)
+	unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs)
 {
 	struct drm_fbdev_cma *fbdev_cma;
 	struct drm_fb_helper *helper;
@@ -333,7 +439,7 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
 
 	helper = &fbdev_cma->fb_helper;
 
-	drm_fb_helper_prepare(dev, helper, &drm_fb_cma_helper_funcs);
+	drm_fb_helper_prepare(dev, helper, funcs);
 
 	ret = drm_fb_helper_init(dev, helper, num_crtc, max_conn_count);
 	if (ret < 0) {
@@ -363,6 +469,24 @@ err_free:
 
 	return ERR_PTR(ret);
 }
+EXPORT_SYMBOL_GPL(drm_fbdev_cma_init_with_funcs);
+
+/**
+ * drm_fbdev_cma_init() - Allocate and initializes a drm_fbdev_cma struct
+ * @dev: DRM device
+ * @preferred_bpp: Preferred bits per pixel for the device
+ * @num_crtc: Number of CRTCs
+ * @max_conn_count: Maximum number of connectors
+ *
+ * Returns a newly allocated drm_fbdev_cma struct or a ERR_PTR.
+ */
+struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
+	unsigned int preferred_bpp, unsigned int num_crtc,
+	unsigned int max_conn_count)
+{
+	return drm_fbdev_cma_init_with_funcs(dev, preferred_bpp, num_crtc,
+				max_conn_count, &drm_fb_cma_helper_funcs);
+}
 EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
 
 /**
@@ -372,6 +496,7 @@ EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
 void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma)
 {
 	drm_fb_helper_unregister_fbi(&fbdev_cma->fb_helper);
+	drm_fbdev_cma_defio_fini(fbdev_cma->fb_helper.fbdev);
 	drm_fb_helper_release_fbi(&fbdev_cma->fb_helper);
 
 	if (fbdev_cma->fb) {
diff --git a/include/drm/drm_fb_cma_helper.h b/include/drm/drm_fb_cma_helper.h
index be62bd3..6554b6f 100644
--- a/include/drm/drm_fb_cma_helper.h
+++ b/include/drm/drm_fb_cma_helper.h
@@ -4,11 +4,18 @@
 struct drm_fbdev_cma;
 struct drm_gem_cma_object;
 
+struct drm_fb_helper_surface_size;
+struct drm_framebuffer_funcs;
+struct drm_fb_helper_funcs;
 struct drm_framebuffer;
+struct drm_fb_helper;
 struct drm_device;
 struct drm_file;
 struct drm_mode_fb_cmd2;
 
+struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
+	unsigned int preferred_bpp, unsigned int num_crtc,
+	unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs);
 struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
 	unsigned int preferred_bpp, unsigned int num_crtc,
 	unsigned int max_conn_count);
@@ -16,6 +23,13 @@ void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma);
 
 void drm_fbdev_cma_restore_mode(struct drm_fbdev_cma *fbdev_cma);
 void drm_fbdev_cma_hotplug_event(struct drm_fbdev_cma *fbdev_cma);
+int drm_fbdev_cma_create_with_funcs(struct drm_fb_helper *helper,
+	struct drm_fb_helper_surface_size *sizes,
+	struct drm_framebuffer_funcs *funcs);
+
+void drm_fb_cma_destroy(struct drm_framebuffer *fb);
+int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
+	struct drm_file *file_priv, unsigned int *handle);
 
 struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
 	struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd);
-- 
2.2.2

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

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

* [RFC v2 3/8] drm: Add helper for simple kms drivers
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 2/8] drm/fb-cma-helper: " Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-13 11:05   ` Daniel Vetter
  2016-04-08 17:05 ` [RFC v2 4/8] drm: Add DRM support for tiny LCD displays Noralf Trønnes
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Provides helper functions for drivers that have a simple display
pipeline. Plane, crtc and encoder are collapsed into one entity.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/Kconfig                 |   7 +
 drivers/gpu/drm/Makefile                |   1 +
 drivers/gpu/drm/drm_simple_kms_helper.c | 262 ++++++++++++++++++++++++++++++++
 include/drm/drm_simple_kms_helper.h     |  44 ++++++
 4 files changed, 314 insertions(+)
 create mode 100644 drivers/gpu/drm/drm_simple_kms_helper.c
 create mode 100644 include/drm/drm_simple_kms_helper.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 8ae7ab6..cb62cd9 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -31,6 +31,13 @@ config DRM_KMS_HELPER
 	help
 	  CRTC helpers for KMS drivers.
 
+config DRM_SIMPLE_KMS_HELPER
+	tristate
+	depends on DRM
+	select DRM_KMS_HELPER
+	help
+	  Helpers for very simple KMS drivers.
+
 config DRM_KMS_FB_HELPER
 	bool
 	depends on DRM_KMS_HELPER
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 61766de..ea9bf59 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -28,6 +28,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
 drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
 
 obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
+obj-$(CONFIG_DRM_SIMPLE_KMS_HELPER) += drm_simple_kms_helper.o
 
 CFLAGS_drm_trace_points.o := -I$(src)
 
diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c
new file mode 100644
index 0000000..e193f7db
--- /dev/null
+++ b/drivers/gpu/drm/drm_simple_kms_helper.c
@@ -0,0 +1,262 @@
+/*
+ * 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/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <linux/slab.h>
+
+struct drm_simple_kms_connector {
+	struct drm_connector base;
+	struct drm_panel *panel;
+};
+
+static inline struct drm_simple_kms_connector *
+to_simple_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct drm_simple_kms_connector, base);
+}
+
+static int drm_simple_kms_connector_get_modes(struct drm_connector *connector)
+{
+	return drm_panel_get_modes(to_simple_connector(connector)->panel);
+}
+
+static struct drm_encoder *
+drm_simple_kms_connector_best_encoder(struct drm_connector *connector)
+{
+	return drm_encoder_find(connector->dev, connector->encoder_ids[0]);
+}
+
+static const struct drm_connector_helper_funcs drm_simple_kms_connector_helper_funcs = {
+	.get_modes = drm_simple_kms_connector_get_modes,
+	.best_encoder = drm_simple_kms_connector_best_encoder,
+};
+
+static enum drm_connector_status
+drm_simple_kms_connector_detect(struct drm_connector *connector, bool force)
+{
+	if (drm_device_is_unplugged(connector->dev))
+		return connector_status_disconnected;
+
+	return connector->status;
+}
+
+static void drm_simple_kms_connector_destroy(struct drm_connector *connector)
+{
+	struct drm_simple_kms_connector *panel_connector;
+
+	panel_connector = to_simple_connector(connector);
+	drm_panel_detach(panel_connector->panel);
+	drm_panel_remove(panel_connector->panel);
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+	kfree(panel_connector);
+}
+
+static const struct drm_connector_funcs drm_simple_kms_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = drm_simple_kms_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_simple_kms_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/**
+ * drm_simple_kms_panel_connector_create - Create simple connector for panel
+ * @dev: DRM device
+ * @panel: DRM panel
+ * @connector_type: user visible type of the connector
+ *
+ * Creates a simple connector for a panel.
+ * The panel needs to provide a get_modes() function.
+ *
+ * Returns:
+ * Pointer to new connector or ERR_PTR on failure.
+ */
+struct drm_connector *
+drm_simple_kms_panel_connector_create(struct drm_device *dev,
+				      struct drm_panel *panel,
+				      int connector_type)
+{
+	struct drm_simple_kms_connector *panel_connector;
+	struct drm_connector *connector;
+	int ret;
+
+	panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
+	if (!panel_connector)
+		return ERR_PTR(-ENOMEM);
+
+	panel_connector->panel = panel;
+	connector = &panel_connector->base;
+	drm_connector_helper_add(connector, &drm_simple_kms_connector_helper_funcs);
+	ret = drm_connector_init(dev, connector, &drm_simple_kms_connector_funcs,
+				 connector_type);
+	if (ret) {
+		kfree(panel_connector);
+		return ERR_PTR(ret);
+	}
+
+	connector->status = connector_status_connected;
+	drm_panel_init(panel);
+	drm_panel_add(panel);
+	drm_panel_attach(panel, connector);
+
+	return connector;
+}
+EXPORT_SYMBOL(drm_simple_kms_panel_connector_create);
+
+static void drm_simple_kms_encoder_disable(struct drm_encoder *encoder)
+{
+}
+
+static void drm_simple_kms_encoder_enable(struct drm_encoder *encoder)
+{
+}
+
+static int drm_simple_kms_encoder_atomic_check(struct drm_encoder *encoder,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state)
+{
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs drm_simple_kms_encoder_helper_funcs = {
+	.disable = drm_simple_kms_encoder_disable,
+	.enable = drm_simple_kms_encoder_enable,
+	.atomic_check = drm_simple_kms_encoder_atomic_check,
+};
+
+static const struct drm_encoder_funcs drm_simple_kms_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static void drm_simple_kms_crtc_enable(struct drm_crtc *crtc)
+{
+	struct drm_simple_display_pipe *pipe;
+
+	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
+	if (!pipe->funcs || !pipe->funcs->enable)
+		return;
+
+	pipe->funcs->enable(pipe, crtc->state);
+}
+
+static void drm_simple_kms_crtc_disable(struct drm_crtc *crtc)
+{
+	struct drm_simple_display_pipe *pipe;
+
+	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
+	if (!pipe->funcs || !pipe->funcs->disable)
+		return;
+
+	pipe->funcs->disable(pipe);
+}
+
+static const struct drm_crtc_helper_funcs drm_simple_kms_crtc_helper_funcs = {
+	.disable = drm_simple_kms_crtc_disable,
+	.enable = drm_simple_kms_crtc_enable,
+};
+
+static const struct drm_crtc_funcs drm_simple_kms_crtc_funcs = {
+	.reset = drm_atomic_helper_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+};
+
+static void drm_simple_kms_plane_atomic_update(struct drm_plane *plane,
+					struct drm_plane_state *old_state)
+{
+	struct drm_simple_display_pipe *pipe;
+
+	pipe = container_of(plane, struct drm_simple_display_pipe, plane);
+	if (!pipe->funcs || !pipe->funcs->plane_update)
+		return;
+
+	pipe->funcs->plane_update(pipe, old_state);
+}
+
+static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = {
+	.atomic_update = drm_simple_kms_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+};
+
+/**
+ * drm_simple_display_pipe_init - Initialize a simple display pipe
+ * @dev: DRM device
+ * @pipe: simple display pipe object to initialize
+ * @funcs: callbacks for the display pipe
+ * @formats: array of supported formats (%DRM_FORMAT_*)
+ * @format_count: number of elements in @formats
+ * @connector: connector to attach and register
+ *
+ * Sets up a display pipe which consist of a really simple plane-crtc-encoder
+ * pipe coupled with the provided connector.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+int drm_simple_display_pipe_init(struct drm_device *dev,
+				 struct drm_simple_display_pipe *pipe,
+				 struct drm_simple_display_pipe_funcs *funcs,
+				 const uint32_t *formats, unsigned int format_count,
+				 struct drm_connector *connector)
+{
+	struct drm_encoder *encoder = &pipe->encoder;
+	struct drm_plane *plane = &pipe->plane;
+	struct drm_crtc *crtc = &pipe->crtc;
+	int ret;
+
+	pipe->funcs = funcs;
+
+	drm_plane_helper_add(plane, &drm_simple_kms_plane_helper_funcs);
+	ret = drm_universal_plane_init(dev, plane, 0, &drm_simple_kms_plane_funcs,
+				       formats, format_count,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret)
+		return ret;
+
+	drm_crtc_helper_add(crtc, &drm_simple_kms_crtc_helper_funcs);
+	ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
+					&drm_simple_kms_crtc_funcs, NULL);
+	if (ret)
+		return ret;
+
+	encoder->possible_crtcs = 1 << drm_crtc_index(crtc);
+	drm_encoder_helper_add(encoder, &drm_simple_kms_encoder_helper_funcs);
+	ret = drm_encoder_init(dev, encoder, &drm_simple_kms_encoder_funcs,
+			       DRM_MODE_ENCODER_NONE, NULL);
+	if (ret)
+		return ret;
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret)
+		return ret;
+
+	return drm_connector_register(connector);
+}
+EXPORT_SYMBOL(drm_simple_display_pipe_init);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h
new file mode 100644
index 0000000..0f7461b
--- /dev/null
+++ b/include/drm/drm_simple_kms_helper.h
@@ -0,0 +1,44 @@
+/*
+ * 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_DRM_SIMPLE_KMS_HELPER_H
+#define __LINUX_DRM_SIMPLE_KMS_HELPER_H
+
+struct drm_simple_display_pipe;
+
+struct drm_simple_display_pipe_funcs {
+	void (*enable)(struct drm_simple_display_pipe *pipe,
+		       struct drm_crtc_state *crtc_state);
+	void (*disable)(struct drm_simple_display_pipe *pipe);
+	void (*plane_update)(struct drm_simple_display_pipe *pipe,
+			     struct drm_plane_state *plane_state);
+};
+
+struct drm_simple_display_pipe {
+	struct drm_crtc crtc;
+	struct drm_plane plane;
+	struct drm_encoder encoder;
+	struct drm_connector *connector;
+
+	struct drm_simple_display_pipe_funcs *funcs;
+};
+
+int drm_simple_display_pipe_init(struct drm_device *dev,
+				 struct drm_simple_display_pipe *pipe,
+				 struct drm_simple_display_pipe_funcs *funcs,
+				 const uint32_t *formats, unsigned int format_count,
+				 struct drm_connector *connector);
+struct drm_connector *
+drm_simple_kms_panel_connector_create(struct drm_device *dev,
+				      struct drm_panel *panel,
+				      int connector_type);
+
+
+
+#endif /* __LINUX_DRM_SIMPLE_KMS_HELPER_H */
-- 
2.2.2

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

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

* [RFC v2 4/8] drm: Add DRM support for tiny LCD displays
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (2 preceding siblings ...)
  2016-04-08 17:05 ` [RFC v2 3/8] drm: Add helper for simple kms drivers Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 5/8] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

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

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/Kconfig                            |   2 +
 drivers/gpu/drm/Makefile                           |   1 +
 drivers/gpu/drm/tinydrm/Kconfig                    |  11 ++
 drivers/gpu/drm/tinydrm/Makefile                   |   1 +
 drivers/gpu/drm/tinydrm/core/Makefile              |   6 +
 drivers/gpu/drm/tinydrm/core/tinydrm-core.c        | 155 +++++++++++++++++++++
 .../gpu/drm/tinydrm/core/tinydrm-display-pipe.c    |  82 +++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c       |  94 +++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c |  99 +++++++++++++
 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  95 +++++++++++++
 include/drm/tinydrm/tinydrm.h                      | 143 +++++++++++++++++++
 11 files changed, 689 insertions(+)
 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-display-pipe.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
 create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
 create mode 100644 include/drm/tinydrm/tinydrm.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index cb62cd9..b495dbf 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -276,3 +276,5 @@ source "drivers/gpu/drm/imx/Kconfig"
 source "drivers/gpu/drm/vc4/Kconfig"
 
 source "drivers/gpu/drm/etnaviv/Kconfig"
+
+source "drivers/gpu/drm/tinydrm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index ea9bf59..184056e 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -75,3 +75,4 @@ obj-y			+= panel/
 obj-y			+= bridge/
 obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/
 obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/
+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..e26e5ed
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -0,0 +1,11 @@
+menuconfig DRM_TINYDRM
+	tristate "Support for small TFT LCD display modules"
+	depends on DRM
+	select DRM_SIMPLE_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_PANEL
+	select VIDEOMODE_HELPERS
+	select FB_DEFERRED_IO if DRM_KMS_FB_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..11366b4
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_DRM_TINYDRM)		+= tinydrm.o
+tinydrm-y				+= tinydrm-core.o
+tinydrm-y				+= tinydrm-display-pipe.o
+tinydrm-y				+= tinydrm-framebuffer.o
+tinydrm-y				+= tinydrm-helpers.o
+tinydrm-$(CONFIG_DRM_KMS_FB_HELPER)	+= tinydrm-fbdev.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..131a2ac
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -0,0 +1,155 @@
+//#define DEBUG
+/*
+ * 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/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/device.h>
+
+static const uint32_t tinydrm_formats[] = {
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_XRGB8888,
+};
+
+static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
+	.fb_create = tinydrm_fb_cma_dumb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+void tinydrm_lastclose(struct drm_device *dev)
+{
+	struct tinydrm_device *tdev = dev->dev_private;
+
+	DRM_DEBUG_KMS("\n");
+	tinydrm_fbdev_restore_mode(tdev);
+}
+EXPORT_SYMBOL(tinydrm_lastclose);
+
+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 void tinydrm_unregister(struct tinydrm_device *tdev)
+{
+	DRM_DEBUG_KMS("\n");
+
+	tinydrm_fbdev_fini(tdev);
+
+	drm_mode_config_cleanup(tdev->base);
+	drm_dev_unregister(tdev->base);
+	drm_dev_unref(tdev->base);
+}
+
+static int tinydrm_register(struct device *parent, struct tinydrm_device *tdev,
+			    struct drm_driver *driver)
+{
+	struct drm_device *dev;
+	int ret;
+
+	DRM_DEBUG_KMS("\n");
+
+	if (WARN_ON(!tdev->dirtyfb))
+		return -EINVAL;
+
+	if (!parent->coherent_dma_mask) {
+		ret = dma_set_coherent_mask(parent, DMA_BIT_MASK(32));
+		if (ret) {
+			DRM_ERROR("Failed to set coherent_dma_mask\n");
+			return ret;
+		}
+	}
+
+	dev = drm_dev_alloc(driver, parent);
+	if (!dev)
+		return -ENOMEM;
+
+	tdev->base = dev;
+	dev->dev_private = tdev;
+
+	ret = drm_dev_set_unique(dev, dev_name(dev->dev));
+	if (ret)
+		goto err_free;
+
+	ret = drm_dev_register(dev, 0);
+	if (ret)
+		goto err_free;
+
+	drm_mode_config_init(dev);
+	dev->mode_config.min_width = tdev->width;
+	dev->mode_config.min_height = tdev->height;
+	dev->mode_config.max_width = tdev->width;
+	dev->mode_config.max_height = tdev->height;
+	dev->mode_config.funcs = &tinydrm_mode_config_funcs;
+
+	ret = tinydrm_display_pipe_init(tdev, tinydrm_formats,
+					ARRAY_SIZE(tinydrm_formats));
+	if (ret)
+		goto err_free;
+
+	drm_mode_config_reset(dev);
+
+	ret = tinydrm_fbdev_init(tdev);
+	if (ret)
+		DRM_ERROR("Failed to initialize fbdev: %d\n", ret);
+
+	DRM_INFO("Device: %s\n", dev_name(dev->dev));
+	DRM_INFO("Initialized %s %d.%d.%d on minor %d\n",
+		 driver->name, driver->major, driver->minor, driver->patchlevel,
+		 dev->primary->index);
+
+	return 0;
+
+err_free:
+	drm_dev_unref(dev);
+
+	return ret;
+}
+
+static void devm_tinydrm_release(struct device *dev, void *res)
+{
+	tinydrm_unregister(*(struct tinydrm_device **)res);
+}
+
+int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev,
+			  struct drm_driver *driver)
+{
+	struct tinydrm_device **ptr;
+	int ret;
+
+	ptr = devres_alloc(devm_tinydrm_release, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return -ENOMEM;
+
+	ret = tinydrm_register(dev, tdev, driver);
+	if (ret) {
+		devres_free(ptr);
+		return ret;
+	}
+
+	*ptr = tdev;
+	devres_add(dev, ptr);
+
+	return 0;
+}
+EXPORT_SYMBOL(devm_tinydrm_register);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-display-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-display-pipe.c
new file mode 100644
index 0000000..5e5fa3c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-display-pipe.c
@@ -0,0 +1,82 @@
+/*
+ * 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_crtc.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+static void tinydrm_display_pipe_enable(struct drm_simple_display_pipe *pipe,
+					struct drm_crtc_state *crtc_state)
+{
+	struct tinydrm_device *tdev;
+
+	tdev = container_of(pipe, struct tinydrm_device, pipe);
+	DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled);
+
+	/* The panel must be prepared on the first crtc enable after probe */
+	tinydrm_prepare(tdev);
+	/* The panel is enabled after the first display update */
+}
+
+static void tinydrm_display_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct tinydrm_device *tdev;
+
+	tdev = container_of(pipe, struct tinydrm_device, pipe);
+	DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled);
+
+	tinydrm_disable(tdev);
+}
+
+struct drm_simple_display_pipe_funcs tinydrm_display_pipe_funcs = {
+	.enable = tinydrm_display_pipe_enable,
+	.disable = tinydrm_display_pipe_disable,
+};
+
+int tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			      const uint32_t *formats, unsigned int format_count)
+{
+	struct drm_device *dev = tdev->base;
+	struct drm_connector *connector;
+	int ret;
+
+	connector = drm_simple_kms_panel_connector_create(dev, &tdev->panel,
+						DRM_MODE_CONNECTOR_VIRTUAL);
+	if (IS_ERR(connector))
+		return PTR_ERR(connector);
+
+	ret = drm_simple_display_pipe_init(dev, &tdev->pipe,
+				&tinydrm_display_pipe_funcs,
+				formats, format_count,
+				connector);
+
+	return ret;
+}
+EXPORT_SYMBOL(tinydrm_display_pipe_init);
+
+int tinydrm_panel_get_modes(struct drm_panel *panel)
+{
+	struct drm_display_mode *mode;
+	struct tinydrm_device *tdev;
+
+	tdev = container_of(panel, struct tinydrm_device, panel);
+// TODO: get width/height somewhere else
+	mode = drm_cvt_mode(panel->connector->dev, tdev->width, tdev->height,
+			    60, false, false, false);
+	if (!mode)
+		return 0;
+
+	mode->type |= DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(panel->connector, mode);
+
+	return 1;
+}
+EXPORT_SYMBOL(tinydrm_panel_get_modes);
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
new file mode 100644
index 0000000..73013b4f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
@@ -0,0 +1,94 @@
+/*
+ * 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/drmP.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+static int tinydrm_fbdev_fb_dirty(struct drm_framebuffer *fb,
+				  struct drm_file *file_priv,
+				  unsigned flags, unsigned color,
+				  struct drm_clip_rect *clips,
+				  unsigned num_clips)
+{
+	struct drm_gem_cma_object *cma = drm_fb_cma_get_gem_obj(fb, 0);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+
+	if (tdev->pipe.plane.fb != fb)
+		return 0;
+
+	return tdev->dirtyfb(fb, cma->vaddr, flags, color, clips, num_clips);
+}
+
+static struct drm_framebuffer_funcs tinydrm_fbdev_fb_funcs = {
+	.destroy	= drm_fb_cma_destroy,
+	.create_handle	= drm_fb_cma_create_handle,
+	.dirty		= tinydrm_fbdev_fb_dirty,
+};
+
+static int tinydrm_fbdev_create(struct drm_fb_helper *helper,
+				struct drm_fb_helper_surface_size *sizes)
+{
+	struct tinydrm_device *tdev = helper->dev->dev_private;
+	int ret;
+
+	ret = drm_fbdev_cma_create_with_funcs(helper, sizes,
+					      &tinydrm_fbdev_fb_funcs);
+	if (ret)
+		return ret;
+
+	if (tdev->fbdefio_delay_ms) {
+		unsigned long delay;
+
+		delay = msecs_to_jiffies(tdev->fbdefio_delay_ms);
+		helper->fbdev->fbdefio->delay = delay ? delay : 1;
+	}
+
+	return 0;
+}
+
+static const struct drm_fb_helper_funcs tinydrm_fb_helper_funcs = {
+	.fb_probe = tinydrm_fbdev_create,
+};
+
+int tinydrm_fbdev_init(struct tinydrm_device *tdev)
+{
+	struct drm_device *dev = tdev->base;
+	struct drm_fbdev_cma *fbdev;
+
+	DRM_DEBUG_KMS("IN\n");
+
+	fbdev = drm_fbdev_cma_init_with_funcs(dev, 16,
+					      dev->mode_config.num_crtc,
+					      dev->mode_config.num_connector,
+					      &tinydrm_fb_helper_funcs);
+	if (IS_ERR(fbdev))
+		return PTR_ERR(fbdev);
+
+	tdev->fbdev_cma = fbdev;
+
+	DRM_DEBUG_KMS("OUT\n");
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_fbdev_init);
+
+void tinydrm_fbdev_fini(struct tinydrm_device *tdev)
+{
+	drm_fbdev_cma_fini(tdev->fbdev_cma);
+	tdev->fbdev_cma = NULL;
+}
+EXPORT_SYMBOL(tinydrm_fbdev_fini);
+
+void tinydrm_fbdev_restore_mode(struct tinydrm_device *tdev)
+{
+	drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
+}
+EXPORT_SYMBOL(tinydrm_fbdev_restore_mode);
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
new file mode 100644
index 0000000..e167f92
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
@@ -0,0 +1,99 @@
+/*
+ * 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/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/tinydrm/tinydrm.h>
+
+struct tinydrm_framebuffer {
+	struct drm_framebuffer base;
+	struct drm_gem_cma_object *cma_obj;
+};
+
+static inline struct tinydrm_framebuffer *to_tinydrm_framebuffer(struct drm_framebuffer *fb)
+{
+	return container_of(fb, struct tinydrm_framebuffer, base);
+}
+
+static void tinydrm_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+	struct tinydrm_framebuffer *tinydrm_fb = to_tinydrm_framebuffer(fb);
+
+	DRM_DEBUG_KMS("fb = %p, cma_obj = %p\n", fb, tinydrm_fb->cma_obj);
+
+	if (tinydrm_fb->cma_obj)
+		drm_gem_object_unreference_unlocked(&tinydrm_fb->cma_obj->base);
+
+	drm_framebuffer_cleanup(fb);
+	kfree(tinydrm_fb);
+}
+
+static int tinydrm_framebuffer_dirty(struct drm_framebuffer *fb,
+				     struct drm_file *file_priv,
+				     unsigned flags, unsigned color,
+				     struct drm_clip_rect *clips,
+				     unsigned num_clips)
+{
+	struct tinydrm_framebuffer *tfb = to_tinydrm_framebuffer(fb);
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+
+	dev_dbg(fb->dev->dev, "%s\n", __func__);
+
+	return tdev->dirtyfb(fb, tfb->cma_obj->vaddr, flags, color, clips, num_clips);
+}
+
+static const struct drm_framebuffer_funcs tinydrm_fb_funcs = {
+	.destroy = tinydrm_framebuffer_destroy,
+	.dirty = tinydrm_framebuffer_dirty,
+/*	TODO?
+ *	.create_handle = tinydrm_framebuffer_create_handle, */
+};
+
+/*
+ * Maybe this could be turned into drm_fb_cma_dumb_create_with_funcs() and put
+ * alongside drm_fb_cma_create() in drm_fb_cma_helper.c
+ */
+struct drm_framebuffer *tinydrm_fb_cma_dumb_create(struct drm_device *dev,
+					struct drm_file *file_priv,
+					const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	struct tinydrm_framebuffer *tinydrm_fb;
+	struct drm_gem_object *obj;
+	int ret;
+
+	/* TODO? Validate the pixel format, size and pitches */
+	DRM_DEBUG_KMS("pixel_format=%s\n", drm_get_format_name(mode_cmd->pixel_format));
+	DRM_DEBUG_KMS("width=%u\n", mode_cmd->width);
+	DRM_DEBUG_KMS("height=%u\n", mode_cmd->height);
+	DRM_DEBUG_KMS("pitches[0]=%u\n", mode_cmd->pitches[0]);
+
+	obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
+	if (!obj)
+		return NULL;
+
+	tinydrm_fb = kzalloc(sizeof(*tinydrm_fb), GFP_KERNEL);
+	if (!tinydrm_fb)
+		return NULL;
+
+	tinydrm_fb->cma_obj = to_drm_gem_cma_obj(obj);
+
+	ret = drm_framebuffer_init(dev, &tinydrm_fb->base, &tinydrm_fb_funcs);
+	if (ret) {
+		kfree(tinydrm_fb);
+		drm_gem_object_unreference_unlocked(obj);
+		return NULL;
+	}
+
+	drm_helper_mode_fill_fb_struct(&tinydrm_fb->base, mode_cmd);
+
+	return &tinydrm_fb->base;
+}
+EXPORT_SYMBOL(tinydrm_fb_cma_dumb_create);
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..3545d7f
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
@@ -0,0 +1,95 @@
+/*
+ * 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/drmP.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/backlight.h>
+#include <linux/spi/spi.h>
+
+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);
+
+	return backlight;
+}
+EXPORT_SYMBOL(tinydrm_of_find_backlight);
+
+int tinydrm_panel_enable_backlight(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+
+	if (tdev->backlight) {
+		if (tdev->backlight->props.brightness == 0)
+			tdev->backlight->props.brightness =
+					tdev->backlight->props.max_brightness;
+		tdev->backlight->props.state &= ~BL_CORE_SUSPENDED;
+		backlight_update_status(tdev->backlight);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_panel_enable_backlight);
+
+int tinydrm_panel_disable_backlight(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+
+	if (tdev->backlight) {
+		tdev->backlight->props.state |= BL_CORE_SUSPENDED;
+		backlight_update_status(tdev->backlight);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tinydrm_panel_disable_backlight);
+
+static int __maybe_unused tinydrm_pm_suspend(struct device *dev)
+{
+	struct tinydrm_device *tdev = dev_get_drvdata(dev);
+
+	tinydrm_disable(tdev);
+	tinydrm_unprepare(tdev);
+
+	return 0;
+}
+
+static int __maybe_unused tinydrm_pm_resume(struct device *dev)
+{
+	struct tinydrm_device *tdev = dev_get_drvdata(dev);
+
+	tinydrm_prepare(tdev);
+	/* The panel is enabled after the first display update */
+
+	return 0;
+}
+
+const struct dev_pm_ops tinydrm_simple_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(tinydrm_pm_suspend, tinydrm_pm_resume)
+};
+EXPORT_SYMBOL(tinydrm_simple_pm_ops);
+
+void tinydrm_spi_shutdown(struct spi_device *spi)
+{
+	struct tinydrm_device *tdev = spi_get_drvdata(spi);
+
+	tinydrm_disable(tdev);
+	tinydrm_unprepare(tdev);
+}
+EXPORT_SYMBOL(tinydrm_spi_shutdown);
diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h
new file mode 100644
index 0000000..5cd5e62
--- /dev/null
+++ b/include/drm/tinydrm/tinydrm.h
@@ -0,0 +1,143 @@
+/*
+ * 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/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct spi_device;
+struct regulator;
+struct lcdreg;
+
+struct tinydrm_device {
+	struct drm_device *base;
+	u32 width, height;
+	struct drm_simple_display_pipe pipe;
+	struct drm_panel panel;
+	struct drm_fbdev_cma *fbdev_cma;
+	unsigned fbdefio_delay_ms;
+	struct backlight_device *backlight;
+	struct regulator *regulator;
+	struct lcdreg *lcdreg;
+	bool prepared;
+	bool enabled;
+	void *dev_private;
+
+	int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags,
+		       unsigned color, struct drm_clip_rect *clips,
+		       unsigned num_clips);
+};
+
+extern const struct file_operations tinydrm_fops;
+void tinydrm_lastclose(struct drm_device *dev);
+
+#define TINYDRM_DRM_DRIVER(name_struct, name_str, desc_str, date_str) \
+static struct drm_driver name_struct = { \
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME \
+				| DRIVER_ATOMIC, \
+	.lastclose		= tinydrm_lastclose, \
+	.gem_free_object	= drm_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 = drm_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, \
+	.name			= name_str, \
+	.desc			= desc_str, \
+	.date			= date_str, \
+	.major			= 1, \
+	.minor			= 0, \
+}
+
+struct drm_framebuffer *tinydrm_fb_cma_dumb_create(struct drm_device *dev,
+					struct drm_file *file_priv,
+					const struct drm_mode_fb_cmd2 *mode_cmd);
+int tinydrm_display_pipe_init(struct tinydrm_device *tdev,
+			      const uint32_t *formats, unsigned int format_count);
+int tinydrm_panel_get_modes(struct drm_panel *panel);
+int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev,
+			  struct drm_driver *driver);
+
+static inline struct tinydrm_device *tinydrm_from_panel(struct drm_panel *panel)
+{
+	return panel->connector->dev->dev_private;
+}
+
+static inline void tinydrm_prepare(struct tinydrm_device *tdev)
+{
+	if (!tdev->prepared) {
+		drm_panel_prepare(&tdev->panel);
+		tdev->prepared = true;
+	}
+}
+
+static inline void tinydrm_unprepare(struct tinydrm_device *tdev)
+{
+	if (tdev->prepared) {
+		drm_panel_unprepare(&tdev->panel);
+		tdev->prepared = false;
+	}
+}
+
+static inline void tinydrm_enable(struct tinydrm_device *tdev)
+{
+	if (!tdev->enabled) {
+		drm_panel_enable(&tdev->panel);
+		tdev->enabled = true;
+	}
+}
+
+static inline void tinydrm_disable(struct tinydrm_device *tdev)
+{
+	if (tdev->enabled) {
+		drm_panel_disable(&tdev->panel);
+		tdev->enabled = false;
+	}
+}
+
+#ifdef CONFIG_DRM_KMS_FB_HELPER
+int tinydrm_fbdev_init(struct tinydrm_device *tdev);
+void tinydrm_fbdev_fini(struct tinydrm_device *tdev);
+void tinydrm_fbdev_restore_mode(struct tinydrm_device *tdev);
+#else
+static inline int tinydrm_fbdev_init(struct tinydrm_device *tdev)
+{
+	return 0;
+}
+
+static inline void tinydrm_fbdev_fini(struct tinydrm_device *tdev)
+{
+}
+
+static inline void tinydrm_fbdev_restore_mode(struct tinydrm_device *tdev)
+{
+}
+#endif
+
+struct backlight_device *tinydrm_of_find_backlight(struct device *dev);
+int tinydrm_panel_enable_backlight(struct drm_panel *panel);
+int tinydrm_panel_disable_backlight(struct drm_panel *panel);
+extern const struct dev_pm_ops tinydrm_simple_pm_ops;
+void tinydrm_spi_shutdown(struct spi_device *spi);
+
+#endif /* __LINUX_TINYDRM_H */
-- 
2.2.2

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

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

* [RFC v2 5/8] drm/tinydrm: Add lcd register abstraction
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (3 preceding siblings ...)
  2016-04-08 17:05 ` [RFC v2 4/8] drm: Add DRM support for tiny LCD displays Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 6/8] drm/tinydrm/lcdreg: Add SPI support Noralf Trønnes
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add LCD register abstraction for MIPI DBI/DCS like controllers.
This hides LCD controller interface implementation details.
When built with debugfs, the register is available to userspace.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/Kconfig                 |   2 +
 drivers/gpu/drm/tinydrm/Makefile                |   1 +
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig          |   3 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile         |   3 +
 drivers/gpu/drm/tinydrm/lcdreg/internal.h       |   8 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c    | 190 ++++++++++++++++
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c | 281 ++++++++++++++++++++++++
 include/drm/tinydrm/lcdreg.h                    | 126 +++++++++++
 8 files changed, 614 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Kconfig
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
 create mode 100644 include/drm/tinydrm/lcdreg.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index e26e5ed..739be06 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -9,3 +9,5 @@ menuconfig DRM_TINYDRM
 	help
 	  Choose this option if you have a tinydrm supported display.
 	  If M is selected the module will be called tinydrm.
+
+source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig"
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 7476ed1..f4a92d9 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
+obj-$(CONFIG_LCDREG)			+= lcdreg/
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
new file mode 100644
index 0000000..41383b1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
@@ -0,0 +1,3 @@
+config LCDREG
+	tristate
+	depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Makefile b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
new file mode 100644
index 0000000..c9ea774
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_LCDREG)                   += lcdreg.o
+lcdreg-y                               += lcdreg-core.o
+lcdreg-$(CONFIG_DEBUG_FS)              += lcdreg-debugfs.o
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/internal.h b/drivers/gpu/drm/tinydrm/lcdreg/internal.h
new file mode 100644
index 0000000..140fcfd
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/internal.h
@@ -0,0 +1,8 @@
+
+#ifdef CONFIG_DEBUG_FS
+void lcdreg_debugfs_init(struct lcdreg *reg);
+void lcdreg_debugfs_exit(struct lcdreg *reg);
+#else
+static inline void lcdreg_debugfs_init(struct lcdreg *reg) { }
+static inline void lcdreg_debugfs_exit(struct lcdreg *reg) { }
+#endif
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
new file mode 100644
index 0000000..bda848c
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
@@ -0,0 +1,190 @@
+//#define DEBUG
+/*
+ * 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/lcdreg.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "internal.h"
+
+/**
+ * Write to LCD register
+ *
+ * @reg: LCD register
+ * @regnr: Register number
+ * @transfer: Transfer to write
+ */
+int lcdreg_write(struct lcdreg *reg, unsigned regnr,
+		 struct lcdreg_transfer *transfer)
+{
+	if (WARN_ON_ONCE(!reg || !reg->write || !transfer))
+		return -EINVAL;
+
+	if (!transfer->width)
+		transfer->width = reg->def_width;
+
+	dev_dbg(reg->dev,
+		"lcdreg_write: regnr=0x%02x, index=%u, count=%u, width=%u\n",
+		regnr, transfer->index, transfer->count, transfer->width);
+	lcdreg_dbg_transfer_buf(transfer);
+
+	return reg->write(reg, regnr, transfer);
+}
+EXPORT_SYMBOL(lcdreg_write);
+
+/**
+ * Write 32-bit wide buffer to LCD register
+ * @reg: lcdreg
+ * @regnr: Register number
+ * @buf: Buffer to write
+ * @count: Number of words to write
+ */
+int lcdreg_write_buf32(struct lcdreg *reg, unsigned regnr, const u32 *buf,
+		       unsigned count)
+{
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.width = reg->def_width,
+		.count = count,
+	};
+	int i, ret;
+
+	if (!buf)
+		return -EINVAL;
+
+	tr.buf = kmalloc_array(count, sizeof(*buf), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	if (reg->def_width <= 8)
+		for (i = 0; i < tr.count; i++)
+			((u8 *)tr.buf)[i] = buf[i];
+	else
+		for (i = 0; i < tr.count; i++)
+			((u16 *)tr.buf)[i] = buf[i];
+	ret = lcdreg_write(reg, regnr, &tr);
+	kfree(tr.buf);
+
+	return ret;
+}
+EXPORT_SYMBOL(lcdreg_write_buf32);
+
+/**
+ * Read from LCD register
+ *
+ * @reg: LCD register
+ * @regnr: Register number
+ * @transfer: Transfer to read into
+ */
+int lcdreg_read(struct lcdreg *reg, unsigned regnr,
+		struct lcdreg_transfer *transfer)
+{
+	int ret;
+
+	if (WARN_ON_ONCE(!reg || !transfer))
+		return -EINVAL;
+
+	if (!reg->read)
+		return -EOPNOTSUPP;
+
+	if (!transfer->width)
+		transfer->width = reg->def_width;
+
+	dev_dbg(reg->dev,
+		"lcdreg_read: regnr=0x%02x, index=%u, count=%u, width=%u\n",
+		regnr, transfer->index, transfer->count, transfer->width);
+
+	ret = reg->read(reg, regnr, transfer);
+
+	lcdreg_dbg_transfer_buf(transfer);
+
+	return ret;
+}
+EXPORT_SYMBOL(lcdreg_read);
+
+/**
+ * Read from LCD register into 32-bit wide buffer
+ * @reg: LCD register
+ * @regnr: Register number
+ * @buf: Buffer to read into
+ * @count: Number of words to read
+ */
+int lcdreg_readreg_buf32(struct lcdreg *reg, unsigned regnr, u32 *buf,
+			 unsigned count)
+{
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.count = count,
+	};
+	int i, ret;
+
+	if (!buf || !count)
+		return -EINVAL;
+
+	tr.buf = kmalloc_array(count, sizeof(*buf), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	ret = lcdreg_read(reg, regnr, &tr);
+	if (ret) {
+		kfree(tr.buf);
+		return ret;
+	}
+
+	if (reg->def_width <= 8)
+		for (i = 0; i < count; i++)
+			buf[i] = ((u8 *)tr.buf)[i];
+	else
+		for (i = 0; i < count; i++)
+			buf[i] = ((u16 *)tr.buf)[i];
+	kfree(tr.buf);
+
+	return ret;
+}
+EXPORT_SYMBOL(lcdreg_readreg_buf32);
+
+static void devm_lcdreg_release(struct device *dev, void *res)
+{
+	struct lcdreg *reg = *(struct lcdreg **)res;
+
+	lcdreg_debugfs_exit(reg);
+	mutex_destroy(&reg->lock);
+}
+
+/**
+ * Device managed lcdreg initialization
+ *
+ * @dev: Device backing the LCD register
+ * @reg: LCD register
+ */
+struct lcdreg *devm_lcdreg_init(struct device *dev, struct lcdreg *reg)
+{
+	struct lcdreg **ptr;
+
+	if (!dev || !reg)
+		return ERR_PTR(-EINVAL);
+
+	ptr = devres_alloc(devm_lcdreg_release, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	*ptr = reg;
+	devres_add(dev, ptr);
+	reg->dev = dev;
+	mutex_init(&reg->lock);
+	lcdreg_debugfs_init(reg);
+
+	return reg;
+}
+EXPORT_SYMBOL(devm_lcdreg_init);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
new file mode 100644
index 0000000..9fcc13d
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
@@ -0,0 +1,281 @@
+/*
+ * 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/lcdreg.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define READ_RESULT_SIZE 16
+
+static struct dentry *lcdreg_debugfs_root;
+
+static int lcdreg_userbuf_to_u32(const char __user *user_buf, size_t count,
+				 u32 *dest, size_t dest_size)
+{
+	char *buf, *start;
+	int ret = -EINVAL;
+	int i;
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, user_buf, count)) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	/* turn whitespace into end-of-string for number parsing */
+	for (i = 0; i < count; i++)
+		if (buf[i] == ' ' || buf[i] == '\n' || buf[i] == '\t')
+			buf[i] = '\0';
+
+	i = 0;
+	start = buf;
+	while (start < buf + count) {
+		/* skip "whitespace" */
+		if (*start == '\0') {
+			start++;
+			continue;
+		}
+
+		if (i == dest_size) {
+			ret = -EFBIG;
+			break;
+		}
+
+		ret = kstrtou32(start, 0, &dest[i++]);
+		if (ret)
+			break;
+
+		/* move past this number */
+		while (*start != '\0')
+			start++;
+	};
+
+	kfree(buf);
+
+	return ret ? : i;
+}
+
+static ssize_t lcdreg_debugfs_write_file(struct file *file,
+					 const char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+	int ret;
+	u32 txbuf[128];
+
+	ret = lcdreg_userbuf_to_u32(user_buf, count, txbuf, ARRAY_SIZE(txbuf));
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&reg->lock);
+	ret = lcdreg_write_buf32(reg, txbuf[0], txbuf + 1, ret - 1);
+	mutex_unlock(&reg->lock);
+
+	return ret ? : count;
+}
+
+static const struct file_operations lcdreg_debugfs_write_fops = {
+	.open = simple_open,
+	.write = lcdreg_debugfs_write_file,
+	.llseek = default_llseek,
+};
+
+static ssize_t lcdreg_debugfs_read_wr(struct file *file,
+				      const char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+	int ret;
+
+	ret = lcdreg_userbuf_to_u32(user_buf, count,
+				    &reg->debugfs_read_reg, 1);
+
+	return ret < 0 ? ret : count;
+}
+
+
+static int lcdreg_debugfs_readreg(struct lcdreg *reg)
+{
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.width = reg->debugfs_read_width,
+		.count = 1,
+	};
+	char *buf = reg->debugfs_read_result;
+	int ret;
+
+	tr.buf = kmalloc(lcdreg_bytes_per_word(tr.width), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	mutex_lock(&reg->lock);
+	ret = lcdreg_read(reg, reg->debugfs_read_reg, &tr);
+	mutex_unlock(&reg->lock);
+	if (ret)
+		goto error_out;
+
+	switch (tr.width) {
+	case 8:
+		snprintf(buf, READ_RESULT_SIZE, "0x%02x\n", *(u8 *)tr.buf);
+		break;
+	case 16:
+		snprintf(buf, READ_RESULT_SIZE, "0x%04x\n", *(u16 *)tr.buf);
+		break;
+	case 24:
+	case 32:
+		snprintf(buf, READ_RESULT_SIZE, "0x%08x\n", *(u32 *)tr.buf);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+error_out:
+	kfree(tr.buf);
+
+	return ret;
+}
+
+static ssize_t lcdreg_debugfs_read_rd(struct file *file,
+				      char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+	int ret;
+
+	if (*ppos < 0 || !count)
+		return -EINVAL;
+
+	if (*reg->debugfs_read_result == '\0') {
+		ret = lcdreg_debugfs_readreg(reg);
+		if (ret)
+			return ret;
+	}
+
+	if (*ppos >= strlen(reg->debugfs_read_result)) {
+		*reg->debugfs_read_result = '\0';
+		return 0;
+	}
+
+	return simple_read_from_buffer(user_buf, count, ppos,
+				       reg->debugfs_read_result,
+				       strlen(reg->debugfs_read_result));
+}
+
+static const struct file_operations lcdreg_debugfs_read_fops = {
+	.open = simple_open,
+	.read = lcdreg_debugfs_read_rd,
+	.write = lcdreg_debugfs_read_wr,
+	.llseek = default_llseek,
+};
+
+static ssize_t lcdreg_debugfs_reset_wr(struct file *file,
+				       const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct lcdreg *reg = file->private_data;
+
+	lcdreg_reset(reg);
+
+	return count;
+}
+
+static const struct file_operations lcdreg_debugfs_reset_fops = {
+	.open = simple_open,
+	.write = lcdreg_debugfs_reset_wr,
+	.llseek = default_llseek,
+};
+
+static int lcdreg_debugfs_readwidth_set(void *data, u64 val)
+{
+	struct lcdreg *reg = data;
+
+	reg->debugfs_read_width = val;
+
+	return 0;
+}
+
+static int lcdreg_debugfs_readwidth_get(void *data, u64 *val)
+{
+	struct lcdreg *reg = data;
+
+	/*
+	* def_width is not set when lcdreg_debugfs_init() is run, it's
+	* set later by the controller init code. Hence the need for this
+	* late assignment.
+	*/
+	if (!reg->debugfs_read_width)
+		reg->debugfs_read_width = reg->def_width;
+
+	*val = reg->debugfs_read_width;
+
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(lcdreg_debugfs_readwidth_fops,
+			lcdreg_debugfs_readwidth_get,
+			lcdreg_debugfs_readwidth_set, "%llu\n");
+
+void lcdreg_debugfs_init(struct lcdreg *reg)
+{
+	if (IS_ERR_OR_NULL(lcdreg_debugfs_root))
+		return;
+
+	reg->debugfs_read_result = devm_kzalloc(reg->dev, READ_RESULT_SIZE,
+						GFP_KERNEL);
+	if (!reg->debugfs_read_result)
+		return;
+
+	reg->debugfs = debugfs_create_dir(dev_name(reg->dev),
+					  lcdreg_debugfs_root);
+	if (!reg->debugfs) {
+		dev_warn(reg->dev, "Failed to create debugfs directory\n");
+		return;
+	}
+
+	debugfs_create_file("write", 0220, reg->debugfs, reg,
+			    &lcdreg_debugfs_write_fops);
+	if (reg->read) {
+		debugfs_create_file("read_width", 0660, reg->debugfs, reg,
+				    &lcdreg_debugfs_readwidth_fops);
+		debugfs_create_file("read", 0660, reg->debugfs, reg,
+				    &lcdreg_debugfs_read_fops);
+	}
+	if (reg->reset) {
+		debugfs_create_file("reset", 0220, reg->debugfs, reg,
+				    &lcdreg_debugfs_reset_fops);
+	}
+}
+
+void lcdreg_debugfs_exit(struct lcdreg *reg)
+{
+	debugfs_remove_recursive(reg->debugfs);
+}
+
+static int lcdreg_debugfs_module_init(void)
+{
+	lcdreg_debugfs_root = debugfs_create_dir("lcdreg", NULL);
+	if (!lcdreg_debugfs_root)
+		pr_warn("lcdreg: Failed to create debugfs root\n");
+
+	return 0;
+}
+module_init(lcdreg_debugfs_module_init);
+
+static void lcdreg_debugfs_module_exit(void)
+{
+	debugfs_remove_recursive(lcdreg_debugfs_root);
+}
+module_exit(lcdreg_debugfs_module_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/lcdreg.h b/include/drm/tinydrm/lcdreg.h
new file mode 100644
index 0000000..74e5e50
--- /dev/null
+++ b/include/drm/tinydrm/lcdreg.h
@@ -0,0 +1,126 @@
+/*
+ * 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_LCDREG_H
+#define __LINUX_LCDREG_H
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+
+/**
+ * struct lcdreg_transfer - LCD register transfer
+ * @index: register index (address)
+ *         Known under the following names:
+ *         D/C (command=0, data=1)
+ *         RS (register selection: index=0, data=1)
+ *         D/I (data/index: index=0, data=1)
+ * @buf: data array to transfer
+ * @count: number of items in array
+ * @width: override default regwidth
+ */
+struct lcdreg_transfer {
+	unsigned index;
+	void *buf;
+	unsigned count;
+	unsigned width;
+};
+
+/**
+ * struct lcdreg - interface to LCD register
+ * @dev: device interface
+ * @lock: mutex for register access locking
+ * @def_width: default register width
+ * @bits_per_word_mask: Bitmask of bits per word supported by the hardware.
+ *                      The driver can emulate more word widths.
+ * @readable: register is readable
+ * @little_endian: register has little endian byte order
+ * @write: write to register
+ * @read: read from register (optional)
+ * @reset: reset controller (optional)
+ */
+struct lcdreg {
+	struct device *dev;
+	struct mutex lock;
+	unsigned def_width;
+	bool readable;
+	bool little_endian;
+	u32 bits_per_word_mask;
+#define LCDREG_BPW_MASK(bits) BIT((bits) - 1)
+
+	int (*write)(struct lcdreg *reg, unsigned regnr,
+		     struct lcdreg_transfer *transfer);
+	int (*read)(struct lcdreg *reg, unsigned regnr,
+		    struct lcdreg_transfer *transfer);
+	void (*reset)(struct lcdreg *reg);
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debugfs;
+	u32 debugfs_read_width;
+	u32 debugfs_read_reg;
+	char *debugfs_read_result;
+#endif
+};
+
+struct lcdreg *devm_lcdreg_init(struct device *dev, struct lcdreg *reg);
+int lcdreg_write(struct lcdreg *reg, unsigned regnr,
+		 struct lcdreg_transfer *transfer);
+int lcdreg_write_buf32(struct lcdreg *reg, unsigned regnr, const u32 *data,
+		       unsigned count);
+
+#define lcdreg_writereg(lcdreg, regnr, seq...) \
+({\
+	u32 d[] = { seq };\
+	lcdreg_write_buf32(lcdreg, regnr, d, ARRAY_SIZE(d));\
+})
+
+int lcdreg_read(struct lcdreg *reg, unsigned regnr,
+		struct lcdreg_transfer *transfer);
+int lcdreg_readreg_buf32(struct lcdreg *reg, unsigned regnr, u32 *buf,
+			 unsigned count);
+
+static inline void lcdreg_reset(struct lcdreg *reg)
+{
+	if (reg->reset)
+		reg->reset(reg);
+}
+
+static inline bool lcdreg_is_readable(struct lcdreg *reg)
+{
+	return reg->readable;
+}
+
+static inline unsigned lcdreg_bytes_per_word(unsigned bits_per_word)
+{
+	if (bits_per_word <= 8)
+		return 1;
+	else if (bits_per_word <= 16)
+		return 2;
+	else /* bits_per_word <= 32 */
+		return 4;
+}
+
+static inline bool lcdreg_bpw_supported(struct lcdreg *reg, unsigned bpw)
+{
+	return LCDREG_BPW_MASK(bpw) & reg->bits_per_word_mask;
+}
+
+#if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG)
+static inline void lcdreg_dbg_transfer_buf(struct lcdreg_transfer *tr)
+{
+	int groupsize = lcdreg_bytes_per_word(tr->width);
+	size_t len = min_t(size_t, 32, tr->count * groupsize);
+
+	print_hex_dump_debug("    buf=", DUMP_PREFIX_NONE, 32, groupsize,
+			     tr->buf, len, false);
+}
+#else
+static inline void lcdreg_dbg_transfer_buf(struct lcdreg_transfer *tr) { }
+#endif /* DEBUG || CONFIG_DYNAMIC_DEBUG */
+
+#endif /* __LINUX_LCDREG_H */
-- 
2.2.2

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

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

* [RFC v2 6/8] drm/tinydrm/lcdreg: Add SPI support
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (4 preceding siblings ...)
  2016-04-08 17:05 ` [RFC v2 5/8] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 7/8] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add SPI bus support to lcdreg.
Supports the following protocols:
- MIPI DBI type C interface option 1 (3-wire) and option 3 (4-wire).
- Option 3 also fits some controllers where all registers are 16-bit.
- 8/16-bit register transfers that need to start with a special startbyte.

It also supports emulation for 16 and 9-bit words if the SPI controller
doesn't support it.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/lcdreg/Kconfig      |   5 +
 drivers/gpu/drm/tinydrm/lcdreg/Makefile     |   2 +
 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c | 720 ++++++++++++++++++++++++++++
 include/drm/tinydrm/lcdreg-spi.h            |  63 +++
 4 files changed, 790 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
 create mode 100644 include/drm/tinydrm/lcdreg-spi.h

diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
index 41383b1..ade465f 100644
--- a/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Kconfig
@@ -1,3 +1,8 @@
 config LCDREG
 	tristate
 	depends on GPIOLIB || COMPILE_TEST
+
+config LCDREG_SPI
+	tristate
+	depends on SPI
+	select LCDREG
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/Makefile b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
index c9ea774..4e14571 100644
--- a/drivers/gpu/drm/tinydrm/lcdreg/Makefile
+++ b/drivers/gpu/drm/tinydrm/lcdreg/Makefile
@@ -1,3 +1,5 @@
 obj-$(CONFIG_LCDREG)                   += lcdreg.o
 lcdreg-y                               += lcdreg-core.o
 lcdreg-$(CONFIG_DEBUG_FS)              += lcdreg-debugfs.o
+
+obj-$(CONFIG_LCDREG_SPI)               += lcdreg-spi.o
diff --git a/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
new file mode 100644
index 0000000..5e9d8fe1
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
@@ -0,0 +1,720 @@
+//#define VERBOSE_DEBUG
+//#define DEBUG
+/*
+ * 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 <asm/unaligned.h>
+#include <drm/tinydrm/lcdreg-spi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+static unsigned txlen;
+module_param(txlen, uint, 0);
+MODULE_PARM_DESC(txlen, "Transmit chunk length");
+
+static unsigned long bpwm;
+module_param(bpwm, ulong, 0);
+MODULE_PARM_DESC(bpwm, "Override SPI master bits_per_word_mask");
+
+struct lcdreg_spi {
+	struct lcdreg reg;
+	enum lcdreg_spi_mode mode;
+unsigned txbuflen;
+	void *txbuf_dc;
+	unsigned id;
+	u32 quirks;
+	u8 (*startbyte)(struct lcdreg *reg, struct lcdreg_transfer *tr,
+			bool read);
+	struct gpio_desc *dc;
+	struct gpio_desc *reset;
+};
+
+static inline struct lcdreg_spi *to_lcdreg_spi(struct lcdreg *reg)
+{
+	return reg ? container_of(reg, struct lcdreg_spi, reg) : NULL;
+}
+
+#ifdef VERBOSE_DEBUG
+static void lcdreg_vdbg_dump_spi(const struct device *dev, struct spi_message *m, u8 *startbyte)
+{
+	struct spi_transfer *tmp;
+	struct list_head *pos;
+	int i = 0;
+
+	if (startbyte)
+		dev_dbg(dev, "spi_message: startbyte=0x%02X\n", startbyte[0]);
+	else
+		dev_dbg(dev, "spi_message:\n");
+
+	list_for_each(pos, &m->transfers) {
+		tmp = list_entry(pos, struct spi_transfer, transfer_list);
+		if (tmp->tx_buf)
+			pr_debug("    tr%i: bpw=%i, len=%u, tx_buf(%p)=[%*ph]\n", i, tmp->bits_per_word, tmp->len, tmp->tx_buf, tmp->len > 64 ? 64 : tmp->len, tmp->tx_buf);
+		if (tmp->rx_buf)
+			pr_debug("    tr%i: bpw=%i, len=%u, rx_buf(%p)=[%*ph]\n", i, tmp->bits_per_word, tmp->len, tmp->rx_buf, tmp->len > 64 ? 64 : tmp->len, tmp->rx_buf);
+		i++;
+	}
+}
+#else
+static void lcdreg_vdbg_dump_spi(const struct device *dev, struct spi_message *m, u8 *startbyte)
+{
+}
+#endif
+
+static int lcdreg_spi_do_transfer(struct lcdreg *reg,
+				  struct lcdreg_transfer *transfer)
+{
+	struct spi_device *sdev = to_spi_device(reg->dev);
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	void *buf = transfer->buf;
+	size_t len = transfer->count * lcdreg_bytes_per_word(transfer->width);
+	size_t max = txlen ? : sdev->master->max_dma_len;
+	size_t room_left_in_page = PAGE_SIZE - offset_in_page(buf);
+	size_t chunk = min_t(size_t, len, max);
+	struct spi_message m;
+	struct spi_transfer *tr;
+	u8 *startbuf = NULL;
+	int ret, i;
+
+	dev_dbg(reg->dev, "%s: index=%u, count=%u, width=%u\n",
+		__func__, transfer->index, transfer->count, transfer->width);
+	lcdreg_dbg_transfer_buf(transfer);
+
+	tr = kzalloc(2 * sizeof(*tr), GFP_KERNEL);
+	if (!tr)
+		return -ENOMEM;
+
+	/* slow down commands? */
+	if (!transfer->index && (spi->quirks & LCDREG_SLOW_INDEX0_WRITE))
+		for (i = 0; i < 2; i++)
+			tr[i].speed_hz = min_t(u32, 2000000,
+					       sdev->max_speed_hz / 2);
+
+	if (spi->mode == LCDREG_SPI_STARTBYTE) {
+		startbuf = kmalloc(1, GFP_KERNEL);
+		if (!startbuf) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		*startbuf = spi->startbyte(reg, transfer, false);
+	}
+
+	/*
+	 * transfer->buf can be unaligned to the page boundary for partial
+	 * updates when videomem is sent directly (no buffering).
+	 * Spi core can sg map the buffer for dma and relies on vmalloc'ed
+	 * memory to be page aligned.
+	 */
+//pr_debug("%s: PAGE_ALIGNED=%d, len > room_left_in_page= %d > %d = %d, chunk=%zu\n", __func__, PAGE_ALIGNED(buf), len, room_left_in_page, len > room_left_in_page, chunk);
+	if (!PAGE_ALIGNED(buf) && len > room_left_in_page) {
+//size_t chunk0 = chunk;
+
+		if (chunk >= room_left_in_page) {
+			chunk = room_left_in_page;
+//pr_debug("%s: chunk: %zu -> %zu, room_left_in_page=%zu\n\n", __func__, chunk0, chunk, room_left_in_page);
+		} else {
+			chunk = room_left_in_page % chunk ? : chunk;
+//pr_debug("%s: chunk: %zu -> %zu, room_left_in_page=%zu, room_left_in_page %% chunk=%zu\n\n", __func__, chunk0, chunk, room_left_in_page, room_left_in_page % chunk);
+		}
+	}
+
+	do {
+		i = 0;
+		spi_message_init(&m);
+
+		if (spi->mode == LCDREG_SPI_STARTBYTE) {
+			tr[i].tx_buf = startbuf;
+			tr[i].len = 1;
+			tr[i].bits_per_word = 8;
+			spi_message_add_tail(&tr[i++], &m);
+		}
+
+		tr[i].tx_buf = buf;
+		tr[i].len = chunk;
+		tr[i].bits_per_word = transfer->width;
+		buf += chunk;
+		len -= chunk;
+		spi_message_add_tail(&tr[i], &m);
+
+		lcdreg_vdbg_dump_spi(&sdev->dev, &m, startbuf);
+		ret = spi_sync(sdev, &m);
+		if (ret)
+			goto out;
+
+		chunk = min_t(size_t, len, max);
+	} while (len);
+
+out:
+	kfree(tr);
+	kfree(startbuf);
+
+	return ret;
+}
+
+static int lcdreg_spi_transfer_emulate9(struct lcdreg *reg,
+					struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct lcdreg_transfer tr = {
+		.index = transfer->index,
+		.width = 8,
+		.count = transfer->count,
+	};
+	u16 *src = transfer->buf;
+	unsigned added = 0;
+	int i, ret;
+	u8 *dst;
+
+	if (transfer->count % 8) {
+		dev_err_once(reg->dev,
+			     "transfer->count=%u must be divisible by 8\n",
+			     transfer->count);
+		return -EINVAL;
+	}
+
+	dst = kzalloc(spi->txbuflen, GFP_KERNEL);
+	if (!dst)
+		return -ENOMEM;
+
+	tr.buf = dst;
+
+	for (i = 0; i < transfer->count; i += 8) {
+		u64 tmp = 0;
+		int j, bits = 63;
+
+		for (j = 0; j < 7; j++) {
+			u64 bit9 = (*src & 0x100) ? 1 : 0;
+			u64 val = *src++ & 0xFF;
+
+			tmp |= bit9 << bits;
+			bits -= 8;
+			tmp |= val << bits--;
+		}
+		tmp |= ((*src & 0x100) ? 1 : 0);
+		*(u64 *)dst = cpu_to_be64(tmp);
+		dst += 8;
+		*dst++ = *src++ & 0xFF;
+		added++;
+	}
+	tr.count += added;
+	ret = lcdreg_spi_do_transfer(reg, &tr);
+	kfree(tr.buf);
+
+	return ret;
+}
+
+static int lcdreg_spi_transfer_emulate16(struct lcdreg *reg,
+					 struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	unsigned to_copy, remain = transfer->count;
+	struct lcdreg_transfer tr = {
+		.index = transfer->index,
+		.width = 8,
+	};
+	u16 *data16 = transfer->buf;
+	u16 *txbuf16;
+	int i, ret = 0;
+
+	txbuf16 = kzalloc(spi->txbuflen, GFP_KERNEL);
+	if (!txbuf16)
+		return -ENOMEM;
+
+	tr.buf = txbuf16;
+
+	while (remain) {
+		to_copy = min(remain, spi->txbuflen / 2);
+		dev_dbg(reg->dev, "    to_copy=%zu, remain=%zu\n",
+					to_copy, remain - to_copy);
+
+		for (i = 0; i < to_copy; i++)
+			txbuf16[i] = swab16(data16[i]);
+
+		data16 = data16 + to_copy;
+		tr.count = to_copy * 2;
+		ret = lcdreg_spi_do_transfer(reg, &tr);
+		if (ret < 0)
+			goto out;
+		remain -= to_copy;
+	}
+
+out:
+	kfree(tr.buf);
+
+	return ret;
+}
+
+static int lcdreg_spi_transfer(struct lcdreg *reg,
+			       struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	bool mach_little_endian;
+
+#ifdef __LITTLE_ENDIAN
+	mach_little_endian = true;
+#endif
+	if (spi->dc)
+		gpiod_set_value_cansleep(spi->dc, transfer->index);
+
+	if (lcdreg_bpw_supported(reg, transfer->width))
+		return lcdreg_spi_do_transfer(reg, transfer);
+
+	if (transfer->width == 9)
+		return lcdreg_spi_transfer_emulate9(reg, transfer);
+
+	if ((mach_little_endian == reg->little_endian) &&
+	    (transfer->width % 8 == 0)) {
+		/* the byte order matches */
+		transfer->count *= transfer->width / 8;
+		transfer->width = 8;
+		return lcdreg_spi_do_transfer(reg, transfer);
+	}
+
+	if (mach_little_endian != reg->little_endian && transfer->width == 16)
+		return lcdreg_spi_transfer_emulate16(reg, transfer);
+
+	dev_err_once(reg->dev, "width=%u is not supported (%u:%u)\n",
+		     transfer->width, mach_little_endian, reg->little_endian);
+
+	return -EINVAL;
+}
+
+static int lcdreg_spi_write_9bit_dc(struct lcdreg *reg,
+				    struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct lcdreg_transfer tr = {
+		.index = transfer->index,
+	};
+	u8 *data8 = transfer->buf;
+	u16 *data16 = transfer->buf;
+	unsigned width;
+	u16 *txbuf16;
+	unsigned remain;
+	unsigned tx_array_size;
+	unsigned to_copy;
+	int pad, i, ret;
+
+width = transfer->width;
+
+	if (width != 8 && width != 16) {
+		dev_err(reg->dev, "transfer width %u is not supported\n",
+								width);
+		return -EINVAL;
+	}
+
+	if (!spi->txbuf_dc) {
+		spi->txbuf_dc = devm_kzalloc(reg->dev, spi->txbuflen,
+							GFP_KERNEL);
+		if (!spi->txbuf_dc)
+			return -ENOMEM;
+		dev_info(reg->dev, "allocated %u KiB 9-bit dc buffer\n",
+						spi->txbuflen / 1024);
+	}
+
+	tr.buf = spi->txbuf_dc;
+	txbuf16 = spi->txbuf_dc;
+	remain = transfer->count;
+	if (width == 8)
+		tx_array_size = spi->txbuflen / 2;
+	else
+		tx_array_size = spi->txbuflen / 4;
+
+	/* If we're emulating 9-bit, the buffer has to be divisible by 8.
+	   Pad with no-ops if necessary (assuming here that zero is a no-op)
+	   FIX: If video buf isn't divisible by 8, it will break.
+	 */
+	if (!lcdreg_bpw_supported(reg, 9) && width == 8 &&
+						remain < tx_array_size) {
+		pad = (transfer->count % 8) ? 8 - (transfer->count % 8) : 0;
+		if (transfer->index == 0)
+			for (i = 0; i < pad; i++)
+				*txbuf16++ = 0x000;
+		for (i = 0; i < remain; i++) {
+			*txbuf16 = *data8++;
+			if (transfer->index)
+				*txbuf16++ |= 0x0100;
+		}
+		if (transfer->index == 1)
+			for (i = 0; i < pad; i++)
+				*txbuf16++ = 0x000;
+		tr.width = 9;
+		tr.count = pad + remain;
+		return lcdreg_spi_transfer(reg, &tr);
+	}
+
+	while (remain) {
+		to_copy = remain > tx_array_size ? tx_array_size : remain;
+		remain -= to_copy;
+		dev_dbg(reg->dev, "    to_copy=%zu, remain=%zu\n",
+					to_copy, remain);
+
+		if (width == 8) {
+			for (i = 0; i < to_copy; i++) {
+				txbuf16[i] = *data8++;
+				if (transfer->index)
+					txbuf16[i] |= 0x0100;
+			}
+		} else {
+			for (i = 0; i < (to_copy * 2); i += 2) {
+				txbuf16[i]     = *data16 >> 8;
+				txbuf16[i + 1] = *data16++ & 0xFF;
+				if (transfer->index) {
+					txbuf16[i]     |= 0x0100;
+					txbuf16[i + 1] |= 0x0100;
+				}
+			}
+		}
+		tr.buf = spi->txbuf_dc;
+		tr.width = 9;
+		tr.count = to_copy * 2;
+		ret = lcdreg_spi_transfer(reg, &tr);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int lcdreg_spi_write(struct lcdreg *reg, unsigned regnr,
+			    struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct lcdreg_transfer tr = {
+		.width = reg->def_width,
+		.count = 1,
+	};
+	int ret;
+
+	tr.buf = kmalloc(sizeof(u32), GFP_KERNEL);
+	if (!tr.buf)
+		return -ENOMEM;
+
+	if (reg->def_width <= 8)
+		((u8 *)tr.buf)[0] = regnr;
+	else
+		((u16 *)tr.buf)[0] = regnr;
+
+	if (spi->mode == LCDREG_SPI_3WIRE)
+		ret = lcdreg_spi_write_9bit_dc(reg, &tr);
+	else
+		ret = lcdreg_spi_transfer(reg, &tr);
+	kfree(tr.buf);
+	if (ret || !transfer || !transfer->count)
+		return ret;
+
+	if (!transfer->width)
+		transfer->width = reg->def_width;
+	if (spi->mode == LCDREG_SPI_3WIRE)
+		ret = lcdreg_spi_write_9bit_dc(reg, transfer);
+	else
+		ret = lcdreg_spi_transfer(reg, transfer);
+
+	return ret;
+}
+
+/*
+   <CMD> <DM> <PA>
+   CMD = Command
+   DM = Dummy read
+   PA = Parameter or display data
+
+   ST7735R read:
+     Parallel: <CMD> <DM> <PA>
+     SPI: 8-bit plain, 24- and 32-bit needs 1 dummy clock cycle
+
+   ILI9320:
+     Parallel: no dummy read, page 51 in datasheet
+     SPI (startbyte): One byte of invalid dummy data read after the start byte.
+
+   ILI9340:
+     Parallel: no info about dummy read
+     SPI: same as ST7735R
+
+   ILI9341:
+     Parallel: no info about dummy read
+     SPI: same as ST7735R
+
+   SSD1289:
+     Parallel: 1 dummy read
+
+ */
+
+static int lcdreg_spi_read_startbyte(struct lcdreg *reg, unsigned regnr,
+				     struct lcdreg_transfer *transfer)
+{
+	struct spi_device *sdev = to_spi_device(reg->dev);
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct spi_message m;
+	struct spi_transfer trtx = {
+		.speed_hz = min_t(u32, 2000000, sdev->max_speed_hz / 2),
+		.bits_per_word = 8,
+		.len = 1,
+	};
+	struct spi_transfer trrx = {
+		.speed_hz = trtx.speed_hz,
+		.bits_per_word = 8,
+		.len = (transfer->count * 2) + 1,
+	};
+	u8 *txbuf, *rxbuf;
+	int i, ret;
+
+	if (!reg->readable)
+		return -EACCES;
+
+	if (!transfer || !transfer->count)
+		return -EINVAL;
+
+	transfer->width = transfer->width ? : reg->def_width;
+	if (WARN_ON(transfer->width != 16))
+		return -EINVAL;
+
+	ret = lcdreg_writereg(reg, regnr);
+	if (ret)
+		return ret;
+
+	txbuf = kzalloc(1, GFP_KERNEL);
+	if (!txbuf)
+		return -ENOMEM;
+
+	rxbuf = kzalloc(trrx.len, GFP_KERNEL);
+	if (!rxbuf) {
+		kfree(txbuf);
+		return -ENOMEM;
+	}
+
+	*txbuf = spi->startbyte(reg, transfer, true);
+
+	trtx.tx_buf = txbuf;
+	trrx.rx_buf = rxbuf;
+	spi_message_init(&m);
+	spi_message_add_tail(&trtx, &m);
+	spi_message_add_tail(&trrx, &m);
+	ret = spi_sync(sdev, &m);
+	lcdreg_vdbg_dump_spi(&sdev->dev, &m, txbuf);
+	kfree(txbuf);
+	if (ret) {
+		kfree(rxbuf);
+		return ret;
+	}
+
+	rxbuf++;
+	for (i = 0; i < transfer->count; i++) {
+		((u16 *)transfer->buf)[i] = get_unaligned_be16(rxbuf);
+		rxbuf += 2;
+	}
+	kfree(trrx.rx_buf);
+
+	return 0;
+}
+
+static int lcdreg_spi_read(struct lcdreg *reg, unsigned regnr,
+			   struct lcdreg_transfer *transfer)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+	struct spi_device *sdev = to_spi_device(reg->dev);
+	struct spi_message m;
+	struct spi_transfer trtx = {
+		.speed_hz = min_t(u32, 2000000, sdev->max_speed_hz / 2),
+		.bits_per_word = reg->def_width,
+		.len = 1,
+	};
+	struct spi_transfer trrx = {
+		.speed_hz = trtx.speed_hz,
+		.rx_buf = transfer->buf,
+		.len = transfer->count,
+	};
+	void *txbuf = NULL;
+	int i, ret;
+
+	transfer->width = transfer->width ? : reg->def_width;
+	if (WARN_ON(transfer->width != reg->def_width || !transfer->count))
+		return -EINVAL;
+
+	if (!reg->readable)
+		return -EACCES;
+
+	txbuf = kzalloc(16, GFP_KERNEL);
+	if (!txbuf)
+		return -ENOMEM;
+
+	spi_message_init(&m);
+	trtx.tx_buf = txbuf;
+	trrx.bits_per_word = transfer->width;
+
+	if (spi->mode == LCDREG_SPI_4WIRE) {
+		if (trtx.bits_per_word == 8) {
+			*(u8 *)txbuf = regnr;
+		} else if (trtx.bits_per_word == 16) {
+			if (lcdreg_bpw_supported(reg, trtx.bits_per_word)) {
+				*(u16 *)txbuf = regnr;
+			} else {
+				*(u16 *)txbuf = cpu_to_be16(regnr);
+				trtx.bits_per_word = 8;
+				trtx.len = 2;
+			}
+		} else {
+			return -EINVAL;
+		}
+		gpiod_set_value_cansleep(spi->dc, 0);
+	} else if (spi->mode == LCDREG_SPI_3WIRE) {
+		if (lcdreg_bpw_supported(reg, 9)) {
+			trtx.bits_per_word = 9;
+			*(u16 *)txbuf = regnr; /* dc=0 */
+		} else {
+			/* 8x 9-bit words, pad with leading zeroes (no-ops) */
+			((u8 *)txbuf)[8] = regnr;
+		}
+	} else {
+		kfree(txbuf);
+		return -EINVAL;
+	}
+	spi_message_add_tail(&trtx, &m);
+
+	if (spi->mode == LCDREG_SPI_4WIRE && transfer->index &&
+	    !(spi->quirks & LCDREG_INDEX0_ON_READ)) {
+		trtx.cs_change = 1; /* not always supported */
+		lcdreg_vdbg_dump_spi(&sdev->dev, &m, NULL);
+		ret = spi_sync(sdev, &m);
+		if (ret) {
+			kfree(txbuf);
+			return ret;
+		}
+		gpiod_set_value_cansleep(spi->dc, 1);
+		spi_message_init(&m);
+	}
+
+	spi_message_add_tail(&trrx, &m);
+	ret = spi_sync(sdev, &m);
+	lcdreg_vdbg_dump_spi(&sdev->dev, &m, NULL);
+	kfree(txbuf);
+	if (ret)
+		return ret;
+
+	if (!lcdreg_bpw_supported(reg, trrx.bits_per_word) &&
+						(trrx.bits_per_word == 16))
+		for (i = 0; i < transfer->count; i++)
+			((u16 *)transfer->buf)[i] = be16_to_cpu(((u16 *)transfer->buf)[i]);
+
+	return 0;
+}
+
+static void lcdreg_spi_reset(struct lcdreg *reg)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+
+	if (!spi->reset)
+		return;
+
+	dev_info(reg->dev, "%s()\n", __func__);
+	gpiod_set_value_cansleep(spi->reset, 0);
+	msleep(20);
+	gpiod_set_value_cansleep(spi->reset, 1);
+	msleep(120);
+}
+
+/* Default startbyte implementation: | 0 | 1 | 1 | 1 | 0 | ID | RS | RW | */
+static u8 lcdreg_spi_startbyte(struct lcdreg *reg, struct lcdreg_transfer *tr,
+			       bool read)
+{
+	struct lcdreg_spi *spi = to_lcdreg_spi(reg);
+
+	return 0x70 | (!!spi->id << 2) | (!!tr->index << 1) | read;
+}
+
+struct lcdreg *devm_lcdreg_spi_init(struct spi_device *sdev,
+				    const struct lcdreg_spi_config *config)
+{
+	char *dc_name = config->dc_name ? : "dc";
+	struct device *dev = &sdev->dev;
+	struct lcdreg_spi *spi;
+	struct lcdreg *reg;
+
+	if (txlen) {
+		if (txlen < PAGE_SIZE) {
+			txlen = rounddown_pow_of_two(txlen);
+			if (txlen < 64)
+				txlen = 64;
+		} else {
+			txlen &= PAGE_MASK;
+		}
+	}
+dev_info(dev, "txlen: %u\n", txlen);
+
+	spi = devm_kzalloc(dev, sizeof(*spi), GFP_KERNEL);
+	if (!spi)
+		return ERR_PTR(-ENOMEM);
+
+	reg = &spi->reg;
+	if (bpwm) {
+		reg->bits_per_word_mask = bpwm;
+	} else {
+		if (sdev->master->bits_per_word_mask)
+			reg->bits_per_word_mask = sdev->master->bits_per_word_mask;
+		else
+			reg->bits_per_word_mask = SPI_BPW_MASK(8);
+	}
+	dev_dbg(dev, "bits_per_word_mask: 0x%08x", reg->bits_per_word_mask);
+
+	reg->def_width = config->def_width;
+	reg->readable = config->readable;
+	reg->reset = lcdreg_spi_reset;
+	reg->write = lcdreg_spi_write;
+	reg->read = lcdreg_spi_read;
+
+	spi->mode = config->mode;
+	spi->quirks = config->quirks;
+	spi->id = config->id;
+	if (!spi->txbuflen)
+		spi->txbuflen = PAGE_SIZE;
+
+	switch (spi->mode) {
+	case LCDREG_SPI_4WIRE:
+		spi->dc = devm_gpiod_get(dev, dc_name, GPIOD_OUT_LOW);
+		if (IS_ERR(spi->dc)) {
+			dev_err(dev, "Failed to get gpio '%s'\n", dc_name);
+			return ERR_CAST(spi->dc);
+		}
+		break;
+	case LCDREG_SPI_3WIRE:
+		break;
+	case LCDREG_SPI_STARTBYTE:
+		reg->read = lcdreg_spi_read_startbyte;
+		spi->startbyte = config->startbyte ? : lcdreg_spi_startbyte;
+		break;
+	default:
+		dev_err(dev, "Mode is not supported: %u\n", spi->mode);
+		return ERR_PTR(-EINVAL);
+	}
+
+	spi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(spi->reset)) {
+		dev_err(dev, "Failed to get gpio 'reset'\n");
+		return ERR_CAST(spi->reset);
+	}
+
+	pr_debug("spi->reg.def_width: %u\n", reg->def_width);
+	if (spi->reset)
+		pr_debug("spi->reset: %i\n", desc_to_gpio(spi->reset));
+	if (spi->dc)
+		pr_debug("spi->dc: %i\n", desc_to_gpio(spi->dc));
+	pr_debug("spi->mode: %u\n", spi->mode);
+
+	return devm_lcdreg_init(dev, reg);
+}
+EXPORT_SYMBOL_GPL(devm_lcdreg_spi_init);
+
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/lcdreg-spi.h b/include/drm/tinydrm/lcdreg-spi.h
new file mode 100644
index 0000000..ba5d492
--- /dev/null
+++ b/include/drm/tinydrm/lcdreg-spi.h
@@ -0,0 +1,63 @@
+/*
+ * 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_LCDREG_SPI_H
+#define __LINUX_LCDREG_SPI_H
+
+#include <drm/tinydrm/lcdreg.h>
+#include <linux/spi/spi.h>
+
+/**
+ * enum lcdreg_spi_mode - SPI interface mode
+ * @LCDREG_SPI_4WIRE: 8-bit + D/CX line, MIPI DBI Type C option 3
+ * @LCDREG_SPI_3WIRE: 9-bit inc. D/CX bit, MIPI DBI Type C option 1
+ * @LCDREG_SPI_STARTBYTE: Startbyte header on every transaction (non MIPI)
+ */
+enum lcdreg_spi_mode {
+	LCDREG_SPI_NOMODE = 0,
+	LCDREG_SPI_4WIRE,
+	LCDREG_SPI_3WIRE,
+	LCDREG_SPI_STARTBYTE,
+};
+
+/**
+ * struct lcdreg_spi_config - SPI interface configuration
+ * @mode: Register interface mode
+ * @def_width: Default register width
+ * @readable: Is the register readable, not all displays have MISO wired.
+ * @id: Display id used with LCDREG_SPI_STARTBYTE
+ * @dc_name: Index pin name, usually dc, rs or di (default is 'dc').
+ * @quirks: Deviations from the MIPI DBI standard
+ * @startbyte: Used with LCDREG_SPI_STARTBYTE to get the startbyte
+ *             (default is lcdreg_spi_startbyte).
+ */
+struct lcdreg_spi_config {
+	enum lcdreg_spi_mode mode;
+	unsigned def_width;
+	bool readable;
+	u32 id;
+	char *dc_name;
+	u32 quirks;
+/* slowdown command (index=0) */
+#define LCDREG_SLOW_INDEX0_WRITE	BIT(0)
+/*
+ * The MIPI DBI spec states that D/C should be HIGH during register reading.
+ * However, not all SPI master drivers support cs_change on last transfer and
+ * there are LCD controllers that ignore D/C on read.
+ */
+#define LCDREG_INDEX0_ON_READ		BIT(1)
+
+	u8 (*startbyte)(struct lcdreg *reg, struct lcdreg_transfer *tr,
+			bool read);
+};
+
+struct lcdreg *devm_lcdreg_spi_init(struct spi_device *sdev,
+				    const struct lcdreg_spi_config *config);
+
+#endif /* __LINUX_LCDREG_SPI_H */
-- 
2.2.2

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

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

* [RFC v2 7/8] drm/tinydrm: Add mipi-dbi support
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (5 preceding siblings ...)
  2016-04-08 17:05 ` [RFC v2 6/8] drm/tinydrm/lcdreg: Add SPI support Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-08 17:05 ` [RFC v2 8/8] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
  2016-04-13 11:11 ` [RFC v2 0/8] drm: Add support for tiny LCD displays Daniel Vetter
  8 siblings, 0 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add support for MIPI DBI interfaced controllers.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/Kconfig    |   3 +
 drivers/gpu/drm/tinydrm/Makefile   |   3 +
 drivers/gpu/drm/tinydrm/mipi-dbi.c | 253 +++++++++++++++++++++++++++++++++++++
 include/drm/tinydrm/mipi-dbi.h     |  24 ++++
 4 files changed, 283 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
 create mode 100644 include/drm/tinydrm/mipi-dbi.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index 739be06..8c41eee 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -10,4 +10,7 @@ menuconfig DRM_TINYDRM
 	  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
+
 source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig"
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index f4a92d9..35ba822 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -1,2 +1,5 @@
 obj-$(CONFIG_DRM_TINYDRM)		+= core/
 obj-$(CONFIG_LCDREG)			+= lcdreg/
+
+# 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..1ddccb7
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c
@@ -0,0 +1,253 @@
+/*
+ * 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/drm_gem_cma_helper.h>
+#include <drm/tinydrm/lcdreg.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/swab.h>
+#include <video/mipi_display.h>
+
+#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))
+
+/* TODO: Move common functions to a separate module */
+void tinydrm_xrgb8888_to_rgb565(u32 *src, u16 *dst, unsigned num_pixels,
+				bool swap_bytes)
+{
+	int i;
+
+	for (i = 0; i < num_pixels; i++) {
+		*dst = ((*src & 0x00F80000) >> 8) |
+		       ((*src & 0x0000FC00) >> 5) |
+		       ((*src & 0x000000F8) >> 3);
+		if (swap_bytes)
+			*dst = swab16(*dst);
+		src++;
+		dst++;
+	}
+}
+
+int tinydrm_update_rgb565_lcdreg(struct lcdreg *reg, u32 regnr,
+				 struct drm_framebuffer *fb, void *vmem,
+				 struct drm_clip_rect *clip)
+{
+	unsigned width = clip->x2 - clip->x1 + 1;
+	unsigned height = clip->y2 - clip->y1 + 1;
+	unsigned num_pixels = width * height;
+	struct lcdreg_transfer tr = {
+		.index = 1,
+		.width = 16,
+		.count = num_pixels
+	};
+	bool byte_swap = false;
+	u16 *buf = NULL;
+	int ret;
+
+	dev_dbg(reg->dev, "%s: x1=%u, x2=%u, y1=%u, y2=%u : width=%u, height=%u\n",
+		__func__, clip->x1, clip->x2, clip->y1, clip->y2, width, height);
+	dev_dbg_once(reg->dev, "pixel_format = %s, bpw = 0x%08x\n",
+		     drm_get_format_name(fb->pixel_format),
+		     reg->bits_per_word_mask);
+
+	if (width != fb->width) {
+		dev_err(reg->dev,
+			"Only full width clips are supported: x1=%u, x2=%u\n",
+			clip->x1, clip->x2);
+		return -EINVAL;
+	}
+
+	switch (fb->pixel_format) {
+	case DRM_FORMAT_RGB565:
+		vmem += clip->y1 * width * 2;
+		tr.buf = vmem;
+		break;
+	case DRM_FORMAT_XRGB8888:
+		vmem += clip->y1 * width * 4;
+		buf = kmalloc(num_pixels * sizeof(u16), GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+#if defined(__LITTLE_ENDIAN)
+		byte_swap = !lcdreg_bpw_supported(reg, 16);
+#endif
+		tinydrm_xrgb8888_to_rgb565(vmem, buf, num_pixels, byte_swap);
+		tr.buf = buf;
+		if (byte_swap) {
+			tr.width = 8;
+			tr.count *= 2;
+		}
+		break;
+	default:
+		dev_err_once(reg->dev, "pixel_format '%s' is not supported\n",
+			     drm_get_format_name(fb->pixel_format));
+		return -EINVAL;
+	}
+
+	ret = lcdreg_write(reg, regnr, &tr);
+	kfree(buf);
+
+	return ret;
+}
+
+/* TODO remove when the drm_clip_rect functions have a home */
+void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height);
+void drm_clip_rect_merge(struct drm_clip_rect *dst,
+			 struct drm_clip_rect *src, unsigned num_clips,
+			 unsigned flags, u32 width, u32 height);
+
+static int mipi_dbi_dirtyfb(struct drm_framebuffer *fb, void *vmem,
+			    unsigned flags, unsigned color,
+			    struct drm_clip_rect *clips, unsigned num_clips)
+{
+	struct tinydrm_device *tdev = fb->dev->dev_private;
+	struct lcdreg *reg = tdev->lcdreg;
+	struct drm_clip_rect clip = { 0 };
+	int ret;
+
+	drm_clip_rect_merge(&clip, clips, num_clips, flags,
+			    fb->width, fb->height);
+	drm_clip_rect_sanetize(&clip, fb->width, fb->height);
+
+	dev_dbg(tdev->base->dev, "%s: vmem=%p, x1=%u, x2=%u, y1=%u, y2=%u\n",
+		__func__, vmem, clip.x1, clip.x2, clip.y1, clip.y2);
+
+	/* Only full width is supported */
+	clip.x1 = 0;
+	clip.x2 = fb->width - 1;
+
+	lcdreg_writereg(reg, MIPI_DCS_SET_COLUMN_ADDRESS,
+			(clip.x1 >> 8) & 0xFF, clip.x1 & 0xFF,
+			(clip.x2 >> 8) & 0xFF, clip.x2 & 0xFF);
+	lcdreg_writereg(reg, MIPI_DCS_SET_PAGE_ADDRESS,
+			(clip.y1 >> 8) & 0xFF, clip.y1 & 0xFF,
+			(clip.y2 >> 8) & 0xFF, clip.y2 & 0xFF);
+
+	ret = tinydrm_update_rgb565_lcdreg(reg, MIPI_DCS_WRITE_MEMORY_START,
+					   fb, vmem, &clip);
+	if (ret)
+		dev_err_once(tdev->base->dev, "Failed to update display %d\n",
+			     ret);
+
+	if (tdev->prepared && !tdev->enabled)
+		tinydrm_enable(tdev);
+
+	return ret;
+}
+
+int mipi_dbi_init(struct device *dev, struct tinydrm_device *tdev)
+{
+	tdev->lcdreg->def_width = 8;
+	tdev->dirtyfb = mipi_dbi_dirtyfb;
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_init);
+
+/* Returns true if the display can be verified to be on */
+bool mipi_dbi_display_is_on(struct lcdreg *reg)
+{
+	u32 val;
+
+	if (!lcdreg_is_readable(reg))
+		return false;
+
+	if (lcdreg_readreg_buf32(reg, MIPI_DCS_GET_POWER_MODE, &val, 1))
+		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);
+
+void mipi_dbi_debug_dump_regs(struct lcdreg *reg)
+{
+	u32 val[4];
+	int ret;
+
+	if (!(lcdreg_is_readable(reg) && (drm_debug & DRM_UT_DRIVER)))
+		return;
+
+	ret = lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_ID, val, 3);
+	if (ret) {
+		dev_warn(reg->dev,
+			 "failed to read from controller: %d", ret);
+		return;
+	}
+
+	DRM_DEBUG_DRIVER("Display ID (%02x): %02x %02x %02x\n",
+			 MIPI_DCS_GET_DISPLAY_ID, val[0], val[1], val[2]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_STATUS, val, 4);
+	DRM_DEBUG_DRIVER("Display status (%02x): %02x %02x %02x %02x\n",
+			 MIPI_DCS_GET_DISPLAY_STATUS, val[0], val[1], val[2], val[3]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_POWER_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Power mode (%02x): %02x\n",
+			 MIPI_DCS_GET_POWER_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_ADDRESS_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Address mode (%02x): %02x\n",
+			 MIPI_DCS_GET_ADDRESS_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_PIXEL_FORMAT, val, 1);
+	DRM_DEBUG_DRIVER("Pixel format (%02x): %02x\n",
+			 MIPI_DCS_GET_PIXEL_FORMAT, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DISPLAY_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Display mode (%02x): %02x\n",
+			 MIPI_DCS_GET_DISPLAY_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_SIGNAL_MODE, val, 1);
+	DRM_DEBUG_DRIVER("Display signal mode (%02x): %02x\n",
+			 MIPI_DCS_GET_SIGNAL_MODE, val[0]);
+
+	lcdreg_readreg_buf32(reg, MIPI_DCS_GET_DIAGNOSTIC_RESULT, val, 1);
+	DRM_DEBUG_DRIVER("Diagnostic result (%02x): %02x\n",
+			 MIPI_DCS_GET_DIAGNOSTIC_RESULT, val[0]);
+}
+EXPORT_SYMBOL(mipi_dbi_debug_dump_regs);
+
+int mipi_dbi_panel_unprepare(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+	struct lcdreg *reg = tdev->lcdreg;
+
+	/*
+	 * Only do this if we have turned off backlight because if it's on the
+	 * display will in most cases turn all white when the pixels are
+	 * turned off.
+	 */
+	if (tdev->backlight) {
+		lcdreg_writereg(reg, MIPI_DCS_SET_DISPLAY_OFF);
+		lcdreg_writereg(reg, MIPI_DCS_ENTER_SLEEP_MODE);
+	}
+
+	if (tdev->regulator)
+		regulator_disable(tdev->regulator);
+
+	return 0;
+}
+EXPORT_SYMBOL(mipi_dbi_panel_unprepare);
+
+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..108a73b
--- /dev/null
+++ b/include/drm/tinydrm/mipi-dbi.h
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+struct tinydrm_device;
+struct drm_panel;
+struct lcdreg;
+
+int mipi_dbi_init(struct device *dev, struct tinydrm_device *tdev);
+bool mipi_dbi_display_is_on(struct lcdreg *reg);
+void mipi_dbi_debug_dump_regs(struct lcdreg *reg);
+int mipi_dbi_panel_unprepare(struct drm_panel *panel);
+
+#endif /* __LINUX_MIPI_DBI_H */
-- 
2.2.2

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

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

* [RFC v2 8/8] drm/tinydrm: Add support for several Adafruit TFT displays
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (6 preceding siblings ...)
  2016-04-08 17:05 ` [RFC v2 7/8] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
@ 2016-04-08 17:05 ` Noralf Trønnes
  2016-04-13 11:11 ` [RFC v2 0/8] drm: Add support for tiny LCD displays Daniel Vetter
  8 siblings, 0 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-08 17:05 UTC (permalink / raw)
  To: dri-devel; +Cc: thomas.petazzoni

Add support for Adafruit MIPI DBI compatible SPI displays:
2.8" PiTFT 320x240 TFT+Touchscreen for Raspberry Pi (#1601)

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
 drivers/gpu/drm/tinydrm/Kconfig        |   9 ++
 drivers/gpu/drm/tinydrm/Makefile       |   3 +
 drivers/gpu/drm/tinydrm/adafruit-tft.c | 257 +++++++++++++++++++++++++++++++++
 include/drm/tinydrm/ili9340.h          |  47 ++++++
 4 files changed, 316 insertions(+)
 create mode 100644 drivers/gpu/drm/tinydrm/adafruit-tft.c
 create mode 100644 include/drm/tinydrm/ili9340.h

diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig
index 8c41eee..621be2f 100644
--- a/drivers/gpu/drm/tinydrm/Kconfig
+++ b/drivers/gpu/drm/tinydrm/Kconfig
@@ -13,4 +13,13 @@ menuconfig DRM_TINYDRM
 config TINYDRM_MIPI_DBI
 	tristate
 
+config TINYDRM_ADAFRUIT_TFT
+	tristate "DRM driver for Adafruit SPI TFT displays"
+	depends on DRM_TINYDRM && SPI
+	select LCDREG_SPI
+	select TINYDRM_MIPI_DBI
+	help
+	  DRM driver for the following Adafruit displays:
+	    2.8" PiTFT 320x240 for Raspberry Pi - ILI9340 (#1601)
+
 source "drivers/gpu/drm/tinydrm/lcdreg/Kconfig"
diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile
index 35ba822..3c00201 100644
--- a/drivers/gpu/drm/tinydrm/Makefile
+++ b/drivers/gpu/drm/tinydrm/Makefile
@@ -3,3 +3,6 @@ obj-$(CONFIG_LCDREG)			+= lcdreg/
 
 # Controllers
 obj-$(CONFIG_TINYDRM_MIPI_DBI)		+= mipi-dbi.o
+
+# Displays
+obj-$(CONFIG_TINYDRM_ADAFRUIT_TFT)	+= adafruit-tft.o
diff --git a/drivers/gpu/drm/tinydrm/adafruit-tft.c b/drivers/gpu/drm/tinydrm/adafruit-tft.c
new file mode 100644
index 0000000..20da98d
--- /dev/null
+++ b/drivers/gpu/drm/tinydrm/adafruit-tft.c
@@ -0,0 +1,257 @@
+/*
+ * DRM driver for Adafruit MIPI compatible SPI TFT displays
+ *
+ * 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/ili9340.h>
+#include <drm/tinydrm/lcdreg-spi.h>
+#include <drm/tinydrm/mipi-dbi.h>
+#include <drm/tinydrm/tinydrm.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <video/mipi_display.h>
+
+enum adafruit_tft_displays {
+	ADAFRUIT_1601 = 1601,
+	ADAFRUIT_797 = 797,
+	ADAFRUIT_358 = 358,
+};
+
+static u32 adafruit_tft_get_rotation(struct device *dev)
+{
+	u32 rotation = 0;
+
+	device_property_read_u32(dev, "rotation", &rotation);
+
+	return rotation;
+}
+
+static int adafruit_tft_1601_panel_prepare(struct drm_panel *panel)
+{
+	struct tinydrm_device *tdev = tinydrm_from_panel(panel);
+	struct lcdreg *reg = tdev->lcdreg;
+	u8 addr_mode;
+	int ret;
+
+	dev_dbg(tdev->base->dev, "%s\n", __func__);
+
+	if (tdev->regulator) {
+		ret = regulator_enable(tdev->regulator);
+		if (ret) {
+			dev_err(tdev->base->dev,
+				"Failed to enable regulator %d\n", ret);
+			return ret;
+		}
+	}
+
+	mipi_dbi_debug_dump_regs(reg);
+
+	/* Avoid flicker by skipping setup if the bootloader has done it */
+	if (mipi_dbi_display_is_on(reg))
+		return 0;
+
+	lcdreg_reset(reg);
+	ret = lcdreg_writereg(reg, MIPI_DCS_SOFT_RESET);
+	if (ret) {
+		dev_err(tdev->base->dev, "Error writing lcdreg %d\n", ret);
+		return ret;
+	}
+
+	msleep(20);
+
+	/* Undocumented registers */
+	lcdreg_writereg(reg, 0xEF, 0x03, 0x80, 0x02);
+	lcdreg_writereg(reg, 0xCF, 0x00, 0xC1, 0x30);
+	lcdreg_writereg(reg, 0xED, 0x64, 0x03, 0x12, 0x81);
+	lcdreg_writereg(reg, 0xE8, 0x85, 0x00, 0x78);
+	lcdreg_writereg(reg, 0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02);
+	lcdreg_writereg(reg, 0xF7, 0x20);
+	lcdreg_writereg(reg, 0xEA, 0x00, 0x00);
+
+	lcdreg_writereg(reg, ILI9340_PWCTRL1, 0x23);
+	lcdreg_writereg(reg, ILI9340_PWCTRL2, 0x10);
+	lcdreg_writereg(reg, ILI9340_VMCTRL1, 0x3e, 0x28);
+	lcdreg_writereg(reg, ILI9340_VMCTRL2, 0x86);
+
+	lcdreg_writereg(reg, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
+	lcdreg_writereg(reg, ILI9340_FRMCTR1, 0x00, 0x18);
+	lcdreg_writereg(reg, ILI9340_DISCTRL, 0x08, 0x82, 0x27);
+
+	/* 3Gamma Function Disable */
+	lcdreg_writereg(reg, 0xF2, 0x00);
+
+	lcdreg_writereg(reg, MIPI_DCS_SET_GAMMA_CURVE, 0x01);
+	lcdreg_writereg(reg, ILI9340_PGAMCTRL,
+			0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1,
+			0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00);
+	lcdreg_writereg(reg, ILI9340_NGAMCTRL,
+			0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1,
+			0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F);
+
+	switch (adafruit_tft_get_rotation(reg->dev)) {
+	default:
+		addr_mode = ILI9340_MADCTL_MV | ILI9340_MADCTL_MY |
+			    ILI9340_MADCTL_MX;
+		break;
+	case 90:
+		addr_mode = ILI9340_MADCTL_MY;
+		break;
+	case 180:
+		addr_mode = ILI9340_MADCTL_MV;
+		break;
+	case 270:
+		addr_mode = ILI9340_MADCTL_MX;
+		break;
+	}
+	addr_mode |= ILI9340_MADCTL_BGR;
+	lcdreg_writereg(reg, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
+
+	lcdreg_writereg(reg, MIPI_DCS_EXIT_SLEEP_MODE);
+	msleep(120);
+	lcdreg_writereg(reg, MIPI_DCS_SET_DISPLAY_ON);
+
+	mipi_dbi_debug_dump_regs(reg);
+
+	return 0;
+}
+
+struct drm_panel_funcs adafruit_tft_1601_funcs = {
+	.get_modes = tinydrm_panel_get_modes,
+	.prepare = adafruit_tft_1601_panel_prepare,
+	.unprepare = mipi_dbi_panel_unprepare,
+	.enable = tinydrm_panel_enable_backlight,
+	.disable = tinydrm_panel_disable_backlight,
+};
+
+static const struct of_device_id adafruit_tft_of_match[] = {
+	{ .compatible = "adafruit,tft1601", .data = (void *)ADAFRUIT_1601 },
+	{ .compatible = "adafruit,tft797",  .data = (void *)ADAFRUIT_797 },
+	{ .compatible = "adafruit,tft358",  .data = (void *)ADAFRUIT_358 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, adafruit_tft_of_match);
+
+static const struct spi_device_id adafruit_tft_id[] = {
+	{ "tft1601", ADAFRUIT_1601 },
+	{ "tft797",  ADAFRUIT_797 },
+	{ "tft358",  ADAFRUIT_358 },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, adafruit_tft_id);
+
+TINYDRM_DRM_DRIVER(adafruit_tft, "adafruit-tft", "Adafruit TFT", "20160317");
+
+static int adafruit_tft_probe(struct spi_device *spi)
+{
+	const struct of_device_id *of_id;
+	struct lcdreg_spi_config cfg = {
+		.mode = LCDREG_SPI_4WIRE,
+	};
+	struct device *dev = &spi->dev;
+	struct tinydrm_device *tdev;
+	bool readable = false;
+	struct lcdreg *reg;
+	int id, ret;
+
+	of_id = of_match_device(adafruit_tft_of_match, dev);
+	if (of_id) {
+		id = (int)of_id->data;
+	} else {
+		const struct spi_device_id *spi_id = spi_get_device_id(spi);
+
+		if (!spi_id)
+			return -EINVAL;
+
+		id = spi_id->driver_data;
+	}
+
+	tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
+	if (!tdev)
+		return -ENOMEM;
+
+	tdev->backlight = tinydrm_of_find_backlight(dev);
+	if (IS_ERR(tdev->backlight))
+		return PTR_ERR(tdev->backlight);
+
+	tdev->regulator = devm_regulator_get_optional(dev, "power");
+	if (IS_ERR(tdev->regulator)) {
+		if (PTR_ERR(tdev->regulator) != -ENODEV)
+			return PTR_ERR(tdev->regulator);
+		tdev->regulator = NULL;
+	}
+
+	switch (id) {
+	case ADAFRUIT_1601:
+		readable = true;
+		cfg.mode = LCDREG_SPI_4WIRE;
+		tdev->width = 320;
+		tdev->height = 240;
+		tdev->panel.funcs = &adafruit_tft_1601_funcs;
+		break;
+	case ADAFRUIT_797:
+		cfg.mode = LCDREG_SPI_3WIRE;
+		tdev->width = 176;
+		tdev->height = 220;
+		/* TODO: tdev->panel.funcs = &adafruit_tft_797_funcs*/
+		break;
+	case ADAFRUIT_358:
+		tdev->width = 128;
+		tdev->height = 160;
+		/* TODO: tdev->panel.funcs = &adafruit_tft_358_funcs */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	DRM_DEBUG_DRIVER("rotation = %u\n", adafruit_tft_get_rotation(dev));
+	switch (adafruit_tft_get_rotation(dev)) {
+	case 90:
+	case 270:
+		swap(tdev->width, tdev->height);
+		break;
+	}
+
+	reg = devm_lcdreg_spi_init(spi, &cfg);
+	if (IS_ERR(reg))
+		return PTR_ERR(reg);
+
+	reg->readable = readable;
+	tdev->lcdreg = reg;
+	ret = mipi_dbi_init(dev, tdev);
+	if (ret)
+		return ret;
+
+	/* TODO: Make configurable */
+	tdev->fbdefio_delay_ms = 40;
+
+	spi_set_drvdata(spi, tdev);
+
+	return devm_tinydrm_register(dev, tdev, &adafruit_tft);
+}
+
+static struct spi_driver adafruit_tft_spi_driver = {
+	.driver = {
+		.name = "adafruit-tft",
+		.owner = THIS_MODULE,
+		.of_match_table = adafruit_tft_of_match,
+		.pm = &tinydrm_simple_pm_ops,
+	},
+	.id_table = adafruit_tft_id,
+	.probe = adafruit_tft_probe,
+	.shutdown = tinydrm_spi_shutdown,
+};
+module_spi_driver(adafruit_tft_spi_driver);
+
+MODULE_DESCRIPTION("Adafruit MIPI compatible SPI displays");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/tinydrm/ili9340.h b/include/drm/tinydrm/ili9340.h
new file mode 100644
index 0000000..c851c41
--- /dev/null
+++ b/include/drm/tinydrm/ili9340.h
@@ -0,0 +1,47 @@
+/*
+ * ILI9340 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_ILI9340_H
+#define __LINUX_ILI9340_H
+
+#define ILI9340_FRMCTR1    0xB1
+#define ILI9340_FRMCTR2    0xB2
+#define ILI9340_FRMCTR3    0xB3
+#define ILI9340_INVTR      0xB4
+#define ILI9340_DISCTRL    0xB6
+
+#define ILI9340_PWCTRL1    0xC0
+#define ILI9340_PWCTRL2    0xC1
+#define ILI9340_PWCTRL3    0xC2
+#define ILI9340_PWCTRL4    0xC3
+#define ILI9340_PWCTRL5    0xC4
+#define ILI9340_VMCTRL1    0xC5
+#define ILI9340_VMCTRL2    0xC7
+
+#define ILI9340_RDID1      0xDA
+#define ILI9340_RDID2      0xDB
+#define ILI9340_RDID3      0xDC
+#define ILI9340_RDID4      0xDD
+
+#define ILI9340_PGAMCTRL   0xE0
+#define ILI9340_NGAMCTRL   0xE1
+
+#define ILI9340_IFCTL      0xF6
+
+#define ILI9340_MADCTL_MH  BIT(2)
+#define ILI9340_MADCTL_BGR BIT(3)
+#define ILI9340_MADCTL_ML  BIT(4)
+#define ILI9340_MADCTL_MV  BIT(5)
+#define ILI9340_MADCTL_MX  BIT(6)
+#define ILI9340_MADCTL_MY  BIT(7)
+
+
+#endif /* __LINUX_ILI9340_H */
-- 
2.2.2

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

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

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
@ 2016-04-13 10:57   ` Daniel Vetter
  2016-04-13 11:09   ` Daniel Vetter
  1 sibling, 0 replies; 21+ messages in thread
From: Daniel Vetter @ 2016-04-13 10:57 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> Accumulated fbdev framebuffer changes are signaled using the callback
> (struct drm_framebuffer_funcs *)->dirty()
> 
> The drm_fb_helper_sys_*() functions will accumulate changes and
> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
> This worker is used by the deferred io mmap code to signal that it
> has been collecting page faults. The page faults and/or other changes
> are then merged into a drm_clip_rect and passed to the framebuffer
> dirty() function.
> 
> The driver is responsible for setting up the fb_info.fbdefio structure
> and calling fb_deferred_io_init() using the provided callback:
> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> ---
>  drivers/gpu/drm/drm_fb_helper.c | 189 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_fb_helper.h     |  15 ++++
>  2 files changed, 203 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 1e103c4..30f3dfd 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -48,6 +48,100 @@ MODULE_PARM_DESC(fbdev_emulation,
>  
>  static LIST_HEAD(kernel_fb_helper_list);
>  
> +/*
> + * Where should I put these drm_clip_rect functions?
> + */

drm_rect.[hc] Some of them are there already but under different names
(e.g. intersect instead of sanitize).
-Daniel

> +
> +/**
> + * drm_clip_rect_reset - Reset clip
> + * @clip: clip rectangle
> + *
> + * Sets clip to {0,0,0,0}.
> + */
> +static inline void drm_clip_rect_reset(struct drm_clip_rect *clip)
> +{
> +	clip->x1 = 0;
> +	clip->x2 = 0;
> +	clip->y1 = 0;
> +	clip->y2 = 0;
> +}
> +
> +/**
> + * drm_clip_rect_is_empty - Is clip empty?
> + * @clip: clip rectangle
> + *
> + * Returns true if clip is {0,0,0,0}.
> + */
> +static inline bool drm_clip_rect_is_empty(struct drm_clip_rect *clip)
> +{
> +	return (!clip->x1 && !clip->x2 && !clip->y1 && !clip->y2);
> +}
> +
> +/**
> + * drm_clip_rect_sanetize - Make sure clip rectangle has sane values
> + * @clip: clip rectangle
> + * @width: maximum width of rectangle
> + * @height: maximum height of rectangle
> + *
> + * Makes sure the clip doesn't exceed the specified width and height and that
> + * x1 <= x2 and y1 <= y2.
> + */
> +void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height)
> +{
> +	if (clip->x1 > clip->x2)
> +		swap(clip->x1, clip->x2);
> +	if (clip->y1 > clip->y2)
> +		swap(clip->y1, clip->y2);
> +
> +	clip->x1 = min_t(u32, clip->x1, width - 1);
> +	clip->x2 = min_t(u32, clip->x2, width - 1);
> +	clip->y1 = min_t(u32, clip->y1, height - 1);
> +	clip->y2 = min_t(u32, clip->y2, height - 1);
> +}
> +EXPORT_SYMBOL(drm_clip_rect_sanetize);
> +
> +/**
> + * drm_clip_rect_merge - Merge clip rectangles
> + * @dst: destination clip rectangle
> + * @src: source clip rectangle(s), can be NULL
> + * @num_clips: number of source clip rectangles
> + * @width: width of rectangle if @src is NULL
> + * @height: height of rectangle if @src is NULL
> + *
> + * The dirtyfb ioctl allows for a NULL clip to be passed in,
> + * so if @src is NULL, width and height is used to set a full clip.
> + * @dst takes part in the merge unless it is empty {0,0,0,0}.
> + */
> +void drm_clip_rect_merge(struct drm_clip_rect *dst,
> +			 struct drm_clip_rect *src, unsigned num_clips,
> +			 unsigned flags, u32 width, u32 height)
> +{
> +	int i;
> +
> +	if (!src || !num_clips) {
> +		dst->x1 = 0;
> +		dst->x2 = width - 1;
> +		dst->y1 = 0;
> +		dst->y2 = height - 1;
> +		return;
> +	}
> +
> +	if (drm_clip_rect_is_empty(dst)) {
> +		dst->x1 = ~0;
> +		dst->y1 = ~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);
> +	}
> +}
> +EXPORT_SYMBOL(drm_clip_rect_merge);
> +
>  /**
>   * DOC: fbdev helpers
>   *
> @@ -410,6 +504,13 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
>  
>  	drm_warn_on_modeset_not_all_locked(dev);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock(&fb_helper->dirty_lock);
> +	drm_clip_rect_merge(&fb_helper->dirty_clip, NULL, 0, 0,
> +			    fb_helper->fbdev->var.xres,
> +			    fb_helper->fbdev->var.yres);
> +	spin_unlock(&fb_helper->dirty_lock);
> +#endif
>  	if (fb_helper->atomic)
>  		return restore_fbdev_mode_atomic(fb_helper);
>  
> @@ -654,6 +755,9 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>  			   const struct drm_fb_helper_funcs *funcs)
>  {
>  	INIT_LIST_HEAD(&helper->kernel_fb_list);
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock_init(&helper->dirty_lock);
> +#endif
>  	helper->funcs = funcs;
>  	helper->dev = dev;
>  }
> @@ -838,6 +942,76 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  }
>  EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	unsigned long start, end, min, max;
> +	struct drm_clip_rect clip;
> +	struct page *page;
> +
> +	if (!helper->fb->funcs->dirty)
> +		return;
> +
> +	spin_lock(&helper->dirty_lock);
> +	clip = helper->dirty_clip;
> +	drm_clip_rect_reset(&helper->dirty_clip);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	min = ULONG_MAX;
> +	max = 0;
> +	list_for_each_entry(page, pagelist, lru) {
> +		start = page->index << PAGE_SHIFT;
> +		end = start + PAGE_SIZE - 1;
> +		min = min(min, start);
> +		max = max(max, end);
> +	}
> +
> +	if (min < max) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = min / info->fix.line_length;
> +		clip.y2 = min_t(u32, max / info->fix.line_length,
> +				    info->var.yres - 1);
> +	} else if (drm_clip_rect_is_empty(&clip)) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = 0;
> +		clip.y2 = info->var.yres - 1;
> +	}
> +
> +	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
> +}
> +EXPORT_SYMBOL(drm_fb_helper_deferred_io);
> +
> +static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +				       u32 width, u32 height)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	struct drm_clip_rect clip;
> +
> +	if (!info->fbdefio)
> +		return;
> +
> +	clip.x1 = x;
> +	clip.x2 = x + width - 1;
> +	clip.y1 = y;
> +	clip.y2 = y + height - 1;
> +
> +	spin_lock(&helper->dirty_lock);
> +	drm_clip_rect_merge(&helper->dirty_clip, &clip, 1, 0, 0, 0);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
> +}
> +#else
> +static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +					      u32 width, u32 height)
> +{
> +}
> +#endif
> +
>  /**
>   * drm_fb_helper_sys_read - wrapper around fb_sys_read
>   * @info: fb_info struct pointer
> @@ -866,7 +1040,14 @@ EXPORT_SYMBOL(drm_fb_helper_sys_read);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
>  				size_t count, loff_t *ppos)
>  {
> -	return fb_sys_write(info, buf, count, ppos);
> +	ssize_t ret;
> +
> +	ret = fb_sys_write(info, buf, count, ppos);
> +	if (ret > 0)
> +		drm_fb_helper_sys_deferred(info, 0, 0,
> +					   info->var.xres, info->var.yres);
> +
> +	return ret;
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_write);
>  
> @@ -881,6 +1062,8 @@ void drm_fb_helper_sys_fillrect(struct fb_info *info,
>  				const struct fb_fillrect *rect)
>  {
>  	sys_fillrect(info, rect);
> +	drm_fb_helper_sys_deferred(info, rect->dx, rect->dy,
> +				   rect->width, rect->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
>  
> @@ -895,6 +1078,8 @@ void drm_fb_helper_sys_copyarea(struct fb_info *info,
>  				const struct fb_copyarea *area)
>  {
>  	sys_copyarea(info, area);
> +	drm_fb_helper_sys_deferred(info, area->dx, area->dy,
> +				   area->width, area->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
>  
> @@ -909,6 +1094,8 @@ void drm_fb_helper_sys_imageblit(struct fb_info *info,
>  				 const struct fb_image *image)
>  {
>  	sys_imageblit(info, image);
> +	drm_fb_helper_sys_deferred(info, image->dx, image->dy,
> +				   image->width, image->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
>  
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index d8a40df..1daadc7 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -172,6 +172,9 @@ struct drm_fb_helper_connector {
>   * @funcs: driver callbacks for fb helper
>   * @fbdev: emulated fbdev device info struct
>   * @pseudo_palette: fake palette of 16 colors
> + * @dirty_clip: clip rectangle used with deferred_io to accumulate damage to
> + *              the screen buffer
> + * @dirty_lock: spinlock protecting @dirty_clip
>   *
>   * This is the main structure used by the fbdev helpers. Drivers supporting
>   * fbdev emulation should embedded this into their overall driver structure.
> @@ -189,6 +192,10 @@ struct drm_fb_helper {
>  	const struct drm_fb_helper_funcs *funcs;
>  	struct fb_info *fbdev;
>  	u32 pseudo_palette[17];
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	struct drm_clip_rect dirty_clip;
> +	spinlock_t dirty_lock;
> +#endif
>  
>  	/**
>  	 * @kernel_fb_list:
> @@ -244,6 +251,9 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
>  
>  void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
>  
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist);
> +
>  ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
>  			       size_t count, loff_t *ppos);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
> @@ -362,6 +372,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  {
>  }
>  
> +static inline void drm_fb_helper_deferred_io(struct fb_info *info,
> +					     struct list_head *pagelist)
> +{
> +}
> +
>  static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info,
>  					     char __user *buf, size_t count,
>  					     loff_t *ppos)
> -- 
> 2.2.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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v2 3/8] drm: Add helper for simple kms drivers
  2016-04-08 17:05 ` [RFC v2 3/8] drm: Add helper for simple kms drivers Noralf Trønnes
@ 2016-04-13 11:05   ` Daniel Vetter
  2016-05-02 15:55     ` Noralf Trønnes
  0 siblings, 1 reply; 21+ messages in thread
From: Daniel Vetter @ 2016-04-13 11:05 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Apr 08, 2016 at 07:05:05PM +0200, Noralf Trønnes wrote:
> Provides helper functions for drivers that have a simple display
> pipeline. Plane, crtc and encoder are collapsed into one entity.
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Looks good. A few comments below, but the big thing is adding kerneldoc
and cross-referencing it (e.g. from drm_panel to drm_panel_connector).
-Daniel

> ---
>  drivers/gpu/drm/Kconfig                 |   7 +
>  drivers/gpu/drm/Makefile                |   1 +
>  drivers/gpu/drm/drm_simple_kms_helper.c | 262 ++++++++++++++++++++++++++++++++
>  include/drm/drm_simple_kms_helper.h     |  44 ++++++
>  4 files changed, 314 insertions(+)
>  create mode 100644 drivers/gpu/drm/drm_simple_kms_helper.c
>  create mode 100644 include/drm/drm_simple_kms_helper.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 8ae7ab6..cb62cd9 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -31,6 +31,13 @@ config DRM_KMS_HELPER
>  	help
>  	  CRTC helpers for KMS drivers.
>  
> +config DRM_SIMPLE_KMS_HELPER
> +	tristate
> +	depends on DRM
> +	select DRM_KMS_HELPER
> +	help
> +	  Helpers for very simple KMS drivers.
> +
>  config DRM_KMS_FB_HELPER
>  	bool
>  	depends on DRM_KMS_HELPER
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 61766de..ea9bf59 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -28,6 +28,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
>  drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
>  
>  obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
> +obj-$(CONFIG_DRM_SIMPLE_KMS_HELPER) += drm_simple_kms_helper.o
>  
>  CFLAGS_drm_trace_points.o := -I$(src)
>  
> diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c
> new file mode 100644
> index 0000000..e193f7db
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_simple_kms_helper.c
> @@ -0,0 +1,262 @@
> +/*
> + * 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/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_plane_helper.h>
> +#include <drm/drm_simple_kms_helper.h>
> +#include <linux/slab.h>
> +
> +struct drm_simple_kms_connector {
> +	struct drm_connector base;
> +	struct drm_panel *panel;
> +};
> +
> +static inline struct drm_simple_kms_connector *
> +to_simple_connector(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct drm_simple_kms_connector, base);
> +}
> +
> +static int drm_simple_kms_connector_get_modes(struct drm_connector *connector)
> +{
> +	return drm_panel_get_modes(to_simple_connector(connector)->panel);
> +}
> +
> +static struct drm_encoder *
> +drm_simple_kms_connector_best_encoder(struct drm_connector *connector)
> +{
> +	return drm_encoder_find(connector->dev, connector->encoder_ids[0]);
> +}

I think this would be useful for atomic drivers in general. I even thought
we have it already somewhere ...

> +
> +static const struct drm_connector_helper_funcs drm_simple_kms_connector_helper_funcs = {
> +	.get_modes = drm_simple_kms_connector_get_modes,
> +	.best_encoder = drm_simple_kms_connector_best_encoder,
> +};
> +
> +static enum drm_connector_status
> +drm_simple_kms_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	if (drm_device_is_unplugged(connector->dev))
> +		return connector_status_disconnected;
> +
> +	return connector->status;
> +}
> +
> +static void drm_simple_kms_connector_destroy(struct drm_connector *connector)
> +{
> +	struct drm_simple_kms_connector *panel_connector;
> +
> +	panel_connector = to_simple_connector(connector);
> +	drm_panel_detach(panel_connector->panel);
> +	drm_panel_remove(panel_connector->panel);
> +	drm_connector_unregister(connector);
> +	drm_connector_cleanup(connector);
> +	kfree(panel_connector);
> +}
> +
> +static const struct drm_connector_funcs drm_simple_kms_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.detect = drm_simple_kms_connector_detect,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_simple_kms_connector_destroy,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};

drm_simple_kms_panel_connector is toooooooooooo long ;-) What about just
drm_panel_connector instead? Also not entirely sure whether this is best
placed here, or better in drm_panel.c, or maybe even in a new
drm_panel_helper.c.

Imo drm_panel_connector and drm_simple_display_pipe should work as
orthogonal helpers.

Or 3rd option: You just expose the above helpers for detect/get_modes
only.

> +
> +/**
> + * drm_simple_kms_panel_connector_create - Create simple connector for panel
> + * @dev: DRM device
> + * @panel: DRM panel
> + * @connector_type: user visible type of the connector
> + *
> + * Creates a simple connector for a panel.
> + * The panel needs to provide a get_modes() function.
> + *
> + * Returns:
> + * Pointer to new connector or ERR_PTR on failure.
> + */
> +struct drm_connector *
> +drm_simple_kms_panel_connector_create(struct drm_device *dev,
> +				      struct drm_panel *panel,
> +				      int connector_type)
> +{
> +	struct drm_simple_kms_connector *panel_connector;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
> +	if (!panel_connector)
> +		return ERR_PTR(-ENOMEM);
> +
> +	panel_connector->panel = panel;
> +	connector = &panel_connector->base;
> +	drm_connector_helper_add(connector, &drm_simple_kms_connector_helper_funcs);
> +	ret = drm_connector_init(dev, connector, &drm_simple_kms_connector_funcs,
> +				 connector_type);
> +	if (ret) {
> +		kfree(panel_connector);
> +		return ERR_PTR(ret);
> +	}
> +
> +	connector->status = connector_status_connected;
> +	drm_panel_init(panel);
> +	drm_panel_add(panel);
> +	drm_panel_attach(panel, connector);
> +
> +	return connector;
> +}
> +EXPORT_SYMBOL(drm_simple_kms_panel_connector_create);
> +
> +static void drm_simple_kms_encoder_disable(struct drm_encoder *encoder)
> +{
> +}
> +
> +static void drm_simple_kms_encoder_enable(struct drm_encoder *encoder)
> +{
> +}
> +
> +static int drm_simple_kms_encoder_atomic_check(struct drm_encoder *encoder,
> +					struct drm_crtc_state *crtc_state,
> +					struct drm_connector_state *conn_state)
> +{
> +	return 0;
> +}

Above dummy functions shouldn't be necessary.

> +
> +static const struct drm_encoder_helper_funcs drm_simple_kms_encoder_helper_funcs = {
> +	.disable = drm_simple_kms_encoder_disable,
> +	.enable = drm_simple_kms_encoder_enable,
> +	.atomic_check = drm_simple_kms_encoder_atomic_check,
> +};
> +
> +static const struct drm_encoder_funcs drm_simple_kms_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static void drm_simple_kms_crtc_enable(struct drm_crtc *crtc)
> +{
> +	struct drm_simple_display_pipe *pipe;
> +
> +	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
> +	if (!pipe->funcs || !pipe->funcs->enable)
> +		return;
> +
> +	pipe->funcs->enable(pipe, crtc->state);
> +}
> +
> +static void drm_simple_kms_crtc_disable(struct drm_crtc *crtc)
> +{
> +	struct drm_simple_display_pipe *pipe;
> +
> +	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
> +	if (!pipe->funcs || !pipe->funcs->disable)
> +		return;
> +
> +	pipe->funcs->disable(pipe);
> +}
> +
> +static const struct drm_crtc_helper_funcs drm_simple_kms_crtc_helper_funcs = {
> +	.disable = drm_simple_kms_crtc_disable,
> +	.enable = drm_simple_kms_crtc_enable,
> +};
> +
> +static const struct drm_crtc_funcs drm_simple_kms_crtc_funcs = {
> +	.reset = drm_atomic_helper_crtc_reset,
> +	.destroy = drm_crtc_cleanup,
> +	.set_config = drm_atomic_helper_set_config,
> +	.page_flip = drm_atomic_helper_page_flip,
> +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static void drm_simple_kms_plane_atomic_update(struct drm_plane *plane,
> +					struct drm_plane_state *old_state)
> +{
> +	struct drm_simple_display_pipe *pipe;
> +
> +	pipe = container_of(plane, struct drm_simple_display_pipe, plane);
> +	if (!pipe->funcs || !pipe->funcs->plane_update)
> +		return;
> +
> +	pipe->funcs->plane_update(pipe, old_state);
> +}
> +
> +static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = {
> +	.atomic_update = drm_simple_kms_plane_atomic_update,
> +};
> +
> +static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
> +	.update_plane		= drm_atomic_helper_update_plane,
> +	.disable_plane		= drm_atomic_helper_disable_plane,
> +	.destroy		= drm_plane_cleanup,
> +	.reset			= drm_atomic_helper_plane_reset,
> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
> +};
> +
> +/**
> + * drm_simple_display_pipe_init - Initialize a simple display pipe
> + * @dev: DRM device
> + * @pipe: simple display pipe object to initialize
> + * @funcs: callbacks for the display pipe
> + * @formats: array of supported formats (%DRM_FORMAT_*)
> + * @format_count: number of elements in @formats
> + * @connector: connector to attach and register
> + *
> + * Sets up a display pipe which consist of a really simple plane-crtc-encoder
> + * pipe coupled with the provided connector.
> + *
> + * Returns:
> + * Zero on success, error code on failure.
> + */
> +int drm_simple_display_pipe_init(struct drm_device *dev,
> +				 struct drm_simple_display_pipe *pipe,
> +				 struct drm_simple_display_pipe_funcs *funcs,
> +				 const uint32_t *formats, unsigned int format_count,
> +				 struct drm_connector *connector)
> +{
> +	struct drm_encoder *encoder = &pipe->encoder;
> +	struct drm_plane *plane = &pipe->plane;
> +	struct drm_crtc *crtc = &pipe->crtc;
> +	int ret;
> +
> +	pipe->funcs = funcs;
> +
> +	drm_plane_helper_add(plane, &drm_simple_kms_plane_helper_funcs);
> +	ret = drm_universal_plane_init(dev, plane, 0, &drm_simple_kms_plane_funcs,
> +				       formats, format_count,
> +				       DRM_PLANE_TYPE_PRIMARY, NULL);
> +	if (ret)
> +		return ret;
> +
> +	drm_crtc_helper_add(crtc, &drm_simple_kms_crtc_helper_funcs);
> +	ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
> +					&drm_simple_kms_crtc_funcs, NULL);
> +	if (ret)
> +		return ret;
> +
> +	encoder->possible_crtcs = 1 << drm_crtc_index(crtc);
> +	drm_encoder_helper_add(encoder, &drm_simple_kms_encoder_helper_funcs);
> +	ret = drm_encoder_init(dev, encoder, &drm_simple_kms_encoder_funcs,
> +			       DRM_MODE_ENCODER_NONE, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = drm_mode_connector_attach_encoder(connector, encoder);
> +	if (ret)
> +		return ret;
> +
> +	return drm_connector_register(connector);
> +}
> +EXPORT_SYMBOL(drm_simple_display_pipe_init);
> +
> +MODULE_LICENSE("GPL");
> diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h
> new file mode 100644
> index 0000000..0f7461b
> --- /dev/null
> +++ b/include/drm/drm_simple_kms_helper.h
> @@ -0,0 +1,44 @@
> +/*
> + * 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_DRM_SIMPLE_KMS_HELPER_H
> +#define __LINUX_DRM_SIMPLE_KMS_HELPER_H
> +
> +struct drm_simple_display_pipe;
> +
> +struct drm_simple_display_pipe_funcs {
> +	void (*enable)(struct drm_simple_display_pipe *pipe,
> +		       struct drm_crtc_state *crtc_state);
> +	void (*disable)(struct drm_simple_display_pipe *pipe);
> +	void (*plane_update)(struct drm_simple_display_pipe *pipe,
> +			     struct drm_plane_state *plane_state);

On 2nd thought maybe we want an atomic_check in here too, with same
parameters *pipe, *crtc_state and *plane_state (so that it's useful for
both plane-only updates and modeset changes).

> +};
> +
> +struct drm_simple_display_pipe {
> +	struct drm_crtc crtc;
> +	struct drm_plane plane;
> +	struct drm_encoder encoder;
> +	struct drm_connector *connector;
> +
> +	struct drm_simple_display_pipe_funcs *funcs;
> +};
> +
> +int drm_simple_display_pipe_init(struct drm_device *dev,
> +				 struct drm_simple_display_pipe *pipe,
> +				 struct drm_simple_display_pipe_funcs *funcs,
> +				 const uint32_t *formats, unsigned int format_count,
> +				 struct drm_connector *connector);
> +struct drm_connector *
> +drm_simple_kms_panel_connector_create(struct drm_device *dev,
> +				      struct drm_panel *panel,
> +				      int connector_type);
> +
> +
> +
> +#endif /* __LINUX_DRM_SIMPLE_KMS_HELPER_H */
> -- 
> 2.2.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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
  2016-04-13 10:57   ` Daniel Vetter
@ 2016-04-13 11:09   ` Daniel Vetter
  2016-04-18 15:15     ` Noralf Trønnes
  1 sibling, 1 reply; 21+ messages in thread
From: Daniel Vetter @ 2016-04-13 11:09 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> Accumulated fbdev framebuffer changes are signaled using the callback
> (struct drm_framebuffer_funcs *)->dirty()
> 
> The drm_fb_helper_sys_*() functions will accumulate changes and
> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
> This worker is used by the deferred io mmap code to signal that it
> has been collecting page faults. The page faults and/or other changes
> are then merged into a drm_clip_rect and passed to the framebuffer
> dirty() function.
> 
> The driver is responsible for setting up the fb_info.fbdefio structure
> and calling fb_deferred_io_init() using the provided callback:
> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
> 
> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

For this one it'd be awesome to throw patches for qxl/udl on top to remove
their own hand-rolled implementations. Just to maximize the testing
coverage of this new code. Should be doable to set up a qxl virtual
machine quickly, but even without that we should be able to pull it in
(since it's mostly just about removing code from these two drivers).
-Daniel

> ---
>  drivers/gpu/drm/drm_fb_helper.c | 189 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_fb_helper.h     |  15 ++++
>  2 files changed, 203 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> index 1e103c4..30f3dfd 100644
> --- a/drivers/gpu/drm/drm_fb_helper.c
> +++ b/drivers/gpu/drm/drm_fb_helper.c
> @@ -48,6 +48,100 @@ MODULE_PARM_DESC(fbdev_emulation,
>  
>  static LIST_HEAD(kernel_fb_helper_list);
>  
> +/*
> + * Where should I put these drm_clip_rect functions?
> + */
> +
> +/**
> + * drm_clip_rect_reset - Reset clip
> + * @clip: clip rectangle
> + *
> + * Sets clip to {0,0,0,0}.
> + */
> +static inline void drm_clip_rect_reset(struct drm_clip_rect *clip)
> +{
> +	clip->x1 = 0;
> +	clip->x2 = 0;
> +	clip->y1 = 0;
> +	clip->y2 = 0;
> +}
> +
> +/**
> + * drm_clip_rect_is_empty - Is clip empty?
> + * @clip: clip rectangle
> + *
> + * Returns true if clip is {0,0,0,0}.
> + */
> +static inline bool drm_clip_rect_is_empty(struct drm_clip_rect *clip)
> +{
> +	return (!clip->x1 && !clip->x2 && !clip->y1 && !clip->y2);
> +}
> +
> +/**
> + * drm_clip_rect_sanetize - Make sure clip rectangle has sane values
> + * @clip: clip rectangle
> + * @width: maximum width of rectangle
> + * @height: maximum height of rectangle
> + *
> + * Makes sure the clip doesn't exceed the specified width and height and that
> + * x1 <= x2 and y1 <= y2.
> + */
> +void drm_clip_rect_sanetize(struct drm_clip_rect *clip, u32 width, u32 height)
> +{
> +	if (clip->x1 > clip->x2)
> +		swap(clip->x1, clip->x2);
> +	if (clip->y1 > clip->y2)
> +		swap(clip->y1, clip->y2);
> +
> +	clip->x1 = min_t(u32, clip->x1, width - 1);
> +	clip->x2 = min_t(u32, clip->x2, width - 1);
> +	clip->y1 = min_t(u32, clip->y1, height - 1);
> +	clip->y2 = min_t(u32, clip->y2, height - 1);
> +}
> +EXPORT_SYMBOL(drm_clip_rect_sanetize);
> +
> +/**
> + * drm_clip_rect_merge - Merge clip rectangles
> + * @dst: destination clip rectangle
> + * @src: source clip rectangle(s), can be NULL
> + * @num_clips: number of source clip rectangles
> + * @width: width of rectangle if @src is NULL
> + * @height: height of rectangle if @src is NULL
> + *
> + * The dirtyfb ioctl allows for a NULL clip to be passed in,
> + * so if @src is NULL, width and height is used to set a full clip.
> + * @dst takes part in the merge unless it is empty {0,0,0,0}.
> + */
> +void drm_clip_rect_merge(struct drm_clip_rect *dst,
> +			 struct drm_clip_rect *src, unsigned num_clips,
> +			 unsigned flags, u32 width, u32 height)
> +{
> +	int i;
> +
> +	if (!src || !num_clips) {
> +		dst->x1 = 0;
> +		dst->x2 = width - 1;
> +		dst->y1 = 0;
> +		dst->y2 = height - 1;
> +		return;
> +	}
> +
> +	if (drm_clip_rect_is_empty(dst)) {
> +		dst->x1 = ~0;
> +		dst->y1 = ~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);
> +	}
> +}
> +EXPORT_SYMBOL(drm_clip_rect_merge);
> +
>  /**
>   * DOC: fbdev helpers
>   *
> @@ -410,6 +504,13 @@ static int restore_fbdev_mode(struct drm_fb_helper *fb_helper)
>  
>  	drm_warn_on_modeset_not_all_locked(dev);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock(&fb_helper->dirty_lock);
> +	drm_clip_rect_merge(&fb_helper->dirty_clip, NULL, 0, 0,
> +			    fb_helper->fbdev->var.xres,
> +			    fb_helper->fbdev->var.yres);
> +	spin_unlock(&fb_helper->dirty_lock);
> +#endif
>  	if (fb_helper->atomic)
>  		return restore_fbdev_mode_atomic(fb_helper);
>  
> @@ -654,6 +755,9 @@ void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper,
>  			   const struct drm_fb_helper_funcs *funcs)
>  {
>  	INIT_LIST_HEAD(&helper->kernel_fb_list);
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	spin_lock_init(&helper->dirty_lock);
> +#endif
>  	helper->funcs = funcs;
>  	helper->dev = dev;
>  }
> @@ -838,6 +942,76 @@ void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  }
>  EXPORT_SYMBOL(drm_fb_helper_unlink_fbi);
>  
> +#ifdef CONFIG_FB_DEFERRED_IO
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	unsigned long start, end, min, max;
> +	struct drm_clip_rect clip;
> +	struct page *page;
> +
> +	if (!helper->fb->funcs->dirty)
> +		return;
> +
> +	spin_lock(&helper->dirty_lock);
> +	clip = helper->dirty_clip;
> +	drm_clip_rect_reset(&helper->dirty_clip);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	min = ULONG_MAX;
> +	max = 0;
> +	list_for_each_entry(page, pagelist, lru) {
> +		start = page->index << PAGE_SHIFT;
> +		end = start + PAGE_SIZE - 1;
> +		min = min(min, start);
> +		max = max(max, end);
> +	}
> +
> +	if (min < max) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = min / info->fix.line_length;
> +		clip.y2 = min_t(u32, max / info->fix.line_length,
> +				    info->var.yres - 1);
> +	} else if (drm_clip_rect_is_empty(&clip)) {
> +		clip.x1 = 0;
> +		clip.x2 = info->var.xres - 1;
> +		clip.y1 = 0;
> +		clip.y2 = info->var.yres - 1;
> +	}
> +
> +	helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip, 1);
> +}
> +EXPORT_SYMBOL(drm_fb_helper_deferred_io);
> +
> +static void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +				       u32 width, u32 height)
> +{
> +	struct drm_fb_helper *helper = info->par;
> +	struct drm_clip_rect clip;
> +
> +	if (!info->fbdefio)
> +		return;
> +
> +	clip.x1 = x;
> +	clip.x2 = x + width - 1;
> +	clip.y1 = y;
> +	clip.y2 = y + height - 1;
> +
> +	spin_lock(&helper->dirty_lock);
> +	drm_clip_rect_merge(&helper->dirty_clip, &clip, 1, 0, 0, 0);
> +	spin_unlock(&helper->dirty_lock);
> +
> +	schedule_delayed_work(&info->deferred_work, info->fbdefio->delay);
> +}
> +#else
> +static inline void drm_fb_helper_sys_deferred(struct fb_info *info, u32 x, u32 y,
> +					      u32 width, u32 height)
> +{
> +}
> +#endif
> +
>  /**
>   * drm_fb_helper_sys_read - wrapper around fb_sys_read
>   * @info: fb_info struct pointer
> @@ -866,7 +1040,14 @@ EXPORT_SYMBOL(drm_fb_helper_sys_read);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
>  				size_t count, loff_t *ppos)
>  {
> -	return fb_sys_write(info, buf, count, ppos);
> +	ssize_t ret;
> +
> +	ret = fb_sys_write(info, buf, count, ppos);
> +	if (ret > 0)
> +		drm_fb_helper_sys_deferred(info, 0, 0,
> +					   info->var.xres, info->var.yres);
> +
> +	return ret;
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_write);
>  
> @@ -881,6 +1062,8 @@ void drm_fb_helper_sys_fillrect(struct fb_info *info,
>  				const struct fb_fillrect *rect)
>  {
>  	sys_fillrect(info, rect);
> +	drm_fb_helper_sys_deferred(info, rect->dx, rect->dy,
> +				   rect->width, rect->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_fillrect);
>  
> @@ -895,6 +1078,8 @@ void drm_fb_helper_sys_copyarea(struct fb_info *info,
>  				const struct fb_copyarea *area)
>  {
>  	sys_copyarea(info, area);
> +	drm_fb_helper_sys_deferred(info, area->dx, area->dy,
> +				   area->width, area->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_copyarea);
>  
> @@ -909,6 +1094,8 @@ void drm_fb_helper_sys_imageblit(struct fb_info *info,
>  				 const struct fb_image *image)
>  {
>  	sys_imageblit(info, image);
> +	drm_fb_helper_sys_deferred(info, image->dx, image->dy,
> +				   image->width, image->height);
>  }
>  EXPORT_SYMBOL(drm_fb_helper_sys_imageblit);
>  
> diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h
> index d8a40df..1daadc7 100644
> --- a/include/drm/drm_fb_helper.h
> +++ b/include/drm/drm_fb_helper.h
> @@ -172,6 +172,9 @@ struct drm_fb_helper_connector {
>   * @funcs: driver callbacks for fb helper
>   * @fbdev: emulated fbdev device info struct
>   * @pseudo_palette: fake palette of 16 colors
> + * @dirty_clip: clip rectangle used with deferred_io to accumulate damage to
> + *              the screen buffer
> + * @dirty_lock: spinlock protecting @dirty_clip
>   *
>   * This is the main structure used by the fbdev helpers. Drivers supporting
>   * fbdev emulation should embedded this into their overall driver structure.
> @@ -189,6 +192,10 @@ struct drm_fb_helper {
>  	const struct drm_fb_helper_funcs *funcs;
>  	struct fb_info *fbdev;
>  	u32 pseudo_palette[17];
> +#ifdef CONFIG_FB_DEFERRED_IO
> +	struct drm_clip_rect dirty_clip;
> +	spinlock_t dirty_lock;
> +#endif
>  
>  	/**
>  	 * @kernel_fb_list:
> @@ -244,6 +251,9 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
>  
>  void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper);
>  
> +void drm_fb_helper_deferred_io(struct fb_info *info,
> +			       struct list_head *pagelist);
> +
>  ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf,
>  			       size_t count, loff_t *ppos);
>  ssize_t drm_fb_helper_sys_write(struct fb_info *info, const char __user *buf,
> @@ -362,6 +372,11 @@ static inline void drm_fb_helper_unlink_fbi(struct drm_fb_helper *fb_helper)
>  {
>  }
>  
> +static inline void drm_fb_helper_deferred_io(struct fb_info *info,
> +					     struct list_head *pagelist)
> +{
> +}
> +
>  static inline ssize_t drm_fb_helper_sys_read(struct fb_info *info,
>  					     char __user *buf, size_t count,
>  					     loff_t *ppos)
> -- 
> 2.2.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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v2 0/8] drm: Add support for tiny LCD displays
  2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
                   ` (7 preceding siblings ...)
  2016-04-08 17:05 ` [RFC v2 8/8] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
@ 2016-04-13 11:11 ` Daniel Vetter
  8 siblings, 0 replies; 21+ messages in thread
From: Daniel Vetter @ 2016-04-13 11:11 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Apr 08, 2016 at 07:05:02PM +0200, Noralf Trønnes wrote:
> This is an attempt at providing a DRM version of drivers/staging/fbtft.
> 
> I'm sending this early before cleaning up the code hoping to get some
> feedback in case there are better ways to structure this.
> 
> The tinydrm module provides a very simplified view of DRM for displays that
> has onboard video memory and is connected through a slow bus like SPI/I2C.
> A driver using tinydrm has to provide a function that will be called from
> the dirtyfb ioctl (or fbdev deferred io) and optional drm_panel functions
> which can be used to set up the display controller and control backlight.
> 
> Display controllers that have a similar register interface as the MIPI DBI/DCS
> controllers can use the lcdreg module for register access.

I like this a lot. For the panel stuff itself I think it'd be good to have
comments from Thierry Redding too. Otherwise I'd say let's polish the def
io and simple kms stuff and start get that merged meanwhile.
-Daniel

> 
> 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 (8):
>   drm/fb-helper: Add fb_deferred_io support
>   drm/fb-cma-helper: Add fb_deferred_io support
>   drm: Add helper for simple kms drivers
>   drm: Add DRM support for tiny LCD displays
>   drm/tinydrm: Add lcd register abstraction
>   drm/tinydrm/lcdreg: Add SPI support
>   drm/tinydrm: Add mipi-dbi support
>   drm/tinydrm: Add support for several Adafruit TFT displays
> 
>  drivers/gpu/drm/Kconfig                            |   9 +
>  drivers/gpu/drm/Makefile                           |   2 +
>  drivers/gpu/drm/drm_fb_cma_helper.c                | 149 ++++-
>  drivers/gpu/drm/drm_fb_helper.c                    | 189 +++++-
>  drivers/gpu/drm/drm_simple_kms_helper.c            | 262 ++++++++
>  drivers/gpu/drm/tinydrm/Kconfig                    |  25 +
>  drivers/gpu/drm/tinydrm/Makefile                   |   8 +
>  drivers/gpu/drm/tinydrm/adafruit-tft.c             | 257 ++++++++
>  drivers/gpu/drm/tinydrm/core/Makefile              |   6 +
>  drivers/gpu/drm/tinydrm/core/tinydrm-core.c        | 155 +++++
>  .../gpu/drm/tinydrm/core/tinydrm-display-pipe.c    |  82 +++
>  drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c       |  94 +++
>  drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c |  99 +++
>  drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c     |  95 +++
>  drivers/gpu/drm/tinydrm/lcdreg/Kconfig             |   8 +
>  drivers/gpu/drm/tinydrm/lcdreg/Makefile            |   5 +
>  drivers/gpu/drm/tinydrm/lcdreg/internal.h          |   8 +
>  drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c       | 190 ++++++
>  drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c    | 281 ++++++++
>  drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c        | 720 +++++++++++++++++++++
>  drivers/gpu/drm/tinydrm/mipi-dbi.c                 | 253 ++++++++
>  include/drm/drm_fb_cma_helper.h                    |  14 +
>  include/drm/drm_fb_helper.h                        |  15 +
>  include/drm/drm_simple_kms_helper.h                |  44 ++
>  include/drm/tinydrm/ili9340.h                      |  47 ++
>  include/drm/tinydrm/lcdreg-spi.h                   |  63 ++
>  include/drm/tinydrm/lcdreg.h                       | 126 ++++
>  include/drm/tinydrm/mipi-dbi.h                     |  24 +
>  include/drm/tinydrm/tinydrm.h                      | 143 ++++
>  29 files changed, 3360 insertions(+), 13 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_simple_kms_helper.c
>  create mode 100644 drivers/gpu/drm/tinydrm/Kconfig
>  create mode 100644 drivers/gpu/drm/tinydrm/Makefile
>  create mode 100644 drivers/gpu/drm/tinydrm/adafruit-tft.c
>  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-display-pipe.c
>  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c
>  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c
>  create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Kconfig
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/Makefile
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/internal.h
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-core.c
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-debugfs.c
>  create mode 100644 drivers/gpu/drm/tinydrm/lcdreg/lcdreg-spi.c
>  create mode 100644 drivers/gpu/drm/tinydrm/mipi-dbi.c
>  create mode 100644 include/drm/drm_simple_kms_helper.h
>  create mode 100644 include/drm/tinydrm/ili9340.h
>  create mode 100644 include/drm/tinydrm/lcdreg-spi.h
>  create mode 100644 include/drm/tinydrm/lcdreg.h
>  create mode 100644 include/drm/tinydrm/mipi-dbi.h
>  create mode 100644 include/drm/tinydrm/tinydrm.h
> 
> --
> 2.2.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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v2 2/8] drm/fb-cma-helper: Add fb_deferred_io support
  2016-04-08 17:05 ` [RFC v2 2/8] drm/fb-cma-helper: " Noralf Trønnes
@ 2016-04-13 11:19   ` Daniel Vetter
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel Vetter @ 2016-04-13 11:19 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Fri, Apr 08, 2016 at 07:05:04PM +0200, Noralf Trønnes wrote:
> This adds fbdev deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> The driver has to provide a (struct drm_framebuffer_funcs *)->dirty()
> callback to get notification of fbdev framebuffer changes.
> If the dirty() hook is set, then fb_deferred_io is set up automatically
> by the helper.
> 
> Two functions have been added so that the driver can provide a dirty()
> function:
> - drm_fbdev_cma_init_with_funcs()
>   This makes it possible for the driver to provided a custom
>   (struct drm_fb_helper_funcs *)->fb_probe() function.
> - drm_fbdev_cma_create_with_funcs()
>   This is used by the .fb_probe hook to set a driver provided
>   (struct drm_framebuffer_funcs *)->dirty() function.
> 
> Example driver code:
> 
> static int driver_fbdev_fb_dirty(struct drm_framebuffer *fb,
> 				 struct drm_file *file_priv,
> 				 unsigned flags, unsigned color,
> 				 struct drm_clip_rect *clips,
> 				 unsigned num_clips)
> {
> 	struct drm_gem_cma_object *cma = drm_fb_cma_get_gem_obj(fb, 0);
> 
> 	return 0;
> }
> 
> static struct drm_framebuffer_funcs driver_fbdev_fb_funcs = {
> 	.destroy	= drm_fb_cma_destroy,
> 	.create_handle	= drm_fb_cma_create_handle,
> 	.dirty		= driver_fbdev_fb_dirty,
> };
> 
> static int driver_fbdev_create(struct drm_fb_helper *helper,
> 	struct drm_fb_helper_surface_size *sizes)
> {
> 	return drm_fbdev_cma_create_with_funcs(helper, sizes,
> 					&driver_fbdev_fb_funcs);
> }
> 
> static const struct drm_fb_helper_funcs driver_fb_helper_funcs = {
> 	.fb_probe = driver_fbdev_create,
> };
> 
> Driver probe:
> 	fbdev = drm_fbdev_cma_init_with_funcs(dev, 16,
> 					dev->mode_config.num_crtc,
> 					dev->mode_config.num_connector,
> 					&driver_fb_helper_funcs);
> 

The above should be part of the cma kerneldoc help text I think. If you
use drm-intel-nightly you can even properly quote source snippets and
stuff:

http://blog.ffwll.ch/2016/01/better-markup-for-kernel-gpu-docbook.html

> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

For this patch we also need an ack from Laurent Pinchart. Please cc him on
the next round.
-Daniel

> ---
>  drivers/gpu/drm/drm_fb_cma_helper.c | 149 +++++++++++++++++++++++++++++++++---
>  include/drm/drm_fb_cma_helper.h     |  14 ++++
>  2 files changed, 151 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c
> index c895b6f..b347ddd 100644
> --- a/drivers/gpu/drm/drm_fb_cma_helper.c
> +++ b/drivers/gpu/drm/drm_fb_cma_helper.c
> @@ -25,6 +25,8 @@
>  #include <drm/drm_fb_cma_helper.h>
>  #include <linux/module.h>
>  
> +#define DEFAULT_FBDEFIO_DELAY_MS 50
> +
>  struct drm_fb_cma {
>  	struct drm_framebuffer		fb;
>  	struct drm_gem_cma_object	*obj[4];
> @@ -45,7 +47,7 @@ static inline struct drm_fb_cma *to_fb_cma(struct drm_framebuffer *fb)
>  	return container_of(fb, struct drm_fb_cma, fb);
>  }
>  
> -static void drm_fb_cma_destroy(struct drm_framebuffer *fb)
> +void drm_fb_cma_destroy(struct drm_framebuffer *fb)
>  {
>  	struct drm_fb_cma *fb_cma = to_fb_cma(fb);
>  	int i;
> @@ -58,8 +60,9 @@ static void drm_fb_cma_destroy(struct drm_framebuffer *fb)
>  	drm_framebuffer_cleanup(fb);
>  	kfree(fb_cma);
>  }
> +EXPORT_SYMBOL(drm_fb_cma_destroy);
>  
> -static int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
> +int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
>  	struct drm_file *file_priv, unsigned int *handle)
>  {
>  	struct drm_fb_cma *fb_cma = to_fb_cma(fb);
> @@ -67,6 +70,7 @@ static int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
>  	return drm_gem_handle_create(file_priv,
>  			&fb_cma->obj[0]->base, handle);
>  }
> +EXPORT_SYMBOL(drm_fb_cma_create_handle);
>  
>  static struct drm_framebuffer_funcs drm_fb_cma_funcs = {
>  	.destroy	= drm_fb_cma_destroy,
> @@ -75,7 +79,7 @@ static struct drm_framebuffer_funcs drm_fb_cma_funcs = {
>  
>  static struct drm_fb_cma *drm_fb_cma_alloc(struct drm_device *dev,
>  	const const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_cma_object **obj,
> -	unsigned int num_planes)
> +	unsigned int num_planes, struct drm_framebuffer_funcs *funcs)
>  {
>  	struct drm_fb_cma *fb_cma;
>  	int ret;
> @@ -90,7 +94,7 @@ static struct drm_fb_cma *drm_fb_cma_alloc(struct drm_device *dev,
>  	for (i = 0; i < num_planes; i++)
>  		fb_cma->obj[i] = obj[i];
>  
> -	ret = drm_framebuffer_init(dev, &fb_cma->fb, &drm_fb_cma_funcs);
> +	ret = drm_framebuffer_init(dev, &fb_cma->fb, funcs);
>  	if (ret) {
>  		dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret);
>  		kfree(fb_cma);
> @@ -144,7 +148,7 @@ struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
>  		objs[i] = to_drm_gem_cma_obj(obj);
>  	}
>  
> -	fb_cma = drm_fb_cma_alloc(dev, mode_cmd, objs, i);
> +	fb_cma = drm_fb_cma_alloc(dev, mode_cmd, objs, i, &drm_fb_cma_funcs);
>  	if (IS_ERR(fb_cma)) {
>  		ret = PTR_ERR(fb_cma);
>  		goto err_gem_object_unreference;
> @@ -232,8 +236,93 @@ static struct fb_ops drm_fbdev_cma_ops = {
>  	.fb_setcmap	= drm_fb_helper_setcmap,
>  };
>  
> -static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
> -	struct drm_fb_helper_surface_size *sizes)
> +#ifdef CONFIG_FB_DEFERRED_IO
> +/*
> + * HACK
> + * fb_deferred_io_mmap() is not exported so I use this hack until it's clear
> + * that this patch will be used.
> + */
> +static int (*fb_deferred_io_mmap)(struct fb_info *info,
> +				  struct vm_area_struct *vma);
> +/*
> + * I sent a question about my need to set vm_page_prot to the fbdev ML, but
> + * I haven't heard back. So exporting fb_deferred_io_mmap() is probably
> + * the best solution.
> + */
> +static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info,
> +					  struct vm_area_struct *vma)
> +{
> +	fb_deferred_io_mmap(info, vma);
> +	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
> +
> +	return 0;
> +}
> +
> +static int drm_fbdev_cma_defio_init(struct fb_info *fbi,
> +				    struct drm_gem_cma_object *cma_obj)
> +{
> +	struct fb_deferred_io *fbdefio;
> +	struct fb_ops *fbops;
> +
> +	/*
> +	 * Per device structures are needed because:
> +	 * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap
> +	 * fbdefio: individual delays
> +	 */
> +	fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL);
> +	fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
> +	if (!fbdefio || !fbops) {
> +		kfree(fbdefio);
> +		return -ENOMEM;
> +	}
> +
> +	/* can't be offset from vaddr since dirty() uses cma_obj */
> +	fbi->screen_buffer = cma_obj->vaddr;
> +	/* fb_deferred_io_fault() needs a physical address */
> +	fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer));
> +
> +	*fbops = *fbi->fbops;
> +	fbi->fbops = fbops;
> +
> +	fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS);
> +	fbdefio->deferred_io = drm_fb_helper_deferred_io;
> +	fbi->fbdefio = fbdefio;
> +	fb_deferred_io_init(fbi);
> +
> +	if (!fb_deferred_io_mmap)
> +		fb_deferred_io_mmap = fbi->fbops->fb_mmap;
> +	fbi->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap;
> +
> +	return 0;
> +}
> +
> +static void drm_fbdev_cma_defio_fini(struct fb_info *fbi)
> +{
> +	if (!fbi->fbdefio)
> +		return;
> +
> +	fb_deferred_io_cleanup(fbi);
> +	kfree(fbi->fbdefio);
> +	kfree(fbi->fbops);
> +}
> +#else
> +static inline int drm_fbdev_cma_defio_init(struct fb_info *fbi)
> +{
> +	return 0;
> +}
> +
> +static inline void drm_fbdev_cma_defio_fini(struct fb_info *fbi)
> +{
> +}
> +#endif /* CONFIG_FB_DEFERRED_IO */
> +
> +/*
> + * For use in a (struct drm_fb_helper_funcs *)->fb_probe callback function that
> + * needs custom struct drm_framebuffer_funcs, like dirty() for deferred_io use.
> + */
> +int drm_fbdev_cma_create_with_funcs(struct drm_fb_helper *helper,
> +	struct drm_fb_helper_surface_size *sizes,
> +	struct drm_framebuffer_funcs *funcs)
>  {
>  	struct drm_fbdev_cma *fbdev_cma = to_fbdev_cma(helper);
>  	struct drm_mode_fb_cmd2 mode_cmd = { 0 };
> @@ -269,7 +358,7 @@ static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
>  		goto err_gem_free_object;
>  	}
>  
> -	fbdev_cma->fb = drm_fb_cma_alloc(dev, &mode_cmd, &obj, 1);
> +	fbdev_cma->fb = drm_fb_cma_alloc(dev, &mode_cmd, &obj, 1, funcs);
>  	if (IS_ERR(fbdev_cma->fb)) {
>  		dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
>  		ret = PTR_ERR(fbdev_cma->fb);
> @@ -295,31 +384,48 @@ static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
>  	fbi->screen_size = size;
>  	fbi->fix.smem_len = size;
>  
> +	if (funcs->dirty) {
> +		ret = drm_fbdev_cma_defio_init(fbi, obj);
> +		if (ret)
> +			goto err_cma_destroy;
> +	}
> +
>  	return 0;
>  
> +err_cma_destroy:
> +	drm_framebuffer_unregister_private(&fbdev_cma->fb->fb);
> +	drm_fb_cma_destroy(&fbdev_cma->fb->fb);
>  err_fb_info_destroy:
>  	drm_fb_helper_release_fbi(helper);
>  err_gem_free_object:
>  	dev->driver->gem_free_object(&obj->base);
>  	return ret;
>  }
> +EXPORT_SYMBOL(drm_fbdev_cma_create_with_funcs);
> +
> +static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
> +	struct drm_fb_helper_surface_size *sizes)
> +{
> +	return drm_fbdev_cma_create_with_funcs(helper, sizes, &drm_fb_cma_funcs);
> +}
>  
>  static const struct drm_fb_helper_funcs drm_fb_cma_helper_funcs = {
>  	.fb_probe = drm_fbdev_cma_create,
>  };
>  
>  /**
> - * drm_fbdev_cma_init() - Allocate and initializes a drm_fbdev_cma struct
> + * drm_fbdev_cma_init_with_funcs() - Allocate and initializes a drm_fbdev_cma struct
>   * @dev: DRM device
>   * @preferred_bpp: Preferred bits per pixel for the device
>   * @num_crtc: Number of CRTCs
>   * @max_conn_count: Maximum number of connectors
> + * @funcs: fb helper functions, in particular fb_probe()
>   *
>   * Returns a newly allocated drm_fbdev_cma struct or a ERR_PTR.
>   */
> -struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
> +struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
>  	unsigned int preferred_bpp, unsigned int num_crtc,
> -	unsigned int max_conn_count)
> +	unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs)
>  {
>  	struct drm_fbdev_cma *fbdev_cma;
>  	struct drm_fb_helper *helper;
> @@ -333,7 +439,7 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
>  
>  	helper = &fbdev_cma->fb_helper;
>  
> -	drm_fb_helper_prepare(dev, helper, &drm_fb_cma_helper_funcs);
> +	drm_fb_helper_prepare(dev, helper, funcs);
>  
>  	ret = drm_fb_helper_init(dev, helper, num_crtc, max_conn_count);
>  	if (ret < 0) {
> @@ -363,6 +469,24 @@ err_free:
>  
>  	return ERR_PTR(ret);
>  }
> +EXPORT_SYMBOL_GPL(drm_fbdev_cma_init_with_funcs);
> +
> +/**
> + * drm_fbdev_cma_init() - Allocate and initializes a drm_fbdev_cma struct
> + * @dev: DRM device
> + * @preferred_bpp: Preferred bits per pixel for the device
> + * @num_crtc: Number of CRTCs
> + * @max_conn_count: Maximum number of connectors
> + *
> + * Returns a newly allocated drm_fbdev_cma struct or a ERR_PTR.
> + */
> +struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
> +	unsigned int preferred_bpp, unsigned int num_crtc,
> +	unsigned int max_conn_count)
> +{
> +	return drm_fbdev_cma_init_with_funcs(dev, preferred_bpp, num_crtc,
> +				max_conn_count, &drm_fb_cma_helper_funcs);
> +}
>  EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
>  
>  /**
> @@ -372,6 +496,7 @@ EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
>  void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma)
>  {
>  	drm_fb_helper_unregister_fbi(&fbdev_cma->fb_helper);
> +	drm_fbdev_cma_defio_fini(fbdev_cma->fb_helper.fbdev);
>  	drm_fb_helper_release_fbi(&fbdev_cma->fb_helper);
>  
>  	if (fbdev_cma->fb) {
> diff --git a/include/drm/drm_fb_cma_helper.h b/include/drm/drm_fb_cma_helper.h
> index be62bd3..6554b6f 100644
> --- a/include/drm/drm_fb_cma_helper.h
> +++ b/include/drm/drm_fb_cma_helper.h
> @@ -4,11 +4,18 @@
>  struct drm_fbdev_cma;
>  struct drm_gem_cma_object;
>  
> +struct drm_fb_helper_surface_size;
> +struct drm_framebuffer_funcs;
> +struct drm_fb_helper_funcs;
>  struct drm_framebuffer;
> +struct drm_fb_helper;
>  struct drm_device;
>  struct drm_file;
>  struct drm_mode_fb_cmd2;
>  
> +struct drm_fbdev_cma *drm_fbdev_cma_init_with_funcs(struct drm_device *dev,
> +	unsigned int preferred_bpp, unsigned int num_crtc,
> +	unsigned int max_conn_count, const struct drm_fb_helper_funcs *funcs);
>  struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
>  	unsigned int preferred_bpp, unsigned int num_crtc,
>  	unsigned int max_conn_count);
> @@ -16,6 +23,13 @@ void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma);
>  
>  void drm_fbdev_cma_restore_mode(struct drm_fbdev_cma *fbdev_cma);
>  void drm_fbdev_cma_hotplug_event(struct drm_fbdev_cma *fbdev_cma);
> +int drm_fbdev_cma_create_with_funcs(struct drm_fb_helper *helper,
> +	struct drm_fb_helper_surface_size *sizes,
> +	struct drm_framebuffer_funcs *funcs);
> +
> +void drm_fb_cma_destroy(struct drm_framebuffer *fb);
> +int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
> +	struct drm_file *file_priv, unsigned int *handle);
>  
>  struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
>  	struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd);
> -- 
> 2.2.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
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-13 11:09   ` Daniel Vetter
@ 2016-04-18 15:15     ` Noralf Trønnes
  2016-04-20 11:12       ` Daniel Vetter
  2016-04-20 22:29       ` Dave Airlie
  0 siblings, 2 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-18 15:15 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 13.04.2016 13:09, skrev Daniel Vetter:
> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>> Accumulated fbdev framebuffer changes are signaled using the callback
>> (struct drm_framebuffer_funcs *)->dirty()
>>
>> The drm_fb_helper_sys_*() functions will accumulate changes and
>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>> This worker is used by the deferred io mmap code to signal that it
>> has been collecting page faults. The page faults and/or other changes
>> are then merged into a drm_clip_rect and passed to the framebuffer
>> dirty() function.
>>
>> The driver is responsible for setting up the fb_info.fbdefio structure
>> and calling fb_deferred_io_init() using the provided callback:
>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> For this one it'd be awesome to throw patches for qxl/udl on top to remove
> their own hand-rolled implementations. Just to maximize the testing
> coverage of this new code. Should be doable to set up a qxl virtual
> machine quickly, but even without that we should be able to pull it in
> (since it's mostly just about removing code from these two drivers).

There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and 
vmwgfx.

drivers/gpu/drm/vmwgfx
It doesn't use drm_fb_helper (uses the cfb_{fillrect,copyarea,imageblit}
functions directly). This made me think that I should probably add
fb_deferred_io support to drm_fb_helper_cfb_*() as well.

drivers/gpu/drm/udl
First of all it has had deferred io disabled by default since 2013
(commit 677d23b). Secondly it handles damage directly in it's
fb_{fillrect,copyarea,imageblit} functions, but defers to the next call if
it is in atomic context. My patch always defers those function to a worker.
The driver uses the drm_fb_helper_sys_* functions, so my patch would mess
it up if someone would enable deferred io (module_param: fb_defio).
So this driver isn't a good candidate for easy conversion also because it
has different code paths for fbdev mmap damage and the other damages
(although the code is similar). But it needs a patch to use the sys_*()
functions directly.

drivers/gpu/drm/qxl
This one uses a worker as a buffer between the mmap damage tracking and
fb_*() functions, and flushing of the changes. I'll give it a go.

Studying these in detail and looking at the git log was useful as it showed
me that the (struct fb_ops *)->fb_*() functions can be called in interrupt
context. This means that I need the irq version of spin_lock(). It also
validates my choice to defer these calls using the mmap defer damage worker
((struct fb_info).deferred_work), because it ensures that the dirty() call
will always run in process context.


Noralf.

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

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

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-18 15:15     ` Noralf Trønnes
@ 2016-04-20 11:12       ` Daniel Vetter
  2016-04-20 15:22         ` Noralf Trønnes
  2016-04-20 22:29       ` Dave Airlie
  1 sibling, 1 reply; 21+ messages in thread
From: Daniel Vetter @ 2016-04-20 11:12 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Mon, Apr 18, 2016 at 05:15:03PM +0200, Noralf Trønnes wrote:
> 
> Den 13.04.2016 13:09, skrev Daniel Vetter:
> >On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
> >>This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
> >>Accumulated fbdev framebuffer changes are signaled using the callback
> >>(struct drm_framebuffer_funcs *)->dirty()
> >>
> >>The drm_fb_helper_sys_*() functions will accumulate changes and
> >>schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
> >>This worker is used by the deferred io mmap code to signal that it
> >>has been collecting page faults. The page faults and/or other changes
> >>are then merged into a drm_clip_rect and passed to the framebuffer
> >>dirty() function.
> >>
> >>The driver is responsible for setting up the fb_info.fbdefio structure
> >>and calling fb_deferred_io_init() using the provided callback:
> >>(struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
> >>
> >>Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> >For this one it'd be awesome to throw patches for qxl/udl on top to remove
> >their own hand-rolled implementations. Just to maximize the testing
> >coverage of this new code. Should be doable to set up a qxl virtual
> >machine quickly, but even without that we should be able to pull it in
> >(since it's mostly just about removing code from these two drivers).
> 
> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
> vmwgfx.
> 
> drivers/gpu/drm/vmwgfx
> It doesn't use drm_fb_helper (uses the cfb_{fillrect,copyarea,imageblit}
> functions directly). This made me think that I should probably add
> fb_deferred_io support to drm_fb_helper_cfb_*() as well.

Yup, that one is special and can be ignored.

> drivers/gpu/drm/udl
> First of all it has had deferred io disabled by default since 2013
> (commit 677d23b). Secondly it handles damage directly in it's
> fb_{fillrect,copyarea,imageblit} functions, but defers to the next call if
> it is in atomic context. My patch always defers those function to a worker.
> The driver uses the drm_fb_helper_sys_* functions, so my patch would mess
> it up if someone would enable deferred io (module_param: fb_defio).
> So this driver isn't a good candidate for easy conversion also because it
> has different code paths for fbdev mmap damage and the other damages
> (although the code is similar). But it needs a patch to use the sys_*()
> functions directly.

Since it's disabled by default I'd just do a conversion. It should result
largely in deleting code and just using the fb helpers. What's special
with the mmap handling, and do we care?

> drivers/gpu/drm/qxl
> This one uses a worker as a buffer between the mmap damage tracking and
> fb_*() functions, and flushing of the changes. I'll give it a go.
> 
> Studying these in detail and looking at the git log was useful as it showed
> me that the (struct fb_ops *)->fb_*() functions can be called in interrupt
> context. This means that I need the irq version of spin_lock(). It also
> validates my choice to defer these calls using the mmap defer damage worker
> ((struct fb_info).deferred_work), because it ensures that the dirty() call
> will always run in process context.

Yeah, mostly I'd like to get qxl&udl converted so that all the lessons
learned in those implementations aren't lost. Great work digging out those
details ;-)

Thanks, 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] 21+ messages in thread

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-20 11:12       ` Daniel Vetter
@ 2016-04-20 15:22         ` Noralf Trønnes
  0 siblings, 0 replies; 21+ messages in thread
From: Noralf Trønnes @ 2016-04-20 15:22 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 20.04.2016 13:12, skrev Daniel Vetter:
> On Mon, Apr 18, 2016 at 05:15:03PM +0200, Noralf Trønnes wrote:
>> Den 13.04.2016 13:09, skrev Daniel Vetter:
>>> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>>>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>>>> Accumulated fbdev framebuffer changes are signaled using the callback
>>>> (struct drm_framebuffer_funcs *)->dirty()
>>>>
>>>> The drm_fb_helper_sys_*() functions will accumulate changes and
>>>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>>>> This worker is used by the deferred io mmap code to signal that it
>>>> has been collecting page faults. The page faults and/or other changes
>>>> are then merged into a drm_clip_rect and passed to the framebuffer
>>>> dirty() function.
>>>>
>>>> The driver is responsible for setting up the fb_info.fbdefio structure
>>>> and calling fb_deferred_io_init() using the provided callback:
>>>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>>>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>> For this one it'd be awesome to throw patches for qxl/udl on top to remove
>>> their own hand-rolled implementations. Just to maximize the testing
>>> coverage of this new code. Should be doable to set up a qxl virtual
>>> machine quickly, but even without that we should be able to pull it in
>>> (since it's mostly just about removing code from these two drivers).
>> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
>> vmwgfx.
>>
>> drivers/gpu/drm/vmwgfx
>> It doesn't use drm_fb_helper (uses the cfb_{fillrect,copyarea,imageblit}
>> functions directly). This made me think that I should probably add
>> fb_deferred_io support to drm_fb_helper_cfb_*() as well.
> Yup, that one is special and can be ignored.
>
>> drivers/gpu/drm/udl
>> First of all it has had deferred io disabled by default since 2013
>> (commit 677d23b). Secondly it handles damage directly in it's
>> fb_{fillrect,copyarea,imageblit} functions, but defers to the next call if
>> it is in atomic context. My patch always defers those function to a worker.
>> The driver uses the drm_fb_helper_sys_* functions, so my patch would mess
>> it up if someone would enable deferred io (module_param: fb_defio).
>> So this driver isn't a good candidate for easy conversion also because it
>> has different code paths for fbdev mmap damage and the other damages
>> (although the code is similar). But it needs a patch to use the sys_*()
>> functions directly.
> Since it's disabled by default I'd just do a conversion. It should result
> largely in deleting code and just using the fb helpers. What's special
> with the mmap handling, and do we care?

The git log mentions page list corruption, but I realised that I can
just disable the deferred mmap code and keep the rest.

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

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

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-18 15:15     ` Noralf Trønnes
  2016-04-20 11:12       ` Daniel Vetter
@ 2016-04-20 22:29       ` Dave Airlie
  2016-04-21  6:53         ` Daniel Vetter
  1 sibling, 1 reply; 21+ messages in thread
From: Dave Airlie @ 2016-04-20 22:29 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: Thomas Petazzoni, dri-devel

On 19 April 2016 at 01:15, Noralf Trønnes <noralf@tronnes.org> wrote:
>
> Den 13.04.2016 13:09, skrev Daniel Vetter:
>>
>> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>>>
>>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>>> Accumulated fbdev framebuffer changes are signaled using the callback
>>> (struct drm_framebuffer_funcs *)->dirty()
>>>
>>> The drm_fb_helper_sys_*() functions will accumulate changes and
>>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>>> This worker is used by the deferred io mmap code to signal that it
>>> has been collecting page faults. The page faults and/or other changes
>>> are then merged into a drm_clip_rect and passed to the framebuffer
>>> dirty() function.
>>>
>>> The driver is responsible for setting up the fb_info.fbdefio structure
>>> and calling fb_deferred_io_init() using the provided callback:
>>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>>
>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>
>> For this one it'd be awesome to throw patches for qxl/udl on top to remove
>> their own hand-rolled implementations. Just to maximize the testing
>> coverage of this new code. Should be doable to set up a qxl virtual
>> machine quickly, but even without that we should be able to pull it in
>> (since it's mostly just about removing code from these two drivers).
>
>
> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
> vmwgfx.

So I'm a bit confused. For me fb defio is a thing which userspace users,
without an ioctl.

So we keep track in the kernel of dirty pages in the fb and then flush those
pages. Now the thing is that last time I tried this it interacted badly with
gem/ttm as defio wanted to do something to the same pages etc.

So I disabled it in udl for that reasons.

if we are talking about just having some damage tracking and not doing
the page level tracking then ignore me.

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

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

* Re: [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support
  2016-04-20 22:29       ` Dave Airlie
@ 2016-04-21  6:53         ` Daniel Vetter
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel Vetter @ 2016-04-21  6:53 UTC (permalink / raw)
  To: Dave Airlie; +Cc: Thomas Petazzoni, dri-devel

On Thu, Apr 21, 2016 at 12:29 AM, Dave Airlie <airlied@gmail.com> wrote:
> On 19 April 2016 at 01:15, Noralf Trønnes <noralf@tronnes.org> wrote:
>>
>> Den 13.04.2016 13:09, skrev Daniel Vetter:
>>>
>>> On Fri, Apr 08, 2016 at 07:05:03PM +0200, Noralf Trønnes wrote:
>>>>
>>>> This adds deferred io support if CONFIG_FB_DEFERRED_IO is enabled.
>>>> Accumulated fbdev framebuffer changes are signaled using the callback
>>>> (struct drm_framebuffer_funcs *)->dirty()
>>>>
>>>> The drm_fb_helper_sys_*() functions will accumulate changes and
>>>> schedule fb_info.deferred_work _if_ fb_info.fbdefio is set.
>>>> This worker is used by the deferred io mmap code to signal that it
>>>> has been collecting page faults. The page faults and/or other changes
>>>> are then merged into a drm_clip_rect and passed to the framebuffer
>>>> dirty() function.
>>>>
>>>> The driver is responsible for setting up the fb_info.fbdefio structure
>>>> and calling fb_deferred_io_init() using the provided callback:
>>>> (struct fb_info *)->fbdefio->deferred_io = drm_fb_helper_deferred_io;
>>>>
>>>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
>>>
>>> For this one it'd be awesome to throw patches for qxl/udl on top to remove
>>> their own hand-rolled implementations. Just to maximize the testing
>>> coverage of this new code. Should be doable to set up a qxl virtual
>>> machine quickly, but even without that we should be able to pull it in
>>> (since it's mostly just about removing code from these two drivers).
>>
>>
>> There are three fb_deferred_io users in drivers/gpu/drm: qxl, udl and
>> vmwgfx.
>
> So I'm a bit confused. For me fb defio is a thing which userspace users,
> without an ioctl.
>
> So we keep track in the kernel of dirty pages in the fb and then flush those
> pages. Now the thing is that last time I tried this it interacted badly with
> gem/ttm as defio wanted to do something to the same pages etc.
>
> So I disabled it in udl for that reasons.
>
> if we are talking about just having some damage tracking and not doing
> the page level tracking then ignore me.

Yeah I read through the code, and with shmem it'll die because shmem
and fb defio will fight over the page lru. But if you have your pages
backed by cma, or the fbdev mmap pointing at an mmio range I think it
should all work (and seems to at least for Noralf).

Either way it's all optional opt-in code (just shared) and drivers can
still opt to only do defio for in-kernel rendering and either have
broken fbdev mmap support (like udl today), or implement their own
defio mmap support compatible with shmem (not that hard either, but
meh). And Noralf's conversion patches should result in bug-for-bug
compatibility.

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] 21+ messages in thread

* Re: [RFC v2 3/8] drm: Add helper for simple kms drivers
  2016-04-13 11:05   ` Daniel Vetter
@ 2016-05-02 15:55     ` Noralf Trønnes
  2016-05-02 20:20       ` Daniel Vetter
  0 siblings, 1 reply; 21+ messages in thread
From: Noralf Trønnes @ 2016-05-02 15:55 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: thomas.petazzoni, dri-devel


Den 13.04.2016 13:05, skrev Daniel Vetter:
> On Fri, Apr 08, 2016 at 07:05:05PM +0200, Noralf Trønnes wrote:
>> Provides helper functions for drivers that have a simple display
>> pipeline. Plane, crtc and encoder are collapsed into one entity.
>>
>> Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

[...]

>> +static void drm_simple_kms_encoder_disable(struct drm_encoder *encoder)
>> +{
>> +}
>> +
>> +static void drm_simple_kms_encoder_enable(struct drm_encoder *encoder)
>> +{
>> +}
>> +
>> +static int drm_simple_kms_encoder_atomic_check(struct drm_encoder *encoder,
>> +					struct drm_crtc_state *crtc_state,
>> +					struct drm_connector_state *conn_state)
>> +{
>> +	return 0;
>> +}
> Above dummy functions shouldn't be necessary.

I could remove .atomic_check, but not .disable and .enable which gave me
NULL pointer errors. I need .enable or .commit, and .disable or .dpms.

Gist:

void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
                                               struct drm_atomic_state 
*old_state)
{
         for_each_connector_in_state(old_state, connector, 
old_conn_state, i) {
                 const struct drm_encoder_helper_funcs *funcs;

                 funcs = encoder->helper_private;

                 if (funcs->enable)
                         funcs->enable(encoder);
                 else
                         funcs->commit(encoder);

static void
disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
{
         for_each_connector_in_state(old_state, connector, 
old_conn_state, i) {
                 const struct drm_encoder_helper_funcs *funcs;

                 funcs = encoder->helper_private;

                 if (connector->state->crtc && funcs->prepare)
                         funcs->prepare(encoder);
                 else if (funcs->disable)
                         funcs->disable(encoder);
                 else
                         funcs->dpms(encoder, DRM_MODE_DPMS_OFF);

>
>> +
>> +static const struct drm_encoder_helper_funcs drm_simple_kms_encoder_helper_funcs = {
>> +	.disable = drm_simple_kms_encoder_disable,
>> +	.enable = drm_simple_kms_encoder_enable,
>> +	.atomic_check = drm_simple_kms_encoder_atomic_check,
>> +};
>> +
>> +static const struct drm_encoder_funcs drm_simple_kms_encoder_funcs = {
>> +	.destroy = drm_encoder_cleanup,
>> +};
>> +
>> +static void drm_simple_kms_crtc_enable(struct drm_crtc *crtc)
>> +{
>> +	struct drm_simple_display_pipe *pipe;
>> +
>> +	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
>> +	if (!pipe->funcs || !pipe->funcs->enable)
>> +		return;
>> +
>> +	pipe->funcs->enable(pipe, crtc->state);
>> +}
>> +
>> +static void drm_simple_kms_crtc_disable(struct drm_crtc *crtc)
>> +{
>> +	struct drm_simple_display_pipe *pipe;
>> +
>> +	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
>> +	if (!pipe->funcs || !pipe->funcs->disable)
>> +		return;
>> +
>> +	pipe->funcs->disable(pipe);
>> +}
>> +
>> +static const struct drm_crtc_helper_funcs drm_simple_kms_crtc_helper_funcs = {
>> +	.disable = drm_simple_kms_crtc_disable,
>> +	.enable = drm_simple_kms_crtc_enable,
>> +};
>> +
>> +static const struct drm_crtc_funcs drm_simple_kms_crtc_funcs = {
>> +	.reset = drm_atomic_helper_crtc_reset,
>> +	.destroy = drm_crtc_cleanup,
>> +	.set_config = drm_atomic_helper_set_config,
>> +	.page_flip = drm_atomic_helper_page_flip,
>> +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
>> +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
>> +};
>> +
>> +static void drm_simple_kms_plane_atomic_update(struct drm_plane *plane,
>> +					struct drm_plane_state *old_state)
>> +{
>> +	struct drm_simple_display_pipe *pipe;
>> +
>> +	pipe = container_of(plane, struct drm_simple_display_pipe, plane);
>> +	if (!pipe->funcs || !pipe->funcs->plane_update)
>> +		return;
>> +
>> +	pipe->funcs->plane_update(pipe, old_state);
>> +}
>> +
>> +static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = {
>> +	.atomic_update = drm_simple_kms_plane_atomic_update,
>> +};
>> +
>> +static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
>> +	.update_plane		= drm_atomic_helper_update_plane,
>> +	.disable_plane		= drm_atomic_helper_disable_plane,
>> +	.destroy		= drm_plane_cleanup,
>> +	.reset			= drm_atomic_helper_plane_reset,
>> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
>> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
>> +};
>> +
>> +/**
>> + * drm_simple_display_pipe_init - Initialize a simple display pipe
>> + * @dev: DRM device
>> + * @pipe: simple display pipe object to initialize
>> + * @funcs: callbacks for the display pipe
>> + * @formats: array of supported formats (%DRM_FORMAT_*)
>> + * @format_count: number of elements in @formats
>> + * @connector: connector to attach and register
>> + *
>> + * Sets up a display pipe which consist of a really simple plane-crtc-encoder
>> + * pipe coupled with the provided connector.
>> + *
>> + * Returns:
>> + * Zero on success, error code on failure.
>> + */
>> +int drm_simple_display_pipe_init(struct drm_device *dev,
>> +				 struct drm_simple_display_pipe *pipe,
>> +				 struct drm_simple_display_pipe_funcs *funcs,
>> +				 const uint32_t *formats, unsigned int format_count,
>> +				 struct drm_connector *connector)
>> +{
>> +	struct drm_encoder *encoder = &pipe->encoder;
>> +	struct drm_plane *plane = &pipe->plane;
>> +	struct drm_crtc *crtc = &pipe->crtc;
>> +	int ret;
>> +
>> +	pipe->funcs = funcs;
>> +
>> +	drm_plane_helper_add(plane, &drm_simple_kms_plane_helper_funcs);
>> +	ret = drm_universal_plane_init(dev, plane, 0, &drm_simple_kms_plane_funcs,
>> +				       formats, format_count,
>> +				       DRM_PLANE_TYPE_PRIMARY, NULL);
>> +	if (ret)
>> +		return ret;
>> +
>> +	drm_crtc_helper_add(crtc, &drm_simple_kms_crtc_helper_funcs);
>> +	ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
>> +					&drm_simple_kms_crtc_funcs, NULL);
>> +	if (ret)
>> +		return ret;
>> +
>> +	encoder->possible_crtcs = 1 << drm_crtc_index(crtc);
>> +	drm_encoder_helper_add(encoder, &drm_simple_kms_encoder_helper_funcs);
>> +	ret = drm_encoder_init(dev, encoder, &drm_simple_kms_encoder_funcs,
>> +			       DRM_MODE_ENCODER_NONE, NULL);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = drm_mode_connector_attach_encoder(connector, encoder);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return drm_connector_register(connector);
>> +}
>> +EXPORT_SYMBOL(drm_simple_display_pipe_init);
>> +
>> +MODULE_LICENSE("GPL");
>> diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h
>> new file mode 100644
>> index 0000000..0f7461b
>> --- /dev/null
>> +++ b/include/drm/drm_simple_kms_helper.h
>> @@ -0,0 +1,44 @@
>> +/*
>> + * 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_DRM_SIMPLE_KMS_HELPER_H
>> +#define __LINUX_DRM_SIMPLE_KMS_HELPER_H
>> +
>> +struct drm_simple_display_pipe;
>> +
>> +struct drm_simple_display_pipe_funcs {
>> +	void (*enable)(struct drm_simple_display_pipe *pipe,
>> +		       struct drm_crtc_state *crtc_state);
>> +	void (*disable)(struct drm_simple_display_pipe *pipe);
>> +	void (*plane_update)(struct drm_simple_display_pipe *pipe,
>> +			     struct drm_plane_state *plane_state);
> On 2nd thought maybe we want an atomic_check in here too, with same
> parameters *pipe, *crtc_state and *plane_state (so that it's useful for
> both plane-only updates and modeset changes).

I could do this for the plane:

struct drm_simple_display_pipe_funcs {
      void (*plane_check)(struct drm_simple_display_pipe *pipe,
                          struct drm_plane_state *plane_state);
}

static void drm_simple_kms_plane_atomic_check(struct drm_plane *plane,
                                         struct drm_plane_state *state)
{
         struct drm_simple_display_pipe *pipe;

         pipe = container_of(plane, struct drm_simple_display_pipe, plane);
         if (!pipe->funcs || !pipe->funcs->plane_check)
                 return;

         pipe->funcs->plane_check(pipe, state);
}

static const struct drm_plane_helper_funcs 
drm_simple_kms_plane_helper_funcs = {
         .atomic_check = drm_simple_kms_plane_atomic_check,
};

But how should I do crtc_state ?

Noralf.

>> +};
>> +
>> +struct drm_simple_display_pipe {
>> +	struct drm_crtc crtc;
>> +	struct drm_plane plane;
>> +	struct drm_encoder encoder;
>> +	struct drm_connector *connector;
>> +
>> +	struct drm_simple_display_pipe_funcs *funcs;
>> +};
>> +
>> +int drm_simple_display_pipe_init(struct drm_device *dev,
>> +				 struct drm_simple_display_pipe *pipe,
>> +				 struct drm_simple_display_pipe_funcs *funcs,
>> +				 const uint32_t *formats, unsigned int format_count,
>> +				 struct drm_connector *connector);
>> +struct drm_connector *
>> +drm_simple_kms_panel_connector_create(struct drm_device *dev,
>> +				      struct drm_panel *panel,
>> +				      int connector_type);
>> +
>> +
>> +
>> +#endif /* __LINUX_DRM_SIMPLE_KMS_HELPER_H */
>> -- 
>> 2.2.2
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

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

* Re: [RFC v2 3/8] drm: Add helper for simple kms drivers
  2016-05-02 15:55     ` Noralf Trønnes
@ 2016-05-02 20:20       ` Daniel Vetter
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel Vetter @ 2016-05-02 20:20 UTC (permalink / raw)
  To: Noralf Trønnes; +Cc: thomas.petazzoni, dri-devel

On Mon, May 02, 2016 at 05:55:15PM +0200, Noralf Trønnes wrote:
> 
> Den 13.04.2016 13:05, skrev Daniel Vetter:
> >On Fri, Apr 08, 2016 at 07:05:05PM +0200, Noralf Trønnes wrote:
> >>Provides helper functions for drivers that have a simple display
> >>pipeline. Plane, crtc and encoder are collapsed into one entity.
> >>
> >>Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
> 
> [...]
> 
> >>+static void drm_simple_kms_encoder_disable(struct drm_encoder *encoder)
> >>+{
> >>+}
> >>+
> >>+static void drm_simple_kms_encoder_enable(struct drm_encoder *encoder)
> >>+{
> >>+}
> >>+
> >>+static int drm_simple_kms_encoder_atomic_check(struct drm_encoder *encoder,
> >>+					struct drm_crtc_state *crtc_state,
> >>+					struct drm_connector_state *conn_state)
> >>+{
> >>+	return 0;
> >>+}
> >Above dummy functions shouldn't be necessary.
> 
> I could remove .atomic_check, but not .disable and .enable which gave me
> NULL pointer errors. I need .enable or .commit, and .disable or .dpms.
> 
> Gist:
> 
> void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
>                                               struct drm_atomic_state
> *old_state)
> {
>         for_each_connector_in_state(old_state, connector, old_conn_state, i)
> {
>                 const struct drm_encoder_helper_funcs *funcs;
> 
>                 funcs = encoder->helper_private;
> 
>                 if (funcs->enable)
>                         funcs->enable(encoder);
>                 else
>                         funcs->commit(encoder);
> 
> static void
> disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
> {
>         for_each_connector_in_state(old_state, connector, old_conn_state, i)
> {
>                 const struct drm_encoder_helper_funcs *funcs;
> 
>                 funcs = encoder->helper_private;
> 
>                 if (connector->state->crtc && funcs->prepare)
>                         funcs->prepare(encoder);
>                 else if (funcs->disable)
>                         funcs->disable(encoder);
>                 else
>                         funcs->dpms(encoder, DRM_MODE_DPMS_OFF);

Hm right. I'm voting that we make them all optional, there's been an
effort all over the place to not force drivers to type in silly dummy
funcs.

> >>+
> >>+static const struct drm_encoder_helper_funcs drm_simple_kms_encoder_helper_funcs = {
> >>+	.disable = drm_simple_kms_encoder_disable,
> >>+	.enable = drm_simple_kms_encoder_enable,
> >>+	.atomic_check = drm_simple_kms_encoder_atomic_check,
> >>+};
> >>+
> >>+static const struct drm_encoder_funcs drm_simple_kms_encoder_funcs = {
> >>+	.destroy = drm_encoder_cleanup,
> >>+};
> >>+
> >>+static void drm_simple_kms_crtc_enable(struct drm_crtc *crtc)
> >>+{
> >>+	struct drm_simple_display_pipe *pipe;
> >>+
> >>+	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
> >>+	if (!pipe->funcs || !pipe->funcs->enable)
> >>+		return;
> >>+
> >>+	pipe->funcs->enable(pipe, crtc->state);
> >>+}
> >>+
> >>+static void drm_simple_kms_crtc_disable(struct drm_crtc *crtc)
> >>+{
> >>+	struct drm_simple_display_pipe *pipe;
> >>+
> >>+	pipe = container_of(crtc, struct drm_simple_display_pipe, crtc);
> >>+	if (!pipe->funcs || !pipe->funcs->disable)
> >>+		return;
> >>+
> >>+	pipe->funcs->disable(pipe);
> >>+}
> >>+
> >>+static const struct drm_crtc_helper_funcs drm_simple_kms_crtc_helper_funcs = {
> >>+	.disable = drm_simple_kms_crtc_disable,
> >>+	.enable = drm_simple_kms_crtc_enable,
> >>+};
> >>+
> >>+static const struct drm_crtc_funcs drm_simple_kms_crtc_funcs = {
> >>+	.reset = drm_atomic_helper_crtc_reset,
> >>+	.destroy = drm_crtc_cleanup,
> >>+	.set_config = drm_atomic_helper_set_config,
> >>+	.page_flip = drm_atomic_helper_page_flip,
> >>+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> >>+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> >>+};
> >>+
> >>+static void drm_simple_kms_plane_atomic_update(struct drm_plane *plane,
> >>+					struct drm_plane_state *old_state)
> >>+{
> >>+	struct drm_simple_display_pipe *pipe;
> >>+
> >>+	pipe = container_of(plane, struct drm_simple_display_pipe, plane);
> >>+	if (!pipe->funcs || !pipe->funcs->plane_update)
> >>+		return;
> >>+
> >>+	pipe->funcs->plane_update(pipe, old_state);
> >>+}
> >>+
> >>+static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = {
> >>+	.atomic_update = drm_simple_kms_plane_atomic_update,
> >>+};
> >>+
> >>+static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
> >>+	.update_plane		= drm_atomic_helper_update_plane,
> >>+	.disable_plane		= drm_atomic_helper_disable_plane,
> >>+	.destroy		= drm_plane_cleanup,
> >>+	.reset			= drm_atomic_helper_plane_reset,
> >>+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
> >>+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
> >>+};
> >>+
> >>+/**
> >>+ * drm_simple_display_pipe_init - Initialize a simple display pipe
> >>+ * @dev: DRM device
> >>+ * @pipe: simple display pipe object to initialize
> >>+ * @funcs: callbacks for the display pipe
> >>+ * @formats: array of supported formats (%DRM_FORMAT_*)
> >>+ * @format_count: number of elements in @formats
> >>+ * @connector: connector to attach and register
> >>+ *
> >>+ * Sets up a display pipe which consist of a really simple plane-crtc-encoder
> >>+ * pipe coupled with the provided connector.
> >>+ *
> >>+ * Returns:
> >>+ * Zero on success, error code on failure.
> >>+ */
> >>+int drm_simple_display_pipe_init(struct drm_device *dev,
> >>+				 struct drm_simple_display_pipe *pipe,
> >>+				 struct drm_simple_display_pipe_funcs *funcs,
> >>+				 const uint32_t *formats, unsigned int format_count,
> >>+				 struct drm_connector *connector)
> >>+{
> >>+	struct drm_encoder *encoder = &pipe->encoder;
> >>+	struct drm_plane *plane = &pipe->plane;
> >>+	struct drm_crtc *crtc = &pipe->crtc;
> >>+	int ret;
> >>+
> >>+	pipe->funcs = funcs;
> >>+
> >>+	drm_plane_helper_add(plane, &drm_simple_kms_plane_helper_funcs);
> >>+	ret = drm_universal_plane_init(dev, plane, 0, &drm_simple_kms_plane_funcs,
> >>+				       formats, format_count,
> >>+				       DRM_PLANE_TYPE_PRIMARY, NULL);
> >>+	if (ret)
> >>+		return ret;
> >>+
> >>+	drm_crtc_helper_add(crtc, &drm_simple_kms_crtc_helper_funcs);
> >>+	ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
> >>+					&drm_simple_kms_crtc_funcs, NULL);
> >>+	if (ret)
> >>+		return ret;
> >>+
> >>+	encoder->possible_crtcs = 1 << drm_crtc_index(crtc);
> >>+	drm_encoder_helper_add(encoder, &drm_simple_kms_encoder_helper_funcs);
> >>+	ret = drm_encoder_init(dev, encoder, &drm_simple_kms_encoder_funcs,
> >>+			       DRM_MODE_ENCODER_NONE, NULL);
> >>+	if (ret)
> >>+		return ret;
> >>+
> >>+	ret = drm_mode_connector_attach_encoder(connector, encoder);
> >>+	if (ret)
> >>+		return ret;
> >>+
> >>+	return drm_connector_register(connector);
> >>+}
> >>+EXPORT_SYMBOL(drm_simple_display_pipe_init);
> >>+
> >>+MODULE_LICENSE("GPL");
> >>diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h
> >>new file mode 100644
> >>index 0000000..0f7461b
> >>--- /dev/null
> >>+++ b/include/drm/drm_simple_kms_helper.h
> >>@@ -0,0 +1,44 @@
> >>+/*
> >>+ * 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_DRM_SIMPLE_KMS_HELPER_H
> >>+#define __LINUX_DRM_SIMPLE_KMS_HELPER_H
> >>+
> >>+struct drm_simple_display_pipe;
> >>+
> >>+struct drm_simple_display_pipe_funcs {
> >>+	void (*enable)(struct drm_simple_display_pipe *pipe,
> >>+		       struct drm_crtc_state *crtc_state);
> >>+	void (*disable)(struct drm_simple_display_pipe *pipe);
> >>+	void (*plane_update)(struct drm_simple_display_pipe *pipe,
> >>+			     struct drm_plane_state *plane_state);
> >On 2nd thought maybe we want an atomic_check in here too, with same
> >parameters *pipe, *crtc_state and *plane_state (so that it's useful for
> >both plane-only updates and modeset changes).
> 
> I could do this for the plane:
> 
> struct drm_simple_display_pipe_funcs {
>      void (*plane_check)(struct drm_simple_display_pipe *pipe,
>                          struct drm_plane_state *plane_state);
> }
> 
> static void drm_simple_kms_plane_atomic_check(struct drm_plane *plane,
>                                         struct drm_plane_state *state)
> {
>         struct drm_simple_display_pipe *pipe;
> 
>         pipe = container_of(plane, struct drm_simple_display_pipe, plane);
>         if (!pipe->funcs || !pipe->funcs->plane_check)
>                 return;
> 
>         pipe->funcs->plane_check(pipe, state);
> }
> 
> static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs
> = {
>         .atomic_check = drm_simple_kms_plane_atomic_check,
> };
> 
> But how should I do crtc_state ?

Atomic core guarantees that if you have a plane state, then the crtc state
for that plane is always hanging around. You should be able to get at it
(but only for atomic_check functions, there's a different way in the
commit hooks) using:

drm_atomic_get_existing_plane_state(plane_state->state,
				    plane_state->crtc);


Yes I know I should get around to documeting all the structures we have
... Unfortunately it's a bit a mess and I'd like to clean up the code
organization first a bit.

Cheers, 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] 21+ messages in thread

end of thread, other threads:[~2016-05-02 20:20 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-08 17:05 [RFC v2 0/8] drm: Add support for tiny LCD displays Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 1/8] drm/fb-helper: Add fb_deferred_io support Noralf Trønnes
2016-04-13 10:57   ` Daniel Vetter
2016-04-13 11:09   ` Daniel Vetter
2016-04-18 15:15     ` Noralf Trønnes
2016-04-20 11:12       ` Daniel Vetter
2016-04-20 15:22         ` Noralf Trønnes
2016-04-20 22:29       ` Dave Airlie
2016-04-21  6:53         ` Daniel Vetter
2016-04-08 17:05 ` [RFC v2 2/8] drm/fb-cma-helper: " Noralf Trønnes
2016-04-13 11:19   ` Daniel Vetter
2016-04-08 17:05 ` [RFC v2 3/8] drm: Add helper for simple kms drivers Noralf Trønnes
2016-04-13 11:05   ` Daniel Vetter
2016-05-02 15:55     ` Noralf Trønnes
2016-05-02 20:20       ` Daniel Vetter
2016-04-08 17:05 ` [RFC v2 4/8] drm: Add DRM support for tiny LCD displays Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 5/8] drm/tinydrm: Add lcd register abstraction Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 6/8] drm/tinydrm/lcdreg: Add SPI support Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 7/8] drm/tinydrm: Add mipi-dbi support Noralf Trønnes
2016-04-08 17:05 ` [RFC v2 8/8] drm/tinydrm: Add support for several Adafruit TFT displays Noralf Trønnes
2016-04-13 11:11 ` [RFC v2 0/8] drm: Add support for tiny LCD displays Daniel Vetter

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.