summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/pl111/pl111_display.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/pl111/pl111_display.c')
-rw-r--r--drivers/gpu/drm/pl111/pl111_display.c115
1 files changed, 104 insertions, 11 deletions
diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c
index 06c4bf7..3106464 100644
--- a/drivers/gpu/drm/pl111/pl111_display.c
+++ b/drivers/gpu/drm/pl111/pl111_display.c
@@ -50,6 +50,41 @@ irqreturn_t pl111_irq(int irq, void *data)
return status;
}
+static enum drm_mode_status
+pl111_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct drm_device *drm = crtc->dev;
+ struct pl111_drm_dev_private *priv = drm->dev_private;
+ u32 cpp = priv->variant->fb_bpp / 8;
+ u64 bw;
+
+ /*
+ * We use the pixelclock to also account for interlaced modes, the
+ * resulting bandwidth is in bytes per second.
+ */
+ bw = mode->clock * 1000; /* In Hz */
+ bw = bw * mode->hdisplay * mode->vdisplay * cpp;
+ bw = div_u64(bw, mode->htotal * mode->vtotal);
+
+ /*
+ * If no bandwidth constraints, anything goes, else
+ * check if we are too fast.
+ */
+ if (priv->memory_bw && (bw > priv->memory_bw)) {
+ DRM_DEBUG_KMS("%d x %d @ %d Hz, %d cpp, bw %llu too fast\n",
+ mode->hdisplay, mode->vdisplay,
+ mode->clock * 1000, cpp, bw);
+
+ return MODE_BAD;
+ }
+ DRM_DEBUG_KMS("%d x %d @ %d Hz, %d cpp, bw %llu bytes/s OK\n",
+ mode->hdisplay, mode->vdisplay,
+ mode->clock * 1000, cpp, bw);
+
+ return MODE_OK;
+}
+
static int pl111_display_check(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *pstate,
struct drm_crtc_state *cstate)
@@ -94,6 +129,7 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
const struct drm_display_mode *mode = &cstate->mode;
struct drm_framebuffer *fb = plane->state->fb;
struct drm_connector *connector = priv->connector;
+ struct drm_bridge *bridge = priv->bridge;
u32 cntl;
u32 ppl, hsw, hfp, hbp;
u32 lpp, vsw, vfp, vbp;
@@ -137,17 +173,46 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
tim2 = readl(priv->regs + CLCD_TIM2);
tim2 &= (TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK);
+ if (priv->variant->broken_clockdivider)
+ tim2 |= TIM2_BCD;
+
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
tim2 |= TIM2_IHS;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
tim2 |= TIM2_IVS;
- if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW)
- tim2 |= TIM2_IOE;
+ if (connector) {
+ if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW)
+ tim2 |= TIM2_IOE;
+
+ if (connector->display_info.bus_flags &
+ DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+ tim2 |= TIM2_IPC;
+ }
- if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
- tim2 |= TIM2_IPC;
+ if (bridge) {
+ const struct drm_bridge_timings *btimings = bridge->timings;
+
+ /*
+ * Here is when things get really fun. Sometimes the bridge
+ * timings are such that the signal out from PL11x is not
+ * stable before the receiving bridge (such as a dumb VGA DAC
+ * or similar) samples it. If that happens, we compensate by
+ * the only method we have: output the data on the opposite
+ * edge of the clock so it is for sure stable when it gets
+ * sampled.
+ *
+ * The PL111 manual does not contain proper timining diagrams
+ * or data for these details, but we know from experiments
+ * that the setup time is more than 3000 picoseconds (3 ns).
+ * If we have a bridge that requires the signal to be stable
+ * earlier than 3000 ps before the clock pulse, we have to
+ * output the data on the opposite edge to avoid flicker.
+ */
+ if (btimings && btimings->setup_time_ps >= 3000)
+ tim2 ^= TIM2_IPC;
+ }
tim2 |= cpl << 16;
writel(tim2, priv->regs + CLCD_TIM2);
@@ -172,10 +237,17 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
cntl |= CNTL_LCDBPP24 | CNTL_BGR;
break;
case DRM_FORMAT_BGR565:
- cntl |= CNTL_LCDBPP16_565;
+ if (priv->variant->is_pl110)
+ cntl |= CNTL_LCDBPP16;
+ else
+ cntl |= CNTL_LCDBPP16_565;
break;
case DRM_FORMAT_RGB565:
- cntl |= CNTL_LCDBPP16_565 | CNTL_BGR;
+ if (priv->variant->is_pl110)
+ cntl |= CNTL_LCDBPP16;
+ else
+ cntl |= CNTL_LCDBPP16_565;
+ cntl |= CNTL_BGR;
break;
case DRM_FORMAT_ABGR1555:
case DRM_FORMAT_XBGR1555:
@@ -199,6 +271,10 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
break;
}
+ /* The PL110 in Integrator/Versatile does the BGR routing externally */
+ if (priv->variant->external_bgr)
+ cntl &= ~CNTL_BGR;
+
/* Power sequence: first enable and chill */
writel(cntl, priv->regs + priv->ctrl);
@@ -215,7 +291,8 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe,
cntl |= CNTL_LCDPWR;
writel(cntl, priv->regs + priv->ctrl);
- drm_crtc_vblank_on(crtc);
+ if (!priv->variant->broken_vblank)
+ drm_crtc_vblank_on(crtc);
}
void pl111_display_disable(struct drm_simple_display_pipe *pipe)
@@ -225,7 +302,8 @@ void pl111_display_disable(struct drm_simple_display_pipe *pipe)
struct pl111_drm_dev_private *priv = drm->dev_private;
u32 cntl;
- drm_crtc_vblank_off(crtc);
+ if (!priv->variant->broken_vblank)
+ drm_crtc_vblank_off(crtc);
/* Power Down */
cntl = readl(priv->regs + priv->ctrl);
@@ -278,8 +356,10 @@ static void pl111_display_update(struct drm_simple_display_pipe *pipe,
}
}
-int pl111_enable_vblank(struct drm_device *drm, unsigned int crtc)
+static int pl111_display_enable_vblank(struct drm_simple_display_pipe *pipe)
{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_device *drm = crtc->dev;
struct pl111_drm_dev_private *priv = drm->dev_private;
writel(CLCD_IRQ_NEXTBASE_UPDATE, priv->regs + priv->ienb);
@@ -287,8 +367,10 @@ int pl111_enable_vblank(struct drm_device *drm, unsigned int crtc)
return 0;
}
-void pl111_disable_vblank(struct drm_device *drm, unsigned int crtc)
+static void pl111_display_disable_vblank(struct drm_simple_display_pipe *pipe)
{
+ struct drm_crtc *crtc = &pipe->crtc;
+ struct drm_device *drm = crtc->dev;
struct pl111_drm_dev_private *priv = drm->dev_private;
writel(0, priv->regs + priv->ienb);
@@ -300,7 +382,8 @@ static int pl111_display_prepare_fb(struct drm_simple_display_pipe *pipe,
return drm_gem_fb_prepare_fb(&pipe->plane, plane_state);
}
-static const struct drm_simple_display_pipe_funcs pl111_display_funcs = {
+static struct drm_simple_display_pipe_funcs pl111_display_funcs = {
+ .mode_valid = pl111_mode_valid,
.check = pl111_display_check,
.enable = pl111_display_enable,
.disable = pl111_display_disable,
@@ -417,6 +500,11 @@ pl111_init_clock_divider(struct drm_device *drm)
dev_err(drm->dev, "CLCD: unable to get clcdclk.\n");
return PTR_ERR(parent);
}
+ /* If the clock divider is broken, use the parent directly */
+ if (priv->variant->broken_clockdivider) {
+ priv->clk = parent;
+ return 0;
+ }
parent_name = __clk_get_name(parent);
spin_lock_init(&priv->tim2_lock);
@@ -454,6 +542,11 @@ int pl111_display_init(struct drm_device *drm)
if (ret)
return ret;
+ if (!priv->variant->broken_vblank) {
+ pl111_display_funcs.enable_vblank = pl111_display_enable_vblank;
+ pl111_display_funcs.disable_vblank = pl111_display_disable_vblank;
+ }
+
ret = drm_simple_display_pipe_init(drm, &priv->pipe,
&pl111_display_funcs,
priv->variant->formats,
OpenPOWER on IntegriCloud