From: Matthias Schiffer Date: Thu, 3 Sep 2015 23:50:51 +0200 Subject: ath9k: add HSR tuner support for UniFi Outdoor Plus Patch-by: Stefan Rompf 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 0000000000000000000000000000000000000000..8e09fee938951ab3636d23b5fe4dee3ab0e11c7a --- /dev/null +++ b/package/kernel/mac80211/patches/931-ubnt-uap-plus-hsr.patch @@ -0,0 +1,349 @@ +--- a/drivers/net/wireless/ath/ath9k/channel.c ++++ b/drivers/net/wireless/ath/ath9k/channel.c +@@ -15,6 +15,8 @@ + */ + + #include "ath9k.h" ++#include ++#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,223 @@ ++/* ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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_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 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,33 @@ ++/* ++ * 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_ ++ ++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); ++ ++#endif /* HSR_H_ */ +--- a/drivers/net/wireless/ath/ath9k/main.c ++++ b/drivers/net/wireless/ath/ath9k/main.c +@@ -16,8 +16,10 @@ + + #include + #include ++#include + #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; +@@ -727,6 +730,11 @@ static int ath9k_start(struct ieee80211_ + ath9k_hw_set_gpio(ah, ah->led_pin, + (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 +@@ -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 */ 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 78035131db93f3e465585d7e96bfae9e88783d28..d865ed29ac268b6b49644c0841be6dfeace75038 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 0000000000000000000000000000000000000000..b8844f1341f9a8a478730ef6ac440833b84b3e98 --- /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 + +--- 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 */