#include <machine/rtems-bsd-kernel-space.h>

/*-
 * Copyright (c) 2016 Andriy Voskoboinyk <avos@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.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, 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 DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <rtems/bsd/local/opt_wlan.h>

#include <sys/param.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/taskqueue.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/linker.h>

#include <net/if.h>
#include <net/ethernet.h>
#include <net/if_media.h>

#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_radiotap.h>

#include <dev/rtwn/if_rtwnvar.h>
#include <dev/rtwn/if_rtwn_debug.h>

#include <dev/rtwn/usb/rtwn_usb_var.h>

#include <dev/rtwn/rtl8812a/r12a_var.h>

#include <dev/rtwn/rtl8821a/usb/r21au.h>
#include <dev/rtwn/rtl8821a/usb/r21au_reg.h>


#define R21AU_RADAR_CHECK_PERIOD	(2 * hz)

static void
r21au_dfs_radar_disable(struct rtwn_softc *sc)
{
	rtwn_bb_setbits(sc, 0x924, 0x00008000, 0);
}

static int
r21au_dfs_radar_is_enabled(struct rtwn_softc *sc)
{
	return !!(rtwn_bb_read(sc, 0x924) & 0x00008000);
}

static int
r21au_dfs_radar_reset(struct rtwn_softc *sc)
{
	int error;

	error = rtwn_bb_setbits(sc, 0x924, 0x00008000, 0);
	if (error != 0)
		return (error);

	return (rtwn_bb_setbits(sc, 0x924, 0, 0x00008000));
}

static int
r21au_dfs_radar_enable(struct rtwn_softc *sc)
{
#define RTWN_CHK(res) do {	\
	if (res != 0)		\
		return (EIO);	\
} while(0)

	RTWN_ASSERT_LOCKED(sc);

	RTWN_CHK(rtwn_bb_setbits(sc, 0x814, 0x3fffffff, 0x04cc4d10));
	RTWN_CHK(rtwn_bb_setbits(sc, R12A_BW_INDICATION, 0xff, 0x06));
	RTWN_CHK(rtwn_bb_write(sc, 0x918, 0x1c16ecdf));
	RTWN_CHK(rtwn_bb_write(sc, 0x924, 0x0152a400));
	RTWN_CHK(rtwn_bb_write(sc, 0x91c, 0x0fa21a20));
	RTWN_CHK(rtwn_bb_write(sc, 0x920, 0xe0f57204));

	return (r21au_dfs_radar_reset(sc));

#undef RTWN_CHK
}

static int
r21au_dfs_radar_is_detected(struct rtwn_softc *sc)
{
	return !!(rtwn_bb_read(sc, 0xf98) & 0x00020000);
}

void
r21au_chan_check(void *arg, int npending __unused)
{
	struct rtwn_softc *sc = arg;
	struct r12a_softc *rs = sc->sc_priv;
	struct ieee80211com *ic = &sc->sc_ic;

	RTWN_LOCK(sc);
#ifdef DIAGNOSTIC
	RTWN_DPRINTF(sc, RTWN_DEBUG_STATE,
	    "%s: periodical radar detection task\n", __func__);
#endif

	if (!r21au_dfs_radar_is_enabled(sc)) {
		if (rs->rs_flags & R12A_RADAR_ENABLED) {
			/* should not happen */
			device_printf(sc->sc_dev,
			    "%s: radar detection was turned off "
			    "unexpectedly, resetting...\n", __func__);

			/* XXX something more appropriate? */
			ieee80211_restart_all(ic);
		}
		RTWN_UNLOCK(sc);
		return;
	}

	if (r21au_dfs_radar_is_detected(sc)) {
		r21au_dfs_radar_reset(sc);

		RTWN_DPRINTF(sc, RTWN_DEBUG_RADAR, "%s: got radar event\n",
		    __func__);

		RTWN_UNLOCK(sc);
		IEEE80211_LOCK(ic);

		ieee80211_dfs_notify_radar(ic, ic->ic_curchan);

		IEEE80211_UNLOCK(ic);
		RTWN_LOCK(sc);
	}

	if (rs->rs_flags & R12A_RADAR_ENABLED) {
		taskqueue_enqueue_timeout(taskqueue_thread,
		    &rs->rs_chan_check, R21AU_RADAR_CHECK_PERIOD);
	}
	RTWN_UNLOCK(sc);
}

