/*
 * Copyright (c) 2002-2014 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

/*===========================================================================

                     dfs_staggered.c

   OVERVIEW:

   Source code borrowed from QCA_MAIN DFS module

   DEPENDENCIES:

   Are listed for each API below.

   ===========================================================================*/

/*===========================================================================

                      EDIT HISTORY FOR FILE

   This section contains comments describing changes made to the module.
   Notice that changes are listed in reverse chronological order.

   when        who     what, where, why
   ----------    ---    --------------------------------------------------------

   ===========================================================================*/

#include "dfs.h"
/* TO DO DFS
   #include <ieee80211_var.h>
 */
#ifdef ATH_SUPPORT_DFS

static int is_pri_multiple(uint32_t sample_pri, uint32_t refpri)
{
#define MAX_ALLOWED_MISSED 3
	int i;

	if (sample_pri < refpri || (!refpri))
		return 0;

	for (i = 1; i <= MAX_ALLOWED_MISSED; i++) {
		if ((sample_pri % (i * refpri) <= 5)) {
			/* printk("sample_pri=%d is a multiple of refpri=%d\n", sample_pri, refpri); */
			return 1;
		}
	}
	return 0;
#undef MAX_ALLOWED_MISSED
}

static int is_unique_pri(uint32_t highestpri, uint32_t midpri,
			 uint32_t lowestpri, uint32_t refpri)
{
#define DFS_STAGGERED_PRI_MARGIN_MIN  20
#define DFS_STAGGERED_PRI_MARGIN_MAX  400
	if ((DFS_DIFF(lowestpri, refpri) >= DFS_STAGGERED_PRI_MARGIN_MIN) &&
	    (DFS_DIFF(midpri, refpri) >= DFS_STAGGERED_PRI_MARGIN_MIN) &&
	    (DFS_DIFF(highestpri, refpri) >= DFS_STAGGERED_PRI_MARGIN_MIN)) {
		return 1;
	} else {
		if ((is_pri_multiple(refpri, highestpri))
		    || (is_pri_multiple(refpri, lowestpri))
		    || (is_pri_multiple(refpri, midpri)))
			return 0;
	}
	return 0;
#undef DFS_STAGGERED_PRI_MARGIN_MIN
#undef DFS_STAGGERED_PRI_MARGIN_MAX
}

