wl12xx: add driver

wl12xx is a driver for TI wl1251 802.11 chipset designed for embedded
devices, supporting both SDIO and SPI busses. Currently the driver
supports only SPI. Adding support 1253 (the 5 GHz version) should be
relatively easy. More information here:

http://focus.ti.com/general/docs/wtbu/wtbuproductcontent.tsp?contentId=4711&navigationId=12494&templateId=6123

(Collapsed original sequence of pre-merge patches into single commit for
initial merge. -- JWL)

Signed-off-by: Kalle Valo <kalle.valo@nokia.com>
Signed-off-by: Bob Copeland <me@bobcopeland.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Kalle Valo 2009-04-29 23:33:31 +03:00 committed by John W. Linville
parent d53d9e67b5
commit 2f01a1f588
31 changed files with 9263 additions and 0 deletions

View file

@ -500,5 +500,6 @@ source "drivers/net/wireless/b43legacy/Kconfig"
source "drivers/net/wireless/zd1211rw/Kconfig"
source "drivers/net/wireless/rt2x00/Kconfig"
source "drivers/net/wireless/orinoco/Kconfig"
source "drivers/net/wireless/wl12xx/Kconfig"
endmenu

View file

@ -58,3 +58,5 @@ obj-$(CONFIG_P54_COMMON) += p54/
obj-$(CONFIG_ATH_COMMON) += ath/
obj-$(CONFIG_MAC80211_HWSIM) += mac80211_hwsim.o
obj-$(CONFIG_WL12XX) += wl12xx/

View file

@ -0,0 +1,11 @@
config WL12XX
tristate "TI wl1251/wl1271 support"
depends on MAC80211 && WLAN_80211 && SPI_MASTER && EXPERIMENTAL
select FW_LOADER
select CRC7
---help---
This module adds support for wireless adapters based on
TI wl1251/wl1271 chipsets.
If you choose to build a module, it'll be called wl12xx. Say N if
unsure.

View file

@ -0,0 +1,4 @@
wl12xx-objs = main.o spi.o event.o tx.o rx.o \
ps.o cmd.o acx.o boot.o init.o wl1251.o \
debugfs.o
obj-$(CONFIG_WL12XX) += wl12xx.o

View file

