mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-11-01 08:38:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
1128
drivers/gpu/drm/msm/mdp/mdp4/mdp4.xml.h
Normal file
1128
drivers/gpu/drm/msm/mdp/mdp4/mdp4.xml.h
Normal file
File diff suppressed because it is too large
Load diff
809
drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
Normal file
809
drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
Normal 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);
|
||||
}
|
||||
303
drivers/gpu/drm/msm/mdp/mdp4/mdp4_dtv_encoder.c
Normal file
303
drivers/gpu/drm/msm/mdp/mdp4/mdp4_dtv_encoder.c
Normal 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);
|
||||
}
|
||||
93
drivers/gpu/drm/msm/mdp/mdp4/mdp4_irq.c
Normal file
93
drivers/gpu/drm/msm/mdp/mdp4/mdp4_irq.c
Normal 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);
|
||||
}
|
||||
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c
Normal file
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c
Normal 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;
|
||||
}
|
||||
256
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h
Normal file
256
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h
Normal 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__ */
|
||||
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c
Normal file
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c
Normal 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);
|
||||
}
|
||||
151
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c
Normal file
151
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c
Normal 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);
|
||||
}
|
||||
172
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c
Normal file
172
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c
Normal 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);
|
||||
}
|
||||
255
drivers/gpu/drm/msm/mdp/mdp4/mdp4_plane.c
Normal file
255
drivers/gpu/drm/msm/mdp/mdp4/mdp4_plane.c
Normal 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);
|
||||
}
|
||||
1139
drivers/gpu/drm/msm/mdp/mdp5/mdp5.xml.h
Normal file
1139
drivers/gpu/drm/msm/mdp/mdp5/mdp5.xml.h
Normal file
File diff suppressed because it is too large
Load diff
575
drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
Normal file
575
drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
Normal 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);
|
||||
}
|
||||
258
drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
Normal file
258
drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
Normal 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);
|
||||
}
|
||||
111
drivers/gpu/drm/msm/mdp/mdp5/mdp5_irq.c
Normal file
111
drivers/gpu/drm/msm/mdp/mdp5/mdp5_irq.c
Normal 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);
|
||||
}
|
||||
491
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
Normal file
491
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
Normal 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;
|
||||
}
|
||||
239
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
Normal file
239
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
Normal 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__ */
|
||||
394
drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c
Normal file
394
drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c
Normal 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);
|
||||
}
|
||||
173
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.c
Normal file
173
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.c
Normal 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);
|
||||
}
|
||||
41
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.h
Normal file
41
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.h
Normal 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__ */
|
||||
78
drivers/gpu/drm/msm/mdp/mdp_common.xml.h
Normal file
78
drivers/gpu/drm/msm/mdp/mdp_common.xml.h
Normal 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 */
|
||||
71
drivers/gpu/drm/msm/mdp/mdp_format.c
Normal file
71
drivers/gpu/drm/msm/mdp/mdp_format.c
Normal 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;
|
||||
}
|
||||
145
drivers/gpu/drm/msm/mdp/mdp_kms.c
Normal file
145
drivers/gpu/drm/msm/mdp/mdp_kms.c
Normal 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);
|
||||
}
|
||||
97
drivers/gpu/drm/msm/mdp/mdp_kms.h
Normal file
97
drivers/gpu/drm/msm/mdp/mdp_kms.h
Normal 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__ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue