400 lines
12 KiB
Diff
400 lines
12 KiB
Diff
From: Matthias Schiffer <mschiffer@universe-factory.net>
|
|
Date: Thu, 3 Sep 2015 23:50:51 +0200
|
|
Subject: ath9k: add HSR tuner support for UniFi Outdoor Plus
|
|
|
|
Patch-by: Stefan Rompf <stefan@loplof.de>
|
|
|
|
diff --git a/package/kernel/mac80211/patches/931-ubnt-uap-plus-hsr.patch b/package/kernel/mac80211/patches/931-ubnt-uap-plus-hsr.patch
|
|
new file mode 100644
|
|
index 0000000..4cd6faf
|
|
--- /dev/null
|
|
+++ b/package/kernel/mac80211/patches/931-ubnt-uap-plus-hsr.patch
|
|
@@ -0,0 +1,353 @@
|
|
+--- 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) {
|
|
++ hsr_enable(ah, chandef->width, chan->center_freq);
|
|
++ 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,220 @@
|
|
++/*
|
|
++ *
|
|
++ * 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 hsr_init(struct ath_hw* ah) {
|
|
++ ath9k_hw_cfg_gpio_input(ah, HSR_GPIO_DIN);
|
|
++ ath9k_hw_cfg_output(ah, HSR_GPIO_CSN, AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
|
|
++ ath9k_hw_cfg_output(ah, HSR_GPIO_CLK, AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
|
|
++ ath9k_hw_cfg_output(ah, HSR_GPIO_DOUT, 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 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, "hsr_write_byte: write byte %d return value is %d %c\n",
|
|
++ value, rval, rval > 32 ? rval : '-');
|
|
++
|
|
++ return rval & 0xff;
|
|
++}
|
|
++
|
|
++static int hsr_write_a_chain(struct ath_hw* ah, char* chain, int items) {
|
|
++ int i = 0;
|
|
++ int status = 0;
|
|
++
|
|
++ // a preamble
|
|
++ hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
|
|
++ status = 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 = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
|
|
++ }
|
|
++ if ( loop >= 42) {
|
|
++ ATH_DBG_WARN(1, "hsr_write_a_chain: can't clear an output buffer after a 42 cycles.\n");
|
|
++ return -1;
|
|
++ }
|
|
++ }
|
|
++
|
|
++ for ( i =0; (i < items) && ( 0 != chain[i]); ++i) {
|
|
++ hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, (u32)chain[i]);
|
|
++ }
|
|
++
|
|
++ hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
|
|
++ mdelay(HSR_DELAY_FINAL / 1000);
|
|
++
|
|
++ // reply
|
|
++ memset(chain, 0, items);
|
|
++
|
|
++ hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0);
|
|
++ udelay(HSR_DELAY_TRAILING);
|
|
++
|
|
++ for ( i = 0; i < (items - 1); ++i) {
|
|
++ u32 ret;
|
|
++ if ( 0 != (ret = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0))) {
|
|
++ chain[i] = (char)ret;
|
|
++ } else {
|
|
++ break;
|
|
++ }
|
|
++ udelay(HSR_DELAY_TRAILING);
|
|
++ }
|
|
++
|
|
++ return (1 < i) ? simple_strtol(chain + 1, NULL, 10) : 0;
|
|
++}
|
|
++
|
|
++int hsr_disable(struct ath_hw* ah) {
|
|
++ char cmd[10] = {'b', '4', '0', 0, 0, 0, 0, 0, 0, 0};
|
|
++ int ret;
|
|
++
|
|
++ ret = hsr_write_a_chain(ah, cmd, sizeof(cmd));
|
|
++ if ( (ret > 0) && (*cmd == 'B')) {
|
|
++ return 0;
|
|
++ }
|
|
++
|
|
++ return -1;
|
|
++}
|
|
++
|
|
++int 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'; // 98
|
|
++ snprintf(cmd + 1, 3, "%02d", bw);
|
|
++
|
|
++ ret = hsr_write_a_chain(ah, cmd, sizeof(cmd));
|
|
++ if ( (*cmd != 'B') || (ret != bw)) {
|
|
++ ATH_DBG_WARN(1, "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'; // 120
|
|
++ ret = hsr_write_a_chain(ah, cmd, sizeof(cmd));
|
|
++ if ( *cmd != 'X') {
|
|
++ ATH_DBG_WARN(1, "hsr_enable: failed 'x' command -> reply (%d, %d) \n", *cmd, ret);
|
|
++ return -1;
|
|
++ }
|
|
++
|
|
++ memset(cmd, 0, sizeof(cmd));
|
|
++ *cmd = 'm'; // 109
|
|
++ ret = hsr_write_a_chain(ah, cmd, sizeof(cmd));
|
|
++ if ( *cmd != 'M') {
|
|
++ ATH_DBG_WARN(1, "hsr_enable: failed 'm' command -> reply (%d, %d) \n", *cmd, ret);
|
|
++ return -1;
|
|
++ }
|
|
++
|
|
++ memset(cmd, 0, sizeof(cmd));
|
|
++ *cmd = 'f'; // 102
|
|
++ snprintf(cmd + 1, 6, "%05d", fq);
|
|
++ ret = hsr_write_a_chain(ah, cmd, sizeof(cmd));
|
|
++ if ( (*cmd != 'F') && (ret != fq)) {
|
|
++ ATH_DBG_WARN(1, "hsr_enable: failed set frequency -> reply (%d, %d) \n", *cmd, ret);
|
|
++ return -1;
|
|
++ }
|
|
++
|
|
++ return 0;
|
|
++}
|
|
++
|
|
++int hsr_status(struct ath_hw* ah) {
|
|
++ char cmd[10] = {'s', 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 115
|
|
++ int ret;
|
|
++
|
|
++ ret = hsr_write_a_chain(ah, cmd, sizeof(cmd));
|
|
++ if ( (*cmd != 'S')) {
|
|
++ ATH_DBG_WARN(1, "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,40 @@
|
|
++/*
|
|
++ * 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 hsr_init(struct ath_hw* ah);
|
|
++int hsr_disable(struct ath_hw* ah);
|
|
++int hsr_enable(struct ath_hw* ah, int bw, int fq);
|
|
++int hsr_status(struct ath_hw* ah);
|
|
++#else
|
|
++static inline void hsr_init(struct ath_hw* ah) {}
|
|
++static inline int hsr_disable(struct ath_hw* ah) { return 0; }
|
|
++static inline int hsr_enable(struct ath_hw* ah, int bw, int fq) { return 0; }
|
|
++static inline int 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_
|
|
+ (ah->config.led_active_high) ? 1 : 0);
|
|
+ }
|
|
+
|
|
++ if (pdata && pdata->ubnt_hsr) {
|
|
++ hsr_init(ah);
|
|
++ 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
|
|
+@@ -6,7 +6,8 @@ ath9k-y += beacon.o \
|
|
+ xmit.o \
|
|
+ link.o \
|
|
+ antenna.o \
|
|
+- channel.o
|
|
++ channel.o \
|
|
++ hsr.o
|
|
+
|
|
+ ath9k-$(CPTCFG_ATH9K_BTCOEX_SUPPORT) += mci.o
|
|
+ ath9k-$(CPTCFG_ATH9K_PCI) += pci.o
|
|
+--- a/include/linux/ath9k_platform.h
|
|
++++ b/include/linux/ath9k_platform.h
|
|
+@@ -44,6 +44,8 @@ struct ath9k_platform_data {
|
|
+
|
|
+ int num_leds;
|
|
+ const struct gpio_led *leds;
|
|
++
|
|
++ bool ubnt_hsr;
|
|
+ };
|
|
+
|
|
+ #endif /* _LINUX_ATH9K_PLATFORM_H */
|
|
diff --git a/target/linux/ar71xx/patches-3.18/608-MIPS-ath79-ubnt-xm-add-more-boards.patch b/target/linux/ar71xx/patches-3.18/608-MIPS-ath79-ubnt-xm-add-more-boards.patch
|
|
index 7803513..d865ed2 100644
|
|
--- a/target/linux/ar71xx/patches-3.18/608-MIPS-ath79-ubnt-xm-add-more-boards.patch
|
|
+++ b/target/linux/ar71xx/patches-3.18/608-MIPS-ath79-ubnt-xm-add-more-boards.patch
|
|
@@ -254,6 +254,7 @@
|
|
+ ath79_register_eth(0);
|
|
+ ath79_register_eth(1);
|
|
+
|
|
++ ap9x_pci_get_wmac_data(0)->ubnt_hsr = true;
|
|
+ ap91_pci_init(ee, NULL);
|
|
+
|
|
+ ath79_register_leds_gpio(-1, ARRAY_SIZE(ubnt_unifi_outdoor_plus_leds_gpio),
|
|
diff --git a/target/linux/generic/patches-3.18/150-ath9k_ubnt_hsr_filter.patch b/target/linux/generic/patches-3.18/150-ath9k_ubnt_hsr_filter.patch
|
|
new file mode 100644
|
|
index 0000000..a0ebbc7
|
|
--- /dev/null
|
|
+++ b/target/linux/generic/patches-3.18/150-ath9k_ubnt_hsr_filter.patch
|
|
@@ -0,0 +1,16 @@
|
|
+Flag that this platform is an Ubiquiti UniFi Outdoor Plus
|
|
+containing a RF filter in ath9k's receive path.
|
|
+
|
|
+Signed-off-by: Stefan Rompf <stefan@loplof.de>
|
|
+
|
|
+--- a/include/linux/ath9k_platform.h
|
|
++++ b/include/linux/ath9k_platform.h
|
|
+@@ -44,6 +44,8 @@ struct ath9k_platform_data {
|
|
+
|
|
+ int num_leds;
|
|
+ const struct gpio_led *leds;
|
|
++
|
|
++ bool ubnt_hsr;
|
|
+ };
|
|
+
|
|
+ #endif /* _LINUX_ATH9K_PLATFORM_H */
|