2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2012-11-15 21:28:22 +00:00
/*
* Copyright ( C ) 2012 Avionic Design GmbH
* Copyright ( C ) 2012 NVIDIA CORPORATION . All rights reserved .
*/
2014-11-24 16:27:13 +01:00
# include <drm/drm_atomic_helper.h>
2020-08-14 01:06:54 +03:00
# include <drm/drm_of.h>
2013-08-30 15:22:36 +02:00
# include <drm/drm_panel.h>
2020-03-05 16:59:43 +01:00
# include <drm/drm_simple_kms_helper.h>
2017-10-12 19:12:57 +02:00
2012-11-15 21:28:22 +00:00
# include "drm.h"
2017-10-12 19:12:57 +02:00
# include "dc.h"
2012-11-15 21:28:22 +00:00
2017-09-11 14:29:52 +02:00
# include <media/cec-notifier.h>
2014-11-28 15:38:40 +01:00
int tegra_output_connector_get_modes ( struct drm_connector * connector )
2012-11-15 21:28:22 +00:00
{
struct tegra_output * output = connector_to_output ( connector ) ;
struct edid * edid = NULL ;
int err = 0 ;
2014-01-13 12:56:19 +01:00
/*
* If the panel provides one or more modes , use them exclusively and
* ignore any other means of obtaining a mode .
*/
2013-08-30 15:22:36 +02:00
if ( output - > panel ) {
2019-12-07 15:03:34 +01:00
err = drm_panel_get_modes ( output - > panel , connector ) ;
2013-08-30 15:22:36 +02:00
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 ) ;
2018-12-10 17:34:54 +01:00
cec_notifier_set_phys_addr_from_edid ( output - > cec , edid ) ;
2018-07-09 10:40:06 +02:00
drm_connector_update_edid_property ( connector , edid ) ;
2012-11-15 21:28:22 +00:00
if ( edid ) {
err = drm_add_edid_modes ( connector , edid ) ;
kfree ( edid ) ;
}
return err ;
}
2014-11-28 15:38:40 +01:00
enum drm_connector_status
tegra_output_connector_detect ( struct drm_connector * connector , bool force )
2012-11-15 21:28:22 +00:00
{
struct tegra_output * output = connector_to_output ( connector ) ;
enum drm_connector_status status = connector_status_unknown ;
2019-06-05 10:46:46 +02:00
if ( output - > hpd_gpio ) {
if ( gpiod_get_value ( output - > hpd_gpio ) = = 0 )
status = connector_status_disconnected ;
else
status = connector_status_connected ;
2012-11-15 21:28:22 +00:00
} 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
}
2017-09-11 14:29:52 +02:00
if ( status ! = connector_status_connected )
2018-12-10 17:34:54 +01:00
cec_notifier_phys_addr_invalidate ( output - > cec ) ;
2017-09-11 14:29:52 +02:00
2012-11-15 21:28:22 +00:00
return status ;
}
2014-11-28 15:38:40 +01:00
void tegra_output_connector_destroy ( struct drm_connector * connector )
2012-11-15 21:28:22 +00:00
{
2019-08-14 12:45:05 +02:00
struct tegra_output * output = connector_to_output ( connector ) ;
if ( output - > cec )
cec_notifier_conn_unregister ( output - > cec ) ;
2014-05-29 16:57:41 +01:00
drm_connector_unregister ( connector ) ;
2012-11-15 21:28:22 +00:00
drm_connector_cleanup ( connector ) ;
}
static irqreturn_t hpd_irq ( int irq , void * data )
{
struct tegra_output * output = data ;
2014-10-21 14:00:09 +02:00
if ( output - > connector . dev )
drm_helper_hpd_irq_event ( output - > connector . dev ) ;
2012-11-15 21:28:22 +00:00
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 ;
2019-06-05 10:46:46 +02:00
unsigned long flags ;
2014-01-13 13:58:17 +01:00
int err , size ;
2012-11-15 21:28:22 +00:00
if ( ! output - > of_node )
output - > of_node = output - > dev - > of_node ;
2020-08-14 01:06:54 +03:00
err = drm_of_find_panel_or_bridge ( output - > of_node , - 1 , - 1 ,
& output - > panel , & output - > bridge ) ;
if ( err & & err ! = - ENODEV )
return err ;
2013-08-30 15:22:36 +02:00
panel = of_parse_phandle ( output - > of_node , " nvidia,panel " , 0 ) ;
if ( panel ) {
2020-08-14 01:06:54 +03:00
/*
* Don ' t mix nvidia , panel phandle with the graph in a
* device - tree .
*/
WARN_ON ( output - > panel | | output - > bridge ) ;
2013-08-30 15:22:36 +02:00
output - > panel = of_drm_find_panel ( panel ) ;
2020-08-14 01:06:53 +03:00
of_node_put ( panel ) ;
2018-05-09 15:00:39 +02:00
if ( IS_ERR ( output - > panel ) )
return PTR_ERR ( output - > panel ) ;
2013-08-30 15:22:36 +02:00
}
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 ) {
2020-04-08 20:01:00 +02:00
output - > ddc = of_get_i2c_adapter_by_node ( ddc ) ;
2020-08-14 01:06:53 +03:00
of_node_put ( ddc ) ;
2012-11-15 21:28:22 +00:00
if ( ! output - > ddc ) {
err = - EPROBE_DEFER ;
return err ;
}
}
2019-06-05 10:46:46 +02:00
output - > hpd_gpio = devm_gpiod_get_from_of_node ( output - > dev ,
output - > of_node ,
" nvidia,hpd-gpio " , 0 ,
GPIOD_IN ,
" HDMI hotplug detect " ) ;
2019-07-05 18:11:39 +03:00
if ( IS_ERR ( output - > hpd_gpio ) ) {
if ( PTR_ERR ( output - > hpd_gpio ) ! = - ENOENT )
return PTR_ERR ( output - > hpd_gpio ) ;
output - > hpd_gpio = NULL ;
}
2012-11-15 21:28:22 +00:00
2019-06-05 10:46:46 +02:00
if ( output - > hpd_gpio ) {
err = gpiod_to_irq ( output - > hpd_gpio ) ;
2012-11-15 21:28:22 +00:00
if ( err < 0 ) {
2019-06-05 10:46:46 +02:00
dev_err ( output - > dev , " gpiod_to_irq(): %d \n " , err ) ;
2013-10-14 14:26:42 +02:00
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
return err ;
2012-11-15 21:28:22 +00:00
}
output - > connector . polled = DRM_CONNECTOR_POLL_HPD ;
2014-10-21 14:00:09 +02:00
/*
* Disable the interrupt until the connector has been
* initialized to avoid a race in the hotplug interrupt
* handler .
*/
disable_irq ( output - > hpd_irq ) ;
2012-11-15 21:28:22 +00:00
}
2013-10-14 14:26:42 +02:00
return 0 ;
}
2014-12-19 15:55:08 +01:00
void tegra_output_remove ( struct tegra_output * output )
2013-10-14 14:26:42 +02:00
{
2019-06-05 10:46:46 +02:00
if ( output - > hpd_gpio )
2013-10-14 14:26:42 +02:00
free_irq ( output - > hpd_irq , output ) ;
if ( output - > ddc )
2020-04-08 20:01:00 +02:00
i2c_put_adapter ( output - > ddc ) ;
2013-10-14 14:26:42 +02:00
}
int tegra_output_init ( struct drm_device * drm , struct tegra_output * output )
{
2019-08-14 12:45:05 +02:00
int connector_type ;
2012-11-15 21:28:22 +00:00
2014-10-21 14:00:09 +02:00
/*
* The connector is now registered and ready to receive hotplug events
* so the hotplug interrupt can be enabled .
*/
2019-06-05 10:46:46 +02:00
if ( output - > hpd_gpio )
2014-10-21 14:00:09 +02:00
enable_irq ( output - > hpd_irq ) ;
2019-08-14 12:45:05 +02:00
connector_type = output - > connector . connector_type ;
/*
* Create a CEC notifier for HDMI connector .
*/
if ( connector_type = = DRM_MODE_CONNECTOR_HDMIA | |
connector_type = = DRM_MODE_CONNECTOR_HDMIB ) {
struct cec_connector_info conn_info ;
cec_fill_conn_info_from_drm ( & conn_info , & output - > connector ) ;
output - > cec = cec_notifier_conn_register ( output - > dev , NULL ,
& conn_info ) ;
if ( ! output - > cec )
return - ENOMEM ;
}
2012-11-15 21:28:22 +00:00
return 0 ;
}
2014-12-19 15:55:08 +01:00
void tegra_output_exit ( struct tegra_output * output )
2012-11-15 21:28:22 +00:00
{
2014-10-21 14:00:09 +02:00
/*
* The connector is going away , so the interrupt must be disabled to
* prevent the hotplug interrupt handler from potentially crashing .
*/
2019-06-05 10:46:46 +02:00
if ( output - > hpd_gpio )
2014-10-21 14:00:09 +02:00
disable_irq ( output - > hpd_irq ) ;
2012-11-15 21:28:22 +00:00
}
2017-10-12 19:12:57 +02:00
void tegra_output_find_possible_crtcs ( struct tegra_output * output ,
struct drm_device * drm )
{
struct device * dev = output - > dev ;
struct drm_crtc * crtc ;
unsigned int mask = 0 ;
drm_for_each_crtc ( crtc , drm ) {
struct tegra_dc * dc = to_tegra_dc ( crtc ) ;
if ( tegra_dc_has_output ( dc , dev ) )
mask | = drm_crtc_mask ( crtc ) ;
}
if ( mask = = 0 ) {
dev_warn ( dev , " missing output definition for heads in DT \n " ) ;
mask = 0x3 ;
}
output - > encoder . possible_crtcs = mask ;
}
2019-12-03 17:29:57 +01:00
int tegra_output_suspend ( struct tegra_output * output )
{
if ( output - > hpd_irq )
disable_irq ( output - > hpd_irq ) ;
return 0 ;
}
int tegra_output_resume ( struct tegra_output * output )
{
if ( output - > hpd_irq )
enable_irq ( output - > hpd_irq ) ;
return 0 ;
}