318 lines
8.6 KiB
C
318 lines
8.6 KiB
C
|
// 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;
|
||
|
union acpi_object *tmp;
|
||
|
union acpi_object *obj;
|
||
|
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;
|
||
|
|
||
|
tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
|
||
|
WBRF_REVISION, WBRF_RECORD, &argv4);
|
||
|
|
||
|
if (!obj)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (obj->type != ACPI_TYPE_INTEGER) {
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ret = obj->integer.value;
|
||
|
if (ret)
|
||
|
ret = -EINVAL;
|
||
|
|
||
|
out:
|
||
|
ACPI_FREE(obj);
|
||
|
kfree(tmp);
|
||
|
|
||
|
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);
|