318 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Wifi Frequency Band Manage Interface4 * Copyright (C) 2023 Advanced Micro Devices5 */6 7#include <linux/acpi.h>8#include <linux/acpi_amd_wbrf.h>9 10/*11 * Functions bit vector for WBRF method12 *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 0x018#define WBRF_RECORD 0x119#define WBRF_RETRIEVE 0x220 21#define WBRF_REVISION 0x122 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) about38 * 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 by63 * the caller is required to be equal to the number of64 * 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 count72 * 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 119out: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 using128 *129 * @dev: device pointer130 * @action: remove or add the frequency band into bios131 * @in: input structure containing the frequency band the device is using132 *133 * Broadcast to other consumers the frequency band the device starts134 * to use. Underneath the surface the information is cached into an135 * internal buffer first. Then a notification is sent to all those136 * registered consumers. So then they can retrieve that buffer to137 * know the latest active frequency bands. Consumers that haven't138 * yet been registered can retrieve the information from the cache139 * when they register.140 *141 * Return:142 * 0 for success add/remove wifi frequency band.143 * Returns a negative error code for failure.144 */145int 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}162EXPORT_SYMBOL_GPL(acpi_amd_wbrf_add_remove);163 164/**165 * acpi_amd_wbrf_supported_producer - determine if the WBRF can be enabled166 * for the device as a producer167 *168 * @dev: device pointer169 *170 * Check if the platform equipped with necessary implementations to171 * support WBRF for the device as a producer.172 *173 * Return:174 * true if WBRF is supported, otherwise returns false175 */176bool 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}187EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_producer);188 189/**190 * acpi_amd_wbrf_supported_consumer - determine if the WBRF can be enabled191 * for the device as a consumer192 *193 * @dev: device pointer194 *195 * Determine if the platform equipped with necessary implementations to196 * support WBRF for the device as a consumer.197 *198 * Return:199 * true if WBRF is supported, otherwise returns false.200 */201bool 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}212EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_consumer);213 214/**215 * amd_wbrf_retrieve_freq_band - retrieve current active frequency bands216 *217 * @dev: device pointer218 * @out: output structure containing all the active frequency bands219 *220 * Retrieve the current active frequency bands which were broadcasted221 * by other producers. The consumer who calls this API should take222 * proper actions if any of the frequency band may cause RFI with its223 * own frequency band used.224 *225 * Return:226 * 0 for getting wifi freq band successfully.227 * Returns a negative error code for failure.228 */229int 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, ¶m);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 entries253 * start_freq of 1st entry(1 QWORD): Start frequency of the 1st entry254 * end_freq of 1st entry(1 QWORD): End frequency of the 1st entry255 * ...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 274out:275 ACPI_FREE(obj);276 return ret;277}278EXPORT_SYMBOL_GPL(amd_wbrf_retrieve_freq_band);279 280/**281 * amd_wbrf_register_notifier - register for notifications of frequency282 * band update283 *284 * @nb: driver notifier block285 *286 * The consumer should register itself via this API so that it can get287 * 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 */293int amd_wbrf_register_notifier(struct notifier_block *nb)294{295 return blocking_notifier_chain_register(&wbrf_chain_head, nb);296}297EXPORT_SYMBOL_GPL(amd_wbrf_register_notifier);298 299/**300 * amd_wbrf_unregister_notifier - unregister for notifications of301 * frequency band update302 *303 * @nb: driver notifier block304 *305 * The consumer should call this API when it is longer interested with306 * the frequency band updates from other producers. Usually, this should307 * be performed during driver cleanup.308 *309 * Return:310 * 0 for unregistering a consumer driver.311 * Returns a negative error code for failure.312 */313int amd_wbrf_unregister_notifier(struct notifier_block *nb)314{315 return blocking_notifier_chain_unregister(&wbrf_chain_head, nb);316}317EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier);318