xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/phy.c (revision 7f7a450ed83a2a700b10b23272ea9a419d27f9bb)
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright (C) 2024-2026 Intel Corporation
4  */
5 #include <net/mac80211.h>
6 
7 #include "phy.h"
8 #include "hcmd.h"
9 #include "fw/api/phy-ctxt.h"
10 
11 int iwl_mld_allocate_fw_phy_id(struct iwl_mld *mld)
12 {
13 	int id;
14 	unsigned long used = mld->used_phy_ids;
15 
16 	for_each_clear_bit(id, &used, NUM_PHY_CTX) {
17 		mld->used_phy_ids |= BIT(id);
18 		return id;
19 	}
20 
21 	return -ENOSPC;
22 }
23 EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_allocate_fw_phy_id);
24 
25 struct iwl_mld_chanctx_usage_data {
26 	struct iwl_mld *mld;
27 	struct ieee80211_chanctx_conf *ctx;
28 	bool use_def;
29 };
30 
31 static bool iwl_mld_chanctx_fils_enabled(struct ieee80211_vif *vif,
32 					 struct ieee80211_chanctx_conf *ctx)
33 {
34 	if (vif->type != NL80211_IFTYPE_AP)
35 		return false;
36 
37 	return cfg80211_channel_is_psc(ctx->def.chan) ||
38 		(ctx->def.chan->band == NL80211_BAND_6GHZ &&
39 		 ctx->def.width >= NL80211_CHAN_WIDTH_80);
40 }
41 
42 static void iwl_mld_chanctx_usage_iter(void *_data, u8 *mac,
43 				       struct ieee80211_vif *vif)
44 {
45 	struct iwl_mld_chanctx_usage_data *data = _data;
46 	struct ieee80211_bss_conf *link_conf;
47 	int link_id;
48 
49 	for_each_vif_active_link(vif, link_conf, link_id) {
50 		if (rcu_access_pointer(link_conf->chanctx_conf) != data->ctx)
51 			continue;
52 
53 		if (vif->type == NL80211_IFTYPE_AP && link_conf->ftm_responder)
54 			data->use_def = true;
55 
56 		if (iwl_mld_chanctx_fils_enabled(vif, data->ctx))
57 			data->use_def = true;
58 	}
59 }
60 
61 struct cfg80211_chan_def *
62 iwl_mld_get_chandef_from_chanctx(struct iwl_mld *mld,
63 				 struct ieee80211_chanctx_conf *ctx)
64 {
65 	struct iwl_mld_chanctx_usage_data data = {
66 		.mld = mld,
67 		.ctx = ctx,
68 	};
69 
70 	ieee80211_iterate_active_interfaces_mtx(mld->hw,
71 						IEEE80211_IFACE_ITER_NORMAL,
72 						iwl_mld_chanctx_usage_iter,
73 						&data);
74 
75 	return data.use_def ? &ctx->def : &ctx->min_def;
76 }
77 
78 static u8
79 iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width)
80 {
81 	switch (width) {
82 	case NL80211_CHAN_WIDTH_20_NOHT:
83 	case NL80211_CHAN_WIDTH_20:
84 		return IWL_PHY_CHANNEL_MODE20;
85 	case NL80211_CHAN_WIDTH_40:
86 		return IWL_PHY_CHANNEL_MODE40;
87 	case NL80211_CHAN_WIDTH_80:
88 		return IWL_PHY_CHANNEL_MODE80;
89 	case NL80211_CHAN_WIDTH_160:
90 		return IWL_PHY_CHANNEL_MODE160;
91 	case NL80211_CHAN_WIDTH_320:
92 		return IWL_PHY_CHANNEL_MODE320;
93 	default:
94 		WARN(1, "Invalid channel width=%u", width);
95 		return IWL_PHY_CHANNEL_MODE20;
96 	}
97 }
98 
99 /* Maps the driver specific control channel position (relative to the center
100  * freq) definitions to the fw values
101  */
102 static u8 _iwl_mld_get_fw_ctrl_pos(u32 control, u32 cf1)
103 {
104 	int offs = control - cf1;
105 	int abs_offs = abs(offs);
106 	u8 ret;
107 
108 	if (offs == 0) {
109 		/* The FW is expected to check the control channel position only
110 		 * when in HT/VHT and the channel width is not 20MHz. Return
111 		 * this value as the default one.
112 		 */
113 		return 0;
114 	}
115 
116 	/* this results in a value 0-7, i.e. fitting into 0b0111 */
117 	ret = (abs_offs - 10) / 20;
118 	/* But we need the value to be in 0b1011 because 0b0100 is
119 	 * IWL_PHY_CTRL_POS_ABOVE, so shift bit 2 up to land in
120 	 * IWL_PHY_CTRL_POS_OFFS_EXT (0b1000)
121 	 */
122 	ret = (ret & IWL_PHY_CTRL_POS_OFFS_MSK) |
123 	      ((ret & BIT(2)) << 1);
124 	/* and add the above bit */
125 	ret |= (offs > 0) * IWL_PHY_CTRL_POS_ABOVE;
126 
127 	return ret;
128 }
129 
130 u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef)
131 {
132 	return _iwl_mld_get_fw_ctrl_pos(chandef->chan->center_freq,
133 					chandef->center_freq1);
134 }
135 
136 int iwl_mld_phy_fw_action(struct iwl_mld *mld,
137 			  struct ieee80211_chanctx_conf *ctx, u32 action)
138 {
139 	struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
140 	struct cfg80211_chan_def *chandef = &phy->chandef;
141 	struct iwl_phy_context_cmd cmd = {
142 		.id_and_color = cpu_to_le32(phy->fw_id),
143 		.action = cpu_to_le32(action),
144 		.puncture_mask = cpu_to_le16(chandef->punctured),
145 		/* Channel info */
146 		.ci.channel = cpu_to_le32(chandef->chan->hw_value),
147 		.ci.band = iwl_mld_nl80211_band_to_fw(chandef->chan->band),
148 		.ci.width = iwl_mld_nl80211_width_to_fw(chandef->width),
149 		.ci.ctrl_pos = iwl_mld_get_fw_ctrl_pos(chandef),
150 	};
151 	int ret;
152 
153 	if (ctx->ap.chan) {
154 		cmd.sbb_bandwidth =
155 			iwl_mld_nl80211_width_to_fw(ctx->ap.width);
156 		cmd.sbb_ctrl_channel_loc = iwl_mld_get_fw_ctrl_pos(&ctx->ap);
157 	}
158 
159 	/*
160 	 * Set NPCA channel if NPCA is used; if not used, just set it to an
161 	 * arbitrary channel on the other side to help firmware.
162 	 */
163 	if (chandef->npca_chan)
164 		cmd.secondary_ctrl_chnl_loc =
165 			_iwl_mld_get_fw_ctrl_pos(chandef->npca_chan->center_freq,
166 						 chandef->center_freq1);
167 	else
168 		cmd.secondary_ctrl_chnl_loc =
169 			cmd.ci.ctrl_pos ^ IWL_PHY_CTRL_POS_ABOVE;
170 
171 	ret = iwl_mld_send_cmd_pdu(mld, PHY_CONTEXT_CMD, &cmd);
172 	if (ret)
173 		IWL_ERR(mld, "Failed to send PHY_CONTEXT_CMD ret = %d\n", ret);
174 
175 	return ret;
176 }
177 
178 static u32 iwl_mld_get_phy_config(struct iwl_mld *mld)
179 {
180 	u32 phy_config = ~(FW_PHY_CFG_TX_CHAIN |
181 			   FW_PHY_CFG_RX_CHAIN);
182 	u32 valid_rx_ant = iwl_mld_get_valid_rx_ant(mld);
183 	u32 valid_tx_ant = iwl_mld_get_valid_tx_ant(mld);
184 
185 	phy_config |= valid_tx_ant << FW_PHY_CFG_TX_CHAIN_POS |
186 		      valid_rx_ant << FW_PHY_CFG_RX_CHAIN_POS;
187 
188 	return mld->fw->phy_config & phy_config;
189 }
190 
191 int iwl_mld_send_phy_cfg_cmd(struct iwl_mld *mld)
192 {
193 	const struct iwl_tlv_calib_ctrl *default_calib =
194 		&mld->fw->default_calib[IWL_UCODE_REGULAR];
195 	struct iwl_phy_cfg_cmd_v3 cmd = {
196 		.phy_cfg = cpu_to_le32(iwl_mld_get_phy_config(mld)),
197 		.calib_control.event_trigger = default_calib->event_trigger,
198 		.calib_control.flow_trigger = default_calib->flow_trigger,
199 		.phy_specific_cfg = mld->fwrt.phy_filters,
200 	};
201 
202 	IWL_DEBUG_INFO(mld, "Sending Phy CFG command: 0x%x\n", cmd.phy_cfg);
203 
204 	return iwl_mld_send_cmd_pdu(mld, PHY_CONFIGURATION_CMD, &cmd);
205 }
206 
207 void iwl_mld_update_phy_chandef(struct iwl_mld *mld,
208 				struct ieee80211_chanctx_conf *ctx)
209 {
210 	struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
211 	struct cfg80211_chan_def *chandef =
212 		iwl_mld_get_chandef_from_chanctx(mld, ctx);
213 
214 	phy->chandef = *chandef;
215 	iwl_mld_phy_fw_action(mld, ctx, FW_CTXT_ACTION_MODIFY);
216 }
217