Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,809 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp4_kms.h"
#include <drm/drm_mode.h>
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
#include "drm_flip_work.h"
struct mdp4_crtc {
struct drm_crtc base;
char name[8];
struct drm_plane *plane;
struct drm_plane *planes[8];
int id;
int ovlp;
enum mdp4_dma dma;
bool enabled;
/* which mixer/encoder we route output to: */
int mixer;
struct {
spinlock_t lock;
bool stale;
uint32_t width, height;
uint32_t x, y;
/* next cursor to scan-out: */
uint32_t next_iova;
struct drm_gem_object *next_bo;
/* current cursor being scanned out: */
struct drm_gem_object *scanout_bo;
} cursor;
/* if there is a pending flip, these will be non-null: */
struct drm_pending_vblank_event *event;
struct msm_fence_cb pageflip_cb;
#define PENDING_CURSOR 0x1
#define PENDING_FLIP 0x2
atomic_t pending;
/* the fb that we logically (from PoV of KMS API) hold a ref
* to. Which we may not yet be scanning out (we may still
* be scanning out previous in case of page_flip while waiting
* for gpu rendering to complete:
*/
struct drm_framebuffer *fb;
/* the fb that we currently hold a scanout ref to: */
struct drm_framebuffer *scanout_fb;
/* for unref'ing framebuffers after scanout completes: */
struct drm_flip_work unref_fb_work;
/* for unref'ing cursor bo's after scanout completes: */
struct drm_flip_work unref_cursor_work;
struct mdp_irq vblank;
struct mdp_irq err;
};
#define to_mdp4_crtc(x) container_of(x, struct mdp4_crtc, base)
static struct mdp4_kms *get_kms(struct drm_crtc *crtc)
{
struct msm_drm_private *priv = crtc->dev->dev_private;
return to_mdp4_kms(to_mdp_kms(priv->kms));
}
static void request_pending(struct drm_crtc *crtc, uint32_t pending)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
atomic_or(pending, &mdp4_crtc->pending);
mdp_irq_register(&get_kms(crtc)->base, &mdp4_crtc->vblank);
}
static void crtc_flush(struct drm_crtc *crtc)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
uint32_t i, flush = 0;
for (i = 0; i < ARRAY_SIZE(mdp4_crtc->planes); i++) {
struct drm_plane *plane = mdp4_crtc->planes[i];
if (plane) {
enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane);
flush |= pipe2flush(pipe_id);
}
}
flush |= ovlp2flush(mdp4_crtc->ovlp);
DBG("%s: flush=%08x", mdp4_crtc->name, flush);
mdp4_write(mdp4_kms, REG_MDP4_OVERLAY_FLUSH, flush);
}
static void update_fb(struct drm_crtc *crtc, struct drm_framebuffer *new_fb)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct drm_framebuffer *old_fb = mdp4_crtc->fb;
/* grab reference to incoming scanout fb: */
drm_framebuffer_reference(new_fb);
mdp4_crtc->base.primary->fb = new_fb;
mdp4_crtc->fb = new_fb;
if (old_fb)
drm_flip_work_queue(&mdp4_crtc->unref_fb_work, old_fb);
}
/* unlike update_fb(), take a ref to the new scanout fb *before* updating
* plane, then call this. Needed to ensure we don't unref the buffer that
* is actually still being scanned out.
*
* Note that this whole thing goes away with atomic.. since we can defer
* calling into driver until rendering is done.
*/
static void update_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_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 (mdp4_crtc->scanout_fb)
drm_flip_work_queue(&mdp4_crtc->unref_fb_work,
mdp4_crtc->scanout_fb);
mdp4_crtc->scanout_fb = fb;
/* enable vblank to complete flip: */
request_pending(crtc, PENDING_FLIP);
}
/* if file!=NULL, this is preclose potential cancel-flip path */
static void complete_flip(struct drm_crtc *crtc, struct drm_file *file)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct drm_pending_vblank_event *event;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
event = mdp4_crtc->event;
if (event) {
/* if regular vblank case (!file) or if cancel-flip from
* preclose on file that requested flip, then send the
* event:
*/
if (!file || (event->base.file_priv == file)) {
mdp4_crtc->event = NULL;
drm_send_vblank_event(dev, mdp4_crtc->id, event);
}
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
static void pageflip_cb(struct msm_fence_cb *cb)
{
struct mdp4_crtc *mdp4_crtc =
container_of(cb, struct mdp4_crtc, pageflip_cb);
struct drm_crtc *crtc = &mdp4_crtc->base;
struct drm_framebuffer *fb = crtc->primary->fb;
if (!fb)
return;
drm_framebuffer_reference(fb);
mdp4_plane_set_scanout(mdp4_crtc->plane, fb);
update_scanout(crtc, fb);
}
static void unref_fb_worker(struct drm_flip_work *work, void *val)
{
struct mdp4_crtc *mdp4_crtc =
container_of(work, struct mdp4_crtc, unref_fb_work);
struct drm_device *dev = mdp4_crtc->base.dev;
mutex_lock(&dev->mode_config.mutex);
drm_framebuffer_unreference(val);
mutex_unlock(&dev->mode_config.mutex);
}
static void unref_cursor_worker(struct drm_flip_work *work, void *val)
{
struct mdp4_crtc *mdp4_crtc =
container_of(work, struct mdp4_crtc, unref_cursor_work);
struct mdp4_kms *mdp4_kms = get_kms(&mdp4_crtc->base);
msm_gem_put_iova(val, mdp4_kms->id);
drm_gem_object_unreference_unlocked(val);
}
static void mdp4_crtc_destroy(struct drm_crtc *crtc)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
drm_crtc_cleanup(crtc);
drm_flip_work_cleanup(&mdp4_crtc->unref_fb_work);
drm_flip_work_cleanup(&mdp4_crtc->unref_cursor_work);
kfree(mdp4_crtc);
}
static void mdp4_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
bool enabled = (mode == DRM_MODE_DPMS_ON);
DBG("%s: mode=%d", mdp4_crtc->name, mode);
if (enabled != mdp4_crtc->enabled) {
if (enabled) {
mdp4_enable(mdp4_kms);
mdp_irq_register(&mdp4_kms->base, &mdp4_crtc->err);
} else {
mdp_irq_unregister(&mdp4_kms->base, &mdp4_crtc->err);
mdp4_disable(mdp4_kms);
}
mdp4_crtc->enabled = enabled;
}
}
static bool mdp4_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void blend_setup(struct drm_crtc *crtc)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
int i, ovlp = mdp4_crtc->ovlp;
uint32_t mixer_cfg = 0;
static const enum mdp_mixer_stage_id stages[] = {
STAGE_BASE, STAGE0, STAGE1, STAGE2, STAGE3,
};
/* statically (for now) map planes to mixer stage (z-order): */
static const int idxs[] = {
[VG1] = 1,
[VG2] = 2,
[RGB1] = 0,
[RGB2] = 0,
[RGB3] = 0,
[VG3] = 3,
[VG4] = 4,
};
bool alpha[4]= { false, false, false, false };
/* Don't rely on value read back from hw, but instead use our
* own shadowed value. Possibly disable/reenable looses the
* previous value and goes back to power-on default?
*/
mixer_cfg = mdp4_kms->mixer_cfg;
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW0(ovlp), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW1(ovlp), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH0(ovlp), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH1(ovlp), 0);
for (i = 0; i < ARRAY_SIZE(mdp4_crtc->planes); i++) {
struct drm_plane *plane = mdp4_crtc->planes[i];
if (plane) {
enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane);
int idx = idxs[pipe_id];
if (idx > 0) {
const struct mdp_format *format =
to_mdp_format(msm_framebuffer_format(plane->fb));
alpha[idx-1] = format->alpha_enable;
}
mixer_cfg = mixercfg(mixer_cfg, mdp4_crtc->mixer,
pipe_id, stages[idx]);
}
}
/* this shouldn't happen.. and seems to cause underflow: */
WARN_ON(!mixer_cfg);
for (i = 0; i < 4; i++) {
uint32_t op;
if (alpha[i]) {
op = MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_PIXEL) |
MDP4_OVLP_STAGE_OP_BG_ALPHA(FG_PIXEL) |
MDP4_OVLP_STAGE_OP_BG_INV_ALPHA;
} else {
op = MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_CONST) |
MDP4_OVLP_STAGE_OP_BG_ALPHA(BG_CONST);
}
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_FG_ALPHA(ovlp, i), 0xff);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_BG_ALPHA(ovlp, i), 0x00);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_OP(ovlp, i), op);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_CO3(ovlp, i), 1);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW0(ovlp, i), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW1(ovlp, i), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH0(ovlp, i), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH1(ovlp, i), 0);
}
mdp4_kms->mixer_cfg = mixer_cfg;
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, mixer_cfg);
}
static int mdp4_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
int x, int y,
struct drm_framebuffer *old_fb)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
enum mdp4_dma dma = mdp4_crtc->dma;
int ret, ovlp = mdp4_crtc->ovlp;
mode = adjusted_mode;
DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
mdp4_crtc->name, mode->base.id, mode->name,
mode->vrefresh, mode->clock,
mode->hdisplay, mode->hsync_start,
mode->hsync_end, mode->htotal,
mode->vdisplay, mode->vsync_start,
mode->vsync_end, mode->vtotal,
mode->type, mode->flags);
/* grab extra ref for update_scanout() */
drm_framebuffer_reference(crtc->primary->fb);
ret = mdp4_plane_mode_set(mdp4_crtc->plane, crtc, crtc->primary->fb,
0, 0, mode->hdisplay, mode->vdisplay,
x << 16, y << 16,
mode->hdisplay << 16, mode->vdisplay << 16);
if (ret) {
drm_framebuffer_unreference(crtc->primary->fb);
dev_err(crtc->dev->dev, "%s: failed to set mode on plane: %d\n",
mdp4_crtc->name, ret);
return ret;
}
mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_SIZE(dma),
MDP4_DMA_SRC_SIZE_WIDTH(mode->hdisplay) |
MDP4_DMA_SRC_SIZE_HEIGHT(mode->vdisplay));
/* take data from pipe: */
mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_BASE(dma), 0);
mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_STRIDE(dma),
crtc->primary->fb->pitches[0]);
mdp4_write(mdp4_kms, REG_MDP4_DMA_DST_SIZE(dma),
MDP4_DMA_DST_SIZE_WIDTH(0) |
MDP4_DMA_DST_SIZE_HEIGHT(0));
mdp4_write(mdp4_kms, REG_MDP4_OVLP_BASE(ovlp), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_SIZE(ovlp),
MDP4_OVLP_SIZE_WIDTH(mode->hdisplay) |
MDP4_OVLP_SIZE_HEIGHT(mode->vdisplay));
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STRIDE(ovlp),
crtc->primary->fb->pitches[0]);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CFG(ovlp), 1);
if (dma == DMA_E) {
mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(0), 0x00ff0000);
mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(1), 0x00ff0000);
mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(2), 0x00ff0000);
}
update_fb(crtc, crtc->primary->fb);
update_scanout(crtc, crtc->primary->fb);
return 0;
}
static void mdp4_crtc_prepare(struct drm_crtc *crtc)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
DBG("%s", mdp4_crtc->name);
/* make sure we hold a ref to mdp clks while setting up mode: */
drm_crtc_vblank_get(crtc);
mdp4_enable(get_kms(crtc));
mdp4_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
}
static void mdp4_crtc_commit(struct drm_crtc *crtc)
{
mdp4_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
crtc_flush(crtc);
/* drop the ref to mdp clk's that we got in prepare: */
mdp4_disable(get_kms(crtc));
drm_crtc_vblank_put(crtc);
}
static int mdp4_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct drm_plane *plane = mdp4_crtc->plane;
struct drm_display_mode *mode = &crtc->mode;
int ret;
/* grab extra ref for update_scanout() */
drm_framebuffer_reference(crtc->primary->fb);
ret = mdp4_plane_mode_set(plane, crtc, crtc->primary->fb,
0, 0, mode->hdisplay, mode->vdisplay,
x << 16, y << 16,
mode->hdisplay << 16, mode->vdisplay << 16);
if (ret) {
drm_framebuffer_unreference(crtc->primary->fb);
return ret;
}
update_fb(crtc, crtc->primary->fb);
update_scanout(crtc, crtc->primary->fb);
return 0;
}
static void mdp4_crtc_load_lut(struct drm_crtc *crtc)
{
}
static int mdp4_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *new_fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct drm_gem_object *obj;
unsigned long flags;
if (mdp4_crtc->event) {
dev_err(dev->dev, "already pending flip!\n");
return -EBUSY;
}
obj = msm_framebuffer_bo(new_fb, 0);
spin_lock_irqsave(&dev->event_lock, flags);
mdp4_crtc->event = event;
spin_unlock_irqrestore(&dev->event_lock, flags);
update_fb(crtc, new_fb);
return msm_gem_queue_inactive_cb(obj, &mdp4_crtc->pageflip_cb);
}
static int mdp4_crtc_set_property(struct drm_crtc *crtc,
struct drm_property *property, uint64_t val)
{
// XXX
return -EINVAL;
}
#define CURSOR_WIDTH 64
#define CURSOR_HEIGHT 64
/* called from IRQ to update cursor related registers (if needed). The
* cursor registers, other than x/y position, appear not to be double
* buffered, and changing them other than from vblank seems to trigger
* underflow.
*/
static void update_cursor(struct drm_crtc *crtc)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
enum mdp4_dma dma = mdp4_crtc->dma;
unsigned long flags;
spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags);
if (mdp4_crtc->cursor.stale) {
struct drm_gem_object *next_bo = mdp4_crtc->cursor.next_bo;
struct drm_gem_object *prev_bo = mdp4_crtc->cursor.scanout_bo;
uint32_t iova = mdp4_crtc->cursor.next_iova;
if (next_bo) {
/* take a obj ref + iova ref when we start scanning out: */
drm_gem_object_reference(next_bo);
msm_gem_get_iova_locked(next_bo, mdp4_kms->id, &iova);
/* enable cursor: */
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_SIZE(dma),
MDP4_DMA_CURSOR_SIZE_WIDTH(mdp4_crtc->cursor.width) |
MDP4_DMA_CURSOR_SIZE_HEIGHT(mdp4_crtc->cursor.height));
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma), iova);
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BLEND_CONFIG(dma),
MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT(CURSOR_ARGB) |
MDP4_DMA_CURSOR_BLEND_CONFIG_CURSOR_EN);
} else {
/* disable cursor: */
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma),
mdp4_kms->blank_cursor_iova);
}
/* and drop the iova ref + obj rev when done scanning out: */
if (prev_bo)
drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, prev_bo);
mdp4_crtc->cursor.scanout_bo = next_bo;
mdp4_crtc->cursor.stale = false;
}
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_POS(dma),
MDP4_DMA_CURSOR_POS_X(mdp4_crtc->cursor.x) |
MDP4_DMA_CURSOR_POS_Y(mdp4_crtc->cursor.y));
spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags);
}
static int mdp4_crtc_cursor_set(struct drm_crtc *crtc,
struct drm_file *file_priv, uint32_t handle,
uint32_t width, uint32_t height)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
struct drm_device *dev = crtc->dev;
struct drm_gem_object *cursor_bo, *old_bo;
unsigned long flags;
uint32_t iova;
int ret;
if ((width > CURSOR_WIDTH) || (height > CURSOR_HEIGHT)) {
dev_err(dev->dev, "bad cursor size: %dx%d\n", width, height);
return -EINVAL;
}
if (handle) {
cursor_bo = drm_gem_object_lookup(dev, file_priv, handle);
if (!cursor_bo)
return -ENOENT;
} else {
cursor_bo = NULL;
}
if (cursor_bo) {
ret = msm_gem_get_iova(cursor_bo, mdp4_kms->id, &iova);
if (ret)
goto fail;
} else {
iova = 0;
}
spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags);
old_bo = mdp4_crtc->cursor.next_bo;
mdp4_crtc->cursor.next_bo = cursor_bo;
mdp4_crtc->cursor.next_iova = iova;
mdp4_crtc->cursor.width = width;
mdp4_crtc->cursor.height = height;
mdp4_crtc->cursor.stale = true;
spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags);
if (old_bo) {
/* drop our previous reference: */
drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, old_bo);
}
request_pending(crtc, PENDING_CURSOR);
return 0;
fail:
drm_gem_object_unreference_unlocked(cursor_bo);
return ret;
}
static int mdp4_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
unsigned long flags;
spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags);
mdp4_crtc->cursor.x = x;
mdp4_crtc->cursor.y = y;
spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags);
crtc_flush(crtc);
request_pending(crtc, PENDING_CURSOR);
return 0;
}
static const struct drm_crtc_funcs mdp4_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.destroy = mdp4_crtc_destroy,
.page_flip = mdp4_crtc_page_flip,
.set_property = mdp4_crtc_set_property,
.cursor_set = mdp4_crtc_cursor_set,
.cursor_move = mdp4_crtc_cursor_move,
};
static const struct drm_crtc_helper_funcs mdp4_crtc_helper_funcs = {
.dpms = mdp4_crtc_dpms,
.mode_fixup = mdp4_crtc_mode_fixup,
.mode_set = mdp4_crtc_mode_set,
.prepare = mdp4_crtc_prepare,
.commit = mdp4_crtc_commit,
.mode_set_base = mdp4_crtc_mode_set_base,
.load_lut = mdp4_crtc_load_lut,
};
static void mdp4_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
{
struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, vblank);
struct drm_crtc *crtc = &mdp4_crtc->base;
struct msm_drm_private *priv = crtc->dev->dev_private;
unsigned pending;
mdp_irq_unregister(&get_kms(crtc)->base, &mdp4_crtc->vblank);
pending = atomic_xchg(&mdp4_crtc->pending, 0);
if (pending & PENDING_FLIP) {
complete_flip(crtc, NULL);
drm_flip_work_commit(&mdp4_crtc->unref_fb_work, priv->wq);
}
if (pending & PENDING_CURSOR) {
update_cursor(crtc);
drm_flip_work_commit(&mdp4_crtc->unref_cursor_work, priv->wq);
}
}
static void mdp4_crtc_err_irq(struct mdp_irq *irq, uint32_t irqstatus)
{
struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, err);
struct drm_crtc *crtc = &mdp4_crtc->base;
DBG("%s: error: %08x", mdp4_crtc->name, irqstatus);
crtc_flush(crtc);
}
uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
return mdp4_crtc->vblank.irqmask;
}
void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file)
{
DBG("cancel: %p", file);
complete_flip(crtc, file);
}
/* set dma config, ie. the format the encoder wants. */
void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
mdp4_write(mdp4_kms, REG_MDP4_DMA_CONFIG(mdp4_crtc->dma), config);
}
/* set interface for routing crtc->encoder: */
void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf, int mixer)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
struct mdp4_kms *mdp4_kms = get_kms(crtc);
uint32_t intf_sel;
intf_sel = mdp4_read(mdp4_kms, REG_MDP4_DISP_INTF_SEL);
switch (mdp4_crtc->dma) {
case DMA_P:
intf_sel &= ~MDP4_DISP_INTF_SEL_PRIM__MASK;
intf_sel |= MDP4_DISP_INTF_SEL_PRIM(intf);
break;
case DMA_S:
intf_sel &= ~MDP4_DISP_INTF_SEL_SEC__MASK;
intf_sel |= MDP4_DISP_INTF_SEL_SEC(intf);
break;
case DMA_E:
intf_sel &= ~MDP4_DISP_INTF_SEL_EXT__MASK;
intf_sel |= MDP4_DISP_INTF_SEL_EXT(intf);
break;
}
if (intf == INTF_DSI_VIDEO) {
intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_CMD;
intf_sel |= MDP4_DISP_INTF_SEL_DSI_VIDEO;
} else if (intf == INTF_DSI_CMD) {
intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_VIDEO;
intf_sel |= MDP4_DISP_INTF_SEL_DSI_CMD;
}
mdp4_crtc->mixer = mixer;
blend_setup(crtc);
DBG("%s: intf_sel=%08x", mdp4_crtc->name, intf_sel);
mdp4_write(mdp4_kms, REG_MDP4_DISP_INTF_SEL, intf_sel);
}
static void set_attach(struct drm_crtc *crtc, enum mdp4_pipe pipe_id,
struct drm_plane *plane)
{
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
BUG_ON(pipe_id >= ARRAY_SIZE(mdp4_crtc->planes));
if (mdp4_crtc->planes[pipe_id] == plane)
return;
mdp4_crtc->planes[pipe_id] = plane;
blend_setup(crtc);
if (mdp4_crtc->enabled && (plane != mdp4_crtc->plane))
crtc_flush(crtc);
}
void mdp4_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane)
{
set_attach(crtc, mdp4_plane_pipe(plane), plane);
}
void mdp4_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane)
{
/* don't actually detatch our primary plane: */
if (to_mdp4_crtc(crtc)->plane == plane)
return;
set_attach(crtc, mdp4_plane_pipe(plane), NULL);
}
static const char *dma_names[] = {
"DMA_P", "DMA_S", "DMA_E",
};
/* initialize crtc */
struct drm_crtc *mdp4_crtc_init(struct drm_device *dev,
struct drm_plane *plane, int id, int ovlp_id,
enum mdp4_dma dma_id)
{
struct drm_crtc *crtc = NULL;
struct mdp4_crtc *mdp4_crtc;
int ret;
mdp4_crtc = kzalloc(sizeof(*mdp4_crtc), GFP_KERNEL);
if (!mdp4_crtc) {
ret = -ENOMEM;
goto fail;
}
crtc = &mdp4_crtc->base;
mdp4_crtc->plane = plane;
mdp4_crtc->id = id;
mdp4_crtc->ovlp = ovlp_id;
mdp4_crtc->dma = dma_id;
mdp4_crtc->vblank.irqmask = dma2irq(mdp4_crtc->dma);
mdp4_crtc->vblank.irq = mdp4_crtc_vblank_irq;
mdp4_crtc->err.irqmask = dma2err(mdp4_crtc->dma);
mdp4_crtc->err.irq = mdp4_crtc_err_irq;
snprintf(mdp4_crtc->name, sizeof(mdp4_crtc->name), "%s:%d",
dma_names[dma_id], ovlp_id);
spin_lock_init(&mdp4_crtc->cursor.lock);
ret = drm_flip_work_init(&mdp4_crtc->unref_fb_work, 16,
"unref fb", unref_fb_worker);
if (ret)
goto fail;
ret = drm_flip_work_init(&mdp4_crtc->unref_cursor_work, 64,
"unref cursor", unref_cursor_worker);
INIT_FENCE_CB(&mdp4_crtc->pageflip_cb, pageflip_cb);
drm_crtc_init_with_planes(dev, crtc, plane, NULL, &mdp4_crtc_funcs);
drm_crtc_helper_add(crtc, &mdp4_crtc_helper_funcs);
mdp4_plane_install_properties(mdp4_crtc->plane, &crtc->base);
return crtc;
fail:
if (crtc)
mdp4_crtc_destroy(crtc);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,303 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp4_kms.h"
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
struct mdp4_dtv_encoder {
struct drm_encoder base;
struct clk *src_clk;
struct clk *hdmi_clk;
struct clk *mdp_clk;
unsigned long int pixclock;
bool enabled;
uint32_t bsc;
};
#define to_mdp4_dtv_encoder(x) container_of(x, struct mdp4_dtv_encoder, base)
static struct mdp4_kms *get_kms(struct drm_encoder *encoder)
{
struct msm_drm_private *priv = encoder->dev->dev_private;
return to_mdp4_kms(to_mdp_kms(priv->kms));
}
#ifdef CONFIG_MSM_BUS_SCALING
#include <mach/board.h>
/* not ironically named at all.. no, really.. */
static void bs_init(struct mdp4_dtv_encoder *mdp4_dtv_encoder)
{
struct drm_device *dev = mdp4_dtv_encoder->base.dev;
struct lcdc_platform_data *dtv_pdata = mdp4_find_pdata("dtv.0");
if (!dtv_pdata) {
dev_err(dev->dev, "could not find dtv pdata\n");
return;
}
if (dtv_pdata->bus_scale_table) {
mdp4_dtv_encoder->bsc = msm_bus_scale_register_client(
dtv_pdata->bus_scale_table);
DBG("bus scale client: %08x", mdp4_dtv_encoder->bsc);
DBG("lcdc_power_save: %p", dtv_pdata->lcdc_power_save);
if (dtv_pdata->lcdc_power_save)
dtv_pdata->lcdc_power_save(1);
}
}
static void bs_fini(struct mdp4_dtv_encoder *mdp4_dtv_encoder)
{
if (mdp4_dtv_encoder->bsc) {
msm_bus_scale_unregister_client(mdp4_dtv_encoder->bsc);
mdp4_dtv_encoder->bsc = 0;
}
}
static void bs_set(struct mdp4_dtv_encoder *mdp4_dtv_encoder, int idx)
{
if (mdp4_dtv_encoder->bsc) {
DBG("set bus scaling: %d", idx);
msm_bus_scale_client_update_request(mdp4_dtv_encoder->bsc, idx);
}
}
#else
static void bs_init(struct mdp4_dtv_encoder *mdp4_dtv_encoder) {}
static void bs_fini(struct mdp4_dtv_encoder *mdp4_dtv_encoder) {}
static void bs_set(struct mdp4_dtv_encoder *mdp4_dtv_encoder, int idx) {}
#endif
static void mdp4_dtv_encoder_destroy(struct drm_encoder *encoder)
{
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
bs_fini(mdp4_dtv_encoder);
drm_encoder_cleanup(encoder);
kfree(mdp4_dtv_encoder);
}
static const struct drm_encoder_funcs mdp4_dtv_encoder_funcs = {
.destroy = mdp4_dtv_encoder_destroy,
};
static void mdp4_dtv_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
struct mdp4_kms *mdp4_kms = get_kms(encoder);
bool enabled = (mode == DRM_MODE_DPMS_ON);
DBG("mode=%d", mode);
if (enabled == mdp4_dtv_encoder->enabled)
return;
if (enabled) {
unsigned long pc = mdp4_dtv_encoder->pixclock;
int ret;
bs_set(mdp4_dtv_encoder, 1);
DBG("setting src_clk=%lu", pc);
ret = clk_set_rate(mdp4_dtv_encoder->src_clk, pc);
if (ret)
dev_err(dev->dev, "failed to set src_clk to %lu: %d\n", pc, ret);
clk_prepare_enable(mdp4_dtv_encoder->src_clk);
ret = clk_prepare_enable(mdp4_dtv_encoder->hdmi_clk);
if (ret)
dev_err(dev->dev, "failed to enable hdmi_clk: %d\n", ret);
ret = clk_prepare_enable(mdp4_dtv_encoder->mdp_clk);
if (ret)
dev_err(dev->dev, "failed to enabled mdp_clk: %d\n", ret);
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 1);
} else {
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0);
/*
* Wait for a vsync so we know the ENABLE=0 latched before
* the (connector) source of the vsync's gets disabled,
* otherwise we end up in a funny state if we re-enable
* before the disable latches, which results that some of
* the settings changes for the new modeset (like new
* scanout buffer) don't latch properly..
*/
mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_EXTERNAL_VSYNC);
clk_disable_unprepare(mdp4_dtv_encoder->src_clk);
clk_disable_unprepare(mdp4_dtv_encoder->hdmi_clk);
clk_disable_unprepare(mdp4_dtv_encoder->mdp_clk);
bs_set(mdp4_dtv_encoder, 0);
}
mdp4_dtv_encoder->enabled = enabled;
}
static bool mdp4_dtv_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void mdp4_dtv_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
struct mdp4_kms *mdp4_kms = get_kms(encoder);
uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol;
uint32_t display_v_start, display_v_end;
uint32_t hsync_start_x, hsync_end_x;
mode = adjusted_mode;
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
mode->base.id, mode->name,
mode->vrefresh, mode->clock,
mode->hdisplay, mode->hsync_start,
mode->hsync_end, mode->htotal,
mode->vdisplay, mode->vsync_start,
mode->vsync_end, mode->vtotal,
mode->type, mode->flags);
mdp4_dtv_encoder->pixclock = mode->clock * 1000;
DBG("pixclock=%lu", mdp4_dtv_encoder->pixclock);
ctrl_pol = 0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
ctrl_pol |= MDP4_DTV_CTRL_POLARITY_HSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
ctrl_pol |= MDP4_DTV_CTRL_POLARITY_VSYNC_LOW;
/* probably need to get DATA_EN polarity from panel.. */
dtv_hsync_skew = 0; /* get this from panel? */
hsync_start_x = (mode->htotal - mode->hsync_start);
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
vsync_period = mode->vtotal * mode->htotal;
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew;
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1;
mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_CTRL,
MDP4_DTV_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) |
MDP4_DTV_HSYNC_CTRL_PERIOD(mode->htotal));
mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_PERIOD, vsync_period);
mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_LEN, vsync_len);
mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_HCTRL,
MDP4_DTV_DISPLAY_HCTRL_START(hsync_start_x) |
MDP4_DTV_DISPLAY_HCTRL_END(hsync_end_x));
mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VSTART, display_v_start);
mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VEND, display_v_end);
mdp4_write(mdp4_kms, REG_MDP4_DTV_BORDER_CLR, 0);
mdp4_write(mdp4_kms, REG_MDP4_DTV_UNDERFLOW_CLR,
MDP4_DTV_UNDERFLOW_CLR_ENABLE_RECOVERY |
MDP4_DTV_UNDERFLOW_CLR_COLOR(0xff));
mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_SKEW, dtv_hsync_skew);
mdp4_write(mdp4_kms, REG_MDP4_DTV_CTRL_POLARITY, ctrl_pol);
mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_HCTL,
MDP4_DTV_ACTIVE_HCTL_START(0) |
MDP4_DTV_ACTIVE_HCTL_END(0));
mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VSTART, 0);
mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VEND, 0);
}
static void mdp4_dtv_encoder_prepare(struct drm_encoder *encoder)
{
mdp4_dtv_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
static void mdp4_dtv_encoder_commit(struct drm_encoder *encoder)
{
mdp4_crtc_set_config(encoder->crtc,
MDP4_DMA_CONFIG_R_BPC(BPC8) |
MDP4_DMA_CONFIG_G_BPC(BPC8) |
MDP4_DMA_CONFIG_B_BPC(BPC8) |
MDP4_DMA_CONFIG_PACK(0x21));
mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 1);
mdp4_dtv_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static const struct drm_encoder_helper_funcs mdp4_dtv_encoder_helper_funcs = {
.dpms = mdp4_dtv_encoder_dpms,
.mode_fixup = mdp4_dtv_encoder_mode_fixup,
.mode_set = mdp4_dtv_encoder_mode_set,
.prepare = mdp4_dtv_encoder_prepare,
.commit = mdp4_dtv_encoder_commit,
};
long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate)
{
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
return clk_round_rate(mdp4_dtv_encoder->src_clk, rate);
}
/* initialize encoder */
struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev)
{
struct drm_encoder *encoder = NULL;
struct mdp4_dtv_encoder *mdp4_dtv_encoder;
int ret;
mdp4_dtv_encoder = kzalloc(sizeof(*mdp4_dtv_encoder), GFP_KERNEL);
if (!mdp4_dtv_encoder) {
ret = -ENOMEM;
goto fail;
}
encoder = &mdp4_dtv_encoder->base;
drm_encoder_init(dev, encoder, &mdp4_dtv_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(encoder, &mdp4_dtv_encoder_helper_funcs);
mdp4_dtv_encoder->src_clk = devm_clk_get(dev->dev, "src_clk");
if (IS_ERR(mdp4_dtv_encoder->src_clk)) {
dev_err(dev->dev, "failed to get src_clk\n");
ret = PTR_ERR(mdp4_dtv_encoder->src_clk);
goto fail;
}
mdp4_dtv_encoder->hdmi_clk = devm_clk_get(dev->dev, "hdmi_clk");
if (IS_ERR(mdp4_dtv_encoder->hdmi_clk)) {
dev_err(dev->dev, "failed to get hdmi_clk\n");
ret = PTR_ERR(mdp4_dtv_encoder->hdmi_clk);
goto fail;
}
mdp4_dtv_encoder->mdp_clk = devm_clk_get(dev->dev, "mdp_clk");
if (IS_ERR(mdp4_dtv_encoder->mdp_clk)) {
dev_err(dev->dev, "failed to get mdp_clk\n");
ret = PTR_ERR(mdp4_dtv_encoder->mdp_clk);
goto fail;
}
bs_init(mdp4_dtv_encoder);
return encoder;
fail:
if (encoder)
mdp4_dtv_encoder_destroy(encoder);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_drv.h"
#include "mdp4_kms.h"
void mdp4_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask)
{
mdp4_write(to_mdp4_kms(mdp_kms), REG_MDP4_INTR_ENABLE, irqmask);
}
static void mdp4_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus)
{
DRM_ERROR("errors: %08x\n", irqstatus);
}
void mdp4_irq_preinstall(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, 0xffffffff);
}
int mdp4_irq_postinstall(struct msm_kms *kms)
{
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
struct mdp4_kms *mdp4_kms = to_mdp4_kms(mdp_kms);
struct mdp_irq *error_handler = &mdp4_kms->error_handler;
error_handler->irq = mdp4_irq_error_handler;
error_handler->irqmask = MDP4_IRQ_PRIMARY_INTF_UDERRUN |
MDP4_IRQ_EXTERNAL_INTF_UDERRUN;
mdp_irq_register(mdp_kms, error_handler);
return 0;
}
void mdp4_irq_uninstall(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, 0x00000000);
}
irqreturn_t mdp4_irq(struct msm_kms *kms)
{
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
struct mdp4_kms *mdp4_kms = to_mdp4_kms(mdp_kms);
struct drm_device *dev = mdp4_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
unsigned int id;
uint32_t status;
status = mdp4_read(mdp4_kms, REG_MDP4_INTR_STATUS);
mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, status);
VERB("status=%08x", status);
mdp_dispatch_irqs(mdp_kms, status);
for (id = 0; id < priv->num_crtcs; id++)
if (status & mdp4_crtc_vblank(priv->crtcs[id]))
drm_handle_vblank(dev, id);
return IRQ_HANDLED;
}
int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
mdp_update_vblank_mask(to_mdp_kms(kms),
mdp4_crtc_vblank(crtc), true);
return 0;
}
void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
mdp_update_vblank_mask(to_mdp_kms(kms),
mdp4_crtc_vblank(crtc), false);
}

