summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c')
-rw-r--r--drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c271
1 files changed, 207 insertions, 64 deletions
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
index b7b32c4..85f2fb4 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (c) 2014 The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
@@ -22,14 +23,21 @@
#include "drm_crtc_helper.h"
#include "drm_flip_work.h"
+#define SSPP_MAX (SSPP_RGB3 + 1) /* TODO: Add SSPP_MAX in mdp5.xml.h */
+
struct mdp5_crtc {
struct drm_crtc base;
char name[8];
int id;
bool enabled;
- /* which mixer/encoder we route output to: */
- int mixer;
+ /* layer mixer used for this CRTC (+ its lock): */
+#define GET_LM_ID(crtc_id) ((crtc_id == 3) ? 5 : crtc_id)
+ int lm;
+ spinlock_t lm_lock; /* protect REG_MDP5_LM_* registers */
+
+ /* CTL used for this CRTC: */
+ void *ctl;
/* if there is a pending flip, these will be non-null: */
struct drm_pending_vblank_event *event;
@@ -71,25 +79,38 @@ static void request_pending(struct drm_crtc *crtc, uint32_t pending)
mdp_irq_register(&get_kms(crtc)->base, &mdp5_crtc->vblank);
}
-static void crtc_flush(struct drm_crtc *crtc)
+#define mdp5_lm_get_flush(lm) mdp_ctl_flush_mask_lm(lm)
+
+static void crtc_flush(struct drm_crtc *crtc, u32 flush_mask)
+{
+ struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+
+ DBG("%s: flush=%08x", mdp5_crtc->name, flush_mask);
+ mdp5_ctl_commit(mdp5_crtc->ctl, flush_mask);
+}
+
+/*
+ * flush updates, to make sure hw is updated to new scanout fb,
+ * so that we can safely queue unref to current fb (ie. next
+ * vblank we know hw is done w/ previous scanout_fb).
+ */
+static void crtc_flush_all(struct drm_crtc *crtc)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
- struct mdp5_kms *mdp5_kms = get_kms(crtc);
- int id = mdp5_crtc->id;
struct drm_plane *plane;
- uint32_t flush = 0;
+ uint32_t flush_mask = 0;
+
+ /* we could have already released CTL in the disable path: */
+ if (!mdp5_crtc->ctl)
+ return;
for_each_plane_on_crtc(crtc, plane) {
- enum mdp5_pipe pipe = mdp5_plane_pipe(plane);
- flush |= pipe2flush(pipe);
+ flush_mask |= mdp5_plane_get_flush(plane);
}
+ flush_mask |= mdp5_ctl_get_flush(mdp5_crtc->ctl);
+ flush_mask |= mdp5_lm_get_flush(mdp5_crtc->lm);
- flush |= mixer2flush(mdp5_crtc->id);
- flush |= MDP5_CTL_FLUSH_CTL;
-
- DBG("%s: flush=%08x", mdp5_crtc->name, flush);
-
- mdp5_write(mdp5_kms, REG_MDP5_CTL_FLUSH(id), flush);
+ crtc_flush(crtc, flush_mask);
}
static void update_fb(struct drm_crtc *crtc, struct drm_framebuffer *new_fb)
@@ -117,12 +138,6 @@ static void update_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
- /* flush updates, to make sure hw is updated to new scanout fb,
- * so that we can safely queue unref to current fb (ie. next
- * vblank we know hw is done w/ previous scanout_fb).
- */
- crtc_flush(crtc);
-
if (mdp5_crtc->scanout_fb)
drm_flip_work_queue(&mdp5_crtc->unref_fb_work,
mdp5_crtc->scanout_fb);
@@ -173,6 +188,7 @@ static void pageflip_cb(struct msm_fence_cb *cb)
drm_framebuffer_reference(fb);
mdp5_plane_set_scanout(crtc->primary, fb);
update_scanout(crtc, fb);
+ crtc_flush_all(crtc);
}
static void unref_fb_worker(struct drm_flip_work *work, void *val)
@@ -223,41 +239,68 @@ static bool mdp5_crtc_mode_fixup(struct drm_crtc *crtc,
return true;
}
+/*
+ * blend_setup() - blend all the planes of a CRTC
+ *
+ * When border is enabled, the border color will ALWAYS be the base layer.
+ * Therefore, the first plane (private RGB pipe) will start at STAGE0.
+ * If disabled, the first plane starts at STAGE_BASE.
+ *
+ * Note:
+ * Border is not enabled here because the private plane is exactly
+ * the CRTC resolution.
+ */
static void blend_setup(struct drm_crtc *crtc)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct mdp5_kms *mdp5_kms = get_kms(crtc);
- int id = mdp5_crtc->id;
+ struct drm_plane *plane;
+ const struct mdp5_cfg_hw *hw_cfg;
+ uint32_t lm = mdp5_crtc->lm, blend_cfg = 0;
+ enum mdp_mixer_stage_id stage;
+ unsigned long flags;
+#define blender(stage) ((stage) - STAGE_BASE)
- /*
- * Hard-coded setup for now until I figure out how the
- * layer-mixer works
- */
+ hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg_priv);
- /* LM[id]: */
- mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(id),
- MDP5_LM_BLEND_COLOR_OUT_STAGE0_FG_ALPHA);
- mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_OP_MODE(id, 0),
- MDP5_LM_BLEND_OP_MODE_FG_ALPHA(FG_CONST) |
- MDP5_LM_BLEND_OP_MODE_BG_ALPHA(FG_PIXEL) |
- MDP5_LM_BLEND_OP_MODE_BG_INV_ALPHA);
- mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_FG_ALPHA(id, 0), 0xff);
- mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_BG_ALPHA(id, 0), 0x00);
-
- /* NOTE: seems that LM[n] and CTL[m], we do not need n==m.. but
- * we want to be setting CTL[m].LAYER[n]. Not sure what the
- * point of having CTL[m].LAYER[o] (for o!=n).. maybe that is
- * used when chaining up mixers for high resolution displays?
- */
+ spin_lock_irqsave(&mdp5_crtc->lm_lock, flags);
+
+ /* ctl could be released already when we are shutting down: */
+ if (!mdp5_crtc->ctl)
+ goto out;
+
+ for_each_plane_on_crtc(crtc, plane) {
+ struct mdp5_overlay_info *overlay;
+
+ overlay = mdp5_plane_get_overlay_info(plane);
+ stage = overlay->zorder;
- /* CTL[id]: */
- mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 0),
- MDP5_CTL_LAYER_REG_RGB0(STAGE0) |
- MDP5_CTL_LAYER_REG_BORDER_COLOR);
- mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 1), 0);
- mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 2), 0);
- mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 3), 0);
- mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 4), 0);
+ /*
+ * Note: This cannot happen with current implementation but
+ * we need to check this condition once z property is added
+ */
+ BUG_ON(stage > hw_cfg->lm.nb_stages);
+
+ /* LM */
+ mdp5_write(mdp5_kms,
+ REG_MDP5_LM_BLEND_OP_MODE(lm, blender(stage)),
+ MDP5_LM_BLEND_OP_MODE_FG_ALPHA(FG_CONST) |
+ MDP5_LM_BLEND_OP_MODE_BG_ALPHA(BG_CONST));
+ mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_FG_ALPHA(lm,
+ blender(stage)), 0xff);
+ mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_BG_ALPHA(lm,
+ blender(stage)), 0x00);
+ /* CTL */
+ blend_cfg |= mdp_ctl_blend_mask(mdp5_plane_pipe(plane), stage);
+ DBG("%s: blending pipe %s on stage=%d", mdp5_crtc->name,
+ pipe2name(mdp5_plane_pipe(plane)), stage);
+ }
+
+ DBG("%s: lm%d: blend config = 0x%08x", mdp5_crtc->name, lm, blend_cfg);
+ mdp5_ctl_blend(mdp5_crtc->ctl, lm, blend_cfg);
+
+out:
+ spin_unlock_irqrestore(&mdp5_crtc->lm_lock, flags);
}
static int mdp5_crtc_mode_set(struct drm_crtc *crtc,
@@ -268,6 +311,7 @@ static int mdp5_crtc_mode_set(struct drm_crtc *crtc,
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct mdp5_kms *mdp5_kms = get_kms(crtc);
+ unsigned long flags;
int ret;
mode = adjusted_mode;
@@ -281,6 +325,13 @@ static int mdp5_crtc_mode_set(struct drm_crtc *crtc,
mode->vsync_end, mode->vtotal,
mode->type, mode->flags);
+ /* request a free CTL, if none is already allocated for this CRTC */
+ if (!mdp5_crtc->ctl) {
+ mdp5_crtc->ctl = mdp5_ctl_request(mdp5_kms->ctl_priv, crtc);
+ if (!mdp5_crtc->ctl)
+ return -EBUSY;
+ }
+
/* grab extra ref for update_scanout() */
drm_framebuffer_reference(crtc->primary->fb);
@@ -295,12 +346,15 @@ static int mdp5_crtc_mode_set(struct drm_crtc *crtc,
return ret;
}
- mdp5_write(mdp5_kms, REG_MDP5_LM_OUT_SIZE(mdp5_crtc->id),
+ spin_lock_irqsave(&mdp5_crtc->lm_lock, flags);
+ mdp5_write(mdp5_kms, REG_MDP5_LM_OUT_SIZE(mdp5_crtc->lm),
MDP5_LM_OUT_SIZE_WIDTH(mode->hdisplay) |
MDP5_LM_OUT_SIZE_HEIGHT(mode->vdisplay));
+ spin_unlock_irqrestore(&mdp5_crtc->lm_lock, flags);
update_fb(crtc, crtc->primary->fb);
update_scanout(crtc, crtc->primary->fb);
+ /* crtc_flush_all(crtc) will be called in _commit callback */
return 0;
}
@@ -317,7 +371,7 @@ static void mdp5_crtc_prepare(struct drm_crtc *crtc)
static void mdp5_crtc_commit(struct drm_crtc *crtc)
{
mdp5_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
- crtc_flush(crtc);
+ crtc_flush_all(crtc);
/* drop the ref to mdp clk's that we got in prepare: */
mdp5_disable(get_kms(crtc));
}
@@ -343,6 +397,7 @@ static int mdp5_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
update_fb(crtc, crtc->primary->fb);
update_scanout(crtc, crtc->primary->fb);
+ crtc_flush_all(crtc);
return 0;
}
@@ -351,6 +406,19 @@ static void mdp5_crtc_load_lut(struct drm_crtc *crtc)
{
}
+static void mdp5_crtc_disable(struct drm_crtc *crtc)
+{
+ struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+
+ DBG("%s", mdp5_crtc->name);
+
+ if (mdp5_crtc->ctl) {
+ mdp5_ctl_release(mdp5_crtc->ctl);
+ mdp5_crtc->ctl = NULL;
+ }
+}
+
+
static int mdp5_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *new_fb,
struct drm_pending_vblank_event *event,
@@ -399,6 +467,7 @@ static const struct drm_crtc_helper_funcs mdp5_crtc_helper_funcs = {
.commit = mdp5_crtc_commit,
.mode_set_base = mdp5_crtc_mode_set_base,
.load_lut = mdp5_crtc_load_lut,
+ .disable = mdp5_crtc_disable,
};
static void mdp5_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
@@ -421,9 +490,8 @@ static void mdp5_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
static void mdp5_crtc_err_irq(struct mdp_irq *irq, uint32_t irqstatus)
{
struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, err);
- struct drm_crtc *crtc = &mdp5_crtc->base;
+
DBG("%s: error: %08x", mdp5_crtc->name, irqstatus);
- crtc_flush(crtc);
}
uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc)
@@ -444,10 +512,9 @@ void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct mdp5_kms *mdp5_kms = get_kms(crtc);
- static const enum mdp5_intfnum intfnum[] = {
- INTF0, INTF1, INTF2, INTF3,
- };
+ uint32_t flush_mask = 0;
uint32_t intf_sel;
+ unsigned long flags;
/* now that we know what irq's we want: */
mdp5_crtc->err.irqmask = intf2err(intf);
@@ -457,6 +524,7 @@ void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
if (!mdp5_kms)
return;
+ spin_lock_irqsave(&mdp5_kms->resource_lock, flags);
intf_sel = mdp5_read(mdp5_kms, REG_MDP5_DISP_INTF_SEL);
switch (intf) {
@@ -481,16 +549,24 @@ void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
break;
}
- blend_setup(crtc);
+ mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, intf_sel);
+ spin_unlock_irqrestore(&mdp5_kms->resource_lock, flags);
DBG("%s: intf_sel=%08x", mdp5_crtc->name, intf_sel);
+ mdp5_ctl_set_intf(mdp5_crtc->ctl, intf);
+ flush_mask |= mdp5_ctl_get_flush(mdp5_crtc->ctl);
+ flush_mask |= mdp5_lm_get_flush(mdp5_crtc->lm);
- mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, intf_sel);
- mdp5_write(mdp5_kms, REG_MDP5_CTL_OP(mdp5_crtc->id),
- MDP5_CTL_OP_MODE(MODE_NONE) |
- MDP5_CTL_OP_INTF_NUM(intfnum[intf]));
+ crtc_flush(crtc, flush_mask);
+}
- crtc_flush(crtc);
+static int count_planes(struct drm_crtc *crtc)
+{
+ struct drm_plane *plane;
+ int cnt = 0;
+ for_each_plane_on_crtc(crtc, plane)
+ cnt++;
+ return cnt;
}
static void set_attach(struct drm_crtc *crtc, enum mdp5_pipe pipe_id,
@@ -498,14 +574,68 @@ static void set_attach(struct drm_crtc *crtc, enum mdp5_pipe pipe_id,
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+ if (plane)
+ plane->crtc = crtc;
+
+ DBG("%s: %d planes attached", mdp5_crtc->name, count_planes(crtc));
+
blend_setup(crtc);
- if (mdp5_crtc->enabled && (plane != crtc->primary))
- crtc_flush(crtc);
+ if (mdp5_crtc->enabled)
+ crtc_flush_all(crtc);
}
-void mdp5_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane)
+int mdp5_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane)
{
+ struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+ struct mdp5_kms *mdp5_kms = get_kms(crtc);
+ struct device *dev = crtc->dev->dev;
+ const struct mdp5_cfg_hw *hw_cfg;
+ bool private_plane = (plane == crtc->primary);
+ struct mdp5_overlay_info overlay_info;
+ enum mdp_mixer_stage_id stage = STAGE_BASE;
+ int max_nb_planes;
+
+ hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg_priv);
+ max_nb_planes = hw_cfg->lm.nb_stages;
+
+ if (count_planes(crtc) >= max_nb_planes) {
+ dev_err(dev, "%s: max # of planes (%d) reached\n",
+ mdp5_crtc->name, max_nb_planes);
+ return -EBUSY;
+ }
+
+ /*
+ * Set default z-ordering depending on the type of plane
+ * private -> lower stage
+ * public -> topmost stage
+ *
+ * TODO: add a property to give userspace an API to change this...
+ * (will come in a subsequent patch)
+ */
+ if (private_plane) {
+ stage = STAGE_BASE;
+ } else {
+ struct drm_plane *attached_plane;
+ for_each_plane_on_crtc(crtc, attached_plane) {
+ struct mdp5_overlay_info *overlay;
+
+ if (!attached_plane)
+ continue;
+ overlay = mdp5_plane_get_overlay_info(attached_plane);
+ stage = max(stage, overlay->zorder);
+ }
+ stage++;
+ }
+ overlay_info.zorder = stage;
+ mdp5_plane_set_overlay_info(plane, &overlay_info);
+
+ DBG("%s: %s plane %s set to stage %d by default", mdp5_crtc->name,
+ private_plane ? "private" : "public",
+ pipe2name(mdp5_plane_pipe(plane)), overlay_info.zorder);
+
set_attach(crtc, mdp5_plane_pipe(plane), plane);
+
+ return 0;
}
void mdp5_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane)
@@ -516,6 +646,16 @@ void mdp5_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane)
set_attach(crtc, mdp5_plane_pipe(plane), NULL);
}
+int mdp5_crtc_get_lm(struct drm_crtc *crtc)
+{
+ struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
+
+ if (WARN_ON(!crtc))
+ return -EINVAL;
+
+ return mdp5_crtc->lm;
+}
+
/* initialize crtc */
struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
struct drm_plane *plane, int id)
@@ -530,6 +670,9 @@ struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
crtc = &mdp5_crtc->base;
mdp5_crtc->id = id;
+ mdp5_crtc->lm = GET_LM_ID(id);
+
+ spin_lock_init(&mdp5_crtc->lm_lock);
mdp5_crtc->vblank.irq = mdp5_crtc_vblank_irq;
mdp5_crtc->err.irq = mdp5_crtc_err_irq;