root/sys/dev/ath/if_ath_lna_div.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2013 Adrian Chadd <adrian@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */
#include <sys/cdefs.h>
/*
 * This module handles LNA diversity for those chips which implement LNA
 * mixing (AR9285/AR9485.)
 */
#include "opt_ath.h"
#include "opt_inet.h"
#include "opt_wlan.h"

#include <sys/param.h>
#include <sys/systm.h> 
#include <sys/sysctl.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/errno.h>

#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/bus.h>

#include <sys/socket.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_media.h>
#include <net/if_arp.h>
#include <net/ethernet.h>               /* XXX for ether_sprintf */

#include <net80211/ieee80211_var.h>

#include <net/bpf.h>

#ifdef INET
#include <netinet/in.h>
#include <netinet/if_ether.h>
#endif

#include <dev/ath/if_athvar.h>
#include <dev/ath/if_ath_debug.h>
#include <dev/ath/if_ath_lna_div.h>

/* Linux compatibility macros */
/*
 * XXX these don't handle rounding, underflow, overflow, wrapping!
 */
#define msecs_to_jiffies(a)             ( (a) * hz / 1000 )

/*
 * Methods which are required
 */

/*
 * Attach the LNA diversity to the given interface
 */
int
ath_lna_div_attach(struct ath_softc *sc)
{
        struct if_ath_ant_comb_state *ss;
        HAL_ANT_COMB_CONFIG div_ant_conf;

        /* Only do this if diversity is enabled */
        if (! ath_hal_hasdivantcomb(sc->sc_ah))
                return (0);

        ss = malloc(sizeof(struct if_ath_ant_comb_state),
            M_TEMP, M_WAITOK | M_ZERO);

        /* Fetch the hardware configuration */
        OS_MEMZERO(&div_ant_conf, sizeof(div_ant_conf));
        ath_hal_div_comb_conf_get(sc->sc_ah, &div_ant_conf);

        /* Figure out what the hardware specific bits should be */
        if ((div_ant_conf.antdiv_configgroup == HAL_ANTDIV_CONFIG_GROUP_1) ||
            (div_ant_conf.antdiv_configgroup == HAL_ANTDIV_CONFIG_GROUP_2)) {
                ss->lna1_lna2_delta = -9;
        } else {
                ss->lna1_lna2_delta = -3;
        }

        /* Let's flip this on */
        sc->sc_lna_div = ss;
        sc->sc_dolnadiv = 1;

        return (0);
}

/*
 * Detach the LNA diversity state from the given interface
 */
int
ath_lna_div_detach(struct ath_softc *sc)
{
        if (sc->sc_lna_div != NULL) {
                free(sc->sc_lna_div, M_TEMP);
                sc->sc_lna_div = NULL;
        }
        sc->sc_dolnadiv = 0;
        return (0);
}

/*
 * Enable LNA diversity on the current channel if it's required.
 */
int
ath_lna_div_enable(struct ath_softc *sc, const struct ieee80211_channel *chan)
{

        return (0);
}

/*
 * Handle ioctl requests from the diagnostic interface.
 *
 * The initial part of this code resembles ath_ioctl_diag();
 * it's likely a good idea to reduce duplication between
 * these two routines.
 */
