// SPDX-License-Identifier: GPL-2.0 /* * NHI specific operations * * Copyright (C) 2019, Intel Corporation * Author: Mika Westerberg */ #include #include #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, };