root/src/add-ons/kernel/drivers/network/wlan/atheroswifi/dev/ath/ath_hal/ar5416/ar5416_radar.c
/*-
 * SPDX-License-Identifier: ISC
 *
 * Copyright (c) 2010-2011 Atheros Communications, Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "opt_ah.h"

#include "ah.h"
#include "ah_internal.h"
#include "ah_devid.h"
#include "ah_desc.h"                    /* NB: for HAL_PHYERR* */

#include "ar5416/ar5416.h"
#include "ar5416/ar5416reg.h"
#include "ar5416/ar5416phy.h"

#include "ah_eeprom_v14.h"      /* for owl_get_ntxchains() */

/*
 * These are default parameters for the AR5416 and
 * later 802.11n NICs.  They simply enable some
 * radar pulse event generation.
 *
 * These are very likely not valid for the AR5212 era
 * NICs.
 *
 * Since these define signal sizing and threshold
 * parameters, they may need changing based on the
 * specific antenna and receive amplifier
 * configuration.
 */
#define AR5416_DFS_FIRPWR       -33
#define AR5416_DFS_RRSSI        20
#define AR5416_DFS_HEIGHT       10
#define AR5416_DFS_PRSSI        15
#define AR5416_DFS_INBAND       15
#define AR5416_DFS_RELPWR       8
#define AR5416_DFS_RELSTEP      12
#define AR5416_DFS_MAXLEN       255

HAL_BOOL
ar5416GetDfsDefaultThresh(struct ath_hal *ah, HAL_PHYERR_PARAM *pe)
{

        /*
         * These are general examples of the parameter values
         * to use when configuring radar pulse detection for
         * the AR5416, AR91xx, AR92xx NICs.  They are only
         * for testing and do require tuning depending upon the
         * hardware and deployment specifics.
         */
        pe->pe_firpwr = AR5416_DFS_FIRPWR;
        pe->pe_rrssi = AR5416_DFS_RRSSI;
        pe->pe_height = AR5416_DFS_HEIGHT;
        pe->pe_prssi = AR5416_DFS_PRSSI;
        pe->pe_inband = AR5416_DFS_INBAND;
        pe->pe_relpwr = AR5416_DFS_RELPWR;
        pe->pe_relstep = AR5416_DFS_RELSTEP;
        pe->pe_maxlen = AR5416_DFS_MAXLEN;

        return (AH_TRUE);
}

/*
 * Get the radar parameter values and return them in the pe
 * structure
 */
void
ar5416GetDfsThresh(struct ath_hal *ah, HAL_PHYERR_PARAM *pe)
{
        uint32_t val, temp;

        val = OS_REG_READ(ah, AR_PHY_RADAR_0);

        temp = MS(val,AR_PHY_RADAR_0_FIRPWR);
        temp |= 0xFFFFFF80;
        pe->pe_firpwr = temp;
        pe->pe_rrssi = MS(val, AR_PHY_RADAR_0_RRSSI);
        pe->pe_height =  MS(val, AR_PHY_RADAR_0_HEIGHT);
        pe->pe_prssi = MS(val, AR_PHY_RADAR_0_PRSSI);
        pe->pe_inband = MS(val, AR_PHY_RADAR_0_INBAND);

        /* RADAR_1 values */
        val = OS_REG_READ(ah, AR_PHY_RADAR_1);
        pe->pe_relpwr = MS(val, AR_PHY_RADAR_1_RELPWR_THRESH);
        pe->pe_relstep = MS(val, AR_PHY_RADAR_1_RELSTEP_THRESH);
        pe->pe_maxlen = MS(val, AR_PHY_RADAR_1_MAXLEN);

        pe->pe_extchannel = !! (OS_REG_READ(ah, AR_PHY_RADAR_EXT) &
            AR_PHY_RADAR_EXT_ENA);

        pe->pe_usefir128 = !! (OS_REG_READ(ah, AR_PHY_RADAR_1) &
            AR_PHY_RADAR_1_USE_FIR128);
        pe->pe_blockradar = !! (OS_REG_READ(ah, AR_PHY_RADAR_1) &
            AR_PHY_RADAR_1_BLOCK_CHECK);
        pe->pe_enmaxrssi = !! (OS_REG_READ(ah, AR_PHY_RADAR_1) &
            AR_PHY_RADAR_1_MAX_RRSSI);
        pe->pe_enabled = !!
            (OS_REG_READ(ah, AR_PHY_RADAR_0) & AR_PHY_RADAR_0_ENA);
        pe->pe_enrelpwr = !! (OS_REG_READ(ah, AR_PHY_RADAR_1) &
            AR_PHY_RADAR_1_RELPWR_ENA);
        pe->pe_en_relstep_check = !! (OS_REG_READ(ah, AR_PHY_RADAR_1) &
            AR_PHY_RADAR_1_RELSTEP_CHECK);
}

