2022-10-07 18:12:02 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* CLx support
*
* Copyright ( C ) 2020 - 2023 , Intel Corporation
* Authors : Gil Fine < gil . fine @ intel . com >
* Mika Westerberg < mika . westerberg @ linux . intel . com >
*/
# include <linux/module.h>
# include "tb.h"
static bool clx_enabled = true ;
module_param_named ( clx , clx_enabled , bool , 0444 ) ;
MODULE_PARM_DESC ( clx , " allow low power states on the high-speed lanes (default: true) " ) ;
static int tb_port_pm_secondary_set ( struct tb_port * port , bool secondary )
{
u32 phy ;
int ret ;
ret = tb_port_read ( port , & phy , TB_CFG_PORT ,
port - > cap_phy + LANE_ADP_CS_1 , 1 ) ;
if ( ret )
return ret ;
if ( secondary )
phy | = LANE_ADP_CS_1_PMS ;
else
phy & = ~ LANE_ADP_CS_1_PMS ;
return tb_port_write ( port , & phy , TB_CFG_PORT ,
port - > cap_phy + LANE_ADP_CS_1 , 1 ) ;
}
static int tb_port_pm_secondary_enable ( struct tb_port * port )
{
return tb_port_pm_secondary_set ( port , true ) ;
}
static int tb_port_pm_secondary_disable ( struct tb_port * port )
{
return tb_port_pm_secondary_set ( port , false ) ;
}
/* Called for USB4 or Titan Ridge routers only */
static bool tb_port_clx_supported ( struct tb_port * port , unsigned int clx_mask )
{
u32 val , mask = 0 ;
bool ret ;
/* Don't enable CLx in case of two single-lane links */
if ( ! port - > bonded & & port - > dual_link_port )
return false ;
/* Don't enable CLx in case of inter-domain link */
if ( port - > xdomain )
return false ;
if ( tb_switch_is_usb4 ( port - > sw ) ) {
if ( ! usb4_port_clx_supported ( port ) )
return false ;
} else if ( ! tb_lc_is_clx_supported ( port ) ) {
return false ;
}
if ( clx_mask & TB_CL1 ) {
/* CL0s and CL1 are enabled and supported together */
mask | = LANE_ADP_CS_0_CL0S_SUPPORT | LANE_ADP_CS_0_CL1_SUPPORT ;
}
if ( clx_mask & TB_CL2 )
mask | = LANE_ADP_CS_0_CL2_SUPPORT ;
ret = tb_port_read ( port , & val , TB_CFG_PORT ,
port - > cap_phy + LANE_ADP_CS_0 , 1 ) ;
if ( ret )
return false ;
return ! ! ( val & mask ) ;
}
static int tb_port_clx_set ( struct tb_port * port , enum tb_clx clx , bool enable )
{
u32 phy , mask ;
int ret ;
/* CL0s and CL1 are enabled and supported together */
if ( clx = = TB_CL1 )
mask = LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE ;
else
/* For now we support only CL0s and CL1. Not CL2 */
return - EOPNOTSUPP ;
ret = tb_port_read ( port , & phy , TB_CFG_PORT ,
port - > cap_phy + LANE_ADP_CS_1 , 1 ) ;
if ( ret )
return ret ;
if ( enable )
phy | = mask ;
else
phy & = ~ mask ;
return tb_port_write ( port , & phy , TB_CFG_PORT ,
port - > cap_phy + LANE_ADP_CS_1 , 1 ) ;
}
static int tb_port_clx_disable ( struct tb_port * port , enum tb_clx clx )
{
return tb_port_clx_set ( port , clx , false ) ;
}
static int tb_port_clx_enable ( struct tb_port * port , enum tb_clx clx )
{
return tb_port_clx_set ( port , clx , true ) ;
}
/**
* tb_port_clx_is_enabled ( ) - Is given CL state enabled
* @ port : USB4 port to check
* @ clx_mask : Mask of CL states to check
*
* Returns true if any of the given CL states is enabled for @ port .
*/
bool tb_port_clx_is_enabled ( struct tb_port * port , unsigned int clx_mask )
{
u32 val , mask = 0 ;
int ret ;
if ( ! tb_port_clx_supported ( port , clx_mask ) )
return false ;
if ( clx_mask & TB_CL1 )
mask | = LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE ;
if ( clx_mask & TB_CL2 )
mask | = LANE_ADP_CS_1_CL2_ENABLE ;
ret = tb_port_read ( port , & val , TB_CFG_PORT ,
port - > cap_phy + LANE_ADP_CS_1 , 1 ) ;
if ( ret )
return false ;
return ! ! ( val & mask ) ;
}
static int tb_switch_pm_secondary_resolve ( struct tb_switch * sw )
{
struct tb_port * up , * down ;
int ret ;
if ( ! tb_route ( sw ) )
return 0 ;
up = tb_upstream_port ( sw ) ;
down = tb_switch_downstream_port ( sw ) ;
ret = tb_port_pm_secondary_enable ( up ) ;
if ( ret )
return ret ;
return tb_port_pm_secondary_disable ( down ) ;
}
static int tb_switch_mask_clx_objections ( struct tb_switch * sw )
{
int up_port = sw - > config . upstream_port_number ;
u32 offset , val [ 2 ] , mask_obj , unmask_obj ;
int ret , i ;
/* Only Titan Ridge of pre-USB4 devices support CLx states */
if ( ! tb_switch_is_titan_ridge ( sw ) )
return 0 ;
if ( ! tb_route ( sw ) )
return 0 ;
/*
* In Titan Ridge there are only 2 dual - lane Thunderbolt ports :
* Port A consists of lane adapters 1 , 2 and
* Port B consists of lane adapters 3 , 4
* If upstream port is A , ( lanes are 1 , 2 ) , we mask objections from
* port B ( lanes 3 , 4 ) and unmask objections from Port A and vice - versa .
*/
if ( up_port = = 1 ) {
mask_obj = TB_LOW_PWR_C0_PORT_B_MASK ;
unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK ;
offset = TB_LOW_PWR_C1_CL1 ;
} else {
mask_obj = TB_LOW_PWR_C1_PORT_A_MASK ;
unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK ;
offset = TB_LOW_PWR_C3_CL1 ;
}
ret = tb_sw_read ( sw , & val , TB_CFG_SWITCH ,
sw - > cap_lp + offset , ARRAY_SIZE ( val ) ) ;
if ( ret )
return ret ;
for ( i = 0 ; i < ARRAY_SIZE ( val ) ; i + + ) {
val [ i ] | = mask_obj ;
val [ i ] & = ~ unmask_obj ;
}
return tb_sw_write ( sw , & val , TB_CFG_SWITCH ,
sw - > cap_lp + offset , ARRAY_SIZE ( val ) ) ;
}
2022-10-10 12:10:33 +03:00
/**
* tb_switch_clx_enable ( ) - Enable CLx on upstream port of specified router
* @ sw : Router to enable CLx for
* @ clx : The CLx state to enable
*
* Enable CLx state only for first hop router . That is the most common
* use - case , that is intended for better thermal management , and so helps
* to improve performance . CLx is enabled only if both sides of the link
* support CLx , and if both sides of the link are not configured as two
* single lane links and only if the link is not inter - domain link . The
* complete set of conditions is described in CM Guide 1.0 section 8.1 .
*
* Return : Returns 0 on success or an error code on failure .
*/
int tb_switch_clx_enable ( struct tb_switch * sw , enum tb_clx clx )
2022-10-07 18:12:02 +03:00
{
2022-10-10 12:10:33 +03:00
struct tb_switch * root_sw = sw - > tb - > root_switch ;
2022-10-07 18:12:02 +03:00
bool up_clx_support , down_clx_support ;
struct tb_port * up , * down ;
int ret ;
2022-10-10 12:10:33 +03:00
if ( ! clx_enabled )
return 0 ;
/*
* CLx is not enabled and validated on Intel USB4 platforms before
* Alder Lake .
*/
if ( root_sw - > generation < 4 | | tb_switch_is_tiger_lake ( root_sw ) )
return 0 ;
switch ( clx ) {
case TB_CL1 :
/* CL0s and CL1 are enabled and supported together */
break ;
default :
return - EOPNOTSUPP ;
}
2022-10-07 18:12:02 +03:00
if ( ! tb_switch_clx_is_supported ( sw ) )
return 0 ;
/*
* Enable CLx for host router ' s downstream port as part of the
* downstream router enabling procedure .
*/
if ( ! tb_route ( sw ) )
return 0 ;
/* Enable CLx only for first hop router (depth = 1) */
if ( tb_route ( tb_switch_parent ( sw ) ) )
return 0 ;
ret = tb_switch_pm_secondary_resolve ( sw ) ;
if ( ret )
return ret ;
up = tb_upstream_port ( sw ) ;
down = tb_switch_downstream_port ( sw ) ;
up_clx_support = tb_port_clx_supported ( up , clx ) ;
down_clx_support = tb_port_clx_supported ( down , clx ) ;
tb_port_dbg ( up , " %s %ssupported \n " , tb_switch_clx_name ( clx ) ,
up_clx_support ? " " : " not " ) ;
tb_port_dbg ( down , " %s %ssupported \n " , tb_switch_clx_name ( clx ) ,
down_clx_support ? " " : " not " ) ;
if ( ! up_clx_support | | ! down_clx_support )
return - EOPNOTSUPP ;
ret = tb_port_clx_enable ( up , clx ) ;
if ( ret )
return ret ;
ret = tb_port_clx_enable ( down , clx ) ;
if ( ret ) {
tb_port_clx_disable ( up , clx ) ;
return ret ;
}
ret = tb_switch_mask_clx_objections ( sw ) ;
if ( ret ) {
tb_port_clx_disable ( up , clx ) ;
tb_port_clx_disable ( down , clx ) ;
return ret ;
}
sw - > clx = clx ;
tb_port_dbg ( up , " %s enabled \n " , tb_switch_clx_name ( clx ) ) ;
return 0 ;
}
/**
2022-10-10 12:10:33 +03:00
* tb_switch_clx_disable ( ) - Disable CLx on upstream port of specified router
* @ sw : Router to disable CLx for
* @ clx : The CLx state to disable
2022-10-07 18:12:02 +03:00
*
* Return : Returns 0 on success or an error code on failure .
*/
2022-10-10 12:10:33 +03:00
int tb_switch_clx_disable ( struct tb_switch * sw , enum tb_clx clx )
2022-10-07 18:12:02 +03:00
{
2022-10-10 12:10:33 +03:00
struct tb_port * up , * down ;
int ret ;
2022-10-07 18:12:02 +03:00
if ( ! clx_enabled )
return 0 ;
switch ( clx ) {
case TB_CL1 :
/* CL0s and CL1 are enabled and supported together */
2022-10-10 12:10:33 +03:00
break ;
2022-10-07 18:12:02 +03:00
default :
return - EOPNOTSUPP ;
}
if ( ! tb_switch_clx_is_supported ( sw ) )
return 0 ;
/*
* Disable CLx for host router ' s downstream port as part of the
* downstream router enabling procedure .
*/
if ( ! tb_route ( sw ) )
return 0 ;
/* Disable CLx only for first hop router (depth = 1) */
if ( tb_route ( tb_switch_parent ( sw ) ) )
return 0 ;
up = tb_upstream_port ( sw ) ;
down = tb_switch_downstream_port ( sw ) ;
ret = tb_port_clx_disable ( up , clx ) ;
if ( ret )
return ret ;
ret = tb_port_clx_disable ( down , clx ) ;
if ( ret )
return ret ;
sw - > clx = TB_CLX_DISABLE ;
tb_port_dbg ( up , " %s disabled \n " , tb_switch_clx_name ( clx ) ) ;
return 0 ;
}