int
ath_lna_div_ioctl(struct ath_softc *sc, struct ath_diag *ad)
{
        unsigned int id = ad->ad_id & ATH_DIAG_ID;
        void *indata = NULL;
        void *outdata = NULL;
        u_int32_t insize = ad->ad_in_size;
        u_int32_t outsize = ad->ad_out_size;
        int error = 0;
//      int val;

        if (ad->ad_id & ATH_DIAG_IN) {
                /*
                 * Copy in data.
                 */
                indata = malloc(insize, M_TEMP, M_NOWAIT);
                if (indata == NULL) {
                        error = ENOMEM;
                        goto bad;
                }
                error = copyin(ad->ad_in_data, indata, insize);
                if (error)
                        goto bad;
        }
        if (ad->ad_id & ATH_DIAG_DYN) {
                /*
                 * Allocate a buffer for the results (otherwise the HAL
                 * returns a pointer to a buffer where we can read the
                 * results).  Note that we depend on the HAL leaving this
                 * pointer for us to use below in reclaiming the buffer;
                 * may want to be more defensive.
                 */
                outdata = malloc(outsize, M_TEMP, M_NOWAIT | M_ZERO);
                if (outdata == NULL) {
                        error = ENOMEM;
                        goto bad;
                }
        }
        switch (id) {
                default:
                        error = EINVAL;
                        goto bad;
        }
        if (outsize < ad->ad_out_size)
                ad->ad_out_size = outsize;
        if (outdata && copyout(outdata, ad->ad_out_data, ad->ad_out_size))
                error = EFAULT;
bad:
        if ((ad->ad_id & ATH_DIAG_IN) && indata != NULL)
                free(indata, M_TEMP);
        if ((ad->ad_id & ATH_DIAG_DYN) && outdata != NULL)
                free(outdata, M_TEMP);
        return (error);
}

/*
 * XXX need to low_rssi_thresh config from ath9k, to support CUS198
 * antenna diversity correctly.
 */
static HAL_BOOL
ath_is_alt_ant_ratio_better(int alt_ratio, int maxdelta, int mindelta,
    int main_rssi_avg, int alt_rssi_avg, int pkt_count)
{
        return (((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) &&
                (alt_rssi_avg > main_rssi_avg + maxdelta)) ||
                (alt_rssi_avg > main_rssi_avg + mindelta)) && (pkt_count > 50);
}

static void
ath_lnaconf_alt_good_scan(struct if_ath_ant_comb_state *antcomb,
    HAL_ANT_COMB_CONFIG *ant_conf, int main_rssi_avg)
{
        antcomb->quick_scan_cnt = 0;

        if (ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA2)
                antcomb->rssi_lna2 = main_rssi_avg;
        else if (ant_conf->main_lna_conf == HAL_ANT_DIV_COMB_LNA1)
                antcomb->rssi_lna1 = main_rssi_avg;

        switch ((ant_conf->main_lna_conf << 4) | ant_conf->alt_lna_conf) {
        case (0x10): /* LNA2 A-B */
                antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                antcomb->first_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1;
                break;
        case (0x20): /* LNA1 A-B */
                antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                antcomb->first_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA2;
                break;
        case (0x21): /* LNA1 LNA2 */
                antcomb->main_conf = HAL_ANT_DIV_COMB_LNA2;
                antcomb->first_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                antcomb->second_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                break;
        case (0x12): /* LNA2 LNA1 */
                antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1;
                antcomb->first_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                antcomb->second_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                break;
        case (0x13): /* LNA2 A+B */
                antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                antcomb->first_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA1;
                break;
        case (0x23): /* LNA1 A+B */
                antcomb->main_conf = HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                antcomb->first_quick_scan_conf =
                        HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                antcomb->second_quick_scan_conf = HAL_ANT_DIV_COMB_LNA2;
                break;
        default:
                break;
        }
}

