ath9k: add support for the HSR tuner of the Ubiquiti UAP Outdoor+
authorMatthias Schiffer <mschiffer@universe-factory.net>
Tue, 15 Nov 2016 17:54:06 +0000 (18:54 +0100)
committerMatthias Schiffer <mschiffer@universe-factory.net>
Tue, 15 Nov 2016 17:54:06 +0000 (18:54 +0100)
Without setting the HSR to the selected channel, the WLAN of the UAP
Outdoor+ will exhibit high packet loss in RX.

Based-on-patch-by: Stefan Rompf <stefan@loplof.de>
Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
package/kernel/mac80211/Makefile
package/kernel/mac80211/patches/560-ath9k_ubnt_uap_plus_hsr.patch [new file with mode: 0644]
target/linux/generic/files/include/linux/ath9k_platform.h

index 25adbfbb90183de50f5007ce87ddd3fb7d7202c6..52209137dd996ac39235c74f5419b1b498e8e67b 100644 (file)
@@ -251,6 +251,11 @@ define KernelPackage/ath9k/config
                bool "Enable TX99 support"
                depends on PACKAGE_kmod-ath9k
 
+       config ATH9K_UBNTHSR
+               bool "Support for Ubiquiti UniFi Outdoor+ access point"
+               depends on PACKAGE_kmod-ath9k && TARGET_ar71xx_generic
+               default y
+
 endef
 
 define KernelPackage/ath9k-htc
@@ -1503,6 +1508,7 @@ config-$(CONFIG_PCI) += ATH9K_PCI
 config-$(CONFIG_ATH_USER_REGD) += ATH_USER_REGD
 config-$(CONFIG_ATH9K_SUPPORT_PCOEM) += ATH9K_PCOEM
 config-$(CONFIG_ATH9K_TX99) += ATH9K_TX99
+config-$(CONFIG_ATH9K_UBNTHSR) += ATH9K_UBNTHSR
 
 config-$(call config_package,ath9k-htc) += ATH9K_HTC
 config-$(call config_package,ath10k) += ATH10K ATH10K_PCI
