brintos

brintos / linux-shallow public Read only

0
0
Text · 8.6 KiB · dd197b3 Raw
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, &param);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