static void
ath_select_ant_div_from_quick_scan(struct if_ath_ant_comb_state *antcomb,
    HAL_ANT_COMB_CONFIG *div_ant_conf, int main_rssi_avg,
    int alt_rssi_avg, int alt_ratio)
{
        /* alt_good */
        switch (antcomb->quick_scan_cnt) {
        case 0:
                /* set alt to main, and alt to first conf */
                div_ant_conf->main_lna_conf = antcomb->main_conf;
                div_ant_conf->alt_lna_conf = antcomb->first_quick_scan_conf;
                break;
        case 1:
                /* set alt to main, and alt to first conf */
                div_ant_conf->main_lna_conf = antcomb->main_conf;
                div_ant_conf->alt_lna_conf = antcomb->second_quick_scan_conf;
                antcomb->rssi_first = main_rssi_avg;
                antcomb->rssi_second = alt_rssi_avg;

                if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) {
                        /* main is LNA1 */
                        if (ath_is_alt_ant_ratio_better(alt_ratio,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_HI,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_LOW,
                                                main_rssi_avg, alt_rssi_avg,
                                                antcomb->total_pkt_count))
                                antcomb->first_ratio = AH_TRUE;
                        else
                                antcomb->first_ratio = AH_FALSE;
                } else if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2) {
                        if (ath_is_alt_ant_ratio_better(alt_ratio,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_MID,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_LOW,
                                                main_rssi_avg, alt_rssi_avg,
                                                antcomb->total_pkt_count))
                                antcomb->first_ratio = AH_TRUE;
                        else
                                antcomb->first_ratio = AH_FALSE;
                } else {
                        if ((((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) &&
                            (alt_rssi_avg > main_rssi_avg +
                            ATH_ANT_DIV_COMB_LNA1_DELTA_HI)) ||
                            (alt_rssi_avg > main_rssi_avg)) &&
                            (antcomb->total_pkt_count > 50))
                                antcomb->first_ratio = AH_TRUE;
                        else
                                antcomb->first_ratio = AH_FALSE;
                }
                break;
        case 2:
                antcomb->alt_good = AH_FALSE;
                antcomb->scan_not_start = AH_FALSE;
                antcomb->scan = AH_FALSE;
                antcomb->rssi_first = main_rssi_avg;
                antcomb->rssi_third = alt_rssi_avg;

                if (antcomb->second_quick_scan_conf == HAL_ANT_DIV_COMB_LNA1)
                        antcomb->rssi_lna1 = alt_rssi_avg;
                else if (antcomb->second_quick_scan_conf ==
                         HAL_ANT_DIV_COMB_LNA2)
                        antcomb->rssi_lna2 = alt_rssi_avg;
                else if (antcomb->second_quick_scan_conf ==
                         HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2) {
                        if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2)
                                antcomb->rssi_lna2 = main_rssi_avg;
                        else if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1)
                                antcomb->rssi_lna1 = main_rssi_avg;
                }

                if (antcomb->rssi_lna2 > antcomb->rssi_lna1 +
                    ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA)
                        div_ant_conf->main_lna_conf = HAL_ANT_DIV_COMB_LNA2;
                else
                        div_ant_conf->main_lna_conf = HAL_ANT_DIV_COMB_LNA1;

                if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) {
                        if (ath_is_alt_ant_ratio_better(alt_ratio,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_HI,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_LOW,
                                                main_rssi_avg, alt_rssi_avg,
                                                antcomb->total_pkt_count))
                                antcomb->second_ratio = AH_TRUE;
                        else
                                antcomb->second_ratio = AH_FALSE;
                } else if (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2) {
                        if (ath_is_alt_ant_ratio_better(alt_ratio,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_MID,
                                                ATH_ANT_DIV_COMB_LNA1_DELTA_LOW,
                                                main_rssi_avg, alt_rssi_avg,
                                                antcomb->total_pkt_count))
                                antcomb->second_ratio = AH_TRUE;
                        else
                                antcomb->second_ratio = AH_FALSE;
                } else {
                        if ((((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) &&
                            (alt_rssi_avg > main_rssi_avg +
                            ATH_ANT_DIV_COMB_LNA1_DELTA_HI)) ||
                            (alt_rssi_avg > main_rssi_avg)) &&
                            (antcomb->total_pkt_count > 50))
                                antcomb->second_ratio = AH_TRUE;
                        else
                                antcomb->second_ratio = AH_FALSE;
                }

                /* set alt to the conf with maximun ratio */
                if (antcomb->first_ratio && antcomb->second_ratio) {
                        if (antcomb->rssi_second > antcomb->rssi_third) {
                                /* first alt*/
                                if ((antcomb->first_quick_scan_conf ==
                                    HAL_ANT_DIV_COMB_LNA1) ||
                                    (antcomb->first_quick_scan_conf ==
                                    HAL_ANT_DIV_COMB_LNA2))
                                        /* Set alt LNA1 or LNA2*/
                                        if (div_ant_conf->main_lna_conf ==
                                            HAL_ANT_DIV_COMB_LNA2)
                                                div_ant_conf->alt_lna_conf =
                                                        HAL_ANT_DIV_COMB_LNA1;
                                        else
                                                div_ant_conf->alt_lna_conf =
                                                        HAL_ANT_DIV_COMB_LNA2;
                                else
                                        /* Set alt to A+B or A-B */
                                        div_ant_conf->alt_lna_conf =
                                                antcomb->first_quick_scan_conf;
                        } else if ((antcomb->second_quick_scan_conf ==
                                   HAL_ANT_DIV_COMB_LNA1) ||
                                   (antcomb->second_quick_scan_conf ==
                                   HAL_ANT_DIV_COMB_LNA2)) {
                                /* Set alt LNA1 or LNA2 */
                                if (div_ant_conf->main_lna_conf ==
                                    HAL_ANT_DIV_COMB_LNA2)
                                        div_ant_conf->alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                else
                                        div_ant_conf->alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                        } else {
                                /* Set alt to A+B or A-B */
                                div_ant_conf->alt_lna_conf =
                                        antcomb->second_quick_scan_conf;
                        }
                } else if (antcomb->first_ratio) {
                        /* first alt */
                        if ((antcomb->first_quick_scan_conf ==
                            HAL_ANT_DIV_COMB_LNA1) ||
                            (antcomb->first_quick_scan_conf ==
                            HAL_ANT_DIV_COMB_LNA2))
                                        /* Set alt LNA1 or LNA2 */
                                if (div_ant_conf->main_lna_conf ==
                                    HAL_ANT_DIV_COMB_LNA2)
                                        div_ant_conf->alt_lna_conf =
                                                        HAL_ANT_DIV_COMB_LNA1;
                                else
                                        div_ant_conf->alt_lna_conf =
                                                        HAL_ANT_DIV_COMB_LNA2;
                        else
                                /* Set alt to A+B or A-B */
                                div_ant_conf->alt_lna_conf =
                                                antcomb->first_quick_scan_conf;
                } else if (antcomb->second_ratio) {
                                /* second alt */
                        if ((antcomb->second_quick_scan_conf ==
                            HAL_ANT_DIV_COMB_LNA1) ||
                            (antcomb->second_quick_scan_conf ==
                            HAL_ANT_DIV_COMB_LNA2))
                                /* Set alt LNA1 or LNA2 */
                                if (div_ant_conf->main_lna_conf ==
                                    HAL_ANT_DIV_COMB_LNA2)
                                        div_ant_conf->alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                else
                                        div_ant_conf->alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                        else
                                /* Set alt to A+B or A-B */
                                div_ant_conf->alt_lna_conf =
                                                antcomb->second_quick_scan_conf;
                } else {
                        /* main is largest */
                        if ((antcomb->main_conf == HAL_ANT_DIV_COMB_LNA1) ||
                            (antcomb->main_conf == HAL_ANT_DIV_COMB_LNA2))
                                /* Set alt LNA1 or LNA2 */
                                if (div_ant_conf->main_lna_conf ==
                                    HAL_ANT_DIV_COMB_LNA2)
                                        div_ant_conf->alt_lna_conf =
                                                        HAL_ANT_DIV_COMB_LNA1;
                                else
                                        div_ant_conf->alt_lna_conf =
                                                        HAL_ANT_DIV_COMB_LNA2;
                        else
                                /* Set alt to A+B or A-B */
                                div_ant_conf->alt_lna_conf = antcomb->main_conf;
                }
                break;
        default:
                break;
        }
}

