]> Git Repo - linux.git/blob - drivers/platform/x86/amd/wbrf.c
Merge tag 's390-6.10-2' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
[linux.git] / drivers / platform / x86 / amd / wbrf.c
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);
This page took 0.055417 seconds and 4 git commands to generate.