}
 }
 
+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 = {
                        .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,
                        .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,
                        .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,
                        .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,
                        .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,
                        .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,
        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)
 {
        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;
 
 #define RTW89_ACPI_METHOD_STATIC_SAR "WRDS"
 #define RTW89_ACPI_METHOD_DYNAMIC_SAR "RWRD"
 #define RTW89_ACPI_METHOD_DYNAMIC_SAR_INDICATOR "RWSI"
+#define RTW89_ACPI_METHOD_GEO_SAR "RWGS"
 
 struct rtw89_acpi_sar_std_legacy {
        u8 v[RTW89_ACPI_SAR_ANT_NR_STD][RTW89_ACPI_SAR_SUBBAND_NR_LEGACY];
 
 struct rtw89_acpi_sar_recognition {
        struct rtw89_acpi_sar_identifier id;
+       const struct rtw89_acpi_geo_sar_handler *geo;
 
        u8 (*rfpath_to_antidx)(enum rtw89_rf_path rfpath);
        s16 (*normalize)(u8 v);
                     struct rtw89_sar_entry_from_acpi *ent);
 };
 
+struct rtw89_acpi_geo_sar_hp_val {
+       u8 max;
+       s8 delta[RTW89_ACPI_SAR_ANT_NR_STD];
+} __packed;
+
+struct rtw89_acpi_geo_sar_hp_legacy_entry {
+       struct rtw89_acpi_geo_sar_hp_val val_2ghz;
+       struct rtw89_acpi_geo_sar_hp_val val_5ghz;
+} __packed;
+
+struct rtw89_acpi_geo_sar_hp_has_6ghz_entry {
+       struct rtw89_acpi_geo_sar_hp_val val_2ghz;
+       struct rtw89_acpi_geo_sar_hp_val val_5ghz;
+       struct rtw89_acpi_geo_sar_hp_val val_6ghz;
+} __packed;
+
+enum rtw89_acpi_geo_sar_regd_hp {
+       RTW89_ACPI_GEO_SAR_REGD_HP_FCC = 0,
+       RTW89_ACPI_GEO_SAR_REGD_HP_ETSI = 1,
+       RTW89_ACPI_GEO_SAR_REGD_HP_WW = 2,
+
+       RTW89_ACPI_GEO_SAR_REGD_NR_HP,
+};
+
+struct rtw89_acpi_geo_sar_hp_legacy {
+       struct rtw89_acpi_geo_sar_hp_legacy_entry
+               entries[RTW89_ACPI_GEO_SAR_REGD_NR_HP];
+} __packed;
+
+struct rtw89_acpi_geo_sar_hp_has_6ghz {
+       struct rtw89_acpi_geo_sar_hp_has_6ghz_entry
+               entries[RTW89_ACPI_GEO_SAR_REGD_NR_HP];
+} __packed;
+
+struct rtw89_acpi_geo_sar_rt_val {
+       u8 max;
+       s8 delta;
+} __packed;
+
+struct rtw89_acpi_geo_sar_rt_legacy_entry {
+       struct rtw89_acpi_geo_sar_rt_val val_2ghz;
+       struct rtw89_acpi_geo_sar_rt_val val_5ghz;
+} __packed;
+
+struct rtw89_acpi_geo_sar_rt_has_6ghz_entry {
+       struct rtw89_acpi_geo_sar_rt_val val_2ghz;
+       struct rtw89_acpi_geo_sar_rt_val val_5ghz;
+       struct rtw89_acpi_geo_sar_rt_val val_6ghz;
+} __packed;
+
+enum rtw89_acpi_geo_sar_regd_rt {
+       RTW89_ACPI_GEO_SAR_REGD_RT_FCC = 0,
+       RTW89_ACPI_GEO_SAR_REGD_RT_ETSI = 1,
+       RTW89_ACPI_GEO_SAR_REGD_RT_MKK = 2,
+       RTW89_ACPI_GEO_SAR_REGD_RT_IC = 3,
+       RTW89_ACPI_GEO_SAR_REGD_RT_KCC = 4,
+       RTW89_ACPI_GEO_SAR_REGD_RT_WW = 5,
+
+       RTW89_ACPI_GEO_SAR_REGD_NR_RT,
+};
+
+struct rtw89_acpi_geo_sar_rt_legacy {
+       struct rtw89_acpi_geo_sar_rt_legacy_entry
+               entries[RTW89_ACPI_GEO_SAR_REGD_NR_RT];
+} __packed;
+
+struct rtw89_acpi_geo_sar_rt_has_6ghz {
+       struct rtw89_acpi_geo_sar_rt_has_6ghz_entry
+               entries[RTW89_ACPI_GEO_SAR_REGD_NR_RT];
+} __packed;
+
+struct rtw89_acpi_geo_sar_handler {
+       u8 data_size;
+
+       void (*load)(struct rtw89_dev *rtwdev,
+                    const void *content,
+                    enum rtw89_regulation_type regd,
+                    struct rtw89_sar_entry_from_acpi *ent);
+};
+
+/* for rtw89_acpi_geo_sar_handler::data_size */
+#define RTW89_ACPI_GEO_SAR_SIZE_MAX U8_MAX
+#define RTW89_ACPI_GEO_SAR_SIZE_OF(type) \
+       (BUILD_BUG_ON_ZERO(sizeof(struct rtw89_acpi_geo_sar_ ## type) > \
+                          RTW89_ACPI_GEO_SAR_SIZE_MAX) + \
+        sizeof(struct rtw89_acpi_geo_sar_ ## type))
+
 enum rtw89_acpi_sar_subband rtw89_acpi_sar_get_subband(struct rtw89_dev *rtwdev,
                                                       u32 center_freq);
 enum rtw89_band rtw89_acpi_sar_subband_to_band(struct rtw89_dev *rtwdev,