int
r21au_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
	struct ieee80211com *ic = vap->iv_ic;
	struct rtwn_softc *sc = ic->ic_softc;
	struct rtwn_vap *rvp = RTWN_VAP(vap);
	struct r12a_softc *rs = sc->sc_priv;
	int error;

	KASSERT(rvp->id == 0 || rvp->id == 1,
	    ("%s: unexpected vap id %d\n", __func__, rvp->id));

	IEEE80211_UNLOCK(ic);
	RTWN_LOCK(sc);

	error = 0;
	if (nstate == IEEE80211_S_CAC &&
	    !(rs->rs_flags & R12A_RADAR_ENABLED)) {
		error = r21au_dfs_radar_enable(sc);
		if (error != 0) {
			device_printf(sc->sc_dev,
			    "%s: cannot enable radar detection\n", __func__);
			goto fail;
		}
		rs->rs_flags |= R12A_RADAR_ENABLED;

		RTWN_DPRINTF(sc, RTWN_DEBUG_RADAR,
		    "%s: radar detection was enabled\n", __func__);

		taskqueue_enqueue_timeout(taskqueue_thread,
		    &rs->rs_chan_check, R21AU_RADAR_CHECK_PERIOD);
	}

	if ((nstate < IEEE80211_S_CAC || nstate == IEEE80211_S_CSA) &&
	    (rs->rs_flags & R12A_RADAR_ENABLED) &&
	    (sc->vaps[!rvp->id] == NULL ||
	    sc->vaps[!rvp->id]->vap.iv_state < IEEE80211_S_CAC ||
	    sc->vaps[!rvp->id]->vap.iv_state == IEEE80211_S_CSA)) {
		taskqueue_cancel_timeout(taskqueue_thread, &rs->rs_chan_check,
		    NULL);

		rs->rs_flags &= ~R12A_RADAR_ENABLED;
		r21au_dfs_radar_disable(sc);

		RTWN_DPRINTF(sc, RTWN_DEBUG_RADAR,
		    "%s: radar detection was disabled\n", __func__);
	}

fail:
	RTWN_UNLOCK(sc);
	IEEE80211_LOCK(ic);

	if (error != 0)
		return (error);

	return (rs->rs_newstate[rvp->id](vap, nstate, arg));
}

void
r21au_scan_start(struct ieee80211com *ic)
{
	struct rtwn_softc *sc = ic->ic_softc;
	struct r12a_softc *rs = sc->sc_priv;

	RTWN_LOCK(sc);
	if (rs->rs_flags & R12A_RADAR_ENABLED) {
		RTWN_UNLOCK(sc);
		while (taskqueue_cancel_timeout(taskqueue_thread,
		    &rs->rs_chan_check, NULL) != 0) {
			taskqueue_drain_timeout(taskqueue_thread,
			    &rs->rs_chan_check);
		}
		RTWN_LOCK(sc);

		r21au_dfs_radar_disable(sc);
		RTWN_DPRINTF(sc, RTWN_DEBUG_RADAR,
		    "%s: radar detection was (temporarily) disabled\n",
		    __func__);
	}
	RTWN_UNLOCK(sc);

	rs->rs_scan_start(ic);
}

void
r21au_scan_end(struct ieee80211com *ic)
{
	struct rtwn_softc *sc = ic->ic_softc;
	struct r12a_softc *rs = sc->sc_priv;
	int error;

	RTWN_LOCK(sc);
	if (rs->rs_flags & R12A_RADAR_ENABLED) {
		error = r21au_dfs_radar_enable(sc);
		if (error != 0) {
			device_printf(sc->sc_dev,
			    "%s: cannot re-enable radar detection\n",
			    __func__);

			/* XXX */
			ieee80211_restart_all(ic);
			RTWN_UNLOCK(sc);
			return;
		}
		RTWN_DPRINTF(sc, RTWN_DEBUG_RADAR,
		    "%s: radar detection was re-enabled\n", __func__);

		taskqueue_enqueue_timeout(taskqueue_thread,
		    &rs->rs_chan_check, R21AU_RADAR_CHECK_PERIOD);
	}
	RTWN_UNLOCK(sc);

	rs->rs_scan_end(ic);
}