@ -0,0 +1,689 @@
#include "acx.h"
#include <linux/module.h>
#include <linux/crc7.h>
#include <linux/spi/spi.h>
#include "wl12xx.h"
#include "wl12xx_80211.h"
#include "reg.h"
#include "spi.h"
#include "ps.h"
int wl12xx_acx_frame_rates(struct wl12xx *wl, u8 ctrl_rate, u8 ctrl_mod,
u8 mgt_rate, u8 mgt_mod)
{
int ret;
struct acx_fw_gen_frame_rates rates;
wl12xx_debug(DEBUG_ACX, "acx frame rates");
rates.header.id = ACX_FW_GEN_FRAME_RATES;
rates.header.len = sizeof(struct acx_fw_gen_frame_rates) -
sizeof(struct acx_header);
rates.tx_ctrl_frame_rate = ctrl_rate;
rates.tx_ctrl_frame_mod = ctrl_mod;
rates.tx_mgt_frame_rate = mgt_rate;
rates.tx_mgt_frame_mod = mgt_mod;
ret = wl12xx_cmd_configure(wl, &rates, sizeof(rates));
if (ret < 0) {
wl12xx_error("Failed to set FW rates and modulation");
return ret;
}
return 0;
}
int wl12xx_acx_station_id(struct wl12xx *wl)
{
int ret, i;
struct dot11_station_id mac;
wl12xx_debug(DEBUG_ACX, "acx dot11_station_id");
mac.header.id = DOT11_STATION_ID;
mac.header.len = sizeof(mac) - sizeof(struct acx_header);
for (i = 0; i < ETH_ALEN; i++)
mac.mac[i] = wl->mac_addr[ETH_ALEN - 1 - i];
ret = wl12xx_cmd_configure(wl, &mac, sizeof(mac));
if (ret < 0)
return ret;
return 0;
}
int wl12xx_acx_default_key(struct wl12xx *wl, u8 key_id)
{
struct acx_dot11_default_key default_key;
int ret;
wl12xx_debug(DEBUG_ACX, "acx dot11_default_key (%d)", key_id);
default_key.header.id = DOT11_DEFAULT_KEY;
default_key.header.len = sizeof(default_key) -
sizeof(struct acx_header);
default_key.id = key_id;
ret = wl12xx_cmd_configure(wl, &default_key, sizeof(default_key));
if (ret < 0) {
wl12xx_error("Couldnt set default key");
return ret;
}
wl->default_key = key_id;
return 0;
}
int wl12xx_acx_wake_up_conditions(struct wl12xx *wl, u8 listen_interval)
{
struct acx_wake_up_condition wake_up;
wl12xx_debug(DEBUG_ACX, "acx wake up conditions");
wake_up.header.id = ACX_WAKE_UP_CONDITIONS;
wake_up.header.len = sizeof(wake_up) - sizeof(struct acx_header);
wake_up.wake_up_event = WAKE_UP_EVENT_DTIM_BITMAP;
wake_up.listen_interval = listen_interval;
return wl12xx_cmd_configure(wl, &wake_up, sizeof(wake_up));
}
int wl12xx_acx_sleep_auth(struct wl12xx *wl, u8 sleep_auth)
{
int ret;
struct acx_sleep_auth auth;
wl12xx_debug(DEBUG_ACX, "acx sleep auth");
auth.header.id = ACX_SLEEP_AUTH;
auth.header.len = sizeof(auth) - sizeof(struct acx_header);
auth.sleep_auth = sleep_auth;
ret = wl12xx_cmd_configure(wl, &auth, sizeof(auth));
if (ret < 0)
return ret;
return 0;
}
int wl12xx_acx_fw_version(struct wl12xx *wl, char *buf, size_t len)
{
struct wl12xx_command cmd;
struct acx_revision *rev;
int ret;
wl12xx_debug(DEBUG_ACX, "acx fw rev");
memset(&cmd, 0, sizeof(cmd));
ret = wl12xx_cmd_interrogate(wl, ACX_FW_REV, sizeof(*rev), &cmd);
if (ret < 0) {
wl12xx_warning("ACX_FW_REV interrogate failed");
return ret;
}
rev = (struct acx_revision *) &cmd.parameters;
/* be careful with the buffer sizes */
strncpy(buf, rev->fw_version, min(len, sizeof(rev->fw_version)));
/*
* if the firmware version string is exactly
* sizeof(rev->fw_version) long or fw_len is less than
* sizeof(rev->fw_version) it won't be null terminated
*/
buf[min(len, sizeof(rev->fw_version)) - 1] = '\0';
return 0;
}
int wl12xx_acx_tx_power(struct wl12xx *wl, int power)
{
struct acx_current_tx_power ie;
int ret;
wl12xx_debug(DEBUG_ACX, "acx dot11_cur_tx_pwr");
if (power < 0 || power > 25)
return -EINVAL;
memset(&ie, 0, sizeof(ie));
ie.header.id = DOT11_CUR_TX_PWR;
ie.header.len = sizeof(ie) - sizeof(struct acx_header);
ie.current_tx_power = power * 10;
ret = wl12xx_cmd_configure(wl, &ie, sizeof(ie));
if (ret < 0) {
wl12xx_warning("configure of tx power failed: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_feature_cfg(struct wl12xx *wl)
{
struct acx_feature_config feature;
int ret;
wl12xx_debug(DEBUG_ACX, "acx feature cfg");
memset(&feature, 0, sizeof(feature));
feature.header.id = ACX_FEATURE_CFG;
feature.header.len = sizeof(feature) - sizeof(struct acx_header);
/* DF_ENCRYPTION_DISABLE and DF_SNIFF_MODE_ENABLE are disabled */
feature.data_flow_options = 0;
feature.options = 0;
ret = wl12xx_cmd_configure(wl, &feature, sizeof(feature));
if (ret < 0)
wl12xx_error("Couldnt set HW encryption");
return ret;
}
int wl12xx_acx_mem_map(struct wl12xx *wl, void *mem_map, size_t len)
{
struct wl12xx_command cmd;
int ret;
wl12xx_debug(DEBUG_ACX, "acx mem map");
ret = wl12xx_cmd_interrogate(wl, ACX_MEM_MAP, len, &cmd);
if (ret < 0)
return ret;
else if (cmd.status != CMD_STATUS_SUCCESS)
return -EIO;
memcpy(mem_map, &cmd.parameters, len);
return 0;
}
int wl12xx_acx_data_path_params(struct wl12xx *wl,
struct acx_data_path_params_resp *data_path)
{
struct acx_data_path_params params;
struct wl12xx_command cmd;
int ret;
wl12xx_debug(DEBUG_ACX, "acx data path params");
params.rx_packet_ring_chunk_size = DP_RX_PACKET_RING_CHUNK_SIZE;
params.tx_packet_ring_chunk_size = DP_TX_PACKET_RING_CHUNK_SIZE;
params.rx_packet_ring_chunk_num = DP_RX_PACKET_RING_CHUNK_NUM;
params.tx_packet_ring_chunk_num = DP_TX_PACKET_RING_CHUNK_NUM;
params.tx_complete_threshold = 1;
params.tx_complete_ring_depth = FW_TX_CMPLT_BLOCK_SIZE;
params.tx_complete_timeout = DP_TX_COMPLETE_TIME_OUT;
params.header.id = ACX_DATA_PATH_PARAMS;
params.header.len = sizeof(params) - sizeof(struct acx_header);
ret = wl12xx_cmd_configure(wl, &params, sizeof(params));
if (ret < 0)
return ret;
ret = wl12xx_cmd_interrogate(wl, ACX_DATA_PATH_PARAMS,
sizeof(struct acx_data_path_params_resp),
&cmd);
if (ret < 0) {
wl12xx_warning("failed to read data path parameters: %d", ret);
return ret;
} else if (cmd.status != CMD_STATUS_SUCCESS) {
wl12xx_warning("data path parameter acx status failed");
return -EIO;
}
memcpy(data_path, &cmd.parameters, sizeof(*data_path));
return 0;
}
int wl12xx_acx_rx_msdu_life_time(struct wl12xx *wl, u32 life_time)
{
struct rx_msdu_lifetime msdu_lifetime;
int ret;
wl12xx_debug(DEBUG_ACX, "acx rx msdu life time");
msdu_lifetime.header.id = DOT11_RX_MSDU_LIFE_TIME;
msdu_lifetime.header.len = sizeof(msdu_lifetime) -
sizeof(struct acx_header);
msdu_lifetime.lifetime = life_time;
ret = wl12xx_cmd_configure(wl, &msdu_lifetime, sizeof(msdu_lifetime));
if (ret < 0) {
wl12xx_warning("failed to set rx msdu life time: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_rx_config(struct wl12xx *wl, u32 config, u32 filter)
{
struct acx_rx_config rx_config;
int ret;
wl12xx_debug(DEBUG_ACX, "acx rx config");
rx_config.header.id = ACX_RX_CFG;
rx_config.header.len = sizeof(rx_config) - sizeof(struct acx_header);
rx_config.config_options = config;
rx_config.filter_options = filter;
ret = wl12xx_cmd_configure(wl, &rx_config, sizeof(rx_config));
if (ret < 0) {
wl12xx_warning("failed to set rx config: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_pd_threshold(struct wl12xx *wl)
{
struct acx_packet_detection packet_detection;
int ret;
wl12xx_debug(DEBUG_ACX, "acx data pd threshold");
/* FIXME: threshold value not set */
packet_detection.header.id = ACX_PD_THRESHOLD;
packet_detection.header.len = sizeof(packet_detection) -
sizeof(struct acx_header);
ret = wl12xx_cmd_configure(wl, &packet_detection,
sizeof(packet_detection));
if (ret < 0) {
wl12xx_warning("failed to set pd threshold: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_slot(struct wl12xx *wl, enum acx_slot_type slot_time)
{
struct acx_slot slot;
int ret;
wl12xx_debug(DEBUG_ACX, "acx slot");
slot.header.id = ACX_SLOT;
slot.header.len = sizeof(slot) - sizeof(struct acx_header);
slot.wone_index = STATION_WONE_INDEX;
slot.slot_time = slot_time;
ret = wl12xx_cmd_configure(wl, &slot, sizeof(slot));
if (ret < 0) {
wl12xx_warning("failed to set slot time: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_group_address_tbl(struct wl12xx *wl)
{
struct multicast_grp_addr_start multicast;
int ret;
wl12xx_debug(DEBUG_ACX, "acx group address tbl");
/* MAC filtering */
multicast.header.id = DOT11_GROUP_ADDRESS_TBL;
multicast.header.len = sizeof(multicast) - sizeof(struct acx_header);
multicast.enabled = 0;
multicast.num_groups = 0;
memset(multicast.mac_table, 0, ADDRESS_GROUP_MAX_LEN);
ret = wl12xx_cmd_configure(wl, &multicast, sizeof(multicast));
if (ret < 0) {
wl12xx_warning("failed to set group addr table: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_service_period_timeout(struct wl12xx *wl)
{
struct acx_rx_timeout rx_timeout;
int ret;
wl12xx_debug(DEBUG_ACX, "acx service period timeout");
/* RX timeout */
rx_timeout.header.id = ACX_SERVICE_PERIOD_TIMEOUT;
rx_timeout.header.len = sizeof(rx_timeout) - sizeof(struct acx_header);
rx_timeout.ps_poll_timeout = RX_TIMEOUT_PS_POLL_DEF;
rx_timeout.upsd_timeout = RX_TIMEOUT_UPSD_DEF;
ret = wl12xx_cmd_configure(wl, &rx_timeout, sizeof(rx_timeout));
if (ret < 0) {
wl12xx_warning("failed to set service period timeout: %d",
ret);
return ret;
}
return 0;
}
int wl12xx_acx_rts_threshold(struct wl12xx *wl, u16 rts_threshold)
{
struct acx_rts_threshold rts;
int ret;
wl12xx_debug(DEBUG_ACX, "acx rts threshold");
rts.header.id = DOT11_RTS_THRESHOLD;
rts.header.len = sizeof(rts) - sizeof(struct acx_header);
rts.threshold = rts_threshold;
ret = wl12xx_cmd_configure(wl, &rts, sizeof(rts));
if (ret < 0) {
wl12xx_warning("failed to set rts threshold: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_beacon_filter_opt(struct wl12xx *wl)
{
struct acx_beacon_filter_option beacon_filter;
int ret;
wl12xx_debug(DEBUG_ACX, "acx beacon filter opt");
beacon_filter.header.id = ACX_BEACON_FILTER_OPT;
beacon_filter.header.len = sizeof(beacon_filter) -
sizeof(struct acx_header);
beacon_filter.enable = 0;
beacon_filter.max_num_beacons = 0;
ret = wl12xx_cmd_configure(wl, &beacon_filter, sizeof(beacon_filter));
if (ret < 0) {
wl12xx_warning("failed to set beacon filter opt: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_beacon_filter_table(struct wl12xx *wl)
{
struct acx_beacon_filter_ie_table ie_table;
int ret;
wl12xx_debug(DEBUG_ACX, "acx beacon filter table");
ie_table.header.id = ACX_BEACON_FILTER_TABLE;
ie_table.header.len = sizeof(ie_table) - sizeof(struct acx_header);
ie_table.num_ie = 0;
memset(ie_table.table, 0, BEACON_FILTER_TABLE_MAX_SIZE);
ret = wl12xx_cmd_configure(wl, &ie_table, sizeof(ie_table));
if (ret < 0) {
wl12xx_warning("failed to set beacon filter table: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_sg_enable(struct wl12xx *wl)
{
struct acx_bt_wlan_coex pta;
int ret;
wl12xx_debug(DEBUG_ACX, "acx sg enable");
pta.header.id = ACX_SG_ENABLE;
pta.header.len = sizeof(pta) - sizeof(struct acx_header);
pta.enable = SG_ENABLE;
ret = wl12xx_cmd_configure(wl, &pta, sizeof(pta));
if (ret < 0) {
wl12xx_warning("failed to set softgemini enable: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_sg_cfg(struct wl12xx *wl)
{
struct acx_bt_wlan_coex_param param;
int ret;
wl12xx_debug(DEBUG_ACX, "acx sg cfg");
/* BT-WLAN coext parameters */
param.header.id = ACX_SG_CFG;
param.header.len = sizeof(param) - sizeof(struct acx_header);
param.min_rate = RATE_INDEX_24MBPS;
param.bt_hp_max_time = PTA_BT_HP_MAXTIME_DEF;
param.wlan_hp_max_time = PTA_WLAN_HP_MAX_TIME_DEF;
param.sense_disable_timer = PTA_SENSE_DISABLE_TIMER_DEF;
param.rx_time_bt_hp = PTA_PROTECTIVE_RX_TIME_DEF;
param.tx_time_bt_hp = PTA_PROTECTIVE_TX_TIME_DEF;
param.rx_time_bt_hp_fast = PTA_PROTECTIVE_RX_TIME_FAST_DEF;
param.tx_time_bt_hp_fast = PTA_PROTECTIVE_TX_TIME_FAST_DEF;
param.wlan_cycle_fast = PTA_CYCLE_TIME_FAST_DEF;
param.bt_anti_starvation_period = PTA_ANTI_STARVE_PERIOD_DEF;
param.next_bt_lp_packet = PTA_TIMEOUT_NEXT_BT_LP_PACKET_DEF;
param.wake_up_beacon = PTA_TIME_BEFORE_BEACON_DEF;
param.hp_dm_max_guard_time = PTA_HPDM_MAX_TIME_DEF;
param.next_wlan_packet = PTA_TIME_OUT_NEXT_WLAN_DEF;
param.antenna_type = PTA_ANTENNA_TYPE_DEF;
param.signal_type = PTA_SIGNALING_TYPE_DEF;
param.afh_leverage_on = PTA_AFH_LEVERAGE_ON_DEF;
param.quiet_cycle_num = PTA_NUMBER_QUIET_CYCLE_DEF;
param.max_cts = PTA_MAX_NUM_CTS_DEF;
param.wlan_packets_num = PTA_NUMBER_OF_WLAN_PACKETS_DEF;
param.bt_packets_num = PTA_NUMBER_OF_BT_PACKETS_DEF;
param.missed_rx_avalanche = PTA_RX_FOR_AVALANCHE_DEF;
param.wlan_elp_hp = PTA_ELP_HP_DEF;
param.bt_anti_starvation_cycles = PTA_ANTI_STARVE_NUM_CYCLE_DEF;
param.ack_mode_dual_ant = PTA_ACK_MODE_DEF;
param.pa_sd_enable = PTA_ALLOW_PA_SD_DEF;
param.pta_auto_mode_enable = PTA_AUTO_MODE_NO_CTS_DEF;
param.bt_hp_respected_num = PTA_BT_HP_RESPECTED_DEF;
ret = wl12xx_cmd_configure(wl, &param, sizeof(param));
if (ret < 0) {
wl12xx_warning("failed to set sg config: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_cca_threshold(struct wl12xx *wl)
{
struct acx_energy_detection detection;
int ret;
wl12xx_debug(DEBUG_ACX, "acx cca threshold");
detection.header.id = ACX_CCA_THRESHOLD;
detection.header.len = sizeof(detection) - sizeof(struct acx_header);
detection.rx_cca_threshold = CCA_THRSH_DISABLE_ENERGY_D;
detection.tx_energy_detection = 0;
ret = wl12xx_cmd_configure(wl, &detection, sizeof(detection));
if (ret < 0) {
wl12xx_warning("failed to set cca threshold: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_bcn_dtim_options(struct wl12xx *wl)
{
struct acx_beacon_broadcast bb;
int ret;
wl12xx_debug(DEBUG_ACX, "acx bcn dtim options");
bb.header.id = ACX_BCN_DTIM_OPTIONS;
bb.header.len = sizeof(bb) - sizeof(struct acx_header);
bb.beacon_rx_timeout = BCN_RX_TIMEOUT_DEF_VALUE;
bb.broadcast_timeout = BROADCAST_RX_TIMEOUT_DEF_VALUE;
bb.rx_broadcast_in_ps = RX_BROADCAST_IN_PS_DEF_VALUE;
bb.ps_poll_threshold = CONSECUTIVE_PS_POLL_FAILURE_DEF;
ret = wl12xx_cmd_configure(wl, &bb, sizeof(bb));
if (ret < 0) {
wl12xx_warning("failed to set rx config: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_aid(struct wl12xx *wl, u16 aid)
{
struct acx_aid acx_aid;
int ret;
wl12xx_debug(DEBUG_ACX, "acx aid");
acx_aid.header.id = ACX_AID;
acx_aid.header.len = sizeof(acx_aid) - sizeof(struct acx_header);
acx_aid.aid = aid;
ret = wl12xx_cmd_configure(wl, &acx_aid, sizeof(acx_aid));
if (ret < 0) {
wl12xx_warning("failed to set aid: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_event_mbox_mask(struct wl12xx *wl, u32 event_mask)
{
struct acx_event_mask mask;
int ret;
wl12xx_debug(DEBUG_ACX, "acx event mbox mask");
mask.header.id = ACX_EVENT_MBOX_MASK;
mask.header.len = sizeof(mask) - sizeof(struct acx_header);
/* high event mask is unused */
mask.high_event_mask = 0xffffffff;
mask.event_mask = event_mask;
ret = wl12xx_cmd_configure(wl, &mask, sizeof(mask));
if (ret < 0) {
wl12xx_warning("failed to set aid: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_set_preamble(struct wl12xx *wl, enum acx_preamble_type preamble)
{
struct acx_preamble ie;
int ret;
wl12xx_debug(DEBUG_ACX, "acx_set_preamble");
memset(&ie, 0, sizeof(ie));
ie.header.id = ACX_PREAMBLE_TYPE;
ie.header.len = sizeof(ie) - sizeof(struct acx_header);
ie.preamble = preamble;
ret = wl12xx_cmd_configure(wl, &ie, sizeof(ie));
if (ret < 0) {
wl12xx_warning("Setting of preamble failed: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_cts_protect(struct wl12xx *wl,
enum acx_ctsprotect_type ctsprotect)
{
struct acx_ctsprotect ie;
int ret;
wl12xx_debug(DEBUG_ACX, "acx_set_ctsprotect");
memset(&ie, 0, sizeof(ie));
ie.header.id = ACX_CTS_PROTECTION;
ie.header.len = sizeof(ie) - sizeof(struct acx_header);
ie.ctsprotect = ctsprotect;
ret = wl12xx_cmd_configure(wl, &ie, sizeof(ie));
if (ret < 0) {
wl12xx_warning("Setting of ctsprotect failed: %d", ret);
return ret;
}
return 0;
}
int wl12xx_acx_statistics(struct wl12xx *wl, struct acx_statistics *stats)
{
struct wl12xx_command *answer;
int ret;
wl12xx_debug(DEBUG_ACX, "acx statistics");
answer = kmalloc(sizeof(*answer), GFP_KERNEL);
if (!answer) {
wl12xx_warning("could not allocate memory for acx statistics");
ret = -ENOMEM;
goto out;
}
ret = wl12xx_cmd_interrogate(wl, ACX_STATISTICS, sizeof(*answer),
answer);
if (ret < 0) {
wl12xx_warning("acx statistics failed: %d", ret);
goto out;
}
memcpy(stats, answer->parameters, sizeof(*stats));
out:
kfree(answer);
return ret;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,295 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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/gpio.h>
#include "reg.h"
#include "boot.h"
#include "spi.h"
#include "event.h"
static void wl12xx_boot_enable_interrupts(struct wl12xx *wl)
{
enable_irq(wl->irq);
}
void wl12xx_boot_target_enable_interrupts(struct wl12xx *wl)
{
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_MASK, ~(wl->intr_mask));
wl12xx_reg_write32(wl, HI_CFG, HI_CFG_DEF_VAL);
}
int wl12xx_boot_soft_reset(struct wl12xx *wl)
{
unsigned long timeout;
u32 boot_data;
/* perform soft reset */
wl12xx_reg_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 = wl12xx_reg_read32(wl, ACX_REG_SLV_SOFT_RESET);
wl12xx_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 */
wl12xx_error("soft reset timeout");
return -1;
}
udelay(SOFT_RESET_STALL_TIME);
}
/* disable Rx/Tx */
wl12xx_reg_write32(wl, ENABLE, 0x0);
/* disable auto calibration on start*/
wl12xx_reg_write32(wl, SPARE_A2, 0xffff);
return 0;
}
int wl12xx_boot_init_seq(struct wl12xx *wl)
{
u32 scr_pad6, init_data, tmp, elp_cmd, ref_freq;
/*
* col #1: INTEGER_DIVIDER
* col #2: FRACTIONAL_DIVIDER
* col #3: ATTN_BB
* col #4: ALPHA_BB
* col #5: STOP_TIME_BB
* col #6: BB_PLL_LOOP_FILTER
*/
static const u32 LUT[REF_FREQ_NUM][LUT_PARAM_NUM] = {
{ 83, 87381, 0xB, 5, 0xF00, 3}, /* REF_FREQ_19_2*/
{ 61, 141154, 0xB, 5, 0x1450, 2}, /* REF_FREQ_26_0*/
{ 41, 174763, 0xC, 6, 0x2D00, 1}, /* REF_FREQ_38_4*/
{ 40, 0, 0xC, 6, 0x2EE0, 1}, /* REF_FREQ_40_0*/
{ 47, 162280, 0xC, 6, 0x2760, 1} /* REF_FREQ_33_6 */
};
/* read NVS params */
scr_pad6 = wl12xx_reg_read32(wl, SCR_PAD6);
wl12xx_debug(DEBUG_BOOT, "scr_pad6 0x%x", scr_pad6);
/* read ELP_CMD */
elp_cmd = wl12xx_reg_read32(wl, ELP_CMD);
wl12xx_debug(DEBUG_BOOT, "elp_cmd 0x%x", elp_cmd);
/* set the BB calibration time to be 300 usec (PLL_CAL_TIME) */
ref_freq = scr_pad6 & 0x000000FF;
wl12xx_debug(DEBUG_BOOT, "ref_freq 0x%x", ref_freq);
wl12xx_reg_write32(wl, PLL_CAL_TIME, 0x9);
/*
* PG 1.2: set the clock buffer time to be 210 usec (CLK_BUF_TIME)
*/
wl12xx_reg_write32(wl, CLK_BUF_TIME, 0x6);
/*
* set the clock detect feature to work in the restart wu procedure
* (ELP_CFG_MODE[14]) and Select the clock source type
* (ELP_CFG_MODE[13:12])
*/
tmp = ((scr_pad6 & 0x0000FF00) << 4) | 0x00004000;
wl12xx_reg_write32(wl, ELP_CFG_MODE, tmp);
/* PG 1.2: enable the BB PLL fix. Enable the PLL_LIMP_CLK_EN_CMD */
elp_cmd |= 0x00000040;
wl12xx_reg_write32(wl, ELP_CMD, elp_cmd);
/* PG 1.2: Set the BB PLL stable time to be 1000usec
* (PLL_STABLE_TIME) */
wl12xx_reg_write32(wl, CFG_PLL_SYNC_CNT, 0x20);
/* PG 1.2: read clock request time */
init_data = wl12xx_reg_read32(wl, CLK_REQ_TIME);
/*
* PG 1.2: set the clock request time to be ref_clk_settling_time -
* 1ms = 4ms
*/
if (init_data > 0x21)
tmp = init_data - 0x21;
else
tmp = 0;
wl12xx_reg_write32(wl, CLK_REQ_TIME, tmp);
/* set BB PLL configurations in RF AFE */
wl12xx_reg_write32(wl, 0x003058cc, 0x4B5);
/* set RF_AFE_REG_5 */
wl12xx_reg_write32(wl, 0x003058d4, 0x50);
/* set RF_AFE_CTRL_REG_2 */
wl12xx_reg_write32(wl, 0x00305948, 0x11c001);
/*
* change RF PLL and BB PLL divider for VCO clock and adjust VCO
* bais current(RF_AFE_REG_13)
*/
wl12xx_reg_write32(wl, 0x003058f4, 0x1e);
/* set BB PLL configurations */
tmp = LUT[ref_freq][LUT_PARAM_INTEGER_DIVIDER] | 0x00017000;
wl12xx_reg_write32(wl, 0x00305840, tmp);
/* set fractional divider according to Appendix C-BB PLL
* Calculations
*/
tmp = LUT[ref_freq][LUT_PARAM_FRACTIONAL_DIVIDER];
wl12xx_reg_write32(wl, 0x00305844, tmp);
/* set the initial data for the sigma delta */
wl12xx_reg_write32(wl, 0x00305848, 0x3039);
/*
* set the accumulator attenuation value, calibration loop1
* (alpha), calibration loop2 (beta), calibration loop3 (gamma) and
* the VCO gain
*/
tmp = (LUT[ref_freq][LUT_PARAM_ATTN_BB] << 16) |
(LUT[ref_freq][LUT_PARAM_ALPHA_BB] << 12) | 0x1;
wl12xx_reg_write32(wl, 0x00305854, tmp);
/*
* set the calibration stop time after holdoff time expires and set
* settling time HOLD_OFF_TIME_BB
*/
tmp = LUT[ref_freq][LUT_PARAM_STOP_TIME_BB] | 0x000A0000;
wl12xx_reg_write32(wl, 0x00305858, tmp);
/*
* set BB PLL Loop filter capacitor3- BB_C3[2:0] and set BB PLL
* constant leakage current to linearize PFD to 0uA -
* BB_ILOOPF[7:3]
*/
tmp = LUT[ref_freq][LUT_PARAM_BB_PLL_LOOP_FILTER] | 0x00000030;
wl12xx_reg_write32(wl, 0x003058f8, tmp);
/*
* set regulator output voltage for n divider to
* 1.35-BB_REFDIV[1:0], set charge pump current- BB_CPGAIN[4:2],
* set BB PLL Loop filter capacitor2- BB_C2[7:5], set gain of BB
* PLL auto-call to normal mode- BB_CALGAIN_3DB[8]
*/
wl12xx_reg_write32(wl, 0x003058f0, 0x29);
/* enable restart wakeup sequence (ELP_CMD[0]) */
wl12xx_reg_write32(wl, ELP_CMD, elp_cmd | 0x1);
/* restart sequence completed */
udelay(2000);
return 0;
}
int wl12xx_boot_run_firmware(struct wl12xx *wl)
{
int loop, ret;
u32 chip_id, interrupt;
wl->chip.op_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT);
chip_id = wl12xx_reg_read32(wl, CHIP_ID_B);
wl12xx_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id);
if (chip_id != wl->chip.id) {
wl12xx_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);
interrupt = wl12xx_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR);
if (interrupt == 0xffffffff) {
wl12xx_error("error reading hardware complete "
"init indication");
return -EIO;
}
/* check that ACX_INTR_INIT_COMPLETE is enabled */
else if (interrupt & wl->chip.intr_init_complete) {
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_ACK,
wl->chip.intr_init_complete);
break;
}
}
if (loop >= INIT_LOOP) {
wl12xx_error("timeout waiting for the hardware to "
"complete initialization");
return -EIO;
}
/* get hardware config command mail box */
wl->cmd_box_addr = wl12xx_reg_read32(wl, REG_COMMAND_MAILBOX_PTR);
/* get hardware config event mail box */
wl->event_box_addr = wl12xx_reg_read32(wl, REG_EVENT_MAILBOX_PTR);
/* set the working partition to its "running" mode offset */
wl12xx_set_partition(wl,
wl->chip.p_table[PART_WORK].mem.start,
wl->chip.p_table[PART_WORK].mem.size,
wl->chip.p_table[PART_WORK].reg.start,
wl->chip.p_table[PART_WORK].reg.size);
wl12xx_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x event_box_addr 0x%x",
wl->cmd_box_addr, wl->event_box_addr);
/*
* in case of full asynchronous mode the firmware event must be
* ready to receive event from the command mailbox
*/
/* enable gpio interrupts */
wl12xx_boot_enable_interrupts(wl);
wl->chip.op_target_enable_interrupts(wl);
/* unmask all mbox events */
wl->event_mask = 0xffffffff;
ret = wl12xx_event_unmask(wl);
if (ret < 0) {
wl12xx_error("EVENT mask setting failed");
return ret;
}
wl12xx_event_mbox_config(wl);
/* firmware startup completed */
return 0;
}

View file

@ -0,0 +1,40 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 wl12xx_boot_soft_reset(struct wl12xx *wl);
int wl12xx_boot_init_seq(struct wl12xx *wl);
int wl12xx_boot_run_firmware(struct wl12xx *wl);
void wl12xx_boot_target_enable_interrupts(struct wl12xx *wl);
/* number of times we try to read the INIT interrupt */
#define INIT_LOOP 20000
/* delay between retries */
#define INIT_LOOP_DELAY 50
#endif

View file

@ -0,0 +1,353 @@
#include "cmd.h"
#include <linux/module.h>
#include <linux/crc7.h>
#include <linux/spi/spi.h>
#include "wl12xx.h"
#include "wl12xx_80211.h"
#include "reg.h"
#include "spi.h"
#include "ps.h"
int wl12xx_cmd_send(struct wl12xx *wl, u16 type, void *buf, size_t buf_len)
{
struct wl12xx_command cmd;
unsigned long timeout;
size_t cmd_len;
u32 intr;
int ret = 0;
memset(&cmd, 0, sizeof(cmd));
cmd.id = type;
cmd.status = 0;
memcpy(cmd.parameters, buf, buf_len);
cmd_len = ALIGN(buf_len, 4) + CMDMBOX_HEADER_LEN;
wl12xx_ps_elp_wakeup(wl);
wl12xx_spi_mem_write(wl, wl->cmd_box_addr, &cmd, cmd_len);
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_TRIG, INTR_TRIG_CMD);
timeout = jiffies + msecs_to_jiffies(WL12XX_COMMAND_TIMEOUT);
intr = wl12xx_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR);
while (!(intr & wl->chip.intr_cmd_complete)) {
if (time_after(jiffies, timeout)) {
wl12xx_error("command complete timeout");
ret = -ETIMEDOUT;
goto out;
}
msleep(1);
intr = wl12xx_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR);
}
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_ACK,
wl->chip.intr_cmd_complete);
out:
wl12xx_ps_elp_sleep(wl);
return ret;
}
int wl12xx_cmd_test(struct wl12xx *wl, void *buf, size_t buf_len, u8 answer)
{
int ret;
wl12xx_debug(DEBUG_CMD, "cmd test");
ret = wl12xx_cmd_send(wl, CMD_TEST, buf, buf_len);
if (ret < 0) {
wl12xx_warning("TEST command failed");
return ret;
}
if (answer) {
struct wl12xx_command *cmd_answer;
/*
* The test command got in, we can read the answer.
* The answer would be a wl12xx_command, where the
* parameter array contains the actual answer.
*/
wl12xx_ps_elp_wakeup(wl);
wl12xx_spi_mem_read(wl, wl->cmd_box_addr, buf, buf_len);
wl12xx_ps_elp_sleep(wl);
cmd_answer = buf;
if (cmd_answer->status != CMD_STATUS_SUCCESS)
wl12xx_error("TEST command answer error: %d",
cmd_answer->status);
}
return 0;
}
int wl12xx_cmd_interrogate(struct wl12xx *wl, u16 ie_id, u16 ie_len,
void *answer)
{
struct wl12xx_command *cmd;
struct acx_header header;
int ret;
wl12xx_debug(DEBUG_CMD, "cmd interrogate");
header.id = ie_id;
header.len = ie_len - sizeof(header);
ret = wl12xx_cmd_send(wl, CMD_INTERROGATE, &header, sizeof(header));
if (ret < 0) {
wl12xx_error("INTERROGATE command failed");
return ret;
}
wl12xx_ps_elp_wakeup(wl);
/* the interrogate command got in, we can read the answer */
wl12xx_spi_mem_read(wl, wl->cmd_box_addr, answer,
CMDMBOX_HEADER_LEN + ie_len);
wl12xx_ps_elp_sleep(wl);
cmd = answer;
if (cmd->status != CMD_STATUS_SUCCESS)
wl12xx_error("INTERROGATE command error: %d",
cmd->status);
return 0;
}
int wl12xx_cmd_configure(struct wl12xx *wl, void *ie, int ie_len)
{
int ret;
wl12xx_debug(DEBUG_CMD, "cmd configure");
ret = wl12xx_cmd_send(wl, CMD_CONFIGURE, ie,
ie_len);
if (ret < 0) {
wl12xx_warning("CONFIGURE command NOK");
return ret;
}
return 0;
}
int wl12xx_cmd_vbm(struct wl12xx *wl, u8 identity,
void *bitmap, u16 bitmap_len, u8 bitmap_control)
{
struct vbm_update_request vbm;
int ret;
wl12xx_debug(DEBUG_CMD, "cmd vbm");
/* Count and period will be filled by the target */
vbm.tim.bitmap_ctrl = bitmap_control;
if (bitmap_len > PARTIAL_VBM_MAX) {
wl12xx_warning("cmd vbm len is %d B, truncating to %d",
bitmap_len, PARTIAL_VBM_MAX);
bitmap_len = PARTIAL_VBM_MAX;
}
memcpy(vbm.tim.pvb_field, bitmap, bitmap_len);
vbm.tim.identity = identity;
vbm.tim.length = bitmap_len + 3;
vbm.len = cpu_to_le16(bitmap_len + 5);
ret = wl12xx_cmd_send(wl, CMD_VBM, &vbm, sizeof(vbm));
if (ret < 0) {
wl12xx_error("VBM command failed");
return ret;
}
return 0;
}
int wl12xx_cmd_data_path(struct wl12xx *wl, u8 channel, u8 enable)
{
int ret;
u16 cmd_rx, cmd_tx;
wl12xx_debug(DEBUG_CMD, "cmd data path");
if (enable) {
cmd_rx = CMD_ENABLE_RX;
cmd_tx = CMD_ENABLE_TX;
} else {
cmd_rx = CMD_DISABLE_RX;
cmd_tx = CMD_DISABLE_TX;
}
ret = wl12xx_cmd_send(wl, cmd_rx, &channel, sizeof(channel));
if (ret < 0) {
wl12xx_error("rx %s cmd for channel %d failed",
enable ? "start" : "stop", channel);
return ret;
}
wl12xx_debug(DEBUG_BOOT, "rx %s cmd channel %d",
enable ? "start" : "stop", channel);
ret = wl12xx_cmd_send(wl, cmd_tx, &channel, sizeof(channel));
if (ret < 0) {
wl12xx_error("tx %s cmd for channel %d failed",
enable ? "start" : "stop", channel);
return ret;
}
wl12xx_debug(DEBUG_BOOT, "tx %s cmd channel %d",
enable ? "start" : "stop", channel);
return 0;
}
int wl12xx_cmd_join(struct wl12xx *wl, u8 bss_type, u8 dtim_interval,
u16 beacon_interval, u8 wait)
{
unsigned long timeout;
struct cmd_join join = {};
int ret, i;
u8 *bssid;
/* FIXME: this should be in main.c */
ret = wl12xx_acx_frame_rates(wl, DEFAULT_HW_GEN_TX_RATE,
DEFAULT_HW_GEN_MODULATION_TYPE,
wl->tx_mgmt_frm_rate,
wl->tx_mgmt_frm_mod);
if (ret < 0)
return ret;
wl12xx_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 = wl->rx_config;
join.rx_filter_options = wl->rx_filter;
join.basic_rate_set = RATE_MASK_1MBPS | RATE_MASK_2MBPS |
RATE_MASK_5_5MBPS | RATE_MASK_11MBPS;
join.beacon_interval = beacon_interval;
join.dtim_interval = dtim_interval;
join.bss_type = bss_type;
join.channel = wl->channel;
join.ctrl = JOIN_CMD_CTRL_TX_FLUSH;
ret = wl12xx_cmd_send(wl, CMD_START_JOIN, &join, sizeof(join));
if (ret < 0) {
wl12xx_error("failed to initiate cmd join");
return ret;
}
timeout = msecs_to_jiffies(JOIN_TIMEOUT);
/*
* ugly hack: we should wait for JOIN_EVENT_COMPLETE_ID but to
* simplify locking we just sleep instead, for now
*/
if (wait)
msleep(10);
return 0;
}
int wl12xx_cmd_ps_mode(struct wl12xx *wl, u8 ps_mode)
{
int ret;
struct acx_ps_params ps_params;
/* FIXME: this should be in ps.c */
ret = wl12xx_acx_wake_up_conditions(wl, wl->listen_int);
if (ret < 0) {
wl12xx_error("Couldnt set wake up conditions");
return ret;
}
wl12xx_debug(DEBUG_CMD, "cmd set ps mode");
ps_params.ps_mode = ps_mode;
ps_params.send_null_data = 1;
ps_params.retries = 5;
ps_params.hang_over_period = 128;
ps_params.null_data_rate = 1; /* 1 Mbps */
ret = wl12xx_cmd_send(wl, CMD_SET_PS_MODE, &ps_params,
sizeof(ps_params));
if (ret < 0) {
wl12xx_error("cmd set_ps_mode failed");
return ret;
}
return 0;
}
int wl12xx_cmd_read_memory(struct wl12xx *wl, u32 addr, u32 len, void *answer)
{
struct cmd_read_write_memory mem_cmd, *mem_answer;
struct wl12xx_command cmd;
int ret;
wl12xx_debug(DEBUG_CMD, "cmd read memory");
memset(&mem_cmd, 0, sizeof(mem_cmd));
mem_cmd.addr = addr;
mem_cmd.size = len;
ret = wl12xx_cmd_send(wl, CMD_READ_MEMORY, &mem_cmd, sizeof(mem_cmd));
if (ret < 0) {
wl12xx_error("read memory command failed: %d", ret);
return ret;
}
/* the read command got in, we can now read the answer */
wl12xx_spi_mem_read(wl, wl->cmd_box_addr, &cmd,
CMDMBOX_HEADER_LEN + sizeof(mem_cmd));
if (cmd.status != CMD_STATUS_SUCCESS)
wl12xx_error("error in read command result: %d", cmd.status);
mem_answer = (struct cmd_read_write_memory *) cmd.parameters;
memcpy(answer, mem_answer->value, len);
return 0;
}
int wl12xx_cmd_template_set(struct wl12xx *wl, u16 cmd_id,
void *buf, size_t buf_len)
{
struct wl12xx_cmd_packet_template template;
int ret;
wl12xx_debug(DEBUG_CMD, "cmd template %d", cmd_id);
memset(&template, 0, sizeof(template));
WARN_ON(buf_len > WL12XX_MAX_TEMPLATE_SIZE);
buf_len = min_t(size_t, buf_len, WL12XX_MAX_TEMPLATE_SIZE);
template.size = cpu_to_le16(buf_len);
if (buf)
memcpy(template.template, buf, buf_len);
ret = wl12xx_cmd_send(wl, cmd_id, &template,
sizeof(template.size) + buf_len);
if (ret < 0) {
wl12xx_warning("cmd set_template failed: %d", ret);
return ret;
}
return 0;
}

View file

@ -0,0 +1,265 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_CMD_H__
#define __WL12XX_CMD_H__
#include "wl12xx.h"
int wl12xx_cmd_send(struct wl12xx *wl, u16 type, void *buf, size_t buf_len);
int wl12xx_cmd_test(struct wl12xx *wl, void *buf, size_t buf_len, u8 answer);
int wl12xx_cmd_interrogate(struct wl12xx *wl, u16 ie_id, u16 ie_len,
void *answer);
int wl12xx_cmd_configure(struct wl12xx *wl, void *ie, int ie_len);
int wl12xx_cmd_vbm(struct wl12xx *wl, u8 identity,
void *bitmap, u16 bitmap_len, u8 bitmap_control);
int wl12xx_cmd_data_path(struct wl12xx *wl, u8 channel, u8 enable);
int wl12xx_cmd_join(struct wl12xx *wl, u8 bss_type, u8 dtim_interval,
u16 beacon_interval, u8 wait);
int wl12xx_cmd_ps_mode(struct wl12xx *wl, u8 ps_mode);
int wl12xx_cmd_read_memory(struct wl12xx *wl, u32 addr, u32 len, void *answer);
int wl12xx_cmd_template_set(struct wl12xx *wl, u16 cmd_id,
void *buf, size_t buf_len);
/* unit ms */
#define WL12XX_COMMAND_TIMEOUT 2000
#define WL12XX_MAX_TEMPLATE_SIZE 300
struct wl12xx_cmd_packet_template {
__le16 size;
u8 template[WL12XX_MAX_TEMPLATE_SIZE];
} __attribute__ ((packed));
enum wl12xx_commands {
CMD_RESET = 0,
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_VBM = 10,
CMD_START_JOIN = 11,
CMD_SET_KEYS = 12,
CMD_READ_MEMORY = 13,
CMD_WRITE_MEMORY = 14,
CMD_BEACON = 19,
CMD_PROBE_RESP = 20,
CMD_NULL_DATA = 21,
CMD_PROBE_REQ = 22,
CMD_TEST = 23,
CMD_RADIO_CALIBRATE = 25, /* OBSOLETE */
CMD_ENABLE_RX_PATH = 27, /* OBSOLETE */
CMD_NOISE_HIST = 28,
CMD_RX_RESET = 29,
CMD_PS_POLL = 30,
CMD_QOS_NULL_DATA = 31,
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,
NUM_COMMANDS,
MAX_COMMAND_ID = 0xFFFF,
};
#define MAX_CMD_PARAMS 572
struct wl12xx_command {
u16 id;
u16 status;
u8 parameters[MAX_CMD_PARAMS];
};
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
};
/*
* CMD_READ_MEMORY
*
* The host issues this command to read the WiLink device memory/registers.
*
* Note: The Base Band address has special handling (16 bits registers and
* addresses). For more information, see the hardware specification.
*/
/*
* CMD_WRITE_MEMORY
*
* The host issues this command to write the WiLink device memory/registers.
*
* The Base Band address has special handling (16 bits registers and
* addresses). For more information, see the hardware specification.
*/
#define MAX_READ_SIZE 256
struct cmd_read_write_memory {
/* The address of the memory to read from or write to.*/
u32 addr;
/* The amount of data in bytes to read from or write to the WiLink
* device.*/
u32 size;
/* The actual value read from or written to the Wilink. The source
of this field is the Host in WRITE command or the Wilink in READ
command. */
u8 value[MAX_READ_SIZE];
};
#define CMDMBOX_HEADER_LEN 4
#define CMDMBOX_INFO_ELEM_HEADER_LEN 4
struct basic_scan_parameters {
u32 rx_config_options;
u32 rx_filter_options;
/*
* Scan options:
* bit 0: When this bit is set, passive scan.
* bit 1: Band, when this bit is set we scan
* in the 5Ghz band.
* bit 2: voice mode, 0 for normal scan.
* bit 3: scan priority, 1 for high priority.
*/
u16 scan_options;
/* Number of channels to scan */
u8 num_channels;
/* Number opf probe requests to send, per channel */
u8 num_probe_requests;
/* Rate and modulation for probe requests */
u16 tx_rate;
u8 tid_trigger;
u8 ssid_len;
u32 ssid[8];
} __attribute__ ((packed));
struct basic_scan_channel_parameters {
u32 min_duration; /* in TU */
u32 max_duration; /* in TU */
u32 bssid_lsb;
u16 bssid_msb;
/*
* bits 0-3: Early termination count.
* bits 4-5: Early termination condition.
*/
u8 early_termination;
u8 tx_power_att;
u8 channel;
u8 pad[3];
} __attribute__ ((packed));
/* SCAN parameters */
#define SCAN_MAX_NUM_OF_CHANNELS 16
struct cmd_scan {
struct basic_scan_parameters params;
struct basic_scan_channel_parameters channels[SCAN_MAX_NUM_OF_CHANNELS];
} __attribute__ ((packed));
enum {
BSS_TYPE_IBSS = 0,
BSS_TYPE_STA_BSS = 2,
BSS_TYPE_AP_BSS = 3,
MAX_BSS_TYPE = 0xFF
};
#define JOIN_CMD_CTRL_TX_FLUSH 0x80 /* Firmware flushes all Tx */
#define JOIN_CMD_CTRL_EARLY_WAKEUP_ENABLE 0x01 /* Early wakeup time */
struct cmd_join {
u32 bssid_lsb;
u16 bssid_msb;
u16 beacon_interval; /* in TBTTs */
u32 rx_config_options;
u32 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).
*/
u16 basic_rate_set;
u8 dtim_interval;
u8 tx_ctrl_frame_rate; /* OBSOLETE */
u8 tx_ctrl_frame_mod; /* OBSOLETE */
/*
* 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 tx_mgt_frame_rate; /* OBSOLETE */
u8 tx_mgt_frame_mod; /* OBSOLETE */
u8 reserved;
} __attribute__ ((packed));
#endif /* __WL12XX_CMD_H__ */

View file

@ -0,0 +1,508 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "wl12xx.h"
#include "acx.h"
/* ms */
#define WL12XX_DEBUGFS_STATS_LIFETIME 1000
/* debugfs macros idea from mac80211 */
#define DEBUGFS_READONLY_FILE(name, buflen, fmt, value...) \
static ssize_t name## _read(struct file *file, char __user *userbuf, \
size_t count, loff_t *ppos) \
{ \
struct wl12xx *wl = file->private_data; \
char buf[buflen]; \
int res; \
\
res = scnprintf(buf, buflen, fmt "\n", ##value); \
return simple_read_from_buffer(userbuf, count, ppos, buf, res); \
} \
\
static const struct file_operations name## _ops = { \
.read = name## _read, \
.open = wl12xx_open_file_generic, \
};
#define DEBUGFS_ADD(name, parent) \
wl->debugfs.name = debugfs_create_file(#name, 0400, parent, \
wl, &name## _ops); \
if (IS_ERR(wl->debugfs.name)) { \
ret = PTR_ERR(wl->debugfs.name); \
wl->debugfs.name = NULL; \
goto out; \
}
#define DEBUGFS_DEL(name) \
do { \
debugfs_remove(wl->debugfs.name); \
wl->debugfs.name = NULL; \
} while (0)
#define DEBUGFS_FWSTATS_FILE(sub, name, buflen, fmt) \
static ssize_t sub## _ ##name## _read(struct file *file, \
char __user *userbuf, \
size_t count, loff_t *ppos) \
{ \
struct wl12xx *wl = file->private_data; \
char buf[buflen]; \
int res; \
\
wl12xx_debugfs_update_stats(wl); \
\
res = scnprintf(buf, buflen, fmt "\n", \
wl->stats.fw_stats->sub.name); \
return simple_read_from_buffer(userbuf, count, ppos, buf, res); \
} \
\
static const struct file_operations sub## _ ##name## _ops = { \
.read = sub## _ ##name## _read, \
.open = wl12xx_open_file_generic, \
};
#define DEBUGFS_FWSTATS_ADD(sub, name) \
DEBUGFS_ADD(sub## _ ##name, wl->debugfs.fw_statistics)
#define DEBUGFS_FWSTATS_DEL(sub, name) \
DEBUGFS_DEL(sub## _ ##name)
static void wl12xx_debugfs_update_stats(struct wl12xx *wl)
{
mutex_lock(&wl->mutex);
if (wl->state == WL12XX_STATE_ON &&
time_after(jiffies, wl->stats.fw_stats_update +
msecs_to_jiffies(WL12XX_DEBUGFS_STATS_LIFETIME))) {
wl12xx_acx_statistics(wl, wl->stats.fw_stats);
wl->stats.fw_stats_update = jiffies;
}
mutex_unlock(&wl->mutex);
}
static int wl12xx_open_file_generic(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
DEBUGFS_FWSTATS_FILE(tx, internal_desc_overflow, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, out_of_mem, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, hdr_overflow, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, hw_stuck, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, dropped, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, fcs_err, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, xfr_hint_trig, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, path_reset, 20, "%u");
DEBUGFS_FWSTATS_FILE(rx, reset_counter, 20, "%u");
DEBUGFS_FWSTATS_FILE(dma, rx_requested, 20, "%u");
DEBUGFS_FWSTATS_FILE(dma, rx_errors, 20, "%u");
DEBUGFS_FWSTATS_FILE(dma, tx_requested, 20, "%u");
DEBUGFS_FWSTATS_FILE(dma, tx_errors, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, cmd_cmplt, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, fiqs, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, rx_headers, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, rx_mem_overflow, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, rx_rdys, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, irqs, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, tx_procs, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, decrypt_done, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, dma0_done, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, dma1_done, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, tx_exch_complete, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, commands, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, rx_procs, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, hw_pm_mode_changes, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, host_acknowledges, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, pci_pm, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, wakeups, 20, "%u");
DEBUGFS_FWSTATS_FILE(isr, low_rssi, 20, "%u");
DEBUGFS_FWSTATS_FILE(wep, addr_key_count, 20, "%u");
DEBUGFS_FWSTATS_FILE(wep, default_key_count, 20, "%u");
/* skipping wep.reserved */
DEBUGFS_FWSTATS_FILE(wep, key_not_found, 20, "%u");
DEBUGFS_FWSTATS_FILE(wep, decrypt_fail, 20, "%u");
DEBUGFS_FWSTATS_FILE(wep, packets, 20, "%u");
DEBUGFS_FWSTATS_FILE(wep, interrupt, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, ps_enter, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, elp_enter, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, missing_bcns, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, wake_on_host, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, wake_on_timer_exp, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, tx_with_ps, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, tx_without_ps, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, rcvd_beacons, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, power_save_off, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, enable_ps, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, disable_ps, 20, "%u");
DEBUGFS_FWSTATS_FILE(pwr, fix_tsf_ps, 20, "%u");
/* skipping cont_miss_bcns_spread for now */
DEBUGFS_FWSTATS_FILE(pwr, rcvd_awake_beacons, 20, "%u");
DEBUGFS_FWSTATS_FILE(mic, rx_pkts, 20, "%u");
DEBUGFS_FWSTATS_FILE(mic, calc_failure, 20, "%u");
DEBUGFS_FWSTATS_FILE(aes, encrypt_fail, 20, "%u");
DEBUGFS_FWSTATS_FILE(aes, decrypt_fail, 20, "%u");
DEBUGFS_FWSTATS_FILE(aes, encrypt_packets, 20, "%u");
DEBUGFS_FWSTATS_FILE(aes, decrypt_packets, 20, "%u");
DEBUGFS_FWSTATS_FILE(aes, encrypt_interrupt, 20, "%u");
DEBUGFS_FWSTATS_FILE(aes, decrypt_interrupt, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, heart_beat, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, calibration, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, rx_mismatch, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, rx_mem_empty, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, rx_pool, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, oom_late, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, phy_transmit_error, 20, "%u");
DEBUGFS_FWSTATS_FILE(event, tx_stuck, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, pspoll_timeouts, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, upsd_timeouts, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, upsd_max_sptime, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, upsd_max_apturn, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, pspoll_max_apturn, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, pspoll_utilization, 20, "%u");
DEBUGFS_FWSTATS_FILE(ps, upsd_utilization, 20, "%u");
DEBUGFS_FWSTATS_FILE(rxpipe, rx_prep_beacon_drop, 20, "%u");
DEBUGFS_FWSTATS_FILE(rxpipe, descr_host_int_trig_rx_data, 20, "%u");
DEBUGFS_FWSTATS_FILE(rxpipe, beacon_buffer_thres_host_int_trig_rx_data,
20, "%u");
DEBUGFS_FWSTATS_FILE(rxpipe, missed_beacon_host_int_trig_rx_data, 20, "%u");
DEBUGFS_FWSTATS_FILE(rxpipe, tx_xfr_host_int_trig_rx_data, 20, "%u");
DEBUGFS_READONLY_FILE(retry_count, 20, "%u", wl->stats.retry_count);
DEBUGFS_READONLY_FILE(excessive_retries, 20, "%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 wl12xx *wl = file->private_data;
u32 queue_len;
char buf[20];
int res;
queue_len = skb_queue_len(&wl->tx_queue);
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 = wl12xx_open_file_generic,
};
static void wl12xx_debugfs_delete_files(struct wl12xx *wl)
{
DEBUGFS_FWSTATS_DEL(tx, internal_desc_overflow);
DEBUGFS_FWSTATS_DEL(rx, out_of_mem);
DEBUGFS_FWSTATS_DEL(rx, hdr_overflow);
DEBUGFS_FWSTATS_DEL(rx, hw_stuck);
DEBUGFS_FWSTATS_DEL(rx, dropped);
DEBUGFS_FWSTATS_DEL(rx, fcs_err);
DEBUGFS_FWSTATS_DEL(rx, xfr_hint_trig);
DEBUGFS_FWSTATS_DEL(rx, path_reset);
DEBUGFS_FWSTATS_DEL(rx, reset_counter);
DEBUGFS_FWSTATS_DEL(dma, rx_requested);
DEBUGFS_FWSTATS_DEL(dma, rx_errors);
DEBUGFS_FWSTATS_DEL(dma, tx_requested);
DEBUGFS_FWSTATS_DEL(dma, tx_errors);
DEBUGFS_FWSTATS_DEL(isr, cmd_cmplt);
DEBUGFS_FWSTATS_DEL(isr, fiqs);
DEBUGFS_FWSTATS_DEL(isr, rx_headers);
DEBUGFS_FWSTATS_DEL(isr, rx_mem_overflow);
DEBUGFS_FWSTATS_DEL(isr, rx_rdys);
DEBUGFS_FWSTATS_DEL(isr, irqs);
DEBUGFS_FWSTATS_DEL(isr, tx_procs);
DEBUGFS_FWSTATS_DEL(isr, decrypt_done);
DEBUGFS_FWSTATS_DEL(isr, dma0_done);
DEBUGFS_FWSTATS_DEL(isr, dma1_done);
DEBUGFS_FWSTATS_DEL(isr, tx_exch_complete);
DEBUGFS_FWSTATS_DEL(isr, commands);
DEBUGFS_FWSTATS_DEL(isr, rx_procs);
DEBUGFS_FWSTATS_DEL(isr, hw_pm_mode_changes);
DEBUGFS_FWSTATS_DEL(isr, host_acknowledges);
DEBUGFS_FWSTATS_DEL(isr, pci_pm);
DEBUGFS_FWSTATS_DEL(isr, wakeups);
DEBUGFS_FWSTATS_DEL(isr, low_rssi);
DEBUGFS_FWSTATS_DEL(wep, addr_key_count);
DEBUGFS_FWSTATS_DEL(wep, default_key_count);
/* skipping wep.reserved */
DEBUGFS_FWSTATS_DEL(wep, key_not_found);
DEBUGFS_FWSTATS_DEL(wep, decrypt_fail);
DEBUGFS_FWSTATS_DEL(wep, packets);
DEBUGFS_FWSTATS_DEL(wep, interrupt);
DEBUGFS_FWSTATS_DEL(pwr, ps_enter);
DEBUGFS_FWSTATS_DEL(pwr, elp_enter);
DEBUGFS_FWSTATS_DEL(pwr, missing_bcns);
DEBUGFS_FWSTATS_DEL(pwr, wake_on_host);
DEBUGFS_FWSTATS_DEL(pwr, wake_on_timer_exp);
DEBUGFS_FWSTATS_DEL(pwr, tx_with_ps);
DEBUGFS_FWSTATS_DEL(pwr, tx_without_ps);
DEBUGFS_FWSTATS_DEL(pwr, rcvd_beacons);
DEBUGFS_FWSTATS_DEL(pwr, power_save_off);
DEBUGFS_FWSTATS_DEL(pwr, enable_ps);
DEBUGFS_FWSTATS_DEL(pwr, disable_ps);
DEBUGFS_FWSTATS_DEL(pwr, fix_tsf_ps);
/* skipping cont_miss_bcns_spread for now */
DEBUGFS_FWSTATS_DEL(pwr, rcvd_awake_beacons);
DEBUGFS_FWSTATS_DEL(mic, rx_pkts);
DEBUGFS_FWSTATS_DEL(mic, calc_failure);
DEBUGFS_FWSTATS_DEL(aes, encrypt_fail);
DEBUGFS_FWSTATS_DEL(aes, decrypt_fail);
DEBUGFS_FWSTATS_DEL(aes, encrypt_packets);
DEBUGFS_FWSTATS_DEL(aes, decrypt_packets);
DEBUGFS_FWSTATS_DEL(aes, encrypt_interrupt);
DEBUGFS_FWSTATS_DEL(aes, decrypt_interrupt);
DEBUGFS_FWSTATS_DEL(event, heart_beat);
DEBUGFS_FWSTATS_DEL(event, calibration);
DEBUGFS_FWSTATS_DEL(event, rx_mismatch);
DEBUGFS_FWSTATS_DEL(event, rx_mem_empty);
DEBUGFS_FWSTATS_DEL(event, rx_pool);
DEBUGFS_FWSTATS_DEL(event, oom_late);
DEBUGFS_FWSTATS_DEL(event, phy_transmit_error);
DEBUGFS_FWSTATS_DEL(event, tx_stuck);
DEBUGFS_FWSTATS_DEL(ps, pspoll_timeouts);
DEBUGFS_FWSTATS_DEL(ps, upsd_timeouts);
DEBUGFS_FWSTATS_DEL(ps, upsd_max_sptime);
DEBUGFS_FWSTATS_DEL(ps, upsd_max_apturn);
DEBUGFS_FWSTATS_DEL(ps, pspoll_max_apturn);
DEBUGFS_FWSTATS_DEL(ps, pspoll_utilization);
DEBUGFS_FWSTATS_DEL(ps, upsd_utilization);
DEBUGFS_FWSTATS_DEL(rxpipe, rx_prep_beacon_drop);
DEBUGFS_FWSTATS_DEL(rxpipe, descr_host_int_trig_rx_data);
DEBUGFS_FWSTATS_DEL(rxpipe, beacon_buffer_thres_host_int_trig_rx_data);
DEBUGFS_FWSTATS_DEL(rxpipe, missed_beacon_host_int_trig_rx_data);
DEBUGFS_FWSTATS_DEL(rxpipe, tx_xfr_host_int_trig_rx_data);
DEBUGFS_DEL(tx_queue_len);
DEBUGFS_DEL(retry_count);
DEBUGFS_DEL(excessive_retries);
}
static int wl12xx_debugfs_add_files(struct wl12xx *wl)
{
int ret = 0;
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, wl->debugfs.rootdir);
DEBUGFS_ADD(retry_count, wl->debugfs.rootdir);
DEBUGFS_ADD(excessive_retries, wl->debugfs.rootdir);
out:
if (ret < 0)
wl12xx_debugfs_delete_files(wl);
return ret;
}
void wl12xx_debugfs_reset(struct wl12xx *wl)
{
memset(wl->stats.fw_stats, 0, sizeof(*wl->stats.fw_stats));
wl->stats.retry_count = 0;
wl->stats.excessive_retries = 0;
}
int wl12xx_debugfs_init(struct wl12xx *wl)
{
int ret;
wl->debugfs.rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL);
if (IS_ERR(wl->debugfs.rootdir)) {
ret = PTR_ERR(wl->debugfs.rootdir);
wl->debugfs.rootdir = NULL;
goto err;
}
wl->debugfs.fw_statistics = debugfs_create_dir("fw-statistics",
wl->debugfs.rootdir);
if (IS_ERR(wl->debugfs.fw_statistics)) {
ret = PTR_ERR(wl->debugfs.fw_statistics);
wl->debugfs.fw_statistics = NULL;
goto err_root;
}
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 = wl12xx_debugfs_add_files(wl);
if (ret < 0)
goto err_file;
return 0;
err_file:
kfree(wl->stats.fw_stats);
wl->stats.fw_stats = NULL;
err_fw:
debugfs_remove(wl->debugfs.fw_statistics);
wl->debugfs.fw_statistics = NULL;
err_root:
debugfs_remove(wl->debugfs.rootdir);
wl->debugfs.rootdir = NULL;
err:
return ret;
}
void wl12xx_debugfs_exit(struct wl12xx *wl)
{
wl12xx_debugfs_delete_files(wl);
kfree(wl->stats.fw_stats);
wl->stats.fw_stats = NULL;
debugfs_remove(wl->debugfs.fw_statistics);
wl->debugfs.fw_statistics = NULL;
debugfs_remove(wl->debugfs.rootdir);
wl->debugfs.rootdir = NULL;
}

View file

@ -0,0 +1,33 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_DEBUGFS_H
#define WL12XX_DEBUGFS_H
#include "wl12xx.h"
int wl12xx_debugfs_init(struct wl12xx *wl);
void wl12xx_debugfs_exit(struct wl12xx *wl);
void wl12xx_debugfs_reset(struct wl12xx *wl);
#endif /* WL12XX_DEBUGFS_H */

View file

@ -0,0 +1,127 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "spi.h"
#include "event.h"
#include "ps.h"
static int wl12xx_event_scan_complete(struct wl12xx *wl,
struct event_mailbox *mbox)
{
wl12xx_debug(DEBUG_EVENT, "status: 0x%x, channels: %d",
mbox->scheduled_scan_status,
mbox->scheduled_scan_channels);
if (wl->scanning) {
mutex_unlock(&wl->mutex);
ieee80211_scan_completed(wl->hw, false);
mutex_lock(&wl->mutex);
wl->scanning = false;
}
return 0;
}
static void wl12xx_event_mbox_dump(struct event_mailbox *mbox)
{
wl12xx_debug(DEBUG_EVENT, "MBOX DUMP:");
wl12xx_debug(DEBUG_EVENT, "\tvector: 0x%x", mbox->events_vector);
wl12xx_debug(DEBUG_EVENT, "\tmask: 0x%x", mbox->events_mask);
}
static int wl12xx_event_process(struct wl12xx *wl, struct event_mailbox *mbox)
{
int ret;
u32 vector;
wl12xx_event_mbox_dump(mbox);
vector = mbox->events_vector & ~(mbox->events_mask);
wl12xx_debug(DEBUG_EVENT, "vector: 0x%x", vector);
if (vector & SCAN_COMPLETE_EVENT_ID) {
ret = wl12xx_event_scan_complete(wl, mbox);
if (ret < 0)
return ret;
}
if (vector & BSS_LOSE_EVENT_ID) {
wl12xx_debug(DEBUG_EVENT, "BSS_LOSE_EVENT");
if (wl->psm_requested && wl->psm) {
ret = wl12xx_ps_set_mode(wl, STATION_ACTIVE_MODE);
if (ret < 0)
return ret;
}
}
return 0;
}
int wl12xx_event_unmask(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_event_mbox_mask(wl, ~(wl->event_mask));
if (ret < 0)
return ret;
return 0;
}
void wl12xx_event_mbox_config(struct wl12xx *wl)
{
wl->mbox_ptr[0] = wl12xx_reg_read32(wl, REG_EVENT_MAILBOX_PTR);
wl->mbox_ptr[1] = wl->mbox_ptr[0] + sizeof(struct event_mailbox);
wl12xx_debug(DEBUG_EVENT, "MBOX ptrs: 0x%x 0x%x",
wl->mbox_ptr[0], wl->mbox_ptr[1]);
}
int wl12xx_event_handle(struct wl12xx *wl, u8 mbox_num)
{
struct event_mailbox mbox;
int ret;
wl12xx_debug(DEBUG_EVENT, "EVENT on mbox %d", mbox_num);
if (mbox_num > 1)
return -EINVAL;
/* first we read the mbox descriptor */
wl12xx_spi_mem_read(wl, wl->mbox_ptr[mbox_num], &mbox,
sizeof(struct event_mailbox));
/* process the descriptor */
ret = wl12xx_event_process(wl, &mbox);
if (ret < 0)
return ret;
/* then we let the firmware know it can go on...*/
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_TRIG, INTR_TRIG_EVENT_ACK);
return 0;
}

View file

@ -0,0 +1,121 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_EVENT_H__
#define __WL12XX_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 {
RESERVED1_EVENT_ID = BIT(0),
RESERVED2_EVENT_ID = BIT(1),
MEASUREMENT_START_EVENT_ID = BIT(2),
SCAN_COMPLETE_EVENT_ID = BIT(3),
CALIBRATION_COMPLETE_EVENT_ID = BIT(4),
ROAMING_TRIGGER_LOW_RSSI_EVENT_ID = BIT(5),
PS_REPORT_EVENT_ID = BIT(6),
SYNCHRONIZATION_TIMEOUT_EVENT_ID = BIT(7),
HEALTH_REPORT_EVENT_ID = BIT(8),
ACI_DETECTION_EVENT_ID = BIT(9),
DEBUG_REPORT_EVENT_ID = BIT(10),
MAC_STATUS_EVENT_ID = BIT(11),
DISCONNECT_EVENT_COMPLETE_ID = BIT(12),
JOIN_EVENT_COMPLETE_ID = BIT(13),
CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(14),
BSS_LOSE_EVENT_ID = BIT(15),
ROAMING_TRIGGER_MAX_TX_RETRY_EVENT_ID = BIT(16),
MEASUREMENT_COMPLETE_EVENT_ID = BIT(17),
AP_DISCOVERY_COMPLETE_EVENT_ID = BIT(18),
SCHEDULED_SCAN_COMPLETE_EVENT_ID = BIT(19),
PSPOLL_DELIVERY_FAILURE_EVENT_ID = BIT(20),
RESET_BSS_EVENT_ID = BIT(21),
REGAINED_BSS_EVENT_ID = BIT(22),
ROAMING_TRIGGER_REGAINED_RSSI_EVENT_ID = BIT(23),
ROAMING_TRIGGER_LOW_SNR_EVENT_ID = BIT(24),
ROAMING_TRIGGER_REGAINED_SNR_EVENT_ID = BIT(25),
DBG_EVENT_ID = BIT(26),
BT_PTA_SENSE_EVENT_ID = BIT(27),
BT_PTA_PREDICTION_EVENT_ID = BIT(28),
BT_PTA_AVALANCHE_EVENT_ID = BIT(29),
PLT_RX_CALIBRATION_COMPLETE_EVENT_ID = BIT(30),
EVENT_MBOX_ALL_EVENT_ID = 0x7fffffff,
};
struct event_debug_report {
u8 debug_event_id;
u8 num_params;
u16 pad;
u32 report_1;
u32 report_2;
u32 report_3;
} __attribute__ ((packed));
struct event_mailbox {
u32 events_vector;
u32 events_mask;
u32 reserved_1;
u32 reserved_2;
char average_rssi_level;
u8 ps_status;
u8 channel_switch_status;
u8 scheduled_scan_status;
/* Channels scanned by the scheduled scan */
u16 scheduled_scan_channels;
/* If bit 0 is set -> target's fatal error */
u16 health_report;
u16 bad_fft_counter;
u8 bt_pta_sense_info;
u8 bt_pta_protective_info;
u32 reserved;
u32 debug_report[2];
/* Number of FCS errors since last event */
u32 fcs_err_counter;
struct event_debug_report report;
u8 average_snr_level;
u8 padding[19];
} __attribute__ ((packed));
int wl12xx_event_unmask(struct wl12xx *wl);
void wl12xx_event_mbox_config(struct wl12xx *wl);
int wl12xx_event_handle(struct wl12xx *wl, u8 mbox);
#endif

View file

@ -0,0 +1,200 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "init.h"
#include "wl12xx_80211.h"
#include "acx.h"
#include "cmd.h"
int wl12xx_hw_init_hwenc_config(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_feature_cfg(wl);
if (ret < 0) {
wl12xx_warning("couldn't set feature config");
return ret;
}
ret = wl12xx_acx_default_key(wl, wl->default_key);
if (ret < 0) {
wl12xx_warning("couldn't set default key");
return ret;
}
return 0;
}
int wl12xx_hw_init_templates_config(struct wl12xx *wl)
{
int ret;
u8 partial_vbm[PARTIAL_VBM_MAX];
/* send empty templates for fw memory reservation */
ret = wl12xx_cmd_template_set(wl, CMD_PROBE_REQ, NULL,
sizeof(struct wl12xx_probe_req_template));
if (ret < 0)
return ret;
ret = wl12xx_cmd_template_set(wl, CMD_NULL_DATA, NULL,
sizeof(struct wl12xx_null_data_template));
if (ret < 0)
return ret;
ret = wl12xx_cmd_template_set(wl, CMD_PS_POLL, NULL,
sizeof(struct wl12xx_ps_poll_template));
if (ret < 0)
return ret;
ret = wl12xx_cmd_template_set(wl, CMD_QOS_NULL_DATA, NULL,
sizeof
(struct wl12xx_qos_null_data_template));
if (ret < 0)
return ret;
ret = wl12xx_cmd_template_set(wl, CMD_PROBE_RESP, NULL,
sizeof
(struct wl12xx_probe_resp_template));
if (ret < 0)
return ret;
ret = wl12xx_cmd_template_set(wl, CMD_BEACON, NULL,
sizeof
(struct wl12xx_beacon_template));
if (ret < 0)
return ret;
/* tim templates, first reserve space then allocate an empty one */
memset(partial_vbm, 0, PARTIAL_VBM_MAX);
ret = wl12xx_cmd_vbm(wl, TIM_ELE_ID, partial_vbm, PARTIAL_VBM_MAX, 0);
if (ret < 0)
return ret;
ret = wl12xx_cmd_vbm(wl, TIM_ELE_ID, partial_vbm, 1, 0);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_rx_config(struct wl12xx *wl, u32 config, u32 filter)
{
int ret;
ret = wl12xx_acx_rx_msdu_life_time(wl, RX_MSDU_LIFETIME_DEF);
if (ret < 0)
return ret;
ret = wl12xx_acx_rx_config(wl, config, filter);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_phy_config(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_pd_threshold(wl);
if (ret < 0)
return ret;
ret = wl12xx_acx_slot(wl, DEFAULT_SLOT_TIME);
if (ret < 0)
return ret;
ret = wl12xx_acx_group_address_tbl(wl);
if (ret < 0)
return ret;
ret = wl12xx_acx_service_period_timeout(wl);
if (ret < 0)
return ret;
ret = wl12xx_acx_rts_threshold(wl, RTS_THRESHOLD_DEF);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_beacon_filter(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_beacon_filter_opt(wl);
if (ret < 0)
return ret;
ret = wl12xx_acx_beacon_filter_table(wl);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_pta(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_sg_enable(wl);
if (ret < 0)
return ret;
ret = wl12xx_acx_sg_cfg(wl);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_energy_detection(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_cca_threshold(wl);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_beacon_broadcast(struct wl12xx *wl)
{
int ret;
ret = wl12xx_acx_bcn_dtim_options(wl);
if (ret < 0)
return ret;
return 0;
}
int wl12xx_hw_init_power_auth(struct wl12xx *wl)
{
return wl12xx_acx_sleep_auth(wl, WL12XX_PSM_CAM);
}

View file

@ -0,0 +1,40 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_INIT_H__
#define __WL12XX_INIT_H__
#include "wl12xx.h"
int wl12xx_hw_init_hwenc_config(struct wl12xx *wl);
int wl12xx_hw_init_templates_config(struct wl12xx *wl);
int wl12xx_hw_init_mem_config(struct wl12xx *wl);
int wl12xx_hw_init_rx_config(struct wl12xx *wl, u32 config, u32 filter);
int wl12xx_hw_init_phy_config(struct wl12xx *wl);
int wl12xx_hw_init_beacon_filter(struct wl12xx *wl);
int wl12xx_hw_init_pta(struct wl12xx *wl);
int wl12xx_hw_init_energy_detection(struct wl12xx *wl);
int wl12xx_hw_init_beacon_broadcast(struct wl12xx *wl);
int wl12xx_hw_init_power_auth(struct wl12xx *wl);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,151 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "spi.h"
#define WL12XX_WAKEUP_TIMEOUT 2000
/* Routines to toggle sleep mode while in ELP */
void wl12xx_ps_elp_sleep(struct wl12xx *wl)
{
if (wl->elp || !wl->psm)
return;
wl12xx_debug(DEBUG_PSM, "chip to elp");
wl12xx_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_SLEEP);
wl->elp = true;
}
int wl12xx_ps_elp_wakeup(struct wl12xx *wl)
{
unsigned long timeout;
u32 elp_reg;
if (!wl->elp)
return 0;
wl12xx_debug(DEBUG_PSM, "waking up chip from elp");
timeout = jiffies + msecs_to_jiffies(WL12XX_WAKEUP_TIMEOUT);
wl12xx_write32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_WAKE_UP);
elp_reg = wl12xx_read32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR);
/*
* FIXME: we should wait for irq from chip but, as a temporary
* solution to simplify locking, let's poll instead
*/
while (!(elp_reg & ELPCTRL_WLAN_READY)) {
if (time_after(jiffies, timeout)) {
wl12xx_error("elp wakeup timeout");
return -ETIMEDOUT;
}
msleep(1);
elp_reg = wl12xx_read32(wl, HW_ACCESS_ELP_CTRL_REG_ADDR);
}
wl12xx_debug(DEBUG_PSM, "wakeup time: %u ms",
jiffies_to_msecs(jiffies) -
(jiffies_to_msecs(timeout) - WL12XX_WAKEUP_TIMEOUT));
wl->elp = false;
return 0;
}
static int wl12xx_ps_set_elp(struct wl12xx *wl, bool enable)
{
int ret;
if (enable) {
wl12xx_debug(DEBUG_PSM, "sleep auth psm/elp");
/*
* FIXME: we should PSM_ELP, but because of firmware wakeup
* problems let's use only PSM_PS
*/
ret = wl12xx_acx_sleep_auth(wl, WL12XX_PSM_PS);
if (ret < 0)
return ret;
wl12xx_ps_elp_sleep(wl);
} else {
wl12xx_debug(DEBUG_PSM, "sleep auth cam");
/*
* When the target is in ELP, we can only
* access the ELP control register. Thus,
* we have to wake the target up before
* changing the power authorization.
*/
wl12xx_ps_elp_wakeup(wl);
ret = wl12xx_acx_sleep_auth(wl, WL12XX_PSM_CAM);
if (ret < 0)
return ret;
}
return 0;
}
int wl12xx_ps_set_mode(struct wl12xx *wl, enum acx_ps_mode mode)
{
int ret;
switch (mode) {
case STATION_POWER_SAVE_MODE:
wl12xx_debug(DEBUG_PSM, "entering psm");
ret = wl12xx_cmd_ps_mode(wl, STATION_POWER_SAVE_MODE);
if (ret < 0)
return ret;
ret = wl12xx_ps_set_elp(wl, true);
if (ret < 0)
return ret;
wl->psm = 1;
break;
case STATION_ACTIVE_MODE:
default:
wl12xx_debug(DEBUG_PSM, "leaving psm");
ret = wl12xx_ps_set_elp(wl, false);
if (ret < 0)
return ret;
ret = wl12xx_cmd_ps_mode(wl, STATION_ACTIVE_MODE);
if (ret < 0)
return ret;
wl->psm = 0;
break;
}
return ret;
}

View file

@ -0,0 +1,36 @@
#ifndef __WL12XX_PS_H__
#define __WL12XX_PS_H__
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "acx.h"
int wl12xx_ps_set_mode(struct wl12xx *wl, enum acx_ps_mode mode);
void wl12xx_ps_elp_sleep(struct wl12xx *wl);
int wl12xx_ps_elp_wakeup(struct wl12xx *wl);
#endif /* __WL12XX_PS_H__ */

View file

@ -0,0 +1,745 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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>
#include "wl12xx.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
/* 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
/*
* Interrupt registers.
* 64 bit interrupt sources registers ws ced.
* sme interupts were removed and new ones were added.
* Order was changed.
*/
#define FIQ_MASK (REGISTERS_BASE + 0x0400)
#define FIQ_MASK_L (REGISTERS_BASE + 0x0400)
#define FIQ_MASK_H (REGISTERS_BASE + 0x0404)
#define FIQ_MASK_SET (REGISTERS_BASE + 0x0408)
#define FIQ_MASK_SET_L (REGISTERS_BASE + 0x0408)
#define FIQ_MASK_SET_H (REGISTERS_BASE + 0x040C)
#define FIQ_MASK_CLR (REGISTERS_BASE + 0x0410)
#define FIQ_MASK_CLR_L (REGISTERS_BASE + 0x0410)
#define FIQ_MASK_CLR_H (REGISTERS_BASE + 0x0414)
#define IRQ_MASK (REGISTERS_BASE + 0x0418)
#define IRQ_MASK_L (REGISTERS_BASE + 0x0418)
#define IRQ_MASK_H (REGISTERS_BASE + 0x041C)
#define IRQ_MASK_SET (REGISTERS_BASE + 0x0420)
#define IRQ_MASK_SET_L (REGISTERS_BASE + 0x0420)
#define IRQ_MASK_SET_H (REGISTERS_BASE + 0x0424)
#define IRQ_MASK_CLR (REGISTERS_BASE + 0x0428)
#define IRQ_MASK_CLR_L (REGISTERS_BASE + 0x0428)
#define IRQ_MASK_CLR_H (REGISTERS_BASE + 0x042C)
#define ECPU_MASK (REGISTERS_BASE + 0x0448)
#define FIQ_STS_L (REGISTERS_BASE + 0x044C)
#define FIQ_STS_H (REGISTERS_BASE + 0x0450)
#define IRQ_STS_L (REGISTERS_BASE + 0x0454)
#define IRQ_STS_H (REGISTERS_BASE + 0x0458)
#define INT_STS_ND (REGISTERS_BASE + 0x0464)
#define INT_STS_RAW_L (REGISTERS_BASE + 0x0464)
#define INT_STS_RAW_H (REGISTERS_BASE + 0x0468)
#define INT_STS_CLR (REGISTERS_BASE + 0x04B4)
#define INT_STS_CLR_L (REGISTERS_BASE + 0x04B4)
#define INT_STS_CLR_H (REGISTERS_BASE + 0x04B8)
#define INT_ACK (REGISTERS_BASE + 0x046C)
#define INT_ACK_L (REGISTERS_BASE + 0x046C)
#define INT_ACK_H (REGISTERS_BASE + 0x0470)
#define INT_TRIG (REGISTERS_BASE + 0x0474)
#define INT_TRIG_L (REGISTERS_BASE + 0x0474)
#define INT_TRIG_H (REGISTERS_BASE + 0x0478)
#define HOST_STS_L (REGISTERS_BASE + 0x045C)
#define HOST_STS_H (REGISTERS_BASE + 0x0460)
#define HOST_MASK (REGISTERS_BASE + 0x0430)
#define HOST_MASK_L (REGISTERS_BASE + 0x0430)
#define HOST_MASK_H (REGISTERS_BASE + 0x0434)
#define HOST_MASK_SET (REGISTERS_BASE + 0x0438)
#define HOST_MASK_SET_L (REGISTERS_BASE + 0x0438)
#define HOST_MASK_SET_H (REGISTERS_BASE + 0x043C)
#define HOST_MASK_CLR (REGISTERS_BASE + 0x0440)
#define HOST_MASK_CLR_L (REGISTERS_BASE + 0x0440)
#define HOST_MASK_CLR_H (REGISTERS_BASE + 0x0444)
/* Host Interrupts*/
#define HINT_MASK (REGISTERS_BASE + 0x0494)
#define HINT_MASK_SET (REGISTERS_BASE + 0x0498)
#define HINT_MASK_CLR (REGISTERS_BASE + 0x049C)
#define HINT_STS_ND_MASKED (REGISTERS_BASE + 0x04A0)
/*1150 spec calls this HINT_STS_RAW*/
#define HINT_STS_ND (REGISTERS_BASE + 0x04B0)
#define HINT_STS_CLR (REGISTERS_BASE + 0x04A4)
#define HINT_ACK (REGISTERS_BASE + 0x04A8)
#define HINT_TRIG (REGISTERS_BASE + 0x04AC)
/* Device Configuration registers*/
#define SOR_CFG (REGISTERS_BASE + 0x0800)
#define ECPU_CTRL (REGISTERS_BASE + 0x0804)
#define HI_CFG (REGISTERS_BASE + 0x0808)
#define EE_START (REGISTERS_BASE + 0x080C)
#define CHIP_ID_B (REGISTERS_BASE + 0x5674)
#define CHIP_ID_1251_PG10 (0x7010101)
#define CHIP_ID_1251_PG11 (0x7020101)
#define CHIP_ID_1251_PG12 (0x7030101)
#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)
enum wl12xx_acx_int_reg {
ACX_REG_INTERRUPT_TRIG,
ACX_REG_INTERRUPT_TRIG_H,
/*=============================================
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
*==============================================*/
ACX_REG_INTERRUPT_MASK,
/*=============================================
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).
==============================================*/
ACX_REG_HINT_MASK_SET,
/*=============================================
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).
=============================================*/
ACX_REG_HINT_MASK_CLR,
/*=============================================
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.
=============================================*/
ACX_REG_INTERRUPT_NO_CLEAR,
/*=============================================
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.
==============================================*/
ACX_REG_INTERRUPT_CLEAR,
/*=============================================
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)
==============================================*/
ACX_REG_INTERRUPT_ACK,
/*===============================================
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.
===============================================*/
ACX_REG_SLV_SOFT_RESET,
/*===============================================
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
*================================================*/
ACX_REG_EE_START,
/* 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
===============================================*/
ACX_REG_ECPU_CONTROL,
ACX_REG_TABLE_LEN
};
#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)
/*===============================================
Phy regs
===============================================*/
#define ACX_PHY_ADDR_REG SBB_ADDR
#define ACX_PHY_DATA_REG SBB_DATA
#define ACX_PHY_CTRL_REG SBB_CTL
#define ACX_PHY_REG_WR_MASK 0x00000001ul
#define ACX_PHY_REG_RD_MASK 0x00000002ul
/*===============================================
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
/*
* Indirect slave register/memory registers
* ----------------------------------------
*/
#define HW_SLAVE_REG_ADDR_REG 0x00000004
#define HW_SLAVE_REG_DATA_REG 0x00000008
#define HW_SLAVE_REG_CTRL_REG 0x0000000c
#define SLAVE_AUTO_INC 0x00010000
#define SLAVE_NO_AUTO_INC 0x00000000
#define SLAVE_HOST_LITTLE_ENDIAN 0x00000000
#define HW_SLAVE_MEM_ADDR_REG SLV_MEM_ADDR
#define HW_SLAVE_MEM_DATA_REG SLV_MEM_DATA
#define HW_SLAVE_MEM_CTRL_REG SLV_MEM_CTL
#define HW_SLAVE_MEM_ENDIAN_REG SLV_END_CTL
#define HW_FUNC_EVENT_INT_EN 0x8000
#define HW_FUNC_EVENT_MASK_REG 0x00000034
#define ACX_MAC_TIMESTAMP_REG (MAC_TIMESTAMP)
/*===============================================
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
/*
* NOTE: USE_ACTIVE_HIGH compilation flag should be defined in makefile
* for platforms using active high interrupt level
*/
#ifdef USE_ACTIVE_HIGH
#define HI_CFG_DEF_VAL \
(HI_CFG_UART_ENABLE | \
HI_CFG_RST232_ENABLE | \
HI_CFG_CLOCK_REQ_SELECT | \
HI_CFG_HOST_INT_ENABLE)
#else
#define HI_CFG_DEF_VAL \
(HI_CFG_UART_ENABLE | \
HI_CFG_RST232_ENABLE | \
HI_CFG_CLOCK_REQ_SELECT | \
HI_CFG_HOST_INT_ENABLE)
#endif
#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
};
enum {
NO_RATE = 0,
RATE_1MBPS = 0x0A,
RATE_2MBPS = 0x14,
RATE_5_5MBPS = 0x37,
RATE_6MBPS = 0x0B,
RATE_9MBPS = 0x0F,
RATE_11MBPS = 0x6E,
RATE_12MBPS = 0x0A,
RATE_18MBPS = 0x0E,
RATE_22MBPS = 0xDC,
RATE_24MBPS = 0x09,
RATE_36MBPS = 0x0D,
RATE_48MBPS = 0x08,
RATE_54MBPS = 0x0C
};
enum {
RATE_INDEX_1MBPS = 0,
RATE_INDEX_2MBPS = 1,
RATE_INDEX_5_5MBPS = 2,
RATE_INDEX_6MBPS = 3,
RATE_INDEX_9MBPS = 4,
RATE_INDEX_11MBPS = 5,
RATE_INDEX_12MBPS = 6,
RATE_INDEX_18MBPS = 7,
RATE_INDEX_22MBPS = 8,
RATE_INDEX_24MBPS = 9,
RATE_INDEX_36MBPS = 10,
RATE_INDEX_48MBPS = 11,
RATE_INDEX_54MBPS = 12,
RATE_INDEX_MAX = RATE_INDEX_54MBPS,
MAX_RATE_INDEX,
INVALID_RATE_INDEX = MAX_RATE_INDEX,
RATE_INDEX_ENUM_MAX_SIZE = 0x7FFFFFFF
};
enum {
RATE_MASK_1MBPS = 0x1,
RATE_MASK_2MBPS = 0x2,
RATE_MASK_5_5MBPS = 0x4,
RATE_MASK_11MBPS = 0x20,
};
#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.
******************************************************************************/
#define TNETW1251_CHIP_ID_PG1_0 0x07010101
#define TNETW1251_CHIP_ID_PG1_1 0x07020101
#define TNETW1251_CHIP_ID_PG1_2 0x07030101
/*************************************************************************
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

View file

@ -0,0 +1,208 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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/skbuff.h>
#include <net/mac80211.h>
#include "wl12xx.h"
#include "reg.h"
#include "spi.h"
#include "rx.h"
static void wl12xx_rx_header(struct wl12xx *wl,
struct wl12xx_rx_descriptor *desc)
{
u32 rx_packet_ring_addr;
rx_packet_ring_addr = wl->data_path->rx_packet_ring_addr;
if (wl->rx_current_buffer)
rx_packet_ring_addr += wl->data_path->rx_packet_ring_chunk_size;
wl12xx_spi_mem_read(wl, rx_packet_ring_addr, desc,
sizeof(struct wl12xx_rx_descriptor));
}
static void wl12xx_rx_status(struct wl12xx *wl,
struct wl12xx_rx_descriptor *desc,
struct ieee80211_rx_status *status,
u8 beacon)
{
memset(status, 0, sizeof(struct ieee80211_rx_status));
status->band = IEEE80211_BAND_2GHZ;
status->mactime = desc->timestamp;
/*
* The rx status timestamp is a 32 bits value while the TSF is a
* 64 bits one.
* For IBSS merging, TSF is mandatory, so we have to get it
* somehow, so we ask for ACX_TSF_INFO.
* That could be moved to the get_tsf() hook, but unfortunately,
* this one must be atomic, while our SPI routines can sleep.
*/
if ((wl->bss_type == BSS_TYPE_IBSS) && beacon) {
u64 mactime;
int ret;
struct wl12xx_command cmd;
struct acx_tsf_info *tsf_info;
memset(&cmd, 0, sizeof(cmd));
ret = wl12xx_cmd_interrogate(wl, ACX_TSF_INFO,
sizeof(struct acx_tsf_info),
&cmd);
if (ret < 0) {
wl12xx_warning("ACX_FW_REV interrogate failed");
return;
}
tsf_info = (struct acx_tsf_info *)&(cmd.parameters);
mactime = tsf_info->current_tsf_lsb |
(tsf_info->current_tsf_msb << 31);
status->mactime = mactime;
}
status->signal = desc->rssi;
status->qual = (desc->rssi - WL12XX_RX_MIN_RSSI) * 100 /
(WL12XX_RX_MAX_RSSI - WL12XX_RX_MIN_RSSI);
status->qual = min(status->qual, 100);
status->qual = max(status->qual, 0);
/*
* FIXME: guessing that snr needs to be divided by two, otherwise
* the values don't make any sense
*/
status->noise = desc->rssi - desc->snr / 2;
status->freq = ieee80211_channel_to_frequency(desc->channel);
status->flag |= RX_FLAG_TSFT;
if (desc->flags & RX_DESC_ENCRYPTION_MASK) {
status->flag |= RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED;
if (likely(!(desc->flags & RX_DESC_DECRYPT_FAIL)))
status->flag |= RX_FLAG_DECRYPTED;
if (unlikely(desc->flags & RX_DESC_MIC_FAIL))
status->flag |= RX_FLAG_MMIC_ERROR;
}
if (unlikely(!(desc->flags & RX_DESC_VALID_FCS)))
status->flag |= RX_FLAG_FAILED_FCS_CRC;
/* FIXME: set status->rate_idx */
}
static void wl12xx_rx_body(struct wl12xx *wl,
struct wl12xx_rx_descriptor *desc)
{
struct sk_buff *skb;
struct ieee80211_rx_status status;
u8 *rx_buffer, beacon = 0;
u16 length, *fc;
u32 curr_id, last_id_inc, rx_packet_ring_addr;
length = WL12XX_RX_ALIGN(desc->length - PLCP_HEADER_LENGTH);
curr_id = (desc->flags & RX_DESC_SEQNUM_MASK) >> RX_DESC_PACKETID_SHIFT;
last_id_inc = (wl->rx_last_id + 1) % (RX_MAX_PACKET_ID + 1);
if (last_id_inc != curr_id) {
wl12xx_warning("curr ID:%d, last ID inc:%d",
curr_id, last_id_inc);
wl->rx_last_id = curr_id;
} else {
wl->rx_last_id = last_id_inc;
}
rx_packet_ring_addr = wl->data_path->rx_packet_ring_addr +
sizeof(struct wl12xx_rx_descriptor) + 20;
if (wl->rx_current_buffer)
rx_packet_ring_addr += wl->data_path->rx_packet_ring_chunk_size;
skb = dev_alloc_skb(length);
if (!skb) {
wl12xx_error("Couldn't allocate RX frame");
return;
}
rx_buffer = skb_put(skb, length);
wl12xx_spi_mem_read(wl, rx_packet_ring_addr, rx_buffer, length);
/* The actual lenght doesn't include the target's alignment */
skb->len = desc->length - PLCP_HEADER_LENGTH;
fc = (u16 *)skb->data;
if ((*fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_BEACON)
beacon = 1;
wl12xx_rx_status(wl, desc, &status, beacon);
wl12xx_debug(DEBUG_RX, "rx skb 0x%p: %d B %s", skb, skb->len,
beacon ? "beacon" : "");
ieee80211_rx(wl->hw, skb, &status);
}
static void wl12xx_rx_ack(struct wl12xx *wl)
{
u32 data, addr;
if (wl->rx_current_buffer) {
addr = ACX_REG_INTERRUPT_TRIG_H;
data = INTR_TRIG_RX_PROC1;
} else {
addr = ACX_REG_INTERRUPT_TRIG;
data = INTR_TRIG_RX_PROC0;
}
wl12xx_reg_write32(wl, addr, data);
/* Toggle buffer ring */
wl->rx_current_buffer = !wl->rx_current_buffer;
}
void wl12xx_rx(struct wl12xx *wl)
{
struct wl12xx_rx_descriptor rx_desc;
if (wl->state != WL12XX_STATE_ON)
return;
/* We first read the frame's header */
wl12xx_rx_header(wl, &rx_desc);
/* Now we can read the body */
wl12xx_rx_body(wl, &rx_desc);
/* Finally, we need to ACK the RX */
wl12xx_rx_ack(wl);
return;
}

View file

@ -0,0 +1,122 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_RX_H__
#define __WL12XX_RX_H__
#include <linux/bitops.h>
/*
* RX PATH
*
* The Rx path uses a double buffer and an rx_contro structure, each located
* at a fixed address in the device memory. The host keeps track of which
* buffer is available and alternates between them on a per packet basis.
* The size of each of the two buffers is large enough to hold the longest
* 802.3 packet.
* The RX path goes like that:
* 1) The target generates an interrupt each time a new packet is received.
* There are 2 RX interrupts, one for each buffer.
* 2) The host reads the received packet from one of the double buffers.
* 3) The host triggers a target interrupt.
* 4) The target prepares the next RX packet.
*/
#define WL12XX_RX_MAX_RSSI -30
#define WL12XX_RX_MIN_RSSI -95
#define WL12XX_RX_ALIGN_TO 4
#define WL12XX_RX_ALIGN(len) (((len) + WL12XX_RX_ALIGN_TO - 1) & \
~(WL12XX_RX_ALIGN_TO - 1))
#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 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
struct wl12xx_rx_descriptor {
u32 timestamp; /* In microseconds */
u16 length; /* Paylod length, including headers */
u16 flags;
/*
* 0 - 802.11
* 1 - 802.3
* 2 - IP
* 3 - Raw Codec
*/
u8 type;
/*
* Recevied Rate:
* 0x0A - 1MBPS
* 0x14 - 2MBPS
* 0x37 - 5_5MBPS
* 0x0B - 6MBPS
* 0x0F - 9MBPS
* 0x6E - 11MBPS
* 0x0A - 12MBPS
* 0x0E - 18MBPS
* 0xDC - 22MBPS
* 0x09 - 24MBPS
* 0x0D - 36MBPS
* 0x08 - 48MBPS
* 0x0C - 54MBPS
*/
u8 rate;
u8 mod_pre; /* Modulation and preamble */
u8 channel;
/*
* 0 - 2.4 Ghz
* 1 - 5 Ghz
*/
u8 band;
s8 rssi; /* in dB */
u8 rcpi; /* in dB */
u8 snr; /* in dB */
} __attribute__ ((packed));
void wl12xx_rx(struct wl12xx *wl);
#endif

View file

@ -0,0 +1,358 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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/crc7.h>
#include <linux/spi/spi.h>
#include "wl12xx.h"
#include "wl12xx_80211.h"
#include "reg.h"
#include "spi.h"
#include "ps.h"
static int wl12xx_translate_reg_addr(struct wl12xx *wl, int addr)
{
/* If the address is lower than REGISTERS_BASE, it means that this is
* a chip-specific register address, so look it up in the registers
* table */
if (addr < REGISTERS_BASE) {
/* Make sure we don't go over the table */
if (addr >= ACX_REG_TABLE_LEN) {
wl12xx_error("address out of range (%d)", addr);
return -EINVAL;
}
addr = wl->chip.acx_reg_table[addr];
}
return addr - wl->physical_reg_addr + wl->virtual_reg_addr;
}
static int wl12xx_translate_mem_addr(struct wl12xx *wl, int addr)
{
return addr - wl->physical_mem_addr + wl->virtual_mem_addr;
}
void wl12xx_spi_reset(struct wl12xx *wl)
{
u8 *cmd;
struct spi_transfer t;
struct spi_message m;
cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL);
if (!cmd) {
wl12xx_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->spi, &m);
wl12xx_dump(DEBUG_SPI, "spi reset -> ", cmd, WSPI_INIT_CMD_LEN);
}
void wl12xx_spi_init(struct wl12xx *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) {
wl12xx_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->spi, &m);
wl12xx_dump(DEBUG_SPI, "spi init -> ", cmd, WSPI_INIT_CMD_LEN);
}
/* Set the SPI partitions to access the chip addresses
*
* There are two VIRTUAL (SPI) partitions (the memory partition and the
* registers partition), which are mapped to two different areas of the
* PHYSICAL (hardware) memory. This function also makes other checks to
* ensure that the partitions are not overlapping. In the diagram below, the
* memory partition comes before the register partition, but the opposite is
* also supported.
*
* PHYSICAL address
* space
*
* | |
* ...+----+--> mem_start
* VIRTUAL address ... | |
* space ... | | [PART_0]
* ... | |
* 0x00000000 <--+----+... ...+----+--> mem_start + mem_size
* | | ... | |
* |MEM | ... | |
* | | ... | |
* part_size <--+----+... | | {unused area)
* | | ... | |
* |REG | ... | |
* part_size | | ... | |
* + <--+----+... ...+----+--> reg_start
* reg_size ... | |
* ... | | [PART_1]
* ... | |
* ...+----+--> reg_start + reg_size
* | |
*
*/
void wl12xx_set_partition(struct wl12xx *wl,
u32 mem_start, u32 mem_size,
u32 reg_start, u32 reg_size)
{
u8 tx_buf[sizeof(u32) + 2 * sizeof(struct wl12xx_partition)];
struct wl12xx_partition *partition;
struct spi_transfer t;
struct spi_message m;
u32 *cmd;
size_t len;
int addr;
spi_message_init(&m);
memset(&t, 0, sizeof(t));
memset(tx_buf, 0, sizeof(tx_buf));
cmd = (u32 *) tx_buf;
partition = (struct wl12xx_partition *) (tx_buf + sizeof(u32));
addr = HW_ACCESS_PART0_SIZE_ADDR;
len = 2 * sizeof(struct wl12xx_partition);
*cmd |= WSPI_CMD_WRITE;
*cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH;
*cmd |= addr & WSPI_CMD_BYTE_ADDR;
wl12xx_debug(DEBUG_SPI, "mem_start %08X mem_size %08X",
mem_start, mem_size);
wl12xx_debug(DEBUG_SPI, "reg_start %08X reg_size %08X",
reg_start, reg_size);
/* Make sure that the two partitions together don't exceed the
* address range */
if ((mem_size + reg_size) > HW_ACCESS_MEMORY_MAX_RANGE) {
wl12xx_debug(DEBUG_SPI, "Total size exceeds maximum virtual"
" address range. Truncating partition[0].");
mem_size = HW_ACCESS_MEMORY_MAX_RANGE - reg_size;
wl12xx_debug(DEBUG_SPI, "mem_start %08X mem_size %08X",
mem_start, mem_size);
wl12xx_debug(DEBUG_SPI, "reg_start %08X reg_size %08X",
reg_start, reg_size);
}
if ((mem_start < reg_start) &&
((mem_start + mem_size) > reg_start)) {
/* Guarantee that the memory partition doesn't overlap the
* registers partition */
wl12xx_debug(DEBUG_SPI, "End of partition[0] is "
"overlapping partition[1]. Adjusted.");
mem_size = reg_start - mem_start;
wl12xx_debug(DEBUG_SPI, "mem_start %08X mem_size %08X",
mem_start, mem_size);
wl12xx_debug(DEBUG_SPI, "reg_start %08X reg_size %08X",
reg_start, reg_size);
} else if ((reg_start < mem_start) &&
((reg_start + reg_size) > mem_start)) {
/* Guarantee that the register partition doesn't overlap the
* memory partition */
wl12xx_debug(DEBUG_SPI, "End of partition[1] is"
" overlapping partition[0]. Adjusted.");
reg_size = mem_start - reg_start;
wl12xx_debug(DEBUG_SPI, "mem_start %08X mem_size %08X",
mem_start, mem_size);
wl12xx_debug(DEBUG_SPI, "reg_start %08X reg_size %08X",
reg_start, reg_size);
}
partition[0].start = mem_start;
partition[0].size = mem_size;
partition[1].start = reg_start;
partition[1].size = reg_size;
wl->physical_mem_addr = mem_start;
wl->physical_reg_addr = reg_start;
wl->virtual_mem_addr = 0;
wl->virtual_reg_addr = mem_size;
t.tx_buf = tx_buf;
t.len = sizeof(tx_buf);
spi_message_add_tail(&t, &m);
spi_sync(wl->spi, &m);
}
void wl12xx_spi_read(struct wl12xx *wl, int addr, void *buf,
size_t len)
{
struct spi_transfer t[3];
struct spi_message m;
char busy_buf[TNETWIF_READ_OFFSET_BYTES];
u32 cmd;
cmd = 0;
cmd |= WSPI_CMD_READ;
cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH;
cmd |= addr & WSPI_CMD_BYTE_ADDR;
spi_message_init(&m);
memset(t, 0, sizeof(t));
t[0].tx_buf = &cmd;
t[0].len = 4;
spi_message_add_tail(&t[0], &m);
/* Busy and non busy words read */
t[1].rx_buf = busy_buf;
t[1].len = TNETWIF_READ_OFFSET_BYTES;
spi_message_add_tail(&t[1], &m);
t[2].rx_buf = buf;
t[2].len = len;
spi_message_add_tail(&t[2], &m);
spi_sync(wl->spi, &m);
/* FIXME: check busy words */
wl12xx_dump(DEBUG_SPI, "spi_read cmd -> ", &cmd, sizeof(cmd));
wl12xx_dump(DEBUG_SPI, "spi_read buf <- ", buf, len);
}
void wl12xx_spi_write(struct wl12xx *wl, int addr, void *buf,
size_t len)
{
struct spi_transfer t[2];
struct spi_message m;
u32 cmd;
cmd = 0;
cmd |= WSPI_CMD_WRITE;
cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH;
cmd |= addr & WSPI_CMD_BYTE_ADDR;
spi_message_init(&m);
memset(t, 0, sizeof(t));
t[0].tx_buf = &cmd;
t[0].len = sizeof(cmd);
spi_message_add_tail(&t[0], &m);
t[1].tx_buf = buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
spi_sync(wl->spi, &m);
wl12xx_dump(DEBUG_SPI, "spi_write cmd -> ", &cmd, sizeof(cmd));
wl12xx_dump(DEBUG_SPI, "spi_write buf -> ", buf, len);
}
void wl12xx_spi_mem_read(struct wl12xx *wl, int addr, void *buf,
size_t len)
{
int physical;
physical = wl12xx_translate_mem_addr(wl, addr);
wl12xx_spi_read(wl, physical, buf, len);
}
void wl12xx_spi_mem_write(struct wl12xx *wl, int addr, void *buf,
size_t len)
{
int physical;
physical = wl12xx_translate_mem_addr(wl, addr);
wl12xx_spi_write(wl, physical, buf, len);
}
u32 wl12xx_mem_read32(struct wl12xx *wl, int addr)
{
return wl12xx_read32(wl, wl12xx_translate_mem_addr(wl, addr));
}
void wl12xx_mem_write32(struct wl12xx *wl, int addr, u32 val)
{
wl12xx_write32(wl, wl12xx_translate_mem_addr(wl, addr), val);
}
u32 wl12xx_reg_read32(struct wl12xx *wl, int addr)
{
return wl12xx_read32(wl, wl12xx_translate_reg_addr(wl, addr));
}
void wl12xx_reg_write32(struct wl12xx *wl, int addr, u32 val)
{
wl12xx_write32(wl, wl12xx_translate_reg_addr(wl, addr), val);
}

View file

@ -0,0 +1,109 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_SPI_H__
#define __WL12XX_SPI_H__
#include "cmd.h"
#include "acx.h"
#include "reg.h"
#define HW_ACCESS_MEMORY_MAX_RANGE 0x1FFC0
#define HW_ACCESS_PART0_SIZE_ADDR 0x1FFC0
#define HW_ACCESS_PART0_START_ADDR 0x1FFC4
#define HW_ACCESS_PART1_SIZE_ADDR 0x1FFC8
#define HW_ACCESS_PART1_START_ADDR 0x1FFCC
#define HW_ACCESS_REGISTER_SIZE 4
#define HW_ACCESS_PRAM_MAX_RANGE 0x3c000
#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 TNETWIF_READ_OFFSET_BYTES 8
#define HW_ACCESS_WSPI_FIXED_BUSY_LEN \
((TNETWIF_READ_OFFSET_BYTES - 4) / sizeof(u32))
#define HW_ACCESS_WSPI_INIT_CMD_MASK 0
/* Raw target IO, address is not translated */
void wl12xx_spi_read(struct wl12xx *wl, int addr, void *buf, size_t len);
void wl12xx_spi_write(struct wl12xx *wl, int addr, void *buf, size_t len);
/* Memory target IO, address is tranlated to partition 0 */
void wl12xx_spi_mem_read(struct wl12xx *wl, int addr, void *buf, size_t len);
void wl12xx_spi_mem_write(struct wl12xx *wl, int addr, void *buf, size_t len);
u32 wl12xx_mem_read32(struct wl12xx *wl, int addr);
void wl12xx_mem_write32(struct wl12xx *wl, int addr, u32 val);
/* Registers IO */
u32 wl12xx_reg_read32(struct wl12xx *wl, int addr);
void wl12xx_reg_write32(struct wl12xx *wl, int addr, u32 val);
/* INIT and RESET words */
void wl12xx_spi_reset(struct wl12xx *wl);
void wl12xx_spi_init(struct wl12xx *wl);
void wl12xx_set_partition(struct wl12xx *wl,
u32 part_start, u32 part_size,
u32 reg_start, u32 reg_size);
static inline u32 wl12xx_read32(struct wl12xx *wl, int addr)
{
u32 response;
wl12xx_spi_read(wl, addr, &response, sizeof(u32));
return response;
}
static inline void wl12xx_write32(struct wl12xx *wl, int addr, u32 val)
{
wl12xx_spi_write(wl, addr, &val, sizeof(u32));
}
#endif /* __WL12XX_SPI_H__ */

View file

@ -0,0 +1,557 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "wl12xx.h"
#include "reg.h"
#include "spi.h"
#include "tx.h"
#include "ps.h"
static bool wl12xx_tx_double_buffer_busy(struct wl12xx *wl, u32 data_out_count)
{
int used, data_in_count;
data_in_count = wl->data_in_count;
if (data_in_count < data_out_count)
/* data_in_count has wrapped */
data_in_count += TX_STATUS_DATA_OUT_COUNT_MASK + 1;
used = data_in_count - data_out_count;
WARN_ON(used < 0);
WARN_ON(used > DP_TX_PACKET_RING_CHUNK_NUM);
if (used >= DP_TX_PACKET_RING_CHUNK_NUM)
return true;
else
return false;
}
static int wl12xx_tx_path_status(struct wl12xx *wl)
{
u32 status, addr, data_out_count;
bool busy;
addr = wl->data_path->tx_control_addr;
status = wl12xx_mem_read32(wl, addr);
data_out_count = status & TX_STATUS_DATA_OUT_COUNT_MASK;
busy = wl12xx_tx_double_buffer_busy(wl, data_out_count);
if (busy)
return -EBUSY;
return 0;
}
static int wl12xx_tx_id(struct wl12xx *wl, struct sk_buff *skb)
{
int i;
for (i = 0; i < FW_TX_CMPLT_BLOCK_SIZE; i++)
if (wl->tx_frames[i] == NULL) {
wl->tx_frames[i] = skb;
return i;
}
return -EBUSY;
}
static void wl12xx_tx_control(struct tx_double_buffer_desc *tx_hdr,
struct ieee80211_tx_info *control, u16 fc)
{
*(u16 *)&tx_hdr->control = 0;
tx_hdr->control.rate_policy = 0;
/* 802.11 packets */
tx_hdr->control.packet_type = 0;
if (control->flags & IEEE80211_TX_CTL_NO_ACK)
tx_hdr->control.ack_policy = 1;
tx_hdr->control.tx_complete = 1;
if ((fc & IEEE80211_FTYPE_DATA) &&
((fc & IEEE80211_STYPE_QOS_DATA) ||
(fc & IEEE80211_STYPE_QOS_NULLFUNC)))
tx_hdr->control.qos = 1;
}
/* RSN + MIC = 8 + 8 = 16 bytes (worst case - AES). */
#define MAX_MSDU_SECURITY_LENGTH 16
#define MAX_MPDU_SECURITY_LENGTH 16
#define WLAN_QOS_HDR_LEN 26
#define MAX_MPDU_HEADER_AND_SECURITY (MAX_MPDU_SECURITY_LENGTH + \
WLAN_QOS_HDR_LEN)
#define HW_BLOCK_SIZE 252
static void wl12xx_tx_frag_block_num(struct tx_double_buffer_desc *tx_hdr)
{
u16 payload_len, frag_threshold, mem_blocks;
u16 num_mpdus, mem_blocks_per_frag;
frag_threshold = IEEE80211_MAX_FRAG_THRESHOLD;
tx_hdr->frag_threshold = cpu_to_le16(frag_threshold);
payload_len = tx_hdr->length + MAX_MSDU_SECURITY_LENGTH;
if (payload_len > frag_threshold) {
mem_blocks_per_frag =
((frag_threshold + MAX_MPDU_HEADER_AND_SECURITY) /
HW_BLOCK_SIZE) + 1;
num_mpdus = payload_len / frag_threshold;
mem_blocks = num_mpdus * mem_blocks_per_frag;
payload_len -= num_mpdus * frag_threshold;
num_mpdus++;
} else {
mem_blocks_per_frag = 0;
mem_blocks = 0;
num_mpdus = 1;
}
mem_blocks += (payload_len / HW_BLOCK_SIZE) + 1;
if (num_mpdus > 1)
mem_blocks += min(num_mpdus, mem_blocks_per_frag);
tx_hdr->num_mem_blocks = mem_blocks;
}
static int wl12xx_tx_fill_hdr(struct wl12xx *wl, struct sk_buff *skb,
struct ieee80211_tx_info *control)
{
struct tx_double_buffer_desc *tx_hdr;
struct ieee80211_rate *rate;
int id;
u16 fc;
if (!skb)
return -EINVAL;
id = wl12xx_tx_id(wl, skb);
if (id < 0)
return id;
fc = *(u16 *)skb->data;
tx_hdr = (struct tx_double_buffer_desc *) skb_push(skb,
sizeof(*tx_hdr));
tx_hdr->length = cpu_to_le16(skb->len - sizeof(*tx_hdr));
rate = ieee80211_get_tx_rate(wl->hw, control);
tx_hdr->rate = cpu_to_le16(rate->hw_value);
tx_hdr->expiry_time = cpu_to_le32(1 << 16);
tx_hdr->id = id;
/* FIXME: how to get the correct queue id? */
tx_hdr->xmit_queue = 0;
wl12xx_tx_control(tx_hdr, control, fc);
wl12xx_tx_frag_block_num(tx_hdr);
return 0;
}
/* We copy the packet to the target */
static int wl12xx_tx_send_packet(struct wl12xx *wl, struct sk_buff *skb,
struct ieee80211_tx_info *control)
{
struct tx_double_buffer_desc *tx_hdr;
int len;
u32 addr;
if (!skb)
return -EINVAL;
tx_hdr = (struct tx_double_buffer_desc *) skb->data;
if (control->control.hw_key &&
control->control.hw_key->alg == ALG_TKIP) {
int hdrlen;
u16 fc;
u8 *pos;
fc = *(u16 *)(skb->data + sizeof(*tx_hdr));
tx_hdr->length += WL12XX_TKIP_IV_SPACE;
hdrlen = ieee80211_hdrlen(fc);
pos = skb_push(skb, WL12XX_TKIP_IV_SPACE);
memmove(pos, pos + WL12XX_TKIP_IV_SPACE,
sizeof(*tx_hdr) + hdrlen);
}
/* Revisit. This is a workaround for getting non-aligned packets.
This happens at least with EAPOL packets from the user space.
Our DMA requires packets to be aligned on a 4-byte boundary.
*/
if (unlikely((long)skb->data & 0x03)) {
int offset = (4 - (long)skb->data) & 0x03;
wl12xx_debug(DEBUG_TX, "skb offset %d", offset);
/* check whether the current skb can be used */
if (!skb_cloned(skb) && (skb_tailroom(skb) >= offset)) {
unsigned char *src = skb->data;
/* align the buffer on a 4-byte boundary */
skb_reserve(skb, offset);
memmove(skb->data, src, skb->len);
} else {
wl12xx_info("No handler, fixme!");
return -EINVAL;
}
}
/* Our skb->data at this point includes the HW header */
len = WL12XX_TX_ALIGN(skb->len);
if (wl->data_in_count & 0x1)
addr = wl->data_path->tx_packet_ring_addr +
wl->data_path->tx_packet_ring_chunk_size;
else
addr = wl->data_path->tx_packet_ring_addr;
wl12xx_spi_mem_write(wl, addr, skb->data, len);
wl12xx_debug(DEBUG_TX, "tx id %u skb 0x%p payload %u rate 0x%x",
tx_hdr->id, skb, tx_hdr->length, tx_hdr->rate);
return 0;
}
static void wl12xx_tx_trigger(struct wl12xx *wl)
{
u32 data, addr;
if (wl->data_in_count & 0x1) {
addr = ACX_REG_INTERRUPT_TRIG_H;
data = INTR_TRIG_TX_PROC1;
} else {
addr = ACX_REG_INTERRUPT_TRIG;
data = INTR_TRIG_TX_PROC0;
}
wl12xx_reg_write32(wl, addr, data);
/* Bumping data in */
wl->data_in_count = (wl->data_in_count + 1) &
TX_STATUS_DATA_OUT_COUNT_MASK;
}
/* caller must hold wl->mutex */
static int wl12xx_tx_frame(struct wl12xx *wl, struct sk_buff *skb)
{
struct ieee80211_tx_info *info;
int ret = 0;
u8 idx;
info = IEEE80211_SKB_CB(skb);
if (info->control.hw_key) {
idx = info->control.hw_key->hw_key_idx;
if (unlikely(wl->default_key != idx)) {
ret = wl12xx_acx_default_key(wl, idx);
if (ret < 0)
return ret;
}
}
ret = wl12xx_tx_path_status(wl);
if (ret < 0)
return ret;
ret = wl12xx_tx_fill_hdr(wl, skb, info);
if (ret < 0)
return ret;
ret = wl12xx_tx_send_packet(wl, skb, info);
if (ret < 0)
return ret;
wl12xx_tx_trigger(wl);
return ret;
}
void wl12xx_tx_work(struct work_struct *work)
{
struct wl12xx *wl = container_of(work, struct wl12xx, tx_work);
struct sk_buff *skb;
bool woken_up = false;
int ret;
mutex_lock(&wl->mutex);
if (unlikely(wl->state == WL12XX_STATE_OFF))
goto out;
while ((skb = skb_dequeue(&wl->tx_queue))) {
if (!woken_up) {
wl12xx_ps_elp_wakeup(wl);
woken_up = true;
}
ret = wl12xx_tx_frame(wl, skb);
if (ret == -EBUSY) {
/* firmware buffer is full, stop queues */
wl12xx_debug(DEBUG_TX, "tx_work: fw buffer full, "
"stop queues");
ieee80211_stop_queues(wl->hw);
wl->tx_queue_stopped = true;
skb_queue_head(&wl->tx_queue, skb);
goto out;
} else if (ret < 0) {
dev_kfree_skb(skb);
goto out;
}
}
out:
if (woken_up)
wl12xx_ps_elp_sleep(wl);
mutex_unlock(&wl->mutex);
}
static const char *wl12xx_tx_parse_status(u8 status)
{
/* 8 bit status field, one character per bit plus null */
static char buf[9];
int i = 0;
memset(buf, 0, sizeof(buf));
if (status & TX_DMA_ERROR)
buf[i++] = 'm';
if (status & TX_DISABLED)
buf[i++] = 'd';
if (status & TX_RETRY_EXCEEDED)
buf[i++] = 'r';
if (status & TX_TIMEOUT)
buf[i++] = 't';
if (status & TX_KEY_NOT_FOUND)
buf[i++] = 'k';
if (status & TX_ENCRYPT_FAIL)
buf[i++] = 'e';
if (status & TX_UNAVAILABLE_PRIORITY)
buf[i++] = 'p';
/* bit 0 is unused apparently */
return buf;
}
static void wl12xx_tx_packet_cb(struct wl12xx *wl,
struct tx_result *result)
{
struct ieee80211_tx_info *info;
struct sk_buff *skb;
int hdrlen, ret;
u8 *frame;
skb = wl->tx_frames[result->id];
if (skb == NULL) {
wl12xx_error("SKB for packet %d is NULL", result->id);
return;
}
info = IEEE80211_SKB_CB(skb);
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) &&
(result->status == TX_SUCCESS))
info->flags |= IEEE80211_TX_STAT_ACK;
info->status.rates[0].count = result->ack_failures + 1;
wl->stats.retry_count += result->ack_failures;
/*
* We have to remove our private TX header before pushing
* the skb back to mac80211.
*/
frame = skb_pull(skb, sizeof(struct tx_double_buffer_desc));
if (info->control.hw_key &&
info->control.hw_key->alg == ALG_TKIP) {
hdrlen = ieee80211_get_hdrlen_from_skb(skb);
memmove(frame + WL12XX_TKIP_IV_SPACE, frame, hdrlen);
skb_pull(skb, WL12XX_TKIP_IV_SPACE);
}
wl12xx_debug(DEBUG_TX, "tx status id %u skb 0x%p failures %u rate 0x%x"
" status 0x%x (%s)",
result->id, skb, result->ack_failures, result->rate,
result->status, wl12xx_tx_parse_status(result->status));
ieee80211_tx_status(wl->hw, skb);
wl->tx_frames[result->id] = NULL;
if (wl->tx_queue_stopped) {
wl12xx_debug(DEBUG_TX, "cb: queue was stopped");
skb = skb_dequeue(&wl->tx_queue);
/* The skb can be NULL because tx_work might have been
scheduled before the queue was stopped making the
queue empty */
if (skb) {
ret = wl12xx_tx_frame(wl, skb);
if (ret == -EBUSY) {
/* firmware buffer is still full */
wl12xx_debug(DEBUG_TX, "cb: fw buffer "
"still full");
skb_queue_head(&wl->tx_queue, skb);
return;
} else if (ret < 0) {
dev_kfree_skb(skb);
return;
}
}
wl12xx_debug(DEBUG_TX, "cb: waking queues");
ieee80211_wake_queues(wl->hw);
wl->tx_queue_stopped = false;
}
}
/* Called upon reception of a TX complete interrupt */
void wl12xx_tx_complete(struct wl12xx *wl)
{
int i, result_index, num_complete = 0;
struct tx_result result[FW_TX_CMPLT_BLOCK_SIZE], *result_ptr;
if (unlikely(wl->state != WL12XX_STATE_ON))
return;
/* First we read the result */
wl12xx_spi_mem_read(wl, wl->data_path->tx_complete_addr,
result, sizeof(result));
result_index = wl->next_tx_complete;
for (i = 0; i < ARRAY_SIZE(result); i++) {
result_ptr = &result[result_index];
if (result_ptr->done_1 == 1 &&
result_ptr->done_2 == 1) {
wl12xx_tx_packet_cb(wl, result_ptr);
result_ptr->done_1 = 0;
result_ptr->done_2 = 0;
result_index = (result_index + 1) &
(FW_TX_CMPLT_BLOCK_SIZE - 1);
num_complete++;
} else {
break;
}
}
/* Every completed frame needs to be acknowledged */
if (num_complete) {
/*
* If we've wrapped, we have to clear
* the results in 2 steps.
*/
if (result_index > wl->next_tx_complete) {
/* Only 1 write is needed */
wl12xx_spi_mem_write(wl,
wl->data_path->tx_complete_addr +
(wl->next_tx_complete *
sizeof(struct tx_result)),
&result[wl->next_tx_complete],
num_complete *
sizeof(struct tx_result));
} else if (result_index < wl->next_tx_complete) {
/* 2 writes are needed */
wl12xx_spi_mem_write(wl,
wl->data_path->tx_complete_addr +
(wl->next_tx_complete *
sizeof(struct tx_result)),
&result[wl->next_tx_complete],
(FW_TX_CMPLT_BLOCK_SIZE -
wl->next_tx_complete) *
sizeof(struct tx_result));
wl12xx_spi_mem_write(wl,
wl->data_path->tx_complete_addr,
result,
(num_complete -
FW_TX_CMPLT_BLOCK_SIZE +
wl->next_tx_complete) *
sizeof(struct tx_result));
} else {
/* We have to write the whole array */
wl12xx_spi_mem_write(wl,
wl->data_path->tx_complete_addr,
result,
FW_TX_CMPLT_BLOCK_SIZE *
sizeof(struct tx_result));
}
}
wl->next_tx_complete = result_index;
}
/* caller must hold wl->mutex */
void wl12xx_tx_flush(struct wl12xx *wl)
{
int i;
struct sk_buff *skb;
struct ieee80211_tx_info *info;
/* TX failure */
/* control->flags = 0; FIXME */
while ((skb = skb_dequeue(&wl->tx_queue))) {
info = IEEE80211_SKB_CB(skb);
wl12xx_debug(DEBUG_TX, "flushing skb 0x%p", skb);
if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS))
continue;
ieee80211_tx_status(wl->hw, skb);
}
for (i = 0; i < FW_TX_CMPLT_BLOCK_SIZE; i++)
if (wl->tx_frames[i] != NULL) {
skb = wl->tx_frames[i];
info = IEEE80211_SKB_CB(skb);
if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS))
continue;
ieee80211_tx_status(wl->hw, skb);
wl->tx_frames[i] = NULL;
}
}

View file

@ -0,0 +1,215 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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_TX_H__
#define __WL12XX_TX_H__
#include <linux/bitops.h>
/*
*
* TX PATH
*
* The Tx path uses a double buffer and a tx_control structure, each located
* at a fixed address in the device's memory. On startup, the host retrieves
* the pointers to these addresses. A double buffer allows for continuous data
* flow towards the device. The host keeps track of which buffer is available
* and alternates between these two buffers on a per packet basis.
*
* The size of each of the two buffers is large enough to hold the longest
* 802.3 packet - maximum size Ethernet packet + header + descriptor.
* TX complete indication will be received a-synchronously in a TX done cyclic
* buffer which is composed of 16 tx_result descriptors structures and is used
* in a cyclic manner.
*
* The TX (HOST) procedure is as follows:
* 1. Read the Tx path status, that will give the data_out_count.
* 2. goto 1, if not possible.
* i.e. if data_in_count - data_out_count >= HwBuffer size (2 for double
* buffer).
* 3. Copy the packet (preceded by double_buffer_desc), if possible.
* i.e. if data_in_count - data_out_count < HwBuffer size (2 for double
* buffer).
* 4. increment data_in_count.
* 5. Inform the firmware by generating a firmware internal interrupt.
* 6. FW will increment data_out_count after it reads the buffer.
*
* The TX Complete procedure:
* 1. To get a TX complete indication the host enables the tx_complete flag in
* the TX descriptor Structure.
* 2. For each packet with a Tx Complete field set, the firmware adds the
* transmit results to the cyclic buffer (txDoneRing) and sets both done_1
* and done_2 to 1 to indicate driver ownership.
* 3. The firmware sends a Tx Complete interrupt to the host to trigger the
* host to process the new data. Note: interrupt will be send per packet if
* TX complete indication was requested in tx_control or per crossing
* aggregation threshold.
* 4. After receiving the Tx Complete interrupt, the host reads the
* TxDescriptorDone information in a cyclic manner and clears both done_1
* and done_2 fields.
*
*/
#define TX_COMPLETE_REQUIRED_BIT 0x80
#define TX_STATUS_DATA_OUT_COUNT_MASK 0xf
#define WL12XX_TX_ALIGN_TO 4
#define WL12XX_TX_ALIGN(len) (((len) + WL12XX_TX_ALIGN_TO - 1) & \
~(WL12XX_TX_ALIGN_TO - 1))
#define WL12XX_TKIP_IV_SPACE 4
struct tx_control {
/* Rate Policy (class) index */
unsigned rate_policy:3;
/* When set, no ack policy is expected */
unsigned ack_policy:1;
/*
* Packet type:
* 0 -> 802.11
* 1 -> 802.3
* 2 -> IP
* 3 -> raw codec
*/
unsigned packet_type:2;
/* If set, this is a QoS-Null or QoS-Data frame */
unsigned qos:1;
/*
* If set, the target triggers the tx complete INT
* upon frame sending completion.
*/
unsigned tx_complete:1;
/* 2 bytes padding before packet header */
unsigned xfer_pad:1;
unsigned reserved:7;
} __attribute__ ((packed));
struct tx_double_buffer_desc {
/* Length of payload, including headers. */
u16 length;
/*
* A bit mask that specifies the initial rate to be used
* Possible values are:
* 0x0001 - 1Mbits
* 0x0002 - 2Mbits
* 0x0004 - 5.5Mbits
* 0x0008 - 6Mbits
* 0x0010 - 9Mbits
* 0x0020 - 11Mbits
* 0x0040 - 12Mbits
* 0x0080 - 18Mbits
* 0x0100 - 22Mbits
* 0x0200 - 24Mbits
* 0x0400 - 36Mbits
* 0x0800 - 48Mbits
* 0x1000 - 54Mbits
*/
u16 rate;
/* Time in us that a packet can spend in the target */
u32 expiry_time;
/* index of the TX queue used for this packet */
u8 xmit_queue;
/* Used to identify a packet */
u8 id;
struct tx_control control;
/*
* The FW should cut the packet into fragments
* of this size.
*/
u16 frag_threshold;
/* Numbers of HW queue blocks to be allocated */
u8 num_mem_blocks;
u8 reserved;
} __attribute__ ((packed));
enum {
TX_SUCCESS = 0,
TX_DMA_ERROR = BIT(7),
TX_DISABLED = BIT(6),
TX_RETRY_EXCEEDED = BIT(5),
TX_TIMEOUT = BIT(4),
TX_KEY_NOT_FOUND = BIT(3),
TX_ENCRYPT_FAIL = BIT(2),
TX_UNAVAILABLE_PRIORITY = BIT(1),
};
struct tx_result {
/*
* Ownership synchronization between the host and
* the firmware. If done_1 and done_2 are cleared,
* owned by the FW (no info ready).
*/
u8 done_1;
/* same as double_buffer_desc->id */
u8 id;
/*
* Total air access duration consumed by this
* packet, including all retries and overheads.
*/
u16 medium_usage;
/* Total media delay (from 1st EDCA AIFS counter until TX Complete). */
u32 medium_delay;
/* Time between host xfer and tx complete */
u32 fw_hnadling_time;
/* The LS-byte of the last TKIP sequence number. */
u8 lsb_seq_num;
/* Retry count */
u8 ack_failures;
/* At which rate we got a ACK */
u16 rate;
u16 reserved;
/* TX_* */
u8 status;
/* See done_1 */
u8 done_2;
} __attribute__ ((packed));
void wl12xx_tx_work(struct work_struct *work);
void wl12xx_tx_complete(struct wl12xx *wl);
void wl12xx_tx_flush(struct wl12xx *wl);
#endif

View file

@ -0,0 +1,709 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2008-2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 "wl1251.h"
#include "reg.h"
#include "spi.h"
#include "boot.h"
#include "event.h"
#include "acx.h"
#include "tx.h"
#include "rx.h"
#include "ps.h"
#include "init.h"
static struct wl12xx_partition_set wl1251_part_table[PART_TABLE_LEN] = {
[PART_DOWN] = {
.mem = {
.start = 0x00000000,
.size = 0x00016800
},
.reg = {
.start = REGISTERS_BASE,
.size = REGISTERS_DOWN_SIZE
},
},
[PART_WORK] = {
.mem = {
.start = 0x00028000,
.size = 0x00014000
},
.reg = {
.start = REGISTERS_BASE,
.size = REGISTERS_WORK_SIZE
},
},
/* WL1251 doesn't use the DRPW partition, so we don't set it here */
};
static enum wl12xx_acx_int_reg wl1251_acx_reg_table[ACX_REG_TABLE_LEN] = {
[ACX_REG_INTERRUPT_TRIG] = (REGISTERS_BASE + 0x0474),
[ACX_REG_INTERRUPT_TRIG_H] = (REGISTERS_BASE + 0x0478),
[ACX_REG_INTERRUPT_MASK] = (REGISTERS_BASE + 0x0494),
[ACX_REG_HINT_MASK_SET] = (REGISTERS_BASE + 0x0498),
[ACX_REG_HINT_MASK_CLR] = (REGISTERS_BASE + 0x049C),
[ACX_REG_INTERRUPT_NO_CLEAR] = (REGISTERS_BASE + 0x04B0),
[ACX_REG_INTERRUPT_CLEAR] = (REGISTERS_BASE + 0x04A4),
[ACX_REG_INTERRUPT_ACK] = (REGISTERS_BASE + 0x04A8),
[ACX_REG_SLV_SOFT_RESET] = (REGISTERS_BASE + 0x0000),
[ACX_REG_EE_START] = (REGISTERS_BASE + 0x080C),
[ACX_REG_ECPU_CONTROL] = (REGISTERS_BASE + 0x0804)
};
static int wl1251_upload_firmware(struct wl12xx *wl)
{
struct wl12xx_partition_set *p_table = wl->chip.p_table;
int addr, chunk_num, partition_limit;
size_t fw_data_len;
u8 *p;
/* whal_FwCtrl_LoadFwImageSm() */
wl12xx_debug(DEBUG_BOOT, "chip id before fw upload: 0x%x",
wl12xx_reg_read32(wl, CHIP_ID_B));
/* 10.0 check firmware length and set partition */
fw_data_len = (wl->fw[4] << 24) | (wl->fw[5] << 16) |
(wl->fw[6] << 8) | (wl->fw[7]);
wl12xx_debug(DEBUG_BOOT, "fw_data_len %d chunk_size %d", fw_data_len,
CHUNK_SIZE);
if ((fw_data_len % 4) != 0) {
wl12xx_error("firmware length not multiple of four");
return -EIO;
}
wl12xx_set_partition(wl,
p_table[PART_DOWN].mem.start,
p_table[PART_DOWN].mem.size,
p_table[PART_DOWN].reg.start,
p_table[PART_DOWN].reg.size);
/* 10.1 set partition limit and chunk num */
chunk_num = 0;
partition_limit = p_table[PART_DOWN].mem.size;
while (chunk_num < fw_data_len / CHUNK_SIZE) {
/* 10.2 update partition, if needed */
addr = p_table[PART_DOWN].mem.start +
(chunk_num + 2) * CHUNK_SIZE;
if (addr > partition_limit) {
addr = p_table[PART_DOWN].mem.start +
chunk_num * CHUNK_SIZE;
partition_limit = chunk_num * CHUNK_SIZE +
p_table[PART_DOWN].mem.size;
wl12xx_set_partition(wl,
addr,
p_table[PART_DOWN].mem.size,
p_table[PART_DOWN].reg.start,
p_table[PART_DOWN].reg.size);
}
/* 10.3 upload the chunk */
addr = p_table[PART_DOWN].mem.start + chunk_num * CHUNK_SIZE;
p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE;
wl12xx_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x",
p, addr);
wl12xx_spi_mem_write(wl, addr, p, CHUNK_SIZE);
chunk_num++;
}
/* 10.4 upload the last chunk */
addr = p_table[PART_DOWN].mem.start + chunk_num * CHUNK_SIZE;
p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE;
wl12xx_debug(DEBUG_BOOT, "uploading fw last chunk (%d B) 0x%p to 0x%x",
fw_data_len % CHUNK_SIZE, p, addr);
wl12xx_spi_mem_write(wl, addr, p, fw_data_len % CHUNK_SIZE);
return 0;
}
static int wl1251_upload_nvs(struct wl12xx *wl)
{
size_t nvs_len, nvs_bytes_written, burst_len;
int nvs_start, i;
u32 dest_addr, val;
u8 *nvs_ptr, *nvs;
nvs = wl->nvs;
if (nvs == NULL)
return -ENODEV;
nvs_ptr = nvs;
nvs_len = wl->nvs_len;
nvs_start = wl->fw_len;
/*
* 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.
*/
while (nvs_ptr[0]) {
burst_len = nvs_ptr[0];
dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8));
/* We move our pointer to the data */
nvs_ptr += 3;
for (i = 0; i < burst_len; i++) {
val = (nvs_ptr[0] | (nvs_ptr[1] << 8)
| (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24));
wl12xx_debug(DEBUG_BOOT,
"nvs burst write 0x%x: 0x%x",
dest_addr, val);
wl12xx_mem_write32(wl, dest_addr, val);
nvs_ptr += 4;
dest_addr += 4;
}
}
/*
* We've reached the first zero length, the first NVS table
* is 7 bytes further.
*/
nvs_ptr += 7;
nvs_len -= nvs_ptr - nvs;
nvs_len = ALIGN(nvs_len, 4);
/* Now we must set the partition correctly */
wl12xx_set_partition(wl, nvs_start,
wl->chip.p_table[PART_DOWN].mem.size,
wl->chip.p_table[PART_DOWN].reg.start,
wl->chip.p_table[PART_DOWN].reg.size);
/* And finally we upload the NVS tables */
nvs_bytes_written = 0;
while (nvs_bytes_written < nvs_len) {
val = (nvs_ptr[0] | (nvs_ptr[1] << 8)
| (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24));
val = cpu_to_le32(val);
wl12xx_debug(DEBUG_BOOT,
"nvs write table 0x%x: 0x%x",
nvs_start, val);
wl12xx_mem_write32(wl, nvs_start, val);
nvs_ptr += 4;
nvs_bytes_written += 4;
nvs_start += 4;
}
return 0;
}
static int wl1251_boot(struct wl12xx *wl)
{
int ret = 0, minor_minor_e2_ver;
u32 tmp, boot_data;
ret = wl12xx_boot_soft_reset(wl);
if (ret < 0)
goto out;
/* 2. start processing NVS file */
ret = wl->chip.op_upload_nvs(wl);
if (ret < 0)
goto out;
/* write firmware's last address (ie. it's length) to
* ACX_EEPROMLESS_IND_REG */
wl12xx_reg_write32(wl, ACX_EEPROMLESS_IND_REG, wl->fw_len);
/* 6. read the EEPROM parameters */
tmp = wl12xx_reg_read32(wl, SCR_PAD2);
/* 7. read bootdata */
wl->boot_attr.radio_type = (tmp & 0x0000FF00) >> 8;
wl->boot_attr.major = (tmp & 0x00FF0000) >> 16;
tmp = wl12xx_reg_read32(wl, SCR_PAD3);
/* 8. check bootdata and call restart sequence */
wl->boot_attr.minor = (tmp & 0x00FF0000) >> 16;
minor_minor_e2_ver = (tmp & 0xFF000000) >> 24;
wl12xx_debug(DEBUG_BOOT, "radioType 0x%x majorE2Ver 0x%x "
"minorE2Ver 0x%x minor_minor_e2_ver 0x%x",
wl->boot_attr.radio_type, wl->boot_attr.major,
wl->boot_attr.minor, minor_minor_e2_ver);
ret = wl12xx_boot_init_seq(wl);
if (ret < 0)
goto out;
/* 9. NVS processing done */
boot_data = wl12xx_reg_read32(wl, ACX_REG_ECPU_CONTROL);
wl12xx_debug(DEBUG_BOOT, "halt boot_data 0x%x", boot_data);
/* 10. check that ECPU_CONTROL_HALT bits are set in
* pWhalBus->uBootData and start uploading firmware
*/
if ((boot_data & ECPU_CONTROL_HALT) == 0) {
wl12xx_error("boot failed, ECPU_CONTROL_HALT not set");
ret = -EIO;
goto out;
}
ret = wl->chip.op_upload_fw(wl);
if (ret < 0)
goto out;
/* 10.5 start firmware */
ret = wl12xx_boot_run_firmware(wl);
if (ret < 0)
goto out;
/* Get and save the firmware version */
wl12xx_acx_fw_version(wl, wl->chip.fw_ver, sizeof(wl->chip.fw_ver));
out:
return ret;
}
static int wl1251_mem_cfg(struct wl12xx *wl)
{
struct wl1251_acx_config_memory mem_conf;
int ret, i;
wl12xx_debug(DEBUG_ACX, "wl1251 mem cfg");
/* memory config */
mem_conf.mem_config.num_stations = cpu_to_le16(DEFAULT_NUM_STATIONS);
mem_conf.mem_config.rx_mem_block_num = 35;
mem_conf.mem_config.tx_min_mem_block_num = 64;
mem_conf.mem_config.num_tx_queues = MAX_TX_QUEUES;
mem_conf.mem_config.host_if_options = HOSTIF_PKT_RING;
mem_conf.mem_config.num_ssid_profiles = 1;
mem_conf.mem_config.debug_buffer_size =
cpu_to_le16(TRACE_BUFFER_MAX_SIZE);
/* RX queue config */
mem_conf.rx_queue_config.dma_address = 0;
mem_conf.rx_queue_config.num_descs = ACX_RX_DESC_DEF;
mem_conf.rx_queue_config.priority = DEFAULT_RXQ_PRIORITY;
mem_conf.rx_queue_config.type = DEFAULT_RXQ_TYPE;
/* TX queue config */
for (i = 0; i < MAX_TX_QUEUES; i++) {
mem_conf.tx_queue_config[i].num_descs = ACX_TX_DESC_DEF;
mem_conf.tx_queue_config[i].attributes = i;
}
mem_conf.header.id = ACX_MEM_CFG;
mem_conf.header.len = sizeof(struct wl1251_acx_config_memory) -
sizeof(struct acx_header);
mem_conf.header.len -=
(MAX_TX_QUEUE_CONFIGS - mem_conf.mem_config.num_tx_queues) *
sizeof(struct wl1251_acx_tx_queue_config);
ret = wl12xx_cmd_configure(wl, &mem_conf,
sizeof(struct wl1251_acx_config_memory));
if (ret < 0)
wl12xx_warning("wl1251 mem config failed: %d", ret);
return ret;
}
static int wl1251_hw_init_mem_config(struct wl12xx *wl)
{
int ret;
ret = wl1251_mem_cfg(wl);
if (ret < 0)
return ret;
wl->target_mem_map = kzalloc(sizeof(struct wl1251_acx_mem_map),
GFP_KERNEL);
if (!wl->target_mem_map) {
wl12xx_error("couldn't allocate target memory map");
return -ENOMEM;
}
/* we now ask for the firmware built memory map */
ret = wl12xx_acx_mem_map(wl, wl->target_mem_map,
sizeof(struct wl1251_acx_mem_map));
if (ret < 0) {
wl12xx_error("couldn't retrieve firmware memory map");
kfree(wl->target_mem_map);
wl->target_mem_map = NULL;
return ret;
}
return 0;
}
static void wl1251_set_ecpu_ctrl(struct wl12xx *wl, u32 flag)
{
u32 cpu_ctrl;
/* 10.5.0 run the firmware (I) */
cpu_ctrl = wl12xx_reg_read32(wl, ACX_REG_ECPU_CONTROL);
/* 10.5.1 run the firmware (II) */
cpu_ctrl &= ~flag;
wl12xx_reg_write32(wl, ACX_REG_ECPU_CONTROL, cpu_ctrl);
}
static void wl1251_target_enable_interrupts(struct wl12xx *wl)
{
/* Enable target's interrupts */
wl->intr_mask = WL1251_ACX_INTR_RX0_DATA |
WL1251_ACX_INTR_RX1_DATA |
WL1251_ACX_INTR_TX_RESULT |
WL1251_ACX_INTR_EVENT_A |
WL1251_ACX_INTR_EVENT_B |
WL1251_ACX_INTR_INIT_COMPLETE;
wl12xx_boot_target_enable_interrupts(wl);
}
static void wl1251_irq_work(struct work_struct *work)
{
u32 intr;
struct wl12xx *wl =
container_of(work, struct wl12xx, irq_work);
mutex_lock(&wl->mutex);
wl12xx_debug(DEBUG_IRQ, "IRQ work");
if (wl->state == WL12XX_STATE_OFF)
goto out;
wl12xx_ps_elp_wakeup(wl);
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_MASK, WL1251_ACX_INTR_ALL);
intr = wl12xx_reg_read32(wl, ACX_REG_INTERRUPT_CLEAR);
wl12xx_debug(DEBUG_IRQ, "intr: 0x%x", intr);
if (wl->data_path) {
wl12xx_spi_mem_read(wl, wl->data_path->rx_control_addr,
&wl->rx_counter, sizeof(u32));
/* We handle a frmware bug here */
switch ((wl->rx_counter - wl->rx_handled) & 0xf) {
case 0:
wl12xx_debug(DEBUG_IRQ, "RX: FW and host in sync");
intr &= ~WL1251_ACX_INTR_RX0_DATA;
intr &= ~WL1251_ACX_INTR_RX1_DATA;
break;
case 1:
wl12xx_debug(DEBUG_IRQ, "RX: FW +1");
intr |= WL1251_ACX_INTR_RX0_DATA;
intr &= ~WL1251_ACX_INTR_RX1_DATA;
break;
case 2:
wl12xx_debug(DEBUG_IRQ, "RX: FW +2");
intr |= WL1251_ACX_INTR_RX0_DATA;
intr |= WL1251_ACX_INTR_RX1_DATA;
break;
default:
wl12xx_warning("RX: FW and host out of sync: %d",
wl->rx_counter - wl->rx_handled);
break;
}
wl->rx_handled = wl->rx_counter;
wl12xx_debug(DEBUG_IRQ, "RX counter: %d", wl->rx_counter);
}
intr &= wl->intr_mask;
if (intr == 0) {
wl12xx_debug(DEBUG_IRQ, "INTR is 0");
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_MASK,
~(wl->intr_mask));
goto out_sleep;
}
if (intr & WL1251_ACX_INTR_RX0_DATA) {
wl12xx_debug(DEBUG_IRQ, "WL1251_ACX_INTR_RX0_DATA");
wl12xx_rx(wl);
}
if (intr & WL1251_ACX_INTR_RX1_DATA) {
wl12xx_debug(DEBUG_IRQ, "WL1251_ACX_INTR_RX1_DATA");
wl12xx_rx(wl);
}
if (intr & WL1251_ACX_INTR_TX_RESULT) {
wl12xx_debug(DEBUG_IRQ, "WL1251_ACX_INTR_TX_RESULT");
wl12xx_tx_complete(wl);
}
if (intr & (WL1251_ACX_INTR_EVENT_A | WL1251_ACX_INTR_EVENT_B)) {
wl12xx_debug(DEBUG_IRQ, "WL1251_ACX_INTR_EVENT (0x%x)", intr);
if (intr & WL1251_ACX_INTR_EVENT_A)
wl12xx_event_handle(wl, 0);
else
wl12xx_event_handle(wl, 1);
}
if (intr & WL1251_ACX_INTR_INIT_COMPLETE)
wl12xx_debug(DEBUG_IRQ, "WL1251_ACX_INTR_INIT_COMPLETE");
wl12xx_reg_write32(wl, ACX_REG_INTERRUPT_MASK, ~(wl->intr_mask));
out_sleep:
wl12xx_ps_elp_sleep(wl);
out:
mutex_unlock(&wl->mutex);
}
static int wl1251_hw_init_txq_fill(u8 qid,
struct acx_tx_queue_qos_config *config,
u32 num_blocks)
{
config->qid = qid;
switch (qid) {
case QOS_AC_BE:
config->high_threshold =
(QOS_TX_HIGH_BE_DEF * num_blocks) / 100;
config->low_threshold =
(QOS_TX_LOW_BE_DEF * num_blocks) / 100;
break;
case QOS_AC_BK:
config->high_threshold =
(QOS_TX_HIGH_BK_DEF * num_blocks) / 100;
config->low_threshold =
(QOS_TX_LOW_BK_DEF * num_blocks) / 100;
break;
case QOS_AC_VI:
config->high_threshold =
(QOS_TX_HIGH_VI_DEF * num_blocks) / 100;
config->low_threshold =
(QOS_TX_LOW_VI_DEF * num_blocks) / 100;
break;
case QOS_AC_VO:
config->high_threshold =
(QOS_TX_HIGH_VO_DEF * num_blocks) / 100;
config->low_threshold =
(QOS_TX_LOW_VO_DEF * num_blocks) / 100;
break;
default:
wl12xx_error("Invalid TX queue id: %d", qid);
return -EINVAL;
}
return 0;
}
static int wl1251_hw_init_tx_queue_config(struct wl12xx *wl)
{
struct acx_tx_queue_qos_config config;
struct wl1251_acx_mem_map *wl_mem_map = wl->target_mem_map;
int ret, i;
wl12xx_debug(DEBUG_ACX, "acx tx queue config");
config.header.id = ACX_TX_QUEUE_CFG;
config.header.len = sizeof(struct acx_tx_queue_qos_config) -
sizeof(struct acx_header);
for (i = 0; i < MAX_NUM_OF_AC; i++) {
ret = wl1251_hw_init_txq_fill(i, &config,
wl_mem_map->num_tx_mem_blocks);
if (ret < 0)
return ret;
ret = wl12xx_cmd_configure(wl, &config, sizeof(config));
if (ret < 0)
return ret;
}
return 0;
}
static int wl1251_hw_init_data_path_config(struct wl12xx *wl)
{
int ret;
/* asking for the data path parameters */
wl->data_path = kzalloc(sizeof(struct acx_data_path_params_resp),
GFP_KERNEL);
if (!wl->data_path) {
wl12xx_error("Couldnt allocate data path parameters");
return -ENOMEM;
}
ret = wl12xx_acx_data_path_params(wl, wl->data_path);
if (ret < 0) {
kfree(wl->data_path);
wl->data_path = NULL;
return ret;
}
return 0;
}
static int wl1251_hw_init(struct wl12xx *wl)
{
struct wl1251_acx_mem_map *wl_mem_map;
int ret;
ret = wl12xx_hw_init_hwenc_config(wl);
if (ret < 0)
return ret;
/* Template settings */
ret = wl12xx_hw_init_templates_config(wl);
if (ret < 0)
return ret;
/* Default memory configuration */
ret = wl1251_hw_init_mem_config(wl);
if (ret < 0)
return ret;
/* Default data path configuration */
ret = wl1251_hw_init_data_path_config(wl);
if (ret < 0)
goto out_free_memmap;
/* RX config */
ret = wl12xx_hw_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_data_path;
/* TX queues config */
ret = wl1251_hw_init_tx_queue_config(wl);
if (ret < 0)
goto out_free_data_path;
/* PHY layer config */
ret = wl12xx_hw_init_phy_config(wl);
if (ret < 0)
goto out_free_data_path;
/* Beacon filtering */
ret = wl12xx_hw_init_beacon_filter(wl);
if (ret < 0)
goto out_free_data_path;
/* Bluetooth WLAN coexistence */
ret = wl12xx_hw_init_pta(wl);
if (ret < 0)
goto out_free_data_path;
/* Energy detection */
ret = wl12xx_hw_init_energy_detection(wl);
if (ret < 0)
goto out_free_data_path;
/* Beacons and boradcast settings */
ret = wl12xx_hw_init_beacon_broadcast(wl);
if (ret < 0)
goto out_free_data_path;
/* Enable data path */
ret = wl12xx_cmd_data_path(wl, wl->channel, 1);
if (ret < 0)
goto out_free_data_path;
/* Default power state */
ret = wl12xx_hw_init_power_auth(wl);
if (ret < 0)
goto out_free_data_path;
wl_mem_map = wl->target_mem_map;
wl12xx_info("%d tx blocks at 0x%x, %d rx blocks at 0x%x",
wl_mem_map->num_tx_mem_blocks,
wl->data_path->tx_control_addr,
wl_mem_map->num_rx_mem_blocks,
wl->data_path->rx_control_addr);
return 0;
out_free_data_path:
kfree(wl->data_path);
out_free_memmap:
kfree(wl->target_mem_map);
return ret;
}
static int wl1251_plt_init(struct wl12xx *wl)
{
int ret;
ret = wl1251_hw_init_mem_config(wl);
if (ret < 0)
return ret;
ret = wl12xx_cmd_data_path(wl, wl->channel, 1);
if (ret < 0)
return ret;
return 0;
}
void wl1251_setup(struct wl12xx *wl)
{
/* FIXME: Is it better to use strncpy here or is this ok? */
wl->chip.fw_filename = WL1251_FW_NAME;
wl->chip.nvs_filename = WL1251_NVS_NAME;
/* Now we know what chip we're using, so adjust the power on sleep
* time accordingly */
wl->chip.power_on_sleep = WL1251_POWER_ON_SLEEP;
wl->chip.intr_cmd_complete = WL1251_ACX_INTR_CMD_COMPLETE;
wl->chip.intr_init_complete = WL1251_ACX_INTR_INIT_COMPLETE;
wl->chip.op_upload_nvs = wl1251_upload_nvs;
wl->chip.op_upload_fw = wl1251_upload_firmware;
wl->chip.op_boot = wl1251_boot;
wl->chip.op_set_ecpu_ctrl = wl1251_set_ecpu_ctrl;
wl->chip.op_target_enable_interrupts = wl1251_target_enable_interrupts;
wl->chip.op_hw_init = wl1251_hw_init;
wl->chip.op_plt_init = wl1251_plt_init;
wl->chip.p_table = wl1251_part_table;
wl->chip.acx_reg_table = wl1251_acx_reg_table;
INIT_WORK(&wl->irq_work, wl1251_irq_work);
}

View file

@ -0,0 +1,165 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2008 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 __WL1251_H__
#define __WL1251_H__
#include <linux/bitops.h>
#include "wl12xx.h"
#include "acx.h"
#define WL1251_FW_NAME "wl1251-fw.bin"
#define WL1251_NVS_NAME "wl1251-nvs.bin"
#define WL1251_POWER_ON_SLEEP 10 /* in miliseconds */
void wl1251_setup(struct wl12xx *wl);
struct wl1251_acx_memory {
__le16 num_stations; /* number of STAs to be supported. */
u16 reserved_1;
/*
* Nmber of memory buffers for the RX mem pool.
* The actual number may be less if there are
* not enough blocks left for the minimum num
* of TX ones.
*/
u8 rx_mem_block_num;
u8 reserved_2;
u8 num_tx_queues; /* From 1 to 16 */
u8 host_if_options; /* HOST_IF* */
u8 tx_min_mem_block_num;
u8 num_ssid_profiles;
__le16 debug_buffer_size;
} __attribute__ ((packed));
#define ACX_RX_DESC_MIN 1
#define ACX_RX_DESC_MAX 127
#define ACX_RX_DESC_DEF 32
struct wl1251_acx_rx_queue_config {
u8 num_descs;
u8 pad;
u8 type;
u8 priority;
__le32 dma_address;
} __attribute__ ((packed));
#define ACX_TX_DESC_MIN 1
#define ACX_TX_DESC_MAX 127
#define ACX_TX_DESC_DEF 16
struct wl1251_acx_tx_queue_config {
u8 num_descs;
u8 pad[2];
u8 attributes;
} __attribute__ ((packed));
#define MAX_TX_QUEUE_CONFIGS 5
#define MAX_TX_QUEUES 4
struct wl1251_acx_config_memory {
struct acx_header header;
struct wl1251_acx_memory mem_config;
struct wl1251_acx_rx_queue_config rx_queue_config;
struct wl1251_acx_tx_queue_config tx_queue_config[MAX_TX_QUEUE_CONFIGS];
} __attribute__ ((packed));
struct wl1251_acx_mem_map {
struct acx_header header;
void *code_start;
void *code_end;
void *wep_defkey_start;
void *wep_defkey_end;
void *sta_table_start;
void *sta_table_end;
void *packet_template_start;
void *packet_template_end;
void *queue_memory_start;
void *queue_memory_end;
void *packet_memory_pool_start;
void *packet_memory_pool_end;
void *debug_buffer1_start;
void *debug_buffer1_end;
void *debug_buffer2_start;
void *debug_buffer2_end;
/* Number of blocks FW allocated for TX packets */
u32 num_tx_mem_blocks;
/* Number of blocks FW allocated for RX packets */
u32 num_rx_mem_blocks;
} __attribute__ ((packed));
/*************************************************************************
Host Interrupt Register (WiLink -> Host)
**************************************************************************/
/* RX packet is ready in Xfer buffer #0 */
#define WL1251_ACX_INTR_RX0_DATA BIT(0)
/* TX result(s) are in the TX complete buffer */
#define WL1251_ACX_INTR_TX_RESULT BIT(1)
/* OBSOLETE */
#define WL1251_ACX_INTR_TX_XFR BIT(2)
/* RX packet is ready in Xfer buffer #1 */
#define WL1251_ACX_INTR_RX1_DATA BIT(3)
/* Event was entered to Event MBOX #A */
#define WL1251_ACX_INTR_EVENT_A BIT(4)
/* Event was entered to Event MBOX #B */
#define WL1251_ACX_INTR_EVENT_B BIT(5)
/* OBSOLETE */
#define WL1251_ACX_INTR_WAKE_ON_HOST BIT(6)
/* Trace meassge on MBOX #A */
#define WL1251_ACX_INTR_TRACE_A BIT(7)
/* Trace meassge on MBOX #B */
#define WL1251_ACX_INTR_TRACE_B BIT(8)
/* Command processing completion */
#define WL1251_ACX_INTR_CMD_COMPLETE BIT(9)
/* Init sequence is done */
#define WL1251_ACX_INTR_INIT_COMPLETE BIT(14)
#define WL1251_ACX_INTR_ALL 0xFFFFFFFF
#endif

View file

@ -0,0 +1,409 @@
/*
* This file is part of wl12xx
*
* Copyright (c) 1998-2007 Texas Instruments Incorporated
* Copyright (C) 2008-2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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/list.h>
#include <linux/bitops.h>
#include <net/mac80211.h>
#define DRIVER_NAME "wl12xx"
#define DRIVER_PREFIX DRIVER_NAME ": "
enum {
DEBUG_NONE = 0,
DEBUG_IRQ = BIT(0),
DEBUG_SPI = BIT(1),
DEBUG_BOOT = BIT(2),
DEBUG_MAILBOX = BIT(3),
DEBUG_NETLINK = 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_ALL = ~0,
};
#define DEBUG_LEVEL (DEBUG_NONE)
#define DEBUG_DUMP_LIMIT 1024
#define wl12xx_error(fmt, arg...) \
printk(KERN_ERR DRIVER_PREFIX "ERROR " fmt "\n", ##arg)
#define wl12xx_warning(fmt, arg...) \
printk(KERN_WARNING DRIVER_PREFIX "WARNING " fmt "\n", ##arg)
#define wl12xx_notice(fmt, arg...) \
printk(KERN_INFO DRIVER_PREFIX fmt "\n", ##arg)
#define wl12xx_info(fmt, arg...) \
printk(KERN_DEBUG DRIVER_PREFIX fmt "\n", ##arg)
#define wl12xx_debug(level, fmt, arg...) \
do { \
if (level & DEBUG_LEVEL) \
printk(KERN_DEBUG DRIVER_PREFIX fmt "\n", ##arg); \
} while (0)
#define wl12xx_dump(level, prefix, buf, len) \
do { \
if (level & 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 wl12xx_dump_ascii(level, prefix, buf, len) \
do { \
if (level & 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 WL12XX_DEFAULT_RX_CONFIG (CFG_UNI_FILTER_EN | \
CFG_BSSID_FILTER_EN)
#define WL12XX_DEFAULT_RX_FILTER (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)
struct boot_attr {
u32 radio_type;
u8 mac_clock;
u8 arm_clock;
int firmware_debug;
u32 minor;
u32 major;
u32 bugfix;
};
enum wl12xx_state {
WL12XX_STATE_OFF,
WL12XX_STATE_ON,
WL12XX_STATE_PLT,
};
enum wl12xx_partition_type {
PART_DOWN,
PART_WORK,
PART_DRPW,
PART_TABLE_LEN
};
struct wl12xx_partition {
u32 size;
u32 start;
};
struct wl12xx_partition_set {
struct wl12xx_partition mem;
struct wl12xx_partition reg;
};
struct wl12xx;
/* FIXME: I'm not sure about this structure name */
struct wl12xx_chip {
u32 id;
const char *fw_filename;
const char *nvs_filename;
char fw_ver[21];
unsigned int power_on_sleep;
int intr_cmd_complete;
int intr_init_complete;
int (*op_upload_fw)(struct wl12xx *wl);
int (*op_upload_nvs)(struct wl12xx *wl);
int (*op_boot)(struct wl12xx *wl);
void (*op_set_ecpu_ctrl)(struct wl12xx *wl, u32 flag);
void (*op_target_enable_interrupts)(struct wl12xx *wl);
int (*op_hw_init)(struct wl12xx *wl);
int (*op_plt_init)(struct wl12xx *wl);
struct wl12xx_partition_set *p_table;
enum wl12xx_acx_int_reg *acx_reg_table;
};
struct wl12xx_stats {
struct acx_statistics *fw_stats;
unsigned long fw_stats_update;
unsigned int retry_count;
unsigned int excessive_retries;
};
struct wl12xx_debugfs {
struct dentry *rootdir;
struct dentry *fw_statistics;
struct dentry *tx_internal_desc_overflow;
struct dentry *rx_out_of_mem;
struct dentry *rx_hdr_overflow;
struct dentry *rx_hw_stuck;
struct dentry *rx_dropped;
struct dentry *rx_fcs_err;
struct dentry *rx_xfr_hint_trig;
struct dentry *rx_path_reset;
struct dentry *rx_reset_counter;
struct dentry *dma_rx_requested;
struct dentry *dma_rx_errors;
struct dentry *dma_tx_requested;
struct dentry *dma_tx_errors;
struct dentry *isr_cmd_cmplt;
struct dentry *isr_fiqs;
struct dentry *isr_rx_headers;
struct dentry *isr_rx_mem_overflow;
struct dentry *isr_rx_rdys;
struct dentry *isr_irqs;
struct dentry *isr_tx_procs;
struct dentry *isr_decrypt_done;
struct dentry *isr_dma0_done;
struct dentry *isr_dma1_done;
struct dentry *isr_tx_exch_complete;
struct dentry *isr_commands;
struct dentry *isr_rx_procs;
struct dentry *isr_hw_pm_mode_changes;
struct dentry *isr_host_acknowledges;
struct dentry *isr_pci_pm;
struct dentry *isr_wakeups;
struct dentry *isr_low_rssi;
struct dentry *wep_addr_key_count;
struct dentry *wep_default_key_count;
/* skipping wep.reserved */
struct dentry *wep_key_not_found;
struct dentry *wep_decrypt_fail;
struct dentry *wep_packets;
struct dentry *wep_interrupt;
struct dentry *pwr_ps_enter;
struct dentry *pwr_elp_enter;
struct dentry *pwr_missing_bcns;
struct dentry *pwr_wake_on_host;
struct dentry *pwr_wake_on_timer_exp;
struct dentry *pwr_tx_with_ps;
struct dentry *pwr_tx_without_ps;
struct dentry *pwr_rcvd_beacons;
struct dentry *pwr_power_save_off;
struct dentry *pwr_enable_ps;
struct dentry *pwr_disable_ps;
struct dentry *pwr_fix_tsf_ps;
/* skipping cont_miss_bcns_spread for now */
struct dentry *pwr_rcvd_awake_beacons;
struct dentry *mic_rx_pkts;
struct dentry *mic_calc_failure;
struct dentry *aes_encrypt_fail;
struct dentry *aes_decrypt_fail;
struct dentry *aes_encrypt_packets;
struct dentry *aes_decrypt_packets;
struct dentry *aes_encrypt_interrupt;
struct dentry *aes_decrypt_interrupt;
struct dentry *event_heart_beat;
struct dentry *event_calibration;
struct dentry *event_rx_mismatch;
struct dentry *event_rx_mem_empty;
struct dentry *event_rx_pool;
struct dentry *event_oom_late;
struct dentry *event_phy_transmit_error;
struct dentry *event_tx_stuck;
struct dentry *ps_pspoll_timeouts;
struct dentry *ps_upsd_timeouts;
struct dentry *ps_upsd_max_sptime;
struct dentry *ps_upsd_max_apturn;
struct dentry *ps_pspoll_max_apturn;
struct dentry *ps_pspoll_utilization;
struct dentry *ps_upsd_utilization;
struct dentry *rxpipe_rx_prep_beacon_drop;
struct dentry *rxpipe_descr_host_int_trig_rx_data;
struct dentry *rxpipe_beacon_buffer_thres_host_int_trig_rx_data;
struct dentry *rxpipe_missed_beacon_host_int_trig_rx_data;
struct dentry *rxpipe_tx_xfr_host_int_trig_rx_data;
struct dentry *tx_queue_len;
struct dentry *retry_count;
struct dentry *excessive_retries;
};
struct wl12xx {
struct ieee80211_hw *hw;
bool mac80211_registered;
struct spi_device *spi;
void (*set_power)(bool enable);
int irq;
enum wl12xx_state state;
struct mutex mutex;
int physical_mem_addr;
int physical_reg_addr;
int virtual_mem_addr;
int virtual_reg_addr;
struct wl12xx_chip chip;
int cmd_box_addr;
int event_box_addr;
struct boot_attr boot_attr;
u8 *fw;
size_t fw_len;
u8 *nvs;
size_t nvs_len;
u8 bssid[ETH_ALEN];
u8 mac_addr[ETH_ALEN];
u8 bss_type;
u8 listen_int;
int channel;
void *target_mem_map;
struct acx_data_path_params_resp *data_path;
/* Number of TX packets transferred to the FW, modulo 16 */
u32 data_in_count;
/* Frames scheduled for transmission, not handled yet */
struct sk_buff_head tx_queue;
bool tx_queue_stopped;
struct work_struct tx_work;
struct work_struct filter_work;
/* Pending TX frames */
struct sk_buff *tx_frames[16];
/*
* Index pointing to the next TX complete entry
* in the cyclic XT complete array we get from
* the FW.
*/
u32 next_tx_complete;
/* FW Rx counter */
u32 rx_counter;
/* Rx frames handled */
u32 rx_handled;
/* Current double buffer */
u32 rx_current_buffer;
u32 rx_last_id;
/* The target interrupt mask */
u32 intr_mask;
struct work_struct irq_work;
/* The mbox event mask */
u32 event_mask;
/* Mailbox pointers */
u32 mbox_ptr[2];
/* Are we currently scanning */
bool scanning;
/* Our association ID */
u16 aid;
/* Default key (for WEP) */
u32 default_key;
unsigned int tx_mgmt_frm_rate;
unsigned int tx_mgmt_frm_mod;
unsigned int rx_config;
unsigned int rx_filter;
/* is firmware in elp mode */
bool elp;
/* we can be in psm, but not in elp, we have to differentiate */
bool psm;
/* PSM mode requested */
bool psm_requested;
/* in dBm */
int power_level;
struct wl12xx_stats stats;
struct wl12xx_debugfs debugfs;
};
int wl12xx_plt_start(struct wl12xx *wl);
int wl12xx_plt_stop(struct wl12xx *wl);
#define DEFAULT_HW_GEN_MODULATION_TYPE CCK_LONG /* Long Preamble */
#define DEFAULT_HW_GEN_TX_RATE RATE_2MBPS
#define JOIN_TIMEOUT 5000 /* 5000 milliseconds to join */
#define WL12XX_DEFAULT_POWER_LEVEL 20
#define WL12XX_TX_QUEUE_MAX_LENGTH 20
/* Different chips need different sleep times after power on. WL1271 needs
* 200ms, WL1251 needs only 10ms. By default we use 200ms, but as soon as we
* know the chip ID, we change the sleep value in the wl12xx chip structure,
* so in subsequent power ons, we don't waste more time then needed. */
#define WL12XX_DEFAULT_POWER_ON_SLEEP 200
#define CHIP_ID_1251_PG10 (0x7010101)
#define CHIP_ID_1251_PG11 (0x7020101)
#define CHIP_ID_1251_PG12 (0x7030101)
#define CHIP_ID_1271_PG10 (0x4030101)
#endif

View file

@ -0,0 +1,156 @@
#ifndef __WL12XX_80211_H__
#define __WL12XX_80211_H__
#include <linux/if_ether.h> /* ETH_ALEN */
/* 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 COUNTRY_STRING_LEN 3
#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];
} __attribute__ ((packed));
struct wl12xx_ie_header {
u8 id;
u8 len;
} __attribute__ ((packed));
/* IEs */
struct wl12xx_ie_ssid {
struct wl12xx_ie_header header;
char ssid[IW_ESSID_MAX_SIZE];
} __attribute__ ((packed));
struct wl12xx_ie_rates {
struct wl12xx_ie_header header;
u8 rates[MAX_SUPPORTED_RATES];
} __attribute__ ((packed));
struct wl12xx_ie_ds_params {
struct wl12xx_ie_header header;
u8 channel;
} __attribute__ ((packed));
struct country_triplet {
u8 channel;
u8 num_channels;
u8 max_tx_power;
} __attribute__ ((packed));
struct wl12xx_ie_country {
struct wl12xx_ie_header header;
u8 country_string[COUNTRY_STRING_LEN];
struct country_triplet triplets[MAX_COUNTRY_TRIPLETS];
} __attribute__ ((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;
} __attribute__ ((packed));
struct wl12xx_null_data_template {
struct ieee80211_header header;
} __attribute__ ((packed));
struct wl12xx_ps_poll_template {
u16 fc;
u16 aid;
u8 bssid[ETH_ALEN];
u8 ta[ETH_ALEN];
} __attribute__ ((packed));
struct wl12xx_qos_null_data_template {
struct ieee80211_header header;
__le16 qos_ctl;
} __attribute__ ((packed));
struct wl12xx_probe_req_template {
struct ieee80211_header header;
struct wl12xx_ie_ssid ssid;
struct wl12xx_ie_rates rates;
struct wl12xx_ie_rates ext_rates;
} __attribute__ ((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;
} __attribute__ ((packed));
#endif

View file

@ -0,0 +1,31 @@
/*
* This file is part of wl12xx
*
* Copyright (C) 2009 Nokia Corporation
*
* Contact: Kalle Valo <kalle.valo@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 _LINUX_SPI_WL12XX_H
#define _LINUX_SPI_WL12XX_H
struct wl12xx_platform_data {
void (*set_power)(bool enable);
};
#endif