| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- // 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);
|