/******************************************************************************
 *
 * Copyright(c) 2020 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#include "phl_headers.h"
#include "phl_scan_instance.h"
#include "phl_scan.h"
#include "phl_regulation.h"

enum {
	ACTIVE_PERIOD_MIN = 40,
	ACTIVE_PERIOD_DEFAULT = 60,
	ACTIVE_PERIOD_MAX = 80
};

enum {
	PASSIVE_PERIOD_MIN = 50,
	PASSIVE_PERIOD_DEFAULT = 80,
	PASSIVE_PERIOD_MAX = 110
};

struct rtw_pickup_channel {
	enum band_type band;
	u8 channel;
	u8 property;
	u8 picked;
};

struct rtw_pickup_chplan_group {
	u32 cnt;
	struct rtw_pickup_channel ch[MAX_CH_NUM_GROUP];
};

static void _set_inst_ch(enum period_strategy strategy,
			struct instance_channel *dest,
			enum band_type band,
			u8 channel, u8 property)
{
	u8 max_t[2] = {PASSIVE_PERIOD_MAX, ACTIVE_PERIOD_MAX};
	u8 min_t[2] = {PASSIVE_PERIOD_MIN, ACTIVE_PERIOD_MIN};
	u8 def_t[2] = {PASSIVE_PERIOD_DEFAULT, ACTIVE_PERIOD_DEFAULT};

	dest->band = band;
	dest->channel = channel;
	dest->property = property;
	dest->mode = NORMAL_SCAN_MODE;
	dest->bw = CHANNEL_WIDTH_20;
	dest->offset = CHAN_OFFSET_NO_EXT;

	/* active or passive */
	if ((dest->property & CH_PASSIVE) ||
		(dest->property & CH_DFS))
		dest->active = 0;
	else
		dest->active = 1;

	if (strategy & PERIOD_ALL_MAX)
		dest->period = max_t[dest->active];
	else if (strategy & PERIOD_ALL_MIN)
		dest->period = min_t[dest->active];
	else {
		if ((strategy & PERIOD_MIN_DFS) &&
			(dest->property & CH_DFS))
			dest->period = min_t[dest->active];
		else
			dest->period = def_t[dest->active];
	}

	PHL_INFO("[REGU], pick channel %d, active=%d, period=%d\n",
			dest->channel, dest->active, dest->period);
}

static void _pick_active_channels(struct rtw_pickup_chplan_group *group,
					struct instance_strategy *strategy,
					struct instance *inst)
{
	struct instance_channel *dest = NULL;
	struct rtw_pickup_channel *src = NULL;
	u32 i = 0;

	dest = &inst->ch[inst->cnt];

	for (i = 0; i < group->cnt; i++) {
		src = &group->ch[i];

		if (src->picked)
			continue;

		if (!(src->property & CH_PASSIVE) &&
			!(src->property & CH_DFS) ) {
			_set_inst_ch(strategy->period, dest,
					src->band,
					src->channel,
					src->property);
			inst->cnt++;
			dest++;
			src->picked = 1;
		}
	}
}

static void _pick_the_rest_channels(struct rtw_pickup_chplan_group *group,
					struct instance_strategy *strategy,
					struct instance *inst)
{
	struct instance_channel *dest = NULL;
	struct rtw_pickup_channel *src = NULL;
	u32 i = 0;

	dest = &inst->ch[inst->cnt];

	for (i = 0; i < group->cnt; i++) {
		src = &group->ch[i];

		if (!src->picked) {
			_set_inst_ch(strategy->period, dest,
					src->band,
					src->channel,
					src->property);
			inst->cnt++;
			dest++;
			src->picked = 1;
		}
	}
}

static void _pick_5ghz_channels(struct rtw_pickup_chplan_group *group,
					struct instance_strategy *strategy,
					struct instance *inst)
{
	u8 order = strategy->order;
	u8 i = 0;

	if (order & ORDER_ACTIVE_PRIOR) {
		for (i = FREQ_GROUP_5GHZ_BAND1;
			i <= FREQ_GROUP_5GHZ_BAND4; i++) {
			_pick_active_channels(&group[i], strategy, inst);
		}
	}

	for (i = FREQ_GROUP_5GHZ_BAND1;
		i <= FREQ_GROUP_5GHZ_BAND4; i++) {
		_pick_the_rest_channels(&group[i], strategy, inst);
	}
}

static void _pick_2ghz_channels(struct rtw_pickup_chplan_group *group,
					struct instance_strategy *strategy,
					struct instance *inst)
{
	u8 order = strategy->order;

	if (order & ORDER_ACTIVE_PRIOR)
		_pick_active_channels(&group[FREQ_GROUP_2GHZ],
						strategy, inst);

	_pick_the_rest_channels(&group[FREQ_GROUP_2GHZ],
					strategy, inst);
}

static void _pick_6ghz_channels(struct rtw_pickup_chplan_group *group,
					struct instance_strategy *strategy,
					struct instance *inst)
{
	_pick_the_rest_channels(&group[FREQ_GROUP_6GHZ_PSC],
					strategy, inst);
}

