Commit | Line | Data |
---|---|---|
1a1a6a69 | 1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
b96f16bd | 2 | // Copyright(c) 2015-2023 Intel Corporation |
1a1a6a69 PLB |
3 | |
4 | #include <linux/acpi.h> | |
5 | #include <linux/soundwire/sdw_registers.h> | |
6 | #include <linux/soundwire/sdw.h> | |
7 | #include <linux/soundwire/sdw_intel.h> | |
8 | #include "cadence_master.h" | |
9 | #include "bus.h" | |
10 | #include "intel.h" | |
11 | ||
12 | int intel_start_bus(struct sdw_intel *sdw) | |
13 | { | |
14 | struct device *dev = sdw->cdns.dev; | |
15 | struct sdw_cdns *cdns = &sdw->cdns; | |
16 | struct sdw_bus *bus = &cdns->bus; | |
17 | int ret; | |
18 | ||
1a1a6a69 PLB |
19 | /* |
20 | * follow recommended programming flows to avoid timeouts when | |
21 | * gsync is enabled | |
22 | */ | |
23 | if (bus->multi_link) | |
24 | sdw_intel_sync_arm(sdw); | |
25 | ||
26 | ret = sdw_cdns_init(cdns); | |
27 | if (ret < 0) { | |
28 | dev_err(dev, "%s: unable to initialize Cadence IP: %d\n", __func__, ret); | |
46b56a5c | 29 | return ret; |
1a1a6a69 PLB |
30 | } |
31 | ||
ffc363d9 | 32 | sdw_cdns_config_update(cdns); |
1a1a6a69 PLB |
33 | |
34 | if (bus->multi_link) { | |
35 | ret = sdw_intel_sync_go(sdw); | |
36 | if (ret < 0) { | |
37 | dev_err(dev, "%s: sync go failed: %d\n", __func__, ret); | |
46b56a5c | 38 | return ret; |
1a1a6a69 PLB |
39 | } |
40 | } | |
46b56a5c | 41 | |
ffc363d9 PLB |
42 | ret = sdw_cdns_config_update_set_wait(cdns); |
43 | if (ret < 0) { | |
44 | dev_err(dev, "%s: CONFIG_UPDATE BIT still set\n", __func__); | |
45 | return ret; | |
46 | } | |
47 | ||
5aedb8d8 | 48 | ret = sdw_cdns_enable_interrupt(cdns, true); |
ffc363d9 | 49 | if (ret < 0) { |
5aedb8d8 | 50 | dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret); |
ffc363d9 PLB |
51 | return ret; |
52 | } | |
53 | ||
5aedb8d8 | 54 | ret = sdw_cdns_exit_reset(cdns); |
46b56a5c | 55 | if (ret < 0) { |
5aedb8d8 | 56 | dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret); |
46b56a5c PLB |
57 | return ret; |
58 | } | |
59 | ||
1a1a6a69 PLB |
60 | sdw_cdns_check_self_clearing_bits(cdns, __func__, |
61 | true, INTEL_MASTER_RESET_ITERATIONS); | |
62 | ||
f8c35d61 PLB |
63 | schedule_delayed_work(&cdns->attach_dwork, |
64 | msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS)); | |
65 | ||
1a1a6a69 | 66 | return 0; |
1a1a6a69 PLB |
67 | } |
68 | ||
69 | int intel_start_bus_after_reset(struct sdw_intel *sdw) | |
70 | { | |
71 | struct device *dev = sdw->cdns.dev; | |
72 | struct sdw_cdns *cdns = &sdw->cdns; | |
73 | struct sdw_bus *bus = &cdns->bus; | |
74 | bool clock_stop0; | |
75 | int status; | |
76 | int ret; | |
77 | ||
78 | /* | |
79 | * An exception condition occurs for the CLK_STOP_BUS_RESET | |
80 | * case if one or more masters remain active. In this condition, | |
81 | * all the masters are powered on for they are in the same power | |
82 | * domain. Master can preserve its context for clock stop0, so | |
83 | * there is no need to clear slave status and reset bus. | |
84 | */ | |
85 | clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns); | |
86 | ||
87 | if (!clock_stop0) { | |
88 | ||
89 | /* | |
90 | * make sure all Slaves are tagged as UNATTACHED and | |
91 | * provide reason for reinitialization | |
92 | */ | |
93 | ||
94 | status = SDW_UNATTACH_REQUEST_MASTER_RESET; | |
95 | sdw_clear_slave_status(bus, status); | |
96 | ||
1a1a6a69 PLB |
97 | /* |
98 | * follow recommended programming flows to avoid | |
99 | * timeouts when gsync is enabled | |
100 | */ | |
101 | if (bus->multi_link) | |
102 | sdw_intel_sync_arm(sdw); | |
103 | ||
104 | /* | |
105 | * Re-initialize the IP since it was powered-off | |
106 | */ | |
107 | sdw_cdns_init(&sdw->cdns); | |
108 | ||
109 | } else { | |
110 | ret = sdw_cdns_enable_interrupt(cdns, true); | |
111 | if (ret < 0) { | |
112 | dev_err(dev, "cannot enable interrupts during resume\n"); | |
113 | return ret; | |
114 | } | |
115 | } | |
116 | ||
117 | ret = sdw_cdns_clock_restart(cdns, !clock_stop0); | |
118 | if (ret < 0) { | |
119 | dev_err(dev, "unable to restart clock during resume\n"); | |
46b56a5c PLB |
120 | if (!clock_stop0) |
121 | sdw_cdns_enable_interrupt(cdns, false); | |
122 | return ret; | |
1a1a6a69 PLB |
123 | } |
124 | ||
125 | if (!clock_stop0) { | |
ffc363d9 | 126 | sdw_cdns_config_update(cdns); |
1a1a6a69 PLB |
127 | |
128 | if (bus->multi_link) { | |
129 | ret = sdw_intel_sync_go(sdw); | |
130 | if (ret < 0) { | |
131 | dev_err(sdw->cdns.dev, "sync go failed during resume\n"); | |
46b56a5c | 132 | return ret; |
1a1a6a69 PLB |
133 | } |
134 | } | |
46b56a5c | 135 | |
ffc363d9 PLB |
136 | ret = sdw_cdns_config_update_set_wait(cdns); |
137 | if (ret < 0) { | |
138 | dev_err(dev, "%s: CONFIG_UPDATE BIT still set\n", __func__); | |
139 | return ret; | |
140 | } | |
141 | ||
5aedb8d8 | 142 | ret = sdw_cdns_enable_interrupt(cdns, true); |
ffc363d9 | 143 | if (ret < 0) { |
5aedb8d8 | 144 | dev_err(dev, "cannot enable interrupts during resume\n"); |
ffc363d9 PLB |
145 | return ret; |
146 | } | |
147 | ||
5aedb8d8 | 148 | ret = sdw_cdns_exit_reset(cdns); |
46b56a5c | 149 | if (ret < 0) { |
5aedb8d8 | 150 | dev_err(dev, "unable to exit bus reset sequence during resume\n"); |
46b56a5c PLB |
151 | return ret; |
152 | } | |
153 | ||
1a1a6a69 PLB |
154 | } |
155 | sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS); | |
156 | ||
f8c35d61 PLB |
157 | schedule_delayed_work(&cdns->attach_dwork, |
158 | msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS)); | |
159 | ||
1a1a6a69 | 160 | return 0; |
1a1a6a69 PLB |
161 | } |
162 | ||
163 | void intel_check_clock_stop(struct sdw_intel *sdw) | |
164 | { | |
165 | struct device *dev = sdw->cdns.dev; | |
166 | bool clock_stop0; | |
167 | ||
168 | clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns); | |
169 | if (!clock_stop0) | |
170 | dev_err(dev, "%s: invalid configuration, clock was not stopped\n", __func__); | |
171 | } | |
172 | ||
173 | int intel_start_bus_after_clock_stop(struct sdw_intel *sdw) | |
174 | { | |
175 | struct device *dev = sdw->cdns.dev; | |
176 | struct sdw_cdns *cdns = &sdw->cdns; | |
177 | int ret; | |
178 | ||
46b56a5c | 179 | ret = sdw_cdns_clock_restart(cdns, false); |
1a1a6a69 | 180 | if (ret < 0) { |
46b56a5c | 181 | dev_err(dev, "%s: unable to restart clock: %d\n", __func__, ret); |
1a1a6a69 PLB |
182 | return ret; |
183 | } | |
184 | ||
46b56a5c | 185 | ret = sdw_cdns_enable_interrupt(cdns, true); |
1a1a6a69 | 186 | if (ret < 0) { |
46b56a5c | 187 | dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret); |
1a1a6a69 PLB |
188 | return ret; |
189 | } | |
190 | ||
ccc6cf15 | 191 | sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS); |
1a1a6a69 | 192 | |
f8c35d61 PLB |
193 | schedule_delayed_work(&cdns->attach_dwork, |
194 | msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS)); | |
195 | ||
1a1a6a69 PLB |
196 | return 0; |
197 | } | |
198 | ||
199 | int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop) | |
200 | { | |
201 | struct device *dev = sdw->cdns.dev; | |
202 | struct sdw_cdns *cdns = &sdw->cdns; | |
203 | bool wake_enable = false; | |
204 | int ret; | |
205 | ||
f8c35d61 PLB |
206 | cancel_delayed_work_sync(&cdns->attach_dwork); |
207 | ||
1a1a6a69 PLB |
208 | if (clock_stop) { |
209 | ret = sdw_cdns_clock_stop(cdns, true); | |
210 | if (ret < 0) | |
211 | dev_err(dev, "%s: cannot stop clock: %d\n", __func__, ret); | |
212 | else | |
213 | wake_enable = true; | |
214 | } | |
215 | ||
216 | ret = sdw_cdns_enable_interrupt(cdns, false); | |
217 | if (ret < 0) { | |
218 | dev_err(dev, "%s: cannot disable interrupts: %d\n", __func__, ret); | |
219 | return ret; | |
220 | } | |
221 | ||
222 | ret = sdw_intel_link_power_down(sdw); | |
223 | if (ret) { | |
224 | dev_err(dev, "%s: Link power down failed: %d\n", __func__, ret); | |
225 | return ret; | |
226 | } | |
227 | ||
228 | sdw_intel_shim_wake(sdw, wake_enable); | |
229 | ||
230 | return 0; | |
231 | } | |
fb43d62e PLB |
232 | |
233 | /* | |
234 | * bank switch routines | |
235 | */ | |
236 | ||
237 | int intel_pre_bank_switch(struct sdw_intel *sdw) | |
238 | { | |
239 | struct sdw_cdns *cdns = &sdw->cdns; | |
240 | struct sdw_bus *bus = &cdns->bus; | |
241 | ||
242 | /* Write to register only for multi-link */ | |
243 | if (!bus->multi_link) | |
244 | return 0; | |
245 | ||
246 | sdw_intel_sync_arm(sdw); | |
247 | ||
248 | return 0; | |
249 | } | |
250 | ||
251 | int intel_post_bank_switch(struct sdw_intel *sdw) | |
252 | { | |
253 | struct sdw_cdns *cdns = &sdw->cdns; | |
254 | struct sdw_bus *bus = &cdns->bus; | |
255 | int ret = 0; | |
256 | ||
257 | /* Write to register only for multi-link */ | |
258 | if (!bus->multi_link) | |
259 | return 0; | |
260 | ||
261 | mutex_lock(sdw->link_res->shim_lock); | |
262 | ||
263 | /* | |
264 | * post_bank_switch() ops is called from the bus in loop for | |
265 | * all the Masters in the steam with the expectation that | |
266 | * we trigger the bankswitch for the only first Master in the list | |
267 | * and do nothing for the other Masters | |
268 | * | |
269 | * So, set the SYNCGO bit only if CMDSYNC bit is set for any Master. | |
270 | */ | |
271 | if (sdw_intel_sync_check_cmdsync_unlocked(sdw)) | |
272 | ret = sdw_intel_sync_go_unlocked(sdw); | |
273 | ||
274 | mutex_unlock(sdw->link_res->shim_lock); | |
275 | ||
276 | if (ret < 0) | |
277 | dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret); | |
278 | ||
279 | return ret; | |
280 | } |