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