static void
ath_ant_adjust_fast_divbias(struct if_ath_ant_comb_state *antcomb,
    int alt_ratio, int alt_ant_ratio_th, u_int config_group,
    HAL_ANT_COMB_CONFIG *pdiv_ant_conf)
{

        if (config_group == HAL_ANTDIV_CONFIG_GROUP_1) {
                switch ((pdiv_ant_conf->main_lna_conf << 4)
                    | pdiv_ant_conf->alt_lna_conf) {
                case (0x01): //A-B LNA2
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x02): //A-B LNA1
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x03): //A-B A+B
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x10): //LNA2 A-B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) {
                                pdiv_ant_conf->fast_div_bias = 0x3f;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x12): //LNA2 LNA1
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                        case (0x13): //LNA2 A+B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) {
                                pdiv_ant_conf->fast_div_bias = 0x3f;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x20): //LNA1 A-B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) {
                                pdiv_ant_conf->fast_div_bias = 0x3f;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x21): //LNA1 LNA2
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x23): //LNA1 A+B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) {
                                pdiv_ant_conf->fast_div_bias = 0x3f;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x30): //A+B A-B
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x31): //A+B LNA2
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x32): //A+B LNA1
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                default:
                        break;
                }
        } else if (config_group == HAL_ANTDIV_CONFIG_GROUP_2) {
                switch ((pdiv_ant_conf->main_lna_conf << 4)
                    | pdiv_ant_conf->alt_lna_conf) {
                case (0x01): //A-B LNA2
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x02): //A-B LNA1
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x03): //A-B A+B
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x10): //LNA2 A-B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > alt_ant_ratio_th)) {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x2;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x12): //LNA2 LNA1
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x13): //LNA2 A+B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > alt_ant_ratio_th)) {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x2;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x20): //LNA1 A-B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > alt_ant_ratio_th)) {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x2;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x21): //LNA1 LNA2
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x23): //LNA1 A+B
                        if ((antcomb->scan == 0)
                            && (alt_ratio > alt_ant_ratio_th)) {
                                pdiv_ant_conf->fast_div_bias = 0x1;
                        } else {
                                pdiv_ant_conf->fast_div_bias = 0x2;
                        }
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x30): //A+B A-B
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x31): //A+B LNA2
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                case (0x32): //A+B LNA1
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        pdiv_ant_conf->main_gaintb   = 0;
                        pdiv_ant_conf->alt_gaintb    = 0;
                        break;
                default:
                        break;
                }
        } else { /* DEFAULT_ANTDIV_CONFIG_GROUP */
                switch ((pdiv_ant_conf->main_lna_conf << 4) | pdiv_ant_conf->alt_lna_conf) {
                case (0x01): //A-B LNA2
                        pdiv_ant_conf->fast_div_bias = 0x3b;
                        break;
                case (0x02): //A-B LNA1
                        pdiv_ant_conf->fast_div_bias = 0x3d;
                        break;
                case (0x03): //A-B A+B
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        break;
                case (0x10): //LNA2 A-B
                        pdiv_ant_conf->fast_div_bias = 0x7;
                        break;
                case (0x12): //LNA2 LNA1
                        pdiv_ant_conf->fast_div_bias = 0x2;
                        break;
                case (0x13): //LNA2 A+B
                        pdiv_ant_conf->fast_div_bias = 0x7;
                        break;
                case (0x20): //LNA1 A-B
                        pdiv_ant_conf->fast_div_bias = 0x6;
                        break;
                case (0x21): //LNA1 LNA2
                        pdiv_ant_conf->fast_div_bias = 0x0;
                        break;
                case (0x23): //LNA1 A+B
                        pdiv_ant_conf->fast_div_bias = 0x6;
                        break;
                case (0x30): //A+B A-B
                        pdiv_ant_conf->fast_div_bias = 0x1;
                        break;
                case (0x31): //A+B LNA2
                        pdiv_ant_conf->fast_div_bias = 0x3b;
                        break;
                case (0x32): //A+B LNA1
                        pdiv_ant_conf->fast_div_bias = 0x3d;
                        break;
                default:
                        break;
                }
        }
}