diff --git a/package/kernel/mac80211/patches/560-ath9k_ubnt_uap_plus_hsr.patch b/package/kernel/mac80211/patches/560-ath9k_ubnt_uap_plus_hsr.patch
new file mode 100644 (file)
index 0000000..7237492
--- /dev/null
@@ -0,0 +1,418 @@
+--- a/drivers/net/wireless/ath/ath9k/channel.c
++++ b/drivers/net/wireless/ath/ath9k/channel.c
+@@ -15,6 +15,8 @@
+  */
+ #include "ath9k.h"
++#include <linux/ath9k_platform.h>
++#include "hsr.h"
+ /* Set/change channels.  If the channel is really being changed, it's done
+  * by reseting the chip.  To accomplish this we must first cleanup any pending
+@@ -22,6 +24,7 @@
+  */
+ static int ath_set_channel(struct ath_softc *sc)
+ {
++      struct ath9k_platform_data *pdata = sc->dev->platform_data;
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ieee80211_hw *hw = sc->hw;
+@@ -41,6 +44,11 @@ static int ath_set_channel(struct ath_so
+       ath_dbg(common, CONFIG, "Set channel: %d MHz width: %d\n",
+               chan->center_freq, chandef->width);
++      if (pdata && pdata->ubnt_hsr) {
++              ath9k_hsr_enable(ah, chandef->width, chan->center_freq);
++              ath9k_hsr_status(ah);
++      }
++
+       /* update survey stats for the old channel before switching */
+       spin_lock_bh(&common->cc_lock);
+       ath_update_survey_stats(sc);
+--- /dev/null
++++ b/drivers/net/wireless/ath/ath9k/hsr.c
+@@ -0,0 +1,247 @@
++/*
++ *
++ * The MIT License (MIT)
++ *
++ * Copyright (c) 2015 Kirill Berezin
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ *
++ */
++
++#include <linux/io.h>
++#include <linux/slab.h>
++#include <linux/module.h>
++#include <linux/time.h>
++#include <linux/bitops.h>
++#include <linux/etherdevice.h>
++#include <linux/rtnetlink.h>
++#include <asm/unaligned.h>
++
++#include "hw.h"
++#include "ath9k.h"
++
++#define HSR_GPIO_CSN 8
++#define HSR_GPIO_CLK 6
++#define HSR_GPIO_DOUT 7
++#define HSR_GPIO_DIN 5
++
++/* delays are in useconds */
++#define HSR_DELAY_HALF_TICK 100
++#define HSR_DELAY_PRE_WRITE 75
++#define HSR_DELAY_FINAL 20000
++#define HSR_DELAY_TRAILING 200
++
++void ath9k_hsr_init(struct ath_hw *ah)
++{
++      ath9k_hw_gpio_request_in(ah, HSR_GPIO_DIN, NULL);
++      ath9k_hw_gpio_request_out(ah, HSR_GPIO_CSN, NULL,
++                                AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
++      ath9k_hw_gpio_request_out(ah, HSR_GPIO_CLK, NULL,
++                                AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
++      ath9k_hw_gpio_request_out(ah, HSR_GPIO_DOUT, NULL,
++                                AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
++
++      ath9k_hw_set_gpio(ah, HSR_GPIO_CSN, 1);
++      ath9k_hw_set_gpio(ah, HSR_GPIO_CLK, 0);
++      ath9k_hw_set_gpio(ah, HSR_GPIO_DOUT, 0);
++
++      udelay(HSR_DELAY_TRAILING);
++}
++
++static u32 ath9k_hsr_write_byte(struct ath_hw *ah, int delay, u32 value)
++{
++      struct ath_common *common = ath9k_hw_common(ah);
++      int i;
++      u32 rval = 0;
++
++      udelay(delay);
++
++      ath9k_hw_set_gpio(ah, HSR_GPIO_CLK, 0);
++      udelay(HSR_DELAY_HALF_TICK);
++
++      ath9k_hw_set_gpio(ah, HSR_GPIO_CSN, 0);
++      udelay(HSR_DELAY_HALF_TICK);
++
++      for (i = 0; i < 8; ++i) {
++              rval = rval << 1;
++
++              /* pattern is left to right, that is 7-th bit runs first */
++              ath9k_hw_set_gpio(ah, HSR_GPIO_DOUT, (value >> (7 - i)) & 0x1);
++              udelay(HSR_DELAY_HALF_TICK);
++
++              ath9k_hw_set_gpio(ah, HSR_GPIO_CLK, 1);
++              udelay(HSR_DELAY_HALF_TICK);
++
++              rval |= ath9k_hw_gpio_get(ah, HSR_GPIO_DIN);
++
++              ath9k_hw_set_gpio(ah, HSR_GPIO_CLK, 0);
++              udelay(HSR_DELAY_HALF_TICK);
++      }
++
++      ath9k_hw_set_gpio(ah, HSR_GPIO_CSN, 1);
++      udelay(HSR_DELAY_HALF_TICK);
++
++      ath_dbg(common, CONFIG, "ath9k_hsr_write_byte: write byte %d return value is %d %c\n",
++              value, rval, rval > 32 ? rval : '-');
++
++      return rval & 0xff;
++}
++
++static int ath9k_hsr_write_a_chain(struct ath_hw *ah, char *chain, int items)
++{
++      int status = 0;
++      int i = 0;
++      int err;
++
++      /* a preamble */
++      ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
++      status = ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
++
++      /* clear HSR's reply buffer */
++      if (status) {
++              int loop = 0;
++
++              for (loop = 0; (loop < 42) && status; ++loop)
++                      status = ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE,
++                                                    0);
++
++              if (loop >= 42) {
++                      ATH_DBG_WARN(1,
++                                   "ath9k_hsr_write_a_chain: can't clear an output buffer after a 42 cycles.\n");
++                      return -1;
++              }
++      }
++
++      for (i = 0; (i < items) && (chain[i] != 0); ++i)
++              ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, (u32)chain[i]);
++
++      ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
++      mdelay(HSR_DELAY_FINAL / 1000);
++
++      /* reply */
++      memset(chain, 0, items);
++
++      ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
++      udelay(HSR_DELAY_TRAILING);
++
++      for (i = 0; i < (items - 1); ++i) {
++              u32 ret;
++
++              ret = ath9k_hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
++              if (ret != 0)
++                      chain[i] = (char)ret;
++              else
++                      break;
++
++              udelay(HSR_DELAY_TRAILING);
++      }
++
++      if (i <= 1)
++              return 0;
++
++      err = kstrtoint(chain + 1, 10, &i);
++      if (err)
++              return err;
++
++      return i;
++}
++
++int ath9k_hsr_disable(struct ath_hw *ah)
++{
++      char cmd[10] = {'b', '4', '0', 0, 0, 0, 0, 0, 0, 0};
++      int ret;
++
++      ret = ath9k_hsr_write_a_chain(ah, cmd, sizeof(cmd));
++      if ((ret > 0) && (*cmd == 'B'))
++              return 0;
++
++      return -1;
++}
++
++int ath9k_hsr_enable(struct ath_hw *ah, int bw, int fq)
++{
++      char cmd[10];
++      int ret;
++
++      /* Bandwidth argument is 0 sometimes. Assume default 802.11bgn
++       * 20MHz on invalid values
++       */
++      if ((bw != 5) && (bw != 10) && (bw != 20) && (bw != 40))
++              bw = 20;
++
++      memset(cmd, 0, sizeof(cmd));
++      *cmd = 'b';
++      snprintf(cmd + 1, 3, "%02d", bw);
++
++      ret = ath9k_hsr_write_a_chain(ah, cmd, sizeof(cmd));
++      if ((*cmd != 'B') || (ret != bw)) {
++              ATH_DBG_WARN(1,
++                           "ath9k_hsr_enable: failed changing bandwidth -> set (%d,%d) reply (%d, %d)\n",
++                           'b', bw, *cmd, ret);
++              return -1;
++      }
++
++      memset(cmd, 0, sizeof(cmd));
++      *cmd = 'x';
++      ret = ath9k_hsr_write_a_chain(ah, cmd, sizeof(cmd));
++      if (*cmd != 'X') {
++              ATH_DBG_WARN(1,
++                           "ath9k_hsr_enable: failed 'x' command -> reply (%d, %d)\n",
++                           *cmd, ret);
++              return -1;
++      }
++
++      memset(cmd, 0, sizeof(cmd));
++      *cmd = 'm';
++      ret = ath9k_hsr_write_a_chain(ah, cmd, sizeof(cmd));
++      if (*cmd != 'M') {
++              ATH_DBG_WARN(1,
++                           "ath9k_hsr_enable: failed 'm' command -> reply (%d, %d)\n",
++                           *cmd, ret);
++              return  -1;
++      }
++
++      memset(cmd, 0, sizeof(cmd));
++      *cmd = 'f';
++      snprintf(cmd + 1, 6, "%05d", fq);
++      ret = ath9k_hsr_write_a_chain(ah, cmd, sizeof(cmd));
++      if ((*cmd != 'F') && (ret != fq)) {
++              ATH_DBG_WARN(1,
++                           "ath9k_hsr_enable: failed set frequency -> reply (%d, %d)\n",
++                           *cmd, ret);
++              return -1;
++      }
++
++      return 0;
++}
++
++int ath9k_hsr_status(struct ath_hw *ah)
++{
++      char cmd[10] = {'s', 0, 0, 0, 0, 0, 0, 0, 0, 0};
++      int ret;
++
++      ret = ath9k_hsr_write_a_chain(ah, cmd, sizeof(cmd));
++      if (*cmd != 'S') {
++              ATH_DBG_WARN(1, "ath9k_hsr_status: returned %d,%d\n", *cmd,
++                           ret);
++              return -1;
++      }
++
++      return 0;
++}
+--- /dev/null
++++ b/drivers/net/wireless/ath/ath9k/hsr.h
+@@ -0,0 +1,48 @@
++/*
++ * The MIT License (MIT)
++ *
++ * Copyright (c) 2015 Kirill Berezin
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#ifndef HSR_H
++#define HSR_H
++
++#ifdef CPTCFG_ATH9K_UBNTHSR
++
++void ath9k_hsr_init(struct ath_hw *ah);
++int ath9k_hsr_disable(struct ath_hw *ah);
++int ath9k_hsr_enable(struct ath_hw *ah, int bw, int fq);
++int ath9k_hsr_status(struct ath_hw *ah);
++
++#else
++static inline void ath9k_hsr_init(struct ath_hw *ah) {}
++
++static inline int ath9k_hsr_enable(struct ath_hw *ah, int bw, int fq)
++{
++      return 0;
++}
++
++static inline int ath9k_hsr_disable(struct ath_hw *ah) { return 0; }
++static inline int ath9k_hsr_status(struct ath_hw *ah) { return 0; }
++
++#endif
++
++#endif /* HSR_H */
+--- a/drivers/net/wireless/ath/ath9k/main.c
++++ b/drivers/net/wireless/ath/ath9k/main.c
+@@ -16,8 +16,10 @@
+ #include <linux/nl80211.h>
+ #include <linux/delay.h>
++#include <linux/ath9k_platform.h>
+ #include "ath9k.h"
+ #include "btcoex.h"
++#include "hsr.h"
+ u8 ath9k_parse_mpdudensity(u8 mpdudensity)
+ {
+@@ -652,6 +654,7 @@ void ath_reset_work(struct work_struct *
+ static int ath9k_start(struct ieee80211_hw *hw)
+ {
+       struct ath_softc *sc = hw->priv;
++      struct ath9k_platform_data *pdata = sc->dev->platform_data;
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+       struct ieee80211_channel *curchan = sc->cur_chan->chandef.chan;
+@@ -730,6 +733,11 @@ static int ath9k_start(struct ieee80211_
+                                         AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
+       }
++      if (pdata && pdata->ubnt_hsr) {
++              ath9k_hsr_init(ah);
++              ath9k_hsr_disable(ah);
++      }
++
+       /*
+        * Reset key cache to sane defaults (all entries cleared) instead of
+        * semi-random values after suspend/resume.
+--- a/drivers/net/wireless/ath/ath9k/Makefile
++++ b/drivers/net/wireless/ath/ath9k/Makefile
+@@ -16,6 +16,7 @@ ath9k-$(CPTCFG_ATH9K_DFS_CERTIFIED) += d
+ ath9k-$(CPTCFG_ATH9K_TX99) += tx99.o
+ ath9k-$(CPTCFG_ATH9K_WOW) += wow.o
+ ath9k-$(CPTCFG_ATH9K_HWRNG) += rng.o
++ath9k-$(CPTCFG_ATH9K_UBNTHSR) += hsr.o
+ ath9k-$(CPTCFG_ATH9K_DEBUGFS) += debug.o
+--- a/include/linux/ath9k_platform.h
++++ b/include/linux/ath9k_platform.h
+@@ -54,6 +54,8 @@ struct ath9k_platform_data {
+       unsigned num_btns;
+       const struct gpio_keys_button *btns;
+       unsigned btn_poll_interval;
++
++      bool ubnt_hsr;
+ };
+ #endif /* _LINUX_ATH9K_PLATFORM_H */
+--- a/.local-symbols
++++ b/.local-symbols
+@@ -153,6 +153,7 @@ ATH9K_WOW=
+ ATH9K_RFKILL=
+ ATH9K_CHANNEL_CONTEXT=
+ ATH9K_PCOEM=
++ATH9K_UBNTHSR=
+ ATH9K_HTC=
+ ATH9K_HTC_DEBUGFS=
+ ATH9K_HWRNG=
+--- a/drivers/net/wireless/ath/ath9k/Kconfig
++++ b/drivers/net/wireless/ath/ath9k/Kconfig
+@@ -59,6 +59,19 @@ config ATH9K_AHB
+         Say Y, if you have a SoC with a compatible built-in
+         wireless MAC. Say N if unsure.
++config ATH9K_UBNTHSR
++      bool "Ubiquiti UniFi Outdoor Plus HSR support"
++      depends on ATH9K
++      ---help---
++        This options enables code to control the HSR RF
++        filter in the receive path of the Ubiquiti UniFi
++        Outdoor Plus access point.
++
++        Say Y if you want to use the access point. The
++        code will only be used if the device is detected,
++        so it does not harm other setup other than occupying
++        a bit of memory.
++
+ config ATH9K_DEBUGFS
+       bool "Atheros ath9k debugging"
+       depends on ATH9K && DEBUG_FS
index 2c19f3b494582144806e175783efbc2e124ca873..558445ff63a0f7f7bfff35286c39f89e243156de 100644 (file)
@@ -54,6 +54,8 @@ struct ath9k_platform_data {
        unsigned num_btns;
        const struct gpio_keys_button *btns;
        unsigned btn_poll_interval;
+
+       bool ubnt_hsr;
 };
 
 #endif /* _LINUX_ATH9K_PLATFORM_H */