/*
 * Enable radar detection and set the radar parameters per the
 * values in pe
 */
void
ar5416EnableDfs(struct ath_hal *ah, HAL_PHYERR_PARAM *pe)
{
        uint32_t val;

        val = OS_REG_READ(ah, AR_PHY_RADAR_0);

        if (pe->pe_firpwr != HAL_PHYERR_PARAM_NOVAL) {
                val &= ~AR_PHY_RADAR_0_FIRPWR;
                val |= SM(pe->pe_firpwr, AR_PHY_RADAR_0_FIRPWR);
        }
        if (pe->pe_rrssi != HAL_PHYERR_PARAM_NOVAL) {
                val &= ~AR_PHY_RADAR_0_RRSSI;
                val |= SM(pe->pe_rrssi, AR_PHY_RADAR_0_RRSSI);
        }
        if (pe->pe_height != HAL_PHYERR_PARAM_NOVAL) {
                val &= ~AR_PHY_RADAR_0_HEIGHT;
                val |= SM(pe->pe_height, AR_PHY_RADAR_0_HEIGHT);
        }
        if (pe->pe_prssi != HAL_PHYERR_PARAM_NOVAL) {
                val &= ~AR_PHY_RADAR_0_PRSSI;
                val |= SM(pe->pe_prssi, AR_PHY_RADAR_0_PRSSI);
        }
        if (pe->pe_inband != HAL_PHYERR_PARAM_NOVAL) {
                val &= ~AR_PHY_RADAR_0_INBAND;
                val |= SM(pe->pe_inband, AR_PHY_RADAR_0_INBAND);
        }

        /*Enable FFT data*/
        val |= AR_PHY_RADAR_0_FFT_ENA;
        OS_REG_WRITE(ah, AR_PHY_RADAR_0, val);

        /* Implicitly enable */
        if (pe->pe_enabled == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_ENA);
        else if (pe->pe_enabled == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_ENA);

        if (pe->pe_usefir128 == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_1, AR_PHY_RADAR_1_USE_FIR128);
        else if (pe->pe_usefir128 == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_1, AR_PHY_RADAR_1_USE_FIR128);

        if (pe->pe_enmaxrssi == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_1, AR_PHY_RADAR_1_MAX_RRSSI);
        else if (pe->pe_enmaxrssi == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_1, AR_PHY_RADAR_1_MAX_RRSSI);

        if (pe->pe_blockradar == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_1, AR_PHY_RADAR_1_BLOCK_CHECK);
        else if (pe->pe_blockradar == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_1, AR_PHY_RADAR_1_BLOCK_CHECK);

        if (pe->pe_relstep != HAL_PHYERR_PARAM_NOVAL) {
                val = OS_REG_READ(ah, AR_PHY_RADAR_1);
                val &= ~AR_PHY_RADAR_1_RELSTEP_THRESH;
                val |= SM(pe->pe_relstep, AR_PHY_RADAR_1_RELSTEP_THRESH);
                OS_REG_WRITE(ah, AR_PHY_RADAR_1, val);
        }
        if (pe->pe_relpwr != HAL_PHYERR_PARAM_NOVAL) {
                val = OS_REG_READ(ah, AR_PHY_RADAR_1);
                val &= ~AR_PHY_RADAR_1_RELPWR_THRESH;
                val |= SM(pe->pe_relpwr, AR_PHY_RADAR_1_RELPWR_THRESH);
                OS_REG_WRITE(ah, AR_PHY_RADAR_1, val);
        }

        if (pe->pe_en_relstep_check == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_1,
                    AR_PHY_RADAR_1_RELSTEP_CHECK);
        else if (pe->pe_en_relstep_check == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_1,
                    AR_PHY_RADAR_1_RELSTEP_CHECK);

        if (pe->pe_enrelpwr == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_1,
                    AR_PHY_RADAR_1_RELPWR_ENA);
        else if (pe->pe_enrelpwr == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_1,
                    AR_PHY_RADAR_1_RELPWR_ENA);

        if (pe->pe_maxlen != HAL_PHYERR_PARAM_NOVAL) {
                val = OS_REG_READ(ah, AR_PHY_RADAR_1);
                val &= ~AR_PHY_RADAR_1_MAXLEN;
                val |= SM(pe->pe_maxlen, AR_PHY_RADAR_1_MAXLEN);
                OS_REG_WRITE(ah, AR_PHY_RADAR_1, val);
        }

        /*
         * Enable HT/40 if the upper layer asks;
         * it should check the channel is HT/40 and HAL_CAP_EXT_CHAN_DFS
         * is available.
         */
        if (pe->pe_extchannel == 1)
                OS_REG_SET_BIT(ah, AR_PHY_RADAR_EXT, AR_PHY_RADAR_EXT_ENA);
        else if (pe->pe_extchannel == 0)
                OS_REG_CLR_BIT(ah, AR_PHY_RADAR_EXT, AR_PHY_RADAR_EXT_ENA);
}

