xref: /linux/drivers/platform/x86/amd/wbrf.c (revision 2cddfc2e8fc78c13b0f5286ea5dd48cdf527ad41)
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 		kfree(tmp);
109 		return -EINVAL;
110 	}
111 
112 	if (obj->type != ACPI_TYPE_INTEGER) {
113 		ret = -EINVAL;
114 		goto out;
115 	}
116 
117 	ret = obj->integer.value;
118 	if (ret)
119 		ret = -EINVAL;
120 
121 out:
122 	ACPI_FREE(obj);
123 	kfree(tmp);
124 
125 	return ret;
126 }
127 
128 /**
129  * acpi_amd_wbrf_add_remove - add or remove the frequency band the device is using
130  *
131  * @dev: device pointer
132  * @action: remove or add the frequency band into bios
133  * @in: input structure containing the frequency band the device is using
134  *
135  * Broadcast to other consumers the frequency band the device starts
136  * to use. Underneath the surface the information is cached into an
137  * internal buffer first. Then a notification is sent to all those
138  * registered consumers. So then they can retrieve that buffer to
139  * know the latest active frequency bands. Consumers that haven't
140  * yet been registered can retrieve the information from the cache
141  * when they register.
142  *
143  * Return:
144  * 0 for success add/remove wifi frequency band.
145  * Returns a negative error code for failure.
146  */
147 int acpi_amd_wbrf_add_remove(struct device *dev, uint8_t action, struct wbrf_ranges_in_out *in)
148 {
149 	struct acpi_device *adev;
150 	int ret;
151 
152 	adev = ACPI_COMPANION(dev);
153 	if (!adev)
154 		return -ENODEV;
155 
156 	ret = wbrf_record(adev, action, in);
157 	if (ret)
158 		return ret;
159 
160 	blocking_notifier_call_chain(&wbrf_chain_head, WBRF_CHANGED, NULL);
161 
162 	return 0;
163 }
164 EXPORT_SYMBOL_GPL(acpi_amd_wbrf_add_remove);
165 
166 /**
167  * acpi_amd_wbrf_supported_producer - determine if the WBRF can be enabled
168  *                                    for the device as a producer
169  *
170  * @dev: device pointer
171  *
172  * Check if the platform equipped with necessary implementations to
173  * support WBRF for the device as a producer.
174  *
175  * Return:
176  * true if WBRF is supported, otherwise returns false
177  */
178 bool acpi_amd_wbrf_supported_producer(struct device *dev)
179 {
180 	struct acpi_device *adev;
181 
182 	adev = ACPI_COMPANION(dev);
183 	if (!adev)
184 		return false;
185 
186 	return acpi_check_dsm(adev->handle, &wifi_acpi_dsm_guid,
187 			      WBRF_REVISION, BIT(WBRF_RECORD));
188 }
189 EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_producer);
190 
191 /**
192  * acpi_amd_wbrf_supported_consumer - determine if the WBRF can be enabled
193  *                                    for the device as a consumer
194  *
195  * @dev: device pointer
196  *
197  * Determine if the platform equipped with necessary implementations to
198  * support WBRF for the device as a consumer.
199  *
200  * Return:
201  * true if WBRF is supported, otherwise returns false.
202  */
203 bool acpi_amd_wbrf_supported_consumer(struct device *dev)
204 {
205 	struct acpi_device *adev;
206 
207 	adev = ACPI_COMPANION(dev);
208 	if (!adev)
209 		return false;
210 
211 	return acpi_check_dsm(adev->handle, &wifi_acpi_dsm_guid,
212 			      WBRF_REVISION, BIT(WBRF_RETRIEVE));
213 }
214 EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_consumer);
215 
216 /**
217  * amd_wbrf_retrieve_freq_band - retrieve current active frequency bands
218  *
219  * @dev: device pointer
220  * @out: output structure containing all the active frequency bands
221  *
222  * Retrieve the current active frequency bands which were broadcasted
223  * by other producers. The consumer who calls this API should take
224  * proper actions if any of the frequency band may cause RFI with its
225  * own frequency band used.
226  *
227  * Return:
228  * 0 for getting wifi freq band successfully.
229  * Returns a negative error code for failure.
230  */
231 int amd_wbrf_retrieve_freq_band(struct device *dev, struct wbrf_ranges_in_out *out)
232 {
233 	struct amd_wbrf_ranges_out acpi_out = {0};
234 	struct acpi_device *adev;
235 	union acpi_object *obj;
236 	union acpi_object param;
237 	int ret = 0;
238 
239 	adev = ACPI_COMPANION(dev);
240 	if (!adev)
241 		return -ENODEV;
242 
243 	param.type = ACPI_TYPE_STRING;
244 	param.string.length = 0;
245 	param.string.pointer = NULL;
246 
247 	obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
248 							WBRF_REVISION, WBRF_RETRIEVE, &param);
249 	if (!obj)
250 		return -EINVAL;
251 
252 	/*
253 	 * The return buffer is with variable length and the format below:
254 	 * number_of_entries(1 DWORD):       Number of entries
255 	 * start_freq of 1st entry(1 QWORD): Start frequency of the 1st entry
256 	 * end_freq of 1st entry(1 QWORD):   End frequency of the 1st entry
257 	 * ...
258 	 * ...
259 	 * start_freq of the last entry(1 QWORD)
260 	 * end_freq of the last entry(1 QWORD)
261 	 *
262 	 * Thus the buffer length is determined by the number of entries.
263 	 * - For zero entry scenario, the buffer length will be 4 bytes.
264 	 * - For one entry scenario, the buffer length will be 20 bytes.
265 	 */
266 	if (obj->buffer.length > sizeof(acpi_out) || obj->buffer.length < 4) {
267 		dev_err(dev, "Wrong sized WBRT information");
268 		ret = -EINVAL;
269 		goto out;
270 	}
271 	memcpy(&acpi_out, obj->buffer.pointer, obj->buffer.length);
272 
273 	out->num_of_ranges = acpi_out.num_of_ranges;
274 	memcpy(out->band_list, acpi_out.band_list, sizeof(acpi_out.band_list));
275 
276 out:
277 	ACPI_FREE(obj);
278 	return ret;
279 }
280 EXPORT_SYMBOL_GPL(amd_wbrf_retrieve_freq_band);
281 
282 /**
283  * amd_wbrf_register_notifier - register for notifications of frequency
284  *                                   band update
285  *
286  * @nb: driver notifier block
287  *
288  * The consumer should register itself via this API so that it can get
289  * notified on the frequency band updates from other producers.
290  *
291  * Return:
292  * 0 for registering a consumer driver successfully.
293  * Returns a negative error code for failure.
294  */
295 int amd_wbrf_register_notifier(struct notifier_block *nb)
296 {
297 	return blocking_notifier_chain_register(&wbrf_chain_head, nb);
298 }
299 EXPORT_SYMBOL_GPL(amd_wbrf_register_notifier);
300 
301 /**
302  * amd_wbrf_unregister_notifier - unregister for notifications of
303  *                                     frequency band update
304  *
305  * @nb: driver notifier block
306  *
307  * The consumer should call this API when it is longer interested with
308  * the frequency band updates from other producers. Usually, this should
309  * be performed during driver cleanup.
310  *
311  * Return:
312  * 0 for unregistering a consumer driver.
313  * Returns a negative error code for failure.
314  */
315 int amd_wbrf_unregister_notifier(struct notifier_block *nb)
316 {
317 	return blocking_notifier_chain_unregister(&wbrf_chain_head, nb);
318 }
319 EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier);
320