13cdb9446SMika Westerberg // SPDX-License-Identifier: GPL-2.0
23cdb9446SMika Westerberg /*
33cdb9446SMika Westerberg * NHI specific operations
43cdb9446SMika Westerberg *
53cdb9446SMika Westerberg * Copyright (C) 2019, Intel Corporation
63cdb9446SMika Westerberg * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
73cdb9446SMika Westerberg */
83cdb9446SMika Westerberg
93cdb9446SMika Westerberg #include <linux/delay.h>
103cdb9446SMika Westerberg #include <linux/suspend.h>
113cdb9446SMika Westerberg
123cdb9446SMika Westerberg #include "nhi.h"
133cdb9446SMika Westerberg #include "nhi_regs.h"
143cdb9446SMika Westerberg #include "tb.h"
153cdb9446SMika Westerberg
163cdb9446SMika Westerberg /* Ice Lake specific NHI operations */
173cdb9446SMika Westerberg
183cdb9446SMika Westerberg #define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */
193cdb9446SMika Westerberg
check_for_device(struct device * dev,void * data)203cdb9446SMika Westerberg static int check_for_device(struct device *dev, void *data)
213cdb9446SMika Westerberg {
223cdb9446SMika Westerberg return tb_is_switch(dev);
233cdb9446SMika Westerberg }
243cdb9446SMika Westerberg
icl_nhi_is_device_connected(struct tb_nhi * nhi)253cdb9446SMika Westerberg static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
263cdb9446SMika Westerberg {
273cdb9446SMika Westerberg struct tb *tb = pci_get_drvdata(nhi->pdev);
283cdb9446SMika Westerberg int ret;
293cdb9446SMika Westerberg
303cdb9446SMika Westerberg ret = device_for_each_child(&tb->root_switch->dev, NULL,
313cdb9446SMika Westerberg check_for_device);
323cdb9446SMika Westerberg return ret > 0;
333cdb9446SMika Westerberg }
343cdb9446SMika Westerberg
icl_nhi_force_power(struct tb_nhi * nhi,bool power)353cdb9446SMika Westerberg static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
363cdb9446SMika Westerberg {
373cdb9446SMika Westerberg u32 vs_cap;
383cdb9446SMika Westerberg
393cdb9446SMika Westerberg /*
403cdb9446SMika Westerberg * The Thunderbolt host controller is present always in Ice Lake
413cdb9446SMika Westerberg * but the firmware may not be loaded and running (depending
423cdb9446SMika Westerberg * whether there is device connected and so on). Each time the
433cdb9446SMika Westerberg * controller is used we need to "Force Power" it first and wait
443cdb9446SMika Westerberg * for the firmware to indicate it is up and running. This "Force
453cdb9446SMika Westerberg * Power" is really not about actually powering on/off the
463cdb9446SMika Westerberg * controller so it is accessible even if "Force Power" is off.
473cdb9446SMika Westerberg *
483cdb9446SMika Westerberg * The actual power management happens inside shared ACPI power
493cdb9446SMika Westerberg * resources using standard ACPI methods.
503cdb9446SMika Westerberg */
513cdb9446SMika Westerberg pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap);
523cdb9446SMika Westerberg if (power) {
533cdb9446SMika Westerberg vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
543cdb9446SMika Westerberg vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
553cdb9446SMika Westerberg vs_cap |= VS_CAP_22_FORCE_POWER;
563cdb9446SMika Westerberg } else {
573cdb9446SMika Westerberg vs_cap &= ~VS_CAP_22_FORCE_POWER;
583cdb9446SMika Westerberg }
593cdb9446SMika Westerberg pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
603cdb9446SMika Westerberg
613cdb9446SMika Westerberg if (power) {
62dc4c4bf1SRajmohan Mani unsigned int retries = 350;
633cdb9446SMika Westerberg u32 val;
643cdb9446SMika Westerberg
653cdb9446SMika Westerberg /* Wait until the firmware tells it is up and running */
663cdb9446SMika Westerberg do {
673cdb9446SMika Westerberg pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
683cdb9446SMika Westerberg if (val & VS_CAP_9_FW_READY)
693cdb9446SMika Westerberg return 0;
70dc4c4bf1SRajmohan Mani usleep_range(3000, 3100);
713cdb9446SMika Westerberg } while (--retries);
723cdb9446SMika Westerberg
733cdb9446SMika Westerberg return -ETIMEDOUT;
743cdb9446SMika Westerberg }
753cdb9446SMika Westerberg
763cdb9446SMika Westerberg return 0;
773cdb9446SMika Westerberg }
783cdb9446SMika Westerberg
icl_nhi_lc_mailbox_cmd(struct tb_nhi * nhi,enum icl_lc_mailbox_cmd cmd)793cdb9446SMika Westerberg static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
803cdb9446SMika Westerberg {
813cdb9446SMika Westerberg u32 data;
823cdb9446SMika Westerberg
833cdb9446SMika Westerberg data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
843cdb9446SMika Westerberg pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);
853cdb9446SMika Westerberg }
863cdb9446SMika Westerberg
icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi * nhi,int timeout)873cdb9446SMika Westerberg static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
883cdb9446SMika Westerberg {
893cdb9446SMika Westerberg unsigned long end;
903cdb9446SMika Westerberg u32 data;
913cdb9446SMika Westerberg
923cdb9446SMika Westerberg if (!timeout)
933cdb9446SMika Westerberg goto clear;
943cdb9446SMika Westerberg
953cdb9446SMika Westerberg end = jiffies + msecs_to_jiffies(timeout);
963cdb9446SMika Westerberg do {
973cdb9446SMika Westerberg pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
983cdb9446SMika Westerberg if (data & VS_CAP_18_DONE)
993cdb9446SMika Westerberg goto clear;
1006651c91dSRajmohan Mani usleep_range(1000, 1100);
1013cdb9446SMika Westerberg } while (time_before(jiffies, end));
1023cdb9446SMika Westerberg
1033cdb9446SMika Westerberg return -ETIMEDOUT;
1043cdb9446SMika Westerberg
1053cdb9446SMika Westerberg clear:
1063cdb9446SMika Westerberg /* Clear the valid bit */
1073cdb9446SMika Westerberg pci_write_config_dword(nhi->pdev, VS_CAP_19, 0);
1083cdb9446SMika Westerberg return 0;
1093cdb9446SMika Westerberg }
1103cdb9446SMika Westerberg
icl_nhi_set_ltr(struct tb_nhi * nhi)1113cdb9446SMika Westerberg static void icl_nhi_set_ltr(struct tb_nhi *nhi)
1123cdb9446SMika Westerberg {
1133cdb9446SMika Westerberg u32 max_ltr, ltr;
1143cdb9446SMika Westerberg
1153cdb9446SMika Westerberg pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr);
1163cdb9446SMika Westerberg max_ltr &= 0xffff;
1173cdb9446SMika Westerberg /* Program the same value for both snoop and no-snoop */
1183cdb9446SMika Westerberg ltr = max_ltr << 16 | max_ltr;
1193cdb9446SMika Westerberg pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr);
1203cdb9446SMika Westerberg }
1213cdb9446SMika Westerberg
icl_nhi_suspend(struct tb_nhi * nhi)1223cdb9446SMika Westerberg static int icl_nhi_suspend(struct tb_nhi *nhi)
1233cdb9446SMika Westerberg {
12459ed8dcaSMika Westerberg struct tb *tb = pci_get_drvdata(nhi->pdev);
1253cdb9446SMika Westerberg int ret;
1263cdb9446SMika Westerberg
1273cdb9446SMika Westerberg if (icl_nhi_is_device_connected(nhi))
1283cdb9446SMika Westerberg return 0;
1293cdb9446SMika Westerberg
13059ed8dcaSMika Westerberg if (tb_switch_is_icm(tb->root_switch)) {
1313cdb9446SMika Westerberg /*
13259ed8dcaSMika Westerberg * If there is no device connected we need to perform
13359ed8dcaSMika Westerberg * both: a handshake through LC mailbox and force power
13459ed8dcaSMika Westerberg * down before entering D3.
1353cdb9446SMika Westerberg */
1363cdb9446SMika Westerberg icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
1373cdb9446SMika Westerberg ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
1383cdb9446SMika Westerberg if (ret)
1393cdb9446SMika Westerberg return ret;
14059ed8dcaSMika Westerberg }
1413cdb9446SMika Westerberg
1423cdb9446SMika Westerberg return icl_nhi_force_power(nhi, false);
1433cdb9446SMika Westerberg }
1443cdb9446SMika Westerberg
icl_nhi_suspend_noirq(struct tb_nhi * nhi,bool wakeup)1453cdb9446SMika Westerberg static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
1463cdb9446SMika Westerberg {
14759ed8dcaSMika Westerberg struct tb *tb = pci_get_drvdata(nhi->pdev);
1483cdb9446SMika Westerberg enum icl_lc_mailbox_cmd cmd;
1493cdb9446SMika Westerberg
1503cdb9446SMika Westerberg if (!pm_suspend_via_firmware())
1513cdb9446SMika Westerberg return icl_nhi_suspend(nhi);
1523cdb9446SMika Westerberg
15359ed8dcaSMika Westerberg if (!tb_switch_is_icm(tb->root_switch))
15459ed8dcaSMika Westerberg return 0;
15559ed8dcaSMika Westerberg
1563cdb9446SMika Westerberg cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
1573cdb9446SMika Westerberg icl_nhi_lc_mailbox_cmd(nhi, cmd);
1583cdb9446SMika Westerberg return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
1593cdb9446SMika Westerberg }
1603cdb9446SMika Westerberg
icl_nhi_resume(struct tb_nhi * nhi)1613cdb9446SMika Westerberg static int icl_nhi_resume(struct tb_nhi *nhi)
1623cdb9446SMika Westerberg {
1633cdb9446SMika Westerberg int ret;
1643cdb9446SMika Westerberg
1653cdb9446SMika Westerberg ret = icl_nhi_force_power(nhi, true);
1663cdb9446SMika Westerberg if (ret)
1673cdb9446SMika Westerberg return ret;
1683cdb9446SMika Westerberg
1693cdb9446SMika Westerberg icl_nhi_set_ltr(nhi);
1703cdb9446SMika Westerberg return 0;
1713cdb9446SMika Westerberg }
1723cdb9446SMika Westerberg
icl_nhi_shutdown(struct tb_nhi * nhi)1733cdb9446SMika Westerberg static void icl_nhi_shutdown(struct tb_nhi *nhi)
1743cdb9446SMika Westerberg {
1753cdb9446SMika Westerberg icl_nhi_force_power(nhi, false);
1763cdb9446SMika Westerberg }
1773cdb9446SMika Westerberg
1783cdb9446SMika Westerberg const struct tb_nhi_ops icl_nhi_ops = {
1793cdb9446SMika Westerberg .init = icl_nhi_resume,
1803cdb9446SMika Westerberg .suspend_noirq = icl_nhi_suspend_noirq,
1813cdb9446SMika Westerberg .resume_noirq = icl_nhi_resume,
1823cdb9446SMika Westerberg .runtime_suspend = icl_nhi_suspend,
1833cdb9446SMika Westerberg .runtime_resume = icl_nhi_resume,
1843cdb9446SMika Westerberg .shutdown = icl_nhi_shutdown,
1853cdb9446SMika Westerberg };
186