root/drivers/net/wireless/realtek/rtw89/acpi.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright(c) 2021-2023  Realtek Corporation
 */

#include <linux/acpi.h>
#include <linux/uuid.h>

#include "acpi.h"
#include "debug.h"

static const guid_t rtw89_guid = GUID_INIT(0xD2A8C3E8, 0x4B69, 0x4F00,
                                           0x82, 0xBD, 0xFE, 0x86,
                                           0x07, 0x80, 0x3A, 0xA7);

static u32 rtw89_acpi_traversal_object(struct rtw89_dev *rtwdev,
                                       const union acpi_object *obj, u8 *pos)
{
        const union acpi_object *elm;
        unsigned int i;
        u32 sub_len;
        u32 len = 0;
        u8 *tmp;

        switch (obj->type) {
        case ACPI_TYPE_INTEGER:
                if (pos)
                        pos[len] = obj->integer.value;

                len++;
                break;
        case ACPI_TYPE_BUFFER:
                if (unlikely(obj->buffer.length == 0)) {
                        rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                                    "%s: invalid buffer type\n", __func__);
                        goto err;
                }

                if (pos)
                        memcpy(pos, obj->buffer.pointer, obj->buffer.length);

                len += obj->buffer.length;
                break;
        case ACPI_TYPE_PACKAGE:
                if (unlikely(obj->package.count == 0)) {
                        rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                                    "%s: invalid package type\n", __func__);
                        goto err;
                }

                for (i = 0; i < obj->package.count; i++) {
                        elm = &obj->package.elements[i];
                        tmp = pos ? pos + len : NULL;

                        sub_len = rtw89_acpi_traversal_object(rtwdev, elm, tmp);
                        if (unlikely(sub_len == 0))
                                goto err;

                        len += sub_len;
                }
                break;
        default:
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: unhandled type: %d\n",
                            __func__, obj->type);
                goto err;
        }

        return len;

err:
        return 0;
}

static u32 rtw89_acpi_calculate_object_length(struct rtw89_dev *rtwdev,
                                              const union acpi_object *obj)
{
        return rtw89_acpi_traversal_object(rtwdev, obj, NULL);
}

static struct rtw89_acpi_data *
rtw89_acpi_evaluate_method(struct rtw89_dev *rtwdev, const char *method)
{
        struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
        struct rtw89_acpi_data *data = NULL;
        acpi_handle root, handle;
        union acpi_object *obj;
        acpi_status status;
        u32 len;

        root = ACPI_HANDLE(rtwdev->dev);
        if (!root) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi (%s): failed to get root\n", method);
                return NULL;
        }

        status = acpi_get_handle(root, (acpi_string)method, &handle);
        if (ACPI_FAILURE(status)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi (%s): failed to get handle\n", method);
                return NULL;
        }

        status = acpi_evaluate_object(handle, NULL, NULL, &buf);
        if (ACPI_FAILURE(status)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi (%s): failed to evaluate object\n", method);
                return NULL;
        }

        obj = buf.pointer;
        len = rtw89_acpi_calculate_object_length(rtwdev, obj);
        if (unlikely(len == 0)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi (%s): failed to traversal obj len\n", method);
                goto out;
        }

        data = kzalloc_flex(*data, buf, len);
        if (!data)
                goto out;

        data->len = len;
        rtw89_acpi_traversal_object(rtwdev, obj, data->buf);

out:
        ACPI_FREE(obj);
        return data;
}

static
int rtw89_acpi_dsm_get_value(struct rtw89_dev *rtwdev, union acpi_object *obj,
                             u8 *value)
{
        if (obj->type != ACPI_TYPE_INTEGER) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi: expect integer but type: %d\n", obj->type);
                return -EINVAL;
        }

        *value = (u8)obj->integer.value;
        return 0;
}

static bool chk_acpi_policy_6ghz_sig(const struct rtw89_acpi_policy_6ghz *p)
{
        return p->signature[0] == 0x00 &&
               p->signature[1] == 0xE0 &&
               p->signature[2] == 0x4C;
}

static
int rtw89_acpi_dsm_get_policy_6ghz(struct rtw89_dev *rtwdev,
                                   union acpi_object *obj,
                                   struct rtw89_acpi_policy_6ghz **policy_6ghz)
{
        const struct rtw89_acpi_policy_6ghz *ptr;
        u32 expect_len;
        u32 len;

        if (obj->type != ACPI_TYPE_BUFFER) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi: expect buffer but type: %d\n", obj->type);
                return -EINVAL;
        }

        len = obj->buffer.length;
        if (len < sizeof(*ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: invalid buffer length: %u\n",
                            __func__, len);
                return -EINVAL;
        }

        ptr = (typeof(ptr))obj->buffer.pointer;
        if (!chk_acpi_policy_6ghz_sig(ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: bad signature\n", __func__);
                return -EINVAL;
        }

        expect_len = struct_size(ptr, country_list, ptr->country_count);
        if (len < expect_len) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: expect %u but length: %u\n",
                            __func__, expect_len, len);
                return -EINVAL;
        }

        *policy_6ghz = kmemdup(ptr, expect_len, GFP_KERNEL);
        if (!*policy_6ghz)
                return -ENOMEM;

        rtw89_hex_dump(rtwdev, RTW89_DBG_ACPI, "policy_6ghz: ", *policy_6ghz,
                       expect_len);
        return 0;
}

static bool chk_acpi_policy_6ghz_sp_sig(const struct rtw89_acpi_policy_6ghz_sp *p)
{
        return p->signature[0] == 0x52 &&
               p->signature[1] == 0x54 &&
               p->signature[2] == 0x4B &&
               p->signature[3] == 0x07;
}

