diff options
Diffstat (limited to 'drivers/gpu/drm/exynos/exynos_drm_crtc.c')
| -rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_crtc.c | 443 |
1 files changed, 139 insertions, 304 deletions
diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index 9a35d171a6d3..d19e796c2061 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* exynos_drm_crtc.c * * Copyright (c) 2011 Samsung Electronics Co., Ltd. @@ -5,401 +6,235 @@ * Inki Dae <inki.dae@samsung.com> * Joonyoung Shim <jy0922.shim@samsung.com> * Seung-Woo Kim <sw0312.kim@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. */ -#include <drm/drmP.h> -#include <drm/drm_crtc_helper.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_encoder.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> +#include "exynos_drm_crtc.h" #include "exynos_drm_drv.h" -#include "exynos_drm_encoder.h" #include "exynos_drm_plane.h" -#define to_exynos_crtc(x) container_of(x, struct exynos_drm_crtc,\ - drm_crtc) - -enum exynos_crtc_mode { - CRTC_MODE_NORMAL, /* normal mode */ - CRTC_MODE_BLANK, /* The private plane of crtc is blank */ -}; - -/* - * Exynos specific crtc structure. - * - * @drm_crtc: crtc object. - * @drm_plane: pointer of private plane object for this crtc - * @pipe: a crtc index created at load() with a new crtc object creation - * and the crtc object would be set to private->crtc array - * to get a crtc object corresponding to this pipe from private->crtc - * array when irq interrupt occured. the reason of using this pipe is that - * drm framework doesn't support multiple irq yet. - * we can refer to the crtc to current hardware interrupt occured through - * this pipe value. - * @dpms: store the crtc dpms value - * @mode: store the crtc mode value - */ -struct exynos_drm_crtc { - struct drm_crtc drm_crtc; - struct drm_plane *plane; - unsigned int pipe; - unsigned int dpms; - enum exynos_crtc_mode mode; - wait_queue_head_t pending_flip_queue; - atomic_t pending_flip; -}; - -static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) +static void exynos_drm_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); - - if (exynos_crtc->dpms == mode) { - DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); - return; - } - - if (mode > DRM_MODE_DPMS_ON) { - /* wait for the completion of page flip. */ - wait_event(exynos_crtc->pending_flip_queue, - atomic_read(&exynos_crtc->pending_flip) == 0); - drm_vblank_off(crtc->dev, exynos_crtc->pipe); - } - - exynos_drm_fn_encoder(crtc, &mode, exynos_drm_encoder_crtc_dpms); - exynos_crtc->dpms = mode; -} + if (exynos_crtc->ops->atomic_enable) + exynos_crtc->ops->atomic_enable(exynos_crtc); -static void exynos_drm_crtc_prepare(struct drm_crtc *crtc) -{ - /* drm framework doesn't check NULL. */ + drm_crtc_vblank_on(crtc); } -static void exynos_drm_crtc_commit(struct drm_crtc *crtc) +static void exynos_drm_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON); - exynos_plane_commit(exynos_crtc->plane); - exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_ON); -} + drm_crtc_vblank_off(crtc); -static bool -exynos_drm_crtc_mode_fixup(struct drm_crtc *crtc, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - /* drm framework doesn't check NULL */ - return true; + if (exynos_crtc->ops->atomic_disable) + exynos_crtc->ops->atomic_disable(exynos_crtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event && !crtc->state->active) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); } -static int -exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode, int x, int y, - struct drm_framebuffer *old_fb) +static int exynos_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) { + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - struct drm_plane *plane = exynos_crtc->plane; - unsigned int crtc_w; - unsigned int crtc_h; - int pipe = exynos_crtc->pipe; - int ret; - - /* - * copy the mode data adjusted by mode_fixup() into crtc->mode - * so that hardware can be seet to proper mode. - */ - memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode)); - - crtc_w = crtc->fb->width - x; - crtc_h = crtc->fb->height - y; - - ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h, - x, y, crtc_w, crtc_h); - if (ret) - return ret; - plane->crtc = crtc; - plane->fb = crtc->fb; + if (!crtc_state->enable) + return 0; - exynos_drm_fn_encoder(crtc, &pipe, exynos_drm_encoder_crtc_pipe); + if (exynos_crtc->ops->atomic_check) + return exynos_crtc->ops->atomic_check(exynos_crtc, crtc_state); return 0; } -static int exynos_drm_crtc_mode_set_commit(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb) +static void exynos_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - struct drm_plane *plane = exynos_crtc->plane; - unsigned int crtc_w; - unsigned int crtc_h; - int ret; - - /* when framebuffer changing is requested, crtc's dpms should be on */ - if (exynos_crtc->dpms > DRM_MODE_DPMS_ON) { - DRM_ERROR("failed framebuffer changing request.\n"); - return -EPERM; - } - - crtc_w = crtc->fb->width - x; - crtc_h = crtc->fb->height - y; - ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h, - x, y, crtc_w, crtc_h); - if (ret) - return ret; + if (exynos_crtc->ops->atomic_begin) + exynos_crtc->ops->atomic_begin(exynos_crtc); +} - exynos_drm_crtc_commit(crtc); +static void exynos_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - return 0; + if (exynos_crtc->ops->atomic_flush) + exynos_crtc->ops->atomic_flush(exynos_crtc); } -static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb) +static enum drm_mode_status exynos_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) { - return exynos_drm_crtc_mode_set_commit(crtc, x, y, old_fb); + struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); + + if (exynos_crtc->ops->mode_valid) + return exynos_crtc->ops->mode_valid(exynos_crtc, mode); + + return MODE_OK; } -static void exynos_drm_crtc_disable(struct drm_crtc *crtc) +static bool exynos_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_OFF); - exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); + if (exynos_crtc->ops->mode_fixup) + return exynos_crtc->ops->mode_fixup(exynos_crtc, mode, + adjusted_mode); + + return true; } -static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = { - .dpms = exynos_drm_crtc_dpms, - .prepare = exynos_drm_crtc_prepare, - .commit = exynos_drm_crtc_commit, - .mode_fixup = exynos_drm_crtc_mode_fixup, - .mode_set = exynos_drm_crtc_mode_set, - .mode_set_base = exynos_drm_crtc_mode_set_base, - .disable = exynos_drm_crtc_disable, + +static const struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = { + .mode_valid = exynos_crtc_mode_valid, + .mode_fixup = exynos_crtc_mode_fixup, + .atomic_check = exynos_crtc_atomic_check, + .atomic_begin = exynos_crtc_atomic_begin, + .atomic_flush = exynos_crtc_atomic_flush, + .atomic_enable = exynos_drm_crtc_atomic_enable, + .atomic_disable = exynos_drm_crtc_atomic_disable, }; -static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc, - struct drm_framebuffer *fb, - struct drm_pending_vblank_event *event) +void exynos_crtc_handle_event(struct exynos_drm_crtc *exynos_crtc) { - struct drm_device *dev = crtc->dev; - struct exynos_drm_private *dev_priv = dev->dev_private; - struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - struct drm_framebuffer *old_fb = crtc->fb; - int ret = -EINVAL; + struct drm_crtc *crtc = &exynos_crtc->base; + struct drm_pending_vblank_event *event = crtc->state->event; + unsigned long flags; - /* when the page flip is requested, crtc's dpms should be on */ - if (exynos_crtc->dpms > DRM_MODE_DPMS_ON) { - DRM_ERROR("failed page flip request.\n"); - return -EINVAL; - } + if (!event) + return; + crtc->state->event = NULL; - mutex_lock(&dev->struct_mutex); - - if (event) { - /* - * the pipe from user always is 0 so we can set pipe number - * of current owner to event. - */ - event->pipe = exynos_crtc->pipe; - - ret = drm_vblank_get(dev, exynos_crtc->pipe); - if (ret) { - DRM_DEBUG("failed to acquire vblank counter\n"); - - goto out; - } - - spin_lock_irq(&dev->event_lock); - list_add_tail(&event->base.link, - &dev_priv->pageflip_event_list); - atomic_set(&exynos_crtc->pending_flip, 1); - spin_unlock_irq(&dev->event_lock); - - crtc->fb = fb; - ret = exynos_drm_crtc_mode_set_commit(crtc, crtc->x, crtc->y, - NULL); - if (ret) { - crtc->fb = old_fb; - - spin_lock_irq(&dev->event_lock); - drm_vblank_put(dev, exynos_crtc->pipe); - list_del(&event->base.link); - spin_unlock_irq(&dev->event_lock); - - goto out; - } - } -out: - mutex_unlock(&dev->struct_mutex); - return ret; + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + drm_crtc_arm_vblank_event(crtc, event); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); } static void exynos_drm_crtc_destroy(struct drm_crtc *crtc) { struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - struct exynos_drm_private *private = crtc->dev->dev_private; - - private->crtc[exynos_crtc->pipe] = NULL; drm_crtc_cleanup(crtc); kfree(exynos_crtc); } -static int exynos_drm_crtc_set_property(struct drm_crtc *crtc, - struct drm_property *property, - uint64_t val) +static int exynos_drm_crtc_enable_vblank(struct drm_crtc *crtc) { - struct drm_device *dev = crtc->dev; - struct exynos_drm_private *dev_priv = dev->dev_private; struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - if (property == dev_priv->crtc_mode_property) { - enum exynos_crtc_mode mode = val; - - if (mode == exynos_crtc->mode) - return 0; + if (exynos_crtc->ops->enable_vblank) + return exynos_crtc->ops->enable_vblank(exynos_crtc); - exynos_crtc->mode = mode; - - switch (mode) { - case CRTC_MODE_NORMAL: - exynos_drm_crtc_commit(crtc); - break; - case CRTC_MODE_BLANK: - exynos_plane_dpms(exynos_crtc->plane, - DRM_MODE_DPMS_OFF); - break; - default: - break; - } + return 0; +} - return 0; - } +static void exynos_drm_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - return -EINVAL; + if (exynos_crtc->ops->disable_vblank) + exynos_crtc->ops->disable_vblank(exynos_crtc); } -static struct drm_crtc_funcs exynos_crtc_funcs = { - .set_config = drm_crtc_helper_set_config, - .page_flip = exynos_drm_crtc_page_flip, +static const struct drm_crtc_funcs exynos_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, .destroy = exynos_drm_crtc_destroy, - .set_property = exynos_drm_crtc_set_property, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = exynos_drm_crtc_enable_vblank, + .disable_vblank = exynos_drm_crtc_disable_vblank, }; -static const struct drm_prop_enum_list mode_names[] = { - { CRTC_MODE_NORMAL, "normal" }, - { CRTC_MODE_BLANK, "blank" }, -}; - -static void exynos_drm_crtc_attach_mode_property(struct drm_crtc *crtc) -{ - struct drm_device *dev = crtc->dev; - struct exynos_drm_private *dev_priv = dev->dev_private; - struct drm_property *prop; - - prop = dev_priv->crtc_mode_property; - if (!prop) { - prop = drm_property_create_enum(dev, 0, "mode", mode_names, - ARRAY_SIZE(mode_names)); - if (!prop) - return; - - dev_priv->crtc_mode_property = prop; - } - - drm_object_attach_property(&crtc->base, prop, 0); -} - -int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) +struct exynos_drm_crtc *exynos_drm_crtc_create(struct drm_device *drm_dev, + struct drm_plane *plane, + enum exynos_drm_output_type type, + const struct exynos_drm_crtc_ops *ops, + void *ctx) { struct exynos_drm_crtc *exynos_crtc; - struct exynos_drm_private *private = dev->dev_private; struct drm_crtc *crtc; + int ret; exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL); - if (!exynos_crtc) { - DRM_ERROR("failed to allocate exynos crtc\n"); - return -ENOMEM; - } + if (!exynos_crtc) + return ERR_PTR(-ENOMEM); - exynos_crtc->pipe = nr; - exynos_crtc->dpms = DRM_MODE_DPMS_OFF; - init_waitqueue_head(&exynos_crtc->pending_flip_queue); - atomic_set(&exynos_crtc->pending_flip, 0); - exynos_crtc->plane = exynos_plane_init(dev, 1 << nr, true); - if (!exynos_crtc->plane) { - kfree(exynos_crtc); - return -ENOMEM; - } + exynos_crtc->type = type; + exynos_crtc->ops = ops; + exynos_crtc->ctx = ctx; - crtc = &exynos_crtc->drm_crtc; + crtc = &exynos_crtc->base; - private->crtc[nr] = crtc; + ret = drm_crtc_init_with_planes(drm_dev, crtc, plane, NULL, + &exynos_crtc_funcs, NULL); + if (ret < 0) + goto err_crtc; - drm_crtc_init(dev, crtc, &exynos_crtc_funcs); drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs); - exynos_drm_crtc_attach_mode_property(crtc); + return exynos_crtc; - return 0; +err_crtc: + plane->funcs->destroy(plane); + kfree(exynos_crtc); + return ERR_PTR(ret); } -int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc) +struct exynos_drm_crtc *exynos_drm_crtc_get_by_type(struct drm_device *drm_dev, + enum exynos_drm_output_type out_type) { - struct exynos_drm_private *private = dev->dev_private; - struct exynos_drm_crtc *exynos_crtc = - to_exynos_crtc(private->crtc[crtc]); - - if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) - return -EPERM; + struct drm_crtc *crtc; - exynos_drm_fn_encoder(private->crtc[crtc], &crtc, - exynos_drm_enable_vblank); + drm_for_each_crtc(crtc, drm_dev) + if (to_exynos_crtc(crtc)->type == out_type) + return to_exynos_crtc(crtc); - return 0; + return ERR_PTR(-ENODEV); } -void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc) +int exynos_drm_set_possible_crtcs(struct drm_encoder *encoder, + enum exynos_drm_output_type out_type) { - struct exynos_drm_private *private = dev->dev_private; - struct exynos_drm_crtc *exynos_crtc = - to_exynos_crtc(private->crtc[crtc]); + struct exynos_drm_crtc *crtc = exynos_drm_crtc_get_by_type(encoder->dev, + out_type); - if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) - return; + if (IS_ERR(crtc)) + return PTR_ERR(crtc); + + encoder->possible_crtcs = drm_crtc_mask(&crtc->base); - exynos_drm_fn_encoder(private->crtc[crtc], &crtc, - exynos_drm_disable_vblank); + return 0; } -void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc) +void exynos_drm_crtc_te_handler(struct drm_crtc *crtc) { - struct exynos_drm_private *dev_priv = dev->dev_private; - struct drm_pending_vblank_event *e, *t; - struct drm_crtc *drm_crtc = dev_priv->crtc[crtc]; - struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(drm_crtc); - unsigned long flags; - - spin_lock_irqsave(&dev->event_lock, flags); - - list_for_each_entry_safe(e, t, &dev_priv->pageflip_event_list, - base.link) { - /* if event's pipe isn't same as crtc then ignore it. */ - if (crtc != e->pipe) - continue; - - list_del(&e->base.link); - drm_send_vblank_event(dev, -1, e); - drm_vblank_put(dev, crtc); - atomic_set(&exynos_crtc->pending_flip, 0); - wake_up(&exynos_crtc->pending_flip_queue); - } + struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); - spin_unlock_irqrestore(&dev->event_lock, flags); + if (exynos_crtc->ops->te_handler) + exynos_crtc->ops->te_handler(exynos_crtc); } |