View file

@ -0,0 +1,506 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_drv.h"
#include "msm_mmu.h"
#include "mdp4_kms.h"
static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev);
static int mdp4_hw_init(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
struct drm_device *dev = mdp4_kms->dev;
uint32_t version, major, minor, dmap_cfg, vg_cfg;
unsigned long clk;
int ret = 0;
pm_runtime_get_sync(dev->dev);
mdp4_enable(mdp4_kms);
version = mdp4_read(mdp4_kms, REG_MDP4_VERSION);
mdp4_disable(mdp4_kms);
major = FIELD(version, MDP4_VERSION_MAJOR);
minor = FIELD(version, MDP4_VERSION_MINOR);
DBG("found MDP4 version v%d.%d", major, minor);
if (major != 4) {
dev_err(dev->dev, "unexpected MDP version: v%d.%d\n",
major, minor);
ret = -ENXIO;
goto out;
}
mdp4_kms->rev = minor;
if (mdp4_kms->dsi_pll_vdda) {
if ((mdp4_kms->rev == 2) || (mdp4_kms->rev == 4)) {
ret = regulator_set_voltage(mdp4_kms->dsi_pll_vdda,
1200000, 1200000);
if (ret) {
dev_err(dev->dev,
"failed to set dsi_pll_vdda voltage: %d\n", ret);
goto out;
}
}
}
if (mdp4_kms->dsi_pll_vddio) {
if (mdp4_kms->rev == 2) {
ret = regulator_set_voltage(mdp4_kms->dsi_pll_vddio,
1800000, 1800000);
if (ret) {
dev_err(dev->dev,
"failed to set dsi_pll_vddio voltage: %d\n", ret);
goto out;
}
}
}
if (mdp4_kms->rev > 1) {
mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER0, 0x0707ffff);
mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER1, 0x03073f3f);
}
mdp4_write(mdp4_kms, REG_MDP4_PORTMAP_MODE, 0x3);
/* max read pending cmd config, 3 pending requests: */
mdp4_write(mdp4_kms, REG_MDP4_READ_CNFG, 0x02222);
clk = clk_get_rate(mdp4_kms->clk);
if ((mdp4_kms->rev >= 1) || (clk >= 90000000)) {
dmap_cfg = 0x47; /* 16 bytes-burst x 8 req */
vg_cfg = 0x47; /* 16 bytes-burs x 8 req */
} else {
dmap_cfg = 0x27; /* 8 bytes-burst x 8 req */
vg_cfg = 0x43; /* 16 bytes-burst x 4 req */
}
DBG("fetch config: dmap=%02x, vg=%02x", dmap_cfg, vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_P), dmap_cfg);
mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_E), dmap_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG1), vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG2), vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB1), vg_cfg);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB2), vg_cfg);
if (mdp4_kms->rev >= 2)
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG_UPDATE_METHOD, 1);
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, 0);
/* disable CSC matrix / YUV by default: */
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG1), 0);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG2), 0);
mdp4_write(mdp4_kms, REG_MDP4_DMA_P_OP_MODE, 0);
mdp4_write(mdp4_kms, REG_MDP4_DMA_S_OP_MODE, 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(1), 0);
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(2), 0);
if (mdp4_kms->rev > 1)
mdp4_write(mdp4_kms, REG_MDP4_RESET_STATUS, 1);
out:
pm_runtime_put_sync(dev->dev);
return ret;
}
static long mdp4_round_pixclk(struct msm_kms *kms, unsigned long rate,
struct drm_encoder *encoder)
{
/* if we had >1 encoder, we'd need something more clever: */
return mdp4_dtv_round_pixclk(encoder, rate);
}
static void mdp4_preclose(struct msm_kms *kms, struct drm_file *file)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
struct msm_drm_private *priv = mdp4_kms->dev->dev_private;
unsigned i;
for (i = 0; i < priv->num_crtcs; i++)
mdp4_crtc_cancel_pending_flip(priv->crtcs[i], file);
}
static void mdp4_destroy(struct msm_kms *kms)
{
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
if (mdp4_kms->blank_cursor_iova)
msm_gem_put_iova(mdp4_kms->blank_cursor_bo, mdp4_kms->id);
if (mdp4_kms->blank_cursor_bo)
drm_gem_object_unreference_unlocked(mdp4_kms->blank_cursor_bo);
kfree(mdp4_kms);
}
static const struct mdp_kms_funcs kms_funcs = {
.base = {
.hw_init = mdp4_hw_init,
.irq_preinstall = mdp4_irq_preinstall,
.irq_postinstall = mdp4_irq_postinstall,
.irq_uninstall = mdp4_irq_uninstall,
.irq = mdp4_irq,
.enable_vblank = mdp4_enable_vblank,
.disable_vblank = mdp4_disable_vblank,
.get_format = mdp_get_format,
.round_pixclk = mdp4_round_pixclk,
.preclose = mdp4_preclose,
.destroy = mdp4_destroy,
},
.set_irqmask = mdp4_set_irqmask,
};
int mdp4_disable(struct mdp4_kms *mdp4_kms)
{
DBG("");
clk_disable_unprepare(mdp4_kms->clk);
if (mdp4_kms->pclk)
clk_disable_unprepare(mdp4_kms->pclk);
clk_disable_unprepare(mdp4_kms->lut_clk);
if (mdp4_kms->axi_clk)
clk_disable_unprepare(mdp4_kms->axi_clk);
return 0;
}
int mdp4_enable(struct mdp4_kms *mdp4_kms)
{
DBG("");
clk_prepare_enable(mdp4_kms->clk);
if (mdp4_kms->pclk)
clk_prepare_enable(mdp4_kms->pclk);
clk_prepare_enable(mdp4_kms->lut_clk);
if (mdp4_kms->axi_clk)
clk_prepare_enable(mdp4_kms->axi_clk);
return 0;
}
#ifdef CONFIG_OF
static struct drm_panel *detect_panel(struct drm_device *dev, const char *name)
{
struct device_node *n;
struct drm_panel *panel = NULL;
n = of_parse_phandle(dev->dev->of_node, name, 0);
if (n) {
panel = of_drm_find_panel(n);
if (!panel)
panel = ERR_PTR(-EPROBE_DEFER);
}
return panel;
}
#else
static struct drm_panel *detect_panel(struct drm_device *dev, const char *name)
{
// ??? maybe use a module param to specify which panel is attached?
}
#endif
static int modeset_init(struct mdp4_kms *mdp4_kms)
{
struct drm_device *dev = mdp4_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
struct drm_plane *plane;
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_panel *panel;
struct hdmi *hdmi;
int ret;
/* construct non-private planes: */
plane = mdp4_plane_init(dev, VG1, false);
if (IS_ERR(plane)) {
dev_err(dev->dev, "failed to construct plane for VG1\n");
ret = PTR_ERR(plane);
goto fail;
}
priv->planes[priv->num_planes++] = plane;
plane = mdp4_plane_init(dev, VG2, false);
if (IS_ERR(plane)) {
dev_err(dev->dev, "failed to construct plane for VG2\n");
ret = PTR_ERR(plane);
goto fail;
}
priv->planes[priv->num_planes++] = plane;
/*
* Setup the LCDC/LVDS path: RGB2 -> DMA_P -> LCDC -> LVDS:
*/
panel = detect_panel(dev, "qcom,lvds-panel");
if (IS_ERR(panel)) {
ret = PTR_ERR(panel);
dev_err(dev->dev, "failed to detect LVDS panel: %d\n", ret);
goto fail;
}
plane = mdp4_plane_init(dev, RGB2, true);
if (IS_ERR(plane)) {
dev_err(dev->dev, "failed to construct plane for RGB2\n");
ret = PTR_ERR(plane);
goto fail;
}
crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 0, DMA_P);
if (IS_ERR(crtc)) {
dev_err(dev->dev, "failed to construct crtc for DMA_P\n");
ret = PTR_ERR(crtc);
goto fail;
}
encoder = mdp4_lcdc_encoder_init(dev, panel);
if (IS_ERR(encoder)) {
dev_err(dev->dev, "failed to construct LCDC encoder\n");
ret = PTR_ERR(encoder);
goto fail;
}
/* LCDC can be hooked to DMA_P: */
encoder->possible_crtcs = 1 << priv->num_crtcs;
priv->crtcs[priv->num_crtcs++] = crtc;
priv->encoders[priv->num_encoders++] = encoder;
connector = mdp4_lvds_connector_init(dev, panel, encoder);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
dev_err(dev->dev, "failed to initialize LVDS connector: %d\n", ret);
goto fail;
}
priv->connectors[priv->num_connectors++] = connector;
/*
* Setup DTV/HDMI path: RGB1 -> DMA_E -> DTV -> HDMI:
*/
plane = mdp4_plane_init(dev, RGB1, true);
if (IS_ERR(plane)) {
dev_err(dev->dev, "failed to construct plane for RGB1\n");
ret = PTR_ERR(plane);
goto fail;
}
crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 1, DMA_E);
if (IS_ERR(crtc)) {
dev_err(dev->dev, "failed to construct crtc for DMA_E\n");
ret = PTR_ERR(crtc);
goto fail;
}
encoder = mdp4_dtv_encoder_init(dev);
if (IS_ERR(encoder)) {
dev_err(dev->dev, "failed to construct DTV encoder\n");
ret = PTR_ERR(encoder);
goto fail;
}
/* DTV can be hooked to DMA_E: */
encoder->possible_crtcs = 1 << priv->num_crtcs;
priv->crtcs[priv->num_crtcs++] = crtc;
priv->encoders[priv->num_encoders++] = encoder;
hdmi = hdmi_init(dev, encoder);
if (IS_ERR(hdmi)) {
ret = PTR_ERR(hdmi);
dev_err(dev->dev, "failed to initialize HDMI: %d\n", ret);
goto fail;
}
return 0;
fail:
return ret;
}
static const char *iommu_ports[] = {
"mdp_port0_cb0", "mdp_port1_cb0",
};
struct msm_kms *mdp4_kms_init(struct drm_device *dev)
{
struct platform_device *pdev = dev->platformdev;
struct mdp4_platform_config *config = mdp4_get_config(pdev);
struct mdp4_kms *mdp4_kms;
struct msm_kms *kms = NULL;
struct msm_mmu *mmu;
int ret;
mdp4_kms = kzalloc(sizeof(*mdp4_kms), GFP_KERNEL);
if (!mdp4_kms) {
dev_err(dev->dev, "failed to allocate kms\n");
ret = -ENOMEM;
goto fail;
}
mdp_kms_init(&mdp4_kms->base, &kms_funcs);
kms = &mdp4_kms->base.base;
mdp4_kms->dev = dev;
mdp4_kms->mmio = msm_ioremap(pdev, NULL, "MDP4");
if (IS_ERR(mdp4_kms->mmio)) {
ret = PTR_ERR(mdp4_kms->mmio);
goto fail;
}
mdp4_kms->dsi_pll_vdda =
devm_regulator_get_optional(&pdev->dev, "dsi_pll_vdda");
if (IS_ERR(mdp4_kms->dsi_pll_vdda))
mdp4_kms->dsi_pll_vdda = NULL;
mdp4_kms->dsi_pll_vddio =
devm_regulator_get_optional(&pdev->dev, "dsi_pll_vddio");
if (IS_ERR(mdp4_kms->dsi_pll_vddio))
mdp4_kms->dsi_pll_vddio = NULL;
mdp4_kms->vdd = devm_regulator_get_exclusive(&pdev->dev, "vdd");
if (IS_ERR(mdp4_kms->vdd))
mdp4_kms->vdd = NULL;
if (mdp4_kms->vdd) {
ret = regulator_enable(mdp4_kms->vdd);
if (ret) {
dev_err(dev->dev, "failed to enable regulator vdd: %d\n", ret);
goto fail;
}
}
mdp4_kms->clk = devm_clk_get(&pdev->dev, "core_clk");
if (IS_ERR(mdp4_kms->clk)) {
dev_err(dev->dev, "failed to get core_clk\n");
ret = PTR_ERR(mdp4_kms->clk);
goto fail;
}
mdp4_kms->pclk = devm_clk_get(&pdev->dev, "iface_clk");
if (IS_ERR(mdp4_kms->pclk))
mdp4_kms->pclk = NULL;
// XXX if (rev >= MDP_REV_42) { ???
mdp4_kms->lut_clk = devm_clk_get(&pdev->dev, "lut_clk");
if (IS_ERR(mdp4_kms->lut_clk)) {
dev_err(dev->dev, "failed to get lut_clk\n");
ret = PTR_ERR(mdp4_kms->lut_clk);
goto fail;
}
mdp4_kms->axi_clk = devm_clk_get(&pdev->dev, "mdp_axi_clk");
if (IS_ERR(mdp4_kms->axi_clk)) {
dev_err(dev->dev, "failed to get axi_clk\n");
ret = PTR_ERR(mdp4_kms->axi_clk);
goto fail;
}
clk_set_rate(mdp4_kms->clk, config->max_clk);
clk_set_rate(mdp4_kms->lut_clk, config->max_clk);
/* make sure things are off before attaching iommu (bootloader could
* have left things on, in which case we'll start getting faults if
* we don't disable):
*/
mdp4_enable(mdp4_kms);
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0);
mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 0);
mdp4_disable(mdp4_kms);
mdelay(16);
if (config->iommu) {
mmu = msm_iommu_new(&pdev->dev, config->iommu);
if (IS_ERR(mmu)) {
ret = PTR_ERR(mmu);
goto fail;
}
ret = mmu->funcs->attach(mmu, iommu_ports,
ARRAY_SIZE(iommu_ports));
if (ret)
goto fail;
} else {
dev_info(dev->dev, "no iommu, fallback to phys "
"contig buffers for scanout\n");
mmu = NULL;
}
mdp4_kms->id = msm_register_mmu(dev, mmu);
if (mdp4_kms->id < 0) {
ret = mdp4_kms->id;
dev_err(dev->dev, "failed to register mdp4 iommu: %d\n", ret);
goto fail;
}
ret = modeset_init(mdp4_kms);
if (ret) {
dev_err(dev->dev, "modeset_init failed: %d\n", ret);
goto fail;
}
mutex_lock(&dev->struct_mutex);
mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC);
mutex_unlock(&dev->struct_mutex);
if (IS_ERR(mdp4_kms->blank_cursor_bo)) {
ret = PTR_ERR(mdp4_kms->blank_cursor_bo);
dev_err(dev->dev, "could not allocate blank-cursor bo: %d\n", ret);
mdp4_kms->blank_cursor_bo = NULL;
goto fail;
}
ret = msm_gem_get_iova(mdp4_kms->blank_cursor_bo, mdp4_kms->id,
&mdp4_kms->blank_cursor_iova);
if (ret) {
dev_err(dev->dev, "could not pin blank-cursor bo: %d\n", ret);
goto fail;
}
return kms;
fail:
if (kms)
mdp4_destroy(kms);
return ERR_PTR(ret);
}
static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev)
{
static struct mdp4_platform_config config = {};
#ifdef CONFIG_OF
/* TODO */
config.max_clk = 266667000;
config.iommu = iommu_domain_alloc(&platform_bus_type);
#else
if (cpu_is_apq8064())
config.max_clk = 266667000;
else
config.max_clk = 200000000;
config.iommu = msm_get_iommu_domain(DISPLAY_READ_DOMAIN);
#endif
return &config;
}

