From e0ad7c0f37478853f12a723f46f87cd672ca9d67 Mon Sep 17 00:00:00 2001 From: Boris BREZILLON Date: Thu, 5 Oct 2017 13:29:17 +0200 Subject: [PATCH 01/32] drm/gem-cma-helper: Change the level of the allocation failure message drm_gem_cma_create() prints an error message when dma_alloc_wc() fails to allocate the amount of memory we requested. This can lead to annoying error messages when CMA is only one possible source of memory for the BO allocation. Turn this error message into a debug one. Signed-off-by: Boris Brezillon Reviewed-by: Daniel Vetter Reviewed-by: Eric Engestrom Reviewed-by: Eric Anholt Link: https://patchwork.freedesktop.org/patch/msgid/20171005112917.15949-1-boris.brezillon@free-electrons.com --- drivers/gpu/drm/drm_gem_cma_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gem_cma_helper.c b/drivers/gpu/drm/drm_gem_cma_helper.c index 373e33f22be4..020e7668dfab 100644 --- a/drivers/gpu/drm/drm_gem_cma_helper.c +++ b/drivers/gpu/drm/drm_gem_cma_helper.c @@ -112,7 +112,7 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm, cma_obj->vaddr = dma_alloc_wc(drm->dev, size, &cma_obj->paddr, GFP_KERNEL | __GFP_NOWARN); if (!cma_obj->vaddr) { - dev_err(drm->dev, "failed to allocate buffer with size %zu\n", + dev_dbg(drm->dev, "failed to allocate buffer with size %zu\n", size); ret = -ENOMEM; goto error; From 4c3cf375bc4042eb8682cc6f4314efe66990421a Mon Sep 17 00:00:00 2001 From: Haneen Mohammed Date: Wed, 20 Sep 2017 12:54:48 -0600 Subject: [PATCH 02/32] drm/armada: Replace drm_gem_object_reference/unreference() with _get/put() This patch replace instances of drm_gem_object_reference/unreference with *_get/put() suffixes, because get/put is shorter and consistent with the kernel use of *_get/put() suffixes. This was done with the following Coccinelle script: @r1@ expression e; @@ ( -drm_gem_object_reference(e); +drm_gem_object_get(e); | -drm_gem_object_unreference(e); +drm_gem_object_put(e); | -drm_gem_object_unreference_unlocked(e); +drm_gem_object_put_unlocked(e); ) Signed-off-by: Haneen Mohammed [resolved small conflict with removed armada_gem_dumb_map_offset] Signed-off-by: Sean Paul Link: https://patchwork.freedesktop.org/patch/msgid/a59ef1ed109ade897bcffcb01b33214262db8942.1505932812.git.hamohammed.sa@gmail.com --- drivers/gpu/drm/armada/armada_crtc.c | 8 ++++---- drivers/gpu/drm/armada/armada_fb.c | 8 ++++---- drivers/gpu/drm/armada/armada_fbdev.c | 6 +++--- drivers/gpu/drm/armada/armada_gem.c | 12 ++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 2a4d163ac76f..381dfa104cbb 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -947,13 +947,13 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc, /* Must be a kernel-mapped object */ if (!obj->addr) { - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); return -EINVAL; } if (obj->obj.size < w * h * 4) { DRM_ERROR("buffer is too small\n"); - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); return -ENOMEM; } } @@ -961,7 +961,7 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc, if (dcrtc->cursor_obj) { dcrtc->cursor_obj->update = NULL; dcrtc->cursor_obj->update_data = NULL; - drm_gem_object_unreference_unlocked(&dcrtc->cursor_obj->obj); + drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj); } dcrtc->cursor_obj = obj; dcrtc->cursor_w = w; @@ -997,7 +997,7 @@ static void armada_drm_crtc_destroy(struct drm_crtc *crtc) struct armada_private *priv = crtc->dev->dev_private; if (dcrtc->cursor_obj) - drm_gem_object_unreference_unlocked(&dcrtc->cursor_obj->obj); + drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj); priv->dcrtc[dcrtc->num] = NULL; drm_crtc_cleanup(&dcrtc->crtc); diff --git a/drivers/gpu/drm/armada/armada_fb.c b/drivers/gpu/drm/armada/armada_fb.c index b9e1637cc4cf..a38d5a0892a9 100644 --- a/drivers/gpu/drm/armada/armada_fb.c +++ b/drivers/gpu/drm/armada/armada_fb.c @@ -17,7 +17,7 @@ static void armada_fb_destroy(struct drm_framebuffer *fb) struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb); drm_framebuffer_cleanup(&dfb->fb); - drm_gem_object_unreference_unlocked(&dfb->obj->obj); + drm_gem_object_put_unlocked(&dfb->obj->obj); kfree(dfb); } @@ -94,7 +94,7 @@ struct armada_framebuffer *armada_framebuffer_create(struct drm_device *dev, * the above call, but the caller will drop their reference * to it. Hence we need to take our own reference. */ - drm_gem_object_reference(&obj->obj); + drm_gem_object_get(&obj->obj); return dfb; } @@ -143,12 +143,12 @@ static struct drm_framebuffer *armada_fb_create(struct drm_device *dev, goto err; } - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); return &dfb->fb; err_unref: - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); err: DRM_ERROR("failed to initialize framebuffer: %d\n", ret); return ERR_PTR(ret); diff --git a/drivers/gpu/drm/armada/armada_fbdev.c b/drivers/gpu/drm/armada/armada_fbdev.c index 10e3fd87a83b..a2ce83f84800 100644 --- a/drivers/gpu/drm/armada/armada_fbdev.c +++ b/drivers/gpu/drm/armada/armada_fbdev.c @@ -51,13 +51,13 @@ static int armada_fb_create(struct drm_fb_helper *fbh, ret = armada_gem_linear_back(dev, obj); if (ret) { - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); return ret; } ptr = armada_gem_map_object(dev, obj); if (!ptr) { - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); return -ENOMEM; } @@ -67,7 +67,7 @@ static int armada_fb_create(struct drm_fb_helper *fbh, * A reference is now held by the framebuffer object if * successful, otherwise this drops the ref for the error path. */ - drm_gem_object_unreference_unlocked(&obj->obj); + drm_gem_object_put_unlocked(&obj->obj); if (IS_ERR(dfb)) return PTR_ERR(dfb); diff --git a/drivers/gpu/drm/armada/armada_gem.c b/drivers/gpu/drm/armada/armada_gem.c index 7837e6adb16f..a97f509743a5 100644 --- a/drivers/gpu/drm/armada/armada_gem.c +++ b/drivers/gpu/drm/armada/armada_gem.c @@ -265,7 +265,7 @@ int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev, /* drop reference from allocate - handle holds it now */ DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle); err: - drm_gem_object_unreference_unlocked(&dobj->obj); + drm_gem_object_put_unlocked(&dobj->obj); return ret; } @@ -297,7 +297,7 @@ int armada_gem_create_ioctl(struct drm_device *dev, void *data, /* drop reference from allocate - handle holds it now */ DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle); err: - drm_gem_object_unreference_unlocked(&dobj->obj); + drm_gem_object_put_unlocked(&dobj->obj); return ret; } @@ -314,13 +314,13 @@ int armada_gem_mmap_ioctl(struct drm_device *dev, void *data, return -ENOENT; if (!dobj->obj.filp) { - drm_gem_object_unreference_unlocked(&dobj->obj); + drm_gem_object_put_unlocked(&dobj->obj); return -EINVAL; } addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE, MAP_SHARED, args->offset); - drm_gem_object_unreference_unlocked(&dobj->obj); + drm_gem_object_put_unlocked(&dobj->obj); if (IS_ERR_VALUE(addr)) return addr; @@ -375,7 +375,7 @@ int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data, } unref: - drm_gem_object_unreference_unlocked(&dobj->obj); + drm_gem_object_put_unlocked(&dobj->obj); return ret; } @@ -524,7 +524,7 @@ armada_gem_prime_import(struct drm_device *dev, struct dma_buf *buf) * Importing our own dmabuf(s) increases the * refcount on the gem object itself. */ - drm_gem_object_reference(obj); + drm_gem_object_get(obj); return obj; } } From a52ff2a509a77aacdcc464f26181e77d49880da9 Mon Sep 17 00:00:00 2001 From: Haneen Mohammed Date: Wed, 20 Sep 2017 12:57:16 -0600 Subject: [PATCH 03/32] drm/armada: Replace drm_framebuffer_reference/unreference() with _get/put() This patch replace instances of drm_framebuffer_reference/unreference with *_get/put() suffixes, because get/put is shorter and consistent with the kernel use of *_get/put suffixes. This was done with the following Coccinelle script: @r1@ expression e; @@ ( -drm_framebuffer_reference(e); +drm_framebuffer_get(e); | -drm_framebuffer_unreference(e); +drm_framebuffer_put(e); ) Signed-off-by: Haneen Mohammed Signed-off-by: Sean Paul Link: https://patchwork.freedesktop.org/patch/msgid/e1df1b3375faa819029559b11c32e10501c5c5d6.1505932812.git.hamohammed.sa@gmail.com --- drivers/gpu/drm/armada/armada_crtc.c | 14 +++++++------- drivers/gpu/drm/armada/armada_drv.c | 2 +- drivers/gpu/drm/armada/armada_overlay.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 381dfa104cbb..2e065facdce7 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -298,7 +298,7 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc, if (force) { /* Display is disabled, so just drop the old fb */ - drm_framebuffer_unreference(fb); + drm_framebuffer_put(fb); return; } @@ -321,7 +321,7 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc, * the best. The worst that will happen is the buffer gets * reused before it has finished being displayed. */ - drm_framebuffer_unreference(fb); + drm_framebuffer_put(fb); } static void armada_drm_vblank_off(struct armada_crtc *dcrtc) @@ -577,7 +577,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc, unsigned i; bool interlaced; - drm_framebuffer_reference(crtc->primary->fb); + drm_framebuffer_get(crtc->primary->fb); interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE); @@ -718,7 +718,7 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, MAX_SCHEDULE_TIMEOUT); /* Take a reference to the new fb as we're using it */ - drm_framebuffer_reference(crtc->primary->fb); + drm_framebuffer_get(crtc->primary->fb); /* Update the base in the CRTC */ armada_drm_crtc_update_regs(dcrtc, regs); @@ -742,7 +742,7 @@ void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc, * primary plane. */ if (plane->fb) - drm_framebuffer_unreference(plane->fb); + drm_framebuffer_put(plane->fb); /* Power down the Y/U/V FIFOs */ sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66; @@ -1045,12 +1045,12 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc, * Ensure that we hold a reference on the new framebuffer. * This has to match the behaviour in mode_set. */ - drm_framebuffer_reference(fb); + drm_framebuffer_get(fb); ret = armada_drm_crtc_queue_frame_work(dcrtc, work); if (ret) { /* Undo our reference above */ - drm_framebuffer_unreference(fb); + drm_framebuffer_put(fb); kfree(work); return ret; } diff --git a/drivers/gpu/drm/armada/armada_drv.c b/drivers/gpu/drm/armada/armada_drv.c index 2d45103d06cb..e857b88a9799 100644 --- a/drivers/gpu/drm/armada/armada_drv.c +++ b/drivers/gpu/drm/armada/armada_drv.c @@ -25,7 +25,7 @@ static void armada_drm_unref_work(struct work_struct *work) struct drm_framebuffer *fb; while (kfifo_get(&priv->fb_unref, &fb)) - drm_framebuffer_unreference(fb); + drm_framebuffer_put(fb); } /* Must be called with dev->event_lock held */ diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index edc44910d79f..b411b608821a 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -177,7 +177,7 @@ armada_ovl_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, * Take a reference on the new framebuffer - we want to * hold on to it while the hardware is displaying it. */ - drm_framebuffer_reference(fb); + drm_framebuffer_get(fb); if (plane->fb) armada_ovl_retire_fb(dplane, plane->fb); @@ -278,7 +278,7 @@ static int armada_ovl_plane_disable(struct drm_plane *plane, fb = xchg(&dplane->old_fb, NULL); if (fb) - drm_framebuffer_unreference(fb); + drm_framebuffer_put(fb); return 0; } From afdfc4c6f55f953e073f0eef9e2afa3c8cf78d7c Mon Sep 17 00:00:00 2001 From: Robert Tarasov Date: Thu, 12 Oct 2017 17:13:49 -0700 Subject: [PATCH 04/32] drm/udl: Fixed problem with UDL adpater reconnection Fixed problem with DisplayLink and DisplayLink certified adapers in drm/udl driver when adapter doesn't want to work if it was initialized with disconnected DVI cable by enabling drm connectot polling and updating current connector's state. Signed-off-by: Robert Tarasov Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher Link: https://patchwork.freedesktop.org/patch/msgid/20171013001350.172155-1-tutankhamen@chromium.org --- drivers/gpu/drm/udl/udl_connector.c | 74 +++++++++++++++++++---------- drivers/gpu/drm/udl/udl_connector.h | 13 +++++ drivers/gpu/drm/udl/udl_drv.c | 4 ++ drivers/gpu/drm/udl/udl_main.c | 5 ++ 4 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 drivers/gpu/drm/udl/udl_connector.h diff --git a/drivers/gpu/drm/udl/udl_connector.c b/drivers/gpu/drm/udl/udl_connector.c index 9f9a49748d17..b4d1464d2d87 100644 --- a/drivers/gpu/drm/udl/udl_connector.c +++ b/drivers/gpu/drm/udl/udl_connector.c @@ -14,6 +14,7 @@ #include #include #include +#include "udl_connector.h" #include "udl_drv.h" /* dummy connector to just get EDID, @@ -56,28 +57,15 @@ error: static int udl_get_modes(struct drm_connector *connector) { - struct udl_device *udl = connector->dev->dev_private; - struct edid *edid; - int ret; + struct udl_drm_connector *udl_connector = + container_of(connector, + struct udl_drm_connector, + connector); - edid = (struct edid *)udl_get_edid(udl); - if (!edid) { - drm_mode_connector_update_edid_property(connector, NULL); - return 0; - } - - /* - * We only read the main block, but if the monitor reports extension - * blocks then the drm edid code expects them to be present, so patch - * the extension count to 0. - */ - edid->checksum += edid->extensions; - edid->extensions = 0; - - drm_mode_connector_update_edid_property(connector, edid); - ret = drm_add_edid_modes(connector, edid); - kfree(edid); - return ret; + drm_mode_connector_update_edid_property(connector, udl_connector->edid); + if (udl_connector->edid) + return drm_add_edid_modes(connector, udl_connector->edid); + return 0; } static int udl_mode_valid(struct drm_connector *connector, @@ -96,8 +84,33 @@ static int udl_mode_valid(struct drm_connector *connector, static enum drm_connector_status udl_detect(struct drm_connector *connector, bool force) { - if (drm_dev_is_unplugged(connector->dev)) + struct edid *edid; + struct udl_device *udl = connector->dev->dev_private; + struct udl_drm_connector *udl_connector = + container_of(connector, + struct udl_drm_connector, + connector); + + if (udl_connector->edid != NULL) { + kfree(udl_connector->edid); + udl_connector->edid = NULL; + } + + edid = (struct edid *)udl_get_edid(udl); + if (!edid || !memchr_inv(edid, 0, EDID_LENGTH)) return connector_status_disconnected; + + udl_connector->edid = edid; + + /* + * We only read the main block, but if the monitor reports extension + * blocks then the drm edid code expects them to be present, so patch + * the extension count to 0. + */ + udl_connector->edid->checksum += + udl_connector->edid->extensions; + udl_connector->edid->extensions = 0; + return connector_status_connected; } @@ -117,8 +130,14 @@ static int udl_connector_set_property(struct drm_connector *connector, static void udl_connector_destroy(struct drm_connector *connector) { + struct udl_drm_connector *udl_connector = + container_of(connector, + struct udl_drm_connector, + connector); + drm_connector_unregister(connector); drm_connector_cleanup(connector); + kfree(udl_connector->edid); kfree(connector); } @@ -138,17 +157,22 @@ static const struct drm_connector_funcs udl_connector_funcs = { int udl_connector_init(struct drm_device *dev, struct drm_encoder *encoder) { + struct udl_drm_connector *udl_connector; struct drm_connector *connector; - connector = kzalloc(sizeof(struct drm_connector), GFP_KERNEL); - if (!connector) + udl_connector = kzalloc(sizeof(struct udl_drm_connector), GFP_KERNEL); + if (!udl_connector) return -ENOMEM; - drm_connector_init(dev, connector, &udl_connector_funcs, DRM_MODE_CONNECTOR_DVII); + connector = &udl_connector->connector; + drm_connector_init(dev, connector, &udl_connector_funcs, + DRM_MODE_CONNECTOR_DVII); drm_connector_helper_add(connector, &udl_connector_helper_funcs); drm_connector_register(connector); drm_mode_connector_attach_encoder(connector, encoder); + connector->polled = DRM_CONNECTOR_POLL_HPD | + DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; return 0; } diff --git a/drivers/gpu/drm/udl/udl_connector.h b/drivers/gpu/drm/udl/udl_connector.h new file mode 100644 index 000000000000..0fb0db5c4612 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_connector.h @@ -0,0 +1,13 @@ +#ifndef __UDL_CONNECTOR_H__ +#define __UDL_CONNECTOR_H__ + +#include + +struct udl_drm_connector { + struct drm_connector connector; + /* last udl_detect edid */ + struct edid *edid; +}; + + +#endif //__UDL_CONNECTOR_H__ diff --git a/drivers/gpu/drm/udl/udl_drv.c b/drivers/gpu/drm/udl/udl_drv.c index 31421b6b586e..3c45a3064726 100644 --- a/drivers/gpu/drm/udl/udl_drv.c +++ b/drivers/gpu/drm/udl/udl_drv.c @@ -14,6 +14,9 @@ static int udl_usb_suspend(struct usb_interface *interface, pm_message_t message) { + struct drm_device *dev = usb_get_intfdata(interface); + + drm_kms_helper_poll_disable(dev); return 0; } @@ -21,6 +24,7 @@ static int udl_usb_resume(struct usb_interface *interface) { struct drm_device *dev = usb_get_intfdata(interface); + drm_kms_helper_poll_enable(dev); udl_modeset_restore(dev); return 0; } diff --git a/drivers/gpu/drm/udl/udl_main.c b/drivers/gpu/drm/udl/udl_main.c index 0328b2c7b210..f1ec4528a73e 100644 --- a/drivers/gpu/drm/udl/udl_main.c +++ b/drivers/gpu/drm/udl/udl_main.c @@ -11,6 +11,7 @@ * more details. */ #include +#include #include "udl_drv.h" /* -BULK_SIZE as per usb-skeleton. Can we get full page and avoid overhead? */ @@ -350,6 +351,8 @@ int udl_driver_load(struct drm_device *dev, unsigned long flags) if (ret) goto err_fb; + drm_kms_helper_poll_init(dev); + return 0; err_fb: udl_fbdev_cleanup(dev); @@ -371,6 +374,8 @@ void udl_driver_unload(struct drm_device *dev) { struct udl_device *udl = dev->dev_private; + drm_kms_helper_poll_fini(dev); + if (udl->urbs.count) udl_free_urb_list(dev); From 75c65ee20adebca6ac29d6f9d301ced073dd8030 Mon Sep 17 00:00:00 2001 From: Robert Tarasov Date: Thu, 12 Oct 2017 17:13:50 -0700 Subject: [PATCH 05/32] drm/udl: Reading all edid blocks in DRM/UDL driver Now DRM/UDL driver retreives all edid data blocks instead of only base one. Previous approch could lead to improper initialization of video mode with certain monitors. Signed-off-by: Robert Tarasov Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher Link: https://patchwork.freedesktop.org/patch/msgid/20171013001350.172155-2-tutankhamen@chromium.org --- drivers/gpu/drm/udl/udl_connector.c | 106 ++++++++++++++++++---------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/udl/udl_connector.c b/drivers/gpu/drm/udl/udl_connector.c index b4d1464d2d87..c0bbd1ce15f0 100644 --- a/drivers/gpu/drm/udl/udl_connector.c +++ b/drivers/gpu/drm/udl/udl_connector.c @@ -17,42 +17,79 @@ #include "udl_connector.h" #include "udl_drv.h" -/* dummy connector to just get EDID, - all UDL appear to have a DVI-D */ - -static u8 *udl_get_edid(struct udl_device *udl) +static bool udl_get_edid_block(struct udl_device *udl, int block_idx, + u8 *buff) { - u8 *block; - char *rbuf; int ret, i; + u8 *read_buff; - block = kmalloc(EDID_LENGTH, GFP_KERNEL); - if (block == NULL) - return NULL; - - rbuf = kmalloc(2, GFP_KERNEL); - if (rbuf == NULL) - goto error; + read_buff = kmalloc(2, GFP_KERNEL); + if (!read_buff) + return false; for (i = 0; i < EDID_LENGTH; i++) { + int bval = (i + block_idx * EDID_LENGTH) << 8; ret = usb_control_msg(udl->udev, - usb_rcvctrlpipe(udl->udev, 0), (0x02), - (0x80 | (0x02 << 5)), i << 8, 0xA1, rbuf, 2, - HZ); + usb_rcvctrlpipe(udl->udev, 0), + (0x02), (0x80 | (0x02 << 5)), bval, + 0xA1, read_buff, 2, HZ); if (ret < 1) { DRM_ERROR("Read EDID byte %d failed err %x\n", i, ret); - goto error; + kfree(read_buff); + return false; } - block[i] = rbuf[1]; + buff[i] = read_buff[1]; } - kfree(rbuf); - return block; + kfree(read_buff); + return true; +} -error: - kfree(block); - kfree(rbuf); - return NULL; +static bool udl_get_edid(struct udl_device *udl, u8 **result_buff, + int *result_buff_size) +{ + int i, extensions; + u8 *block_buff = NULL, *buff_ptr; + + block_buff = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (block_buff == NULL) + return false; + + if (udl_get_edid_block(udl, 0, block_buff) && + memchr_inv(block_buff, 0, EDID_LENGTH)) { + extensions = ((struct edid *)block_buff)->extensions; + if (extensions > 0) { + /* we have to read all extensions one by one */ + *result_buff_size = EDID_LENGTH * (extensions + 1); + *result_buff = kmalloc(*result_buff_size, GFP_KERNEL); + buff_ptr = *result_buff; + if (buff_ptr == NULL) { + kfree(block_buff); + return false; + } + memcpy(buff_ptr, block_buff, EDID_LENGTH); + kfree(block_buff); + buff_ptr += EDID_LENGTH; + for (i = 1; i < extensions; ++i) { + if (udl_get_edid_block(udl, i, buff_ptr)) { + buff_ptr += EDID_LENGTH; + } else { + kfree(*result_buff); + *result_buff = NULL; + return false; + } + } + return true; + } + /* we have only base edid block */ + *result_buff = block_buff; + *result_buff_size = EDID_LENGTH; + return true; + } + + kfree(block_buff); + + return false; } static int udl_get_modes(struct drm_connector *connector) @@ -84,33 +121,26 @@ static int udl_mode_valid(struct drm_connector *connector, static enum drm_connector_status udl_detect(struct drm_connector *connector, bool force) { - struct edid *edid; + u8 *edid_buff = NULL; + int edid_buff_size = 0; struct udl_device *udl = connector->dev->dev_private; struct udl_drm_connector *udl_connector = container_of(connector, struct udl_drm_connector, connector); + /* cleanup previous edid */ if (udl_connector->edid != NULL) { kfree(udl_connector->edid); udl_connector->edid = NULL; } - edid = (struct edid *)udl_get_edid(udl); - if (!edid || !memchr_inv(edid, 0, EDID_LENGTH)) + + if (!udl_get_edid(udl, &edid_buff, &edid_buff_size)) return connector_status_disconnected; - udl_connector->edid = edid; - - /* - * We only read the main block, but if the monitor reports extension - * blocks then the drm edid code expects them to be present, so patch - * the extension count to 0. - */ - udl_connector->edid->checksum += - udl_connector->edid->extensions; - udl_connector->edid->extensions = 0; - + udl_connector->edid = (struct edid *)edid_buff; + return connector_status_connected; } From 4edd60847287c34222116af58f76be985ebddef7 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Tue, 17 Oct 2017 07:20:47 +0200 Subject: [PATCH 06/32] drm/atomic: Make atomic helper track newly assigned planes correctly, v2. Commit 669c9215afea ("drm/atomic: Make async plane update checks work as intended, v2.") forced planes to always be tracked, but forgot to explicitly get the crtc commit from the new crtc when available. This broke plane commit tracking, and caused kms_atomic_transitions to randomly fail with -EBUSY. Changes since v1: - Prefer new_crtc_state->crtc above old_crtc_state->crtc. Signed-off-by: Maarten Lankhorst Fixes: 669c9215afea ("drm/atomic: Make async plane update checks work as intended, v2.") Cc: Gustavo Padovan Cc: Daniel Vetter Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=102671 Testcase: kms_atomic_transitions Link: https://patchwork.freedesktop.org/patch/msgid/20171017052047.8983-1-maarten.lankhorst@linux.intel.com Reviewed-by: Daniel Vetter --- drivers/gpu/drm/drm_atomic_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index b83bd462868c..aec73f4c70c6 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1801,7 +1801,7 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, !try_wait_for_completion(&old_plane_state->commit->flip_done)) return -EBUSY; - commit = crtc_or_fake_commit(state, old_plane_state->crtc); + commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc); if (!commit) return -ENOMEM; From 080de2e5be2d4b396b6792cde4cb2048fcdb4ce9 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Mon, 16 Oct 2017 15:29:28 +0200 Subject: [PATCH 07/32] drm/atomic: Check for busy planes/connectors before setting the commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We still want to fail with -EBUSY if a plane or connector is part of a commit, even if it will be assigned to a new commit. This closes a small hole left open where we should return -EBUSY. It's not severe, because wait_for_dependencies and swap_state helpers still block. But it should return -EBUSY and not stall. Signed-off-by: Maarten Lankhorst Fixes: 21a01abbe32a ("drm/atomic: Fix freeing connector/plane state too early by tracking commits, v3.") Link: https://patchwork.freedesktop.org/patch/msgid/20171016132928.6498-2-maarten.lankhorst@linux.intel.com Reviewed-by: Ville Syrjälä --- drivers/gpu/drm/drm_atomic_helper.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index aec73f4c70c6..3472d9ae8b95 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1772,16 +1772,16 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, } for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) { - /* commit tracked through new_crtc_state->commit, no need to do it explicitly */ - if (new_conn_state->crtc) - continue; - /* Userspace is not allowed to get ahead of the previous * commit with nonblocking ones. */ if (nonblock && old_conn_state->commit && !try_wait_for_completion(&old_conn_state->commit->flip_done)) return -EBUSY; + /* commit tracked through new_crtc_state->commit, no need to do it explicitly */ + if (new_conn_state->crtc) + continue; + commit = crtc_or_fake_commit(state, old_conn_state->crtc); if (!commit) return -ENOMEM; @@ -1790,17 +1790,16 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, } for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { - /* - * Unlike connectors, always track planes explicitly for - * async pageflip support. - */ - /* Userspace is not allowed to get ahead of the previous * commit with nonblocking ones. */ if (nonblock && old_plane_state->commit && !try_wait_for_completion(&old_plane_state->commit->flip_done)) return -EBUSY; + /* + * Unlike connectors, always track planes explicitly for + * async pageflip support. + */ commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc); if (!commit) return -ENOMEM; From 4690803b09c6f1da33058336844091f8b7f3118f Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 17 Oct 2017 12:23:47 +0800 Subject: [PATCH 08/32] drm/sun4i: backend: Offset layer buffer address by DRAM starting address The display backend, as well as other peripherals that have a DRAM clock gate and access DRAM directly, bypassing the system bus, address the DRAM starting from 0x0, while physical addresses the system uses starts from 0x40000000 (or 0x20000000 in A80's case). This issue was witnessed on the Cubietruck, which has 2GB of RAM. Devices with less RAM function normally due to the DRAM address wrapping around. CMA seems to always allocate its buffer at a very high address, close to the end of DRAM. On a 1GB RAM device, the physical address would be something like 0x78000000. The DRAM address 0x78000000 would access the same DRAM region as 0x38000000 on a system, as the DRAM address would only span 0x0 ~ 0x3fffffff. The bit 0x40000000 is non-functional in this case. However on the Cubietruck, the DRAM is 2GB. The physical address is 0x40000000 ~ 0xbfffffff. The buffer would be something like 0xb8000000. But the DRAM address span 0x0 ~ 0x7fffffff, meaning the buffer address wraps around to 0x38000000, which is wrong. The correct DRAM address for it should be 0x78000000. Correct the address configured into the backend layer registers by PHYS_OFFSET to account for this. Fixes: 9026e0d122ac ("drm: Add Allwinner A10 Display Engine support") Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20171017042349.31743-6-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_backend.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index 4fefd8add714..d18d7e88d511 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -216,6 +216,13 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, paddr = drm_fb_cma_get_gem_addr(fb, state, 0); DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); + /* + * backend DMA accesses DRAM directly, bypassing the system + * bus. As such, the address range is different and the buffer + * address needs to be corrected. + */ + paddr -= PHYS_OFFSET; + /* Write the 32 lower bits of the address (in bits) */ lo_paddr = paddr << 3; DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); From 8b11aaface2b182ee2b36509843d746c453f8750 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:08 +0200 Subject: [PATCH 09/32] drm/sun4i: Implement endpoint parsing using kfifo The commit da82b8785eeb ("drm/sun4i: add components in breadth first traversal order") implemented a breadth first traversal of our device tree nodes graph. However, it was relying on the kernel linked lists, and those are not really safe for addition. Indeed, in a single pipeline stage, your first stage (ie, the mixer or fronted) will be queued, and it will be the final iteration of that list as far as list_for_each_entry_safe is concerned. Then, during that final iteration, we'll queue another element (the TCON or the backend) that list_for_each_entry_safe will not account for, and we will leave the loop without having iterated over all the elements. And since we won't have built our components list properly, the DRM driver will be left non-functional. We can instead use a kfifo to queue and enqueue components in-order, as was the original intention. This also has the benefit of removing any dynamic allocation, making the error handling path simpler too. The only thing we're losing is the ability to tell whether an element has already been queued, but that was only needed to remove spurious logs, and therefore purely cosmetic. This means that this commit effectively reverses e8afb7b67fba ("drm/sun4i: don't add components that are already in the queue"). Fixes: da82b8785eeb ("drm/sun4i: add components in breadth first traversal order") Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/4ecb323e787918208f6a5d9f0ebba12c62583c98.1508231063.git-series.maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_drv.c | 71 ++++++------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index b5879d4620d8..a27efad9bc76 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -222,29 +223,15 @@ static int compare_of(struct device *dev, void *data) * matching system handles this for us. */ struct endpoint_list { - struct device_node *node; - struct list_head list; + DECLARE_KFIFO(fifo, struct device_node *, 16); }; -static bool node_is_in_list(struct list_head *endpoints, - struct device_node *node) -{ - struct endpoint_list *endpoint; - - list_for_each_entry(endpoint, endpoints, list) - if (endpoint->node == node) - return true; - - return false; -} - static int sun4i_drv_add_endpoints(struct device *dev, - struct list_head *endpoints, + struct endpoint_list *list, struct component_match **match, struct device_node *node) { struct device_node *port, *ep, *remote; - struct endpoint_list *endpoint; int count = 0; /* @@ -304,19 +291,7 @@ static int sun4i_drv_add_endpoints(struct device *dev, } } - /* skip downstream node if it is already in the queue */ - if (node_is_in_list(endpoints, remote)) - continue; - - /* Add downstream nodes to the queue */ - endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); - if (!endpoint) { - of_node_put(remote); - return -ENOMEM; - } - - endpoint->node = remote; - list_add_tail(&endpoint->list, endpoints); + kfifo_put(&list->fifo, remote); } return count; @@ -325,10 +300,11 @@ static int sun4i_drv_add_endpoints(struct device *dev, static int sun4i_drv_probe(struct platform_device *pdev) { struct component_match *match = NULL; - struct device_node *np = pdev->dev.of_node; - struct endpoint_list *endpoint, *endpoint_temp; + struct device_node *np = pdev->dev.of_node, *endpoint; + struct endpoint_list list; int i, ret, count = 0; - LIST_HEAD(endpoints); + + INIT_KFIFO(list.fifo); for (i = 0;; i++) { struct device_node *pipeline = of_parse_phandle(np, @@ -337,31 +313,19 @@ static int sun4i_drv_probe(struct platform_device *pdev) if (!pipeline) break; - endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); - if (!endpoint) { - ret = -ENOMEM; - goto err_free_endpoints; - } - - endpoint->node = pipeline; - list_add_tail(&endpoint->list, &endpoints); + kfifo_put(&list.fifo, pipeline); } - list_for_each_entry_safe(endpoint, endpoint_temp, &endpoints, list) { + while (kfifo_get(&list.fifo, &endpoint)) { /* process this endpoint */ - ret = sun4i_drv_add_endpoints(&pdev->dev, &endpoints, &match, - endpoint->node); + ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match, + endpoint); /* sun4i_drv_add_endpoints can fail to allocate memory */ if (ret < 0) - goto err_free_endpoints; + return ret; count += ret; - - /* delete and cleanup the current entry */ - list_del(&endpoint->list); - of_node_put(endpoint->node); - kfree(endpoint); } if (count) @@ -370,15 +334,6 @@ static int sun4i_drv_probe(struct platform_device *pdev) match); else return 0; - -err_free_endpoints: - list_for_each_entry_safe(endpoint, endpoint_temp, &endpoints, list) { - list_del(&endpoint->list); - of_node_put(endpoint->node); - kfree(endpoint); - } - - return ret; } static int sun4i_drv_remove(struct platform_device *pdev) From 79c6109243fdfe4377c7e5518c0bae7269cfc716 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:09 +0200 Subject: [PATCH 10/32] drm/sun4i: Realign Makefile padding and reorder it Some options were not padded as they should, and the order in the Makefile was chaotic. Fix that. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/9410b284ec97453fa692537dffaaa4fb4833347c.1508231063.git-series.maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/Makefile | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 43c753cafc88..301b5b1452db 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -1,24 +1,25 @@ -sun4i-drm-y += sun4i_drv.o -sun4i-drm-y += sun4i_framebuffer.o +sun4i-backend-y += sun4i_backend.o sun4i_layer.o -sun4i-drm-hdmi-y += sun4i_hdmi_enc.o -sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o -sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o -sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o +sun4i-drm-y += sun4i_drv.o +sun4i-drm-y += sun4i_framebuffer.o -sun4i-tcon-y += sun4i_tcon.o -sun4i-tcon-y += sun4i_rgb.o -sun4i-tcon-y += sun4i_dotclock.o -sun4i-tcon-y += sun4i_crtc.o +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o +sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o -sun4i-backend-y += sun4i_backend.o sun4i_layer.o +sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o -sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o +sun4i-tcon-y += sun4i_crtc.o +sun4i-tcon-y += sun4i_dotclock.o +sun4i-tcon-y += sun4i_tcon.o +sun4i-tcon-y += sun4i_rgb.o -obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o -obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o +obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o +obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o +obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o -obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o +obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o -obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o +obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o From abcb8766b32d277859163e4da585f76f10d998f1 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:10 +0200 Subject: [PATCH 11/32] drm/sun4i: tcon: Make tcon_set_mux mode argument const The drm_display_mode pointer can be mark const, so let's do it. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/b0cce5a43fc3b56953d21a54fc3c14672f755f42.1508231063.git-series.maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 6 +++--- drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 68751b999877..54e1796d2953 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -134,7 +134,7 @@ static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm) } void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, - struct drm_encoder *encoder) + const struct drm_encoder *encoder) { int ret = -ENOTSUPP; @@ -783,7 +783,7 @@ static int sun4i_tcon_remove(struct platform_device *pdev) /* platform specific TCON muxing callbacks */ static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, - struct drm_encoder *encoder) + const struct drm_encoder *encoder) { u32 val; @@ -799,7 +799,7 @@ static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, } static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, - struct drm_encoder *encoder) + const struct drm_encoder *encoder) { struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); u32 shift; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index d9e1357cc8ae..d81c6e20efe6 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -152,7 +152,7 @@ struct sun4i_tcon_quirks { bool needs_de_be_mux; /* sun6i needs mux to select backend */ /* callback to handle tcon muxing options */ - int (*set_mux)(struct sun4i_tcon *, struct drm_encoder *); + int (*set_mux)(struct sun4i_tcon *, const struct drm_encoder *); }; struct sun4i_tcon { From 961c645cf00c8be82b05ad10068179a86fdbc181 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:11 +0200 Subject: [PATCH 12/32] drm/sun4i: tcon: Make tcon_get_clk_delay mode argument const The drm_display_mode pointer can be mark const, so let's do it. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/e6f92f126640aa6de639386f9b4677db3d8bb37b.1508231063.git-series.maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 54e1796d2953..9b5b21ad8378 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -146,7 +146,7 @@ void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, } EXPORT_SYMBOL(sun4i_tcon_set_mux); -static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, +static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, int channel) { int delay = mode->vtotal - mode->vdisplay; From 45e88f994add84d8cd3864d36064ea1ed1b08cdf Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:12 +0200 Subject: [PATCH 13/32] drm/sun4i: tcon: Don't rely on encoders to enable the TCON So far, we've required all the TCON-connected encoders to call the TCON enable and disable functions. This was made this way because in the RGB/LVDS case, the TCON is the CRTC and the encoder. However, in all the other cases (HDMI, TV, DSI, etc.), we have another encoder down the road that needs to be programmed. We also needed to know which channel the encoder is connected to, which is encoder-specific. The CRTC's enable and disable callbacks can work just fine for our use case, and we can get the channel to use just by looking at the type of encoder, since that is fixed. Implement those callbacks, which will remove some of the encoder boilerplate. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/90b4396e19b3eca61b2ebfdae0672074b88ad74d.1508231063.git-series.maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_crtc.c | 22 +++++- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 8 --- drivers/gpu/drm/sun4i/sun4i_rgb.c | 14 ++-- drivers/gpu/drm/sun4i/sun4i_tcon.c | 99 ++++++++++++-------------- drivers/gpu/drm/sun4i/sun4i_tcon.h | 10 +-- drivers/gpu/drm/sun4i/sun4i_tv.c | 6 -- 6 files changed, 73 insertions(+), 86 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index d097c6f93ad0..e86baa3746af 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -30,6 +30,22 @@ #include "sunxi_engine.h" #include "sun4i_tcon.h" +/* + * While this isn't really working in the DRM theory, in practice we + * can only ever have one encoder per TCON since we have a mux in our + * TCON. + */ +static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + + drm_for_each_encoder(encoder, crtc->dev) + if (encoder->crtc == crtc) + return encoder; + + return NULL; +} + static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { @@ -72,11 +88,12 @@ static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); DRM_DEBUG_DRIVER("Disabling the CRTC\n"); - sun4i_tcon_disable(scrtc->tcon); + sun4i_tcon_set_status(scrtc->tcon, encoder, false); if (crtc->state->event && !crtc->state->active) { spin_lock_irq(&crtc->dev->event_lock); @@ -90,11 +107,12 @@ static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc, static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); DRM_DEBUG_DRIVER("Enabling the CRTC\n"); - sun4i_tcon_enable(scrtc->tcon); + sun4i_tcon_set_status(scrtc->tcon, encoder, true); } static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 6ca6e6a74c4a..482bf03d55c1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -86,8 +86,6 @@ static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder, static void sun4i_hdmi_disable(struct drm_encoder *encoder) { struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; u32 val; DRM_DEBUG_DRIVER("Disabling the HDMI Output\n"); @@ -95,22 +93,16 @@ static void sun4i_hdmi_disable(struct drm_encoder *encoder) val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG); val &= ~SUN4I_HDMI_VID_CTRL_ENABLE; writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG); - - sun4i_tcon_channel_disable(tcon, 1); } static void sun4i_hdmi_enable(struct drm_encoder *encoder) { struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; u32 val = 0; DRM_DEBUG_DRIVER("Enabling the HDMI Output\n"); - sun4i_tcon_channel_enable(tcon, 1); - sun4i_hdmi_setup_avi_infoframes(hdmi, mode); val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 7cd7090ad63a..a7f297ed40c1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -134,13 +134,10 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Enabling RGB output\n"); - if (!IS_ERR(tcon->panel)) + if (!IS_ERR(tcon->panel)) { drm_panel_prepare(tcon->panel); - - sun4i_tcon_channel_enable(tcon, 0); - - if (!IS_ERR(tcon->panel)) drm_panel_enable(tcon->panel); + } } static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) @@ -150,13 +147,10 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("Disabling RGB output\n"); - if (!IS_ERR(tcon->panel)) + if (!IS_ERR(tcon->panel)) { drm_panel_disable(tcon->panel); - - sun4i_tcon_channel_disable(tcon, 0); - - if (!IS_ERR(tcon->panel)) drm_panel_unprepare(tcon->panel); + } } static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 9b5b21ad8378..964cf22a1ced 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -35,66 +35,61 @@ #include "sun4i_tcon.h" #include "sunxi_engine.h" -void sun4i_tcon_disable(struct sun4i_tcon *tcon) +static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel, + bool enabled) { - DRM_DEBUG_DRIVER("Disabling TCON\n"); + struct clk *clk; - /* Disable the TCON */ - regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, - SUN4I_TCON_GCTL_TCON_ENABLE, 0); -} -EXPORT_SYMBOL(sun4i_tcon_disable); - -void sun4i_tcon_enable(struct sun4i_tcon *tcon) -{ - DRM_DEBUG_DRIVER("Enabling TCON\n"); - - /* Enable the TCON */ - regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, - SUN4I_TCON_GCTL_TCON_ENABLE, - SUN4I_TCON_GCTL_TCON_ENABLE); -} -EXPORT_SYMBOL(sun4i_tcon_enable); - -void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) -{ - DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel); - - /* Disable the TCON's channel */ - if (channel == 0) { - regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, - SUN4I_TCON0_CTL_TCON_ENABLE, 0); - clk_disable_unprepare(tcon->dclk); - return; - } - - WARN_ON(!tcon->quirks->has_channel_1); - regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, - SUN4I_TCON1_CTL_TCON_ENABLE, 0); - clk_disable_unprepare(tcon->sclk1); -} -EXPORT_SYMBOL(sun4i_tcon_channel_disable); - -void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) -{ - DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel); - - /* Enable the TCON's channel */ - if (channel == 0) { + switch (channel) { + case 0: regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, - SUN4I_TCON0_CTL_TCON_ENABLE); - clk_prepare_enable(tcon->dclk); + enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0); + clk = tcon->dclk; + break; + case 1: + WARN_ON(!tcon->quirks->has_channel_1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, + enabled ? SUN4I_TCON1_CTL_TCON_ENABLE : 0); + clk = tcon->sclk1; + break; + default: + DRM_WARN("Unknown channel... doing nothing\n"); return; } - WARN_ON(!tcon->quirks->has_channel_1); - regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, - SUN4I_TCON1_CTL_TCON_ENABLE, - SUN4I_TCON1_CTL_TCON_ENABLE); - clk_prepare_enable(tcon->sclk1); + if (enabled) + clk_prepare_enable(clk); + else + clk_disable_unprepare(clk); +} + +void sun4i_tcon_set_status(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + bool enabled) +{ + int channel; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_NONE: + channel = 0; + break; + case DRM_MODE_ENCODER_TMDS: + case DRM_MODE_ENCODER_TVDAC: + channel = 1; + break; + default: + DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n"); + return; + } + + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, + enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0); + + sun4i_tcon_channel_set_status(tcon, channel, enabled); } -EXPORT_SYMBOL(sun4i_tcon_channel_enable); void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) { diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index d81c6e20efe6..03f983927baa 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -190,15 +190,9 @@ struct sun4i_tcon { struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); struct drm_panel *sun4i_tcon_find_panel(struct device_node *node); -/* Global Control */ -void sun4i_tcon_disable(struct sun4i_tcon *tcon); -void sun4i_tcon_enable(struct sun4i_tcon *tcon); - -/* Channel Control */ -void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel); -void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel); - void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); +void sun4i_tcon_set_status(struct sun4i_tcon *crtc, + const struct drm_encoder *encoder, bool enable); /* Mode Related Controls */ void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 050cfd43c7a0..2e27ff9fc58f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -345,12 +345,9 @@ static void sun4i_tv_disable(struct drm_encoder *encoder) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; DRM_DEBUG_DRIVER("Disabling the TV Output\n"); - sun4i_tcon_channel_disable(tcon, 1); - regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_ENABLE, 0); @@ -362,7 +359,6 @@ static void sun4i_tv_enable(struct drm_encoder *encoder) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; DRM_DEBUG_DRIVER("Enabling the TV Output\n"); @@ -371,8 +367,6 @@ static void sun4i_tv_enable(struct drm_encoder *encoder) regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_ENABLE, SUN4I_TVE_EN_ENABLE); - - sun4i_tcon_channel_enable(tcon, 1); } static void sun4i_tv_mode_set(struct drm_encoder *encoder, From 5b8f09100d6c85c4a8049a830b57f199b5b08278 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:13 +0200 Subject: [PATCH 14/32] drm/sun4i: tcon: Don't rely on encoders to set the TCON mode Just like we did for the TCON enable and disable, for historical reasons we used to rely on the encoders calling the TCON mode_set function, while the CRTC has a callback for that. Let's implement it in order to reduce the boilerplate code. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/faa3a4d511039af1d116270dfef3a8b60ca3591e.1508231063.git-series.maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_crtc.c | 10 +++++++ drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 1 - drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 7 ----- drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 1 - drivers/gpu/drm/sun4i/sun4i_rgb.c | 15 ---------- drivers/gpu/drm/sun4i/sun4i_tcon.c | 31 ++++++++++++++++----- drivers/gpu/drm/sun4i/sun4i_tcon.h | 11 ++------ drivers/gpu/drm/sun4i/sun4i_tv.c | 6 ---- 8 files changed, 37 insertions(+), 45 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index e86baa3746af..5decae0069d0 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -115,11 +115,21 @@ static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc, sun4i_tcon_set_status(scrtc->tcon, encoder, true); } +static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc); + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + + sun4i_tcon_mode_set(scrtc->tcon, encoder, mode); +} + static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { .atomic_begin = sun4i_crtc_atomic_begin, .atomic_flush = sun4i_crtc_atomic_flush, .atomic_enable = sun4i_crtc_atomic_enable, .atomic_disable = sun4i_crtc_atomic_disable, + .mode_set_nofb = sun4i_crtc_mode_set_nofb, }; static int sun4i_crtc_enable_vblank(struct drm_crtc *crtc) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c index 04f85b1cf922..e826da34e919 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c @@ -13,7 +13,6 @@ #include #include -#include "sun4i_tcon.h" #include "sun4i_hdmi.h" struct sun4i_ddc { diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 482bf03d55c1..d2eb0a60e568 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -30,7 +30,6 @@ #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sun4i_hdmi.h" -#include "sun4i_tcon.h" static inline struct sun4i_hdmi * drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder) @@ -120,15 +119,9 @@ static void sun4i_hdmi_mode_set(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; unsigned int x, y; u32 val; - sun4i_tcon1_mode_set(tcon, mode); - sun4i_tcon_set_mux(tcon, 1, encoder); - - clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000); clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000); diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c index 1b6b37aefc38..dc332ea56f6c 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c @@ -12,7 +12,6 @@ #include -#include "sun4i_tcon.h" #include "sun4i_hdmi.h" struct sun4i_tmds { diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index a7f297ed40c1..832f8f9bc47f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -153,22 +153,7 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) } } -static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); - struct sun4i_tcon *tcon = rgb->tcon; - - sun4i_tcon0_mode_set(tcon, mode); - sun4i_tcon_set_mux(tcon, 0, encoder); - - /* FIXME: This seems to be board specific */ - clk_set_phase(tcon->dclk, 120); -} - static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { - .mode_set = sun4i_rgb_encoder_mode_set, .disable = sun4i_rgb_encoder_disable, .enable = sun4i_rgb_encoder_enable, }; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 964cf22a1ced..7afd3b9de7e3 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -139,7 +139,6 @@ void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n", encoder->name, encoder->crtc->name, ret); } -EXPORT_SYMBOL(sun4i_tcon_set_mux); static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, int channel) @@ -159,8 +158,8 @@ static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, return delay; } -void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode) +static void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) { unsigned int bp, hsync, vsync; u8 clk_delay; @@ -233,10 +232,9 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, /* Enable the output on the pins */ regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); } -EXPORT_SYMBOL(sun4i_tcon0_mode_set); -void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode) +static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) { unsigned int bp, hsync, vsync, vtotal; u8 clk_delay; @@ -324,7 +322,26 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, SUN4I_TCON_GCTL_IOMAP_MASK, SUN4I_TCON_GCTL_IOMAP_TCON1); } -EXPORT_SYMBOL(sun4i_tcon1_mode_set); + +void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_NONE: + sun4i_tcon0_mode_set(tcon, mode); + sun4i_tcon_set_mux(tcon, 0, encoder); + break; + case DRM_MODE_ENCODER_TVDAC: + case DRM_MODE_ENCODER_TMDS: + sun4i_tcon1_mode_set(tcon, mode); + sun4i_tcon_set_mux(tcon, 1, encoder); + break; + default: + DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n"); + } +} +EXPORT_SYMBOL(sun4i_tcon_mode_set); static void sun4i_tcon_finish_page_flip(struct drm_device *dev, struct sun4i_crtc *scrtc) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 03f983927baa..f61bf6d83b4a 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -191,15 +191,10 @@ struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node); struct drm_panel *sun4i_tcon_find_panel(struct device_node *node); void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); +void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder, + const struct drm_display_mode *mode); void sun4i_tcon_set_status(struct sun4i_tcon *crtc, const struct drm_encoder *encoder, bool enable); -/* Mode Related Controls */ -void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, - struct drm_encoder *encoder); -void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode); -void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, - struct drm_display_mode *mode); - #endif /* __SUN4I_TCON_H__ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 2e27ff9fc58f..b070d522ed8d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -24,7 +24,6 @@ #include "sun4i_crtc.h" #include "sun4i_drv.h" -#include "sun4i_tcon.h" #include "sunxi_engine.h" #define SUN4I_TVE_EN_REG 0x000 @@ -374,13 +373,8 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, struct drm_display_mode *adjusted_mode) { struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); - struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc); - struct sun4i_tcon *tcon = crtc->tcon; const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); - sun4i_tcon1_mode_set(tcon, mode); - sun4i_tcon_set_mux(tcon, 1, encoder); - /* Enable and map the DAC to the output */ regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, SUN4I_TVE_EN_DAC_MAP_MASK, From ba19c53704d8bc503dd20011819d1d3251899c56 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 17 Oct 2017 11:06:14 +0200 Subject: [PATCH 15/32] drm/sun4i: tcon: Move out the tcon0 common setup Some channel0 setup has to be done, no matter what the output interface is (RGB, CPU, LVDS). Move that code into a common function in order to avoid duplication. Reviewed-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/183100/ --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 7afd3b9de7e3..f69bcdf11cb8 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -158,15 +158,26 @@ static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode, return delay; } -static void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, - const struct drm_display_mode *mode) +static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) +{ + /* Configure the dot clock */ + clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + + /* Set the resolution */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, + SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); +} + +static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon, + const struct drm_display_mode *mode) { unsigned int bp, hsync, vsync; u8 clk_delay; u32 val = 0; - /* Configure the dot clock */ - clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + sun4i_tcon0_mode_set_common(tcon, mode); /* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 0); @@ -174,11 +185,6 @@ static void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, SUN4I_TCON0_CTL_CLK_DELAY_MASK, SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); - /* Set the resolution */ - regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, - SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | - SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); - /* * This is called a backporch in the register documentation, * but it really is the back porch + hsync @@ -329,7 +335,7 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, { switch (encoder->encoder_type) { case DRM_MODE_ENCODER_NONE: - sun4i_tcon0_mode_set(tcon, mode); + sun4i_tcon0_mode_set_rgb(tcon, mode); sun4i_tcon_set_mux(tcon, 0, encoder); break; case DRM_MODE_ENCODER_TVDAC: From f55c83d37b85de411db74c6700808d08684522aa Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 17 Oct 2017 20:17:58 +0800 Subject: [PATCH 16/32] drm/sun4i: backend: Support output muxing The backend has a mux to select the destination of the data to output to. It can select the TCON or the frontends. On the A20, it includes an option to output to the second TCON. This is not documented in the user manual, but the vendor kernel uses it nevertheless, so the second backend outputs to the second TCON. Although the muxing can be changed on the fly, DRM needs to be able to group a bunch of layers such that they get switched to another crtc together. This is because the display backend does the layer compositing, while the TCON generates the display timings. This constraint is not supported by DRM. Here we simply pair up backends and TCONs with the same ID. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20171017121807.2994-2-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_backend.c | 52 +++++++++++++++++++++++++-- drivers/gpu/drm/sun4i/sun4i_backend.h | 3 +- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index d18d7e88d511..e8477321f978 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -28,6 +29,11 @@ #include "sun4i_layer.h" #include "sunxi_engine.h" +struct sun4i_backend_quirks { + /* backend <-> TCON muxing selection done in backend */ + bool needs_output_muxing; +}; + static const u32 sunxi_rgb2yuv_coef[12] = { 0x00000107, 0x00000204, 0x00000064, 0x00000108, 0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808, @@ -345,6 +351,7 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct sun4i_drv *drv = drm->dev_private; struct sun4i_backend *backend; + const struct sun4i_backend_quirks *quirks; struct resource *res; void __iomem *regs; int i, ret; @@ -439,6 +446,27 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, SUN4I_BACKEND_MODCTL_DEBE_EN | SUN4I_BACKEND_MODCTL_START_CTL); + /* Set output selection if needed */ + quirks = of_device_get_match_data(dev); + if (quirks->needs_output_muxing) { + /* + * We assume there is no dynamic muxing of backends + * and TCONs, so we select the backend with same ID. + * + * While dynamic selection might be interesting, since + * the CRTC is tied to the TCON, while the layers are + * tied to the backends, this means, we will need to + * switch between groups of layers. There might not be + * a way to represent this constraint in DRM. + */ + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_OUT_SEL, + (backend->engine.id + ? SUN4I_BACKEND_MODCTL_OUT_LCD1 + : SUN4I_BACKEND_MODCTL_OUT_LCD0)); + } + return 0; err_disable_ram_clk: @@ -486,10 +514,28 @@ static int sun4i_backend_remove(struct platform_device *pdev) return 0; } +static const struct sun4i_backend_quirks sun5i_backend_quirks = { +}; + +static const struct sun4i_backend_quirks sun6i_backend_quirks = { +}; + +static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = { +}; + static const struct of_device_id sun4i_backend_of_table[] = { - { .compatible = "allwinner,sun5i-a13-display-backend" }, - { .compatible = "allwinner,sun6i-a31-display-backend" }, - { .compatible = "allwinner,sun8i-a33-display-backend" }, + { + .compatible = "allwinner,sun5i-a13-display-backend", + .data = &sun5i_backend_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-display-backend", + .data = &sun6i_backend_quirks, + }, + { + .compatible = "allwinner,sun8i-a33-display-backend", + .data = &sun8i_a33_backend_quirks, + }, { } }; MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index 21945af67a9d..ac3cc029f5cd 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -25,7 +25,8 @@ #define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) #define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) #define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20) -#define SUN4I_BACKEND_MODCTL_OUT_LCD (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD0 (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD1 (1 << 20) #define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) #define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) #define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) From 4bb206bf4d8cbccaa2c2ee76168381f6c29a9e33 Mon Sep 17 00:00:00 2001 From: Jonathan Liu Date: Tue, 17 Oct 2017 20:17:59 +0800 Subject: [PATCH 17/32] drm/sun4i: tcon: Add support for A10 TCON The A10 has two TCONs that are similar to the ones found on other SoCs. Like the A31, TCON0 has a register used to mux the TCON outputs to the downstream encoders. The bit fields are slightly different. Signed-off-by: Jonathan Liu [wens@csie.org: Reworked for A10 and fixed up commit message] Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20171017121807.2994-3-wens@csie.org --- .../bindings/display/sunxi/sun4i-drm.txt | 1 + drivers/gpu/drm/sun4i/sun4i_drv.c | 3 +- drivers/gpu/drm/sun4i/sun4i_tcon.c | 30 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index 46df3b78ae9e..b2c08af73a95 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -86,6 +86,7 @@ The TCON acts as a timing controller for RGB, LVDS and TV interfaces. Required properties: - compatible: value must be either: + * allwinner,sun4i-a10-tcon * allwinner,sun5i-a13-tcon * allwinner,sun6i-a31-tcon * allwinner,sun6i-a31s-tcon diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index a27efad9bc76..12855ed53d13 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -185,7 +185,8 @@ static bool sun4i_drv_node_is_frontend(struct device_node *node) static bool sun4i_drv_node_is_tcon(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || + return of_device_is_compatible(node, "allwinner,sun4i-a10-tcon") || + of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index f69bcdf11cb8..7beac80c406e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -800,6 +800,30 @@ static int sun4i_tcon_remove(struct platform_device *pdev) } /* platform specific TCON muxing callbacks */ +static int sun4i_a10_tcon_set_mux(struct sun4i_tcon *tcon, + const struct drm_encoder *encoder) +{ + struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); + u32 shift; + + if (!tcon0) + return -EINVAL; + + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_TMDS: + /* HDMI */ + shift = 8; + break; + default: + return -EINVAL; + } + + regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, + 0x3 << shift, tcon->id << shift); + + return 0; +} + static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, const struct drm_encoder *encoder) { @@ -841,6 +865,11 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, return 0; } +static const struct sun4i_tcon_quirks sun4i_a10_quirks = { + .has_channel_1 = true, + .set_mux = sun4i_a10_tcon_set_mux, +}; + static const struct sun4i_tcon_quirks sun5i_a13_quirks = { .has_channel_1 = true, .set_mux = sun5i_a13_tcon_set_mux, @@ -866,6 +895,7 @@ static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { }; static const struct of_device_id sun4i_tcon_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tcon", .data = &sun4i_a10_quirks }, { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, From 7ea4291f9f1117d7f78b336629e8ff9d0c964919 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 17 Oct 2017 20:18:00 +0800 Subject: [PATCH 18/32] drm/sun4i: hdmi: Support HDMI controller on A10 The HDMI controller in the A10 SoC is the same as the one currently supported in the A10s. It has slightly different setup parameters. Since these parameters are not thoroughly understood, we add support for this variant by copying these parameters verbatim. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20171017121807.2994-4-wens@csie.org --- .../bindings/display/sunxi/sun4i-drm.txt | 1 + drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index b2c08af73a95..5650efcfd2ac 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -40,6 +40,7 @@ CEC. It is one end of the pipeline. Required properties: - compatible: value must be one of: + * allwinner,sun4i-a10-hdmi * allwinner,sun5i-a10s-hdmi * allwinner,sun6i-a31-hdmi - reg: base address and size of memory-mapped region diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index d2eb0a60e568..dda904ec0534 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -274,6 +274,58 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { #define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0)) #define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0)) +/* Only difference from sun5i is AMP is 4 instead of 6 */ +static const struct sun4i_hdmi_variant sun4i_variant = { + .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | + SUN4I_HDMI_PAD_CTRL0_CKEN | + SUN4I_HDMI_PAD_CTRL0_PWENG | + SUN4I_HDMI_PAD_CTRL0_PWEND | + SUN4I_HDMI_PAD_CTRL0_PWENC | + SUN4I_HDMI_PAD_CTRL0_LDODEN | + SUN4I_HDMI_PAD_CTRL0_LDOCEN | + SUN4I_HDMI_PAD_CTRL0_BIASEN, + .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(4) | + SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) | + SUN4I_HDMI_PAD_CTRL1_REG_DENCK | + SUN4I_HDMI_PAD_CTRL1_REG_DEN | + SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_EMP_OPT | + SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT | + SUN4I_HDMI_PAD_CTRL1_AMP_OPT, + .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) | + SUN4I_HDMI_PLL_CTRL_CS(7) | + SUN4I_HDMI_PLL_CTRL_CP_S(15) | + SUN4I_HDMI_PLL_CTRL_S(7) | + SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | + SUN4I_HDMI_PLL_CTRL_SDIV2 | + SUN4I_HDMI_PLL_CTRL_LDO2_EN | + SUN4I_HDMI_PLL_CTRL_LDO1_EN | + SUN4I_HDMI_PLL_CTRL_HV_IS_33 | + SUN4I_HDMI_PLL_CTRL_BWS | + SUN4I_HDMI_PLL_CTRL_PLL_EN, + + .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6), + .ddc_clk_pre_divider = 2, + .ddc_clk_m_offset = 1, + + .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31), + .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30), + .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0), + .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31), + .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6), + .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8), + .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31), + .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7), + .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3), + .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9), + .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2), + .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9), + .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8), + + .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG, + .ddc_fifo_has_dir = true, +}; + static const struct sun4i_hdmi_variant sun5i_variant = { .pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN | @@ -598,6 +650,7 @@ static int sun4i_hdmi_remove(struct platform_device *pdev) } static const struct of_device_id sun4i_hdmi_of_table[] = { + { .compatible = "allwinner,sun4i-a10-hdmi", .data = &sun4i_variant, }, { .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, }, { .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, }, { } From 9a8187c00373bce839388574910f72711c9c4c33 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 17 Oct 2017 20:18:01 +0800 Subject: [PATCH 19/32] drm/sun4i: Add support for A10 display pipeline components The A10 display pipeline has 2 frontends, 2 backends, and 2 TCONs. This patch adds support (or a compatible string in the frontend's case) for these components. The TCONs support directly outputting to CPU/RGB/LVDS LCD panels, or it can output to HDMI via an on-chip HDMI controller, or CVBS/YPbPr/VGA signals via on-chip TV encoders. These additional encoders are not covered in this patch. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20171017121807.2994-5-wens@csie.org --- .../devicetree/bindings/display/sunxi/sun4i-drm.txt | 3 +++ drivers/gpu/drm/sun4i/sun4i_backend.c | 8 ++++++++ drivers/gpu/drm/sun4i/sun4i_drv.c | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index 5650efcfd2ac..8f9a58181f89 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -155,6 +155,7 @@ system. Required properties: - compatible: value must be one of: + * allwinner,sun4i-a10-display-backend * allwinner,sun5i-a13-display-backend * allwinner,sun6i-a31-display-backend * allwinner,sun8i-a33-display-backend @@ -187,6 +188,7 @@ deinterlacing and color space conversion. Required properties: - compatible: value must be one of: + * allwinner,sun4i-a10-display-frontend * allwinner,sun5i-a13-display-frontend * allwinner,sun6i-a31-display-frontend * allwinner,sun8i-a33-display-frontend @@ -233,6 +235,7 @@ extra node. Required properties: - compatible: value must be one of: + * allwinner,sun4i-a10-display-engine * allwinner,sun5i-a10s-display-engine * allwinner,sun5i-a13-display-engine * allwinner,sun6i-a31-display-engine diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index e8477321f978..77f9e183df00 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -514,6 +514,10 @@ static int sun4i_backend_remove(struct platform_device *pdev) return 0; } +static const struct sun4i_backend_quirks sun4i_backend_quirks = { + .needs_output_muxing = true, +}; + static const struct sun4i_backend_quirks sun5i_backend_quirks = { }; @@ -524,6 +528,10 @@ static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = { }; static const struct of_device_id sun4i_backend_of_table[] = { + { + .compatible = "allwinner,sun4i-a10-display-backend", + .data = &sun4i_backend_quirks, + }, { .compatible = "allwinner,sun5i-a13-display-backend", .data = &sun5i_backend_quirks, diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 12855ed53d13..51ad1fb6843c 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -178,7 +178,8 @@ static bool sun4i_drv_node_is_connector(struct device_node *node) static bool sun4i_drv_node_is_frontend(struct device_node *node) { - return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || + return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") || + of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); } @@ -343,6 +344,7 @@ static int sun4i_drv_remove(struct platform_device *pdev) } static const struct of_device_id sun4i_drv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-display-engine" }, { .compatible = "allwinner,sun5i-a10s-display-engine" }, { .compatible = "allwinner,sun5i-a13-display-engine" }, { .compatible = "allwinner,sun6i-a31-display-engine" }, From aaddb6d22a49923c5223a4703af7710c249503da Mon Sep 17 00:00:00 2001 From: Jonathan Liu Date: Tue, 17 Oct 2017 20:18:02 +0800 Subject: [PATCH 20/32] drm/sun4i: Add support for A20 display pipeline components The A20 display pipeline has 2 frontends, 2 backends, and 2 TCONs. This patch adds support (or a compatible string in the frontend's case) for these components. The TCONs support directly outputting to CPU/RGB/LVDS LCD panels, or it can output to HDMI via an on-chip HDMI controller, or CVBS/YPbPr/VGA signals via on-chip TV encoders. These additional encoders are not covered in this patch. Signed-off-by: Jonathan Liu [wens@csie.org: Expand commit message] Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20171017121807.2994-6-wens@csie.org --- .../devicetree/bindings/display/sunxi/sun4i-drm.txt | 4 ++++ drivers/gpu/drm/sun4i/sun4i_backend.c | 8 ++++++++ drivers/gpu/drm/sun4i/sun4i_drv.c | 3 +++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 7 +++++++ 4 files changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index 8f9a58181f89..013e76b348ba 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -91,6 +91,7 @@ Required properties: * allwinner,sun5i-a13-tcon * allwinner,sun6i-a31-tcon * allwinner,sun6i-a31s-tcon + * allwinner,sun7i-a20-tcon * allwinner,sun8i-a33-tcon * allwinner,sun8i-v3s-tcon - reg: base address and size of memory-mapped region @@ -158,6 +159,7 @@ Required properties: * allwinner,sun4i-a10-display-backend * allwinner,sun5i-a13-display-backend * allwinner,sun6i-a31-display-backend + * allwinner,sun7i-a20-display-backend * allwinner,sun8i-a33-display-backend - reg: base address and size of the memory-mapped region. - interrupts: interrupt associated to this IP @@ -191,6 +193,7 @@ Required properties: * allwinner,sun4i-a10-display-frontend * allwinner,sun5i-a13-display-frontend * allwinner,sun6i-a31-display-frontend + * allwinner,sun7i-a20-display-frontend * allwinner,sun8i-a33-display-frontend - reg: base address and size of the memory-mapped region. - interrupts: interrupt associated to this IP @@ -240,6 +243,7 @@ Required properties: * allwinner,sun5i-a13-display-engine * allwinner,sun6i-a31-display-engine * allwinner,sun6i-a31s-display-engine + * allwinner,sun7i-a20-display-engine * allwinner,sun8i-a33-display-engine * allwinner,sun8i-v3s-display-engine diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index 77f9e183df00..847eecbe4d14 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -524,6 +524,10 @@ static const struct sun4i_backend_quirks sun5i_backend_quirks = { static const struct sun4i_backend_quirks sun6i_backend_quirks = { }; +static const struct sun4i_backend_quirks sun7i_backend_quirks = { + .needs_output_muxing = true, +}; + static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = { }; @@ -540,6 +544,10 @@ static const struct of_device_id sun4i_backend_of_table[] = { .compatible = "allwinner,sun6i-a31-display-backend", .data = &sun6i_backend_quirks, }, + { + .compatible = "allwinner,sun7i-a20-display-backend", + .data = &sun7i_backend_quirks, + }, { .compatible = "allwinner,sun8i-a33-display-backend", .data = &sun8i_a33_backend_quirks, diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 51ad1fb6843c..75c76cdd82bc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -181,6 +181,7 @@ static bool sun4i_drv_node_is_frontend(struct device_node *node) return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") || of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || + of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") || of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); } @@ -190,6 +191,7 @@ static bool sun4i_drv_node_is_tcon(struct device_node *node) of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || + of_device_is_compatible(node, "allwinner,sun7i-a20-tcon") || of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon"); } @@ -349,6 +351,7 @@ static const struct of_device_id sun4i_drv_of_table[] = { { .compatible = "allwinner,sun5i-a13-display-engine" }, { .compatible = "allwinner,sun6i-a31-display-engine" }, { .compatible = "allwinner,sun6i-a31s-display-engine" }, + { .compatible = "allwinner,sun7i-a20-display-engine" }, { .compatible = "allwinner,sun8i-a33-display-engine" }, { .compatible = "allwinner,sun8i-v3s-display-engine" }, { } diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 7beac80c406e..e122f5b2a395 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -886,6 +886,12 @@ static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { .needs_de_be_mux = true, }; +static const struct sun4i_tcon_quirks sun7i_a20_quirks = { + .has_channel_1 = true, + /* Same display pipeline structure as A10 */ + .set_mux = sun4i_a10_tcon_set_mux, +}; + static const struct sun4i_tcon_quirks sun8i_a33_quirks = { /* nothing is supported */ }; @@ -899,6 +905,7 @@ static const struct of_device_id sun4i_tcon_of_table[] = { { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, + { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks }, { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, { } From 8b32948690946e89c198e44f8a1252295473f348 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 17 Oct 2017 23:43:43 +0300 Subject: [PATCH 21/32] drm/bridge: adv7511: Fix a use after free We free "edid", then use it again on the next line. Fixes: 3b1b975003e4 ("drm: adv7511/33: add HDMI CEC support") Signed-off-by: Dan Carpenter Signed-off-by: Archit Taneja Link: https://patchwork.freedesktop.org/patch/msgid/20171017204343.zctliubjkq7imudi@mwanda --- drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 31ca883bda83..0e14f1572d05 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -607,10 +607,10 @@ static int adv7511_get_modes(struct adv7511 *adv7511, adv7511_set_config_csc(adv7511, connector, adv7511->rgb, drm_detect_hdmi_monitor(edid)); - kfree(edid); - cec_s_phys_addr_from_edid(adv7511->cec_adap, edid); + kfree(edid); + return count; } From a37c0f48950b56f6ef2ee637ba597855504e3056 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 12 Oct 2017 22:47:22 -0500 Subject: [PATCH 22/32] vgaarb: Select a default VGA device even if there's no legacy VGA Daniel Axtens reported that on the HiSilicon D05 board, the VGA device is behind a bridge that doesn't support PCI_BRIDGE_CTL_VGA, so the VGA arbiter never selects it as the default, which means Xorg auto-detection doesn't work. VGA is a legacy PCI feature: a VGA device can respond to addresses, e.g., [mem 0xa0000-0xbffff], [io 0x3b0-0x3bb], [io 0x3c0-0x3df], etc., that are not configurable by BARs. Consequently, multiple VGA devices can conflict with each other. The VGA arbiter avoids conflicts by ensuring that those legacy resources are only routed to one VGA device at a time. The arbiter identifies the "default VGA" device, i.e., a legacy VGA device that was used by boot firmware. It selects the first device that: - is of PCI_CLASS_DISPLAY_VGA, - has both PCI_COMMAND_IO and PCI_COMMAND_MEMORY enabled, and - has PCI_BRIDGE_CTL_VGA set in all upstream bridges. Some systems don't have such a device. For example, if a host bridge doesn't support I/O space, PCI_COMMAND_IO probably won't be enabled for any devices below it. Or, as on the HiSilicon D05, the VGA device may be behind a bridge that doesn't support PCI_BRIDGE_CTL_VGA, so accesses to the legacy VGA resources will never reach the device. This patch extends the arbiter so that if it doesn't find a device that meets all the above criteria, it selects the first device that: - is of PCI_CLASS_DISPLAY_VGA and - has PCI_COMMAND_IO or PCI_COMMAND_MEMORY enabled If it doesn't find even that, it selects the first device that: - is of class PCI_CLASS_DISPLAY_VGA. Such a device may not be able to use the legacy VGA resources, but most drivers can operate the device without those. Setting it as the default device means its "boot_vga" sysfs file will contain "1", which Xorg (via libpciaccess) uses to help select its default output device. This fixes Xorg auto-detection on some arm64 systems (HiSilicon D05 in particular; see the link below). It also replaces the powerpc fixup_vga() quirk, albeit with slightly different semantics: the quirk selected the first VGA device we found, and overrode that selection with any enabled VGA device we found. If there were several enabled VGA devices, the *last* one we found would become the default. The code here instead selects the *first* enabled VGA device we find, and if none are enabled, the first VGA device we find. Link: http://lkml.kernel.org/r/20170901072744.2409-1-dja@axtens.net Tested-by: Daniel Axtens # arm64, ppc64-qemu-tcg Tested-by: Zhou Wang # D05 Hisi Hip07, Hip08 Signed-off-by: Bjorn Helgaas Acked-by: Ard Biesheuvel Reviewed-by: Daniel Axtens Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20171013034721.14630.65913.stgit@bhelgaas-glaptop.roam.corp.google.com --- arch/powerpc/kernel/pci-common.c | 12 ------------ drivers/gpu/vga/vgaarb.c | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c index 02831a396419..0ac7aa346c69 100644 --- a/arch/powerpc/kernel/pci-common.c +++ b/arch/powerpc/kernel/pci-common.c @@ -1740,15 +1740,3 @@ static void fixup_hide_host_resource_fsl(struct pci_dev *dev) } DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MOTOROLA, PCI_ANY_ID, fixup_hide_host_resource_fsl); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_FREESCALE, PCI_ANY_ID, fixup_hide_host_resource_fsl); - -static void fixup_vga(struct pci_dev *pdev) -{ - u16 cmd; - - pci_read_config_word(pdev, PCI_COMMAND, &cmd); - if ((cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) || !vga_default_device()) - vga_set_default_device(pdev); - -} -DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID, - PCI_CLASS_DISPLAY_VGA, 8, fixup_vga); diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c index 76875f6299b8..8035e38d5110 100644 --- a/drivers/gpu/vga/vgaarb.c +++ b/drivers/gpu/vga/vgaarb.c @@ -1468,6 +1468,31 @@ static int __init vga_arb_device_init(void) vgaarb_info(dev, "no bridge control possible\n"); } + if (!vga_default_device()) { + list_for_each_entry(vgadev, &vga_list, list) { + struct device *dev = &vgadev->pdev->dev; + u16 cmd; + + pdev = vgadev->pdev; + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) { + vgaarb_info(dev, "setting as boot device (VGA legacy resources not available)\n"); + vga_set_default_device(pdev); + break; + } + } + } + + if (!vga_default_device()) { + vgadev = list_first_entry_or_null(&vga_list, + struct vga_device, list); + if (vgadev) { + struct device *dev = &vgadev->pdev->dev; + vgaarb_info(dev, "setting as boot device (VGA legacy resources not available)\n"); + vga_set_default_device(vgadev->pdev); + } + } + pr_info("loaded\n"); return rc; } From 505a1b571711a977b7d069dec3c14381521e9683 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 12 Oct 2017 22:47:29 -0500 Subject: [PATCH 23/32] vgaarb: Factor out EFI and fallback default device selection The default VGA device is normally set in vga_arbiter_add_pci_device() when we call it for the first enabled device that can be accessed with the legacy VGA resources ([mem 0xa0000-0xbffff], etc.) That default device can be overridden by an EFI device that owns the boot framebuffer. As a fallback, we can also select a VGA device that can't be accessed via legacy VGA resources, or a VGA device that isn't even enabled. Factor out this EFI and fallback selection from vga_arb_device_init() into a separate vga_arb_select_default_device() function. This doesn't change any behavior, but it untangles the "bridge control possible" checking and messages from the default device selection. Tested-by: Zhou Wang # D05 Hisi Hip07, Hip08 Signed-off-by: Bjorn Helgaas Acked-by: Ard Biesheuvel Reviewed-by: Daniel Axtens Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20171013034729.14630.30419.stgit@bhelgaas-glaptop.roam.corp.google.com --- drivers/gpu/vga/vgaarb.c | 57 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c index 8035e38d5110..d35d6d271f3f 100644 --- a/drivers/gpu/vga/vgaarb.c +++ b/drivers/gpu/vga/vgaarb.c @@ -1402,29 +1402,14 @@ static struct miscdevice vga_arb_device = { MISC_DYNAMIC_MINOR, "vga_arbiter", &vga_arb_device_fops }; -static int __init vga_arb_device_init(void) +static void __init vga_arb_select_default_device(void) { - int rc; struct pci_dev *pdev; struct vga_device *vgadev; - rc = misc_register(&vga_arb_device); - if (rc < 0) - pr_err("error %d registering device\n", rc); - - bus_register_notifier(&pci_bus_type, &pci_notifier); - - /* We add all pci devices satisfying vga class in the arbiter by - * default */ - pdev = NULL; - while ((pdev = - pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, - PCI_ANY_ID, pdev)) != NULL) - vga_arbiter_add_pci_device(pdev); - +#if defined(CONFIG_X86) || defined(CONFIG_IA64) list_for_each_entry(vgadev, &vga_list, list) { struct device *dev = &vgadev->pdev->dev; -#if defined(CONFIG_X86) || defined(CONFIG_IA64) /* * Override vga_arbiter_add_pci_device()'s I/O based detection * as it may take the wrong device (e.g. on Apple system under @@ -1461,12 +1446,8 @@ static int __init vga_arb_device_init(void) vgaarb_info(dev, "overriding boot device\n"); vga_set_default_device(vgadev->pdev); } -#endif - if (vgadev->bridge_has_one_vga) - vgaarb_info(dev, "bridge control possible\n"); - else - vgaarb_info(dev, "no bridge control possible\n"); } +#endif if (!vga_default_device()) { list_for_each_entry(vgadev, &vga_list, list) { @@ -1492,6 +1473,38 @@ static int __init vga_arb_device_init(void) vga_set_default_device(vgadev->pdev); } } +} + +static int __init vga_arb_device_init(void) +{ + int rc; + struct pci_dev *pdev; + struct vga_device *vgadev; + + rc = misc_register(&vga_arb_device); + if (rc < 0) + pr_err("error %d registering device\n", rc); + + bus_register_notifier(&pci_bus_type, &pci_notifier); + + /* We add all PCI devices satisfying VGA class in the arbiter by + * default */ + pdev = NULL; + while ((pdev = + pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, + PCI_ANY_ID, pdev)) != NULL) + vga_arbiter_add_pci_device(pdev); + + list_for_each_entry(vgadev, &vga_list, list) { + struct device *dev = &vgadev->pdev->dev; + + if (vgadev->bridge_has_one_vga) + vgaarb_info(dev, "bridge control possible\n"); + else + vgaarb_info(dev, "no bridge control possible\n"); + } + + vga_arb_select_default_device(); pr_info("loaded\n"); return rc; From 81a7bd4a3f4497af81694c017b5b91253009f8cb Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 17 Oct 2017 18:29:18 +0200 Subject: [PATCH 24/32] drm: some KMS todo ideas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inspired by discussions with Keith and Ville. Cc: Ville Syrjälä Cc: Keith Packard Acked-by: Sean Paul Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20171017162918.8380-1-daniel.vetter@ffwll.ch --- Documentation/gpu/todo.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index 92ee2f982572..96f8ec7dbe4e 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -304,6 +304,18 @@ There's a bunch of issues with it: Contact: Daniel Vetter +KMS cleanups +------------ + +Some of these date from the very introduction of KMS in 2008 ... + +- drm_mode_config.crtc_idr is misnamed, since it contains all KMS object. Should + be renamed to drm_mode_config.object_idr. + +- drm_display_mode doesn't need to be derived from drm_mode_object. That's + leftovers from older (never merged into upstream) KMS designs where modes + where set using their ID, including support to add/remove modes. + Better Testing ============== From 84014b0a39eef6df4a26f8afabf2b50bd376d515 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 17 Oct 2017 17:27:14 +0200 Subject: [PATCH 25/32] drm/atomic-helper: check that drivers call drm_crtc_vblank_off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At least when they have vblank support they need to call this, or the vblank core will happily call into their crtc->enable_vblank callback even when the crtc is off. Which leads to a boom when the clocks are off on most hardware (besides the inevitable confusion in the book-keeping). The consistency checks in drm_vblank.c will then make sure that vblank_off/on calls are balanced, and if drivers forget to re-enable it all the commits will stall, so I think we're covered. It'd be nice to be able to place this check outside of commit helpers, but tha's not really possible (due to nonblocking commits and all that). Placing it into atomic helpers should at least cover most drivers. Also note that vblank support is still optional (for virtual drivers, which tend to not have this), check for that. v2: Fixup the handling for vblank_put (Rob). Cc: Rob Clark Tested-by: Rob Clark Cc: Ville Syrjälä Reviewed-by: Ville Syrjälä Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20171017152714.6849-1-daniel.vetter@ffwll.ch --- drivers/gpu/drm/drm_atomic_helper.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 3472d9ae8b95..27dc6838533a 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -860,6 +860,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state) for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; + int ret; /* Shut down everything that needs a full modeset. */ if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) @@ -883,6 +884,14 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state) funcs->disable(crtc); else funcs->dpms(crtc, DRM_MODE_DPMS_OFF); + + if (!(dev->irq_enabled && dev->num_crtcs)) + continue; + + ret = drm_crtc_vblank_get(crtc); + WARN_ONCE(ret != -EINVAL, "driver forgot to call drm_crtc_vblank_off()\n"); + if (ret == 0) + drm_crtc_vblank_put(crtc); } } From a483159d2b5238b69ef6a3310a4aa61b0dd496c3 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 11 Oct 2017 14:59:56 +0200 Subject: [PATCH 26/32] drm/panel: simple: fix vertical timings for Innolux AT043TN24 The vsync length should be 10 lines, as specified in the data sheet. This gets the actual refresh rate closer to nominal 60 Hz given the 9 MHz pixel clock. Signed-off-by: Philipp Zabel Tested-by: Marco Franchi Signed-off-by: Thierry Reding Link: https://patchwork.freedesktop.org/patch/msgid/20171011125958.23064-1-p.zabel@pengutronix.de --- drivers/gpu/drm/panel/panel-simple.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index a3c96d2ea41c..fae989b7477c 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1018,8 +1018,8 @@ static const struct drm_display_mode innolux_at043tn24_mode = { .htotal = 480 + 2 + 41 + 2, .vdisplay = 272, .vsync_start = 272 + 2, - .vsync_end = 272 + 2 + 11, - .vtotal = 272 + 2 + 11 + 2, + .vsync_end = 272 + 2 + 10, + .vtotal = 272 + 2 + 10 + 2, .vrefresh = 60, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; From 6560279c8fd08838ccaeea89f33ec10d26dc7857 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 11 Oct 2017 14:59:57 +0200 Subject: [PATCH 27/32] drm/panel: simple: add bus flags for Innolux AT043TN24 For LCD interface controllers that support configuring polarity of pixel clock and data enable signal, specify bus flags in the panel descriptor. Signed-off-by: Philipp Zabel Tested-by: Marco Franchi Signed-off-by: Thierry Reding Link: https://patchwork.freedesktop.org/patch/msgid/20171011125958.23064-2-p.zabel@pengutronix.de --- drivers/gpu/drm/panel/panel-simple.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index fae989b7477c..1bbe212c4780 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1033,6 +1033,7 @@ static const struct panel_desc innolux_at043tn24 = { .height = 54, }, .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, }; static const struct drm_display_mode innolux_at070tn92_mode = { From 6c684e3b677eee1272f7952a16ffb9aac2c228f9 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Wed, 11 Oct 2017 14:59:58 +0200 Subject: [PATCH 28/32] drm/panel: simple: add delays for Innolux AT043TN24 The delays between video data and backlight enable and between backlight disable and end of video data are given as >= 160 ms in the datasheet. Signed-off-by: Philipp Zabel Tested-by: Marco Franchi Signed-off-by: Thierry Reding Link: https://patchwork.freedesktop.org/patch/msgid/20171011125958.23064-3-p.zabel@pengutronix.de --- drivers/gpu/drm/panel/panel-simple.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 1bbe212c4780..8838de2b9709 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1008,6 +1008,10 @@ static const struct panel_desc hitachi_tx23d38vm0caa = { .width = 195, .height = 117, }, + .delay = { + .enable = 160, + .disable = 160, + }, }; static const struct drm_display_mode innolux_at043tn24_mode = { From 448956d619a35538e71e8a2d8f1e4bc274037767 Mon Sep 17 00:00:00 2001 From: Frank Binns Date: Wed, 18 Oct 2017 14:30:23 +0100 Subject: [PATCH 29/32] dma-fence: remove duplicate word in comment Signed-off-by: Frank Binns Reviewed-by: Chris Wilson Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/1508333423-5394-1-git-send-email-frank.binns@imgtec.com --- include/linux/dma-fence.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h index ca974224d92e..efdabbb64e3c 100644 --- a/include/linux/dma-fence.h +++ b/include/linux/dma-fence.h @@ -128,7 +128,7 @@ struct dma_fence_cb { * implementation know that there is another driver waiting on * the signal (ie. hw->sw case). * - * This function can be called called from atomic context, but not + * This function can be called from atomic context, but not * from irq context, so normal spinlocks can be used. * * A return value of false indicates the fence already passed, From 06e733e41f87d75a60347b0c93a18fc0104d709d Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 18 Oct 2017 19:22:40 +0200 Subject: [PATCH 30/32] drm/panel: simple: add Toshiba LT089AC19000 Only exposes a single mode and not a complete display timing, as the datasheet is rather vague about the minimum/maximum values. Signed-off-by: Lucas Stach Acked-by: Rob Herring Signed-off-by: Thierry Reding Link: https://patchwork.freedesktop.org/patch/msgid/20171018172240.8772-1-l.stach@pengutronix.de --- .../display/panel/toshiba,lt089ac29000.txt | 8 ++++++ drivers/gpu/drm/panel/panel-simple.c | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/toshiba,lt089ac29000.txt diff --git a/Documentation/devicetree/bindings/display/panel/toshiba,lt089ac29000.txt b/Documentation/devicetree/bindings/display/panel/toshiba,lt089ac29000.txt new file mode 100644 index 000000000000..4c0caaf246c9 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/toshiba,lt089ac29000.txt @@ -0,0 +1,8 @@ +Toshiba 8.9" WXGA (1280x768) TFT LCD panel + +Required properties: +- compatible: should be "toshiba,lt089ac29000.txt" +- power-supply: as specified in the base binding + +This binding is compatible with the simple-panel binding, which is specified +in simple-panel.txt in this directory. diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 8838de2b9709..b7c4709f7b34 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1837,6 +1837,30 @@ static const struct panel_desc tianma_tm070jdhg30 = { .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, }; +static const struct drm_display_mode toshiba_lt089ac29000_mode = { + .clock = 79500, + .hdisplay = 1280, + .hsync_start = 1280 + 192, + .hsync_end = 1280 + 192 + 128, + .htotal = 1280 + 192 + 128 + 64, + .vdisplay = 768, + .vsync_start = 768 + 20, + .vsync_end = 768 + 20 + 7, + .vtotal = 768 + 20 + 7 + 3, + .vrefresh = 60, +}; + +static const struct panel_desc toshiba_lt089ac29000 = { + .modes = &toshiba_lt089ac29000_mode, + .num_modes = 1, + .size = { + .width = 194, + .height = 116, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE, +}; + static const struct drm_display_mode tpk_f07a_0102_mode = { .clock = 33260, .hdisplay = 800, @@ -2118,6 +2142,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "tianma,tm070jdhg30", .data = &tianma_tm070jdhg30, + }, { + .compatible = "toshiba,lt089ac29000", + .data = &toshiba_lt089ac29000, }, { .compatible = "tpk,f07a-0102", .data = &tpk_f07a_0102, From b9f19259b84dc648f207a46f3581d15eeaedf4b6 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Thu, 19 Oct 2017 14:57:48 +0200 Subject: [PATCH 31/32] drm/vc4: Add the DRM_IOCTL_VC4_GEM_MADVISE ioctl This ioctl will allow us to purge inactive userspace buffers when the system is running out of contiguous memory. For now, the purge logic is rather dumb in that it does not try to release only the amount of BO needed to meet the last CMA alloc request but instead purges all objects placed in the purgeable pool as soon as we experience a CMA allocation failure. Note that the in-kernel BO cache is always purged before the purgeable cache because those objects are known to be unused while objects marked as purgeable by a userspace application/library might have to be restored when they are marked back as unpurgeable, which can be expensive. Signed-off-by: Boris Brezillon Signed-off-by: Eric Anholt Reviewed-by: Eric Anholt Link: https://patchwork.freedesktop.org/patch/msgid/20171019125748.3152-1-boris.brezillon@free-electrons.com --- drivers/gpu/drm/vc4/vc4_bo.c | 289 +++++++++++++++++++++++++++++++- drivers/gpu/drm/vc4/vc4_drv.c | 10 +- drivers/gpu/drm/vc4/vc4_drv.h | 30 ++++ drivers/gpu/drm/vc4/vc4_gem.c | 156 ++++++++++++++++- drivers/gpu/drm/vc4/vc4_plane.c | 20 +++ include/uapi/drm/vc4_drm.h | 19 +++ 6 files changed, 508 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 3afdbf4bc10b..01a53ba304f8 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -53,6 +53,17 @@ static void vc4_bo_stats_dump(struct vc4_dev *vc4) vc4->bo_labels[i].size_allocated / 1024, vc4->bo_labels[i].num_allocated); } + + mutex_lock(&vc4->purgeable.lock); + if (vc4->purgeable.num) + DRM_INFO("%30s: %6zdkb BOs (%d)\n", "userspace BO cache", + vc4->purgeable.size / 1024, vc4->purgeable.num); + + if (vc4->purgeable.purged_num) + DRM_INFO("%30s: %6zdkb BOs (%d)\n", "total purged BO", + vc4->purgeable.purged_size / 1024, + vc4->purgeable.purged_num); + mutex_unlock(&vc4->purgeable.lock); } #ifdef CONFIG_DEBUG_FS @@ -75,6 +86,17 @@ int vc4_bo_stats_debugfs(struct seq_file *m, void *unused) } mutex_unlock(&vc4->bo_lock); + mutex_lock(&vc4->purgeable.lock); + if (vc4->purgeable.num) + seq_printf(m, "%30s: %6dkb BOs (%d)\n", "userspace BO cache", + vc4->purgeable.size / 1024, vc4->purgeable.num); + + if (vc4->purgeable.purged_num) + seq_printf(m, "%30s: %6dkb BOs (%d)\n", "total purged BO", + vc4->purgeable.purged_size / 1024, + vc4->purgeable.purged_num); + mutex_unlock(&vc4->purgeable.lock); + return 0; } #endif @@ -247,6 +269,109 @@ static void vc4_bo_cache_purge(struct drm_device *dev) mutex_unlock(&vc4->bo_lock); } +void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo) +{ + struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); + + mutex_lock(&vc4->purgeable.lock); + list_add_tail(&bo->size_head, &vc4->purgeable.list); + vc4->purgeable.num++; + vc4->purgeable.size += bo->base.base.size; + mutex_unlock(&vc4->purgeable.lock); +} + +static void vc4_bo_remove_from_purgeable_pool_locked(struct vc4_bo *bo) +{ + struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); + + /* list_del_init() is used here because the caller might release + * the purgeable lock in order to acquire the madv one and update the + * madv status. + * During this short period of time a user might decide to mark + * the BO as unpurgeable, and if bo->madv is set to + * VC4_MADV_DONTNEED it will try to remove the BO from the + * purgeable list which will fail if the ->next/prev fields + * are set to LIST_POISON1/LIST_POISON2 (which is what + * list_del() does). + * Re-initializing the list element guarantees that list_del() + * will work correctly even if it's a NOP. + */ + list_del_init(&bo->size_head); + vc4->purgeable.num--; + vc4->purgeable.size -= bo->base.base.size; +} + +void vc4_bo_remove_from_purgeable_pool(struct vc4_bo *bo) +{ + struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev); + + mutex_lock(&vc4->purgeable.lock); + vc4_bo_remove_from_purgeable_pool_locked(bo); + mutex_unlock(&vc4->purgeable.lock); +} + +static void vc4_bo_purge(struct drm_gem_object *obj) +{ + struct vc4_bo *bo = to_vc4_bo(obj); + struct drm_device *dev = obj->dev; + + WARN_ON(!mutex_is_locked(&bo->madv_lock)); + WARN_ON(bo->madv != VC4_MADV_DONTNEED); + + drm_vma_node_unmap(&obj->vma_node, dev->anon_inode->i_mapping); + + dma_free_wc(dev->dev, obj->size, bo->base.vaddr, bo->base.paddr); + bo->base.vaddr = NULL; + bo->madv = __VC4_MADV_PURGED; +} + +static void vc4_bo_userspace_cache_purge(struct drm_device *dev) +{ + struct vc4_dev *vc4 = to_vc4_dev(dev); + + mutex_lock(&vc4->purgeable.lock); + while (!list_empty(&vc4->purgeable.list)) { + struct vc4_bo *bo = list_first_entry(&vc4->purgeable.list, + struct vc4_bo, size_head); + struct drm_gem_object *obj = &bo->base.base; + size_t purged_size = 0; + + vc4_bo_remove_from_purgeable_pool_locked(bo); + + /* Release the purgeable lock while we're purging the BO so + * that other people can continue inserting things in the + * purgeable pool without having to wait for all BOs to be + * purged. + */ + mutex_unlock(&vc4->purgeable.lock); + mutex_lock(&bo->madv_lock); + + /* Since we released the purgeable pool lock before acquiring + * the BO madv one, the user may have marked the BO as WILLNEED + * and re-used it in the meantime. + * Before purging the BO we need to make sure + * - it is still marked as DONTNEED + * - it has not been re-inserted in the purgeable list + * - it is not used by HW blocks + * If one of these conditions is not met, just skip the entry. + */ + if (bo->madv == VC4_MADV_DONTNEED && + list_empty(&bo->size_head) && + !refcount_read(&bo->usecnt)) { + purged_size = bo->base.base.size; + vc4_bo_purge(obj); + } + mutex_unlock(&bo->madv_lock); + mutex_lock(&vc4->purgeable.lock); + + if (purged_size) { + vc4->purgeable.purged_size += purged_size; + vc4->purgeable.purged_num++; + } + } + mutex_unlock(&vc4->purgeable.lock); +} + static struct vc4_bo *vc4_bo_get_from_cache(struct drm_device *dev, uint32_t size, enum vc4_kernel_bo_type type) @@ -293,6 +418,9 @@ struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size) if (!bo) return ERR_PTR(-ENOMEM); + bo->madv = VC4_MADV_WILLNEED; + refcount_set(&bo->usecnt, 0); + mutex_init(&bo->madv_lock); mutex_lock(&vc4->bo_lock); bo->label = VC4_BO_TYPE_KERNEL; vc4->bo_labels[VC4_BO_TYPE_KERNEL].num_allocated++; @@ -330,16 +458,38 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size, * CMA allocations we've got laying around and try again. */ vc4_bo_cache_purge(dev); - cma_obj = drm_gem_cma_create(dev, size); - if (IS_ERR(cma_obj)) { - DRM_ERROR("Failed to allocate from CMA:\n"); - vc4_bo_stats_dump(vc4); - return ERR_PTR(-ENOMEM); - } + } + + if (IS_ERR(cma_obj)) { + /* + * Still not enough CMA memory, purge the userspace BO + * cache and retry. + * This is sub-optimal since we purge the whole userspace + * BO cache which forces user that want to re-use the BO to + * restore its initial content. + * Ideally, we should purge entries one by one and retry + * after each to see if CMA allocation succeeds. Or even + * better, try to find an entry with at least the same + * size. + */ + vc4_bo_userspace_cache_purge(dev); + cma_obj = drm_gem_cma_create(dev, size); + } + + if (IS_ERR(cma_obj)) { + DRM_ERROR("Failed to allocate from CMA:\n"); + vc4_bo_stats_dump(vc4); + return ERR_PTR(-ENOMEM); } bo = to_vc4_bo(&cma_obj->base); + /* By default, BOs do not support the MADV ioctl. This will be enabled + * only on BOs that are exposed to userspace (V3D, V3D_SHADER and DUMB + * BOs). + */ + bo->madv = __VC4_MADV_NOTSUPP; + mutex_lock(&vc4->bo_lock); vc4_bo_set_label(&cma_obj->base, type); mutex_unlock(&vc4->bo_lock); @@ -365,6 +515,8 @@ int vc4_dumb_create(struct drm_file *file_priv, if (IS_ERR(bo)) return PTR_ERR(bo); + bo->madv = VC4_MADV_WILLNEED; + ret = drm_gem_handle_create(file_priv, &bo->base.base, &args->handle); drm_gem_object_put_unlocked(&bo->base.base); @@ -403,6 +555,12 @@ void vc4_free_object(struct drm_gem_object *gem_bo) struct vc4_bo *bo = to_vc4_bo(gem_bo); struct list_head *cache_list; + /* Remove the BO from the purgeable list. */ + mutex_lock(&bo->madv_lock); + if (bo->madv == VC4_MADV_DONTNEED && !refcount_read(&bo->usecnt)) + vc4_bo_remove_from_purgeable_pool(bo); + mutex_unlock(&bo->madv_lock); + mutex_lock(&vc4->bo_lock); /* If the object references someone else's memory, we can't cache it. */ @@ -418,7 +576,8 @@ void vc4_free_object(struct drm_gem_object *gem_bo) } /* If this object was partially constructed but CMA allocation - * had failed, just free it. + * had failed, just free it. Can also happen when the BO has been + * purged. */ if (!bo->base.vaddr) { vc4_bo_destroy(bo); @@ -437,6 +596,10 @@ void vc4_free_object(struct drm_gem_object *gem_bo) bo->validated_shader = NULL; } + /* Reset madv and usecnt before adding the BO to the cache. */ + bo->madv = __VC4_MADV_NOTSUPP; + refcount_set(&bo->usecnt, 0); + bo->t_format = false; bo->free_time = jiffies; list_add(&bo->size_head, cache_list); @@ -461,6 +624,56 @@ static void vc4_bo_cache_time_work(struct work_struct *work) mutex_unlock(&vc4->bo_lock); } +int vc4_bo_inc_usecnt(struct vc4_bo *bo) +{ + int ret; + + /* Fast path: if the BO is already retained by someone, no need to + * check the madv status. + */ + if (refcount_inc_not_zero(&bo->usecnt)) + return 0; + + mutex_lock(&bo->madv_lock); + switch (bo->madv) { + case VC4_MADV_WILLNEED: + refcount_inc(&bo->usecnt); + ret = 0; + break; + case VC4_MADV_DONTNEED: + /* We shouldn't use a BO marked as purgeable if at least + * someone else retained its content by incrementing usecnt. + * Luckily the BO hasn't been purged yet, but something wrong + * is happening here. Just throw an error instead of + * authorizing this use case. + */ + case __VC4_MADV_PURGED: + /* We can't use a purged BO. */ + default: + /* Invalid madv value. */ + ret = -EINVAL; + break; + } + mutex_unlock(&bo->madv_lock); + + return ret; +} + +void vc4_bo_dec_usecnt(struct vc4_bo *bo) +{ + /* Fast path: if the BO is still retained by someone, no need to test + * the madv value. + */ + if (refcount_dec_not_one(&bo->usecnt)) + return; + + mutex_lock(&bo->madv_lock); + if (refcount_dec_and_test(&bo->usecnt) && + bo->madv == VC4_MADV_DONTNEED) + vc4_bo_add_to_purgeable_pool(bo); + mutex_unlock(&bo->madv_lock); +} + static void vc4_bo_cache_time_timer(unsigned long data) { struct drm_device *dev = (struct drm_device *)data; @@ -480,18 +693,52 @@ struct dma_buf * vc4_prime_export(struct drm_device *dev, struct drm_gem_object *obj, int flags) { struct vc4_bo *bo = to_vc4_bo(obj); + struct dma_buf *dmabuf; + int ret; if (bo->validated_shader) { DRM_DEBUG("Attempting to export shader BO\n"); return ERR_PTR(-EINVAL); } - return drm_gem_prime_export(dev, obj, flags); + /* Note: as soon as the BO is exported it becomes unpurgeable, because + * noone ever decrements the usecnt even if the reference held by the + * exported BO is released. This shouldn't be a problem since we don't + * expect exported BOs to be marked as purgeable. + */ + ret = vc4_bo_inc_usecnt(bo); + if (ret) { + DRM_ERROR("Failed to increment BO usecnt\n"); + return ERR_PTR(ret); + } + + dmabuf = drm_gem_prime_export(dev, obj, flags); + if (IS_ERR(dmabuf)) + vc4_bo_dec_usecnt(bo); + + return dmabuf; +} + +int vc4_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct drm_gem_object *obj = vma->vm_private_data; + struct vc4_bo *bo = to_vc4_bo(obj); + + /* The only reason we would end up here is when user-space accesses + * BO's memory after it's been purged. + */ + mutex_lock(&bo->madv_lock); + WARN_ON(bo->madv != __VC4_MADV_PURGED); + mutex_unlock(&bo->madv_lock); + + return VM_FAULT_SIGBUS; } int vc4_mmap(struct file *filp, struct vm_area_struct *vma) { struct drm_gem_object *gem_obj; + unsigned long vm_pgoff; struct vc4_bo *bo; int ret; @@ -507,16 +754,36 @@ int vc4_mmap(struct file *filp, struct vm_area_struct *vma) return -EINVAL; } + if (bo->madv != VC4_MADV_WILLNEED) { + DRM_DEBUG("mmaping of %s BO not allowed\n", + bo->madv == VC4_MADV_DONTNEED ? + "purgeable" : "purged"); + return -EINVAL; + } + /* * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map * the whole buffer. */ vma->vm_flags &= ~VM_PFNMAP; - vma->vm_pgoff = 0; + /* This ->vm_pgoff dance is needed to make all parties happy: + * - dma_mmap_wc() uses ->vm_pgoff as an offset within the allocated + * mem-region, hence the need to set it to zero (the value set by + * the DRM core is a virtual offset encoding the GEM object-id) + * - the mmap() core logic needs ->vm_pgoff to be restored to its + * initial value before returning from this function because it + * encodes the offset of this GEM in the dev->anon_inode pseudo-file + * and this information will be used when we invalidate userspace + * mappings with drm_vma_node_unmap() (called from vc4_gem_purge()). + */ + vm_pgoff = vma->vm_pgoff; + vma->vm_pgoff = 0; ret = dma_mmap_wc(bo->base.base.dev->dev, vma, bo->base.vaddr, bo->base.paddr, vma->vm_end - vma->vm_start); + vma->vm_pgoff = vm_pgoff; + if (ret) drm_gem_vm_close(vma); @@ -580,6 +847,8 @@ int vc4_create_bo_ioctl(struct drm_device *dev, void *data, if (IS_ERR(bo)) return PTR_ERR(bo); + bo->madv = VC4_MADV_WILLNEED; + ret = drm_gem_handle_create(file_priv, &bo->base.base, &args->handle); drm_gem_object_put_unlocked(&bo->base.base); @@ -633,6 +902,8 @@ vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data, if (IS_ERR(bo)) return PTR_ERR(bo); + bo->madv = VC4_MADV_WILLNEED; + if (copy_from_user(bo->base.vaddr, (void __user *)(uintptr_t)args->data, args->size)) { diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 1c96edcb302b..e3c29729da2e 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -100,6 +100,7 @@ static int vc4_get_param_ioctl(struct drm_device *dev, void *data, case DRM_VC4_PARAM_SUPPORTS_ETC1: case DRM_VC4_PARAM_SUPPORTS_THREADED_FS: case DRM_VC4_PARAM_SUPPORTS_FIXED_RCL_ORDER: + case DRM_VC4_PARAM_SUPPORTS_MADVISE: args->value = true; break; default: @@ -117,6 +118,12 @@ static void vc4_lastclose(struct drm_device *dev) drm_fbdev_cma_restore_mode(vc4->fbdev); } +static const struct vm_operations_struct vc4_vm_ops = { + .fault = vc4_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + static const struct file_operations vc4_drm_fops = { .owner = THIS_MODULE, .open = drm_open, @@ -142,6 +149,7 @@ static const struct drm_ioctl_desc vc4_drm_ioctls[] = { DRM_IOCTL_DEF_DRV(VC4_SET_TILING, vc4_set_tiling_ioctl, DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(VC4_GET_TILING, vc4_get_tiling_ioctl, DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(VC4_LABEL_BO, vc4_label_bo_ioctl, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(VC4_GEM_MADVISE, vc4_gem_madvise_ioctl, DRM_RENDER_ALLOW), }; static struct drm_driver vc4_drm_driver = { @@ -166,7 +174,7 @@ static struct drm_driver vc4_drm_driver = { .gem_create_object = vc4_create_object, .gem_free_object_unlocked = vc4_free_object, - .gem_vm_ops = &drm_gem_cma_vm_ops, + .gem_vm_ops = &vc4_vm_ops, .prime_handle_to_fd = drm_gem_prime_handle_to_fd, .prime_fd_to_handle = drm_gem_prime_fd_to_handle, diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 87f2d8e5c134..9c0d380c96f2 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -74,6 +74,19 @@ struct vc4_dev { /* Protects bo_cache and bo_labels. */ struct mutex bo_lock; + /* Purgeable BO pool. All BOs in this pool can have their memory + * reclaimed if the driver is unable to allocate new BOs. We also + * keep stats related to the purge mechanism here. + */ + struct { + struct list_head list; + unsigned int num; + size_t size; + unsigned int purged_num; + size_t purged_size; + struct mutex lock; + } purgeable; + uint64_t dma_fence_context; /* Sequence number for the last job queued in bin_job_list. @@ -192,6 +205,16 @@ struct vc4_bo { * for user-allocated labels. */ int label; + + /* Count the number of active users. This is needed to determine + * whether we can move the BO to the purgeable list or not (when the BO + * is used by the GPU or the display engine we can't purge it). + */ + refcount_t usecnt; + + /* Store purgeable/purged state here */ + u32 madv; + struct mutex madv_lock; }; static inline struct vc4_bo * @@ -503,6 +526,7 @@ int vc4_get_hang_state_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int vc4_label_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +int vc4_fault(struct vm_fault *vmf); int vc4_mmap(struct file *filp, struct vm_area_struct *vma); struct reservation_object *vc4_prime_res_obj(struct drm_gem_object *obj); int vc4_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma); @@ -513,6 +537,10 @@ void *vc4_prime_vmap(struct drm_gem_object *obj); int vc4_bo_cache_init(struct drm_device *dev); void vc4_bo_cache_destroy(struct drm_device *dev); int vc4_bo_stats_debugfs(struct seq_file *m, void *arg); +int vc4_bo_inc_usecnt(struct vc4_bo *bo); +void vc4_bo_dec_usecnt(struct vc4_bo *bo); +void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo); +void vc4_bo_remove_from_purgeable_pool(struct vc4_bo *bo); /* vc4_crtc.c */ extern struct platform_driver vc4_crtc_driver; @@ -557,6 +585,8 @@ void vc4_job_handle_completed(struct vc4_dev *vc4); int vc4_queue_seqno_cb(struct drm_device *dev, struct vc4_seqno_cb *cb, uint64_t seqno, void (*func)(struct vc4_seqno_cb *cb)); +int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); /* vc4_hdmi.c */ extern struct platform_driver vc4_hdmi_driver; diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index d0c6bfb68c4e..e00ac2f3a264 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -188,11 +188,22 @@ vc4_save_hang_state(struct drm_device *dev) continue; for (j = 0; j < exec[i]->bo_count; j++) { + bo = to_vc4_bo(&exec[i]->bo[j]->base); + + /* Retain BOs just in case they were marked purgeable. + * This prevents the BO from being purged before + * someone had a chance to dump the hang state. + */ + WARN_ON(!refcount_read(&bo->usecnt)); + refcount_inc(&bo->usecnt); drm_gem_object_get(&exec[i]->bo[j]->base); kernel_state->bo[j + prev_idx] = &exec[i]->bo[j]->base; } list_for_each_entry(bo, &exec[i]->unref_list, unref_head) { + /* No need to retain BOs coming from the ->unref_list + * because they are naturally unpurgeable. + */ drm_gem_object_get(&bo->base.base); kernel_state->bo[j + prev_idx] = &bo->base.base; j++; @@ -233,6 +244,26 @@ vc4_save_hang_state(struct drm_device *dev) state->fdbgs = V3D_READ(V3D_FDBGS); state->errstat = V3D_READ(V3D_ERRSTAT); + /* We need to turn purgeable BOs into unpurgeable ones so that + * userspace has a chance to dump the hang state before the kernel + * decides to purge those BOs. + * Note that BO consistency at dump time cannot be guaranteed. For + * example, if the owner of these BOs decides to re-use them or mark + * them purgeable again there's nothing we can do to prevent it. + */ + for (i = 0; i < kernel_state->user_state.bo_count; i++) { + struct vc4_bo *bo = to_vc4_bo(kernel_state->bo[i]); + + if (bo->madv == __VC4_MADV_NOTSUPP) + continue; + + mutex_lock(&bo->madv_lock); + if (!WARN_ON(bo->madv == __VC4_MADV_PURGED)) + bo->madv = VC4_MADV_WILLNEED; + refcount_dec(&bo->usecnt); + mutex_unlock(&bo->madv_lock); + } + spin_lock_irqsave(&vc4->job_lock, irqflags); if (vc4->hang_state) { spin_unlock_irqrestore(&vc4->job_lock, irqflags); @@ -639,9 +670,6 @@ vc4_queue_submit(struct drm_device *dev, struct vc4_exec_info *exec, * The command validator needs to reference BOs by their index within * the submitted job's BO list. This does the validation of the job's * BO list and reference counting for the lifetime of the job. - * - * Note that this function doesn't need to unreference the BOs on - * failure, because that will happen at vc4_complete_exec() time. */ static int vc4_cl_lookup_bos(struct drm_device *dev, @@ -693,16 +721,47 @@ vc4_cl_lookup_bos(struct drm_device *dev, DRM_DEBUG("Failed to look up GEM BO %d: %d\n", i, handles[i]); ret = -EINVAL; - spin_unlock(&file_priv->table_lock); - goto fail; + break; } + drm_gem_object_get(bo); exec->bo[i] = (struct drm_gem_cma_object *)bo; } spin_unlock(&file_priv->table_lock); + if (ret) + goto fail_put_bo; + + for (i = 0; i < exec->bo_count; i++) { + ret = vc4_bo_inc_usecnt(to_vc4_bo(&exec->bo[i]->base)); + if (ret) + goto fail_dec_usecnt; + } + + kvfree(handles); + return 0; + +fail_dec_usecnt: + /* Decrease usecnt on acquired objects. + * We cannot rely on vc4_complete_exec() to release resources here, + * because vc4_complete_exec() has no information about which BO has + * had its ->usecnt incremented. + * To make things easier we just free everything explicitly and set + * exec->bo to NULL so that vc4_complete_exec() skips the 'BO release' + * step. + */ + for (i-- ; i >= 0; i--) + vc4_bo_dec_usecnt(to_vc4_bo(&exec->bo[i]->base)); + +fail_put_bo: + /* Release any reference to acquired objects. */ + for (i = 0; i < exec->bo_count && exec->bo[i]; i++) + drm_gem_object_put_unlocked(&exec->bo[i]->base); + fail: kvfree(handles); + kvfree(exec->bo); + exec->bo = NULL; return ret; } @@ -833,8 +892,12 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec) dma_fence_signal(exec->fence); if (exec->bo) { - for (i = 0; i < exec->bo_count; i++) + for (i = 0; i < exec->bo_count; i++) { + struct vc4_bo *bo = to_vc4_bo(&exec->bo[i]->base); + + vc4_bo_dec_usecnt(bo); drm_gem_object_put_unlocked(&exec->bo[i]->base); + } kvfree(exec->bo); } @@ -1098,6 +1161,9 @@ vc4_gem_init(struct drm_device *dev) INIT_WORK(&vc4->job_done_work, vc4_job_done_work); mutex_init(&vc4->power_lock); + + INIT_LIST_HEAD(&vc4->purgeable.list); + mutex_init(&vc4->purgeable.lock); } void @@ -1121,3 +1187,81 @@ vc4_gem_destroy(struct drm_device *dev) if (vc4->hang_state) vc4_free_hang_state(dev, vc4->hang_state); } + +int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_vc4_gem_madvise *args = data; + struct drm_gem_object *gem_obj; + struct vc4_bo *bo; + int ret; + + switch (args->madv) { + case VC4_MADV_DONTNEED: + case VC4_MADV_WILLNEED: + break; + default: + return -EINVAL; + } + + if (args->pad != 0) + return -EINVAL; + + gem_obj = drm_gem_object_lookup(file_priv, args->handle); + if (!gem_obj) { + DRM_DEBUG("Failed to look up GEM BO %d\n", args->handle); + return -ENOENT; + } + + bo = to_vc4_bo(gem_obj); + + /* Only BOs exposed to userspace can be purged. */ + if (bo->madv == __VC4_MADV_NOTSUPP) { + DRM_DEBUG("madvise not supported on this BO\n"); + ret = -EINVAL; + goto out_put_gem; + } + + /* Not sure it's safe to purge imported BOs. Let's just assume it's + * not until proven otherwise. + */ + if (gem_obj->import_attach) { + DRM_DEBUG("madvise not supported on imported BOs\n"); + ret = -EINVAL; + goto out_put_gem; + } + + mutex_lock(&bo->madv_lock); + + if (args->madv == VC4_MADV_DONTNEED && bo->madv == VC4_MADV_WILLNEED && + !refcount_read(&bo->usecnt)) { + /* If the BO is about to be marked as purgeable, is not used + * and is not already purgeable or purged, add it to the + * purgeable list. + */ + vc4_bo_add_to_purgeable_pool(bo); + } else if (args->madv == VC4_MADV_WILLNEED && + bo->madv == VC4_MADV_DONTNEED && + !refcount_read(&bo->usecnt)) { + /* The BO has not been purged yet, just remove it from + * the purgeable list. + */ + vc4_bo_remove_from_purgeable_pool(bo); + } + + /* Save the purged state. */ + args->retained = bo->madv != __VC4_MADV_PURGED; + + /* Update internal madv state only if the bo was not purged. */ + if (bo->madv != __VC4_MADV_PURGED) + bo->madv = args->madv; + + mutex_unlock(&bo->madv_lock); + + ret = 0; + +out_put_gem: + drm_gem_object_put_unlocked(gem_obj); + + return ret; +} diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 3a767a038f72..423a23ed8fc2 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -23,6 +23,7 @@ #include #include +#include "uapi/drm/vc4_drm.h" #include "vc4_drv.h" #include "vc4_regs.h" @@ -774,21 +775,40 @@ static int vc4_prepare_fb(struct drm_plane *plane, { struct vc4_bo *bo; struct dma_fence *fence; + int ret; if ((plane->state->fb == state->fb) || !state->fb) return 0; bo = to_vc4_bo(&drm_fb_cma_get_gem_obj(state->fb, 0)->base); + + ret = vc4_bo_inc_usecnt(bo); + if (ret) + return ret; + fence = reservation_object_get_excl_rcu(bo->resv); drm_atomic_set_fence_for_plane(state, fence); return 0; } +static void vc4_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct vc4_bo *bo; + + if (plane->state->fb == state->fb || !state->fb) + return; + + bo = to_vc4_bo(&drm_fb_cma_get_gem_obj(state->fb, 0)->base); + vc4_bo_dec_usecnt(bo); +} + static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = { .atomic_check = vc4_plane_atomic_check, .atomic_update = vc4_plane_atomic_update, .prepare_fb = vc4_prepare_fb, + .cleanup_fb = vc4_cleanup_fb, }; static void vc4_plane_destroy(struct drm_plane *plane) diff --git a/include/uapi/drm/vc4_drm.h b/include/uapi/drm/vc4_drm.h index afae87004963..52263b575bdc 100644 --- a/include/uapi/drm/vc4_drm.h +++ b/include/uapi/drm/vc4_drm.h @@ -41,6 +41,7 @@ extern "C" { #define DRM_VC4_SET_TILING 0x08 #define DRM_VC4_GET_TILING 0x09 #define DRM_VC4_LABEL_BO 0x0a +#define DRM_VC4_GEM_MADVISE 0x0b #define DRM_IOCTL_VC4_SUBMIT_CL DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_SUBMIT_CL, struct drm_vc4_submit_cl) #define DRM_IOCTL_VC4_WAIT_SEQNO DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_WAIT_SEQNO, struct drm_vc4_wait_seqno) @@ -53,6 +54,7 @@ extern "C" { #define DRM_IOCTL_VC4_SET_TILING DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_SET_TILING, struct drm_vc4_set_tiling) #define DRM_IOCTL_VC4_GET_TILING DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_GET_TILING, struct drm_vc4_get_tiling) #define DRM_IOCTL_VC4_LABEL_BO DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_LABEL_BO, struct drm_vc4_label_bo) +#define DRM_IOCTL_VC4_GEM_MADVISE DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_GEM_MADVISE, struct drm_vc4_gem_madvise) struct drm_vc4_submit_rcl_surface { __u32 hindex; /* Handle index, or ~0 if not present. */ @@ -305,6 +307,7 @@ struct drm_vc4_get_hang_state { #define DRM_VC4_PARAM_SUPPORTS_ETC1 4 #define DRM_VC4_PARAM_SUPPORTS_THREADED_FS 5 #define DRM_VC4_PARAM_SUPPORTS_FIXED_RCL_ORDER 6 +#define DRM_VC4_PARAM_SUPPORTS_MADVISE 7 struct drm_vc4_get_param { __u32 param; @@ -333,6 +336,22 @@ struct drm_vc4_label_bo { __u64 name; }; +/* + * States prefixed with '__' are internal states and cannot be passed to the + * DRM_IOCTL_VC4_GEM_MADVISE ioctl. + */ +#define VC4_MADV_WILLNEED 0 +#define VC4_MADV_DONTNEED 1 +#define __VC4_MADV_PURGED 2 +#define __VC4_MADV_NOTSUPP 3 + +struct drm_vc4_gem_madvise { + __u32 handle; + __u32 madv; + __u32 retained; + __u32 pad; +}; + #if defined(__cplusplus) } #endif From af0c8c10564aac5b6d67308129ec09c4ad5db476 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Fri, 13 Oct 2017 17:12:55 -0700 Subject: [PATCH 32/32] drm/vc4: Fix sleeps during the IRQ handler for DSI transactions. VC4's DSI1 has a bug where the AXI connection is broken for 32-bit writes from the CPU, so we use the DMA engine to DMA 32-bit values into registers instead. That sleeps, so we can't do it from the top half. As a solution, use an interrupt thread so that all our writes happen when sleeping is is allowed. v2: Use IRQF_ONESHOT (suggested by Boris) v3: Style nitpicks. Signed-off-by: Eric Anholt Link: https://patchwork.freedesktop.org/patch/msgid/20171014001255.32005-1-eric@anholt.net Reviewed-by: Boris Brezillon (v2) --- drivers/gpu/drm/vc4/vc4_dsi.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 554605af344e..94085f8bcd68 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1360,6 +1360,27 @@ static void dsi_handle_error(struct vc4_dsi *dsi, *ret = IRQ_HANDLED; } +/* + * Initial handler for port 1 where we need the reg_dma workaround. + * The register DMA writes sleep, so we can't do it in the top half. + * Instead we use IRQF_ONESHOT so that the IRQ gets disabled in the + * parent interrupt contrller until our interrupt thread is done. + */ +static irqreturn_t vc4_dsi_irq_defer_to_thread_handler(int irq, void *data) +{ + struct vc4_dsi *dsi = data; + u32 stat = DSI_PORT_READ(INT_STAT); + + if (!stat) + return IRQ_NONE; + + return IRQ_WAKE_THREAD; +} + +/* + * Normal IRQ handler for port 0, or the threaded IRQ handler for port + * 1 where we need the reg_dma workaround. + */ static irqreturn_t vc4_dsi_irq_handler(int irq, void *data) { struct vc4_dsi *dsi = data; @@ -1539,8 +1560,15 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) /* Clear any existing interrupt state. */ DSI_PORT_WRITE(INT_STAT, DSI_PORT_READ(INT_STAT)); - ret = devm_request_irq(dev, platform_get_irq(pdev, 0), - vc4_dsi_irq_handler, 0, "vc4 dsi", dsi); + if (dsi->reg_dma_mem) + ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), + vc4_dsi_irq_defer_to_thread_handler, + vc4_dsi_irq_handler, + IRQF_ONESHOT, + "vc4 dsi", dsi); + else + ret = devm_request_irq(dev, platform_get_irq(pdev, 0), + vc4_dsi_irq_handler, 0, "vc4 dsi", dsi); if (ret) { if (ret != -EPROBE_DEFER) dev_err(dev, "Failed to get interrupt: %d\n", ret);