2018-01-16 23:19:00 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* NHI specific operations
*
* Copyright ( C ) 2019 , Intel Corporation
* Author : Mika Westerberg < mika . westerberg @ linux . intel . com >
*/
# include <linux/delay.h>
# include <linux/suspend.h>
# include "nhi.h"
# include "nhi_regs.h"
# include "tb.h"
/* Ice Lake specific NHI operations */
# define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */
static int check_for_device ( struct device * dev , void * data )
{
return tb_is_switch ( dev ) ;
}
static bool icl_nhi_is_device_connected ( struct tb_nhi * nhi )
{
struct tb * tb = pci_get_drvdata ( nhi - > pdev ) ;
int ret ;
ret = device_for_each_child ( & tb - > root_switch - > dev , NULL ,
check_for_device ) ;
return ret > 0 ;
}
static int icl_nhi_force_power ( struct tb_nhi * nhi , bool power )
{
u32 vs_cap ;
/*
* The Thunderbolt host controller is present always in Ice Lake
* but the firmware may not be loaded and running ( depending
* whether there is device connected and so on ) . Each time the
* controller is used we need to " Force Power " it first and wait
* for the firmware to indicate it is up and running . This " Force
* Power " is really not about actually powering on/off the
* controller so it is accessible even if " Force Power " is off .
*
* The actual power management happens inside shared ACPI power
* resources using standard ACPI methods .
*/
pci_read_config_dword ( nhi - > pdev , VS_CAP_22 , & vs_cap ) ;
if ( power ) {
vs_cap & = ~ VS_CAP_22_DMA_DELAY_MASK ;
vs_cap | = 0x22 < < VS_CAP_22_DMA_DELAY_SHIFT ;
vs_cap | = VS_CAP_22_FORCE_POWER ;
} else {
vs_cap & = ~ VS_CAP_22_FORCE_POWER ;
}
pci_write_config_dword ( nhi - > pdev , VS_CAP_22 , vs_cap ) ;
if ( power ) {
2020-08-17 18:55:20 +03:00
unsigned int retries = 350 ;
2018-01-16 23:19:00 +03:00
u32 val ;
/* Wait until the firmware tells it is up and running */
do {
pci_read_config_dword ( nhi - > pdev , VS_CAP_9 , & val ) ;
if ( val & VS_CAP_9_FW_READY )
return 0 ;
2020-08-17 18:55:20 +03:00
usleep_range ( 3000 , 3100 ) ;
2018-01-16 23:19:00 +03:00
} while ( - - retries ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
static void icl_nhi_lc_mailbox_cmd ( struct tb_nhi * nhi , enum icl_lc_mailbox_cmd cmd )
{
u32 data ;
data = ( cmd < < VS_CAP_19_CMD_SHIFT ) & VS_CAP_19_CMD_MASK ;
pci_write_config_dword ( nhi - > pdev , VS_CAP_19 , data | VS_CAP_19_VALID ) ;
}
static int icl_nhi_lc_mailbox_cmd_complete ( struct tb_nhi * nhi , int timeout )
{
unsigned long end ;
u32 data ;
if ( ! timeout )
goto clear ;
end = jiffies + msecs_to_jiffies ( timeout ) ;
do {
pci_read_config_dword ( nhi - > pdev , VS_CAP_18 , & data ) ;
if ( data & VS_CAP_18_DONE )
goto clear ;
2020-08-17 18:55:21 +03:00
usleep_range ( 1000 , 1100 ) ;
2018-01-16 23:19:00 +03:00
} while ( time_before ( jiffies , end ) ) ;
return - ETIMEDOUT ;
clear :
/* Clear the valid bit */
pci_write_config_dword ( nhi - > pdev , VS_CAP_19 , 0 ) ;
return 0 ;
}
static void icl_nhi_set_ltr ( struct tb_nhi * nhi )
{
u32 max_ltr , ltr ;
pci_read_config_dword ( nhi - > pdev , VS_CAP_16 , & max_ltr ) ;
max_ltr & = 0xffff ;
/* Program the same value for both snoop and no-snoop */
ltr = max_ltr < < 16 | max_ltr ;
pci_write_config_dword ( nhi - > pdev , VS_CAP_15 , ltr ) ;
}
static int icl_nhi_suspend ( struct tb_nhi * nhi )
{
2020-07-23 16:43:00 +03:00
struct tb * tb = pci_get_drvdata ( nhi - > pdev ) ;
2018-01-16 23:19:00 +03:00
int ret ;
if ( icl_nhi_is_device_connected ( nhi ) )
return 0 ;
2020-07-23 16:43:00 +03:00
if ( tb_switch_is_icm ( tb - > root_switch ) ) {
/*
* If there is no device connected we need to perform
* both : a handshake through LC mailbox and force power
* down before entering D3 .
*/
icl_nhi_lc_mailbox_cmd ( nhi , ICL_LC_PREPARE_FOR_RESET ) ;
ret = icl_nhi_lc_mailbox_cmd_complete ( nhi , ICL_LC_MAILBOX_TIMEOUT ) ;
if ( ret )
return ret ;
}
2018-01-16 23:19:00 +03:00
return icl_nhi_force_power ( nhi , false ) ;
}
static int icl_nhi_suspend_noirq ( struct tb_nhi * nhi , bool wakeup )
{
2020-07-23 16:43:00 +03:00
struct tb * tb = pci_get_drvdata ( nhi - > pdev ) ;
2018-01-16 23:19:00 +03:00
enum icl_lc_mailbox_cmd cmd ;
if ( ! pm_suspend_via_firmware ( ) )
return icl_nhi_suspend ( nhi ) ;
2020-07-23 16:43:00 +03:00
if ( ! tb_switch_is_icm ( tb - > root_switch ) )
return 0 ;
2018-01-16 23:19:00 +03:00
cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE ;
icl_nhi_lc_mailbox_cmd ( nhi , cmd ) ;
return icl_nhi_lc_mailbox_cmd_complete ( nhi , ICL_LC_MAILBOX_TIMEOUT ) ;
}
static int icl_nhi_resume ( struct tb_nhi * nhi )
{
int ret ;
ret = icl_nhi_force_power ( nhi , true ) ;
if ( ret )
return ret ;
icl_nhi_set_ltr ( nhi ) ;
return 0 ;
}
static void icl_nhi_shutdown ( struct tb_nhi * nhi )
{
icl_nhi_force_power ( nhi , false ) ;
}
const struct tb_nhi_ops icl_nhi_ops = {
. init = icl_nhi_resume ,
. suspend_noirq = icl_nhi_suspend_noirq ,
. resume_noirq = icl_nhi_resume ,
. runtime_suspend = icl_nhi_suspend ,
. runtime_resume = icl_nhi_resume ,
. shutdown = icl_nhi_shutdown ,
} ;