/*
 * 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_bindetects.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 removing
   #include <ieee80211_var.h>
 */
#ifdef ATH_SUPPORT_DFS

int
dfs_bin_fixedpattern_check(struct ath_dfs *dfs, struct dfs_filter *rf,
			   uint32_t dur, int ext_chan_flag, int seg_id)
{
	struct dfs_pulseline *pl;
	int i, n, refpri, primargin, numpulses = 0;
	uint64_t start_ts, end_ts, event_ts, prev_event_ts, next_event_ts,
		 window_start, window_end;
	uint32_t index, next_index, deltadur;

	if (dfs->ic->ic_curchan->ic_80p80_both_dfs)
		pl = (seg_id == 0) ? dfs->pulses : dfs->pulses_ext_seg;
	else
		pl = dfs->pulses;

	/* For fixed pattern types, rf->rf_patterntype=1 */
	primargin =
		dfs_get_pri_margin(dfs, ext_chan_flag, (rf->rf_patterntype == 1));

	refpri = (rf->rf_minpri + rf->rf_maxpri) / 2;
	index = pl->pl_lastelem;
	end_ts = pl->pl_elems[index].p_time;
	start_ts = end_ts - (refpri * rf->rf_numpulses);

	DFS_DPRINTK(dfs, ATH_DEBUG_DFS3,
		    "lastelem ts=%llu start_ts=%llu, end_ts=%llu\n",
		    (unsigned long long)pl->pl_elems[index].p_time,
		    (unsigned long long)start_ts, (unsigned long long)end_ts);

	/* find the index of first element in our window of interest */
	for (i = 0; i < pl->pl_numelems; i++) {
		index = (index - 1) & DFS_MAX_PULSE_BUFFER_MASK;
		if (pl->pl_elems[index].p_time >= start_ts)
			continue;
		else {
			index = (index) & DFS_MAX_PULSE_BUFFER_MASK;
			break;
		}
	}
	for (n = 0; n <= rf->rf_numpulses; n++) {
		window_start = (start_ts + (refpri * n)) - (primargin + n);
		window_end = window_start + 2 * (primargin + n);
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
			    "window_start %u window_end %u \n",
			    (uint32_t) window_start, (uint32_t) window_end);
		for (i = 0; i < pl->pl_numelems; i++) {
			prev_event_ts = pl->pl_elems[index].p_time;
			index = (index + 1) & DFS_MAX_PULSE_BUFFER_MASK;
			event_ts = pl->pl_elems[index].p_time;
			next_index = (index + 1) & DFS_MAX_PULSE_BUFFER_MASK;
			next_event_ts = pl->pl_elems[next_index].p_time;
			DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
				    "ts %u \n", (uint32_t) event_ts);
			if ((event_ts <= window_end)
			    && (event_ts >= window_start)) {
				deltadur =
					DFS_DIFF(pl->pl_elems[index].p_dur, dur);
				if ((pl->pl_elems[index].p_dur == 1)
				    || ((dur != 1) && (deltadur <= 2))) {
					numpulses++;
					DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
						    "numpulses %u \n",
						    numpulses);
					break;
				}
			} else if (event_ts > window_end) {
				index = (index - 1) & DFS_MAX_PULSE_BUFFER_MASK;
				break;
			} else if (event_ts == prev_event_ts) {
				if (((next_event_ts - event_ts) > refpri) ||
				    ((next_event_ts - event_ts) == 0)) {
					deltadur =
						DFS_DIFF(pl->pl_elems[index].p_dur,
							 dur);
					if ((pl->pl_elems[index].p_dur == 1)
					    || ((pl->pl_elems[index].p_dur != 1)
						&& (deltadur <= 2))) {
						numpulses++;
						DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
							    "zero PRI: numpulses %u \n",
							    numpulses);
						break;
					}
				}
			}
		}
	}
	if (numpulses >= dfs_get_filter_threshold(dfs, rf, ext_chan_flag)) {
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
			    "%s FOUND filterID=%u numpulses=%d unadj thresh=%d\n",
			    __func__, rf->rf_pulseid, numpulses,
			    rf->rf_threshold);
		return 1;
	} else
		return 0;
}

