| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* |
| * Copyright (C) 2023 Intel Corporation |
| */ |
| #include <linux/dmi.h> |
| #include "iwl-drv.h" |
| #include "iwl-debug.h" |
| #include "regulatory.h" |
| #include "fw/runtime.h" |
| #include "fw/uefi.h" |
| |
| #define GET_BIOS_TABLE(__name, ...) \ |
| do { \ |
| int ret = -ENOENT; \ |
| if (fwrt->uefi_tables_lock_status > UEFI_WIFI_GUID_UNLOCKED) \ |
| ret = iwl_uefi_get_ ## __name(__VA_ARGS__); \ |
| if (ret < 0) \ |
| ret = iwl_acpi_get_ ## __name(__VA_ARGS__); \ |
| return ret; \ |
| } while (0) |
| |
| #define IWL_BIOS_TABLE_LOADER(__name) \ |
| int iwl_bios_get_ ## __name(struct iwl_fw_runtime *fwrt) \ |
| {GET_BIOS_TABLE(__name, fwrt); } \ |
| IWL_EXPORT_SYMBOL(iwl_bios_get_ ## __name) |
| |
| #define IWL_BIOS_TABLE_LOADER_DATA(__name, data_type) \ |
| int iwl_bios_get_ ## __name(struct iwl_fw_runtime *fwrt, \ |
| data_type * data) \ |
| {GET_BIOS_TABLE(__name, fwrt, data); } \ |
| IWL_EXPORT_SYMBOL(iwl_bios_get_ ## __name) |
| |
| IWL_BIOS_TABLE_LOADER(wrds_table); |
| IWL_BIOS_TABLE_LOADER(ewrd_table); |
| IWL_BIOS_TABLE_LOADER(wgds_table); |
| IWL_BIOS_TABLE_LOADER(ppag_table); |
| IWL_BIOS_TABLE_LOADER_DATA(tas_table, struct iwl_tas_data); |
| IWL_BIOS_TABLE_LOADER_DATA(pwr_limit, u64); |
| IWL_BIOS_TABLE_LOADER_DATA(mcc, char); |
| IWL_BIOS_TABLE_LOADER_DATA(eckv, u32); |
| |
| |
| static const struct dmi_system_id dmi_ppag_approved_list[] = { |
| { .ident = "HP", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "HP"), |
| }, |
| }, |
| { .ident = "SAMSUNG", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), |
| }, |
| }, |
| { .ident = "MSFT", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| }, |
| }, |
| { .ident = "ASUS", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), |
| }, |
| }, |
| { .ident = "GOOGLE-HP", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Google"), |
| DMI_MATCH(DMI_BOARD_VENDOR, "HP"), |
| }, |
| }, |
| { .ident = "GOOGLE-ASUS", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Google"), |
| DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTek COMPUTER INC."), |
| }, |
| }, |
| { .ident = "GOOGLE-SAMSUNG", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Google"), |
| DMI_MATCH(DMI_BOARD_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), |
| }, |
| }, |
| { .ident = "DELL", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
| }, |
| }, |
| { .ident = "DELL", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), |
| }, |
| }, |
| { .ident = "RAZER", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Razer"), |
| }, |
| }, |
| { .ident = "Honor", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "HONOR"), |
| }, |
| }, |
| {} |
| }; |
| |
| static const struct dmi_system_id dmi_tas_approved_list[] = { |
| { .ident = "HP", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "HP"), |
| }, |
| }, |
| { .ident = "SAMSUNG", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), |
| }, |
| }, |
| { .ident = "LENOVO", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
| }, |
| }, |
| { .ident = "DELL", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
| }, |
| }, |
| { .ident = "MSFT", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), |
| }, |
| }, |
| { .ident = "Acer", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Acer"), |
| }, |
| }, |
| { .ident = "ASUS", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), |
| }, |
| }, |
| { .ident = "GOOGLE-HP", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Google"), |
| DMI_MATCH(DMI_BOARD_VENDOR, "HP"), |
| }, |
| }, |
| { .ident = "MSI", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), |
| }, |
| }, |
| { .ident = "Honor", |
| .matches = { |
| DMI_MATCH(DMI_SYS_VENDOR, "HONOR"), |
| }, |
| }, |
| /* keep last */ |
| {} |
| }; |
| |
| bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt) |
| { |
| /* |
| * The PER_CHAIN_LIMIT_OFFSET_CMD command is not supported on |
| * earlier firmware versions. Unfortunately, we don't have a |
| * TLV API flag to rely on, so rely on the major version which |
| * is in the first byte of ucode_ver. This was implemented |
| * initially on version 38 and then backported to 17. It was |
| * also backported to 29, but only for 7265D devices. The |
| * intention was to have it in 36 as well, but not all 8000 |
| * family got this feature enabled. The 8000 family is the |
| * only one using version 36, so skip this version entirely. |
| */ |
| return IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) >= 38 || |
| (IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 17 && |
| fwrt->trans->hw_rev != CSR_HW_REV_TYPE_3160) || |
| (IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 29 && |
| ((fwrt->trans->hw_rev & CSR_HW_REV_TYPE_MSK) == |
| CSR_HW_REV_TYPE_7265D)); |
| } |
| IWL_EXPORT_SYMBOL(iwl_sar_geo_support); |
| |
| int iwl_sar_geo_fill_table(struct iwl_fw_runtime *fwrt, |
| struct iwl_per_chain_offset *table, |
| u32 n_bands, u32 n_profiles) |
| { |
| int i, j; |
| |
| if (!fwrt->geo_enabled) |
| return -ENODATA; |
| |
| if (!iwl_sar_geo_support(fwrt)) |
| return -EOPNOTSUPP; |
| |
| for (i = 0; i < n_profiles; i++) { |
| for (j = 0; j < n_bands; j++) { |
| struct iwl_per_chain_offset *chain = |
| &table[i * n_bands + j]; |
| |
| chain->max_tx_power = |
| cpu_to_le16(fwrt->geo_profiles[i].bands[j].max); |
| chain->chain_a = |
| fwrt->geo_profiles[i].bands[j].chains[0]; |
| chain->chain_b = |
| fwrt->geo_profiles[i].bands[j].chains[1]; |
| IWL_DEBUG_RADIO(fwrt, |
| "SAR geographic profile[%d] Band[%d]: chain A = %d chain B = %d max_tx_power = %d\n", |
| i, j, |
| fwrt->geo_profiles[i].bands[j].chains[0], |
| fwrt->geo_profiles[i].bands[j].chains[1], |
| fwrt->geo_profiles[i].bands[j].max); |
| } |
| } |
| |
| return 0; |
| } |
| IWL_EXPORT_SYMBOL(iwl_sar_geo_fill_table); |
| |
| static int iwl_sar_fill_table(struct iwl_fw_runtime *fwrt, |
| __le16 *per_chain, u32 n_subbands, |
| int prof_a, int prof_b) |
| { |
| int profs[BIOS_SAR_NUM_CHAINS] = { prof_a, prof_b }; |
| int i, j; |
| |
| for (i = 0; i < BIOS_SAR_NUM_CHAINS; i++) { |
| struct iwl_sar_profile *prof; |
| |
| /* don't allow SAR to be disabled (profile 0 means disable) */ |
| if (profs[i] == 0) |
| return -EPERM; |
| |
| /* we are off by one, so allow up to BIOS_SAR_MAX_PROFILE_NUM */ |
| if (profs[i] > BIOS_SAR_MAX_PROFILE_NUM) |
| return -EINVAL; |
| |
| /* profiles go from 1 to 4, so decrement to access the array */ |
| prof = &fwrt->sar_profiles[profs[i] - 1]; |
| |
| /* if the profile is disabled, do nothing */ |
| if (!prof->enabled) { |
| IWL_DEBUG_RADIO(fwrt, "SAR profile %d is disabled.\n", |
| profs[i]); |
| /* |
| * if one of the profiles is disabled, we |
| * ignore all of them and return 1 to |
| * differentiate disabled from other failures. |
| */ |
| return 1; |
| } |
| |
| IWL_DEBUG_INFO(fwrt, |
| "SAR EWRD: chain %d profile index %d\n", |
| i, profs[i]); |
| IWL_DEBUG_RADIO(fwrt, " Chain[%d]:\n", i); |
| for (j = 0; j < n_subbands; j++) { |
| per_chain[i * n_subbands + j] = |
| cpu_to_le16(prof->chains[i].subbands[j]); |
| IWL_DEBUG_RADIO(fwrt, " Band[%d] = %d * .125dBm\n", |
| j, prof->chains[i].subbands[j]); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int iwl_sar_fill_profile(struct iwl_fw_runtime *fwrt, |
| __le16 *per_chain, u32 n_tables, u32 n_subbands, |
| int prof_a, int prof_b) |
| { |
| int i, ret = 0; |
| |
| for (i = 0; i < n_tables; i++) { |
| ret = iwl_sar_fill_table(fwrt, |
| &per_chain[i * n_subbands * BIOS_SAR_NUM_CHAINS], |
| n_subbands, prof_a, prof_b); |
| if (ret) |
| break; |
| } |
| |
| return ret; |
| } |
| IWL_EXPORT_SYMBOL(iwl_sar_fill_profile); |
| |
| static bool iwl_ppag_value_valid(struct iwl_fw_runtime *fwrt, int chain, |
| int subband) |
| { |
| s8 ppag_val = fwrt->ppag_chains[chain].subbands[subband]; |
| |
| if ((subband == 0 && |
| (ppag_val > IWL_PPAG_MAX_LB || ppag_val < IWL_PPAG_MIN_LB)) || |
| (subband != 0 && |
| (ppag_val > IWL_PPAG_MAX_HB || ppag_val < IWL_PPAG_MIN_HB))) { |
| IWL_DEBUG_RADIO(fwrt, "Invalid PPAG value: %d\n", ppag_val); |
| return false; |
| } |
| return true; |
| } |
| |
| int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt, |
| union iwl_ppag_table_cmd *cmd, int *cmd_size) |
| { |
| u8 cmd_ver; |
| int i, j, num_sub_bands; |
| s8 *gain; |
| bool send_ppag_always; |
| |
| /* many firmware images for JF lie about this */ |
| if (CSR_HW_RFID_TYPE(fwrt->trans->hw_rf_id) == |
| CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_JF)) |
| return -EOPNOTSUPP; |
| |
| if (!fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SET_PPAG)) { |
| IWL_DEBUG_RADIO(fwrt, |
| "PPAG capability not supported by FW, command not sent.\n"); |
| return -EINVAL; |
| } |
| |
| cmd_ver = iwl_fw_lookup_cmd_ver(fwrt->fw, |
| WIDE_ID(PHY_OPS_GROUP, |
| PER_PLATFORM_ANT_GAIN_CMD), 1); |
| /* |
| * Starting from ver 4, driver needs to send the PPAG CMD regardless |
| * if PPAG is enabled/disabled or valid/invalid. |
| */ |
| send_ppag_always = cmd_ver > 3; |
| |
| /* Don't send PPAG if it is disabled */ |
| if (!send_ppag_always && !fwrt->ppag_flags) { |
| IWL_DEBUG_RADIO(fwrt, "PPAG not enabled, command not sent.\n"); |
| return -EINVAL; |
| } |
| |
| /* The 'flags' field is the same in v1 and in v2 so we can just |
| * use v1 to access it. |
| */ |
| cmd->v1.flags = cpu_to_le32(fwrt->ppag_flags); |
| |
| IWL_DEBUG_RADIO(fwrt, "PPAG cmd ver is %d\n", cmd_ver); |
| if (cmd_ver == 1) { |
| num_sub_bands = IWL_NUM_SUB_BANDS_V1; |
| gain = cmd->v1.gain[0]; |
| *cmd_size = sizeof(cmd->v1); |
| if (fwrt->ppag_ver >= 1) { |
| /* in this case FW supports revision 0 */ |
| IWL_DEBUG_RADIO(fwrt, |
| "PPAG table rev is %d, send truncated table\n", |
| fwrt->ppag_ver); |
| } |
| } else if (cmd_ver >= 2 && cmd_ver <= 5) { |
| num_sub_bands = IWL_NUM_SUB_BANDS_V2; |
| gain = cmd->v2.gain[0]; |
| *cmd_size = sizeof(cmd->v2); |
| if (fwrt->ppag_ver == 0) { |
| /* in this case FW supports revisions 1,2 or 3 */ |
| IWL_DEBUG_RADIO(fwrt, |
| "PPAG table rev is 0, send padded table\n"); |
| } |
| } else { |
| IWL_DEBUG_RADIO(fwrt, "Unsupported PPAG command version\n"); |
| return -EINVAL; |
| } |
| |
| /* ppag mode */ |
| IWL_DEBUG_RADIO(fwrt, |
| "PPAG MODE bits were read from bios: %d\n", |
| le32_to_cpu(cmd->v1.flags)); |
| |
| if (cmd_ver == 5) |
| cmd->v1.flags &= cpu_to_le32(IWL_PPAG_CMD_V5_MASK); |
| else if (cmd_ver < 5) |
| cmd->v1.flags &= cpu_to_le32(IWL_PPAG_CMD_V4_MASK); |
| |
| if ((cmd_ver == 1 && |
| !fw_has_capa(&fwrt->fw->ucode_capa, |
| IWL_UCODE_TLV_CAPA_PPAG_CHINA_BIOS_SUPPORT)) || |
| (cmd_ver == 2 && fwrt->ppag_ver >= 2)) { |
| cmd->v1.flags &= cpu_to_le32(IWL_PPAG_ETSI_MASK); |
| IWL_DEBUG_RADIO(fwrt, "masking ppag China bit\n"); |
| } else { |
| IWL_DEBUG_RADIO(fwrt, "isn't masking ppag China bit\n"); |
| } |
| |
| IWL_DEBUG_RADIO(fwrt, |
| "PPAG MODE bits going to be sent: %d\n", |
| le32_to_cpu(cmd->v1.flags)); |
| |
| for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) { |
| for (j = 0; j < num_sub_bands; j++) { |
| if (!send_ppag_always && |
| !iwl_ppag_value_valid(fwrt, i, j)) |
| return -EINVAL; |
| |
| gain[i * num_sub_bands + j] = |
| fwrt->ppag_chains[i].subbands[j]; |
| IWL_DEBUG_RADIO(fwrt, |
| "PPAG table: chain[%d] band[%d]: gain = %d\n", |
| i, j, gain[i * num_sub_bands + j]); |
| } |
| } |
| |
| return 0; |
| } |
| IWL_EXPORT_SYMBOL(iwl_fill_ppag_table); |
| |
| bool iwl_is_ppag_approved(struct iwl_fw_runtime *fwrt) |
| { |
| if (!dmi_check_system(dmi_ppag_approved_list)) { |
| IWL_DEBUG_RADIO(fwrt, |
| "System vendor '%s' is not in the approved list, disabling PPAG.\n", |
| dmi_get_system_info(DMI_SYS_VENDOR) ?: "<unknown>"); |
| fwrt->ppag_flags = 0; |
| return false; |
| } |
| |
| return true; |
| } |
| IWL_EXPORT_SYMBOL(iwl_is_ppag_approved); |
| |
| bool iwl_is_tas_approved(void) |
| { |
| return dmi_check_system(dmi_tas_approved_list); |
| } |
| IWL_EXPORT_SYMBOL(iwl_is_tas_approved); |
| |
| int iwl_parse_tas_selection(struct iwl_fw_runtime *fwrt, |
| struct iwl_tas_data *tas_data, |
| const u32 tas_selection) |
| { |
| u8 override_iec = u32_get_bits(tas_selection, |
| IWL_WTAS_OVERRIDE_IEC_MSK); |
| u8 enabled_iec = u32_get_bits(tas_selection, IWL_WTAS_ENABLE_IEC_MSK); |
| u8 usa_tas_uhb = u32_get_bits(tas_selection, IWL_WTAS_USA_UHB_MSK); |
| int enabled = tas_selection & IWL_WTAS_ENABLED_MSK; |
| |
| IWL_DEBUG_RADIO(fwrt, "TAS selection as read from BIOS: 0x%x\n", |
| tas_selection); |
| |
| tas_data->usa_tas_uhb_allowed = usa_tas_uhb; |
| tas_data->override_tas_iec = override_iec; |
| tas_data->enable_tas_iec = enabled_iec; |
| |
| return enabled; |
| } |
| |
| __le32 iwl_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt) |
| { |
| int ret; |
| u32 val; |
| __le32 config_bitmap = 0; |
| |
| switch (CSR_HW_RFID_TYPE(fwrt->trans->hw_rf_id)) { |
| case IWL_CFG_RF_TYPE_HR1: |
| case IWL_CFG_RF_TYPE_HR2: |
| case IWL_CFG_RF_TYPE_JF1: |
| case IWL_CFG_RF_TYPE_JF2: |
| ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENABLE_INDONESIA_5G2, |
| &val); |
| |
| if (!ret && val == DSM_VALUE_INDONESIA_ENABLE) |
| config_bitmap |= |
| cpu_to_le32(LARI_CONFIG_ENABLE_5G2_IN_INDONESIA_MSK); |
| break; |
| default: |
| break; |
| } |
| |
| ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_DISABLE_SRD, &val); |
| if (!ret) { |
| if (val == DSM_VALUE_SRD_PASSIVE) |
| config_bitmap |= |
| cpu_to_le32(LARI_CONFIG_CHANGE_ETSI_TO_PASSIVE_MSK); |
| else if (val == DSM_VALUE_SRD_DISABLE) |
| config_bitmap |= |
| cpu_to_le32(LARI_CONFIG_CHANGE_ETSI_TO_DISABLED_MSK); |
| } |
| |
| if (fw_has_capa(&fwrt->fw->ucode_capa, |
| IWL_UCODE_TLV_CAPA_CHINA_22_REG_SUPPORT)) { |
| ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_REGULATORY_CONFIG, |
| &val); |
| /* |
| * China 2022 enable if the BIOS object does not exist or |
| * if it is enabled in BIOS. |
| */ |
| if (ret < 0 || val & DSM_MASK_CHINA_22_REG) |
| config_bitmap |= |
| cpu_to_le32(LARI_CONFIG_ENABLE_CHINA_22_REG_SUPPORT_MSK); |
| } |
| |
| return config_bitmap; |
| } |
| IWL_EXPORT_SYMBOL(iwl_get_lari_config_bitmap); |
| |
| int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func, |
| u32 *value) |
| { |
| GET_BIOS_TABLE(dsm, fwrt, func, value); |
| } |
| IWL_EXPORT_SYMBOL(iwl_bios_get_dsm); |