--- 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; @@ -42,6 +45,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_irqsave(&common->cc_lock, flags); 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 +#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 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 #include +#include #include "ath9k.h" #include "btcoex.h" +#include "hsr.h" u8 ath9k_parse_mpdudensity(u8 mpdudensity) { @@ -649,6 +651,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_ 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 @@ -17,6 +17,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 @@ -53,6 +53,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 @@ -112,6 +112,7 @@ ATH9K_WOW= ATH9K_RFKILL= ATH9K_CHANNEL_CONTEXT= ATH9K_PCOEM= +ATH9K_UBNTHSR= ATH9K_PCI_NO_EEPROM= ATH9K_HTC= ATH9K_HTC_DEBUGFS= --- a/drivers/net/wireless/ath/ath9k/Kconfig +++ b/drivers/net/wireless/ath/ath9k/Kconfig @@ -60,6 +60,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