text: /*-
   * 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 "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);
  }
  