diff options
Diffstat (limited to 'carl9170fw/carlfw/src')
-rw-r--r-- | carl9170fw/carlfw/src/ashlsi3.S | 193 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/cam.c | 161 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/cmd.c | 154 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/dma.c | 255 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/fw.c | 116 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/gpio.c | 52 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/hostif.c | 165 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/main.c | 256 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/memcpy.S | 228 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/memset.S | 58 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/printf.c | 161 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/reboot.S | 11 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/rf.c | 277 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/uart.c | 76 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/udivsi3_i4i-Os.S | 149 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/wlan.c | 1211 | ||||
-rw-r--r-- | carl9170fw/carlfw/src/wol.c | 287 |
17 files changed, 3810 insertions, 0 deletions
diff --git a/carl9170fw/carlfw/src/ashlsi3.S b/carl9170fw/carlfw/src/ashlsi3.S new file mode 100644 index 0000000..593d902 --- /dev/null +++ b/carl9170fw/carlfw/src/ashlsi3.S @@ -0,0 +1,193 @@ +/* Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003, + 2004, 2005, 2006 + Free Software Foundation, Inc. + +This file is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +In addition to the permissions in the GNU General Public License, the +Free Software Foundation gives you unlimited permission to link the +compiled version of this file into combinations with other programs, +and to distribute those combinations without any restriction coming +from the use of this file. (The General Public License restrictions +do apply in other respects; for example, they cover modification of +the file, and distribution when not linked into a combine +executable.) + +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. */ + +!! libgcc routines for the Renesas / SuperH SH CPUs. +!! Contributed by Steve Chamberlain. +!! sac@cygnus.com + +!! ashiftrt_r4_x, ___ashrsi3, ___ashlsi3, ___lshrsi3 routines +!! recoded in assembly by Toshiyasu Morita +!! tm@netcom.com + +/* SH2 optimizations for ___ashrsi3, ___ashlsi3, ___lshrsi3 and + ELF local label prefixes by J"orn Rennecke + amylaar@cygnus.com */ + +! +! __ashlsi3 +! +! Entry: +! +! r4: Value to shift +! r5: Shifts +! +! Exit: +! +! r0: Result +! +! Destroys: +! +! (none) +! + .global ___ashlsi3 + + .align 2 +___ashlsi3: + mov #31,r0 + and r0,r5 + mova ashlsi3_table,r0 + mov.b @(r0,r5),r5 +#ifdef __sh1__ + add r5,r0 + jmp @r0 +#else + braf r5 +#endif + mov r4,r0 + + .align 2 +ashlsi3_table: + .byte ashlsi3_0-ashlsi3_table + .byte ashlsi3_1-ashlsi3_table + .byte ashlsi3_2-ashlsi3_table + .byte ashlsi3_3-ashlsi3_table + .byte ashlsi3_4-ashlsi3_table + .byte ashlsi3_5-ashlsi3_table + .byte ashlsi3_6-ashlsi3_table + .byte ashlsi3_7-ashlsi3_table + .byte ashlsi3_8-ashlsi3_table + .byte ashlsi3_9-ashlsi3_table + .byte ashlsi3_10-ashlsi3_table + .byte ashlsi3_11-ashlsi3_table + .byte ashlsi3_12-ashlsi3_table + .byte ashlsi3_13-ashlsi3_table + .byte ashlsi3_14-ashlsi3_table + .byte ashlsi3_15-ashlsi3_table + .byte ashlsi3_16-ashlsi3_table + .byte ashlsi3_17-ashlsi3_table + .byte ashlsi3_18-ashlsi3_table + .byte ashlsi3_19-ashlsi3_table + .byte ashlsi3_20-ashlsi3_table + .byte ashlsi3_21-ashlsi3_table + .byte ashlsi3_22-ashlsi3_table + .byte ashlsi3_23-ashlsi3_table + .byte ashlsi3_24-ashlsi3_table + .byte ashlsi3_25-ashlsi3_table + .byte ashlsi3_26-ashlsi3_table + .byte ashlsi3_27-ashlsi3_table + .byte ashlsi3_28-ashlsi3_table + .byte ashlsi3_29-ashlsi3_table + .byte ashlsi3_30-ashlsi3_table + .byte ashlsi3_31-ashlsi3_table + +ashlsi3_6: + shll2 r0 +ashlsi3_4: + shll2 r0 +ashlsi3_2: + rts + shll2 r0 + +ashlsi3_7: + shll2 r0 +ashlsi3_5: + shll2 r0 +ashlsi3_3: + shll2 r0 +ashlsi3_1: + rts + shll r0 + +ashlsi3_14: + shll2 r0 +ashlsi3_12: + shll2 r0 +ashlsi3_10: + shll2 r0 +ashlsi3_8: + rts + shll8 r0 + +ashlsi3_15: + shll2 r0 +ashlsi3_13: + shll2 r0 +ashlsi3_11: + shll2 r0 +ashlsi3_9: + shll8 r0 + rts + shll r0 + +ashlsi3_22: + shll2 r0 +ashlsi3_20: + shll2 r0 +ashlsi3_18: + shll2 r0 +ashlsi3_16: + rts + shll16 r0 + +ashlsi3_23: + shll2 r0 +ashlsi3_21: + shll2 r0 +ashlsi3_19: + shll2 r0 +ashlsi3_17: + shll16 r0 + rts + shll r0 + +ashlsi3_30: + shll2 r0 +ashlsi3_28: + shll2 r0 +ashlsi3_26: + shll2 r0 +ashlsi3_24: + shll16 r0 + rts + shll8 r0 + +ashlsi3_31: + shll2 r0 +ashlsi3_29: + shll2 r0 +ashlsi3_27: + shll2 r0 +ashlsi3_25: + shll16 r0 + shll8 r0 + rts + shll r0 + +ashlsi3_0: + rts + nop diff --git a/carl9170fw/carlfw/src/cam.c b/carl9170fw/carlfw/src/cam.c new file mode 100644 index 0000000..7c0e61c --- /dev/null +++ b/carl9170fw/carlfw/src/cam.c @@ -0,0 +1,161 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * Security Engine + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "cam.h" + +#ifdef CONFIG_CARL9170FW_SECURITY_ENGINE +static void disable_cam_user(const uint16_t userId) +{ + if (userId <= 31) + andl(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L, (~((uint32_t) 1 << userId))); + else if (userId <= 63) + andl(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H, (~((uint32_t) 1 << (userId - 32)))); +} + +static void enable_cam_user(const uint16_t userId) +{ + if (userId <= 31) + orl(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L, (((uint32_t) 1) << userId)); + else if (userId <= 63) + orl(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H, (((uint32_t) 1) << (userId - 32))); +} + +static void wait_for_cam_read_ready(void) +{ + while ((get(AR9170_MAC_REG_CAM_STATE) & AR9170_MAC_CAM_STATE_READ_PENDING) == 0) { + /* + * wait + */ + } +} + +static void wait_for_cam_write_ready(void) +{ + while ((get(AR9170_MAC_REG_CAM_STATE) & AR9170_MAC_CAM_STATE_WRITE_PENDING) == 0) { + /* + * wait some more + */ + } +} + +static void HW_CAM_Avail(void) +{ + uint32_t tmpValue; + + do { + tmpValue = get(AR9170_MAC_REG_CAM_MODE); + } while (tmpValue & AR9170_MAC_CAM_HOST_PENDING); +} + +static void HW_CAM_Write128(const uint32_t address, const uint32_t *data) +{ + HW_CAM_Avail(); + + set(AR9170_MAC_REG_CAM_DATA0, data[0]); + set(AR9170_MAC_REG_CAM_DATA1, data[1]); + set(AR9170_MAC_REG_CAM_DATA2, data[2]); + set(AR9170_MAC_REG_CAM_DATA3, data[3]); + + set(AR9170_MAC_REG_CAM_ADDR, address | AR9170_MAC_CAM_ADDR_WRITE); + + wait_for_cam_write_ready(); +} + +static void HW_CAM_Read128(const uint32_t address, uint32_t *data) +{ + + HW_CAM_Avail(); + set(AR9170_MAC_REG_CAM_ADDR, address); + + wait_for_cam_read_ready(); + HW_CAM_Avail(); + data[0] = get(AR9170_MAC_REG_CAM_DATA0); + data[1] = get(AR9170_MAC_REG_CAM_DATA1); + data[2] = get(AR9170_MAC_REG_CAM_DATA2); + data[3] = get(AR9170_MAC_REG_CAM_DATA3); +} + +void set_key(const struct carl9170_set_key_cmd *key) +{ + uint32_t data[4]; + uint16_t row, wordId, nibbleId, i; + + if (key->user > (AR9170_CAM_MAX_USER + 3)) + return ; + + if (key->keyId > 1) + return ; + + /* Disable Key */ + disable_cam_user(key->user); + + /* Set encrypt type */ + if (key->user >= AR9170_CAM_MAX_USER) { + /* default */ + row = DEFAULT_ENCRY_TYPE; + wordId = 0; + nibbleId = (key->user - AR9170_CAM_MAX_USER) & 0x7; + } else { + row = ENCRY_TYPE_START_ADDR + (key->user >> 5); + wordId = (key->user >> 3) & 0x3; + nibbleId = key->user & 0x7; + } + + HW_CAM_Read128(row, data); + data[wordId] &= (~(0xf << ((uint32_t) nibbleId * 4))); + data[wordId] |= (key->type << ((uint32_t) nibbleId * 4)); + HW_CAM_Write128(row, data); + + /* Set MAC address */ + if (key->user < AR9170_CAM_MAX_USER) { + uint16_t byteId; + wordId = (key->user >> 2) & 0x3; + byteId = key->user & 0x3; + row = (key->user >> 4) * 6; + + for (i = 0; i < 6; i++) { + HW_CAM_Read128(row + i, data); + data[wordId] &= (~(0xff << ((uint32_t) byteId * 8))); + data[wordId] |= (key->macAddr[i] << ((uint32_t) byteId * 8)); + HW_CAM_Write128(row + i, data); + } + } + + /* Set key */ + row = KEY_START_ADDR + (key->user * 2) + key->keyId; + + HW_CAM_Write128(row, key->key); + + /* Enable Key */ + enable_cam_user(key->user); +} + +void disable_key(const struct carl9170_disable_key_cmd *key) +{ + disable_cam_user(key->user); +} + +#endif /* CONFIG_CARL9170FW_SECURITY_ENGINE */ diff --git a/carl9170fw/carlfw/src/cmd.c b/carl9170fw/carlfw/src/cmd.c new file mode 100644 index 0000000..5382083 --- /dev/null +++ b/carl9170fw/carlfw/src/cmd.c @@ -0,0 +1,154 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * Code to handle commands from the host driver. + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "io.h" +#include "cam.h" +#include "rf.h" +#include "printf.h" +#include "timer.h" +#include "wl.h" +#include "wol.h" + +void handle_cmd(struct carl9170_rsp *resp) +{ + struct carl9170_cmd *cmd = &dma_mem.reserved.cmd.cmd; + unsigned int i; + + /* copies cmd, len and extra fields */ + resp->hdr.len = cmd->hdr.len; + resp->hdr.cmd = cmd->hdr.cmd; + resp->hdr.ext = cmd->hdr.ext; + resp->hdr.seq |= cmd->hdr.seq; + + switch (cmd->hdr.cmd & ~CARL9170_CMD_ASYNC_FLAG) { + case CARL9170_CMD_RREG: + for (i = 0; i < (cmd->hdr.len / 4); i++) + resp->rreg_res.vals[i] = get(cmd->rreg.regs[i]); + break; + + case CARL9170_CMD_WREG: + resp->hdr.len = 0; + for (i = 0; i < (cmd->hdr.len / 8); i++) + set(cmd->wreg.regs[i].addr, cmd->wreg.regs[i].val); + break; + + case CARL9170_CMD_ECHO: + memcpy(resp->echo.vals, cmd->echo.vals, cmd->hdr.len); + break; + + case CARL9170_CMD_SWRST: +#ifdef CONFIG_CARL9170FW_FW_MAC_RESET + /* + * Command has no payload, so the response + * has no payload either. + * resp->hdr.len = 0; + */ + fw.wlan.mac_reset = CARL9170_MAC_RESET_FORCE; +#endif /* CONFIG_CARL9170FW_FW_MAC_RESET */ + break; + + case CARL9170_CMD_REBOOT: + /* + * resp->len = 0; + */ + fw.reboot = 1; + break; + + case CARL9170_CMD_READ_TSF: + resp->hdr.len = 8; + read_tsf((uint32_t *)resp->tsf.tsf); + break; + + case CARL9170_CMD_RX_FILTER: + resp->hdr.len = 0; + fw.wlan.rx_filter = cmd->rx_filter.rx_filter; + break; + + case CARL9170_CMD_WOL: + wol_cmd(&cmd->wol); + break; + + case CARL9170_CMD_TALLY: + resp->hdr.len = sizeof(struct carl9170_tally_rsp); + memcpy(&resp->tally, &fw.tally, sizeof(struct carl9170_tally_rsp)); + resp->tally.tick = fw.ticks_per_usec; + memset(&fw.tally, 0, sizeof(struct carl9170_tally_rsp)); + break; + +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + case CARL9170_CMD_BCN_CTRL: + resp->hdr.len = 0; + + if (cmd->bcn_ctrl.mode & CARL9170_BCN_CTRL_CAB_TRIGGER) { + wlan_modify_beacon(cmd->bcn_ctrl.vif_id, + cmd->bcn_ctrl.bcn_addr, cmd->bcn_ctrl.bcn_len); + set(AR9170_MAC_REG_BCN_ADDR, cmd->bcn_ctrl.bcn_addr); + set(AR9170_MAC_REG_BCN_LENGTH, cmd->bcn_ctrl.bcn_len); + set(AR9170_MAC_REG_BCN_CTRL, AR9170_BCN_CTRL_READY); + } else { + wlan_cab_flush_queue(cmd->bcn_ctrl.vif_id); + fw.wlan.cab_flush_trigger[cmd->bcn_ctrl.vif_id] = CARL9170_CAB_TRIGGER_EMPTY; + } + break; +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + +#ifdef CONFIG_CARL9170FW_SECURITY_ENGINE + case CARL9170_CMD_EKEY: + resp->hdr.len = 0; + set_key(&cmd->setkey); + break; + + case CARL9170_CMD_DKEY: + resp->hdr.len = 0; + disable_key(&cmd->disablekey); + break; +#endif /* CONFIG_CARL9170FW_SECURITY_ENGINE */ + +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + case CARL9170_CMD_FREQUENCY: + case CARL9170_CMD_RF_INIT: + rf_cmd(cmd, resp); + break; + + case CARL9170_CMD_FREQ_START: + /* + * resp->hdr.len = 0; + */ + rf_notify_set_channel(); + break; + + case CARL9170_CMD_PSM: + resp->hdr.len = 0; + fw.phy.psm.state = le32_to_cpu(cmd->psm.state); + rf_psm(); + break; +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ + + default: + BUG("Unknown command %x\n", cmd->hdr.cmd); + break; + } +} diff --git a/carl9170fw/carlfw/src/dma.c b/carl9170fw/carlfw/src/dma.c new file mode 100644 index 0000000..9b83fcf --- /dev/null +++ b/carl9170fw/carlfw/src/dma.c @@ -0,0 +1,255 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * DMA descriptor handling functions + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "wl.h" +#include "printf.h" + +struct ar9170_dma_memory dma_mem __section(sram); + +static void copy_dma_desc(struct dma_desc *dst, + struct dma_desc *src) +{ + memcpy(dst, src, sizeof(struct dma_desc)); +} + +static void clear_descriptor(struct dma_desc *d) +{ + d->status = AR9170_OWN_BITS_SW; + d->ctrl = 0; + d->dataSize = 0; + d->totalLen = 0; + d->lastAddr = d; + d->dataAddr = NULL; + d->nextAddr = d; +} + +static void fill_descriptor(struct dma_desc *d, uint16_t size, uint8_t *data) +{ + d->status = AR9170_OWN_BITS_SW; + d->ctrl = 0; + d->dataSize = size; + d->totalLen = 0; + d->lastAddr = d; + d->dataAddr = data; + d->nextAddr = NULL; +} + +static void init_queue(struct dma_queue *q, struct dma_desc *d) +{ + q->head = q->terminator = d; +} + +/* + * - Init up_queue, down_queue, tx_queue[5], rx_queue. + * - Setup descriptors and data buffer address. + * - Ring descriptors rx_queue and down_queue by dma_reclaim(). + * + * NOTE: LastAddr tempary point (same) to nextAddr after initialize. + * Because LastAddr is don't care in function dma_reclaim(). + */ +void dma_init_descriptors(void) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(dma_mem.terminator); i++) + clear_descriptor(&dma_mem.terminator[i]); + + /* Assign terminators to DMA queues */ + i = 0; + init_queue(&fw.pta.up_queue, &dma_mem.terminator[i++]); + init_queue(&fw.pta.down_queue, &dma_mem.terminator[i++]); + for (j = 0; j < __AR9170_NUM_TX_QUEUES; j++) + init_queue(&fw.wlan.tx_queue[j], &dma_mem.terminator[i++]); + init_queue(&fw.wlan.tx_retry, &dma_mem.terminator[i++]); + init_queue(&fw.wlan.rx_queue, &dma_mem.terminator[i++]); + fw.usb.int_desc = &dma_mem.terminator[i++]; + fw.wlan.fw_desc = &dma_mem.terminator[i++]; + +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + for (j = 0; j < CARL9170_INTF_NUM; j++) + init_queue(&fw.wlan.cab_queue[j], &dma_mem.terminator[i++]); +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + + BUG_ON(AR9170_TERMINATOR_NUMBER != i); + + DBG("Blocks:%d [tx:%d, rx:%d] Terminators:%d/%d\n", + AR9170_BLOCK_NUMBER, AR9170_TX_BLOCK_NUMBER, + AR9170_RX_BLOCK_NUMBER, AR9170_TERMINATOR_NUMBER, i); + + /* Init descriptors and memory blocks */ + for (i = 0; i < AR9170_BLOCK_NUMBER; i++) { + fill_descriptor(&dma_mem.block[i], AR9170_BLOCK_SIZE, dma_mem.data[i].data); + + if (i < AR9170_TX_BLOCK_NUMBER) + dma_reclaim(&fw.pta.down_queue, &dma_mem.block[i]); + else + dma_reclaim(&fw.wlan.rx_queue, &dma_mem.block[i]); + } + + /* Set DMA address registers */ + set(AR9170_PTA_REG_DN_DMA_ADDRH, (uint32_t) fw.pta.down_queue.head >> 16); + set(AR9170_PTA_REG_DN_DMA_ADDRL, (uint32_t) fw.pta.down_queue.head & 0xffff); + set(AR9170_PTA_REG_UP_DMA_ADDRH, (uint32_t) fw.pta.up_queue.head >> 16); + set(AR9170_PTA_REG_UP_DMA_ADDRL, (uint32_t) fw.pta.up_queue.head & 0xffff); + + for (i = 0; i < __AR9170_NUM_TX_QUEUES; i++) + set_wlan_txq_dma_addr(i, (uint32_t) fw.wlan.tx_queue[i].head); + + set(AR9170_MAC_REG_DMA_RXQ_ADDR, (uint32_t) fw.wlan.rx_queue.head); + fw.usb.int_desc->dataSize = AR9170_BLOCK_SIZE; + fw.usb.int_desc->dataAddr = (void *) &dma_mem.reserved.rsp; + + memset(DESC_PAYLOAD(fw.usb.int_desc), 0xff, + AR9170_INT_MAGIC_HEADER_SIZE); + memset(DESC_PAYLOAD_OFF(fw.usb.int_desc, AR9170_INT_MAGIC_HEADER_SIZE), + 0, AR9170_BLOCK_SIZE - AR9170_INT_MAGIC_HEADER_SIZE); + + /* rsp is now available for use */ + fw.usb.int_desc_available = 1; + + memset(DESC_PAYLOAD(fw.wlan.fw_desc), 0, 128); + fw.wlan.fw_desc_available = 1; +} + +/* + * Free descriptor. + * + * Exchange the terminator and the first descriptor of the packet + * for hardware ascy... + */ +void dma_reclaim(struct dma_queue *q, struct dma_desc *desc) +{ + struct dma_desc *tmpDesc, *last; + struct dma_desc tdesc; + + /* 1. Set OWN bit to HW for all TDs to be added, clear ctrl and size */ + tmpDesc = desc; + last = desc->lastAddr; + + while (1) { + tmpDesc->status = AR9170_OWN_BITS_HW; + tmpDesc->ctrl = 0; + tmpDesc->totalLen = 0; + tmpDesc->dataSize = AR9170_BLOCK_SIZE; + + /* TODO : Exception handle */ + + tmpDesc->lastAddr = tmpDesc; + + if (tmpDesc == last) + break; + + tmpDesc = tmpDesc->nextAddr; + } + + /* 2. Next address of Last TD to be added = first TD */ + tmpDesc->nextAddr = desc; + + /* Link first TD to self */ + desc->lastAddr = q->terminator; + + /* 3. Copy first TD to be added to TTD */ + copy_dma_desc(&tdesc, desc); + + /* 4. Initialize new terminator */ + clear_descriptor(desc); + + /* 5. Copy TTD to last TD */ + tdesc.status = 0; + copy_dma_desc((void *)q->terminator, (void *)&tdesc); + q->terminator->status |= AR9170_OWN_BITS_HW; + + /* Update terminator pointer */ + q->terminator = desc; +} + +/* + * Put a complete packet into the tail of the Queue q. + * Exchange the terminator and the first descriptor of the packet + * for hardware ascy... + */ +void dma_put(struct dma_queue *q, struct dma_desc *desc) +{ + struct dma_desc *tmpDesc; + struct dma_desc tdesc; + + tmpDesc = desc; + + while (1) { + /* update totalLen */ + tmpDesc->totalLen = desc->totalLen; + + /* 1. Set OWN bit to HW for all TDs to be added */ + tmpDesc->status = AR9170_OWN_BITS_HW; + /* TODO : Exception handle */ + + tmpDesc->lastAddr = desc->lastAddr; + + if (desc->lastAddr == tmpDesc) + break; + + tmpDesc = tmpDesc->nextAddr; + } + + /* 2. Next address of Last TD to be added = first TD */ + desc->lastAddr->nextAddr = desc; + + /* If there is only one descriptor, update pointer of last descriptor */ + if (desc->lastAddr == desc) + desc->lastAddr = q->terminator; + + /* 3. Copy first TD to be added to TTD */ + copy_dma_desc(&tdesc, desc); + + /* 4. Initialize new terminator */ + clear_descriptor(desc); + + /* 5. Copy TTD to last TD */ + tdesc.status &= (~AR9170_OWN_BITS); + copy_dma_desc((void *)q->terminator, (void *)&tdesc); + q->terminator->status |= AR9170_OWN_BITS_HW; + + /* Update terminator pointer */ + q->terminator = desc; +} + +struct dma_desc *dma_unlink_head(struct dma_queue *queue) +{ + struct dma_desc *desc; + + if (queue_empty(queue)) + return NULL; + + desc = queue->head; + + queue->head = desc->lastAddr->nextAddr; + + /* poison nextAddr address */ + desc->lastAddr->nextAddr = desc->lastAddr; + desc->lastAddr->lastAddr = desc->lastAddr; + + return desc; +} diff --git a/carl9170fw/carlfw/src/fw.c b/carl9170fw/carlfw/src/fw.c new file mode 100644 index 0000000..5bde675 --- /dev/null +++ b/carl9170fw/carlfw/src/fw.c @@ -0,0 +1,116 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * Firmware descriptor + * + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "carl9170.h" +#include "fwdsc.h" + +#define FILL(small, big, more...) \ + .small = { \ + CARL9170FW_FILL_DESC(big##_MAGIC, \ + sizeof(struct carl9170fw_## small##_desc), \ + CARL9170FW_## big##_DESC_MIN_VER, \ + CARL9170FW_## big##_DESC_CUR_VER), \ + more \ + } + +const struct carl9170_firmware_descriptor __section(fwdsc) carl9170fw_desc = { + FILL(otus, OTUS, + .feature_set = cpu_to_le32(BIT(CARL9170FW_DUMMY_FEATURE) | + BIT(CARL9170FW_USB_RESP_EP2) | + BIT(CARL9170FW_HANDLE_BACK_REQ) | + BIT(CARL9170FW_RX_FILTER) | + BIT(CARL9170FW_HW_COUNTERS) | + BIT(CARL9170FW_RX_BA_FILTER) | + BIT(CARL9170FW_USB_INIT_FIRMWARE) | +#ifdef CONFIG_CARL9170FW_USB_UP_STREAM + BIT(CARL9170FW_USB_UP_STREAM) | +#endif /* CONFIG_CARL9170FW_USB_UP_STREAM */ +#ifdef CONFIG_CARL9170FW_USB_DOWN_STREAM + BIT(CARL9170FW_USB_DOWN_STREAM) | +#endif /* CONFIG_CARL9170FW_USB_DOWN_STREAM */ +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + BIT(CARL9170FW_COMMAND_PHY) | + BIT(CARL9170FW_PSM) | + BIT(CARL9170FW_FIXED_5GHZ_PSM) | +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ +#ifdef CONFIG_CARL9170FW_SECURITY_ENGINE + BIT(CARL9170FW_COMMAND_CAM) | +#endif /* CONFIG_CARL9170FW_SECURITY_ENGINE */ +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + BIT(CARL9170FW_WLANTX_CAB) | +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ +#ifdef CONFIG_CARL9170FW_UNUSABLE + BIT(CARL9170FW_UNUSABLE) | +#endif /* CONFIG_CARL9170FW_UNUSABLE */ +#ifdef CONFIG_CARL9170FW_GPIO_INTERRUPT + BIT(CARL9170FW_GPIO_INTERRUPT) | +#endif /* CONFIG_CARL9170FW_GPIO_INTERRUPT */ +#ifdef CONFIG_CARL9170FW_WOL + BIT(CARL9170FW_WOL) | +#endif /* CONFIG_CARL9170FW_WOL */ + (0)), + + .miniboot_size = cpu_to_le16(0), + .tx_descs = AR9170_TX_BLOCK_NUMBER, + .cmd_bufs = CARL9170_INT_RQ_CACHES, + .rx_max_frame_len = cpu_to_le16(CONFIG_CARL9170FW_RX_FRAME_LEN), + .tx_frag_len = cpu_to_le16(AR9170_BLOCK_SIZE), + .fw_address = cpu_to_le32(AR9170_PRAM_OFFSET), + .bcn_addr = (__le32) cpu_to_le32(&dma_mem.reserved.bcn), + .bcn_len = (__le16) cpu_to_le16(sizeof(dma_mem.reserved.bcn)), + .vif_num = CARL9170_INTF_NUM, + .api_ver = CONFIG_CARL9170FW_RELEASE_VERSION, + ), + + FILL(txsq, TXSQ, + .seq_table_addr = cpu_to_le32(&fw.wlan.sequence), + ), + +#ifdef CONFIG_CARL9170FW_WOL + FILL(wol, WOL, + .supported_triggers = BIT(CARL9170_WOL_DISCONNECT) | + BIT(CARL9170_WOL_MAGIC_PKT), + ), +#endif /* CONFIG_CARL9170FW_WOL */ + + + FILL(motd, MOTD, + .fw_year_month_day = cpu_to_le32( + CARL9170FW_SET_DAY(CARL9170FW_VERSION_DAY) + + CARL9170FW_SET_MONTH(CARL9170FW_VERSION_MONTH) + + CARL9170FW_SET_YEAR(CARL9170FW_VERSION_YEAR)), + .desc = "Community AR9170 Linux", + .release = CARL9170FW_VERSION_GIT), + + FILL(dbg, DBG, + .bogoclock_addr = cpu_to_le32(0), + .counter_addr = cpu_to_le32(&fw.counter), + .rx_total_addr = cpu_to_le32(0), + .rx_overrun_addr = cpu_to_le32(0), + .rx_filter = cpu_to_le32(&fw.wlan.rx_filter), + ), + + FILL(last, LAST), +}; + +#undef FILL + +struct firmware_context_struct fw; diff --git a/carl9170fw/carlfw/src/gpio.c b/carl9170fw/carlfw/src/gpio.c new file mode 100644 index 0000000..85cd81f --- /dev/null +++ b/carl9170fw/carlfw/src/gpio.c @@ -0,0 +1,52 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * GPIO interrupt service + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "gpio.h" + +#ifdef CONFIG_CARL9170FW_GPIO_INTERRUPT +void gpio_timer(void) +{ + uint32_t cur; + + cur = get(AR9170_GPIO_REG_PORT_DATA) & CARL9170_GPIO_MASK; + + if (cur != fw.cached_gpio_state.gpio) { + fw.cached_gpio_state.gpio = cur; + + send_cmd_to_host(sizeof(struct carl9170_gpio), + CARL9170_RSP_GPIO, 0x00, + (uint8_t *)&fw.cached_gpio_state); + +# ifdef CONFIG_CARL9170FW_WATCHDOG_BUTTON + for (;;) { + /* + * Loop forever... Until the watchdog triggers. + */ + } +# endif /* CONFIG_CARL9170FW_WATCHDOG_BUTTON */ + } +} +#endif /* CONFIG_CARL9170FW_GPIO_INTERRUPT */ diff --git a/carl9170fw/carlfw/src/hostif.c b/carl9170fw/carlfw/src/hostif.c new file mode 100644 index 0000000..e445d5f --- /dev/null +++ b/carl9170fw/carlfw/src/hostif.c @@ -0,0 +1,165 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * Host interface routines + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "hostif.h" +#include "printf.h" +#include "wl.h" + +static bool length_check(struct dma_desc *desc) +{ + volatile struct carl9170_tx_superframe *super = __get_super(desc); + + if (unlikely(desc->totalLen < sizeof(struct carl9170_tx_superdesc))) + return false; + + /* + * check if the DMA is complete, or clipped. + * + * NB: The hardware aligns the descriptor length to + * a 4 byte boundary. This makes the direct comparison + * difficult, or unnecessary complex for a hot-path. + */ + if (unlikely(super->s.len > desc->totalLen)) + return false; + + return true; +} + +static void handle_download(void) +{ + struct dma_desc *desc; + + /* + * Under normal conditions, all completed descs should have + * the AR9170_OWN_BITS_SE status flag set. + * However there seems to be a undocumented case where the flag + * is _SW ( handle_download_exception ) + */ + + for_each_desc_not_bits(desc, &fw.pta.down_queue, AR9170_OWN_BITS_HW) { + if (unlikely((length_check(desc) == false))) { + /* + * There is no easy way of telling what was lost. + * + * Therefore we just reclaim the data. + * The driver has to have some sort frame + * timeout mechanism. + */ + + wlan_tx_complete(__get_super(desc), false); + dma_reclaim(&fw.pta.down_queue, desc); + down_trigger(); + } else { + wlan_tx(desc); + } + } + +#ifdef CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT + xorl(AR9170_GPIO_REG_PORT_DATA, 2); +#endif /* CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT */ +} + +static void handle_upload(void) +{ + struct dma_desc *desc; + + for_each_desc_not_bits(desc, &fw.pta.up_queue, AR9170_OWN_BITS_HW) { + /* + * BIG FAT NOTE: + * + * DO NOT compare the descriptor addresses. + */ + if (DESC_PAYLOAD(desc) == (void *) &dma_mem.reserved.rsp) { + fw.usb.int_desc = desc; + fw.usb.int_desc_available = 1; + } else { + dma_reclaim(&fw.wlan.rx_queue, desc); + wlan_trigger(AR9170_DMA_TRIGGER_RXQ); + } + } + +#ifdef CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT + xorl(AR9170_GPIO_REG_PORT_DATA, 2); +#endif /* CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT */ +} + +static void handle_download_exception(void) +{ + struct dma_desc *desc, *target; + + /* actually, the queue should be stopped by now? */ + usb_stop_down_queue(); + + target = (void *)((get(AR9170_PTA_REG_DN_CURR_ADDRH) << 16) | + get(AR9170_PTA_REG_DN_CURR_ADDRL)); + + /* + * Put "forgotten" packets from the head of the queue, back + * to the current position + */ + __while_desc_bits(desc, &fw.pta.down_queue, AR9170_OWN_BITS_HW) { + if (desc == target) + break; + + dma_reclaim(&fw.pta.down_queue, + dma_unlink_head(&fw.pta.down_queue)); + } + + __for_each_desc_continue(desc, &fw.pta.down_queue) { + if ((desc->status & AR9170_OWN_BITS) == AR9170_OWN_BITS_SW) + dma_fix_downqueue(desc); + } + + + usb_start_down_queue(); + + down_trigger(); +} + +/* handle interrupts from DMA chip */ +void handle_host_interface(void) +{ + uint32_t pta_int; + + pta_int = get(AR9170_PTA_REG_INT_FLAG); + +#define HANDLER(intr, flag, func) \ + do { \ + if ((intr & flag) != 0) { \ + func(); \ + } \ + } while (0) + + HANDLER(pta_int, AR9170_PTA_INT_FLAG_DN, handle_download); + + HANDLER(pta_int, AR9170_PTA_INT_FLAG_UP, handle_upload); + + /* This is just guesswork and MAGIC */ + pta_int = get(AR9170_PTA_REG_DMA_STATUS); + HANDLER(pta_int, 0x1, handle_download_exception); + +#undef HANDLER +} diff --git a/carl9170fw/carlfw/src/main.c b/carl9170fw/carlfw/src/main.c new file mode 100644 index 0000000..17cbaf9 --- /dev/null +++ b/carl9170fw/carlfw/src/main.c @@ -0,0 +1,256 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * initialization and main() loop + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "timer.h" +#include "hostif.h" +#include "printf.h" +#include "gpio.h" +#include "wl.h" +#include "rf.h" +#include "usb.h" + +#define AR9170_WATCH_DOG_TIMER 0x100 + +static void timer_init(const unsigned int timer, const unsigned int interval) +{ + /* Set timer to periodic mode */ + orl(AR9170_TIMER_REG_CONTROL, BIT(timer)); + + /* Set time interval */ + set(AR9170_TIMER_REG_TIMER0 + (timer << 2), interval - 1); + + /* Clear timer interrupt flag */ + orl(AR9170_TIMER_REG_INTERRUPT, BIT(timer)); +} + +void clock_set(enum cpu_clock_t clock_, bool on) +{ + /* + * Word of Warning! + * This setting does more than just mess with the CPU Clock. + * So watch out, if you need _stable_ timer interrupts. + */ +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + if (fw.phy.frequency < 3000000) + set(AR9170_PWR_REG_PLL_ADDAC, 0x5163); + else + set(AR9170_PWR_REG_PLL_ADDAC, 0x5143); +#else + set(AR9170_PWR_REG_PLL_ADDAC, 0x5163); +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ + + fw.ticks_per_usec = GET_VAL(AR9170_PWR_PLL_ADDAC_DIV, + get(AR9170_PWR_REG_PLL_ADDAC)); + + set(AR9170_PWR_REG_CLOCK_SEL, (uint32_t) ((on ? 0x70 : 0x600) | clock_)); + + switch (clock_) { + case AHB_20_22MHZ: + fw.ticks_per_usec >>= 1; + case AHB_40MHZ_OSC: + case AHB_40_44MHZ: + fw.ticks_per_usec >>= 1; + case AHB_80_88MHZ: + break; + } +} + +static void init(void) +{ + led_init(); + +#ifdef CONFIG_CARL9170FW_DEBUG_UART + uart_init(); +#endif /* CONFIG_CARL9170FW_DEBUG_UART */ + + /* 25/50/100ms timer (depends on cpu clock) */ + timer_init(0, 50000); + + /* USB init */ + usb_init(); + + /* initialize DMA memory */ + memset(&dma_mem, 0, sizeof(dma_mem)); + + /* fill DMA rings */ + dma_init_descriptors(); + + /* clear all interrupt */ + set(AR9170_MAC_REG_INT_CTRL, 0xffff); + + orl(AR9170_MAC_REG_AFTER_PNP, 1); + + /* Init watch dog control flag */ + fw.watchdog_enable = 1; + + set(AR9170_TIMER_REG_WATCH_DOG, AR9170_WATCH_DOG_TIMER); + +#ifdef CONFIG_CARL9170FW_GPIO_INTERRUPT + fw.cached_gpio_state.gpio = get(AR9170_GPIO_REG_PORT_DATA) & + CARL9170_GPIO_MASK; +#endif /* CONFIG_CARL9170FW_GPIO_INTERRUPT */ + + /* this will get the downqueue moving. */ + down_trigger(); +} + +static void handle_fw(void) +{ + if (fw.watchdog_enable == 1) + set(AR9170_TIMER_REG_WATCH_DOG, AR9170_WATCH_DOG_TIMER); + + if (fw.reboot) + reboot(); +} + +static void timer0_isr(void) +{ + wlan_timer(); + +#ifdef CONFIG_CARL9170FW_GPIO_INTERRUPT + gpio_timer(); +#endif /* CONFIG_CARL9170FW_GPIO_INTERRUPT */ + +#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 */ +} + +static void handle_timer(void) +{ + uint32_t intr; + + intr = get(AR9170_TIMER_REG_INTERRUPT); + + /* ACK timer interrupt */ + set(AR9170_TIMER_REG_INTERRUPT, intr); + +#define HANDLER(intr, flag, func) \ + do { \ + if ((intr & flag) != 0) { \ + intr &= ~flag; \ + func(); \ + } \ + } while (0) + + HANDLER(intr, BIT(0), timer0_isr); + + if (intr) + DBG("Unhandled Timer Event %x", (unsigned int) intr); + +#undef HANDLER +} + +static void tally_update(void) +{ + unsigned int boff, time, delta; + + time = get_clock_counter(); + if (fw.phy.state == CARL9170_PHY_ON) { + delta = (time - fw.tally_clock); + + fw.tally.active += delta; + + boff = get(AR9170_MAC_REG_BACKOFF_STATUS); + if (boff & AR9170_MAC_BACKOFF_TX_PE) + fw.tally.tx_time += delta; + if (boff & AR9170_MAC_BACKOFF_CCA) + fw.tally.cca += delta; + } + + fw.tally_clock = time; + fw.counter++; +} + +static void __noreturn main_loop(void) +{ + /* main loop */ + while (1) { + handle_fw(); + + /* + * Due to frame order persevation, the wlan subroutines + * must be executed before handle_host_interface. + */ + handle_wlan(); + + handle_host_interface(); + + handle_usb(); + + handle_timer(); + + tally_update(); + } +} + +/* + * The bootcode will work with the device driver to load the firmware + * onto the device's Program SRAM. The Program SRAM has a size of 16 KB + * and also contains the stack, which grows down from 0x204000. + * + * The Program SRAM starts at address 0x200000 on the device. + * The firmware entry point (0x200004) is located in boot.S. + * we put _start() there with the linker script carl9170.lds. + */ + +void __section(boot) start(void) +{ + clock_set(AHB_40MHZ_OSC, true); + + /* watchdog magic pattern check */ + if ((get(AR9170_PWR_REG_WATCH_DOG_MAGIC) & 0xffff0000) == 0x12340000) { + /* watch dog warm start */ + incl(AR9170_PWR_REG_WATCH_DOG_MAGIC); + usb_trigger_out(); + } else if ((get(AR9170_PWR_REG_WATCH_DOG_MAGIC) & 0xffff0000) == 0x98760000) { + /* suspend/resume */ + } + + /* write the magic pattern for watch dog */ + andl(AR9170_PWR_REG_WATCH_DOG_MAGIC, 0xFFFF); + orl(AR9170_PWR_REG_WATCH_DOG_MAGIC, 0x12340000); + + init(); + +#ifdef CONFIG_CARL9170FW_DEBUG + + BUG("TEST BUG"); + BUG_ON(0x2b || !0x2b); + INFO("INFO MESSAGE"); + + /* a set of unique characters to detect transfer data corruptions */ + DBG("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" + " ~`!1@2#3$4%%5^6&7*8(9)0_-+={[}]|\\:;\"'<,>.?/"); +#endif /* CONFIG_CARL9170FW_DEBUG */ + + /* + * Tell the host, that the firmware has booted and is + * now ready to process requests. + */ + send_cmd_to_host(0, CARL9170_RSP_BOOT, 0x00, NULL); + main_loop(); +} diff --git a/carl9170fw/carlfw/src/memcpy.S b/carl9170fw/carlfw/src/memcpy.S new file mode 100644 index 0000000..9539a6b --- /dev/null +++ b/carl9170fw/carlfw/src/memcpy.S @@ -0,0 +1,228 @@ +/* $Id: memcpy.S,v 1.3 2001/07/27 11:50:52 gniibe Exp $ + * + * "memcpy" implementation of SuperH + * + * Copyright (C) 1999 Niibe Yutaka + * + */ + +/* + * void *memcpy(void *dst, const void *src, size_t n); + * No overlap between the memory of DST and of SRC are assumed. + */ + + .globl _memcpy + .align 2 + _memcpy: + tst r6,r6 + bt/s 9f ! if n=0, do nothing + mov r4,r0 + sub r4,r5 ! From here, r5 has the distance to r0 + add r6,r0 ! From here, r0 points the end of copying point + mov #12,r1 + cmp/gt r6,r1 + bt/s 7f ! if it's too small, copy a byte at once + add #-1,r5 + add #1,r5 + ! From here, r6 is free + ! + ! r4 --> [ ... ] DST [ ... ] SRC + ! [ ... ] [ ... ] + ! : : + ! r0 --> [ ... ] r0+r5 --> [ ... ] + ! + ! + mov r5,r1 + mov #3,r2 + and r2,r1 + shll2 r1 + mov r0,r3 ! Save the value on R0 to R3 + mova jmptable,r0 + add r1,r0 + mov.l @r0,r1 + jmp @r1 + mov r3,r0 ! and back to R0 + .balign 4 +jmptable: + .long case0 + .long case1 + .long case2 + .long case3 + + ! copy a byte at once +7: mov r4,r2 + add #1,r2 +8: + cmp/hi r2,r0 + mov.b @(r0,r5),r1 + bt/s 8b ! while (r0>r2) + mov.b r1,@-r0 +9: + rts + nop + +case0: + ! + ! GHIJ KLMN OPQR --> GHIJ KLMN OPQR + ! + ! First, align to long word boundary + mov r0,r3 + and r2,r3 + tst r3,r3 + bt/s 2f + add #-4,r5 + add #3,r5 +1: dt r3 + mov.b @(r0,r5),r1 + bf/s 1b + mov.b r1,@-r0 + ! + add #-3,r5 +2: ! Second, copy a long word at once + mov r4,r2 + add #7,r2 +3: mov.l @(r0,r5),r1 + cmp/hi r2,r0 + bt/s 3b + mov.l r1,@-r0 + ! + ! Third, copy a byte at once, if necessary + cmp/eq r4,r0 + bt/s 9b + add #3,r5 + bra 8b + add #-6,r2 + +case1: + ! + ! GHIJ KLMN OPQR --> ...G HIJK LMNO PQR. + ! + ! First, align to long word boundary + mov r0,r3 + and r2,r3 + tst r3,r3 + bt/s 2f + add #-1,r5 +1: dt r3 + mov.b @(r0,r5),r1 + bf/s 1b + mov.b r1,@-r0 + ! +2: ! Second, read a long word and write a long word at once + mov.l @(r0,r5),r1 + add #-4,r5 + mov r4,r2 + add #7,r2 + ! +#ifdef __LITTLE_ENDIAN__ +3: mov r1,r3 ! RQPO + shll16 r3 + shll8 r3 ! Oxxx + mov.l @(r0,r5),r1 ! NMLK + mov r1,r6 + shlr8 r6 ! xNML + or r6,r3 ! ONML + cmp/hi r2,r0 + bt/s 3b + mov.l r3,@-r0 +#else +3: mov r1,r3 ! OPQR + shlr16 r3 + shlr8 r3 ! xxxO + mov.l @(r0,r5),r1 ! KLMN + mov r1,r6 + shll8 r6 ! LMNx + or r6,r3 ! LMNO + cmp/hi r2,r0 + bt/s 3b + mov.l r3,@-r0 +#endif + ! + ! Third, copy a byte at once, if necessary + cmp/eq r4,r0 + bt/s 9b + add #4,r5 + bra 8b + add #-6,r2 + +case2: + ! + ! GHIJ KLMN OPQR --> ..GH IJKL MNOP QR.. + ! + ! First, align to word boundary + tst #1,r0 + bt/s 2f + add #-1,r5 + mov.b @(r0,r5),r1 + mov.b r1,@-r0 + ! +2: ! Second, read a word and write a word at once + add #-1,r5 + mov r4,r2 + add #3,r2 + ! +3: mov.w @(r0,r5),r1 + cmp/hi r2,r0 + bt/s 3b + mov.w r1,@-r0 + ! + ! Third, copy a byte at once, if necessary + cmp/eq r4,r0 + bt/s 9b + add #1,r5 + mov.b @(r0,r5),r1 + rts + mov.b r1,@-r0 + +case3: + ! + ! GHIJ KLMN OPQR --> .GHI JKLM NOPQ R... + ! + ! First, align to long word boundary + mov r0,r3 + and r2,r3 + tst r3,r3 + bt/s 2f + add #-1,r5 +1: dt r3 + mov.b @(r0,r5),r1 + bf/s 1b + mov.b r1,@-r0 + ! +2: ! Second, read a long word and write a long word at once + add #-2,r5 + mov.l @(r0,r5),r1 + add #-4,r5 + mov r4,r2 + add #7,r2 + ! +#ifdef __LITTLE_ENDIAN__ +3: mov r1,r3 ! RQPO + shll8 r3 ! QPOx + mov.l @(r0,r5),r1 ! NMLK + mov r1,r6 + shlr16 r6 + shlr8 r6 ! xxxN + or r6,r3 ! QPON + cmp/hi r2,r0 + bt/s 3b + mov.l r3,@-r0 +#else +3: mov r1,r3 ! OPQR + shlr8 r3 ! xOPQ + mov.l @(r0,r5),r1 ! KLMN + mov r1,r6 + shll16 r6 + shll8 r6 ! Nxxx + or r6,r3 ! NOPQ + cmp/hi r2,r0 + bt/s 3b + mov.l r3,@-r0 +#endif + ! + ! Third, copy a byte at once, if necessary + cmp/eq r4,r0 + bt/s 9b + add #6,r5 + bra 8b + add #-6,r2 diff --git a/carl9170fw/carlfw/src/memset.S b/carl9170fw/carlfw/src/memset.S new file mode 100644 index 0000000..d39c8a6 --- /dev/null +++ b/carl9170fw/carlfw/src/memset.S @@ -0,0 +1,58 @@ +/* $Id: memset.S,v 1.1 2000/04/14 16:49:01 mjd Exp $ + * + * "memset" implementation of SuperH + * + * Copyright (C) 1999 Niibe Yutaka + * + */ + +/* + * void *memset(void *s, int c, size_t n); + */ + + .globl _memset + .align 2 + _memset: + tst r6,r6 + bt/s 5f ! if n=0, do nothing + add r6,r4 + mov #12,r0 + cmp/gt r6,r0 + bt/s 4f ! if it's too small, set a byte at once + mov r4,r0 + and #3,r0 + cmp/eq #0,r0 + bt/s 2f ! It's aligned + sub r0,r6 +1: + dt r0 + bf/s 1b + mov.b r5,@-r4 +2: ! make VVVV + extu.b r5,r5 + swap.b r5,r0 ! V0 + or r0,r5 ! VV + swap.w r5,r0 ! VV00 + or r0,r5 ! VVVV + ! + mov r6,r0 + shlr2 r0 + shlr r0 ! r0 = r6 >> 3 +3: + dt r0 + mov.l r5,@-r4 ! set 8-byte at once + bf/s 3b + mov.l r5,@-r4 + ! + mov #7,r0 + and r0,r6 + tst r6,r6 + bt 5f + ! fill bytes +4: + dt r6 + bf/s 4b + mov.b r5,@-r4 +5: + rts + mov r4,r0 diff --git a/carl9170fw/carlfw/src/printf.c b/carl9170fw/carlfw/src/printf.c new file mode 100644 index 0000000..3ff05c4 --- /dev/null +++ b/carl9170fw/carlfw/src/printf.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2004,2008 Kustaa Nyholm + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "carl9170.h" +#include "printf.h" + +#ifdef CONFIG_CARL9170FW_PRINTF +static char *bf; +static char buf[12]; +static unsigned int num; +static char uc; +static char zs; + +static void out(const char c) +{ + *bf++ = c; +} + +static void outDgt(const char dgt) +{ + out(dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10)); + zs = 1; +} + +static void divOut(const unsigned int d) +{ + unsigned char dgt = 0; + + while (num >= d) { + num -= d; + dgt++; + } + + if (zs || dgt > 0) + outDgt(dgt); +} + +void tfp_printf(const char *fmt, ...) +{ + va_list va; + char *p; + unsigned int i; + char ch; + + va_start(va, fmt); + + while ((ch = *(fmt++))) { + if (ch != '%') { + putcharacter(ch); + } else { + char lz = 0; + char w = 0; + ch = *(fmt++); + + if (ch == '0') { + ch = *(fmt++); + lz = 1; + } + + if (ch >= '0' && ch <= '9') { + w = 0; + while (ch >= '0' && ch <= '9') { + w = (((w << 2) + w) << 1) + ch - '0'; + ch = *fmt++; + } + } + + bf = buf; + p = bf; + zs = 0; + + switch (ch) { + case 0: + goto abort; + + case 'u': + case 'd': + num = va_arg(va, unsigned int); + if (ch == 'd' && (int) num < 0) { + num = -(int)num; + out('-'); + } + + for (i = 100000000; i != 1; i /= 10) + divOut(i); + + outDgt(num); + break; + + case 'p': + case 'x': + case 'X': + uc = ch == 'X'; + num = va_arg(va, unsigned int); + for (i = 0x10000000; i != 0x1; i >>= 4) + divOut(i); + + outDgt(num); + break; + + case 'c': + out((char)(va_arg(va, int))); + break; + + case 's': + p = va_arg(va, char*); + break; + case '%': + out('%'); + break; + + default: + break; + } + + *bf = 0; + bf = p; + while (*bf++ && w > 0) + w--; + + while (w-- > 0) + putcharacter(lz ? '0' : ' '); + + while ((ch = *p++)) + putcharacter(ch); + } + } + +abort: + putcharacter('\0'); + va_end(va); +} + +#else + +void min_printf(const char *fmt, ...) +{ + char ch; + + do { + ch = *(fmt++); + putcharacter(ch); + } while (ch); +} + +#endif /* CONFIG_CARL9170FW_PRINTF */ diff --git a/carl9170fw/carlfw/src/reboot.S b/carl9170fw/carlfw/src/reboot.S new file mode 100644 index 0000000..14c1813 --- /dev/null +++ b/carl9170fw/carlfw/src/reboot.S @@ -0,0 +1,11 @@ + .globl _jump_to_bootcode + .type _jump_to_bootcode, @function + _jump_to_bootcode: + mov.l stack_start, r0 + mov.l @r0, sp + mov.l eeprom_start, r0 + mov.l @r0, r0 + jmp @r0 + .align 4 +stack_start: .long 0x00000004 +eeprom_start: .long 0x00000000 diff --git a/carl9170fw/carlfw/src/rf.c b/carl9170fw/carlfw/src/rf.c new file mode 100644 index 0000000..e031dd8 --- /dev/null +++ b/carl9170fw/carlfw/src/rf.c @@ -0,0 +1,277 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * PHY and RF functions + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "timer.h" +#include "printf.h" +#include "rf.h" +#include "shared/phy.h" + +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS +static void set_channel_end(void) +{ + /* Manipulate CCA threshold to resume transmission */ + set(AR9170_PHY_REG_CCA_THRESHOLD, 0x0); + /* Disable Virtual CCA */ + andl(AR9170_MAC_REG_QOS_PRIORITY_VIRTUAL_CCA, + ~AR9170_MAC_VIRTUAL_CCA_ALL); + + fw.phy.state = CARL9170_PHY_ON; +} + +void rf_notify_set_channel(void) +{ + /* Manipulate CCA threshold to stop transmission */ + set(AR9170_PHY_REG_CCA_THRESHOLD, 0x300); + /* Enable Virtual CCA */ + orl(AR9170_MAC_REG_QOS_PRIORITY_VIRTUAL_CCA, + AR9170_MAC_VIRTUAL_CCA_ALL); + + /* reset CCA stats */ + fw.tally.active = 0; + fw.tally.cca = 0; + fw.tally.tx_time = 0; + fw.phy.state = CARL9170_PHY_OFF; +} + +/* + * Update delta slope coeff man and exp + */ +static void hw_turn_off_dyn(const uint32_t delta_slope_coeff_exp, + const uint32_t delta_slope_coeff_man, + const uint32_t delta_slope_coeff_exp_shgi, + const uint32_t delta_slope_coeff_man_shgi) +{ + uint32_t tmp; + + tmp = get_async(AR9170_PHY_REG_TIMING3) & 0x00001fff; + tmp |= (delta_slope_coeff_man << AR9170_PHY_TIMING3_DSC_MAN_S) & + AR9170_PHY_TIMING3_DSC_MAN; + tmp |= (delta_slope_coeff_exp << AR9170_PHY_TIMING3_DSC_EXP_S) & + AR9170_PHY_TIMING3_DSC_EXP; + + set(AR9170_PHY_REG_TIMING3, tmp); + + tmp = (delta_slope_coeff_man_shgi << AR9170_PHY_HALFGI_DSC_MAN_S) & + AR9170_PHY_HALFGI_DSC_MAN; + + tmp |= (delta_slope_coeff_exp_shgi << AR9170_PHY_HALFGI_DSC_EXP_S) & + AR9170_PHY_HALFGI_DSC_EXP; + + set(AR9170_PHY_REG_HALFGI, tmp); +} + +static void program_ADDAC(void) +{ + /* ??? Select Internal ADDAC ??? (is external radio) */ + set(AR9170_PHY_REG_ADC_SERIAL_CTL, AR9170_PHY_ADC_SCTL_SEL_EXTERNAL_RADIO); + + delay(10); + + set(0x1c589c, 0x00000000); /*# 7-0 */ + set(0x1c589c, 0x00000000); /*# 15-8 */ + set(0x1c589c, 0x00000000); /*# 23-16 */ + set(0x1c589c, 0x00000000); /*# 31- */ + + set(0x1c589c, 0x00000000); /*# 39- */ + set(0x1c589c, 0x00000000); /*# 47- */ + set(0x1c589c, 0x00000000); /*# 55- [48]:doubles the xtalosc bias current */ + set(0x1c589c, 0x00000000); /*# 63- */ + + set(0x1c589c, 0x00000000); /*# 71- */ + set(0x1c589c, 0x00000000); /*# 79- */ + set(0x1c589c, 0x00000000); /*# 87- */ + set(0x1c589c, 0x00000000); /*# 95- */ + + set(0x1c589c, 0x00000000); /*# 103- */ + set(0x1c589c, 0x00000000); /*# 111- */ + set(0x1c589c, 0x00000000); /*# 119- */ + set(0x1c589c, 0x00000000); /*# 127- */ + + set(0x1c589c, 0x00000000); /*# 135- */ + set(0x1c589c, 0x00000000); /*# 143- */ + set(0x1c589c, 0x00000000); /*# 151- */ + set(0x1c589c, 0x00000030); /*# 159- #[158:156]=xlnabufmode */ + + set(0x1c589c, 0x00000004); /*# 167- [162]:disable clkp_driver to flow */ + set(0x1c589c, 0x00000000); /*# 175- */ + set(0x1c589c, 0x00000000); /*# 183-176 */ + set(0x1c589c, 0x00000000); /*# 191-184 */ + + set(0x1c589c, 0x00000000); /*# 199- */ + set(0x1c589c, 0x00000000); /*# 207- */ + set(0x1c589c, 0x00000000); /*# 215- */ + set(0x1c589c, 0x00000000); /*# 223- */ + + set(0x1c589c, 0x00000000); /*# 231- */ + set(0x1c58c4, 0x00000000); /*# 233-232 */ + + delay(10); + + /* Select External Flow ???? (is internal addac??) */ + set(AR9170_PHY_REG_ADC_SERIAL_CTL, AR9170_PHY_ADC_SCTL_SEL_INTERNAL_ADDAC); +} + +static uint32_t AGC_calibration(uint32_t loop) +{ + uint32_t wrdata; + uint32_t ret; + +#define AGC_CAL_NF (AR9170_PHY_AGC_CONTROL_CAL | AR9170_PHY_AGC_CONTROL_NF) + + wrdata = get_async(AR9170_PHY_REG_AGC_CONTROL) | AGC_CAL_NF; + set(AR9170_PHY_REG_AGC_CONTROL, wrdata); + + ret = get_async(AR9170_PHY_REG_AGC_CONTROL) & AGC_CAL_NF; + + /* sitesurvey : 100 ms / current connected 200 ms */ + while ((ret != 0) && loop--) { + udelay(100); + + ret = get_async(AR9170_PHY_REG_AGC_CONTROL) & AGC_CAL_NF; + } + + /* return the AGC/Noise calibration state to the driver */ + return ret; +} + +#define EIGHTY_FLAG (CARL9170FW_PHY_HT_ENABLE | CARL9170FW_PHY_HT_DYN2040) + +static uint32_t rf_init(const uint32_t delta_slope_coeff_exp, + const uint32_t delta_slope_coeff_man, + const uint32_t delta_slope_coeff_exp_shgi, + const uint32_t delta_slope_coeff_man_shgi, + const uint32_t finiteLoopCount, + const bool initialize) +{ + uint32_t ret; + + hw_turn_off_dyn(delta_slope_coeff_exp, + delta_slope_coeff_man, + delta_slope_coeff_exp_shgi, + delta_slope_coeff_man_shgi); + + if (initialize) { + /* Real Chip */ + program_ADDAC(); + + /* inverse chain 0 <-> chain 2 */ + set(AR9170_PHY_REG_ANALOG_SWAP, AR9170_PHY_ANALOG_SWAP_AB); + + /* swap chain 0 and chain 2 */ + set(AR9170_PHY_REG_ANALOG_SWAP, AR9170_PHY_ANALOG_SWAP_AB | + AR9170_PHY_ANALOG_SWAP_ALT_CHAIN); + + /* Activate BB */ + set(AR9170_PHY_REG_ACTIVE, AR9170_PHY_ACTIVE_EN); + delay(10); + } + + ret = AGC_calibration(finiteLoopCount); + + set_channel_end(); + return ret; +} + +void rf_cmd(const struct carl9170_cmd *cmd, struct carl9170_rsp *resp) +{ + uint32_t ret; + + fw.phy.ht_settings = cmd->rf_init.ht_settings; + fw.phy.frequency = cmd->rf_init.freq; + + /* + * Is the clock controlled by the PHY? + */ + if ((fw.phy.ht_settings & EIGHTY_FLAG) == EIGHTY_FLAG) + clock_set(AHB_80_88MHZ, true); + else + clock_set(AHB_40_44MHZ, true); + + ret = rf_init(le32_to_cpu(cmd->rf_init.delta_slope_coeff_exp), + le32_to_cpu(cmd->rf_init.delta_slope_coeff_man), + le32_to_cpu(cmd->rf_init.delta_slope_coeff_exp_shgi), + le32_to_cpu(cmd->rf_init.delta_slope_coeff_man_shgi), + le32_to_cpu(cmd->rf_init.finiteLoopCount), + cmd->hdr.cmd == CARL9170_CMD_RF_INIT); + + resp->hdr.len = sizeof(struct carl9170_rf_init_result); + resp->rf_init_res.ret = cpu_to_le32(ret); +} + +void rf_psm(void) +{ + u32 bank3; + + if (fw.phy.psm.state == CARL9170_PSM_SOFTWARE) { + /* not enabled by the driver */ + return; + } + + if (fw.phy.psm.state & CARL9170_PSM_SLEEP) { + fw.phy.psm.state &= ~CARL9170_PSM_SLEEP; + + /* disable all agc gain and offset updates to a2 */ + set(AR9170_PHY_REG_TEST2, 0x8000000); + + /* power down ADDAC */ + set(AR9170_PHY_REG_ADC_CTL, + AR9170_PHY_ADC_CTL_OFF_PWDDAC | + AR9170_PHY_ADC_CTL_OFF_PWDADC | + 0xa0000000); + + /* Synthesizer off + RX off */ + bank3 = 0x00400018; + + fw.phy.state = CARL9170_PHY_OFF; + } else { + /* advance to the next PSM step */ + fw.phy.psm.state--; + + if (fw.phy.psm.state == CARL9170_PSM_WAKE) { + /* wake up ADDAC */ + set(AR9170_PHY_REG_ADC_CTL, + AR9170_PHY_ADC_CTL_OFF_PWDDAC | + AR9170_PHY_ADC_CTL_OFF_PWDADC); + + /* enable all agc gain and offset updates to a2 */ + set(AR9170_PHY_REG_TEST2, 0x0); + + /* Synthesizer on + RX on */ + bank3 = 0x01420098; + + fw.phy.state = CARL9170_PHY_ON; + } else { + return ; + } + } + + if (fw.phy.frequency < 3000000) + bank3 |= 0x00800000; + + set(0x1c58f0, bank3); +} + +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ diff --git a/carl9170fw/carlfw/src/uart.c b/carl9170fw/carlfw/src/uart.c new file mode 100644 index 0000000..58f6486 --- /dev/null +++ b/carl9170fw/carlfw/src/uart.c @@ -0,0 +1,76 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * UART debug interface functions. + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "uart.h" +#include "io.h" + +#ifdef CONFIG_CARL9170FW_DEBUG_UART +void uart_putc(const char c) +{ + set(AR9170_UART_REG_TX_HOLDING, c); + + while (get(AR9170_UART_REG_LINE_STATUS) & + AR9170_UART_LINE_STS_TX_FIFO_ALMOST_EMPTY) { + /* + * wait until the byte has made it + */ + } +} + +void uart_print_hex_dump(const void *buf, const int len) +{ + unsigned int offset = 0; + + uart_putc('H'); + uart_putc('D'); + uart_putc(':'); + + while (len > 0) { + uart_putc(*((uint8_t *) buf + offset)); + offset++; + } +} + +void uart_init(void) +{ + unsigned int timeout = 0; + +#ifdef CONFIG_CARL9170FW_UART_CLOCK_25M + set(AR9170_UART_REG_DIVISOR_LSB, 0xc); +#elif CONFIG_CARL9170FW_UART_CLOCK_40M + set(AR9170_UART_REG_DIVISOR_LSB, 0x14); /* 40 MHz */ + set(AR9170_UART_REG_REMAINDER, 0xb38e); +#else +#error "Unsupported UART clock" +#endif /* CARL9170FW_UART_CLOCK_25M */ + + while (get(AR9170_UART_REG_LINE_STATUS) & + AR9170_UART_LINE_STS_TRANSMITTER_EMPTY) { + if (timeout++ >= 10000) + break; + } +} +#endif /* CONFIG_CARL9170FW_DEBUG_UART */ diff --git a/carl9170fw/carlfw/src/udivsi3_i4i-Os.S b/carl9170fw/carlfw/src/udivsi3_i4i-Os.S new file mode 100644 index 0000000..7bbec4c --- /dev/null +++ b/carl9170fw/carlfw/src/udivsi3_i4i-Os.S @@ -0,0 +1,149 @@ +/* Copyright (C) 2006 Free Software Foundation, Inc. + +This file is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +In addition to the permissions in the GNU General Public License, the +Free Software Foundation gives you unlimited permission to link the +compiled version of this file into combinations with other programs, +and to distribute those combinations without any restriction coming +from the use of this file. (The General Public License restrictions +do apply in other respects; for example, they cover modification of +the file, and distribution when not linked into a combine +executable.) + +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. */ + +/* Moderately Space-optimized libgcc routines for the Renesas SH / + STMicroelectronics ST40 CPUs. + Contributed by J"orn Rennecke joern.rennecke@st.com. */ + +/* Size: 186 bytes jointly for udivsi3_i4i and sdivsi3_i4i + sh4-200 run times: + udiv small divisor: 55 cycles + udiv large divisor: 52 cycles + sdiv small divisor, positive result: 59 cycles + sdiv large divisor, positive result: 56 cycles + sdiv small divisor, negative result: 65 cycles (*) + sdiv large divisor, negative result: 62 cycles (*) + (*): r2 is restored in the rts delay slot and has a lingering latency + of two more cycles. */ + .balign 4 + .global ___udivsi3_i4i + .global ___udivsi3_i4 + .set ___udivsi3_i4, ___udivsi3_i4i + .type ___udivsi3_i4i, @function + .type ___sdivsi3_i4i, @function +___udivsi3_i4i: + sts pr,r1 + mov.l r4,@-r15 + extu.w r5,r0 + cmp/eq r5,r0 + swap.w r4,r0 + shlr16 r4 + bf/s large_divisor + div0u + mov.l r5,@-r15 + shll16 r5 +sdiv_small_divisor: + div1 r5,r4 + bsr div6 + div1 r5,r4 + div1 r5,r4 + bsr div6 + div1 r5,r4 + xtrct r4,r0 + xtrct r0,r4 + bsr div7 + swap.w r4,r4 + div1 r5,r4 + bsr div7 + div1 r5,r4 + xtrct r4,r0 + mov.l @r15+,r5 + swap.w r0,r0 + mov.l @r15+,r4 + jmp @r1 + rotcl r0 +div7: + div1 r5,r4 +div6: + div1 r5,r4; div1 r5,r4; div1 r5,r4 + div1 r5,r4; div1 r5,r4; rts; div1 r5,r4 + +divx3: + rotcl r0 + div1 r5,r4 + rotcl r0 + div1 r5,r4 + rotcl r0 + rts + div1 r5,r4 + +large_divisor: + mov.l r5,@-r15 +sdiv_large_divisor: + xor r4,r0 + .rept 4 + rotcl r0 + bsr divx3 + div1 r5,r4 + .endr + mov.l @r15+,r5 + mov.l @r15+,r4 + jmp @r1 + rotcl r0 + + .global __sdivsi3_i4i + .global __sdivsi3_i4 + .global __sdivsi3 + .set __sdivsi3_i4, __sdivsi3_i4i + .set __sdivsi3, __sdivsi3_i4i +__sdivsi3_i4i: + mov.l r4,@-r15 + cmp/pz r5 + mov.l r5,@-r15 + bt/s pos_divisor + cmp/pz r4 + neg r5,r5 + extu.w r5,r0 + bt/s neg_result + cmp/eq r5,r0 + neg r4,r4 +pos_result: + swap.w r4,r0 + bra sdiv_check_divisor + sts pr,r1 +pos_divisor: + extu.w r5,r0 + bt/s pos_result + cmp/eq r5,r0 + neg r4,r4 +neg_result: + mova negate_result,r0 + ; + mov r0,r1 + swap.w r4,r0 + lds r2,macl + sts pr,r2 +sdiv_check_divisor: + shlr16 r4 + bf/s sdiv_large_divisor + div0u + bra sdiv_small_divisor + shll16 r5 + .balign 4 +negate_result: + neg r0,r0 + jmp @r2 + sts macl,r2 diff --git a/carl9170fw/carlfw/src/wlan.c b/carl9170fw/carlfw/src/wlan.c new file mode 100644 index 0000000..d3328d6 --- /dev/null +++ b/carl9170fw/carlfw/src/wlan.c @@ -0,0 +1,1211 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * Interface to the WLAN part of the chip + * + * Copyright (c) 2000-2005 ZyDAS Technology Corporation + * Copyright (c) 2007-2009 Atheros Communications, Inc. + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2009-2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "shared/phy.h" +#include "hostif.h" +#include "timer.h" +#include "wl.h" +#include "printf.h" +#include "rf.h" +#include "linux/ieee80211.h" +#include "wol.h" + +static void wlan_txunstuck(unsigned int queue) +{ + set_wlan_txq_dma_addr(queue, ((uint32_t) fw.wlan.tx_queue[queue].head) | 1); +} + +#ifdef CONFIG_CARL9170FW_DMA_QUEUE_BUMP +static void wlan_txupdate(unsigned int queue) +{ + set_wlan_txq_dma_addr(queue, ((uint32_t) fw.wlan.tx_queue[queue].head)); +} + +static void wlan_dma_bump(unsigned int qidx) +{ + unsigned int offset = qidx; + uint32_t status, trigger; + + status = get(AR9170_MAC_REG_DMA_STATUS) >> 12; + trigger = get(AR9170_MAC_REG_DMA_TRIGGER) >> 12; + + while (offset != 0) { + status >>= 4; + trigger >>= 4; + offset--; + } + + status &= 0xf; + trigger &= 0xf; + + if ((trigger == 0xa) && (status == 0x8)) { + DBG("UNSTUCK"); + wlan_txunstuck(qidx); + } else { + DBG("UPDATE"); + wlan_txupdate(qidx); + } +} +#endif /* CONFIG_CARL9170FW_DMA_QUEUE_BUMP */ + +#ifdef CONFIG_CARL9170FW_DEBUG +static void wlan_dump_queue(unsigned int qidx) +{ + + struct dma_desc *desc; + struct carl9170_tx_superframe *super; + int entries = 0; + + __for_each_desc(desc, &fw.wlan.tx_queue[qidx]) { + super = get_super(desc); + DBG("%d: %p s:%x c:%x tl:%x ds:%x n:%p l:%p ", entries, desc, + desc->status, desc->ctrl, desc->totalLen, + desc->dataSize, desc->nextAddr, desc->lastAddr); + + DBG("c:%x tr:%d ri:%d l:%x m:%x p:%x fc:%x", + super->s.cookie, super->s.cnt, super->s.rix, + super->f.hdr.length, super->f.hdr.mac.set, + (unsigned int) le32_to_cpu(super->f.hdr.phy.set), + super->f.data.i3e.frame_control); + + entries++; + } + + desc = get_wlan_txq_addr(qidx); + + DBG("Queue: %d: te:%d td:%d h:%p c:%p t:%p", + qidx, entries, queue_len(&fw.wlan.tx_queue[qidx]), + fw.wlan.tx_queue[qidx].head, + desc, fw.wlan.tx_queue[qidx].terminator); + + DBG("HW: t:%x s:%x ac:%x c:%x", + (unsigned int) get(AR9170_MAC_REG_DMA_TRIGGER), + (unsigned int) get(AR9170_MAC_REG_DMA_STATUS), + (unsigned int) get(AR9170_MAC_REG_AMPDU_COUNT), + (unsigned int) get(AR9170_MAC_REG_DMA_TXQX_ADDR_CURR)); +} +#endif /* CONFIG_CARL9170FW_DEBUG */ + +static void wlan_send_buffered_tx_status(void) +{ + unsigned int len; + + while (fw.wlan.tx_status_pending) { + len = min((unsigned int)fw.wlan.tx_status_pending, + CARL9170_RSP_TX_STATUS_NUM); + len = min(len, CARL9170_TX_STATUS_NUM - fw.wlan.tx_status_head_idx); + + /* + * rather than memcpy each individual request into a large buffer, + * we _splice_ them all together. + * + * The only downside is however that we have to be careful around + * the edges of the tx_status_cache. + * + * Note: + * Each tx_status is about 2 bytes. However every command package + * must have a size which is a multiple of 4. + */ + + send_cmd_to_host((len * sizeof(struct carl9170_tx_status) + 3) & ~3, + CARL9170_RSP_TXCOMP, len, (void *) + &fw.wlan.tx_status_cache[fw.wlan.tx_status_head_idx]); + + fw.wlan.tx_status_pending -= len; + fw.wlan.tx_status_head_idx += len; + fw.wlan.tx_status_head_idx %= CARL9170_TX_STATUS_NUM; + } +} + +static struct carl9170_tx_status *wlan_get_tx_status_buffer(void) +{ + struct carl9170_tx_status *tmp; + + tmp = &fw.wlan.tx_status_cache[fw.wlan.tx_status_tail_idx++]; + fw.wlan.tx_status_tail_idx %= CARL9170_TX_STATUS_NUM; + + if (fw.wlan.tx_status_pending == CARL9170_TX_STATUS_NUM) + wlan_send_buffered_tx_status(); + + fw.wlan.tx_status_pending++; + + return tmp; +} + +/* generate _aggregated_ tx_status for the host */ +void wlan_tx_complete(struct carl9170_tx_superframe *super, + bool txs) +{ + struct carl9170_tx_status *status; + + status = wlan_get_tx_status_buffer(); + + /* + * The *unique* cookie and AC_ID is used by the driver for + * frame lookup. + */ + status->cookie = super->s.cookie; + status->queue = super->s.queue; + super->s.cookie = 0; + + /* + * This field holds the number of tries of the rate in + * the rate index field (rix). + */ + status->rix = super->s.rix; + status->tries = super->s.cnt; + status->success = (txs) ? 1 : 0; +} + +static bool wlan_tx_consume_retry(struct carl9170_tx_superframe *super) +{ + /* check if this was the last possible retry with this rate */ + if (unlikely(super->s.cnt >= super->s.ri[super->s.rix].tries)) { + /* end of the road - indicate tx failure */ + if (unlikely(super->s.rix == CARL9170_TX_MAX_RETRY_RATES)) + return false; + + /* check if there are alternative rates available */ + if (!super->s.rr[super->s.rix].set) + return false; + + /* try next retry rate */ + super->f.hdr.phy.set = super->s.rr[super->s.rix].set; + + /* finally - mark the old rate as USED */ + super->s.rix++; + + /* update MAC flags */ + super->f.hdr.mac.erp_prot = super->s.ri[super->s.rix].erp_prot; + super->f.hdr.mac.ampdu = super->s.ri[super->s.rix].ampdu; + + /* reinitialize try counter */ + super->s.cnt = 1; + } else { + /* just increase retry counter */ + super->s.cnt++; + } + + return true; +} + +static inline u16 get_tid(struct ieee80211_hdr *hdr) +{ + return (ieee80211_get_qos_ctl(hdr))[0] & IEEE80211_QOS_CTL_TID_MASK; +} + +/* This function will only work on uint32_t-aligned pointers! */ +static bool same_hdr(const void *_d0, const void *_d1) +{ + const uint32_t *d0 = _d0; + const uint32_t *d1 = _d1; + + /* BUG_ON((unsigned long)d0 & 3 || (unsigned long)d1 & 3)) */ + return !((d0[0] ^ d1[0]) | /* FC + DU */ + (d0[1] ^ d1[1]) | /* addr1 */ + (d0[2] ^ d1[2]) | (d0[3] ^ d1[3]) | /* addr2 + addr3 */ + (d0[4] ^ d1[4])); /* addr3 */ +} + +static inline bool same_aggr(struct ieee80211_hdr *a, struct ieee80211_hdr *b) +{ + return (get_tid(a) == get_tid(b)) || same_hdr(a, b); +} + +static void wlan_tx_ampdu_reset(unsigned int qidx) +{ + fw.wlan.ampdu_prev[qidx] = NULL; +} + +static void wlan_tx_ampdu_end(unsigned int qidx) +{ + struct carl9170_tx_superframe *ht_prev = fw.wlan.ampdu_prev[qidx]; + + if (ht_prev) + ht_prev->f.hdr.mac.ba_end = 1; + + wlan_tx_ampdu_reset(qidx); +} + +static void wlan_tx_ampdu(struct carl9170_tx_superframe *super) +{ + unsigned int qidx = super->s.queue; + struct carl9170_tx_superframe *ht_prev = fw.wlan.ampdu_prev[qidx]; + + if (super->f.hdr.mac.ampdu) { + if (ht_prev && + !same_aggr(&super->f.data.i3e, &ht_prev->f.data.i3e)) + ht_prev->f.hdr.mac.ba_end = 1; + else + super->f.hdr.mac.ba_end = 0; + + fw.wlan.ampdu_prev[qidx] = super; + } else { + wlan_tx_ampdu_end(qidx); + } +} + +/* for all tries */ +static void __wlan_tx(struct dma_desc *desc) +{ + struct carl9170_tx_superframe *super = get_super(desc); + + if (unlikely(super->s.fill_in_tsf)) { + struct ieee80211_mgmt *mgmt = (void *) &super->f.data.i3e; + uint32_t *tsf = (uint32_t *) &mgmt->u.probe_resp.timestamp; + + /* + * Truth be told: this is a hack. + * + * The *real* TSF is definitely going to be higher/older. + * But this hardware emulation code is head and shoulders + * above anything a driver can possibly do. + * + * (even, if it's got an accurate atomic clock source). + */ + + read_tsf(tsf); + } + + wlan_tx_ampdu(super); + +#ifdef CONFIG_CARL9170FW_DEBUG + BUG_ON(fw.phy.psm.state != CARL9170_PSM_WAKE); +#endif /* CONFIG_CARL9170FW_DEBUG */ + + /* insert desc into the right queue */ + dma_put(&fw.wlan.tx_queue[super->s.queue], desc); +} + +static void wlan_assign_seq(struct ieee80211_hdr *hdr, unsigned int vif) +{ + hdr->seq_ctrl &= cpu_to_le16(~IEEE80211_SCTL_SEQ); + hdr->seq_ctrl |= cpu_to_le16(fw.wlan.sequence[vif]); + + if (ieee80211_is_first_frag(hdr->seq_ctrl)) + fw.wlan.sequence[vif] += 0x10; +} + +/* prepares frame for the first transmission */ +static void _wlan_tx(struct dma_desc *desc) +{ + struct carl9170_tx_superframe *super = get_super(desc); + + if (unlikely(super->s.assign_seq)) + wlan_assign_seq(&super->f.data.i3e, super->s.vif_id); + + if (unlikely(super->s.ampdu_commit_density)) { + set(AR9170_MAC_REG_AMPDU_DENSITY, + MOD_VAL(AR9170_MAC_AMPDU_DENSITY, + get(AR9170_MAC_REG_AMPDU_DENSITY), + super->s.ampdu_density)); + } + + if (unlikely(super->s.ampdu_commit_factor)) { + set(AR9170_MAC_REG_AMPDU_FACTOR, + MOD_VAL(AR9170_MAC_AMPDU_FACTOR, + get(AR9170_MAC_REG_AMPDU_FACTOR), + 8 << super->s.ampdu_factor)); + } +} + +/* propagate transmission status back to the driver */ +static bool wlan_tx_status(struct dma_queue *queue, + struct dma_desc *desc) +{ + struct carl9170_tx_superframe *super = get_super(desc); + unsigned int qidx = super->s.queue; + bool txfail = false, success; + + success = true; + + /* update hangcheck */ + fw.wlan.last_super_num[qidx] = 0; + + /* + * Note: + * There could be a corner case when the TXFAIL is set + * even though the frame was properly ACKed by the peer: + * a BlockAckReq with the immediate policy will cause + * the receiving peer to produce a BlockACK unfortunately + * the MAC in this chip seems to be expecting a legacy + * ACK and marks the BAR as failed! + */ + + if (!!(desc->ctrl & AR9170_CTRL_FAIL)) { + txfail = !!(desc->ctrl & AR9170_CTRL_TXFAIL); + + /* reset retry indicator flags */ + desc->ctrl &= ~(AR9170_CTRL_TXFAIL | AR9170_CTRL_BAFAIL); + + /* + * Note: wlan_tx_consume_retry will override the old + * phy [CCK,OFDM, HT, BW20/40, MCS...] and mac vectors + * [AMPDU,RTS/CTS,...] therefore be careful when they + * are used. + */ + if (wlan_tx_consume_retry(super)) { + /* + * retry for simple and aggregated 802.11 frames. + * + * Note: We must not mess up the original frame + * order. + */ + + if (!super->f.hdr.mac.ampdu) { + /* + * 802.11 - 7.1.3.1.5. + * set "Retry Field" for consecutive attempts + * + * Note: For AMPDU see: + * 802.11n 9.9.1.6 "Retransmit Procedures" + */ + super->f.data.i3e.frame_control |= + cpu_to_le16(IEEE80211_FCTL_RETRY); + } + + if (txfail) { + /* Normal TX Failure */ + + /* demise descriptor ownership back to the hardware */ + dma_rearm(desc); + + /* + * And this will get the queue going again. + * To understand why: you have to get the HW + * specs... But sadly I never saw them. + */ + wlan_txunstuck(qidx); + + /* abort cycle - this is necessary due to HW design */ + return false; + } else { + /* (HT-) BlockACK failure */ + + /* + * Unlink the failed attempt and put it into + * the retry queue. The caller routine must + * be aware of this so the frames don't get lost. + */ + +#ifndef CONFIG_CARL9170FW_DEBUG + dma_unlink_head(queue); +#else /* CONFIG_CARL9170FW_DEBUG */ + BUG_ON(dma_unlink_head(queue) != desc); +#endif /* CONFIG_CARL9170FW_DEBUG */ + dma_put(&fw.wlan.tx_retry, desc); + return true; + } + } else { + /* out of frame attempts - discard frame */ + success = false; + } + } + +#ifndef CONFIG_CARL9170FW_DEBUG + dma_unlink_head(queue); +#else /* CONFIG_CARL9170FW_DEBUG */ + BUG_ON(dma_unlink_head(queue) != desc); +#endif /* CONFIG_CARL9170FW_DEBUG */ + if (txfail) { + /* + * Issue the queue bump, + * We need to do this in case this was the frame's last + * possible retry attempt and it unfortunately: it failed. + */ + + wlan_txunstuck(qidx); + } + + unhide_super(desc); + + if (unlikely(super == fw.wlan.fw_desc_data)) { + fw.wlan.fw_desc = desc; + fw.wlan.fw_desc_available = 1; + + if (fw.wlan.fw_desc_callback) + fw.wlan.fw_desc_callback(super, success); + + return true; + } + +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + if (unlikely(super->s.cab)) + fw.wlan.cab_queue_len[super->s.vif_id]--; +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + + wlan_tx_complete(super, success); + + if (ieee80211_is_back_req(super->f.data.i3e.frame_control)) { + fw.wlan.queued_bar--; + } + + /* recycle freed descriptors */ + dma_reclaim(&fw.pta.down_queue, desc); + down_trigger(); + return true; +} + +static void handle_tx_completion(void) +{ + struct dma_desc *desc; + int i; + + for (i = AR9170_TXQ_SPECIAL; i >= AR9170_TXQ0; i--) { + __while_desc_bits(desc, &fw.wlan.tx_queue[i], AR9170_OWN_BITS_SW) { + if (!wlan_tx_status(&fw.wlan.tx_queue[i], desc)) { + /* termination requested. */ + break; + } + } + + wlan_tx_ampdu_reset(i); + + for_each_desc(desc, &fw.wlan.tx_retry) + __wlan_tx(desc); + + wlan_tx_ampdu_end(i); + if (!queue_empty(&fw.wlan.tx_queue[i])) + wlan_trigger(BIT(i)); + } +} + +void __hot wlan_tx(struct dma_desc *desc) +{ + struct carl9170_tx_superframe *super = DESC_PAYLOAD(desc); + + if (ieee80211_is_back_req(super->f.data.i3e.frame_control)) { + fw.wlan.queued_bar++; + } + + /* initialize rate control struct */ + super->s.rix = 0; + super->s.cnt = 1; + hide_super(desc); + +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + if (unlikely(super->s.cab)) { + fw.wlan.cab_queue_len[super->s.vif_id]++; + dma_put(&fw.wlan.cab_queue[super->s.vif_id], desc); + return; + } +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + + _wlan_tx(desc); + __wlan_tx(desc); + wlan_trigger(BIT(super->s.queue)); +} + +void wlan_tx_fw(struct carl9170_tx_superdesc *super, fw_desc_callback_t cb) +{ + if (!fw.wlan.fw_desc_available) + return; + + fw.wlan.fw_desc_available = 0; + + /* Format BlockAck */ + fw.wlan.fw_desc->ctrl = AR9170_CTRL_FS_BIT | AR9170_CTRL_LS_BIT; + fw.wlan.fw_desc->status = AR9170_OWN_BITS_SW; + + fw.wlan.fw_desc->totalLen = fw.wlan.fw_desc->dataSize = super->len; + fw.wlan.fw_desc_data = fw.wlan.fw_desc->dataAddr = super; + fw.wlan.fw_desc->nextAddr = fw.wlan.fw_desc->lastAddr = + fw.wlan.fw_desc; + fw.wlan.fw_desc_callback = cb; + wlan_tx(fw.wlan.fw_desc); +} + +static void wlan_send_buffered_ba(void) +{ + struct carl9170_tx_ba_superframe *baf = &dma_mem.reserved.ba.ba; + struct ieee80211_ba *ba = (struct ieee80211_ba *) &baf->f.ba; + struct carl9170_bar_ctx *ctx; + + if (likely(!fw.wlan.queued_ba)) + return; + + /* there's no point to continue when the ba_desc is not available. */ + if (!fw.wlan.fw_desc_available) + return; + + ctx = &fw.wlan.ba_cache[fw.wlan.ba_head_idx]; + fw.wlan.ba_head_idx++; + fw.wlan.ba_head_idx %= CONFIG_CARL9170FW_BACK_REQS_NUM; + fw.wlan.queued_ba--; + + baf->s.len = sizeof(struct carl9170_tx_superdesc) + + sizeof(struct ar9170_tx_hwdesc) + + sizeof(struct ieee80211_ba); + baf->s.ri[0].tries = 1; + baf->s.cookie = 0; + baf->s.queue = AR9170_TXQ_VO; + baf->f.hdr.length = sizeof(struct ieee80211_ba) + FCS_LEN; + + baf->f.hdr.mac.no_ack = 1; + + baf->f.hdr.phy.modulation = 1; /* OFDM */ + baf->f.hdr.phy.tx_power = 34; /* 17 dBm */ + baf->f.hdr.phy.chains = 1; + baf->f.hdr.phy.mcs = AR9170_TXRX_PHY_RATE_OFDM_6M; + + /* format outgoing BA */ + ba->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_BACK); + ba->duration = cpu_to_le16(0); + + /* the BAR contains all necessary MACs. All we need is to swap them */ + memcpy(ba->ra, ctx->ta, 6); + memcpy(ba->ta, ctx->ra, 6); + + /* + * Unfortunately, we cannot look into the hardware's scoreboard. + * Therefore we have to proceed as described in 802.11n 9.10.7.5 + * and send a null BlockAck. + */ + memset(ba->bitmap, 0x0, sizeof(ba->bitmap)); + + /* + * Both, the original firmare and ath9k set the NO ACK flag in + * the BA Ack Policy subfield. + */ + ba->control = ctx->control | cpu_to_le16(1); + ba->start_seq_num = ctx->start_seq_num; + wlan_tx_fw(&baf->s, NULL); +} + +static struct carl9170_bar_ctx *wlan_get_bar_cache_buffer(void) +{ + struct carl9170_bar_ctx *tmp; + + tmp = &fw.wlan.ba_cache[fw.wlan.ba_tail_idx]; + fw.wlan.ba_tail_idx++; + fw.wlan.ba_tail_idx %= CONFIG_CARL9170FW_BACK_REQS_NUM; + if (fw.wlan.queued_ba < CONFIG_CARL9170FW_BACK_REQS_NUM) + fw.wlan.queued_ba++; + + return tmp; +} + +static void handle_bar(struct dma_desc *desc __unused, struct ieee80211_hdr *hdr, + unsigned int len, unsigned int mac_err) +{ + struct ieee80211_bar *bar; + struct carl9170_bar_ctx *ctx; + + if (unlikely(mac_err)) { + /* + * This check does a number of things: + * 1. checks if the frame is in good nick + * 2. checks if the RA (MAC) matches + */ + return ; + } + + if (unlikely(len < (sizeof(struct ieee80211_bar) + FCS_LEN))) { + /* + * Sneaky, corrupted BARs... but not with us! + */ + + return ; + } + + bar = (void *) hdr; + + if ((bar->control & cpu_to_le16(IEEE80211_BAR_CTRL_MULTI_TID)) || + !(bar->control & cpu_to_le16(IEEE80211_BAR_CTRL_CBMTID_COMPRESSED_BA))) { + /* not implemented yet */ + + return ; + } + + ctx = wlan_get_bar_cache_buffer(); + + memcpy(ctx->ra, bar->ra, 6); + memcpy(ctx->ta, bar->ta, 6); + ctx->control = bar->control; + ctx->start_seq_num = bar->start_seq_num; +} + +static void wlan_check_rx_overrun(void) +{ + uint32_t overruns, total; + + fw.tally.rx_total += total = get(AR9170_MAC_REG_RX_TOTAL); + fw.tally.rx_overrun += overruns = get(AR9170_MAC_REG_RX_OVERRUN); + if (unlikely(overruns)) { + if (overruns == total) { + DBG("RX Overrun"); + fw.wlan.mac_reset++; + } + + wlan_trigger(AR9170_DMA_TRIGGER_RXQ); + } +} + +static unsigned int wlan_rx_filter(struct dma_desc *desc) +{ + struct ieee80211_hdr *hdr; + unsigned int data_len; + unsigned int rx_filter; + unsigned int mac_err; + + data_len = ar9170_get_rx_mpdu_len(desc); + mac_err = ar9170_get_rx_macstatus_error(desc); + +#define AR9170_RX_ERROR_BAD (AR9170_RX_ERROR_FCS | AR9170_RX_ERROR_PLCP) + + if (unlikely(data_len < (4 + 6 + FCS_LEN) || + desc->totalLen > CONFIG_CARL9170FW_RX_FRAME_LEN) || + mac_err & AR9170_RX_ERROR_BAD) { + /* + * This frame is too damaged to do anything + * useful with it. + */ + + return CARL9170_RX_FILTER_BAD; + } + + rx_filter = 0; + if (mac_err & AR9170_RX_ERROR_WRONG_RA) + rx_filter |= CARL9170_RX_FILTER_OTHER_RA; + + if (mac_err & AR9170_RX_ERROR_DECRYPT) + rx_filter |= CARL9170_RX_FILTER_DECRY_FAIL; + + hdr = ar9170_get_rx_i3e(desc); + if (likely(ieee80211_is_data(hdr->frame_control))) { + rx_filter |= CARL9170_RX_FILTER_DATA; + } else if (ieee80211_is_ctl(hdr->frame_control)) { + switch (le16_to_cpu(hdr->frame_control) & IEEE80211_FCTL_STYPE) { + case IEEE80211_STYPE_BACK_REQ: + handle_bar(desc, hdr, data_len, mac_err); + rx_filter |= CARL9170_RX_FILTER_CTL_BACKR; + break; + case IEEE80211_STYPE_PSPOLL: + rx_filter |= CARL9170_RX_FILTER_CTL_PSPOLL; + break; + case IEEE80211_STYPE_BACK: + if (fw.wlan.queued_bar) { + /* + * Don't filter block acks when the application + * has queued BARs. This is because the firmware + * can't do the accouting and the application + * has to sort out if the BA belongs to any BARs. + */ + break; + } + /* otherwise fall through */ + default: + rx_filter |= CARL9170_RX_FILTER_CTL_OTHER; + break; + } + } else { + /* ieee80211_is_mgmt */ + rx_filter |= CARL9170_RX_FILTER_MGMT; + } + + if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) { + wol_rx(rx_filter, hdr, min(data_len, + (unsigned int)AR9170_BLOCK_SIZE)); + } + +#undef AR9170_RX_ERROR_BAD + + return rx_filter; +} + +static void handle_rx(void) +{ + struct dma_desc *desc; + + for_each_desc_not_bits(desc, &fw.wlan.rx_queue, AR9170_OWN_BITS_HW) { + if (!(wlan_rx_filter(desc) & fw.wlan.rx_filter)) { + dma_put(&fw.pta.up_queue, desc); + up_trigger(); + } else { + dma_reclaim(&fw.wlan.rx_queue, desc); + wlan_trigger(AR9170_DMA_TRIGGER_RXQ); + } + } +} + +#ifdef CONFIG_CARL9170FW_CAB_QUEUE +void wlan_cab_flush_queue(const unsigned int vif) +{ + struct dma_queue *cab_queue = &fw.wlan.cab_queue[vif]; + struct dma_desc *desc; + + /* move queued frames into the main tx queues */ + for_each_desc(desc, cab_queue) { + struct carl9170_tx_superframe *super = get_super(desc); + if (!queue_empty(cab_queue)) { + /* + * Set MOREDATA flag for all, + * but the last queued frame. + * see: 802.11-2007 11.2.1.5 f) + * + * This is actually the reason to why + * we need to prevent the reentry. + */ + + super->f.data.i3e.frame_control |= + cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } else { + super->f.data.i3e.frame_control &= + cpu_to_le16(~IEEE80211_FCTL_MOREDATA); + } + + /* ready to roll! */ + _wlan_tx(desc); + __wlan_tx(desc); + wlan_trigger(BIT(super->s.queue)); + } +} + +static uint8_t *beacon_find_ie(uint8_t ie, void *addr, + const unsigned int len) +{ + struct ieee80211_mgmt *mgmt = addr; + uint8_t *pos, *end; + + pos = mgmt->u.beacon.variable; + end = (uint8_t *) ((unsigned long)mgmt + (len - FCS_LEN)); + while (pos < end) { + if (pos + 2 + pos[1] > end) + return NULL; + + if (pos[0] == ie) + return pos; + + pos += pos[1] + 2; + } + + return NULL; +} + +void wlan_modify_beacon(const unsigned int vif, + const unsigned int addr, const unsigned int len) +{ + uint8_t *_ie; + struct ieee80211_tim_ie *ie; + + _ie = beacon_find_ie(WLAN_EID_TIM, (void *)addr, len); + if (likely(_ie)) { + ie = (struct ieee80211_tim_ie *) &_ie[2]; + + if (!queue_empty(&fw.wlan.cab_queue[vif]) && (ie->dtim_count == 0)) { + /* schedule DTIM transfer */ + fw.wlan.cab_flush_trigger[vif] = CARL9170_CAB_TRIGGER_ARMED; + } else if ((fw.wlan.cab_queue_len[vif] == 0) && (fw.wlan.cab_flush_trigger[vif])) { + /* undo all chances to the beacon structure */ + ie->bitmap_ctrl &= ~0x1; + fw.wlan.cab_flush_trigger[vif] = CARL9170_CAB_TRIGGER_EMPTY; + } + + /* Triggered by CARL9170_CAB_TRIGGER_ARMED || CARL9170_CAB_TRIGGER_DEFER */ + if (fw.wlan.cab_flush_trigger[vif]) { + /* Set the almighty Multicast Traffic Indication Bit. */ + ie->bitmap_ctrl |= 0x1; + } + } + + /* + * Ideally, the sequence number should be assigned by the TX arbiter + * hardware. But AFAIK that's not possible, so we have to go for the + * next best thing and write it into the beacon fifo during the open + * beacon update window. + */ + + wlan_assign_seq((struct ieee80211_hdr *)addr, vif); +} + +static void wlan_send_buffered_cab(void) +{ + unsigned int i; + + for (i = 0; i < CARL9170_INTF_NUM; i++) { + if (unlikely(fw.wlan.cab_flush_trigger[i] == CARL9170_CAB_TRIGGER_ARMED)) { + /* + * This is hardcoded into carl9170usb driver. + * + * The driver must set the PRETBTT event to beacon_interval - + * CARL9170_PRETBTT_KUS (usually 6) Kus. + * + * But still, we can only do so much about 802.11-2007 9.3.2.1 & + * 11.2.1.6. Let's hope the current solution is adequate enough. + */ + + if (is_after_msecs(fw.wlan.cab_flush_time, (CARL9170_TBTT_DELTA))) { + wlan_cab_flush_queue(i); + + /* + * This prevents the code from sending new BC/MC frames + * which were queued after the previous buffered traffic + * has been sent out... They will have to wait until the + * next DTIM beacon comes along. + */ + fw.wlan.cab_flush_trigger[i] = CARL9170_CAB_TRIGGER_DEFER; + } + } + + } +} +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + +static void handle_beacon_config(void) +{ + uint32_t bcn_count; + + bcn_count = get(AR9170_MAC_REG_BCN_COUNT); + send_cmd_to_host(4, CARL9170_RSP_BEACON_CONFIG, 0x00, + (uint8_t *) &bcn_count); +} + +static void handle_pretbtt(void) +{ +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + fw.wlan.cab_flush_time = get_clock_counter(); +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + rf_psm(); + + send_cmd_to_host(4, CARL9170_RSP_PRETBTT, 0x00, + (uint8_t *) &fw.phy.psm.state); +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ +} + +static void handle_atim(void) +{ + send_cmd_to_host(0, CARL9170_RSP_ATIM, 0x00, NULL); +} + +#ifdef CONFIG_CARL9170FW_DEBUG +static void handle_qos(void) +{ + /* + * What is the QoS Bit used for? + * Is it only an indicator for TXOP & Burst, or + * should we do something here? + */ +} + +static void handle_radar(void) +{ + send_cmd_to_host(0, CARL9170_RSP_RADAR, 0x00, NULL); +} +#endif /* CONFIG_CARL9170FW_DEBUG */ + +static void wlan_janitor(void) +{ +#ifdef CONFIG_CARL9170FW_CAB_QUEUE + wlan_send_buffered_cab(); +#endif /* CONFIG_CARL9170FW_CAB_QUEUE */ + + wlan_send_buffered_tx_status(); + + wlan_send_buffered_ba(); + + wol_janitor(); +} + +void handle_wlan(void) +{ + uint32_t intr; + + intr = get(AR9170_MAC_REG_INT_CTRL); + /* ACK Interrupt */ + set(AR9170_MAC_REG_INT_CTRL, intr); + +#define HANDLER(intr, flag, func) \ + do { \ + if ((intr & flag) != 0) { \ + func(); \ + } \ + } while (0) + + intr |= fw.wlan.soft_int; + fw.wlan.soft_int = 0; + + HANDLER(intr, AR9170_MAC_INT_PRETBTT, handle_pretbtt); + + HANDLER(intr, AR9170_MAC_INT_ATIM, handle_atim); + + HANDLER(intr, AR9170_MAC_INT_RXC, handle_rx); + + HANDLER(intr, (AR9170_MAC_INT_TXC | AR9170_MAC_INT_RETRY_FAIL), + handle_tx_completion); + +#ifdef CONFIG_CARL9170FW_DEBUG + HANDLER(intr, AR9170_MAC_INT_QOS, handle_qos); + + HANDLER(intr, AR9170_MAC_INT_RADAR, handle_radar); +#endif /* CONFIG_CARL9170FW_DEBUG */ + + HANDLER(intr, AR9170_MAC_INT_CFG_BCN, handle_beacon_config); + + if (unlikely(intr)) + DBG("Unhandled Interrupt %x\n", (unsigned int) intr); + + wlan_janitor(); + +#undef HANDLER +} + +enum { + CARL9170FW_TX_MAC_BUMP = 4, + CARL9170FW_TX_MAC_DEBUG = 6, + CARL9170FW_TX_MAC_RESET = 7, +}; + +static void wlan_check_hang(void) +{ + struct dma_desc *desc; + int i; + + for (i = AR9170_TXQ_SPECIAL; i >= AR9170_TXQ0; i--) { + if (queue_empty(&fw.wlan.tx_queue[i])) { + /* Nothing to do here... move along */ + continue; + } + + /* fetch the current DMA queue position */ + desc = (struct dma_desc *)get_wlan_txq_addr(i); + + /* Stuck frame detection */ + if (unlikely(DESC_PAYLOAD(desc) == fw.wlan.last_super[i])) { + fw.wlan.last_super_num[i]++; + + if (unlikely(fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_RESET)) { + /* + * schedule MAC reset (aka OFF/ON => dead) + * + * This will almost certainly kill + * the device for good, but it's the + * recommended thing to do... + */ + + fw.wlan.mac_reset++; + } + +#ifdef CONFIG_CARL9170FW_DEBUG + if (unlikely(fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_DEBUG)) { + /* + * Sigh, the queue is almost certainly + * dead. Dump the queue content to the + * user, maybe we find out why it got + * so stuck. + */ + + wlan_dump_queue(i); + } +#endif /* CONFIG_CARL9170FW_DEBUG */ + +#ifdef CONFIG_CARL9170FW_DMA_QUEUE_BUMP + if (unlikely(fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_BUMP)) { + /* + * Hrrm, bump the queue a bit. + * maybe this will get it going again. + */ + + wlan_dma_bump(i); + wlan_trigger(BIT(i)); + } +#endif /* CONFIG_CARL9170FW_DMA_QUEUE_BUMP */ + } else { + /* Nothing stuck */ + fw.wlan.last_super[i] = DESC_PAYLOAD(desc); + fw.wlan.last_super_num[i] = 0; + } + } +} + +#ifdef CONFIG_CARL9170FW_FW_MAC_RESET +/* + * NB: Resetting the MAC is a two-edged sword. + * On most occasions, it does what it is supposed to do. + * But there is a chance that this will make it + * even worse and the radio dies silently. + */ +static void wlan_mac_reset(void) +{ + uint32_t val; + uint32_t agg_wait_counter; + uint32_t agg_density; + uint32_t bcn_start_addr; + uint32_t rctl, rcth; + uint32_t cam_mode; + uint32_t ack_power; + uint32_t rts_cts_tpc; + uint32_t rts_cts_rate; + int i; + +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + uint32_t rx_BB; +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ + +#ifdef CONFIG_CARL9170FW_NOISY_MAC_RESET + INFO("MAC RESET"); +#endif /* CONFIG_CARL9170FW_NOISY_MAC_RESET */ + + /* Save aggregation parameters */ + agg_wait_counter = get(AR9170_MAC_REG_AMPDU_FACTOR); + agg_density = get(AR9170_MAC_REG_AMPDU_DENSITY); + + bcn_start_addr = get(AR9170_MAC_REG_BCN_ADDR); + + cam_mode = get(AR9170_MAC_REG_CAM_MODE); + rctl = get(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L); + rcth = get(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H); + + ack_power = get(AR9170_MAC_REG_ACK_TPC); + rts_cts_tpc = get(AR9170_MAC_REG_RTS_CTS_TPC); + rts_cts_rate = get(AR9170_MAC_REG_RTS_CTS_RATE); + +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + /* 0x1c8960 write only */ + rx_BB = get(AR9170_PHY_REG_SWITCH_CHAIN_0); +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ + + /* TX/RX must be stopped by now */ + val = get(AR9170_MAC_REG_POWER_STATE_CTRL); + + val |= AR9170_MAC_POWER_STATE_CTRL_RESET; + + /* + * Manipulate CCA threshold to stop transmission + * + * set(AR9170_PHY_REG_CCA_THRESHOLD, 0x300); + */ + + /* + * check Rx state in 0(idle) 9(disable) + * + * chState = (get(AR9170_MAC_REG_MISC_684) >> 16) & 0xf; + * while( (chState != 0) && (chState != 9)) { + * chState = (get(AR9170_MAC_REG_MISC_684) >> 16) & 0xf; + * } + */ + + set(AR9170_MAC_REG_POWER_STATE_CTRL, val); + + delay(2); + + /* Restore aggregation parameters */ + set(AR9170_MAC_REG_AMPDU_FACTOR, agg_wait_counter); + set(AR9170_MAC_REG_AMPDU_DENSITY, agg_density); + + set(AR9170_MAC_REG_BCN_ADDR, bcn_start_addr); + set(AR9170_MAC_REG_CAM_MODE, cam_mode); + set(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L, rctl); + set(AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H, rcth); + + set(AR9170_MAC_REG_RTS_CTS_TPC, rts_cts_tpc); + set(AR9170_MAC_REG_ACK_TPC, ack_power); + set(AR9170_MAC_REG_RTS_CTS_RATE, rts_cts_rate); + +#ifdef CONFIG_CARL9170FW_RADIO_FUNCTIONS + set(AR9170_PHY_REG_SWITCH_CHAIN_2, rx_BB); +#endif /* CONFIG_CARL9170FW_RADIO_FUNCTIONS */ + + /* + * Manipulate CCA threshold to resume transmission + * + * set(AR9170_PHY_REG_CCA_THRESHOLD, 0x0); + */ + + val = AR9170_DMA_TRIGGER_RXQ; + /* Reinitialize all WLAN TX DMA queues. */ + for (i = AR9170_TXQ_SPECIAL; i >= AR9170_TXQ0; i--) { + struct dma_desc *iter; + + __for_each_desc_bits(iter, &fw.wlan.tx_queue[i], AR9170_OWN_BITS_SW); + + /* kill the stuck frame */ + if (!is_terminator(&fw.wlan.tx_queue[i], iter) && + fw.wlan.last_super_num[i] >= CARL9170FW_TX_MAC_RESET && + fw.wlan.last_super[i] == DESC_PAYLOAD(iter)) { + struct carl9170_tx_superframe *super = get_super(iter); + + iter->status = AR9170_OWN_BITS_SW; + /* + * Mark the frame as failed. + * The BAFAIL flag allows the frame to sail through + * wlan_tx_status without much "unstuck" trouble. + */ + iter->ctrl &= ~(AR9170_CTRL_FAIL); + iter->ctrl |= AR9170_CTRL_BAFAIL; + + super->s.cnt = CARL9170_TX_MAX_RATE_TRIES; + super->s.rix = CARL9170_TX_MAX_RETRY_RATES; + + fw.wlan.last_super_num[i] = 0; + fw.wlan.last_super[i] = NULL; + iter = iter->lastAddr->nextAddr; + } + + set_wlan_txq_dma_addr(i, (uint32_t) iter); + if (!is_terminator(&fw.wlan.tx_queue[i], iter)) + val |= BIT(i); + + DBG("Q:%d l:%d h:%p t:%p cu:%p it:%p ct:%x st:%x\n", i, queue_len(&fw.wlan.tx_queue[i]), + fw.wlan.tx_queue[i].head, fw.wlan.tx_queue[i].terminator, + get_wlan_txq_addr(i), iter, iter->ctrl, iter->status); + } + + fw.wlan.soft_int |= AR9170_MAC_INT_RXC | AR9170_MAC_INT_TXC | + AR9170_MAC_INT_RETRY_FAIL; + + set(AR9170_MAC_REG_DMA_RXQ_ADDR, (uint32_t) fw.wlan.rx_queue.head); + wlan_trigger(val); +} +#else +static void wlan_mac_reset(void) +{ + /* The driver takes care of reinitializing the device */ + BUG("MAC RESET"); +} +#endif /* CONFIG_CARL9170FW_FW_MAC_RESET */ + +void __cold wlan_timer(void) +{ + unsigned int cached_mac_reset; + + cached_mac_reset = fw.wlan.mac_reset; + + /* TX Queue Hang check */ + wlan_check_hang(); + + /* RX Overrun check */ + wlan_check_rx_overrun(); + + if (unlikely(fw.wlan.mac_reset >= CARL9170_MAC_RESET_RESET)) { + wlan_mac_reset(); + fw.wlan.mac_reset = CARL9170_MAC_RESET_OFF; + } else { + if (fw.wlan.mac_reset && cached_mac_reset == fw.wlan.mac_reset) + fw.wlan.mac_reset--; + } +} diff --git a/carl9170fw/carlfw/src/wol.c b/carl9170fw/carlfw/src/wol.c new file mode 100644 index 0000000..7afb8e5 --- /dev/null +++ b/carl9170fw/carlfw/src/wol.c @@ -0,0 +1,287 @@ +/* + * carl9170 firmware - used by the ar9170 wireless device + * + * WakeUp on WLAN functions + * + * Copyright 2011 Christian Lamparter <chunkeey@googlemail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "carl9170.h" +#include "shared/phy.h" +#include "timer.h" +#include "wl.h" +#include "printf.h" +#include "rf.h" +#include "wol.h" +#include "linux/ieee80211.h" + +#ifdef CONFIG_CARL9170FW_WOL + +void wol_cmd(const struct carl9170_wol_cmd *cmd) +{ + memcpy(&fw.wol.cmd, cmd, sizeof(cmd)); +} + +void wol_prepare(void) +{ + /* set MAC filter */ + memcpy((void *)AR9170_MAC_REG_MAC_ADDR_L, fw.wol.cmd.mac, 6); + memcpy((void *)AR9170_MAC_REG_BSSID_L, fw.wol.cmd.bssid, 6); + set(AR9170_MAC_REG_RX_CONTROL, AR9170_MAC_RX_CTRL_DEAGG); + + /* set filter policy to: discard everything */ + fw.wlan.rx_filter = CARL9170_RX_FILTER_EVERYTHING; + + /* reenable rx dma */ + wlan_trigger(AR9170_DMA_TRIGGER_RXQ); + + /* initialize the last_beacon timer */ + fw.wol.last_null = fw.wol.last_beacon = get_clock_counter(); +} + +#ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS +static bool wlan_rx_wol_magic_packet(const struct ieee80211_hdr *hdr, const 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! + */ + + mac = (const unsigned char *) AR9170_MAC_REG_MAC_ADDR_L; + + 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)) + return true; + + data++; + } + + return false; +} + +static void wlan_wol_connect_callback(void __unused *dummy, bool success) +{ + if (success) + fw.wol.lost_null = 0; + else + fw.wol.lost_null++; +} + +static void wlan_wol_connection_monitor(void) +{ + struct carl9170_tx_null_superframe *nullf = &dma_mem.reserved.cmd.null; + struct ieee80211_hdr *null = (struct ieee80211_hdr *) &nullf->f.null; + + if (!fw.wlan.fw_desc_available) + return; + + memset(nullf, 0, sizeof(*nullf)); + + nullf->s.len = sizeof(struct carl9170_tx_superdesc) + + sizeof(struct ar9170_tx_hwdesc) + + sizeof(struct ieee80211_hdr); + nullf->s.ri[0].tries = 3; + nullf->s.assign_seq = true; + nullf->s.queue = AR9170_TXQ_VO; + nullf->f.hdr.length = sizeof(struct ieee80211_hdr) + FCS_LEN; + + nullf->f.hdr.mac.backoff = 1; + nullf->f.hdr.mac.hw_duration = 1; + nullf->f.hdr.mac.erp_prot = AR9170_TX_MAC_PROT_RTS; + + nullf->f.hdr.phy.modulation = AR9170_TX_PHY_MOD_OFDM; + nullf->f.hdr.phy.bandwidth = AR9170_TX_PHY_BW_20MHZ; + nullf->f.hdr.phy.chains = AR9170_TX_PHY_TXCHAIN_2; + nullf->f.hdr.phy.tx_power = 29; /* 14.5 dBm */ + nullf->f.hdr.phy.mcs = AR9170_TXRX_PHY_RATE_OFDM_6M; + + /* format outgoing nullfunc */ + null->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_NULLFUNC | IEEE80211_FCTL_TODS); + + memcpy(null->addr1, fw.wol.cmd.bssid, 6); + memcpy(null->addr2, fw.wol.cmd.mac, 6); + memcpy(null->addr3, fw.wol.cmd.bssid, 6); + + wlan_tx_fw(&nullf->s, wlan_wol_connect_callback); +} + +static bool wlan_rx_wol_disconnect(const unsigned int rx_filter, + const struct ieee80211_hdr *hdr, + const unsigned int __unused len) +{ + const unsigned char *bssid; + bssid = (const unsigned char *) AR9170_MAC_REG_BSSID_L; + + /* should catch both broadcast and unicast MLMEs */ + if (!(rx_filter & CARL9170_RX_FILTER_OTHER_RA)) { + if (ieee80211_is_deauth(hdr->frame_control) || + ieee80211_is_disassoc(hdr->frame_control)) + return true; + } + + if (ieee80211_is_beacon(hdr->frame_control) && + compare_ether_address(hdr->addr3, bssid)) { + fw.wol.last_beacon = get_clock_counter(); + } + + return false; +} + +#endif /* CARL9170FW_WOL_NL80211_TRIGGERS */ + +#ifdef CONFIG_CARL9170FW_WOL_PROBE_REQUEST + +/* + * Note: CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID is not a real + * string. We have to be careful not to add a \0 at the end. + */ +static const struct { + u8 ssid_ie; + u8 ssid_len; + u8 ssid[sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1]; +} __packed probe_req = { + .ssid_ie = WLAN_EID_SSID, + .ssid_len = sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1, + .ssid = CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID, +}; + +static bool wlan_rx_wol_probe_ssid(const struct ieee80211_hdr *hdr, const unsigned int len) +{ + const unsigned char *data, *end, *scan = (void *) &probe_req; + + /* + * IEEE 802.11-2007 7.3.2.1 specifies that the SSID is no + * longer than 32 octets. + */ + BUILD_BUG_ON((sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1) > 32); + + if (ieee80211_is_probe_req(hdr->frame_control)) { + unsigned int i; + end = (u8 *)((unsigned long)hdr + len); + + /* + * The position of the SSID information element inside + * a probe request frame is more or less "fixed". + */ + data = (u8 *)((struct ieee80211_mgmt *)hdr)->u.probe_req.variable; + for (i = 0; i < (unsigned int)(probe_req.ssid_len + 1); i++) { + if (data > end || scan[i] != data[i]) + return false; + } + + return true; + } + + return false; +} +#endif /* CONFIG_CARL9170FW_WOL_PROBE_REQUEST */ + +void wol_rx(const unsigned int rx_filter __unused, const struct ieee80211_hdr *hdr __unused, const unsigned int len __unused) +{ +#ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS + /* Disconnect is always enabled */ + if (fw.wol.cmd.flags & CARL9170_WOL_DISCONNECT && + rx_filter & CARL9170_RX_FILTER_MGMT) + fw.wol.wake_up |= wlan_rx_wol_disconnect(rx_filter, hdr, len); + + if (fw.wol.cmd.flags & CARL9170_WOL_MAGIC_PKT && + rx_filter & CARL9170_RX_FILTER_DATA) + fw.wol.wake_up |= wlan_rx_wol_magic_packet(hdr, len); +#endif /* CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS */ + +#ifdef CONFIG_CARL9170FW_WOL_PROBE_REQUEST + if (rx_filter & CARL9170_RX_FILTER_MGMT) + fw.wol.wake_up |= wlan_rx_wol_probe_ssid(hdr, len); +#endif /* CONFIG_CARL9170FW_WOL_PROBE_REQUEST */ +} + +void wol_janitor(void) +{ + if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) { +#ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS + if (fw.wol.cmd.flags & CARL9170_WOL_DISCONNECT) { + /* + * connection lost after 10sec without receiving + * a beacon + */ + if (is_after_msecs(fw.wol.last_beacon, 10000)) + fw.wol.wake_up |= true; + + if (fw.wol.cmd.null_interval && + is_after_msecs(fw.wol.last_null, fw.wol.cmd.null_interval)) + wlan_wol_connection_monitor(); + + if (fw.wol.lost_null >= 5) + fw.wol.wake_up |= true; + } +#endif /* CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS */ + + if (fw.wol.wake_up) { + fw.suspend_mode = CARL9170_AWAKE_HOST; + set(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE); + } + } +} +#else + +#endif /* CONFIG_CARL9170FW_WOL */ |