summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c')
-rw-r--r--drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c350
1 files changed, 350 insertions, 0 deletions
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
new file mode 100644
index 000000000000..303c0f5c9c66
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
@@ -0,0 +1,350 @@
+// 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);