From patchwork Wed Jun 3 20:36:38 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Rompf X-Patchwork-Id: 480151 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from arrakis.dune.hu (arrakis.dune.hu [78.24.191.176]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3561C1402A7 for ; Thu, 4 Jun 2015 06:39:30 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=loplof.de header.i=@loplof.de header.b=cJE6VgnV; dkim-atps=neutral Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 559D228C045; Wed, 3 Jun 2015 22:37:47 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on arrakis.dune.hu X-Spam-Level: X-Spam-Status: No, score=-1.5 required=5.0 tests=BAYES_00,T_DKIM_INVALID autolearn=unavailable version=3.3.2 Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id CB23C28C061 for ; Wed, 3 Jun 2015 22:37:29 +0200 (CEST) X-policyd-weight: using cached result; rate: -8.5 Received: from mo4-p00-ob.smtp.rzone.de (mo4-p00-ob.smtp.rzone.de [81.169.146.218]) by arrakis.dune.hu (Postfix) with ESMTPS for ; Wed, 3 Jun 2015 22:37:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; t=1433363943; l=11528; s=domk; d=loplof.de; h=Content-Type:MIME-Version:In-Reply-To:References:Cc:Date:Subject:To: From; bh=mkKy2TRGAv7TPlGVnPW1PCbFylEdWYaE9cnjK1DDk+4=; b=cJE6VgnVoq1jXkG6YjVHb5jlrwyyTPWKFnBuojI9Lwge0pMHCb6dlTuwzyC7s457nYF aAad+biMxkpSYMAia7zEFv2WSR5zYpi3r5X64ZMef3dgmnvNqm6n1oz5NlT+M8URmCbbF 3VPWti2SiBT+mMSblbuKRCgAO5H8RdfWnvM= X-RZG-AUTH: :P3gBc0GmW/MnlBFpG8pTynGixNLZJ3Qpuna7EXLmjrAdigkKTIiwEDE= X-RZG-CLASS-ID: mo00 Received: from dose.localnet (x55b4b905.dyn.telefonica.de [85.180.185.5]) by smtp.strato.de (RZmta 37.6 DYNA|AUTH) with ESMTPA id q053aer53Kd2JAa; Wed, 3 Jun 2015 22:39:02 +0200 (CEST) From: Stefan Rompf To: Felix Fietkau , Kirill Berezin Date: Wed, 3 Jun 2015 22:36:38 +0200 User-Agent: KMail/1.13.6 (Linux/3.16.7-7-desktop; KDE/4.14.6; x86_64; ; ) References: <201505272248.21879.stefan@loplof.de> <556CEE08.7080206@openwrt.org> In-Reply-To: <556CEE08.7080206@openwrt.org> MIME-Version: 1.0 Message-Id: <201506032236.38911.stefan@loplof.de> Cc: ikhodyakov@arccn.ru, TSmelyanskiy@arccn.ru, openwrt-devel@lists.openwrt.org, smonin@arccn.ru Subject: Re: [OpenWrt-Devel] ar71xx: How to integrate UniFi AP Outdoor Plus HSR? X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openwrt-devel-bounces@lists.openwrt.org Sender: "openwrt-devel" Hi Felix, > I think deferring this to user space might mess up timings during fast > active scans. I'd prefer to have a callback in ath9k_platform.h that > allows the platform code to provide a hook which implements this. > That way the changes to ath9k stay very small, and the code is still > quite simple. this would move the hsr driver into the kernel image, wouldn't it? That's IMHO a maintenance nightmare as it would force us to flash a new image whenever we want to update or even debug the driver. What about this solution: -Add the possibility to register a channel change helper in ath9k.ko. As with your idea, this is only a very small patch that all ar71xx targets would have to carry. -Make the hsr driver a channel change helper .ko -Install the hsr kernel module only into the UniFi Outdoor Plus image. Like in the attached RFC patch (that has be applied manually). Stefan diff -Nur linux/drivers/net/wireless/ath9k.orig/ath/ath9k.h linux/drivers/net/wireless/ath9k/ath/ath9k.h --- linux/drivers/net/wireless/ath/ath9k.orig/ath9k.h 2015-04-04 04:46:37.000000000 +0200 +++ linux/drivers/net/wireless/ath/ath9k/ath9k.h 2015-06-03 18:37:29.000000000 +0200 @@ -1098,4 +1098,10 @@ 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 -Nur linux/drivers/net/wireless/ath/ath9k.orig/channel.c linux/drivers/net/wireless/ath/ath9k/channel.c --- linux/drivers/net/wireless/ath/ath9k.orig/channel.c 2015-04-04 04:46:37.000000000 +0200 +++ linux/drivers/net/wireless/ath/ath9k/channel.c 2015-06-03 18:42:28.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 @@ 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 -Nur linux/drivers/net/wireless/ath/ath9k.orig/hsr.c linux/drivers/net/wireless/ath/ath9k/hsr.c --- linux/drivers/net/wireless/ath/ath9k.orig/hsr.c 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/net/wireless/ath/ath9k/hsr.c 2015-06-03 18:45:04.000000000 +0200 @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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) { + + if ( NULL == ah) { + return; + } + + 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; + int loops = 0; + // a preabmle + hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); + + status = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); + + if ( status) { + int loop = 2; + ++ loops; + if ( loops > 42) { + printk(KERN_NOTICE "hsr_write_a_chain: too many loops in preamble. giving up.\n"); + return 0; + } + do { + status = hsr_write_byte(ah, HSR_DELAY_PRE_WRITE, 0); + loop = (loop + 1) & 0xffff; + if ( loop < 2) { + continue; + } + } 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; + + if ( NULL == ah) { + return 0; + } + + 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; +} + +static int hsr_enable(struct ath_hw* ah, int bw, int fq) { + char cmd[10]; + int ret; + + if ( NULL == ah) { + return 0; + } + + + 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)) { + printk(KERN_NOTICE "hsr_enable: failed changing bandwidth -> set (%d,%d) reply (%d, %d) \n", 'b', bw, *cmd, ret); + return 0; + } + + 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 0; + } + + 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 0; + } + + 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 0; + } + + 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; + if ( NULL == ah) { + return 0; + } + + 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; + + if (!initialized) { + initialized = 1; + hsr_init(ah); + } + hsr_enable(ah, bw, fq); + hsr_status(ah); +} + + + +static int __init hsr_mod_init(void) +{ + rtnl_lock(); /* Should lock against nl80211_set_channel() */ + ath9k_register_set_channel_helper(hsr_tune); + rtnl_unlock(); +} + +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("GPL"); + diff -Nur linux/drivers/net/wireless/ath/ath9k.orig/Makefile linux/drivers/net/wireless/ath/ath9k/Makefile --- linux/drivers/net/wireless/ath/ath9k.orig/Makefile 2015-04-04 04:46:37.000000000 +0200 +++ linux/drivers/net/wireless/ath/ath9k/Makefile 2015-06-03 18:37:29.000000000 +0200 @@ -23,6 +23,10 @@ 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 \