123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * NHI specific operations
- *
- * Copyright (C) 2019, Intel Corporation
- * Author: Mika Westerberg <[email protected]>
- */
- #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) {
- unsigned int retries = 350;
- 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;
- usleep_range(3000, 3100);
- } 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;
- usleep_range(1000, 1100);
- } 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)
- {
- struct tb *tb = pci_get_drvdata(nhi->pdev);
- int ret;
- if (icl_nhi_is_device_connected(nhi))
- return 0;
- 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;
- }
- return icl_nhi_force_power(nhi, false);
- }
- static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
- {
- struct tb *tb = pci_get_drvdata(nhi->pdev);
- enum icl_lc_mailbox_cmd cmd;
- if (!pm_suspend_via_firmware())
- return icl_nhi_suspend(nhi);
- if (!tb_switch_is_icm(tb->root_switch))
- return 0;
- 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,
- };
|