![Krzysztof Kozlowski](/assets/img/avatar_default.png)
The DECON headers contain only defines for registers. There are no other drivers using them so this should be put locally to the Exynos DRM driver. Keeping headers local helps managing the code. Suggested-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Krzysztof Kozlowski <krzk@kernel.org> Signed-off-by: Inki Dae <inki.dae@samsung.com>
846 lines
20 KiB
C
846 lines
20 KiB
C
/* drivers/gpu/drm/exynos/exynos7_drm_decon.c
|
|
*
|
|
* Copyright (C) 2014 Samsung Electronics Co.Ltd
|
|
* Authors:
|
|
* Akshu Agarwal <akshua@gmail.com>
|
|
* Ajay Kumar <ajaykumar.rs@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
#include <drm/drmP.h>
|
|
#include <drm/exynos_drm.h>
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/component.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <video/of_display_timing.h>
|
|
#include <video/of_videomode.h>
|
|
|
|
#include "exynos_drm_crtc.h"
|
|
#include "exynos_drm_plane.h"
|
|
#include "exynos_drm_drv.h"
|
|
#include "exynos_drm_fb.h"
|
|
#include "exynos_drm_iommu.h"
|
|
#include "regs-decon7.h"
|
|
|
|
/*
|
|
* DECON stands for Display and Enhancement controller.
|
|
*/
|
|
|
|
#define MIN_FB_WIDTH_FOR_16WORD_BURST 128
|
|
|
|
#define WINDOWS_NR 2
|
|
|
|
struct decon_context {
|
|
struct device *dev;
|
|
struct drm_device *drm_dev;
|
|
struct exynos_drm_crtc *crtc;
|
|
struct exynos_drm_plane planes[WINDOWS_NR];
|
|
struct exynos_drm_plane_config configs[WINDOWS_NR];
|
|
struct clk *pclk;
|
|
struct clk *aclk;
|
|
struct clk *eclk;
|
|
struct clk *vclk;
|
|
void __iomem *regs;
|
|
unsigned long irq_flags;
|
|
bool i80_if;
|
|
bool suspended;
|
|
wait_queue_head_t wait_vsync_queue;
|
|
atomic_t wait_vsync_event;
|
|
|
|
struct drm_encoder *encoder;
|
|
};
|
|
|
|
static const struct of_device_id decon_driver_dt_match[] = {
|
|
{.compatible = "samsung,exynos7-decon"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, decon_driver_dt_match);
|
|
|
|
static const uint32_t decon_formats[] = {
|
|
DRM_FORMAT_RGB565,
|
|
DRM_FORMAT_XRGB8888,
|
|
DRM_FORMAT_XBGR8888,
|
|
DRM_FORMAT_RGBX8888,
|
|
DRM_FORMAT_BGRX8888,
|
|
DRM_FORMAT_ARGB8888,
|
|
DRM_FORMAT_ABGR8888,
|
|
DRM_FORMAT_RGBA8888,
|
|
DRM_FORMAT_BGRA8888,
|
|
};
|
|
|
|
static const enum drm_plane_type decon_win_types[WINDOWS_NR] = {
|
|
DRM_PLANE_TYPE_PRIMARY,
|
|
DRM_PLANE_TYPE_CURSOR,
|
|
};
|
|
|
|
static void decon_wait_for_vblank(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
atomic_set(&ctx->wait_vsync_event, 1);
|
|
|
|
/*
|
|
* wait for DECON to signal VSYNC interrupt or return after
|
|
* timeout which is set to 50ms (refresh rate of 20).
|
|
*/
|
|
if (!wait_event_timeout(ctx->wait_vsync_queue,
|
|
!atomic_read(&ctx->wait_vsync_event),
|
|
HZ/20))
|
|
DRM_DEBUG_KMS("vblank wait timed out.\n");
|
|
}
|
|
|
|
static void decon_clear_channels(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
unsigned int win, ch_enabled = 0;
|
|
|
|
DRM_DEBUG_KMS("%s\n", __FILE__);
|
|
|
|
/* Check if any channel is enabled. */
|
|
for (win = 0; win < WINDOWS_NR; win++) {
|
|
u32 val = readl(ctx->regs + WINCON(win));
|
|
|
|
if (val & WINCONx_ENWIN) {
|
|
val &= ~WINCONx_ENWIN;
|
|
writel(val, ctx->regs + WINCON(win));
|
|
ch_enabled = 1;
|
|
}
|
|
}
|
|
|
|
/* Wait for vsync, as disable channel takes effect at next vsync */
|
|
if (ch_enabled)
|
|
decon_wait_for_vblank(ctx->crtc);
|
|
}
|
|
|
|
static int decon_ctx_initialize(struct decon_context *ctx,
|
|
struct drm_device *drm_dev)
|
|
{
|
|
ctx->drm_dev = drm_dev;
|
|
|
|
decon_clear_channels(ctx->crtc);
|
|
|
|
return drm_iommu_attach_device(drm_dev, ctx->dev);
|
|
}
|
|
|
|
static void decon_ctx_remove(struct decon_context *ctx)
|
|
{
|
|
/* detach this sub driver from iommu mapping if supported. */
|
|
drm_iommu_detach_device(ctx->drm_dev, ctx->dev);
|
|
}
|
|
|
|
static u32 decon_calc_clkdiv(struct decon_context *ctx,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh;
|
|
u32 clkdiv;
|
|
|
|
/* Find the clock divider value that gets us closest to ideal_clk */
|
|
clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->vclk), ideal_clk);
|
|
|
|
return (clkdiv < 0x100) ? clkdiv : 0xff;
|
|
}
|
|
|
|
static void decon_commit(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
struct drm_display_mode *mode = &crtc->base.state->adjusted_mode;
|
|
u32 val, clkdiv;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
/* nothing to do if we haven't set the mode yet */
|
|
if (mode->htotal == 0 || mode->vtotal == 0)
|
|
return;
|
|
|
|
if (!ctx->i80_if) {
|
|
int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
|
|
/* setup vertical timing values. */
|
|
vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
|
|
vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
|
|
vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;
|
|
|
|
val = VIDTCON0_VBPD(vbpd - 1) | VIDTCON0_VFPD(vfpd - 1);
|
|
writel(val, ctx->regs + VIDTCON0);
|
|
|
|
val = VIDTCON1_VSPW(vsync_len - 1);
|
|
writel(val, ctx->regs + VIDTCON1);
|
|
|
|
/* setup horizontal timing values. */
|
|
hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
|
|
hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
|
|
hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;
|
|
|
|
/* setup horizontal timing values. */
|
|
val = VIDTCON2_HBPD(hbpd - 1) | VIDTCON2_HFPD(hfpd - 1);
|
|
writel(val, ctx->regs + VIDTCON2);
|
|
|
|
val = VIDTCON3_HSPW(hsync_len - 1);
|
|
writel(val, ctx->regs + VIDTCON3);
|
|
}
|
|
|
|
/* setup horizontal and vertical display size. */
|
|
val = VIDTCON4_LINEVAL(mode->vdisplay - 1) |
|
|
VIDTCON4_HOZVAL(mode->hdisplay - 1);
|
|
writel(val, ctx->regs + VIDTCON4);
|
|
|
|
writel(mode->vdisplay - 1, ctx->regs + LINECNT_OP_THRESHOLD);
|
|
|
|
/*
|
|
* fields of register with prefix '_F' would be updated
|
|
* at vsync(same as dma start)
|
|
*/
|
|
val = VIDCON0_ENVID | VIDCON0_ENVID_F;
|
|
writel(val, ctx->regs + VIDCON0);
|
|
|
|
clkdiv = decon_calc_clkdiv(ctx, mode);
|
|
if (clkdiv > 1) {
|
|
val = VCLKCON1_CLKVAL_NUM_VCLK(clkdiv - 1);
|
|
writel(val, ctx->regs + VCLKCON1);
|
|
writel(val, ctx->regs + VCLKCON2);
|
|
}
|
|
|
|
val = readl(ctx->regs + DECON_UPDATE);
|
|
val |= DECON_UPDATE_STANDALONE_F;
|
|
writel(val, ctx->regs + DECON_UPDATE);
|
|
}
|
|
|
|
static int decon_enable_vblank(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
u32 val;
|
|
|
|
if (ctx->suspended)
|
|
return -EPERM;
|
|
|
|
if (!test_and_set_bit(0, &ctx->irq_flags)) {
|
|
val = readl(ctx->regs + VIDINTCON0);
|
|
|
|
val |= VIDINTCON0_INT_ENABLE;
|
|
|
|
if (!ctx->i80_if) {
|
|
val |= VIDINTCON0_INT_FRAME;
|
|
val &= ~VIDINTCON0_FRAMESEL0_MASK;
|
|
val |= VIDINTCON0_FRAMESEL0_VSYNC;
|
|
}
|
|
|
|
writel(val, ctx->regs + VIDINTCON0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void decon_disable_vblank(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
u32 val;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
if (test_and_clear_bit(0, &ctx->irq_flags)) {
|
|
val = readl(ctx->regs + VIDINTCON0);
|
|
|
|
val &= ~VIDINTCON0_INT_ENABLE;
|
|
if (!ctx->i80_if)
|
|
val &= ~VIDINTCON0_INT_FRAME;
|
|
|
|
writel(val, ctx->regs + VIDINTCON0);
|
|
}
|
|
}
|
|
|
|
static void decon_win_set_pixfmt(struct decon_context *ctx, unsigned int win,
|
|
struct drm_framebuffer *fb)
|
|
{
|
|
unsigned long val;
|
|
int padding;
|
|
|
|
val = readl(ctx->regs + WINCON(win));
|
|
val &= ~WINCONx_BPPMODE_MASK;
|
|
|
|
switch (fb->format->format) {
|
|
case DRM_FORMAT_RGB565:
|
|
val |= WINCONx_BPPMODE_16BPP_565;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_XRGB8888:
|
|
val |= WINCONx_BPPMODE_24BPP_xRGB;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_XBGR8888:
|
|
val |= WINCONx_BPPMODE_24BPP_xBGR;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_RGBX8888:
|
|
val |= WINCONx_BPPMODE_24BPP_RGBx;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_BGRX8888:
|
|
val |= WINCONx_BPPMODE_24BPP_BGRx;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_ARGB8888:
|
|
val |= WINCONx_BPPMODE_32BPP_ARGB | WINCONx_BLD_PIX |
|
|
WINCONx_ALPHA_SEL;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_ABGR8888:
|
|
val |= WINCONx_BPPMODE_32BPP_ABGR | WINCONx_BLD_PIX |
|
|
WINCONx_ALPHA_SEL;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_RGBA8888:
|
|
val |= WINCONx_BPPMODE_32BPP_RGBA | WINCONx_BLD_PIX |
|
|
WINCONx_ALPHA_SEL;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
case DRM_FORMAT_BGRA8888:
|
|
default:
|
|
val |= WINCONx_BPPMODE_32BPP_BGRA | WINCONx_BLD_PIX |
|
|
WINCONx_ALPHA_SEL;
|
|
val |= WINCONx_BURSTLEN_16WORD;
|
|
break;
|
|
}
|
|
|
|
DRM_DEBUG_KMS("cpp = %d\n", fb->format->cpp[0]);
|
|
|
|
/*
|
|
* In case of exynos, setting dma-burst to 16Word causes permanent
|
|
* tearing for very small buffers, e.g. cursor buffer. Burst Mode
|
|
* switching which is based on plane size is not recommended as
|
|
* plane size varies a lot towards the end of the screen and rapid
|
|
* movement causes unstable DMA which results into iommu crash/tear.
|
|
*/
|
|
|
|
padding = (fb->pitches[0] / fb->format->cpp[0]) - fb->width;
|
|
if (fb->width + padding < MIN_FB_WIDTH_FOR_16WORD_BURST) {
|
|
val &= ~WINCONx_BURSTLEN_MASK;
|
|
val |= WINCONx_BURSTLEN_8WORD;
|
|
}
|
|
|
|
writel(val, ctx->regs + WINCON(win));
|
|
}
|
|
|
|
static void decon_win_set_colkey(struct decon_context *ctx, unsigned int win)
|
|
{
|
|
unsigned int keycon0 = 0, keycon1 = 0;
|
|
|
|
keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F |
|
|
WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0);
|
|
|
|
keycon1 = WxKEYCON1_COLVAL(0xffffffff);
|
|
|
|
writel(keycon0, ctx->regs + WKEYCON0_BASE(win));
|
|
writel(keycon1, ctx->regs + WKEYCON1_BASE(win));
|
|
}
|
|
|
|
/**
|
|
* shadow_protect_win() - disable updating values from shadow registers at vsync
|
|
*
|
|
* @win: window to protect registers for
|
|
* @protect: 1 to protect (disable updates)
|
|
*/
|
|
static void decon_shadow_protect_win(struct decon_context *ctx,
|
|
unsigned int win, bool protect)
|
|
{
|
|
u32 bits, val;
|
|
|
|
bits = SHADOWCON_WINx_PROTECT(win);
|
|
|
|
val = readl(ctx->regs + SHADOWCON);
|
|
if (protect)
|
|
val |= bits;
|
|
else
|
|
val &= ~bits;
|
|
writel(val, ctx->regs + SHADOWCON);
|
|
}
|
|
|
|
static void decon_atomic_begin(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
int i;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
for (i = 0; i < WINDOWS_NR; i++)
|
|
decon_shadow_protect_win(ctx, i, true);
|
|
}
|
|
|
|
static void decon_update_plane(struct exynos_drm_crtc *crtc,
|
|
struct exynos_drm_plane *plane)
|
|
{
|
|
struct exynos_drm_plane_state *state =
|
|
to_exynos_plane_state(plane->base.state);
|
|
struct decon_context *ctx = crtc->ctx;
|
|
struct drm_framebuffer *fb = state->base.fb;
|
|
int padding;
|
|
unsigned long val, alpha;
|
|
unsigned int last_x;
|
|
unsigned int last_y;
|
|
unsigned int win = plane->index;
|
|
unsigned int cpp = fb->format->cpp[0];
|
|
unsigned int pitch = fb->pitches[0];
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
/*
|
|
* SHADOWCON/PRTCON register is used for enabling timing.
|
|
*
|
|
* for example, once only width value of a register is set,
|
|
* if the dma is started then decon hardware could malfunction so
|
|
* with protect window setting, the register fields with prefix '_F'
|
|
* wouldn't be updated at vsync also but updated once unprotect window
|
|
* is set.
|
|
*/
|
|
|
|
/* buffer start address */
|
|
val = (unsigned long)exynos_drm_fb_dma_addr(fb, 0);
|
|
writel(val, ctx->regs + VIDW_BUF_START(win));
|
|
|
|
padding = (pitch / cpp) - fb->width;
|
|
|
|
/* buffer size */
|
|
writel(fb->width + padding, ctx->regs + VIDW_WHOLE_X(win));
|
|
writel(fb->height, ctx->regs + VIDW_WHOLE_Y(win));
|
|
|
|
/* offset from the start of the buffer to read */
|
|
writel(state->src.x, ctx->regs + VIDW_OFFSET_X(win));
|
|
writel(state->src.y, ctx->regs + VIDW_OFFSET_Y(win));
|
|
|
|
DRM_DEBUG_KMS("start addr = 0x%lx\n",
|
|
(unsigned long)val);
|
|
DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n",
|
|
state->crtc.w, state->crtc.h);
|
|
|
|
val = VIDOSDxA_TOPLEFT_X(state->crtc.x) |
|
|
VIDOSDxA_TOPLEFT_Y(state->crtc.y);
|
|
writel(val, ctx->regs + VIDOSD_A(win));
|
|
|
|
last_x = state->crtc.x + state->crtc.w;
|
|
if (last_x)
|
|
last_x--;
|
|
last_y = state->crtc.y + state->crtc.h;
|
|
if (last_y)
|
|
last_y--;
|
|
|
|
val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y);
|
|
|
|
writel(val, ctx->regs + VIDOSD_B(win));
|
|
|
|
DRM_DEBUG_KMS("osd pos: tx = %d, ty = %d, bx = %d, by = %d\n",
|
|
state->crtc.x, state->crtc.y, last_x, last_y);
|
|
|
|
/* OSD alpha */
|
|
alpha = VIDOSDxC_ALPHA0_R_F(0x0) |
|
|
VIDOSDxC_ALPHA0_G_F(0x0) |
|
|
VIDOSDxC_ALPHA0_B_F(0x0);
|
|
|
|
writel(alpha, ctx->regs + VIDOSD_C(win));
|
|
|
|
alpha = VIDOSDxD_ALPHA1_R_F(0xff) |
|
|
VIDOSDxD_ALPHA1_G_F(0xff) |
|
|
VIDOSDxD_ALPHA1_B_F(0xff);
|
|
|
|
writel(alpha, ctx->regs + VIDOSD_D(win));
|
|
|
|
decon_win_set_pixfmt(ctx, win, fb);
|
|
|
|
/* hardware window 0 doesn't support color key. */
|
|
if (win != 0)
|
|
decon_win_set_colkey(ctx, win);
|
|
|
|
/* wincon */
|
|
val = readl(ctx->regs + WINCON(win));
|
|
val |= WINCONx_TRIPLE_BUF_MODE;
|
|
val |= WINCONx_ENWIN;
|
|
writel(val, ctx->regs + WINCON(win));
|
|
|
|
/* Enable DMA channel and unprotect windows */
|
|
decon_shadow_protect_win(ctx, win, false);
|
|
|
|
val = readl(ctx->regs + DECON_UPDATE);
|
|
val |= DECON_UPDATE_STANDALONE_F;
|
|
writel(val, ctx->regs + DECON_UPDATE);
|
|
}
|
|
|
|
static void decon_disable_plane(struct exynos_drm_crtc *crtc,
|
|
struct exynos_drm_plane *plane)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
unsigned int win = plane->index;
|
|
u32 val;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
/* protect windows */
|
|
decon_shadow_protect_win(ctx, win, true);
|
|
|
|
/* wincon */
|
|
val = readl(ctx->regs + WINCON(win));
|
|
val &= ~WINCONx_ENWIN;
|
|
writel(val, ctx->regs + WINCON(win));
|
|
|
|
val = readl(ctx->regs + DECON_UPDATE);
|
|
val |= DECON_UPDATE_STANDALONE_F;
|
|
writel(val, ctx->regs + DECON_UPDATE);
|
|
}
|
|
|
|
static void decon_atomic_flush(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
int i;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
for (i = 0; i < WINDOWS_NR; i++)
|
|
decon_shadow_protect_win(ctx, i, false);
|
|
exynos_crtc_handle_event(crtc);
|
|
}
|
|
|
|
static void decon_init(struct decon_context *ctx)
|
|
{
|
|
u32 val;
|
|
|
|
writel(VIDCON0_SWRESET, ctx->regs + VIDCON0);
|
|
|
|
val = VIDOUTCON0_DISP_IF_0_ON;
|
|
if (!ctx->i80_if)
|
|
val |= VIDOUTCON0_RGBIF;
|
|
writel(val, ctx->regs + VIDOUTCON0);
|
|
|
|
writel(VCLKCON0_CLKVALUP | VCLKCON0_VCLKFREE, ctx->regs + VCLKCON0);
|
|
|
|
if (!ctx->i80_if)
|
|
writel(VIDCON1_VCLK_HOLD, ctx->regs + VIDCON1(0));
|
|
}
|
|
|
|
static void decon_enable(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
|
|
if (!ctx->suspended)
|
|
return;
|
|
|
|
pm_runtime_get_sync(ctx->dev);
|
|
|
|
decon_init(ctx);
|
|
|
|
/* if vblank was enabled status, enable it again. */
|
|
if (test_and_clear_bit(0, &ctx->irq_flags))
|
|
decon_enable_vblank(ctx->crtc);
|
|
|
|
decon_commit(ctx->crtc);
|
|
|
|
ctx->suspended = false;
|
|
}
|
|
|
|
static void decon_disable(struct exynos_drm_crtc *crtc)
|
|
{
|
|
struct decon_context *ctx = crtc->ctx;
|
|
int i;
|
|
|
|
if (ctx->suspended)
|
|
return;
|
|
|
|
/*
|
|
* We need to make sure that all windows are disabled before we
|
|
* suspend that connector. Otherwise we might try to scan from
|
|
* a destroyed buffer later.
|
|
*/
|
|
for (i = 0; i < WINDOWS_NR; i++)
|
|
decon_disable_plane(crtc, &ctx->planes[i]);
|
|
|
|
pm_runtime_put_sync(ctx->dev);
|
|
|
|
ctx->suspended = true;
|
|
}
|
|
|
|
static const struct exynos_drm_crtc_ops decon_crtc_ops = {
|
|
.enable = decon_enable,
|
|
.disable = decon_disable,
|
|
.enable_vblank = decon_enable_vblank,
|
|
.disable_vblank = decon_disable_vblank,
|
|
.atomic_begin = decon_atomic_begin,
|
|
.update_plane = decon_update_plane,
|
|
.disable_plane = decon_disable_plane,
|
|
.atomic_flush = decon_atomic_flush,
|
|
};
|
|
|
|
|
|
static irqreturn_t decon_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct decon_context *ctx = (struct decon_context *)dev_id;
|
|
u32 val, clear_bit;
|
|
|
|
val = readl(ctx->regs + VIDINTCON1);
|
|
|
|
clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME;
|
|
if (val & clear_bit)
|
|
writel(clear_bit, ctx->regs + VIDINTCON1);
|
|
|
|
/* check the crtc is detached already from encoder */
|
|
if (!ctx->drm_dev)
|
|
goto out;
|
|
|
|
if (!ctx->i80_if) {
|
|
drm_crtc_handle_vblank(&ctx->crtc->base);
|
|
|
|
/* set wait vsync event to zero and wake up queue. */
|
|
if (atomic_read(&ctx->wait_vsync_event)) {
|
|
atomic_set(&ctx->wait_vsync_event, 0);
|
|
wake_up(&ctx->wait_vsync_queue);
|
|
}
|
|
}
|
|
out:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int decon_bind(struct device *dev, struct device *master, void *data)
|
|
{
|
|
struct decon_context *ctx = dev_get_drvdata(dev);
|
|
struct drm_device *drm_dev = data;
|
|
struct exynos_drm_plane *exynos_plane;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = decon_ctx_initialize(ctx, drm_dev);
|
|
if (ret) {
|
|
DRM_ERROR("decon_ctx_initialize failed.\n");
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < WINDOWS_NR; i++) {
|
|
ctx->configs[i].pixel_formats = decon_formats;
|
|
ctx->configs[i].num_pixel_formats = ARRAY_SIZE(decon_formats);
|
|
ctx->configs[i].zpos = i;
|
|
ctx->configs[i].type = decon_win_types[i];
|
|
|
|
ret = exynos_plane_init(drm_dev, &ctx->planes[i], i,
|
|
&ctx->configs[i]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
exynos_plane = &ctx->planes[DEFAULT_WIN];
|
|
ctx->crtc = exynos_drm_crtc_create(drm_dev, &exynos_plane->base,
|
|
EXYNOS_DISPLAY_TYPE_LCD, &decon_crtc_ops, ctx);
|
|
if (IS_ERR(ctx->crtc)) {
|
|
decon_ctx_remove(ctx);
|
|
return PTR_ERR(ctx->crtc);
|
|
}
|
|
|
|
if (ctx->encoder)
|
|
exynos_dpi_bind(drm_dev, ctx->encoder);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void decon_unbind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
struct decon_context *ctx = dev_get_drvdata(dev);
|
|
|
|
decon_disable(ctx->crtc);
|
|
|
|
if (ctx->encoder)
|
|
exynos_dpi_remove(ctx->encoder);
|
|
|
|
decon_ctx_remove(ctx);
|
|
}
|
|
|
|
static const struct component_ops decon_component_ops = {
|
|
.bind = decon_bind,
|
|
.unbind = decon_unbind,
|
|
};
|
|
|
|
static int decon_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct decon_context *ctx;
|
|
struct device_node *i80_if_timings;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
if (!dev->of_node)
|
|
return -ENODEV;
|
|
|
|
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
ctx->dev = dev;
|
|
ctx->suspended = true;
|
|
|
|
i80_if_timings = of_get_child_by_name(dev->of_node, "i80-if-timings");
|
|
if (i80_if_timings)
|
|
ctx->i80_if = true;
|
|
of_node_put(i80_if_timings);
|
|
|
|
ctx->regs = of_iomap(dev->of_node, 0);
|
|
if (!ctx->regs)
|
|
return -ENOMEM;
|
|
|
|
ctx->pclk = devm_clk_get(dev, "pclk_decon0");
|
|
if (IS_ERR(ctx->pclk)) {
|
|
dev_err(dev, "failed to get bus clock pclk\n");
|
|
ret = PTR_ERR(ctx->pclk);
|
|
goto err_iounmap;
|
|
}
|
|
|
|
ctx->aclk = devm_clk_get(dev, "aclk_decon0");
|
|
if (IS_ERR(ctx->aclk)) {
|
|
dev_err(dev, "failed to get bus clock aclk\n");
|
|
ret = PTR_ERR(ctx->aclk);
|
|
goto err_iounmap;
|
|
}
|
|
|
|
ctx->eclk = devm_clk_get(dev, "decon0_eclk");
|
|
if (IS_ERR(ctx->eclk)) {
|
|
dev_err(dev, "failed to get eclock\n");
|
|
ret = PTR_ERR(ctx->eclk);
|
|
goto err_iounmap;
|
|
}
|
|
|
|
ctx->vclk = devm_clk_get(dev, "decon0_vclk");
|
|
if (IS_ERR(ctx->vclk)) {
|
|
dev_err(dev, "failed to get vclock\n");
|
|
ret = PTR_ERR(ctx->vclk);
|
|
goto err_iounmap;
|
|
}
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
|
|
ctx->i80_if ? "lcd_sys" : "vsync");
|
|
if (!res) {
|
|
dev_err(dev, "irq request failed.\n");
|
|
ret = -ENXIO;
|
|
goto err_iounmap;
|
|
}
|
|
|
|
ret = devm_request_irq(dev, res->start, decon_irq_handler,
|
|
0, "drm_decon", ctx);
|
|
if (ret) {
|
|
dev_err(dev, "irq request failed.\n");
|
|
goto err_iounmap;
|
|
}
|
|
|
|
init_waitqueue_head(&ctx->wait_vsync_queue);
|
|
atomic_set(&ctx->wait_vsync_event, 0);
|
|
|
|
platform_set_drvdata(pdev, ctx);
|
|
|
|
ctx->encoder = exynos_dpi_probe(dev);
|
|
if (IS_ERR(ctx->encoder)) {
|
|
ret = PTR_ERR(ctx->encoder);
|
|
goto err_iounmap;
|
|
}
|
|
|
|
pm_runtime_enable(dev);
|
|
|
|
ret = component_add(dev, &decon_component_ops);
|
|
if (ret)
|
|
goto err_disable_pm_runtime;
|
|
|
|
return ret;
|
|
|
|
err_disable_pm_runtime:
|
|
pm_runtime_disable(dev);
|
|
|
|
err_iounmap:
|
|
iounmap(ctx->regs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int decon_remove(struct platform_device *pdev)
|
|
{
|
|
struct decon_context *ctx = dev_get_drvdata(&pdev->dev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
iounmap(ctx->regs);
|
|
|
|
component_del(&pdev->dev, &decon_component_ops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int exynos7_decon_suspend(struct device *dev)
|
|
{
|
|
struct decon_context *ctx = dev_get_drvdata(dev);
|
|
|
|
clk_disable_unprepare(ctx->vclk);
|
|
clk_disable_unprepare(ctx->eclk);
|
|
clk_disable_unprepare(ctx->aclk);
|
|
clk_disable_unprepare(ctx->pclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos7_decon_resume(struct device *dev)
|
|
{
|
|
struct decon_context *ctx = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(ctx->pclk);
|
|
if (ret < 0) {
|
|
DRM_ERROR("Failed to prepare_enable the pclk [%d]\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(ctx->aclk);
|
|
if (ret < 0) {
|
|
DRM_ERROR("Failed to prepare_enable the aclk [%d]\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(ctx->eclk);
|
|
if (ret < 0) {
|
|
DRM_ERROR("Failed to prepare_enable the eclk [%d]\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(ctx->vclk);
|
|
if (ret < 0) {
|
|
DRM_ERROR("Failed to prepare_enable the vclk [%d]\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops exynos7_decon_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(exynos7_decon_suspend, exynos7_decon_resume,
|
|
NULL)
|
|
};
|
|
|
|
struct platform_driver decon_driver = {
|
|
.probe = decon_probe,
|
|
.remove = decon_remove,
|
|
.driver = {
|
|
.name = "exynos-decon",
|
|
.pm = &exynos7_decon_pm_ops,
|
|
.of_match_table = decon_driver_dt_match,
|
|
},
|
|
};
|