prompt "Firmware-supported ampdu scheduling"
depends on CARL9170FW_EXPERIMENTAL
+config CARL9170FW_WOL
+ def_bool n
+ prompt "Wakeup on WLAN"
+ depends on CARL9170FW_EXPERIMENTAL
+ ---help---
+ With this option enabled, the firmware can wake-up
+ suspended hosts... As long as they fully support
+ USB remote wakeup.
+
config CARL9170FW_VIFS_NUM
default 1
int
CARL9170_MAC_RESET_FORCE,
};
+enum carl9170_suspend_mode {
+ CARL9170_HOST_AWAKE = 0,
+ CARL9170_HOST_SUSPENDED,
+ CARL9170_AWAKE_HOST,
+};
+
/*
* This platform - being an odd 32-bit architecture - prefers to
* have 32-Bit variables.
/* misc */
unsigned int watchdog_enable;
unsigned int reboot;
+ unsigned int suspend_mode;
struct {
/* Host Interface DMA queues */
struct {
unsigned int config,
interface_setting,
- alternate_interface_setting;
+ alternate_interface_setting,
+ device_feature;
enum carl9170_ep0_action ep0_action;
void *ep0_txrx_buffer;
AR9170_USB_DEVICE_ADDRESS_CONFIGURE);
}
-static inline __inline void usb_remote_wakeup(void)
+static inline __inline void usb_enable_remote_wakeup(void)
{
orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_REMOTE_WAKEUP);
}
+static inline __inline void usb_disable_remote_wakeup(void)
+{
+ andb(AR9170_USB_REG_MAIN_CTRL, ~AR9170_USB_MAIN_CTRL_REMOTE_WAKEUP);
+}
+
static inline __inline void usb_enable_global_int(void)
{
orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_ENABLE_GLOBAL_INT);
const unsigned int bcn_addr,
const unsigned int bcn_len);
+static inline void wlan_prepare_wol(void)
+{
+ /* set filter policy to: discard everything */
+ fw.wlan.rx_filter = CARL9170_RX_FILTER_EVERYTHING;
+
+ /* reenable rx dma */
+ wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
+}
+
static inline void __check_wlantx(void)
{
BUILD_BUG_ON(CARL9170_TX_SUPERDESC_LEN & 3);
BIT(CARL9170FW_PSM) |
#endif /* CONFIG_CARL9170FW_PSM */
BIT(CARL9170FW_RX_FILTER) |
+#ifdef CONFIG_CARL9170FW_WOL
+ BIT(CARL9170FW_WOL) |
+#endif /* CONFIG_CARL9170FW_WOL */
(0)),
.miniboot_size = cpu_to_le16(0),
#include "printf.h"
#include "gpio.h"
#include "wl.h"
+#include "usb.h"
#define AR9170_WATCH_DOG_TIMER 0x100
gpio_timer();
#endif /* CONFIG_CARL9170FW_GPIO_INTERRUPT */
+ usb_timer();
+
#ifdef CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT
set(AR9170_GPIO_REG_PORT_DATA, get(AR9170_GPIO_REG_PORT_DATA) ^ 1);
#endif /* CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT */
#include "printf.h"
#include "rf.h"
#include "linux/ieee80211.h"
+#include "rom.h"
static void wlan_txunstuck(unsigned int queue)
{
}
}
+#ifdef CONFIG_CARL9170FW_WOL
+static void wlan_rx_wol(struct ieee80211_hdr *hdr, unsigned int len)
+{
+ const unsigned char *data, *end, *mac;
+ unsigned int found = 0;
+
+ /*
+ * LIMITATION:
+ * We can only scan the first AR9170_BLOCK_SIZE [=~320] bytes
+ * for MAGIC patterns!
+ */
+
+ /*
+ * TODO:
+ * Currently, the MAGIC MAC Address is fixed to the EEPROM default.
+ * It's possible to make it fully configurable, e.g:
+ *
+ * mac = (const unsigned char *) AR9170_MAC_REG_MAC_ADDR_L;
+ * But this will clash with the driver's suspend path, because it
+ * needs to reset the registers.
+ */
+ mac = rom.sys.mac_address;
+
+ data = (u8 *)((unsigned long)hdr + ieee80211_hdrlen(hdr->frame_control));
+ end = (u8 *)((unsigned long)hdr + len);
+
+ /*
+ * scan for standard WOL Magic frame
+ *
+ * "A physical WakeOnLAN (Magic Packet) will look like this:
+ * ---------------------------------------------------------------
+ * | Synchronization Stream | Target MAC | Password (optional) |
+ * | 6 octets | 96 octets | 0, 4 or 6 |
+ * ---------------------------------------------------------------
+ *
+ * The Synchronization Stream is defined as 6 bytes of FFh.
+ * The Target MAC block contains 16 duplications of the IEEEaddress
+ * of the target, with no breaks or interruptions.
+ *
+ * The Password field is optional, but if present, contains either
+ * 4 bytes or 6 bytes. The WakeOnLAN dissector was implemented to
+ * dissect the password, if present, according to the command-line
+ * format that ether-wake uses, therefore, if a 4-byte password is
+ * present, it will be dissected as an IPv4 address and if a 6-byte
+ * password is present, it will be dissected as an Ethernet address.
+ *
+ * <http://wiki.wireshark.org/WakeOnLAN>
+ */
+
+ while (data < end) {
+ if (found >= 6) {
+ if (*data == mac[found % 6])
+ found++;
+ else
+ found = 0;
+ }
+
+ /* previous check might reset found counter */
+ if (found < 6) {
+ if (*data == 0xff)
+ found++;
+ else
+ found = 0;
+ }
+
+ if (found == (6 + 16 * 6)) {
+ fw.suspend_mode = CARL9170_AWAKE_HOST;
+ return;
+ }
+
+ data++;
+ }
+
+ return;
+}
+#endif /* CONFIG_CARL9170FW_WOL */
+
static unsigned int wlan_rx_filter(struct dma_desc *desc)
{
struct ieee80211_hdr *hdr;
rx_filter |= CARL9170_RX_FILTER_MGMT;
}
+#ifdef CONFIG_CARL9170FW_WOL
+ if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) {
+ if (rx_filter & CARL9170_RX_FILTER_DATA)
+ wlan_rx_wol(hdr, min(data_len,
+ (unsigned int)AR9170_BLOCK_SIZE));
+ }
+#endif /* CONFIG_CARL9170FW_WOL */
+
#undef AR9170_RX_ERROR_BAD
return rx_filter;
#include "printf.h"
#include "timer.h"
#include "rom.h"
+#include "wl.h"
#include "shared/phy.h"
#ifdef CONFIG_CARL9170FW_DEBUG_USB
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_SUSPEND) {
usb_suspend_ack();
- disable_watchdog();
+ fw.suspend_mode = CARL9170_HOST_SUSPENDED;
- /* GO_TO_SUSPEND stops the CPU clock too. */
- orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND);
+ if (!(fw.usb.device_feature & USB_DEVICE_REMOTE_WAKEUP)) {
+ disable_watchdog();
+
+ /* GO_TO_SUSPEND stops the CPU clock too. */
+ orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND);
+ } else {
+ wlan_prepare_wol();
+ }
}
if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_RESUME) {
+ fw.suspend_mode = CARL9170_HOST_AWAKE;
+ andl(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE);
+
usb_resume_ack();
reboot();
}
usb_trigger_in();
}
+void usb_timer(void)
+{
+#ifdef CONFIG_CARL9170FW_WOL
+ if (fw.suspend_mode == CARL9170_AWAKE_HOST) {
+ orl(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE);
+ }
+#endif /* CONFIG_CARL9170FW_WOL */
+}
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
- .bmAttributes = USB_CONFIG_ATT_ONE,
+ .bmAttributes = USB_CONFIG_ATT_ONE |
+#ifdef CONFIG_CARL9170FW_WOL
+ USB_CONFIG_ATT_WAKEUP |
+#endif /* CONFIG_CARL9170FW_WOL */
+ 0,
.bMaxPower = 0xfa, /* 500 mA */
},
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
- .bmAttributes = USB_CONFIG_ATT_ONE,
+ .bmAttributes = USB_CONFIG_ATT_ONE |
+#ifdef CONFIG_CARL9170FW_WOL
+ USB_CONFIG_ATT_WAKEUP |
+#endif /* CONFIG_CARL9170FW_WOL */
+ 0,
.bMaxPower = 0xfa, /* 500 mA */
},
*
* fw.usb.interface_setting = 0;
* fw.usb.alternate_interface_setting = 0;
+ * fw.usb.device_feature = 0;
*/
+
+#ifdef CONFIG_CARL9170FW_WOL
+ fw.usb.device_feature |= USB_DEVICE_REMOTE_WAKEUP;
+ usb_enable_remote_wakeup();
+#endif /* CONFIG_CARL9170FW_WOL */
}
#define GET_ARRAY(a, o) ((uint32_t *) (((unsigned long) data) + offset))
#ifdef CONFIG_CARL9170FW_USB_STANDARD_CMDS
static int usb_get_status(const struct usb_ctrlrequest *ctrl)
{
- __le16 status = cpu_to_le16(0);
+ __le16 status = cpu_to_le16(fw.usb.device_feature);
if ((ctrl->bRequestType & USB_DIR_MASK) != USB_DIR_IN)
return -1;
return usb_ep0tx_data(&fw.usb.alternate_interface_setting, 1);
}
+static int usb_manipulate_feature(const struct usb_ctrlrequest *ctrl, bool __unused clear)
+{
+ unsigned int feature;
+ if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_OUT))
+ return -1;
+
+ if (usb_configured() == false)
+ return -1;
+
+ feature = le16_to_cpu(ctrl->wValue);
+
+#ifdef CONFIG_CARL9170FW_WOL
+ if (feature & USB_DEVICE_REMOTE_WAKEUP) {
+ if (clear)
+ usb_disable_remote_wakeup();
+ else
+ usb_enable_remote_wakeup();
+ }
+#endif /* CONFIG_CARL9170FW_WOL */
+
+ if (clear)
+ fw.usb.device_feature &= ~feature;
+ else
+ fw.usb.device_feature |= feature;
+
+ return 1;
+}
+
#ifdef CONFIG_CARL9170FW_USB_MODESWITCH
static int usb_set_interface(const struct usb_ctrlrequest *ctrl)
{
break;
case USB_REQ_CLEAR_FEATURE:
- break;
-
case USB_REQ_SET_FEATURE:
+ usb_manipulate_feature(ctrl, ctrl->bRequest == USB_REQ_CLEAR_FEATURE);
break;
case USB_REQ_SET_ADDRESS:
CHECK_FOR_FEATURE(CARL9170FW_GPIO_INTERRUPT),
CHECK_FOR_FEATURE(CARL9170FW_PSM),
CHECK_FOR_FEATURE(CARL9170FW_RX_FILTER),
+ CHECK_FOR_FEATURE(CARL9170FW_WOL),
};
static void check_feature_list(const struct carl9170fw_desc_head *head,