File Index
Files and directories
Sign in
FILE

wbrf.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/amd/wbrf.c 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 */
 27struct amd_wbrf_ranges_out {
 28	u32			num_of_ranges;
 29	struct freq_band_range	band_list[MAX_NUM_OF_WBRF_RANGES];
 30} __packed;
 31
 32static 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 */
 40static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head);
 41
 42static 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
121out:
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 */
147int 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}
164EXPORT_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 */
178bool 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}
189EXPORT_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 */
203bool 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}
214EXPORT_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 */
231int 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
276out:
277	ACPI_FREE(obj);
278	return ret;
279}
280EXPORT_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 */
295int amd_wbrf_register_notifier(struct notifier_block *nb)
296{
297	return blocking_notifier_chain_register(&wbrf_chain_head, nb);
298}
299EXPORT_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 */
315int amd_wbrf_unregister_notifier(struct notifier_block *nb)
316{
317	return blocking_notifier_chain_unregister(&wbrf_chain_head, nb);
318}
319EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier);
~/ Read-only file preview.