root/drivers/net/wireless/intel/iwlwifi/mld/phy.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2024-2025 Intel Corporation
 */
#include <net/mac80211.h>

#include "phy.h"
#include "hcmd.h"
#include "fw/api/phy-ctxt.h"

int iwl_mld_allocate_fw_phy_id(struct iwl_mld *mld)
{
        int id;
        unsigned long used = mld->used_phy_ids;

        for_each_clear_bit(id, &used, NUM_PHY_CTX) {
                mld->used_phy_ids |= BIT(id);
                return id;
        }

        return -ENOSPC;
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_allocate_fw_phy_id);

struct iwl_mld_chanctx_usage_data {
        struct iwl_mld *mld;
        struct ieee80211_chanctx_conf *ctx;
        bool use_def;
};

static bool iwl_mld_chanctx_fils_enabled(struct ieee80211_vif *vif,
                                         struct ieee80211_chanctx_conf *ctx)
{
        if (vif->type != NL80211_IFTYPE_AP)
                return false;

        return cfg80211_channel_is_psc(ctx->def.chan) ||
                (ctx->def.chan->band == NL80211_BAND_6GHZ &&
                 ctx->def.width >= NL80211_CHAN_WIDTH_80);
}

static void iwl_mld_chanctx_usage_iter(void *_data, u8 *mac,
                                       struct ieee80211_vif *vif)
{
        struct iwl_mld_chanctx_usage_data *data = _data;
        struct ieee80211_bss_conf *link_conf;
        int link_id;

        for_each_vif_active_link(vif, link_conf, link_id) {
                if (rcu_access_pointer(link_conf->chanctx_conf) != data->ctx)
                        continue;

                if (vif->type == NL80211_IFTYPE_AP && link_conf->ftm_responder)
                        data->use_def = true;

                if (iwl_mld_chanctx_fils_enabled(vif, data->ctx))
                        data->use_def = true;
        }
}

struct cfg80211_chan_def *
iwl_mld_get_chandef_from_chanctx(struct iwl_mld *mld,
                                 struct ieee80211_chanctx_conf *ctx)
{
        struct iwl_mld_chanctx_usage_data data = {
                .mld = mld,
                .ctx = ctx,
        };

        ieee80211_iterate_active_interfaces_mtx(mld->hw,
                                                IEEE80211_IFACE_ITER_NORMAL,
                                                iwl_mld_chanctx_usage_iter,
                                                &data);

        return data.use_def ? &ctx->def : &ctx->min_def;
}

static u8
iwl_mld_nl80211_width_to_fw(enum nl80211_chan_width width)
{
        switch (width) {
        case NL80211_CHAN_WIDTH_20_NOHT:
        case NL80211_CHAN_WIDTH_20:
                return IWL_PHY_CHANNEL_MODE20;
        case NL80211_CHAN_WIDTH_40:
                return IWL_PHY_CHANNEL_MODE40;
        case NL80211_CHAN_WIDTH_80:
                return IWL_PHY_CHANNEL_MODE80;
        case NL80211_CHAN_WIDTH_160:
                return IWL_PHY_CHANNEL_MODE160;
        case NL80211_CHAN_WIDTH_320:
                return IWL_PHY_CHANNEL_MODE320;
        default:
                WARN(1, "Invalid channel width=%u", width);
                return IWL_PHY_CHANNEL_MODE20;
        }
}

/* Maps the driver specific control channel position (relative to the center
 * freq) definitions to the fw values
 */
