| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Wifi Frequency Band Manage Interface | |
| * Copyright (C) 2023 Advanced Micro Devices | |
| */ | |
| #include <linux/acpi.h> | |
| #include <linux/acpi_amd_wbrf.h> | |
| /* | |
| * Functions bit vector for WBRF method | |
| * | |
| * Bit 0: WBRF supported. | |
| * Bit 1: Function 1 (Add / Remove frequency) is supported. | |
| * Bit 2: Function 2 (Get frequency list) is supported. | |
| */ | |
| #define WBRF_ENABLED 0x0 | |
| #define WBRF_RECORD 0x1 | |
| #define WBRF_RETRIEVE 0x2 | |
| #define WBRF_REVISION 0x1 | |
| /* | |
| * The data structure used for WBRF_RETRIEVE is not naturally aligned. | |
| * And unfortunately the design has been settled down. | |
| */ | |
| struct amd_wbrf_ranges_out { | |
| u32 num_of_ranges; | |
| struct freq_band_range band_list[MAX_NUM_OF_WBRF_RANGES]; | |
| } __packed; | |
| static const guid_t wifi_acpi_dsm_guid = | |
| GUID_INIT(0x7b7656cf, 0xdc3d, 0x4c1c, | |
| 0x83, 0xe9, 0x66, 0xe7, 0x21, 0xde, 0x30, 0x70); | |
| /* | |
| * Used to notify consumer (amdgpu driver currently) about | |
| * the wifi frequency is change. | |
| */ | |
| static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); | |
| static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in) | |
| { | |
| union acpi_object argv4; | |
| u32 num_of_ranges = 0; | |
| u32 num_of_elements; | |
| u32 arg_idx = 0; | |
| int ret; | |
| u32 i; | |
| if (!in) | |
| return -EINVAL; | |
| for (i = 0; i < ARRAY_SIZE(in->band_list); i++) { | |
| if (in->band_list[i].start && in->band_list[i].end) | |
| num_of_ranges++; | |
| } | |
| /* | |
| * The num_of_ranges value in the "in" object supplied by | |
| * the caller is required to be equal to the number of | |
| * entries in the band_list array in there. | |
| */ | |
| if (num_of_ranges != in->num_of_ranges) | |
| return -EINVAL; | |
| /* | |
| * Every input frequency band comes with two end points(start/end) | |
| * and each is accounted as an element. Meanwhile the range count | |
| * and action type are accounted as an element each. | |
| * So, the total element count = 2 * num_of_ranges + 1 + 1. | |
| */ | |
| num_of_elements = 2 * num_of_ranges + 2; | |
| union acpi_object *tmp __free(kfree) = kzalloc_objs(*tmp, | |
| num_of_elements); | |
| if (!tmp) | |
| return -ENOMEM; | |
| argv4.package.type = ACPI_TYPE_PACKAGE; | |
| argv4.package.count = num_of_elements; | |
| argv4.package.elements = tmp; | |
| /* save the number of ranges*/ | |
| tmp[0].integer.type = ACPI_TYPE_INTEGER; | |
| tmp[0].integer.value = num_of_ranges; | |
| /* save the action(WBRF_RECORD_ADD/REMOVE/RETRIEVE) */ | |
| tmp[1].integer.type = ACPI_TYPE_INTEGER; | |
| tmp[1].integer.value = action; | |
| arg_idx = 2; | |
| for (i = 0; i < ARRAY_SIZE(in->band_list); i++) { | |
| if (!in->band_list[i].start || !in->band_list[i].end) | |
| continue; | |
| tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; | |
| tmp[arg_idx++].integer.value = in->band_list[i].start; | |
| tmp[arg_idx].integer.type = ACPI_TYPE_INTEGER; | |
| tmp[arg_idx++].integer.value = in->band_list[i].end; | |
| } | |
| union acpi_object *obj __free(kfree) = | |
| acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, | |
| WBRF_REVISION, WBRF_RECORD, &argv4); | |
| if (!obj) | |
| return -EINVAL; | |
| if (obj->type != ACPI_TYPE_INTEGER) | |
| return -EINVAL; | |
| ret = obj->integer.value; | |
| if (ret) | |
| return -EINVAL; | |
| return ret; | |
| } | |
| /** | |
| * acpi_amd_wbrf_add_remove - add or remove the frequency band the device is using | |
| * | |
| * @dev: device pointer | |
| * @action: remove or add the frequency band into bios | |
| * @in: input structure containing the frequency band the device is using | |
| * | |
| * Broadcast to other consumers the frequency band the device starts | |
| * to use. Underneath the surface the information is cached into an | |
| * internal buffer first. Then a notification is sent to all those | |
| * registered consumers. So then they can retrieve that buffer to | |
| * know the latest active frequency bands. Consumers that haven't | |
| * yet been registered can retrieve the information from the cache | |
| * when they register. | |
| * | |
| * Return: | |
| * 0 for success add/remove wifi frequency band. | |
| * Returns a negative error code for failure. | |
| */ | |
| int acpi_amd_wbrf_add_remove(struct device *dev, uint8_t action, struct wbrf_ranges_in_out *in) | |
| { | |
| struct acpi_device *adev; | |
| int ret; | |
| adev = ACPI_COMPANION(dev); | |
| if (!adev) | |
| return -ENODEV; | |
| ret = wbrf_record(adev, action, in); | |
| if (ret) | |
| return ret; | |
| blocking_notifier_call_chain(&wbrf_chain_head, WBRF_CHANGED, NULL); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_amd_wbrf_add_remove); | |
| /** | |
| * acpi_amd_wbrf_supported_producer - determine if the WBRF can be enabled | |
| * for the device as a producer | |
| * | |
| * @dev: device pointer | |
| * | |
| * Check if the platform equipped with necessary implementations to | |
| * support WBRF for the device as a producer. | |
| * | |
| * Return: | |
| * true if WBRF is supported, otherwise returns false | |
| */ | |
| bool acpi_amd_wbrf_supported_producer(struct device *dev) | |
| { | |
| struct acpi_device *adev; | |
| adev = ACPI_COMPANION(dev); | |
| if (!adev) | |
| return false; | |
| return acpi_check_dsm(adev->handle, &wifi_acpi_dsm_guid, | |
| WBRF_REVISION, BIT(WBRF_RECORD)); | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_producer); | |
| /** | |
| * acpi_amd_wbrf_supported_consumer - determine if the WBRF can be enabled | |
| * for the device as a consumer | |
| * | |
| * @dev: device pointer | |
| * | |
| * Determine if the platform equipped with necessary implementations to | |
| * support WBRF for the device as a consumer. | |
| * | |
| * Return: | |
| * true if WBRF is supported, otherwise returns false. | |
| */ | |
| bool acpi_amd_wbrf_supported_consumer(struct device *dev) | |
| { | |
| struct acpi_device *adev; | |
| adev = ACPI_COMPANION(dev); | |
| if (!adev) | |
| return false; | |
| return acpi_check_dsm(adev->handle, &wifi_acpi_dsm_guid, | |
| WBRF_REVISION, BIT(WBRF_RETRIEVE)); | |
| } | |
| EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_consumer); | |
| /** | |
| * amd_wbrf_retrieve_freq_band - retrieve current active frequency bands | |
| * | |
| * @dev: device pointer | |
| * @out: output structure containing all the active frequency bands | |
| * | |
| * Retrieve the current active frequency bands which were broadcasted | |
| * by other producers. The consumer who calls this API should take | |
| * proper actions if any of the frequency band may cause RFI with its | |
| * own frequency band used. | |
| * | |
| * Return: | |
| * 0 for getting wifi freq band successfully. | |
| * Returns a negative error code for failure. | |
| */ | |
| int amd_wbrf_retrieve_freq_band(struct device *dev, struct wbrf_ranges_in_out *out) | |
| { | |
| struct amd_wbrf_ranges_out acpi_out = {0}; | |
| struct acpi_device *adev; | |
| union acpi_object *obj; | |
| union acpi_object param; | |
| int ret = 0; | |
| adev = ACPI_COMPANION(dev); | |
| if (!adev) | |
| return -ENODEV; | |
| param.type = ACPI_TYPE_STRING; | |
| param.string.length = 0; | |
| param.string.pointer = NULL; | |
| obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid, | |
| WBRF_REVISION, WBRF_RETRIEVE, ¶m); | |
| if (!obj) | |
| return -EINVAL; | |
| /* | |
| * The return buffer is with variable length and the format below: | |
| * number_of_entries(1 DWORD): Number of entries | |
| * start_freq of 1st entry(1 QWORD): Start frequency of the 1st entry | |
| * end_freq of 1st entry(1 QWORD): End frequency of the 1st entry | |
| * ... | |
| * ... | |
| * start_freq of the last entry(1 QWORD) | |
| * end_freq of the last entry(1 QWORD) | |
| * | |
| * Thus the buffer length is determined by the number of entries. | |
| * - For zero entry scenario, the buffer length will be 4 bytes. | |
| * - For one entry scenario, the buffer length will be 20 bytes. | |
| */ | |
| if (obj->buffer.length > sizeof(acpi_out) || obj->buffer.length < 4) { | |
| dev_err(dev, "Wrong sized WBRT information"); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&acpi_out, obj->buffer.pointer, obj->buffer.length); | |
| out->num_of_ranges = acpi_out.num_of_ranges; | |
| memcpy(out->band_list, acpi_out.band_list, sizeof(acpi_out.band_list)); | |
| out: | |
| ACPI_FREE(obj); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(amd_wbrf_retrieve_freq_band); | |
| /** | |
| * amd_wbrf_register_notifier - register for notifications of frequency | |
| * band update | |
| * | |
| * @nb: driver notifier block | |
| * | |
| * The consumer should register itself via this API so that it can get | |
| * notified on the frequency band updates from other producers. | |
| * | |
| * Return: | |
| * 0 for registering a consumer driver successfully. | |
| * Returns a negative error code for failure. | |
| */ | |
| int amd_wbrf_register_notifier(struct notifier_block *nb) | |
| { | |
| return blocking_notifier_chain_register(&wbrf_chain_head, nb); | |
| } | |
| EXPORT_SYMBOL_GPL(amd_wbrf_register_notifier); | |
| /** | |
| * amd_wbrf_unregister_notifier - unregister for notifications of | |
| * frequency band update | |
| * | |
| * @nb: driver notifier block | |
| * | |
| * The consumer should call this API when it is longer interested with | |
| * the frequency band updates from other producers. Usually, this should | |
| * be performed during driver cleanup. | |
| * | |
| * Return: | |
| * 0 for unregistering a consumer driver. | |
| * Returns a negative error code for failure. | |
| */ | |
| int amd_wbrf_unregister_notifier(struct notifier_block *nb) | |
| { | |
| return blocking_notifier_chain_unregister(&wbrf_chain_head, nb); | |
| } | |
| EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier); |