View file

@ -0,0 +1,256 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __MDP4_KMS_H__
#define __MDP4_KMS_H__
#include "msm_drv.h"
#include "msm_kms.h"
#include "mdp/mdp_kms.h"
#include "mdp4.xml.h"
#include "drm_panel.h"
struct mdp4_kms {
struct mdp_kms base;
struct drm_device *dev;
int rev;
/* Shadow value for MDP4_LAYERMIXER_IN_CFG.. since setup for all
* crtcs/encoders is in one shared register, we need to update it
* via read/modify/write. But to avoid getting confused by power-
* on-default values after resume, use this shadow value instead:
*/
uint32_t mixer_cfg;
/* mapper-id used to request GEM buffer mapped for scanout: */
int id;
void __iomem *mmio;
struct regulator *dsi_pll_vdda;
struct regulator *dsi_pll_vddio;
struct regulator *vdd;
struct clk *clk;
struct clk *pclk;
struct clk *lut_clk;
struct clk *axi_clk;
struct mdp_irq error_handler;
/* empty/blank cursor bo to use when cursor is "disabled" */
struct drm_gem_object *blank_cursor_bo;
uint32_t blank_cursor_iova;
};
#define to_mdp4_kms(x) container_of(x, struct mdp4_kms, base)
/* platform config data (ie. from DT, or pdata) */
struct mdp4_platform_config {
struct iommu_domain *iommu;
uint32_t max_clk;
};
static inline void mdp4_write(struct mdp4_kms *mdp4_kms, u32 reg, u32 data)
{
msm_writel(data, mdp4_kms->mmio + reg);
}
static inline u32 mdp4_read(struct mdp4_kms *mdp4_kms, u32 reg)
{
return msm_readl(mdp4_kms->mmio + reg);
}
static inline uint32_t pipe2flush(enum mdp4_pipe pipe)
{
switch (pipe) {
case VG1: return MDP4_OVERLAY_FLUSH_VG1;
case VG2: return MDP4_OVERLAY_FLUSH_VG2;
case RGB1: return MDP4_OVERLAY_FLUSH_RGB1;
case RGB2: return MDP4_OVERLAY_FLUSH_RGB2;
default: return 0;
}
}
static inline uint32_t ovlp2flush(int ovlp)
{
switch (ovlp) {
case 0: return MDP4_OVERLAY_FLUSH_OVLP0;
case 1: return MDP4_OVERLAY_FLUSH_OVLP1;
default: return 0;
}
}
static inline uint32_t dma2irq(enum mdp4_dma dma)
{
switch (dma) {
case DMA_P: return MDP4_IRQ_DMA_P_DONE;
case DMA_S: return MDP4_IRQ_DMA_S_DONE;
case DMA_E: return MDP4_IRQ_DMA_E_DONE;
default: return 0;
}
}
static inline uint32_t dma2err(enum mdp4_dma dma)
{
switch (dma) {
case DMA_P: return MDP4_IRQ_PRIMARY_INTF_UDERRUN;
case DMA_S: return 0; // ???
case DMA_E: return MDP4_IRQ_EXTERNAL_INTF_UDERRUN;
default: return 0;
}
}
static inline uint32_t mixercfg(uint32_t mixer_cfg, int mixer,
enum mdp4_pipe pipe, enum mdp_mixer_stage_id stage)
{
switch (pipe) {
case VG1:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE0__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE0(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1);
break;
case VG2:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE1__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE1(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1);
break;
case RGB1:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE2__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE2(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1);
break;
case RGB2:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE3__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE3(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1);
break;
case RGB3:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE4__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE4(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1);
break;
case VG3:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE5__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE5(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1);
break;
case VG4:
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE6__MASK |
MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1);
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE6(stage) |
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1);
break;
default:
WARN_ON("invalid pipe");
break;
}
return mixer_cfg;
}
int mdp4_disable(struct mdp4_kms *mdp4_kms);
int mdp4_enable(struct mdp4_kms *mdp4_kms);
void mdp4_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask);
void mdp4_irq_preinstall(struct msm_kms *kms);
int mdp4_irq_postinstall(struct msm_kms *kms);
void mdp4_irq_uninstall(struct msm_kms *kms);
irqreturn_t mdp4_irq(struct msm_kms *kms);
int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
static inline
uint32_t mdp4_get_formats(enum mdp4_pipe pipe_id, uint32_t *pixel_formats,
uint32_t max_formats)
{
/* TODO when we have YUV, we need to filter supported formats
* based on pipe_id..
*/
return mdp_get_formats(pixel_formats, max_formats);
}
void mdp4_plane_install_properties(struct drm_plane *plane,
struct drm_mode_object *obj);
void mdp4_plane_set_scanout(struct drm_plane *plane,
struct drm_framebuffer *fb);
int mdp4_plane_mode_set(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h);
enum mdp4_pipe mdp4_plane_pipe(struct drm_plane *plane);
struct drm_plane *mdp4_plane_init(struct drm_device *dev,
enum mdp4_pipe pipe_id, bool private_plane);
uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc);
void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file);
void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config);
void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf, int mixer);
void mdp4_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane);
void mdp4_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane);
struct drm_crtc *mdp4_crtc_init(struct drm_device *dev,
struct drm_plane *plane, int id, int ovlp_id,
enum mdp4_dma dma_id);
long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate);
struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev);
long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate);
struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev,
struct drm_panel *panel);
struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev,
struct drm_panel *panel, struct drm_encoder *encoder);
#ifdef CONFIG_COMMON_CLK
struct clk *mpd4_lvds_pll_init(struct drm_device *dev);
#else
static inline struct clk *mpd4_lvds_pll_init(struct drm_device *dev)
{
return ERR_PTR(-ENODEV);
}
#endif
#ifdef CONFIG_MSM_BUS_SCALING
static inline int match_dev_name(struct device *dev, void *data)
{
return !strcmp(dev_name(dev), data);
}
/* bus scaling data is associated with extra pointless platform devices,
* "dtv", etc.. this is a bit of a hack, but we need a way for encoders
* to find their pdata to make the bus-scaling stuff work.
*/
static inline void *mdp4_find_pdata(const char *devname)
{
struct device *dev;
dev = bus_find_device(&platform_bus_type, NULL,
(void *)devname, match_dev_name);
return dev ? dev->platform_data : NULL;
}
#endif
#endif /* __MDP4_KMS_H__ */

View file

