1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Wifi Frequency Band Manage Interface |
4 | * Copyright (C) 2023 Advanced Micro Devices |
5 | */ |
6 | |
7 | #include <linux/acpi.h> |
8 | #include <linux/acpi_amd_wbrf.h> |
9 | |
10 | /* |
11 | * Functions bit vector for WBRF method |
12 | * |
13 | * Bit 0: WBRF supported. |
14 | * Bit 1: Function 1 (Add / Remove frequency) is supported. |
15 | * Bit 2: Function 2 (Get frequency list) is supported. |
16 | */ |
17 | #define WBRF_ENABLED 0x0 |
18 | #define WBRF_RECORD 0x1 |
19 | #define WBRF_RETRIEVE 0x2 |
20 | |
21 | #define WBRF_REVISION 0x1 |
22 | |
23 | /* |
24 | * The data structure used for WBRF_RETRIEVE is not naturally aligned. |
25 | * And unfortunately the design has been settled down. |
26 | */ |
27 | struct amd_wbrf_ranges_out { |
28 | u32 num_of_ranges; |
29 | struct freq_band_range band_list[MAX_NUM_OF_WBRF_RANGES]; |
30 | } __packed; |
31 | |
32 | static const guid_t wifi_acpi_dsm_guid = |
33 | GUID_INIT(0x7b7656cf, 0xdc3d, 0x4c1c, |
34 | 0x83, 0xe9, 0x66, 0xe7, 0x21, 0xde, 0x30, 0x70); |
35 | |
36 | /* |
37 | * Used to notify consumer (amdgpu driver currently) about |
38 | * the wifi frequency is change. |
39 | */ |
40 | static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); |
41 | |
42 | static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in) |
43 | { |
44 | union acpi_object argv4; |
45 | union acpi_object *tmp; |
46 | union acpi_object *obj; |
47 | u32 num_of_ranges = 0; |
48 | u32 num_of_elements; |
49 | u32 arg_idx = 0; |
50 | int ret; |
51 | u32 i; |
52 | |
53 | if (!in) |
54 | return -EINVAL; |
55 | |
56 | for (i = 0; i < ARRAY_SIZE(in->band_list); i++) { |
57 | if (in->band_list[i].start && in->band_list[i].end) |
58 | num_of_ranges++; |
59 | } |
60 | |
61 | /* |
62 | * The num_of_ranges value in the "in" object supplied by |
63 | * the caller is required to be equal to the number of |
64 | * entries in the band_list array in there. |
65 | */ |
66 | if (num_of_ranges != in->num_of_ranges) |
67 | return -EINVAL; |
68 | |
69 | /* |
70 | * Every input frequency band comes with two end points(start/end) |
71 | * and each is accounted as an element. Meanwhile the range count |
72 | * and action type are accounted as an element each. |
73 | * So, the total element count = 2 * num_of_ranges + 1 + 1. |
74 | */ |
75 | num_of_elements = 2 * num_of_ranges + 2; |
76 | |
77 | tmp = kcalloc(n: num_of_elements, size: sizeof(*tmp), GFP_KERNEL); |
78 | if (!tmp) |
79 | return -ENOMEM; |
80 | |
81 | argv4.package.type = ACPI_TYPE_PACKAGE; |
82 | argv4.package.count = num_of_elements; |
83 | argv4.package.elements = tmp; |
84 | |
85 | /* save the number of ranges*/ |
86 | tmp[0].integer.type = ACPI_TYPE_INTEGER; |
87 | tmp[0].integer.value = num_of_ranges; |
88 | |
89 | /* save the action(WBRF_RECORD_ADD/REMOVE/RETRIEVE) */ |
90 | tmp[1].integer.type = ACPI_TYPE_INTEGER; |
91 | tmp[1].integer.value = action; |
92 | |
93 | arg_idx = 2; |
94 | for (i = 0; i < ARRAY_SIZE(in->band_list); i++) { |
95 | if (!in->band_list[i].start || !in->band_list[i].end) |
96 | continue; |
97 | |
98 | tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; |
99 | tmp[arg_idx++].integer.value = in->band_list[i].start; |
100 | tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; |
101 | tmp[arg_idx++].integer.value = in->band_list[i].end; |
102 | } |
103 | |
104 | obj = acpi_evaluate_dsm(handle: adev->handle, guid: &wifi_acpi_dsm_guid, |
105 | WBRF_REVISION, WBRF_RECORD, argv4: &argv4); |
106 | |
107 | if (!obj) |
108 | return -EINVAL; |
109 | |
110 | if (obj->type != ACPI_TYPE_INTEGER) { |
111 | ret = -EINVAL; |
112 | goto out; |
113 | } |
114 | |
115 | ret = obj->integer.value; |
116 | if (ret) |
117 | ret = -EINVAL; |
118 | |
119 | out: |
120 | ACPI_FREE(obj); |
121 | kfree(objp: tmp); |
122 | |
123 | return ret; |
124 | } |
125 | |
126 | /** |
127 | * acpi_amd_wbrf_add_remove - add or remove the frequency band the device is using |
128 | * |
129 | * @dev: device pointer |
130 | * @action: remove or add the frequency band into bios |
131 | * @in: input structure containing the frequency band the device is using |
132 | * |
133 | * Broadcast to other consumers the frequency band the device starts |
134 | * to use. Underneath the surface the information is cached into an |
135 | * internal buffer first. Then a notification is sent to all those |
136 | * registered consumers. So then they can retrieve that buffer to |
137 | * know the latest active frequency bands. Consumers that haven't |
138 | * yet been registered can retrieve the information from the cache |
139 | * when they register. |
140 | * |
141 | * Return: |
142 | * 0 for success add/remove wifi frequency band. |
143 | * Returns a negative error code for failure. |
144 | */ |
145 | int acpi_amd_wbrf_add_remove(struct device *dev, uint8_t action, struct wbrf_ranges_in_out *in) |
146 | { |
147 | struct acpi_device *adev; |
148 | int ret; |
149 | |
150 | adev = ACPI_COMPANION(dev); |
151 | if (!adev) |
152 | return -ENODEV; |
153 | |
154 | ret = wbrf_record(adev, action, in); |
155 | if (ret) |
156 | return ret; |
157 | |
158 | blocking_notifier_call_chain(nh: &wbrf_chain_head, val: WBRF_CHANGED, NULL); |
159 | |
160 | return 0; |
161 | } |
162 | EXPORT_SYMBOL_GPL(acpi_amd_wbrf_add_remove); |
163 | |
164 | /** |
165 | * acpi_amd_wbrf_supported_producer - determine if the WBRF can be enabled |
166 | * for the device as a producer |
167 | * |
168 | * @dev: device pointer |
169 | * |
170 | * Check if the platform equipped with necessary implementations to |
171 | * support WBRF for the device as a producer. |
172 | * |
173 | * Return: |
174 | * true if WBRF is supported, otherwise returns false |
175 | */ |
176 | bool acpi_amd_wbrf_supported_producer(struct device *dev) |
177 | { |
178 | struct acpi_device *adev; |
179 | |
180 | adev = ACPI_COMPANION(dev); |
181 | if (!adev) |
182 | return false; |
183 | |
184 | return acpi_check_dsm(handle: adev->handle, guid: &wifi_acpi_dsm_guid, |
185 | WBRF_REVISION, BIT(WBRF_RECORD)); |
186 | } |
187 | EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_producer); |
188 | |
189 | /** |
190 | * acpi_amd_wbrf_supported_consumer - determine if the WBRF can be enabled |
191 | * for the device as a consumer |
192 | * |
193 | * @dev: device pointer |
194 | * |
195 | * Determine if the platform equipped with necessary implementations to |
196 | * support WBRF for the device as a consumer. |
197 | * |
198 | * Return: |
199 | * true if WBRF is supported, otherwise returns false. |
200 | */ |
201 | bool acpi_amd_wbrf_supported_consumer(struct device *dev) |
202 | { |
203 | struct acpi_device *adev; |
204 | |
205 | adev = ACPI_COMPANION(dev); |
206 | if (!adev) |
207 | return false; |
208 | |
209 | return acpi_check_dsm(handle: adev->handle, guid: &wifi_acpi_dsm_guid, |
210 | WBRF_REVISION, BIT(WBRF_RETRIEVE)); |
211 | } |
212 | EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_consumer); |
213 | |
214 | /** |
215 | * amd_wbrf_retrieve_freq_band - retrieve current active frequency bands |
216 | * |
217 | * @dev: device pointer |
218 | * @out: output structure containing all the active frequency bands |
219 | * |
220 | * Retrieve the current active frequency bands which were broadcasted |
221 | * by other producers. The consumer who calls this API should take |
222 | * proper actions if any of the frequency band may cause RFI with its |
223 | * own frequency band used. |
224 | * |
225 | * Return: |
226 | * 0 for getting wifi freq band successfully. |
227 | * Returns a negative error code for failure. |
228 | */ |
229 | int amd_wbrf_retrieve_freq_band(struct device *dev, struct wbrf_ranges_in_out *out) |
230 | { |
231 | struct amd_wbrf_ranges_out acpi_out = {0}; |
232 | struct acpi_device *adev; |
233 | union acpi_object *obj; |
234 | union acpi_object param; |
235 | int ret = 0; |
236 | |
237 | adev = ACPI_COMPANION(dev); |
238 | if (!adev) |
239 | return -ENODEV; |
240 | |
241 | param.type = ACPI_TYPE_STRING; |
242 | param.string.length = 0; |
243 | param.string.pointer = NULL; |
244 | |
245 | obj = acpi_evaluate_dsm(handle: adev->handle, guid: &wifi_acpi_dsm_guid, |
246 | WBRF_REVISION, WBRF_RETRIEVE, argv4: ¶m); |
247 | if (!obj) |
248 | return -EINVAL; |
249 | |
250 | /* |
251 | * The return buffer is with variable length and the format below: |
252 | * number_of_entries(1 DWORD): Number of entries |
253 | * start_freq of 1st entry(1 QWORD): Start frequency of the 1st entry |
254 | * end_freq of 1st entry(1 QWORD): End frequency of the 1st entry |
255 | * ... |
256 | * ... |
257 | * start_freq of the last entry(1 QWORD) |
258 | * end_freq of the last entry(1 QWORD) |
259 | * |
260 | * Thus the buffer length is determined by the number of entries. |
261 | * - For zero entry scenario, the buffer length will be 4 bytes. |
262 | * - For one entry scenario, the buffer length will be 20 bytes. |
263 | */ |
264 | if (obj->buffer.length > sizeof(acpi_out) || obj->buffer.length < 4) { |
265 | dev_err(dev, "Wrong sized WBRT information" ); |
266 | ret = -EINVAL; |
267 | goto out; |
268 | } |
269 | memcpy(&acpi_out, obj->buffer.pointer, obj->buffer.length); |
270 | |
271 | out->num_of_ranges = acpi_out.num_of_ranges; |
272 | memcpy(out->band_list, acpi_out.band_list, sizeof(acpi_out.band_list)); |
273 | |
274 | out: |
275 | ACPI_FREE(obj); |
276 | return ret; |
277 | } |
278 | EXPORT_SYMBOL_GPL(amd_wbrf_retrieve_freq_band); |
279 | |
280 | /** |
281 | * amd_wbrf_register_notifier - register for notifications of frequency |
282 | * band update |
283 | * |
284 | * @nb: driver notifier block |
285 | * |
286 | * The consumer should register itself via this API so that it can get |
287 | * notified on the frequency band updates from other producers. |
288 | * |
289 | * Return: |
290 | * 0 for registering a consumer driver successfully. |
291 | * Returns a negative error code for failure. |
292 | */ |
293 | int amd_wbrf_register_notifier(struct notifier_block *nb) |
294 | { |
295 | return blocking_notifier_chain_register(nh: &wbrf_chain_head, nb); |
296 | } |
297 | EXPORT_SYMBOL_GPL(amd_wbrf_register_notifier); |
298 | |
299 | /** |
300 | * amd_wbrf_unregister_notifier - unregister for notifications of |
301 | * frequency band update |
302 | * |
303 | * @nb: driver notifier block |
304 | * |
305 | * The consumer should call this API when it is longer interested with |
306 | * the frequency band updates from other producers. Usually, this should |
307 | * be performed during driver cleanup. |
308 | * |
309 | * Return: |
310 | * 0 for unregistering a consumer driver. |
311 | * Returns a negative error code for failure. |
312 | */ |
313 | int amd_wbrf_unregister_notifier(struct notifier_block *nb) |
314 | { |
315 | return blocking_notifier_chain_unregister(nh: &wbrf_chain_head, nb); |
316 | } |
317 | EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier); |
318 | |