| // SPDX-License-Identifier: ISC |
| /* Copyright (C) 2023 MediaTek Inc. */ |
| |
| #include <linux/acpi.h> |
| #include "mt792x.h" |
| |
| static int |
| mt792x_acpi_read(struct mt792x_dev *dev, u8 *method, u8 **tbl, u32 *len) |
| { |
| struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; |
| struct mt76_dev *mdev = &dev->mt76; |
| union acpi_object *sar_root; |
| acpi_handle root, handle; |
| acpi_status status; |
| u32 i = 0; |
| int ret; |
| |
| root = ACPI_HANDLE(mdev->dev); |
| if (!root) |
| return -EOPNOTSUPP; |
| |
| status = acpi_get_handle(root, method, &handle); |
| if (ACPI_FAILURE(status)) |
| return -EIO; |
| |
| status = acpi_evaluate_object(handle, NULL, NULL, &buf); |
| if (ACPI_FAILURE(status)) |
| return -EIO; |
| |
| sar_root = buf.pointer; |
| if (sar_root->type != ACPI_TYPE_PACKAGE || |
| sar_root->package.count < 4 || |
| sar_root->package.elements[0].type != ACPI_TYPE_INTEGER) { |
| dev_err(mdev->dev, "sar cnt = %d\n", |
| sar_root->package.count); |
| ret = -EINVAL; |
| goto free; |
| } |
| |
| if (!*tbl) { |
| *tbl = devm_kzalloc(mdev->dev, sar_root->package.count, |
| GFP_KERNEL); |
| if (!*tbl) { |
| ret = -ENOMEM; |
| goto free; |
| } |
| } |
| |
| if (len) |
| *len = sar_root->package.count; |
| |
| for (i = 0; i < sar_root->package.count; i++) { |
| union acpi_object *sar_unit = &sar_root->package.elements[i]; |
| |
| if (sar_unit->type != ACPI_TYPE_INTEGER) |
| break; |
| |
| *(*tbl + i) = (u8)sar_unit->integer.value; |
| } |
| |
| ret = i == sar_root->package.count ? 0 : -EINVAL; |
| free: |
| kfree(sar_root); |
| |
| return ret; |
| } |
| |
| /* MTCL : Country List Table for 6G band */ |
| static void |
| mt792x_asar_acpi_read_mtcl(struct mt792x_dev *dev, u8 **table, u8 *version) |
| { |
| if (mt792x_acpi_read(dev, MT792x_ACPI_MTCL, table, NULL) < 0) |
| *version = 1; |
| else |
| *version = 2; |
| } |
| |
| /* MTDS : Dynamic SAR Power Table */ |
| static int |
| mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version) |
| { |
| int len, ret, sarlen, prelen, tblcnt; |
| bool enable; |
| |
| ret = mt792x_acpi_read(dev, MT792x_ACPI_MTDS, table, &len); |
| if (ret) |
| return ret; |
| |
| /* Table content validation */ |
| switch (version) { |
| case 1: |
| enable = ((struct mt792x_asar_dyn *)*table)->enable; |
| sarlen = sizeof(struct mt792x_asar_dyn_limit); |
| prelen = sizeof(struct mt792x_asar_dyn); |
| break; |
| case 2: |
| enable = ((struct mt792x_asar_dyn_v2 *)*table)->enable; |
| sarlen = sizeof(struct mt792x_asar_dyn_limit_v2); |
| prelen = sizeof(struct mt792x_asar_dyn_v2); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| tblcnt = (len - prelen) / sarlen; |
| if (!enable || |
| tblcnt > MT792x_ASAR_MAX_DYN || tblcnt < MT792x_ASAR_MIN_DYN) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| /* MTGS : Geo SAR Power Table */ |
| static int |
| mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version) |
| { |
| int len, ret, sarlen, prelen, tblcnt; |
| |
| ret = mt792x_acpi_read(dev, MT792x_ACPI_MTGS, table, &len); |
| if (ret) |
| return ret; |
| |
| /* Table content validation */ |
| switch (version) { |
| case 1: |
| sarlen = sizeof(struct mt792x_asar_geo_limit); |
| prelen = sizeof(struct mt792x_asar_geo); |
| break; |
| case 2: |
| sarlen = sizeof(struct mt792x_asar_geo_limit_v2); |
| prelen = sizeof(struct mt792x_asar_geo_v2); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| tblcnt = (len - prelen) / sarlen; |
| if (tblcnt > MT792x_ASAR_MAX_GEO || tblcnt < MT792x_ASAR_MIN_GEO) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| /* MTFG : Flag Table */ |
| static int |
| mt792x_asar_acpi_read_mtfg(struct mt792x_dev *dev, u8 **table) |
| { |
| int len, ret; |
| |
| ret = mt792x_acpi_read(dev, MT792x_ACPI_MTFG, table, &len); |
| if (ret) |
| return ret; |
| |
| if (len < MT792x_ASAR_MIN_FG) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| int mt792x_init_acpi_sar(struct mt792x_dev *dev) |
| { |
| struct mt792x_acpi_sar *asar; |
| int ret; |
| |
| asar = devm_kzalloc(dev->mt76.dev, sizeof(*asar), GFP_KERNEL); |
| if (!asar) |
| return -ENOMEM; |
| |
| mt792x_asar_acpi_read_mtcl(dev, (u8 **)&asar->countrylist, &asar->ver); |
| |
| /* MTDS is mandatory. Return error if table is invalid */ |
| ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver); |
| if (ret) { |
| devm_kfree(dev->mt76.dev, asar->dyn); |
| devm_kfree(dev->mt76.dev, asar->countrylist); |
| devm_kfree(dev->mt76.dev, asar); |
| |
| return ret; |
| } |
| |
| /* MTGS is optional */ |
| ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver); |
| if (ret) { |
| devm_kfree(dev->mt76.dev, asar->geo); |
| asar->geo = NULL; |
| } |
| |
| /* MTFG is optional */ |
| ret = mt792x_asar_acpi_read_mtfg(dev, (u8 **)&asar->fg); |
| if (ret) { |
| devm_kfree(dev->mt76.dev, asar->fg); |
| asar->fg = NULL; |
| } |
| dev->phy.acpisar = asar; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mt792x_init_acpi_sar); |
| |
| static s8 |
| mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, |
| enum nl80211_band band, s8 dyn_power) |
| { |
| struct mt792x_acpi_sar *asar = phy->acpisar; |
| struct mt792x_asar_geo_band *band_pwr; |
| s8 geo_power; |
| u8 idx, max; |
| |
| if (!asar->geo) |
| return dyn_power; |
| |
| switch (phy->mt76->dev->region) { |
| case NL80211_DFS_FCC: |
| idx = 0; |
| break; |
| case NL80211_DFS_ETSI: |
| idx = 1; |
| break; |
| default: /* WW */ |
| idx = 2; |
| break; |
| } |
| |
| if (asar->ver == 1) { |
| band_pwr = &asar->geo->tbl[idx].band[0]; |
| max = ARRAY_SIZE(asar->geo->tbl[idx].band); |
| } else { |
| band_pwr = &asar->geo_v2->tbl[idx].band[0]; |
| max = ARRAY_SIZE(asar->geo_v2->tbl[idx].band); |
| } |
| |
| switch (band) { |
| case NL80211_BAND_2GHZ: |
| idx = 0; |
| break; |
| case NL80211_BAND_5GHZ: |
| idx = 1; |
| break; |
| case NL80211_BAND_6GHZ: |
| idx = 2; |
| break; |
| default: |
| return dyn_power; |
| } |
| |
| if (idx >= max) |
| return dyn_power; |
| |
| geo_power = (band_pwr + idx)->pwr; |
| dyn_power += (band_pwr + idx)->offset; |
| |
| return min(geo_power, dyn_power); |
| } |
| |
| static s8 |
| mt792x_asar_range_pwr(struct mt792x_phy *phy, |
| const struct cfg80211_sar_freq_ranges *range, |
| u8 idx) |
| { |
| const struct cfg80211_sar_capa *capa = phy->mt76->hw->wiphy->sar_capa; |
| struct mt792x_acpi_sar *asar = phy->acpisar; |
| u8 *limit, band, max; |
| |
| if (!capa) |
| return 127; |
| |
| if (asar->ver == 1) { |
| limit = &asar->dyn->tbl[0].frp[0]; |
| max = ARRAY_SIZE(asar->dyn->tbl[0].frp); |
| } else { |
| limit = &asar->dyn_v2->tbl[0].frp[0]; |
| max = ARRAY_SIZE(asar->dyn_v2->tbl[0].frp); |
| } |
| |
| if (idx >= max) |
| return 127; |
| |
| if (range->start_freq >= 5945) |
| band = NL80211_BAND_6GHZ; |
| else if (range->start_freq >= 5150) |
| band = NL80211_BAND_5GHZ; |
| else |
| band = NL80211_BAND_2GHZ; |
| |
| return mt792x_asar_get_geo_pwr(phy, band, limit[idx]); |
| } |
| |
| int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) |
| { |
| const struct cfg80211_sar_capa *capa = phy->mt76->hw->wiphy->sar_capa; |
| int i; |
| |
| if (!phy->acpisar) |
| return 0; |
| |
| /* When ACPI SAR enabled in HW, we should apply rules for .frp |
| * 1. w/o .sar_specs : set ACPI SAR power as the defatul value |
| * 2. w/ .sar_specs : set power with min(.sar_specs, ACPI_SAR) |
| */ |
| for (i = 0; i < capa->num_freq_ranges; i++) { |
| struct mt76_freq_range_power *frp = &phy->mt76->frp[i]; |
| |
| frp->range = set_default ? &capa->freq_ranges[i] : frp->range; |
| if (!frp->range) |
| continue; |
| |
| frp->power = min_t(s8, set_default ? 127 : frp->power, |
| mt792x_asar_range_pwr(phy, frp->range, i)); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mt792x_init_acpi_sar_power); |
| |
| u8 mt792x_acpi_get_flags(struct mt792x_phy *phy) |
| { |
| struct mt792x_acpi_sar *acpisar = phy->acpisar; |
| struct mt792x_asar_fg *fg; |
| struct { |
| u8 acpi_idx; |
| u8 chip_idx; |
| } map[] = { |
| { 1, 1 }, |
| { 4, 2 }, |
| }; |
| u8 flags = BIT(0); |
| int i, j; |
| |
| if (!acpisar) |
| return 0; |
| |
| fg = acpisar->fg; |
| if (!fg) |
| return flags; |
| |
| /* pickup necessary settings per device and |
| * translate the index of bitmap for chip command. |
| */ |
| for (i = 0; i < fg->nr_flag; i++) { |
| for (j = 0; j < ARRAY_SIZE(map); j++) { |
| if (fg->flag[i] == map[j].acpi_idx) { |
| flags |= BIT(map[j].chip_idx); |
| break; |
| } |
| } |
| } |
| |
| return flags; |
| } |
| EXPORT_SYMBOL_GPL(mt792x_acpi_get_flags); |
| |
| static u8 |
| mt792x_acpi_get_mtcl_map(int row, int column, struct mt792x_asar_cl *cl) |
| { |
| u8 config = 0; |
| |
| if (cl->cl6g[row] & BIT(column)) |
| config |= (cl->mode_6g & 0x3) << 2; |
| if (cl->version > 1 && cl->cl5g9[row] & BIT(column)) |
| config |= (cl->mode_5g9 & 0x3); |
| |
| return config; |
| } |
| |
| u8 mt792x_acpi_get_mtcl_conf(struct mt792x_phy *phy, char *alpha2) |
| { |
| static const char * const cc_list_all[] = { |
| "00", "EU", "AR", "AU", "AZ", "BY", "BO", "BR", |
| "CA", "CL", "CN", "ID", "JP", "MY", "MX", "ME", |
| "MA", "NZ", "NG", "PH", "RU", "RS", "SG", "KR", |
| "TW", "TH", "UA", "GB", "US", "VN", "KH", "PY", |
| }; |
| static const char * const cc_list_eu[] = { |
| "AT", "BE", "BG", "CY", "CZ", "HR", "DK", "EE", |
| "FI", "FR", "DE", "GR", "HU", "IS", "IE", "IT", |
| "LV", "LI", "LT", "LU", "MT", "NL", "NO", "PL", |
| "PT", "RO", "MT", "SK", "SI", "ES", "CH", |
| }; |
| struct mt792x_acpi_sar *sar = phy->acpisar; |
| struct mt792x_asar_cl *cl; |
| int col, row, i; |
| |
| if (!sar) |
| return 0xf; |
| |
| cl = sar->countrylist; |
| if (!cl) |
| return 0xc; |
| |
| for (i = 0; i < ARRAY_SIZE(cc_list_all); i++) { |
| col = 7 - i % 8; |
| row = i / 8; |
| if (!memcmp(cc_list_all[i], alpha2, 2)) |
| return mt792x_acpi_get_mtcl_map(row, col, cl); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(cc_list_eu); i++) |
| if (!memcmp(cc_list_eu[i], alpha2, 2)) |
| return mt792x_acpi_get_mtcl_map(0, 6, cl); |
| |
| return mt792x_acpi_get_mtcl_map(0, 7, cl); |
| } |
| EXPORT_SYMBOL_GPL(mt792x_acpi_get_mtcl_conf); |