u8 iwl_mld_get_fw_ctrl_pos(const struct cfg80211_chan_def *chandef)
{
        int offs = chandef->chan->center_freq - chandef->center_freq1;
        int abs_offs = abs(offs);
        u8 ret;

        if (offs == 0) {
                /* The FW is expected to check the control channel position only
                 * when in HT/VHT and the channel width is not 20MHz. Return
                 * this value as the default one.
                 */
                return 0;
        }

        /* this results in a value 0-7, i.e. fitting into 0b0111 */
        ret = (abs_offs - 10) / 20;
        /* But we need the value to be in 0b1011 because 0b0100 is
         * IWL_PHY_CTRL_POS_ABOVE, so shift bit 2 up to land in
         * IWL_PHY_CTRL_POS_OFFS_EXT (0b1000)
         */
        ret = (ret & IWL_PHY_CTRL_POS_OFFS_MSK) |
              ((ret & BIT(2)) << 1);
        /* and add the above bit */
        ret |= (offs > 0) * IWL_PHY_CTRL_POS_ABOVE;

        return ret;
}

int iwl_mld_phy_fw_action(struct iwl_mld *mld,
                          struct ieee80211_chanctx_conf *ctx, u32 action)
{
        struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
        struct cfg80211_chan_def *chandef = &phy->chandef;
        struct iwl_phy_context_cmd cmd = {
                .id_and_color = cpu_to_le32(phy->fw_id),
                .action = cpu_to_le32(action),
                .puncture_mask = cpu_to_le16(chandef->punctured),
                /* Channel info */
                .ci.channel = cpu_to_le32(chandef->chan->hw_value),
                .ci.band = iwl_mld_nl80211_band_to_fw(chandef->chan->band),
                .ci.width = iwl_mld_nl80211_width_to_fw(chandef->width),
                .ci.ctrl_pos = iwl_mld_get_fw_ctrl_pos(chandef),
        };
        int ret;

        if (ctx->ap.chan) {
                cmd.sbb_bandwidth =
                        iwl_mld_nl80211_width_to_fw(ctx->ap.width);
                cmd.sbb_ctrl_channel_loc = iwl_mld_get_fw_ctrl_pos(&ctx->ap);
        }

        ret = iwl_mld_send_cmd_pdu(mld, PHY_CONTEXT_CMD, &cmd);
        if (ret)
                IWL_ERR(mld, "Failed to send PHY_CONTEXT_CMD ret = %d\n", ret);

        return ret;
}

static u32 iwl_mld_get_phy_config(struct iwl_mld *mld)
{
        u32 phy_config = ~(FW_PHY_CFG_TX_CHAIN |
                           FW_PHY_CFG_RX_CHAIN);
        u32 valid_rx_ant = iwl_mld_get_valid_rx_ant(mld);
        u32 valid_tx_ant = iwl_mld_get_valid_tx_ant(mld);

        phy_config |= valid_tx_ant << FW_PHY_CFG_TX_CHAIN_POS |
                      valid_rx_ant << FW_PHY_CFG_RX_CHAIN_POS;

        return mld->fw->phy_config & phy_config;
}

int iwl_mld_send_phy_cfg_cmd(struct iwl_mld *mld)
{
        const struct iwl_tlv_calib_ctrl *default_calib =
                &mld->fw->default_calib[IWL_UCODE_REGULAR];
        struct iwl_phy_cfg_cmd_v3 cmd = {
                .phy_cfg = cpu_to_le32(iwl_mld_get_phy_config(mld)),
                .calib_control.event_trigger = default_calib->event_trigger,
                .calib_control.flow_trigger = default_calib->flow_trigger,
                .phy_specific_cfg = mld->fwrt.phy_filters,
        };

        IWL_DEBUG_INFO(mld, "Sending Phy CFG command: 0x%x\n", cmd.phy_cfg);

        return iwl_mld_send_cmd_pdu(mld, PHY_CONFIGURATION_CMD, &cmd);
}

void iwl_mld_update_phy_chandef(struct iwl_mld *mld,
                                struct ieee80211_chanctx_conf *ctx)
{
        struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
        struct cfg80211_chan_def *chandef =
                iwl_mld_get_chandef_from_chanctx(mld, ctx);

        phy->chandef = *chandef;
        iwl_mld_phy_fw_action(mld, ctx, FW_CTXT_ACTION_MODIFY);
}