diff options
Diffstat (limited to 'drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c')
-rw-r--r-- | drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c new file mode 100644 index 000000000..6e7aac621 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RZ/G2L Display Unit CRTCs + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_crtc.c + */ + +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_vblank.h> + +#include "rzg2l_du_crtc.h" +#include "rzg2l_du_drv.h" +#include "rzg2l_du_encoder.h" +#include "rzg2l_du_kms.h" +#include "rzg2l_du_vsp.h" + +#define DU_MCR0 0x00 +#define DU_MCR0_DI_EN BIT(8) + +#define DU_DITR0 0x10 +#define DU_DITR0_DEMD_HIGH (BIT(8) | BIT(9)) +#define DU_DITR0_VSPOL BIT(16) +#define DU_DITR0_HSPOL BIT(17) + +#define DU_DITR1 0x14 +#define DU_DITR1_VSA(x) ((x) << 0) +#define DU_DITR1_VACTIVE(x) ((x) << 16) + +#define DU_DITR2 0x18 +#define DU_DITR2_VBP(x) ((x) << 0) +#define DU_DITR2_VFP(x) ((x) << 16) + +#define DU_DITR3 0x1c +#define DU_DITR3_HSA(x) ((x) << 0) +#define DU_DITR3_HACTIVE(x) ((x) << 16) + +#define DU_DITR4 0x20 +#define DU_DITR4_HBP(x) ((x) << 0) +#define DU_DITR4_HFP(x) ((x) << 16) + +#define DU_MCR1 0x40 +#define DU_MCR1_PB_AUTOCLR BIT(16) + +#define DU_PBCR0 0x4c +#define DU_PBCR0_PB_DEP(x) ((x) << 0) + +/* ----------------------------------------------------------------------------- + * Hardware Setup + */ + +static void rzg2l_du_crtc_set_display_timing(struct rzg2l_du_crtc *rcrtc) +{ + const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode; + unsigned long mode_clock = mode->clock * 1000; + u32 ditr0, ditr1, ditr2, ditr3, ditr4, pbcr0; + struct rzg2l_du_device *rcdu = rcrtc->dev; + + clk_prepare_enable(rcrtc->rzg2l_clocks.dclk); + clk_set_rate(rcrtc->rzg2l_clocks.dclk, mode_clock); + + ditr0 = (DU_DITR0_DEMD_HIGH + | ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? DU_DITR0_VSPOL : 0) + | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? DU_DITR0_HSPOL : 0)); + + ditr1 = DU_DITR1_VSA(mode->vsync_end - mode->vsync_start) + | DU_DITR1_VACTIVE(mode->vdisplay); + + ditr2 = DU_DITR2_VBP(mode->vtotal - mode->vsync_end) + | DU_DITR2_VFP(mode->vsync_start - mode->vdisplay); + + ditr3 = DU_DITR3_HSA(mode->hsync_end - mode->hsync_start) + | DU_DITR3_HACTIVE(mode->hdisplay); + + ditr4 = DU_DITR4_HBP(mode->htotal - mode->hsync_end) + | DU_DITR4_HFP(mode->hsync_start - mode->hdisplay); + + pbcr0 = DU_PBCR0_PB_DEP(0x1f); + + writel(ditr0, rcdu->mmio + DU_DITR0); + writel(ditr1, rcdu->mmio + DU_DITR1); + writel(ditr2, rcdu->mmio + DU_DITR2); + writel(ditr3, rcdu->mmio + DU_DITR3); + writel(ditr4, rcdu->mmio + DU_DITR4); + writel(pbcr0, rcdu->mmio + DU_PBCR0); + + /* Enable auto clear */ + writel(DU_MCR1_PB_AUTOCLR, rcdu->mmio + DU_MCR1); +} + +/* ----------------------------------------------------------------------------- + * Page Flip + */ + +void rzg2l_du_crtc_finish_page_flip(struct rzg2l_du_crtc *rcrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + rcrtc->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (!event) + return; + + spin_lock_irqsave(&dev->event_lock, flags); + drm_crtc_send_vblank_event(&rcrtc->crtc, event); + wake_up(&rcrtc->flip_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_crtc_vblank_put(&rcrtc->crtc); +} + +static bool rzg2l_du_crtc_page_flip_pending(struct rzg2l_du_crtc *rcrtc) +{ + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + bool pending; + + spin_lock_irqsave(&dev->event_lock, flags); + pending = rcrtc->event; + spin_unlock_irqrestore(&dev->event_lock, flags); + + return pending; +} + +static void rzg2l_du_crtc_wait_page_flip(struct rzg2l_du_crtc *rcrtc) +{ + struct rzg2l_du_device *rcdu = rcrtc->dev; + + if (wait_event_timeout(rcrtc->flip_wait, + !rzg2l_du_crtc_page_flip_pending(rcrtc), + msecs_to_jiffies(50))) + return; + + dev_warn(rcdu->dev, "page flip timeout\n"); + + rzg2l_du_crtc_finish_page_flip(rcrtc); +} + +/* ----------------------------------------------------------------------------- + * Start/Stop and Suspend/Resume + */ + +static void rzg2l_du_crtc_setup(struct rzg2l_du_crtc *rcrtc) +{ + /* Configure display timings and output routing */ + rzg2l_du_crtc_set_display_timing(rcrtc); + + /* Enable the VSP compositor. */ + rzg2l_du_vsp_enable(rcrtc); + + /* Turn vertical blanking interrupt reporting on. */ + drm_crtc_vblank_on(&rcrtc->crtc); +} + +static int rzg2l_du_crtc_get(struct rzg2l_du_crtc *rcrtc) +{ + int ret; + + /* + * Guard against double-get, as the function is called from both the + * .atomic_enable() and .atomic_flush() handlers. + */ + if (rcrtc->initialized) + return 0; + + ret = clk_prepare_enable(rcrtc->rzg2l_clocks.aclk); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(rcrtc->rzg2l_clocks.pclk); + if (ret < 0) + goto error_bus_clock; + + ret = reset_control_deassert(rcrtc->rstc); + if (ret < 0) + goto error_peri_clock; + + rzg2l_du_crtc_setup(rcrtc); + rcrtc->initialized = true; + + return 0; + +error_peri_clock: + clk_disable_unprepare(rcrtc->rzg2l_clocks.pclk); +error_bus_clock: + clk_disable_unprepare(rcrtc->rzg2l_clocks.aclk); + return ret; +} + +static void rzg2l_du_crtc_put(struct rzg2l_du_crtc *rcrtc) +{ + clk_disable_unprepare(rcrtc->rzg2l_clocks.dclk); + reset_control_assert(rcrtc->rstc); + clk_disable_unprepare(rcrtc->rzg2l_clocks.pclk); + clk_disable_unprepare(rcrtc->rzg2l_clocks.aclk); + + rcrtc->initialized = false; +} + +static void rzg2l_du_start_stop(struct rzg2l_du_crtc *rcrtc, bool start) +{ + struct rzg2l_du_device *rcdu = rcrtc->dev; + + writel(start ? DU_MCR0_DI_EN : 0, rcdu->mmio + DU_MCR0); +} + +static void rzg2l_du_crtc_start(struct rzg2l_du_crtc *rcrtc) +{ + rzg2l_du_start_stop(rcrtc, true); +} + +static void rzg2l_du_crtc_stop(struct rzg2l_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + + /* + * Disable vertical blanking interrupt reporting. We first need to wait + * for page flip completion before stopping the CRTC as userspace + * expects page flips to eventually complete. + */ + rzg2l_du_crtc_wait_page_flip(rcrtc); + drm_crtc_vblank_off(crtc); + + /* Disable the VSP compositor. */ + rzg2l_du_vsp_disable(rcrtc); + + rzg2l_du_start_stop(rcrtc, false); +} + +/* ----------------------------------------------------------------------------- + * CRTC Functions + */ + +static void rzg2l_du_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rzg2l_du_crtc_get(rcrtc); + + rzg2l_du_crtc_start(rcrtc); +} + +static void rzg2l_du_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rzg2l_du_crtc_stop(rcrtc); + rzg2l_du_crtc_put(rcrtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void rzg2l_du_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + WARN_ON(!crtc->state->enable); + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + rcrtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + rzg2l_du_vsp_atomic_flush(rcrtc); +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .atomic_flush = rzg2l_du_crtc_atomic_flush, + .atomic_enable = rzg2l_du_crtc_atomic_enable, + .atomic_disable = rzg2l_du_crtc_atomic_disable, +}; + +static struct drm_crtc_state * +rzg2l_du_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc_state *state; + struct rzg2l_du_crtc_state *copy; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = to_rzg2l_crtc_state(crtc->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, ©->state); + + return ©->state; +} + +static void rzg2l_du_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_rzg2l_crtc_state(state)); +} + +static void rzg2l_du_crtc_reset(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc_state *state; + + if (crtc->state) { + rzg2l_du_crtc_atomic_destroy_state(crtc, crtc->state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + __drm_atomic_helper_crtc_reset(crtc, &state->state); +} + +static int rzg2l_du_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rcrtc->vblank_enable = true; + + return 0; +} + +static void rzg2l_du_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rcrtc->vblank_enable = false; +} + +static const struct drm_crtc_funcs crtc_funcs_rz = { + .reset = rzg2l_du_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = rzg2l_du_crtc_atomic_duplicate_state, + .atomic_destroy_state = rzg2l_du_crtc_atomic_destroy_state, + .enable_vblank = rzg2l_du_crtc_enable_vblank, + .disable_vblank = rzg2l_du_crtc_disable_vblank, +}; + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int rzg2l_du_crtc_create(struct rzg2l_du_device *rcdu) +{ + struct rzg2l_du_crtc *rcrtc = &rcdu->crtcs[0]; + struct drm_crtc *crtc = &rcrtc->crtc; + struct drm_plane *primary; + int ret; + + rcrtc->rstc = devm_reset_control_get_shared(rcdu->dev, NULL); + if (IS_ERR(rcrtc->rstc)) { + dev_err(rcdu->dev, "can't get cpg reset\n"); + return PTR_ERR(rcrtc->rstc); + } + + rcrtc->rzg2l_clocks.aclk = devm_clk_get(rcdu->dev, "aclk"); + if (IS_ERR(rcrtc->rzg2l_clocks.aclk)) { + dev_err(rcdu->dev, "no axi clock for DU\n"); + return PTR_ERR(rcrtc->rzg2l_clocks.aclk); + } + + rcrtc->rzg2l_clocks.pclk = devm_clk_get(rcdu->dev, "pclk"); + if (IS_ERR(rcrtc->rzg2l_clocks.pclk)) { + dev_err(rcdu->dev, "no peripheral clock for DU\n"); + return PTR_ERR(rcrtc->rzg2l_clocks.pclk); + } + + rcrtc->rzg2l_clocks.dclk = devm_clk_get(rcdu->dev, "vclk"); + if (IS_ERR(rcrtc->rzg2l_clocks.dclk)) { + dev_err(rcdu->dev, "no video clock for DU\n"); + return PTR_ERR(rcrtc->rzg2l_clocks.dclk); + } + + init_waitqueue_head(&rcrtc->flip_wait); + rcrtc->dev = rcdu; + + primary = rzg2l_du_vsp_get_drm_plane(rcrtc, rcrtc->vsp_pipe); + if (IS_ERR(primary)) + return PTR_ERR(primary); + + ret = drmm_crtc_init_with_planes(&rcdu->ddev, crtc, primary, NULL, + &crtc_funcs_rz, NULL); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + return 0; +} |