void
dfs_add_pulse(struct ath_dfs *dfs, struct dfs_filter *rf, struct dfs_event *re,
	      uint32_t deltaT, uint64_t this_ts, int seg_id)
{
	uint32_t index, n, window;
	struct dfs_delayline *dl;

	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;
	/* Circular buffer of size 2^n */
	index = (dl->dl_lastelem + 1) & DFS_MAX_DL_MASK;
	/* if ((dl->dl_numelems+1) == DFS_MAX_DL_SIZE) */
	if ((dl->dl_numelems) == DFS_MAX_DL_SIZE)
		dl->dl_firstelem = (dl->dl_firstelem + 1) & DFS_MAX_DL_MASK;
	else
		dl->dl_numelems++;
	dl->dl_lastelem = index;
	dl->dl_elems[index].de_time = deltaT;
	dl->dl_elems[index].de_ts = this_ts;
	window = deltaT;
	dl->dl_elems[index].de_dur = re->re_dur;
	dl->dl_elems[index].de_rssi = re->re_rssi;

	DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
		    "%s: adding: filter id %d, dur=%d, rssi=%d, ts=%llu\n",
		    __func__,
		    rf->rf_pulseid,
		    re->re_dur, re->re_rssi, (unsigned long long int)this_ts);

	for (n = 0; n < dl->dl_numelems - 1; n++) {
		index = (index - 1) & DFS_MAX_DL_MASK;
		/*
		 * calculate window based on full time stamp instead of deltaT
		 * deltaT (de_time) may result in incorrect window value
		 */
		window = (uint32_t) (this_ts - dl->dl_elems[index].de_ts);

		if (window > rf->rf_filterlen) {
			dl->dl_firstelem = (index + 1) & DFS_MAX_DL_MASK;
			dl->dl_numelems = n + 1;
		}
	}
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
		    "dl firstElem = %d  lastElem = %d\n", dl->dl_firstelem,
		    dl->dl_lastelem);
}