static
int rtw89_acpi_dsm_get_policy_6ghz_sp(struct rtw89_dev *rtwdev,
                                      union acpi_object *obj,
                                      struct rtw89_acpi_policy_6ghz_sp **policy)
{
        const struct rtw89_acpi_policy_6ghz_sp *ptr;
        u32 buf_len;

        if (obj->type != ACPI_TYPE_BUFFER) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi: expect buffer but type: %d\n", obj->type);
                return -EINVAL;
        }

        buf_len = obj->buffer.length;
        if (buf_len < sizeof(*ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: invalid buffer length: %u\n",
                            __func__, buf_len);
                return -EINVAL;
        }

        ptr = (typeof(ptr))obj->buffer.pointer;
        if (!chk_acpi_policy_6ghz_sp_sig(ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: bad signature\n", __func__);
                return -EINVAL;
        }

        *policy = kmemdup(ptr, sizeof(*ptr), GFP_KERNEL);
        if (!*policy)
                return -ENOMEM;

        rtw89_hex_dump(rtwdev, RTW89_DBG_ACPI, "policy_6ghz_sp: ", *policy,
                       sizeof(*ptr));
        return 0;
}

static bool chk_acpi_policy_6ghz_vlp_sig(const struct rtw89_acpi_policy_6ghz_vlp *p)
{
        return p->signature[0] == 0x52 &&
               p->signature[1] == 0x54 &&
               p->signature[2] == 0x4B &&
               p->signature[3] == 0x0B;
}

static
int rtw89_acpi_dsm_get_policy_6ghz_vlp(struct rtw89_dev *rtwdev,
                                       union acpi_object *obj,
                                       struct rtw89_acpi_policy_6ghz_vlp **policy)
{
        const struct rtw89_acpi_policy_6ghz_vlp *ptr;
        u32 buf_len;

        if (obj->type != ACPI_TYPE_BUFFER) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi: expect buffer but type: %d\n", obj->type);
                return -EINVAL;
        }

        buf_len = obj->buffer.length;
        if (buf_len < sizeof(*ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: invalid buffer length: %u\n",
                            __func__, buf_len);
                return -EINVAL;
        }

        ptr = (typeof(ptr))obj->buffer.pointer;
        if (!chk_acpi_policy_6ghz_vlp_sig(ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: bad signature\n", __func__);
                return -EINVAL;
        }

        *policy = kmemdup(ptr, sizeof(*ptr), GFP_KERNEL);
        if (!*policy)
                return -ENOMEM;

        rtw89_hex_dump(rtwdev, RTW89_DBG_ACPI, "policy_6ghz_vlp: ", *policy,
                       sizeof(*ptr));
        return 0;
}

static bool chk_acpi_policy_tas_sig(const struct rtw89_acpi_policy_tas *p)
{
        return p->signature[0] == 0x52 &&
               p->signature[1] == 0x54 &&
               p->signature[2] == 0x4B &&
               p->signature[3] == 0x05;
}

static int rtw89_acpi_dsm_get_policy_tas(struct rtw89_dev *rtwdev,
                                         union acpi_object *obj,
                                         struct rtw89_acpi_policy_tas **policy)
{
        const struct rtw89_acpi_policy_tas *ptr;
        u32 buf_len;

        if (obj->type != ACPI_TYPE_BUFFER) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi: expect buffer but type: %d\n", obj->type);
                return -EINVAL;
        }

        buf_len = obj->buffer.length;
        if (buf_len < sizeof(*ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: invalid buffer length: %u\n",
                            __func__, buf_len);
                return -EINVAL;
        }

        ptr = (typeof(ptr))obj->buffer.pointer;
        if (!chk_acpi_policy_tas_sig(ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: bad signature\n", __func__);
                return -EINVAL;
        }

        *policy = kmemdup(ptr, sizeof(*ptr), GFP_KERNEL);
        if (!*policy)
                return -ENOMEM;

        rtw89_hex_dump(rtwdev, RTW89_DBG_ACPI, "policy_tas: ", *policy,
                       sizeof(*ptr));
        return 0;
}

static
bool chk_acpi_policy_reg_rules_sig(const struct rtw89_acpi_policy_reg_rules *p)
{
        return p->signature[0] == 0x52 &&
               p->signature[1] == 0x54 &&
               p->signature[2] == 0x4B &&
               p->signature[3] == 0x0A;
}

static
int rtw89_acpi_dsm_get_policy_reg_rules(struct rtw89_dev *rtwdev,
                                        union acpi_object *obj,
                                        struct rtw89_acpi_policy_reg_rules **policy)
{
        const struct rtw89_acpi_policy_reg_rules *ptr;
        u32 buf_len;

        if (obj->type != ACPI_TYPE_BUFFER) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi: expect buffer but type: %d\n", obj->type);
                return -EINVAL;
        }

        buf_len = obj->buffer.length;
        if (buf_len < sizeof(*ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: invalid buffer length: %u\n",
                            __func__, buf_len);
                return -EINVAL;
        }

        ptr = (typeof(ptr))obj->buffer.pointer;
        if (!chk_acpi_policy_reg_rules_sig(ptr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: bad signature\n", __func__);
                return -EINVAL;
        }

        *policy = kmemdup(ptr, sizeof(*ptr), GFP_KERNEL);
        if (!*policy)
                return -ENOMEM;

        rtw89_hex_dump(rtwdev, RTW89_DBG_ACPI, "policy_reg_rules: ", *policy,
                       sizeof(*ptr));
        return 0;
}

int rtw89_acpi_evaluate_dsm(struct rtw89_dev *rtwdev,
                            enum rtw89_acpi_dsm_func func,
                            struct rtw89_acpi_dsm_result *res)
{
        union acpi_object *obj;
        int ret;

