|
| 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 | + */ |
| 27 | +struct amd_wbrf_ranges_out { |
| 28 | + u32 num_of_ranges; |
| 29 | + struct freq_band_range band_list[MAX_NUM_OF_WBRF_RANGES]; |
| 30 | +} __packed; |
| 31 | + |
| 32 | +static 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 | + */ |
| 40 | +static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head); |
| 41 | + |
| 42 | +static 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 | + 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 | + |
| 119 | +out: |
| 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 using |
| 128 | + * |
| 129 | + * @dev: device pointer |
| 130 | + * @action: remove or add the frequency band into bios |
| 131 | + * @in: input structure containing the frequency band the device is using |
| 132 | + * |
| 133 | + * Broadcast to other consumers the frequency band the device starts |
| 134 | + * to use. Underneath the surface the information is cached into an |
| 135 | + * internal buffer first. Then a notification is sent to all those |
| 136 | + * registered consumers. So then they can retrieve that buffer to |
| 137 | + * know the latest active frequency bands. Consumers that haven't |
| 138 | + * yet been registered can retrieve the information from the cache |
| 139 | + * when they register. |
| 140 | + * |
| 141 | + * Return: |
| 142 | + * 0 for success add/remove wifi frequency band. |
| 143 | + * Returns a negative error code for failure. |
| 144 | + */ |
| 145 | +int 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 | +} |
| 162 | +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_add_remove); |
| 163 | + |
| 164 | +/** |
| 165 | + * acpi_amd_wbrf_supported_producer - determine if the WBRF can be enabled |
| 166 | + * for the device as a producer |
| 167 | + * |
| 168 | + * @dev: device pointer |
| 169 | + * |
| 170 | + * Check if the platform equipped with necessary implementations to |
| 171 | + * support WBRF for the device as a producer. |
| 172 | + * |
| 173 | + * Return: |
| 174 | + * true if WBRF is supported, otherwise returns false |
| 175 | + */ |
| 176 | +bool 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 | +} |
| 187 | +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_producer); |
| 188 | + |
| 189 | +/** |
| 190 | + * acpi_amd_wbrf_supported_consumer - determine if the WBRF can be enabled |
| 191 | + * for the device as a consumer |
| 192 | + * |
| 193 | + * @dev: device pointer |
| 194 | + * |
| 195 | + * Determine if the platform equipped with necessary implementations to |
| 196 | + * support WBRF for the device as a consumer. |
| 197 | + * |
| 198 | + * Return: |
| 199 | + * true if WBRF is supported, otherwise returns false. |
| 200 | + */ |
| 201 | +bool 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 | +} |
| 212 | +EXPORT_SYMBOL_GPL(acpi_amd_wbrf_supported_consumer); |
| 213 | + |
| 214 | +/** |
| 215 | + * amd_wbrf_retrieve_freq_band - retrieve current active frequency bands |
| 216 | + * |
| 217 | + * @dev: device pointer |
| 218 | + * @out: output structure containing all the active frequency bands |
| 219 | + * |
| 220 | + * Retrieve the current active frequency bands which were broadcasted |
| 221 | + * by other producers. The consumer who calls this API should take |
| 222 | + * proper actions if any of the frequency band may cause RFI with its |
| 223 | + * own frequency band used. |
| 224 | + * |
| 225 | + * Return: |
| 226 | + * 0 for getting wifi freq band successfully. |
| 227 | + * Returns a negative error code for failure. |
| 228 | + */ |
| 229 | +int 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 entries |
| 253 | + * start_freq of 1st entry(1 QWORD): Start frequency of the 1st entry |
| 254 | + * end_freq of 1st entry(1 QWORD): End frequency of the 1st entry |
| 255 | + * ... |
| 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 | + |
| 274 | +out: |
| 275 | + ACPI_FREE(obj); |
| 276 | + return ret; |
| 277 | +} |
| 278 | +EXPORT_SYMBOL_GPL(amd_wbrf_retrieve_freq_band); |
| 279 | + |
| 280 | +/** |
| 281 | + * amd_wbrf_register_notifier - register for notifications of frequency |
| 282 | + * band update |
| 283 | + * |
| 284 | + * @nb: driver notifier block |
| 285 | + * |
| 286 | + * The consumer should register itself via this API so that it can get |
| 287 | + * 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 | + */ |
| 293 | +int amd_wbrf_register_notifier(struct notifier_block *nb) |
| 294 | +{ |
| 295 | + return blocking_notifier_chain_register(&wbrf_chain_head, nb); |
| 296 | +} |
| 297 | +EXPORT_SYMBOL_GPL(amd_wbrf_register_notifier); |
| 298 | + |
| 299 | +/** |
| 300 | + * amd_wbrf_unregister_notifier - unregister for notifications of |
| 301 | + * frequency band update |
| 302 | + * |
| 303 | + * @nb: driver notifier block |
| 304 | + * |
| 305 | + * The consumer should call this API when it is longer interested with |
| 306 | + * the frequency band updates from other producers. Usually, this should |
| 307 | + * be performed during driver cleanup. |
| 308 | + * |
| 309 | + * Return: |
| 310 | + * 0 for unregistering a consumer driver. |
| 311 | + * Returns a negative error code for failure. |
| 312 | + */ |
| 313 | +int amd_wbrf_unregister_notifier(struct notifier_block *nb) |
| 314 | +{ |
| 315 | + return blocking_notifier_chain_unregister(&wbrf_chain_head, nb); |
| 316 | +} |
| 317 | +EXPORT_SYMBOL_GPL(amd_wbrf_unregister_notifier); |
0 commit comments