@ -0,0 +1,506 @@
/*
* Copyright (C) 2014 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
* Author: Vinay Simha <vinaysimha@inforcecomputing.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp4_kms.h"
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
struct mdp4_lcdc_encoder {
struct drm_encoder base;
struct drm_panel *panel;
struct clk *lcdc_clk;
unsigned long int pixclock;
struct regulator *regs[3];
bool enabled;
uint32_t bsc;
};
#define to_mdp4_lcdc_encoder(x) container_of(x, struct mdp4_lcdc_encoder, base)
static struct mdp4_kms *get_kms(struct drm_encoder *encoder)
{
struct msm_drm_private *priv = encoder->dev->dev_private;
return to_mdp4_kms(to_mdp_kms(priv->kms));
}
#ifdef CONFIG_MSM_BUS_SCALING
#include <mach/board.h>
static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder)
{
struct drm_device *dev = mdp4_lcdc_encoder->base.dev;
struct lcdc_platform_data *lcdc_pdata = mdp4_find_pdata("lvds.0");
if (!lcdc_pdata) {
dev_err(dev->dev, "could not find lvds pdata\n");
return;
}
if (lcdc_pdata->bus_scale_table) {
mdp4_lcdc_encoder->bsc = msm_bus_scale_register_client(
lcdc_pdata->bus_scale_table);
DBG("lvds : bus scale client: %08x", mdp4_lcdc_encoder->bsc);
}
}
static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder)
{
if (mdp4_lcdc_encoder->bsc) {
msm_bus_scale_unregister_client(mdp4_lcdc_encoder->bsc);
mdp4_lcdc_encoder->bsc = 0;
}
}
static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx)
{
if (mdp4_lcdc_encoder->bsc) {
DBG("set bus scaling: %d", idx);
msm_bus_scale_client_update_request(mdp4_lcdc_encoder->bsc, idx);
}
}
#else
static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {}
static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {}
static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx) {}
#endif
static void mdp4_lcdc_encoder_destroy(struct drm_encoder *encoder)
{
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
to_mdp4_lcdc_encoder(encoder);
bs_fini(mdp4_lcdc_encoder);
drm_encoder_cleanup(encoder);
kfree(mdp4_lcdc_encoder);
}
static const struct drm_encoder_funcs mdp4_lcdc_encoder_funcs = {
.destroy = mdp4_lcdc_encoder_destroy,
};
/* this should probably be a helper: */
struct drm_connector *get_connector(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct drm_connector *connector;
list_for_each_entry(connector, &dev->mode_config.connector_list, head)
if (connector->encoder == encoder)
return connector;
return NULL;
}
static void setup_phy(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct drm_connector *connector = get_connector(encoder);
struct mdp4_kms *mdp4_kms = get_kms(encoder);
uint32_t lvds_intf = 0, lvds_phy_cfg0 = 0;
int bpp, nchan, swap;
if (!connector)
return;
bpp = 3 * connector->display_info.bpc;
if (!bpp)
bpp = 18;
/* TODO, these should come from panel somehow: */
nchan = 1;
swap = 0;
switch (bpp) {
case 24:
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x08) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x05) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x04) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x03));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x02) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x01) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x00));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x11) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x10) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0d) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0c));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0b) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0a) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x09));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x15));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x14) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x13) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x12));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(3),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1b) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x17) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x16) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0f));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(3),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0e) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x07) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x06));
if (nchan == 2) {
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE3_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
} else {
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
}
break;
case 18:
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x0a) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x07) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x06) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x05));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x04) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x03) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x02));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x13) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x12) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0f) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0e));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0d) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0c) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x0b));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2),
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) |
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x17));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2),
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x16) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x15) |
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x14));
if (nchan == 2) {
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
} else {
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
}
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_RGB_OUT;
break;
default:
dev_err(dev->dev, "unknown bpp: %d\n", bpp);
return;
}
switch (nchan) {
case 1:
lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0;
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN |
MDP4_LCDC_LVDS_INTF_CTL_MODE_SEL;
break;
case 2:
lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0 |
MDP4_LVDS_PHY_CFG0_CHANNEL1;
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_CLK_LANE_EN |
MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN;
break;
default:
dev_err(dev->dev, "unknown # of channels: %d\n", nchan);
return;
}
if (swap)
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH_SWAP;
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_ENABLE;
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_INTF_CTL, lvds_intf);
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG2, 0x30);
mb();
udelay(1);
lvds_phy_cfg0 |= MDP4_LVDS_PHY_CFG0_SERIALIZATION_ENBLE;
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0);
}
static void mdp4_lcdc_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
to_mdp4_lcdc_encoder(encoder);
struct mdp4_kms *mdp4_kms = get_kms(encoder);
struct drm_panel *panel = mdp4_lcdc_encoder->panel;
bool enabled = (mode == DRM_MODE_DPMS_ON);
int i, ret;
DBG("mode=%d", mode);
if (enabled == mdp4_lcdc_encoder->enabled)
return;
if (enabled) {
unsigned long pc = mdp4_lcdc_encoder->pixclock;
int ret;
bs_set(mdp4_lcdc_encoder, 1);
for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) {
ret = regulator_enable(mdp4_lcdc_encoder->regs[i]);
if (ret)
dev_err(dev->dev, "failed to enable regulator: %d\n", ret);
}
DBG("setting lcdc_clk=%lu", pc);
ret = clk_set_rate(mdp4_lcdc_encoder->lcdc_clk, pc);
if (ret)
dev_err(dev->dev, "failed to configure lcdc_clk: %d\n", ret);
ret = clk_prepare_enable(mdp4_lcdc_encoder->lcdc_clk);
if (ret)
dev_err(dev->dev, "failed to enable lcdc_clk: %d\n", ret);
if (panel)
drm_panel_enable(panel);
setup_phy(encoder);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 1);
} else {
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0);
if (panel)
drm_panel_disable(panel);
/*
* Wait for a vsync so we know the ENABLE=0 latched before
* the (connector) source of the vsync's gets disabled,
* otherwise we end up in a funny state if we re-enable
* before the disable latches, which results that some of
* the settings changes for the new modeset (like new
* scanout buffer) don't latch properly..
*/
mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_PRIMARY_VSYNC);
clk_disable_unprepare(mdp4_lcdc_encoder->lcdc_clk);
for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) {
ret = regulator_disable(mdp4_lcdc_encoder->regs[i]);
if (ret)
dev_err(dev->dev, "failed to disable regulator: %d\n", ret);
}
bs_set(mdp4_lcdc_encoder, 0);
}
mdp4_lcdc_encoder->enabled = enabled;
}
static bool mdp4_lcdc_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void mdp4_lcdc_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
to_mdp4_lcdc_encoder(encoder);
struct mdp4_kms *mdp4_kms = get_kms(encoder);
uint32_t lcdc_hsync_skew, vsync_period, vsync_len, ctrl_pol;
uint32_t display_v_start, display_v_end;
uint32_t hsync_start_x, hsync_end_x;
mode = adjusted_mode;
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
mode->base.id, mode->name,
mode->vrefresh, mode->clock,
mode->hdisplay, mode->hsync_start,
mode->hsync_end, mode->htotal,
mode->vdisplay, mode->vsync_start,
mode->vsync_end, mode->vtotal,
mode->type, mode->flags);
mdp4_lcdc_encoder->pixclock = mode->clock * 1000;
DBG("pixclock=%lu", mdp4_lcdc_encoder->pixclock);
ctrl_pol = 0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_HSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_VSYNC_LOW;
/* probably need to get DATA_EN polarity from panel.. */
lcdc_hsync_skew = 0; /* get this from panel? */
hsync_start_x = (mode->htotal - mode->hsync_start);
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
vsync_period = mode->vtotal * mode->htotal;
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + lcdc_hsync_skew;
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + lcdc_hsync_skew - 1;
mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_CTRL,
MDP4_LCDC_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) |
MDP4_LCDC_HSYNC_CTRL_PERIOD(mode->htotal));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_PERIOD, vsync_period);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_LEN, vsync_len);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_HCTRL,
MDP4_LCDC_DISPLAY_HCTRL_START(hsync_start_x) |
MDP4_LCDC_DISPLAY_HCTRL_END(hsync_end_x));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VSTART, display_v_start);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VEND, display_v_end);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_BORDER_CLR, 0);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_UNDERFLOW_CLR,
MDP4_LCDC_UNDERFLOW_CLR_ENABLE_RECOVERY |
MDP4_LCDC_UNDERFLOW_CLR_COLOR(0xff));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_SKEW, lcdc_hsync_skew);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_CTRL_POLARITY, ctrl_pol);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_HCTL,
MDP4_LCDC_ACTIVE_HCTL_START(0) |
MDP4_LCDC_ACTIVE_HCTL_END(0));
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VSTART, 0);
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VEND, 0);
}
static void mdp4_lcdc_encoder_prepare(struct drm_encoder *encoder)
{
mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
static void mdp4_lcdc_encoder_commit(struct drm_encoder *encoder)
{
/* TODO: hard-coded for 18bpp: */
mdp4_crtc_set_config(encoder->crtc,
MDP4_DMA_CONFIG_R_BPC(BPC6) |
MDP4_DMA_CONFIG_G_BPC(BPC6) |
MDP4_DMA_CONFIG_B_BPC(BPC6) |
MDP4_DMA_CONFIG_PACK_ALIGN_MSB |
MDP4_DMA_CONFIG_PACK(0x21) |
MDP4_DMA_CONFIG_DEFLKR_EN |
MDP4_DMA_CONFIG_DITHER_EN);
mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 0);
mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static const struct drm_encoder_helper_funcs mdp4_lcdc_encoder_helper_funcs = {
.dpms = mdp4_lcdc_encoder_dpms,
.mode_fixup = mdp4_lcdc_encoder_mode_fixup,
.mode_set = mdp4_lcdc_encoder_mode_set,
.prepare = mdp4_lcdc_encoder_prepare,
.commit = mdp4_lcdc_encoder_commit,
};
long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate)
{
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
to_mdp4_lcdc_encoder(encoder);
return clk_round_rate(mdp4_lcdc_encoder->lcdc_clk, rate);
}
/* initialize encoder */
struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev,
struct drm_panel *panel)
{
struct drm_encoder *encoder = NULL;
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder;
struct regulator *reg;
int ret;
mdp4_lcdc_encoder = kzalloc(sizeof(*mdp4_lcdc_encoder), GFP_KERNEL);
if (!mdp4_lcdc_encoder) {
ret = -ENOMEM;
goto fail;
}
mdp4_lcdc_encoder->panel = panel;
encoder = &mdp4_lcdc_encoder->base;
drm_encoder_init(dev, encoder, &mdp4_lcdc_encoder_funcs,
DRM_MODE_ENCODER_LVDS);
drm_encoder_helper_add(encoder, &mdp4_lcdc_encoder_helper_funcs);
/* TODO: do we need different pll in other cases? */
mdp4_lcdc_encoder->lcdc_clk = mpd4_lvds_pll_init(dev);
if (IS_ERR(mdp4_lcdc_encoder->lcdc_clk)) {
dev_err(dev->dev, "failed to get lvds_clk\n");
ret = PTR_ERR(mdp4_lcdc_encoder->lcdc_clk);
goto fail;
}
/* TODO: different regulators in other cases? */
reg = devm_regulator_get(dev->dev, "lvds-vccs-3p3v");
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
dev_err(dev->dev, "failed to get lvds-vccs-3p3v: %d\n", ret);
goto fail;
}
mdp4_lcdc_encoder->regs[0] = reg;
reg = devm_regulator_get(dev->dev, "lvds-pll-vdda");
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
dev_err(dev->dev, "failed to get lvds-pll-vdda: %d\n", ret);
goto fail;
}
mdp4_lcdc_encoder->regs[1] = reg;
reg = devm_regulator_get(dev->dev, "lvds-vdda");
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
dev_err(dev->dev, "failed to get lvds-vdda: %d\n", ret);
goto fail;
}
mdp4_lcdc_encoder->regs[2] = reg;
bs_init(mdp4_lcdc_encoder);
return encoder;
fail:
if (encoder)
mdp4_lcdc_encoder_destroy(encoder);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (C) 2014 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
* Author: Vinay Simha <vinaysimha@inforcecomputing.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/gpio.h>
#include "mdp4_kms.h"
struct mdp4_lvds_connector {
struct drm_connector base;
struct drm_encoder *encoder;
struct drm_panel *panel;
};
#define to_mdp4_lvds_connector(x) container_of(x, struct mdp4_lvds_connector, base)
static enum drm_connector_status mdp4_lvds_connector_detect(
struct drm_connector *connector, bool force)
{
struct mdp4_lvds_connector *mdp4_lvds_connector =
to_mdp4_lvds_connector(connector);
return mdp4_lvds_connector->panel ?
connector_status_connected :
connector_status_disconnected;
}
static void mdp4_lvds_connector_destroy(struct drm_connector *connector)
{
struct mdp4_lvds_connector *mdp4_lvds_connector =
to_mdp4_lvds_connector(connector);
struct drm_panel *panel = mdp4_lvds_connector->panel;
if (panel)
drm_panel_detach(panel);
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
kfree(mdp4_lvds_connector);
}
static int mdp4_lvds_connector_get_modes(struct drm_connector *connector)
{
struct mdp4_lvds_connector *mdp4_lvds_connector =
to_mdp4_lvds_connector(connector);
struct drm_panel *panel = mdp4_lvds_connector->panel;
int ret = 0;
if (panel)
ret = panel->funcs->get_modes(panel);
return ret;
}
static int mdp4_lvds_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct mdp4_lvds_connector *mdp4_lvds_connector =
to_mdp4_lvds_connector(connector);
struct drm_encoder *encoder = mdp4_lvds_connector->encoder;
long actual, requested;
requested = 1000 * mode->clock;
actual = mdp4_lcdc_round_pixclk(encoder, requested);
DBG("requested=%ld, actual=%ld", requested, actual);
if (actual != requested)
return MODE_CLOCK_RANGE;
return MODE_OK;
}
static struct drm_encoder *
mdp4_lvds_connector_best_encoder(struct drm_connector *connector)
{
struct mdp4_lvds_connector *mdp4_lvds_connector =
to_mdp4_lvds_connector(connector);
return mdp4_lvds_connector->encoder;
}
static const struct drm_connector_funcs mdp4_lvds_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = mdp4_lvds_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = mdp4_lvds_connector_destroy,
};
static const struct drm_connector_helper_funcs mdp4_lvds_connector_helper_funcs = {
.get_modes = mdp4_lvds_connector_get_modes,
.mode_valid = mdp4_lvds_connector_mode_valid,
.best_encoder = mdp4_lvds_connector_best_encoder,
};
/* initialize connector */
struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev,
struct drm_panel *panel, struct drm_encoder *encoder)
{
struct drm_connector *connector = NULL;
struct mdp4_lvds_connector *mdp4_lvds_connector;
int ret;
mdp4_lvds_connector = kzalloc(sizeof(*mdp4_lvds_connector), GFP_KERNEL);
if (!mdp4_lvds_connector) {
ret = -ENOMEM;
goto fail;
}
mdp4_lvds_connector->encoder = encoder;
mdp4_lvds_connector->panel = panel;
connector = &mdp4_lvds_connector->base;
drm_connector_init(dev, connector, &mdp4_lvds_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
drm_connector_helper_add(connector, &mdp4_lvds_connector_helper_funcs);
connector->polled = 0;
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
drm_connector_register(connector);
drm_mode_connector_attach_encoder(connector, encoder);
if (panel)
drm_panel_attach(panel, connector);
return connector;
fail:
if (connector)
mdp4_lvds_connector_destroy(connector);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,172 @@
/*
* Copyright (C) 2014 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include "mdp4_kms.h"
struct mdp4_lvds_pll {
struct clk_hw pll_hw;
struct drm_device *dev;
unsigned long pixclk;
};
#define to_mdp4_lvds_pll(x) container_of(x, struct mdp4_lvds_pll, pll_hw)
static struct mdp4_kms *get_kms(struct mdp4_lvds_pll *lvds_pll)
{
struct msm_drm_private *priv = lvds_pll->dev->dev_private;
return to_mdp4_kms(to_mdp_kms(priv->kms));
}
struct pll_rate {
unsigned long rate;
struct {
uint32_t val;
uint32_t reg;
} conf[32];
};
/* NOTE: keep sorted highest freq to lowest: */
static const struct pll_rate freqtbl[] = {
{ 72000000, {
{ 0x8f, REG_MDP4_LVDS_PHY_PLL_CTRL_1 },
{ 0x30, REG_MDP4_LVDS_PHY_PLL_CTRL_2 },
{ 0xc6, REG_MDP4_LVDS_PHY_PLL_CTRL_3 },
{ 0x10, REG_MDP4_LVDS_PHY_PLL_CTRL_5 },
{ 0x07, REG_MDP4_LVDS_PHY_PLL_CTRL_6 },
{ 0x62, REG_MDP4_LVDS_PHY_PLL_CTRL_7 },
{ 0x41, REG_MDP4_LVDS_PHY_PLL_CTRL_8 },
{ 0x0d, REG_MDP4_LVDS_PHY_PLL_CTRL_9 },
{ 0, 0 } }
},
};
static const struct pll_rate *find_rate(unsigned long rate)
{
int i;
for (i = 1; i < ARRAY_SIZE(freqtbl); i++)
if (rate > freqtbl[i].rate)
return &freqtbl[i-1];
return &freqtbl[i-1];
}
static int mpd4_lvds_pll_enable(struct clk_hw *hw)
{
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
struct mdp4_kms *mdp4_kms = get_kms(lvds_pll);
const struct pll_rate *pll_rate = find_rate(lvds_pll->pixclk);
int i;
DBG("pixclk=%lu (%lu)", lvds_pll->pixclk, pll_rate->rate);
if (WARN_ON(!pll_rate))
return -EINVAL;
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_PHY_RESET, 0x33);
for (i = 0; pll_rate->conf[i].reg; i++)
mdp4_write(mdp4_kms, pll_rate->conf[i].reg, pll_rate->conf[i].val);
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x01);
/* Wait until LVDS PLL is locked and ready */
while (!mdp4_read(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_LOCKED))
cpu_relax();
return 0;
}
static void mpd4_lvds_pll_disable(struct clk_hw *hw)
{
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
struct mdp4_kms *mdp4_kms = get_kms(lvds_pll);
DBG("");
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, 0x0);
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x0);
}
static unsigned long mpd4_lvds_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
return lvds_pll->pixclk;
}
static long mpd4_lvds_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
const struct pll_rate *pll_rate = find_rate(rate);
return pll_rate->rate;
}
static int mpd4_lvds_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
lvds_pll->pixclk = rate;
return 0;
}
static const struct clk_ops mpd4_lvds_pll_ops = {
.enable = mpd4_lvds_pll_enable,
.disable = mpd4_lvds_pll_disable,
.recalc_rate = mpd4_lvds_pll_recalc_rate,
.round_rate = mpd4_lvds_pll_round_rate,
.set_rate = mpd4_lvds_pll_set_rate,
};
static const char *mpd4_lvds_pll_parents[] = {
"pxo",
};
static struct clk_init_data pll_init = {
.name = "mpd4_lvds_pll",
.ops = &mpd4_lvds_pll_ops,
.parent_names = mpd4_lvds_pll_parents,
.num_parents = ARRAY_SIZE(mpd4_lvds_pll_parents),
};
struct clk *mpd4_lvds_pll_init(struct drm_device *dev)
{
struct mdp4_lvds_pll *lvds_pll;
struct clk *clk;
int ret;
lvds_pll = devm_kzalloc(dev->dev, sizeof(*lvds_pll), GFP_KERNEL);
if (!lvds_pll) {
ret = -ENOMEM;
goto fail;
}
lvds_pll->dev = dev;
lvds_pll->pll_hw.init = &pll_init;
clk = devm_clk_register(dev->dev, &lvds_pll->pll_hw);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
goto fail;
}
return clk;
fail:
return ERR_PTR(ret);
}

View file

