mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-21 17:01:57 +07:00
0bc69592ab
In general, PLL has VCO (= Voltage controlled oscillator), one of the very important electronic feature called as "jitter" is related to this VCO. In academic generalism, VCO should be maximum to be more small jitter. In high frequency clock, jitter will be large impact. Thus, selecting Hi VCO is general theory. fin fvco fout fclkout in --> [1/M] --> |PD| -> [LPF] -> [VCO] -> [1/P] -+-> [1/FDPLL] -> out +-> | | | | | +-----------------[1/N]<-------------+ fclkout = fvco / P / FDPLL -- (1) In PD, it will loop until fin/M = fvco/P/N fvco = fin * P * N / M -- (2) (1) + (2) indicates fclkout = fin * N / M / FDPLL In this device, N = (n + 1), M = (m + 1), P = 2, FDPLL = (fdpll + 1). fclkout = fin * (n + 1) / (m + 1) / (fdpll + 1) This is the datasheet formula. One note here is that it should be 2kHz < fvco < 4096MHz To be smaller jitter, fvco should be maximum, in other words, N as large as possible, M as small as possible driver should select. Here, basically M=1. This patch do it. Reported-by: HIROSHI INOSE <hiroshi.inose.rb@renesas.com> Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> [Small clarifications in comments, renamed finnm to fout] Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
858 lines
23 KiB
C
858 lines
23 KiB
C
/*
|
|
* rcar_du_crtc.c -- R-Car Display Unit CRTCs
|
|
*
|
|
* Copyright (C) 2013-2015 Renesas Electronics Corporation
|
|
*
|
|
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 <linux/clk.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/sys_soc.h>
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_fb_cma_helper.h>
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#include <drm/drm_plane_helper.h>
|
|
|
|
#include "rcar_du_crtc.h"
|
|
#include "rcar_du_drv.h"
|
|
#include "rcar_du_kms.h"
|
|
#include "rcar_du_plane.h"
|
|
#include "rcar_du_regs.h"
|
|
#include "rcar_du_vsp.h"
|
|
|
|
static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
|
|
return rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
|
|
}
|
|
|
|
static void rcar_du_crtc_write(struct rcar_du_crtc *rcrtc, u32 reg, u32 data)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
|
|
rcar_du_write(rcdu, rcrtc->mmio_offset + reg, data);
|
|
}
|
|
|
|
static void rcar_du_crtc_clr(struct rcar_du_crtc *rcrtc, u32 reg, u32 clr)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
|
|
rcar_du_write(rcdu, rcrtc->mmio_offset + reg,
|
|
rcar_du_read(rcdu, rcrtc->mmio_offset + reg) & ~clr);
|
|
}
|
|
|
|
static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
|
|
rcar_du_write(rcdu, rcrtc->mmio_offset + reg,
|
|
rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set);
|
|
}
|
|
|
|
static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg,
|
|
u32 clr, u32 set)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
u32 value = rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
|
|
|
|
rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set);
|
|
}
|
|
|
|
static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(rcrtc->clock);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(rcrtc->extclock);
|
|
if (ret < 0)
|
|
goto error_clock;
|
|
|
|
ret = rcar_du_group_get(rcrtc->group);
|
|
if (ret < 0)
|
|
goto error_group;
|
|
|
|
return 0;
|
|
|
|
error_group:
|
|
clk_disable_unprepare(rcrtc->extclock);
|
|
error_clock:
|
|
clk_disable_unprepare(rcrtc->clock);
|
|
return ret;
|
|
}
|
|
|
|
static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
rcar_du_group_put(rcrtc->group);
|
|
|
|
clk_disable_unprepare(rcrtc->extclock);
|
|
clk_disable_unprepare(rcrtc->clock);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Hardware Setup
|
|
*/
|
|
|
|
struct dpll_info {
|
|
unsigned int output;
|
|
unsigned int fdpll;
|
|
unsigned int n;
|
|
unsigned int m;
|
|
};
|
|
|
|
static void rcar_du_dpll_divider(struct rcar_du_crtc *rcrtc,
|
|
struct dpll_info *dpll,
|
|
unsigned long input,
|
|
unsigned long target)
|
|
{
|
|
unsigned long best_diff = (unsigned long)-1;
|
|
unsigned long diff;
|
|
unsigned int fdpll;
|
|
unsigned int m;
|
|
unsigned int n;
|
|
|
|
/*
|
|
* fin fvco fout fclkout
|
|
* in --> [1/M] --> |PD| -> [LPF] -> [VCO] -> [1/P] -+-> [1/FDPLL] -> out
|
|
* +-> | | |
|
|
* | |
|
|
* +---------------- [1/N] <------------+
|
|
*
|
|
* fclkout = fvco / P / FDPLL -- (1)
|
|
*
|
|
* fin/M = fvco/P/N
|
|
*
|
|
* fvco = fin * P * N / M -- (2)
|
|
*
|
|
* (1) + (2) indicates
|
|
*
|
|
* fclkout = fin * N / M / FDPLL
|
|
*
|
|
* NOTES
|
|
* N : (n + 1)
|
|
* M : (m + 1)
|
|
* FDPLL : (fdpll + 1)
|
|
* P : 2
|
|
* 2kHz < fvco < 4096MHz
|
|
*
|
|
* To minimize the jitter,
|
|
* N : as large as possible
|
|
* M : as small as possible
|
|
*/
|
|
for (m = 0; m < 4; m++) {
|
|
for (n = 119; n > 38; n--) {
|
|
/*
|
|
* This code only runs on 64-bit architectures, the
|
|
* unsigned long type can thus be used for 64-bit
|
|
* computation. It will still compile without any
|
|
* warning on 32-bit architectures.
|
|
*
|
|
* To optimize calculations, use fout instead of fvco
|
|
* to verify the VCO frequency constraint.
|
|
*/
|
|
unsigned long fout = input * (n + 1) / (m + 1);
|
|
|
|
if (fout < 1000 || fout > 2048 * 1000 * 1000U)
|
|
continue;
|
|
|
|
for (fdpll = 1; fdpll < 32; fdpll++) {
|
|
unsigned long output;
|
|
|
|
output = fout / (fdpll + 1);
|
|
if (output >= 400 * 1000 * 1000)
|
|
continue;
|
|
|
|
diff = abs((long)output - (long)target);
|
|
if (best_diff > diff) {
|
|
best_diff = diff;
|
|
dpll->n = n;
|
|
dpll->m = m;
|
|
dpll->fdpll = fdpll;
|
|
dpll->output = output;
|
|
}
|
|
|
|
if (diff == 0)
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
dev_dbg(rcrtc->group->dev->dev,
|
|
"output:%u, fdpll:%u, n:%u, m:%u, diff:%lu\n",
|
|
dpll->output, dpll->fdpll, dpll->n, dpll->m,
|
|
best_diff);
|
|
}
|
|
|
|
static const struct soc_device_attribute rcar_du_r8a7795_es1[] = {
|
|
{ .soc_id = "r8a7795", .revision = "ES1.*" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode;
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
unsigned long mode_clock = mode->clock * 1000;
|
|
unsigned long clk;
|
|
u32 value;
|
|
u32 escr;
|
|
u32 div;
|
|
|
|
/*
|
|
* Compute the clock divisor and select the internal or external dot
|
|
* clock based on the requested frequency.
|
|
*/
|
|
clk = clk_get_rate(rcrtc->clock);
|
|
div = DIV_ROUND_CLOSEST(clk, mode_clock);
|
|
div = clamp(div, 1U, 64U) - 1;
|
|
escr = div | ESCR_DCLKSEL_CLKS;
|
|
|
|
if (rcrtc->extclock) {
|
|
struct dpll_info dpll = { 0 };
|
|
unsigned long extclk;
|
|
unsigned long extrate;
|
|
unsigned long rate;
|
|
u32 extdiv;
|
|
|
|
extclk = clk_get_rate(rcrtc->extclock);
|
|
if (rcdu->info->dpll_ch & (1 << rcrtc->index)) {
|
|
unsigned long target = mode_clock;
|
|
|
|
/*
|
|
* The H3 ES1.x exhibits dot clock duty cycle stability
|
|
* issues. We can work around them by configuring the
|
|
* DPLL to twice the desired frequency, coupled with a
|
|
* /2 post-divider. This isn't needed on other SoCs and
|
|
* breaks HDMI output on M3-W for a currently unknown
|
|
* reason, so restrict the workaround to H3 ES1.x.
|
|
*/
|
|
if (soc_device_match(rcar_du_r8a7795_es1))
|
|
target *= 2;
|
|
|
|
rcar_du_dpll_divider(rcrtc, &dpll, extclk, target);
|
|
extclk = dpll.output;
|
|
}
|
|
|
|
extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock);
|
|
extdiv = clamp(extdiv, 1U, 64U) - 1;
|
|
|
|
rate = clk / (div + 1);
|
|
extrate = extclk / (extdiv + 1);
|
|
|
|
if (abs((long)extrate - (long)mode_clock) <
|
|
abs((long)rate - (long)mode_clock)) {
|
|
|
|
if (rcdu->info->dpll_ch & (1 << rcrtc->index)) {
|
|
u32 dpllcr = DPLLCR_CODE | DPLLCR_CLKE
|
|
| DPLLCR_FDPLL(dpll.fdpll)
|
|
| DPLLCR_N(dpll.n) | DPLLCR_M(dpll.m)
|
|
| DPLLCR_STBY;
|
|
|
|
if (rcrtc->index == 1)
|
|
dpllcr |= DPLLCR_PLCS1
|
|
| DPLLCR_INCS_DOTCLKIN1;
|
|
else
|
|
dpllcr |= DPLLCR_PLCS0
|
|
| DPLLCR_INCS_DOTCLKIN0;
|
|
|
|
rcar_du_group_write(rcrtc->group, DPLLCR,
|
|
dpllcr);
|
|
}
|
|
|
|
escr = ESCR_DCLKSEL_DCLKIN | extdiv;
|
|
}
|
|
|
|
dev_dbg(rcrtc->group->dev->dev,
|
|
"mode clock %lu extrate %lu rate %lu ESCR 0x%08x\n",
|
|
mode_clock, extrate, rate, escr);
|
|
}
|
|
|
|
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? ESCR2 : ESCR,
|
|
escr);
|
|
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? OTAR2 : OTAR, 0);
|
|
|
|
/* Signal polarities */
|
|
value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? DSMR_VSL : 0)
|
|
| ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? DSMR_HSL : 0)
|
|
| DSMR_DIPM_DISP | DSMR_CSPM;
|
|
rcar_du_crtc_write(rcrtc, DSMR, value);
|
|
|
|
/* Display timings */
|
|
rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19);
|
|
rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start +
|
|
mode->hdisplay - 19);
|
|
rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end -
|
|
mode->hsync_start - 1);
|
|
rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1);
|
|
|
|
rcar_du_crtc_write(rcrtc, VDSR, mode->crtc_vtotal -
|
|
mode->crtc_vsync_end - 2);
|
|
rcar_du_crtc_write(rcrtc, VDER, mode->crtc_vtotal -
|
|
mode->crtc_vsync_end +
|
|
mode->crtc_vdisplay - 2);
|
|
rcar_du_crtc_write(rcrtc, VSPR, mode->crtc_vtotal -
|
|
mode->crtc_vsync_end +
|
|
mode->crtc_vsync_start - 1);
|
|
rcar_du_crtc_write(rcrtc, VCR, mode->crtc_vtotal - 1);
|
|
|
|
rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start - 1);
|
|
rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay);
|
|
}
|
|
|
|
void rcar_du_crtc_route_output(struct drm_crtc *crtc,
|
|
enum rcar_du_output output)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
|
|
/*
|
|
* Store the route from the CRTC output to the DU output. The DU will be
|
|
* configured when starting the CRTC.
|
|
*/
|
|
rcrtc->outputs |= BIT(output);
|
|
|
|
/*
|
|
* Store RGB routing to DPAD0, the hardware will be configured when
|
|
* starting the CRTC.
|
|
*/
|
|
if (output == RCAR_DU_OUTPUT_DPAD0)
|
|
rcdu->dpad0_source = rcrtc->index;
|
|
}
|
|
|
|
static unsigned int plane_zpos(struct rcar_du_plane *plane)
|
|
{
|
|
return plane->plane.state->normalized_zpos;
|
|
}
|
|
|
|
static const struct rcar_du_format_info *
|
|
plane_format(struct rcar_du_plane *plane)
|
|
{
|
|
return to_rcar_plane_state(plane->plane.state)->format;
|
|
}
|
|
|
|
static void rcar_du_crtc_update_planes(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
struct rcar_du_plane *planes[RCAR_DU_NUM_HW_PLANES];
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
unsigned int num_planes = 0;
|
|
unsigned int dptsr_planes;
|
|
unsigned int hwplanes = 0;
|
|
unsigned int prio = 0;
|
|
unsigned int i;
|
|
u32 dspr = 0;
|
|
|
|
for (i = 0; i < rcrtc->group->num_planes; ++i) {
|
|
struct rcar_du_plane *plane = &rcrtc->group->planes[i];
|
|
unsigned int j;
|
|
|
|
if (plane->plane.state->crtc != &rcrtc->crtc ||
|
|
!plane->plane.state->visible)
|
|
continue;
|
|
|
|
/* Insert the plane in the sorted planes array. */
|
|
for (j = num_planes++; j > 0; --j) {
|
|
if (plane_zpos(planes[j-1]) <= plane_zpos(plane))
|
|
break;
|
|
planes[j] = planes[j-1];
|
|
}
|
|
|
|
planes[j] = plane;
|
|
prio += plane_format(plane)->planes * 4;
|
|
}
|
|
|
|
for (i = 0; i < num_planes; ++i) {
|
|
struct rcar_du_plane *plane = planes[i];
|
|
struct drm_plane_state *state = plane->plane.state;
|
|
unsigned int index = to_rcar_plane_state(state)->hwindex;
|
|
|
|
prio -= 4;
|
|
dspr |= (index + 1) << prio;
|
|
hwplanes |= 1 << index;
|
|
|
|
if (plane_format(plane)->planes == 2) {
|
|
index = (index + 1) % 8;
|
|
|
|
prio -= 4;
|
|
dspr |= (index + 1) << prio;
|
|
hwplanes |= 1 << index;
|
|
}
|
|
}
|
|
|
|
/* If VSP+DU integration is enabled the plane assignment is fixed. */
|
|
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) {
|
|
if (rcdu->info->gen < 3) {
|
|
dspr = (rcrtc->index % 2) + 1;
|
|
hwplanes = 1 << (rcrtc->index % 2);
|
|
} else {
|
|
dspr = (rcrtc->index % 2) ? 3 : 1;
|
|
hwplanes = 1 << ((rcrtc->index % 2) ? 2 : 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update the planes to display timing and dot clock generator
|
|
* associations.
|
|
*
|
|
* Updating the DPTSR register requires restarting the CRTC group,
|
|
* resulting in visible flicker. To mitigate the issue only update the
|
|
* association if needed by enabled planes. Planes being disabled will
|
|
* keep their current association.
|
|
*/
|
|
mutex_lock(&rcrtc->group->lock);
|
|
|
|
dptsr_planes = rcrtc->index % 2 ? rcrtc->group->dptsr_planes | hwplanes
|
|
: rcrtc->group->dptsr_planes & ~hwplanes;
|
|
|
|
if (dptsr_planes != rcrtc->group->dptsr_planes) {
|
|
rcar_du_group_write(rcrtc->group, DPTSR,
|
|
(dptsr_planes << 16) | dptsr_planes);
|
|
rcrtc->group->dptsr_planes = dptsr_planes;
|
|
|
|
if (rcrtc->group->used_crtcs)
|
|
rcar_du_group_restart(rcrtc->group);
|
|
}
|
|
|
|
/* Restart the group if plane sources have changed. */
|
|
if (rcrtc->group->need_restart)
|
|
rcar_du_group_restart(rcrtc->group);
|
|
|
|
mutex_unlock(&rcrtc->group->lock);
|
|
|
|
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR,
|
|
dspr);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Page Flip
|
|
*/
|
|
|
|
void rcar_du_crtc_finish_page_flip(struct rcar_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 == NULL)
|
|
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 rcar_du_crtc_page_flip_pending(struct rcar_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 != NULL;
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
|
return pending;
|
|
}
|
|
|
|
static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
|
|
if (wait_event_timeout(rcrtc->flip_wait,
|
|
!rcar_du_crtc_page_flip_pending(rcrtc),
|
|
msecs_to_jiffies(50)))
|
|
return;
|
|
|
|
dev_warn(rcdu->dev, "page flip timeout\n");
|
|
|
|
rcar_du_crtc_finish_page_flip(rcrtc);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Start/Stop and Suspend/Resume
|
|
*/
|
|
|
|
static void rcar_du_crtc_setup(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
/* Set display off and background to black */
|
|
rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0));
|
|
rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0));
|
|
|
|
/* Configure display timings and output routing */
|
|
rcar_du_crtc_set_display_timing(rcrtc);
|
|
rcar_du_group_set_routing(rcrtc->group);
|
|
|
|
/* Start with all planes disabled. */
|
|
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
|
|
|
|
/* Enable the VSP compositor. */
|
|
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
|
|
rcar_du_vsp_enable(rcrtc);
|
|
|
|
/* Turn vertical blanking interrupt reporting on. */
|
|
drm_crtc_vblank_on(&rcrtc->crtc);
|
|
}
|
|
|
|
static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
bool interlaced;
|
|
|
|
/*
|
|
* Select master sync mode. This enables display operation in master
|
|
* sync mode (with the HSYNC and VSYNC signals configured as outputs and
|
|
* actively driven).
|
|
*/
|
|
interlaced = rcrtc->crtc.mode.flags & DRM_MODE_FLAG_INTERLACE;
|
|
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK | DSYSR_SCM_MASK,
|
|
(interlaced ? DSYSR_SCM_INT_VIDEO : 0) |
|
|
DSYSR_TVM_MASTER);
|
|
|
|
rcar_du_group_start_stop(rcrtc->group, true);
|
|
}
|
|
|
|
static void rcar_du_crtc_disable_planes(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
struct drm_crtc *crtc = &rcrtc->crtc;
|
|
u32 status;
|
|
|
|
/* Make sure vblank interrupts are enabled. */
|
|
drm_crtc_vblank_get(crtc);
|
|
|
|
/*
|
|
* Disable planes and calculate how many vertical blanking interrupts we
|
|
* have to wait for. If a vertical blanking interrupt has been triggered
|
|
* but not processed yet, we don't know whether it occurred before or
|
|
* after the planes got disabled. We thus have to wait for two vblank
|
|
* interrupts in that case.
|
|
*/
|
|
spin_lock_irq(&rcrtc->vblank_lock);
|
|
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
|
|
status = rcar_du_crtc_read(rcrtc, DSSR);
|
|
rcrtc->vblank_count = status & DSSR_VBK ? 2 : 1;
|
|
spin_unlock_irq(&rcrtc->vblank_lock);
|
|
|
|
if (!wait_event_timeout(rcrtc->vblank_wait, rcrtc->vblank_count == 0,
|
|
msecs_to_jiffies(100)))
|
|
dev_warn(rcdu->dev, "vertical blanking timeout\n");
|
|
|
|
drm_crtc_vblank_put(crtc);
|
|
}
|
|
|
|
static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
|
|
{
|
|
struct drm_crtc *crtc = &rcrtc->crtc;
|
|
|
|
/*
|
|
* Disable all planes and wait for the change to take effect. This is
|
|
* required as the plane enable registers are updated on vblank, and no
|
|
* vblank will occur once the CRTC is stopped. Disabling planes when
|
|
* starting the CRTC thus wouldn't be enough as it would start scanning
|
|
* out immediately from old frame buffers until the next vblank.
|
|
*
|
|
* This increases the CRTC stop delay, especially when multiple CRTCs
|
|
* are stopped in one operation as we now wait for one vblank per CRTC.
|
|
* Whether this can be improved needs to be researched.
|
|
*/
|
|
rcar_du_crtc_disable_planes(rcrtc);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
rcar_du_crtc_wait_page_flip(rcrtc);
|
|
drm_crtc_vblank_off(crtc);
|
|
|
|
/* Disable the VSP compositor. */
|
|
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
|
|
rcar_du_vsp_disable(rcrtc);
|
|
|
|
/*
|
|
* Select switch sync mode. This stops display operation and configures
|
|
* the HSYNC and VSYNC signals as inputs.
|
|
*/
|
|
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH);
|
|
|
|
rcar_du_group_start_stop(rcrtc->group, false);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* CRTC Functions
|
|
*/
|
|
|
|
static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_state)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
|
|
/*
|
|
* If the CRTC has already been setup by the .atomic_begin() handler we
|
|
* can skip the setup stage.
|
|
*/
|
|
if (!rcrtc->initialized) {
|
|
rcar_du_crtc_get(rcrtc);
|
|
rcar_du_crtc_setup(rcrtc);
|
|
rcrtc->initialized = true;
|
|
}
|
|
|
|
rcar_du_crtc_start(rcrtc);
|
|
}
|
|
|
|
static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_state)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
|
|
rcar_du_crtc_stop(rcrtc);
|
|
rcar_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);
|
|
|
|
rcrtc->initialized = false;
|
|
rcrtc->outputs = 0;
|
|
}
|
|
|
|
static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_crtc_state)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
|
|
WARN_ON(!crtc->state->enable);
|
|
|
|
/*
|
|
* If a mode set is in progress we can be called with the CRTC disabled.
|
|
* We then need to first setup the CRTC in order to configure planes.
|
|
* The .atomic_enable() handler will notice and skip the CRTC setup.
|
|
*/
|
|
if (!rcrtc->initialized) {
|
|
rcar_du_crtc_get(rcrtc);
|
|
rcar_du_crtc_setup(rcrtc);
|
|
rcrtc->initialized = true;
|
|
}
|
|
|
|
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
|
|
rcar_du_vsp_atomic_begin(rcrtc);
|
|
}
|
|
|
|
static void rcar_du_crtc_atomic_flush(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_crtc_state)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
struct drm_device *dev = rcrtc->crtc.dev;
|
|
unsigned long flags;
|
|
|
|
rcar_du_crtc_update_planes(rcrtc);
|
|
|
|
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);
|
|
}
|
|
|
|
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
|
|
rcar_du_vsp_atomic_flush(rcrtc);
|
|
}
|
|
|
|
static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
|
|
.atomic_begin = rcar_du_crtc_atomic_begin,
|
|
.atomic_flush = rcar_du_crtc_atomic_flush,
|
|
.atomic_enable = rcar_du_crtc_atomic_enable,
|
|
.atomic_disable = rcar_du_crtc_atomic_disable,
|
|
};
|
|
|
|
static int rcar_du_crtc_enable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
|
|
rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL);
|
|
rcar_du_crtc_set(rcrtc, DIER, DIER_VBE);
|
|
rcrtc->vblank_enable = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rcar_du_crtc_disable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
|
|
|
rcar_du_crtc_clr(rcrtc, DIER, DIER_VBE);
|
|
rcrtc->vblank_enable = false;
|
|
}
|
|
|
|
static const struct drm_crtc_funcs crtc_funcs = {
|
|
.reset = drm_atomic_helper_crtc_reset,
|
|
.destroy = drm_crtc_cleanup,
|
|
.set_config = drm_atomic_helper_set_config,
|
|
.page_flip = drm_atomic_helper_page_flip,
|
|
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
|
|
.enable_vblank = rcar_du_crtc_enable_vblank,
|
|
.disable_vblank = rcar_du_crtc_disable_vblank,
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Interrupt Handling
|
|
*/
|
|
|
|
static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
|
|
{
|
|
struct rcar_du_crtc *rcrtc = arg;
|
|
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
u32 status;
|
|
|
|
spin_lock(&rcrtc->vblank_lock);
|
|
|
|
status = rcar_du_crtc_read(rcrtc, DSSR);
|
|
rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
|
|
|
|
if (status & DSSR_VBK) {
|
|
/*
|
|
* Wake up the vblank wait if the counter reaches 0. This must
|
|
* be protected by the vblank_lock to avoid races in
|
|
* rcar_du_crtc_disable_planes().
|
|
*/
|
|
if (rcrtc->vblank_count) {
|
|
if (--rcrtc->vblank_count == 0)
|
|
wake_up(&rcrtc->vblank_wait);
|
|
}
|
|
}
|
|
|
|
spin_unlock(&rcrtc->vblank_lock);
|
|
|
|
if (status & DSSR_VBK) {
|
|
if (rcdu->info->gen < 3) {
|
|
drm_crtc_handle_vblank(&rcrtc->crtc);
|
|
rcar_du_crtc_finish_page_flip(rcrtc);
|
|
}
|
|
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Initialization
|
|
*/
|
|
|
|
int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
|
|
{
|
|
static const unsigned int mmio_offsets[] = {
|
|
DU0_REG_OFFSET, DU1_REG_OFFSET, DU2_REG_OFFSET, DU3_REG_OFFSET
|
|
};
|
|
|
|
struct rcar_du_device *rcdu = rgrp->dev;
|
|
struct platform_device *pdev = to_platform_device(rcdu->dev);
|
|
struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index];
|
|
struct drm_crtc *crtc = &rcrtc->crtc;
|
|
struct drm_plane *primary;
|
|
unsigned int irqflags;
|
|
struct clk *clk;
|
|
char clk_name[9];
|
|
char *name;
|
|
int irq;
|
|
int ret;
|
|
|
|
/* Get the CRTC clock and the optional external clock. */
|
|
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) {
|
|
sprintf(clk_name, "du.%u", index);
|
|
name = clk_name;
|
|
} else {
|
|
name = NULL;
|
|
}
|
|
|
|
rcrtc->clock = devm_clk_get(rcdu->dev, name);
|
|
if (IS_ERR(rcrtc->clock)) {
|
|
dev_err(rcdu->dev, "no clock for CRTC %u\n", index);
|
|
return PTR_ERR(rcrtc->clock);
|
|
}
|
|
|
|
sprintf(clk_name, "dclkin.%u", index);
|
|
clk = devm_clk_get(rcdu->dev, clk_name);
|
|
if (!IS_ERR(clk)) {
|
|
rcrtc->extclock = clk;
|
|
} else if (PTR_ERR(rcrtc->clock) == -EPROBE_DEFER) {
|
|
dev_info(rcdu->dev, "can't get external clock %u\n", index);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
init_waitqueue_head(&rcrtc->flip_wait);
|
|
init_waitqueue_head(&rcrtc->vblank_wait);
|
|
spin_lock_init(&rcrtc->vblank_lock);
|
|
|
|
rcrtc->group = rgrp;
|
|
rcrtc->mmio_offset = mmio_offsets[index];
|
|
rcrtc->index = index;
|
|
|
|
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE))
|
|
primary = &rcrtc->vsp->planes[rcrtc->vsp_pipe].plane;
|
|
else
|
|
primary = &rgrp->planes[index % 2].plane;
|
|
|
|
ret = drm_crtc_init_with_planes(rcdu->ddev, crtc, primary,
|
|
NULL, &crtc_funcs, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
|
|
|
|
/* Start with vertical blanking interrupt reporting disabled. */
|
|
drm_crtc_vblank_off(crtc);
|
|
|
|
/* Register the interrupt handler. */
|
|
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) {
|
|
irq = platform_get_irq(pdev, index);
|
|
irqflags = 0;
|
|
} else {
|
|
irq = platform_get_irq(pdev, 0);
|
|
irqflags = IRQF_SHARED;
|
|
}
|
|
|
|
if (irq < 0) {
|
|
dev_err(rcdu->dev, "no IRQ for CRTC %u\n", index);
|
|
return irq;
|
|
}
|
|
|
|
ret = devm_request_irq(rcdu->dev, irq, rcar_du_crtc_irq, irqflags,
|
|
dev_name(rcdu->dev), rcrtc);
|
|
if (ret < 0) {
|
|
dev_err(rcdu->dev,
|
|
"failed to register IRQ for CRTC %u\n", index);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|