Commit | Line | Data |
---|---|---|
3cdb9446 MW |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * NHI specific operations | |
4 | * | |
5 | * Copyright (C) 2019, Intel Corporation | |
6 | * 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 | ||
20 | static int check_for_device(struct device *dev, void *data) | |
21 | { | |
22 | return tb_is_switch(dev); | |
23 | } | |
24 | ||
25 | static 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 | ||
35 | static 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 Lake | |
41 | * but the firmware may not be loaded and running (depending | |
42 | * whether there is device connected and so on). Each time the | |
43 | * controller is used we need to "Force Power" it first and wait | |
44 | * for the firmware to indicate it is up and running. This "Force | |
45 | * Power" is really not about actually powering on/off the | |
46 | * controller so it is accessible even if "Force Power" is off. | |
47 | * | |
48 | * The actual power management happens inside shared ACPI power | |
49 | * 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) { | |
dc4c4bf1 | 62 | unsigned int retries = 350; |
3cdb9446 MW |
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; | |
dc4c4bf1 | 70 | usleep_range(3000, 3100); |
3cdb9446 MW |
71 | } while (--retries); |
72 | ||
73 | return -ETIMEDOUT; | |
74 | } | |
75 | ||
76 | return 0; | |
77 | } | |
78 | ||
79 | static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) | |
80 | { | |
81 | u32 data; | |
82 | ||
3cdb9446 MW |
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 | ||
87 | static 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; | |
6651c91d | 100 | usleep_range(1000, 1100); |
3cdb9446 MW |
101 | } while (time_before(jiffies, end)); |
102 | ||
103 | return -ETIMEDOUT; | |
104 | ||
105 | clear: | |
106 | /* Clear the valid bit */ | |
107 | pci_write_config_dword(nhi->pdev, VS_CAP_19, 0); | |
108 | return 0; | |
109 | } | |
110 | ||
111 | static 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 | ||
122 | static int icl_nhi_suspend(struct tb_nhi *nhi) | |
123 | { | |
59ed8dca | 124 | struct tb *tb = pci_get_drvdata(nhi->pdev); |
3cdb9446 MW |
125 | int ret; |
126 | ||
127 | if (icl_nhi_is_device_connected(nhi)) | |
128 | return 0; | |
129 | ||
59ed8dca MW |
130 | if (tb_switch_is_icm(tb->root_switch)) { |
131 | /* | |
132 | * If there is no device connected we need to perform | |
133 | * both: a handshake through LC mailbox and force power | |
134 | * 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 | } | |
3cdb9446 MW |
141 | |
142 | return icl_nhi_force_power(nhi, false); | |
143 | } | |
144 | ||
145 | static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) | |
146 | { | |
59ed8dca | 147 | struct tb *tb = pci_get_drvdata(nhi->pdev); |
3cdb9446 MW |
148 | enum icl_lc_mailbox_cmd cmd; |
149 | ||
150 | if (!pm_suspend_via_firmware()) | |
151 | return icl_nhi_suspend(nhi); | |
152 | ||
59ed8dca MW |
153 | if (!tb_switch_is_icm(tb->root_switch)) |
154 | return 0; | |
155 | ||
3cdb9446 MW |
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 | ||
161 | static 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 | ||
173 | static void icl_nhi_shutdown(struct tb_nhi *nhi) | |
174 | { | |
175 | icl_nhi_force_power(nhi, false); | |
176 | } | |
177 | ||
178 | const 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 | }; |