@ -0,0 +1,255 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp4_kms.h"
struct mdp4_plane {
struct drm_plane base;
const char *name;
enum mdp4_pipe pipe;
uint32_t nformats;
uint32_t formats[32];
bool enabled;
};
#define to_mdp4_plane(x) container_of(x, struct mdp4_plane, base)
static struct mdp4_kms *get_kms(struct drm_plane *plane)
{
struct msm_drm_private *priv = plane->dev->dev_private;
return to_mdp4_kms(to_mdp_kms(priv->kms));
}
static int mdp4_plane_update(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
mdp4_plane->enabled = true;
if (plane->fb)
drm_framebuffer_unreference(plane->fb);
drm_framebuffer_reference(fb);
return mdp4_plane_mode_set(plane, crtc, fb,
crtc_x, crtc_y, crtc_w, crtc_h,
src_x, src_y, src_w, src_h);
}
static int mdp4_plane_disable(struct drm_plane *plane)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
DBG("%s: disable", mdp4_plane->name);
if (plane->crtc)
mdp4_crtc_detach(plane->crtc, plane);
return 0;
}
static void mdp4_plane_destroy(struct drm_plane *plane)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
mdp4_plane_disable(plane);
drm_plane_cleanup(plane);
kfree(mdp4_plane);
}
/* helper to install properties which are common to planes and crtcs */
void mdp4_plane_install_properties(struct drm_plane *plane,
struct drm_mode_object *obj)
{
// XXX
}
int mdp4_plane_set_property(struct drm_plane *plane,
struct drm_property *property, uint64_t val)
{
// XXX
return -EINVAL;
}
static const struct drm_plane_funcs mdp4_plane_funcs = {
.update_plane = mdp4_plane_update,
.disable_plane = mdp4_plane_disable,
.destroy = mdp4_plane_destroy,
.set_property = mdp4_plane_set_property,
};
void mdp4_plane_set_scanout(struct drm_plane *plane,
struct drm_framebuffer *fb)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
struct mdp4_kms *mdp4_kms = get_kms(plane);
enum mdp4_pipe pipe = mdp4_plane->pipe;
uint32_t iova;
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_A(pipe),
MDP4_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) |
MDP4_PIPE_SRC_STRIDE_A_P1(fb->pitches[1]));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_B(pipe),
MDP4_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) |
MDP4_PIPE_SRC_STRIDE_B_P3(fb->pitches[3]));
msm_gem_get_iova(msm_framebuffer_bo(fb, 0), mdp4_kms->id, &iova);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP0_BASE(pipe), iova);
plane->fb = fb;
}
#define MDP4_VG_PHASE_STEP_DEFAULT 0x20000000
int mdp4_plane_mode_set(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
struct mdp4_kms *mdp4_kms = get_kms(plane);
enum mdp4_pipe pipe = mdp4_plane->pipe;
const struct mdp_format *format;
uint32_t op_mode = 0;
uint32_t phasex_step = MDP4_VG_PHASE_STEP_DEFAULT;
uint32_t phasey_step = MDP4_VG_PHASE_STEP_DEFAULT;
/* src values are in Q16 fixed point, convert to integer: */
src_x = src_x >> 16;
src_y = src_y >> 16;
src_w = src_w >> 16;
src_h = src_h >> 16;
DBG("%s: FB[%u] %u,%u,%u,%u -> CRTC[%u] %d,%d,%u,%u", mdp4_plane->name,
fb->base.id, src_x, src_y, src_w, src_h,
crtc->base.id, crtc_x, crtc_y, crtc_w, crtc_h);
if (src_w != crtc_w) {
op_mode |= MDP4_PIPE_OP_MODE_SCALEX_EN;
/* TODO calc phasex_step */
}
if (src_h != crtc_h) {
op_mode |= MDP4_PIPE_OP_MODE_SCALEY_EN;
/* TODO calc phasey_step */
}
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_SIZE(pipe),
MDP4_PIPE_SRC_SIZE_WIDTH(src_w) |
MDP4_PIPE_SRC_SIZE_HEIGHT(src_h));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_XY(pipe),
MDP4_PIPE_SRC_XY_X(src_x) |
MDP4_PIPE_SRC_XY_Y(src_y));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_SIZE(pipe),
MDP4_PIPE_DST_SIZE_WIDTH(crtc_w) |
MDP4_PIPE_DST_SIZE_HEIGHT(crtc_h));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_XY(pipe),
MDP4_PIPE_DST_XY_X(crtc_x) |
MDP4_PIPE_DST_XY_Y(crtc_y));
mdp4_plane_set_scanout(plane, fb);
format = to_mdp_format(msm_framebuffer_format(fb));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_FORMAT(pipe),
MDP4_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) |
MDP4_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) |
MDP4_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) |
MDP4_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) |
COND(format->alpha_enable, MDP4_PIPE_SRC_FORMAT_ALPHA_ENABLE) |
MDP4_PIPE_SRC_FORMAT_CPP(format->cpp - 1) |
MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) |
COND(format->unpack_tight, MDP4_PIPE_SRC_FORMAT_UNPACK_TIGHT));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_UNPACK(pipe),
MDP4_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) |
MDP4_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) |
MDP4_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) |
MDP4_PIPE_SRC_UNPACK_ELEM3(format->unpack[3]));
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(pipe), op_mode);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEX_STEP(pipe), phasex_step);
mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEY_STEP(pipe), phasey_step);
/* TODO detach from old crtc (if we had more than one) */
mdp4_crtc_attach(crtc, plane);
return 0;
}
static const char *pipe_names[] = {
"VG1", "VG2",
"RGB1", "RGB2", "RGB3",
"VG3", "VG4",
};
enum mdp4_pipe mdp4_plane_pipe(struct drm_plane *plane)
{
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
return mdp4_plane->pipe;
}
/* initialize plane */
struct drm_plane *mdp4_plane_init(struct drm_device *dev,
enum mdp4_pipe pipe_id, bool private_plane)
{
struct drm_plane *plane = NULL;
struct mdp4_plane *mdp4_plane;
int ret;
enum drm_plane_type type;
mdp4_plane = kzalloc(sizeof(*mdp4_plane), GFP_KERNEL);
if (!mdp4_plane) {
ret = -ENOMEM;
goto fail;
}
plane = &mdp4_plane->base;
mdp4_plane->pipe = pipe_id;
mdp4_plane->name = pipe_names[pipe_id];
mdp4_plane->nformats = mdp4_get_formats(pipe_id, mdp4_plane->formats,
ARRAY_SIZE(mdp4_plane->formats));
type = private_plane ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
drm_universal_plane_init(dev, plane, 0xff, &mdp4_plane_funcs,
mdp4_plane->formats, mdp4_plane->nformats,
type);
mdp4_plane_install_properties(plane, &plane->base);
return plane;
fail:
if (plane)
mdp4_plane_destroy(plane);
return ERR_PTR(ret);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,575 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp5_kms.h"
#include <drm/drm_mode.h>
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
#include "drm_flip_work.h"
struct mdp5_crtc {
struct drm_crtc base;
char name[8];
struct drm_plane *plane;
struct drm_plane *planes[8];
int id;
bool enabled;
/* which mixer/encoder we route output to: */
int mixer;
/* if there is a pending flip, these will be non-null: */
struct drm_pending_vblank_event *event;
struct msm_fence_cb pageflip_cb;
#define PENDING_CURSOR 0x1
#define PENDING_FLIP 0x2
atomic_t pending;
/* the fb that we logically (from PoV of KMS API) hold a ref
* to. Which we may not yet be scanning out (we may still
* be scanning out previous in case of page_flip while waiting
* for gpu rendering to complete:
*/
struct drm_framebuffer *fb;
/* the fb that we currently hold a scanout ref to: */
struct drm_framebuffer *scanout_fb;
/* for unref'ing framebuffers after scanout completes: */
struct drm_flip_work unref_fb_work;
struct mdp_irq vblank;
struct mdp_irq err;
};
#define to_mdp5_crtc(x) container_of(x, struct mdp5_crtc, base)
static struct mdp5_kms *get_kms(struct drm_crtc *crtc)
{
struct msm_drm_private *priv = crtc->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
static void request_pending(struct drm_crtc *crtc, uint32_t pending)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
atomic_or(pending, &mdp5_crtc->pending);
mdp_irq_register(&get_kms(crtc)->base, &mdp5_crtc->vblank);
}
static void crtc_flush(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;
uint32_t i, flush = 0;
for (i = 0; i < ARRAY_SIZE(mdp5_crtc->planes); i++) {
struct drm_plane *plane = mdp5_crtc->planes[i];
if (plane) {
enum mdp5_pipe pipe = mdp5_plane_pipe(plane);
flush |= pipe2flush(pipe);
}
}
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);
}
static void update_fb(struct drm_crtc *crtc, struct drm_framebuffer *new_fb)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct drm_framebuffer *old_fb = mdp5_crtc->fb;
/* grab reference to incoming scanout fb: */
drm_framebuffer_reference(new_fb);
mdp5_crtc->base.primary->fb = new_fb;
mdp5_crtc->fb = new_fb;
if (old_fb)
drm_flip_work_queue(&mdp5_crtc->unref_fb_work, old_fb);
}
/* unlike update_fb(), take a ref to the new scanout fb *before* updating
* plane, then call this. Needed to ensure we don't unref the buffer that
* is actually still being scanned out.
*
* Note that this whole thing goes away with atomic.. since we can defer
* calling into driver until rendering is done.
*/
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);
mdp5_crtc->scanout_fb = fb;
/* enable vblank to complete flip: */
request_pending(crtc, PENDING_FLIP);
}
/* if file!=NULL, this is preclose potential cancel-flip path */
static void complete_flip(struct drm_crtc *crtc, struct drm_file *file)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct drm_pending_vblank_event *event;
unsigned long flags, i;
spin_lock_irqsave(&dev->event_lock, flags);
event = mdp5_crtc->event;
if (event) {
/* if regular vblank case (!file) or if cancel-flip from
* preclose on file that requested flip, then send the
* event:
*/
if (!file || (event->base.file_priv == file)) {
mdp5_crtc->event = NULL;
drm_send_vblank_event(dev, mdp5_crtc->id, event);
}
}
spin_unlock_irqrestore(&dev->event_lock, flags);
for (i = 0; i < ARRAY_SIZE(mdp5_crtc->planes); i++) {
struct drm_plane *plane = mdp5_crtc->planes[i];
if (plane)
mdp5_plane_complete_flip(plane);
}
}
static void pageflip_cb(struct msm_fence_cb *cb)
{
struct mdp5_crtc *mdp5_crtc =
container_of(cb, struct mdp5_crtc, pageflip_cb);
struct drm_crtc *crtc = &mdp5_crtc->base;
struct drm_framebuffer *fb = mdp5_crtc->fb;
if (!fb)
return;
drm_framebuffer_reference(fb);
mdp5_plane_set_scanout(mdp5_crtc->plane, fb);
update_scanout(crtc, fb);
}
static void unref_fb_worker(struct drm_flip_work *work, void *val)
{
struct mdp5_crtc *mdp5_crtc =
container_of(work, struct mdp5_crtc, unref_fb_work);
struct drm_device *dev = mdp5_crtc->base.dev;
mutex_lock(&dev->mode_config.mutex);
drm_framebuffer_unreference(val);
mutex_unlock(&dev->mode_config.mutex);
}
static void mdp5_crtc_destroy(struct drm_crtc *crtc)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
drm_crtc_cleanup(crtc);
drm_flip_work_cleanup(&mdp5_crtc->unref_fb_work);
kfree(mdp5_crtc);
}
static void mdp5_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct mdp5_kms *mdp5_kms = get_kms(crtc);
bool enabled = (mode == DRM_MODE_DPMS_ON);
DBG("%s: mode=%d", mdp5_crtc->name, mode);
if (enabled != mdp5_crtc->enabled) {
if (enabled) {
mdp5_enable(mdp5_kms);
mdp_irq_register(&mdp5_kms->base, &mdp5_crtc->err);
} else {
mdp_irq_unregister(&mdp5_kms->base, &mdp5_crtc->err);
mdp5_disable(mdp5_kms);
}
mdp5_crtc->enabled = enabled;
}
}
static bool mdp5_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
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;
/*
* Hard-coded setup for now until I figure out how the
* layer-mixer works
*/
/* 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?
*/
/* 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);
}
static int mdp5_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
int x, int y,
struct drm_framebuffer *old_fb)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct mdp5_kms *mdp5_kms = get_kms(crtc);
int ret;
mode = adjusted_mode;
DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
mdp5_crtc->name, mode->base.id, mode->name,
mode->vrefresh, mode->clock,
mode->hdisplay, mode->hsync_start,
mode->hsync_end, mode->htotal,
mode->vdisplay, mode->vsync_start,
mode->vsync_end, mode->vtotal,
mode->type, mode->flags);
/* grab extra ref for update_scanout() */
drm_framebuffer_reference(crtc->primary->fb);
ret = mdp5_plane_mode_set(mdp5_crtc->plane, crtc, crtc->primary->fb,
0, 0, mode->hdisplay, mode->vdisplay,
x << 16, y << 16,
mode->hdisplay << 16, mode->vdisplay << 16);
if (ret) {
drm_framebuffer_unreference(crtc->primary->fb);
dev_err(crtc->dev->dev, "%s: failed to set mode on plane: %d\n",
mdp5_crtc->name, ret);
return ret;
}
mdp5_write(mdp5_kms, REG_MDP5_LM_OUT_SIZE(mdp5_crtc->id),
MDP5_LM_OUT_SIZE_WIDTH(mode->hdisplay) |
MDP5_LM_OUT_SIZE_HEIGHT(mode->vdisplay));
update_fb(crtc, crtc->primary->fb);
update_scanout(crtc, crtc->primary->fb);
return 0;
}
static void mdp5_crtc_prepare(struct drm_crtc *crtc)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
DBG("%s", mdp5_crtc->name);
/* make sure we hold a ref to mdp clks while setting up mode: */
mdp5_enable(get_kms(crtc));
mdp5_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
}
static void mdp5_crtc_commit(struct drm_crtc *crtc)
{
mdp5_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
crtc_flush(crtc);
/* drop the ref to mdp clk's that we got in prepare: */
mdp5_disable(get_kms(crtc));
}
static int mdp5_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct drm_plane *plane = mdp5_crtc->plane;
struct drm_display_mode *mode = &crtc->mode;
int ret;
/* grab extra ref for update_scanout() */
drm_framebuffer_reference(crtc->primary->fb);
ret = mdp5_plane_mode_set(plane, crtc, crtc->primary->fb,
0, 0, mode->hdisplay, mode->vdisplay,
x << 16, y << 16,
mode->hdisplay << 16, mode->vdisplay << 16);
if (ret) {
drm_framebuffer_unreference(crtc->primary->fb);
return ret;
}
update_fb(crtc, crtc->primary->fb);
update_scanout(crtc, crtc->primary->fb);
return 0;
}
static void mdp5_crtc_load_lut(struct drm_crtc *crtc)
{
}
static int mdp5_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *new_fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
struct drm_device *dev = crtc->dev;
struct drm_gem_object *obj;
unsigned long flags;
if (mdp5_crtc->event) {
dev_err(dev->dev, "already pending flip!\n");
return -EBUSY;
}
obj = msm_framebuffer_bo(new_fb, 0);
spin_lock_irqsave(&dev->event_lock, flags);
mdp5_crtc->event = event;
spin_unlock_irqrestore(&dev->event_lock, flags);
update_fb(crtc, new_fb);
return msm_gem_queue_inactive_cb(obj, &mdp5_crtc->pageflip_cb);
}
static int mdp5_crtc_set_property(struct drm_crtc *crtc,
struct drm_property *property, uint64_t val)
{
// XXX
return -EINVAL;
}
static const struct drm_crtc_funcs mdp5_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.destroy = mdp5_crtc_destroy,
.page_flip = mdp5_crtc_page_flip,
.set_property = mdp5_crtc_set_property,
};
static const struct drm_crtc_helper_funcs mdp5_crtc_helper_funcs = {
.dpms = mdp5_crtc_dpms,
.mode_fixup = mdp5_crtc_mode_fixup,
.mode_set = mdp5_crtc_mode_set,
.prepare = mdp5_crtc_prepare,
.commit = mdp5_crtc_commit,
.mode_set_base = mdp5_crtc_mode_set_base,
.load_lut = mdp5_crtc_load_lut,
};
static void mdp5_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
{
struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, vblank);
struct drm_crtc *crtc = &mdp5_crtc->base;
struct msm_drm_private *priv = crtc->dev->dev_private;
unsigned pending;
mdp_irq_unregister(&get_kms(crtc)->base, &mdp5_crtc->vblank);
pending = atomic_xchg(&mdp5_crtc->pending, 0);
if (pending & PENDING_FLIP) {
complete_flip(crtc, NULL);
drm_flip_work_commit(&mdp5_crtc->unref_fb_work, priv->wq);
}
}
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)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
return mdp5_crtc->vblank.irqmask;
}
void mdp5_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file)
{
DBG("cancel: %p", file);
complete_flip(crtc, file);
}
/* set interface for routing crtc->encoder: */
void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
enum mdp5_intf intf_id)
{
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 intf_sel;
/* now that we know what irq's we want: */
mdp5_crtc->err.irqmask = intf2err(intf);
mdp5_crtc->vblank.irqmask = intf2vblank(intf);
/* when called from modeset_init(), skip the rest until later: */
if (!mdp5_kms)
return;
intf_sel = mdp5_read(mdp5_kms, REG_MDP5_DISP_INTF_SEL);
switch (intf) {
case 0:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF0__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF0(intf_id);
break;
case 1:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF1__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF1(intf_id);
break;
case 2:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF2__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF2(intf_id);
break;
case 3:
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF3__MASK;
intf_sel |= MDP5_DISP_INTF_SEL_INTF3(intf_id);
break;
default:
BUG();
break;
}
blend_setup(crtc);
DBG("%s: intf_sel=%08x", mdp5_crtc->name, intf_sel);
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);
}
static void set_attach(struct drm_crtc *crtc, enum mdp5_pipe pipe_id,
struct drm_plane *plane)
{
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
BUG_ON(pipe_id >= ARRAY_SIZE(mdp5_crtc->planes));
if (mdp5_crtc->planes[pipe_id] == plane)
return;
mdp5_crtc->planes[pipe_id] = plane;
blend_setup(crtc);
if (mdp5_crtc->enabled && (plane != mdp5_crtc->plane))
crtc_flush(crtc);
}
void mdp5_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane)
{
set_attach(crtc, mdp5_plane_pipe(plane), plane);
}
void mdp5_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane)
{
/* don't actually detatch our primary plane: */
if (to_mdp5_crtc(crtc)->plane == plane)
return;
set_attach(crtc, mdp5_plane_pipe(plane), NULL);
}
/* initialize crtc */
struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
struct drm_plane *plane, int id)
{
struct drm_crtc *crtc = NULL;
struct mdp5_crtc *mdp5_crtc;
int ret;
mdp5_crtc = kzalloc(sizeof(*mdp5_crtc), GFP_KERNEL);
if (!mdp5_crtc) {
ret = -ENOMEM;
goto fail;
}
crtc = &mdp5_crtc->base;
mdp5_crtc->plane = plane;
mdp5_crtc->id = id;
mdp5_crtc->vblank.irq = mdp5_crtc_vblank_irq;
mdp5_crtc->err.irq = mdp5_crtc_err_irq;
snprintf(mdp5_crtc->name, sizeof(mdp5_crtc->name), "%s:%d",
pipe2name(mdp5_plane_pipe(plane)), id);
ret = drm_flip_work_init(&mdp5_crtc->unref_fb_work, 16,
"unref fb", unref_fb_worker);
if (ret)
goto fail;
INIT_FENCE_CB(&mdp5_crtc->pageflip_cb, pageflip_cb);
drm_crtc_init_with_planes(dev, crtc, plane, NULL, &mdp5_crtc_funcs);
drm_crtc_helper_add(crtc, &mdp5_crtc_helper_funcs);
mdp5_plane_install_properties(mdp5_crtc->plane, &crtc->base);
return crtc;
fail:
if (crtc)
mdp5_crtc_destroy(crtc);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,258 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp5_kms.h"
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
struct mdp5_encoder {
struct drm_encoder base;
int intf;
enum mdp5_intf intf_id;
bool enabled;
uint32_t bsc;
};
#define to_mdp5_encoder(x) container_of(x, struct mdp5_encoder, base)
static struct mdp5_kms *get_kms(struct drm_encoder *encoder)
{
struct msm_drm_private *priv = encoder->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
#ifdef CONFIG_MSM_BUS_SCALING
#include <mach/board.h>
#include <mach/msm_bus.h>
#include <mach/msm_bus_board.h>
#define MDP_BUS_VECTOR_ENTRY(ab_val, ib_val) \
{ \
.src = MSM_BUS_MASTER_MDP_PORT0, \
.dst = MSM_BUS_SLAVE_EBI_CH0, \
.ab = (ab_val), \
.ib = (ib_val), \
}
static struct msm_bus_vectors mdp_bus_vectors[] = {
MDP_BUS_VECTOR_ENTRY(0, 0),
MDP_BUS_VECTOR_ENTRY(2000000000, 2000000000),
};
static struct msm_bus_paths mdp_bus_usecases[] = { {
.num_paths = 1,
.vectors = &mdp_bus_vectors[0],
}, {
.num_paths = 1,
.vectors = &mdp_bus_vectors[1],
} };
static struct msm_bus_scale_pdata mdp_bus_scale_table = {
.usecase = mdp_bus_usecases,
.num_usecases = ARRAY_SIZE(mdp_bus_usecases),
.name = "mdss_mdp",
};
static void bs_init(struct mdp5_encoder *mdp5_encoder)
{
mdp5_encoder->bsc = msm_bus_scale_register_client(
&mdp_bus_scale_table);
DBG("bus scale client: %08x", mdp5_encoder->bsc);
}
static void bs_fini(struct mdp5_encoder *mdp5_encoder)
{
if (mdp5_encoder->bsc) {
msm_bus_scale_unregister_client(mdp5_encoder->bsc);
mdp5_encoder->bsc = 0;
}
}
static void bs_set(struct mdp5_encoder *mdp5_encoder, int idx)
{
if (mdp5_encoder->bsc) {
DBG("set bus scaling: %d", idx);
/* HACK: scaling down, and then immediately back up
* seems to leave things broken (underflow).. so
* never disable:
*/
idx = 1;
msm_bus_scale_client_update_request(mdp5_encoder->bsc, idx);
}
}
#else
static void bs_init(struct mdp5_encoder *mdp5_encoder) {}
static void bs_fini(struct mdp5_encoder *mdp5_encoder) {}
static void bs_set(struct mdp5_encoder *mdp5_encoder, int idx) {}
#endif
static void mdp5_encoder_destroy(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
bs_fini(mdp5_encoder);
drm_encoder_cleanup(encoder);
kfree(mdp5_encoder);
}
static const struct drm_encoder_funcs mdp5_encoder_funcs = {
.destroy = mdp5_encoder_destroy,
};
static void mdp5_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
int intf = mdp5_encoder->intf;
bool enabled = (mode == DRM_MODE_DPMS_ON);
DBG("mode=%d", mode);
if (enabled == mdp5_encoder->enabled)
return;
if (enabled) {
bs_set(mdp5_encoder, 1);
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intf), 1);
} else {
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intf), 0);
bs_set(mdp5_encoder, 0);
}
mdp5_encoder->enabled = enabled;
}
static bool mdp5_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void mdp5_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
struct mdp5_kms *mdp5_kms = get_kms(encoder);
int intf = mdp5_encoder->intf;
uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol;
uint32_t display_v_start, display_v_end;
uint32_t hsync_start_x, hsync_end_x;
uint32_t format;
mode = adjusted_mode;
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
mode->base.id, mode->name,
mode->vrefresh, mode->clock,
mode->hdisplay, mode->hsync_start,
mode->hsync_end, mode->htotal,
mode->vdisplay, mode->vsync_start,
mode->vsync_end, mode->vtotal,
mode->type, mode->flags);
ctrl_pol = 0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
ctrl_pol |= MDP5_INTF_POLARITY_CTL_HSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
ctrl_pol |= MDP5_INTF_POLARITY_CTL_VSYNC_LOW;
/* probably need to get DATA_EN polarity from panel.. */
dtv_hsync_skew = 0; /* get this from panel? */
format = 0x213f; /* get this from panel? */
hsync_start_x = (mode->htotal - mode->hsync_start);
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
vsync_period = mode->vtotal * mode->htotal;
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew;
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1;
mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_CTL(intf),
MDP5_INTF_HSYNC_CTL_PULSEW(mode->hsync_end - mode->hsync_start) |
MDP5_INTF_HSYNC_CTL_PERIOD(mode->htotal));
mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_PERIOD_F0(intf), vsync_period);
mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_LEN_F0(intf), vsync_len);
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_HCTL(intf),
MDP5_INTF_DISPLAY_HCTL_START(hsync_start_x) |
MDP5_INTF_DISPLAY_HCTL_END(hsync_end_x));
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VSTART_F0(intf), display_v_start);
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VEND_F0(intf), display_v_end);
mdp5_write(mdp5_kms, REG_MDP5_INTF_BORDER_COLOR(intf), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_UNDERFLOW_COLOR(intf), 0xff);
mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_SKEW(intf), dtv_hsync_skew);
mdp5_write(mdp5_kms, REG_MDP5_INTF_POLARITY_CTL(intf), ctrl_pol);
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_HCTL(intf),
MDP5_INTF_ACTIVE_HCTL_START(0) |
MDP5_INTF_ACTIVE_HCTL_END(0));
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VSTART_F0(intf), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VEND_F0(intf), 0);
mdp5_write(mdp5_kms, REG_MDP5_INTF_PANEL_FORMAT(intf), format);
mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(intf), 0x3); /* frame+line? */
}
static void mdp5_encoder_prepare(struct drm_encoder *encoder)
{
mdp5_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
}
static void mdp5_encoder_commit(struct drm_encoder *encoder)
{
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
mdp5_crtc_set_intf(encoder->crtc, mdp5_encoder->intf,
mdp5_encoder->intf_id);
mdp5_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
}
static const struct drm_encoder_helper_funcs mdp5_encoder_helper_funcs = {
.dpms = mdp5_encoder_dpms,
.mode_fixup = mdp5_encoder_mode_fixup,
.mode_set = mdp5_encoder_mode_set,
.prepare = mdp5_encoder_prepare,
.commit = mdp5_encoder_commit,
};
/* initialize encoder */
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev, int intf,
enum mdp5_intf intf_id)
{
struct drm_encoder *encoder = NULL;
struct mdp5_encoder *mdp5_encoder;
int ret;
mdp5_encoder = kzalloc(sizeof(*mdp5_encoder), GFP_KERNEL);
if (!mdp5_encoder) {
ret = -ENOMEM;
goto fail;
}
mdp5_encoder->intf = intf;
mdp5_encoder->intf_id = intf_id;
encoder = &mdp5_encoder->base;
drm_encoder_init(dev, encoder, &mdp5_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
drm_encoder_helper_add(encoder, &mdp5_encoder_helper_funcs);
bs_init(mdp5_encoder);
return encoder;
fail:
if (encoder)
mdp5_encoder_destroy(encoder);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,111 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_drv.h"
#include "mdp5_kms.h"
void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask)
{
mdp5_write(to_mdp5_kms(mdp_kms), REG_MDP5_INTR_EN, irqmask);
}
static void mdp5_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus)
{
DRM_ERROR("errors: %08x\n", irqstatus);
}
void mdp5_irq_preinstall(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, 0xffffffff);
}
int mdp5_irq_postinstall(struct msm_kms *kms)
{
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
struct mdp_irq *error_handler = &mdp5_kms->error_handler;
error_handler->irq = mdp5_irq_error_handler;
error_handler->irqmask = MDP5_IRQ_INTF0_UNDER_RUN |
MDP5_IRQ_INTF1_UNDER_RUN |
MDP5_IRQ_INTF2_UNDER_RUN |
MDP5_IRQ_INTF3_UNDER_RUN;
mdp_irq_register(mdp_kms, error_handler);
return 0;
}
void mdp5_irq_uninstall(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
mdp5_write(mdp5_kms, REG_MDP5_INTR_EN, 0x00000000);
}
static void mdp5_irq_mdp(struct mdp_kms *mdp_kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
struct drm_device *dev = mdp5_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
unsigned int id;
uint32_t status;
status = mdp5_read(mdp5_kms, REG_MDP5_INTR_STATUS);
mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, status);
VERB("status=%08x", status);
mdp_dispatch_irqs(mdp_kms, status);
for (id = 0; id < priv->num_crtcs; id++)
if (status & mdp5_crtc_vblank(priv->crtcs[id]))
drm_handle_vblank(dev, id);
}
irqreturn_t mdp5_irq(struct msm_kms *kms)
{
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
uint32_t intr;
intr = mdp5_read(mdp5_kms, REG_MDP5_HW_INTR_STATUS);
VERB("intr=%08x", intr);
if (intr & MDP5_HW_INTR_STATUS_INTR_MDP)
mdp5_irq_mdp(mdp_kms);
if (intr & MDP5_HW_INTR_STATUS_INTR_HDMI)
hdmi_irq(0, mdp5_kms->hdmi);
return IRQ_HANDLED;
}
int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
mdp_update_vblank_mask(to_mdp_kms(kms),
mdp5_crtc_vblank(crtc), true);
return 0;
}
void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
{
mdp_update_vblank_mask(to_mdp_kms(kms),
mdp5_crtc_vblank(crtc), false);
}

