2011-10-04 19:19:01 +09:00
/*
* 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 >
*
* 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
* VA LINUX SYSTEMS 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 .
*/
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
2012-02-14 15:59:46 +09:00
# include <drm/exynos_drm.h>
2011-10-04 19:19:01 +09:00
# include "exynos_drm_drv.h"
# include "exynos_drm_encoder.h"
# define MAX_EDID 256
# define to_exynos_connector(x) container_of(x, struct exynos_drm_connector,\
drm_connector )
struct exynos_drm_connector {
struct drm_connector drm_connector ;
2011-10-19 17:16:55 +09:00
uint32_t encoder_id ;
struct exynos_drm_manager * manager ;
2012-08-20 21:29:25 +09:00
uint32_t dpms ;
2011-10-04 19:19:01 +09:00
} ;
/* convert exynos_video_timings to drm_display_mode */
static inline void
convert_to_display_mode ( struct drm_display_mode * mode ,
2012-02-14 15:59:46 +09:00
struct exynos_drm_panel_info * panel )
2011-10-04 19:19:01 +09:00
{
2012-02-14 15:59:46 +09:00
struct fb_videomode * timing = & panel - > timing ;
2011-10-04 19:19:01 +09:00
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
mode - > clock = timing - > pixclock / 1000 ;
2011-10-19 15:11:55 +09:00
mode - > vrefresh = timing - > refresh ;
2011-10-04 19:19:01 +09:00
mode - > hdisplay = timing - > xres ;
2012-03-09 09:45:21 +09:00
mode - > hsync_start = mode - > hdisplay + timing - > right_margin ;
2011-10-04 19:19:01 +09:00
mode - > hsync_end = mode - > hsync_start + timing - > hsync_len ;
2012-03-09 09:45:21 +09:00
mode - > htotal = mode - > hsync_end + timing - > left_margin ;
2011-10-04 19:19:01 +09:00
mode - > vdisplay = timing - > yres ;
2012-03-09 09:45:21 +09:00
mode - > vsync_start = mode - > vdisplay + timing - > lower_margin ;
2011-10-04 19:19:01 +09:00
mode - > vsync_end = mode - > vsync_start + timing - > vsync_len ;
2012-03-09 09:45:21 +09:00
mode - > vtotal = mode - > vsync_end + timing - > upper_margin ;
2012-02-14 15:59:46 +09:00
mode - > width_mm = panel - > width_mm ;
mode - > height_mm = panel - > height_mm ;
2011-10-19 15:11:55 +09:00
if ( timing - > vmode & FB_VMODE_INTERLACED )
mode - > flags | = DRM_MODE_FLAG_INTERLACE ;
if ( timing - > vmode & FB_VMODE_DOUBLE )
mode - > flags | = DRM_MODE_FLAG_DBLSCAN ;
2011-10-04 19:19:01 +09:00
}
/* convert drm_display_mode to exynos_video_timings */
static inline void
convert_to_video_timing ( struct fb_videomode * timing ,
struct drm_display_mode * mode )
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
memset ( timing , 0 , sizeof ( * timing ) ) ;
timing - > pixclock = mode - > clock * 1000 ;
2011-10-19 15:11:55 +09:00
timing - > refresh = drm_mode_vrefresh ( mode ) ;
2011-10-04 19:19:01 +09:00
timing - > xres = mode - > hdisplay ;
2012-03-09 09:45:21 +09:00
timing - > right_margin = mode - > hsync_start - mode - > hdisplay ;
2011-10-04 19:19:01 +09:00
timing - > hsync_len = mode - > hsync_end - mode - > hsync_start ;
2012-03-09 09:45:21 +09:00
timing - > left_margin = mode - > htotal - mode - > hsync_end ;
2011-10-04 19:19:01 +09:00
timing - > yres = mode - > vdisplay ;
2012-03-09 09:45:21 +09:00
timing - > lower_margin = mode - > vsync_start - mode - > vdisplay ;
2011-10-04 19:19:01 +09:00
timing - > vsync_len = mode - > vsync_end - mode - > vsync_start ;
2012-03-09 09:45:21 +09:00
timing - > upper_margin = mode - > vtotal - mode - > vsync_end ;
2011-10-04 19:19:01 +09:00
if ( mode - > flags & DRM_MODE_FLAG_INTERLACE )
timing - > vmode = FB_VMODE_INTERLACED ;
else
timing - > vmode = FB_VMODE_NONINTERLACED ;
if ( mode - > flags & DRM_MODE_FLAG_DBLSCAN )
timing - > vmode | = FB_VMODE_DOUBLE ;
}
static int exynos_drm_connector_get_modes ( struct drm_connector * connector )
{
2011-10-19 17:16:55 +09:00
struct exynos_drm_connector * exynos_connector =
to_exynos_connector ( connector ) ;
struct exynos_drm_manager * manager = exynos_connector - > manager ;
2011-10-19 17:23:07 +09:00
struct exynos_drm_display_ops * display_ops = manager - > display_ops ;
2011-10-04 19:19:01 +09:00
unsigned int count ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-10-19 17:23:07 +09:00
if ( ! display_ops ) {
DRM_DEBUG_KMS ( " display_ops is null. \n " ) ;
2011-10-04 19:19:01 +09:00
return 0 ;
}
/*
* if get_edid ( ) exists then get_edid ( ) callback of hdmi side
* is called to get edid data through i2c interface else
* get timing from the FIMD driver ( display controller ) .
*
* P . S . in case of lcd panel , count is always 1 if success
* because lcd panel has only one mode .
*/
2011-10-19 17:23:07 +09:00
if ( display_ops - > get_edid ) {
2011-10-04 19:19:01 +09:00
int ret ;
void * edid ;
edid = kzalloc ( MAX_EDID , GFP_KERNEL ) ;
if ( ! edid ) {
DRM_ERROR ( " failed to allocate edid \n " ) ;
return 0 ;
}
2011-10-19 17:23:07 +09:00
ret = display_ops - > get_edid ( manager - > dev , connector ,
2011-10-04 19:19:01 +09:00
edid , MAX_EDID ) ;
if ( ret < 0 ) {
DRM_ERROR ( " failed to get edid data. \n " ) ;
kfree ( edid ) ;
edid = NULL ;
return 0 ;
}
drm_mode_connector_update_edid_property ( connector , edid ) ;
count = drm_add_edid_modes ( connector , edid ) ;
2012-08-15 09:32:39 +00:00
kfree ( edid ) ;
2011-10-04 19:19:01 +09:00
} else {
2012-02-14 15:59:46 +09:00
struct exynos_drm_panel_info * panel ;
2012-09-18 15:51:30 +05:30
struct drm_display_mode * mode = drm_mode_create ( connector - > dev ) ;
if ( ! mode ) {
DRM_ERROR ( " failed to create a new display mode. \n " ) ;
return 0 ;
}
2011-10-04 19:19:01 +09:00
2012-02-14 15:59:46 +09:00
if ( display_ops - > get_panel )
panel = display_ops - > get_panel ( manager - > dev ) ;
2011-10-04 19:19:01 +09:00
else {
drm_mode_destroy ( connector - > dev , mode ) ;
return 0 ;
}
2012-02-14 15:59:46 +09:00
convert_to_display_mode ( mode , panel ) ;
connector - > display_info . width_mm = mode - > width_mm ;
connector - > display_info . height_mm = mode - > height_mm ;
2011-10-04 19:19:01 +09:00
mode - > type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED ;
drm_mode_set_name ( mode ) ;
drm_mode_probed_add ( connector , mode ) ;
count = 1 ;
}
return count ;
}
static int exynos_drm_connector_mode_valid ( struct drm_connector * connector ,
struct drm_display_mode * mode )
{
2011-10-19 17:16:55 +09:00
struct exynos_drm_connector * exynos_connector =
to_exynos_connector ( connector ) ;
struct exynos_drm_manager * manager = exynos_connector - > manager ;
2011-10-19 17:23:07 +09:00
struct exynos_drm_display_ops * display_ops = manager - > display_ops ;
2011-10-04 19:19:01 +09:00
struct fb_videomode timing ;
int ret = MODE_BAD ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
convert_to_video_timing ( & timing , mode ) ;
2011-10-19 17:23:07 +09:00
if ( display_ops & & display_ops - > check_timing )
if ( ! display_ops - > check_timing ( manager - > dev , ( void * ) & timing ) )
2011-10-04 19:19:01 +09:00
ret = MODE_OK ;
return ret ;
}
2012-09-24 20:04:24 +09:00
struct drm_encoder * exynos_drm_best_encoder ( struct drm_connector * connector )
2011-10-04 19:19:01 +09:00
{
2011-10-19 17:16:55 +09:00
struct drm_device * dev = connector - > dev ;
struct exynos_drm_connector * exynos_connector =
to_exynos_connector ( connector ) ;
struct drm_mode_object * obj ;
struct drm_encoder * encoder ;
2011-10-04 19:19:01 +09:00
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-10-19 17:16:55 +09:00
obj = drm_mode_object_find ( dev , exynos_connector - > encoder_id ,
DRM_MODE_OBJECT_ENCODER ) ;
if ( ! obj ) {
DRM_DEBUG_KMS ( " Unknown ENCODER ID %d \n " ,
exynos_connector - > encoder_id ) ;
return NULL ;
}
encoder = obj_to_encoder ( obj ) ;
return encoder ;
2011-10-04 19:19:01 +09:00
}
static struct drm_connector_helper_funcs exynos_connector_helper_funcs = {
. get_modes = exynos_drm_connector_get_modes ,
. mode_valid = exynos_drm_connector_mode_valid ,
. best_encoder = exynos_drm_best_encoder ,
} ;
2012-08-20 21:29:25 +09:00
void exynos_drm_display_power ( struct drm_connector * connector , int mode )
{
2012-09-24 20:04:24 +09:00
struct drm_encoder * encoder = exynos_drm_best_encoder ( connector ) ;
2012-08-20 21:29:25 +09:00
struct exynos_drm_connector * exynos_connector ;
struct exynos_drm_manager * manager = exynos_drm_get_manager ( encoder ) ;
struct exynos_drm_display_ops * display_ops = manager - > display_ops ;
exynos_connector = to_exynos_connector ( connector ) ;
if ( exynos_connector - > dpms = = mode ) {
DRM_DEBUG_KMS ( " desired dpms mode is same as previous one. \n " ) ;
return ;
}
if ( display_ops & & display_ops - > power_on )
display_ops - > power_on ( manager - > dev , mode ) ;
exynos_connector - > dpms = mode ;
}
static void exynos_drm_connector_dpms ( struct drm_connector * connector ,
int mode )
{
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
/*
* in case that drm_crtc_helper_set_mode ( ) is called ,
* encoder / crtc - > funcs - > dpms ( ) will be just returned
* because they already were DRM_MODE_DPMS_ON so only
* exynos_drm_display_power ( ) will be called .
*/
drm_helper_connector_dpms ( connector , mode ) ;
exynos_drm_display_power ( connector , mode ) ;
}
2012-03-16 18:47:04 +09:00
static int exynos_drm_connector_fill_modes ( struct drm_connector * connector ,
unsigned int max_width , unsigned int max_height )
{
struct exynos_drm_connector * exynos_connector =
to_exynos_connector ( connector ) ;
struct exynos_drm_manager * manager = exynos_connector - > manager ;
struct exynos_drm_manager_ops * ops = manager - > ops ;
unsigned int width , height ;
width = max_width ;
height = max_height ;
/*
* if specific driver want to find desired_mode using maxmum
* resolution then get max width and height from that driver .
*/
if ( ops & & ops - > get_max_resol )
ops - > get_max_resol ( manager - > dev , & width , & height ) ;
return drm_helper_probe_single_connector_modes ( connector , width ,
height ) ;
}
2011-10-04 19:19:01 +09:00
/* get detection status of display device. */
static enum drm_connector_status
exynos_drm_connector_detect ( struct drm_connector * connector , bool force )
{
2011-10-19 17:16:55 +09:00
struct exynos_drm_connector * exynos_connector =
to_exynos_connector ( connector ) ;
struct exynos_drm_manager * manager = exynos_connector - > manager ;
2011-10-19 17:23:07 +09:00
struct exynos_drm_display_ops * display_ops =
manager - > display_ops ;
2011-10-04 19:19:01 +09:00
enum drm_connector_status status = connector_status_disconnected ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-10-19 17:23:07 +09:00
if ( display_ops & & display_ops - > is_connected ) {
if ( display_ops - > is_connected ( manager - > dev ) )
2011-10-04 19:19:01 +09:00
status = connector_status_connected ;
else
status = connector_status_disconnected ;
}
return status ;
}
static void exynos_drm_connector_destroy ( struct drm_connector * connector )
{
struct exynos_drm_connector * exynos_connector =
to_exynos_connector ( connector ) ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
drm_sysfs_connector_remove ( connector ) ;
drm_connector_cleanup ( connector ) ;
kfree ( exynos_connector ) ;
}
static struct drm_connector_funcs exynos_connector_funcs = {
2012-08-20 21:29:25 +09:00
. dpms = exynos_drm_connector_dpms ,
2012-03-16 18:47:04 +09:00
. fill_modes = exynos_drm_connector_fill_modes ,
2011-10-04 19:19:01 +09:00
. detect = exynos_drm_connector_detect ,
. destroy = exynos_drm_connector_destroy ,
} ;
struct drm_connector * exynos_drm_connector_create ( struct drm_device * dev ,
struct drm_encoder * encoder )
{
struct exynos_drm_connector * exynos_connector ;
struct exynos_drm_manager * manager = exynos_drm_get_manager ( encoder ) ;
struct drm_connector * connector ;
int type ;
int err ;
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
exynos_connector = kzalloc ( sizeof ( * exynos_connector ) , GFP_KERNEL ) ;
if ( ! exynos_connector ) {
DRM_ERROR ( " failed to allocate connector \n " ) ;
return NULL ;
}
connector = & exynos_connector - > drm_connector ;
2011-10-19 17:23:07 +09:00
switch ( manager - > display_ops - > type ) {
2011-10-04 19:19:01 +09:00
case EXYNOS_DISPLAY_TYPE_HDMI :
type = DRM_MODE_CONNECTOR_HDMIA ;
2011-10-19 15:10:10 +09:00
connector - > interlace_allowed = true ;
connector - > polled = DRM_CONNECTOR_POLL_HPD ;
2011-10-04 19:19:01 +09:00
break ;
2012-03-21 10:55:26 +09:00
case EXYNOS_DISPLAY_TYPE_VIDI :
type = DRM_MODE_CONNECTOR_VIRTUAL ;
connector - > polled = DRM_CONNECTOR_POLL_HPD ;
break ;
2011-10-04 19:19:01 +09:00
default :
type = DRM_MODE_CONNECTOR_Unknown ;
break ;
}
drm_connector_init ( dev , connector , & exynos_connector_funcs , type ) ;
drm_connector_helper_add ( connector , & exynos_connector_helper_funcs ) ;
err = drm_sysfs_connector_add ( connector ) ;
if ( err )
goto err_connector ;
2011-10-19 17:16:55 +09:00
exynos_connector - > encoder_id = encoder - > base . id ;
exynos_connector - > manager = manager ;
2012-08-20 21:29:25 +09:00
exynos_connector - > dpms = DRM_MODE_DPMS_OFF ;
2012-10-18 18:59:55 +09:00
connector - > dpms = DRM_MODE_DPMS_OFF ;
2011-10-04 19:19:01 +09:00
connector - > encoder = encoder ;
2011-10-19 17:16:55 +09:00
2011-10-04 19:19:01 +09:00
err = drm_mode_connector_attach_encoder ( connector , encoder ) ;
if ( err ) {
DRM_ERROR ( " failed to attach a connector to a encoder \n " ) ;
goto err_sysfs ;
}
DRM_DEBUG_KMS ( " connector has been created \n " ) ;
return connector ;
err_sysfs :
drm_sysfs_connector_remove ( connector ) ;
err_connector :
drm_connector_cleanup ( connector ) ;
kfree ( exynos_connector ) ;
return NULL ;
}