162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * NHI specific operations 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019, Intel Corporation 662306a36Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/suspend.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "nhi.h" 1362306a36Sopenharmony_ci#include "nhi_regs.h" 1462306a36Sopenharmony_ci#include "tb.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/* Ice Lake specific NHI operations */ 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic int check_for_device(struct device *dev, void *data) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci return tb_is_switch(dev); 2362306a36Sopenharmony_ci} 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic bool icl_nhi_is_device_connected(struct tb_nhi *nhi) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci struct tb *tb = pci_get_drvdata(nhi->pdev); 2862306a36Sopenharmony_ci int ret; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci ret = device_for_each_child(&tb->root_switch->dev, NULL, 3162306a36Sopenharmony_ci check_for_device); 3262306a36Sopenharmony_ci return ret > 0; 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic int icl_nhi_force_power(struct tb_nhi *nhi, bool power) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci u32 vs_cap; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* 4062306a36Sopenharmony_ci * The Thunderbolt host controller is present always in Ice Lake 4162306a36Sopenharmony_ci * but the firmware may not be loaded and running (depending 4262306a36Sopenharmony_ci * whether there is device connected and so on). Each time the 4362306a36Sopenharmony_ci * controller is used we need to "Force Power" it first and wait 4462306a36Sopenharmony_ci * for the firmware to indicate it is up and running. This "Force 4562306a36Sopenharmony_ci * Power" is really not about actually powering on/off the 4662306a36Sopenharmony_ci * controller so it is accessible even if "Force Power" is off. 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci * The actual power management happens inside shared ACPI power 4962306a36Sopenharmony_ci * resources using standard ACPI methods. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_ci pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap); 5262306a36Sopenharmony_ci if (power) { 5362306a36Sopenharmony_ci vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; 5462306a36Sopenharmony_ci vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; 5562306a36Sopenharmony_ci vs_cap |= VS_CAP_22_FORCE_POWER; 5662306a36Sopenharmony_ci } else { 5762306a36Sopenharmony_ci vs_cap &= ~VS_CAP_22_FORCE_POWER; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (power) { 6262306a36Sopenharmony_ci unsigned int retries = 350; 6362306a36Sopenharmony_ci u32 val; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* Wait until the firmware tells it is up and running */ 6662306a36Sopenharmony_ci do { 6762306a36Sopenharmony_ci pci_read_config_dword(nhi->pdev, VS_CAP_9, &val); 6862306a36Sopenharmony_ci if (val & VS_CAP_9_FW_READY) 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci usleep_range(3000, 3100); 7162306a36Sopenharmony_ci } while (--retries); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return -ETIMEDOUT; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return 0; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci u32 data; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; 8462306a36Sopenharmony_ci pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci unsigned long end; 9062306a36Sopenharmony_ci u32 data; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (!timeout) 9362306a36Sopenharmony_ci goto clear; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci end = jiffies + msecs_to_jiffies(timeout); 9662306a36Sopenharmony_ci do { 9762306a36Sopenharmony_ci pci_read_config_dword(nhi->pdev, VS_CAP_18, &data); 9862306a36Sopenharmony_ci if (data & VS_CAP_18_DONE) 9962306a36Sopenharmony_ci goto clear; 10062306a36Sopenharmony_ci usleep_range(1000, 1100); 10162306a36Sopenharmony_ci } while (time_before(jiffies, end)); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return -ETIMEDOUT; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ciclear: 10662306a36Sopenharmony_ci /* Clear the valid bit */ 10762306a36Sopenharmony_ci pci_write_config_dword(nhi->pdev, VS_CAP_19, 0); 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic void icl_nhi_set_ltr(struct tb_nhi *nhi) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci u32 max_ltr, ltr; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr); 11662306a36Sopenharmony_ci max_ltr &= 0xffff; 11762306a36Sopenharmony_ci /* Program the same value for both snoop and no-snoop */ 11862306a36Sopenharmony_ci ltr = max_ltr << 16 | max_ltr; 11962306a36Sopenharmony_ci pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int icl_nhi_suspend(struct tb_nhi *nhi) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci struct tb *tb = pci_get_drvdata(nhi->pdev); 12562306a36Sopenharmony_ci int ret; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (icl_nhi_is_device_connected(nhi)) 12862306a36Sopenharmony_ci return 0; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (tb_switch_is_icm(tb->root_switch)) { 13162306a36Sopenharmony_ci /* 13262306a36Sopenharmony_ci * If there is no device connected we need to perform 13362306a36Sopenharmony_ci * both: a handshake through LC mailbox and force power 13462306a36Sopenharmony_ci * down before entering D3. 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_ci icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); 13762306a36Sopenharmony_ci ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); 13862306a36Sopenharmony_ci if (ret) 13962306a36Sopenharmony_ci return ret; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return icl_nhi_force_power(nhi, false); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct tb *tb = pci_get_drvdata(nhi->pdev); 14862306a36Sopenharmony_ci enum icl_lc_mailbox_cmd cmd; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (!pm_suspend_via_firmware()) 15162306a36Sopenharmony_ci return icl_nhi_suspend(nhi); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (!tb_switch_is_icm(tb->root_switch)) 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; 15762306a36Sopenharmony_ci icl_nhi_lc_mailbox_cmd(nhi, cmd); 15862306a36Sopenharmony_ci return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int icl_nhi_resume(struct tb_nhi *nhi) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci int ret; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ret = icl_nhi_force_power(nhi, true); 16662306a36Sopenharmony_ci if (ret) 16762306a36Sopenharmony_ci return ret; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci icl_nhi_set_ltr(nhi); 17062306a36Sopenharmony_ci return 0; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic void icl_nhi_shutdown(struct tb_nhi *nhi) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci icl_nhi_force_power(nhi, false); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ciconst struct tb_nhi_ops icl_nhi_ops = { 17962306a36Sopenharmony_ci .init = icl_nhi_resume, 18062306a36Sopenharmony_ci .suspend_noirq = icl_nhi_suspend_noirq, 18162306a36Sopenharmony_ci .resume_noirq = icl_nhi_resume, 18262306a36Sopenharmony_ci .runtime_suspend = icl_nhi_suspend, 18362306a36Sopenharmony_ci .runtime_resume = icl_nhi_resume, 18462306a36Sopenharmony_ci .shutdown = icl_nhi_shutdown, 18562306a36Sopenharmony_ci}; 186