View file

@ -0,0 +1,491 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_drv.h"
#include "msm_mmu.h"
#include "mdp5_kms.h"
static const char *iommu_ports[] = {
"mdp_0",
};
static struct mdp5_platform_config *mdp5_get_config(struct platform_device *dev);
const struct mdp5_config *mdp5_cfg;
static const struct mdp5_config msm8x74_config = {
.name = "msm8x74",
.ctl = {
.count = 5,
.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
},
.pipe_vig = {
.count = 3,
.base = { 0x01200, 0x01600, 0x01a00 },
},
.pipe_rgb = {
.count = 3,
.base = { 0x01e00, 0x02200, 0x02600 },
},
.pipe_dma = {
.count = 2,
.base = { 0x02a00, 0x02e00 },
},
.lm = {
.count = 5,
.base = { 0x03200, 0x03600, 0x03a00, 0x03e00, 0x04200 },
},
.dspp = {
.count = 3,
.base = { 0x04600, 0x04a00, 0x04e00 },
},
.ad = {
.count = 2,
.base = { 0x13100, 0x13300 }, /* NOTE: no ad in v1.0 */
},
.intf = {
.count = 4,
.base = { 0x12500, 0x12700, 0x12900, 0x12b00 },
},
};
static const struct mdp5_config apq8084_config = {
.name = "apq8084",
.ctl = {
.count = 5,
.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
},
.pipe_vig = {
.count = 4,
.base = { 0x01200, 0x01600, 0x01a00, 0x01e00 },
},
.pipe_rgb = {
.count = 4,
.base = { 0x02200, 0x02600, 0x02a00, 0x02e00 },
},
.pipe_dma = {
.count = 2,
.base = { 0x03200, 0x03600 },
},
.lm = {
.count = 6,
.base = { 0x03a00, 0x03e00, 0x04200, 0x04600, 0x04a00, 0x04e00 },
},
.dspp = {
.count = 4,
.base = { 0x05200, 0x05600, 0x05a00, 0x05e00 },
},
.ad = {
.count = 3,
.base = { 0x13500, 0x13700, 0x13900 },
},
.intf = {
.count = 5,
.base = { 0x12500, 0x12700, 0x12900, 0x12b00, 0x12d00 },
},
};
struct mdp5_config_entry {
int revision;
const struct mdp5_config *config;
};
static const struct mdp5_config_entry mdp5_configs[] = {
{ .revision = 0, .config = &msm8x74_config },
{ .revision = 2, .config = &msm8x74_config },
{ .revision = 3, .config = &apq8084_config },
};
static int mdp5_select_hw_cfg(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct drm_device *dev = mdp5_kms->dev;
uint32_t version, major, minor;
int i, ret = 0;
mdp5_enable(mdp5_kms);
version = mdp5_read(mdp5_kms, REG_MDP5_MDP_VERSION);
mdp5_disable(mdp5_kms);
major = FIELD(version, MDP5_MDP_VERSION_MAJOR);
minor = FIELD(version, MDP5_MDP_VERSION_MINOR);
DBG("found MDP5 version v%d.%d", major, minor);
if (major != 1) {
dev_err(dev->dev, "unexpected MDP major version: v%d.%d\n",
major, minor);
ret = -ENXIO;
goto out;
}
mdp5_kms->rev = minor;
/* only after mdp5_cfg global pointer's init can we access the hw */
for (i = 0; i < ARRAY_SIZE(mdp5_configs); i++) {
if (mdp5_configs[i].revision != minor)
continue;
mdp5_kms->hw_cfg = mdp5_cfg = mdp5_configs[i].config;
break;
}
if (unlikely(!mdp5_kms->hw_cfg)) {
dev_err(dev->dev, "unexpected MDP minor revision: v%d.%d\n",
major, minor);
ret = -ENXIO;
goto out;
}
DBG("MDP5: %s config selected", mdp5_kms->hw_cfg->name);
return 0;
out:
return ret;
}
static int mdp5_hw_init(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct drm_device *dev = mdp5_kms->dev;
int i;
pm_runtime_get_sync(dev->dev);
/* Magic unknown register writes:
*
* W VBIF:0x004 00000001 (mdss_mdp.c:839)
* W MDP5:0x2e0 0xe9 (mdss_mdp.c:839)
* W MDP5:0x2e4 0x55 (mdss_mdp.c:839)
* W MDP5:0x3ac 0xc0000ccc (mdss_mdp.c:839)
* W MDP5:0x3b4 0xc0000ccc (mdss_mdp.c:839)
* W MDP5:0x3bc 0xcccccc (mdss_mdp.c:839)
* W MDP5:0x4a8 0xcccc0c0 (mdss_mdp.c:839)
* W MDP5:0x4b0 0xccccc0c0 (mdss_mdp.c:839)
* W MDP5:0x4b8 0xccccc000 (mdss_mdp.c:839)
*
* Downstream fbdev driver gets these register offsets/values
* from DT.. not really sure what these registers are or if
* different values for different boards/SoC's, etc. I guess
* they are the golden registers.
*
* Not setting these does not seem to cause any problem. But
* we may be getting lucky with the bootloader initializing
* them for us. OTOH, if we can always count on the bootloader
* setting the golden registers, then perhaps we don't need to
* care.
*/
mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, 0);
for (i = 0; i < mdp5_kms->hw_cfg->ctl.count; i++)
mdp5_write(mdp5_kms, REG_MDP5_CTL_OP(i), 0);
pm_runtime_put_sync(dev->dev);
return 0;
}
static long mdp5_round_pixclk(struct msm_kms *kms, unsigned long rate,
struct drm_encoder *encoder)
{
return rate;
}
static void mdp5_preclose(struct msm_kms *kms, struct drm_file *file)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct msm_drm_private *priv = mdp5_kms->dev->dev_private;
unsigned i;
for (i = 0; i < priv->num_crtcs; i++)
mdp5_crtc_cancel_pending_flip(priv->crtcs[i], file);
}
static void mdp5_destroy(struct msm_kms *kms)
{
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
struct msm_mmu *mmu = mdp5_kms->mmu;
if (mmu) {
mmu->funcs->detach(mmu, iommu_ports, ARRAY_SIZE(iommu_ports));
mmu->funcs->destroy(mmu);
}
kfree(mdp5_kms);
}
static const struct mdp_kms_funcs kms_funcs = {
.base = {
.hw_init = mdp5_hw_init,
.irq_preinstall = mdp5_irq_preinstall,
.irq_postinstall = mdp5_irq_postinstall,
.irq_uninstall = mdp5_irq_uninstall,
.irq = mdp5_irq,
.enable_vblank = mdp5_enable_vblank,
.disable_vblank = mdp5_disable_vblank,
.get_format = mdp_get_format,
.round_pixclk = mdp5_round_pixclk,
.preclose = mdp5_preclose,
.destroy = mdp5_destroy,
},
.set_irqmask = mdp5_set_irqmask,
};
int mdp5_disable(struct mdp5_kms *mdp5_kms)
{
DBG("");
clk_disable_unprepare(mdp5_kms->ahb_clk);
clk_disable_unprepare(mdp5_kms->axi_clk);
clk_disable_unprepare(mdp5_kms->core_clk);
clk_disable_unprepare(mdp5_kms->lut_clk);
return 0;
}
int mdp5_enable(struct mdp5_kms *mdp5_kms)
{
DBG("");
clk_prepare_enable(mdp5_kms->ahb_clk);
clk_prepare_enable(mdp5_kms->axi_clk);
clk_prepare_enable(mdp5_kms->core_clk);
clk_prepare_enable(mdp5_kms->lut_clk);
return 0;
}
static int modeset_init(struct mdp5_kms *mdp5_kms)
{
static const enum mdp5_pipe crtcs[] = {
SSPP_RGB0, SSPP_RGB1, SSPP_RGB2, SSPP_RGB3,
};
struct drm_device *dev = mdp5_kms->dev;
struct msm_drm_private *priv = dev->dev_private;
struct drm_encoder *encoder;
int i, ret;
/* construct CRTCs: */
for (i = 0; i < mdp5_kms->hw_cfg->pipe_rgb.count; i++) {
struct drm_plane *plane;
struct drm_crtc *crtc;
plane = mdp5_plane_init(dev, crtcs[i], true);
if (IS_ERR(plane)) {
ret = PTR_ERR(plane);
dev_err(dev->dev, "failed to construct plane for %s (%d)\n",
pipe2name(crtcs[i]), ret);
goto fail;
}
crtc = mdp5_crtc_init(dev, plane, i);
if (IS_ERR(crtc)) {
ret = PTR_ERR(crtc);
dev_err(dev->dev, "failed to construct crtc for %s (%d)\n",
pipe2name(crtcs[i]), ret);
goto fail;
}
priv->crtcs[priv->num_crtcs++] = crtc;
}
/* Construct encoder for HDMI: */
encoder = mdp5_encoder_init(dev, 3, INTF_HDMI);
if (IS_ERR(encoder)) {
dev_err(dev->dev, "failed to construct encoder\n");
ret = PTR_ERR(encoder);
goto fail;
}
/* NOTE: the vsync and error irq's are actually associated with
* the INTF/encoder.. the easiest way to deal with this (ie. what
* we do now) is assume a fixed relationship between crtc's and
* encoders. I'm not sure if there is ever a need to more freely
* assign crtcs to encoders, but if there is then we need to take
* care of error and vblank irq's that the crtc has registered,
* and also update user-requested vblank_mask.
*/
encoder->possible_crtcs = BIT(0);
mdp5_crtc_set_intf(priv->crtcs[0], 3, INTF_HDMI);
priv->encoders[priv->num_encoders++] = encoder;
/* Construct bridge/connector for HDMI: */
mdp5_kms->hdmi = hdmi_init(dev, encoder);
if (IS_ERR(mdp5_kms->hdmi)) {
ret = PTR_ERR(mdp5_kms->hdmi);
dev_err(dev->dev, "failed to initialize HDMI: %d\n", ret);
goto fail;
}
return 0;
fail:
return ret;
}
static int get_clk(struct platform_device *pdev, struct clk **clkp,
const char *name)
{
struct device *dev = &pdev->dev;
struct clk *clk = devm_clk_get(dev, name);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get %s (%ld)\n", name, PTR_ERR(clk));
return PTR_ERR(clk);
}
*clkp = clk;
return 0;
}
struct msm_kms *mdp5_kms_init(struct drm_device *dev)
{
struct platform_device *pdev = dev->platformdev;
struct mdp5_platform_config *config = mdp5_get_config(pdev);
struct mdp5_kms *mdp5_kms;
struct msm_kms *kms = NULL;
struct msm_mmu *mmu;
int i, ret;
mdp5_kms = kzalloc(sizeof(*mdp5_kms), GFP_KERNEL);
if (!mdp5_kms) {
dev_err(dev->dev, "failed to allocate kms\n");
ret = -ENOMEM;
goto fail;
}
mdp_kms_init(&mdp5_kms->base, &kms_funcs);
kms = &mdp5_kms->base.base;
mdp5_kms->dev = dev;
mdp5_kms->smp_blk_cnt = config->smp_blk_cnt;
mdp5_kms->mmio = msm_ioremap(pdev, "mdp_phys", "MDP5");
if (IS_ERR(mdp5_kms->mmio)) {
ret = PTR_ERR(mdp5_kms->mmio);
goto fail;
}
mdp5_kms->vbif = msm_ioremap(pdev, "vbif_phys", "VBIF");
if (IS_ERR(mdp5_kms->vbif)) {
ret = PTR_ERR(mdp5_kms->vbif);
goto fail;
}
mdp5_kms->vdd = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(mdp5_kms->vdd)) {
ret = PTR_ERR(mdp5_kms->vdd);
goto fail;
}
ret = regulator_enable(mdp5_kms->vdd);
if (ret) {
dev_err(dev->dev, "failed to enable regulator vdd: %d\n", ret);
goto fail;
}
ret = get_clk(pdev, &mdp5_kms->axi_clk, "bus_clk");
if (ret)
goto fail;
ret = get_clk(pdev, &mdp5_kms->ahb_clk, "iface_clk");
if (ret)
goto fail;
ret = get_clk(pdev, &mdp5_kms->src_clk, "core_clk_src");
if (ret)
goto fail;
ret = get_clk(pdev, &mdp5_kms->core_clk, "core_clk");
if (ret)
goto fail;
ret = get_clk(pdev, &mdp5_kms->lut_clk, "lut_clk");
if (ret)
goto fail;
ret = get_clk(pdev, &mdp5_kms->vsync_clk, "vsync_clk");
if (ret)
goto fail;
ret = clk_set_rate(mdp5_kms->src_clk, config->max_clk);
ret = mdp5_select_hw_cfg(kms);
if (ret)
goto fail;
/* make sure things are off before attaching iommu (bootloader could
* have left things on, in which case we'll start getting faults if
* we don't disable):
*/
mdp5_enable(mdp5_kms);
for (i = 0; i < mdp5_kms->hw_cfg->intf.count; i++)
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
mdp5_disable(mdp5_kms);
mdelay(16);
if (config->iommu) {
mmu = msm_iommu_new(&pdev->dev, config->iommu);
if (IS_ERR(mmu)) {
ret = PTR_ERR(mmu);
dev_err(dev->dev, "failed to init iommu: %d\n", ret);
goto fail;
}
ret = mmu->funcs->attach(mmu, iommu_ports,
ARRAY_SIZE(iommu_ports));
if (ret) {
dev_err(dev->dev, "failed to attach iommu: %d\n", ret);
mmu->funcs->destroy(mmu);
goto fail;
}
} else {
dev_info(dev->dev, "no iommu, fallback to phys "
"contig buffers for scanout\n");
mmu = NULL;
}
mdp5_kms->mmu = mmu;
mdp5_kms->id = msm_register_mmu(dev, mmu);
if (mdp5_kms->id < 0) {
ret = mdp5_kms->id;
dev_err(dev->dev, "failed to register mdp5 iommu: %d\n", ret);
goto fail;
}
ret = modeset_init(mdp5_kms);
if (ret) {
dev_err(dev->dev, "modeset_init failed: %d\n", ret);
goto fail;
}
return kms;
fail:
if (kms)
mdp5_destroy(kms);
return ERR_PTR(ret);
}
static struct mdp5_platform_config *mdp5_get_config(struct platform_device *dev)
{
static struct mdp5_platform_config config = {};
#ifdef CONFIG_OF
/* TODO */
#endif
config.iommu = iommu_domain_alloc(&platform_bus_type);
/* TODO hard-coded in downstream mdss, but should it be? */
config.max_clk = 200000000;
/* TODO get from DT: */
config.smp_blk_cnt = 22;
return &config;
}

