xref: /linux/drivers/platform/x86/amd/wbrf.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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(num_of_elements, 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(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 		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(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(&wbrf_chain_head, 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(adev->handle, &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(adev->handle, &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(adev->handle, &wifi_acpi_dsm_guid,
246 							WBRF_REVISION, WBRF_RETRIEVE, &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 
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(&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(&wbrf_chain_head, nb);
316 }
317 EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier);
318