2012-11-15 21:28:22 +00:00
/*
* Copyright ( C ) 2012 Avionic Design GmbH
* Copyright ( C ) 2012 NVIDIA CORPORATION . All rights reserved .
*
* 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 .
*/
# include <linux/of_gpio.h>
2013-08-30 15:22:36 +02:00
# include <drm/drm_panel.h>
2012-11-15 21:28:22 +00:00
# include "drm.h"
static int tegra_connector_get_modes ( struct drm_connector * connector )
{
struct tegra_output * output = connector_to_output ( connector ) ;
struct edid * edid = NULL ;
int err = 0 ;
2013-08-30 15:22:36 +02:00
if ( output - > panel ) {
err = output - > panel - > funcs - > get_modes ( output - > panel ) ;
if ( err > 0 )
return err ;
}
2012-11-15 21:28:22 +00:00
if ( output - > edid )
edid = kmemdup ( output - > edid , sizeof ( * edid ) , GFP_KERNEL ) ;
else if ( output - > ddc )
edid = drm_get_edid ( connector , output - > ddc ) ;
drm_mode_connector_update_edid_property ( connector , edid ) ;
if ( edid ) {
err = drm_add_edid_modes ( connector , edid ) ;
kfree ( edid ) ;
}
return err ;
}
static int tegra_connector_mode_valid ( struct drm_connector * connector ,
struct drm_display_mode * mode )
{
struct tegra_output * output = connector_to_output ( connector ) ;
enum drm_mode_status status = MODE_OK ;
int err ;
err = tegra_output_check_mode ( output , mode , & status ) ;
if ( err < 0 )
return MODE_ERROR ;
return status ;
}
static struct drm_encoder *
tegra_connector_best_encoder ( struct drm_connector * connector )
{
struct tegra_output * output = connector_to_output ( connector ) ;
return & output - > encoder ;
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
. get_modes = tegra_connector_get_modes ,
. mode_valid = tegra_connector_mode_valid ,
. best_encoder = tegra_connector_best_encoder ,
} ;
static enum drm_connector_status
tegra_connector_detect ( struct drm_connector * connector , bool force )
{
struct tegra_output * output = connector_to_output ( connector ) ;
enum drm_connector_status status = connector_status_unknown ;
if ( gpio_is_valid ( output - > hpd_gpio ) ) {
if ( gpio_get_value ( output - > hpd_gpio ) = = 0 )
status = connector_status_disconnected ;
else
status = connector_status_connected ;
} else {
2013-08-30 15:22:36 +02:00
if ( ! output - > panel )
status = connector_status_disconnected ;
else
status = connector_status_connected ;
2012-11-15 21:28:22 +00:00
if ( connector - > connector_type = = DRM_MODE_CONNECTOR_LVDS )
status = connector_status_connected ;
}
return status ;
}
2013-10-14 14:06:02 +02:00
static void drm_connector_clear ( struct drm_connector * connector )
{
memset ( connector , 0 , sizeof ( * connector ) ) ;
}
2012-11-15 21:28:22 +00:00
static void tegra_connector_destroy ( struct drm_connector * connector )
{
drm_sysfs_connector_remove ( connector ) ;
drm_connector_cleanup ( connector ) ;
2013-10-14 14:06:02 +02:00
drm_connector_clear ( connector ) ;
2012-11-15 21:28:22 +00:00
}
static const struct drm_connector_funcs connector_funcs = {
. dpms = drm_helper_connector_dpms ,
. detect = tegra_connector_detect ,
. fill_modes = drm_helper_probe_single_connector_modes ,
. destroy = tegra_connector_destroy ,
} ;
2013-10-14 14:06:02 +02:00
static void drm_encoder_clear ( struct drm_encoder * encoder )
{
memset ( encoder , 0 , sizeof ( * encoder ) ) ;
}
2012-11-15 21:28:22 +00:00
static void tegra_encoder_destroy ( struct drm_encoder * encoder )
{
drm_encoder_cleanup ( encoder ) ;
2013-10-14 14:06:02 +02:00
drm_encoder_clear ( encoder ) ;
2012-11-15 21:28:22 +00:00
}
static const struct drm_encoder_funcs encoder_funcs = {
. destroy = tegra_encoder_destroy ,
} ;
static void tegra_encoder_dpms ( struct drm_encoder * encoder , int mode )
{
2013-08-30 15:22:36 +02:00
struct tegra_output * output = encoder_to_output ( encoder ) ;
struct drm_panel * panel = output - > panel ;
if ( panel & & panel - > funcs ) {
if ( mode ! = DRM_MODE_DPMS_ON )
drm_panel_disable ( panel ) ;
else
drm_panel_enable ( panel ) ;
}
2012-11-15 21:28:22 +00:00
}
static bool tegra_encoder_mode_fixup ( struct drm_encoder * encoder ,
const struct drm_display_mode * mode ,
struct drm_display_mode * adjusted )
{
return true ;
}
static void tegra_encoder_prepare ( struct drm_encoder * encoder )
{
}
static void tegra_encoder_commit ( struct drm_encoder * encoder )
{
}
static void tegra_encoder_mode_set ( struct drm_encoder * encoder ,
struct drm_display_mode * mode ,
struct drm_display_mode * adjusted )
{
struct tegra_output * output = encoder_to_output ( encoder ) ;
int err ;
err = tegra_output_enable ( output ) ;
if ( err < 0 )
dev_err ( encoder - > dev - > dev , " tegra_output_enable(): %d \n " , err ) ;
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
. dpms = tegra_encoder_dpms ,
. mode_fixup = tegra_encoder_mode_fixup ,
. prepare = tegra_encoder_prepare ,
. commit = tegra_encoder_commit ,
. mode_set = tegra_encoder_mode_set ,
} ;
static irqreturn_t hpd_irq ( int irq , void * data )
{
struct tegra_output * output = data ;
drm_helper_hpd_irq_event ( output - > connector . dev ) ;
return IRQ_HANDLED ;
}
2013-10-14 14:26:42 +02:00
int tegra_output_probe ( struct tegra_output * output )
2012-11-15 21:28:22 +00:00
{
2013-08-30 15:22:36 +02:00
struct device_node * ddc , * panel ;
2012-11-15 21:28:22 +00:00
enum of_gpio_flags flags ;
size_t size ;
int err ;
if ( ! output - > of_node )
output - > of_node = output - > dev - > of_node ;
2013-08-30 15:22:36 +02:00
panel = of_parse_phandle ( output - > of_node , " nvidia,panel " , 0 ) ;
if ( panel ) {
output - > panel = of_drm_find_panel ( panel ) ;
if ( ! output - > panel )
return - EPROBE_DEFER ;
of_node_put ( panel ) ;
}
2012-11-15 21:28:22 +00:00
output - > edid = of_get_property ( output - > of_node , " nvidia,edid " , & size ) ;
ddc = of_parse_phandle ( output - > of_node , " nvidia,ddc-i2c-bus " , 0 ) ;
if ( ddc ) {
output - > ddc = of_find_i2c_adapter_by_node ( ddc ) ;
if ( ! output - > ddc ) {
err = - EPROBE_DEFER ;
of_node_put ( ddc ) ;
return err ;
}
of_node_put ( ddc ) ;
}
output - > hpd_gpio = of_get_named_gpio_flags ( output - > of_node ,
" nvidia,hpd-gpio " , 0 ,
& flags ) ;
if ( gpio_is_valid ( output - > hpd_gpio ) ) {
unsigned long flags ;
err = gpio_request_one ( output - > hpd_gpio , GPIOF_DIR_IN ,
" HDMI hotplug detect " ) ;
if ( err < 0 ) {
dev_err ( output - > dev , " gpio_request_one(): %d \n " , err ) ;
return err ;
}
err = gpio_to_irq ( output - > hpd_gpio ) ;
if ( err < 0 ) {
dev_err ( output - > dev , " gpio_to_irq(): %d \n " , err ) ;
2013-10-14 14:26:42 +02:00
gpio_free ( output - > hpd_gpio ) ;
return err ;
2012-11-15 21:28:22 +00:00
}
output - > hpd_irq = err ;
flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT ;
err = request_threaded_irq ( output - > hpd_irq , NULL , hpd_irq ,
flags , " hpd " , output ) ;
if ( err < 0 ) {
dev_err ( output - > dev , " failed to request IRQ#%u: %d \n " ,
output - > hpd_irq , err ) ;
2013-10-14 14:26:42 +02:00
gpio_free ( output - > hpd_gpio ) ;
return err ;
2012-11-15 21:28:22 +00:00
}
output - > connector . polled = DRM_CONNECTOR_POLL_HPD ;
}
2013-10-14 14:26:42 +02:00
return 0 ;
}
int tegra_output_remove ( struct tegra_output * output )
{
if ( gpio_is_valid ( output - > hpd_gpio ) ) {
free_irq ( output - > hpd_irq , output ) ;
gpio_free ( output - > hpd_gpio ) ;
}
if ( output - > ddc )
put_device ( & output - > ddc - > dev ) ;
return 0 ;
}
int tegra_output_init ( struct drm_device * drm , struct tegra_output * output )
{
int connector , encoder ;
2012-11-15 21:28:22 +00:00
switch ( output - > type ) {
case TEGRA_OUTPUT_RGB :
connector = DRM_MODE_CONNECTOR_LVDS ;
encoder = DRM_MODE_ENCODER_LVDS ;
break ;
2012-11-15 21:28:23 +00:00
case TEGRA_OUTPUT_HDMI :
connector = DRM_MODE_CONNECTOR_HDMIA ;
encoder = DRM_MODE_ENCODER_TMDS ;
break ;
2012-11-15 21:28:22 +00:00
default :
connector = DRM_MODE_CONNECTOR_Unknown ;
encoder = DRM_MODE_ENCODER_NONE ;
break ;
}
drm_connector_init ( drm , & output - > connector , & connector_funcs ,
connector ) ;
drm_connector_helper_add ( & output - > connector , & connector_helper_funcs ) ;
2013-09-24 09:58:08 +02:00
output - > connector . dpms = DRM_MODE_DPMS_OFF ;
2012-11-15 21:28:22 +00:00
2013-08-30 15:22:36 +02:00
if ( output - > panel )
drm_panel_attach ( output - > panel , & output - > connector ) ;
2012-11-15 21:28:22 +00:00
drm_encoder_init ( drm , & output - > encoder , & encoder_funcs , encoder ) ;
drm_encoder_helper_add ( & output - > encoder , & encoder_helper_funcs ) ;
drm_mode_connector_attach_encoder ( & output - > connector , & output - > encoder ) ;
drm_sysfs_connector_add ( & output - > connector ) ;
output - > encoder . possible_crtcs = 0x3 ;
return 0 ;
}
int tegra_output_exit ( struct tegra_output * output )
{
return 0 ;
}