Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0deed25b authored by Stephane Viau's avatar Stephane Viau Committed by Rob Clark
Browse files

drm/msm: add multiple CRTC and overlay support



MDP5 currently support one single CRTC with its private pipe.
This change allows the configuration of multiple CRTCs with
the possibility to attach several public planes to these CRTCs.

Signed-off-by: default avatarStephane Viau <sviau@codeaurora.org>
Signed-off-by: default avatarRob Clark <robdclark@gmail.com>
parent ac7a5704
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ msm-y := \
	mdp/mdp4/mdp4_kms.o \
	mdp/mdp4/mdp4_plane.o \
	mdp/mdp5/mdp5_cfg.o \
	mdp/mdp5/mdp5_ctl.o \
	mdp/mdp5/mdp5_crtc.o \
	mdp/mdp5/mdp5_encoder.o \
	mdp/mdp5/mdp5_irq.o \
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
 */
extern const struct mdp5_cfg_hw *mdp5_cfg;

#define MAX_CTL			8
#define MAX_BASES		8
#define MAX_SMP_BLOCKS		44
#define MAX_CLIENTS		32
+207 −64
Original line number Diff line number Diff line
/*
 * 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);
	struct mdp5_kms *mdp5_kms = get_kms(crtc);
	int id = mdp5_crtc->id;
	struct drm_plane *plane;
	uint32_t flush = 0;

	for_each_plane_on_crtc(crtc, plane) {
		enum mdp5_pipe pipe = mdp5_plane_pipe(plane);
		flush |= pipe2flush(pipe);
	DBG("%s: flush=%08x", mdp5_crtc->name, flush_mask);
	mdp5_ctl_commit(mdp5_crtc->ctl, flush_mask);
}

	flush |= mixer2flush(mdp5_crtc->id);
	flush |= MDP5_CTL_FLUSH_CTL;
/*
 * 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 drm_plane *plane;
	uint32_t flush_mask = 0;

	/* we could have already released CTL in the disable path: */
	if (!mdp5_crtc->ctl)
		return;

	DBG("%s: flush=%08x", mdp5_crtc->name, flush);
	for_each_plane_on_crtc(crtc, plane) {
		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);

	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)

	hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg_priv);

	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;

		/*
	 * Hard-coded setup for now until I figure out how the
	 * layer-mixer works
		 * 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[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),
		/* 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(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?
	 */
				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);

	/* 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);
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;
+325 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2014 The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include "mdp5_kms.h"
#include "mdp5_ctl.h"

/*
 * CTL - MDP Control Pool Manager
 *
 * Controls are shared between all CRTCs.
 *
 * They are intended to be used for data path configuration.
 * The top level register programming describes the complete data path for
 * a specific data path ID - REG_MDP5_CTL_*(<id>, ...)
 *
 * Hardware capabilities determine the number of concurrent data paths
 *
 * In certain use cases (high-resolution dual pipe), one single CTL can be
 * shared across multiple CRTCs.
 *
 * Because the number of CTLs can be less than the number of CRTCs,
 * CTLs are dynamically allocated from a pool of CTLs, only once a CRTC is
 * requested by the client (in mdp5_crtc_mode_set()).
 */

struct mdp5_ctl {
	u32 id;

	/* whether this CTL has been allocated or not: */
	bool busy;

	/* memory output connection (@see mdp5_ctl_mode): */
	u32 mode;

	/* REG_MDP5_CTL_*(<id>) registers access info + lock: */
	spinlock_t hw_lock;
	u32 reg_offset;

	/* flush mask used to commit CTL registers */
	u32 flush_mask;

	bool cursor_on;
	void *crtc;
};

struct mdp5_ctl_manager {
	struct drm_device *dev;

	/* number of CTL / Layer Mixers in this hw config: */
	u32 nlm;
	u32 nctl;

	/* pool of CTLs + lock to protect resource allocation (ctls[i].busy) */
	spinlock_t pool_lock;
	struct mdp5_ctl ctls[MAX_CTL];
};

static struct mdp5_ctl_manager mdp5_ctl_mgr;

static inline
struct mdp5_kms *get_kms(struct mdp5_ctl_manager *ctl_mgr)
{
	struct msm_drm_private *priv = ctl_mgr->dev->dev_private;

	return to_mdp5_kms(to_mdp_kms(priv->kms));
}

