diff mbox

[OpenWrt-Devel] ar71xx: add support for the UniFi AP Outdoor Plus

Message ID 5565A0FC.4010403@rol.ru
State Changes Requested
Headers show

Commit Message

Kirill Berezin May 27, 2015, 10:48 a.m. UTC
Hi,

Well, this was my bad )) I accidentally chose cpu's gpio and realised 
that this is a mistake yesterday night.

So I made a patch for atheros driver and it works. A signal level on 
different frequencies is more or less equal and seems adequate (about 
-30 dbm if a client within a meter from ap).

The patch for bb 14.07 is in the attachment. It should be placed in 
package/kernel/mac80211/patches . The code generates a lot of messages, 
so it'll be easy to track what happens.


Kirill

On 05/26/2015 11:25 PM, Stefan Rompf wrote:

> Until I found that I was accessing GPIO lines of the CPU (cat
> /sys/devices/virtual/gpio/gpiochip0/label => ath79), not of the 928x wifi
> chip. It seems that ath9k does not even export its pins to the GPIO subsystem.
>
> Stefan
>

Comments

Stefan Rompf May 27, 2015, 8:48 p.m. UTC | #1
Hi Kirill,

> So I made a patch for atheros driver and it works. A signal level on
> different frequencies is more or less equal and seems adequate (about
> -30 dbm if a client within a meter from ap).

so you reverse engineered the timer values, the response format and the 's' 
command I had planned looking at next weekend. Very cool work!

Now that talking to the HSR seems mostly solved, this makes me think about 
OpenWrt integration. Currently I see two possibities:

Integration into the kernel driver like your latest patch does.

Caveat: The build process seems to assume that all routers in the 
ar71xx/generic target share the same set of kernel modules. So patching and 
compiling yourself is no problem, but a working image on downloads.openwrt.org 
is not feasible as every router would get the HSR driver. Correct me if I am 
wrong.

Or making ath9k expose the gpio and driving from user space. A generic patch 
for ath9k to register its gpio pins via the standard gpio subsystem. This 
should go upstream to the kernel and may prove usable for other access points 
to.

An Unifi specific user space daemon listens to scan and channel change events 
via netlink (will check next weekend if channel change is broadcasted 
reliably) and tune the filter via sysfs interface accordingly like hsr.c 
tried.

Caveat: Changing ath9k driver is possibly stuff for Designated Driver. May be 
the user space component can fall back to bit banging for Chaos Calmer.

What do you think? What do OpenWrt core developers think? I'd prefer exposing 
gpio and driving from userspace.

Stefan

PS: I've started working on the wiki page 
http://wiki.openwrt.org/toh/ubiquiti/unifi_outdoorplus that I hope to move to 
"supported" soon ;-)
Stefan Rompf June 1, 2015, 11:26 p.m. UTC | #2
Hi Kirill,

> > An Unifi specific user space daemon listens to scan and channel change
> > events via netlink (will check next weekend if channel change is
> > broadcasted reliably) and tune the filter via sysfs interface
> > accordingly like hsr.c tried.
> 
> I like this idea because in this case  it'll be much easier to correct
> problems or add changes.

I cannot get events for userspace triggered channel changes from nl80211, 
there seems no way to realize my userspace daemon idea other than by ugly 
polling.

So your driver patch is clearly the way to go. I'll test with chaos calmer but 
I have no doubt that it will work. Care to push it for inclusion?

> > PS: I've started working on the wiki page
> > http://wiki.openwrt.org/toh/ubiquiti/unifi_outdoorplus that I hope to
> > move to "supported" soon ;-)
> 
> I have a couple photos of pcb (one with annotated serial port), can send
> them if you need.

Please, do so or just include them into the wiki page.

Stefan
Felix Fietkau June 1, 2015, 11:43 p.m. UTC | #3
On 2015-05-27 22:48, Stefan Rompf wrote:
> Hi Kirill,
> 
>> So I made a patch for atheros driver and it works. A signal level on
>> different frequencies is more or less equal and seems adequate (about
>> -30 dbm if a client within a meter from ap).
> 
> so you reverse engineered the timer values, the response format and the 's' 
> command I had planned looking at next weekend. Very cool work!
> 
> Now that talking to the HSR seems mostly solved, this makes me think about 
> OpenWrt integration. Currently I see two possibities:
> 
> Integration into the kernel driver like your latest patch does.
> 
> Caveat: The build process seems to assume that all routers in the 
> ar71xx/generic target share the same set of kernel modules. So patching and 
> compiling yourself is no problem, but a working image on downloads.openwrt.org 
> is not feasible as every router would get the HSR driver. Correct me if I am 
> wrong.
> 
> Or making ath9k expose the gpio and driving from user space. A generic patch 
> for ath9k to register its gpio pins via the standard gpio subsystem. This 
> should go upstream to the kernel and may prove usable for other access points 
> to.
> 
> An Unifi specific user space daemon listens to scan and channel change events 
> via netlink (will check next weekend if channel change is broadcasted 
> reliably) and tune the filter via sysfs interface accordingly like hsr.c 
> tried.
> 
> Caveat: Changing ath9k driver is possibly stuff for Designated Driver. May be 
> the user space component can fall back to bit banging for Chaos Calmer.
> 
> What do you think? What do OpenWrt core developers think? I'd prefer exposing 
> gpio and driving from userspace.
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.