int
dfs_bin_check(struct ath_dfs *dfs, struct dfs_filter *rf, uint32_t deltaT,
	      uint32_t width, int ext_chan_flag, int seg_id)
{
	uint32_t refpri, refdur, searchpri, deltapri, deltapri_2, deltapri_3,
		 averagerefpri;
	uint32_t n, i, primargin, durmargin, highscore, highscoreindex;
	int score[DFS_MAX_DL_SIZE], delayindex, dindex, found = 0;
	struct dfs_delayline *dl;
	uint32_t scoreindex, lowpriindex = 0, lowpri = 0xffff;
	int numpulses = 0;
	int lowprichk = 3, pri_match = 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)) {
		return 0;
	}
	if (deltaT > rf->rf_filterlen)
		return 0;

	primargin =
		dfs_get_pri_margin(dfs, ext_chan_flag, (rf->rf_patterntype == 1));

	if (rf->rf_maxdur < 10) {
		durmargin = 4;
	} else {
		durmargin = 6;
	}

	if (rf->rf_patterntype == 1) {
		found =
			dfs_bin_fixedpattern_check(dfs, rf, width,
							ext_chan_flag, seg_id);
		if (found) {
			dl->dl_numelems = 0;
		}
		return found;
	}

	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) {   /* use only valid PRI range for high score */
			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);
				deltapri_2 = DFS_DIFF(searchpri, 2 * refpri);
				deltapri_3 = DFS_DIFF(searchpri, 3 * refpri);
				if (rf->rf_ignore_pri_window == 2) {
					pri_match = ((deltapri < primargin)
						     || (deltapri_2 < primargin)
						     || (deltapri_3 <
							 primargin));
				} else {
					pri_match = (deltapri < primargin);
				}

				if (pri_match)
					score[n]++;
			}
		} else {
			score[n] = 0;
		}
		if (score[n] > rf->rf_threshold) {
			/* we got the most possible candidate,
			 * no need to continue further */
			break;
		}
	}
	/* find out the high scorer */
	highscore = 0;
	highscoreindex = 0;
	for (n = 0; n < dl->dl_numelems; n++) {
		if (score[n] > highscore) {
			highscore = score[n];
			highscoreindex = n;
		} else if (score[n] == highscore) {
			/*more than one pri has highscore take the least pri */
			delayindex =
				(dl->dl_firstelem +
				 highscoreindex) & DFS_MAX_DL_MASK;
			dindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
			if (dl->dl_elems[dindex].de_time <=
			    dl->dl_elems[delayindex].de_time) {
				highscoreindex = n;
			}
		}
	}
	/* find the average pri of pulses around the pri of highscore or
	 * the pulses around the lowest pri */
	if (rf->rf_ignore_pri_window > 0) {
		lowprichk = (rf->rf_threshold >> 1) + 1;
	} else {
		lowprichk = 3;
	}

	if (highscore < lowprichk) {
		scoreindex = lowpriindex;
	} else {
		scoreindex = highscoreindex;
	}
	/* We got the possible pri, save its parameters as reference */
	delayindex = (dl->dl_firstelem + scoreindex) & DFS_MAX_DL_MASK;
	refdur = dl->dl_elems[delayindex].de_dur;
	refpri = dl->dl_elems[delayindex].de_time;
	averagerefpri = 0;

	if (rf->rf_fixed_pri_radar_pulse) {
		refpri = (rf->rf_minpri + rf->rf_maxpri) / 2;
	}

	numpulses = dfs_bin_pri_check(dfs, rf, dl, score[scoreindex], refpri,
				      refdur, ext_chan_flag, refpri);
	if (numpulses >= dfs_get_filter_threshold(dfs, rf, ext_chan_flag)) {
		found = 1;
		DFS_DPRINTK(dfs, ATH_DEBUG_DFS1,
			    "ext_flag=%d MATCH filter=%u numpulses=%u thresh=%u refdur=%d refpri=%d primargin=%d\n",
			    ext_chan_flag, rf->rf_pulseid, numpulses,
			    rf->rf_threshold, refdur, refpri, primargin);

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

		dfs_print_filter(dfs, rf);
	}
	return found;
}