static inline
void ctl_write(struct mdp5_ctl *ctl, u32 reg, u32 data)
{
	struct mdp5_ctl_manager *ctl_mgr = &mdp5_ctl_mgr;
	struct mdp5_kms *mdp5_kms = get_kms(ctl_mgr);

	(void)ctl->reg_offset; /* TODO use this instead of mdp5_write */
	mdp5_write(mdp5_kms, reg, data);
}

static inline
u32 ctl_read(struct mdp5_ctl *ctl, u32 reg)
{
	struct mdp5_ctl_manager *ctl_mgr = &mdp5_ctl_mgr;
	struct mdp5_kms *mdp5_kms = get_kms(ctl_mgr);

	(void)ctl->reg_offset; /* TODO use this instead of mdp5_write */
	return mdp5_read(mdp5_kms, reg);
}


int mdp5_ctl_set_intf(void *c, enum mdp5_intf intf)
{
	struct mdp5_ctl *ctl = c;
	unsigned long flags;
	static const enum mdp5_intfnum intfnum[] = {
			INTF0, INTF1, INTF2, INTF3,
	};

	spin_lock_irqsave(&ctl->hw_lock, flags);
	ctl_write(ctl, REG_MDP5_CTL_OP(ctl->id),
			MDP5_CTL_OP_MODE(ctl->mode) |
			MDP5_CTL_OP_INTF_NUM(intfnum[intf]));
	spin_unlock_irqrestore(&ctl->hw_lock, flags);

	return 0;
}

int mdp5_ctl_set_cursor(void *c, bool enable)
{
	struct mdp5_ctl_manager *ctl_mgr = &mdp5_ctl_mgr;
	struct mdp5_ctl *ctl = c;
	unsigned long flags;
	u32 blend_cfg;
	int lm;

	lm = mdp5_crtc_get_lm(ctl->crtc);
	if (unlikely(WARN_ON(lm < 0))) {
		dev_err(ctl_mgr->dev->dev, "CTL %d cannot find LM: %d",
				ctl->id, lm);
		return -EINVAL;
	}

	spin_lock_irqsave(&ctl->hw_lock, flags);

	blend_cfg = ctl_read(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, lm));

	if (enable)
		blend_cfg |=  MDP5_CTL_LAYER_REG_CURSOR_OUT;
	else
		blend_cfg &= ~MDP5_CTL_LAYER_REG_CURSOR_OUT;

	ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, lm), blend_cfg);

	spin_unlock_irqrestore(&ctl->hw_lock, flags);

	ctl->cursor_on = enable;

	return 0;
}


int mdp5_ctl_blend(void *c, u32 lm, u32 blend_cfg)
{
	struct mdp5_ctl *ctl = c;
	unsigned long flags;

	if (ctl->cursor_on)
		blend_cfg |=  MDP5_CTL_LAYER_REG_CURSOR_OUT;
	else
		blend_cfg &= ~MDP5_CTL_LAYER_REG_CURSOR_OUT;

	spin_lock_irqsave(&ctl->hw_lock, flags);
	ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, lm), blend_cfg);
	spin_unlock_irqrestore(&ctl->hw_lock, flags);

	return 0;
}

int mdp5_ctl_commit(void *c, u32 flush_mask)
{
	struct mdp5_ctl_manager *ctl_mgr = &mdp5_ctl_mgr;
	struct mdp5_ctl *ctl = c;
	unsigned long flags;

	if (flush_mask & MDP5_CTL_FLUSH_CURSOR_DUMMY) {
		int lm = mdp5_crtc_get_lm(ctl->crtc);

		if (unlikely(WARN_ON(lm < 0))) {
			dev_err(ctl_mgr->dev->dev, "CTL %d cannot find LM: %d",
					ctl->id, lm);
			return -EINVAL;
		}

		/* for current targets, cursor bit is the same as LM bit */
		flush_mask |= mdp_ctl_flush_mask_lm(lm);
	}

	spin_lock_irqsave(&ctl->hw_lock, flags);
	ctl_write(ctl, REG_MDP5_CTL_FLUSH(ctl->id), flush_mask);
	spin_unlock_irqrestore(&ctl->hw_lock, flags);

	return 0;
}

u32 mdp5_ctl_get_flush(void *c)
{
	struct mdp5_ctl *ctl = c;

	return ctl->flush_mask;
}