- Felix
diff mbox

Patch

diff -urN old/drivers/net/wireless/ath/ath9k/hsr.c new/drivers/net/wireless/ath/ath9k/hsr.c
--- old/drivers/net/wireless/ath/ath9k/hsr.c	1970-01-01 03:00:00.000000000 +0300
+++ new/drivers/net/wireless/ath/ath9k/hsr.c	2015-05-27 12:47:43.000000000 +0300
@@ -0,0 +1,236 @@ 
+/*
+ *
+ * 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 <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"
+
+#include "hsr.h"
+
+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");
+
+}
+EXPORT_SYMBOL(hsr_init);
+
+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);  
+	udelay(HSR_DELAY_FINAL);
+
+	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;
+}
+
+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;
+}
+EXPORT_SYMBOL(hsr_disable);
+
+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;
+}
+EXPORT_SYMBOL(hsr_enable);
+
+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;
+}
+EXPORT_SYMBOL(hsr_status);
diff -urN old/drivers/net/wireless/ath/ath9k/hsr.h new/drivers/net/wireless/ath/ath9k/hsr.h
--- old/drivers/net/wireless/ath/ath9k/hsr.h	1970-01-01 03:00:00.000000000 +0300
+++ new/drivers/net/wireless/ath/ath9k/hsr.h	2015-05-27 11:30:16.000000000 +0300
@@ -0,0 +1,44 @@ 
+/*
+ * 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_
+
+#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 hsr_init(struct ath_hw* ah);
+int hsr_disable(struct ath_hw* ah);
+int hsr_enable(struct ath_hw* ah, int bw, int fq);
+int hsr_status(struct ath_hw* ah);
+
+#endif /* HSR_H_ */
diff -urN old/drivers/net/wireless/ath/ath9k/main.c new/drivers/net/wireless/ath/ath9k/main.c
--- old/drivers/net/wireless/ath/ath9k/main.c	2015-04-22 11:20:18.000000000 +0300
+++ new/drivers/net/wireless/ath/ath9k/main.c	2015-05-27 11:58:38.000000000 +0300
@@ -18,6 +18,7 @@ 
 #include <linux/delay.h>
 #include "ath9k.h"
 #include "btcoex.h"
+#include "hsr.h"
 
 static void ath9k_set_assoc_state(struct ath_softc *sc,
 				  struct ieee80211_vif *vif);
@@ -353,6 +354,9 @@ 
 	ath_dbg(common, CONFIG, "Set channel: %d MHz width: %d\n",
 		chan->center_freq, chandef->width);
 
+	hsr_enable(ah, chandef->width, chan->center_freq);
+	hsr_status(ah);
+
 	/* update survey stats for the old channel before switching */
 	spin_lock_bh(&common->cc_lock);
 	ath_update_survey_stats(sc);
@@ -793,6 +797,9 @@ 
 		ath9k_hw_set_gpio(ah, ah->led_pin, 0);
 	}
 
+	hsr_init(ah);
+	hsr_disable(ah);
+
 	/*
 	 * Reset key cache to sane defaults (all entries cleared) instead of
 	 * semi-random values after suspend/resume.
diff -urN old/drivers/net/wireless/ath/ath9k/Makefile new/drivers/net/wireless/ath/ath9k/Makefile
--- old/drivers/net/wireless/ath/ath9k/Makefile	2015-04-22 11:20:18.000000000 +0300
+++ new/drivers/net/wireless/ath/ath9k/Makefile	2015-05-27 08:35:46.000000000 +0300
@@ -41,7 +41,8 @@ 
 		ar9002_mac.o \
 		ar9003_mac.o \
 		ar9003_eeprom.o \
-		ar9003_paprd.o
+		ar9003_paprd.o \
+		hsr.o
 
 ath9k_hw-$(CPTCFG_ATH9K_WOW) += ar9003_wow.o