[OpenWrt-Devel] [PATCH RFC] Add support for Ubiquiti Unifi Outdoor Plus

Stefan Rompf stefan at loplof.de
Sat Jun 6 09:28:42 EDT 2015


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))
_______________________________________________
openwrt-devel mailing list
openwrt-devel at lists.openwrt.org
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel



More information about the openwrt-devel mailing list