void mdp5_ctl_release(void *c)
{
	struct mdp5_ctl_manager *ctl_mgr = &mdp5_ctl_mgr;
	struct mdp5_ctl *ctl = c;
	unsigned long flags;

	if (unlikely(WARN_ON(ctl->id >= MAX_CTL) || !ctl->busy)) {
		dev_err(ctl_mgr->dev->dev, "CTL %d in bad state (%d)",
				ctl->id, ctl->busy);
		return;
	}

	spin_lock_irqsave(&ctl_mgr->pool_lock, flags);
	ctl->busy = false;
	spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags);

	DBG("CTL %d released", ctl->id);
}

/*
 * mdp5_ctl_request() - CTL dynamic allocation
 *
 * Note: Current implementation considers that we can only have one CRTC per CTL
 *
 * @return first free CTL
 */
void *mdp5_ctl_request(void *ctlm, void *crtc)
{
	struct mdp5_ctl_manager *ctl_mgr = ctlm;
	struct mdp5_ctl *ctl = NULL;
	unsigned long flags;
	int c;

	spin_lock_irqsave(&ctl_mgr->pool_lock, flags);

	for (c = 0; c < ctl_mgr->nctl; c++)
		if (!ctl_mgr->ctls[c].busy)
			break;

	if (unlikely(c >= ctl_mgr->nctl)) {
		dev_err(ctl_mgr->dev->dev, "No more CTL available!");
		goto unlock;
	}

	ctl = &ctl_mgr->ctls[c];

	ctl->crtc = crtc;
	ctl->busy = true;
	DBG("CTL %d allocated", ctl->id);

unlock:
	spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags);
	return ctl;
}

void mdp5_ctlm_hw_reset(void *ctlm)
{
	struct mdp5_ctl_manager *ctl_mgr = ctlm;
	unsigned long flags;
	int c;

	for (c = 0; c < ctl_mgr->nctl; c++) {
		struct mdp5_ctl *ctl = &ctl_mgr->ctls[c];

		spin_lock_irqsave(&ctl->hw_lock, flags);
		ctl_write(ctl, REG_MDP5_CTL_OP(ctl->id), 0);
		spin_unlock_irqrestore(&ctl->hw_lock, flags);
	}
}

void mdp5_ctlm_destroy(void *ctlm)
{
	struct mdp5_ctl_manager *ctl_mgr = ctlm;

	kfree(ctl_mgr);
}

void *mdp5_ctlm_init(struct drm_device *dev, void __iomem *mmio_base,
		const struct mdp5_cfg_hw *hw_cfg)
{
	struct mdp5_ctl_manager *ctl_mgr = &mdp5_ctl_mgr;
	const struct mdp5_sub_block *ctl_cfg = &hw_cfg->ctl;
	unsigned long flags;
	int c, ret;

	if (unlikely(WARN_ON(ctl_cfg->count > MAX_CTL))) {
		dev_err(dev->dev, "Increase static pool size to at least %d\n",
				ctl_cfg->count);
		ret = -ENOSPC;
		goto fail;
	}

	/* initialize the CTL manager: */
	ctl_mgr->dev = dev;
	ctl_mgr->nlm = hw_cfg->lm.count;
	ctl_mgr->nctl = ctl_cfg->count;
	spin_lock_init(&ctl_mgr->pool_lock);

	/* initialize each CTL of the pool: */
	spin_lock_irqsave(&ctl_mgr->pool_lock, flags);
	for (c = 0; c < ctl_mgr->nctl; c++) {
		struct mdp5_ctl *ctl = &ctl_mgr->ctls[c];

		if (WARN_ON(!ctl_cfg->base[c])) {
			dev_err(dev->dev, "CTL_%d: base is null!\n", c);
			ret = -EINVAL;
			goto fail;
		}
		ctl->id = c;
		ctl->mode = MODE_NONE;
		ctl->reg_offset = ctl_cfg->base[c];
		ctl->flush_mask = MDP5_CTL_FLUSH_CTL;
		ctl->busy = false;
		spin_lock_init(&ctl->hw_lock);
	}
	spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags);
	DBG("Pool of %d CTLs created.", ctl_mgr->nctl);

	return ctl_mgr;

fail:
	if (ctl_mgr)
		mdp5_ctlm_destroy(ctl_mgr);

	return ERR_PTR(ret);
}
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2014 The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifndef __MDP5_CTL_H__
#define __MDP5_CTL_H__

#include "msm_drv.h"

/*
 * CTL Manager prototypes:
 * mdp5_ctlm_init() returns a ctlm (CTL Manager) handler,
 * which is then used to call the other mdp5_ctlm_*(ctlm, ...) functions.
 */
void *mdp5_ctlm_init(struct drm_device *dev, void __iomem *mmio_base,
		const struct mdp5_cfg_hw *hw_cfg);
