| // SPDX-License-Identifier: BSD-3-Clause-Clear | |
| /* | |
| * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. | |
| * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. | |
| */ | |
| #include "core.h" | |
| #include "acpi.h" | |
| #include "debug.h" | |
| static int ath12k_acpi_dsm_get_data(struct ath12k_base *ab, int func) | |
| { | |
| union acpi_object *obj; | |
| acpi_handle root_handle; | |
| int ret, i; | |
| root_handle = ACPI_HANDLE(ab->dev); | |
| if (!root_handle) { | |
| ath12k_dbg(ab, ATH12K_DBG_BOOT, "invalid acpi handler\n"); | |
| return -EOPNOTSUPP; | |
| } | |
| obj = acpi_evaluate_dsm(root_handle, ab->hw_params->acpi_guid, 0, func, | |
| NULL); | |
| if (!obj) { | |
| ath12k_dbg(ab, ATH12K_DBG_BOOT, "acpi_evaluate_dsm() failed\n"); | |
| return -ENOENT; | |
| } | |
| if (obj->type == ACPI_TYPE_INTEGER) { | |
| switch (func) { | |
| case ATH12K_ACPI_DSM_FUNC_SUPPORT_FUNCS: | |
| ab->acpi.func_bit = obj->integer.value; | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_DISABLE_FLAG: | |
| ab->acpi.bit_flag = obj->integer.value; | |
| break; | |
| } | |
| } else if (obj->type == ACPI_TYPE_STRING) { | |
| switch (func) { | |
| case ATH12K_ACPI_DSM_FUNC_BDF_EXT: | |
| if (obj->string.length <= ATH12K_ACPI_BDF_ANCHOR_STRING_LEN || | |
| obj->string.length > ATH12K_ACPI_BDF_MAX_LEN || | |
| memcmp(obj->string.pointer, ATH12K_ACPI_BDF_ANCHOR_STRING, | |
| ATH12K_ACPI_BDF_ANCHOR_STRING_LEN)) { | |
| ath12k_warn(ab, "invalid ACPI DSM BDF size: %d\n", | |
| obj->string.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(ab->acpi.bdf_string, obj->string.pointer, | |
| obj->buffer.length); | |
| break; | |
| } | |
| } else if (obj->type == ACPI_TYPE_BUFFER) { | |
| switch (func) { | |
| case ATH12K_ACPI_DSM_FUNC_SUPPORT_FUNCS: | |
| if (obj->buffer.length < ATH12K_ACPI_DSM_FUNC_MIN_BITMAP_SIZE || | |
| obj->buffer.length > ATH12K_ACPI_DSM_FUNC_MAX_BITMAP_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI DSM func size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| ab->acpi.func_bit = 0; | |
| for (i = 0; i < obj->buffer.length; i++) | |
| ab->acpi.func_bit += obj->buffer.pointer[i] << (i * 8); | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_TAS_CFG: | |
| if (obj->buffer.length != ATH12K_ACPI_DSM_TAS_CFG_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI DSM TAS config size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&ab->acpi.tas_cfg, obj->buffer.pointer, | |
| obj->buffer.length); | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_TAS_DATA: | |
| if (obj->buffer.length != ATH12K_ACPI_DSM_TAS_DATA_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI DSM TAS data size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&ab->acpi.tas_sar_power_table, obj->buffer.pointer, | |
| obj->buffer.length); | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_BIOS_SAR: | |
| if (obj->buffer.length != ATH12K_ACPI_DSM_BIOS_SAR_DATA_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI BIOS SAR data size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&ab->acpi.bios_sar_data, obj->buffer.pointer, | |
| obj->buffer.length); | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_GEO_OFFSET: | |
| if (obj->buffer.length != ATH12K_ACPI_DSM_GEO_OFFSET_DATA_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI GEO OFFSET data size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&ab->acpi.geo_offset_data, obj->buffer.pointer, | |
| obj->buffer.length); | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_INDEX_CCA: | |
| if (obj->buffer.length != ATH12K_ACPI_DSM_CCA_DATA_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI DSM CCA data size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&ab->acpi.cca_data, obj->buffer.pointer, | |
| obj->buffer.length); | |
| break; | |
| case ATH12K_ACPI_DSM_FUNC_INDEX_BAND_EDGE: | |
| if (obj->buffer.length != ATH12K_ACPI_DSM_BAND_EDGE_DATA_SIZE) { | |
| ath12k_warn(ab, "invalid ACPI DSM band edge data size: %d\n", | |
| obj->buffer.length); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| memcpy(&ab->acpi.band_edge_power, obj->buffer.pointer, | |
| obj->buffer.length); | |
| break; | |
| } | |
| } else { | |
| ath12k_warn(ab, "ACPI DSM method returned an unsupported object type: %d\n", | |
| obj->type); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| ret = 0; | |
| out: | |
| ACPI_FREE(obj); | |
| return ret; | |
| } | |
| static int ath12k_acpi_set_power_limit(struct ath12k_base *ab) | |
| { | |
| const u8 *tas_sar_power_table = ab->acpi.tas_sar_power_table; | |
| int ret; | |
| if (tas_sar_power_table[0] != ATH12K_ACPI_TAS_DATA_VERSION || | |
| tas_sar_power_table[1] != ATH12K_ACPI_TAS_DATA_ENABLE) { | |
| ath12k_warn(ab, "latest ACPI TAS data is invalid\n"); | |
| return -EINVAL; | |
| } | |
| ret = ath12k_wmi_set_bios_cmd(ab, WMI_BIOS_PARAM_TAS_DATA_TYPE, | |
| tas_sar_power_table, | |
| ATH12K_ACPI_DSM_TAS_DATA_SIZE); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to send ACPI TAS data table: %d\n", ret); | |
| return ret; | |
| } | |
| return ret; | |
| } | |
| static int ath12k_acpi_set_bios_sar_power(struct ath12k_base *ab) | |
| { | |
| int ret; | |
| if (ab->acpi.bios_sar_data[0] != ATH12K_ACPI_POWER_LIMIT_VERSION || | |
| ab->acpi.bios_sar_data[1] != ATH12K_ACPI_POWER_LIMIT_ENABLE_FLAG) { | |
| ath12k_warn(ab, "invalid latest ACPI BIOS SAR data\n"); | |
| return -EINVAL; | |
| } | |
| ret = ath12k_wmi_set_bios_sar_cmd(ab, ab->acpi.bios_sar_data); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to set ACPI BIOS SAR table: %d\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static void ath12k_acpi_dsm_notify(acpi_handle handle, u32 event, void *data) | |
| { | |
| int ret; | |
| struct ath12k_base *ab = data; | |
| if (event == ATH12K_ACPI_NOTIFY_EVENT) { | |
| ath12k_warn(ab, "unknown acpi notify %u\n", event); | |
| return; | |
| } | |
| if (!ab->acpi.acpi_tas_enable) { | |
| ath12k_dbg(ab, ATH12K_DBG_BOOT, "acpi_tas_enable is false\n"); | |
| return; | |
| } | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_TAS_DATA); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to update ACPI TAS data table: %d\n", ret); | |
| return; | |
| } | |
| ret = ath12k_acpi_set_power_limit(ab); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to set ACPI TAS power limit data: %d", ret); | |
| return; | |
| } | |
| if (!ab->acpi.acpi_bios_sar_enable) | |
| return; | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_BIOS_SAR); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to update BIOS SAR: %d\n", ret); | |
| return; | |
| } | |
| ret = ath12k_acpi_set_bios_sar_power(ab); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to set BIOS SAR power limit: %d\n", ret); | |
| return; | |
| } | |
| } | |
| static int ath12k_acpi_set_bios_sar_params(struct ath12k_base *ab) | |
| { | |
| int ret; | |
| ret = ath12k_wmi_set_bios_sar_cmd(ab, ab->acpi.bios_sar_data); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to set ACPI BIOS SAR table: %d\n", ret); | |
| return ret; | |
| } | |
| ret = ath12k_wmi_set_bios_geo_cmd(ab, ab->acpi.geo_offset_data); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to set ACPI BIOS GEO table: %d\n", ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int ath12k_acpi_set_tas_params(struct ath12k_base *ab) | |
| { | |
| int ret; | |
| ret = ath12k_wmi_set_bios_cmd(ab, WMI_BIOS_PARAM_TAS_CONFIG_TYPE, | |
| ab->acpi.tas_cfg, | |
| ATH12K_ACPI_DSM_TAS_CFG_SIZE); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to send ACPI TAS config table parameter: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| ret = ath12k_wmi_set_bios_cmd(ab, WMI_BIOS_PARAM_TAS_DATA_TYPE, | |
| ab->acpi.tas_sar_power_table, | |
| ATH12K_ACPI_DSM_TAS_DATA_SIZE); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to send ACPI TAS data table parameter: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| bool ath12k_acpi_get_disable_rfkill(struct ath12k_base *ab) | |
| { | |
| return ab->acpi.acpi_disable_rfkill; | |
| } | |
| bool ath12k_acpi_get_disable_11be(struct ath12k_base *ab) | |
| { | |
| return ab->acpi.acpi_disable_11be; | |
| } | |
| void ath12k_acpi_set_dsm_func(struct ath12k_base *ab) | |
| { | |
| int ret; | |
| u8 *buf; | |
| if (!ab->hw_params->acpi_guid) | |
| /* not supported with this hardware */ | |
| return; | |
| if (ab->acpi.acpi_tas_enable) { | |
| ret = ath12k_acpi_set_tas_params(ab); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to send ACPI TAS parameters: %d\n", ret); | |
| return; | |
| } | |
| } | |
| if (ab->acpi.acpi_bios_sar_enable) { | |
| ret = ath12k_acpi_set_bios_sar_params(ab); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to send ACPI BIOS SAR: %d\n", ret); | |
| return; | |
| } | |
| } | |
| if (ab->acpi.acpi_cca_enable) { | |
| buf = ab->acpi.cca_data + ATH12K_ACPI_CCA_THR_OFFSET_DATA_OFFSET; | |
| ret = ath12k_wmi_set_bios_cmd(ab, | |
| WMI_BIOS_PARAM_CCA_THRESHOLD_TYPE, | |
| buf, | |
| ATH12K_ACPI_CCA_THR_OFFSET_LEN); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to set ACPI DSM CCA threshold: %d\n", | |
| ret); | |
| return; | |
| } | |
| } | |
| if (ab->acpi.acpi_band_edge_enable) { | |
| ret = ath12k_wmi_set_bios_cmd(ab, | |
| WMI_BIOS_PARAM_TYPE_BANDEDGE, | |
| ab->acpi.band_edge_power, | |
| sizeof(ab->acpi.band_edge_power)); | |
| if (ret) { | |
| ath12k_warn(ab, | |
| "failed to set ACPI DSM band edge channel power: %d\n", | |
| ret); | |
| return; | |
| } | |
| } | |
| } | |
| int ath12k_acpi_start(struct ath12k_base *ab) | |
| { | |
| acpi_status status; | |
| int ret; | |
| ab->acpi.acpi_tas_enable = false; | |
| ab->acpi.acpi_disable_11be = false; | |
| ab->acpi.acpi_disable_rfkill = false; | |
| ab->acpi.acpi_bios_sar_enable = false; | |
| ab->acpi.acpi_cca_enable = false; | |
| ab->acpi.acpi_band_edge_enable = false; | |
| ab->acpi.acpi_enable_bdf = false; | |
| ab->acpi.bdf_string[0] = '\0'; | |
| if (!ab->hw_params->acpi_guid) | |
| /* not supported with this hardware */ | |
| return 0; | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_SUPPORT_FUNCS); | |
| if (ret) { | |
| ath12k_dbg(ab, ATH12K_DBG_BOOT, "failed to get ACPI DSM data: %d\n", ret); | |
| return ret; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_DISABLE_FLAG)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_DISABLE_FLAG); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI DISABLE FLAG: %d\n", ret); | |
| return ret; | |
| } | |
| if (ATH12K_ACPI_CHEK_BIT_VALID(ab->acpi, | |
| ATH12K_ACPI_DSM_DISABLE_11BE_BIT)) | |
| ab->acpi.acpi_disable_11be = true; | |
| if (!ATH12K_ACPI_CHEK_BIT_VALID(ab->acpi, | |
| ATH12K_ACPI_DSM_DISABLE_RFKILL_BIT)) | |
| ab->acpi.acpi_disable_rfkill = true; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_BDF_EXT)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_BDF_EXT); | |
| if (ret || ab->acpi.bdf_string[0] == '\0') { | |
| ath12k_warn(ab, "failed to get ACPI BDF EXT: %d\n", ret); | |
| return ret; | |
| } | |
| ab->acpi.acpi_enable_bdf = true; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_TAS_CFG)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_TAS_CFG); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI TAS config table: %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_TAS_DATA)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_TAS_DATA); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI TAS data table: %d\n", ret); | |
| return ret; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_TAS_CFG) && | |
| ab->acpi.tas_sar_power_table[0] == ATH12K_ACPI_TAS_DATA_VERSION && | |
| ab->acpi.tas_sar_power_table[1] == ATH12K_ACPI_TAS_DATA_ENABLE) | |
| ab->acpi.acpi_tas_enable = true; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_BIOS_SAR)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_BIOS_SAR); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI bios sar data: %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_GEO_OFFSET)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_GEO_OFFSET); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI geo offset data: %d\n", ret); | |
| return ret; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_BIOS_SAR) && | |
| ab->acpi.bios_sar_data[0] == ATH12K_ACPI_POWER_LIMIT_VERSION && | |
| ab->acpi.bios_sar_data[1] == ATH12K_ACPI_POWER_LIMIT_ENABLE_FLAG && | |
| !ab->acpi.acpi_tas_enable) | |
| ab->acpi.acpi_bios_sar_enable = true; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, ATH12K_ACPI_FUNC_BIT_CCA)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_INDEX_CCA); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI DSM CCA threshold configuration: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| if (ab->acpi.cca_data[0] == ATH12K_ACPI_CCA_THR_VERSION && | |
| ab->acpi.cca_data[ATH12K_ACPI_CCA_THR_OFFSET_DATA_OFFSET] == | |
| ATH12K_ACPI_CCA_THR_ENABLE_FLAG) | |
| ab->acpi.acpi_cca_enable = true; | |
| } | |
| if (ATH12K_ACPI_FUNC_BIT_VALID(ab->acpi, | |
| ATH12K_ACPI_FUNC_BIT_BAND_EDGE_CHAN_POWER)) { | |
| ret = ath12k_acpi_dsm_get_data(ab, ATH12K_ACPI_DSM_FUNC_INDEX_BAND_EDGE); | |
| if (ret) { | |
| ath12k_warn(ab, "failed to get ACPI DSM band edge channel power: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| if (ab->acpi.band_edge_power[0] == ATH12K_ACPI_BAND_EDGE_VERSION && | |
| ab->acpi.band_edge_power[1] == ATH12K_ACPI_BAND_EDGE_ENABLE_FLAG) | |
| ab->acpi.acpi_band_edge_enable = true; | |
| } | |
| status = acpi_install_notify_handler(ACPI_HANDLE(ab->dev), | |
| ACPI_DEVICE_NOTIFY, | |
| ath12k_acpi_dsm_notify, ab); | |
| if (ACPI_FAILURE(status)) { | |
| ath12k_warn(ab, "failed to install DSM notify callback: %d\n", status); | |
| return -EIO; | |
| } | |
| ab->acpi.started = true; | |
| return 0; | |
| } | |
| int ath12k_acpi_check_bdf_variant_name(struct ath12k_base *ab) | |
| { | |
| size_t max_len = sizeof(ab->qmi.target.bdf_ext); | |
| if (!ab->acpi.acpi_enable_bdf) | |
| return -ENODATA; | |
| if (strscpy(ab->qmi.target.bdf_ext, ab->acpi.bdf_string + 4, max_len) < 0) | |
| ath12k_dbg(ab, ATH12K_DBG_BOOT, | |
| "acpi bdf variant longer than the buffer (variant: %s)\n", | |
| ab->acpi.bdf_string); | |
| return 0; | |
| } | |
| void ath12k_acpi_stop(struct ath12k_base *ab) | |
| { | |
| if (!ab->acpi.started) | |
| return; | |
| acpi_remove_notify_handler(ACPI_HANDLE(ab->dev), | |
| ACPI_DEVICE_NOTIFY, | |
| ath12k_acpi_dsm_notify); | |
| memset(&ab->acpi, 0, sizeof(ab->acpi)); | |
| } |