From: Mario Limonciello <mario.limoncie...@amd.com>

Due to electrical and mechanical constraints in certain platform designs
there may be likely interference of relatively high-powered harmonics of
the (G-)DDR memory clocks with local radio module frequency bands used
by Wifi 6/6e/7.

To mitigate this, AMD has introduced an ACPI based mechanism that
devices can use to notify active use of particular frequencies so
that devices can make relative internal adjustments as necessary
to avoid this resonance.

In order for a device to support this, the expected flow for device
driver or subsystems:

Drivers/subsystems contributing frequencies:

1) During probe, check `wbrf_supported_producer` to see if WBRF supported
   for the device.
2) If adding frequencies, then call `wbrf_add_exclusion` with the
   start and end ranges of the frequencies. For net/wireless subsystem
   specifically, `wbrf_add_exclusion_wlan` can be used instead.
3) If removing frequencies, then call `wbrf_remove_exclusion` with
   start and end ranges of the frequencies. For net/wireless subsystem
   specifically, `wbrf_remove_exclusion_wlan` can be used instead.

Drivers/subsystems responding to frequencies:

1) During probe, check `wbrf_supported_consumer` to see if WBRF is supported
   for the device.
2) Call the `wbrf_retrieve_exclusions` to retrieve the current
   exclusions on receiving an ACPI notification for a new frequency
   change.

Signed-off-by: Mario Limonciello <mario.limoncie...@amd.com>
Co-developed-by: Evan Quan <evan.q...@amd.com>
Signed-off-by: Evan Quan <evan.q...@amd.com>
--
v1->v2:
  - update wifi_acpi_dsm_guid and wbrf_record to fit latest designs
  - the frequency range fed to BIOS is expected to be in HZ unit
v2->v3:
  - correct the algorithm for frequency range start and end point
    calculation
  - drop unneeded notifier register/unregister APIs
v3->v4:
  - concentrate all wbrf ACPI related code in acpi_wbrf.c
v4->v5:
  - typo and some other minor fixes(Mario)
v5->v6:
  - correct the calculations around start/end frequency points
v6->v7:
  - correct the data type for arg3 to fit ACPI spce
---
 drivers/acpi/Kconfig     |   7 +
 drivers/acpi/Makefile    |   2 +
 drivers/acpi/acpi_wbrf.c | 343 +++++++++++++++++++++++++++++++++++++++
 include/linux/wbrf.h     |  70 ++++++++
 4 files changed, 422 insertions(+)
 create mode 100644 drivers/acpi/acpi_wbrf.c
 create mode 100644 include/linux/wbrf.h

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index ccbeab9500ec..9ee7c7dcc3e6 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -611,3 +611,10 @@ config X86_PM_TIMER
 
          You should nearly always say Y here because many modern
          systems require this timer.
+
+config ACPI_WBRF
+       bool "ACPI Wifi band RF mitigation mechanism"
+       help
+         Wifi band RF mitigation mechanism allows multiple drivers from
+         different domains to notify the frequencies in use so that hardware
+         can be reconfigured to avoid harmonic conflicts.
\ No newline at end of file
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index feb36c0b9446..be173e76aa62 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -131,3 +131,5 @@ obj-y                               += dptf/
 obj-$(CONFIG_ARM64)            += arm64/
 
 obj-$(CONFIG_ACPI_VIOT)                += viot.o