/*
 * Extract the radar event information from the given phy error.
 *
 * Returns AH_TRUE if the phy error was actually a phy error,
 * AH_FALSE if the phy error wasn't a phy error.
 */

/* Flags for pulse_bw_info */
#define PRI_CH_RADAR_FOUND              0x01
#define EXT_CH_RADAR_FOUND              0x02
#define EXT_CH_RADAR_EARLY_FOUND        0x04

HAL_BOOL
ar5416ProcessRadarEvent(struct ath_hal *ah, struct ath_rx_status *rxs,
    uint64_t fulltsf, const char *buf, HAL_DFS_EVENT *event)
{
        HAL_BOOL doDfsExtCh;
        HAL_BOOL doDfsEnhanced;
        HAL_BOOL doDfsCombinedRssi;

        uint8_t rssi = 0, ext_rssi = 0;
        uint8_t pulse_bw_info = 0, pulse_length_ext = 0, pulse_length_pri = 0;
        uint32_t dur = 0;
        int pri_found = 1, ext_found = 0;
        int early_ext = 0;
        int is_dc = 0;
        uint16_t datalen;               /* length from the RX status field */

        /* Check whether the given phy error is a radar event */
        if ((rxs->rs_phyerr != HAL_PHYERR_RADAR) &&
            (rxs->rs_phyerr != HAL_PHYERR_FALSE_RADAR_EXT)) {
                return AH_FALSE;
        }

        /* Grab copies of the capabilities; just to make the code clearer */
        doDfsExtCh = AH_PRIVATE(ah)->ah_caps.halExtChanDfsSupport;
        doDfsEnhanced = AH_PRIVATE(ah)->ah_caps.halEnhancedDfsSupport;
        doDfsCombinedRssi = AH_PRIVATE(ah)->ah_caps.halUseCombinedRadarRssi;

        datalen = rxs->rs_datalen;

        /* If hardware supports it, use combined RSSI, else use chain 0 RSSI */
        if (doDfsCombinedRssi)
                rssi = (uint8_t) rxs->rs_rssi;
        else            
                rssi = (uint8_t) rxs->rs_rssi_ctl[0];

        /* Set this; but only use it if doDfsExtCh is set */
        ext_rssi = (uint8_t) rxs->rs_rssi_ext[0];

        /* Cap it at 0 if the RSSI is a negative number */
        if (rssi & 0x80)
                rssi = 0;

        if (ext_rssi & 0x80)
                ext_rssi = 0;

        /*
         * Fetch the relevant data from the frame
         */
        if (doDfsExtCh) {
                if (datalen < 3)
                        return AH_FALSE;

                /* Last three bytes of the frame are of interest */
                pulse_length_pri = *(buf + datalen - 3);
                pulse_length_ext = *(buf + datalen - 2);
                pulse_bw_info = *(buf + datalen - 1);
                HALDEBUG(ah, HAL_DEBUG_DFS, "%s: rssi=%d, ext_rssi=%d, pulse_length_pri=%d,"
                    " pulse_length_ext=%d, pulse_bw_info=%x\n",
                    __func__, rssi, ext_rssi, pulse_length_pri, pulse_length_ext,
                    pulse_bw_info);
        } else {
                /* The pulse width is byte 0 of the data */
                if (datalen >= 1)
                        dur = ((uint8_t) buf[0]) & 0xff;
                else
                        dur = 0;

                if (dur == 0 && rssi == 0) {
                        HALDEBUG(ah, HAL_DEBUG_DFS, "%s: dur and rssi are 0\n", __func__);
                        return AH_FALSE;
                }

                HALDEBUG(ah, HAL_DEBUG_DFS, "%s: rssi=%d, dur=%d\n", __func__, rssi, dur);

                /* Single-channel only */
                pri_found = 1;
                ext_found = 0;
        }

        /*
         * If doing extended channel data, pulse_bw_info must
         * have one of the flags set.
         */
        if (doDfsExtCh && pulse_bw_info == 0x0)
                return AH_FALSE;
                
        /*
         * If the extended channel data is available, calculate
         * which to pay attention to.
         */
        if (doDfsExtCh) {
                /* If pulse is on DC, take the larger duration of the two */
                if ((pulse_bw_info & EXT_CH_RADAR_FOUND) &&
                    (pulse_bw_info & PRI_CH_RADAR_FOUND)) {
                        is_dc = 1;
                        if (pulse_length_ext > pulse_length_pri) {
                                dur = pulse_length_ext;
                                pri_found = 0;
                                ext_found = 1;
                        } else {
                                dur = pulse_length_pri;
                                pri_found = 1;
                                ext_found = 0;
                        }
                } else if (pulse_bw_info & EXT_CH_RADAR_EARLY_FOUND) {
                        dur = pulse_length_ext;
                        pri_found = 0;
                        ext_found = 1;
                        early_ext = 1;
                } else if (pulse_bw_info & PRI_CH_RADAR_FOUND) {
                        dur = pulse_length_pri;
                        pri_found = 1;
                        ext_found = 0;
                } else if (pulse_bw_info & EXT_CH_RADAR_FOUND) {
                        dur = pulse_length_ext;
                        pri_found = 0;
                        ext_found = 1;
                }
                
        }

        /*
         * For enhanced DFS (Merlin and later), pulse_bw_info has
         * implications for selecting the correct RSSI value.
         */
        if (doDfsEnhanced) {
                switch (pulse_bw_info & 0x03) {
                case 0:
                        /* No radar? */
                        rssi = 0;
                        break;
                case PRI_CH_RADAR_FOUND:
                        /* Radar in primary channel */
                        /* Cannot use ctrl channel RSSI if ext channel is stronger */
                        if (ext_rssi >= (rssi + 3)) {
                                rssi = 0;
                        }
                        break;
                case EXT_CH_RADAR_FOUND:
                        /* Radar in extended channel */
                        /* Cannot use ext channel RSSI if ctrl channel is stronger */
                        if (rssi >= (ext_rssi + 12)) {
                                rssi = 0;
                        } else {
                                rssi = ext_rssi;
                        }
                        break;
                case (PRI_CH_RADAR_FOUND | EXT_CH_RADAR_FOUND):
                        /* When both are present, use stronger one */
                        if (rssi < ext_rssi)
                                rssi = ext_rssi;
                        break;
                }
        }

        /*
         * If not doing enhanced DFS, choose the ext channel if
         * it is stronger than the main channel
         */
        if (doDfsExtCh && !doDfsEnhanced) {
                if ((ext_rssi > rssi) && (ext_rssi < 128))
                        rssi = ext_rssi;
        }

        /*
         * XXX what happens if the above code decides the RSSI
         * XXX wasn't valid, an sets it to 0?
         */

        /*
         * Fill out dfs_event structure.
         */
        event->re_full_ts = fulltsf;
        event->re_ts = rxs->rs_tstamp;
        event->re_rssi = rssi;
        event->re_dur = dur;

        event->re_flags = 0;
        if (pri_found)
                event->re_flags |= HAL_DFS_EVENT_PRICH;
        if (ext_found)
                event->re_flags |= HAL_DFS_EVENT_EXTCH;
        if (early_ext)
                event->re_flags |= HAL_DFS_EVENT_EXTEARLY;
        if (is_dc)
                event->re_flags |= HAL_DFS_EVENT_ISDC;

        return AH_TRUE;
}

/*
 * Return whether fast-clock is currently enabled for this
 * channel.
 */
HAL_BOOL
ar5416IsFastClockEnabled(struct ath_hal *ah)
{
        struct ath_hal_private *ahp = AH_PRIVATE(ah);

        return IS_5GHZ_FAST_CLOCK_EN(ah, ahp->ah_curchan);
}