Message ID | 201506061528.42571.stefan@loplof.de |
---|---|
State | RFC |
Headers | show |
Hi Stefan, I have a couple of suggestions. First of all I think it will be better to initialize static variables in hsr_tune with zeroes : > ++static void hsr_tune(struct ath_hw* ah, int bw, int fq) { > ++ static int initialized = 0; > ++ static int last_bw = 0, last_fq = 0; And I have an updated versions of write functions. I added to hsr_write_byte debug messages that can be activated by atheros CONFIG flag (of course atheros debugging must be compiled in). Also I simplified loops in hsr_write_a_chain because it seems that they are needed to get data from a some sort of output buffer which includes less than 6 bytes. static u32 hsr_write_byte(struct ath_hw* ah, int delay, u32 value){ int i; u32 rval = 0; struct ath_common *common = ath9k_hw_common(ah); 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; 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) { printk(KERN_WARNING "hsr_write_a_chain: can't clear an output buffer after a 42 cycles.\n"); return 0; } } // data 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); udelay(HSR_DELAY_FINAL); // 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; } > I also tried to change target/linux/ar71xx/generic/profiles/ubnt.mk and > target/linux/ar71xx/image/Makefile to include this module into the > UBNTUNIFIOUTDOORPLUS image, but this fails. Any idea why? May be this happens when building a bunch of different profiles at once? Kirill. On 06/06/2015 04:28 PM, Stefan Rompf wrote: > Hi, > > please review my patch to add support for the Ubiquiti Unifi Outdoor Plus > access point based on the work of Kirill Berezin and me. > > The access point has a configurable RF filter in the receive path that must be > tuned according to the selected Wifi channel. > > A patch to compat_wireless adds support to register a callback to the ath9k > driver that is called whenever the channel changes. It also adds hsr.c, the > channel change helper that tunes the filter connected to the AR9287 GPIO pins. > I'm running this part successfully on top of the Chaos Calmer RC1 image. > > A new configuration option to create the kmod-ath-hsr. It contains the driver > .ko and must be installed on the access point. This is compile tested on > trunk. > > I also tried to change target/linux/ar71xx/generic/profiles/ubnt.mk and > target/linux/ar71xx/image/Makefile to include this module into the > UBNTUNIFIOUTDOORPLUS image, but this fails. Any idea why? > > Comments? > > Stefan > > Index: package/kernel/mac80211/Makefile > =================================================================== > --- package/kernel/mac80211/Makefile (Revision 45907) > +++ package/kernel/mac80211/Makefile (Arbeitskopie) > @@ -27,7 +27,7 @@ > rt2x00-lib rt2x00-pci rt2x00-usb rt2800-lib rt2400-pci rt2500-pci \ > rt2500-usb rt61-pci rt73-usb rt2800-mmio rt2800-pci rt2800-usb rt2800-soc \ > rtl8180 rtl8187 zd1211rw mac80211-hwsim carl9170 b43 b43legacy \ > - ath9k-common ath9k ath9k-htc ath10k ath net-libipw net-ipw2100 net-ipw2200 \ > + ath9k-common ath9k ath9k-htc ath9k-hsr ath10k ath net-libipw net-ipw2100 net-ipw2200 \ > mwl8k mwifiex-pcie net-hermes net-hermes-pci net-hermes-plx net-hermes-pcmcia \ > iwl-legacy iwl3945 iwl4965 iwlagn wlcore wl12xx wl18xx lib80211 \ > rtlwifi rtlwifi-pci rtlwifi-usb rtl8192c-common rtl8192ce rtl8192se \ > @@ -589,6 +589,22 @@ > > endef > > +define KernelPackage/ath9k-hsr > + $(call KernelPackage/mac80211/Default) > + TITLE:=Driver for the Ubiquiti UniFi Outdoor Plus HSR filter > + URL:=http://wiki.openwrt.org/toh/ubiquiti/unifi_outdoorplus > + DEPENDS+= @PCI_SUPPORT||TARGET_ar71xx +kmod-ath9k > + FILES:= \ > + $(PKG_BUILD_DIR)/drivers/net/wireless/ath/ath9k/ath9k_hsr.ko > + AUTOLOAD:=$(call AutoProbe,ath9k_hsr) > +endef > + > +define KernelPackage/ath9k-hsr/description > +This modules adds support for the 'High-Selectivity Receiver' > +RF filter in the receive path of the access point. It is > +required for this and only for this access point. > +endef > + > define KernelPackage/ath9k-htc > $(call KernelPackage/mac80211/Default) > TITLE:=Atheros 802.11n USB device support > @@ -1559,6 +1575,8 @@ > config-$(CONFIG_ATH_USER_REGD) += ATH_USER_REGD > config-$(CONFIG_ATH9K_SUPPORT_PCOEM) += ATH9K_PCOEM > > +config-$(call config_package,ath9k-hsr) += ATH9K_HSR > + > config-$(call config_package,ath9k-htc) += ATH9K_HTC > config-$(call config_package,ath10k) += ATH10K ATH10K_PCI > > @@ -2055,6 +2073,7 @@ > $(eval $(call KernelPackage,mac80211-hwsim)) > $(eval $(call KernelPackage,ath9k-common)) > $(eval $(call KernelPackage,ath9k)) > +$(eval $(call KernelPackage,ath9k-hsr)) > $(eval $(call KernelPackage,ath9k-htc)) > $(eval $(call KernelPackage,ath10k)) > $(eval $(call KernelPackage,ath)) > Index: package/kernel/mac80211/patches/930-ubnt-uap-plus-hsr.patch > =================================================================== > --- package/kernel/mac80211/patches/930-ubnt-uap-plus-hsr.patch (Revision 0) > +++ package/kernel/mac80211/patches/930-ubnt-uap-plus-hsr.patch (Arbeitskopie) > @@ -0,0 +1,346 @@ > +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/ath9k.h kernel/drivers/net/wireless/ath/ath9k/ath9k.h > +--- kernel/drivers/net/wireless/ath/ath9k.orig/ath9k.h 2015-06-04 21:19:11.000000000 +0200 > ++++ kernel/drivers/net/wireless/ath/ath9k/ath9k.h 2015-06-06 10:23:05.000000000 +0200 > +@@ -1110,4 +1110,10 @@ static inline int ath_ahb_init(void) { r > + static inline void ath_ahb_exit(void) {}; > + #endif > + > ++/* > ++ * OpenWrt UBNT HSR filter support > ++ */ > ++typedef void (set_channel_helper_fn)(struct ath_hw* ah, int bw, int fq); > ++void ath9k_register_set_channel_helper(set_channel_helper_fn *); > ++ > + #endif /* ATH9K_H */ > +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/channel.c kernel/drivers/net/wireless/ath/ath9k/channel.c > +--- kernel/drivers/net/wireless/ath/ath9k.orig/channel.c 2015-03-10 04:37:15.000000000 +0100 > ++++ kernel/drivers/net/wireless/ath/ath9k/channel.c 2015-06-06 10:23:05.000000000 +0200 > +@@ -16,6 +16,18 @@ > + > + #include "ath9k.h" > + > ++/* > ++ * OpenWrt UBNT HSR filter support > ++ */ > ++static set_channel_helper_fn *ath9k_set_channel_helper; > ++ > ++void ath9k_register_set_channel_helper(set_channel_helper_fn *chanfn) > ++{ > ++ ath9k_set_channel_helper = chanfn; > ++} > ++EXPORT_SYMBOL(ath9k_register_set_channel_helper); > ++ > ++ > + /* 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 > + * DMA, then restart stuff. > +@@ -41,6 +53,9 @@ 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 (ath9k_set_channel_helper) > ++ ath9k_set_channel_helper(ah, chandef->width, chan->center_freq); > ++ > + /* update survey stats for the old channel before switching */ > + spin_lock_bh(&common->cc_lock); > + ath_update_survey_stats(sc); > +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/hsr.c kernel/drivers/net/wireless/ath/ath9k/hsr.c > +--- kernel/drivers/net/wireless/ath/ath9k.orig/hsr.c 1970-01-01 01:00:00.000000000 +0100 > ++++ kernel/drivers/net/wireless/ath/ath9k/hsr.c 2015-06-06 10:48:46.000000000 +0200 > +@@ -0,0 +1,282 @@ > ++/* > ++ * > ++ * 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 "hw-ops.h" > ++#include "ar9003_mac.h" > ++#include "ar9003_mci.h" > ++#include "ar9003_phy.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 > ++ > ++static void hsr_init(struct ath_hw* ah); > ++static int hsr_disable(struct ath_hw* ah); > ++static int hsr_enable(struct ath_hw* ah, int bw, int fq); > ++static int hsr_status(struct ath_hw* ah); > ++ > ++static 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); > ++ > ++ printk(KERN_NOTICE "hsr_init: done"); > ++} > ++ > ++static u32 hsr_write_byte(struct ath_hw* ah, int delay, u32 value){ > ++ 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); > ++ > ++ /* printk(KERN_NOTICE "hsr_write_byte: write byte %d return value is %x %d %c \n", value, rval, rval, rval > 32 ? rval : '-'); */ > ++ > ++ return rval & 0xff; > ++} > ++ > ++static int hsr_write_a_chain(struct ath_hw* ah, char* chain, int items) { > ++ int i = 0, j; > ++ int status = 0; > ++ > ++ // a preamble > ++ hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); > ++ status = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); > ++ > ++ // Continue preamble if hsr returns non zero (perhaps a stray command result) > ++ if (status) { > ++ int loop = 2; > ++ do { > ++ ++loop; > ++ if (loop > 42) { > ++ printk(KERN_NOTICE "hsr_write_a_chain: too many loops in preamble. giving up.\n"); > ++ return -1; > ++ } > ++ status = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); > ++ } while(status); > ++ } > ++ > ++ 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); > ++ > ++ memset(chain, 0, items); > ++ > ++ for ( j = 0, i = 0; (i < 7) && (j < (items - 1)) ; ++i) { > ++ u32 ret; > ++ if ( 31 < (ret = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0))) { > ++ chain[j] = (char)ret; > ++ ++ j; > ++ } > ++ udelay(HSR_DELAY_TRAILING); > ++ } > ++ /* printk(KERN_NOTICE "hsr_write_a_chain: j %d \n", j); */ > ++ return j > 1 ? simple_strtol(chain + 1, NULL, 10) : 0; > ++} > ++ > ++static 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)); > ++ /* printk(KERN_NOTICE "hsr_disable: return %d \n", ret); */ > ++ if ( (ret > 0) && (*cmd == 'B')) { > ++ printk(KERN_NOTICE "hsr_disable: bandwidth set %d \n", ret); > ++ return 0; > ++ } > ++ > ++ return -1; > ++} > ++ > ++static int hsr_enable(struct ath_hw* ah, int bw, int fq) { > ++ char cmd[10]; > ++ int ret; > ++ > ++ 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)) { > ++ printk(KERN_NOTICE "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') { > ++ printk(KERN_NOTICE "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') { > ++ printk(KERN_NOTICE "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)) { > ++ printk(KERN_NOTICE "hsr_enable: failed set frequency -> reply (%d, %d) \n", *cmd, ret); > ++ return -1; > ++ } > ++ > ++ printk(KERN_NOTICE "hsr_enable: center frequency %dMHz bandwidth %dMHz \n", fq, bw); > ++ > ++ return 0; > ++} > ++ > ++static 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')) { > ++ printk(KERN_NOTICE "hsr_status: returned %d,%d \n", *cmd, ret); > ++ return -1; > ++ } > ++ > ++ printk(KERN_NOTICE "hsr_status: current status is %d \n", ret); > ++ > ++ return 0; > ++} > ++ > ++static void hsr_tune(struct ath_hw* ah, int bw, int fq) { > ++ static int initialized; > ++ static int last_bw, last_fq; > ++ > ++ if (NULL == ah) { > ++ return; > ++ } > ++ > ++ /* Bandwidth argument is 0 sometimes. Assume default 802.11bgn > ++ 20MHz on invalid values */ > ++ if ( (bw != 5) && (bw != 10) && (bw != 20) && (bw != 40)) { > ++ bw = 20; > ++ } > ++ > ++ if (bw == last_bw && fq == last_fq) { > ++ /* Avoid tuning if nothing changes */ > ++ printk(KERN_NOTICE "hsr_tune: already tuned to center frequency %dMHz bandwidth %dMHz\n", fq, bw); > ++ return; > ++ } > ++ > ++ if (!initialized) { > ++ initialized = 1; > ++ hsr_init(ah); > ++ } > ++ > ++ if (!hsr_enable(ah, bw, fq)) { > ++ hsr_status(ah); > ++ last_bw = bw; > ++ last_fq = fq; > ++ } else { > ++ /* Tuning failed - make sure that we try again */ > ++ last_bw = -1; > ++ } > ++} > ++ > ++ > ++static int __init hsr_mod_init(void) > ++{ > ++ rtnl_lock(); /* Should lock against nl80211_set_channel() */ > ++ ath9k_register_set_channel_helper(hsr_tune); > ++ rtnl_unlock(); > ++ return 0; > ++} > ++ > ++static void __exit hsr_mod_exit(void) > ++{ > ++ rtnl_lock(); > ++ ath9k_register_set_channel_helper(NULL); > ++ rtnl_unlock(); > ++} > ++ > ++module_init(hsr_mod_init); > ++module_exit(hsr_mod_exit); > ++ > ++MODULE_AUTHOR("Kirill Berezin, Stefan Rompf"); > ++MODULE_DESCRIPTION("Support for Ubiquiti Outdoor Plus HSR filter."); > ++MODULE_SUPPORTED_DEVICE("Ubiquiti Outdoor Plus"); > ++MODULE_LICENSE("Dual MIT/GPL"); > ++ > +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/Makefile kernel/drivers/net/wireless/ath/ath9k/Makefile > +--- kernel/drivers/net/wireless/ath/ath9k.orig/Makefile 2015-03-10 04:37:16.000000000 +0100 > ++++ kernel/drivers/net/wireless/ath/ath9k/Makefile 2015-06-06 10:23:05.000000000 +0200 > +@@ -22,6 +22,10 @@ ath9k-$(CPTCFG_ATH9K_STATION_STATISTICS) > + > + obj-$(CPTCFG_ATH9K) += ath9k.o > + > ++ath9k_hsr-y := hsr.o > ++ > ++obj-$(CPTCFG_ATH9K) += ath9k_hsr.o > ++ > + ath9k_hw-y:= \ > + ar9002_hw.o \ > + ar9003_hw.o \ > Index: target/linux/ar71xx/generic/profiles/ubnt.mk > =================================================================== > --- target/linux/ar71xx/generic/profiles/ubnt.mk (Revision 45907) > +++ target/linux/ar71xx/generic/profiles/ubnt.mk (Arbeitskopie) > @@ -49,6 +49,17 @@ > > $(eval $(call Profile,UBNTUNIFIOUTDOOR)) > > +define Profile/UBNTUNIFIOUTDOORPLUS > + NAME:=Ubiquiti UniFiAP Outdoor Plus > + PACKAGES:=kmod-ath9k-hsr > +endef > + > +define Profile/UBNTUNIFIOUTDOORPLUS/Description > + Package set optimized for the Ubiquiti UniFiAP Outdoor Plus. > +endef > + > +$(eval $(call Profile,UBNTUNIFIOUTDOORPLUS)) > + > define Profile/UAPPRO > NAME:=Ubiquiti UniFi AP Pro > PACKAGES:= > Index: target/linux/ar71xx/image/Makefile > =================================================================== > --- target/linux/ar71xx/image/Makefile (Revision 45907) > +++ target/linux/ar71xx/image/Makefile (Arbeitskopie) > @@ -1975,7 +1975,7 @@ > $(eval $(call MultiProfile,TLWR1043,TLWR1043V1 TLWR1043V2)) > $(eval $(call MultiProfile,TLWDR4300,TLWDR3500V1 TLWDR3600V1 TLWDR4300V1 TLWDR4300V1IL TLWDR4310V1 MW4530RV1)) > $(eval $(call MultiProfile,TUBE2H,TUBE2H8M TUBE2H16M)) > -$(eval $(call MultiProfile,UBNT,UBNTAIRROUTER UBNTRS UBNTRSPRO UBNTLSSR71 UBNTBULLETM UBNTROCKETM UBNTROCKETMXW UBNTNANOM UBNTNANOMXW UBNTLOCOXW UBNTUNIFI > UBNTUNIFIOUTDOOR UBNTUNIFIOUTDOORPLUS UAPPRO UBNTAIRGW)) > +$(eval $(call MultiProfile,UBNT,UBNTAIRROUTER UBNTRS UBNTRSPRO UBNTLSSR71 UBNTBULLETM UBNTROCKETM UBNTROCKETMXW UBNTNANOM UBNTNANOMXW UBNTLOCOXW UBNTUNIFI > UBNTUNIFIOUTDOOR UAPPRO UBNTAIRGW)) > $(eval $(call MultiProfile,WNR612V2,REALWNR612V2 N150R)) > $(eval $(call MultiProfile,WNR1000V2,REALWNR1000V2 WNR1000V2_VC)) > $(eval $(call MultiProfile,WP543,WP543_2M WP543_4M WP543_8M WP543_16M)) >
Citeren Kirill Berezin <kyb22@rol.ru>: > Hi Stefan, > > I have a couple of suggestions. > > First of all I think it will be better to initialize static > variables in hsr_tune with zeroes : > >> ++static void hsr_tune(struct ath_hw* ah, int bw, int fq) { >> ++ static int initialized = 0; >> ++ static int last_bw = 0, last_fq = 0; These static variables will be initialized to zero anyway, so the above is redundant (but may be clearer to read). See http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, ยง6.7.8 sub-paragraph 10.
Hi Kirill, hi Arjen, thanks for your feedback. I'll create a new version of the patch with your updates and suggestions. I will also try to move the HSR tuner module into it's own subdirectory below package/kernel to minimize the mac80211 patch that Felix will have to maintain. > > I also tried to change target/linux/ar71xx/generic/profiles/ubnt.mk and > > target/linux/ar71xx/image/Makefile to include this module into the > > UBNTUNIFIOUTDOORPLUS image, but this fails. Any idea why? > > May be this happens when building a bunch of different profiles at once? I've also tried only one profile, no idea yet. Maybe Alexander Couzens' porting to new BuildCode helps. Stefan
Index: package/kernel/mac80211/Makefile =================================================================== --- package/kernel/mac80211/Makefile (Revision 45907) +++ package/kernel/mac80211/Makefile (Arbeitskopie) @@ -27,7 +27,7 @@ rt2x00-lib rt2x00-pci rt2x00-usb rt2800-lib rt2400-pci rt2500-pci \ rt2500-usb rt61-pci rt73-usb rt2800-mmio rt2800-pci rt2800-usb rt2800-soc \ rtl8180 rtl8187 zd1211rw mac80211-hwsim carl9170 b43 b43legacy \ - ath9k-common ath9k ath9k-htc ath10k ath net-libipw net-ipw2100 net-ipw2200 \ + ath9k-common ath9k ath9k-htc ath9k-hsr ath10k ath net-libipw net-ipw2100 net-ipw2200 \ mwl8k mwifiex-pcie net-hermes net-hermes-pci net-hermes-plx net-hermes-pcmcia \ iwl-legacy iwl3945 iwl4965 iwlagn wlcore wl12xx wl18xx lib80211 \ rtlwifi rtlwifi-pci rtlwifi-usb rtl8192c-common rtl8192ce rtl8192se \ @@ -589,6 +589,22 @@ endef +define KernelPackage/ath9k-hsr + $(call KernelPackage/mac80211/Default) + TITLE:=Driver for the Ubiquiti UniFi Outdoor Plus HSR filter + URL:=http://wiki.openwrt.org/toh/ubiquiti/unifi_outdoorplus + DEPENDS+= @PCI_SUPPORT||TARGET_ar71xx +kmod-ath9k + FILES:= \ + $(PKG_BUILD_DIR)/drivers/net/wireless/ath/ath9k/ath9k_hsr.ko + AUTOLOAD:=$(call AutoProbe,ath9k_hsr) +endef + +define KernelPackage/ath9k-hsr/description +This modules adds support for the 'High-Selectivity Receiver' +RF filter in the receive path of the access point. It is +required for this and only for this access point. +endef + define KernelPackage/ath9k-htc $(call KernelPackage/mac80211/Default) TITLE:=Atheros 802.11n USB device support @@ -1559,6 +1575,8 @@ config-$(CONFIG_ATH_USER_REGD) += ATH_USER_REGD config-$(CONFIG_ATH9K_SUPPORT_PCOEM) += ATH9K_PCOEM +config-$(call config_package,ath9k-hsr) += ATH9K_HSR + config-$(call config_package,ath9k-htc) += ATH9K_HTC config-$(call config_package,ath10k) += ATH10K ATH10K_PCI @@ -2055,6 +2073,7 @@ $(eval $(call KernelPackage,mac80211-hwsim)) $(eval $(call KernelPackage,ath9k-common)) $(eval $(call KernelPackage,ath9k)) +$(eval $(call KernelPackage,ath9k-hsr)) $(eval $(call KernelPackage,ath9k-htc)) $(eval $(call KernelPackage,ath10k)) $(eval $(call KernelPackage,ath)) Index: package/kernel/mac80211/patches/930-ubnt-uap-plus-hsr.patch =================================================================== --- package/kernel/mac80211/patches/930-ubnt-uap-plus-hsr.patch (Revision 0) +++ package/kernel/mac80211/patches/930-ubnt-uap-plus-hsr.patch (Arbeitskopie) @@ -0,0 +1,346 @@ +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/ath9k.h kernel/drivers/net/wireless/ath/ath9k/ath9k.h +--- kernel/drivers/net/wireless/ath/ath9k.orig/ath9k.h 2015-06-04 21:19:11.000000000 +0200 ++++ kernel/drivers/net/wireless/ath/ath9k/ath9k.h 2015-06-06 10:23:05.000000000 +0200 +@@ -1110,4 +1110,10 @@ static inline int ath_ahb_init(void) { r + static inline void ath_ahb_exit(void) {}; + #endif + ++/* ++ * OpenWrt UBNT HSR filter support ++ */ ++typedef void (set_channel_helper_fn)(struct ath_hw* ah, int bw, int fq); ++void ath9k_register_set_channel_helper(set_channel_helper_fn *); ++ + #endif /* ATH9K_H */ +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/channel.c kernel/drivers/net/wireless/ath/ath9k/channel.c +--- kernel/drivers/net/wireless/ath/ath9k.orig/channel.c 2015-03-10 04:37:15.000000000 +0100 ++++ kernel/drivers/net/wireless/ath/ath9k/channel.c 2015-06-06 10:23:05.000000000 +0200 +@@ -16,6 +16,18 @@ + + #include "ath9k.h" + ++/* ++ * OpenWrt UBNT HSR filter support ++ */ ++static set_channel_helper_fn *ath9k_set_channel_helper; ++ ++void ath9k_register_set_channel_helper(set_channel_helper_fn *chanfn) ++{ ++ ath9k_set_channel_helper = chanfn; ++} ++EXPORT_SYMBOL(ath9k_register_set_channel_helper); ++ ++ + /* 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 + * DMA, then restart stuff. +@@ -41,6 +53,9 @@ 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 (ath9k_set_channel_helper) ++ ath9k_set_channel_helper(ah, chandef->width, chan->center_freq); ++ + /* update survey stats for the old channel before switching */ + spin_lock_bh(&common->cc_lock); + ath_update_survey_stats(sc); +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/hsr.c kernel/drivers/net/wireless/ath/ath9k/hsr.c +--- kernel/drivers/net/wireless/ath/ath9k.orig/hsr.c 1970-01-01 01:00:00.000000000 +0100 ++++ kernel/drivers/net/wireless/ath/ath9k/hsr.c 2015-06-06 10:48:46.000000000 +0200 +@@ -0,0 +1,282 @@ ++/* ++ * ++ * 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 "hw-ops.h" ++#include "ar9003_mac.h" ++#include "ar9003_mci.h" ++#include "ar9003_phy.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 ++ ++static void hsr_init(struct ath_hw* ah); ++static int hsr_disable(struct ath_hw* ah); ++static int hsr_enable(struct ath_hw* ah, int bw, int fq); ++static int hsr_status(struct ath_hw* ah); ++ ++static 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); ++ ++ printk(KERN_NOTICE "hsr_init: done"); ++} ++ ++static u32 hsr_write_byte(struct ath_hw* ah, int delay, u32 value){ ++ 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); ++ ++ /* printk(KERN_NOTICE "hsr_write_byte: write byte %d return value is %x %d %c \n", value, rval, rval, rval > 32 ? rval : '-'); */ ++ ++ return rval & 0xff; ++} ++ ++static int hsr_write_a_chain(struct ath_hw* ah, char* chain, int items) { ++ int i = 0, j; ++ int status = 0; ++ ++ // a preamble ++ hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); ++ status = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); ++ ++ // Continue preamble if hsr returns non zero (perhaps a stray command result) ++ if (status) { ++ int loop = 2; ++ do { ++ ++loop; ++ if (loop > 42) { ++ printk(KERN_NOTICE "hsr_write_a_chain: too many loops in preamble. giving up.\n"); ++ return -1; ++ } ++ status = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); ++ } while(status); ++ } ++ ++ 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); ++ ++ memset(chain, 0, items); ++ ++ for ( j = 0, i = 0; (i < 7) && (j < (items - 1)) ; ++i) { ++ u32 ret; ++ if ( 31 < (ret = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0))) { ++ chain[j] = (char)ret; ++ ++ j; ++ } ++ udelay(HSR_DELAY_TRAILING); ++ } ++ /* printk(KERN_NOTICE "hsr_write_a_chain: j %d \n", j); */ ++ return j > 1 ? simple_strtol(chain + 1, NULL, 10) : 0; ++} ++ ++static 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)); ++ /* printk(KERN_NOTICE "hsr_disable: return %d \n", ret); */ ++ if ( (ret > 0) && (*cmd == 'B')) { ++ printk(KERN_NOTICE "hsr_disable: bandwidth set %d \n", ret); ++ return 0; ++ } ++ ++ return -1; ++} ++ ++static int hsr_enable(struct ath_hw* ah, int bw, int fq) { ++ char cmd[10]; ++ int ret; ++ ++ 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)) { ++ printk(KERN_NOTICE "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') { ++ printk(KERN_NOTICE "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') { ++ printk(KERN_NOTICE "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)) { ++ printk(KERN_NOTICE "hsr_enable: failed set frequency -> reply (%d, %d) \n", *cmd, ret); ++ return -1; ++ } ++ ++ printk(KERN_NOTICE "hsr_enable: center frequency %dMHz bandwidth %dMHz \n", fq, bw); ++ ++ return 0; ++} ++ ++static 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')) { ++ printk(KERN_NOTICE "hsr_status: returned %d,%d \n", *cmd, ret); ++ return -1; ++ } ++ ++ printk(KERN_NOTICE "hsr_status: current status is %d \n", ret); ++ ++ return 0; ++} ++ ++static void hsr_tune(struct ath_hw* ah, int bw, int fq) { ++ static int initialized; ++ static int last_bw, last_fq; ++ ++ if (NULL == ah) { ++ return; ++ } ++ ++ /* Bandwidth argument is 0 sometimes. Assume default 802.11bgn ++ 20MHz on invalid values */ ++ if ( (bw != 5) && (bw != 10) && (bw != 20) && (bw != 40)) { ++ bw = 20; ++ } ++ ++ if (bw == last_bw && fq == last_fq) { ++ /* Avoid tuning if nothing changes */ ++ printk(KERN_NOTICE "hsr_tune: already tuned to center frequency %dMHz bandwidth %dMHz\n", fq, bw); ++ return; ++ } ++ ++ if (!initialized) { ++ initialized = 1; ++ hsr_init(ah); ++ } ++ ++ if (!hsr_enable(ah, bw, fq)) { ++ hsr_status(ah); ++ last_bw = bw; ++ last_fq = fq; ++ } else { ++ /* Tuning failed - make sure that we try again */ ++ last_bw = -1; ++ } ++} ++ ++ ++static int __init hsr_mod_init(void) ++{ ++ rtnl_lock(); /* Should lock against nl80211_set_channel() */ ++ ath9k_register_set_channel_helper(hsr_tune); ++ rtnl_unlock(); ++ return 0; ++} ++ ++static void __exit hsr_mod_exit(void) ++{ ++ rtnl_lock(); ++ ath9k_register_set_channel_helper(NULL); ++ rtnl_unlock(); ++} ++ ++module_init(hsr_mod_init); ++module_exit(hsr_mod_exit); ++ ++MODULE_AUTHOR("Kirill Berezin, Stefan Rompf"); ++MODULE_DESCRIPTION("Support for Ubiquiti Outdoor Plus HSR filter."); ++MODULE_SUPPORTED_DEVICE("Ubiquiti Outdoor Plus"); ++MODULE_LICENSE("Dual MIT/GPL"); ++ +diff -X diffign -Npur kernel/drivers/net/wireless/ath/ath9k.orig/Makefile kernel/drivers/net/wireless/ath/ath9k/Makefile +--- kernel/drivers/net/wireless/ath/ath9k.orig/Makefile 2015-03-10 04:37:16.000000000 +0100 ++++ kernel/drivers/net/wireless/ath/ath9k/Makefile 2015-06-06 10:23:05.000000000 +0200 +@@ -22,6 +22,10 @@ ath9k-$(CPTCFG_ATH9K_STATION_STATISTICS) + + obj-$(CPTCFG_ATH9K) += ath9k.o + ++ath9k_hsr-y := hsr.o ++ ++obj-$(CPTCFG_ATH9K) += ath9k_hsr.o ++ + ath9k_hw-y:= \ + ar9002_hw.o \ + ar9003_hw.o \ Index: target/linux/ar71xx/generic/profiles/ubnt.mk =================================================================== --- target/linux/ar71xx/generic/profiles/ubnt.mk (Revision 45907) +++ target/linux/ar71xx/generic/profiles/ubnt.mk (Arbeitskopie) @@ -49,6 +49,17 @@ $(eval $(call Profile,UBNTUNIFIOUTDOOR)) +define Profile/UBNTUNIFIOUTDOORPLUS + NAME:=Ubiquiti UniFiAP Outdoor Plus + PACKAGES:=kmod-ath9k-hsr +endef + +define Profile/UBNTUNIFIOUTDOORPLUS/Description + Package set optimized for the Ubiquiti UniFiAP Outdoor Plus. +endef + +$(eval $(call Profile,UBNTUNIFIOUTDOORPLUS)) + define Profile/UAPPRO NAME:=Ubiquiti UniFi AP Pro PACKAGES:= Index: target/linux/ar71xx/image/Makefile =================================================================== --- target/linux/ar71xx/image/Makefile (Revision 45907) +++ target/linux/ar71xx/image/Makefile (Arbeitskopie) @@ -1975,7 +1975,7 @@ $(eval $(call MultiProfile,TLWR1043,TLWR1043V1 TLWR1043V2)) $(eval $(call MultiProfile,TLWDR4300,TLWDR3500V1 TLWDR3600V1 TLWDR4300V1 TLWDR4300V1IL TLWDR4310V1 MW4530RV1)) $(eval $(call MultiProfile,TUBE2H,TUBE2H8M TUBE2H16M)) -$(eval $(call MultiProfile,UBNT,UBNTAIRROUTER UBNTRS UBNTRSPRO UBNTLSSR71 UBNTBULLETM UBNTROCKETM UBNTROCKETMXW UBNTNANOM UBNTNANOMXW UBNTLOCOXW UBNTUNIFI UBNTUNIFIOUTDOOR UBNTUNIFIOUTDOORPLUS UAPPRO UBNTAIRGW)) +$(eval $(call MultiProfile,UBNT,UBNTAIRROUTER UBNTRS UBNTRSPRO UBNTLSSR71 UBNTBULLETM UBNTROCKETM UBNTROCKETMXW UBNTNANOM UBNTNANOMXW UBNTLOCOXW UBNTUNIFI UBNTUNIFIOUTDOOR UAPPRO UBNTAIRGW)) $(eval $(call MultiProfile,WNR612V2,REALWNR612V2 N150R))