+
+obj-$(CONFIG_ACPI_WBRF)                += acpi_wbrf.o
\ No newline at end of file
diff --git a/drivers/acpi/acpi_wbrf.c b/drivers/acpi/acpi_wbrf.c
new file mode 100644
index 000000000000..bf8e0ed73072
--- /dev/null
+++ b/drivers/acpi/acpi_wbrf.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Wifi Band Exclusion Interface
+ * Copyright (C) 2023 Advanced Micro Devices
+ *
+ */
+
+#include <linux/wbrf.h>
+
+/* functions */
+#define WBRF_RECORD            0x1
+#define WBRF_RETRIEVE          0x2
+
+/* record actions */
+#define WBRF_RECORD_ADD                0x0
+#define WBRF_RECORD_REMOVE     0x1
+
+#define WBRF_REVISION          0x1
+
+#define KHZ_TO_HZ(freq)                ((freq) * 1000ULL)
+
+static const guid_t wifi_acpi_dsm_guid =
+       GUID_INIT(0x7b7656cf, 0xdc3d, 0x4c1c,
+                 0x83, 0xe9, 0x66, 0xe7, 0x21, 0xde, 0x30, 0x70);
+
+static int wbrf_dsm(struct acpi_device *adev, u8 fn,
+                   union acpi_object *argv4,
+                   union acpi_object **out)
+{
+       union acpi_object *obj;
+       int rc;
+
+       obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
+                               WBRF_REVISION, fn, argv4);
+       if (!obj)
+               return -ENXIO;
+
+       switch (obj->type) {
+       case ACPI_TYPE_BUFFER:
+               if (!*out) {
+                       rc = -EINVAL;
+                       break;
+               }
+               *out = obj;
+               return 0;
+
+       case ACPI_TYPE_INTEGER:
+               rc =  obj->integer.value ? -EINVAL : 0;
+               break;
+       default:
+               rc = -EOPNOTSUPP;
+       }
+       ACPI_FREE(obj);
+
+       return rc;
+}
+
+static int wbrf_record(struct acpi_device *adev, uint8_t action,
+                      struct wbrf_ranges_in *in)
+{
+       union acpi_object *argv4;
+       uint32_t num_of_ranges = 0;
+       uint32_t arg_idx = 0;
+       uint32_t loop_idx;
+       int ret;
+
+       if (!in)
+               return -EINVAL;
+
+       for (loop_idx = 0; loop_idx < ARRAY_SIZE(in->band_list);
+            loop_idx++)
+               if (in->band_list[loop_idx].start &&
+                   in->band_list[loop_idx].end)
+                       num_of_ranges++;
+
+       argv4 = kzalloc(sizeof(*argv4) * (2 * num_of_ranges + 2 + 1), 
GFP_KERNEL);
+       if (!argv4)
+               return -ENOMEM;
+
+       argv4[arg_idx].package.type = ACPI_TYPE_PACKAGE;
+       argv4[arg_idx].package.count = 2 + 2 * num_of_ranges;
+       argv4[arg_idx++].package.elements = &argv4[1];
+       argv4[arg_idx].integer.type = ACPI_TYPE_INTEGER;
+       argv4[arg_idx++].integer.value = num_of_ranges;
+       argv4[arg_idx].integer.type = ACPI_TYPE_INTEGER;
+       argv4[arg_idx++].integer.value = action;
+
+       for (loop_idx = 0; loop_idx < ARRAY_SIZE(in->band_list);
+            loop_idx++) {
+               if (!in->band_list[loop_idx].start ||
+                   !in->band_list[loop_idx].end)
+                       continue;
+
+               argv4[arg_idx].integer.type = ACPI_TYPE_INTEGER;
+               argv4[arg_idx++].integer.value = in->band_list[loop_idx].start;
+               argv4[arg_idx].integer.type = ACPI_TYPE_INTEGER;
+               argv4[arg_idx++].integer.value = in->band_list[loop_idx].end;
+       }
+
+       ret = wbrf_dsm(adev, WBRF_RECORD, argv4, NULL);
+
+       kfree(argv4);
+
+       return ret;
+}
+
+int wbrf_add_exclusion(struct acpi_device *adev,
+                      struct wbrf_ranges_in *in)
+{
+       return wbrf_record(adev, WBRF_RECORD_ADD, in);
+}
+EXPORT_SYMBOL_GPL(wbrf_add_exclusion);
+
+int wbrf_remove_exclusion(struct acpi_device *adev,
+                         struct wbrf_ranges_in *in)
+{
+       return wbrf_record(adev, WBRF_RECORD_REMOVE, in);
+}
+EXPORT_SYMBOL_GPL(wbrf_remove_exclusion);
+
+static int chan_width_to_mhz(enum nl80211_chan_width chan_width)
+{
+       int mhz;
+
+       switch (chan_width) {
+       case NL80211_CHAN_WIDTH_1:
+               mhz = 1;
+               break;
+       case NL80211_CHAN_WIDTH_2:
+               mhz = 2;
+               break;
+       case NL80211_CHAN_WIDTH_4:
+               mhz = 4;
+               break;
+       case NL80211_CHAN_WIDTH_8:
+               mhz = 8;
+               break;
+       case NL80211_CHAN_WIDTH_16:
+               mhz = 16;
+               break;
+       case NL80211_CHAN_WIDTH_5:
+               mhz = 5;
+               break;
+       case NL80211_CHAN_WIDTH_10:
+               mhz = 10;
+               break;
+       case NL80211_CHAN_WIDTH_20:
+       case NL80211_CHAN_WIDTH_20_NOHT:
+               mhz = 20;
+               break;
+       case NL80211_CHAN_WIDTH_40:
+               mhz = 40;
+               break;
+       case NL80211_CHAN_WIDTH_80P80:
+       case NL80211_CHAN_WIDTH_80:
+               mhz = 80;
+               break;
+       case NL80211_CHAN_WIDTH_160:
+               mhz = 160;
+               break;
+       case NL80211_CHAN_WIDTH_320:
+               mhz = 320;
+               break;
+       default:
+               WARN_ON_ONCE(1);
+               return -1;
+       }
+       return mhz;
+}
+
+static void get_chan_freq_boundary(u32 center_freq,
+                                  u32 bandwidth,
+                                  u64 *start,
+                                  u64 *end)
+{
+       bandwidth = MHZ_TO_KHZ(bandwidth);
+       center_freq = MHZ_TO_KHZ(center_freq);
+
+       *start = center_freq - bandwidth / 2;
+       *end = center_freq + bandwidth / 2;
+
+       /* Frequency in HZ is expected in BIOS */
+       *start = KHZ_TO_HZ(*start);
+       *end = KHZ_TO_HZ(*end);
+}
+
+static int wbrf_get_ranges_from_chandef(struct cfg80211_chan_def *chandef,
+                                       struct wbrf_ranges_in *ranges_in)
+{
+       u64 start_freq1, end_freq1;
+       u64 start_freq2, end_freq2;
+       int bandwidth;
+
+       bandwidth = chan_width_to_mhz(chandef->width);
+       if (bandwidth < 0)
+               return -EINVAL;
+
+       get_chan_freq_boundary(chandef->center_freq1,
+                              bandwidth,
+                              &start_freq1,
+                              &end_freq1);
+
+       ranges_in->band_list[0].start = start_freq1;
+       ranges_in->band_list[0].end = end_freq1;
+
+       if (chandef->width == NL80211_CHAN_WIDTH_80P80) {
+               get_chan_freq_boundary(chandef->center_freq2,
+                                      bandwidth,
+                                      &start_freq2,
+                                      &end_freq2);
+
+               ranges_in->band_list[1].start = start_freq2;
+               ranges_in->band_list[1].end = end_freq2;
+       }
+
+       return 0;
+}
+
+int wbrf_add_exclusion_wlan(struct acpi_device *adev,
+                           struct cfg80211_chan_def *chandef)
+{
+       struct wbrf_ranges_in ranges_in = {0};
+       int ret;
+
+       ret = wbrf_get_ranges_from_chandef(chandef, &ranges_in);
+       if (ret)
+               return ret;
+
+       return wbrf_add_exclusion(adev, &ranges_in);
+}
+EXPORT_SYMBOL_GPL(wbrf_add_exclusion_wlan);
+
+int wbrf_remove_exclusion_wlan(struct acpi_device *adev,
+                              struct cfg80211_chan_def *chandef)
+{
+       struct wbrf_ranges_in ranges_in = {0};
+       int ret;
+
+       ret = wbrf_get_ranges_from_chandef(chandef, &ranges_in);
+       if (ret)
+               return ret;
+
+       return wbrf_remove_exclusion(adev, &ranges_in);
+}
+EXPORT_SYMBOL_GPL(wbrf_remove_exclusion_wlan);
+
+bool wbrf_supported_producer(struct acpi_device *adev)
+{
+       return acpi_check_dsm(adev->handle, &wifi_acpi_dsm_guid,
+                             WBRF_REVISION,
+                             (1ULL << WBRF_RECORD) | (1ULL << WBRF_RETRIEVE));
+}
+EXPORT_SYMBOL_GPL(wbrf_supported_producer);
+
+static union acpi_object *
+acpi_evaluate_wbrf(acpi_handle handle, u64 rev, u64 func)
+{
+       acpi_status ret;
+       struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
+       union acpi_object params[4];
+       struct acpi_object_list input = {
+               .count = 4,
+               .pointer = params,
+       };
+
+       params[0].type = ACPI_TYPE_INTEGER;
+       params[0].integer.value = rev;
+       params[1].type = ACPI_TYPE_INTEGER;
+       params[1].integer.value = func;
+       params[2].type = ACPI_TYPE_PACKAGE;
+       params[2].package.count = 0;
+       params[2].package.elements = NULL;
+       params[3].type = ACPI_TYPE_STRING;
+       params[3].string.length = 0;
+       params[3].string.pointer= NULL;
+
+       ret = acpi_evaluate_object(handle, "WBRF", &input, &buf);
+       if (ACPI_SUCCESS(ret))
+               return (union acpi_object *)buf.pointer;
+
+       if (ret != AE_NOT_FOUND)
+               acpi_handle_warn(handle,
+                                "failed to evaluate WBRF(0x%x)\n", ret);
+
+       return NULL;
+}
+
+static bool check_acpi_wbrf(acpi_handle handle, u64 rev, u64 funcs)
+{
+       int i;
+       u64 mask = 0;
+       union acpi_object *obj;
+
+       if (funcs == 0)
+               return false;
+
+       obj = acpi_evaluate_wbrf(handle, rev, 0);
+       if (!obj)
+               return false;
+
+       if (obj->type != ACPI_TYPE_BUFFER)
+               return false;
+
+       for (i = 0; i < obj->buffer.length && i < 8; i++)
+               mask |= (((u64)obj->buffer.pointer[i]) << (i * 8));
+       ACPI_FREE(obj);
+
+       /*
+        * Bit 0 indicates whether there's support for any functions other than
+        * function 0.
+        */
+       if ((mask & 0x1) && (mask & funcs) == funcs)
+               return true;
+
+       return false;
+}
+
+bool wbrf_supported_consumer(struct acpi_device *adev)
+{
+       return check_acpi_wbrf(adev->handle,
+                              WBRF_REVISION,
+                              1ULL << WBRF_RETRIEVE);
+}
+EXPORT_SYMBOL_GPL(wbrf_supported_consumer);
+
+int wbrf_retrieve_exclusions(struct acpi_device *adev,
+                            struct wbrf_ranges_out *exclusions_out)
+{
+       union acpi_object *obj;
+
+       obj = acpi_evaluate_wbrf(adev->handle,
+                                WBRF_REVISION,
+                                WBRF_RETRIEVE);
+       if (!obj)
+               return -EINVAL;
+
+       memcpy(exclusions_out, obj->buffer.pointer, obj->buffer.length);
+
+       ACPI_FREE(obj);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(wbrf_retrieve_exclusions);
diff --git a/include/linux/wbrf.h b/include/linux/wbrf.h
new file mode 100644
index 000000000000..c77766069d73
--- /dev/null
+++ b/include/linux/wbrf.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Wifi Band Exclusion Interface
+ * Copyright (C) 2023 Advanced Micro Devices
+ */
+
+#ifndef _LINUX_WBRF_H
+#define _LINUX_WBRF_H
+
+#include <linux/acpi.h>
+#include <net/cfg80211.h>
+
+/* Maximum number of wbrf ranges */
+#define MAX_NUM_OF_WBRF_RANGES         11
+
+struct exclusion_range {
+       uint64_t        start;
+       uint64_t        end;
+};
+
+struct wbrf_ranges_in {
+       /* valid entry: `start` and `end` filled with non-zero values */
+       struct exclusion_range  band_list[MAX_NUM_OF_WBRF_RANGES];
+};
+
+struct wbrf_ranges_out {
+       uint32_t                num_of_ranges;
+       struct exclusion_range  band_list[MAX_NUM_OF_WBRF_RANGES];
+} __attribute__((packed));
+
+/**
+ * APIs needed by drivers/subsystems for contributing frequencies:
+ * During probe, check `wbrf_supported_producer` to see if WBRF is supported.
+ * If adding frequencies, then call `wbrf_add_exclusion` with the
+ * start and end points specified for the frequency ranges added.
+ * If removing frequencies, then call `wbrf_remove_exclusion` with
+ * start and end points specified for the frequency ranges added.
+ */
+bool wbrf_supported_producer(struct acpi_device *adev);
+int wbrf_add_exclusion(struct acpi_device *adev,
+                      struct wbrf_ranges_in *in);
+int wbrf_remove_exclusion(struct acpi_device *adev,
+                         struct wbrf_ranges_in *in);
+/**
+ * The following two APIs are created specially for net/wireless subsystem(
+ * a main player for contributing frequencies) for adding and removing
+ * frequencies:
+ * If adding frequencies, then call `wbrf_add_exclusion_wlan` with
+ * `struct cfg80211_chan_def` which defines the center frequency points
+ * and bandwidth.
+ * If removing frequencies, then call `wbrf_remove_exclusion_wlan` with
+ * `struct cfg80211_chan_def` which defines the center frequency points
+ * and bandwidth.
+ */
+int wbrf_add_exclusion_wlan(struct acpi_device *adev,
+                           struct cfg80211_chan_def *chandef);
+int wbrf_remove_exclusion_wlan(struct acpi_device *adev,
+                              struct cfg80211_chan_def *chandef);
+
+/**
+ * APIs needed by drivers/subsystems responding to frequencies:
+ * During probe, check `wbrf_supported_consumer` to see if WBRF is supported.
+ * When receiving an ACPI notification for some frequencies change, run
+ * `wbrf_retrieve_exclusions` to retrieve the latest frequencies ranges.
+ */
+int wbrf_retrieve_exclusions(struct acpi_device *adev,
+                            struct wbrf_ranges_out *out);
+bool wbrf_supported_consumer(struct acpi_device *adev);
+
+#endif /* _LINUX_WBRF_H */
-- 
2.34.1

Reply via email to