        obj = acpi_evaluate_dsm(ACPI_HANDLE(rtwdev->dev), &rtw89_guid,
                                0, func, NULL);
        if (!obj) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "acpi dsm fail to evaluate func: %d\n", func);
                return -ENOENT;
        }

        if (func == RTW89_ACPI_DSM_FUNC_6G_BP)
                ret = rtw89_acpi_dsm_get_policy_6ghz(rtwdev, obj,
                                                     &res->u.policy_6ghz);
        else if (func == RTW89_ACPI_DSM_FUNC_6GHZ_SP_SUP)
                ret = rtw89_acpi_dsm_get_policy_6ghz_sp(rtwdev, obj,
                                                        &res->u.policy_6ghz_sp);
        else if (func == RTW89_ACPI_DSM_FUNC_6GHZ_VLP_SUP)
                ret = rtw89_acpi_dsm_get_policy_6ghz_vlp(rtwdev, obj,
                                                         &res->u.policy_6ghz_vlp);
        else if (func == RTW89_ACPI_DSM_FUNC_TAS_EN)
                ret = rtw89_acpi_dsm_get_policy_tas(rtwdev, obj, &res->u.policy_tas);
        else if (func == RTW89_ACPI_DSM_FUNC_REG_RULES_EN)
                ret = rtw89_acpi_dsm_get_policy_reg_rules(rtwdev, obj,
                                                          &res->u.policy_reg_rules);
        else
                ret = rtw89_acpi_dsm_get_value(rtwdev, obj, &res->u.value);

        ACPI_FREE(obj);
        return ret;
}

int rtw89_acpi_evaluate_rtag(struct rtw89_dev *rtwdev,
                             struct rtw89_acpi_rtag_result *res)
{
        const struct rtw89_acpi_data *data;
        u32 buf_len;
        int ret = 0;

        data = rtw89_acpi_evaluate_method(rtwdev, "RTAG");
        if (!data)
                return -EIO;

        buf_len = data->len;
        if (buf_len != sizeof(*res)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "%s: invalid buffer length: %u\n",
                            __func__, buf_len);
                ret = -EINVAL;
                goto out;
        }

        *res = *(struct rtw89_acpi_rtag_result *)data->buf;

        rtw89_hex_dump(rtwdev, RTW89_DBG_ACPI, "antenna_gain: ", res, sizeof(*res));

out:
        kfree(data);
        return ret;
}

enum rtw89_acpi_sar_subband rtw89_acpi_sar_get_subband(struct rtw89_dev *rtwdev,
                                                       u32 center_freq)
{
        switch (center_freq) {
        default:
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "center freq %u to ACPI SAR subband is unhandled\n",
                            center_freq);
                fallthrough;
        case 2412 ... 2484:
                return RTW89_ACPI_SAR_2GHZ_SUBBAND;
        case 5180 ... 5240:
                return RTW89_ACPI_SAR_5GHZ_SUBBAND_1;
        case 5250 ... 5320:
                return RTW89_ACPI_SAR_5GHZ_SUBBAND_2;
        case 5500 ... 5720:
                return RTW89_ACPI_SAR_5GHZ_SUBBAND_2E;
        case 5745 ... 5885:
                return RTW89_ACPI_SAR_5GHZ_SUBBAND_3_4;
        case 5955 ... 6155:
                return RTW89_ACPI_SAR_6GHZ_SUBBAND_5_L;
        case 6175 ... 6415:
                return RTW89_ACPI_SAR_6GHZ_SUBBAND_5_H;
        case 6435 ... 6515:
                return RTW89_ACPI_SAR_6GHZ_SUBBAND_6;
        case 6535 ... 6695:
                return RTW89_ACPI_SAR_6GHZ_SUBBAND_7_L;
        case 6715 ... 6855:
                return RTW89_ACPI_SAR_6GHZ_SUBBAND_7_H;

        /* freq 6875 (ch 185, 20MHz) spans RTW89_ACPI_SAR_6GHZ_SUBBAND_7_H
         * and RTW89_ACPI_SAR_6GHZ_SUBBAND_8, so directly describe it with
         * struct rtw89_6ghz_span.
         */

        case 6895 ... 7115:
                return RTW89_ACPI_SAR_6GHZ_SUBBAND_8;
        }
}

enum rtw89_band rtw89_acpi_sar_subband_to_band(struct rtw89_dev *rtwdev,
                                               enum rtw89_acpi_sar_subband subband)
{
        switch (subband) {
        default:
                rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                            "ACPI SAR subband %u to band is unhandled\n", subband);
                fallthrough;
        case RTW89_ACPI_SAR_2GHZ_SUBBAND:
                return RTW89_BAND_2G;
        case RTW89_ACPI_SAR_5GHZ_SUBBAND_1:
                return RTW89_BAND_5G;
        case RTW89_ACPI_SAR_5GHZ_SUBBAND_2:
                return RTW89_BAND_5G;
        case RTW89_ACPI_SAR_5GHZ_SUBBAND_2E:
                return RTW89_BAND_5G;
        case RTW89_ACPI_SAR_5GHZ_SUBBAND_3_4:
                return RTW89_BAND_5G;
        case RTW89_ACPI_SAR_6GHZ_SUBBAND_5_L:
                return RTW89_BAND_6G;
        case RTW89_ACPI_SAR_6GHZ_SUBBAND_5_H:
                return RTW89_BAND_6G;
        case RTW89_ACPI_SAR_6GHZ_SUBBAND_6:
                return RTW89_BAND_6G;
        case RTW89_ACPI_SAR_6GHZ_SUBBAND_7_L:
                return RTW89_BAND_6G;
        case RTW89_ACPI_SAR_6GHZ_SUBBAND_7_H:
                return RTW89_BAND_6G;
        case RTW89_ACPI_SAR_6GHZ_SUBBAND_8:
                return RTW89_BAND_6G;
        }
}

