2011-10-04 14:19:01 +04:00
/* exynos_drm_fimd.c
*
* Copyright ( C ) 2011 Samsung Electronics Co . Ltd
* Authors :
* Joonyoung Shim < jy0922 . shim @ samsung . com >
* Inki Dae < inki . dae @ 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 .
*
*/
2012-10-02 21:01:07 +04:00
# include <drm/drmP.h>
2011-10-04 14:19:01 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
2011-12-09 11:52:11 +04:00
# include <linux/pm_runtime.h>
2011-10-04 14:19:01 +04:00
2012-08-08 04:44:49 +04:00
# include <video/samsung_fimd.h>
2011-10-04 14:19:01 +04:00
# include <drm/exynos_drm.h>
# include "exynos_drm_drv.h"
# include "exynos_drm_fbdev.h"
# include "exynos_drm_crtc.h"
/*
* FIMD is stand for Fully Interactive Mobile Display and
* as a display controller , it transfers contents drawn on memory
* to a LCD Panel through Display Interfaces such as RGB or
* CPU Interface .
*/
/* position control register for hardware window 0, 2 ~ 4.*/
# define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16)
# define VIDOSD_B(win) (VIDOSD_BASE + 0x04 + (win) * 16)
/* size control register for hardware window 0. */
# define VIDOSD_C_SIZE_W0 (VIDOSD_BASE + 0x08)
/* alpha control register for hardware window 1 ~ 4. */
# define VIDOSD_C(win) (VIDOSD_BASE + 0x18 + (win) * 16)
/* size control register for hardware window 1 ~ 4. */
# define VIDOSD_D(win) (VIDOSD_BASE + 0x0C + (win) * 16)
# define VIDWx_BUF_START(win, buf) (VIDW_BUF_START(buf) + (win) * 8)
# define VIDWx_BUF_END(win, buf) (VIDW_BUF_END(buf) + (win) * 8)
# define VIDWx_BUF_SIZE(win, buf) (VIDW_BUF_SIZE(buf) + (win) * 4)
/* color key control register for hardware window 1 ~ 4. */
# define WKEYCON0_BASE(x) ((WKEYCON0 + 0x140) + (x * 8))
/* color key value register for hardware window 1 ~ 4. */
# define WKEYCON1_BASE(x) ((WKEYCON1 + 0x140) + (x * 8))
/* FIMD has totally five hardware windows. */
# define WINDOWS_NR 5
# define get_fimd_context(dev) platform_get_drvdata(to_platform_device(dev))
2012-09-21 15:22:15 +04:00
struct fimd_driver_data {
unsigned int timing_base ;
} ;
struct fimd_driver_data exynos4_fimd_driver_data = {
. timing_base = 0x0 ,
} ;
struct fimd_driver_data exynos5_fimd_driver_data = {
. timing_base = 0x20000 ,
} ;
2011-10-04 14:19:01 +04:00
struct fimd_win_data {
unsigned int offset_x ;
unsigned int offset_y ;
2011-10-14 08:29:46 +04:00
unsigned int ovl_width ;
unsigned int ovl_height ;
unsigned int fb_width ;
unsigned int fb_height ;
2011-10-04 14:19:01 +04:00
unsigned int bpp ;
2011-11-12 10:23:32 +04:00
dma_addr_t dma_addr ;
2011-10-04 14:19:01 +04:00
void __iomem * vaddr ;
unsigned int buf_offsize ;
unsigned int line_size ; /* bytes */
2011-12-06 06:06:54 +04:00
bool enabled ;
2011-10-04 14:19:01 +04:00
} ;
struct fimd_context {
struct exynos_drm_subdrv subdrv ;
int irq ;
struct drm_crtc * crtc ;
struct clk * bus_clk ;
struct clk * lcd_clk ;
void __iomem * regs ;
struct fimd_win_data win_data [ WINDOWS_NR ] ;
unsigned int clkdiv ;
unsigned int default_win ;
unsigned long irq_flags ;
u32 vidcon0 ;
u32 vidcon1 ;
2011-12-09 11:52:11 +04:00
bool suspended ;
2011-12-16 16:49:03 +04:00
struct mutex lock ;
2011-10-04 14:19:01 +04:00
2012-02-14 10:59:46 +04:00
struct exynos_drm_panel_info * panel ;
2011-10-04 14:19:01 +04:00
} ;
2012-09-21 15:22:15 +04:00
static inline struct fimd_driver_data * drm_fimd_get_driver_data (
struct platform_device * pdev )
{
return ( struct fimd_driver_data * )
platform_get_device_id ( pdev ) - > driver_data ;
}
2011-10-04 14:19:01 +04:00
static bool fimd_display_is_connected ( struct device * dev )
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
/* TODO. */
return true ;
}
2012-02-14 10:59:46 +04:00
static void * fimd_get_panel ( struct device * dev )
2011-10-04 14:19:01 +04:00
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2012-02-14 10:59:46 +04:00
return ctx - > panel ;
2011-10-04 14:19:01 +04:00
}
static int fimd_check_timing ( struct device * dev , void * timing )
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
/* TODO. */
return 0 ;
}
static int fimd_display_power_on ( struct device * dev , int mode )
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-06 06:06:54 +04:00
/* TODO */
2011-10-04 14:19:01 +04:00
return 0 ;
}
2011-10-19 12:23:07 +04:00
static struct exynos_drm_display_ops fimd_display_ops = {
2011-10-04 14:19:01 +04:00
. type = EXYNOS_DISPLAY_TYPE_LCD ,
. is_connected = fimd_display_is_connected ,
2012-02-14 10:59:46 +04:00
. get_panel = fimd_get_panel ,
2011-10-04 14:19:01 +04:00
. check_timing = fimd_check_timing ,
. power_on = fimd_display_power_on ,
} ;
2011-12-06 06:06:54 +04:00
static void fimd_dpms ( struct device * subdrv_dev , int mode )
{
2011-12-16 16:49:03 +04:00
struct fimd_context * ctx = get_fimd_context ( subdrv_dev ) ;
2011-12-06 06:06:54 +04:00
DRM_DEBUG_KMS ( " %s, %d \n " , __FILE__ , mode ) ;
2011-12-16 16:49:03 +04:00
mutex_lock ( & ctx - > lock ) ;
2011-12-09 11:52:11 +04:00
switch ( mode ) {
case DRM_MODE_DPMS_ON :
2011-12-16 16:49:03 +04:00
/*
* enable fimd hardware only if suspended status .
*
* P . S . fimd_dpms function would be called at booting time so
* clk_enable could be called double time .
*/
if ( ctx - > suspended )
pm_runtime_get_sync ( subdrv_dev ) ;
2011-12-09 11:52:11 +04:00
break ;
case DRM_MODE_DPMS_STANDBY :
case DRM_MODE_DPMS_SUSPEND :
case DRM_MODE_DPMS_OFF :
2012-01-27 06:54:58 +04:00
if ( ! ctx - > suspended )
pm_runtime_put_sync ( subdrv_dev ) ;
2011-12-09 11:52:11 +04:00
break ;
default :
DRM_DEBUG_KMS ( " unspecified mode %d \n " , mode ) ;
break ;
}
2011-12-16 16:49:03 +04:00
mutex_unlock ( & ctx - > lock ) ;
2011-12-06 06:06:54 +04:00
}
static void fimd_apply ( struct device * subdrv_dev )
{
struct fimd_context * ctx = get_fimd_context ( subdrv_dev ) ;
2012-04-05 15:49:27 +04:00
struct exynos_drm_manager * mgr = ctx - > subdrv . manager ;
2011-12-06 06:06:54 +04:00
struct exynos_drm_manager_ops * mgr_ops = mgr - > ops ;
struct exynos_drm_overlay_ops * ovl_ops = mgr - > overlay_ops ;
struct fimd_win_data * win_data ;
2011-12-08 12:54:07 +04:00
int i ;
2011-12-06 06:06:54 +04:00
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-08 12:54:07 +04:00
for ( i = 0 ; i < WINDOWS_NR ; i + + ) {
win_data = & ctx - > win_data [ i ] ;
if ( win_data - > enabled & & ( ovl_ops & & ovl_ops - > commit ) )
ovl_ops - > commit ( subdrv_dev , i ) ;
}
2011-12-06 06:06:54 +04:00
if ( mgr_ops & & mgr_ops - > commit )
mgr_ops - > commit ( subdrv_dev ) ;
}
2011-10-04 14:19:01 +04:00
static void fimd_commit ( struct device * dev )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
2012-02-14 10:59:46 +04:00
struct exynos_drm_panel_info * panel = ctx - > panel ;
struct fb_videomode * timing = & panel - > timing ;
2012-09-21 15:22:15 +04:00
struct fimd_driver_data * driver_data ;
struct platform_device * pdev = to_platform_device ( dev ) ;
2011-10-04 14:19:01 +04:00
u32 val ;
2012-09-21 15:22:15 +04:00
driver_data = drm_fimd_get_driver_data ( pdev ) ;
2011-12-12 11:35:20 +04:00
if ( ctx - > suspended )
return ;
2011-10-04 14:19:01 +04:00
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
/* setup polarity values from machine code. */
2012-09-21 15:22:15 +04:00
writel ( ctx - > vidcon1 , ctx - > regs + driver_data - > timing_base + VIDCON1 ) ;
2011-10-04 14:19:01 +04:00
/* setup vertical timing values. */
val = VIDTCON0_VBPD ( timing - > upper_margin - 1 ) |
VIDTCON0_VFPD ( timing - > lower_margin - 1 ) |
VIDTCON0_VSPW ( timing - > vsync_len - 1 ) ;
2012-09-21 15:22:15 +04:00
writel ( val , ctx - > regs + driver_data - > timing_base + VIDTCON0 ) ;
2011-10-04 14:19:01 +04:00
/* setup horizontal timing values. */
val = VIDTCON1_HBPD ( timing - > left_margin - 1 ) |
VIDTCON1_HFPD ( timing - > right_margin - 1 ) |
VIDTCON1_HSPW ( timing - > hsync_len - 1 ) ;
2012-09-21 15:22:15 +04:00
writel ( val , ctx - > regs + driver_data - > timing_base + VIDTCON1 ) ;
2011-10-04 14:19:01 +04:00
/* setup horizontal and vertical display size. */
val = VIDTCON2_LINEVAL ( timing - > yres - 1 ) |
VIDTCON2_HOZVAL ( timing - > xres - 1 ) ;
2012-09-21 15:22:15 +04:00
writel ( val , ctx - > regs + driver_data - > timing_base + VIDTCON2 ) ;
2011-10-04 14:19:01 +04:00
/* setup clock source, clock divider, enable dma. */
val = ctx - > vidcon0 ;
val & = ~ ( VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR ) ;
if ( ctx - > clkdiv > 1 )
val | = VIDCON0_CLKVAL_F ( ctx - > clkdiv - 1 ) | VIDCON0_CLKDIR ;
else
val & = ~ VIDCON0_CLKDIR ; /* 1:1 clock */
/*
* 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 ) ;
}
static int fimd_enable_vblank ( struct device * dev )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
u32 val ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-09 11:52:11 +04:00
if ( ctx - > suspended )
return - EPERM ;
2011-10-04 14:19:01 +04:00
if ( ! test_and_set_bit ( 0 , & ctx - > irq_flags ) ) {
val = readl ( ctx - > regs + VIDINTCON0 ) ;
val | = VIDINTCON0_INT_ENABLE ;
val | = VIDINTCON0_INT_FRAME ;
val & = ~ VIDINTCON0_FRAMESEL0_MASK ;
val | = VIDINTCON0_FRAMESEL0_VSYNC ;
val & = ~ VIDINTCON0_FRAMESEL1_MASK ;
val | = VIDINTCON0_FRAMESEL1_NONE ;
writel ( val , ctx - > regs + VIDINTCON0 ) ;
}
return 0 ;
}
static void fimd_disable_vblank ( struct device * dev )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
u32 val ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-09 11:52:11 +04:00
if ( ctx - > suspended )
return ;
2011-10-04 14:19:01 +04:00
if ( test_and_clear_bit ( 0 , & ctx - > irq_flags ) ) {
val = readl ( ctx - > regs + VIDINTCON0 ) ;
val & = ~ VIDINTCON0_INT_FRAME ;
val & = ~ VIDINTCON0_INT_ENABLE ;
writel ( val , ctx - > regs + VIDINTCON0 ) ;
}
}
static struct exynos_drm_manager_ops fimd_manager_ops = {
2011-12-06 06:06:54 +04:00
. dpms = fimd_dpms ,
. apply = fimd_apply ,
2011-10-04 14:19:01 +04:00
. commit = fimd_commit ,
. enable_vblank = fimd_enable_vblank ,
. disable_vblank = fimd_disable_vblank ,
} ;
static void fimd_win_mode_set ( struct device * dev ,
struct exynos_drm_overlay * overlay )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
struct fimd_win_data * win_data ;
2011-12-08 12:54:07 +04:00
int win ;
2011-10-14 08:29:46 +04:00
unsigned long offset ;
2011-10-04 14:19:01 +04:00
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
if ( ! overlay ) {
dev_err ( dev , " overlay is NULL \n " ) ;
return ;
}
2011-12-08 12:54:07 +04:00
win = overlay - > zpos ;
if ( win = = DEFAULT_ZPOS )
win = ctx - > default_win ;
if ( win < 0 | | win > WINDOWS_NR )
return ;
2011-10-14 08:29:46 +04:00
offset = overlay - > fb_x * ( overlay - > bpp > > 3 ) ;
offset + = overlay - > fb_y * overlay - > pitch ;
DRM_DEBUG_KMS ( " offset = 0x%lx, pitch = %x \n " , offset , overlay - > pitch ) ;
2011-12-08 12:54:07 +04:00
win_data = & ctx - > win_data [ win ] ;
2011-10-04 14:19:01 +04:00
2011-10-14 08:29:46 +04:00
win_data - > offset_x = overlay - > crtc_x ;
win_data - > offset_y = overlay - > crtc_y ;
win_data - > ovl_width = overlay - > crtc_width ;
win_data - > ovl_height = overlay - > crtc_height ;
win_data - > fb_width = overlay - > fb_width ;
win_data - > fb_height = overlay - > fb_height ;
2011-12-15 09:36:22 +04:00
win_data - > dma_addr = overlay - > dma_addr [ 0 ] + offset ;
win_data - > vaddr = overlay - > vaddr [ 0 ] + offset ;
2011-10-04 14:19:01 +04:00
win_data - > bpp = overlay - > bpp ;
2011-10-14 08:29:46 +04:00
win_data - > buf_offsize = ( overlay - > fb_width - overlay - > crtc_width ) *
( overlay - > bpp > > 3 ) ;
win_data - > line_size = overlay - > crtc_width * ( overlay - > bpp > > 3 ) ;
DRM_DEBUG_KMS ( " offset_x = %d, offset_y = %d \n " ,
win_data - > offset_x , win_data - > offset_y ) ;
DRM_DEBUG_KMS ( " ovl_width = %d, ovl_height = %d \n " ,
win_data - > ovl_width , win_data - > ovl_height ) ;
DRM_DEBUG_KMS ( " paddr = 0x%lx, vaddr = 0x%lx \n " ,
2011-11-12 10:23:32 +04:00
( unsigned long ) win_data - > dma_addr ,
2011-10-14 08:29:46 +04:00
( unsigned long ) win_data - > vaddr ) ;
DRM_DEBUG_KMS ( " fb_width = %d, crtc_width = %d \n " ,
overlay - > fb_width , overlay - > crtc_width ) ;
2011-10-04 14:19:01 +04:00
}
static void fimd_win_set_pixfmt ( struct device * dev , unsigned int win )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
struct fimd_win_data * win_data = & ctx - > win_data [ win ] ;
unsigned long val ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
val = WINCONx_ENWIN ;
switch ( win_data - > bpp ) {
case 1 :
val | = WINCON0_BPPMODE_1BPP ;
val | = WINCONx_BITSWP ;
val | = WINCONx_BURSTLEN_4WORD ;
break ;
case 2 :
val | = WINCON0_BPPMODE_2BPP ;
val | = WINCONx_BITSWP ;
val | = WINCONx_BURSTLEN_8WORD ;
break ;
case 4 :
val | = WINCON0_BPPMODE_4BPP ;
val | = WINCONx_BITSWP ;
val | = WINCONx_BURSTLEN_8WORD ;
break ;
case 8 :
val | = WINCON0_BPPMODE_8BPP_PALETTE ;
val | = WINCONx_BURSTLEN_8WORD ;
val | = WINCONx_BYTSWP ;
break ;
case 16 :
val | = WINCON0_BPPMODE_16BPP_565 ;
val | = WINCONx_HAWSWP ;
val | = WINCONx_BURSTLEN_16WORD ;
break ;
case 24 :
val | = WINCON0_BPPMODE_24BPP_888 ;
val | = WINCONx_WSWP ;
val | = WINCONx_BURSTLEN_16WORD ;
break ;
case 32 :
val | = WINCON1_BPPMODE_28BPP_A4888
| WINCON1_BLD_PIX | WINCON1_ALPHA_SEL ;
val | = WINCONx_WSWP ;
val | = WINCONx_BURSTLEN_16WORD ;
break ;
default :
DRM_DEBUG_KMS ( " invalid pixel size so using unpacked 24bpp. \n " ) ;
val | = WINCON0_BPPMODE_24BPP_888 ;
val | = WINCONx_WSWP ;
val | = WINCONx_BURSTLEN_16WORD ;
break ;
}
DRM_DEBUG_KMS ( " bpp = %d \n " , win_data - > bpp ) ;
writel ( val , ctx - > regs + WINCON ( win ) ) ;
}
static void fimd_win_set_colkey ( struct device * dev , unsigned int win )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
unsigned int keycon0 = 0 , keycon1 = 0 ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
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 ) ) ;
}
2011-12-08 12:54:07 +04:00
static void fimd_win_commit ( struct device * dev , int zpos )
2011-10-04 14:19:01 +04:00
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
struct fimd_win_data * win_data ;
2011-12-08 12:54:07 +04:00
int win = zpos ;
2011-10-04 14:19:01 +04:00
unsigned long val , alpha , size ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-12 11:35:20 +04:00
if ( ctx - > suspended )
return ;
2011-12-08 12:54:07 +04:00
if ( win = = DEFAULT_ZPOS )
win = ctx - > default_win ;
2011-10-04 14:19:01 +04:00
if ( win < 0 | | win > WINDOWS_NR )
return ;
win_data = & ctx - > win_data [ win ] ;
/*
* SHADOWCON register is used for enabling timing .
*
* for example , once only width value of a register is set ,
* if the dma is started then fimd 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 .
*/
/* protect windows */
val = readl ( ctx - > regs + SHADOWCON ) ;
val | = SHADOWCON_WINx_PROTECT ( win ) ;
writel ( val , ctx - > regs + SHADOWCON ) ;
/* buffer start address */
2011-11-12 10:23:32 +04:00
val = ( unsigned long ) win_data - > dma_addr ;
2011-10-04 14:19:01 +04:00
writel ( val , ctx - > regs + VIDWx_BUF_START ( win , 0 ) ) ;
/* buffer end address */
2011-10-14 08:29:46 +04:00
size = win_data - > fb_width * win_data - > ovl_height * ( win_data - > bpp > > 3 ) ;
2011-11-12 10:23:32 +04:00
val = ( unsigned long ) ( win_data - > dma_addr + size ) ;
2011-10-04 14:19:01 +04:00
writel ( val , ctx - > regs + VIDWx_BUF_END ( win , 0 ) ) ;
DRM_DEBUG_KMS ( " start addr = 0x%lx, end addr = 0x%lx, size = 0x%lx \n " ,
2011-11-12 10:23:32 +04:00
( unsigned long ) win_data - > dma_addr , val , size ) ;
2011-10-14 08:29:46 +04:00
DRM_DEBUG_KMS ( " ovl_width = %d, ovl_height = %d \n " ,
win_data - > ovl_width , win_data - > ovl_height ) ;
2011-10-04 14:19:01 +04:00
/* buffer size */
val = VIDW_BUF_SIZE_OFFSET ( win_data - > buf_offsize ) |
VIDW_BUF_SIZE_PAGEWIDTH ( win_data - > line_size ) ;
writel ( val , ctx - > regs + VIDWx_BUF_SIZE ( win , 0 ) ) ;
/* OSD position */
val = VIDOSDxA_TOPLEFT_X ( win_data - > offset_x ) |
VIDOSDxA_TOPLEFT_Y ( win_data - > offset_y ) ;
writel ( val , ctx - > regs + VIDOSD_A ( win ) ) ;
2011-10-14 08:29:46 +04:00
val = VIDOSDxB_BOTRIGHT_X ( win_data - > offset_x +
win_data - > ovl_width - 1 ) |
VIDOSDxB_BOTRIGHT_Y ( win_data - > offset_y +
win_data - > ovl_height - 1 ) ;
2011-10-04 14:19:01 +04:00
writel ( val , ctx - > regs + VIDOSD_B ( win ) ) ;
2011-10-14 08:29:46 +04:00
DRM_DEBUG_KMS ( " osd pos: tx = %d, ty = %d, bx = %d, by = %d \n " ,
2011-10-04 14:19:01 +04:00
win_data - > offset_x , win_data - > offset_y ,
2011-10-14 08:29:46 +04:00
win_data - > offset_x + win_data - > ovl_width - 1 ,
win_data - > offset_y + win_data - > ovl_height - 1 ) ;
2011-10-04 14:19:01 +04:00
/* hardware window 0 doesn't support alpha channel. */
if ( win ! = 0 ) {
/* OSD alpha */
alpha = VIDISD14C_ALPHA1_R ( 0xf ) |
VIDISD14C_ALPHA1_G ( 0xf ) |
VIDISD14C_ALPHA1_B ( 0xf ) ;
writel ( alpha , ctx - > regs + VIDOSD_C ( win ) ) ;
}
/* OSD size */
if ( win ! = 3 & & win ! = 4 ) {
u32 offset = VIDOSD_D ( win ) ;
if ( win = = 0 )
offset = VIDOSD_C_SIZE_W0 ;
2011-10-14 08:29:46 +04:00
val = win_data - > ovl_width * win_data - > ovl_height ;
2011-10-04 14:19:01 +04:00
writel ( val , ctx - > regs + offset ) ;
DRM_DEBUG_KMS ( " osd size = 0x%x \n " , ( unsigned int ) val ) ;
}
fimd_win_set_pixfmt ( dev , win ) ;
/* hardware window 0 doesn't support color key. */
if ( win ! = 0 )
fimd_win_set_colkey ( dev , win ) ;
2011-12-06 06:06:54 +04:00
/* wincon */
val = readl ( ctx - > regs + WINCON ( win ) ) ;
val | = WINCONx_ENWIN ;
writel ( val , ctx - > regs + WINCON ( win ) ) ;
2011-10-04 14:19:01 +04:00
/* Enable DMA channel and unprotect windows */
val = readl ( ctx - > regs + SHADOWCON ) ;
val | = SHADOWCON_CHx_ENABLE ( win ) ;
val & = ~ SHADOWCON_WINx_PROTECT ( win ) ;
writel ( val , ctx - > regs + SHADOWCON ) ;
2011-12-06 06:06:54 +04:00
win_data - > enabled = true ;
2011-10-04 14:19:01 +04:00
}
2011-12-08 12:54:07 +04:00
static void fimd_win_disable ( struct device * dev , int zpos )
2011-10-04 14:19:01 +04:00
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
2011-12-06 06:06:54 +04:00
struct fimd_win_data * win_data ;
2011-12-08 12:54:07 +04:00
int win = zpos ;
2011-10-04 14:19:01 +04:00
u32 val ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-08 12:54:07 +04:00
if ( win = = DEFAULT_ZPOS )
win = ctx - > default_win ;
2011-10-04 14:19:01 +04:00
if ( win < 0 | | win > WINDOWS_NR )
return ;
2011-12-06 06:06:54 +04:00
win_data = & ctx - > win_data [ win ] ;
2011-10-04 14:19:01 +04:00
/* protect windows */
val = readl ( ctx - > regs + SHADOWCON ) ;
val | = SHADOWCON_WINx_PROTECT ( win ) ;
writel ( val , ctx - > regs + SHADOWCON ) ;
/* wincon */
val = readl ( ctx - > regs + WINCON ( win ) ) ;
val & = ~ WINCONx_ENWIN ;
writel ( val , ctx - > regs + WINCON ( win ) ) ;
/* unprotect windows */
val = readl ( ctx - > regs + SHADOWCON ) ;
val & = ~ SHADOWCON_CHx_ENABLE ( win ) ;
val & = ~ SHADOWCON_WINx_PROTECT ( win ) ;
writel ( val , ctx - > regs + SHADOWCON ) ;
2011-12-06 06:06:54 +04:00
win_data - > enabled = false ;
2011-10-04 14:19:01 +04:00
}
2012-08-20 14:58:05 +04:00
static void fimd_wait_for_vblank ( struct device * dev )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
int ret ;
ret = wait_for ( ( __raw_readl ( ctx - > regs + VIDCON1 ) &
VIDCON1_VSTATUS_VSYNC ) , 50 ) ;
if ( ret < 0 )
DRM_DEBUG_KMS ( " vblank wait timed out. \n " ) ;
}
2011-10-04 14:19:01 +04:00
static struct exynos_drm_overlay_ops fimd_overlay_ops = {
. mode_set = fimd_win_mode_set ,
. commit = fimd_win_commit ,
. disable = fimd_win_disable ,
2012-08-20 14:58:05 +04:00
. wait_for_vblank = fimd_wait_for_vblank ,
2011-10-04 14:19:01 +04:00
} ;
2012-04-05 15:49:27 +04:00
static struct exynos_drm_manager fimd_manager = {
. pipe = - 1 ,
. ops = & fimd_manager_ops ,
. overlay_ops = & fimd_overlay_ops ,
. display_ops = & fimd_display_ops ,
} ;
2011-10-04 14:19:01 +04:00
static void fimd_finish_pageflip ( struct drm_device * drm_dev , int crtc )
{
struct exynos_drm_private * dev_priv = drm_dev - > dev_private ;
struct drm_pending_vblank_event * e , * t ;
struct timeval now ;
unsigned long flags ;
2011-10-14 08:29:51 +04:00
bool is_checked = false ;
2011-10-04 14:19:01 +04:00
spin_lock_irqsave ( & drm_dev - > event_lock , flags ) ;
list_for_each_entry_safe ( e , t , & dev_priv - > pageflip_event_list ,
base . link ) {
2011-10-14 08:29:52 +04:00
/* if event's pipe isn't same as crtc then ignore it. */
2011-10-14 08:29:51 +04:00
if ( crtc ! = e - > pipe )
continue ;
is_checked = true ;
2011-10-04 14:19:01 +04:00
do_gettimeofday ( & now ) ;
e - > event . sequence = 0 ;
e - > event . tv_sec = now . tv_sec ;
e - > event . tv_usec = now . tv_usec ;
list_move_tail ( & e - > base . link , & e - > base . file_priv - > event_list ) ;
wake_up_interruptible ( & e - > base . file_priv - > event_wait ) ;
}
2011-12-06 06:06:54 +04:00
if ( is_checked ) {
2012-02-14 06:15:09 +04:00
/*
* call drm_vblank_put only in case that drm_vblank_get was
* called .
*/
if ( atomic_read ( & drm_dev - > vblank_refcount [ crtc ] ) > 0 )
drm_vblank_put ( drm_dev , crtc ) ;
2011-10-04 14:19:01 +04:00
2011-12-06 06:06:54 +04:00
/*
* don ' t off vblank if vblank_disable_allowed is 1 ,
* because vblank would be off by timer handler .
*/
if ( ! drm_dev - > vblank_disable_allowed )
drm_vblank_off ( drm_dev , crtc ) ;
}
2011-10-04 14:19:01 +04:00
spin_unlock_irqrestore ( & drm_dev - > event_lock , flags ) ;
}
static irqreturn_t fimd_irq_handler ( int irq , void * dev_id )
{
struct fimd_context * ctx = ( struct fimd_context * ) dev_id ;
struct exynos_drm_subdrv * subdrv = & ctx - > subdrv ;
struct drm_device * drm_dev = subdrv - > drm_dev ;
2012-04-05 15:49:27 +04:00
struct exynos_drm_manager * manager = subdrv - > manager ;
2011-10-04 14:19:01 +04:00
u32 val ;
val = readl ( ctx - > regs + VIDINTCON1 ) ;
if ( val & VIDINTCON1_INT_FRAME )
/* VSYNC interrupt */
writel ( VIDINTCON1_INT_FRAME , ctx - > regs + VIDINTCON1 ) ;
2011-12-06 06:06:54 +04:00
/* check the crtc is detached already from encoder */
if ( manager - > pipe < 0 )
goto out ;
2011-11-11 16:28:00 +04:00
2011-10-04 14:19:01 +04:00
drm_handle_vblank ( drm_dev , manager - > pipe ) ;
fimd_finish_pageflip ( drm_dev , manager - > pipe ) ;
2011-12-06 06:06:54 +04:00
out :
2011-10-04 14:19:01 +04:00
return IRQ_HANDLED ;
}
2011-10-14 08:29:48 +04:00
static int fimd_subdrv_probe ( struct drm_device * drm_dev , struct device * dev )
2011-10-04 14:19:01 +04:00
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
/*
* enable drm irq mode .
* - with irq_enabled = 1 , we can use the vblank feature .
*
* P . S . note that we wouldn ' t use drm irq handler but
* just specific driver own one instead because
* drm framework supports only one irq handler .
*/
drm_dev - > irq_enabled = 1 ;
2011-12-06 06:06:54 +04:00
/*
* with vblank_disable_allowed = 1 , vblank interrupt will be disabled
* by drm timer once a current process gives up ownership of
* vblank event . ( after drm_vblank_put function is called )
*/
drm_dev - > vblank_disable_allowed = 1 ;
2011-10-04 14:19:01 +04:00
return 0 ;
}
2012-09-05 09:12:06 +04:00
static void fimd_subdrv_remove ( struct drm_device * drm_dev , struct device * dev )
2011-10-04 14:19:01 +04:00
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
/* TODO. */
}
static int fimd_calc_clkdiv ( struct fimd_context * ctx ,
struct fb_videomode * timing )
{
unsigned long clk = clk_get_rate ( ctx - > lcd_clk ) ;
u32 retrace ;
u32 clkdiv ;
u32 best_framerate = 0 ;
u32 framerate ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
retrace = timing - > left_margin + timing - > hsync_len +
timing - > right_margin + timing - > xres ;
retrace * = timing - > upper_margin + timing - > vsync_len +
timing - > lower_margin + timing - > yres ;
/* default framerate is 60Hz */
if ( ! timing - > refresh )
timing - > refresh = 60 ;
clk / = retrace ;
for ( clkdiv = 1 ; clkdiv < 0x100 ; clkdiv + + ) {
int tmp ;
/* get best framerate */
framerate = clk / clkdiv ;
tmp = timing - > refresh - framerate ;
if ( tmp < 0 ) {
best_framerate = framerate ;
continue ;
} else {
if ( ! best_framerate )
best_framerate = framerate ;
else if ( tmp < ( best_framerate - framerate ) )
best_framerate = framerate ;
break ;
}
}
return clkdiv ;
}
static void fimd_clear_win ( struct fimd_context * ctx , int win )
{
u32 val ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
writel ( 0 , ctx - > regs + WINCON ( win ) ) ;
writel ( 0 , ctx - > regs + VIDOSD_A ( win ) ) ;
writel ( 0 , ctx - > regs + VIDOSD_B ( win ) ) ;
writel ( 0 , ctx - > regs + VIDOSD_C ( win ) ) ;
if ( win = = 1 | | win = = 2 )
writel ( 0 , ctx - > regs + VIDOSD_D ( win ) ) ;
val = readl ( ctx - > regs + SHADOWCON ) ;
val & = ~ SHADOWCON_WINx_PROTECT ( win ) ;
writel ( val , ctx - > regs + SHADOWCON ) ;
}
2012-08-17 12:08:04 +04:00
static int fimd_clock ( struct fimd_context * ctx , bool enable )
2012-01-27 06:54:58 +04:00
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
if ( enable ) {
int ret ;
ret = clk_enable ( ctx - > bus_clk ) ;
if ( ret < 0 )
return ret ;
ret = clk_enable ( ctx - > lcd_clk ) ;
if ( ret < 0 ) {
clk_disable ( ctx - > bus_clk ) ;
return ret ;
}
2012-08-17 12:08:04 +04:00
} else {
clk_disable ( ctx - > lcd_clk ) ;
clk_disable ( ctx - > bus_clk ) ;
}
return 0 ;
}
static int fimd_activate ( struct fimd_context * ctx , bool enable )
{
if ( enable ) {
int ret ;
struct device * dev = ctx - > subdrv . dev ;
ret = fimd_clock ( ctx , true ) ;
if ( ret < 0 )
return ret ;
2012-01-27 06:54:58 +04:00
ctx - > suspended = false ;
/* if vblank was enabled status, enable it again. */
if ( test_and_clear_bit ( 0 , & ctx - > irq_flags ) )
fimd_enable_vblank ( dev ) ;
} else {
2012-08-17 12:08:04 +04:00
fimd_clock ( ctx , false ) ;
2012-01-27 06:54:58 +04:00
ctx - > suspended = true ;
}
return 0 ;
}
2011-10-04 14:19:01 +04:00
static int __devinit fimd_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct fimd_context * ctx ;
struct exynos_drm_subdrv * subdrv ;
struct exynos_drm_fimd_pdata * pdata ;
2012-02-14 10:59:46 +04:00
struct exynos_drm_panel_info * panel ;
2011-10-04 14:19:01 +04:00
struct resource * res ;
int win ;
int ret = - EINVAL ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
pdata = pdev - > dev . platform_data ;
if ( ! pdata ) {
dev_err ( dev , " no platform data specified \n " ) ;
return - EINVAL ;
}
2012-02-14 10:59:46 +04:00
panel = & pdata - > panel ;
if ( ! panel ) {
dev_err ( dev , " panel is null. \n " ) ;
2011-10-04 14:19:01 +04:00
return - EINVAL ;
}
2012-06-19 10:17:39 +04:00
ctx = devm_kzalloc ( & pdev - > dev , sizeof ( * ctx ) , GFP_KERNEL ) ;
2011-10-04 14:19:01 +04:00
if ( ! ctx )
return - ENOMEM ;
ctx - > bus_clk = clk_get ( dev , " fimd " ) ;
if ( IS_ERR ( ctx - > bus_clk ) ) {
dev_err ( dev , " failed to get bus clock \n " ) ;
ret = PTR_ERR ( ctx - > bus_clk ) ;
goto err_clk_get ;
}
ctx - > lcd_clk = clk_get ( dev , " sclk_fimd " ) ;
if ( IS_ERR ( ctx - > lcd_clk ) ) {
dev_err ( dev , " failed to get lcd clock \n " ) ;
ret = PTR_ERR ( ctx - > lcd_clk ) ;
goto err_bus_clk ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2012-06-19 10:17:39 +04:00
ctx - > regs = devm_request_and_ioremap ( & pdev - > dev , res ) ;
2011-10-04 14:19:01 +04:00
if ( ! ctx - > regs ) {
dev_err ( dev , " failed to map registers \n " ) ;
ret = - ENXIO ;
2012-06-19 10:17:39 +04:00
goto err_clk ;
2011-10-04 14:19:01 +04:00
}
res = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
if ( ! res ) {
dev_err ( dev , " irq request failed. \n " ) ;
2012-06-19 10:17:39 +04:00
goto err_clk ;
2011-10-04 14:19:01 +04:00
}
ctx - > irq = res - > start ;
2012-06-19 10:17:39 +04:00
ret = devm_request_irq ( & pdev - > dev , ctx - > irq , fimd_irq_handler ,
0 , " drm_fimd " , ctx ) ;
if ( ret ) {
2011-10-04 14:19:01 +04:00
dev_err ( dev , " irq request failed. \n " ) ;
2012-06-19 10:17:39 +04:00
goto err_clk ;
2011-10-04 14:19:01 +04:00
}
ctx - > vidcon0 = pdata - > vidcon0 ;
ctx - > vidcon1 = pdata - > vidcon1 ;
ctx - > default_win = pdata - > default_win ;
2012-02-14 10:59:46 +04:00
ctx - > panel = panel ;
2011-10-04 14:19:01 +04:00
subdrv = & ctx - > subdrv ;
2012-04-05 15:49:27 +04:00
subdrv - > dev = dev ;
subdrv - > manager = & fimd_manager ;
2011-10-04 14:19:01 +04:00
subdrv - > probe = fimd_subdrv_probe ;
subdrv - > remove = fimd_subdrv_remove ;
2011-12-16 16:49:03 +04:00
mutex_init ( & ctx - > lock ) ;
2011-10-04 14:19:01 +04:00
platform_set_drvdata ( pdev , ctx ) ;
2011-12-16 16:49:03 +04:00
pm_runtime_enable ( dev ) ;
pm_runtime_get_sync ( dev ) ;
2012-03-08 05:28:56 +04:00
ctx - > clkdiv = fimd_calc_clkdiv ( ctx , & panel - > timing ) ;
panel - > timing . pixclock = clk_get_rate ( ctx - > lcd_clk ) / ctx - > clkdiv ;
DRM_DEBUG_KMS ( " pixel clock = %d, clkdiv = %d \n " ,
panel - > timing . pixclock , ctx - > clkdiv ) ;
2011-12-16 16:49:03 +04:00
for ( win = 0 ; win < WINDOWS_NR ; win + + )
fimd_clear_win ( ctx , win ) ;
2011-10-04 14:19:01 +04:00
exynos_drm_subdrv_register ( subdrv ) ;
return 0 ;
err_clk :
clk_disable ( ctx - > lcd_clk ) ;
clk_put ( ctx - > lcd_clk ) ;
err_bus_clk :
clk_disable ( ctx - > bus_clk ) ;
clk_put ( ctx - > bus_clk ) ;
err_clk_get :
return ret ;
}
static int __devexit fimd_remove ( struct platform_device * pdev )
{
2011-12-09 11:52:11 +04:00
struct device * dev = & pdev - > dev ;
2011-10-04 14:19:01 +04:00
struct fimd_context * ctx = platform_get_drvdata ( pdev ) ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
exynos_drm_subdrv_unregister ( & ctx - > subdrv ) ;
2011-12-09 11:52:11 +04:00
if ( ctx - > suspended )
goto out ;
2011-10-04 14:19:01 +04:00
clk_disable ( ctx - > lcd_clk ) ;
clk_disable ( ctx - > bus_clk ) ;
2011-12-09 11:52:11 +04:00
pm_runtime_set_suspended ( dev ) ;
pm_runtime_put_sync ( dev ) ;
out :
pm_runtime_disable ( dev ) ;
2011-10-04 14:19:01 +04:00
clk_put ( ctx - > lcd_clk ) ;
clk_put ( ctx - > bus_clk ) ;
return 0 ;
}
2011-12-12 11:35:20 +04:00
# ifdef CONFIG_PM_SLEEP
static int fimd_suspend ( struct device * dev )
{
2012-01-27 06:54:58 +04:00
struct fimd_context * ctx = get_fimd_context ( dev ) ;
2011-12-12 11:35:20 +04:00
2012-01-27 06:54:58 +04:00
/*
* do not use pm_runtime_suspend ( ) . if pm_runtime_suspend ( ) is
* called here , an error would be returned by that interface
* because the usage_count of pm runtime is more than 1.
*/
2012-08-17 12:08:04 +04:00
if ( ! pm_runtime_suspended ( dev ) )
return fimd_activate ( ctx , false ) ;
return 0 ;
2011-12-12 11:35:20 +04:00
}
static int fimd_resume ( struct device * dev )
{
2012-01-27 06:54:58 +04:00
struct fimd_context * ctx = get_fimd_context ( dev ) ;
2011-12-12 11:35:20 +04:00
2012-01-27 06:54:58 +04:00
/*
* if entered to sleep when lcd panel was on , the usage_count
* of pm runtime would still be 1 so in this case , fimd driver
* should be on directly not drawing on pm runtime interface .
*/
2012-08-17 12:08:04 +04:00
if ( pm_runtime_suspended ( dev ) ) {
int ret ;
ret = fimd_activate ( ctx , true ) ;
if ( ret < 0 )
return ret ;
/*
* in case of dpms on ( standby ) , fimd_apply function will
* be called by encoder ' s dpms callback to update fimd ' s
* registers but in case of sleep wakeup , it ' s not .
* so fimd_apply function should be called at here .
*/
fimd_apply ( dev ) ;
}
2011-12-12 11:35:20 +04:00
return 0 ;
}
# endif
2011-12-09 11:52:11 +04:00
# ifdef CONFIG_PM_RUNTIME
static int fimd_runtime_suspend ( struct device * dev )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2012-08-17 12:08:04 +04:00
return fimd_activate ( ctx , false ) ;
2011-12-09 11:52:11 +04:00
}
static int fimd_runtime_resume ( struct device * dev )
{
struct fimd_context * ctx = get_fimd_context ( dev ) ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2012-08-17 12:08:04 +04:00
return fimd_activate ( ctx , true ) ;
2011-12-09 11:52:11 +04:00
}
# endif
2012-09-21 15:22:15 +04:00
static struct platform_device_id fimd_driver_ids [ ] = {
{
. name = " exynos4-fb " ,
. driver_data = ( unsigned long ) & exynos4_fimd_driver_data ,
} , {
. name = " exynos5-fb " ,
. driver_data = ( unsigned long ) & exynos5_fimd_driver_data ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( platform , fimd_driver_ids ) ;
2011-12-09 11:52:11 +04:00
static const struct dev_pm_ops fimd_pm_ops = {
2011-12-12 11:35:20 +04:00
SET_SYSTEM_SLEEP_PM_OPS ( fimd_suspend , fimd_resume )
2011-12-09 11:52:11 +04:00
SET_RUNTIME_PM_OPS ( fimd_runtime_suspend , fimd_runtime_resume , NULL )
} ;
2012-03-16 13:47:08 +04:00
struct platform_driver fimd_driver = {
2011-10-04 14:19:01 +04:00
. probe = fimd_probe ,
. remove = __devexit_p ( fimd_remove ) ,
2012-09-21 15:22:15 +04:00
. id_table = fimd_driver_ids ,
2011-10-04 14:19:01 +04:00
. driver = {
. name = " exynos4-fb " ,
. owner = THIS_MODULE ,
2011-12-09 11:52:11 +04:00
. pm = & fimd_pm_ops ,
2011-10-04 14:19:01 +04:00
} ,
} ;