View file

@ -0,0 +1,239 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __MDP5_KMS_H__
#define __MDP5_KMS_H__
#include "msm_drv.h"
#include "msm_kms.h"
#include "mdp/mdp_kms.h"
/* dynamic offsets used by mdp5.xml.h (initialized in mdp5_kms.c) */
#define MDP5_MAX_BASES 8
struct mdp5_sub_block {
int count;
uint32_t base[MDP5_MAX_BASES];
};
struct mdp5_config {
char *name;
struct mdp5_sub_block ctl;
struct mdp5_sub_block pipe_vig;
struct mdp5_sub_block pipe_rgb;
struct mdp5_sub_block pipe_dma;
struct mdp5_sub_block lm;
struct mdp5_sub_block dspp;
struct mdp5_sub_block ad;
struct mdp5_sub_block intf;
};
extern const struct mdp5_config *mdp5_cfg;
#include "mdp5.xml.h"
#include "mdp5_smp.h"
struct mdp5_kms {
struct mdp_kms base;
struct drm_device *dev;
int rev;
const struct mdp5_config *hw_cfg;
/* mapper-id used to request GEM buffer mapped for scanout: */
int id;
struct msm_mmu *mmu;
/* for tracking smp allocation amongst pipes: */
mdp5_smp_state_t smp_state;
struct mdp5_client_smp_state smp_client_state[CID_MAX];
int smp_blk_cnt;
/* io/register spaces: */
void __iomem *mmio, *vbif;
struct regulator *vdd;
struct clk *axi_clk;
struct clk *ahb_clk;
struct clk *src_clk;
struct clk *core_clk;
struct clk *lut_clk;
struct clk *vsync_clk;
struct hdmi *hdmi;
struct mdp_irq error_handler;
};
#define to_mdp5_kms(x) container_of(x, struct mdp5_kms, base)
/* platform config data (ie. from DT, or pdata) */
struct mdp5_platform_config {
struct iommu_domain *iommu;
uint32_t max_clk;
int smp_blk_cnt;
};
static inline void mdp5_write(struct mdp5_kms *mdp5_kms, u32 reg, u32 data)
{
msm_writel(data, mdp5_kms->mmio + reg);
}
static inline u32 mdp5_read(struct mdp5_kms *mdp5_kms, u32 reg)
{
return msm_readl(mdp5_kms->mmio + reg);
}
static inline const char *pipe2name(enum mdp5_pipe pipe)
{
static const char *names[] = {
#define NAME(n) [SSPP_ ## n] = #n
NAME(VIG0), NAME(VIG1), NAME(VIG2),
NAME(RGB0), NAME(RGB1), NAME(RGB2),
NAME(DMA0), NAME(DMA1),
NAME(VIG3), NAME(RGB3),
#undef NAME
};
return names[pipe];
}
static inline uint32_t pipe2flush(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;
}
}
static inline int pipe2nclients(enum mdp5_pipe pipe)
{
switch (pipe) {
case SSPP_RGB0:
case SSPP_RGB1:
case SSPP_RGB2:
case SSPP_RGB3:
return 1;
default:
return 3;
}
}
static inline enum mdp5_client_id pipe2client(enum mdp5_pipe pipe, int plane)
{
WARN_ON(plane >= pipe2nclients(pipe));
switch (pipe) {
case SSPP_VIG0: return CID_VIG0_Y + plane;
case SSPP_VIG1: return CID_VIG1_Y + plane;
case SSPP_VIG2: return CID_VIG2_Y + plane;
case SSPP_RGB0: return CID_RGB0;
case SSPP_RGB1: return CID_RGB1;
case SSPP_RGB2: return CID_RGB2;
case SSPP_DMA0: return CID_DMA0_Y + plane;
case SSPP_DMA1: return CID_DMA1_Y + plane;
case SSPP_VIG3: return CID_VIG3_Y + plane;
case SSPP_RGB3: return CID_RGB3;
default: return CID_UNUSED;
}
}
static inline uint32_t mixer2flush(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;
default: return 0;
}
}
static inline uint32_t intf2err(int intf)
{
switch (intf) {
case 0: return MDP5_IRQ_INTF0_UNDER_RUN;
case 1: return MDP5_IRQ_INTF1_UNDER_RUN;
case 2: return MDP5_IRQ_INTF2_UNDER_RUN;
case 3: return MDP5_IRQ_INTF3_UNDER_RUN;
default: return 0;
}
}
static inline uint32_t intf2vblank(int intf)
{
switch (intf) {
case 0: return MDP5_IRQ_INTF0_VSYNC;
case 1: return MDP5_IRQ_INTF1_VSYNC;
case 2: return MDP5_IRQ_INTF2_VSYNC;
case 3: return MDP5_IRQ_INTF3_VSYNC;
default: return 0;
}
}
int mdp5_disable(struct mdp5_kms *mdp5_kms);
int mdp5_enable(struct mdp5_kms *mdp5_kms);
void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask);
void mdp5_irq_preinstall(struct msm_kms *kms);
int mdp5_irq_postinstall(struct msm_kms *kms);
void mdp5_irq_uninstall(struct msm_kms *kms);
irqreturn_t mdp5_irq(struct msm_kms *kms);
int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
static inline
uint32_t mdp5_get_formats(enum mdp5_pipe pipe, uint32_t *pixel_formats,
uint32_t max_formats)
{
/* TODO when we have YUV, we need to filter supported formats
* based on pipe id..
*/
return mdp_get_formats(pixel_formats, max_formats);
}
void mdp5_plane_install_properties(struct drm_plane *plane,
struct drm_mode_object *obj);
void mdp5_plane_set_scanout(struct drm_plane *plane,
struct drm_framebuffer *fb);
int mdp5_plane_mode_set(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h);
void mdp5_plane_complete_flip(struct drm_plane *plane);
enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane);
struct drm_plane *mdp5_plane_init(struct drm_device *dev,
enum mdp5_pipe pipe, bool private_plane);
uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc);
void mdp5_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file);
void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
enum mdp5_intf intf_id);
void mdp5_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane);
void mdp5_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane);
struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
struct drm_plane *plane, int id);
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev, int intf,
enum mdp5_intf intf_id);
#endif /* __MDP5_KMS_H__ */

View file

@ -0,0 +1,394 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp5_kms.h"
struct mdp5_plane {
struct drm_plane base;
const char *name;
enum mdp5_pipe pipe;
uint32_t nformats;
uint32_t formats[32];
bool enabled;
};
#define to_mdp5_plane(x) container_of(x, struct mdp5_plane, base)
static struct mdp5_kms *get_kms(struct drm_plane *plane)
{
struct msm_drm_private *priv = plane->dev->dev_private;
return to_mdp5_kms(to_mdp_kms(priv->kms));
}
static int mdp5_plane_update(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
mdp5_plane->enabled = true;
if (plane->fb)
drm_framebuffer_unreference(plane->fb);
drm_framebuffer_reference(fb);
return mdp5_plane_mode_set(plane, crtc, fb,
crtc_x, crtc_y, crtc_w, crtc_h,
src_x, src_y, src_w, src_h);
}
static int mdp5_plane_disable(struct drm_plane *plane)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
struct mdp5_kms *mdp5_kms = get_kms(plane);
enum mdp5_pipe pipe = mdp5_plane->pipe;
int i;
DBG("%s: disable", mdp5_plane->name);
/* update our SMP request to zero (release all our blks): */
for (i = 0; i < pipe2nclients(pipe); i++)
mdp5_smp_request(mdp5_kms, pipe2client(pipe, i), 0);
/* TODO detaching now will cause us not to get the last
* vblank and mdp5_smp_commit().. so other planes will
* still see smp blocks previously allocated to us as
* in-use..
*/
if (plane->crtc)
mdp5_crtc_detach(plane->crtc, plane);
return 0;
}
static void mdp5_plane_destroy(struct drm_plane *plane)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
struct msm_drm_private *priv = plane->dev->dev_private;
if (priv->kms)
mdp5_plane_disable(plane);
drm_plane_cleanup(plane);
kfree(mdp5_plane);
}
/* helper to install properties which are common to planes and crtcs */
void mdp5_plane_install_properties(struct drm_plane *plane,
struct drm_mode_object *obj)
{
// XXX
}
int mdp5_plane_set_property(struct drm_plane *plane,
struct drm_property *property, uint64_t val)
{
// XXX
return -EINVAL;
}
static const struct drm_plane_funcs mdp5_plane_funcs = {
.update_plane = mdp5_plane_update,
.disable_plane = mdp5_plane_disable,
.destroy = mdp5_plane_destroy,
.set_property = mdp5_plane_set_property,
};
void mdp5_plane_set_scanout(struct drm_plane *plane,
struct drm_framebuffer *fb)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
struct mdp5_kms *mdp5_kms = get_kms(plane);
enum mdp5_pipe pipe = mdp5_plane->pipe;
uint32_t nplanes = drm_format_num_planes(fb->pixel_format);
uint32_t iova[4];
int i;
for (i = 0; i < nplanes; i++) {
struct drm_gem_object *bo = msm_framebuffer_bo(fb, i);
msm_gem_get_iova(bo, mdp5_kms->id, &iova[i]);
}
for (; i < 4; i++)
iova[i] = 0;
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_A(pipe),
MDP5_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) |
MDP5_PIPE_SRC_STRIDE_A_P1(fb->pitches[1]));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_B(pipe),
MDP5_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) |
MDP5_PIPE_SRC_STRIDE_B_P3(fb->pitches[3]));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC0_ADDR(pipe), iova[0]);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC1_ADDR(pipe), iova[1]);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC2_ADDR(pipe), iova[2]);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC3_ADDR(pipe), iova[3]);
plane->fb = fb;
}
/* NOTE: looks like if horizontal decimation is used (if we supported that)
* then the width used to calculate SMP block requirements is the post-
* decimated width. Ie. SMP buffering sits downstream of decimation (which
* presumably happens during the dma from scanout buffer).
*/
static int request_smp_blocks(struct drm_plane *plane, uint32_t format,
uint32_t nplanes, uint32_t width)
{
struct drm_device *dev = plane->dev;
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
struct mdp5_kms *mdp5_kms = get_kms(plane);
enum mdp5_pipe pipe = mdp5_plane->pipe;
int i, hsub, nlines, nblks, ret;
hsub = drm_format_horz_chroma_subsampling(format);
/* different if BWC (compressed framebuffer?) enabled: */
nlines = 2;
for (i = 0, nblks = 0; i < nplanes; i++) {
int n, fetch_stride, cpp;
cpp = drm_format_plane_cpp(format, i);
fetch_stride = width * cpp / (i ? hsub : 1);
n = DIV_ROUND_UP(fetch_stride * nlines, SMP_BLK_SIZE);
/* for hw rev v1.00 */
if (mdp5_kms->rev == 0)
n = roundup_pow_of_two(n);
DBG("%s[%d]: request %d SMP blocks", mdp5_plane->name, i, n);
ret = mdp5_smp_request(mdp5_kms, pipe2client(pipe, i), n);
if (ret) {
dev_err(dev->dev, "Could not allocate %d SMP blocks: %d\n",
n, ret);
return ret;
}
nblks += n;
}
/* in success case, return total # of blocks allocated: */
return nblks;
}
static void set_fifo_thresholds(struct drm_plane *plane, int nblks)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
struct mdp5_kms *mdp5_kms = get_kms(plane);
enum mdp5_pipe pipe = mdp5_plane->pipe;
uint32_t val;
/* 1/4 of SMP pool that is being fetched */
val = (nblks * SMP_ENTRIES_PER_BLK) / 4;
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_0(pipe), val * 1);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_1(pipe), val * 2);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_2(pipe), val * 3);
}
int mdp5_plane_mode_set(struct drm_plane *plane,
struct drm_crtc *crtc, struct drm_framebuffer *fb,
int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
struct mdp5_kms *mdp5_kms = get_kms(plane);
enum mdp5_pipe pipe = mdp5_plane->pipe;
const struct mdp_format *format;
uint32_t nplanes, config = 0;
uint32_t phasex_step = 0, phasey_step = 0;
uint32_t hdecm = 0, vdecm = 0;
int i, nblks;
nplanes = drm_format_num_planes(fb->pixel_format);
/* bad formats should already be rejected: */
if (WARN_ON(nplanes > pipe2nclients(pipe)))
return -EINVAL;
/* src values are in Q16 fixed point, convert to integer: */
src_x = src_x >> 16;
src_y = src_y >> 16;
src_w = src_w >> 16;
src_h = src_h >> 16;
DBG("%s: FB[%u] %u,%u,%u,%u -> CRTC[%u] %d,%d,%u,%u", mdp5_plane->name,
fb->base.id, src_x, src_y, src_w, src_h,
crtc->base.id, crtc_x, crtc_y, crtc_w, crtc_h);
/*
* Calculate and request required # of smp blocks:
*/
nblks = request_smp_blocks(plane, fb->pixel_format, nplanes, src_w);
if (nblks < 0)
return nblks;
/*
* Currently we update the hw for allocations/requests immediately,
* but once atomic modeset/pageflip is in place, the allocation
* would move into atomic->check_plane_state(), while updating the
* hw would remain here:
*/
for (i = 0; i < pipe2nclients(pipe); i++)
mdp5_smp_configure(mdp5_kms, pipe2client(pipe, i));
if (src_w != crtc_w) {
config |= MDP5_PIPE_SCALE_CONFIG_SCALEX_EN;
/* TODO calc phasex_step, hdecm */
}
if (src_h != crtc_h) {
config |= MDP5_PIPE_SCALE_CONFIG_SCALEY_EN;
/* TODO calc phasey_step, vdecm */
}
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_IMG_SIZE(pipe),
MDP5_PIPE_SRC_IMG_SIZE_WIDTH(src_w) |
MDP5_PIPE_SRC_IMG_SIZE_HEIGHT(src_h));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_SIZE(pipe),
MDP5_PIPE_SRC_SIZE_WIDTH(src_w) |
MDP5_PIPE_SRC_SIZE_HEIGHT(src_h));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_XY(pipe),
MDP5_PIPE_SRC_XY_X(src_x) |
MDP5_PIPE_SRC_XY_Y(src_y));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_OUT_SIZE(pipe),
MDP5_PIPE_OUT_SIZE_WIDTH(crtc_w) |
MDP5_PIPE_OUT_SIZE_HEIGHT(crtc_h));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_OUT_XY(pipe),
MDP5_PIPE_OUT_XY_X(crtc_x) |
MDP5_PIPE_OUT_XY_Y(crtc_y));
mdp5_plane_set_scanout(plane, fb);
format = to_mdp_format(msm_framebuffer_format(fb));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_FORMAT(pipe),
MDP5_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) |
MDP5_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) |
MDP5_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) |
MDP5_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) |
COND(format->alpha_enable, MDP5_PIPE_SRC_FORMAT_ALPHA_ENABLE) |
MDP5_PIPE_SRC_FORMAT_CPP(format->cpp - 1) |
MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) |
COND(format->unpack_tight, MDP5_PIPE_SRC_FORMAT_UNPACK_TIGHT) |
MDP5_PIPE_SRC_FORMAT_NUM_PLANES(nplanes - 1) |
MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP(CHROMA_RGB));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_UNPACK(pipe),
MDP5_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) |
MDP5_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) |
MDP5_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) |
MDP5_PIPE_SRC_UNPACK_ELEM3(format->unpack[3]));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_OP_MODE(pipe),
MDP5_PIPE_SRC_OP_MODE_BWC(BWC_LOSSLESS));
/* not using secure mode: */
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_ADDR_SW_STATUS(pipe), 0);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_PHASE_STEP_X(pipe), phasex_step);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_PHASE_STEP_Y(pipe), phasey_step);
mdp5_write(mdp5_kms, REG_MDP5_PIPE_DECIMATION(pipe),
MDP5_PIPE_DECIMATION_VERT(vdecm) |
MDP5_PIPE_DECIMATION_HORZ(hdecm));
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_CONFIG(pipe),
MDP5_PIPE_SCALE_CONFIG_SCALEX_MIN_FILTER(SCALE_FILTER_NEAREST) |
MDP5_PIPE_SCALE_CONFIG_SCALEY_MIN_FILTER(SCALE_FILTER_NEAREST) |
MDP5_PIPE_SCALE_CONFIG_SCALEX_CR_FILTER(SCALE_FILTER_NEAREST) |
MDP5_PIPE_SCALE_CONFIG_SCALEY_CR_FILTER(SCALE_FILTER_NEAREST) |
MDP5_PIPE_SCALE_CONFIG_SCALEX_MAX_FILTER(SCALE_FILTER_NEAREST) |
MDP5_PIPE_SCALE_CONFIG_SCALEY_MAX_FILTER(SCALE_FILTER_NEAREST));
set_fifo_thresholds(plane, nblks);
/* TODO detach from old crtc (if we had more than one) */
mdp5_crtc_attach(crtc, plane);
return 0;
}
void mdp5_plane_complete_flip(struct drm_plane *plane)
{
struct mdp5_kms *mdp5_kms = get_kms(plane);
enum mdp5_pipe pipe = to_mdp5_plane(plane)->pipe;
int i;
for (i = 0; i < pipe2nclients(pipe); i++)
mdp5_smp_commit(mdp5_kms, pipe2client(pipe, i));
}
enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane)
{
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
return mdp5_plane->pipe;
}
/* initialize plane */
struct drm_plane *mdp5_plane_init(struct drm_device *dev,
enum mdp5_pipe pipe, bool private_plane)
{
struct drm_plane *plane = NULL;
struct mdp5_plane *mdp5_plane;
int ret;
enum drm_plane_type type;
mdp5_plane = kzalloc(sizeof(*mdp5_plane), GFP_KERNEL);
if (!mdp5_plane) {
ret = -ENOMEM;
goto fail;
}
plane = &mdp5_plane->base;
mdp5_plane->pipe = pipe;
mdp5_plane->name = pipe2name(pipe);
mdp5_plane->nformats = mdp5_get_formats(pipe, mdp5_plane->formats,
ARRAY_SIZE(mdp5_plane->formats));
type = private_plane ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
drm_universal_plane_init(dev, plane, 0xff, &mdp5_plane_funcs,
mdp5_plane->formats, mdp5_plane->nformats,
type);
mdp5_plane_install_properties(plane, &plane->base);
return plane;
fail:
if (plane)
mdp5_plane_destroy(plane);
return ERR_PTR(ret);
}