static u8 rtw89_acpi_sar_rfpath_to_hp_antidx(enum rtw89_rf_path rfpath)
{
        switch (rfpath) {
        default:
        case RF_PATH_B:
                return 0;
        case RF_PATH_A:
                return 1;
        }
}

static u8 rtw89_acpi_sar_rfpath_to_rt_antidx(enum rtw89_rf_path rfpath)
{
        switch (rfpath) {
        default:
        case RF_PATH_A:
                return 0;
        case RF_PATH_B:
                return 1;
        }
}

static s16 rtw89_acpi_sar_normalize_hp_val(u8 v)
{
        static const u8 bias = 10;
        static const u8 fct = 1;
        u16 res;

        BUILD_BUG_ON(fct > TXPWR_FACTOR_OF_RTW89_ACPI_SAR);

        res = (bias << TXPWR_FACTOR_OF_RTW89_ACPI_SAR) +
              (v << (TXPWR_FACTOR_OF_RTW89_ACPI_SAR - fct));

        return min_t(s32, res, MAX_VAL_OF_RTW89_ACPI_SAR);
}

static s16 rtw89_acpi_sar_normalize_rt_val(u8 v)
{
        static const u8 fct = 3;
        u16 res;

        BUILD_BUG_ON(fct > TXPWR_FACTOR_OF_RTW89_ACPI_SAR);

        res = v << (TXPWR_FACTOR_OF_RTW89_ACPI_SAR - fct);

        return min_t(s32, res, MAX_VAL_OF_RTW89_ACPI_SAR);
}

static
void rtw89_acpi_sar_load_std_legacy(struct rtw89_dev *rtwdev,
                                    const struct rtw89_acpi_sar_recognition *rec,
                                    const void *content,
                                    struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_sar_std_legacy *ptr = content;
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++) {
                        u8 antidx = rec->rfpath_to_antidx(path);

                        if (subband < RTW89_ACPI_SAR_SUBBAND_NR_LEGACY)
                                ent->v[subband][path] =
                                        rec->normalize(ptr->v[antidx][subband]);
                        else
                                ent->v[subband][path] = MAX_VAL_OF_RTW89_ACPI_SAR;
                }
        }
}

static
void rtw89_acpi_sar_load_std_has_6ghz(struct rtw89_dev *rtwdev,
                                      const struct rtw89_acpi_sar_recognition *rec,
                                      const void *content,
                                      struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_sar_std_has_6ghz *ptr = content;
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;

        BUILD_BUG_ON(RTW89_ACPI_SAR_SUBBAND_NR_HAS_6GHZ != NUM_OF_RTW89_ACPI_SAR_SUBBAND);

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++) {
                        u8 antidx = rec->rfpath_to_antidx(path);

                        ent->v[subband][path] = rec->normalize(ptr->v[antidx][subband]);
                }
        }
}

static
void rtw89_acpi_sar_load_sml_legacy(struct rtw89_dev *rtwdev,
                                    const struct rtw89_acpi_sar_recognition *rec,
                                    const void *content,
                                    struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_sar_sml_legacy *ptr = content;
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++) {
                        u8 antidx = rec->rfpath_to_antidx(path);

                        if (subband < RTW89_ACPI_SAR_SUBBAND_NR_LEGACY)
                                ent->v[subband][path] =
                                        rec->normalize(ptr->v[antidx][subband]);
                        else
                                ent->v[subband][path] = MAX_VAL_OF_RTW89_ACPI_SAR;
                }
        }
}

static
void rtw89_acpi_sar_load_sml_has_6ghz(struct rtw89_dev *rtwdev,
                                      const struct rtw89_acpi_sar_recognition *rec,
                                      const void *content,
                                      struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_sar_sml_has_6ghz *ptr = content;
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;

        BUILD_BUG_ON(RTW89_ACPI_SAR_SUBBAND_NR_HAS_6GHZ != NUM_OF_RTW89_ACPI_SAR_SUBBAND);

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++) {
                        u8 antidx = rec->rfpath_to_antidx(path);

                        ent->v[subband][path] = rec->normalize(ptr->v[antidx][subband]);
                }
        }
}

static s16 rtw89_acpi_geo_sar_normalize_delta(s8 delta)
{
        static const u8 fct = 1;

        BUILD_BUG_ON(fct > TXPWR_FACTOR_OF_RTW89_ACPI_SAR);

        return delta << (TXPWR_FACTOR_OF_RTW89_ACPI_SAR - fct);
}

static enum rtw89_acpi_geo_sar_regd_hp
rtw89_acpi_geo_sar_regd_convert_hp_idx(enum rtw89_regulation_type regd)
{
        switch (regd) {
        case RTW89_FCC:
        case RTW89_IC:
        case RTW89_NCC:
        case RTW89_CHILE:
        case RTW89_MEXICO:
                return RTW89_ACPI_GEO_SAR_REGD_HP_FCC;
        case RTW89_ETSI:
        case RTW89_MKK:
        case RTW89_ACMA:
                return RTW89_ACPI_GEO_SAR_REGD_HP_ETSI;
        default:
        case RTW89_WW:
        case RTW89_NA:
        case RTW89_KCC:
                return RTW89_ACPI_GEO_SAR_REGD_HP_WW;
        }
}

