186 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * NHI specific operations4 *5 * Copyright (C) 2019, Intel Corporation6 * Author: Mika Westerberg <mika.westerberg@linux.intel.com>7 */8 9#include <linux/delay.h>10#include <linux/suspend.h>11 12#include "nhi.h"13#include "nhi_regs.h"14#include "tb.h"15 16/* Ice Lake specific NHI operations */17 18#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */19 20static int check_for_device(struct device *dev, void *data)21{22 return tb_is_switch(dev);23}24 25static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)26{27 struct tb *tb = pci_get_drvdata(nhi->pdev);28 int ret;29 30 ret = device_for_each_child(&tb->root_switch->dev, NULL,31 check_for_device);32 return ret > 0;33}34 35static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)36{37 u32 vs_cap;38 39 /*40 * The Thunderbolt host controller is present always in Ice Lake41 * but the firmware may not be loaded and running (depending42 * whether there is device connected and so on). Each time the43 * controller is used we need to "Force Power" it first and wait44 * for the firmware to indicate it is up and running. This "Force45 * Power" is really not about actually powering on/off the46 * controller so it is accessible even if "Force Power" is off.47 *48 * The actual power management happens inside shared ACPI power49 * resources using standard ACPI methods.50 */51 pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap);52 if (power) {53 vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;54 vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;55 vs_cap |= VS_CAP_22_FORCE_POWER;56 } else {57 vs_cap &= ~VS_CAP_22_FORCE_POWER;58 }59 pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);60 61 if (power) {62 unsigned int retries = 350;63 u32 val;64 65 /* Wait until the firmware tells it is up and running */66 do {67 pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);68 if (val & VS_CAP_9_FW_READY)69 return 0;70 usleep_range(3000, 3100);71 } while (--retries);72 73 return -ETIMEDOUT;74 }75 76 return 0;77}78 79static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)80{81 u32 data;82 83 data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;84 pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);85}86 87static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)88{89 unsigned long end;90 u32 data;91 92 if (!timeout)93 goto clear;94 95 end = jiffies + msecs_to_jiffies(timeout);96 do {97 pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);98 if (data & VS_CAP_18_DONE)99 goto clear;100 usleep_range(1000, 1100);101 } while (time_before(jiffies, end));102 103 return -ETIMEDOUT;104 105clear:106 /* Clear the valid bit */107 pci_write_config_dword(nhi->pdev, VS_CAP_19, 0);108 return 0;109}110 111static void icl_nhi_set_ltr(struct tb_nhi *nhi)112{113 u32 max_ltr, ltr;114 115 pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr);116 max_ltr &= 0xffff;117 /* Program the same value for both snoop and no-snoop */118 ltr = max_ltr << 16 | max_ltr;119 pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr);120}121 122static int icl_nhi_suspend(struct tb_nhi *nhi)123{124 struct tb *tb = pci_get_drvdata(nhi->pdev);125 int ret;126 127 if (icl_nhi_is_device_connected(nhi))128 return 0;129 130 if (tb_switch_is_icm(tb->root_switch)) {131 /*132 * If there is no device connected we need to perform133 * both: a handshake through LC mailbox and force power134 * down before entering D3.135 */136 icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);137 ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);138 if (ret)139 return ret;140 }141 142 return icl_nhi_force_power(nhi, false);143}144 145static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)146{147 struct tb *tb = pci_get_drvdata(nhi->pdev);148 enum icl_lc_mailbox_cmd cmd;149 150 if (!pm_suspend_via_firmware())151 return icl_nhi_suspend(nhi);152 153 if (!tb_switch_is_icm(tb->root_switch))154 return 0;155 156 cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;157 icl_nhi_lc_mailbox_cmd(nhi, cmd);158 return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);159}160 161static int icl_nhi_resume(struct tb_nhi *nhi)162{163 int ret;164 165 ret = icl_nhi_force_power(nhi, true);166 if (ret)167 return ret;168 169 icl_nhi_set_ltr(nhi);170 return 0;171}172 173static void icl_nhi_shutdown(struct tb_nhi *nhi)174{175 icl_nhi_force_power(nhi, false);176}177 178const struct tb_nhi_ops icl_nhi_ops = {179 .init = icl_nhi_resume,180 .suspend_noirq = icl_nhi_suspend_noirq,181 .resume_noirq = icl_nhi_resume,182 .runtime_suspend = icl_nhi_suspend,183 .runtime_resume = icl_nhi_resume,184 .shutdown = icl_nhi_shutdown,185};186