static void _generate_instance(
		struct rtw_pickup_chplan_group *group,
		struct instance_strategy *strategy,
		struct instance *inst)
{
	u8 order = strategy->order;

	inst->cnt = 0;
	if (order & ORDER_5GHZ_PRIOR) {
		_pick_5ghz_channels(group, strategy, inst);
		_pick_2ghz_channels(group, strategy, inst);
	} else {
		_pick_2ghz_channels(group, strategy, inst);
		_pick_5ghz_channels(group, strategy, inst);
	}

	_pick_6ghz_channels(group, strategy, inst);
}

static void _select_channels_by_group(struct instance_strategy *strategy,
					struct rtw_regulation_chplan *plan,
					struct rtw_pickup_chplan_group *group)
{
	u8 skip = strategy->skip;
	u8 chnl = 0, property = 0, gpidx = 0, keep = 0;
	enum band_type band = BAND_ON_24G;
	u32 i = 0;

	for (i = 0; i < plan->cnt; i++) {
		band = plan->ch[i].band;
		chnl = plan->ch[i].channel;
		property = plan->ch[i].property;
		keep = 0;

		/* skip passive channels */
		if ((skip & SKIP_PASSIVE) && (property & CH_PASSIVE))
			continue;

		/* skip DFS channels */
		if ((skip & SKIP_DFS) && (property & CH_DFS))
			continue;

		if ((BAND_2GHZ(band)) && !(skip & SKIP_2GHZ)) {
			gpidx = FREQ_GROUP_2GHZ;
			keep = 1;
		} else if ((BAND_5GHZ(band)) && !(skip & SKIP_5GHZ)) {
			if (CH_5GHZ_BAND1(chnl))
				gpidx = FREQ_GROUP_5GHZ_BAND1;
			else if (CH_5GHZ_BAND2(chnl))
				gpidx = FREQ_GROUP_5GHZ_BAND2;
			else if (CH_5GHZ_BAND3(chnl))
				gpidx = FREQ_GROUP_5GHZ_BAND3;
			else if (CH_5GHZ_BAND4(chnl))
				gpidx = FREQ_GROUP_5GHZ_BAND4;
			else
				continue;
			keep = 1;
		} else if ((BAND_6GHZ(band)) && !(skip & SKIP_6GHZ)) {
			gpidx = FREQ_GROUP_6GHZ_PSC;
			keep = 1;
		}

		if (keep) {
			group[gpidx].ch[group[gpidx].cnt].band = band;
			group[gpidx].ch[group[gpidx].cnt].channel = chnl;
			group[gpidx].ch[group[gpidx].cnt].property = property;
			group[gpidx].ch[group[gpidx].cnt].picked = 0;
			group[gpidx].cnt++;
			PHL_INFO("[REGU], keep group-%d channel %d, cnt=%d\n",
					gpidx, chnl, group[gpidx].cnt);
		}
	}
}

bool rtw_phl_generate_scan_instance(struct instance_strategy *strategy,
					struct rtw_regulation_chplan *chplan,
					struct instance *inst)
{
	struct rtw_pickup_chplan_group group[FREQ_GROUP_MAX] = {0};
	u32 i = 0;

	if (!strategy || !inst || !chplan)
		return false;

	PHL_INFO("[REGU], Generate Scan Instance, strategy [skip=0x%x, order=0x%x, period=0x%x] \n",
			strategy->skip, strategy->order, strategy->period);
	PHL_INFO("[REGU], Channel Plan Source : \n");

	for (i = 0; i < chplan->cnt; i++) {
		PHL_INFO("[REGU], %d. band=%d, ch=%d, dfs=%d, passive=%d (property=0x%x)\n",
			i + 1, chplan->ch[i].band, chplan->ch[i].channel,
			((chplan->ch[i].property & CH_DFS) ? 1 : 0),
			((chplan->ch[i].property & CH_PASSIVE) ? 1 : 0),
			chplan->ch[i].property);
	}

	/* step1 : remove "skip channels" and select channels into groups */
	_select_channels_by_group(strategy, chplan, group);


	/* step2 : generate instance by strategy */
	_generate_instance(group, strategy, inst);

	PHL_INFO("[REGU], Output Scan Instance : \n");
	for (i = 0; i < inst->cnt; i++) {
		PHL_INFO("[REGU], %d. band=%d, ch=%d, active=%d, period=%d \n",
				i + 1, inst->ch[i].band,
				inst->ch[i].channel,
				inst->ch[i].active,
				inst->ch[i].period);
	}

	return true;
}

bool rtw_phl_scan_instance_insert_ch(void *phl, struct instance *inst,
					enum band_type band, u8 channel,
						u8 strategy_period)
{
	struct rtw_regulation_channel ch = {0};
	struct instance_channel *dest = NULL;

	if (!phl || !inst)
		return false;

	if (inst->cnt >= MAX_SCAN_INSTANCE)
		return false;

	if (rtw_phl_regulation_query_ch(phl, band, channel, &ch)) {
		dest = &inst->ch[inst->cnt];
		_set_inst_ch(strategy_period, dest,
				ch.band, ch.channel, ch.property);
		inst->cnt++;
		return true;
	}

	return false;
}