void  mdp5_ctlm_hw_reset(void *ctlm);
void  mdp5_ctlm_destroy(void *ctlm);

/*
 * CTL prototypes:
 * mdp5_ctl_request(ctlm, ...) returns a ctl (CTL resource) handler,
 * which is then used to call the other mdp5_ctl_*(ctl, ...) functions.
 */
void *mdp5_ctl_request(void *ctlm, void *crtc);

int mdp5_ctl_set_intf(void *ctl, enum mdp5_intf intf);

int mdp5_ctl_set_cursor(void *ctl, bool enable);

/* @blend_cfg: see LM blender config definition below */
int mdp5_ctl_blend(void *ctl, u32 lm, u32 blend_cfg);

/* @flush_mask: see CTL flush masks definitions below */
int mdp5_ctl_commit(void *ctl, u32 flush_mask);
u32 mdp5_ctl_get_flush(void *ctl);

void mdp5_ctl_release(void *ctl);

/*
 * blend_cfg (LM blender config):
 *
 * The function below allows the caller of mdp5_ctl_blend() to specify how pipes
 * are being blended according to their stage (z-order), through @blend_cfg arg.
 */
static inline u32 mdp_ctl_blend_mask(enum mdp5_pipe pipe,
		enum mdp_mixer_stage_id stage)
{
	switch (pipe) {
	case SSPP_VIG0: return MDP5_CTL_LAYER_REG_VIG0(stage);
	case SSPP_VIG1: return MDP5_CTL_LAYER_REG_VIG1(stage);
	case SSPP_VIG2: return MDP5_CTL_LAYER_REG_VIG2(stage);
	case SSPP_RGB0: return MDP5_CTL_LAYER_REG_RGB0(stage);
	case SSPP_RGB1: return MDP5_CTL_LAYER_REG_RGB1(stage);
	case SSPP_RGB2: return MDP5_CTL_LAYER_REG_RGB2(stage);
	case SSPP_DMA0: return MDP5_CTL_LAYER_REG_DMA0(stage);
	case SSPP_DMA1: return MDP5_CTL_LAYER_REG_DMA1(stage);
	case SSPP_VIG3: return MDP5_CTL_LAYER_REG_VIG3(stage);
	case SSPP_RGB3: return MDP5_CTL_LAYER_REG_RGB3(stage);
	default:	return 0;
	}
}

/*
 * flush_mask (CTL flush masks):
 *
 * The following functions allow each DRM entity to get and store
 * their own flush mask.
 * Once stored, these masks will then be accessed through each DRM's
 * interface and used by the caller of mdp5_ctl_commit() to specify
 * which block(s) need to be flushed through @flush_mask parameter.
 */

#define MDP5_CTL_FLUSH_CURSOR_DUMMY	0x80000000

static inline u32 mdp_ctl_flush_mask_cursor(int cursor_id)
{
	/* TODO: use id once multiple cursor support is present */
	(void)cursor_id;

	return MDP5_CTL_FLUSH_CURSOR_DUMMY;
}

static inline u32 mdp_ctl_flush_mask_lm(int lm)
{
	switch (lm) {
	case 0:  return MDP5_CTL_FLUSH_LM0;
	case 1:  return MDP5_CTL_FLUSH_LM1;
	case 2:  return MDP5_CTL_FLUSH_LM2;
	case 5:  return MDP5_CTL_FLUSH_LM5;
	default: return 0;
	}
}

static inline u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe)
{
	switch (pipe) {
	case SSPP_VIG0: return MDP5_CTL_FLUSH_VIG0;
	case SSPP_VIG1: return MDP5_CTL_FLUSH_VIG1;
	case SSPP_VIG2: return MDP5_CTL_FLUSH_VIG2;
	case SSPP_RGB0: return MDP5_CTL_FLUSH_RGB0;
	case SSPP_RGB1: return MDP5_CTL_FLUSH_RGB1;
	case SSPP_RGB2: return MDP5_CTL_FLUSH_RGB2;
	case SSPP_DMA0: return MDP5_CTL_FLUSH_DMA0;
	case SSPP_DMA1: return MDP5_CTL_FLUSH_DMA1;
	case SSPP_VIG3: return MDP5_CTL_FLUSH_VIG3;
	case SSPP_RGB3: return MDP5_CTL_FLUSH_RGB3;
	default:        return 0;
	}
}

#endif /* __MDP5_CTL_H__ */
Loading