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
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)
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
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
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.
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
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.
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.
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
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);
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
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 */
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
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
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
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 */
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(®->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(®->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(®->lock); + ret = lcdreg_write_buf32(reg, txbuf[0], txbuf + 1, ret - 1); + mutex_unlock(®->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, + ®->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(®->lock); + ret = lcdreg_read(reg, reg->debugfs_read_reg, &tr); + mutex_unlock(®->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 */
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 */
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 */
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 */
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
dri-devel@lists.freedesktop.org