2017-03-02 12:47:26 +02:00
/*
* Copyright ( C ) 2016 Laurent Pinchart < laurent . pinchart @ ideasonboard . 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 .
*/
# include <drm/drmP.h>
# include <drm/drm_atomic_helper.h>
# include <drm/drm_connector.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_encoder.h>
# include <drm/drm_modeset_helper_vtables.h>
# include <drm/drm_panel.h>
# include <linux/of_graph.h>
struct lvds_encoder {
struct device * dev ;
struct drm_bridge bridge ;
struct drm_connector connector ;
struct drm_panel * panel ;
} ;
static inline struct lvds_encoder *
drm_bridge_to_lvds_encoder ( struct drm_bridge * bridge )
{
return container_of ( bridge , struct lvds_encoder , bridge ) ;
}
static inline struct lvds_encoder *
drm_connector_to_lvds_encoder ( struct drm_connector * connector )
{
return container_of ( connector , struct lvds_encoder , connector ) ;
}
static int lvds_connector_get_modes ( struct drm_connector * connector )
{
struct lvds_encoder * lvds = drm_connector_to_lvds_encoder ( connector ) ;
return drm_panel_get_modes ( lvds - > panel ) ;
}
static const struct drm_connector_helper_funcs lvds_connector_helper_funcs = {
. get_modes = lvds_connector_get_modes ,
} ;
static const struct drm_connector_funcs lvds_connector_funcs = {
. dpms = drm_atomic_helper_connector_dpms ,
. reset = drm_atomic_helper_connector_reset ,
. fill_modes = drm_helper_probe_single_connector_modes ,
. destroy = drm_connector_cleanup ,
. atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state ,
. atomic_destroy_state = drm_atomic_helper_connector_destroy_state ,
} ;
static int lvds_encoder_attach ( struct drm_bridge * bridge )
{
struct lvds_encoder * lvds = drm_bridge_to_lvds_encoder ( bridge ) ;
struct drm_connector * connector = & lvds - > connector ;
int ret ;
if ( ! bridge - > encoder ) {
DRM_ERROR ( " Missing encoder \n " ) ;
return - ENODEV ;
}
drm_connector_helper_add ( connector , & lvds_connector_helper_funcs ) ;
ret = drm_connector_init ( bridge - > dev , connector , & lvds_connector_funcs ,
DRM_MODE_CONNECTOR_LVDS ) ;
if ( ret ) {
DRM_ERROR ( " Failed to initialize connector \n " ) ;
return ret ;
}
drm_mode_connector_attach_encoder ( & lvds - > connector , bridge - > encoder ) ;
ret = drm_panel_attach ( lvds - > panel , & lvds - > connector ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static void lvds_encoder_detach ( struct drm_bridge * bridge )
{
struct lvds_encoder * lvds = drm_bridge_to_lvds_encoder ( bridge ) ;
drm_panel_detach ( lvds - > panel ) ;
}
static void lvds_encoder_pre_enable ( struct drm_bridge * bridge )
{
struct lvds_encoder * lvds = drm_bridge_to_lvds_encoder ( bridge ) ;
drm_panel_prepare ( lvds - > panel ) ;
}
static void lvds_encoder_enable ( struct drm_bridge * bridge )
{
struct lvds_encoder * lvds = drm_bridge_to_lvds_encoder ( bridge ) ;
drm_panel_enable ( lvds - > panel ) ;
}
static void lvds_encoder_disable ( struct drm_bridge * bridge )
{
struct lvds_encoder * lvds = drm_bridge_to_lvds_encoder ( bridge ) ;
drm_panel_disable ( lvds - > panel ) ;
}
static void lvds_encoder_post_disable ( struct drm_bridge * bridge )
{
struct lvds_encoder * lvds = drm_bridge_to_lvds_encoder ( bridge ) ;
drm_panel_unprepare ( lvds - > panel ) ;
}
static const struct drm_bridge_funcs lvds_encoder_bridge_funcs = {
. attach = lvds_encoder_attach ,
. detach = lvds_encoder_detach ,
. pre_enable = lvds_encoder_pre_enable ,
. enable = lvds_encoder_enable ,
. disable = lvds_encoder_disable ,
. post_disable = lvds_encoder_post_disable ,
} ;
static int lvds_encoder_probe ( struct platform_device * pdev )
{
struct lvds_encoder * lvds ;
struct device_node * port ;
struct device_node * endpoint ;
struct device_node * panel ;
lvds = devm_kzalloc ( & pdev - > dev , sizeof ( * lvds ) , GFP_KERNEL ) ;
if ( ! lvds )
return - ENOMEM ;
lvds - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , lvds ) ;
lvds - > bridge . funcs = & lvds_encoder_bridge_funcs ;
lvds - > bridge . of_node = pdev - > dev . of_node ;
/* Locate the panel DT node. */
port = of_graph_get_port_by_id ( pdev - > dev . of_node , 1 ) ;
if ( ! port ) {
dev_dbg ( & pdev - > dev , " port 1 not found \n " ) ;
return - ENXIO ;
}
endpoint = of_get_child_by_name ( port , " endpoint " ) ;
of_node_put ( port ) ;
if ( ! endpoint ) {
dev_dbg ( & pdev - > dev , " no endpoint for port 1 \n " ) ;
return - ENXIO ;
}
panel = of_graph_get_remote_port_parent ( endpoint ) ;
of_node_put ( endpoint ) ;
if ( ! panel ) {
dev_dbg ( & pdev - > dev , " no remote endpoint for port 1 \n " ) ;
return - ENXIO ;
}
lvds - > panel = of_drm_find_panel ( panel ) ;
of_node_put ( panel ) ;
if ( ! lvds - > panel ) {
dev_dbg ( & pdev - > dev , " panel not found, deferring probe \n " ) ;
return - EPROBE_DEFER ;
}
/* Register the bridge. */
return drm_bridge_add ( & lvds - > bridge ) ;
}
static int lvds_encoder_remove ( struct platform_device * pdev )
{
struct lvds_encoder * encoder = platform_get_drvdata ( pdev ) ;
drm_bridge_remove ( & encoder - > bridge ) ;
return 0 ;
}
static const struct of_device_id lvds_encoder_match [ ] = {
{ . compatible = " lvds-encoder " } ,
2017-03-02 12:47:28 +02:00
{ . compatible = " thine,thc63lvdm83d " } ,
2017-03-02 12:47:26 +02:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , lvds_encoder_match ) ;
static struct platform_driver lvds_encoder_driver = {
. probe = lvds_encoder_probe ,
. remove = lvds_encoder_remove ,
. driver = {
. name = " lvds-encoder " ,
. of_match_table = lvds_encoder_match ,
} ,
} ;
module_platform_driver ( lvds_encoder_driver ) ;
MODULE_AUTHOR ( " Laurent Pinchart <laurent.pinchart@ideasonboard.com> " ) ;
MODULE_DESCRIPTION ( " Transparent parallel to LVDS encoder " ) ;
MODULE_LICENSE ( " GPL " ) ;