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