2019-04-02 15:26:00 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* ACPI support
*
* Copyright ( C ) 2020 , Intel Corporation
* Author : Mika Westerberg < mika . westerberg @ linux . intel . com >
*/
# include <linux/acpi.h>
# include "tb.h"
static acpi_status tb_acpi_add_link ( acpi_handle handle , u32 level , void * data ,
void * * return_value )
{
struct fwnode_reference_args args ;
struct fwnode_handle * fwnode ;
struct tb_nhi * nhi = data ;
struct acpi_device * adev ;
struct pci_dev * pdev ;
struct device * dev ;
int ret ;
if ( acpi_bus_get_device ( handle , & adev ) )
return AE_OK ;
fwnode = acpi_fwnode_handle ( adev ) ;
ret = fwnode_property_get_reference_args ( fwnode , " usb4-host-interface " ,
NULL , 0 , 0 , & args ) ;
if ( ret )
return AE_OK ;
/* It needs to reference this NHI */
if ( nhi - > pdev - > dev . fwnode ! = args . fwnode )
goto out_put ;
/*
* Try to find physical device walking upwards to the hierarcy .
* We need to do this because the xHCI driver might not yet be
* bound so the USB3 SuperSpeed ports are not yet created .
*/
dev = acpi_get_first_physical_node ( adev ) ;
while ( ! dev ) {
adev = adev - > parent ;
if ( ! adev )
break ;
dev = acpi_get_first_physical_node ( adev ) ;
}
if ( ! dev )
goto out_put ;
/*
* Check that the device is PCIe . This is because USB3
* SuperSpeed ports have this property and they are not power
* managed with the xHCI and the SuperSpeed hub so we create the
* link from xHCI instead .
*/
2020-10-26 19:12:59 +03:00
while ( dev & & ! dev_is_pci ( dev ) )
2019-04-02 15:26:00 +03:00
dev = dev - > parent ;
if ( ! dev )
goto out_put ;
/*
* Check that this actually matches the type of device we
* expect . It should either be xHCI or PCIe root / downstream
* port .
*/
pdev = to_pci_dev ( dev ) ;
if ( pdev - > class = = PCI_CLASS_SERIAL_USB_XHCI | |
( pci_is_pcie ( pdev ) & &
( pci_pcie_type ( pdev ) = = PCI_EXP_TYPE_ROOT_PORT | |
pci_pcie_type ( pdev ) = = PCI_EXP_TYPE_DOWNSTREAM ) ) ) {
const struct device_link * link ;
link = device_link_add ( & pdev - > dev , & nhi - > pdev - > dev ,
DL_FLAG_AUTOREMOVE_SUPPLIER |
DL_FLAG_PM_RUNTIME ) ;
if ( link ) {
dev_dbg ( & nhi - > pdev - > dev , " created link from %s \n " ,
dev_name ( & pdev - > dev ) ) ;
} else {
dev_warn ( & nhi - > pdev - > dev , " device link creation from %s failed \n " ,
dev_name ( & pdev - > dev ) ) ;
}
}
out_put :
fwnode_handle_put ( args . fwnode ) ;
return AE_OK ;
}
/**
* tb_acpi_add_links ( ) - Add device links based on ACPI description
* @ nhi : Pointer to NHI
*
* Goes over ACPI namespace finding tunneled ports that reference to
* @ nhi ACPI node . For each reference a device link is added . The link
* is automatically removed by the driver core .
*/
void tb_acpi_add_links ( struct tb_nhi * nhi )
{
acpi_status status ;
if ( ! has_acpi_companion ( & nhi - > pdev - > dev ) )
return ;
/*
* Find all devices that have usb4 - host - controller interface
* property that references to this NHI .
*/
status = acpi_walk_namespace ( ACPI_TYPE_DEVICE , ACPI_ROOT_OBJECT , 32 ,
tb_acpi_add_link , NULL , nhi , NULL ) ;
if ( ACPI_FAILURE ( status ) )
dev_warn ( & nhi - > pdev - > dev , " failed to enumerate tunneled ports \n " ) ;
}
2020-02-18 17:14:42 +03:00
/**
* tb_acpi_is_native ( ) - Did the platform grant native TBT / USB4 control
*
* Returns % true if the platform granted OS native control over
* TBT / USB4 . In this case software based connection manager can be used ,
* otherwise there is firmware based connection manager running .
*/
bool tb_acpi_is_native ( void )
{
return osc_sb_native_usb4_support_confirmed & &
osc_sb_native_usb4_control ;
}
/**
* tb_acpi_may_tunnel_usb3 ( ) - Is USB3 tunneling allowed by the platform
*
* When software based connection manager is used , this function
* returns % true if platform allows native USB3 tunneling .
*/
bool tb_acpi_may_tunnel_usb3 ( void )
{
if ( tb_acpi_is_native ( ) )
return osc_sb_native_usb4_control & OSC_USB_USB3_TUNNELING ;
return true ;
}
/**
* tb_acpi_may_tunnel_dp ( ) - Is DisplayPort tunneling allowed by the platform
*
* When software based connection manager is used , this function
* returns % true if platform allows native DP tunneling .
*/
bool tb_acpi_may_tunnel_dp ( void )
{
if ( tb_acpi_is_native ( ) )
return osc_sb_native_usb4_control & OSC_USB_DP_TUNNELING ;
return true ;
}
/**
* tb_acpi_may_tunnel_pcie ( ) - Is PCIe tunneling allowed by the platform
*
* When software based connection manager is used , this function
* returns % true if platform allows native PCIe tunneling .
*/
bool tb_acpi_may_tunnel_pcie ( void )
{
if ( tb_acpi_is_native ( ) )
return osc_sb_native_usb4_control & OSC_USB_PCIE_TUNNELING ;
return true ;
}
/**
* tb_acpi_is_xdomain_allowed ( ) - Are XDomain connections allowed
*
* When software based connection manager is used , this function
* returns % true if platform allows XDomain connections .
*/
bool tb_acpi_is_xdomain_allowed ( void )
{
if ( tb_acpi_is_native ( ) )
return osc_sb_native_usb4_control & OSC_USB_XDOMAIN ;
return true ;
}
2021-04-01 18:20:17 +03:00
/* UUID for retimer _DSM: e0053122-795b-4122-8a5e-57be1d26acb3 */
static const guid_t retimer_dsm_guid =
GUID_INIT ( 0xe0053122 , 0x795b , 0x4122 ,
0x8a , 0x5e , 0x57 , 0xbe , 0x1d , 0x26 , 0xac , 0xb3 ) ;
# define RETIMER_DSM_QUERY_ONLINE_STATE 1
# define RETIMER_DSM_SET_ONLINE_STATE 2
static int tb_acpi_retimer_set_power ( struct tb_port * port , bool power )
{
struct usb4_port * usb4 = port - > usb4 ;
union acpi_object argv4 [ 2 ] ;
struct acpi_device * adev ;
union acpi_object * obj ;
int ret ;
if ( ! usb4 - > can_offline )
return 0 ;
adev = ACPI_COMPANION ( & usb4 - > dev ) ;
if ( WARN_ON ( ! adev ) )
return 0 ;
/* Check if we are already powered on (and in correct mode) */
obj = acpi_evaluate_dsm_typed ( adev - > handle , & retimer_dsm_guid , 1 ,
RETIMER_DSM_QUERY_ONLINE_STATE , NULL ,
ACPI_TYPE_INTEGER ) ;
if ( ! obj ) {
tb_port_warn ( port , " ACPI: query online _DSM failed \n " ) ;
return - EIO ;
}
ret = obj - > integer . value ;
ACPI_FREE ( obj ) ;
if ( power = = ret )
return 0 ;
tb_port_dbg ( port , " ACPI: calling _DSM to power %s retimers \n " ,
power ? " on " : " off " ) ;
argv4 [ 0 ] . type = ACPI_TYPE_PACKAGE ;
argv4 [ 0 ] . package . count = 1 ;
argv4 [ 0 ] . package . elements = & argv4 [ 1 ] ;
argv4 [ 1 ] . integer . type = ACPI_TYPE_INTEGER ;
argv4 [ 1 ] . integer . value = power ;
obj = acpi_evaluate_dsm_typed ( adev - > handle , & retimer_dsm_guid , 1 ,
RETIMER_DSM_SET_ONLINE_STATE , argv4 ,
ACPI_TYPE_INTEGER ) ;
if ( ! obj ) {
tb_port_warn ( port ,
" ACPI: set online state _DSM evaluation failed \n " ) ;
return - EIO ;
}
ret = obj - > integer . value ;
ACPI_FREE ( obj ) ;
if ( ret > = 0 ) {
if ( power )
return ret = = 1 ? 0 : - EBUSY ;
return 0 ;
}
tb_port_warn ( port , " ACPI: set online state _DSM failed with error %d \n " , ret ) ;
return - EIO ;
}
/**
* tb_acpi_power_on_retimers ( ) - Call platform to power on retimers
* @ port : USB4 port
*
* Calls platform to turn on power to all retimers behind this USB4
* port . After this function returns successfully the caller can
* continue with the normal retimer flows ( as specified in the USB4
* spec ) . Note if this returns % - EBUSY it means the type - C port is in
* non - USB4 / TBT mode ( there is non - USB4 / TBT device connected ) .
*
* This should only be called if the USB4 / TBT link is not up .
*
* Returns % 0 on success .
*/
int tb_acpi_power_on_retimers ( struct tb_port * port )
{
return tb_acpi_retimer_set_power ( port , true ) ;
}
/**
* tb_acpi_power_off_retimers ( ) - Call platform to power off retimers
* @ port : USB4 port
*
* This is the opposite of tb_acpi_power_on_retimers ( ) . After returning
* successfully the normal operations with the @ port can continue .
*
* Returns % 0 on success .
*/
int tb_acpi_power_off_retimers ( struct tb_port * port )
{
return tb_acpi_retimer_set_power ( port , false ) ;
}
static bool tb_acpi_bus_match ( struct device * dev )
{
return tb_is_switch ( dev ) | | tb_is_usb4_port_device ( dev ) ;
}
static struct acpi_device * tb_acpi_find_port ( struct acpi_device * adev ,
const struct tb_port * port )
{
struct acpi_device * port_adev ;
if ( ! adev )
return NULL ;
/*
* Device routers exists under the downstream facing USB4 port
* of the parent router . Their _ADR is always 0.
*/
list_for_each_entry ( port_adev , & adev - > children , node ) {
if ( acpi_device_adr ( port_adev ) = = port - > port )
return port_adev ;
}
return NULL ;
}
static struct acpi_device * tb_acpi_switch_find_companion ( struct tb_switch * sw )
{
struct acpi_device * adev = NULL ;
struct tb_switch * parent_sw ;
parent_sw = tb_switch_parent ( sw ) ;
if ( parent_sw ) {
struct tb_port * port = tb_port_at ( tb_route ( sw ) , parent_sw ) ;
struct acpi_device * port_adev ;
port_adev = tb_acpi_find_port ( ACPI_COMPANION ( & parent_sw - > dev ) , port ) ;
if ( port_adev )
adev = acpi_find_child_device ( port_adev , 0 , false ) ;
} else {
struct tb_nhi * nhi = sw - > tb - > nhi ;
struct acpi_device * parent_adev ;
parent_adev = ACPI_COMPANION ( & nhi - > pdev - > dev ) ;
if ( parent_adev )
adev = acpi_find_child_device ( parent_adev , 0 , false ) ;
}
return adev ;
}
static struct acpi_device * tb_acpi_find_companion ( struct device * dev )
{
/*
* The Thunderbolt / USB4 hierarchy looks like following :
*
* Device ( NHI )
* Device ( HR ) // Host router _ADR == 0
* Device ( DFP0 ) // Downstream port _ADR == lane 0 adapter
* Device ( DR ) // Device router _ADR == 0
* Device ( UFP ) // Upstream port _ADR == lane 0 adapter
* Device ( DFP1 ) // Downstream port _ADR == lane 0 adapter number
*
* At the moment we bind the host router to the corresponding
* Linux device .
*/
if ( tb_is_switch ( dev ) )
return tb_acpi_switch_find_companion ( tb_to_switch ( dev ) ) ;
else if ( tb_is_usb4_port_device ( dev ) )
return tb_acpi_find_port ( ACPI_COMPANION ( dev - > parent ) ,
tb_to_usb4_port_device ( dev ) - > port ) ;
return NULL ;
}
static void tb_acpi_setup ( struct device * dev )
{
struct acpi_device * adev = ACPI_COMPANION ( dev ) ;
struct usb4_port * usb4 = tb_to_usb4_port_device ( dev ) ;
if ( ! adev | | ! usb4 )
return ;
if ( acpi_check_dsm ( adev - > handle , & retimer_dsm_guid , 1 ,
BIT ( RETIMER_DSM_QUERY_ONLINE_STATE ) |
BIT ( RETIMER_DSM_SET_ONLINE_STATE ) ) )
usb4 - > can_offline = true ;
}
static struct acpi_bus_type tb_acpi_bus = {
. name = " thunderbolt " ,
. match = tb_acpi_bus_match ,
. find_companion = tb_acpi_find_companion ,
. setup = tb_acpi_setup ,
} ;
int tb_acpi_init ( void )
{
return register_acpi_bus_type ( & tb_acpi_bus ) ;
}
void tb_acpi_exit ( void )
{
unregister_acpi_bus_type ( & tb_acpi_bus ) ;
}