static enum rtw89_acpi_geo_sar_regd_rt
rtw89_acpi_geo_sar_regd_convert_rt_idx(enum rtw89_regulation_type regd)
{
        switch (regd) {
        case RTW89_FCC:
        case RTW89_NCC:
        case RTW89_CHILE:
        case RTW89_MEXICO:
                return RTW89_ACPI_GEO_SAR_REGD_RT_FCC;
        case RTW89_ETSI:
        case RTW89_ACMA:
                return RTW89_ACPI_GEO_SAR_REGD_RT_ETSI;
        case RTW89_MKK:
                return RTW89_ACPI_GEO_SAR_REGD_RT_MKK;
        case RTW89_IC:
                return RTW89_ACPI_GEO_SAR_REGD_RT_IC;
        case RTW89_KCC:
                return RTW89_ACPI_GEO_SAR_REGD_RT_KCC;
        default:
        case RTW89_WW:
        case RTW89_NA:
                return RTW89_ACPI_GEO_SAR_REGD_RT_WW;
        }
}

static
void rtw89_acpi_geo_sar_load_by_hp(struct rtw89_dev *rtwdev,
                                   const struct rtw89_acpi_geo_sar_hp_val *ptr,
                                   enum rtw89_rf_path path, s16 *val)
{
        u8 antidx = rtw89_acpi_sar_rfpath_to_hp_antidx(path);
        s16 delta = rtw89_acpi_geo_sar_normalize_delta(ptr->delta[antidx]);
        s16 max = rtw89_acpi_sar_normalize_hp_val(ptr->max);

        *val = clamp_t(s32, (*val) + delta, MIN_VAL_OF_RTW89_ACPI_SAR, max);
}

static
void rtw89_acpi_geo_sar_load_by_rt(struct rtw89_dev *rtwdev,
                                   const struct rtw89_acpi_geo_sar_rt_val *ptr,
                                   s16 *val)
{
        s16 delta = rtw89_acpi_geo_sar_normalize_delta(ptr->delta);
        s16 max = rtw89_acpi_sar_normalize_rt_val(ptr->max);

        *val = clamp_t(s32, (*val) + delta, MIN_VAL_OF_RTW89_ACPI_SAR, max);
}

static
void rtw89_acpi_geo_sar_load_hp_legacy(struct rtw89_dev *rtwdev,
                                       const void *content,
                                       enum rtw89_regulation_type regd,
                                       struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_geo_sar_hp_legacy *ptr = content;
        const struct rtw89_acpi_geo_sar_hp_legacy_entry *ptr_ent;
        const struct rtw89_acpi_geo_sar_hp_val *ptr_ent_val;
        enum rtw89_acpi_geo_sar_regd_hp geo_idx =
                rtw89_acpi_geo_sar_regd_convert_hp_idx(regd);
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;
        enum rtw89_band band;

        ptr_ent = &ptr->entries[geo_idx];

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                band = rtw89_acpi_sar_subband_to_band(rtwdev, subband);
                switch (band) {
                case RTW89_BAND_2G:
                        ptr_ent_val = &ptr_ent->val_2ghz;
                        break;
                case RTW89_BAND_5G:
                        ptr_ent_val = &ptr_ent->val_5ghz;
                        break;
                default:
                case RTW89_BAND_6G:
                        ptr_ent_val = NULL;
                        break;
                }

                if (!ptr_ent_val)
                        continue;

                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++)
                        rtw89_acpi_geo_sar_load_by_hp(rtwdev, ptr_ent_val, path,
                                                      &ent->v[subband][path]);
        }
}

static
void rtw89_acpi_geo_sar_load_hp_has_6ghz(struct rtw89_dev *rtwdev,
                                         const void *content,
                                         enum rtw89_regulation_type regd,
                                         struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_geo_sar_hp_has_6ghz *ptr = content;
        const struct rtw89_acpi_geo_sar_hp_has_6ghz_entry *ptr_ent;
        const struct rtw89_acpi_geo_sar_hp_val *ptr_ent_val;
        enum rtw89_acpi_geo_sar_regd_hp geo_idx =
                rtw89_acpi_geo_sar_regd_convert_hp_idx(regd);
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;
        enum rtw89_band band;

        ptr_ent = &ptr->entries[geo_idx];

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                band = rtw89_acpi_sar_subband_to_band(rtwdev, subband);
                switch (band) {
                case RTW89_BAND_2G:
                        ptr_ent_val = &ptr_ent->val_2ghz;
                        break;
                case RTW89_BAND_5G:
                        ptr_ent_val = &ptr_ent->val_5ghz;
                        break;
                case RTW89_BAND_6G:
                        ptr_ent_val = &ptr_ent->val_6ghz;
                        break;
                default:
                        ptr_ent_val = NULL;
                        break;
                }

                if (!ptr_ent_val)
                        continue;

                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++)
                        rtw89_acpi_geo_sar_load_by_hp(rtwdev, ptr_ent_val, path,
                                                      &ent->v[subband][path]);
        }
}

static
void rtw89_acpi_geo_sar_load_rt_legacy(struct rtw89_dev *rtwdev,
                                       const void *content,
                                       enum rtw89_regulation_type regd,
                                       struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_geo_sar_rt_legacy *ptr = content;
        const struct rtw89_acpi_geo_sar_rt_legacy_entry *ptr_ent;
        const struct rtw89_acpi_geo_sar_rt_val *ptr_ent_val;
        enum rtw89_acpi_geo_sar_regd_rt geo_idx =
                rtw89_acpi_geo_sar_regd_convert_rt_idx(regd);
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;
        enum rtw89_band band;

        ptr_ent = &ptr->entries[geo_idx];

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                band = rtw89_acpi_sar_subband_to_band(rtwdev, subband);
                switch (band) {
                case RTW89_BAND_2G:
                        ptr_ent_val = &ptr_ent->val_2ghz;
                        break;
                case RTW89_BAND_5G:
                        ptr_ent_val = &ptr_ent->val_5ghz;
                        break;
                default:
                case RTW89_BAND_6G:
                        ptr_ent_val = NULL;
                        break;
                }

                if (!ptr_ent_val)
                        continue;

                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++)
                        rtw89_acpi_geo_sar_load_by_rt(rtwdev, ptr_ent_val,
                                                      &ent->v[subband][path]);
        }
}

