2015-01-06 11:13:28 +01:00
/*
* Copyright ( C ) 2014 Traphandler
* Copyright ( C ) 2014 Free Electrons
* Copyright ( C ) 2014 Atmel
*
* Author : Jean - Jacques Hiblot < jjhiblot @ traphandler . com >
* Author : Boris BREZILLON < boris . brezillon @ free - electrons . com >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License along with
* this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/of_graph.h>
# include <drm/drmP.h>
2017-03-29 13:55:46 -05:00
# include <drm/drm_of.h>
2015-01-06 11:13:28 +01:00
# include "atmel_hlcdc_dc.h"
/**
* Atmel HLCDC RGB connector structure
*
* This structure stores RGB slave device information .
*
* @ connector : DRM connector
* @ encoder : DRM encoder
* @ dc : pointer to the atmel_hlcdc_dc structure
2016-01-06 10:16:32 +01:00
* @ panel : panel connected on the RGB output
2015-01-06 11:13:28 +01:00
*/
struct atmel_hlcdc_rgb_output {
struct drm_connector connector ;
struct drm_encoder encoder ;
struct atmel_hlcdc_dc * dc ;
2016-01-06 10:16:32 +01:00
struct drm_panel * panel ;
2015-01-06 11:13:28 +01:00
} ;
static inline struct atmel_hlcdc_rgb_output *
drm_connector_to_atmel_hlcdc_rgb_output ( struct drm_connector * connector )
{
return container_of ( connector , struct atmel_hlcdc_rgb_output ,
connector ) ;
}
static inline struct atmel_hlcdc_rgb_output *
drm_encoder_to_atmel_hlcdc_rgb_output ( struct drm_encoder * encoder )
{
return container_of ( encoder , struct atmel_hlcdc_rgb_output , encoder ) ;
}
2016-01-06 10:16:32 +01:00
static void atmel_hlcdc_rgb_encoder_enable ( struct drm_encoder * encoder )
2015-01-06 11:13:28 +01:00
{
struct atmel_hlcdc_rgb_output * rgb =
drm_encoder_to_atmel_hlcdc_rgb_output ( encoder ) ;
2016-01-06 10:16:32 +01:00
if ( rgb - > panel ) {
drm_panel_prepare ( rgb - > panel ) ;
drm_panel_enable ( rgb - > panel ) ;
}
2015-02-05 16:32:33 +01:00
}
2015-01-06 11:13:28 +01:00
2016-01-06 10:16:32 +01:00
static void atmel_hlcdc_rgb_encoder_disable ( struct drm_encoder * encoder )
2015-02-05 16:32:33 +01:00
{
struct atmel_hlcdc_rgb_output * rgb =
drm_encoder_to_atmel_hlcdc_rgb_output ( encoder ) ;
2015-01-06 11:13:28 +01:00
2016-01-06 10:16:32 +01:00
if ( rgb - > panel ) {
drm_panel_disable ( rgb - > panel ) ;
drm_panel_unprepare ( rgb - > panel ) ;
}
2015-01-06 11:13:28 +01:00
}
2015-12-15 12:21:00 +01:00
static const struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = {
2016-01-06 10:16:32 +01:00
. disable = atmel_hlcdc_rgb_encoder_disable ,
. enable = atmel_hlcdc_rgb_encoder_enable ,
2015-01-06 11:13:28 +01:00
} ;
static void atmel_hlcdc_rgb_encoder_destroy ( struct drm_encoder * encoder )
{
drm_encoder_cleanup ( encoder ) ;
memset ( encoder , 0 , sizeof ( * encoder ) ) ;
}
static const struct drm_encoder_funcs atmel_hlcdc_panel_encoder_funcs = {
. destroy = atmel_hlcdc_rgb_encoder_destroy ,
} ;
static int atmel_hlcdc_panel_get_modes ( struct drm_connector * connector )
{
struct atmel_hlcdc_rgb_output * rgb =
drm_connector_to_atmel_hlcdc_rgb_output ( connector ) ;
2016-01-06 10:16:32 +01:00
if ( rgb - > panel )
return rgb - > panel - > funcs - > get_modes ( rgb - > panel ) ;
return 0 ;
2015-01-06 11:13:28 +01:00
}
static int atmel_hlcdc_rgb_mode_valid ( struct drm_connector * connector ,
struct drm_display_mode * mode )
{
struct atmel_hlcdc_rgb_output * rgb =
drm_connector_to_atmel_hlcdc_rgb_output ( connector ) ;
return atmel_hlcdc_dc_mode_valid ( rgb - > dc , mode ) ;
}
2015-12-15 12:21:00 +01:00
static const struct drm_connector_helper_funcs atmel_hlcdc_panel_connector_helper_funcs = {
2015-01-06 11:13:28 +01:00
. get_modes = atmel_hlcdc_panel_get_modes ,
. mode_valid = atmel_hlcdc_rgb_mode_valid ,
} ;
static enum drm_connector_status
atmel_hlcdc_panel_connector_detect ( struct drm_connector * connector , bool force )
{
2016-01-06 10:16:32 +01:00
struct atmel_hlcdc_rgb_output * rgb =
drm_connector_to_atmel_hlcdc_rgb_output ( connector ) ;
if ( rgb - > panel )
return connector_status_connected ;
return connector_status_disconnected ;
2015-01-06 11:13:28 +01:00
}
static void
atmel_hlcdc_panel_connector_destroy ( struct drm_connector * connector )
{
struct atmel_hlcdc_rgb_output * rgb =
drm_connector_to_atmel_hlcdc_rgb_output ( connector ) ;
2016-01-06 10:16:32 +01:00
if ( rgb - > panel )
drm_panel_detach ( rgb - > panel ) ;
2015-01-06 11:13:28 +01:00
drm_connector_cleanup ( connector ) ;
}
static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = {
2015-02-05 16:32:33 +01:00
. dpms = drm_atomic_helper_connector_dpms ,
2015-01-06 11:13:28 +01:00
. detect = atmel_hlcdc_panel_connector_detect ,
. fill_modes = drm_helper_probe_single_connector_modes ,
. destroy = atmel_hlcdc_panel_connector_destroy ,
2015-02-05 16:32:33 +01:00
. reset = drm_atomic_helper_connector_reset ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
2015-01-06 11:13:28 +01:00
} ;
2017-05-18 14:35:21 +02:00
static int atmel_hlcdc_attach_endpoint ( struct drm_device * dev , int endpoint )
2016-01-06 10:16:32 +01:00
{
struct atmel_hlcdc_dc * dc = dev - > dev_private ;
struct atmel_hlcdc_rgb_output * output ;
struct drm_panel * panel ;
struct drm_bridge * bridge ;
int ret ;
2015-01-06 11:13:28 +01:00
2017-05-18 14:35:21 +02:00
ret = drm_of_find_panel_or_bridge ( dev - > dev - > of_node , 0 , endpoint ,
& panel , & bridge ) ;
if ( ret )
return ret ;
2016-01-06 10:16:32 +01:00
output = devm_kzalloc ( dev - > dev , sizeof ( * output ) , GFP_KERNEL ) ;
if ( ! output )
return - EINVAL ;
2015-01-06 11:13:28 +01:00
2016-01-06 10:16:32 +01:00
output - > dc = dc ;
2015-01-06 11:13:28 +01:00
2016-01-06 10:16:32 +01:00
drm_encoder_helper_add ( & output - > encoder ,
2015-01-06 11:13:28 +01:00
& atmel_hlcdc_panel_encoder_helper_funcs ) ;
2016-01-06 10:16:32 +01:00
ret = drm_encoder_init ( dev , & output - > encoder ,
2015-01-06 11:13:28 +01:00
& atmel_hlcdc_panel_encoder_funcs ,
2015-12-31 18:24:09 +01:00
DRM_MODE_ENCODER_NONE , NULL ) ;
2015-01-06 11:13:28 +01:00
if ( ret )
return ret ;
2016-01-06 10:16:32 +01:00
output - > encoder . possible_crtcs = 0x1 ;
if ( panel ) {
output - > connector . dpms = DRM_MODE_DPMS_OFF ;
output - > connector . polled = DRM_CONNECTOR_POLL_CONNECT ;
drm_connector_helper_add ( & output - > connector ,
& atmel_hlcdc_panel_connector_helper_funcs ) ;
ret = drm_connector_init ( dev , & output - > connector ,
& atmel_hlcdc_panel_connector_funcs ,
DRM_MODE_CONNECTOR_Unknown ) ;
if ( ret )
goto err_encoder_cleanup ;
2015-01-06 11:13:28 +01:00
2016-01-06 10:16:32 +01:00
drm_mode_connector_attach_encoder ( & output - > connector ,
& output - > encoder ) ;
ret = drm_panel_attach ( panel , & output - > connector ) ;
if ( ret ) {
drm_connector_cleanup ( & output - > connector ) ;
goto err_encoder_cleanup ;
}
output - > panel = panel ;
return 0 ;
}
if ( bridge ) {
2016-11-28 17:59:08 +02:00
ret = drm_bridge_attach ( & output - > encoder , bridge , NULL ) ;
2016-01-06 10:16:32 +01:00
if ( ! ret )
return 0 ;
}
2015-01-06 11:13:28 +01:00
err_encoder_cleanup :
2016-01-06 10:16:32 +01:00
drm_encoder_cleanup ( & output - > encoder ) ;
2015-01-06 11:13:28 +01:00
return ret ;
}
int atmel_hlcdc_create_outputs ( struct drm_device * dev )
{
2017-05-18 14:35:21 +02:00
int endpoint , ret = 0 ;
for ( endpoint = 0 ; ! ret ; endpoint + + )
ret = atmel_hlcdc_attach_endpoint ( dev , endpoint ) ;
/* At least one device was successfully attached.*/
if ( ret = = - ENODEV & & endpoint )
return 0 ;
2015-01-06 11:13:28 +01:00
2017-03-29 13:55:46 -05:00
return ret ;
2015-01-06 11:13:28 +01:00
}