diff options
author | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
---|---|---|
committer | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
commit | 849369d6c66d3054688672f97d31fceb8e8230fb (patch) | |
tree | 6135abc790ca67dedbe07c39806591e70eda81ce /drivers/net/wireless/wl12xx | |
download | linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2 linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip |
initial_commit
Diffstat (limited to 'drivers/net/wireless/wl12xx')
36 files changed, 19695 insertions, 0 deletions
diff --git a/drivers/net/wireless/wl12xx/Kconfig b/drivers/net/wireless/wl12xx/Kconfig new file mode 100644 index 00000000..35ce7b0f --- /dev/null +++ b/drivers/net/wireless/wl12xx/Kconfig @@ -0,0 +1,68 @@ +menuconfig WL12XX_MENU + tristate "TI wl12xx driver support" + depends on MAC80211 && EXPERIMENTAL + ---help--- + This will enable TI wl12xx driver support for the following chips: + wl1271, wl1273, wl1281 and wl1283. + The drivers make use of the mac80211 stack. + +config WL12XX + tristate "TI wl12xx support" + depends on WL12XX_MENU && GENERIC_HARDIRQS + depends on INET + select FW_LOADER + select CRC7 + ---help--- + This module adds support for wireless adapters based on TI wl1271 and + TI wl1273 chipsets. This module does *not* include support for wl1251. + For wl1251 support, use the separate homonymous driver instead. + + If you choose to build a module, it will be called wl12xx. Say N if + unsure. + +config WL12XX_HT + bool "TI wl12xx 802.11 HT support (EXPERIMENTAL)" + depends on WL12XX && EXPERIMENTAL + default n + ---help--- + This will enable 802.11 HT support in the wl12xx module. + + That configuration is temporary due to the code incomplete and + still in testing process. + +config WL12XX_SPI + tristate "TI wl12xx SPI support" + depends on WL12XX && SPI_MASTER + ---help--- + This module adds support for the SPI interface of adapters using + TI wl12xx chipsets. Select this if your platform is using + the SPI bus. + + If you choose to build a module, it'll be called wl12xx_spi. + Say N if unsure. + +config WL12XX_SDIO + tristate "TI wl12xx SDIO support" + depends on WL12XX && MMC + ---help--- + This module adds support for the SDIO interface of adapters using + TI wl12xx chipsets. Select this if your platform is using + the SDIO bus. + + If you choose to build a module, it'll be called wl12xx_sdio. + Say N if unsure. + +config WL12XX_SDIO_TEST + tristate "TI wl12xx SDIO testing support" + depends on WL12XX && MMC && WL12XX_SDIO + default n + ---help--- + This module adds support for the SDIO bus testing with the + TI wl12xx chipsets. You probably don't want this unless you are + testing a new hardware platform. Select this if you want to test the + SDIO bus which is connected to the wl12xx chip. + +config WL12XX_PLATFORM_DATA + bool + depends on WL12XX_SDIO != n || WL1251_SDIO != n + default y diff --git a/drivers/net/wireless/wl12xx/Makefile b/drivers/net/wireless/wl12xx/Makefile new file mode 100644 index 00000000..521c0414 --- /dev/null +++ b/drivers/net/wireless/wl12xx/Makefile @@ -0,0 +1,16 @@ +wl12xx-objs = main.o cmd.o io.o event.o tx.o rx.o ps.o acx.o \ + boot.o init.o debugfs.o scan.o + +wl12xx_spi-objs = spi.o +wl12xx_sdio-objs = sdio.o +wl12xx_sdio_test-objs = sdio_test.o + +wl12xx-$(CONFIG_NL80211_TESTMODE) += testmode.o +obj-$(CONFIG_WL12XX) += wl12xx.o +obj-$(CONFIG_WL12XX_SPI) += wl12xx_spi.o +obj-$(CONFIG_WL12XX_SDIO) += wl12xx_sdio.o + +obj-$(CONFIG_WL12XX_SDIO_TEST) += wl12xx_sdio_test.o + +# small builtin driver bit +obj-$(CONFIG_WL12XX_PLATFORM_DATA) += wl12xx_platform_data.o diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c new file mode 100644 index 00000000..c6ee530e --- /dev/null +++ b/drivers/net/wireless/wl12xx/acx.c @@ -0,0 +1,1722 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "acx.h" + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/crc7.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> + +#include "wl12xx.h" +#include "wl12xx_80211.h" +#include "reg.h" +#include "ps.h" + +int wl1271_acx_wake_up_conditions(struct wl1271 *wl) +{ + struct acx_wake_up_condition *wake_up; + int ret; + + wl1271_debug(DEBUG_ACX, "acx wake up conditions"); + + wake_up = kzalloc(sizeof(*wake_up), GFP_KERNEL); + if (!wake_up) { + ret = -ENOMEM; + goto out; + } + + wake_up->wake_up_event = wl->conf.conn.wake_up_event; + wake_up->listen_interval = wl->conf.conn.listen_interval; + + ret = wl1271_cmd_configure(wl, ACX_WAKE_UP_CONDITIONS, + wake_up, sizeof(*wake_up)); + if (ret < 0) { + wl1271_warning("could not set wake up conditions: %d", ret); + goto out; + } + +out: + kfree(wake_up); + return ret; +} + +int wl1271_acx_sleep_auth(struct wl1271 *wl, u8 sleep_auth) +{ + struct acx_sleep_auth *auth; + int ret; + + wl1271_debug(DEBUG_ACX, "acx sleep auth"); + + auth = kzalloc(sizeof(*auth), GFP_KERNEL); + if (!auth) { + ret = -ENOMEM; + goto out; + } + + auth->sleep_auth = sleep_auth; + + ret = wl1271_cmd_configure(wl, ACX_SLEEP_AUTH, auth, sizeof(*auth)); + if (ret < 0) + return ret; + +out: + kfree(auth); + return ret; +} + +int wl1271_acx_tx_power(struct wl1271 *wl, int power) +{ + struct acx_current_tx_power *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx dot11_cur_tx_pwr"); + + if (power < 0 || power > 25) + return -EINVAL; + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->current_tx_power = power * 10; + + ret = wl1271_cmd_configure(wl, DOT11_CUR_TX_PWR, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("configure of tx power failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_feature_cfg(struct wl1271 *wl) +{ + struct acx_feature_config *feature; + int ret; + + wl1271_debug(DEBUG_ACX, "acx feature cfg"); + + feature = kzalloc(sizeof(*feature), GFP_KERNEL); + if (!feature) { + ret = -ENOMEM; + goto out; + } + + /* DF_ENCRYPTION_DISABLE and DF_SNIFF_MODE_ENABLE are disabled */ + feature->data_flow_options = 0; + feature->options = 0; + + ret = wl1271_cmd_configure(wl, ACX_FEATURE_CFG, + feature, sizeof(*feature)); + if (ret < 0) { + wl1271_error("Couldnt set HW encryption"); + goto out; + } + +out: + kfree(feature); + return ret; +} + +int wl1271_acx_mem_map(struct wl1271 *wl, struct acx_header *mem_map, + size_t len) +{ + int ret; + + wl1271_debug(DEBUG_ACX, "acx mem map"); + + ret = wl1271_cmd_interrogate(wl, ACX_MEM_MAP, mem_map, len); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_acx_rx_msdu_life_time(struct wl1271 *wl) +{ + struct acx_rx_msdu_lifetime *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx rx msdu life time"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->lifetime = cpu_to_le32(wl->conf.rx.rx_msdu_life_time); + ret = wl1271_cmd_configure(wl, DOT11_RX_MSDU_LIFE_TIME, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set rx msdu life time: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_rx_config(struct wl1271 *wl, u32 config, u32 filter) +{ + struct acx_rx_config *rx_config; + int ret; + + wl1271_debug(DEBUG_ACX, "acx rx config"); + + rx_config = kzalloc(sizeof(*rx_config), GFP_KERNEL); + if (!rx_config) { + ret = -ENOMEM; + goto out; + } + + rx_config->config_options = cpu_to_le32(config); + rx_config->filter_options = cpu_to_le32(filter); + + ret = wl1271_cmd_configure(wl, ACX_RX_CFG, + rx_config, sizeof(*rx_config)); + if (ret < 0) { + wl1271_warning("failed to set rx config: %d", ret); + goto out; + } + +out: + kfree(rx_config); + return ret; +} + +int wl1271_acx_pd_threshold(struct wl1271 *wl) +{ + struct acx_packet_detection *pd; + int ret; + + wl1271_debug(DEBUG_ACX, "acx data pd threshold"); + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) { + ret = -ENOMEM; + goto out; + } + + pd->threshold = cpu_to_le32(wl->conf.rx.packet_detection_threshold); + + ret = wl1271_cmd_configure(wl, ACX_PD_THRESHOLD, pd, sizeof(*pd)); + if (ret < 0) { + wl1271_warning("failed to set pd threshold: %d", ret); + goto out; + } + +out: + kfree(pd); + return ret; +} + +int wl1271_acx_slot(struct wl1271 *wl, enum acx_slot_type slot_time) +{ + struct acx_slot *slot; + int ret; + + wl1271_debug(DEBUG_ACX, "acx slot"); + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) { + ret = -ENOMEM; + goto out; + } + + slot->wone_index = STATION_WONE_INDEX; + slot->slot_time = slot_time; + + ret = wl1271_cmd_configure(wl, ACX_SLOT, slot, sizeof(*slot)); + if (ret < 0) { + wl1271_warning("failed to set slot time: %d", ret); + goto out; + } + +out: + kfree(slot); + return ret; +} + +int wl1271_acx_group_address_tbl(struct wl1271 *wl, bool enable, + void *mc_list, u32 mc_list_len) +{ + struct acx_dot11_grp_addr_tbl *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx group address tbl"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* MAC filtering */ + acx->enabled = enable; + acx->num_groups = mc_list_len; + memcpy(acx->mac_table, mc_list, mc_list_len * ETH_ALEN); + + ret = wl1271_cmd_configure(wl, DOT11_GROUP_ADDRESS_TBL, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set group addr table: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_service_period_timeout(struct wl1271 *wl) +{ + struct acx_rx_timeout *rx_timeout; + int ret; + + rx_timeout = kzalloc(sizeof(*rx_timeout), GFP_KERNEL); + if (!rx_timeout) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_ACX, "acx service period timeout"); + + rx_timeout->ps_poll_timeout = cpu_to_le16(wl->conf.rx.ps_poll_timeout); + rx_timeout->upsd_timeout = cpu_to_le16(wl->conf.rx.upsd_timeout); + + ret = wl1271_cmd_configure(wl, ACX_SERVICE_PERIOD_TIMEOUT, + rx_timeout, sizeof(*rx_timeout)); + if (ret < 0) { + wl1271_warning("failed to set service period timeout: %d", + ret); + goto out; + } + +out: + kfree(rx_timeout); + return ret; +} + +int wl1271_acx_rts_threshold(struct wl1271 *wl, u32 rts_threshold) +{ + struct acx_rts_threshold *rts; + int ret; + + /* + * If the RTS threshold is not configured or out of range, use the + * default value. + */ + if (rts_threshold > IEEE80211_MAX_RTS_THRESHOLD) + rts_threshold = wl->conf.rx.rts_threshold; + + wl1271_debug(DEBUG_ACX, "acx rts threshold: %d", rts_threshold); + + rts = kzalloc(sizeof(*rts), GFP_KERNEL); + if (!rts) { + ret = -ENOMEM; + goto out; + } + + rts->threshold = cpu_to_le16((u16)rts_threshold); + + ret = wl1271_cmd_configure(wl, DOT11_RTS_THRESHOLD, rts, sizeof(*rts)); + if (ret < 0) { + wl1271_warning("failed to set rts threshold: %d", ret); + goto out; + } + +out: + kfree(rts); + return ret; +} + +int wl1271_acx_dco_itrim_params(struct wl1271 *wl) +{ + struct acx_dco_itrim_params *dco; + struct conf_itrim_settings *c = &wl->conf.itrim; + int ret; + + wl1271_debug(DEBUG_ACX, "acx dco itrim parameters"); + + dco = kzalloc(sizeof(*dco), GFP_KERNEL); + if (!dco) { + ret = -ENOMEM; + goto out; + } + + dco->enable = c->enable; + dco->timeout = cpu_to_le32(c->timeout); + + ret = wl1271_cmd_configure(wl, ACX_SET_DCO_ITRIM_PARAMS, + dco, sizeof(*dco)); + if (ret < 0) { + wl1271_warning("failed to set dco itrim parameters: %d", ret); + goto out; + } + +out: + kfree(dco); + return ret; +} + +int wl1271_acx_beacon_filter_opt(struct wl1271 *wl, bool enable_filter) +{ + struct acx_beacon_filter_option *beacon_filter = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx beacon filter opt"); + + if (enable_filter && + wl->conf.conn.bcn_filt_mode == CONF_BCN_FILT_MODE_DISABLED) + goto out; + + beacon_filter = kzalloc(sizeof(*beacon_filter), GFP_KERNEL); + if (!beacon_filter) { + ret = -ENOMEM; + goto out; + } + + beacon_filter->enable = enable_filter; + + /* + * When set to zero, and the filter is enabled, beacons + * without the unicast TIM bit set are dropped. + */ + beacon_filter->max_num_beacons = 0; + + ret = wl1271_cmd_configure(wl, ACX_BEACON_FILTER_OPT, + beacon_filter, sizeof(*beacon_filter)); + if (ret < 0) { + wl1271_warning("failed to set beacon filter opt: %d", ret); + goto out; + } + +out: + kfree(beacon_filter); + return ret; +} + +int wl1271_acx_beacon_filter_table(struct wl1271 *wl) +{ + struct acx_beacon_filter_ie_table *ie_table; + int i, idx = 0; + int ret; + bool vendor_spec = false; + + wl1271_debug(DEBUG_ACX, "acx beacon filter table"); + + ie_table = kzalloc(sizeof(*ie_table), GFP_KERNEL); + if (!ie_table) { + ret = -ENOMEM; + goto out; + } + + /* configure default beacon pass-through rules */ + ie_table->num_ie = 0; + for (i = 0; i < wl->conf.conn.bcn_filt_ie_count; i++) { + struct conf_bcn_filt_rule *r = &(wl->conf.conn.bcn_filt_ie[i]); + ie_table->table[idx++] = r->ie; + ie_table->table[idx++] = r->rule; + + if (r->ie == WLAN_EID_VENDOR_SPECIFIC) { + /* only one vendor specific ie allowed */ + if (vendor_spec) + continue; + + /* for vendor specific rules configure the + additional fields */ + memcpy(&(ie_table->table[idx]), r->oui, + CONF_BCN_IE_OUI_LEN); + idx += CONF_BCN_IE_OUI_LEN; + ie_table->table[idx++] = r->type; + memcpy(&(ie_table->table[idx]), r->version, + CONF_BCN_IE_VER_LEN); + idx += CONF_BCN_IE_VER_LEN; + vendor_spec = true; + } + + ie_table->num_ie++; + } + + ret = wl1271_cmd_configure(wl, ACX_BEACON_FILTER_TABLE, + ie_table, sizeof(*ie_table)); + if (ret < 0) { + wl1271_warning("failed to set beacon filter table: %d", ret); + goto out; + } + +out: + kfree(ie_table); + return ret; +} + +#define ACX_CONN_MONIT_DISABLE_VALUE 0xffffffff + +int wl1271_acx_conn_monit_params(struct wl1271 *wl, bool enable) +{ + struct acx_conn_monit_params *acx; + u32 threshold = ACX_CONN_MONIT_DISABLE_VALUE; + u32 timeout = ACX_CONN_MONIT_DISABLE_VALUE; + int ret; + + wl1271_debug(DEBUG_ACX, "acx connection monitor parameters: %s", + enable ? "enabled" : "disabled"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + if (enable) { + threshold = wl->conf.conn.synch_fail_thold; + timeout = wl->conf.conn.bss_lose_timeout; + } + + acx->synch_fail_thold = cpu_to_le32(threshold); + acx->bss_lose_timeout = cpu_to_le32(timeout); + + ret = wl1271_cmd_configure(wl, ACX_CONN_MONIT_PARAMS, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set connection monitor " + "parameters: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + + +int wl1271_acx_sg_enable(struct wl1271 *wl, bool enable) +{ + struct acx_bt_wlan_coex *pta; + int ret; + + wl1271_debug(DEBUG_ACX, "acx sg enable"); + + pta = kzalloc(sizeof(*pta), GFP_KERNEL); + if (!pta) { + ret = -ENOMEM; + goto out; + } + + if (enable) + pta->enable = wl->conf.sg.state; + else + pta->enable = CONF_SG_DISABLE; + + ret = wl1271_cmd_configure(wl, ACX_SG_ENABLE, pta, sizeof(*pta)); + if (ret < 0) { + wl1271_warning("failed to set softgemini enable: %d", ret); + goto out; + } + +out: + kfree(pta); + return ret; +} + +int wl1271_acx_sta_sg_cfg(struct wl1271 *wl) +{ + struct acx_sta_bt_wlan_coex_param *param; + struct conf_sg_settings *c = &wl->conf.sg; + int i, ret; + + wl1271_debug(DEBUG_ACX, "acx sg sta cfg"); + + param = kzalloc(sizeof(*param), GFP_KERNEL); + if (!param) { + ret = -ENOMEM; + goto out; + } + + /* BT-WLAN coext parameters */ + for (i = 0; i < CONF_SG_STA_PARAMS_MAX; i++) + param->params[i] = cpu_to_le32(c->sta_params[i]); + param->param_idx = CONF_SG_PARAMS_ALL; + + ret = wl1271_cmd_configure(wl, ACX_SG_CFG, param, sizeof(*param)); + if (ret < 0) { + wl1271_warning("failed to set sg config: %d", ret); + goto out; + } + +out: + kfree(param); + return ret; +} + +int wl1271_acx_ap_sg_cfg(struct wl1271 *wl) +{ + struct acx_ap_bt_wlan_coex_param *param; + struct conf_sg_settings *c = &wl->conf.sg; + int i, ret; + + wl1271_debug(DEBUG_ACX, "acx sg ap cfg"); + + param = kzalloc(sizeof(*param), GFP_KERNEL); + if (!param) { + ret = -ENOMEM; + goto out; + } + + /* BT-WLAN coext parameters */ + for (i = 0; i < CONF_SG_AP_PARAMS_MAX; i++) + param->params[i] = cpu_to_le32(c->ap_params[i]); + param->param_idx = CONF_SG_PARAMS_ALL; + + ret = wl1271_cmd_configure(wl, ACX_SG_CFG, param, sizeof(*param)); + if (ret < 0) { + wl1271_warning("failed to set sg config: %d", ret); + goto out; + } + +out: + kfree(param); + return ret; +} + +int wl1271_acx_cca_threshold(struct wl1271 *wl) +{ + struct acx_energy_detection *detection; + int ret; + + wl1271_debug(DEBUG_ACX, "acx cca threshold"); + + detection = kzalloc(sizeof(*detection), GFP_KERNEL); + if (!detection) { + ret = -ENOMEM; + goto out; + } + + detection->rx_cca_threshold = cpu_to_le16(wl->conf.rx.rx_cca_threshold); + detection->tx_energy_detection = wl->conf.tx.tx_energy_detection; + + ret = wl1271_cmd_configure(wl, ACX_CCA_THRESHOLD, + detection, sizeof(*detection)); + if (ret < 0) { + wl1271_warning("failed to set cca threshold: %d", ret); + return ret; + } + +out: + kfree(detection); + return ret; +} + +int wl1271_acx_bcn_dtim_options(struct wl1271 *wl) +{ + struct acx_beacon_broadcast *bb; + int ret; + + wl1271_debug(DEBUG_ACX, "acx bcn dtim options"); + + bb = kzalloc(sizeof(*bb), GFP_KERNEL); + if (!bb) { + ret = -ENOMEM; + goto out; + } + + bb->beacon_rx_timeout = cpu_to_le16(wl->conf.conn.beacon_rx_timeout); + bb->broadcast_timeout = cpu_to_le16(wl->conf.conn.broadcast_timeout); + bb->rx_broadcast_in_ps = wl->conf.conn.rx_broadcast_in_ps; + bb->ps_poll_threshold = wl->conf.conn.ps_poll_threshold; + + ret = wl1271_cmd_configure(wl, ACX_BCN_DTIM_OPTIONS, bb, sizeof(*bb)); + if (ret < 0) { + wl1271_warning("failed to set rx config: %d", ret); + goto out; + } + +out: + kfree(bb); + return ret; +} + +int wl1271_acx_aid(struct wl1271 *wl, u16 aid) +{ + struct acx_aid *acx_aid; + int ret; + + wl1271_debug(DEBUG_ACX, "acx aid"); + + acx_aid = kzalloc(sizeof(*acx_aid), GFP_KERNEL); + if (!acx_aid) { + ret = -ENOMEM; + goto out; + } + + acx_aid->aid = cpu_to_le16(aid); + + ret = wl1271_cmd_configure(wl, ACX_AID, acx_aid, sizeof(*acx_aid)); + if (ret < 0) { + wl1271_warning("failed to set aid: %d", ret); + goto out; + } + +out: + kfree(acx_aid); + return ret; +} + +int wl1271_acx_event_mbox_mask(struct wl1271 *wl, u32 event_mask) +{ + struct acx_event_mask *mask; + int ret; + + wl1271_debug(DEBUG_ACX, "acx event mbox mask"); + + mask = kzalloc(sizeof(*mask), GFP_KERNEL); + if (!mask) { + ret = -ENOMEM; + goto out; + } + + /* high event mask is unused */ + mask->high_event_mask = cpu_to_le32(0xffffffff); + mask->event_mask = cpu_to_le32(event_mask); + + ret = wl1271_cmd_configure(wl, ACX_EVENT_MBOX_MASK, + mask, sizeof(*mask)); + if (ret < 0) { + wl1271_warning("failed to set acx_event_mbox_mask: %d", ret); + goto out; + } + +out: + kfree(mask); + return ret; +} + +int wl1271_acx_set_preamble(struct wl1271 *wl, enum acx_preamble_type preamble) +{ + struct acx_preamble *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx_set_preamble"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->preamble = preamble; + + ret = wl1271_cmd_configure(wl, ACX_PREAMBLE_TYPE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of preamble failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_cts_protect(struct wl1271 *wl, + enum acx_ctsprotect_type ctsprotect) +{ + struct acx_ctsprotect *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx_set_ctsprotect"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->ctsprotect = ctsprotect; + + ret = wl1271_cmd_configure(wl, ACX_CTS_PROTECTION, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of ctsprotect failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_statistics(struct wl1271 *wl, struct acx_statistics *stats) +{ + int ret; + + wl1271_debug(DEBUG_ACX, "acx statistics"); + + ret = wl1271_cmd_interrogate(wl, ACX_STATISTICS, stats, + sizeof(*stats)); + if (ret < 0) { + wl1271_warning("acx statistics failed: %d", ret); + return -ENOMEM; + } + + return 0; +} + +int wl1271_acx_sta_rate_policies(struct wl1271 *wl) +{ + struct acx_sta_rate_policy *acx; + struct conf_tx_rate_class *c = &wl->conf.tx.sta_rc_conf; + int idx = 0; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx rate policies"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* configure one basic rate class */ + idx = ACX_TX_BASIC_RATE; + acx->rate_class[idx].enabled_rates = cpu_to_le32(wl->basic_rate); + acx->rate_class[idx].short_retry_limit = c->short_retry_limit; + acx->rate_class[idx].long_retry_limit = c->long_retry_limit; + acx->rate_class[idx].aflags = c->aflags; + + /* configure one AP supported rate class */ + idx = ACX_TX_AP_FULL_RATE; + acx->rate_class[idx].enabled_rates = cpu_to_le32(wl->rate_set); + acx->rate_class[idx].short_retry_limit = c->short_retry_limit; + acx->rate_class[idx].long_retry_limit = c->long_retry_limit; + acx->rate_class[idx].aflags = c->aflags; + + acx->rate_class_cnt = cpu_to_le32(ACX_TX_RATE_POLICY_CNT); + + wl1271_debug(DEBUG_ACX, "basic_rate: 0x%x, full_rate: 0x%x", + acx->rate_class[ACX_TX_BASIC_RATE].enabled_rates, + acx->rate_class[ACX_TX_AP_FULL_RATE].enabled_rates); + + ret = wl1271_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of rate policies failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_ap_rate_policy(struct wl1271 *wl, struct conf_tx_rate_class *c, + u8 idx) +{ + struct acx_ap_rate_policy *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ap rate policy %d rates 0x%x", + idx, c->enabled_rates); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->rate_policy.enabled_rates = cpu_to_le32(c->enabled_rates); + acx->rate_policy.short_retry_limit = c->short_retry_limit; + acx->rate_policy.long_retry_limit = c->long_retry_limit; + acx->rate_policy.aflags = c->aflags; + + acx->rate_policy_idx = cpu_to_le32(idx); + + ret = wl1271_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of ap rate policy failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_ac_cfg(struct wl1271 *wl, u8 ac, u8 cw_min, u16 cw_max, + u8 aifsn, u16 txop) +{ + struct acx_ac_cfg *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ac cfg %d cw_ming %d cw_max %d " + "aifs %d txop %d", ac, cw_min, cw_max, aifsn, txop); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->ac = ac; + acx->cw_min = cw_min; + acx->cw_max = cpu_to_le16(cw_max); + acx->aifsn = aifsn; + acx->tx_op_limit = cpu_to_le16(txop); + + ret = wl1271_cmd_configure(wl, ACX_AC_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ac cfg failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_tid_cfg(struct wl1271 *wl, u8 queue_id, u8 channel_type, + u8 tsid, u8 ps_scheme, u8 ack_policy, + u32 apsd_conf0, u32 apsd_conf1) +{ + struct acx_tid_config *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx tid config"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->queue_id = queue_id; + acx->channel_type = channel_type; + acx->tsid = tsid; + acx->ps_scheme = ps_scheme; + acx->ack_policy = ack_policy; + acx->apsd_conf[0] = cpu_to_le32(apsd_conf0); + acx->apsd_conf[1] = cpu_to_le32(apsd_conf1); + + ret = wl1271_cmd_configure(wl, ACX_TID_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of tid config failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_frag_threshold(struct wl1271 *wl, u32 frag_threshold) +{ + struct acx_frag_threshold *acx; + int ret = 0; + + /* + * If the fragmentation is not configured or out of range, use the + * default value. + */ + if (frag_threshold > IEEE80211_MAX_FRAG_THRESHOLD) + frag_threshold = wl->conf.tx.frag_threshold; + + wl1271_debug(DEBUG_ACX, "acx frag threshold: %d", frag_threshold); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->frag_threshold = cpu_to_le16((u16)frag_threshold); + ret = wl1271_cmd_configure(wl, ACX_FRAG_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of frag threshold failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_tx_config_options(struct wl1271 *wl) +{ + struct acx_tx_config_options *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx tx config options"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->tx_compl_timeout = cpu_to_le16(wl->conf.tx.tx_compl_timeout); + acx->tx_compl_threshold = cpu_to_le16(wl->conf.tx.tx_compl_threshold); + ret = wl1271_cmd_configure(wl, ACX_TX_CONFIG_OPT, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of tx options failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_ap_mem_cfg(struct wl1271 *wl) +{ + struct wl1271_acx_ap_config_memory *mem_conf; + struct conf_memory_settings *mem; + int ret; + + wl1271_debug(DEBUG_ACX, "wl1271 mem cfg"); + + mem_conf = kzalloc(sizeof(*mem_conf), GFP_KERNEL); + if (!mem_conf) { + ret = -ENOMEM; + goto out; + } + + if (wl->chip.id == CHIP_ID_1283_PG20) + /* + * FIXME: The 128x AP FW does not yet support dynamic memory. + * Use the base memory configuration for 128x for now. This + * should be fine tuned in the future. + */ + mem = &wl->conf.mem_wl128x; + else + mem = &wl->conf.mem_wl127x; + + /* memory config */ + mem_conf->num_stations = mem->num_stations; + mem_conf->rx_mem_block_num = mem->rx_block_num; + mem_conf->tx_min_mem_block_num = mem->tx_min_block_num; + mem_conf->num_ssid_profiles = mem->ssid_profiles; + mem_conf->total_tx_descriptors = cpu_to_le32(ACX_TX_DESCRIPTORS); + + ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, + sizeof(*mem_conf)); + if (ret < 0) { + wl1271_warning("wl1271 mem config failed: %d", ret); + goto out; + } + +out: + kfree(mem_conf); + return ret; +} + +int wl1271_acx_sta_mem_cfg(struct wl1271 *wl) +{ + struct wl1271_acx_sta_config_memory *mem_conf; + struct conf_memory_settings *mem; + int ret; + + wl1271_debug(DEBUG_ACX, "wl1271 mem cfg"); + + mem_conf = kzalloc(sizeof(*mem_conf), GFP_KERNEL); + if (!mem_conf) { + ret = -ENOMEM; + goto out; + } + + if (wl->chip.id == CHIP_ID_1283_PG20) + mem = &wl->conf.mem_wl128x; + else + mem = &wl->conf.mem_wl127x; + + /* memory config */ + mem_conf->num_stations = mem->num_stations; + mem_conf->rx_mem_block_num = mem->rx_block_num; + mem_conf->tx_min_mem_block_num = mem->tx_min_block_num; + mem_conf->num_ssid_profiles = mem->ssid_profiles; + mem_conf->total_tx_descriptors = cpu_to_le32(ACX_TX_DESCRIPTORS); + mem_conf->dyn_mem_enable = mem->dynamic_memory; + mem_conf->tx_free_req = mem->min_req_tx_blocks; + mem_conf->rx_free_req = mem->min_req_rx_blocks; + mem_conf->tx_min = mem->tx_min; + + ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, + sizeof(*mem_conf)); + if (ret < 0) { + wl1271_warning("wl1271 mem config failed: %d", ret); + goto out; + } + +out: + kfree(mem_conf); + return ret; +} + +int wl1271_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap) +{ + struct wl1271_acx_host_config_bitmap *bitmap_conf; + int ret; + + bitmap_conf = kzalloc(sizeof(*bitmap_conf), GFP_KERNEL); + if (!bitmap_conf) { + ret = -ENOMEM; + goto out; + } + + bitmap_conf->host_cfg_bitmap = cpu_to_le32(host_cfg_bitmap); + + ret = wl1271_cmd_configure(wl, ACX_HOST_IF_CFG_BITMAP, + bitmap_conf, sizeof(*bitmap_conf)); + if (ret < 0) { + wl1271_warning("wl1271 bitmap config opt failed: %d", ret); + goto out; + } + +out: + kfree(bitmap_conf); + + return ret; +} + +int wl1271_acx_init_mem_config(struct wl1271 *wl) +{ + int ret; + + wl->target_mem_map = kzalloc(sizeof(struct wl1271_acx_mem_map), + GFP_KERNEL); + if (!wl->target_mem_map) { + wl1271_error("couldn't allocate target memory map"); + return -ENOMEM; + } + + /* we now ask for the firmware built memory map */ + ret = wl1271_acx_mem_map(wl, (void *)wl->target_mem_map, + sizeof(struct wl1271_acx_mem_map)); + if (ret < 0) { + wl1271_error("couldn't retrieve firmware memory map"); + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + return ret; + } + + /* initialize TX block book keeping */ + wl->tx_blocks_available = + le32_to_cpu(wl->target_mem_map->num_tx_mem_blocks); + wl1271_debug(DEBUG_TX, "available tx blocks: %d", + wl->tx_blocks_available); + + return 0; +} + +int wl1271_acx_init_rx_interrupt(struct wl1271 *wl) +{ + struct wl1271_acx_rx_config_opt *rx_conf; + int ret; + + wl1271_debug(DEBUG_ACX, "wl1271 rx interrupt config"); + + rx_conf = kzalloc(sizeof(*rx_conf), GFP_KERNEL); + if (!rx_conf) { + ret = -ENOMEM; + goto out; + } + + rx_conf->threshold = cpu_to_le16(wl->conf.rx.irq_pkt_threshold); + rx_conf->timeout = cpu_to_le16(wl->conf.rx.irq_timeout); + rx_conf->mblk_threshold = cpu_to_le16(wl->conf.rx.irq_blk_threshold); + rx_conf->queue_type = wl->conf.rx.queue_type; + + ret = wl1271_cmd_configure(wl, ACX_RX_CONFIG_OPT, rx_conf, + sizeof(*rx_conf)); + if (ret < 0) { + wl1271_warning("wl1271 rx config opt failed: %d", ret); + goto out; + } + +out: + kfree(rx_conf); + return ret; +} + +int wl1271_acx_bet_enable(struct wl1271 *wl, bool enable) +{ + struct wl1271_acx_bet_enable *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx bet enable"); + + if (enable && wl->conf.conn.bet_enable == CONF_BET_MODE_DISABLE) + goto out; + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->enable = enable ? CONF_BET_MODE_ENABLE : CONF_BET_MODE_DISABLE; + acx->max_consecutive = wl->conf.conn.bet_max_consecutive; + + ret = wl1271_cmd_configure(wl, ACX_BET_ENABLE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx bet enable failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_arp_ip_filter(struct wl1271 *wl, u8 enable, __be32 address) +{ + struct wl1271_acx_arp_filter *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx arp ip filter, enable: %d", enable); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->version = ACX_IPV4_VERSION; + acx->enable = enable; + + if (enable) + memcpy(acx->address, &address, ACX_IPV4_ADDR_SIZE); + + ret = wl1271_cmd_configure(wl, ACX_ARP_IP_FILTER, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set arp ip filter: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_pm_config(struct wl1271 *wl) +{ + struct wl1271_acx_pm_config *acx = NULL; + struct conf_pm_config_settings *c = &wl->conf.pm_config; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx pm config"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->host_clk_settling_time = cpu_to_le32(c->host_clk_settling_time); + acx->host_fast_wakeup_support = c->host_fast_wakeup_support; + + ret = wl1271_cmd_configure(wl, ACX_PM_CONFIG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx pm config failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_keep_alive_mode(struct wl1271 *wl, bool enable) +{ + struct wl1271_acx_keep_alive_mode *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx keep alive mode: %d", enable); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->enabled = enable; + + ret = wl1271_cmd_configure(wl, ACX_KEEP_ALIVE_MODE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx keep alive mode failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_keep_alive_config(struct wl1271 *wl, u8 index, u8 tpl_valid) +{ + struct wl1271_acx_keep_alive_config *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx keep alive config"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->period = cpu_to_le32(wl->conf.conn.keep_alive_interval); + acx->index = index; + acx->tpl_validation = tpl_valid; + acx->trigger = ACX_KEEP_ALIVE_NO_TX; + + ret = wl1271_cmd_configure(wl, ACX_SET_KEEP_ALIVE_CONFIG, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx keep alive config failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_rssi_snr_trigger(struct wl1271 *wl, bool enable, + s16 thold, u8 hyst) +{ + struct wl1271_acx_rssi_snr_trigger *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx rssi snr trigger"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + wl->last_rssi_event = -1; + + acx->pacing = cpu_to_le16(wl->conf.roam_trigger.trigger_pacing); + acx->metric = WL1271_ACX_TRIG_METRIC_RSSI_BEACON; + acx->type = WL1271_ACX_TRIG_TYPE_EDGE; + if (enable) + acx->enable = WL1271_ACX_TRIG_ENABLE; + else + acx->enable = WL1271_ACX_TRIG_DISABLE; + + acx->index = WL1271_ACX_TRIG_IDX_RSSI; + acx->dir = WL1271_ACX_TRIG_DIR_BIDIR; + acx->threshold = cpu_to_le16(thold); + acx->hysteresis = hyst; + + ret = wl1271_cmd_configure(wl, ACX_RSSI_SNR_TRIGGER, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx rssi snr trigger setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_rssi_snr_avg_weights(struct wl1271 *wl) +{ + struct wl1271_acx_rssi_snr_avg_weights *acx = NULL; + struct conf_roam_trigger_settings *c = &wl->conf.roam_trigger; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx rssi snr avg weights"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->rssi_beacon = c->avg_weight_rssi_beacon; + acx->rssi_data = c->avg_weight_rssi_data; + acx->snr_beacon = c->avg_weight_snr_beacon; + acx->snr_data = c->avg_weight_snr_data; + + ret = wl1271_cmd_configure(wl, ACX_RSSI_SNR_WEIGHTS, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx rssi snr trigger weights failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_set_ht_capabilities(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation) +{ + struct wl1271_acx_ht_capabilities *acx; + u8 mac_address[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + int ret = 0; + u32 ht_capabilites = 0; + + wl1271_debug(DEBUG_ACX, "acx ht capabilities setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* Allow HT Operation ? */ + if (allow_ht_operation) { + ht_capabilites = + WL1271_ACX_FW_CAP_HT_OPERATION; + if (ht_cap->cap & IEEE80211_HT_CAP_GRN_FLD) + ht_capabilites |= + WL1271_ACX_FW_CAP_GREENFIELD_FRAME_FORMAT; + if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) + ht_capabilites |= + WL1271_ACX_FW_CAP_SHORT_GI_FOR_20MHZ_PACKETS; + if (ht_cap->cap & IEEE80211_HT_CAP_LSIG_TXOP_PROT) + ht_capabilites |= + WL1271_ACX_FW_CAP_LSIG_TXOP_PROTECTION; + + /* get data from A-MPDU parameters field */ + acx->ampdu_max_length = ht_cap->ampdu_factor; + acx->ampdu_min_spacing = ht_cap->ampdu_density; + } + + memcpy(acx->mac_address, mac_address, ETH_ALEN); + acx->ht_capabilites = cpu_to_le32(ht_capabilites); + + ret = wl1271_cmd_configure(wl, ACX_PEER_HT_CAP, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ht capabilities setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_set_ht_information(struct wl1271 *wl, + u16 ht_operation_mode) +{ + struct wl1271_acx_ht_information *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ht information setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->ht_protection = + (u8)(ht_operation_mode & IEEE80211_HT_OP_MODE_PROTECTION); + acx->rifs_mode = 0; + acx->gf_protection = + !!(ht_operation_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + acx->ht_tx_burst_limit = 0; + acx->dual_cts_protection = 0; + + ret = wl1271_cmd_configure(wl, ACX_HT_BSS_OPERATION, acx, sizeof(*acx)); + + if (ret < 0) { + wl1271_warning("acx ht information setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +/* Configure BA session initiator/receiver parameters setting in the FW. */ +int wl1271_acx_set_ba_session(struct wl1271 *wl, + enum ieee80211_back_parties direction, + u8 tid_index, u8 policy) +{ + struct wl1271_acx_ba_session_policy *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx ba session setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* ANY role */ + acx->role_id = 0xff; + acx->tid = tid_index; + acx->enable = policy; + acx->ba_direction = direction; + + switch (direction) { + case WLAN_BACK_INITIATOR: + acx->win_size = wl->conf.ht.tx_ba_win_size; + acx->inactivity_timeout = wl->conf.ht.inactivity_timeout; + break; + case WLAN_BACK_RECIPIENT: + acx->win_size = RX_BA_WIN_SIZE; + acx->inactivity_timeout = 0; + break; + default: + wl1271_error("Incorrect acx command id=%x\n", direction); + ret = -EINVAL; + goto out; + } + + ret = wl1271_cmd_configure(wl, + ACX_BA_SESSION_POLICY_CFG, + acx, + sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ba session setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +/* setup BA session receiver setting in the FW. */ +int wl1271_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, u16 ssn, + bool enable) +{ + struct wl1271_acx_ba_receiver_setup *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx ba receiver session setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* Single link for now */ + acx->link_id = 1; + acx->tid = tid_index; + acx->enable = enable; + acx->win_size = 0; + acx->ssn = ssn; + + ret = wl1271_cmd_configure(wl, ACX_BA_SESSION_RX_SETUP, acx, + sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ba receiver session failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime) +{ + struct wl1271_acx_fw_tsf_information *tsf_info; + int ret; + + tsf_info = kzalloc(sizeof(*tsf_info), GFP_KERNEL); + if (!tsf_info) { + ret = -ENOMEM; + goto out; + } + + ret = wl1271_cmd_interrogate(wl, ACX_TSF_INFO, + tsf_info, sizeof(*tsf_info)); + if (ret < 0) { + wl1271_warning("acx tsf info interrogate failed"); + goto out; + } + + *mactime = le32_to_cpu(tsf_info->current_tsf_low) | + ((u64) le32_to_cpu(tsf_info->current_tsf_high) << 32); + +out: + kfree(tsf_info); + return ret; +} + +int wl1271_acx_max_tx_retry(struct wl1271 *wl) +{ + struct wl1271_acx_max_tx_retry *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx max tx retry"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->max_tx_retry = cpu_to_le16(wl->conf.tx.ap_max_tx_retries); + + ret = wl1271_cmd_configure(wl, ACX_MAX_TX_FAILURE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx max tx retry failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_config_ps(struct wl1271 *wl) +{ + struct wl1271_acx_config_ps *config_ps; + int ret; + + wl1271_debug(DEBUG_ACX, "acx config ps"); + + config_ps = kzalloc(sizeof(*config_ps), GFP_KERNEL); + if (!config_ps) { + ret = -ENOMEM; + goto out; + } + + config_ps->exit_retries = wl->conf.conn.psm_exit_retries; + config_ps->enter_retries = wl->conf.conn.psm_entry_retries; + config_ps->null_data_rate = cpu_to_le32(wl->basic_rate); + + ret = wl1271_cmd_configure(wl, ACX_CONFIG_PS, config_ps, + sizeof(*config_ps)); + + if (ret < 0) { + wl1271_warning("acx config ps failed: %d", ret); + goto out; + } + +out: + kfree(config_ps); + return ret; +} + +int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr) +{ + struct wl1271_acx_inconnection_sta *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx set inconnaction sta %pM", addr); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + memcpy(acx->addr, addr, ETH_ALEN); + + ret = wl1271_cmd_configure(wl, ACX_UPDATE_INCONNECTION_STA_LIST, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx set inconnaction sta failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_set_ap_beacon_filter(struct wl1271 *wl, bool enable) +{ + struct acx_ap_beacon_filter *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx set ap beacon filter: %d", enable); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->enable = enable ? 1 : 0; + + ret = wl1271_cmd_configure(wl, ACX_AP_BEACON_FILTER_OPT, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx set ap beacon filter failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_fm_coex(struct wl1271 *wl) +{ + struct wl1271_acx_fm_coex *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx fm coex setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->enable = wl->conf.fm_coex.enable; + acx->swallow_period = wl->conf.fm_coex.swallow_period; + acx->n_divider_fref_set_1 = wl->conf.fm_coex.n_divider_fref_set_1; + acx->n_divider_fref_set_2 = wl->conf.fm_coex.n_divider_fref_set_2; + acx->m_divider_fref_set_1 = + cpu_to_le16(wl->conf.fm_coex.m_divider_fref_set_1); + acx->m_divider_fref_set_2 = + cpu_to_le16(wl->conf.fm_coex.m_divider_fref_set_2); + acx->coex_pll_stabilization_time = + cpu_to_le32(wl->conf.fm_coex.coex_pll_stabilization_time); + acx->ldo_stabilization_time = + cpu_to_le16(wl->conf.fm_coex.ldo_stabilization_time); + acx->fm_disturbed_band_margin = + wl->conf.fm_coex.fm_disturbed_band_margin; + acx->swallow_clk_diff = wl->conf.fm_coex.swallow_clk_diff; + + ret = wl1271_cmd_configure(wl, ACX_FM_COEX_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx fm coex setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h new file mode 100644 index 00000000..9a895e3c --- /dev/null +++ b/drivers/net/wireless/wl12xx/acx.h @@ -0,0 +1,1393 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __ACX_H__ +#define __ACX_H__ + +#include "wl12xx.h" +#include "cmd.h" + +/************************************************************************* + + Host Interrupt Register (WiLink -> Host) + +**************************************************************************/ +/* HW Initiated interrupt Watchdog timer expiration */ +#define WL1271_ACX_INTR_WATCHDOG BIT(0) +/* Init sequence is done (masked interrupt, detection through polling only ) */ +#define WL1271_ACX_INTR_INIT_COMPLETE BIT(1) +/* Event was entered to Event MBOX #A*/ +#define WL1271_ACX_INTR_EVENT_A BIT(2) +/* Event was entered to Event MBOX #B*/ +#define WL1271_ACX_INTR_EVENT_B BIT(3) +/* Command processing completion*/ +#define WL1271_ACX_INTR_CMD_COMPLETE BIT(4) +/* Signaling the host on HW wakeup */ +#define WL1271_ACX_INTR_HW_AVAILABLE BIT(5) +/* The MISC bit is used for aggregation of RX, TxComplete and TX rate update */ +#define WL1271_ACX_INTR_DATA BIT(6) +/* Trace message on MBOX #A */ +#define WL1271_ACX_INTR_TRACE_A BIT(7) +/* Trace message on MBOX #B */ +#define WL1271_ACX_INTR_TRACE_B BIT(8) + +#define WL1271_ACX_INTR_ALL 0xFFFFFFFF +#define WL1271_ACX_ALL_EVENTS_VECTOR (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_INIT_COMPLETE | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_CMD_COMPLETE | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA) + +#define WL1271_INTR_MASK (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA) + +/* Target's information element */ +struct acx_header { + struct wl1271_cmd_header cmd; + + /* acx (or information element) header */ + __le16 id; + + /* payload length (not including headers */ + __le16 len; +} __packed; + +struct acx_error_counter { + struct acx_header header; + + /* The number of PLCP errors since the last time this */ + /* information element was interrogated. This field is */ + /* automatically cleared when it is interrogated.*/ + __le32 PLCP_error; + + /* The number of FCS errors since the last time this */ + /* information element was interrogated. This field is */ + /* automatically cleared when it is interrogated.*/ + __le32 FCS_error; + + /* The number of MPDUs without PLCP header errors received*/ + /* since the last time this information element was interrogated. */ + /* This field is automatically cleared when it is interrogated.*/ + __le32 valid_frame; + + /* the number of missed sequence numbers in the squentially */ + /* values of frames seq numbers */ + __le32 seq_num_miss; +} __packed; + +enum wl1271_psm_mode { + /* Active mode */ + WL1271_PSM_CAM = 0, + + /* Power save mode */ + WL1271_PSM_PS = 1, + + /* Extreme low power */ + WL1271_PSM_ELP = 2, +}; + +struct acx_sleep_auth { + struct acx_header header; + + /* The sleep level authorization of the device. */ + /* 0 - Always active*/ + /* 1 - Power down mode: light / fast sleep*/ + /* 2 - ELP mode: Deep / Max sleep*/ + u8 sleep_auth; + u8 padding[3]; +} __packed; + +enum { + HOSTIF_PCI_MASTER_HOST_INDIRECT, + HOSTIF_PCI_MASTER_HOST_DIRECT, + HOSTIF_SLAVE, + HOSTIF_PKT_RING, + HOSTIF_DONTCARE = 0xFF +}; + +#define DEFAULT_UCAST_PRIORITY 0 +#define DEFAULT_RX_Q_PRIORITY 0 +#define DEFAULT_RXQ_PRIORITY 0 /* low 0 .. 15 high */ +#define DEFAULT_RXQ_TYPE 0x07 /* All frames, Data/Ctrl/Mgmt */ +#define TRACE_BUFFER_MAX_SIZE 256 + +#define DP_RX_PACKET_RING_CHUNK_SIZE 1600 +#define DP_TX_PACKET_RING_CHUNK_SIZE 1600 +#define DP_RX_PACKET_RING_CHUNK_NUM 2 +#define DP_TX_PACKET_RING_CHUNK_NUM 2 +#define DP_TX_COMPLETE_TIME_OUT 20 + +#define TX_MSDU_LIFETIME_MIN 0 +#define TX_MSDU_LIFETIME_MAX 3000 +#define TX_MSDU_LIFETIME_DEF 512 +#define RX_MSDU_LIFETIME_MIN 0 +#define RX_MSDU_LIFETIME_MAX 0xFFFFFFFF +#define RX_MSDU_LIFETIME_DEF 512000 + +struct acx_rx_msdu_lifetime { + struct acx_header header; + + /* + * The maximum amount of time, in TU, before the + * firmware discards the MSDU. + */ + __le32 lifetime; +} __packed; + +/* + * RX Config Options Table + * Bit Definition + * === ========== + * 31:14 Reserved + * 13 Copy RX Status - when set, write three receive status words + * to top of rx'd MPDUs. + * When cleared, do not write three status words (added rev 1.5) + * 12 Reserved + * 11 RX Complete upon FCS error - when set, give rx complete + * interrupt for FCS errors, after the rx filtering, e.g. unicast + * frames not to us with FCS error will not generate an interrupt. + * 10 SSID Filter Enable - When set, the WiLink discards all beacon, + * probe request, and probe response frames with an SSID that does + * not match the SSID specified by the host in the START/JOIN + * command. + * When clear, the WiLink receives frames with any SSID. + * 9 Broadcast Filter Enable - When set, the WiLink discards all + * broadcast frames. When clear, the WiLink receives all received + * broadcast frames. + * 8:6 Reserved + * 5 BSSID Filter Enable - When set, the WiLink discards any frames + * with a BSSID that does not match the BSSID specified by the + * host. + * When clear, the WiLink receives frames from any BSSID. + * 4 MAC Addr Filter - When set, the WiLink discards any frames + * with a destination address that does not match the MAC address + * of the adaptor. + * When clear, the WiLink receives frames destined to any MAC + * address. + * 3 Promiscuous - When set, the WiLink receives all valid frames + * (i.e., all frames that pass the FCS check). + * When clear, only frames that pass the other filters specified + * are received. + * 2 FCS - When set, the WiLink includes the FCS with the received + * frame. + * When cleared, the FCS is discarded. + * 1 PLCP header - When set, write all data from baseband to frame + * buffer including PHY header. + * 0 Reserved - Always equal to 0. + * + * RX Filter Options Table + * Bit Definition + * === ========== + * 31:12 Reserved - Always equal to 0. + * 11 Association - When set, the WiLink receives all association + * related frames (association request/response, reassocation + * request/response, and disassociation). When clear, these frames + * are discarded. + * 10 Auth/De auth - When set, the WiLink receives all authentication + * and de-authentication frames. When clear, these frames are + * discarded. + * 9 Beacon - When set, the WiLink receives all beacon frames. + * When clear, these frames are discarded. + * 8 Contention Free - When set, the WiLink receives all contention + * free frames. + * When clear, these frames are discarded. + * 7 Control - When set, the WiLink receives all control frames. + * When clear, these frames are discarded. + * 6 Data - When set, the WiLink receives all data frames. + * When clear, these frames are discarded. + * 5 FCS Error - When set, the WiLink receives frames that have FCS + * errors. + * When clear, these frames are discarded. + * 4 Management - When set, the WiLink receives all management + * frames. + * When clear, these frames are discarded. + * 3 Probe Request - When set, the WiLink receives all probe request + * frames. + * When clear, these frames are discarded. + * 2 Probe Response - When set, the WiLink receives all probe + * response frames. + * When clear, these frames are discarded. + * 1 RTS/CTS/ACK - When set, the WiLink receives all RTS, CTS and ACK + * frames. + * When clear, these frames are discarded. + * 0 Rsvd Type/Sub Type - When set, the WiLink receives all frames + * that have reserved frame types and sub types as defined by the + * 802.11 specification. + * When clear, these frames are discarded. + */ +struct acx_rx_config { + struct acx_header header; + + __le32 config_options; + __le32 filter_options; +} __packed; + +struct acx_packet_detection { + struct acx_header header; + + __le32 threshold; +} __packed; + + +enum acx_slot_type { + SLOT_TIME_LONG = 0, + SLOT_TIME_SHORT = 1, + DEFAULT_SLOT_TIME = SLOT_TIME_SHORT, + MAX_SLOT_TIMES = 0xFF +}; + +#define STATION_WONE_INDEX 0 + +struct acx_slot { + struct acx_header header; + + u8 wone_index; /* Reserved */ + u8 slot_time; + u8 reserved[6]; +} __packed; + + +#define ACX_MC_ADDRESS_GROUP_MAX (8) +#define ADDRESS_GROUP_MAX_LEN (ETH_ALEN * ACX_MC_ADDRESS_GROUP_MAX) + +struct acx_dot11_grp_addr_tbl { + struct acx_header header; + + u8 enabled; + u8 num_groups; + u8 pad[2]; + u8 mac_table[ADDRESS_GROUP_MAX_LEN]; +} __packed; + +struct acx_rx_timeout { + struct acx_header header; + + __le16 ps_poll_timeout; + __le16 upsd_timeout; +} __packed; + +struct acx_rts_threshold { + struct acx_header header; + + __le16 threshold; + u8 pad[2]; +} __packed; + +struct acx_beacon_filter_option { + struct acx_header header; + + u8 enable; + /* + * The number of beacons without the unicast TIM + * bit set that the firmware buffers before + * signaling the host about ready frames. + * When set to 0 and the filter is enabled, beacons + * without the unicast TIM bit set are dropped. + */ + u8 max_num_beacons; + u8 pad[2]; +} __packed; + +/* + * ACXBeaconFilterEntry (not 221) + * Byte Offset Size (Bytes) Definition + * =========== ============ ========== + * 0 1 IE identifier + * 1 1 Treatment bit mask + * + * ACXBeaconFilterEntry (221) + * Byte Offset Size (Bytes) Definition + * =========== ============ ========== + * 0 1 IE identifier + * 1 1 Treatment bit mask + * 2 3 OUI + * 5 1 Type + * 6 2 Version + * + * + * Treatment bit mask - The information element handling: + * bit 0 - The information element is compared and transferred + * in case of change. + * bit 1 - The information element is transferred to the host + * with each appearance or disappearance. + * Note that both bits can be set at the same time. + */ +#define BEACON_FILTER_TABLE_MAX_IE_NUM (32) +#define BEACON_FILTER_TABLE_MAX_VENDOR_SPECIFIC_IE_NUM (6) +#define BEACON_FILTER_TABLE_IE_ENTRY_SIZE (2) +#define BEACON_FILTER_TABLE_EXTRA_VENDOR_SPECIFIC_IE_SIZE (6) +#define BEACON_FILTER_TABLE_MAX_SIZE ((BEACON_FILTER_TABLE_MAX_IE_NUM * \ + BEACON_FILTER_TABLE_IE_ENTRY_SIZE) + \ + (BEACON_FILTER_TABLE_MAX_VENDOR_SPECIFIC_IE_NUM * \ + BEACON_FILTER_TABLE_EXTRA_VENDOR_SPECIFIC_IE_SIZE)) + +struct acx_beacon_filter_ie_table { + struct acx_header header; + + u8 num_ie; + u8 pad[3]; + u8 table[BEACON_FILTER_TABLE_MAX_SIZE]; +} __packed; + +struct acx_conn_monit_params { + struct acx_header header; + + __le32 synch_fail_thold; /* number of beacons missed */ + __le32 bss_lose_timeout; /* number of TU's from synch fail */ +} __packed; + +struct acx_bt_wlan_coex { + struct acx_header header; + + u8 enable; + u8 pad[3]; +} __packed; + +struct acx_sta_bt_wlan_coex_param { + struct acx_header header; + + __le32 params[CONF_SG_STA_PARAMS_MAX]; + u8 param_idx; + u8 padding[3]; +} __packed; + +struct acx_ap_bt_wlan_coex_param { + struct acx_header header; + + __le32 params[CONF_SG_AP_PARAMS_MAX]; + u8 param_idx; + u8 padding[3]; +} __packed; + + +struct acx_dco_itrim_params { + struct acx_header header; + + u8 enable; + u8 padding[3]; + __le32 timeout; +} __packed; + +struct acx_energy_detection { + struct acx_header header; + + /* The RX Clear Channel Assessment threshold in the PHY */ + __le16 rx_cca_threshold; + u8 tx_energy_detection; + u8 pad; +} __packed; + +struct acx_beacon_broadcast { + struct acx_header header; + + __le16 beacon_rx_timeout; + __le16 broadcast_timeout; + + /* Enables receiving of broadcast packets in PS mode */ + u8 rx_broadcast_in_ps; + + /* Consecutive PS Poll failures before updating the host */ + u8 ps_poll_threshold; + u8 pad[2]; +} __packed; + +struct acx_event_mask { + struct acx_header header; + + __le32 event_mask; + __le32 high_event_mask; /* Unused */ +} __packed; + +#define CFG_RX_FCS BIT(2) +#define CFG_RX_ALL_GOOD BIT(3) +#define CFG_UNI_FILTER_EN BIT(4) +#define CFG_BSSID_FILTER_EN BIT(5) +#define CFG_MC_FILTER_EN BIT(6) +#define CFG_MC_ADDR0_EN BIT(7) +#define CFG_MC_ADDR1_EN BIT(8) +#define CFG_BC_REJECT_EN BIT(9) +#define CFG_SSID_FILTER_EN BIT(10) +#define CFG_RX_INT_FCS_ERROR BIT(11) +#define CFG_RX_INT_ENCRYPTED BIT(12) +#define CFG_RX_WR_RX_STATUS BIT(13) +#define CFG_RX_FILTER_NULTI BIT(14) +#define CFG_RX_RESERVE BIT(15) +#define CFG_RX_TIMESTAMP_TSF BIT(16) + +#define CFG_RX_RSV_EN BIT(0) +#define CFG_RX_RCTS_ACK BIT(1) +#define CFG_RX_PRSP_EN BIT(2) +#define CFG_RX_PREQ_EN BIT(3) +#define CFG_RX_MGMT_EN BIT(4) +#define CFG_RX_FCS_ERROR BIT(5) +#define CFG_RX_DATA_EN BIT(6) +#define CFG_RX_CTL_EN BIT(7) +#define CFG_RX_CF_EN BIT(8) +#define CFG_RX_BCN_EN BIT(9) +#define CFG_RX_AUTH_EN BIT(10) +#define CFG_RX_ASSOC_EN BIT(11) + +#define SCAN_PASSIVE BIT(0) +#define SCAN_5GHZ_BAND BIT(1) +#define SCAN_TRIGGERED BIT(2) +#define SCAN_PRIORITY_HIGH BIT(3) + +/* When set, disable HW encryption */ +#define DF_ENCRYPTION_DISABLE 0x01 +#define DF_SNIFF_MODE_ENABLE 0x80 + +struct acx_feature_config { + struct acx_header header; + + __le32 options; + __le32 data_flow_options; +} __packed; + +struct acx_current_tx_power { + struct acx_header header; + + u8 current_tx_power; + u8 padding[3]; +} __packed; + +struct acx_wake_up_condition { + struct acx_header header; + + u8 wake_up_event; /* Only one bit can be set */ + u8 listen_interval; + u8 pad[2]; +} __packed; + +struct acx_aid { + struct acx_header header; + + /* + * To be set when associated with an AP. + */ + __le16 aid; + u8 pad[2]; +} __packed; + +enum acx_preamble_type { + ACX_PREAMBLE_LONG = 0, + ACX_PREAMBLE_SHORT = 1 +}; + +struct acx_preamble { + struct acx_header header; + + /* + * When set, the WiLink transmits the frames with a short preamble and + * when cleared, the WiLink transmits the frames with a long preamble. + */ + u8 preamble; + u8 padding[3]; +} __packed; + +enum acx_ctsprotect_type { + CTSPROTECT_DISABLE = 0, + CTSPROTECT_ENABLE = 1 +}; + +struct acx_ctsprotect { + struct acx_header header; + u8 ctsprotect; + u8 padding[3]; +} __packed; + +struct acx_tx_statistics { + __le32 internal_desc_overflow; +} __packed; + +struct acx_rx_statistics { + __le32 out_of_mem; + __le32 hdr_overflow; + __le32 hw_stuck; + __le32 dropped; + __le32 fcs_err; + __le32 xfr_hint_trig; + __le32 path_reset; + __le32 reset_counter; +} __packed; + +struct acx_dma_statistics { + __le32 rx_requested; + __le32 rx_errors; + __le32 tx_requested; + __le32 tx_errors; +} __packed; + +struct acx_isr_statistics { + /* host command complete */ + __le32 cmd_cmplt; + + /* fiqisr() */ + __le32 fiqs; + + /* (INT_STS_ND & INT_TRIG_RX_HEADER) */ + __le32 rx_headers; + + /* (INT_STS_ND & INT_TRIG_RX_CMPLT) */ + __le32 rx_completes; + + /* (INT_STS_ND & INT_TRIG_NO_RX_BUF) */ + __le32 rx_mem_overflow; + + /* (INT_STS_ND & INT_TRIG_S_RX_RDY) */ + __le32 rx_rdys; + + /* irqisr() */ + __le32 irqs; + + /* (INT_STS_ND & INT_TRIG_TX_PROC) */ + __le32 tx_procs; + + /* (INT_STS_ND & INT_TRIG_DECRYPT_DONE) */ + __le32 decrypt_done; + + /* (INT_STS_ND & INT_TRIG_DMA0) */ + __le32 dma0_done; + + /* (INT_STS_ND & INT_TRIG_DMA1) */ + __le32 dma1_done; + + /* (INT_STS_ND & INT_TRIG_TX_EXC_CMPLT) */ + __le32 tx_exch_complete; + + /* (INT_STS_ND & INT_TRIG_COMMAND) */ + __le32 commands; + + /* (INT_STS_ND & INT_TRIG_RX_PROC) */ + __le32 rx_procs; + + /* (INT_STS_ND & INT_TRIG_PM_802) */ + __le32 hw_pm_mode_changes; + + /* (INT_STS_ND & INT_TRIG_ACKNOWLEDGE) */ + __le32 host_acknowledges; + + /* (INT_STS_ND & INT_TRIG_PM_PCI) */ + __le32 pci_pm; + + /* (INT_STS_ND & INT_TRIG_ACM_WAKEUP) */ + __le32 wakeups; + + /* (INT_STS_ND & INT_TRIG_LOW_RSSI) */ + __le32 low_rssi; +} __packed; + +struct acx_wep_statistics { + /* WEP address keys configured */ + __le32 addr_key_count; + + /* default keys configured */ + __le32 default_key_count; + + __le32 reserved; + + /* number of times that WEP key not found on lookup */ + __le32 key_not_found; + + /* number of times that WEP key decryption failed */ + __le32 decrypt_fail; + + /* WEP packets decrypted */ + __le32 packets; + + /* WEP decrypt interrupts */ + __le32 interrupt; +} __packed; + +#define ACX_MISSED_BEACONS_SPREAD 10 + +struct acx_pwr_statistics { + /* the amount of enters into power save mode (both PD & ELP) */ + __le32 ps_enter; + + /* the amount of enters into ELP mode */ + __le32 elp_enter; + + /* the amount of missing beacon interrupts to the host */ + __le32 missing_bcns; + + /* the amount of wake on host-access times */ + __le32 wake_on_host; + + /* the amount of wake on timer-expire */ + __le32 wake_on_timer_exp; + + /* the number of packets that were transmitted with PS bit set */ + __le32 tx_with_ps; + + /* the number of packets that were transmitted with PS bit clear */ + __le32 tx_without_ps; + + /* the number of received beacons */ + __le32 rcvd_beacons; + + /* the number of entering into PowerOn (power save off) */ + __le32 power_save_off; + + /* the number of entries into power save mode */ + __le16 enable_ps; + + /* + * the number of exits from power save, not including failed PS + * transitions + */ + __le16 disable_ps; + + /* + * the number of times the TSF counter was adjusted because + * of drift + */ + __le32 fix_tsf_ps; + + /* Gives statistics about the spread continuous missed beacons. + * The 16 LSB are dedicated for the PS mode. + * The 16 MSB are dedicated for the PS mode. + * cont_miss_bcns_spread[0] - single missed beacon. + * cont_miss_bcns_spread[1] - two continuous missed beacons. + * cont_miss_bcns_spread[2] - three continuous missed beacons. + * ... + * cont_miss_bcns_spread[9] - ten and more continuous missed beacons. + */ + __le32 cont_miss_bcns_spread[ACX_MISSED_BEACONS_SPREAD]; + + /* the number of beacons in awake mode */ + __le32 rcvd_awake_beacons; +} __packed; + +struct acx_mic_statistics { + __le32 rx_pkts; + __le32 calc_failure; +} __packed; + +struct acx_aes_statistics { + __le32 encrypt_fail; + __le32 decrypt_fail; + __le32 encrypt_packets; + __le32 decrypt_packets; + __le32 encrypt_interrupt; + __le32 decrypt_interrupt; +} __packed; + +struct acx_event_statistics { + __le32 heart_beat; + __le32 calibration; + __le32 rx_mismatch; + __le32 rx_mem_empty; + __le32 rx_pool; + __le32 oom_late; + __le32 phy_transmit_error; + __le32 tx_stuck; +} __packed; + +struct acx_ps_statistics { + __le32 pspoll_timeouts; + __le32 upsd_timeouts; + __le32 upsd_max_sptime; + __le32 upsd_max_apturn; + __le32 pspoll_max_apturn; + __le32 pspoll_utilization; + __le32 upsd_utilization; +} __packed; + +struct acx_rxpipe_statistics { + __le32 rx_prep_beacon_drop; + __le32 descr_host_int_trig_rx_data; + __le32 beacon_buffer_thres_host_int_trig_rx_data; + __le32 missed_beacon_host_int_trig_rx_data; + __le32 tx_xfr_host_int_trig_rx_data; +} __packed; + +struct acx_statistics { + struct acx_header header; + + struct acx_tx_statistics tx; + struct acx_rx_statistics rx; + struct acx_dma_statistics dma; + struct acx_isr_statistics isr; + struct acx_wep_statistics wep; + struct acx_pwr_statistics pwr; + struct acx_aes_statistics aes; + struct acx_mic_statistics mic; + struct acx_event_statistics event; + struct acx_ps_statistics ps; + struct acx_rxpipe_statistics rxpipe; +} __packed; + +struct acx_rate_class { + __le32 enabled_rates; + u8 short_retry_limit; + u8 long_retry_limit; + u8 aflags; + u8 reserved; +}; + +#define ACX_TX_BASIC_RATE 0 +#define ACX_TX_AP_FULL_RATE 1 +#define ACX_TX_RATE_POLICY_CNT 2 +struct acx_sta_rate_policy { + struct acx_header header; + + __le32 rate_class_cnt; + struct acx_rate_class rate_class[CONF_TX_MAX_RATE_CLASSES]; +} __packed; + + +#define ACX_TX_AP_MODE_MGMT_RATE 4 +#define ACX_TX_AP_MODE_BCST_RATE 5 +struct acx_ap_rate_policy { + struct acx_header header; + + __le32 rate_policy_idx; + struct acx_rate_class rate_policy; +} __packed; + +struct acx_ac_cfg { + struct acx_header header; + u8 ac; + u8 cw_min; + __le16 cw_max; + u8 aifsn; + u8 reserved; + __le16 tx_op_limit; +} __packed; + +struct acx_tid_config { + struct acx_header header; + u8 queue_id; + u8 channel_type; + u8 tsid; + u8 ps_scheme; + u8 ack_policy; + u8 padding[3]; + __le32 apsd_conf[2]; +} __packed; + +struct acx_frag_threshold { + struct acx_header header; + __le16 frag_threshold; + u8 padding[2]; +} __packed; + +struct acx_tx_config_options { + struct acx_header header; + __le16 tx_compl_timeout; /* msec */ + __le16 tx_compl_threshold; /* number of packets */ +} __packed; + +#define ACX_TX_DESCRIPTORS 32 + +struct wl1271_acx_ap_config_memory { + struct acx_header header; + + u8 rx_mem_block_num; + u8 tx_min_mem_block_num; + u8 num_stations; + u8 num_ssid_profiles; + __le32 total_tx_descriptors; +} __packed; + +struct wl1271_acx_sta_config_memory { + struct acx_header header; + + u8 rx_mem_block_num; + u8 tx_min_mem_block_num; + u8 num_stations; + u8 num_ssid_profiles; + __le32 total_tx_descriptors; + u8 dyn_mem_enable; + u8 tx_free_req; + u8 rx_free_req; + u8 tx_min; +} __packed; + +struct wl1271_acx_mem_map { + struct acx_header header; + + __le32 code_start; + __le32 code_end; + + __le32 wep_defkey_start; + __le32 wep_defkey_end; + + __le32 sta_table_start; + __le32 sta_table_end; + + __le32 packet_template_start; + __le32 packet_template_end; + + /* Address of the TX result interface (control block) */ + __le32 tx_result; + __le32 tx_result_queue_start; + + __le32 queue_memory_start; + __le32 queue_memory_end; + + __le32 packet_memory_pool_start; + __le32 packet_memory_pool_end; + + __le32 debug_buffer1_start; + __le32 debug_buffer1_end; + + __le32 debug_buffer2_start; + __le32 debug_buffer2_end; + + /* Number of blocks FW allocated for TX packets */ + __le32 num_tx_mem_blocks; + + /* Number of blocks FW allocated for RX packets */ + __le32 num_rx_mem_blocks; + + /* the following 4 fields are valid in SLAVE mode only */ + u8 *tx_cbuf; + u8 *rx_cbuf; + __le32 rx_ctrl; + __le32 tx_ctrl; +} __packed; + +struct wl1271_acx_rx_config_opt { + struct acx_header header; + + __le16 mblk_threshold; + __le16 threshold; + __le16 timeout; + u8 queue_type; + u8 reserved; +} __packed; + + +struct wl1271_acx_bet_enable { + struct acx_header header; + + u8 enable; + u8 max_consecutive; + u8 padding[2]; +} __packed; + +#define ACX_IPV4_VERSION 4 +#define ACX_IPV6_VERSION 6 +#define ACX_IPV4_ADDR_SIZE 4 + +/* bitmap of enabled arp_filter features */ +#define ACX_ARP_FILTER_ARP_FILTERING BIT(0) +#define ACX_ARP_FILTER_AUTO_ARP BIT(1) + +struct wl1271_acx_arp_filter { + struct acx_header header; + u8 version; /* ACX_IPV4_VERSION, ACX_IPV6_VERSION */ + u8 enable; /* bitmap of enabled ARP filtering features */ + u8 padding[2]; + u8 address[16]; /* The configured device IP address - all ARP + requests directed to this IP address will pass + through. For IPv4, the first four bytes are + used. */ +} __packed; + +struct wl1271_acx_pm_config { + struct acx_header header; + + __le32 host_clk_settling_time; + u8 host_fast_wakeup_support; + u8 padding[3]; +} __packed; + +struct wl1271_acx_keep_alive_mode { + struct acx_header header; + + u8 enabled; + u8 padding[3]; +} __packed; + +enum { + ACX_KEEP_ALIVE_NO_TX = 0, + ACX_KEEP_ALIVE_PERIOD_ONLY +}; + +enum { + ACX_KEEP_ALIVE_TPL_INVALID = 0, + ACX_KEEP_ALIVE_TPL_VALID +}; + +struct wl1271_acx_keep_alive_config { + struct acx_header header; + + __le32 period; + u8 index; + u8 tpl_validation; + u8 trigger; + u8 padding; +} __packed; + +#define HOST_IF_CFG_RX_FIFO_ENABLE BIT(0) +#define HOST_IF_CFG_TX_EXTRA_BLKS_SWAP BIT(1) +#define HOST_IF_CFG_TX_PAD_TO_SDIO_BLK BIT(3) + +struct wl1271_acx_host_config_bitmap { + struct acx_header header; + + __le32 host_cfg_bitmap; +} __packed; + +enum { + WL1271_ACX_TRIG_TYPE_LEVEL = 0, + WL1271_ACX_TRIG_TYPE_EDGE, +}; + +enum { + WL1271_ACX_TRIG_DIR_LOW = 0, + WL1271_ACX_TRIG_DIR_HIGH, + WL1271_ACX_TRIG_DIR_BIDIR, +}; + +enum { + WL1271_ACX_TRIG_ENABLE = 1, + WL1271_ACX_TRIG_DISABLE, +}; + +enum { + WL1271_ACX_TRIG_METRIC_RSSI_BEACON = 0, + WL1271_ACX_TRIG_METRIC_RSSI_DATA, + WL1271_ACX_TRIG_METRIC_SNR_BEACON, + WL1271_ACX_TRIG_METRIC_SNR_DATA, +}; + +enum { + WL1271_ACX_TRIG_IDX_RSSI = 0, + WL1271_ACX_TRIG_COUNT = 8, +}; + +struct wl1271_acx_rssi_snr_trigger { + struct acx_header header; + + __le16 threshold; + __le16 pacing; /* 0 - 60000 ms */ + u8 metric; + u8 type; + u8 dir; + u8 hysteresis; + u8 index; + u8 enable; + u8 padding[2]; +}; + +struct wl1271_acx_rssi_snr_avg_weights { + struct acx_header header; + + u8 rssi_beacon; + u8 rssi_data; + u8 snr_beacon; + u8 snr_data; +}; + +/* + * ACX_PEER_HT_CAP + * Configure HT capabilities - declare the capabilities of the peer + * we are connected to. + */ +struct wl1271_acx_ht_capabilities { + struct acx_header header; + + /* + * bit 0 - Allow HT Operation + * bit 1 - Allow Greenfield format in TX + * bit 2 - Allow Short GI in TX + * bit 3 - Allow L-SIG TXOP Protection in TX + * bit 4 - Allow HT Control fields in TX. + * Note, driver will still leave space for HT control in packets + * regardless of the value of this field. FW will be responsible + * to drop the HT field from any frame when this Bit set to 0. + * bit 5 - Allow RD initiation in TXOP. FW is allowed to initate RD. + * Exact policy setting for this feature is TBD. + * Note, this bit can only be set to 1 if bit 3 is set to 1. + */ + __le32 ht_capabilites; + + /* + * Indicates to which peer these capabilities apply. + * For infrastructure use ff:ff:ff:ff:ff:ff that indicates relevance + * for all peers. + * Only valid for IBSS/DLS operation. + */ + u8 mac_address[ETH_ALEN]; + + /* + * This the maximum A-MPDU length supported by the AP. The FW may not + * exceed this length when sending A-MPDUs + */ + u8 ampdu_max_length; + + /* This is the minimal spacing required when sending A-MPDUs to the AP*/ + u8 ampdu_min_spacing; +} __packed; + +/* HT Capabilites Fw Bit Mask Mapping */ +#define WL1271_ACX_FW_CAP_HT_OPERATION BIT(0) +#define WL1271_ACX_FW_CAP_GREENFIELD_FRAME_FORMAT BIT(1) +#define WL1271_ACX_FW_CAP_SHORT_GI_FOR_20MHZ_PACKETS BIT(2) +#define WL1271_ACX_FW_CAP_LSIG_TXOP_PROTECTION BIT(3) +#define WL1271_ACX_FW_CAP_HT_CONTROL_FIELDS BIT(4) +#define WL1271_ACX_FW_CAP_RD_INITIATION BIT(5) + + +/* + * ACX_HT_BSS_OPERATION + * Configure HT capabilities - AP rules for behavior in the BSS. + */ +struct wl1271_acx_ht_information { + struct acx_header header; + + /* Values: 0 - RIFS not allowed, 1 - RIFS allowed */ + u8 rifs_mode; + + /* Values: 0 - 3 like in spec */ + u8 ht_protection; + + /* Values: 0 - GF protection not required, 1 - GF protection required */ + u8 gf_protection; + + /*Values: 0 - TX Burst limit not required, 1 - TX Burst Limit required*/ + u8 ht_tx_burst_limit; + + /* + * Values: 0 - Dual CTS protection not required, + * 1 - Dual CTS Protection required + * Note: When this value is set to 1 FW will protect all TXOP with RTS + * frame and will not use CTS-to-self regardless of the value of the + * ACX_CTS_PROTECTION information element + */ + u8 dual_cts_protection; + + u8 padding[3]; +} __packed; + +#define RX_BA_WIN_SIZE 8 + +struct wl1271_acx_ba_session_policy { + struct acx_header header; + /* + * Specifies role Id, Range 0-7, 0xFF means ANY role. + * Future use. For now this field is irrelevant + */ + u8 role_id; + /* + * Specifies Link Id, Range 0-31, 0xFF means ANY Link Id. + * Not applicable if Role Id is set to ANY. + */ + u8 link_id; + + u8 tid; + + u8 enable; + + /* Windows size in number of packets */ + u16 win_size; + + /* + * As initiator inactivity timeout in time units(TU) of 1024us. + * As receiver reserved + */ + u16 inactivity_timeout; + + /* Initiator = 1/Receiver = 0 */ + u8 ba_direction; + + u8 padding[3]; +} __packed; + +struct wl1271_acx_ba_receiver_setup { + struct acx_header header; + + /* Specifies Link Id, Range 0-31, 0xFF means ANY Link Id */ + u8 link_id; + + u8 tid; + + u8 enable; + + u8 padding[1]; + + /* Windows size in number of packets */ + u16 win_size; + + /* BA session starting sequence number. RANGE 0-FFF */ + u16 ssn; +} __packed; + +struct wl1271_acx_fw_tsf_information { + struct acx_header header; + + __le32 current_tsf_high; + __le32 current_tsf_low; + __le32 last_bttt_high; + __le32 last_tbtt_low; + u8 last_dtim_count; + u8 padding[3]; +} __packed; + +struct wl1271_acx_max_tx_retry { + struct acx_header header; + + /* + * the number of frames transmission failures before + * issuing the aging event. + */ + __le16 max_tx_retry; + u8 padding_1[2]; +} __packed; + +struct wl1271_acx_config_ps { + struct acx_header header; + + u8 exit_retries; + u8 enter_retries; + u8 padding[2]; + __le32 null_data_rate; +} __packed; + +struct wl1271_acx_inconnection_sta { + struct acx_header header; + + u8 addr[ETH_ALEN]; + u8 padding1[2]; +} __packed; + +struct acx_ap_beacon_filter { + struct acx_header header; + + u8 enable; + u8 pad[3]; +} __packed; + +/* + * ACX_FM_COEX_CFG + * set the FM co-existence parameters. + */ +struct wl1271_acx_fm_coex { + struct acx_header header; + /* enable(1) / disable(0) the FM Coex feature */ + u8 enable; + /* + * Swallow period used in COEX PLL swallowing mechanism. + * 0xFF = use FW default + */ + u8 swallow_period; + /* + * The N divider used in COEX PLL swallowing mechanism for Fref of + * 38.4/19.2 Mhz. 0xFF = use FW default + */ + u8 n_divider_fref_set_1; + /* + * The N divider used in COEX PLL swallowing mechanism for Fref of + * 26/52 Mhz. 0xFF = use FW default + */ + u8 n_divider_fref_set_2; + /* + * The M divider used in COEX PLL swallowing mechanism for Fref of + * 38.4/19.2 Mhz. 0xFFFF = use FW default + */ + __le16 m_divider_fref_set_1; + /* + * The M divider used in COEX PLL swallowing mechanism for Fref of + * 26/52 Mhz. 0xFFFF = use FW default + */ + __le16 m_divider_fref_set_2; + /* + * The time duration in uSec required for COEX PLL to stabilize. + * 0xFFFFFFFF = use FW default + */ + __le32 coex_pll_stabilization_time; + /* + * The time duration in uSec required for LDO to stabilize. + * 0xFFFFFFFF = use FW default + */ + __le16 ldo_stabilization_time; + /* + * The disturbed frequency band margin around the disturbed frequency + * center (single sided). + * For example, if 2 is configured, the following channels will be + * considered disturbed channel: + * 80 +- 0.1 MHz, 91 +- 0.1 MHz, 98 +- 0.1 MHz, 102 +- 0.1 MH + * 0xFF = use FW default + */ + u8 fm_disturbed_band_margin; + /* + * The swallow clock difference of the swallowing mechanism. + * 0xFF = use FW default + */ + u8 swallow_clk_diff; +} __packed; + +enum { + ACX_WAKE_UP_CONDITIONS = 0x0002, + ACX_MEM_CFG = 0x0003, + ACX_SLOT = 0x0004, + ACX_AC_CFG = 0x0007, + ACX_MEM_MAP = 0x0008, + ACX_AID = 0x000A, + /* ACX_FW_REV is missing in the ref driver, but seems to work */ + ACX_FW_REV = 0x000D, + ACX_MEDIUM_USAGE = 0x000F, + ACX_RX_CFG = 0x0010, + ACX_TX_QUEUE_CFG = 0x0011, /* FIXME: only used by wl1251 */ + ACX_STATISTICS = 0x0013, /* Debug API */ + ACX_PWR_CONSUMPTION_STATISTICS = 0x0014, + ACX_FEATURE_CFG = 0x0015, + ACX_TID_CFG = 0x001A, + ACX_PS_RX_STREAMING = 0x001B, + ACX_BEACON_FILTER_OPT = 0x001F, + ACX_AP_BEACON_FILTER_OPT = 0x0020, + ACX_NOISE_HIST = 0x0021, + ACX_HDK_VERSION = 0x0022, /* ??? */ + ACX_PD_THRESHOLD = 0x0023, + ACX_TX_CONFIG_OPT = 0x0024, + ACX_CCA_THRESHOLD = 0x0025, + ACX_EVENT_MBOX_MASK = 0x0026, + ACX_CONN_MONIT_PARAMS = 0x002D, + ACX_CONS_TX_FAILURE = 0x002F, + ACX_BCN_DTIM_OPTIONS = 0x0031, + ACX_SG_ENABLE = 0x0032, + ACX_SG_CFG = 0x0033, + ACX_FM_COEX_CFG = 0x0034, + ACX_BEACON_FILTER_TABLE = 0x0038, + ACX_ARP_IP_FILTER = 0x0039, + ACX_ROAMING_STATISTICS_TBL = 0x003B, + ACX_RATE_POLICY = 0x003D, + ACX_CTS_PROTECTION = 0x003E, + ACX_SLEEP_AUTH = 0x003F, + ACX_PREAMBLE_TYPE = 0x0040, + ACX_ERROR_CNT = 0x0041, + ACX_IBSS_FILTER = 0x0044, + ACX_SERVICE_PERIOD_TIMEOUT = 0x0045, + ACX_TSF_INFO = 0x0046, + ACX_CONFIG_PS_WMM = 0x0049, + ACX_ENABLE_RX_DATA_FILTER = 0x004A, + ACX_SET_RX_DATA_FILTER = 0x004B, + ACX_GET_DATA_FILTER_STATISTICS = 0x004C, + ACX_RX_CONFIG_OPT = 0x004E, + ACX_FRAG_CFG = 0x004F, + ACX_BET_ENABLE = 0x0050, + ACX_RSSI_SNR_TRIGGER = 0x0051, + ACX_RSSI_SNR_WEIGHTS = 0x0052, + ACX_KEEP_ALIVE_MODE = 0x0053, + ACX_SET_KEEP_ALIVE_CONFIG = 0x0054, + ACX_BA_SESSION_POLICY_CFG = 0x0055, + ACX_BA_SESSION_RX_SETUP = 0x0056, + ACX_PEER_HT_CAP = 0x0057, + ACX_HT_BSS_OPERATION = 0x0058, + ACX_COEX_ACTIVITY = 0x0059, + ACX_SET_DCO_ITRIM_PARAMS = 0x0061, + ACX_GEN_FW_CMD = 0x0070, + ACX_HOST_IF_CFG_BITMAP = 0x0071, + ACX_MAX_TX_FAILURE = 0x0072, + ACX_UPDATE_INCONNECTION_STA_LIST = 0x0073, + DOT11_RX_MSDU_LIFE_TIME = 0x1004, + DOT11_CUR_TX_PWR = 0x100D, + DOT11_RX_DOT11_MODE = 0x1012, + DOT11_RTS_THRESHOLD = 0x1013, + DOT11_GROUP_ADDRESS_TBL = 0x1014, + ACX_PM_CONFIG = 0x1016, + ACX_CONFIG_PS = 0x1017, + ACX_CONFIG_HANGOVER = 0x1018, +}; + + +int wl1271_acx_wake_up_conditions(struct wl1271 *wl); +int wl1271_acx_sleep_auth(struct wl1271 *wl, u8 sleep_auth); +int wl1271_acx_tx_power(struct wl1271 *wl, int power); +int wl1271_acx_feature_cfg(struct wl1271 *wl); +int wl1271_acx_mem_map(struct wl1271 *wl, + struct acx_header *mem_map, size_t len); +int wl1271_acx_rx_msdu_life_time(struct wl1271 *wl); +int wl1271_acx_rx_config(struct wl1271 *wl, u32 config, u32 filter); +int wl1271_acx_pd_threshold(struct wl1271 *wl); +int wl1271_acx_slot(struct wl1271 *wl, enum acx_slot_type slot_time); +int wl1271_acx_group_address_tbl(struct wl1271 *wl, bool enable, + void *mc_list, u32 mc_list_len); +int wl1271_acx_service_period_timeout(struct wl1271 *wl); +int wl1271_acx_rts_threshold(struct wl1271 *wl, u32 rts_threshold); +int wl1271_acx_dco_itrim_params(struct wl1271 *wl); +int wl1271_acx_beacon_filter_opt(struct wl1271 *wl, bool enable_filter); +int wl1271_acx_beacon_filter_table(struct wl1271 *wl); +int wl1271_acx_conn_monit_params(struct wl1271 *wl, bool enable); +int wl1271_acx_sg_enable(struct wl1271 *wl, bool enable); +int wl1271_acx_sta_sg_cfg(struct wl1271 *wl); +int wl1271_acx_ap_sg_cfg(struct wl1271 *wl); +int wl1271_acx_cca_threshold(struct wl1271 *wl); +int wl1271_acx_bcn_dtim_options(struct wl1271 *wl); +int wl1271_acx_aid(struct wl1271 *wl, u16 aid); +int wl1271_acx_event_mbox_mask(struct wl1271 *wl, u32 event_mask); +int wl1271_acx_set_preamble(struct wl1271 *wl, enum acx_preamble_type preamble); +int wl1271_acx_cts_protect(struct wl1271 *wl, + enum acx_ctsprotect_type ctsprotect); +int wl1271_acx_statistics(struct wl1271 *wl, struct acx_statistics *stats); +int wl1271_acx_sta_rate_policies(struct wl1271 *wl); +int wl1271_acx_ap_rate_policy(struct wl1271 *wl, struct conf_tx_rate_class *c, + u8 idx); +int wl1271_acx_ac_cfg(struct wl1271 *wl, u8 ac, u8 cw_min, u16 cw_max, + u8 aifsn, u16 txop); +int wl1271_acx_tid_cfg(struct wl1271 *wl, u8 queue_id, u8 channel_type, + u8 tsid, u8 ps_scheme, u8 ack_policy, + u32 apsd_conf0, u32 apsd_conf1); +int wl1271_acx_frag_threshold(struct wl1271 *wl, u32 frag_threshold); +int wl1271_acx_tx_config_options(struct wl1271 *wl); +int wl1271_acx_ap_mem_cfg(struct wl1271 *wl); +int wl1271_acx_sta_mem_cfg(struct wl1271 *wl); +int wl1271_acx_init_mem_config(struct wl1271 *wl); +int wl1271_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap); +int wl1271_acx_init_rx_interrupt(struct wl1271 *wl); +int wl1271_acx_smart_reflex(struct wl1271 *wl); +int wl1271_acx_bet_enable(struct wl1271 *wl, bool enable); +int wl1271_acx_arp_ip_filter(struct wl1271 *wl, u8 enable, __be32 address); +int wl1271_acx_pm_config(struct wl1271 *wl); +int wl1271_acx_keep_alive_mode(struct wl1271 *wl, bool enable); +int wl1271_acx_keep_alive_config(struct wl1271 *wl, u8 index, u8 tpl_valid); +int wl1271_acx_rssi_snr_trigger(struct wl1271 *wl, bool enable, + s16 thold, u8 hyst); +int wl1271_acx_rssi_snr_avg_weights(struct wl1271 *wl); +int wl1271_acx_set_ht_capabilities(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation); +int wl1271_acx_set_ht_information(struct wl1271 *wl, + u16 ht_operation_mode); +int wl1271_acx_set_ba_session(struct wl1271 *wl, + enum ieee80211_back_parties direction, + u8 tid_index, u8 policy); +int wl1271_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, u16 ssn, + bool enable); +int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime); +int wl1271_acx_max_tx_retry(struct wl1271 *wl); +int wl1271_acx_config_ps(struct wl1271 *wl); +int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); +int wl1271_acx_set_ap_beacon_filter(struct wl1271 *wl, bool enable); +int wl1271_acx_fm_coex(struct wl1271 *wl); + +#endif /* __WL1271_ACX_H__ */ diff --git a/drivers/net/wireless/wl12xx/boot.c b/drivers/net/wireless/wl12xx/boot.c new file mode 100644 index 00000000..e0e16888 --- /dev/null +++ b/drivers/net/wireless/wl12xx/boot.c @@ -0,0 +1,840 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/slab.h> +#include <linux/wl12xx.h> + +#include "acx.h" +#include "reg.h" +#include "boot.h" +#include "io.h" +#include "event.h" +#include "rx.h" + +static struct wl1271_partition_set part_table[PART_TABLE_LEN] = { + [PART_DOWN] = { + .mem = { + .start = 0x00000000, + .size = 0x000177c0 + }, + .reg = { + .start = REGISTERS_BASE, + .size = 0x00008800 + }, + .mem2 = { + .start = 0x00000000, + .size = 0x00000000 + }, + .mem3 = { + .start = 0x00000000, + .size = 0x00000000 + }, + }, + + [PART_WORK] = { + .mem = { + .start = 0x00040000, + .size = 0x00014fc0 + }, + .reg = { + .start = REGISTERS_BASE, + .size = 0x0000a000 + }, + .mem2 = { + .start = 0x003004f8, + .size = 0x00000004 + }, + .mem3 = { + .start = 0x00040404, + .size = 0x00000000 + }, + }, + + [PART_DRPW] = { + .mem = { + .start = 0x00040000, + .size = 0x00014fc0 + }, + .reg = { + .start = DRPW_BASE, + .size = 0x00006000 + }, + .mem2 = { + .start = 0x00000000, + .size = 0x00000000 + }, + .mem3 = { + .start = 0x00000000, + .size = 0x00000000 + } + } +}; + +static void wl1271_boot_set_ecpu_ctrl(struct wl1271 *wl, u32 flag) +{ + u32 cpu_ctrl; + + /* 10.5.0 run the firmware (I) */ + cpu_ctrl = wl1271_read32(wl, ACX_REG_ECPU_CONTROL); + + /* 10.5.1 run the firmware (II) */ + cpu_ctrl |= flag; + wl1271_write32(wl, ACX_REG_ECPU_CONTROL, cpu_ctrl); +} + +static void wl1271_parse_fw_ver(struct wl1271 *wl) +{ + int ret; + + ret = sscanf(wl->chip.fw_ver_str + 4, "%u.%u.%u.%u.%u", + &wl->chip.fw_ver[0], &wl->chip.fw_ver[1], + &wl->chip.fw_ver[2], &wl->chip.fw_ver[3], + &wl->chip.fw_ver[4]); + + if (ret != 5) { + wl1271_warning("fw version incorrect value"); + memset(wl->chip.fw_ver, 0, sizeof(wl->chip.fw_ver)); + return; + } +} + +static void wl1271_boot_fw_version(struct wl1271 *wl) +{ + struct wl1271_static_data static_data; + + wl1271_read(wl, wl->cmd_box_addr, &static_data, sizeof(static_data), + false); + + strncpy(wl->chip.fw_ver_str, static_data.fw_version, + sizeof(wl->chip.fw_ver_str)); + + /* make sure the string is NULL-terminated */ + wl->chip.fw_ver_str[sizeof(wl->chip.fw_ver_str) - 1] = '\0'; + + wl1271_parse_fw_ver(wl); +} + +static int wl1271_boot_upload_firmware_chunk(struct wl1271 *wl, void *buf, + size_t fw_data_len, u32 dest) +{ + struct wl1271_partition_set partition; + int addr, chunk_num, partition_limit; + u8 *p, *chunk; + + /* whal_FwCtrl_LoadFwImageSm() */ + + wl1271_debug(DEBUG_BOOT, "starting firmware upload"); + + wl1271_debug(DEBUG_BOOT, "fw_data_len %zd chunk_size %d", + fw_data_len, CHUNK_SIZE); + + if ((fw_data_len % 4) != 0) { + wl1271_error("firmware length not multiple of four"); + return -EIO; + } + + chunk = kmalloc(CHUNK_SIZE, GFP_KERNEL); + if (!chunk) { + wl1271_error("allocation for firmware upload chunk failed"); + return -ENOMEM; + } + + memcpy(&partition, &part_table[PART_DOWN], sizeof(partition)); + partition.mem.start = dest; + wl1271_set_partition(wl, &partition); + + /* 10.1 set partition limit and chunk num */ + chunk_num = 0; + partition_limit = part_table[PART_DOWN].mem.size; + + while (chunk_num < fw_data_len / CHUNK_SIZE) { + /* 10.2 update partition, if needed */ + addr = dest + (chunk_num + 2) * CHUNK_SIZE; + if (addr > partition_limit) { + addr = dest + chunk_num * CHUNK_SIZE; + partition_limit = chunk_num * CHUNK_SIZE + + part_table[PART_DOWN].mem.size; + partition.mem.start = addr; + wl1271_set_partition(wl, &partition); + } + + /* 10.3 upload the chunk */ + addr = dest + chunk_num * CHUNK_SIZE; + p = buf + chunk_num * CHUNK_SIZE; + memcpy(chunk, p, CHUNK_SIZE); + wl1271_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x", + p, addr); + wl1271_write(wl, addr, chunk, CHUNK_SIZE, false); + + chunk_num++; + } + + /* 10.4 upload the last chunk */ + addr = dest + chunk_num * CHUNK_SIZE; + p = buf + chunk_num * CHUNK_SIZE; + memcpy(chunk, p, fw_data_len % CHUNK_SIZE); + wl1271_debug(DEBUG_BOOT, "uploading fw last chunk (%zd B) 0x%p to 0x%x", + fw_data_len % CHUNK_SIZE, p, addr); + wl1271_write(wl, addr, chunk, fw_data_len % CHUNK_SIZE, false); + + kfree(chunk); + return 0; +} + +static int wl1271_boot_upload_firmware(struct wl1271 *wl) +{ + u32 chunks, addr, len; + int ret = 0; + u8 *fw; + + fw = wl->fw; + chunks = be32_to_cpup((__be32 *) fw); + fw += sizeof(u32); + + wl1271_debug(DEBUG_BOOT, "firmware chunks to be uploaded: %u", chunks); + + while (chunks--) { + addr = be32_to_cpup((__be32 *) fw); + fw += sizeof(u32); + len = be32_to_cpup((__be32 *) fw); + fw += sizeof(u32); + + if (len > 300000) { + wl1271_info("firmware chunk too long: %u", len); + return -EINVAL; + } + wl1271_debug(DEBUG_BOOT, "chunk %d addr 0x%x len %u", + chunks, addr, len); + ret = wl1271_boot_upload_firmware_chunk(wl, fw, len, addr); + if (ret != 0) + break; + fw += len; + } + + return ret; +} + +static int wl1271_boot_upload_nvs(struct wl1271 *wl) +{ + size_t nvs_len, burst_len; + int i; + u32 dest_addr, val; + u8 *nvs_ptr, *nvs_aligned; + + if (wl->nvs == NULL) + return -ENODEV; + + if (wl->chip.id == CHIP_ID_1283_PG20) { + struct wl128x_nvs_file *nvs = (struct wl128x_nvs_file *)wl->nvs; + + if (wl->nvs_len == sizeof(struct wl128x_nvs_file)) { + if (nvs->general_params.dual_mode_select) + wl->enable_11a = true; + } else { + wl1271_error("nvs size is not as expected: %zu != %zu", + wl->nvs_len, + sizeof(struct wl128x_nvs_file)); + kfree(wl->nvs); + wl->nvs = NULL; + wl->nvs_len = 0; + return -EILSEQ; + } + + /* only the first part of the NVS needs to be uploaded */ + nvs_len = sizeof(nvs->nvs); + nvs_ptr = (u8 *)nvs->nvs; + + } else { + struct wl1271_nvs_file *nvs = + (struct wl1271_nvs_file *)wl->nvs; + /* + * FIXME: the LEGACY NVS image support (NVS's missing the 5GHz + * band configurations) can be removed when those NVS files stop + * floating around. + */ + if (wl->nvs_len == sizeof(struct wl1271_nvs_file) || + wl->nvs_len == WL1271_INI_LEGACY_NVS_FILE_SIZE) { + /* for now 11a is unsupported in AP mode */ + if (wl->bss_type != BSS_TYPE_AP_BSS && + nvs->general_params.dual_mode_select) + wl->enable_11a = true; + } + + if (wl->nvs_len != sizeof(struct wl1271_nvs_file) && + (wl->nvs_len != WL1271_INI_LEGACY_NVS_FILE_SIZE || + wl->enable_11a)) { + wl1271_error("nvs size is not as expected: %zu != %zu", + wl->nvs_len, sizeof(struct wl1271_nvs_file)); + kfree(wl->nvs); + wl->nvs = NULL; + wl->nvs_len = 0; + return -EILSEQ; + } + + /* only the first part of the NVS needs to be uploaded */ + nvs_len = sizeof(nvs->nvs); + nvs_ptr = (u8 *) nvs->nvs; + } + + /* update current MAC address to NVS */ + nvs_ptr[11] = wl->mac_addr[0]; + nvs_ptr[10] = wl->mac_addr[1]; + nvs_ptr[6] = wl->mac_addr[2]; + nvs_ptr[5] = wl->mac_addr[3]; + nvs_ptr[4] = wl->mac_addr[4]; + nvs_ptr[3] = wl->mac_addr[5]; + + /* + * Layout before the actual NVS tables: + * 1 byte : burst length. + * 2 bytes: destination address. + * n bytes: data to burst copy. + * + * This is ended by a 0 length, then the NVS tables. + */ + + /* FIXME: Do we need to check here whether the LSB is 1? */ + while (nvs_ptr[0]) { + burst_len = nvs_ptr[0]; + dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8)); + + /* + * Due to our new wl1271_translate_reg_addr function, + * we need to add the REGISTER_BASE to the destination + */ + dest_addr += REGISTERS_BASE; + + /* We move our pointer to the data */ + nvs_ptr += 3; + + for (i = 0; i < burst_len; i++) { + if (nvs_ptr + 3 >= (u8 *) wl->nvs + nvs_len) + goto out_badnvs; + + val = (nvs_ptr[0] | (nvs_ptr[1] << 8) + | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); + + wl1271_debug(DEBUG_BOOT, + "nvs burst write 0x%x: 0x%x", + dest_addr, val); + wl1271_write32(wl, dest_addr, val); + + nvs_ptr += 4; + dest_addr += 4; + } + + if (nvs_ptr >= (u8 *) wl->nvs + nvs_len) + goto out_badnvs; + } + + /* + * We've reached the first zero length, the first NVS table + * is located at an aligned offset which is at least 7 bytes further. + * NOTE: The wl->nvs->nvs element must be first, in order to + * simplify the casting, we assume it is at the beginning of + * the wl->nvs structure. + */ + nvs_ptr = (u8 *)wl->nvs + + ALIGN(nvs_ptr - (u8 *)wl->nvs + 7, 4); + + if (nvs_ptr >= (u8 *) wl->nvs + nvs_len) + goto out_badnvs; + + nvs_len -= nvs_ptr - (u8 *)wl->nvs; + + /* Now we must set the partition correctly */ + wl1271_set_partition(wl, &part_table[PART_WORK]); + + /* Copy the NVS tables to a new block to ensure alignment */ + nvs_aligned = kmemdup(nvs_ptr, nvs_len, GFP_KERNEL); + if (!nvs_aligned) + return -ENOMEM; + + /* And finally we upload the NVS tables */ + wl1271_write(wl, CMD_MBOX_ADDRESS, nvs_aligned, nvs_len, false); + + kfree(nvs_aligned); + return 0; + +out_badnvs: + wl1271_error("nvs data is malformed"); + return -EILSEQ; +} + +static void wl1271_boot_enable_interrupts(struct wl1271 *wl) +{ + wl1271_enable_interrupts(wl); + wl1271_write32(wl, ACX_REG_INTERRUPT_MASK, + WL1271_ACX_INTR_ALL & ~(WL1271_INTR_MASK)); + wl1271_write32(wl, HI_CFG, HI_CFG_DEF_VAL); +} + +static int wl1271_boot_soft_reset(struct wl1271 *wl) +{ + unsigned long timeout; + u32 boot_data; + + /* perform soft reset */ + wl1271_write32(wl, ACX_REG_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT); + + /* SOFT_RESET is self clearing */ + timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME); + while (1) { + boot_data = wl1271_read32(wl, ACX_REG_SLV_SOFT_RESET); + wl1271_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data); + if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0) + break; + + if (time_after(jiffies, timeout)) { + /* 1.2 check pWhalBus->uSelfClearTime if the + * timeout was reached */ + wl1271_error("soft reset timeout"); + return -1; + } + + udelay(SOFT_RESET_STALL_TIME); + } + + /* disable Rx/Tx */ + wl1271_write32(wl, ENABLE, 0x0); + + /* disable auto calibration on start*/ + wl1271_write32(wl, SPARE_A2, 0xffff); + + return 0; +} + +static int wl1271_boot_run_firmware(struct wl1271 *wl) +{ + int loop, ret; + u32 chip_id, intr; + + wl1271_boot_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT); + + chip_id = wl1271_read32(wl, CHIP_ID_B); + + wl1271_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id); + + if (chip_id != wl->chip.id) { + wl1271_error("chip id doesn't match after firmware boot"); + return -EIO; + } + + /* wait for init to complete */ + loop = 0; + while (loop++ < INIT_LOOP) { + udelay(INIT_LOOP_DELAY); + intr = wl1271_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); + + if (intr == 0xffffffff) { + wl1271_error("error reading hardware complete " + "init indication"); + return -EIO; + } + /* check that ACX_INTR_INIT_COMPLETE is enabled */ + else if (intr & WL1271_ACX_INTR_INIT_COMPLETE) { + wl1271_write32(wl, ACX_REG_INTERRUPT_ACK, + WL1271_ACX_INTR_INIT_COMPLETE); + break; + } + } + + if (loop > INIT_LOOP) { + wl1271_error("timeout waiting for the hardware to " + "complete initialization"); + return -EIO; + } + + /* get hardware config command mail box */ + wl->cmd_box_addr = wl1271_read32(wl, REG_COMMAND_MAILBOX_PTR); + + /* get hardware config event mail box */ + wl->event_box_addr = wl1271_read32(wl, REG_EVENT_MAILBOX_PTR); + + /* set the working partition to its "running" mode offset */ + wl1271_set_partition(wl, &part_table[PART_WORK]); + + wl1271_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x event_box_addr 0x%x", + wl->cmd_box_addr, wl->event_box_addr); + + wl1271_boot_fw_version(wl); + + /* + * in case of full asynchronous mode the firmware event must be + * ready to receive event from the command mailbox + */ + + /* unmask required mbox events */ + wl->event_mask = BSS_LOSE_EVENT_ID | + SCAN_COMPLETE_EVENT_ID | + PS_REPORT_EVENT_ID | + JOIN_EVENT_COMPLETE_ID | + DISCONNECT_EVENT_COMPLETE_ID | + RSSI_SNR_TRIGGER_0_EVENT_ID | + PSPOLL_DELIVERY_FAILURE_EVENT_ID | + SOFT_GEMINI_SENSE_EVENT_ID | + PERIODIC_SCAN_REPORT_EVENT_ID | + PERIODIC_SCAN_COMPLETE_EVENT_ID; + + if (wl->bss_type == BSS_TYPE_AP_BSS) + wl->event_mask |= STA_REMOVE_COMPLETE_EVENT_ID; + else + wl->event_mask |= DUMMY_PACKET_EVENT_ID; + + ret = wl1271_event_unmask(wl); + if (ret < 0) { + wl1271_error("EVENT mask setting failed"); + return ret; + } + + wl1271_event_mbox_config(wl); + + /* firmware startup completed */ + return 0; +} + +static int wl1271_boot_write_irq_polarity(struct wl1271 *wl) +{ + u32 polarity; + + polarity = wl1271_top_reg_read(wl, OCP_REG_POLARITY); + + /* We use HIGH polarity, so unset the LOW bit */ + polarity &= ~POLARITY_LOW; + wl1271_top_reg_write(wl, OCP_REG_POLARITY, polarity); + + return 0; +} + +static void wl1271_boot_hw_version(struct wl1271 *wl) +{ + u32 fuse; + + fuse = wl1271_top_reg_read(wl, REG_FUSE_DATA_2_1); + fuse = (fuse & PG_VER_MASK) >> PG_VER_OFFSET; + + wl->hw_pg_ver = (s8)fuse; + + if (((wl->hw_pg_ver & PG_MAJOR_VER_MASK) >> PG_MAJOR_VER_OFFSET) < 3) + wl->quirks |= WL12XX_QUIRK_END_OF_TRANSACTION; +} + +static int wl128x_switch_tcxo_to_fref(struct wl1271 *wl) +{ + u16 spare_reg; + + /* Mask bits [2] & [8:4] in the sys_clk_cfg register */ + spare_reg = wl1271_top_reg_read(wl, WL_SPARE_REG); + if (spare_reg == 0xFFFF) + return -EFAULT; + spare_reg |= (BIT(3) | BIT(5) | BIT(6)); + wl1271_top_reg_write(wl, WL_SPARE_REG, spare_reg); + + /* Enable FREF_CLK_REQ & mux MCS and coex PLLs to FREF */ + wl1271_top_reg_write(wl, SYS_CLK_CFG_REG, + WL_CLK_REQ_TYPE_PG2 | MCS_PLL_CLK_SEL_FREF); + + /* Delay execution for 15msec, to let the HW settle */ + mdelay(15); + + return 0; +} + +static bool wl128x_is_tcxo_valid(struct wl1271 *wl) +{ + u16 tcxo_detection; + + tcxo_detection = wl1271_top_reg_read(wl, TCXO_CLK_DETECT_REG); + if (tcxo_detection & TCXO_DET_FAILED) + return false; + + return true; +} + +static bool wl128x_is_fref_valid(struct wl1271 *wl) +{ + u16 fref_detection; + + fref_detection = wl1271_top_reg_read(wl, FREF_CLK_DETECT_REG); + if (fref_detection & FREF_CLK_DETECT_FAIL) + return false; + + return true; +} + +static int wl128x_manually_configure_mcs_pll(struct wl1271 *wl) +{ + wl1271_top_reg_write(wl, MCS_PLL_M_REG, MCS_PLL_M_REG_VAL); + wl1271_top_reg_write(wl, MCS_PLL_N_REG, MCS_PLL_N_REG_VAL); + wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, MCS_PLL_CONFIG_REG_VAL); + + return 0; +} + +static int wl128x_configure_mcs_pll(struct wl1271 *wl, int clk) +{ + u16 spare_reg; + u16 pll_config; + u8 input_freq; + + /* Mask bits [3:1] in the sys_clk_cfg register */ + spare_reg = wl1271_top_reg_read(wl, WL_SPARE_REG); + if (spare_reg == 0xFFFF) + return -EFAULT; + spare_reg |= BIT(2); + wl1271_top_reg_write(wl, WL_SPARE_REG, spare_reg); + + /* Handle special cases of the TCXO clock */ + if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_8 || + wl->tcxo_clock == WL12XX_TCXOCLOCK_33_6) + return wl128x_manually_configure_mcs_pll(wl); + + /* Set the input frequency according to the selected clock source */ + input_freq = (clk & 1) + 1; + + pll_config = wl1271_top_reg_read(wl, MCS_PLL_CONFIG_REG); + if (pll_config == 0xFFFF) + return -EFAULT; + pll_config |= (input_freq << MCS_SEL_IN_FREQ_SHIFT); + pll_config |= MCS_PLL_ENABLE_HP; + wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, pll_config); + + return 0; +} + +/* + * WL128x has two clocks input - TCXO and FREF. + * TCXO is the main clock of the device, while FREF is used to sync + * between the GPS and the cellular modem. + * In cases where TCXO is 32.736MHz or 16.368MHz, the FREF will be used + * as the WLAN/BT main clock. + */ +static int wl128x_boot_clk(struct wl1271 *wl, int *selected_clock) +{ + u16 sys_clk_cfg; + + /* For XTAL-only modes, FREF will be used after switching from TCXO */ + if (wl->ref_clock == WL12XX_REFCLOCK_26_XTAL || + wl->ref_clock == WL12XX_REFCLOCK_38_XTAL) { + if (!wl128x_switch_tcxo_to_fref(wl)) + return -EINVAL; + goto fref_clk; + } + + /* Query the HW, to determine which clock source we should use */ + sys_clk_cfg = wl1271_top_reg_read(wl, SYS_CLK_CFG_REG); + if (sys_clk_cfg == 0xFFFF) + return -EINVAL; + if (sys_clk_cfg & PRCM_CM_EN_MUX_WLAN_FREF) + goto fref_clk; + + /* If TCXO is either 32.736MHz or 16.368MHz, switch to FREF */ + if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_368 || + wl->tcxo_clock == WL12XX_TCXOCLOCK_32_736) { + if (!wl128x_switch_tcxo_to_fref(wl)) + return -EINVAL; + goto fref_clk; + } + + /* TCXO clock is selected */ + if (!wl128x_is_tcxo_valid(wl)) + return -EINVAL; + *selected_clock = wl->tcxo_clock; + goto config_mcs_pll; + +fref_clk: + /* FREF clock is selected */ + if (!wl128x_is_fref_valid(wl)) + return -EINVAL; + *selected_clock = wl->ref_clock; + +config_mcs_pll: + return wl128x_configure_mcs_pll(wl, *selected_clock); +} + +static int wl127x_boot_clk(struct wl1271 *wl) +{ + u32 pause; + u32 clk; + + wl1271_boot_hw_version(wl); + + if (wl->ref_clock == CONF_REF_CLK_19_2_E || + wl->ref_clock == CONF_REF_CLK_38_4_E || + wl->ref_clock == CONF_REF_CLK_38_4_M_XTAL) + /* ref clk: 19.2/38.4/38.4-XTAL */ + clk = 0x3; + else if (wl->ref_clock == CONF_REF_CLK_26_E || + wl->ref_clock == CONF_REF_CLK_52_E) + /* ref clk: 26/52 */ + clk = 0x5; + else + return -EINVAL; + + if (wl->ref_clock != CONF_REF_CLK_19_2_E) { + u16 val; + /* Set clock type (open drain) */ + val = wl1271_top_reg_read(wl, OCP_REG_CLK_TYPE); + val &= FREF_CLK_TYPE_BITS; + wl1271_top_reg_write(wl, OCP_REG_CLK_TYPE, val); + + /* Set clock pull mode (no pull) */ + val = wl1271_top_reg_read(wl, OCP_REG_CLK_PULL); + val |= NO_PULL; + wl1271_top_reg_write(wl, OCP_REG_CLK_PULL, val); + } else { + u16 val; + /* Set clock polarity */ + val = wl1271_top_reg_read(wl, OCP_REG_CLK_POLARITY); + val &= FREF_CLK_POLARITY_BITS; + val |= CLK_REQ_OUTN_SEL; + wl1271_top_reg_write(wl, OCP_REG_CLK_POLARITY, val); + } + + wl1271_write32(wl, PLL_PARAMETERS, clk); + + pause = wl1271_read32(wl, PLL_PARAMETERS); + + wl1271_debug(DEBUG_BOOT, "pause1 0x%x", pause); + + pause &= ~(WU_COUNTER_PAUSE_VAL); + pause |= WU_COUNTER_PAUSE_VAL; + wl1271_write32(wl, WU_COUNTER_PAUSE, pause); + + return 0; +} + +/* uploads NVS and firmware */ +int wl1271_load_firmware(struct wl1271 *wl) +{ + int ret = 0; + u32 tmp, clk; + int selected_clock = -1; + + if (wl->chip.id == CHIP_ID_1283_PG20) { + ret = wl128x_boot_clk(wl, &selected_clock); + if (ret < 0) + goto out; + } else { + ret = wl127x_boot_clk(wl); + if (ret < 0) + goto out; + } + + /* Continue the ELP wake up sequence */ + wl1271_write32(wl, WELP_ARM_COMMAND, WELP_ARM_COMMAND_VAL); + udelay(500); + + wl1271_set_partition(wl, &part_table[PART_DRPW]); + + /* Read-modify-write DRPW_SCRATCH_START register (see next state) + to be used by DRPw FW. The RTRIM value will be added by the FW + before taking DRPw out of reset */ + + wl1271_debug(DEBUG_BOOT, "DRPW_SCRATCH_START %08x", DRPW_SCRATCH_START); + clk = wl1271_read32(wl, DRPW_SCRATCH_START); + + wl1271_debug(DEBUG_BOOT, "clk2 0x%x", clk); + + if (wl->chip.id == CHIP_ID_1283_PG20) { + clk |= ((selected_clock & 0x3) << 1) << 4; + } else { + clk |= (wl->ref_clock << 1) << 4; + } + + wl1271_write32(wl, DRPW_SCRATCH_START, clk); + + wl1271_set_partition(wl, &part_table[PART_WORK]); + + /* Disable interrupts */ + wl1271_write32(wl, ACX_REG_INTERRUPT_MASK, WL1271_ACX_INTR_ALL); + + ret = wl1271_boot_soft_reset(wl); + if (ret < 0) + goto out; + + /* 2. start processing NVS file */ + ret = wl1271_boot_upload_nvs(wl); + if (ret < 0) + goto out; + + /* write firmware's last address (ie. it's length) to + * ACX_EEPROMLESS_IND_REG */ + wl1271_debug(DEBUG_BOOT, "ACX_EEPROMLESS_IND_REG"); + + wl1271_write32(wl, ACX_EEPROMLESS_IND_REG, ACX_EEPROMLESS_IND_REG); + + tmp = wl1271_read32(wl, CHIP_ID_B); + + wl1271_debug(DEBUG_BOOT, "chip id 0x%x", tmp); + + /* 6. read the EEPROM parameters */ + tmp = wl1271_read32(wl, SCR_PAD2); + + /* WL1271: The reference driver skips steps 7 to 10 (jumps directly + * to upload_fw) */ + + if (wl->chip.id == CHIP_ID_1283_PG20) + wl1271_top_reg_write(wl, SDIO_IO_DS, wl->conf.hci_io_ds); + + ret = wl1271_boot_upload_firmware(wl); + if (ret < 0) + goto out; + +out: + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_load_firmware); + +int wl1271_boot(struct wl1271 *wl) +{ + int ret; + + /* upload NVS and firmware */ + ret = wl1271_load_firmware(wl); + if (ret) + return ret; + + /* 10.5 start firmware */ + ret = wl1271_boot_run_firmware(wl); + if (ret < 0) + goto out; + + ret = wl1271_boot_write_irq_polarity(wl); + if (ret < 0) + goto out; + + wl1271_write32(wl, ACX_REG_INTERRUPT_MASK, + WL1271_ACX_ALL_EVENTS_VECTOR); + + /* Enable firmware interrupts now */ + wl1271_boot_enable_interrupts(wl); + + /* set the wl1271 default filters */ + wl1271_set_default_filters(wl); + + wl1271_event_mbox_config(wl); + +out: + return ret; +} diff --git a/drivers/net/wireless/wl12xx/boot.h b/drivers/net/wireless/wl12xx/boot.h new file mode 100644 index 00000000..e8f8255b --- /dev/null +++ b/drivers/net/wireless/wl12xx/boot.h @@ -0,0 +1,129 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __BOOT_H__ +#define __BOOT_H__ + +#include "wl12xx.h" + +int wl1271_boot(struct wl1271 *wl); +int wl1271_load_firmware(struct wl1271 *wl); + +#define WL1271_NO_SUBBANDS 8 +#define WL1271_NO_POWER_LEVELS 4 +#define WL1271_FW_VERSION_MAX_LEN 20 + +struct wl1271_static_data { + u8 mac_address[ETH_ALEN]; + u8 padding[2]; + u8 fw_version[WL1271_FW_VERSION_MAX_LEN]; + u32 hw_version; + u8 tx_power_table[WL1271_NO_SUBBANDS][WL1271_NO_POWER_LEVELS]; +}; + +/* number of times we try to read the INIT interrupt */ +#define INIT_LOOP 20000 + +/* delay between retries */ +#define INIT_LOOP_DELAY 50 + +#define WU_COUNTER_PAUSE_VAL 0x3FF +#define WELP_ARM_COMMAND_VAL 0x4 + +#define OCP_REG_POLARITY 0x0064 +#define OCP_REG_CLK_TYPE 0x0448 +#define OCP_REG_CLK_POLARITY 0x0cb2 +#define OCP_REG_CLK_PULL 0x0cb4 + +#define REG_FUSE_DATA_2_1 0x050a +#define PG_VER_MASK 0x3c +#define PG_VER_OFFSET 2 + +#define PG_MAJOR_VER_MASK 0x3 +#define PG_MAJOR_VER_OFFSET 0x0 +#define PG_MINOR_VER_MASK 0xc +#define PG_MINOR_VER_OFFSET 0x2 + +#define CMD_MBOX_ADDRESS 0x407B4 + +#define POLARITY_LOW BIT(1) +#define NO_PULL (BIT(14) | BIT(15)) + +#define FREF_CLK_TYPE_BITS 0xfffffe7f +#define CLK_REQ_PRCM 0x100 +#define FREF_CLK_POLARITY_BITS 0xfffff8ff +#define CLK_REQ_OUTN_SEL 0x700 + +/* PLL configuration algorithm for wl128x */ +#define SYS_CLK_CFG_REG 0x2200 +/* Bit[0] - 0-TCXO, 1-FREF */ +#define MCS_PLL_CLK_SEL_FREF BIT(0) +/* Bit[3:2] - 01-TCXO, 10-FREF */ +#define WL_CLK_REQ_TYPE_FREF BIT(3) +#define WL_CLK_REQ_TYPE_PG2 (BIT(3) | BIT(2)) +/* Bit[4] - 0-TCXO, 1-FREF */ +#define PRCM_CM_EN_MUX_WLAN_FREF BIT(4) + +#define TCXO_ILOAD_INT_REG 0x2264 +#define TCXO_CLK_DETECT_REG 0x2266 + +#define TCXO_DET_FAILED BIT(4) + +#define FREF_ILOAD_INT_REG 0x2084 +#define FREF_CLK_DETECT_REG 0x2086 +#define FREF_CLK_DETECT_FAIL BIT(4) + +/* Use this reg for masking during driver access */ +#define WL_SPARE_REG 0x2320 +#define WL_SPARE_VAL BIT(2) +/* Bit[6:5:3] - mask wl write SYS_CLK_CFG[8:5:2:4] */ +#define WL_SPARE_MASK_8526 (BIT(6) | BIT(5) | BIT(3)) + +#define PLL_LOCK_COUNTERS_REG 0xD8C +#define PLL_LOCK_COUNTERS_COEX 0x0F +#define PLL_LOCK_COUNTERS_MCS 0xF0 +#define MCS_PLL_OVERRIDE_REG 0xD90 +#define MCS_PLL_CONFIG_REG 0xD92 +#define MCS_SEL_IN_FREQ_MASK 0x0070 +#define MCS_SEL_IN_FREQ_SHIFT 4 +#define MCS_PLL_CONFIG_REG_VAL 0x73 +#define MCS_PLL_ENABLE_HP (BIT(0) | BIT(1)) + +#define MCS_PLL_M_REG 0xD94 +#define MCS_PLL_N_REG 0xD96 +#define MCS_PLL_M_REG_VAL 0xC8 +#define MCS_PLL_N_REG_VAL 0x07 + +#define SDIO_IO_DS 0xd14 + +/* SDIO/wSPI DS configuration values */ +enum { + HCI_IO_DS_8MA = 0, + HCI_IO_DS_4MA = 1, /* default */ + HCI_IO_DS_6MA = 2, + HCI_IO_DS_2MA = 3, +}; + +/* end PLL configuration algorithm for wl128x */ + +#endif diff --git a/drivers/net/wireless/wl12xx/cmd.c b/drivers/net/wireless/wl12xx/cmd.c new file mode 100644 index 00000000..b8ec8cd6 --- /dev/null +++ b/drivers/net/wireless/wl12xx/cmd.c @@ -0,0 +1,1254 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/crc7.h> +#include <linux/spi/spi.h> +#include <linux/etherdevice.h> +#include <linux/ieee80211.h> +#include <linux/slab.h> + +#include "wl12xx.h" +#include "reg.h" +#include "io.h" +#include "acx.h" +#include "wl12xx_80211.h" +#include "cmd.h" +#include "event.h" +#include "tx.h" + +#define WL1271_CMD_FAST_POLL_COUNT 50 + +/* + * send command to firmware + * + * @wl: wl struct + * @id: command id + * @buf: buffer containing the command, must work with dma + * @len: length of the buffer + */ +int wl1271_cmd_send(struct wl1271 *wl, u16 id, void *buf, size_t len, + size_t res_len) +{ + struct wl1271_cmd_header *cmd; + unsigned long timeout; + u32 intr; + int ret = 0; + u16 status; + u16 poll_count = 0; + + cmd = buf; + cmd->id = cpu_to_le16(id); + cmd->status = 0; + + WARN_ON(len % 4 != 0); + WARN_ON(test_bit(WL1271_FLAG_IN_ELP, &wl->flags)); + + wl1271_write(wl, wl->cmd_box_addr, buf, len, false); + + wl1271_write32(wl, ACX_REG_INTERRUPT_TRIG, INTR_TRIG_CMD); + + timeout = jiffies + msecs_to_jiffies(WL1271_COMMAND_TIMEOUT); + + intr = wl1271_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); + while (!(intr & WL1271_ACX_INTR_CMD_COMPLETE)) { + if (time_after(jiffies, timeout)) { + wl1271_error("command complete timeout"); + ret = -ETIMEDOUT; + goto fail; + } + + poll_count++; + if (poll_count < WL1271_CMD_FAST_POLL_COUNT) + udelay(10); + else + msleep(1); + + intr = wl1271_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); + } + + /* read back the status code of the command */ + if (res_len == 0) + res_len = sizeof(struct wl1271_cmd_header); + wl1271_read(wl, wl->cmd_box_addr, cmd, res_len, false); + + status = le16_to_cpu(cmd->status); + if (status != CMD_STATUS_SUCCESS) { + wl1271_error("command execute failure %d", status); + ret = -EIO; + goto fail; + } + + wl1271_write32(wl, ACX_REG_INTERRUPT_ACK, + WL1271_ACX_INTR_CMD_COMPLETE); + return 0; + +fail: + WARN_ON(1); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + return ret; +} + +int wl1271_cmd_general_parms(struct wl1271 *wl) +{ + struct wl1271_general_parms_cmd *gen_parms; + struct wl1271_ini_general_params *gp = + &((struct wl1271_nvs_file *)wl->nvs)->general_params; + bool answer = false; + int ret; + + if (!wl->nvs) + return -ENODEV; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from INI out of bounds"); + return -EINVAL; + } + + gen_parms = kzalloc(sizeof(*gen_parms), GFP_KERNEL); + if (!gen_parms) + return -ENOMEM; + + gen_parms->test.id = TEST_CMD_INI_FILE_GENERAL_PARAM; + + memcpy(&gen_parms->general_params, gp, sizeof(*gp)); + + if (gp->tx_bip_fem_auto_detect) + answer = true; + + /* Override the REF CLK from the NVS with the one from platform data */ + gen_parms->general_params.ref_clock = wl->ref_clock; + + ret = wl1271_cmd_test(wl, gen_parms, sizeof(*gen_parms), answer); + if (ret < 0) { + wl1271_warning("CMD_INI_FILE_GENERAL_PARAM failed"); + goto out; + } + + gp->tx_bip_fem_manufacturer = + gen_parms->general_params.tx_bip_fem_manufacturer; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from FW out of bounds"); + ret = -EINVAL; + goto out; + } + + wl1271_debug(DEBUG_CMD, "FEM autodetect: %s, manufacturer: %d\n", + answer ? "auto" : "manual", gp->tx_bip_fem_manufacturer); + +out: + kfree(gen_parms); + return ret; +} + +int wl128x_cmd_general_parms(struct wl1271 *wl) +{ + struct wl128x_general_parms_cmd *gen_parms; + struct wl128x_ini_general_params *gp = + &((struct wl128x_nvs_file *)wl->nvs)->general_params; + bool answer = false; + int ret; + + if (!wl->nvs) + return -ENODEV; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from ini out of bounds"); + return -EINVAL; + } + + gen_parms = kzalloc(sizeof(*gen_parms), GFP_KERNEL); + if (!gen_parms) + return -ENOMEM; + + gen_parms->test.id = TEST_CMD_INI_FILE_GENERAL_PARAM; + + memcpy(&gen_parms->general_params, gp, sizeof(*gp)); + + if (gp->tx_bip_fem_auto_detect) + answer = true; + + /* Replace REF and TCXO CLKs with the ones from platform data */ + gen_parms->general_params.ref_clock = wl->ref_clock; + gen_parms->general_params.tcxo_ref_clock = wl->tcxo_clock; + + ret = wl1271_cmd_test(wl, gen_parms, sizeof(*gen_parms), answer); + if (ret < 0) { + wl1271_warning("CMD_INI_FILE_GENERAL_PARAM failed"); + goto out; + } + + gp->tx_bip_fem_manufacturer = + gen_parms->general_params.tx_bip_fem_manufacturer; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from FW out of bounds"); + ret = -EINVAL; + goto out; + } + + wl1271_debug(DEBUG_CMD, "FEM autodetect: %s, manufacturer: %d\n", + answer ? "auto" : "manual", gp->tx_bip_fem_manufacturer); + +out: + kfree(gen_parms); + return ret; +} + +int wl1271_cmd_radio_parms(struct wl1271 *wl) +{ + struct wl1271_nvs_file *nvs = (struct wl1271_nvs_file *)wl->nvs; + struct wl1271_radio_parms_cmd *radio_parms; + struct wl1271_ini_general_params *gp = &nvs->general_params; + int ret; + + if (!wl->nvs) + return -ENODEV; + + radio_parms = kzalloc(sizeof(*radio_parms), GFP_KERNEL); + if (!radio_parms) + return -ENOMEM; + + radio_parms->test.id = TEST_CMD_INI_FILE_RADIO_PARAM; + + /* 2.4GHz parameters */ + memcpy(&radio_parms->static_params_2, &nvs->stat_radio_params_2, + sizeof(struct wl1271_ini_band_params_2)); + memcpy(&radio_parms->dyn_params_2, + &nvs->dyn_radio_params_2[gp->tx_bip_fem_manufacturer].params, + sizeof(struct wl1271_ini_fem_params_2)); + + /* 5GHz parameters */ + memcpy(&radio_parms->static_params_5, + &nvs->stat_radio_params_5, + sizeof(struct wl1271_ini_band_params_5)); + memcpy(&radio_parms->dyn_params_5, + &nvs->dyn_radio_params_5[gp->tx_bip_fem_manufacturer].params, + sizeof(struct wl1271_ini_fem_params_5)); + + wl1271_dump(DEBUG_CMD, "TEST_CMD_INI_FILE_RADIO_PARAM: ", + radio_parms, sizeof(*radio_parms)); + + ret = wl1271_cmd_test(wl, radio_parms, sizeof(*radio_parms), 0); + if (ret < 0) + wl1271_warning("CMD_INI_FILE_RADIO_PARAM failed"); + + kfree(radio_parms); + return ret; +} + +int wl128x_cmd_radio_parms(struct wl1271 *wl) +{ + struct wl128x_nvs_file *nvs = (struct wl128x_nvs_file *)wl->nvs; + struct wl128x_radio_parms_cmd *radio_parms; + struct wl128x_ini_general_params *gp = &nvs->general_params; + int ret; + + if (!wl->nvs) + return -ENODEV; + + radio_parms = kzalloc(sizeof(*radio_parms), GFP_KERNEL); + if (!radio_parms) + return -ENOMEM; + + radio_parms->test.id = TEST_CMD_INI_FILE_RADIO_PARAM; + + /* 2.4GHz parameters */ + memcpy(&radio_parms->static_params_2, &nvs->stat_radio_params_2, + sizeof(struct wl128x_ini_band_params_2)); + memcpy(&radio_parms->dyn_params_2, + &nvs->dyn_radio_params_2[gp->tx_bip_fem_manufacturer].params, + sizeof(struct wl128x_ini_fem_params_2)); + + /* 5GHz parameters */ + memcpy(&radio_parms->static_params_5, + &nvs->stat_radio_params_5, + sizeof(struct wl128x_ini_band_params_5)); + memcpy(&radio_parms->dyn_params_5, + &nvs->dyn_radio_params_5[gp->tx_bip_fem_manufacturer].params, + sizeof(struct wl128x_ini_fem_params_5)); + + radio_parms->fem_vendor_and_options = nvs->fem_vendor_and_options; + + wl1271_dump(DEBUG_CMD, "TEST_CMD_INI_FILE_RADIO_PARAM: ", + radio_parms, sizeof(*radio_parms)); + + ret = wl1271_cmd_test(wl, radio_parms, sizeof(*radio_parms), 0); + if (ret < 0) + wl1271_warning("CMD_INI_FILE_RADIO_PARAM failed"); + + kfree(radio_parms); + return ret; +} + +int wl1271_cmd_ext_radio_parms(struct wl1271 *wl) +{ + struct wl1271_ext_radio_parms_cmd *ext_radio_parms; + struct conf_rf_settings *rf = &wl->conf.rf; + int ret; + + if (!wl->nvs) + return -ENODEV; + + ext_radio_parms = kzalloc(sizeof(*ext_radio_parms), GFP_KERNEL); + if (!ext_radio_parms) + return -ENOMEM; + + ext_radio_parms->test.id = TEST_CMD_INI_FILE_RF_EXTENDED_PARAM; + + memcpy(ext_radio_parms->tx_per_channel_power_compensation_2, + rf->tx_per_channel_power_compensation_2, + CONF_TX_PWR_COMPENSATION_LEN_2); + memcpy(ext_radio_parms->tx_per_channel_power_compensation_5, + rf->tx_per_channel_power_compensation_5, + CONF_TX_PWR_COMPENSATION_LEN_5); + + wl1271_dump(DEBUG_CMD, "TEST_CMD_INI_FILE_EXT_RADIO_PARAM: ", + ext_radio_parms, sizeof(*ext_radio_parms)); + + ret = wl1271_cmd_test(wl, ext_radio_parms, sizeof(*ext_radio_parms), 0); + if (ret < 0) + wl1271_warning("TEST_CMD_INI_FILE_RF_EXTENDED_PARAM failed"); + + kfree(ext_radio_parms); + return ret; +} + +/* + * Poll the mailbox event field until any of the bits in the mask is set or a + * timeout occurs (WL1271_EVENT_TIMEOUT in msecs) + */ +static int wl1271_cmd_wait_for_event_or_timeout(struct wl1271 *wl, u32 mask) +{ + u32 events_vector, event; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(WL1271_EVENT_TIMEOUT); + + do { + if (time_after(jiffies, timeout)) { + wl1271_debug(DEBUG_CMD, "timeout waiting for event %d", + (int)mask); + return -ETIMEDOUT; + } + + msleep(1); + + /* read from both event fields */ + wl1271_read(wl, wl->mbox_ptr[0], &events_vector, + sizeof(events_vector), false); + event = events_vector & mask; + wl1271_read(wl, wl->mbox_ptr[1], &events_vector, + sizeof(events_vector), false); + event |= events_vector & mask; + } while (!event); + + return 0; +} + +static int wl1271_cmd_wait_for_event(struct wl1271 *wl, u32 mask) +{ + int ret; + + ret = wl1271_cmd_wait_for_event_or_timeout(wl, mask); + if (ret != 0) { + ieee80211_queue_work(wl->hw, &wl->recovery_work); + return ret; + } + + return 0; +} + +int wl1271_cmd_join(struct wl1271 *wl, u8 bss_type) +{ + struct wl1271_cmd_join *join; + int ret, i; + u8 *bssid; + + join = kzalloc(sizeof(*join), GFP_KERNEL); + if (!join) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd join"); + + /* Reverse order BSSID */ + bssid = (u8 *) &join->bssid_lsb; + for (i = 0; i < ETH_ALEN; i++) + bssid[i] = wl->bssid[ETH_ALEN - i - 1]; + + join->rx_config_options = cpu_to_le32(wl->rx_config); + join->rx_filter_options = cpu_to_le32(wl->rx_filter); + join->bss_type = bss_type; + join->basic_rate_set = cpu_to_le32(wl->basic_rate_set); + join->supported_rate_set = cpu_to_le32(wl->rate_set); + + if (wl->band == IEEE80211_BAND_5GHZ) + join->bss_type |= WL1271_JOIN_CMD_BSS_TYPE_5GHZ; + + join->beacon_interval = cpu_to_le16(wl->beacon_int); + join->dtim_interval = WL1271_DEFAULT_DTIM_PERIOD; + + join->channel = wl->channel; + join->ssid_len = wl->ssid_len; + memcpy(join->ssid, wl->ssid, wl->ssid_len); + + join->ctrl |= wl->session_counter << WL1271_JOIN_CMD_TX_SESSION_OFFSET; + + /* reset TX security counters */ + wl->tx_security_last_seq = 0; + wl->tx_security_seq = 0; + + wl1271_debug(DEBUG_CMD, "cmd join: basic_rate_set=0x%x, rate_set=0x%x", + join->basic_rate_set, join->supported_rate_set); + + ret = wl1271_cmd_send(wl, CMD_START_JOIN, join, sizeof(*join), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd join"); + goto out_free; + } + + ret = wl1271_cmd_wait_for_event(wl, JOIN_EVENT_COMPLETE_ID); + if (ret < 0) + wl1271_error("cmd join event completion error"); + +out_free: + kfree(join); + +out: + return ret; +} + +/** + * send test command to firmware + * + * @wl: wl struct + * @buf: buffer containing the command, with all headers, must work with dma + * @len: length of the buffer + * @answer: is answer needed + */ +int wl1271_cmd_test(struct wl1271 *wl, void *buf, size_t buf_len, u8 answer) +{ + int ret; + size_t res_len = 0; + + wl1271_debug(DEBUG_CMD, "cmd test"); + + if (answer) + res_len = buf_len; + + ret = wl1271_cmd_send(wl, CMD_TEST, buf, buf_len, res_len); + + if (ret < 0) { + wl1271_warning("TEST command failed"); + return ret; + } + + return ret; +} + +/** + * read acx from firmware + * + * @wl: wl struct + * @id: acx id + * @buf: buffer for the response, including all headers, must work with dma + * @len: length of buf + */ +int wl1271_cmd_interrogate(struct wl1271 *wl, u16 id, void *buf, size_t len) +{ + struct acx_header *acx = buf; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd interrogate"); + + acx->id = cpu_to_le16(id); + + /* payload length, does not include any headers */ + acx->len = cpu_to_le16(len - sizeof(*acx)); + + ret = wl1271_cmd_send(wl, CMD_INTERROGATE, acx, sizeof(*acx), len); + if (ret < 0) + wl1271_error("INTERROGATE command failed"); + + return ret; +} + +/** + * write acx value to firmware + * + * @wl: wl struct + * @id: acx id + * @buf: buffer containing acx, including all headers, must work with dma + * @len: length of buf + */ +int wl1271_cmd_configure(struct wl1271 *wl, u16 id, void *buf, size_t len) +{ + struct acx_header *acx = buf; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd configure"); + + acx->id = cpu_to_le16(id); + + /* payload length, does not include any headers */ + acx->len = cpu_to_le16(len - sizeof(*acx)); + + ret = wl1271_cmd_send(wl, CMD_CONFIGURE, acx, len, 0); + if (ret < 0) { + wl1271_warning("CONFIGURE command NOK"); + return ret; + } + + return 0; +} + +int wl1271_cmd_data_path(struct wl1271 *wl, bool enable) +{ + struct cmd_enabledisable_path *cmd; + int ret; + u16 cmd_rx, cmd_tx; + + wl1271_debug(DEBUG_CMD, "cmd data path"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + /* the channel here is only used for calibration, so hardcoded to 1 */ + cmd->channel = 1; + + if (enable) { + cmd_rx = CMD_ENABLE_RX; + cmd_tx = CMD_ENABLE_TX; + } else { + cmd_rx = CMD_DISABLE_RX; + cmd_tx = CMD_DISABLE_TX; + } + + ret = wl1271_cmd_send(wl, cmd_rx, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("rx %s cmd for channel %d failed", + enable ? "start" : "stop", cmd->channel); + goto out; + } + + wl1271_debug(DEBUG_BOOT, "rx %s cmd channel %d", + enable ? "start" : "stop", cmd->channel); + + ret = wl1271_cmd_send(wl, cmd_tx, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("tx %s cmd for channel %d failed", + enable ? "start" : "stop", cmd->channel); + goto out; + } + + wl1271_debug(DEBUG_BOOT, "tx %s cmd channel %d", + enable ? "start" : "stop", cmd->channel); + +out: + kfree(cmd); + return ret; +} + +int wl1271_cmd_ps_mode(struct wl1271 *wl, u8 ps_mode) +{ + struct wl1271_cmd_ps_params *ps_params = NULL; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set ps mode"); + + ps_params = kzalloc(sizeof(*ps_params), GFP_KERNEL); + if (!ps_params) { + ret = -ENOMEM; + goto out; + } + + ps_params->ps_mode = ps_mode; + + ret = wl1271_cmd_send(wl, CMD_SET_PS_MODE, ps_params, + sizeof(*ps_params), 0); + if (ret < 0) { + wl1271_error("cmd set_ps_mode failed"); + goto out; + } + +out: + kfree(ps_params); + return ret; +} + +int wl1271_cmd_template_set(struct wl1271 *wl, u16 template_id, + void *buf, size_t buf_len, int index, u32 rates) +{ + struct wl1271_cmd_template_set *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd template_set %d", template_id); + + WARN_ON(buf_len > WL1271_CMD_TEMPL_MAX_SIZE); + buf_len = min_t(size_t, buf_len, WL1271_CMD_TEMPL_MAX_SIZE); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->len = cpu_to_le16(buf_len); + cmd->template_type = template_id; + cmd->enabled_rates = cpu_to_le32(rates); + cmd->short_retry_limit = wl->conf.tx.tmpl_short_retry_limit; + cmd->long_retry_limit = wl->conf.tx.tmpl_long_retry_limit; + cmd->index = index; + + if (buf) + memcpy(cmd->template_data, buf, buf_len); + + ret = wl1271_cmd_send(wl, CMD_SET_TEMPLATE, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("cmd set_template failed: %d", ret); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl1271_cmd_build_null_data(struct wl1271 *wl) +{ + struct sk_buff *skb = NULL; + int size; + void *ptr; + int ret = -ENOMEM; + + + if (wl->bss_type == BSS_TYPE_IBSS) { + size = sizeof(struct wl12xx_null_data_template); + ptr = NULL; + } else { + skb = ieee80211_nullfunc_get(wl->hw, wl->vif); + if (!skb) + goto out; + size = skb->len; + ptr = skb->data; + } + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_NULL_DATA, ptr, size, 0, + wl->basic_rate); + +out: + dev_kfree_skb(skb); + if (ret) + wl1271_warning("cmd buld null data failed %d", ret); + + return ret; + +} + +int wl1271_cmd_build_klv_null_data(struct wl1271 *wl) +{ + struct sk_buff *skb = NULL; + int ret = -ENOMEM; + + skb = ieee80211_nullfunc_get(wl->hw, wl->vif); + if (!skb) + goto out; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_KLV, + skb->data, skb->len, + CMD_TEMPL_KLV_IDX_NULL_DATA, + wl->basic_rate); + +out: + dev_kfree_skb(skb); + if (ret) + wl1271_warning("cmd build klv null data failed %d", ret); + + return ret; + +} + +int wl1271_cmd_build_ps_poll(struct wl1271 *wl, u16 aid) +{ + struct sk_buff *skb; + int ret = 0; + + skb = ieee80211_pspoll_get(wl->hw, wl->vif); + if (!skb) + goto out; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_PS_POLL, skb->data, + skb->len, 0, wl->basic_rate_set); + +out: + dev_kfree_skb(skb); + return ret; +} + +int wl1271_cmd_build_probe_req(struct wl1271 *wl, + const u8 *ssid, size_t ssid_len, + const u8 *ie, size_t ie_len, u8 band) +{ + struct sk_buff *skb; + int ret; + + skb = ieee80211_probereq_get(wl->hw, wl->vif, ssid, ssid_len, + ie, ie_len); + if (!skb) { + ret = -ENOMEM; + goto out; + } + + wl1271_dump(DEBUG_SCAN, "PROBE REQ: ", skb->data, skb->len); + + if (band == IEEE80211_BAND_2GHZ) + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_CFG_PROBE_REQ_2_4, + skb->data, skb->len, 0, + wl->conf.tx.basic_rate); + else + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_CFG_PROBE_REQ_5, + skb->data, skb->len, 0, + wl->conf.tx.basic_rate_5); + +out: + dev_kfree_skb(skb); + return ret; +} + +struct sk_buff *wl1271_cmd_build_ap_probe_req(struct wl1271 *wl, + struct sk_buff *skb) +{ + int ret; + + if (!skb) + skb = ieee80211_ap_probereq_get(wl->hw, wl->vif); + if (!skb) + goto out; + + wl1271_dump(DEBUG_SCAN, "AP PROBE REQ: ", skb->data, skb->len); + + if (wl->band == IEEE80211_BAND_2GHZ) + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_CFG_PROBE_REQ_2_4, + skb->data, skb->len, 0, + wl->conf.tx.basic_rate); + else + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_CFG_PROBE_REQ_5, + skb->data, skb->len, 0, + wl->conf.tx.basic_rate_5); + + if (ret < 0) + wl1271_error("Unable to set ap probe request template."); + +out: + return skb; +} + +int wl1271_cmd_build_arp_rsp(struct wl1271 *wl, __be32 ip_addr) +{ + int ret; + struct wl12xx_arp_rsp_template tmpl; + struct ieee80211_hdr_3addr *hdr; + struct arphdr *arp_hdr; + + memset(&tmpl, 0, sizeof(tmpl)); + + /* mac80211 header */ + hdr = &tmpl.hdr; + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_DATA | + IEEE80211_FCTL_TODS); + memcpy(hdr->addr1, wl->vif->bss_conf.bssid, ETH_ALEN); + memcpy(hdr->addr2, wl->vif->addr, ETH_ALEN); + memset(hdr->addr3, 0xff, ETH_ALEN); + + /* llc layer */ + memcpy(tmpl.llc_hdr, rfc1042_header, sizeof(rfc1042_header)); + tmpl.llc_type = cpu_to_be16(ETH_P_ARP); + + /* arp header */ + arp_hdr = &tmpl.arp_hdr; + arp_hdr->ar_hrd = cpu_to_be16(ARPHRD_ETHER); + arp_hdr->ar_pro = cpu_to_be16(ETH_P_IP); + arp_hdr->ar_hln = ETH_ALEN; + arp_hdr->ar_pln = 4; + arp_hdr->ar_op = cpu_to_be16(ARPOP_REPLY); + + /* arp payload */ + memcpy(tmpl.sender_hw, wl->vif->addr, ETH_ALEN); + tmpl.sender_ip = ip_addr; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_ARP_RSP, + &tmpl, sizeof(tmpl), 0, + wl->basic_rate); + + return ret; +} + +int wl1271_build_qos_null_data(struct wl1271 *wl) +{ + struct ieee80211_qos_hdr template; + + memset(&template, 0, sizeof(template)); + + memcpy(template.addr1, wl->bssid, ETH_ALEN); + memcpy(template.addr2, wl->mac_addr, ETH_ALEN); + memcpy(template.addr3, wl->bssid, ETH_ALEN); + + template.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_NULLFUNC | + IEEE80211_FCTL_TODS); + + /* FIXME: not sure what priority to use here */ + template.qos_ctrl = cpu_to_le16(0); + + return wl1271_cmd_template_set(wl, CMD_TEMPL_QOS_NULL_DATA, &template, + sizeof(template), 0, + wl->basic_rate); +} + +int wl1271_cmd_set_sta_default_wep_key(struct wl1271 *wl, u8 id) +{ + struct wl1271_cmd_set_sta_keys *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set_default_wep_key %d", id); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->id = id; + cmd->key_action = cpu_to_le16(KEY_SET_ID); + cmd->key_type = KEY_WEP; + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("cmd set_default_wep_key failed: %d", ret); + goto out; + } + +out: + kfree(cmd); + + return ret; +} + +int wl1271_cmd_set_ap_default_wep_key(struct wl1271 *wl, u8 id) +{ + struct wl1271_cmd_set_ap_keys *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set_ap_default_wep_key %d", id); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->hlid = WL1271_AP_BROADCAST_HLID; + cmd->key_id = id; + cmd->lid_key_type = WEP_DEFAULT_LID_TYPE; + cmd->key_action = cpu_to_le16(KEY_SET_ID); + cmd->key_type = KEY_WEP; + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("cmd set_ap_default_wep_key failed: %d", ret); + goto out; + } + +out: + kfree(cmd); + + return ret; +} + +int wl1271_cmd_set_sta_key(struct wl1271 *wl, u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, const u8 *addr, + u32 tx_seq_32, u16 tx_seq_16) +{ + struct wl1271_cmd_set_sta_keys *cmd; + int ret = 0; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + if (key_type != KEY_WEP) + memcpy(cmd->addr, addr, ETH_ALEN); + + cmd->key_action = cpu_to_le16(action); + cmd->key_size = key_size; + cmd->key_type = key_type; + + cmd->ac_seq_num16[0] = cpu_to_le16(tx_seq_16); + cmd->ac_seq_num32[0] = cpu_to_le32(tx_seq_32); + + /* we have only one SSID profile */ + cmd->ssid_profile = 0; + + cmd->id = id; + + if (key_type == KEY_TKIP) { + /* + * We get the key in the following form: + * TKIP (16 bytes) - TX MIC (8 bytes) - RX MIC (8 bytes) + * but the target is expecting: + * TKIP - RX MIC - TX MIC + */ + memcpy(cmd->key, key, 16); + memcpy(cmd->key + 16, key + 24, 8); + memcpy(cmd->key + 24, key + 16, 8); + + } else { + memcpy(cmd->key, key, key_size); + } + + wl1271_dump(DEBUG_CRYPT, "TARGET KEY: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("could not set keys"); + goto out; + } + +out: + kfree(cmd); + + return ret; +} + +int wl1271_cmd_set_ap_key(struct wl1271 *wl, u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, u8 hlid, u32 tx_seq_32, + u16 tx_seq_16) +{ + struct wl1271_cmd_set_ap_keys *cmd; + int ret = 0; + u8 lid_type; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + if (hlid == WL1271_AP_BROADCAST_HLID) { + if (key_type == KEY_WEP) + lid_type = WEP_DEFAULT_LID_TYPE; + else + lid_type = BROADCAST_LID_TYPE; + } else { + lid_type = UNICAST_LID_TYPE; + } + + wl1271_debug(DEBUG_CRYPT, "ap key action: %d id: %d lid: %d type: %d" + " hlid: %d", (int)action, (int)id, (int)lid_type, + (int)key_type, (int)hlid); + + cmd->lid_key_type = lid_type; + cmd->hlid = hlid; + cmd->key_action = cpu_to_le16(action); + cmd->key_size = key_size; + cmd->key_type = key_type; + cmd->key_id = id; + cmd->ac_seq_num16[0] = cpu_to_le16(tx_seq_16); + cmd->ac_seq_num32[0] = cpu_to_le32(tx_seq_32); + + if (key_type == KEY_TKIP) { + /* + * We get the key in the following form: + * TKIP (16 bytes) - TX MIC (8 bytes) - RX MIC (8 bytes) + * but the target is expecting: + * TKIP - RX MIC - TX MIC + */ + memcpy(cmd->key, key, 16); + memcpy(cmd->key + 16, key + 24, 8); + memcpy(cmd->key + 24, key + 16, 8); + } else { + memcpy(cmd->key, key, key_size); + } + + wl1271_dump(DEBUG_CRYPT, "TARGET AP KEY: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("could not set ap keys"); + goto out; + } + +out: + kfree(cmd); + return ret; +} + +int wl1271_cmd_disconnect(struct wl1271 *wl) +{ + struct wl1271_cmd_disconnect *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd disconnect"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->rx_config_options = cpu_to_le32(wl->rx_config); + cmd->rx_filter_options = cpu_to_le32(wl->rx_filter); + /* disconnect reason is not used in immediate disconnections */ + cmd->type = DISCONNECT_IMMEDIATE; + + ret = wl1271_cmd_send(wl, CMD_DISCONNECT, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send disconnect command"); + goto out_free; + } + + ret = wl1271_cmd_wait_for_event(wl, DISCONNECT_EVENT_COMPLETE_ID); + if (ret < 0) + wl1271_error("cmd disconnect event completion error"); + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl1271_cmd_set_sta_state(struct wl1271 *wl) +{ + struct wl1271_cmd_set_sta_state *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set sta state"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->state = WL1271_CMD_STA_STATE_CONNECTED; + + ret = wl1271_cmd_send(wl, CMD_SET_STA_STATE, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send set STA state command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl1271_cmd_start_bss(struct wl1271 *wl) +{ + struct wl1271_cmd_bss_start *cmd; + struct ieee80211_bss_conf *bss_conf = &wl->vif->bss_conf; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd start bss"); + + /* + * FIXME: We currently do not support hidden SSID. The real SSID + * should be fetched from mac80211 first. + */ + if (wl->ssid_len == 0) { + wl1271_warning("Hidden SSID currently not supported for AP"); + ret = -EINVAL; + goto out; + } + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + memcpy(cmd->bssid, bss_conf->bssid, ETH_ALEN); + + cmd->aging_period = cpu_to_le16(WL1271_AP_DEF_INACTIV_SEC); + cmd->bss_index = WL1271_AP_BSS_INDEX; + cmd->global_hlid = WL1271_AP_GLOBAL_HLID; + cmd->broadcast_hlid = WL1271_AP_BROADCAST_HLID; + cmd->basic_rate_set = cpu_to_le32(wl->basic_rate_set); + cmd->beacon_interval = cpu_to_le16(wl->beacon_int); + cmd->dtim_interval = bss_conf->dtim_period; + cmd->beacon_expiry = WL1271_AP_DEF_BEACON_EXP; + cmd->channel = wl->channel; + cmd->ssid_len = wl->ssid_len; + cmd->ssid_type = SSID_TYPE_PUBLIC; + memcpy(cmd->ssid, wl->ssid, wl->ssid_len); + + switch (wl->band) { + case IEEE80211_BAND_2GHZ: + cmd->band = RADIO_BAND_2_4GHZ; + break; + case IEEE80211_BAND_5GHZ: + cmd->band = RADIO_BAND_5GHZ; + break; + default: + wl1271_warning("bss start - unknown band: %d", (int)wl->band); + cmd->band = RADIO_BAND_2_4GHZ; + break; + } + + ret = wl1271_cmd_send(wl, CMD_BSS_START, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd start bss"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl1271_cmd_stop_bss(struct wl1271 *wl) +{ + struct wl1271_cmd_bss_start *cmd; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd stop bss"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->bss_index = WL1271_AP_BSS_INDEX; + + ret = wl1271_cmd_send(wl, CMD_BSS_STOP, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd stop bss"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl1271_cmd_add_sta(struct wl1271 *wl, struct ieee80211_sta *sta, u8 hlid) +{ + struct wl1271_cmd_add_sta *cmd; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd add sta %d", (int)hlid); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + /* currently we don't support UAPSD */ + cmd->sp_len = 0; + + memcpy(cmd->addr, sta->addr, ETH_ALEN); + cmd->bss_index = WL1271_AP_BSS_INDEX; + cmd->aid = sta->aid; + cmd->hlid = hlid; + + /* + * FIXME: Does STA support QOS? We need to propagate this info from + * hostapd. Currently not that important since this is only used for + * sending the correct flavor of null-data packet in response to a + * trigger. + */ + cmd->wmm = 0; + + cmd->supported_rates = cpu_to_le32(wl1271_tx_enabled_rates_get(wl, + sta->supp_rates[wl->band])); + + wl1271_debug(DEBUG_CMD, "new sta rates: 0x%x", cmd->supported_rates); + + ret = wl1271_cmd_send(wl, CMD_ADD_STA, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd add sta"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl1271_cmd_remove_sta(struct wl1271 *wl, u8 hlid) +{ + struct wl1271_cmd_remove_sta *cmd; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd remove sta %d", (int)hlid); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->hlid = hlid; + /* We never send a deauth, mac80211 is in charge of this */ + cmd->reason_opcode = 0; + cmd->send_deauth_flag = 0; + + ret = wl1271_cmd_send(wl, CMD_REMOVE_STA, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd remove sta"); + goto out_free; + } + + /* + * We are ok with a timeout here. The event is sometimes not sent + * due to a firmware bug. + */ + wl1271_cmd_wait_for_event_or_timeout(wl, STA_REMOVE_COMPLETE_EVENT_ID); + +out_free: + kfree(cmd); + +out: + return ret; +} diff --git a/drivers/net/wireless/wl12xx/cmd.h b/drivers/net/wireless/wl12xx/cmd.h new file mode 100644 index 00000000..5cac95d9 --- /dev/null +++ b/drivers/net/wireless/wl12xx/cmd.h @@ -0,0 +1,578 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __CMD_H__ +#define __CMD_H__ + +#include "wl12xx.h" + +struct acx_header; + +int wl1271_cmd_send(struct wl1271 *wl, u16 id, void *buf, size_t len, + size_t res_len); +int wl1271_cmd_general_parms(struct wl1271 *wl); +int wl128x_cmd_general_parms(struct wl1271 *wl); +int wl1271_cmd_radio_parms(struct wl1271 *wl); +int wl128x_cmd_radio_parms(struct wl1271 *wl); +int wl1271_cmd_ext_radio_parms(struct wl1271 *wl); +int wl1271_cmd_join(struct wl1271 *wl, u8 bss_type); +int wl1271_cmd_test(struct wl1271 *wl, void *buf, size_t buf_len, u8 answer); +int wl1271_cmd_interrogate(struct wl1271 *wl, u16 id, void *buf, size_t len); +int wl1271_cmd_configure(struct wl1271 *wl, u16 id, void *buf, size_t len); +int wl1271_cmd_data_path(struct wl1271 *wl, bool enable); +int wl1271_cmd_ps_mode(struct wl1271 *wl, u8 ps_mode); +int wl1271_cmd_read_memory(struct wl1271 *wl, u32 addr, void *answer, + size_t len); +int wl1271_cmd_template_set(struct wl1271 *wl, u16 template_id, + void *buf, size_t buf_len, int index, u32 rates); +int wl1271_cmd_build_null_data(struct wl1271 *wl); +int wl1271_cmd_build_ps_poll(struct wl1271 *wl, u16 aid); +int wl1271_cmd_build_probe_req(struct wl1271 *wl, + const u8 *ssid, size_t ssid_len, + const u8 *ie, size_t ie_len, u8 band); +struct sk_buff *wl1271_cmd_build_ap_probe_req(struct wl1271 *wl, + struct sk_buff *skb); +int wl1271_cmd_build_arp_rsp(struct wl1271 *wl, __be32 ip_addr); +int wl1271_build_qos_null_data(struct wl1271 *wl); +int wl1271_cmd_build_klv_null_data(struct wl1271 *wl); +int wl1271_cmd_set_sta_default_wep_key(struct wl1271 *wl, u8 id); +int wl1271_cmd_set_ap_default_wep_key(struct wl1271 *wl, u8 id); +int wl1271_cmd_set_sta_key(struct wl1271 *wl, u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, const u8 *addr, + u32 tx_seq_32, u16 tx_seq_16); +int wl1271_cmd_set_ap_key(struct wl1271 *wl, u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, u8 hlid, u32 tx_seq_32, + u16 tx_seq_16); +int wl1271_cmd_disconnect(struct wl1271 *wl); +int wl1271_cmd_set_sta_state(struct wl1271 *wl); +int wl1271_cmd_start_bss(struct wl1271 *wl); +int wl1271_cmd_stop_bss(struct wl1271 *wl); +int wl1271_cmd_add_sta(struct wl1271 *wl, struct ieee80211_sta *sta, u8 hlid); +int wl1271_cmd_remove_sta(struct wl1271 *wl, u8 hlid); + +enum wl1271_commands { + CMD_INTERROGATE = 1, /*use this to read information elements*/ + CMD_CONFIGURE = 2, /*use this to write information elements*/ + CMD_ENABLE_RX = 3, + CMD_ENABLE_TX = 4, + CMD_DISABLE_RX = 5, + CMD_DISABLE_TX = 6, + CMD_SCAN = 8, + CMD_STOP_SCAN = 9, + CMD_START_JOIN = 11, + CMD_SET_KEYS = 12, + CMD_READ_MEMORY = 13, + CMD_WRITE_MEMORY = 14, + CMD_SET_TEMPLATE = 19, + CMD_TEST = 23, + CMD_NOISE_HIST = 28, + CMD_LNA_CONTROL = 32, + CMD_SET_BCN_MODE = 33, + CMD_MEASUREMENT = 34, + CMD_STOP_MEASUREMENT = 35, + CMD_DISCONNECT = 36, + CMD_SET_PS_MODE = 37, + CMD_CHANNEL_SWITCH = 38, + CMD_STOP_CHANNEL_SWICTH = 39, + CMD_AP_DISCOVERY = 40, + CMD_STOP_AP_DISCOVERY = 41, + CMD_SPS_SCAN = 42, + CMD_STOP_SPS_SCAN = 43, + CMD_HEALTH_CHECK = 45, + CMD_DEBUG = 46, + CMD_TRIGGER_SCAN_TO = 47, + CMD_CONNECTION_SCAN_CFG = 48, + CMD_CONNECTION_SCAN_SSID_CFG = 49, + CMD_START_PERIODIC_SCAN = 50, + CMD_STOP_PERIODIC_SCAN = 51, + CMD_SET_STA_STATE = 52, + + /* AP mode commands */ + CMD_BSS_START = 60, + CMD_BSS_STOP = 61, + CMD_ADD_STA = 62, + CMD_REMOVE_STA = 63, + + NUM_COMMANDS, + MAX_COMMAND_ID = 0xFFFF, +}; + +#define MAX_CMD_PARAMS 572 + +enum { + CMD_TEMPL_KLV_IDX_NULL_DATA = 0, + CMD_TEMPL_KLV_IDX_MAX = 4 +}; + +enum cmd_templ { + CMD_TEMPL_NULL_DATA = 0, + CMD_TEMPL_BEACON, + CMD_TEMPL_CFG_PROBE_REQ_2_4, + CMD_TEMPL_CFG_PROBE_REQ_5, + CMD_TEMPL_PROBE_RESPONSE, + CMD_TEMPL_QOS_NULL_DATA, + CMD_TEMPL_PS_POLL, + CMD_TEMPL_KLV, + CMD_TEMPL_DISCONNECT, + CMD_TEMPL_PROBE_REQ_2_4, /* for firmware internal use only */ + CMD_TEMPL_PROBE_REQ_5, /* for firmware internal use only */ + CMD_TEMPL_BAR, /* for firmware internal use only */ + CMD_TEMPL_CTS, /* + * For CTS-to-self (FastCTS) mechanism + * for BT/WLAN coexistence (SoftGemini). */ + CMD_TEMPL_ARP_RSP, + CMD_TEMPL_LINK_MEASUREMENT_REPORT, + + /* AP-mode specific */ + CMD_TEMPL_AP_BEACON = 13, + CMD_TEMPL_AP_PROBE_RESPONSE, + CMD_TEMPL_AP_ARP_RSP, + CMD_TEMPL_DEAUTH_AP, + + CMD_TEMPL_MAX = 0xff +}; + +/* unit ms */ +#define WL1271_COMMAND_TIMEOUT 2000 +#define WL1271_CMD_TEMPL_MAX_SIZE 252 +#define WL1271_EVENT_TIMEOUT 750 + +struct wl1271_cmd_header { + __le16 id; + __le16 status; + /* payload */ + u8 data[0]; +} __packed; + +#define WL1271_CMD_MAX_PARAMS 572 + +struct wl1271_command { + struct wl1271_cmd_header header; + u8 parameters[WL1271_CMD_MAX_PARAMS]; +} __packed; + +enum { + CMD_MAILBOX_IDLE = 0, + CMD_STATUS_SUCCESS = 1, + CMD_STATUS_UNKNOWN_CMD = 2, + CMD_STATUS_UNKNOWN_IE = 3, + CMD_STATUS_REJECT_MEAS_SG_ACTIVE = 11, + CMD_STATUS_RX_BUSY = 13, + CMD_STATUS_INVALID_PARAM = 14, + CMD_STATUS_TEMPLATE_TOO_LARGE = 15, + CMD_STATUS_OUT_OF_MEMORY = 16, + CMD_STATUS_STA_TABLE_FULL = 17, + CMD_STATUS_RADIO_ERROR = 18, + CMD_STATUS_WRONG_NESTING = 19, + CMD_STATUS_TIMEOUT = 21, /* Driver internal use.*/ + CMD_STATUS_FW_RESET = 22, /* Driver internal use.*/ + MAX_COMMAND_STATUS = 0xff +}; + +#define CMDMBOX_HEADER_LEN 4 +#define CMDMBOX_INFO_ELEM_HEADER_LEN 4 + +enum { + BSS_TYPE_IBSS = 0, + BSS_TYPE_STA_BSS = 2, + BSS_TYPE_AP_BSS = 3, + MAX_BSS_TYPE = 0xFF +}; + +#define WL1271_JOIN_CMD_CTRL_TX_FLUSH 0x80 /* Firmware flushes all Tx */ +#define WL1271_JOIN_CMD_TX_SESSION_OFFSET 1 +#define WL1271_JOIN_CMD_BSS_TYPE_5GHZ 0x10 + +struct wl1271_cmd_join { + struct wl1271_cmd_header header; + + __le32 bssid_lsb; + __le16 bssid_msb; + __le16 beacon_interval; /* in TBTTs */ + __le32 rx_config_options; + __le32 rx_filter_options; + + /* + * The target uses this field to determine the rate at + * which to transmit control frame responses (such as + * ACK or CTS frames). + */ + __le32 basic_rate_set; + __le32 supported_rate_set; + u8 dtim_interval; + /* + * bits 0-2: This bitwise field specifies the type + * of BSS to start or join (BSS_TYPE_*). + * bit 4: Band - The radio band in which to join + * or start. + * 0 - 2.4GHz band + * 1 - 5GHz band + * bits 3, 5-7: Reserved + */ + u8 bss_type; + u8 channel; + u8 ssid_len; + u8 ssid[IW_ESSID_MAX_SIZE]; + u8 ctrl; /* JOIN_CMD_CTRL_* */ + u8 reserved[3]; +} __packed; + +struct cmd_enabledisable_path { + struct wl1271_cmd_header header; + + u8 channel; + u8 padding[3]; +} __packed; + +#define WL1271_RATE_AUTOMATIC 0 + +struct wl1271_cmd_template_set { + struct wl1271_cmd_header header; + + __le16 len; + u8 template_type; + u8 index; /* relevant only for KLV_TEMPLATE type */ + __le32 enabled_rates; + u8 short_retry_limit; + u8 long_retry_limit; + u8 aflags; + u8 reserved; + u8 template_data[WL1271_CMD_TEMPL_MAX_SIZE]; +} __packed; + +#define TIM_ELE_ID 5 +#define PARTIAL_VBM_MAX 251 + +struct wl1271_tim { + u8 identity; + u8 length; + u8 dtim_count; + u8 dtim_period; + u8 bitmap_ctrl; + u8 pvb_field[PARTIAL_VBM_MAX]; /* Partial Virtual Bitmap */ +} __packed; + +enum wl1271_cmd_ps_mode { + STATION_ACTIVE_MODE, + STATION_POWER_SAVE_MODE +}; + +struct wl1271_cmd_ps_params { + struct wl1271_cmd_header header; + + u8 ps_mode; /* STATION_* */ + u8 padding[3]; +} __packed; + +/* HW encryption keys */ +#define NUM_ACCESS_CATEGORIES_COPY 4 + +enum wl1271_cmd_key_action { + KEY_ADD_OR_REPLACE = 1, + KEY_REMOVE = 2, + KEY_SET_ID = 3, + MAX_KEY_ACTION = 0xffff, +}; + +enum wl1271_cmd_key_type { + KEY_NONE = 0, + KEY_WEP = 1, + KEY_TKIP = 2, + KEY_AES = 3, + KEY_GEM = 4, +}; + +/* FIXME: Add description for key-types */ + +struct wl1271_cmd_set_sta_keys { + struct wl1271_cmd_header header; + + /* Ignored for default WEP key */ + u8 addr[ETH_ALEN]; + + /* key_action_e */ + __le16 key_action; + + __le16 reserved_1; + + /* key size in bytes */ + u8 key_size; + + /* key_type_e */ + u8 key_type; + u8 ssid_profile; + + /* + * TKIP, AES: frame's key id field. + * For WEP default key: key id; + */ + u8 id; + u8 reserved_2[6]; + u8 key[MAX_KEY_SIZE]; + __le16 ac_seq_num16[NUM_ACCESS_CATEGORIES_COPY]; + __le32 ac_seq_num32[NUM_ACCESS_CATEGORIES_COPY]; +} __packed; + +enum wl1271_cmd_lid_key_type { + UNICAST_LID_TYPE = 0, + BROADCAST_LID_TYPE = 1, + WEP_DEFAULT_LID_TYPE = 2 +}; + +struct wl1271_cmd_set_ap_keys { + struct wl1271_cmd_header header; + + /* + * Indicates whether the HLID is a unicast key set + * or broadcast key set. A special value 0xFF is + * used to indicate that the HLID is on WEP-default + * (multi-hlids). of type wl1271_cmd_lid_key_type. + */ + u8 hlid; + + /* + * In WEP-default network (hlid == 0xFF) used to + * indicate which network STA/IBSS/AP role should be + * changed + */ + u8 lid_key_type; + + /* + * Key ID - For TKIP and AES key types, this field + * indicates the value that should be inserted into + * the KeyID field of frames transmitted using this + * key entry. For broadcast keys the index use as a + * marker for TX/RX key. + * For WEP default network (HLID=0xFF), this field + * indicates the ID of the key to add or remove. + */ + u8 key_id; + u8 reserved_1; + + /* key_action_e */ + __le16 key_action; + + /* key size in bytes */ + u8 key_size; + + /* key_type_e */ + u8 key_type; + + /* This field holds the security key data to add to the STA table */ + u8 key[MAX_KEY_SIZE]; + __le16 ac_seq_num16[NUM_ACCESS_CATEGORIES_COPY]; + __le32 ac_seq_num32[NUM_ACCESS_CATEGORIES_COPY]; +} __packed; + +struct wl1271_cmd_test_header { + u8 id; + u8 padding[3]; +} __packed; + +enum wl1271_channel_tune_bands { + WL1271_CHANNEL_TUNE_BAND_2_4, + WL1271_CHANNEL_TUNE_BAND_5, + WL1271_CHANNEL_TUNE_BAND_4_9 +}; + +#define WL1271_PD_REFERENCE_POINT_BAND_B_G 0 + +#define TEST_CMD_INI_FILE_RADIO_PARAM 0x19 +#define TEST_CMD_INI_FILE_GENERAL_PARAM 0x1E +#define TEST_CMD_INI_FILE_RF_EXTENDED_PARAM 0x26 + +struct wl1271_general_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + struct wl1271_ini_general_params general_params; + + u8 sr_debug_table[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 sr_sen_n_p; + u8 sr_sen_n_p_gain; + u8 sr_sen_nrn; + u8 sr_sen_prn; + u8 padding[3]; +} __packed; + +struct wl128x_general_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + struct wl128x_ini_general_params general_params; + + u8 sr_debug_table[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 sr_sen_n_p; + u8 sr_sen_n_p_gain; + u8 sr_sen_nrn; + u8 sr_sen_prn; + u8 padding[3]; +} __packed; + +struct wl1271_radio_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + /* Static radio parameters */ + struct wl1271_ini_band_params_2 static_params_2; + struct wl1271_ini_band_params_5 static_params_5; + + /* Dynamic radio parameters */ + struct wl1271_ini_fem_params_2 dyn_params_2; + u8 padding2; + struct wl1271_ini_fem_params_5 dyn_params_5; + u8 padding3[2]; +} __packed; + +struct wl128x_radio_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + /* Static radio parameters */ + struct wl128x_ini_band_params_2 static_params_2; + struct wl128x_ini_band_params_5 static_params_5; + + u8 fem_vendor_and_options; + + /* Dynamic radio parameters */ + struct wl128x_ini_fem_params_2 dyn_params_2; + u8 padding2; + struct wl128x_ini_fem_params_5 dyn_params_5; +} __packed; + +struct wl1271_ext_radio_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + u8 tx_per_channel_power_compensation_2[CONF_TX_PWR_COMPENSATION_LEN_2]; + u8 tx_per_channel_power_compensation_5[CONF_TX_PWR_COMPENSATION_LEN_5]; + u8 padding[3]; +} __packed; + +/* + * There are three types of disconnections: + * + * DISCONNECT_IMMEDIATE: the fw doesn't send any frames + * DISCONNECT_DEAUTH: the fw generates a DEAUTH request with the reason + * we have passed + * DISCONNECT_DISASSOC: the fw generates a DESASSOC request with the reason + * we have passed + */ +enum wl1271_disconnect_type { + DISCONNECT_IMMEDIATE, + DISCONNECT_DEAUTH, + DISCONNECT_DISASSOC +}; + +struct wl1271_cmd_disconnect { + struct wl1271_cmd_header header; + + __le32 rx_config_options; + __le32 rx_filter_options; + + __le16 reason; + u8 type; + + u8 padding; +} __packed; + +#define WL1271_CMD_STA_STATE_CONNECTED 1 + +struct wl1271_cmd_set_sta_state { + struct wl1271_cmd_header header; + + u8 state; + u8 padding[3]; +} __packed; + +enum wl1271_ssid_type { + SSID_TYPE_PUBLIC = 0, + SSID_TYPE_HIDDEN = 1 +}; + +struct wl1271_cmd_bss_start { + struct wl1271_cmd_header header; + + /* wl1271_ssid_type */ + u8 ssid_type; + u8 ssid_len; + u8 ssid[IW_ESSID_MAX_SIZE]; + u8 padding_1[2]; + + /* Basic rate set */ + __le32 basic_rate_set; + /* Aging period in seconds*/ + __le16 aging_period; + + /* + * This field specifies the time between target beacon + * transmission times (TBTTs), in time units (TUs). + * Valid values are 1 to 1024. + */ + __le16 beacon_interval; + u8 bssid[ETH_ALEN]; + u8 bss_index; + /* Radio band */ + u8 band; + u8 channel; + /* The host link id for the AP's global queue */ + u8 global_hlid; + /* The host link id for the AP's broadcast queue */ + u8 broadcast_hlid; + /* DTIM count */ + u8 dtim_interval; + /* Beacon expiry time in ms */ + u8 beacon_expiry; + u8 padding_2[3]; +} __packed; + +struct wl1271_cmd_add_sta { + struct wl1271_cmd_header header; + + u8 addr[ETH_ALEN]; + u8 hlid; + u8 aid; + u8 psd_type[NUM_ACCESS_CATEGORIES_COPY]; + __le32 supported_rates; + u8 bss_index; + u8 sp_len; + u8 wmm; + u8 padding1; +} __packed; + +struct wl1271_cmd_remove_sta { + struct wl1271_cmd_header header; + + u8 hlid; + u8 reason_opcode; + u8 send_deauth_flag; + u8 padding1; +} __packed; + +#endif /* __WL1271_CMD_H__ */ diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h new file mode 100644 index 00000000..c83fefb6 --- /dev/null +++ b/drivers/net/wireless/wl12xx/conf.h @@ -0,0 +1,1269 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __CONF_H__ +#define __CONF_H__ + +enum { + CONF_HW_BIT_RATE_1MBPS = BIT(0), + CONF_HW_BIT_RATE_2MBPS = BIT(1), + CONF_HW_BIT_RATE_5_5MBPS = BIT(2), + CONF_HW_BIT_RATE_6MBPS = BIT(3), + CONF_HW_BIT_RATE_9MBPS = BIT(4), + CONF_HW_BIT_RATE_11MBPS = BIT(5), + CONF_HW_BIT_RATE_12MBPS = BIT(6), + CONF_HW_BIT_RATE_18MBPS = BIT(7), + CONF_HW_BIT_RATE_22MBPS = BIT(8), + CONF_HW_BIT_RATE_24MBPS = BIT(9), + CONF_HW_BIT_RATE_36MBPS = BIT(10), + CONF_HW_BIT_RATE_48MBPS = BIT(11), + CONF_HW_BIT_RATE_54MBPS = BIT(12), + CONF_HW_BIT_RATE_MCS_0 = BIT(13), + CONF_HW_BIT_RATE_MCS_1 = BIT(14), + CONF_HW_BIT_RATE_MCS_2 = BIT(15), + CONF_HW_BIT_RATE_MCS_3 = BIT(16), + CONF_HW_BIT_RATE_MCS_4 = BIT(17), + CONF_HW_BIT_RATE_MCS_5 = BIT(18), + CONF_HW_BIT_RATE_MCS_6 = BIT(19), + CONF_HW_BIT_RATE_MCS_7 = BIT(20) +}; + +enum { + CONF_HW_RATE_INDEX_1MBPS = 0, + CONF_HW_RATE_INDEX_2MBPS = 1, + CONF_HW_RATE_INDEX_5_5MBPS = 2, + CONF_HW_RATE_INDEX_6MBPS = 3, + CONF_HW_RATE_INDEX_9MBPS = 4, + CONF_HW_RATE_INDEX_11MBPS = 5, + CONF_HW_RATE_INDEX_12MBPS = 6, + CONF_HW_RATE_INDEX_18MBPS = 7, + CONF_HW_RATE_INDEX_22MBPS = 8, + CONF_HW_RATE_INDEX_24MBPS = 9, + CONF_HW_RATE_INDEX_36MBPS = 10, + CONF_HW_RATE_INDEX_48MBPS = 11, + CONF_HW_RATE_INDEX_54MBPS = 12, + CONF_HW_RATE_INDEX_MAX = CONF_HW_RATE_INDEX_54MBPS, +}; + +enum { + CONF_HW_RXTX_RATE_MCS7 = 0, + CONF_HW_RXTX_RATE_MCS6, + CONF_HW_RXTX_RATE_MCS5, + CONF_HW_RXTX_RATE_MCS4, + CONF_HW_RXTX_RATE_MCS3, + CONF_HW_RXTX_RATE_MCS2, + CONF_HW_RXTX_RATE_MCS1, + CONF_HW_RXTX_RATE_MCS0, + CONF_HW_RXTX_RATE_54, + CONF_HW_RXTX_RATE_48, + CONF_HW_RXTX_RATE_36, + CONF_HW_RXTX_RATE_24, + CONF_HW_RXTX_RATE_22, + CONF_HW_RXTX_RATE_18, + CONF_HW_RXTX_RATE_12, + CONF_HW_RXTX_RATE_11, + CONF_HW_RXTX_RATE_9, + CONF_HW_RXTX_RATE_6, + CONF_HW_RXTX_RATE_5_5, + CONF_HW_RXTX_RATE_2, + CONF_HW_RXTX_RATE_1, + CONF_HW_RXTX_RATE_MAX, + CONF_HW_RXTX_RATE_UNSUPPORTED = 0xff +}; + +enum { + CONF_SG_DISABLE = 0, + CONF_SG_PROTECTIVE, + CONF_SG_OPPORTUNISTIC +}; + +enum { + /* + * PER threshold in PPM of the BT voice + * + * Range: 0 - 10000000 + */ + CONF_SG_BT_PER_THRESHOLD = 0, + + /* + * Number of consequent RX_ACTIVE activities to override BT voice + * frames to ensure WLAN connection + * + * Range: 0 - 100 + */ + CONF_SG_HV3_MAX_OVERRIDE, + + /* + * Defines the PER threshold of the BT voice + * + * Range: 0 - 65000 + */ + CONF_SG_BT_NFS_SAMPLE_INTERVAL, + + /* + * Defines the load ratio of BT + * + * Range: 0 - 100 (%) + */ + CONF_SG_BT_LOAD_RATIO, + + /* + * Defines whether the SG will force WLAN host to enter/exit PSM + * + * Range: 1 - SG can force, 0 - host handles PSM + */ + CONF_SG_AUTO_PS_MODE, + + /* + * Compensation percentage of probe requests when scan initiated + * during BT voice/ACL link. + * + * Range: 0 - 255 (%) + */ + CONF_SG_AUTO_SCAN_PROBE_REQ, + + /* + * Compensation percentage of probe requests when active scan initiated + * during BT voice + * + * Range: 0 - 255 (%) + */ + CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3, + + /* + * Defines antenna configuration (single/dual antenna) + * + * Range: 0 - single antenna, 1 - dual antenna + */ + CONF_SG_ANTENNA_CONFIGURATION, + + /* + * The threshold (percent) of max consequtive beacon misses before + * increasing priority of beacon reception. + * + * Range: 0 - 100 (%) + */ + CONF_SG_BEACON_MISS_PERCENT, + + /* + * The rate threshold below which receiving a data frame from the AP + * will increase the priority of the data frame above BT traffic. + * + * Range: 0,2, 5(=5.5), 6, 9, 11, 12, 18, 24, 36, 48, 54 + */ + CONF_SG_RATE_ADAPT_THRESH, + + /* + * Not used currently. + * + * Range: 0 + */ + CONF_SG_RATE_ADAPT_SNR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN PSM / BT master basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_BR, + CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_BR, + + /* + * The time after it expires no new WLAN trigger frame is trasmitted + * in WLAN PSM / BT master basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_BR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN PSM / BT slave basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_BR, + CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_BR, + + /* + * The time after it expires no new WLAN trigger frame is trasmitted + * in WLAN PSM / BT slave basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_BR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN PSM / BT master EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_EDR, + CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_EDR, + + /* + * The time after it expires no new WLAN trigger frame is trasmitted + * in WLAN PSM / BT master EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_EDR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN PSM / BT slave EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_EDR, + CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_EDR, + + /* + * The time after it expires no new WLAN trigger frame is trasmitted + * in WLAN PSM / BT slave EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_EDR, + + /* + * RX guard time before the beginning of a new BT voice frame during + * which no new WLAN trigger frame is transmitted. + * + * Range: 0 - 100000 (us) + */ + CONF_SG_RXT, + + /* + * TX guard time before the beginning of a new BT voice frame during + * which no new WLAN frame is transmitted. + * + * Range: 0 - 100000 (us) + */ + + CONF_SG_TXT, + + /* + * Enable adaptive RXT/TXT algorithm. If disabled, the host values + * will be utilized. + * + * Range: 0 - disable, 1 - enable + */ + CONF_SG_ADAPTIVE_RXT_TXT, + + /* + * The used WLAN legacy service period during active BT ACL link + * + * Range: 0 - 255 (ms) + */ + CONF_SG_PS_POLL_TIMEOUT, + + /* + * The used WLAN UPSD service period during active BT ACL link + * + * Range: 0 - 255 (ms) + */ + CONF_SG_UPSD_TIMEOUT, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN Active / BT master EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MIN_EDR, + CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MAX_EDR, + + /* + * The maximum time WLAN can gain the antenna for + * in WLAN Active / BT master EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_MASTER_EDR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN Active / BT slave EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MIN_EDR, + CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MAX_EDR, + + /* + * The maximum time WLAN can gain the antenna for + * in WLAN Active / BT slave EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_SLAVE_EDR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN Active / BT basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_ACTIVE_BT_ACL_MIN_BR, + CONF_SG_WLAN_ACTIVE_BT_ACL_MAX_BR, + + /* + * The maximum time WLAN can gain the antenna for + * in WLAN Active / BT basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_BR, + + /* + * Compensation percentage of WLAN passive scan window if initiated + * during BT voice + * + * Range: 0 - 1000 (%) + */ + CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3, + + /* + * Compensation percentage of WLAN passive scan window if initiated + * during BT A2DP + * + * Range: 0 - 1000 (%) + */ + CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP, + + /* + * Fixed time ensured for BT traffic to gain the antenna during WLAN + * passive scan. + * + * Range: 0 - 1000 ms + */ + CONF_SG_PASSIVE_SCAN_A2DP_BT_TIME, + + /* + * Fixed time ensured for WLAN traffic to gain the antenna during WLAN + * passive scan. + * + * Range: 0 - 1000 ms + */ + CONF_SG_PASSIVE_SCAN_A2DP_WLAN_TIME, + + /* + * Number of consequent BT voice frames not interrupted by WLAN + * + * Range: 0 - 100 + */ + CONF_SG_HV3_MAX_SERVED, + + /* + * Protection time of the DHCP procedure. + * + * Range: 0 - 100000 (ms) + */ + CONF_SG_DHCP_TIME, + + /* + * Compensation percentage of WLAN active scan window if initiated + * during BT A2DP + * + * Range: 0 - 1000 (%) + */ + CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP, + CONF_SG_TEMP_PARAM_1, + CONF_SG_TEMP_PARAM_2, + CONF_SG_TEMP_PARAM_3, + CONF_SG_TEMP_PARAM_4, + CONF_SG_TEMP_PARAM_5, + + /* + * AP beacon miss + * + * Range: 0 - 255 + */ + CONF_SG_AP_BEACON_MISS_TX, + + /* + * AP RX window length + * + * Range: 0 - 50 + */ + CONF_SG_RX_WINDOW_LENGTH, + + /* + * AP connection protection time + * + * Range: 0 - 5000 + */ + CONF_SG_AP_CONNECTION_PROTECTION_TIME, + + CONF_SG_TEMP_PARAM_6, + CONF_SG_TEMP_PARAM_7, + CONF_SG_TEMP_PARAM_8, + CONF_SG_TEMP_PARAM_9, + CONF_SG_TEMP_PARAM_10, + + CONF_SG_STA_PARAMS_MAX = CONF_SG_TEMP_PARAM_5 + 1, + CONF_SG_AP_PARAMS_MAX = CONF_SG_TEMP_PARAM_10 + 1, + + CONF_SG_PARAMS_ALL = 0xff +}; + +struct conf_sg_settings { + u32 sta_params[CONF_SG_STA_PARAMS_MAX]; + u32 ap_params[CONF_SG_AP_PARAMS_MAX]; + u8 state; +}; + +enum conf_rx_queue_type { + CONF_RX_QUEUE_TYPE_LOW_PRIORITY, /* All except the high priority */ + CONF_RX_QUEUE_TYPE_HIGH_PRIORITY, /* Management and voice packets */ +}; + +struct conf_rx_settings { + /* + * The maximum amount of time, in TU, before the + * firmware discards the MSDU. + * + * Range: 0 - 0xFFFFFFFF + */ + u32 rx_msdu_life_time; + + /* + * Packet detection threshold in the PHY. + * + * FIXME: details unknown. + */ + u32 packet_detection_threshold; + + /* + * The longest time the STA will wait to receive traffic from the AP + * after a PS-poll has been transmitted. + * + * Range: 0 - 200000 + */ + u16 ps_poll_timeout; + /* + * The longest time the STA will wait to receive traffic from the AP + * after a frame has been sent from an UPSD enabled queue. + * + * Range: 0 - 200000 + */ + u16 upsd_timeout; + + /* + * The number of octets in an MPDU, below which an RTS/CTS + * handshake is not performed. + * + * Range: 0 - 4096 + */ + u16 rts_threshold; + + /* + * The RX Clear Channel Assessment threshold in the PHY + * (the energy threshold). + * + * Range: ENABLE_ENERGY_D == 0x140A + * DISABLE_ENERGY_D == 0xFFEF + */ + u16 rx_cca_threshold; + + /* + * Occupied Rx mem-blocks number which requires interrupting the host + * (0 = no buffering, 0xffff = disabled). + * + * Range: u16 + */ + u16 irq_blk_threshold; + + /* + * Rx packets number which requires interrupting the host + * (0 = no buffering). + * + * Range: u16 + */ + u16 irq_pkt_threshold; + + /* + * Max time in msec the FW may delay RX-Complete interrupt. + * + * Range: 1 - 100 + */ + u16 irq_timeout; + + /* + * The RX queue type. + * + * Range: RX_QUEUE_TYPE_RX_LOW_PRIORITY, RX_QUEUE_TYPE_RX_HIGH_PRIORITY, + */ + u8 queue_type; +}; + +#define CONF_TX_MAX_RATE_CLASSES 8 + +#define CONF_TX_RATE_MASK_UNSPECIFIED 0 +#define CONF_TX_RATE_MASK_BASIC (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS) +#define CONF_TX_RATE_RETRY_LIMIT 10 + +/* + * Rates supported for data packets when operating as AP. Note the absence + * of the 22Mbps rate. There is a FW limitation on 12 rates so we must drop + * one. The rate dropped is not mandatory under any operating mode. + */ +#define CONF_TX_AP_ENABLED_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS | \ + CONF_HW_BIT_RATE_6MBPS | CONF_HW_BIT_RATE_9MBPS | \ + CONF_HW_BIT_RATE_11MBPS | CONF_HW_BIT_RATE_12MBPS | \ + CONF_HW_BIT_RATE_18MBPS | CONF_HW_BIT_RATE_24MBPS | \ + CONF_HW_BIT_RATE_36MBPS | CONF_HW_BIT_RATE_48MBPS | \ + CONF_HW_BIT_RATE_54MBPS) + +#define CONF_TX_OFDM_RATES (CONF_HW_BIT_RATE_6MBPS | \ + CONF_HW_BIT_RATE_12MBPS | CONF_HW_BIT_RATE_24MBPS | \ + CONF_HW_BIT_RATE_36MBPS | CONF_HW_BIT_RATE_48MBPS | \ + CONF_HW_BIT_RATE_54MBPS) + + +/* + * Default rates for management traffic when operating in AP mode. This + * should be configured according to the basic rate set of the AP + */ +#define CONF_TX_AP_DEFAULT_MGMT_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS) + +/* + * Default rates for working as IBSS. use 11b rates + */ +#define CONF_TX_IBSS_DEFAULT_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS | \ + CONF_HW_BIT_RATE_11MBPS); + +struct conf_tx_rate_class { + + /* + * The rates enabled for this rate class. + * + * Range: CONF_HW_BIT_RATE_* bit mask + */ + u32 enabled_rates; + + /* + * The dot11 short retry limit used for TX retries. + * + * Range: u8 + */ + u8 short_retry_limit; + + /* + * The dot11 long retry limit used for TX retries. + * + * Range: u8 + */ + u8 long_retry_limit; + + /* + * Flags controlling the attributes of TX transmission. + * + * Range: bit 0: Truncate - when set, FW attempts to send a frame stop + * when the total valid per-rate attempts have + * been exhausted; otherwise transmissions + * will continue at the lowest available rate + * until the appropriate one of the + * short_retry_limit, long_retry_limit, + * dot11_max_transmit_msdu_life_time, or + * max_tx_life_time, is exhausted. + * 1: Preamble Override - indicates if the preamble type + * should be used in TX. + * 2: Preamble Type - the type of the preamble to be used by + * the policy (0 - long preamble, 1 - short preamble. + */ + u8 aflags; +}; + +#define CONF_TX_MAX_AC_COUNT 4 + +/* Slot number setting to start transmission at PIFS interval */ +#define CONF_TX_AIFS_PIFS 1 +/* Slot number setting to start transmission at DIFS interval normal + * DCF access */ +#define CONF_TX_AIFS_DIFS 2 + + +enum conf_tx_ac { + CONF_TX_AC_BE = 0, /* best effort / legacy */ + CONF_TX_AC_BK = 1, /* background */ + CONF_TX_AC_VI = 2, /* video */ + CONF_TX_AC_VO = 3, /* voice */ + CONF_TX_AC_CTS2SELF = 4, /* fictitious AC, follows AC_VO */ + CONF_TX_AC_ANY_TID = 0x1f +}; + +struct conf_tx_ac_category { + /* + * The AC class identifier. + * + * Range: enum conf_tx_ac + */ + u8 ac; + + /* + * The contention window minimum size (in slots) for the access + * class. + * + * Range: u8 + */ + u8 cw_min; + + /* + * The contention window maximum size (in slots) for the access + * class. + * + * Range: u8 + */ + u16 cw_max; + + /* + * The AIF value (in slots) for the access class. + * + * Range: u8 + */ + u8 aifsn; + + /* + * The TX Op Limit (in microseconds) for the access class. + * + * Range: u16 + */ + u16 tx_op_limit; +}; + +#define CONF_TX_MAX_TID_COUNT 8 + +enum { + CONF_CHANNEL_TYPE_DCF = 0, /* DC/LEGACY*/ + CONF_CHANNEL_TYPE_EDCF = 1, /* EDCA*/ + CONF_CHANNEL_TYPE_HCCA = 2, /* HCCA*/ +}; + +enum { + CONF_PS_SCHEME_LEGACY = 0, + CONF_PS_SCHEME_UPSD_TRIGGER = 1, + CONF_PS_SCHEME_LEGACY_PSPOLL = 2, + CONF_PS_SCHEME_SAPSD = 3, +}; + +enum { + CONF_ACK_POLICY_LEGACY = 0, + CONF_ACK_POLICY_NO_ACK = 1, + CONF_ACK_POLICY_BLOCK = 2, +}; + + +struct conf_tx_tid { + u8 queue_id; + u8 channel_type; + u8 tsid; + u8 ps_scheme; + u8 ack_policy; + u32 apsd_conf[2]; +}; + +struct conf_tx_settings { + /* + * The TX ED value for TELEC Enable/Disable. + * + * Range: 0, 1 + */ + u8 tx_energy_detection; + + /* + * Configuration for rate classes for TX (currently only one + * rate class supported). Used in non-AP mode. + */ + struct conf_tx_rate_class sta_rc_conf; + + /* + * Configuration for access categories for TX rate control. + */ + u8 ac_conf_count; + struct conf_tx_ac_category ac_conf[CONF_TX_MAX_AC_COUNT]; + + /* + * AP-mode - allow this number of TX retries to a station before an + * event is triggered from FW. + */ + u16 ap_max_tx_retries; + + /* + * Configuration for TID parameters. + */ + u8 tid_conf_count; + struct conf_tx_tid tid_conf[CONF_TX_MAX_TID_COUNT]; + + /* + * The TX fragmentation threshold. + * + * Range: u16 + */ + u16 frag_threshold; + + /* + * Max time in msec the FW may delay frame TX-Complete interrupt. + * + * Range: u16 + */ + u16 tx_compl_timeout; + + /* + * Completed TX packet count which requires to issue the TX-Complete + * interrupt. + * + * Range: u16 + */ + u16 tx_compl_threshold; + + /* + * The rate used for control messages and scanning on the 2.4GHz band + * + * Range: CONF_HW_BIT_RATE_* bit mask + */ + u32 basic_rate; + + /* + * The rate used for control messages and scanning on the 5GHz band + * + * Range: CONF_HW_BIT_RATE_* bit mask + */ + u32 basic_rate_5; + + /* + * TX retry limits for templates + */ + u8 tmpl_short_retry_limit; + u8 tmpl_long_retry_limit; +}; + +enum { + CONF_WAKE_UP_EVENT_BEACON = 0x01, /* Wake on every Beacon*/ + CONF_WAKE_UP_EVENT_DTIM = 0x02, /* Wake on every DTIM*/ + CONF_WAKE_UP_EVENT_N_DTIM = 0x04, /* Wake every Nth DTIM */ + CONF_WAKE_UP_EVENT_N_BEACONS = 0x08, /* Wake every Nth beacon */ + CONF_WAKE_UP_EVENT_BITS_MASK = 0x0F +}; + +#define CONF_MAX_BCN_FILT_IE_COUNT 32 + +#define CONF_BCN_RULE_PASS_ON_CHANGE BIT(0) +#define CONF_BCN_RULE_PASS_ON_APPEARANCE BIT(1) + +#define CONF_BCN_IE_OUI_LEN 3 +#define CONF_BCN_IE_VER_LEN 2 + +struct conf_bcn_filt_rule { + /* + * IE number to which to associate a rule. + * + * Range: u8 + */ + u8 ie; + + /* + * Rule to associate with the specific ie. + * + * Range: CONF_BCN_RULE_PASS_ON_* + */ + u8 rule; + + /* + * OUI for the vendor specifie IE (221) + */ + u8 oui[CONF_BCN_IE_OUI_LEN]; + + /* + * Type for the vendor specifie IE (221) + */ + u8 type; + + /* + * Version for the vendor specifie IE (221) + */ + u8 version[CONF_BCN_IE_VER_LEN]; +}; + +#define CONF_MAX_RSSI_SNR_TRIGGERS 8 + +enum { + CONF_TRIG_METRIC_RSSI_BEACON = 0, + CONF_TRIG_METRIC_RSSI_DATA, + CONF_TRIG_METRIC_SNR_BEACON, + CONF_TRIG_METRIC_SNR_DATA +}; + +enum { + CONF_TRIG_EVENT_TYPE_LEVEL = 0, + CONF_TRIG_EVENT_TYPE_EDGE +}; + +enum { + CONF_TRIG_EVENT_DIR_LOW = 0, + CONF_TRIG_EVENT_DIR_HIGH, + CONF_TRIG_EVENT_DIR_BIDIR +}; + +struct conf_sig_weights { + + /* + * RSSI from beacons average weight. + * + * Range: u8 + */ + u8 rssi_bcn_avg_weight; + + /* + * RSSI from data average weight. + * + * Range: u8 + */ + u8 rssi_pkt_avg_weight; + + /* + * SNR from beacons average weight. + * + * Range: u8 + */ + u8 snr_bcn_avg_weight; + + /* + * SNR from data average weight. + * + * Range: u8 + */ + u8 snr_pkt_avg_weight; +}; + +enum conf_bcn_filt_mode { + CONF_BCN_FILT_MODE_DISABLED = 0, + CONF_BCN_FILT_MODE_ENABLED = 1 +}; + +enum conf_bet_mode { + CONF_BET_MODE_DISABLE = 0, + CONF_BET_MODE_ENABLE = 1, +}; + +struct conf_conn_settings { + /* + * Firmware wakeup conditions configuration. The host may set only + * one bit. + * + * Range: CONF_WAKE_UP_EVENT_* + */ + u8 wake_up_event; + + /* + * Listen interval for beacons or Dtims. + * + * Range: 0 for beacon and Dtim wakeup + * 1-10 for x Dtims + * 1-255 for x beacons + */ + u8 listen_interval; + + /* + * Enable or disable the beacon filtering. + * + * Range: CONF_BCN_FILT_MODE_* + */ + enum conf_bcn_filt_mode bcn_filt_mode; + + /* + * Configure Beacon filter pass-thru rules. + */ + u8 bcn_filt_ie_count; + struct conf_bcn_filt_rule bcn_filt_ie[CONF_MAX_BCN_FILT_IE_COUNT]; + + /* + * The number of consequtive beacons to lose, before the firmware + * becomes out of synch. + * + * Range: u32 + */ + u32 synch_fail_thold; + + /* + * After out-of-synch, the number of TU's to wait without a further + * received beacon (or probe response) before issuing the BSS_EVENT_LOSE + * event. + * + * Range: u32 + */ + u32 bss_lose_timeout; + + /* + * Beacon receive timeout. + * + * Range: u32 + */ + u32 beacon_rx_timeout; + + /* + * Broadcast receive timeout. + * + * Range: u32 + */ + u32 broadcast_timeout; + + /* + * Enable/disable reception of broadcast packets in power save mode + * + * Range: 1 - enable, 0 - disable + */ + u8 rx_broadcast_in_ps; + + /* + * Consequtive PS Poll failures before sending event to driver + * + * Range: u8 + */ + u8 ps_poll_threshold; + + /* + * PS Poll failure recovery ACTIVE period length + * + * Range: u32 (ms) + */ + u32 ps_poll_recovery_period; + + /* + * Configuration of signal average weights. + */ + struct conf_sig_weights sig_weights; + + /* + * Specifies if beacon early termination procedure is enabled or + * disabled. + * + * Range: CONF_BET_MODE_* + */ + u8 bet_enable; + + /* + * Specifies the maximum number of consecutive beacons that may be + * early terminated. After this number is reached at least one full + * beacon must be correctly received in FW before beacon ET + * resumes. + * + * Range 0 - 255 + */ + u8 bet_max_consecutive; + + /* + * Specifies the maximum number of times to try PSM entry if it fails + * (if sending the appropriate null-func message fails.) + * + * Range 0 - 255 + */ + u8 psm_entry_retries; + + /* + * Specifies the maximum number of times to try PSM exit if it fails + * (if sending the appropriate null-func message fails.) + * + * Range 0 - 255 + */ + u8 psm_exit_retries; + + /* + * Specifies the maximum number of times to try transmit the PSM entry + * null-func frame for each PSM entry attempt + * + * Range 0 - 255 + */ + u8 psm_entry_nullfunc_retries; + + /* + * Specifies the time to linger in active mode after successfully + * transmitting the PSM entry null-func frame. + * + * Range 0 - 255 TU's + */ + u8 psm_entry_hangover_period; + + /* + * + * Specifies the interval of the connection keep-alive null-func + * frame in ms. + * + * Range: 1000 - 3600000 + */ + u32 keep_alive_interval; + + /* + * Maximum listen interval supported by the driver in units of beacons. + * + * Range: u16 + */ + u8 max_listen_interval; +}; + +enum { + CONF_REF_CLK_19_2_E, + CONF_REF_CLK_26_E, + CONF_REF_CLK_38_4_E, + CONF_REF_CLK_52_E, + CONF_REF_CLK_38_4_M_XTAL, + CONF_REF_CLK_26_M_XTAL, +}; + +enum single_dual_band_enum { + CONF_SINGLE_BAND, + CONF_DUAL_BAND +}; + +#define CONF_RSSI_AND_PROCESS_COMPENSATION_SIZE 15 +#define CONF_NUMBER_OF_SUB_BANDS_5 7 +#define CONF_NUMBER_OF_RATE_GROUPS 6 +#define CONF_NUMBER_OF_CHANNELS_2_4 14 +#define CONF_NUMBER_OF_CHANNELS_5 35 + +struct conf_itrim_settings { + /* enable dco itrim */ + u8 enable; + + /* moderation timeout in microsecs from the last TX */ + u32 timeout; +}; + +struct conf_pm_config_settings { + /* + * Host clock settling time + * + * Range: 0 - 30000 us + */ + u32 host_clk_settling_time; + + /* + * Host fast wakeup support + * + * Range: true, false + */ + bool host_fast_wakeup_support; +}; + +struct conf_roam_trigger_settings { + /* + * The minimum interval between two trigger events. + * + * Range: 0 - 60000 ms + */ + u16 trigger_pacing; + + /* + * The weight for rssi/beacon average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_rssi_beacon; + + /* + * The weight for rssi/data frame average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_rssi_data; + + /* + * The weight for snr/beacon average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_snr_beacon; + + /* + * The weight for snr/data frame average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_snr_data; +}; + +struct conf_scan_settings { + /* + * The minimum time to wait on each channel for active scans + * + * Range: u32 tu/1000 + */ + u32 min_dwell_time_active; + + /* + * The maximum time to wait on each channel for active scans + * + * Range: u32 tu/1000 + */ + u32 max_dwell_time_active; + + /* + * The minimum time to wait on each channel for passive scans + * + * Range: u32 tu/1000 + */ + u32 min_dwell_time_passive; + + /* + * The maximum time to wait on each channel for passive scans + * + * Range: u32 tu/1000 + */ + u32 max_dwell_time_passive; + + /* + * Number of probe requests to transmit on each active scan channel + * + * Range: u8 + */ + u16 num_probe_reqs; + +}; + +struct conf_sched_scan_settings { + /* minimum time to wait on the channel for active scans (in TUs) */ + u16 min_dwell_time_active; + + /* maximum time to wait on the channel for active scans (in TUs) */ + u16 max_dwell_time_active; + + /* time to wait on the channel for passive scans (in TUs) */ + u32 dwell_time_passive; + + /* time to wait on the channel for DFS scans (in TUs) */ + u32 dwell_time_dfs; + + /* number of probe requests to send on each channel in active scans */ + u8 num_probe_reqs; + + /* RSSI threshold to be used for filtering */ + s8 rssi_threshold; + + /* SNR threshold to be used for filtering */ + s8 snr_threshold; +}; + +/* these are number of channels on the band divided by two, rounded up */ +#define CONF_TX_PWR_COMPENSATION_LEN_2 7 +#define CONF_TX_PWR_COMPENSATION_LEN_5 18 + +struct conf_rf_settings { + /* + * Per channel power compensation for 2.4GHz + * + * Range: s8 + */ + u8 tx_per_channel_power_compensation_2[CONF_TX_PWR_COMPENSATION_LEN_2]; + + /* + * Per channel power compensation for 5GHz + * + * Range: s8 + */ + u8 tx_per_channel_power_compensation_5[CONF_TX_PWR_COMPENSATION_LEN_5]; +}; + +struct conf_ht_setting { + u16 tx_ba_win_size; + u16 inactivity_timeout; +}; + +struct conf_memory_settings { + /* Number of stations supported in IBSS mode */ + u8 num_stations; + + /* Number of ssid profiles used in IBSS mode */ + u8 ssid_profiles; + + /* Number of memory buffers allocated to rx pool */ + u8 rx_block_num; + + /* Minimum number of blocks allocated to tx pool */ + u8 tx_min_block_num; + + /* Disable/Enable dynamic memory */ + u8 dynamic_memory; + + /* + * Minimum required free tx memory blocks in order to assure optimum + * performance + * + * Range: 0-120 + */ + u8 min_req_tx_blocks; + + /* + * Minimum required free rx memory blocks in order to assure optimum + * performance + * + * Range: 0-120 + */ + u8 min_req_rx_blocks; + + /* + * Minimum number of mem blocks (free+used) guaranteed for TX + * + * Range: 0-120 + */ + u8 tx_min; +}; + +struct conf_fm_coex { + u8 enable; + u8 swallow_period; + u8 n_divider_fref_set_1; + u8 n_divider_fref_set_2; + u16 m_divider_fref_set_1; + u16 m_divider_fref_set_2; + u32 coex_pll_stabilization_time; + u16 ldo_stabilization_time; + u8 fm_disturbed_band_margin; + u8 swallow_clk_diff; +}; + +struct conf_drv_settings { + struct conf_sg_settings sg; + struct conf_rx_settings rx; + struct conf_tx_settings tx; + struct conf_conn_settings conn; + struct conf_itrim_settings itrim; + struct conf_pm_config_settings pm_config; + struct conf_roam_trigger_settings roam_trigger; + struct conf_scan_settings scan; + struct conf_sched_scan_settings sched_scan; + struct conf_rf_settings rf; + struct conf_ht_setting ht; + struct conf_memory_settings mem_wl127x; + struct conf_memory_settings mem_wl128x; + struct conf_fm_coex fm_coex; + u8 hci_io_ds; +}; + +#endif diff --git a/drivers/net/wireless/wl12xx/debugfs.c b/drivers/net/wireless/wl12xx/debugfs.c new file mode 100644 index 00000000..f1f8df9b --- /dev/null +++ b/drivers/net/wireless/wl12xx/debugfs.c @@ -0,0 +1,709 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "debugfs.h" + +#include <linux/skbuff.h> +#include <linux/slab.h> + +#include "wl12xx.h" +#include "acx.h" +#include "ps.h" +#include "io.h" + +/* ms */ +#define WL1271_DEBUGFS_STATS_LIFETIME 1000 + +/* debugfs macros idea from mac80211 */ +#define DEBUGFS_FORMAT_BUFFER_SIZE 100 +static int wl1271_format_buffer(char __user *userbuf, size_t count, + loff_t *ppos, char *fmt, ...) +{ + va_list args; + char buf[DEBUGFS_FORMAT_BUFFER_SIZE]; + int res; + + va_start(args, fmt); + res = vscnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + return simple_read_from_buffer(userbuf, count, ppos, buf, res); +} + +#define DEBUGFS_READONLY_FILE(name, fmt, value...) \ +static ssize_t name## _read(struct file *file, char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1271 *wl = file->private_data; \ + return wl1271_format_buffer(userbuf, count, ppos, \ + fmt "\n", ##value); \ +} \ + \ +static const struct file_operations name## _ops = { \ + .read = name## _read, \ + .open = wl1271_open_file_generic, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_ADD(name, parent) \ + entry = debugfs_create_file(#name, 0400, parent, \ + wl, &name## _ops); \ + if (!entry || IS_ERR(entry)) \ + goto err; \ + +#define DEBUGFS_FWSTATS_FILE(sub, name, fmt) \ +static ssize_t sub## _ ##name## _read(struct file *file, \ + char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1271 *wl = file->private_data; \ + \ + wl1271_debugfs_update_stats(wl); \ + \ + return wl1271_format_buffer(userbuf, count, ppos, fmt "\n", \ + wl->stats.fw_stats->sub.name); \ +} \ + \ +static const struct file_operations sub## _ ##name## _ops = { \ + .read = sub## _ ##name## _read, \ + .open = wl1271_open_file_generic, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_FWSTATS_ADD(sub, name) \ + DEBUGFS_ADD(sub## _ ##name, stats) + +static void wl1271_debugfs_update_stats(struct wl1271 *wl) +{ + int ret; + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (wl->state == WL1271_STATE_ON && + time_after(jiffies, wl->stats.fw_stats_update + + msecs_to_jiffies(WL1271_DEBUGFS_STATS_LIFETIME))) { + wl1271_acx_statistics(wl, wl->stats.fw_stats); + wl->stats.fw_stats_update = jiffies; + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +static int wl1271_open_file_generic(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +DEBUGFS_FWSTATS_FILE(tx, internal_desc_overflow, "%u"); + +DEBUGFS_FWSTATS_FILE(rx, out_of_mem, "%u"); +DEBUGFS_FWSTATS_FILE(rx, hdr_overflow, "%u"); +DEBUGFS_FWSTATS_FILE(rx, hw_stuck, "%u"); +DEBUGFS_FWSTATS_FILE(rx, dropped, "%u"); +DEBUGFS_FWSTATS_FILE(rx, fcs_err, "%u"); +DEBUGFS_FWSTATS_FILE(rx, xfr_hint_trig, "%u"); +DEBUGFS_FWSTATS_FILE(rx, path_reset, "%u"); +DEBUGFS_FWSTATS_FILE(rx, reset_counter, "%u"); + +DEBUGFS_FWSTATS_FILE(dma, rx_requested, "%u"); +DEBUGFS_FWSTATS_FILE(dma, rx_errors, "%u"); +DEBUGFS_FWSTATS_FILE(dma, tx_requested, "%u"); +DEBUGFS_FWSTATS_FILE(dma, tx_errors, "%u"); + +DEBUGFS_FWSTATS_FILE(isr, cmd_cmplt, "%u"); +DEBUGFS_FWSTATS_FILE(isr, fiqs, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_headers, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_mem_overflow, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_rdys, "%u"); +DEBUGFS_FWSTATS_FILE(isr, irqs, "%u"); +DEBUGFS_FWSTATS_FILE(isr, tx_procs, "%u"); +DEBUGFS_FWSTATS_FILE(isr, decrypt_done, "%u"); +DEBUGFS_FWSTATS_FILE(isr, dma0_done, "%u"); +DEBUGFS_FWSTATS_FILE(isr, dma1_done, "%u"); +DEBUGFS_FWSTATS_FILE(isr, tx_exch_complete, "%u"); +DEBUGFS_FWSTATS_FILE(isr, commands, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_procs, "%u"); +DEBUGFS_FWSTATS_FILE(isr, hw_pm_mode_changes, "%u"); +DEBUGFS_FWSTATS_FILE(isr, host_acknowledges, "%u"); +DEBUGFS_FWSTATS_FILE(isr, pci_pm, "%u"); +DEBUGFS_FWSTATS_FILE(isr, wakeups, "%u"); +DEBUGFS_FWSTATS_FILE(isr, low_rssi, "%u"); + +DEBUGFS_FWSTATS_FILE(wep, addr_key_count, "%u"); +DEBUGFS_FWSTATS_FILE(wep, default_key_count, "%u"); +/* skipping wep.reserved */ +DEBUGFS_FWSTATS_FILE(wep, key_not_found, "%u"); +DEBUGFS_FWSTATS_FILE(wep, decrypt_fail, "%u"); +DEBUGFS_FWSTATS_FILE(wep, packets, "%u"); +DEBUGFS_FWSTATS_FILE(wep, interrupt, "%u"); + +DEBUGFS_FWSTATS_FILE(pwr, ps_enter, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, elp_enter, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, missing_bcns, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, wake_on_host, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, wake_on_timer_exp, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, tx_with_ps, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, tx_without_ps, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, rcvd_beacons, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, power_save_off, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, enable_ps, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, disable_ps, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, fix_tsf_ps, "%u"); +/* skipping cont_miss_bcns_spread for now */ +DEBUGFS_FWSTATS_FILE(pwr, rcvd_awake_beacons, "%u"); + +DEBUGFS_FWSTATS_FILE(mic, rx_pkts, "%u"); +DEBUGFS_FWSTATS_FILE(mic, calc_failure, "%u"); + +DEBUGFS_FWSTATS_FILE(aes, encrypt_fail, "%u"); +DEBUGFS_FWSTATS_FILE(aes, decrypt_fail, "%u"); +DEBUGFS_FWSTATS_FILE(aes, encrypt_packets, "%u"); +DEBUGFS_FWSTATS_FILE(aes, decrypt_packets, "%u"); +DEBUGFS_FWSTATS_FILE(aes, encrypt_interrupt, "%u"); +DEBUGFS_FWSTATS_FILE(aes, decrypt_interrupt, "%u"); + +DEBUGFS_FWSTATS_FILE(event, heart_beat, "%u"); +DEBUGFS_FWSTATS_FILE(event, calibration, "%u"); +DEBUGFS_FWSTATS_FILE(event, rx_mismatch, "%u"); +DEBUGFS_FWSTATS_FILE(event, rx_mem_empty, "%u"); +DEBUGFS_FWSTATS_FILE(event, rx_pool, "%u"); +DEBUGFS_FWSTATS_FILE(event, oom_late, "%u"); +DEBUGFS_FWSTATS_FILE(event, phy_transmit_error, "%u"); +DEBUGFS_FWSTATS_FILE(event, tx_stuck, "%u"); + +DEBUGFS_FWSTATS_FILE(ps, pspoll_timeouts, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_timeouts, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_max_sptime, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_max_apturn, "%u"); +DEBUGFS_FWSTATS_FILE(ps, pspoll_max_apturn, "%u"); +DEBUGFS_FWSTATS_FILE(ps, pspoll_utilization, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_utilization, "%u"); + +DEBUGFS_FWSTATS_FILE(rxpipe, rx_prep_beacon_drop, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, descr_host_int_trig_rx_data, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, beacon_buffer_thres_host_int_trig_rx_data, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, missed_beacon_host_int_trig_rx_data, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, tx_xfr_host_int_trig_rx_data, "%u"); + +DEBUGFS_READONLY_FILE(retry_count, "%u", wl->stats.retry_count); +DEBUGFS_READONLY_FILE(excessive_retries, "%u", + wl->stats.excessive_retries); + +static ssize_t tx_queue_len_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u32 queue_len; + char buf[20]; + int res; + + queue_len = wl->tx_queue_count; + + res = scnprintf(buf, sizeof(buf), "%u\n", queue_len); + return simple_read_from_buffer(userbuf, count, ppos, buf, res); +} + +static const struct file_operations tx_queue_len_ops = { + .read = tx_queue_len_read, + .open = wl1271_open_file_generic, + .llseek = default_llseek, +}; + +static ssize_t gpio_power_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + bool state = test_bit(WL1271_FLAG_GPIO_POWER, &wl->flags); + + int res; + char buf[10]; + + res = scnprintf(buf, sizeof(buf), "%d\n", state); + + return simple_read_from_buffer(user_buf, count, ppos, buf, res); +} + +static ssize_t gpio_power_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + char buf[10]; + size_t len; + unsigned long value; + int ret; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) { + return -EFAULT; + } + buf[len] = '\0'; + + ret = kstrtoul(buf, 0, &value); + if (ret < 0) { + wl1271_warning("illegal value in gpio_power"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + + if (value) + wl1271_power_on(wl); + else + wl1271_power_off(wl); + + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations gpio_power_ops = { + .read = gpio_power_read, + .write = gpio_power_write, + .open = wl1271_open_file_generic, + .llseek = default_llseek, +}; + +static ssize_t start_recovery_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + mutex_lock(&wl->mutex); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + mutex_unlock(&wl->mutex); + + return count; +} + +static const struct file_operations start_recovery_ops = { + .write = start_recovery_write, + .open = wl1271_open_file_generic, + .llseek = default_llseek, +}; + +static ssize_t driver_state_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + int res = 0; + char buf[1024]; + + mutex_lock(&wl->mutex); + +#define DRIVER_STATE_PRINT(x, fmt) \ + (res += scnprintf(buf + res, sizeof(buf) - res,\ + #x " = " fmt "\n", wl->x)) + +#define DRIVER_STATE_PRINT_LONG(x) DRIVER_STATE_PRINT(x, "%ld") +#define DRIVER_STATE_PRINT_INT(x) DRIVER_STATE_PRINT(x, "%d") +#define DRIVER_STATE_PRINT_STR(x) DRIVER_STATE_PRINT(x, "%s") +#define DRIVER_STATE_PRINT_LHEX(x) DRIVER_STATE_PRINT(x, "0x%lx") +#define DRIVER_STATE_PRINT_HEX(x) DRIVER_STATE_PRINT(x, "0x%x") + + DRIVER_STATE_PRINT_INT(tx_blocks_available); + DRIVER_STATE_PRINT_INT(tx_allocated_blocks); + DRIVER_STATE_PRINT_INT(tx_frames_cnt); + DRIVER_STATE_PRINT_LHEX(tx_frames_map[0]); + DRIVER_STATE_PRINT_INT(tx_queue_count); + DRIVER_STATE_PRINT_INT(tx_packets_count); + DRIVER_STATE_PRINT_INT(tx_results_count); + DRIVER_STATE_PRINT_LHEX(flags); + DRIVER_STATE_PRINT_INT(tx_blocks_freed[0]); + DRIVER_STATE_PRINT_INT(tx_blocks_freed[1]); + DRIVER_STATE_PRINT_INT(tx_blocks_freed[2]); + DRIVER_STATE_PRINT_INT(tx_blocks_freed[3]); + DRIVER_STATE_PRINT_INT(tx_security_last_seq); + DRIVER_STATE_PRINT_INT(rx_counter); + DRIVER_STATE_PRINT_INT(session_counter); + DRIVER_STATE_PRINT_INT(state); + DRIVER_STATE_PRINT_INT(bss_type); + DRIVER_STATE_PRINT_INT(channel); + DRIVER_STATE_PRINT_HEX(rate_set); + DRIVER_STATE_PRINT_HEX(basic_rate_set); + DRIVER_STATE_PRINT_HEX(basic_rate); + DRIVER_STATE_PRINT_INT(band); + DRIVER_STATE_PRINT_INT(beacon_int); + DRIVER_STATE_PRINT_INT(psm_entry_retry); + DRIVER_STATE_PRINT_INT(ps_poll_failures); + DRIVER_STATE_PRINT_HEX(filters); + DRIVER_STATE_PRINT_HEX(rx_config); + DRIVER_STATE_PRINT_HEX(rx_filter); + DRIVER_STATE_PRINT_INT(power_level); + DRIVER_STATE_PRINT_INT(rssi_thold); + DRIVER_STATE_PRINT_INT(last_rssi_event); + DRIVER_STATE_PRINT_INT(sg_enabled); + DRIVER_STATE_PRINT_INT(enable_11a); + DRIVER_STATE_PRINT_INT(noise); + DRIVER_STATE_PRINT_LHEX(ap_hlid_map[0]); + DRIVER_STATE_PRINT_INT(last_tx_hlid); + DRIVER_STATE_PRINT_INT(ba_support); + DRIVER_STATE_PRINT_HEX(ba_rx_bitmap); + DRIVER_STATE_PRINT_HEX(ap_fw_ps_map); + DRIVER_STATE_PRINT_LHEX(ap_ps_map); + DRIVER_STATE_PRINT_HEX(quirks); + DRIVER_STATE_PRINT_HEX(irq); + DRIVER_STATE_PRINT_HEX(ref_clock); + DRIVER_STATE_PRINT_HEX(tcxo_clock); + DRIVER_STATE_PRINT_HEX(hw_pg_ver); + DRIVER_STATE_PRINT_HEX(platform_quirks); + DRIVER_STATE_PRINT_HEX(chip.id); + DRIVER_STATE_PRINT_STR(chip.fw_ver_str); + DRIVER_STATE_PRINT_INT(sched_scanning); + +#undef DRIVER_STATE_PRINT_INT +#undef DRIVER_STATE_PRINT_LONG +#undef DRIVER_STATE_PRINT_HEX +#undef DRIVER_STATE_PRINT_LHEX +#undef DRIVER_STATE_PRINT_STR +#undef DRIVER_STATE_PRINT + + mutex_unlock(&wl->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, res); +} + +static const struct file_operations driver_state_ops = { + .read = driver_state_read, + .open = wl1271_open_file_generic, + .llseek = default_llseek, +}; + +static ssize_t dtim_interval_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u8 value; + + if (wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_DTIM || + wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_N_DTIM) + value = wl->conf.conn.listen_interval; + else + value = 0; + + return wl1271_format_buffer(user_buf, count, ppos, "%d\n", value); +} + +static ssize_t dtim_interval_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + char buf[10]; + size_t len; + unsigned long value; + int ret; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + ret = kstrtoul(buf, 0, &value); + if (ret < 0) { + wl1271_warning("illegal value for dtim_interval"); + return -EINVAL; + } + + if (value < 1 || value > 10) { + wl1271_warning("dtim value is not in valid range"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.listen_interval = value; + /* for some reason there are different event types for 1 and >1 */ + if (value == 1) + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_DTIM; + else + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_N_DTIM; + + /* + * we don't reconfigure ACX_WAKE_UP_CONDITIONS now, so it will only + * take effect on the next time we enter psm. + */ + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations dtim_interval_ops = { + .read = dtim_interval_read, + .write = dtim_interval_write, + .open = wl1271_open_file_generic, + .llseek = default_llseek, +}; + +static ssize_t beacon_interval_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u8 value; + + if (wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_BEACON || + wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_N_BEACONS) + value = wl->conf.conn.listen_interval; + else + value = 0; + + return wl1271_format_buffer(user_buf, count, ppos, "%d\n", value); +} + +static ssize_t beacon_interval_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + char buf[10]; + size_t len; + unsigned long value; + int ret; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + ret = kstrtoul(buf, 0, &value); + if (ret < 0) { + wl1271_warning("illegal value for beacon_interval"); + return -EINVAL; + } + + if (value < 1 || value > 255) { + wl1271_warning("beacon interval value is not in valid range"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.listen_interval = value; + /* for some reason there are different event types for 1 and >1 */ + if (value == 1) + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_BEACON; + else + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_N_BEACONS; + + /* + * we don't reconfigure ACX_WAKE_UP_CONDITIONS now, so it will only + * take effect on the next time we enter psm. + */ + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations beacon_interval_ops = { + .read = beacon_interval_read, + .write = beacon_interval_write, + .open = wl1271_open_file_generic, + .llseek = default_llseek, +}; + +static int wl1271_debugfs_add_files(struct wl1271 *wl, + struct dentry *rootdir) +{ + int ret = 0; + struct dentry *entry, *stats; + + stats = debugfs_create_dir("fw-statistics", rootdir); + if (!stats || IS_ERR(stats)) { + entry = stats; + goto err; + } + + DEBUGFS_FWSTATS_ADD(tx, internal_desc_overflow); + + DEBUGFS_FWSTATS_ADD(rx, out_of_mem); + DEBUGFS_FWSTATS_ADD(rx, hdr_overflow); + DEBUGFS_FWSTATS_ADD(rx, hw_stuck); + DEBUGFS_FWSTATS_ADD(rx, dropped); + DEBUGFS_FWSTATS_ADD(rx, fcs_err); + DEBUGFS_FWSTATS_ADD(rx, xfr_hint_trig); + DEBUGFS_FWSTATS_ADD(rx, path_reset); + DEBUGFS_FWSTATS_ADD(rx, reset_counter); + + DEBUGFS_FWSTATS_ADD(dma, rx_requested); + DEBUGFS_FWSTATS_ADD(dma, rx_errors); + DEBUGFS_FWSTATS_ADD(dma, tx_requested); + DEBUGFS_FWSTATS_ADD(dma, tx_errors); + + DEBUGFS_FWSTATS_ADD(isr, cmd_cmplt); + DEBUGFS_FWSTATS_ADD(isr, fiqs); + DEBUGFS_FWSTATS_ADD(isr, rx_headers); + DEBUGFS_FWSTATS_ADD(isr, rx_mem_overflow); + DEBUGFS_FWSTATS_ADD(isr, rx_rdys); + DEBUGFS_FWSTATS_ADD(isr, irqs); + DEBUGFS_FWSTATS_ADD(isr, tx_procs); + DEBUGFS_FWSTATS_ADD(isr, decrypt_done); + DEBUGFS_FWSTATS_ADD(isr, dma0_done); + DEBUGFS_FWSTATS_ADD(isr, dma1_done); + DEBUGFS_FWSTATS_ADD(isr, tx_exch_complete); + DEBUGFS_FWSTATS_ADD(isr, commands); + DEBUGFS_FWSTATS_ADD(isr, rx_procs); + DEBUGFS_FWSTATS_ADD(isr, hw_pm_mode_changes); + DEBUGFS_FWSTATS_ADD(isr, host_acknowledges); + DEBUGFS_FWSTATS_ADD(isr, pci_pm); + DEBUGFS_FWSTATS_ADD(isr, wakeups); + DEBUGFS_FWSTATS_ADD(isr, low_rssi); + + DEBUGFS_FWSTATS_ADD(wep, addr_key_count); + DEBUGFS_FWSTATS_ADD(wep, default_key_count); + /* skipping wep.reserved */ + DEBUGFS_FWSTATS_ADD(wep, key_not_found); + DEBUGFS_FWSTATS_ADD(wep, decrypt_fail); + DEBUGFS_FWSTATS_ADD(wep, packets); + DEBUGFS_FWSTATS_ADD(wep, interrupt); + + DEBUGFS_FWSTATS_ADD(pwr, ps_enter); + DEBUGFS_FWSTATS_ADD(pwr, elp_enter); + DEBUGFS_FWSTATS_ADD(pwr, missing_bcns); + DEBUGFS_FWSTATS_ADD(pwr, wake_on_host); + DEBUGFS_FWSTATS_ADD(pwr, wake_on_timer_exp); + DEBUGFS_FWSTATS_ADD(pwr, tx_with_ps); + DEBUGFS_FWSTATS_ADD(pwr, tx_without_ps); + DEBUGFS_FWSTATS_ADD(pwr, rcvd_beacons); + DEBUGFS_FWSTATS_ADD(pwr, power_save_off); + DEBUGFS_FWSTATS_ADD(pwr, enable_ps); + DEBUGFS_FWSTATS_ADD(pwr, disable_ps); + DEBUGFS_FWSTATS_ADD(pwr, fix_tsf_ps); + /* skipping cont_miss_bcns_spread for now */ + DEBUGFS_FWSTATS_ADD(pwr, rcvd_awake_beacons); + + DEBUGFS_FWSTATS_ADD(mic, rx_pkts); + DEBUGFS_FWSTATS_ADD(mic, calc_failure); + + DEBUGFS_FWSTATS_ADD(aes, encrypt_fail); + DEBUGFS_FWSTATS_ADD(aes, decrypt_fail); + DEBUGFS_FWSTATS_ADD(aes, encrypt_packets); + DEBUGFS_FWSTATS_ADD(aes, decrypt_packets); + DEBUGFS_FWSTATS_ADD(aes, encrypt_interrupt); + DEBUGFS_FWSTATS_ADD(aes, decrypt_interrupt); + + DEBUGFS_FWSTATS_ADD(event, heart_beat); + DEBUGFS_FWSTATS_ADD(event, calibration); + DEBUGFS_FWSTATS_ADD(event, rx_mismatch); + DEBUGFS_FWSTATS_ADD(event, rx_mem_empty); + DEBUGFS_FWSTATS_ADD(event, rx_pool); + DEBUGFS_FWSTATS_ADD(event, oom_late); + DEBUGFS_FWSTATS_ADD(event, phy_transmit_error); + DEBUGFS_FWSTATS_ADD(event, tx_stuck); + + DEBUGFS_FWSTATS_ADD(ps, pspoll_timeouts); + DEBUGFS_FWSTATS_ADD(ps, upsd_timeouts); + DEBUGFS_FWSTATS_ADD(ps, upsd_max_sptime); + DEBUGFS_FWSTATS_ADD(ps, upsd_max_apturn); + DEBUGFS_FWSTATS_ADD(ps, pspoll_max_apturn); + DEBUGFS_FWSTATS_ADD(ps, pspoll_utilization); + DEBUGFS_FWSTATS_ADD(ps, upsd_utilization); + + DEBUGFS_FWSTATS_ADD(rxpipe, rx_prep_beacon_drop); + DEBUGFS_FWSTATS_ADD(rxpipe, descr_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, beacon_buffer_thres_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, missed_beacon_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, tx_xfr_host_int_trig_rx_data); + + DEBUGFS_ADD(tx_queue_len, rootdir); + DEBUGFS_ADD(retry_count, rootdir); + DEBUGFS_ADD(excessive_retries, rootdir); + + DEBUGFS_ADD(gpio_power, rootdir); + DEBUGFS_ADD(start_recovery, rootdir); + DEBUGFS_ADD(driver_state, rootdir); + DEBUGFS_ADD(dtim_interval, rootdir); + DEBUGFS_ADD(beacon_interval, rootdir); + + return 0; + +err: + if (IS_ERR(entry)) + ret = PTR_ERR(entry); + else + ret = -ENOMEM; + + return ret; +} + +void wl1271_debugfs_reset(struct wl1271 *wl) +{ + if (!wl->stats.fw_stats) + return; + + memset(wl->stats.fw_stats, 0, sizeof(*wl->stats.fw_stats)); + wl->stats.retry_count = 0; + wl->stats.excessive_retries = 0; +} + +int wl1271_debugfs_init(struct wl1271 *wl) +{ + int ret; + struct dentry *rootdir; + + rootdir = debugfs_create_dir(KBUILD_MODNAME, + wl->hw->wiphy->debugfsdir); + + if (IS_ERR(rootdir)) { + ret = PTR_ERR(rootdir); + goto err; + } + + wl->stats.fw_stats = kzalloc(sizeof(*wl->stats.fw_stats), + GFP_KERNEL); + + if (!wl->stats.fw_stats) { + ret = -ENOMEM; + goto err_fw; + } + + wl->stats.fw_stats_update = jiffies; + + ret = wl1271_debugfs_add_files(wl, rootdir); + + if (ret < 0) + goto err_file; + + return 0; + +err_file: + kfree(wl->stats.fw_stats); + wl->stats.fw_stats = NULL; + +err_fw: + debugfs_remove_recursive(rootdir); + +err: + return ret; +} + +void wl1271_debugfs_exit(struct wl1271 *wl) +{ + kfree(wl->stats.fw_stats); + wl->stats.fw_stats = NULL; +} diff --git a/drivers/net/wireless/wl12xx/debugfs.h b/drivers/net/wireless/wl12xx/debugfs.h new file mode 100644 index 00000000..254c5b29 --- /dev/null +++ b/drivers/net/wireless/wl12xx/debugfs.h @@ -0,0 +1,33 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __DEBUGFS_H__ +#define __DEBUGFS_H__ + +#include "wl12xx.h" + +int wl1271_debugfs_init(struct wl1271 *wl); +void wl1271_debugfs_exit(struct wl1271 *wl); +void wl1271_debugfs_reset(struct wl1271 *wl); + +#endif /* WL1271_DEBUGFS_H */ diff --git a/drivers/net/wireless/wl12xx/event.c b/drivers/net/wireless/wl12xx/event.c new file mode 100644 index 00000000..c3c554cd --- /dev/null +++ b/drivers/net/wireless/wl12xx/event.c @@ -0,0 +1,310 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "wl12xx.h" +#include "reg.h" +#include "io.h" +#include "event.h" +#include "ps.h" +#include "scan.h" +#include "wl12xx_80211.h" + +void wl1271_pspoll_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + int ret; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, pspoll_work); + + wl1271_debug(DEBUG_EVENT, "pspoll work"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + if (!test_and_clear_bit(WL1271_FLAG_PSPOLL_FAILURE, &wl->flags)) + goto out; + + if (!test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) + goto out; + + /* + * if we end up here, then we were in powersave when the pspoll + * delivery failure occurred, and no-one changed state since, so + * we should go back to powersave. + */ + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_ps_set_mode(wl, STATION_POWER_SAVE_MODE, wl->basic_rate, true); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +}; + +static void wl1271_event_pspoll_delivery_fail(struct wl1271 *wl) +{ + int delay = wl->conf.conn.ps_poll_recovery_period; + int ret; + + wl->ps_poll_failures++; + if (wl->ps_poll_failures == 1) + wl1271_info("AP with dysfunctional ps-poll, " + "trying to work around it."); + + /* force active mode receive data from the AP */ + if (test_bit(WL1271_FLAG_PSM, &wl->flags)) { + ret = wl1271_ps_set_mode(wl, STATION_ACTIVE_MODE, + wl->basic_rate, true); + if (ret < 0) + return; + set_bit(WL1271_FLAG_PSPOLL_FAILURE, &wl->flags); + ieee80211_queue_delayed_work(wl->hw, &wl->pspoll_work, + msecs_to_jiffies(delay)); + } + + /* + * If already in active mode, lets we should be getting data from + * the AP right away. If we enter PSM too fast after this, and data + * remains on the AP, we will get another event like this, and we'll + * go into active once more. + */ +} + +static int wl1271_event_ps_report(struct wl1271 *wl, + struct event_mailbox *mbox, + bool *beacon_loss) +{ + int ret = 0; + u32 total_retries = wl->conf.conn.psm_entry_retries; + + wl1271_debug(DEBUG_EVENT, "ps_status: 0x%x", mbox->ps_status); + + switch (mbox->ps_status) { + case EVENT_ENTER_POWER_SAVE_FAIL: + wl1271_debug(DEBUG_PSM, "PSM entry failed"); + + if (!test_bit(WL1271_FLAG_PSM, &wl->flags)) { + /* remain in active mode */ + wl->psm_entry_retry = 0; + break; + } + + if (wl->psm_entry_retry < total_retries) { + wl->psm_entry_retry++; + ret = wl1271_ps_set_mode(wl, STATION_POWER_SAVE_MODE, + wl->basic_rate, true); + } else { + wl1271_info("No ack to nullfunc from AP."); + wl->psm_entry_retry = 0; + *beacon_loss = true; + } + break; + case EVENT_ENTER_POWER_SAVE_SUCCESS: + wl->psm_entry_retry = 0; + + /* enable beacon filtering */ + ret = wl1271_acx_beacon_filter_opt(wl, true); + if (ret < 0) + break; + + /* enable beacon early termination */ + ret = wl1271_acx_bet_enable(wl, true); + if (ret < 0) + break; + + if (wl->ps_compl) { + complete(wl->ps_compl); + wl->ps_compl = NULL; + } + break; + default: + break; + } + + return ret; +} + +static void wl1271_event_rssi_trigger(struct wl1271 *wl, + struct event_mailbox *mbox) +{ + enum nl80211_cqm_rssi_threshold_event event; + s8 metric = mbox->rssi_snr_trigger_metric[0]; + + wl1271_debug(DEBUG_EVENT, "RSSI trigger metric: %d", metric); + + if (metric <= wl->rssi_thold) + event = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW; + else + event = NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH; + + if (event != wl->last_rssi_event) + ieee80211_cqm_rssi_notify(wl->vif, event, GFP_KERNEL); + wl->last_rssi_event = event; +} + +static void wl1271_event_mbox_dump(struct event_mailbox *mbox) +{ + wl1271_debug(DEBUG_EVENT, "MBOX DUMP:"); + wl1271_debug(DEBUG_EVENT, "\tvector: 0x%x", mbox->events_vector); + wl1271_debug(DEBUG_EVENT, "\tmask: 0x%x", mbox->events_mask); +} + +static int wl1271_event_process(struct wl1271 *wl, struct event_mailbox *mbox) +{ + int ret; + u32 vector; + bool beacon_loss = false; + bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + + wl1271_event_mbox_dump(mbox); + + vector = le32_to_cpu(mbox->events_vector); + vector &= ~(le32_to_cpu(mbox->events_mask)); + wl1271_debug(DEBUG_EVENT, "vector: 0x%x", vector); + + if (vector & SCAN_COMPLETE_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "status: 0x%x", + mbox->scheduled_scan_status); + + wl1271_scan_stm(wl); + } + + if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_REPORT_EVENT " + "(status 0x%0x)", mbox->scheduled_scan_status); + + wl1271_scan_sched_scan_results(wl); + } + + if (vector & PERIODIC_SCAN_COMPLETE_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_COMPLETE_EVENT " + "(status 0x%0x)", mbox->scheduled_scan_status); + if (wl->sched_scanning) { + wl1271_scan_sched_scan_stop(wl); + ieee80211_sched_scan_stopped(wl->hw); + } + } + + /* disable dynamic PS when requested by the firmware */ + if (vector & SOFT_GEMINI_SENSE_EVENT_ID && + wl->bss_type == BSS_TYPE_STA_BSS) { + if (mbox->soft_gemini_sense_info) + ieee80211_disable_dyn_ps(wl->vif); + else + ieee80211_enable_dyn_ps(wl->vif); + } + + /* + * The BSS_LOSE_EVENT_ID is only needed while psm (and hence beacon + * filtering) is enabled. Without PSM, the stack will receive all + * beacons and can detect beacon loss by itself. + * + * As there's possibility that the driver disables PSM before receiving + * BSS_LOSE_EVENT, beacon loss has to be reported to the stack. + * + */ + if ((vector & BSS_LOSE_EVENT_ID) && !is_ap) { + wl1271_info("Beacon loss detected."); + + /* indicate to the stack, that beacons have been lost */ + beacon_loss = true; + } + + if ((vector & PS_REPORT_EVENT_ID) && !is_ap) { + wl1271_debug(DEBUG_EVENT, "PS_REPORT_EVENT"); + ret = wl1271_event_ps_report(wl, mbox, &beacon_loss); + if (ret < 0) + return ret; + } + + if ((vector & PSPOLL_DELIVERY_FAILURE_EVENT_ID) && !is_ap) + wl1271_event_pspoll_delivery_fail(wl); + + if (vector & RSSI_SNR_TRIGGER_0_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "RSSI_SNR_TRIGGER_0_EVENT"); + if (wl->vif) + wl1271_event_rssi_trigger(wl, mbox); + } + + if ((vector & DUMMY_PACKET_EVENT_ID) && !is_ap) { + wl1271_debug(DEBUG_EVENT, "DUMMY_PACKET_ID_EVENT_ID"); + if (wl->vif) + wl1271_tx_dummy_packet(wl); + } + + if (wl->vif && beacon_loss) + ieee80211_connection_loss(wl->vif); + + return 0; +} + +int wl1271_event_unmask(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_event_mbox_mask(wl, ~(wl->event_mask)); + if (ret < 0) + return ret; + + return 0; +} + +void wl1271_event_mbox_config(struct wl1271 *wl) +{ + wl->mbox_ptr[0] = wl1271_read32(wl, REG_EVENT_MAILBOX_PTR); + wl->mbox_ptr[1] = wl->mbox_ptr[0] + sizeof(struct event_mailbox); + + wl1271_debug(DEBUG_EVENT, "MBOX ptrs: 0x%x 0x%x", + wl->mbox_ptr[0], wl->mbox_ptr[1]); +} + +int wl1271_event_handle(struct wl1271 *wl, u8 mbox_num) +{ + struct event_mailbox mbox; + int ret; + + wl1271_debug(DEBUG_EVENT, "EVENT on mbox %d", mbox_num); + + if (mbox_num > 1) + return -EINVAL; + + /* first we read the mbox descriptor */ + wl1271_read(wl, wl->mbox_ptr[mbox_num], &mbox, + sizeof(struct event_mailbox), false); + + /* process the descriptor */ + ret = wl1271_event_process(wl, &mbox); + if (ret < 0) + return ret; + + /* then we let the firmware know it can go on...*/ + wl1271_write32(wl, ACX_REG_INTERRUPT_TRIG, INTR_TRIG_EVENT_ACK); + + return 0; +} diff --git a/drivers/net/wireless/wl12xx/event.h b/drivers/net/wireless/wl12xx/event.h new file mode 100644 index 00000000..b6cf06e5 --- /dev/null +++ b/drivers/net/wireless/wl12xx/event.h @@ -0,0 +1,133 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __EVENT_H__ +#define __EVENT_H__ + +/* + * Mbox events + * + * The event mechanism is based on a pair of event buffers (buffers A and + * B) at fixed locations in the target's memory. The host processes one + * buffer while the other buffer continues to collect events. If the host + * is not processing events, an interrupt is issued to signal that a buffer + * is ready. Once the host is done with processing events from one buffer, + * it signals the target (with an ACK interrupt) that the event buffer is + * free. + */ + +enum { + RSSI_SNR_TRIGGER_0_EVENT_ID = BIT(0), + RSSI_SNR_TRIGGER_1_EVENT_ID = BIT(1), + RSSI_SNR_TRIGGER_2_EVENT_ID = BIT(2), + RSSI_SNR_TRIGGER_3_EVENT_ID = BIT(3), + RSSI_SNR_TRIGGER_4_EVENT_ID = BIT(4), + RSSI_SNR_TRIGGER_5_EVENT_ID = BIT(5), + RSSI_SNR_TRIGGER_6_EVENT_ID = BIT(6), + RSSI_SNR_TRIGGER_7_EVENT_ID = BIT(7), + MEASUREMENT_START_EVENT_ID = BIT(8), + MEASUREMENT_COMPLETE_EVENT_ID = BIT(9), + SCAN_COMPLETE_EVENT_ID = BIT(10), + SCHEDULED_SCAN_COMPLETE_EVENT_ID = BIT(11), + AP_DISCOVERY_COMPLETE_EVENT_ID = BIT(12), + PS_REPORT_EVENT_ID = BIT(13), + PSPOLL_DELIVERY_FAILURE_EVENT_ID = BIT(14), + DISCONNECT_EVENT_COMPLETE_ID = BIT(15), + JOIN_EVENT_COMPLETE_ID = BIT(16), + CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(17), + BSS_LOSE_EVENT_ID = BIT(18), + REGAINED_BSS_EVENT_ID = BIT(19), + ROAMING_TRIGGER_MAX_TX_RETRY_EVENT_ID = BIT(20), + /* STA: dummy paket for dynamic mem blocks */ + DUMMY_PACKET_EVENT_ID = BIT(21), + /* AP: STA remove complete */ + STA_REMOVE_COMPLETE_EVENT_ID = BIT(21), + SOFT_GEMINI_SENSE_EVENT_ID = BIT(22), + SOFT_GEMINI_PREDICTION_EVENT_ID = BIT(23), + SOFT_GEMINI_AVALANCHE_EVENT_ID = BIT(24), + PLT_RX_CALIBRATION_COMPLETE_EVENT_ID = BIT(25), + DBG_EVENT_ID = BIT(26), + HEALTH_CHECK_REPLY_EVENT_ID = BIT(27), + PERIODIC_SCAN_COMPLETE_EVENT_ID = BIT(28), + PERIODIC_SCAN_REPORT_EVENT_ID = BIT(29), + BA_SESSION_TEAR_DOWN_EVENT_ID = BIT(30), + EVENT_MBOX_ALL_EVENT_ID = 0x7fffffff, +}; + +enum { + EVENT_ENTER_POWER_SAVE_FAIL = 0, + EVENT_ENTER_POWER_SAVE_SUCCESS, +}; + +struct event_debug_report { + u8 debug_event_id; + u8 num_params; + __le16 pad; + __le32 report_1; + __le32 report_2; + __le32 report_3; +} __packed; + +#define NUM_OF_RSSI_SNR_TRIGGERS 8 + +struct event_mailbox { + __le32 events_vector; + __le32 events_mask; + __le32 reserved_1; + __le32 reserved_2; + + u8 dbg_event_id; + u8 num_relevant_params; + __le16 reserved_3; + __le32 event_report_p1; + __le32 event_report_p2; + __le32 event_report_p3; + + u8 number_of_scan_results; + u8 scan_tag; + u8 reserved_4[2]; + __le32 compl_scheduled_scan_status; + + __le16 scheduled_scan_attended_channels; + u8 soft_gemini_sense_info; + u8 soft_gemini_protective_info; + s8 rssi_snr_trigger_metric[NUM_OF_RSSI_SNR_TRIGGERS]; + u8 channel_switch_status; + u8 scheduled_scan_status; + u8 ps_status; + + /* AP FW only */ + u8 hlid_removed; + __le16 sta_aging_status; + __le16 sta_tx_retry_exceeded; + + u8 reserved_5[24]; +} __packed; + +int wl1271_event_unmask(struct wl1271 *wl); +void wl1271_event_mbox_config(struct wl1271 *wl); +int wl1271_event_handle(struct wl1271 *wl, u8 mbox); +void wl1271_pspoll_work(struct work_struct *work); + +#endif diff --git a/drivers/net/wireless/wl12xx/ini.h b/drivers/net/wireless/wl12xx/ini.h new file mode 100644 index 00000000..1420c842 --- /dev/null +++ b/drivers/net/wireless/wl12xx/ini.h @@ -0,0 +1,217 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __INI_H__ +#define __INI_H__ + +#define WL1271_INI_MAX_SMART_REFLEX_PARAM 16 + +struct wl1271_ini_general_params { + u8 ref_clock; + u8 settling_time; + u8 clk_valid_on_wakeup; + u8 dc2dc_mode; + u8 dual_mode_select; + u8 tx_bip_fem_auto_detect; + u8 tx_bip_fem_manufacturer; + u8 general_settings; + u8 sr_state; + u8 srf1[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf2[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf3[WL1271_INI_MAX_SMART_REFLEX_PARAM]; +} __packed; + +#define WL128X_INI_MAX_SETTINGS_PARAM 4 + +struct wl128x_ini_general_params { + u8 ref_clock; + u8 settling_time; + u8 clk_valid_on_wakeup; + u8 tcxo_ref_clock; + u8 tcxo_settling_time; + u8 tcxo_valid_on_wakeup; + u8 tcxo_ldo_voltage; + u8 xtal_itrim_val; + u8 platform_conf; + u8 dual_mode_select; + u8 tx_bip_fem_auto_detect; + u8 tx_bip_fem_manufacturer; + u8 general_settings[WL128X_INI_MAX_SETTINGS_PARAM]; + u8 sr_state; + u8 srf1[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf2[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf3[WL1271_INI_MAX_SMART_REFLEX_PARAM]; +} __packed; + +#define WL1271_INI_RSSI_PROCESS_COMPENS_SIZE 15 + +struct wl1271_ini_band_params_2 { + u8 rx_trace_insertion_loss; + u8 tx_trace_loss; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +#define WL1271_INI_CHANNEL_COUNT_2 14 + +struct wl128x_ini_band_params_2 { + u8 rx_trace_insertion_loss; + u8 tx_trace_loss[WL1271_INI_CHANNEL_COUNT_2]; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +#define WL1271_INI_RATE_GROUP_COUNT 6 + +struct wl1271_ini_fem_params_2 { + __le16 tx_bip_ref_pd_voltage; + u8 tx_bip_ref_power; + u8 tx_bip_ref_offset; + u8 tx_per_rate_pwr_limits_normal[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_11b[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_pd_vs_rate_offsets[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL1271_INI_RATE_GROUP_COUNT]; + u8 rx_fem_insertion_loss; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +#define WL128X_INI_RATE_GROUP_COUNT 7 +/* low and high temperatures */ +#define WL128X_INI_PD_VS_TEMPERATURE_RANGES 2 + +struct wl128x_ini_fem_params_2 { + __le16 tx_bip_ref_pd_voltage; + u8 tx_bip_ref_power; + u8 tx_bip_ref_offset; + u8 tx_per_rate_pwr_limits_normal[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_11b[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_pd_vs_rate_offsets[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL128X_INI_RATE_GROUP_COUNT + 1]; + u8 tx_pd_vs_chan_offsets[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_pd_vs_temperature[WL128X_INI_PD_VS_TEMPERATURE_RANGES]; + u8 rx_fem_insertion_loss; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +#define WL1271_INI_CHANNEL_COUNT_5 35 +#define WL1271_INI_SUB_BAND_COUNT_5 7 + +struct wl1271_ini_band_params_5 { + u8 rx_trace_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_trace_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +struct wl128x_ini_band_params_5 { + u8 rx_trace_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_trace_loss[WL1271_INI_CHANNEL_COUNT_5]; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +struct wl1271_ini_fem_params_5 { + __le16 tx_bip_ref_pd_voltage[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_power[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_offset[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_per_rate_pwr_limits_normal[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_5]; + u8 tx_pd_vs_rate_offsets[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL1271_INI_RATE_GROUP_COUNT]; + u8 rx_fem_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +struct wl128x_ini_fem_params_5 { + __le16 tx_bip_ref_pd_voltage[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_power[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_offset[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_per_rate_pwr_limits_normal[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_5]; + u8 tx_pd_vs_rate_offsets[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_pd_vs_chan_offsets[WL1271_INI_CHANNEL_COUNT_5]; + u8 tx_pd_vs_temperature[WL1271_INI_SUB_BAND_COUNT_5 * + WL128X_INI_PD_VS_TEMPERATURE_RANGES]; + u8 rx_fem_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +/* NVS data structure */ +#define WL1271_INI_NVS_SECTION_SIZE 468 +#define WL1271_INI_FEM_MODULE_COUNT 2 + +#define WL1271_INI_LEGACY_NVS_FILE_SIZE 800 + +struct wl1271_nvs_file { + /* NVS section - must be first! */ + u8 nvs[WL1271_INI_NVS_SECTION_SIZE]; + + /* INI section */ + struct wl1271_ini_general_params general_params; + u8 padding1; + struct wl1271_ini_band_params_2 stat_radio_params_2; + u8 padding2; + struct { + struct wl1271_ini_fem_params_2 params; + u8 padding; + } dyn_radio_params_2[WL1271_INI_FEM_MODULE_COUNT]; + struct wl1271_ini_band_params_5 stat_radio_params_5; + u8 padding3; + struct { + struct wl1271_ini_fem_params_5 params; + u8 padding; + } dyn_radio_params_5[WL1271_INI_FEM_MODULE_COUNT]; +} __packed; + +struct wl128x_nvs_file { + /* NVS section - must be first! */ + u8 nvs[WL1271_INI_NVS_SECTION_SIZE]; + + /* INI section */ + struct wl128x_ini_general_params general_params; + u8 fem_vendor_and_options; + struct wl128x_ini_band_params_2 stat_radio_params_2; + u8 padding2; + struct { + struct wl128x_ini_fem_params_2 params; + u8 padding; + } dyn_radio_params_2[WL1271_INI_FEM_MODULE_COUNT]; + struct wl128x_ini_band_params_5 stat_radio_params_5; + u8 padding3; + struct { + struct wl128x_ini_fem_params_5 params; + u8 padding; + } dyn_radio_params_5[WL1271_INI_FEM_MODULE_COUNT]; +} __packed; +#endif diff --git a/drivers/net/wireless/wl12xx/init.c b/drivers/net/wireless/wl12xx/init.c new file mode 100644 index 00000000..a8f4f156 --- /dev/null +++ b/drivers/net/wireless/wl12xx/init.c @@ -0,0 +1,723 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "init.h" +#include "wl12xx_80211.h" +#include "acx.h" +#include "cmd.h" +#include "reg.h" +#include "tx.h" +#include "io.h" + +int wl1271_sta_init_templates_config(struct wl1271 *wl) +{ + int ret, i; + + /* send empty templates for fw memory reservation */ + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_CFG_PROBE_REQ_2_4, NULL, + WL1271_CMD_TEMPL_MAX_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_CFG_PROBE_REQ_5, + NULL, WL1271_CMD_TEMPL_MAX_SIZE, 0, + WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_NULL_DATA, NULL, + sizeof(struct wl12xx_null_data_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_PS_POLL, NULL, + sizeof(struct wl12xx_ps_poll_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_QOS_NULL_DATA, NULL, + sizeof + (struct wl12xx_qos_null_data_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_PROBE_RESPONSE, NULL, + sizeof + (struct wl12xx_probe_resp_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_BEACON, NULL, + sizeof + (struct wl12xx_beacon_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_ARP_RSP, NULL, + sizeof + (struct wl12xx_arp_rsp_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + for (i = 0; i < CMD_TEMPL_KLV_IDX_MAX; i++) { + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_KLV, NULL, + WL1271_CMD_TEMPL_MAX_SIZE, i, + WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + } + + return 0; +} + +static int wl1271_ap_init_deauth_template(struct wl1271 *wl) +{ + struct wl12xx_disconn_template *tmpl; + int ret; + + tmpl = kzalloc(sizeof(*tmpl), GFP_KERNEL); + if (!tmpl) { + ret = -ENOMEM; + goto out; + } + + tmpl->header.frame_ctl = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_DEAUTH); + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_DEAUTH_AP, + tmpl, sizeof(*tmpl), 0, + wl1271_tx_min_rate_get(wl)); + +out: + kfree(tmpl); + return ret; +} + +static int wl1271_ap_init_null_template(struct wl1271 *wl) +{ + struct ieee80211_hdr_3addr *nullfunc; + int ret; + + nullfunc = kzalloc(sizeof(*nullfunc), GFP_KERNEL); + if (!nullfunc) { + ret = -ENOMEM; + goto out; + } + + nullfunc->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_NULLFUNC | + IEEE80211_FCTL_FROMDS); + + /* nullfunc->addr1 is filled by FW */ + + memcpy(nullfunc->addr2, wl->mac_addr, ETH_ALEN); + memcpy(nullfunc->addr3, wl->mac_addr, ETH_ALEN); + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_NULL_DATA, nullfunc, + sizeof(*nullfunc), 0, + wl1271_tx_min_rate_get(wl)); + +out: + kfree(nullfunc); + return ret; +} + +static int wl1271_ap_init_qos_null_template(struct wl1271 *wl) +{ + struct ieee80211_qos_hdr *qosnull; + int ret; + + qosnull = kzalloc(sizeof(*qosnull), GFP_KERNEL); + if (!qosnull) { + ret = -ENOMEM; + goto out; + } + + qosnull->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_NULLFUNC | + IEEE80211_FCTL_FROMDS); + + /* qosnull->addr1 is filled by FW */ + + memcpy(qosnull->addr2, wl->mac_addr, ETH_ALEN); + memcpy(qosnull->addr3, wl->mac_addr, ETH_ALEN); + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_QOS_NULL_DATA, qosnull, + sizeof(*qosnull), 0, + wl1271_tx_min_rate_get(wl)); + +out: + kfree(qosnull); + return ret; +} + +static int wl1271_ap_init_templates_config(struct wl1271 *wl) +{ + int ret; + + /* + * Put very large empty placeholders for all templates. These + * reserve memory for later. + */ + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_AP_PROBE_RESPONSE, NULL, + sizeof + (struct wl12xx_probe_resp_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_AP_BEACON, NULL, + sizeof + (struct wl12xx_beacon_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_DEAUTH_AP, NULL, + sizeof + (struct wl12xx_disconn_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_NULL_DATA, NULL, + sizeof(struct wl12xx_null_data_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, CMD_TEMPL_QOS_NULL_DATA, NULL, + sizeof + (struct wl12xx_qos_null_data_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_init_rx_config(struct wl1271 *wl, u32 config, u32 filter) +{ + int ret; + + ret = wl1271_acx_rx_msdu_life_time(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_rx_config(wl, config, filter); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_init_phy_config(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_pd_threshold(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_slot(wl, DEFAULT_SLOT_TIME); + if (ret < 0) + return ret; + + ret = wl1271_acx_service_period_timeout(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_rts_threshold(wl, wl->hw->wiphy->rts_threshold); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_init_beacon_filter(struct wl1271 *wl) +{ + int ret; + + /* disable beacon filtering at this stage */ + ret = wl1271_acx_beacon_filter_opt(wl, false); + if (ret < 0) + return ret; + + ret = wl1271_acx_beacon_filter_table(wl); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_init_pta(struct wl1271 *wl) +{ + int ret; + + if (wl->bss_type == BSS_TYPE_AP_BSS) + ret = wl1271_acx_ap_sg_cfg(wl); + else + ret = wl1271_acx_sta_sg_cfg(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_sg_enable(wl, wl->sg_enabled); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_init_energy_detection(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_cca_threshold(wl); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_init_beacon_broadcast(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_bcn_dtim_options(wl); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_sta_hw_init(struct wl1271 *wl) +{ + int ret; + + if (wl->chip.id != CHIP_ID_1283_PG20) { + ret = wl1271_cmd_ext_radio_parms(wl); + if (ret < 0) + return ret; + } + + /* PS config */ + ret = wl1271_acx_config_ps(wl); + if (ret < 0) + return ret; + + ret = wl1271_sta_init_templates_config(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_group_address_tbl(wl, true, NULL, 0); + if (ret < 0) + return ret; + + /* Initialize connection monitoring thresholds */ + ret = wl1271_acx_conn_monit_params(wl, false); + if (ret < 0) + return ret; + + /* Beacon filtering */ + ret = wl1271_init_beacon_filter(wl); + if (ret < 0) + return ret; + + /* FM WLAN coexistence */ + ret = wl1271_acx_fm_coex(wl); + if (ret < 0) + return ret; + + /* Beacons and broadcast settings */ + ret = wl1271_init_beacon_broadcast(wl); + if (ret < 0) + return ret; + + /* Configure for ELP power saving */ + ret = wl1271_acx_sleep_auth(wl, WL1271_PSM_ELP); + if (ret < 0) + return ret; + + /* Configure rssi/snr averaging weights */ + ret = wl1271_acx_rssi_snr_avg_weights(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_sta_rate_policies(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_sta_mem_cfg(wl); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_sta_hw_init_post_mem(struct wl1271 *wl) +{ + int ret, i; + + ret = wl1271_cmd_set_sta_default_wep_key(wl, wl->default_key); + if (ret < 0) { + wl1271_warning("couldn't set default key"); + return ret; + } + + /* disable all keep-alive templates */ + for (i = 0; i < CMD_TEMPL_KLV_IDX_MAX; i++) { + ret = wl1271_acx_keep_alive_config(wl, i, + ACX_KEEP_ALIVE_TPL_INVALID); + if (ret < 0) + return ret; + } + + /* disable the keep-alive feature */ + ret = wl1271_acx_keep_alive_mode(wl, false); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_ap_hw_init(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_ap_init_templates_config(wl); + if (ret < 0) + return ret; + + /* Configure for power always on */ + ret = wl1271_acx_sleep_auth(wl, WL1271_PSM_CAM); + if (ret < 0) + return ret; + + ret = wl1271_init_ap_rates(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_max_tx_retry(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_ap_mem_cfg(wl); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_ap_init_templates(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_ap_init_deauth_template(wl); + if (ret < 0) + return ret; + + ret = wl1271_ap_init_null_template(wl); + if (ret < 0) + return ret; + + ret = wl1271_ap_init_qos_null_template(wl); + if (ret < 0) + return ret; + + /* + * when operating as AP we want to receive external beacons for + * configuring ERP protection. + */ + ret = wl1271_acx_set_ap_beacon_filter(wl, false); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_ap_hw_init_post_mem(struct wl1271 *wl) +{ + return wl1271_ap_init_templates(wl); +} + +int wl1271_init_ap_rates(struct wl1271 *wl) +{ + int i, ret; + struct conf_tx_rate_class rc; + u32 supported_rates; + + wl1271_debug(DEBUG_AP, "AP basic rate set: 0x%x", wl->basic_rate_set); + + if (wl->basic_rate_set == 0) + return -EINVAL; + + rc.enabled_rates = wl->basic_rate_set; + rc.long_retry_limit = 10; + rc.short_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, ACX_TX_AP_MODE_MGMT_RATE); + if (ret < 0) + return ret; + + /* use the min basic rate for AP broadcast/multicast */ + rc.enabled_rates = wl1271_tx_min_rate_get(wl); + rc.short_retry_limit = 10; + rc.long_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, ACX_TX_AP_MODE_BCST_RATE); + if (ret < 0) + return ret; + + /* + * If the basic rates contain OFDM rates, use OFDM only + * rates for unicast TX as well. Else use all supported rates. + */ + if ((wl->basic_rate_set & CONF_TX_OFDM_RATES)) + supported_rates = CONF_TX_OFDM_RATES; + else + supported_rates = CONF_TX_AP_ENABLED_RATES; + + /* configure unicast TX rate classes */ + for (i = 0; i < wl->conf.tx.ac_conf_count; i++) { + rc.enabled_rates = supported_rates; + rc.short_retry_limit = 10; + rc.long_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static void wl1271_check_ba_support(struct wl1271 *wl) +{ + /* validate FW cose ver x.x.x.50-60.x */ + if ((wl->chip.fw_ver[3] >= WL12XX_BA_SUPPORT_FW_COST_VER2_START) && + (wl->chip.fw_ver[3] < WL12XX_BA_SUPPORT_FW_COST_VER2_END)) { + wl->ba_support = true; + return; + } + + wl->ba_support = false; +} + +static int wl1271_set_ba_policies(struct wl1271 *wl) +{ + u8 tid_index; + int ret = 0; + + /* Reset the BA RX indicators */ + wl->ba_rx_bitmap = 0; + + /* validate that FW support BA */ + wl1271_check_ba_support(wl); + + if (wl->ba_support) + /* 802.11n initiator BA session setting */ + for (tid_index = 0; tid_index < CONF_TX_MAX_TID_COUNT; + ++tid_index) { + ret = wl1271_acx_set_ba_session(wl, WLAN_BACK_INITIATOR, + tid_index, true); + if (ret < 0) + break; + } + + return ret; +} + +int wl1271_chip_specific_init(struct wl1271 *wl) +{ + int ret = 0; + + if (wl->chip.id == CHIP_ID_1283_PG20) { + u32 host_cfg_bitmap = HOST_IF_CFG_RX_FIFO_ENABLE; + + if (wl->quirks & WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT) + /* Enable SDIO padding */ + host_cfg_bitmap |= HOST_IF_CFG_TX_PAD_TO_SDIO_BLK; + + /* Must be before wl1271_acx_init_mem_config() */ + ret = wl1271_acx_host_if_cfg_bitmap(wl, host_cfg_bitmap); + if (ret < 0) + goto out; + } +out: + return ret; +} + + +int wl1271_hw_init(struct wl1271 *wl) +{ + struct conf_tx_ac_category *conf_ac; + struct conf_tx_tid *conf_tid; + int ret, i; + bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + + if (wl->chip.id == CHIP_ID_1283_PG20) + ret = wl128x_cmd_general_parms(wl); + else + ret = wl1271_cmd_general_parms(wl); + if (ret < 0) + return ret; + + if (wl->chip.id == CHIP_ID_1283_PG20) + ret = wl128x_cmd_radio_parms(wl); + else + ret = wl1271_cmd_radio_parms(wl); + if (ret < 0) + return ret; + + /* Chip-specific init */ + ret = wl1271_chip_specific_init(wl); + if (ret < 0) + return ret; + + /* Mode specific init */ + if (is_ap) + ret = wl1271_ap_hw_init(wl); + else + ret = wl1271_sta_hw_init(wl); + + if (ret < 0) + return ret; + + /* Bluetooth WLAN coexistence */ + ret = wl1271_init_pta(wl); + if (ret < 0) + return ret; + + /* Default memory configuration */ + ret = wl1271_acx_init_mem_config(wl); + if (ret < 0) + return ret; + + /* RX config */ + ret = wl1271_init_rx_config(wl, + RX_CFG_PROMISCUOUS | RX_CFG_TSF, + RX_FILTER_OPTION_DEF); + /* RX_CONFIG_OPTION_ANY_DST_ANY_BSS, + RX_FILTER_OPTION_FILTER_ALL); */ + if (ret < 0) + goto out_free_memmap; + + /* PHY layer config */ + ret = wl1271_init_phy_config(wl); + if (ret < 0) + goto out_free_memmap; + + ret = wl1271_acx_dco_itrim_params(wl); + if (ret < 0) + goto out_free_memmap; + + /* Configure TX patch complete interrupt behavior */ + ret = wl1271_acx_tx_config_options(wl); + if (ret < 0) + goto out_free_memmap; + + /* RX complete interrupt pacing */ + ret = wl1271_acx_init_rx_interrupt(wl); + if (ret < 0) + goto out_free_memmap; + + /* Energy detection */ + ret = wl1271_init_energy_detection(wl); + if (ret < 0) + goto out_free_memmap; + + /* Default fragmentation threshold */ + ret = wl1271_acx_frag_threshold(wl, wl->hw->wiphy->frag_threshold); + if (ret < 0) + goto out_free_memmap; + + /* Default TID/AC configuration */ + BUG_ON(wl->conf.tx.tid_conf_count != wl->conf.tx.ac_conf_count); + for (i = 0; i < wl->conf.tx.tid_conf_count; i++) { + conf_ac = &wl->conf.tx.ac_conf[i]; + ret = wl1271_acx_ac_cfg(wl, conf_ac->ac, conf_ac->cw_min, + conf_ac->cw_max, conf_ac->aifsn, + conf_ac->tx_op_limit); + if (ret < 0) + goto out_free_memmap; + + conf_tid = &wl->conf.tx.tid_conf[i]; + ret = wl1271_acx_tid_cfg(wl, conf_tid->queue_id, + conf_tid->channel_type, + conf_tid->tsid, + conf_tid->ps_scheme, + conf_tid->ack_policy, + conf_tid->apsd_conf[0], + conf_tid->apsd_conf[1]); + if (ret < 0) + goto out_free_memmap; + } + + /* Enable data path */ + ret = wl1271_cmd_data_path(wl, 1); + if (ret < 0) + goto out_free_memmap; + + /* Configure HW encryption */ + ret = wl1271_acx_feature_cfg(wl); + if (ret < 0) + goto out_free_memmap; + + /* configure PM */ + ret = wl1271_acx_pm_config(wl); + if (ret < 0) + goto out_free_memmap; + + /* Mode specific init - post mem init */ + if (is_ap) + ret = wl1271_ap_hw_init_post_mem(wl); + else + ret = wl1271_sta_hw_init_post_mem(wl); + + if (ret < 0) + goto out_free_memmap; + + /* Configure initiator BA sessions policies */ + ret = wl1271_set_ba_policies(wl); + if (ret < 0) + goto out_free_memmap; + + return 0; + + out_free_memmap: + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + + return ret; +} diff --git a/drivers/net/wireless/wl12xx/init.h b/drivers/net/wireless/wl12xx/init.h new file mode 100644 index 00000000..3a3c230f --- /dev/null +++ b/drivers/net/wireless/wl12xx/init.h @@ -0,0 +1,39 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __INIT_H__ +#define __INIT_H__ + +#include "wl12xx.h" + +int wl1271_hw_init_power_auth(struct wl1271 *wl); +int wl1271_sta_init_templates_config(struct wl1271 *wl); +int wl1271_init_phy_config(struct wl1271 *wl); +int wl1271_init_pta(struct wl1271 *wl); +int wl1271_init_energy_detection(struct wl1271 *wl); +int wl1271_chip_specific_init(struct wl1271 *wl); +int wl1271_hw_init(struct wl1271 *wl); +int wl1271_init_ap_rates(struct wl1271 *wl); +int wl1271_ap_init_templates(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/wl12xx/io.c b/drivers/net/wireless/wl12xx/io.c new file mode 100644 index 00000000..da5c1ad9 --- /dev/null +++ b/drivers/net/wireless/wl12xx/io.c @@ -0,0 +1,182 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/crc7.h> +#include <linux/spi/spi.h> + +#include "wl12xx.h" +#include "wl12xx_80211.h" +#include "io.h" +#include "tx.h" + +#define OCP_CMD_LOOP 32 + +#define OCP_CMD_WRITE 0x1 +#define OCP_CMD_READ 0x2 + +#define OCP_READY_MASK BIT(18) +#define OCP_STATUS_MASK (BIT(16) | BIT(17)) + +#define OCP_STATUS_NO_RESP 0x00000 +#define OCP_STATUS_OK 0x10000 +#define OCP_STATUS_REQ_FAILED 0x20000 +#define OCP_STATUS_RESP_ERROR 0x30000 + +bool wl1271_set_block_size(struct wl1271 *wl) +{ + if (wl->if_ops->set_block_size) { + wl->if_ops->set_block_size(wl, WL12XX_BUS_BLOCK_SIZE); + return true; + } + + return false; +} + +void wl1271_disable_interrupts(struct wl1271 *wl) +{ + wl->if_ops->disable_irq(wl); +} + +void wl1271_enable_interrupts(struct wl1271 *wl) +{ + wl->if_ops->enable_irq(wl); +} + +/* Set the SPI partitions to access the chip addresses + * + * To simplify driver code, a fixed (virtual) memory map is defined for + * register and memory addresses. Because in the chipset, in different stages + * of operation, those addresses will move around, an address translation + * mechanism is required. + * + * There are four partitions (three memory and one register partition), + * which are mapped to two different areas of the hardware memory. + * + * Virtual address + * space + * + * | | + * ...+----+--> mem.start + * Physical address ... | | + * space ... | | [PART_0] + * ... | | + * 00000000 <--+----+... ...+----+--> mem.start + mem.size + * | | ... | | + * |MEM | ... | | + * | | ... | | + * mem.size <--+----+... | | {unused area) + * | | ... | | + * |REG | ... | | + * mem.size | | ... | | + * + <--+----+... ...+----+--> reg.start + * reg.size | | ... | | + * |MEM2| ... | | [PART_1] + * | | ... | | + * ...+----+--> reg.start + reg.size + * | | + * + */ +int wl1271_set_partition(struct wl1271 *wl, + struct wl1271_partition_set *p) +{ + /* copy partition info */ + memcpy(&wl->part, p, sizeof(*p)); + + wl1271_debug(DEBUG_SPI, "mem_start %08X mem_size %08X", + p->mem.start, p->mem.size); + wl1271_debug(DEBUG_SPI, "reg_start %08X reg_size %08X", + p->reg.start, p->reg.size); + wl1271_debug(DEBUG_SPI, "mem2_start %08X mem2_size %08X", + p->mem2.start, p->mem2.size); + wl1271_debug(DEBUG_SPI, "mem3_start %08X mem3_size %08X", + p->mem3.start, p->mem3.size); + + /* write partition info to the chipset */ + wl1271_raw_write32(wl, HW_PART0_START_ADDR, p->mem.start); + wl1271_raw_write32(wl, HW_PART0_SIZE_ADDR, p->mem.size); + wl1271_raw_write32(wl, HW_PART1_START_ADDR, p->reg.start); + wl1271_raw_write32(wl, HW_PART1_SIZE_ADDR, p->reg.size); + wl1271_raw_write32(wl, HW_PART2_START_ADDR, p->mem2.start); + wl1271_raw_write32(wl, HW_PART2_SIZE_ADDR, p->mem2.size); + wl1271_raw_write32(wl, HW_PART3_START_ADDR, p->mem3.start); + + return 0; +} +EXPORT_SYMBOL_GPL(wl1271_set_partition); + +void wl1271_io_reset(struct wl1271 *wl) +{ + wl->if_ops->reset(wl); +} + +void wl1271_io_init(struct wl1271 *wl) +{ + wl->if_ops->init(wl); +} + +void wl1271_top_reg_write(struct wl1271 *wl, int addr, u16 val) +{ + /* write address >> 1 + 0x30000 to OCP_POR_CTR */ + addr = (addr >> 1) + 0x30000; + wl1271_write32(wl, OCP_POR_CTR, addr); + + /* write value to OCP_POR_WDATA */ + wl1271_write32(wl, OCP_DATA_WRITE, val); + + /* write 1 to OCP_CMD */ + wl1271_write32(wl, OCP_CMD, OCP_CMD_WRITE); +} + +u16 wl1271_top_reg_read(struct wl1271 *wl, int addr) +{ + u32 val; + int timeout = OCP_CMD_LOOP; + + /* write address >> 1 + 0x30000 to OCP_POR_CTR */ + addr = (addr >> 1) + 0x30000; + wl1271_write32(wl, OCP_POR_CTR, addr); + + /* write 2 to OCP_CMD */ + wl1271_write32(wl, OCP_CMD, OCP_CMD_READ); + + /* poll for data ready */ + do { + val = wl1271_read32(wl, OCP_DATA_READ); + } while (!(val & OCP_READY_MASK) && --timeout); + + if (!timeout) { + wl1271_warning("Top register access timed out."); + return 0xffff; + } + + /* check data status and return if OK */ + if ((val & OCP_STATUS_MASK) == OCP_STATUS_OK) + return val & 0xffff; + else { + wl1271_warning("Top register access returned error."); + return 0xffff; + } +} + diff --git a/drivers/net/wireless/wl12xx/io.h b/drivers/net/wireless/wl12xx/io.h new file mode 100644 index 00000000..beed621a --- /dev/null +++ b/drivers/net/wireless/wl12xx/io.h @@ -0,0 +1,176 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __IO_H__ +#define __IO_H__ + +#include "reg.h" + +#define HW_ACCESS_MEMORY_MAX_RANGE 0x1FFC0 + +#define HW_PARTITION_REGISTERS_ADDR 0x1FFC0 +#define HW_PART0_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR) +#define HW_PART0_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 4) +#define HW_PART1_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR + 8) +#define HW_PART1_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 12) +#define HW_PART2_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR + 16) +#define HW_PART2_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 20) +#define HW_PART3_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 24) + +#define HW_ACCESS_REGISTER_SIZE 4 + +#define HW_ACCESS_PRAM_MAX_RANGE 0x3c000 + +struct wl1271; + +void wl1271_disable_interrupts(struct wl1271 *wl); +void wl1271_enable_interrupts(struct wl1271 *wl); + +void wl1271_io_reset(struct wl1271 *wl); +void wl1271_io_init(struct wl1271 *wl); + +static inline struct device *wl1271_wl_to_dev(struct wl1271 *wl) +{ + return wl->if_ops->dev(wl); +} + + +/* Raw target IO, address is not translated */ +static inline void wl1271_raw_write(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + wl->if_ops->write(wl, addr, buf, len, fixed); +} + +static inline void wl1271_raw_read(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + wl->if_ops->read(wl, addr, buf, len, fixed); +} + +static inline u32 wl1271_raw_read32(struct wl1271 *wl, int addr) +{ + wl1271_raw_read(wl, addr, &wl->buffer_32, + sizeof(wl->buffer_32), false); + + return le32_to_cpu(wl->buffer_32); +} + +static inline void wl1271_raw_write32(struct wl1271 *wl, int addr, u32 val) +{ + wl->buffer_32 = cpu_to_le32(val); + wl1271_raw_write(wl, addr, &wl->buffer_32, + sizeof(wl->buffer_32), false); +} + +/* Translated target IO */ +static inline int wl1271_translate_addr(struct wl1271 *wl, int addr) +{ + /* + * To translate, first check to which window of addresses the + * particular address belongs. Then subtract the starting address + * of that window from the address. Then, add offset of the + * translated region. + * + * The translated regions occur next to each other in physical device + * memory, so just add the sizes of the preceding address regions to + * get the offset to the new region. + * + * Currently, only the two first regions are addressed, and the + * assumption is that all addresses will fall into either of those + * two. + */ + if ((addr >= wl->part.reg.start) && + (addr < wl->part.reg.start + wl->part.reg.size)) + return addr - wl->part.reg.start + wl->part.mem.size; + else + return addr - wl->part.mem.start; +} + +static inline void wl1271_read(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + int physical; + + physical = wl1271_translate_addr(wl, addr); + + wl1271_raw_read(wl, physical, buf, len, fixed); +} + +static inline void wl1271_write(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + int physical; + + physical = wl1271_translate_addr(wl, addr); + + wl1271_raw_write(wl, physical, buf, len, fixed); +} + +static inline u32 wl1271_read32(struct wl1271 *wl, int addr) +{ + return wl1271_raw_read32(wl, wl1271_translate_addr(wl, addr)); +} + +static inline void wl1271_write32(struct wl1271 *wl, int addr, u32 val) +{ + wl1271_raw_write32(wl, wl1271_translate_addr(wl, addr), val); +} + +static inline void wl1271_power_off(struct wl1271 *wl) +{ + wl->if_ops->power(wl, false); + clear_bit(WL1271_FLAG_GPIO_POWER, &wl->flags); +} + +static inline int wl1271_power_on(struct wl1271 *wl) +{ + int ret = wl->if_ops->power(wl, true); + if (ret == 0) + set_bit(WL1271_FLAG_GPIO_POWER, &wl->flags); + + return ret; +} + + +/* Top Register IO */ +void wl1271_top_reg_write(struct wl1271 *wl, int addr, u16 val); +u16 wl1271_top_reg_read(struct wl1271 *wl, int addr); + +int wl1271_set_partition(struct wl1271 *wl, + struct wl1271_partition_set *p); + +/* Functions from wl1271_main.c */ + +int wl1271_register_hw(struct wl1271 *wl); +void wl1271_unregister_hw(struct wl1271 *wl); +int wl1271_init_ieee80211(struct wl1271 *wl); +struct ieee80211_hw *wl1271_alloc_hw(void); +int wl1271_free_hw(struct wl1271 *wl); +irqreturn_t wl1271_irq(int irq, void *data); +bool wl1271_set_block_size(struct wl1271 *wl); +int wl1271_tx_dummy_packet(struct wl1271 *wl); +void wl1271_configure_filters(struct wl1271 *wl, unsigned int filters); + +#endif diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c new file mode 100644 index 00000000..e6497dc6 --- /dev/null +++ b/drivers/net/wireless/wl12xx/main.c @@ -0,0 +1,4095 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/vmalloc.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/wl12xx.h> + +#include "wl12xx.h" +#include "wl12xx_80211.h" +#include "reg.h" +#include "io.h" +#include "event.h" +#include "tx.h" +#include "rx.h" +#include "ps.h" +#include "init.h" +#include "debugfs.h" +#include "cmd.h" +#include "boot.h" +#include "testmode.h" +#include "scan.h" + +#define WL1271_BOOT_RETRIES 3 + +static struct conf_drv_settings default_conf = { + .sg = { + .sta_params = { + [CONF_SG_BT_PER_THRESHOLD] = 7500, + [CONF_SG_HV3_MAX_OVERRIDE] = 0, + [CONF_SG_BT_NFS_SAMPLE_INTERVAL] = 400, + [CONF_SG_BT_LOAD_RATIO] = 200, + [CONF_SG_AUTO_PS_MODE] = 1, + [CONF_SG_AUTO_SCAN_PROBE_REQ] = 170, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3] = 50, + [CONF_SG_ANTENNA_CONFIGURATION] = 0, + [CONF_SG_BEACON_MISS_PERCENT] = 60, + [CONF_SG_RATE_ADAPT_THRESH] = 12, + [CONF_SG_RATE_ADAPT_SNR] = 0, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_BR] = 10, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_BR] = 30, + [CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_BR] = 8, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_BR] = 20, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_BR] = 50, + /* Note: with UPSD, this should be 4 */ + [CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_BR] = 8, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_EDR] = 7, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_EDR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_EDR] = 20, + /* Note: with UPDS, this should be 15 */ + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_EDR] = 8, + /* Note: with UPDS, this should be 50 */ + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_EDR] = 40, + /* Note: with UPDS, this should be 10 */ + [CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_EDR] = 20, + [CONF_SG_RXT] = 1200, + [CONF_SG_TXT] = 1000, + [CONF_SG_ADAPTIVE_RXT_TXT] = 1, + [CONF_SG_PS_POLL_TIMEOUT] = 10, + [CONF_SG_UPSD_TIMEOUT] = 10, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MIN_EDR] = 7, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MAX_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_MASTER_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MIN_EDR] = 8, + [CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MAX_EDR] = 20, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_SLAVE_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MIN_BR] = 20, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MAX_BR] = 50, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_BR] = 10, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3] = 200, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP] = 800, + [CONF_SG_PASSIVE_SCAN_A2DP_BT_TIME] = 75, + [CONF_SG_PASSIVE_SCAN_A2DP_WLAN_TIME] = 15, + [CONF_SG_HV3_MAX_SERVED] = 6, + [CONF_SG_DHCP_TIME] = 5000, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP] = 100, + }, + .ap_params = { + [CONF_SG_BT_PER_THRESHOLD] = 7500, + [CONF_SG_HV3_MAX_OVERRIDE] = 0, + [CONF_SG_BT_NFS_SAMPLE_INTERVAL] = 400, + [CONF_SG_BT_LOAD_RATIO] = 50, + [CONF_SG_AUTO_PS_MODE] = 1, + [CONF_SG_AUTO_SCAN_PROBE_REQ] = 170, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3] = 50, + [CONF_SG_ANTENNA_CONFIGURATION] = 0, + [CONF_SG_BEACON_MISS_PERCENT] = 60, + [CONF_SG_RATE_ADAPT_THRESH] = 64, + [CONF_SG_RATE_ADAPT_SNR] = 1, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_BR] = 10, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_BR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_BR] = 25, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_BR] = 20, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_BR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_BR] = 25, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MIN_EDR] = 7, + [CONF_SG_WLAN_PS_BT_ACL_MASTER_MAX_EDR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_MASTER_EDR] = 25, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MIN_EDR] = 8, + [CONF_SG_WLAN_PS_BT_ACL_SLAVE_MAX_EDR] = 25, + [CONF_SG_WLAN_PS_MAX_BT_ACL_SLAVE_EDR] = 25, + [CONF_SG_RXT] = 1200, + [CONF_SG_TXT] = 1000, + [CONF_SG_ADAPTIVE_RXT_TXT] = 1, + [CONF_SG_PS_POLL_TIMEOUT] = 10, + [CONF_SG_UPSD_TIMEOUT] = 10, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MIN_EDR] = 7, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MASTER_MAX_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_MASTER_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MIN_EDR] = 8, + [CONF_SG_WLAN_ACTIVE_BT_ACL_SLAVE_MAX_EDR] = 20, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_SLAVE_EDR] = 15, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MIN_BR] = 20, + [CONF_SG_WLAN_ACTIVE_BT_ACL_MAX_BR] = 50, + [CONF_SG_WLAN_ACTIVE_MAX_BT_ACL_BR] = 10, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3] = 200, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP] = 800, + [CONF_SG_PASSIVE_SCAN_A2DP_BT_TIME] = 75, + [CONF_SG_PASSIVE_SCAN_A2DP_WLAN_TIME] = 15, + [CONF_SG_HV3_MAX_SERVED] = 6, + [CONF_SG_DHCP_TIME] = 5000, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP] = 100, + [CONF_SG_TEMP_PARAM_1] = 0, + [CONF_SG_TEMP_PARAM_2] = 0, + [CONF_SG_TEMP_PARAM_3] = 0, + [CONF_SG_TEMP_PARAM_4] = 0, + [CONF_SG_TEMP_PARAM_5] = 0, + [CONF_SG_AP_BEACON_MISS_TX] = 3, + [CONF_SG_RX_WINDOW_LENGTH] = 6, + [CONF_SG_AP_CONNECTION_PROTECTION_TIME] = 50, + [CONF_SG_TEMP_PARAM_6] = 1, + }, + .state = CONF_SG_PROTECTIVE, + }, + .rx = { + .rx_msdu_life_time = 512000, + .packet_detection_threshold = 0, + .ps_poll_timeout = 15, + .upsd_timeout = 15, + .rts_threshold = IEEE80211_MAX_RTS_THRESHOLD, + .rx_cca_threshold = 0, + .irq_blk_threshold = 0xFFFF, + .irq_pkt_threshold = 0, + .irq_timeout = 600, + .queue_type = CONF_RX_QUEUE_TYPE_LOW_PRIORITY, + }, + .tx = { + .tx_energy_detection = 0, + .sta_rc_conf = { + .enabled_rates = 0, + .short_retry_limit = 10, + .long_retry_limit = 10, + .aflags = 0, + }, + .ac_conf_count = 4, + .ac_conf = { + [CONF_TX_AC_BE] = { + .ac = CONF_TX_AC_BE, + .cw_min = 15, + .cw_max = 63, + .aifsn = 3, + .tx_op_limit = 0, + }, + [CONF_TX_AC_BK] = { + .ac = CONF_TX_AC_BK, + .cw_min = 15, + .cw_max = 63, + .aifsn = 7, + .tx_op_limit = 0, + }, + [CONF_TX_AC_VI] = { + .ac = CONF_TX_AC_VI, + .cw_min = 15, + .cw_max = 63, + .aifsn = CONF_TX_AIFS_PIFS, + .tx_op_limit = 3008, + }, + [CONF_TX_AC_VO] = { + .ac = CONF_TX_AC_VO, + .cw_min = 15, + .cw_max = 63, + .aifsn = CONF_TX_AIFS_PIFS, + .tx_op_limit = 1504, + }, + }, + .ap_max_tx_retries = 100, + .tid_conf_count = 4, + .tid_conf = { + [CONF_TX_AC_BE] = { + .queue_id = CONF_TX_AC_BE, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_BE, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_BK] = { + .queue_id = CONF_TX_AC_BK, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_BK, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_VI] = { + .queue_id = CONF_TX_AC_VI, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_VI, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_VO] = { + .queue_id = CONF_TX_AC_VO, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_VO, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + }, + .frag_threshold = IEEE80211_MAX_FRAG_THRESHOLD, + .tx_compl_timeout = 700, + .tx_compl_threshold = 4, + .basic_rate = CONF_HW_BIT_RATE_1MBPS, + .basic_rate_5 = CONF_HW_BIT_RATE_6MBPS, + .tmpl_short_retry_limit = 10, + .tmpl_long_retry_limit = 10, + }, + .conn = { + .wake_up_event = CONF_WAKE_UP_EVENT_DTIM, + .listen_interval = 1, + .bcn_filt_mode = CONF_BCN_FILT_MODE_ENABLED, + .bcn_filt_ie_count = 2, + .bcn_filt_ie = { + [0] = { + .ie = WLAN_EID_CHANNEL_SWITCH, + .rule = CONF_BCN_RULE_PASS_ON_APPEARANCE, + }, + [1] = { + .ie = WLAN_EID_HT_INFORMATION, + .rule = CONF_BCN_RULE_PASS_ON_CHANGE, + }, + }, + .synch_fail_thold = 10, + .bss_lose_timeout = 100, + .beacon_rx_timeout = 10000, + .broadcast_timeout = 20000, + .rx_broadcast_in_ps = 1, + .ps_poll_threshold = 10, + .ps_poll_recovery_period = 700, + .bet_enable = CONF_BET_MODE_ENABLE, + .bet_max_consecutive = 50, + .psm_entry_retries = 5, + .psm_exit_retries = 16, + .psm_entry_nullfunc_retries = 3, + .psm_entry_hangover_period = 1, + .keep_alive_interval = 55000, + .max_listen_interval = 20, + }, + .itrim = { + .enable = false, + .timeout = 50000, + }, + .pm_config = { + .host_clk_settling_time = 5000, + .host_fast_wakeup_support = false + }, + .roam_trigger = { + .trigger_pacing = 1, + .avg_weight_rssi_beacon = 20, + .avg_weight_rssi_data = 10, + .avg_weight_snr_beacon = 20, + .avg_weight_snr_data = 10, + }, + .scan = { + .min_dwell_time_active = 7500, + .max_dwell_time_active = 30000, + .min_dwell_time_passive = 100000, + .max_dwell_time_passive = 100000, + .num_probe_reqs = 2, + }, + .sched_scan = { + /* sched_scan requires dwell times in TU instead of TU/1000 */ + .min_dwell_time_active = 8, + .max_dwell_time_active = 30, + .dwell_time_passive = 100, + .dwell_time_dfs = 150, + .num_probe_reqs = 2, + .rssi_threshold = -90, + .snr_threshold = 0, + }, + .rf = { + .tx_per_channel_power_compensation_2 = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + .tx_per_channel_power_compensation_5 = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + .ht = { + .tx_ba_win_size = 64, + .inactivity_timeout = 10000, + }, + .mem_wl127x = { + .num_stations = 1, + .ssid_profiles = 1, + .rx_block_num = 70, + .tx_min_block_num = 40, + .dynamic_memory = 1, + .min_req_tx_blocks = 100, + .min_req_rx_blocks = 22, + .tx_min = 27, + }, + .mem_wl128x = { + .num_stations = 1, + .ssid_profiles = 1, + .rx_block_num = 40, + .tx_min_block_num = 40, + .dynamic_memory = 1, + .min_req_tx_blocks = 45, + .min_req_rx_blocks = 22, + .tx_min = 27, + }, + .fm_coex = { + .enable = true, + .swallow_period = 5, + .n_divider_fref_set_1 = 0xff, /* default */ + .n_divider_fref_set_2 = 12, + .m_divider_fref_set_1 = 148, + .m_divider_fref_set_2 = 0xffff, /* default */ + .coex_pll_stabilization_time = 0xffffffff, /* default */ + .ldo_stabilization_time = 0xffff, /* default */ + .fm_disturbed_band_margin = 0xff, /* default */ + .swallow_clk_diff = 0xff, /* default */ + }, + .hci_io_ds = HCI_IO_DS_6MA, +}; + +static void __wl1271_op_remove_interface(struct wl1271 *wl, + bool reset_tx_queues); +static void wl1271_free_ap_keys(struct wl1271 *wl); + + +static void wl1271_device_release(struct device *dev) +{ + +} + +static struct platform_device wl1271_device = { + .name = "wl1271", + .id = -1, + + /* device model insists to have a release function */ + .dev = { + .release = wl1271_device_release, + }, +}; + +static DEFINE_MUTEX(wl_list_mutex); +static LIST_HEAD(wl_list); + +static int wl1271_dev_notify(struct notifier_block *me, unsigned long what, + void *arg) +{ + struct net_device *dev = arg; + struct wireless_dev *wdev; + struct wiphy *wiphy; + struct ieee80211_hw *hw; + struct wl1271 *wl; + struct wl1271 *wl_temp; + int ret = 0; + + /* Check that this notification is for us. */ + if (what != NETDEV_CHANGE) + return NOTIFY_DONE; + + wdev = dev->ieee80211_ptr; + if (wdev == NULL) + return NOTIFY_DONE; + + wiphy = wdev->wiphy; + if (wiphy == NULL) + return NOTIFY_DONE; + + hw = wiphy_priv(wiphy); + if (hw == NULL) + return NOTIFY_DONE; + + wl_temp = hw->priv; + mutex_lock(&wl_list_mutex); + list_for_each_entry(wl, &wl_list, list) { + if (wl == wl_temp) + break; + } + mutex_unlock(&wl_list_mutex); + if (wl != wl_temp) + return NOTIFY_DONE; + + mutex_lock(&wl->mutex); + + if (wl->state == WL1271_STATE_OFF) + goto out; + + if (!test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if ((dev->operstate == IF_OPER_UP) && + !test_and_set_bit(WL1271_FLAG_STA_STATE_SENT, &wl->flags)) { + wl1271_cmd_set_sta_state(wl); + wl1271_info("Association completed."); + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return NOTIFY_OK; +} + +static int wl1271_reg_notify(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_supported_band *band; + struct ieee80211_channel *ch; + int i; + + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + for (i = 0; i < band->n_channels; i++) { + ch = &band->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (ch->flags & IEEE80211_CHAN_RADAR) + ch->flags |= IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN; + + } + + return 0; +} + +static void wl1271_conf_init(struct wl1271 *wl) +{ + + /* + * This function applies the default configuration to the driver. This + * function is invoked upon driver load (spi probe.) + * + * The configuration is stored in a run-time structure in order to + * facilitate for run-time adjustment of any of the parameters. Making + * changes to the configuration structure will apply the new values on + * the next interface up (wl1271_op_start.) + */ + + /* apply driver default configuration */ + memcpy(&wl->conf, &default_conf, sizeof(default_conf)); +} + + +static int wl1271_plt_init(struct wl1271 *wl) +{ + struct conf_tx_ac_category *conf_ac; + struct conf_tx_tid *conf_tid; + int ret, i; + + if (wl->chip.id == CHIP_ID_1283_PG20) + ret = wl128x_cmd_general_parms(wl); + else + ret = wl1271_cmd_general_parms(wl); + if (ret < 0) + return ret; + + if (wl->chip.id == CHIP_ID_1283_PG20) + ret = wl128x_cmd_radio_parms(wl); + else + ret = wl1271_cmd_radio_parms(wl); + if (ret < 0) + return ret; + + if (wl->chip.id != CHIP_ID_1283_PG20) { + ret = wl1271_cmd_ext_radio_parms(wl); + if (ret < 0) + return ret; + } + if (ret < 0) + return ret; + + /* Chip-specific initializations */ + ret = wl1271_chip_specific_init(wl); + if (ret < 0) + return ret; + + ret = wl1271_sta_init_templates_config(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_init_mem_config(wl); + if (ret < 0) + return ret; + + /* PHY layer config */ + ret = wl1271_init_phy_config(wl); + if (ret < 0) + goto out_free_memmap; + + ret = wl1271_acx_dco_itrim_params(wl); + if (ret < 0) + goto out_free_memmap; + + /* Initialize connection monitoring thresholds */ + ret = wl1271_acx_conn_monit_params(wl, false); + if (ret < 0) + goto out_free_memmap; + + /* Bluetooth WLAN coexistence */ + ret = wl1271_init_pta(wl); + if (ret < 0) + goto out_free_memmap; + + /* FM WLAN coexistence */ + ret = wl1271_acx_fm_coex(wl); + if (ret < 0) + goto out_free_memmap; + + /* Energy detection */ + ret = wl1271_init_energy_detection(wl); + if (ret < 0) + goto out_free_memmap; + + ret = wl1271_acx_sta_mem_cfg(wl); + if (ret < 0) + goto out_free_memmap; + + /* Default fragmentation threshold */ + ret = wl1271_acx_frag_threshold(wl, wl->conf.tx.frag_threshold); + if (ret < 0) + goto out_free_memmap; + + /* Default TID/AC configuration */ + BUG_ON(wl->conf.tx.tid_conf_count != wl->conf.tx.ac_conf_count); + for (i = 0; i < wl->conf.tx.tid_conf_count; i++) { + conf_ac = &wl->conf.tx.ac_conf[i]; + ret = wl1271_acx_ac_cfg(wl, conf_ac->ac, conf_ac->cw_min, + conf_ac->cw_max, conf_ac->aifsn, + conf_ac->tx_op_limit); + if (ret < 0) + goto out_free_memmap; + + conf_tid = &wl->conf.tx.tid_conf[i]; + ret = wl1271_acx_tid_cfg(wl, conf_tid->queue_id, + conf_tid->channel_type, + conf_tid->tsid, + conf_tid->ps_scheme, + conf_tid->ack_policy, + conf_tid->apsd_conf[0], + conf_tid->apsd_conf[1]); + if (ret < 0) + goto out_free_memmap; + } + + /* Enable data path */ + ret = wl1271_cmd_data_path(wl, 1); + if (ret < 0) + goto out_free_memmap; + + /* Configure for CAM power saving (ie. always active) */ + ret = wl1271_acx_sleep_auth(wl, WL1271_PSM_CAM); + if (ret < 0) + goto out_free_memmap; + + /* configure PM */ + ret = wl1271_acx_pm_config(wl); + if (ret < 0) + goto out_free_memmap; + + return 0; + + out_free_memmap: + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + + return ret; +} + +static void wl1271_irq_ps_regulate_link(struct wl1271 *wl, u8 hlid, u8 tx_blks) +{ + bool fw_ps; + + /* only regulate station links */ + if (hlid < WL1271_AP_STA_HLID_START) + return; + + fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); + + /* + * Wake up from high level PS if the STA is asleep with too little + * blocks in FW or if the STA is awake. + */ + if (!fw_ps || tx_blks < WL1271_PS_STA_MAX_BLOCKS) + wl1271_ps_link_end(wl, hlid); + + /* Start high-level PS if the STA is asleep with enough blocks in FW */ + else if (fw_ps && tx_blks >= WL1271_PS_STA_MAX_BLOCKS) + wl1271_ps_link_start(wl, hlid, true); +} + +static void wl1271_irq_update_links_status(struct wl1271 *wl, + struct wl1271_fw_ap_status *status) +{ + u32 cur_fw_ps_map; + u8 hlid; + + cur_fw_ps_map = le32_to_cpu(status->link_ps_bitmap); + if (wl->ap_fw_ps_map != cur_fw_ps_map) { + wl1271_debug(DEBUG_PSM, + "link ps prev 0x%x cur 0x%x changed 0x%x", + wl->ap_fw_ps_map, cur_fw_ps_map, + wl->ap_fw_ps_map ^ cur_fw_ps_map); + + wl->ap_fw_ps_map = cur_fw_ps_map; + } + + for (hlid = WL1271_AP_STA_HLID_START; hlid < AP_MAX_LINKS; hlid++) { + u8 cnt = status->tx_lnk_free_blks[hlid] - + wl->links[hlid].prev_freed_blks; + + wl->links[hlid].prev_freed_blks = + status->tx_lnk_free_blks[hlid]; + wl->links[hlid].allocated_blks -= cnt; + + wl1271_irq_ps_regulate_link(wl, hlid, + wl->links[hlid].allocated_blks); + } +} + +static void wl1271_fw_status(struct wl1271 *wl, + struct wl1271_fw_full_status *full_status) +{ + struct wl1271_fw_common_status *status = &full_status->common; + struct timespec ts; + u32 old_tx_blk_count = wl->tx_blocks_available; + u32 freed_blocks = 0; + int i; + + if (wl->bss_type == BSS_TYPE_AP_BSS) { + wl1271_raw_read(wl, FW_STATUS_ADDR, status, + sizeof(struct wl1271_fw_ap_status), false); + } else { + wl1271_raw_read(wl, FW_STATUS_ADDR, status, + sizeof(struct wl1271_fw_sta_status), false); + } + + wl1271_debug(DEBUG_IRQ, "intr: 0x%x (fw_rx_counter = %d, " + "drv_rx_counter = %d, tx_results_counter = %d)", + status->intr, + status->fw_rx_counter, + status->drv_rx_counter, + status->tx_results_counter); + + /* update number of available TX blocks */ + for (i = 0; i < NUM_TX_QUEUES; i++) { + freed_blocks += le32_to_cpu(status->tx_released_blks[i]) - + wl->tx_blocks_freed[i]; + + wl->tx_blocks_freed[i] = + le32_to_cpu(status->tx_released_blks[i]); + } + + wl->tx_allocated_blocks -= freed_blocks; + + if (wl->bss_type == BSS_TYPE_AP_BSS) { + /* Update num of allocated TX blocks per link and ps status */ + wl1271_irq_update_links_status(wl, &full_status->ap); + wl->tx_blocks_available += freed_blocks; + } else { + int avail = full_status->sta.tx_total - wl->tx_allocated_blocks; + + /* + * The FW might change the total number of TX memblocks before + * we get a notification about blocks being released. Thus, the + * available blocks calculation might yield a temporary result + * which is lower than the actual available blocks. Keeping in + * mind that only blocks that were allocated can be moved from + * TX to RX, tx_blocks_available should never decrease here. + */ + wl->tx_blocks_available = max((int)wl->tx_blocks_available, + avail); + } + + /* if more blocks are available now, tx work can be scheduled */ + if (wl->tx_blocks_available > old_tx_blk_count) + clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); + + /* update the host-chipset time offset */ + getnstimeofday(&ts); + wl->time_offset = (timespec_to_ns(&ts) >> 10) - + (s64)le32_to_cpu(status->fw_localtime); +} + +static void wl1271_flush_deferred_work(struct wl1271 *wl) +{ + struct sk_buff *skb; + + /* Pass all received frames to the network stack */ + while ((skb = skb_dequeue(&wl->deferred_rx_queue))) + ieee80211_rx_ni(wl->hw, skb); + + /* Return sent skbs to the network stack */ + while ((skb = skb_dequeue(&wl->deferred_tx_queue))) + ieee80211_tx_status(wl->hw, skb); +} + +static void wl1271_netstack_work(struct work_struct *work) +{ + struct wl1271 *wl = + container_of(work, struct wl1271, netstack_work); + + do { + wl1271_flush_deferred_work(wl); + } while (skb_queue_len(&wl->deferred_rx_queue)); +} + +#define WL1271_IRQ_MAX_LOOPS 256 + +irqreturn_t wl1271_irq(int irq, void *cookie) +{ + int ret; + u32 intr; + int loopcount = WL1271_IRQ_MAX_LOOPS; + struct wl1271 *wl = (struct wl1271 *)cookie; + bool done = false; + unsigned int defer_count; + unsigned long flags; + + /* TX might be handled here, avoid redundant work */ + set_bit(WL1271_FLAG_TX_PENDING, &wl->flags); + cancel_work_sync(&wl->tx_work); + + /* + * In case edge triggered interrupt must be used, we cannot iterate + * more than once without introducing race conditions with the hardirq. + */ + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + loopcount = 1; + + mutex_lock(&wl->mutex); + + wl1271_debug(DEBUG_IRQ, "IRQ work"); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + while (!done && loopcount--) { + /* + * In order to avoid a race with the hardirq, clear the flag + * before acknowledging the chip. Since the mutex is held, + * wl1271_ps_elp_wakeup cannot be called concurrently. + */ + clear_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags); + smp_mb__after_clear_bit(); + + wl1271_fw_status(wl, wl->fw_status); + intr = le32_to_cpu(wl->fw_status->common.intr); + intr &= WL1271_INTR_MASK; + if (!intr) { + done = true; + continue; + } + + if (unlikely(intr & WL1271_ACX_INTR_WATCHDOG)) { + wl1271_error("watchdog interrupt received! " + "starting recovery."); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + + /* restarting the chip. ignore any other interrupt. */ + goto out; + } + + if (likely(intr & WL1271_ACX_INTR_DATA)) { + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_DATA"); + + wl1271_rx(wl, &wl->fw_status->common); + + /* Check if any tx blocks were freed */ + spin_lock_irqsave(&wl->wl_lock, flags); + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && + wl->tx_queue_count) { + spin_unlock_irqrestore(&wl->wl_lock, flags); + /* + * In order to avoid starvation of the TX path, + * call the work function directly. + */ + wl1271_tx_work_locked(wl); + } else { + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + /* check for tx results */ + if (wl->fw_status->common.tx_results_counter != + (wl->tx_results_count & 0xff)) + wl1271_tx_complete(wl); + + /* Make sure the deferred queues don't get too long */ + defer_count = skb_queue_len(&wl->deferred_tx_queue) + + skb_queue_len(&wl->deferred_rx_queue); + if (defer_count > WL1271_DEFERRED_QUEUE_LIMIT) + wl1271_flush_deferred_work(wl); + } + + if (intr & WL1271_ACX_INTR_EVENT_A) { + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_EVENT_A"); + wl1271_event_handle(wl, 0); + } + + if (intr & WL1271_ACX_INTR_EVENT_B) { + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_EVENT_B"); + wl1271_event_handle(wl, 1); + } + + if (intr & WL1271_ACX_INTR_INIT_COMPLETE) + wl1271_debug(DEBUG_IRQ, + "WL1271_ACX_INTR_INIT_COMPLETE"); + + if (intr & WL1271_ACX_INTR_HW_AVAILABLE) + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_HW_AVAILABLE"); + } + + wl1271_ps_elp_sleep(wl); + +out: + spin_lock_irqsave(&wl->wl_lock, flags); + /* In case TX was not handled here, queue TX work */ + clear_bit(WL1271_FLAG_TX_PENDING, &wl->flags); + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && + wl->tx_queue_count) + ieee80211_queue_work(wl->hw, &wl->tx_work); + spin_unlock_irqrestore(&wl->wl_lock, flags); + + mutex_unlock(&wl->mutex); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wl1271_irq); + +static int wl1271_fetch_firmware(struct wl1271 *wl) +{ + const struct firmware *fw; + const char *fw_name; + int ret; + + switch (wl->bss_type) { + case BSS_TYPE_AP_BSS: + if (wl->chip.id == CHIP_ID_1283_PG20) + fw_name = WL128X_AP_FW_NAME; + else + fw_name = WL127X_AP_FW_NAME; + break; + case BSS_TYPE_IBSS: + case BSS_TYPE_STA_BSS: + if (wl->chip.id == CHIP_ID_1283_PG20) + fw_name = WL128X_FW_NAME; + else + fw_name = WL1271_FW_NAME; + break; + default: + wl1271_error("no compatible firmware for bss_type %d", + wl->bss_type); + return -EINVAL; + } + + wl1271_debug(DEBUG_BOOT, "booting firmware %s", fw_name); + + ret = request_firmware(&fw, fw_name, wl1271_wl_to_dev(wl)); + + if (ret < 0) { + wl1271_error("could not get firmware: %d", ret); + return ret; + } + + if (fw->size % 4) { + wl1271_error("firmware size is not multiple of 32 bits: %zu", + fw->size); + ret = -EILSEQ; + goto out; + } + + vfree(wl->fw); + wl->fw_len = fw->size; + wl->fw = vmalloc(wl->fw_len); + + if (!wl->fw) { + wl1271_error("could not allocate memory for the firmware"); + ret = -ENOMEM; + goto out; + } + + memcpy(wl->fw, fw->data, wl->fw_len); + wl->fw_bss_type = wl->bss_type; + ret = 0; + +out: + release_firmware(fw); + + return ret; +} + +static int wl1271_fetch_nvs(struct wl1271 *wl) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, WL12XX_NVS_NAME, wl1271_wl_to_dev(wl)); + + if (ret < 0) { + wl1271_error("could not get nvs file: %d", ret); + return ret; + } + + wl->nvs = kmemdup(fw->data, fw->size, GFP_KERNEL); + + if (!wl->nvs) { + wl1271_error("could not allocate memory for the nvs file"); + ret = -ENOMEM; + goto out; + } + + wl->nvs_len = fw->size; + +out: + release_firmware(fw); + + return ret; +} + +static void wl1271_recovery_work(struct work_struct *work) +{ + struct wl1271 *wl = + container_of(work, struct wl1271, recovery_work); + + mutex_lock(&wl->mutex); + + if (wl->state != WL1271_STATE_ON) + goto out; + + wl1271_info("Hardware recovery in progress. FW ver: %s pc: 0x%x", + wl->chip.fw_ver_str, wl1271_read32(wl, SCR_PAD4)); + + if (test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) + ieee80211_connection_loss(wl->vif); + + /* Prevent spurious TX during FW restart */ + ieee80211_stop_queues(wl->hw); + + if (wl->sched_scanning) { + ieee80211_sched_scan_stopped(wl->hw); + wl->sched_scanning = false; + } + + /* reboot the chipset */ + __wl1271_op_remove_interface(wl, false); + ieee80211_restart_hw(wl->hw); + + /* + * Its safe to enable TX now - the queues are stopped after a request + * to restart the HW. + */ + ieee80211_wake_queues(wl->hw); + +out: + mutex_unlock(&wl->mutex); +} + +static void wl1271_fw_wakeup(struct wl1271 *wl) +{ + u32 elp_reg; + + elp_reg = ELPCTRL_WAKE_UP; + wl1271_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, elp_reg); +} + +static int wl1271_setup(struct wl1271 *wl) +{ + wl->fw_status = kmalloc(sizeof(*wl->fw_status), GFP_KERNEL); + if (!wl->fw_status) + return -ENOMEM; + + wl->tx_res_if = kmalloc(sizeof(*wl->tx_res_if), GFP_KERNEL); + if (!wl->tx_res_if) { + kfree(wl->fw_status); + return -ENOMEM; + } + + return 0; +} + +static int wl1271_chip_wakeup(struct wl1271 *wl) +{ + struct wl1271_partition_set partition; + int ret = 0; + + msleep(WL1271_PRE_POWER_ON_SLEEP); + ret = wl1271_power_on(wl); + if (ret < 0) + goto out; + msleep(WL1271_POWER_ON_SLEEP); + wl1271_io_reset(wl); + wl1271_io_init(wl); + + /* We don't need a real memory partition here, because we only want + * to use the registers at this point. */ + memset(&partition, 0, sizeof(partition)); + partition.reg.start = REGISTERS_BASE; + partition.reg.size = REGISTERS_DOWN_SIZE; + wl1271_set_partition(wl, &partition); + + /* ELP module wake up */ + wl1271_fw_wakeup(wl); + + /* whal_FwCtrl_BootSm() */ + + /* 0. read chip id from CHIP_ID */ + wl->chip.id = wl1271_read32(wl, CHIP_ID_B); + + /* 1. check if chip id is valid */ + + switch (wl->chip.id) { + case CHIP_ID_1271_PG10: + wl1271_warning("chip id 0x%x (1271 PG10) support is obsolete", + wl->chip.id); + + ret = wl1271_setup(wl); + if (ret < 0) + goto out; + break; + case CHIP_ID_1271_PG20: + wl1271_debug(DEBUG_BOOT, "chip id 0x%x (1271 PG20)", + wl->chip.id); + + /* end-of-transaction flag should be set in wl127x AP mode */ + if (wl->bss_type == BSS_TYPE_AP_BSS) + wl->quirks |= WL12XX_QUIRK_END_OF_TRANSACTION; + + ret = wl1271_setup(wl); + if (ret < 0) + goto out; + break; + case CHIP_ID_1283_PG20: + wl1271_debug(DEBUG_BOOT, "chip id 0x%x (1283 PG20)", + wl->chip.id); + + ret = wl1271_setup(wl); + if (ret < 0) + goto out; + if (wl1271_set_block_size(wl)) + wl->quirks |= WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT; + break; + case CHIP_ID_1283_PG10: + default: + wl1271_warning("unsupported chip id: 0x%x", wl->chip.id); + ret = -ENODEV; + goto out; + } + + /* Make sure the firmware type matches the BSS type */ + if (wl->fw == NULL || wl->fw_bss_type != wl->bss_type) { + ret = wl1271_fetch_firmware(wl); + if (ret < 0) + goto out; + } + + /* No NVS from netlink, try to get it from the filesystem */ + if (wl->nvs == NULL) { + ret = wl1271_fetch_nvs(wl); + if (ret < 0) + goto out; + } + +out: + return ret; +} + +static unsigned int wl1271_get_fw_ver_quirks(struct wl1271 *wl) +{ + unsigned int quirks = 0; + unsigned int *fw_ver = wl->chip.fw_ver; + + /* Only for wl127x */ + if ((fw_ver[FW_VER_CHIP] == FW_VER_CHIP_WL127X) && + /* Check STA version */ + (((fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_STA) && + (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_1_SPARE_STA_MIN)) || + /* Check AP version */ + ((fw_ver[FW_VER_IF_TYPE] == FW_VER_IF_TYPE_AP) && + (fw_ver[FW_VER_MINOR] < FW_VER_MINOR_1_SPARE_AP_MIN)))) + quirks |= WL12XX_QUIRK_USE_2_SPARE_BLOCKS; + + return quirks; +} + +int wl1271_plt_start(struct wl1271 *wl) +{ + int retries = WL1271_BOOT_RETRIES; + int ret; + + mutex_lock(&wl->mutex); + + wl1271_notice("power up"); + + if (wl->state != WL1271_STATE_OFF) { + wl1271_error("cannot go into PLT state because not " + "in off state: %d", wl->state); + ret = -EBUSY; + goto out; + } + + wl->bss_type = BSS_TYPE_STA_BSS; + + while (retries) { + retries--; + ret = wl1271_chip_wakeup(wl); + if (ret < 0) + goto power_off; + + ret = wl1271_boot(wl); + if (ret < 0) + goto power_off; + + ret = wl1271_plt_init(wl); + if (ret < 0) + goto irq_disable; + + wl->state = WL1271_STATE_PLT; + wl1271_notice("firmware booted in PLT mode (%s)", + wl->chip.fw_ver_str); + + /* Check if any quirks are needed with older fw versions */ + wl->quirks |= wl1271_get_fw_ver_quirks(wl); + goto out; + +irq_disable: + mutex_unlock(&wl->mutex); + /* Unlocking the mutex in the middle of handling is + inherently unsafe. In this case we deem it safe to do, + because we need to let any possibly pending IRQ out of + the system (and while we are WL1271_STATE_OFF the IRQ + work function will not do anything.) Also, any other + possible concurrent operations will fail due to the + current state, hence the wl1271 struct should be safe. */ + wl1271_disable_interrupts(wl); + wl1271_flush_deferred_work(wl); + cancel_work_sync(&wl->netstack_work); + mutex_lock(&wl->mutex); +power_off: + wl1271_power_off(wl); + } + + wl1271_error("firmware boot in PLT mode failed despite %d retries", + WL1271_BOOT_RETRIES); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int __wl1271_plt_stop(struct wl1271 *wl) +{ + int ret = 0; + + wl1271_notice("power down"); + + if (wl->state != WL1271_STATE_PLT) { + wl1271_error("cannot power down because not in PLT " + "state: %d", wl->state); + ret = -EBUSY; + goto out; + } + + wl1271_power_off(wl); + + wl->state = WL1271_STATE_OFF; + wl->rx_counter = 0; + + mutex_unlock(&wl->mutex); + wl1271_disable_interrupts(wl); + wl1271_flush_deferred_work(wl); + cancel_work_sync(&wl->netstack_work); + cancel_work_sync(&wl->recovery_work); + mutex_lock(&wl->mutex); +out: + return ret; +} + +int wl1271_plt_stop(struct wl1271 *wl) +{ + int ret; + + mutex_lock(&wl->mutex); + ret = __wl1271_plt_stop(wl); + mutex_unlock(&wl->mutex); + return ret; +} + +static void wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct wl1271 *wl = hw->priv; + unsigned long flags; + int q; + u8 hlid = 0; + + q = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + + if (wl->bss_type == BSS_TYPE_AP_BSS) + hlid = wl1271_tx_get_hlid(skb); + + spin_lock_irqsave(&wl->wl_lock, flags); + + wl->tx_queue_count++; + + /* + * The workqueue is slow to process the tx_queue and we need stop + * the queue here, otherwise the queue will get too long. + */ + if (wl->tx_queue_count >= WL1271_TX_QUEUE_HIGH_WATERMARK) { + wl1271_debug(DEBUG_TX, "op_tx: stopping queues"); + ieee80211_stop_queues(wl->hw); + set_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags); + } + + /* queue the packet */ + if (wl->bss_type == BSS_TYPE_AP_BSS) { + wl1271_debug(DEBUG_TX, "queue skb hlid %d q %d", hlid, q); + skb_queue_tail(&wl->links[hlid].tx_queue[q], skb); + } else { + skb_queue_tail(&wl->tx_queue[q], skb); + } + + /* + * The chip specific setup must run before the first TX packet - + * before that, the tx_work will not be initialized! + */ + + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && + !test_bit(WL1271_FLAG_TX_PENDING, &wl->flags)) + ieee80211_queue_work(wl->hw, &wl->tx_work); + + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +int wl1271_tx_dummy_packet(struct wl1271 *wl) +{ + unsigned long flags; + + spin_lock_irqsave(&wl->wl_lock, flags); + set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags); + wl->tx_queue_count++; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + /* The FW is low on RX memory blocks, so send the dummy packet asap */ + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags)) + wl1271_tx_work_locked(wl); + + /* + * If the FW TX is busy, TX work will be scheduled by the threaded + * interrupt handler function + */ + return 0; +} + +/* + * The size of the dummy packet should be at least 1400 bytes. However, in + * order to minimize the number of bus transactions, aligning it to 512 bytes + * boundaries could be beneficial, performance wise + */ +#define TOTAL_TX_DUMMY_PACKET_SIZE (ALIGN(1400, 512)) + +static struct sk_buff *wl12xx_alloc_dummy_packet(struct wl1271 *wl) +{ + struct sk_buff *skb; + struct ieee80211_hdr_3addr *hdr; + unsigned int dummy_packet_size; + + dummy_packet_size = TOTAL_TX_DUMMY_PACKET_SIZE - + sizeof(struct wl1271_tx_hw_descr) - sizeof(*hdr); + + skb = dev_alloc_skb(TOTAL_TX_DUMMY_PACKET_SIZE); + if (!skb) { + wl1271_warning("Failed to allocate a dummy packet skb"); + return NULL; + } + + skb_reserve(skb, sizeof(struct wl1271_tx_hw_descr)); + + hdr = (struct ieee80211_hdr_3addr *) skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_NULLFUNC | + IEEE80211_FCTL_TODS); + + memset(skb_put(skb, dummy_packet_size), 0, dummy_packet_size); + + /* Dummy packets require the TID to be management */ + skb->priority = WL1271_TID_MGMT; + + /* Initialize all fields that might be used */ + skb_set_queue_mapping(skb, 0); + memset(IEEE80211_SKB_CB(skb), 0, sizeof(struct ieee80211_tx_info)); + + return skb; +} + + +static struct notifier_block wl1271_dev_notifier = { + .notifier_call = wl1271_dev_notify, +}; + +#ifdef CONFIG_PM +static int wl1271_configure_suspend(struct wl1271 *wl) +{ + int ret; + + if (wl->bss_type != BSS_TYPE_STA_BSS) + return 0; + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + + /* enter psm if needed*/ + if (!test_bit(WL1271_FLAG_PSM, &wl->flags)) { + DECLARE_COMPLETION_ONSTACK(compl); + + wl->ps_compl = &compl; + ret = wl1271_ps_set_mode(wl, STATION_POWER_SAVE_MODE, + wl->basic_rate, true); + if (ret < 0) + goto out_sleep; + + /* we must unlock here so we will be able to get events */ + wl1271_ps_elp_sleep(wl); + mutex_unlock(&wl->mutex); + + ret = wait_for_completion_timeout( + &compl, msecs_to_jiffies(WL1271_PS_COMPLETE_TIMEOUT)); + if (ret <= 0) { + wl1271_warning("couldn't enter ps mode!"); + ret = -EBUSY; + goto out; + } + + /* take mutex again, and wakeup */ + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + } +out_sleep: + wl1271_ps_elp_sleep(wl); +out_unlock: + mutex_unlock(&wl->mutex); +out: + return ret; + +} + +static void wl1271_configure_resume(struct wl1271 *wl) +{ + int ret; + + if (wl->bss_type != BSS_TYPE_STA_BSS) + return; + + mutex_lock(&wl->mutex); + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* exit psm if it wasn't configured */ + if (!test_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags)) + wl1271_ps_set_mode(wl, STATION_ACTIVE_MODE, + wl->basic_rate, true); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static int wl1271_op_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wow) +{ + struct wl1271 *wl = hw->priv; + wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow); + wl->wow_enabled = !!wow; + if (wl->wow_enabled) { + int ret; + ret = wl1271_configure_suspend(wl); + if (ret < 0) { + wl1271_warning("couldn't prepare device to suspend"); + return ret; + } + /* flush any remaining work */ + wl1271_debug(DEBUG_MAC80211, "flushing remaining works"); + flush_delayed_work(&wl->scan_complete_work); + + /* + * disable and re-enable interrupts in order to flush + * the threaded_irq + */ + wl1271_disable_interrupts(wl); + + /* + * set suspended flag to avoid triggering a new threaded_irq + * work. no need for spinlock as interrupts are disabled. + */ + set_bit(WL1271_FLAG_SUSPENDED, &wl->flags); + + wl1271_enable_interrupts(wl); + flush_work(&wl->tx_work); + flush_delayed_work(&wl->pspoll_work); + flush_delayed_work(&wl->elp_work); + } + return 0; +} + +static int wl1271_op_resume(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d", + wl->wow_enabled); + + /* + * re-enable irq_work enqueuing, and call irq_work directly if + * there is a pending work. + */ + if (wl->wow_enabled) { + struct wl1271 *wl = hw->priv; + unsigned long flags; + bool run_irq_work = false; + + spin_lock_irqsave(&wl->wl_lock, flags); + clear_bit(WL1271_FLAG_SUSPENDED, &wl->flags); + if (test_and_clear_bit(WL1271_FLAG_PENDING_WORK, &wl->flags)) + run_irq_work = true; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + if (run_irq_work) { + wl1271_debug(DEBUG_MAC80211, + "run postponed irq_work directly"); + wl1271_irq(0, wl); + wl1271_enable_interrupts(wl); + } + + wl1271_configure_resume(wl); + } + + return 0; +} +#endif + +static int wl1271_op_start(struct ieee80211_hw *hw) +{ + wl1271_debug(DEBUG_MAC80211, "mac80211 start"); + + /* + * We have to delay the booting of the hardware because + * we need to know the local MAC address before downloading and + * initializing the firmware. The MAC address cannot be changed + * after boot, and without the proper MAC address, the firmware + * will not function properly. + * + * The MAC address is first known when the corresponding interface + * is added. That is where we will initialize the hardware. + * + * In addition, we currently have different firmwares for AP and managed + * operation. We will know which to boot according to interface type. + */ + + return 0; +} + +static void wl1271_op_stop(struct ieee80211_hw *hw) +{ + wl1271_debug(DEBUG_MAC80211, "mac80211 stop"); +} + +static int wl1271_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + struct wiphy *wiphy = hw->wiphy; + int retries = WL1271_BOOT_RETRIES; + int ret = 0; + bool booted = false; + + wl1271_debug(DEBUG_MAC80211, "mac80211 add interface type %d mac %pM", + vif->type, vif->addr); + + mutex_lock(&wl->mutex); + if (wl->vif) { + wl1271_debug(DEBUG_MAC80211, + "multiple vifs are not supported yet"); + ret = -EBUSY; + goto out; + } + + /* + * in some very corner case HW recovery scenarios its possible to + * get here before __wl1271_op_remove_interface is complete, so + * opt out if that is the case. + */ + if (test_bit(WL1271_FLAG_IF_INITIALIZED, &wl->flags)) { + ret = -EBUSY; + goto out; + } + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + wl->bss_type = BSS_TYPE_STA_BSS; + wl->set_bss_type = BSS_TYPE_STA_BSS; + break; + case NL80211_IFTYPE_ADHOC: + wl->bss_type = BSS_TYPE_IBSS; + wl->set_bss_type = BSS_TYPE_STA_BSS; + break; + case NL80211_IFTYPE_AP: + wl->bss_type = BSS_TYPE_AP_BSS; + break; + default: + ret = -EOPNOTSUPP; + goto out; + } + + memcpy(wl->mac_addr, vif->addr, ETH_ALEN); + + if (wl->state != WL1271_STATE_OFF) { + wl1271_error("cannot start because not in off state: %d", + wl->state); + ret = -EBUSY; + goto out; + } + + while (retries) { + retries--; + ret = wl1271_chip_wakeup(wl); + if (ret < 0) + goto power_off; + + ret = wl1271_boot(wl); + if (ret < 0) + goto power_off; + + ret = wl1271_hw_init(wl); + if (ret < 0) + goto irq_disable; + + booted = true; + break; + +irq_disable: + mutex_unlock(&wl->mutex); + /* Unlocking the mutex in the middle of handling is + inherently unsafe. In this case we deem it safe to do, + because we need to let any possibly pending IRQ out of + the system (and while we are WL1271_STATE_OFF the IRQ + work function will not do anything.) Also, any other + possible concurrent operations will fail due to the + current state, hence the wl1271 struct should be safe. */ + wl1271_disable_interrupts(wl); + wl1271_flush_deferred_work(wl); + cancel_work_sync(&wl->netstack_work); + mutex_lock(&wl->mutex); +power_off: + wl1271_power_off(wl); + } + + if (!booted) { + wl1271_error("firmware boot failed despite %d retries", + WL1271_BOOT_RETRIES); + goto out; + } + + wl->vif = vif; + wl->state = WL1271_STATE_ON; + set_bit(WL1271_FLAG_IF_INITIALIZED, &wl->flags); + wl1271_info("firmware booted (%s)", wl->chip.fw_ver_str); + + /* update hw/fw version info in wiphy struct */ + wiphy->hw_version = wl->chip.id; + strncpy(wiphy->fw_version, wl->chip.fw_ver_str, + sizeof(wiphy->fw_version)); + + /* Check if any quirks are needed with older fw versions */ + wl->quirks |= wl1271_get_fw_ver_quirks(wl); + + /* + * Now we know if 11a is supported (info from the NVS), so disable + * 11a channels if not supported + */ + if (!wl->enable_11a) + wiphy->bands[IEEE80211_BAND_5GHZ]->n_channels = 0; + + wl1271_debug(DEBUG_MAC80211, "11a is %ssupported", + wl->enable_11a ? "" : "not "); + +out: + mutex_unlock(&wl->mutex); + + mutex_lock(&wl_list_mutex); + if (!ret) + list_add(&wl->list, &wl_list); + mutex_unlock(&wl_list_mutex); + + return ret; +} + +static void __wl1271_op_remove_interface(struct wl1271 *wl, + bool reset_tx_queues) +{ + int i; + + wl1271_debug(DEBUG_MAC80211, "mac80211 remove interface"); + + /* because of hardware recovery, we may get here twice */ + if (wl->state != WL1271_STATE_ON) + return; + + wl1271_info("down"); + + mutex_lock(&wl_list_mutex); + list_del(&wl->list); + mutex_unlock(&wl_list_mutex); + + /* enable dyn ps just in case (if left on due to fw crash etc) */ + if (wl->bss_type == BSS_TYPE_STA_BSS) + ieee80211_enable_dyn_ps(wl->vif); + + if (wl->scan.state != WL1271_SCAN_STATE_IDLE) { + wl->scan.state = WL1271_SCAN_STATE_IDLE; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + wl->scan.req = NULL; + ieee80211_scan_completed(wl->hw, true); + } + + /* + * this must be before the cancel_work calls below, so that the work + * functions don't perform further work. + */ + wl->state = WL1271_STATE_OFF; + + mutex_unlock(&wl->mutex); + + wl1271_disable_interrupts(wl); + wl1271_flush_deferred_work(wl); + cancel_delayed_work_sync(&wl->scan_complete_work); + cancel_work_sync(&wl->netstack_work); + cancel_work_sync(&wl->tx_work); + cancel_delayed_work_sync(&wl->pspoll_work); + cancel_delayed_work_sync(&wl->elp_work); + + mutex_lock(&wl->mutex); + + /* let's notify MAC80211 about the remaining pending TX frames */ + wl1271_tx_reset(wl, reset_tx_queues); + wl1271_power_off(wl); + + memset(wl->bssid, 0, ETH_ALEN); + memset(wl->ssid, 0, IW_ESSID_MAX_SIZE + 1); + wl->ssid_len = 0; + wl->bss_type = MAX_BSS_TYPE; + wl->set_bss_type = MAX_BSS_TYPE; + wl->band = IEEE80211_BAND_2GHZ; + + wl->rx_counter = 0; + wl->psm_entry_retry = 0; + wl->power_level = WL1271_DEFAULT_POWER_LEVEL; + wl->tx_blocks_available = 0; + wl->tx_allocated_blocks = 0; + wl->tx_results_count = 0; + wl->tx_packets_count = 0; + wl->tx_security_last_seq = 0; + wl->tx_security_seq = 0; + wl->time_offset = 0; + wl->session_counter = 0; + wl->rate_set = CONF_TX_RATE_MASK_BASIC; + wl->vif = NULL; + wl->filters = 0; + wl1271_free_ap_keys(wl); + memset(wl->ap_hlid_map, 0, sizeof(wl->ap_hlid_map)); + wl->ap_fw_ps_map = 0; + wl->ap_ps_map = 0; + wl->sched_scanning = false; + + /* + * this is performed after the cancel_work calls and the associated + * mutex_lock, so that wl1271_op_add_interface does not accidentally + * get executed before all these vars have been reset. + */ + wl->flags = 0; + + for (i = 0; i < NUM_TX_QUEUES; i++) + wl->tx_blocks_freed[i] = 0; + + wl1271_debugfs_reset(wl); + + kfree(wl->fw_status); + wl->fw_status = NULL; + kfree(wl->tx_res_if); + wl->tx_res_if = NULL; + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; +} + +static void wl1271_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + + mutex_lock(&wl->mutex); + /* + * wl->vif can be null here if someone shuts down the interface + * just when hardware recovery has been started. + */ + if (wl->vif) { + WARN_ON(wl->vif != vif); + __wl1271_op_remove_interface(wl, true); + } + + mutex_unlock(&wl->mutex); + cancel_work_sync(&wl->recovery_work); +} + +void wl1271_configure_filters(struct wl1271 *wl, unsigned int filters) +{ + wl1271_set_default_filters(wl); + + /* combine requested filters with current filter config */ + filters = wl->filters | filters; + + wl1271_debug(DEBUG_FILTERS, "RX filters set: "); + + if (filters & FIF_PROMISC_IN_BSS) { + wl1271_debug(DEBUG_FILTERS, " - FIF_PROMISC_IN_BSS"); + wl->rx_config &= ~CFG_UNI_FILTER_EN; + wl->rx_config |= CFG_BSSID_FILTER_EN; + } + if (filters & FIF_BCN_PRBRESP_PROMISC) { + wl1271_debug(DEBUG_FILTERS, " - FIF_BCN_PRBRESP_PROMISC"); + wl->rx_config &= ~CFG_BSSID_FILTER_EN; + wl->rx_config &= ~CFG_SSID_FILTER_EN; + } + if (filters & FIF_OTHER_BSS) { + wl1271_debug(DEBUG_FILTERS, " - FIF_OTHER_BSS"); + wl->rx_config &= ~CFG_BSSID_FILTER_EN; + } + if (filters & FIF_CONTROL) { + wl1271_debug(DEBUG_FILTERS, " - FIF_CONTROL"); + wl->rx_filter |= CFG_RX_CTL_EN; + } + if (filters & FIF_FCSFAIL) { + wl1271_debug(DEBUG_FILTERS, " - FIF_FCSFAIL"); + wl->rx_filter |= CFG_RX_FCS_ERROR; + } +} + +static int wl1271_dummy_join(struct wl1271 *wl) +{ + int ret = 0; + /* we need to use a dummy BSSID for now */ + static const u8 dummy_bssid[ETH_ALEN] = { 0x0b, 0xad, 0xde, + 0xad, 0xbe, 0xef }; + + memcpy(wl->bssid, dummy_bssid, ETH_ALEN); + + /* pass through frames from all BSS */ + wl1271_configure_filters(wl, FIF_OTHER_BSS); + + ret = wl1271_cmd_join(wl, wl->set_bss_type); + if (ret < 0) + goto out; + + set_bit(WL1271_FLAG_JOINED, &wl->flags); + +out: + return ret; +} + +static int wl1271_join(struct wl1271 *wl, bool set_assoc) +{ + int ret; + + /* + * One of the side effects of the JOIN command is that is clears + * WPA/WPA2 keys from the chipset. Performing a JOIN while associated + * to a WPA/WPA2 access point will therefore kill the data-path. + * Currently the only valid scenario for JOIN during association + * is on roaming, in which case we will also be given new keys. + * Keep the below message for now, unless it starts bothering + * users who really like to roam a lot :) + */ + if (test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) + wl1271_info("JOIN while associated."); + + if (set_assoc) + set_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags); + + ret = wl1271_cmd_join(wl, wl->set_bss_type); + if (ret < 0) + goto out; + + set_bit(WL1271_FLAG_JOINED, &wl->flags); + + if (!test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) + goto out; + + /* + * The join command disable the keep-alive mode, shut down its process, + * and also clear the template config, so we need to reset it all after + * the join. The acx_aid starts the keep-alive process, and the order + * of the commands below is relevant. + */ + ret = wl1271_acx_keep_alive_mode(wl, true); + if (ret < 0) + goto out; + + ret = wl1271_acx_aid(wl, wl->aid); + if (ret < 0) + goto out; + + ret = wl1271_cmd_build_klv_null_data(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_keep_alive_config(wl, CMD_TEMPL_KLV_IDX_NULL_DATA, + ACX_KEEP_ALIVE_TPL_VALID); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wl1271_unjoin(struct wl1271 *wl) +{ + int ret; + + /* to stop listening to a channel, we disconnect */ + ret = wl1271_cmd_disconnect(wl); + if (ret < 0) + goto out; + + clear_bit(WL1271_FLAG_JOINED, &wl->flags); + memset(wl->bssid, 0, ETH_ALEN); + + /* stop filtering packets based on bssid */ + wl1271_configure_filters(wl, FIF_OTHER_BSS); + +out: + return ret; +} + +static void wl1271_set_band_rate(struct wl1271 *wl) +{ + if (wl->band == IEEE80211_BAND_2GHZ) + wl->basic_rate_set = wl->conf.tx.basic_rate; + else + wl->basic_rate_set = wl->conf.tx.basic_rate_5; +} + +static int wl1271_sta_handle_idle(struct wl1271 *wl, bool idle) +{ + int ret; + + if (idle) { + if (test_bit(WL1271_FLAG_JOINED, &wl->flags)) { + ret = wl1271_unjoin(wl); + if (ret < 0) + goto out; + } + wl->rate_set = wl1271_tx_min_rate_get(wl); + ret = wl1271_acx_sta_rate_policies(wl); + if (ret < 0) + goto out; + ret = wl1271_acx_keep_alive_config( + wl, CMD_TEMPL_KLV_IDX_NULL_DATA, + ACX_KEEP_ALIVE_TPL_INVALID); + if (ret < 0) + goto out; + set_bit(WL1271_FLAG_IDLE, &wl->flags); + } else { + /* increment the session counter */ + wl->session_counter++; + if (wl->session_counter >= SESSION_COUNTER_MAX) + wl->session_counter = 0; + + /* The current firmware only supports sched_scan in idle */ + if (wl->sched_scanning) { + wl1271_scan_sched_scan_stop(wl); + ieee80211_sched_scan_stopped(wl->hw); + } + + ret = wl1271_dummy_join(wl); + if (ret < 0) + goto out; + clear_bit(WL1271_FLAG_IDLE, &wl->flags); + } + +out: + return ret; +} + +static int wl1271_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct wl1271 *wl = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + int channel, ret = 0; + bool is_ap; + + channel = ieee80211_frequency_to_channel(conf->channel->center_freq); + + wl1271_debug(DEBUG_MAC80211, "mac80211 config ch %d psm %s power %d %s" + " changed 0x%x", + channel, + conf->flags & IEEE80211_CONF_PS ? "on" : "off", + conf->power_level, + conf->flags & IEEE80211_CONF_IDLE ? "idle" : "in use", + changed); + + /* + * mac80211 will go to idle nearly immediately after transmitting some + * frames, such as the deauth. To make sure those frames reach the air, + * wait here until the TX queue is fully flushed. + */ + if ((changed & IEEE80211_CONF_CHANGE_IDLE) && + (conf->flags & IEEE80211_CONF_IDLE)) + wl1271_tx_flush(wl); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) { + /* we support configuring the channel and band while off */ + if ((changed & IEEE80211_CONF_CHANGE_CHANNEL)) { + wl->band = conf->channel->band; + wl->channel = channel; + } + + goto out; + } + + is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* if the channel changes while joined, join again */ + if (changed & IEEE80211_CONF_CHANGE_CHANNEL && + ((wl->band != conf->channel->band) || + (wl->channel != channel))) { + wl->band = conf->channel->band; + wl->channel = channel; + + if (!is_ap) { + /* + * FIXME: the mac80211 should really provide a fixed + * rate to use here. for now, just use the smallest + * possible rate for the band as a fixed rate for + * association frames and other control messages. + */ + if (!test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) + wl1271_set_band_rate(wl); + + wl->basic_rate = wl1271_tx_min_rate_get(wl); + ret = wl1271_acx_sta_rate_policies(wl); + if (ret < 0) + wl1271_warning("rate policy for channel " + "failed %d", ret); + + if (test_bit(WL1271_FLAG_JOINED, &wl->flags)) { + ret = wl1271_join(wl, false); + if (ret < 0) + wl1271_warning("cmd join on channel " + "failed %d", ret); + } + } + } + + if (changed & IEEE80211_CONF_CHANGE_IDLE && !is_ap) { + ret = wl1271_sta_handle_idle(wl, + conf->flags & IEEE80211_CONF_IDLE); + if (ret < 0) + wl1271_warning("idle mode change failed %d", ret); + } + + /* + * if mac80211 changes the PSM mode, make sure the mode is not + * incorrectly changed after the pspoll failure active window. + */ + if (changed & IEEE80211_CONF_CHANGE_PS) + clear_bit(WL1271_FLAG_PSPOLL_FAILURE, &wl->flags); + + if (conf->flags & IEEE80211_CONF_PS && + !test_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags)) { + set_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags); + + /* + * We enter PSM only if we're already associated. + * If we're not, we'll enter it when joining an SSID, + * through the bss_info_changed() hook. + */ + if (test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) { + wl1271_debug(DEBUG_PSM, "psm enabled"); + ret = wl1271_ps_set_mode(wl, STATION_POWER_SAVE_MODE, + wl->basic_rate, true); + } + } else if (!(conf->flags & IEEE80211_CONF_PS) && + test_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags)) { + wl1271_debug(DEBUG_PSM, "psm disabled"); + + clear_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags); + + if (test_bit(WL1271_FLAG_PSM, &wl->flags)) + ret = wl1271_ps_set_mode(wl, STATION_ACTIVE_MODE, + wl->basic_rate, true); + } + + if (conf->power_level != wl->power_level) { + ret = wl1271_acx_tx_power(wl, conf->power_level); + if (ret < 0) + goto out_sleep; + + wl->power_level = conf->power_level; + } + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +struct wl1271_filter_params { + bool enabled; + int mc_list_length; + u8 mc_list[ACX_MC_ADDRESS_GROUP_MAX][ETH_ALEN]; +}; + +static u64 wl1271_op_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct wl1271_filter_params *fp; + struct netdev_hw_addr *ha; + struct wl1271 *wl = hw->priv; + + if (unlikely(wl->state == WL1271_STATE_OFF)) + return 0; + + fp = kzalloc(sizeof(*fp), GFP_ATOMIC); + if (!fp) { + wl1271_error("Out of memory setting filters."); + return 0; + } + + /* update multicast filtering parameters */ + fp->mc_list_length = 0; + if (netdev_hw_addr_list_count(mc_list) > ACX_MC_ADDRESS_GROUP_MAX) { + fp->enabled = false; + } else { + fp->enabled = true; + netdev_hw_addr_list_for_each(ha, mc_list) { + memcpy(fp->mc_list[fp->mc_list_length], + ha->addr, ETH_ALEN); + fp->mc_list_length++; + } + } + + return (u64)(unsigned long)fp; +} + +#define WL1271_SUPPORTED_FILTERS (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_FCSFAIL | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_CONTROL | \ + FIF_OTHER_BSS) + +static void wl1271_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed, + unsigned int *total, u64 multicast) +{ + struct wl1271_filter_params *fp = (void *)(unsigned long)multicast; + struct wl1271 *wl = hw->priv; + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 configure filter changed %x" + " total %x", changed, *total); + + mutex_lock(&wl->mutex); + + *total &= WL1271_SUPPORTED_FILTERS; + changed &= WL1271_SUPPORTED_FILTERS; + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (wl->bss_type != BSS_TYPE_AP_BSS) { + if (*total & FIF_ALLMULTI) + ret = wl1271_acx_group_address_tbl(wl, false, NULL, 0); + else if (fp) + ret = wl1271_acx_group_address_tbl(wl, fp->enabled, + fp->mc_list, + fp->mc_list_length); + if (ret < 0) + goto out_sleep; + } + + /* determine, whether supported filter values have changed */ + if (changed == 0) + goto out_sleep; + + /* configure filters */ + wl->filters = *total; + wl1271_configure_filters(wl, 0); + + /* apply configured filters */ + ret = wl1271_acx_rx_config(wl, wl->rx_config, wl->rx_filter); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + kfree(fp); +} + +static int wl1271_record_ap_key(struct wl1271 *wl, u8 id, u8 key_type, + u8 key_size, const u8 *key, u8 hlid, u32 tx_seq_32, + u16 tx_seq_16) +{ + struct wl1271_ap_key *ap_key; + int i; + + wl1271_debug(DEBUG_CRYPT, "record ap key id %d", (int)id); + + if (key_size > MAX_KEY_SIZE) + return -EINVAL; + + /* + * Find next free entry in ap_keys. Also check we are not replacing + * an existing key. + */ + for (i = 0; i < MAX_NUM_KEYS; i++) { + if (wl->recorded_ap_keys[i] == NULL) + break; + + if (wl->recorded_ap_keys[i]->id == id) { + wl1271_warning("trying to record key replacement"); + return -EINVAL; + } + } + + if (i == MAX_NUM_KEYS) + return -EBUSY; + + ap_key = kzalloc(sizeof(*ap_key), GFP_KERNEL); + if (!ap_key) + return -ENOMEM; + + ap_key->id = id; + ap_key->key_type = key_type; + ap_key->key_size = key_size; + memcpy(ap_key->key, key, key_size); + ap_key->hlid = hlid; + ap_key->tx_seq_32 = tx_seq_32; + ap_key->tx_seq_16 = tx_seq_16; + + wl->recorded_ap_keys[i] = ap_key; + return 0; +} + +static void wl1271_free_ap_keys(struct wl1271 *wl) +{ + int i; + + for (i = 0; i < MAX_NUM_KEYS; i++) { + kfree(wl->recorded_ap_keys[i]); + wl->recorded_ap_keys[i] = NULL; + } +} + +static int wl1271_ap_init_hwenc(struct wl1271 *wl) +{ + int i, ret = 0; + struct wl1271_ap_key *key; + bool wep_key_added = false; + + for (i = 0; i < MAX_NUM_KEYS; i++) { + if (wl->recorded_ap_keys[i] == NULL) + break; + + key = wl->recorded_ap_keys[i]; + ret = wl1271_cmd_set_ap_key(wl, KEY_ADD_OR_REPLACE, + key->id, key->key_type, + key->key_size, key->key, + key->hlid, key->tx_seq_32, + key->tx_seq_16); + if (ret < 0) + goto out; + + if (key->key_type == KEY_WEP) + wep_key_added = true; + } + + if (wep_key_added) { + ret = wl1271_cmd_set_ap_default_wep_key(wl, wl->default_key); + if (ret < 0) + goto out; + } + +out: + wl1271_free_ap_keys(wl); + return ret; +} + +static int wl1271_set_key(struct wl1271 *wl, u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, u32 tx_seq_32, + u16 tx_seq_16, struct ieee80211_sta *sta) +{ + int ret; + bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + + if (is_ap) { + struct wl1271_station *wl_sta; + u8 hlid; + + if (sta) { + wl_sta = (struct wl1271_station *)sta->drv_priv; + hlid = wl_sta->hlid; + } else { + hlid = WL1271_AP_BROADCAST_HLID; + } + + if (!test_bit(WL1271_FLAG_AP_STARTED, &wl->flags)) { + /* + * We do not support removing keys after AP shutdown. + * Pretend we do to make mac80211 happy. + */ + if (action != KEY_ADD_OR_REPLACE) + return 0; + + ret = wl1271_record_ap_key(wl, id, + key_type, key_size, + key, hlid, tx_seq_32, + tx_seq_16); + } else { + ret = wl1271_cmd_set_ap_key(wl, action, + id, key_type, key_size, + key, hlid, tx_seq_32, + tx_seq_16); + } + + if (ret < 0) + return ret; + } else { + const u8 *addr; + static const u8 bcast_addr[ETH_ALEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + addr = sta ? sta->addr : bcast_addr; + + if (is_zero_ether_addr(addr)) { + /* We dont support TX only encryption */ + return -EOPNOTSUPP; + } + + /* The wl1271 does not allow to remove unicast keys - they + will be cleared automatically on next CMD_JOIN. Ignore the + request silently, as we dont want the mac80211 to emit + an error message. */ + if (action == KEY_REMOVE && !is_broadcast_ether_addr(addr)) + return 0; + + ret = wl1271_cmd_set_sta_key(wl, action, + id, key_type, key_size, + key, addr, tx_seq_32, + tx_seq_16); + if (ret < 0) + return ret; + + /* the default WEP key needs to be configured at least once */ + if (key_type == KEY_WEP) { + ret = wl1271_cmd_set_sta_default_wep_key(wl, + wl->default_key); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int wl1271_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + struct wl1271 *wl = hw->priv; + int ret; + u32 tx_seq_32 = 0; + u16 tx_seq_16 = 0; + u8 key_type; + + wl1271_debug(DEBUG_MAC80211, "mac80211 set key"); + + wl1271_debug(DEBUG_CRYPT, "CMD: 0x%x sta: %p", cmd, sta); + wl1271_debug(DEBUG_CRYPT, "Key: algo:0x%x, id:%d, len:%d flags 0x%x", + key_conf->cipher, key_conf->keyidx, + key_conf->keylen, key_conf->flags); + wl1271_dump(DEBUG_CRYPT, "KEY: ", key_conf->key, key_conf->keylen); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) { + ret = -EAGAIN; + goto out_unlock; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + + switch (key_conf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + key_type = KEY_WEP; + + key_conf->hw_key_idx = key_conf->keyidx; + break; + case WLAN_CIPHER_SUITE_TKIP: + key_type = KEY_TKIP; + + key_conf->hw_key_idx = key_conf->keyidx; + tx_seq_32 = WL1271_TX_SECURITY_HI32(wl->tx_security_seq); + tx_seq_16 = WL1271_TX_SECURITY_LO16(wl->tx_security_seq); + break; + case WLAN_CIPHER_SUITE_CCMP: + key_type = KEY_AES; + + key_conf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + tx_seq_32 = WL1271_TX_SECURITY_HI32(wl->tx_security_seq); + tx_seq_16 = WL1271_TX_SECURITY_LO16(wl->tx_security_seq); + break; + case WL1271_CIPHER_SUITE_GEM: + key_type = KEY_GEM; + tx_seq_32 = WL1271_TX_SECURITY_HI32(wl->tx_security_seq); + tx_seq_16 = WL1271_TX_SECURITY_LO16(wl->tx_security_seq); + break; + default: + wl1271_error("Unknown key algo 0x%x", key_conf->cipher); + + ret = -EOPNOTSUPP; + goto out_sleep; + } + + switch (cmd) { + case SET_KEY: + ret = wl1271_set_key(wl, KEY_ADD_OR_REPLACE, + key_conf->keyidx, key_type, + key_conf->keylen, key_conf->key, + tx_seq_32, tx_seq_16, sta); + if (ret < 0) { + wl1271_error("Could not add or replace key"); + goto out_sleep; + } + break; + + case DISABLE_KEY: + ret = wl1271_set_key(wl, KEY_REMOVE, + key_conf->keyidx, key_type, + key_conf->keylen, key_conf->key, + 0, 0, sta); + if (ret < 0) { + wl1271_error("Could not remove key"); + goto out_sleep; + } + break; + + default: + wl1271_error("Unsupported key cmd 0x%x", cmd); + ret = -EOPNOTSUPP; + break; + } + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out_unlock: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_op_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct wl1271 *wl = hw->priv; + int ret; + u8 *ssid = NULL; + size_t len = 0; + + wl1271_debug(DEBUG_MAC80211, "mac80211 hw scan"); + + if (req->n_ssids) { + ssid = req->ssids[0].ssid; + len = req->ssids[0].ssid_len; + } + + mutex_lock(&wl->mutex); + + if (wl->state == WL1271_STATE_OFF) { + /* + * We cannot return -EBUSY here because cfg80211 will expect + * a call to ieee80211_scan_completed if we do - in this case + * there won't be any call. + */ + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_scan(hw->priv, ssid, len, req); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_op_sched_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl1271 *wl = hw->priv; + int ret; + + wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_start"); + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_scan_sched_scan_config(wl, req, ies); + if (ret < 0) + goto out_sleep; + + ret = wl1271_scan_sched_scan_start(wl); + if (ret < 0) + goto out_sleep; + + wl->sched_scanning = true; + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static void wl1271_op_sched_scan_stop(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + int ret; + + wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_stop"); + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_scan_sched_scan_stop(wl); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct wl1271 *wl = hw->priv; + int ret = 0; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) { + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_frag_threshold(wl, value); + if (ret < 0) + wl1271_warning("wl1271_op_set_frag_threshold failed: %d", ret); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct wl1271 *wl = hw->priv; + int ret = 0; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) { + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_rts_threshold(wl, value); + if (ret < 0) + wl1271_warning("wl1271_op_set_rts_threshold failed: %d", ret); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_ssid_set(struct wl1271 *wl, struct sk_buff *skb, + int offset) +{ + u8 ssid_len; + const u8 *ptr = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset, + skb->len - offset); + + if (!ptr) { + wl1271_error("No SSID in IEs!"); + return -ENOENT; + } + + ssid_len = ptr[1]; + if (ssid_len > IEEE80211_MAX_SSID_LEN) { + wl1271_error("SSID is too long!"); + return -EINVAL; + } + + wl->ssid_len = ssid_len; + memcpy(wl->ssid, ptr+2, ssid_len); + return 0; +} + +static int wl1271_bss_erp_info_changed(struct wl1271 *wl, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + int ret = 0; + + if (changed & BSS_CHANGED_ERP_SLOT) { + if (bss_conf->use_short_slot) + ret = wl1271_acx_slot(wl, SLOT_TIME_SHORT); + else + ret = wl1271_acx_slot(wl, SLOT_TIME_LONG); + if (ret < 0) { + wl1271_warning("Set slot time failed %d", ret); + goto out; + } + } + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + if (bss_conf->use_short_preamble) + wl1271_acx_set_preamble(wl, ACX_PREAMBLE_SHORT); + else + wl1271_acx_set_preamble(wl, ACX_PREAMBLE_LONG); + } + + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + if (bss_conf->use_cts_prot) + ret = wl1271_acx_cts_protect(wl, CTSPROTECT_ENABLE); + else + ret = wl1271_acx_cts_protect(wl, CTSPROTECT_DISABLE); + if (ret < 0) { + wl1271_warning("Set ctsprotect failed %d", ret); + goto out; + } + } + +out: + return ret; +} + +static int wl1271_bss_beacon_info_changed(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + int ret = 0; + + if ((changed & BSS_CHANGED_BEACON_INT)) { + wl1271_debug(DEBUG_MASTER, "beacon interval updated: %d", + bss_conf->beacon_int); + + wl->beacon_int = bss_conf->beacon_int; + } + + if ((changed & BSS_CHANGED_BEACON)) { + struct ieee80211_hdr *hdr; + int ieoffset = offsetof(struct ieee80211_mgmt, + u.beacon.variable); + struct sk_buff *beacon = ieee80211_beacon_get(wl->hw, vif); + u16 tmpl_id; + + if (!beacon) + goto out; + + wl1271_debug(DEBUG_MASTER, "beacon updated"); + + ret = wl1271_ssid_set(wl, beacon, ieoffset); + if (ret < 0) { + dev_kfree_skb(beacon); + goto out; + } + tmpl_id = is_ap ? CMD_TEMPL_AP_BEACON : + CMD_TEMPL_BEACON; + ret = wl1271_cmd_template_set(wl, tmpl_id, + beacon->data, + beacon->len, 0, + wl1271_tx_min_rate_get(wl)); + if (ret < 0) { + dev_kfree_skb(beacon); + goto out; + } + + hdr = (struct ieee80211_hdr *) beacon->data; + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + + tmpl_id = is_ap ? CMD_TEMPL_AP_PROBE_RESPONSE : + CMD_TEMPL_PROBE_RESPONSE; + ret = wl1271_cmd_template_set(wl, + tmpl_id, + beacon->data, + beacon->len, 0, + wl1271_tx_min_rate_get(wl)); + dev_kfree_skb(beacon); + if (ret < 0) + goto out; + } + +out: + return ret; +} + +/* AP mode changes */ +static void wl1271_bss_info_changed_ap(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + int ret = 0; + + if ((changed & BSS_CHANGED_BASIC_RATES)) { + u32 rates = bss_conf->basic_rates; + + wl->basic_rate_set = wl1271_tx_enabled_rates_get(wl, rates); + wl->basic_rate = wl1271_tx_min_rate_get(wl); + + ret = wl1271_init_ap_rates(wl); + if (ret < 0) { + wl1271_error("AP rate policy change failed %d", ret); + goto out; + } + + ret = wl1271_ap_init_templates(wl); + if (ret < 0) + goto out; + } + + ret = wl1271_bss_beacon_info_changed(wl, vif, bss_conf, changed); + if (ret < 0) + goto out; + + if ((changed & BSS_CHANGED_BEACON_ENABLED)) { + if (bss_conf->enable_beacon) { + if (!test_bit(WL1271_FLAG_AP_STARTED, &wl->flags)) { + ret = wl1271_cmd_start_bss(wl); + if (ret < 0) + goto out; + + set_bit(WL1271_FLAG_AP_STARTED, &wl->flags); + wl1271_debug(DEBUG_AP, "started AP"); + + ret = wl1271_ap_init_hwenc(wl); + if (ret < 0) + goto out; + } + } else { + if (test_bit(WL1271_FLAG_AP_STARTED, &wl->flags)) { + ret = wl1271_cmd_stop_bss(wl); + if (ret < 0) + goto out; + + clear_bit(WL1271_FLAG_AP_STARTED, &wl->flags); + wl1271_debug(DEBUG_AP, "stopped AP"); + } + } + } + + if (changed & BSS_CHANGED_IBSS) { + wl1271_debug(DEBUG_ADHOC, "ibss_joined: %d", + bss_conf->ibss_joined); + + if (bss_conf->ibss_joined) { + u32 rates = bss_conf->basic_rates; + wl->basic_rate_set = wl1271_tx_enabled_rates_get(wl, + rates); + wl->basic_rate = wl1271_tx_min_rate_get(wl); + + /* by default, use 11b rates */ + wl->rate_set = CONF_TX_IBSS_DEFAULT_RATES; + ret = wl1271_acx_sta_rate_policies(wl); + if (ret < 0) + goto out; + } + } + + ret = wl1271_bss_erp_info_changed(wl, bss_conf, changed); + if (ret < 0) + goto out; +out: + return; +} + +/* STA/IBSS mode changes */ +static void wl1271_bss_info_changed_sta(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + bool do_join = false, set_assoc = false; + bool is_ibss = (wl->bss_type == BSS_TYPE_IBSS); + u32 sta_rate_set = 0; + int ret; + struct ieee80211_sta *sta; + bool sta_exists = false; + struct ieee80211_sta_ht_cap sta_ht_cap; + + if (is_ibss) { + ret = wl1271_bss_beacon_info_changed(wl, vif, bss_conf, + changed); + if (ret < 0) + goto out; + } + + if ((changed & BSS_CHANGED_BEACON_INT) && is_ibss) + do_join = true; + + /* Need to update the SSID (for filtering etc) */ + if ((changed & BSS_CHANGED_BEACON) && is_ibss) + do_join = true; + + if ((changed & BSS_CHANGED_BEACON_ENABLED) && is_ibss) { + wl1271_debug(DEBUG_ADHOC, "ad-hoc beaconing: %s", + bss_conf->enable_beacon ? "enabled" : "disabled"); + + if (bss_conf->enable_beacon) + wl->set_bss_type = BSS_TYPE_IBSS; + else + wl->set_bss_type = BSS_TYPE_STA_BSS; + do_join = true; + } + + if ((changed & BSS_CHANGED_CQM)) { + bool enable = false; + if (bss_conf->cqm_rssi_thold) + enable = true; + ret = wl1271_acx_rssi_snr_trigger(wl, enable, + bss_conf->cqm_rssi_thold, + bss_conf->cqm_rssi_hyst); + if (ret < 0) + goto out; + wl->rssi_thold = bss_conf->cqm_rssi_thold; + } + + if ((changed & BSS_CHANGED_BSSID) && + /* + * Now we know the correct bssid, so we send a new join command + * and enable the BSSID filter + */ + memcmp(wl->bssid, bss_conf->bssid, ETH_ALEN)) { + memcpy(wl->bssid, bss_conf->bssid, ETH_ALEN); + + if (!is_zero_ether_addr(wl->bssid)) { + ret = wl1271_cmd_build_null_data(wl); + if (ret < 0) + goto out; + + ret = wl1271_build_qos_null_data(wl); + if (ret < 0) + goto out; + + /* filter out all packets not from this BSSID */ + wl1271_configure_filters(wl, 0); + + /* Need to update the BSSID (for filtering etc) */ + do_join = true; + } + } + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (sta) { + /* save the supp_rates of the ap */ + sta_rate_set = sta->supp_rates[wl->hw->conf.channel->band]; + if (sta->ht_cap.ht_supported) + sta_rate_set |= + (sta->ht_cap.mcs.rx_mask[0] << HW_HT_RATES_OFFSET); + sta_ht_cap = sta->ht_cap; + sta_exists = true; + } + rcu_read_unlock(); + + if (sta_exists) { + /* handle new association with HT and HT information change */ + if ((changed & BSS_CHANGED_HT) && + (bss_conf->channel_type != NL80211_CHAN_NO_HT)) { + ret = wl1271_acx_set_ht_capabilities(wl, &sta_ht_cap, + true); + if (ret < 0) { + wl1271_warning("Set ht cap true failed %d", + ret); + goto out; + } + ret = wl1271_acx_set_ht_information(wl, + bss_conf->ht_operation_mode); + if (ret < 0) { + wl1271_warning("Set ht information failed %d", + ret); + goto out; + } + } + /* handle new association without HT and disassociation */ + else if (changed & BSS_CHANGED_ASSOC) { + ret = wl1271_acx_set_ht_capabilities(wl, &sta_ht_cap, + false); + if (ret < 0) { + wl1271_warning("Set ht cap false failed %d", + ret); + goto out; + } + } + } + + if ((changed & BSS_CHANGED_ASSOC)) { + if (bss_conf->assoc) { + u32 rates; + int ieoffset; + wl->aid = bss_conf->aid; + set_assoc = true; + + wl->ps_poll_failures = 0; + + /* + * use basic rates from AP, and determine lowest rate + * to use with control frames. + */ + rates = bss_conf->basic_rates; + wl->basic_rate_set = wl1271_tx_enabled_rates_get(wl, + rates); + wl->basic_rate = wl1271_tx_min_rate_get(wl); + if (sta_rate_set) + wl->rate_set = wl1271_tx_enabled_rates_get(wl, + sta_rate_set); + ret = wl1271_acx_sta_rate_policies(wl); + if (ret < 0) + goto out; + + /* + * with wl1271, we don't need to update the + * beacon_int and dtim_period, because the firmware + * updates it by itself when the first beacon is + * received after a join. + */ + ret = wl1271_cmd_build_ps_poll(wl, wl->aid); + if (ret < 0) + goto out; + + /* + * Get a template for hardware connection maintenance + */ + dev_kfree_skb(wl->probereq); + wl->probereq = wl1271_cmd_build_ap_probe_req(wl, NULL); + ieoffset = offsetof(struct ieee80211_mgmt, + u.probe_req.variable); + wl1271_ssid_set(wl, wl->probereq, ieoffset); + + /* enable the connection monitoring feature */ + ret = wl1271_acx_conn_monit_params(wl, true); + if (ret < 0) + goto out; + + /* If we want to go in PSM but we're not there yet */ + if (test_bit(WL1271_FLAG_PSM_REQUESTED, &wl->flags) && + !test_bit(WL1271_FLAG_PSM, &wl->flags)) { + enum wl1271_cmd_ps_mode mode; + + mode = STATION_POWER_SAVE_MODE; + ret = wl1271_ps_set_mode(wl, mode, + wl->basic_rate, + true); + if (ret < 0) + goto out; + } + } else { + /* use defaults when not associated */ + bool was_assoc = + !!test_and_clear_bit(WL1271_FLAG_STA_ASSOCIATED, + &wl->flags); + clear_bit(WL1271_FLAG_STA_STATE_SENT, &wl->flags); + wl->aid = 0; + + /* free probe-request template */ + dev_kfree_skb(wl->probereq); + wl->probereq = NULL; + + /* re-enable dynamic ps - just in case */ + ieee80211_enable_dyn_ps(wl->vif); + + /* revert back to minimum rates for the current band */ + wl1271_set_band_rate(wl); + wl->basic_rate = wl1271_tx_min_rate_get(wl); + ret = wl1271_acx_sta_rate_policies(wl); + if (ret < 0) + goto out; + + /* disable connection monitor features */ + ret = wl1271_acx_conn_monit_params(wl, false); + + /* Disable the keep-alive feature */ + ret = wl1271_acx_keep_alive_mode(wl, false); + if (ret < 0) + goto out; + + /* restore the bssid filter and go to dummy bssid */ + if (was_assoc) { + wl1271_unjoin(wl); + wl1271_dummy_join(wl); + } + } + } + + ret = wl1271_bss_erp_info_changed(wl, bss_conf, changed); + if (ret < 0) + goto out; + + if (changed & BSS_CHANGED_ARP_FILTER) { + __be32 addr = bss_conf->arp_addr_list[0]; + WARN_ON(wl->bss_type != BSS_TYPE_STA_BSS); + + if (bss_conf->arp_addr_cnt == 1 && + bss_conf->arp_filter_enabled) { + /* + * The template should have been configured only upon + * association. however, it seems that the correct ip + * isn't being set (when sending), so we have to + * reconfigure the template upon every ip change. + */ + ret = wl1271_cmd_build_arp_rsp(wl, addr); + if (ret < 0) { + wl1271_warning("build arp rsp failed: %d", ret); + goto out; + } + + ret = wl1271_acx_arp_ip_filter(wl, + ACX_ARP_FILTER_ARP_FILTERING, + addr); + } else + ret = wl1271_acx_arp_ip_filter(wl, 0, addr); + + if (ret < 0) + goto out; + } + + if (do_join) { + ret = wl1271_join(wl, set_assoc); + if (ret < 0) { + wl1271_warning("cmd join failed %d", ret); + goto out; + } + } + +out: + return; +} + +static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl1271 *wl = hw->priv; + bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 bss info changed 0x%x", + (int)changed); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (is_ap) + wl1271_bss_info_changed_ap(wl, vif, bss_conf, changed); + else + wl1271_bss_info_changed_sta(wl, vif, bss_conf, changed); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +static int wl1271_op_conf_tx(struct ieee80211_hw *hw, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct wl1271 *wl = hw->priv; + u8 ps_scheme; + int ret = 0; + + mutex_lock(&wl->mutex); + + wl1271_debug(DEBUG_MAC80211, "mac80211 conf tx %d", queue); + + if (params->uapsd) + ps_scheme = CONF_PS_SCHEME_UPSD_TRIGGER; + else + ps_scheme = CONF_PS_SCHEME_LEGACY; + + if (wl->state == WL1271_STATE_OFF) { + /* + * If the state is off, the parameters will be recorded and + * configured on init. This happens in AP-mode. + */ + struct conf_tx_ac_category *conf_ac = + &wl->conf.tx.ac_conf[wl1271_tx_get_queue(queue)]; + struct conf_tx_tid *conf_tid = + &wl->conf.tx.tid_conf[wl1271_tx_get_queue(queue)]; + + conf_ac->ac = wl1271_tx_get_queue(queue); + conf_ac->cw_min = (u8)params->cw_min; + conf_ac->cw_max = params->cw_max; + conf_ac->aifsn = params->aifs; + conf_ac->tx_op_limit = params->txop << 5; + + conf_tid->queue_id = wl1271_tx_get_queue(queue); + conf_tid->channel_type = CONF_CHANNEL_TYPE_EDCF; + conf_tid->tsid = wl1271_tx_get_queue(queue); + conf_tid->ps_scheme = ps_scheme; + conf_tid->ack_policy = CONF_ACK_POLICY_LEGACY; + conf_tid->apsd_conf[0] = 0; + conf_tid->apsd_conf[1] = 0; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* + * the txop is confed in units of 32us by the mac80211, + * we need us + */ + ret = wl1271_acx_ac_cfg(wl, wl1271_tx_get_queue(queue), + params->cw_min, params->cw_max, + params->aifs, params->txop << 5); + if (ret < 0) + goto out_sleep; + + ret = wl1271_acx_tid_cfg(wl, wl1271_tx_get_queue(queue), + CONF_CHANNEL_TYPE_EDCF, + wl1271_tx_get_queue(queue), + ps_scheme, CONF_ACK_POLICY_LEGACY, + 0, 0); + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static u64 wl1271_op_get_tsf(struct ieee80211_hw *hw) +{ + + struct wl1271 *wl = hw->priv; + u64 mactime = ULLONG_MAX; + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 get tsf"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_tsf_info(wl, &mactime); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + return mactime; +} + +static int wl1271_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct wl1271 *wl = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->channel; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = wl->noise; + + return 0; +} + +static int wl1271_allocate_sta(struct wl1271 *wl, + struct ieee80211_sta *sta, + u8 *hlid) +{ + struct wl1271_station *wl_sta; + int id; + + id = find_first_zero_bit(wl->ap_hlid_map, AP_MAX_STATIONS); + if (id >= AP_MAX_STATIONS) { + wl1271_warning("could not allocate HLID - too much stations"); + return -EBUSY; + } + + wl_sta = (struct wl1271_station *)sta->drv_priv; + __set_bit(id, wl->ap_hlid_map); + wl_sta->hlid = WL1271_AP_STA_HLID_START + id; + *hlid = wl_sta->hlid; + memcpy(wl->links[wl_sta->hlid].addr, sta->addr, ETH_ALEN); + return 0; +} + +static void wl1271_free_sta(struct wl1271 *wl, u8 hlid) +{ + int id = hlid - WL1271_AP_STA_HLID_START; + + if (WARN_ON(!test_bit(id, wl->ap_hlid_map))) + return; + + __clear_bit(id, wl->ap_hlid_map); + memset(wl->links[hlid].addr, 0, ETH_ALEN); + wl1271_tx_reset_link_queues(wl, hlid); + __clear_bit(hlid, &wl->ap_ps_map); + __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); +} + +static int wl1271_op_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct wl1271 *wl = hw->priv; + int ret = 0; + u8 hlid; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + if (wl->bss_type != BSS_TYPE_AP_BSS) + goto out; + + wl1271_debug(DEBUG_MAC80211, "mac80211 add sta %d", (int)sta->aid); + + ret = wl1271_allocate_sta(wl, sta, &hlid); + if (ret < 0) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_free_sta; + + ret = wl1271_cmd_add_sta(wl, sta, hlid); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out_free_sta: + if (ret < 0) + wl1271_free_sta(wl, hlid); + +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static int wl1271_op_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct wl1271 *wl = hw->priv; + struct wl1271_station *wl_sta; + int ret = 0, id; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + if (wl->bss_type != BSS_TYPE_AP_BSS) + goto out; + + wl1271_debug(DEBUG_MAC80211, "mac80211 remove sta %d", (int)sta->aid); + + wl_sta = (struct wl1271_station *)sta->drv_priv; + id = wl_sta->hlid - WL1271_AP_STA_HLID_START; + if (WARN_ON(!test_bit(id, wl->ap_hlid_map))) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_cmd_remove_sta(wl, wl_sta->hlid); + if (ret < 0) + goto out_sleep; + + wl1271_free_sta(wl, wl_sta->hlid); + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static int wl1271_op_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + struct wl1271 *wl = hw->priv; + int ret; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) { + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (wl->ba_support) { + ret = wl1271_acx_set_ba_receiver_session(wl, tid, *ssn, + true); + if (!ret) + wl->ba_rx_bitmap |= BIT(tid); + } else { + ret = -ENOTSUPP; + } + break; + + case IEEE80211_AMPDU_RX_STOP: + ret = wl1271_acx_set_ba_receiver_session(wl, tid, 0, false); + if (!ret) + wl->ba_rx_bitmap &= ~BIT(tid); + break; + + /* + * The BA initiator session management in FW independently. + * Falling break here on purpose for all TX APDU commands. + */ + case IEEE80211_AMPDU_TX_START: + case IEEE80211_AMPDU_TX_STOP: + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = -EINVAL; + break; + + default: + wl1271_error("Incorrect ampdu action id=%x\n", action); + ret = -EINVAL; + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static bool wl1271_tx_frames_pending(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + bool ret = false; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + /* packets are considered pending if in the TX queue or the FW */ + ret = (wl->tx_queue_count > 0) || (wl->tx_frames_cnt > 0); + + /* the above is appropriate for STA mode for PS purposes */ + WARN_ON(wl->bss_type != BSS_TYPE_STA_BSS); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_rate wl1271_rates[] = { + { .bitrate = 10, + .hw_value = CONF_HW_BIT_RATE_1MBPS, + .hw_value_short = CONF_HW_BIT_RATE_1MBPS, }, + { .bitrate = 20, + .hw_value = CONF_HW_BIT_RATE_2MBPS, + .hw_value_short = CONF_HW_BIT_RATE_2MBPS, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = CONF_HW_BIT_RATE_5_5MBPS, + .hw_value_short = CONF_HW_BIT_RATE_5_5MBPS, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = CONF_HW_BIT_RATE_11MBPS, + .hw_value_short = CONF_HW_BIT_RATE_11MBPS, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, + .hw_value = CONF_HW_BIT_RATE_6MBPS, + .hw_value_short = CONF_HW_BIT_RATE_6MBPS, }, + { .bitrate = 90, + .hw_value = CONF_HW_BIT_RATE_9MBPS, + .hw_value_short = CONF_HW_BIT_RATE_9MBPS, }, + { .bitrate = 120, + .hw_value = CONF_HW_BIT_RATE_12MBPS, + .hw_value_short = CONF_HW_BIT_RATE_12MBPS, }, + { .bitrate = 180, + .hw_value = CONF_HW_BIT_RATE_18MBPS, + .hw_value_short = CONF_HW_BIT_RATE_18MBPS, }, + { .bitrate = 240, + .hw_value = CONF_HW_BIT_RATE_24MBPS, + .hw_value_short = CONF_HW_BIT_RATE_24MBPS, }, + { .bitrate = 360, + .hw_value = CONF_HW_BIT_RATE_36MBPS, + .hw_value_short = CONF_HW_BIT_RATE_36MBPS, }, + { .bitrate = 480, + .hw_value = CONF_HW_BIT_RATE_48MBPS, + .hw_value_short = CONF_HW_BIT_RATE_48MBPS, }, + { .bitrate = 540, + .hw_value = CONF_HW_BIT_RATE_54MBPS, + .hw_value_short = CONF_HW_BIT_RATE_54MBPS, }, +}; + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_channel wl1271_channels[] = { + { .hw_value = 1, .center_freq = 2412, .max_power = 25 }, + { .hw_value = 2, .center_freq = 2417, .max_power = 25 }, + { .hw_value = 3, .center_freq = 2422, .max_power = 25 }, + { .hw_value = 4, .center_freq = 2427, .max_power = 25 }, + { .hw_value = 5, .center_freq = 2432, .max_power = 25 }, + { .hw_value = 6, .center_freq = 2437, .max_power = 25 }, + { .hw_value = 7, .center_freq = 2442, .max_power = 25 }, + { .hw_value = 8, .center_freq = 2447, .max_power = 25 }, + { .hw_value = 9, .center_freq = 2452, .max_power = 25 }, + { .hw_value = 10, .center_freq = 2457, .max_power = 25 }, + { .hw_value = 11, .center_freq = 2462, .max_power = 25 }, + { .hw_value = 12, .center_freq = 2467, .max_power = 25 }, + { .hw_value = 13, .center_freq = 2472, .max_power = 25 }, + { .hw_value = 14, .center_freq = 2484, .max_power = 25 }, +}; + +/* mapping to indexes for wl1271_rates */ +static const u8 wl1271_rate_to_idx_2ghz[] = { + /* MCS rates are used only with 11n */ + 7, /* CONF_HW_RXTX_RATE_MCS7 */ + 6, /* CONF_HW_RXTX_RATE_MCS6 */ + 5, /* CONF_HW_RXTX_RATE_MCS5 */ + 4, /* CONF_HW_RXTX_RATE_MCS4 */ + 3, /* CONF_HW_RXTX_RATE_MCS3 */ + 2, /* CONF_HW_RXTX_RATE_MCS2 */ + 1, /* CONF_HW_RXTX_RATE_MCS1 */ + 0, /* CONF_HW_RXTX_RATE_MCS0 */ + + 11, /* CONF_HW_RXTX_RATE_54 */ + 10, /* CONF_HW_RXTX_RATE_48 */ + 9, /* CONF_HW_RXTX_RATE_36 */ + 8, /* CONF_HW_RXTX_RATE_24 */ + + /* TI-specific rate */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_22 */ + + 7, /* CONF_HW_RXTX_RATE_18 */ + 6, /* CONF_HW_RXTX_RATE_12 */ + 3, /* CONF_HW_RXTX_RATE_11 */ + 5, /* CONF_HW_RXTX_RATE_9 */ + 4, /* CONF_HW_RXTX_RATE_6 */ + 2, /* CONF_HW_RXTX_RATE_5_5 */ + 1, /* CONF_HW_RXTX_RATE_2 */ + 0 /* CONF_HW_RXTX_RATE_1 */ +}; + +/* 11n STA capabilities */ +#define HW_RX_HIGHEST_RATE 72 + +#ifdef CONFIG_WL12XX_HT +#define WL12XX_HT_CAP { \ + .cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 | \ + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT), \ + .ht_supported = true, \ + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, \ + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_8, \ + .mcs = { \ + .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, \ + .rx_highest = cpu_to_le16(HW_RX_HIGHEST_RATE), \ + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, \ + }, \ +} +#else +#define WL12XX_HT_CAP { \ + .ht_supported = false, \ +} +#endif + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_supported_band wl1271_band_2ghz = { + .channels = wl1271_channels, + .n_channels = ARRAY_SIZE(wl1271_channels), + .bitrates = wl1271_rates, + .n_bitrates = ARRAY_SIZE(wl1271_rates), + .ht_cap = WL12XX_HT_CAP, +}; + +/* 5 GHz data rates for WL1273 */ +static struct ieee80211_rate wl1271_rates_5ghz[] = { + { .bitrate = 60, + .hw_value = CONF_HW_BIT_RATE_6MBPS, + .hw_value_short = CONF_HW_BIT_RATE_6MBPS, }, + { .bitrate = 90, + .hw_value = CONF_HW_BIT_RATE_9MBPS, + .hw_value_short = CONF_HW_BIT_RATE_9MBPS, }, + { .bitrate = 120, + .hw_value = CONF_HW_BIT_RATE_12MBPS, + .hw_value_short = CONF_HW_BIT_RATE_12MBPS, }, + { .bitrate = 180, + .hw_value = CONF_HW_BIT_RATE_18MBPS, + .hw_value_short = CONF_HW_BIT_RATE_18MBPS, }, + { .bitrate = 240, + .hw_value = CONF_HW_BIT_RATE_24MBPS, + .hw_value_short = CONF_HW_BIT_RATE_24MBPS, }, + { .bitrate = 360, + .hw_value = CONF_HW_BIT_RATE_36MBPS, + .hw_value_short = CONF_HW_BIT_RATE_36MBPS, }, + { .bitrate = 480, + .hw_value = CONF_HW_BIT_RATE_48MBPS, + .hw_value_short = CONF_HW_BIT_RATE_48MBPS, }, + { .bitrate = 540, + .hw_value = CONF_HW_BIT_RATE_54MBPS, + .hw_value_short = CONF_HW_BIT_RATE_54MBPS, }, +}; + +/* 5 GHz band channels for WL1273 */ +static struct ieee80211_channel wl1271_channels_5ghz[] = { + { .hw_value = 7, .center_freq = 5035}, + { .hw_value = 8, .center_freq = 5040}, + { .hw_value = 9, .center_freq = 5045}, + { .hw_value = 11, .center_freq = 5055}, + { .hw_value = 12, .center_freq = 5060}, + { .hw_value = 16, .center_freq = 5080}, + { .hw_value = 34, .center_freq = 5170}, + { .hw_value = 36, .center_freq = 5180}, + { .hw_value = 38, .center_freq = 5190}, + { .hw_value = 40, .center_freq = 5200}, + { .hw_value = 42, .center_freq = 5210}, + { .hw_value = 44, .center_freq = 5220}, + { .hw_value = 46, .center_freq = 5230}, + { .hw_value = 48, .center_freq = 5240}, + { .hw_value = 52, .center_freq = 5260}, + { .hw_value = 56, .center_freq = 5280}, + { .hw_value = 60, .center_freq = 5300}, + { .hw_value = 64, .center_freq = 5320}, + { .hw_value = 100, .center_freq = 5500}, + { .hw_value = 104, .center_freq = 5520}, + { .hw_value = 108, .center_freq = 5540}, + { .hw_value = 112, .center_freq = 5560}, + { .hw_value = 116, .center_freq = 5580}, + { .hw_value = 120, .center_freq = 5600}, + { .hw_value = 124, .center_freq = 5620}, + { .hw_value = 128, .center_freq = 5640}, + { .hw_value = 132, .center_freq = 5660}, + { .hw_value = 136, .center_freq = 5680}, + { .hw_value = 140, .center_freq = 5700}, + { .hw_value = 149, .center_freq = 5745}, + { .hw_value = 153, .center_freq = 5765}, + { .hw_value = 157, .center_freq = 5785}, + { .hw_value = 161, .center_freq = 5805}, + { .hw_value = 165, .center_freq = 5825}, +}; + +/* mapping to indexes for wl1271_rates_5ghz */ +static const u8 wl1271_rate_to_idx_5ghz[] = { + /* MCS rates are used only with 11n */ + 7, /* CONF_HW_RXTX_RATE_MCS7 */ + 6, /* CONF_HW_RXTX_RATE_MCS6 */ + 5, /* CONF_HW_RXTX_RATE_MCS5 */ + 4, /* CONF_HW_RXTX_RATE_MCS4 */ + 3, /* CONF_HW_RXTX_RATE_MCS3 */ + 2, /* CONF_HW_RXTX_RATE_MCS2 */ + 1, /* CONF_HW_RXTX_RATE_MCS1 */ + 0, /* CONF_HW_RXTX_RATE_MCS0 */ + + 7, /* CONF_HW_RXTX_RATE_54 */ + 6, /* CONF_HW_RXTX_RATE_48 */ + 5, /* CONF_HW_RXTX_RATE_36 */ + 4, /* CONF_HW_RXTX_RATE_24 */ + + /* TI-specific rate */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_22 */ + + 3, /* CONF_HW_RXTX_RATE_18 */ + 2, /* CONF_HW_RXTX_RATE_12 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_11 */ + 1, /* CONF_HW_RXTX_RATE_9 */ + 0, /* CONF_HW_RXTX_RATE_6 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_5_5 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_2 */ + CONF_HW_RXTX_RATE_UNSUPPORTED /* CONF_HW_RXTX_RATE_1 */ +}; + +static struct ieee80211_supported_band wl1271_band_5ghz = { + .channels = wl1271_channels_5ghz, + .n_channels = ARRAY_SIZE(wl1271_channels_5ghz), + .bitrates = wl1271_rates_5ghz, + .n_bitrates = ARRAY_SIZE(wl1271_rates_5ghz), + .ht_cap = WL12XX_HT_CAP, +}; + +static const u8 *wl1271_band_rate_to_idx[] = { + [IEEE80211_BAND_2GHZ] = wl1271_rate_to_idx_2ghz, + [IEEE80211_BAND_5GHZ] = wl1271_rate_to_idx_5ghz +}; + +static const struct ieee80211_ops wl1271_ops = { + .start = wl1271_op_start, + .stop = wl1271_op_stop, + .add_interface = wl1271_op_add_interface, + .remove_interface = wl1271_op_remove_interface, +#ifdef CONFIG_PM + .suspend = wl1271_op_suspend, + .resume = wl1271_op_resume, +#endif + .config = wl1271_op_config, + .prepare_multicast = wl1271_op_prepare_multicast, + .configure_filter = wl1271_op_configure_filter, + .tx = wl1271_op_tx, + .set_key = wl1271_op_set_key, + .hw_scan = wl1271_op_hw_scan, + .sched_scan_start = wl1271_op_sched_scan_start, + .sched_scan_stop = wl1271_op_sched_scan_stop, + .bss_info_changed = wl1271_op_bss_info_changed, + .set_frag_threshold = wl1271_op_set_frag_threshold, + .set_rts_threshold = wl1271_op_set_rts_threshold, + .conf_tx = wl1271_op_conf_tx, + .get_tsf = wl1271_op_get_tsf, + .get_survey = wl1271_op_get_survey, + .sta_add = wl1271_op_sta_add, + .sta_remove = wl1271_op_sta_remove, + .ampdu_action = wl1271_op_ampdu_action, + .tx_frames_pending = wl1271_tx_frames_pending, + CFG80211_TESTMODE_CMD(wl1271_tm_cmd) +}; + + +u8 wl1271_rate_to_idx(int rate, enum ieee80211_band band) +{ + u8 idx; + + BUG_ON(band >= sizeof(wl1271_band_rate_to_idx)/sizeof(u8 *)); + + if (unlikely(rate >= CONF_HW_RXTX_RATE_MAX)) { + wl1271_error("Illegal RX rate from HW: %d", rate); + return 0; + } + + idx = wl1271_band_rate_to_idx[band][rate]; + if (unlikely(idx == CONF_HW_RXTX_RATE_UNSUPPORTED)) { + wl1271_error("Unsupported RX rate from HW: %d", rate); + return 0; + } + + return idx; +} + +static ssize_t wl1271_sysfs_show_bt_coex_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wl1271 *wl = dev_get_drvdata(dev); + ssize_t len; + + len = PAGE_SIZE; + + mutex_lock(&wl->mutex); + len = snprintf(buf, len, "%d\n\n0 - off\n1 - on\n", + wl->sg_enabled); + mutex_unlock(&wl->mutex); + + return len; + +} + +static ssize_t wl1271_sysfs_store_bt_coex_state(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wl1271 *wl = dev_get_drvdata(dev); + unsigned long res; + int ret; + + ret = kstrtoul(buf, 10, &res); + if (ret < 0) { + wl1271_warning("incorrect value written to bt_coex_mode"); + return count; + } + + mutex_lock(&wl->mutex); + + res = !!res; + + if (res == wl->sg_enabled) + goto out; + + wl->sg_enabled = res; + + if (wl->state == WL1271_STATE_OFF) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_acx_sg_enable(wl, wl->sg_enabled); + wl1271_ps_elp_sleep(wl); + + out: + mutex_unlock(&wl->mutex); + return count; +} + +static DEVICE_ATTR(bt_coex_state, S_IRUGO | S_IWUSR, + wl1271_sysfs_show_bt_coex_state, + wl1271_sysfs_store_bt_coex_state); + +static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wl1271 *wl = dev_get_drvdata(dev); + ssize_t len; + + len = PAGE_SIZE; + + mutex_lock(&wl->mutex); + if (wl->hw_pg_ver >= 0) + len = snprintf(buf, len, "%d\n", wl->hw_pg_ver); + else + len = snprintf(buf, len, "n/a\n"); + mutex_unlock(&wl->mutex); + + return len; +} + +static DEVICE_ATTR(hw_pg_ver, S_IRUGO | S_IWUSR, + wl1271_sysfs_show_hw_pg_ver, NULL); + +int wl1271_register_hw(struct wl1271 *wl) +{ + int ret; + + if (wl->mac80211_registered) + return 0; + + ret = wl1271_fetch_nvs(wl); + if (ret == 0) { + /* NOTE: The wl->nvs->nvs element must be first, in + * order to simplify the casting, we assume it is at + * the beginning of the wl->nvs structure. + */ + u8 *nvs_ptr = (u8 *)wl->nvs; + + wl->mac_addr[0] = nvs_ptr[11]; + wl->mac_addr[1] = nvs_ptr[10]; + wl->mac_addr[2] = nvs_ptr[6]; + wl->mac_addr[3] = nvs_ptr[5]; + wl->mac_addr[4] = nvs_ptr[4]; + wl->mac_addr[5] = nvs_ptr[3]; + } + + SET_IEEE80211_PERM_ADDR(wl->hw, wl->mac_addr); + + ret = ieee80211_register_hw(wl->hw); + if (ret < 0) { + wl1271_error("unable to register mac80211 hw: %d", ret); + return ret; + } + + wl->mac80211_registered = true; + + wl1271_debugfs_init(wl); + + register_netdevice_notifier(&wl1271_dev_notifier); + + wl1271_notice("loaded"); + + return 0; +} +EXPORT_SYMBOL_GPL(wl1271_register_hw); + +void wl1271_unregister_hw(struct wl1271 *wl) +{ + if (wl->state == WL1271_STATE_PLT) + __wl1271_plt_stop(wl); + + unregister_netdevice_notifier(&wl1271_dev_notifier); + ieee80211_unregister_hw(wl->hw); + wl->mac80211_registered = false; + +} +EXPORT_SYMBOL_GPL(wl1271_unregister_hw); + +int wl1271_init_ieee80211(struct wl1271 *wl) +{ + static const u32 cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WL1271_CIPHER_SUITE_GEM, + }; + + /* The tx descriptor buffer and the TKIP space. */ + wl->hw->extra_tx_headroom = WL1271_TKIP_IV_SPACE + + sizeof(struct wl1271_tx_hw_descr); + + /* unit us */ + /* FIXME: find a proper value */ + wl->hw->channel_change_time = 10000; + wl->hw->max_listen_interval = wl->conf.conn.max_listen_interval; + + wl->hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_BEACON_FILTER | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_UAPSD | + IEEE80211_HW_HAS_RATE_CONTROL | + IEEE80211_HW_CONNECTION_MONITOR | + IEEE80211_HW_SUPPORTS_CQM_RSSI | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_AP_LINK_PS; + + wl->hw->wiphy->cipher_suites = cipher_suites; + wl->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + + wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_AP); + wl->hw->wiphy->max_scan_ssids = 1; + /* + * Maximum length of elements in scanning probe request templates + * should be the maximum length possible for a template, without + * the IEEE80211 header of the template + */ + wl->hw->wiphy->max_scan_ie_len = WL1271_CMD_TEMPL_MAX_SIZE - + sizeof(struct ieee80211_header); + + /* make sure all our channels fit in the scanned_ch bitmask */ + BUILD_BUG_ON(ARRAY_SIZE(wl1271_channels) + + ARRAY_SIZE(wl1271_channels_5ghz) > + WL1271_MAX_CHANNELS); + /* + * We keep local copies of the band structs because we need to + * modify them on a per-device basis. + */ + memcpy(&wl->bands[IEEE80211_BAND_2GHZ], &wl1271_band_2ghz, + sizeof(wl1271_band_2ghz)); + memcpy(&wl->bands[IEEE80211_BAND_5GHZ], &wl1271_band_5ghz, + sizeof(wl1271_band_5ghz)); + + wl->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &wl->bands[IEEE80211_BAND_2GHZ]; + wl->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &wl->bands[IEEE80211_BAND_5GHZ]; + + wl->hw->queues = 4; + wl->hw->max_rates = 1; + + wl->hw->wiphy->reg_notifier = wl1271_reg_notify; + + SET_IEEE80211_DEV(wl->hw, wl1271_wl_to_dev(wl)); + + wl->hw->sta_data_size = sizeof(struct wl1271_station); + + wl->hw->max_rx_aggregation_subframes = 8; + + return 0; +} +EXPORT_SYMBOL_GPL(wl1271_init_ieee80211); + +#define WL1271_DEFAULT_CHANNEL 0 + +struct ieee80211_hw *wl1271_alloc_hw(void) +{ + struct ieee80211_hw *hw; + struct platform_device *plat_dev = NULL; + struct wl1271 *wl; + int i, j, ret; + unsigned int order; + + hw = ieee80211_alloc_hw(sizeof(*wl), &wl1271_ops); + if (!hw) { + wl1271_error("could not alloc ieee80211_hw"); + ret = -ENOMEM; + goto err_hw_alloc; + } + + plat_dev = kmemdup(&wl1271_device, sizeof(wl1271_device), GFP_KERNEL); + if (!plat_dev) { + wl1271_error("could not allocate platform_device"); + ret = -ENOMEM; + goto err_plat_alloc; + } + + wl = hw->priv; + memset(wl, 0, sizeof(*wl)); + + INIT_LIST_HEAD(&wl->list); + + wl->hw = hw; + wl->plat_dev = plat_dev; + + for (i = 0; i < NUM_TX_QUEUES; i++) + skb_queue_head_init(&wl->tx_queue[i]); + + for (i = 0; i < NUM_TX_QUEUES; i++) + for (j = 0; j < AP_MAX_LINKS; j++) + skb_queue_head_init(&wl->links[j].tx_queue[i]); + + skb_queue_head_init(&wl->deferred_rx_queue); + skb_queue_head_init(&wl->deferred_tx_queue); + + INIT_DELAYED_WORK(&wl->elp_work, wl1271_elp_work); + INIT_DELAYED_WORK(&wl->pspoll_work, wl1271_pspoll_work); + INIT_WORK(&wl->netstack_work, wl1271_netstack_work); + INIT_WORK(&wl->tx_work, wl1271_tx_work); + INIT_WORK(&wl->recovery_work, wl1271_recovery_work); + INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work); + wl->channel = WL1271_DEFAULT_CHANNEL; + wl->beacon_int = WL1271_DEFAULT_BEACON_INT; + wl->default_key = 0; + wl->rx_counter = 0; + wl->rx_config = WL1271_DEFAULT_STA_RX_CONFIG; + wl->rx_filter = WL1271_DEFAULT_STA_RX_FILTER; + wl->psm_entry_retry = 0; + wl->power_level = WL1271_DEFAULT_POWER_LEVEL; + wl->basic_rate_set = CONF_TX_RATE_MASK_BASIC; + wl->basic_rate = CONF_TX_RATE_MASK_BASIC; + wl->rate_set = CONF_TX_RATE_MASK_BASIC; + wl->band = IEEE80211_BAND_2GHZ; + wl->vif = NULL; + wl->flags = 0; + wl->sg_enabled = true; + wl->hw_pg_ver = -1; + wl->bss_type = MAX_BSS_TYPE; + wl->set_bss_type = MAX_BSS_TYPE; + wl->fw_bss_type = MAX_BSS_TYPE; + wl->last_tx_hlid = 0; + wl->ap_ps_map = 0; + wl->ap_fw_ps_map = 0; + wl->quirks = 0; + wl->platform_quirks = 0; + wl->sched_scanning = false; + + memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); + for (i = 0; i < ACX_TX_DESCRIPTORS; i++) + wl->tx_frames[i] = NULL; + + spin_lock_init(&wl->wl_lock); + + wl->state = WL1271_STATE_OFF; + mutex_init(&wl->mutex); + + /* Apply default driver configuration. */ + wl1271_conf_init(wl); + + order = get_order(WL1271_AGGR_BUFFER_SIZE); + wl->aggr_buf = (u8 *)__get_free_pages(GFP_KERNEL, order); + if (!wl->aggr_buf) { + ret = -ENOMEM; + goto err_hw; + } + + wl->dummy_packet = wl12xx_alloc_dummy_packet(wl); + if (!wl->dummy_packet) { + ret = -ENOMEM; + goto err_aggr; + } + + /* Register platform device */ + ret = platform_device_register(wl->plat_dev); + if (ret) { + wl1271_error("couldn't register platform device"); + goto err_dummy_packet; + } + dev_set_drvdata(&wl->plat_dev->dev, wl); + + /* Create sysfs file to control bt coex state */ + ret = device_create_file(&wl->plat_dev->dev, &dev_attr_bt_coex_state); + if (ret < 0) { + wl1271_error("failed to create sysfs file bt_coex_state"); + goto err_platform; + } + + /* Create sysfs file to get HW PG version */ + ret = device_create_file(&wl->plat_dev->dev, &dev_attr_hw_pg_ver); + if (ret < 0) { + wl1271_error("failed to create sysfs file hw_pg_ver"); + goto err_bt_coex_state; + } + + return hw; + +err_bt_coex_state: + device_remove_file(&wl->plat_dev->dev, &dev_attr_bt_coex_state); + +err_platform: + platform_device_unregister(wl->plat_dev); + +err_dummy_packet: + dev_kfree_skb(wl->dummy_packet); + +err_aggr: + free_pages((unsigned long)wl->aggr_buf, order); + +err_hw: + wl1271_debugfs_exit(wl); + kfree(plat_dev); + +err_plat_alloc: + ieee80211_free_hw(hw); + +err_hw_alloc: + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(wl1271_alloc_hw); + +int wl1271_free_hw(struct wl1271 *wl) +{ + platform_device_unregister(wl->plat_dev); + dev_kfree_skb(wl->dummy_packet); + free_pages((unsigned long)wl->aggr_buf, + get_order(WL1271_AGGR_BUFFER_SIZE)); + kfree(wl->plat_dev); + + wl1271_debugfs_exit(wl); + + vfree(wl->fw); + wl->fw = NULL; + kfree(wl->nvs); + wl->nvs = NULL; + + kfree(wl->fw_status); + kfree(wl->tx_res_if); + + ieee80211_free_hw(wl->hw); + + return 0; +} +EXPORT_SYMBOL_GPL(wl1271_free_hw); + +u32 wl12xx_debug_level = DEBUG_NONE; +EXPORT_SYMBOL_GPL(wl12xx_debug_level); +module_param_named(debug_level, wl12xx_debug_level, uint, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(debug_level, "wl12xx debugging level"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>"); +MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>"); diff --git a/drivers/net/wireless/wl12xx/ps.c b/drivers/net/wireless/wl12xx/ps.c new file mode 100644 index 00000000..b59b6771 --- /dev/null +++ b/drivers/net/wireless/wl12xx/ps.c @@ -0,0 +1,269 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "reg.h" +#include "ps.h" +#include "io.h" +#include "tx.h" + +#define WL1271_WAKEUP_TIMEOUT 500 + +void wl1271_elp_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, elp_work); + + wl1271_debug(DEBUG_PSM, "elp work"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1271_STATE_OFF)) + goto out; + + /* our work might have been already cancelled */ + if (unlikely(!test_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags))) + goto out; + + if (test_bit(WL1271_FLAG_IN_ELP, &wl->flags) || + (!test_bit(WL1271_FLAG_PSM, &wl->flags) && + !test_bit(WL1271_FLAG_IDLE, &wl->flags))) + goto out; + + wl1271_debug(DEBUG_PSM, "chip to elp"); + wl1271_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_SLEEP); + set_bit(WL1271_FLAG_IN_ELP, &wl->flags); + +out: + mutex_unlock(&wl->mutex); +} + +#define ELP_ENTRY_DELAY 5 + +/* Routines to toggle sleep mode while in ELP */ +void wl1271_ps_elp_sleep(struct wl1271 *wl) +{ + /* we shouldn't get consecutive sleep requests */ + if (WARN_ON(test_and_set_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags))) + return; + + if (!test_bit(WL1271_FLAG_PSM, &wl->flags) && + !test_bit(WL1271_FLAG_IDLE, &wl->flags)) + return; + + ieee80211_queue_delayed_work(wl->hw, &wl->elp_work, + msecs_to_jiffies(ELP_ENTRY_DELAY)); +} + +int wl1271_ps_elp_wakeup(struct wl1271 *wl) +{ + DECLARE_COMPLETION_ONSTACK(compl); + unsigned long flags; + int ret; + u32 start_time = jiffies; + bool pending = false; + + /* + * we might try to wake up even if we didn't go to sleep + * before (e.g. on boot) + */ + if (!test_and_clear_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags)) + return 0; + + /* don't cancel_sync as it might contend for a mutex and deadlock */ + cancel_delayed_work(&wl->elp_work); + + if (!test_bit(WL1271_FLAG_IN_ELP, &wl->flags)) + return 0; + + wl1271_debug(DEBUG_PSM, "waking up chip from elp"); + + /* + * The spinlock is required here to synchronize both the work and + * the completion variable in one entity. + */ + spin_lock_irqsave(&wl->wl_lock, flags); + if (test_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags)) + pending = true; + else + wl->elp_compl = &compl; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + wl1271_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_WAKE_UP); + + if (!pending) { + ret = wait_for_completion_timeout( + &compl, msecs_to_jiffies(WL1271_WAKEUP_TIMEOUT)); + if (ret == 0) { + wl1271_error("ELP wakeup timeout!"); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + ret = -ETIMEDOUT; + goto err; + } else if (ret < 0) { + wl1271_error("ELP wakeup completion error."); + goto err; + } + } + + clear_bit(WL1271_FLAG_IN_ELP, &wl->flags); + + wl1271_debug(DEBUG_PSM, "wakeup time: %u ms", + jiffies_to_msecs(jiffies - start_time)); + goto out; + +err: + spin_lock_irqsave(&wl->wl_lock, flags); + wl->elp_compl = NULL; + spin_unlock_irqrestore(&wl->wl_lock, flags); + return ret; + +out: + return 0; +} + +int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode, + u32 rates, bool send) +{ + int ret; + + switch (mode) { + case STATION_POWER_SAVE_MODE: + wl1271_debug(DEBUG_PSM, "entering psm"); + + ret = wl1271_acx_wake_up_conditions(wl); + if (ret < 0) { + wl1271_error("couldn't set wake up conditions"); + return ret; + } + + ret = wl1271_cmd_ps_mode(wl, STATION_POWER_SAVE_MODE); + if (ret < 0) + return ret; + + set_bit(WL1271_FLAG_PSM, &wl->flags); + break; + case STATION_ACTIVE_MODE: + default: + wl1271_debug(DEBUG_PSM, "leaving psm"); + + /* disable beacon early termination */ + ret = wl1271_acx_bet_enable(wl, false); + if (ret < 0) + return ret; + + /* disable beacon filtering */ + ret = wl1271_acx_beacon_filter_opt(wl, false); + if (ret < 0) + return ret; + + ret = wl1271_cmd_ps_mode(wl, STATION_ACTIVE_MODE); + if (ret < 0) + return ret; + + clear_bit(WL1271_FLAG_PSM, &wl->flags); + break; + } + + return ret; +} + +static void wl1271_ps_filter_frames(struct wl1271 *wl, u8 hlid) +{ + int i, filtered = 0; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + unsigned long flags; + + /* filter all frames currently the low level queus for this hlid */ + for (i = 0; i < NUM_TX_QUEUES; i++) { + while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) { + info = IEEE80211_SKB_CB(skb); + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + info->status.rates[0].idx = -1; + ieee80211_tx_status(wl->hw, skb); + filtered++; + } + } + + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count -= filtered; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + wl1271_handle_tx_low_watermark(wl); +} + +void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues) +{ + struct ieee80211_sta *sta; + + if (test_bit(hlid, &wl->ap_ps_map)) + return; + + wl1271_debug(DEBUG_PSM, "start mac80211 PSM on hlid %d blks %d " + "clean_queues %d", hlid, wl->links[hlid].allocated_blks, + clean_queues); + + rcu_read_lock(); + sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr); + if (!sta) { + wl1271_error("could not find sta %pM for starting ps", + wl->links[hlid].addr); + rcu_read_unlock(); + return; + } + + ieee80211_sta_ps_transition_ni(sta, true); + rcu_read_unlock(); + + /* do we want to filter all frames from this link's queues? */ + if (clean_queues) + wl1271_ps_filter_frames(wl, hlid); + + __set_bit(hlid, &wl->ap_ps_map); +} + +void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid) +{ + struct ieee80211_sta *sta; + + if (!test_bit(hlid, &wl->ap_ps_map)) + return; + + wl1271_debug(DEBUG_PSM, "end mac80211 PSM on hlid %d", hlid); + + __clear_bit(hlid, &wl->ap_ps_map); + + rcu_read_lock(); + sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr); + if (!sta) { + wl1271_error("could not find sta %pM for ending ps", + wl->links[hlid].addr); + goto end; + } + + ieee80211_sta_ps_transition_ni(sta, false); +end: + rcu_read_unlock(); +} diff --git a/drivers/net/wireless/wl12xx/ps.h b/drivers/net/wireless/wl12xx/ps.h new file mode 100644 index 00000000..25eb9bc9 --- /dev/null +++ b/drivers/net/wireless/wl12xx/ps.h @@ -0,0 +1,40 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __PS_H__ +#define __PS_H__ + +#include "wl12xx.h" +#include "acx.h" + +int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode, + u32 rates, bool send); +void wl1271_ps_elp_sleep(struct wl1271 *wl); +int wl1271_ps_elp_wakeup(struct wl1271 *wl); +void wl1271_elp_work(struct work_struct *work); +void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues); +void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid); + +#define WL1271_PS_COMPLETE_TIMEOUT 500 + +#endif /* __WL1271_PS_H__ */ diff --git a/drivers/net/wireless/wl12xx/reg.h b/drivers/net/wireless/wl12xx/reg.h new file mode 100644 index 00000000..440a4ee9 --- /dev/null +++ b/drivers/net/wireless/wl12xx/reg.h @@ -0,0 +1,603 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __REG_H__ +#define __REG_H__ + +#include <linux/bitops.h> + +#define REGISTERS_BASE 0x00300000 +#define DRPW_BASE 0x00310000 + +#define REGISTERS_DOWN_SIZE 0x00008800 +#define REGISTERS_WORK_SIZE 0x0000b000 + +#define HW_ACCESS_ELP_CTRL_REG_ADDR 0x1FFFC +#define FW_STATUS_ADDR (0x14FC0 + 0xA000) + +/* ELP register commands */ +#define ELPCTRL_WAKE_UP 0x1 +#define ELPCTRL_WAKE_UP_WLAN_READY 0x5 +#define ELPCTRL_SLEEP 0x0 +/* ELP WLAN_READY bit */ +#define ELPCTRL_WLAN_READY 0x2 + +/*=============================================== + Host Software Reset - 32bit RW + ------------------------------------------ + [31:1] Reserved + 0 SOFT_RESET Soft Reset - When this bit is set, + it holds the Wlan hardware in a soft reset state. + This reset disables all MAC and baseband processor + clocks except the CardBus/PCI interface clock. + It also initializes all MAC state machines except + the host interface. It does not reload the + contents of the EEPROM. When this bit is cleared + (not self-clearing), the Wlan hardware + exits the software reset state. +===============================================*/ +#define ACX_REG_SLV_SOFT_RESET (REGISTERS_BASE + 0x0000) + +#define WL1271_SLV_REG_DATA (REGISTERS_BASE + 0x0008) +#define WL1271_SLV_REG_ADATA (REGISTERS_BASE + 0x000c) +#define WL1271_SLV_MEM_DATA (REGISTERS_BASE + 0x0018) + +#define ACX_REG_INTERRUPT_TRIG (REGISTERS_BASE + 0x0474) +#define ACX_REG_INTERRUPT_TRIG_H (REGISTERS_BASE + 0x0478) + +/*============================================= + Host Interrupt Mask Register - 32bit (RW) + ------------------------------------------ + Setting a bit in this register masks the + corresponding interrupt to the host. + 0 - RX0 - Rx first dubble buffer Data Interrupt + 1 - TXD - Tx Data Interrupt + 2 - TXXFR - Tx Transfer Interrupt + 3 - RX1 - Rx second dubble buffer Data Interrupt + 4 - RXXFR - Rx Transfer Interrupt + 5 - EVENT_A - Event Mailbox interrupt + 6 - EVENT_B - Event Mailbox interrupt + 7 - WNONHST - Wake On Host Interrupt + 8 - TRACE_A - Debug Trace interrupt + 9 - TRACE_B - Debug Trace interrupt + 10 - CDCMP - Command Complete Interrupt + 11 - + 12 - + 13 - + 14 - ICOMP - Initialization Complete Interrupt + 16 - SG SE - Soft Gemini - Sense enable interrupt + 17 - SG SD - Soft Gemini - Sense disable interrupt + 18 - - + 19 - - + 20 - - + 21- - + Default: 0x0001 +*==============================================*/ +#define ACX_REG_INTERRUPT_MASK (REGISTERS_BASE + 0x04DC) + +/*============================================= + Host Interrupt Mask Set 16bit, (Write only) + ------------------------------------------ + Setting a bit in this register sets + the corresponding bin in ACX_HINT_MASK register + without effecting the mask + state of other bits (0 = no effect). +==============================================*/ +#define ACX_REG_HINT_MASK_SET (REGISTERS_BASE + 0x04E0) + +/*============================================= + Host Interrupt Mask Clear 16bit,(Write only) + ------------------------------------------ + Setting a bit in this register clears + the corresponding bin in ACX_HINT_MASK register + without effecting the mask + state of other bits (0 = no effect). +=============================================*/ +#define ACX_REG_HINT_MASK_CLR (REGISTERS_BASE + 0x04E4) + +/*============================================= + Host Interrupt Status Nondestructive Read + 16bit,(Read only) + ------------------------------------------ + The host can read this register to determine + which interrupts are active. + Reading this register doesn't + effect its content. +=============================================*/ +#define ACX_REG_INTERRUPT_NO_CLEAR (REGISTERS_BASE + 0x04E8) + +/*============================================= + Host Interrupt Status Clear on Read Register + 16bit,(Read only) + ------------------------------------------ + The host can read this register to determine + which interrupts are active. + Reading this register clears it, + thus making all interrupts inactive. +==============================================*/ +#define ACX_REG_INTERRUPT_CLEAR (REGISTERS_BASE + 0x04F8) + +/*============================================= + Host Interrupt Acknowledge Register + 16bit,(Write only) + ------------------------------------------ + The host can set individual bits in this + register to clear (acknowledge) the corresp. + interrupt status bits in the HINT_STS_CLR and + HINT_STS_ND registers, thus making the + assotiated interrupt inactive. (0-no effect) +==============================================*/ +#define ACX_REG_INTERRUPT_ACK (REGISTERS_BASE + 0x04F0) + +#define RX_DRIVER_COUNTER_ADDRESS (REGISTERS_BASE + 0x0538) + +/* Device Configuration registers*/ +#define SOR_CFG (REGISTERS_BASE + 0x0800) + +/* Embedded ARM CPU Control */ + +/*=============================================== + Halt eCPU - 32bit RW + ------------------------------------------ + 0 HALT_ECPU Halt Embedded CPU - This bit is the + compliment of bit 1 (MDATA2) in the SOR_CFG register. + During a hardware reset, this bit holds + the inverse of MDATA2. + When downloading firmware from the host, + set this bit (pull down MDATA2). + The host clears this bit after downloading the firmware into + zero-wait-state SSRAM. + When loading firmware from Flash, clear this bit (pull up MDATA2) + so that the eCPU can run the bootloader code in Flash + HALT_ECPU eCPU State + -------------------- + 1 halt eCPU + 0 enable eCPU + ===============================================*/ +#define ACX_REG_ECPU_CONTROL (REGISTERS_BASE + 0x0804) + +#define HI_CFG (REGISTERS_BASE + 0x0808) + +/*=============================================== + EEPROM Burst Read Start - 32bit RW + ------------------------------------------ + [31:1] Reserved + 0 ACX_EE_START - EEPROM Burst Read Start 0 + Setting this bit starts a burst read from + the external EEPROM. + If this bit is set (after reset) before an EEPROM read/write, + the burst read starts at EEPROM address 0. + Otherwise, it starts at the address + following the address of the previous access. + TheWlan hardware hardware clears this bit automatically. + + Default: 0x00000000 +*================================================*/ +#define ACX_REG_EE_START (REGISTERS_BASE + 0x080C) + +#define OCP_POR_CTR (REGISTERS_BASE + 0x09B4) +#define OCP_DATA_WRITE (REGISTERS_BASE + 0x09B8) +#define OCP_DATA_READ (REGISTERS_BASE + 0x09BC) +#define OCP_CMD (REGISTERS_BASE + 0x09C0) + +#define WL1271_HOST_WR_ACCESS (REGISTERS_BASE + 0x09F8) + +#define CHIP_ID_B (REGISTERS_BASE + 0x5674) + +#define CHIP_ID_1271_PG10 (0x4030101) +#define CHIP_ID_1271_PG20 (0x4030111) +#define CHIP_ID_1283_PG10 (0x05030101) +#define CHIP_ID_1283_PG20 (0x05030111) + +#define ENABLE (REGISTERS_BASE + 0x5450) + +/* Power Management registers */ +#define ELP_CFG_MODE (REGISTERS_BASE + 0x5804) +#define ELP_CMD (REGISTERS_BASE + 0x5808) +#define PLL_CAL_TIME (REGISTERS_BASE + 0x5810) +#define CLK_REQ_TIME (REGISTERS_BASE + 0x5814) +#define CLK_BUF_TIME (REGISTERS_BASE + 0x5818) + +#define CFG_PLL_SYNC_CNT (REGISTERS_BASE + 0x5820) + +/* Scratch Pad registers*/ +#define SCR_PAD0 (REGISTERS_BASE + 0x5608) +#define SCR_PAD1 (REGISTERS_BASE + 0x560C) +#define SCR_PAD2 (REGISTERS_BASE + 0x5610) +#define SCR_PAD3 (REGISTERS_BASE + 0x5614) +#define SCR_PAD4 (REGISTERS_BASE + 0x5618) +#define SCR_PAD4_SET (REGISTERS_BASE + 0x561C) +#define SCR_PAD4_CLR (REGISTERS_BASE + 0x5620) +#define SCR_PAD5 (REGISTERS_BASE + 0x5624) +#define SCR_PAD5_SET (REGISTERS_BASE + 0x5628) +#define SCR_PAD5_CLR (REGISTERS_BASE + 0x562C) +#define SCR_PAD6 (REGISTERS_BASE + 0x5630) +#define SCR_PAD7 (REGISTERS_BASE + 0x5634) +#define SCR_PAD8 (REGISTERS_BASE + 0x5638) +#define SCR_PAD9 (REGISTERS_BASE + 0x563C) + +/* Spare registers*/ +#define SPARE_A1 (REGISTERS_BASE + 0x0994) +#define SPARE_A2 (REGISTERS_BASE + 0x0998) +#define SPARE_A3 (REGISTERS_BASE + 0x099C) +#define SPARE_A4 (REGISTERS_BASE + 0x09A0) +#define SPARE_A5 (REGISTERS_BASE + 0x09A4) +#define SPARE_A6 (REGISTERS_BASE + 0x09A8) +#define SPARE_A7 (REGISTERS_BASE + 0x09AC) +#define SPARE_A8 (REGISTERS_BASE + 0x09B0) +#define SPARE_B1 (REGISTERS_BASE + 0x5420) +#define SPARE_B2 (REGISTERS_BASE + 0x5424) +#define SPARE_B3 (REGISTERS_BASE + 0x5428) +#define SPARE_B4 (REGISTERS_BASE + 0x542C) +#define SPARE_B5 (REGISTERS_BASE + 0x5430) +#define SPARE_B6 (REGISTERS_BASE + 0x5434) +#define SPARE_B7 (REGISTERS_BASE + 0x5438) +#define SPARE_B8 (REGISTERS_BASE + 0x543C) + +#define PLL_PARAMETERS (REGISTERS_BASE + 0x6040) +#define WU_COUNTER_PAUSE (REGISTERS_BASE + 0x6008) +#define WELP_ARM_COMMAND (REGISTERS_BASE + 0x6100) +#define DRPW_SCRATCH_START (DRPW_BASE + 0x002C) + + +#define ACX_SLV_SOFT_RESET_BIT BIT(1) +#define ACX_REG_EEPROM_START_BIT BIT(1) + +/* Command/Information Mailbox Pointers */ + +/*=============================================== + Command Mailbox Pointer - 32bit RW + ------------------------------------------ + This register holds the start address of + the command mailbox located in the Wlan hardware memory. + The host must read this pointer after a reset to + find the location of the command mailbox. + The Wlan hardware initializes the command mailbox + pointer with the default address of the command mailbox. + The command mailbox pointer is not valid until after + the host receives the Init Complete interrupt from + the Wlan hardware. + ===============================================*/ +#define REG_COMMAND_MAILBOX_PTR (SCR_PAD0) + +/*=============================================== + Information Mailbox Pointer - 32bit RW + ------------------------------------------ + This register holds the start address of + the information mailbox located in the Wlan hardware memory. + The host must read this pointer after a reset to find + the location of the information mailbox. + The Wlan hardware initializes the information mailbox pointer + with the default address of the information mailbox. + The information mailbox pointer is not valid + until after the host receives the Init Complete interrupt from + the Wlan hardware. + ===============================================*/ +#define REG_EVENT_MAILBOX_PTR (SCR_PAD1) + + +/* Misc */ + +#define REG_ENABLE_TX_RX (ENABLE) +/* + * Rx configuration (filter) information element + * --------------------------------------------- + */ +#define REG_RX_CONFIG (RX_CFG) +#define REG_RX_FILTER (RX_FILTER_CFG) + + +#define RX_CFG_ENABLE_PHY_HEADER_PLCP 0x0002 + +/* promiscuous - receives all valid frames */ +#define RX_CFG_PROMISCUOUS 0x0008 + +/* receives frames from any BSSID */ +#define RX_CFG_BSSID 0x0020 + +/* receives frames destined to any MAC address */ +#define RX_CFG_MAC 0x0010 + +#define RX_CFG_ENABLE_ONLY_MY_DEST_MAC 0x0010 +#define RX_CFG_ENABLE_ANY_DEST_MAC 0x0000 +#define RX_CFG_ENABLE_ONLY_MY_BSSID 0x0020 +#define RX_CFG_ENABLE_ANY_BSSID 0x0000 + +/* discards all broadcast frames */ +#define RX_CFG_DISABLE_BCAST 0x0200 + +#define RX_CFG_ENABLE_ONLY_MY_SSID 0x0400 +#define RX_CFG_ENABLE_RX_CMPLT_FCS_ERROR 0x0800 +#define RX_CFG_COPY_RX_STATUS 0x2000 +#define RX_CFG_TSF 0x10000 + +#define RX_CONFIG_OPTION_ANY_DST_MY_BSS (RX_CFG_ENABLE_ANY_DEST_MAC | \ + RX_CFG_ENABLE_ONLY_MY_BSSID) + +#define RX_CONFIG_OPTION_MY_DST_ANY_BSS (RX_CFG_ENABLE_ONLY_MY_DEST_MAC\ + | RX_CFG_ENABLE_ANY_BSSID) + +#define RX_CONFIG_OPTION_ANY_DST_ANY_BSS (RX_CFG_ENABLE_ANY_DEST_MAC | \ + RX_CFG_ENABLE_ANY_BSSID) + +#define RX_CONFIG_OPTION_MY_DST_MY_BSS (RX_CFG_ENABLE_ONLY_MY_DEST_MAC\ + | RX_CFG_ENABLE_ONLY_MY_BSSID) + +#define RX_CONFIG_OPTION_FOR_SCAN (RX_CFG_ENABLE_PHY_HEADER_PLCP \ + | RX_CFG_ENABLE_RX_CMPLT_FCS_ERROR \ + | RX_CFG_COPY_RX_STATUS | RX_CFG_TSF) + +#define RX_CONFIG_OPTION_FOR_MEASUREMENT (RX_CFG_ENABLE_ANY_DEST_MAC) + +#define RX_CONFIG_OPTION_FOR_JOIN (RX_CFG_ENABLE_ONLY_MY_BSSID | \ + RX_CFG_ENABLE_ONLY_MY_DEST_MAC) + +#define RX_CONFIG_OPTION_FOR_IBSS_JOIN (RX_CFG_ENABLE_ONLY_MY_SSID | \ + RX_CFG_ENABLE_ONLY_MY_DEST_MAC) + +#define RX_FILTER_OPTION_DEF (CFG_RX_MGMT_EN | CFG_RX_DATA_EN\ + | CFG_RX_CTL_EN | CFG_RX_BCN_EN\ + | CFG_RX_AUTH_EN | CFG_RX_ASSOC_EN) + +#define RX_FILTER_OPTION_FILTER_ALL 0 + +#define RX_FILTER_OPTION_DEF_PRSP_BCN (CFG_RX_PRSP_EN | CFG_RX_MGMT_EN\ + | CFG_RX_RCTS_ACK | CFG_RX_BCN_EN) + +#define RX_FILTER_OPTION_JOIN (CFG_RX_MGMT_EN | CFG_RX_DATA_EN\ + | CFG_RX_BCN_EN | CFG_RX_AUTH_EN\ + | CFG_RX_ASSOC_EN | CFG_RX_RCTS_ACK\ + | CFG_RX_PRSP_EN) + + +/*=============================================== + EEPROM Read/Write Request 32bit RW + ------------------------------------------ + 1 EE_READ - EEPROM Read Request 1 - Setting this bit + loads a single byte of data into the EE_DATA + register from the EEPROM location specified in + the EE_ADDR register. + The Wlan hardware hardware clears this bit automatically. + EE_DATA is valid when this bit is cleared. + + 0 EE_WRITE - EEPROM Write Request - Setting this bit + writes a single byte of data from the EE_DATA register into the + EEPROM location specified in the EE_ADDR register. + The Wlan hardware hardware clears this bit automatically. +*===============================================*/ +#define ACX_EE_CTL_REG EE_CTL +#define EE_WRITE 0x00000001ul +#define EE_READ 0x00000002ul + +/*=============================================== + EEPROM Address - 32bit RW + ------------------------------------------ + This register specifies the address + within the EEPROM from/to which to read/write data. + ===============================================*/ +#define ACX_EE_ADDR_REG EE_ADDR + +/*=============================================== + EEPROM Data - 32bit RW + ------------------------------------------ + This register either holds the read 8 bits of + data from the EEPROM or the write data + to be written to the EEPROM. + ===============================================*/ +#define ACX_EE_DATA_REG EE_DATA + +/*=============================================== + EEPROM Base Address - 32bit RW + ------------------------------------------ + This register holds the upper nine bits + [23:15] of the 24-bit Wlan hardware memory + address for burst reads from EEPROM accesses. + The EEPROM provides the lower 15 bits of this address. + The MSB of the address from the EEPROM is ignored. + ===============================================*/ +#define ACX_EE_CFG EE_CFG + +/*=============================================== + GPIO Output Values -32bit, RW + ------------------------------------------ + [31:16] Reserved + [15: 0] Specify the output values (at the output driver inputs) for + GPIO[15:0], respectively. + ===============================================*/ +#define ACX_GPIO_OUT_REG GPIO_OUT +#define ACX_MAX_GPIO_LINES 15 + +/*=============================================== + Contention window -32bit, RW + ------------------------------------------ + [31:26] Reserved + [25:16] Max (0x3ff) + [15:07] Reserved + [06:00] Current contention window value - default is 0x1F + ===============================================*/ +#define ACX_CONT_WIND_CFG_REG CONT_WIND_CFG +#define ACX_CONT_WIND_MIN_MASK 0x0000007f +#define ACX_CONT_WIND_MAX 0x03ff0000 + +/*=============================================== + HI_CFG Interface Configuration Register Values + ------------------------------------------ + ===============================================*/ +#define HI_CFG_UART_ENABLE 0x00000004 +#define HI_CFG_RST232_ENABLE 0x00000008 +#define HI_CFG_CLOCK_REQ_SELECT 0x00000010 +#define HI_CFG_HOST_INT_ENABLE 0x00000020 +#define HI_CFG_VLYNQ_OUTPUT_ENABLE 0x00000040 +#define HI_CFG_HOST_INT_ACTIVE_LOW 0x00000080 +#define HI_CFG_UART_TX_OUT_GPIO_15 0x00000100 +#define HI_CFG_UART_TX_OUT_GPIO_14 0x00000200 +#define HI_CFG_UART_TX_OUT_GPIO_7 0x00000400 + +#define HI_CFG_DEF_VAL \ + (HI_CFG_UART_ENABLE | \ + HI_CFG_RST232_ENABLE | \ + HI_CFG_CLOCK_REQ_SELECT | \ + HI_CFG_HOST_INT_ENABLE) + +#define REF_FREQ_19_2 0 +#define REF_FREQ_26_0 1 +#define REF_FREQ_38_4 2 +#define REF_FREQ_40_0 3 +#define REF_FREQ_33_6 4 +#define REF_FREQ_NUM 5 + +#define LUT_PARAM_INTEGER_DIVIDER 0 +#define LUT_PARAM_FRACTIONAL_DIVIDER 1 +#define LUT_PARAM_ATTN_BB 2 +#define LUT_PARAM_ALPHA_BB 3 +#define LUT_PARAM_STOP_TIME_BB 4 +#define LUT_PARAM_BB_PLL_LOOP_FILTER 5 +#define LUT_PARAM_NUM 6 + +#define ACX_EEPROMLESS_IND_REG (SCR_PAD4) +#define USE_EEPROM 0 +#define SOFT_RESET_MAX_TIME 1000000 +#define SOFT_RESET_STALL_TIME 1000 +#define NVS_DATA_BUNDARY_ALIGNMENT 4 + + +/* Firmware image load chunk size */ +#define CHUNK_SIZE 512 + +/* Firmware image header size */ +#define FW_HDR_SIZE 8 + +#define ECPU_CONTROL_HALT 0x00000101 + + +/****************************************************************************** + + CHANNELS, BAND & REG DOMAINS definitions + +******************************************************************************/ + + +enum { + RADIO_BAND_2_4GHZ = 0, /* 2.4 Ghz band */ + RADIO_BAND_5GHZ = 1, /* 5 Ghz band */ + RADIO_BAND_JAPAN_4_9_GHZ = 2, + DEFAULT_BAND = RADIO_BAND_2_4GHZ, + INVALID_BAND = 0xFE, + MAX_RADIO_BANDS = 0xFF +}; + +#define SHORT_PREAMBLE_BIT BIT(0) /* CCK or Barker depending on the rate */ +#define OFDM_RATE_BIT BIT(6) +#define PBCC_RATE_BIT BIT(7) + +enum { + CCK_LONG = 0, + CCK_SHORT = SHORT_PREAMBLE_BIT, + PBCC_LONG = PBCC_RATE_BIT, + PBCC_SHORT = PBCC_RATE_BIT | SHORT_PREAMBLE_BIT, + OFDM = OFDM_RATE_BIT +}; + +/****************************************************************************** + +Transmit-Descriptor RATE-SET field definitions... + +Define a new "Rate-Set" for TX path that incorporates the +Rate & Modulation info into a single 16-bit field. + +TxdRateSet_t: +b15 - Indicates Preamble type (1=SHORT, 0=LONG). + Notes: + Must be LONG (0) for 1Mbps rate. + Does not apply (set to 0) for RevG-OFDM rates. +b14 - Indicates PBCC encoding (1=PBCC, 0=not). + Notes: + Does not apply (set to 0) for rates 1 and 2 Mbps. + Does not apply (set to 0) for RevG-OFDM rates. +b13 - Unused (set to 0). +b12-b0 - Supported Rate indicator bits as defined below. + +******************************************************************************/ + + +/************************************************************************* + + Interrupt Trigger Register (Host -> WiLink) + +**************************************************************************/ + +/* Hardware to Embedded CPU Interrupts - first 32-bit register set */ + +/* + * Host Command Interrupt. Setting this bit masks + * the interrupt that the host issues to inform + * the FW that it has sent a command + * to the Wlan hardware Command Mailbox. + */ +#define INTR_TRIG_CMD BIT(0) + +/* + * Host Event Acknowlegde Interrupt. The host + * sets this bit to acknowledge that it received + * the unsolicited information from the event + * mailbox. + */ +#define INTR_TRIG_EVENT_ACK BIT(1) + +/* + * The host sets this bit to inform the Wlan + * FW that a TX packet is in the XFER + * Buffer #0. + */ +#define INTR_TRIG_TX_PROC0 BIT(2) + +/* + * The host sets this bit to inform the FW + * that it read a packet from RX XFER + * Buffer #0. + */ +#define INTR_TRIG_RX_PROC0 BIT(3) + +#define INTR_TRIG_DEBUG_ACK BIT(4) + +#define INTR_TRIG_STATE_CHANGED BIT(5) + + +/* Hardware to Embedded CPU Interrupts - second 32-bit register set */ + +/* + * The host sets this bit to inform the FW + * that it read a packet from RX XFER + * Buffer #1. + */ +#define INTR_TRIG_RX_PROC1 BIT(17) + +/* + * The host sets this bit to inform the Wlan + * hardware that a TX packet is in the XFER + * Buffer #1. + */ +#define INTR_TRIG_TX_PROC1 BIT(18) + +#endif diff --git a/drivers/net/wireless/wl12xx/rx.c b/drivers/net/wireless/wl12xx/rx.c new file mode 100644 index 00000000..70091035 --- /dev/null +++ b/drivers/net/wireless/wl12xx/rx.c @@ -0,0 +1,244 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/gfp.h> + +#include "wl12xx.h" +#include "acx.h" +#include "reg.h" +#include "rx.h" +#include "io.h" + +static u8 wl1271_rx_get_mem_block(struct wl1271_fw_common_status *status, + u32 drv_rx_counter) +{ + return le32_to_cpu(status->rx_pkt_descs[drv_rx_counter]) & + RX_MEM_BLOCK_MASK; +} + +static u32 wl1271_rx_get_buf_size(struct wl1271_fw_common_status *status, + u32 drv_rx_counter) +{ + return (le32_to_cpu(status->rx_pkt_descs[drv_rx_counter]) & + RX_BUF_SIZE_MASK) >> RX_BUF_SIZE_SHIFT_DIV; +} + +static void wl1271_rx_status(struct wl1271 *wl, + struct wl1271_rx_descriptor *desc, + struct ieee80211_rx_status *status, + u8 beacon) +{ + memset(status, 0, sizeof(struct ieee80211_rx_status)); + + if ((desc->flags & WL1271_RX_DESC_BAND_MASK) == WL1271_RX_DESC_BAND_BG) + status->band = IEEE80211_BAND_2GHZ; + else + status->band = IEEE80211_BAND_5GHZ; + + status->rate_idx = wl1271_rate_to_idx(desc->rate, status->band); + +#ifdef CONFIG_WL12XX_HT + /* 11n support */ + if (desc->rate <= CONF_HW_RXTX_RATE_MCS0) + status->flag |= RX_FLAG_HT; +#endif + + status->signal = desc->rssi; + + /* + * FIXME: In wl1251, the SNR should be divided by two. In wl1271 we + * need to divide by two for now, but TI has been discussing about + * changing it. This needs to be rechecked. + */ + wl->noise = desc->rssi - (desc->snr >> 1); + + status->freq = ieee80211_channel_to_frequency(desc->channel, + status->band); + + if (desc->flags & WL1271_RX_DESC_ENCRYPT_MASK) { + u8 desc_err_code = desc->status & WL1271_RX_DESC_STATUS_MASK; + + status->flag |= RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED | + RX_FLAG_DECRYPTED; + + if (unlikely(desc_err_code == WL1271_RX_DESC_MIC_FAIL)) { + status->flag |= RX_FLAG_MMIC_ERROR; + wl1271_warning("Michael MIC error"); + } + } +} + +static int wl1271_rx_handle_data(struct wl1271 *wl, u8 *data, u32 length) +{ + struct wl1271_rx_descriptor *desc; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + u8 *buf; + u8 beacon = 0; + + /* + * In PLT mode we seem to get frames and mac80211 warns about them, + * workaround this by not retrieving them at all. + */ + if (unlikely(wl->state == WL1271_STATE_PLT)) + return -EINVAL; + + /* the data read starts with the descriptor */ + desc = (struct wl1271_rx_descriptor *) data; + + switch (desc->status & WL1271_RX_DESC_STATUS_MASK) { + /* discard corrupted packets */ + case WL1271_RX_DESC_DRIVER_RX_Q_FAIL: + case WL1271_RX_DESC_DECRYPT_FAIL: + wl1271_warning("corrupted packet in RX with status: 0x%x", + desc->status & WL1271_RX_DESC_STATUS_MASK); + return -EINVAL; + case WL1271_RX_DESC_SUCCESS: + case WL1271_RX_DESC_MIC_FAIL: + break; + default: + wl1271_error("invalid RX descriptor status: 0x%x", + desc->status & WL1271_RX_DESC_STATUS_MASK); + return -EINVAL; + } + + skb = __dev_alloc_skb(length, GFP_KERNEL); + if (!skb) { + wl1271_error("Couldn't allocate RX frame"); + return -ENOMEM; + } + + buf = skb_put(skb, length); + memcpy(buf, data, length); + + /* now we pull the descriptor out of the buffer */ + skb_pull(skb, sizeof(*desc)); + + hdr = (struct ieee80211_hdr *)skb->data; + if (ieee80211_is_beacon(hdr->frame_control)) + beacon = 1; + + wl1271_rx_status(wl, desc, IEEE80211_SKB_RXCB(skb), beacon); + + wl1271_debug(DEBUG_RX, "rx skb 0x%p: %d B %s", skb, + skb->len - desc->pad_len, + beacon ? "beacon" : ""); + + skb_trim(skb, skb->len - desc->pad_len); + + skb_queue_tail(&wl->deferred_rx_queue, skb); + ieee80211_queue_work(wl->hw, &wl->netstack_work); + + return 0; +} + +void wl1271_rx(struct wl1271 *wl, struct wl1271_fw_common_status *status) +{ + struct wl1271_acx_mem_map *wl_mem_map = wl->target_mem_map; + u32 buf_size; + u32 fw_rx_counter = status->fw_rx_counter & NUM_RX_PKT_DESC_MOD_MASK; + u32 drv_rx_counter = wl->rx_counter & NUM_RX_PKT_DESC_MOD_MASK; + u32 rx_counter; + u32 mem_block; + u32 pkt_length; + u32 pkt_offset; + + while (drv_rx_counter != fw_rx_counter) { + buf_size = 0; + rx_counter = drv_rx_counter; + while (rx_counter != fw_rx_counter) { + pkt_length = wl1271_rx_get_buf_size(status, rx_counter); + if (buf_size + pkt_length > WL1271_AGGR_BUFFER_SIZE) + break; + buf_size += pkt_length; + rx_counter++; + rx_counter &= NUM_RX_PKT_DESC_MOD_MASK; + } + + if (buf_size == 0) { + wl1271_warning("received empty data"); + break; + } + + if (wl->chip.id != CHIP_ID_1283_PG20) { + /* + * Choose the block we want to read + * For aggregated packets, only the first memory block + * should be retrieved. The FW takes care of the rest. + */ + mem_block = wl1271_rx_get_mem_block(status, + drv_rx_counter); + + wl->rx_mem_pool_addr.addr = (mem_block << 8) + + le32_to_cpu(wl_mem_map->packet_memory_pool_start); + + wl->rx_mem_pool_addr.addr_extra = + wl->rx_mem_pool_addr.addr + 4; + + wl1271_write(wl, WL1271_SLV_REG_DATA, + &wl->rx_mem_pool_addr, + sizeof(wl->rx_mem_pool_addr), false); + } + + /* Read all available packets at once */ + wl1271_read(wl, WL1271_SLV_MEM_DATA, wl->aggr_buf, + buf_size, true); + + /* Split data into separate packets */ + pkt_offset = 0; + while (pkt_offset < buf_size) { + pkt_length = wl1271_rx_get_buf_size(status, + drv_rx_counter); + /* + * the handle data call can only fail in memory-outage + * conditions, in that case the received frame will just + * be dropped. + */ + wl1271_rx_handle_data(wl, + wl->aggr_buf + pkt_offset, + pkt_length); + wl->rx_counter++; + drv_rx_counter++; + drv_rx_counter &= NUM_RX_PKT_DESC_MOD_MASK; + pkt_offset += pkt_length; + } + } + + /* + * Write the driver's packet counter to the FW. This is only required + * for older hardware revisions + */ + if (wl->quirks & WL12XX_QUIRK_END_OF_TRANSACTION) + wl1271_write32(wl, RX_DRIVER_COUNTER_ADDRESS, wl->rx_counter); +} + +void wl1271_set_default_filters(struct wl1271 *wl) +{ + if (wl->bss_type == BSS_TYPE_AP_BSS) { + wl->rx_config = WL1271_DEFAULT_AP_RX_CONFIG; + wl->rx_filter = WL1271_DEFAULT_AP_RX_FILTER; + } else { + wl->rx_config = WL1271_DEFAULT_STA_RX_CONFIG; + wl->rx_filter = WL1271_DEFAULT_STA_RX_FILTER; + } +} diff --git a/drivers/net/wireless/wl12xx/rx.h b/drivers/net/wireless/wl12xx/rx.h new file mode 100644 index 00000000..75fabf83 --- /dev/null +++ b/drivers/net/wireless/wl12xx/rx.h @@ -0,0 +1,122 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __RX_H__ +#define __RX_H__ + +#include <linux/bitops.h> + +#define WL1271_RX_MAX_RSSI -30 +#define WL1271_RX_MIN_RSSI -95 + +#define SHORT_PREAMBLE_BIT BIT(0) +#define OFDM_RATE_BIT BIT(6) +#define PBCC_RATE_BIT BIT(7) + +#define PLCP_HEADER_LENGTH 8 +#define RX_DESC_PACKETID_SHIFT 11 +#define RX_MAX_PACKET_ID 3 + +#define NUM_RX_PKT_DESC_MOD_MASK 7 + +#define RX_DESC_VALID_FCS 0x0001 +#define RX_DESC_MATCH_RXADDR1 0x0002 +#define RX_DESC_MCAST 0x0004 +#define RX_DESC_STAINTIM 0x0008 +#define RX_DESC_VIRTUAL_BM 0x0010 +#define RX_DESC_BCAST 0x0020 +#define RX_DESC_MATCH_SSID 0x0040 +#define RX_DESC_MATCH_BSSID 0x0080 +#define RX_DESC_ENCRYPTION_MASK 0x0300 +#define RX_DESC_MEASURMENT 0x0400 +#define RX_DESC_SEQNUM_MASK 0x1800 +#define RX_DESC_MIC_FAIL 0x2000 +#define RX_DESC_DECRYPT_FAIL 0x4000 + +/* + * RX Descriptor flags: + * + * Bits 0-1 - band + * Bit 2 - STBC + * Bit 3 - A-MPDU + * Bit 4 - HT + * Bits 5-7 - encryption + */ +#define WL1271_RX_DESC_BAND_MASK 0x03 +#define WL1271_RX_DESC_ENCRYPT_MASK 0xE0 + +#define WL1271_RX_DESC_BAND_BG 0x00 +#define WL1271_RX_DESC_BAND_J 0x01 +#define WL1271_RX_DESC_BAND_A 0x02 + +#define WL1271_RX_DESC_STBC BIT(2) +#define WL1271_RX_DESC_A_MPDU BIT(3) +#define WL1271_RX_DESC_HT BIT(4) + +#define WL1271_RX_DESC_ENCRYPT_WEP 0x20 +#define WL1271_RX_DESC_ENCRYPT_TKIP 0x40 +#define WL1271_RX_DESC_ENCRYPT_AES 0x60 +#define WL1271_RX_DESC_ENCRYPT_GEM 0x80 + +/* + * RX Descriptor status + * + * Bits 0-2 - error code + * Bits 3-5 - process_id tag (AP mode FW) + * Bits 6-7 - reserved + */ +#define WL1271_RX_DESC_STATUS_MASK 0x07 + +#define WL1271_RX_DESC_SUCCESS 0x00 +#define WL1271_RX_DESC_DECRYPT_FAIL 0x01 +#define WL1271_RX_DESC_MIC_FAIL 0x02 +#define WL1271_RX_DESC_DRIVER_RX_Q_FAIL 0x03 + +#define RX_MEM_BLOCK_MASK 0xFF +#define RX_BUF_SIZE_MASK 0xFFF00 +#define RX_BUF_SIZE_SHIFT_DIV 6 + +struct wl1271_rx_descriptor { + __le16 length; + u8 status; + u8 flags; + u8 rate; + u8 channel; + s8 rssi; + u8 snr; + __le32 timestamp; + u8 packet_class; + union { + u8 process_id; /* STA FW */ + u8 hlid; /* AP FW */ + } __packed; + u8 pad_len; + u8 reserved; +} __packed; + +void wl1271_rx(struct wl1271 *wl, struct wl1271_fw_common_status *status); +u8 wl1271_rate_to_idx(int rate, enum ieee80211_band band); +void wl1271_set_default_filters(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/wl12xx/scan.c b/drivers/net/wireless/wl12xx/scan.c new file mode 100644 index 00000000..9542e466 --- /dev/null +++ b/drivers/net/wireless/wl12xx/scan.c @@ -0,0 +1,589 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/ieee80211.h> + +#include "wl12xx.h" +#include "cmd.h" +#include "scan.h" +#include "acx.h" +#include "ps.h" + +void wl1271_scan_complete_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, scan_complete_work); + + wl1271_debug(DEBUG_SCAN, "Scanning complete"); + + mutex_lock(&wl->mutex); + + if (wl->state == WL1271_STATE_OFF) + goto out; + + if (wl->scan.state == WL1271_SCAN_STATE_IDLE) + goto out; + + wl->scan.state = WL1271_SCAN_STATE_IDLE; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + wl->scan.req = NULL; + ieee80211_scan_completed(wl->hw, false); + + /* restore hardware connection monitoring template */ + if (test_bit(WL1271_FLAG_STA_ASSOCIATED, &wl->flags)) { + if (wl1271_ps_elp_wakeup(wl) == 0) { + wl1271_cmd_build_ap_probe_req(wl, wl->probereq); + wl1271_ps_elp_sleep(wl); + } + } + + if (wl->scan.failed) { + wl1271_info("Scan completed due to error."); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + } + +out: + mutex_unlock(&wl->mutex); + +} + + +static int wl1271_get_scan_channels(struct wl1271 *wl, + struct cfg80211_scan_request *req, + struct basic_scan_channel_params *channels, + enum ieee80211_band band, bool passive) +{ + struct conf_scan_settings *c = &wl->conf.scan; + int i, j; + u32 flags; + + for (i = 0, j = 0; + i < req->n_channels && j < WL1271_SCAN_MAX_CHANNELS; + i++) { + flags = req->channels[i]->flags; + + if (!test_bit(i, wl->scan.scanned_ch) && + !(flags & IEEE80211_CHAN_DISABLED) && + (req->channels[i]->band == band) && + /* + * In passive scans, we scan all remaining + * channels, even if not marked as such. + * In active scans, we only scan channels not + * marked as passive. + */ + (passive || !(flags & IEEE80211_CHAN_PASSIVE_SCAN))) { + wl1271_debug(DEBUG_SCAN, "band %d, center_freq %d ", + req->channels[i]->band, + req->channels[i]->center_freq); + wl1271_debug(DEBUG_SCAN, "hw_value %d, flags %X", + req->channels[i]->hw_value, + req->channels[i]->flags); + wl1271_debug(DEBUG_SCAN, + "max_antenna_gain %d, max_power %d", + req->channels[i]->max_antenna_gain, + req->channels[i]->max_power); + wl1271_debug(DEBUG_SCAN, "beacon_found %d", + req->channels[i]->beacon_found); + + if (!passive) { + channels[j].min_duration = + cpu_to_le32(c->min_dwell_time_active); + channels[j].max_duration = + cpu_to_le32(c->max_dwell_time_active); + } else { + channels[j].min_duration = + cpu_to_le32(c->min_dwell_time_passive); + channels[j].max_duration = + cpu_to_le32(c->max_dwell_time_passive); + } + channels[j].early_termination = 0; + channels[j].tx_power_att = req->channels[i]->max_power; + channels[j].channel = req->channels[i]->hw_value; + + memset(&channels[j].bssid_lsb, 0xff, 4); + memset(&channels[j].bssid_msb, 0xff, 2); + + /* Mark the channels we already used */ + set_bit(i, wl->scan.scanned_ch); + + j++; + } + } + + return j; +} + +#define WL1271_NOTHING_TO_SCAN 1 + +static int wl1271_scan_send(struct wl1271 *wl, enum ieee80211_band band, + bool passive, u32 basic_rate) +{ + struct wl1271_cmd_scan *cmd; + struct wl1271_cmd_trigger_scan_to *trigger; + int ret; + u16 scan_options = 0; + + /* skip active scans if we don't have SSIDs */ + if (!passive && wl->scan.req->n_ssids == 0) + return WL1271_NOTHING_TO_SCAN; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + trigger = kzalloc(sizeof(*trigger), GFP_KERNEL); + if (!cmd || !trigger) { + ret = -ENOMEM; + goto out; + } + + /* We always use high priority scans */ + scan_options = WL1271_SCAN_OPT_PRIORITY_HIGH; + + if (passive) + scan_options |= WL1271_SCAN_OPT_PASSIVE; + + cmd->params.scan_options = cpu_to_le16(scan_options); + + cmd->params.n_ch = wl1271_get_scan_channels(wl, wl->scan.req, + cmd->channels, + band, passive); + if (cmd->params.n_ch == 0) { + ret = WL1271_NOTHING_TO_SCAN; + goto out; + } + + cmd->params.tx_rate = cpu_to_le32(basic_rate); + cmd->params.rx_config_options = cpu_to_le32(CFG_RX_ALL_GOOD); + cmd->params.rx_filter_options = + cpu_to_le32(CFG_RX_PRSP_EN | CFG_RX_MGMT_EN | CFG_RX_BCN_EN); + + cmd->params.n_probe_reqs = wl->conf.scan.num_probe_reqs; + cmd->params.tx_rate = cpu_to_le32(basic_rate); + cmd->params.tid_trigger = 0; + cmd->params.scan_tag = WL1271_SCAN_DEFAULT_TAG; + + if (band == IEEE80211_BAND_2GHZ) + cmd->params.band = WL1271_SCAN_BAND_2_4_GHZ; + else + cmd->params.band = WL1271_SCAN_BAND_5_GHZ; + + if (wl->scan.ssid_len && wl->scan.ssid) { + cmd->params.ssid_len = wl->scan.ssid_len; + memcpy(cmd->params.ssid, wl->scan.ssid, wl->scan.ssid_len); + } + + ret = wl1271_cmd_build_probe_req(wl, wl->scan.ssid, wl->scan.ssid_len, + wl->scan.req->ie, wl->scan.req->ie_len, + band); + if (ret < 0) { + wl1271_error("PROBE request template failed"); + goto out; + } + + /* disable the timeout */ + trigger->timeout = 0; + ret = wl1271_cmd_send(wl, CMD_TRIGGER_SCAN_TO, trigger, + sizeof(*trigger), 0); + if (ret < 0) { + wl1271_error("trigger scan to failed for hw scan"); + goto out; + } + + wl1271_dump(DEBUG_SCAN, "SCAN: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SCAN, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("SCAN failed"); + goto out; + } + +out: + kfree(cmd); + kfree(trigger); + return ret; +} + +void wl1271_scan_stm(struct wl1271 *wl) +{ + int ret = 0; + + switch (wl->scan.state) { + case WL1271_SCAN_STATE_IDLE: + break; + + case WL1271_SCAN_STATE_2GHZ_ACTIVE: + ret = wl1271_scan_send(wl, IEEE80211_BAND_2GHZ, false, + wl->conf.tx.basic_rate); + if (ret == WL1271_NOTHING_TO_SCAN) { + wl->scan.state = WL1271_SCAN_STATE_2GHZ_PASSIVE; + wl1271_scan_stm(wl); + } + + break; + + case WL1271_SCAN_STATE_2GHZ_PASSIVE: + ret = wl1271_scan_send(wl, IEEE80211_BAND_2GHZ, true, + wl->conf.tx.basic_rate); + if (ret == WL1271_NOTHING_TO_SCAN) { + if (wl->enable_11a) + wl->scan.state = WL1271_SCAN_STATE_5GHZ_ACTIVE; + else + wl->scan.state = WL1271_SCAN_STATE_DONE; + wl1271_scan_stm(wl); + } + + break; + + case WL1271_SCAN_STATE_5GHZ_ACTIVE: + ret = wl1271_scan_send(wl, IEEE80211_BAND_5GHZ, false, + wl->conf.tx.basic_rate_5); + if (ret == WL1271_NOTHING_TO_SCAN) { + wl->scan.state = WL1271_SCAN_STATE_5GHZ_PASSIVE; + wl1271_scan_stm(wl); + } + + break; + + case WL1271_SCAN_STATE_5GHZ_PASSIVE: + ret = wl1271_scan_send(wl, IEEE80211_BAND_5GHZ, true, + wl->conf.tx.basic_rate_5); + if (ret == WL1271_NOTHING_TO_SCAN) { + wl->scan.state = WL1271_SCAN_STATE_DONE; + wl1271_scan_stm(wl); + } + + break; + + case WL1271_SCAN_STATE_DONE: + wl->scan.failed = false; + cancel_delayed_work(&wl->scan_complete_work); + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(0)); + break; + + default: + wl1271_error("invalid scan state"); + break; + } + + if (ret < 0) { + cancel_delayed_work(&wl->scan_complete_work); + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(0)); + } +} + +int wl1271_scan(struct wl1271 *wl, const u8 *ssid, size_t ssid_len, + struct cfg80211_scan_request *req) +{ + /* + * cfg80211 should guarantee that we don't get more channels + * than what we have registered. + */ + BUG_ON(req->n_channels > WL1271_MAX_CHANNELS); + + if (wl->scan.state != WL1271_SCAN_STATE_IDLE) + return -EBUSY; + + wl->scan.state = WL1271_SCAN_STATE_2GHZ_ACTIVE; + + if (ssid_len && ssid) { + wl->scan.ssid_len = ssid_len; + memcpy(wl->scan.ssid, ssid, ssid_len); + } else { + wl->scan.ssid_len = 0; + } + + wl->scan.req = req; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + + /* we assume failure so that timeout scenarios are handled correctly */ + wl->scan.failed = true; + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(WL1271_SCAN_TIMEOUT)); + + wl1271_scan_stm(wl); + + return 0; +} + +static int +wl1271_scan_get_sched_scan_channels(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct conn_scan_ch_params *channels, + u32 band, bool radar, bool passive, + int start) +{ + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + int i, j; + u32 flags; + bool force_passive = !req->n_ssids; + + for (i = 0, j = start; + i < req->n_channels && j < MAX_CHANNELS_ALL_BANDS; + i++) { + flags = req->channels[i]->flags; + + if (force_passive) + flags |= IEEE80211_CHAN_PASSIVE_SCAN; + + if ((req->channels[i]->band == band) && + !(flags & IEEE80211_CHAN_DISABLED) && + (!!(flags & IEEE80211_CHAN_RADAR) == radar) && + /* if radar is set, we ignore the passive flag */ + (radar || + !!(flags & IEEE80211_CHAN_PASSIVE_SCAN) == passive)) { + wl1271_debug(DEBUG_SCAN, "band %d, center_freq %d ", + req->channels[i]->band, + req->channels[i]->center_freq); + wl1271_debug(DEBUG_SCAN, "hw_value %d, flags %X", + req->channels[i]->hw_value, + req->channels[i]->flags); + wl1271_debug(DEBUG_SCAN, "max_power %d", + req->channels[i]->max_power); + + if (flags & IEEE80211_CHAN_RADAR) { + channels[j].flags |= SCAN_CHANNEL_FLAGS_DFS; + channels[j].passive_duration = + cpu_to_le16(c->dwell_time_dfs); + } + else if (flags & IEEE80211_CHAN_PASSIVE_SCAN) { + channels[j].passive_duration = + cpu_to_le16(c->dwell_time_passive); + } else { + channels[j].min_duration = + cpu_to_le16(c->min_dwell_time_active); + channels[j].max_duration = + cpu_to_le16(c->max_dwell_time_active); + } + channels[j].tx_power_att = req->channels[i]->max_power; + channels[j].channel = req->channels[i]->hw_value; + + j++; + } + } + + return j - start; +} + +static int +wl1271_scan_sched_scan_channels(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct wl1271_cmd_sched_scan_config *cfg) +{ + int idx = 0; + + cfg->passive[0] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_2GHZ, + false, true, idx); + idx += cfg->passive[0]; + + cfg->active[0] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_2GHZ, + false, false, idx); + /* + * 5GHz channels always start at position 14, not immediately + * after the last 2.4GHz channel + */ + idx = 14; + + cfg->passive[1] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_5GHZ, + false, true, idx); + idx += cfg->passive[1]; + + cfg->dfs = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_5GHZ, + true, true, idx); + idx += cfg->dfs; + + cfg->active[1] = + wl1271_scan_get_sched_scan_channels(wl, req, cfg->channels, + IEEE80211_BAND_5GHZ, + false, false, idx); + idx += cfg->active[1]; + + wl1271_debug(DEBUG_SCAN, " 2.4GHz: active %d passive %d", + cfg->active[0], cfg->passive[0]); + wl1271_debug(DEBUG_SCAN, " 5GHz: active %d passive %d", + cfg->active[1], cfg->passive[1]); + wl1271_debug(DEBUG_SCAN, " DFS: %d", cfg->dfs); + + return idx; +} + +int wl1271_scan_sched_scan_config(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl1271_cmd_sched_scan_config *cfg = NULL; + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + int i, total_channels, ret; + bool force_passive = !req->n_ssids; + + wl1271_debug(DEBUG_CMD, "cmd sched_scan scan config"); + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->rssi_threshold = c->rssi_threshold; + cfg->snr_threshold = c->snr_threshold; + cfg->n_probe_reqs = c->num_probe_reqs; + /* cycles set to 0 it means infinite (until manually stopped) */ + cfg->cycles = 0; + /* report APs when at least 1 is found */ + cfg->report_after = 1; + /* don't stop scanning automatically when something is found */ + cfg->terminate = 0; + cfg->tag = WL1271_SCAN_DEFAULT_TAG; + /* don't filter on BSS type */ + cfg->bss_type = SCAN_BSS_TYPE_ANY; + /* currently NL80211 supports only a single interval */ + for (i = 0; i < SCAN_MAX_CYCLE_INTERVALS; i++) + cfg->intervals[i] = cpu_to_le32(req->interval); + + if (!force_passive && req->ssids[0].ssid_len && req->ssids[0].ssid) { + cfg->filter_type = SCAN_SSID_FILTER_SPECIFIC; + cfg->ssid_len = req->ssids[0].ssid_len; + memcpy(cfg->ssid, req->ssids[0].ssid, + req->ssids[0].ssid_len); + } else { + cfg->filter_type = SCAN_SSID_FILTER_ANY; + cfg->ssid_len = 0; + } + + total_channels = wl1271_scan_sched_scan_channels(wl, req, cfg); + if (total_channels == 0) { + wl1271_error("scan channel list is empty"); + ret = -EINVAL; + goto out; + } + + if (!force_passive && cfg->active[0]) { + ret = wl1271_cmd_build_probe_req(wl, req->ssids[0].ssid, + req->ssids[0].ssid_len, + ies->ie[IEEE80211_BAND_2GHZ], + ies->len[IEEE80211_BAND_2GHZ], + IEEE80211_BAND_2GHZ); + if (ret < 0) { + wl1271_error("2.4GHz PROBE request template failed"); + goto out; + } + } + + if (!force_passive && cfg->active[1]) { + ret = wl1271_cmd_build_probe_req(wl, req->ssids[0].ssid, + req->ssids[0].ssid_len, + ies->ie[IEEE80211_BAND_5GHZ], + ies->len[IEEE80211_BAND_5GHZ], + IEEE80211_BAND_5GHZ); + if (ret < 0) { + wl1271_error("5GHz PROBE request template failed"); + goto out; + } + } + + wl1271_dump(DEBUG_SCAN, "SCAN_CFG: ", cfg, sizeof(*cfg)); + + ret = wl1271_cmd_send(wl, CMD_CONNECTION_SCAN_CFG, cfg, + sizeof(*cfg), 0); + if (ret < 0) { + wl1271_error("SCAN configuration failed"); + goto out; + } +out: + kfree(cfg); + return ret; +} + +int wl1271_scan_sched_scan_start(struct wl1271 *wl) +{ + struct wl1271_cmd_sched_scan_start *start; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan start"); + + if (wl->bss_type != BSS_TYPE_STA_BSS) + return -EOPNOTSUPP; + + if (!test_bit(WL1271_FLAG_IDLE, &wl->flags)) + return -EBUSY; + + start = kzalloc(sizeof(*start), GFP_KERNEL); + if (!start) + return -ENOMEM; + + start->tag = WL1271_SCAN_DEFAULT_TAG; + + ret = wl1271_cmd_send(wl, CMD_START_PERIODIC_SCAN, start, + sizeof(*start), 0); + if (ret < 0) { + wl1271_error("failed to send scan start command"); + goto out_free; + } + +out_free: + kfree(start); + return ret; +} + +void wl1271_scan_sched_scan_results(struct wl1271 *wl) +{ + wl1271_debug(DEBUG_SCAN, "got periodic scan results"); + + ieee80211_sched_scan_results(wl->hw); +} + +void wl1271_scan_sched_scan_stop(struct wl1271 *wl) +{ + struct wl1271_cmd_sched_scan_stop *stop; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan stop"); + + /* FIXME: what to do if alloc'ing to stop fails? */ + stop = kzalloc(sizeof(*stop), GFP_KERNEL); + if (!stop) { + wl1271_error("failed to alloc memory to send sched scan stop"); + return; + } + + stop->tag = WL1271_SCAN_DEFAULT_TAG; + + ret = wl1271_cmd_send(wl, CMD_STOP_PERIODIC_SCAN, stop, + sizeof(*stop), 0); + if (ret < 0) { + wl1271_error("failed to send sched scan stop command"); + goto out_free; + } + wl->sched_scanning = false; + +out_free: + kfree(stop); +} diff --git a/drivers/net/wireless/wl12xx/scan.h b/drivers/net/wireless/wl12xx/scan.h new file mode 100644 index 00000000..a0b6c5d6 --- /dev/null +++ b/drivers/net/wireless/wl12xx/scan.h @@ -0,0 +1,226 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __SCAN_H__ +#define __SCAN_H__ + +#include "wl12xx.h" + +int wl1271_scan(struct wl1271 *wl, const u8 *ssid, size_t ssid_len, + struct cfg80211_scan_request *req); +int wl1271_scan_build_probe_req(struct wl1271 *wl, + const u8 *ssid, size_t ssid_len, + const u8 *ie, size_t ie_len, u8 band); +void wl1271_scan_stm(struct wl1271 *wl); +void wl1271_scan_complete_work(struct work_struct *work); +int wl1271_scan_sched_scan_config(struct wl1271 *wl, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); +int wl1271_scan_sched_scan_start(struct wl1271 *wl); +void wl1271_scan_sched_scan_stop(struct wl1271 *wl); +void wl1271_scan_sched_scan_results(struct wl1271 *wl); + +#define WL1271_SCAN_MAX_CHANNELS 24 +#define WL1271_SCAN_DEFAULT_TAG 1 +#define WL1271_SCAN_CURRENT_TX_PWR 0 +#define WL1271_SCAN_OPT_ACTIVE 0 +#define WL1271_SCAN_OPT_PASSIVE 1 +#define WL1271_SCAN_OPT_PRIORITY_HIGH 4 +#define WL1271_SCAN_BAND_2_4_GHZ 0 +#define WL1271_SCAN_BAND_5_GHZ 1 + +#define WL1271_SCAN_TIMEOUT 10000 /* msec */ + +enum { + WL1271_SCAN_STATE_IDLE, + WL1271_SCAN_STATE_2GHZ_ACTIVE, + WL1271_SCAN_STATE_2GHZ_PASSIVE, + WL1271_SCAN_STATE_5GHZ_ACTIVE, + WL1271_SCAN_STATE_5GHZ_PASSIVE, + WL1271_SCAN_STATE_DONE +}; + +struct basic_scan_params { + __le32 rx_config_options; + __le32 rx_filter_options; + /* Scan option flags (WL1271_SCAN_OPT_*) */ + __le16 scan_options; + /* Number of scan channels in the list (maximum 30) */ + u8 n_ch; + /* This field indicates the number of probe requests to send + per channel for an active scan */ + u8 n_probe_reqs; + /* Rate bit field for sending the probes */ + __le32 tx_rate; + u8 tid_trigger; + u8 ssid_len; + /* in order to align */ + u8 padding1[2]; + u8 ssid[IW_ESSID_MAX_SIZE]; + /* Band to scan */ + u8 band; + u8 use_ssid_list; + u8 scan_tag; + u8 padding2; +} __packed; + +struct basic_scan_channel_params { + /* Duration in TU to wait for frames on a channel for active scan */ + __le32 min_duration; + __le32 max_duration; + __le32 bssid_lsb; + __le16 bssid_msb; + u8 early_termination; + u8 tx_power_att; + u8 channel; + /* FW internal use only! */ + u8 dfs_candidate; + u8 activity_detected; + u8 pad; +} __packed; + +struct wl1271_cmd_scan { + struct wl1271_cmd_header header; + + struct basic_scan_params params; + struct basic_scan_channel_params channels[WL1271_SCAN_MAX_CHANNELS]; +} __packed; + +struct wl1271_cmd_trigger_scan_to { + struct wl1271_cmd_header header; + + __le32 timeout; +} __packed; + +#define MAX_CHANNELS_ALL_BANDS 41 +#define SCAN_MAX_CYCLE_INTERVALS 16 +#define SCAN_MAX_BANDS 3 + +enum { + SCAN_CHANNEL_TYPE_2GHZ_PASSIVE, + SCAN_CHANNEL_TYPE_2GHZ_ACTIVE, + SCAN_CHANNEL_TYPE_5GHZ_PASSIVE, + SCAN_CHANNEL_TYPE_5GHZ_ACTIVE, + SCAN_CHANNEL_TYPE_5GHZ_DFS, +}; + +enum { + SCAN_SSID_FILTER_ANY = 0, + SCAN_SSID_FILTER_SPECIFIC = 1, + SCAN_SSID_FILTER_LIST = 2, + SCAN_SSID_FILTER_DISABLED = 3 +}; + +enum { + SCAN_BSS_TYPE_INDEPENDENT, + SCAN_BSS_TYPE_INFRASTRUCTURE, + SCAN_BSS_TYPE_ANY, +}; + +#define SCAN_CHANNEL_FLAGS_DFS BIT(0) +#define SCAN_CHANNEL_FLAGS_DFS_ENABLED BIT(1) + +struct conn_scan_ch_params { + __le16 min_duration; + __le16 max_duration; + __le16 passive_duration; + + u8 channel; + u8 tx_power_att; + + /* bit 0: DFS channel; bit 1: DFS enabled */ + u8 flags; + + u8 padding[3]; +} __packed; + +struct wl1271_cmd_sched_scan_config { + struct wl1271_cmd_header header; + + __le32 intervals[SCAN_MAX_CYCLE_INTERVALS]; + + s8 rssi_threshold; /* for filtering (in dBm) */ + s8 snr_threshold; /* for filtering (in dB) */ + + u8 cycles; /* maximum number of scan cycles */ + u8 report_after; /* report when this number of results are received */ + u8 terminate; /* stop scanning after reporting */ + + u8 tag; + u8 bss_type; /* for filtering */ + u8 filter_type; + + u8 ssid_len; /* For SCAN_SSID_FILTER_SPECIFIC */ + u8 ssid[IW_ESSID_MAX_SIZE]; + + u8 n_probe_reqs; /* Number of probes requests per channel */ + + u8 passive[SCAN_MAX_BANDS]; + u8 active[SCAN_MAX_BANDS]; + + u8 dfs; + + u8 padding[3]; + + struct conn_scan_ch_params channels[MAX_CHANNELS_ALL_BANDS]; +} __packed; + + +#define SCHED_SCAN_MAX_SSIDS 8 + +enum { + SCAN_SSID_TYPE_PUBLIC = 0, + SCAN_SSID_TYPE_HIDDEN = 1, +}; + +struct wl1271_ssid { + u8 type; + u8 len; + u8 ssid[IW_ESSID_MAX_SIZE]; + /* u8 padding[2]; */ +} __packed; + +struct wl1271_cmd_sched_scan_ssid_list { + struct wl1271_cmd_header header; + + u8 n_ssids; + struct wl1271_ssid ssids[SCHED_SCAN_MAX_SSIDS]; + u8 padding[3]; +} __packed; + +struct wl1271_cmd_sched_scan_start { + struct wl1271_cmd_header header; + + u8 tag; + u8 padding[3]; +} __packed; + +struct wl1271_cmd_sched_scan_stop { + struct wl1271_cmd_header header; + + u8 tag; + u8 padding[3]; +} __packed; + + +#endif /* __WL1271_SCAN_H__ */ diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c new file mode 100644 index 00000000..536e5065 --- /dev/null +++ b/drivers/net/wireless/wl12xx/sdio.c @@ -0,0 +1,433 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/crc7.h> +#include <linux/vmalloc.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> +#include <linux/gpio.h> +#include <linux/wl12xx.h> +#include <linux/pm_runtime.h> + +#include "wl12xx.h" +#include "wl12xx_80211.h" +#include "io.h" + +#ifndef SDIO_VENDOR_ID_TI +#define SDIO_VENDOR_ID_TI 0x0097 +#endif + +#ifndef SDIO_DEVICE_ID_TI_WL1271 +#define SDIO_DEVICE_ID_TI_WL1271 0x4076 +#endif + +static const struct sdio_device_id wl1271_devices[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271) }, + {} +}; +MODULE_DEVICE_TABLE(sdio, wl1271_devices); + +static void wl1271_sdio_set_block_size(struct wl1271 *wl, unsigned int blksz) +{ + sdio_claim_host(wl->if_priv); + sdio_set_block_size(wl->if_priv, blksz); + sdio_release_host(wl->if_priv); +} + +static inline struct sdio_func *wl_to_func(struct wl1271 *wl) +{ + return wl->if_priv; +} + +static struct device *wl1271_sdio_wl_to_dev(struct wl1271 *wl) +{ + return &(wl_to_func(wl)->dev); +} + +static irqreturn_t wl1271_hardirq(int irq, void *cookie) +{ + struct wl1271 *wl = cookie; + unsigned long flags; + + wl1271_debug(DEBUG_IRQ, "IRQ"); + + /* complete the ELP completion */ + spin_lock_irqsave(&wl->wl_lock, flags); + set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags); + if (wl->elp_compl) { + complete(wl->elp_compl); + wl->elp_compl = NULL; + } + + if (test_bit(WL1271_FLAG_SUSPENDED, &wl->flags)) { + /* don't enqueue a work right now. mark it as pending */ + set_bit(WL1271_FLAG_PENDING_WORK, &wl->flags); + wl1271_debug(DEBUG_IRQ, "should not enqueue work"); + disable_irq_nosync(wl->irq); + pm_wakeup_event(wl1271_sdio_wl_to_dev(wl), 0); + spin_unlock_irqrestore(&wl->wl_lock, flags); + return IRQ_HANDLED; + } + spin_unlock_irqrestore(&wl->wl_lock, flags); + + return IRQ_WAKE_THREAD; +} + +static void wl1271_sdio_disable_interrupts(struct wl1271 *wl) +{ + disable_irq(wl->irq); +} + +static void wl1271_sdio_enable_interrupts(struct wl1271 *wl) +{ + enable_irq(wl->irq); +} + +static void wl1271_sdio_reset(struct wl1271 *wl) +{ +} + +static void wl1271_sdio_init(struct wl1271 *wl) +{ +} + +static void wl1271_sdio_raw_read(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + int ret; + struct sdio_func *func = wl_to_func(wl); + + if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG_ADDR)) { + ((u8 *)buf)[0] = sdio_f0_readb(func, addr, &ret); + wl1271_debug(DEBUG_SDIO, "sdio read 52 addr 0x%x, byte 0x%02x", + addr, ((u8 *)buf)[0]); + } else { + if (fixed) + ret = sdio_readsb(func, buf, addr, len); + else + ret = sdio_memcpy_fromio(func, buf, addr, len); + + wl1271_debug(DEBUG_SDIO, "sdio read 53 addr 0x%x, %zu bytes", + addr, len); + wl1271_dump_ascii(DEBUG_SDIO, "data: ", buf, len); + } + + if (ret) + wl1271_error("sdio read failed (%d)", ret); +} + +static void wl1271_sdio_raw_write(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + int ret; + struct sdio_func *func = wl_to_func(wl); + + if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG_ADDR)) { + sdio_f0_writeb(func, ((u8 *)buf)[0], addr, &ret); + wl1271_debug(DEBUG_SDIO, "sdio write 52 addr 0x%x, byte 0x%02x", + addr, ((u8 *)buf)[0]); + } else { + wl1271_debug(DEBUG_SDIO, "sdio write 53 addr 0x%x, %zu bytes", + addr, len); + wl1271_dump_ascii(DEBUG_SDIO, "data: ", buf, len); + + if (fixed) + ret = sdio_writesb(func, addr, buf, len); + else + ret = sdio_memcpy_toio(func, addr, buf, len); + } + + if (ret) + wl1271_error("sdio write failed (%d)", ret); +} + +static int wl1271_sdio_power_on(struct wl1271 *wl) +{ + struct sdio_func *func = wl_to_func(wl); + int ret; + + /* Make sure the card will not be powered off by runtime PM */ + ret = pm_runtime_get_sync(&func->dev); + if (ret < 0) + goto out; + + /* Runtime PM might be disabled, so power up the card manually */ + ret = mmc_power_restore_host(func->card->host); + if (ret < 0) + goto out; + + sdio_claim_host(func); + sdio_enable_func(func); + +out: + return ret; +} + +static int wl1271_sdio_power_off(struct wl1271 *wl) +{ + struct sdio_func *func = wl_to_func(wl); + int ret; + + sdio_disable_func(func); + sdio_release_host(func); + + /* Runtime PM might be disabled, so power off the card manually */ + ret = mmc_power_save_host(func->card->host); + if (ret < 0) + return ret; + + /* Let runtime PM know the card is powered off */ + return pm_runtime_put_sync(&func->dev); +} + +static int wl1271_sdio_set_power(struct wl1271 *wl, bool enable) +{ + if (enable) + return wl1271_sdio_power_on(wl); + else + return wl1271_sdio_power_off(wl); +} + +static struct wl1271_if_operations sdio_ops = { + .read = wl1271_sdio_raw_read, + .write = wl1271_sdio_raw_write, + .reset = wl1271_sdio_reset, + .init = wl1271_sdio_init, + .power = wl1271_sdio_set_power, + .dev = wl1271_sdio_wl_to_dev, + .enable_irq = wl1271_sdio_enable_interrupts, + .disable_irq = wl1271_sdio_disable_interrupts, + .set_block_size = wl1271_sdio_set_block_size, +}; + +static int __devinit wl1271_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct ieee80211_hw *hw; + const struct wl12xx_platform_data *wlan_data; + struct wl1271 *wl; + unsigned long irqflags; + mmc_pm_flag_t mmcflags; + int ret; + + /* We are only able to handle the wlan function */ + if (func->num != 0x02) + return -ENODEV; + + hw = wl1271_alloc_hw(); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + wl = hw->priv; + + wl->if_priv = func; + wl->if_ops = &sdio_ops; + + /* Grab access to FN0 for ELP reg. */ + func->card->quirks |= MMC_QUIRK_LENIENT_FN0; + + /* Use block mode for transferring over one block size of data */ + func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; + + wlan_data = wl12xx_get_platform_data(); + if (IS_ERR(wlan_data)) { + ret = PTR_ERR(wlan_data); + wl1271_error("missing wlan platform data: %d", ret); + goto out_free; + } + + wl->irq = wlan_data->irq; + wl->ref_clock = wlan_data->board_ref_clock; + wl->tcxo_clock = wlan_data->board_tcxo_clock; + wl->platform_quirks = wlan_data->platform_quirks; + + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + irqflags = IRQF_TRIGGER_RISING; + else + irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; + + ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq, + irqflags, + DRIVER_NAME, wl); + if (ret < 0) { + wl1271_error("request_irq() failed: %d", ret); + goto out_free; + } + + enable_irq_wake(wl->irq); + device_init_wakeup(wl1271_sdio_wl_to_dev(wl), 1); + + disable_irq(wl->irq); + + /* if sdio can keep power while host is suspended, enable wow */ + mmcflags = sdio_get_host_pm_caps(func); + wl1271_debug(DEBUG_SDIO, "sdio PM caps = 0x%x", mmcflags); + + if (mmcflags & MMC_PM_KEEP_POWER) + hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY; + + ret = wl1271_init_ieee80211(wl); + if (ret) + goto out_irq; + + ret = wl1271_register_hw(wl); + if (ret) + goto out_irq; + + sdio_set_drvdata(func, wl); + + /* Tell PM core that we don't need the card to be powered now */ + pm_runtime_put_noidle(&func->dev); + + wl1271_notice("initialized"); + + return 0; + + out_irq: + free_irq(wl->irq, wl); + + out_free: + wl1271_free_hw(wl); + + return ret; +} + +static void __devexit wl1271_remove(struct sdio_func *func) +{ + struct wl1271 *wl = sdio_get_drvdata(func); + + /* Undo decrement done above in wl1271_probe */ + pm_runtime_get_noresume(&func->dev); + + wl1271_unregister_hw(wl); + device_init_wakeup(wl1271_sdio_wl_to_dev(wl), 0); + disable_irq_wake(wl->irq); + free_irq(wl->irq, wl); + wl1271_free_hw(wl); +} + +#ifdef CONFIG_PM +static int wl1271_suspend(struct device *dev) +{ + /* Tell MMC/SDIO core it's OK to power down the card + * (if it isn't already), but not to remove it completely */ + struct sdio_func *func = dev_to_sdio_func(dev); + struct wl1271 *wl = sdio_get_drvdata(func); + mmc_pm_flag_t sdio_flags; + int ret = 0; + + wl1271_debug(DEBUG_MAC80211, "wl1271 suspend. wow_enabled: %d", + wl->wow_enabled); + + /* check whether sdio should keep power */ + if (wl->wow_enabled) { + sdio_flags = sdio_get_host_pm_caps(func); + + if (!(sdio_flags & MMC_PM_KEEP_POWER)) { + wl1271_error("can't keep power while host " + "is suspended"); + ret = -EINVAL; + goto out; + } + + /* keep power while host suspended */ + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) { + wl1271_error("error while trying to keep power"); + goto out; + } + + /* release host */ + sdio_release_host(func); + } +out: + return ret; +} + +static int wl1271_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct wl1271 *wl = sdio_get_drvdata(func); + + wl1271_debug(DEBUG_MAC80211, "wl1271 resume"); + if (wl->wow_enabled) { + /* claim back host */ + sdio_claim_host(func); + } + + return 0; +} + +static const struct dev_pm_ops wl1271_sdio_pm_ops = { + .suspend = wl1271_suspend, + .resume = wl1271_resume, +}; +#endif + +static struct sdio_driver wl1271_sdio_driver = { + .name = "wl1271_sdio", + .id_table = wl1271_devices, + .probe = wl1271_probe, + .remove = __devexit_p(wl1271_remove), +#ifdef CONFIG_PM + .drv = { + .pm = &wl1271_sdio_pm_ops, + }, +#endif +}; + +static int __init wl1271_init(void) +{ + int ret; + + ret = sdio_register_driver(&wl1271_sdio_driver); + if (ret < 0) { + wl1271_error("failed to register sdio driver: %d", ret); + goto out; + } + +out: + return ret; +} + +static void __exit wl1271_exit(void) +{ + sdio_unregister_driver(&wl1271_sdio_driver); + + wl1271_notice("unloaded"); +} + +module_init(wl1271_init); +module_exit(wl1271_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>"); +MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>"); +MODULE_FIRMWARE(WL1271_FW_NAME); +MODULE_FIRMWARE(WL128X_FW_NAME); +MODULE_FIRMWARE(WL127X_AP_FW_NAME); +MODULE_FIRMWARE(WL128X_AP_FW_NAME); diff --git a/drivers/net/wireless/wl12xx/sdio_test.c b/drivers/net/wireless/wl12xx/sdio_test.c new file mode 100644 index 00000000..f2891539 --- /dev/null +++ b/drivers/net/wireless/wl12xx/sdio_test.c @@ -0,0 +1,534 @@ +/* + * SDIO testing driver for wl12xx + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Roger Quadros <roger.quadros@nokia.com> + * + * wl12xx read/write routines taken from the main module + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/crc7.h> +#include <linux/vmalloc.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/card.h> +#include <linux/gpio.h> +#include <linux/wl12xx.h> +#include <linux/kthread.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> + +#include "wl12xx.h" +#include "io.h" +#include "boot.h" + +#ifndef SDIO_VENDOR_ID_TI +#define SDIO_VENDOR_ID_TI 0x0097 +#endif + +#ifndef SDIO_DEVICE_ID_TI_WL1271 +#define SDIO_DEVICE_ID_TI_WL1271 0x4076 +#endif + +static bool rx, tx; + +module_param(rx, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(rx, "Perform rx test. Default (0). " + "This test continuously reads data from the SDIO device.\n"); + +module_param(tx, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(tx, "Perform tx test. Default (0). " + "This test continuously writes data to the SDIO device.\n"); + +struct wl1271_test { + struct wl1271 wl; + struct task_struct *test_task; +}; + +static const struct sdio_device_id wl1271_devices[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271) }, + {} +}; + +static inline struct sdio_func *wl_to_func(struct wl1271 *wl) +{ + return wl->if_priv; +} + +static struct device *wl1271_sdio_wl_to_dev(struct wl1271 *wl) +{ + return &(wl_to_func(wl)->dev); +} + +static void wl1271_sdio_raw_read(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + int ret = 0; + struct sdio_func *func = wl_to_func(wl); + + if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG_ADDR)) { + ((u8 *)buf)[0] = sdio_f0_readb(func, addr, &ret); + wl1271_debug(DEBUG_SDIO, "sdio read 52 addr 0x%x, byte 0x%02x", + addr, ((u8 *)buf)[0]); + } else { + if (fixed) + ret = sdio_readsb(func, buf, addr, len); + else + ret = sdio_memcpy_fromio(func, buf, addr, len); + + wl1271_debug(DEBUG_SDIO, "sdio read 53 addr 0x%x, %zu bytes", + addr, len); + wl1271_dump_ascii(DEBUG_SDIO, "data: ", buf, len); + } + + if (ret) + wl1271_error("sdio read failed (%d)", ret); +} + +static void wl1271_sdio_raw_write(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + int ret = 0; + struct sdio_func *func = wl_to_func(wl); + + if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG_ADDR)) { + sdio_f0_writeb(func, ((u8 *)buf)[0], addr, &ret); + wl1271_debug(DEBUG_SDIO, "sdio write 52 addr 0x%x, byte 0x%02x", + addr, ((u8 *)buf)[0]); + } else { + wl1271_debug(DEBUG_SDIO, "sdio write 53 addr 0x%x, %zu bytes", + addr, len); + wl1271_dump_ascii(DEBUG_SDIO, "data: ", buf, len); + + if (fixed) + ret = sdio_writesb(func, addr, buf, len); + else + ret = sdio_memcpy_toio(func, addr, buf, len); + } + if (ret) + wl1271_error("sdio write failed (%d)", ret); + +} + +static int wl1271_sdio_set_power(struct wl1271 *wl, bool enable) +{ + struct sdio_func *func = wl_to_func(wl); + int ret; + + /* Let the SDIO stack handle wlan_enable control, so we + * keep host claimed while wlan is in use to keep wl1271 + * alive. + */ + if (enable) { + /* Power up the card */ + ret = pm_runtime_get_sync(&func->dev); + if (ret < 0) + goto out; + sdio_claim_host(func); + sdio_enable_func(func); + sdio_release_host(func); + } else { + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + /* Power down the card */ + ret = pm_runtime_put_sync(&func->dev); + } + +out: + return ret; +} + +static void wl1271_sdio_disable_interrupts(struct wl1271 *wl) +{ +} + +static void wl1271_sdio_enable_interrupts(struct wl1271 *wl) +{ +} + + +static struct wl1271_if_operations sdio_ops = { + .read = wl1271_sdio_raw_read, + .write = wl1271_sdio_raw_write, + .power = wl1271_sdio_set_power, + .dev = wl1271_sdio_wl_to_dev, + .enable_irq = wl1271_sdio_enable_interrupts, + .disable_irq = wl1271_sdio_disable_interrupts, +}; + +static void wl1271_fw_wakeup(struct wl1271 *wl) +{ + u32 elp_reg; + + elp_reg = ELPCTRL_WAKE_UP; + wl1271_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, elp_reg); +} + +static int wl1271_fetch_firmware(struct wl1271 *wl) +{ + const struct firmware *fw; + int ret; + + if (wl->chip.id == CHIP_ID_1283_PG20) + ret = request_firmware(&fw, WL128X_FW_NAME, + wl1271_wl_to_dev(wl)); + else + ret = request_firmware(&fw, WL1271_FW_NAME, + wl1271_wl_to_dev(wl)); + + if (ret < 0) { + wl1271_error("could not get firmware: %d", ret); + return ret; + } + + if (fw->size % 4) { + wl1271_error("firmware size is not multiple of 32 bits: %zu", + fw->size); + ret = -EILSEQ; + goto out; + } + + wl->fw_len = fw->size; + wl->fw = vmalloc(wl->fw_len); + + if (!wl->fw) { + wl1271_error("could not allocate memory for the firmware"); + ret = -ENOMEM; + goto out; + } + + memcpy(wl->fw, fw->data, wl->fw_len); + + ret = 0; + +out: + release_firmware(fw); + + return ret; +} + +static int wl1271_fetch_nvs(struct wl1271 *wl) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, WL12XX_NVS_NAME, wl1271_wl_to_dev(wl)); + + if (ret < 0) { + wl1271_error("could not get nvs file: %d", ret); + return ret; + } + + wl->nvs = kmemdup(fw->data, fw->size, GFP_KERNEL); + + if (!wl->nvs) { + wl1271_error("could not allocate memory for the nvs file"); + ret = -ENOMEM; + goto out; + } + + wl->nvs_len = fw->size; + +out: + release_firmware(fw); + + return ret; +} + +static int wl1271_chip_wakeup(struct wl1271 *wl) +{ + struct wl1271_partition_set partition; + int ret; + + msleep(WL1271_PRE_POWER_ON_SLEEP); + ret = wl1271_power_on(wl); + if (ret) + return ret; + + msleep(WL1271_POWER_ON_SLEEP); + + /* We don't need a real memory partition here, because we only want + * to use the registers at this point. */ + memset(&partition, 0, sizeof(partition)); + partition.reg.start = REGISTERS_BASE; + partition.reg.size = REGISTERS_DOWN_SIZE; + wl1271_set_partition(wl, &partition); + + /* ELP module wake up */ + wl1271_fw_wakeup(wl); + + /* whal_FwCtrl_BootSm() */ + + /* 0. read chip id from CHIP_ID */ + wl->chip.id = wl1271_read32(wl, CHIP_ID_B); + + /* 1. check if chip id is valid */ + + switch (wl->chip.id) { + case CHIP_ID_1271_PG10: + wl1271_warning("chip id 0x%x (1271 PG10) support is obsolete", + wl->chip.id); + break; + case CHIP_ID_1271_PG20: + wl1271_notice("chip id 0x%x (1271 PG20)", + wl->chip.id); + break; + case CHIP_ID_1283_PG20: + wl1271_notice("chip id 0x%x (1283 PG20)", + wl->chip.id); + break; + case CHIP_ID_1283_PG10: + default: + wl1271_warning("unsupported chip id: 0x%x", wl->chip.id); + return -ENODEV; + } + + return ret; +} + +static struct wl1271_partition_set part_down = { + .mem = { + .start = 0x00000000, + .size = 0x000177c0 + }, + .reg = { + .start = REGISTERS_BASE, + .size = 0x00008800 + }, + .mem2 = { + .start = 0x00000000, + .size = 0x00000000 + }, + .mem3 = { + .start = 0x00000000, + .size = 0x00000000 + }, +}; + +static int tester(void *data) +{ + struct wl1271 *wl = data; + struct sdio_func *func = wl_to_func(wl); + struct device *pdev = &func->dev; + int ret = 0; + bool rx_started = 0; + bool tx_started = 0; + uint8_t *tx_buf, *rx_buf; + int test_size = PAGE_SIZE; + u32 addr = 0; + struct wl1271_partition_set partition; + + /* We assume chip is powered up and firmware fetched */ + + memcpy(&partition, &part_down, sizeof(partition)); + partition.mem.start = addr; + wl1271_set_partition(wl, &partition); + + tx_buf = kmalloc(test_size, GFP_KERNEL); + rx_buf = kmalloc(test_size, GFP_KERNEL); + if (!tx_buf || !rx_buf) { + dev_err(pdev, + "Could not allocate memory. Test will not run.\n"); + ret = -ENOMEM; + goto free; + } + + memset(tx_buf, 0x5a, test_size); + + /* write something in data area so we can read it back */ + wl1271_write(wl, addr, tx_buf, test_size, false); + + while (!kthread_should_stop()) { + if (rx && !rx_started) { + dev_info(pdev, "starting rx test\n"); + rx_started = 1; + } else if (!rx && rx_started) { + dev_info(pdev, "stopping rx test\n"); + rx_started = 0; + } + + if (tx && !tx_started) { + dev_info(pdev, "starting tx test\n"); + tx_started = 1; + } else if (!tx && tx_started) { + dev_info(pdev, "stopping tx test\n"); + tx_started = 0; + } + + if (rx_started) + wl1271_read(wl, addr, rx_buf, test_size, false); + + if (tx_started) + wl1271_write(wl, addr, tx_buf, test_size, false); + + if (!rx_started && !tx_started) + msleep(100); + } + +free: + kfree(tx_buf); + kfree(rx_buf); + return ret; +} + +static int __devinit wl1271_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + const struct wl12xx_platform_data *wlan_data; + struct wl1271 *wl; + struct wl1271_test *wl_test; + int ret = 0; + + /* wl1271 has 2 sdio functions we handle just the wlan part */ + if (func->num != 0x02) + return -ENODEV; + + wl_test = kzalloc(sizeof(struct wl1271_test), GFP_KERNEL); + if (!wl_test) { + dev_err(&func->dev, "Could not allocate memory\n"); + return -ENOMEM; + } + + wl = &wl_test->wl; + + wl->if_priv = func; + wl->if_ops = &sdio_ops; + + /* Grab access to FN0 for ELP reg. */ + func->card->quirks |= MMC_QUIRK_LENIENT_FN0; + + /* Use block mode for transferring over one block size of data */ + func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; + + wlan_data = wl12xx_get_platform_data(); + if (IS_ERR(wlan_data)) { + ret = PTR_ERR(wlan_data); + dev_err(&func->dev, "missing wlan platform data: %d\n", ret); + goto out_free; + } + + wl->irq = wlan_data->irq; + wl->ref_clock = wlan_data->board_ref_clock; + wl->tcxo_clock = wlan_data->board_tcxo_clock; + + sdio_set_drvdata(func, wl_test); + + + /* power up the device */ + ret = wl1271_chip_wakeup(wl); + if (ret) { + dev_err(&func->dev, "could not wake up chip\n"); + goto out_free; + } + + if (wl->fw == NULL) { + ret = wl1271_fetch_firmware(wl); + if (ret < 0) { + dev_err(&func->dev, "firmware fetch error\n"); + goto out_off; + } + } + + /* fetch NVS */ + if (wl->nvs == NULL) { + ret = wl1271_fetch_nvs(wl); + if (ret < 0) { + dev_err(&func->dev, "NVS fetch error\n"); + goto out_off; + } + } + + ret = wl1271_load_firmware(wl); + if (ret < 0) { + dev_err(&func->dev, "firmware load error: %d\n", ret); + goto out_free; + } + + dev_info(&func->dev, "initialized\n"); + + /* I/O testing will be done in the tester thread */ + + wl_test->test_task = kthread_run(tester, wl, "sdio_tester"); + if (IS_ERR(wl_test->test_task)) { + dev_err(&func->dev, "unable to create kernel thread\n"); + ret = PTR_ERR(wl_test->test_task); + goto out_free; + } + + return 0; + +out_off: + /* power off the chip */ + wl1271_power_off(wl); + +out_free: + kfree(wl_test); + return ret; +} + +static void __devexit wl1271_remove(struct sdio_func *func) +{ + struct wl1271_test *wl_test = sdio_get_drvdata(func); + + /* stop the I/O test thread */ + kthread_stop(wl_test->test_task); + + /* power off the chip */ + wl1271_power_off(&wl_test->wl); + + vfree(wl_test->wl.fw); + wl_test->wl.fw = NULL; + kfree(wl_test->wl.nvs); + wl_test->wl.nvs = NULL; + + kfree(wl_test); +} + +static struct sdio_driver wl1271_sdio_driver = { + .name = "wl12xx_sdio_test", + .id_table = wl1271_devices, + .probe = wl1271_probe, + .remove = __devexit_p(wl1271_remove), +}; + +static int __init wl1271_init(void) +{ + int ret; + + ret = sdio_register_driver(&wl1271_sdio_driver); + if (ret < 0) + pr_err("failed to register sdio driver: %d\n", ret); + + return ret; +} +module_init(wl1271_init); + +static void __exit wl1271_exit(void) +{ + sdio_unregister_driver(&wl1271_sdio_driver); +} +module_exit(wl1271_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Roger Quadros <roger.quadros@nokia.com>"); + diff --git a/drivers/net/wireless/wl12xx/spi.c b/drivers/net/wireless/wl12xx/spi.c new file mode 100644 index 00000000..51662bb6 --- /dev/null +++ b/drivers/net/wireless/wl12xx/spi.c @@ -0,0 +1,505 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/crc7.h> +#include <linux/spi/spi.h> +#include <linux/wl12xx.h> +#include <linux/slab.h> + +#include "wl12xx.h" +#include "wl12xx_80211.h" +#include "io.h" + +#include "reg.h" + +#define WSPI_CMD_READ 0x40000000 +#define WSPI_CMD_WRITE 0x00000000 +#define WSPI_CMD_FIXED 0x20000000 +#define WSPI_CMD_BYTE_LENGTH 0x1FFE0000 +#define WSPI_CMD_BYTE_LENGTH_OFFSET 17 +#define WSPI_CMD_BYTE_ADDR 0x0001FFFF + +#define WSPI_INIT_CMD_CRC_LEN 5 + +#define WSPI_INIT_CMD_START 0x00 +#define WSPI_INIT_CMD_TX 0x40 +/* the extra bypass bit is sampled by the TNET as '1' */ +#define WSPI_INIT_CMD_BYPASS_BIT 0x80 +#define WSPI_INIT_CMD_FIXEDBUSY_LEN 0x07 +#define WSPI_INIT_CMD_EN_FIXEDBUSY 0x80 +#define WSPI_INIT_CMD_DIS_FIXEDBUSY 0x00 +#define WSPI_INIT_CMD_IOD 0x40 +#define WSPI_INIT_CMD_IP 0x20 +#define WSPI_INIT_CMD_CS 0x10 +#define WSPI_INIT_CMD_WS 0x08 +#define WSPI_INIT_CMD_WSPI 0x01 +#define WSPI_INIT_CMD_END 0x01 + +#define WSPI_INIT_CMD_LEN 8 + +#define HW_ACCESS_WSPI_FIXED_BUSY_LEN \ + ((WL1271_BUSY_WORD_LEN - 4) / sizeof(u32)) +#define HW_ACCESS_WSPI_INIT_CMD_MASK 0 + +/* HW limitation: maximum possible chunk size is 4095 bytes */ +#define WSPI_MAX_CHUNK_SIZE 4092 + +#define WSPI_MAX_NUM_OF_CHUNKS (WL1271_AGGR_BUFFER_SIZE / WSPI_MAX_CHUNK_SIZE) + +static inline struct spi_device *wl_to_spi(struct wl1271 *wl) +{ + return wl->if_priv; +} + +static struct device *wl1271_spi_wl_to_dev(struct wl1271 *wl) +{ + return &(wl_to_spi(wl)->dev); +} + +static void wl1271_spi_disable_interrupts(struct wl1271 *wl) +{ + disable_irq(wl->irq); +} + +static void wl1271_spi_enable_interrupts(struct wl1271 *wl) +{ + enable_irq(wl->irq); +} + +static void wl1271_spi_reset(struct wl1271 *wl) +{ + u8 *cmd; + struct spi_transfer t; + struct spi_message m; + + cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); + if (!cmd) { + wl1271_error("could not allocate cmd for spi reset"); + return; + } + + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + memset(cmd, 0xff, WSPI_INIT_CMD_LEN); + + t.tx_buf = cmd; + t.len = WSPI_INIT_CMD_LEN; + spi_message_add_tail(&t, &m); + + spi_sync(wl_to_spi(wl), &m); + + wl1271_dump(DEBUG_SPI, "spi reset -> ", cmd, WSPI_INIT_CMD_LEN); + kfree(cmd); +} + +static void wl1271_spi_init(struct wl1271 *wl) +{ + u8 crc[WSPI_INIT_CMD_CRC_LEN], *cmd; + struct spi_transfer t; + struct spi_message m; + + cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); + if (!cmd) { + wl1271_error("could not allocate cmd for spi init"); + return; + } + + memset(crc, 0, sizeof(crc)); + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + /* + * Set WSPI_INIT_COMMAND + * the data is being send from the MSB to LSB + */ + cmd[2] = 0xff; + cmd[3] = 0xff; + cmd[1] = WSPI_INIT_CMD_START | WSPI_INIT_CMD_TX; + cmd[0] = 0; + cmd[7] = 0; + cmd[6] |= HW_ACCESS_WSPI_INIT_CMD_MASK << 3; + cmd[6] |= HW_ACCESS_WSPI_FIXED_BUSY_LEN & WSPI_INIT_CMD_FIXEDBUSY_LEN; + + if (HW_ACCESS_WSPI_FIXED_BUSY_LEN == 0) + cmd[5] |= WSPI_INIT_CMD_DIS_FIXEDBUSY; + else + cmd[5] |= WSPI_INIT_CMD_EN_FIXEDBUSY; + + cmd[5] |= WSPI_INIT_CMD_IOD | WSPI_INIT_CMD_IP | WSPI_INIT_CMD_CS + | WSPI_INIT_CMD_WSPI | WSPI_INIT_CMD_WS; + + crc[0] = cmd[1]; + crc[1] = cmd[0]; + crc[2] = cmd[7]; + crc[3] = cmd[6]; + crc[4] = cmd[5]; + + cmd[4] |= crc7(0, crc, WSPI_INIT_CMD_CRC_LEN) << 1; + cmd[4] |= WSPI_INIT_CMD_END; + + t.tx_buf = cmd; + t.len = WSPI_INIT_CMD_LEN; + spi_message_add_tail(&t, &m); + + spi_sync(wl_to_spi(wl), &m); + wl1271_dump(DEBUG_SPI, "spi init -> ", cmd, WSPI_INIT_CMD_LEN); + kfree(cmd); +} + +#define WL1271_BUSY_WORD_TIMEOUT 1000 + +static int wl1271_spi_read_busy(struct wl1271 *wl) +{ + struct spi_transfer t[1]; + struct spi_message m; + u32 *busy_buf; + int num_busy_bytes = 0; + + /* + * Read further busy words from SPI until a non-busy word is + * encountered, then read the data itself into the buffer. + */ + + num_busy_bytes = WL1271_BUSY_WORD_TIMEOUT; + busy_buf = wl->buffer_busyword; + while (num_busy_bytes) { + num_busy_bytes--; + spi_message_init(&m); + memset(t, 0, sizeof(t)); + t[0].rx_buf = busy_buf; + t[0].len = sizeof(u32); + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + spi_sync(wl_to_spi(wl), &m); + + if (*busy_buf & 0x1) + return 0; + } + + /* The SPI bus is unresponsive, the read failed. */ + wl1271_error("SPI read busy-word timeout!\n"); + return -ETIMEDOUT; +} + +static void wl1271_spi_raw_read(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + struct spi_transfer t[2]; + struct spi_message m; + u32 *busy_buf; + u32 *cmd; + u32 chunk_len; + + while (len > 0) { + chunk_len = min((size_t)WSPI_MAX_CHUNK_SIZE, len); + + cmd = &wl->buffer_cmd; + busy_buf = wl->buffer_busyword; + + *cmd = 0; + *cmd |= WSPI_CMD_READ; + *cmd |= (chunk_len << WSPI_CMD_BYTE_LENGTH_OFFSET) & + WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + if (fixed) + *cmd |= WSPI_CMD_FIXED; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = 4; + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + + /* Busy and non busy words read */ + t[1].rx_buf = busy_buf; + t[1].len = WL1271_BUSY_WORD_LEN; + t[1].cs_change = true; + spi_message_add_tail(&t[1], &m); + + spi_sync(wl_to_spi(wl), &m); + + if (!(busy_buf[WL1271_BUSY_WORD_CNT - 1] & 0x1) && + wl1271_spi_read_busy(wl)) { + memset(buf, 0, chunk_len); + return; + } + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].rx_buf = buf; + t[0].len = chunk_len; + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + + spi_sync(wl_to_spi(wl), &m); + + wl1271_dump(DEBUG_SPI, "spi_read cmd -> ", cmd, sizeof(*cmd)); + wl1271_dump(DEBUG_SPI, "spi_read buf <- ", buf, chunk_len); + + if (!fixed) + addr += chunk_len; + buf += chunk_len; + len -= chunk_len; + } +} + +static void wl1271_spi_raw_write(struct wl1271 *wl, int addr, void *buf, + size_t len, bool fixed) +{ + struct spi_transfer t[2 * WSPI_MAX_NUM_OF_CHUNKS]; + struct spi_message m; + u32 commands[WSPI_MAX_NUM_OF_CHUNKS]; + u32 *cmd; + u32 chunk_len; + int i; + + WARN_ON(len > WL1271_AGGR_BUFFER_SIZE); + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + cmd = &commands[0]; + i = 0; + while (len > 0) { + chunk_len = min((size_t)WSPI_MAX_CHUNK_SIZE, len); + + *cmd = 0; + *cmd |= WSPI_CMD_WRITE; + *cmd |= (chunk_len << WSPI_CMD_BYTE_LENGTH_OFFSET) & + WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + if (fixed) + *cmd |= WSPI_CMD_FIXED; + + t[i].tx_buf = cmd; + t[i].len = sizeof(*cmd); + spi_message_add_tail(&t[i++], &m); + + t[i].tx_buf = buf; + t[i].len = chunk_len; + spi_message_add_tail(&t[i++], &m); + + wl1271_dump(DEBUG_SPI, "spi_write cmd -> ", cmd, sizeof(*cmd)); + wl1271_dump(DEBUG_SPI, "spi_write buf -> ", buf, chunk_len); + + if (!fixed) + addr += chunk_len; + buf += chunk_len; + len -= chunk_len; + cmd++; + } + + spi_sync(wl_to_spi(wl), &m); +} + +static irqreturn_t wl1271_hardirq(int irq, void *cookie) +{ + struct wl1271 *wl = cookie; + unsigned long flags; + + wl1271_debug(DEBUG_IRQ, "IRQ"); + + /* complete the ELP completion */ + spin_lock_irqsave(&wl->wl_lock, flags); + set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags); + if (wl->elp_compl) { + complete(wl->elp_compl); + wl->elp_compl = NULL; + } + spin_unlock_irqrestore(&wl->wl_lock, flags); + + return IRQ_WAKE_THREAD; +} + +static int wl1271_spi_set_power(struct wl1271 *wl, bool enable) +{ + if (wl->set_power) + wl->set_power(enable); + + return 0; +} + +static struct wl1271_if_operations spi_ops = { + .read = wl1271_spi_raw_read, + .write = wl1271_spi_raw_write, + .reset = wl1271_spi_reset, + .init = wl1271_spi_init, + .power = wl1271_spi_set_power, + .dev = wl1271_spi_wl_to_dev, + .enable_irq = wl1271_spi_enable_interrupts, + .disable_irq = wl1271_spi_disable_interrupts, + .set_block_size = NULL, +}; + +static int __devinit wl1271_probe(struct spi_device *spi) +{ + struct wl12xx_platform_data *pdata; + struct ieee80211_hw *hw; + struct wl1271 *wl; + unsigned long irqflags; + int ret; + + pdata = spi->dev.platform_data; + if (!pdata) { + wl1271_error("no platform data"); + return -ENODEV; + } + + hw = wl1271_alloc_hw(); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + wl = hw->priv; + + dev_set_drvdata(&spi->dev, wl); + wl->if_priv = spi; + + wl->if_ops = &spi_ops; + + /* This is the only SPI value that we need to set here, the rest + * comes from the board-peripherals file */ + spi->bits_per_word = 32; + + ret = spi_setup(spi); + if (ret < 0) { + wl1271_error("spi_setup failed"); + goto out_free; + } + + wl->set_power = pdata->set_power; + if (!wl->set_power) { + wl1271_error("set power function missing in platform data"); + ret = -ENODEV; + goto out_free; + } + + wl->ref_clock = pdata->board_ref_clock; + wl->tcxo_clock = pdata->board_tcxo_clock; + wl->platform_quirks = pdata->platform_quirks; + + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + irqflags = IRQF_TRIGGER_RISING; + else + irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; + + wl->irq = spi->irq; + if (wl->irq < 0) { + wl1271_error("irq missing in platform data"); + ret = -ENODEV; + goto out_free; + } + + ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq, + irqflags, + DRIVER_NAME, wl); + if (ret < 0) { + wl1271_error("request_irq() failed: %d", ret); + goto out_free; + } + + disable_irq(wl->irq); + + ret = wl1271_init_ieee80211(wl); + if (ret) + goto out_irq; + + ret = wl1271_register_hw(wl); + if (ret) + goto out_irq; + + wl1271_notice("initialized"); + + return 0; + + out_irq: + free_irq(wl->irq, wl); + + out_free: + wl1271_free_hw(wl); + + return ret; +} + +static int __devexit wl1271_remove(struct spi_device *spi) +{ + struct wl1271 *wl = dev_get_drvdata(&spi->dev); + + wl1271_unregister_hw(wl); + free_irq(wl->irq, wl); + wl1271_free_hw(wl); + + return 0; +} + + +static struct spi_driver wl1271_spi_driver = { + .driver = { + .name = "wl1271_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = wl1271_probe, + .remove = __devexit_p(wl1271_remove), +}; + +static int __init wl1271_init(void) +{ + int ret; + + ret = spi_register_driver(&wl1271_spi_driver); + if (ret < 0) { + wl1271_error("failed to register spi driver: %d", ret); + goto out; + } + +out: + return ret; +} + +static void __exit wl1271_exit(void) +{ + spi_unregister_driver(&wl1271_spi_driver); + + wl1271_notice("unloaded"); +} + +module_init(wl1271_init); +module_exit(wl1271_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>"); +MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>"); +MODULE_FIRMWARE(WL1271_FW_NAME); +MODULE_FIRMWARE(WL128X_FW_NAME); +MODULE_FIRMWARE(WL127X_AP_FW_NAME); +MODULE_FIRMWARE(WL128X_AP_FW_NAME); +MODULE_ALIAS("spi:wl1271"); diff --git a/drivers/net/wireless/wl12xx/testmode.c b/drivers/net/wireless/wl12xx/testmode.c new file mode 100644 index 00000000..da351d7c --- /dev/null +++ b/drivers/net/wireless/wl12xx/testmode.c @@ -0,0 +1,297 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#include "testmode.h" + +#include <linux/slab.h> +#include <net/genetlink.h> + +#include "wl12xx.h" +#include "acx.h" +#include "reg.h" + +#define WL1271_TM_MAX_DATA_LENGTH 1024 + +enum wl1271_tm_commands { + WL1271_TM_CMD_UNSPEC, + WL1271_TM_CMD_TEST, + WL1271_TM_CMD_INTERROGATE, + WL1271_TM_CMD_CONFIGURE, + WL1271_TM_CMD_NVS_PUSH, + WL1271_TM_CMD_SET_PLT_MODE, + WL1271_TM_CMD_RECOVER, + + __WL1271_TM_CMD_AFTER_LAST +}; +#define WL1271_TM_CMD_MAX (__WL1271_TM_CMD_AFTER_LAST - 1) + +enum wl1271_tm_attrs { + WL1271_TM_ATTR_UNSPEC, + WL1271_TM_ATTR_CMD_ID, + WL1271_TM_ATTR_ANSWER, + WL1271_TM_ATTR_DATA, + WL1271_TM_ATTR_IE_ID, + WL1271_TM_ATTR_PLT_MODE, + + __WL1271_TM_ATTR_AFTER_LAST +}; +#define WL1271_TM_ATTR_MAX (__WL1271_TM_ATTR_AFTER_LAST - 1) + +static struct nla_policy wl1271_tm_policy[WL1271_TM_ATTR_MAX + 1] = { + [WL1271_TM_ATTR_CMD_ID] = { .type = NLA_U32 }, + [WL1271_TM_ATTR_ANSWER] = { .type = NLA_U8 }, + [WL1271_TM_ATTR_DATA] = { .type = NLA_BINARY, + .len = WL1271_TM_MAX_DATA_LENGTH }, + [WL1271_TM_ATTR_IE_ID] = { .type = NLA_U32 }, + [WL1271_TM_ATTR_PLT_MODE] = { .type = NLA_U32 }, +}; + + +static int wl1271_tm_cmd_test(struct wl1271 *wl, struct nlattr *tb[]) +{ + int buf_len, ret, len; + struct sk_buff *skb; + void *buf; + u8 answer = 0; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd test"); + + if (!tb[WL1271_TM_ATTR_DATA]) + return -EINVAL; + + buf = nla_data(tb[WL1271_TM_ATTR_DATA]); + buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); + + if (tb[WL1271_TM_ATTR_ANSWER]) + answer = nla_get_u8(tb[WL1271_TM_ATTR_ANSWER]); + + if (buf_len > sizeof(struct wl1271_command)) + return -EMSGSIZE; + + mutex_lock(&wl->mutex); + ret = wl1271_cmd_test(wl, buf, buf_len, answer); + mutex_unlock(&wl->mutex); + + if (ret < 0) { + wl1271_warning("testmode cmd test failed: %d", ret); + return ret; + } + + if (answer) { + len = nla_total_size(buf_len); + skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len); + if (!skb) + return -ENOMEM; + + NLA_PUT(skb, WL1271_TM_ATTR_DATA, buf_len, buf); + ret = cfg80211_testmode_reply(skb); + if (ret < 0) + return ret; + } + + return 0; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +static int wl1271_tm_cmd_interrogate(struct wl1271 *wl, struct nlattr *tb[]) +{ + int ret; + struct wl1271_command *cmd; + struct sk_buff *skb; + u8 ie_id; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd interrogate"); + + if (!tb[WL1271_TM_ATTR_IE_ID]) + return -EINVAL; + + ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + mutex_lock(&wl->mutex); + ret = wl1271_cmd_interrogate(wl, ie_id, cmd, sizeof(*cmd)); + mutex_unlock(&wl->mutex); + + if (ret < 0) { + wl1271_warning("testmode cmd interrogate failed: %d", ret); + return ret; + } + + skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + NLA_PUT(skb, WL1271_TM_ATTR_DATA, sizeof(*cmd), cmd); + + return 0; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +static int wl1271_tm_cmd_configure(struct wl1271 *wl, struct nlattr *tb[]) +{ + int buf_len, ret; + void *buf; + u8 ie_id; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd configure"); + + if (!tb[WL1271_TM_ATTR_DATA]) + return -EINVAL; + if (!tb[WL1271_TM_ATTR_IE_ID]) + return -EINVAL; + + ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); + buf = nla_data(tb[WL1271_TM_ATTR_DATA]); + buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); + + if (buf_len > sizeof(struct wl1271_command)) + return -EMSGSIZE; + + mutex_lock(&wl->mutex); + ret = wl1271_cmd_configure(wl, ie_id, buf, buf_len); + mutex_unlock(&wl->mutex); + + if (ret < 0) { + wl1271_warning("testmode cmd configure failed: %d", ret); + return ret; + } + + return 0; +} + +static int wl1271_tm_cmd_nvs_push(struct wl1271 *wl, struct nlattr *tb[]) +{ + int ret = 0; + size_t len; + void *buf; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd nvs push"); + + if (!tb[WL1271_TM_ATTR_DATA]) + return -EINVAL; + + buf = nla_data(tb[WL1271_TM_ATTR_DATA]); + len = nla_len(tb[WL1271_TM_ATTR_DATA]); + + mutex_lock(&wl->mutex); + + kfree(wl->nvs); + + if ((wl->chip.id == CHIP_ID_1283_PG20) && + (len != sizeof(struct wl128x_nvs_file))) + return -EINVAL; + else if (len != sizeof(struct wl1271_nvs_file)) + return -EINVAL; + + wl->nvs = kzalloc(len, GFP_KERNEL); + if (!wl->nvs) { + wl1271_error("could not allocate memory for the nvs file"); + ret = -ENOMEM; + goto out; + } + + memcpy(wl->nvs, buf, len); + wl->nvs_len = len; + + wl1271_debug(DEBUG_TESTMODE, "testmode pushed nvs"); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_tm_cmd_set_plt_mode(struct wl1271 *wl, struct nlattr *tb[]) +{ + u32 val; + int ret; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd set plt mode"); + + if (!tb[WL1271_TM_ATTR_PLT_MODE]) + return -EINVAL; + + val = nla_get_u32(tb[WL1271_TM_ATTR_PLT_MODE]); + + switch (val) { + case 0: + ret = wl1271_plt_stop(wl); + break; + case 1: + ret = wl1271_plt_start(wl); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int wl1271_tm_cmd_recover(struct wl1271 *wl, struct nlattr *tb[]) +{ + wl1271_debug(DEBUG_TESTMODE, "testmode cmd recover"); + + ieee80211_queue_work(wl->hw, &wl->recovery_work); + + return 0; +} + +int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len) +{ + struct wl1271 *wl = hw->priv; + struct nlattr *tb[WL1271_TM_ATTR_MAX + 1]; + int err; + + err = nla_parse(tb, WL1271_TM_ATTR_MAX, data, len, wl1271_tm_policy); + if (err) + return err; + + if (!tb[WL1271_TM_ATTR_CMD_ID]) + return -EINVAL; + + switch (nla_get_u32(tb[WL1271_TM_ATTR_CMD_ID])) { + case WL1271_TM_CMD_TEST: + return wl1271_tm_cmd_test(wl, tb); + case WL1271_TM_CMD_INTERROGATE: + return wl1271_tm_cmd_interrogate(wl, tb); + case WL1271_TM_CMD_CONFIGURE: + return wl1271_tm_cmd_configure(wl, tb); + case WL1271_TM_CMD_NVS_PUSH: + return wl1271_tm_cmd_nvs_push(wl, tb); + case WL1271_TM_CMD_SET_PLT_MODE: + return wl1271_tm_cmd_set_plt_mode(wl, tb); + case WL1271_TM_CMD_RECOVER: + return wl1271_tm_cmd_recover(wl, tb); + default: + return -EOPNOTSUPP; + } +} diff --git a/drivers/net/wireless/wl12xx/testmode.h b/drivers/net/wireless/wl12xx/testmode.h new file mode 100644 index 00000000..80716542 --- /dev/null +++ b/drivers/net/wireless/wl12xx/testmode.h @@ -0,0 +1,31 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TESTMODE_H__ +#define __TESTMODE_H__ + +#include <net/mac80211.h> + +int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len); + +#endif /* __WL1271_TESTMODE_H__ */ diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c new file mode 100644 index 00000000..ca3ab1c1 --- /dev/null +++ b/drivers/net/wireless/wl12xx/tx.c @@ -0,0 +1,885 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/etherdevice.h> + +#include "wl12xx.h" +#include "io.h" +#include "reg.h" +#include "ps.h" +#include "tx.h" + +static int wl1271_set_default_wep_key(struct wl1271 *wl, u8 id) +{ + int ret; + bool is_ap = (wl->bss_type == BSS_TYPE_AP_BSS); + + if (is_ap) + ret = wl1271_cmd_set_ap_default_wep_key(wl, id); + else + ret = wl1271_cmd_set_sta_default_wep_key(wl, id); + + if (ret < 0) + return ret; + + wl1271_debug(DEBUG_CRYPT, "default wep key idx: %d", (int)id); + return 0; +} + +static int wl1271_alloc_tx_id(struct wl1271 *wl, struct sk_buff *skb) +{ + int id; + + id = find_first_zero_bit(wl->tx_frames_map, ACX_TX_DESCRIPTORS); + if (id >= ACX_TX_DESCRIPTORS) + return -EBUSY; + + __set_bit(id, wl->tx_frames_map); + wl->tx_frames[id] = skb; + wl->tx_frames_cnt++; + return id; +} + +static void wl1271_free_tx_id(struct wl1271 *wl, int id) +{ + if (__test_and_clear_bit(id, wl->tx_frames_map)) { + if (unlikely(wl->tx_frames_cnt == ACX_TX_DESCRIPTORS)) + clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); + + wl->tx_frames[id] = NULL; + wl->tx_frames_cnt--; + } +} + +static int wl1271_tx_update_filters(struct wl1271 *wl, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + + hdr = (struct ieee80211_hdr *)(skb->data + + sizeof(struct wl1271_tx_hw_descr)); + + /* + * stop bssid-based filtering before transmitting authentication + * requests. this way the hw will never drop authentication + * responses coming from BSSIDs it isn't familiar with (e.g. on + * roaming) + */ + if (!ieee80211_is_auth(hdr->frame_control)) + return 0; + + wl1271_configure_filters(wl, FIF_OTHER_BSS); + + return wl1271_acx_rx_config(wl, wl->rx_config, wl->rx_filter); +} + +static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + + /* + * add the station to the known list before transmitting the + * authentication response. this way it won't get de-authed by FW + * when transmitting too soon. + */ + hdr = (struct ieee80211_hdr *)(skb->data + + sizeof(struct wl1271_tx_hw_descr)); + if (ieee80211_is_auth(hdr->frame_control)) + wl1271_acx_set_inconnection_sta(wl, hdr->addr1); +} + +static void wl1271_tx_regulate_link(struct wl1271 *wl, u8 hlid) +{ + bool fw_ps; + u8 tx_blks; + + /* only regulate station links */ + if (hlid < WL1271_AP_STA_HLID_START) + return; + + fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); + tx_blks = wl->links[hlid].allocated_blks; + + /* + * if in FW PS and there is enough data in FW we can put the link + * into high-level PS and clean out its TX queues. + */ + if (fw_ps && tx_blks >= WL1271_PS_STA_MAX_BLOCKS) + wl1271_ps_link_start(wl, hlid, true); +} + +u8 wl1271_tx_get_hlid(struct sk_buff *skb) +{ + struct ieee80211_tx_info *control = IEEE80211_SKB_CB(skb); + + if (control->control.sta) { + struct wl1271_station *wl_sta; + + wl_sta = (struct wl1271_station *) + control->control.sta->drv_priv; + return wl_sta->hlid; + } else { + struct ieee80211_hdr *hdr; + + hdr = (struct ieee80211_hdr *)skb->data; + if (ieee80211_is_mgmt(hdr->frame_control)) + return WL1271_AP_GLOBAL_HLID; + else + return WL1271_AP_BROADCAST_HLID; + } +} + +static unsigned int wl12xx_calc_packet_alignment(struct wl1271 *wl, + unsigned int packet_length) +{ + if (wl->quirks & WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT) + return ALIGN(packet_length, WL12XX_BUS_BLOCK_SIZE); + else + return ALIGN(packet_length, WL1271_TX_ALIGN_TO); +} + +static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra, + u32 buf_offset, u8 hlid) +{ + struct wl1271_tx_hw_descr *desc; + u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; + u32 len; + u32 total_blocks; + int id, ret = -EBUSY; + u32 spare_blocks; + + if (unlikely(wl->quirks & WL12XX_QUIRK_USE_2_SPARE_BLOCKS)) + spare_blocks = 2; + else + spare_blocks = 1; + + if (buf_offset + total_len > WL1271_AGGR_BUFFER_SIZE) + return -EAGAIN; + + /* allocate free identifier for the packet */ + id = wl1271_alloc_tx_id(wl, skb); + if (id < 0) + return id; + + /* approximate the number of blocks required for this packet + in the firmware */ + len = wl12xx_calc_packet_alignment(wl, total_len); + + total_blocks = (len + TX_HW_BLOCK_SIZE - 1) / TX_HW_BLOCK_SIZE + + spare_blocks; + + if (total_blocks <= wl->tx_blocks_available) { + desc = (struct wl1271_tx_hw_descr *)skb_push( + skb, total_len - skb->len); + + /* HW descriptor fields change between wl127x and wl128x */ + if (wl->chip.id == CHIP_ID_1283_PG20) { + desc->wl128x_mem.total_mem_blocks = total_blocks; + } else { + desc->wl127x_mem.extra_blocks = spare_blocks; + desc->wl127x_mem.total_mem_blocks = total_blocks; + } + + desc->id = id; + + wl->tx_blocks_available -= total_blocks; + wl->tx_allocated_blocks += total_blocks; + + if (wl->bss_type == BSS_TYPE_AP_BSS) + wl->links[hlid].allocated_blks += total_blocks; + + ret = 0; + + wl1271_debug(DEBUG_TX, + "tx_allocate: size: %d, blocks: %d, id: %d", + total_len, total_blocks, id); + } else { + wl1271_free_tx_id(wl, id); + } + + return ret; +} + +static bool wl12xx_is_dummy_packet(struct wl1271 *wl, struct sk_buff *skb) +{ + return wl->dummy_packet == skb; +} + +static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, + u32 extra, struct ieee80211_tx_info *control, + u8 hlid) +{ + struct timespec ts; + struct wl1271_tx_hw_descr *desc; + int aligned_len, ac, rate_idx; + s64 hosttime; + u16 tx_attr; + + desc = (struct wl1271_tx_hw_descr *) skb->data; + + /* relocate space for security header */ + if (extra) { + void *framestart = skb->data + sizeof(*desc); + u16 fc = *(u16 *)(framestart + extra); + int hdrlen = ieee80211_hdrlen(cpu_to_le16(fc)); + memmove(framestart, framestart + extra, hdrlen); + } + + /* configure packet life time */ + getnstimeofday(&ts); + hosttime = (timespec_to_ns(&ts) >> 10); + desc->start_time = cpu_to_le32(hosttime - wl->time_offset); + + if (wl->bss_type != BSS_TYPE_AP_BSS) + desc->life_time = cpu_to_le16(TX_HW_MGMT_PKT_LIFETIME_TU); + else + desc->life_time = cpu_to_le16(TX_HW_AP_MODE_PKT_LIFETIME_TU); + + /* queue */ + ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + desc->tid = skb->priority; + + if (wl12xx_is_dummy_packet(wl, skb)) { + /* + * FW expects the dummy packet to have an invalid session id - + * any session id that is different than the one set in the join + */ + tx_attr = ((~wl->session_counter) << + TX_HW_ATTR_OFST_SESSION_COUNTER) & + TX_HW_ATTR_SESSION_COUNTER; + + tx_attr |= TX_HW_ATTR_TX_DUMMY_REQ; + } else { + /* configure the tx attributes */ + tx_attr = + wl->session_counter << TX_HW_ATTR_OFST_SESSION_COUNTER; + } + + if (wl->bss_type != BSS_TYPE_AP_BSS) { + desc->aid = hlid; + + /* if the packets are destined for AP (have a STA entry) + send them with AP rate policies, otherwise use default + basic rates */ + if (control->control.sta) + rate_idx = ACX_TX_AP_FULL_RATE; + else + rate_idx = ACX_TX_BASIC_RATE; + } else { + desc->hlid = hlid; + switch (hlid) { + case WL1271_AP_GLOBAL_HLID: + rate_idx = ACX_TX_AP_MODE_MGMT_RATE; + break; + case WL1271_AP_BROADCAST_HLID: + rate_idx = ACX_TX_AP_MODE_BCST_RATE; + break; + default: + rate_idx = ac; + break; + } + } + + tx_attr |= rate_idx << TX_HW_ATTR_OFST_RATE_POLICY; + desc->reserved = 0; + + aligned_len = wl12xx_calc_packet_alignment(wl, skb->len); + + if (wl->chip.id == CHIP_ID_1283_PG20) { + desc->wl128x_mem.extra_bytes = aligned_len - skb->len; + desc->length = cpu_to_le16(aligned_len >> 2); + + wl1271_debug(DEBUG_TX, "tx_fill_hdr: hlid: %d " + "tx_attr: 0x%x len: %d life: %d mem: %d", + desc->hlid, tx_attr, + le16_to_cpu(desc->length), + le16_to_cpu(desc->life_time), + desc->wl128x_mem.total_mem_blocks); + } else { + int pad; + + /* Store the aligned length in terms of words */ + desc->length = cpu_to_le16(aligned_len >> 2); + + /* calculate number of padding bytes */ + pad = aligned_len - skb->len; + tx_attr |= pad << TX_HW_ATTR_OFST_LAST_WORD_PAD; + + wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d hlid: %d " + "tx_attr: 0x%x len: %d life: %d mem: %d", pad, + desc->hlid, tx_attr, + le16_to_cpu(desc->length), + le16_to_cpu(desc->life_time), + desc->wl127x_mem.total_mem_blocks); + } + + desc->tx_attr = cpu_to_le16(tx_attr); +} + +/* caller must hold wl->mutex */ +static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb, + u32 buf_offset) +{ + struct ieee80211_tx_info *info; + u32 extra = 0; + int ret = 0; + u32 total_len; + u8 hlid; + + if (!skb) + return -EINVAL; + + info = IEEE80211_SKB_CB(skb); + + if (info->control.hw_key && + info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) + extra = WL1271_TKIP_IV_SPACE; + + if (info->control.hw_key) { + bool is_wep; + u8 idx = info->control.hw_key->hw_key_idx; + u32 cipher = info->control.hw_key->cipher; + + is_wep = (cipher == WLAN_CIPHER_SUITE_WEP40) || + (cipher == WLAN_CIPHER_SUITE_WEP104); + + if (unlikely(is_wep && wl->default_key != idx)) { + ret = wl1271_set_default_wep_key(wl, idx); + if (ret < 0) + return ret; + wl->default_key = idx; + } + } + + if (wl->bss_type == BSS_TYPE_AP_BSS) + hlid = wl1271_tx_get_hlid(skb); + else + hlid = TX_HW_DEFAULT_AID; + + ret = wl1271_tx_allocate(wl, skb, extra, buf_offset, hlid); + if (ret < 0) + return ret; + + if (wl->bss_type == BSS_TYPE_AP_BSS) { + wl1271_tx_ap_update_inconnection_sta(wl, skb); + wl1271_tx_regulate_link(wl, hlid); + } else { + wl1271_tx_update_filters(wl, skb); + } + + wl1271_tx_fill_hdr(wl, skb, extra, info, hlid); + + /* + * The length of each packet is stored in terms of + * words. Thus, we must pad the skb data to make sure its + * length is aligned. The number of padding bytes is computed + * and set in wl1271_tx_fill_hdr. + * In special cases, we want to align to a specific block size + * (eg. for wl128x with SDIO we align to 256). + */ + total_len = wl12xx_calc_packet_alignment(wl, skb->len); + + memcpy(wl->aggr_buf + buf_offset, skb->data, skb->len); + memset(wl->aggr_buf + buf_offset + skb->len, 0, total_len - skb->len); + + /* Revert side effects in the dummy packet skb, so it can be reused */ + if (wl12xx_is_dummy_packet(wl, skb)) + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + + return total_len; +} + +u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set) +{ + struct ieee80211_supported_band *band; + u32 enabled_rates = 0; + int bit; + + band = wl->hw->wiphy->bands[wl->band]; + for (bit = 0; bit < band->n_bitrates; bit++) { + if (rate_set & 0x1) + enabled_rates |= band->bitrates[bit].hw_value; + rate_set >>= 1; + } + +#ifdef CONFIG_WL12XX_HT + /* MCS rates indication are on bits 16 - 23 */ + rate_set >>= HW_HT_RATES_OFFSET - band->n_bitrates; + + for (bit = 0; bit < 8; bit++) { + if (rate_set & 0x1) + enabled_rates |= (CONF_HW_BIT_RATE_MCS_0 << bit); + rate_set >>= 1; + } +#endif + + return enabled_rates; +} + +void wl1271_handle_tx_low_watermark(struct wl1271 *wl) +{ + unsigned long flags; + + if (test_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags) && + wl->tx_queue_count <= WL1271_TX_QUEUE_LOW_WATERMARK) { + /* firmware buffer has space, restart queues */ + spin_lock_irqsave(&wl->wl_lock, flags); + ieee80211_wake_queues(wl->hw); + clear_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags); + spin_unlock_irqrestore(&wl->wl_lock, flags); + } +} + +static struct sk_buff *wl1271_sta_skb_dequeue(struct wl1271 *wl) +{ + struct sk_buff *skb = NULL; + unsigned long flags; + + skb = skb_dequeue(&wl->tx_queue[CONF_TX_AC_VO]); + if (skb) + goto out; + skb = skb_dequeue(&wl->tx_queue[CONF_TX_AC_VI]); + if (skb) + goto out; + skb = skb_dequeue(&wl->tx_queue[CONF_TX_AC_BE]); + if (skb) + goto out; + skb = skb_dequeue(&wl->tx_queue[CONF_TX_AC_BK]); + +out: + if (skb) { + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count--; + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + return skb; +} + +static struct sk_buff *wl1271_ap_skb_dequeue(struct wl1271 *wl) +{ + struct sk_buff *skb = NULL; + unsigned long flags; + int i, h, start_hlid; + + /* start from the link after the last one */ + start_hlid = (wl->last_tx_hlid + 1) % AP_MAX_LINKS; + + /* dequeue according to AC, round robin on each link */ + for (i = 0; i < AP_MAX_LINKS; i++) { + h = (start_hlid + i) % AP_MAX_LINKS; + + skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_VO]); + if (skb) + goto out; + skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_VI]); + if (skb) + goto out; + skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_BE]); + if (skb) + goto out; + skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_BK]); + if (skb) + goto out; + } + +out: + if (skb) { + wl->last_tx_hlid = h; + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count--; + spin_unlock_irqrestore(&wl->wl_lock, flags); + } else { + wl->last_tx_hlid = 0; + } + + return skb; +} + +static struct sk_buff *wl1271_skb_dequeue(struct wl1271 *wl) +{ + unsigned long flags; + struct sk_buff *skb = NULL; + + if (wl->bss_type == BSS_TYPE_AP_BSS) + skb = wl1271_ap_skb_dequeue(wl); + else + skb = wl1271_sta_skb_dequeue(wl); + + if (!skb && + test_and_clear_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags)) { + skb = wl->dummy_packet; + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count--; + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + return skb; +} + +static void wl1271_skb_queue_head(struct wl1271 *wl, struct sk_buff *skb) +{ + unsigned long flags; + int q = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + + if (wl12xx_is_dummy_packet(wl, skb)) { + set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags); + } else if (wl->bss_type == BSS_TYPE_AP_BSS) { + u8 hlid = wl1271_tx_get_hlid(skb); + skb_queue_head(&wl->links[hlid].tx_queue[q], skb); + + /* make sure we dequeue the same packet next time */ + wl->last_tx_hlid = (hlid + AP_MAX_LINKS - 1) % AP_MAX_LINKS; + } else { + skb_queue_head(&wl->tx_queue[q], skb); + } + + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count++; + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +void wl1271_tx_work_locked(struct wl1271 *wl) +{ + struct sk_buff *skb; + u32 buf_offset = 0; + bool sent_packets = false; + int ret; + + if (unlikely(wl->state == WL1271_STATE_OFF)) + return; + + while ((skb = wl1271_skb_dequeue(wl))) { + ret = wl1271_prepare_tx_frame(wl, skb, buf_offset); + if (ret == -EAGAIN) { + /* + * Aggregation buffer is full. + * Flush buffer and try again. + */ + wl1271_skb_queue_head(wl, skb); + wl1271_write(wl, WL1271_SLV_MEM_DATA, wl->aggr_buf, + buf_offset, true); + sent_packets = true; + buf_offset = 0; + continue; + } else if (ret == -EBUSY) { + /* + * Firmware buffer is full. + * Queue back last skb, and stop aggregating. + */ + wl1271_skb_queue_head(wl, skb); + /* No work left, avoid scheduling redundant tx work */ + set_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); + goto out_ack; + } else if (ret < 0) { + dev_kfree_skb(skb); + goto out_ack; + } + buf_offset += ret; + wl->tx_packets_count++; + } + +out_ack: + if (buf_offset) { + wl1271_write(wl, WL1271_SLV_MEM_DATA, wl->aggr_buf, + buf_offset, true); + sent_packets = true; + } + if (sent_packets) { + /* + * Interrupt the firmware with the new packets. This is only + * required for older hardware revisions + */ + if (wl->quirks & WL12XX_QUIRK_END_OF_TRANSACTION) + wl1271_write32(wl, WL1271_HOST_WR_ACCESS, + wl->tx_packets_count); + + wl1271_handle_tx_low_watermark(wl); + } +} + +void wl1271_tx_work(struct work_struct *work) +{ + struct wl1271 *wl = container_of(work, struct wl1271, tx_work); + int ret; + + mutex_lock(&wl->mutex); + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_tx_work_locked(wl); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static void wl1271_tx_complete_packet(struct wl1271 *wl, + struct wl1271_tx_hw_res_descr *result) +{ + struct ieee80211_tx_info *info; + struct sk_buff *skb; + int id = result->id; + int rate = -1; + u8 retries = 0; + + /* check for id legality */ + if (unlikely(id >= ACX_TX_DESCRIPTORS || wl->tx_frames[id] == NULL)) { + wl1271_warning("TX result illegal id: %d", id); + return; + } + + skb = wl->tx_frames[id]; + info = IEEE80211_SKB_CB(skb); + + if (wl12xx_is_dummy_packet(wl, skb)) { + wl1271_free_tx_id(wl, id); + return; + } + + /* update the TX status info */ + if (result->status == TX_SUCCESS) { + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + info->flags |= IEEE80211_TX_STAT_ACK; + rate = wl1271_rate_to_idx(result->rate_class_index, wl->band); + retries = result->ack_failures; + } else if (result->status == TX_RETRY_EXCEEDED) { + wl->stats.excessive_retries++; + retries = result->ack_failures; + } + + info->status.rates[0].idx = rate; + info->status.rates[0].count = retries; + info->status.rates[0].flags = 0; + info->status.ack_signal = -1; + + wl->stats.retry_count += result->ack_failures; + + /* update security sequence number */ + wl->tx_security_seq += (result->lsb_security_sequence_number - + wl->tx_security_last_seq); + wl->tx_security_last_seq = result->lsb_security_sequence_number; + + /* remove private header from packet */ + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + + /* remove TKIP header space if present */ + if (info->control.hw_key && + info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + int hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + WL1271_TKIP_IV_SPACE, skb->data, hdrlen); + skb_pull(skb, WL1271_TKIP_IV_SPACE); + } + + wl1271_debug(DEBUG_TX, "tx status id %u skb 0x%p failures %u rate 0x%x" + " status 0x%x", + result->id, skb, result->ack_failures, + result->rate_class_index, result->status); + + /* return the packet to the stack */ + skb_queue_tail(&wl->deferred_tx_queue, skb); + ieee80211_queue_work(wl->hw, &wl->netstack_work); + wl1271_free_tx_id(wl, result->id); +} + +/* Called upon reception of a TX complete interrupt */ +void wl1271_tx_complete(struct wl1271 *wl) +{ + struct wl1271_acx_mem_map *memmap = + (struct wl1271_acx_mem_map *)wl->target_mem_map; + u32 count, fw_counter; + u32 i; + + /* read the tx results from the chipset */ + wl1271_read(wl, le32_to_cpu(memmap->tx_result), + wl->tx_res_if, sizeof(*wl->tx_res_if), false); + fw_counter = le32_to_cpu(wl->tx_res_if->tx_result_fw_counter); + + /* write host counter to chipset (to ack) */ + wl1271_write32(wl, le32_to_cpu(memmap->tx_result) + + offsetof(struct wl1271_tx_hw_res_if, + tx_result_host_counter), fw_counter); + + count = fw_counter - wl->tx_results_count; + wl1271_debug(DEBUG_TX, "tx_complete received, packets: %d", count); + + /* verify that the result buffer is not getting overrun */ + if (unlikely(count > TX_HW_RESULT_QUEUE_LEN)) + wl1271_warning("TX result overflow from chipset: %d", count); + + /* process the results */ + for (i = 0; i < count; i++) { + struct wl1271_tx_hw_res_descr *result; + u8 offset = wl->tx_results_count & TX_HW_RESULT_QUEUE_LEN_MASK; + + /* process the packet */ + result = &(wl->tx_res_if->tx_results_queue[offset]); + wl1271_tx_complete_packet(wl, result); + + wl->tx_results_count++; + } +} + +void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid) +{ + struct sk_buff *skb; + int i, total = 0; + unsigned long flags; + struct ieee80211_tx_info *info; + + for (i = 0; i < NUM_TX_QUEUES; i++) { + while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) { + wl1271_debug(DEBUG_TX, "link freeing skb 0x%p", skb); + info = IEEE80211_SKB_CB(skb); + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + ieee80211_tx_status(wl->hw, skb); + total++; + } + } + + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count -= total; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + wl1271_handle_tx_low_watermark(wl); +} + +/* caller must hold wl->mutex and TX must be stopped */ +void wl1271_tx_reset(struct wl1271 *wl, bool reset_tx_queues) +{ + int i; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + + /* TX failure */ + if (wl->bss_type == BSS_TYPE_AP_BSS) { + for (i = 0; i < AP_MAX_LINKS; i++) { + wl1271_tx_reset_link_queues(wl, i); + wl->links[i].allocated_blks = 0; + wl->links[i].prev_freed_blks = 0; + } + + wl->last_tx_hlid = 0; + } else { + for (i = 0; i < NUM_TX_QUEUES; i++) { + while ((skb = skb_dequeue(&wl->tx_queue[i]))) { + wl1271_debug(DEBUG_TX, "freeing skb 0x%p", + skb); + + if (!wl12xx_is_dummy_packet(wl, skb)) { + info = IEEE80211_SKB_CB(skb); + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + ieee80211_tx_status(wl->hw, skb); + } + } + } + } + + wl->tx_queue_count = 0; + + /* + * Make sure the driver is at a consistent state, in case this + * function is called from a context other than interface removal. + * This call will always wake the TX queues. + */ + if (reset_tx_queues) + wl1271_handle_tx_low_watermark(wl); + + for (i = 0; i < ACX_TX_DESCRIPTORS; i++) { + if (wl->tx_frames[i] == NULL) + continue; + + skb = wl->tx_frames[i]; + wl1271_free_tx_id(wl, i); + wl1271_debug(DEBUG_TX, "freeing skb 0x%p", skb); + + if (!wl12xx_is_dummy_packet(wl, skb)) { + /* + * Remove private headers before passing the skb to + * mac80211 + */ + info = IEEE80211_SKB_CB(skb); + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + if (info->control.hw_key && + info->control.hw_key->cipher == + WLAN_CIPHER_SUITE_TKIP) { + int hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + WL1271_TKIP_IV_SPACE, + skb->data, hdrlen); + skb_pull(skb, WL1271_TKIP_IV_SPACE); + } + + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + + ieee80211_tx_status(wl->hw, skb); + } + } +} + +#define WL1271_TX_FLUSH_TIMEOUT 500000 + +/* caller must *NOT* hold wl->mutex */ +void wl1271_tx_flush(struct wl1271 *wl) +{ + unsigned long timeout; + timeout = jiffies + usecs_to_jiffies(WL1271_TX_FLUSH_TIMEOUT); + + while (!time_after(jiffies, timeout)) { + mutex_lock(&wl->mutex); + wl1271_debug(DEBUG_TX, "flushing tx buffer: %d %d", + wl->tx_frames_cnt, wl->tx_queue_count); + if ((wl->tx_frames_cnt == 0) && (wl->tx_queue_count == 0)) { + mutex_unlock(&wl->mutex); + return; + } + mutex_unlock(&wl->mutex); + msleep(1); + } + + wl1271_warning("Unable to flush all TX buffers, timed out."); +} + +u32 wl1271_tx_min_rate_get(struct wl1271 *wl) +{ + int i; + u32 rate = 0; + + if (!wl->basic_rate_set) { + WARN_ON(1); + wl->basic_rate_set = wl->conf.tx.basic_rate; + } + + for (i = 0; !rate; i++) { + if ((wl->basic_rate_set >> i) & 0x1) + rate = 1 << i; + } + + return rate; +} diff --git a/drivers/net/wireless/wl12xx/tx.h b/drivers/net/wireless/wl12xx/tx.h new file mode 100644 index 00000000..832f9258 --- /dev/null +++ b/drivers/net/wireless/wl12xx/tx.h @@ -0,0 +1,197 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TX_H__ +#define __TX_H__ + +#define TX_HW_BLOCK_SIZE 252 + +#define TX_HW_MGMT_PKT_LIFETIME_TU 2000 +#define TX_HW_AP_MODE_PKT_LIFETIME_TU 8000 +/* The chipset reference driver states, that the "aid" value 1 + * is for infra-BSS, but is still always used */ +#define TX_HW_DEFAULT_AID 1 + +#define TX_HW_ATTR_SAVE_RETRIES BIT(0) +#define TX_HW_ATTR_HEADER_PAD BIT(1) +#define TX_HW_ATTR_SESSION_COUNTER (BIT(2) | BIT(3) | BIT(4)) +#define TX_HW_ATTR_RATE_POLICY (BIT(5) | BIT(6) | BIT(7) | \ + BIT(8) | BIT(9)) +#define TX_HW_ATTR_LAST_WORD_PAD (BIT(10) | BIT(11)) +#define TX_HW_ATTR_TX_CMPLT_REQ BIT(12) +#define TX_HW_ATTR_TX_DUMMY_REQ BIT(13) + +#define TX_HW_ATTR_OFST_SAVE_RETRIES 0 +#define TX_HW_ATTR_OFST_HEADER_PAD 1 +#define TX_HW_ATTR_OFST_SESSION_COUNTER 2 +#define TX_HW_ATTR_OFST_RATE_POLICY 5 +#define TX_HW_ATTR_OFST_LAST_WORD_PAD 10 +#define TX_HW_ATTR_OFST_TX_CMPLT_REQ 12 + +#define TX_HW_RESULT_QUEUE_LEN 16 +#define TX_HW_RESULT_QUEUE_LEN_MASK 0xf + +#define WL1271_TX_ALIGN_TO 4 +#define WL1271_TKIP_IV_SPACE 4 + +/* Used for management frames and dummy packets */ +#define WL1271_TID_MGMT 7 + +struct wl127x_tx_mem { + /* + * Number of extra memory blocks to allocate for this packet + * in addition to the number of blocks derived from the packet + * length. + */ + u8 extra_blocks; + /* + * Total number of memory blocks allocated by the host for + * this packet. Must be equal or greater than the actual + * blocks number allocated by HW. + */ + u8 total_mem_blocks; +} __packed; + +struct wl128x_tx_mem { + /* + * Total number of memory blocks allocated by the host for + * this packet. + */ + u8 total_mem_blocks; + /* + * Number of extra bytes, at the end of the frame. the host + * uses this padding to complete each frame to integer number + * of SDIO blocks. + */ + u8 extra_bytes; +} __packed; + +/* + * On wl128x based devices, when TX packets are aggregated, each packet + * size must be aligned to the SDIO block size. The maximum block size + * is bounded by the type of the padded bytes field that is sent to the + * FW. Currently the type is u8, so the maximum block size is 256 bytes. + */ +#define WL12XX_BUS_BLOCK_SIZE min(512u, \ + (1u << (8 * sizeof(((struct wl128x_tx_mem *) 0)->extra_bytes)))) + +struct wl1271_tx_hw_descr { + /* Length of packet in words, including descriptor+header+data */ + __le16 length; + union { + struct wl127x_tx_mem wl127x_mem; + struct wl128x_tx_mem wl128x_mem; + } __packed; + /* Device time (in us) when the packet arrived to the driver */ + __le32 start_time; + /* + * Max delay in TUs until transmission. The last device time the + * packet can be transmitted is: start_time + (1024 * life_time) + */ + __le16 life_time; + /* Bitwise fields - see TX_ATTR... definitions above. */ + __le16 tx_attr; + /* Packet identifier used also in the Tx-Result. */ + u8 id; + /* The packet TID value (as User-Priority) */ + u8 tid; + union { + /* STA - Identifier of the remote STA in IBSS, 1 in infra-BSS */ + u8 aid; + /* AP - host link ID (HLID) */ + u8 hlid; + } __packed; + u8 reserved; +} __packed; + +enum wl1271_tx_hw_res_status { + TX_SUCCESS = 0, + TX_HW_ERROR = 1, + TX_DISABLED = 2, + TX_RETRY_EXCEEDED = 3, + TX_TIMEOUT = 4, + TX_KEY_NOT_FOUND = 5, + TX_PEER_NOT_FOUND = 6, + TX_SESSION_MISMATCH = 7 +}; + +struct wl1271_tx_hw_res_descr { + /* Packet Identifier - same value used in the Tx descriptor.*/ + u8 id; + /* The status of the transmission, indicating success or one of + several possible reasons for failure. */ + u8 status; + /* Total air access duration including all retrys and overheads.*/ + __le16 medium_usage; + /* The time passed from host xfer to Tx-complete.*/ + __le32 fw_handling_time; + /* Total media delay + (from 1st EDCA AIFS counter until TX Complete). */ + __le32 medium_delay; + /* LS-byte of last TKIP seq-num (saved per AC for recovery). */ + u8 lsb_security_sequence_number; + /* Retry count - number of transmissions without successful ACK.*/ + u8 ack_failures; + /* The rate that succeeded getting ACK + (Valid only if status=SUCCESS). */ + u8 rate_class_index; + /* for 4-byte alignment. */ + u8 spare; +} __packed; + +struct wl1271_tx_hw_res_if { + __le32 tx_result_fw_counter; + __le32 tx_result_host_counter; + struct wl1271_tx_hw_res_descr tx_results_queue[TX_HW_RESULT_QUEUE_LEN]; +} __packed; + +static inline int wl1271_tx_get_queue(int queue) +{ + switch (queue) { + case 0: + return CONF_TX_AC_VO; + case 1: + return CONF_TX_AC_VI; + case 2: + return CONF_TX_AC_BE; + case 3: + return CONF_TX_AC_BK; + default: + return CONF_TX_AC_BE; + } +} + +void wl1271_tx_work(struct work_struct *work); +void wl1271_tx_work_locked(struct wl1271 *wl); +void wl1271_tx_complete(struct wl1271 *wl); +void wl1271_tx_reset(struct wl1271 *wl, bool reset_tx_queues); +void wl1271_tx_flush(struct wl1271 *wl); +u8 wl1271_rate_to_idx(int rate, enum ieee80211_band band); +u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set); +u32 wl1271_tx_min_rate_get(struct wl1271 *wl); +u8 wl1271_tx_get_hlid(struct sk_buff *skb); +void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid); +void wl1271_handle_tx_low_watermark(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h new file mode 100644 index 00000000..fbe8f46d --- /dev/null +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -0,0 +1,639 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho <luciano.coelho@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_H__ +#define __WL12XX_H__ + +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/bitops.h> +#include <net/mac80211.h> + +#include "conf.h" +#include "ini.h" + +#define DRIVER_NAME "wl1271" +#define DRIVER_PREFIX DRIVER_NAME ": " + +/* + * FW versions support BA 11n + * versions marks x.x.x.50-60.x + */ +#define WL12XX_BA_SUPPORT_FW_COST_VER2_START 50 +#define WL12XX_BA_SUPPORT_FW_COST_VER2_END 60 + +enum { + DEBUG_NONE = 0, + DEBUG_IRQ = BIT(0), + DEBUG_SPI = BIT(1), + DEBUG_BOOT = BIT(2), + DEBUG_MAILBOX = BIT(3), + DEBUG_TESTMODE = BIT(4), + DEBUG_EVENT = BIT(5), + DEBUG_TX = BIT(6), + DEBUG_RX = BIT(7), + DEBUG_SCAN = BIT(8), + DEBUG_CRYPT = BIT(9), + DEBUG_PSM = BIT(10), + DEBUG_MAC80211 = BIT(11), + DEBUG_CMD = BIT(12), + DEBUG_ACX = BIT(13), + DEBUG_SDIO = BIT(14), + DEBUG_FILTERS = BIT(15), + DEBUG_ADHOC = BIT(16), + DEBUG_AP = BIT(17), + DEBUG_MASTER = (DEBUG_ADHOC | DEBUG_AP), + DEBUG_ALL = ~0, +}; + +extern u32 wl12xx_debug_level; + +#define DEBUG_DUMP_LIMIT 1024 + +#define wl1271_error(fmt, arg...) \ + pr_err(DRIVER_PREFIX "ERROR " fmt "\n", ##arg) + +#define wl1271_warning(fmt, arg...) \ + pr_warning(DRIVER_PREFIX "WARNING " fmt "\n", ##arg) + +#define wl1271_notice(fmt, arg...) \ + pr_info(DRIVER_PREFIX fmt "\n", ##arg) + +#define wl1271_info(fmt, arg...) \ + pr_info(DRIVER_PREFIX fmt "\n", ##arg) + +#define wl1271_debug(level, fmt, arg...) \ + do { \ + if (level & wl12xx_debug_level) \ + pr_debug(DRIVER_PREFIX fmt "\n", ##arg); \ + } while (0) + +/* TODO: use pr_debug_hex_dump when it will be available */ +#define wl1271_dump(level, prefix, buf, len) \ + do { \ + if (level & wl12xx_debug_level) \ + print_hex_dump(KERN_DEBUG, DRIVER_PREFIX prefix, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, \ + min_t(size_t, len, DEBUG_DUMP_LIMIT), \ + 0); \ + } while (0) + +#define wl1271_dump_ascii(level, prefix, buf, len) \ + do { \ + if (level & wl12xx_debug_level) \ + print_hex_dump(KERN_DEBUG, DRIVER_PREFIX prefix, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, \ + min_t(size_t, len, DEBUG_DUMP_LIMIT), \ + true); \ + } while (0) + +#define WL1271_DEFAULT_STA_RX_CONFIG (CFG_UNI_FILTER_EN | \ + CFG_BSSID_FILTER_EN | \ + CFG_MC_FILTER_EN) + +#define WL1271_DEFAULT_STA_RX_FILTER (CFG_RX_RCTS_ACK | CFG_RX_PRSP_EN | \ + CFG_RX_MGMT_EN | CFG_RX_DATA_EN | \ + CFG_RX_CTL_EN | CFG_RX_BCN_EN | \ + CFG_RX_AUTH_EN | CFG_RX_ASSOC_EN) + +#define WL1271_DEFAULT_AP_RX_CONFIG 0 + +#define WL1271_DEFAULT_AP_RX_FILTER (CFG_RX_RCTS_ACK | CFG_RX_PREQ_EN | \ + CFG_RX_MGMT_EN | CFG_RX_DATA_EN | \ + CFG_RX_CTL_EN | CFG_RX_AUTH_EN | \ + CFG_RX_ASSOC_EN) + + + +#define WL1271_FW_NAME "ti-connectivity/wl1271-fw-2.bin" +#define WL128X_FW_NAME "ti-connectivity/wl128x-fw.bin" +#define WL127X_AP_FW_NAME "ti-connectivity/wl1271-fw-ap.bin" +#define WL128X_AP_FW_NAME "ti-connectivity/wl128x-fw-ap.bin" + +/* + * wl127x and wl128x are using the same NVS file name. However, the + * ini parameters between them are different. The driver validates + * the correct NVS size in wl1271_boot_upload_nvs(). + */ +#define WL12XX_NVS_NAME "ti-connectivity/wl1271-nvs.bin" + +#define WL1271_TX_SECURITY_LO16(s) ((u16)((s) & 0xffff)) +#define WL1271_TX_SECURITY_HI32(s) ((u32)(((s) >> 16) & 0xffffffff)) + +#define WL1271_CIPHER_SUITE_GEM 0x00147201 + +#define WL1271_BUSY_WORD_CNT 1 +#define WL1271_BUSY_WORD_LEN (WL1271_BUSY_WORD_CNT * sizeof(u32)) + +#define WL1271_ELP_HW_STATE_ASLEEP 0 +#define WL1271_ELP_HW_STATE_IRQ 1 + +#define WL1271_DEFAULT_BEACON_INT 100 +#define WL1271_DEFAULT_DTIM_PERIOD 1 + +#define WL1271_AP_GLOBAL_HLID 0 +#define WL1271_AP_BROADCAST_HLID 1 +#define WL1271_AP_STA_HLID_START 2 + +/* + * When in AP-mode, we allow (at least) this number of mem-blocks + * to be transmitted to FW for a STA in PS-mode. Only when packets are + * present in the FW buffers it will wake the sleeping STA. We want to put + * enough packets for the driver to transmit all of its buffered data before + * the STA goes to sleep again. But we don't want to take too much mem-blocks + * as it might hurt the throughput of active STAs. + * The number of blocks (18) is enough for 2 large packets. + */ +#define WL1271_PS_STA_MAX_BLOCKS (2 * 9) + +#define WL1271_AP_BSS_INDEX 0 +#define WL1271_AP_DEF_INACTIV_SEC 300 +#define WL1271_AP_DEF_BEACON_EXP 20 + +#define ACX_TX_DESCRIPTORS 32 + +#define WL1271_AGGR_BUFFER_SIZE (4 * PAGE_SIZE) + +enum wl1271_state { + WL1271_STATE_OFF, + WL1271_STATE_ON, + WL1271_STATE_PLT, +}; + +enum wl1271_partition_type { + PART_DOWN, + PART_WORK, + PART_DRPW, + + PART_TABLE_LEN +}; + +struct wl1271_partition { + u32 size; + u32 start; +}; + +struct wl1271_partition_set { + struct wl1271_partition mem; + struct wl1271_partition reg; + struct wl1271_partition mem2; + struct wl1271_partition mem3; +}; + +struct wl1271; + +enum { + FW_VER_CHIP, + FW_VER_IF_TYPE, + FW_VER_MAJOR, + FW_VER_SUBTYPE, + FW_VER_MINOR, + + NUM_FW_VER +}; + +#define FW_VER_CHIP_WL127X 6 +#define FW_VER_CHIP_WL128X 7 + +#define FW_VER_IF_TYPE_STA 1 +#define FW_VER_IF_TYPE_AP 2 + +#define FW_VER_MINOR_1_SPARE_STA_MIN 58 +#define FW_VER_MINOR_1_SPARE_AP_MIN 47 + +struct wl1271_chip { + u32 id; + char fw_ver_str[ETHTOOL_BUSINFO_LEN]; + unsigned int fw_ver[NUM_FW_VER]; +}; + +struct wl1271_stats { + struct acx_statistics *fw_stats; + unsigned long fw_stats_update; + + unsigned int retry_count; + unsigned int excessive_retries; +}; + +#define NUM_TX_QUEUES 4 +#define NUM_RX_PKT_DESC 8 + +#define AP_MAX_STATIONS 5 + +/* Broadcast and Global links + links to stations */ +#define AP_MAX_LINKS (AP_MAX_STATIONS + 2) + +/* FW status registers common for AP/STA */ +struct wl1271_fw_common_status { + __le32 intr; + u8 fw_rx_counter; + u8 drv_rx_counter; + u8 reserved; + u8 tx_results_counter; + __le32 rx_pkt_descs[NUM_RX_PKT_DESC]; + __le32 tx_released_blks[NUM_TX_QUEUES]; + __le32 fw_localtime; +} __packed; + +/* FW status registers for AP */ +struct wl1271_fw_ap_status { + struct wl1271_fw_common_status common; + + /* Next fields valid only in AP FW */ + + /* + * A bitmap (where each bit represents a single HLID) + * to indicate if the station is in PS mode. + */ + __le32 link_ps_bitmap; + + /* Number of freed MBs per HLID */ + u8 tx_lnk_free_blks[AP_MAX_LINKS]; + u8 padding_1[1]; +} __packed; + +/* FW status registers for STA */ +struct wl1271_fw_sta_status { + struct wl1271_fw_common_status common; + + u8 tx_total; + u8 reserved1; + __le16 reserved2; + /* Total structure size is 68 bytes */ + u32 padding; +} __packed; + +struct wl1271_fw_full_status { + union { + struct wl1271_fw_common_status common; + struct wl1271_fw_sta_status sta; + struct wl1271_fw_ap_status ap; + }; +} __packed; + + +struct wl1271_rx_mem_pool_addr { + u32 addr; + u32 addr_extra; +}; + +#define WL1271_MAX_CHANNELS 64 +struct wl1271_scan { + struct cfg80211_scan_request *req; + unsigned long scanned_ch[BITS_TO_LONGS(WL1271_MAX_CHANNELS)]; + bool failed; + u8 state; + u8 ssid[IW_ESSID_MAX_SIZE+1]; + size_t ssid_len; +}; + +struct wl1271_if_operations { + void (*read)(struct wl1271 *wl, int addr, void *buf, size_t len, + bool fixed); + void (*write)(struct wl1271 *wl, int addr, void *buf, size_t len, + bool fixed); + void (*reset)(struct wl1271 *wl); + void (*init)(struct wl1271 *wl); + int (*power)(struct wl1271 *wl, bool enable); + struct device* (*dev)(struct wl1271 *wl); + void (*enable_irq)(struct wl1271 *wl); + void (*disable_irq)(struct wl1271 *wl); + void (*set_block_size) (struct wl1271 *wl, unsigned int blksz); +}; + +#define MAX_NUM_KEYS 14 +#define MAX_KEY_SIZE 32 + +struct wl1271_ap_key { + u8 id; + u8 key_type; + u8 key_size; + u8 key[MAX_KEY_SIZE]; + u8 hlid; + u32 tx_seq_32; + u16 tx_seq_16; +}; + +enum wl12xx_flags { + WL1271_FLAG_STA_ASSOCIATED, + WL1271_FLAG_JOINED, + WL1271_FLAG_GPIO_POWER, + WL1271_FLAG_TX_QUEUE_STOPPED, + WL1271_FLAG_TX_PENDING, + WL1271_FLAG_IN_ELP, + WL1271_FLAG_ELP_REQUESTED, + WL1271_FLAG_PSM, + WL1271_FLAG_PSM_REQUESTED, + WL1271_FLAG_IRQ_RUNNING, + WL1271_FLAG_IDLE, + WL1271_FLAG_PSPOLL_FAILURE, + WL1271_FLAG_STA_STATE_SENT, + WL1271_FLAG_FW_TX_BUSY, + WL1271_FLAG_AP_STARTED, + WL1271_FLAG_IF_INITIALIZED, + WL1271_FLAG_DUMMY_PACKET_PENDING, + WL1271_FLAG_SUSPENDED, + WL1271_FLAG_PENDING_WORK, +}; + +struct wl1271_link { + /* AP-mode - TX queue per AC in link */ + struct sk_buff_head tx_queue[NUM_TX_QUEUES]; + + /* accounting for allocated / available TX blocks in FW */ + u8 allocated_blks; + u8 prev_freed_blks; + + u8 addr[ETH_ALEN]; +}; + +struct wl1271 { + struct platform_device *plat_dev; + struct ieee80211_hw *hw; + bool mac80211_registered; + + void *if_priv; + + struct wl1271_if_operations *if_ops; + + void (*set_power)(bool enable); + int irq; + int ref_clock; + + spinlock_t wl_lock; + + enum wl1271_state state; + struct mutex mutex; + + unsigned long flags; + + struct wl1271_partition_set part; + + struct wl1271_chip chip; + + int cmd_box_addr; + int event_box_addr; + + u8 *fw; + size_t fw_len; + u8 fw_bss_type; + void *nvs; + size_t nvs_len; + + s8 hw_pg_ver; + + u8 bssid[ETH_ALEN]; + u8 mac_addr[ETH_ALEN]; + u8 bss_type; + u8 set_bss_type; + u8 ssid[IW_ESSID_MAX_SIZE + 1]; + u8 ssid_len; + int channel; + + struct wl1271_acx_mem_map *target_mem_map; + + /* Accounting for allocated / available TX blocks on HW */ + u32 tx_blocks_freed[NUM_TX_QUEUES]; + u32 tx_blocks_available; + u32 tx_allocated_blocks; + u32 tx_results_count; + + /* Transmitted TX packets counter for chipset interface */ + u32 tx_packets_count; + + /* Time-offset between host and chipset clocks */ + s64 time_offset; + + /* Session counter for the chipset */ + int session_counter; + + /* Frames scheduled for transmission, not handled yet */ + struct sk_buff_head tx_queue[NUM_TX_QUEUES]; + int tx_queue_count; + + /* Frames received, not handled yet by mac80211 */ + struct sk_buff_head deferred_rx_queue; + + /* Frames sent, not returned yet to mac80211 */ + struct sk_buff_head deferred_tx_queue; + + struct work_struct tx_work; + + /* Pending TX frames */ + unsigned long tx_frames_map[BITS_TO_LONGS(ACX_TX_DESCRIPTORS)]; + struct sk_buff *tx_frames[ACX_TX_DESCRIPTORS]; + int tx_frames_cnt; + + /* Security sequence number counters */ + u8 tx_security_last_seq; + s64 tx_security_seq; + + /* FW Rx counter */ + u32 rx_counter; + + /* Rx memory pool address */ + struct wl1271_rx_mem_pool_addr rx_mem_pool_addr; + + /* Intermediate buffer, used for packet aggregation */ + u8 *aggr_buf; + + /* Reusable dummy packet template */ + struct sk_buff *dummy_packet; + + /* Network stack work */ + struct work_struct netstack_work; + + /* Hardware recovery work */ + struct work_struct recovery_work; + + /* The mbox event mask */ + u32 event_mask; + + /* Mailbox pointers */ + u32 mbox_ptr[2]; + + /* Are we currently scanning */ + struct wl1271_scan scan; + struct delayed_work scan_complete_work; + + bool sched_scanning; + + /* probe-req template for the current AP */ + struct sk_buff *probereq; + + /* Our association ID */ + u16 aid; + + /* + * currently configured rate set: + * bits 0-15 - 802.11abg rates + * bits 16-23 - 802.11n MCS index mask + * support only 1 stream, thus only 8 bits for the MCS rates (0-7). + */ + u32 basic_rate_set; + u32 basic_rate; + u32 rate_set; + + /* The current band */ + enum ieee80211_band band; + + /* Beaconing interval (needed for ad-hoc) */ + u32 beacon_int; + + /* Default key (for WEP) */ + u32 default_key; + + unsigned int filters; + unsigned int rx_config; + unsigned int rx_filter; + + struct completion *elp_compl; + struct completion *ps_compl; + struct delayed_work elp_work; + struct delayed_work pspoll_work; + + /* counter for ps-poll delivery failures */ + int ps_poll_failures; + + /* retry counter for PSM entries */ + u8 psm_entry_retry; + + /* in dBm */ + int power_level; + + int rssi_thold; + int last_rssi_event; + + struct wl1271_stats stats; + + __le32 buffer_32; + u32 buffer_cmd; + u32 buffer_busyword[WL1271_BUSY_WORD_CNT]; + + struct wl1271_fw_full_status *fw_status; + struct wl1271_tx_hw_res_if *tx_res_if; + + struct ieee80211_vif *vif; + + /* Current chipset configuration */ + struct conf_drv_settings conf; + + bool sg_enabled; + + bool enable_11a; + + struct list_head list; + + /* Most recently reported noise in dBm */ + s8 noise; + + /* map for HLIDs of associated stations - when operating in AP mode */ + unsigned long ap_hlid_map[BITS_TO_LONGS(AP_MAX_STATIONS)]; + + /* recoreded keys for AP-mode - set here before AP startup */ + struct wl1271_ap_key *recorded_ap_keys[MAX_NUM_KEYS]; + + /* bands supported by this instance of wl12xx */ + struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS]; + + /* RX BA constraint value */ + bool ba_support; + u8 ba_rx_bitmap; + + int tcxo_clock; + + /* + * wowlan trigger was configured during suspend. + * (currently, only "ANY" trigger is supported) + */ + bool wow_enabled; + + /* + * AP-mode - links indexed by HLID. The global and broadcast links + * are always active. + */ + struct wl1271_link links[AP_MAX_LINKS]; + + /* the hlid of the link where the last transmitted skb came from */ + int last_tx_hlid; + + /* AP-mode - a bitmap of links currently in PS mode according to FW */ + u32 ap_fw_ps_map; + + /* AP-mode - a bitmap of links currently in PS mode in mac80211 */ + unsigned long ap_ps_map; + + /* Quirks of specific hardware revisions */ + unsigned int quirks; + + /* Platform limitations */ + unsigned int platform_quirks; +}; + +struct wl1271_station { + u8 hlid; +}; + +int wl1271_plt_start(struct wl1271 *wl); +int wl1271_plt_stop(struct wl1271 *wl); + +#define JOIN_TIMEOUT 5000 /* 5000 milliseconds to join */ + +#define SESSION_COUNTER_MAX 7 /* maximum value for the session counter */ + +#define WL1271_DEFAULT_POWER_LEVEL 0 + +#define WL1271_TX_QUEUE_LOW_WATERMARK 10 +#define WL1271_TX_QUEUE_HIGH_WATERMARK 25 + +#define WL1271_DEFERRED_QUEUE_LIMIT 64 + +/* WL1271 needs a 200ms sleep after power on, and a 20ms sleep before power + on in case is has been shut down shortly before */ +#define WL1271_PRE_POWER_ON_SLEEP 20 /* in milliseconds */ +#define WL1271_POWER_ON_SLEEP 200 /* in milliseconds */ + +/* Macros to handle wl1271.sta_rate_set */ +#define HW_BG_RATES_MASK 0xffff +#define HW_HT_RATES_OFFSET 16 + +/* Quirks */ + +/* Each RX/TX transaction requires an end-of-transaction transfer */ +#define WL12XX_QUIRK_END_OF_TRANSACTION BIT(0) + +/* + * Older firmwares use 2 spare TX blocks + * (for STA < 6.1.3.50.58 or for AP < 6.2.0.0.47) + */ +#define WL12XX_QUIRK_USE_2_SPARE_BLOCKS BIT(1) + +/* WL128X requires aggregated packets to be aligned to the SDIO block size */ +#define WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT BIT(2) + +#endif diff --git a/drivers/net/wireless/wl12xx/wl12xx_80211.h b/drivers/net/wireless/wl12xx/wl12xx_80211.h new file mode 100644 index 00000000..18fe5423 --- /dev/null +++ b/drivers/net/wireless/wl12xx/wl12xx_80211.h @@ -0,0 +1,167 @@ +#ifndef __WL12XX_80211_H__ +#define __WL12XX_80211_H__ + +#include <linux/if_ether.h> /* ETH_ALEN */ +#include <linux/if_arp.h> + +/* RATES */ +#define IEEE80211_CCK_RATE_1MB 0x02 +#define IEEE80211_CCK_RATE_2MB 0x04 +#define IEEE80211_CCK_RATE_5MB 0x0B +#define IEEE80211_CCK_RATE_11MB 0x16 +#define IEEE80211_OFDM_RATE_6MB 0x0C +#define IEEE80211_OFDM_RATE_9MB 0x12 +#define IEEE80211_OFDM_RATE_12MB 0x18 +#define IEEE80211_OFDM_RATE_18MB 0x24 +#define IEEE80211_OFDM_RATE_24MB 0x30 +#define IEEE80211_OFDM_RATE_36MB 0x48 +#define IEEE80211_OFDM_RATE_48MB 0x60 +#define IEEE80211_OFDM_RATE_54MB 0x6C +#define IEEE80211_BASIC_RATE_MASK 0x80 + +#define IEEE80211_CCK_RATE_1MB_MASK (1<<0) +#define IEEE80211_CCK_RATE_2MB_MASK (1<<1) +#define IEEE80211_CCK_RATE_5MB_MASK (1<<2) +#define IEEE80211_CCK_RATE_11MB_MASK (1<<3) +#define IEEE80211_OFDM_RATE_6MB_MASK (1<<4) +#define IEEE80211_OFDM_RATE_9MB_MASK (1<<5) +#define IEEE80211_OFDM_RATE_12MB_MASK (1<<6) +#define IEEE80211_OFDM_RATE_18MB_MASK (1<<7) +#define IEEE80211_OFDM_RATE_24MB_MASK (1<<8) +#define IEEE80211_OFDM_RATE_36MB_MASK (1<<9) +#define IEEE80211_OFDM_RATE_48MB_MASK (1<<10) +#define IEEE80211_OFDM_RATE_54MB_MASK (1<<11) + +#define IEEE80211_CCK_RATES_MASK 0x0000000F +#define IEEE80211_CCK_BASIC_RATES_MASK (IEEE80211_CCK_RATE_1MB_MASK | \ + IEEE80211_CCK_RATE_2MB_MASK) +#define IEEE80211_CCK_DEFAULT_RATES_MASK (IEEE80211_CCK_BASIC_RATES_MASK | \ + IEEE80211_CCK_RATE_5MB_MASK | \ + IEEE80211_CCK_RATE_11MB_MASK) + +#define IEEE80211_OFDM_RATES_MASK 0x00000FF0 +#define IEEE80211_OFDM_BASIC_RATES_MASK (IEEE80211_OFDM_RATE_6MB_MASK | \ + IEEE80211_OFDM_RATE_12MB_MASK | \ + IEEE80211_OFDM_RATE_24MB_MASK) +#define IEEE80211_OFDM_DEFAULT_RATES_MASK (IEEE80211_OFDM_BASIC_RATES_MASK | \ + IEEE80211_OFDM_RATE_9MB_MASK | \ + IEEE80211_OFDM_RATE_18MB_MASK | \ + IEEE80211_OFDM_RATE_36MB_MASK | \ + IEEE80211_OFDM_RATE_48MB_MASK | \ + IEEE80211_OFDM_RATE_54MB_MASK) +#define IEEE80211_DEFAULT_RATES_MASK (IEEE80211_OFDM_DEFAULT_RATES_MASK | \ + IEEE80211_CCK_DEFAULT_RATES_MASK) + + +/* This really should be 8, but not for our firmware */ +#define MAX_SUPPORTED_RATES 32 +#define MAX_COUNTRY_TRIPLETS 32 + +/* Headers */ +struct ieee80211_header { + __le16 frame_ctl; + __le16 duration_id; + u8 da[ETH_ALEN]; + u8 sa[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 seq_ctl; + u8 payload[0]; +} __packed; + +struct wl12xx_ie_header { + u8 id; + u8 len; +} __packed; + +/* IEs */ + +struct wl12xx_ie_ssid { + struct wl12xx_ie_header header; + char ssid[IW_ESSID_MAX_SIZE]; +} __packed; + +struct wl12xx_ie_rates { + struct wl12xx_ie_header header; + u8 rates[MAX_SUPPORTED_RATES]; +} __packed; + +struct wl12xx_ie_ds_params { + struct wl12xx_ie_header header; + u8 channel; +} __packed; + +struct country_triplet { + u8 channel; + u8 num_channels; + u8 max_tx_power; +} __packed; + +struct wl12xx_ie_country { + struct wl12xx_ie_header header; + u8 country_string[IEEE80211_COUNTRY_STRING_LEN]; + struct country_triplet triplets[MAX_COUNTRY_TRIPLETS]; +} __packed; + + +/* Templates */ + +struct wl12xx_beacon_template { + struct ieee80211_header header; + __le32 time_stamp[2]; + __le16 beacon_interval; + __le16 capability; + struct wl12xx_ie_ssid ssid; + struct wl12xx_ie_rates rates; + struct wl12xx_ie_rates ext_rates; + struct wl12xx_ie_ds_params ds_params; + struct wl12xx_ie_country country; +} __packed; + +struct wl12xx_null_data_template { + struct ieee80211_header header; +} __packed; + +struct wl12xx_ps_poll_template { + __le16 fc; + __le16 aid; + u8 bssid[ETH_ALEN]; + u8 ta[ETH_ALEN]; +} __packed; + +struct wl12xx_qos_null_data_template { + struct ieee80211_header header; + __le16 qos_ctl; +} __packed; + +struct wl12xx_arp_rsp_template { + struct ieee80211_hdr_3addr hdr; + + u8 llc_hdr[sizeof(rfc1042_header)]; + __be16 llc_type; + + struct arphdr arp_hdr; + u8 sender_hw[ETH_ALEN]; + __be32 sender_ip; + u8 target_hw[ETH_ALEN]; + __be32 target_ip; +} __packed; + + +struct wl12xx_probe_resp_template { + struct ieee80211_header header; + __le32 time_stamp[2]; + __le16 beacon_interval; + __le16 capability; + struct wl12xx_ie_ssid ssid; + struct wl12xx_ie_rates rates; + struct wl12xx_ie_rates ext_rates; + struct wl12xx_ie_ds_params ds_params; + struct wl12xx_ie_country country; +} __packed; + +struct wl12xx_disconn_template { + struct ieee80211_header header; + __le16 disconn_reason; +} __packed; + +#endif diff --git a/drivers/net/wireless/wl12xx/wl12xx_platform_data.c b/drivers/net/wireless/wl12xx/wl12xx_platform_data.c new file mode 100644 index 00000000..973b1106 --- /dev/null +++ b/drivers/net/wireless/wl12xx/wl12xx_platform_data.c @@ -0,0 +1,28 @@ +#include <linux/module.h> +#include <linux/err.h> +#include <linux/wl12xx.h> + +static const struct wl12xx_platform_data *platform_data; + +int __init wl12xx_set_platform_data(const struct wl12xx_platform_data *data) +{ + if (platform_data) + return -EBUSY; + if (!data) + return -EINVAL; + + platform_data = kmemdup(data, sizeof(*data), GFP_KERNEL); + if (!platform_data) + return -ENOMEM; + + return 0; +} + +const struct wl12xx_platform_data *wl12xx_get_platform_data(void) +{ + if (!platform_data) + return ERR_PTR(-ENODEV); + + return platform_data; +} +EXPORT_SYMBOL(wl12xx_get_platform_data); |