summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/marvell/libertas
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/marvell/libertas')
-rw-r--r--drivers/net/wireless/marvell/libertas/Kconfig44
-rw-r--r--drivers/net/wireless/marvell/libertas/LICENSE16
-rw-r--r--drivers/net/wireless/marvell/libertas/Makefile22
-rw-r--r--drivers/net/wireless/marvell/libertas/README239
-rw-r--r--drivers/net/wireless/marvell/libertas/cfg.c2167
-rw-r--r--drivers/net/wireless/marvell/libertas/cfg.h22
-rw-r--r--drivers/net/wireless/marvell/libertas/cmd.c1598
-rw-r--r--drivers/net/wireless/marvell/libertas/cmd.h142
-rw-r--r--drivers/net/wireless/marvell/libertas/cmdresp.c353
-rw-r--r--drivers/net/wireless/marvell/libertas/debugfs.c931
-rw-r--r--drivers/net/wireless/marvell/libertas/debugfs.h11
-rw-r--r--drivers/net/wireless/marvell/libertas/decl.h83
-rw-r--r--drivers/net/wireless/marvell/libertas/defs.h385
-rw-r--r--drivers/net/wireless/marvell/libertas/dev.h212
-rw-r--r--drivers/net/wireless/marvell/libertas/ethtool.c118
-rw-r--r--drivers/net/wireless/marvell/libertas/firmware.c228
-rw-r--r--drivers/net/wireless/marvell/libertas/host.h979
-rw-r--r--drivers/net/wireless/marvell/libertas/if_cs.c957
-rw-r--r--drivers/net/wireless/marvell/libertas/if_sdio.c1416
-rw-r--r--drivers/net/wireless/marvell/libertas/if_sdio.h48
-rw-r--r--drivers/net/wireless/marvell/libertas/if_spi.c1288
-rw-r--r--drivers/net/wireless/marvell/libertas/if_spi.h202
-rw-r--r--drivers/net/wireless/marvell/libertas/if_usb.c999
-rw-r--r--drivers/net/wireless/marvell/libertas/if_usb.h107
-rw-r--r--drivers/net/wireless/marvell/libertas/main.c1157
-rw-r--r--drivers/net/wireless/marvell/libertas/mesh.c1139
-rw-r--r--drivers/net/wireless/marvell/libertas/mesh.h77
-rw-r--r--drivers/net/wireless/marvell/libertas/radiotap.h45
-rw-r--r--drivers/net/wireless/marvell/libertas/rx.c271
-rw-r--r--drivers/net/wireless/marvell/libertas/tx.c205
-rw-r--r--drivers/net/wireless/marvell/libertas/types.h269
31 files changed, 15730 insertions, 0 deletions
diff --git a/drivers/net/wireless/marvell/libertas/Kconfig b/drivers/net/wireless/marvell/libertas/Kconfig
new file mode 100644
index 000000000..c7d02adb3
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/Kconfig
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config LIBERTAS
+ tristate "Marvell 8xxx Libertas WLAN driver support"
+ depends on CFG80211
+ select LIB80211
+ select FW_LOADER
+ help
+ A library for Marvell Libertas 8xxx devices.
+
+config LIBERTAS_USB
+ tristate "Marvell Libertas 8388 USB 802.11b/g cards"
+ depends on LIBERTAS && USB
+ help
+ A driver for Marvell Libertas 8388 USB devices.
+
+config LIBERTAS_CS
+ tristate "Marvell Libertas 8385 CompactFlash 802.11b/g cards"
+ depends on LIBERTAS && PCMCIA && HAS_IOPORT_MAP
+ help
+ A driver for Marvell Libertas 8385 CompactFlash devices.
+
+config LIBERTAS_SDIO
+ tristate "Marvell Libertas 8385/8686/8688 SDIO 802.11b/g cards"
+ depends on LIBERTAS && MMC
+ help
+ A driver for Marvell Libertas 8385/8686/8688 SDIO devices.
+
+config LIBERTAS_SPI
+ tristate "Marvell Libertas 8686 SPI 802.11b/g cards"
+ depends on LIBERTAS && SPI
+ help
+ A driver for Marvell Libertas 8686 SPI devices.
+
+config LIBERTAS_DEBUG
+ bool "Enable full debugging output in the Libertas module."
+ depends on LIBERTAS
+ help
+ Debugging support.
+
+config LIBERTAS_MESH
+ bool "Enable mesh support"
+ depends on LIBERTAS
+ help
+ This enables Libertas' MESH support, used by e.g. the OLPC people.
diff --git a/drivers/net/wireless/marvell/libertas/LICENSE b/drivers/net/wireless/marvell/libertas/LICENSE
new file mode 100644
index 000000000..886274221
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/LICENSE
@@ -0,0 +1,16 @@
+ Copyright (c) 2003-2006, Marvell International Ltd.
+ All Rights Reserved
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of version 2 of the GNU General Public License as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program; if not, write to the Free Software Foundation, Inc., 59
+ Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
diff --git a/drivers/net/wireless/marvell/libertas/Makefile b/drivers/net/wireless/marvell/libertas/Makefile
new file mode 100644
index 000000000..41b9b440a
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/Makefile
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0
+libertas-y += cfg.o
+libertas-y += cmd.o
+libertas-y += cmdresp.o
+libertas-y += debugfs.o
+libertas-y += ethtool.o
+libertas-y += main.o
+libertas-y += rx.o
+libertas-y += tx.o
+libertas-y += firmware.o
+libertas-$(CONFIG_LIBERTAS_MESH) += mesh.o
+
+usb8xxx-objs += if_usb.o
+libertas_cs-objs += if_cs.o
+libertas_sdio-objs += if_sdio.o
+libertas_spi-objs += if_spi.o
+
+obj-$(CONFIG_LIBERTAS) += libertas.o
+obj-$(CONFIG_LIBERTAS_USB) += usb8xxx.o
+obj-$(CONFIG_LIBERTAS_CS) += libertas_cs.o
+obj-$(CONFIG_LIBERTAS_SDIO) += libertas_sdio.o
+obj-$(CONFIG_LIBERTAS_SPI) += libertas_spi.o
diff --git a/drivers/net/wireless/marvell/libertas/README b/drivers/net/wireless/marvell/libertas/README
new file mode 100644
index 000000000..1a554a685
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/README
@@ -0,0 +1,239 @@
+================================================================================
+ README for Libertas
+
+ (c) Copyright © 2003-2006, Marvell International Ltd.
+ All Rights Reserved
+
+ 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 along with the File in the license.txt file or on the worldwide
+ web at http://www.gnu.org/licenses/gpl.txt.
+
+ 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.
+================================================================================
+
+=====================
+DRIVER LOADING
+=====================
+
+ o. Copy the firmware image (e.g. usb8388.bin) to /lib/firmware/
+
+ o. Load driver by using the following command:
+
+ insmod usb8388.ko [fw_name=usb8388.bin]
+
+=========================
+ETHTOOL
+=========================
+
+
+Use the -i option to retrieve version information from the driver.
+
+# ethtool -i eth0
+driver: libertas
+version: COMM-USB8388-318.p4
+firmware-version: 5.110.7
+bus-info:
+
+Use the -e option to read the EEPROM contents of the card.
+
+ Usage:
+ ethtool -e ethX [raw on|off] [offset N] [length N]
+
+ -e retrieves and prints an EEPROM dump for the specified ethernet
+ device. When raw is enabled, then it dumps the raw EEPROM data
+ to stdout. The length and offset parameters allow dumping cer-
+ tain portions of the EEPROM. Default is to dump the entire EEP-
+ ROM.
+
+# ethtool -e eth0 offset 0 length 16
+Offset Values
+------ ------
+0x0000 38 33 30 58 00 00 34 f4 00 00 10 00 00 c4 17 00
+
+========================
+DEBUGFS COMMANDS
+========================
+
+those commands are used via debugfs interface
+
+===========
+rdmac
+rdbbp
+rdrf
+ These commands are used to read the MAC, BBP and RF registers from the
+ card. These commands take one parameter that specifies the offset
+ location that is to be read. This parameter must be specified in
+ hexadecimal (its possible to precede preceding the number with a "0x").
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/registers/
+
+ Usage:
+ echo "0xa123" > rdmac ; cat rdmac
+ echo "0xa123" > rdbbp ; cat rdbbp
+ echo "0xa123" > rdrf ; cat rdrf
+wrmac
+wrbbp
+wrrf
+ These commands are used to write the MAC, BBP and RF registers in the
+ card. These commands take two parameters that specify the offset
+ location and the value that is to be written. This parameters must
+ be specified in hexadecimal (its possible to precede the number
+ with a "0x").
+
+ Usage:
+ echo "0xa123 0xaa" > wrmac
+ echo "0xa123 0xaa" > wrbbp
+ echo "0xa123 0xaa" > wrrf
+
+sleepparams
+ This command is used to set the sleepclock configurations
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/
+
+ Usage:
+ cat sleepparams: reads the current sleepclock configuration
+
+ echo "p1 p2 p3 p4 p5 p6" > sleepparams: writes the sleepclock configuration.
+
+ where:
+ p1 is Sleep clock error in ppm (0-65535)
+ p2 is Wakeup offset in usec (0-65535)
+ p3 is Clock stabilization time in usec (0-65535)
+ p4 is Control periodic calibration (0-2)
+ p5 is Control the use of external sleep clock (0-2)
+ p6 is reserved for debug (0-65535)
+
+subscribed_events
+
+ The subscribed_events directory contains the interface for the
+ subscribed events API.
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/subscribed_events/
+
+ Each event is represented by a filename. Each filename consists of the
+ following three fields:
+ Value Frequency Subscribed
+
+ To read the current values for a given event, do:
+ cat event
+ To set the current values, do:
+ echo "60 2 1" > event
+
+ Frequency field specifies the reporting frequency for this event.
+ If it is set to 0, then the event is reported only once, and then
+ automatically unsubscribed. If it is set to 1, then the event is
+ reported every time it occurs. If it is set to N, then the event is
+ reported every Nth time it occurs.
+
+ beacon_missed
+ Value field specifies the number of consecutive missing beacons which
+ triggers the LINK_LOSS event. This event is generated only once after
+ which the firmware resets its state. At initialization, the LINK_LOSS
+ event is subscribed by default. The default value of MissedBeacons is
+ 60.
+
+ failure_count
+ Value field specifies the consecutive failure count threshold which
+ triggers the generation of the MAX_FAIL event. Once this event is
+ generated, the consecutive failure count is reset to 0.
+ At initialization, the MAX_FAIL event is NOT subscribed by
+ default.
+
+ high_rssi
+ This event is generated when the average received RSSI in beacons goes
+ above a threshold, specified by Value.
+
+ low_rssi
+ This event is generated when the average received RSSI in beacons goes
+ below a threshold, specified by Value.
+
+ high_snr
+ This event is generated when the average received SNR in beacons goes
+ above a threshold, specified by Value.
+
+ low_snr
+ This event is generated when the average received SNR in beacons goes
+ below a threshold, specified by Value.
+
+extscan
+ This command is used to do a specific scan.
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/
+
+ Usage: echo "SSID" > extscan
+
+ Example:
+ echo "LINKSYS-AP" > extscan
+
+ To see the results of use getscantable command.
+
+getscantable
+
+ Display the current contents of the driver scan table (ie. get the
+ scan results).
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/
+
+ Usage:
+ cat getscantable
+
+setuserscan
+ Initiate a customized scan and retrieve the results
+
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/
+
+ Usage:
+ echo "[ARGS]" > setuserscan
+
+ where [ARGS]:
+
+ bssid=xx:xx:xx:xx:xx:xx specify a BSSID filter for the scan
+ ssid="[SSID]" specify a SSID filter for the scan
+ keep=[0 or 1] keep the previous scan results (1), discard (0)
+ dur=[scan time] time to scan for each channel in milliseconds
+ type=[1,2,3] BSS type: 1 (Infra), 2(Adhoc), 3(Any)
+
+ Any combination of the above arguments can be supplied on the command
+ line. If dur tokens are absent, the driver default setting will be used.
+ The bssid and ssid fields, if blank, will produce an unfiltered scan.
+ The type field will default to 3 (Any) and the keep field will default
+ to 0 (Discard).
+
+ Examples:
+ 1) Perform a passive scan on all channels for 20 ms per channel:
+ echo "dur=20" > setuserscan
+
+ 2) Perform an active scan for a specific SSID:
+ echo "ssid="TestAP"" > setuserscan
+
+ 3) Scan all available channels (B/G, A bands) for a specific BSSID, keep
+ the current scan table intact, update existing or append new scan data:
+ echo "bssid=00:50:43:20:12:82 keep=1" > setuserscan
+
+ 4) Scan for all infrastructure networks.
+ Keep the previous scan table intact. Update any duplicate BSSID/SSID
+ matches with the new scan data:
+ echo "type=1 keep=1" > setuserscan
+
+ All entries in the scan table (not just the new scan data when keep=1)
+ will be displayed upon completion by use of the getscantable ioctl.
+
+hostsleep
+ This command is used to enable/disable host sleep.
+ Note: Host sleep parameters should be configured using
+ "ethtool -s ethX wol X" command before enabling host sleep.
+
+ Path: /sys/kernel/debug/libertas_wireless/ethX/
+
+ Usage:
+ cat hostsleep: reads the current hostsleep state
+ echo "1" > hostsleep : enable host sleep.
+ echo "0" > hostsleep : disable host sleep
+
diff --git a/drivers/net/wireless/marvell/libertas/cfg.c b/drivers/net/wireless/marvell/libertas/cfg.c
new file mode 100644
index 000000000..4e3de6849
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/cfg.c
@@ -0,0 +1,2167 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implement cfg80211 ("iw") support.
+ *
+ * Copyright (C) 2009 M&N Solutions GmbH, 61191 Rosbach, Germany
+ * Holger Schurig <hs4233@mail.mn-solutions.de>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hardirq.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <asm/unaligned.h>
+
+#include "decl.h"
+#include "cfg.h"
+#include "cmd.h"
+#include "mesh.h"
+
+
+#define CHAN2G(_channel, _freq, _flags) { \
+ .band = NL80211_BAND_2GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_channel), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+static struct ieee80211_channel lbs_2ghz_channels[] = {
+ CHAN2G(1, 2412, 0),
+ CHAN2G(2, 2417, 0),
+ CHAN2G(3, 2422, 0),
+ CHAN2G(4, 2427, 0),
+ CHAN2G(5, 2432, 0),
+ CHAN2G(6, 2437, 0),
+ CHAN2G(7, 2442, 0),
+ CHAN2G(8, 2447, 0),
+ CHAN2G(9, 2452, 0),
+ CHAN2G(10, 2457, 0),
+ CHAN2G(11, 2462, 0),
+ CHAN2G(12, 2467, 0),
+ CHAN2G(13, 2472, 0),
+ CHAN2G(14, 2484, 0),
+};
+
+#define RATETAB_ENT(_rate, _hw_value, _flags) { \
+ .bitrate = (_rate), \
+ .hw_value = (_hw_value), \
+ .flags = (_flags), \
+}
+
+
+/* Table 6 in section 3.2.1.1 */
+static struct ieee80211_rate lbs_rates[] = {
+ RATETAB_ENT(10, 0, 0),
+ RATETAB_ENT(20, 1, 0),
+ RATETAB_ENT(55, 2, 0),
+ RATETAB_ENT(110, 3, 0),
+ RATETAB_ENT(60, 9, 0),
+ RATETAB_ENT(90, 6, 0),
+ RATETAB_ENT(120, 7, 0),
+ RATETAB_ENT(180, 8, 0),
+ RATETAB_ENT(240, 9, 0),
+ RATETAB_ENT(360, 10, 0),
+ RATETAB_ENT(480, 11, 0),
+ RATETAB_ENT(540, 12, 0),
+};
+
+static struct ieee80211_supported_band lbs_band_2ghz = {
+ .channels = lbs_2ghz_channels,
+ .n_channels = ARRAY_SIZE(lbs_2ghz_channels),
+ .bitrates = lbs_rates,
+ .n_bitrates = ARRAY_SIZE(lbs_rates),
+};
+
+
+static const u32 cipher_suites[] = {
+ WLAN_CIPHER_SUITE_WEP40,
+ WLAN_CIPHER_SUITE_WEP104,
+ WLAN_CIPHER_SUITE_TKIP,
+ WLAN_CIPHER_SUITE_CCMP,
+};
+
+/* Time to stay on the channel */
+#define LBS_DWELL_PASSIVE 100
+#define LBS_DWELL_ACTIVE 40
+
+
+/***************************************************************************
+ * Misc utility functions
+ *
+ * TLVs are Marvell specific. They are very similar to IEs, they have the
+ * same structure: type, length, data*. The only difference: for IEs, the
+ * type and length are u8, but for TLVs they're __le16.
+ */
+
+/*
+ * Convert NL80211's auth_type to the one from Libertas, see chapter 5.9.1
+ * in the firmware spec
+ */
+static int lbs_auth_to_authtype(enum nl80211_auth_type auth_type)
+{
+ int ret = -ENOTSUPP;
+
+ switch (auth_type) {
+ case NL80211_AUTHTYPE_OPEN_SYSTEM:
+ case NL80211_AUTHTYPE_SHARED_KEY:
+ ret = auth_type;
+ break;
+ case NL80211_AUTHTYPE_AUTOMATIC:
+ ret = NL80211_AUTHTYPE_OPEN_SYSTEM;
+ break;
+ case NL80211_AUTHTYPE_NETWORK_EAP:
+ ret = 0x80;
+ break;
+ default:
+ /* silence compiler */
+ break;
+ }
+ return ret;
+}
+
+
+/*
+ * Various firmware commands need the list of supported rates, but with
+ * the hight-bit set for basic rates
+ */
+static int lbs_add_rates(u8 *rates)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(lbs_rates); i++) {
+ u8 rate = lbs_rates[i].bitrate / 5;
+ if (rate == 0x02 || rate == 0x04 ||
+ rate == 0x0b || rate == 0x16)
+ rate |= 0x80;
+ rates[i] = rate;
+ }
+ return ARRAY_SIZE(lbs_rates);
+}
+
+
+/***************************************************************************
+ * TLV utility functions
+ *
+ * TLVs are Marvell specific. They are very similar to IEs, they have the
+ * same structure: type, length, data*. The only difference: for IEs, the
+ * type and length are u8, but for TLVs they're __le16.
+ */
+
+
+/*
+ * Add ssid TLV
+ */
+#define LBS_MAX_SSID_TLV_SIZE \
+ (sizeof(struct mrvl_ie_header) \
+ + IEEE80211_MAX_SSID_LEN)
+
+static int lbs_add_ssid_tlv(u8 *tlv, const u8 *ssid, int ssid_len)
+{
+ struct mrvl_ie_ssid_param_set *ssid_tlv = (void *)tlv;
+
+ /*
+ * TLV-ID SSID 00 00
+ * length 06 00
+ * ssid 4d 4e 54 45 53 54
+ */
+ ssid_tlv->header.type = cpu_to_le16(TLV_TYPE_SSID);
+ ssid_tlv->header.len = cpu_to_le16(ssid_len);
+ memcpy(ssid_tlv->ssid, ssid, ssid_len);
+ return sizeof(ssid_tlv->header) + ssid_len;
+}
+
+
+/*
+ * Add channel list TLV (section 8.4.2)
+ *
+ * Actual channel data comes from priv->wdev->wiphy->channels.
+ */
+#define LBS_MAX_CHANNEL_LIST_TLV_SIZE \
+ (sizeof(struct mrvl_ie_header) \
+ + (LBS_SCAN_BEFORE_NAP * sizeof(struct chanscanparamset)))
+
+static int lbs_add_channel_list_tlv(struct lbs_private *priv, u8 *tlv,
+ int last_channel, int active_scan)
+{
+ int chanscanparamsize = sizeof(struct chanscanparamset) *
+ (last_channel - priv->scan_channel);
+
+ struct mrvl_ie_header *header = (void *) tlv;
+
+ /*
+ * TLV-ID CHANLIST 01 01
+ * length 0e 00
+ * channel 00 01 00 00 00 64 00
+ * radio type 00
+ * channel 01
+ * scan type 00
+ * min scan time 00 00
+ * max scan time 64 00
+ * channel 2 00 02 00 00 00 64 00
+ *
+ */
+
+ header->type = cpu_to_le16(TLV_TYPE_CHANLIST);
+ header->len = cpu_to_le16(chanscanparamsize);
+ tlv += sizeof(struct mrvl_ie_header);
+
+ /* lbs_deb_scan("scan: channels %d to %d\n", priv->scan_channel,
+ last_channel); */
+ memset(tlv, 0, chanscanparamsize);
+
+ while (priv->scan_channel < last_channel) {
+ struct chanscanparamset *param = (void *) tlv;
+
+ param->radiotype = CMD_SCAN_RADIO_TYPE_BG;
+ param->channumber =
+ priv->scan_req->channels[priv->scan_channel]->hw_value;
+ if (active_scan) {
+ param->maxscantime = cpu_to_le16(LBS_DWELL_ACTIVE);
+ } else {
+ param->chanscanmode.passivescan = 1;
+ param->maxscantime = cpu_to_le16(LBS_DWELL_PASSIVE);
+ }
+ tlv += sizeof(struct chanscanparamset);
+ priv->scan_channel++;
+ }
+ return sizeof(struct mrvl_ie_header) + chanscanparamsize;
+}
+
+
+/*
+ * Add rates TLV
+ *
+ * The rates are in lbs_bg_rates[], but for the 802.11b
+ * rates the high bit is set. We add this TLV only because
+ * there's a firmware which otherwise doesn't report all
+ * APs in range.
+ */
+#define LBS_MAX_RATES_TLV_SIZE \
+ (sizeof(struct mrvl_ie_header) \
+ + (ARRAY_SIZE(lbs_rates)))
+
+/* Adds a TLV with all rates the hardware supports */
+static int lbs_add_supported_rates_tlv(u8 *tlv)
+{
+ size_t i;
+ struct mrvl_ie_rates_param_set *rate_tlv = (void *)tlv;
+
+ /*
+ * TLV-ID RATES 01 00
+ * length 0e 00
+ * rates 82 84 8b 96 0c 12 18 24 30 48 60 6c
+ */
+ rate_tlv->header.type = cpu_to_le16(TLV_TYPE_RATES);
+ tlv += sizeof(rate_tlv->header);
+ i = lbs_add_rates(tlv);
+ tlv += i;
+ rate_tlv->header.len = cpu_to_le16(i);
+ return sizeof(rate_tlv->header) + i;
+}
+
+/* Add common rates from a TLV and return the new end of the TLV */
+static u8 *
+add_ie_rates(u8 *tlv, const u8 *ie, int *nrates)
+{
+ int hw, ap, ap_max = ie[1];
+ u8 hw_rate;
+
+ if (ap_max > MAX_RATES) {
+ lbs_deb_assoc("invalid rates\n");
+ return tlv;
+ }
+ /* Advance past IE header */
+ ie += 2;
+
+ lbs_deb_hex(LBS_DEB_ASSOC, "AP IE Rates", (u8 *) ie, ap_max);
+
+ for (hw = 0; hw < ARRAY_SIZE(lbs_rates); hw++) {
+ hw_rate = lbs_rates[hw].bitrate / 5;
+ for (ap = 0; ap < ap_max; ap++) {
+ if (hw_rate == (ie[ap] & 0x7f)) {
+ *tlv++ = ie[ap];
+ *nrates = *nrates + 1;
+ }
+ }
+ }
+ return tlv;
+}
+
+/*
+ * Adds a TLV with all rates the hardware *and* BSS supports.
+ */
+static int lbs_add_common_rates_tlv(u8 *tlv, struct cfg80211_bss *bss)
+{
+ struct mrvl_ie_rates_param_set *rate_tlv = (void *)tlv;
+ const u8 *rates_eid, *ext_rates_eid;
+ int n = 0;
+
+ rcu_read_lock();
+ rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
+ ext_rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES);
+
+ /*
+ * 01 00 TLV_TYPE_RATES
+ * 04 00 len
+ * 82 84 8b 96 rates
+ */
+ rate_tlv->header.type = cpu_to_le16(TLV_TYPE_RATES);
+ tlv += sizeof(rate_tlv->header);
+
+ /* Add basic rates */
+ if (rates_eid) {
+ tlv = add_ie_rates(tlv, rates_eid, &n);
+
+ /* Add extended rates, if any */
+ if (ext_rates_eid)
+ tlv = add_ie_rates(tlv, ext_rates_eid, &n);
+ } else {
+ lbs_deb_assoc("assoc: bss had no basic rate IE\n");
+ /* Fallback: add basic 802.11b rates */
+ *tlv++ = 0x82;
+ *tlv++ = 0x84;
+ *tlv++ = 0x8b;
+ *tlv++ = 0x96;
+ n = 4;
+ }
+ rcu_read_unlock();
+
+ rate_tlv->header.len = cpu_to_le16(n);
+ return sizeof(rate_tlv->header) + n;
+}
+
+
+/*
+ * Add auth type TLV.
+ *
+ * This is only needed for newer firmware (V9 and up).
+ */
+#define LBS_MAX_AUTH_TYPE_TLV_SIZE \
+ sizeof(struct mrvl_ie_auth_type)
+
+static int lbs_add_auth_type_tlv(u8 *tlv, enum nl80211_auth_type auth_type)
+{
+ struct mrvl_ie_auth_type *auth = (void *) tlv;
+
+ /*
+ * 1f 01 TLV_TYPE_AUTH_TYPE
+ * 01 00 len
+ * 01 auth type
+ */
+ auth->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE);
+ auth->header.len = cpu_to_le16(sizeof(*auth)-sizeof(auth->header));
+ auth->auth = cpu_to_le16(lbs_auth_to_authtype(auth_type));
+ return sizeof(*auth);
+}
+
+
+/*
+ * Add channel (phy ds) TLV
+ */
+#define LBS_MAX_CHANNEL_TLV_SIZE \
+ sizeof(struct mrvl_ie_header)
+
+static int lbs_add_channel_tlv(u8 *tlv, u8 channel)
+{
+ struct mrvl_ie_ds_param_set *ds = (void *) tlv;
+
+ /*
+ * 03 00 TLV_TYPE_PHY_DS
+ * 01 00 len
+ * 06 channel
+ */
+ ds->header.type = cpu_to_le16(TLV_TYPE_PHY_DS);
+ ds->header.len = cpu_to_le16(sizeof(*ds)-sizeof(ds->header));
+ ds->channel = channel;
+ return sizeof(*ds);
+}
+
+
+/*
+ * Add (empty) CF param TLV of the form:
+ */
+#define LBS_MAX_CF_PARAM_TLV_SIZE \
+ sizeof(struct mrvl_ie_header)
+
+static int lbs_add_cf_param_tlv(u8 *tlv)
+{
+ struct mrvl_ie_cf_param_set *cf = (void *)tlv;
+
+ /*
+ * 04 00 TLV_TYPE_CF
+ * 06 00 len
+ * 00 cfpcnt
+ * 00 cfpperiod
+ * 00 00 cfpmaxduration
+ * 00 00 cfpdurationremaining
+ */
+ cf->header.type = cpu_to_le16(TLV_TYPE_CF);
+ cf->header.len = cpu_to_le16(sizeof(*cf)-sizeof(cf->header));
+ return sizeof(*cf);
+}
+
+/*
+ * Add WPA TLV
+ */
+#define LBS_MAX_WPA_TLV_SIZE \
+ (sizeof(struct mrvl_ie_header) \
+ + 128 /* TODO: I guessed the size */)
+
+static int lbs_add_wpa_tlv(u8 *tlv, const u8 *ie, u8 ie_len)
+{
+ size_t tlv_len;
+
+ /*
+ * We need just convert an IE to an TLV. IEs use u8 for the header,
+ * u8 type
+ * u8 len
+ * u8[] data
+ * but TLVs use __le16 instead:
+ * __le16 type
+ * __le16 len
+ * u8[] data
+ */
+ *tlv++ = *ie++;
+ *tlv++ = 0;
+ tlv_len = *tlv++ = *ie++;
+ *tlv++ = 0;
+ while (tlv_len--)
+ *tlv++ = *ie++;
+ /* the TLV is two bytes larger than the IE */
+ return ie_len + 2;
+}
+
+/*
+ * Set Channel
+ */
+
+static int lbs_cfg_set_monitor_channel(struct wiphy *wiphy,
+ struct cfg80211_chan_def *chandef)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ int ret = -ENOTSUPP;
+
+ if (cfg80211_get_chandef_type(chandef) != NL80211_CHAN_NO_HT)
+ goto out;
+
+ ret = lbs_set_channel(priv, chandef->chan->hw_value);
+
+ out:
+ return ret;
+}
+
+static int lbs_cfg_set_mesh_channel(struct wiphy *wiphy,
+ struct net_device *netdev,
+ struct ieee80211_channel *channel)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ int ret = -ENOTSUPP;
+
+ if (netdev != priv->mesh_dev)
+ goto out;
+
+ ret = lbs_mesh_set_channel(priv, channel->hw_value);
+
+ out:
+ return ret;
+}
+
+
+
+/*
+ * Scanning
+ */
+
+/*
+ * When scanning, the firmware doesn't send a nul packet with the power-safe
+ * bit to the AP. So we cannot stay away from our current channel too long,
+ * otherwise we loose data. So take a "nap" while scanning every other
+ * while.
+ */
+#define LBS_SCAN_BEFORE_NAP 4
+
+
+/*
+ * When the firmware reports back a scan-result, it gives us an "u8 rssi",
+ * which isn't really an RSSI, as it becomes larger when moving away from
+ * the AP. Anyway, we need to convert that into mBm.
+ */
+#define LBS_SCAN_RSSI_TO_MBM(rssi) \
+ ((-(int)rssi + 3)*100)
+
+static int lbs_ret_scan(struct lbs_private *priv, unsigned long dummy,
+ struct cmd_header *resp)
+{
+ struct cfg80211_bss *bss;
+ struct cmd_ds_802_11_scan_rsp *scanresp = (void *)resp;
+ int bsssize;
+ const u8 *pos;
+ const u8 *tsfdesc;
+ int tsfsize;
+ int i;
+ int ret = -EILSEQ;
+
+ bsssize = get_unaligned_le16(&scanresp->bssdescriptsize);
+
+ lbs_deb_scan("scan response: %d BSSs (%d bytes); resp size %d bytes\n",
+ scanresp->nr_sets, bsssize, le16_to_cpu(resp->size));
+
+ if (scanresp->nr_sets == 0) {
+ ret = 0;
+ goto done;
+ }
+
+ /*
+ * The general layout of the scan response is described in chapter
+ * 5.7.1. Basically we have a common part, then any number of BSS
+ * descriptor sections. Finally we have section with the same number
+ * of TSFs.
+ *
+ * cmd_ds_802_11_scan_rsp
+ * cmd_header
+ * pos_size
+ * nr_sets
+ * bssdesc 1
+ * bssid
+ * rssi
+ * timestamp
+ * intvl
+ * capa
+ * IEs
+ * bssdesc 2
+ * bssdesc n
+ * MrvlIEtypes_TsfFimestamp_t
+ * TSF for BSS 1
+ * TSF for BSS 2
+ * TSF for BSS n
+ */
+
+ pos = scanresp->bssdesc_and_tlvbuffer;
+
+ lbs_deb_hex(LBS_DEB_SCAN, "SCAN_RSP", scanresp->bssdesc_and_tlvbuffer,
+ scanresp->bssdescriptsize);
+
+ tsfdesc = pos + bsssize;
+ tsfsize = 4 + 8 * scanresp->nr_sets;
+ lbs_deb_hex(LBS_DEB_SCAN, "SCAN_TSF", (u8 *) tsfdesc, tsfsize);
+
+ /* Validity check: we expect a Marvell-Local TLV */
+ i = get_unaligned_le16(tsfdesc);
+ tsfdesc += 2;
+ if (i != TLV_TYPE_TSFTIMESTAMP) {
+ lbs_deb_scan("scan response: invalid TSF Timestamp %d\n", i);
+ goto done;
+ }
+
+ /*
+ * Validity check: the TLV holds TSF values with 8 bytes each, so
+ * the size in the TLV must match the nr_sets value
+ */
+ i = get_unaligned_le16(tsfdesc);
+ tsfdesc += 2;
+ if (i / 8 != scanresp->nr_sets) {
+ lbs_deb_scan("scan response: invalid number of TSF timestamp "
+ "sets (expected %d got %d)\n", scanresp->nr_sets,
+ i / 8);
+ goto done;
+ }
+
+ for (i = 0; i < scanresp->nr_sets; i++) {
+ const u8 *bssid;
+ const u8 *ie;
+ int left;
+ int ielen;
+ int rssi;
+ u16 intvl;
+ u16 capa;
+ int chan_no = -1;
+ const u8 *ssid = NULL;
+ u8 ssid_len = 0;
+
+ int len = get_unaligned_le16(pos);
+ pos += 2;
+
+ /* BSSID */
+ bssid = pos;
+ pos += ETH_ALEN;
+ /* RSSI */
+ rssi = *pos++;
+ /* Packet time stamp */
+ pos += 8;
+ /* Beacon interval */
+ intvl = get_unaligned_le16(pos);
+ pos += 2;
+ /* Capabilities */
+ capa = get_unaligned_le16(pos);
+ pos += 2;
+
+ /* To find out the channel, we must parse the IEs */
+ ie = pos;
+ /*
+ * 6+1+8+2+2: size of BSSID, RSSI, time stamp, beacon
+ * interval, capabilities
+ */
+ ielen = left = len - (6 + 1 + 8 + 2 + 2);
+ while (left >= 2) {
+ u8 id, elen;
+ id = *pos++;
+ elen = *pos++;
+ left -= 2;
+ if (elen > left) {
+ lbs_deb_scan("scan response: invalid IE fmt\n");
+ goto done;
+ }
+
+ if (id == WLAN_EID_DS_PARAMS)
+ chan_no = *pos;
+ if (id == WLAN_EID_SSID) {
+ ssid = pos;
+ ssid_len = elen;
+ }
+ left -= elen;
+ pos += elen;
+ }
+
+ /* No channel, no luck */
+ if (chan_no != -1) {
+ struct wiphy *wiphy = priv->wdev->wiphy;
+ int freq = ieee80211_channel_to_frequency(chan_no,
+ NL80211_BAND_2GHZ);
+ struct ieee80211_channel *channel =
+ ieee80211_get_channel(wiphy, freq);
+
+ lbs_deb_scan("scan: %pM, capa %04x, chan %2d, %*pE, %d dBm\n",
+ bssid, capa, chan_no, ssid_len, ssid,
+ LBS_SCAN_RSSI_TO_MBM(rssi)/100);
+
+ if (channel &&
+ !(channel->flags & IEEE80211_CHAN_DISABLED)) {
+ bss = cfg80211_inform_bss(wiphy, channel,
+ CFG80211_BSS_FTYPE_UNKNOWN,
+ bssid, get_unaligned_le64(tsfdesc),
+ capa, intvl, ie, ielen,
+ LBS_SCAN_RSSI_TO_MBM(rssi),
+ GFP_KERNEL);
+ cfg80211_put_bss(wiphy, bss);
+ }
+ } else
+ lbs_deb_scan("scan response: missing BSS channel IE\n");
+
+ tsfdesc += 8;
+ }
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+
+/*
+ * Our scan command contains a TLV, consting of a SSID TLV, a channel list
+ * TLV and a rates TLV. Determine the maximum size of them:
+ */
+#define LBS_SCAN_MAX_CMD_SIZE \
+ (sizeof(struct cmd_ds_802_11_scan) \
+ + LBS_MAX_SSID_TLV_SIZE \
+ + LBS_MAX_CHANNEL_LIST_TLV_SIZE \
+ + LBS_MAX_RATES_TLV_SIZE)
+
+/*
+ * Assumes priv->scan_req is initialized and valid
+ * Assumes priv->scan_channel is initialized
+ */
+static void lbs_scan_worker(struct work_struct *work)
+{
+ struct lbs_private *priv =
+ container_of(work, struct lbs_private, scan_work.work);
+ struct cmd_ds_802_11_scan *scan_cmd;
+ u8 *tlv; /* pointer into our current, growing TLV storage area */
+ int last_channel;
+ int running, carrier;
+
+ scan_cmd = kzalloc(LBS_SCAN_MAX_CMD_SIZE, GFP_KERNEL);
+ if (scan_cmd == NULL)
+ return;
+
+ /* prepare fixed part of scan command */
+ scan_cmd->bsstype = CMD_BSS_TYPE_ANY;
+
+ /* stop network while we're away from our main channel */
+ running = !netif_queue_stopped(priv->dev);
+ carrier = netif_carrier_ok(priv->dev);
+ if (running)
+ netif_stop_queue(priv->dev);
+ if (carrier)
+ netif_carrier_off(priv->dev);
+
+ /* prepare fixed part of scan command */
+ tlv = scan_cmd->tlvbuffer;
+
+ /* add SSID TLV */
+ if (priv->scan_req->n_ssids && priv->scan_req->ssids[0].ssid_len > 0)
+ tlv += lbs_add_ssid_tlv(tlv,
+ priv->scan_req->ssids[0].ssid,
+ priv->scan_req->ssids[0].ssid_len);
+
+ /* add channel TLVs */
+ last_channel = priv->scan_channel + LBS_SCAN_BEFORE_NAP;
+ if (last_channel > priv->scan_req->n_channels)
+ last_channel = priv->scan_req->n_channels;
+ tlv += lbs_add_channel_list_tlv(priv, tlv, last_channel,
+ priv->scan_req->n_ssids);
+
+ /* add rates TLV */
+ tlv += lbs_add_supported_rates_tlv(tlv);
+
+ if (priv->scan_channel < priv->scan_req->n_channels) {
+ cancel_delayed_work(&priv->scan_work);
+ if (netif_running(priv->dev))
+ queue_delayed_work(priv->work_thread, &priv->scan_work,
+ msecs_to_jiffies(300));
+ }
+
+ /* This is the final data we are about to send */
+ scan_cmd->hdr.size = cpu_to_le16(tlv - (u8 *)scan_cmd);
+ lbs_deb_hex(LBS_DEB_SCAN, "SCAN_CMD", (void *)scan_cmd,
+ sizeof(*scan_cmd));
+ lbs_deb_hex(LBS_DEB_SCAN, "SCAN_TLV", scan_cmd->tlvbuffer,
+ tlv - scan_cmd->tlvbuffer);
+
+ __lbs_cmd(priv, CMD_802_11_SCAN, &scan_cmd->hdr,
+ le16_to_cpu(scan_cmd->hdr.size),
+ lbs_ret_scan, 0);
+
+ if (priv->scan_channel >= priv->scan_req->n_channels) {
+ /* Mark scan done */
+ cancel_delayed_work(&priv->scan_work);
+ lbs_scan_done(priv);
+ }
+
+ /* Restart network */
+ if (carrier)
+ netif_carrier_on(priv->dev);
+ if (running && !priv->tx_pending_len)
+ netif_wake_queue(priv->dev);
+
+ kfree(scan_cmd);
+
+ /* Wake up anything waiting on scan completion */
+ if (priv->scan_req == NULL) {
+ lbs_deb_scan("scan: waking up waiters\n");
+ wake_up_all(&priv->scan_q);
+ }
+}
+
+static void _internal_start_scan(struct lbs_private *priv, bool internal,
+ struct cfg80211_scan_request *request)
+{
+ lbs_deb_scan("scan: ssids %d, channels %d, ie_len %zd\n",
+ request->n_ssids, request->n_channels, request->ie_len);
+
+ priv->scan_channel = 0;
+ priv->scan_req = request;
+ priv->internal_scan = internal;
+
+ queue_delayed_work(priv->work_thread, &priv->scan_work,
+ msecs_to_jiffies(50));
+}
+
+/*
+ * Clean up priv->scan_req. Should be used to handle the allocation details.
+ */
+void lbs_scan_done(struct lbs_private *priv)
+{
+ WARN_ON(!priv->scan_req);
+
+ if (priv->internal_scan) {
+ kfree(priv->scan_req);
+ } else {
+ struct cfg80211_scan_info info = {
+ .aborted = false,
+ };
+
+ cfg80211_scan_done(priv->scan_req, &info);
+ }
+
+ priv->scan_req = NULL;
+}
+
+static int lbs_cfg_scan(struct wiphy *wiphy,
+ struct cfg80211_scan_request *request)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ int ret = 0;
+
+ if (priv->scan_req || delayed_work_pending(&priv->scan_work)) {
+ /* old scan request not yet processed */
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ _internal_start_scan(priv, false, request);
+
+ if (priv->surpriseremoved)
+ ret = -EIO;
+
+ out:
+ return ret;
+}
+
+
+
+
+/*
+ * Events
+ */
+
+void lbs_send_disconnect_notification(struct lbs_private *priv,
+ bool locally_generated)
+{
+ cfg80211_disconnected(priv->dev, 0, NULL, 0, locally_generated,
+ GFP_KERNEL);
+}
+
+void lbs_send_mic_failureevent(struct lbs_private *priv, u32 event)
+{
+ cfg80211_michael_mic_failure(priv->dev,
+ priv->assoc_bss,
+ event == MACREG_INT_CODE_MIC_ERR_MULTICAST ?
+ NL80211_KEYTYPE_GROUP :
+ NL80211_KEYTYPE_PAIRWISE,
+ -1,
+ NULL,
+ GFP_KERNEL);
+}
+
+
+
+
+/*
+ * Connect/disconnect
+ */
+
+
+/*
+ * This removes all WEP keys
+ */
+static int lbs_remove_wep_keys(struct lbs_private *priv)
+{
+ struct cmd_ds_802_11_set_wep cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.keyindex = cpu_to_le16(priv->wep_tx_key);
+ cmd.action = cpu_to_le16(CMD_ACT_REMOVE);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SET_WEP, &cmd);
+
+ return ret;
+}
+
+/*
+ * Set WEP keys
+ */
+static int lbs_set_wep_keys(struct lbs_private *priv)
+{
+ struct cmd_ds_802_11_set_wep cmd;
+ int i;
+ int ret;
+
+ /*
+ * command 13 00
+ * size 50 00
+ * sequence xx xx
+ * result 00 00
+ * action 02 00 ACT_ADD
+ * transmit key 00 00
+ * type for key 1 01 WEP40
+ * type for key 2 00
+ * type for key 3 00
+ * type for key 4 00
+ * key 1 39 39 39 39 39 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * key 2 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * key 3 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * key 4 00 00 00 00 00 00 00 00
+ */
+ if (priv->wep_key_len[0] || priv->wep_key_len[1] ||
+ priv->wep_key_len[2] || priv->wep_key_len[3]) {
+ /* Only set wep keys if we have at least one of them */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.keyindex = cpu_to_le16(priv->wep_tx_key);
+ cmd.action = cpu_to_le16(CMD_ACT_ADD);
+
+ for (i = 0; i < 4; i++) {
+ switch (priv->wep_key_len[i]) {
+ case WLAN_KEY_LEN_WEP40:
+ cmd.keytype[i] = CMD_TYPE_WEP_40_BIT;
+ break;
+ case WLAN_KEY_LEN_WEP104:
+ cmd.keytype[i] = CMD_TYPE_WEP_104_BIT;
+ break;
+ default:
+ cmd.keytype[i] = 0;
+ break;
+ }
+ memcpy(cmd.keymaterial[i], priv->wep_key[i],
+ priv->wep_key_len[i]);
+ }
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SET_WEP, &cmd);
+ } else {
+ /* Otherwise remove all wep keys */
+ ret = lbs_remove_wep_keys(priv);
+ }
+
+ return ret;
+}
+
+
+/*
+ * Enable/Disable RSN status
+ */
+static int lbs_enable_rsn(struct lbs_private *priv, int enable)
+{
+ struct cmd_ds_802_11_enable_rsn cmd;
+ int ret;
+
+ /*
+ * cmd 2f 00
+ * size 0c 00
+ * sequence xx xx
+ * result 00 00
+ * action 01 00 ACT_SET
+ * enable 01 00
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.enable = cpu_to_le16(enable);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_ENABLE_RSN, &cmd);
+
+ return ret;
+}
+
+
+/*
+ * Set WPA/WPA key material
+ */
+
+/*
+ * like "struct cmd_ds_802_11_key_material", but with cmd_header. Once we
+ * get rid of WEXT, this should go into host.h
+ */
+
+struct cmd_key_material {
+ struct cmd_header hdr;
+
+ __le16 action;
+ struct MrvlIEtype_keyParamSet param;
+} __packed;
+
+static int lbs_set_key_material(struct lbs_private *priv,
+ int key_type, int key_info,
+ const u8 *key, u16 key_len)
+{
+ struct cmd_key_material cmd;
+ int ret;
+
+ /*
+ * Example for WPA (TKIP):
+ *
+ * cmd 5e 00
+ * size 34 00
+ * sequence xx xx
+ * result 00 00
+ * action 01 00
+ * TLV type 00 01 key param
+ * length 00 26
+ * key type 01 00 TKIP
+ * key info 06 00 UNICAST | ENABLED
+ * key len 20 00
+ * key 32 bytes
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.param.type = cpu_to_le16(TLV_TYPE_KEY_MATERIAL);
+ cmd.param.length = cpu_to_le16(sizeof(cmd.param) - 4);
+ cmd.param.keytypeid = cpu_to_le16(key_type);
+ cmd.param.keyinfo = cpu_to_le16(key_info);
+ cmd.param.keylen = cpu_to_le16(key_len);
+ if (key && key_len)
+ memcpy(cmd.param.key, key, key_len);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_KEY_MATERIAL, &cmd);
+
+ return ret;
+}
+
+
+/*
+ * Sets the auth type (open, shared, etc) in the firmware. That
+ * we use CMD_802_11_AUTHENTICATE is misleading, this firmware
+ * command doesn't send an authentication frame at all, it just
+ * stores the auth_type.
+ */
+static int lbs_set_authtype(struct lbs_private *priv,
+ struct cfg80211_connect_params *sme)
+{
+ struct cmd_ds_802_11_authenticate cmd;
+ int ret;
+
+ /*
+ * cmd 11 00
+ * size 19 00
+ * sequence xx xx
+ * result 00 00
+ * BSS id 00 13 19 80 da 30
+ * auth type 00
+ * reserved 00 00 00 00 00 00 00 00 00 00
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ if (sme->bssid)
+ memcpy(cmd.bssid, sme->bssid, ETH_ALEN);
+ /* convert auth_type */
+ ret = lbs_auth_to_authtype(sme->auth_type);
+ if (ret < 0)
+ goto done;
+
+ cmd.authtype = ret;
+ ret = lbs_cmd_with_response(priv, CMD_802_11_AUTHENTICATE, &cmd);
+
+ done:
+ return ret;
+}
+
+
+/*
+ * Create association request
+ */
+#define LBS_ASSOC_MAX_CMD_SIZE \
+ (sizeof(struct cmd_ds_802_11_associate) \
+ - 512 /* cmd_ds_802_11_associate.iebuf */ \
+ + LBS_MAX_SSID_TLV_SIZE \
+ + LBS_MAX_CHANNEL_TLV_SIZE \
+ + LBS_MAX_CF_PARAM_TLV_SIZE \
+ + LBS_MAX_AUTH_TYPE_TLV_SIZE \
+ + LBS_MAX_WPA_TLV_SIZE)
+
+static int lbs_associate(struct lbs_private *priv,
+ struct cfg80211_bss *bss,
+ struct cfg80211_connect_params *sme)
+{
+ struct cmd_ds_802_11_associate_response *resp;
+ struct cmd_ds_802_11_associate *cmd = kzalloc(LBS_ASSOC_MAX_CMD_SIZE,
+ GFP_KERNEL);
+ const u8 *ssid_eid;
+ size_t len, resp_ie_len;
+ int status;
+ int ret;
+ u8 *pos;
+ u8 *tmp;
+
+ if (!cmd) {
+ ret = -ENOMEM;
+ goto done;
+ }
+ pos = &cmd->iebuf[0];
+
+ /*
+ * cmd 50 00
+ * length 34 00
+ * sequence xx xx
+ * result 00 00
+ * BSS id 00 13 19 80 da 30
+ * capabilities 11 00
+ * listen interval 0a 00
+ * beacon interval 00 00
+ * DTIM period 00
+ * TLVs xx (up to 512 bytes)
+ */
+ cmd->hdr.command = cpu_to_le16(CMD_802_11_ASSOCIATE);
+
+ /* Fill in static fields */
+ memcpy(cmd->bssid, bss->bssid, ETH_ALEN);
+ cmd->listeninterval = cpu_to_le16(MRVDRV_DEFAULT_LISTEN_INTERVAL);
+ cmd->capability = cpu_to_le16(bss->capability);
+
+ /* add SSID TLV */
+ rcu_read_lock();
+ ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
+ if (ssid_eid)
+ pos += lbs_add_ssid_tlv(pos, ssid_eid + 2, ssid_eid[1]);
+ else
+ lbs_deb_assoc("no SSID\n");
+ rcu_read_unlock();
+
+ /* add DS param TLV */
+ if (bss->channel)
+ pos += lbs_add_channel_tlv(pos, bss->channel->hw_value);
+ else
+ lbs_deb_assoc("no channel\n");
+
+ /* add (empty) CF param TLV */
+ pos += lbs_add_cf_param_tlv(pos);
+
+ /* add rates TLV */
+ tmp = pos + 4; /* skip Marvell IE header */
+ pos += lbs_add_common_rates_tlv(pos, bss);
+ lbs_deb_hex(LBS_DEB_ASSOC, "Common Rates", tmp, pos - tmp);
+
+ /* add auth type TLV */
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) >= 9)
+ pos += lbs_add_auth_type_tlv(pos, sme->auth_type);
+
+ /* add WPA/WPA2 TLV */
+ if (sme->ie && sme->ie_len)
+ pos += lbs_add_wpa_tlv(pos, sme->ie, sme->ie_len);
+
+ len = (sizeof(*cmd) - sizeof(cmd->iebuf)) +
+ (u16)(pos - (u8 *) &cmd->iebuf);
+ cmd->hdr.size = cpu_to_le16(len);
+
+ lbs_deb_hex(LBS_DEB_ASSOC, "ASSOC_CMD", (u8 *) cmd,
+ le16_to_cpu(cmd->hdr.size));
+
+ /* store for later use */
+ memcpy(priv->assoc_bss, bss->bssid, ETH_ALEN);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_ASSOCIATE, cmd);
+ if (ret)
+ goto done;
+
+ /* generate connect message to cfg80211 */
+
+ resp = (void *) cmd; /* recast for easier field access */
+ status = le16_to_cpu(resp->statuscode);
+
+ /* Older FW versions map the IEEE 802.11 Status Code in the association
+ * response to the following values returned in resp->statuscode:
+ *
+ * IEEE Status Code Marvell Status Code
+ * 0 -> 0x0000 ASSOC_RESULT_SUCCESS
+ * 13 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED
+ * 14 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED
+ * 15 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED
+ * 16 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED
+ * others -> 0x0003 ASSOC_RESULT_REFUSED
+ *
+ * Other response codes:
+ * 0x0001 -> ASSOC_RESULT_INVALID_PARAMETERS (unused)
+ * 0x0002 -> ASSOC_RESULT_TIMEOUT (internal timer expired waiting for
+ * association response from the AP)
+ */
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) {
+ switch (status) {
+ case 0:
+ break;
+ case 1:
+ lbs_deb_assoc("invalid association parameters\n");
+ status = WLAN_STATUS_CAPS_UNSUPPORTED;
+ break;
+ case 2:
+ lbs_deb_assoc("timer expired while waiting for AP\n");
+ status = WLAN_STATUS_AUTH_TIMEOUT;
+ break;
+ case 3:
+ lbs_deb_assoc("association refused by AP\n");
+ status = WLAN_STATUS_ASSOC_DENIED_UNSPEC;
+ break;
+ case 4:
+ lbs_deb_assoc("authentication refused by AP\n");
+ status = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
+ break;
+ default:
+ lbs_deb_assoc("association failure %d\n", status);
+ /* v5 OLPC firmware does return the AP status code if
+ * it's not one of the values above. Let that through.
+ */
+ break;
+ }
+ }
+
+ lbs_deb_assoc("status %d, statuscode 0x%04x, capability 0x%04x, "
+ "aid 0x%04x\n", status, le16_to_cpu(resp->statuscode),
+ le16_to_cpu(resp->capability), le16_to_cpu(resp->aid));
+
+ resp_ie_len = le16_to_cpu(resp->hdr.size)
+ - sizeof(resp->hdr)
+ - 6;
+ cfg80211_connect_result(priv->dev,
+ priv->assoc_bss,
+ sme->ie, sme->ie_len,
+ resp->iebuf, resp_ie_len,
+ status,
+ GFP_KERNEL);
+
+ if (status == 0) {
+ /* TODO: get rid of priv->connect_status */
+ priv->connect_status = LBS_CONNECTED;
+ netif_carrier_on(priv->dev);
+ if (!priv->tx_pending_len)
+ netif_tx_wake_all_queues(priv->dev);
+ }
+
+ kfree(cmd);
+done:
+ return ret;
+}
+
+static struct cfg80211_scan_request *
+_new_connect_scan_req(struct wiphy *wiphy, struct cfg80211_connect_params *sme)
+{
+ struct cfg80211_scan_request *creq = NULL;
+ int i, n_channels = ieee80211_get_num_supported_channels(wiphy);
+ enum nl80211_band band;
+
+ creq = kzalloc(sizeof(*creq) + sizeof(struct cfg80211_ssid) +
+ n_channels * sizeof(void *),
+ GFP_ATOMIC);
+ if (!creq)
+ return NULL;
+
+ /* SSIDs come after channels */
+ creq->ssids = (void *)&creq->channels[n_channels];
+ creq->n_channels = n_channels;
+ creq->n_ssids = 1;
+
+ /* Scan all available channels */
+ i = 0;
+ for (band = 0; band < NUM_NL80211_BANDS; band++) {
+ int j;
+
+ if (!wiphy->bands[band])
+ continue;
+
+ for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
+ /* ignore disabled channels */
+ if (wiphy->bands[band]->channels[j].flags &
+ IEEE80211_CHAN_DISABLED)
+ continue;
+
+ creq->channels[i] = &wiphy->bands[band]->channels[j];
+ i++;
+ }
+ }
+ if (i) {
+ /* Set real number of channels specified in creq->channels[] */
+ creq->n_channels = i;
+
+ /* Scan for the SSID we're going to connect to */
+ memcpy(creq->ssids[0].ssid, sme->ssid, sme->ssid_len);
+ creq->ssids[0].ssid_len = sme->ssid_len;
+ } else {
+ /* No channels found... */
+ kfree(creq);
+ creq = NULL;
+ }
+
+ return creq;
+}
+
+static int lbs_cfg_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ struct cfg80211_bss *bss = NULL;
+ int ret = 0;
+ u8 preamble = RADIO_PREAMBLE_SHORT;
+
+ if (dev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ if (!sme->bssid) {
+ struct cfg80211_scan_request *creq;
+
+ /*
+ * Scan for the requested network after waiting for existing
+ * scans to finish.
+ */
+ lbs_deb_assoc("assoc: waiting for existing scans\n");
+ wait_event_interruptible_timeout(priv->scan_q,
+ (priv->scan_req == NULL),
+ (15 * HZ));
+
+ creq = _new_connect_scan_req(wiphy, sme);
+ if (!creq) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ lbs_deb_assoc("assoc: scanning for compatible AP\n");
+ _internal_start_scan(priv, true, creq);
+
+ lbs_deb_assoc("assoc: waiting for scan to complete\n");
+ wait_event_interruptible_timeout(priv->scan_q,
+ (priv->scan_req == NULL),
+ (15 * HZ));
+ lbs_deb_assoc("assoc: scanning completed\n");
+ }
+
+ /* Find the BSS we want using available scan results */
+ bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid,
+ sme->ssid, sme->ssid_len, IEEE80211_BSS_TYPE_ESS,
+ IEEE80211_PRIVACY_ANY);
+ if (!bss) {
+ wiphy_err(wiphy, "assoc: bss %pM not in scan results\n",
+ sme->bssid);
+ ret = -ENOENT;
+ goto done;
+ }
+ lbs_deb_assoc("trying %pM\n", bss->bssid);
+ lbs_deb_assoc("cipher 0x%x, key index %d, key len %d\n",
+ sme->crypto.cipher_group,
+ sme->key_idx, sme->key_len);
+
+ /* As this is a new connection, clear locally stored WEP keys */
+ priv->wep_tx_key = 0;
+ memset(priv->wep_key, 0, sizeof(priv->wep_key));
+ memset(priv->wep_key_len, 0, sizeof(priv->wep_key_len));
+
+ /* set/remove WEP keys */
+ switch (sme->crypto.cipher_group) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ /* Store provided WEP keys in priv-> */
+ priv->wep_tx_key = sme->key_idx;
+ priv->wep_key_len[sme->key_idx] = sme->key_len;
+ memcpy(priv->wep_key[sme->key_idx], sme->key, sme->key_len);
+ /* Set WEP keys and WEP mode */
+ lbs_set_wep_keys(priv);
+ priv->mac_control |= CMD_ACT_MAC_WEP_ENABLE;
+ lbs_set_mac_control(priv);
+ /* No RSN mode for WEP */
+ lbs_enable_rsn(priv, 0);
+ break;
+ case 0: /* there's no WLAN_CIPHER_SUITE_NONE definition */
+ /*
+ * If we don't have no WEP, no WPA and no WPA2,
+ * we remove all keys like in the WPA/WPA2 setup,
+ * we just don't set RSN.
+ *
+ * Therefore: fall-through
+ */
+ case WLAN_CIPHER_SUITE_TKIP:
+ case WLAN_CIPHER_SUITE_CCMP:
+ /* Remove WEP keys and WEP mode */
+ lbs_remove_wep_keys(priv);
+ priv->mac_control &= ~CMD_ACT_MAC_WEP_ENABLE;
+ lbs_set_mac_control(priv);
+
+ /* clear the WPA/WPA2 keys */
+ lbs_set_key_material(priv,
+ KEY_TYPE_ID_WEP, /* doesn't matter */
+ KEY_INFO_WPA_UNICAST,
+ NULL, 0);
+ lbs_set_key_material(priv,
+ KEY_TYPE_ID_WEP, /* doesn't matter */
+ KEY_INFO_WPA_MCAST,
+ NULL, 0);
+ /* RSN mode for WPA/WPA2 */
+ lbs_enable_rsn(priv, sme->crypto.cipher_group != 0);
+ break;
+ default:
+ wiphy_err(wiphy, "unsupported cipher group 0x%x\n",
+ sme->crypto.cipher_group);
+ ret = -ENOTSUPP;
+ goto done;
+ }
+
+ ret = lbs_set_authtype(priv, sme);
+ if (ret == -ENOTSUPP) {
+ wiphy_err(wiphy, "unsupported authtype 0x%x\n", sme->auth_type);
+ goto done;
+ }
+
+ lbs_set_radio(priv, preamble, 1);
+
+ /* Do the actual association */
+ ret = lbs_associate(priv, bss, sme);
+
+ done:
+ if (bss)
+ cfg80211_put_bss(wiphy, bss);
+ return ret;
+}
+
+int lbs_disconnect(struct lbs_private *priv, u16 reason)
+{
+ struct cmd_ds_802_11_deauthenticate cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ /* Mildly ugly to use a locally store my own BSSID ... */
+ memcpy(cmd.macaddr, &priv->assoc_bss, ETH_ALEN);
+ cmd.reasoncode = cpu_to_le16(reason);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_DEAUTHENTICATE, &cmd);
+ if (ret)
+ return ret;
+
+ cfg80211_disconnected(priv->dev,
+ reason,
+ NULL, 0, true,
+ GFP_KERNEL);
+ priv->connect_status = LBS_DISCONNECTED;
+
+ return 0;
+}
+
+static int lbs_cfg_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+
+ if (dev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ /* store for lbs_cfg_ret_disconnect() */
+ priv->disassoc_reason = reason_code;
+
+ return lbs_disconnect(priv, reason_code);
+}
+
+static int lbs_cfg_set_default_key(struct wiphy *wiphy,
+ struct net_device *netdev,
+ u8 key_index, bool unicast,
+ bool multicast)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+
+ if (netdev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ if (key_index != priv->wep_tx_key) {
+ lbs_deb_assoc("set_default_key: to %d\n", key_index);
+ priv->wep_tx_key = key_index;
+ lbs_set_wep_keys(priv);
+ }
+
+ return 0;
+}
+
+
+static int lbs_cfg_add_key(struct wiphy *wiphy, struct net_device *netdev,
+ u8 idx, bool pairwise, const u8 *mac_addr,
+ struct key_params *params)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ u16 key_info;
+ u16 key_type;
+ int ret = 0;
+
+ if (netdev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ lbs_deb_assoc("add_key: cipher 0x%x, mac_addr %pM\n",
+ params->cipher, mac_addr);
+ lbs_deb_assoc("add_key: key index %d, key len %d\n",
+ idx, params->key_len);
+ if (params->key_len)
+ lbs_deb_hex(LBS_DEB_CFG80211, "KEY",
+ params->key, params->key_len);
+
+ lbs_deb_assoc("add_key: seq len %d\n", params->seq_len);
+ if (params->seq_len)
+ lbs_deb_hex(LBS_DEB_CFG80211, "SEQ",
+ params->seq, params->seq_len);
+
+ switch (params->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ /* actually compare if something has changed ... */
+ if ((priv->wep_key_len[idx] != params->key_len) ||
+ memcmp(priv->wep_key[idx],
+ params->key, params->key_len) != 0) {
+ priv->wep_key_len[idx] = params->key_len;
+ memcpy(priv->wep_key[idx],
+ params->key, params->key_len);
+ lbs_set_wep_keys(priv);
+ }
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ case WLAN_CIPHER_SUITE_CCMP:
+ key_info = KEY_INFO_WPA_ENABLED | ((idx == 0)
+ ? KEY_INFO_WPA_UNICAST
+ : KEY_INFO_WPA_MCAST);
+ key_type = (params->cipher == WLAN_CIPHER_SUITE_TKIP)
+ ? KEY_TYPE_ID_TKIP
+ : KEY_TYPE_ID_AES;
+ lbs_set_key_material(priv,
+ key_type,
+ key_info,
+ params->key, params->key_len);
+ break;
+ default:
+ wiphy_err(wiphy, "unhandled cipher 0x%x\n", params->cipher);
+ ret = -ENOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
+
+static int lbs_cfg_del_key(struct wiphy *wiphy, struct net_device *netdev,
+ u8 key_index, bool pairwise, const u8 *mac_addr)
+{
+
+ lbs_deb_assoc("del_key: key_idx %d, mac_addr %pM\n",
+ key_index, mac_addr);
+
+#ifdef TODO
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ /*
+ * I think can keep this a NO-OP, because:
+
+ * - we clear all keys whenever we do lbs_cfg_connect() anyway
+ * - neither "iw" nor "wpa_supplicant" won't call this during
+ * an ongoing connection
+ * - TODO: but I have to check if this is still true when
+ * I set the AP to periodic re-keying
+ * - we've not kzallec() something when we've added a key at
+ * lbs_cfg_connect() or lbs_cfg_add_key().
+ *
+ * This causes lbs_cfg_del_key() only called at disconnect time,
+ * where we'd just waste time deleting a key that is not going
+ * to be used anyway.
+ */
+ if (key_index < 3 && priv->wep_key_len[key_index]) {
+ priv->wep_key_len[key_index] = 0;
+ lbs_set_wep_keys(priv);
+ }
+#endif
+
+ return 0;
+}
+
+
+/*
+ * Get station
+ */
+
+static int lbs_cfg_get_station(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *mac, struct station_info *sinfo)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ s8 signal, noise;
+ int ret;
+ size_t i;
+
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES) |
+ BIT_ULL(NL80211_STA_INFO_TX_PACKETS) |
+ BIT_ULL(NL80211_STA_INFO_RX_BYTES) |
+ BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+ sinfo->tx_bytes = priv->dev->stats.tx_bytes;
+ sinfo->tx_packets = priv->dev->stats.tx_packets;
+ sinfo->rx_bytes = priv->dev->stats.rx_bytes;
+ sinfo->rx_packets = priv->dev->stats.rx_packets;
+
+ /* Get current RSSI */
+ ret = lbs_get_rssi(priv, &signal, &noise);
+ if (ret == 0) {
+ sinfo->signal = signal;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL);
+ }
+
+ /* Convert priv->cur_rate from hw_value to NL80211 value */
+ for (i = 0; i < ARRAY_SIZE(lbs_rates); i++) {
+ if (priv->cur_rate == lbs_rates[i].hw_value) {
+ sinfo->txrate.legacy = lbs_rates[i].bitrate;
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+
+
+/*
+ * Change interface
+ */
+
+static int lbs_change_intf(struct wiphy *wiphy, struct net_device *dev,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ int ret = 0;
+
+ if (dev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ switch (type) {
+ case NL80211_IFTYPE_MONITOR:
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_ADHOC:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (priv->iface_running)
+ ret = lbs_set_iface_type(priv, type);
+
+ if (!ret)
+ priv->wdev->iftype = type;
+
+ return ret;
+}
+
+
+
+/*
+ * IBSS (Ad-Hoc)
+ */
+
+/*
+ * The firmware needs the following bits masked out of the beacon-derived
+ * capability field when associating/joining to a BSS:
+ * 9 (QoS), 11 (APSD), 12 (unused), 14 (unused), 15 (unused)
+ */
+#define CAPINFO_MASK (~(0xda00))
+
+
+static void lbs_join_post(struct lbs_private *priv,
+ struct cfg80211_ibss_params *params,
+ u8 *bssid, u16 capability)
+{
+ u8 fake_ie[2 + IEEE80211_MAX_SSID_LEN + /* ssid */
+ 2 + 4 + /* basic rates */
+ 2 + 1 + /* DS parameter */
+ 2 + 2 + /* atim */
+ 2 + 8]; /* extended rates */
+ u8 *fake = fake_ie;
+ struct cfg80211_bss *bss;
+
+ /*
+ * For cfg80211_inform_bss, we'll need a fake IE, as we can't get
+ * the real IE from the firmware. So we fabricate a fake IE based on
+ * what the firmware actually sends (sniffed with wireshark).
+ */
+ /* Fake SSID IE */
+ *fake++ = WLAN_EID_SSID;
+ *fake++ = params->ssid_len;
+ memcpy(fake, params->ssid, params->ssid_len);
+ fake += params->ssid_len;
+ /* Fake supported basic rates IE */
+ *fake++ = WLAN_EID_SUPP_RATES;
+ *fake++ = 4;
+ *fake++ = 0x82;
+ *fake++ = 0x84;
+ *fake++ = 0x8b;
+ *fake++ = 0x96;
+ /* Fake DS channel IE */
+ *fake++ = WLAN_EID_DS_PARAMS;
+ *fake++ = 1;
+ *fake++ = params->chandef.chan->hw_value;
+ /* Fake IBSS params IE */
+ *fake++ = WLAN_EID_IBSS_PARAMS;
+ *fake++ = 2;
+ *fake++ = 0; /* ATIM=0 */
+ *fake++ = 0;
+ /* Fake extended rates IE, TODO: don't add this for 802.11b only,
+ * but I don't know how this could be checked */
+ *fake++ = WLAN_EID_EXT_SUPP_RATES;
+ *fake++ = 8;
+ *fake++ = 0x0c;
+ *fake++ = 0x12;
+ *fake++ = 0x18;
+ *fake++ = 0x24;
+ *fake++ = 0x30;
+ *fake++ = 0x48;
+ *fake++ = 0x60;
+ *fake++ = 0x6c;
+ lbs_deb_hex(LBS_DEB_CFG80211, "IE", fake_ie, fake - fake_ie);
+
+ bss = cfg80211_inform_bss(priv->wdev->wiphy,
+ params->chandef.chan,
+ CFG80211_BSS_FTYPE_UNKNOWN,
+ bssid,
+ 0,
+ capability,
+ params->beacon_interval,
+ fake_ie, fake - fake_ie,
+ 0, GFP_KERNEL);
+ cfg80211_put_bss(priv->wdev->wiphy, bss);
+
+ cfg80211_ibss_joined(priv->dev, bssid, params->chandef.chan,
+ GFP_KERNEL);
+
+ /* TODO: consider doing this at MACREG_INT_CODE_LINK_SENSED time */
+ priv->connect_status = LBS_CONNECTED;
+ netif_carrier_on(priv->dev);
+ if (!priv->tx_pending_len)
+ netif_wake_queue(priv->dev);
+}
+
+static int lbs_ibss_join_existing(struct lbs_private *priv,
+ struct cfg80211_ibss_params *params,
+ struct cfg80211_bss *bss)
+{
+ const u8 *rates_eid;
+ struct cmd_ds_802_11_ad_hoc_join cmd;
+ u8 preamble = RADIO_PREAMBLE_SHORT;
+ int ret = 0;
+ int hw, i;
+ u8 rates_max;
+ u8 *rates;
+
+ /* TODO: set preamble based on scan result */
+ ret = lbs_set_radio(priv, preamble, 1);
+ if (ret)
+ goto out;
+
+ /*
+ * Example CMD_802_11_AD_HOC_JOIN command:
+ *
+ * command 2c 00 CMD_802_11_AD_HOC_JOIN
+ * size 65 00
+ * sequence xx xx
+ * result 00 00
+ * bssid 02 27 27 97 2f 96
+ * ssid 49 42 53 53 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * type 02 CMD_BSS_TYPE_IBSS
+ * beacon period 64 00
+ * dtim period 00
+ * timestamp 00 00 00 00 00 00 00 00
+ * localtime 00 00 00 00 00 00 00 00
+ * IE DS 03
+ * IE DS len 01
+ * IE DS channel 01
+ * reserveed 00 00 00 00
+ * IE IBSS 06
+ * IE IBSS len 02
+ * IE IBSS atim 00 00
+ * reserved 00 00 00 00
+ * capability 02 00
+ * rates 82 84 8b 96 0c 12 18 24 30 48 60 6c 00
+ * fail timeout ff 00
+ * probe delay 00 00
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+
+ memcpy(cmd.bss.bssid, bss->bssid, ETH_ALEN);
+ memcpy(cmd.bss.ssid, params->ssid, params->ssid_len);
+ cmd.bss.type = CMD_BSS_TYPE_IBSS;
+ cmd.bss.beaconperiod = cpu_to_le16(params->beacon_interval);
+ cmd.bss.ds.header.id = WLAN_EID_DS_PARAMS;
+ cmd.bss.ds.header.len = 1;
+ cmd.bss.ds.channel = params->chandef.chan->hw_value;
+ cmd.bss.ibss.header.id = WLAN_EID_IBSS_PARAMS;
+ cmd.bss.ibss.header.len = 2;
+ cmd.bss.ibss.atimwindow = 0;
+ cmd.bss.capability = cpu_to_le16(bss->capability & CAPINFO_MASK);
+
+ /* set rates to the intersection of our rates and the rates in the
+ bss */
+ rcu_read_lock();
+ rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
+ if (!rates_eid) {
+ lbs_add_rates(cmd.bss.rates);
+ } else {
+ rates_max = rates_eid[1];
+ if (rates_max > MAX_RATES) {
+ lbs_deb_join("invalid rates");
+ rcu_read_unlock();
+ ret = -EINVAL;
+ goto out;
+ }
+ rates = cmd.bss.rates;
+ for (hw = 0; hw < ARRAY_SIZE(lbs_rates); hw++) {
+ u8 hw_rate = lbs_rates[hw].bitrate / 5;
+ for (i = 0; i < rates_max; i++) {
+ if (hw_rate == (rates_eid[i+2] & 0x7f)) {
+ u8 rate = rates_eid[i+2];
+ if (rate == 0x02 || rate == 0x04 ||
+ rate == 0x0b || rate == 0x16)
+ rate |= 0x80;
+ *rates++ = rate;
+ }
+ }
+ }
+ }
+ rcu_read_unlock();
+
+ /* Only v8 and below support setting this */
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) {
+ cmd.failtimeout = cpu_to_le16(MRVDRV_ASSOCIATION_TIME_OUT);
+ cmd.probedelay = cpu_to_le16(CMD_SCAN_PROBE_DELAY_TIME);
+ }
+ ret = lbs_cmd_with_response(priv, CMD_802_11_AD_HOC_JOIN, &cmd);
+ if (ret)
+ goto out;
+
+ /*
+ * This is a sample response to CMD_802_11_AD_HOC_JOIN:
+ *
+ * response 2c 80
+ * size 09 00
+ * sequence xx xx
+ * result 00 00
+ * reserved 00
+ */
+ lbs_join_post(priv, params, bss->bssid, bss->capability);
+
+ out:
+ return ret;
+}
+
+
+
+static int lbs_ibss_start_new(struct lbs_private *priv,
+ struct cfg80211_ibss_params *params)
+{
+ struct cmd_ds_802_11_ad_hoc_start cmd;
+ struct cmd_ds_802_11_ad_hoc_result *resp =
+ (struct cmd_ds_802_11_ad_hoc_result *) &cmd;
+ u8 preamble = RADIO_PREAMBLE_SHORT;
+ int ret = 0;
+ u16 capability;
+
+ ret = lbs_set_radio(priv, preamble, 1);
+ if (ret)
+ goto out;
+
+ /*
+ * Example CMD_802_11_AD_HOC_START command:
+ *
+ * command 2b 00 CMD_802_11_AD_HOC_START
+ * size b1 00
+ * sequence xx xx
+ * result 00 00
+ * ssid 54 45 53 54 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * 00 00 00 00 00 00 00 00
+ * bss type 02
+ * beacon period 64 00
+ * dtim period 00
+ * IE IBSS 06
+ * IE IBSS len 02
+ * IE IBSS atim 00 00
+ * reserved 00 00 00 00
+ * IE DS 03
+ * IE DS len 01
+ * IE DS channel 01
+ * reserved 00 00 00 00
+ * probe delay 00 00
+ * capability 02 00
+ * rates 82 84 8b 96 (basic rates with have bit 7 set)
+ * 0c 12 18 24 30 48 60 6c
+ * padding 100 bytes
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ memcpy(cmd.ssid, params->ssid, params->ssid_len);
+ cmd.bsstype = CMD_BSS_TYPE_IBSS;
+ cmd.beaconperiod = cpu_to_le16(params->beacon_interval);
+ cmd.ibss.header.id = WLAN_EID_IBSS_PARAMS;
+ cmd.ibss.header.len = 2;
+ cmd.ibss.atimwindow = 0;
+ cmd.ds.header.id = WLAN_EID_DS_PARAMS;
+ cmd.ds.header.len = 1;
+ cmd.ds.channel = params->chandef.chan->hw_value;
+ /* Only v8 and below support setting probe delay */
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8)
+ cmd.probedelay = cpu_to_le16(CMD_SCAN_PROBE_DELAY_TIME);
+ /* TODO: mix in WLAN_CAPABILITY_PRIVACY */
+ capability = WLAN_CAPABILITY_IBSS;
+ cmd.capability = cpu_to_le16(capability);
+ lbs_add_rates(cmd.rates);
+
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_AD_HOC_START, &cmd);
+ if (ret)
+ goto out;
+
+ /*
+ * This is a sample response to CMD_802_11_AD_HOC_JOIN:
+ *
+ * response 2b 80
+ * size 14 00
+ * sequence xx xx
+ * result 00 00
+ * reserved 00
+ * bssid 02 2b 7b 0f 86 0e
+ */
+ lbs_join_post(priv, params, resp->bssid, capability);
+
+ out:
+ return ret;
+}
+
+
+static int lbs_join_ibss(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_ibss_params *params)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ int ret = 0;
+ struct cfg80211_bss *bss;
+
+ if (dev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ if (!params->chandef.chan) {
+ ret = -ENOTSUPP;
+ goto out;
+ }
+
+ ret = lbs_set_channel(priv, params->chandef.chan->hw_value);
+ if (ret)
+ goto out;
+
+ /* Search if someone is beaconing. This assumes that the
+ * bss list is populated already */
+ bss = cfg80211_get_bss(wiphy, params->chandef.chan, params->bssid,
+ params->ssid, params->ssid_len,
+ IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY);
+
+ if (bss) {
+ ret = lbs_ibss_join_existing(priv, params, bss);
+ cfg80211_put_bss(wiphy, bss);
+ } else
+ ret = lbs_ibss_start_new(priv, params);
+
+
+ out:
+ return ret;
+}
+
+
+static int lbs_leave_ibss(struct wiphy *wiphy, struct net_device *dev)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+ struct cmd_ds_802_11_ad_hoc_stop cmd;
+ int ret = 0;
+
+ if (dev == priv->mesh_dev)
+ return -EOPNOTSUPP;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ ret = lbs_cmd_with_response(priv, CMD_802_11_AD_HOC_STOP, &cmd);
+
+ /* TODO: consider doing this at MACREG_INT_CODE_ADHOC_BCN_LOST time */
+ lbs_mac_event_disconnected(priv, true);
+
+ return ret;
+}
+
+
+
+static int lbs_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
+ bool enabled, int timeout)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+
+ if (!(priv->fwcapinfo & FW_CAPINFO_PS)) {
+ if (!enabled)
+ return 0;
+ else
+ return -EINVAL;
+ }
+ /* firmware does not work well with too long latency with power saving
+ * enabled, so do not enable it if there is only polling, no
+ * interrupts (like in some sdio hosts which can only
+ * poll for sdio irqs)
+ */
+ if (priv->is_polling) {
+ if (!enabled)
+ return 0;
+ else
+ return -EINVAL;
+ }
+ if (!enabled) {
+ priv->psmode = LBS802_11POWERMODECAM;
+ if (priv->psstate != PS_STATE_FULL_POWER)
+ lbs_set_ps_mode(priv,
+ PS_MODE_ACTION_EXIT_PS,
+ true);
+ return 0;
+ }
+ if (priv->psmode != LBS802_11POWERMODECAM)
+ return 0;
+ priv->psmode = LBS802_11POWERMODEMAX_PSP;
+ if (priv->connect_status == LBS_CONNECTED)
+ lbs_set_ps_mode(priv, PS_MODE_ACTION_ENTER_PS, true);
+ return 0;
+}
+
+/*
+ * Initialization
+ */
+
+static const struct cfg80211_ops lbs_cfg80211_ops = {
+ .set_monitor_channel = lbs_cfg_set_monitor_channel,
+ .libertas_set_mesh_channel = lbs_cfg_set_mesh_channel,
+ .scan = lbs_cfg_scan,
+ .connect = lbs_cfg_connect,
+ .disconnect = lbs_cfg_disconnect,
+ .add_key = lbs_cfg_add_key,
+ .del_key = lbs_cfg_del_key,
+ .set_default_key = lbs_cfg_set_default_key,
+ .get_station = lbs_cfg_get_station,
+ .change_virtual_intf = lbs_change_intf,
+ .join_ibss = lbs_join_ibss,
+ .leave_ibss = lbs_leave_ibss,
+ .set_power_mgmt = lbs_set_power_mgmt,
+};
+
+
+/*
+ * At this time lbs_private *priv doesn't even exist, so we just allocate
+ * memory and don't initialize the wiphy further. This is postponed until we
+ * can talk to the firmware and happens at registration time in
+ * lbs_cfg_wiphy_register().
+ */
+struct wireless_dev *lbs_cfg_alloc(struct device *dev)
+{
+ int ret = 0;
+ struct wireless_dev *wdev;
+
+ wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
+ if (!wdev)
+ return ERR_PTR(-ENOMEM);
+
+ wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private));
+ if (!wdev->wiphy) {
+ dev_err(dev, "cannot allocate wiphy\n");
+ ret = -ENOMEM;
+ goto err_wiphy_new;
+ }
+
+ return wdev;
+
+ err_wiphy_new:
+ kfree(wdev);
+ return ERR_PTR(ret);
+}
+
+
+static void lbs_cfg_set_regulatory_hint(struct lbs_private *priv)
+{
+ struct region_code_mapping {
+ const char *cn;
+ int code;
+ };
+
+ /* Section 5.17.2 */
+ static const struct region_code_mapping regmap[] = {
+ {"US ", 0x10}, /* US FCC */
+ {"CA ", 0x20}, /* Canada */
+ {"EU ", 0x30}, /* ETSI */
+ {"ES ", 0x31}, /* Spain */
+ {"FR ", 0x32}, /* France */
+ {"JP ", 0x40}, /* Japan */
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(regmap); i++)
+ if (regmap[i].code == priv->regioncode) {
+ regulatory_hint(priv->wdev->wiphy, regmap[i].cn);
+ break;
+ }
+}
+
+static void lbs_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct lbs_private *priv = wiphy_priv(wiphy);
+
+ memcpy(priv->country_code, request->alpha2, sizeof(request->alpha2));
+ if (lbs_iface_active(priv))
+ lbs_set_11d_domain_info(priv);
+}
+
+/*
+ * This function get's called after lbs_setup_firmware() determined the
+ * firmware capabities. So we can setup the wiphy according to our
+ * hardware/firmware.
+ */
+int lbs_cfg_register(struct lbs_private *priv)
+{
+ struct wireless_dev *wdev = priv->wdev;
+ int ret;
+
+ wdev->wiphy->max_scan_ssids = 1;
+ wdev->wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+
+ wdev->wiphy->interface_modes =
+ BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_ADHOC);
+ if (lbs_rtap_supported(priv))
+ wdev->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
+ if (lbs_mesh_activated(priv))
+ wdev->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MESH_POINT);
+
+ wdev->wiphy->bands[NL80211_BAND_2GHZ] = &lbs_band_2ghz;
+
+ /*
+ * We could check priv->fwcapinfo && FW_CAPINFO_WPA, but I have
+ * never seen a firmware without WPA
+ */
+ wdev->wiphy->cipher_suites = cipher_suites;
+ wdev->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
+ wdev->wiphy->reg_notifier = lbs_reg_notifier;
+
+ ret = wiphy_register(wdev->wiphy);
+ if (ret < 0)
+ pr_err("cannot register wiphy device\n");
+
+ priv->wiphy_registered = true;
+
+ ret = register_netdev(priv->dev);
+ if (ret)
+ pr_err("cannot register network device\n");
+
+ INIT_DELAYED_WORK(&priv->scan_work, lbs_scan_worker);
+
+ lbs_cfg_set_regulatory_hint(priv);
+
+ return ret;
+}
+
+void lbs_scan_deinit(struct lbs_private *priv)
+{
+ cancel_delayed_work_sync(&priv->scan_work);
+}
+
+
+void lbs_cfg_free(struct lbs_private *priv)
+{
+ struct wireless_dev *wdev = priv->wdev;
+
+ if (!wdev)
+ return;
+
+ if (priv->wiphy_registered)
+ wiphy_unregister(wdev->wiphy);
+
+ if (wdev->wiphy)
+ wiphy_free(wdev->wiphy);
+
+ kfree(wdev);
+}
diff --git a/drivers/net/wireless/marvell/libertas/cfg.h b/drivers/net/wireless/marvell/libertas/cfg.h
new file mode 100644
index 000000000..0e48dc6d8
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/cfg.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LBS_CFG80211_H__
+#define __LBS_CFG80211_H__
+
+struct device;
+struct lbs_private;
+struct regulatory_request;
+struct wiphy;
+
+struct wireless_dev *lbs_cfg_alloc(struct device *dev);
+int lbs_cfg_register(struct lbs_private *priv);
+void lbs_cfg_free(struct lbs_private *priv);
+
+void lbs_send_disconnect_notification(struct lbs_private *priv,
+ bool locally_generated);
+void lbs_send_mic_failureevent(struct lbs_private *priv, u32 event);
+
+void lbs_scan_done(struct lbs_private *priv);
+void lbs_scan_deinit(struct lbs_private *priv);
+int lbs_disconnect(struct lbs_private *priv, u16 reason);
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/cmd.c b/drivers/net/wireless/marvell/libertas/cmd.c
new file mode 100644
index 000000000..a4d9dd73b
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/cmd.c
@@ -0,0 +1,1598 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains the handling of command.
+ * It prepares command and sends it to firmware when it is ready.
+ */
+
+#include <linux/hardirq.h>
+#include <linux/kfifo.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/if_arp.h>
+#include <linux/export.h>
+
+#include "decl.h"
+#include "cfg.h"
+#include "cmd.h"
+
+#define CAL_NF(nf) ((s32)(-(s32)(nf)))
+#define CAL_RSSI(snr, nf) ((s32)((s32)(snr) + CAL_NF(nf)))
+
+/**
+ * lbs_cmd_copyback - Simple callback that copies response back into command
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @extra: A pointer to the original command structure for which
+ * 'resp' is a response
+ * @resp: A pointer to the command response
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra,
+ struct cmd_header *resp)
+{
+ struct cmd_header *buf = (void *)extra;
+ uint16_t copy_len;
+
+ copy_len = min(le16_to_cpu(buf->size), le16_to_cpu(resp->size));
+ memcpy(buf, resp, copy_len);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lbs_cmd_copyback);
+
+/**
+ * lbs_cmd_async_callback - Simple callback that ignores the result.
+ * Use this if you just want to send a command to the hardware, but don't
+ * care for the result.
+ *
+ * @priv: ignored
+ * @extra: ignored
+ * @resp: ignored
+ *
+ * returns: 0 for success
+ */
+static int lbs_cmd_async_callback(struct lbs_private *priv, unsigned long extra,
+ struct cmd_header *resp)
+{
+ return 0;
+}
+
+
+/**
+ * is_command_allowed_in_ps - tests if a command is allowed in Power Save mode
+ *
+ * @cmd: the command ID
+ *
+ * returns: 1 if allowed, 0 if not allowed
+ */
+static u8 is_command_allowed_in_ps(u16 cmd)
+{
+ switch (cmd) {
+ case CMD_802_11_RSSI:
+ return 1;
+ case CMD_802_11_HOST_SLEEP_CFG:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/**
+ * lbs_update_hw_spec - Updates the hardware details like MAC address
+ * and regulatory region
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_update_hw_spec(struct lbs_private *priv)
+{
+ struct cmd_ds_get_hw_spec cmd;
+ int ret = -1;
+ u32 i;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ memcpy(cmd.permanentaddr, priv->current_addr, ETH_ALEN);
+ ret = lbs_cmd_with_response(priv, CMD_GET_HW_SPEC, &cmd);
+ if (ret)
+ goto out;
+
+ priv->fwcapinfo = le32_to_cpu(cmd.fwcapinfo);
+
+ /* The firmware release is in an interesting format: the patch
+ * level is in the most significant nibble ... so fix that: */
+ priv->fwrelease = le32_to_cpu(cmd.fwrelease);
+ priv->fwrelease = (priv->fwrelease << 8) |
+ (priv->fwrelease >> 24 & 0xff);
+
+ /* Some firmware capabilities:
+ * CF card firmware 5.0.16p0: cap 0x00000303
+ * USB dongle firmware 5.110.17p2: cap 0x00000303
+ */
+ netdev_info(priv->dev, "%pM, fw %u.%u.%up%u, cap 0x%08x\n",
+ cmd.permanentaddr,
+ priv->fwrelease >> 24 & 0xff,
+ priv->fwrelease >> 16 & 0xff,
+ priv->fwrelease >> 8 & 0xff,
+ priv->fwrelease & 0xff,
+ priv->fwcapinfo);
+ lbs_deb_cmd("GET_HW_SPEC: hardware interface 0x%x, hardware spec 0x%04x\n",
+ cmd.hwifversion, cmd.version);
+
+ /* Clamp region code to 8-bit since FW spec indicates that it should
+ * only ever be 8-bit, even though the field size is 16-bit. Some firmware
+ * returns non-zero high 8 bits here.
+ *
+ * Firmware version 4.0.102 used in CF8381 has region code shifted. We
+ * need to check for this problem and handle it properly.
+ */
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V4)
+ priv->regioncode = (le16_to_cpu(cmd.regioncode) >> 8) & 0xFF;
+ else
+ priv->regioncode = le16_to_cpu(cmd.regioncode) & 0xFF;
+
+ for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) {
+ /* use the region code to search for the index */
+ if (priv->regioncode == lbs_region_code_to_index[i])
+ break;
+ }
+
+ /* if it's unidentified region code, use the default (USA) */
+ if (i >= MRVDRV_MAX_REGION_CODE) {
+ priv->regioncode = 0x10;
+ netdev_info(priv->dev,
+ "unidentified region code; using the default (USA)\n");
+ }
+
+ if (priv->current_addr[0] == 0xff)
+ memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN);
+
+ if (!priv->copied_hwaddr) {
+ memcpy(priv->dev->dev_addr, priv->current_addr, ETH_ALEN);
+ if (priv->mesh_dev)
+ memcpy(priv->mesh_dev->dev_addr,
+ priv->current_addr, ETH_ALEN);
+ priv->copied_hwaddr = 1;
+ }
+
+out:
+ return ret;
+}
+
+static int lbs_ret_host_sleep_cfg(struct lbs_private *priv, unsigned long dummy,
+ struct cmd_header *resp)
+{
+ if (priv->is_host_sleep_activated) {
+ priv->is_host_sleep_configured = 0;
+ if (priv->psstate == PS_STATE_FULL_POWER) {
+ priv->is_host_sleep_activated = 0;
+ wake_up_interruptible(&priv->host_sleep_q);
+ }
+ } else {
+ priv->is_host_sleep_configured = 1;
+ }
+
+ return 0;
+}
+
+int lbs_host_sleep_cfg(struct lbs_private *priv, uint32_t criteria,
+ struct wol_config *p_wol_config)
+{
+ struct cmd_ds_host_sleep cmd_config;
+ int ret;
+
+ /*
+ * Certain firmware versions do not support EHS_REMOVE_WAKEUP command
+ * and the card will return a failure. Since we need to be
+ * able to reset the mask, in those cases we set a 0 mask instead.
+ */
+ if (criteria == EHS_REMOVE_WAKEUP && !priv->ehs_remove_supported)
+ criteria = 0;
+
+ cmd_config.hdr.size = cpu_to_le16(sizeof(cmd_config));
+ cmd_config.criteria = cpu_to_le32(criteria);
+ cmd_config.gpio = priv->wol_gpio;
+ cmd_config.gap = priv->wol_gap;
+
+ if (p_wol_config != NULL)
+ memcpy((uint8_t *)&cmd_config.wol_conf, (uint8_t *)p_wol_config,
+ sizeof(struct wol_config));
+ else
+ cmd_config.wol_conf.action = CMD_ACT_ACTION_NONE;
+
+ ret = __lbs_cmd(priv, CMD_802_11_HOST_SLEEP_CFG, &cmd_config.hdr,
+ le16_to_cpu(cmd_config.hdr.size),
+ lbs_ret_host_sleep_cfg, 0);
+ if (!ret) {
+ if (p_wol_config)
+ memcpy((uint8_t *) p_wol_config,
+ (uint8_t *)&cmd_config.wol_conf,
+ sizeof(struct wol_config));
+ } else {
+ netdev_info(priv->dev, "HOST_SLEEP_CFG failed %d\n", ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lbs_host_sleep_cfg);
+
+/**
+ * lbs_set_ps_mode - Sets the Power Save mode
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @cmd_action: The Power Save operation (PS_MODE_ACTION_ENTER_PS or
+ * PS_MODE_ACTION_EXIT_PS)
+ * @block: Whether to block on a response or not
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_set_ps_mode(struct lbs_private *priv, u16 cmd_action, bool block)
+{
+ struct cmd_ds_802_11_ps_mode cmd;
+ int ret = 0;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(cmd_action);
+
+ if (cmd_action == PS_MODE_ACTION_ENTER_PS) {
+ lbs_deb_cmd("PS_MODE: action ENTER_PS\n");
+ cmd.multipledtim = cpu_to_le16(1); /* Default DTIM multiple */
+ } else if (cmd_action == PS_MODE_ACTION_EXIT_PS) {
+ lbs_deb_cmd("PS_MODE: action EXIT_PS\n");
+ } else {
+ /* We don't handle CONFIRM_SLEEP here because it needs to
+ * be fastpathed to the firmware.
+ */
+ lbs_deb_cmd("PS_MODE: unknown action 0x%X\n", cmd_action);
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (block)
+ ret = lbs_cmd_with_response(priv, CMD_802_11_PS_MODE, &cmd);
+ else
+ lbs_cmd_async(priv, CMD_802_11_PS_MODE, &cmd.hdr, sizeof (cmd));
+
+out:
+ return ret;
+}
+
+int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, uint16_t cmd_action,
+ struct sleep_params *sp)
+{
+ struct cmd_ds_802_11_sleep_params cmd;
+ int ret;
+
+ if (cmd_action == CMD_ACT_GET) {
+ memset(&cmd, 0, sizeof(cmd));
+ } else {
+ cmd.error = cpu_to_le16(sp->sp_error);
+ cmd.offset = cpu_to_le16(sp->sp_offset);
+ cmd.stabletime = cpu_to_le16(sp->sp_stabletime);
+ cmd.calcontrol = sp->sp_calcontrol;
+ cmd.externalsleepclk = sp->sp_extsleepclk;
+ cmd.reserved = cpu_to_le16(sp->sp_reserved);
+ }
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(cmd_action);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SLEEP_PARAMS, &cmd);
+
+ if (!ret) {
+ lbs_deb_cmd("error 0x%x, offset 0x%x, stabletime 0x%x, "
+ "calcontrol 0x%x extsleepclk 0x%x\n",
+ le16_to_cpu(cmd.error), le16_to_cpu(cmd.offset),
+ le16_to_cpu(cmd.stabletime), cmd.calcontrol,
+ cmd.externalsleepclk);
+
+ sp->sp_error = le16_to_cpu(cmd.error);
+ sp->sp_offset = le16_to_cpu(cmd.offset);
+ sp->sp_stabletime = le16_to_cpu(cmd.stabletime);
+ sp->sp_calcontrol = cmd.calcontrol;
+ sp->sp_extsleepclk = cmd.externalsleepclk;
+ sp->sp_reserved = le16_to_cpu(cmd.reserved);
+ }
+
+ return ret;
+}
+
+static int lbs_wait_for_ds_awake(struct lbs_private *priv)
+{
+ int ret = 0;
+
+ if (priv->is_deep_sleep) {
+ if (!wait_event_interruptible_timeout(priv->ds_awake_q,
+ !priv->is_deep_sleep, (10 * HZ))) {
+ netdev_err(priv->dev, "ds_awake_q: timer expired\n");
+ ret = -1;
+ }
+ }
+
+ return ret;
+}
+
+int lbs_set_deep_sleep(struct lbs_private *priv, int deep_sleep)
+{
+ int ret = 0;
+
+ if (deep_sleep) {
+ if (priv->is_deep_sleep != 1) {
+ lbs_deb_cmd("deep sleep: sleep\n");
+ BUG_ON(!priv->enter_deep_sleep);
+ ret = priv->enter_deep_sleep(priv);
+ if (!ret) {
+ netif_stop_queue(priv->dev);
+ netif_carrier_off(priv->dev);
+ }
+ } else {
+ netdev_err(priv->dev, "deep sleep: already enabled\n");
+ }
+ } else {
+ if (priv->is_deep_sleep) {
+ lbs_deb_cmd("deep sleep: wakeup\n");
+ BUG_ON(!priv->exit_deep_sleep);
+ ret = priv->exit_deep_sleep(priv);
+ if (!ret) {
+ ret = lbs_wait_for_ds_awake(priv);
+ if (ret)
+ netdev_err(priv->dev,
+ "deep sleep: wakeup failed\n");
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int lbs_ret_host_sleep_activate(struct lbs_private *priv,
+ unsigned long dummy,
+ struct cmd_header *cmd)
+{
+ priv->is_host_sleep_activated = 1;
+ wake_up_interruptible(&priv->host_sleep_q);
+
+ return 0;
+}
+
+int lbs_set_host_sleep(struct lbs_private *priv, int host_sleep)
+{
+ struct cmd_header cmd;
+ int ret = 0;
+ uint32_t criteria = EHS_REMOVE_WAKEUP;
+
+ if (host_sleep) {
+ if (priv->is_host_sleep_activated != 1) {
+ memset(&cmd, 0, sizeof(cmd));
+ ret = lbs_host_sleep_cfg(priv, priv->wol_criteria,
+ (struct wol_config *)NULL);
+ if (ret) {
+ netdev_info(priv->dev,
+ "Host sleep configuration failed: %d\n",
+ ret);
+ return ret;
+ }
+ if (priv->psstate == PS_STATE_FULL_POWER) {
+ ret = __lbs_cmd(priv,
+ CMD_802_11_HOST_SLEEP_ACTIVATE,
+ &cmd,
+ sizeof(cmd),
+ lbs_ret_host_sleep_activate, 0);
+ if (ret)
+ netdev_info(priv->dev,
+ "HOST_SLEEP_ACTIVATE failed: %d\n",
+ ret);
+ }
+
+ if (!wait_event_interruptible_timeout(
+ priv->host_sleep_q,
+ priv->is_host_sleep_activated,
+ (10 * HZ))) {
+ netdev_err(priv->dev,
+ "host_sleep_q: timer expired\n");
+ ret = -1;
+ }
+ } else {
+ netdev_err(priv->dev, "host sleep: already enabled\n");
+ }
+ } else {
+ if (priv->is_host_sleep_activated)
+ ret = lbs_host_sleep_cfg(priv, criteria,
+ (struct wol_config *)NULL);
+ }
+
+ return ret;
+}
+
+/**
+ * lbs_set_snmp_mib - Set an SNMP MIB value
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @oid: The OID to set in the firmware
+ * @val: Value to set the OID to
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_set_snmp_mib(struct lbs_private *priv, u32 oid, u16 val)
+{
+ struct cmd_ds_802_11_snmp_mib cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof (cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.oid = cpu_to_le16((u16) oid);
+
+ switch (oid) {
+ case SNMP_MIB_OID_BSS_TYPE:
+ cmd.bufsize = cpu_to_le16(sizeof(u8));
+ cmd.value[0] = val;
+ break;
+ case SNMP_MIB_OID_11D_ENABLE:
+ case SNMP_MIB_OID_FRAG_THRESHOLD:
+ case SNMP_MIB_OID_RTS_THRESHOLD:
+ case SNMP_MIB_OID_SHORT_RETRY_LIMIT:
+ case SNMP_MIB_OID_LONG_RETRY_LIMIT:
+ cmd.bufsize = cpu_to_le16(sizeof(u16));
+ *((__le16 *)(&cmd.value)) = cpu_to_le16(val);
+ break;
+ default:
+ lbs_deb_cmd("SNMP_CMD: (set) unhandled OID 0x%x\n", oid);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ lbs_deb_cmd("SNMP_CMD: (set) oid 0x%x, oid size 0x%x, value 0x%x\n",
+ le16_to_cpu(cmd.oid), le16_to_cpu(cmd.bufsize), val);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd);
+
+out:
+ return ret;
+}
+
+/**
+ * lbs_get_snmp_mib - Get an SNMP MIB value
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @oid: The OID to retrieve from the firmware
+ * @out_val: Location for the returned value
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_get_snmp_mib(struct lbs_private *priv, u32 oid, u16 *out_val)
+{
+ struct cmd_ds_802_11_snmp_mib cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof (cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_GET);
+ cmd.oid = cpu_to_le16(oid);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd);
+ if (ret)
+ goto out;
+
+ switch (le16_to_cpu(cmd.bufsize)) {
+ case sizeof(u8):
+ *out_val = cmd.value[0];
+ break;
+ case sizeof(u16):
+ *out_val = le16_to_cpu(*((__le16 *)(&cmd.value)));
+ break;
+ default:
+ lbs_deb_cmd("SNMP_CMD: (get) unhandled OID 0x%x size %d\n",
+ oid, le16_to_cpu(cmd.bufsize));
+ break;
+ }
+
+out:
+ return ret;
+}
+
+/**
+ * lbs_get_tx_power - Get the min, max, and current TX power
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @curlevel: Current power level in dBm
+ * @minlevel: Minimum supported power level in dBm (optional)
+ * @maxlevel: Maximum supported power level in dBm (optional)
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_get_tx_power(struct lbs_private *priv, s16 *curlevel, s16 *minlevel,
+ s16 *maxlevel)
+{
+ struct cmd_ds_802_11_rf_tx_power cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_GET);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd);
+ if (ret == 0) {
+ *curlevel = le16_to_cpu(cmd.curlevel);
+ if (minlevel)
+ *minlevel = cmd.minlevel;
+ if (maxlevel)
+ *maxlevel = cmd.maxlevel;
+ }
+
+ return ret;
+}
+
+/**
+ * lbs_set_tx_power - Set the TX power
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @dbm: The desired power level in dBm
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_set_tx_power(struct lbs_private *priv, s16 dbm)
+{
+ struct cmd_ds_802_11_rf_tx_power cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.curlevel = cpu_to_le16(dbm);
+
+ lbs_deb_cmd("SET_RF_TX_POWER: %d dBm\n", dbm);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd);
+
+ return ret;
+}
+
+/**
+ * lbs_set_monitor_mode - Enable or disable monitor mode
+ * (only implemented on OLPC usb8388 FW)
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @enable: 1 to enable monitor mode, 0 to disable
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_set_monitor_mode(struct lbs_private *priv, int enable)
+{
+ struct cmd_ds_802_11_monitor_mode cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ if (enable)
+ cmd.mode = cpu_to_le16(0x1);
+
+ lbs_deb_cmd("SET_MONITOR_MODE: %d\n", enable);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_MONITOR_MODE, &cmd);
+ if (ret == 0) {
+ priv->dev->type = enable ? ARPHRD_IEEE80211_RADIOTAP :
+ ARPHRD_ETHER;
+ }
+
+ return ret;
+}
+
+/**
+ * lbs_get_channel - Get the radio channel
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: The channel on success, error on failure
+ */
+static int lbs_get_channel(struct lbs_private *priv)
+{
+ struct cmd_ds_802_11_rf_channel cmd;
+ int ret = 0;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_GET);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd);
+ if (ret)
+ goto out;
+
+ ret = le16_to_cpu(cmd.channel);
+ lbs_deb_cmd("current radio channel is %d\n", ret);
+
+out:
+ return ret;
+}
+
+int lbs_update_channel(struct lbs_private *priv)
+{
+ int ret;
+
+ /* the channel in f/w could be out of sync; get the current channel */
+ ret = lbs_get_channel(priv);
+ if (ret > 0) {
+ priv->channel = ret;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * lbs_set_channel - Set the radio channel
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @channel: The desired channel, or 0 to clear a locked channel
+ *
+ * returns: 0 on success, error on failure
+ */
+int lbs_set_channel(struct lbs_private *priv, u8 channel)
+{
+ struct cmd_ds_802_11_rf_channel cmd;
+#ifdef DEBUG
+ u8 old_channel = priv->channel;
+#endif
+ int ret = 0;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_SET);
+ cmd.channel = cpu_to_le16(channel);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd);
+ if (ret)
+ goto out;
+
+ priv->channel = (uint8_t) le16_to_cpu(cmd.channel);
+ lbs_deb_cmd("channel switch from %d to %d\n", old_channel,
+ priv->channel);
+
+out:
+ return ret;
+}
+
+/**
+ * lbs_get_rssi - Get current RSSI and noise floor
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @rssi: On successful return, signal level in mBm
+ * @nf: On successful return, Noise floor
+ *
+ * returns: The channel on success, error on failure
+ */
+int lbs_get_rssi(struct lbs_private *priv, s8 *rssi, s8 *nf)
+{
+ struct cmd_ds_802_11_rssi cmd;
+ int ret = 0;
+
+ BUG_ON(rssi == NULL);
+ BUG_ON(nf == NULL);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ /* Average SNR over last 8 beacons */
+ cmd.n_or_snr = cpu_to_le16(8);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_RSSI, &cmd);
+ if (ret == 0) {
+ *nf = CAL_NF(le16_to_cpu(cmd.nf));
+ *rssi = CAL_RSSI(le16_to_cpu(cmd.n_or_snr), le16_to_cpu(cmd.nf));
+ }
+
+ return ret;
+}
+
+/**
+ * lbs_set_11d_domain_info - Send regulatory and 802.11d domain information
+ * to the firmware
+ *
+ * @priv: pointer to &struct lbs_private
+ *
+ * returns: 0 on success, error code on failure
+*/
+int lbs_set_11d_domain_info(struct lbs_private *priv)
+{
+ struct wiphy *wiphy = priv->wdev->wiphy;
+ struct ieee80211_supported_band **bands = wiphy->bands;
+ struct cmd_ds_802_11d_domain_info cmd;
+ struct mrvl_ie_domain_param_set *domain = &cmd.domain;
+ struct ieee80211_country_ie_triplet *t;
+ enum nl80211_band band;
+ struct ieee80211_channel *ch;
+ u8 num_triplet = 0;
+ u8 num_parsed_chan = 0;
+ u8 first_channel = 0, next_chan = 0, max_pwr = 0;
+ u8 i, flag = 0;
+ size_t triplet_size;
+ int ret = 0;
+
+ if (!priv->country_code[0])
+ goto out;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+
+ lbs_deb_11d("Setting country code '%c%c'\n",
+ priv->country_code[0], priv->country_code[1]);
+
+ domain->header.type = cpu_to_le16(TLV_TYPE_DOMAIN);
+
+ /* Set country code */
+ domain->country_code[0] = priv->country_code[0];
+ domain->country_code[1] = priv->country_code[1];
+ domain->country_code[2] = ' ';
+
+ /* Now set up the channel triplets; firmware is somewhat picky here
+ * and doesn't validate channel numbers and spans; hence it would
+ * interpret a triplet of (36, 4, 20) as channels 36, 37, 38, 39. Since
+ * the last 3 aren't valid channels, the driver is responsible for
+ * splitting that up into 4 triplet pairs of (36, 1, 20) + (40, 1, 20)
+ * etc.
+ */
+ for (band = 0;
+ (band < NUM_NL80211_BANDS) && (num_triplet < MAX_11D_TRIPLETS);
+ band++) {
+
+ if (!bands[band])
+ continue;
+
+ for (i = 0;
+ (i < bands[band]->n_channels) && (num_triplet < MAX_11D_TRIPLETS);
+ i++) {
+ ch = &bands[band]->channels[i];
+ if (ch->flags & IEEE80211_CHAN_DISABLED)
+ continue;
+
+ if (!flag) {
+ flag = 1;
+ next_chan = first_channel = (u32) ch->hw_value;
+ max_pwr = ch->max_power;
+ num_parsed_chan = 1;
+ continue;
+ }
+
+ if ((ch->hw_value == next_chan + 1) &&
+ (ch->max_power == max_pwr)) {
+ /* Consolidate adjacent channels */
+ next_chan++;
+ num_parsed_chan++;
+ } else {
+ /* Add this triplet */
+ lbs_deb_11d("11D triplet (%d, %d, %d)\n",
+ first_channel, num_parsed_chan,
+ max_pwr);
+ t = &domain->triplet[num_triplet];
+ t->chans.first_channel = first_channel;
+ t->chans.num_channels = num_parsed_chan;
+ t->chans.max_power = max_pwr;
+ num_triplet++;
+ flag = 0;
+ }
+ }
+
+ if (flag) {
+ /* Add last triplet */
+ lbs_deb_11d("11D triplet (%d, %d, %d)\n", first_channel,
+ num_parsed_chan, max_pwr);
+ t = &domain->triplet[num_triplet];
+ t->chans.first_channel = first_channel;
+ t->chans.num_channels = num_parsed_chan;
+ t->chans.max_power = max_pwr;
+ num_triplet++;
+ }
+ }
+
+ lbs_deb_11d("# triplets %d\n", num_triplet);
+
+ /* Set command header sizes */
+ triplet_size = num_triplet * sizeof(struct ieee80211_country_ie_triplet);
+ domain->header.len = cpu_to_le16(sizeof(domain->country_code) +
+ triplet_size);
+
+ lbs_deb_hex(LBS_DEB_11D, "802.11D domain param set",
+ (u8 *) &cmd.domain.country_code,
+ le16_to_cpu(domain->header.len));
+
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd.hdr) +
+ sizeof(cmd.action) +
+ sizeof(cmd.domain.header) +
+ sizeof(cmd.domain.country_code) +
+ triplet_size);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11D_DOMAIN_INFO, &cmd);
+
+out:
+ return ret;
+}
+
+/**
+ * lbs_get_reg - Read a MAC, Baseband, or RF register
+ *
+ * @priv: pointer to &struct lbs_private
+ * @reg: register command, one of CMD_MAC_REG_ACCESS,
+ * CMD_BBP_REG_ACCESS, or CMD_RF_REG_ACCESS
+ * @offset: byte offset of the register to get
+ * @value: on success, the value of the register at 'offset'
+ *
+ * returns: 0 on success, error code on failure
+*/
+int lbs_get_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 *value)
+{
+ struct cmd_ds_reg_access cmd;
+ int ret = 0;
+
+ BUG_ON(value == NULL);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_GET);
+ cmd.offset = cpu_to_le16(offset);
+
+ if (reg != CMD_MAC_REG_ACCESS &&
+ reg != CMD_BBP_REG_ACCESS &&
+ reg != CMD_RF_REG_ACCESS) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = lbs_cmd_with_response(priv, reg, &cmd);
+ if (!ret) {
+ if (reg == CMD_BBP_REG_ACCESS || reg == CMD_RF_REG_ACCESS)
+ *value = cmd.value.bbp_rf;
+ else if (reg == CMD_MAC_REG_ACCESS)
+ *value = le32_to_cpu(cmd.value.mac);
+ }
+
+out:
+ return ret;
+}
+
+/**
+ * lbs_set_reg - Write a MAC, Baseband, or RF register
+ *
+ * @priv: pointer to &struct lbs_private
+ * @reg: register command, one of CMD_MAC_REG_ACCESS,
+ * CMD_BBP_REG_ACCESS, or CMD_RF_REG_ACCESS
+ * @offset: byte offset of the register to set
+ * @value: the value to write to the register at 'offset'
+ *
+ * returns: 0 on success, error code on failure
+*/
+int lbs_set_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 value)
+{
+ struct cmd_ds_reg_access cmd;
+ int ret = 0;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.offset = cpu_to_le16(offset);
+
+ if (reg == CMD_BBP_REG_ACCESS || reg == CMD_RF_REG_ACCESS)
+ cmd.value.bbp_rf = (u8) (value & 0xFF);
+ else if (reg == CMD_MAC_REG_ACCESS)
+ cmd.value.mac = cpu_to_le32(value);
+ else {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = lbs_cmd_with_response(priv, reg, &cmd);
+
+out:
+ return ret;
+}
+
+static void lbs_queue_cmd(struct lbs_private *priv,
+ struct cmd_ctrl_node *cmdnode)
+{
+ unsigned long flags;
+ int addtail = 1;
+
+ if (!cmdnode) {
+ lbs_deb_host("QUEUE_CMD: cmdnode is NULL\n");
+ return;
+ }
+ if (!cmdnode->cmdbuf->size) {
+ lbs_deb_host("DNLD_CMD: cmd size is zero\n");
+ return;
+ }
+ cmdnode->result = 0;
+
+ /* Exit_PS command needs to be queued in the header always. */
+ if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_PS_MODE) {
+ struct cmd_ds_802_11_ps_mode *psm = (void *)cmdnode->cmdbuf;
+
+ if (psm->action == cpu_to_le16(PS_MODE_ACTION_EXIT_PS)) {
+ if (priv->psstate != PS_STATE_FULL_POWER)
+ addtail = 0;
+ }
+ }
+
+ if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_WAKEUP_CONFIRM)
+ addtail = 0;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (addtail)
+ list_add_tail(&cmdnode->list, &priv->cmdpendingq);
+ else
+ list_add(&cmdnode->list, &priv->cmdpendingq);
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ lbs_deb_host("QUEUE_CMD: inserted command 0x%04x into cmdpendingq\n",
+ le16_to_cpu(cmdnode->cmdbuf->command));
+}
+
+static void lbs_submit_command(struct lbs_private *priv,
+ struct cmd_ctrl_node *cmdnode)
+{
+ unsigned long flags;
+ struct cmd_header *cmd;
+ uint16_t cmdsize;
+ uint16_t command;
+ int timeo = 3 * HZ;
+ int ret;
+
+ cmd = cmdnode->cmdbuf;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ priv->seqnum++;
+ cmd->seqnum = cpu_to_le16(priv->seqnum);
+ priv->cur_cmd = cmdnode;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ cmdsize = le16_to_cpu(cmd->size);
+ command = le16_to_cpu(cmd->command);
+
+ /* These commands take longer */
+ if (command == CMD_802_11_SCAN || command == CMD_802_11_ASSOCIATE)
+ timeo = 5 * HZ;
+
+ lbs_deb_cmd("DNLD_CMD: command 0x%04x, seq %d, size %d\n",
+ command, le16_to_cpu(cmd->seqnum), cmdsize);
+ lbs_deb_hex(LBS_DEB_CMD, "DNLD_CMD", (void *) cmdnode->cmdbuf, cmdsize);
+
+ ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) cmd, cmdsize);
+
+ if (ret) {
+ netdev_info(priv->dev, "DNLD_CMD: hw_host_to_card failed: %d\n",
+ ret);
+ /* Reset dnld state machine, report failure */
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+ lbs_complete_command(priv, cmdnode, ret);
+ }
+
+ if (command == CMD_802_11_DEEP_SLEEP) {
+ if (priv->is_auto_deep_sleep_enabled) {
+ priv->wakeup_dev_required = 1;
+ priv->dnld_sent = 0;
+ }
+ priv->is_deep_sleep = 1;
+ lbs_complete_command(priv, cmdnode, 0);
+ } else {
+ /* Setup the timer after transmit command */
+ mod_timer(&priv->command_timer, jiffies + timeo);
+ }
+}
+
+/*
+ * This function inserts command node to cmdfreeq
+ * after cleans it. Requires priv->driver_lock held.
+ */
+static void __lbs_cleanup_and_insert_cmd(struct lbs_private *priv,
+ struct cmd_ctrl_node *cmdnode)
+{
+ if (!cmdnode)
+ return;
+
+ cmdnode->callback = NULL;
+ cmdnode->callback_arg = 0;
+
+ memset(cmdnode->cmdbuf, 0, LBS_CMD_BUFFER_SIZE);
+
+ list_add_tail(&cmdnode->list, &priv->cmdfreeq);
+}
+
+static void lbs_cleanup_and_insert_cmd(struct lbs_private *priv,
+ struct cmd_ctrl_node *ptempcmd)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ __lbs_cleanup_and_insert_cmd(priv, ptempcmd);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+
+void __lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd,
+ int result)
+{
+ /*
+ * Normally, commands are removed from cmdpendingq before being
+ * submitted. However, we can arrive here on alternative codepaths
+ * where the command is still pending. Make sure the command really
+ * isn't part of a list at this point.
+ */
+ list_del_init(&cmd->list);
+
+ cmd->result = result;
+ cmd->cmdwaitqwoken = 1;
+ wake_up(&cmd->cmdwait_q);
+
+ if (!cmd->callback || cmd->callback == lbs_cmd_async_callback)
+ __lbs_cleanup_and_insert_cmd(priv, cmd);
+ priv->cur_cmd = NULL;
+ wake_up(&priv->waitq);
+}
+
+void lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd,
+ int result)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ __lbs_complete_command(priv, cmd, result);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+
+int lbs_set_radio(struct lbs_private *priv, u8 preamble, u8 radio_on)
+{
+ struct cmd_ds_802_11_radio_control cmd;
+ int ret = -EINVAL;
+
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.control = 0;
+
+ /* Only v8 and below support setting the preamble */
+ if (priv->fwrelease < 0x09000000) {
+ switch (preamble) {
+ case RADIO_PREAMBLE_SHORT:
+ case RADIO_PREAMBLE_AUTO:
+ case RADIO_PREAMBLE_LONG:
+ cmd.control = cpu_to_le16(preamble);
+ break;
+ default:
+ goto out;
+ }
+ }
+
+ if (radio_on)
+ cmd.control |= cpu_to_le16(0x1);
+ else {
+ cmd.control &= cpu_to_le16(~0x1);
+ priv->txpower_cur = 0;
+ }
+
+ lbs_deb_cmd("RADIO_CONTROL: radio %s, preamble %d\n",
+ radio_on ? "ON" : "OFF", preamble);
+
+ priv->radio_on = radio_on;
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_RADIO_CONTROL, &cmd);
+
+out:
+ return ret;
+}
+
+void lbs_set_mac_control(struct lbs_private *priv)
+{
+ struct cmd_ds_mac_control cmd;
+
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(priv->mac_control);
+ cmd.reserved = 0;
+
+ lbs_cmd_async(priv, CMD_MAC_CONTROL, &cmd.hdr, sizeof(cmd));
+}
+
+int lbs_set_mac_control_sync(struct lbs_private *priv)
+{
+ struct cmd_ds_mac_control cmd;
+ int ret = 0;
+
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(priv->mac_control);
+ cmd.reserved = 0;
+ ret = lbs_cmd_with_response(priv, CMD_MAC_CONTROL, &cmd);
+
+ return ret;
+}
+
+/**
+ * lbs_allocate_cmd_buffer - allocates the command buffer and links
+ * it to command free queue
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: 0 for success or -1 on error
+ */
+int lbs_allocate_cmd_buffer(struct lbs_private *priv)
+{
+ int ret = 0;
+ u32 bufsize;
+ u32 i;
+ struct cmd_ctrl_node *cmdarray;
+
+ /* Allocate and initialize the command array */
+ bufsize = sizeof(struct cmd_ctrl_node) * LBS_NUM_CMD_BUFFERS;
+ if (!(cmdarray = kzalloc(bufsize, GFP_KERNEL))) {
+ lbs_deb_host("ALLOC_CMD_BUF: tempcmd_array is NULL\n");
+ ret = -1;
+ goto done;
+ }
+ priv->cmd_array = cmdarray;
+
+ /* Allocate and initialize each command buffer in the command array */
+ for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
+ cmdarray[i].cmdbuf = kzalloc(LBS_CMD_BUFFER_SIZE, GFP_KERNEL);
+ if (!cmdarray[i].cmdbuf) {
+ lbs_deb_host("ALLOC_CMD_BUF: ptempvirtualaddr is NULL\n");
+ ret = -1;
+ goto done;
+ }
+ }
+
+ for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
+ init_waitqueue_head(&cmdarray[i].cmdwait_q);
+ lbs_cleanup_and_insert_cmd(priv, &cmdarray[i]);
+ }
+ ret = 0;
+
+done:
+ return ret;
+}
+
+/**
+ * lbs_free_cmd_buffer - free the command buffer
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: 0 for success
+ */
+int lbs_free_cmd_buffer(struct lbs_private *priv)
+{
+ struct cmd_ctrl_node *cmdarray;
+ unsigned int i;
+
+ /* need to check if cmd array is allocated or not */
+ if (priv->cmd_array == NULL) {
+ lbs_deb_host("FREE_CMD_BUF: cmd_array is NULL\n");
+ goto done;
+ }
+
+ cmdarray = priv->cmd_array;
+
+ /* Release shared memory buffers */
+ for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) {
+ if (cmdarray[i].cmdbuf) {
+ kfree(cmdarray[i].cmdbuf);
+ cmdarray[i].cmdbuf = NULL;
+ }
+ }
+
+ /* Release cmd_ctrl_node */
+ if (priv->cmd_array) {
+ kfree(priv->cmd_array);
+ priv->cmd_array = NULL;
+ }
+
+done:
+ return 0;
+}
+
+/**
+ * lbs_get_free_cmd_node - gets a free command node if available in
+ * command free queue
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: A pointer to &cmd_ctrl_node structure on success
+ * or %NULL on error
+ */
+static struct cmd_ctrl_node *lbs_get_free_cmd_node(struct lbs_private *priv)
+{
+ struct cmd_ctrl_node *tempnode;
+ unsigned long flags;
+
+ if (!priv)
+ return NULL;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (!list_empty(&priv->cmdfreeq)) {
+ tempnode = list_first_entry(&priv->cmdfreeq,
+ struct cmd_ctrl_node, list);
+ list_del_init(&tempnode->list);
+ } else {
+ lbs_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n");
+ tempnode = NULL;
+ }
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ return tempnode;
+}
+
+/**
+ * lbs_execute_next_command - execute next command in command
+ * pending queue. Will put firmware back to PS mode if applicable.
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: 0 on success or -1 on error
+ */
+int lbs_execute_next_command(struct lbs_private *priv)
+{
+ struct cmd_ctrl_node *cmdnode = NULL;
+ struct cmd_header *cmd;
+ unsigned long flags;
+ int ret = 0;
+
+ /* Debug group is LBS_DEB_THREAD and not LBS_DEB_HOST, because the
+ * only caller to us is lbs_thread() and we get even when a
+ * data packet is received */
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (priv->cur_cmd) {
+ netdev_alert(priv->dev,
+ "EXEC_NEXT_CMD: already processing command!\n");
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ ret = -1;
+ goto done;
+ }
+
+ if (!list_empty(&priv->cmdpendingq)) {
+ cmdnode = list_first_entry(&priv->cmdpendingq,
+ struct cmd_ctrl_node, list);
+ }
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ if (cmdnode) {
+ cmd = cmdnode->cmdbuf;
+
+ if (is_command_allowed_in_ps(le16_to_cpu(cmd->command))) {
+ if ((priv->psstate == PS_STATE_SLEEP) ||
+ (priv->psstate == PS_STATE_PRE_SLEEP)) {
+ lbs_deb_host(
+ "EXEC_NEXT_CMD: cannot send cmd 0x%04x in psstate %d\n",
+ le16_to_cpu(cmd->command),
+ priv->psstate);
+ ret = -1;
+ goto done;
+ }
+ lbs_deb_host("EXEC_NEXT_CMD: OK to send command "
+ "0x%04x in psstate %d\n",
+ le16_to_cpu(cmd->command), priv->psstate);
+ } else if (priv->psstate != PS_STATE_FULL_POWER) {
+ /*
+ * 1. Non-PS command:
+ * Queue it. set needtowakeup to TRUE if current state
+ * is SLEEP, otherwise call send EXIT_PS.
+ * 2. PS command but not EXIT_PS:
+ * Ignore it.
+ * 3. PS command EXIT_PS:
+ * Set needtowakeup to TRUE if current state is SLEEP,
+ * otherwise send this command down to firmware
+ * immediately.
+ */
+ if (cmd->command != cpu_to_le16(CMD_802_11_PS_MODE)) {
+ /* Prepare to send Exit PS,
+ * this non PS command will be sent later */
+ if ((priv->psstate == PS_STATE_SLEEP)
+ || (priv->psstate == PS_STATE_PRE_SLEEP)
+ ) {
+ /* w/ new scheme, it will not reach here.
+ since it is blocked in main_thread. */
+ priv->needtowakeup = 1;
+ } else {
+ lbs_set_ps_mode(priv,
+ PS_MODE_ACTION_EXIT_PS,
+ false);
+ }
+
+ ret = 0;
+ goto done;
+ } else {
+ /*
+ * PS command. Ignore it if it is not Exit_PS.
+ * otherwise send it down immediately.
+ */
+ struct cmd_ds_802_11_ps_mode *psm = (void *)cmd;
+
+ lbs_deb_host(
+ "EXEC_NEXT_CMD: PS cmd, action 0x%02x\n",
+ psm->action);
+ if (psm->action !=
+ cpu_to_le16(PS_MODE_ACTION_EXIT_PS)) {
+ lbs_deb_host(
+ "EXEC_NEXT_CMD: ignore ENTER_PS cmd\n");
+ lbs_complete_command(priv, cmdnode, 0);
+
+ ret = 0;
+ goto done;
+ }
+
+ if ((priv->psstate == PS_STATE_SLEEP) ||
+ (priv->psstate == PS_STATE_PRE_SLEEP)) {
+ lbs_deb_host(
+ "EXEC_NEXT_CMD: ignore EXIT_PS cmd in sleep\n");
+ lbs_complete_command(priv, cmdnode, 0);
+ priv->needtowakeup = 1;
+
+ ret = 0;
+ goto done;
+ }
+
+ lbs_deb_host(
+ "EXEC_NEXT_CMD: sending EXIT_PS\n");
+ }
+ }
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ list_del_init(&cmdnode->list);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ lbs_deb_host("EXEC_NEXT_CMD: sending command 0x%04x\n",
+ le16_to_cpu(cmd->command));
+ lbs_submit_command(priv, cmdnode);
+ } else {
+ /*
+ * check if in power save mode, if yes, put the device back
+ * to PS mode
+ */
+ if ((priv->psmode != LBS802_11POWERMODECAM) &&
+ (priv->psstate == PS_STATE_FULL_POWER) &&
+ (priv->connect_status == LBS_CONNECTED)) {
+ lbs_deb_host(
+ "EXEC_NEXT_CMD: cmdpendingq empty, go back to PS_SLEEP");
+ lbs_set_ps_mode(priv, PS_MODE_ACTION_ENTER_PS,
+ false);
+ }
+ }
+
+ ret = 0;
+done:
+ return ret;
+}
+
+static void lbs_send_confirmsleep(struct lbs_private *priv)
+{
+ unsigned long flags;
+ int ret;
+
+ lbs_deb_hex(LBS_DEB_HOST, "sleep confirm", (u8 *) &confirm_sleep,
+ sizeof(confirm_sleep));
+
+ ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) &confirm_sleep,
+ sizeof(confirm_sleep));
+ if (ret) {
+ netdev_alert(priv->dev, "confirm_sleep failed\n");
+ return;
+ }
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ /* We don't get a response on the sleep-confirmation */
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+
+ if (priv->is_host_sleep_configured) {
+ priv->is_host_sleep_activated = 1;
+ wake_up_interruptible(&priv->host_sleep_q);
+ }
+
+ /* If nothing to do, go back to sleep (?) */
+ if (!kfifo_len(&priv->event_fifo) && !priv->resp_len[priv->resp_idx])
+ priv->psstate = PS_STATE_SLEEP;
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+
+/**
+ * lbs_ps_confirm_sleep - checks condition and prepares to
+ * send sleep confirm command to firmware if ok
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ *
+ * returns: n/a
+ */
+void lbs_ps_confirm_sleep(struct lbs_private *priv)
+{
+ unsigned long flags =0;
+ int allowed = 1;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ if (priv->dnld_sent) {
+ allowed = 0;
+ lbs_deb_host("dnld_sent was set\n");
+ }
+
+ /* In-progress command? */
+ if (priv->cur_cmd) {
+ allowed = 0;
+ lbs_deb_host("cur_cmd was set\n");
+ }
+
+ /* Pending events or command responses? */
+ if (kfifo_len(&priv->event_fifo) || priv->resp_len[priv->resp_idx]) {
+ allowed = 0;
+ lbs_deb_host("pending events or command responses\n");
+ }
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ if (allowed) {
+ lbs_deb_host("sending lbs_ps_confirm_sleep\n");
+ lbs_send_confirmsleep(priv);
+ } else {
+ lbs_deb_host("sleep confirm has been delayed\n");
+ }
+}
+
+
+/**
+ * lbs_set_tpc_cfg - Configures the transmission power control functionality
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @enable: Transmission power control enable
+ * @p0: Power level when link quality is good (dBm).
+ * @p1: Power level when link quality is fair (dBm).
+ * @p2: Power level when link quality is poor (dBm).
+ * @usesnr: Use Signal to Noise Ratio in TPC
+ *
+ * returns: 0 on success
+ */
+int lbs_set_tpc_cfg(struct lbs_private *priv, int enable, int8_t p0, int8_t p1,
+ int8_t p2, int usesnr)
+{
+ struct cmd_ds_802_11_tpc_cfg cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.enable = !!enable;
+ cmd.usesnr = !!usesnr;
+ cmd.P0 = p0;
+ cmd.P1 = p1;
+ cmd.P2 = p2;
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_TPC_CFG, &cmd);
+
+ return ret;
+}
+
+/**
+ * lbs_set_power_adapt_cfg - Configures the power adaptation settings
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @enable: Power adaptation enable
+ * @p0: Power level for 1, 2, 5.5 and 11 Mbps (dBm).
+ * @p1: Power level for 6, 9, 12, 18, 22, 24 and 36 Mbps (dBm).
+ * @p2: Power level for 48 and 54 Mbps (dBm).
+ *
+ * returns: 0 on Success
+ */
+
+int lbs_set_power_adapt_cfg(struct lbs_private *priv, int enable, int8_t p0,
+ int8_t p1, int8_t p2)
+{
+ struct cmd_ds_802_11_pa_cfg cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ cmd.enable = !!enable;
+ cmd.P0 = p0;
+ cmd.P1 = p1;
+ cmd.P2 = p2;
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_PA_CFG , &cmd);
+
+ return ret;
+}
+
+
+struct cmd_ctrl_node *__lbs_cmd_async(struct lbs_private *priv,
+ uint16_t command, struct cmd_header *in_cmd, int in_cmd_size,
+ int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *),
+ unsigned long callback_arg)
+{
+ struct cmd_ctrl_node *cmdnode;
+
+ if (priv->surpriseremoved) {
+ lbs_deb_host("PREP_CMD: card removed\n");
+ cmdnode = ERR_PTR(-ENOENT);
+ goto done;
+ }
+
+ /* No commands are allowed in Deep Sleep until we toggle the GPIO
+ * to wake up the card and it has signaled that it's ready.
+ */
+ if (!priv->is_auto_deep_sleep_enabled) {
+ if (priv->is_deep_sleep) {
+ lbs_deb_cmd("command not allowed in deep sleep\n");
+ cmdnode = ERR_PTR(-EBUSY);
+ goto done;
+ }
+ }
+
+ cmdnode = lbs_get_free_cmd_node(priv);
+ if (cmdnode == NULL) {
+ lbs_deb_host("PREP_CMD: cmdnode is NULL\n");
+
+ /* Wake up main thread to execute next command */
+ wake_up(&priv->waitq);
+ cmdnode = ERR_PTR(-ENOBUFS);
+ goto done;
+ }
+
+ cmdnode->callback = callback;
+ cmdnode->callback_arg = callback_arg;
+
+ /* Copy the incoming command to the buffer */
+ memcpy(cmdnode->cmdbuf, in_cmd, in_cmd_size);
+
+ /* Set command, clean result, move to buffer */
+ cmdnode->cmdbuf->command = cpu_to_le16(command);
+ cmdnode->cmdbuf->size = cpu_to_le16(in_cmd_size);
+ cmdnode->cmdbuf->result = 0;
+
+ lbs_deb_host("PREP_CMD: command 0x%04x\n", command);
+
+ cmdnode->cmdwaitqwoken = 0;
+ lbs_queue_cmd(priv, cmdnode);
+ wake_up(&priv->waitq);
+
+ done:
+ return cmdnode;
+}
+
+void lbs_cmd_async(struct lbs_private *priv, uint16_t command,
+ struct cmd_header *in_cmd, int in_cmd_size)
+{
+ __lbs_cmd_async(priv, command, in_cmd, in_cmd_size,
+ lbs_cmd_async_callback, 0);
+}
+
+int __lbs_cmd(struct lbs_private *priv, uint16_t command,
+ struct cmd_header *in_cmd, int in_cmd_size,
+ int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *),
+ unsigned long callback_arg)
+{
+ struct cmd_ctrl_node *cmdnode;
+ unsigned long flags;
+ int ret = 0;
+
+ cmdnode = __lbs_cmd_async(priv, command, in_cmd, in_cmd_size,
+ callback, callback_arg);
+ if (IS_ERR(cmdnode)) {
+ ret = PTR_ERR(cmdnode);
+ goto done;
+ }
+
+ might_sleep();
+
+ /*
+ * Be careful with signals here. A signal may be received as the system
+ * goes into suspend or resume. We do not want this to interrupt the
+ * command, so we perform an uninterruptible sleep.
+ */
+ wait_event(cmdnode->cmdwait_q, cmdnode->cmdwaitqwoken);
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ ret = cmdnode->result;
+ if (ret)
+ netdev_info(priv->dev, "PREP_CMD: command 0x%04x failed: %d\n",
+ command, ret);
+
+ __lbs_cleanup_and_insert_cmd(priv, cmdnode);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+done:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__lbs_cmd);
diff --git a/drivers/net/wireless/marvell/libertas/cmd.h b/drivers/net/wireless/marvell/libertas/cmd.h
new file mode 100644
index 000000000..3c1930746
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/cmd.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2007, Red Hat, Inc. */
+
+#ifndef _LBS_CMD_H_
+#define _LBS_CMD_H_
+
+#include <net/cfg80211.h>
+
+#include "host.h"
+#include "dev.h"
+
+
+/* Command & response transfer between host and card */
+
+struct cmd_ctrl_node {
+ struct list_head list;
+ int result;
+ /* command response */
+ int (*callback)(struct lbs_private *,
+ unsigned long,
+ struct cmd_header *);
+ unsigned long callback_arg;
+ /* command data */
+ struct cmd_header *cmdbuf;
+ /* wait queue */
+ u16 cmdwaitqwoken;
+ wait_queue_head_t cmdwait_q;
+};
+
+
+/* lbs_cmd() infers the size of the buffer to copy data back into, from
+ the size of the target of the pointer. Since the command to be sent
+ may often be smaller, that size is set in cmd->size by the caller.*/
+#define lbs_cmd(priv, cmdnr, cmd, cb, cb_arg) ({ \
+ uint16_t __sz = le16_to_cpu((cmd)->hdr.size); \
+ (cmd)->hdr.size = cpu_to_le16(sizeof(*(cmd))); \
+ __lbs_cmd(priv, cmdnr, &(cmd)->hdr, __sz, cb, cb_arg); \
+})
+
+#define lbs_cmd_with_response(priv, cmdnr, cmd) \
+ lbs_cmd(priv, cmdnr, cmd, lbs_cmd_copyback, (unsigned long) (cmd))
+
+void lbs_cmd_async(struct lbs_private *priv, uint16_t command,
+ struct cmd_header *in_cmd, int in_cmd_size);
+
+int __lbs_cmd(struct lbs_private *priv, uint16_t command,
+ struct cmd_header *in_cmd, int in_cmd_size,
+ int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *),
+ unsigned long callback_arg);
+
+struct cmd_ctrl_node *__lbs_cmd_async(struct lbs_private *priv,
+ uint16_t command, struct cmd_header *in_cmd, int in_cmd_size,
+ int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *),
+ unsigned long callback_arg);
+
+int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra,
+ struct cmd_header *resp);
+
+int lbs_allocate_cmd_buffer(struct lbs_private *priv);
+int lbs_free_cmd_buffer(struct lbs_private *priv);
+
+int lbs_execute_next_command(struct lbs_private *priv);
+void __lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd,
+ int result);
+void lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd,
+ int result);
+int lbs_process_command_response(struct lbs_private *priv, u8 *data, u32 len);
+
+
+/* From cmdresp.c */
+
+void lbs_mac_event_disconnected(struct lbs_private *priv,
+ bool locally_generated);
+
+
+
+/* Events */
+
+void lbs_process_event(struct lbs_private *priv, u32 event);
+
+
+/* Actual commands */
+
+int lbs_update_hw_spec(struct lbs_private *priv);
+
+int lbs_set_channel(struct lbs_private *priv, u8 channel);
+
+int lbs_update_channel(struct lbs_private *priv);
+
+int lbs_host_sleep_cfg(struct lbs_private *priv, uint32_t criteria,
+ struct wol_config *p_wol_config);
+
+int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, uint16_t cmd_action,
+ struct sleep_params *sp);
+
+void lbs_ps_confirm_sleep(struct lbs_private *priv);
+
+int lbs_set_radio(struct lbs_private *priv, u8 preamble, u8 radio_on);
+
+void lbs_set_mac_control(struct lbs_private *priv);
+int lbs_set_mac_control_sync(struct lbs_private *priv);
+
+int lbs_get_tx_power(struct lbs_private *priv, s16 *curlevel, s16 *minlevel,
+ s16 *maxlevel);
+
+int lbs_set_snmp_mib(struct lbs_private *priv, u32 oid, u16 val);
+
+int lbs_get_snmp_mib(struct lbs_private *priv, u32 oid, u16 *out_val);
+
+
+/* Commands only used in wext.c, assoc. and scan.c */
+
+int lbs_set_power_adapt_cfg(struct lbs_private *priv, int enable, int8_t p0,
+ int8_t p1, int8_t p2);
+
+int lbs_set_tpc_cfg(struct lbs_private *priv, int enable, int8_t p0, int8_t p1,
+ int8_t p2, int usesnr);
+
+int lbs_set_data_rate(struct lbs_private *priv, u8 rate);
+
+int lbs_cmd_802_11_rate_adapt_rateset(struct lbs_private *priv,
+ uint16_t cmd_action);
+
+int lbs_set_tx_power(struct lbs_private *priv, s16 dbm);
+
+int lbs_set_deep_sleep(struct lbs_private *priv, int deep_sleep);
+
+int lbs_set_host_sleep(struct lbs_private *priv, int host_sleep);
+
+int lbs_set_monitor_mode(struct lbs_private *priv, int enable);
+
+int lbs_get_rssi(struct lbs_private *priv, s8 *snr, s8 *nf);
+
+int lbs_set_11d_domain_info(struct lbs_private *priv);
+
+int lbs_get_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 *value);
+
+int lbs_set_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 value);
+
+int lbs_set_ps_mode(struct lbs_private *priv, u16 cmd_action, bool block);
+
+#endif /* _LBS_CMD_H */
diff --git a/drivers/net/wireless/marvell/libertas/cmdresp.c b/drivers/net/wireless/marvell/libertas/cmdresp.c
new file mode 100644
index 000000000..74cb7551f
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/cmdresp.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains the handling of command
+ * responses as well as events generated by firmware.
+ */
+
+#include <linux/hardirq.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <asm/unaligned.h>
+#include <net/cfg80211.h>
+
+#include "cfg.h"
+#include "cmd.h"
+
+/**
+ * lbs_mac_event_disconnected - handles disconnect event. It
+ * reports disconnect to upper layer, clean tx/rx packets,
+ * reset link state etc.
+ *
+ * @priv: A pointer to struct lbs_private structure
+ * @locally_generated: indicates disconnect was requested locally
+ * (usually by userspace)
+ *
+ * returns: n/a
+ */
+void lbs_mac_event_disconnected(struct lbs_private *priv,
+ bool locally_generated)
+{
+ unsigned long flags;
+
+ if (priv->connect_status != LBS_CONNECTED)
+ return;
+
+ /*
+ * Cisco AP sends EAP failure and de-auth in less than 0.5 ms.
+ * It causes problem in the Supplicant
+ */
+ msleep_interruptible(1000);
+
+ if (priv->wdev->iftype == NL80211_IFTYPE_STATION)
+ lbs_send_disconnect_notification(priv, locally_generated);
+
+ /* report disconnect to upper layer */
+ netif_stop_queue(priv->dev);
+ netif_carrier_off(priv->dev);
+
+ /* Free Tx and Rx packets */
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ dev_kfree_skb_irq(priv->currenttxskb);
+ priv->currenttxskb = NULL;
+ priv->tx_pending_len = 0;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ priv->connect_status = LBS_DISCONNECTED;
+
+ if (priv->psstate != PS_STATE_FULL_POWER) {
+ /* make firmware to exit PS mode */
+ lbs_deb_cmd("disconnected, so exit PS mode\n");
+ lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, false);
+ }
+}
+
+int lbs_process_command_response(struct lbs_private *priv, u8 *data, u32 len)
+{
+ uint16_t respcmd, curcmd;
+ struct cmd_header *resp;
+ int ret = 0;
+ unsigned long flags;
+ uint16_t result;
+
+ mutex_lock(&priv->lock);
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (!priv->cur_cmd) {
+ lbs_deb_host("CMD_RESP: cur_cmd is NULL\n");
+ ret = -1;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ goto done;
+ }
+
+ resp = (void *)data;
+ curcmd = le16_to_cpu(priv->cur_cmd->cmdbuf->command);
+ respcmd = le16_to_cpu(resp->command);
+ result = le16_to_cpu(resp->result);
+
+ lbs_deb_cmd("CMD_RESP: response 0x%04x, seq %d, size %d\n",
+ respcmd, le16_to_cpu(resp->seqnum), len);
+ lbs_deb_hex(LBS_DEB_CMD, "CMD_RESP", (void *) resp, len);
+
+ if (resp->seqnum != priv->cur_cmd->cmdbuf->seqnum) {
+ netdev_info(priv->dev,
+ "Received CMD_RESP with invalid sequence %d (expected %d)\n",
+ le16_to_cpu(resp->seqnum),
+ le16_to_cpu(priv->cur_cmd->cmdbuf->seqnum));
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ ret = -1;
+ goto done;
+ }
+ if (respcmd != CMD_RET(curcmd) &&
+ respcmd != CMD_RET_802_11_ASSOCIATE && curcmd != CMD_802_11_ASSOCIATE) {
+ netdev_info(priv->dev, "Invalid CMD_RESP %x to command %x!\n",
+ respcmd, curcmd);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ ret = -1;
+ goto done;
+ }
+
+ if (resp->result == cpu_to_le16(0x0004)) {
+ /* 0x0004 means -EAGAIN. Drop the response, let it time out
+ and be resubmitted */
+ netdev_info(priv->dev,
+ "Firmware returns DEFER to command %x. Will let it time out...\n",
+ le16_to_cpu(resp->command));
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ ret = -1;
+ goto done;
+ }
+
+ /* Now we got response from FW, cancel the command timer */
+ del_timer(&priv->command_timer);
+ priv->cmd_timed_out = 0;
+
+ if (respcmd == CMD_RET(CMD_802_11_PS_MODE)) {
+ /* struct cmd_ds_802_11_ps_mode also contains
+ * the header
+ */
+ struct cmd_ds_802_11_ps_mode *psmode = (void *)resp;
+ u16 action = le16_to_cpu(psmode->action);
+
+ lbs_deb_host(
+ "CMD_RESP: PS_MODE cmd reply result 0x%x, action 0x%x\n",
+ result, action);
+
+ if (result) {
+ lbs_deb_host("CMD_RESP: PS command failed with 0x%x\n",
+ result);
+ /*
+ * We should not re-try enter-ps command in
+ * ad-hoc mode. It takes place in
+ * lbs_execute_next_command().
+ */
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR &&
+ action == PS_MODE_ACTION_ENTER_PS)
+ priv->psmode = LBS802_11POWERMODECAM;
+ } else if (action == PS_MODE_ACTION_ENTER_PS) {
+ priv->needtowakeup = 0;
+ priv->psstate = PS_STATE_AWAKE;
+
+ lbs_deb_host("CMD_RESP: ENTER_PS command response\n");
+ if (priv->connect_status != LBS_CONNECTED) {
+ /*
+ * When Deauth Event received before Enter_PS command
+ * response, We need to wake up the firmware.
+ */
+ lbs_deb_host(
+ "disconnected, invoking lbs_ps_wakeup\n");
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ mutex_unlock(&priv->lock);
+ lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS,
+ false);
+ mutex_lock(&priv->lock);
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ }
+ } else if (action == PS_MODE_ACTION_EXIT_PS) {
+ priv->needtowakeup = 0;
+ priv->psstate = PS_STATE_FULL_POWER;
+ lbs_deb_host("CMD_RESP: EXIT_PS command response\n");
+ } else {
+ lbs_deb_host("CMD_RESP: PS action 0x%X\n", action);
+ }
+
+ __lbs_complete_command(priv, priv->cur_cmd, result);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ ret = 0;
+ goto done;
+ }
+
+ /* If the command is not successful, cleanup and return failure */
+ if ((result != 0 || !(respcmd & 0x8000))) {
+ lbs_deb_host("CMD_RESP: error 0x%04x in command reply 0x%04x\n",
+ result, respcmd);
+ /*
+ * Handling errors here
+ */
+ switch (respcmd) {
+ case CMD_RET(CMD_GET_HW_SPEC):
+ case CMD_RET(CMD_802_11_RESET):
+ lbs_deb_host("CMD_RESP: reset failed\n");
+ break;
+
+ }
+ __lbs_complete_command(priv, priv->cur_cmd, result);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ ret = -1;
+ goto done;
+ }
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ if (priv->cur_cmd && priv->cur_cmd->callback) {
+ ret = priv->cur_cmd->callback(priv, priv->cur_cmd->callback_arg,
+ resp);
+ }
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (priv->cur_cmd) {
+ /* Clean up and Put current command back to cmdfreeq */
+ __lbs_complete_command(priv, priv->cur_cmd, result);
+ }
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+done:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+void lbs_process_event(struct lbs_private *priv, u32 event)
+{
+ struct cmd_header cmd;
+
+ switch (event) {
+ case MACREG_INT_CODE_LINK_SENSED:
+ lbs_deb_cmd("EVENT: link sensed\n");
+ break;
+
+ case MACREG_INT_CODE_DEAUTHENTICATED:
+ lbs_deb_cmd("EVENT: deauthenticated\n");
+ lbs_mac_event_disconnected(priv, false);
+ break;
+
+ case MACREG_INT_CODE_DISASSOCIATED:
+ lbs_deb_cmd("EVENT: disassociated\n");
+ lbs_mac_event_disconnected(priv, false);
+ break;
+
+ case MACREG_INT_CODE_LINK_LOST_NO_SCAN:
+ lbs_deb_cmd("EVENT: link lost\n");
+ lbs_mac_event_disconnected(priv, true);
+ break;
+
+ case MACREG_INT_CODE_PS_SLEEP:
+ lbs_deb_cmd("EVENT: ps sleep\n");
+
+ /* handle unexpected PS SLEEP event */
+ if (priv->psstate == PS_STATE_FULL_POWER) {
+ lbs_deb_cmd(
+ "EVENT: in FULL POWER mode, ignoring PS_SLEEP\n");
+ break;
+ }
+ if (!list_empty(&priv->cmdpendingq)) {
+ lbs_deb_cmd("EVENT: commands in queue, do not sleep\n");
+ break;
+ }
+ priv->psstate = PS_STATE_PRE_SLEEP;
+
+ lbs_ps_confirm_sleep(priv);
+
+ break;
+
+ case MACREG_INT_CODE_HOST_AWAKE:
+ lbs_deb_cmd("EVENT: host awake\n");
+ if (priv->reset_deep_sleep_wakeup)
+ priv->reset_deep_sleep_wakeup(priv);
+ priv->is_deep_sleep = 0;
+ lbs_cmd_async(priv, CMD_802_11_WAKEUP_CONFIRM, &cmd,
+ sizeof(cmd));
+ priv->is_host_sleep_activated = 0;
+ wake_up_interruptible(&priv->host_sleep_q);
+ break;
+
+ case MACREG_INT_CODE_DEEP_SLEEP_AWAKE:
+ if (priv->reset_deep_sleep_wakeup)
+ priv->reset_deep_sleep_wakeup(priv);
+ lbs_deb_cmd("EVENT: ds awake\n");
+ priv->is_deep_sleep = 0;
+ priv->wakeup_dev_required = 0;
+ wake_up_interruptible(&priv->ds_awake_q);
+ break;
+
+ case MACREG_INT_CODE_PS_AWAKE:
+ lbs_deb_cmd("EVENT: ps awake\n");
+ /* handle unexpected PS AWAKE event */
+ if (priv->psstate == PS_STATE_FULL_POWER) {
+ lbs_deb_cmd(
+ "EVENT: In FULL POWER mode - ignore PS AWAKE\n");
+ break;
+ }
+
+ priv->psstate = PS_STATE_AWAKE;
+
+ if (priv->needtowakeup) {
+ /*
+ * wait for the command processing to finish
+ * before resuming sending
+ * priv->needtowakeup will be set to FALSE
+ * in lbs_ps_wakeup()
+ */
+ lbs_deb_cmd("waking up ...\n");
+ lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, false);
+ }
+ break;
+
+ case MACREG_INT_CODE_MIC_ERR_UNICAST:
+ lbs_deb_cmd("EVENT: UNICAST MIC ERROR\n");
+ lbs_send_mic_failureevent(priv, event);
+ break;
+
+ case MACREG_INT_CODE_MIC_ERR_MULTICAST:
+ lbs_deb_cmd("EVENT: MULTICAST MIC ERROR\n");
+ lbs_send_mic_failureevent(priv, event);
+ break;
+
+ case MACREG_INT_CODE_MIB_CHANGED:
+ lbs_deb_cmd("EVENT: MIB CHANGED\n");
+ break;
+ case MACREG_INT_CODE_INIT_DONE:
+ lbs_deb_cmd("EVENT: INIT DONE\n");
+ break;
+ case MACREG_INT_CODE_ADHOC_BCN_LOST:
+ lbs_deb_cmd("EVENT: ADHOC beacon lost\n");
+ break;
+ case MACREG_INT_CODE_RSSI_LOW:
+ netdev_alert(priv->dev, "EVENT: rssi low\n");
+ break;
+ case MACREG_INT_CODE_SNR_LOW:
+ netdev_alert(priv->dev, "EVENT: snr low\n");
+ break;
+ case MACREG_INT_CODE_MAX_FAIL:
+ netdev_alert(priv->dev, "EVENT: max fail\n");
+ break;
+ case MACREG_INT_CODE_RSSI_HIGH:
+ netdev_alert(priv->dev, "EVENT: rssi high\n");
+ break;
+ case MACREG_INT_CODE_SNR_HIGH:
+ netdev_alert(priv->dev, "EVENT: snr high\n");
+ break;
+
+ case MACREG_INT_CODE_MESH_AUTO_STARTED:
+ /* Ignore spurious autostart events */
+ netdev_info(priv->dev, "EVENT: MESH_AUTO_STARTED (ignoring)\n");
+ break;
+
+ default:
+ netdev_alert(priv->dev, "EVENT: unknown event id %d\n", event);
+ break;
+ }
+}
diff --git a/drivers/net/wireless/marvell/libertas/debugfs.c b/drivers/net/wireless/marvell/libertas/debugfs.c
new file mode 100644
index 000000000..c604613ab
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/debugfs.c
@@ -0,0 +1,931 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dcache.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/hardirq.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+#include "decl.h"
+#include "cmd.h"
+#include "debugfs.h"
+
+static struct dentry *lbs_dir;
+static char *szStates[] = {
+ "Connected",
+ "Disconnected"
+};
+
+#ifdef PROC_DEBUG
+static void lbs_debug_init(struct lbs_private *priv);
+#endif
+
+static ssize_t write_file_dummy(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return -EINVAL;
+}
+
+static const size_t len = PAGE_SIZE;
+
+static ssize_t lbs_dev_info(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ size_t pos = 0;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ ssize_t res;
+ if (!buf)
+ return -ENOMEM;
+
+ pos += snprintf(buf+pos, len-pos, "state = %s\n",
+ szStates[priv->connect_status]);
+ pos += snprintf(buf+pos, len-pos, "region_code = %02x\n",
+ (u32) priv->regioncode);
+
+ res = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+
+ free_page(addr);
+ return res;
+}
+
+static ssize_t lbs_sleepparams_write(struct file *file,
+ const char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t ret;
+ struct sleep_params sp;
+ int p1, p2, p3, p4, p5, p6;
+ char *buf;
+
+ buf = memdup_user_nul(user_buf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%d %d %d %d %d %d", &p1, &p2, &p3, &p4, &p5, &p6);
+ if (ret != 6) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ sp.sp_error = p1;
+ sp.sp_offset = p2;
+ sp.sp_stabletime = p3;
+ sp.sp_calcontrol = p4;
+ sp.sp_extsleepclk = p5;
+ sp.sp_reserved = p6;
+
+ ret = lbs_cmd_802_11_sleep_params(priv, CMD_ACT_SET, &sp);
+ if (!ret)
+ ret = count;
+ else if (ret > 0)
+ ret = -EINVAL;
+
+out_unlock:
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t ret;
+ size_t pos = 0;
+ struct sleep_params sp;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ if (!buf)
+ return -ENOMEM;
+
+ ret = lbs_cmd_802_11_sleep_params(priv, CMD_ACT_GET, &sp);
+ if (ret)
+ goto out_unlock;
+
+ pos += snprintf(buf, len, "%d %d %d %d %d %d\n", sp.sp_error,
+ sp.sp_offset, sp.sp_stabletime,
+ sp.sp_calcontrol, sp.sp_extsleepclk,
+ sp.sp_reserved);
+
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+
+out_unlock:
+ free_page(addr);
+ return ret;
+}
+
+static ssize_t lbs_host_sleep_write(struct file *file,
+ const char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t ret;
+ int host_sleep;
+ char *buf;
+
+ buf = memdup_user_nul(user_buf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%d", &host_sleep);
+ if (ret != 1) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (host_sleep == 0)
+ ret = lbs_set_host_sleep(priv, 0);
+ else if (host_sleep == 1) {
+ if (priv->wol_criteria == EHS_REMOVE_WAKEUP) {
+ netdev_info(priv->dev,
+ "wake parameters not configured\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ ret = lbs_set_host_sleep(priv, 1);
+ } else {
+ netdev_err(priv->dev, "invalid option\n");
+ ret = -EINVAL;
+ }
+
+ if (!ret)
+ ret = count;
+
+out_unlock:
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t ret;
+ size_t pos = 0;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ if (!buf)
+ return -ENOMEM;
+
+ pos += snprintf(buf, len, "%d\n", priv->is_host_sleep_activated);
+
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+
+ free_page(addr);
+ return ret;
+}
+
+/*
+ * When calling CMD_802_11_SUBSCRIBE_EVENT with CMD_ACT_GET, me might
+ * get a bunch of vendor-specific TLVs (a.k.a. IEs) back from the
+ * firmware. Here's an example:
+ * 04 01 02 00 00 00 05 01 02 00 00 00 06 01 02 00
+ * 00 00 07 01 02 00 3c 00 00 00 00 00 00 00 03 03
+ * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ *
+ * The 04 01 is the TLV type (here TLV_TYPE_RSSI_LOW), 02 00 is the length,
+ * 00 00 are the data bytes of this TLV. For this TLV, their meaning is
+ * defined in mrvlietypes_thresholds
+ *
+ * This function searches in this TLV data chunk for a given TLV type
+ * and returns a pointer to the first data byte of the TLV, or to NULL
+ * if the TLV hasn't been found.
+ */
+static void *lbs_tlv_find(uint16_t tlv_type, const uint8_t *tlv, uint16_t size)
+{
+ struct mrvl_ie_header *tlv_h;
+ uint16_t length;
+ ssize_t pos = 0;
+
+ while (pos < size) {
+ tlv_h = (struct mrvl_ie_header *) tlv;
+ if (!tlv_h->len)
+ return NULL;
+ if (tlv_h->type == cpu_to_le16(tlv_type))
+ return tlv_h;
+ length = le16_to_cpu(tlv_h->len) + sizeof(*tlv_h);
+ pos += length;
+ tlv += length;
+ }
+ return NULL;
+}
+
+
+static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask,
+ struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct cmd_ds_802_11_subscribe_event *subscribed;
+ struct mrvl_ie_thresholds *got;
+ struct lbs_private *priv = file->private_data;
+ ssize_t ret = 0;
+ size_t pos = 0;
+ char *buf;
+ u8 value;
+ u8 freq;
+ int events = 0;
+
+ buf = (char *)get_zeroed_page(GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ subscribed = kzalloc(sizeof(*subscribed), GFP_KERNEL);
+ if (!subscribed) {
+ ret = -ENOMEM;
+ goto out_page;
+ }
+
+ subscribed->hdr.size = cpu_to_le16(sizeof(*subscribed));
+ subscribed->action = cpu_to_le16(CMD_ACT_GET);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SUBSCRIBE_EVENT, subscribed);
+ if (ret)
+ goto out_cmd;
+
+ got = lbs_tlv_find(tlv_type, subscribed->tlv, sizeof(subscribed->tlv));
+ if (got) {
+ value = got->value;
+ freq = got->freq;
+ events = le16_to_cpu(subscribed->events);
+
+ pos += snprintf(buf, len, "%d %d %d\n", value, freq,
+ !!(events & event_mask));
+ }
+
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+
+ out_cmd:
+ kfree(subscribed);
+
+ out_page:
+ free_page((unsigned long)buf);
+ return ret;
+}
+
+
+static ssize_t lbs_threshold_write(uint16_t tlv_type, uint16_t event_mask,
+ struct file *file,
+ const char __user *userbuf, size_t count,
+ loff_t *ppos)
+{
+ struct cmd_ds_802_11_subscribe_event *events;
+ struct mrvl_ie_thresholds *tlv;
+ struct lbs_private *priv = file->private_data;
+ int value, freq, new_mask;
+ uint16_t curr_mask;
+ char *buf;
+ int ret;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%d %d %d", &value, &freq, &new_mask);
+ if (ret != 3) {
+ ret = -EINVAL;
+ goto out_page;
+ }
+ events = kzalloc(sizeof(*events), GFP_KERNEL);
+ if (!events) {
+ ret = -ENOMEM;
+ goto out_page;
+ }
+
+ events->hdr.size = cpu_to_le16(sizeof(*events));
+ events->action = cpu_to_le16(CMD_ACT_GET);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SUBSCRIBE_EVENT, events);
+ if (ret)
+ goto out_events;
+
+ curr_mask = le16_to_cpu(events->events);
+
+ if (new_mask)
+ new_mask = curr_mask | event_mask;
+ else
+ new_mask = curr_mask & ~event_mask;
+
+ /* Now everything is set and we can send stuff down to the firmware */
+
+ tlv = (void *)events->tlv;
+
+ events->action = cpu_to_le16(CMD_ACT_SET);
+ events->events = cpu_to_le16(new_mask);
+ tlv->header.type = cpu_to_le16(tlv_type);
+ tlv->header.len = cpu_to_le16(sizeof(*tlv) - sizeof(tlv->header));
+ tlv->value = value;
+ if (tlv_type != TLV_TYPE_BCNMISS)
+ tlv->freq = freq;
+
+ /* The command header, the action, the event mask, and one TLV */
+ events->hdr.size = cpu_to_le16(sizeof(events->hdr) + 4 + sizeof(*tlv));
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_SUBSCRIBE_EVENT, events);
+
+ if (!ret)
+ ret = count;
+ out_events:
+ kfree(events);
+ out_page:
+ kfree(buf);
+ return ret;
+}
+
+
+static ssize_t lbs_lowrssi_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_read(TLV_TYPE_RSSI_LOW, CMD_SUBSCRIBE_RSSI_LOW,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_lowrssi_write(struct file *file, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_write(TLV_TYPE_RSSI_LOW, CMD_SUBSCRIBE_RSSI_LOW,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_lowsnr_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_read(TLV_TYPE_SNR_LOW, CMD_SUBSCRIBE_SNR_LOW,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_lowsnr_write(struct file *file, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_write(TLV_TYPE_SNR_LOW, CMD_SUBSCRIBE_SNR_LOW,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_failcount_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_read(TLV_TYPE_FAILCOUNT, CMD_SUBSCRIBE_FAILCOUNT,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_failcount_write(struct file *file, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_write(TLV_TYPE_FAILCOUNT, CMD_SUBSCRIBE_FAILCOUNT,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_highrssi_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_read(TLV_TYPE_RSSI_HIGH, CMD_SUBSCRIBE_RSSI_HIGH,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_highrssi_write(struct file *file, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_write(TLV_TYPE_RSSI_HIGH, CMD_SUBSCRIBE_RSSI_HIGH,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_highsnr_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_read(TLV_TYPE_SNR_HIGH, CMD_SUBSCRIBE_SNR_HIGH,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_highsnr_write(struct file *file, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_write(TLV_TYPE_SNR_HIGH, CMD_SUBSCRIBE_SNR_HIGH,
+ file, userbuf, count, ppos);
+}
+
+static ssize_t lbs_bcnmiss_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_read(TLV_TYPE_BCNMISS, CMD_SUBSCRIBE_BCNMISS,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_bcnmiss_write(struct file *file, const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ return lbs_threshold_write(TLV_TYPE_BCNMISS, CMD_SUBSCRIBE_BCNMISS,
+ file, userbuf, count, ppos);
+}
+
+
+static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t pos = 0;
+ int ret;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ u32 val = 0;
+
+ if (!buf)
+ return -ENOMEM;
+
+ ret = lbs_get_reg(priv, CMD_MAC_REG_ACCESS, priv->mac_offset, &val);
+ mdelay(10);
+ if (!ret) {
+ pos = snprintf(buf, len, "MAC[0x%x] = 0x%08x\n",
+ priv->mac_offset, val);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+ }
+ free_page(addr);
+ return ret;
+}
+
+static ssize_t lbs_rdmac_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ priv->mac_offset = simple_strtoul(buf, NULL, 16);
+ kfree(buf);
+ return count;
+}
+
+static ssize_t lbs_wrmac_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+
+ struct lbs_private *priv = file->private_data;
+ ssize_t res;
+ u32 offset, value;
+ char *buf;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ res = sscanf(buf, "%x %x", &offset, &value);
+ if (res != 2) {
+ res = -EFAULT;
+ goto out_unlock;
+ }
+
+ res = lbs_set_reg(priv, CMD_MAC_REG_ACCESS, offset, value);
+ mdelay(10);
+
+ if (!res)
+ res = count;
+out_unlock:
+ kfree(buf);
+ return res;
+}
+
+static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t pos = 0;
+ int ret;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ u32 val;
+
+ if (!buf)
+ return -ENOMEM;
+
+ ret = lbs_get_reg(priv, CMD_BBP_REG_ACCESS, priv->bbp_offset, &val);
+ mdelay(10);
+ if (!ret) {
+ pos = snprintf(buf, len, "BBP[0x%x] = 0x%08x\n",
+ priv->bbp_offset, val);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+ }
+ free_page(addr);
+
+ return ret;
+}
+
+static ssize_t lbs_rdbbp_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ priv->bbp_offset = simple_strtoul(buf, NULL, 16);
+ kfree(buf);
+
+ return count;
+}
+
+static ssize_t lbs_wrbbp_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+
+ struct lbs_private *priv = file->private_data;
+ ssize_t res;
+ u32 offset, value;
+ char *buf;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ res = sscanf(buf, "%x %x", &offset, &value);
+ if (res != 2) {
+ res = -EFAULT;
+ goto out_unlock;
+ }
+
+ res = lbs_set_reg(priv, CMD_BBP_REG_ACCESS, offset, value);
+ mdelay(10);
+
+ if (!res)
+ res = count;
+out_unlock:
+ kfree(buf);
+ return res;
+}
+
+static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ ssize_t pos = 0;
+ int ret;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ u32 val;
+
+ if (!buf)
+ return -ENOMEM;
+
+ ret = lbs_get_reg(priv, CMD_RF_REG_ACCESS, priv->rf_offset, &val);
+ mdelay(10);
+ if (!ret) {
+ pos = snprintf(buf, len, "RF[0x%x] = 0x%08x\n",
+ priv->rf_offset, val);
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
+ }
+ free_page(addr);
+
+ return ret;
+}
+
+static ssize_t lbs_rdrf_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct lbs_private *priv = file->private_data;
+ char *buf;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ priv->rf_offset = simple_strtoul(buf, NULL, 16);
+ kfree(buf);
+ return count;
+}
+
+static ssize_t lbs_wrrf_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+
+ struct lbs_private *priv = file->private_data;
+ ssize_t res;
+ u32 offset, value;
+ char *buf;
+
+ buf = memdup_user_nul(userbuf, min(count, len - 1));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ res = sscanf(buf, "%x %x", &offset, &value);
+ if (res != 2) {
+ res = -EFAULT;
+ goto out_unlock;
+ }
+
+ res = lbs_set_reg(priv, CMD_RF_REG_ACCESS, offset, value);
+ mdelay(10);
+
+ if (!res)
+ res = count;
+out_unlock:
+ kfree(buf);
+ return res;
+}
+
+#define FOPS(fread, fwrite) { \
+ .owner = THIS_MODULE, \
+ .open = simple_open, \
+ .read = (fread), \
+ .write = (fwrite), \
+ .llseek = generic_file_llseek, \
+}
+
+struct lbs_debugfs_files {
+ const char *name;
+ umode_t perm;
+ struct file_operations fops;
+};
+
+static const struct lbs_debugfs_files debugfs_files[] = {
+ { "info", 0444, FOPS(lbs_dev_info, write_file_dummy), },
+ { "sleepparams", 0644, FOPS(lbs_sleepparams_read,
+ lbs_sleepparams_write), },
+ { "hostsleep", 0644, FOPS(lbs_host_sleep_read,
+ lbs_host_sleep_write), },
+};
+
+static const struct lbs_debugfs_files debugfs_events_files[] = {
+ {"low_rssi", 0644, FOPS(lbs_lowrssi_read,
+ lbs_lowrssi_write), },
+ {"low_snr", 0644, FOPS(lbs_lowsnr_read,
+ lbs_lowsnr_write), },
+ {"failure_count", 0644, FOPS(lbs_failcount_read,
+ lbs_failcount_write), },
+ {"beacon_missed", 0644, FOPS(lbs_bcnmiss_read,
+ lbs_bcnmiss_write), },
+ {"high_rssi", 0644, FOPS(lbs_highrssi_read,
+ lbs_highrssi_write), },
+ {"high_snr", 0644, FOPS(lbs_highsnr_read,
+ lbs_highsnr_write), },
+};
+
+static const struct lbs_debugfs_files debugfs_regs_files[] = {
+ {"rdmac", 0644, FOPS(lbs_rdmac_read, lbs_rdmac_write), },
+ {"wrmac", 0600, FOPS(NULL, lbs_wrmac_write), },
+ {"rdbbp", 0644, FOPS(lbs_rdbbp_read, lbs_rdbbp_write), },
+ {"wrbbp", 0600, FOPS(NULL, lbs_wrbbp_write), },
+ {"rdrf", 0644, FOPS(lbs_rdrf_read, lbs_rdrf_write), },
+ {"wrrf", 0600, FOPS(NULL, lbs_wrrf_write), },
+};
+
+void lbs_debugfs_init(void)
+{
+ if (!lbs_dir)
+ lbs_dir = debugfs_create_dir("lbs_wireless", NULL);
+}
+
+void lbs_debugfs_remove(void)
+{
+ debugfs_remove(lbs_dir);
+}
+
+void lbs_debugfs_init_one(struct lbs_private *priv, struct net_device *dev)
+{
+ int i;
+ const struct lbs_debugfs_files *files;
+ if (!lbs_dir)
+ goto exit;
+
+ priv->debugfs_dir = debugfs_create_dir(dev->name, lbs_dir);
+
+ for (i=0; i<ARRAY_SIZE(debugfs_files); i++) {
+ files = &debugfs_files[i];
+ priv->debugfs_files[i] = debugfs_create_file(files->name,
+ files->perm,
+ priv->debugfs_dir,
+ priv,
+ &files->fops);
+ }
+
+ priv->events_dir = debugfs_create_dir("subscribed_events", priv->debugfs_dir);
+
+ for (i=0; i<ARRAY_SIZE(debugfs_events_files); i++) {
+ files = &debugfs_events_files[i];
+ priv->debugfs_events_files[i] = debugfs_create_file(files->name,
+ files->perm,
+ priv->events_dir,
+ priv,
+ &files->fops);
+ }
+
+ priv->regs_dir = debugfs_create_dir("registers", priv->debugfs_dir);
+
+ for (i=0; i<ARRAY_SIZE(debugfs_regs_files); i++) {
+ files = &debugfs_regs_files[i];
+ priv->debugfs_regs_files[i] = debugfs_create_file(files->name,
+ files->perm,
+ priv->regs_dir,
+ priv,
+ &files->fops);
+ }
+
+#ifdef PROC_DEBUG
+ lbs_debug_init(priv);
+#endif
+exit:
+ return;
+}
+
+void lbs_debugfs_remove_one(struct lbs_private *priv)
+{
+ int i;
+
+ for(i=0; i<ARRAY_SIZE(debugfs_regs_files); i++)
+ debugfs_remove(priv->debugfs_regs_files[i]);
+
+ debugfs_remove(priv->regs_dir);
+
+ for(i=0; i<ARRAY_SIZE(debugfs_events_files); i++)
+ debugfs_remove(priv->debugfs_events_files[i]);
+
+ debugfs_remove(priv->events_dir);
+#ifdef PROC_DEBUG
+ debugfs_remove(priv->debugfs_debug);
+#endif
+ for(i=0; i<ARRAY_SIZE(debugfs_files); i++)
+ debugfs_remove(priv->debugfs_files[i]);
+ debugfs_remove(priv->debugfs_dir);
+}
+
+
+
+/* debug entry */
+
+#ifdef PROC_DEBUG
+
+#define item_size(n) (sizeof_field(struct lbs_private, n))
+#define item_addr(n) (offsetof(struct lbs_private, n))
+
+
+struct debug_data {
+ char name[32];
+ u32 size;
+ size_t addr;
+};
+
+/* To debug any member of struct lbs_private, simply add one line here.
+ */
+static struct debug_data items[] = {
+ {"psmode", item_size(psmode), item_addr(psmode)},
+ {"psstate", item_size(psstate), item_addr(psstate)},
+};
+
+static int num_of_items = ARRAY_SIZE(items);
+
+/**
+ * lbs_debugfs_read - proc read function
+ *
+ * @file: file to read
+ * @userbuf: pointer to buffer
+ * @count: number of bytes to read
+ * @ppos: read data starting position
+ *
+ * returns: amount of data read or negative error code
+ */
+static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ int val = 0;
+ size_t pos = 0;
+ ssize_t res;
+ char *p;
+ int i;
+ struct debug_data *d;
+ unsigned long addr = get_zeroed_page(GFP_KERNEL);
+ char *buf = (char *)addr;
+ if (!buf)
+ return -ENOMEM;
+
+ p = buf;
+
+ d = file->private_data;
+
+ for (i = 0; i < num_of_items; i++) {
+ if (d[i].size == 1)
+ val = *((u8 *) d[i].addr);
+ else if (d[i].size == 2)
+ val = *((u16 *) d[i].addr);
+ else if (d[i].size == 4)
+ val = *((u32 *) d[i].addr);
+ else if (d[i].size == 8)
+ val = *((u64 *) d[i].addr);
+
+ pos += sprintf(p + pos, "%s=%d\n", d[i].name, val);
+ }
+
+ res = simple_read_from_buffer(userbuf, count, ppos, p, pos);
+
+ free_page(addr);
+ return res;
+}
+
+/**
+ * lbs_debugfs_write - proc write function
+ *
+ * @f: file pointer
+ * @buf: pointer to data buffer
+ * @cnt: data number to write
+ * @ppos: file position
+ *
+ * returns: amount of data written
+ */
+static ssize_t lbs_debugfs_write(struct file *f, const char __user *buf,
+ size_t cnt, loff_t *ppos)
+{
+ int r, i;
+ char *pdata;
+ char *p;
+ char *p0;
+ char *p1;
+ char *p2;
+ struct debug_data *d = f->private_data;
+
+ if (cnt == 0)
+ return 0;
+
+ pdata = memdup_user_nul(buf, cnt);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+
+ p0 = pdata;
+ for (i = 0; i < num_of_items; i++) {
+ do {
+ p = strstr(p0, d[i].name);
+ if (p == NULL)
+ break;
+ p1 = strchr(p, '\n');
+ if (p1 == NULL)
+ break;
+ p0 = p1++;
+ p2 = strchr(p, '=');
+ if (!p2)
+ break;
+ p2++;
+ r = simple_strtoul(p2, NULL, 0);
+ if (d[i].size == 1)
+ *((u8 *) d[i].addr) = (u8) r;
+ else if (d[i].size == 2)
+ *((u16 *) d[i].addr) = (u16) r;
+ else if (d[i].size == 4)
+ *((u32 *) d[i].addr) = (u32) r;
+ else if (d[i].size == 8)
+ *((u64 *) d[i].addr) = (u64) r;
+ break;
+ } while (1);
+ }
+ kfree(pdata);
+
+ return (ssize_t)cnt;
+}
+
+static const struct file_operations lbs_debug_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .write = lbs_debugfs_write,
+ .read = lbs_debugfs_read,
+ .llseek = default_llseek,
+};
+
+/**
+ * lbs_debug_init - create debug proc file
+ *
+ * @priv: pointer to &struct lbs_private
+ *
+ * returns: N/A
+ */
+static void lbs_debug_init(struct lbs_private *priv)
+{
+ int i;
+
+ if (!priv->debugfs_dir)
+ return;
+
+ for (i = 0; i < num_of_items; i++)
+ items[i].addr += (size_t) priv;
+
+ priv->debugfs_debug = debugfs_create_file("debug", 0644,
+ priv->debugfs_dir, &items[0],
+ &lbs_debug_fops);
+}
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/debugfs.h b/drivers/net/wireless/marvell/libertas/debugfs.h
new file mode 100644
index 000000000..6efd1a66d
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/debugfs.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LBS_DEBUGFS_H_
+#define _LBS_DEBUGFS_H_
+
+void lbs_debugfs_init(void);
+void lbs_debugfs_remove(void);
+
+void lbs_debugfs_init_one(struct lbs_private *priv, struct net_device *dev);
+void lbs_debugfs_remove_one(struct lbs_private *priv);
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/decl.h b/drivers/net/wireless/marvell/libertas/decl.h
new file mode 100644
index 000000000..5d1e30e0c
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/decl.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * This file contains declaration referring to
+ * functions defined in other source files
+ */
+
+#ifndef _LBS_DECL_H_
+#define _LBS_DECL_H_
+
+#include <linux/netdevice.h>
+#include <linux/firmware.h>
+#include <linux/nl80211.h>
+
+/* Should be terminated by a NULL entry */
+struct lbs_fw_table {
+ int model;
+ const char *helper;
+ const char *fwname;
+};
+
+struct lbs_private;
+typedef void (*lbs_fw_cb)(struct lbs_private *priv, int ret,
+ const struct firmware *helper, const struct firmware *mainfw);
+
+struct lbs_private;
+struct sk_buff;
+struct net_device;
+struct cmd_ds_command;
+
+
+/* ethtool.c */
+extern const struct ethtool_ops lbs_ethtool_ops;
+
+
+/* tx.c */
+void lbs_send_tx_feedback(struct lbs_private *priv, u32 try_count);
+netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb,
+ struct net_device *dev);
+
+/* rx.c */
+int lbs_process_rxed_packet(struct lbs_private *priv, struct sk_buff *);
+
+
+/* main.c */
+struct lbs_private *lbs_add_card(void *card, struct device *dmdev);
+void lbs_remove_card(struct lbs_private *priv);
+int lbs_start_card(struct lbs_private *priv);
+void lbs_stop_card(struct lbs_private *priv);
+void lbs_host_to_card_done(struct lbs_private *priv);
+
+int lbs_start_iface(struct lbs_private *priv);
+int lbs_stop_iface(struct lbs_private *priv);
+int lbs_set_iface_type(struct lbs_private *priv, enum nl80211_iftype type);
+
+int lbs_rtap_supported(struct lbs_private *priv);
+
+int lbs_set_mac_address(struct net_device *dev, void *addr);
+void lbs_set_multicast_list(struct net_device *dev);
+void lbs_update_mcast(struct lbs_private *priv);
+
+int lbs_suspend(struct lbs_private *priv);
+int lbs_resume(struct lbs_private *priv);
+
+void lbs_queue_event(struct lbs_private *priv, u32 event);
+void lbs_notify_command_response(struct lbs_private *priv, u8 resp_idx);
+
+int lbs_enter_auto_deep_sleep(struct lbs_private *priv);
+int lbs_exit_auto_deep_sleep(struct lbs_private *priv);
+
+u32 lbs_fw_index_to_data_rate(u8 index);
+u8 lbs_data_rate_to_fw_index(u32 rate);
+
+int lbs_get_firmware(struct device *dev, u32 card_model,
+ const struct lbs_fw_table *fw_table,
+ const struct firmware **helper,
+ const struct firmware **mainfw);
+int lbs_get_firmware_async(struct lbs_private *priv, struct device *device,
+ u32 card_model, const struct lbs_fw_table *fw_table,
+ lbs_fw_cb callback);
+void lbs_wait_for_firmware_load(struct lbs_private *priv);
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/defs.h b/drivers/net/wireless/marvell/libertas/defs.h
new file mode 100644
index 000000000..f7e7bf56f
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/defs.h
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header file contains global constant/enum definitions,
+ * global variable declaration.
+ */
+#ifndef _LBS_DEFS_H_
+#define _LBS_DEFS_H_
+
+#include <linux/spinlock.h>
+
+#ifdef CONFIG_LIBERTAS_DEBUG
+#define DEBUG
+#define PROC_DEBUG
+#endif
+
+#ifndef DRV_NAME
+#define DRV_NAME "libertas"
+#endif
+
+
+#define LBS_DEB_ENTER 0x00000001
+#define LBS_DEB_LEAVE 0x00000002
+#define LBS_DEB_MAIN 0x00000004
+#define LBS_DEB_NET 0x00000008
+#define LBS_DEB_MESH 0x00000010
+#define LBS_DEB_WEXT 0x00000020
+#define LBS_DEB_IOCTL 0x00000040
+#define LBS_DEB_SCAN 0x00000080
+#define LBS_DEB_ASSOC 0x00000100
+#define LBS_DEB_JOIN 0x00000200
+#define LBS_DEB_11D 0x00000400
+#define LBS_DEB_DEBUGFS 0x00000800
+#define LBS_DEB_ETHTOOL 0x00001000
+#define LBS_DEB_HOST 0x00002000
+#define LBS_DEB_CMD 0x00004000
+#define LBS_DEB_RX 0x00008000
+#define LBS_DEB_TX 0x00010000
+#define LBS_DEB_USB 0x00020000
+#define LBS_DEB_CS 0x00040000
+#define LBS_DEB_FW 0x00080000
+#define LBS_DEB_THREAD 0x00100000
+#define LBS_DEB_HEX 0x00200000
+#define LBS_DEB_SDIO 0x00400000
+#define LBS_DEB_SYSFS 0x00800000
+#define LBS_DEB_SPI 0x01000000
+#define LBS_DEB_CFG80211 0x02000000
+
+extern unsigned int lbs_debug;
+
+#ifdef DEBUG
+#define LBS_DEB_LL(grp, grpnam, fmt, args...) \
+do { if ((lbs_debug & (grp)) == (grp)) \
+ printk(KERN_DEBUG DRV_NAME grpnam ": " fmt, ## args); } while (0)
+#else
+#define LBS_DEB_LL(grp, grpnam, fmt, args...) do {} while (0)
+#endif
+
+#define lbs_deb_main(fmt, args...) LBS_DEB_LL(LBS_DEB_MAIN, " main", fmt, ##args)
+#define lbs_deb_net(fmt, args...) LBS_DEB_LL(LBS_DEB_NET, " net", fmt, ##args)
+#define lbs_deb_mesh(fmt, args...) LBS_DEB_LL(LBS_DEB_MESH, " mesh", fmt, ##args)
+#define lbs_deb_wext(fmt, args...) LBS_DEB_LL(LBS_DEB_WEXT, " wext", fmt, ##args)
+#define lbs_deb_ioctl(fmt, args...) LBS_DEB_LL(LBS_DEB_IOCTL, " ioctl", fmt, ##args)
+#define lbs_deb_scan(fmt, args...) LBS_DEB_LL(LBS_DEB_SCAN, " scan", fmt, ##args)
+#define lbs_deb_assoc(fmt, args...) LBS_DEB_LL(LBS_DEB_ASSOC, " assoc", fmt, ##args)
+#define lbs_deb_join(fmt, args...) LBS_DEB_LL(LBS_DEB_JOIN, " join", fmt, ##args)
+#define lbs_deb_11d(fmt, args...) LBS_DEB_LL(LBS_DEB_11D, " 11d", fmt, ##args)
+#define lbs_deb_debugfs(fmt, args...) LBS_DEB_LL(LBS_DEB_DEBUGFS, " debugfs", fmt, ##args)
+#define lbs_deb_ethtool(fmt, args...) LBS_DEB_LL(LBS_DEB_ETHTOOL, " ethtool", fmt, ##args)
+#define lbs_deb_host(fmt, args...) LBS_DEB_LL(LBS_DEB_HOST, " host", fmt, ##args)
+#define lbs_deb_cmd(fmt, args...) LBS_DEB_LL(LBS_DEB_CMD, " cmd", fmt, ##args)
+#define lbs_deb_rx(fmt, args...) LBS_DEB_LL(LBS_DEB_RX, " rx", fmt, ##args)
+#define lbs_deb_tx(fmt, args...) LBS_DEB_LL(LBS_DEB_TX, " tx", fmt, ##args)
+#define lbs_deb_fw(fmt, args...) LBS_DEB_LL(LBS_DEB_FW, " fw", fmt, ##args)
+#define lbs_deb_usb(fmt, args...) LBS_DEB_LL(LBS_DEB_USB, " usb", fmt, ##args)
+#define lbs_deb_usbd(dev, fmt, args...) LBS_DEB_LL(LBS_DEB_USB, " usbd", "%s:" fmt, dev_name(dev), ##args)
+#define lbs_deb_cs(fmt, args...) LBS_DEB_LL(LBS_DEB_CS, " cs", fmt, ##args)
+#define lbs_deb_thread(fmt, args...) LBS_DEB_LL(LBS_DEB_THREAD, " thread", fmt, ##args)
+#define lbs_deb_sdio(fmt, args...) LBS_DEB_LL(LBS_DEB_SDIO, " sdio", fmt, ##args)
+#define lbs_deb_sysfs(fmt, args...) LBS_DEB_LL(LBS_DEB_SYSFS, " sysfs", fmt, ##args)
+#define lbs_deb_spi(fmt, args...) LBS_DEB_LL(LBS_DEB_SPI, " spi", fmt, ##args)
+#define lbs_deb_cfg80211(fmt, args...) LBS_DEB_LL(LBS_DEB_CFG80211, " cfg80211", fmt, ##args)
+
+#ifdef DEBUG
+static inline void lbs_deb_hex(unsigned int grp, const char *prompt,
+ const u8 *buf, int len)
+{
+ int i = 0;
+
+ if (len &&
+ (lbs_debug & LBS_DEB_HEX) &&
+ (lbs_debug & grp))
+ {
+ for (i = 1; i <= len; i++) {
+ if ((i & 0xf) == 1) {
+ if (i != 1)
+ printk("\n");
+ printk(DRV_NAME " %s: ", prompt);
+ }
+ printk("%02x ", (u8) * buf);
+ buf++;
+ }
+ printk("\n");
+ }
+}
+#else
+#define lbs_deb_hex(grp,prompt,buf,len) do {} while (0)
+#endif
+
+
+
+/* Buffer Constants */
+
+/* The size of SQ memory PPA, DPA are 8 DWORDs, that keep the physical
+ * addresses of TxPD buffers. Station has only 8 TxPD available, Whereas
+ * driver has more local TxPDs. Each TxPD on the host memory is associated
+ * with a Tx control node. The driver maintains 8 RxPD descriptors for
+ * station firmware to store Rx packet information.
+ *
+ * Current version of MAC has a 32x6 multicast address buffer.
+ *
+ * 802.11b can have up to 14 channels, the driver keeps the
+ * BSSID(MAC address) of each APs or Ad hoc stations it has sensed.
+ */
+
+#define MRVDRV_MAX_MULTICAST_LIST_SIZE 32
+#define LBS_NUM_CMD_BUFFERS 10
+#define LBS_CMD_BUFFER_SIZE (2 * 1024)
+#define MRVDRV_MAX_CHANNEL_SIZE 14
+#define MRVDRV_ASSOCIATION_TIME_OUT 255
+#define MRVDRV_SNAP_HEADER_LEN 8
+
+#define LBS_UPLD_SIZE 2312
+#define DEV_NAME_LEN 32
+
+/* Wake criteria for HOST_SLEEP_CFG command */
+#define EHS_WAKE_ON_BROADCAST_DATA 0x0001
+#define EHS_WAKE_ON_UNICAST_DATA 0x0002
+#define EHS_WAKE_ON_MAC_EVENT 0x0004
+#define EHS_WAKE_ON_MULTICAST_DATA 0x0008
+#define EHS_REMOVE_WAKEUP 0xFFFFFFFF
+/* Wake rules for Host_Sleep_CFG command */
+#define WOL_RULE_NET_TYPE_INFRA_OR_IBSS 0x00
+#define WOL_RULE_NET_TYPE_MESH 0x10
+#define WOL_RULE_ADDR_TYPE_BCAST 0x01
+#define WOL_RULE_ADDR_TYPE_MCAST 0x08
+#define WOL_RULE_ADDR_TYPE_UCAST 0x02
+#define WOL_RULE_OP_AND 0x01
+#define WOL_RULE_OP_OR 0x02
+#define WOL_RULE_OP_INVALID 0xFF
+#define WOL_RESULT_VALID_CMD 0
+#define WOL_RESULT_NOSPC_ERR 1
+#define WOL_RESULT_EEXIST_ERR 2
+
+/* Misc constants */
+/* This section defines 802.11 specific contants */
+
+#define MRVDRV_MAX_BSS_DESCRIPTS 16
+#define MRVDRV_MAX_REGION_CODE 6
+
+#define MRVDRV_DEFAULT_LISTEN_INTERVAL 10
+
+#define MRVDRV_CHANNELS_PER_SCAN 4
+#define MRVDRV_MAX_CHANNELS_PER_SCAN 14
+
+#define MRVDRV_MIN_BEACON_INTERVAL 20
+#define MRVDRV_MAX_BEACON_INTERVAL 1000
+#define MRVDRV_BEACON_INTERVAL 100
+
+#define MARVELL_MESH_IE_LENGTH 9
+
+/*
+ * Values used to populate the struct mrvl_mesh_ie. The only time you need this
+ * is when enabling the mesh using CMD_MESH_CONFIG.
+ */
+#define MARVELL_MESH_IE_TYPE 4
+#define MARVELL_MESH_IE_SUBTYPE 0
+#define MARVELL_MESH_IE_VERSION 0
+#define MARVELL_MESH_PROTO_ID_HWMP 0
+#define MARVELL_MESH_METRIC_ID 0
+#define MARVELL_MESH_CAPABILITY 0
+
+/* INT status Bit Definition */
+#define MRVDRV_TX_DNLD_RDY 0x0001
+#define MRVDRV_RX_UPLD_RDY 0x0002
+#define MRVDRV_CMD_DNLD_RDY 0x0004
+#define MRVDRV_CMD_UPLD_RDY 0x0008
+#define MRVDRV_CARDEVENT 0x0010
+
+/* Automatic TX control default levels */
+#define POW_ADAPT_DEFAULT_P0 13
+#define POW_ADAPT_DEFAULT_P1 15
+#define POW_ADAPT_DEFAULT_P2 18
+#define TPC_DEFAULT_P0 5
+#define TPC_DEFAULT_P1 10
+#define TPC_DEFAULT_P2 13
+
+/* TxPD status */
+
+/*
+ * Station firmware use TxPD status field to report final Tx transmit
+ * result, Bit masks are used to present combined situations.
+ */
+
+#define MRVDRV_TxPD_POWER_MGMT_NULL_PACKET 0x01
+#define MRVDRV_TxPD_POWER_MGMT_LAST_PACKET 0x08
+
+/* Tx mesh flag */
+/*
+ * Currently we are using normal WDS flag as mesh flag.
+ * TODO: change to proper mesh flag when MAC understands it.
+ */
+#define TxPD_CONTROL_WDS_FRAME (1<<17)
+#define TxPD_MESH_FRAME TxPD_CONTROL_WDS_FRAME
+
+/* Mesh interface ID */
+#define MESH_IFACE_ID 0x0001
+/* Mesh id should be in bits 14-13-12 */
+#define MESH_IFACE_BIT_OFFSET 0x000c
+/* Mesh enable bit in FW capability */
+#define MESH_CAPINFO_ENABLE_MASK (1<<16)
+
+/* FW definition from Marvell v4 */
+#define MRVL_FW_V4 (0x04)
+/* FW definition from Marvell v5 */
+#define MRVL_FW_V5 (0x05)
+/* FW definition from Marvell v10 */
+#define MRVL_FW_V10 (0x0a)
+/* FW major revision definition */
+#define MRVL_FW_MAJOR_REV(x) ((x)>>24)
+
+/* RxPD status */
+
+#define MRVDRV_RXPD_STATUS_OK 0x0001
+
+/* RxPD status - Received packet types */
+/* Rx mesh flag */
+/*
+ * Currently we are using normal WDS flag as mesh flag.
+ * TODO: change to proper mesh flag when MAC understands it.
+ */
+#define RxPD_CONTROL_WDS_FRAME (0x40)
+#define RxPD_MESH_FRAME RxPD_CONTROL_WDS_FRAME
+
+/* RSSI-related defines */
+/*
+ * RSSI constants are used to implement 802.11 RSSI threshold
+ * indication. if the Rx packet signal got too weak for 5 consecutive
+ * times, miniport driver (driver) will report this event to wrapper
+ */
+
+#define MRVDRV_NF_DEFAULT_SCAN_VALUE (-96)
+
+/* RTS/FRAG related defines */
+#define MRVDRV_RTS_MIN_VALUE 0
+#define MRVDRV_RTS_MAX_VALUE 2347
+#define MRVDRV_FRAG_MIN_VALUE 256
+#define MRVDRV_FRAG_MAX_VALUE 2346
+
+/* This is for firmware specific length */
+#define EXTRA_LEN 36
+
+#define MRVDRV_ETH_TX_PACKET_BUFFER_SIZE \
+ (ETH_FRAME_LEN + sizeof(struct txpd) + EXTRA_LEN)
+
+#define MRVDRV_ETH_RX_PACKET_BUFFER_SIZE \
+ (ETH_FRAME_LEN + sizeof(struct rxpd) \
+ + MRVDRV_SNAP_HEADER_LEN + EXTRA_LEN)
+
+#define CMD_F_HOSTCMD (1 << 0)
+#define FW_CAPINFO_WPA (1 << 0)
+#define FW_CAPINFO_PS (1 << 1)
+#define FW_CAPINFO_FIRMWARE_UPGRADE (1 << 13)
+#define FW_CAPINFO_BOOT2_UPGRADE (1<<14)
+#define FW_CAPINFO_PERSISTENT_CONFIG (1<<15)
+
+#define KEY_LEN_WPA_AES 16
+#define KEY_LEN_WPA_TKIP 32
+#define KEY_LEN_WEP_104 13
+#define KEY_LEN_WEP_40 5
+
+#define RF_ANTENNA_1 0x1
+#define RF_ANTENNA_2 0x2
+#define RF_ANTENNA_AUTO 0xFFFF
+
+#define BAND_B (0x01)
+#define BAND_G (0x02)
+#define ALL_802_11_BANDS (BAND_B | BAND_G)
+
+#define MAX_RATES 14
+
+#define MAX_LEDS 8
+
+/* Global Variable Declaration */
+extern const char lbs_driver_version[];
+extern u16 lbs_region_code_to_index[MRVDRV_MAX_REGION_CODE];
+
+
+/* ENUM definition */
+/* SNRNF_TYPE */
+enum SNRNF_TYPE {
+ TYPE_BEACON = 0,
+ TYPE_RXPD,
+ MAX_TYPE_B
+};
+
+/* SNRNF_DATA */
+enum SNRNF_DATA {
+ TYPE_NOAVG = 0,
+ TYPE_AVG,
+ MAX_TYPE_AVG
+};
+
+/* LBS_802_11_POWER_MODE */
+enum LBS_802_11_POWER_MODE {
+ LBS802_11POWERMODECAM,
+ LBS802_11POWERMODEMAX_PSP,
+ LBS802_11POWERMODEFAST_PSP,
+ /* not a real mode, defined as an upper bound */
+ LBS802_11POWEMODEMAX
+};
+
+/* PS_STATE */
+enum PS_STATE {
+ PS_STATE_FULL_POWER,
+ PS_STATE_AWAKE,
+ PS_STATE_PRE_SLEEP,
+ PS_STATE_SLEEP
+};
+
+/* DNLD_STATE */
+enum DNLD_STATE {
+ DNLD_RES_RECEIVED,
+ DNLD_DATA_SENT,
+ DNLD_CMD_SENT,
+ DNLD_BOOTCMD_SENT,
+};
+
+/* LBS_MEDIA_STATE */
+enum LBS_MEDIA_STATE {
+ LBS_CONNECTED,
+ LBS_DISCONNECTED
+};
+
+/* LBS_802_11_PRIVACY_FILTER */
+enum LBS_802_11_PRIVACY_FILTER {
+ LBS802_11PRIVFILTERACCEPTALL,
+ LBS802_11PRIVFILTER8021XWEP
+};
+
+/* mv_ms_type */
+enum mv_ms_type {
+ MVMS_DAT = 0,
+ MVMS_CMD = 1,
+ MVMS_TXDONE = 2,
+ MVMS_EVENT
+};
+
+/* KEY_TYPE_ID */
+enum KEY_TYPE_ID {
+ KEY_TYPE_ID_WEP = 0,
+ KEY_TYPE_ID_TKIP,
+ KEY_TYPE_ID_AES
+};
+
+/* KEY_INFO_WPA (applies to both TKIP and AES/CCMP) */
+enum KEY_INFO_WPA {
+ KEY_INFO_WPA_MCAST = 0x01,
+ KEY_INFO_WPA_UNICAST = 0x02,
+ KEY_INFO_WPA_ENABLED = 0x04
+};
+
+/* Default values for fwt commands. */
+#define FWT_DEFAULT_METRIC 0
+#define FWT_DEFAULT_DIR 1
+/* Default Rate, 11Mbps */
+#define FWT_DEFAULT_RATE 3
+#define FWT_DEFAULT_SSN 0xffffffff
+#define FWT_DEFAULT_DSN 0
+#define FWT_DEFAULT_HOPCOUNT 0
+#define FWT_DEFAULT_TTL 0
+#define FWT_DEFAULT_EXPIRATION 0
+#define FWT_DEFAULT_SLEEPMODE 0
+#define FWT_DEFAULT_SNR 0
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/dev.h b/drivers/net/wireless/marvell/libertas/dev.h
new file mode 100644
index 000000000..4b6e05a8e
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/dev.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file contains definitions and data structures specific
+ * to Marvell 802.11 NIC. It contains the Device Information
+ * structure struct lbs_private..
+ */
+#ifndef _LBS_DEV_H_
+#define _LBS_DEV_H_
+
+#include "defs.h"
+#include "decl.h"
+#include "host.h"
+
+#include <linux/kfifo.h>
+
+/* sleep_params */
+struct sleep_params {
+ uint16_t sp_error;
+ uint16_t sp_offset;
+ uint16_t sp_stabletime;
+ uint8_t sp_calcontrol;
+ uint8_t sp_extsleepclk;
+ uint16_t sp_reserved;
+};
+
+/* Mesh statistics */
+struct lbs_mesh_stats {
+ u32 fwd_bcast_cnt; /* Fwd: Broadcast counter */
+ u32 fwd_unicast_cnt; /* Fwd: Unicast counter */
+ u32 fwd_drop_ttl; /* Fwd: TTL zero */
+ u32 fwd_drop_rbt; /* Fwd: Recently Broadcasted */
+ u32 fwd_drop_noroute; /* Fwd: No route to Destination */
+ u32 fwd_drop_nobuf; /* Fwd: Run out of internal buffers */
+ u32 drop_blind; /* Rx: Dropped by blinding table */
+ u32 tx_failed_cnt; /* Tx: Failed transmissions */
+};
+
+/* Private structure for the MV device */
+struct lbs_private {
+
+ /* Basic networking */
+ struct net_device *dev;
+ u32 connect_status;
+ struct work_struct mcast_work;
+ u32 nr_of_multicastmacaddr;
+ u8 multicastlist[MRVDRV_MAX_MULTICAST_LIST_SIZE][ETH_ALEN];
+
+ /* CFG80211 */
+ struct wireless_dev *wdev;
+ bool wiphy_registered;
+ struct cfg80211_scan_request *scan_req;
+ u8 assoc_bss[ETH_ALEN];
+ u8 country_code[IEEE80211_COUNTRY_STRING_LEN];
+ u8 disassoc_reason;
+
+ /* Mesh */
+ struct net_device *mesh_dev; /* Virtual device */
+#ifdef CONFIG_LIBERTAS_MESH
+ struct lbs_mesh_stats mstats;
+ uint16_t mesh_tlv;
+ u8 mesh_channel;
+#endif
+
+ /* Debugfs */
+ struct dentry *debugfs_dir;
+ struct dentry *debugfs_debug;
+ struct dentry *debugfs_files[6];
+ struct dentry *events_dir;
+ struct dentry *debugfs_events_files[6];
+ struct dentry *regs_dir;
+ struct dentry *debugfs_regs_files[6];
+
+ /* Hardware debugging */
+ u32 mac_offset;
+ u32 bbp_offset;
+ u32 rf_offset;
+
+ /* Power management */
+ u16 psmode;
+ u32 psstate;
+ u8 needtowakeup;
+
+ /* Deep sleep */
+ int is_deep_sleep;
+ int deep_sleep_required;
+ int is_auto_deep_sleep_enabled;
+ int wakeup_dev_required;
+ int is_activity_detected;
+ int auto_deep_sleep_timeout; /* in ms */
+ wait_queue_head_t ds_awake_q;
+ struct timer_list auto_deepsleep_timer;
+
+ /* Host sleep*/
+ int is_host_sleep_configured;
+ int is_host_sleep_activated;
+ wait_queue_head_t host_sleep_q;
+
+ /* Hardware access */
+ void *card;
+ bool iface_running;
+ u8 is_polling; /* host has to poll the card irq */
+ u8 fw_ready;
+ u8 surpriseremoved;
+ u8 setup_fw_on_resume;
+ u8 power_up_on_resume;
+ int (*hw_host_to_card) (struct lbs_private *priv, u8 type, u8 *payload, u16 nb);
+ void (*reset_card) (struct lbs_private *priv);
+ int (*power_save) (struct lbs_private *priv);
+ int (*power_restore) (struct lbs_private *priv);
+ int (*enter_deep_sleep) (struct lbs_private *priv);
+ int (*exit_deep_sleep) (struct lbs_private *priv);
+ int (*reset_deep_sleep_wakeup) (struct lbs_private *priv);
+
+ /* Adapter info (from EEPROM) */
+ u32 fwrelease;
+ u32 fwcapinfo;
+ u16 regioncode;
+ u8 current_addr[ETH_ALEN];
+ u8 copied_hwaddr;
+
+ /* Command download */
+ u8 dnld_sent;
+ /* bit0 1/0=data_sent/data_tx_done,
+ bit1 1/0=cmd_sent/cmd_tx_done,
+ all other bits reserved 0 */
+ u16 seqnum;
+ struct cmd_ctrl_node *cmd_array;
+ struct cmd_ctrl_node *cur_cmd;
+ struct list_head cmdfreeq; /* free command buffers */
+ struct list_head cmdpendingq; /* pending command buffers */
+ struct timer_list command_timer;
+ int cmd_timed_out;
+
+ /* Command responses sent from the hardware to the driver */
+ u8 resp_idx;
+ u8 resp_buf[2][LBS_UPLD_SIZE];
+ u32 resp_len[2];
+
+ /* Events sent from hardware to driver */
+ struct kfifo event_fifo;
+
+ /* thread to service interrupts */
+ struct task_struct *main_thread;
+ wait_queue_head_t waitq;
+ struct workqueue_struct *work_thread;
+
+ /* Encryption stuff */
+ u8 authtype_auto;
+ u8 wep_tx_key;
+ u8 wep_key[4][WLAN_KEY_LEN_WEP104];
+ u8 wep_key_len[4];
+
+ /* Wake On LAN */
+ uint32_t wol_criteria;
+ uint8_t wol_gpio;
+ uint8_t wol_gap;
+ bool ehs_remove_supported;
+
+ /* Transmitting */
+ int tx_pending_len; /* -1 while building packet */
+ u8 tx_pending_buf[LBS_UPLD_SIZE];
+ /* protected by hard_start_xmit serialization */
+ u8 txretrycount;
+ struct sk_buff *currenttxskb;
+ struct timer_list tx_lockup_timer;
+
+ /* Locks */
+ struct mutex lock;
+ spinlock_t driver_lock;
+
+ /* NIC/link operation characteristics */
+ u16 mac_control;
+ u8 radio_on;
+ u8 cur_rate;
+ u8 channel;
+ s16 txpower_cur;
+ s16 txpower_min;
+ s16 txpower_max;
+
+ /* Scanning */
+ struct delayed_work scan_work;
+ int scan_channel;
+ /* Queue of things waiting for scan completion */
+ wait_queue_head_t scan_q;
+ /* Whether the scan was initiated internally and not by cfg80211 */
+ bool internal_scan;
+
+ /* Firmware load */
+ u32 fw_model;
+ wait_queue_head_t fw_waitq;
+ struct device *fw_device;
+ const struct firmware *helper_fw;
+ const struct lbs_fw_table *fw_table;
+ const struct lbs_fw_table *fw_iter;
+ lbs_fw_cb fw_callback;
+};
+
+extern struct cmd_confirm_sleep confirm_sleep;
+
+/* Check if there is an interface active. */
+static inline int lbs_iface_active(struct lbs_private *priv)
+{
+ int r;
+
+ r = netif_running(priv->dev);
+ if (priv->mesh_dev)
+ r |= netif_running(priv->mesh_dev);
+
+ return r;
+}
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/ethtool.c b/drivers/net/wireless/marvell/libertas/ethtool.c
new file mode 100644
index 000000000..1bb8746a0
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/ethtool.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/hardirq.h>
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+#include <linux/delay.h>
+
+#include "decl.h"
+#include "cmd.h"
+#include "mesh.h"
+
+
+static void lbs_ethtool_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ snprintf(info->fw_version, sizeof(info->fw_version),
+ "%u.%u.%u.p%u",
+ priv->fwrelease >> 24 & 0xff,
+ priv->fwrelease >> 16 & 0xff,
+ priv->fwrelease >> 8 & 0xff,
+ priv->fwrelease & 0xff);
+ strlcpy(info->driver, "libertas", sizeof(info->driver));
+ strlcpy(info->version, lbs_driver_version, sizeof(info->version));
+}
+
+/*
+ * All 8388 parts have 16KiB EEPROM size at the time of writing.
+ * In case that changes this needs fixing.
+ */
+#define LBS_EEPROM_LEN 16384
+
+static int lbs_ethtool_get_eeprom_len(struct net_device *dev)
+{
+ return LBS_EEPROM_LEN;
+}
+
+static int lbs_ethtool_get_eeprom(struct net_device *dev,
+ struct ethtool_eeprom *eeprom, u8 * bytes)
+{
+ struct lbs_private *priv = dev->ml_priv;
+ struct cmd_ds_802_11_eeprom_access cmd;
+ int ret;
+
+ if (eeprom->offset + eeprom->len > LBS_EEPROM_LEN ||
+ eeprom->len > LBS_EEPROM_READ_LEN) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ cmd.hdr.size = cpu_to_le16(sizeof(struct cmd_ds_802_11_eeprom_access) -
+ LBS_EEPROM_READ_LEN + eeprom->len);
+ cmd.action = cpu_to_le16(CMD_ACT_GET);
+ cmd.offset = cpu_to_le16(eeprom->offset);
+ cmd.len = cpu_to_le16(eeprom->len);
+ ret = lbs_cmd_with_response(priv, CMD_802_11_EEPROM_ACCESS, &cmd);
+ if (!ret)
+ memcpy(bytes, cmd.value, eeprom->len);
+
+out:
+ return ret;
+}
+
+static void lbs_ethtool_get_wol(struct net_device *dev,
+ struct ethtool_wolinfo *wol)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ wol->supported = WAKE_UCAST|WAKE_MCAST|WAKE_BCAST|WAKE_PHY;
+
+ if (priv->wol_criteria == EHS_REMOVE_WAKEUP)
+ return;
+
+ if (priv->wol_criteria & EHS_WAKE_ON_UNICAST_DATA)
+ wol->wolopts |= WAKE_UCAST;
+ if (priv->wol_criteria & EHS_WAKE_ON_MULTICAST_DATA)
+ wol->wolopts |= WAKE_MCAST;
+ if (priv->wol_criteria & EHS_WAKE_ON_BROADCAST_DATA)
+ wol->wolopts |= WAKE_BCAST;
+ if (priv->wol_criteria & EHS_WAKE_ON_MAC_EVENT)
+ wol->wolopts |= WAKE_PHY;
+}
+
+static int lbs_ethtool_set_wol(struct net_device *dev,
+ struct ethtool_wolinfo *wol)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ if (wol->wolopts & ~(WAKE_UCAST|WAKE_MCAST|WAKE_BCAST|WAKE_PHY))
+ return -EOPNOTSUPP;
+
+ priv->wol_criteria = 0;
+ if (wol->wolopts & WAKE_UCAST)
+ priv->wol_criteria |= EHS_WAKE_ON_UNICAST_DATA;
+ if (wol->wolopts & WAKE_MCAST)
+ priv->wol_criteria |= EHS_WAKE_ON_MULTICAST_DATA;
+ if (wol->wolopts & WAKE_BCAST)
+ priv->wol_criteria |= EHS_WAKE_ON_BROADCAST_DATA;
+ if (wol->wolopts & WAKE_PHY)
+ priv->wol_criteria |= EHS_WAKE_ON_MAC_EVENT;
+ if (wol->wolopts == 0)
+ priv->wol_criteria |= EHS_REMOVE_WAKEUP;
+ return 0;
+}
+
+const struct ethtool_ops lbs_ethtool_ops = {
+ .get_drvinfo = lbs_ethtool_get_drvinfo,
+ .get_eeprom = lbs_ethtool_get_eeprom,
+ .get_eeprom_len = lbs_ethtool_get_eeprom_len,
+#ifdef CONFIG_LIBERTAS_MESH
+ .get_sset_count = lbs_mesh_ethtool_get_sset_count,
+ .get_ethtool_stats = lbs_mesh_ethtool_get_stats,
+ .get_strings = lbs_mesh_ethtool_get_strings,
+#endif
+ .get_wol = lbs_ethtool_get_wol,
+ .set_wol = lbs_ethtool_set_wol,
+};
+
diff --git a/drivers/net/wireless/marvell/libertas/firmware.c b/drivers/net/wireless/marvell/libertas/firmware.c
new file mode 100644
index 000000000..f12411094
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/firmware.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Firmware loading and handling functions.
+ */
+
+#include <linux/sched.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+
+#include "dev.h"
+#include "decl.h"
+
+static void load_next_firmware_from_table(struct lbs_private *private);
+
+static void lbs_fw_loaded(struct lbs_private *priv, int ret,
+ const struct firmware *helper, const struct firmware *mainfw)
+{
+ unsigned long flags;
+
+ lbs_deb_fw("firmware load complete, code %d\n", ret);
+
+ /* User must free helper/mainfw */
+ priv->fw_callback(priv, ret, helper, mainfw);
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ priv->fw_callback = NULL;
+ wake_up(&priv->fw_waitq);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+
+static void do_load_firmware(struct lbs_private *priv, const char *name,
+ void (*cb)(const struct firmware *fw, void *context))
+{
+ int ret;
+
+ lbs_deb_fw("Requesting %s\n", name);
+ ret = request_firmware_nowait(THIS_MODULE, true, name,
+ priv->fw_device, GFP_KERNEL, priv, cb);
+ if (ret) {
+ lbs_deb_fw("request_firmware_nowait error %d\n", ret);
+ lbs_fw_loaded(priv, ret, NULL, NULL);
+ }
+}
+
+static void main_firmware_cb(const struct firmware *firmware, void *context)
+{
+ struct lbs_private *priv = context;
+
+ if (!firmware) {
+ /* Failed to find firmware: try next table entry */
+ load_next_firmware_from_table(priv);
+ return;
+ }
+
+ /* Firmware found! */
+ lbs_fw_loaded(priv, 0, priv->helper_fw, firmware);
+ if (priv->helper_fw) {
+ release_firmware (priv->helper_fw);
+ priv->helper_fw = NULL;
+ }
+ release_firmware (firmware);
+}
+
+static void helper_firmware_cb(const struct firmware *firmware, void *context)
+{
+ struct lbs_private *priv = context;
+
+ if (!firmware) {
+ /* Failed to find firmware: try next table entry */
+ load_next_firmware_from_table(priv);
+ return;
+ }
+
+ /* Firmware found! */
+ if (priv->fw_iter->fwname) {
+ priv->helper_fw = firmware;
+ do_load_firmware(priv, priv->fw_iter->fwname, main_firmware_cb);
+ } else {
+ /* No main firmware needed for this helper --> success! */
+ lbs_fw_loaded(priv, 0, firmware, NULL);
+ }
+}
+
+static void load_next_firmware_from_table(struct lbs_private *priv)
+{
+ const struct lbs_fw_table *iter;
+
+ if (!priv->fw_iter)
+ iter = priv->fw_table;
+ else
+ iter = ++priv->fw_iter;
+
+ if (priv->helper_fw) {
+ release_firmware(priv->helper_fw);
+ priv->helper_fw = NULL;
+ }
+
+next:
+ if (!iter->helper) {
+ /* End of table hit. */
+ lbs_fw_loaded(priv, -ENOENT, NULL, NULL);
+ return;
+ }
+
+ if (iter->model != priv->fw_model) {
+ iter++;
+ goto next;
+ }
+
+ priv->fw_iter = iter;
+ do_load_firmware(priv, iter->helper, helper_firmware_cb);
+}
+
+void lbs_wait_for_firmware_load(struct lbs_private *priv)
+{
+ wait_event(priv->fw_waitq, priv->fw_callback == NULL);
+}
+
+/**
+ * lbs_get_firmware_async - Retrieves firmware asynchronously. Can load
+ * either a helper firmware and a main firmware (2-stage), or just the helper.
+ *
+ * @priv: Pointer to lbs_private instance
+ * @device: A pointer to &device structure
+ * @card_model: Bus-specific card model ID used to filter firmware table
+ * elements
+ * @fw_table: Table of firmware file names and device model numbers
+ * terminated by an entry with a NULL helper name
+ * @callback: User callback to invoke when firmware load succeeds or fails.
+ */
+int lbs_get_firmware_async(struct lbs_private *priv, struct device *device,
+ u32 card_model, const struct lbs_fw_table *fw_table,
+ lbs_fw_cb callback)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ if (priv->fw_callback) {
+ lbs_deb_fw("firmware load already in progress\n");
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ return -EBUSY;
+ }
+
+ priv->fw_device = device;
+ priv->fw_callback = callback;
+ priv->fw_table = fw_table;
+ priv->fw_iter = NULL;
+ priv->fw_model = card_model;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ lbs_deb_fw("Starting async firmware load\n");
+ load_next_firmware_from_table(priv);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lbs_get_firmware_async);
+
+/**
+ * lbs_get_firmware - Retrieves two-stage firmware
+ *
+ * @dev: A pointer to &device structure
+ * @card_model: Bus-specific card model ID used to filter firmware table
+ * elements
+ * @fw_table: Table of firmware file names and device model numbers
+ * terminated by an entry with a NULL helper name
+ * @helper: On success, the helper firmware; caller must free
+ * @mainfw: On success, the main firmware; caller must free
+ *
+ * Deprecated: use lbs_get_firmware_async() instead.
+ *
+ * returns: 0 on success, non-zero on failure
+ */
+int lbs_get_firmware(struct device *dev, u32 card_model,
+ const struct lbs_fw_table *fw_table,
+ const struct firmware **helper,
+ const struct firmware **mainfw)
+{
+ const struct lbs_fw_table *iter;
+ int ret;
+
+ BUG_ON(helper == NULL);
+ BUG_ON(mainfw == NULL);
+
+ /* Search for firmware to use from the table. */
+ iter = fw_table;
+ while (iter && iter->helper) {
+ if (iter->model != card_model)
+ goto next;
+
+ if (*helper == NULL) {
+ ret = request_firmware(helper, iter->helper, dev);
+ if (ret)
+ goto next;
+
+ /* If the device has one-stage firmware (ie cf8305) and
+ * we've got it then we don't need to bother with the
+ * main firmware.
+ */
+ if (iter->fwname == NULL)
+ return 0;
+ }
+
+ if (*mainfw == NULL) {
+ ret = request_firmware(mainfw, iter->fwname, dev);
+ if (ret) {
+ /* Clear the helper to ensure we don't have
+ * mismatched firmware pairs.
+ */
+ release_firmware(*helper);
+ *helper = NULL;
+ }
+ }
+
+ if (*helper && *mainfw)
+ return 0;
+
+ next:
+ iter++;
+ }
+
+ /* Failed */
+ release_firmware(*helper);
+ *helper = NULL;
+ release_firmware(*mainfw);
+ *mainfw = NULL;
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(lbs_get_firmware);
diff --git a/drivers/net/wireless/marvell/libertas/host.h b/drivers/net/wireless/marvell/libertas/host.h
new file mode 100644
index 000000000..dfa22468b
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/host.h
@@ -0,0 +1,979 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file function prototypes, data structure
+ * and definitions for all the host/station commands
+ */
+
+#ifndef _LBS_HOST_H_
+#define _LBS_HOST_H_
+
+#include "types.h"
+#include "defs.h"
+
+#define DEFAULT_AD_HOC_CHANNEL 6
+
+#define CMD_OPTION_WAITFORRSP 0x0002
+
+/* Host command IDs */
+
+/*
+ * Return command are almost always the same as the host command, but with
+ * bit 15 set high. There are a few exceptions, though...
+ */
+#define CMD_RET(cmd) (0x8000 | cmd)
+
+/* Return command convention exceptions: */
+#define CMD_RET_802_11_ASSOCIATE 0x8012
+
+/* Command codes */
+#define CMD_GET_HW_SPEC 0x0003
+#define CMD_EEPROM_UPDATE 0x0004
+#define CMD_802_11_RESET 0x0005
+#define CMD_802_11_SCAN 0x0006
+#define CMD_802_11_GET_LOG 0x000b
+#define CMD_MAC_MULTICAST_ADR 0x0010
+#define CMD_802_11_AUTHENTICATE 0x0011
+#define CMD_802_11_EEPROM_ACCESS 0x0059
+#define CMD_802_11_ASSOCIATE 0x0050
+#define CMD_802_11_SET_WEP 0x0013
+#define CMD_802_11_GET_STAT 0x0014
+#define CMD_802_3_GET_STAT 0x0015
+#define CMD_802_11_SNMP_MIB 0x0016
+#define CMD_MAC_REG_MAP 0x0017
+#define CMD_BBP_REG_MAP 0x0018
+#define CMD_MAC_REG_ACCESS 0x0019
+#define CMD_BBP_REG_ACCESS 0x001a
+#define CMD_RF_REG_ACCESS 0x001b
+#define CMD_802_11_RADIO_CONTROL 0x001c
+#define CMD_802_11_RF_CHANNEL 0x001d
+#define CMD_802_11_RF_TX_POWER 0x001e
+#define CMD_802_11_RSSI 0x001f
+#define CMD_802_11_RF_ANTENNA 0x0020
+#define CMD_802_11_PS_MODE 0x0021
+#define CMD_802_11_DATA_RATE 0x0022
+#define CMD_RF_REG_MAP 0x0023
+#define CMD_802_11_DEAUTHENTICATE 0x0024
+#define CMD_802_11_REASSOCIATE 0x0025
+#define CMD_MAC_CONTROL 0x0028
+#define CMD_802_11_AD_HOC_START 0x002b
+#define CMD_802_11_AD_HOC_JOIN 0x002c
+#define CMD_802_11_QUERY_TKIP_REPLY_CNTRS 0x002e
+#define CMD_802_11_ENABLE_RSN 0x002f
+#define CMD_802_11_SET_AFC 0x003c
+#define CMD_802_11_GET_AFC 0x003d
+#define CMD_802_11_DEEP_SLEEP 0x003e
+#define CMD_802_11_AD_HOC_STOP 0x0040
+#define CMD_802_11_HOST_SLEEP_CFG 0x0043
+#define CMD_802_11_WAKEUP_CONFIRM 0x0044
+#define CMD_802_11_HOST_SLEEP_ACTIVATE 0x0045
+#define CMD_802_11_BEACON_STOP 0x0049
+#define CMD_802_11_MAC_ADDRESS 0x004d
+#define CMD_802_11_LED_GPIO_CTRL 0x004e
+#define CMD_802_11_BAND_CONFIG 0x0058
+#define CMD_GSPI_BUS_CONFIG 0x005a
+#define CMD_802_11D_DOMAIN_INFO 0x005b
+#define CMD_802_11_KEY_MATERIAL 0x005e
+#define CMD_802_11_SLEEP_PARAMS 0x0066
+#define CMD_802_11_INACTIVITY_TIMEOUT 0x0067
+#define CMD_802_11_SLEEP_PERIOD 0x0068
+#define CMD_802_11_TPC_CFG 0x0072
+#define CMD_802_11_PA_CFG 0x0073
+#define CMD_802_11_FW_WAKE_METHOD 0x0074
+#define CMD_802_11_SUBSCRIBE_EVENT 0x0075
+#define CMD_802_11_RATE_ADAPT_RATESET 0x0076
+#define CMD_802_11_TX_RATE_QUERY 0x007f
+#define CMD_GET_TSF 0x0080
+#define CMD_BT_ACCESS 0x0087
+#define CMD_FWT_ACCESS 0x0095
+#define CMD_802_11_MONITOR_MODE 0x0098
+#define CMD_MESH_ACCESS 0x009b
+#define CMD_MESH_CONFIG_OLD 0x00a3
+#define CMD_MESH_CONFIG 0x00ac
+#define CMD_SET_BOOT2_VER 0x00a5
+#define CMD_FUNC_INIT 0x00a9
+#define CMD_FUNC_SHUTDOWN 0x00aa
+#define CMD_802_11_BEACON_CTRL 0x00b0
+
+/* For the IEEE Power Save */
+#define PS_MODE_ACTION_ENTER_PS 0x0030
+#define PS_MODE_ACTION_EXIT_PS 0x0031
+#define PS_MODE_ACTION_SLEEP_CONFIRMED 0x0034
+
+#define CMD_ENABLE_RSN 0x0001
+#define CMD_DISABLE_RSN 0x0000
+
+#define CMD_ACT_GET 0x0000
+#define CMD_ACT_SET 0x0001
+
+/* Define action or option for CMD_802_11_SET_WEP */
+#define CMD_ACT_ADD 0x0002
+#define CMD_ACT_REMOVE 0x0004
+
+#define CMD_TYPE_WEP_40_BIT 0x01
+#define CMD_TYPE_WEP_104_BIT 0x02
+
+#define CMD_NUM_OF_WEP_KEYS 4
+
+#define CMD_WEP_KEY_INDEX_MASK 0x3fff
+
+/* Define action or option for CMD_802_11_SCAN */
+#define CMD_BSS_TYPE_BSS 0x0001
+#define CMD_BSS_TYPE_IBSS 0x0002
+#define CMD_BSS_TYPE_ANY 0x0003
+
+/* Define action or option for CMD_802_11_SCAN */
+#define CMD_SCAN_TYPE_ACTIVE 0x0000
+#define CMD_SCAN_TYPE_PASSIVE 0x0001
+
+#define CMD_SCAN_RADIO_TYPE_BG 0
+
+#define CMD_SCAN_PROBE_DELAY_TIME 0
+
+/* Define action or option for CMD_MAC_CONTROL */
+#define CMD_ACT_MAC_RX_ON 0x0001
+#define CMD_ACT_MAC_TX_ON 0x0002
+#define CMD_ACT_MAC_LOOPBACK_ON 0x0004
+#define CMD_ACT_MAC_WEP_ENABLE 0x0008
+#define CMD_ACT_MAC_INT_ENABLE 0x0010
+#define CMD_ACT_MAC_MULTICAST_ENABLE 0x0020
+#define CMD_ACT_MAC_BROADCAST_ENABLE 0x0040
+#define CMD_ACT_MAC_PROMISCUOUS_ENABLE 0x0080
+#define CMD_ACT_MAC_ALL_MULTICAST_ENABLE 0x0100
+#define CMD_ACT_MAC_STRICT_PROTECTION_ENABLE 0x0400
+
+/* Event flags for CMD_802_11_SUBSCRIBE_EVENT */
+#define CMD_SUBSCRIBE_RSSI_LOW 0x0001
+#define CMD_SUBSCRIBE_SNR_LOW 0x0002
+#define CMD_SUBSCRIBE_FAILCOUNT 0x0004
+#define CMD_SUBSCRIBE_BCNMISS 0x0008
+#define CMD_SUBSCRIBE_RSSI_HIGH 0x0010
+#define CMD_SUBSCRIBE_SNR_HIGH 0x0020
+
+#define RADIO_PREAMBLE_LONG 0x00
+#define RADIO_PREAMBLE_SHORT 0x02
+#define RADIO_PREAMBLE_AUTO 0x04
+
+/* Define action or option for CMD_802_11_RF_CHANNEL */
+#define CMD_OPT_802_11_RF_CHANNEL_GET 0x00
+#define CMD_OPT_802_11_RF_CHANNEL_SET 0x01
+
+/* Define action or option for CMD_802_11_DATA_RATE */
+#define CMD_ACT_SET_TX_AUTO 0x0000
+#define CMD_ACT_SET_TX_FIX_RATE 0x0001
+#define CMD_ACT_GET_TX_RATE 0x0002
+
+/* Options for CMD_802_11_FW_WAKE_METHOD */
+#define CMD_WAKE_METHOD_UNCHANGED 0x0000
+#define CMD_WAKE_METHOD_COMMAND_INT 0x0001
+#define CMD_WAKE_METHOD_GPIO 0x0002
+
+/* Object IDs for CMD_802_11_SNMP_MIB */
+#define SNMP_MIB_OID_BSS_TYPE 0x0000
+#define SNMP_MIB_OID_OP_RATE_SET 0x0001
+#define SNMP_MIB_OID_BEACON_PERIOD 0x0002 /* Reserved on v9+ */
+#define SNMP_MIB_OID_DTIM_PERIOD 0x0003 /* Reserved on v9+ */
+#define SNMP_MIB_OID_ASSOC_TIMEOUT 0x0004 /* Reserved on v9+ */
+#define SNMP_MIB_OID_RTS_THRESHOLD 0x0005
+#define SNMP_MIB_OID_SHORT_RETRY_LIMIT 0x0006
+#define SNMP_MIB_OID_LONG_RETRY_LIMIT 0x0007
+#define SNMP_MIB_OID_FRAG_THRESHOLD 0x0008
+#define SNMP_MIB_OID_11D_ENABLE 0x0009
+#define SNMP_MIB_OID_11H_ENABLE 0x000A
+
+/* Define action or option for CMD_BT_ACCESS */
+enum cmd_bt_access_opts {
+ /* The bt commands start at 5 instead of 1 because the old dft commands
+ * are mapped to 1-4. These old commands are no longer maintained and
+ * should not be called.
+ */
+ CMD_ACT_BT_ACCESS_ADD = 5,
+ CMD_ACT_BT_ACCESS_DEL,
+ CMD_ACT_BT_ACCESS_LIST,
+ CMD_ACT_BT_ACCESS_RESET,
+ CMD_ACT_BT_ACCESS_SET_INVERT,
+ CMD_ACT_BT_ACCESS_GET_INVERT
+};
+
+/* Define action or option for CMD_FWT_ACCESS */
+enum cmd_fwt_access_opts {
+ CMD_ACT_FWT_ACCESS_ADD = 1,
+ CMD_ACT_FWT_ACCESS_DEL,
+ CMD_ACT_FWT_ACCESS_LOOKUP,
+ CMD_ACT_FWT_ACCESS_LIST,
+ CMD_ACT_FWT_ACCESS_LIST_ROUTE,
+ CMD_ACT_FWT_ACCESS_LIST_NEIGHBOR,
+ CMD_ACT_FWT_ACCESS_RESET,
+ CMD_ACT_FWT_ACCESS_CLEANUP,
+ CMD_ACT_FWT_ACCESS_TIME,
+};
+
+/* Define action or option for CMD_802_11_HOST_SLEEP_CFG */
+enum cmd_wol_cfg_opts {
+ CMD_ACT_ACTION_NONE = 0,
+ CMD_ACT_SET_WOL_RULE,
+ CMD_ACT_GET_WOL_RULE,
+ CMD_ACT_RESET_WOL_RULE,
+};
+
+/* Define action or option for CMD_MESH_ACCESS */
+enum cmd_mesh_access_opts {
+ CMD_ACT_MESH_GET_TTL = 1,
+ CMD_ACT_MESH_SET_TTL,
+ CMD_ACT_MESH_GET_STATS,
+ CMD_ACT_MESH_GET_ANYCAST,
+ CMD_ACT_MESH_SET_ANYCAST,
+ CMD_ACT_MESH_SET_LINK_COSTS,
+ CMD_ACT_MESH_GET_LINK_COSTS,
+ CMD_ACT_MESH_SET_BCAST_RATE,
+ CMD_ACT_MESH_GET_BCAST_RATE,
+ CMD_ACT_MESH_SET_RREQ_DELAY,
+ CMD_ACT_MESH_GET_RREQ_DELAY,
+ CMD_ACT_MESH_SET_ROUTE_EXP,
+ CMD_ACT_MESH_GET_ROUTE_EXP,
+ CMD_ACT_MESH_SET_AUTOSTART_ENABLED,
+ CMD_ACT_MESH_GET_AUTOSTART_ENABLED,
+ CMD_ACT_MESH_SET_GET_PRB_RSP_LIMIT = 17,
+};
+
+/* Define actions and types for CMD_MESH_CONFIG */
+enum cmd_mesh_config_actions {
+ CMD_ACT_MESH_CONFIG_STOP = 0,
+ CMD_ACT_MESH_CONFIG_START,
+ CMD_ACT_MESH_CONFIG_SET,
+ CMD_ACT_MESH_CONFIG_GET,
+};
+
+enum cmd_mesh_config_types {
+ CMD_TYPE_MESH_SET_BOOTFLAG = 1,
+ CMD_TYPE_MESH_SET_BOOTTIME,
+ CMD_TYPE_MESH_SET_DEF_CHANNEL,
+ CMD_TYPE_MESH_SET_MESH_IE,
+ CMD_TYPE_MESH_GET_DEFAULTS,
+ CMD_TYPE_MESH_GET_MESH_IE, /* GET_DEFAULTS is superset of GET_MESHIE */
+};
+
+/* Card Event definition */
+#define MACREG_INT_CODE_TX_PPA_FREE 0
+#define MACREG_INT_CODE_TX_DMA_DONE 1
+#define MACREG_INT_CODE_LINK_LOST_W_SCAN 2
+#define MACREG_INT_CODE_LINK_LOST_NO_SCAN 3
+#define MACREG_INT_CODE_LINK_SENSED 4
+#define MACREG_INT_CODE_CMD_FINISHED 5
+#define MACREG_INT_CODE_MIB_CHANGED 6
+#define MACREG_INT_CODE_INIT_DONE 7
+#define MACREG_INT_CODE_DEAUTHENTICATED 8
+#define MACREG_INT_CODE_DISASSOCIATED 9
+#define MACREG_INT_CODE_PS_AWAKE 10
+#define MACREG_INT_CODE_PS_SLEEP 11
+#define MACREG_INT_CODE_MIC_ERR_MULTICAST 13
+#define MACREG_INT_CODE_MIC_ERR_UNICAST 14
+#define MACREG_INT_CODE_WM_AWAKE 15
+#define MACREG_INT_CODE_DEEP_SLEEP_AWAKE 16
+#define MACREG_INT_CODE_ADHOC_BCN_LOST 17
+#define MACREG_INT_CODE_HOST_AWAKE 18
+#define MACREG_INT_CODE_STOP_TX 19
+#define MACREG_INT_CODE_START_TX 20
+#define MACREG_INT_CODE_CHANNEL_SWITCH 21
+#define MACREG_INT_CODE_MEASUREMENT_RDY 22
+#define MACREG_INT_CODE_WMM_CHANGE 23
+#define MACREG_INT_CODE_BG_SCAN_REPORT 24
+#define MACREG_INT_CODE_RSSI_LOW 25
+#define MACREG_INT_CODE_SNR_LOW 26
+#define MACREG_INT_CODE_MAX_FAIL 27
+#define MACREG_INT_CODE_RSSI_HIGH 28
+#define MACREG_INT_CODE_SNR_HIGH 29
+#define MACREG_INT_CODE_MESH_AUTO_STARTED 35
+#define MACREG_INT_CODE_FIRMWARE_READY 48
+
+
+/* 802.11-related definitions */
+
+/* TxPD descriptor */
+struct txpd {
+ /* union to cope up with later FW revisions */
+ union {
+ /* Current Tx packet status */
+ __le32 tx_status;
+ struct {
+ /* BSS type: client, AP, etc. */
+ u8 bss_type;
+ /* BSS number */
+ u8 bss_num;
+ /* Reserved */
+ __le16 reserved;
+ } bss;
+ } u;
+ /* Tx control */
+ __le32 tx_control;
+ __le32 tx_packet_location;
+ /* Tx packet length */
+ __le16 tx_packet_length;
+ /* First 2 byte of destination MAC address */
+ u8 tx_dest_addr_high[2];
+ /* Last 4 byte of destination MAC address */
+ u8 tx_dest_addr_low[4];
+ /* Pkt Priority */
+ u8 priority;
+ /* Pkt Trasnit Power control */
+ u8 powermgmt;
+ /* Amount of time the packet has been queued (units = 2ms) */
+ u8 pktdelay_2ms;
+ /* reserved */
+ u8 reserved1;
+} __packed;
+
+/* RxPD Descriptor */
+struct rxpd {
+ /* union to cope up with later FW revisions */
+ union {
+ /* Current Rx packet status */
+ __le16 status;
+ struct {
+ /* BSS type: client, AP, etc. */
+ u8 bss_type;
+ /* BSS number */
+ u8 bss_num;
+ } __packed bss;
+ } __packed u;
+
+ /* SNR */
+ u8 snr;
+
+ /* Tx control */
+ u8 rx_control;
+
+ /* Pkt length */
+ __le16 pkt_len;
+
+ /* Noise Floor */
+ u8 nf;
+
+ /* Rx Packet Rate */
+ u8 rx_rate;
+
+ /* Pkt addr */
+ __le32 pkt_ptr;
+
+ /* Next Rx RxPD addr */
+ __le32 next_rxpd_ptr;
+
+ /* Pkt Priority */
+ u8 priority;
+ u8 reserved[3];
+} __packed;
+
+struct cmd_header {
+ __le16 command;
+ __le16 size;
+ __le16 seqnum;
+ __le16 result;
+} __packed;
+
+/* Generic structure to hold all key types. */
+struct enc_key {
+ u16 len;
+ u16 flags; /* KEY_INFO_* from defs.h */
+ u16 type; /* KEY_TYPE_* from defs.h */
+ u8 key[32];
+};
+
+/* lbs_offset_value */
+struct lbs_offset_value {
+ u32 offset;
+ u32 value;
+} __packed;
+
+#define MAX_11D_TRIPLETS 83
+
+struct mrvl_ie_domain_param_set {
+ struct mrvl_ie_header header;
+
+ u8 country_code[IEEE80211_COUNTRY_STRING_LEN];
+ struct ieee80211_country_ie_triplet triplet[MAX_11D_TRIPLETS];
+} __packed;
+
+struct cmd_ds_802_11d_domain_info {
+ struct cmd_header hdr;
+
+ __le16 action;
+ struct mrvl_ie_domain_param_set domain;
+} __packed;
+
+/*
+ * Define data structure for CMD_GET_HW_SPEC
+ * This structure defines the response for the GET_HW_SPEC command
+ */
+struct cmd_ds_get_hw_spec {
+ struct cmd_header hdr;
+
+ /* HW Interface version number */
+ __le16 hwifversion;
+ /* HW version number */
+ __le16 version;
+ /* Max number of TxPD FW can handle */
+ __le16 nr_txpd;
+ /* Max no of Multicast address */
+ __le16 nr_mcast_adr;
+ /* MAC address */
+ u8 permanentaddr[6];
+
+ /* region Code */
+ __le16 regioncode;
+
+ /* Number of antenna used */
+ __le16 nr_antenna;
+
+ /* FW release number, example 0x01030304 = 2.3.4p1 */
+ __le32 fwrelease;
+
+ /* Base Address of TxPD queue */
+ __le32 wcb_base;
+ /* Read Pointer of RxPd queue */
+ __le32 rxpd_rdptr;
+
+ /* Write Pointer of RxPd queue */
+ __le32 rxpd_wrptr;
+
+ /*FW/HW capability */
+ __le32 fwcapinfo;
+} __packed;
+
+struct cmd_ds_802_11_subscribe_event {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 events;
+
+ /* A TLV to the CMD_802_11_SUBSCRIBE_EVENT command can contain a
+ * number of TLVs. From the v5.1 manual, those TLVs would add up to
+ * 40 bytes. However, future firmware might add additional TLVs, so I
+ * bump this up a bit.
+ */
+ uint8_t tlv[128];
+} __packed;
+
+/*
+ * This scan handle Country Information IE(802.11d compliant)
+ * Define data structure for CMD_802_11_SCAN
+ */
+struct cmd_ds_802_11_scan {
+ struct cmd_header hdr;
+
+ uint8_t bsstype;
+ uint8_t bssid[ETH_ALEN];
+ uint8_t tlvbuffer[];
+} __packed;
+
+struct cmd_ds_802_11_scan_rsp {
+ struct cmd_header hdr;
+
+ __le16 bssdescriptsize;
+ uint8_t nr_sets;
+ uint8_t bssdesc_and_tlvbuffer[];
+} __packed;
+
+struct cmd_ds_802_11_get_log {
+ struct cmd_header hdr;
+
+ __le32 mcasttxframe;
+ __le32 failed;
+ __le32 retry;
+ __le32 multiretry;
+ __le32 framedup;
+ __le32 rtssuccess;
+ __le32 rtsfailure;
+ __le32 ackfailure;
+ __le32 rxfrag;
+ __le32 mcastrxframe;
+ __le32 fcserror;
+ __le32 txframe;
+ __le32 wepundecryptable;
+} __packed;
+
+struct cmd_ds_mac_control {
+ struct cmd_header hdr;
+ __le16 action;
+ u16 reserved;
+} __packed;
+
+struct cmd_ds_mac_multicast_adr {
+ struct cmd_header hdr;
+ __le16 action;
+ __le16 nr_of_adrs;
+ u8 maclist[ETH_ALEN * MRVDRV_MAX_MULTICAST_LIST_SIZE];
+} __packed;
+
+struct cmd_ds_802_11_authenticate {
+ struct cmd_header hdr;
+
+ u8 bssid[ETH_ALEN];
+ u8 authtype;
+ u8 reserved[10];
+} __packed;
+
+struct cmd_ds_802_11_deauthenticate {
+ struct cmd_header hdr;
+
+ u8 macaddr[ETH_ALEN];
+ __le16 reasoncode;
+} __packed;
+
+struct cmd_ds_802_11_associate {
+ struct cmd_header hdr;
+
+ u8 bssid[6];
+ __le16 capability;
+ __le16 listeninterval;
+ __le16 bcnperiod;
+ u8 dtimperiod;
+ u8 iebuf[512]; /* Enough for required and most optional IEs */
+} __packed;
+
+struct cmd_ds_802_11_associate_response {
+ struct cmd_header hdr;
+
+ __le16 capability;
+ __le16 statuscode;
+ __le16 aid;
+ u8 iebuf[512];
+} __packed;
+
+struct cmd_ds_802_11_set_wep {
+ struct cmd_header hdr;
+
+ /* ACT_ADD, ACT_REMOVE or ACT_ENABLE */
+ __le16 action;
+
+ /* key Index selected for Tx */
+ __le16 keyindex;
+
+ /* 40, 128bit or TXWEP */
+ uint8_t keytype[4];
+ uint8_t keymaterial[4][16];
+} __packed;
+
+struct cmd_ds_802_11_snmp_mib {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 oid;
+ __le16 bufsize;
+ u8 value[128];
+} __packed;
+
+struct cmd_ds_reg_access {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 offset;
+ union {
+ u8 bbp_rf; /* for BBP and RF registers */
+ __le32 mac; /* for MAC registers */
+ } value;
+} __packed;
+
+struct cmd_ds_802_11_radio_control {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 control;
+} __packed;
+
+struct cmd_ds_802_11_beacon_control {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 beacon_enable;
+ __le16 beacon_period;
+} __packed;
+
+struct cmd_ds_802_11_sleep_params {
+ struct cmd_header hdr;
+
+ /* ACT_GET/ACT_SET */
+ __le16 action;
+
+ /* Sleep clock error in ppm */
+ __le16 error;
+
+ /* Wakeup offset in usec */
+ __le16 offset;
+
+ /* Clock stabilization time in usec */
+ __le16 stabletime;
+
+ /* control periodic calibration */
+ uint8_t calcontrol;
+
+ /* control the use of external sleep clock */
+ uint8_t externalsleepclk;
+
+ /* reserved field, should be set to zero */
+ __le16 reserved;
+} __packed;
+
+struct cmd_ds_802_11_rf_channel {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 channel;
+ __le16 rftype; /* unused */
+ __le16 reserved; /* unused */
+ u8 channellist[32]; /* unused */
+} __packed;
+
+struct cmd_ds_802_11_rssi {
+ struct cmd_header hdr;
+
+ /*
+ * request: number of beacons (N) to average the SNR and NF over
+ * response: SNR of most recent beacon
+ */
+ __le16 n_or_snr;
+
+ /*
+ * The following fields are only set in the response.
+ * In the request these are reserved and should be set to 0.
+ */
+ __le16 nf; /* most recent beacon noise floor */
+ __le16 avg_snr; /* average SNR weighted by N from request */
+ __le16 avg_nf; /* average noise floor weighted by N from request */
+} __packed;
+
+struct cmd_ds_802_11_mac_address {
+ struct cmd_header hdr;
+
+ __le16 action;
+ u8 macadd[ETH_ALEN];
+} __packed;
+
+struct cmd_ds_802_11_rf_tx_power {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 curlevel;
+ s8 maxlevel;
+ s8 minlevel;
+} __packed;
+
+/* MONITOR_MODE only exists in OLPC v5 firmware */
+struct cmd_ds_802_11_monitor_mode {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 mode;
+} __packed;
+
+struct cmd_ds_set_boot2_ver {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 version;
+} __packed;
+
+struct cmd_ds_802_11_fw_wake_method {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 method;
+} __packed;
+
+struct cmd_ds_802_11_ps_mode {
+ struct cmd_header hdr;
+
+ __le16 action;
+
+ /*
+ * Interval for keepalive in PS mode:
+ * 0x0000 = don't change
+ * 0x001E = firmware default
+ * 0xFFFF = disable
+ */
+ __le16 nullpktinterval;
+
+ /*
+ * Number of DTIM intervals to wake up for:
+ * 0 = don't change
+ * 1 = firmware default
+ * 5 = max
+ */
+ __le16 multipledtim;
+
+ __le16 reserved;
+ __le16 locallisteninterval;
+
+ /*
+ * AdHoc awake period (FW v9+ only):
+ * 0 = don't change
+ * 1 = always awake (IEEE standard behavior)
+ * 2 - 31 = sleep for (n - 1) periods and awake for 1 period
+ * 32 - 254 = invalid
+ * 255 = sleep at each ATIM
+ */
+ __le16 adhoc_awake_period;
+} __packed;
+
+struct cmd_confirm_sleep {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 nullpktinterval;
+ __le16 multipledtim;
+ __le16 reserved;
+ __le16 locallisteninterval;
+} __packed;
+
+struct cmd_ds_802_11_data_rate {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 reserved;
+ u8 rates[MAX_RATES];
+} __packed;
+
+struct cmd_ds_802_11_rate_adapt_rateset {
+ struct cmd_header hdr;
+ __le16 action;
+ __le16 enablehwauto;
+ __le16 bitmap;
+} __packed;
+
+struct cmd_ds_802_11_ad_hoc_start {
+ struct cmd_header hdr;
+
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 bsstype;
+ __le16 beaconperiod;
+ u8 dtimperiod; /* Reserved on v9 and later */
+ struct ieee_ie_ibss_param_set ibss;
+ u8 reserved1[4];
+ struct ieee_ie_ds_param_set ds;
+ u8 reserved2[4];
+ __le16 probedelay; /* Reserved on v9 and later */
+ __le16 capability;
+ u8 rates[MAX_RATES];
+ u8 tlv_memory_size_pad[100];
+} __packed;
+
+struct cmd_ds_802_11_ad_hoc_result {
+ struct cmd_header hdr;
+
+ u8 pad[3];
+ u8 bssid[ETH_ALEN];
+} __packed;
+
+struct adhoc_bssdesc {
+ u8 bssid[ETH_ALEN];
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 type;
+ __le16 beaconperiod;
+ u8 dtimperiod;
+ __le64 timestamp;
+ __le64 localtime;
+ struct ieee_ie_ds_param_set ds;
+ u8 reserved1[4];
+ struct ieee_ie_ibss_param_set ibss;
+ u8 reserved2[4];
+ __le16 capability;
+ u8 rates[MAX_RATES];
+
+ /*
+ * DO NOT ADD ANY FIELDS TO THIS STRUCTURE. It is used below in the
+ * Adhoc join command and will cause a binary layout mismatch with
+ * the firmware
+ */
+} __packed;
+
+struct cmd_ds_802_11_ad_hoc_join {
+ struct cmd_header hdr;
+
+ struct adhoc_bssdesc bss;
+ __le16 failtimeout; /* Reserved on v9 and later */
+ __le16 probedelay; /* Reserved on v9 and later */
+} __packed;
+
+struct cmd_ds_802_11_ad_hoc_stop {
+ struct cmd_header hdr;
+} __packed;
+
+struct cmd_ds_802_11_enable_rsn {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 enable;
+} __packed;
+
+struct MrvlIEtype_keyParamSet {
+ /* type ID */
+ __le16 type;
+
+ /* length of Payload */
+ __le16 length;
+
+ /* type of key: WEP=0, TKIP=1, AES=2 */
+ __le16 keytypeid;
+
+ /* key control Info specific to a keytypeid */
+ __le16 keyinfo;
+
+ /* length of key */
+ __le16 keylen;
+
+ /* key material of size keylen */
+ u8 key[32];
+} __packed;
+
+#define MAX_WOL_RULES 16
+
+struct host_wol_rule {
+ uint8_t rule_no;
+ uint8_t rule_ops;
+ __le16 sig_offset;
+ __le16 sig_length;
+ __le16 reserve;
+ __be32 sig_mask;
+ __be32 signature;
+} __packed;
+
+struct wol_config {
+ uint8_t action;
+ uint8_t pattern;
+ uint8_t no_rules_in_cmd;
+ uint8_t result;
+ struct host_wol_rule rule[MAX_WOL_RULES];
+} __packed;
+
+struct cmd_ds_host_sleep {
+ struct cmd_header hdr;
+ __le32 criteria;
+ uint8_t gpio;
+ uint16_t gap;
+ struct wol_config wol_conf;
+} __packed;
+
+
+
+struct cmd_ds_802_11_key_material {
+ struct cmd_header hdr;
+
+ __le16 action;
+ struct MrvlIEtype_keyParamSet keyParamSet[2];
+} __packed;
+
+struct cmd_ds_802_11_eeprom_access {
+ struct cmd_header hdr;
+ __le16 action;
+ __le16 offset;
+ __le16 len;
+ /* firmware says it returns a maximum of 20 bytes */
+#define LBS_EEPROM_READ_LEN 20
+ u8 value[LBS_EEPROM_READ_LEN];
+} __packed;
+
+struct cmd_ds_802_11_tpc_cfg {
+ struct cmd_header hdr;
+
+ __le16 action;
+ uint8_t enable;
+ int8_t P0;
+ int8_t P1;
+ int8_t P2;
+ uint8_t usesnr;
+} __packed;
+
+
+struct cmd_ds_802_11_pa_cfg {
+ struct cmd_header hdr;
+
+ __le16 action;
+ uint8_t enable;
+ int8_t P0;
+ int8_t P1;
+ int8_t P2;
+} __packed;
+
+
+struct cmd_ds_802_11_led_ctrl {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 numled;
+ u8 data[256];
+} __packed;
+
+/* Automatic Frequency Control */
+struct cmd_ds_802_11_afc {
+ struct cmd_header hdr;
+
+ __le16 afc_auto;
+ union {
+ struct {
+ __le16 threshold;
+ __le16 period;
+ };
+ struct {
+ __le16 timing_offset; /* signed */
+ __le16 carrier_offset; /* signed */
+ };
+ };
+} __packed;
+
+struct cmd_tx_rate_query {
+ __le16 txrate;
+} __packed;
+
+struct cmd_ds_get_tsf {
+ __le64 tsfvalue;
+} __packed;
+
+struct cmd_ds_bt_access {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le32 id;
+ u8 addr1[ETH_ALEN];
+ u8 addr2[ETH_ALEN];
+} __packed;
+
+struct cmd_ds_fwt_access {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le32 id;
+ u8 valid;
+ u8 da[ETH_ALEN];
+ u8 dir;
+ u8 ra[ETH_ALEN];
+ __le32 ssn;
+ __le32 dsn;
+ __le32 metric;
+ u8 rate;
+ u8 hopcount;
+ u8 ttl;
+ __le32 expiration;
+ u8 sleepmode;
+ __le32 snr;
+ __le32 references;
+ u8 prec[ETH_ALEN];
+} __packed;
+
+struct cmd_ds_mesh_config {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le16 channel;
+ __le16 type;
+ __le16 length;
+ u8 data[128]; /* last position reserved */
+} __packed;
+
+struct cmd_ds_mesh_access {
+ struct cmd_header hdr;
+
+ __le16 action;
+ __le32 data[32]; /* last position reserved */
+} __packed;
+
+/* Number of stats counters returned by the firmware */
+#define MESH_STATS_NUM 8
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/if_cs.c b/drivers/net/wireless/marvell/libertas/if_cs.c
new file mode 100644
index 000000000..4103f15bc
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_cs.c
@@ -0,0 +1,957 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+
+ Driver for the Marvell 8385 based compact flash WLAN cards.
+
+ (C) 2007 by Holger Schurig <hs4233@mail.mn-solutions.de>
+
+
+*/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/netdevice.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include <linux/io.h>
+
+#define DRV_NAME "libertas_cs"
+
+#include "decl.h"
+#include "defs.h"
+#include "dev.h"
+
+
+/********************************************************************/
+/* Module stuff */
+/********************************************************************/
+
+MODULE_AUTHOR("Holger Schurig <hs4233@mail.mn-solutions.de>");
+MODULE_DESCRIPTION("Driver for Marvell 83xx compact flash WLAN cards");
+MODULE_LICENSE("GPL");
+
+
+
+/********************************************************************/
+/* Data structures */
+/********************************************************************/
+
+struct if_cs_card {
+ struct pcmcia_device *p_dev;
+ struct lbs_private *priv;
+ void __iomem *iobase;
+ bool align_regs;
+ u32 model;
+};
+
+
+enum {
+ MODEL_UNKNOWN = 0x00,
+ MODEL_8305 = 0x01,
+ MODEL_8381 = 0x02,
+ MODEL_8385 = 0x03
+};
+
+static const struct lbs_fw_table fw_table[] = {
+ { MODEL_8305, "libertas/cf8305.bin", NULL },
+ { MODEL_8305, "libertas_cs_helper.fw", NULL },
+ { MODEL_8381, "libertas/cf8381_helper.bin", "libertas/cf8381.bin" },
+ { MODEL_8381, "libertas_cs_helper.fw", "libertas_cs.fw" },
+ { MODEL_8385, "libertas/cf8385_helper.bin", "libertas/cf8385.bin" },
+ { MODEL_8385, "libertas_cs_helper.fw", "libertas_cs.fw" },
+ { 0, NULL, NULL }
+};
+MODULE_FIRMWARE("libertas/cf8305.bin");
+MODULE_FIRMWARE("libertas/cf8381_helper.bin");
+MODULE_FIRMWARE("libertas/cf8381.bin");
+MODULE_FIRMWARE("libertas/cf8385_helper.bin");
+MODULE_FIRMWARE("libertas/cf8385.bin");
+MODULE_FIRMWARE("libertas_cs_helper.fw");
+MODULE_FIRMWARE("libertas_cs.fw");
+
+
+/********************************************************************/
+/* Hardware access */
+/********************************************************************/
+
+/* This define enables wrapper functions which allow you
+ to dump all register accesses. You normally won't this,
+ except for development */
+/* #define DEBUG_IO */
+
+#ifdef DEBUG_IO
+static int debug_output = 0;
+#else
+/* This way the compiler optimizes the printk's away */
+#define debug_output 0
+#endif
+
+static inline unsigned int if_cs_read8(struct if_cs_card *card, uint reg)
+{
+ unsigned int val = ioread8(card->iobase + reg);
+ if (debug_output)
+ printk(KERN_INFO "inb %08x<%02x\n", reg, val);
+ return val;
+}
+static inline unsigned int if_cs_read16(struct if_cs_card *card, uint reg)
+{
+ unsigned int val = ioread16(card->iobase + reg);
+ if (debug_output)
+ printk(KERN_INFO "inw %08x<%04x\n", reg, val);
+ return val;
+}
+static inline void if_cs_read16_rep(
+ struct if_cs_card *card,
+ uint reg,
+ void *buf,
+ unsigned long count)
+{
+ if (debug_output)
+ printk(KERN_INFO "insw %08x<(0x%lx words)\n",
+ reg, count);
+ ioread16_rep(card->iobase + reg, buf, count);
+}
+
+static inline void if_cs_write8(struct if_cs_card *card, uint reg, u8 val)
+{
+ if (debug_output)
+ printk(KERN_INFO "outb %08x>%02x\n", reg, val);
+ iowrite8(val, card->iobase + reg);
+}
+
+static inline void if_cs_write16(struct if_cs_card *card, uint reg, u16 val)
+{
+ if (debug_output)
+ printk(KERN_INFO "outw %08x>%04x\n", reg, val);
+ iowrite16(val, card->iobase + reg);
+}
+
+static inline void if_cs_write16_rep(
+ struct if_cs_card *card,
+ uint reg,
+ const void *buf,
+ unsigned long count)
+{
+ if (debug_output)
+ printk(KERN_INFO "outsw %08x>(0x%lx words)\n",
+ reg, count);
+ iowrite16_rep(card->iobase + reg, buf, count);
+}
+
+
+/*
+ * I know that polling/delaying is frowned upon. However, this procedure
+ * with polling is needed while downloading the firmware. At this stage,
+ * the hardware does unfortunately not create any interrupts.
+ *
+ * Fortunately, this function is never used once the firmware is in
+ * the card. :-)
+ *
+ * As a reference, see the "Firmware Specification v5.1", page 18
+ * and 19. I did not follow their suggested timing to the word,
+ * but this works nice & fast anyway.
+ */
+static int if_cs_poll_while_fw_download(struct if_cs_card *card, uint addr, u8 reg)
+{
+ int i;
+
+ for (i = 0; i < 100000; i++) {
+ u8 val = if_cs_read8(card, addr);
+ if (val == reg)
+ return 0;
+ udelay(5);
+ }
+ return -ETIME;
+}
+
+
+
+/*
+ * First the bitmasks for the host/card interrupt/status registers:
+ */
+#define IF_CS_BIT_TX 0x0001
+#define IF_CS_BIT_RX 0x0002
+#define IF_CS_BIT_COMMAND 0x0004
+#define IF_CS_BIT_RESP 0x0008
+#define IF_CS_BIT_EVENT 0x0010
+#define IF_CS_BIT_MASK 0x001f
+
+
+
+/*
+ * It's not really clear to me what the host status register is for. It
+ * needs to be set almost in union with "host int cause". The following
+ * bits from above are used:
+ *
+ * IF_CS_BIT_TX driver downloaded a data packet
+ * IF_CS_BIT_RX driver got a data packet
+ * IF_CS_BIT_COMMAND driver downloaded a command
+ * IF_CS_BIT_RESP not used (has some meaning with powerdown)
+ * IF_CS_BIT_EVENT driver read a host event
+ */
+#define IF_CS_HOST_STATUS 0x00000000
+
+/*
+ * With the host int cause register can the host (that is, Linux) cause
+ * an interrupt in the firmware, to tell the firmware about those events:
+ *
+ * IF_CS_BIT_TX a data packet has been downloaded
+ * IF_CS_BIT_RX a received data packet has retrieved
+ * IF_CS_BIT_COMMAND a firmware block or a command has been downloaded
+ * IF_CS_BIT_RESP not used (has some meaning with powerdown)
+ * IF_CS_BIT_EVENT a host event (link lost etc) has been retrieved
+ */
+#define IF_CS_HOST_INT_CAUSE 0x00000002
+
+/*
+ * The host int mask register is used to enable/disable interrupt. However,
+ * I have the suspicion that disabled interrupts are lost.
+ */
+#define IF_CS_HOST_INT_MASK 0x00000004
+
+/*
+ * Used to send or receive data packets:
+ */
+#define IF_CS_WRITE 0x00000016
+#define IF_CS_WRITE_LEN 0x00000014
+#define IF_CS_READ 0x00000010
+#define IF_CS_READ_LEN 0x00000024
+
+/*
+ * Used to send commands (and to send firmware block) and to
+ * receive command responses:
+ */
+#define IF_CS_CMD 0x0000001A
+#define IF_CS_CMD_LEN 0x00000018
+#define IF_CS_RESP 0x00000012
+#define IF_CS_RESP_LEN 0x00000030
+
+/*
+ * The card status registers shows what the card/firmware actually
+ * accepts:
+ *
+ * IF_CS_BIT_TX you may send a data packet
+ * IF_CS_BIT_RX you may retrieve a data packet
+ * IF_CS_BIT_COMMAND you may send a command
+ * IF_CS_BIT_RESP you may retrieve a command response
+ * IF_CS_BIT_EVENT the card has a event for use (link lost, snr low etc)
+ *
+ * When reading this register several times, you will get back the same
+ * results --- with one exception: the IF_CS_BIT_EVENT clear itself
+ * automatically.
+ *
+ * Not that we don't rely on BIT_RX,_BIT_RESP or BIT_EVENT because
+ * we handle this via the card int cause register.
+ */
+#define IF_CS_CARD_STATUS 0x00000020
+#define IF_CS_CARD_STATUS_MASK 0x7f00
+
+/*
+ * The card int cause register is used by the card/firmware to notify us
+ * about the following events:
+ *
+ * IF_CS_BIT_TX a data packet has successfully been sentx
+ * IF_CS_BIT_RX a data packet has been received and can be retrieved
+ * IF_CS_BIT_COMMAND not used
+ * IF_CS_BIT_RESP the firmware has a command response for us
+ * IF_CS_BIT_EVENT the card has a event for use (link lost, snr low etc)
+ */
+#define IF_CS_CARD_INT_CAUSE 0x00000022
+
+/*
+ * This is used to for handshaking with the card's bootloader/helper image
+ * to synchronize downloading of firmware blocks.
+ */
+#define IF_CS_SQ_READ_LOW 0x00000028
+#define IF_CS_SQ_HELPER_OK 0x10
+
+/*
+ * The scratch register tells us ...
+ *
+ * IF_CS_SCRATCH_BOOT_OK the bootloader runs
+ * IF_CS_SCRATCH_HELPER_OK the helper firmware already runs
+ */
+#define IF_CS_SCRATCH 0x0000003F
+#define IF_CS_SCRATCH_BOOT_OK 0x00
+#define IF_CS_SCRATCH_HELPER_OK 0x5a
+
+/*
+ * Used to detect ancient chips:
+ */
+#define IF_CS_PRODUCT_ID 0x0000001C
+#define IF_CS_CF8385_B1_REV 0x12
+#define IF_CS_CF8381_B3_REV 0x04
+#define IF_CS_CF8305_B1_REV 0x03
+
+/*
+ * Used to detect other cards than CF8385 since their revisions of silicon
+ * doesn't match those from CF8385, eg. CF8381 B3 works with this driver.
+ */
+#define CF8305_MANFID 0x02db
+#define CF8305_CARDID 0x8103
+#define CF8381_MANFID 0x02db
+#define CF8381_CARDID 0x6064
+#define CF8385_MANFID 0x02df
+#define CF8385_CARDID 0x8103
+
+/*
+ * FIXME: just use the 'driver_info' field of 'struct pcmcia_device_id' when
+ * that gets fixed. Currently there's no way to access it from the probe hook.
+ */
+static inline u32 get_model(u16 manf_id, u16 card_id)
+{
+ /* NOTE: keep in sync with if_cs_ids */
+ if (manf_id == CF8305_MANFID && card_id == CF8305_CARDID)
+ return MODEL_8305;
+ else if (manf_id == CF8381_MANFID && card_id == CF8381_CARDID)
+ return MODEL_8381;
+ else if (manf_id == CF8385_MANFID && card_id == CF8385_CARDID)
+ return MODEL_8385;
+ return MODEL_UNKNOWN;
+}
+
+/********************************************************************/
+/* I/O and interrupt handling */
+/********************************************************************/
+
+static inline void if_cs_enable_ints(struct if_cs_card *card)
+{
+ if_cs_write16(card, IF_CS_HOST_INT_MASK, 0);
+}
+
+static inline void if_cs_disable_ints(struct if_cs_card *card)
+{
+ if_cs_write16(card, IF_CS_HOST_INT_MASK, IF_CS_BIT_MASK);
+}
+
+/*
+ * Called from if_cs_host_to_card to send a command to the hardware
+ */
+static int if_cs_send_cmd(struct lbs_private *priv, u8 *buf, u16 nb)
+{
+ struct if_cs_card *card = (struct if_cs_card *)priv->card;
+ int ret = -1;
+ int loops = 0;
+
+ if_cs_disable_ints(card);
+
+ /* Is hardware ready? */
+ while (1) {
+ u16 status = if_cs_read16(card, IF_CS_CARD_STATUS);
+ if (status & IF_CS_BIT_COMMAND)
+ break;
+ if (++loops > 100) {
+ netdev_err(priv->dev, "card not ready for commands\n");
+ goto done;
+ }
+ mdelay(1);
+ }
+
+ if_cs_write16(card, IF_CS_CMD_LEN, nb);
+
+ if_cs_write16_rep(card, IF_CS_CMD, buf, nb / 2);
+ /* Are we supposed to transfer an odd amount of bytes? */
+ if (nb & 1)
+ if_cs_write8(card, IF_CS_CMD, buf[nb-1]);
+
+ /* "Assert the download over interrupt command in the Host
+ * status register" */
+ if_cs_write16(card, IF_CS_HOST_STATUS, IF_CS_BIT_COMMAND);
+
+ /* "Assert the download over interrupt command in the Card
+ * interrupt case register" */
+ if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND);
+ ret = 0;
+
+done:
+ if_cs_enable_ints(card);
+ return ret;
+}
+
+/*
+ * Called from if_cs_host_to_card to send a data to the hardware
+ */
+static void if_cs_send_data(struct lbs_private *priv, u8 *buf, u16 nb)
+{
+ struct if_cs_card *card = (struct if_cs_card *)priv->card;
+ u16 status;
+
+ if_cs_disable_ints(card);
+
+ status = if_cs_read16(card, IF_CS_CARD_STATUS);
+ BUG_ON((status & IF_CS_BIT_TX) == 0);
+
+ if_cs_write16(card, IF_CS_WRITE_LEN, nb);
+
+ /* write even number of bytes, then odd byte if necessary */
+ if_cs_write16_rep(card, IF_CS_WRITE, buf, nb / 2);
+ if (nb & 1)
+ if_cs_write8(card, IF_CS_WRITE, buf[nb-1]);
+
+ if_cs_write16(card, IF_CS_HOST_STATUS, IF_CS_BIT_TX);
+ if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_TX);
+ if_cs_enable_ints(card);
+}
+
+/*
+ * Get the command result out of the card.
+ */
+static int if_cs_receive_cmdres(struct lbs_private *priv, u8 *data, u32 *len)
+{
+ unsigned long flags;
+ int ret = -1;
+ u16 status;
+
+ /* is hardware ready? */
+ status = if_cs_read16(priv->card, IF_CS_CARD_STATUS);
+ if ((status & IF_CS_BIT_RESP) == 0) {
+ netdev_err(priv->dev, "no cmd response in card\n");
+ *len = 0;
+ goto out;
+ }
+
+ *len = if_cs_read16(priv->card, IF_CS_RESP_LEN);
+ if ((*len == 0) || (*len > LBS_CMD_BUFFER_SIZE)) {
+ netdev_err(priv->dev,
+ "card cmd buffer has invalid # of bytes (%d)\n",
+ *len);
+ goto out;
+ }
+
+ /* read even number of bytes, then odd byte if necessary */
+ if_cs_read16_rep(priv->card, IF_CS_RESP, data, *len/sizeof(u16));
+ if (*len & 1)
+ data[*len-1] = if_cs_read8(priv->card, IF_CS_RESP);
+
+ /* This is a workaround for a firmware that reports too much
+ * bytes */
+ *len -= 8;
+ ret = 0;
+
+ /* Clear this flag again */
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+out:
+ return ret;
+}
+
+static struct sk_buff *if_cs_receive_data(struct lbs_private *priv)
+{
+ struct sk_buff *skb = NULL;
+ u16 len;
+ u8 *data;
+
+ len = if_cs_read16(priv->card, IF_CS_READ_LEN);
+ if (len == 0 || len > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) {
+ netdev_err(priv->dev,
+ "card data buffer has invalid # of bytes (%d)\n",
+ len);
+ priv->dev->stats.rx_dropped++;
+ goto dat_err;
+ }
+
+ skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + 2);
+ if (!skb)
+ goto out;
+ skb_put(skb, len);
+ skb_reserve(skb, 2);/* 16 byte align */
+ data = skb->data;
+
+ /* read even number of bytes, then odd byte if necessary */
+ if_cs_read16_rep(priv->card, IF_CS_READ, data, len/sizeof(u16));
+ if (len & 1)
+ data[len-1] = if_cs_read8(priv->card, IF_CS_READ);
+
+dat_err:
+ if_cs_write16(priv->card, IF_CS_HOST_STATUS, IF_CS_BIT_RX);
+ if_cs_write16(priv->card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_RX);
+
+out:
+ return skb;
+}
+
+static irqreturn_t if_cs_interrupt(int irq, void *data)
+{
+ struct if_cs_card *card = data;
+ struct lbs_private *priv = card->priv;
+ u16 cause;
+
+ /* Ask card interrupt cause register if there is something for us */
+ cause = if_cs_read16(card, IF_CS_CARD_INT_CAUSE);
+ lbs_deb_cs("cause 0x%04x\n", cause);
+
+ if (cause == 0) {
+ /* Not for us */
+ return IRQ_NONE;
+ }
+
+ if (cause == 0xffff) {
+ /* Read in junk, the card has probably been removed */
+ card->priv->surpriseremoved = 1;
+ return IRQ_HANDLED;
+ }
+
+ if (cause & IF_CS_BIT_RX) {
+ struct sk_buff *skb;
+ lbs_deb_cs("rx packet\n");
+ skb = if_cs_receive_data(priv);
+ if (skb)
+ lbs_process_rxed_packet(priv, skb);
+ }
+
+ if (cause & IF_CS_BIT_TX) {
+ lbs_deb_cs("tx done\n");
+ lbs_host_to_card_done(priv);
+ }
+
+ if (cause & IF_CS_BIT_RESP) {
+ unsigned long flags;
+ u8 i;
+
+ lbs_deb_cs("cmd resp\n");
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ i = (priv->resp_idx == 0) ? 1 : 0;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ BUG_ON(priv->resp_len[i]);
+ if_cs_receive_cmdres(priv, priv->resp_buf[i],
+ &priv->resp_len[i]);
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ lbs_notify_command_response(priv, i);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ }
+
+ if (cause & IF_CS_BIT_EVENT) {
+ u16 status = if_cs_read16(priv->card, IF_CS_CARD_STATUS);
+ if_cs_write16(priv->card, IF_CS_HOST_INT_CAUSE,
+ IF_CS_BIT_EVENT);
+ lbs_queue_event(priv, (status & IF_CS_CARD_STATUS_MASK) >> 8);
+ }
+
+ /* Clear interrupt cause */
+ if_cs_write16(card, IF_CS_CARD_INT_CAUSE, cause & IF_CS_BIT_MASK);
+
+ return IRQ_HANDLED;
+}
+
+
+
+
+/********************************************************************/
+/* Firmware */
+/********************************************************************/
+
+/*
+ * Tries to program the helper firmware.
+ *
+ * Return 0 on success
+ */
+static int if_cs_prog_helper(struct if_cs_card *card, const struct firmware *fw)
+{
+ int ret = 0;
+ int sent = 0;
+ u8 scratch;
+
+ /*
+ * This is the only place where an unaligned register access happens on
+ * the CF8305 card, therefore for the sake of speed of the driver, we do
+ * the alignment correction here.
+ */
+ if (card->align_regs)
+ scratch = if_cs_read16(card, IF_CS_SCRATCH) >> 8;
+ else
+ scratch = if_cs_read8(card, IF_CS_SCRATCH);
+
+ /* "If the value is 0x5a, the firmware is already
+ * downloaded successfully"
+ */
+ if (scratch == IF_CS_SCRATCH_HELPER_OK)
+ goto done;
+
+ /* "If the value is != 00, it is invalid value of register */
+ if (scratch != IF_CS_SCRATCH_BOOT_OK) {
+ ret = -ENODEV;
+ goto done;
+ }
+
+ lbs_deb_cs("helper size %td\n", fw->size);
+
+ /* "Set the 5 bytes of the helper image to 0" */
+ /* Not needed, this contains an ARM branch instruction */
+
+ for (;;) {
+ /* "the number of bytes to send is 256" */
+ int count = 256;
+ int remain = fw->size - sent;
+
+ if (remain < count)
+ count = remain;
+
+ /*
+ * "write the number of bytes to be sent to the I/O Command
+ * write length register"
+ */
+ if_cs_write16(card, IF_CS_CMD_LEN, count);
+
+ /* "write this to I/O Command port register as 16 bit writes */
+ if (count)
+ if_cs_write16_rep(card, IF_CS_CMD,
+ &fw->data[sent],
+ count >> 1);
+
+ /*
+ * "Assert the download over interrupt command in the Host
+ * status register"
+ */
+ if_cs_write8(card, IF_CS_HOST_STATUS, IF_CS_BIT_COMMAND);
+
+ /*
+ * "Assert the download over interrupt command in the Card
+ * interrupt case register"
+ */
+ if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND);
+
+ /*
+ * "The host polls the Card Status register ... for 50 ms before
+ * declaring a failure"
+ */
+ ret = if_cs_poll_while_fw_download(card, IF_CS_CARD_STATUS,
+ IF_CS_BIT_COMMAND);
+ if (ret < 0) {
+ pr_err("can't download helper at 0x%x, ret %d\n",
+ sent, ret);
+ goto done;
+ }
+
+ if (count == 0)
+ break;
+
+ sent += count;
+ }
+
+done:
+ return ret;
+}
+
+
+static int if_cs_prog_real(struct if_cs_card *card, const struct firmware *fw)
+{
+ int ret = 0;
+ int retry = 0;
+ int len = 0;
+ int sent;
+
+ lbs_deb_cs("fw size %td\n", fw->size);
+
+ ret = if_cs_poll_while_fw_download(card, IF_CS_SQ_READ_LOW,
+ IF_CS_SQ_HELPER_OK);
+ if (ret < 0) {
+ pr_err("helper firmware doesn't answer\n");
+ goto done;
+ }
+
+ for (sent = 0; sent < fw->size; sent += len) {
+ len = if_cs_read16(card, IF_CS_SQ_READ_LOW);
+ if (len & 1) {
+ retry++;
+ pr_info("odd, need to retry this firmware block\n");
+ } else {
+ retry = 0;
+ }
+
+ if (retry > 20) {
+ pr_err("could not download firmware\n");
+ ret = -ENODEV;
+ goto done;
+ }
+ if (retry) {
+ sent -= len;
+ }
+
+
+ if_cs_write16(card, IF_CS_CMD_LEN, len);
+
+ if_cs_write16_rep(card, IF_CS_CMD,
+ &fw->data[sent],
+ (len+1) >> 1);
+ if_cs_write8(card, IF_CS_HOST_STATUS, IF_CS_BIT_COMMAND);
+ if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND);
+
+ ret = if_cs_poll_while_fw_download(card, IF_CS_CARD_STATUS,
+ IF_CS_BIT_COMMAND);
+ if (ret < 0) {
+ pr_err("can't download firmware at 0x%x\n", sent);
+ goto done;
+ }
+ }
+
+ ret = if_cs_poll_while_fw_download(card, IF_CS_SCRATCH, 0x5a);
+ if (ret < 0)
+ pr_err("firmware download failed\n");
+
+done:
+ return ret;
+}
+
+static void if_cs_prog_firmware(struct lbs_private *priv, int ret,
+ const struct firmware *helper,
+ const struct firmware *mainfw)
+{
+ struct if_cs_card *card = priv->card;
+
+ if (ret) {
+ pr_err("failed to find firmware (%d)\n", ret);
+ return;
+ }
+
+ /* Load the firmware */
+ ret = if_cs_prog_helper(card, helper);
+ if (ret == 0 && (card->model != MODEL_8305))
+ ret = if_cs_prog_real(card, mainfw);
+ if (ret)
+ return;
+
+ /* Now actually get the IRQ */
+ ret = request_irq(card->p_dev->irq, if_cs_interrupt,
+ IRQF_SHARED, DRV_NAME, card);
+ if (ret) {
+ pr_err("error in request_irq\n");
+ return;
+ }
+
+ /*
+ * Clear any interrupt cause that happened while sending
+ * firmware/initializing card
+ */
+ if_cs_write16(card, IF_CS_CARD_INT_CAUSE, IF_CS_BIT_MASK);
+ if_cs_enable_ints(card);
+
+ /* And finally bring the card up */
+ priv->fw_ready = 1;
+ if (lbs_start_card(priv) != 0) {
+ pr_err("could not activate card\n");
+ free_irq(card->p_dev->irq, card);
+ }
+}
+
+
+/********************************************************************/
+/* Callback functions for libertas.ko */
+/********************************************************************/
+
+/* Send commands or data packets to the card */
+static int if_cs_host_to_card(struct lbs_private *priv,
+ u8 type,
+ u8 *buf,
+ u16 nb)
+{
+ int ret = -1;
+
+ switch (type) {
+ case MVMS_DAT:
+ priv->dnld_sent = DNLD_DATA_SENT;
+ if_cs_send_data(priv, buf, nb);
+ ret = 0;
+ break;
+ case MVMS_CMD:
+ priv->dnld_sent = DNLD_CMD_SENT;
+ ret = if_cs_send_cmd(priv, buf, nb);
+ break;
+ default:
+ netdev_err(priv->dev, "%s: unsupported type %d\n",
+ __func__, type);
+ }
+
+ return ret;
+}
+
+
+static void if_cs_release(struct pcmcia_device *p_dev)
+{
+ struct if_cs_card *card = p_dev->priv;
+
+ free_irq(p_dev->irq, card);
+ pcmcia_disable_device(p_dev);
+ if (card->iobase)
+ ioport_unmap(card->iobase);
+}
+
+
+static int if_cs_ioprobe(struct pcmcia_device *p_dev, void *priv_data)
+{
+ p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+ p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+
+ if (p_dev->resource[1]->end) {
+ pr_err("wrong CIS (check number of IO windows)\n");
+ return -ENODEV;
+ }
+
+ /* This reserves IO space but doesn't actually enable it */
+ return pcmcia_request_io(p_dev);
+}
+
+static int if_cs_probe(struct pcmcia_device *p_dev)
+{
+ int ret = -ENOMEM;
+ unsigned int prod_id;
+ struct lbs_private *priv;
+ struct if_cs_card *card;
+
+ card = kzalloc(sizeof(struct if_cs_card), GFP_KERNEL);
+ if (!card)
+ goto out;
+
+ card->p_dev = p_dev;
+ p_dev->priv = card;
+
+ p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
+ if (pcmcia_loop_config(p_dev, if_cs_ioprobe, NULL)) {
+ pr_err("error in pcmcia_loop_config\n");
+ goto out1;
+ }
+
+ /*
+ * Allocate an interrupt line. Note that this does not assign
+ * a handler to the interrupt, unless the 'Handler' member of
+ * the irq structure is initialized.
+ */
+ if (!p_dev->irq)
+ goto out1;
+
+ /* Initialize io access */
+ card->iobase = ioport_map(p_dev->resource[0]->start,
+ resource_size(p_dev->resource[0]));
+ if (!card->iobase) {
+ pr_err("error in ioport_map\n");
+ ret = -EIO;
+ goto out1;
+ }
+
+ ret = pcmcia_enable_device(p_dev);
+ if (ret) {
+ pr_err("error in pcmcia_enable_device\n");
+ goto out2;
+ }
+
+ /* Finally, report what we've done */
+ lbs_deb_cs("irq %d, io %pR", p_dev->irq, p_dev->resource[0]);
+
+ /*
+ * Most of the libertas cards can do unaligned register access, but some
+ * weird ones cannot. That's especially true for the CF8305 card.
+ */
+ card->align_regs = false;
+
+ card->model = get_model(p_dev->manf_id, p_dev->card_id);
+ if (card->model == MODEL_UNKNOWN) {
+ pr_err("unsupported manf_id 0x%04x / card_id 0x%04x\n",
+ p_dev->manf_id, p_dev->card_id);
+ ret = -ENODEV;
+ goto out2;
+ }
+
+ /* Check if we have a current silicon */
+ prod_id = if_cs_read8(card, IF_CS_PRODUCT_ID);
+ if (card->model == MODEL_8305) {
+ card->align_regs = true;
+ if (prod_id < IF_CS_CF8305_B1_REV) {
+ pr_err("8305 rev B0 and older are not supported\n");
+ ret = -ENODEV;
+ goto out2;
+ }
+ }
+
+ if ((card->model == MODEL_8381) && prod_id < IF_CS_CF8381_B3_REV) {
+ pr_err("8381 rev B2 and older are not supported\n");
+ ret = -ENODEV;
+ goto out2;
+ }
+
+ if ((card->model == MODEL_8385) && prod_id < IF_CS_CF8385_B1_REV) {
+ pr_err("8385 rev B0 and older are not supported\n");
+ ret = -ENODEV;
+ goto out2;
+ }
+
+ /* Make this card known to the libertas driver */
+ priv = lbs_add_card(card, &p_dev->dev);
+ if (IS_ERR(priv)) {
+ ret = PTR_ERR(priv);
+ goto out2;
+ }
+
+ /* Set up fields in lbs_private */
+ card->priv = priv;
+ priv->card = card;
+ priv->hw_host_to_card = if_cs_host_to_card;
+ priv->enter_deep_sleep = NULL;
+ priv->exit_deep_sleep = NULL;
+ priv->reset_deep_sleep_wakeup = NULL;
+
+ /* Get firmware */
+ ret = lbs_get_firmware_async(priv, &p_dev->dev, card->model, fw_table,
+ if_cs_prog_firmware);
+ if (ret) {
+ pr_err("failed to find firmware (%d)\n", ret);
+ goto out3;
+ }
+
+ goto out;
+
+out3:
+ lbs_remove_card(priv);
+out2:
+ ioport_unmap(card->iobase);
+out1:
+ pcmcia_disable_device(p_dev);
+out:
+ return ret;
+}
+
+
+static void if_cs_detach(struct pcmcia_device *p_dev)
+{
+ struct if_cs_card *card = p_dev->priv;
+
+ lbs_stop_card(card->priv);
+ lbs_remove_card(card->priv);
+ if_cs_disable_ints(card);
+ if_cs_release(p_dev);
+ kfree(card);
+}
+
+
+
+/********************************************************************/
+/* Module initialization */
+/********************************************************************/
+
+static const struct pcmcia_device_id if_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(CF8305_MANFID, CF8305_CARDID),
+ PCMCIA_DEVICE_MANF_CARD(CF8381_MANFID, CF8381_CARDID),
+ PCMCIA_DEVICE_MANF_CARD(CF8385_MANFID, CF8385_CARDID),
+ /* NOTE: keep in sync with get_model() */
+ PCMCIA_DEVICE_NULL,
+};
+MODULE_DEVICE_TABLE(pcmcia, if_cs_ids);
+
+static struct pcmcia_driver lbs_driver = {
+ .owner = THIS_MODULE,
+ .name = DRV_NAME,
+ .probe = if_cs_probe,
+ .remove = if_cs_detach,
+ .id_table = if_cs_ids,
+};
+module_pcmcia_driver(lbs_driver);
diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.c b/drivers/net/wireless/marvell/libertas/if_sdio.c
new file mode 100644
index 000000000..44fbd0acb
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_sdio.c
@@ -0,0 +1,1416 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/net/wireless/libertas/if_sdio.c
+ *
+ * Copyright 2007-2008 Pierre Ossman
+ *
+ * Inspired by if_cs.c, Copyright 2007 Holger Schurig
+ *
+ * This hardware has more or less no CMD53 support, so all registers
+ * must be accessed using sdio_readb()/sdio_writeb().
+ *
+ * Transfers must be in one transaction or the firmware goes bonkers.
+ * This means that the transfer must either be small enough to do a
+ * byte based transfer or it must be padded to a multiple of the
+ * current block size.
+ *
+ * As SDIO is still new to the kernel, it is unfortunately common with
+ * bugs in the host controllers related to that. One such bug is that
+ * controllers cannot do transfers that aren't a multiple of 4 bytes.
+ * If you don't have time to fix the host controller driver, you can
+ * work around the problem by modifying if_sdio_host_to_card() and
+ * if_sdio_card_to_host() to pad the data.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/host.h>
+#include <linux/pm_runtime.h>
+
+#include "host.h"
+#include "decl.h"
+#include "defs.h"
+#include "dev.h"
+#include "cmd.h"
+#include "if_sdio.h"
+
+static void if_sdio_interrupt(struct sdio_func *func);
+
+/* The if_sdio_remove() callback function is called when
+ * user removes this module from kernel space or ejects
+ * the card from the slot. The driver handles these 2 cases
+ * differently for SD8688 combo chip.
+ * If the user is removing the module, the FUNC_SHUTDOWN
+ * command for SD8688 is sent to the firmware.
+ * If the card is removed, there is no need to send this command.
+ *
+ * The variable 'user_rmmod' is used to distinguish these two
+ * scenarios. This flag is initialized as FALSE in case the card
+ * is removed, and will be set to TRUE for module removal when
+ * module_exit function is called.
+ */
+static u8 user_rmmod;
+
+static const struct sdio_device_id if_sdio_ids[] = {
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL,
+ SDIO_DEVICE_ID_MARVELL_LIBERTAS) },
+ { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL,
+ SDIO_DEVICE_ID_MARVELL_8688_WLAN) },
+ { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(sdio, if_sdio_ids);
+
+#define MODEL_8385 0x04
+#define MODEL_8686 0x0b
+#define MODEL_8688 0x10
+
+static const struct lbs_fw_table fw_table[] = {
+ { MODEL_8385, "libertas/sd8385_helper.bin", "libertas/sd8385.bin" },
+ { MODEL_8385, "sd8385_helper.bin", "sd8385.bin" },
+ { MODEL_8686, "libertas/sd8686_v9_helper.bin", "libertas/sd8686_v9.bin" },
+ { MODEL_8686, "libertas/sd8686_v8_helper.bin", "libertas/sd8686_v8.bin" },
+ { MODEL_8686, "sd8686_helper.bin", "sd8686.bin" },
+ { MODEL_8688, "libertas/sd8688_helper.bin", "libertas/sd8688.bin" },
+ { MODEL_8688, "sd8688_helper.bin", "sd8688.bin" },
+ { 0, NULL, NULL }
+};
+MODULE_FIRMWARE("libertas/sd8385_helper.bin");
+MODULE_FIRMWARE("libertas/sd8385.bin");
+MODULE_FIRMWARE("sd8385_helper.bin");
+MODULE_FIRMWARE("sd8385.bin");
+MODULE_FIRMWARE("libertas/sd8686_v9_helper.bin");
+MODULE_FIRMWARE("libertas/sd8686_v9.bin");
+MODULE_FIRMWARE("libertas/sd8686_v8_helper.bin");
+MODULE_FIRMWARE("libertas/sd8686_v8.bin");
+MODULE_FIRMWARE("sd8686_helper.bin");
+MODULE_FIRMWARE("sd8686.bin");
+MODULE_FIRMWARE("libertas/sd8688_helper.bin");
+MODULE_FIRMWARE("libertas/sd8688.bin");
+MODULE_FIRMWARE("sd8688_helper.bin");
+MODULE_FIRMWARE("sd8688.bin");
+
+struct if_sdio_packet {
+ struct if_sdio_packet *next;
+ u16 nb;
+ u8 buffer[] __aligned(4);
+};
+
+struct if_sdio_card {
+ struct sdio_func *func;
+ struct lbs_private *priv;
+
+ int model;
+ unsigned long ioport;
+ unsigned int scratch_reg;
+ bool started;
+ wait_queue_head_t pwron_waitq;
+
+ u8 buffer[65536] __attribute__((aligned(4)));
+
+ spinlock_t lock;
+ struct if_sdio_packet *packets;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct packet_worker;
+
+ u8 rx_unit;
+};
+
+static void if_sdio_finish_power_on(struct if_sdio_card *card);
+static int if_sdio_power_off(struct if_sdio_card *card);
+
+/********************************************************************/
+/* I/O */
+/********************************************************************/
+
+/*
+ * For SD8385/SD8686, this function reads firmware status after
+ * the image is downloaded, or reads RX packet length when
+ * interrupt (with IF_SDIO_H_INT_UPLD bit set) is received.
+ * For SD8688, this function reads firmware status only.
+ */
+static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err)
+{
+ int ret;
+ u16 scratch;
+
+ scratch = sdio_readb(card->func, card->scratch_reg, &ret);
+ if (!ret)
+ scratch |= sdio_readb(card->func, card->scratch_reg + 1,
+ &ret) << 8;
+
+ if (err)
+ *err = ret;
+
+ if (ret)
+ return 0xffff;
+
+ return scratch;
+}
+
+static u8 if_sdio_read_rx_unit(struct if_sdio_card *card)
+{
+ int ret;
+ u8 rx_unit;
+
+ rx_unit = sdio_readb(card->func, IF_SDIO_RX_UNIT, &ret);
+
+ if (ret)
+ rx_unit = 0;
+
+ return rx_unit;
+}
+
+static u16 if_sdio_read_rx_len(struct if_sdio_card *card, int *err)
+{
+ int ret;
+ u16 rx_len;
+
+ switch (card->model) {
+ case MODEL_8385:
+ case MODEL_8686:
+ rx_len = if_sdio_read_scratch(card, &ret);
+ break;
+ case MODEL_8688:
+ default: /* for newer chipsets */
+ rx_len = sdio_readb(card->func, IF_SDIO_RX_LEN, &ret);
+ if (!ret)
+ rx_len <<= card->rx_unit;
+ else
+ rx_len = 0xffff; /* invalid length */
+
+ break;
+ }
+
+ if (err)
+ *err = ret;
+
+ return rx_len;
+}
+
+static int if_sdio_handle_cmd(struct if_sdio_card *card,
+ u8 *buffer, unsigned size)
+{
+ struct lbs_private *priv = card->priv;
+ int ret;
+ unsigned long flags;
+ u8 i;
+
+ if (size > LBS_CMD_BUFFER_SIZE) {
+ lbs_deb_sdio("response packet too large (%d bytes)\n",
+ (int)size);
+ ret = -E2BIG;
+ goto out;
+ }
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ i = (priv->resp_idx == 0) ? 1 : 0;
+ BUG_ON(priv->resp_len[i]);
+ priv->resp_len[i] = size;
+ memcpy(priv->resp_buf[i], buffer, size);
+ lbs_notify_command_response(priv, i);
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int if_sdio_handle_data(struct if_sdio_card *card,
+ u8 *buffer, unsigned size)
+{
+ int ret;
+ struct sk_buff *skb;
+
+ if (size > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) {
+ lbs_deb_sdio("response packet too large (%d bytes)\n",
+ (int)size);
+ ret = -E2BIG;
+ goto out;
+ }
+
+ skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ skb_reserve(skb, NET_IP_ALIGN);
+
+ skb_put_data(skb, buffer, size);
+
+ lbs_process_rxed_packet(card->priv, skb);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int if_sdio_handle_event(struct if_sdio_card *card,
+ u8 *buffer, unsigned size)
+{
+ int ret;
+ u32 event;
+
+ if (card->model == MODEL_8385) {
+ event = sdio_readb(card->func, IF_SDIO_EVENT, &ret);
+ if (ret)
+ goto out;
+
+ /* right shift 3 bits to get the event id */
+ event >>= 3;
+ } else {
+ if (size < 4) {
+ lbs_deb_sdio("event packet too small (%d bytes)\n",
+ (int)size);
+ ret = -EINVAL;
+ goto out;
+ }
+ event = buffer[3] << 24;
+ event |= buffer[2] << 16;
+ event |= buffer[1] << 8;
+ event |= buffer[0] << 0;
+ }
+
+ lbs_queue_event(card->priv, event & 0xFF);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int if_sdio_wait_status(struct if_sdio_card *card, const u8 condition)
+{
+ u8 status;
+ unsigned long timeout;
+ int ret = 0;
+
+ timeout = jiffies + HZ;
+ while (1) {
+ status = sdio_readb(card->func, IF_SDIO_STATUS, &ret);
+ if (ret)
+ return ret;
+ if ((status & condition) == condition)
+ break;
+ if (time_after(jiffies, timeout))
+ return -ETIMEDOUT;
+ mdelay(1);
+ }
+ return ret;
+}
+
+static int if_sdio_card_to_host(struct if_sdio_card *card)
+{
+ int ret;
+ u16 size, type, chunk;
+
+ size = if_sdio_read_rx_len(card, &ret);
+ if (ret)
+ goto out;
+
+ if (size < 4) {
+ lbs_deb_sdio("invalid packet size (%d bytes) from firmware\n",
+ (int)size);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY);
+ if (ret)
+ goto out;
+
+ /*
+ * The transfer must be in one transaction or the firmware
+ * goes suicidal. There's no way to guarantee that for all
+ * controllers, but we can at least try.
+ */
+ chunk = sdio_align_size(card->func, size);
+
+ ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
+ if (ret)
+ goto out;
+
+ chunk = card->buffer[0] | (card->buffer[1] << 8);
+ type = card->buffer[2] | (card->buffer[3] << 8);
+
+ lbs_deb_sdio("packet of type %d and size %d bytes\n",
+ (int)type, (int)chunk);
+
+ if (chunk > size) {
+ lbs_deb_sdio("packet fragment (%d > %d)\n",
+ (int)chunk, (int)size);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (chunk < size) {
+ lbs_deb_sdio("packet fragment (%d < %d)\n",
+ (int)chunk, (int)size);
+ }
+
+ switch (type) {
+ case MVMS_CMD:
+ ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);
+ if (ret)
+ goto out;
+ break;
+ case MVMS_DAT:
+ ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);
+ if (ret)
+ goto out;
+ break;
+ case MVMS_EVENT:
+ ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);
+ if (ret)
+ goto out;
+ break;
+ default:
+ lbs_deb_sdio("invalid type (%d) from firmware\n",
+ (int)type);
+ ret = -EINVAL;
+ goto out;
+ }
+
+out:
+ if (ret)
+ pr_err("problem fetching packet from firmware\n");
+
+ return ret;
+}
+
+static void if_sdio_host_to_card_worker(struct work_struct *work)
+{
+ struct if_sdio_card *card;
+ struct if_sdio_packet *packet;
+ int ret;
+ unsigned long flags;
+
+ card = container_of(work, struct if_sdio_card, packet_worker);
+
+ while (1) {
+ spin_lock_irqsave(&card->lock, flags);
+ packet = card->packets;
+ if (packet)
+ card->packets = packet->next;
+ spin_unlock_irqrestore(&card->lock, flags);
+
+ if (!packet)
+ break;
+
+ sdio_claim_host(card->func);
+
+ ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY);
+ if (ret == 0) {
+ ret = sdio_writesb(card->func, card->ioport,
+ packet->buffer, packet->nb);
+ }
+
+ if (ret)
+ pr_err("error %d sending packet to firmware\n", ret);
+
+ sdio_release_host(card->func);
+
+ kfree(packet);
+ }
+}
+
+/********************************************************************/
+/* Firmware */
+/********************************************************************/
+
+#define FW_DL_READY_STATUS (IF_SDIO_IO_RDY | IF_SDIO_DL_RDY)
+
+static int if_sdio_prog_helper(struct if_sdio_card *card,
+ const struct firmware *fw)
+{
+ int ret;
+ unsigned long timeout;
+ u8 *chunk_buffer;
+ u32 chunk_size;
+ const u8 *firmware;
+ size_t size;
+
+ chunk_buffer = kzalloc(64, GFP_KERNEL);
+ if (!chunk_buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ sdio_claim_host(card->func);
+
+ ret = sdio_set_block_size(card->func, 32);
+ if (ret)
+ goto release;
+
+ firmware = fw->data;
+ size = fw->size;
+
+ while (size) {
+ ret = if_sdio_wait_status(card, FW_DL_READY_STATUS);
+ if (ret)
+ goto release;
+
+ /* On some platforms (like Davinci) the chip needs more time
+ * between helper blocks.
+ */
+ mdelay(2);
+
+ chunk_size = min_t(size_t, size, 60);
+
+ *((__le32*)chunk_buffer) = cpu_to_le32(chunk_size);
+ memcpy(chunk_buffer + 4, firmware, chunk_size);
+/*
+ lbs_deb_sdio("sending %d bytes chunk\n", chunk_size);
+*/
+ ret = sdio_writesb(card->func, card->ioport,
+ chunk_buffer, 64);
+ if (ret)
+ goto release;
+
+ firmware += chunk_size;
+ size -= chunk_size;
+ }
+
+ /* an empty block marks the end of the transfer */
+ memset(chunk_buffer, 0, 4);
+ ret = sdio_writesb(card->func, card->ioport, chunk_buffer, 64);
+ if (ret)
+ goto release;
+
+ lbs_deb_sdio("waiting for helper to boot...\n");
+
+ /* wait for the helper to boot by looking at the size register */
+ timeout = jiffies + HZ;
+ while (1) {
+ u16 req_size;
+
+ req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret);
+ if (ret)
+ goto release;
+
+ req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8;
+ if (ret)
+ goto release;
+
+ if (req_size != 0)
+ break;
+
+ if (time_after(jiffies, timeout)) {
+ ret = -ETIMEDOUT;
+ goto release;
+ }
+
+ msleep(10);
+ }
+
+ ret = 0;
+
+release:
+ sdio_release_host(card->func);
+ kfree(chunk_buffer);
+
+out:
+ if (ret)
+ pr_err("failed to load helper firmware\n");
+
+ return ret;
+}
+
+static int if_sdio_prog_real(struct if_sdio_card *card,
+ const struct firmware *fw)
+{
+ int ret;
+ unsigned long timeout;
+ u8 *chunk_buffer;
+ u32 chunk_size;
+ const u8 *firmware;
+ size_t size, req_size;
+
+ chunk_buffer = kzalloc(512, GFP_KERNEL);
+ if (!chunk_buffer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ sdio_claim_host(card->func);
+
+ ret = sdio_set_block_size(card->func, 32);
+ if (ret)
+ goto release;
+
+ firmware = fw->data;
+ size = fw->size;
+
+ while (size) {
+ timeout = jiffies + HZ;
+ while (1) {
+ ret = if_sdio_wait_status(card, FW_DL_READY_STATUS);
+ if (ret)
+ goto release;
+
+ req_size = sdio_readb(card->func, IF_SDIO_RD_BASE,
+ &ret);
+ if (ret)
+ goto release;
+
+ req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1,
+ &ret) << 8;
+ if (ret)
+ goto release;
+
+ /*
+ * For SD8688 wait until the length is not 0, 1 or 2
+ * before downloading the first FW block,
+ * since BOOT code writes the register to indicate the
+ * helper/FW download winner,
+ * the value could be 1 or 2 (Func1 or Func2).
+ */
+ if ((size != fw->size) || (req_size > 2))
+ break;
+ if (time_after(jiffies, timeout)) {
+ ret = -ETIMEDOUT;
+ goto release;
+ }
+ mdelay(1);
+ }
+
+/*
+ lbs_deb_sdio("firmware wants %d bytes\n", (int)req_size);
+*/
+ if (req_size == 0) {
+ lbs_deb_sdio("firmware helper gave up early\n");
+ ret = -EIO;
+ goto release;
+ }
+
+ if (req_size & 0x01) {
+ lbs_deb_sdio("firmware helper signalled error\n");
+ ret = -EIO;
+ goto release;
+ }
+
+ if (req_size > size)
+ req_size = size;
+
+ while (req_size) {
+ chunk_size = min_t(size_t, req_size, 512);
+
+ memcpy(chunk_buffer, firmware, chunk_size);
+/*
+ lbs_deb_sdio("sending %d bytes (%d bytes) chunk\n",
+ chunk_size, (chunk_size + 31) / 32 * 32);
+*/
+ ret = sdio_writesb(card->func, card->ioport,
+ chunk_buffer, roundup(chunk_size, 32));
+ if (ret)
+ goto release;
+
+ firmware += chunk_size;
+ size -= chunk_size;
+ req_size -= chunk_size;
+ }
+ }
+
+ ret = 0;
+
+ lbs_deb_sdio("waiting for firmware to boot...\n");
+
+ /* wait for the firmware to boot */
+ timeout = jiffies + HZ;
+ while (1) {
+ u16 scratch;
+
+ scratch = if_sdio_read_scratch(card, &ret);
+ if (ret)
+ goto release;
+
+ if (scratch == IF_SDIO_FIRMWARE_OK)
+ break;
+
+ if (time_after(jiffies, timeout)) {
+ ret = -ETIMEDOUT;
+ goto release;
+ }
+
+ msleep(10);
+ }
+
+ ret = 0;
+
+release:
+ sdio_release_host(card->func);
+ kfree(chunk_buffer);
+
+out:
+ if (ret)
+ pr_err("failed to load firmware\n");
+
+ return ret;
+}
+
+static void if_sdio_do_prog_firmware(struct lbs_private *priv, int ret,
+ const struct firmware *helper,
+ const struct firmware *mainfw)
+{
+ struct if_sdio_card *card = priv->card;
+
+ if (ret) {
+ pr_err("failed to find firmware (%d)\n", ret);
+ return;
+ }
+
+ ret = if_sdio_prog_helper(card, helper);
+ if (ret)
+ return;
+
+ lbs_deb_sdio("Helper firmware loaded\n");
+
+ ret = if_sdio_prog_real(card, mainfw);
+ if (ret)
+ return;
+
+ lbs_deb_sdio("Firmware loaded\n");
+ if_sdio_finish_power_on(card);
+}
+
+static int if_sdio_prog_firmware(struct if_sdio_card *card)
+{
+ int ret;
+ u16 scratch;
+
+ /*
+ * Disable interrupts
+ */
+ sdio_claim_host(card->func);
+ sdio_writeb(card->func, 0x00, IF_SDIO_H_INT_MASK, &ret);
+ sdio_release_host(card->func);
+
+ sdio_claim_host(card->func);
+ scratch = if_sdio_read_scratch(card, &ret);
+ sdio_release_host(card->func);
+
+ lbs_deb_sdio("firmware status = %#x\n", scratch);
+ lbs_deb_sdio("scratch ret = %d\n", ret);
+
+ if (ret)
+ goto out;
+
+
+ /*
+ * The manual clearly describes that FEDC is the right code to use
+ * to detect firmware presence, but for SD8686 it is not that simple.
+ * Scratch is also used to store the RX packet length, so we lose
+ * the FEDC value early on. So we use a non-zero check in order
+ * to validate firmware presence.
+ * Additionally, the SD8686 in the Gumstix always has the high scratch
+ * bit set, even when the firmware is not loaded. So we have to
+ * exclude that from the test.
+ */
+ if (scratch == IF_SDIO_FIRMWARE_OK) {
+ lbs_deb_sdio("firmware already loaded\n");
+ if_sdio_finish_power_on(card);
+ return 0;
+ } else if ((card->model == MODEL_8686) && (scratch & 0x7fff)) {
+ lbs_deb_sdio("firmware may be running\n");
+ if_sdio_finish_power_on(card);
+ return 0;
+ }
+
+ ret = lbs_get_firmware_async(card->priv, &card->func->dev, card->model,
+ fw_table, if_sdio_do_prog_firmware);
+
+out:
+ return ret;
+}
+
+/********************************************************************/
+/* Power management */
+/********************************************************************/
+
+/* Finish power on sequence (after firmware is loaded) */
+static void if_sdio_finish_power_on(struct if_sdio_card *card)
+{
+ struct sdio_func *func = card->func;
+ struct lbs_private *priv = card->priv;
+ int ret;
+
+ sdio_claim_host(func);
+ sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE);
+
+ /*
+ * Get rx_unit if the chip is SD8688 or newer.
+ * SD8385 & SD8686 do not have rx_unit.
+ */
+ if ((card->model != MODEL_8385)
+ && (card->model != MODEL_8686))
+ card->rx_unit = if_sdio_read_rx_unit(card);
+ else
+ card->rx_unit = 0;
+
+ /*
+ * Set up the interrupt handler late.
+ *
+ * If we set it up earlier, the (buggy) hardware generates a spurious
+ * interrupt, even before the interrupt has been enabled, with
+ * CCCR_INTx = 0.
+ *
+ * We register the interrupt handler late so that we can handle any
+ * spurious interrupts, and also to avoid generation of that known
+ * spurious interrupt in the first place.
+ */
+ ret = sdio_claim_irq(func, if_sdio_interrupt);
+ if (ret)
+ goto release;
+
+ /*
+ * Enable interrupts now that everything is set up
+ */
+ sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret);
+ if (ret)
+ goto release_irq;
+
+ sdio_release_host(func);
+
+ /* Set fw_ready before queuing any commands so that
+ * lbs_thread won't block from sending them to firmware.
+ */
+ priv->fw_ready = 1;
+
+ /*
+ * FUNC_INIT is required for SD8688 WLAN/BT multiple functions
+ */
+ if (card->model == MODEL_8688) {
+ struct cmd_header cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ lbs_deb_sdio("send function INIT command\n");
+ if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd),
+ lbs_cmd_copyback, (unsigned long) &cmd))
+ netdev_alert(priv->dev, "CMD_FUNC_INIT cmd failed\n");
+ }
+
+ wake_up(&card->pwron_waitq);
+
+ if (!card->started) {
+ ret = lbs_start_card(priv);
+ if_sdio_power_off(card);
+ if (ret == 0) {
+ card->started = true;
+ /* Tell PM core that we don't need the card to be
+ * powered now */
+ pm_runtime_put(&func->dev);
+ }
+ }
+
+ return;
+
+release_irq:
+ sdio_release_irq(func);
+release:
+ sdio_release_host(func);
+}
+
+static int if_sdio_power_on(struct if_sdio_card *card)
+{
+ struct sdio_func *func = card->func;
+ struct mmc_host *host = func->card->host;
+ int ret;
+
+ sdio_claim_host(func);
+
+ ret = sdio_enable_func(func);
+ if (ret)
+ goto release;
+
+ /* For 1-bit transfers to the 8686 model, we need to enable the
+ * interrupt flag in the CCCR register. Set the MMC_QUIRK_LENIENT_FN0
+ * bit to allow access to non-vendor registers. */
+ if ((card->model == MODEL_8686) &&
+ (host->caps & MMC_CAP_SDIO_IRQ) &&
+ (host->ios.bus_width == MMC_BUS_WIDTH_1)) {
+ u8 reg;
+
+ func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+ reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret);
+ if (ret)
+ goto disable;
+
+ reg |= SDIO_BUS_ECSI;
+ sdio_f0_writeb(func, reg, SDIO_CCCR_IF, &ret);
+ if (ret)
+ goto disable;
+ }
+
+ card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret);
+ if (ret)
+ goto disable;
+
+ card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8;
+ if (ret)
+ goto disable;
+
+ card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16;
+ if (ret)
+ goto disable;
+
+ sdio_release_host(func);
+ ret = if_sdio_prog_firmware(card);
+ if (ret) {
+ sdio_claim_host(func);
+ goto disable;
+ }
+
+ return 0;
+
+disable:
+ sdio_disable_func(func);
+release:
+ sdio_release_host(func);
+ return ret;
+}
+
+static int if_sdio_power_off(struct if_sdio_card *card)
+{
+ struct sdio_func *func = card->func;
+ struct lbs_private *priv = card->priv;
+
+ priv->fw_ready = 0;
+
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+ return 0;
+}
+
+
+/*******************************************************************/
+/* Libertas callbacks */
+/*******************************************************************/
+
+static int if_sdio_host_to_card(struct lbs_private *priv,
+ u8 type, u8 *buf, u16 nb)
+{
+ int ret;
+ struct if_sdio_card *card;
+ struct if_sdio_packet *packet, *cur;
+ u16 size;
+ unsigned long flags;
+
+ card = priv->card;
+
+ if (nb > (65536 - sizeof(struct if_sdio_packet) - 4)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * The transfer must be in one transaction or the firmware
+ * goes suicidal. There's no way to guarantee that for all
+ * controllers, but we can at least try.
+ */
+ size = sdio_align_size(card->func, nb + 4);
+
+ packet = kzalloc(sizeof(struct if_sdio_packet) + size,
+ GFP_ATOMIC);
+ if (!packet) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ packet->next = NULL;
+ packet->nb = size;
+
+ /*
+ * SDIO specific header.
+ */
+ packet->buffer[0] = (nb + 4) & 0xff;
+ packet->buffer[1] = ((nb + 4) >> 8) & 0xff;
+ packet->buffer[2] = type;
+ packet->buffer[3] = 0;
+
+ memcpy(packet->buffer + 4, buf, nb);
+
+ spin_lock_irqsave(&card->lock, flags);
+
+ if (!card->packets)
+ card->packets = packet;
+ else {
+ cur = card->packets;
+ while (cur->next)
+ cur = cur->next;
+ cur->next = packet;
+ }
+
+ switch (type) {
+ case MVMS_CMD:
+ priv->dnld_sent = DNLD_CMD_SENT;
+ break;
+ case MVMS_DAT:
+ priv->dnld_sent = DNLD_DATA_SENT;
+ break;
+ default:
+ lbs_deb_sdio("unknown packet type %d\n", (int)type);
+ }
+
+ spin_unlock_irqrestore(&card->lock, flags);
+
+ queue_work(card->workqueue, &card->packet_worker);
+
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int if_sdio_enter_deep_sleep(struct lbs_private *priv)
+{
+ int ret = -1;
+ struct cmd_header cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ lbs_deb_sdio("send DEEP_SLEEP command\n");
+ ret = __lbs_cmd(priv, CMD_802_11_DEEP_SLEEP, &cmd, sizeof(cmd),
+ lbs_cmd_copyback, (unsigned long) &cmd);
+ if (ret)
+ netdev_err(priv->dev, "DEEP_SLEEP cmd failed\n");
+
+ mdelay(200);
+ return ret;
+}
+
+static int if_sdio_exit_deep_sleep(struct lbs_private *priv)
+{
+ struct if_sdio_card *card = priv->card;
+ int ret = -1;
+
+ sdio_claim_host(card->func);
+
+ sdio_writeb(card->func, HOST_POWER_UP, CONFIGURATION_REG, &ret);
+ if (ret)
+ netdev_err(priv->dev, "sdio_writeb failed!\n");
+
+ sdio_release_host(card->func);
+
+ return ret;
+}
+
+static int if_sdio_reset_deep_sleep_wakeup(struct lbs_private *priv)
+{
+ struct if_sdio_card *card = priv->card;
+ int ret = -1;
+
+ sdio_claim_host(card->func);
+
+ sdio_writeb(card->func, 0, CONFIGURATION_REG, &ret);
+ if (ret)
+ netdev_err(priv->dev, "sdio_writeb failed!\n");
+
+ sdio_release_host(card->func);
+
+ return ret;
+
+}
+
+static struct mmc_host *reset_host;
+
+static void if_sdio_reset_card_worker(struct work_struct *work)
+{
+ /*
+ * The actual reset operation must be run outside of lbs_thread. This
+ * is because mmc_remove_host() will cause the device to be instantly
+ * destroyed, and the libertas driver then needs to end lbs_thread,
+ * leading to a deadlock.
+ *
+ * We run it in a workqueue totally independent from the if_sdio_card
+ * instance for that reason.
+ */
+
+ pr_info("Resetting card...");
+ mmc_remove_host(reset_host);
+ mmc_add_host(reset_host);
+}
+static DECLARE_WORK(card_reset_work, if_sdio_reset_card_worker);
+
+static void if_sdio_reset_card(struct lbs_private *priv)
+{
+ struct if_sdio_card *card = priv->card;
+
+ if (work_pending(&card_reset_work))
+ return;
+
+ reset_host = card->func->card->host;
+ schedule_work(&card_reset_work);
+}
+
+static int if_sdio_power_save(struct lbs_private *priv)
+{
+ struct if_sdio_card *card = priv->card;
+ int ret;
+
+ flush_workqueue(card->workqueue);
+
+ ret = if_sdio_power_off(card);
+
+ /* Let runtime PM know the card is powered off */
+ pm_runtime_put_sync(&card->func->dev);
+
+ return ret;
+}
+
+static int if_sdio_power_restore(struct lbs_private *priv)
+{
+ struct if_sdio_card *card = priv->card;
+ int r;
+
+ /* Make sure the card will not be powered off by runtime PM */
+ pm_runtime_get_sync(&card->func->dev);
+
+ r = if_sdio_power_on(card);
+ if (r)
+ return r;
+
+ wait_event(card->pwron_waitq, priv->fw_ready);
+ return 0;
+}
+
+
+/*******************************************************************/
+/* SDIO callbacks */
+/*******************************************************************/
+
+static void if_sdio_interrupt(struct sdio_func *func)
+{
+ int ret;
+ struct if_sdio_card *card;
+ u8 cause;
+
+ card = sdio_get_drvdata(func);
+
+ cause = sdio_readb(card->func, IF_SDIO_H_INT_STATUS, &ret);
+ if (ret || !cause)
+ return;
+
+ lbs_deb_sdio("interrupt: 0x%X\n", (unsigned)cause);
+
+ sdio_writeb(card->func, ~cause, IF_SDIO_H_INT_STATUS, &ret);
+ if (ret)
+ return;
+
+ /*
+ * Ignore the define name, this really means the card has
+ * successfully received the command.
+ */
+ card->priv->is_activity_detected = 1;
+ if (cause & IF_SDIO_H_INT_DNLD)
+ lbs_host_to_card_done(card->priv);
+
+
+ if (cause & IF_SDIO_H_INT_UPLD) {
+ ret = if_sdio_card_to_host(card);
+ if (ret)
+ return;
+ }
+}
+
+static int if_sdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct if_sdio_card *card;
+ struct lbs_private *priv;
+ int ret, i;
+ unsigned int model;
+ struct if_sdio_packet *packet;
+
+ for (i = 0;i < func->card->num_info;i++) {
+ if (sscanf(func->card->info[i],
+ "802.11 SDIO ID: %x", &model) == 1)
+ break;
+ if (sscanf(func->card->info[i],
+ "ID: %x", &model) == 1)
+ break;
+ if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) {
+ model = MODEL_8385;
+ break;
+ }
+ }
+
+ if (i == func->card->num_info) {
+ pr_err("unable to identify card model\n");
+ return -ENODEV;
+ }
+
+ card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ card->func = func;
+ card->model = model;
+
+ switch (card->model) {
+ case MODEL_8385:
+ card->scratch_reg = IF_SDIO_SCRATCH_OLD;
+ break;
+ case MODEL_8686:
+ card->scratch_reg = IF_SDIO_SCRATCH;
+ break;
+ case MODEL_8688:
+ default: /* for newer chipsets */
+ card->scratch_reg = IF_SDIO_FW_STATUS;
+ break;
+ }
+
+ spin_lock_init(&card->lock);
+ card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0);
+ if (unlikely(!card->workqueue)) {
+ ret = -ENOMEM;
+ goto err_queue;
+ }
+ INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
+ init_waitqueue_head(&card->pwron_waitq);
+
+ /* Check if we support this card */
+ for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
+ if (card->model == fw_table[i].model)
+ break;
+ }
+ if (i == ARRAY_SIZE(fw_table)) {
+ pr_err("unknown card model 0x%x\n", card->model);
+ ret = -ENODEV;
+ goto free;
+ }
+
+ sdio_set_drvdata(func, card);
+
+ lbs_deb_sdio("class = 0x%X, vendor = 0x%X, "
+ "device = 0x%X, model = 0x%X, ioport = 0x%X\n",
+ func->class, func->vendor, func->device,
+ model, (unsigned)card->ioport);
+
+
+ priv = lbs_add_card(card, &func->dev);
+ if (IS_ERR(priv)) {
+ ret = PTR_ERR(priv);
+ goto free;
+ }
+
+ card->priv = priv;
+
+ priv->card = card;
+ priv->hw_host_to_card = if_sdio_host_to_card;
+ priv->enter_deep_sleep = if_sdio_enter_deep_sleep;
+ priv->exit_deep_sleep = if_sdio_exit_deep_sleep;
+ priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;
+ priv->reset_card = if_sdio_reset_card;
+ priv->power_save = if_sdio_power_save;
+ priv->power_restore = if_sdio_power_restore;
+ priv->is_polling = !(func->card->host->caps & MMC_CAP_SDIO_IRQ);
+ ret = if_sdio_power_on(card);
+ if (ret)
+ goto err_activate_card;
+
+out:
+ return ret;
+
+err_activate_card:
+ flush_workqueue(card->workqueue);
+ lbs_remove_card(priv);
+free:
+ destroy_workqueue(card->workqueue);
+err_queue:
+ while (card->packets) {
+ packet = card->packets;
+ card->packets = card->packets->next;
+ kfree(packet);
+ }
+
+ kfree(card);
+
+ goto out;
+}
+
+static void if_sdio_remove(struct sdio_func *func)
+{
+ struct if_sdio_card *card;
+ struct if_sdio_packet *packet;
+
+ card = sdio_get_drvdata(func);
+
+ /* Undo decrement done above in if_sdio_probe */
+ pm_runtime_get_noresume(&func->dev);
+
+ if (user_rmmod && (card->model == MODEL_8688)) {
+ /*
+ * FUNC_SHUTDOWN is required for SD8688 WLAN/BT
+ * multiple functions
+ */
+ struct cmd_header cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ lbs_deb_sdio("send function SHUTDOWN command\n");
+ if (__lbs_cmd(card->priv, CMD_FUNC_SHUTDOWN,
+ &cmd, sizeof(cmd), lbs_cmd_copyback,
+ (unsigned long) &cmd))
+ pr_alert("CMD_FUNC_SHUTDOWN cmd failed\n");
+ }
+
+
+ lbs_deb_sdio("call remove card\n");
+ lbs_stop_card(card->priv);
+ lbs_remove_card(card->priv);
+
+ destroy_workqueue(card->workqueue);
+
+ while (card->packets) {
+ packet = card->packets;
+ card->packets = card->packets->next;
+ kfree(packet);
+ }
+
+ kfree(card);
+}
+
+static int if_sdio_suspend(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct if_sdio_card *card = sdio_get_drvdata(func);
+ struct lbs_private *priv = card->priv;
+ int ret;
+
+ mmc_pm_flag_t flags = sdio_get_host_pm_caps(func);
+ priv->power_up_on_resume = false;
+
+ /* If we're powered off anyway, just let the mmc layer remove the
+ * card. */
+ if (!lbs_iface_active(priv)) {
+ if (priv->fw_ready) {
+ priv->power_up_on_resume = true;
+ if_sdio_power_off(card);
+ }
+
+ return 0;
+ }
+
+ dev_info(dev, "%s: suspend: PM flags = 0x%x\n",
+ sdio_func_id(func), flags);
+
+ /* If we aren't being asked to wake on anything, we should bail out
+ * and let the SD stack power down the card.
+ */
+ if (priv->wol_criteria == EHS_REMOVE_WAKEUP) {
+ dev_info(dev, "Suspend without wake params -- powering down card\n");
+ if (priv->fw_ready) {
+ ret = lbs_suspend(priv);
+ if (ret)
+ return ret;
+
+ priv->power_up_on_resume = true;
+ if_sdio_power_off(card);
+ }
+
+ return 0;
+ }
+
+ if (!(flags & MMC_PM_KEEP_POWER)) {
+ dev_err(dev, "%s: cannot remain alive while host is suspended\n",
+ sdio_func_id(func));
+ return -ENOSYS;
+ }
+
+ ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+ if (ret)
+ return ret;
+
+ ret = lbs_suspend(priv);
+ if (ret)
+ return ret;
+
+ return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
+}
+
+static int if_sdio_resume(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct if_sdio_card *card = sdio_get_drvdata(func);
+ int ret;
+
+ dev_info(dev, "%s: resume: we're back\n", sdio_func_id(func));
+
+ if (card->priv->power_up_on_resume) {
+ if_sdio_power_on(card);
+ wait_event(card->pwron_waitq, card->priv->fw_ready);
+ }
+
+ ret = lbs_resume(card->priv);
+
+ return ret;
+}
+
+static const struct dev_pm_ops if_sdio_pm_ops = {
+ .suspend = if_sdio_suspend,
+ .resume = if_sdio_resume,
+};
+
+static struct sdio_driver if_sdio_driver = {
+ .name = "libertas_sdio",
+ .id_table = if_sdio_ids,
+ .probe = if_sdio_probe,
+ .remove = if_sdio_remove,
+ .drv = {
+ .pm = &if_sdio_pm_ops,
+ },
+};
+
+/*******************************************************************/
+/* Module functions */
+/*******************************************************************/
+
+static int __init if_sdio_init_module(void)
+{
+ int ret = 0;
+
+ printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n");
+ printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n");
+
+ ret = sdio_register_driver(&if_sdio_driver);
+
+ /* Clear the flag in case user removes the card. */
+ user_rmmod = 0;
+
+ return ret;
+}
+
+static void __exit if_sdio_exit_module(void)
+{
+ /* Set the flag as user is removing this module. */
+ user_rmmod = 1;
+
+ cancel_work_sync(&card_reset_work);
+
+ sdio_unregister_driver(&if_sdio_driver);
+}
+
+module_init(if_sdio_init_module);
+module_exit(if_sdio_exit_module);
+
+MODULE_DESCRIPTION("Libertas SDIO WLAN Driver");
+MODULE_AUTHOR("Pierre Ossman");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.h b/drivers/net/wireless/marvell/libertas/if_sdio.h
new file mode 100644
index 000000000..a78fde1f9
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_sdio.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/net/wireless/libertas/if_sdio.h
+ *
+ * Copyright 2007 Pierre Ossman
+ */
+
+#ifndef _LBS_IF_SDIO_H
+#define _LBS_IF_SDIO_H
+
+#define IF_SDIO_IOPORT 0x00
+
+#define IF_SDIO_H_INT_MASK 0x04
+#define IF_SDIO_H_INT_OFLOW 0x08
+#define IF_SDIO_H_INT_UFLOW 0x04
+#define IF_SDIO_H_INT_DNLD 0x02
+#define IF_SDIO_H_INT_UPLD 0x01
+
+#define IF_SDIO_H_INT_STATUS 0x05
+#define IF_SDIO_H_INT_RSR 0x06
+#define IF_SDIO_H_INT_STATUS2 0x07
+
+#define IF_SDIO_RD_BASE 0x10
+
+#define IF_SDIO_STATUS 0x20
+#define IF_SDIO_IO_RDY 0x08
+#define IF_SDIO_CIS_RDY 0x04
+#define IF_SDIO_UL_RDY 0x02
+#define IF_SDIO_DL_RDY 0x01
+
+#define IF_SDIO_C_INT_MASK 0x24
+#define IF_SDIO_C_INT_STATUS 0x28
+#define IF_SDIO_C_INT_RSR 0x2C
+
+#define IF_SDIO_SCRATCH 0x34
+#define IF_SDIO_SCRATCH_OLD 0x80fe
+#define IF_SDIO_FW_STATUS 0x40
+#define IF_SDIO_FIRMWARE_OK 0xfedc
+
+#define IF_SDIO_RX_LEN 0x42
+#define IF_SDIO_RX_UNIT 0x43
+
+#define IF_SDIO_EVENT 0x80fc
+
+#define IF_SDIO_BLOCK_SIZE 256
+#define CONFIGURATION_REG 0x03
+#define HOST_POWER_UP (0x1U << 1)
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/if_spi.c b/drivers/net/wireless/marvell/libertas/if_spi.c
new file mode 100644
index 000000000..cd9f8ecf1
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_spi.c
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/net/wireless/libertas/if_spi.c
+ *
+ * Driver for Marvell SPI WLAN cards.
+ *
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Authors:
+ * Andrey Yurovsky <andrey@cozybit.com>
+ * Colin McCabe <colin@cozybit.com>
+ *
+ * Inspired by if_sdio.c, Copyright 2007-2008 Pierre Ossman
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hardirq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/spi/libertas_spi.h>
+#include <linux/spi/spi.h>
+
+#include "host.h"
+#include "decl.h"
+#include "defs.h"
+#include "dev.h"
+#include "if_spi.h"
+
+struct if_spi_packet {
+ struct list_head list;
+ u16 blen;
+ u8 buffer[] __aligned(4);
+};
+
+struct if_spi_card {
+ struct spi_device *spi;
+ struct lbs_private *priv;
+ struct libertas_spi_platform_data *pdata;
+
+ /* The card ID and card revision, as reported by the hardware. */
+ u16 card_id;
+ u8 card_rev;
+
+ /* The last time that we initiated an SPU operation */
+ unsigned long prev_xfer_time;
+
+ int use_dummy_writes;
+ unsigned long spu_port_delay;
+ unsigned long spu_reg_delay;
+
+ /* Handles all SPI communication (except for FW load) */
+ struct workqueue_struct *workqueue;
+ struct work_struct packet_work;
+ struct work_struct resume_work;
+
+ u8 cmd_buffer[IF_SPI_CMD_BUF_SIZE];
+
+ /* A buffer of incoming packets from libertas core.
+ * Since we can't sleep in hw_host_to_card, we have to buffer
+ * them. */
+ struct list_head cmd_packet_list;
+ struct list_head data_packet_list;
+
+ /* Protects cmd_packet_list and data_packet_list */
+ spinlock_t buffer_lock;
+
+ /* True is card suspended */
+ u8 suspended;
+};
+
+static void free_if_spi_card(struct if_spi_card *card)
+{
+ struct list_head *cursor, *next;
+ struct if_spi_packet *packet;
+
+ list_for_each_safe(cursor, next, &card->cmd_packet_list) {
+ packet = container_of(cursor, struct if_spi_packet, list);
+ list_del(&packet->list);
+ kfree(packet);
+ }
+ list_for_each_safe(cursor, next, &card->data_packet_list) {
+ packet = container_of(cursor, struct if_spi_packet, list);
+ list_del(&packet->list);
+ kfree(packet);
+ }
+ kfree(card);
+}
+
+#define MODEL_8385 0x04
+#define MODEL_8686 0x0b
+#define MODEL_8688 0x10
+
+static const struct lbs_fw_table fw_table[] = {
+ { MODEL_8385, "libertas/gspi8385_helper.bin", "libertas/gspi8385.bin" },
+ { MODEL_8385, "libertas/gspi8385_hlp.bin", "libertas/gspi8385.bin" },
+ { MODEL_8686, "libertas/gspi8686_v9_helper.bin", "libertas/gspi8686_v9.bin" },
+ { MODEL_8686, "libertas/gspi8686_hlp.bin", "libertas/gspi8686.bin" },
+ { MODEL_8688, "libertas/gspi8688_helper.bin", "libertas/gspi8688.bin" },
+ { 0, NULL, NULL }
+};
+MODULE_FIRMWARE("libertas/gspi8385_helper.bin");
+MODULE_FIRMWARE("libertas/gspi8385_hlp.bin");
+MODULE_FIRMWARE("libertas/gspi8385.bin");
+MODULE_FIRMWARE("libertas/gspi8686_v9_helper.bin");
+MODULE_FIRMWARE("libertas/gspi8686_v9.bin");
+MODULE_FIRMWARE("libertas/gspi8686_hlp.bin");
+MODULE_FIRMWARE("libertas/gspi8686.bin");
+MODULE_FIRMWARE("libertas/gspi8688_helper.bin");
+MODULE_FIRMWARE("libertas/gspi8688.bin");
+
+
+/*
+ * SPI Interface Unit Routines
+ *
+ * The SPU sits between the host and the WLAN module.
+ * All communication with the firmware is through SPU transactions.
+ *
+ * First we have to put a SPU register name on the bus. Then we can
+ * either read from or write to that register.
+ *
+ */
+
+static void spu_transaction_init(struct if_spi_card *card)
+{
+ if (!time_after(jiffies, card->prev_xfer_time + 1)) {
+ /* Unfortunately, the SPU requires a delay between successive
+ * transactions. If our last transaction was more than a jiffy
+ * ago, we have obviously already delayed enough.
+ * If not, we have to busy-wait to be on the safe side. */
+ ndelay(400);
+ }
+}
+
+static void spu_transaction_finish(struct if_spi_card *card)
+{
+ card->prev_xfer_time = jiffies;
+}
+
+/*
+ * Write out a byte buffer to an SPI register,
+ * using a series of 16-bit transfers.
+ */
+static int spu_write(struct if_spi_card *card, u16 reg, const u8 *buf, int len)
+{
+ int err = 0;
+ __le16 reg_out = cpu_to_le16(reg | IF_SPI_WRITE_OPERATION_MASK);
+ struct spi_message m;
+ struct spi_transfer reg_trans;
+ struct spi_transfer data_trans;
+
+ spi_message_init(&m);
+ memset(&reg_trans, 0, sizeof(reg_trans));
+ memset(&data_trans, 0, sizeof(data_trans));
+
+ /* You must give an even number of bytes to the SPU, even if it
+ * doesn't care about the last one. */
+ BUG_ON(len & 0x1);
+
+ spu_transaction_init(card);
+
+ /* write SPU register index */
+ reg_trans.tx_buf = &reg_out;
+ reg_trans.len = sizeof(reg_out);
+
+ data_trans.tx_buf = buf;
+ data_trans.len = len;
+
+ spi_message_add_tail(&reg_trans, &m);
+ spi_message_add_tail(&data_trans, &m);
+
+ err = spi_sync(card->spi, &m);
+ spu_transaction_finish(card);
+ return err;
+}
+
+static inline int spu_write_u16(struct if_spi_card *card, u16 reg, u16 val)
+{
+ __le16 buff;
+
+ buff = cpu_to_le16(val);
+ return spu_write(card, reg, (u8 *)&buff, sizeof(u16));
+}
+
+static inline int spu_reg_is_port_reg(u16 reg)
+{
+ switch (reg) {
+ case IF_SPI_IO_RDWRPORT_REG:
+ case IF_SPI_CMD_RDWRPORT_REG:
+ case IF_SPI_DATA_RDWRPORT_REG:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int spu_read(struct if_spi_card *card, u16 reg, u8 *buf, int len)
+{
+ unsigned int delay;
+ int err = 0;
+ __le16 reg_out = cpu_to_le16(reg | IF_SPI_READ_OPERATION_MASK);
+ struct spi_message m;
+ struct spi_transfer reg_trans;
+ struct spi_transfer dummy_trans;
+ struct spi_transfer data_trans;
+
+ /*
+ * You must take an even number of bytes from the SPU, even if you
+ * don't care about the last one.
+ */
+ BUG_ON(len & 0x1);
+
+ spu_transaction_init(card);
+
+ spi_message_init(&m);
+ memset(&reg_trans, 0, sizeof(reg_trans));
+ memset(&dummy_trans, 0, sizeof(dummy_trans));
+ memset(&data_trans, 0, sizeof(data_trans));
+
+ /* write SPU register index */
+ reg_trans.tx_buf = &reg_out;
+ reg_trans.len = sizeof(reg_out);
+ spi_message_add_tail(&reg_trans, &m);
+
+ delay = spu_reg_is_port_reg(reg) ? card->spu_port_delay :
+ card->spu_reg_delay;
+ if (card->use_dummy_writes) {
+ /* Clock in dummy cycles while the SPU fills the FIFO */
+ dummy_trans.len = delay / 8;
+ spi_message_add_tail(&dummy_trans, &m);
+ } else {
+ /* Busy-wait while the SPU fills the FIFO */
+ reg_trans.delay.value =
+ DIV_ROUND_UP((100 + (delay * 10)), 1000);
+ reg_trans.delay.unit = SPI_DELAY_UNIT_USECS;
+ }
+
+ /* read in data */
+ data_trans.rx_buf = buf;
+ data_trans.len = len;
+ spi_message_add_tail(&data_trans, &m);
+
+ err = spi_sync(card->spi, &m);
+ spu_transaction_finish(card);
+ return err;
+}
+
+/* Read 16 bits from an SPI register */
+static inline int spu_read_u16(struct if_spi_card *card, u16 reg, u16 *val)
+{
+ __le16 buf;
+ int ret;
+
+ ret = spu_read(card, reg, (u8 *)&buf, sizeof(buf));
+ if (ret == 0)
+ *val = le16_to_cpup(&buf);
+ return ret;
+}
+
+/*
+ * Read 32 bits from an SPI register.
+ * The low 16 bits are read first.
+ */
+static int spu_read_u32(struct if_spi_card *card, u16 reg, u32 *val)
+{
+ __le32 buf;
+ int err;
+
+ err = spu_read(card, reg, (u8 *)&buf, sizeof(buf));
+ if (!err)
+ *val = le32_to_cpup(&buf);
+ return err;
+}
+
+/*
+ * Keep reading 16 bits from an SPI register until you get the correct result.
+ *
+ * If mask = 0, the correct result is any non-zero number.
+ * If mask != 0, the correct result is any number where
+ * number & target_mask == target
+ *
+ * Returns -ETIMEDOUT if a second passes without the correct result.
+ */
+static int spu_wait_for_u16(struct if_spi_card *card, u16 reg,
+ u16 target_mask, u16 target)
+{
+ int err;
+ unsigned long timeout = jiffies + 5*HZ;
+ while (1) {
+ u16 val;
+ err = spu_read_u16(card, reg, &val);
+ if (err)
+ return err;
+ if (target_mask) {
+ if ((val & target_mask) == target)
+ return 0;
+ } else {
+ if (val)
+ return 0;
+ }
+ udelay(100);
+ if (time_after(jiffies, timeout)) {
+ pr_err("%s: timeout with val=%02x, target_mask=%02x, target=%02x\n",
+ __func__, val, target_mask, target);
+ return -ETIMEDOUT;
+ }
+ }
+}
+
+/*
+ * Read 16 bits from an SPI register until you receive a specific value.
+ * Returns -ETIMEDOUT if a 4 tries pass without success.
+ */
+static int spu_wait_for_u32(struct if_spi_card *card, u32 reg, u32 target)
+{
+ int err, try;
+ for (try = 0; try < 4; ++try) {
+ u32 val = 0;
+ err = spu_read_u32(card, reg, &val);
+ if (err)
+ return err;
+ if (val == target)
+ return 0;
+ mdelay(100);
+ }
+ return -ETIMEDOUT;
+}
+
+static int spu_set_interrupt_mode(struct if_spi_card *card,
+ int suppress_host_int,
+ int auto_int)
+{
+ int err = 0;
+
+ /*
+ * We can suppress a host interrupt by clearing the appropriate
+ * bit in the "host interrupt status mask" register
+ */
+ if (suppress_host_int) {
+ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, 0);
+ if (err)
+ return err;
+ } else {
+ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG,
+ IF_SPI_HISM_TX_DOWNLOAD_RDY |
+ IF_SPI_HISM_RX_UPLOAD_RDY |
+ IF_SPI_HISM_CMD_DOWNLOAD_RDY |
+ IF_SPI_HISM_CARDEVENT |
+ IF_SPI_HISM_CMD_UPLOAD_RDY);
+ if (err)
+ return err;
+ }
+
+ /*
+ * If auto-interrupts are on, the completion of certain transactions
+ * will trigger an interrupt automatically. If auto-interrupts
+ * are off, we need to set the "Card Interrupt Cause" register to
+ * trigger a card interrupt.
+ */
+ if (auto_int) {
+ err = spu_write_u16(card, IF_SPI_HOST_INT_CTRL_REG,
+ IF_SPI_HICT_TX_DOWNLOAD_OVER_AUTO |
+ IF_SPI_HICT_RX_UPLOAD_OVER_AUTO |
+ IF_SPI_HICT_CMD_DOWNLOAD_OVER_AUTO |
+ IF_SPI_HICT_CMD_UPLOAD_OVER_AUTO);
+ if (err)
+ return err;
+ } else {
+ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, 0);
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int spu_get_chip_revision(struct if_spi_card *card,
+ u16 *card_id, u8 *card_rev)
+{
+ int err = 0;
+ u32 dev_ctrl;
+ err = spu_read_u32(card, IF_SPI_DEVICEID_CTRL_REG, &dev_ctrl);
+ if (err)
+ return err;
+ *card_id = IF_SPI_DEVICEID_CTRL_REG_TO_CARD_ID(dev_ctrl);
+ *card_rev = IF_SPI_DEVICEID_CTRL_REG_TO_CARD_REV(dev_ctrl);
+ return err;
+}
+
+static int spu_set_bus_mode(struct if_spi_card *card, u16 mode)
+{
+ int err = 0;
+ u16 rval;
+ /* set bus mode */
+ err = spu_write_u16(card, IF_SPI_SPU_BUS_MODE_REG, mode);
+ if (err)
+ return err;
+ /* Check that we were able to read back what we just wrote. */
+ err = spu_read_u16(card, IF_SPI_SPU_BUS_MODE_REG, &rval);
+ if (err)
+ return err;
+ if ((rval & 0xF) != mode) {
+ pr_err("Can't read bus mode register\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int spu_init(struct if_spi_card *card, int use_dummy_writes)
+{
+ int err = 0;
+ u32 delay;
+
+ /*
+ * We have to start up in timed delay mode so that we can safely
+ * read the Delay Read Register.
+ */
+ card->use_dummy_writes = 0;
+ err = spu_set_bus_mode(card,
+ IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING |
+ IF_SPI_BUS_MODE_DELAY_METHOD_TIMED |
+ IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA);
+ if (err)
+ return err;
+ card->spu_port_delay = 1000;
+ card->spu_reg_delay = 1000;
+ err = spu_read_u32(card, IF_SPI_DELAY_READ_REG, &delay);
+ if (err)
+ return err;
+ card->spu_port_delay = delay & 0x0000ffff;
+ card->spu_reg_delay = (delay & 0xffff0000) >> 16;
+
+ /* If dummy clock delay mode has been requested, switch to it now */
+ if (use_dummy_writes) {
+ card->use_dummy_writes = 1;
+ err = spu_set_bus_mode(card,
+ IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING |
+ IF_SPI_BUS_MODE_DELAY_METHOD_DUMMY_CLOCK |
+ IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA);
+ if (err)
+ return err;
+ }
+
+ lbs_deb_spi("Initialized SPU unit. "
+ "spu_port_delay=0x%04lx, spu_reg_delay=0x%04lx\n",
+ card->spu_port_delay, card->spu_reg_delay);
+ return err;
+}
+
+/*
+ * Firmware Loading
+ */
+
+static int if_spi_prog_helper_firmware(struct if_spi_card *card,
+ const struct firmware *firmware)
+{
+ int err = 0;
+ int bytes_remaining;
+ const u8 *fw;
+ u8 temp[HELPER_FW_LOAD_CHUNK_SZ];
+
+ err = spu_set_interrupt_mode(card, 1, 0);
+ if (err)
+ goto out;
+
+ bytes_remaining = firmware->size;
+ fw = firmware->data;
+
+ /* Load helper firmware image */
+ while (bytes_remaining > 0) {
+ /*
+ * Scratch pad 1 should contain the number of bytes we
+ * want to download to the firmware
+ */
+ err = spu_write_u16(card, IF_SPI_SCRATCH_1_REG,
+ HELPER_FW_LOAD_CHUNK_SZ);
+ if (err)
+ goto out;
+
+ err = spu_wait_for_u16(card, IF_SPI_HOST_INT_STATUS_REG,
+ IF_SPI_HIST_CMD_DOWNLOAD_RDY,
+ IF_SPI_HIST_CMD_DOWNLOAD_RDY);
+ if (err)
+ goto out;
+
+ /*
+ * Feed the data into the command read/write port reg
+ * in chunks of 64 bytes
+ */
+ memset(temp, 0, sizeof(temp));
+ memcpy(temp, fw,
+ min(bytes_remaining, HELPER_FW_LOAD_CHUNK_SZ));
+ mdelay(10);
+ err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG,
+ temp, HELPER_FW_LOAD_CHUNK_SZ);
+ if (err)
+ goto out;
+
+ /* Interrupt the boot code */
+ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0);
+ if (err)
+ goto out;
+ err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG,
+ IF_SPI_CIC_CMD_DOWNLOAD_OVER);
+ if (err)
+ goto out;
+ bytes_remaining -= HELPER_FW_LOAD_CHUNK_SZ;
+ fw += HELPER_FW_LOAD_CHUNK_SZ;
+ }
+
+ /*
+ * Once the helper / single stage firmware download is complete,
+ * write 0 to scratch pad 1 and interrupt the
+ * bootloader. This completes the helper download.
+ */
+ err = spu_write_u16(card, IF_SPI_SCRATCH_1_REG, FIRMWARE_DNLD_OK);
+ if (err)
+ goto out;
+ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0);
+ if (err)
+ goto out;
+ err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG,
+ IF_SPI_CIC_CMD_DOWNLOAD_OVER);
+out:
+ if (err)
+ pr_err("failed to load helper firmware (err=%d)\n", err);
+
+ return err;
+}
+
+/*
+ * Returns the length of the next packet the firmware expects us to send.
+ * Sets crc_err if the previous transfer had a CRC error.
+ */
+static int if_spi_prog_main_firmware_check_len(struct if_spi_card *card,
+ int *crc_err)
+{
+ u16 len;
+ int err = 0;
+
+ /*
+ * wait until the host interrupt status register indicates
+ * that we are ready to download
+ */
+ err = spu_wait_for_u16(card, IF_SPI_HOST_INT_STATUS_REG,
+ IF_SPI_HIST_CMD_DOWNLOAD_RDY,
+ IF_SPI_HIST_CMD_DOWNLOAD_RDY);
+ if (err) {
+ pr_err("timed out waiting for host_int_status\n");
+ return err;
+ }
+
+ /* Ask the device how many bytes of firmware it wants. */
+ err = spu_read_u16(card, IF_SPI_SCRATCH_1_REG, &len);
+ if (err)
+ return err;
+
+ if (len > IF_SPI_CMD_BUF_SIZE) {
+ pr_err("firmware load device requested a larger transfer than we are prepared to handle (len = %d)\n",
+ len);
+ return -EIO;
+ }
+ if (len & 0x1) {
+ lbs_deb_spi("%s: crc error\n", __func__);
+ len &= ~0x1;
+ *crc_err = 1;
+ } else
+ *crc_err = 0;
+
+ return len;
+}
+
+static int if_spi_prog_main_firmware(struct if_spi_card *card,
+ const struct firmware *firmware)
+{
+ struct lbs_private *priv = card->priv;
+ int len, prev_len;
+ int bytes, crc_err = 0, err = 0;
+ const u8 *fw;
+ u16 num_crc_errs;
+
+ err = spu_set_interrupt_mode(card, 1, 0);
+ if (err)
+ goto out;
+
+ err = spu_wait_for_u16(card, IF_SPI_SCRATCH_1_REG, 0, 0);
+ if (err) {
+ netdev_err(priv->dev,
+ "%s: timed out waiting for initial scratch reg = 0\n",
+ __func__);
+ goto out;
+ }
+
+ num_crc_errs = 0;
+ prev_len = 0;
+ bytes = firmware->size;
+ fw = firmware->data;
+ while ((len = if_spi_prog_main_firmware_check_len(card, &crc_err))) {
+ if (len < 0) {
+ err = len;
+ goto out;
+ }
+ if (bytes < 0) {
+ /*
+ * If there are no more bytes left, we would normally
+ * expect to have terminated with len = 0
+ */
+ netdev_err(priv->dev,
+ "Firmware load wants more bytes than we have to offer.\n");
+ break;
+ }
+ if (crc_err) {
+ /* Previous transfer failed. */
+ if (++num_crc_errs > MAX_MAIN_FW_LOAD_CRC_ERR) {
+ pr_err("Too many CRC errors encountered in firmware load.\n");
+ err = -EIO;
+ goto out;
+ }
+ } else {
+ /* Previous transfer succeeded. Advance counters. */
+ bytes -= prev_len;
+ fw += prev_len;
+ }
+ if (bytes < len) {
+ memset(card->cmd_buffer, 0, len);
+ memcpy(card->cmd_buffer, fw, bytes);
+ } else
+ memcpy(card->cmd_buffer, fw, len);
+
+ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0);
+ if (err)
+ goto out;
+ err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG,
+ card->cmd_buffer, len);
+ if (err)
+ goto out;
+ err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG ,
+ IF_SPI_CIC_CMD_DOWNLOAD_OVER);
+ if (err)
+ goto out;
+ prev_len = len;
+ }
+ if (bytes > prev_len) {
+ pr_err("firmware load wants fewer bytes than we have to offer\n");
+ }
+
+ /* Confirm firmware download */
+ err = spu_wait_for_u32(card, IF_SPI_SCRATCH_4_REG,
+ SUCCESSFUL_FW_DOWNLOAD_MAGIC);
+ if (err) {
+ pr_err("failed to confirm the firmware download\n");
+ goto out;
+ }
+
+out:
+ if (err)
+ pr_err("failed to load firmware (err=%d)\n", err);
+
+ return err;
+}
+
+/*
+ * SPI Transfer Thread
+ *
+ * The SPI worker handles all SPI transfers, so there is no need for a lock.
+ */
+
+/* Move a command from the card to the host */
+static int if_spi_c2h_cmd(struct if_spi_card *card)
+{
+ struct lbs_private *priv = card->priv;
+ unsigned long flags;
+ int err = 0;
+ u16 len;
+ u8 i;
+
+ /*
+ * We need a buffer big enough to handle whatever people send to
+ * hw_host_to_card
+ */
+ BUILD_BUG_ON(IF_SPI_CMD_BUF_SIZE < LBS_CMD_BUFFER_SIZE);
+ BUILD_BUG_ON(IF_SPI_CMD_BUF_SIZE < LBS_UPLD_SIZE);
+
+ /*
+ * It's just annoying if the buffer size isn't a multiple of 4, because
+ * then we might have len < IF_SPI_CMD_BUF_SIZE but
+ * ALIGN(len, 4) > IF_SPI_CMD_BUF_SIZE
+ */
+ BUILD_BUG_ON(IF_SPI_CMD_BUF_SIZE % 4 != 0);
+
+ /* How many bytes are there to read? */
+ err = spu_read_u16(card, IF_SPI_SCRATCH_2_REG, &len);
+ if (err)
+ goto out;
+ if (!len) {
+ netdev_err(priv->dev, "%s: error: card has no data for host\n",
+ __func__);
+ err = -EINVAL;
+ goto out;
+ } else if (len > IF_SPI_CMD_BUF_SIZE) {
+ netdev_err(priv->dev,
+ "%s: error: response packet too large: %d bytes, but maximum is %d\n",
+ __func__, len, IF_SPI_CMD_BUF_SIZE);
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* Read the data from the WLAN module into our command buffer */
+ err = spu_read(card, IF_SPI_CMD_RDWRPORT_REG,
+ card->cmd_buffer, ALIGN(len, 4));
+ if (err)
+ goto out;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ i = (priv->resp_idx == 0) ? 1 : 0;
+ BUG_ON(priv->resp_len[i]);
+ priv->resp_len[i] = len;
+ memcpy(priv->resp_buf[i], card->cmd_buffer, len);
+ lbs_notify_command_response(priv, i);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+out:
+ if (err)
+ netdev_err(priv->dev, "%s: err=%d\n", __func__, err);
+
+ return err;
+}
+
+/* Move data from the card to the host */
+static int if_spi_c2h_data(struct if_spi_card *card)
+{
+ struct lbs_private *priv = card->priv;
+ struct sk_buff *skb;
+ char *data;
+ u16 len;
+ int err = 0;
+
+ /* How many bytes are there to read? */
+ err = spu_read_u16(card, IF_SPI_SCRATCH_1_REG, &len);
+ if (err)
+ goto out;
+ if (!len) {
+ netdev_err(priv->dev, "%s: error: card has no data for host\n",
+ __func__);
+ err = -EINVAL;
+ goto out;
+ } else if (len > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) {
+ netdev_err(priv->dev,
+ "%s: error: card has %d bytes of data, but our maximum skb size is %zu\n",
+ __func__, len, MRVDRV_ETH_RX_PACKET_BUFFER_SIZE);
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* TODO: should we allocate a smaller skb if we have less data? */
+ skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE);
+ if (!skb) {
+ err = -ENOBUFS;
+ goto out;
+ }
+ skb_reserve(skb, IPFIELD_ALIGN_OFFSET);
+ data = skb_put(skb, len);
+
+ /* Read the data from the WLAN module into our skb... */
+ err = spu_read(card, IF_SPI_DATA_RDWRPORT_REG, data, ALIGN(len, 4));
+ if (err) {
+ dev_kfree_skb(skb);
+ goto out;
+ }
+
+ /* pass the SKB to libertas */
+ err = lbs_process_rxed_packet(card->priv, skb);
+ /* lbs_process_rxed_packet() consumes the skb */
+
+out:
+ if (err)
+ netdev_err(priv->dev, "%s: err=%d\n", __func__, err);
+
+ return err;
+}
+
+/* Move data or a command from the host to the card. */
+static void if_spi_h2c(struct if_spi_card *card,
+ struct if_spi_packet *packet, int type)
+{
+ struct lbs_private *priv = card->priv;
+ int err = 0;
+ u16 port_reg;
+
+ switch (type) {
+ case MVMS_DAT:
+ port_reg = IF_SPI_DATA_RDWRPORT_REG;
+ break;
+ case MVMS_CMD:
+ port_reg = IF_SPI_CMD_RDWRPORT_REG;
+ break;
+ default:
+ netdev_err(priv->dev, "can't transfer buffer of type %d\n",
+ type);
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* Write the data to the card */
+ err = spu_write(card, port_reg, packet->buffer, packet->blen);
+ if (err)
+ goto out;
+
+out:
+ kfree(packet);
+
+ if (err)
+ netdev_err(priv->dev, "%s: error %d\n", __func__, err);
+}
+
+/* Inform the host about a card event */
+static void if_spi_e2h(struct if_spi_card *card)
+{
+ int err = 0;
+ u32 cause;
+ struct lbs_private *priv = card->priv;
+
+ err = spu_read_u32(card, IF_SPI_SCRATCH_3_REG, &cause);
+ if (err)
+ goto out;
+
+ /* re-enable the card event interrupt */
+ spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG,
+ ~IF_SPI_HICU_CARD_EVENT);
+
+ /* generate a card interrupt */
+ spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, IF_SPI_CIC_HOST_EVENT);
+
+ lbs_queue_event(priv, cause & 0xff);
+out:
+ if (err)
+ netdev_err(priv->dev, "%s: error %d\n", __func__, err);
+}
+
+static void if_spi_host_to_card_worker(struct work_struct *work)
+{
+ int err;
+ struct if_spi_card *card;
+ u16 hiStatus;
+ unsigned long flags;
+ struct if_spi_packet *packet;
+ struct lbs_private *priv;
+
+ card = container_of(work, struct if_spi_card, packet_work);
+ priv = card->priv;
+
+ /*
+ * Read the host interrupt status register to see what we
+ * can do.
+ */
+ err = spu_read_u16(card, IF_SPI_HOST_INT_STATUS_REG,
+ &hiStatus);
+ if (err) {
+ netdev_err(priv->dev, "I/O error\n");
+ goto err;
+ }
+
+ if (hiStatus & IF_SPI_HIST_CMD_UPLOAD_RDY) {
+ err = if_spi_c2h_cmd(card);
+ if (err)
+ goto err;
+ }
+ if (hiStatus & IF_SPI_HIST_RX_UPLOAD_RDY) {
+ err = if_spi_c2h_data(card);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * workaround: in PS mode, the card does not set the Command
+ * Download Ready bit, but it sets TX Download Ready.
+ */
+ if (hiStatus & IF_SPI_HIST_CMD_DOWNLOAD_RDY ||
+ (card->priv->psstate != PS_STATE_FULL_POWER &&
+ (hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY))) {
+ /*
+ * This means two things. First of all,
+ * if there was a previous command sent, the card has
+ * successfully received it.
+ * Secondly, it is now ready to download another
+ * command.
+ */
+ lbs_host_to_card_done(card->priv);
+
+ /* Do we have any command packets from the host to send? */
+ packet = NULL;
+ spin_lock_irqsave(&card->buffer_lock, flags);
+ if (!list_empty(&card->cmd_packet_list)) {
+ packet = (struct if_spi_packet *)(card->
+ cmd_packet_list.next);
+ list_del(&packet->list);
+ }
+ spin_unlock_irqrestore(&card->buffer_lock, flags);
+
+ if (packet)
+ if_spi_h2c(card, packet, MVMS_CMD);
+ }
+ if (hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY) {
+ /* Do we have any data packets from the host to send? */
+ packet = NULL;
+ spin_lock_irqsave(&card->buffer_lock, flags);
+ if (!list_empty(&card->data_packet_list)) {
+ packet = (struct if_spi_packet *)(card->
+ data_packet_list.next);
+ list_del(&packet->list);
+ }
+ spin_unlock_irqrestore(&card->buffer_lock, flags);
+
+ if (packet)
+ if_spi_h2c(card, packet, MVMS_DAT);
+ }
+ if (hiStatus & IF_SPI_HIST_CARD_EVENT)
+ if_spi_e2h(card);
+
+err:
+ if (err)
+ netdev_err(priv->dev, "%s: got error %d\n", __func__, err);
+}
+
+/*
+ * Host to Card
+ *
+ * Called from Libertas to transfer some data to the WLAN device
+ * We can't sleep here.
+ */
+static int if_spi_host_to_card(struct lbs_private *priv,
+ u8 type, u8 *buf, u16 nb)
+{
+ int err = 0;
+ unsigned long flags;
+ struct if_spi_card *card = priv->card;
+ struct if_spi_packet *packet;
+ u16 blen;
+
+ if (nb == 0) {
+ netdev_err(priv->dev, "%s: invalid size requested: %d\n",
+ __func__, nb);
+ err = -EINVAL;
+ goto out;
+ }
+ blen = ALIGN(nb, 4);
+ packet = kzalloc(sizeof(struct if_spi_packet) + blen, GFP_ATOMIC);
+ if (!packet) {
+ err = -ENOMEM;
+ goto out;
+ }
+ packet->blen = blen;
+ memcpy(packet->buffer, buf, nb);
+ memset(packet->buffer + nb, 0, blen - nb);
+
+ switch (type) {
+ case MVMS_CMD:
+ priv->dnld_sent = DNLD_CMD_SENT;
+ spin_lock_irqsave(&card->buffer_lock, flags);
+ list_add_tail(&packet->list, &card->cmd_packet_list);
+ spin_unlock_irqrestore(&card->buffer_lock, flags);
+ break;
+ case MVMS_DAT:
+ priv->dnld_sent = DNLD_DATA_SENT;
+ spin_lock_irqsave(&card->buffer_lock, flags);
+ list_add_tail(&packet->list, &card->data_packet_list);
+ spin_unlock_irqrestore(&card->buffer_lock, flags);
+ break;
+ default:
+ kfree(packet);
+ netdev_err(priv->dev, "can't transfer buffer of type %d\n",
+ type);
+ err = -EINVAL;
+ break;
+ }
+
+ /* Queue spi xfer work */
+ queue_work(card->workqueue, &card->packet_work);
+out:
+ return err;
+}
+
+/*
+ * Host Interrupts
+ *
+ * Service incoming interrupts from the WLAN device. We can't sleep here, so
+ * don't try to talk on the SPI bus, just queue the SPI xfer work.
+ */
+static irqreturn_t if_spi_host_interrupt(int irq, void *dev_id)
+{
+ struct if_spi_card *card = dev_id;
+
+ queue_work(card->workqueue, &card->packet_work);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * SPI callbacks
+ */
+
+static int if_spi_init_card(struct if_spi_card *card)
+{
+ struct lbs_private *priv = card->priv;
+ int err, i;
+ u32 scratch;
+ const struct firmware *helper = NULL;
+ const struct firmware *mainfw = NULL;
+
+ err = spu_init(card, card->pdata->use_dummy_writes);
+ if (err)
+ goto out;
+ err = spu_get_chip_revision(card, &card->card_id, &card->card_rev);
+ if (err)
+ goto out;
+
+ err = spu_read_u32(card, IF_SPI_SCRATCH_4_REG, &scratch);
+ if (err)
+ goto out;
+ if (scratch == SUCCESSFUL_FW_DOWNLOAD_MAGIC)
+ lbs_deb_spi("Firmware is already loaded for "
+ "Marvell WLAN 802.11 adapter\n");
+ else {
+ /* Check if we support this card */
+ for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
+ if (card->card_id == fw_table[i].model)
+ break;
+ }
+ if (i == ARRAY_SIZE(fw_table)) {
+ netdev_err(priv->dev, "Unsupported chip_id: 0x%02x\n",
+ card->card_id);
+ err = -ENODEV;
+ goto out;
+ }
+
+ err = lbs_get_firmware(&card->spi->dev, card->card_id,
+ &fw_table[0], &helper, &mainfw);
+ if (err) {
+ netdev_err(priv->dev, "failed to find firmware (%d)\n",
+ err);
+ goto out;
+ }
+
+ lbs_deb_spi("Initializing FW for Marvell WLAN 802.11 adapter "
+ "(chip_id = 0x%04x, chip_rev = 0x%02x) "
+ "attached to SPI bus_num %d, chip_select %d. "
+ "spi->max_speed_hz=%d\n",
+ card->card_id, card->card_rev,
+ card->spi->master->bus_num,
+ card->spi->chip_select,
+ card->spi->max_speed_hz);
+ err = if_spi_prog_helper_firmware(card, helper);
+ if (err)
+ goto out;
+ err = if_spi_prog_main_firmware(card, mainfw);
+ if (err)
+ goto out;
+ lbs_deb_spi("loaded FW for Marvell WLAN 802.11 adapter\n");
+ }
+
+ err = spu_set_interrupt_mode(card, 0, 1);
+ if (err)
+ goto out;
+
+out:
+ return err;
+}
+
+static void if_spi_resume_worker(struct work_struct *work)
+{
+ struct if_spi_card *card;
+
+ card = container_of(work, struct if_spi_card, resume_work);
+
+ if (card->suspended) {
+ if (card->pdata->setup)
+ card->pdata->setup(card->spi);
+
+ /* Init card ... */
+ if_spi_init_card(card);
+
+ enable_irq(card->spi->irq);
+
+ /* And resume it ... */
+ lbs_resume(card->priv);
+
+ card->suspended = 0;
+ }
+}
+
+static int if_spi_probe(struct spi_device *spi)
+{
+ struct if_spi_card *card;
+ struct lbs_private *priv = NULL;
+ struct libertas_spi_platform_data *pdata = dev_get_platdata(&spi->dev);
+ int err = 0;
+
+ if (!pdata) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (pdata->setup) {
+ err = pdata->setup(spi);
+ if (err)
+ goto out;
+ }
+
+ /* Allocate card structure to represent this specific device */
+ card = kzalloc(sizeof(struct if_spi_card), GFP_KERNEL);
+ if (!card) {
+ err = -ENOMEM;
+ goto teardown;
+ }
+ spi_set_drvdata(spi, card);
+ card->pdata = pdata;
+ card->spi = spi;
+ card->prev_xfer_time = jiffies;
+
+ INIT_LIST_HEAD(&card->cmd_packet_list);
+ INIT_LIST_HEAD(&card->data_packet_list);
+ spin_lock_init(&card->buffer_lock);
+
+ /* Initialize the SPI Interface Unit */
+
+ /* Firmware load */
+ err = if_spi_init_card(card);
+ if (err)
+ goto free_card;
+
+ /*
+ * Register our card with libertas.
+ * This will call alloc_etherdev.
+ */
+ priv = lbs_add_card(card, &spi->dev);
+ if (IS_ERR(priv)) {
+ err = PTR_ERR(priv);
+ goto free_card;
+ }
+ card->priv = priv;
+ priv->setup_fw_on_resume = 1;
+ priv->card = card;
+ priv->hw_host_to_card = if_spi_host_to_card;
+ priv->enter_deep_sleep = NULL;
+ priv->exit_deep_sleep = NULL;
+ priv->reset_deep_sleep_wakeup = NULL;
+ priv->fw_ready = 1;
+
+ /* Initialize interrupt handling stuff. */
+ card->workqueue = alloc_workqueue("libertas_spi", WQ_MEM_RECLAIM, 0);
+ if (!card->workqueue) {
+ err = -ENOMEM;
+ goto remove_card;
+ }
+ INIT_WORK(&card->packet_work, if_spi_host_to_card_worker);
+ INIT_WORK(&card->resume_work, if_spi_resume_worker);
+
+ err = request_irq(spi->irq, if_spi_host_interrupt,
+ IRQF_TRIGGER_FALLING, "libertas_spi", card);
+ if (err) {
+ pr_err("can't get host irq line-- request_irq failed\n");
+ goto terminate_workqueue;
+ }
+
+ /*
+ * Start the card.
+ * This will call register_netdev, and we'll start
+ * getting interrupts...
+ */
+ err = lbs_start_card(priv);
+ if (err)
+ goto release_irq;
+
+ lbs_deb_spi("Finished initializing WLAN module.\n");
+
+ /* successful exit */
+ goto out;
+
+release_irq:
+ free_irq(spi->irq, card);
+terminate_workqueue:
+ destroy_workqueue(card->workqueue);
+remove_card:
+ lbs_remove_card(priv); /* will call free_netdev */
+free_card:
+ free_if_spi_card(card);
+teardown:
+ if (pdata->teardown)
+ pdata->teardown(spi);
+out:
+ return err;
+}
+
+static int libertas_spi_remove(struct spi_device *spi)
+{
+ struct if_spi_card *card = spi_get_drvdata(spi);
+ struct lbs_private *priv = card->priv;
+
+ lbs_deb_spi("libertas_spi_remove\n");
+
+ cancel_work_sync(&card->resume_work);
+
+ lbs_stop_card(priv);
+ lbs_remove_card(priv); /* will call free_netdev */
+
+ free_irq(spi->irq, card);
+ destroy_workqueue(card->workqueue);
+ if (card->pdata->teardown)
+ card->pdata->teardown(spi);
+ free_if_spi_card(card);
+
+ return 0;
+}
+
+static int if_spi_suspend(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct if_spi_card *card = spi_get_drvdata(spi);
+
+ if (!card->suspended) {
+ lbs_suspend(card->priv);
+ flush_workqueue(card->workqueue);
+ disable_irq(spi->irq);
+
+ if (card->pdata->teardown)
+ card->pdata->teardown(spi);
+ card->suspended = 1;
+ }
+
+ return 0;
+}
+
+static int if_spi_resume(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct if_spi_card *card = spi_get_drvdata(spi);
+
+ /* Schedule delayed work */
+ schedule_work(&card->resume_work);
+
+ return 0;
+}
+
+static const struct dev_pm_ops if_spi_pm_ops = {
+ .suspend = if_spi_suspend,
+ .resume = if_spi_resume,
+};
+
+static struct spi_driver libertas_spi_driver = {
+ .probe = if_spi_probe,
+ .remove = libertas_spi_remove,
+ .driver = {
+ .name = "libertas_spi",
+ .pm = &if_spi_pm_ops,
+ },
+};
+
+/*
+ * Module functions
+ */
+
+static int __init if_spi_init_module(void)
+{
+ int ret = 0;
+
+ printk(KERN_INFO "libertas_spi: Libertas SPI driver\n");
+ ret = spi_register_driver(&libertas_spi_driver);
+
+ return ret;
+}
+
+static void __exit if_spi_exit_module(void)
+{
+ spi_unregister_driver(&libertas_spi_driver);
+}
+
+module_init(if_spi_init_module);
+module_exit(if_spi_exit_module);
+
+MODULE_DESCRIPTION("Libertas SPI WLAN Driver");
+MODULE_AUTHOR("Andrey Yurovsky <andrey@cozybit.com>, "
+ "Colin McCabe <colin@cozybit.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:libertas_spi");
diff --git a/drivers/net/wireless/marvell/libertas/if_spi.h b/drivers/net/wireless/marvell/libertas/if_spi.h
new file mode 100644
index 000000000..c65bdb941
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_spi.h
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/net/wireless/libertas/if_spi.c
+ *
+ * Driver for Marvell SPI WLAN cards.
+ *
+ * Copyright 2008 Analog Devices Inc.
+ *
+ * Authors:
+ * Andrey Yurovsky <andrey@cozybit.com>
+ * Colin McCabe <colin@cozybit.com>
+ */
+
+#ifndef _LBS_IF_SPI_H_
+#define _LBS_IF_SPI_H_
+
+#define IPFIELD_ALIGN_OFFSET 2
+#define IF_SPI_CMD_BUF_SIZE 2400
+
+/***************** Firmware *****************/
+
+#define IF_SPI_FW_NAME_MAX 30
+
+#define MAX_MAIN_FW_LOAD_CRC_ERR 10
+
+/* Chunk size when loading the helper firmware */
+#define HELPER_FW_LOAD_CHUNK_SZ 64
+
+/* Value to write to indicate end of helper firmware dnld */
+#define FIRMWARE_DNLD_OK 0x0000
+
+/* Value to check once the main firmware is downloaded */
+#define SUCCESSFUL_FW_DOWNLOAD_MAGIC 0x88888888
+
+/***************** SPI Interface Unit *****************/
+/* Masks used in SPI register read/write operations */
+#define IF_SPI_READ_OPERATION_MASK 0x0
+#define IF_SPI_WRITE_OPERATION_MASK 0x8000
+
+/* SPI register offsets. 4-byte aligned. */
+#define IF_SPI_DEVICEID_CTRL_REG 0x00 /* DeviceID controller reg */
+#define IF_SPI_IO_READBASE_REG 0x04 /* Read I/O base reg */
+#define IF_SPI_IO_WRITEBASE_REG 0x08 /* Write I/O base reg */
+#define IF_SPI_IO_RDWRPORT_REG 0x0C /* Read/Write I/O port reg */
+
+#define IF_SPI_CMD_READBASE_REG 0x10 /* Read command base reg */
+#define IF_SPI_CMD_WRITEBASE_REG 0x14 /* Write command base reg */
+#define IF_SPI_CMD_RDWRPORT_REG 0x18 /* Read/Write command port reg */
+
+#define IF_SPI_DATA_READBASE_REG 0x1C /* Read data base reg */
+#define IF_SPI_DATA_WRITEBASE_REG 0x20 /* Write data base reg */
+#define IF_SPI_DATA_RDWRPORT_REG 0x24 /* Read/Write data port reg */
+
+#define IF_SPI_SCRATCH_1_REG 0x28 /* Scratch reg 1 */
+#define IF_SPI_SCRATCH_2_REG 0x2C /* Scratch reg 2 */
+#define IF_SPI_SCRATCH_3_REG 0x30 /* Scratch reg 3 */
+#define IF_SPI_SCRATCH_4_REG 0x34 /* Scratch reg 4 */
+
+#define IF_SPI_TX_FRAME_SEQ_NUM_REG 0x38 /* Tx frame sequence number reg */
+#define IF_SPI_TX_FRAME_STATUS_REG 0x3C /* Tx frame status reg */
+
+#define IF_SPI_HOST_INT_CTRL_REG 0x40 /* Host interrupt controller reg */
+
+#define IF_SPI_CARD_INT_CAUSE_REG 0x44 /* Card interrupt cause reg */
+#define IF_SPI_CARD_INT_STATUS_REG 0x48 /* Card interrupt status reg */
+#define IF_SPI_CARD_INT_EVENT_MASK_REG 0x4C /* Card interrupt event mask */
+#define IF_SPI_CARD_INT_STATUS_MASK_REG 0x50 /* Card interrupt status mask */
+
+#define IF_SPI_CARD_INT_RESET_SELECT_REG 0x54 /* Card interrupt reset select */
+
+#define IF_SPI_HOST_INT_CAUSE_REG 0x58 /* Host interrupt cause reg */
+#define IF_SPI_HOST_INT_STATUS_REG 0x5C /* Host interrupt status reg */
+#define IF_SPI_HOST_INT_EVENT_MASK_REG 0x60 /* Host interrupt event mask */
+#define IF_SPI_HOST_INT_STATUS_MASK_REG 0x64 /* Host interrupt status mask */
+#define IF_SPI_HOST_INT_RESET_SELECT_REG 0x68 /* Host interrupt reset select */
+
+#define IF_SPI_DELAY_READ_REG 0x6C /* Delay read reg */
+#define IF_SPI_SPU_BUS_MODE_REG 0x70 /* SPU BUS mode reg */
+
+/***************** IF_SPI_DEVICEID_CTRL_REG *****************/
+#define IF_SPI_DEVICEID_CTRL_REG_TO_CARD_ID(dc) ((dc & 0xffff0000)>>16)
+#define IF_SPI_DEVICEID_CTRL_REG_TO_CARD_REV(dc) (dc & 0x000000ff)
+
+/***************** IF_SPI_HOST_INT_CTRL_REG *****************/
+/* Host Interrupt Control bit : Wake up */
+#define IF_SPI_HICT_WAKE_UP (1<<0)
+/* Host Interrupt Control bit : WLAN ready */
+#define IF_SPI_HICT_WLAN_READY (1<<1)
+/*#define IF_SPI_HICT_FIFO_FIRST_HALF_EMPTY (1<<2) */
+/*#define IF_SPI_HICT_FIFO_SECOND_HALF_EMPTY (1<<3) */
+/*#define IF_SPI_HICT_IRQSRC_WLAN (1<<4) */
+/* Host Interrupt Control bit : Tx auto download */
+#define IF_SPI_HICT_TX_DOWNLOAD_OVER_AUTO (1<<5)
+/* Host Interrupt Control bit : Rx auto upload */
+#define IF_SPI_HICT_RX_UPLOAD_OVER_AUTO (1<<6)
+/* Host Interrupt Control bit : Command auto download */
+#define IF_SPI_HICT_CMD_DOWNLOAD_OVER_AUTO (1<<7)
+/* Host Interrupt Control bit : Command auto upload */
+#define IF_SPI_HICT_CMD_UPLOAD_OVER_AUTO (1<<8)
+
+/***************** IF_SPI_CARD_INT_CAUSE_REG *****************/
+/* Card Interrupt Case bit : Tx download over */
+#define IF_SPI_CIC_TX_DOWNLOAD_OVER (1<<0)
+/* Card Interrupt Case bit : Rx upload over */
+#define IF_SPI_CIC_RX_UPLOAD_OVER (1<<1)
+/* Card Interrupt Case bit : Command download over */
+#define IF_SPI_CIC_CMD_DOWNLOAD_OVER (1<<2)
+/* Card Interrupt Case bit : Host event */
+#define IF_SPI_CIC_HOST_EVENT (1<<3)
+/* Card Interrupt Case bit : Command upload over */
+#define IF_SPI_CIC_CMD_UPLOAD_OVER (1<<4)
+/* Card Interrupt Case bit : Power down */
+#define IF_SPI_CIC_POWER_DOWN (1<<5)
+
+/***************** IF_SPI_CARD_INT_STATUS_REG *****************/
+#define IF_SPI_CIS_TX_DOWNLOAD_OVER (1<<0)
+#define IF_SPI_CIS_RX_UPLOAD_OVER (1<<1)
+#define IF_SPI_CIS_CMD_DOWNLOAD_OVER (1<<2)
+#define IF_SPI_CIS_HOST_EVENT (1<<3)
+#define IF_SPI_CIS_CMD_UPLOAD_OVER (1<<4)
+#define IF_SPI_CIS_POWER_DOWN (1<<5)
+
+/***************** IF_SPI_HOST_INT_CAUSE_REG *****************/
+#define IF_SPI_HICU_TX_DOWNLOAD_RDY (1<<0)
+#define IF_SPI_HICU_RX_UPLOAD_RDY (1<<1)
+#define IF_SPI_HICU_CMD_DOWNLOAD_RDY (1<<2)
+#define IF_SPI_HICU_CARD_EVENT (1<<3)
+#define IF_SPI_HICU_CMD_UPLOAD_RDY (1<<4)
+#define IF_SPI_HICU_IO_WR_FIFO_OVERFLOW (1<<5)
+#define IF_SPI_HICU_IO_RD_FIFO_UNDERFLOW (1<<6)
+#define IF_SPI_HICU_DATA_WR_FIFO_OVERFLOW (1<<7)
+#define IF_SPI_HICU_DATA_RD_FIFO_UNDERFLOW (1<<8)
+#define IF_SPI_HICU_CMD_WR_FIFO_OVERFLOW (1<<9)
+#define IF_SPI_HICU_CMD_RD_FIFO_UNDERFLOW (1<<10)
+
+/***************** IF_SPI_HOST_INT_STATUS_REG *****************/
+/* Host Interrupt Status bit : Tx download ready */
+#define IF_SPI_HIST_TX_DOWNLOAD_RDY (1<<0)
+/* Host Interrupt Status bit : Rx upload ready */
+#define IF_SPI_HIST_RX_UPLOAD_RDY (1<<1)
+/* Host Interrupt Status bit : Command download ready */
+#define IF_SPI_HIST_CMD_DOWNLOAD_RDY (1<<2)
+/* Host Interrupt Status bit : Card event */
+#define IF_SPI_HIST_CARD_EVENT (1<<3)
+/* Host Interrupt Status bit : Command upload ready */
+#define IF_SPI_HIST_CMD_UPLOAD_RDY (1<<4)
+/* Host Interrupt Status bit : I/O write FIFO overflow */
+#define IF_SPI_HIST_IO_WR_FIFO_OVERFLOW (1<<5)
+/* Host Interrupt Status bit : I/O read FIFO underflow */
+#define IF_SPI_HIST_IO_RD_FIFO_UNDRFLOW (1<<6)
+/* Host Interrupt Status bit : Data write FIFO overflow */
+#define IF_SPI_HIST_DATA_WR_FIFO_OVERFLOW (1<<7)
+/* Host Interrupt Status bit : Data read FIFO underflow */
+#define IF_SPI_HIST_DATA_RD_FIFO_UNDERFLOW (1<<8)
+/* Host Interrupt Status bit : Command write FIFO overflow */
+#define IF_SPI_HIST_CMD_WR_FIFO_OVERFLOW (1<<9)
+/* Host Interrupt Status bit : Command read FIFO underflow */
+#define IF_SPI_HIST_CMD_RD_FIFO_UNDERFLOW (1<<10)
+
+/***************** IF_SPI_HOST_INT_STATUS_MASK_REG *****************/
+/* Host Interrupt Status Mask bit : Tx download ready */
+#define IF_SPI_HISM_TX_DOWNLOAD_RDY (1<<0)
+/* Host Interrupt Status Mask bit : Rx upload ready */
+#define IF_SPI_HISM_RX_UPLOAD_RDY (1<<1)
+/* Host Interrupt Status Mask bit : Command download ready */
+#define IF_SPI_HISM_CMD_DOWNLOAD_RDY (1<<2)
+/* Host Interrupt Status Mask bit : Card event */
+#define IF_SPI_HISM_CARDEVENT (1<<3)
+/* Host Interrupt Status Mask bit : Command upload ready */
+#define IF_SPI_HISM_CMD_UPLOAD_RDY (1<<4)
+/* Host Interrupt Status Mask bit : I/O write FIFO overflow */
+#define IF_SPI_HISM_IO_WR_FIFO_OVERFLOW (1<<5)
+/* Host Interrupt Status Mask bit : I/O read FIFO underflow */
+#define IF_SPI_HISM_IO_RD_FIFO_UNDERFLOW (1<<6)
+/* Host Interrupt Status Mask bit : Data write FIFO overflow */
+#define IF_SPI_HISM_DATA_WR_FIFO_OVERFLOW (1<<7)
+/* Host Interrupt Status Mask bit : Data write FIFO underflow */
+#define IF_SPI_HISM_DATA_RD_FIFO_UNDERFLOW (1<<8)
+/* Host Interrupt Status Mask bit : Command write FIFO overflow */
+#define IF_SPI_HISM_CMD_WR_FIFO_OVERFLOW (1<<9)
+/* Host Interrupt Status Mask bit : Command write FIFO underflow */
+#define IF_SPI_HISM_CMD_RD_FIFO_UNDERFLOW (1<<10)
+
+/***************** IF_SPI_SPU_BUS_MODE_REG *****************/
+/* SCK edge on which the WLAN module outputs data on MISO */
+#define IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_FALLING 0x8
+#define IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING 0x0
+
+/* In a SPU read operation, there is a delay between writing the SPU
+ * register name and getting back data from the WLAN module.
+ * This can be specified in terms of nanoseconds or in terms of dummy
+ * clock cycles which the master must output before receiving a response. */
+#define IF_SPI_BUS_MODE_DELAY_METHOD_DUMMY_CLOCK 0x4
+#define IF_SPI_BUS_MODE_DELAY_METHOD_TIMED 0x0
+
+/* Some different modes of SPI operation */
+#define IF_SPI_BUS_MODE_8_BIT_ADDRESS_16_BIT_DATA 0x00
+#define IF_SPI_BUS_MODE_8_BIT_ADDRESS_32_BIT_DATA 0x01
+#define IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA 0x02
+#define IF_SPI_BUS_MODE_16_BIT_ADDRESS_32_BIT_DATA 0x03
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/if_usb.c b/drivers/net/wireless/marvell/libertas/if_usb.c
new file mode 100644
index 000000000..2240b4db8
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_usb.c
@@ -0,0 +1,999 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains functions used in USB interface module.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/olpc-ec.h>
+
+#ifdef CONFIG_OLPC
+#include <asm/olpc.h>
+#endif
+
+#define DRV_NAME "usb8xxx"
+
+#include "host.h"
+#include "decl.h"
+#include "defs.h"
+#include "dev.h"
+#include "cmd.h"
+#include "if_usb.h"
+
+#define INSANEDEBUG 0
+#define lbs_deb_usb2(...) do { if (INSANEDEBUG) lbs_deb_usbd(__VA_ARGS__); } while (0)
+
+#define MESSAGE_HEADER_LEN 4
+
+MODULE_FIRMWARE("libertas/usb8388_v9.bin");
+MODULE_FIRMWARE("libertas/usb8388_v5.bin");
+MODULE_FIRMWARE("libertas/usb8388.bin");
+MODULE_FIRMWARE("libertas/usb8682.bin");
+MODULE_FIRMWARE("usb8388.bin");
+
+enum {
+ MODEL_UNKNOWN = 0x0,
+ MODEL_8388 = 0x1,
+ MODEL_8682 = 0x2
+};
+
+/* table of firmware file names */
+static const struct lbs_fw_table fw_table[] = {
+ { MODEL_8388, "libertas/usb8388_olpc.bin", NULL },
+ { MODEL_8388, "libertas/usb8388_v9.bin", NULL },
+ { MODEL_8388, "libertas/usb8388_v5.bin", NULL },
+ { MODEL_8388, "libertas/usb8388.bin", NULL },
+ { MODEL_8388, "usb8388.bin", NULL },
+ { MODEL_8682, "libertas/usb8682.bin", NULL },
+ { 0, NULL, NULL }
+};
+
+static const struct usb_device_id if_usb_table[] = {
+ /* Enter the device signature inside */
+ { USB_DEVICE(0x1286, 0x2001), .driver_info = MODEL_8388 },
+ { USB_DEVICE(0x05a3, 0x8388), .driver_info = MODEL_8388 },
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, if_usb_table);
+
+static void if_usb_receive(struct urb *urb);
+static void if_usb_receive_fwload(struct urb *urb);
+static void if_usb_prog_firmware(struct lbs_private *priv, int ret,
+ const struct firmware *fw,
+ const struct firmware *unused);
+static int if_usb_host_to_card(struct lbs_private *priv, uint8_t type,
+ uint8_t *payload, uint16_t nb);
+static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload,
+ uint16_t nb);
+static void if_usb_free(struct if_usb_card *cardp);
+static int if_usb_submit_rx_urb(struct if_usb_card *cardp);
+static int if_usb_reset_device(struct if_usb_card *cardp);
+
+/**
+ * if_usb_write_bulk_callback - callback function to handle the status
+ * of the URB
+ * @urb: pointer to &urb structure
+ * returns: N/A
+ */
+static void if_usb_write_bulk_callback(struct urb *urb)
+{
+ struct if_usb_card *cardp = (struct if_usb_card *) urb->context;
+
+ /* handle the transmission complete validations */
+
+ if (urb->status == 0) {
+ struct lbs_private *priv = cardp->priv;
+
+ lbs_deb_usb2(&urb->dev->dev, "URB status is successful\n");
+ lbs_deb_usb2(&urb->dev->dev, "Actual length transmitted %d\n",
+ urb->actual_length);
+
+ /* Boot commands such as UPDATE_FW and UPDATE_BOOT2 are not
+ * passed up to the lbs level.
+ */
+ if (priv && priv->dnld_sent != DNLD_BOOTCMD_SENT)
+ lbs_host_to_card_done(priv);
+ } else {
+ /* print the failure status number for debug */
+ pr_info("URB in failure status: %d\n", urb->status);
+ }
+}
+
+/**
+ * if_usb_free - free tx/rx urb, skb and rx buffer
+ * @cardp: pointer to &if_usb_card
+ * returns: N/A
+ */
+static void if_usb_free(struct if_usb_card *cardp)
+{
+ /* Unlink tx & rx urb */
+ usb_kill_urb(cardp->tx_urb);
+ usb_kill_urb(cardp->rx_urb);
+
+ usb_free_urb(cardp->tx_urb);
+ cardp->tx_urb = NULL;
+
+ usb_free_urb(cardp->rx_urb);
+ cardp->rx_urb = NULL;
+
+ kfree(cardp->ep_out_buf);
+ cardp->ep_out_buf = NULL;
+}
+
+static void if_usb_setup_firmware(struct lbs_private *priv)
+{
+ struct if_usb_card *cardp = priv->card;
+ struct cmd_ds_set_boot2_ver b2_cmd;
+ struct cmd_ds_802_11_fw_wake_method wake_method;
+
+ b2_cmd.hdr.size = cpu_to_le16(sizeof(b2_cmd));
+ b2_cmd.action = 0;
+ b2_cmd.version = cardp->boot2_version;
+
+ if (lbs_cmd_with_response(priv, CMD_SET_BOOT2_VER, &b2_cmd))
+ lbs_deb_usb("Setting boot2 version failed\n");
+
+ priv->wol_gpio = 2; /* Wake via GPIO2... */
+ priv->wol_gap = 20; /* ... after 20ms */
+ lbs_host_sleep_cfg(priv, EHS_WAKE_ON_UNICAST_DATA,
+ (struct wol_config *) NULL);
+
+ wake_method.hdr.size = cpu_to_le16(sizeof(wake_method));
+ wake_method.action = cpu_to_le16(CMD_ACT_GET);
+ if (lbs_cmd_with_response(priv, CMD_802_11_FW_WAKE_METHOD, &wake_method)) {
+ netdev_info(priv->dev, "Firmware does not seem to support PS mode\n");
+ priv->fwcapinfo &= ~FW_CAPINFO_PS;
+ } else {
+ if (le16_to_cpu(wake_method.method) == CMD_WAKE_METHOD_COMMAND_INT) {
+ lbs_deb_usb("Firmware seems to support PS with wake-via-command\n");
+ } else {
+ /* The versions which boot up this way don't seem to
+ work even if we set it to the command interrupt */
+ priv->fwcapinfo &= ~FW_CAPINFO_PS;
+ netdev_info(priv->dev,
+ "Firmware doesn't wake via command interrupt; disabling PS mode\n");
+ }
+ }
+}
+
+static void if_usb_fw_timeo(struct timer_list *t)
+{
+ struct if_usb_card *cardp = from_timer(cardp, t, fw_timeout);
+
+ if (cardp->fwdnldover) {
+ lbs_deb_usb("Download complete, no event. Assuming success\n");
+ } else {
+ pr_err("Download timed out\n");
+ cardp->surprise_removed = 1;
+ }
+ wake_up(&cardp->fw_wq);
+}
+
+#ifdef CONFIG_OLPC
+static void if_usb_reset_olpc_card(struct lbs_private *priv)
+{
+ printk(KERN_CRIT "Resetting OLPC wireless via EC...\n");
+ olpc_ec_cmd(0x25, NULL, 0, NULL, 0);
+}
+#endif
+
+/**
+ * if_usb_probe - sets the configuration values
+ * @intf: &usb_interface pointer
+ * @id: pointer to usb_device_id
+ * returns: 0 on success, error code on failure
+ */
+static int if_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ struct lbs_private *priv;
+ struct if_usb_card *cardp;
+ int r = -ENOMEM;
+ int i;
+
+ udev = interface_to_usbdev(intf);
+
+ cardp = kzalloc(sizeof(struct if_usb_card), GFP_KERNEL);
+ if (!cardp)
+ goto error;
+
+ timer_setup(&cardp->fw_timeout, if_usb_fw_timeo, 0);
+ init_waitqueue_head(&cardp->fw_wq);
+
+ cardp->udev = udev;
+ cardp->model = (uint32_t) id->driver_info;
+ iface_desc = intf->cur_altsetting;
+
+ lbs_deb_usbd(&udev->dev, "bcdUSB = 0x%X bDeviceClass = 0x%X"
+ " bDeviceSubClass = 0x%X, bDeviceProtocol = 0x%X\n",
+ le16_to_cpu(udev->descriptor.bcdUSB),
+ udev->descriptor.bDeviceClass,
+ udev->descriptor.bDeviceSubClass,
+ udev->descriptor.bDeviceProtocol);
+
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ endpoint = &iface_desc->endpoint[i].desc;
+ if (usb_endpoint_is_bulk_in(endpoint)) {
+ cardp->ep_in_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ cardp->ep_in = usb_endpoint_num(endpoint);
+
+ lbs_deb_usbd(&udev->dev, "in_endpoint = %d\n", cardp->ep_in);
+ lbs_deb_usbd(&udev->dev, "Bulk in size is %d\n", cardp->ep_in_size);
+
+ } else if (usb_endpoint_is_bulk_out(endpoint)) {
+ cardp->ep_out_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ cardp->ep_out = usb_endpoint_num(endpoint);
+
+ lbs_deb_usbd(&udev->dev, "out_endpoint = %d\n", cardp->ep_out);
+ lbs_deb_usbd(&udev->dev, "Bulk out size is %d\n", cardp->ep_out_size);
+ }
+ }
+ if (!cardp->ep_out_size || !cardp->ep_in_size) {
+ lbs_deb_usbd(&udev->dev, "Endpoints not found\n");
+ goto dealloc;
+ }
+ if (!(cardp->rx_urb = usb_alloc_urb(0, GFP_KERNEL))) {
+ lbs_deb_usbd(&udev->dev, "Rx URB allocation failed\n");
+ goto dealloc;
+ }
+ if (!(cardp->tx_urb = usb_alloc_urb(0, GFP_KERNEL))) {
+ lbs_deb_usbd(&udev->dev, "Tx URB allocation failed\n");
+ goto dealloc;
+ }
+ cardp->ep_out_buf = kmalloc(MRVDRV_ETH_TX_PACKET_BUFFER_SIZE, GFP_KERNEL);
+ if (!cardp->ep_out_buf) {
+ lbs_deb_usbd(&udev->dev, "Could not allocate buffer\n");
+ goto dealloc;
+ }
+
+ priv = lbs_add_card(cardp, &intf->dev);
+ if (IS_ERR(priv)) {
+ r = PTR_ERR(priv);
+ goto err_add_card;
+ }
+
+ cardp->priv = priv;
+
+ priv->hw_host_to_card = if_usb_host_to_card;
+ priv->enter_deep_sleep = NULL;
+ priv->exit_deep_sleep = NULL;
+ priv->reset_deep_sleep_wakeup = NULL;
+ priv->is_polling = false;
+#ifdef CONFIG_OLPC
+ if (machine_is_olpc())
+ priv->reset_card = if_usb_reset_olpc_card;
+#endif
+
+ cardp->boot2_version = udev->descriptor.bcdDevice;
+
+ usb_get_dev(udev);
+ usb_set_intfdata(intf, cardp);
+
+ r = lbs_get_firmware_async(priv, &udev->dev, cardp->model,
+ fw_table, if_usb_prog_firmware);
+ if (r)
+ goto err_get_fw;
+
+ return 0;
+
+err_get_fw:
+ usb_put_dev(udev);
+ lbs_remove_card(priv);
+err_add_card:
+ if_usb_reset_device(cardp);
+dealloc:
+ if_usb_free(cardp);
+ kfree(cardp);
+
+error:
+ return r;
+}
+
+/**
+ * if_usb_disconnect - free resource and cleanup
+ * @intf: USB interface structure
+ * returns: N/A
+ */
+static void if_usb_disconnect(struct usb_interface *intf)
+{
+ struct if_usb_card *cardp = usb_get_intfdata(intf);
+ struct lbs_private *priv = cardp->priv;
+
+ cardp->surprise_removed = 1;
+
+ if (priv) {
+ lbs_stop_card(priv);
+ lbs_remove_card(priv);
+ }
+
+ /* Unlink and free urb */
+ if_usb_free(cardp);
+ kfree(cardp);
+
+ usb_set_intfdata(intf, NULL);
+ usb_put_dev(interface_to_usbdev(intf));
+}
+
+/**
+ * if_usb_send_fw_pkt - download FW
+ * @cardp: pointer to &struct if_usb_card
+ * returns: 0
+ */
+static int if_usb_send_fw_pkt(struct if_usb_card *cardp)
+{
+ struct fwdata *fwdata = cardp->ep_out_buf;
+ const uint8_t *firmware = cardp->fw->data;
+
+ /* If we got a CRC failure on the last block, back
+ up and retry it */
+ if (!cardp->CRC_OK) {
+ cardp->totalbytes = cardp->fwlastblksent;
+ cardp->fwseqnum--;
+ }
+
+ lbs_deb_usb2(&cardp->udev->dev, "totalbytes = %d\n",
+ cardp->totalbytes);
+
+ /* struct fwdata (which we sent to the card) has an
+ extra __le32 field in between the header and the data,
+ which is not in the struct fwheader in the actual
+ firmware binary. Insert the seqnum in the middle... */
+ memcpy(&fwdata->hdr, &firmware[cardp->totalbytes],
+ sizeof(struct fwheader));
+
+ cardp->fwlastblksent = cardp->totalbytes;
+ cardp->totalbytes += sizeof(struct fwheader);
+
+ memcpy(fwdata->data, &firmware[cardp->totalbytes],
+ le32_to_cpu(fwdata->hdr.datalength));
+
+ lbs_deb_usb2(&cardp->udev->dev, "Data length = %d\n",
+ le32_to_cpu(fwdata->hdr.datalength));
+
+ fwdata->seqnum = cpu_to_le32(++cardp->fwseqnum);
+ cardp->totalbytes += le32_to_cpu(fwdata->hdr.datalength);
+
+ usb_tx_block(cardp, cardp->ep_out_buf, sizeof(struct fwdata) +
+ le32_to_cpu(fwdata->hdr.datalength));
+
+ if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_DATA_TO_RECV)) {
+ lbs_deb_usb2(&cardp->udev->dev, "There are data to follow\n");
+ lbs_deb_usb2(&cardp->udev->dev, "seqnum = %d totalbytes = %d\n",
+ cardp->fwseqnum, cardp->totalbytes);
+ } else if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_LAST_BLOCK)) {
+ lbs_deb_usb2(&cardp->udev->dev, "Host has finished FW downloading\n");
+ lbs_deb_usb2(&cardp->udev->dev, "Downloading FW JUMP BLOCK\n");
+
+ cardp->fwfinalblk = 1;
+ }
+
+ lbs_deb_usb2(&cardp->udev->dev, "Firmware download done; size %d\n",
+ cardp->totalbytes);
+
+ return 0;
+}
+
+static int if_usb_reset_device(struct if_usb_card *cardp)
+{
+ struct cmd_header *cmd = cardp->ep_out_buf + 4;
+ int ret;
+
+ *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_REQUEST);
+
+ cmd->command = cpu_to_le16(CMD_802_11_RESET);
+ cmd->size = cpu_to_le16(sizeof(cmd));
+ cmd->result = cpu_to_le16(0);
+ cmd->seqnum = cpu_to_le16(0x5a5a);
+ usb_tx_block(cardp, cardp->ep_out_buf, 4 + sizeof(struct cmd_header));
+
+ msleep(100);
+ ret = usb_reset_device(cardp->udev);
+ msleep(100);
+
+#ifdef CONFIG_OLPC
+ if (ret && machine_is_olpc())
+ if_usb_reset_olpc_card(NULL);
+#endif
+
+ return ret;
+}
+
+/**
+ * usb_tx_block - transfer the data to the device
+ * @cardp: pointer to &struct if_usb_card
+ * @payload: pointer to payload data
+ * @nb: data length
+ * returns: 0 for success or negative error code
+ */
+static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload, uint16_t nb)
+{
+ int ret;
+
+ /* check if device is removed */
+ if (cardp->surprise_removed) {
+ lbs_deb_usbd(&cardp->udev->dev, "Device removed\n");
+ ret = -ENODEV;
+ goto tx_ret;
+ }
+
+ usb_fill_bulk_urb(cardp->tx_urb, cardp->udev,
+ usb_sndbulkpipe(cardp->udev,
+ cardp->ep_out),
+ payload, nb, if_usb_write_bulk_callback, cardp);
+
+ cardp->tx_urb->transfer_flags |= URB_ZERO_PACKET;
+
+ if ((ret = usb_submit_urb(cardp->tx_urb, GFP_ATOMIC))) {
+ lbs_deb_usbd(&cardp->udev->dev, "usb_submit_urb failed: %d\n", ret);
+ } else {
+ lbs_deb_usb2(&cardp->udev->dev, "usb_submit_urb success\n");
+ ret = 0;
+ }
+
+tx_ret:
+ return ret;
+}
+
+static int __if_usb_submit_rx_urb(struct if_usb_card *cardp,
+ void (*callbackfn)(struct urb *urb))
+{
+ struct sk_buff *skb;
+ int ret = -1;
+
+ if (!(skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE))) {
+ pr_err("No free skb\n");
+ goto rx_ret;
+ }
+
+ cardp->rx_skb = skb;
+
+ /* Fill the receive configuration URB and initialise the Rx call back */
+ usb_fill_bulk_urb(cardp->rx_urb, cardp->udev,
+ usb_rcvbulkpipe(cardp->udev, cardp->ep_in),
+ skb->data + IPFIELD_ALIGN_OFFSET,
+ MRVDRV_ETH_RX_PACKET_BUFFER_SIZE, callbackfn,
+ cardp);
+
+ lbs_deb_usb2(&cardp->udev->dev, "Pointer for rx_urb %p\n", cardp->rx_urb);
+ if ((ret = usb_submit_urb(cardp->rx_urb, GFP_ATOMIC))) {
+ lbs_deb_usbd(&cardp->udev->dev, "Submit Rx URB failed: %d\n", ret);
+ kfree_skb(skb);
+ cardp->rx_skb = NULL;
+ ret = -1;
+ } else {
+ lbs_deb_usb2(&cardp->udev->dev, "Submit Rx URB success\n");
+ ret = 0;
+ }
+
+rx_ret:
+ return ret;
+}
+
+static int if_usb_submit_rx_urb_fwload(struct if_usb_card *cardp)
+{
+ return __if_usb_submit_rx_urb(cardp, &if_usb_receive_fwload);
+}
+
+static int if_usb_submit_rx_urb(struct if_usb_card *cardp)
+{
+ return __if_usb_submit_rx_urb(cardp, &if_usb_receive);
+}
+
+static void if_usb_receive_fwload(struct urb *urb)
+{
+ struct if_usb_card *cardp = urb->context;
+ struct sk_buff *skb = cardp->rx_skb;
+ struct fwsyncheader *syncfwheader;
+ struct bootcmdresp bootcmdresp;
+
+ if (urb->status) {
+ lbs_deb_usbd(&cardp->udev->dev,
+ "URB status is failed during fw load\n");
+ kfree_skb(skb);
+ return;
+ }
+
+ if (cardp->fwdnldover) {
+ __le32 *tmp = (__le32 *)(skb->data + IPFIELD_ALIGN_OFFSET);
+
+ if (tmp[0] == cpu_to_le32(CMD_TYPE_INDICATION) &&
+ tmp[1] == cpu_to_le32(MACREG_INT_CODE_FIRMWARE_READY)) {
+ pr_info("Firmware ready event received\n");
+ wake_up(&cardp->fw_wq);
+ } else {
+ lbs_deb_usb("Waiting for confirmation; got %x %x\n",
+ le32_to_cpu(tmp[0]), le32_to_cpu(tmp[1]));
+ if_usb_submit_rx_urb_fwload(cardp);
+ }
+ kfree_skb(skb);
+ return;
+ }
+ if (cardp->bootcmdresp <= 0) {
+ memcpy (&bootcmdresp, skb->data + IPFIELD_ALIGN_OFFSET,
+ sizeof(bootcmdresp));
+
+ if (le16_to_cpu(cardp->udev->descriptor.bcdDevice) < 0x3106) {
+ kfree_skb(skb);
+ if_usb_submit_rx_urb_fwload(cardp);
+ cardp->bootcmdresp = BOOT_CMD_RESP_OK;
+ lbs_deb_usbd(&cardp->udev->dev,
+ "Received valid boot command response\n");
+ return;
+ }
+ if (bootcmdresp.magic != cpu_to_le32(BOOT_CMD_MAGIC_NUMBER)) {
+ if (bootcmdresp.magic == cpu_to_le32(CMD_TYPE_REQUEST) ||
+ bootcmdresp.magic == cpu_to_le32(CMD_TYPE_DATA) ||
+ bootcmdresp.magic == cpu_to_le32(CMD_TYPE_INDICATION)) {
+ if (!cardp->bootcmdresp)
+ pr_info("Firmware already seems alive; resetting\n");
+ cardp->bootcmdresp = -1;
+ } else {
+ pr_info("boot cmd response wrong magic number (0x%x)\n",
+ le32_to_cpu(bootcmdresp.magic));
+ }
+ } else if ((bootcmdresp.cmd != BOOT_CMD_FW_BY_USB) &&
+ (bootcmdresp.cmd != BOOT_CMD_UPDATE_FW) &&
+ (bootcmdresp.cmd != BOOT_CMD_UPDATE_BOOT2)) {
+ pr_info("boot cmd response cmd_tag error (%d)\n",
+ bootcmdresp.cmd);
+ } else if (bootcmdresp.result != BOOT_CMD_RESP_OK) {
+ pr_info("boot cmd response result error (%d)\n",
+ bootcmdresp.result);
+ } else {
+ cardp->bootcmdresp = 1;
+ lbs_deb_usbd(&cardp->udev->dev,
+ "Received valid boot command response\n");
+ }
+ kfree_skb(skb);
+ if_usb_submit_rx_urb_fwload(cardp);
+ return;
+ }
+
+ syncfwheader = kmemdup(skb->data + IPFIELD_ALIGN_OFFSET,
+ sizeof(struct fwsyncheader), GFP_ATOMIC);
+ if (!syncfwheader) {
+ lbs_deb_usbd(&cardp->udev->dev, "Failure to allocate syncfwheader\n");
+ kfree_skb(skb);
+ return;
+ }
+
+ if (!syncfwheader->cmd) {
+ lbs_deb_usb2(&cardp->udev->dev, "FW received Blk with correct CRC\n");
+ lbs_deb_usb2(&cardp->udev->dev, "FW received Blk seqnum = %d\n",
+ le32_to_cpu(syncfwheader->seqnum));
+ cardp->CRC_OK = 1;
+ } else {
+ lbs_deb_usbd(&cardp->udev->dev, "FW received Blk with CRC error\n");
+ cardp->CRC_OK = 0;
+ }
+
+ kfree_skb(skb);
+
+ /* Give device 5s to either write firmware to its RAM or eeprom */
+ mod_timer(&cardp->fw_timeout, jiffies + (HZ*5));
+
+ if (cardp->fwfinalblk) {
+ cardp->fwdnldover = 1;
+ goto exit;
+ }
+
+ if_usb_send_fw_pkt(cardp);
+
+ exit:
+ if_usb_submit_rx_urb_fwload(cardp);
+
+ kfree(syncfwheader);
+}
+
+#define MRVDRV_MIN_PKT_LEN 30
+
+static inline void process_cmdtypedata(int recvlength, struct sk_buff *skb,
+ struct if_usb_card *cardp,
+ struct lbs_private *priv)
+{
+ if (recvlength > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + MESSAGE_HEADER_LEN
+ || recvlength < MRVDRV_MIN_PKT_LEN) {
+ lbs_deb_usbd(&cardp->udev->dev, "Packet length is Invalid\n");
+ kfree_skb(skb);
+ return;
+ }
+
+ skb_reserve(skb, IPFIELD_ALIGN_OFFSET);
+ skb_put(skb, recvlength);
+ skb_pull(skb, MESSAGE_HEADER_LEN);
+
+ lbs_process_rxed_packet(priv, skb);
+}
+
+static inline void process_cmdrequest(int recvlength, uint8_t *recvbuff,
+ struct sk_buff *skb,
+ struct if_usb_card *cardp,
+ struct lbs_private *priv)
+{
+ unsigned long flags;
+ u8 i;
+
+ if (recvlength > LBS_CMD_BUFFER_SIZE) {
+ lbs_deb_usbd(&cardp->udev->dev,
+ "The receive buffer is too large\n");
+ kfree_skb(skb);
+ return;
+ }
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ i = (priv->resp_idx == 0) ? 1 : 0;
+ BUG_ON(priv->resp_len[i]);
+ priv->resp_len[i] = (recvlength - MESSAGE_HEADER_LEN);
+ memcpy(priv->resp_buf[i], recvbuff + MESSAGE_HEADER_LEN,
+ priv->resp_len[i]);
+ dev_kfree_skb_irq(skb);
+ lbs_notify_command_response(priv, i);
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ lbs_deb_usbd(&cardp->udev->dev,
+ "Wake up main thread to handle cmd response\n");
+}
+
+/**
+ * if_usb_receive - read the packet into the upload buffer,
+ * wake up the main thread and initialise the Rx callack
+ *
+ * @urb: pointer to &struct urb
+ * returns: N/A
+ */
+static void if_usb_receive(struct urb *urb)
+{
+ struct if_usb_card *cardp = urb->context;
+ struct sk_buff *skb = cardp->rx_skb;
+ struct lbs_private *priv = cardp->priv;
+ int recvlength = urb->actual_length;
+ uint8_t *recvbuff = NULL;
+ uint32_t recvtype = 0;
+ __le32 *pkt = (__le32 *)(skb->data + IPFIELD_ALIGN_OFFSET);
+ uint32_t event;
+
+ if (recvlength) {
+ if (urb->status) {
+ lbs_deb_usbd(&cardp->udev->dev, "RX URB failed: %d\n",
+ urb->status);
+ kfree_skb(skb);
+ goto setup_for_next;
+ }
+
+ recvbuff = skb->data + IPFIELD_ALIGN_OFFSET;
+ recvtype = le32_to_cpu(pkt[0]);
+ lbs_deb_usbd(&cardp->udev->dev,
+ "Recv length = 0x%x, Recv type = 0x%X\n",
+ recvlength, recvtype);
+ } else if (urb->status) {
+ kfree_skb(skb);
+ return;
+ }
+
+ switch (recvtype) {
+ case CMD_TYPE_DATA:
+ process_cmdtypedata(recvlength, skb, cardp, priv);
+ break;
+
+ case CMD_TYPE_REQUEST:
+ process_cmdrequest(recvlength, recvbuff, skb, cardp, priv);
+ break;
+
+ case CMD_TYPE_INDICATION:
+ /* Event handling */
+ event = le32_to_cpu(pkt[1]);
+ lbs_deb_usbd(&cardp->udev->dev, "**EVENT** 0x%X\n", event);
+ kfree_skb(skb);
+
+ /* Icky undocumented magic special case */
+ if (event & 0xffff0000) {
+ u32 trycount = (event & 0xffff0000) >> 16;
+
+ lbs_send_tx_feedback(priv, trycount);
+ } else
+ lbs_queue_event(priv, event & 0xFF);
+ break;
+
+ default:
+ lbs_deb_usbd(&cardp->udev->dev, "Unknown command type 0x%X\n",
+ recvtype);
+ kfree_skb(skb);
+ break;
+ }
+
+setup_for_next:
+ if_usb_submit_rx_urb(cardp);
+}
+
+/**
+ * if_usb_host_to_card - downloads data to FW
+ * @priv: pointer to &struct lbs_private structure
+ * @type: type of data
+ * @payload: pointer to data buffer
+ * @nb: number of bytes
+ * returns: 0 for success or negative error code
+ */
+static int if_usb_host_to_card(struct lbs_private *priv, uint8_t type,
+ uint8_t *payload, uint16_t nb)
+{
+ struct if_usb_card *cardp = priv->card;
+
+ lbs_deb_usbd(&cardp->udev->dev,"*** type = %u\n", type);
+ lbs_deb_usbd(&cardp->udev->dev,"size after = %d\n", nb);
+
+ if (type == MVMS_CMD) {
+ *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_REQUEST);
+ priv->dnld_sent = DNLD_CMD_SENT;
+ } else {
+ *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_DATA);
+ priv->dnld_sent = DNLD_DATA_SENT;
+ }
+
+ memcpy((cardp->ep_out_buf + MESSAGE_HEADER_LEN), payload, nb);
+
+ return usb_tx_block(cardp, cardp->ep_out_buf, nb + MESSAGE_HEADER_LEN);
+}
+
+/**
+ * if_usb_issue_boot_command - issues Boot command to the Boot2 code
+ * @cardp: pointer to &if_usb_card
+ * @ivalue: 1:Boot from FW by USB-Download
+ * 2:Boot from FW in EEPROM
+ * returns: 0 for success or negative error code
+ */
+static int if_usb_issue_boot_command(struct if_usb_card *cardp, int ivalue)
+{
+ struct bootcmd *bootcmd = cardp->ep_out_buf;
+
+ /* Prepare command */
+ bootcmd->magic = cpu_to_le32(BOOT_CMD_MAGIC_NUMBER);
+ bootcmd->cmd = ivalue;
+ memset(bootcmd->pad, 0, sizeof(bootcmd->pad));
+
+ /* Issue command */
+ usb_tx_block(cardp, cardp->ep_out_buf, sizeof(*bootcmd));
+
+ return 0;
+}
+
+
+/**
+ * check_fwfile_format - check the validity of Boot2/FW image
+ *
+ * @data: pointer to image
+ * @totlen: image length
+ * returns: 0 (good) or 1 (failure)
+ */
+static int check_fwfile_format(const uint8_t *data, uint32_t totlen)
+{
+ uint32_t bincmd, exit;
+ uint32_t blksize, offset, len;
+ int ret;
+
+ ret = 1;
+ exit = len = 0;
+
+ do {
+ struct fwheader *fwh = (void *)data;
+
+ bincmd = le32_to_cpu(fwh->dnldcmd);
+ blksize = le32_to_cpu(fwh->datalength);
+ switch (bincmd) {
+ case FW_HAS_DATA_TO_RECV:
+ offset = sizeof(struct fwheader) + blksize;
+ data += offset;
+ len += offset;
+ if (len >= totlen)
+ exit = 1;
+ break;
+ case FW_HAS_LAST_BLOCK:
+ exit = 1;
+ ret = 0;
+ break;
+ default:
+ exit = 1;
+ break;
+ }
+ } while (!exit);
+
+ if (ret)
+ pr_err("firmware file format check FAIL\n");
+ else
+ lbs_deb_fw("firmware file format check PASS\n");
+
+ return ret;
+}
+
+static void if_usb_prog_firmware(struct lbs_private *priv, int ret,
+ const struct firmware *fw,
+ const struct firmware *unused)
+{
+ struct if_usb_card *cardp = priv->card;
+ int i = 0;
+ static int reset_count = 10;
+
+ if (ret) {
+ pr_err("failed to find firmware (%d)\n", ret);
+ goto done;
+ }
+
+ cardp->fw = fw;
+ if (check_fwfile_format(cardp->fw->data, cardp->fw->size)) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Cancel any pending usb business */
+ usb_kill_urb(cardp->rx_urb);
+ usb_kill_urb(cardp->tx_urb);
+
+ cardp->fwlastblksent = 0;
+ cardp->fwdnldover = 0;
+ cardp->totalbytes = 0;
+ cardp->fwfinalblk = 0;
+ cardp->bootcmdresp = 0;
+
+restart:
+ if (if_usb_submit_rx_urb_fwload(cardp) < 0) {
+ lbs_deb_usbd(&cardp->udev->dev, "URB submission is failed\n");
+ ret = -EIO;
+ goto done;
+ }
+
+ cardp->bootcmdresp = 0;
+ do {
+ int j = 0;
+ i++;
+ if_usb_issue_boot_command(cardp, BOOT_CMD_FW_BY_USB);
+ /* wait for command response */
+ do {
+ j++;
+ msleep_interruptible(100);
+ } while (cardp->bootcmdresp == 0 && j < 10);
+ } while (cardp->bootcmdresp == 0 && i < 5);
+
+ if (cardp->bootcmdresp == BOOT_CMD_RESP_NOT_SUPPORTED) {
+ /* Return to normal operation */
+ ret = -EOPNOTSUPP;
+ usb_kill_urb(cardp->rx_urb);
+ usb_kill_urb(cardp->tx_urb);
+ if (if_usb_submit_rx_urb(cardp) < 0)
+ ret = -EIO;
+ goto done;
+ } else if (cardp->bootcmdresp <= 0) {
+ if (--reset_count >= 0) {
+ if_usb_reset_device(cardp);
+ goto restart;
+ }
+ ret = -EIO;
+ goto done;
+ }
+
+ i = 0;
+
+ cardp->totalbytes = 0;
+ cardp->fwlastblksent = 0;
+ cardp->CRC_OK = 1;
+ cardp->fwdnldover = 0;
+ cardp->fwseqnum = -1;
+ cardp->totalbytes = 0;
+ cardp->fwfinalblk = 0;
+
+ /* Send the first firmware packet... */
+ if_usb_send_fw_pkt(cardp);
+
+ /* ... and wait for the process to complete */
+ wait_event_interruptible(cardp->fw_wq, cardp->surprise_removed || cardp->fwdnldover);
+
+ del_timer_sync(&cardp->fw_timeout);
+ usb_kill_urb(cardp->rx_urb);
+
+ if (!cardp->fwdnldover) {
+ pr_info("failed to load fw, resetting device!\n");
+ if (--reset_count >= 0) {
+ if_usb_reset_device(cardp);
+ goto restart;
+ }
+
+ pr_info("FW download failure, time = %d ms\n", i * 100);
+ ret = -EIO;
+ goto done;
+ }
+
+ cardp->priv->fw_ready = 1;
+ if_usb_submit_rx_urb(cardp);
+
+ if (lbs_start_card(priv))
+ goto done;
+
+ if_usb_setup_firmware(priv);
+
+ /*
+ * EHS_REMOVE_WAKEUP is not supported on all versions of the firmware.
+ */
+ priv->wol_criteria = EHS_REMOVE_WAKEUP;
+ if (lbs_host_sleep_cfg(priv, priv->wol_criteria, NULL))
+ priv->ehs_remove_supported = false;
+
+ done:
+ cardp->fw = NULL;
+}
+
+
+#ifdef CONFIG_PM
+static int if_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct if_usb_card *cardp = usb_get_intfdata(intf);
+ struct lbs_private *priv = cardp->priv;
+ int ret;
+
+ if (priv->psstate != PS_STATE_FULL_POWER) {
+ ret = -1;
+ goto out;
+ }
+
+#ifdef CONFIG_OLPC
+ if (machine_is_olpc()) {
+ if (priv->wol_criteria == EHS_REMOVE_WAKEUP)
+ olpc_ec_wakeup_clear(EC_SCI_SRC_WLAN);
+ else
+ olpc_ec_wakeup_set(EC_SCI_SRC_WLAN);
+ }
+#endif
+
+ ret = lbs_suspend(priv);
+ if (ret)
+ goto out;
+
+ /* Unlink tx & rx urb */
+ usb_kill_urb(cardp->tx_urb);
+ usb_kill_urb(cardp->rx_urb);
+
+ out:
+ return ret;
+}
+
+static int if_usb_resume(struct usb_interface *intf)
+{
+ struct if_usb_card *cardp = usb_get_intfdata(intf);
+ struct lbs_private *priv = cardp->priv;
+
+ if_usb_submit_rx_urb(cardp);
+
+ lbs_resume(priv);
+
+ return 0;
+}
+#else
+#define if_usb_suspend NULL
+#define if_usb_resume NULL
+#endif
+
+static struct usb_driver if_usb_driver = {
+ .name = DRV_NAME,
+ .probe = if_usb_probe,
+ .disconnect = if_usb_disconnect,
+ .id_table = if_usb_table,
+ .suspend = if_usb_suspend,
+ .resume = if_usb_resume,
+ .reset_resume = if_usb_resume,
+ .disable_hub_initiated_lpm = 1,
+};
+
+module_usb_driver(if_usb_driver);
+
+MODULE_DESCRIPTION("8388 USB WLAN Driver");
+MODULE_AUTHOR("Marvell International Ltd. and Red Hat, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/marvell/libertas/if_usb.h b/drivers/net/wireless/marvell/libertas/if_usb.h
new file mode 100644
index 000000000..7d0daeb33
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/if_usb.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LBS_IF_USB_H
+#define _LBS_IF_USB_H
+
+#include <linux/wait.h>
+#include <linux/timer.h>
+
+struct lbs_private;
+
+/*
+ * This file contains definition for USB interface.
+ */
+#define CMD_TYPE_REQUEST 0xF00DFACE
+#define CMD_TYPE_DATA 0xBEADC0DE
+#define CMD_TYPE_INDICATION 0xBEEFFACE
+
+#define IPFIELD_ALIGN_OFFSET 2
+
+#define BOOT_CMD_FW_BY_USB 0x01
+#define BOOT_CMD_FW_IN_EEPROM 0x02
+#define BOOT_CMD_UPDATE_BOOT2 0x03
+#define BOOT_CMD_UPDATE_FW 0x04
+#define BOOT_CMD_MAGIC_NUMBER 0x4C56524D /* LVRM */
+
+struct bootcmd
+{
+ __le32 magic;
+ uint8_t cmd;
+ uint8_t pad[11];
+};
+
+#define BOOT_CMD_RESP_OK 0x0001
+#define BOOT_CMD_RESP_FAIL 0x0000
+#define BOOT_CMD_RESP_NOT_SUPPORTED 0x0002
+
+struct bootcmdresp
+{
+ __le32 magic;
+ uint8_t cmd;
+ uint8_t result;
+ uint8_t pad[2];
+};
+
+/* USB card description structure*/
+struct if_usb_card {
+ struct usb_device *udev;
+ uint32_t model; /* MODEL_* */
+ struct urb *rx_urb, *tx_urb;
+ struct lbs_private *priv;
+
+ struct sk_buff *rx_skb;
+
+ uint8_t ep_in;
+ uint8_t ep_out;
+
+ /* bootcmdresp == 0 means command is pending
+ * bootcmdresp < 0 means error
+ * bootcmdresp > 0 is a BOOT_CMD_RESP_* from firmware
+ */
+ int8_t bootcmdresp;
+
+ int ep_in_size;
+
+ void *ep_out_buf;
+ int ep_out_size;
+
+ const struct firmware *fw;
+ struct timer_list fw_timeout;
+ wait_queue_head_t fw_wq;
+ uint32_t fwseqnum;
+ uint32_t totalbytes;
+ uint32_t fwlastblksent;
+ uint8_t CRC_OK;
+ uint8_t fwdnldover;
+ uint8_t fwfinalblk;
+ uint8_t surprise_removed;
+
+ __le16 boot2_version;
+};
+
+/* fwheader */
+struct fwheader {
+ __le32 dnldcmd;
+ __le32 baseaddr;
+ __le32 datalength;
+ __le32 CRC;
+};
+
+#define FW_MAX_DATA_BLK_SIZE 600
+/* FWData */
+struct fwdata {
+ struct fwheader hdr;
+ __le32 seqnum;
+ uint8_t data[];
+};
+
+/* fwsyncheader */
+struct fwsyncheader {
+ __le32 cmd;
+ __le32 seqnum;
+};
+
+#define FW_HAS_DATA_TO_RECV 0x00000001
+#define FW_HAS_LAST_BLOCK 0x00000004
+
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/main.c b/drivers/net/wireless/marvell/libertas/main.c
new file mode 100644
index 000000000..1c56cc274
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/main.c
@@ -0,0 +1,1157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains the major functions in WLAN
+ * driver. It includes init, exit, open, close and main
+ * thread etc..
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/hardirq.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/kthread.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
+#include <net/cfg80211.h>
+
+#include "host.h"
+#include "decl.h"
+#include "dev.h"
+#include "cfg.h"
+#include "debugfs.h"
+#include "cmd.h"
+#include "mesh.h"
+
+#define DRIVER_RELEASE_VERSION "323.p0"
+const char lbs_driver_version[] = "COMM-USB8388-" DRIVER_RELEASE_VERSION
+#ifdef DEBUG
+ "-dbg"
+#endif
+ "";
+
+
+/* Module parameters */
+unsigned int lbs_debug;
+EXPORT_SYMBOL_GPL(lbs_debug);
+module_param_named(libertas_debug, lbs_debug, int, 0644);
+
+unsigned int lbs_disablemesh;
+EXPORT_SYMBOL_GPL(lbs_disablemesh);
+module_param_named(libertas_disablemesh, lbs_disablemesh, int, 0644);
+
+
+/*
+ * This global structure is used to send the confirm_sleep command as
+ * fast as possible down to the firmware.
+ */
+struct cmd_confirm_sleep confirm_sleep;
+
+
+/*
+ * the table to keep region code
+ */
+u16 lbs_region_code_to_index[MRVDRV_MAX_REGION_CODE] =
+ { 0x10, 0x20, 0x30, 0x31, 0x32, 0x40 };
+
+/*
+ * FW rate table. FW refers to rates by their index in this table, not by the
+ * rate value itself. Values of 0x00 are
+ * reserved positions.
+ */
+static u8 fw_data_rates[MAX_RATES] =
+ { 0x02, 0x04, 0x0B, 0x16, 0x00, 0x0C, 0x12,
+ 0x18, 0x24, 0x30, 0x48, 0x60, 0x6C, 0x00
+};
+
+/**
+ * lbs_fw_index_to_data_rate - use index to get the data rate
+ *
+ * @idx: The index of data rate
+ * returns: data rate or 0
+ */
+u32 lbs_fw_index_to_data_rate(u8 idx)
+{
+ if (idx >= sizeof(fw_data_rates))
+ idx = 0;
+ return fw_data_rates[idx];
+}
+
+/**
+ * lbs_data_rate_to_fw_index - use rate to get the index
+ *
+ * @rate: data rate
+ * returns: index or 0
+ */
+u8 lbs_data_rate_to_fw_index(u32 rate)
+{
+ u8 i;
+
+ if (!rate)
+ return 0;
+
+ for (i = 0; i < sizeof(fw_data_rates); i++) {
+ if (rate == fw_data_rates[i])
+ return i;
+ }
+ return 0;
+}
+
+int lbs_set_iface_type(struct lbs_private *priv, enum nl80211_iftype type)
+{
+ int ret = 0;
+
+ switch (type) {
+ case NL80211_IFTYPE_MONITOR:
+ ret = lbs_set_monitor_mode(priv, 1);
+ break;
+ case NL80211_IFTYPE_STATION:
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR)
+ ret = lbs_set_monitor_mode(priv, 0);
+ if (!ret)
+ ret = lbs_set_snmp_mib(priv, SNMP_MIB_OID_BSS_TYPE, 1);
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR)
+ ret = lbs_set_monitor_mode(priv, 0);
+ if (!ret)
+ ret = lbs_set_snmp_mib(priv, SNMP_MIB_OID_BSS_TYPE, 2);
+ break;
+ default:
+ ret = -ENOTSUPP;
+ }
+ return ret;
+}
+
+int lbs_start_iface(struct lbs_private *priv)
+{
+ struct cmd_ds_802_11_mac_address cmd;
+ int ret;
+
+ if (priv->power_restore) {
+ ret = priv->power_restore(priv);
+ if (ret)
+ return ret;
+ }
+
+ cmd.hdr.size = cpu_to_le16(sizeof(cmd));
+ cmd.action = cpu_to_le16(CMD_ACT_SET);
+ memcpy(cmd.macadd, priv->current_addr, ETH_ALEN);
+
+ ret = lbs_cmd_with_response(priv, CMD_802_11_MAC_ADDRESS, &cmd);
+ if (ret) {
+ lbs_deb_net("set MAC address failed\n");
+ goto err;
+ }
+
+ ret = lbs_set_iface_type(priv, priv->wdev->iftype);
+ if (ret) {
+ lbs_deb_net("set iface type failed\n");
+ goto err;
+ }
+
+ ret = lbs_set_11d_domain_info(priv);
+ if (ret) {
+ lbs_deb_net("set 11d domain info failed\n");
+ goto err;
+ }
+
+ lbs_update_channel(priv);
+
+ priv->iface_running = true;
+ return 0;
+
+err:
+ if (priv->power_save)
+ priv->power_save(priv);
+ return ret;
+}
+
+/**
+ * lbs_dev_open - open the ethX interface
+ *
+ * @dev: A pointer to &net_device structure
+ * returns: 0 or -EBUSY if monitor mode active
+ */
+static int lbs_dev_open(struct net_device *dev)
+{
+ struct lbs_private *priv = dev->ml_priv;
+ int ret = 0;
+
+ if (!priv->iface_running) {
+ ret = lbs_start_iface(priv);
+ if (ret)
+ goto out;
+ }
+
+ spin_lock_irq(&priv->driver_lock);
+
+ netif_carrier_off(dev);
+
+ if (!priv->tx_pending_len)
+ netif_wake_queue(dev);
+
+ spin_unlock_irq(&priv->driver_lock);
+
+out:
+ return ret;
+}
+
+static bool lbs_command_queue_empty(struct lbs_private *priv)
+{
+ unsigned long flags;
+ bool ret;
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ ret = priv->cur_cmd == NULL && list_empty(&priv->cmdpendingq);
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ return ret;
+}
+
+int lbs_stop_iface(struct lbs_private *priv)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ priv->iface_running = false;
+ dev_kfree_skb_irq(priv->currenttxskb);
+ priv->currenttxskb = NULL;
+ priv->tx_pending_len = 0;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ cancel_work_sync(&priv->mcast_work);
+ del_timer_sync(&priv->tx_lockup_timer);
+
+ /* Disable command processing, and wait for all commands to complete */
+ lbs_deb_main("waiting for commands to complete\n");
+ wait_event(priv->waitq, lbs_command_queue_empty(priv));
+ lbs_deb_main("all commands completed\n");
+
+ if (priv->power_save)
+ ret = priv->power_save(priv);
+
+ return ret;
+}
+
+/**
+ * lbs_eth_stop - close the ethX interface
+ *
+ * @dev: A pointer to &net_device structure
+ * returns: 0
+ */
+static int lbs_eth_stop(struct net_device *dev)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ if (priv->connect_status == LBS_CONNECTED)
+ lbs_disconnect(priv, WLAN_REASON_DEAUTH_LEAVING);
+
+ spin_lock_irq(&priv->driver_lock);
+ netif_stop_queue(dev);
+ spin_unlock_irq(&priv->driver_lock);
+
+ lbs_update_mcast(priv);
+ cancel_delayed_work_sync(&priv->scan_work);
+ if (priv->scan_req)
+ lbs_scan_done(priv);
+
+ netif_carrier_off(priv->dev);
+
+ if (!lbs_iface_active(priv))
+ lbs_stop_iface(priv);
+
+ return 0;
+}
+
+void lbs_host_to_card_done(struct lbs_private *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ del_timer(&priv->tx_lockup_timer);
+
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+
+ /* Wake main thread if commands are pending */
+ if (!priv->cur_cmd || priv->tx_pending_len > 0) {
+ if (!priv->wakeup_dev_required)
+ wake_up(&priv->waitq);
+ }
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+EXPORT_SYMBOL_GPL(lbs_host_to_card_done);
+
+int lbs_set_mac_address(struct net_device *dev, void *addr)
+{
+ int ret = 0;
+ struct lbs_private *priv = dev->ml_priv;
+ struct sockaddr *phwaddr = addr;
+
+ /*
+ * Can only set MAC address when all interfaces are down, to be written
+ * to the hardware when one of them is brought up.
+ */
+ if (lbs_iface_active(priv))
+ return -EBUSY;
+
+ /* In case it was called from the mesh device */
+ dev = priv->dev;
+
+ memcpy(priv->current_addr, phwaddr->sa_data, ETH_ALEN);
+ memcpy(dev->dev_addr, phwaddr->sa_data, ETH_ALEN);
+ if (priv->mesh_dev)
+ memcpy(priv->mesh_dev->dev_addr, phwaddr->sa_data, ETH_ALEN);
+
+ return ret;
+}
+
+
+static inline int mac_in_list(unsigned char *list, int list_len,
+ unsigned char *mac)
+{
+ while (list_len) {
+ if (!memcmp(list, mac, ETH_ALEN))
+ return 1;
+ list += ETH_ALEN;
+ list_len--;
+ }
+ return 0;
+}
+
+
+static int lbs_add_mcast_addrs(struct cmd_ds_mac_multicast_adr *cmd,
+ struct net_device *dev, int nr_addrs)
+{
+ int i = nr_addrs;
+ struct netdev_hw_addr *ha;
+ int cnt;
+
+ if ((dev->flags & (IFF_UP|IFF_MULTICAST)) != (IFF_UP|IFF_MULTICAST))
+ return nr_addrs;
+
+ netif_addr_lock_bh(dev);
+ cnt = netdev_mc_count(dev);
+ netdev_for_each_mc_addr(ha, dev) {
+ if (mac_in_list(cmd->maclist, nr_addrs, ha->addr)) {
+ lbs_deb_net("mcast address %s:%pM skipped\n", dev->name,
+ ha->addr);
+ cnt--;
+ continue;
+ }
+
+ if (i == MRVDRV_MAX_MULTICAST_LIST_SIZE)
+ break;
+ memcpy(&cmd->maclist[6*i], ha->addr, ETH_ALEN);
+ lbs_deb_net("mcast address %s:%pM added to filter\n", dev->name,
+ ha->addr);
+ i++;
+ cnt--;
+ }
+ netif_addr_unlock_bh(dev);
+ if (cnt)
+ return -EOVERFLOW;
+
+ return i;
+}
+
+void lbs_update_mcast(struct lbs_private *priv)
+{
+ struct cmd_ds_mac_multicast_adr mcast_cmd;
+ int dev_flags = 0;
+ int nr_addrs;
+ int old_mac_control = priv->mac_control;
+
+ if (netif_running(priv->dev))
+ dev_flags |= priv->dev->flags;
+ if (priv->mesh_dev && netif_running(priv->mesh_dev))
+ dev_flags |= priv->mesh_dev->flags;
+
+ if (dev_flags & IFF_PROMISC) {
+ priv->mac_control |= CMD_ACT_MAC_PROMISCUOUS_ENABLE;
+ priv->mac_control &= ~(CMD_ACT_MAC_ALL_MULTICAST_ENABLE |
+ CMD_ACT_MAC_MULTICAST_ENABLE);
+ goto out_set_mac_control;
+ } else if (dev_flags & IFF_ALLMULTI) {
+ do_allmulti:
+ priv->mac_control |= CMD_ACT_MAC_ALL_MULTICAST_ENABLE;
+ priv->mac_control &= ~(CMD_ACT_MAC_PROMISCUOUS_ENABLE |
+ CMD_ACT_MAC_MULTICAST_ENABLE);
+ goto out_set_mac_control;
+ }
+
+ /* Once for priv->dev, again for priv->mesh_dev if it exists */
+ nr_addrs = lbs_add_mcast_addrs(&mcast_cmd, priv->dev, 0);
+ if (nr_addrs >= 0 && priv->mesh_dev)
+ nr_addrs = lbs_add_mcast_addrs(&mcast_cmd, priv->mesh_dev, nr_addrs);
+ if (nr_addrs < 0)
+ goto do_allmulti;
+
+ if (nr_addrs) {
+ int size = offsetof(struct cmd_ds_mac_multicast_adr,
+ maclist[6*nr_addrs]);
+
+ mcast_cmd.action = cpu_to_le16(CMD_ACT_SET);
+ mcast_cmd.hdr.size = cpu_to_le16(size);
+ mcast_cmd.nr_of_adrs = cpu_to_le16(nr_addrs);
+
+ lbs_cmd_async(priv, CMD_MAC_MULTICAST_ADR, &mcast_cmd.hdr, size);
+
+ priv->mac_control |= CMD_ACT_MAC_MULTICAST_ENABLE;
+ } else
+ priv->mac_control &= ~CMD_ACT_MAC_MULTICAST_ENABLE;
+
+ priv->mac_control &= ~(CMD_ACT_MAC_PROMISCUOUS_ENABLE |
+ CMD_ACT_MAC_ALL_MULTICAST_ENABLE);
+ out_set_mac_control:
+ if (priv->mac_control != old_mac_control)
+ lbs_set_mac_control(priv);
+}
+
+static void lbs_set_mcast_worker(struct work_struct *work)
+{
+ struct lbs_private *priv = container_of(work, struct lbs_private, mcast_work);
+ lbs_update_mcast(priv);
+}
+
+void lbs_set_multicast_list(struct net_device *dev)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ schedule_work(&priv->mcast_work);
+}
+
+/**
+ * lbs_thread - handles the major jobs in the LBS driver.
+ * It handles all events generated by firmware, RX data received
+ * from firmware and TX data sent from kernel.
+ *
+ * @data: A pointer to &lbs_thread structure
+ * returns: 0
+ */
+static int lbs_thread(void *data)
+{
+ struct net_device *dev = data;
+ struct lbs_private *priv = dev->ml_priv;
+ wait_queue_entry_t wait;
+
+ init_waitqueue_entry(&wait, current);
+
+ for (;;) {
+ int shouldsleep;
+ u8 resp_idx;
+
+ lbs_deb_thread("1: currenttxskb %p, dnld_sent %d\n",
+ priv->currenttxskb, priv->dnld_sent);
+
+ add_wait_queue(&priv->waitq, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_lock_irq(&priv->driver_lock);
+
+ if (kthread_should_stop())
+ shouldsleep = 0; /* Bye */
+ else if (priv->surpriseremoved)
+ shouldsleep = 1; /* We need to wait until we're _told_ to die */
+ else if (priv->psstate == PS_STATE_SLEEP)
+ shouldsleep = 1; /* Sleep mode. Nothing we can do till it wakes */
+ else if (priv->cmd_timed_out)
+ shouldsleep = 0; /* Command timed out. Recover */
+ else if (!priv->fw_ready)
+ shouldsleep = 1; /* Firmware not ready. We're waiting for it */
+ else if (priv->dnld_sent)
+ shouldsleep = 1; /* Something is en route to the device already */
+ else if (priv->tx_pending_len > 0)
+ shouldsleep = 0; /* We've a packet to send */
+ else if (priv->resp_len[priv->resp_idx])
+ shouldsleep = 0; /* We have a command response */
+ else if (priv->cur_cmd)
+ shouldsleep = 1; /* Can't send a command; one already running */
+ else if (!list_empty(&priv->cmdpendingq) &&
+ !(priv->wakeup_dev_required))
+ shouldsleep = 0; /* We have a command to send */
+ else if (kfifo_len(&priv->event_fifo))
+ shouldsleep = 0; /* We have an event to process */
+ else
+ shouldsleep = 1; /* No command */
+
+ if (shouldsleep) {
+ lbs_deb_thread("sleeping, connect_status %d, "
+ "psmode %d, psstate %d\n",
+ priv->connect_status,
+ priv->psmode, priv->psstate);
+ spin_unlock_irq(&priv->driver_lock);
+ schedule();
+ } else
+ spin_unlock_irq(&priv->driver_lock);
+
+ lbs_deb_thread("2: currenttxskb %p, dnld_send %d\n",
+ priv->currenttxskb, priv->dnld_sent);
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&priv->waitq, &wait);
+
+ lbs_deb_thread("3: currenttxskb %p, dnld_sent %d\n",
+ priv->currenttxskb, priv->dnld_sent);
+
+ if (kthread_should_stop()) {
+ lbs_deb_thread("break from main thread\n");
+ break;
+ }
+
+ if (priv->surpriseremoved) {
+ lbs_deb_thread("adapter removed; waiting to die...\n");
+ continue;
+ }
+
+ lbs_deb_thread("4: currenttxskb %p, dnld_sent %d\n",
+ priv->currenttxskb, priv->dnld_sent);
+
+ /* Process any pending command response */
+ spin_lock_irq(&priv->driver_lock);
+ resp_idx = priv->resp_idx;
+ if (priv->resp_len[resp_idx]) {
+ spin_unlock_irq(&priv->driver_lock);
+ lbs_process_command_response(priv,
+ priv->resp_buf[resp_idx],
+ priv->resp_len[resp_idx]);
+ spin_lock_irq(&priv->driver_lock);
+ priv->resp_len[resp_idx] = 0;
+ }
+ spin_unlock_irq(&priv->driver_lock);
+
+ /* Process hardware events, e.g. card removed, link lost */
+ spin_lock_irq(&priv->driver_lock);
+ while (kfifo_len(&priv->event_fifo)) {
+ u32 event;
+
+ if (kfifo_out(&priv->event_fifo,
+ (unsigned char *) &event, sizeof(event)) !=
+ sizeof(event))
+ break;
+ spin_unlock_irq(&priv->driver_lock);
+ lbs_process_event(priv, event);
+ spin_lock_irq(&priv->driver_lock);
+ }
+ spin_unlock_irq(&priv->driver_lock);
+
+ if (priv->wakeup_dev_required) {
+ lbs_deb_thread("Waking up device...\n");
+ /* Wake up device */
+ if (priv->exit_deep_sleep(priv))
+ lbs_deb_thread("Wakeup device failed\n");
+ continue;
+ }
+
+ /* command timeout stuff */
+ if (priv->cmd_timed_out && priv->cur_cmd) {
+ struct cmd_ctrl_node *cmdnode = priv->cur_cmd;
+
+ netdev_info(dev, "Timeout submitting command 0x%04x\n",
+ le16_to_cpu(cmdnode->cmdbuf->command));
+ lbs_complete_command(priv, cmdnode, -ETIMEDOUT);
+
+ /* Reset card, but only when it isn't in the process
+ * of being shutdown anyway. */
+ if (!dev->dismantle && priv->reset_card)
+ priv->reset_card(priv);
+ }
+ priv->cmd_timed_out = 0;
+
+ if (!priv->fw_ready)
+ continue;
+
+ /* Check if we need to confirm Sleep Request received previously */
+ if (priv->psstate == PS_STATE_PRE_SLEEP &&
+ !priv->dnld_sent && !priv->cur_cmd) {
+ if (priv->connect_status == LBS_CONNECTED) {
+ lbs_deb_thread("pre-sleep, currenttxskb %p, "
+ "dnld_sent %d, cur_cmd %p\n",
+ priv->currenttxskb, priv->dnld_sent,
+ priv->cur_cmd);
+
+ lbs_ps_confirm_sleep(priv);
+ } else {
+ /* workaround for firmware sending
+ * deauth/linkloss event immediately
+ * after sleep request; remove this
+ * after firmware fixes it
+ */
+ priv->psstate = PS_STATE_AWAKE;
+ netdev_alert(dev,
+ "ignore PS_SleepConfirm in non-connected state\n");
+ }
+ }
+
+ /* The PS state is changed during processing of Sleep Request
+ * event above
+ */
+ if ((priv->psstate == PS_STATE_SLEEP) ||
+ (priv->psstate == PS_STATE_PRE_SLEEP))
+ continue;
+
+ if (priv->is_deep_sleep)
+ continue;
+
+ /* Execute the next command */
+ if (!priv->dnld_sent && !priv->cur_cmd)
+ lbs_execute_next_command(priv);
+
+ spin_lock_irq(&priv->driver_lock);
+ if (!priv->dnld_sent && priv->tx_pending_len > 0) {
+ int ret = priv->hw_host_to_card(priv, MVMS_DAT,
+ priv->tx_pending_buf,
+ priv->tx_pending_len);
+ if (ret) {
+ lbs_deb_tx("host_to_card failed %d\n", ret);
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+ } else {
+ mod_timer(&priv->tx_lockup_timer,
+ jiffies + (HZ * 5));
+ }
+ priv->tx_pending_len = 0;
+ if (!priv->currenttxskb) {
+ /* We can wake the queues immediately if we aren't
+ waiting for TX feedback */
+ if (priv->connect_status == LBS_CONNECTED)
+ netif_wake_queue(priv->dev);
+ if (priv->mesh_dev &&
+ netif_running(priv->mesh_dev))
+ netif_wake_queue(priv->mesh_dev);
+ }
+ }
+ spin_unlock_irq(&priv->driver_lock);
+ }
+
+ del_timer(&priv->command_timer);
+ del_timer(&priv->tx_lockup_timer);
+ del_timer(&priv->auto_deepsleep_timer);
+
+ return 0;
+}
+
+/**
+ * lbs_setup_firmware - gets the HW spec from the firmware and sets
+ * some basic parameters
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * returns: 0 or -1
+ */
+static int lbs_setup_firmware(struct lbs_private *priv)
+{
+ int ret = -1;
+ s16 curlevel = 0, minlevel = 0, maxlevel = 0;
+
+ /* Read MAC address from firmware */
+ eth_broadcast_addr(priv->current_addr);
+ ret = lbs_update_hw_spec(priv);
+ if (ret)
+ goto done;
+
+ /* Read power levels if available */
+ ret = lbs_get_tx_power(priv, &curlevel, &minlevel, &maxlevel);
+ if (ret == 0) {
+ priv->txpower_cur = curlevel;
+ priv->txpower_min = minlevel;
+ priv->txpower_max = maxlevel;
+ }
+
+ /* Send cmd to FW to enable 11D function */
+ ret = lbs_set_snmp_mib(priv, SNMP_MIB_OID_11D_ENABLE, 1);
+ if (ret)
+ goto done;
+
+ ret = lbs_set_mac_control_sync(priv);
+done:
+ return ret;
+}
+
+int lbs_suspend(struct lbs_private *priv)
+{
+ int ret;
+
+ if (priv->is_deep_sleep) {
+ ret = lbs_set_deep_sleep(priv, 0);
+ if (ret) {
+ netdev_err(priv->dev,
+ "deep sleep cancellation failed: %d\n", ret);
+ return ret;
+ }
+ priv->deep_sleep_required = 1;
+ }
+
+ ret = lbs_set_host_sleep(priv, 1);
+
+ netif_device_detach(priv->dev);
+ if (priv->mesh_dev)
+ netif_device_detach(priv->mesh_dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lbs_suspend);
+
+int lbs_resume(struct lbs_private *priv)
+{
+ int ret;
+
+ ret = lbs_set_host_sleep(priv, 0);
+
+ netif_device_attach(priv->dev);
+ if (priv->mesh_dev)
+ netif_device_attach(priv->mesh_dev);
+
+ if (priv->deep_sleep_required) {
+ priv->deep_sleep_required = 0;
+ ret = lbs_set_deep_sleep(priv, 1);
+ if (ret)
+ netdev_err(priv->dev,
+ "deep sleep activation failed: %d\n", ret);
+ }
+
+ if (priv->setup_fw_on_resume)
+ ret = lbs_setup_firmware(priv);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lbs_resume);
+
+/**
+ * lbs_cmd_timeout_handler - handles the timeout of command sending.
+ * It will re-send the same command again.
+ *
+ * @t: Context from which to retrieve a &struct lbs_private pointer
+ */
+static void lbs_cmd_timeout_handler(struct timer_list *t)
+{
+ struct lbs_private *priv = from_timer(priv, t, command_timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (!priv->cur_cmd)
+ goto out;
+
+ netdev_info(priv->dev, "command 0x%04x timed out\n",
+ le16_to_cpu(priv->cur_cmd->cmdbuf->command));
+
+ priv->cmd_timed_out = 1;
+
+ /*
+ * If the device didn't even acknowledge the command, reset the state
+ * so that we don't block all future commands due to this one timeout.
+ */
+ if (priv->dnld_sent == DNLD_CMD_SENT)
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+
+ wake_up(&priv->waitq);
+out:
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+
+/**
+ * lbs_tx_lockup_handler - handles the timeout of the passing of TX frames
+ * to the hardware. This is known to frequently happen with SD8686 when
+ * waking up after a Wake-on-WLAN-triggered resume.
+ *
+ * @t: Context from which to retrieve a &struct lbs_private pointer
+ */
+static void lbs_tx_lockup_handler(struct timer_list *t)
+{
+ struct lbs_private *priv = from_timer(priv, t, tx_lockup_timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ netdev_info(priv->dev, "TX lockup detected\n");
+ if (priv->reset_card)
+ priv->reset_card(priv);
+
+ priv->dnld_sent = DNLD_RES_RECEIVED;
+ wake_up_interruptible(&priv->waitq);
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+
+/**
+ * auto_deepsleep_timer_fn - put the device back to deep sleep mode when
+ * timer expires and no activity (command, event, data etc.) is detected.
+ * @t: Context from which to retrieve a &struct lbs_private pointer
+ * returns: N/A
+ */
+static void auto_deepsleep_timer_fn(struct timer_list *t)
+{
+ struct lbs_private *priv = from_timer(priv, t, auto_deepsleep_timer);
+
+ if (priv->is_activity_detected) {
+ priv->is_activity_detected = 0;
+ } else {
+ if (priv->is_auto_deep_sleep_enabled &&
+ (!priv->wakeup_dev_required) &&
+ (priv->connect_status != LBS_CONNECTED)) {
+ struct cmd_header cmd;
+
+ lbs_deb_main("Entering auto deep sleep mode...\n");
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.size = cpu_to_le16(sizeof(cmd));
+ lbs_cmd_async(priv, CMD_802_11_DEEP_SLEEP, &cmd,
+ sizeof(cmd));
+ }
+ }
+ mod_timer(&priv->auto_deepsleep_timer , jiffies +
+ (priv->auto_deep_sleep_timeout * HZ)/1000);
+}
+
+int lbs_enter_auto_deep_sleep(struct lbs_private *priv)
+{
+ priv->is_auto_deep_sleep_enabled = 1;
+ if (priv->is_deep_sleep)
+ priv->wakeup_dev_required = 1;
+ mod_timer(&priv->auto_deepsleep_timer ,
+ jiffies + (priv->auto_deep_sleep_timeout * HZ)/1000);
+
+ return 0;
+}
+
+int lbs_exit_auto_deep_sleep(struct lbs_private *priv)
+{
+ priv->is_auto_deep_sleep_enabled = 0;
+ priv->auto_deep_sleep_timeout = 0;
+ del_timer(&priv->auto_deepsleep_timer);
+
+ return 0;
+}
+
+static int lbs_init_adapter(struct lbs_private *priv)
+{
+ int ret;
+
+ eth_broadcast_addr(priv->current_addr);
+
+ priv->connect_status = LBS_DISCONNECTED;
+ priv->channel = DEFAULT_AD_HOC_CHANNEL;
+ priv->mac_control = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON;
+ priv->radio_on = 1;
+ priv->psmode = LBS802_11POWERMODECAM;
+ priv->psstate = PS_STATE_FULL_POWER;
+ priv->is_deep_sleep = 0;
+ priv->is_auto_deep_sleep_enabled = 0;
+ priv->deep_sleep_required = 0;
+ priv->wakeup_dev_required = 0;
+ init_waitqueue_head(&priv->ds_awake_q);
+ init_waitqueue_head(&priv->scan_q);
+ priv->authtype_auto = 1;
+ priv->is_host_sleep_configured = 0;
+ priv->is_host_sleep_activated = 0;
+ init_waitqueue_head(&priv->host_sleep_q);
+ init_waitqueue_head(&priv->fw_waitq);
+ mutex_init(&priv->lock);
+
+ timer_setup(&priv->command_timer, lbs_cmd_timeout_handler, 0);
+ timer_setup(&priv->tx_lockup_timer, lbs_tx_lockup_handler, 0);
+ timer_setup(&priv->auto_deepsleep_timer, auto_deepsleep_timer_fn, 0);
+
+ INIT_LIST_HEAD(&priv->cmdfreeq);
+ INIT_LIST_HEAD(&priv->cmdpendingq);
+
+ spin_lock_init(&priv->driver_lock);
+
+ /* Allocate the command buffers */
+ if (lbs_allocate_cmd_buffer(priv)) {
+ pr_err("Out of memory allocating command buffers\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ priv->resp_idx = 0;
+ priv->resp_len[0] = priv->resp_len[1] = 0;
+
+ /* Create the event FIFO */
+ ret = kfifo_alloc(&priv->event_fifo, sizeof(u32) * 16, GFP_KERNEL);
+ if (ret) {
+ pr_err("Out of memory allocating event FIFO buffer\n");
+ lbs_free_cmd_buffer(priv);
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static void lbs_free_adapter(struct lbs_private *priv)
+{
+ lbs_free_cmd_buffer(priv);
+ kfifo_free(&priv->event_fifo);
+ del_timer(&priv->command_timer);
+ del_timer(&priv->tx_lockup_timer);
+ del_timer(&priv->auto_deepsleep_timer);
+}
+
+static const struct net_device_ops lbs_netdev_ops = {
+ .ndo_open = lbs_dev_open,
+ .ndo_stop = lbs_eth_stop,
+ .ndo_start_xmit = lbs_hard_start_xmit,
+ .ndo_set_mac_address = lbs_set_mac_address,
+ .ndo_set_rx_mode = lbs_set_multicast_list,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+/**
+ * lbs_add_card - adds the card. It will probe the
+ * card, allocate the lbs_priv and initialize the device.
+ *
+ * @card: A pointer to card
+ * @dmdev: A pointer to &struct device
+ * returns: A pointer to &struct lbs_private structure
+ */
+struct lbs_private *lbs_add_card(void *card, struct device *dmdev)
+{
+ struct net_device *dev;
+ struct wireless_dev *wdev;
+ struct lbs_private *priv = NULL;
+ int err;
+
+ /* Allocate an Ethernet device and register it */
+ wdev = lbs_cfg_alloc(dmdev);
+ if (IS_ERR(wdev)) {
+ err = PTR_ERR(wdev);
+ pr_err("cfg80211 init failed\n");
+ goto err_cfg;
+ }
+
+ wdev->iftype = NL80211_IFTYPE_STATION;
+ priv = wdev_priv(wdev);
+ priv->wdev = wdev;
+
+ err = lbs_init_adapter(priv);
+ if (err) {
+ pr_err("failed to initialize adapter structure\n");
+ goto err_wdev;
+ }
+
+ dev = alloc_netdev(0, "wlan%d", NET_NAME_UNKNOWN, ether_setup);
+ if (!dev) {
+ err = -ENOMEM;
+ dev_err(dmdev, "no memory for network device instance\n");
+ goto err_adapter;
+ }
+
+ dev->ieee80211_ptr = wdev;
+ dev->ml_priv = priv;
+ SET_NETDEV_DEV(dev, dmdev);
+ wdev->netdev = dev;
+ priv->dev = dev;
+
+ dev->netdev_ops = &lbs_netdev_ops;
+ dev->watchdog_timeo = 5 * HZ;
+ dev->ethtool_ops = &lbs_ethtool_ops;
+ dev->flags |= IFF_BROADCAST | IFF_MULTICAST;
+
+ priv->card = card;
+
+ strcpy(dev->name, "wlan%d");
+
+ lbs_deb_thread("Starting main thread...\n");
+ init_waitqueue_head(&priv->waitq);
+ priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");
+ if (IS_ERR(priv->main_thread)) {
+ err = PTR_ERR(priv->main_thread);
+ lbs_deb_thread("Error creating main thread.\n");
+ goto err_ndev;
+ }
+
+ priv->work_thread = create_singlethread_workqueue("lbs_worker");
+ INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker);
+
+ priv->wol_criteria = EHS_REMOVE_WAKEUP;
+ priv->wol_gpio = 0xff;
+ priv->wol_gap = 20;
+ priv->ehs_remove_supported = true;
+
+ return priv;
+
+ err_ndev:
+ free_netdev(dev);
+
+ err_adapter:
+ lbs_free_adapter(priv);
+
+ err_wdev:
+ lbs_cfg_free(priv);
+
+ err_cfg:
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(lbs_add_card);
+
+
+void lbs_remove_card(struct lbs_private *priv)
+{
+ struct net_device *dev = priv->dev;
+
+ lbs_remove_mesh(priv);
+
+ if (priv->wiphy_registered)
+ lbs_scan_deinit(priv);
+
+ lbs_wait_for_firmware_load(priv);
+
+ /* worker thread destruction blocks on the in-flight command which
+ * should have been cleared already in lbs_stop_card().
+ */
+ lbs_deb_main("destroying worker thread\n");
+ destroy_workqueue(priv->work_thread);
+ lbs_deb_main("done destroying worker thread\n");
+
+ if (priv->psmode == LBS802_11POWERMODEMAX_PSP) {
+ priv->psmode = LBS802_11POWERMODECAM;
+ /* no need to wakeup if already woken up,
+ * on suspend, this exit ps command is not processed
+ * the driver hangs
+ */
+ if (priv->psstate != PS_STATE_FULL_POWER)
+ lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, true);
+ }
+
+ if (priv->is_deep_sleep) {
+ priv->is_deep_sleep = 0;
+ wake_up_interruptible(&priv->ds_awake_q);
+ }
+
+ priv->is_host_sleep_configured = 0;
+ priv->is_host_sleep_activated = 0;
+ wake_up_interruptible(&priv->host_sleep_q);
+
+ /* Stop the thread servicing the interrupts */
+ priv->surpriseremoved = 1;
+ kthread_stop(priv->main_thread);
+
+ lbs_free_adapter(priv);
+ lbs_cfg_free(priv);
+ free_netdev(dev);
+}
+EXPORT_SYMBOL_GPL(lbs_remove_card);
+
+
+int lbs_rtap_supported(struct lbs_private *priv)
+{
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V5)
+ return 1;
+
+ /* newer firmware use a capability mask */
+ return ((MRVL_FW_MAJOR_REV(priv->fwrelease) >= MRVL_FW_V10) &&
+ (priv->fwcapinfo & MESH_CAPINFO_ENABLE_MASK));
+}
+
+
+int lbs_start_card(struct lbs_private *priv)
+{
+ struct net_device *dev = priv->dev;
+ int ret;
+
+ /* poke the firmware */
+ ret = lbs_setup_firmware(priv);
+ if (ret)
+ goto done;
+
+ if (!lbs_disablemesh)
+ lbs_init_mesh(priv);
+ else
+ pr_info("%s: mesh disabled\n", dev->name);
+
+ ret = lbs_cfg_register(priv);
+ if (ret) {
+ pr_err("cannot register device\n");
+ goto done;
+ }
+
+ if (lbs_mesh_activated(priv))
+ lbs_start_mesh(priv);
+
+ lbs_debugfs_init_one(priv, dev);
+
+ netdev_info(dev, "Marvell WLAN 802.11 adapter\n");
+
+ ret = 0;
+
+done:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lbs_start_card);
+
+
+void lbs_stop_card(struct lbs_private *priv)
+{
+ struct net_device *dev;
+
+ if (!priv)
+ return;
+ dev = priv->dev;
+
+ /* If the netdev isn't registered, it means that lbs_start_card() was
+ * never called so we have nothing to do here. */
+ if (dev->reg_state != NETREG_REGISTERED)
+ return;
+
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+
+ lbs_debugfs_remove_one(priv);
+ lbs_deinit_mesh(priv);
+ unregister_netdev(dev);
+}
+EXPORT_SYMBOL_GPL(lbs_stop_card);
+
+
+void lbs_queue_event(struct lbs_private *priv, u32 event)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (priv->psstate == PS_STATE_SLEEP)
+ priv->psstate = PS_STATE_AWAKE;
+
+ kfifo_in(&priv->event_fifo, (unsigned char *) &event, sizeof(u32));
+
+ wake_up(&priv->waitq);
+
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+}
+EXPORT_SYMBOL_GPL(lbs_queue_event);
+
+void lbs_notify_command_response(struct lbs_private *priv, u8 resp_idx)
+{
+ if (priv->psstate == PS_STATE_SLEEP)
+ priv->psstate = PS_STATE_AWAKE;
+
+ /* Swap buffers by flipping the response index */
+ BUG_ON(resp_idx > 1);
+ priv->resp_idx = resp_idx;
+
+ wake_up(&priv->waitq);
+}
+EXPORT_SYMBOL_GPL(lbs_notify_command_response);
+
+static int __init lbs_init_module(void)
+{
+ memset(&confirm_sleep, 0, sizeof(confirm_sleep));
+ confirm_sleep.hdr.command = cpu_to_le16(CMD_802_11_PS_MODE);
+ confirm_sleep.hdr.size = cpu_to_le16(sizeof(confirm_sleep));
+ confirm_sleep.action = cpu_to_le16(PS_MODE_ACTION_SLEEP_CONFIRMED);
+ lbs_debugfs_init();
+
+ return 0;
+}
+
+static void __exit lbs_exit_module(void)
+{
+ lbs_debugfs_remove();
+}
+
+module_init(lbs_init_module);
+module_exit(lbs_exit_module);
+
+MODULE_DESCRIPTION("Libertas WLAN Driver Library");
+MODULE_AUTHOR("Marvell International Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/marvell/libertas/mesh.c b/drivers/net/wireless/marvell/libertas/mesh.c
new file mode 100644
index 000000000..c68814841
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/mesh.c
@@ -0,0 +1,1139 @@
+// SPDX-License-Identifier: GPL-2.0
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/hardirq.h>
+#include <linux/netdevice.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <linux/kthread.h>
+#include <linux/kfifo.h>
+#include <net/cfg80211.h>
+
+#include "mesh.h"
+#include "decl.h"
+#include "cmd.h"
+
+
+static int lbs_add_mesh(struct lbs_private *priv);
+
+/***************************************************************************
+ * Mesh command handling
+ */
+
+static int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action,
+ struct cmd_ds_mesh_access *cmd)
+{
+ int ret;
+
+ cmd->hdr.command = cpu_to_le16(CMD_MESH_ACCESS);
+ cmd->hdr.size = cpu_to_le16(sizeof(*cmd));
+ cmd->hdr.result = 0;
+
+ cmd->action = cpu_to_le16(cmd_action);
+
+ ret = lbs_cmd_with_response(priv, CMD_MESH_ACCESS, cmd);
+
+ return ret;
+}
+
+static int __lbs_mesh_config_send(struct lbs_private *priv,
+ struct cmd_ds_mesh_config *cmd,
+ uint16_t action, uint16_t type)
+{
+ int ret;
+ u16 command = CMD_MESH_CONFIG_OLD;
+
+ /*
+ * Command id is 0xac for v10 FW along with mesh interface
+ * id in bits 14-13-12.
+ */
+ if (priv->mesh_tlv == TLV_TYPE_MESH_ID)
+ command = CMD_MESH_CONFIG |
+ (MESH_IFACE_ID << MESH_IFACE_BIT_OFFSET);
+
+ cmd->hdr.command = cpu_to_le16(command);
+ cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_mesh_config));
+ cmd->hdr.result = 0;
+
+ cmd->type = cpu_to_le16(type);
+ cmd->action = cpu_to_le16(action);
+
+ ret = lbs_cmd_with_response(priv, command, cmd);
+
+ return ret;
+}
+
+static int lbs_mesh_config_send(struct lbs_private *priv,
+ struct cmd_ds_mesh_config *cmd,
+ uint16_t action, uint16_t type)
+{
+ int ret;
+
+ if (!(priv->fwcapinfo & FW_CAPINFO_PERSISTENT_CONFIG))
+ return -EOPNOTSUPP;
+
+ ret = __lbs_mesh_config_send(priv, cmd, action, type);
+ return ret;
+}
+
+/* This function is the CMD_MESH_CONFIG legacy function. It only handles the
+ * START and STOP actions. The extended actions supported by CMD_MESH_CONFIG
+ * are all handled by preparing a struct cmd_ds_mesh_config and passing it to
+ * lbs_mesh_config_send.
+ */
+static int lbs_mesh_config(struct lbs_private *priv, uint16_t action,
+ uint16_t chan)
+{
+ struct wireless_dev *mesh_wdev;
+ struct cmd_ds_mesh_config cmd;
+ struct mrvl_meshie *ie;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.channel = cpu_to_le16(chan);
+ ie = (struct mrvl_meshie *)cmd.data;
+
+ switch (action) {
+ case CMD_ACT_MESH_CONFIG_START:
+ ie->id = WLAN_EID_VENDOR_SPECIFIC;
+ ie->val.oui[0] = 0x00;
+ ie->val.oui[1] = 0x50;
+ ie->val.oui[2] = 0x43;
+ ie->val.type = MARVELL_MESH_IE_TYPE;
+ ie->val.subtype = MARVELL_MESH_IE_SUBTYPE;
+ ie->val.version = MARVELL_MESH_IE_VERSION;
+ ie->val.active_protocol_id = MARVELL_MESH_PROTO_ID_HWMP;
+ ie->val.active_metric_id = MARVELL_MESH_METRIC_ID;
+ ie->val.mesh_capability = MARVELL_MESH_CAPABILITY;
+
+ if (priv->mesh_dev) {
+ mesh_wdev = priv->mesh_dev->ieee80211_ptr;
+ ie->val.mesh_id_len = mesh_wdev->mesh_id_up_len;
+ memcpy(ie->val.mesh_id, mesh_wdev->ssid,
+ mesh_wdev->mesh_id_up_len);
+ }
+
+ ie->len = sizeof(struct mrvl_meshie_val) -
+ IEEE80211_MAX_SSID_LEN + ie->val.mesh_id_len;
+
+ cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie_val));
+ break;
+ case CMD_ACT_MESH_CONFIG_STOP:
+ break;
+ default:
+ return -1;
+ }
+ lbs_deb_cmd("mesh config action %d type %x channel %d SSID %*pE\n",
+ action, priv->mesh_tlv, chan, ie->val.mesh_id_len,
+ ie->val.mesh_id);
+
+ return __lbs_mesh_config_send(priv, &cmd, action, priv->mesh_tlv);
+}
+
+int lbs_mesh_set_channel(struct lbs_private *priv, u8 channel)
+{
+ priv->mesh_channel = channel;
+ return lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, channel);
+}
+
+static uint16_t lbs_mesh_get_channel(struct lbs_private *priv)
+{
+ return priv->mesh_channel ?: 1;
+}
+
+/***************************************************************************
+ * Mesh sysfs support
+ */
+
+/*
+ * Attributes exported through sysfs
+ */
+
+/**
+ * lbs_anycast_get - Get function for sysfs attribute anycast_mask
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t lbs_anycast_get(struct device *dev,
+ struct device_attribute *attr, char * buf)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_access mesh_access;
+ int ret;
+
+ memset(&mesh_access, 0, sizeof(mesh_access));
+
+ ret = lbs_mesh_access(priv, CMD_ACT_MESH_GET_ANYCAST, &mesh_access);
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 12, "0x%X\n", le32_to_cpu(mesh_access.data[0]));
+}
+
+/**
+ * lbs_anycast_set - Set function for sysfs attribute anycast_mask
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t lbs_anycast_set(struct device *dev,
+ struct device_attribute *attr, const char * buf, size_t count)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_access mesh_access;
+ uint32_t datum;
+ int ret;
+
+ memset(&mesh_access, 0, sizeof(mesh_access));
+ sscanf(buf, "%x", &datum);
+ mesh_access.data[0] = cpu_to_le32(datum);
+
+ ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_ANYCAST, &mesh_access);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * lbs_prb_rsp_limit_get - Get function for sysfs attribute prb_rsp_limit
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t lbs_prb_rsp_limit_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_access mesh_access;
+ int ret;
+ u32 retry_limit;
+
+ memset(&mesh_access, 0, sizeof(mesh_access));
+ mesh_access.data[0] = cpu_to_le32(CMD_ACT_GET);
+
+ ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_GET_PRB_RSP_LIMIT,
+ &mesh_access);
+ if (ret)
+ return ret;
+
+ retry_limit = le32_to_cpu(mesh_access.data[1]);
+ return snprintf(buf, 10, "%d\n", retry_limit);
+}
+
+/**
+ * lbs_prb_rsp_limit_set - Set function for sysfs attribute prb_rsp_limit
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t lbs_prb_rsp_limit_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_access mesh_access;
+ int ret;
+ unsigned long retry_limit;
+
+ memset(&mesh_access, 0, sizeof(mesh_access));
+ mesh_access.data[0] = cpu_to_le32(CMD_ACT_SET);
+
+ ret = kstrtoul(buf, 10, &retry_limit);
+ if (ret)
+ return ret;
+ if (retry_limit > 15)
+ return -ENOTSUPP;
+
+ mesh_access.data[1] = cpu_to_le32(retry_limit);
+
+ ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_GET_PRB_RSP_LIMIT,
+ &mesh_access);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * lbs_mesh_get - Get function for sysfs attribute mesh
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t lbs_mesh_get(struct device *dev,
+ struct device_attribute *attr, char * buf)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ return snprintf(buf, 5, "0x%X\n", !!priv->mesh_dev);
+}
+
+/**
+ * lbs_mesh_set - Set function for sysfs attribute mesh
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t lbs_mesh_set(struct device *dev,
+ struct device_attribute *attr, const char * buf, size_t count)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ int enable;
+
+ sscanf(buf, "%x", &enable);
+ enable = !!enable;
+ if (enable == !!priv->mesh_dev)
+ return count;
+
+ if (enable)
+ lbs_add_mesh(priv);
+ else
+ lbs_remove_mesh(priv);
+
+ return count;
+}
+
+/*
+ * lbs_mesh attribute to be exported per ethX interface
+ * through sysfs (/sys/class/net/ethX/lbs_mesh)
+ */
+static DEVICE_ATTR(lbs_mesh, 0644, lbs_mesh_get, lbs_mesh_set);
+
+/*
+ * anycast_mask attribute to be exported per mshX interface
+ * through sysfs (/sys/class/net/mshX/anycast_mask)
+ */
+static DEVICE_ATTR(anycast_mask, 0644, lbs_anycast_get, lbs_anycast_set);
+
+/*
+ * prb_rsp_limit attribute to be exported per mshX interface
+ * through sysfs (/sys/class/net/mshX/prb_rsp_limit)
+ */
+static DEVICE_ATTR(prb_rsp_limit, 0644, lbs_prb_rsp_limit_get,
+ lbs_prb_rsp_limit_set);
+
+static struct attribute *lbs_mesh_sysfs_entries[] = {
+ &dev_attr_anycast_mask.attr,
+ &dev_attr_prb_rsp_limit.attr,
+ NULL,
+};
+
+static const struct attribute_group lbs_mesh_attr_group = {
+ .attrs = lbs_mesh_sysfs_entries,
+};
+
+
+/***************************************************************************
+ * Persistent configuration support
+ */
+
+static int mesh_get_default_parameters(struct device *dev,
+ struct mrvl_mesh_defaults *defs)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_config cmd;
+ int ret;
+
+ memset(&cmd, 0, sizeof(struct cmd_ds_mesh_config));
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_GET,
+ CMD_TYPE_MESH_GET_DEFAULTS);
+
+ if (ret)
+ return -EOPNOTSUPP;
+
+ memcpy(defs, &cmd.data[0], sizeof(struct mrvl_mesh_defaults));
+
+ return 0;
+}
+
+/**
+ * bootflag_get - Get function for sysfs attribute bootflag
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t bootflag_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 12, "%d\n", le32_to_cpu(defs.bootflag));
+}
+
+/**
+ * bootflag_set - Set function for sysfs attribute bootflag
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t bootflag_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_config cmd;
+ uint32_t datum;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ ret = sscanf(buf, "%d", &datum);
+ if ((ret != 1) || (datum > 1))
+ return -EINVAL;
+
+ *((__le32 *)&cmd.data[0]) = cpu_to_le32(!!datum);
+ cmd.length = cpu_to_le16(sizeof(uint32_t));
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_BOOTFLAG);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * boottime_get - Get function for sysfs attribute boottime
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t boottime_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 12, "%d\n", defs.boottime);
+}
+
+/**
+ * boottime_set - Set function for sysfs attribute boottime
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t boottime_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_config cmd;
+ uint32_t datum;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ ret = sscanf(buf, "%d", &datum);
+ if ((ret != 1) || (datum > 255))
+ return -EINVAL;
+
+ /* A too small boot time will result in the device booting into
+ * standalone (no-host) mode before the host can take control of it,
+ * so the change will be hard to revert. This may be a desired
+ * feature (e.g to configure a very fast boot time for devices that
+ * will not be attached to a host), but dangerous. So I'm enforcing a
+ * lower limit of 20 seconds: remove and recompile the driver if this
+ * does not work for you.
+ */
+ datum = (datum < 20) ? 20 : datum;
+ cmd.data[0] = datum;
+ cmd.length = cpu_to_le16(sizeof(uint8_t));
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_BOOTTIME);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * channel_get - Get function for sysfs attribute channel
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t channel_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 12, "%d\n", le16_to_cpu(defs.channel));
+}
+
+/**
+ * channel_set - Set function for sysfs attribute channel
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t channel_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ struct cmd_ds_mesh_config cmd;
+ uint32_t datum;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ ret = sscanf(buf, "%d", &datum);
+ if (ret != 1 || datum < 1 || datum > 11)
+ return -EINVAL;
+
+ *((__le16 *)&cmd.data[0]) = cpu_to_le16(datum);
+ cmd.length = cpu_to_le16(sizeof(uint16_t));
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_DEF_CHANNEL);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * mesh_id_get - Get function for sysfs attribute mesh_id
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t mesh_id_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ if (defs.meshie.val.mesh_id_len > IEEE80211_MAX_SSID_LEN) {
+ dev_err(dev, "inconsistent mesh ID length\n");
+ defs.meshie.val.mesh_id_len = IEEE80211_MAX_SSID_LEN;
+ }
+
+ memcpy(buf, defs.meshie.val.mesh_id, defs.meshie.val.mesh_id_len);
+ buf[defs.meshie.val.mesh_id_len] = '\n';
+ buf[defs.meshie.val.mesh_id_len + 1] = '\0';
+
+ return defs.meshie.val.mesh_id_len + 1;
+}
+
+/**
+ * mesh_id_set - Set function for sysfs attribute mesh_id
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t mesh_id_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cmd_ds_mesh_config cmd;
+ struct mrvl_mesh_defaults defs;
+ struct mrvl_meshie *ie;
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ int len;
+ int ret;
+
+ if (count < 2 || count > IEEE80211_MAX_SSID_LEN + 1)
+ return -EINVAL;
+
+ memset(&cmd, 0, sizeof(struct cmd_ds_mesh_config));
+ ie = (struct mrvl_meshie *) &cmd.data[0];
+
+ /* fetch all other Information Element parameters */
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie));
+
+ /* transfer IE elements */
+ memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie));
+
+ len = count - 1;
+ memcpy(ie->val.mesh_id, buf, len);
+ /* SSID len */
+ ie->val.mesh_id_len = len;
+ /* IE len */
+ ie->len = sizeof(struct mrvl_meshie_val) - IEEE80211_MAX_SSID_LEN + len;
+
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_MESH_IE);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * protocol_id_get - Get function for sysfs attribute protocol_id
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t protocol_id_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 5, "%d\n", defs.meshie.val.active_protocol_id);
+}
+
+/**
+ * protocol_id_set - Set function for sysfs attribute protocol_id
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t protocol_id_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct cmd_ds_mesh_config cmd;
+ struct mrvl_mesh_defaults defs;
+ struct mrvl_meshie *ie;
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ uint32_t datum;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ ret = sscanf(buf, "%d", &datum);
+ if ((ret != 1) || (datum > 255))
+ return -EINVAL;
+
+ /* fetch all other Information Element parameters */
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie));
+
+ /* transfer IE elements */
+ ie = (struct mrvl_meshie *) &cmd.data[0];
+ memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie));
+ /* update protocol id */
+ ie->val.active_protocol_id = datum;
+
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_MESH_IE);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * metric_id_get - Get function for sysfs attribute metric_id
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t metric_id_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 5, "%d\n", defs.meshie.val.active_metric_id);
+}
+
+/**
+ * metric_id_set - Set function for sysfs attribute metric_id
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t metric_id_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cmd_ds_mesh_config cmd;
+ struct mrvl_mesh_defaults defs;
+ struct mrvl_meshie *ie;
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ uint32_t datum;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ ret = sscanf(buf, "%d", &datum);
+ if ((ret != 1) || (datum > 255))
+ return -EINVAL;
+
+ /* fetch all other Information Element parameters */
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie));
+
+ /* transfer IE elements */
+ ie = (struct mrvl_meshie *) &cmd.data[0];
+ memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie));
+ /* update metric id */
+ ie->val.active_metric_id = datum;
+
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_MESH_IE);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+/**
+ * capability_get - Get function for sysfs attribute capability
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer where data will be returned
+ */
+static ssize_t capability_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mrvl_mesh_defaults defs;
+ int ret;
+
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ if (ret)
+ return ret;
+
+ return snprintf(buf, 5, "%d\n", defs.meshie.val.mesh_capability);
+}
+
+/**
+ * capability_set - Set function for sysfs attribute capability
+ * @dev: the &struct device
+ * @attr: device attributes
+ * @buf: buffer that contains new attribute value
+ * @count: size of buffer
+ */
+static ssize_t capability_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct cmd_ds_mesh_config cmd;
+ struct mrvl_mesh_defaults defs;
+ struct mrvl_meshie *ie;
+ struct lbs_private *priv = to_net_dev(dev)->ml_priv;
+ uint32_t datum;
+ int ret;
+
+ memset(&cmd, 0, sizeof(cmd));
+ ret = sscanf(buf, "%d", &datum);
+ if ((ret != 1) || (datum > 255))
+ return -EINVAL;
+
+ /* fetch all other Information Element parameters */
+ ret = mesh_get_default_parameters(dev, &defs);
+
+ cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie));
+
+ /* transfer IE elements */
+ ie = (struct mrvl_meshie *) &cmd.data[0];
+ memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie));
+ /* update value */
+ ie->val.mesh_capability = datum;
+
+ ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET,
+ CMD_TYPE_MESH_SET_MESH_IE);
+ if (ret)
+ return ret;
+
+ return strlen(buf);
+}
+
+
+static DEVICE_ATTR(bootflag, 0644, bootflag_get, bootflag_set);
+static DEVICE_ATTR(boottime, 0644, boottime_get, boottime_set);
+static DEVICE_ATTR(channel, 0644, channel_get, channel_set);
+static DEVICE_ATTR(mesh_id, 0644, mesh_id_get, mesh_id_set);
+static DEVICE_ATTR(protocol_id, 0644, protocol_id_get, protocol_id_set);
+static DEVICE_ATTR(metric_id, 0644, metric_id_get, metric_id_set);
+static DEVICE_ATTR(capability, 0644, capability_get, capability_set);
+
+static struct attribute *boot_opts_attrs[] = {
+ &dev_attr_bootflag.attr,
+ &dev_attr_boottime.attr,
+ &dev_attr_channel.attr,
+ NULL
+};
+
+static const struct attribute_group boot_opts_group = {
+ .name = "boot_options",
+ .attrs = boot_opts_attrs,
+};
+
+static struct attribute *mesh_ie_attrs[] = {
+ &dev_attr_mesh_id.attr,
+ &dev_attr_protocol_id.attr,
+ &dev_attr_metric_id.attr,
+ &dev_attr_capability.attr,
+ NULL
+};
+
+static const struct attribute_group mesh_ie_group = {
+ .name = "mesh_ie",
+ .attrs = mesh_ie_attrs,
+};
+
+
+/***************************************************************************
+ * Initializing and starting, stopping mesh
+ */
+
+/*
+ * Check mesh FW version and appropriately send the mesh start
+ * command
+ */
+void lbs_init_mesh(struct lbs_private *priv)
+{
+ /* Determine mesh_fw_ver from fwrelease and fwcapinfo */
+ /* 5.0.16p0 9.0.0.p0 is known to NOT support any mesh */
+ /* 5.110.22 have mesh command with 0xa3 command id */
+ /* 10.0.0.p0 FW brings in mesh config command with different id */
+ /* Check FW version MSB and initialize mesh_fw_ver */
+ if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V5) {
+ /* Enable mesh, if supported, and work out which TLV it uses.
+ 0x100 + 291 is an unofficial value used in 5.110.20.pXX
+ 0x100 + 37 is the official value used in 5.110.21.pXX
+ but we check them in that order because 20.pXX doesn't
+ give an error -- it just silently fails. */
+
+ /* 5.110.20.pXX firmware will fail the command if the channel
+ doesn't match the existing channel. But only if the TLV
+ is correct. If the channel is wrong, _BOTH_ versions will
+ give an error to 0x100+291, and allow 0x100+37 to succeed.
+ It's just that 5.110.20.pXX will not have done anything
+ useful */
+
+ priv->mesh_tlv = TLV_TYPE_OLD_MESH_ID;
+ if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) {
+ priv->mesh_tlv = TLV_TYPE_MESH_ID;
+ if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1))
+ priv->mesh_tlv = 0;
+ }
+ } else
+ if ((MRVL_FW_MAJOR_REV(priv->fwrelease) >= MRVL_FW_V10) &&
+ (priv->fwcapinfo & MESH_CAPINFO_ENABLE_MASK)) {
+ /* 10.0.0.pXX new firmwares should succeed with TLV
+ * 0x100+37; Do not invoke command with old TLV.
+ */
+ priv->mesh_tlv = TLV_TYPE_MESH_ID;
+ if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1))
+ priv->mesh_tlv = 0;
+ }
+
+ /* Stop meshing until interface is brought up */
+ lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_STOP, 1);
+}
+
+void lbs_start_mesh(struct lbs_private *priv)
+{
+ lbs_add_mesh(priv);
+
+ if (device_create_file(&priv->dev->dev, &dev_attr_lbs_mesh))
+ netdev_err(priv->dev, "cannot register lbs_mesh attribute\n");
+}
+
+int lbs_deinit_mesh(struct lbs_private *priv)
+{
+ struct net_device *dev = priv->dev;
+ int ret = 0;
+
+ if (priv->mesh_tlv) {
+ device_remove_file(&dev->dev, &dev_attr_lbs_mesh);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+
+/**
+ * lbs_mesh_stop - close the mshX interface
+ *
+ * @dev: A pointer to &net_device structure
+ * returns: 0
+ */
+static int lbs_mesh_stop(struct net_device *dev)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_STOP,
+ lbs_mesh_get_channel(priv));
+
+ spin_lock_irq(&priv->driver_lock);
+
+ netif_stop_queue(dev);
+ netif_carrier_off(dev);
+
+ spin_unlock_irq(&priv->driver_lock);
+
+ lbs_update_mcast(priv);
+ if (!lbs_iface_active(priv))
+ lbs_stop_iface(priv);
+
+ return 0;
+}
+
+/**
+ * lbs_mesh_dev_open - open the mshX interface
+ *
+ * @dev: A pointer to &net_device structure
+ * returns: 0 or -EBUSY if monitor mode active
+ */
+static int lbs_mesh_dev_open(struct net_device *dev)
+{
+ struct lbs_private *priv = dev->ml_priv;
+ int ret = 0;
+
+ if (!priv->iface_running) {
+ ret = lbs_start_iface(priv);
+ if (ret)
+ goto out;
+ }
+
+ spin_lock_irq(&priv->driver_lock);
+
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) {
+ ret = -EBUSY;
+ spin_unlock_irq(&priv->driver_lock);
+ goto out;
+ }
+
+ netif_carrier_on(dev);
+
+ if (!priv->tx_pending_len)
+ netif_wake_queue(dev);
+
+ spin_unlock_irq(&priv->driver_lock);
+
+ ret = lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START,
+ lbs_mesh_get_channel(priv));
+
+out:
+ return ret;
+}
+
+static const struct net_device_ops mesh_netdev_ops = {
+ .ndo_open = lbs_mesh_dev_open,
+ .ndo_stop = lbs_mesh_stop,
+ .ndo_start_xmit = lbs_hard_start_xmit,
+ .ndo_set_mac_address = lbs_set_mac_address,
+ .ndo_set_rx_mode = lbs_set_multicast_list,
+};
+
+/**
+ * lbs_add_mesh - add mshX interface
+ *
+ * @priv: A pointer to the &struct lbs_private structure
+ * returns: 0 if successful, -X otherwise
+ */
+static int lbs_add_mesh(struct lbs_private *priv)
+{
+ struct net_device *mesh_dev = NULL;
+ struct wireless_dev *mesh_wdev;
+ int ret = 0;
+
+ /* Allocate a virtual mesh device */
+ mesh_wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
+ if (!mesh_wdev) {
+ lbs_deb_mesh("init mshX wireless device failed\n");
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ mesh_dev = alloc_netdev(0, "msh%d", NET_NAME_UNKNOWN, ether_setup);
+ if (!mesh_dev) {
+ lbs_deb_mesh("init mshX device failed\n");
+ ret = -ENOMEM;
+ goto err_free_wdev;
+ }
+
+ mesh_wdev->iftype = NL80211_IFTYPE_MESH_POINT;
+ mesh_wdev->wiphy = priv->wdev->wiphy;
+
+ if (priv->mesh_tlv) {
+ sprintf(mesh_wdev->ssid, "mesh");
+ mesh_wdev->mesh_id_up_len = 4;
+ }
+
+ mesh_wdev->netdev = mesh_dev;
+
+ mesh_dev->ml_priv = priv;
+ mesh_dev->ieee80211_ptr = mesh_wdev;
+ priv->mesh_dev = mesh_dev;
+
+ mesh_dev->netdev_ops = &mesh_netdev_ops;
+ mesh_dev->ethtool_ops = &lbs_ethtool_ops;
+ eth_hw_addr_inherit(mesh_dev, priv->dev);
+
+ SET_NETDEV_DEV(priv->mesh_dev, priv->dev->dev.parent);
+
+ mesh_dev->flags |= IFF_BROADCAST | IFF_MULTICAST;
+ mesh_dev->sysfs_groups[0] = &lbs_mesh_attr_group;
+ mesh_dev->sysfs_groups[1] = &boot_opts_group;
+ mesh_dev->sysfs_groups[2] = &mesh_ie_group;
+
+ /* Register virtual mesh interface */
+ ret = register_netdev(mesh_dev);
+ if (ret) {
+ pr_err("cannot register mshX virtual interface\n");
+ goto err_free_netdev;
+ }
+
+ /* Everything successful */
+ ret = 0;
+ goto done;
+
+err_free_netdev:
+ free_netdev(mesh_dev);
+
+err_free_wdev:
+ kfree(mesh_wdev);
+
+done:
+ return ret;
+}
+
+void lbs_remove_mesh(struct lbs_private *priv)
+{
+ struct net_device *mesh_dev;
+
+ mesh_dev = priv->mesh_dev;
+ if (!mesh_dev)
+ return;
+
+ netif_stop_queue(mesh_dev);
+ netif_carrier_off(mesh_dev);
+ unregister_netdev(mesh_dev);
+ priv->mesh_dev = NULL;
+ kfree(mesh_dev->ieee80211_ptr);
+ free_netdev(mesh_dev);
+}
+
+
+/***************************************************************************
+ * Sending and receiving
+ */
+struct net_device *lbs_mesh_set_dev(struct lbs_private *priv,
+ struct net_device *dev, struct rxpd *rxpd)
+{
+ if (priv->mesh_dev) {
+ if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) {
+ if (rxpd->rx_control & RxPD_MESH_FRAME)
+ dev = priv->mesh_dev;
+ } else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) {
+ if (rxpd->u.bss.bss_num == MESH_IFACE_ID)
+ dev = priv->mesh_dev;
+ }
+ }
+ return dev;
+}
+
+
+void lbs_mesh_set_txpd(struct lbs_private *priv,
+ struct net_device *dev, struct txpd *txpd)
+{
+ if (dev == priv->mesh_dev) {
+ if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID)
+ txpd->tx_control |= cpu_to_le32(TxPD_MESH_FRAME);
+ else if (priv->mesh_tlv == TLV_TYPE_MESH_ID)
+ txpd->u.bss.bss_num = MESH_IFACE_ID;
+ }
+}
+
+
+/***************************************************************************
+ * Ethtool related
+ */
+
+static const char mesh_stat_strings[MESH_STATS_NUM][ETH_GSTRING_LEN] = {
+ "drop_duplicate_bcast",
+ "drop_ttl_zero",
+ "drop_no_fwd_route",
+ "drop_no_buffers",
+ "fwded_unicast_cnt",
+ "fwded_bcast_cnt",
+ "drop_blind_table",
+ "tx_failed_cnt"
+};
+
+void lbs_mesh_ethtool_get_stats(struct net_device *dev,
+ struct ethtool_stats *stats, uint64_t *data)
+{
+ struct lbs_private *priv = dev->ml_priv;
+ struct cmd_ds_mesh_access mesh_access;
+ int ret;
+
+ /* Get Mesh Statistics */
+ ret = lbs_mesh_access(priv, CMD_ACT_MESH_GET_STATS, &mesh_access);
+
+ if (ret) {
+ memset(data, 0, MESH_STATS_NUM*(sizeof(uint64_t)));
+ return;
+ }
+
+ priv->mstats.fwd_drop_rbt = le32_to_cpu(mesh_access.data[0]);
+ priv->mstats.fwd_drop_ttl = le32_to_cpu(mesh_access.data[1]);
+ priv->mstats.fwd_drop_noroute = le32_to_cpu(mesh_access.data[2]);
+ priv->mstats.fwd_drop_nobuf = le32_to_cpu(mesh_access.data[3]);
+ priv->mstats.fwd_unicast_cnt = le32_to_cpu(mesh_access.data[4]);
+ priv->mstats.fwd_bcast_cnt = le32_to_cpu(mesh_access.data[5]);
+ priv->mstats.drop_blind = le32_to_cpu(mesh_access.data[6]);
+ priv->mstats.tx_failed_cnt = le32_to_cpu(mesh_access.data[7]);
+
+ data[0] = priv->mstats.fwd_drop_rbt;
+ data[1] = priv->mstats.fwd_drop_ttl;
+ data[2] = priv->mstats.fwd_drop_noroute;
+ data[3] = priv->mstats.fwd_drop_nobuf;
+ data[4] = priv->mstats.fwd_unicast_cnt;
+ data[5] = priv->mstats.fwd_bcast_cnt;
+ data[6] = priv->mstats.drop_blind;
+ data[7] = priv->mstats.tx_failed_cnt;
+}
+
+int lbs_mesh_ethtool_get_sset_count(struct net_device *dev, int sset)
+{
+ struct lbs_private *priv = dev->ml_priv;
+
+ if (sset == ETH_SS_STATS && dev == priv->mesh_dev)
+ return MESH_STATS_NUM;
+
+ return -EOPNOTSUPP;
+}
+
+void lbs_mesh_ethtool_get_strings(struct net_device *dev,
+ uint32_t stringset, uint8_t *s)
+{
+ switch (stringset) {
+ case ETH_SS_STATS:
+ memcpy(s, mesh_stat_strings, sizeof(mesh_stat_strings));
+ break;
+ }
+}
diff --git a/drivers/net/wireless/marvell/libertas/mesh.h b/drivers/net/wireless/marvell/libertas/mesh.h
new file mode 100644
index 000000000..d49717b20
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/mesh.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Contains all definitions needed for the Libertas' MESH implementation.
+ */
+#ifndef _LBS_MESH_H_
+#define _LBS_MESH_H_
+
+
+#include <net/iw_handler.h>
+#include <net/lib80211.h>
+
+#include "host.h"
+#include "dev.h"
+
+#ifdef CONFIG_LIBERTAS_MESH
+
+struct net_device;
+
+void lbs_init_mesh(struct lbs_private *priv);
+void lbs_start_mesh(struct lbs_private *priv);
+int lbs_deinit_mesh(struct lbs_private *priv);
+
+void lbs_remove_mesh(struct lbs_private *priv);
+
+static inline bool lbs_mesh_activated(struct lbs_private *priv)
+{
+ return !!priv->mesh_tlv;
+}
+
+int lbs_mesh_set_channel(struct lbs_private *priv, u8 channel);
+
+/* Sending / Receiving */
+
+struct rxpd;
+struct txpd;
+
+struct net_device *lbs_mesh_set_dev(struct lbs_private *priv,
+ struct net_device *dev, struct rxpd *rxpd);
+void lbs_mesh_set_txpd(struct lbs_private *priv,
+ struct net_device *dev, struct txpd *txpd);
+
+
+/* Command handling */
+
+struct cmd_ds_command;
+struct cmd_ds_mesh_access;
+struct cmd_ds_mesh_config;
+
+
+/* Ethtool statistics */
+
+struct ethtool_stats;
+
+void lbs_mesh_ethtool_get_stats(struct net_device *dev,
+ struct ethtool_stats *stats, uint64_t *data);
+int lbs_mesh_ethtool_get_sset_count(struct net_device *dev, int sset);
+void lbs_mesh_ethtool_get_strings(struct net_device *dev,
+ uint32_t stringset, uint8_t *s);
+
+
+#else
+
+#define lbs_init_mesh(priv)
+#define lbs_deinit_mesh(priv)
+#define lbs_start_mesh(priv)
+#define lbs_add_mesh(priv)
+#define lbs_remove_mesh(priv)
+#define lbs_mesh_set_dev(priv, dev, rxpd) (dev)
+#define lbs_mesh_set_txpd(priv, dev, txpd)
+#define lbs_mesh_set_channel(priv, channel) (0)
+#define lbs_mesh_activated(priv) (false)
+
+#endif
+
+
+
+#endif
diff --git a/drivers/net/wireless/marvell/libertas/radiotap.h b/drivers/net/wireless/marvell/libertas/radiotap.h
new file mode 100644
index 000000000..1ed5608d3
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/radiotap.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <net/ieee80211_radiotap.h>
+
+struct tx_radiotap_hdr {
+ struct ieee80211_radiotap_header hdr;
+ u8 rate;
+ u8 txpower;
+ u8 rts_retries;
+ u8 data_retries;
+} __packed;
+
+#define TX_RADIOTAP_PRESENT ( \
+ (1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_DBM_TX_POWER) | \
+ (1 << IEEE80211_RADIOTAP_RTS_RETRIES) | \
+ (1 << IEEE80211_RADIOTAP_DATA_RETRIES) | \
+ 0)
+
+#define IEEE80211_FC_VERSION_MASK 0x0003
+#define IEEE80211_FC_TYPE_MASK 0x000c
+#define IEEE80211_FC_TYPE_MGT 0x0000
+#define IEEE80211_FC_TYPE_CTL 0x0004
+#define IEEE80211_FC_TYPE_DATA 0x0008
+#define IEEE80211_FC_SUBTYPE_MASK 0x00f0
+#define IEEE80211_FC_TOFROMDS_MASK 0x0300
+#define IEEE80211_FC_TODS_MASK 0x0100
+#define IEEE80211_FC_FROMDS_MASK 0x0200
+#define IEEE80211_FC_NODS 0x0000
+#define IEEE80211_FC_TODS 0x0100
+#define IEEE80211_FC_FROMDS 0x0200
+#define IEEE80211_FC_DSTODS 0x0300
+
+struct rx_radiotap_hdr {
+ struct ieee80211_radiotap_header hdr;
+ u8 flags;
+ u8 rate;
+ u8 antsignal;
+} __packed;
+
+#define RX_RADIOTAP_PRESENT ( \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL) |\
+ 0)
+
diff --git a/drivers/net/wireless/marvell/libertas/rx.c b/drivers/net/wireless/marvell/libertas/rx.c
new file mode 100644
index 000000000..9f24b0760
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/rx.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains the handling of RX in wlan driver.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/etherdevice.h>
+#include <linux/hardirq.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/export.h>
+#include <net/cfg80211.h>
+
+#include "defs.h"
+#include "host.h"
+#include "radiotap.h"
+#include "decl.h"
+#include "dev.h"
+#include "mesh.h"
+
+struct eth803hdr {
+ u8 dest_addr[6];
+ u8 src_addr[6];
+ u16 h803_len;
+} __packed;
+
+struct rfc1042hdr {
+ u8 llc_dsap;
+ u8 llc_ssap;
+ u8 llc_ctrl;
+ u8 snap_oui[3];
+ u16 snap_type;
+} __packed;
+
+struct rxpackethdr {
+ struct eth803hdr eth803_hdr;
+ struct rfc1042hdr rfc1042_hdr;
+} __packed;
+
+struct rx80211packethdr {
+ struct rxpd rx_pd;
+ void *eth80211_hdr;
+} __packed;
+
+static int process_rxed_802_11_packet(struct lbs_private *priv,
+ struct sk_buff *skb);
+
+/**
+ * lbs_process_rxed_packet - processes received packet and forwards it
+ * to kernel/upper layer
+ *
+ * @priv: A pointer to &struct lbs_private
+ * @skb: A pointer to skb which includes the received packet
+ * returns: 0 or -1
+ */
+int lbs_process_rxed_packet(struct lbs_private *priv, struct sk_buff *skb)
+{
+ int ret = 0;
+ struct net_device *dev = priv->dev;
+ struct rxpackethdr *p_rx_pkt;
+ struct rxpd *p_rx_pd;
+ int hdrchop;
+ struct ethhdr *p_ethhdr;
+
+ BUG_ON(!skb);
+
+ skb->ip_summed = CHECKSUM_NONE;
+
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) {
+ ret = process_rxed_802_11_packet(priv, skb);
+ goto done;
+ }
+
+ p_rx_pd = (struct rxpd *) skb->data;
+ p_rx_pkt = (struct rxpackethdr *) ((u8 *)p_rx_pd +
+ le32_to_cpu(p_rx_pd->pkt_ptr));
+
+ dev = lbs_mesh_set_dev(priv, dev, p_rx_pd);
+
+ lbs_deb_hex(LBS_DEB_RX, "RX Data: Before chop rxpd", skb->data,
+ min_t(unsigned int, skb->len, 100));
+
+ if (skb->len < (ETH_HLEN + 8 + sizeof(struct rxpd))) {
+ lbs_deb_rx("rx err: frame received with bad length\n");
+ dev->stats.rx_length_errors++;
+ ret = -EINVAL;
+ dev_kfree_skb(skb);
+ goto done;
+ }
+
+ lbs_deb_rx("rx data: skb->len - pkt_ptr = %d-%zd = %zd\n",
+ skb->len, (size_t)le32_to_cpu(p_rx_pd->pkt_ptr),
+ skb->len - (size_t)le32_to_cpu(p_rx_pd->pkt_ptr));
+
+ lbs_deb_hex(LBS_DEB_RX, "RX Data: Dest", p_rx_pkt->eth803_hdr.dest_addr,
+ sizeof(p_rx_pkt->eth803_hdr.dest_addr));
+ lbs_deb_hex(LBS_DEB_RX, "RX Data: Src", p_rx_pkt->eth803_hdr.src_addr,
+ sizeof(p_rx_pkt->eth803_hdr.src_addr));
+
+ if (memcmp(&p_rx_pkt->rfc1042_hdr,
+ rfc1042_header, sizeof(rfc1042_header)) == 0) {
+ /*
+ * Replace the 803 header and rfc1042 header (llc/snap) with an
+ * EthernetII header, keep the src/dst and snap_type (ethertype)
+ *
+ * The firmware only passes up SNAP frames converting
+ * all RX Data from 802.11 to 802.2/LLC/SNAP frames.
+ *
+ * To create the Ethernet II, just move the src, dst address right
+ * before the snap_type.
+ */
+ p_ethhdr = (struct ethhdr *)
+ ((u8 *) &p_rx_pkt->eth803_hdr
+ + sizeof(p_rx_pkt->eth803_hdr) + sizeof(p_rx_pkt->rfc1042_hdr)
+ - sizeof(p_rx_pkt->eth803_hdr.dest_addr)
+ - sizeof(p_rx_pkt->eth803_hdr.src_addr)
+ - sizeof(p_rx_pkt->rfc1042_hdr.snap_type));
+
+ memcpy(p_ethhdr->h_source, p_rx_pkt->eth803_hdr.src_addr,
+ sizeof(p_ethhdr->h_source));
+ memcpy(p_ethhdr->h_dest, p_rx_pkt->eth803_hdr.dest_addr,
+ sizeof(p_ethhdr->h_dest));
+
+ /* Chop off the rxpd + the excess memory from the 802.2/llc/snap header
+ * that was removed
+ */
+ hdrchop = (u8 *)p_ethhdr - (u8 *)p_rx_pd;
+ } else {
+ lbs_deb_hex(LBS_DEB_RX, "RX Data: LLC/SNAP",
+ (u8 *) &p_rx_pkt->rfc1042_hdr,
+ sizeof(p_rx_pkt->rfc1042_hdr));
+
+ /* Chop off the rxpd */
+ hdrchop = (u8 *)&p_rx_pkt->eth803_hdr - (u8 *)p_rx_pd;
+ }
+
+ /* Chop off the leading header bytes so the skb points to the start of
+ * either the reconstructed EthII frame or the 802.2/llc/snap frame
+ */
+ skb_pull(skb, hdrchop);
+
+ priv->cur_rate = lbs_fw_index_to_data_rate(p_rx_pd->rx_rate);
+
+ lbs_deb_rx("rx data: size of actual packet %d\n", skb->len);
+ dev->stats.rx_bytes += skb->len;
+ dev->stats.rx_packets++;
+
+ skb->protocol = eth_type_trans(skb, dev);
+ netif_rx_any_context(skb);
+
+ ret = 0;
+done:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lbs_process_rxed_packet);
+
+/**
+ * convert_mv_rate_to_radiotap - converts Tx/Rx rates from Marvell WLAN format
+ * (see Table 2 in Section 3.1) to IEEE80211_RADIOTAP_RATE units (500 Kb/s)
+ *
+ * @rate: Input rate
+ * returns: Output Rate (0 if invalid)
+ */
+static u8 convert_mv_rate_to_radiotap(u8 rate)
+{
+ switch (rate) {
+ case 0: /* 1 Mbps */
+ return 2;
+ case 1: /* 2 Mbps */
+ return 4;
+ case 2: /* 5.5 Mbps */
+ return 11;
+ case 3: /* 11 Mbps */
+ return 22;
+ /* case 4: reserved */
+ case 5: /* 6 Mbps */
+ return 12;
+ case 6: /* 9 Mbps */
+ return 18;
+ case 7: /* 12 Mbps */
+ return 24;
+ case 8: /* 18 Mbps */
+ return 36;
+ case 9: /* 24 Mbps */
+ return 48;
+ case 10: /* 36 Mbps */
+ return 72;
+ case 11: /* 48 Mbps */
+ return 96;
+ case 12: /* 54 Mbps */
+ return 108;
+ }
+ pr_alert("Invalid Marvell WLAN rate %i\n", rate);
+ return 0;
+}
+
+/**
+ * process_rxed_802_11_packet - processes a received 802.11 packet and forwards
+ * it to kernel/upper layer
+ *
+ * @priv: A pointer to &struct lbs_private
+ * @skb: A pointer to skb which includes the received packet
+ * returns: 0 or -1
+ */
+static int process_rxed_802_11_packet(struct lbs_private *priv,
+ struct sk_buff *skb)
+{
+ int ret = 0;
+ struct net_device *dev = priv->dev;
+ struct rx80211packethdr *p_rx_pkt;
+ struct rxpd *prxpd;
+ struct rx_radiotap_hdr radiotap_hdr;
+ struct rx_radiotap_hdr *pradiotap_hdr;
+
+ p_rx_pkt = (struct rx80211packethdr *) skb->data;
+ prxpd = &p_rx_pkt->rx_pd;
+
+ /* lbs_deb_hex(LBS_DEB_RX, "RX Data: Before chop rxpd", skb->data, min(skb->len, 100)); */
+
+ if (skb->len < (ETH_HLEN + 8 + sizeof(struct rxpd))) {
+ lbs_deb_rx("rx err: frame received with bad length\n");
+ dev->stats.rx_length_errors++;
+ ret = -EINVAL;
+ kfree_skb(skb);
+ goto done;
+ }
+
+ lbs_deb_rx("rx data: skb->len-sizeof(RxPd) = %d-%zd = %zd\n",
+ skb->len, sizeof(struct rxpd), skb->len - sizeof(struct rxpd));
+
+ /* create the exported radio header */
+
+ /* radiotap header */
+ memset(&radiotap_hdr, 0, sizeof(radiotap_hdr));
+ /* XXX must check radiotap_hdr.hdr.it_pad for pad */
+ radiotap_hdr.hdr.it_len = cpu_to_le16 (sizeof(struct rx_radiotap_hdr));
+ radiotap_hdr.hdr.it_present = cpu_to_le32 (RX_RADIOTAP_PRESENT);
+ radiotap_hdr.rate = convert_mv_rate_to_radiotap(prxpd->rx_rate);
+ /* XXX must check no carryout */
+ radiotap_hdr.antsignal = prxpd->snr + prxpd->nf;
+
+ /* chop the rxpd */
+ skb_pull(skb, sizeof(struct rxpd));
+
+ /* add space for the new radio header */
+ if ((skb_headroom(skb) < sizeof(struct rx_radiotap_hdr)) &&
+ pskb_expand_head(skb, sizeof(struct rx_radiotap_hdr), 0, GFP_ATOMIC)) {
+ netdev_alert(dev, "%s: couldn't pskb_expand_head\n", __func__);
+ ret = -ENOMEM;
+ kfree_skb(skb);
+ goto done;
+ }
+
+ pradiotap_hdr = skb_push(skb, sizeof(struct rx_radiotap_hdr));
+ memcpy(pradiotap_hdr, &radiotap_hdr, sizeof(struct rx_radiotap_hdr));
+
+ priv->cur_rate = lbs_fw_index_to_data_rate(prxpd->rx_rate);
+
+ lbs_deb_rx("rx data: size of actual packet %d\n", skb->len);
+ dev->stats.rx_bytes += skb->len;
+ dev->stats.rx_packets++;
+
+ skb->protocol = eth_type_trans(skb, priv->dev);
+ netif_rx_any_context(skb);
+
+ ret = 0;
+
+done:
+ return ret;
+}
diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c
new file mode 100644
index 000000000..aeb481740
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/tx.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains the handling of TX in wlan driver.
+ */
+#include <linux/hardirq.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/sched.h>
+#include <linux/export.h>
+#include <net/cfg80211.h>
+
+#include "host.h"
+#include "radiotap.h"
+#include "decl.h"
+#include "defs.h"
+#include "dev.h"
+#include "mesh.h"
+
+/**
+ * convert_radiotap_rate_to_mv - converts Tx/Rx rates from IEEE80211_RADIOTAP_RATE
+ * units (500 Kb/s) into Marvell WLAN format (see Table 8 in Section 3.2.1)
+ *
+ * @rate: Input rate
+ * returns: Output Rate (0 if invalid)
+ */
+static u32 convert_radiotap_rate_to_mv(u8 rate)
+{
+ switch (rate) {
+ case 2: /* 1 Mbps */
+ return 0 | (1 << 4);
+ case 4: /* 2 Mbps */
+ return 1 | (1 << 4);
+ case 11: /* 5.5 Mbps */
+ return 2 | (1 << 4);
+ case 22: /* 11 Mbps */
+ return 3 | (1 << 4);
+ case 12: /* 6 Mbps */
+ return 4 | (1 << 4);
+ case 18: /* 9 Mbps */
+ return 5 | (1 << 4);
+ case 24: /* 12 Mbps */
+ return 6 | (1 << 4);
+ case 36: /* 18 Mbps */
+ return 7 | (1 << 4);
+ case 48: /* 24 Mbps */
+ return 8 | (1 << 4);
+ case 72: /* 36 Mbps */
+ return 9 | (1 << 4);
+ case 96: /* 48 Mbps */
+ return 10 | (1 << 4);
+ case 108: /* 54 Mbps */
+ return 11 | (1 << 4);
+ }
+ return 0;
+}
+
+/**
+ * lbs_hard_start_xmit - checks the conditions and sends packet to IF
+ * layer if everything is ok
+ *
+ * @skb: A pointer to skb which includes TX packet
+ * @dev: A pointer to the &struct net_device
+ * returns: 0 or -1
+ */
+netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ unsigned long flags;
+ struct lbs_private *priv = dev->ml_priv;
+ struct txpd *txpd;
+ char *p802x_hdr;
+ uint16_t pkt_len;
+ netdev_tx_t ret = NETDEV_TX_OK;
+
+ /* We need to protect against the queues being restarted before
+ we get round to stopping them */
+ spin_lock_irqsave(&priv->driver_lock, flags);
+
+ if (priv->surpriseremoved)
+ goto free;
+
+ if (!skb->len || (skb->len > MRVDRV_ETH_TX_PACKET_BUFFER_SIZE)) {
+ lbs_deb_tx("tx err: skb length %d 0 or > %zd\n",
+ skb->len, MRVDRV_ETH_TX_PACKET_BUFFER_SIZE);
+ /* We'll never manage to send this one; drop it and return 'OK' */
+
+ dev->stats.tx_dropped++;
+ dev->stats.tx_errors++;
+ goto free;
+ }
+
+
+ netif_stop_queue(priv->dev);
+ if (priv->mesh_dev)
+ netif_stop_queue(priv->mesh_dev);
+
+ if (priv->tx_pending_len) {
+ /* This can happen if packets come in on the mesh and eth
+ device simultaneously -- there's no mutual exclusion on
+ hard_start_xmit() calls between devices. */
+ lbs_deb_tx("Packet on %s while busy\n", dev->name);
+ ret = NETDEV_TX_BUSY;
+ goto unlock;
+ }
+
+ priv->tx_pending_len = -1;
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+
+ lbs_deb_hex(LBS_DEB_TX, "TX Data", skb->data, min_t(unsigned int, skb->len, 100));
+
+ txpd = (void *)priv->tx_pending_buf;
+ memset(txpd, 0, sizeof(struct txpd));
+
+ p802x_hdr = skb->data;
+ pkt_len = skb->len;
+
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) {
+ struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data;
+
+ /* set txpd fields from the radiotap header */
+ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate));
+
+ /* skip the radiotap header */
+ p802x_hdr += sizeof(*rtap_hdr);
+ pkt_len -= sizeof(*rtap_hdr);
+
+ /* copy destination address from 802.11 header */
+ memcpy(txpd->tx_dest_addr_high, p802x_hdr + 4, ETH_ALEN);
+ } else {
+ /* copy destination address from 802.3 header */
+ memcpy(txpd->tx_dest_addr_high, p802x_hdr, ETH_ALEN);
+ }
+
+ txpd->tx_packet_length = cpu_to_le16(pkt_len);
+ txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd));
+
+ lbs_mesh_set_txpd(priv, dev, txpd);
+
+ lbs_deb_hex(LBS_DEB_TX, "txpd", (u8 *) &txpd, sizeof(struct txpd));
+
+ lbs_deb_hex(LBS_DEB_TX, "Tx Data", (u8 *) p802x_hdr, le16_to_cpu(txpd->tx_packet_length));
+
+ memcpy(&txpd[1], p802x_hdr, le16_to_cpu(txpd->tx_packet_length));
+
+ spin_lock_irqsave(&priv->driver_lock, flags);
+ priv->tx_pending_len = pkt_len + sizeof(struct txpd);
+
+ lbs_deb_tx("%s lined up packet\n", __func__);
+
+ dev->stats.tx_packets++;
+ dev->stats.tx_bytes += skb->len;
+
+ if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) {
+ /* Keep the skb to echo it back once Tx feedback is
+ received from FW */
+ skb_orphan(skb);
+
+ /* Keep the skb around for when we get feedback */
+ priv->currenttxskb = skb;
+ } else {
+ free:
+ dev_kfree_skb_any(skb);
+ }
+
+ unlock:
+ spin_unlock_irqrestore(&priv->driver_lock, flags);
+ wake_up(&priv->waitq);
+
+ return ret;
+}
+
+/**
+ * lbs_send_tx_feedback - sends to the host the last transmitted packet,
+ * filling the radiotap headers with transmission information.
+ *
+ * @priv: A pointer to &struct lbs_private structure
+ * @try_count: A 32-bit value containing transmission retry status.
+ *
+ * returns: void
+ */
+void lbs_send_tx_feedback(struct lbs_private *priv, u32 try_count)
+{
+ struct tx_radiotap_hdr *radiotap_hdr;
+
+ if (priv->wdev->iftype != NL80211_IFTYPE_MONITOR ||
+ priv->currenttxskb == NULL)
+ return;
+
+ radiotap_hdr = (struct tx_radiotap_hdr *)priv->currenttxskb->data;
+
+ radiotap_hdr->data_retries = try_count ?
+ (1 + priv->txretrycount - try_count) : 0;
+
+ priv->currenttxskb->protocol = eth_type_trans(priv->currenttxskb,
+ priv->dev);
+ netif_rx(priv->currenttxskb);
+
+ priv->currenttxskb = NULL;
+
+ if (priv->connect_status == LBS_CONNECTED)
+ netif_wake_queue(priv->dev);
+
+ if (priv->mesh_dev && netif_running(priv->mesh_dev))
+ netif_wake_queue(priv->mesh_dev);
+}
+EXPORT_SYMBOL_GPL(lbs_send_tx_feedback);
diff --git a/drivers/net/wireless/marvell/libertas/types.h b/drivers/net/wireless/marvell/libertas/types.h
new file mode 100644
index 000000000..cd4ceb6f8
--- /dev/null
+++ b/drivers/net/wireless/marvell/libertas/types.h
@@ -0,0 +1,269 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header file contains definition for global types
+ */
+#ifndef _LBS_TYPES_H_
+#define _LBS_TYPES_H_
+
+#include <linux/if_ether.h>
+#include <linux/ieee80211.h>
+#include <asm/byteorder.h>
+
+struct ieee_ie_header {
+ u8 id;
+ u8 len;
+} __packed;
+
+struct ieee_ie_cf_param_set {
+ struct ieee_ie_header header;
+
+ u8 cfpcnt;
+ u8 cfpperiod;
+ __le16 cfpmaxduration;
+ __le16 cfpdurationremaining;
+} __packed;
+
+
+struct ieee_ie_ibss_param_set {
+ struct ieee_ie_header header;
+
+ __le16 atimwindow;
+} __packed;
+
+union ieee_ss_param_set {
+ struct ieee_ie_cf_param_set cf;
+ struct ieee_ie_ibss_param_set ibss;
+} __packed;
+
+struct ieee_ie_fh_param_set {
+ struct ieee_ie_header header;
+
+ __le16 dwelltime;
+ u8 hopset;
+ u8 hoppattern;
+ u8 hopindex;
+} __packed;
+
+struct ieee_ie_ds_param_set {
+ struct ieee_ie_header header;
+
+ u8 channel;
+} __packed;
+
+union ieee_phy_param_set {
+ struct ieee_ie_fh_param_set fh;
+ struct ieee_ie_ds_param_set ds;
+} __packed;
+
+/* TLV type ID definition */
+#define PROPRIETARY_TLV_BASE_ID 0x0100
+
+/* Terminating TLV type */
+#define MRVL_TERMINATE_TLV_ID 0xffff
+
+#define TLV_TYPE_SSID 0x0000
+#define TLV_TYPE_RATES 0x0001
+#define TLV_TYPE_PHY_FH 0x0002
+#define TLV_TYPE_PHY_DS 0x0003
+#define TLV_TYPE_CF 0x0004
+#define TLV_TYPE_IBSS 0x0006
+
+#define TLV_TYPE_DOMAIN 0x0007
+
+#define TLV_TYPE_POWER_CAPABILITY 0x0021
+
+#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0)
+#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1)
+#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2)
+#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4)
+#define TLV_TYPE_SNR_LOW (PROPRIETARY_TLV_BASE_ID + 5)
+#define TLV_TYPE_FAILCOUNT (PROPRIETARY_TLV_BASE_ID + 6)
+#define TLV_TYPE_BCNMISS (PROPRIETARY_TLV_BASE_ID + 7)
+#define TLV_TYPE_LED_GPIO (PROPRIETARY_TLV_BASE_ID + 8)
+#define TLV_TYPE_LEDBEHAVIOR (PROPRIETARY_TLV_BASE_ID + 9)
+#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10)
+#define TLV_TYPE_REASSOCAP (PROPRIETARY_TLV_BASE_ID + 11)
+#define TLV_TYPE_POWER_TBL_2_4GHZ (PROPRIETARY_TLV_BASE_ID + 12)
+#define TLV_TYPE_POWER_TBL_5GHZ (PROPRIETARY_TLV_BASE_ID + 13)
+#define TLV_TYPE_BCASTPROBE (PROPRIETARY_TLV_BASE_ID + 14)
+#define TLV_TYPE_NUMSSID_PROBE (PROPRIETARY_TLV_BASE_ID + 15)
+#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16)
+#define TLV_TYPE_CRYPTO_DATA (PROPRIETARY_TLV_BASE_ID + 17)
+#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18)
+#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19)
+#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22)
+#define TLV_TYPE_SNR_HIGH (PROPRIETARY_TLV_BASE_ID + 23)
+#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31)
+#define TLV_TYPE_MESH_ID (PROPRIETARY_TLV_BASE_ID + 37)
+#define TLV_TYPE_OLD_MESH_ID (PROPRIETARY_TLV_BASE_ID + 291)
+
+/* TLV related data structures */
+struct mrvl_ie_header {
+ __le16 type;
+ __le16 len;
+} __packed;
+
+struct mrvl_ie_data {
+ struct mrvl_ie_header header;
+ u8 Data[1];
+} __packed;
+
+struct mrvl_ie_rates_param_set {
+ struct mrvl_ie_header header;
+ u8 rates[1];
+} __packed;
+
+struct mrvl_ie_ssid_param_set {
+ struct mrvl_ie_header header;
+ u8 ssid[1];
+} __packed;
+
+struct mrvl_ie_wildcard_ssid_param_set {
+ struct mrvl_ie_header header;
+ u8 MaxSsidlength;
+ u8 ssid[1];
+} __packed;
+
+struct chanscanmode {
+#ifdef __BIG_ENDIAN_BITFIELD
+ u8 reserved_2_7:6;
+ u8 disablechanfilt:1;
+ u8 passivescan:1;
+#else
+ u8 passivescan:1;
+ u8 disablechanfilt:1;
+ u8 reserved_2_7:6;
+#endif
+} __packed;
+
+struct chanscanparamset {
+ u8 radiotype;
+ u8 channumber;
+ struct chanscanmode chanscanmode;
+ __le16 minscantime;
+ __le16 maxscantime;
+} __packed;
+
+struct mrvl_ie_chanlist_param_set {
+ struct mrvl_ie_header header;
+ struct chanscanparamset chanscanparam[1];
+} __packed;
+
+struct mrvl_ie_cf_param_set {
+ struct mrvl_ie_header header;
+ u8 cfpcnt;
+ u8 cfpperiod;
+ __le16 cfpmaxduration;
+ __le16 cfpdurationremaining;
+} __packed;
+
+struct mrvl_ie_ds_param_set {
+ struct mrvl_ie_header header;
+ u8 channel;
+} __packed;
+
+struct mrvl_ie_rsn_param_set {
+ struct mrvl_ie_header header;
+ u8 rsnie[1];
+} __packed;
+
+struct mrvl_ie_tsf_timestamp {
+ struct mrvl_ie_header header;
+ __le64 tsftable[1];
+} __packed;
+
+/* v9 and later firmware only */
+struct mrvl_ie_auth_type {
+ struct mrvl_ie_header header;
+ __le16 auth;
+} __packed;
+
+/* Local Power capability */
+struct mrvl_ie_power_capability {
+ struct mrvl_ie_header header;
+ s8 minpower;
+ s8 maxpower;
+} __packed;
+
+/* used in CMD_802_11_SUBSCRIBE_EVENT for SNR, RSSI and Failure */
+struct mrvl_ie_thresholds {
+ struct mrvl_ie_header header;
+ u8 value;
+ u8 freq;
+} __packed;
+
+struct mrvl_ie_beacons_missed {
+ struct mrvl_ie_header header;
+ u8 beaconmissed;
+ u8 reserved;
+} __packed;
+
+struct mrvl_ie_num_probes {
+ struct mrvl_ie_header header;
+ __le16 numprobes;
+} __packed;
+
+struct mrvl_ie_bcast_probe {
+ struct mrvl_ie_header header;
+ __le16 bcastprobe;
+} __packed;
+
+struct mrvl_ie_num_ssid_probe {
+ struct mrvl_ie_header header;
+ __le16 numssidprobe;
+} __packed;
+
+struct led_pin {
+ u8 led;
+ u8 pin;
+} __packed;
+
+struct mrvl_ie_ledgpio {
+ struct mrvl_ie_header header;
+ struct led_pin ledpin[1];
+} __packed;
+
+struct led_bhv {
+ uint8_t firmwarestate;
+ uint8_t led;
+ uint8_t ledstate;
+ uint8_t ledarg;
+} __packed;
+
+
+struct mrvl_ie_ledbhv {
+ struct mrvl_ie_header header;
+ struct led_bhv ledbhv[1];
+} __packed;
+
+/*
+ * Meant to be packed as the value member of a struct ieee80211_info_element.
+ * Note that the len member of the ieee80211_info_element varies depending on
+ * the mesh_id_len
+ */
+struct mrvl_meshie_val {
+ uint8_t oui[3];
+ uint8_t type;
+ uint8_t subtype;
+ uint8_t version;
+ uint8_t active_protocol_id;
+ uint8_t active_metric_id;
+ uint8_t mesh_capability;
+ uint8_t mesh_id_len;
+ uint8_t mesh_id[IEEE80211_MAX_SSID_LEN];
+} __packed;
+
+struct mrvl_meshie {
+ u8 id, len;
+ struct mrvl_meshie_val val;
+} __packed;
+
+struct mrvl_mesh_defaults {
+ __le32 bootflag;
+ uint8_t boottime;
+ uint8_t reserved;
+ __le16 channel;
+ struct mrvl_meshie meshie;
+} __packed;
+
+#endif