static
void rtw89_acpi_geo_sar_load_rt_has_6ghz(struct rtw89_dev *rtwdev,
                                         const void *content,
                                         enum rtw89_regulation_type regd,
                                         struct rtw89_sar_entry_from_acpi *ent)
{
        const struct rtw89_acpi_geo_sar_rt_has_6ghz *ptr = content;
        const struct rtw89_acpi_geo_sar_rt_has_6ghz_entry *ptr_ent;
        const struct rtw89_acpi_geo_sar_rt_val *ptr_ent_val;
        enum rtw89_acpi_geo_sar_regd_rt geo_idx =
                rtw89_acpi_geo_sar_regd_convert_rt_idx(regd);
        enum rtw89_acpi_sar_subband subband;
        enum rtw89_rf_path path;
        enum rtw89_band band;

        ptr_ent = &ptr->entries[geo_idx];

        for (subband = 0; subband < NUM_OF_RTW89_ACPI_SAR_SUBBAND; subband++) {
                band = rtw89_acpi_sar_subband_to_band(rtwdev, subband);
                switch (band) {
                case RTW89_BAND_2G:
                        ptr_ent_val = &ptr_ent->val_2ghz;
                        break;
                case RTW89_BAND_5G:
                        ptr_ent_val = &ptr_ent->val_5ghz;
                        break;
                case RTW89_BAND_6G:
                        ptr_ent_val = &ptr_ent->val_6ghz;
                        break;
                default:
                        ptr_ent_val = NULL;
                        break;
                }

                if (!ptr_ent_val)
                        continue;

                for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++)
                        rtw89_acpi_geo_sar_load_by_rt(rtwdev, ptr_ent_val,
                                                      &ent->v[subband][path]);
        }
}

#define RTW89_ACPI_GEO_SAR_DECL_HANDLER(type) \
static const struct rtw89_acpi_geo_sar_handler \
rtw89_acpi_geo_sar_handler_ ## type = { \
        .data_size = RTW89_ACPI_GEO_SAR_SIZE_OF(type), \
        .load = rtw89_acpi_geo_sar_load_ ## type, \
}

RTW89_ACPI_GEO_SAR_DECL_HANDLER(hp_legacy);
RTW89_ACPI_GEO_SAR_DECL_HANDLER(hp_has_6ghz);
RTW89_ACPI_GEO_SAR_DECL_HANDLER(rt_legacy);
RTW89_ACPI_GEO_SAR_DECL_HANDLER(rt_has_6ghz);

static const struct rtw89_acpi_sar_recognition rtw89_acpi_sar_recs[] = {
        {
                .id = {
                        .cid = RTW89_ACPI_SAR_CID_HP,
                        .rev = RTW89_ACPI_SAR_REV_LEGACY,
                        .size = RTW89_ACPI_SAR_SIZE_OF(std_legacy),
                },
                .geo = &rtw89_acpi_geo_sar_handler_hp_legacy,

                .rfpath_to_antidx = rtw89_acpi_sar_rfpath_to_hp_antidx,
                .normalize = rtw89_acpi_sar_normalize_hp_val,
                .load = rtw89_acpi_sar_load_std_legacy,
        },
        {
                .id = {
                        .cid = RTW89_ACPI_SAR_CID_HP,
                        .rev = RTW89_ACPI_SAR_REV_HAS_6GHZ,
                        .size = RTW89_ACPI_SAR_SIZE_OF(std_has_6ghz),
                },
                .geo = &rtw89_acpi_geo_sar_handler_hp_has_6ghz,

                .rfpath_to_antidx = rtw89_acpi_sar_rfpath_to_hp_antidx,
                .normalize = rtw89_acpi_sar_normalize_hp_val,
                .load = rtw89_acpi_sar_load_std_has_6ghz,
        },
        {
                .id = {
                        .cid = RTW89_ACPI_SAR_CID_RT,
                        .rev = RTW89_ACPI_SAR_REV_LEGACY,
                        .size = RTW89_ACPI_SAR_SIZE_OF(std_legacy),
                },
                .geo = &rtw89_acpi_geo_sar_handler_rt_legacy,

                .rfpath_to_antidx = rtw89_acpi_sar_rfpath_to_rt_antidx,
                .normalize = rtw89_acpi_sar_normalize_rt_val,
                .load = rtw89_acpi_sar_load_std_legacy,
        },
        {
                .id = {
                        .cid = RTW89_ACPI_SAR_CID_RT,
                        .rev = RTW89_ACPI_SAR_REV_HAS_6GHZ,
                        .size = RTW89_ACPI_SAR_SIZE_OF(std_has_6ghz),
                },
                .geo = &rtw89_acpi_geo_sar_handler_rt_has_6ghz,

                .rfpath_to_antidx = rtw89_acpi_sar_rfpath_to_rt_antidx,
                .normalize = rtw89_acpi_sar_normalize_rt_val,
                .load = rtw89_acpi_sar_load_std_has_6ghz,
        },
        {
                .id = {
                        .cid = RTW89_ACPI_SAR_CID_RT,
                        .rev = RTW89_ACPI_SAR_REV_LEGACY,
                        .size = RTW89_ACPI_SAR_SIZE_OF(sml_legacy),
                },
                .geo = &rtw89_acpi_geo_sar_handler_rt_legacy,

                .rfpath_to_antidx = rtw89_acpi_sar_rfpath_to_rt_antidx,
                .normalize = rtw89_acpi_sar_normalize_rt_val,
                .load = rtw89_acpi_sar_load_sml_legacy,
        },
        {
                .id = {
                        .cid = RTW89_ACPI_SAR_CID_RT,
                        .rev = RTW89_ACPI_SAR_REV_HAS_6GHZ,
                        .size = RTW89_ACPI_SAR_SIZE_OF(sml_has_6ghz),
                },
                .geo = &rtw89_acpi_geo_sar_handler_rt_has_6ghz,

                .rfpath_to_antidx = rtw89_acpi_sar_rfpath_to_rt_antidx,
                .normalize = rtw89_acpi_sar_normalize_rt_val,
                .load = rtw89_acpi_sar_load_sml_has_6ghz,
        },
};

struct rtw89_acpi_sar_rec_parm {
        u32 pld_len;
        u8 tbl_cnt;
        u16 cid;
        u8 rev;
};

static const struct rtw89_acpi_sar_recognition *
rtw89_acpi_sar_recognize(struct rtw89_dev *rtwdev,
                         const struct rtw89_acpi_sar_rec_parm *parm)
{
        const u32 tbl_len = parm->pld_len / parm->tbl_cnt;
        const struct rtw89_acpi_sar_recognition *rec;
        struct rtw89_acpi_sar_identifier id = {};

        rtw89_debug(rtwdev, RTW89_DBG_ACPI,
                    "%s: cid %u, rev %u, tbl len %u, tbl cnt %u\n",
                    __func__, parm->cid, parm->rev, tbl_len, parm->tbl_cnt);

        if (unlikely(parm->pld_len % parm->tbl_cnt)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid pld len %u\n",
                            parm->pld_len);
                return NULL;
        }

        if (unlikely(tbl_len > RTW89_ACPI_SAR_SIZE_MAX)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid tbl len %u\n",
                            tbl_len);
                return NULL;
        }

        if (unlikely(parm->tbl_cnt > MAX_NUM_OF_RTW89_ACPI_SAR_TBL)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid tbl cnt %u\n",
                            parm->tbl_cnt);
                return NULL;
        }

        switch (parm->cid) {
        case RTW89_ACPI_SAR_CID_HP:
        case RTW89_ACPI_SAR_CID_RT:
                id.cid = parm->cid;
                break;
        default:
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid cid 0x%x\n",
                            parm->cid);
                return NULL;
        }

        switch (parm->rev) {
        case RTW89_ACPI_SAR_REV_LEGACY:
        case RTW89_ACPI_SAR_REV_HAS_6GHZ:
                id.rev = parm->rev;
                break;
        default:
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid rev %u\n",
                            parm->rev);
                return NULL;
        }

        id.size = tbl_len;
        for (unsigned int i = 0; i < ARRAY_SIZE(rtw89_acpi_sar_recs); i++) {
                rec = &rtw89_acpi_sar_recs[i];
                if (memcmp(&rec->id, &id, sizeof(rec->id)) == 0)
                        return rec;
        }

        rtw89_debug(rtwdev, RTW89_DBG_ACPI, "failed to recognize\n");
        return NULL;
}

static const struct rtw89_acpi_sar_recognition *
rtw89_acpi_evaluate_static_sar(struct rtw89_dev *rtwdev,
                               struct rtw89_sar_cfg_acpi *cfg)
{
        const struct rtw89_acpi_sar_recognition *rec = NULL;
        const struct rtw89_acpi_static_sar_hdr *hdr;
        struct rtw89_sar_entry_from_acpi tmp = {};
        struct rtw89_acpi_sar_rec_parm parm = {};
        struct rtw89_sar_table_from_acpi *tbl;
        const struct rtw89_acpi_data *data;
        u32 len;

        data = rtw89_acpi_evaluate_method(rtwdev, RTW89_ACPI_METHOD_STATIC_SAR);
        if (!data)
                return NULL;

        rtw89_debug(rtwdev, RTW89_DBG_ACPI, "acpi load static sar\n");

        len = data->len;
        if (len <= sizeof(*hdr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid buf len %u\n", len);
                goto out;
        }

        hdr = (typeof(hdr))data->buf;

        parm.cid = le16_to_cpu(hdr->cid);
        parm.rev = hdr->rev;
        parm.tbl_cnt = 1;
        parm.pld_len = len - sizeof(*hdr);

        rec = rtw89_acpi_sar_recognize(rtwdev, &parm);
        if (!rec)
                goto out;

        rec->load(rtwdev, rec, hdr->content, &tmp);

        tbl = &cfg->tables[0];
        for (u8 regd = 0; regd < RTW89_REGD_NUM; regd++)
                tbl->entries[regd] = tmp;

        cfg->valid_num = 1;

out:
        kfree(data);
        return rec;
}

static const struct rtw89_acpi_sar_recognition *
rtw89_acpi_evaluate_dynamic_sar(struct rtw89_dev *rtwdev,
                                struct rtw89_sar_cfg_acpi *cfg)
{
        const struct rtw89_acpi_sar_recognition *rec = NULL;
        const struct rtw89_acpi_dynamic_sar_hdr *hdr;
        struct rtw89_acpi_sar_rec_parm parm = {};
        struct rtw89_sar_table_from_acpi *tbl;
        const struct rtw89_acpi_data *data;
        u32 len;

        data = rtw89_acpi_evaluate_method(rtwdev, RTW89_ACPI_METHOD_DYNAMIC_SAR);
        if (!data)
                return NULL;

        rtw89_debug(rtwdev, RTW89_DBG_ACPI, "acpi load dynamic sar\n");

        len = data->len;
        if (len <= sizeof(*hdr)) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid buf len %u\n", len);
                goto out;
        }

        hdr = (typeof(hdr))data->buf;

        parm.cid = le16_to_cpu(hdr->cid);
        parm.rev = hdr->rev;
        parm.tbl_cnt = hdr->cnt;
        parm.pld_len = len - sizeof(*hdr);

        rec = rtw89_acpi_sar_recognize(rtwdev, &parm);
        if (!rec)
                goto out;

        for (unsigned int i = 0; i < hdr->cnt; i++) {
                const u8 *content = hdr->content + rec->id.size * i;
                struct rtw89_sar_entry_from_acpi tmp = {};

                rec->load(rtwdev, rec, content, &tmp);

                tbl = &cfg->tables[i];
                for (u8 regd = 0; regd < RTW89_REGD_NUM; regd++)
                        tbl->entries[regd] = tmp;
        }

        cfg->valid_num = hdr->cnt;

out:
        kfree(data);
        return rec;
}

int rtw89_acpi_evaluate_dynamic_sar_indicator(struct rtw89_dev *rtwdev,
                                              struct rtw89_sar_cfg_acpi *cfg,
                                              bool *poll_changed)
{
        struct rtw89_sar_indicator_from_acpi *ind = &cfg->indicator;
        struct rtw89_sar_indicator_from_acpi tmp = *ind;
        const struct rtw89_acpi_data *data;
        const u8 *tbl_base1_by_ant;
        enum rtw89_rf_path path;
        int ret = 0;
        u32 len;

        data = rtw89_acpi_evaluate_method(rtwdev, RTW89_ACPI_METHOD_DYNAMIC_SAR_INDICATOR);
        if (!data)
                return -EFAULT;

        if (!poll_changed)
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "acpi load dynamic sar indicator\n");

        len = data->len;
        if (len != ind->fields) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid buf len %u\n", len);
                ret = -EINVAL;
                goto out;
        }

        tbl_base1_by_ant = data->buf;

        for (path = 0; path < NUM_OF_RTW89_ACPI_SAR_RF_PATH; path++) {
                u8 antidx = ind->rfpath_to_antidx(path);
                u8 sel;

                if (antidx >= ind->fields)
                        antidx = 0;

                /* convert the table index from 1-based to 0-based */
                sel = tbl_base1_by_ant[antidx] - 1;
                if (sel >= cfg->valid_num)
                        sel = 0;

                tmp.tblsel[path] = sel;
        }

        if (memcmp(ind, &tmp, sizeof(*ind)) == 0) {
                if (poll_changed)
                        *poll_changed = false;
        } else {
                if (poll_changed)
                        *poll_changed = true;

                *ind = tmp;
        }

out:
        kfree(data);
        return ret;
}

static
void rtw89_acpi_evaluate_geo_sar(struct rtw89_dev *rtwdev,
                                 const struct rtw89_acpi_geo_sar_handler *hdl,
                                 struct rtw89_sar_cfg_acpi *cfg)
{
        const struct rtw89_acpi_data *data;
        u32 len;

        data = rtw89_acpi_evaluate_method(rtwdev, RTW89_ACPI_METHOD_GEO_SAR);
        if (!data)
                return;

        rtw89_debug(rtwdev, RTW89_DBG_ACPI, "acpi load geo sar\n");

        len = data->len;
        if (len != hdl->data_size) {
                rtw89_debug(rtwdev, RTW89_DBG_ACPI, "invalid buf len %u (expected %u)\n",
                            len, hdl->data_size);
                goto out;
        }

        for (unsigned int i = 0; i < cfg->valid_num; i++)
                for (u8 regd = 0; regd < RTW89_REGD_NUM; regd++)
                        hdl->load(rtwdev, data->buf, regd, &cfg->tables[i].entries[regd]);

out:
        kfree(data);
}

int rtw89_acpi_evaluate_sar(struct rtw89_dev *rtwdev,
                            struct rtw89_sar_cfg_acpi *cfg)
{
        struct rtw89_sar_indicator_from_acpi *ind = &cfg->indicator;
        const struct rtw89_acpi_sar_recognition *rec;
        bool fetch_indicator = false;
        int ret;

        rec = rtw89_acpi_evaluate_static_sar(rtwdev, cfg);
        if (rec)
                goto recognized;

        rec = rtw89_acpi_evaluate_dynamic_sar(rtwdev, cfg);
        if (!rec)
                return -ENOENT;

        fetch_indicator = true;

recognized:
        rtw89_acpi_evaluate_geo_sar(rtwdev, rec->geo, cfg);

        switch (rec->id.cid) {
        case RTW89_ACPI_SAR_CID_HP:
                cfg->downgrade_2tx = 3 << TXPWR_FACTOR_OF_RTW89_ACPI_SAR;
                ind->fields = RTW89_ACPI_SAR_ANT_NR_STD;
                break;
        case RTW89_ACPI_SAR_CID_RT:
                cfg->downgrade_2tx = 0;
                ind->fields = 1;
                break;
        default:
                return -EFAULT;
        }

        if (fetch_indicator) {
                ind->rfpath_to_antidx = rec->rfpath_to_antidx;
                ret = rtw89_acpi_evaluate_dynamic_sar_indicator(rtwdev, cfg, NULL);
                if (ret)
                        fetch_indicator = false;
        }

        if (!fetch_indicator)
                memset(ind->tblsel, 0, sizeof(ind->tblsel));

        ind->enable_sync = fetch_indicator;
        return 0;
}