int dfs_staggered_check(struct ath_dfs *dfs, struct dfs_filter *rf,
			uint32_t deltaT, uint32_t width, int seg_id)
{
	uint32_t refpri, refdur, searchpri = 0, deltapri;       /* , averagerefpri; */
	uint32_t n, i, primargin, durmargin;
	int score[DFS_MAX_DL_SIZE], delayindex, dindex, found = 0;
	struct dfs_delayline *dl;
	uint32_t scoreindex, lowpriindex = 0, lowpri = 0xffff;
#if 0
	int numpulses = 0;
#endif
	int higherthan, lowerthan, numscores;
	int numpulseshigh = 0, numpulsesmid = 0, numpulsestemp = 0;
	uint32_t lowestscore = 0, lowestscoreindex = 0, lowestpri = 0;
	uint32_t midscore = 0, midscoreindex = 0, midpri = 0;
	uint32_t highestscore = 0, highestscoreindex = 0, highestpri = 0;

	if (dfs->ic->ic_curchan->ic_80p80_both_dfs)
		dl = (seg_id == 0) ? &rf->rf_dl : &rf->rf_dl_ext_seg;
	else
		dl = &rf->rf_dl;

	if (dl->dl_numelems < (rf->rf_threshold - 1)) {
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
			    "numelems %d < threshold for filter %d\n",
			    dl->dl_numelems, rf->rf_pulseid);
		return 0;
	}
	if (deltaT > rf->rf_filterlen) {
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
			    "numelems %d < threshold for filter %d\n",
			    dl->dl_numelems, rf->rf_pulseid);
		return 0;
	}
	primargin = 6;
	if (rf->rf_maxdur < 10) {
		durmargin = 4;
	} else {
		durmargin = 6;
	}

	OS_MEMZERO(score, sizeof(int) * DFS_MAX_DL_SIZE);
	/* find out the lowest pri */
	for (n = 0; n < dl->dl_numelems; n++) {
		delayindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
		refpri = dl->dl_elems[delayindex].de_time;
		if (refpri == 0)
			continue;
		else if (refpri < lowpri) {
			lowpri = dl->dl_elems[delayindex].de_time;
			lowpriindex = n;
		}
	}
	/* find out the each delay element's pri score */
	for (n = 0; n < dl->dl_numelems; n++) {
		delayindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
		refpri = dl->dl_elems[delayindex].de_time;
		if (refpri == 0) {
			continue;
		}

		if ((refpri > rf->rf_maxpri) || (refpri < rf->rf_minpri)) {
			score[n] = 0;
			continue;
		}

		for (i = 0; i < dl->dl_numelems; i++) {
			dindex = (dl->dl_firstelem + i) & DFS_MAX_DL_MASK;
			searchpri = dl->dl_elems[dindex].de_time;
			deltapri = DFS_DIFF(searchpri, refpri);
			if (deltapri < primargin)
				score[n]++;
		}
	}
	for (n = 0; n < dl->dl_numelems; n++) {
		delayindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
		refdur = dl->dl_elems[delayindex].de_time;
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS2, "score[%d]=%d pri=%d\n", n,
			    score[n], refdur);
	}

	/* find out the 2 or 3 highest scorers */
	scoreindex = 0;
	highestscore = 0;
	highestscoreindex = 0;
	highestpri = 0;
	numscores = 0;
	lowestscore = 0;

	for (n = 0; n < dl->dl_numelems; n++) {
		higherthan = 0;
		lowerthan = 0;
		delayindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
		refpri = dl->dl_elems[delayindex].de_time;

		if ((score[n] >= highestscore) &&
		    (is_unique_pri(highestpri, midpri, lowestpri, refpri))) {
			lowestscore = midscore;
			lowestpri = midpri;
			lowestscoreindex = midscoreindex;
			midscore = highestscore;
			midpri = highestpri;
			midscoreindex = highestscoreindex;
			highestscore = score[n];
			highestpri = refpri;
			highestscoreindex = n;
		} else {
			if ((score[n] >= midscore) &&
			    (is_unique_pri
				     (highestpri, midpri, lowestpri, refpri))) {
				lowestscore = midscore;
				lowestpri = midpri;
				lowestscoreindex = midscoreindex;
				midscore = score[n];
				midpri = refpri;
				midscoreindex = n;
			} else if ((score[n] >= lowestscore) &&
				   (is_unique_pri
					    (highestpri, midpri, lowestpri, refpri))) {
				lowestscore = score[n];
				lowestpri = refpri;
				lowestscoreindex = n;
			}
		}

	}

	if (midscore == 0) {
		/* This means we have only 1 pulse type. It can not be staggered! */
		return 0;
	}

	DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
		    "FINAL highestscore=%d highestscoreindex=%d highestpri=%d\n",
		    highestscore, highestscoreindex, highestpri);
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
		    "FINAL lowestscore=%d lowestscoreindex=%d lowpri=%d\n",
		    lowestscore, lowestscoreindex, lowestpri);
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
		    "FINAL midscore=%d midscoreindex=%d midpri=%d\n",
		    midscore, midscoreindex, midpri);

	delayindex = (dl->dl_firstelem + highestscoreindex) & DFS_MAX_DL_MASK;
	refdur = dl->dl_elems[delayindex].de_dur;
	refpri = dl->dl_elems[delayindex].de_time;
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
		    "highscoreindex=%d refdur=%d refpri=%d\n",
		    highestscoreindex, refdur, refpri);

	numpulsestemp =
		dfs_bin_pri_check(dfs, rf, dl, highestscore, refpri, refdur, 0,
				  highestpri);
	numpulseshigh = numpulsestemp;
	numpulsestemp =
		dfs_bin_pri_check(dfs, rf, dl, highestscore, refpri, refdur, 0,
				  highestpri + midpri);
	if (numpulsestemp > numpulseshigh) {
		numpulseshigh = numpulsestemp;
	}
	numpulsestemp =
		dfs_bin_pri_check(dfs, rf, dl, highestscore, refpri, refdur, 0,
				  highestpri + midpri + lowestpri);
	if (numpulsestemp > numpulseshigh) {
		numpulseshigh = numpulsestemp;
	}

	delayindex = (dl->dl_firstelem + midscoreindex) & DFS_MAX_DL_MASK;
	refdur = dl->dl_elems[delayindex].de_dur;
	refpri = dl->dl_elems[delayindex].de_time;
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
		    "midscoreindex=%d refdur=%d refpri=%d\n",
		    midscoreindex, refdur, refpri);

	/* numpulsesmid = dfs_bin_pri_check(dfs, rf, dl, midscore, refpri, refdur,0, 1); */
	numpulsestemp =
		dfs_bin_pri_check(dfs, rf, dl, midscore, refpri, refdur, 0, midpri);
	numpulsesmid = numpulsestemp;
	numpulsestemp =
		dfs_bin_pri_check(dfs, rf, dl, midscore, refpri, refdur, 0,
				  highestpri + midpri);
	if (numpulsestemp > numpulsesmid) {
		numpulsesmid = numpulsestemp;
	}
	numpulsestemp =
		dfs_bin_pri_check(dfs, rf, dl, midscore, refpri, refdur, 0,
				  highestpri + midpri + lowestpri);
	if (numpulsestemp > numpulsesmid) {
		numpulsesmid = numpulsestemp;
	}

	/*delayindex = (dl->dl_firstelem + lowestscoreindex) & DFS_MAX_DL_MASK;
	   refdur = dl->dl_elems[delayindex].de_dur;
	   refpri = dl->dl_elems[delayindex].de_time;
	   DFS_DPRINTK(ic, ATH_DEBUG_DFS1, "lowestscoreindex=%d refdur=%d refpri=%d\n", lowestscoreindex, refdur, refpri);

	   numpulseslow = dfs_bin_pri_check(dfs, rf, dl, lowestscore, refpri, refdur,0, 1);
	 */
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
		    "numpulseshigh=%d, numpulsesmid=%d\n",
		    numpulseshigh, numpulsesmid);
/*            printf("numpulseshigh=%d, numpulsesmid=%d, numpulseslow %d\n",numpulseshigh, numpulsesmid, numpulseslow); */

	if ((numpulseshigh >= rf->rf_threshold)
	    && (numpulsesmid >= rf->rf_threshold)) {
		/*if (numpulses >= rf->rf_threshold) { */
		found = 1;
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
			    "MATCH filter=%u numpulseshigh=%u numpulsesmid= %u thresh=%u\n",
			    rf->rf_pulseid, numpulseshigh, numpulsesmid,
			    rf->rf_threshold);
	}
	return found;
}

#endif /* ATH_SUPPORT_DFS */
