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 */
27struct amd_wbrf_ranges_out {
28 u32 num_of_ranges;
29 struct freq_band_range band_list[MAX_NUM_OF_WBRF_RANGES];
30} __packed;
31
32static 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 */
40static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head);
41
42static 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
119out:
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 */
145int 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}
162EXPORT_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 */
176bool 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}
187EXPORT_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 */
201bool 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}
212EXPORT_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 */
229int 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: &param);
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
274out:
275 ACPI_FREE(obj);
276 return ret;
277}
278EXPORT_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 */
293int amd_wbrf_register_notifier(struct notifier_block *nb)
294{
295 return blocking_notifier_chain_register(nh: &wbrf_chain_head, nb);
296}
297EXPORT_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 */
313int amd_wbrf_unregister_notifier(struct notifier_block *nb)
314{
315 return blocking_notifier_chain_unregister(nh: &wbrf_chain_head, nb);
316}
317EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier);
318

source code of linux/drivers/platform/x86/amd/wbrf.c