2011-10-04 14:19:01 +04:00
/* exynos_drm_crtc.c
*
* Copyright ( c ) 2011 Samsung Electronics Co . , Ltd .
* Authors :
* Inki Dae < inki . dae @ samsung . com >
* Joonyoung Shim < jy0922 . shim @ samsung . com >
* Seung - Woo Kim < sw0312 . kim @ samsung . com >
*
2012-12-17 21:30:17 +04:00
* 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 .
2011-10-04 14:19:01 +04:00
*/
2012-10-02 21:01:07 +04:00
# include <drm/drmP.h>
# include <drm/drm_crtc_helper.h>
2011-10-04 14:19:01 +04:00
2013-08-13 03:46:40 +04:00
# include "exynos_drm_crtc.h"
2011-10-04 14:19:01 +04:00
# include "exynos_drm_drv.h"
# include "exynos_drm_encoder.h"
2012-06-27 09:27:04 +04:00
# include "exynos_drm_plane.h"
2011-10-04 14:19:01 +04:00
static void exynos_drm_crtc_dpms ( struct drm_crtc * crtc , int mode )
{
2011-11-04 12:04:45 +04:00
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
2014-02-19 16:02:55 +04:00
struct exynos_drm_manager * manager = exynos_crtc - > manager ;
2011-10-04 14:19:01 +04:00
2011-11-04 12:04:45 +04:00
DRM_DEBUG_KMS ( " crtc[%d] mode[%d] \n " , crtc - > base . id , mode ) ;
2011-12-06 06:06:54 +04:00
if ( exynos_crtc - > dpms = = mode ) {
DRM_DEBUG_KMS ( " desired dpms mode is same as previous one. \n " ) ;
return ;
}
2013-05-21 11:55:58 +04:00
if ( mode > DRM_MODE_DPMS_ON ) {
/* wait for the completion of page flip. */
2014-07-17 13:01:17 +04:00
if ( ! wait_event_timeout ( exynos_crtc - > pending_flip_queue ,
! atomic_read ( & exynos_crtc - > pending_flip ) ,
HZ / 20 ) )
atomic_set ( & exynos_crtc - > pending_flip , 0 ) ;
2014-10-10 16:31:55 +04:00
drm_crtc_vblank_off ( crtc ) ;
2013-05-21 11:55:58 +04:00
}
2014-02-19 16:02:55 +04:00
if ( manager - > ops - > dpms )
manager - > ops - > dpms ( manager , mode ) ;
2012-06-27 09:27:09 +04:00
exynos_crtc - > dpms = mode ;
2014-10-10 16:31:55 +04:00
if ( mode = = DRM_MODE_DPMS_ON )
drm_crtc_vblank_on ( crtc ) ;
2011-10-04 14:19:01 +04:00
}
static void exynos_drm_crtc_prepare ( struct drm_crtc * crtc )
{
/* drm framework doesn't check NULL. */
}
static void exynos_drm_crtc_commit ( struct drm_crtc * crtc )
{
2011-11-04 12:04:45 +04:00
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
2014-02-19 16:02:55 +04:00
struct exynos_drm_manager * manager = exynos_crtc - > manager ;
2014-11-14 03:17:46 +03:00
struct exynos_drm_plane * exynos_plane = to_exynos_plane ( crtc - > primary ) ;
2011-11-04 12:04:45 +04:00
2012-08-20 16:29:25 +04:00
exynos_drm_crtc_dpms ( crtc , DRM_MODE_DPMS_ON ) ;
2014-02-19 16:02:55 +04:00
2014-11-14 03:17:46 +03:00
if ( manager - > ops - > win_commit )
manager - > ops - > win_commit ( manager , exynos_plane - > zpos ) ;
2014-02-19 16:02:55 +04:00
if ( manager - > ops - > commit )
manager - > ops - > commit ( manager ) ;
2014-09-19 16:58:53 +04:00
exynos_plane_dpms ( crtc - > primary , DRM_MODE_DPMS_ON ) ;
2011-10-04 14:19:01 +04:00
}
static bool
exynos_drm_crtc_mode_fixup ( struct drm_crtc * crtc ,
2012-07-17 19:56:50 +04:00
const struct drm_display_mode * mode ,
2011-10-04 14:19:01 +04:00
struct drm_display_mode * adjusted_mode )
{
2014-01-31 01:19:19 +04:00
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
struct exynos_drm_manager * manager = exynos_crtc - > manager ;
if ( manager - > ops - > mode_fixup )
return manager - > ops - > mode_fixup ( manager , mode , adjusted_mode ) ;
2011-10-04 14:19:01 +04:00
return true ;
}
static int
exynos_drm_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 )
{
2012-06-27 09:27:01 +04:00
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
2014-01-31 01:19:18 +04:00
struct exynos_drm_manager * manager = exynos_crtc - > manager ;
2014-09-19 16:58:53 +04:00
struct drm_framebuffer * fb = crtc - > primary - > fb ;
2012-06-27 09:27:05 +04:00
unsigned int crtc_w ;
unsigned int crtc_h ;
2012-06-27 09:27:01 +04:00
2012-03-16 13:47:04 +04:00
/*
* copy the mode data adjusted by mode_fixup ( ) into crtc - > mode
* so that hardware can be seet to proper mode .
*/
memcpy ( & crtc - > mode , adjusted_mode , sizeof ( * adjusted_mode ) ) ;
2011-10-04 14:19:01 +04:00
2014-09-19 16:58:53 +04:00
crtc_w = fb - > width - x ;
crtc_h = fb - > height - y ;
2012-06-27 09:27:05 +04:00
2014-01-31 01:19:18 +04:00
if ( manager - > ops - > mode_set )
manager - > ops - > mode_set ( manager , & crtc - > mode ) ;
2014-09-19 16:58:53 +04:00
return exynos_plane_mode_set ( crtc - > primary , crtc , fb , 0 , 0 ,
crtc_w , crtc_h , x , y , crtc_w , crtc_h ) ;
2011-10-04 14:19:01 +04:00
}
2014-11-14 03:30:00 +03:00
static int exynos_drm_crtc_mode_set_base ( struct drm_crtc * crtc , int x , int y ,
2011-10-04 14:19:01 +04:00
struct drm_framebuffer * old_fb )
{
2012-06-27 09:27:05 +04:00
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
2014-09-19 16:58:53 +04:00
struct drm_framebuffer * fb = crtc - > primary - > fb ;
2012-06-27 09:27:05 +04:00
unsigned int crtc_w ;
unsigned int crtc_h ;
2011-10-04 14:19:01 +04:00
2012-09-14 08:29:47 +04:00
/* when framebuffer changing is requested, crtc's dpms should be on */
if ( exynos_crtc - > dpms > DRM_MODE_DPMS_ON ) {
DRM_ERROR ( " failed framebuffer changing request. \n " ) ;
return - EPERM ;
}
2014-09-19 16:58:53 +04:00
crtc_w = fb - > width - x ;
crtc_h = fb - > height - y ;
2012-06-27 09:27:05 +04:00
2014-11-25 16:21:17 +03:00
return exynos_update_plane ( crtc - > primary , crtc , fb , 0 , 0 ,
crtc_w , crtc_h , x , y , crtc_w , crtc_h ) ;
2011-10-04 14:19:01 +04:00
}
2012-06-27 09:27:10 +04:00
static void exynos_drm_crtc_disable ( struct drm_crtc * crtc )
{
2014-01-31 01:19:17 +04:00
struct drm_plane * plane ;
int ret ;
2012-06-27 09:27:10 +04:00
exynos_drm_crtc_dpms ( crtc , DRM_MODE_DPMS_OFF ) ;
2014-01-31 01:19:17 +04:00
2014-04-02 02:22:31 +04:00
drm_for_each_legacy_plane ( plane , & crtc - > dev - > mode_config . plane_list ) {
2014-01-31 01:19:17 +04:00
if ( plane - > crtc ! = crtc )
continue ;
ret = plane - > funcs - > disable_plane ( plane ) ;
if ( ret )
DRM_ERROR ( " Failed to disable plane %d \n " , ret ) ;
}
2012-06-27 09:27:10 +04:00
}
2011-10-04 14:19:01 +04:00
static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = {
. dpms = exynos_drm_crtc_dpms ,
. prepare = exynos_drm_crtc_prepare ,
. commit = exynos_drm_crtc_commit ,
. mode_fixup = exynos_drm_crtc_mode_fixup ,
. mode_set = exynos_drm_crtc_mode_set ,
. mode_set_base = exynos_drm_crtc_mode_set_base ,
2012-06-27 09:27:10 +04:00
. disable = exynos_drm_crtc_disable ,
2011-10-04 14:19:01 +04:00
} ;
static int exynos_drm_crtc_page_flip ( struct drm_crtc * crtc ,
2013-07-23 05:49:58 +04:00
struct drm_framebuffer * fb ,
struct drm_pending_vblank_event * event ,
uint32_t page_flip_flags )
2011-10-04 14:19:01 +04:00
{
struct drm_device * dev = crtc - > dev ;
struct exynos_drm_private * dev_priv = dev - > dev_private ;
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
2014-04-02 02:22:40 +04:00
struct drm_framebuffer * old_fb = crtc - > primary - > fb ;
2014-11-25 21:18:34 +03:00
unsigned int crtc_w , crtc_h ;
2011-10-04 14:19:01 +04:00
int ret = - EINVAL ;
2012-09-11 13:25:21 +04:00
/* when the page flip is requested, crtc's dpms should be on */
if ( exynos_crtc - > dpms > DRM_MODE_DPMS_ON ) {
DRM_ERROR ( " failed page flip request. \n " ) ;
return - EINVAL ;
}
2011-10-04 14:19:01 +04:00
mutex_lock ( & dev - > struct_mutex ) ;
2011-10-14 08:29:51 +04:00
if ( event ) {
/*
* the pipe from user always is 0 so we can set pipe number
* of current owner to event .
*/
event - > pipe = exynos_crtc - > pipe ;
2011-10-04 14:19:01 +04:00
ret = drm_vblank_get ( dev , exynos_crtc - > pipe ) ;
if ( ret ) {
DRM_DEBUG ( " failed to acquire vblank counter \n " ) ;
2011-10-14 08:29:51 +04:00
2011-10-04 14:19:01 +04:00
goto out ;
}
2012-11-02 15:30:47 +04:00
spin_lock_irq ( & dev - > event_lock ) ;
2012-02-15 06:25:20 +04:00
list_add_tail ( & event - > base . link ,
& dev_priv - > pageflip_event_list ) ;
2013-05-21 11:55:58 +04:00
atomic_set ( & exynos_crtc - > pending_flip , 1 ) ;
2012-11-02 15:30:47 +04:00
spin_unlock_irq ( & dev - > event_lock ) ;
2012-02-15 06:25:20 +04:00
2014-04-02 02:22:40 +04:00
crtc - > primary - > fb = fb ;
2014-11-25 21:18:34 +03:00
crtc_w = fb - > width - crtc - > x ;
crtc_h = fb - > height - crtc - > y ;
ret = exynos_update_plane ( crtc - > primary , crtc , fb , 0 , 0 ,
crtc_w , crtc_h , crtc - > x , crtc - > y ,
crtc_w , crtc_h ) ;
2011-10-04 14:19:01 +04:00
if ( ret ) {
2014-04-02 02:22:40 +04:00
crtc - > primary - > fb = old_fb ;
2012-11-02 15:30:47 +04:00
spin_lock_irq ( & dev - > event_lock ) ;
2011-10-04 14:19:01 +04:00
drm_vblank_put ( dev , exynos_crtc - > pipe ) ;
2011-10-14 08:29:51 +04:00
list_del ( & event - > base . link ) ;
2014-07-17 13:01:17 +04:00
atomic_set ( & exynos_crtc - > pending_flip , 0 ) ;
2012-11-02 15:30:47 +04:00
spin_unlock_irq ( & dev - > event_lock ) ;
2011-10-04 14:19:01 +04:00
goto out ;
}
}
out :
mutex_unlock ( & dev - > struct_mutex ) ;
return ret ;
}
static void exynos_drm_crtc_destroy ( struct drm_crtc * crtc )
{
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
struct exynos_drm_private * private = crtc - > dev - > dev_private ;
private - > crtc [ exynos_crtc - > pipe ] = NULL ;
drm_crtc_cleanup ( crtc ) ;
kfree ( exynos_crtc ) ;
}
2012-06-27 09:27:11 +04:00
static int exynos_drm_crtc_set_property ( struct drm_crtc * crtc ,
struct drm_property * property ,
uint64_t val )
{
struct drm_device * dev = crtc - > dev ;
struct exynos_drm_private * dev_priv = dev - > dev_private ;
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( crtc ) ;
if ( property = = dev_priv - > crtc_mode_property ) {
enum exynos_crtc_mode mode = val ;
if ( mode = = exynos_crtc - > mode )
return 0 ;
exynos_crtc - > mode = mode ;
switch ( mode ) {
case CRTC_MODE_NORMAL :
exynos_drm_crtc_commit ( crtc ) ;
break ;
case CRTC_MODE_BLANK :
2014-09-19 16:58:53 +04:00
exynos_plane_dpms ( crtc - > primary , DRM_MODE_DPMS_OFF ) ;
2012-06-27 09:27:11 +04:00
break ;
default :
break ;
}
return 0 ;
}
return - EINVAL ;
}
2011-10-04 14:19:01 +04:00
static struct drm_crtc_funcs exynos_crtc_funcs = {
. set_config = drm_crtc_helper_set_config ,
. page_flip = exynos_drm_crtc_page_flip ,
. destroy = exynos_drm_crtc_destroy ,
2012-06-27 09:27:11 +04:00
. set_property = exynos_drm_crtc_set_property ,
} ;
static const struct drm_prop_enum_list mode_names [ ] = {
{ CRTC_MODE_NORMAL , " normal " } ,
{ CRTC_MODE_BLANK , " blank " } ,
2011-10-04 14:19:01 +04:00
} ;
2012-06-27 09:27:11 +04:00
static void exynos_drm_crtc_attach_mode_property ( struct drm_crtc * crtc )
{
struct drm_device * dev = crtc - > dev ;
struct exynos_drm_private * dev_priv = dev - > dev_private ;
struct drm_property * prop ;
prop = dev_priv - > crtc_mode_property ;
if ( ! prop ) {
prop = drm_property_create_enum ( dev , 0 , " mode " , mode_names ,
ARRAY_SIZE ( mode_names ) ) ;
if ( ! prop )
return ;
dev_priv - > crtc_mode_property = prop ;
}
drm_object_attach_property ( & crtc - > base , prop , 0 ) ;
}
2014-11-06 00:51:35 +03:00
int exynos_drm_crtc_create ( struct exynos_drm_manager * manager , int pipe ,
enum exynos_drm_output_type type )
2011-10-04 14:19:01 +04:00
{
struct exynos_drm_crtc * exynos_crtc ;
2014-09-19 16:58:53 +04:00
struct drm_plane * plane ;
2014-02-19 16:02:55 +04:00
struct exynos_drm_private * private = manager - > drm_dev - > dev_private ;
2011-10-04 14:19:01 +04:00
struct drm_crtc * crtc ;
2014-09-19 16:58:53 +04:00
int ret ;
2011-10-04 14:19:01 +04:00
exynos_crtc = kzalloc ( sizeof ( * exynos_crtc ) , GFP_KERNEL ) ;
2013-08-19 14:04:55 +04:00
if ( ! exynos_crtc )
2011-10-04 14:19:01 +04:00
return - ENOMEM ;
2013-05-21 11:55:58 +04:00
init_waitqueue_head ( & exynos_crtc - > pending_flip_queue ) ;
atomic_set ( & exynos_crtc - > pending_flip , 0 ) ;
2014-02-19 16:02:55 +04:00
exynos_crtc - > dpms = DRM_MODE_DPMS_OFF ;
exynos_crtc - > manager = manager ;
2014-11-04 23:25:27 +03:00
exynos_crtc - > pipe = pipe ;
2014-11-06 00:51:35 +03:00
exynos_crtc - > type = type ;
2014-11-04 23:25:27 +03:00
plane = exynos_plane_init ( manager - > drm_dev , 1 < < pipe ,
2014-09-19 16:58:53 +04:00
DRM_PLANE_TYPE_PRIMARY ) ;
if ( IS_ERR ( plane ) ) {
ret = PTR_ERR ( plane ) ;
goto err_plane ;
2012-06-27 09:27:04 +04:00
}
2014-11-03 23:20:29 +03:00
manager - > crtc = & exynos_crtc - > base ;
crtc = & exynos_crtc - > base ;
2011-10-04 14:19:01 +04:00
2014-11-04 23:25:27 +03:00
private - > crtc [ pipe ] = crtc ;
2011-10-04 14:19:01 +04:00
2014-09-19 16:58:53 +04:00
ret = drm_crtc_init_with_planes ( manager - > drm_dev , crtc , plane , NULL ,
& exynos_crtc_funcs ) ;
if ( ret < 0 )
goto err_crtc ;
2011-10-04 14:19:01 +04:00
drm_crtc_helper_add ( crtc , & exynos_crtc_helper_funcs ) ;
2012-06-27 09:27:11 +04:00
exynos_drm_crtc_attach_mode_property ( crtc ) ;
2011-10-04 14:19:01 +04:00
return 0 ;
2014-09-19 16:58:53 +04:00
err_crtc :
plane - > funcs - > destroy ( plane ) ;
err_plane :
kfree ( exynos_crtc ) ;
return ret ;
2011-10-04 14:19:01 +04:00
}
2014-02-19 16:02:55 +04:00
int exynos_drm_crtc_enable_vblank ( struct drm_device * dev , int pipe )
2011-10-04 14:19:01 +04:00
{
struct exynos_drm_private * private = dev - > dev_private ;
2011-12-06 06:06:54 +04:00
struct exynos_drm_crtc * exynos_crtc =
2014-02-19 16:02:55 +04:00
to_exynos_crtc ( private - > crtc [ pipe ] ) ;
struct exynos_drm_manager * manager = exynos_crtc - > manager ;
2011-10-04 14:19:01 +04:00
2011-12-06 06:06:54 +04:00
if ( exynos_crtc - > dpms ! = DRM_MODE_DPMS_ON )
return - EPERM ;
2014-02-19 16:02:55 +04:00
if ( manager - > ops - > enable_vblank )
manager - > ops - > enable_vblank ( manager ) ;
2011-10-04 14:19:01 +04:00
return 0 ;
}
2014-02-19 16:02:55 +04:00
void exynos_drm_crtc_disable_vblank ( struct drm_device * dev , int pipe )
2011-10-04 14:19:01 +04:00
{
struct exynos_drm_private * private = dev - > dev_private ;
2011-12-06 06:06:54 +04:00
struct exynos_drm_crtc * exynos_crtc =
2014-02-19 16:02:55 +04:00
to_exynos_crtc ( private - > crtc [ pipe ] ) ;
struct exynos_drm_manager * manager = exynos_crtc - > manager ;
2011-10-04 14:19:01 +04:00
2011-12-06 06:06:54 +04:00
if ( exynos_crtc - > dpms ! = DRM_MODE_DPMS_ON )
return ;
2014-02-19 16:02:55 +04:00
if ( manager - > ops - > disable_vblank )
manager - > ops - > disable_vblank ( manager ) ;
2011-10-04 14:19:01 +04:00
}
2013-01-03 14:44:04 +04:00
2014-02-19 16:02:55 +04:00
void exynos_drm_crtc_finish_pageflip ( struct drm_device * dev , int pipe )
2013-01-03 14:44:04 +04:00
{
struct exynos_drm_private * dev_priv = dev - > dev_private ;
struct drm_pending_vblank_event * e , * t ;
2014-02-19 16:02:55 +04:00
struct drm_crtc * drm_crtc = dev_priv - > crtc [ pipe ] ;
2013-05-21 11:55:58 +04:00
struct exynos_drm_crtc * exynos_crtc = to_exynos_crtc ( drm_crtc ) ;
2013-01-03 14:44:04 +04:00
unsigned long flags ;
spin_lock_irqsave ( & dev - > event_lock , flags ) ;
list_for_each_entry_safe ( e , t , & dev_priv - > pageflip_event_list ,
base . link ) {
/* if event's pipe isn't same as crtc then ignore it. */
2014-02-19 16:02:55 +04:00
if ( pipe ! = e - > pipe )
2013-01-03 14:44:04 +04:00
continue ;
2013-05-22 06:48:40 +04:00
list_del ( & e - > base . link ) ;
drm_send_vblank_event ( dev , - 1 , e ) ;
2014-02-19 16:02:55 +04:00
drm_vblank_put ( dev , pipe ) ;
2013-05-21 11:55:58 +04:00
atomic_set ( & exynos_crtc - > pending_flip , 0 ) ;
wake_up ( & exynos_crtc - > pending_flip_queue ) ;
2013-01-03 14:44:04 +04:00
}
spin_unlock_irqrestore ( & dev - > event_lock , flags ) ;
}
2014-02-19 16:02:55 +04:00
void exynos_drm_crtc_complete_scanout ( struct drm_framebuffer * fb )
{
struct exynos_drm_manager * manager ;
struct drm_device * dev = fb - > dev ;
struct drm_crtc * crtc ;
/*
* make sure that overlay data are updated to real hardware
* for all encoders .
*/
list_for_each_entry ( crtc , & dev - > mode_config . crtc_list , head ) {
manager = to_exynos_crtc ( crtc ) - > manager ;
/*
* wait for vblank interrupt
* - this makes sure that overlay data are updated to
* real hardware .
*/
if ( manager - > ops - > wait_for_vblank )
manager - > ops - > wait_for_vblank ( manager ) ;
}
}
2014-05-09 09:25:20 +04:00
int exynos_drm_crtc_get_pipe_from_type ( struct drm_device * drm_dev ,
unsigned int out_type )
{
struct drm_crtc * crtc ;
list_for_each_entry ( crtc , & drm_dev - > mode_config . crtc_list , head ) {
struct exynos_drm_crtc * exynos_crtc ;
exynos_crtc = to_exynos_crtc ( crtc ) ;
2014-11-06 00:51:35 +03:00
if ( exynos_crtc - > type = = out_type )
2014-11-04 23:44:47 +03:00
return exynos_crtc - > pipe ;
2014-05-09 09:25:20 +04:00
}
return - EPERM ;
}
2014-07-17 13:01:19 +04:00
void exynos_drm_crtc_te_handler ( struct drm_crtc * crtc )
{
struct exynos_drm_manager * manager = to_exynos_crtc ( crtc ) - > manager ;
if ( manager - > ops - > te_handler )
manager - > ops - > te_handler ( manager ) ;
}