2011-10-04 19:19:01 +09:00
/* exynos_drm_encoder.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-18 02:30:17 +09: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 19:19:01 +09:00
*/
2012-10-02 18:01:07 +01:00
# include <drm/drmP.h>
# include <drm/drm_crtc_helper.h>
2011-10-04 19:19:01 +09:00
# include "exynos_drm_drv.h"
# include "exynos_drm_encoder.h"
2012-08-20 21:29:25 +09:00
# include "exynos_drm_connector.h"
2011-10-04 19:19:01 +09:00
# define to_exynos_encoder(x) container_of(x, struct exynos_drm_encoder,\
drm_encoder )
/*
* exynos specific encoder structure .
*
* @ drm_encoder : encoder object .
* @ manager : specific encoder has its own manager to control a hardware
* appropriately and we can access a hardware drawing on this manager .
*/
struct exynos_drm_encoder {
2012-08-17 17:58:38 +09:00
struct drm_crtc * old_crtc ;
2011-10-04 19:19:01 +09:00
struct drm_encoder drm_encoder ;
struct exynos_drm_manager * manager ;
} ;
2011-12-06 11:06:54 +09:00
static void exynos_drm_encoder_dpms ( struct drm_encoder * encoder , int mode )
{
2014-01-30 16:19:08 -05:00
struct exynos_drm_manager * manager = exynos_drm_get_manager ( encoder ) ;
struct exynos_drm_display_ops * display_ops = manager - > display_ops ;
2011-10-04 19:19:01 +09:00
2013-06-12 10:40:52 +09:00
DRM_DEBUG_KMS ( " encoder dpms: %d \n " , mode ) ;
2011-10-04 19:19:01 +09:00
2014-01-30 16:19:09 -05:00
if ( display_ops & & display_ops - > dpms )
display_ops - > dpms ( manager - > ctx , mode ) ;
2011-10-04 19:19:01 +09:00
}
static bool
exynos_drm_encoder_mode_fixup ( struct drm_encoder * encoder ,
2012-07-17 17:56:50 +02:00
const struct drm_display_mode * mode ,
2011-10-04 19:19:01 +09:00
struct drm_display_mode * adjusted_mode )
{
2012-03-16 18:47:04 +09:00
struct drm_device * dev = encoder - > dev ;
struct drm_connector * connector ;
struct exynos_drm_manager * manager = exynos_drm_get_manager ( encoder ) ;
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
list_for_each_entry ( connector , & dev - > mode_config . connector_list , head ) {
if ( connector - > encoder = = encoder )
if ( manager_ops & & manager_ops - > mode_fixup )
2014-01-30 16:19:06 -05:00
manager_ops - > mode_fixup ( manager , connector ,
2012-03-16 18:47:04 +09:00
mode , adjusted_mode ) ;
}
2011-10-04 19:19:01 +09:00
return true ;
}
2012-08-17 17:58:38 +09:00
static void disable_plane_to_crtc ( struct drm_device * dev ,
struct drm_crtc * old_crtc ,
struct drm_crtc * new_crtc )
{
struct drm_plane * plane ;
/*
* if old_crtc isn ' t same as encoder - > crtc then it means that
* user changed crtc id to another one so the plane to old_crtc
* should be disabled and plane - > crtc should be set to new_crtc
* ( encoder - > crtc )
*/
list_for_each_entry ( plane , & dev - > mode_config . plane_list , head ) {
if ( plane - > crtc = = old_crtc ) {
/*
* do not change below call order .
*
* plane - > funcs - > disable_plane call checks
* if encoder - > crtc is same as plane - > crtc and if same
2014-01-30 16:19:02 -05:00
* then manager_ops - > win_disable callback will be called
2012-08-17 17:58:38 +09:00
* to diasble current hw overlay so plane - > crtc should
* have new_crtc because new_crtc was set to
* encoder - > crtc in advance .
*/
plane - > crtc = new_crtc ;
plane - > funcs - > disable_plane ( plane ) ;
}
}
}
2011-10-04 19:19:01 +09:00
static void exynos_drm_encoder_mode_set ( struct drm_encoder * encoder ,
struct drm_display_mode * mode ,
struct drm_display_mode * adjusted_mode )
{
struct drm_device * dev = encoder - > dev ;
struct drm_connector * connector ;
2012-08-17 17:58:38 +09:00
struct exynos_drm_manager * manager ;
struct exynos_drm_manager_ops * manager_ops ;
2011-10-04 19:19:01 +09:00
list_for_each_entry ( connector , & dev - > mode_config . connector_list , head ) {
2012-08-17 17:58:38 +09:00
if ( connector - > encoder = = encoder ) {
struct exynos_drm_encoder * exynos_encoder ;
exynos_encoder = to_exynos_encoder ( encoder ) ;
if ( exynos_encoder - > old_crtc ! = encoder - > crtc & &
exynos_encoder - > old_crtc ) {
/*
* disable a plane to old crtc and change
* crtc of the plane to new one .
*/
disable_plane_to_crtc ( dev ,
exynos_encoder - > old_crtc ,
encoder - > crtc ) ;
}
manager = exynos_drm_get_manager ( encoder ) ;
manager_ops = manager - > ops ;
2011-10-04 19:19:01 +09:00
if ( manager_ops & & manager_ops - > mode_set )
2014-01-30 16:19:06 -05:00
manager_ops - > mode_set ( manager , adjusted_mode ) ;
2012-08-17 17:58:38 +09:00
exynos_encoder - > old_crtc = encoder - > crtc ;
}
2011-10-04 19:19:01 +09:00
}
}
static void exynos_drm_encoder_prepare ( struct drm_encoder * encoder )
{
/* drm framework doesn't check NULL. */
}
static void exynos_drm_encoder_commit ( struct drm_encoder * encoder )
{
2012-10-18 18:59:55 +09:00
struct exynos_drm_encoder * exynos_encoder = to_exynos_encoder ( encoder ) ;
struct exynos_drm_manager * manager = exynos_encoder - > manager ;
2011-10-04 19:19:01 +09:00
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
if ( manager_ops & & manager_ops - > commit )
2014-01-30 16:19:06 -05:00
manager_ops - > commit ( manager ) ;
2011-10-04 19:19:01 +09:00
}
2012-11-22 17:41:23 +09:00
void exynos_drm_encoder_complete_scanout ( struct drm_framebuffer * fb )
{
struct exynos_drm_encoder * exynos_encoder ;
2012-12-06 20:16:00 +05:30
struct exynos_drm_manager_ops * ops ;
2012-11-22 17:41:23 +09:00
struct drm_device * dev = fb - > dev ;
struct drm_encoder * encoder ;
/*
* make sure that overlay data are updated to real hardware
* for all encoders .
*/
list_for_each_entry ( encoder , & dev - > mode_config . encoder_list , head ) {
exynos_encoder = to_exynos_encoder ( encoder ) ;
2012-12-06 20:16:00 +05:30
ops = exynos_encoder - > manager - > ops ;
2012-11-22 17:41:23 +09:00
/*
* wait for vblank interrupt
* - this makes sure that overlay data are updated to
* real hardware .
*/
2012-12-06 20:16:00 +05:30
if ( ops - > wait_for_vblank )
2014-01-30 16:19:06 -05:00
ops - > wait_for_vblank ( exynos_encoder - > manager ) ;
2012-11-22 17:41:23 +09:00
}
}
2012-08-24 10:54:12 -07:00
static void exynos_drm_encoder_disable ( struct drm_encoder * encoder )
{
struct drm_plane * plane ;
struct drm_device * dev = encoder - > dev ;
exynos_drm_encoder_dpms ( encoder , DRM_MODE_DPMS_OFF ) ;
/* all planes connected to this encoder should be also disabled. */
list_for_each_entry ( plane , & dev - > mode_config . plane_list , head ) {
if ( plane - > crtc = = encoder - > crtc )
plane - > funcs - > disable_plane ( plane ) ;
}
}
2011-10-04 19:19:01 +09:00
static struct drm_encoder_helper_funcs exynos_encoder_helper_funcs = {
. dpms = exynos_drm_encoder_dpms ,
. mode_fixup = exynos_drm_encoder_mode_fixup ,
. mode_set = exynos_drm_encoder_mode_set ,
. prepare = exynos_drm_encoder_prepare ,
. commit = exynos_drm_encoder_commit ,
2012-08-24 10:54:12 -07:00
. disable = exynos_drm_encoder_disable ,
2011-10-04 19:19:01 +09:00
} ;
static void exynos_drm_encoder_destroy ( struct drm_encoder * encoder )
{
struct exynos_drm_encoder * exynos_encoder =
to_exynos_encoder ( encoder ) ;
exynos_encoder - > manager - > pipe = - 1 ;
drm_encoder_cleanup ( encoder ) ;
kfree ( exynos_encoder ) ;
}
static struct drm_encoder_funcs exynos_encoder_funcs = {
. destroy = exynos_drm_encoder_destroy ,
} ;
2012-02-15 11:25:19 +09:00
static unsigned int exynos_drm_encoder_clones ( struct drm_encoder * encoder )
{
struct drm_encoder * clone ;
struct drm_device * dev = encoder - > dev ;
struct exynos_drm_encoder * exynos_encoder = to_exynos_encoder ( encoder ) ;
struct exynos_drm_display_ops * display_ops =
exynos_encoder - > manager - > display_ops ;
unsigned int clone_mask = 0 ;
int cnt = 0 ;
list_for_each_entry ( clone , & dev - > mode_config . encoder_list , head ) {
switch ( display_ops - > type ) {
case EXYNOS_DISPLAY_TYPE_LCD :
case EXYNOS_DISPLAY_TYPE_HDMI :
2012-03-21 10:55:26 +09:00
case EXYNOS_DISPLAY_TYPE_VIDI :
2012-02-15 11:25:19 +09:00
clone_mask | = ( 1 < < ( cnt + + ) ) ;
break ;
default :
continue ;
}
}
return clone_mask ;
}
void exynos_drm_encoder_setup ( struct drm_device * dev )
{
struct drm_encoder * encoder ;
list_for_each_entry ( encoder , & dev - > mode_config . encoder_list , head )
encoder - > possible_clones = exynos_drm_encoder_clones ( encoder ) ;
}
2011-10-04 19:19:01 +09:00
struct drm_encoder *
exynos_drm_encoder_create ( struct drm_device * dev ,
struct exynos_drm_manager * manager ,
unsigned int possible_crtcs )
{
struct drm_encoder * encoder ;
struct exynos_drm_encoder * exynos_encoder ;
2014-01-30 16:19:03 -05:00
int ret ;
2011-10-04 19:19:01 +09:00
if ( ! manager | | ! possible_crtcs )
return NULL ;
if ( ! manager - > dev )
return NULL ;
exynos_encoder = kzalloc ( sizeof ( * exynos_encoder ) , GFP_KERNEL ) ;
2013-08-19 19:04:55 +09:00
if ( ! exynos_encoder )
2011-10-04 19:19:01 +09:00
return NULL ;
exynos_encoder - > manager = manager ;
encoder = & exynos_encoder - > drm_encoder ;
encoder - > possible_crtcs = possible_crtcs ;
DRM_DEBUG_KMS ( " possible_crtcs = 0x%x \n " , encoder - > possible_crtcs ) ;
drm_encoder_init ( dev , encoder , & exynos_encoder_funcs ,
DRM_MODE_ENCODER_TMDS ) ;
drm_encoder_helper_add ( encoder , & exynos_encoder_helper_funcs ) ;
2014-01-30 16:19:03 -05:00
if ( manager - > ops & & manager - > ops - > initialize ) {
2014-01-30 16:19:06 -05:00
ret = manager - > ops - > initialize ( manager , dev ) ;
2014-01-30 16:19:03 -05:00
if ( ret ) {
DRM_ERROR ( " Manager initialize failed %d \n " , ret ) ;
goto error ;
}
}
if ( manager - > display_ops & & manager - > display_ops - > initialize ) {
ret = manager - > display_ops - > initialize ( manager - > dev , dev ) ;
if ( ret ) {
DRM_ERROR ( " Display initialize failed %d \n " , ret ) ;
goto error ;
}
}
2011-10-04 19:19:01 +09:00
DRM_DEBUG_KMS ( " encoder has been created \n " ) ;
return encoder ;
2014-01-30 16:19:03 -05:00
error :
exynos_drm_encoder_destroy ( & exynos_encoder - > drm_encoder ) ;
return NULL ;
2011-10-04 19:19:01 +09:00
}
struct exynos_drm_manager * exynos_drm_get_manager ( struct drm_encoder * encoder )
{
return to_exynos_encoder ( encoder ) - > manager ;
}
void exynos_drm_fn_encoder ( struct drm_crtc * crtc , void * data ,
void ( * fn ) ( struct drm_encoder * , void * ) )
{
struct drm_device * dev = crtc - > dev ;
struct drm_encoder * encoder ;
2011-11-04 17:04:45 +09:00
struct exynos_drm_private * private = dev - > dev_private ;
struct exynos_drm_manager * manager ;
2011-10-04 19:19:01 +09:00
list_for_each_entry ( encoder , & dev - > mode_config . encoder_list , head ) {
2011-11-04 17:04:45 +09:00
/*
* if crtc is detached from encoder , check pipe ,
* otherwise check crtc attached to encoder
*/
if ( ! encoder - > crtc ) {
manager = to_exynos_encoder ( encoder ) - > manager ;
if ( manager - > pipe < 0 | |
private - > crtc [ manager - > pipe ] ! = crtc )
continue ;
} else {
if ( encoder - > crtc ! = crtc )
continue ;
}
2011-10-04 19:19:01 +09:00
fn ( encoder , data ) ;
}
}
void exynos_drm_enable_vblank ( struct drm_encoder * encoder , void * data )
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
int crtc = * ( int * ) data ;
2012-06-27 14:27:02 +09:00
if ( manager - > pipe ! = crtc )
return ;
2011-10-04 19:19:01 +09:00
if ( manager_ops - > enable_vblank )
2014-01-30 16:19:06 -05:00
manager_ops - > enable_vblank ( manager ) ;
2011-10-04 19:19:01 +09:00
}
void exynos_drm_disable_vblank ( struct drm_encoder * encoder , void * data )
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
int crtc = * ( int * ) data ;
2012-06-27 14:27:02 +09:00
if ( manager - > pipe ! = crtc )
return ;
2011-10-04 19:19:01 +09:00
if ( manager_ops - > disable_vblank )
2014-01-30 16:19:06 -05:00
manager_ops - > disable_vblank ( manager ) ;
2011-10-04 19:19:01 +09:00
}
2011-12-06 11:06:54 +09:00
void exynos_drm_encoder_crtc_dpms ( struct drm_encoder * encoder , void * data )
{
struct exynos_drm_encoder * exynos_encoder = to_exynos_encoder ( encoder ) ;
struct exynos_drm_manager * manager = exynos_encoder - > manager ;
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
int mode = * ( int * ) data ;
if ( manager_ops & & manager_ops - > dpms )
2014-01-30 16:19:06 -05:00
manager_ops - > dpms ( manager , mode ) ;
2011-12-06 11:06:54 +09:00
/*
* if this condition is ok then it means that the crtc is already
* detached from encoder and last function for detaching is properly
* done , so clear pipe from manager to prevent repeated call .
*/
if ( mode > DRM_MODE_DPMS_ON ) {
if ( ! encoder - > crtc )
manager - > pipe = - 1 ;
}
}
2012-06-27 14:27:05 +09:00
void exynos_drm_encoder_crtc_pipe ( struct drm_encoder * encoder , void * data )
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
int pipe = * ( int * ) data ;
/*
* when crtc is detached from encoder , this pipe is used
* to select manager operation
*/
manager - > pipe = pipe ;
}
void exynos_drm_encoder_plane_mode_set ( struct drm_encoder * encoder , void * data )
2011-10-04 19:19:01 +09:00
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
2014-01-30 16:19:02 -05:00
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
2011-10-04 19:19:01 +09:00
struct exynos_drm_overlay * overlay = data ;
2014-01-30 16:19:02 -05:00
if ( manager_ops & & manager_ops - > win_mode_set )
2014-01-30 16:19:06 -05:00
manager_ops - > win_mode_set ( manager , overlay ) ;
2011-10-04 19:19:01 +09:00
}
2012-06-27 14:27:05 +09:00
void exynos_drm_encoder_plane_commit ( struct drm_encoder * encoder , void * data )
2011-11-04 17:04:45 +09:00
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
2014-01-30 16:19:02 -05:00
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
2011-12-08 17:54:07 +09:00
int zpos = DEFAULT_ZPOS ;
2011-11-04 17:04:45 +09:00
2011-12-08 17:54:07 +09:00
if ( data )
zpos = * ( int * ) data ;
2014-01-30 16:19:02 -05:00
if ( manager_ops & & manager_ops - > win_commit )
2014-01-30 16:19:06 -05:00
manager_ops - > win_commit ( manager , zpos ) ;
2011-11-04 17:04:45 +09:00
}
2012-06-27 14:27:02 +09:00
2012-06-27 14:27:09 +09:00
void exynos_drm_encoder_plane_enable ( struct drm_encoder * encoder , void * data )
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
2014-01-30 16:19:02 -05:00
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
2012-06-27 14:27:09 +09:00
int zpos = DEFAULT_ZPOS ;
if ( data )
zpos = * ( int * ) data ;
2014-01-30 16:19:02 -05:00
if ( manager_ops & & manager_ops - > win_enable )
2014-01-30 16:19:06 -05:00
manager_ops - > win_enable ( manager , zpos ) ;
2012-06-27 14:27:09 +09:00
}
2012-06-27 14:27:05 +09:00
void exynos_drm_encoder_plane_disable ( struct drm_encoder * encoder , void * data )
2012-06-27 14:27:02 +09:00
{
struct exynos_drm_manager * manager =
to_exynos_encoder ( encoder ) - > manager ;
2014-01-30 16:19:02 -05:00
struct exynos_drm_manager_ops * manager_ops = manager - > ops ;
2012-06-27 14:27:05 +09:00
int zpos = DEFAULT_ZPOS ;
2012-06-27 14:27:02 +09:00
2012-06-27 14:27:05 +09:00
if ( data )
zpos = * ( int * ) data ;
2014-01-30 16:19:02 -05:00
if ( manager_ops & & manager_ops - > win_disable )
2014-01-30 16:19:06 -05:00
manager_ops - > win_disable ( manager , zpos ) ;
2012-06-27 14:27:02 +09:00
}