diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c
index c5bb97b381cf..304249038ac6 100644
--- a/drivers/net/wireless/mac80211_hwsim.c
+++ b/drivers/net/wireless/mac80211_hwsim.c
@@ -1547,7 +1547,7 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
 	memset(&rx_status, 0, sizeof(rx_status));
 	rx_status.flag |= RX_FLAG_MACTIME_START;
 	rx_status.freq = chan->center_freq;
-	rx_status.freq_offset = chan->freq_offset ? 1 : 0;
+	rx_status.freq_offset = chan->freq_offset;
 	rx_status.band = chan->band;
 	if (info->control.rates[0].flags & IEEE80211_TX_RC_VHT_MCS) {
 		rx_status.rate_idx =
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 1fd461ed746d..93fe4b98017c 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -848,6 +848,10 @@ enum mac80211_tx_info_flags {
 #define IEEE80211_TX_CTL_STBC_SHIFT		23
 
 #define IEEE80211_TX_RC_S1G_MCS IEEE80211_TX_RC_VHT_MCS
+#define IEEE80211_TX_RC_2_MHZ_WIDTH IEEE80211_TX_RC_MCS
+#define IEEE80211_TX_RC_4_MHZ_WIDTH IEEE80211_TX_RC_40_MHZ_WIDTH
+#define IEEE80211_TX_RC_8_MHZ_WIDTH IEEE80211_TX_RC_80_MHZ_WIDTH
+#define IEEE80211_TX_RC_16_MHZ_WIDTH IEEE80211_TX_RC_160_MHZ_WIDTH
 
 /**
  * enum mac80211_tx_control_flags - flags to describe transmit control
@@ -1028,6 +1032,20 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate)
 	return (rate->idx >> 4) + 1;
 }
 
+static inline u8
+ieee80211_rate_get_s1g_mcs(const struct ieee80211_tx_rate *rate)
+{
+	/* S1G uses the same MCS encoding as VHT */
+	return ieee80211_rate_get_vht_mcs(rate);
+}
+
+static inline u8
+ieee80211_rate_get_s1g_nss(const struct ieee80211_tx_rate *rate)
+{
+	/* S1G uses the same NSS encoding as VHT */
+	return ieee80211_rate_get_vht_nss(rate);
+}
+
 /**
  * struct ieee80211_tx_info - skb transmit information
  *
@@ -1408,6 +1426,7 @@ enum mac80211_rx_encoding {
 	RX_ENC_HT,
 	RX_ENC_VHT,
 	RX_ENC_HE,
+	RX_ENC_S1G,
 };
 
 /**
@@ -1458,10 +1477,11 @@ struct ieee80211_rx_status {
 	u32 device_timestamp;
 	u32 ampdu_reference;
 	u32 flag;
-	u16 freq: 13, freq_offset: 1;
+	u16 freq;
+	u16 freq_offset;
 	u8 enc_flags;
-	u8 encoding:2, bw:3, he_ru:3;
-	u8 he_gi:2, he_dcm:1;
+	u8 encoding:3, bw:5;
+	u8 he_ru:3, he_gi:2, he_dcm:1;
 	u8 rate_idx;
 	u8 nss;
 	u8 rx_flags;
@@ -1477,8 +1497,7 @@ struct ieee80211_rx_status {
 static inline u32
 ieee80211_rx_status_to_khz(struct ieee80211_rx_status *rx_status)
 {
-	return MHZ_TO_KHZ(rx_status->freq) +
-	       (rx_status->freq_offset ? 500 : 0);
+	return MHZ_TO_KHZ(rx_status->freq) + rx_status->freq_offset;
 }
 
 /**
@@ -6506,6 +6525,9 @@ bool rate_usable_index_exists(struct ieee80211_supported_band *sband,
 {
 	unsigned int i;
 
+	if (sband->band == NL80211_BAND_S1GHZ)
+		return true;
+
 	for (i = 0; i < sband->n_bitrates; i++)
 		if (rate_supported(sta, sband->band, i))
 			return true;
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index b4c2f4e9083e..a5511eb56dd9 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -743,8 +743,16 @@ void sta_set_rate_info_tx(struct sta_info *sta,
 			  const struct ieee80211_tx_rate *rate,
 			  struct rate_info *rinfo)
 {
+	struct ieee80211_supported_band *sband;
+
+	sband = ieee80211_get_sband(sta->sdata);
 	rinfo->flags = 0;
-	if (rate->flags & IEEE80211_TX_RC_MCS) {
+	if (rate->flags & IEEE80211_TX_RC_S1G_MCS &&
+	    sband->band == NL80211_BAND_S1GHZ) {
+		rinfo->flags |= RATE_INFO_FLAGS_S1G_MCS;
+		rinfo->mcs = ieee80211_rate_get_s1g_mcs(rate);
+		rinfo->nss = ieee80211_rate_get_s1g_nss(rate);
+	} else if (rate->flags & IEEE80211_TX_RC_MCS) {
 		rinfo->flags |= RATE_INFO_FLAGS_MCS;
 		rinfo->mcs = rate->idx;
 	} else if (rate->flags & IEEE80211_TX_RC_VHT_MCS) {
@@ -752,25 +760,37 @@ void sta_set_rate_info_tx(struct sta_info *sta,
 		rinfo->mcs = ieee80211_rate_get_vht_mcs(rate);
 		rinfo->nss = ieee80211_rate_get_vht_nss(rate);
 	} else {
-		struct ieee80211_supported_band *sband;
 		int shift = ieee80211_vif_get_shift(&sta->sdata->vif);
 		u16 brate;
 
-		sband = ieee80211_get_sband(sta->sdata);
 		WARN_ON_ONCE(sband && !sband->bitrates);
 		if (sband && sband->bitrates) {
 			brate = sband->bitrates[rate->idx].bitrate;
 			rinfo->legacy = DIV_ROUND_UP(brate, 1 << shift);
 		}
 	}
-	if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
-		rinfo->bw = RATE_INFO_BW_40;
-	else if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
-		rinfo->bw = RATE_INFO_BW_80;
-	else if (rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH)
-		rinfo->bw = RATE_INFO_BW_160;
-	else
-		rinfo->bw = RATE_INFO_BW_20;
+	if (sband->band == NL80211_BAND_S1GHZ) {
+		if (rate->flags & IEEE80211_TX_RC_2_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_2;
+		else if (rate->flags & IEEE80211_TX_RC_4_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_4;
+		else if (rate->flags & IEEE80211_TX_RC_8_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_8;
+		else if (rate->flags & IEEE80211_TX_RC_16_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_16;
+		else
+			rinfo->bw = RATE_INFO_BW_1;
+	} else {
+		if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_40;
+		else if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_80;
+		else if (rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH)
+			rinfo->bw = RATE_INFO_BW_160;
+		else
+			rinfo->bw = RATE_INFO_BW_20;
+	}
+
 	if (rate->flags & IEEE80211_TX_RC_SHORT_GI)
 		rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
 }
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index feab1d58e932..e0a2975d0959 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4982,6 +4982,14 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
 				      status->rate_idx, status->nss))
 				goto drop;
 			break;
+		case RX_ENC_S1G:
+			if (WARN_ONCE(status->rate_idx > 10 ||
+				      !status->nss ||
+				      status->nss > 8,
+				      "Rate marked as a S1G rate but data is invalid: MCS: %d, NSS: %d\n",
+				      status->rate_idx, status->nss))
+				goto drop;
+			break;
 		default:
 			WARN_ON_ONCE(1);
 			fallthrough;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 014032369994..da03d4eef321 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -2297,6 +2297,13 @@ static void sta_stats_decode_rate(struct ieee80211_local *local, u32 rate,
 		rinfo->he_ru_alloc = STA_STATS_GET(HE_RU, rate);
 		rinfo->he_dcm = STA_STATS_GET(HE_DCM, rate);
 		break;
+	case STA_STATS_RATE_TYPE_S1G:
+		rinfo->flags = RATE_INFO_FLAGS_S1G_MCS;
+		rinfo->mcs = STA_STATS_GET(S1G_MCS, rate);
+		rinfo->nss = STA_STATS_GET(S1G_NSS, rate);
+		if (STA_STATS_GET(SGI, rate))
+			rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
+		break;
 	}
 }
 
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 218430790660..d2173a191da4 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -931,6 +931,8 @@ enum sta_stats_type {
 #define STA_STATS_FIELD_VHT_NSS		GENMASK( 7,  4)
 #define STA_STATS_FIELD_HE_MCS		GENMASK( 3,  0)
 #define STA_STATS_FIELD_HE_NSS		GENMASK( 7,  4)
+#define STA_STATS_FIELD_S1G_MCS		GENMASK( 3,  0)
+#define STA_STATS_FIELD_S1G_NSS		GENMASK( 7,  4)
 #define STA_STATS_FIELD_BW		GENMASK(11,  8)
 #define STA_STATS_FIELD_SGI		GENMASK(12, 12)
 #define STA_STATS_FIELD_TYPE		GENMASK(15, 13)
@@ -975,6 +977,11 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
 		r |= STA_STATS_FIELD(HE_RU, s->he_ru);
 		r |= STA_STATS_FIELD(HE_DCM, s->he_dcm);
 		break;
+	case RX_ENC_S1G:
+		r |= STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_S1G);
+		r |= STA_STATS_FIELD(S1G_NSS, s->nss);
+		r |= STA_STATS_FIELD(S1G_MCS, s->rate_idx);
+		break;
 	default:
 		WARN_ON(1);
 		return STA_STATS_RATE_INVALID;
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 51a2c1dee360..8b896aec5f8c 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3861,7 +3861,7 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
 	u64 ts = status->mactime;
 	struct rate_info ri;
 	u16 rate;
-	u8 n_ltf;
+	u8 n_ltf, guard_factor;
 
 	if (WARN_ON(!ieee80211_have_rx_timestamp(status)))
 		return 0;
@@ -3948,6 +3948,34 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
 			ts += 4 * n_ltf;
 		}
 
+		break;
+	case RX_ENC_S1G:
+		/* Set to duration of S1G OFDM symbol with normal GI */
+		guard_factor = 40;
+		ri.flags |= RATE_INFO_FLAGS_S1G_MCS;
+		ri.mcs = status->rate_idx;
+		ri.nss = status->nss;
+		if (status->enc_flags & RX_ENC_FLAG_SHORT_GI) {
+			ri.flags |= RATE_INFO_FLAGS_SHORT_GI;
+			guard_factor = 36;
+		}
+
+		/*
+		 * See 80211-2020, section 23.3.2 for S1G PPDU
+		 * format and 23.3.6 for timing-related parameters.
+		 * Here using the general structure for S1G_1M as
+		 * in figure 23-3.
+		 */
+		if (status->flag & RX_FLAG_MACTIME_PLCP_START) {
+			mpdu_offset += 2;
+			ts += (14 * guard_factor);
+
+			/* Add S1G-LTFs per streams */
+			n_ltf = (ri.nss != 1) && (ri.nss % 2) ?
+				ri.nss + 1 : ri.nss;
+			ts += (guard_factor * n_ltf);
+		}
+
 		break;
 	default:
 		WARN_ON(1);
