xref: /linux/drivers/platform/x86/amd/wbrf.c (revision b9c8fc2caea6ff7e45c6942de8fee53515c66b34)
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 	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  */
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  */
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  */
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  */
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, &param);
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  */
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  */
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