/*
 * AR9485/AR933x TODO:
 * + Select a ratio based on whether RSSI is low or not; but I need
 *   to figure out what "low_rssi_th" is sourced from.
 * + What's ath_ant_div_comb_alt_check() in the reference driver do?
 * + .. and there's likely a bunch of other things to include in this.
 */

/* Antenna diversity and combining */
void
ath_lna_rx_comb_scan(struct ath_softc *sc, struct ath_rx_status *rs,
    unsigned long ticks, int hz)
{
        HAL_ANT_COMB_CONFIG div_ant_conf;
        struct if_ath_ant_comb_state *antcomb = sc->sc_lna_div;
        int alt_ratio = 0, alt_rssi_avg = 0, main_rssi_avg = 0, curr_alt_set;
        int curr_main_set, curr_bias;
        int main_rssi = rs->rs_rssi_ctl[0];
        int alt_rssi = rs->rs_rssi_ctl[1];
        int rx_ant_conf, main_ant_conf, alt_ant_conf;
        HAL_BOOL short_scan = AH_FALSE;

        rx_ant_conf = (rs->rs_rssi_ctl[2] >> 4) & ATH_ANT_RX_MASK;
        main_ant_conf = (rs->rs_rssi_ctl[2] >> 2) & ATH_ANT_RX_MASK;
        alt_ant_conf = (rs->rs_rssi_ctl[2] >> 0) & ATH_ANT_RX_MASK;

#if 0
        DPRINTF(sc, ATH_DEBUG_DIVERSITY,
            "%s: RSSI %d/%d, conf %x/%x, rxconf %x, LNA: %d; ANT: %d; "
            "FastDiv: %d\n",
            __func__,
            main_rssi,
            alt_rssi,
            main_ant_conf,
            alt_ant_conf,
            rx_ant_conf,
            !!(rs->rs_rssi_ctl[2] & 0x80),
            !!(rs->rs_rssi_ctl[2] & 0x40),
            !!(rs->rs_rssi_ext[2] & 0x40));
#endif

        /*
         * If LNA diversity combining isn't enabled, don't run this.
         */
        if (! sc->sc_dolnadiv)
                return;

        /*
         * XXX this is ugly, but the HAL code attaches the
         * LNA diversity to the TX antenna settings.
         * I don't know why.
         */
        if (sc->sc_txantenna != HAL_ANT_VARIABLE)
                return;

        /* Record packet only when alt_rssi is positive */
        if (main_rssi > 0 && alt_rssi > 0) {
                antcomb->total_pkt_count++;
                antcomb->main_total_rssi += main_rssi;
                antcomb->alt_total_rssi  += alt_rssi;
                if (main_ant_conf == rx_ant_conf)
                        antcomb->main_recv_cnt++;
                else
                        antcomb->alt_recv_cnt++;
        }

        /* Short scan check */
        if (antcomb->scan && antcomb->alt_good) {
                if (ieee80211_time_after(ticks, antcomb->scan_start_time +
                    msecs_to_jiffies(ATH_ANT_DIV_COMB_SHORT_SCAN_INTR)))
                        short_scan = AH_TRUE;
                else
                        if (antcomb->total_pkt_count ==
                            ATH_ANT_DIV_COMB_SHORT_SCAN_PKTCOUNT) {
                                alt_ratio = ((antcomb->alt_recv_cnt * 100) /
                                            antcomb->total_pkt_count);
                                if (alt_ratio < ATH_ANT_DIV_COMB_ALT_ANT_RATIO)
                                        short_scan = AH_TRUE;
                        }
        }

#if 0
        DPRINTF(sc, ATH_DEBUG_DIVERSITY,
            "%s: total pkt=%d, aggr=%d, short_scan=%d\n",
            __func__,
            antcomb->total_pkt_count,
            !! (rs->rs_moreaggr),
            !! (short_scan));
#endif

        if (((antcomb->total_pkt_count < ATH_ANT_DIV_COMB_MAX_PKTCOUNT) ||
            rs->rs_moreaggr) && !short_scan)
                return;

        if (antcomb->total_pkt_count) {
                alt_ratio = ((antcomb->alt_recv_cnt * 100) /
                             antcomb->total_pkt_count);
                main_rssi_avg = (antcomb->main_total_rssi /
                                 antcomb->total_pkt_count);
                alt_rssi_avg = (antcomb->alt_total_rssi /
                                 antcomb->total_pkt_count);
        }

        OS_MEMZERO(&div_ant_conf, sizeof(div_ant_conf));

        ath_hal_div_comb_conf_get(sc->sc_ah, &div_ant_conf);
        curr_alt_set = div_ant_conf.alt_lna_conf;
        curr_main_set = div_ant_conf.main_lna_conf;
        curr_bias = div_ant_conf.fast_div_bias;

        antcomb->count++;

        if (antcomb->count == ATH_ANT_DIV_COMB_MAX_COUNT) {
                if (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO) {
                        ath_lnaconf_alt_good_scan(antcomb, &div_ant_conf,
                                                  main_rssi_avg);
                        antcomb->alt_good = AH_TRUE;
                } else {
                        antcomb->alt_good = AH_FALSE;
                }

                antcomb->count = 0;
                antcomb->scan = AH_TRUE;
                antcomb->scan_not_start = AH_TRUE;
        }

        if (!antcomb->scan) {
                if (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO) {
                        if (curr_alt_set == HAL_ANT_DIV_COMB_LNA2) {
                                /* Switch main and alt LNA */
                                div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                                div_ant_conf.alt_lna_conf  =
                                                HAL_ANT_DIV_COMB_LNA1;
                        } else if (curr_alt_set == HAL_ANT_DIV_COMB_LNA1) {
                                div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                div_ant_conf.alt_lna_conf  =
                                                HAL_ANT_DIV_COMB_LNA2;
                        }

                        goto div_comb_done;
                } else if ((curr_alt_set != HAL_ANT_DIV_COMB_LNA1) &&
                           (curr_alt_set != HAL_ANT_DIV_COMB_LNA2)) {
                        /* Set alt to another LNA */
                        if (curr_main_set == HAL_ANT_DIV_COMB_LNA2)
                                div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                        else if (curr_main_set == HAL_ANT_DIV_COMB_LNA1)
                                div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;

                        goto div_comb_done;
                }

                if ((alt_rssi_avg < (main_rssi_avg +
                    antcomb->lna1_lna2_delta)))
                        goto div_comb_done;
        }

        if (!antcomb->scan_not_start) {
                switch (curr_alt_set) {
                case HAL_ANT_DIV_COMB_LNA2:
                        antcomb->rssi_lna2 = alt_rssi_avg;
                        antcomb->rssi_lna1 = main_rssi_avg;
                        antcomb->scan = AH_TRUE;
                        /* set to A+B */
                        div_ant_conf.main_lna_conf =
                                HAL_ANT_DIV_COMB_LNA1;
                        div_ant_conf.alt_lna_conf  =
                                HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                        break;
                case HAL_ANT_DIV_COMB_LNA1:
                        antcomb->rssi_lna1 = alt_rssi_avg;
                        antcomb->rssi_lna2 = main_rssi_avg;
                        antcomb->scan = AH_TRUE;
                        /* set to A+B */
                        div_ant_conf.main_lna_conf = HAL_ANT_DIV_COMB_LNA2;
                        div_ant_conf.alt_lna_conf  =
                                HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                        break;
                case HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2:
                        antcomb->rssi_add = alt_rssi_avg;
                        antcomb->scan = AH_TRUE;
                        /* set to A-B */
                        div_ant_conf.alt_lna_conf =
                                HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                        break;
                case HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2:
                        antcomb->rssi_sub = alt_rssi_avg;
                        antcomb->scan = AH_FALSE;
                        if (antcomb->rssi_lna2 >
                            (antcomb->rssi_lna1 +
                            ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA)) {
                                /* use LNA2 as main LNA */
                                if ((antcomb->rssi_add > antcomb->rssi_lna1) &&
                                    (antcomb->rssi_add > antcomb->rssi_sub)) {
                                        /* set to A+B */
                                        div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                                        div_ant_conf.alt_lna_conf  =
                                                HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                                } else if (antcomb->rssi_sub >
                                           antcomb->rssi_lna1) {
                                        /* set to A-B */
                                        div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                                        div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                                } else {
                                        /* set to LNA1 */
                                        div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                                        div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                }
                        } else {
                                /* use LNA1 as main LNA */
                                if ((antcomb->rssi_add > antcomb->rssi_lna2) &&
                                    (antcomb->rssi_add > antcomb->rssi_sub)) {
                                        /* set to A+B */
                                        div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                        div_ant_conf.alt_lna_conf  =
                                                HAL_ANT_DIV_COMB_LNA1_PLUS_LNA2;
                                } else if (antcomb->rssi_sub >
                                           antcomb->rssi_lna1) {
                                        /* set to A-B */
                                        div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                        div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1_MINUS_LNA2;
                                } else {
                                        /* set to LNA2 */
                                        div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                        div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                                }
                        }
                        break;
                default:
                        break;
                }
        } else {
                if (!antcomb->alt_good) {
                        antcomb->scan_not_start = AH_FALSE;
                        /* Set alt to another LNA */
                        if (curr_main_set == HAL_ANT_DIV_COMB_LNA2) {
                                div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                                div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                        } else if (curr_main_set == HAL_ANT_DIV_COMB_LNA1) {
                                div_ant_conf.main_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA1;
                                div_ant_conf.alt_lna_conf =
                                                HAL_ANT_DIV_COMB_LNA2;
                        }
                        goto div_comb_done;
                }
        }

        ath_select_ant_div_from_quick_scan(antcomb, &div_ant_conf,
                                           main_rssi_avg, alt_rssi_avg,
                                           alt_ratio);

        antcomb->quick_scan_cnt++;

div_comb_done:
#if 0
        ath_ant_div_conf_fast_divbias(&div_ant_conf);
#endif

        ath_ant_adjust_fast_divbias(antcomb,
            alt_ratio,
            ATH_ANT_DIV_COMB_ALT_ANT_RATIO,
            div_ant_conf.antdiv_configgroup,
            &div_ant_conf);

        ath_hal_div_comb_conf_set(sc->sc_ah, &div_ant_conf);

        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: total_pkt_count=%d\n",
           __func__, antcomb->total_pkt_count);

        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_total_rssi=%d\n",
           __func__, antcomb->main_total_rssi);
        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: alt_total_rssi=%d\n",
           __func__, antcomb->alt_total_rssi);

        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_rssi_avg=%d\n",
           __func__, main_rssi_avg);
        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: alt_alt_rssi_avg=%d\n",
           __func__, alt_rssi_avg);

        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_recv_cnt=%d\n",
           __func__, antcomb->main_recv_cnt);
        DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: alt_recv_cnt=%d\n",
           __func__, antcomb->alt_recv_cnt);

//      if (curr_alt_set != div_ant_conf.alt_lna_conf)
                DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: lna_conf: %x -> %x\n",
                    __func__, curr_alt_set, div_ant_conf.alt_lna_conf);
//      if (curr_main_set != div_ant_conf.main_lna_conf)
                DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: main_lna_conf: %x -> %x\n",
                    __func__, curr_main_set, div_ant_conf.main_lna_conf);
//      if (curr_bias != div_ant_conf.fast_div_bias)
                DPRINTF(sc, ATH_DEBUG_DIVERSITY, "%s: fast_div_bias: %x -> %x\n",
                    __func__, curr_bias, div_ant_conf.fast_div_bias);

        antcomb->scan_start_time = ticks;
        antcomb->total_pkt_count = 0;
        antcomb->main_total_rssi = 0;
        antcomb->alt_total_rssi = 0;
        antcomb->main_recv_cnt = 0;
        antcomb->alt_recv_cnt = 0;
}