int
dfs_bin_pri_check(struct ath_dfs *dfs, struct dfs_filter *rf,
		  struct dfs_delayline *dl, uint32_t score, uint32_t refpri,
		  uint32_t refdur, int ext_chan_flag, int fundamentalpri)
{
	uint32_t searchpri, searchdur, searchrssi, deltapri = 0, deltapri1 =
		0, deltapri2 = 0, deltadur, averagerefpri = 0, MatchCount = 0;
	uint32_t delta_ts_variance, delta_time_stamps, prev_good_timestamp = 0;
	int delayindex, dindex;
	uint32_t i, j = 0, primargin, durmargin, highscore =
		score, highscoreindex = 0;
	int numpulses = 1;      /* first pulse in the burst is most likely being filtered out based on maxfilterlen */
	int priscorechk = 1, numpulsetochk = 2, primatch = 0;

	/* Use the adjusted PRI margin to reduce false alarms */
	/* For non fixed pattern types, rf->rf_patterntype=0 */
	primargin =
		dfs_get_pri_margin(dfs, ext_chan_flag, (rf->rf_patterntype == 1));

	if ((refpri > rf->rf_maxpri) || (refpri < rf->rf_minpri)) {
		numpulses = 0;
		return numpulses;
	}

	if (rf->rf_maxdur < 10) {
		durmargin = 4;
	} else {
		durmargin = 6;
	}

	if ((!rf->rf_fixed_pri_radar_pulse)) {
		if (rf->rf_ignore_pri_window == 1) {
			priscorechk = (rf->rf_threshold >> 1);
		} else {
			priscorechk = 1;
		}

		MatchCount = 0;
		if (score > priscorechk) {
			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) {
					averagerefpri += searchpri;
					MatchCount++;
				}
			}
			if (rf->rf_patterntype != 2) {
				if (MatchCount > 0)
					refpri = (averagerefpri / MatchCount);  /* average */
			} else {
				refpri = (averagerefpri / score);
			}
		}
	}
	/* Note: Following primultiple calculation should be done once per filter
	 * during initialization stage (dfs_attach) and stored in its array
	 * atleast for fixed frequency types like FCC Bin1 to save some CPU cycles.
	 * multiplication, devide operators in the following code are left as it is
	 * for readability hoping the complier will use left/right shifts wherever possible
	 */
	DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
		    "refpri = %d high score = %d index = %d numpulses = %d\n",
		    refpri, highscore, highscoreindex, numpulses);
	/* Count the other delay elements that have  pri and dur with in the
	 * acceptable range from the reference one */
	for (i = 0; i < dl->dl_numelems; i++) {
		delayindex = (dl->dl_firstelem + i) & DFS_MAX_DL_MASK;
		searchpri = dl->dl_elems[delayindex].de_time;
		if (searchpri == 0) {
			/* This events PRI is zero, take it as a
			 * valid pulse but decrement next event's PRI by refpri
			 */
			dindex = (delayindex + 1) & DFS_MAX_DL_MASK;
			dl->dl_elems[dindex].de_time -= refpri;
			searchpri = refpri;
		}
		searchdur = dl->dl_elems[delayindex].de_dur;
		searchrssi = dl->dl_elems[delayindex].de_rssi;
		deltadur = DFS_DIFF(searchdur, refdur);
		deltapri = DFS_DIFF(searchpri, refpri);

		/* deltapri3 = DFS_DIFF(searchpri, 3 * refpri); */
		primatch = 0;

		if ((rf->rf_ignore_pri_window > 0) && (rf->rf_patterntype != 2)) {
			for (j = 0; j < rf->rf_numpulses; j++) {
				deltapri1 =
					DFS_DIFF(searchpri, (j + 1) * refpri);
				if (deltapri1 < (2 * primargin)) {
					primatch = 1;
					break;
				}
			}
		} else {
			if ((deltapri1 < primargin) || (deltapri2 < primargin)) {
				primatch = 1;
			}
		}

		if (primatch && (deltadur < durmargin)) {
			if ((numpulses == 1)) {
				numpulses++;
			} else {
				delta_time_stamps =
					dl->dl_elems[delayindex].de_ts -
					prev_good_timestamp;
				if ((rf->rf_ignore_pri_window > 0)) {
					numpulsetochk = rf->rf_numpulses;

					if ((rf->rf_patterntype == 2)
					    && (fundamentalpri <
						refpri + 100)) {
						numpulsetochk = 4;
					}
				} else {
					numpulsetochk = 4;
				}
				for (j = 0; j < numpulsetochk; j++) {
					delta_ts_variance =
						DFS_DIFF(delta_time_stamps,
							 ((j +
							   1) * fundamentalpri));
					if (delta_ts_variance <
					    (2 * (j + 1) * primargin)) {
						numpulses++;
						if (rf->rf_ignore_pri_window >
						    0) {
							break;
						}
					}
				}
			}
			prev_good_timestamp = dl->dl_elems[delayindex].de_ts;

			DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
				    "rf->minpri=%d rf->maxpri=%d searchpri = %d index = %d numpulses = %d deltapri=%d j=%d\n",
				    rf->rf_minpri, rf->rf_maxpri, searchpri, i,
				    numpulses, deltapri, j);
		}

	}
	return numpulses;
}
#endif /* ATH_SUPPORT_DFS */