View file

@ -0,0 +1,173 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mdp5_kms.h"
#include "mdp5_smp.h"
/* SMP - Shared Memory Pool
*
* These are shared between all the clients, where each plane in a
* scanout buffer is a SMP client. Ie. scanout of 3 plane I420 on
* pipe VIG0 => 3 clients: VIG0_Y, VIG0_CB, VIG0_CR.
*
* Based on the size of the attached scanout buffer, a certain # of
* blocks must be allocated to that client out of the shared pool.
*
* For each block, it can be either free, or pending/in-use by a
* client. The updates happen in three steps:
*
* 1) mdp5_smp_request():
* When plane scanout is setup, calculate required number of
* blocks needed per client, and request. Blocks not inuse or
* pending by any other client are added to client's pending
* set.
*
* 2) mdp5_smp_configure():
* As hw is programmed, before FLUSH, MDP5_SMP_ALLOC registers
* are configured for the union(pending, inuse)
*
* 3) mdp5_smp_commit():
* After next vblank, copy pending -> inuse. Optionally update
* MDP5_SMP_ALLOC registers if there are newly unused blocks
*
* On the next vblank after changes have been committed to hw, the
* client's pending blocks become it's in-use blocks (and no-longer
* in-use blocks become available to other clients).
*
* btw, hurray for confusing overloaded acronyms! :-/
*
* NOTE: for atomic modeset/pageflip NONBLOCK operations, step #1
* should happen at (or before)? atomic->check(). And we'd need
* an API to discard previous requests if update is aborted or
* (test-only).
*
* TODO would perhaps be nice to have debugfs to dump out kernel
* inuse and pending state of all clients..
*/
static DEFINE_SPINLOCK(smp_lock);
/* step #1: update # of blocks pending for the client: */
int mdp5_smp_request(struct mdp5_kms *mdp5_kms,
enum mdp5_client_id cid, int nblks)
{
struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid];
int i, ret, avail, cur_nblks, cnt = mdp5_kms->smp_blk_cnt;
unsigned long flags;
spin_lock_irqsave(&smp_lock, flags);
avail = cnt - bitmap_weight(mdp5_kms->smp_state, cnt);
if (nblks > avail) {
ret = -ENOSPC;
goto fail;
}
cur_nblks = bitmap_weight(ps->pending, cnt);
if (nblks > cur_nblks) {
/* grow the existing pending reservation: */
for (i = cur_nblks; i < nblks; i++) {
int blk = find_first_zero_bit(mdp5_kms->smp_state, cnt);
set_bit(blk, ps->pending);
set_bit(blk, mdp5_kms->smp_state);
}
} else {
/* shrink the existing pending reservation: */
for (i = cur_nblks; i > nblks; i--) {
int blk = find_first_bit(ps->pending, cnt);
clear_bit(blk, ps->pending);
/* don't clear in global smp_state until _commit() */
}
}
fail:
spin_unlock_irqrestore(&smp_lock, flags);
return 0;
}
static void update_smp_state(struct mdp5_kms *mdp5_kms,
enum mdp5_client_id cid, mdp5_smp_state_t *assigned)
{
int cnt = mdp5_kms->smp_blk_cnt;
uint32_t blk, val;
for_each_set_bit(blk, *assigned, cnt) {
int idx = blk / 3;
int fld = blk % 3;
val = mdp5_read(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx));
switch (fld) {
case 0:
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK;
val |= MDP5_SMP_ALLOC_W_REG_CLIENT0(cid);
break;
case 1:
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK;
val |= MDP5_SMP_ALLOC_W_REG_CLIENT1(cid);
break;
case 2:
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK;
val |= MDP5_SMP_ALLOC_W_REG_CLIENT2(cid);
break;
}
mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx), val);
mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_R_REG(idx), val);
}
}
/* step #2: configure hw for union(pending, inuse): */
void mdp5_smp_configure(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid)
{
struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid];
int cnt = mdp5_kms->smp_blk_cnt;
mdp5_smp_state_t assigned;
bitmap_or(assigned, ps->inuse, ps->pending, cnt);
update_smp_state(mdp5_kms, cid, &assigned);
}
/* step #3: after vblank, copy pending -> inuse: */
void mdp5_smp_commit(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid)
{
struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid];
int cnt = mdp5_kms->smp_blk_cnt;
mdp5_smp_state_t released;
/*
* Figure out if there are any blocks we where previously
* using, which can be released and made available to other
* clients:
*/
if (bitmap_andnot(released, ps->inuse, ps->pending, cnt)) {
unsigned long flags;
spin_lock_irqsave(&smp_lock, flags);
/* clear released blocks: */
bitmap_andnot(mdp5_kms->smp_state, mdp5_kms->smp_state,
released, cnt);
spin_unlock_irqrestore(&smp_lock, flags);
update_smp_state(mdp5_kms, CID_UNUSED, &released);
}
bitmap_copy(ps->inuse, ps->pending, cnt);
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __MDP5_SMP_H__
#define __MDP5_SMP_H__
#include "msm_drv.h"
#define MAX_SMP_BLOCKS 22
#define SMP_BLK_SIZE 4096
#define SMP_ENTRIES_PER_BLK (SMP_BLK_SIZE / 16)
typedef DECLARE_BITMAP(mdp5_smp_state_t, MAX_SMP_BLOCKS);
struct mdp5_client_smp_state {
mdp5_smp_state_t inuse;
mdp5_smp_state_t pending;
};
struct mdp5_kms;
int mdp5_smp_request(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid, int nblks);
void mdp5_smp_configure(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid);
void mdp5_smp_commit(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid);
#endif /* __MDP5_SMP_H__ */

View file

@ -0,0 +1,78 @@
#ifndef MDP_COMMON_XML
#define MDP_COMMON_XML
/* Autogenerated file, DO NOT EDIT manually!
This file was generated by the rules-ng-ng headergen tool in this git repository:
http://github.com/freedreno/envytools/
git clone https://github.com/freedreno/envytools.git
The rules-ng-ng source files this header was generated from are:
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 17996 bytes, from 2013-12-01 19:10:31)
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2013-11-30 15:00:52)
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-06-25 12:55:02)
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1544 bytes, from 2013-08-16 19:17:05)
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-06-25 12:53:44)
Copyright (C) 2013 by the following authors:
- Rob Clark <robdclark@gmail.com> (robclark)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice (including the
next paragraph) shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
enum mdp_mixer_stage_id {
STAGE_UNUSED = 0,
STAGE_BASE = 1,
STAGE0 = 2,
STAGE1 = 3,
STAGE2 = 4,
STAGE3 = 5,
};
enum mdp_alpha_type {
FG_CONST = 0,
BG_CONST = 1,
FG_PIXEL = 2,
BG_PIXEL = 3,
};
enum mdp_bpc {
BPC1 = 0,
BPC5 = 1,
BPC6 = 2,
BPC8 = 3,
};
enum mdp_bpc_alpha {
BPC1A = 0,
BPC4A = 1,
BPC6A = 2,
BPC8A = 3,
};
#endif /* MDP_COMMON_XML */

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_drv.h"
#include "mdp_kms.h"
#define FMT(name, a, r, g, b, e0, e1, e2, e3, alpha, tight, c, cnt) { \
.base = { .pixel_format = DRM_FORMAT_ ## name }, \
.bpc_a = BPC ## a ## A, \
.bpc_r = BPC ## r, \
.bpc_g = BPC ## g, \
.bpc_b = BPC ## b, \
.unpack = { e0, e1, e2, e3 }, \
.alpha_enable = alpha, \
.unpack_tight = tight, \
.cpp = c, \
.unpack_count = cnt, \
}
#define BPC0A 0
static const struct mdp_format formats[] = {
/* name a r g b e0 e1 e2 e3 alpha tight cpp cnt */
FMT(ARGB8888, 8, 8, 8, 8, 1, 0, 2, 3, true, true, 4, 4),
FMT(XRGB8888, 8, 8, 8, 8, 1, 0, 2, 3, false, true, 4, 4),
FMT(RGB888, 0, 8, 8, 8, 1, 0, 2, 0, false, true, 3, 3),
FMT(BGR888, 0, 8, 8, 8, 2, 0, 1, 0, false, true, 3, 3),
FMT(RGB565, 0, 5, 6, 5, 1, 0, 2, 0, false, true, 2, 3),
FMT(BGR565, 0, 5, 6, 5, 2, 0, 1, 0, false, true, 2, 3),
};
uint32_t mdp_get_formats(uint32_t *pixel_formats, uint32_t max_formats)
{
uint32_t i;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
const struct mdp_format *f = &formats[i];
if (i == max_formats)
break;
pixel_formats[i] = f->base.pixel_format;
}
return i;
}
const struct msm_format *mdp_get_format(struct msm_kms *kms, uint32_t format)
{
int i;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
const struct mdp_format *f = &formats[i];
if (f->base.pixel_format == format)
return &f->base;
}
return NULL;
}

View file

@ -0,0 +1,145 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "msm_drv.h"
#include "mdp_kms.h"
struct mdp_irq_wait {
struct mdp_irq irq;
int count;
};
static DECLARE_WAIT_QUEUE_HEAD(wait_event);
static DEFINE_SPINLOCK(list_lock);
static void update_irq(struct mdp_kms *mdp_kms)
{
struct mdp_irq *irq;
uint32_t irqmask = mdp_kms->vblank_mask;
BUG_ON(!spin_is_locked(&list_lock));
list_for_each_entry(irq, &mdp_kms->irq_list, node)
irqmask |= irq->irqmask;
mdp_kms->funcs->set_irqmask(mdp_kms, irqmask);
}
static void update_irq_unlocked(struct mdp_kms *mdp_kms)
{
unsigned long flags;
spin_lock_irqsave(&list_lock, flags);
update_irq(mdp_kms);
spin_unlock_irqrestore(&list_lock, flags);
}
void mdp_dispatch_irqs(struct mdp_kms *mdp_kms, uint32_t status)
{
struct mdp_irq *handler, *n;
unsigned long flags;
spin_lock_irqsave(&list_lock, flags);
mdp_kms->in_irq = true;
list_for_each_entry_safe(handler, n, &mdp_kms->irq_list, node) {
if (handler->irqmask & status) {
spin_unlock_irqrestore(&list_lock, flags);
handler->irq(handler, handler->irqmask & status);
spin_lock_irqsave(&list_lock, flags);
}
}
mdp_kms->in_irq = false;
update_irq(mdp_kms);
spin_unlock_irqrestore(&list_lock, flags);
}
void mdp_update_vblank_mask(struct mdp_kms *mdp_kms, uint32_t mask, bool enable)
{
unsigned long flags;
spin_lock_irqsave(&list_lock, flags);
if (enable)
mdp_kms->vblank_mask |= mask;
else
mdp_kms->vblank_mask &= ~mask;
update_irq(mdp_kms);
spin_unlock_irqrestore(&list_lock, flags);
}
static void wait_irq(struct mdp_irq *irq, uint32_t irqstatus)
{
struct mdp_irq_wait *wait =
container_of(irq, struct mdp_irq_wait, irq);
wait->count--;
wake_up_all(&wait_event);
}
void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask)
{
struct mdp_irq_wait wait = {
.irq = {
.irq = wait_irq,
.irqmask = irqmask,
},
.count = 1,
};
mdp_irq_register(mdp_kms, &wait.irq);
wait_event_timeout(wait_event, (wait.count <= 0),
msecs_to_jiffies(100));
mdp_irq_unregister(mdp_kms, &wait.irq);
}
void mdp_irq_register(struct mdp_kms *mdp_kms, struct mdp_irq *irq)
{
unsigned long flags;
bool needs_update = false;
spin_lock_irqsave(&list_lock, flags);
if (!irq->registered) {
irq->registered = true;
list_add(&irq->node, &mdp_kms->irq_list);
needs_update = !mdp_kms->in_irq;
}
spin_unlock_irqrestore(&list_lock, flags);
if (needs_update)
update_irq_unlocked(mdp_kms);
}
void mdp_irq_unregister(struct mdp_kms *mdp_kms, struct mdp_irq *irq)
{
unsigned long flags;
bool needs_update = false;
spin_lock_irqsave(&list_lock, flags);
if (irq->registered) {
irq->registered = false;
list_del(&irq->node);
needs_update = !mdp_kms->in_irq;
}
spin_unlock_irqrestore(&list_lock, flags);
if (needs_update)
update_irq_unlocked(mdp_kms);
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __MDP_KMS_H__
#define __MDP_KMS_H__
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include "msm_drv.h"
#include "msm_kms.h"
#include "mdp_common.xml.h"
struct mdp_kms;
struct mdp_kms_funcs {
struct msm_kms_funcs base;
void (*set_irqmask)(struct mdp_kms *mdp_kms, uint32_t irqmask);
};
struct mdp_kms {
struct msm_kms base;
const struct mdp_kms_funcs *funcs;
/* irq handling: */
bool in_irq;
struct list_head irq_list; /* list of mdp4_irq */
uint32_t vblank_mask; /* irq bits set for userspace vblank */
};
#define to_mdp_kms(x) container_of(x, struct mdp_kms, base)
static inline void mdp_kms_init(struct mdp_kms *mdp_kms,
const struct mdp_kms_funcs *funcs)
{
mdp_kms->funcs = funcs;
INIT_LIST_HEAD(&mdp_kms->irq_list);
msm_kms_init(&mdp_kms->base, &funcs->base);
}
/*
* irq helpers:
*/
/* For transiently registering for different MDP irqs that various parts
* of the KMS code need during setup/configuration. These are not
* necessarily the same as what drm_vblank_get/put() are requesting, and
* the hysteresis in drm_vblank_put() is not necessarily desirable for
* internal housekeeping related irq usage.
*/
struct mdp_irq {
struct list_head node;
uint32_t irqmask;
bool registered;
void (*irq)(struct mdp_irq *irq, uint32_t irqstatus);
};
void mdp_dispatch_irqs(struct mdp_kms *mdp_kms, uint32_t status);
void mdp_update_vblank_mask(struct mdp_kms *mdp_kms, uint32_t mask, bool enable);
void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask);
void mdp_irq_register(struct mdp_kms *mdp_kms, struct mdp_irq *irq);
void mdp_irq_unregister(struct mdp_kms *mdp_kms, struct mdp_irq *irq);
/*
* pixel format helpers:
*/
struct mdp_format {
struct msm_format base;
enum mdp_bpc bpc_r, bpc_g, bpc_b;
enum mdp_bpc_alpha bpc_a;
uint8_t unpack[4];
bool alpha_enable, unpack_tight;
uint8_t cpp, unpack_count;
};
#define to_mdp_format(x) container_of(x, struct mdp_format, base)
uint32_t mdp_get_formats(uint32_t *formats, uint32_t max_formats);
const struct msm_format *mdp_get_format(struct msm_kms *kms, uint32_t format);
#endif /* __MDP_KMS_H__ */