Signed-off-by: David Lin <dlin@xxxxxxxxxxx> --- drivers/net/wireless/Kconfig | 1 + drivers/net/wireless/Makefile | 2 + drivers/net/wireless/mwlwifi/Kconfig | 17 + drivers/net/wireless/mwlwifi/Makefile | 9 + drivers/net/wireless/mwlwifi/dev.h | 435 ++++++ drivers/net/wireless/mwlwifi/fwcmd.c | 2328 +++++++++++++++++++++++++++++++ drivers/net/wireless/mwlwifi/fwcmd.h | 175 +++ drivers/net/wireless/mwlwifi/fwdl.c | 183 +++ drivers/net/wireless/mwlwifi/fwdl.h | 27 + drivers/net/wireless/mwlwifi/hostcmd.h | 753 ++++++++++ drivers/net/wireless/mwlwifi/isr.c | 148 ++ drivers/net/wireless/mwlwifi/isr.h | 26 + drivers/net/wireless/mwlwifi/mac80211.c | 743 ++++++++++ drivers/net/wireless/mwlwifi/mac80211.h | 25 + drivers/net/wireless/mwlwifi/main.c | 858 ++++++++++++ drivers/net/wireless/mwlwifi/rx.c | 523 +++++++ drivers/net/wireless/mwlwifi/rx.h | 25 + drivers/net/wireless/mwlwifi/sysadpt.h | 67 + drivers/net/wireless/mwlwifi/tx.c | 836 +++++++++++ drivers/net/wireless/mwlwifi/tx.h | 28 + 20 files changed, 7209 insertions(+) create mode 100644 drivers/net/wireless/mwlwifi/Kconfig create mode 100644 drivers/net/wireless/mwlwifi/Makefile create mode 100644 drivers/net/wireless/mwlwifi/dev.h create mode 100644 drivers/net/wireless/mwlwifi/fwcmd.c create mode 100644 drivers/net/wireless/mwlwifi/fwcmd.h create mode 100644 drivers/net/wireless/mwlwifi/fwdl.c create mode 100644 drivers/net/wireless/mwlwifi/fwdl.h create mode 100644 drivers/net/wireless/mwlwifi/hostcmd.h create mode 100644 drivers/net/wireless/mwlwifi/isr.c create mode 100644 drivers/net/wireless/mwlwifi/isr.h create mode 100644 drivers/net/wireless/mwlwifi/mac80211.c create mode 100644 drivers/net/wireless/mwlwifi/mac80211.h create mode 100644 drivers/net/wireless/mwlwifi/main.c create mode 100644 drivers/net/wireless/mwlwifi/rx.c create mode 100644 drivers/net/wireless/mwlwifi/rx.h create mode 100644 drivers/net/wireless/mwlwifi/sysadpt.h create mode 100644 drivers/net/wireless/mwlwifi/tx.c create mode 100644 drivers/net/wireless/mwlwifi/tx.h diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index a63ab2e..1c60845 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -284,5 +284,6 @@ source "drivers/net/wireless/zd1211rw/Kconfig" source "drivers/net/wireless/mwifiex/Kconfig" source "drivers/net/wireless/cw1200/Kconfig" source "drivers/net/wireless/rsi/Kconfig" +source "drivers/net/wireless/mwlwifi/Kconfig" endif # WLAN diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 6b9e729..1fe0f0d 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -62,3 +62,5 @@ obj-$(CONFIG_BRCMSMAC) += brcm80211/ obj-$(CONFIG_CW1200) += cw1200/ obj-$(CONFIG_RSI_91X) += rsi/ + +obj-$(CONFIG_MWLWIFI) += mwlwifi/ diff --git a/drivers/net/wireless/mwlwifi/Kconfig b/drivers/net/wireless/mwlwifi/Kconfig new file mode 100644 index 0000000..fd60ea2 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/Kconfig @@ -0,0 +1,17 @@ +config MWLWIFI + tristate "Marvell Wireless WiFi driver (mwlwifi)" + depends on PCI && MAC80211 && MWIFIEX_PCIE=n + select FW_LOADER + select OF + ---help--- + Select to build the driver supporting the: + + Marvell Wireless WiFi 88W8864 modules + Marvell Wireless WiFi 88W8897 modules + + This driver uses the kernel's mac80211 subsystem. + + If you want to compile the driver as a module (= code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read <file:Documentation/kbuild/modules.txt>. The + module will be called mwlwifi. diff --git a/drivers/net/wireless/mwlwifi/Makefile b/drivers/net/wireless/mwlwifi/Makefile new file mode 100644 index 0000000..c6e03a3 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_MWLWIFI) += mwlwifi.o + +mwlwifi-objs += main.o +mwlwifi-objs += mac80211.o +mwlwifi-objs += fwdl.o +mwlwifi-objs += fwcmd.o +mwlwifi-objs += tx.o +mwlwifi-objs += rx.o +mwlwifi-objs += isr.o diff --git a/drivers/net/wireless/mwlwifi/dev.h b/drivers/net/wireless/mwlwifi/dev.h new file mode 100644 index 0000000..4c6cdb5 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/dev.h @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines device related information. */ + +#ifndef _dev_h_ +#define _dev_h_ + +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <net/mac80211.h> + +/* Map to 0x80000000 (Bus control) on BAR0 */ +#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_CAUSE 0x00000C1C /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_MASK 0x00000C20 /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_CLEAR_SEL 0x00000C24 /* (From host to ARM) */ +#define MACREG_REG_H2A_INTERRUPT_STATUS_MASK 0x00000C28 /* (From host to ARM) */ + +#define MACREG_REG_A2H_INTERRUPT_EVENTS 0x00000C2C /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_MASK 0x00000C34 /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_CLEAR_SEL 0x00000C38 /* (From ARM to host) */ +#define MACREG_REG_A2H_INTERRUPT_STATUS_MASK 0x00000C3C /* (From ARM to host) */ + +/* Map to 0x80000000 on BAR1 */ +#define MACREG_REG_GEN_PTR 0x00000C10 +#define MACREG_REG_INT_CODE 0x00000C14 + +/* Bit definitio for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) */ +#define MACREG_A2HRIC_BIT_TX_DONE BIT(0) +#define MACREG_A2HRIC_BIT_RX_RDY BIT(1) +#define MACREG_A2HRIC_BIT_OPC_DONE BIT(2) +#define MACREG_A2HRIC_BIT_MAC_EVENT BIT(3) +#define MACREG_A2HRIC_BIT_RX_PROBLEM BIT(4) +#define MACREG_A2HRIC_BIT_RADIO_OFF BIT(5) +#define MACREG_A2HRIC_BIT_RADIO_ON BIT(6) +#define MACREG_A2HRIC_BIT_RADAR_DETECT BIT(7) +#define MACREG_A2HRIC_BIT_ICV_ERROR BIT(8) +#define MACREG_A2HRIC_BIT_WEAKIV_ERROR BIT(9) +#define MACREG_A2HRIC_BIT_QUEUE_EMPTY BIT(10) +#define MACREG_A2HRIC_BIT_QUEUE_FULL BIT(11) +#define MACREG_A2HRIC_BIT_CHAN_SWITCH BIT(12) +#define MACREG_A2HRIC_BIT_TX_WATCHDOG BIT(13) +#define MACREG_A2HRIC_BA_WATCHDOG BIT(14) +/* 15 taken by ISR_TXACK */ +#define MACREG_A2HRIC_BIT_SSU_DONE BIT(16) +#define MACREG_A2HRIC_CONSEC_TXFAIL BIT(17) + +#define ISR_SRC_BITS (MACREG_A2HRIC_BIT_RX_RDY | \ + MACREG_A2HRIC_BIT_TX_DONE | \ + MACREG_A2HRIC_BIT_OPC_DONE | \ + MACREG_A2HRIC_BIT_MAC_EVENT | \ + MACREG_A2HRIC_BIT_WEAKIV_ERROR | \ + MACREG_A2HRIC_BIT_ICV_ERROR | \ + MACREG_A2HRIC_BIT_SSU_DONE | \ + MACREG_A2HRIC_BIT_RADAR_DETECT | \ + MACREG_A2HRIC_BIT_CHAN_SWITCH | \ + MACREG_A2HRIC_BIT_TX_WATCHDOG | \ + MACREG_A2HRIC_BIT_QUEUE_EMPTY | \ + MACREG_A2HRIC_BA_WATCHDOG | \ + MACREG_A2HRIC_CONSEC_TXFAIL) + +#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS + +/* Bit definitio for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) */ +#define MACREG_H2ARIC_BIT_PPA_READY BIT(0) +#define MACREG_H2ARIC_BIT_DOOR_BELL BIT(1) +#define MACREG_H2ARIC_BIT_PS BIT(2) +#define MACREG_H2ARIC_BIT_PSPOLL BIT(3) +#define ISR_RESET BIT(15) +#define ISR_RESET_AP33 BIT(26) + +/* Data descriptor related constants */ +#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00 +#define EAGLE_RXD_CTRL_DMA_OWN 0x80 + +#define EAGLE_RXD_STATUS_OK 0x01 + +#define EAGLE_TXD_STATUS_IDLE 0x00000000 +#define EAGLE_TXD_STATUS_OK 0x00000001 +#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000 + +/* Antenna control */ +#define ANTENNA_TX_4_AUTO 0 +#define ANTENNA_TX_2 3 +#define ANTENNA_RX_4_AUTO 0 +#define ANTENNA_RX_2 2 + +/* Band related constants */ +#define BAND_24_CHANNEL_NUM 14 +#define BAND_24_RATE_NUM 13 +#define BAND_50_CHANNEL_NUM 24 +#define BAND_50_RATE_NUM 8 + +/* vif and station */ +#define NUM_WEP_KEYS 4 +#define MWL_MAX_TID 8 +#define MWL_AMSDU_SIZE_4K 0 +#define MWL_AMSDU_SIZE_8K 1 + +/* power init */ +#define MWL_POWER_INIT_1 1 +#define MWL_POWER_INIT_2 2 + +enum { + MWL8864 = 0, + MWL8897, + MWLUNKNOWN, +}; + +enum { + AP_MODE_11AC = 0x10, /* generic 11ac indication mode */ + AP_MODE_2_4GHZ_11AC_MIXED = 0x17, +}; + +enum { + AMPDU_NO_STREAM, + AMPDU_STREAM_NEW, + AMPDU_STREAM_IN_PROGRESS, + AMPDU_STREAM_ACTIVE, +}; + +struct mwl_chip_info { + char *part_name; + char *fw_image; +}; + +struct mwl_tx_pwr_tbl { + u8 channel; + u8 setcap; + u16 tx_power[SYSADPT_TX_POWER_LEVEL_TOTAL]; + u8 cdd; /* 0: off, 1: on */ + u16 txantenna2; +}; + +struct mwl_hw_data { + u32 fw_release_num; /* MajNbr:MinNbr:SubMin:PatchLevel */ + u8 hw_version; /* plain number indicating version */ + u8 host_interface; /* plain number of interface */ + u16 max_num_tx_desc; /* max number of TX descriptors */ + u16 max_num_mc_addr; /* max number multicast addresses */ + u16 num_antennas; /* number antennas used */ + u16 region_code; /* region (eg. 0x10 for USA FCC) */ + unsigned char mac_addr[ETH_ALEN]; /* well known -> AA:BB:CC:DD:EE:FF */ +}; + +#define MWL_TX_RATE_FORMAT_MASK 0x00000003 +#define MWL_TX_RATE_BANDWIDTH_MASK 0x00000030 +#define MWL_TX_RATE_BANDWIDTH_SHIFT 4 +#define MWL_TX_RATE_SHORTGI_MASK 0x00000040 +#define MWL_TX_RATE_SHORTGI_SHIFT 6 +#define MWL_TX_RATE_RATEIDMCS_MASK 0x00007F00 +#define MWL_TX_RATE_RATEIDMCS_SHIFT 8 + +struct mwl_tx_desc { + u8 data_rate; + u8 tx_priority; + __le16 qos_ctrl; + __le32 pkt_ptr; + __le16 pkt_len; + u8 dest_addr[ETH_ALEN]; + __le32 pphys_next; + __le32 sap_pkt_info; + __le32 rate_info; + u8 type; + u8 xmit_control; /* bit 0: use rateinfo, bit 1: disable ampdu */ + __le16 reserved; + __le32 tcpack_sn; + __le32 tcpack_src_dst; + __le32 reserved1; + __le32 reserved2; + u8 reserved3[2]; + u8 packet_info; + u8 packet_id; + __le16 packet_len_and_retry; + __le16 packet_rate_info; + __le32 reserved4; + __le32 status; +} __packed; + +struct mwl_tx_hndl { + struct sk_buff *psk_buff; + u8 *sta_info; + struct mwl_tx_desc *pdesc; + struct mwl_tx_hndl *pnext; +}; + +#define MWL_RX_RATE_FORMAT_MASK 0x0007 +#define MWL_RX_RATE_NSS_MASK 0x0018 +#define MWL_RX_RATE_NSS_SHIFT 3 +#define MWL_RX_RATE_BW_MASK 0x0060 +#define MWL_RX_RATE_BW_SHIFT 5 +#define MWL_RX_RATE_GI_MASK 0x0080 +#define MWL_RX_RATE_GI_SHIFT 7 +#define MWL_RX_RATE_RT_MASK 0xFF00 +#define MWL_RX_RATE_RT_SHIFT 8 + +struct mwl_rx_desc { + __le16 pkt_len; /* total length of received data */ + __le16 rate; /* receive rate information */ + __le32 pphys_buff_data; /* physical address of payload data */ + __le32 pphys_next; /* physical address of next RX desc */ + __le16 qos_ctrl; /* received QosCtrl field variable */ + __le16 ht_sig2; /* like name states */ + __le32 hw_rssi_info; + __le32 hw_noise_floor_info; + u8 noise_floor; + u8 reserved[3]; + u8 rssi; /* received signal strengt indication */ + u8 status; /* status field containing USED bit */ + u8 channel; /* channel this pkt was received on */ + u8 rx_control; /* the control element of the desc */ + __le32 reserved1[3]; +} __packed; + +struct mwl_rx_hndl { + struct sk_buff *psk_buff; /* associated sk_buff for Linux */ + struct mwl_rx_desc *pdesc; + struct mwl_rx_hndl *pnext; +}; + +struct mwl_desc_data { + dma_addr_t pphys_tx_ring; /* ptr to first TX desc (phys.) */ + struct mwl_tx_desc *ptx_ring; /* ptr to first TX desc (virt.) */ + struct mwl_tx_hndl *tx_hndl; + struct mwl_tx_hndl *pnext_tx_hndl; /* next TX handle that can be used */ + struct mwl_tx_hndl *pstale_tx_hndl;/* the staled TX handle */ + dma_addr_t pphys_rx_ring; /* ptr to first RX desc (phys.) */ + struct mwl_rx_desc *prx_ring; /* ptr to first RX desc (virt.) */ + struct mwl_rx_hndl *rx_hndl; + struct mwl_rx_hndl *pnext_rx_hndl; /* next RX handle that can be used */ + u32 wcb_base; /* FW base offset for registers */ + u32 rx_desc_write; /* FW descriptor write position */ + u32 rx_desc_read; /* FW descriptor read position */ + u32 rx_buf_size; /* length of the RX buffers */ +}; + +struct mwl_ampdu_stream { + struct ieee80211_sta *sta; + u8 tid; + u8 state; + u8 idx; +}; + +struct mwl_priv { + struct ieee80211_hw *hw; + const struct firmware *fw_ucode; + int chip_type; + + struct device_node *dt_node; + struct device_node *pwr_node; + bool disable_2g; + bool disable_5g; + int antenna_tx; + int antenna_rx; + + struct mwl_tx_pwr_tbl tx_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS]; + u32 cdd; /* 0: off, 1: on */ + u16 txantenna2; + u8 powinited; + u16 max_tx_pow[SYSADPT_TX_POWER_LEVEL_TOTAL]; /* max tx power (dBm) */ + u16 target_powers[SYSADPT_TX_POWER_LEVEL_TOTAL]; /* target powers */ + u8 cal_tbl[200]; + + struct pci_dev *pdev; + void *iobase0; /* MEM Base Address Register 0 */ + void *iobase1; /* MEM Base Address Register 1 */ + u32 next_bar_num; + + spinlock_t fwcmd_lock; /* for firmware command */ + unsigned short *pcmd_buf; /* pointer to CmdBuf (virtual) */ + dma_addr_t pphys_cmd_buf; /* pointer to CmdBuf (physical) */ + bool in_send_cmd; + + int irq; + struct mwl_hw_data hw_data; /* Adapter HW specific info */ + + /* various descriptor data */ + spinlock_t tx_desc_lock; /* for tx descriptor data */ + struct mwl_desc_data desc_data[SYSADPT_NUM_OF_DESC_DATA]; + struct sk_buff_head txq[SYSADPT_NUM_OF_DESC_DATA]; + struct sk_buff_head delay_q; + /* number of descriptors owned by fw at any one time */ + int fw_desc_cnt[SYSADPT_NUM_OF_DESC_DATA]; + + struct tasklet_struct tx_task; + struct tasklet_struct rx_task; + int txq_limit; + bool is_tx_schedule; + int recv_limit; + bool is_rx_schedule; + s8 noise; /* Most recently reported noise in dBm */ + struct ieee80211_supported_band band_24; + struct ieee80211_channel channels_24[BAND_24_CHANNEL_NUM]; + struct ieee80211_rate rates_24[BAND_24_RATE_NUM]; + struct ieee80211_supported_band band_50; + struct ieee80211_channel channels_50[BAND_50_CHANNEL_NUM]; + struct ieee80211_rate rates_50[BAND_50_RATE_NUM]; + + u32 ap_macids_supported; + u32 sta_macids_supported; + u32 macids_used; + spinlock_t vif_lock; /* for private interface info */ + struct list_head vif_list; /* List of interfaces. */ + u32 running_bsses; /* bitmap of running BSSes */ + + spinlock_t sta_lock; /* for private sta info */ + struct list_head sta_list; /* List of stations */ + + bool radio_on; + bool radio_short_preamble; + bool wmm_enabled; + struct ieee80211_tx_queue_params wmm_params[SYSADPT_TX_WMM_QUEUES]; + + /* Ampdu stream information */ + u8 num_ampdu_queues; + spinlock_t stream_lock; /* for ampdu stream */ + struct mwl_ampdu_stream ampdu[SYSADPT_TX_AMPDU_QUEUES]; + struct work_struct watchdog_ba_handle; +}; + +struct beacon_info { + bool valid; + u16 cap_info; + u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u16 ie_wmm_len; /* Keep WMM IE */ + u8 *ie_wmm_ptr; + u16 ie_rsn_len; /* Keep WPA IE */ + u8 *ie_rsn_ptr; + u16 ie_rsn48_len; /* Keep WPA2 IE */ + u8 *ie_rsn48_ptr; + u16 ie_ht_len; /* Keep HT IE */ + u8 *ie_ht_ptr; + u8 ie_list_ht[148]; + u16 ie_vht_len; /* Keep VHT IE */ + u8 *ie_vht_ptr; + u8 ie_list_vht[24]; +}; + +struct mwl_vif { + struct list_head list; + struct ieee80211_vif *vif; + int macid; /* Firmware macid for this vif. */ + u16 seqno; /* Non AMPDU sequence number assigned by driver. */ + struct { /* Saved WEP keys */ + u8 enabled; + u8 key[sizeof(struct ieee80211_key_conf) + WLAN_KEY_LEN_WEP104]; + } wep_key_conf[NUM_WEP_KEYS]; + u8 bssid[ETH_ALEN]; /* BSSID */ + u8 sta_mac[ETH_ALEN]; /* Station mac address */ + /* A flag to indicate is HW crypto is enabled for this bssid */ + bool is_hw_crypto_enabled; + /* Indicate if this is station mode */ + bool is_sta; + struct beacon_info beacon_info; + u16 iv16; + u32 iv32; + s8 keyidx; +}; + +struct mwl_tx_info { + u32 start_time; + u32 pkts; +}; + +struct mwl_amsdu_frag { + struct sk_buff *skb; + u8 pad; + u8 *cur_pos; + u8 num; + u32 jiffies; +}; + +struct mwl_amsdu_ctrl { + struct mwl_amsdu_frag frag[SYSADPT_TX_WMM_QUEUES]; + u8 cap; +}; + +struct mwl_sta { + struct list_head list; + struct ieee80211_sta *sta; + bool is_ampdu_allowed; + struct mwl_tx_info tx_stats[MWL_MAX_TID]; + bool is_amsdu_allowed; + struct mwl_amsdu_ctrl amsdu_ctrl; + u16 iv16; + u32 iv32; +}; + +/* DMA header used by firmware and hardware. */ +struct mwl_dma_data { + __le16 fwlen; + struct ieee80211_hdr wh; + char data[0]; +} __packed; + +/* Transmission information to transmit a socket buffer. */ +struct mwl_tx_ctrl { + void *sta; + void *vif; + void *k_conf; + u8 tx_priority; + u8 type; + u16 qos_ctrl; + u8 xmit_control; +}; + +static inline struct mwl_vif *mwl_dev_get_vif(const struct ieee80211_vif *vif) +{ + return (struct mwl_vif *)&vif->drv_priv; +} + +static inline struct mwl_sta *mwl_dev_get_sta(const struct ieee80211_sta *sta) +{ + return (struct mwl_sta *)&sta->drv_priv; +} + +#endif /* _dev_h_ */ diff --git a/drivers/net/wireless/mwlwifi/fwcmd.c b/drivers/net/wireless/mwlwifi/fwcmd.c new file mode 100644 index 0000000..9ff4187 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/fwcmd.c @@ -0,0 +1,2328 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements firmware host command related + * functions. + */ + +#include <linux/etherdevice.h> + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "hostcmd.h" + +#define MAX_WAIT_FW_COMPLETE_ITERATIONS 10000 +#define MAX_WAIT_GET_HW_SPECS_ITERATONS 3 + +static bool mwl_fwcmd_chk_adapter(struct mwl_priv *priv) +{ + u32 regval; + + regval = readl(priv->iobase1 + MACREG_REG_INT_CODE); + + if (regval == 0xffffffff) { + wiphy_err(priv->hw->wiphy, "adapter is not existed"); + return false; + } + + return true; +} + +static void mwl_fwcmd_send_cmd(struct mwl_priv *priv) +{ + writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR); + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); +} + +static char *mwl_fwcmd_get_cmd_string(unsigned short cmd) +{ + int max_entries = 0; + int curr_cmd = 0; + + static const struct { + u16 cmd; + char *cmd_string; + } cmds[] = { + { HOSTCMD_CMD_GET_HW_SPEC, "GetHwSpecifications" }, + { HOSTCMD_CMD_SET_HW_SPEC, "SetHwSepcifications" }, + { HOSTCMD_CMD_802_11_GET_STAT, "80211GetStat" }, + { HOSTCMD_CMD_802_11_RADIO_CONTROL, "80211RadioControl" }, + { HOSTCMD_CMD_802_11_TX_POWER, "80211TxPower" }, + { HOSTCMD_CMD_802_11_RF_ANTENNA, "80211RfAntenna" }, + { HOSTCMD_CMD_BROADCAST_SSID_ENABLE, "broadcast_ssid_enable" }, + { HOSTCMD_CMD_SET_RF_CHANNEL, "SetRfChannel" }, + { HOSTCMD_CMD_SET_AID, "SetAid" }, + { HOSTCMD_CMD_SET_INFRA_MODE, "SetInfraMode" }, + { HOSTCMD_CMD_802_11_RTS_THSD, "80211RtsThreshold" }, + { HOSTCMD_CMD_SET_EDCA_PARAMS, "SetEDCAParams" }, + { HOSTCMD_CMD_SET_WMM_MODE, "SetWMMMode" }, + { HOSTCMD_CMD_SET_FIXED_RATE, "SetFixedRate" }, + { HOSTCMD_CMD_SET_IES, "SetInformationElements" }, + { HOSTCMD_CMD_SET_RATE_ADAPT_MODE, "SetRateAdaptationMode" }, + { HOSTCMD_CMD_SET_MAC_ADDR, "SetMacAddr" }, + { HOSTCMD_CMD_GET_WATCHDOG_BITMAP, "GetWatchdogBitMap" }, + { HOSTCMD_CMD_DEL_MAC_ADDR, "DelMacAddr" }, + { HOSTCMD_CMD_BSS_START, "BssStart" }, + { HOSTCMD_CMD_AP_BEACON, "SetApBeacon" }, + { HOSTCMD_CMD_SET_NEW_STN, "SetNewStation" }, + { HOSTCMD_CMD_SET_APMODE, "SetApMode" }, + { HOSTCMD_CMD_UPDATE_ENCRYPTION, "UpdateEncryption" }, + { HOSTCMD_CMD_BASTREAM, "BAStream" }, + { HOSTCMD_CMD_DWDS_ENABLE, "DwdsEnable" }, + { HOSTCMD_CMD_FW_FLUSH_TIMER, "FwFlushTimer" }, + { HOSTCMD_CMD_SET_CDD, "SetCDD" }, + }; + + max_entries = sizeof(cmds) / sizeof(cmds[0]); + + for (curr_cmd = 0; curr_cmd < max_entries; curr_cmd++) + if ((cmd & 0x7fff) == cmds[curr_cmd].cmd) + return cmds[curr_cmd].cmd_string; + + return "unknown"; +} + +static int mwl_fwcmd_wait_complete(struct mwl_priv *priv, unsigned short cmd) +{ + unsigned int curr_iteration = MAX_WAIT_FW_COMPLETE_ITERATIONS; + + unsigned short int_code = 0; + + do { + int_code = le16_to_cpu(*((__le16 *)&priv->pcmd_buf[0])); + mdelay(1); + } while ((int_code != cmd) && (--curr_iteration)); + + if (curr_iteration == 0) { + wiphy_err(priv->hw->wiphy, "cmd 0x%04x=%s timed out", + cmd, mwl_fwcmd_get_cmd_string(cmd)); + return -EIO; + } + + mdelay(3); + + return 0; +} + +static int mwl_fwcmd_exec_cmd(struct mwl_priv *priv, unsigned short cmd) +{ + bool busy = false; + + if (!mwl_fwcmd_chk_adapter(priv)) { + wiphy_err(priv->hw->wiphy, "no adapter existed"); + priv->in_send_cmd = false; + return -EIO; + } + + if (!priv->in_send_cmd) { + priv->in_send_cmd = true; + mwl_fwcmd_send_cmd(priv); + if (mwl_fwcmd_wait_complete(priv, 0x8000 | cmd)) { + wiphy_err(priv->hw->wiphy, "timeout"); + priv->in_send_cmd = false; + return -EIO; + } + } else { + wiphy_warn(priv->hw->wiphy, + "previous command is still running"); + busy = true; + } + + if (!busy) + priv->in_send_cmd = false; + + return 0; +} + +static int mwl_fwcmd_802_11_radio_control(struct mwl_priv *priv, + bool enable, bool force) +{ + struct hostcmd_cmd_802_11_radio_control *pcmd; + unsigned long flags; + + if (enable == priv->radio_on && !force) + return 0; + + pcmd = (struct hostcmd_cmd_802_11_radio_control *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RADIO_CONTROL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->control = cpu_to_le16(priv->radio_short_preamble ? + WL_AUTO_PREAMBLE : WL_LONG_PREAMBLE); + pcmd->radio_on = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RADIO_CONTROL)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(priv->hw->wiphy, "failed execution"); + return -EIO; + } + + priv->radio_on = enable; + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +static int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, u16 ch, + u16 band, u16 width, u16 sub_ch) +{ + struct hostcmd_cmd_802_11_tx_power *pcmd; + unsigned long flags; + int i; + + pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_GET_LIST); + pcmd->ch = cpu_to_le16(ch); + pcmd->bw = cpu_to_le16(width); + pcmd->band = cpu_to_le16(band); + pcmd->sub_ch = cpu_to_le16(sub_ch); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(priv->hw->wiphy, "failed execution"); + return -EIO; + } + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) + powlist[i] = le16_to_cpu(pcmd->power_level_list[i]); + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +static int mwl_fwcmd_set_tx_powers(struct mwl_priv *priv, u16 txpow[], + u8 action, u16 ch, u16 band, + u16 width, u16 sub_ch) +{ + struct hostcmd_cmd_802_11_tx_power *pcmd; + unsigned long flags; + int i; + + pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(action); + pcmd->ch = cpu_to_le16(ch); + pcmd->bw = cpu_to_le16(width); + pcmd->band = cpu_to_le16(band); + pcmd->sub_ch = cpu_to_le16(sub_ch); + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) + pcmd->power_level_list[i] = cpu_to_le16(txpow[i]); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(priv->hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +static u8 mwl_fwcmd_get_80m_pri_chnl_offset(u8 channel) +{ + u8 act_primary = ACT_PRIMARY_CHAN_0; + + switch (channel) { + case 36: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 40: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 44: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 48: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 52: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 56: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 60: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 64: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 100: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 104: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 108: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 112: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 116: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 120: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 124: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 128: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 132: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 136: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 140: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 144: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 149: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 153: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 157: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 161: + act_primary = ACT_PRIMARY_CHAN_3; + break; + } + + return act_primary; +} + +static void mwl_fwcmd_parse_beacon(struct mwl_priv *priv, + struct mwl_vif *vif, u8 *beacon, int len) +{ + struct ieee80211_mgmt *mgmt; + struct beacon_info *beacon_info; + int baselen; + u8 *pos; + size_t left; + bool elem_parse_failed; + + mgmt = (struct ieee80211_mgmt *)beacon; + + baselen = (u8 *)mgmt->u.beacon.variable - (u8 *)mgmt; + if (baselen > len) + return; + + beacon_info = &vif->beacon_info; + memset(beacon_info, 0, sizeof(struct beacon_info)); + beacon_info->valid = false; + beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; + beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; + + beacon_info->cap_info = le16_to_cpu(mgmt->u.beacon.capab_info); + + pos = (u8 *)mgmt->u.beacon.variable; + left = len - baselen; + + elem_parse_failed = false; + + while (left >= 2) { + u8 id, elen; + + id = *pos++; + elen = *pos++; + left -= 2; + + if (elen > left) { + elem_parse_failed = true; + break; + } + + switch (id) { + case WLAN_EID_SUPP_RATES: + case WLAN_EID_EXT_SUPP_RATES: + { + int idx, bi, oi; + u8 rate; + + for (bi = 0; bi < SYSADPT_MAX_DATA_RATES_G; + bi++) { + if (beacon_info->b_rate_set[bi] == 0) + break; + } + + for (oi = 0; oi < SYSADPT_MAX_DATA_RATES_G; + oi++) { + if (beacon_info->op_rate_set[oi] == 0) + break; + } + + for (idx = 0; idx < elen; idx++) { + rate = pos[idx]; + if ((rate & 0x80) != 0) { + if (bi < SYSADPT_MAX_DATA_RATES_G) + beacon_info->b_rate_set[bi++] + = rate & 0x7f; + else { + elem_parse_failed = true; + break; + } + } + if (oi < SYSADPT_MAX_DATA_RATES_G) + beacon_info->op_rate_set[oi++] = + rate & 0x7f; + else { + elem_parse_failed = true; + break; + } + } + } + break; + case WLAN_EID_RSN: + beacon_info->ie_rsn48_len = (elen + 2); + beacon_info->ie_rsn48_ptr = (pos - 2); + break; + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_HT_OPERATION: + case WLAN_EID_OVERLAP_BSS_SCAN_PARAM: + case WLAN_EID_EXT_CAPABILITY: + beacon_info->ie_ht_len += (elen + 2); + if (beacon_info->ie_ht_len > + sizeof(beacon_info->ie_list_ht)) { + elem_parse_failed = true; + } else { + *beacon_info->ie_ht_ptr++ = id; + *beacon_info->ie_ht_ptr++ = elen; + memcpy(beacon_info->ie_ht_ptr, pos, elen); + beacon_info->ie_ht_ptr += elen; + } + break; + case WLAN_EID_VHT_CAPABILITY: + case WLAN_EID_VHT_OPERATION: + case WLAN_EID_OPMODE_NOTIF: + beacon_info->ie_vht_len += (elen + 2); + if (beacon_info->ie_vht_len > + sizeof(beacon_info->ie_list_vht)) { + elem_parse_failed = true; + } else { + *beacon_info->ie_vht_ptr++ = id; + *beacon_info->ie_vht_ptr++ = elen; + memcpy(beacon_info->ie_vht_ptr, pos, elen); + beacon_info->ie_vht_ptr += elen; + } + break; + case WLAN_EID_VENDOR_SPECIFIC: + if ((pos[0] == 0x00) && (pos[1] == 0x50) && + (pos[2] == 0xf2)) { + if (pos[3] == 0x01) { + beacon_info->ie_rsn_len = (elen + 2); + beacon_info->ie_rsn_ptr = (pos - 2); + } + + if (pos[3] == 0x02) { + beacon_info->ie_wmm_len = (elen + 2); + beacon_info->ie_wmm_ptr = (pos - 2); + } + } + break; + default: + break; + } + + left -= elen; + pos += elen; + } + + if (!elem_parse_failed) { + beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; + beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; + beacon_info->valid = true; + + wiphy_info(priv->hw->wiphy, + "wmm:%d, rsn:%d, rsn48:%d, ht:%d, vht:%d", + beacon_info->ie_wmm_len, + beacon_info->ie_rsn_len, + beacon_info->ie_rsn48_len, + beacon_info->ie_ht_len, + beacon_info->ie_vht_len); + } +} + +static int mwl_fwcmd_set_ies(struct mwl_priv *priv, struct mwl_vif *mwl_vif) +{ + struct hostcmd_cmd_set_ies *pcmd; + unsigned long flags; + + if (!mwl_vif->beacon_info.valid) + return -EINVAL; + + if (mwl_vif->beacon_info.ie_ht_len > sizeof(pcmd->ie_list_ht)) + goto einval; + + if (mwl_vif->beacon_info.ie_vht_len > sizeof(pcmd->ie_list_vht)) + goto einval; + + pcmd = (struct hostcmd_cmd_set_ies *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_IES); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); + + pcmd->ie_list_len_ht = cpu_to_le16(mwl_vif->beacon_info.ie_ht_len); + memcpy(pcmd->ie_list_ht, mwl_vif->beacon_info.ie_ht_ptr, + mwl_vif->beacon_info.ie_ht_len); + + pcmd->ie_list_len_vht = cpu_to_le16(mwl_vif->beacon_info.ie_vht_len); + memcpy(pcmd->ie_list_vht, mwl_vif->beacon_info.ie_vht_ptr, + mwl_vif->beacon_info.ie_vht_len); + + if (priv->chip_type == MWL8897) { + pcmd->ie_list_len_proprietary = + cpu_to_le16(mwl_vif->beacon_info.ie_wmm_len); + memcpy(pcmd->ie_list_proprietary, + mwl_vif->beacon_info.ie_wmm_ptr, + mwl_vif->beacon_info.ie_wmm_len); + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_IES)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(priv->hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; + +einval: + + wiphy_err(priv->hw->wiphy, "length of IE is too long"); + + return -EINVAL; +} + +static int mwl_fwcmd_set_ap_beacon(struct mwl_priv *priv, + struct mwl_vif *mwl_vif, + struct ieee80211_bss_conf *bss_conf) +{ + struct hostcmd_cmd_ap_beacon *pcmd; + unsigned long flags; + struct ds_params *phy_ds_param_set; + + if (!mwl_vif->beacon_info.valid) + return -EINVAL; + + /* wmm structure of start command is defined less one byte, + * due to following field country is not used, add byte one + * to bypass the check. + */ + if (mwl_vif->beacon_info.ie_wmm_len > + (sizeof(pcmd->start_cmd.wmm_param) + 1)) + goto ielenerr; + + if (mwl_vif->beacon_info.ie_rsn_len > sizeof(pcmd->start_cmd.rsn_ie)) + goto ielenerr; + + if (mwl_vif->beacon_info.ie_rsn48_len > + sizeof(pcmd->start_cmd.rsn48_ie)) + goto ielenerr; + + pcmd = (struct hostcmd_cmd_ap_beacon *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_AP_BEACON); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + ether_addr_copy(pcmd->start_cmd.sta_mac_addr, mwl_vif->bssid); + memcpy(pcmd->start_cmd.ssid, bss_conf->ssid, bss_conf->ssid_len); + pcmd->start_cmd.bss_type = 1; + pcmd->start_cmd.bcn_period = cpu_to_le16(bss_conf->beacon_int); + pcmd->start_cmd.dtim_period = bss_conf->dtim_period; /* 8bit */ + + phy_ds_param_set = &pcmd->start_cmd.phy_param_set.ds_param_set; + phy_ds_param_set->elem_id = WLAN_EID_DS_PARAMS; + phy_ds_param_set->len = sizeof(phy_ds_param_set->current_chnl); + phy_ds_param_set->current_chnl = bss_conf->chandef.chan->hw_value; + + pcmd->start_cmd.probe_delay = cpu_to_le16(10); + pcmd->start_cmd.cap_info = cpu_to_le16(mwl_vif->beacon_info.cap_info); + + memcpy(&pcmd->start_cmd.wmm_param, mwl_vif->beacon_info.ie_wmm_ptr, + mwl_vif->beacon_info.ie_wmm_len); + + memcpy(&pcmd->start_cmd.rsn_ie, mwl_vif->beacon_info.ie_rsn_ptr, + mwl_vif->beacon_info.ie_rsn_len); + + memcpy(&pcmd->start_cmd.rsn48_ie, mwl_vif->beacon_info.ie_rsn48_ptr, + mwl_vif->beacon_info.ie_rsn48_len); + + memcpy(pcmd->start_cmd.b_rate_set, mwl_vif->beacon_info.b_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + memcpy(pcmd->start_cmd.op_rate_set, mwl_vif->beacon_info.op_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_AP_BEACON)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(priv->hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; + +ielenerr: + + wiphy_err(priv->hw->wiphy, "length of IE is too long"); + + return -EINVAL; +} + +static int mwl_fwcmd_encryption_set_cmd_info(struct hostcmd_cmd_set_key *cmd, + u8 *addr, + struct ieee80211_key_conf *key) +{ + cmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + cmd->cmd_hdr.len = cpu_to_le16(sizeof(*cmd)); + cmd->key_param.length = cpu_to_le16(sizeof(*cmd) - + offsetof(struct hostcmd_cmd_set_key, key_param)); + cmd->key_param.key_index = cpu_to_le32(key->keyidx); + cmd->key_param.key_len = cpu_to_le16(key->keylen); + ether_addr_copy(cmd->key_param.mac_addr, addr); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_WEP); + if (key->keyidx == 0) + cmd->key_param.key_info = + cpu_to_le32(ENCR_KEY_FLAG_WEP_TXKEY); + break; + case WLAN_CIPHER_SUITE_TKIP: + cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_TKIP); + cmd->key_param.key_info = + (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) : + cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY); + cmd->key_param.key_info |= + cpu_to_le32(ENCR_KEY_FLAG_MICKEY_VALID | + ENCR_KEY_FLAG_TSC_VALID); + break; + case WLAN_CIPHER_SUITE_CCMP: + cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_AES); + cmd->key_param.key_info = + (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) : + cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY); + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +void mwl_fwcmd_reset(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + if (mwl_fwcmd_chk_adapter(priv)) + writel(ISR_RESET, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); +} + +void mwl_fwcmd_int_enable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + if (mwl_fwcmd_chk_adapter(priv)) { + writel(0x00, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + writel((MACREG_A2HRIC_BIT_MASK), + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + } +} + +void mwl_fwcmd_int_disable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + if (mwl_fwcmd_chk_adapter(priv)) + writel(0x00, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); +} + +int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_get_hw_spec *pcmd; + unsigned long flags; + int retry; + int i; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_get_hw_spec *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + wiphy_debug(hw->wiphy, "pcmd = %x", (unsigned int)pcmd); + memset(pcmd, 0x00, sizeof(*pcmd)); + memset(&pcmd->permanent_addr[0], 0xff, ETH_ALEN); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_HW_SPEC); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->fw_awake_cookie = cpu_to_le32(priv->pphys_cmd_buf + 2048); + + retry = 0; + while (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_HW_SPEC)) { + if (retry++ > MAX_WAIT_GET_HW_SPECS_ITERATONS) { + wiphy_err(hw->wiphy, "can't get hw specs"); + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + return -EIO; + } + + mdelay(1000); + wiphy_debug(hw->wiphy, + "repeat command = %x", (unsigned int)pcmd); + } + + ether_addr_copy(&priv->hw_data.mac_addr[0], pcmd->permanent_addr); + priv->desc_data[0].wcb_base = + le32_to_cpu(pcmd->wcb_base0) & 0x0000ffff; +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + priv->desc_data[i].wcb_base = + le32_to_cpu(pcmd->wcb_base[i - 1]) & 0x0000ffff; +#endif + priv->desc_data[0].rx_desc_read = + le32_to_cpu(pcmd->rxpd_rd_ptr) & 0x0000ffff; + priv->desc_data[0].rx_desc_write = + le32_to_cpu(pcmd->rxpd_wr_ptr) & 0x0000ffff; + priv->hw_data.region_code = le16_to_cpu(pcmd->region_code) & 0x00ff; + priv->hw_data.fw_release_num = le32_to_cpu(pcmd->fw_release_num); + priv->hw_data.max_num_tx_desc = le16_to_cpu(pcmd->num_wcb); + priv->hw_data.max_num_mc_addr = le16_to_cpu(pcmd->num_mcast_addr); + priv->hw_data.num_antennas = le16_to_cpu(pcmd->num_antenna); + priv->hw_data.hw_version = pcmd->version; + priv->hw_data.host_interface = pcmd->host_if; + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_hw_spec *pcmd; + unsigned long flags; + int i; + + priv = hw->priv; + + /* Info for debugging + */ + wiphy_info(hw->wiphy, "%s ...", __func__); + wiphy_info(hw->wiphy, " -->pPhysTxRing[0] = %x", + priv->desc_data[0].pphys_tx_ring); + wiphy_info(hw->wiphy, " -->pPhysTxRing[1] = %x", + priv->desc_data[1].pphys_tx_ring); + wiphy_info(hw->wiphy, " -->pPhysTxRing[2] = %x", + priv->desc_data[2].pphys_tx_ring); + wiphy_info(hw->wiphy, " -->pPhysTxRing[3] = %x", + priv->desc_data[3].pphys_tx_ring); + wiphy_info(hw->wiphy, " -->pPhysRxRing = %x", + priv->desc_data[0].pphys_rx_ring); + wiphy_info(hw->wiphy, " -->numtxq %d wcbperq %d totalrxwcb %d", + SYSADPT_NUM_OF_DESC_DATA, + SYSADPT_MAX_NUM_TX_DESC, + SYSADPT_MAX_NUM_RX_DESC); + + pcmd = (struct hostcmd_cmd_set_hw_spec *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_HW_SPEC); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->wcb_base[0] = cpu_to_le32(priv->desc_data[0].pphys_tx_ring); +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + pcmd->wcb_base[i] = + cpu_to_le32(priv->desc_data[i].pphys_tx_ring); +#endif + pcmd->tx_wcb_num_per_queue = cpu_to_le32(SYSADPT_MAX_NUM_TX_DESC); + pcmd->num_tx_queues = cpu_to_le32(SYSADPT_NUM_OF_DESC_DATA); + pcmd->total_rx_wcb = cpu_to_le32(SYSADPT_MAX_NUM_RX_DESC); + pcmd->rxpd_wr_ptr = cpu_to_le32(priv->desc_data[0].pphys_rx_ring); + pcmd->features = 0; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_HW_SPEC)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_802_11_get_stat *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_802_11_get_stat *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_GET_STAT); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_GET_STAT)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + stats->dot11ACKFailureCount = + le32_to_cpu(pcmd->ack_failures); + stats->dot11RTSFailureCount = + le32_to_cpu(pcmd->rts_failures); + stats->dot11FCSErrorCount = + le32_to_cpu(pcmd->rx_fcs_errors); + stats->dot11RTSSuccessCount = + le32_to_cpu(pcmd->rts_successes); + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw) +{ + return mwl_fwcmd_802_11_radio_control(hw->priv, true, false); +} + +int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw) +{ + return mwl_fwcmd_802_11_radio_control(hw->priv, false, false); +} + +int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, bool short_preamble) +{ + struct mwl_priv *priv; + int rc; + + priv = hw->priv; + + priv->radio_short_preamble = short_preamble; + rc = mwl_fwcmd_802_11_radio_control(priv, true, true); + + return rc; +} + +int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv; + int reduce_val = 0; + u16 band = 0, width = 0, sub_ch = 0; + u16 maxtxpow[SYSADPT_TX_POWER_LEVEL_TOTAL]; + int i, tmp; + int rc; + + priv = hw->priv; + + switch (fraction) { + case 0: + reduce_val = 0; /* Max */ + break; + case 1: + reduce_val = 2; /* 75% -1.25db */ + break; + case 2: + reduce_val = 3; /* 50% -3db */ + break; + case 3: + reduce_val = 6; /* 25% -6db */ + break; + default: + /* larger than case 3, pCmd->MaxPowerLevel is min */ + reduce_val = 0xff; + break; + } + + if (channel->band == IEEE80211_BAND_2GHZ) + band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == IEEE80211_BAND_5GHZ) + band = FREQ_BAND_5GHZ; + + switch (conf->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + width = CH_20_MHZ_WIDTH; + sub_ch = NO_EXT_CHANNEL; + break; + case NL80211_CHAN_WIDTH_40: + width = CH_40_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + break; + case NL80211_CHAN_WIDTH_80: + width = CH_80_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + break; + default: + return -EINVAL; + } + + if ((priv->powinited & MWL_POWER_INIT_2) == 0) { + mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, + channel->hw_value, band, width, sub_ch); + priv->powinited |= MWL_POWER_INIT_2; + } + + if ((priv->powinited & MWL_POWER_INIT_1) == 0) { + mwl_fwcmd_get_tx_powers(priv, priv->target_powers, + channel->hw_value, band, width, sub_ch); + priv->powinited |= MWL_POWER_INIT_1; + } + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) { + if (priv->target_powers[i] > priv->max_tx_pow[i]) + tmp = priv->max_tx_pow[i]; + else + tmp = priv->target_powers[i]; + maxtxpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0; + } + + rc = mwl_fwcmd_set_tx_powers(priv, maxtxpow, HOSTCMD_ACT_GEN_SET, + channel->hw_value, band, width, sub_ch); + + return rc; +} + +int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv; + int reduce_val = 0; + u16 band = 0, width = 0, sub_ch = 0; + u16 txpow[SYSADPT_TX_POWER_LEVEL_TOTAL]; + int index, found = 0; + int i, tmp; + int rc; + + priv = hw->priv; + + switch (fraction) { + case 0: + reduce_val = 0; /* Max */ + break; + case 1: + reduce_val = 2; /* 75% -1.25db */ + break; + case 2: + reduce_val = 3; /* 50% -3db */ + break; + case 3: + reduce_val = 6; /* 25% -6db */ + break; + default: + /* larger than case 3, pCmd->MaxPowerLevel is min */ + reduce_val = 0xff; + break; + } + + if (channel->band == IEEE80211_BAND_2GHZ) + band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == IEEE80211_BAND_5GHZ) + band = FREQ_BAND_5GHZ; + + switch (conf->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + width = CH_20_MHZ_WIDTH; + sub_ch = NO_EXT_CHANNEL; + break; + case NL80211_CHAN_WIDTH_40: + width = CH_40_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + break; + case NL80211_CHAN_WIDTH_80: + width = CH_80_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + break; + default: + return -EINVAL; + } + + /* search tx power table if exist */ + for (index = 0; index < SYSADPT_MAX_NUM_CHANNELS; index++) { + struct mwl_tx_pwr_tbl *tx_pwr; + + tx_pwr = &priv->tx_pwr_tbl[index]; + + /* do nothing if table is not loaded */ + if (tx_pwr->channel == 0) + break; + + if (tx_pwr->channel == channel->hw_value) { + priv->cdd = tx_pwr->cdd; + priv->txantenna2 = tx_pwr->txantenna2; + + if (tx_pwr->setcap) + priv->powinited = MWL_POWER_INIT_1; + else + priv->powinited = MWL_POWER_INIT_2; + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) { + if (tx_pwr->setcap) + priv->max_tx_pow[i] = + tx_pwr->tx_power[i]; + else + priv->target_powers[i] = + tx_pwr->tx_power[i]; + } + + found = 1; + break; + } + } + + if ((priv->powinited & MWL_POWER_INIT_2) == 0) { + mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, + channel->hw_value, band, width, sub_ch); + + priv->powinited |= MWL_POWER_INIT_2; + } + + if ((priv->powinited & MWL_POWER_INIT_1) == 0) { + mwl_fwcmd_get_tx_powers(priv, priv->target_powers, + channel->hw_value, band, width, sub_ch); + + priv->powinited |= MWL_POWER_INIT_1; + } + + for (i = 0; i < SYSADPT_TX_POWER_LEVEL_TOTAL; i++) { + if (found) { + if ((priv->tx_pwr_tbl[index].setcap) && + (priv->tx_pwr_tbl[index].tx_power[i] > + priv->max_tx_pow[i])) + tmp = priv->max_tx_pow[i]; + else + tmp = priv->tx_pwr_tbl[index].tx_power[i]; + } else { + if (priv->target_powers[i] > priv->max_tx_pow[i]) + tmp = priv->max_tx_pow[i]; + else + tmp = priv->target_powers[i]; + } + + txpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0; + } + + rc = mwl_fwcmd_set_tx_powers(priv, txpow, HOSTCMD_ACT_GEN_SET_LIST, + channel->hw_value, band, width, sub_ch); + + return rc; +} + +int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_802_11_rf_antenna *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_802_11_rf_antenna *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RF_ANTENNA); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action = cpu_to_le16(dir); + + if (dir == WL_ANTENNATYPE_RX) { + u8 rx_antenna = 4; /* if auto, set 4 rx antennas in SC2 */ + + if (antenna != 0) + pcmd->antenna_mode = cpu_to_le16(antenna); + else + pcmd->antenna_mode = cpu_to_le16(rx_antenna); + } else { + u8 tx_antenna = 0xf; /* if auto, set 4 tx antennas in SC2 */ + + if (antenna != 0) + pcmd->antenna_mode = cpu_to_le16(antenna); + else + pcmd->antenna_mode = cpu_to_le16(tx_antenna); + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RF_ANTENNA)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_broadcast_ssid_enable *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_broadcast_ssid_enable *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BROADCAST_SSID_ENABLE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->enable = cpu_to_le32(enable); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BROADCAST_SSID_ENABLE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv; + struct hostcmd_cmd_set_rf_channel *pcmd; + unsigned long flags; + u32 chnl_flags, freq_band, chnl_width, act_primary; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_rf_channel *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RF_CHANNEL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->curr_chnl = channel->hw_value; + + if (channel->band == IEEE80211_BAND_2GHZ) + freq_band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == IEEE80211_BAND_5GHZ) + freq_band = FREQ_BAND_5GHZ; + else + return -EINVAL; + + switch (conf->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + chnl_width = CH_20_MHZ_WIDTH; + act_primary = ACT_PRIMARY_CHAN_0; + break; + case NL80211_CHAN_WIDTH_40: + chnl_width = CH_40_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + act_primary = ACT_PRIMARY_CHAN_0; + else + act_primary = ACT_PRIMARY_CHAN_1; + break; + case NL80211_CHAN_WIDTH_80: + chnl_width = CH_80_MHZ_WIDTH; + act_primary = + mwl_fwcmd_get_80m_pri_chnl_offset(pcmd->curr_chnl); + break; + default: + return -EINVAL; + } + + chnl_flags = (freq_band & FREQ_BAND_MASK) | + ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) | + ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK); + + pcmd->chnl_flags = cpu_to_le32(chnl_flags); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RF_CHANNEL)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_aid(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *bssid, u16 aid) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_aid *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_aid *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_AID); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->aid = cpu_to_le16(aid); + ether_addr_copy(pcmd->mac_addr, bssid); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_AID)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_infra_mode *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_infra_mode *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_INFRA_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_INFRA_MODE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, int threshold) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_802_11_rts_thsd *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_802_11_rts_thsd *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RTS_THSD); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->threshold = cpu_to_le16(threshold); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RTS_THSD)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, + u16 cw_min, u16 cw_max, u8 aifs, u16 txop) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_edca_params *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_edca_params *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_EDCA_PARAMS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action = cpu_to_le16(0xffff); + pcmd->txop = cpu_to_le16(txop); + pcmd->cw_max = cpu_to_le32(ilog2(cw_max + 1)); + pcmd->cw_min = cpu_to_le32(ilog2(cw_min + 1)); + pcmd->aifsn = aifs; + pcmd->txq_num = index; + + /* The array index defined in qos.h has a reversed bk and be. + * The HW queue was not used this way; the qos code needs to + * be changed or checked + */ + if (index == 0) + pcmd->txq_num = 1; + else if (index == 1) + pcmd->txq_num = 0; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_EDCA_PARAMS)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_wmm_mode *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_wmm_mode *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WMM_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WMM_MODE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, int mcast, int mgmt) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_fixed_rate *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_fixed_rate *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_FIXED_RATE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action = cpu_to_le32(HOSTCMD_ACT_NOT_USE_FIXED_RATE); + pcmd->multicast_rate = mcast; + pcmd->management_rate = mgmt; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_FIXED_RATE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, u16 mode) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_rate_adapt_mode *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_rate_adapt_mode *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RATE_ADAPT_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->rate_adapt_mode = cpu_to_le16(mode); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RATE_ADAPT_MODE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_mac_addr *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_MAC_ADDR); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->mac_type = cpu_to_le16(WL_MAC_TYPE_SECONDARY_CLIENT); + ether_addr_copy(pcmd->mac_addr, mac_addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_MAC_ADDR)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_get_watchdog_bitmap *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_get_watchdog_bitmap *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_WATCHDOG_BITMAP); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_WATCHDOG_BITMAP)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + *bitmap = pcmd->watchdog_bitmap; + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_mac_addr *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DEL_MAC_ADDR); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + ether_addr_copy(pcmd->mac_addr, mac_addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DEL_MAC_ADDR)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bss_start *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + if (enable && (priv->running_bsses & (1 << mwl_vif->macid))) + return 0; + + if (!enable && !(priv->running_bsses & (1 << mwl_vif->macid))) + return 0; + + pcmd = (struct hostcmd_cmd_bss_start *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BSS_START); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (enable) { + pcmd->enable = cpu_to_le32(WL_ENABLE); + } else { + if (mwl_vif->macid == 0) + pcmd->enable = cpu_to_le32(WL_DISABLE); + else + pcmd->enable = cpu_to_le32(WL_DISABLE_VMAC); + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BSS_START)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (enable) + priv->running_bsses |= (1 << mwl_vif->macid); + else + priv->running_bsses &= ~(1 << mwl_vif->macid); + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + mwl_fwcmd_parse_beacon(priv, mwl_vif, beacon, len); + + if (mwl_fwcmd_set_ies(priv, mwl_vif)) + goto err; + + if (mwl_fwcmd_set_ap_beacon(priv, mwl_vif, &vif->bss_conf)) + goto err; + + mwl_vif->beacon_info.valid = false; + + return 0; + +err: + + mwl_vif->beacon_info.valid = false; + + return -EIO; +} + +int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + unsigned long flags; + u32 rates; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); + if (mwl_vif->is_sta) { + pcmd->aid = 0; + pcmd->stn_id = 0; + } else { + pcmd->aid = cpu_to_le16(sta->aid); + pcmd->stn_id = cpu_to_le16(sta->aid); + } + ether_addr_copy(pcmd->mac_addr, sta->addr); + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rates = sta->supp_rates[IEEE80211_BAND_2GHZ]; + else + rates = sta->supp_rates[IEEE80211_BAND_5GHZ] << 5; + pcmd->peer_info.legacy_rate_bitmap = cpu_to_le32(rates); + + if (sta->ht_cap.ht_supported) { + pcmd->peer_info.ht_rates[0] = sta->ht_cap.mcs.rx_mask[0]; + pcmd->peer_info.ht_rates[1] = sta->ht_cap.mcs.rx_mask[1]; + pcmd->peer_info.ht_rates[2] = sta->ht_cap.mcs.rx_mask[2]; + pcmd->peer_info.ht_rates[3] = sta->ht_cap.mcs.rx_mask[3]; + pcmd->peer_info.ht_cap_info = cpu_to_le16(sta->ht_cap.cap); + pcmd->peer_info.mac_ht_param_info = + (sta->ht_cap.ampdu_factor & 3) | + ((sta->ht_cap.ampdu_density & 7) << 2); + } + + if (sta->vht_cap.vht_supported) { + pcmd->peer_info.vht_max_rx_mcs = + cpu_to_le32(*((u32 *) + &sta->vht_cap.vht_mcs.rx_mcs_map)); + pcmd->peer_info.vht_cap = cpu_to_le32(sta->vht_cap.cap); + pcmd->peer_info.vht_rx_channel_width = sta->bandwidth; + } + + pcmd->is_qos_sta = sta->wme; + pcmd->qos_info = ((sta->uapsd_queues << 4) | (sta->max_sp << 1)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (mwl_vif->is_sta) { + ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); + ether_addr_copy(pcmd->mac_addr, vif->addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_REMOVE); + ether_addr_copy(pcmd->mac_addr, addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (mwl_vif->is_sta) { + ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_apmode *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_apmode *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_APMODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->apmode = apmode; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_APMODE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, u8 encr_type) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_update_encryption *pcmd; + unsigned long flags; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_update_encryption *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action_type = cpu_to_le32(ENCR_ACTION_ENABLE_HW_ENCR); + ether_addr_copy(pcmd->mac_addr, addr); + pcmd->action_data[0] = encr_type; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (mwl_vif->is_sta) { + if (memcmp(mwl_vif->bssid, addr, ETH_ALEN) == 0) + ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); + else + ether_addr_copy(pcmd->mac_addr, mwl_vif->bssid); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + unsigned long flags; + int rc; + int keymlen; + u32 action; + u8 idx; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key); + if (rc) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "encryption not support"); + return rc; + } + + idx = key->keyidx; + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + action = ENCR_ACTION_TYPE_SET_KEY; + else + action = ENCR_ACTION_TYPE_SET_GROUP_KEY; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (!mwl_vif->wep_key_conf[idx].enabled) { + memcpy(mwl_vif->wep_key_conf[idx].key, key, + sizeof(*key) + key->keylen); + mwl_vif->wep_key_conf[idx].enabled = 1; + } + + keymlen = key->keylen; + action = ENCR_ACTION_TYPE_SET_KEY; + break; + case WLAN_CIPHER_SUITE_TKIP: + keymlen = MAX_ENCR_KEY_LENGTH + 2 * MIC_KEY_LENGTH; + break; + case WLAN_CIPHER_SUITE_CCMP: + keymlen = key->keylen; + break; + default: + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "encryption not support"); + return -ENOTSUPP; + } + + memcpy((void *)&pcmd->key_param.key, key->key, keymlen); + pcmd->action_type = cpu_to_le32(action); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (mwl_vif->is_sta) { + if (memcmp(mwl_vif->bssid, addr, ETH_ALEN) == 0) + ether_addr_copy(pcmd->key_param.mac_addr, + mwl_vif->sta_mac); + else + ether_addr_copy(pcmd->key_param.mac_addr, + mwl_vif->bssid); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + unsigned long flags; + int rc; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key); + if (rc) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "encryption not support"); + return rc; + } + + pcmd->action_type = cpu_to_le32(ENCR_ACTION_TYPE_REMOVE_KEY); + + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) + mwl_vif->wep_key_conf[key->keyidx].enabled = 0; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bastream *pcmd; + unsigned long flags; + u32 ba_flags, ba_type, ba_direction; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->cmd_hdr.result = cpu_to_le16(0xffff); + + pcmd->action_type = cpu_to_le32(BA_CHECK_STREAM); + memcpy(&pcmd->ba_info.create_params.peer_mac_addr[0], + stream->sta->addr, ETH_ALEN); + pcmd->ba_info.create_params.tid = stream->tid; + ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE; + ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM; + ba_flags = (ba_type & BA_TYPE_MASK) | + ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); + pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags); + pcmd->ba_info.create_params.queue_id = stream->idx; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "result error"); + return -EINVAL; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif) +{ + struct mwl_priv *priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bastream *pcmd; + unsigned long flags; + u32 ba_flags, ba_type, ba_direction; + + priv = hw->priv; + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->cmd_hdr.result = cpu_to_le16(0xffff); + + pcmd->action_type = cpu_to_le32(BA_CREATE_STREAM); + pcmd->ba_info.create_params.bar_thrs = cpu_to_le32(buf_size); + pcmd->ba_info.create_params.window_size = cpu_to_le32(buf_size); + memcpy(&pcmd->ba_info.create_params.peer_mac_addr[0], + stream->sta->addr, ETH_ALEN); + pcmd->ba_info.create_params.tid = stream->tid; + ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE; + ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM; + ba_flags = (ba_type & BA_TYPE_MASK) | + ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); + pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags); + pcmd->ba_info.create_params.queue_id = stream->idx; + pcmd->ba_info.create_params.param_info = + (stream->sta->ht_cap.ampdu_factor & + IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((stream->sta->ht_cap.ampdu_density << 2) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + pcmd->ba_info.create_params.reset_seq_no = 1; + pcmd->ba_info.create_params.current_seq = cpu_to_le16(0); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "result error"); + return -EINVAL; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, + u8 idx) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_bastream *pcmd; + unsigned long flags; + u32 ba_flags, ba_type, ba_direction; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action_type = cpu_to_le32(BA_DESTROY_STREAM); + ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE; + ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM; + ba_flags = (ba_type & BA_TYPE_MASK) | + ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); + pcmd->ba_info.destroy_params.flags = cpu_to_le32(ba_flags); + pcmd->ba_info.destroy_params.fw_ba_context.context = cpu_to_le32(idx); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +/* caller must hold priv->stream_lock when calling the stream functions */ +struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 tid) +{ + struct mwl_priv *priv; + struct mwl_ampdu_stream *stream; + int i; + + priv = hw->priv; + + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_NO_STREAM) { + stream->sta = sta; + stream->state = AMPDU_STREAM_NEW; + stream->tid = tid; + stream->idx = i; + return stream; + } + } + + return NULL; +} + +int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream) +{ + /* if the stream has already been started, don't start it again */ + if (stream->state != AMPDU_STREAM_NEW) + return 0; + + return ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0); +} + +void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream) +{ + memset(stream, 0, sizeof(*stream)); +} + +struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, + u8 *addr, u8 tid) +{ + struct mwl_priv *priv; + struct mwl_ampdu_stream *stream; + int i; + + priv = hw->priv; + + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_NO_STREAM) + continue; + + if (!memcmp(stream->sta->addr, addr, ETH_ALEN) && + stream->tid == tid) + return stream; + } + + return NULL; +} + +bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_sta *sta_info; + struct mwl_tx_info *tx_stats; + + sta_info = mwl_dev_get_sta(sta); + + BUG_ON(tid >= SYSADPT_MAX_TID); + + tx_stats = &sta_info->tx_stats[tid]; + + return (sta_info->is_ampdu_allowed && + tx_stats->pkts > SYSADPT_AMPDU_PACKET_THRESHOLD); +} + +int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_dwds_enable *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_dwds_enable *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DWDS_ENABLE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->enable = cpu_to_le32(enable); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DWDS_ENABLE)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_fw_flush_timer *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_fw_flush_timer *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_FW_FLUSH_TIMER); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->value = cpu_to_le32(value); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_FW_FLUSH_TIMER)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} + +int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + struct hostcmd_cmd_set_cdd *pcmd; + unsigned long flags; + + priv = hw->priv; + + pcmd = (struct hostcmd_cmd_set_cdd *)&priv->pcmd_buf[0]; + + spin_lock_irqsave(&priv->fwcmd_lock, flags); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_CDD); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->enable = cpu_to_le32(priv->cdd); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_CDD)) { + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + wiphy_err(hw->wiphy, "failed execution"); + return -EIO; + } + + spin_unlock_irqrestore(&priv->fwcmd_lock, flags); + + return 0; +} diff --git a/drivers/net/wireless/mwlwifi/fwcmd.h b/drivers/net/wireless/mwlwifi/fwcmd.h new file mode 100644 index 0000000..6b8ec74 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/fwcmd.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines firmware host command related + * functions. + */ + +#ifndef _fwcmd_h_ +#define _fwcmd_h_ + +/* Define OpMode for SoftAP/Station mode + * + * The following mode signature has to be written to PCI scratch register#0 + * right after successfully downloading the last block of firmware and + * before waiting for firmware ready signature + */ + +#define HOSTCMD_STA_MODE 0x5A +#define HOSTCMD_SOFTAP_MODE 0xA5 + +#define HOSTCMD_STA_FWRDY_SIGNATURE 0xF0F1F2F4 +#define HOSTCMD_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5 + +enum { + WL_ANTENNATYPE_RX = 1, + WL_ANTENNATYPE_TX = 2, +}; + +enum encr_type { + ENCR_TYPE_WEP = 0, + ENCR_TYPE_DISABLE = 1, + ENCR_TYPE_TKIP = 4, + ENCR_TYPE_AES = 6, + ENCR_TYPE_MIX = 7, +}; + +void mwl_fwcmd_reset(struct ieee80211_hw *hw); + +void mwl_fwcmd_int_enable(struct ieee80211_hw *hw); + +void mwl_fwcmd_int_disable(struct ieee80211_hw *hw); + +int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw); + +int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats); + +int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw); + +int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, + bool short_preamble); + +int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction); + +int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u8 fraction); + +int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna); + +int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable); + +int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf); + +int mwl_fwcmd_set_aid(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *bssid, u16 aid); + +int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); + +int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, + int threshold); + +int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, + u16 cw_min, u16 cw_max, u8 aifs, u16 txop); + +int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, + bool enable); + +int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, + int mcast, int mgmt); + +int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, + u16 mode); + +int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr); + +int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, + u8 *bitmap); + +int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr); + +int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable); + +int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len); + +int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); + +int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr); + +int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode); + +int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, u8 encr_type); + +int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + struct ieee80211_vif *vif); + +int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif); + +int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, + u8 idx); + +struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 tid); + +int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream); + +void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream); + +struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, + u8 *addr, u8 tid); + +bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid); + +int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable); + +int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value); + +int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw); + +#endif /* _fwcmd_h_ */ diff --git a/drivers/net/wireless/mwlwifi/fwdl.c b/drivers/net/wireless/mwlwifi/fwdl.c new file mode 100644 index 0000000..cad577d --- /dev/null +++ b/drivers/net/wireless/mwlwifi/fwdl.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements firmware download related + * functions. + */ + +#include <linux/io.h> + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "fwdl.h" + +#define FW_DOWNLOAD_BLOCK_SIZE 256 +#define FW_CHECK_MSECS 1 + +#define FW_MAX_NUM_CHECKS 0xffff + +static void mwl_fwdl_trig_pcicmd(struct mwl_priv *priv) +{ + writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR); + + writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE); + + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); +} + +static void mwl_fwdl_trig_pcicmd_bootcode(struct mwl_priv *priv) +{ + writel(priv->pphys_cmd_buf, priv->iobase1 + MACREG_REG_GEN_PTR); + + writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE); + + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); +} + +int mwl_fwdl_download_firmware(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + const struct firmware *fw; + u32 curr_iteration = 0; + u32 size_fw_downloaded = 0; + u32 int_code = 0; + u32 len = 0; + + priv = hw->priv; + fw = priv->fw_ucode; + + mwl_fwcmd_reset(hw); + + /* FW before jumping to boot rom, it will enable PCIe transaction retry, + * wait for boot code to stop it. + */ + mdelay(FW_CHECK_MSECS); + + writel(MACREG_A2HRIC_BIT_MASK, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CLEAR_SEL); + writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + writel(0x00, priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + writel(MACREG_A2HRIC_BIT_MASK, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + /* this routine interacts with SC2 bootrom to download firmware binary + * to the device. After DMA'd to SC2, the firmware could be deflated to + * reside on its respective blocks such as ITCM, DTCM, SQRAM, + * (or even DDR, AFTER DDR is init'd before fw download + */ + wiphy_info(hw->wiphy, "fw download start 88"); + + /* Disable PFU before FWDL */ + writel(0x100, priv->iobase1 + 0xE0E4); + + /* make sure SCRATCH2 C40 is clear, in case we are too quick */ + while (readl(priv->iobase1 + 0xc40) == 0) + ; + + while (size_fw_downloaded < fw->size) { + len = readl(priv->iobase1 + 0xc40); + + if (!len) + break; + + /* this copies the next chunk of fw binary to be delivered */ + memcpy((char *)&priv->pcmd_buf[0], + (fw->data + size_fw_downloaded), len); + + /* this function writes pdata to c10, then write 2 to c18 */ + mwl_fwdl_trig_pcicmd_bootcode(priv); + + /* this is arbitrary per your platform; we use 0xffff */ + curr_iteration = FW_MAX_NUM_CHECKS; + + /* NOTE: the following back to back checks on C1C is time + * sensitive, hence may need to be tweaked dependent on host + * processor. Time for SC2 to go from the write of event 2 to + * C1C == 2 is ~1300 nSec. Hence the checkings on host has to + * consider how efficient your code can be to meet this timing, + * or you can alternatively tweak this routines to fit your + * platform + */ + do { + int_code = readl(priv->iobase1 + 0xc1c); + if (int_code != 0) + break; + curr_iteration--; + } while (curr_iteration); + + do { + int_code = readl(priv->iobase1 + 0xc1c); + if ((int_code & MACREG_H2ARIC_BIT_DOOR_BELL) != + MACREG_H2ARIC_BIT_DOOR_BELL) + break; + curr_iteration--; + } while (curr_iteration); + + if (curr_iteration == 0) { + /* This limited loop check allows you to exit gracefully + * without locking up your entire system just because fw + * download failed + */ + wiphy_err(hw->wiphy, + "Exhausted curr_iteration for fw download"); + goto err_download; + } + + size_fw_downloaded += len; + } + + wiphy_info(hw->wiphy, + "FwSize = %d downloaded Size = %d curr_iteration %d", + (int)fw->size, size_fw_downloaded, curr_iteration); + + /* Now firware is downloaded successfully, so this part is to check + * whether fw can properly execute to an extent that write back + * signature to indicate its readiness to the host. NOTE: if your + * downloaded fw crashes, this signature checking will fail. This + * part is similar as SC1 + */ + writew(0x00, &priv->pcmd_buf[1]); + mwl_fwdl_trig_pcicmd(priv); + curr_iteration = FW_MAX_NUM_CHECKS; + do { + curr_iteration--; + writel(HOSTCMD_SOFTAP_MODE, priv->iobase1 + MACREG_REG_GEN_PTR); + mdelay(FW_CHECK_MSECS); + int_code = readl(priv->iobase1 + MACREG_REG_INT_CODE); + if (!(curr_iteration % 0xff)) + wiphy_err(hw->wiphy, "%x;", int_code); + } while ((curr_iteration) && + (int_code != HOSTCMD_SOFTAP_FWRDY_SIGNATURE)); + + if (curr_iteration == 0) { + wiphy_err(hw->wiphy, + "Exhausted curr_iteration for fw signature"); + goto err_download; + } + + wiphy_info(hw->wiphy, "complete"); + writel(0x00, priv->iobase1 + MACREG_REG_INT_CODE); + + return 0; + +err_download: + + mwl_fwcmd_reset(hw); + + return -EIO; +} diff --git a/drivers/net/wireless/mwlwifi/fwdl.h b/drivers/net/wireless/mwlwifi/fwdl.h new file mode 100644 index 0000000..a6f3a1fb --- /dev/null +++ b/drivers/net/wireless/mwlwifi/fwdl.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines firmware download related + * functions. + */ + +#ifndef _fwdl_h_ +#define _fwdl_h_ + +#include <net/mac80211.h> + +int mwl_fwdl_download_firmware(struct ieee80211_hw *hw); + +#endif /* _fwdl_h_ */ diff --git a/drivers/net/wireless/mwlwifi/hostcmd.h b/drivers/net/wireless/mwlwifi/hostcmd.h new file mode 100644 index 0000000..d9db0ee --- /dev/null +++ b/drivers/net/wireless/mwlwifi/hostcmd.h @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines firmware host command related + * structure. + */ + +#ifndef _hostcmd_h_ +#define _hostcmd_h_ + +/* 16 bit host command code */ +#define HOSTCMD_CMD_GET_HW_SPEC 0x0003 +#define HOSTCMD_CMD_SET_HW_SPEC 0x0004 +#define HOSTCMD_CMD_802_11_GET_STAT 0x0014 +#define HOSTCMD_CMD_802_11_RADIO_CONTROL 0x001c +#define HOSTCMD_CMD_802_11_TX_POWER 0x001f +#define HOSTCMD_CMD_802_11_RF_ANTENNA 0x0020 +#define HOSTCMD_CMD_BROADCAST_SSID_ENABLE 0x0050 /* per-vif */ +#define HOSTCMD_CMD_SET_RF_CHANNEL 0x010a +#define HOSTCMD_CMD_SET_AID 0x010d /* per-vif */ +#define HOSTCMD_CMD_SET_INFRA_MODE 0x010e /* per-vif */ +#define HOSTCMD_CMD_802_11_RTS_THSD 0x0113 +#define HOSTCMD_CMD_SET_EDCA_PARAMS 0x0115 +#define HOSTCMD_CMD_SET_WMM_MODE 0x0123 +#define HOSTCMD_CMD_SET_FIXED_RATE 0x0126 +#define HOSTCMD_CMD_SET_IES 0x0127 +#define HOSTCMD_CMD_SET_MAC_ADDR 0x0202 /* per-vif */ +#define HOSTCMD_CMD_SET_RATE_ADAPT_MODE 0x0203 +#define HOSTCMD_CMD_GET_WATCHDOG_BITMAP 0x0205 +#define HOSTCMD_CMD_DEL_MAC_ADDR 0x0206 /* pre-vif */ +#define HOSTCMD_CMD_BSS_START 0x1100 /* per-vif */ +#define HOSTCMD_CMD_AP_BEACON 0x1101 /* per-vif */ +#define HOSTCMD_CMD_SET_NEW_STN 0x1111 /* per-vif */ +#define HOSTCMD_CMD_SET_APMODE 0x1114 +#define HOSTCMD_CMD_UPDATE_ENCRYPTION 0x1122 /* per-vif */ +#define HOSTCMD_CMD_BASTREAM 0x1125 +#define HOSTCMD_CMD_DWDS_ENABLE 0x1144 +#define HOSTCMD_CMD_FW_FLUSH_TIMER 0x1148 +#define HOSTCMD_CMD_SET_CDD 0x1150 + +/* Define general result code for each command */ +#define HOSTCMD_RESULT_OK 0x0000 +/* General error */ +#define HOSTCMD_RESULT_ERROR 0x0001 +/* Command is not valid */ +#define HOSTCMD_RESULT_NOT_SUPPORT 0x0002 +/* Command is pending (will be processed) */ +#define HOSTCMD_RESULT_PENDING 0x0003 +/* System is busy (command ignored) */ +#define HOSTCMD_RESULT_BUSY 0x0004 +/* Data buffer is not big enough */ +#define HOSTCMD_RESULT_PARTIAL_DATA 0x0005 + +/* Define channel related constants */ +#define FREQ_BAND_2DOT4GHZ 0x1 +#define FREQ_BAND_4DOT9GHZ 0x2 +#define FREQ_BAND_5GHZ 0x4 +#define FREQ_BAND_5DOT2GHZ 0x8 +#define CH_AUTO_WIDTH 0 +#define CH_10_MHZ_WIDTH 0x1 +#define CH_20_MHZ_WIDTH 0x2 +#define CH_40_MHZ_WIDTH 0x4 +#define CH_80_MHZ_WIDTH 0x5 +#define EXT_CH_ABOVE_CTRL_CH 0x1 +#define EXT_CH_AUTO 0x2 +#define EXT_CH_BELOW_CTRL_CH 0x3 +#define NO_EXT_CHANNEL 0x0 + +#define ACT_PRIMARY_CHAN_0 0 +#define ACT_PRIMARY_CHAN_1 1 +#define ACT_PRIMARY_CHAN_2 2 +#define ACT_PRIMARY_CHAN_3 3 + +/* Define rate related constants */ +#define HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 + +/* Define station related constants */ +#define HOSTCMD_ACT_STA_ACTION_ADD 0 +#define HOSTCMD_ACT_STA_ACTION_REMOVE 2 + +/* Define key related constants */ +#define MAX_ENCR_KEY_LENGTH 16 +#define MIC_KEY_LENGTH 8 + +#define KEY_TYPE_ID_WEP 0x00 +#define KEY_TYPE_ID_TKIP 0x01 +#define KEY_TYPE_ID_AES 0x02 + +#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004 +#define ENCR_KEY_FLAG_PAIRWISE 0x00000008 +#define ENCR_KEY_FLAG_TSC_VALID 0x00000040 +#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000 +#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000 + +/* Define block ack related constants */ +#define BASTREAM_FLAG_IMMEDIATE_TYPE 1 +#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0 + +/* Define general purpose action */ +#define HOSTCMD_ACT_GEN_SET 0x0001 +#define HOSTCMD_ACT_GEN_SET_LIST 0x0002 +#define HOSTCMD_ACT_GEN_GET_LIST 0x0003 + +/* Misc */ +#define MAX_ENCR_KEY_LENGTH 16 +#define MIC_KEY_LENGTH 8 + +enum { + WL_DISABLE = 0, + WL_ENABLE = 1, + WL_DISABLE_VMAC = 0x80, +}; + +enum { + WL_GET = 0, + WL_SET = 1, + WL_RESET = 2, +}; + +enum { + WL_LONG_PREAMBLE = 1, + WL_SHORT_PREAMBLE = 3, + WL_AUTO_PREAMBLE = 5, +}; + +enum encr_action_type { + /* request to enable/disable HW encryption */ + ENCR_ACTION_ENABLE_HW_ENCR, + /* request to set encryption key */ + ENCR_ACTION_TYPE_SET_KEY, + /* request to remove one or more keys */ + ENCR_ACTION_TYPE_REMOVE_KEY, + ENCR_ACTION_TYPE_SET_GROUP_KEY, +}; + +enum ba_action_type { + BA_CREATE_STREAM, + BA_UPDATE_STREAM, + BA_DESTROY_STREAM, + BA_FLUSH_STREAM, + BA_CHECK_STREAM, +}; + +enum mac_type { + WL_MAC_TYPE_PRIMARY_CLIENT, + WL_MAC_TYPE_SECONDARY_CLIENT, + WL_MAC_TYPE_PRIMARY_AP, + WL_MAC_TYPE_SECONDARY_AP, +}; + +/* General host command header */ +struct hostcmd_header { + __le16 cmd; + __le16 len; + u8 seq_num; + u8 macid; + __le16 result; +} __packed; + +/* HOSTCMD_CMD_GET_HW_SPEC */ +struct hostcmd_cmd_get_hw_spec { + struct hostcmd_header cmd_hdr; + u8 version; /* version of the HW */ + u8 host_if; /* host interface */ + __le16 num_wcb; /* Max. number of WCB FW can handle */ + __le16 num_mcast_addr; /* MaxNbr of MC addresses FW can handle */ + u8 permanent_addr[ETH_ALEN]; /* MAC address programmed in HW */ + __le16 region_code; + __le16 num_antenna; /* Number of antenna used */ + __le32 fw_release_num; /* 4 byte of FW release number */ + __le32 wcb_base0; + __le32 rxpd_wr_ptr; + __le32 rxpd_rd_ptr; + __le32 fw_awake_cookie; + __le32 wcb_base[SYSADPT_TOTAL_TX_QUEUES - 1]; +} __packed; + +/* HOSTCMD_CMD_SET_HW_SPEC */ +struct hostcmd_cmd_set_hw_spec { + struct hostcmd_header cmd_hdr; + /* HW revision */ + u8 version; + /* Host interface */ + u8 host_if; + /* Max. number of Multicast address FW can handle */ + __le16 num_mcast_addr; + /* MAC address */ + u8 permanent_addr[ETH_ALEN]; + /* Region Code */ + __le16 region_code; + /* 4 byte of FW release number, example 0x1234=1.2.3.4 */ + __le32 fw_release_num; + /* Firmware awake cookie - used to ensure that the device + * is not in sleep mode + */ + __le32 fw_awake_cookie; + /* Device capabilities (see above) */ + __le32 device_caps; + /* Rx shared memory queue */ + __le32 rxpd_wr_ptr; + /* Actual number of TX queues in WcbBase array */ + __le32 num_tx_queues; + /* TX WCB Rings */ + __le32 wcb_base[SYSADPT_NUM_OF_DESC_DATA]; + /* Max AMSDU size (00 - AMSDU Disabled, + * 01 - 4K, 10 - 8K, 11 - not defined) + */ + __le32 features; + __le32 tx_wcb_num_per_queue; + __le32 total_rx_wcb; +} __packed; + +/* HOSTCMD_CMD_802_11_GET_STAT */ +struct hostcmd_cmd_802_11_get_stat { + struct hostcmd_header cmd_hdr; + __le32 tx_retry_successes; + __le32 tx_multiple_retry_successes; + __le32 tx_failures; + __le32 rts_successes; + __le32 rts_failures; + __le32 ack_failures; + __le32 rx_duplicate_frames; + __le32 rx_fcs_errors; + __le32 tx_watchdog_timeouts; + __le32 rx_overflows; + __le32 rx_frag_errors; + __le32 rx_mem_errors; + __le32 pointer_errors; + __le32 tx_underflows; + __le32 tx_done; + __le32 tx_done_buf_try_put; + __le32 tx_done_buf_put; + /* Put size of requested buffer in here */ + __le32 wait_for_tx_buf; + __le32 tx_attempts; + __le32 tx_successes; + __le32 tx_fragments; + __le32 tx_multicasts; + __le32 rx_non_ctl_pkts; + __le32 rx_multicasts; + __le32 rx_undecryptable_frames; + __le32 rx_icv_errors; + __le32 rx_excluded_frames; + __le32 rx_weak_iv_count; + __le32 rx_unicasts; + __le32 rx_bytes; + __le32 rx_errors; + __le32 rx_rts_count; + __le32 tx_cts_count; +} __packed; + +/* HOSTCMD_CMD_802_11_RADIO_CONTROL */ +struct hostcmd_cmd_802_11_radio_control { + struct hostcmd_header cmd_hdr; + __le16 action; + /* @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix */ + __le16 control; + __le16 radio_on; +} __packed; + +/* HOSTCMD_CMD_802_11_TX_POWER */ +struct hostcmd_cmd_802_11_tx_power { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 band; + __le16 ch; + __le16 bw; + __le16 sub_ch; + __le16 power_level_list[SYSADPT_TX_POWER_LEVEL_TOTAL]; +} __packed; + +/* HOSTCMD_CMD_802_11_RF_ANTENNA */ +struct hostcmd_cmd_802_11_rf_antenna { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 antenna_mode; /* Number of antennas or 0xffff(diversity) */ +} __packed; + +/* HOSTCMD_CMD_BROADCAST_SSID_ENABLE */ +struct hostcmd_cmd_broadcast_ssid_enable { + struct hostcmd_header cmd_hdr; + __le32 enable; +} __packed; + +/* HOSTCMD_CMD_SET_RF_CHANNEL */ +#define FREQ_BAND_MASK 0x0000003f +#define CHNL_WIDTH_MASK 0x000007c0 +#define CHNL_WIDTH_SHIFT 6 +#define ACT_PRIMARY_MASK 0x00003800 +#define ACT_PRIMARY_SHIFT 11 + +struct hostcmd_cmd_set_rf_channel { + struct hostcmd_header cmd_hdr; + __le16 action; + u8 curr_chnl; + __le32 chnl_flags; +} __packed; + +/* HOSTCMD_CMD_SET_AID */ +struct hostcmd_cmd_set_aid { + struct hostcmd_header cmd_hdr; + __le16 aid; + u8 mac_addr[ETH_ALEN]; /* AP's Mac Address(BSSID) */ + __le32 gprotect; + u8 ap_rates[SYSADPT_MAX_DATA_RATES_G]; +} __packed; + +/* HOSTCMD_CMD_SET_INFRA_MODE */ +struct hostcmd_cmd_set_infra_mode { + struct hostcmd_header cmd_hdr; +} __packed; + +/* HOSTCMD_CMD_802_11_RTS_THSD */ +struct hostcmd_cmd_802_11_rts_thsd { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 threshold; +} __packed; + +/* HOSTCMD_CMD_SET_EDCA_PARAMS */ +struct hostcmd_cmd_set_edca_params { + struct hostcmd_header cmd_hdr; + /* 0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN */ + __le16 action; + __le16 txop; /* in unit of 32 us */ + __le32 cw_max; /* 0~15 */ + __le32 cw_min; /* 0~15 */ + u8 aifsn; + u8 txq_num; /* Tx Queue number. */ +} __packed; + +/* HOSTCMD_CMD_SET_WMM_MODE */ +struct hostcmd_cmd_set_wmm_mode { + struct hostcmd_header cmd_hdr; + __le16 action; /* 0->unset, 1->set */ +} __packed; + +/* HOSTCMD_CMD_SET_FIXED_RATE */ +struct fix_rate_flag { /* lower rate after the retry count */ + /* 0: legacy, 1: HT */ + __le32 fix_rate_type; + /* 0: retry count is not valid, 1: use retry count specified */ + __le32 retry_count_valid; +} __packed; + +struct fix_rate_entry { + struct fix_rate_flag fix_rate_type_flags; + /* depending on the flags above, this can be either a legacy + * rate(not index) or an MCS code. + */ + __le32 fixed_rate; + __le32 retry_count; +} __packed; + +struct hostcmd_cmd_set_fixed_rate { + struct hostcmd_header cmd_hdr; + /* HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 */ + __le32 action; + /* use fixed rate specified but firmware can drop to */ + __le32 allow_rate_drop; + __le32 entry_count; + struct fix_rate_entry fixed_rate_table[4]; + u8 multicast_rate; + u8 multi_rate_tx_type; + u8 management_rate; +} __packed; + +/* HOSTCMD_CMD_SET_IES */ +struct hostcmd_cmd_set_ies { + struct hostcmd_header cmd_hdr; + __le16 action; /* 0->unset, 1->set */ + __le16 ie_list_len_ht; + __le16 ie_list_len_vht; + __le16 ie_list_len_proprietary; + /*Buffer size same as Generic_Beacon*/ + u8 ie_list_ht[148]; + u8 ie_list_vht[24]; + u8 ie_list_proprietary[112]; +} __packed; + +/* HOSTCMD_CMD_SET_RATE_ADAPT_MODE */ +struct hostcmd_cmd_set_rate_adapt_mode { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 rate_adapt_mode; /* 0:Indoor, 1:Outdoor */ +} __packed; + +/* HOSTCMD_CMD_SET_MAC_ADDR, HOSTCMD_CMD_DEL_MAC_ADDR */ +struct hostcmd_cmd_set_mac_addr { + struct hostcmd_header cmd_hdr; + __le16 mac_type; + u8 mac_addr[ETH_ALEN]; +} __packed; + +/* HOSTCMD_CMD_GET_WATCHDOG_BITMAP */ +struct hostcmd_cmd_get_watchdog_bitmap { + struct hostcmd_header cmd_hdr; + u8 watchdog_bitmap; /* for SW/BA */ +} __packed; + +/* HOSTCMD_CMD_BSS_START */ +struct hostcmd_cmd_bss_start { + struct hostcmd_header cmd_hdr; + __le32 enable; /* FALSE: Disable or TRUE: Enable */ +} __packed; + +/* HOSTCMD_CMD_AP_BEACON */ +struct cf_params { + u8 elem_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct ibss_params { + u8 elem_id; + u8 len; + __le16 atim_window; +} __packed; + +union ss_params { + struct cf_params cf_param_set; + struct ibss_params ibss_param_set; +} __packed; + +struct fh_params { + u8 elem_id; + u8 len; + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct ds_params { + u8 elem_id; + u8 len; + u8 current_chnl; +} __packed; + +union phy_params { + struct fh_params fh_param_set; + struct ds_params ds_param_set; +} __packed; + +struct rsn_ie { + u8 elem_id; + u8 len; + u8 oui_type[4]; /* 00:50:f2:01 */ + u8 ver[2]; + u8 grp_key_cipher[4]; + u8 pws_key_cnt[2]; + u8 pws_key_cipher_list[4]; + u8 auth_key_cnt[2]; + u8 auth_key_list[4]; +} __packed; + +struct rsn48_ie { + u8 elem_id; + u8 len; + u8 ver[2]; + u8 grp_key_cipher[4]; + u8 pws_key_cnt[2]; + u8 pws_key_cipher_list[4]; + u8 auth_key_cnt[2]; + u8 auth_key_list[4]; + u8 rsn_cap[2]; + u8 pmk_id_cnt[2]; + u8 pmk_id_list[16]; /* Should modify to 16 * S */ + u8 reserved[8]; +} __packed; + +struct ac_param_rcd { + u8 aci_aifsn; + u8 ecw_min_max; + __le16 txop_lim; +} __packed; + +struct wmm_param_elem { + u8 elem_id; + u8 len; + u8 oui[3]; + u8 type; + u8 sub_type; + u8 version; + u8 rsvd; + struct ac_param_rcd ac_be; + struct ac_param_rcd ac_bk; + struct ac_param_rcd ac_vi; + struct ac_param_rcd ac_vo; +} __packed; + +struct channel_info { + u8 first_channel_num; + u8 num_channels; + u8 max_tx_pwr_level; +} __packed; + +struct country { + u8 elem_id; + u8 len; + u8 country_str[3]; + struct channel_info channel_info[40]; +} __packed; + +struct start_cmd { + u8 sta_mac_addr[ETH_ALEN]; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bss_type; + __le16 bcn_period; + u8 dtim_period; + union ss_params ss_param_set; + union phy_params phy_param_set; + __le16 probe_delay; + __le16 cap_info; + u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; + struct rsn_ie rsn_ie; + struct rsn48_ie rsn48_ie; + struct wmm_param_elem wmm_param; + struct country country; + __le32 ap_rf_type; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */ +} __packed; + +struct hostcmd_cmd_ap_beacon { + struct hostcmd_header cmd_hdr; + struct start_cmd start_cmd; +} __packed; + +/* HOSTCMD_CMD_SET_NEW_STN */ +struct add_ht_info { + u8 control_chnl; + u8 add_chnl; + __le16 op_mode; + __le16 stbc; +} __packed; + +struct peer_info { + __le32 legacy_rate_bitmap; + u8 ht_rates[4]; + __le16 cap_info; + __le16 ht_cap_info; + u8 mac_ht_param_info; + u8 mrvl_sta; + struct add_ht_info add_ht_info; + __le32 tx_bf_capabilities; /* EXBF_SUPPORT */ + __le32 vht_max_rx_mcs; + __le32 vht_cap; + /* 0:20Mhz, 1:40Mhz, 2:80Mhz, 3:160 or 80+80Mhz */ + u8 vht_rx_channel_width; +} __packed; + +struct hostcmd_cmd_set_new_stn { + struct hostcmd_header cmd_hdr; + __le16 aid; + u8 mac_addr[ETH_ALEN]; + __le16 stn_id; + __le16 action; + __le16 reserved; + struct peer_info peer_info; + /* UAPSD_SUPPORT */ + u8 qos_info; + u8 is_qos_sta; + __le32 fw_sta_ptr; +} __packed; + +/* HOSTCMD_CMD_SET_APMODE */ +struct hostcmd_cmd_set_apmode { + struct hostcmd_header cmd_hdr; + u8 apmode; +} __packed; + +/* HOSTCMD_CMD_UPDATE_ENCRYPTION */ +struct hostcmd_cmd_update_encryption { + struct hostcmd_header cmd_hdr; + /* Action type - see encr_action_type */ + __le32 action_type; /* encr_action_type */ + /* size of the data buffer attached. */ + __le32 data_length; + u8 mac_addr[ETH_ALEN]; + u8 action_data[1]; +} __packed; + +struct wep_type_key { + /* WEP key material (max 128bit) */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; +} __packed; + +struct encr_tkip_seqcnt { + __le16 low; + __le32 high; +} __packed; + +struct tkip_type_key { + /* TKIP Key material. Key type (group or pairwise key) is + * determined by flags + */ + /* in KEY_PARAM_SET structure. */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; + /* MIC keys */ + u8 tkip_tx_mic_key[MIC_KEY_LENGTH]; + u8 tkip_rx_mic_key[MIC_KEY_LENGTH]; + struct encr_tkip_seqcnt tkip_rsc; + struct encr_tkip_seqcnt tkip_tsc; +} __packed; + +struct aes_type_key { + /* AES Key material */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; +} __packed; + +union mwl_key_type { + struct wep_type_key wep_key; + struct tkip_type_key tkip_key; + struct aes_type_key aes_key; +} __packed; + +struct key_param_set { + /* Total length of this structure (Key is variable size array) */ + __le16 length; + /* Key type - WEP, TKIP or AES-CCMP. */ + /* See definitions above */ + __le16 key_type_id; + /* key flags (ENCR_KEY_FLAG_XXX_ */ + __le32 key_info; + /* For WEP only - actual key index */ + __le32 key_index; + /* Size of the key */ + __le16 key_len; + /* Key material (variable size array) */ + union mwl_key_type key; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct hostcmd_cmd_set_key { + struct hostcmd_header cmd_hdr; + /* Action type - see encr_action_type */ + __le32 action_type; /* encr_action_type */ + /* size of the data buffer attached. */ + __le32 data_length; + /* data buffer - maps to one KEY_PARAM_SET structure */ + struct key_param_set key_param; +} __packed; + +/* HOSTCMD_CMD_BASTREAM */ +#define BA_TYPE_MASK 0x00000001 +#define BA_DIRECTION_MASK 0x00000006 +#define BA_DIRECTION_SHIFT 1 + +struct ba_context { + __le32 context; +} __packed; + +/* parameters for block ack creation */ +struct create_ba_params { + /* BA Creation flags - see above */ + __le32 flags; + /* idle threshold */ + __le32 idle_thrs; + /* block ack transmit threshold (after how many pkts should we + * send BAR?) + */ + __le32 bar_thrs; + /* receiver window size */ + __le32 window_size; + /* MAC Address of the BA partner */ + u8 peer_mac_addr[ETH_ALEN]; + /* Dialog Token */ + u8 dialog_token; + /* TID for the traffic stream in this BA */ + u8 tid; + /* shared memory queue ID (not sure if this is required) */ + u8 queue_id; + u8 param_info; + /* returned by firmware - firmware context pointer. */ + /* this context pointer will be passed to firmware for all + * future commands. + */ + struct ba_context fw_ba_context; + u8 reset_seq_no; /** 0 or 1**/ + __le16 current_seq; + /* This is for virtual station in Sta proxy mode for V6FW */ + u8 sta_src_mac_addr[ETH_ALEN]; +} __packed; + +/* new transmit sequence number information */ +struct ba_update_seq_num { + /* BA flags - see above */ + __le32 flags; + /* returned by firmware in the create ba stream response */ + struct ba_context fw_ba_context; + /* new sequence number for this block ack stream */ + __le16 ba_seq_num; +} __packed; + +struct ba_stream_context { + /* BA Stream flags */ + __le32 flags; + /* returned by firmware in the create ba stream response */ + struct ba_context fw_ba_context; +} __packed; + +union ba_info { + /* information required to create BA Stream... */ + struct create_ba_params create_params; + /* update starting/new sequence number etc. */ + struct ba_update_seq_num updt_seq_num; + /* destroy an existing stream... */ + struct ba_stream_context destroy_params; + /* destroy an existing stream... */ + struct ba_stream_context flush_params; +} __packed; + +struct hostcmd_cmd_bastream { + struct hostcmd_header cmd_hdr; + __le32 action_type; + union ba_info ba_info; +} __packed; + +/* HOSTCMD_CMD_DWDS_ENABLE */ +struct hostcmd_cmd_dwds_enable { + struct hostcmd_header cmd_hdr; + __le32 enable; /* 0 -- Disable. or 1 -- Enable. */ +} __packed; + +/* HOSTCMD_CMD_FW_FLUSH_TIMER */ +struct hostcmd_cmd_fw_flush_timer { + struct hostcmd_header cmd_hdr; + /* 0 -- Disable. > 0 -- holds time value in usecs. */ + __le32 value; +} __packed; + +/* HOSTCMD_CMD_SET_CDD */ +struct hostcmd_cmd_set_cdd { + struct hostcmd_header cmd_hdr; + __le32 enable; +} __packed; + +#endif /* _hostcmd_h_ */ diff --git a/drivers/net/wireless/mwlwifi/isr.c b/drivers/net/wireless/mwlwifi/isr.c new file mode 100644 index 0000000..1cc0c36 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/isr.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements interrupt related functions. */ + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "isr.h" + +#define INVALID_WATCHDOG 0xAA + +irqreturn_t mwl_isr(int irq, void *dev_id) +{ + struct ieee80211_hw *hw = dev_id; + struct mwl_priv *priv; + void *int_status_mask; + unsigned int int_status, clr_status; + u32 status; + + priv = hw->priv; + + int_status_mask = priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK; + + int_status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + + if (int_status == 0x00000000) + return IRQ_NONE; + + if (int_status == 0xffffffff) { + wiphy_warn(hw->wiphy, "card plugged out???"); + } else { + clr_status = int_status; + + if (int_status & MACREG_A2HRIC_BIT_TX_DONE) { + int_status &= ~MACREG_A2HRIC_BIT_TX_DONE; + + if (!priv->is_tx_schedule) { + status = readl(int_status_mask); + writel((status & ~MACREG_A2HRIC_BIT_TX_DONE), + int_status_mask); + tasklet_schedule(&priv->tx_task); + priv->is_tx_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BIT_RX_RDY) { + int_status &= ~MACREG_A2HRIC_BIT_RX_RDY; + + if (!priv->is_rx_schedule) { + status = readl(int_status_mask); + writel((status & ~MACREG_A2HRIC_BIT_RX_RDY), + int_status_mask); + tasklet_schedule(&priv->rx_task); + priv->is_rx_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BA_WATCHDOG) { + status = readl(int_status_mask); + writel((status & ~MACREG_A2HRIC_BA_WATCHDOG), + int_status_mask); + int_status &= ~MACREG_A2HRIC_BA_WATCHDOG; + ieee80211_queue_work(hw, &priv->watchdog_ba_handle); + } + + writel(~clr_status, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + } + + return IRQ_HANDLED; +} + +void mwl_watchdog_ba_events(struct work_struct *work) +{ + int rc; + u8 bitmap = 0, stream_index; + struct mwl_ampdu_stream *streams; + struct mwl_priv *priv = + container_of(work, struct mwl_priv, watchdog_ba_handle); + struct ieee80211_hw *hw = priv->hw; + u32 status; + + rc = mwl_fwcmd_get_watchdog_bitmap(priv->hw, &bitmap); + + if (rc) + goto done; + + spin_lock(&priv->stream_lock); + + /* the bitmap is the hw queue number. Map it to the ampdu queue. */ + if (bitmap != INVALID_WATCHDOG) { + if (bitmap == SYSADPT_TX_AMPDU_QUEUES) + stream_index = 0; + else if (bitmap > SYSADPT_TX_AMPDU_QUEUES) + stream_index = bitmap - SYSADPT_TX_AMPDU_QUEUES; + else + stream_index = bitmap + 3; /** queue 0 is stream 3*/ + + if (bitmap != 0xFF) { + /* Check if the stream is in use before disabling it */ + streams = &priv->ampdu[stream_index]; + + if (streams->state == AMPDU_STREAM_ACTIVE) { + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + spin_unlock(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, stream_index); + spin_lock(&priv->stream_lock); + } + } else { + for (stream_index = 0; + stream_index < SYSADPT_TX_AMPDU_QUEUES; + stream_index++) { + streams = &priv->ampdu[stream_index]; + + if (streams->state != AMPDU_STREAM_ACTIVE) + continue; + + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + spin_unlock(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, stream_index); + spin_lock(&priv->stream_lock); + } + } + } + + spin_unlock(&priv->stream_lock); + +done: + + status = readl(priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status | MACREG_A2HRIC_BA_WATCHDOG, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); +} diff --git a/drivers/net/wireless/mwlwifi/isr.h b/drivers/net/wireless/mwlwifi/isr.h new file mode 100644 index 0000000..2598115 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/isr.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines interrupt related functions. */ + +#ifndef _isr_h_ +#define _isr_h_ + +#include <linux/interrupt.h> + +irqreturn_t mwl_isr(int irq, void *dev_id); +void mwl_watchdog_ba_events(struct work_struct *work); + +#endif /* _isr_h_ */ diff --git a/drivers/net/wireless/mwlwifi/mac80211.c b/drivers/net/wireless/mwlwifi/mac80211.c new file mode 100644 index 0000000..ef53aa8 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/mac80211.c @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements mac80211 related functions. */ + +#include <linux/etherdevice.h> + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "tx.h" +#include "mac80211.h" +#include "isr.h" + +#define MWL_DRV_NAME KBUILD_MODNAME + +#define MAX_AMPDU_ATTEMPTS 5 + +static const struct ieee80211_rate mwl_rates_24[] = { + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4, }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 220, .hw_value = 44, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_rate mwl_rates_50[] = { + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static void mwl_mac80211_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + if (!priv->radio_on) { + wiphy_warn(hw->wiphy, + "dropped TX frame since radio disabled"); + dev_kfree_skb_any(skb); + return; + } + + mwl_tx_xmit(hw, control, skb); +} + +static int mwl_mac80211_start(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + priv = hw->priv; + + rc = request_irq(priv->pdev->irq, mwl_isr, + IRQF_SHARED, MWL_DRV_NAME, hw); + if (rc) { + priv->irq = -1; + wiphy_err(hw->wiphy, "fail to register IRQ handler"); + return rc; + } + priv->irq = priv->pdev->irq; + + /* Enable TX reclaim and RX tasklets. */ + tasklet_enable(&priv->tx_task); + tasklet_enable(&priv->rx_task); + + /* Enable interrupts */ + mwl_fwcmd_int_enable(hw); + + rc = mwl_fwcmd_radio_enable(hw); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_wmm_mode(hw, true); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_dwds_stamode(hw, true); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_fw_flush_timer(hw, 0); + if (rc) + goto fwcmd_fail; + + ieee80211_wake_queues(hw); + return 0; + +fwcmd_fail: + mwl_fwcmd_int_disable(hw); + free_irq(priv->pdev->irq, hw); + priv->irq = -1; + tasklet_disable(&priv->tx_task); + tasklet_disable(&priv->rx_task); + + return rc; +} + +static void mwl_mac80211_stop(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + mwl_fwcmd_radio_disable(hw); + + ieee80211_stop_queues(hw); + + /* Disable interrupts */ + mwl_fwcmd_int_disable(hw); + + if (priv->irq != -1) { + free_irq(priv->pdev->irq, hw); + priv->irq = -1; + } + + cancel_work_sync(&priv->watchdog_ba_handle); + + /* Disable TX reclaim and RX tasklets. */ + tasklet_disable(&priv->tx_task); + tasklet_disable(&priv->rx_task); + + /* Return all skbs to mac80211 */ + mwl_tx_done((unsigned long)hw); +} + +static int mwl_mac80211_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + u32 macids_supported; + int macid; + unsigned long flags; + + switch (vif->type) { + case NL80211_IFTYPE_AP: + macids_supported = priv->ap_macids_supported; + break; + case NL80211_IFTYPE_STATION: + macids_supported = priv->sta_macids_supported; + break; + default: + return -EINVAL; + } + + macid = ffs(macids_supported & ~priv->macids_used); + + if (!macid--) { + wiphy_warn(hw->wiphy, "no macid can be allocated"); + return -EBUSY; + } + + /* Setup driver private area. */ + mwl_vif = mwl_dev_get_vif(vif); + memset(mwl_vif, 0, sizeof(*mwl_vif)); + mwl_vif->vif = vif; + mwl_vif->macid = macid; + mwl_vif->seqno = 0; + mwl_vif->is_hw_crypto_enabled = false; + mwl_vif->is_sta = false; + mwl_vif->beacon_info.valid = false; + mwl_vif->iv16 = 1; + mwl_vif->iv32 = 0; + mwl_vif->keyidx = 0; + + if (vif->type == NL80211_IFTYPE_STATION) { + ether_addr_copy(mwl_vif->sta_mac, vif->addr); + mwl_vif->is_sta = true; + mwl_fwcmd_bss_start(hw, vif, true); + mwl_fwcmd_set_infra_mode(hw, vif); + mwl_fwcmd_set_mac_addr_client(hw, vif, vif->addr); + } + + if (vif->type == NL80211_IFTYPE_AP) { + ether_addr_copy(mwl_vif->bssid, vif->addr); + mwl_fwcmd_set_new_stn_add_self(hw, vif); + } + + priv->macids_used |= 1 << mwl_vif->macid; + spin_lock_irqsave(&priv->vif_lock, flags); + list_add_tail(&mwl_vif->list, &priv->vif_list); + spin_unlock_irqrestore(&priv->vif_lock, flags); + + return 0; +} + +static void mwl_mac80211_remove_vif(struct mwl_priv *priv, struct mwl_vif *vif) +{ + unsigned long flags; + int num; + struct sk_buff *skb, *tmp; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + + if (!priv->macids_used) + return; + + for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + spin_lock_irqsave(&priv->txq[num].lock, flags); + skb_queue_walk_safe(&priv->txq[num], skb, tmp) { + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + if (tx_ctrl->vif == vif) { + __skb_unlink(skb, &priv->txq[num]); + dev_kfree_skb_any(skb); + } + } + spin_unlock_irqrestore(&priv->txq[num].lock, flags); + } + + priv->macids_used &= ~(1 << vif->macid); + spin_lock_irqsave(&priv->vif_lock, flags); + list_del(&vif->list); + spin_unlock_irqrestore(&priv->vif_lock, flags); +} + +static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + + mwl_vif = mwl_dev_get_vif(vif); + + if (vif->type == NL80211_IFTYPE_STATION) + mwl_fwcmd_remove_mac_addr(hw, vif, vif->addr); + + if (vif->type == NL80211_IFTYPE_AP) + mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr); + + mwl_mac80211_remove_vif(priv, mwl_vif); +} + +static int mwl_mac80211_config(struct ieee80211_hw *hw, + u32 changed) +{ + struct ieee80211_conf *conf = &hw->conf; + int rc; + + wiphy_debug(hw->wiphy, "change: 0x%x", changed); + + if (conf->flags & IEEE80211_CONF_IDLE) + rc = mwl_fwcmd_radio_disable(hw); + else + rc = mwl_fwcmd_radio_enable(hw); + + if (rc) + goto out; + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + int rate = 0; + + if (conf->chandef.chan->band == IEEE80211_BAND_2GHZ) { + mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED); + rate = mwl_rates_24[0].hw_value; + } else if (conf->chandef.chan->band == IEEE80211_BAND_5GHZ) { + mwl_fwcmd_set_apmode(hw, AP_MODE_11AC); + rate = mwl_rates_50[0].hw_value; + } + + rc = mwl_fwcmd_set_rf_channel(hw, conf); + if (rc) + goto out; + rc = mwl_fwcmd_use_fixed_rate(hw, rate, rate); + if (rc) + goto out; + rc = mwl_fwcmd_max_tx_power(hw, conf, 0); + if (rc) + goto out; + rc = mwl_fwcmd_tx_power(hw, conf, 0); + if (rc) + goto out; + rc = mwl_fwcmd_set_cdd(hw); + } + +out: + + return rc; +} + +static void mwl_mac80211_bss_info_changed_sta(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + if (changed & BSS_CHANGED_ERP_PREAMBLE) + mwl_fwcmd_set_radio_preamble(hw, + vif->bss_conf.use_short_preamble); + + if ((changed & BSS_CHANGED_ASSOC) && vif->bss_conf.assoc) + mwl_fwcmd_set_aid(hw, vif, (u8 *)vif->bss_conf.bssid, + vif->bss_conf.aid); +} + +static void mwl_mac80211_bss_info_changed_ap(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + if (changed & BSS_CHANGED_ERP_PREAMBLE) + mwl_fwcmd_set_radio_preamble(hw, + vif->bss_conf.use_short_preamble); + + if (changed & BSS_CHANGED_BASIC_RATES) { + int idx; + int rate; + + /* Use lowest supported basic rate for multicasts + * and management frames (such as probe responses -- + * beacons will always go out at 1 Mb/s). + */ + idx = ffs(vif->bss_conf.basic_rates); + if (idx) + idx--; + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rate = mwl_rates_24[idx].hw_value; + else + rate = mwl_rates_50[idx].hw_value; + + mwl_fwcmd_use_fixed_rate(hw, rate, rate); + } + + if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) { + struct sk_buff *skb; + + skb = ieee80211_beacon_get(hw, vif); + + if (skb) { + mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len); + dev_kfree_skb_any(skb); + } + + if ((info->ssid[0] != '\0') && + (info->ssid_len != 0) && + (!info->hidden_ssid)) + mwl_fwcmd_broadcast_ssid_enable(hw, vif, true); + else + mwl_fwcmd_broadcast_ssid_enable(hw, vif, false); + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) + mwl_fwcmd_bss_start(hw, vif, info->enable_beacon); +} + +static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + wiphy_debug(hw->wiphy, "interface: %d, change: 0x%x", + vif->type, changed); + + if (vif->type == NL80211_IFTYPE_STATION) + mwl_mac80211_bss_info_changed_sta(hw, vif, info, changed); + + if (vif->type == NL80211_IFTYPE_AP) + mwl_mac80211_bss_info_changed_ap(hw, vif, info, changed); +} + +static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + /* AP firmware doesn't allow fine-grained control over + * the receive filter. + */ + *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC; +} + +static int mwl_mac80211_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd_param, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct mwl_vif *mwl_vif; + int rc = 0; + u8 encr_type; + u8 *addr; + + mwl_vif = mwl_dev_get_vif(vif); + + if (!sta) { + addr = vif->addr; + } else { + addr = sta->addr; + if (mwl_vif->is_sta) + ether_addr_copy(mwl_vif->bssid, addr); + } + + if (cmd_param == SET_KEY) { + rc = mwl_fwcmd_encryption_set_key(hw, vif, addr, key); + + if (rc) + goto out; + + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40) || + (key->cipher == WLAN_CIPHER_SUITE_WEP104)) { + encr_type = ENCR_TYPE_WEP; + } else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) { + encr_type = ENCR_TYPE_AES; + if ((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) == 0) { + if (!mwl_vif->is_sta) + mwl_vif->keyidx = key->keyidx; + } + } else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + encr_type = ENCR_TYPE_TKIP; + } else { + encr_type = ENCR_TYPE_DISABLE; + } + + rc = mwl_fwcmd_update_encryption_enable(hw, vif, addr, + encr_type); + if (rc) + goto out; + + mwl_vif->is_hw_crypto_enabled = true; + } else { + rc = mwl_fwcmd_encryption_remove_key(hw, vif, addr, key); + if (rc) + goto out; + } + +out: + + return rc; +} + +static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw, + u32 value) +{ + return mwl_fwcmd_set_rts_threshold(hw, value); +} + +static int mwl_mac80211_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct mwl_sta *sta_info; + unsigned long flags; + struct ieee80211_key_conf *key; + int rc; + int i; + + mwl_vif = mwl_dev_get_vif(vif); + sta_info = mwl_dev_get_sta(sta); + + memset(sta_info, 0, sizeof(*sta_info)); + sta_info->sta = sta; + if (sta->ht_cap.ht_supported) { + sta_info->is_ampdu_allowed = true; + sta_info->is_amsdu_allowed = true; + if (sta->ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU) + sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_8K; + else + sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_4K; + } + sta_info->iv16 = 1; + sta_info->iv32 = 0; + spin_lock_irqsave(&priv->sta_lock, flags); + list_add_tail(&sta_info->list, &priv->sta_list); + spin_unlock_irqrestore(&priv->sta_lock, flags); + + if (mwl_vif->is_sta) + mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); + + rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta); + + for (i = 0; i < NUM_WEP_KEYS; i++) { + key = (struct ieee80211_key_conf *)mwl_vif->wep_key_conf[i].key; + + if (mwl_vif->wep_key_conf[i].enabled) + mwl_mac80211_set_key(hw, SET_KEY, vif, sta, key); + } + + return rc; +} + +static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_sta *sta_info; + unsigned long flags; + int num; + struct sk_buff *skb, *tmp; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + int rc; + + sta_info = mwl_dev_get_sta(sta); + + for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + spin_lock_irqsave(&priv->txq[num].lock, flags); + skb_queue_walk_safe(&priv->txq[num], skb, tmp) { + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + if (tx_ctrl->sta == sta) { + __skb_unlink(skb, &priv->txq[num]); + dev_kfree_skb_any(skb); + } + } + spin_unlock_irqrestore(&priv->txq[num].lock, flags); + } + + rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); + + spin_lock_irqsave(&priv->sta_lock, flags); + list_del(&sta_info->list); + spin_unlock_irqrestore(&priv->sta_lock, flags); + + return rc; +} + +static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct mwl_priv *priv = hw->priv; + int rc = 0; + + BUG_ON(queue > SYSADPT_TX_WMM_QUEUES - 1); + + memcpy(&priv->wmm_params[queue], params, sizeof(*params)); + + if (!priv->wmm_enabled) { + rc = mwl_fwcmd_set_wmm_mode(hw, true); + priv->wmm_enabled = true; + } + + if (!rc) { + int q = SYSADPT_TX_WMM_QUEUES - 1 - queue; + + rc = mwl_fwcmd_set_edca_params(hw, q, + params->cw_min, params->cw_max, + params->aifs, params->txop); + } + + return rc; +} + +static int mwl_mac80211_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + return mwl_fwcmd_get_stat(hw, stats); +} + +static int mwl_mac80211_get_survey(struct ieee80211_hw *hw, + int idx, + struct survey_info *survey) +{ + struct mwl_priv *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->noise; + + return 0; +} + +static int mwl_mac80211_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size) +{ + int i, rc = 0; + struct mwl_priv *priv = hw->priv; + struct mwl_ampdu_stream *stream; + u8 *addr = sta->addr, idx; + struct mwl_sta *sta_info; + + sta_info = mwl_dev_get_sta(sta); + + spin_lock(&priv->stream_lock); + + stream = mwl_fwcmd_lookup_stream(hw, addr, tid); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + break; + case IEEE80211_AMPDU_TX_START: + /* By the time we get here the hw queues may contain outgoing + * packets for this RA/TID that are not part of this BA + * session. The hw will assign sequence numbers to these + * packets as they go out. So if we query the hw for its next + * sequence number and use that for the SSN here, it may end up + * being wrong, which will lead to sequence number mismatch at + * the recipient. To avoid this, we reset the sequence number + * to O for the first MPDU in this BA stream. + */ + *ssn = 0; + + if (!stream) { + /* This means that somebody outside this driver called + * ieee80211_start_tx_ba_session. This is unexpected + * because we do our own rate control. Just warn and + * move on. + */ + stream = mwl_fwcmd_add_stream(hw, sta, tid); + } + + if (!stream) { + wiphy_warn(hw->wiphy, "no stream found"); + rc = -EBUSY; + break; + } + + stream->state = AMPDU_STREAM_IN_PROGRESS; + + /* Release the lock before we do the time consuming stuff */ + spin_unlock(&priv->stream_lock); + + for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) { + /* Check if link is still valid */ + if (!sta_info->is_ampdu_allowed) { + spin_lock(&priv->stream_lock); + mwl_fwcmd_remove_stream(hw, stream); + spin_unlock(&priv->stream_lock); + wiphy_warn(hw->wiphy, + "link is no valid now"); + return -EBUSY; + } + + rc = mwl_fwcmd_check_ba(hw, stream, vif); + + if (!rc) + break; + + mdelay(1000); + } + + spin_lock(&priv->stream_lock); + + if (rc) { + mwl_fwcmd_remove_stream(hw, stream); + wiphy_err(hw->wiphy, "error code: %d", rc); + rc = -EBUSY; + break; + } + + ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + if (stream) { + if (stream->state == AMPDU_STREAM_ACTIVE) { + idx = stream->idx; + spin_unlock(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, idx); + spin_lock(&priv->stream_lock); + } + + mwl_fwcmd_remove_stream(hw, stream); + } + + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + BUG_ON(stream->state != AMPDU_STREAM_IN_PROGRESS); + spin_unlock(&priv->stream_lock); + rc = mwl_fwcmd_create_ba(hw, stream, buf_size, vif); + spin_lock(&priv->stream_lock); + + if (!rc) { + stream->state = AMPDU_STREAM_ACTIVE; + } else { + idx = stream->idx; + spin_unlock(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, idx); + spin_lock(&priv->stream_lock); + mwl_fwcmd_remove_stream(hw, stream); + } + break; + default: + rc = -ENOTSUPP; + } + + spin_unlock(&priv->stream_lock); + + return rc; +} + +const struct ieee80211_ops mwl_mac80211_ops = { + .tx = mwl_mac80211_tx, + .start = mwl_mac80211_start, + .stop = mwl_mac80211_stop, + .add_interface = mwl_mac80211_add_interface, + .remove_interface = mwl_mac80211_remove_interface, + .config = mwl_mac80211_config, + .bss_info_changed = mwl_mac80211_bss_info_changed, + .configure_filter = mwl_mac80211_configure_filter, + .set_key = mwl_mac80211_set_key, + .set_rts_threshold = mwl_mac80211_set_rts_threshold, + .sta_add = mwl_mac80211_sta_add, + .sta_remove = mwl_mac80211_sta_remove, + .conf_tx = mwl_mac80211_conf_tx, + .get_stats = mwl_mac80211_get_stats, + .get_survey = mwl_mac80211_get_survey, + .ampdu_action = mwl_mac80211_ampdu_action, +}; diff --git a/drivers/net/wireless/mwlwifi/mac80211.h b/drivers/net/wireless/mwlwifi/mac80211.h new file mode 100644 index 0000000..0267dd85 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/mac80211.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines mac80211 related functions. */ + +#ifndef _mac80211_h_ +#define _mac80211_h_ + +#include <net/mac80211.h> + +extern const struct ieee80211_ops mwl_mac80211_ops; + +#endif /* _mac80211_h_ */ diff --git a/drivers/net/wireless/mwlwifi/main.c b/drivers/net/wireless/mwlwifi/main.c new file mode 100644 index 0000000..c01d4cc --- /dev/null +++ b/drivers/net/wireless/mwlwifi/main.c @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements main functions of this module. */ + +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include "sysadpt.h" +#include "dev.h" +#include "fwdl.h" +#include "fwcmd.h" +#include "tx.h" +#include "rx.h" +#include "mac80211.h" +#include "isr.h" + +#define MWL_DESC "Marvell 802.11ac Wireless Network Driver" +#define MWL_DEV_NAME "Marvell 802.11ac Adapter" +#define MWL_DRV_NAME KBUILD_MODNAME +#define MWL_DRV_VERSION "10.3.0.4" + +#define FILE_PATH_LEN 64 +#define CMD_BUF_SIZE 0x4000 + +static struct pci_device_id mwl_pci_id_tbl[] = { + { PCI_VDEVICE(MARVELL, 0x2a55), .driver_data = MWL8864, }, + { PCI_VDEVICE(MARVELL, 0x2b38), .driver_data = MWL8897, }, + { }, +}; + +static struct mwl_chip_info mwl_chip_tbl[] = { + [MWL8864] = { + .part_name = "88W8864", + .fw_image = "mwlwifi/88W8864.bin", + }, + [MWL8897] = { + .part_name = "88W8897", + .fw_image = "mwlwifi/88W8897.bin", + }, +}; + +static const struct ieee80211_channel mwl_channels_24[] = { + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, }, +}; + +static const struct ieee80211_rate mwl_rates_24[] = { + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4, }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 220, .hw_value = 44, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_channel mwl_channels_50[] = { + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5260, .hw_value = 52, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5280, .hw_value = 56, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5300, .hw_value = 60, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5320, .hw_value = 64, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5500, .hw_value = 100, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5520, .hw_value = 104, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5540, .hw_value = 108, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5560, .hw_value = 112, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5580, .hw_value = 116, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5600, .hw_value = 120, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5620, .hw_value = 124, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5640, .hw_value = 128, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5660, .hw_value = 132, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5680, .hw_value = 136, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5700, .hw_value = 140, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5720, .hw_value = 144, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5745, .hw_value = 149, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5765, .hw_value = 153, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5785, .hw_value = 157, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5805, .hw_value = 161, }, +}; + +static const struct ieee80211_rate mwl_rates_50[] = { + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_iface_limit ap_if_limits[] = { + { .max = SYSADPT_NUM_OF_AP, .types = BIT(NL80211_IFTYPE_AP) }, + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION) }, +}; + +static const struct ieee80211_iface_combination ap_if_comb = { + .limits = ap_if_limits, + .n_limits = ARRAY_SIZE(ap_if_limits), + .max_interfaces = SYSADPT_NUM_OF_AP, + .num_different_channels = 1, +}; + +static int mwl_alloc_pci_resource(struct mwl_priv *priv) +{ + struct pci_dev *pdev; + u32 phys_addr = 0; + u32 flags; + void *phys_addr1[2]; + void *phys_addr2[2]; + + pdev = priv->pdev; + + phys_addr = pci_resource_start(pdev, 0); + flags = pci_resource_flags(pdev, 0); + + priv->next_bar_num = 1; /* 32-bit */ + + if (flags & 0x04) + priv->next_bar_num = 2; /* 64-bit */ + + if (!request_mem_region(phys_addr, pci_resource_len(pdev, 0), + MWL_DRV_NAME)) { + wiphy_err(priv->hw->wiphy, + "%s: cannot reserve PCI memory region 0", + MWL_DRV_NAME); + goto err_reserve_mem_region_bar0; + } + + phys_addr1[0] = ioremap(phys_addr, pci_resource_len(pdev, 0)); + phys_addr1[1] = 0; + + priv->iobase0 = phys_addr1[0]; + if (!priv->iobase0) { + wiphy_err(priv->hw->wiphy, + "%s: cannot remap PCI memory region 0", + MWL_DRV_NAME); + goto err_release_mem_region_bar0; + } + + wiphy_info(priv->hw->wiphy, "priv->iobase0 = %x", + (unsigned int)priv->iobase0); + + phys_addr = pci_resource_start(pdev, priv->next_bar_num); + + if (!request_mem_region(phys_addr, + pci_resource_len(pdev, priv->next_bar_num), + MWL_DRV_NAME)) { + wiphy_err(priv->hw->wiphy, + "%s: cannot reserve PCI memory region 1", + MWL_DRV_NAME); + goto err_iounmap_iobase0; + } + + phys_addr2[0] = ioremap(phys_addr, + pci_resource_len(pdev, priv->next_bar_num)); + phys_addr2[1] = 0; + priv->iobase1 = phys_addr2[0]; + + if (!priv->iobase1) { + wiphy_err(priv->hw->wiphy, + "%s: cannot remap PCI memory region 1", + MWL_DRV_NAME); + goto err_release_mem_region_bar1; + } + + wiphy_info(priv->hw->wiphy, "priv->iobase1 = %x", + (unsigned int)priv->iobase1); + + priv->pcmd_buf = + (unsigned short *)dma_alloc_coherent(&priv->pdev->dev, + CMD_BUF_SIZE, + &priv->pphys_cmd_buf, + GFP_KERNEL); + + if (!priv->pcmd_buf) { + wiphy_err(priv->hw->wiphy, + "%s: cannot alloc memory for command buffer", + MWL_DRV_NAME); + goto err_iounmap_iobase1; + } + + wiphy_info(priv->hw->wiphy, + "priv->pcmd_buf = %x priv->pphys_cmd_buf = %x", + (unsigned int)priv->pcmd_buf, + (unsigned int)priv->pphys_cmd_buf); + + memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); + + return 0; + +err_iounmap_iobase1: + + iounmap(priv->iobase1); + +err_release_mem_region_bar1: + + release_mem_region(pci_resource_start(pdev, 1), + pci_resource_len(pdev, 1)); + +err_iounmap_iobase0: + + iounmap(priv->iobase0); + +err_release_mem_region_bar0: + + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + +err_reserve_mem_region_bar0: + + wiphy_err(priv->hw->wiphy, "pci alloc fail"); + + return -EIO; +} + +static void mwl_free_pci_resource(struct mwl_priv *priv) +{ + struct pci_dev *pdev; + + pdev = priv->pdev; + + iounmap(priv->iobase0); + iounmap(priv->iobase1); + release_mem_region(pci_resource_start(pdev, priv->next_bar_num), + pci_resource_len(pdev, priv->next_bar_num)); + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + dma_free_coherent(&priv->pdev->dev, CMD_BUF_SIZE, + priv->pcmd_buf, priv->pphys_cmd_buf); +} + +static int mwl_init_firmware(struct mwl_priv *priv, char *fw_name) +{ + struct pci_dev *pdev; + int rc = 0; + + pdev = priv->pdev; + + rc = request_firmware(&priv->fw_ucode, fw_name, &priv->pdev->dev); + if (rc) { + wiphy_err(priv->hw->wiphy, + "%s: cannot load firmware image <%s>", + MWL_DRV_NAME, fw_name); + goto err_load_fw; + } + + rc = mwl_fwdl_download_firmware(priv->hw); + if (rc) { + wiphy_err(priv->hw->wiphy, + "%s: cannot download firmware image <%s>", + MWL_DRV_NAME, fw_name); + goto err_download_fw; + } + + return rc; + +err_download_fw: + + release_firmware(priv->fw_ucode); + +err_load_fw: + + wiphy_err(priv->hw->wiphy, "firmware init fail"); + + return rc; +} + +static void mwl_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw; + struct mwl_priv *priv; + struct property *prop; + struct property *fcc_prop = NULL; + struct property *etsi_prop = NULL; + struct property *specific_prop = NULL; + u32 prop_value; + int i, j, k; + + hw = (struct ieee80211_hw *)wiphy_priv(wiphy); + priv = hw->priv; + + if (priv->pwr_node) { + for_each_property_of_node(priv->pwr_node, prop) { + if (strcmp(prop->name, "FCC") == 0) + fcc_prop = prop; + if (strcmp(prop->name, "ETSI") == 0) + etsi_prop = prop; + if ((prop->name[0] == request->alpha2[0]) && + (prop->name[1] == request->alpha2[1])) + specific_prop = prop; + } + + prop = NULL; + + if (specific_prop) { + prop = specific_prop; + } else { + if (request->dfs_region == NL80211_DFS_ETSI) + prop = etsi_prop; + else + prop = fcc_prop; + } + + if (prop) { + /* Reset the whole table */ + for (i = 0; i < SYSADPT_MAX_NUM_CHANNELS; i++) + memset(&priv->tx_pwr_tbl[i], 0, + sizeof(struct mwl_tx_pwr_tbl)); + + /* Load related power table */ + i = 0; + j = 0; + while (i < prop->length) { + prop_value = + be32_to_cpu(*(__be32 *) + (prop->value + i)); + priv->tx_pwr_tbl[j].channel = prop_value; + i += 4; + prop_value = + be32_to_cpu(*(__be32 *) + (prop->value + i)); + priv->tx_pwr_tbl[j].setcap = prop_value; + i += 4; + for (k = 0; k < SYSADPT_TX_POWER_LEVEL_TOTAL; + k++) { + prop_value = + be32_to_cpu(*(__be32 *) + (prop->value + i)); + priv->tx_pwr_tbl[j].tx_power[k] = + prop_value; + i += 4; + } + prop_value = + be32_to_cpu(*(__be32 *) + (prop->value + i)); + priv->tx_pwr_tbl[j].cdd = prop_value; + i += 4; + prop_value = + be32_to_cpu(*(__be32 *) + (prop->value + i)); + priv->tx_pwr_tbl[j].txantenna2 = prop_value; + i += 4; + j++; + } + + /* Dump loaded power tabel */ + wiphy_info(hw->wiphy, "%s: %s\n", dev_name(&wiphy->dev), + prop->name); + for (i = 0; i < SYSADPT_MAX_NUM_CHANNELS; i++) { + struct mwl_tx_pwr_tbl *pwr_tbl; + char disp_buf[64]; + char *disp_ptr; + + pwr_tbl = &priv->tx_pwr_tbl[i]; + if (pwr_tbl->channel == 0) + break; + wiphy_info(hw->wiphy, + "Channel: %d: 0x%x 0x%x 0x%x", + pwr_tbl->channel, + pwr_tbl->setcap, + pwr_tbl->cdd, + pwr_tbl->txantenna2); + disp_ptr = disp_buf; + for (j = 0; j < SYSADPT_TX_POWER_LEVEL_TOTAL; + j++) { + disp_ptr += + sprintf(disp_ptr, "%x ", + pwr_tbl->tx_power[j]); + } + wiphy_info(hw->wiphy, "%s", disp_buf); + } + } + } +} + +static int mwl_process_of_dts(struct mwl_priv *priv) +{ + struct property *prop; + u32 prop_value; + + priv->disable_2g = false; + priv->disable_5g = false; + priv->antenna_tx = ANTENNA_TX_4_AUTO; + priv->antenna_rx = ANTENNA_RX_4_AUTO; + + priv->dt_node = + of_find_node_by_name(pci_bus_to_OF_node(priv->pdev->bus), + "mwlwifi"); + if (!priv->dt_node) + return -EPERM; + + /* look for all matching property names */ + for_each_property_of_node(priv->dt_node, prop) { + if (strcmp(prop->name, "marvell,2ghz") == 0) + priv->disable_2g = true; + if (strcmp(prop->name, "marvell,5ghz") == 0) + priv->disable_5g = true; + if (strcmp(prop->name, "marvell,chainmask") == 0) { + prop_value = be32_to_cpu(*((__be32 *)prop->value)); + if (prop_value == 2) + priv->antenna_tx = ANTENNA_TX_2; + + prop_value = be32_to_cpu(*((__be32 *) + (prop->value + 4))); + if (prop_value == 2) + priv->antenna_rx = ANTENNA_RX_2; + } + } + + priv->pwr_node = of_find_node_by_name(priv->dt_node, + "marvell,powertable"); + + wiphy_info(priv->hw->wiphy, + "2G: %s\n", priv->disable_2g ? "disable" : "enable"); + wiphy_info(priv->hw->wiphy, + "5G: %s\n", priv->disable_5g ? "disable" : "enable"); + + if (priv->antenna_tx == ANTENNA_TX_4_AUTO) + wiphy_info(priv->hw->wiphy, "TX: 4 antennas\n"); + else if (priv->antenna_tx == ANTENNA_TX_2) + wiphy_info(priv->hw->wiphy, "TX: 2 antennas\n"); + else + wiphy_info(priv->hw->wiphy, "TX: unknown\n"); + if (priv->antenna_rx == ANTENNA_RX_4_AUTO) + wiphy_info(priv->hw->wiphy, "RX: 4 antennas\n"); + else if (priv->antenna_rx == ANTENNA_RX_2) + wiphy_info(priv->hw->wiphy, "RX: 2 antennas\n"); + else + wiphy_info(priv->hw->wiphy, "RX: unknown\n"); + + return 0; +} + +static void mwl_set_ht_caps(struct mwl_priv *priv, + struct ieee80211_supported_band *band) +{ + struct ieee80211_hw *hw; + + hw = priv->hw; + + band->ht_cap.ht_supported = 1; + + band->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + band->ht_cap.cap |= IEEE80211_HT_CAP_SM_PS; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + band->ht_cap.mcs.rx_mask[0] = 0xff; + band->ht_cap.mcs.rx_mask[1] = 0xff; + if (priv->antenna_rx == ANTENNA_RX_4_AUTO) + band->ht_cap.mcs.rx_mask[2] = 0xff; + band->ht_cap.mcs.rx_mask[4] = 0x01; + + band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +static void mwl_set_vht_caps(struct mwl_priv *priv, + struct ieee80211_supported_band *band) +{ + band->vht_cap.vht_supported = 1; + + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; + band->vht_cap.cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + if (priv->antenna_rx == ANTENNA_RX_2) + band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xfffa); + else + band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xffea); + + if (priv->antenna_tx == ANTENNA_TX_2) + band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xfffa); + else + band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xffea); +} + +static void mwl_set_caps(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + + hw = priv->hw; + + /* set up band information for 2.4G */ + if (!priv->disable_2g) { + BUILD_BUG_ON(sizeof(priv->channels_24) != + sizeof(mwl_channels_24)); + memcpy(priv->channels_24, mwl_channels_24, + sizeof(mwl_channels_24)); + + BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl_rates_24)); + memcpy(priv->rates_24, mwl_rates_24, sizeof(mwl_rates_24)); + + priv->band_24.band = IEEE80211_BAND_2GHZ; + priv->band_24.channels = priv->channels_24; + priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24); + priv->band_24.bitrates = priv->rates_24; + priv->band_24.n_bitrates = ARRAY_SIZE(mwl_rates_24); + + mwl_set_ht_caps(priv, &priv->band_24); + mwl_set_vht_caps(priv, &priv->band_24); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band_24; + } + + /* set up band information for 5G */ + if (!priv->disable_5g) { + BUILD_BUG_ON(sizeof(priv->channels_50) != + sizeof(mwl_channels_50)); + memcpy(priv->channels_50, mwl_channels_50, + sizeof(mwl_channels_50)); + + BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl_rates_50)); + memcpy(priv->rates_50, mwl_rates_50, sizeof(mwl_rates_50)); + + priv->band_50.band = IEEE80211_BAND_5GHZ; + priv->band_50.channels = priv->channels_50; + priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); + priv->band_50.bitrates = priv->rates_50; + priv->band_50.n_bitrates = ARRAY_SIZE(mwl_rates_50); + + mwl_set_ht_caps(priv, &priv->band_50); + mwl_set_vht_caps(priv, &priv->band_50); + + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &priv->band_50; + } +} + +static int mwl_wl_init(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + int rc; + int i; + + hw = priv->hw; + + hw->extra_tx_headroom = SYSADPT_MIN_BYTES_HEADROOM; + hw->queues = SYSADPT_TX_WMM_QUEUES; + + /* Set rssi values to dBm */ + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, HAS_RATE_CONTROL); + + /* Ask mac80211 to not to trigger PS mode + * based on PM bit of incoming frames. + */ + ieee80211_hw_set(hw, AP_LINK_PS); + + hw->vif_data_size = sizeof(struct mwl_vif); + hw->sta_data_size = sizeof(struct mwl_sta); + + priv->ap_macids_supported = 0x0000ffff; + priv->sta_macids_supported = 0x00010000; + priv->macids_used = 0; + INIT_LIST_HEAD(&priv->vif_list); + INIT_LIST_HEAD(&priv->sta_list); + + /* Set default radio state, preamble and wmm */ + priv->radio_on = false; + priv->radio_short_preamble = false; + priv->wmm_enabled = false; + + priv->powinited = 0; + + /* Handle watchdog ba events */ + INIT_WORK(&priv->watchdog_ba_handle, mwl_watchdog_ba_events); + + tasklet_init(&priv->tx_task, (void *)mwl_tx_done, (unsigned long)hw); + tasklet_disable(&priv->tx_task); + tasklet_init(&priv->rx_task, (void *)mwl_rx_recv, (unsigned long)hw); + tasklet_disable(&priv->rx_task); + priv->txq_limit = SYSADPT_TX_QUEUE_LIMIT; + priv->is_tx_schedule = false; + priv->recv_limit = SYSADPT_RECEIVE_LIMIT; + priv->is_rx_schedule = false; + + spin_lock_init(&priv->tx_desc_lock); + spin_lock_init(&priv->fwcmd_lock); + spin_lock_init(&priv->vif_lock); + spin_lock_init(&priv->sta_lock); + spin_lock_init(&priv->stream_lock); + + rc = mwl_tx_init(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize TX", + MWL_DRV_NAME); + goto err_mwl_tx_init; + } + + rc = mwl_rx_init(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize RX", + MWL_DRV_NAME); + goto err_mwl_rx_init; + } + + rc = mwl_fwcmd_get_hw_specs(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to get HW specifications", + MWL_DRV_NAME); + goto err_get_hw_specs; + } + + SET_IEEE80211_PERM_ADDR(hw, priv->hw_data.mac_addr); + + writel(priv->desc_data[0].pphys_tx_ring, + priv->iobase0 + priv->desc_data[0].wcb_base); +#if SYSADPT_NUM_OF_DESC_DATA > 3 + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + writel(priv->desc_data[i].pphys_tx_ring, + priv->iobase0 + priv->desc_data[i].wcb_base); +#endif + writel(priv->desc_data[0].pphys_rx_ring, + priv->iobase0 + priv->desc_data[0].rx_desc_read); + writel(priv->desc_data[0].pphys_rx_ring, + priv->iobase0 + priv->desc_data[0].rx_desc_write); + + rc = mwl_fwcmd_set_hw_specs(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to set HW specifications", + MWL_DRV_NAME); + goto err_set_hw_specs; + } + + wiphy_info(hw->wiphy, + "firmware version: 0x%x", priv->hw_data.fw_release_num); + + mwl_fwcmd_radio_disable(hw); + + mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_TX, priv->antenna_tx); + + mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_RX, priv->antenna_rx); + + hw->wiphy->interface_modes = 0; + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); + hw->wiphy->iface_combinations = &ap_if_comb; + hw->wiphy->n_iface_combinations = 1; + + mwl_set_caps(priv); + + rc = ieee80211_register_hw(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to register device", + MWL_DRV_NAME); + goto err_register_hw; + } + + return rc; + +err_register_hw: +err_set_hw_specs: +err_get_hw_specs: + + mwl_rx_deinit(hw); + +err_mwl_rx_init: + + mwl_tx_deinit(hw); + +err_mwl_tx_init: + + wiphy_err(hw->wiphy, "init fail"); + + return rc; +} + +static void mwl_wl_deinit(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw; + + hw = priv->hw; + + ieee80211_unregister_hw(hw); + mwl_rx_deinit(hw); + mwl_tx_deinit(hw); + tasklet_kill(&priv->rx_task); + tasklet_kill(&priv->tx_task); + mwl_fwcmd_reset(hw); +} + +static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + static bool printed_version; + struct ieee80211_hw *hw; + struct mwl_priv *priv; + int rc = 0; + + if (id->driver_data >= MWLUNKNOWN) + return -ENODEV; + + if (!printed_version) { + pr_info("<<%s version %s>>", + MWL_DESC, MWL_DRV_VERSION); + printed_version = true; + } + + rc = pci_enable_device(pdev); + if (rc) { + pr_err("%s: cannot enable new PCI device", + MWL_DRV_NAME); + return rc; + } + + rc = pci_set_dma_mask(pdev, 0xffffffff); + if (rc) { + pr_err("%s: 32-bit PCI DMA not supported", + MWL_DRV_NAME); + goto err_pci_disable_device; + } + + pci_set_master(pdev); + + hw = ieee80211_alloc_hw(sizeof(*priv), &mwl_mac80211_ops); + if (!hw) { + pr_err("%s: ieee80211 alloc failed", + MWL_DRV_NAME); + rc = -ENOMEM; + goto err_pci_disable_device; + } + + /* hook regulatory domain change notification */ + hw->wiphy->reg_notifier = mwl_reg_notifier; + + SET_IEEE80211_DEV(hw, &pdev->dev); + pci_set_drvdata(pdev, hw); + + priv = hw->priv; + priv->hw = hw; + priv->pdev = pdev; + priv->chip_type = id->driver_data; + + rc = mwl_alloc_pci_resource(priv); + if (rc) + goto err_alloc_pci_resource; + + rc = mwl_init_firmware(priv, mwl_chip_tbl[priv->chip_type].fw_image); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize firmware", + MWL_DRV_NAME); + goto err_init_firmware; + } + + /* firmware is loaded to H/W, it can be released now */ + release_firmware(priv->fw_ucode); + + rc = mwl_process_of_dts(priv); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to load dts mwlwifi parameters", + MWL_DRV_NAME); + goto err_process_of_dts; + } + + rc = mwl_wl_init(priv); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize wireless lan", + MWL_DRV_NAME); + goto err_wl_init; + } + + return rc; + +err_wl_init: +err_process_of_dts: +err_init_firmware: + + mwl_fwcmd_reset(hw); + +err_alloc_pci_resource: + + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + +err_pci_disable_device: + + pci_disable_device(pdev); + + return rc; +} + +static void mwl_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct mwl_priv *priv; + + if (!hw) + return; + + priv = hw->priv; + + mwl_wl_deinit(priv); + mwl_free_pci_resource(priv); + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + pci_disable_device(pdev); +} + +static struct pci_driver mwl_pci_driver = { + .name = MWL_DRV_NAME, + .id_table = mwl_pci_id_tbl, + .probe = mwl_probe, + .remove = mwl_remove +}; + +module_pci_driver(mwl_pci_driver); + +MODULE_DESCRIPTION(MWL_DESC); +MODULE_VERSION(MWL_DRV_VERSION); +MODULE_AUTHOR("Marvell Semiconductor, Inc."); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE(MWL_DEV_NAME); +MODULE_DEVICE_TABLE(pci, mwl_pci_id_tbl); diff --git a/drivers/net/wireless/mwlwifi/rx.c b/drivers/net/wireless/mwlwifi/rx.c new file mode 100644 index 0000000..3cd51ff --- /dev/null +++ b/drivers/net/wireless/mwlwifi/rx.c @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements receive related functions. */ + +#include <linux/skbuff.h> + +#include "sysadpt.h" +#include "dev.h" +#include "rx.h" + +#define MAX_NUM_RX_RING_BYTES (SYSADPT_MAX_NUM_RX_DESC * \ + sizeof(struct mwl_rx_desc)) + +#define MAX_NUM_RX_HNDL_BYTES (SYSADPT_MAX_NUM_RX_DESC * \ + sizeof(struct mwl_rx_hndl)) + +#define DECRYPT_ERR_MASK 0x80 +#define GENERAL_DECRYPT_ERR 0xFF +#define TKIP_DECRYPT_MIC_ERR 0x02 +#define WEP_DECRYPT_ICV_ERR 0x04 +#define TKIP_DECRYPT_ICV_ERR 0x08 + +#define W836X_RSSI_OFFSET 8 + +/* Receive rate information constants */ +#define RX_RATE_INFO_FORMAT_11A 0 +#define RX_RATE_INFO_FORMAT_11B 1 +#define RX_RATE_INFO_FORMAT_11N 2 +#define RX_RATE_INFO_FORMAT_11AC 4 + +#define RX_RATE_INFO_HT20 0 +#define RX_RATE_INFO_HT40 1 +#define RX_RATE_INFO_HT80 2 + +#define RX_RATE_INFO_LONG_INTERVAL 0 +#define RX_RATE_INFO_SHORT_INTERVAL 1 + +static int mwl_rx_ring_alloc(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + + desc = &priv->desc_data[0]; + + desc->prx_ring = (struct mwl_rx_desc *) + dma_alloc_coherent(&priv->pdev->dev, + MAX_NUM_RX_RING_BYTES, + &desc->pphys_rx_ring, + GFP_KERNEL); + + if (!desc->prx_ring) { + wiphy_err(priv->hw->wiphy, "can not alloc mem"); + return -ENOMEM; + } + + memset(desc->prx_ring, 0x00, MAX_NUM_RX_RING_BYTES); + + desc->rx_hndl = kmalloc(MAX_NUM_RX_HNDL_BYTES, GFP_KERNEL); + + if (!desc->rx_hndl) { + dma_free_coherent(&priv->pdev->dev, + MAX_NUM_RX_RING_BYTES, + desc->prx_ring, + desc->pphys_rx_ring); + return -ENOMEM; + } + + memset(desc->rx_hndl, 0x00, MAX_NUM_RX_HNDL_BYTES); + + return 0; +} + +static int mwl_rx_ring_init(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + int i; + struct mwl_rx_hndl *rx_hndl; + + desc = &priv->desc_data[0]; + + if (desc->prx_ring) { + desc->rx_buf_size = SYSADPT_MAX_AGGR_SIZE; + + for (i = 0; i < SYSADPT_MAX_NUM_RX_DESC; i++) { + rx_hndl = &desc->rx_hndl[i]; + rx_hndl->psk_buff = + dev_alloc_skb(desc->rx_buf_size); + + if (skb_linearize(rx_hndl->psk_buff)) { + dev_kfree_skb_any(rx_hndl->psk_buff); + wiphy_err(priv->hw->wiphy, + "need linearize memory"); + return -ENOMEM; + } + + skb_reserve(rx_hndl->psk_buff, + SYSADPT_MIN_BYTES_HEADROOM); + desc->prx_ring[i].rx_control = + EAGLE_RXD_CTRL_DRIVER_OWN; + desc->prx_ring[i].status = EAGLE_RXD_STATUS_OK; + desc->prx_ring[i].qos_ctrl = 0x0000; + desc->prx_ring[i].channel = 0x00; + desc->prx_ring[i].rssi = 0x00; + + if (rx_hndl->psk_buff) { + dma_addr_t dma; + u32 val; + + desc->prx_ring[i].pkt_len = + cpu_to_le16(SYSADPT_MAX_AGGR_SIZE); + dma = pci_map_single(priv->pdev, + rx_hndl->psk_buff->data, + desc->rx_buf_size, + PCI_DMA_FROMDEVICE); + desc->prx_ring[i].pphys_buff_data = + cpu_to_le32(dma); + val = (u32)desc->pphys_rx_ring + + ((i + 1) * sizeof(struct mwl_rx_desc)); + desc->prx_ring[i].pphys_next = + cpu_to_le32(val); + rx_hndl->pdesc = &desc->prx_ring[i]; + if (i < (SYSADPT_MAX_NUM_RX_DESC - 1)) + rx_hndl->pnext = + &desc->rx_hndl[i + 1]; + } else { + wiphy_err(priv->hw->wiphy, + "rxdesc %i: no skbuff available", i); + return -ENOMEM; + } + } + desc->prx_ring[SYSADPT_MAX_NUM_RX_DESC - 1].pphys_next = + cpu_to_le32((u32)desc->pphys_rx_ring); + desc->rx_hndl[SYSADPT_MAX_NUM_RX_DESC - 1].pnext = + &desc->rx_hndl[0]; + desc->pnext_rx_hndl = &desc->rx_hndl[0]; + + return 0; + } + + wiphy_err(priv->hw->wiphy, "no valid RX mem"); + + return -ENOMEM; +} + +static void mwl_rx_ring_cleanup(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + int i; + struct mwl_rx_hndl *rx_hndl; + + desc = &priv->desc_data[0]; + + if (desc->prx_ring) { + for (i = 0; i < SYSADPT_MAX_NUM_RX_DESC; i++) { + rx_hndl = &desc->rx_hndl[i]; + if (!rx_hndl->psk_buff) + continue; + + if (skb_shinfo(rx_hndl->psk_buff)->nr_frags) + skb_shinfo(rx_hndl->psk_buff)->nr_frags = 0; + + if (skb_shinfo(rx_hndl->psk_buff)->frag_list) + skb_shinfo(rx_hndl->psk_buff)->frag_list = NULL; + + pci_unmap_single(priv->pdev, + le32_to_cpu + (rx_hndl->pdesc->pphys_buff_data), + desc->rx_buf_size, + PCI_DMA_FROMDEVICE); + + dev_kfree_skb_any(rx_hndl->psk_buff); + + wiphy_info(priv->hw->wiphy, + "unmapped+free'd %i 0x%p 0x%x %i", + i, rx_hndl->psk_buff->data, + le32_to_cpu(rx_hndl->pdesc->pphys_buff_data), + desc->rx_buf_size); + + rx_hndl->psk_buff = NULL; + } + } +} + +static void mwl_rx_ring_free(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + + desc = &priv->desc_data[0]; + + if (desc->prx_ring) { + mwl_rx_ring_cleanup(priv); + + dma_free_coherent(&priv->pdev->dev, + MAX_NUM_RX_RING_BYTES, + desc->prx_ring, + desc->pphys_rx_ring); + + desc->prx_ring = NULL; + } + + kfree(desc->rx_hndl); + + desc->pnext_rx_hndl = NULL; +} + +static inline void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, + struct ieee80211_rx_status *status) +{ + u16 rate, format, nss, bw, gi, rt; + + memset(status, 0, sizeof(*status)); + + status->signal = -(pdesc->rssi + W836X_RSSI_OFFSET); + + rate = le16_to_cpu(pdesc->rate); + format = rate & MWL_RX_RATE_FORMAT_MASK; + nss = (rate & MWL_RX_RATE_NSS_MASK) >> MWL_RX_RATE_NSS_SHIFT; + bw = (rate & MWL_RX_RATE_BW_MASK) >> MWL_RX_RATE_BW_SHIFT; + gi = (rate & MWL_RX_RATE_GI_MASK) >> MWL_RX_RATE_GI_SHIFT; + rt = (rate & MWL_RX_RATE_RT_MASK) >> MWL_RX_RATE_RT_SHIFT; + + switch (format) { + case RX_RATE_INFO_FORMAT_11N: + status->flag |= RX_FLAG_HT; + if (bw == RX_RATE_INFO_HT40) + status->flag |= RX_FLAG_40MHZ; + if (gi == RX_RATE_INFO_SHORT_INTERVAL) + status->flag |= RX_FLAG_SHORT_GI; + break; + case RX_RATE_INFO_FORMAT_11AC: + status->flag |= RX_FLAG_VHT; + if (bw == RX_RATE_INFO_HT40) + status->flag |= RX_FLAG_40MHZ; + if (bw == RX_RATE_INFO_HT80) + status->vht_flag |= RX_VHT_FLAG_80MHZ; + if (gi == RX_RATE_INFO_SHORT_INTERVAL) + status->flag |= RX_FLAG_SHORT_GI; + status->vht_nss = (nss + 1); + break; + } + + status->rate_idx = rt; + + if (pdesc->channel > BAND_24_CHANNEL_NUM) { + status->band = IEEE80211_BAND_5GHZ; + if ((!(status->flag & RX_FLAG_HT)) && + (!(status->flag & RX_FLAG_VHT))) { + status->rate_idx -= 5; + if (status->rate_idx >= BAND_50_RATE_NUM) + status->rate_idx = BAND_50_RATE_NUM - 1; + } + } else { + status->band = IEEE80211_BAND_2GHZ; + if ((!(status->flag & RX_FLAG_HT)) && + (!(status->flag & RX_FLAG_VHT))) { + if (status->rate_idx >= BAND_24_RATE_NUM) + status->rate_idx = BAND_24_RATE_NUM - 1; + } + } + + status->freq = ieee80211_channel_to_frequency(pdesc->channel, + status->band); + + /* check if status has a specific error bit (bit 7) set or indicates + * a general decrypt error + */ + if ((pdesc->status == GENERAL_DECRYPT_ERR) || + (pdesc->status & DECRYPT_ERR_MASK)) { + /* check if status is not equal to 0xFF + * the 0xFF check is for backward compatibility + */ + if (pdesc->status != GENERAL_DECRYPT_ERR) { + if (((pdesc->status & (~DECRYPT_ERR_MASK)) & + TKIP_DECRYPT_MIC_ERR) && !((pdesc->status & + (WEP_DECRYPT_ICV_ERR | TKIP_DECRYPT_ICV_ERR)))) { + status->flag |= RX_FLAG_MMIC_ERROR; + } + } + } +} + +static inline struct mwl_vif *mwl_rx_find_vif_bss(struct mwl_priv *priv, + u8 *bssid) +{ + struct mwl_vif *mwl_vif; + unsigned long flags; + + spin_lock_irqsave(&priv->vif_lock, flags); + list_for_each_entry(mwl_vif, &priv->vif_list, list) { + if (memcmp(bssid, mwl_vif->bssid, ETH_ALEN) == 0) { + spin_unlock_irqrestore(&priv->vif_lock, flags); + return mwl_vif; + } + } + spin_unlock_irqrestore(&priv->vif_lock, flags); + + return NULL; +} + +static inline void mwl_rx_remove_dma_header(struct sk_buff *skb, __le16 qos) +{ + struct mwl_dma_data *tr; + int hdrlen; + + tr = (struct mwl_dma_data *)skb->data; + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + + if (hdrlen != sizeof(tr->wh)) { + if (ieee80211_is_data_qos(tr->wh.frame_control)) { + memmove(tr->data - hdrlen, &tr->wh, hdrlen - 2); + *((__le16 *)(tr->data - 2)) = qos; + } else { + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + } + } + + if (hdrlen != sizeof(*tr)) + skb_pull(skb, sizeof(*tr) - hdrlen); +} + +static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_hndl *rx_hndl) +{ + struct mwl_desc_data *desc; + + desc = &priv->desc_data[0]; + + rx_hndl->psk_buff = dev_alloc_skb(desc->rx_buf_size); + + if (!rx_hndl->psk_buff) + goto nomem; + + if (skb_linearize(rx_hndl->psk_buff)) { + dev_kfree_skb_any(rx_hndl->psk_buff); + wiphy_err(priv->hw->wiphy, "need linearize memory"); + goto nomem; + } + + skb_reserve(rx_hndl->psk_buff, SYSADPT_MIN_BYTES_HEADROOM); + + rx_hndl->pdesc->status = EAGLE_RXD_STATUS_OK; + rx_hndl->pdesc->qos_ctrl = 0x0000; + rx_hndl->pdesc->channel = 0x00; + rx_hndl->pdesc->rssi = 0x00; + + rx_hndl->pdesc->pkt_len = cpu_to_le16(desc->rx_buf_size); + rx_hndl->pdesc->pphys_buff_data = + cpu_to_le32(pci_map_single(priv->pdev, + rx_hndl->psk_buff->data, + desc->rx_buf_size, + PCI_DMA_BIDIRECTIONAL)); + + return 0; + +nomem: + + wiphy_err(priv->hw->wiphy, "no memory"); + + return -ENOMEM; +} + +int mwl_rx_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + priv = hw->priv; + + rc = mwl_rx_ring_alloc(priv); + if (rc) { + wiphy_err(hw->wiphy, "allocating RX ring failed"); + } else { + rc = mwl_rx_ring_init(priv); + if (rc) { + mwl_rx_ring_free(priv); + wiphy_err(hw->wiphy, + "initializing RX ring failed"); + } + } + + return rc; +} + +void mwl_rx_deinit(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + mwl_rx_ring_cleanup(priv); + mwl_rx_ring_free(priv); +} + +void mwl_rx_recv(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv; + struct mwl_desc_data *desc; + struct mwl_rx_hndl *curr_hndl; + int work_done = 0; + struct sk_buff *prx_skb = NULL; + int pkt_len; + struct ieee80211_rx_status status; + struct mwl_vif *mwl_vif = NULL; + struct ieee80211_hdr *wh; + u32 status_mask; + + priv = hw->priv; + + desc = &priv->desc_data[0]; + curr_hndl = desc->pnext_rx_hndl; + + if (!curr_hndl) { + status_mask = readl(priv->iobase1 + + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + priv->is_rx_schedule = false; + + wiphy_warn(hw->wiphy, "busy or no receiving packets"); + return; + } + + while ((curr_hndl->pdesc->rx_control == EAGLE_RXD_CTRL_DMA_OWN) && + (work_done < priv->recv_limit)) { + prx_skb = curr_hndl->psk_buff; + if (!prx_skb) + goto out; + pci_unmap_single(priv->pdev, + le32_to_cpu(curr_hndl->pdesc->pphys_buff_data), + desc->rx_buf_size, + PCI_DMA_FROMDEVICE); + pkt_len = le16_to_cpu(curr_hndl->pdesc->pkt_len); + + if (skb_tailroom(prx_skb) < pkt_len) { + dev_kfree_skb_any(prx_skb); + goto out; + } + + if (curr_hndl->pdesc->channel != + hw->conf.chandef.chan->hw_value) { + dev_kfree_skb_any(prx_skb); + goto out; + } + + mwl_rx_prepare_status(curr_hndl->pdesc, &status); + + priv->noise = -curr_hndl->pdesc->noise_floor; + + wh = &((struct mwl_dma_data *)prx_skb->data)->wh; + + if (ieee80211_has_protected(wh->frame_control)) { + /* Check if hw crypto has been enabled for + * this bss. If yes, set the status flags + * accordingly + */ + if (ieee80211_has_tods(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr1); + else + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr2); + + if (mwl_vif && mwl_vif->is_hw_crypto_enabled) { + /* When MMIC ERROR is encountered + * by the firmware, payload is + * dropped and only 32 bytes of + * mwl8k Firmware header is sent + * to the host. + * + * We need to add four bytes of + * key information. In it + * MAC80211 expects keyidx set to + * 0 for triggering Counter + * Measure of MMIC failure. + */ + if (status.flag & RX_FLAG_MMIC_ERROR) { + struct mwl_dma_data *tr; + + tr = (struct mwl_dma_data *) + prx_skb->data; + memset((void *)&tr->data, 0, 4); + pkt_len += 4; + } + + if (!ieee80211_is_auth(wh->frame_control)) + status.flag |= RX_FLAG_IV_STRIPPED | + RX_FLAG_DECRYPTED | + RX_FLAG_MMIC_STRIPPED; + } + } + + skb_put(prx_skb, pkt_len); + mwl_rx_remove_dma_header(prx_skb, curr_hndl->pdesc->qos_ctrl); + memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status)); + ieee80211_rx(hw, prx_skb); +out: + mwl_rx_refill(priv, curr_hndl); + curr_hndl->pdesc->rx_control = EAGLE_RXD_CTRL_DRIVER_OWN; + curr_hndl->pdesc->qos_ctrl = 0; + curr_hndl = curr_hndl->pnext; + work_done++; + } + + desc->pnext_rx_hndl = curr_hndl; + + status_mask = readl(priv->iobase1 + + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status_mask | MACREG_A2HRIC_BIT_RX_RDY, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + priv->is_rx_schedule = false; +} diff --git a/drivers/net/wireless/mwlwifi/rx.h b/drivers/net/wireless/mwlwifi/rx.h new file mode 100644 index 0000000..d32c558 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/rx.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines receive related functions. */ + +#ifndef _rx_h_ +#define _rx_h_ + +int mwl_rx_init(struct ieee80211_hw *hw); +void mwl_rx_deinit(struct ieee80211_hw *hw); +void mwl_rx_recv(unsigned long data); + +#endif /* _rx_h_ */ diff --git a/drivers/net/wireless/mwlwifi/sysadpt.h b/drivers/net/wireless/mwlwifi/sysadpt.h new file mode 100644 index 0000000..384ec34 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/sysadpt.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines system adaptation related information. */ + +#ifndef _mwl_sysadpt_h_ +#define _mwl_sysadpt_h_ + +#define SYSADPT_MAX_NUM_CHANNELS 64 + +#define SYSADPT_MAX_DATA_RATES_G 14 + +#define SYSADPT_TX_POWER_LEVEL_TOTAL 16 + +#define SYSADPT_TX_WMM_QUEUES 4 + +#define SYSADPT_TX_AMPDU_QUEUES 4 + +#define SYSADPT_NUM_OF_AP 16 + +#define SYSADPT_TOTAL_TX_QUEUES (SYSADPT_TX_WMM_QUEUES + \ + SYSADPT_NUM_OF_AP) + +#define SYSADPT_TOTAL_HW_QUEUES (SYSADPT_TX_WMM_QUEUES + \ + SYSADPT_TX_AMPDU_QUEUES) + +#define SYSADPT_NUM_OF_DESC_DATA (4 + SYSADPT_NUM_OF_AP) + +#define SYSADPT_MAX_NUM_TX_DESC 256 + +#define SYSADPT_TX_QUEUE_LIMIT 1024 + +#define SYSADPT_DELAY_FREE_Q_LIMIT SYSADPT_MAX_NUM_TX_DESC + +#define SYSADPT_MAX_NUM_RX_DESC 256 + +#define SYSADPT_RECEIVE_LIMIT 64 + +#define SYSADPT_MAX_AGGR_SIZE 8192 + +#define SYSADPT_MIN_BYTES_HEADROOM 64 + +#define SYSADPT_AMPDU_PACKET_THRESHOLD 64 + +#define SYSADPT_AMSDU_MAX_SIZE 3300 + +#define SYSADPT_AMSDU_ALLOW_SIZE 1540 + +#define SYSADPT_AMSDU_FLUSH_TIME 500 + +#define SYSADPT_AMSDU_PACKET_THRESHOLD 10 + +#define SYSADPT_MAX_TID 8 + +#endif /* _mwl_sysadpt_h_ */ diff --git a/drivers/net/wireless/mwlwifi/tx.c b/drivers/net/wireless/mwlwifi/tx.c new file mode 100644 index 0000000..f92a661 --- /dev/null +++ b/drivers/net/wireless/mwlwifi/tx.c @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements transmit related functions. */ + +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "tx.h" + +#define MAX_NUM_TX_RING_BYTES (SYSADPT_MAX_NUM_TX_DESC * \ + sizeof(struct mwl_tx_desc)) + +#define MAX_NUM_TX_HNDL_BYTES (SYSADPT_MAX_NUM_TX_DESC * \ + sizeof(struct mwl_tx_hndl)) + +#define EAGLE_TXD_XMITCTRL_USE_MC_RATE 0x8 /* Use multicast data rate */ + +#define MWL_QOS_ACK_POLICY_MASK 0x0060 +#define MWL_QOS_ACK_POLICY_NORMAL 0x0000 +#define MWL_QOS_ACK_POLICY_BLOCKACK 0x0060 + +#define EXT_IV 0x20 +#define INCREASE_IV(iv16, iv32) \ +{ \ + (iv16)++; \ + if ((iv16) == 0) \ + (iv32)++; \ +} + +/* Transmit rate information constants */ +#define TX_RATE_FORMAT_LEGACY 0 +#define TX_RATE_FORMAT_11N 1 +#define TX_RATE_FORMAT_11AC 2 + +#define TX_RATE_BANDWIDTH_20 0 +#define TX_RATE_BANDWIDTH_40 1 +#define TX_RATE_BANDWIDTH_80 2 + +#define TX_RATE_INFO_STD_GI 0 +#define TX_RATE_INFO_SHORT_GI 1 + +enum { + IEEE_TYPE_MANAGEMENT = 0, + IEEE_TYPE_CONTROL, + IEEE_TYPE_DATA +}; + +static int mwl_tx_ring_alloc(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + int num; + u8 *mem; + + desc = &priv->desc_data[0]; + + mem = (u8 *)dma_alloc_coherent(&priv->pdev->dev, + MAX_NUM_TX_RING_BYTES * SYSADPT_NUM_OF_DESC_DATA, + &desc->pphys_tx_ring, GFP_KERNEL); + + if (!mem) { + wiphy_err(priv->hw->wiphy, "can not alloc mem"); + return -ENOMEM; + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + desc = &priv->desc_data[num]; + + desc->ptx_ring = (struct mwl_tx_desc *) + (mem + num * MAX_NUM_TX_RING_BYTES); + + desc->pphys_tx_ring = (dma_addr_t) + ((u32)priv->desc_data[0].pphys_tx_ring + + num * MAX_NUM_TX_RING_BYTES); + + memset(desc->ptx_ring, 0x00, + MAX_NUM_TX_RING_BYTES); + } + + mem = kmalloc(MAX_NUM_TX_HNDL_BYTES * SYSADPT_NUM_OF_DESC_DATA, + GFP_KERNEL); + + if (!mem) { + wiphy_err(priv->hw->wiphy, "can not alloc mem"); + dma_free_coherent(&priv->pdev->dev, + MAX_NUM_TX_RING_BYTES * + SYSADPT_NUM_OF_DESC_DATA, + priv->desc_data[0].ptx_ring, + priv->desc_data[0].pphys_tx_ring); + return -ENOMEM; + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + desc = &priv->desc_data[num]; + + desc->tx_hndl = (struct mwl_tx_hndl *) + (mem + num * MAX_NUM_TX_HNDL_BYTES); + + memset(desc->tx_hndl, 0x00, + MAX_NUM_TX_HNDL_BYTES); + } + + return 0; +} + +static int mwl_tx_ring_init(struct mwl_priv *priv) +{ + int num, i; + struct mwl_desc_data *desc; + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + skb_queue_head_init(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + + desc = &priv->desc_data[num]; + + if (desc->ptx_ring) { + for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) { + desc->ptx_ring[i].status = + cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + desc->ptx_ring[i].pphys_next = + cpu_to_le32((u32)desc->pphys_tx_ring + + ((i + 1) * sizeof(struct mwl_tx_desc))); + desc->tx_hndl[i].pdesc = + &desc->ptx_ring[i]; + if (i < SYSADPT_MAX_NUM_TX_DESC - 1) + desc->tx_hndl[i].pnext = + &desc->tx_hndl[i + 1]; + } + desc->ptx_ring[SYSADPT_MAX_NUM_TX_DESC - 1].pphys_next = + cpu_to_le32((u32)desc->pphys_tx_ring); + desc->tx_hndl[SYSADPT_MAX_NUM_TX_DESC - 1].pnext = + &desc->tx_hndl[0]; + + desc->pstale_tx_hndl = &desc->tx_hndl[0]; + desc->pnext_tx_hndl = &desc->tx_hndl[0]; + } else { + wiphy_err(priv->hw->wiphy, "no valid TX mem"); + return -ENOMEM; + } + } + + return 0; +} + +static void mwl_tx_ring_cleanup(struct mwl_priv *priv) +{ + int cleaned_tx_desc = 0; + int num, i; + struct mwl_desc_data *desc; + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + skb_queue_purge(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + + desc = &priv->desc_data[num]; + + if (desc->ptx_ring) { + for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) { + if (!desc->tx_hndl[i].psk_buff) + continue; + + wiphy_info(priv->hw->wiphy, + "unmapped and free'd %i 0x%p 0x%x", + i, + desc->tx_hndl[i].psk_buff->data, + le32_to_cpu( + desc->ptx_ring[i].pkt_ptr)); + pci_unmap_single(priv->pdev, + le32_to_cpu( + desc->ptx_ring[i].pkt_ptr), + desc->tx_hndl[i].psk_buff->len, + PCI_DMA_TODEVICE); + dev_kfree_skb_any(desc->tx_hndl[i].psk_buff); + desc->ptx_ring[i].status = + cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + desc->ptx_ring[i].pkt_ptr = 0; + desc->ptx_ring[i].pkt_len = 0; + desc->tx_hndl[i].psk_buff = NULL; + cleaned_tx_desc++; + } + } + } + + wiphy_info(priv->hw->wiphy, "cleaned %i TX descr", cleaned_tx_desc); +} + +static void mwl_tx_ring_free(struct mwl_priv *priv) +{ + int num; + + if (priv->desc_data[0].ptx_ring) { + dma_free_coherent(&priv->pdev->dev, + MAX_NUM_TX_RING_BYTES * + SYSADPT_NUM_OF_DESC_DATA, + priv->desc_data[0].ptx_ring, + priv->desc_data[0].pphys_tx_ring); + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + if (priv->desc_data[num].ptx_ring) + priv->desc_data[num].ptx_ring = NULL; + priv->desc_data[num].pstale_tx_hndl = NULL; + priv->desc_data[num].pnext_tx_hndl = NULL; + } + + kfree(priv->desc_data[0].tx_hndl); +} + +static inline void mwl_tx_add_dma_header(struct mwl_priv *priv, + struct sk_buff *skb, + int head_pad, + int tail_pad) +{ + struct ieee80211_hdr *wh; + int hdrlen; + int reqd_hdrlen; + struct mwl_dma_data *tr; + + /* Add a firmware DMA header; the firmware requires that we + * present a 2-byte payload length followed by a 4-address + * header (without QoS field), followed (optionally) by any + * WEP/ExtIV header (but only filled in for CCMP). + */ + wh = (struct ieee80211_hdr *)skb->data; + + hdrlen = ieee80211_hdrlen(wh->frame_control); + + reqd_hdrlen = sizeof(*tr) + head_pad; + + if (hdrlen != reqd_hdrlen) + skb_push(skb, reqd_hdrlen - hdrlen); + + if (ieee80211_is_data_qos(wh->frame_control)) + hdrlen -= IEEE80211_QOS_CTL_LEN; + + tr = (struct mwl_dma_data *)skb->data; + + if (wh != &tr->wh) + memmove(&tr->wh, wh, hdrlen); + + if (hdrlen != sizeof(tr->wh)) + memset(((void *)&tr->wh) + hdrlen, 0, sizeof(tr->wh) - hdrlen); + + /* Firmware length is the length of the fully formed "802.11 + * payload". That is, everything except for the 802.11 header. + * This includes all crypto material including the MIC. + */ + tr->fwlen = cpu_to_le16(skb->len - sizeof(*tr) + tail_pad); +} + +static inline void mwl_tx_encapsulate_frame(struct mwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_key_conf *k_conf, + bool *ccmp) +{ + int head_pad = 0; + int data_pad = 0; + + /* Make sure the packet header is in the DMA header format (4-address + * without QoS), and add head & tail padding when HW crypto is enabled. + * + * We have the following trailer padding requirements: + * - WEP: 4 trailer bytes (ICV) + * - TKIP: 12 trailer bytes (8 MIC + 4 ICV) + * - CCMP: 8 trailer bytes (MIC) + */ + + if (k_conf) { + head_pad = k_conf->iv_len; + + switch (k_conf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + data_pad = 4; + break; + case WLAN_CIPHER_SUITE_TKIP: + data_pad = 12; + break; + case WLAN_CIPHER_SUITE_CCMP: + data_pad = 8; + *ccmp = true; + break; + } + } + + mwl_tx_add_dma_header(priv, skb, head_pad, data_pad); +} + +static inline void mwl_tx_insert_ccmp_hdr(u8 *pccmp_hdr, + u8 key_id, u16 iv16, u32 iv32) +{ + *((u16 *)pccmp_hdr) = iv16; + pccmp_hdr[2] = 0; + pccmp_hdr[3] = EXT_IV | (key_id << 6); + *((u32 *)&pccmp_hdr[4]) = iv32; +} + +static inline int mwl_tx_tid_queue_mapping(u8 tid) +{ + BUG_ON(tid > 7); + + switch (tid) { + case 0: + case 3: + return IEEE80211_AC_BE; + case 1: + case 2: + return IEEE80211_AC_BK; + case 4: + case 5: + return IEEE80211_AC_VI; + case 6: + case 7: + return IEEE80211_AC_VO; + default: + break; + } + + return -1; +} + +static inline void mwl_tx_count_packet(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_sta *sta_info; + struct mwl_tx_info *tx_stats; + + BUG_ON(tid >= SYSADPT_MAX_TID); + + sta_info = mwl_dev_get_sta(sta); + + tx_stats = &sta_info->tx_stats[tid]; + + if (tx_stats->start_time == 0) + tx_stats->start_time = jiffies; + + /* reset the packet count after each second elapses. If the number of + * packets ever exceeds the ampdu_min_traffic threshold, we will allow + * an ampdu stream to be started. + */ + if (jiffies - tx_stats->start_time > HZ) { + tx_stats->pkts = 0; + tx_stats->start_time = 0; + } else { + tx_stats->pkts++; + } +} + +static inline bool mwl_tx_available(struct mwl_priv *priv, int desc_num) +{ + struct mwl_tx_hndl *tx_hndl; + + tx_hndl = priv->desc_data[desc_num].pnext_tx_hndl; + + if (!tx_hndl->pdesc) + return false; + + if (tx_hndl->pdesc->status != + EAGLE_TXD_STATUS_IDLE) { + /* Interrupt F/W anyway */ + if (tx_hndl->pdesc->status & + cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)) + writel(MACREG_H2ARIC_BIT_PPA_READY, + priv->iobase1 + + MACREG_REG_H2A_INTERRUPT_EVENTS); + return false; + } + + return true; +} + +static inline void mwl_tx_skb(struct mwl_priv *priv, int desc_num, + struct sk_buff *tx_skb) +{ + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + struct mwl_tx_hndl *tx_hndl; + struct mwl_tx_desc *tx_desc; + struct ieee80211_sta *sta; + struct mwl_vif *mwl_vif; + struct ieee80211_key_conf *k_conf; + bool ccmp = false; + struct mwl_dma_data *dma_data; + struct ieee80211_hdr *wh; + + BUG_ON(!tx_skb); + + tx_info = IEEE80211_SKB_CB(tx_skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + sta = (struct ieee80211_sta *)tx_ctrl->sta; + mwl_vif = (struct mwl_vif *)tx_ctrl->vif; + k_conf = (struct ieee80211_key_conf *)tx_ctrl->k_conf; + + mwl_tx_encapsulate_frame(priv, tx_skb, k_conf, &ccmp); + + dma_data = (struct mwl_dma_data *)tx_skb->data; + wh = &dma_data->wh; + + if (ieee80211_is_data(wh->frame_control)) { + if (is_multicast_ether_addr(wh->addr1)) { + if (ccmp) { + mwl_tx_insert_ccmp_hdr(dma_data->data, + mwl_vif->keyidx, + mwl_vif->iv16, + mwl_vif->iv32); + INCREASE_IV(mwl_vif->iv16, mwl_vif->iv32); + } + } else { + if (ccmp) { + if (mwl_vif->is_sta) { + mwl_tx_insert_ccmp_hdr(dma_data->data, + mwl_vif->keyidx, + mwl_vif->iv16, + mwl_vif->iv32); + INCREASE_IV(mwl_vif->iv16, + mwl_vif->iv32); + } else { + struct mwl_sta *sta_info; + + sta_info = mwl_dev_get_sta(sta); + + mwl_tx_insert_ccmp_hdr(dma_data->data, + 0, + sta_info->iv16, + sta_info->iv32); + INCREASE_IV(sta_info->iv16, + sta_info->iv32); + } + } + } + } + + tx_hndl = priv->desc_data[desc_num].pnext_tx_hndl; + priv->desc_data[desc_num].pnext_tx_hndl = tx_hndl->pnext; + tx_hndl->psk_buff = tx_skb; + tx_hndl->sta_info = tx_ctrl->sta; + tx_desc = tx_hndl->pdesc; + tx_desc->tx_priority = tx_ctrl->tx_priority; + tx_desc->qos_ctrl = cpu_to_le16(tx_ctrl->qos_ctrl); + tx_desc->pkt_len = cpu_to_le16(tx_skb->len); + tx_desc->packet_info = 0; + tx_desc->data_rate = 0; + tx_desc->type = tx_ctrl->type; + tx_desc->xmit_control = tx_ctrl->xmit_control; + tx_desc->sap_pkt_info = 0; + tx_desc->pkt_ptr = + cpu_to_le32(pci_map_single(priv->pdev, tx_skb->data, + tx_skb->len, PCI_DMA_TODEVICE)); + tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED); + /* make sure all the memory transactions done by cpu were completed */ + wmb(); /*Data Memory Barrier*/ + writel(MACREG_H2ARIC_BIT_PPA_READY, + priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + priv->fw_desc_cnt[desc_num]++; +} + +static inline void mwl_tx_skbs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int num = SYSADPT_NUM_OF_DESC_DATA; + struct sk_buff *tx_skb; + unsigned long flags; + + priv = hw->priv; + + spin_lock_irqsave(&priv->tx_desc_lock, flags); + while (num--) { + while (skb_queue_len(&priv->txq[num]) > 0) { + if (mwl_tx_available(priv, num) == false) + break; + tx_skb = skb_dequeue(&priv->txq[num]); + mwl_tx_skb(priv, num, tx_skb); + } + } + spin_unlock_irqrestore(&priv->tx_desc_lock, flags); +} + +int mwl_tx_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int rc; + + priv = hw->priv; + + skb_queue_head_init(&priv->delay_q); + + rc = mwl_tx_ring_alloc(priv); + if (rc) { + wiphy_err(hw->wiphy, "allocating TX ring failed"); + } else { + rc = mwl_tx_ring_init(priv); + if (rc) { + mwl_tx_ring_free(priv); + wiphy_err(hw->wiphy, "initializing TX ring failed"); + } + } + + return rc; +} + +void mwl_tx_deinit(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + priv = hw->priv; + + skb_queue_purge(&priv->delay_q); + + mwl_tx_ring_cleanup(priv); + mwl_tx_ring_free(priv); +} + +void mwl_tx_xmit(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mwl_priv *priv; + int index; + struct ieee80211_sta *sta; + struct ieee80211_tx_info *tx_info; + struct mwl_vif *mwl_vif; + struct ieee80211_hdr *wh; + u8 xmitcontrol; + u16 qos; + int txpriority; + u8 tid = 0; + struct mwl_ampdu_stream *stream = NULL; + bool start_ba_session = false; + bool mgmtframe = false; + struct ieee80211_mgmt *mgmt; + bool eapol_frame = false; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_key_conf *k_conf = NULL; + + priv = hw->priv; + index = skb_get_queue_mapping(skb); + sta = control->sta; + + wh = (struct ieee80211_hdr *)skb->data; + + if (ieee80211_is_data_qos(wh->frame_control)) + qos = *((u16 *)ieee80211_get_qos_ctl(wh)); + else + qos = 0; + + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) { + index = IEEE80211_AC_VO; + eapol_frame = true; + } + + if (ieee80211_is_mgmt(wh->frame_control)) { + mgmtframe = true; + mgmt = (struct ieee80211_mgmt *)skb->data; + } + + tx_info = IEEE80211_SKB_CB(skb); + mwl_vif = mwl_dev_get_vif(tx_info->control.vif); + + if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + wh->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + wh->seq_ctrl |= cpu_to_le16(mwl_vif->seqno); + mwl_vif->seqno += 0x10; + } + + /* Setup firmware control bit fields for each frame type. */ + xmitcontrol = 0; + + if (mgmtframe || ieee80211_is_ctl(wh->frame_control)) { + qos = 0; + } else if (ieee80211_is_data(wh->frame_control)) { + qos &= ~MWL_QOS_ACK_POLICY_MASK; + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + xmitcontrol &= 0xfb; + qos |= MWL_QOS_ACK_POLICY_BLOCKACK; + } else { + xmitcontrol |= 0x4; + qos |= MWL_QOS_ACK_POLICY_NORMAL; + } + + if (is_multicast_ether_addr(wh->addr1)) + xmitcontrol |= EAGLE_TXD_XMITCTRL_USE_MC_RATE; + + k_conf = tx_info->control.hw_key; + } + + /* Queue ADDBA request in the respective data queue. While setting up + * the ampdu stream, mac80211 queues further packets for that + * particular ra/tid pair. However, packets piled up in the hardware + * for that ra/tid pair will still go out. ADDBA request and the + * related data packets going out from different queues asynchronously + * will cause a shift in the receiver window which might result in + * ampdu packets getting dropped at the receiver after the stream has + * been setup. + */ + if (mgmtframe) { + if (unlikely(ieee80211_is_action(wh->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK && + mgmt->u.action.u.addba_req.action_code == + WLAN_ACTION_ADDBA_REQ)) { + u16 capab = + le16_to_cpu(mgmt->u.action.u.addba_req.capab); + tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; + index = mwl_tx_tid_queue_mapping(tid); + } + } + + index = SYSADPT_TX_WMM_QUEUES - index - 1; + txpriority = index; + + if (sta && sta->ht_cap.ht_supported && !eapol_frame && + ieee80211_is_data_qos(wh->frame_control)) { + tid = qos & 0xf; + mwl_tx_count_packet(sta, tid); + + spin_lock(&priv->stream_lock); + stream = mwl_fwcmd_lookup_stream(hw, sta->addr, tid); + + if (stream) { + if (stream->state == AMPDU_STREAM_ACTIVE) { + WARN_ON(!(qos & MWL_QOS_ACK_POLICY_BLOCKACK)); + + txpriority = + (SYSADPT_TX_WMM_QUEUES + stream->idx) % + SYSADPT_TOTAL_HW_QUEUES; + } else if (stream->state == AMPDU_STREAM_NEW) { + /* We get here if the driver sends us packets + * after we've initiated a stream, but before + * our ampdu_action routine has been called + * with IEEE80211_AMPDU_TX_START to get the SSN + * for the ADDBA request. So this packet can + * go out with no risk of sequence number + * mismatch. No special handling is required. + */ + } else { + /* Drop packets that would go out after the + * ADDBA request was sent but before the ADDBA + * response is received. If we don't do this, + * the recipient would probably receive it + * after the ADDBA request with SSN 0. This + * will cause the recipient's BA receive window + * to shift, which would cause the subsequent + * packets in the BA stream to be discarded. + * mac80211 queues our packets for us in this + * case, so this is really just a safety check. + */ + wiphy_warn(hw->wiphy, + "can't send packet during ADDBA"); + spin_unlock(&priv->stream_lock); + dev_kfree_skb_any(skb); + return; + } + } else { + /* Defer calling mwl8k_start_stream so that the current + * skb can go out before the ADDBA request. This + * prevents sequence number mismatch at the recipient + * as described above. + */ + if (mwl_fwcmd_ampdu_allowed(sta, tid)) { + stream = mwl_fwcmd_add_stream(hw, sta, tid); + + if (stream) + start_ba_session = true; + } + } + + spin_unlock(&priv->stream_lock); + } else { + qos &= ~MWL_QOS_ACK_POLICY_MASK; + qos |= MWL_QOS_ACK_POLICY_NORMAL; + } + + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + tx_ctrl->tx_priority = txpriority; + tx_ctrl->qos_ctrl = qos; + tx_ctrl->type = (mgmtframe ? IEEE_TYPE_MANAGEMENT : IEEE_TYPE_DATA); + tx_ctrl->xmit_control = xmitcontrol; + tx_ctrl->sta = (void *)sta; + tx_ctrl->vif = (void *)mwl_vif; + tx_ctrl->k_conf = (void *)k_conf; + + if (skb_queue_len(&priv->txq[index]) > priv->txq_limit) + dev_kfree_skb_any(skb); + else + skb_queue_tail(&priv->txq[index], skb); + + mwl_tx_skbs(hw); + + /* Initiate the ampdu session here */ + if (start_ba_session) { + spin_lock(&priv->stream_lock); + if (mwl_fwcmd_start_stream(hw, stream)) + mwl_fwcmd_remove_stream(hw, stream); + spin_unlock(&priv->stream_lock); + } +} + +void mwl_tx_done(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv; + unsigned long flags; + int num; + struct mwl_desc_data *desc; + struct mwl_tx_hndl *tx_hndl; + struct mwl_tx_desc *tx_desc; + struct sk_buff *done_skb; + u32 rate, format, bandwidth, short_gi, rate_id; + struct mwl_dma_data *tr; + struct ieee80211_tx_info *info; + int hdrlen; + + priv = hw->priv; + + spin_lock_irqsave(&priv->tx_desc_lock, flags); + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + desc = &priv->desc_data[num]; + tx_hndl = desc->pstale_tx_hndl; + tx_desc = tx_hndl->pdesc; + + while (tx_hndl && + (tx_desc->status & cpu_to_le32(EAGLE_TXD_STATUS_OK)) && + (!(tx_desc->status & + cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)))) { + pci_unmap_single(priv->pdev, + le32_to_cpu(tx_desc->pkt_ptr), + tx_desc->pkt_len, + PCI_DMA_TODEVICE); + done_skb = tx_hndl->psk_buff; + rate = le32_to_cpu(tx_desc->rate_info); + tx_desc->pkt_len = 0; + tx_desc->status = + cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + tx_hndl->psk_buff = NULL; + wmb(); /* memory barrier */ + + tr = (struct mwl_dma_data *)done_skb->data; + info = IEEE80211_SKB_CB(done_skb); + ieee80211_tx_info_clear_status(info); + + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + info->status.rates[0].flags = 0; + + if (ieee80211_is_data(tr->wh.frame_control) || + ieee80211_is_data_qos(tr->wh.frame_control)) { + skb_get(done_skb); + skb_queue_tail(&priv->delay_q, done_skb); + + if (skb_queue_len(&priv->delay_q) > + SYSADPT_DELAY_FREE_Q_LIMIT) + dev_kfree_skb_any( + skb_dequeue(&priv->delay_q)); + + /* Prepare rate information */ + format = rate & MWL_TX_RATE_FORMAT_MASK; + bandwidth = + (rate & MWL_TX_RATE_BANDWIDTH_MASK) >> + MWL_TX_RATE_BANDWIDTH_SHIFT; + short_gi = (rate & MWL_TX_RATE_SHORTGI_MASK) >> + MWL_TX_RATE_SHORTGI_SHIFT; + rate_id = (rate & MWL_TX_RATE_RATEIDMCS_MASK) >> + MWL_TX_RATE_RATEIDMCS_SHIFT; + + info->status.rates[0].idx = rate_id; + if (format == TX_RATE_FORMAT_LEGACY) { + if (hw->conf.chandef.chan->hw_value > + BAND_24_CHANNEL_NUM) { + info->status.rates[0].idx -= 5; + } + } + if (format == TX_RATE_FORMAT_11N) + info->status.rates[0].flags |= + IEEE80211_TX_RC_MCS; + if (format == TX_RATE_FORMAT_11AC) + info->status.rates[0].flags |= + IEEE80211_TX_RC_VHT_MCS; + if (bandwidth == TX_RATE_BANDWIDTH_40) + info->status.rates[0].flags |= + IEEE80211_TX_RC_40_MHZ_WIDTH; + if (bandwidth == TX_RATE_BANDWIDTH_80) + info->status.rates[0].flags |= + IEEE80211_TX_RC_80_MHZ_WIDTH; + if (short_gi == TX_RATE_INFO_SHORT_GI) + info->status.rates[0].flags |= + IEEE80211_TX_RC_SHORT_GI; + info->status.rates[0].count = 1; + + info->status.rates[1].idx = -1; + } + + /* Remove H/W dma header */ + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + skb_pull(done_skb, sizeof(*tr) - hdrlen); + + info->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status(hw, done_skb); + + tx_hndl = tx_hndl->pnext; + tx_desc = tx_hndl->pdesc; + priv->fw_desc_cnt[num]--; + } + + desc->pstale_tx_hndl = tx_hndl; + } + spin_unlock_irqrestore(&priv->tx_desc_lock, flags); + + if (priv->irq != -1) { + u32 status; + + status = readl(priv->iobase1 + + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + writel(status | MACREG_A2HRIC_BIT_TX_DONE, + priv->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + mwl_tx_skbs(hw); + } + + priv->is_tx_schedule = false; +} diff --git a/drivers/net/wireless/mwlwifi/tx.h b/drivers/net/wireless/mwlwifi/tx.h new file mode 100644 index 0000000..175437c --- /dev/null +++ b/drivers/net/wireless/mwlwifi/tx.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2006-2015, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines transmit related functions. */ + +#ifndef _tx_h_ +#define _tx_h_ + +int mwl_tx_init(struct ieee80211_hw *hw); +void mwl_tx_deinit(struct ieee80211_hw *hw); +void mwl_tx_xmit(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +void mwl_tx_done(unsigned long data); + +#endif /* _tx_h_ */ -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html