Add kmod-ubnt-hsr package that tunes the HSR filter of the access point as a ath9k channel change helper
Signed-off-by: Stefan Rompf <ste...@loplof.de> --- Index: package/kernel/ubnt-hsr/Makefile =================================================================== --- package/kernel/ubnt-hsr/Makefile (Revision 0) +++ package/kernel/ubnt-hsr/Makefile (Arbeitskopie) @@ -0,0 +1,56 @@ +# +# Copyright (C) 2015 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=ubnt_hsr +PKG_VERSION:=0.1 +PKG_RELEASE:=1 +PKG_BUILD_DEPENDS:=mac80211 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/ubnt-hsr + $(call KernelPackage/mac80211/Default) + SUBMENU:=Wireless Drivers + TITLE:=Driver for 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)/ubnt_hsr.ko + AUTOLOAD:=$(call AutoProbe,ubnt_hsr) +endef + +define KernelPackage/ubnt-hsr/description +ubnt-hsr adds support for the 'High-Selectivity Receiver' +RF filter built into the receive path of the Ubiquiti +UniFi Outdoor Plus access point. It is required for this and +only for this access point. +endef + +include $(INCLUDE_DIR)/kernel-defaults.mk + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + cp src/Makefile src/ubnt_hsr.c $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) $(KERNEL_MAKEOPTS) SUBDIRS="$(PKG_BUILD_DIR)" \ + LINUXINCLUDE="-I$(STAGING_DIR)/usr/include/mac80211-backport/uapi -I$(STAGING_DIR)/usr/include/mac80211-backport \ + -I$(STAGING_DIR)/usr/include/mac80211/uapi -I$(STAGING_DIR)/usr/include/mac80211 \ + -I$(LINUX_DIR)/include -I$(LINUX_DIR)/include/$(LINUX_UAPI_DIR) \ + -I$(LINUX_DIR)/include/generated/uapi/ -Iarch/$(LINUX_KARCH)/include \ + -Iarch/$(LINUX_KARCH)/include/$(LINUX_UAPI_DIR) \ + -Iarch/$(LINUX_KARCH)/include/generated \ + -Iarch/$(LINUX_KARCH)/include/generated/$(LINUX_UAPI_DIR) \ + -include generated/autoconf.h \ + -include backport/backport.h " \ + V="${V}" modules +endef + +$(eval $(call KernelPackage,ubnt-hsr)) Index: package/kernel/ubnt-hsr/src/Makefile =================================================================== --- package/kernel/ubnt-hsr/src/Makefile (Revision 0) +++ package/kernel/ubnt-hsr/src/Makefile (Arbeitskopie) @@ -0,0 +1 @@ +obj-m := ubnt_hsr.o Index: package/kernel/ubnt-hsr/src/ubnt_hsr.c =================================================================== --- package/kernel/ubnt-hsr/src/ubnt_hsr.c (Revision 0) +++ package/kernel/ubnt-hsr/src/ubnt_hsr.c (Arbeitskopie) @@ -0,0 +1,283 @@ +/* + * + * 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 <ath/ath9k/hw.h> +#include <ath/ath9k/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; + 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) { + printk(KERN_WARNING "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; +} + +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"); + _______________________________________________ openwrt-devel mailing list openwrt-devel@lists.openwrt.org https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel