summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/intersil/p54
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/intersil/p54')
-rw-r--r--drivers/net/wireless/intersil/p54/Kconfig75
-rw-r--r--drivers/net/wireless/intersil/p54/Makefile8
-rw-r--r--drivers/net/wireless/intersil/p54/eeprom.c982
-rw-r--r--drivers/net/wireless/intersil/p54/eeprom.h245
-rw-r--r--drivers/net/wireless/intersil/p54/fwio.c764
-rw-r--r--drivers/net/wireless/intersil/p54/led.c161
-rw-r--r--drivers/net/wireless/intersil/p54/lmac.h562
-rw-r--r--drivers/net/wireless/intersil/p54/main.c867
-rw-r--r--drivers/net/wireless/intersil/p54/p54.h281
-rw-r--r--drivers/net/wireless/intersil/p54/p54pci.c703
-rw-r--r--drivers/net/wireless/intersil/p54/p54pci.h112
-rw-r--r--drivers/net/wireless/intersil/p54/p54spi.c720
-rw-r--r--drivers/net/wireless/intersil/p54/p54spi.h125
-rw-r--r--drivers/net/wireless/intersil/p54/p54spi_eeprom.h679
-rw-r--r--drivers/net/wireless/intersil/p54/p54usb.c1151
-rw-r--r--drivers/net/wireless/intersil/p54/p54usb.h162
-rw-r--r--drivers/net/wireless/intersil/p54/txrx.c940
17 files changed, 8537 insertions, 0 deletions
diff --git a/drivers/net/wireless/intersil/p54/Kconfig b/drivers/net/wireless/intersil/p54/Kconfig
new file mode 100644
index 0000000..a41aca2
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/Kconfig
@@ -0,0 +1,75 @@
+config P54_COMMON
+ tristate "Softmac Prism54 support"
+ depends on m
+ depends on MAC80211
+ depends on FW_LOADER
+ depends on CRC_CCITT
+ ---help---
+ This is common code for isl38xx/stlc45xx based modules.
+ This module does nothing by itself - the USB/PCI/SPI front-ends
+ also need to be enabled in order to support any devices.
+
+ These devices require softmac firmware which can be found at
+ <http://wireless.kernel.org/en/users/Drivers/p54>
+
+ If you choose to build a module, it'll be called p54common.
+
+config P54_USB
+ tristate "Prism54 USB support"
+ depends on m
+ depends on P54_COMMON && USB
+ depends on CRC32
+ ---help---
+ This driver is for USB isl38xx based wireless cards.
+
+ These devices require softmac firmware which can be found at
+ <http://wireless.kernel.org/en/users/Drivers/p54>
+
+ If you choose to build a module, it'll be called p54usb.
+
+config P54_PCI
+ tristate "Prism54 PCI support"
+ depends on m
+ depends on P54_COMMON && PCI
+ ---help---
+ This driver is for PCI isl38xx based wireless cards.
+ This driver supports most devices that are supported by the
+ fullmac prism54 driver plus many devices which are not
+ supported by the fullmac driver/firmware.
+
+ This driver requires softmac firmware which can be found at
+ <http://wireless.kernel.org/en/users/Drivers/p54>
+
+ If you choose to build a module, it'll be called p54pci.
+
+config P54_SPI
+ tristate "Prism54 SPI (stlc45xx) support"
+ depends on m
+ depends on P54_COMMON && SPI_MASTER
+ ---help---
+ This driver is for stlc4550 or stlc4560 based wireless chips
+ such as Nokia's N800/N810 Portable Internet Tablet.
+
+ If you choose to build a module, it'll be called p54spi.
+
+config P54_SPI_DEFAULT_EEPROM
+ bool "Include fallback EEPROM blob"
+ depends on P54_SPI
+ default n
+ ---help---
+ Unlike the PCI or USB devices, the SPI variants don't have
+ a dedicated EEPROM chip to store all device specific values
+ for calibration, country and interface settings.
+
+ The driver will try to load the image "3826.eeprom", if the
+ file is put at the right place. (usually /lib/firmware.)
+
+ Only if this request fails, this option will provide a
+ backup set of generic values to get the device working.
+
+ Enabling this option adds about 4k to p54spi.
+
+config P54_LEDS
+ bool
+ depends on P54_COMMON && MAC80211_LEDS && (LEDS_CLASS = y || LEDS_CLASS = P54_COMMON)
+ default y
diff --git a/drivers/net/wireless/intersil/p54/Makefile b/drivers/net/wireless/intersil/p54/Makefile
new file mode 100644
index 0000000..042f99d
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+p54common-objs := eeprom.o fwio.o txrx.o main.o
+p54common-$(CPTCFG_P54_LEDS) += led.o
+
+obj-$(CPTCFG_P54_COMMON) += p54common.o
+obj-$(CPTCFG_P54_USB) += p54usb.o
+obj-$(CPTCFG_P54_PCI) += p54pci.o
+obj-$(CPTCFG_P54_SPI) += p54spi.o
diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c
new file mode 100644
index 0000000..d4c73d3
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/eeprom.c
@@ -0,0 +1,982 @@
+/*
+ * EEPROM parser code for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ * Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ * - stlc45xx driver
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <linux/sort.h>
+#include <linux/slab.h>
+
+#include <net/mac80211.h>
+#include <linux/crc-ccitt.h>
+#include <linux/export.h>
+
+#include "p54.h"
+#include "eeprom.h"
+#include "lmac.h"
+
+static struct ieee80211_rate p54_bgrates[] = {
+ { .bitrate = 10, .hw_value = 0, },
+ { .bitrate = 20, .hw_value = 1, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
+ { .bitrate = 55, .hw_value = 2, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
+ { .bitrate = 110, .hw_value = 3, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
+ { .bitrate = 60, .hw_value = 4, },
+ { .bitrate = 90, .hw_value = 5, },
+ { .bitrate = 120, .hw_value = 6, },
+ { .bitrate = 180, .hw_value = 7, },
+ { .bitrate = 240, .hw_value = 8, },
+ { .bitrate = 360, .hw_value = 9, },
+ { .bitrate = 480, .hw_value = 10, },
+ { .bitrate = 540, .hw_value = 11, },
+};
+
+static struct ieee80211_rate p54_arates[] = {
+ { .bitrate = 60, .hw_value = 4, },
+ { .bitrate = 90, .hw_value = 5, },
+ { .bitrate = 120, .hw_value = 6, },
+ { .bitrate = 180, .hw_value = 7, },
+ { .bitrate = 240, .hw_value = 8, },
+ { .bitrate = 360, .hw_value = 9, },
+ { .bitrate = 480, .hw_value = 10, },
+ { .bitrate = 540, .hw_value = 11, },
+};
+
+static struct p54_rssi_db_entry p54_rssi_default = {
+ /*
+ * The defaults are taken from usb-logs of the
+ * vendor driver. So, they should be safe to
+ * use in case we can't get a match from the
+ * rssi <-> dBm conversion database.
+ */
+ .mul = 130,
+ .add = -398,
+};
+
+#define CHAN_HAS_CAL BIT(0)
+#define CHAN_HAS_LIMIT BIT(1)
+#define CHAN_HAS_CURVE BIT(2)
+#define CHAN_HAS_ALL (CHAN_HAS_CAL | CHAN_HAS_LIMIT | CHAN_HAS_CURVE)
+
+struct p54_channel_entry {
+ u16 freq;
+ u16 data;
+ int index;
+ int max_power;
+ enum nl80211_band band;
+};
+
+struct p54_channel_list {
+ struct p54_channel_entry *channels;
+ size_t entries;
+ size_t max_entries;
+ size_t band_channel_num[NUM_NL80211_BANDS];
+};
+
+static int p54_get_band_from_freq(u16 freq)
+{
+ /* FIXME: sync these values with the 802.11 spec */
+
+ if ((freq >= 2412) && (freq <= 2484))
+ return NL80211_BAND_2GHZ;
+
+ if ((freq >= 4920) && (freq <= 5825))
+ return NL80211_BAND_5GHZ;
+
+ return -1;
+}
+
+static int same_band(u16 freq, u16 freq2)
+{
+ return p54_get_band_from_freq(freq) == p54_get_band_from_freq(freq2);
+}
+
+static int p54_compare_channels(const void *_a,
+ const void *_b)
+{
+ const struct p54_channel_entry *a = _a;
+ const struct p54_channel_entry *b = _b;
+
+ return a->freq - b->freq;
+}
+
+static int p54_compare_rssichan(const void *_a,
+ const void *_b)
+{
+ const struct p54_rssi_db_entry *a = _a;
+ const struct p54_rssi_db_entry *b = _b;
+
+ return a->freq - b->freq;
+}
+
+static int p54_fill_band_bitrates(struct ieee80211_hw *dev,
+ struct ieee80211_supported_band *band_entry,
+ enum nl80211_band band)
+{
+ /* TODO: generate rate array dynamically */
+
+ switch (band) {
+ case NL80211_BAND_2GHZ:
+ band_entry->bitrates = p54_bgrates;
+ band_entry->n_bitrates = ARRAY_SIZE(p54_bgrates);
+ break;
+ case NL80211_BAND_5GHZ:
+ band_entry->bitrates = p54_arates;
+ band_entry->n_bitrates = ARRAY_SIZE(p54_arates);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int p54_generate_band(struct ieee80211_hw *dev,
+ struct p54_channel_list *list,
+ unsigned int *chan_num,
+ enum nl80211_band band)
+{
+ struct p54_common *priv = dev->priv;
+ struct ieee80211_supported_band *tmp, *old;
+ unsigned int i, j;
+ int ret = -ENOMEM;
+
+ if ((!list->entries) || (!list->band_channel_num[band]))
+ return -EINVAL;
+
+ tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
+ if (!tmp)
+ goto err_out;
+
+ tmp->channels = kzalloc(sizeof(struct ieee80211_channel) *
+ list->band_channel_num[band], GFP_KERNEL);
+ if (!tmp->channels)
+ goto err_out;
+
+ ret = p54_fill_band_bitrates(dev, tmp, band);
+ if (ret)
+ goto err_out;
+
+ for (i = 0, j = 0; (j < list->band_channel_num[band]) &&
+ (i < list->entries); i++) {
+ struct p54_channel_entry *chan = &list->channels[i];
+ struct ieee80211_channel *dest = &tmp->channels[j];
+
+ if (chan->band != band)
+ continue;
+
+ if (chan->data != CHAN_HAS_ALL) {
+ wiphy_err(dev->wiphy, "%s%s%s is/are missing for "
+ "channel:%d [%d MHz].\n",
+ (chan->data & CHAN_HAS_CAL ? "" :
+ " [iqauto calibration data]"),
+ (chan->data & CHAN_HAS_LIMIT ? "" :
+ " [output power limits]"),
+ (chan->data & CHAN_HAS_CURVE ? "" :
+ " [curve data]"),
+ chan->index, chan->freq);
+ continue;
+ }
+
+ dest->band = chan->band;
+ dest->center_freq = chan->freq;
+ dest->max_power = chan->max_power;
+ priv->survey[*chan_num].channel = &tmp->channels[j];
+ priv->survey[*chan_num].filled = SURVEY_INFO_NOISE_DBM |
+ SURVEY_INFO_TIME |
+ SURVEY_INFO_TIME_BUSY |
+ SURVEY_INFO_TIME_TX;
+ dest->hw_value = (*chan_num);
+ j++;
+ (*chan_num)++;
+ }
+
+ if (j == 0) {
+ wiphy_err(dev->wiphy, "Disabling totally damaged %d GHz band\n",
+ (band == NL80211_BAND_2GHZ) ? 2 : 5);
+
+ ret = -ENODATA;
+ goto err_out;
+ }
+
+ tmp->n_channels = j;
+ old = priv->band_table[band];
+ priv->band_table[band] = tmp;
+ if (old) {
+ kfree(old->channels);
+ kfree(old);
+ }
+
+ return 0;
+
+err_out:
+ if (tmp) {
+ kfree(tmp->channels);
+ kfree(tmp);
+ }
+
+ return ret;
+}
+
+static struct p54_channel_entry *p54_update_channel_param(struct p54_channel_list *list,
+ u16 freq, u16 data)
+{
+ int i;
+ struct p54_channel_entry *entry = NULL;
+
+ /*
+ * usually all lists in the eeprom are mostly sorted.
+ * so it's very likely that the entry we are looking for
+ * is right at the end of the list
+ */
+ for (i = list->entries; i >= 0; i--) {
+ if (freq == list->channels[i].freq) {
+ entry = &list->channels[i];
+ break;
+ }
+ }
+
+ if ((i < 0) && (list->entries < list->max_entries)) {
+ /* entry does not exist yet. Initialize a new one. */
+ int band = p54_get_band_from_freq(freq);
+
+ /*
+ * filter out frequencies which don't belong into
+ * any supported band.
+ */
+ if (band >= 0) {
+ i = list->entries++;
+ list->band_channel_num[band]++;
+
+ entry = &list->channels[i];
+ entry->freq = freq;
+ entry->band = band;
+ entry->index = ieee80211_frequency_to_channel(freq);
+ entry->max_power = 0;
+ entry->data = 0;
+ }
+ }
+
+ if (entry)
+ entry->data |= data;
+
+ return entry;
+}
+
+static int p54_get_maxpower(struct p54_common *priv, void *data)
+{
+ switch (priv->rxhw & PDR_SYNTH_FRONTEND_MASK) {
+ case PDR_SYNTH_FRONTEND_LONGBOW: {
+ struct pda_channel_output_limit_longbow *pda = data;
+ int j;
+ u16 rawpower = 0;
+ pda = data;
+ for (j = 0; j < ARRAY_SIZE(pda->point); j++) {
+ struct pda_channel_output_limit_point_longbow *point =
+ &pda->point[j];
+ rawpower = max_t(u16,
+ rawpower, le16_to_cpu(point->val_qpsk));
+ rawpower = max_t(u16,
+ rawpower, le16_to_cpu(point->val_bpsk));
+ rawpower = max_t(u16,
+ rawpower, le16_to_cpu(point->val_16qam));
+ rawpower = max_t(u16,
+ rawpower, le16_to_cpu(point->val_64qam));
+ }
+ /* longbow seems to use 1/16 dBm units */
+ return rawpower / 16;
+ }
+
+ case PDR_SYNTH_FRONTEND_DUETTE3:
+ case PDR_SYNTH_FRONTEND_DUETTE2:
+ case PDR_SYNTH_FRONTEND_FRISBEE:
+ case PDR_SYNTH_FRONTEND_XBOW: {
+ struct pda_channel_output_limit *pda = data;
+ u8 rawpower = 0;
+ rawpower = max(rawpower, pda->val_qpsk);
+ rawpower = max(rawpower, pda->val_bpsk);
+ rawpower = max(rawpower, pda->val_16qam);
+ rawpower = max(rawpower, pda->val_64qam);
+ /* raw values are in 1/4 dBm units */
+ return rawpower / 4;
+ }
+
+ default:
+ return 20;
+ }
+}
+
+static int p54_generate_channel_lists(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+ struct p54_channel_list *list;
+ unsigned int i, j, k, max_channel_num;
+ int ret = 0;
+ u16 freq;
+
+ if ((priv->iq_autocal_len != priv->curve_data->entries) ||
+ (priv->iq_autocal_len != priv->output_limit->entries))
+ wiphy_err(dev->wiphy,
+ "Unsupported or damaged EEPROM detected. "
+ "You may not be able to use all channels.\n");
+
+ max_channel_num = max_t(unsigned int, priv->output_limit->entries,
+ priv->iq_autocal_len);
+ max_channel_num = max_t(unsigned int, max_channel_num,
+ priv->curve_data->entries);
+
+ list = kzalloc(sizeof(*list), GFP_KERNEL);
+ if (!list) {
+ ret = -ENOMEM;
+ goto free;
+ }
+ priv->chan_num = max_channel_num;
+ priv->survey = kzalloc(sizeof(struct survey_info) * max_channel_num,
+ GFP_KERNEL);
+ if (!priv->survey) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ list->max_entries = max_channel_num;
+ list->channels = kzalloc(sizeof(struct p54_channel_entry) *
+ max_channel_num, GFP_KERNEL);
+ if (!list->channels) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ for (i = 0; i < max_channel_num; i++) {
+ if (i < priv->iq_autocal_len) {
+ freq = le16_to_cpu(priv->iq_autocal[i].freq);
+ p54_update_channel_param(list, freq, CHAN_HAS_CAL);
+ }
+
+ if (i < priv->output_limit->entries) {
+ struct p54_channel_entry *tmp;
+
+ void *data = (void *) ((unsigned long) i *
+ priv->output_limit->entry_size +
+ priv->output_limit->offset +
+ priv->output_limit->data);
+
+ freq = le16_to_cpup((__le16 *) data);
+ tmp = p54_update_channel_param(list, freq,
+ CHAN_HAS_LIMIT);
+ if (tmp) {
+ tmp->max_power = p54_get_maxpower(priv, data);
+ }
+ }
+
+ if (i < priv->curve_data->entries) {
+ freq = le16_to_cpup((__le16 *) (i *
+ priv->curve_data->entry_size +
+ priv->curve_data->offset +
+ priv->curve_data->data));
+
+ p54_update_channel_param(list, freq, CHAN_HAS_CURVE);
+ }
+ }
+
+ /* sort the channel list by frequency */
+ sort(list->channels, list->entries, sizeof(struct p54_channel_entry),
+ p54_compare_channels, NULL);
+
+ k = 0;
+ for (i = 0, j = 0; i < NUM_NL80211_BANDS; i++) {
+ if (p54_generate_band(dev, list, &k, i) == 0)
+ j++;
+ }
+ if (j == 0) {
+ /* no useable band available. */
+ ret = -EINVAL;
+ }
+
+free:
+ if (list) {
+ kfree(list->channels);
+ kfree(list);
+ }
+ if (ret) {
+ kfree(priv->survey);
+ priv->survey = NULL;
+ }
+
+ return ret;
+}
+
+static int p54_convert_rev0(struct ieee80211_hw *dev,
+ struct pda_pa_curve_data *curve_data)
+{
+ struct p54_common *priv = dev->priv;
+ struct p54_pa_curve_data_sample *dst;
+ struct pda_pa_curve_data_sample_rev0 *src;
+ size_t cd_len = sizeof(*curve_data) +
+ (curve_data->points_per_channel*sizeof(*dst) + 2) *
+ curve_data->channels;
+ unsigned int i, j;
+ void *source, *target;
+
+ priv->curve_data = kmalloc(sizeof(*priv->curve_data) + cd_len,
+ GFP_KERNEL);
+ if (!priv->curve_data)
+ return -ENOMEM;
+
+ priv->curve_data->entries = curve_data->channels;
+ priv->curve_data->entry_size = sizeof(__le16) +
+ sizeof(*dst) * curve_data->points_per_channel;
+ priv->curve_data->offset = offsetof(struct pda_pa_curve_data, data);
+ priv->curve_data->len = cd_len;
+ memcpy(priv->curve_data->data, curve_data, sizeof(*curve_data));
+ source = curve_data->data;
+ target = ((struct pda_pa_curve_data *) priv->curve_data->data)->data;
+ for (i = 0; i < curve_data->channels; i++) {
+ __le16 *freq = source;
+ source += sizeof(__le16);
+ *((__le16 *)target) = *freq;
+ target += sizeof(__le16);
+ for (j = 0; j < curve_data->points_per_channel; j++) {
+ dst = target;
+ src = source;
+
+ dst->rf_power = src->rf_power;
+ dst->pa_detector = src->pa_detector;
+ dst->data_64qam = src->pcv;
+ /* "invent" the points for the other modulations */
+#define SUB(x, y) (u8)(((x) - (y)) > (x) ? 0 : (x) - (y))
+ dst->data_16qam = SUB(src->pcv, 12);
+ dst->data_qpsk = SUB(dst->data_16qam, 12);
+ dst->data_bpsk = SUB(dst->data_qpsk, 12);
+ dst->data_barker = SUB(dst->data_bpsk, 14);
+#undef SUB
+ target += sizeof(*dst);
+ source += sizeof(*src);
+ }
+ }
+
+ return 0;
+}
+
+static int p54_convert_rev1(struct ieee80211_hw *dev,
+ struct pda_pa_curve_data *curve_data)
+{
+ struct p54_common *priv = dev->priv;
+ struct p54_pa_curve_data_sample *dst;
+ struct pda_pa_curve_data_sample_rev1 *src;
+ size_t cd_len = sizeof(*curve_data) +
+ (curve_data->points_per_channel*sizeof(*dst) + 2) *
+ curve_data->channels;
+ unsigned int i, j;
+ void *source, *target;
+
+ priv->curve_data = kzalloc(cd_len + sizeof(*priv->curve_data),
+ GFP_KERNEL);
+ if (!priv->curve_data)
+ return -ENOMEM;
+
+ priv->curve_data->entries = curve_data->channels;
+ priv->curve_data->entry_size = sizeof(__le16) +
+ sizeof(*dst) * curve_data->points_per_channel;
+ priv->curve_data->offset = offsetof(struct pda_pa_curve_data, data);
+ priv->curve_data->len = cd_len;
+ memcpy(priv->curve_data->data, curve_data, sizeof(*curve_data));
+ source = curve_data->data;
+ target = ((struct pda_pa_curve_data *) priv->curve_data->data)->data;
+ for (i = 0; i < curve_data->channels; i++) {
+ __le16 *freq = source;
+ source += sizeof(__le16);
+ *((__le16 *)target) = *freq;
+ target += sizeof(__le16);
+ for (j = 0; j < curve_data->points_per_channel; j++) {
+ memcpy(target, source, sizeof(*src));
+
+ target += sizeof(*dst);
+ source += sizeof(*src);
+ }
+ source++;
+ }
+
+ return 0;
+}
+
+static const char *p54_rf_chips[] = { "INVALID-0", "Duette3", "Duette2",
+ "Frisbee", "Xbow", "Longbow", "INVALID-6", "INVALID-7" };
+
+static int p54_parse_rssical(struct ieee80211_hw *dev,
+ u8 *data, int len, u16 type)
+{
+ struct p54_common *priv = dev->priv;
+ struct p54_rssi_db_entry *entry;
+ size_t db_len, entries;
+ int offset = 0, i;
+
+ if (type != PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED) {
+ entries = (type == PDR_RSSI_LINEAR_APPROXIMATION) ? 1 : 2;
+ if (len != sizeof(struct pda_rssi_cal_entry) * entries) {
+ wiphy_err(dev->wiphy, "rssical size mismatch.\n");
+ goto err_data;
+ }
+ } else {
+ /*
+ * Some devices (Dell 1450 USB, Xbow 5GHz card, etc...)
+ * have an empty two byte header.
+ */
+ if (*((__le16 *)&data[offset]) == cpu_to_le16(0))
+ offset += 2;
+
+ entries = (len - offset) /
+ sizeof(struct pda_rssi_cal_ext_entry);
+
+ if (len < offset ||
+ (len - offset) % sizeof(struct pda_rssi_cal_ext_entry) ||
+ entries == 0) {
+ wiphy_err(dev->wiphy, "invalid rssi database.\n");
+ goto err_data;
+ }
+ }
+
+ db_len = sizeof(*entry) * entries;
+ priv->rssi_db = kzalloc(db_len + sizeof(*priv->rssi_db), GFP_KERNEL);
+ if (!priv->rssi_db)
+ return -ENOMEM;
+
+ priv->rssi_db->offset = 0;
+ priv->rssi_db->entries = entries;
+ priv->rssi_db->entry_size = sizeof(*entry);
+ priv->rssi_db->len = db_len;
+
+ entry = (void *)((unsigned long)priv->rssi_db->data + priv->rssi_db->offset);
+ if (type == PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED) {
+ struct pda_rssi_cal_ext_entry *cal = (void *) &data[offset];
+
+ for (i = 0; i < entries; i++) {
+ entry[i].freq = le16_to_cpu(cal[i].freq);
+ entry[i].mul = (s16) le16_to_cpu(cal[i].mul);
+ entry[i].add = (s16) le16_to_cpu(cal[i].add);
+ }
+ } else {
+ struct pda_rssi_cal_entry *cal = (void *) &data[offset];
+
+ for (i = 0; i < entries; i++) {
+ u16 freq = 0;
+ switch (i) {
+ case NL80211_BAND_2GHZ:
+ freq = 2437;
+ break;
+ case NL80211_BAND_5GHZ:
+ freq = 5240;
+ break;
+ }
+
+ entry[i].freq = freq;
+ entry[i].mul = (s16) le16_to_cpu(cal[i].mul);
+ entry[i].add = (s16) le16_to_cpu(cal[i].add);
+ }
+ }
+
+ /* sort the list by channel frequency */
+ sort(entry, entries, sizeof(*entry), p54_compare_rssichan, NULL);
+ return 0;
+
+err_data:
+ wiphy_err(dev->wiphy,
+ "rssi calibration data packing type:(%x) len:%d.\n",
+ type, len);
+
+ print_hex_dump_bytes("rssical:", DUMP_PREFIX_NONE, data, len);
+
+ wiphy_err(dev->wiphy, "please report this issue.\n");
+ return -EINVAL;
+}
+
+struct p54_rssi_db_entry *p54_rssi_find(struct p54_common *priv, const u16 freq)
+{
+ struct p54_rssi_db_entry *entry;
+ int i, found = -1;
+
+ if (!priv->rssi_db)
+ return &p54_rssi_default;
+
+ entry = (void *)(priv->rssi_db->data + priv->rssi_db->offset);
+ for (i = 0; i < priv->rssi_db->entries; i++) {
+ if (!same_band(freq, entry[i].freq))
+ continue;
+
+ if (found == -1) {
+ found = i;
+ continue;
+ }
+
+ /* nearest match */
+ if (abs(freq - entry[i].freq) <
+ abs(freq - entry[found].freq)) {
+ found = i;
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ return found < 0 ? &p54_rssi_default : &entry[found];
+}
+
+static void p54_parse_default_country(struct ieee80211_hw *dev,
+ void *data, int len)
+{
+ struct pda_country *country;
+
+ if (len != sizeof(*country)) {
+ wiphy_err(dev->wiphy,
+ "found possible invalid default country eeprom entry. (entry size: %d)\n",
+ len);
+
+ print_hex_dump_bytes("country:", DUMP_PREFIX_NONE,
+ data, len);
+
+ wiphy_err(dev->wiphy, "please report this issue.\n");
+ return;
+ }
+
+ country = (struct pda_country *) data;
+ if (country->flags == PDR_COUNTRY_CERT_CODE_PSEUDO)
+ regulatory_hint(dev->wiphy, country->alpha2);
+ else {
+ /* TODO:
+ * write a shared/common function that converts
+ * "Regulatory domain codes" (802.11-2007 14.8.2.2)
+ * into ISO/IEC 3166-1 alpha2 for regulatory_hint.
+ */
+ }
+}
+
+static int p54_convert_output_limits(struct ieee80211_hw *dev,
+ u8 *data, size_t len)
+{
+ struct p54_common *priv = dev->priv;
+
+ if (len < 2)
+ return -EINVAL;
+
+ if (data[0] != 0) {
+ wiphy_err(dev->wiphy, "unknown output power db revision:%x\n",
+ data[0]);
+ return -EINVAL;
+ }
+
+ if (2 + data[1] * sizeof(struct pda_channel_output_limit) > len)
+ return -EINVAL;
+
+ priv->output_limit = kmalloc(data[1] *
+ sizeof(struct pda_channel_output_limit) +
+ sizeof(*priv->output_limit), GFP_KERNEL);
+
+ if (!priv->output_limit)
+ return -ENOMEM;
+
+ priv->output_limit->offset = 0;
+ priv->output_limit->entries = data[1];
+ priv->output_limit->entry_size =
+ sizeof(struct pda_channel_output_limit);
+ priv->output_limit->len = priv->output_limit->entry_size *
+ priv->output_limit->entries +
+ priv->output_limit->offset;
+
+ memcpy(priv->output_limit->data, &data[2],
+ data[1] * sizeof(struct pda_channel_output_limit));
+
+ return 0;
+}
+
+static struct p54_cal_database *p54_convert_db(struct pda_custom_wrapper *src,
+ size_t total_len)
+{
+ struct p54_cal_database *dst;
+ size_t payload_len, entries, entry_size, offset;
+
+ payload_len = le16_to_cpu(src->len);
+ entries = le16_to_cpu(src->entries);
+ entry_size = le16_to_cpu(src->entry_size);
+ offset = le16_to_cpu(src->offset);
+ if (((entries * entry_size + offset) != payload_len) ||
+ (payload_len + sizeof(*src) != total_len))
+ return NULL;
+
+ dst = kmalloc(sizeof(*dst) + payload_len, GFP_KERNEL);
+ if (!dst)
+ return NULL;
+
+ dst->entries = entries;
+ dst->entry_size = entry_size;
+ dst->offset = offset;
+ dst->len = payload_len;
+
+ memcpy(dst->data, src->data, payload_len);
+ return dst;
+}
+
+int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len)
+{
+ struct p54_common *priv = dev->priv;
+ struct eeprom_pda_wrap *wrap;
+ struct pda_entry *entry;
+ unsigned int data_len, entry_len;
+ void *tmp;
+ int err;
+ u8 *end = (u8 *)eeprom + len;
+ u16 synth = 0;
+ u16 crc16 = ~0;
+
+ wrap = (struct eeprom_pda_wrap *) eeprom;
+ entry = (void *)wrap->data + le16_to_cpu(wrap->len);
+
+ /* verify that at least the entry length/code fits */
+ while ((u8 *)entry <= end - sizeof(*entry)) {
+ entry_len = le16_to_cpu(entry->len);
+ data_len = ((entry_len - 1) << 1);
+
+ /* abort if entry exceeds whole structure */
+ if ((u8 *)entry + sizeof(*entry) + data_len > end)
+ break;
+
+ switch (le16_to_cpu(entry->code)) {
+ case PDR_MAC_ADDRESS:
+ if (data_len != ETH_ALEN)
+ break;
+ SET_IEEE80211_PERM_ADDR(dev, entry->data);
+ break;
+ case PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS:
+ if (priv->output_limit)
+ break;
+ err = p54_convert_output_limits(dev, entry->data,
+ data_len);
+ if (err)
+ goto err;
+ break;
+ case PDR_PRISM_PA_CAL_CURVE_DATA: {
+ struct pda_pa_curve_data *curve_data =
+ (struct pda_pa_curve_data *)entry->data;
+ if (data_len < sizeof(*curve_data)) {
+ err = -EINVAL;
+ goto err;
+ }
+
+ switch (curve_data->cal_method_rev) {
+ case 0:
+ err = p54_convert_rev0(dev, curve_data);
+ break;
+ case 1:
+ err = p54_convert_rev1(dev, curve_data);
+ break;
+ default:
+ wiphy_err(dev->wiphy,
+ "unknown curve data revision %d\n",
+ curve_data->cal_method_rev);
+ err = -ENODEV;
+ break;
+ }
+ if (err)
+ goto err;
+ }
+ break;
+ case PDR_PRISM_ZIF_TX_IQ_CALIBRATION:
+ priv->iq_autocal = kmemdup(entry->data, data_len,
+ GFP_KERNEL);
+ if (!priv->iq_autocal) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ priv->iq_autocal_len = data_len / sizeof(struct pda_iq_autocal_entry);
+ break;
+ case PDR_DEFAULT_COUNTRY:
+ p54_parse_default_country(dev, entry->data, data_len);
+ break;
+ case PDR_INTERFACE_LIST:
+ tmp = entry->data;
+ while ((u8 *)tmp < entry->data + data_len) {
+ struct exp_if *exp_if = tmp;
+ if (exp_if->if_id == cpu_to_le16(IF_ID_ISL39000))
+ synth = le16_to_cpu(exp_if->variant);
+ tmp += sizeof(*exp_if);
+ }
+ break;
+ case PDR_HARDWARE_PLATFORM_COMPONENT_ID:
+ if (data_len < 2)
+ break;
+ priv->version = *(u8 *)(entry->data + 1);
+ break;
+ case PDR_RSSI_LINEAR_APPROXIMATION:
+ case PDR_RSSI_LINEAR_APPROXIMATION_DUAL_BAND:
+ case PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED:
+ err = p54_parse_rssical(dev, entry->data, data_len,
+ le16_to_cpu(entry->code));
+ if (err)
+ goto err;
+ break;
+ case PDR_RSSI_LINEAR_APPROXIMATION_CUSTOMV2: {
+ struct pda_custom_wrapper *pda = (void *) entry->data;
+ __le16 *src;
+ u16 *dst;
+ int i;
+
+ if (priv->rssi_db || data_len < sizeof(*pda))
+ break;
+
+ priv->rssi_db = p54_convert_db(pda, data_len);
+ if (!priv->rssi_db)
+ break;
+
+ src = (void *) priv->rssi_db->data;
+ dst = (void *) priv->rssi_db->data;
+
+ for (i = 0; i < priv->rssi_db->entries; i++)
+ *(dst++) = (s16) le16_to_cpu(*(src++));
+
+ }
+ break;
+ case PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM: {
+ struct pda_custom_wrapper *pda = (void *) entry->data;
+ if (priv->output_limit || data_len < sizeof(*pda))
+ break;
+ priv->output_limit = p54_convert_db(pda, data_len);
+ }
+ break;
+ case PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM: {
+ struct pda_custom_wrapper *pda = (void *) entry->data;
+ if (priv->curve_data || data_len < sizeof(*pda))
+ break;
+ priv->curve_data = p54_convert_db(pda, data_len);
+ }
+ break;
+ case PDR_END:
+ crc16 = ~crc_ccitt(crc16, (u8 *) entry, sizeof(*entry));
+ if (crc16 != le16_to_cpup((__le16 *)entry->data)) {
+ wiphy_err(dev->wiphy, "eeprom failed checksum "
+ "test!\n");
+ err = -ENOMSG;
+ goto err;
+ } else {
+ goto good_eeprom;
+ }
+ break;
+ default:
+ break;
+ }
+
+ crc16 = crc_ccitt(crc16, (u8 *)entry, (entry_len + 1) * 2);
+ entry = (void *)entry + (entry_len + 1) * 2;
+ }
+
+ wiphy_err(dev->wiphy, "unexpected end of eeprom data.\n");
+ err = -ENODATA;
+ goto err;
+
+good_eeprom:
+ if (!synth || !priv->iq_autocal || !priv->output_limit ||
+ !priv->curve_data) {
+ wiphy_err(dev->wiphy,
+ "not all required entries found in eeprom!\n");
+ err = -EINVAL;
+ goto err;
+ }
+
+ priv->rxhw = synth & PDR_SYNTH_FRONTEND_MASK;
+
+ err = p54_generate_channel_lists(dev);
+ if (err)
+ goto err;
+
+ if (priv->rxhw == PDR_SYNTH_FRONTEND_XBOW)
+ p54_init_xbow_synth(priv);
+ if (!(synth & PDR_SYNTH_24_GHZ_DISABLED))
+ dev->wiphy->bands[NL80211_BAND_2GHZ] =
+ priv->band_table[NL80211_BAND_2GHZ];
+ if (!(synth & PDR_SYNTH_5_GHZ_DISABLED))
+ dev->wiphy->bands[NL80211_BAND_5GHZ] =
+ priv->band_table[NL80211_BAND_5GHZ];
+ if ((synth & PDR_SYNTH_RX_DIV_MASK) == PDR_SYNTH_RX_DIV_SUPPORTED)
+ priv->rx_diversity_mask = 3;
+ if ((synth & PDR_SYNTH_TX_DIV_MASK) == PDR_SYNTH_TX_DIV_SUPPORTED)
+ priv->tx_diversity_mask = 3;
+
+ if (!is_valid_ether_addr(dev->wiphy->perm_addr)) {
+ u8 perm_addr[ETH_ALEN];
+
+ wiphy_warn(dev->wiphy,
+ "Invalid hwaddr! Using randomly generated MAC addr\n");
+ eth_random_addr(perm_addr);
+ SET_IEEE80211_PERM_ADDR(dev, perm_addr);
+ }
+
+ priv->cur_rssi = &p54_rssi_default;
+
+ wiphy_info(dev->wiphy, "hwaddr %pM, MAC:isl38%02x RF:%s\n",
+ dev->wiphy->perm_addr, priv->version,
+ p54_rf_chips[priv->rxhw]);
+
+ return 0;
+
+err:
+ kfree(priv->iq_autocal);
+ kfree(priv->output_limit);
+ kfree(priv->curve_data);
+ kfree(priv->rssi_db);
+ kfree(priv->survey);
+ priv->iq_autocal = NULL;
+ priv->output_limit = NULL;
+ priv->curve_data = NULL;
+ priv->rssi_db = NULL;
+ priv->survey = NULL;
+
+ wiphy_err(dev->wiphy, "eeprom parse failed!\n");
+ return err;
+}
+EXPORT_SYMBOL_GPL(p54_parse_eeprom);
+
+int p54_read_eeprom(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+ size_t eeprom_size = 0x2020, offset = 0, blocksize, maxblocksize;
+ int ret = -ENOMEM;
+ void *eeprom;
+
+ maxblocksize = EEPROM_READBACK_LEN;
+ if (priv->fw_var >= 0x509)
+ maxblocksize -= 0xc;
+ else
+ maxblocksize -= 0x4;
+
+ eeprom = kzalloc(eeprom_size, GFP_KERNEL);
+ if (unlikely(!eeprom))
+ goto free;
+
+ while (eeprom_size) {
+ blocksize = min(eeprom_size, maxblocksize);
+ ret = p54_download_eeprom(priv, eeprom + offset,
+ offset, blocksize);
+ if (unlikely(ret))
+ goto free;
+
+ offset += blocksize;
+ eeprom_size -= blocksize;
+ }
+
+ ret = p54_parse_eeprom(dev, eeprom, offset);
+free:
+ kfree(eeprom);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(p54_read_eeprom);
diff --git a/drivers/net/wireless/intersil/p54/eeprom.h b/drivers/net/wireless/intersil/p54/eeprom.h
new file mode 100644
index 0000000..20ebe39
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/eeprom.h
@@ -0,0 +1,245 @@
+/*
+ * eeprom specific definitions for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ *
+ * - LMAC API interface header file for STLC4560 (lmac_longbow.h)
+ * Copyright (C) 2007 Conexant Systems, Inc.
+ *
+ * - islmvc driver
+ * Copyright (C) 2001 Intersil Americas Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef EEPROM_H
+#define EEPROM_H
+
+/* PDA defines are Copyright (C) 2005 Nokia Corporation (taken from islsm_pda.h) */
+
+struct pda_entry {
+ __le16 len; /* includes both code and data */
+ __le16 code;
+ u8 data[0];
+} __packed;
+
+struct eeprom_pda_wrap {
+ __le32 magic;
+ __le16 pad;
+ __le16 len;
+ __le32 arm_opcode;
+ u8 data[0];
+} __packed;
+
+struct p54_iq_autocal_entry {
+ __le16 iq_param[4];
+} __packed;
+
+struct pda_iq_autocal_entry {
+ __le16 freq;
+ struct p54_iq_autocal_entry params;
+} __packed;
+
+struct pda_channel_output_limit {
+ __le16 freq;
+ u8 val_bpsk;
+ u8 val_qpsk;
+ u8 val_16qam;
+ u8 val_64qam;
+ u8 rate_set_mask;
+ u8 rate_set_size;
+} __packed;
+
+struct pda_channel_output_limit_point_longbow {
+ __le16 val_bpsk;
+ __le16 val_qpsk;
+ __le16 val_16qam;
+ __le16 val_64qam;
+} __packed;
+
+struct pda_channel_output_limit_longbow {
+ __le16 freq;
+ struct pda_channel_output_limit_point_longbow point[3];
+} __packed;
+
+struct pda_pa_curve_data_sample_rev0 {
+ u8 rf_power;
+ u8 pa_detector;
+ u8 pcv;
+} __packed;
+
+struct pda_pa_curve_data_sample_rev1 {
+ u8 rf_power;
+ u8 pa_detector;
+ u8 data_barker;
+ u8 data_bpsk;
+ u8 data_qpsk;
+ u8 data_16qam;
+ u8 data_64qam;
+} __packed;
+
+struct pda_pa_curve_data {
+ u8 cal_method_rev;
+ u8 channels;
+ u8 points_per_channel;
+ u8 padding;
+ u8 data[0];
+} __packed;
+
+struct pda_rssi_cal_ext_entry {
+ __le16 freq;
+ __le16 mul;
+ __le16 add;
+} __packed;
+
+struct pda_rssi_cal_entry {
+ __le16 mul;
+ __le16 add;
+} __packed;
+
+struct pda_country {
+ u8 regdomain;
+ u8 alpha2[2];
+ u8 flags;
+} __packed;
+
+struct pda_antenna_gain {
+ struct {
+ u8 gain_5GHz; /* 0.25 dBi units */
+ u8 gain_2GHz; /* 0.25 dBi units */
+ } __packed antenna[0];
+} __packed;
+
+struct pda_custom_wrapper {
+ __le16 entries;
+ __le16 entry_size;
+ __le16 offset;
+ __le16 len;
+ u8 data[0];
+} __packed;
+
+/*
+ * this defines the PDR codes used to build PDAs as defined in document
+ * number 553155. The current implementation mirrors version 1.1 of the
+ * document and lists only PDRs supported by the ARM platform.
+ */
+
+/* common and choice range (0x0000 - 0x0fff) */
+#define PDR_END 0x0000
+#define PDR_MANUFACTURING_PART_NUMBER 0x0001
+#define PDR_PDA_VERSION 0x0002
+#define PDR_NIC_SERIAL_NUMBER 0x0003
+#define PDR_NIC_RAM_SIZE 0x0005
+#define PDR_RFMODEM_SUP_RANGE 0x0006
+#define PDR_PRISM_MAC_SUP_RANGE 0x0007
+#define PDR_NIC_ID 0x0008
+
+#define PDR_MAC_ADDRESS 0x0101
+#define PDR_REGULATORY_DOMAIN_LIST 0x0103 /* obsolete */
+#define PDR_ALLOWED_CHAN_SET 0x0104
+#define PDR_DEFAULT_CHAN 0x0105
+#define PDR_TEMPERATURE_TYPE 0x0107
+
+#define PDR_IFR_SETTING 0x0200
+#define PDR_RFR_SETTING 0x0201
+#define PDR_3861_BASELINE_REG_SETTINGS 0x0202
+#define PDR_3861_SHADOW_REG_SETTINGS 0x0203
+#define PDR_3861_IFRF_REG_SETTINGS 0x0204
+
+#define PDR_3861_CHAN_CALIB_SET_POINTS 0x0300
+#define PDR_3861_CHAN_CALIB_INTEGRATOR 0x0301
+
+#define PDR_3842_PRISM_II_NIC_CONFIG 0x0400
+#define PDR_PRISM_USB_ID 0x0401
+#define PDR_PRISM_PCI_ID 0x0402
+#define PDR_PRISM_PCI_IF_CONFIG 0x0403
+#define PDR_PRISM_PCI_PM_CONFIG 0x0404
+
+#define PDR_3861_MF_TEST_CHAN_SET_POINTS 0x0900
+#define PDR_3861_MF_TEST_CHAN_INTEGRATORS 0x0901
+
+/* ARM range (0x1000 - 0x1fff) */
+#define PDR_COUNTRY_INFORMATION 0x1000 /* obsolete */
+#define PDR_INTERFACE_LIST 0x1001
+#define PDR_HARDWARE_PLATFORM_COMPONENT_ID 0x1002
+#define PDR_OEM_NAME 0x1003
+#define PDR_PRODUCT_NAME 0x1004
+#define PDR_UTF8_OEM_NAME 0x1005
+#define PDR_UTF8_PRODUCT_NAME 0x1006
+#define PDR_COUNTRY_LIST 0x1007
+#define PDR_DEFAULT_COUNTRY 0x1008
+
+#define PDR_ANTENNA_GAIN 0x1100
+
+#define PDR_PRISM_INDIGO_PA_CALIBRATION_DATA 0x1901
+#define PDR_RSSI_LINEAR_APPROXIMATION 0x1902
+#define PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS 0x1903
+#define PDR_PRISM_PA_CAL_CURVE_DATA 0x1904
+#define PDR_RSSI_LINEAR_APPROXIMATION_DUAL_BAND 0x1905
+#define PDR_PRISM_ZIF_TX_IQ_CALIBRATION 0x1906
+#define PDR_REGULATORY_POWER_LIMITS 0x1907
+#define PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED 0x1908
+#define PDR_RADIATED_TRANSMISSION_CORRECTION 0x1909
+#define PDR_PRISM_TX_IQ_CALIBRATION 0x190a
+
+/* reserved range (0x2000 - 0x7fff) */
+
+/* customer range (0x8000 - 0xffff) */
+#define PDR_BASEBAND_REGISTERS 0x8000
+#define PDR_PER_CHANNEL_BASEBAND_REGISTERS 0x8001
+
+/* used by our modificated eeprom image */
+#define PDR_RSSI_LINEAR_APPROXIMATION_CUSTOM 0xDEAD
+#define PDR_RSSI_LINEAR_APPROXIMATION_CUSTOMV2 0xCAFF
+#define PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM 0xBEEF
+#define PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM 0xB05D
+
+/* Interface Definitions */
+#define PDR_INTERFACE_ROLE_SERVER 0x0000
+#define PDR_INTERFACE_ROLE_CLIENT 0x0001
+
+/* PDR definitions for default country & country list */
+#define PDR_COUNTRY_CERT_CODE 0x80
+#define PDR_COUNTRY_CERT_CODE_REAL 0x00
+#define PDR_COUNTRY_CERT_CODE_PSEUDO 0x80
+#define PDR_COUNTRY_CERT_BAND 0x40
+#define PDR_COUNTRY_CERT_BAND_2GHZ 0x00
+#define PDR_COUNTRY_CERT_BAND_5GHZ 0x40
+#define PDR_COUNTRY_CERT_IODOOR 0x30
+#define PDR_COUNTRY_CERT_IODOOR_BOTH 0x00
+#define PDR_COUNTRY_CERT_IODOOR_INDOOR 0x20
+#define PDR_COUNTRY_CERT_IODOOR_OUTDOOR 0x30
+#define PDR_COUNTRY_CERT_INDEX 0x0f
+
+/* Specific LMAC FW/HW variant definitions */
+#define PDR_SYNTH_FRONTEND_MASK 0x0007
+#define PDR_SYNTH_FRONTEND_DUETTE3 0x0001
+#define PDR_SYNTH_FRONTEND_DUETTE2 0x0002
+#define PDR_SYNTH_FRONTEND_FRISBEE 0x0003
+#define PDR_SYNTH_FRONTEND_XBOW 0x0004
+#define PDR_SYNTH_FRONTEND_LONGBOW 0x0005
+#define PDR_SYNTH_IQ_CAL_MASK 0x0018
+#define PDR_SYNTH_IQ_CAL_PA_DETECTOR 0x0000
+#define PDR_SYNTH_IQ_CAL_DISABLED 0x0008
+#define PDR_SYNTH_IQ_CAL_ZIF 0x0010
+#define PDR_SYNTH_FAA_SWITCH_MASK 0x0020
+#define PDR_SYNTH_FAA_SWITCH_ENABLED 0x0020
+#define PDR_SYNTH_24_GHZ_MASK 0x0040
+#define PDR_SYNTH_24_GHZ_DISABLED 0x0040
+#define PDR_SYNTH_5_GHZ_MASK 0x0080
+#define PDR_SYNTH_5_GHZ_DISABLED 0x0080
+#define PDR_SYNTH_RX_DIV_MASK 0x0100
+#define PDR_SYNTH_RX_DIV_SUPPORTED 0x0100
+#define PDR_SYNTH_TX_DIV_MASK 0x0200
+#define PDR_SYNTH_TX_DIV_SUPPORTED 0x0200
+#define PDR_SYNTH_ASM_MASK 0x0400
+#define PDR_SYNTH_ASM_XSWON 0x0400
+
+#endif /* EEPROM_H */
diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c
new file mode 100644
index 0000000..52c095c
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/fwio.c
@@ -0,0 +1,764 @@
+/*
+ * Firmware I/O code for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ * Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ * - stlc45xx driver
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <linux/export.h>
+
+#include <net/mac80211.h>
+
+#include "p54.h"
+#include "eeprom.h"
+#include "lmac.h"
+
+int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw)
+{
+ struct p54_common *priv = dev->priv;
+ struct exp_if *exp_if;
+ struct bootrec *bootrec;
+ u32 *data = (u32 *)fw->data;
+ u32 *end_data = (u32 *)fw->data + (fw->size >> 2);
+ u8 *fw_version = NULL;
+ size_t len;
+ int i;
+ int maxlen;
+
+ if (priv->rx_start)
+ return 0;
+
+ while (data < end_data && *data)
+ data++;
+
+ while (data < end_data && !*data)
+ data++;
+
+ bootrec = (struct bootrec *) data;
+
+ while (bootrec->data <= end_data && (bootrec->data +
+ (len = le32_to_cpu(bootrec->len))) <= end_data) {
+ u32 code = le32_to_cpu(bootrec->code);
+ switch (code) {
+ case BR_CODE_COMPONENT_ID:
+ priv->fw_interface = be32_to_cpup((__be32 *)
+ bootrec->data);
+ switch (priv->fw_interface) {
+ case FW_LM86:
+ case FW_LM20:
+ case FW_LM87: {
+ char *iftype = (char *)bootrec->data;
+ wiphy_info(priv->hw->wiphy,
+ "p54 detected a LM%c%c firmware\n",
+ iftype[2], iftype[3]);
+ break;
+ }
+ case FW_FMAC:
+ default:
+ wiphy_err(priv->hw->wiphy,
+ "unsupported firmware\n");
+ return -ENODEV;
+ }
+ break;
+ case BR_CODE_COMPONENT_VERSION:
+ /* 24 bytes should be enough for all firmwares */
+ if (strnlen((unsigned char *) bootrec->data, 24) < 24)
+ fw_version = (unsigned char *) bootrec->data;
+ break;
+ case BR_CODE_DESCR: {
+ struct bootrec_desc *desc =
+ (struct bootrec_desc *)bootrec->data;
+ priv->rx_start = le32_to_cpu(desc->rx_start);
+ /* FIXME add sanity checking */
+ priv->rx_end = le32_to_cpu(desc->rx_end) - 0x3500;
+ priv->headroom = desc->headroom;
+ priv->tailroom = desc->tailroom;
+ priv->privacy_caps = desc->privacy_caps;
+ priv->rx_keycache_size = desc->rx_keycache_size;
+ if (le32_to_cpu(bootrec->len) == 11)
+ priv->rx_mtu = le16_to_cpu(desc->rx_mtu);
+ else
+ priv->rx_mtu = (size_t)
+ 0x620 - priv->tx_hdr_len;
+ maxlen = priv->tx_hdr_len + /* USB devices */
+ sizeof(struct p54_rx_data) +
+ 4 + /* rx alignment */
+ IEEE80211_MAX_FRAG_THRESHOLD;
+ if (priv->rx_mtu > maxlen && PAGE_SIZE == 4096) {
+ printk(KERN_INFO "p54: rx_mtu reduced from %d "
+ "to %d\n", priv->rx_mtu, maxlen);
+ priv->rx_mtu = maxlen;
+ }
+ break;
+ }
+ case BR_CODE_EXPOSED_IF:
+ exp_if = (struct exp_if *) bootrec->data;
+ for (i = 0; i < (len * sizeof(*exp_if) / 4); i++)
+ if (exp_if[i].if_id == cpu_to_le16(IF_ID_LMAC))
+ priv->fw_var = le16_to_cpu(exp_if[i].variant);
+ break;
+ case BR_CODE_DEPENDENT_IF:
+ break;
+ case BR_CODE_END_OF_BRA:
+ case LEGACY_BR_CODE_END_OF_BRA:
+ end_data = NULL;
+ break;
+ default:
+ break;
+ }
+ bootrec = (struct bootrec *)&bootrec->data[len];
+ }
+
+ if (fw_version) {
+ wiphy_info(priv->hw->wiphy,
+ "FW rev %s - Softmac protocol %x.%x\n",
+ fw_version, priv->fw_var >> 8, priv->fw_var & 0xff);
+ snprintf(dev->wiphy->fw_version, sizeof(dev->wiphy->fw_version),
+ "%s - %x.%x", fw_version,
+ priv->fw_var >> 8, priv->fw_var & 0xff);
+ }
+
+ if (priv->fw_var < 0x500)
+ wiphy_info(priv->hw->wiphy,
+ "you are using an obsolete firmware. "
+ "visit http://wireless.kernel.org/en/users/Drivers/p54 "
+ "and grab one for \"kernel >= 2.6.28\"!\n");
+
+ if (priv->fw_var >= 0x300) {
+ /* Firmware supports QoS, use it! */
+
+ if (priv->fw_var >= 0x500) {
+ priv->tx_stats[P54_QUEUE_AC_VO].limit = 16;
+ priv->tx_stats[P54_QUEUE_AC_VI].limit = 16;
+ priv->tx_stats[P54_QUEUE_AC_BE].limit = 16;
+ priv->tx_stats[P54_QUEUE_AC_BK].limit = 16;
+ } else {
+ priv->tx_stats[P54_QUEUE_AC_VO].limit = 3;
+ priv->tx_stats[P54_QUEUE_AC_VI].limit = 4;
+ priv->tx_stats[P54_QUEUE_AC_BE].limit = 3;
+ priv->tx_stats[P54_QUEUE_AC_BK].limit = 2;
+ }
+ priv->hw->queues = P54_QUEUE_AC_NUM;
+ }
+
+ wiphy_info(priv->hw->wiphy,
+ "cryptographic accelerator WEP:%s, TKIP:%s, CCMP:%s\n",
+ (priv->privacy_caps & BR_DESC_PRIV_CAP_WEP) ? "YES" : "no",
+ (priv->privacy_caps &
+ (BR_DESC_PRIV_CAP_TKIP | BR_DESC_PRIV_CAP_MICHAEL))
+ ? "YES" : "no",
+ (priv->privacy_caps & BR_DESC_PRIV_CAP_AESCCMP)
+ ? "YES" : "no");
+
+ if (priv->rx_keycache_size) {
+ /*
+ * NOTE:
+ *
+ * The firmware provides at most 255 (0 - 254) slots
+ * for keys which are then used to offload decryption.
+ * As a result the 255 entry (aka 0xff) can be used
+ * safely by the driver to mark keys that didn't fit
+ * into the full cache. This trick saves us from
+ * keeping a extra list for uploaded keys.
+ */
+
+ priv->used_rxkeys = kcalloc(BITS_TO_LONGS(priv->rx_keycache_size),
+ sizeof(long),
+ GFP_KERNEL);
+
+ if (!priv->used_rxkeys)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(p54_parse_firmware);
+
+static struct sk_buff *p54_alloc_skb(struct p54_common *priv, u16 hdr_flags,
+ u16 payload_len, u16 type, gfp_t memflags)
+{
+ struct p54_hdr *hdr;
+ struct sk_buff *skb;
+ size_t frame_len = sizeof(*hdr) + payload_len;
+
+ if (frame_len > P54_MAX_CTRL_FRAME_LEN)
+ return NULL;
+
+ if (unlikely(skb_queue_len(&priv->tx_pending) > 64))
+ return NULL;
+
+ skb = __dev_alloc_skb(priv->tx_hdr_len + frame_len, memflags);
+ if (!skb)
+ return NULL;
+ skb_reserve(skb, priv->tx_hdr_len);
+
+ hdr = skb_put(skb, sizeof(*hdr));
+ hdr->flags = cpu_to_le16(hdr_flags);
+ hdr->len = cpu_to_le16(payload_len);
+ hdr->type = cpu_to_le16(type);
+ hdr->tries = hdr->rts_tries = 0;
+ return skb;
+}
+
+int p54_download_eeprom(struct p54_common *priv, void *buf,
+ u16 offset, u16 len)
+{
+ struct p54_eeprom_lm86 *eeprom_hdr;
+ struct sk_buff *skb;
+ size_t eeprom_hdr_size;
+ int ret = 0;
+ long timeout;
+
+ if (priv->fw_var >= 0x509)
+ eeprom_hdr_size = sizeof(*eeprom_hdr);
+ else
+ eeprom_hdr_size = 0x4;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL, eeprom_hdr_size +
+ len, P54_CONTROL_TYPE_EEPROM_READBACK,
+ GFP_KERNEL);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ mutex_lock(&priv->eeprom_mutex);
+ priv->eeprom = buf;
+ eeprom_hdr = skb_put(skb, eeprom_hdr_size + len);
+
+ if (priv->fw_var < 0x509) {
+ eeprom_hdr->v1.offset = cpu_to_le16(offset);
+ eeprom_hdr->v1.len = cpu_to_le16(len);
+ } else {
+ eeprom_hdr->v2.offset = cpu_to_le32(offset);
+ eeprom_hdr->v2.len = cpu_to_le16(len);
+ eeprom_hdr->v2.magic2 = 0xf;
+ memcpy(eeprom_hdr->v2.magic, (const char *)"LOCK", 4);
+ }
+
+ p54_tx(priv, skb);
+
+ timeout = wait_for_completion_interruptible_timeout(
+ &priv->eeprom_comp, HZ);
+ if (timeout <= 0) {
+ wiphy_err(priv->hw->wiphy,
+ "device does not respond or signal received!\n");
+ ret = -EBUSY;
+ }
+ priv->eeprom = NULL;
+ mutex_unlock(&priv->eeprom_mutex);
+ return ret;
+}
+
+int p54_update_beacon_tim(struct p54_common *priv, u16 aid, bool set)
+{
+ struct sk_buff *skb;
+ struct p54_tim *tim;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*tim),
+ P54_CONTROL_TYPE_TIM, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ tim = skb_put(skb, sizeof(*tim));
+ tim->count = 1;
+ tim->entry[0] = cpu_to_le16(set ? (aid | 0x8000) : aid);
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_sta_unlock(struct p54_common *priv, u8 *addr)
+{
+ struct sk_buff *skb;
+ struct p54_sta_unlock *sta;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*sta),
+ P54_CONTROL_TYPE_PSM_STA_UNLOCK, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ sta = skb_put(skb, sizeof(*sta));
+ memcpy(sta->addr, addr, ETH_ALEN);
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_tx_cancel(struct p54_common *priv, __le32 req_id)
+{
+ struct sk_buff *skb;
+ struct p54_txcancel *cancel;
+ u32 _req_id = le32_to_cpu(req_id);
+
+ if (unlikely(_req_id < priv->rx_start || _req_id > priv->rx_end))
+ return -EINVAL;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*cancel),
+ P54_CONTROL_TYPE_TXCANCEL, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ cancel = skb_put(skb, sizeof(*cancel));
+ cancel->req_id = req_id;
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_setup_mac(struct p54_common *priv)
+{
+ struct sk_buff *skb;
+ struct p54_setup_mac *setup;
+ u16 mode;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*setup),
+ P54_CONTROL_TYPE_SETUP, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ setup = skb_put(skb, sizeof(*setup));
+ if (!(priv->hw->conf.flags & IEEE80211_CONF_IDLE)) {
+ switch (priv->mode) {
+ case NL80211_IFTYPE_STATION:
+ mode = P54_FILTER_TYPE_STATION;
+ break;
+ case NL80211_IFTYPE_AP:
+ mode = P54_FILTER_TYPE_AP;
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MESH_POINT:
+ mode = P54_FILTER_TYPE_IBSS;
+ break;
+ case NL80211_IFTYPE_MONITOR:
+ mode = P54_FILTER_TYPE_PROMISCUOUS;
+ break;
+ default:
+ mode = P54_FILTER_TYPE_HIBERNATE;
+ break;
+ }
+
+ /*
+ * "TRANSPARENT and PROMISCUOUS are mutually exclusive"
+ * STSW45X0C LMAC API - page 12
+ */
+ if (priv->filter_flags & FIF_OTHER_BSS &&
+ (mode != P54_FILTER_TYPE_PROMISCUOUS))
+ mode |= P54_FILTER_TYPE_TRANSPARENT;
+ } else {
+ mode = P54_FILTER_TYPE_HIBERNATE;
+ }
+
+ setup->mac_mode = cpu_to_le16(mode);
+ memcpy(setup->mac_addr, priv->mac_addr, ETH_ALEN);
+ memcpy(setup->bssid, priv->bssid, ETH_ALEN);
+ setup->rx_antenna = 2 & priv->rx_diversity_mask; /* automatic */
+ setup->rx_align = 0;
+ if (priv->fw_var < 0x500) {
+ setup->v1.basic_rate_mask = cpu_to_le32(priv->basic_rate_mask);
+ memset(setup->v1.rts_rates, 0, 8);
+ setup->v1.rx_addr = cpu_to_le32(priv->rx_end);
+ setup->v1.max_rx = cpu_to_le16(priv->rx_mtu);
+ setup->v1.rxhw = cpu_to_le16(priv->rxhw);
+ setup->v1.wakeup_timer = cpu_to_le16(priv->wakeup_timer);
+ setup->v1.unalloc0 = cpu_to_le16(0);
+ } else {
+ setup->v2.rx_addr = cpu_to_le32(priv->rx_end);
+ setup->v2.max_rx = cpu_to_le16(priv->rx_mtu);
+ setup->v2.rxhw = cpu_to_le16(priv->rxhw);
+ setup->v2.timer = cpu_to_le16(priv->wakeup_timer);
+ setup->v2.truncate = cpu_to_le16(48896);
+ setup->v2.basic_rate_mask = cpu_to_le32(priv->basic_rate_mask);
+ setup->v2.sbss_offset = 0;
+ setup->v2.mcast_window = 0;
+ setup->v2.rx_rssi_threshold = 0;
+ setup->v2.rx_ed_threshold = 0;
+ setup->v2.ref_clock = cpu_to_le32(644245094);
+ setup->v2.lpf_bandwidth = cpu_to_le16(65535);
+ setup->v2.osc_start_delay = cpu_to_le16(65535);
+ }
+ p54_tx(priv, skb);
+ priv->phy_idle = mode == P54_FILTER_TYPE_HIBERNATE;
+ return 0;
+}
+
+int p54_scan(struct p54_common *priv, u16 mode, u16 dwell)
+{
+ struct sk_buff *skb;
+ struct p54_hdr *hdr;
+ struct p54_scan_head *head;
+ struct p54_iq_autocal_entry *iq_autocal;
+ union p54_scan_body_union *body;
+ struct p54_scan_tail_rate *rate;
+ struct pda_rssi_cal_entry *rssi;
+ struct p54_rssi_db_entry *rssi_data;
+ unsigned int i;
+ void *entry;
+ __le16 freq = cpu_to_le16(priv->hw->conf.chandef.chan->center_freq);
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*head) +
+ 2 + sizeof(*iq_autocal) + sizeof(*body) +
+ sizeof(*rate) + 2 * sizeof(*rssi),
+ P54_CONTROL_TYPE_SCAN, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ head = skb_put(skb, sizeof(*head));
+ memset(head->scan_params, 0, sizeof(head->scan_params));
+ head->mode = cpu_to_le16(mode);
+ head->dwell = cpu_to_le16(dwell);
+ head->freq = freq;
+
+ if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) {
+ __le16 *pa_power_points = skb_put(skb, 2);
+ *pa_power_points = cpu_to_le16(0x0c);
+ }
+
+ iq_autocal = skb_put(skb, sizeof(*iq_autocal));
+ for (i = 0; i < priv->iq_autocal_len; i++) {
+ if (priv->iq_autocal[i].freq != freq)
+ continue;
+
+ memcpy(iq_autocal, &priv->iq_autocal[i].params,
+ sizeof(struct p54_iq_autocal_entry));
+ break;
+ }
+ if (i == priv->iq_autocal_len)
+ goto err;
+
+ if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW)
+ body = skb_put(skb, sizeof(body->longbow));
+ else
+ body = skb_put(skb, sizeof(body->normal));
+
+ for (i = 0; i < priv->output_limit->entries; i++) {
+ __le16 *entry_freq = (void *) (priv->output_limit->data +
+ priv->output_limit->entry_size * i);
+
+ if (*entry_freq != freq)
+ continue;
+
+ if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) {
+ memcpy(&body->longbow.power_limits,
+ (void *) entry_freq + sizeof(__le16),
+ priv->output_limit->entry_size);
+ } else {
+ struct pda_channel_output_limit *limits =
+ (void *) entry_freq;
+
+ body->normal.val_barker = 0x38;
+ body->normal.val_bpsk = body->normal.dup_bpsk =
+ limits->val_bpsk;
+ body->normal.val_qpsk = body->normal.dup_qpsk =
+ limits->val_qpsk;
+ body->normal.val_16qam = body->normal.dup_16qam =
+ limits->val_16qam;
+ body->normal.val_64qam = body->normal.dup_64qam =
+ limits->val_64qam;
+ }
+ break;
+ }
+ if (i == priv->output_limit->entries)
+ goto err;
+
+ entry = (void *)(priv->curve_data->data + priv->curve_data->offset);
+ for (i = 0; i < priv->curve_data->entries; i++) {
+ if (*((__le16 *)entry) != freq) {
+ entry += priv->curve_data->entry_size;
+ continue;
+ }
+
+ if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) {
+ memcpy(&body->longbow.curve_data,
+ entry + sizeof(__le16),
+ priv->curve_data->entry_size);
+ } else {
+ struct p54_scan_body *chan = &body->normal;
+ struct pda_pa_curve_data *curve_data =
+ (void *) priv->curve_data->data;
+
+ entry += sizeof(__le16);
+ chan->pa_points_per_curve = 8;
+ memset(chan->curve_data, 0, sizeof(chan->curve_data));
+ memcpy(chan->curve_data, entry,
+ sizeof(struct p54_pa_curve_data_sample) *
+ min((u8)8, curve_data->points_per_channel));
+ }
+ break;
+ }
+ if (i == priv->curve_data->entries)
+ goto err;
+
+ if ((priv->fw_var >= 0x500) && (priv->fw_var < 0x509)) {
+ rate = skb_put(skb, sizeof(*rate));
+ rate->basic_rate_mask = cpu_to_le32(priv->basic_rate_mask);
+ for (i = 0; i < sizeof(rate->rts_rates); i++)
+ rate->rts_rates[i] = i;
+ }
+
+ rssi = skb_put(skb, sizeof(*rssi));
+ rssi_data = p54_rssi_find(priv, le16_to_cpu(freq));
+ rssi->mul = cpu_to_le16(rssi_data->mul);
+ rssi->add = cpu_to_le16(rssi_data->add);
+ if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) {
+ /* Longbow frontend needs ever more */
+ rssi = skb_put(skb, sizeof(*rssi));
+ rssi->mul = cpu_to_le16(rssi_data->longbow_unkn);
+ rssi->add = cpu_to_le16(rssi_data->longbow_unk2);
+ }
+
+ if (priv->fw_var >= 0x509) {
+ rate = skb_put(skb, sizeof(*rate));
+ rate->basic_rate_mask = cpu_to_le32(priv->basic_rate_mask);
+ for (i = 0; i < sizeof(rate->rts_rates); i++)
+ rate->rts_rates[i] = i;
+ }
+
+ hdr = (struct p54_hdr *) skb->data;
+ hdr->len = cpu_to_le16(skb->len - sizeof(*hdr));
+
+ p54_tx(priv, skb);
+ priv->cur_rssi = rssi_data;
+ return 0;
+
+err:
+ wiphy_err(priv->hw->wiphy, "frequency change to channel %d failed.\n",
+ ieee80211_frequency_to_channel(
+ priv->hw->conf.chandef.chan->center_freq));
+
+ dev_kfree_skb_any(skb);
+ return -EINVAL;
+}
+
+int p54_set_leds(struct p54_common *priv)
+{
+ struct sk_buff *skb;
+ struct p54_led *led;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*led),
+ P54_CONTROL_TYPE_LED, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ led = skb_put(skb, sizeof(*led));
+ led->flags = cpu_to_le16(0x0003);
+ led->mask[0] = led->mask[1] = cpu_to_le16(priv->softled_state);
+ led->delay[0] = cpu_to_le16(1);
+ led->delay[1] = cpu_to_le16(0);
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_set_edcf(struct p54_common *priv)
+{
+ struct sk_buff *skb;
+ struct p54_edcf *edcf;
+ u8 rtd;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*edcf),
+ P54_CONTROL_TYPE_DCFINIT, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ edcf = skb_put(skb, sizeof(*edcf));
+ if (priv->use_short_slot) {
+ edcf->slottime = 9;
+ edcf->sifs = 0x10;
+ edcf->eofpad = 0x00;
+ } else {
+ edcf->slottime = 20;
+ edcf->sifs = 0x0a;
+ edcf->eofpad = 0x06;
+ }
+ /*
+ * calculate the extra round trip delay according to the
+ * formula from 802.11-2007 17.3.8.6.
+ */
+ rtd = 3 * priv->coverage_class;
+ edcf->slottime += rtd;
+ edcf->round_trip_delay = cpu_to_le16(rtd);
+ /* (see prism54/isl_oid.h for further details) */
+ edcf->frameburst = cpu_to_le16(0);
+ edcf->flags = 0;
+ memset(edcf->mapping, 0, sizeof(edcf->mapping));
+ memcpy(edcf->queue, priv->qos_params, sizeof(edcf->queue));
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_set_ps(struct p54_common *priv)
+{
+ struct sk_buff *skb;
+ struct p54_psm *psm;
+ unsigned int i;
+ u16 mode;
+
+ if (priv->hw->conf.flags & IEEE80211_CONF_PS &&
+ !priv->powersave_override)
+ mode = P54_PSM | P54_PSM_BEACON_TIMEOUT | P54_PSM_DTIM |
+ P54_PSM_CHECKSUM | P54_PSM_MCBC;
+ else
+ mode = P54_PSM_CAM;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*psm),
+ P54_CONTROL_TYPE_PSM, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ psm = skb_put(skb, sizeof(*psm));
+ psm->mode = cpu_to_le16(mode);
+ psm->aid = cpu_to_le16(priv->aid);
+ for (i = 0; i < ARRAY_SIZE(psm->intervals); i++) {
+ psm->intervals[i].interval =
+ cpu_to_le16(priv->hw->conf.listen_interval);
+ psm->intervals[i].periods = cpu_to_le16(1);
+ }
+
+ psm->beacon_rssi_skip_max = 200;
+ psm->rssi_delta_threshold = 0;
+ psm->nr = 1;
+ psm->exclude[0] = WLAN_EID_TIM;
+
+ p54_tx(priv, skb);
+ priv->phy_ps = mode != P54_PSM_CAM;
+ return 0;
+}
+
+int p54_init_xbow_synth(struct p54_common *priv)
+{
+ struct sk_buff *skb;
+ struct p54_xbow_synth *xbow;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*xbow),
+ P54_CONTROL_TYPE_XBOW_SYNTH_CFG, GFP_KERNEL);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ xbow = skb_put(skb, sizeof(*xbow));
+ xbow->magic1 = cpu_to_le16(0x1);
+ xbow->magic2 = cpu_to_le16(0x2);
+ xbow->freq = cpu_to_le16(5390);
+ memset(xbow->padding, 0, sizeof(xbow->padding));
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_upload_key(struct p54_common *priv, u8 algo, int slot, u8 idx, u8 len,
+ u8 *addr, u8* key)
+{
+ struct sk_buff *skb;
+ struct p54_keycache *rxkey;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*rxkey),
+ P54_CONTROL_TYPE_RX_KEYCACHE, GFP_KERNEL);
+ if (unlikely(!skb))
+ return -ENOMEM;
+
+ rxkey = skb_put(skb, sizeof(*rxkey));
+ rxkey->entry = slot;
+ rxkey->key_id = idx;
+ rxkey->key_type = algo;
+ if (addr)
+ memcpy(rxkey->mac, addr, ETH_ALEN);
+ else
+ eth_broadcast_addr(rxkey->mac);
+
+ switch (algo) {
+ case P54_CRYPTO_WEP:
+ case P54_CRYPTO_AESCCMP:
+ rxkey->key_len = min_t(u8, 16, len);
+ memcpy(rxkey->key, key, rxkey->key_len);
+ break;
+
+ case P54_CRYPTO_TKIPMICHAEL:
+ rxkey->key_len = 24;
+ memcpy(rxkey->key, key, 16);
+ memcpy(&(rxkey->key[16]), &(key
+ [NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY]), 8);
+ break;
+
+ case P54_CRYPTO_NONE:
+ rxkey->key_len = 0;
+ memset(rxkey->key, 0, sizeof(rxkey->key));
+ break;
+
+ default:
+ wiphy_err(priv->hw->wiphy,
+ "invalid cryptographic algorithm: %d\n", algo);
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_fetch_statistics(struct p54_common *priv)
+{
+ struct ieee80211_tx_info *txinfo;
+ struct p54_tx_info *p54info;
+ struct sk_buff *skb;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL,
+ sizeof(struct p54_statistics),
+ P54_CONTROL_TYPE_STAT_READBACK, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ /*
+ * The statistic feedback causes some extra headaches here, if it
+ * is not to crash/corrupt the firmware data structures.
+ *
+ * Unlike all other Control Get OIDs we can not use helpers like
+ * skb_put to reserve the space for the data we're requesting.
+ * Instead the extra frame length -which will hold the results later-
+ * will only be told to the p54_assign_address, so that following
+ * frames won't be placed into the allegedly empty area.
+ */
+ txinfo = IEEE80211_SKB_CB(skb);
+ p54info = (void *) txinfo->rate_driver_data;
+ p54info->extra_len = sizeof(struct p54_statistics);
+
+ p54_tx(priv, skb);
+ return 0;
+}
+
+int p54_set_groupfilter(struct p54_common *priv)
+{
+ struct p54_group_address_table *grp;
+ struct sk_buff *skb;
+ bool on = false;
+
+ skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*grp),
+ P54_CONTROL_TYPE_GROUP_ADDRESS_TABLE, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ grp = skb_put(skb, sizeof(*grp));
+
+ on = !(priv->filter_flags & FIF_ALLMULTI) &&
+ (priv->mc_maclist_num > 0 &&
+ priv->mc_maclist_num <= MC_FILTER_ADDRESS_NUM);
+
+ if (on) {
+ grp->filter_enable = cpu_to_le16(1);
+ grp->num_address = cpu_to_le16(priv->mc_maclist_num);
+ memcpy(grp->mac_list, priv->mc_maclist, sizeof(grp->mac_list));
+ } else {
+ grp->filter_enable = cpu_to_le16(0);
+ grp->num_address = cpu_to_le16(0);
+ memset(grp->mac_list, 0, sizeof(grp->mac_list));
+ }
+
+ p54_tx(priv, skb);
+ return 0;
+}
diff --git a/drivers/net/wireless/intersil/p54/led.c b/drivers/net/wireless/intersil/p54/led.c
new file mode 100644
index 0000000..51c33c3
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/led.c
@@ -0,0 +1,161 @@
+/*
+ * Common code for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ * Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ * - stlc45xx driver
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+
+#include <net/mac80211.h>
+#ifdef CPTCFG_P54_LEDS
+#include <linux/leds.h>
+#endif /* CPTCFG_P54_LEDS */
+
+#include "p54.h"
+#include "lmac.h"
+
+static void p54_update_leds(struct work_struct *work)
+{
+ struct p54_common *priv = container_of(work, struct p54_common,
+ led_work.work);
+ int err, i, tmp, blink_delay = 400;
+ bool rerun = false;
+
+ /* Don't toggle the LED, when the device is down. */
+ if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
+ return ;
+
+ for (i = 0; i < ARRAY_SIZE(priv->leds); i++)
+ if (priv->leds[i].toggled) {
+ priv->softled_state |= BIT(i);
+
+ tmp = 70 + 200 / (priv->leds[i].toggled);
+ if (tmp < blink_delay)
+ blink_delay = tmp;
+
+ if (priv->leds[i].led_dev.brightness == LED_OFF)
+ rerun = true;
+
+ priv->leds[i].toggled =
+ !!priv->leds[i].led_dev.brightness;
+ } else
+ priv->softled_state &= ~BIT(i);
+
+ err = p54_set_leds(priv);
+ if (err && net_ratelimit())
+ wiphy_err(priv->hw->wiphy,
+ "failed to update LEDs (%d).\n", err);
+
+ if (rerun)
+ ieee80211_queue_delayed_work(priv->hw, &priv->led_work,
+ msecs_to_jiffies(blink_delay));
+}
+
+static void p54_led_brightness_set(struct led_classdev *led_dev,
+ enum led_brightness brightness)
+{
+ struct p54_led_dev *led = container_of(led_dev, struct p54_led_dev,
+ led_dev);
+ struct ieee80211_hw *dev = led->hw_dev;
+ struct p54_common *priv = dev->priv;
+
+ if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
+ return ;
+
+ if ((brightness) && (led->registered)) {
+ led->toggled++;
+ ieee80211_queue_delayed_work(priv->hw, &priv->led_work, HZ/10);
+ }
+}
+
+static int p54_register_led(struct p54_common *priv,
+ unsigned int led_index,
+ char *name, const char *trigger)
+{
+ struct p54_led_dev *led = &priv->leds[led_index];
+ int err;
+
+ if (led->registered)
+ return -EEXIST;
+
+ snprintf(led->name, sizeof(led->name), "p54-%s::%s",
+ wiphy_name(priv->hw->wiphy), name);
+ led->hw_dev = priv->hw;
+ led->index = led_index;
+ led->led_dev.name = led->name;
+ led->led_dev.default_trigger = trigger;
+ led->led_dev.brightness_set = p54_led_brightness_set;
+
+ err = led_classdev_register(wiphy_dev(priv->hw->wiphy), &led->led_dev);
+ if (err)
+ wiphy_err(priv->hw->wiphy,
+ "Failed to register %s LED.\n", name);
+ else
+ led->registered = 1;
+
+ return err;
+}
+
+int p54_init_leds(struct p54_common *priv)
+{
+ int err;
+
+ /*
+ * TODO:
+ * Figure out if the EEPROM contains some hints about the number
+ * of available/programmable LEDs of the device.
+ */
+
+ INIT_DELAYED_WORK(&priv->led_work, p54_update_leds);
+
+ err = p54_register_led(priv, 0, "assoc",
+ ieee80211_get_assoc_led_name(priv->hw));
+ if (err)
+ return err;
+
+ err = p54_register_led(priv, 1, "tx",
+ ieee80211_get_tx_led_name(priv->hw));
+ if (err)
+ return err;
+
+ err = p54_register_led(priv, 2, "rx",
+ ieee80211_get_rx_led_name(priv->hw));
+ if (err)
+ return err;
+
+ err = p54_register_led(priv, 3, "radio",
+ ieee80211_get_radio_led_name(priv->hw));
+ if (err)
+ return err;
+
+ err = p54_set_leds(priv);
+ return err;
+}
+
+void p54_unregister_leds(struct p54_common *priv)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(priv->leds); i++) {
+ if (priv->leds[i].registered) {
+ priv->leds[i].registered = false;
+ priv->leds[i].toggled = 0;
+ led_classdev_unregister(&priv->leds[i].led_dev);
+ }
+ }
+
+ cancel_delayed_work_sync(&priv->led_work);
+}
diff --git a/drivers/net/wireless/intersil/p54/lmac.h b/drivers/net/wireless/intersil/p54/lmac.h
new file mode 100644
index 0000000..de1d46b
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/lmac.h
@@ -0,0 +1,562 @@
+/*
+ * LMAC Interface specific definitions for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007 - 2009, Christian Lamparter <chunkeey@web.de>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ *
+ * - LMAC API interface header file for STLC4560 (lmac_longbow.h)
+ * Copyright (C) 2007 Conexant Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef LMAC_H
+#define LMAC_H
+
+enum p54_control_frame_types {
+ P54_CONTROL_TYPE_SETUP = 0,
+ P54_CONTROL_TYPE_SCAN,
+ P54_CONTROL_TYPE_TRAP,
+ P54_CONTROL_TYPE_DCFINIT,
+ P54_CONTROL_TYPE_RX_KEYCACHE,
+ P54_CONTROL_TYPE_TIM,
+ P54_CONTROL_TYPE_PSM,
+ P54_CONTROL_TYPE_TXCANCEL,
+ P54_CONTROL_TYPE_TXDONE,
+ P54_CONTROL_TYPE_BURST,
+ P54_CONTROL_TYPE_STAT_READBACK,
+ P54_CONTROL_TYPE_BBP,
+ P54_CONTROL_TYPE_EEPROM_READBACK,
+ P54_CONTROL_TYPE_LED,
+ P54_CONTROL_TYPE_GPIO,
+ P54_CONTROL_TYPE_TIMER,
+ P54_CONTROL_TYPE_MODULATION,
+ P54_CONTROL_TYPE_SYNTH_CONFIG,
+ P54_CONTROL_TYPE_DETECTOR_VALUE,
+ P54_CONTROL_TYPE_XBOW_SYNTH_CFG,
+ P54_CONTROL_TYPE_CCE_QUIET,
+ P54_CONTROL_TYPE_PSM_STA_UNLOCK,
+ P54_CONTROL_TYPE_PCS,
+ P54_CONTROL_TYPE_BT_BALANCER = 28,
+ P54_CONTROL_TYPE_GROUP_ADDRESS_TABLE = 30,
+ P54_CONTROL_TYPE_ARPTABLE = 31,
+ P54_CONTROL_TYPE_BT_OPTIONS = 35,
+};
+
+#define P54_HDR_FLAG_CONTROL BIT(15)
+#define P54_HDR_FLAG_CONTROL_OPSET (BIT(15) + BIT(0))
+#define P54_HDR_FLAG_DATA_ALIGN BIT(14)
+
+#define P54_HDR_FLAG_DATA_OUT_PROMISC BIT(0)
+#define P54_HDR_FLAG_DATA_OUT_TIMESTAMP BIT(1)
+#define P54_HDR_FLAG_DATA_OUT_SEQNR BIT(2)
+#define P54_HDR_FLAG_DATA_OUT_BIT3 BIT(3)
+#define P54_HDR_FLAG_DATA_OUT_BURST BIT(4)
+#define P54_HDR_FLAG_DATA_OUT_NOCANCEL BIT(5)
+#define P54_HDR_FLAG_DATA_OUT_CLEARTIM BIT(6)
+#define P54_HDR_FLAG_DATA_OUT_HITCHHIKE BIT(7)
+#define P54_HDR_FLAG_DATA_OUT_COMPRESS BIT(8)
+#define P54_HDR_FLAG_DATA_OUT_CONCAT BIT(9)
+#define P54_HDR_FLAG_DATA_OUT_PCS_ACCEPT BIT(10)
+#define P54_HDR_FLAG_DATA_OUT_WAITEOSP BIT(11)
+
+#define P54_HDR_FLAG_DATA_IN_FCS_GOOD BIT(0)
+#define P54_HDR_FLAG_DATA_IN_MATCH_MAC BIT(1)
+#define P54_HDR_FLAG_DATA_IN_MCBC BIT(2)
+#define P54_HDR_FLAG_DATA_IN_BEACON BIT(3)
+#define P54_HDR_FLAG_DATA_IN_MATCH_BSS BIT(4)
+#define P54_HDR_FLAG_DATA_IN_BCAST_BSS BIT(5)
+#define P54_HDR_FLAG_DATA_IN_DATA BIT(6)
+#define P54_HDR_FLAG_DATA_IN_TRUNCATED BIT(7)
+#define P54_HDR_FLAG_DATA_IN_BIT8 BIT(8)
+#define P54_HDR_FLAG_DATA_IN_TRANSPARENT BIT(9)
+
+struct p54_hdr {
+ __le16 flags;
+ __le16 len;
+ __le32 req_id;
+ __le16 type; /* enum p54_control_frame_types */
+ u8 rts_tries;
+ u8 tries;
+ u8 data[0];
+} __packed;
+
+#define GET_REQ_ID(skb) \
+ (((struct p54_hdr *) ((struct sk_buff *) skb)->data)->req_id) \
+
+#define FREE_AFTER_TX(skb) \
+ ((((struct p54_hdr *) ((struct sk_buff *) skb)->data)-> \
+ flags) == cpu_to_le16(P54_HDR_FLAG_CONTROL_OPSET))
+
+#define IS_DATA_FRAME(skb) \
+ (!((((struct p54_hdr *) ((struct sk_buff *) skb)->data)-> \
+ flags) & cpu_to_le16(P54_HDR_FLAG_CONTROL)))
+
+#define GET_HW_QUEUE(skb) \
+ (((struct p54_tx_data *)((struct p54_hdr *) \
+ skb->data)->data)->hw_queue)
+
+/*
+ * shared interface ID definitions
+ * The interface ID is a unique identification of a specific interface.
+ * The following values are reserved: 0x0000, 0x0002, 0x0012, 0x0014, 0x0015
+ */
+#define IF_ID_ISL36356A 0x0001 /* ISL36356A <-> Firmware */
+#define IF_ID_MVC 0x0003 /* MAC Virtual Coprocessor */
+#define IF_ID_DEBUG 0x0008 /* PolDebug Interface */
+#define IF_ID_PRODUCT 0x0009
+#define IF_ID_OEM 0x000a
+#define IF_ID_PCI3877 0x000b /* 3877 <-> Host PCI */
+#define IF_ID_ISL37704C 0x000c /* ISL37704C <-> Fw */
+#define IF_ID_ISL39000 0x000f /* ISL39000 <-> Fw */
+#define IF_ID_ISL39300A 0x0010 /* ISL39300A <-> Fw */
+#define IF_ID_ISL37700_UAP 0x0016 /* ISL37700 uAP Fw <-> Fw */
+#define IF_ID_ISL39000_UAP 0x0017 /* ISL39000 uAP Fw <-> Fw */
+#define IF_ID_LMAC 0x001a /* Interface exposed by LMAC */
+
+struct exp_if {
+ __le16 role;
+ __le16 if_id;
+ __le16 variant;
+ __le16 btm_compat;
+ __le16 top_compat;
+} __packed;
+
+struct dep_if {
+ __le16 role;
+ __le16 if_id;
+ __le16 variant;
+} __packed;
+
+/* driver <-> lmac definitions */
+struct p54_eeprom_lm86 {
+ union {
+ struct {
+ __le16 offset;
+ __le16 len;
+ u8 data[0];
+ } __packed v1;
+ struct {
+ __le32 offset;
+ __le16 len;
+ u8 magic2;
+ u8 pad;
+ u8 magic[4];
+ u8 data[0];
+ } __packed v2;
+ } __packed;
+} __packed;
+
+enum p54_rx_decrypt_status {
+ P54_DECRYPT_NONE = 0,
+ P54_DECRYPT_OK,
+ P54_DECRYPT_NOKEY,
+ P54_DECRYPT_NOMICHAEL,
+ P54_DECRYPT_NOCKIPMIC,
+ P54_DECRYPT_FAIL_WEP,
+ P54_DECRYPT_FAIL_TKIP,
+ P54_DECRYPT_FAIL_MICHAEL,
+ P54_DECRYPT_FAIL_CKIPKP,
+ P54_DECRYPT_FAIL_CKIPMIC,
+ P54_DECRYPT_FAIL_AESCCMP
+};
+
+struct p54_rx_data {
+ __le16 flags;
+ __le16 len;
+ __le16 freq;
+ u8 antenna;
+ u8 rate;
+ u8 rssi;
+ u8 quality;
+ u8 decrypt_status;
+ u8 rssi_raw;
+ __le32 tsf32;
+ __le32 unalloc0;
+ u8 align[0];
+} __packed;
+
+enum p54_trap_type {
+ P54_TRAP_SCAN = 0,
+ P54_TRAP_TIMER,
+ P54_TRAP_BEACON_TX,
+ P54_TRAP_FAA_RADIO_ON,
+ P54_TRAP_FAA_RADIO_OFF,
+ P54_TRAP_RADAR,
+ P54_TRAP_NO_BEACON,
+ P54_TRAP_TBTT,
+ P54_TRAP_SCO_ENTER,
+ P54_TRAP_SCO_EXIT
+};
+
+struct p54_trap {
+ __le16 event;
+ __le16 frequency;
+} __packed;
+
+enum p54_frame_sent_status {
+ P54_TX_OK = 0,
+ P54_TX_FAILED,
+ P54_TX_PSM,
+ P54_TX_PSM_CANCELLED = 4
+};
+
+struct p54_frame_sent {
+ u8 status;
+ u8 tries;
+ u8 ack_rssi;
+ u8 quality;
+ __le16 seq;
+ u8 antenna;
+ u8 padding;
+} __packed;
+
+enum p54_tx_data_crypt {
+ P54_CRYPTO_NONE = 0,
+ P54_CRYPTO_WEP,
+ P54_CRYPTO_TKIP,
+ P54_CRYPTO_TKIPMICHAEL,
+ P54_CRYPTO_CCX_WEPMIC,
+ P54_CRYPTO_CCX_KPMIC,
+ P54_CRYPTO_CCX_KP,
+ P54_CRYPTO_AESCCMP
+};
+
+enum p54_tx_data_queue {
+ P54_QUEUE_BEACON = 0,
+ P54_QUEUE_FWSCAN = 1,
+ P54_QUEUE_MGMT = 2,
+ P54_QUEUE_CAB = 3,
+ P54_QUEUE_DATA = 4,
+
+ P54_QUEUE_AC_NUM = 4,
+ P54_QUEUE_AC_VO = 4,
+ P54_QUEUE_AC_VI = 5,
+ P54_QUEUE_AC_BE = 6,
+ P54_QUEUE_AC_BK = 7,
+
+ /* keep last */
+ P54_QUEUE_NUM = 8,
+};
+
+#define IS_QOS_QUEUE(n) (n >= P54_QUEUE_DATA)
+
+struct p54_tx_data {
+ u8 rateset[8];
+ u8 rts_rate_idx;
+ u8 crypt_offset;
+ u8 key_type;
+ u8 key_len;
+ u8 key[16];
+ u8 hw_queue;
+ u8 backlog;
+ __le16 durations[4];
+ u8 tx_antenna;
+ union {
+ struct {
+ u8 cts_rate;
+ __le16 output_power;
+ } __packed longbow;
+ struct {
+ u8 output_power;
+ u8 cts_rate;
+ u8 unalloc;
+ } __packed normal;
+ } __packed;
+ u8 unalloc2[2];
+ u8 align[0];
+} __packed;
+
+/* unit is ms */
+#define P54_TX_FRAME_LIFETIME 2000
+#define P54_TX_TIMEOUT 4000
+#define P54_STATISTICS_UPDATE 5000
+
+#define P54_FILTER_TYPE_NONE 0
+#define P54_FILTER_TYPE_STATION BIT(0)
+#define P54_FILTER_TYPE_IBSS BIT(1)
+#define P54_FILTER_TYPE_AP BIT(2)
+#define P54_FILTER_TYPE_TRANSPARENT BIT(3)
+#define P54_FILTER_TYPE_PROMISCUOUS BIT(4)
+#define P54_FILTER_TYPE_HIBERNATE BIT(5)
+#define P54_FILTER_TYPE_NOACK BIT(6)
+#define P54_FILTER_TYPE_RX_DISABLED BIT(7)
+
+struct p54_setup_mac {
+ __le16 mac_mode;
+ u8 mac_addr[ETH_ALEN];
+ u8 bssid[ETH_ALEN];
+ u8 rx_antenna;
+ u8 rx_align;
+ union {
+ struct {
+ __le32 basic_rate_mask;
+ u8 rts_rates[8];
+ __le32 rx_addr;
+ __le16 max_rx;
+ __le16 rxhw;
+ __le16 wakeup_timer;
+ __le16 unalloc0;
+ } __packed v1;
+ struct {
+ __le32 rx_addr;
+ __le16 max_rx;
+ __le16 rxhw;
+ __le16 timer;
+ __le16 truncate;
+ __le32 basic_rate_mask;
+ u8 sbss_offset;
+ u8 mcast_window;
+ u8 rx_rssi_threshold;
+ u8 rx_ed_threshold;
+ __le32 ref_clock;
+ __le16 lpf_bandwidth;
+ __le16 osc_start_delay;
+ } __packed v2;
+ } __packed;
+} __packed;
+
+#define P54_SETUP_V1_LEN 40
+#define P54_SETUP_V2_LEN (sizeof(struct p54_setup_mac))
+
+#define P54_SCAN_EXIT BIT(0)
+#define P54_SCAN_TRAP BIT(1)
+#define P54_SCAN_ACTIVE BIT(2)
+#define P54_SCAN_FILTER BIT(3)
+
+struct p54_scan_head {
+ __le16 mode;
+ __le16 dwell;
+ u8 scan_params[20];
+ __le16 freq;
+} __packed;
+
+struct p54_pa_curve_data_sample {
+ u8 rf_power;
+ u8 pa_detector;
+ u8 data_barker;
+ u8 data_bpsk;
+ u8 data_qpsk;
+ u8 data_16qam;
+ u8 data_64qam;
+ u8 padding;
+} __packed;
+
+struct p54_scan_body {
+ u8 pa_points_per_curve;
+ u8 val_barker;
+ u8 val_bpsk;
+ u8 val_qpsk;
+ u8 val_16qam;
+ u8 val_64qam;
+ struct p54_pa_curve_data_sample curve_data[8];
+ u8 dup_bpsk;
+ u8 dup_qpsk;
+ u8 dup_16qam;
+ u8 dup_64qam;
+} __packed;
+
+/*
+ * Warning: Longbow's structures are bogus.
+ */
+struct p54_channel_output_limit_longbow {
+ __le16 rf_power_points[12];
+} __packed;
+
+struct p54_pa_curve_data_sample_longbow {
+ __le16 rf_power;
+ __le16 pa_detector;
+ struct {
+ __le16 data[4];
+ } points[3] __packed;
+} __packed;
+
+struct p54_scan_body_longbow {
+ struct p54_channel_output_limit_longbow power_limits;
+ struct p54_pa_curve_data_sample_longbow curve_data[8];
+ __le16 unkn[6]; /* maybe more power_limits or rate_mask */
+} __packed;
+
+union p54_scan_body_union {
+ struct p54_scan_body normal;
+ struct p54_scan_body_longbow longbow;
+} __packed;
+
+struct p54_scan_tail_rate {
+ __le32 basic_rate_mask;
+ u8 rts_rates[8];
+} __packed;
+
+struct p54_led {
+ __le16 flags;
+ __le16 mask[2];
+ __le16 delay[2];
+} __packed;
+
+struct p54_edcf {
+ u8 flags;
+ u8 slottime;
+ u8 sifs;
+ u8 eofpad;
+ struct p54_edcf_queue_param queue[8];
+ u8 mapping[4];
+ __le16 frameburst;
+ __le16 round_trip_delay;
+} __packed;
+
+struct p54_statistics {
+ __le32 rx_success;
+ __le32 rx_bad_fcs;
+ __le32 rx_abort;
+ __le32 rx_abort_phy;
+ __le32 rts_success;
+ __le32 rts_fail;
+ __le32 tsf32;
+ __le32 airtime;
+ __le32 noise;
+ __le32 sample_noise[8];
+ __le32 sample_cca;
+ __le32 sample_tx;
+} __packed;
+
+struct p54_xbow_synth {
+ __le16 magic1;
+ __le16 magic2;
+ __le16 freq;
+ u32 padding[5];
+} __packed;
+
+struct p54_timer {
+ __le32 interval;
+} __packed;
+
+struct p54_keycache {
+ u8 entry;
+ u8 key_id;
+ u8 mac[ETH_ALEN];
+ u8 padding[2];
+ u8 key_type;
+ u8 key_len;
+ u8 key[24];
+} __packed;
+
+struct p54_burst {
+ u8 flags;
+ u8 queue;
+ u8 backlog;
+ u8 pad;
+ __le16 durations[32];
+} __packed;
+
+struct p54_psm_interval {
+ __le16 interval;
+ __le16 periods;
+} __packed;
+
+#define P54_PSM_CAM 0
+#define P54_PSM BIT(0)
+#define P54_PSM_DTIM BIT(1)
+#define P54_PSM_MCBC BIT(2)
+#define P54_PSM_CHECKSUM BIT(3)
+#define P54_PSM_SKIP_MORE_DATA BIT(4)
+#define P54_PSM_BEACON_TIMEOUT BIT(5)
+#define P54_PSM_HFOSLEEP BIT(6)
+#define P54_PSM_AUTOSWITCH_SLEEP BIT(7)
+#define P54_PSM_LPIT BIT(8)
+#define P54_PSM_BF_UCAST_SKIP BIT(9)
+#define P54_PSM_BF_MCAST_SKIP BIT(10)
+
+struct p54_psm {
+ __le16 mode;
+ __le16 aid;
+ struct p54_psm_interval intervals[4];
+ u8 beacon_rssi_skip_max;
+ u8 rssi_delta_threshold;
+ u8 nr;
+ u8 exclude[1];
+} __packed;
+
+#define MC_FILTER_ADDRESS_NUM 4
+
+struct p54_group_address_table {
+ __le16 filter_enable;
+ __le16 num_address;
+ u8 mac_list[MC_FILTER_ADDRESS_NUM][ETH_ALEN];
+} __packed;
+
+struct p54_txcancel {
+ __le32 req_id;
+} __packed;
+
+struct p54_sta_unlock {
+ u8 addr[ETH_ALEN];
+ u16 padding;
+} __packed;
+
+#define P54_TIM_CLEAR BIT(15)
+struct p54_tim {
+ u8 count;
+ u8 padding[3];
+ __le16 entry[8];
+} __packed;
+
+struct p54_cce_quiet {
+ __le32 period;
+} __packed;
+
+struct p54_bt_balancer {
+ __le16 prio_thresh;
+ __le16 acl_thresh;
+} __packed;
+
+struct p54_arp_table {
+ __le16 filter_enable;
+ u8 ipv4_addr[4];
+} __packed;
+
+/* LED control */
+int p54_set_leds(struct p54_common *priv);
+int p54_init_leds(struct p54_common *priv);
+void p54_unregister_leds(struct p54_common *priv);
+
+/* xmit functions */
+void p54_tx_80211(struct ieee80211_hw *dev,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb);
+int p54_tx_cancel(struct p54_common *priv, __le32 req_id);
+void p54_tx(struct p54_common *priv, struct sk_buff *skb);
+
+/* synth/phy configuration */
+int p54_init_xbow_synth(struct p54_common *priv);
+int p54_scan(struct p54_common *priv, u16 mode, u16 dwell);
+
+/* MAC */
+int p54_sta_unlock(struct p54_common *priv, u8 *addr);
+int p54_update_beacon_tim(struct p54_common *priv, u16 aid, bool set);
+int p54_setup_mac(struct p54_common *priv);
+int p54_set_ps(struct p54_common *priv);
+int p54_fetch_statistics(struct p54_common *priv);
+int p54_set_groupfilter(struct p54_common *priv);
+
+/* e/v DCF setup */
+int p54_set_edcf(struct p54_common *priv);
+
+/* cryptographic engine */
+int p54_upload_key(struct p54_common *priv, u8 algo, int slot,
+ u8 idx, u8 len, u8 *addr, u8* key);
+
+/* eeprom */
+int p54_download_eeprom(struct p54_common *priv, void *buf,
+ u16 offset, u16 len);
+struct p54_rssi_db_entry *p54_rssi_find(struct p54_common *p, const u16 freq);
+
+/* utility */
+u8 *p54_find_ie(struct sk_buff *skb, u8 ie);
+
+#endif /* LMAC_H */
diff --git a/drivers/net/wireless/intersil/p54/main.c b/drivers/net/wireless/intersil/p54/main.c
new file mode 100644
index 0000000..5ae3e7d
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/main.c
@@ -0,0 +1,867 @@
+/*
+ * mac80211 glue code for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ * Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ * - stlc45xx driver
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+
+#include <net/mac80211.h>
+
+#include "p54.h"
+#include "lmac.h"
+
+static bool modparam_nohwcrypt;
+module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO);
+MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption.");
+MODULE_AUTHOR("Michael Wu <flamingice@sourmilk.net>");
+MODULE_DESCRIPTION("Softmac Prism54 common code");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("prism54common");
+
+static int p54_sta_add_remove(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct p54_common *priv = hw->priv;
+
+ /*
+ * Notify the firmware that we don't want or we don't
+ * need to buffer frames for this station anymore.
+ */
+
+ p54_sta_unlock(priv, sta->addr);
+
+ return 0;
+}
+
+static void p54_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
+ enum sta_notify_cmd notify_cmd,
+ struct ieee80211_sta *sta)
+{
+ struct p54_common *priv = dev->priv;
+
+ switch (notify_cmd) {
+ case STA_NOTIFY_AWAKE:
+ /* update the firmware's filter table */
+ p54_sta_unlock(priv, sta->addr);
+ break;
+ default:
+ break;
+ }
+}
+
+static int p54_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
+ bool set)
+{
+ struct p54_common *priv = dev->priv;
+
+ return p54_update_beacon_tim(priv, sta->aid, set);
+}
+
+u8 *p54_find_ie(struct sk_buff *skb, u8 ie)
+{
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+ u8 *pos, *end;
+
+ if (skb->len <= sizeof(mgmt))
+ return NULL;
+
+ pos = (u8 *)mgmt->u.beacon.variable;
+ end = skb->data + skb->len;
+ while (pos < end) {
+ if (pos + 2 + pos[1] > end)
+ return NULL;
+
+ if (pos[0] == ie)
+ return pos;
+
+ pos += 2 + pos[1];
+ }
+ return NULL;
+}
+
+static int p54_beacon_format_ie_tim(struct sk_buff *skb)
+{
+ /*
+ * the good excuse for this mess is ... the firmware.
+ * The dummy TIM MUST be at the end of the beacon frame,
+ * because it'll be overwritten!
+ */
+ u8 *tim;
+ u8 dtim_len;
+ u8 dtim_period;
+ u8 *next;
+
+ tim = p54_find_ie(skb, WLAN_EID_TIM);
+ if (!tim)
+ return 0;
+
+ dtim_len = tim[1];
+ dtim_period = tim[3];
+ next = tim + 2 + dtim_len;
+
+ if (dtim_len < 3)
+ return -EINVAL;
+
+ memmove(tim, next, skb_tail_pointer(skb) - next);
+ tim = skb_tail_pointer(skb) - (dtim_len + 2);
+
+ /* add the dummy at the end */
+ tim[0] = WLAN_EID_TIM;
+ tim[1] = 3;
+ tim[2] = 0;
+ tim[3] = dtim_period;
+ tim[4] = 0;
+
+ if (dtim_len > 3)
+ skb_trim(skb, skb->len - (dtim_len - 3));
+
+ return 0;
+}
+
+static int p54_beacon_update(struct p54_common *priv,
+ struct ieee80211_vif *vif)
+{
+ struct ieee80211_tx_control control = { };
+ struct sk_buff *beacon;
+ int ret;
+
+ beacon = ieee80211_beacon_get(priv->hw, vif);
+ if (!beacon)
+ return -ENOMEM;
+ ret = p54_beacon_format_ie_tim(beacon);
+ if (ret)
+ return ret;
+
+ /*
+ * During operation, the firmware takes care of beaconing.
+ * The driver only needs to upload a new beacon template, once
+ * the template was changed by the stack or userspace.
+ *
+ * LMAC API 3.2.2 also specifies that the driver does not need
+ * to cancel the old beacon template by hand, instead the firmware
+ * will release the previous one through the feedback mechanism.
+ */
+ p54_tx_80211(priv->hw, &control, beacon);
+ priv->tsf_high32 = 0;
+ priv->tsf_low32 = 0;
+
+ return 0;
+}
+
+static int p54_start(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+ int err;
+
+ mutex_lock(&priv->conf_mutex);
+ err = priv->open(dev);
+ if (err)
+ goto out;
+ P54_SET_QUEUE(priv->qos_params[0], 0x0002, 0x0003, 0x0007, 47);
+ P54_SET_QUEUE(priv->qos_params[1], 0x0002, 0x0007, 0x000f, 94);
+ P54_SET_QUEUE(priv->qos_params[2], 0x0003, 0x000f, 0x03ff, 0);
+ P54_SET_QUEUE(priv->qos_params[3], 0x0007, 0x000f, 0x03ff, 0);
+ err = p54_set_edcf(priv);
+ if (err)
+ goto out;
+
+ eth_broadcast_addr(priv->bssid);
+ priv->mode = NL80211_IFTYPE_MONITOR;
+ err = p54_setup_mac(priv);
+ if (err) {
+ priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+ goto out;
+ }
+
+ ieee80211_queue_delayed_work(dev, &priv->work, 0);
+
+ priv->softled_state = 0;
+ err = p54_set_leds(priv);
+
+out:
+ mutex_unlock(&priv->conf_mutex);
+ return err;
+}
+
+static void p54_stop(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+ int i;
+
+ priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+ priv->softled_state = 0;
+ cancel_delayed_work_sync(&priv->work);
+ mutex_lock(&priv->conf_mutex);
+ p54_set_leds(priv);
+ priv->stop(dev);
+ skb_queue_purge(&priv->tx_pending);
+ skb_queue_purge(&priv->tx_queue);
+ for (i = 0; i < P54_QUEUE_NUM; i++) {
+ priv->tx_stats[i].count = 0;
+ priv->tx_stats[i].len = 0;
+ }
+
+ priv->beacon_req_id = cpu_to_le32(0);
+ priv->tsf_high32 = priv->tsf_low32 = 0;
+ mutex_unlock(&priv->conf_mutex);
+}
+
+static int p54_add_interface(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif)
+{
+ struct p54_common *priv = dev->priv;
+ int err;
+
+ vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER;
+
+ mutex_lock(&priv->conf_mutex);
+ if (priv->mode != NL80211_IFTYPE_MONITOR) {
+ mutex_unlock(&priv->conf_mutex);
+ return -EOPNOTSUPP;
+ }
+
+ priv->vif = vif;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_MESH_POINT:
+ priv->mode = vif->type;
+ break;
+ default:
+ mutex_unlock(&priv->conf_mutex);
+ return -EOPNOTSUPP;
+ }
+
+ memcpy(priv->mac_addr, vif->addr, ETH_ALEN);
+ err = p54_setup_mac(priv);
+ mutex_unlock(&priv->conf_mutex);
+ return err;
+}
+
+static void p54_remove_interface(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif)
+{
+ struct p54_common *priv = dev->priv;
+
+ mutex_lock(&priv->conf_mutex);
+ priv->vif = NULL;
+
+ /*
+ * LMAC API 3.2.2 states that any active beacon template must be
+ * canceled by the driver before attempting a mode transition.
+ */
+ if (le32_to_cpu(priv->beacon_req_id) != 0) {
+ p54_tx_cancel(priv, priv->beacon_req_id);
+ wait_for_completion_interruptible_timeout(&priv->beacon_comp, HZ);
+ }
+ priv->mode = NL80211_IFTYPE_MONITOR;
+ eth_zero_addr(priv->mac_addr);
+ eth_zero_addr(priv->bssid);
+ p54_setup_mac(priv);
+ mutex_unlock(&priv->conf_mutex);
+}
+
+static int p54_wait_for_stats(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+ int ret;
+
+ priv->update_stats = true;
+ ret = p54_fetch_statistics(priv);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->stat_comp, HZ);
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static void p54_reset_stats(struct p54_common *priv)
+{
+ struct ieee80211_channel *chan = priv->curchan;
+
+ if (chan) {
+ struct survey_info *info = &priv->survey[chan->hw_value];
+
+ /* only reset channel statistics, don't touch .filled, etc. */
+ info->time = 0;
+ info->time_busy = 0;
+ info->time_tx = 0;
+ }
+
+ priv->update_stats = true;
+ priv->survey_raw.active = 0;
+ priv->survey_raw.cca = 0;
+ priv->survey_raw.tx = 0;
+}
+
+static int p54_config(struct ieee80211_hw *dev, u32 changed)
+{
+ int ret = 0;
+ struct p54_common *priv = dev->priv;
+ struct ieee80211_conf *conf = &dev->conf;
+
+ mutex_lock(&priv->conf_mutex);
+ if (changed & IEEE80211_CONF_CHANGE_POWER)
+ priv->output_power = conf->power_level << 2;
+ if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
+ struct ieee80211_channel *oldchan;
+ WARN_ON(p54_wait_for_stats(dev));
+ oldchan = priv->curchan;
+ priv->curchan = NULL;
+ ret = p54_scan(priv, P54_SCAN_EXIT, 0);
+ if (ret) {
+ priv->curchan = oldchan;
+ goto out;
+ }
+ /*
+ * TODO: Use the LM_SCAN_TRAP to determine the current
+ * operating channel.
+ */
+ priv->curchan = priv->hw->conf.chandef.chan;
+ p54_reset_stats(priv);
+ WARN_ON(p54_fetch_statistics(priv));
+ }
+ if (changed & IEEE80211_CONF_CHANGE_PS) {
+ WARN_ON(p54_wait_for_stats(dev));
+ ret = p54_set_ps(priv);
+ if (ret)
+ goto out;
+ WARN_ON(p54_wait_for_stats(dev));
+ }
+ if (changed & IEEE80211_CONF_CHANGE_IDLE) {
+ WARN_ON(p54_wait_for_stats(dev));
+ ret = p54_setup_mac(priv);
+ if (ret)
+ goto out;
+ WARN_ON(p54_wait_for_stats(dev));
+ }
+
+out:
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+static u64 p54_prepare_multicast(struct ieee80211_hw *dev,
+ struct netdev_hw_addr_list *mc_list)
+{
+ struct p54_common *priv = dev->priv;
+ struct netdev_hw_addr *ha;
+ int i;
+
+ BUILD_BUG_ON(ARRAY_SIZE(priv->mc_maclist) !=
+ ARRAY_SIZE(((struct p54_group_address_table *)NULL)->mac_list));
+ /*
+ * The first entry is reserved for the global broadcast MAC.
+ * Otherwise the firmware will drop it and ARP will no longer work.
+ */
+ i = 1;
+ priv->mc_maclist_num = netdev_hw_addr_list_count(mc_list) + i;
+ netdev_hw_addr_list_for_each(ha, mc_list) {
+ memcpy(&priv->mc_maclist[i], ha->addr, ETH_ALEN);
+ i++;
+ if (i >= ARRAY_SIZE(priv->mc_maclist))
+ break;
+ }
+
+ return 1; /* update */
+}
+
+static void p54_configure_filter(struct ieee80211_hw *dev,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 multicast)
+{
+ struct p54_common *priv = dev->priv;
+
+ *total_flags &= FIF_ALLMULTI | FIF_OTHER_BSS;
+
+ priv->filter_flags = *total_flags;
+
+ if (changed_flags & FIF_OTHER_BSS)
+ p54_setup_mac(priv);
+
+ if (changed_flags & FIF_ALLMULTI || multicast)
+ p54_set_groupfilter(priv);
+}
+
+static int p54_conf_tx(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif, u16 queue,
+ const struct ieee80211_tx_queue_params *params)
+{
+ struct p54_common *priv = dev->priv;
+ int ret;
+
+ mutex_lock(&priv->conf_mutex);
+ if (queue < dev->queues) {
+ P54_SET_QUEUE(priv->qos_params[queue], params->aifs,
+ params->cw_min, params->cw_max, params->txop);
+ ret = p54_set_edcf(priv);
+ } else
+ ret = -EINVAL;
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+static void p54_work(struct work_struct *work)
+{
+ struct p54_common *priv = container_of(work, struct p54_common,
+ work.work);
+
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED))
+ return ;
+
+ /*
+ * TODO: walk through tx_queue and do the following tasks
+ * 1. initiate bursts.
+ * 2. cancel stuck frames / reset the device if necessary.
+ */
+
+ mutex_lock(&priv->conf_mutex);
+ WARN_ON_ONCE(p54_fetch_statistics(priv));
+ mutex_unlock(&priv->conf_mutex);
+}
+
+static int p54_get_stats(struct ieee80211_hw *dev,
+ struct ieee80211_low_level_stats *stats)
+{
+ struct p54_common *priv = dev->priv;
+
+ memcpy(stats, &priv->stats, sizeof(*stats));
+ return 0;
+}
+
+static void p54_bss_info_changed(struct ieee80211_hw *dev,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ struct p54_common *priv = dev->priv;
+
+ mutex_lock(&priv->conf_mutex);
+ if (changed & BSS_CHANGED_BSSID) {
+ memcpy(priv->bssid, info->bssid, ETH_ALEN);
+ p54_setup_mac(priv);
+ }
+
+ if (changed & BSS_CHANGED_BEACON) {
+ p54_scan(priv, P54_SCAN_EXIT, 0);
+ p54_setup_mac(priv);
+ p54_beacon_update(priv, vif);
+ p54_set_edcf(priv);
+ }
+
+ if (changed & (BSS_CHANGED_ERP_SLOT | BSS_CHANGED_BEACON)) {
+ priv->use_short_slot = info->use_short_slot;
+ p54_set_edcf(priv);
+ }
+ if (changed & BSS_CHANGED_BASIC_RATES) {
+ if (dev->conf.chandef.chan->band == NL80211_BAND_5GHZ)
+ priv->basic_rate_mask = (info->basic_rates << 4);
+ else
+ priv->basic_rate_mask = info->basic_rates;
+ p54_setup_mac(priv);
+ if (priv->fw_var >= 0x500)
+ p54_scan(priv, P54_SCAN_EXIT, 0);
+ }
+ if (changed & BSS_CHANGED_ASSOC) {
+ if (info->assoc) {
+ priv->aid = info->aid;
+ priv->wakeup_timer = info->beacon_int *
+ info->dtim_period * 5;
+ p54_setup_mac(priv);
+ } else {
+ priv->wakeup_timer = 500;
+ priv->aid = 0;
+ }
+ }
+
+ mutex_unlock(&priv->conf_mutex);
+}
+
+static int p54_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
+ struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key)
+{
+ struct p54_common *priv = dev->priv;
+ int slot, ret = 0;
+ u8 algo = 0;
+ u8 *addr = NULL;
+
+ if (modparam_nohwcrypt)
+ return -EOPNOTSUPP;
+
+ if (key->flags & IEEE80211_KEY_FLAG_RX_MGMT) {
+ /*
+ * Unfortunately most/all firmwares are trying to decrypt
+ * incoming management frames if a suitable key can be found.
+ * However, in doing so the data in these frames gets
+ * corrupted. So, we can't have firmware supported crypto
+ * offload in this case.
+ */
+ return -EOPNOTSUPP;
+ }
+
+ mutex_lock(&priv->conf_mutex);
+ if (cmd == SET_KEY) {
+ switch (key->cipher) {
+ case WLAN_CIPHER_SUITE_TKIP:
+ if (!(priv->privacy_caps & (BR_DESC_PRIV_CAP_MICHAEL |
+ BR_DESC_PRIV_CAP_TKIP))) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+ key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
+ algo = P54_CRYPTO_TKIPMICHAEL;
+ break;
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ if (!(priv->privacy_caps & BR_DESC_PRIV_CAP_WEP)) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+ key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
+ algo = P54_CRYPTO_WEP;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ if (!(priv->privacy_caps & BR_DESC_PRIV_CAP_AESCCMP)) {
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+ key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
+ algo = P54_CRYPTO_AESCCMP;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+ slot = bitmap_find_free_region(priv->used_rxkeys,
+ priv->rx_keycache_size, 0);
+
+ if (slot < 0) {
+ /*
+ * The device supports the chosen algorithm, but the
+ * firmware does not provide enough key slots to store
+ * all of them.
+ * But encryption offload for outgoing frames is always
+ * possible, so we just pretend that the upload was
+ * successful and do the decryption in software.
+ */
+
+ /* mark the key as invalid. */
+ key->hw_key_idx = 0xff;
+ goto out_unlock;
+ }
+
+ key->flags |= IEEE80211_KEY_FLAG_RESERVE_TAILROOM;
+ } else {
+ slot = key->hw_key_idx;
+
+ if (slot == 0xff) {
+ /* This key was not uploaded into the rx key cache. */
+
+ goto out_unlock;
+ }
+
+ bitmap_release_region(priv->used_rxkeys, slot, 0);
+ algo = 0;
+ }
+
+ if (sta)
+ addr = sta->addr;
+
+ ret = p54_upload_key(priv, algo, slot, key->keyidx,
+ key->keylen, addr, key->key);
+ if (ret) {
+ bitmap_release_region(priv->used_rxkeys, slot, 0);
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ key->hw_key_idx = slot;
+
+out_unlock:
+ mutex_unlock(&priv->conf_mutex);
+ return ret;
+}
+
+static int p54_get_survey(struct ieee80211_hw *dev, int idx,
+ struct survey_info *survey)
+{
+ struct p54_common *priv = dev->priv;
+ struct ieee80211_channel *chan;
+ int err, tries;
+ bool in_use = false;
+
+ if (idx >= priv->chan_num)
+ return -ENOENT;
+
+#define MAX_TRIES 1
+ for (tries = 0; tries < MAX_TRIES; tries++) {
+ chan = priv->curchan;
+ if (chan && chan->hw_value == idx) {
+ mutex_lock(&priv->conf_mutex);
+ err = p54_wait_for_stats(dev);
+ mutex_unlock(&priv->conf_mutex);
+ if (err)
+ return err;
+
+ in_use = true;
+ }
+
+ memcpy(survey, &priv->survey[idx], sizeof(*survey));
+
+ if (in_use) {
+ /* test if the reported statistics are valid. */
+ if (survey->time != 0) {
+ survey->filled |= SURVEY_INFO_IN_USE;
+ } else {
+ /*
+ * hw/fw has not accumulated enough sample sets.
+ * Wait for 100ms, this ought to be enough to
+ * to get at least one non-null set of channel
+ * usage statistics.
+ */
+ msleep(100);
+ continue;
+ }
+ }
+ return 0;
+ }
+ return -ETIMEDOUT;
+#undef MAX_TRIES
+}
+
+static unsigned int p54_flush_count(struct p54_common *priv)
+{
+ unsigned int total = 0, i;
+
+ BUILD_BUG_ON(P54_QUEUE_NUM > ARRAY_SIZE(priv->tx_stats));
+
+ /*
+ * Because the firmware has the sole control over any frames
+ * in the P54_QUEUE_BEACON or P54_QUEUE_SCAN queues, they
+ * don't really count as pending or active.
+ */
+ for (i = P54_QUEUE_MGMT; i < P54_QUEUE_NUM; i++)
+ total += priv->tx_stats[i].len;
+ return total;
+}
+
+static void p54_flush(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
+ u32 queues, bool drop)
+{
+ struct p54_common *priv = dev->priv;
+ unsigned int total, i;
+
+ /*
+ * Currently, it wouldn't really matter if we wait for one second
+ * or 15 minutes. But once someone gets around and completes the
+ * TODOs [ancel stuck frames / reset device] in p54_work, it will
+ * suddenly make sense to wait that long.
+ */
+ i = P54_STATISTICS_UPDATE * 2 / 20;
+
+ /*
+ * In this case no locking is required because as we speak the
+ * queues have already been stopped and no new frames can sneak
+ * up from behind.
+ */
+ while ((total = p54_flush_count(priv) && i--)) {
+ /* waste time */
+ msleep(20);
+ }
+
+ WARN(total, "tx flush timeout, unresponsive firmware");
+}
+
+static void p54_set_coverage_class(struct ieee80211_hw *dev,
+ s16 coverage_class)
+{
+ struct p54_common *priv = dev->priv;
+
+ mutex_lock(&priv->conf_mutex);
+ /* support all coverage class values as in 802.11-2007 Table 7-27 */
+ priv->coverage_class = clamp_t(u8, coverage_class, 0, 31);
+ p54_set_edcf(priv);
+ mutex_unlock(&priv->conf_mutex);
+}
+
+static const struct ieee80211_ops p54_ops = {
+ .tx = p54_tx_80211,
+ .start = p54_start,
+ .stop = p54_stop,
+ .add_interface = p54_add_interface,
+ .remove_interface = p54_remove_interface,
+ .set_tim = p54_set_tim,
+ .sta_notify = p54_sta_notify,
+ .sta_add = p54_sta_add_remove,
+ .sta_remove = p54_sta_add_remove,
+ .set_key = p54_set_key,
+ .config = p54_config,
+ .flush = p54_flush,
+ .bss_info_changed = p54_bss_info_changed,
+ .prepare_multicast = p54_prepare_multicast,
+ .configure_filter = p54_configure_filter,
+ .conf_tx = p54_conf_tx,
+ .get_stats = p54_get_stats,
+ .get_survey = p54_get_survey,
+ .set_coverage_class = p54_set_coverage_class,
+};
+
+struct ieee80211_hw *p54_init_common(size_t priv_data_len)
+{
+ struct ieee80211_hw *dev;
+ struct p54_common *priv;
+
+ dev = ieee80211_alloc_hw(priv_data_len, &p54_ops);
+ if (!dev)
+ return NULL;
+
+ priv = dev->priv;
+ priv->hw = dev;
+ priv->mode = NL80211_IFTYPE_UNSPECIFIED;
+ priv->basic_rate_mask = 0x15f;
+ spin_lock_init(&priv->tx_stats_lock);
+ skb_queue_head_init(&priv->tx_queue);
+ skb_queue_head_init(&priv->tx_pending);
+ ieee80211_hw_set(dev, REPORTS_TX_ACK_STATUS);
+ ieee80211_hw_set(dev, MFP_CAPABLE);
+ ieee80211_hw_set(dev, PS_NULLFUNC_STACK);
+ ieee80211_hw_set(dev, SUPPORTS_PS);
+ ieee80211_hw_set(dev, RX_INCLUDES_FCS);
+ ieee80211_hw_set(dev, SIGNAL_DBM);
+
+ dev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_ADHOC) |
+ BIT(NL80211_IFTYPE_AP) |
+ BIT(NL80211_IFTYPE_MESH_POINT);
+
+ priv->beacon_req_id = cpu_to_le32(0);
+ priv->tx_stats[P54_QUEUE_BEACON].limit = 1;
+ priv->tx_stats[P54_QUEUE_FWSCAN].limit = 1;
+ priv->tx_stats[P54_QUEUE_MGMT].limit = 3;
+ priv->tx_stats[P54_QUEUE_CAB].limit = 3;
+ priv->tx_stats[P54_QUEUE_DATA].limit = 5;
+ dev->queues = 1;
+ priv->noise = -94;
+ /*
+ * We support at most 8 tries no matter which rate they're at,
+ * we cannot support max_rates * max_rate_tries as we set it
+ * here, but setting it correctly to 4/2 or so would limit us
+ * artificially if the RC algorithm wants just two rates, so
+ * let's say 4/7, we'll redistribute it at TX time, see the
+ * comments there.
+ */
+ dev->max_rates = 4;
+ dev->max_rate_tries = 7;
+ dev->extra_tx_headroom = sizeof(struct p54_hdr) + 4 +
+ sizeof(struct p54_tx_data);
+
+ /*
+ * For now, disable PS by default because it affects
+ * link stability significantly.
+ */
+ dev->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+
+ mutex_init(&priv->conf_mutex);
+ mutex_init(&priv->eeprom_mutex);
+ init_completion(&priv->stat_comp);
+ init_completion(&priv->eeprom_comp);
+ init_completion(&priv->beacon_comp);
+ INIT_DELAYED_WORK(&priv->work, p54_work);
+
+ eth_broadcast_addr(priv->mc_maclist[0]);
+ priv->curchan = NULL;
+ p54_reset_stats(priv);
+ return dev;
+}
+EXPORT_SYMBOL_GPL(p54_init_common);
+
+int p54_register_common(struct ieee80211_hw *dev, struct device *pdev)
+{
+ struct p54_common __maybe_unused *priv = dev->priv;
+ int err;
+
+ err = ieee80211_register_hw(dev);
+ if (err) {
+ dev_err(pdev, "Cannot register device (%d).\n", err);
+ return err;
+ }
+ priv->registered = true;
+
+#ifdef CPTCFG_P54_LEDS
+ err = p54_init_leds(priv);
+ if (err) {
+ p54_unregister_common(dev);
+ return err;
+ }
+#endif /* CPTCFG_P54_LEDS */
+
+ dev_info(pdev, "is registered as '%s'\n", wiphy_name(dev->wiphy));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(p54_register_common);
+
+void p54_free_common(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+ unsigned int i;
+
+ for (i = 0; i < NUM_NL80211_BANDS; i++)
+ kfree(priv->band_table[i]);
+
+ kfree(priv->iq_autocal);
+ kfree(priv->output_limit);
+ kfree(priv->curve_data);
+ kfree(priv->rssi_db);
+ kfree(priv->used_rxkeys);
+ kfree(priv->survey);
+ priv->iq_autocal = NULL;
+ priv->output_limit = NULL;
+ priv->curve_data = NULL;
+ priv->rssi_db = NULL;
+ priv->used_rxkeys = NULL;
+ priv->survey = NULL;
+ ieee80211_free_hw(dev);
+}
+EXPORT_SYMBOL_GPL(p54_free_common);
+
+void p54_unregister_common(struct ieee80211_hw *dev)
+{
+ struct p54_common *priv = dev->priv;
+
+#ifdef CPTCFG_P54_LEDS
+ p54_unregister_leds(priv);
+#endif /* CPTCFG_P54_LEDS */
+
+ if (priv->registered) {
+ priv->registered = false;
+ ieee80211_unregister_hw(dev);
+ }
+
+ mutex_destroy(&priv->conf_mutex);
+ mutex_destroy(&priv->eeprom_mutex);
+}
+EXPORT_SYMBOL_GPL(p54_unregister_common);
diff --git a/drivers/net/wireless/intersil/p54/p54.h b/drivers/net/wireless/intersil/p54/p54.h
new file mode 100644
index 0000000..d1920b1
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54.h
@@ -0,0 +1,281 @@
+/*
+ * Shared defines for all mac80211 Prism54 code
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ *
+ * Based on the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef P54_H
+#define P54_H
+
+#ifdef CPTCFG_P54_LEDS
+#include <linux/leds.h>
+#endif /* CPTCFG_P54_LEDS */
+
+#define ISL38XX_DEV_FIRMWARE_ADDR 0x20000
+
+#define BR_CODE_MIN 0x80000000
+#define BR_CODE_COMPONENT_ID 0x80000001
+#define BR_CODE_COMPONENT_VERSION 0x80000002
+#define BR_CODE_DEPENDENT_IF 0x80000003
+#define BR_CODE_EXPOSED_IF 0x80000004
+#define BR_CODE_DESCR 0x80000101
+#define BR_CODE_MAX 0x8FFFFFFF
+#define BR_CODE_END_OF_BRA 0xFF0000FF
+#define LEGACY_BR_CODE_END_OF_BRA 0xFFFFFFFF
+
+struct bootrec {
+ __le32 code;
+ __le32 len;
+ u32 data[10];
+} __packed;
+
+/* Interface role definitions */
+#define BR_INTERFACE_ROLE_SERVER 0x0000
+#define BR_INTERFACE_ROLE_CLIENT 0x8000
+
+#define BR_DESC_PRIV_CAP_WEP BIT(0)
+#define BR_DESC_PRIV_CAP_TKIP BIT(1)
+#define BR_DESC_PRIV_CAP_MICHAEL BIT(2)
+#define BR_DESC_PRIV_CAP_CCX_CP BIT(3)
+#define BR_DESC_PRIV_CAP_CCX_MIC BIT(4)
+#define BR_DESC_PRIV_CAP_AESCCMP BIT(5)
+
+struct bootrec_desc {
+ __le16 modes;
+ __le16 flags;
+ __le32 rx_start;
+ __le32 rx_end;
+ u8 headroom;
+ u8 tailroom;
+ u8 tx_queues;
+ u8 tx_depth;
+ u8 privacy_caps;
+ u8 rx_keycache_size;
+ u8 time_size;
+ u8 padding;
+ u8 rates[16];
+ u8 padding2[4];
+ __le16 rx_mtu;
+} __packed;
+
+#define FW_FMAC 0x464d4143
+#define FW_LM86 0x4c4d3836
+#define FW_LM87 0x4c4d3837
+#define FW_LM20 0x4c4d3230
+
+struct bootrec_comp_id {
+ __le32 fw_variant;
+} __packed;
+
+struct bootrec_comp_ver {
+ char fw_version[24];
+} __packed;
+
+struct bootrec_end {
+ __le16 crc;
+ u8 padding[2];
+ u8 md5[16];
+} __packed;
+
+/* provide 16 bytes for the transport back-end */
+#define P54_TX_INFO_DATA_SIZE 16
+
+/* stored in ieee80211_tx_info's rate_driver_data */
+struct p54_tx_info {
+ u32 start_addr;
+ u32 end_addr;
+ union {
+ void *data[P54_TX_INFO_DATA_SIZE / sizeof(void *)];
+ struct {
+ u32 extra_len;
+ };
+ };
+};
+
+#define P54_MAX_CTRL_FRAME_LEN 0x1000
+
+#define P54_SET_QUEUE(queue, ai_fs, cw_min, cw_max, _txop) \
+do { \
+ queue.aifs = cpu_to_le16(ai_fs); \
+ queue.cwmin = cpu_to_le16(cw_min); \
+ queue.cwmax = cpu_to_le16(cw_max); \
+ queue.txop = cpu_to_le16(_txop); \
+} while (0)
+
+struct p54_edcf_queue_param {
+ __le16 aifs;
+ __le16 cwmin;
+ __le16 cwmax;
+ __le16 txop;
+} __packed;
+
+struct p54_rssi_db_entry {
+ u16 freq;
+ s16 mul;
+ s16 add;
+ s16 longbow_unkn;
+ s16 longbow_unk2;
+};
+
+struct p54_cal_database {
+ size_t entries;
+ size_t entry_size;
+ size_t offset;
+ size_t len;
+ u8 data[0];
+};
+
+#define EEPROM_READBACK_LEN 0x3fc
+
+enum fw_state {
+ FW_STATE_OFF,
+ FW_STATE_BOOTING,
+ FW_STATE_READY,
+ FW_STATE_RESET,
+ FW_STATE_RESETTING,
+};
+
+#ifdef CPTCFG_P54_LEDS
+
+#define P54_LED_MAX_NAME_LEN 31
+
+struct p54_led_dev {
+ struct ieee80211_hw *hw_dev;
+ struct led_classdev led_dev;
+ char name[P54_LED_MAX_NAME_LEN + 1];
+
+ unsigned int toggled;
+ unsigned int index;
+ unsigned int registered;
+};
+
+#endif /* CPTCFG_P54_LEDS */
+
+struct p54_tx_queue_stats {
+ unsigned int len;
+ unsigned int limit;
+ unsigned int count;
+};
+
+struct p54_common {
+ struct ieee80211_hw *hw;
+ struct ieee80211_vif *vif;
+ void (*tx)(struct ieee80211_hw *dev, struct sk_buff *skb);
+ int (*open)(struct ieee80211_hw *dev);
+ void (*stop)(struct ieee80211_hw *dev);
+ struct sk_buff_head tx_pending;
+ struct sk_buff_head tx_queue;
+ struct mutex conf_mutex;
+ bool registered;
+
+ /* memory management (as seen by the firmware) */
+ u32 rx_start;
+ u32 rx_end;
+ u16 rx_mtu;
+ u8 headroom;
+ u8 tailroom;
+
+ /* firmware/hardware info */
+ unsigned int tx_hdr_len;
+ unsigned int fw_var;
+ unsigned int fw_interface;
+ u8 version;
+
+ /* (e)DCF / QOS state */
+ bool use_short_slot;
+ spinlock_t tx_stats_lock;
+ struct p54_tx_queue_stats tx_stats[8];
+ struct p54_edcf_queue_param qos_params[8];
+
+ /* Radio data */
+ u16 rxhw;
+ u8 rx_diversity_mask;
+ u8 tx_diversity_mask;
+ unsigned int output_power;
+ struct p54_rssi_db_entry *cur_rssi;
+ struct ieee80211_channel *curchan;
+ struct survey_info *survey;
+ unsigned int chan_num;
+ struct completion stat_comp;
+ bool update_stats;
+ struct {
+ unsigned int timestamp;
+ unsigned int cached_cca;
+ unsigned int cached_tx;
+ unsigned int cached_rssi;
+ u64 active;
+ u64 cca;
+ u64 tx;
+ u64 rssi;
+ } survey_raw;
+
+ int noise;
+ /* calibration, output power limit and rssi<->dBm conversation data */
+ struct pda_iq_autocal_entry *iq_autocal;
+ unsigned int iq_autocal_len;
+ struct p54_cal_database *curve_data;
+ struct p54_cal_database *output_limit;
+ struct p54_cal_database *rssi_db;
+ struct ieee80211_supported_band *band_table[NUM_NL80211_BANDS];
+
+ /* BBP/MAC state */
+ u8 mac_addr[ETH_ALEN];
+ u8 bssid[ETH_ALEN];
+ u8 mc_maclist[4][ETH_ALEN];
+ u16 wakeup_timer;
+ unsigned int filter_flags;
+ int mc_maclist_num;
+ int mode;
+ u32 tsf_low32, tsf_high32;
+ u32 basic_rate_mask;
+ u16 aid;
+ u8 coverage_class;
+ bool phy_idle;
+ bool phy_ps;
+ bool powersave_override;
+ __le32 beacon_req_id;
+ struct completion beacon_comp;
+
+ /* cryptographic engine information */
+ u8 privacy_caps;
+ u8 rx_keycache_size;
+ unsigned long *used_rxkeys;
+
+ /* LED management */
+#ifdef CPTCFG_P54_LEDS
+ struct p54_led_dev leds[4];
+ struct delayed_work led_work;
+#endif /* CPTCFG_P54_LEDS */
+ u16 softled_state; /* bit field of glowing LEDs */
+
+ /* statistics */
+ struct ieee80211_low_level_stats stats;
+ struct delayed_work work;
+
+ /* eeprom handling */
+ void *eeprom;
+ struct completion eeprom_comp;
+ struct mutex eeprom_mutex;
+};
+
+/* interfaces for the drivers */
+int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb);
+void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb);
+int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw);
+int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len);
+int p54_read_eeprom(struct ieee80211_hw *dev);
+
+struct ieee80211_hw *p54_init_common(size_t priv_data_len);
+int p54_register_common(struct ieee80211_hw *dev, struct device *pdev);
+void p54_free_common(struct ieee80211_hw *dev);
+
+void p54_unregister_common(struct ieee80211_hw *dev);
+
+#endif /* P54_H */
diff --git a/drivers/net/wireless/intersil/p54/p54pci.c b/drivers/net/wireless/intersil/p54/p54pci.c
new file mode 100644
index 0000000..27a4906
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54pci.c
@@ -0,0 +1,703 @@
+
+/*
+ * Linux device driver for PCI based Prism54
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2008, Christian Lamparter <chunkeey@web.de>
+ *
+ * Based on the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jean-baptiste.note@m4x.org>, et al.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <net/mac80211.h>
+
+#include "p54.h"
+#include "lmac.h"
+#include "p54pci.h"
+
+MODULE_AUTHOR("Michael Wu <flamingice@sourmilk.net>");
+MODULE_DESCRIPTION("Prism54 PCI wireless driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("prism54pci");
+MODULE_FIRMWARE("isl3886pci");
+
+static const struct pci_device_id p54p_table[] = {
+ /* Intersil PRISM Duette/Prism GT Wireless LAN adapter */
+ { PCI_DEVICE(0x1260, 0x3890) },
+ /* 3COM 3CRWE154G72 Wireless LAN adapter */
+ { PCI_DEVICE(0x10b7, 0x6001) },
+ /* Intersil PRISM Indigo Wireless LAN adapter */
+ { PCI_DEVICE(0x1260, 0x3877) },
+ /* Intersil PRISM Javelin/Xbow Wireless LAN adapter */
+ { PCI_DEVICE(0x1260, 0x3886) },
+ /* Intersil PRISM Xbow Wireless LAN adapter (Symbol AP-300) */
+ { PCI_DEVICE(0x1260, 0xffff) },
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, p54p_table);
+
+static int p54p_upload_firmware(struct ieee80211_hw *dev)
+{
+ struct p54p_priv *priv = dev->priv;
+ __le32 reg;
+ int err;
+ __le32 *data;
+ u32 remains, left, device_addr;
+
+ P54P_WRITE(int_enable, cpu_to_le32(0));
+ P54P_READ(int_enable);
+ udelay(10);
+
+ reg = P54P_READ(ctrl_stat);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RAMBOOT);
+ P54P_WRITE(ctrl_stat, reg);
+ P54P_READ(ctrl_stat);
+ udelay(10);
+
+ reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET);
+ P54P_WRITE(ctrl_stat, reg);
+ wmb();
+ udelay(10);
+
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ P54P_WRITE(ctrl_stat, reg);
+ wmb();
+
+ /* wait for the firmware to reset properly */
+ mdelay(10);
+
+ err = p54_parse_firmware(dev, priv->firmware);
+ if (err)
+ return err;
+
+ if (priv->common.fw_interface != FW_LM86) {
+ dev_err(&priv->pdev->dev, "wrong firmware, "
+ "please get a LM86(PCI) firmware a try again.\n");
+ return -EINVAL;
+ }
+
+ data = (__le32 *) priv->firmware->data;
+ remains = priv->firmware->size;
+ device_addr = ISL38XX_DEV_FIRMWARE_ADDR;
+ while (remains) {
+ u32 i = 0;
+ left = min((u32)0x1000, remains);
+ P54P_WRITE(direct_mem_base, cpu_to_le32(device_addr));
+ P54P_READ(int_enable);
+
+ device_addr += 0x1000;
+ while (i < left) {
+ P54P_WRITE(direct_mem_win[i], *data++);
+ i += sizeof(u32);
+ }
+
+ remains -= left;
+ P54P_READ(int_enable);
+ }
+
+ reg = P54P_READ(ctrl_stat);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_CLKRUN);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RAMBOOT);
+ P54P_WRITE(ctrl_stat, reg);
+ P54P_READ(ctrl_stat);
+ udelay(10);
+
+ reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET);
+ P54P_WRITE(ctrl_stat, reg);
+ wmb();
+ udelay(10);
+
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ P54P_WRITE(ctrl_stat, reg);
+ wmb();
+ udelay(10);
+
+ /* wait for the firmware to boot properly */
+ mdelay(100);
+
+ return 0;
+}
+
+static void p54p_refill_rx_ring(struct ieee80211_hw *dev,
+ int ring_index, struct p54p_desc *ring, u32 ring_limit,
+ struct sk_buff **rx_buf, u32 index)
+{
+ struct p54p_priv *priv = dev->priv;
+ struct p54p_ring_control *ring_control = priv->ring_control;
+ u32 limit, idx, i;
+
+ idx = le32_to_cpu(ring_control->host_idx[ring_index]);
+ limit = idx;
+ limit -= index;
+ limit = ring_limit - limit;
+
+ i = idx % ring_limit;
+ while (limit-- > 1) {
+ struct p54p_desc *desc = &ring[i];
+
+ if (!desc->host_addr) {
+ struct sk_buff *skb;
+ dma_addr_t mapping;
+ skb = dev_alloc_skb(priv->common.rx_mtu + 32);
+ if (!skb)
+ break;
+
+ mapping = pci_map_single(priv->pdev,
+ skb_tail_pointer(skb),
+ priv->common.rx_mtu + 32,
+ PCI_DMA_FROMDEVICE);
+
+ if (pci_dma_mapping_error(priv->pdev, mapping)) {
+ dev_kfree_skb_any(skb);
+ dev_err(&priv->pdev->dev,
+ "RX DMA Mapping error\n");
+ break;
+ }
+
+ desc->host_addr = cpu_to_le32(mapping);
+ desc->device_addr = 0; // FIXME: necessary?
+ desc->len = cpu_to_le16(priv->common.rx_mtu + 32);
+ desc->flags = 0;
+ rx_buf[i] = skb;
+ }
+
+ i++;
+ idx++;
+ i %= ring_limit;
+ }
+
+ wmb();
+ ring_control->host_idx[ring_index] = cpu_to_le32(idx);
+}
+
+static void p54p_check_rx_ring(struct ieee80211_hw *dev, u32 *index,
+ int ring_index, struct p54p_desc *ring, u32 ring_limit,
+ struct sk_buff **rx_buf)
+{
+ struct p54p_priv *priv = dev->priv;
+ struct p54p_ring_control *ring_control = priv->ring_control;
+ struct p54p_desc *desc;
+ u32 idx, i;
+
+ i = (*index) % ring_limit;
+ (*index) = idx = le32_to_cpu(ring_control->device_idx[ring_index]);
+ idx %= ring_limit;
+ while (i != idx) {
+ u16 len;
+ struct sk_buff *skb;
+ dma_addr_t dma_addr;
+ desc = &ring[i];
+ len = le16_to_cpu(desc->len);
+ skb = rx_buf[i];
+
+ if (!skb) {
+ i++;
+ i %= ring_limit;
+ continue;
+ }
+
+ if (unlikely(len > priv->common.rx_mtu)) {
+ if (net_ratelimit())
+ dev_err(&priv->pdev->dev, "rx'd frame size "
+ "exceeds length threshold.\n");
+
+ len = priv->common.rx_mtu;
+ }
+ dma_addr = le32_to_cpu(desc->host_addr);
+ pci_dma_sync_single_for_cpu(priv->pdev, dma_addr,
+ priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE);
+ skb_put(skb, len);
+
+ if (p54_rx(dev, skb)) {
+ pci_unmap_single(priv->pdev, dma_addr,
+ priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE);
+ rx_buf[i] = NULL;
+ desc->host_addr = cpu_to_le32(0);
+ } else {
+ skb_trim(skb, 0);
+ pci_dma_sync_single_for_device(priv->pdev, dma_addr,
+ priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE);
+ desc->len = cpu_to_le16(priv->common.rx_mtu + 32);
+ }
+
+ i++;
+ i %= ring_limit;
+ }
+
+ p54p_refill_rx_ring(dev, ring_index, ring, ring_limit, rx_buf, *index);
+}
+
+static void p54p_check_tx_ring(struct ieee80211_hw *dev, u32 *index,
+ int ring_index, struct p54p_desc *ring, u32 ring_limit,
+ struct sk_buff **tx_buf)
+{
+ struct p54p_priv *priv = dev->priv;
+ struct p54p_ring_control *ring_control = priv->ring_control;
+ struct p54p_desc *desc;
+ struct sk_buff *skb;
+ u32 idx, i;
+
+ i = (*index) % ring_limit;
+ (*index) = idx = le32_to_cpu(ring_control->device_idx[ring_index]);
+ idx %= ring_limit;
+
+ while (i != idx) {
+ desc = &ring[i];
+
+ skb = tx_buf[i];
+ tx_buf[i] = NULL;
+
+ pci_unmap_single(priv->pdev, le32_to_cpu(desc->host_addr),
+ le16_to_cpu(desc->len), PCI_DMA_TODEVICE);
+
+ desc->host_addr = 0;
+ desc->device_addr = 0;
+ desc->len = 0;
+ desc->flags = 0;
+
+ if (skb && FREE_AFTER_TX(skb))
+ p54_free_skb(dev, skb);
+
+ i++;
+ i %= ring_limit;
+ }
+}
+
+static void p54p_tasklet(unsigned long dev_id)
+{
+ struct ieee80211_hw *dev = (struct ieee80211_hw *)dev_id;
+ struct p54p_priv *priv = dev->priv;
+ struct p54p_ring_control *ring_control = priv->ring_control;
+
+ p54p_check_tx_ring(dev, &priv->tx_idx_mgmt, 3, ring_control->tx_mgmt,
+ ARRAY_SIZE(ring_control->tx_mgmt),
+ priv->tx_buf_mgmt);
+
+ p54p_check_tx_ring(dev, &priv->tx_idx_data, 1, ring_control->tx_data,
+ ARRAY_SIZE(ring_control->tx_data),
+ priv->tx_buf_data);
+
+ p54p_check_rx_ring(dev, &priv->rx_idx_mgmt, 2, ring_control->rx_mgmt,
+ ARRAY_SIZE(ring_control->rx_mgmt), priv->rx_buf_mgmt);
+
+ p54p_check_rx_ring(dev, &priv->rx_idx_data, 0, ring_control->rx_data,
+ ARRAY_SIZE(ring_control->rx_data), priv->rx_buf_data);
+
+ wmb();
+ P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_UPDATE));
+}
+
+static irqreturn_t p54p_interrupt(int irq, void *dev_id)
+{
+ struct ieee80211_hw *dev = dev_id;
+ struct p54p_priv *priv = dev->priv;
+ __le32 reg;
+
+ reg = P54P_READ(int_ident);
+ if (unlikely(reg == cpu_to_le32(0xFFFFFFFF))) {
+ goto out;
+ }
+ P54P_WRITE(int_ack, reg);
+
+ reg &= P54P_READ(int_enable);
+
+ if (reg & cpu_to_le32(ISL38XX_INT_IDENT_UPDATE))
+ tasklet_schedule(&priv->tasklet);
+ else if (reg & cpu_to_le32(ISL38XX_INT_IDENT_INIT))
+ complete(&priv->boot_comp);
+
+out:
+ return reg ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void p54p_tx(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ unsigned long flags;
+ struct p54p_priv *priv = dev->priv;
+ struct p54p_ring_control *ring_control = priv->ring_control;
+ struct p54p_desc *desc;
+ dma_addr_t mapping;
+ u32 idx, i;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ idx = le32_to_cpu(ring_control->host_idx[1]);
+ i = idx % ARRAY_SIZE(ring_control->tx_data);
+
+ mapping = pci_map_single(priv->pdev, skb->data, skb->len,
+ PCI_DMA_TODEVICE);
+ if (pci_dma_mapping_error(priv->pdev, mapping)) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ p54_free_skb(dev, skb);
+ dev_err(&priv->pdev->dev, "TX DMA mapping error\n");
+ return ;
+ }
+ priv->tx_buf_data[i] = skb;
+
+ desc = &ring_control->tx_data[i];
+ desc->host_addr = cpu_to_le32(mapping);
+ desc->device_addr = ((struct p54_hdr *)skb->data)->req_id;
+ desc->len = cpu_to_le16(skb->len);
+ desc->flags = 0;
+
+ wmb();
+ ring_control->host_idx[1] = cpu_to_le32(idx + 1);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_UPDATE));
+ P54P_READ(dev_int);
+}
+
+static void p54p_stop(struct ieee80211_hw *dev)
+{
+ struct p54p_priv *priv = dev->priv;
+ struct p54p_ring_control *ring_control = priv->ring_control;
+ unsigned int i;
+ struct p54p_desc *desc;
+
+ P54P_WRITE(int_enable, cpu_to_le32(0));
+ P54P_READ(int_enable);
+ udelay(10);
+
+ free_irq(priv->pdev->irq, dev);
+
+ tasklet_kill(&priv->tasklet);
+
+ P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_RESET));
+
+ for (i = 0; i < ARRAY_SIZE(priv->rx_buf_data); i++) {
+ desc = &ring_control->rx_data[i];
+ if (desc->host_addr)
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(desc->host_addr),
+ priv->common.rx_mtu + 32,
+ PCI_DMA_FROMDEVICE);
+ kfree_skb(priv->rx_buf_data[i]);
+ priv->rx_buf_data[i] = NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(priv->rx_buf_mgmt); i++) {
+ desc = &ring_control->rx_mgmt[i];
+ if (desc->host_addr)
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(desc->host_addr),
+ priv->common.rx_mtu + 32,
+ PCI_DMA_FROMDEVICE);
+ kfree_skb(priv->rx_buf_mgmt[i]);
+ priv->rx_buf_mgmt[i] = NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(priv->tx_buf_data); i++) {
+ desc = &ring_control->tx_data[i];
+ if (desc->host_addr)
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(desc->host_addr),
+ le16_to_cpu(desc->len),
+ PCI_DMA_TODEVICE);
+
+ p54_free_skb(dev, priv->tx_buf_data[i]);
+ priv->tx_buf_data[i] = NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(priv->tx_buf_mgmt); i++) {
+ desc = &ring_control->tx_mgmt[i];
+ if (desc->host_addr)
+ pci_unmap_single(priv->pdev,
+ le32_to_cpu(desc->host_addr),
+ le16_to_cpu(desc->len),
+ PCI_DMA_TODEVICE);
+
+ p54_free_skb(dev, priv->tx_buf_mgmt[i]);
+ priv->tx_buf_mgmt[i] = NULL;
+ }
+
+ memset(ring_control, 0, sizeof(*ring_control));
+}
+
+static int p54p_open(struct ieee80211_hw *dev)
+{
+ struct p54p_priv *priv = dev->priv;
+ int err;
+ long timeout;
+
+ init_completion(&priv->boot_comp);
+ err = request_irq(priv->pdev->irq, p54p_interrupt,
+ IRQF_SHARED, "p54pci", dev);
+ if (err) {
+ dev_err(&priv->pdev->dev, "failed to register IRQ handler\n");
+ return err;
+ }
+
+ memset(priv->ring_control, 0, sizeof(*priv->ring_control));
+ err = p54p_upload_firmware(dev);
+ if (err) {
+ free_irq(priv->pdev->irq, dev);
+ return err;
+ }
+ priv->rx_idx_data = priv->tx_idx_data = 0;
+ priv->rx_idx_mgmt = priv->tx_idx_mgmt = 0;
+
+ p54p_refill_rx_ring(dev, 0, priv->ring_control->rx_data,
+ ARRAY_SIZE(priv->ring_control->rx_data), priv->rx_buf_data, 0);
+
+ p54p_refill_rx_ring(dev, 2, priv->ring_control->rx_mgmt,
+ ARRAY_SIZE(priv->ring_control->rx_mgmt), priv->rx_buf_mgmt, 0);
+
+ P54P_WRITE(ring_control_base, cpu_to_le32(priv->ring_control_dma));
+ P54P_READ(ring_control_base);
+ wmb();
+ udelay(10);
+
+ P54P_WRITE(int_enable, cpu_to_le32(ISL38XX_INT_IDENT_INIT));
+ P54P_READ(int_enable);
+ wmb();
+ udelay(10);
+
+ P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_RESET));
+ P54P_READ(dev_int);
+
+ timeout = wait_for_completion_interruptible_timeout(
+ &priv->boot_comp, HZ);
+ if (timeout <= 0) {
+ wiphy_err(dev->wiphy, "Cannot boot firmware!\n");
+ p54p_stop(dev);
+ return timeout ? -ERESTARTSYS : -ETIMEDOUT;
+ }
+
+ P54P_WRITE(int_enable, cpu_to_le32(ISL38XX_INT_IDENT_UPDATE));
+ P54P_READ(int_enable);
+ wmb();
+ udelay(10);
+
+ P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_UPDATE));
+ P54P_READ(dev_int);
+ wmb();
+ udelay(10);
+
+ return 0;
+}
+
+static void p54p_firmware_step2(const struct firmware *fw,
+ void *context)
+{
+ struct p54p_priv *priv = context;
+ struct ieee80211_hw *dev = priv->common.hw;
+ struct pci_dev *pdev = priv->pdev;
+ int err;
+
+ if (!fw) {
+ dev_err(&pdev->dev, "Cannot find firmware (isl3886pci)\n");
+ err = -ENOENT;
+ goto out;
+ }
+
+ priv->firmware = fw;
+
+ err = p54p_open(dev);
+ if (err)
+ goto out;
+ err = p54_read_eeprom(dev);
+ p54p_stop(dev);
+ if (err)
+ goto out;
+
+ err = p54_register_common(dev, &pdev->dev);
+ if (err)
+ goto out;
+
+out:
+
+ complete(&priv->fw_loaded);
+
+ if (err) {
+ struct device *parent = pdev->dev.parent;
+
+ if (parent)
+ device_lock(parent);
+
+ /*
+ * This will indirectly result in a call to p54p_remove.
+ * Hence, we don't need to bother with freeing any
+ * allocated ressources at all.
+ */
+ device_release_driver(&pdev->dev);
+
+ if (parent)
+ device_unlock(parent);
+ }
+
+ pci_dev_put(pdev);
+}
+
+static int p54p_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct p54p_priv *priv;
+ struct ieee80211_hw *dev;
+ unsigned long mem_addr, mem_len;
+ int err;
+
+ pci_dev_get(pdev);
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "Cannot enable new PCI device\n");
+ return err;
+ }
+
+ mem_addr = pci_resource_start(pdev, 0);
+ mem_len = pci_resource_len(pdev, 0);
+ if (mem_len < sizeof(struct p54p_csr)) {
+ dev_err(&pdev->dev, "Too short PCI resources\n");
+ err = -ENODEV;
+ goto err_disable_dev;
+ }
+
+ err = pci_request_regions(pdev, "p54pci");
+ if (err) {
+ dev_err(&pdev->dev, "Cannot obtain PCI resources\n");
+ goto err_disable_dev;
+ }
+
+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (!err)
+ err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
+ if (err) {
+ dev_err(&pdev->dev, "No suitable DMA available\n");
+ goto err_free_reg;
+ }
+
+ pci_set_master(pdev);
+ pci_try_set_mwi(pdev);
+
+ pci_write_config_byte(pdev, 0x40, 0);
+ pci_write_config_byte(pdev, 0x41, 0);
+
+ dev = p54_init_common(sizeof(*priv));
+ if (!dev) {
+ dev_err(&pdev->dev, "ieee80211 alloc failed\n");
+ err = -ENOMEM;
+ goto err_free_reg;
+ }
+
+ priv = dev->priv;
+ priv->pdev = pdev;
+
+ init_completion(&priv->fw_loaded);
+ SET_IEEE80211_DEV(dev, &pdev->dev);
+ pci_set_drvdata(pdev, dev);
+
+ priv->map = ioremap(mem_addr, mem_len);
+ if (!priv->map) {
+ dev_err(&pdev->dev, "Cannot map device memory\n");
+ err = -ENOMEM;
+ goto err_free_dev;
+ }
+
+ priv->ring_control = pci_alloc_consistent(pdev, sizeof(*priv->ring_control),
+ &priv->ring_control_dma);
+ if (!priv->ring_control) {
+ dev_err(&pdev->dev, "Cannot allocate rings\n");
+ err = -ENOMEM;
+ goto err_iounmap;
+ }
+ priv->common.open = p54p_open;
+ priv->common.stop = p54p_stop;
+ priv->common.tx = p54p_tx;
+
+ spin_lock_init(&priv->lock);
+ tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev);
+
+ err = request_firmware_nowait(THIS_MODULE, 1, "isl3886pci",
+ &priv->pdev->dev, GFP_KERNEL,
+ priv, p54p_firmware_step2);
+ if (!err)
+ return 0;
+
+ pci_free_consistent(pdev, sizeof(*priv->ring_control),
+ priv->ring_control, priv->ring_control_dma);
+
+ err_iounmap:
+ iounmap(priv->map);
+
+ err_free_dev:
+ p54_free_common(dev);
+
+ err_free_reg:
+ pci_release_regions(pdev);
+ err_disable_dev:
+ pci_disable_device(pdev);
+ pci_dev_put(pdev);
+ return err;
+}
+
+static void p54p_remove(struct pci_dev *pdev)
+{
+ struct ieee80211_hw *dev = pci_get_drvdata(pdev);
+ struct p54p_priv *priv;
+
+ if (!dev)
+ return;
+
+ priv = dev->priv;
+ wait_for_completion(&priv->fw_loaded);
+ p54_unregister_common(dev);
+ release_firmware(priv->firmware);
+ pci_free_consistent(pdev, sizeof(*priv->ring_control),
+ priv->ring_control, priv->ring_control_dma);
+ iounmap(priv->map);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ p54_free_common(dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int p54p_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+
+ pci_save_state(pdev);
+ pci_set_power_state(pdev, PCI_D3hot);
+ pci_disable_device(pdev);
+ return 0;
+}
+
+static int p54p_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ int err;
+
+ err = pci_reenable_device(pdev);
+ if (err)
+ return err;
+ return pci_set_power_state(pdev, PCI_D0);
+}
+
+static SIMPLE_DEV_PM_OPS(p54pci_pm_ops, p54p_suspend, p54p_resume);
+
+#define P54P_PM_OPS (&p54pci_pm_ops)
+#else
+#define P54P_PM_OPS (NULL)
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver p54p_driver = {
+ .name = "p54pci",
+ .id_table = p54p_table,
+ .probe = p54p_probe,
+ .remove = p54p_remove,
+ .driver.pm = P54P_PM_OPS,
+};
+
+module_pci_driver(p54p_driver);
diff --git a/drivers/net/wireless/intersil/p54/p54pci.h b/drivers/net/wireless/intersil/p54/p54pci.h
new file mode 100644
index 0000000..68405c1
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54pci.h
@@ -0,0 +1,112 @@
+#ifndef P54PCI_H
+#define P54PCI_H
+#include <linux/interrupt.h>
+
+/*
+ * Defines for PCI based mac80211 Prism54 driver
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ *
+ * Based on the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* Device Interrupt register bits */
+#define ISL38XX_DEV_INT_RESET 0x0001
+#define ISL38XX_DEV_INT_UPDATE 0x0002
+#define ISL38XX_DEV_INT_WAKEUP 0x0008
+#define ISL38XX_DEV_INT_SLEEP 0x0010
+#define ISL38XX_DEV_INT_ABORT 0x0020
+/* these two only used in USB */
+#define ISL38XX_DEV_INT_DATA 0x0040
+#define ISL38XX_DEV_INT_MGMT 0x0080
+
+#define ISL38XX_DEV_INT_PCIUART_CTS 0x4000
+#define ISL38XX_DEV_INT_PCIUART_DR 0x8000
+
+/* Interrupt Identification/Acknowledge/Enable register bits */
+#define ISL38XX_INT_IDENT_UPDATE 0x0002
+#define ISL38XX_INT_IDENT_INIT 0x0004
+#define ISL38XX_INT_IDENT_WAKEUP 0x0008
+#define ISL38XX_INT_IDENT_SLEEP 0x0010
+#define ISL38XX_INT_IDENT_PCIUART_CTS 0x4000
+#define ISL38XX_INT_IDENT_PCIUART_DR 0x8000
+
+/* Control/Status register bits */
+#define ISL38XX_CTRL_STAT_SLEEPMODE 0x00000200
+#define ISL38XX_CTRL_STAT_CLKRUN 0x00800000
+#define ISL38XX_CTRL_STAT_RESET 0x10000000
+#define ISL38XX_CTRL_STAT_RAMBOOT 0x20000000
+#define ISL38XX_CTRL_STAT_STARTHALTED 0x40000000
+#define ISL38XX_CTRL_STAT_HOST_OVERRIDE 0x80000000
+
+struct p54p_csr {
+ __le32 dev_int;
+ u8 unused_1[12];
+ __le32 int_ident;
+ __le32 int_ack;
+ __le32 int_enable;
+ u8 unused_2[4];
+ union {
+ __le32 ring_control_base;
+ __le32 gen_purp_com[2];
+ };
+ u8 unused_3[8];
+ __le32 direct_mem_base;
+ u8 unused_4[44];
+ __le32 dma_addr;
+ __le32 dma_len;
+ __le32 dma_ctrl;
+ u8 unused_5[12];
+ __le32 ctrl_stat;
+ u8 unused_6[1924];
+ u8 cardbus_cis[0x800];
+ u8 direct_mem_win[0x1000];
+} __packed;
+
+/* usb backend only needs the register defines above */
+#ifndef P54USB_H
+struct p54p_desc {
+ __le32 host_addr;
+ __le32 device_addr;
+ __le16 len;
+ __le16 flags;
+} __packed;
+
+struct p54p_ring_control {
+ __le32 host_idx[4];
+ __le32 device_idx[4];
+ struct p54p_desc rx_data[8];
+ struct p54p_desc tx_data[32];
+ struct p54p_desc rx_mgmt[4];
+ struct p54p_desc tx_mgmt[4];
+} __packed;
+
+#define P54P_READ(r) (__force __le32)__raw_readl(&priv->map->r)
+#define P54P_WRITE(r, val) __raw_writel((__force u32)(__le32)(val), &priv->map->r)
+
+struct p54p_priv {
+ struct p54_common common;
+ struct pci_dev *pdev;
+ struct p54p_csr __iomem *map;
+ struct tasklet_struct tasklet;
+ const struct firmware *firmware;
+ spinlock_t lock;
+ struct p54p_ring_control *ring_control;
+ dma_addr_t ring_control_dma;
+ u32 rx_idx_data, tx_idx_data;
+ u32 rx_idx_mgmt, tx_idx_mgmt;
+ struct sk_buff *rx_buf_data[8];
+ struct sk_buff *rx_buf_mgmt[4];
+ struct sk_buff *tx_buf_data[32];
+ struct sk_buff *tx_buf_mgmt[4];
+ struct completion boot_comp;
+ struct completion fw_loaded;
+};
+
+#endif /* P54USB_H */
+#endif /* P54PCI_H */
diff --git a/drivers/net/wireless/intersil/p54/p54spi.c b/drivers/net/wireless/intersil/p54/p54spi.c
new file mode 100644
index 0000000..238b0a5
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54spi.c
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2008 Christian Lamparter <chunkeey@web.de>
+ * Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * This driver is a port from stlc45xx:
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/spi/spi.h>
+#include <linux/etherdevice.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+
+#include "p54spi.h"
+#include "p54.h"
+
+#include "lmac.h"
+
+#ifdef CPTCFG_P54_SPI_DEFAULT_EEPROM
+#include "p54spi_eeprom.h"
+#endif /* CPTCFG_P54_SPI_DEFAULT_EEPROM */
+
+MODULE_FIRMWARE("3826.arm");
+
+/* gpios should be handled in board files and provided via platform data,
+ * but because it's currently impossible for p54spi to have a header file
+ * in include/linux, let's use module paramaters for now
+ */
+
+static int p54spi_gpio_power = 97;
+module_param(p54spi_gpio_power, int, 0444);
+MODULE_PARM_DESC(p54spi_gpio_power, "gpio number for power line");
+
+static int p54spi_gpio_irq = 87;
+module_param(p54spi_gpio_irq, int, 0444);
+MODULE_PARM_DESC(p54spi_gpio_irq, "gpio number for irq line");
+
+static void p54spi_spi_read(struct p54s_priv *priv, u8 address,
+ void *buf, size_t len)
+{
+ struct spi_transfer t[2];
+ struct spi_message m;
+ __le16 addr;
+
+ /* We first push the address */
+ addr = cpu_to_le16(address << 8 | SPI_ADRS_READ_BIT_15);
+
+ spi_message_init(&m);
+ memset(t, 0, sizeof(t));
+
+ t[0].tx_buf = &addr;
+ t[0].len = sizeof(addr);
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].rx_buf = buf;
+ t[1].len = len;
+ spi_message_add_tail(&t[1], &m);
+
+ spi_sync(priv->spi, &m);
+}
+
+
+static void p54spi_spi_write(struct p54s_priv *priv, u8 address,
+ const void *buf, size_t len)
+{
+ struct spi_transfer t[3];
+ struct spi_message m;
+ __le16 addr;
+
+ /* We first push the address */
+ addr = cpu_to_le16(address << 8);
+
+ spi_message_init(&m);
+ memset(t, 0, sizeof(t));
+
+ t[0].tx_buf = &addr;
+ t[0].len = sizeof(addr);
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].tx_buf = buf;
+ t[1].len = len & ~1;
+ spi_message_add_tail(&t[1], &m);
+
+ if (len % 2) {
+ __le16 last_word;
+ last_word = cpu_to_le16(((u8 *)buf)[len - 1]);
+
+ t[2].tx_buf = &last_word;
+ t[2].len = sizeof(last_word);
+ spi_message_add_tail(&t[2], &m);
+ }
+
+ spi_sync(priv->spi, &m);
+}
+
+static u32 p54spi_read32(struct p54s_priv *priv, u8 addr)
+{
+ __le32 val;
+
+ p54spi_spi_read(priv, addr, &val, sizeof(val));
+
+ return le32_to_cpu(val);
+}
+
+static inline void p54spi_write16(struct p54s_priv *priv, u8 addr, __le16 val)
+{
+ p54spi_spi_write(priv, addr, &val, sizeof(val));
+}
+
+static inline void p54spi_write32(struct p54s_priv *priv, u8 addr, __le32 val)
+{
+ p54spi_spi_write(priv, addr, &val, sizeof(val));
+}
+
+static int p54spi_wait_bit(struct p54s_priv *priv, u16 reg, u32 bits)
+{
+ int i;
+
+ for (i = 0; i < 2000; i++) {
+ u32 buffer = p54spi_read32(priv, reg);
+ if ((buffer & bits) == bits)
+ return 1;
+ }
+ return 0;
+}
+
+static int p54spi_spi_write_dma(struct p54s_priv *priv, __le32 base,
+ const void *buf, size_t len)
+{
+ if (!p54spi_wait_bit(priv, SPI_ADRS_DMA_WRITE_CTRL, HOST_ALLOWED)) {
+ dev_err(&priv->spi->dev, "spi_write_dma not allowed "
+ "to DMA write.\n");
+ return -EAGAIN;
+ }
+
+ p54spi_write16(priv, SPI_ADRS_DMA_WRITE_CTRL,
+ cpu_to_le16(SPI_DMA_WRITE_CTRL_ENABLE));
+
+ p54spi_write16(priv, SPI_ADRS_DMA_WRITE_LEN, cpu_to_le16(len));
+ p54spi_write32(priv, SPI_ADRS_DMA_WRITE_BASE, base);
+ p54spi_spi_write(priv, SPI_ADRS_DMA_DATA, buf, len);
+ return 0;
+}
+
+static int p54spi_request_firmware(struct ieee80211_hw *dev)
+{
+ struct p54s_priv *priv = dev->priv;
+ int ret;
+
+ /* FIXME: should driver use it's own struct device? */
+ ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev);
+
+ if (ret < 0) {
+ dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret);
+ return ret;
+ }
+
+ ret = p54_parse_firmware(dev, priv->firmware);
+ if (ret) {
+ release_firmware(priv->firmware);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int p54spi_request_eeprom(struct ieee80211_hw *dev)
+{
+ struct p54s_priv *priv = dev->priv;
+ const struct firmware *eeprom;
+ int ret;
+
+ /* allow users to customize their eeprom.
+ */
+
+ ret = request_firmware_direct(&eeprom, "3826.eeprom", &priv->spi->dev);
+ if (ret < 0) {
+#ifdef CPTCFG_P54_SPI_DEFAULT_EEPROM
+ dev_info(&priv->spi->dev, "loading default eeprom...\n");
+ ret = p54_parse_eeprom(dev, (void *) p54spi_eeprom,
+ sizeof(p54spi_eeprom));
+#else
+ dev_err(&priv->spi->dev, "Failed to request user eeprom\n");
+#endif /* CPTCFG_P54_SPI_DEFAULT_EEPROM */
+ } else {
+ dev_info(&priv->spi->dev, "loading user eeprom...\n");
+ ret = p54_parse_eeprom(dev, (void *) eeprom->data,
+ (int)eeprom->size);
+ release_firmware(eeprom);
+ }
+ return ret;
+}
+
+static int p54spi_upload_firmware(struct ieee80211_hw *dev)
+{
+ struct p54s_priv *priv = dev->priv;
+ unsigned long fw_len, _fw_len;
+ unsigned int offset = 0;
+ int err = 0;
+ u8 *fw;
+
+ fw_len = priv->firmware->size;
+ fw = kmemdup(priv->firmware->data, fw_len, GFP_KERNEL);
+ if (!fw)
+ return -ENOMEM;
+
+ /* stop the device */
+ p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16(
+ SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET |
+ SPI_CTRL_STAT_START_HALTED));
+
+ msleep(TARGET_BOOT_SLEEP);
+
+ p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16(
+ SPI_CTRL_STAT_HOST_OVERRIDE |
+ SPI_CTRL_STAT_START_HALTED));
+
+ msleep(TARGET_BOOT_SLEEP);
+
+ while (fw_len > 0) {
+ _fw_len = min_t(long, fw_len, SPI_MAX_PACKET_SIZE);
+
+ err = p54spi_spi_write_dma(priv, cpu_to_le32(
+ ISL38XX_DEV_FIRMWARE_ADDR + offset),
+ (fw + offset), _fw_len);
+ if (err < 0)
+ goto out;
+
+ fw_len -= _fw_len;
+ offset += _fw_len;
+ }
+
+ BUG_ON(fw_len != 0);
+
+ /* enable host interrupts */
+ p54spi_write32(priv, SPI_ADRS_HOST_INT_EN,
+ cpu_to_le32(SPI_HOST_INTS_DEFAULT));
+
+ /* boot the device */
+ p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16(
+ SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET |
+ SPI_CTRL_STAT_RAM_BOOT));
+
+ msleep(TARGET_BOOT_SLEEP);
+
+ p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16(
+ SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_RAM_BOOT));
+ msleep(TARGET_BOOT_SLEEP);
+
+out:
+ kfree(fw);
+ return err;
+}
+
+static void p54spi_power_off(struct p54s_priv *priv)
+{
+ disable_irq(gpio_to_irq(p54spi_gpio_irq));
+ gpio_set_value(p54spi_gpio_power, 0);
+}
+
+static void p54spi_power_on(struct p54s_priv *priv)
+{
+ gpio_set_value(p54spi_gpio_power, 1);
+ enable_irq(gpio_to_irq(p54spi_gpio_irq));
+
+ /* need to wait a while before device can be accessed, the length
+ * is just a guess
+ */
+ msleep(10);
+}
+
+static inline void p54spi_int_ack(struct p54s_priv *priv, u32 val)
+{
+ p54spi_write32(priv, SPI_ADRS_HOST_INT_ACK, cpu_to_le32(val));
+}
+
+static int p54spi_wakeup(struct p54s_priv *priv)
+{
+ /* wake the chip */
+ p54spi_write32(priv, SPI_ADRS_ARM_INTERRUPTS,
+ cpu_to_le32(SPI_TARGET_INT_WAKEUP));
+
+ /* And wait for the READY interrupt */
+ if (!p54spi_wait_bit(priv, SPI_ADRS_HOST_INTERRUPTS,
+ SPI_HOST_INT_READY)) {
+ dev_err(&priv->spi->dev, "INT_READY timeout\n");
+ return -EBUSY;
+ }
+
+ p54spi_int_ack(priv, SPI_HOST_INT_READY);
+ return 0;
+}
+
+static inline void p54spi_sleep(struct p54s_priv *priv)
+{
+ p54spi_write32(priv, SPI_ADRS_ARM_INTERRUPTS,
+ cpu_to_le32(SPI_TARGET_INT_SLEEP));
+}
+
+static void p54spi_int_ready(struct p54s_priv *priv)
+{
+ p54spi_write32(priv, SPI_ADRS_HOST_INT_EN, cpu_to_le32(
+ SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE));
+
+ switch (priv->fw_state) {
+ case FW_STATE_BOOTING:
+ priv->fw_state = FW_STATE_READY;
+ complete(&priv->fw_comp);
+ break;
+ case FW_STATE_RESETTING:
+ priv->fw_state = FW_STATE_READY;
+ /* TODO: reinitialize state */
+ break;
+ default:
+ break;
+ }
+}
+
+static int p54spi_rx(struct p54s_priv *priv)
+{
+ struct sk_buff *skb;
+ u16 len;
+ u16 rx_head[2];
+#define READAHEAD_SZ (sizeof(rx_head)-sizeof(u16))
+
+ if (p54spi_wakeup(priv) < 0)
+ return -EBUSY;
+
+ /* Read data size and first data word in one SPI transaction
+ * This is workaround for firmware/DMA bug,
+ * when first data word gets lost under high load.
+ */
+ p54spi_spi_read(priv, SPI_ADRS_DMA_DATA, rx_head, sizeof(rx_head));
+ len = rx_head[0];
+
+ if (len == 0) {
+ p54spi_sleep(priv);
+ dev_err(&priv->spi->dev, "rx request of zero bytes\n");
+ return 0;
+ }
+
+ /* Firmware may insert up to 4 padding bytes after the lmac header,
+ * but it does not amend the size of SPI data transfer.
+ * Such packets has correct data size in header, thus referencing
+ * past the end of allocated skb. Reserve extra 4 bytes for this case
+ */
+ skb = dev_alloc_skb(len + 4);
+ if (!skb) {
+ p54spi_sleep(priv);
+ dev_err(&priv->spi->dev, "could not alloc skb");
+ return -ENOMEM;
+ }
+
+ if (len <= READAHEAD_SZ) {
+ skb_put_data(skb, rx_head + 1, len);
+ } else {
+ skb_put_data(skb, rx_head + 1, READAHEAD_SZ);
+ p54spi_spi_read(priv, SPI_ADRS_DMA_DATA,
+ skb_put(skb, len - READAHEAD_SZ),
+ len - READAHEAD_SZ);
+ }
+ p54spi_sleep(priv);
+ /* Put additional bytes to compensate for the possible
+ * alignment-caused truncation
+ */
+ skb_put(skb, 4);
+
+ if (p54_rx(priv->hw, skb) == 0)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+
+static irqreturn_t p54spi_interrupt(int irq, void *config)
+{
+ struct spi_device *spi = config;
+ struct p54s_priv *priv = spi_get_drvdata(spi);
+
+ ieee80211_queue_work(priv->hw, &priv->work);
+
+ return IRQ_HANDLED;
+}
+
+static int p54spi_tx_frame(struct p54s_priv *priv, struct sk_buff *skb)
+{
+ struct p54_hdr *hdr = (struct p54_hdr *) skb->data;
+ int ret = 0;
+
+ if (p54spi_wakeup(priv) < 0)
+ return -EBUSY;
+
+ ret = p54spi_spi_write_dma(priv, hdr->req_id, skb->data, skb->len);
+ if (ret < 0)
+ goto out;
+
+ if (!p54spi_wait_bit(priv, SPI_ADRS_HOST_INTERRUPTS,
+ SPI_HOST_INT_WR_READY)) {
+ dev_err(&priv->spi->dev, "WR_READY timeout\n");
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ p54spi_int_ack(priv, SPI_HOST_INT_WR_READY);
+
+ if (FREE_AFTER_TX(skb))
+ p54_free_skb(priv->hw, skb);
+out:
+ p54spi_sleep(priv);
+ return ret;
+}
+
+static int p54spi_wq_tx(struct p54s_priv *priv)
+{
+ struct p54s_tx_info *entry;
+ struct sk_buff *skb;
+ struct ieee80211_tx_info *info;
+ struct p54_tx_info *minfo;
+ struct p54s_tx_info *dinfo;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+
+ while (!list_empty(&priv->tx_pending)) {
+ entry = list_entry(priv->tx_pending.next,
+ struct p54s_tx_info, tx_list);
+
+ list_del_init(&entry->tx_list);
+
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ dinfo = container_of((void *) entry, struct p54s_tx_info,
+ tx_list);
+ minfo = container_of((void *) dinfo, struct p54_tx_info,
+ data);
+ info = container_of((void *) minfo, struct ieee80211_tx_info,
+ rate_driver_data);
+ skb = container_of((void *) info, struct sk_buff, cb);
+
+ ret = p54spi_tx_frame(priv, skb);
+
+ if (ret < 0) {
+ p54_free_skb(priv->hw, skb);
+ return ret;
+ }
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+ }
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+ return ret;
+}
+
+static void p54spi_op_tx(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ struct p54s_priv *priv = dev->priv;
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct p54_tx_info *mi = (struct p54_tx_info *) info->rate_driver_data;
+ struct p54s_tx_info *di = (struct p54s_tx_info *) mi->data;
+ unsigned long flags;
+
+ BUILD_BUG_ON(sizeof(*di) > sizeof((mi->data)));
+
+ spin_lock_irqsave(&priv->tx_lock, flags);
+ list_add_tail(&di->tx_list, &priv->tx_pending);
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ ieee80211_queue_work(priv->hw, &priv->work);
+}
+
+static void p54spi_work(struct work_struct *work)
+{
+ struct p54s_priv *priv = container_of(work, struct p54s_priv, work);
+ u32 ints;
+ int ret;
+
+ mutex_lock(&priv->mutex);
+
+ if (priv->fw_state == FW_STATE_OFF)
+ goto out;
+
+ ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS);
+
+ if (ints & SPI_HOST_INT_READY) {
+ p54spi_int_ready(priv);
+ p54spi_int_ack(priv, SPI_HOST_INT_READY);
+ }
+
+ if (priv->fw_state != FW_STATE_READY)
+ goto out;
+
+ if (ints & SPI_HOST_INT_UPDATE) {
+ p54spi_int_ack(priv, SPI_HOST_INT_UPDATE);
+ ret = p54spi_rx(priv);
+ if (ret < 0)
+ goto out;
+ }
+ if (ints & SPI_HOST_INT_SW_UPDATE) {
+ p54spi_int_ack(priv, SPI_HOST_INT_SW_UPDATE);
+ ret = p54spi_rx(priv);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = p54spi_wq_tx(priv);
+out:
+ mutex_unlock(&priv->mutex);
+}
+
+static int p54spi_op_start(struct ieee80211_hw *dev)
+{
+ struct p54s_priv *priv = dev->priv;
+ unsigned long timeout;
+ int ret = 0;
+
+ if (mutex_lock_interruptible(&priv->mutex)) {
+ ret = -EINTR;
+ goto out;
+ }
+
+ priv->fw_state = FW_STATE_BOOTING;
+
+ p54spi_power_on(priv);
+
+ ret = p54spi_upload_firmware(dev);
+ if (ret < 0) {
+ p54spi_power_off(priv);
+ goto out_unlock;
+ }
+
+ mutex_unlock(&priv->mutex);
+
+ timeout = msecs_to_jiffies(2000);
+ timeout = wait_for_completion_interruptible_timeout(&priv->fw_comp,
+ timeout);
+ if (!timeout) {
+ dev_err(&priv->spi->dev, "firmware boot failed");
+ p54spi_power_off(priv);
+ ret = -1;
+ goto out;
+ }
+
+ if (mutex_lock_interruptible(&priv->mutex)) {
+ ret = -EINTR;
+ p54spi_power_off(priv);
+ goto out;
+ }
+
+ WARN_ON(priv->fw_state != FW_STATE_READY);
+
+out_unlock:
+ mutex_unlock(&priv->mutex);
+
+out:
+ return ret;
+}
+
+static void p54spi_op_stop(struct ieee80211_hw *dev)
+{
+ struct p54s_priv *priv = dev->priv;
+ unsigned long flags;
+
+ mutex_lock(&priv->mutex);
+ WARN_ON(priv->fw_state != FW_STATE_READY);
+
+ p54spi_power_off(priv);
+ spin_lock_irqsave(&priv->tx_lock, flags);
+ INIT_LIST_HEAD(&priv->tx_pending);
+ spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+ priv->fw_state = FW_STATE_OFF;
+ mutex_unlock(&priv->mutex);
+
+ cancel_work_sync(&priv->work);
+}
+
+static int p54spi_probe(struct spi_device *spi)
+{
+ struct p54s_priv *priv = NULL;
+ struct ieee80211_hw *hw;
+ int ret = -EINVAL;
+
+ hw = p54_init_common(sizeof(*priv));
+ if (!hw) {
+ dev_err(&spi->dev, "could not alloc ieee80211_hw");
+ return -ENOMEM;
+ }
+
+ priv = hw->priv;
+ priv->hw = hw;
+ spi_set_drvdata(spi, priv);
+ priv->spi = spi;
+
+ spi->bits_per_word = 16;
+ spi->max_speed_hz = 24000000;
+
+ ret = spi_setup(spi);
+ if (ret < 0) {
+ dev_err(&priv->spi->dev, "spi_setup failed");
+ goto err_free;
+ }
+
+ ret = gpio_request(p54spi_gpio_power, "p54spi power");
+ if (ret < 0) {
+ dev_err(&priv->spi->dev, "power GPIO request failed: %d", ret);
+ goto err_free;
+ }
+
+ ret = gpio_request(p54spi_gpio_irq, "p54spi irq");
+ if (ret < 0) {
+ dev_err(&priv->spi->dev, "irq GPIO request failed: %d", ret);
+ goto err_free_gpio_power;
+ }
+
+ gpio_direction_output(p54spi_gpio_power, 0);
+ gpio_direction_input(p54spi_gpio_irq);
+
+ ret = request_irq(gpio_to_irq(p54spi_gpio_irq),
+ p54spi_interrupt, 0, "p54spi",
+ priv->spi);
+ if (ret < 0) {
+ dev_err(&priv->spi->dev, "request_irq() failed");
+ goto err_free_gpio_irq;
+ }
+
+ irq_set_irq_type(gpio_to_irq(p54spi_gpio_irq), IRQ_TYPE_EDGE_RISING);
+
+ disable_irq(gpio_to_irq(p54spi_gpio_irq));
+
+ INIT_WORK(&priv->work, p54spi_work);
+ init_completion(&priv->fw_comp);
+ INIT_LIST_HEAD(&priv->tx_pending);
+ mutex_init(&priv->mutex);
+ spin_lock_init(&priv->tx_lock);
+ SET_IEEE80211_DEV(hw, &spi->dev);
+ priv->common.open = p54spi_op_start;
+ priv->common.stop = p54spi_op_stop;
+ priv->common.tx = p54spi_op_tx;
+
+ ret = p54spi_request_firmware(hw);
+ if (ret < 0)
+ goto err_free_common;
+
+ ret = p54spi_request_eeprom(hw);
+ if (ret)
+ goto err_free_common;
+
+ ret = p54_register_common(hw, &priv->spi->dev);
+ if (ret)
+ goto err_free_common;
+
+ return 0;
+
+err_free_common:
+ free_irq(gpio_to_irq(p54spi_gpio_irq), spi);
+err_free_gpio_irq:
+ gpio_free(p54spi_gpio_irq);
+err_free_gpio_power:
+ gpio_free(p54spi_gpio_power);
+err_free:
+ p54_free_common(priv->hw);
+ return ret;
+}
+
+static int p54spi_remove(struct spi_device *spi)
+{
+ struct p54s_priv *priv = spi_get_drvdata(spi);
+
+ p54_unregister_common(priv->hw);
+
+ free_irq(gpio_to_irq(p54spi_gpio_irq), spi);
+
+ gpio_free(p54spi_gpio_power);
+ gpio_free(p54spi_gpio_irq);
+ release_firmware(priv->firmware);
+
+ mutex_destroy(&priv->mutex);
+
+ p54_free_common(priv->hw);
+
+ return 0;
+}
+
+
+static struct spi_driver p54spi_driver = {
+ .driver = {
+ .name = "p54spi",
+ },
+
+ .probe = p54spi_probe,
+ .remove = p54spi_remove,
+};
+
+module_spi_driver(p54spi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christian Lamparter <chunkeey@web.de>");
+MODULE_ALIAS("spi:cx3110x");
+MODULE_ALIAS("spi:p54spi");
+MODULE_ALIAS("spi:stlc45xx");
diff --git a/drivers/net/wireless/intersil/p54/p54spi.h b/drivers/net/wireless/intersil/p54/p54spi.h
new file mode 100644
index 0000000..dfaa62a
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54spi.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2008 Christian Lamparter <chunkeey@web.de>
+ *
+ * This driver is a port from stlc45xx:
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef P54SPI_H
+#define P54SPI_H
+
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <net/mac80211.h>
+
+#include "p54.h"
+
+/* Bit 15 is read/write bit; ON = READ, OFF = WRITE */
+#define SPI_ADRS_READ_BIT_15 0x8000
+
+#define SPI_ADRS_ARM_INTERRUPTS 0x00
+#define SPI_ADRS_ARM_INT_EN 0x04
+
+#define SPI_ADRS_HOST_INTERRUPTS 0x08
+#define SPI_ADRS_HOST_INT_EN 0x0c
+#define SPI_ADRS_HOST_INT_ACK 0x10
+
+#define SPI_ADRS_GEN_PURP_1 0x14
+#define SPI_ADRS_GEN_PURP_2 0x18
+
+#define SPI_ADRS_DEV_CTRL_STAT 0x26 /* high word */
+
+#define SPI_ADRS_DMA_DATA 0x28
+
+#define SPI_ADRS_DMA_WRITE_CTRL 0x2c
+#define SPI_ADRS_DMA_WRITE_LEN 0x2e
+#define SPI_ADRS_DMA_WRITE_BASE 0x30
+
+#define SPI_ADRS_DMA_READ_CTRL 0x34
+#define SPI_ADRS_DMA_READ_LEN 0x36
+#define SPI_ADRS_DMA_READ_BASE 0x38
+
+#define SPI_CTRL_STAT_HOST_OVERRIDE 0x8000
+#define SPI_CTRL_STAT_START_HALTED 0x4000
+#define SPI_CTRL_STAT_RAM_BOOT 0x2000
+#define SPI_CTRL_STAT_HOST_RESET 0x1000
+#define SPI_CTRL_STAT_HOST_CPU_EN 0x0800
+
+#define SPI_DMA_WRITE_CTRL_ENABLE 0x0001
+#define SPI_DMA_READ_CTRL_ENABLE 0x0001
+#define HOST_ALLOWED (1 << 7)
+
+#define SPI_TIMEOUT 100 /* msec */
+
+#define SPI_MAX_TX_PACKETS 32
+
+#define SPI_MAX_PACKET_SIZE 32767
+
+#define SPI_TARGET_INT_WAKEUP 0x00000001
+#define SPI_TARGET_INT_SLEEP 0x00000002
+#define SPI_TARGET_INT_RDDONE 0x00000004
+
+#define SPI_TARGET_INT_CTS 0x00004000
+#define SPI_TARGET_INT_DR 0x00008000
+
+#define SPI_HOST_INT_READY 0x00000001
+#define SPI_HOST_INT_WR_READY 0x00000002
+#define SPI_HOST_INT_SW_UPDATE 0x00000004
+#define SPI_HOST_INT_UPDATE 0x10000000
+
+/* clear to send */
+#define SPI_HOST_INT_CR 0x00004000
+
+/* data ready */
+#define SPI_HOST_INT_DR 0x00008000
+
+#define SPI_HOST_INTS_DEFAULT \
+ (SPI_HOST_INT_READY | SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE)
+
+#define TARGET_BOOT_SLEEP 50
+
+struct p54s_dma_regs {
+ __le16 cmd;
+ __le16 len;
+ __le32 addr;
+} __packed;
+
+struct p54s_tx_info {
+ struct list_head tx_list;
+};
+
+struct p54s_priv {
+ /* p54_common has to be the first entry */
+ struct p54_common common;
+ struct ieee80211_hw *hw;
+ struct spi_device *spi;
+
+ struct work_struct work;
+
+ struct mutex mutex;
+ struct completion fw_comp;
+
+ spinlock_t tx_lock;
+
+ /* protected by tx_lock */
+ struct list_head tx_pending;
+
+ enum fw_state fw_state;
+ const struct firmware *firmware;
+};
+
+#endif /* P54SPI_H */
diff --git a/drivers/net/wireless/intersil/p54/p54spi_eeprom.h b/drivers/net/wireless/intersil/p54/p54spi_eeprom.h
new file mode 100644
index 0000000..0b7bfb0
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54spi_eeprom.h
@@ -0,0 +1,679 @@
+/*
+ * Copyright (C) 2003 Conexant Americas Inc. All Rights Reserved.
+ * Copyright (C) 2004, 2005, 2006 Nokia Corporation
+ * Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2008 Christian Lamparter <chunkeey@web.de>
+ *
+ * based on:
+ * - cx3110x's pda.h from Nokia
+ * - cx3110-transfer.log by Johannes Berg
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef P54SPI_EEPROM_H
+#define P54SPI_EEPROM_H
+
+static unsigned char p54spi_eeprom[] = {
+
+/* struct eeprom_pda_wrap */
+0x47, 0x4d, 0x55, 0xaa, /* magic */
+0x00, 0x00, /* pad */
+0x00, 0x00, /* eeprom_pda_data_wrap length */
+0x00, 0x00, 0x00, 0x00, /* arm opcode */
+
+/* bogus MAC address */
+0x04, 0x00, 0x01, 0x01, /* PDR_MAC_ADDRESS */
+ 0x00, 0x02, 0xee, 0xc0, 0xff, 0xee,
+
+/* struct bootrec_exp_if */
+0x06, 0x00, 0x01, 0x10, /* PDR_INTERFACE_LIST */
+ 0x00, 0x00, /* role */
+ 0x0f, 0x00, /* if_id */
+ 0x85, 0x00, /* variant = Longbow RF, 2GHz */
+ 0x01, 0x00, /* btm_compat */
+ 0x1f, 0x00, /* top_compat */
+
+0x03, 0x00, 0x02, 0x10, /* PDR_HARDWARE_PLATFORM_COMPONENT_ID */
+ 0x03, 0x20, 0x00, 0x43,
+
+/* struct pda_country[6] */
+0x0d, 0x00, 0x07, 0x10, /* PDR_COUNTRY_LIST */
+ 0x10, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00,
+ 0x31, 0x00, 0x00, 0x00,
+ 0x32, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00,
+
+/* struct pda_country */
+0x03, 0x00, 0x08, 0x10, /* PDR_DEFAULT_COUNTRY */
+ 0x30, 0x00, 0x00, 0x00, /* ETSI */
+
+0x03, 0x00, 0x00, 0x11, /* PDR_ANTENNA_GAIN */
+ 0x08, 0x08, 0x08, 0x08,
+
+0x0a, 0x00, 0xff, 0xca, /* PDR_RSSI_LINEAR_APPROXIMATION_CUSTOMV2 */
+ 0x01, 0x00, 0x0a, 0x00,
+ 0x00, 0x00, 0x0a, 0x00,
+ 0x85, 0x09, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00,
+
+/* struct pda_custom_wrapper */
+0x10, 0x06, 0x5d, 0xb0, /* PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM */
+ 0x0d, 0x00, 0xee, 0x00, /* 13 entries, 238 bytes per entry */
+ 0x00, 0x00, 0x16, 0x0c, /* no offset, 3094 total len */
+ /* 2412 MHz */
+ 0x6c, 0x09,
+ 0x10, 0x01, 0x9a, 0x84,
+ 0xaa, 0x8a, 0xaa, 0x8a, 0xaa, 0x8a, 0xaa, 0x8a,
+ 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6,
+ 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6,
+ 0xf0, 0x00, 0x94, 0x6c,
+ 0x99, 0x82, 0x99, 0x82, 0x99, 0x82, 0x99, 0x82,
+ 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae,
+ 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae,
+ 0xd0, 0x00, 0xaa, 0x5a,
+ 0x88, 0x7a, 0x88, 0x7a, 0x88, 0x7a, 0x88, 0x7a,
+ 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6,
+ 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6,
+ 0xa0, 0x00, 0xf3, 0x47,
+ 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e,
+ 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a,
+ 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a,
+ 0x50, 0x00, 0x59, 0x36,
+ 0x43, 0x5a, 0x43, 0x5a, 0x43, 0x5a, 0x43, 0x5a,
+ 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85,
+ 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85,
+ 0x00, 0x00, 0xe4, 0x2d,
+ 0x18, 0x46, 0x18, 0x46, 0x18, 0x46, 0x18, 0x46,
+ 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71,
+ 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2417 MHz */
+ 0x71, 0x09,
+ 0x10, 0x01, 0xb9, 0x83,
+ 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a,
+ 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6,
+ 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6,
+ 0xf0, 0x00, 0x2e, 0x6c,
+ 0x68, 0x82, 0x68, 0x82, 0x68, 0x82, 0x68, 0x82,
+ 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad,
+ 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad,
+ 0xd0, 0x00, 0x8d, 0x5a,
+ 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a,
+ 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5,
+ 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5,
+ 0xa0, 0x00, 0x0a, 0x48,
+ 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e,
+ 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99,
+ 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99,
+ 0x50, 0x00, 0x7c, 0x36,
+ 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59,
+ 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85,
+ 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85,
+ 0x00, 0x00, 0xf5, 0x2d,
+ 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45,
+ 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71,
+ 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2422 MHz */
+ 0x76, 0x09,
+ 0x10, 0x01, 0xb9, 0x83,
+ 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a,
+ 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6,
+ 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6,
+ 0xf0, 0x00, 0x2e, 0x6c,
+ 0x68, 0x82, 0x68, 0x82, 0x68, 0x82, 0x68, 0x82,
+ 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad,
+ 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad,
+ 0xd0, 0x00, 0x8d, 0x5a,
+ 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a,
+ 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5,
+ 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5,
+ 0xa0, 0x00, 0x0a, 0x48,
+ 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e,
+ 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99,
+ 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99,
+ 0x50, 0x00, 0x7c, 0x36,
+ 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59,
+ 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85,
+ 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85,
+ 0x00, 0x00, 0xf5, 0x2d,
+ 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45,
+ 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71,
+ 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2427 MHz */
+ 0x7b, 0x09,
+ 0x10, 0x01, 0x48, 0x83,
+ 0x67, 0x8a, 0x67, 0x8a, 0x67, 0x8a, 0x67, 0x8a,
+ 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5,
+ 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5,
+ 0xf0, 0x00, 0xfb, 0x6b,
+ 0x50, 0x82, 0x50, 0x82, 0x50, 0x82, 0x50, 0x82,
+ 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad,
+ 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad,
+ 0xd0, 0x00, 0x7e, 0x5a,
+ 0x38, 0x7a, 0x38, 0x7a, 0x38, 0x7a, 0x38, 0x7a,
+ 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5,
+ 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5,
+ 0xa0, 0x00, 0x15, 0x48,
+ 0x14, 0x6e, 0x14, 0x6e, 0x14, 0x6e, 0x14, 0x6e,
+ 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99,
+ 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99,
+ 0x50, 0x00, 0x8e, 0x36,
+ 0xd9, 0x59, 0xd9, 0x59, 0xd9, 0x59, 0xd9, 0x59,
+ 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85,
+ 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85,
+ 0x00, 0x00, 0xfe, 0x2d,
+ 0x9d, 0x45, 0x9d, 0x45, 0x9d, 0x45, 0x9d, 0x45,
+ 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71,
+ 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2432 MHz */
+ 0x80, 0x09,
+ 0x10, 0x01, 0xd7, 0x82,
+ 0x51, 0x8a, 0x51, 0x8a, 0x51, 0x8a, 0x51, 0x8a,
+ 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5,
+ 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5,
+ 0xf0, 0x00, 0xc8, 0x6b,
+ 0x37, 0x82, 0x37, 0x82, 0x37, 0x82, 0x37, 0x82,
+ 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad,
+ 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad,
+ 0xd0, 0x00, 0x6f, 0x5a,
+ 0x1d, 0x7a, 0x1d, 0x7a, 0x1d, 0x7a, 0x1d, 0x7a,
+ 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5,
+ 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5,
+ 0xa0, 0x00, 0x20, 0x48,
+ 0xf6, 0x6d, 0xf6, 0x6d, 0xf6, 0x6d, 0xf6, 0x6d,
+ 0x88, 0x99, 0x88, 0x99, 0x88, 0x99, 0x88, 0x99,
+ 0x88, 0x99, 0x88, 0x99, 0x88, 0x99, 0x88, 0x99,
+ 0x50, 0x00, 0x9f, 0x36,
+ 0xb5, 0x59, 0xb5, 0x59, 0xb5, 0x59, 0xb5, 0x59,
+ 0x47, 0x85, 0x47, 0x85, 0x47, 0x85, 0x47, 0x85,
+ 0x47, 0x85, 0x47, 0x85, 0x47, 0x85, 0x47, 0x85,
+ 0x00, 0x00, 0x06, 0x2e,
+ 0x74, 0x45, 0x74, 0x45, 0x74, 0x45, 0x74, 0x45,
+ 0x06, 0x71, 0x06, 0x71, 0x06, 0x71, 0x06, 0x71,
+ 0x06, 0x71, 0x06, 0x71, 0x06, 0x71, 0x06, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2437 MHz */
+ 0x85, 0x09,
+ 0x10, 0x01, 0x67, 0x82,
+ 0x3a, 0x8a, 0x3a, 0x8a, 0x3a, 0x8a, 0x3a, 0x8a,
+ 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5,
+ 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5,
+ 0xf0, 0x00, 0x95, 0x6b,
+ 0x1f, 0x82, 0x1f, 0x82, 0x1f, 0x82, 0x1f, 0x82,
+ 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad,
+ 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad,
+ 0xd0, 0x00, 0x61, 0x5a,
+ 0x02, 0x7a, 0x02, 0x7a, 0x02, 0x7a, 0x02, 0x7a,
+ 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5,
+ 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5,
+ 0xa0, 0x00, 0x2c, 0x48,
+ 0xd8, 0x6d, 0xd8, 0x6d, 0xd8, 0x6d, 0xd8, 0x6d,
+ 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99,
+ 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99,
+ 0x50, 0x00, 0xb1, 0x36,
+ 0x92, 0x59, 0x92, 0x59, 0x92, 0x59, 0x92, 0x59,
+ 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85,
+ 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85,
+ 0x00, 0x00, 0x0f, 0x2e,
+ 0x4b, 0x45, 0x4b, 0x45, 0x4b, 0x45, 0x4b, 0x45,
+ 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70,
+ 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2442 MHz */
+ 0x8a, 0x09,
+ 0x10, 0x01, 0xf6, 0x81,
+ 0x24, 0x8a, 0x24, 0x8a, 0x24, 0x8a, 0x24, 0x8a,
+ 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5,
+ 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5,
+ 0xf0, 0x00, 0x62, 0x6b,
+ 0x06, 0x82, 0x06, 0x82, 0x06, 0x82, 0x06, 0x82,
+ 0x98, 0xad, 0x98, 0xad, 0x98, 0xad, 0x98, 0xad,
+ 0x98, 0xad, 0x98, 0xad, 0x98, 0xad, 0x98, 0xad,
+ 0xd0, 0x00, 0x52, 0x5a,
+ 0xe7, 0x79, 0xe7, 0x79, 0xe7, 0x79, 0xe7, 0x79,
+ 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5,
+ 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5,
+ 0xa0, 0x00, 0x37, 0x48,
+ 0xba, 0x6d, 0xba, 0x6d, 0xba, 0x6d, 0xba, 0x6d,
+ 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99,
+ 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99,
+ 0x50, 0x00, 0xc2, 0x36,
+ 0x6e, 0x59, 0x6e, 0x59, 0x6e, 0x59, 0x6e, 0x59,
+ 0x00, 0x85, 0x00, 0x85, 0x00, 0x85, 0x00, 0x85,
+ 0x00, 0x85, 0x00, 0x85, 0x00, 0x85, 0x00, 0x85,
+ 0x00, 0x00, 0x17, 0x2e,
+ 0x22, 0x45, 0x22, 0x45, 0x22, 0x45, 0x22, 0x45,
+ 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70,
+ 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2447 MHz */
+ 0x8f, 0x09,
+ 0x10, 0x01, 0x75, 0x83,
+ 0x61, 0x8a, 0x61, 0x8a, 0x61, 0x8a, 0x61, 0x8a,
+ 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5,
+ 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5,
+ 0xf0, 0x00, 0x4b, 0x6c,
+ 0x3f, 0x82, 0x3f, 0x82, 0x3f, 0x82, 0x3f, 0x82,
+ 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad,
+ 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad,
+ 0xd0, 0x00, 0xda, 0x5a,
+ 0x1c, 0x7a, 0x1c, 0x7a, 0x1c, 0x7a, 0x1c, 0x7a,
+ 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5,
+ 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5,
+ 0xa0, 0x00, 0x6d, 0x48,
+ 0xe9, 0x6d, 0xe9, 0x6d, 0xe9, 0x6d, 0xe9, 0x6d,
+ 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99,
+ 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99,
+ 0x50, 0x00, 0xc6, 0x36,
+ 0x92, 0x59, 0x92, 0x59, 0x92, 0x59, 0x92, 0x59,
+ 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85,
+ 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85,
+ 0x00, 0x00, 0x15, 0x2e,
+ 0x3c, 0x45, 0x3c, 0x45, 0x3c, 0x45, 0x3c, 0x45,
+ 0xce, 0x70, 0xce, 0x70, 0xce, 0x70, 0xce, 0x70,
+ 0xce, 0x70, 0xce, 0x70, 0xce, 0x70, 0xce, 0x70,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2452 MHz */
+ 0x94, 0x09,
+ 0x10, 0x01, 0xf4, 0x84,
+ 0x9e, 0x8a, 0x9e, 0x8a, 0x9e, 0x8a, 0x9e, 0x8a,
+ 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6,
+ 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6,
+ 0xf0, 0x00, 0x34, 0x6d,
+ 0x77, 0x82, 0x77, 0x82, 0x77, 0x82, 0x77, 0x82,
+ 0x09, 0xae, 0x09, 0xae, 0x09, 0xae, 0x09, 0xae,
+ 0x09, 0xae, 0x09, 0xae, 0x09, 0xae, 0x09, 0xae,
+ 0xd0, 0x00, 0x62, 0x5b,
+ 0x50, 0x7a, 0x50, 0x7a, 0x50, 0x7a, 0x50, 0x7a,
+ 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5,
+ 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5,
+ 0xa0, 0x00, 0xa2, 0x48,
+ 0x17, 0x6e, 0x17, 0x6e, 0x17, 0x6e, 0x17, 0x6e,
+ 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99,
+ 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99,
+ 0x50, 0x00, 0xc9, 0x36,
+ 0xb7, 0x59, 0xb7, 0x59, 0xb7, 0x59, 0xb7, 0x59,
+ 0x49, 0x85, 0x49, 0x85, 0x49, 0x85, 0x49, 0x85,
+ 0x49, 0x85, 0x49, 0x85, 0x49, 0x85, 0x49, 0x85,
+ 0x00, 0x00, 0x12, 0x2e,
+ 0x57, 0x45, 0x57, 0x45, 0x57, 0x45, 0x57, 0x45,
+ 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70,
+ 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2452 MHz */
+ 0x99, 0x09,
+ 0x10, 0x01, 0x74, 0x86,
+ 0xdb, 0x8a, 0xdb, 0x8a, 0xdb, 0x8a, 0xdb, 0x8a,
+ 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6,
+ 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6,
+ 0xf0, 0x00, 0x1e, 0x6e,
+ 0xb0, 0x82, 0xb0, 0x82, 0xb0, 0x82, 0xb0, 0x82,
+ 0x42, 0xae, 0x42, 0xae, 0x42, 0xae, 0x42, 0xae,
+ 0x42, 0xae, 0x42, 0xae, 0x42, 0xae, 0x42, 0xae,
+ 0xd0, 0x00, 0xeb, 0x5b,
+ 0x85, 0x7a, 0x85, 0x7a, 0x85, 0x7a, 0x85, 0x7a,
+ 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6,
+ 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6,
+ 0xa0, 0x00, 0xd8, 0x48,
+ 0x46, 0x6e, 0x46, 0x6e, 0x46, 0x6e, 0x46, 0x6e,
+ 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99,
+ 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99,
+ 0x50, 0x00, 0xcd, 0x36,
+ 0xdb, 0x59, 0xdb, 0x59, 0xdb, 0x59, 0xdb, 0x59,
+ 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85,
+ 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85,
+ 0x00, 0x00, 0x10, 0x2e,
+ 0x71, 0x45, 0x71, 0x45, 0x71, 0x45, 0x71, 0x45,
+ 0x03, 0x71, 0x03, 0x71, 0x03, 0x71, 0x03, 0x71,
+ 0x03, 0x71, 0x03, 0x71, 0x03, 0x71, 0x03, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2557 MHz */
+ 0x9e, 0x09,
+ 0x10, 0x01, 0xf3, 0x87,
+ 0x17, 0x8b, 0x17, 0x8b, 0x17, 0x8b, 0x17, 0x8b,
+ 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6,
+ 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6,
+ 0xf0, 0x00, 0x07, 0x6f,
+ 0xe9, 0x82, 0xe9, 0x82, 0xe9, 0x82, 0xe9, 0x82,
+ 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae,
+ 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae,
+ 0xd0, 0x00, 0x73, 0x5c,
+ 0xba, 0x7a, 0xba, 0x7a, 0xba, 0x7a, 0xba, 0x7a,
+ 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6,
+ 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6,
+ 0xa0, 0x00, 0x0d, 0x49,
+ 0x74, 0x6e, 0x74, 0x6e, 0x74, 0x6e, 0x74, 0x6e,
+ 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a,
+ 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a,
+ 0x50, 0x00, 0xd1, 0x36,
+ 0xff, 0x59, 0xff, 0x59, 0xff, 0x59, 0xff, 0x59,
+ 0x91, 0x85, 0x91, 0x85, 0x91, 0x85, 0x91, 0x85,
+ 0x91, 0x85, 0x91, 0x85, 0x91, 0x85, 0x91, 0x85,
+ 0x00, 0x00, 0x0e, 0x2e,
+ 0x8b, 0x45, 0x8b, 0x45, 0x8b, 0x45, 0x8b, 0x45,
+ 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71,
+ 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2562 MHz */
+ 0xa3, 0x09,
+ 0x10, 0x01, 0x72, 0x89,
+ 0x54, 0x8b, 0x54, 0x8b, 0x54, 0x8b, 0x54, 0x8b,
+ 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6,
+ 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6,
+ 0xf0, 0x00, 0xf0, 0x6f,
+ 0x21, 0x83, 0x21, 0x83, 0x21, 0x83, 0x21, 0x83,
+ 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae,
+ 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae,
+ 0xd0, 0x00, 0xfb, 0x5c,
+ 0xee, 0x7a, 0xee, 0x7a, 0xee, 0x7a, 0xee, 0x7a,
+ 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6,
+ 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6,
+ 0xa0, 0x00, 0x43, 0x49,
+ 0xa3, 0x6e, 0xa3, 0x6e, 0xa3, 0x6e, 0xa3, 0x6e,
+ 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a,
+ 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a,
+ 0x50, 0x00, 0xd4, 0x36,
+ 0x24, 0x5a, 0x24, 0x5a, 0x24, 0x5a, 0x24, 0x5a,
+ 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85,
+ 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85,
+ 0x00, 0x00, 0x0b, 0x2e,
+ 0xa6, 0x45, 0xa6, 0x45, 0xa6, 0x45, 0xa6, 0x45,
+ 0x38, 0x71, 0x38, 0x71, 0x38, 0x71, 0x38, 0x71,
+ 0x38, 0x71, 0x38, 0x71, 0x38, 0x71, 0x38, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+ /* 2572 MHz */
+ 0xa8, 0x09,
+ 0x10, 0x01, 0xf1, 0x8a,
+ 0x91, 0x8b, 0x91, 0x8b, 0x91, 0x8b, 0x91, 0x8b,
+ 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7,
+ 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7,
+ 0xf0, 0x00, 0xd9, 0x70,
+ 0x5a, 0x83, 0x5a, 0x83, 0x5a, 0x83, 0x5a, 0x83,
+ 0xec, 0xae, 0xec, 0xae, 0xec, 0xae, 0xec, 0xae,
+ 0xec, 0xae, 0xec, 0xae, 0xec, 0xae, 0xec, 0xae,
+ 0xd0, 0x00, 0x83, 0x5d,
+ 0x23, 0x7b, 0x23, 0x7b, 0x23, 0x7b, 0x23, 0x7b,
+ 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6,
+ 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6,
+ 0xa0, 0x00, 0x78, 0x49,
+ 0xd1, 0x6e, 0xd1, 0x6e, 0xd1, 0x6e, 0xd1, 0x6e,
+ 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a,
+ 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a,
+ 0x50, 0x00, 0xd8, 0x36,
+ 0x48, 0x5a, 0x48, 0x5a, 0x48, 0x5a, 0x48, 0x5a,
+ 0xda, 0x85, 0xda, 0x85, 0xda, 0x85, 0xda, 0x85,
+ 0xda, 0x85, 0xda, 0x85, 0xda, 0x85, 0xda, 0x85,
+ 0x00, 0x00, 0x09, 0x2e,
+ 0xc0, 0x45, 0xc0, 0x45, 0xc0, 0x45, 0xc0, 0x45,
+ 0x52, 0x71, 0x52, 0x71, 0x52, 0x71, 0x52, 0x71,
+ 0x52, 0x71, 0x52, 0x71, 0x52, 0x71, 0x52, 0x71,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00,
+
+/*
+ * Not really sure if this is actually the power_limit database,
+ * it looks a bit "related" to PDR_PRISM_ZIF_TX_IQ_CALIBRATION
+ */
+/* struct pda_custom_wrapper */
+0xae, 0x00, 0xef, 0xbe, /* PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM */
+ 0x0d, 0x00, 0x1a, 0x00, /* 13 entries, 26 bytes per entry */
+ 0x00, 0x00, 0x52, 0x01, /* no offset, 338 bytes total */
+
+ /* 2412 MHz */
+ 0x6c, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xe0, 0x00, 0xe0, 0x00, 0xe0, 0x00, 0xe0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2417 MHz */
+ 0x71, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2422 MHz */
+ 0x76, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2427 MHz */
+ 0x7b, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2432 MHz */
+ 0x80, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2437 MHz */
+ 0x85, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2442 MHz */
+ 0x8a, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2447 MHz */
+ 0x8f, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2452 MHz */
+ 0x94, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2457 MHz */
+ 0x99, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2462 MHz */
+ 0x9e, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2467 MHz */
+ 0xa3, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+ /* 2472 MHz */
+ 0xa8, 0x09,
+ 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01,
+ 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00,
+ 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00,
+
+/* struct pda_iq_autocal_entry[13] */
+0x42, 0x00, 0x06, 0x19, /* PDR_PRISM_ZIF_TX_IQ_CALIBRATION */
+ /* 2412 MHz */
+ 0x6c, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2417 MHz */
+ 0x71, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2422 MHz */
+ 0x76, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2427 MHz */
+ 0x7b, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2432 MHz */
+ 0x80, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2437 MHz */
+ 0x85, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2442 MHz */
+ 0x8a, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2447 MHz */
+ 0x8f, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2452 MHz */
+ 0x94, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00,
+ /* 2457 MHz */
+ 0x99, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01,
+ /* 2462 MHz */
+ 0x9e, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01,
+ /* 2467 MHz */
+ 0xa3, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01,
+ /* 2472 MHz */
+ 0xa8, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01,
+
+0x02, 0x00, 0x00, 0x00, /* PDR_END */
+ 0xb6, 0x04,
+};
+
+#endif /* P54SPI_EEPROM_H */
+
diff --git a/drivers/net/wireless/intersil/p54/p54usb.c b/drivers/net/wireless/intersil/p54/p54usb.c
new file mode 100644
index 0000000..43ce5c6
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54usb.c
@@ -0,0 +1,1151 @@
+
+/*
+ * Linux device driver for USB based Prism54
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ *
+ * Based on the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/usb.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+#include <linux/crc32.h>
+#include <linux/module.h>
+#include <net/mac80211.h>
+
+#include "p54.h"
+#include "lmac.h"
+#include "p54usb.h"
+
+MODULE_AUTHOR("Michael Wu <flamingice@sourmilk.net>");
+MODULE_DESCRIPTION("Prism54 USB wireless driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("prism54usb");
+MODULE_FIRMWARE("isl3886usb");
+MODULE_FIRMWARE("isl3887usb");
+
+/*
+ * Note:
+ *
+ * Always update our wiki's device list (located at:
+ * http://wireless.kernel.org/en/users/Drivers/p54/devices ),
+ * whenever you add a new device.
+ */
+
+static const struct usb_device_id p54u_table[] = {
+ /* Version 1 devices (pci chip + net2280) */
+ {USB_DEVICE(0x0411, 0x0050)}, /* Buffalo WLI2-USB2-G54 */
+ {USB_DEVICE(0x045e, 0x00c2)}, /* Microsoft MN-710 */
+ {USB_DEVICE(0x0506, 0x0a11)}, /* 3COM 3CRWE254G72 */
+ {USB_DEVICE(0x0675, 0x0530)}, /* DrayTek Vigor 530 */
+ {USB_DEVICE(0x06b9, 0x0120)}, /* Thomson SpeedTouch 120g */
+ {USB_DEVICE(0x0707, 0xee06)}, /* SMC 2862W-G */
+ {USB_DEVICE(0x07aa, 0x001c)}, /* Corega CG-WLUSB2GT */
+ {USB_DEVICE(0x083a, 0x4501)}, /* Accton 802.11g WN4501 USB */
+ {USB_DEVICE(0x083a, 0x4502)}, /* Siemens Gigaset USB Adapter */
+ {USB_DEVICE(0x083a, 0x5501)}, /* Phillips CPWUA054 */
+ {USB_DEVICE(0x0846, 0x4200)}, /* Netgear WG121 */
+ {USB_DEVICE(0x0846, 0x4210)}, /* Netgear WG121 the second ? */
+ {USB_DEVICE(0x0846, 0x4220)}, /* Netgear WG111 */
+ {USB_DEVICE(0x09aa, 0x1000)}, /* Spinnaker Proto board */
+ {USB_DEVICE(0x0bf8, 0x1007)}, /* Fujitsu E-5400 USB */
+ {USB_DEVICE(0x0cde, 0x0006)}, /* Medion 40900, Roper Europe */
+ {USB_DEVICE(0x0db0, 0x6826)}, /* MSI UB54G (MS-6826) */
+ {USB_DEVICE(0x107b, 0x55f2)}, /* Gateway WGU-210 (Gemtek) */
+ {USB_DEVICE(0x124a, 0x4023)}, /* Shuttle PN15, Airvast WM168g, IOGear GWU513 */
+ {USB_DEVICE(0x1435, 0x0210)}, /* Inventel UR054G */
+ {USB_DEVICE(0x15a9, 0x0002)}, /* Gemtek WUBI-100GW 802.11g */
+ {USB_DEVICE(0x1630, 0x0005)}, /* 2Wire 802.11g USB (v1) / Z-Com */
+ {USB_DEVICE(0x182d, 0x096b)}, /* Sitecom WL-107 */
+ {USB_DEVICE(0x1915, 0x2234)}, /* Linksys WUSB54G OEM */
+ {USB_DEVICE(0x1915, 0x2235)}, /* Linksys WUSB54G Portable OEM */
+ {USB_DEVICE(0x2001, 0x3701)}, /* DLink DWL-G120 Spinnaker */
+ {USB_DEVICE(0x2001, 0x3703)}, /* DLink DWL-G122 */
+ {USB_DEVICE(0x2001, 0x3762)}, /* Conceptronic C54U */
+ {USB_DEVICE(0x5041, 0x2234)}, /* Linksys WUSB54G */
+ {USB_DEVICE(0x5041, 0x2235)}, /* Linksys WUSB54G Portable */
+
+ /* Version 2 devices (3887) */
+ {USB_DEVICE(0x0471, 0x1230)}, /* Philips CPWUA054/00 */
+ {USB_DEVICE(0x050d, 0x7050)}, /* Belkin F5D7050 ver 1000 */
+ {USB_DEVICE(0x0572, 0x2000)}, /* Cohiba Proto board */
+ {USB_DEVICE(0x0572, 0x2002)}, /* Cohiba Proto board */
+ {USB_DEVICE(0x06a9, 0x000e)}, /* Westell 802.11g USB (A90-211WG-01) */
+ {USB_DEVICE(0x06b9, 0x0121)}, /* Thomson SpeedTouch 121g */
+ {USB_DEVICE(0x0707, 0xee13)}, /* SMC 2862W-G version 2 */
+ {USB_DEVICE(0x07aa, 0x0020)}, /* Corega WLUSB2GTST USB */
+ {USB_DEVICE(0x0803, 0x4310)}, /* Zoom 4410a */
+ {USB_DEVICE(0x083a, 0x4521)}, /* Siemens Gigaset USB Adapter 54 version 2 */
+ {USB_DEVICE(0x083a, 0x4531)}, /* T-Com Sinus 154 data II */
+ {USB_DEVICE(0x083a, 0xc501)}, /* Zoom Wireless-G 4410 */
+ {USB_DEVICE(0x083a, 0xf503)}, /* Accton FD7050E ver 1010ec */
+ {USB_DEVICE(0x0846, 0x4240)}, /* Netgear WG111 (v2) */
+ {USB_DEVICE(0x0915, 0x2000)}, /* Cohiba Proto board */
+ {USB_DEVICE(0x0915, 0x2002)}, /* Cohiba Proto board */
+ {USB_DEVICE(0x0baf, 0x0118)}, /* U.S. Robotics U5 802.11g Adapter*/
+ {USB_DEVICE(0x0bf8, 0x1009)}, /* FUJITSU E-5400 USB D1700*/
+ /* {USB_DEVICE(0x0cde, 0x0006)}, * Medion MD40900 already listed above,
+ * just noting it here for clarity */
+ {USB_DEVICE(0x0cde, 0x0008)}, /* Sagem XG703A */
+ {USB_DEVICE(0x0cde, 0x0015)}, /* Zcomax XG-705A */
+ {USB_DEVICE(0x0d8e, 0x3762)}, /* DLink DWL-G120 Cohiba */
+ {USB_DEVICE(0x124a, 0x4025)}, /* IOGear GWU513 (GW3887IK chip) */
+ {USB_DEVICE(0x1260, 0xee22)}, /* SMC 2862W-G version 2 */
+ {USB_DEVICE(0x13b1, 0x000a)}, /* Linksys WUSB54G ver 2 */
+ {USB_DEVICE(0x13B1, 0x000C)}, /* Linksys WUSB54AG */
+ {USB_DEVICE(0x1413, 0x5400)}, /* Telsey 802.11g USB2.0 Adapter */
+ {USB_DEVICE(0x1435, 0x0427)}, /* Inventel UR054G */
+ /* {USB_DEVICE(0x15a9, 0x0002)}, * Also SparkLAN WL-682 with 3887 */
+ {USB_DEVICE(0x1668, 0x1050)}, /* Actiontec 802UIG-1 */
+ {USB_DEVICE(0x1740, 0x1000)}, /* Senao NUB-350 */
+ {USB_DEVICE(0x2001, 0x3704)}, /* DLink DWL-G122 rev A2 */
+ {USB_DEVICE(0x2001, 0x3705)}, /* D-Link DWL-G120 rev C1 */
+ {USB_DEVICE(0x413c, 0x5513)}, /* Dell WLA3310 USB Wireless Adapter */
+ {USB_DEVICE(0x413c, 0x8102)}, /* Spinnaker DUT */
+ {USB_DEVICE(0x413c, 0x8104)}, /* Cohiba Proto board */
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, p54u_table);
+
+static const struct {
+ u32 intf;
+ enum p54u_hw_type type;
+ const char *fw;
+ char hw[20];
+} p54u_fwlist[__NUM_P54U_HWTYPES] = {
+ {
+ .type = P54U_NET2280,
+ .intf = FW_LM86,
+ .fw = "isl3886usb",
+ .hw = "ISL3886 + net2280",
+ },
+ {
+ .type = P54U_3887,
+ .intf = FW_LM87,
+ .fw = "isl3887usb",
+ .hw = "ISL3887",
+ },
+};
+
+static void p54u_rx_cb(struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct p54u_rx_info *info = (struct p54u_rx_info *)skb->cb;
+ struct ieee80211_hw *dev = info->dev;
+ struct p54u_priv *priv = dev->priv;
+
+ skb_unlink(skb, &priv->rx_queue);
+
+ if (unlikely(urb->status)) {
+ dev_kfree_skb_irq(skb);
+ return;
+ }
+
+ skb_put(skb, urb->actual_length);
+
+ if (priv->hw_type == P54U_NET2280)
+ skb_pull(skb, priv->common.tx_hdr_len);
+ if (priv->common.fw_interface == FW_LM87) {
+ skb_pull(skb, 4);
+ skb_put(skb, 4);
+ }
+
+ if (p54_rx(dev, skb)) {
+ skb = dev_alloc_skb(priv->common.rx_mtu + 32);
+ if (unlikely(!skb)) {
+ /* TODO check rx queue length and refill *somewhere* */
+ return;
+ }
+
+ info = (struct p54u_rx_info *) skb->cb;
+ info->urb = urb;
+ info->dev = dev;
+ urb->transfer_buffer = skb_tail_pointer(skb);
+ urb->context = skb;
+ } else {
+ if (priv->hw_type == P54U_NET2280)
+ skb_push(skb, priv->common.tx_hdr_len);
+ if (priv->common.fw_interface == FW_LM87) {
+ skb_push(skb, 4);
+ skb_put(skb, 4);
+ }
+ skb_reset_tail_pointer(skb);
+ skb_trim(skb, 0);
+ urb->transfer_buffer = skb_tail_pointer(skb);
+ }
+ skb_queue_tail(&priv->rx_queue, skb);
+ usb_anchor_urb(urb, &priv->submitted);
+ if (usb_submit_urb(urb, GFP_ATOMIC)) {
+ skb_unlink(skb, &priv->rx_queue);
+ usb_unanchor_urb(urb);
+ dev_kfree_skb_irq(skb);
+ }
+}
+
+static void p54u_tx_cb(struct urb *urb)
+{
+ struct sk_buff *skb = urb->context;
+ struct ieee80211_hw *dev =
+ usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
+
+ p54_free_skb(dev, skb);
+}
+
+static void p54u_tx_dummy_cb(struct urb *urb) { }
+
+static void p54u_free_urbs(struct ieee80211_hw *dev)
+{
+ struct p54u_priv *priv = dev->priv;
+ usb_kill_anchored_urbs(&priv->submitted);
+}
+
+static void p54u_stop(struct ieee80211_hw *dev)
+{
+ /*
+ * TODO: figure out how to reliably stop the 3887 and net2280 so
+ * the hardware is still usable next time we want to start it.
+ * until then, we just stop listening to the hardware..
+ */
+ p54u_free_urbs(dev);
+}
+
+static int p54u_init_urbs(struct ieee80211_hw *dev)
+{
+ struct p54u_priv *priv = dev->priv;
+ struct urb *entry = NULL;
+ struct sk_buff *skb;
+ struct p54u_rx_info *info;
+ int ret = 0;
+
+ while (skb_queue_len(&priv->rx_queue) < 32) {
+ skb = __dev_alloc_skb(priv->common.rx_mtu + 32, GFP_KERNEL);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ entry = usb_alloc_urb(0, GFP_KERNEL);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ usb_fill_bulk_urb(entry, priv->udev,
+ usb_rcvbulkpipe(priv->udev, P54U_PIPE_DATA),
+ skb_tail_pointer(skb),
+ priv->common.rx_mtu + 32, p54u_rx_cb, skb);
+ info = (struct p54u_rx_info *) skb->cb;
+ info->urb = entry;
+ info->dev = dev;
+ skb_queue_tail(&priv->rx_queue, skb);
+
+ usb_anchor_urb(entry, &priv->submitted);
+ ret = usb_submit_urb(entry, GFP_KERNEL);
+ if (ret) {
+ skb_unlink(skb, &priv->rx_queue);
+ usb_unanchor_urb(entry);
+ goto err;
+ }
+ usb_free_urb(entry);
+ entry = NULL;
+ }
+
+ return 0;
+
+ err:
+ usb_free_urb(entry);
+ kfree_skb(skb);
+ p54u_free_urbs(dev);
+ return ret;
+}
+
+static int p54u_open(struct ieee80211_hw *dev)
+{
+ /*
+ * TODO: Because we don't know how to reliably stop the 3887 and
+ * the isl3886+net2280, other than brutally cut off all
+ * communications. We have to reinitialize the urbs on every start.
+ */
+ return p54u_init_urbs(dev);
+}
+
+static __le32 p54u_lm87_chksum(const __le32 *data, size_t length)
+{
+ u32 chk = 0;
+
+ length >>= 2;
+ while (length--) {
+ chk ^= le32_to_cpu(*data++);
+ chk = (chk >> 5) ^ (chk << 3);
+ }
+
+ return cpu_to_le32(chk);
+}
+
+static void p54u_tx_lm87(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ struct p54u_priv *priv = dev->priv;
+ struct urb *data_urb;
+ struct lm87_tx_hdr *hdr = (void *)skb->data - sizeof(*hdr);
+
+ data_urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!data_urb) {
+ p54_free_skb(dev, skb);
+ return;
+ }
+
+ hdr->chksum = p54u_lm87_chksum((__le32 *)skb->data, skb->len);
+ hdr->device_addr = ((struct p54_hdr *)skb->data)->req_id;
+
+ usb_fill_bulk_urb(data_urb, priv->udev,
+ usb_sndbulkpipe(priv->udev, P54U_PIPE_DATA),
+ hdr, skb->len + sizeof(*hdr), FREE_AFTER_TX(skb) ?
+ p54u_tx_cb : p54u_tx_dummy_cb, skb);
+ data_urb->transfer_flags |= URB_ZERO_PACKET;
+
+ usb_anchor_urb(data_urb, &priv->submitted);
+ if (usb_submit_urb(data_urb, GFP_ATOMIC)) {
+ usb_unanchor_urb(data_urb);
+ p54_free_skb(dev, skb);
+ }
+ usb_free_urb(data_urb);
+}
+
+static void p54u_tx_net2280(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ struct p54u_priv *priv = dev->priv;
+ struct urb *int_urb = NULL, *data_urb = NULL;
+ struct net2280_tx_hdr *hdr = (void *)skb->data - sizeof(*hdr);
+ struct net2280_reg_write *reg = NULL;
+ int err = -ENOMEM;
+
+ reg = kmalloc(sizeof(*reg), GFP_ATOMIC);
+ if (!reg)
+ goto out;
+
+ int_urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!int_urb)
+ goto out;
+
+ data_urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!data_urb)
+ goto out;
+
+ reg->port = cpu_to_le16(NET2280_DEV_U32);
+ reg->addr = cpu_to_le32(P54U_DEV_BASE);
+ reg->val = cpu_to_le32(ISL38XX_DEV_INT_DATA);
+
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->len = cpu_to_le16(skb->len);
+ hdr->device_addr = ((struct p54_hdr *) skb->data)->req_id;
+
+ usb_fill_bulk_urb(int_urb, priv->udev,
+ usb_sndbulkpipe(priv->udev, P54U_PIPE_DEV), reg, sizeof(*reg),
+ p54u_tx_dummy_cb, dev);
+
+ /*
+ * URB_FREE_BUFFER triggers a code path in the USB subsystem that will
+ * free what is inside the transfer_buffer after the last reference to
+ * the int_urb is dropped.
+ */
+ int_urb->transfer_flags |= URB_FREE_BUFFER | URB_ZERO_PACKET;
+ reg = NULL;
+
+ usb_fill_bulk_urb(data_urb, priv->udev,
+ usb_sndbulkpipe(priv->udev, P54U_PIPE_DATA),
+ hdr, skb->len + sizeof(*hdr), FREE_AFTER_TX(skb) ?
+ p54u_tx_cb : p54u_tx_dummy_cb, skb);
+ data_urb->transfer_flags |= URB_ZERO_PACKET;
+
+ usb_anchor_urb(int_urb, &priv->submitted);
+ err = usb_submit_urb(int_urb, GFP_ATOMIC);
+ if (err) {
+ usb_unanchor_urb(int_urb);
+ goto out;
+ }
+
+ usb_anchor_urb(data_urb, &priv->submitted);
+ err = usb_submit_urb(data_urb, GFP_ATOMIC);
+ if (err) {
+ usb_unanchor_urb(data_urb);
+ goto out;
+ }
+out:
+ usb_free_urb(int_urb);
+ usb_free_urb(data_urb);
+
+ if (err) {
+ kfree(reg);
+ p54_free_skb(dev, skb);
+ }
+}
+
+static int p54u_write(struct p54u_priv *priv,
+ struct net2280_reg_write *buf,
+ enum net2280_op_type type,
+ __le32 addr, __le32 val)
+{
+ unsigned int ep;
+ int alen;
+
+ if (type & 0x0800)
+ ep = usb_sndbulkpipe(priv->udev, P54U_PIPE_DEV);
+ else
+ ep = usb_sndbulkpipe(priv->udev, P54U_PIPE_BRG);
+
+ buf->port = cpu_to_le16(type);
+ buf->addr = addr;
+ buf->val = val;
+
+ return usb_bulk_msg(priv->udev, ep, buf, sizeof(*buf), &alen, 1000);
+}
+
+static int p54u_read(struct p54u_priv *priv, void *buf,
+ enum net2280_op_type type,
+ __le32 addr, __le32 *val)
+{
+ struct net2280_reg_read *read = buf;
+ __le32 *reg = buf;
+ unsigned int ep;
+ int alen, err;
+
+ if (type & 0x0800)
+ ep = P54U_PIPE_DEV;
+ else
+ ep = P54U_PIPE_BRG;
+
+ read->port = cpu_to_le16(type);
+ read->addr = addr;
+
+ err = usb_bulk_msg(priv->udev, usb_sndbulkpipe(priv->udev, ep),
+ read, sizeof(*read), &alen, 1000);
+ if (err)
+ return err;
+
+ err = usb_bulk_msg(priv->udev, usb_rcvbulkpipe(priv->udev, ep),
+ reg, sizeof(*reg), &alen, 1000);
+ if (err)
+ return err;
+
+ *val = *reg;
+ return 0;
+}
+
+static int p54u_bulk_msg(struct p54u_priv *priv, unsigned int ep,
+ void *data, size_t len)
+{
+ int alen;
+ return usb_bulk_msg(priv->udev, usb_sndbulkpipe(priv->udev, ep),
+ data, len, &alen, 2000);
+}
+
+static int p54u_device_reset(struct ieee80211_hw *dev)
+{
+ struct p54u_priv *priv = dev->priv;
+ int ret, lock = (priv->intf->condition != USB_INTERFACE_BINDING);
+
+ if (lock) {
+ ret = usb_lock_device_for_reset(priv->udev, priv->intf);
+ if (ret < 0) {
+ dev_err(&priv->udev->dev, "(p54usb) unable to lock "
+ "device for reset (%d)!\n", ret);
+ return ret;
+ }
+ }
+
+ ret = usb_reset_device(priv->udev);
+ if (lock)
+ usb_unlock_device(priv->udev);
+
+ if (ret)
+ dev_err(&priv->udev->dev, "(p54usb) unable to reset "
+ "device (%d)!\n", ret);
+
+ return ret;
+}
+
+static const char p54u_romboot_3887[] = "~~~~";
+static int p54u_firmware_reset_3887(struct ieee80211_hw *dev)
+{
+ struct p54u_priv *priv = dev->priv;
+ u8 *buf;
+ int ret;
+
+ buf = kmemdup(p54u_romboot_3887, 4, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ ret = p54u_bulk_msg(priv, P54U_PIPE_DATA,
+ buf, 4);
+ kfree(buf);
+ if (ret)
+ dev_err(&priv->udev->dev, "(p54usb) unable to jump to "
+ "boot ROM (%d)!\n", ret);
+
+ return ret;
+}
+
+static const char p54u_firmware_upload_3887[] = "<\r";
+static int p54u_upload_firmware_3887(struct ieee80211_hw *dev)
+{
+ struct p54u_priv *priv = dev->priv;
+ int err, alen;
+ u8 carry = 0;
+ u8 *buf, *tmp;
+ const u8 *data;
+ unsigned int left, remains, block_size;
+ struct x2_header *hdr;
+ unsigned long timeout;
+
+ err = p54u_firmware_reset_3887(dev);
+ if (err)
+ return err;
+
+ tmp = buf = kmalloc(P54U_FW_BLOCK, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ left = block_size = min_t(size_t, P54U_FW_BLOCK, priv->fw->size);
+ strcpy(buf, p54u_firmware_upload_3887);
+ left -= strlen(p54u_firmware_upload_3887);
+ tmp += strlen(p54u_firmware_upload_3887);
+
+ data = priv->fw->data;
+ remains = priv->fw->size;
+
+ hdr = (struct x2_header *)(buf + strlen(p54u_firmware_upload_3887));
+ memcpy(hdr->signature, X2_SIGNATURE, X2_SIGNATURE_SIZE);
+ hdr->fw_load_addr = cpu_to_le32(ISL38XX_DEV_FIRMWARE_ADDR);
+ hdr->fw_length = cpu_to_le32(priv->fw->size);
+ hdr->crc = cpu_to_le32(~crc32_le(~0, (void *)&hdr->fw_load_addr,
+ sizeof(u32)*2));
+ left -= sizeof(*hdr);
+ tmp += sizeof(*hdr);
+
+ while (remains) {
+ while (left--) {
+ if (carry) {
+ *tmp++ = carry;
+ carry = 0;
+ remains--;
+ continue;
+ }
+ switch (*data) {
+ case '~':
+ *tmp++ = '}';
+ carry = '^';
+ break;
+ case '}':
+ *tmp++ = '}';
+ carry = ']';
+ break;
+ default:
+ *tmp++ = *data;
+ remains--;
+ break;
+ }
+ data++;
+ }
+
+ err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, block_size);
+ if (err) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware "
+ "upload failed!\n");
+ goto err_upload_failed;
+ }
+
+ tmp = buf;
+ left = block_size = min((unsigned int)P54U_FW_BLOCK, remains);
+ }
+
+ *((__le32 *)buf) = cpu_to_le32(~crc32_le(~0, priv->fw->data,
+ priv->fw->size));
+ err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, sizeof(u32));
+ if (err) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware upload failed!\n");
+ goto err_upload_failed;
+ }
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (!(err = usb_bulk_msg(priv->udev,
+ usb_rcvbulkpipe(priv->udev, P54U_PIPE_DATA), buf, 128, &alen, 1000))) {
+ if (alen > 2 && !memcmp(buf, "OK", 2))
+ break;
+
+ if (alen > 5 && !memcmp(buf, "ERROR", 5)) {
+ err = -EINVAL;
+ break;
+ }
+
+ if (time_after(jiffies, timeout)) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware boot "
+ "timed out!\n");
+ err = -ETIMEDOUT;
+ break;
+ }
+ }
+ if (err) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware upload failed!\n");
+ goto err_upload_failed;
+ }
+
+ buf[0] = 'g';
+ buf[1] = '\r';
+ err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, 2);
+ if (err) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware boot failed!\n");
+ goto err_upload_failed;
+ }
+
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (!(err = usb_bulk_msg(priv->udev,
+ usb_rcvbulkpipe(priv->udev, P54U_PIPE_DATA), buf, 128, &alen, 1000))) {
+ if (alen > 0 && buf[0] == 'g')
+ break;
+
+ if (time_after(jiffies, timeout)) {
+ err = -ETIMEDOUT;
+ break;
+ }
+ }
+ if (err)
+ goto err_upload_failed;
+
+err_upload_failed:
+ kfree(buf);
+ return err;
+}
+
+static int p54u_upload_firmware_net2280(struct ieee80211_hw *dev)
+{
+ struct p54u_priv *priv = dev->priv;
+ const struct p54p_csr *devreg = (const struct p54p_csr *) P54U_DEV_BASE;
+ int err, alen;
+ void *buf;
+ __le32 reg;
+ unsigned int remains, offset;
+ const u8 *data;
+
+ buf = kmalloc(512, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+#define P54U_WRITE(type, addr, data) \
+ do {\
+ err = p54u_write(priv, buf, type,\
+ cpu_to_le32((u32)(unsigned long)addr), data);\
+ if (err) \
+ goto fail;\
+ } while (0)
+
+#define P54U_READ(type, addr) \
+ do {\
+ err = p54u_read(priv, buf, type,\
+ cpu_to_le32((u32)(unsigned long)addr), &reg);\
+ if (err)\
+ goto fail;\
+ } while (0)
+
+ /* power down net2280 bridge */
+ P54U_READ(NET2280_BRG_U32, NET2280_GPIOCTL);
+ reg |= cpu_to_le32(P54U_BRG_POWER_DOWN);
+ reg &= cpu_to_le32(~P54U_BRG_POWER_UP);
+ P54U_WRITE(NET2280_BRG_U32, NET2280_GPIOCTL, reg);
+
+ mdelay(100);
+
+ /* power up bridge */
+ reg |= cpu_to_le32(P54U_BRG_POWER_UP);
+ reg &= cpu_to_le32(~P54U_BRG_POWER_DOWN);
+ P54U_WRITE(NET2280_BRG_U32, NET2280_GPIOCTL, reg);
+
+ mdelay(100);
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_DEVINIT,
+ cpu_to_le32(NET2280_CLK_30Mhz |
+ NET2280_PCI_ENABLE |
+ NET2280_PCI_SOFT_RESET));
+
+ mdelay(20);
+
+ P54U_WRITE(NET2280_BRG_CFG_U16, PCI_COMMAND,
+ cpu_to_le32(PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER));
+
+ P54U_WRITE(NET2280_BRG_CFG_U32, PCI_BASE_ADDRESS_0,
+ cpu_to_le32(NET2280_BASE));
+
+ P54U_READ(NET2280_BRG_CFG_U16, PCI_STATUS);
+ reg |= cpu_to_le32(PCI_STATUS_REC_MASTER_ABORT);
+ P54U_WRITE(NET2280_BRG_CFG_U16, PCI_STATUS, reg);
+
+ // TODO: we really need this?
+ P54U_READ(NET2280_BRG_U32, NET2280_RELNUM);
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_EPA_RSP,
+ cpu_to_le32(NET2280_CLEAR_NAK_OUT_PACKETS_MODE));
+ P54U_WRITE(NET2280_BRG_U32, NET2280_EPC_RSP,
+ cpu_to_le32(NET2280_CLEAR_NAK_OUT_PACKETS_MODE));
+
+ P54U_WRITE(NET2280_BRG_CFG_U32, PCI_BASE_ADDRESS_2,
+ cpu_to_le32(NET2280_BASE2));
+
+ /* finally done setting up the bridge */
+
+ P54U_WRITE(NET2280_DEV_CFG_U16, 0x10000 | PCI_COMMAND,
+ cpu_to_le32(PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER));
+
+ P54U_WRITE(NET2280_DEV_CFG_U16, 0x10000 | 0x40 /* TRDY timeout */, 0);
+ P54U_WRITE(NET2280_DEV_CFG_U32, 0x10000 | PCI_BASE_ADDRESS_0,
+ cpu_to_le32(P54U_DEV_BASE));
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_USBIRQENB1, 0);
+ P54U_WRITE(NET2280_BRG_U32, NET2280_IRQSTAT1,
+ cpu_to_le32(NET2280_PCI_INTA_INTERRUPT));
+
+ /* do romboot */
+ P54U_WRITE(NET2280_DEV_U32, &devreg->int_enable, 0);
+
+ P54U_READ(NET2280_DEV_U32, &devreg->ctrl_stat);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RAMBOOT);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_CLKRUN);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg);
+
+ mdelay(20);
+
+ reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg);
+
+ mdelay(20);
+
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg);
+
+ mdelay(100);
+
+ P54U_READ(NET2280_DEV_U32, &devreg->int_ident);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->int_ack, reg);
+
+ /* finally, we can upload firmware now! */
+ remains = priv->fw->size;
+ data = priv->fw->data;
+ offset = ISL38XX_DEV_FIRMWARE_ADDR;
+
+ while (remains) {
+ unsigned int block_len = min(remains, (unsigned int)512);
+ memcpy(buf, data, block_len);
+
+ err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, block_len);
+ if (err) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware block "
+ "upload failed\n");
+ goto fail;
+ }
+
+ P54U_WRITE(NET2280_DEV_U32, &devreg->direct_mem_base,
+ cpu_to_le32(0xc0000f00));
+
+ P54U_WRITE(NET2280_DEV_U32,
+ 0x0020 | (unsigned long)&devreg->direct_mem_win, 0);
+ P54U_WRITE(NET2280_DEV_U32,
+ 0x0020 | (unsigned long)&devreg->direct_mem_win,
+ cpu_to_le32(1));
+
+ P54U_WRITE(NET2280_DEV_U32,
+ 0x0024 | (unsigned long)&devreg->direct_mem_win,
+ cpu_to_le32(block_len));
+ P54U_WRITE(NET2280_DEV_U32,
+ 0x0028 | (unsigned long)&devreg->direct_mem_win,
+ cpu_to_le32(offset));
+
+ P54U_WRITE(NET2280_DEV_U32, &devreg->dma_addr,
+ cpu_to_le32(NET2280_EPA_FIFO_PCI_ADDR));
+ P54U_WRITE(NET2280_DEV_U32, &devreg->dma_len,
+ cpu_to_le32(block_len >> 2));
+ P54U_WRITE(NET2280_DEV_U32, &devreg->dma_ctrl,
+ cpu_to_le32(ISL38XX_DMA_MASTER_CONTROL_TRIGGER));
+
+ mdelay(10);
+
+ P54U_READ(NET2280_DEV_U32,
+ 0x002C | (unsigned long)&devreg->direct_mem_win);
+ if (!(reg & cpu_to_le32(ISL38XX_DMA_STATUS_DONE)) ||
+ !(reg & cpu_to_le32(ISL38XX_DMA_STATUS_READY))) {
+ dev_err(&priv->udev->dev, "(p54usb) firmware DMA "
+ "transfer failed\n");
+ goto fail;
+ }
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_EPA_STAT,
+ cpu_to_le32(NET2280_FIFO_FLUSH));
+
+ remains -= block_len;
+ data += block_len;
+ offset += block_len;
+ }
+
+ /* do ramboot */
+ P54U_READ(NET2280_DEV_U32, &devreg->ctrl_stat);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_CLKRUN);
+ reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RAMBOOT);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg);
+
+ mdelay(20);
+
+ reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg);
+
+ reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg);
+
+ mdelay(100);
+
+ P54U_READ(NET2280_DEV_U32, &devreg->int_ident);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->int_ack, reg);
+
+ /* start up the firmware */
+ P54U_WRITE(NET2280_DEV_U32, &devreg->int_enable,
+ cpu_to_le32(ISL38XX_INT_IDENT_INIT));
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_IRQSTAT1,
+ cpu_to_le32(NET2280_PCI_INTA_INTERRUPT));
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_USBIRQENB1,
+ cpu_to_le32(NET2280_PCI_INTA_INTERRUPT_ENABLE |
+ NET2280_USB_INTERRUPT_ENABLE));
+
+ P54U_WRITE(NET2280_DEV_U32, &devreg->dev_int,
+ cpu_to_le32(ISL38XX_DEV_INT_RESET));
+
+ err = usb_interrupt_msg(priv->udev,
+ usb_rcvbulkpipe(priv->udev, P54U_PIPE_INT),
+ buf, sizeof(__le32), &alen, 1000);
+ if (err || alen != sizeof(__le32))
+ goto fail;
+
+ P54U_READ(NET2280_DEV_U32, &devreg->int_ident);
+ P54U_WRITE(NET2280_DEV_U32, &devreg->int_ack, reg);
+
+ if (!(reg & cpu_to_le32(ISL38XX_INT_IDENT_INIT)))
+ err = -EINVAL;
+
+ P54U_WRITE(NET2280_BRG_U32, NET2280_USBIRQENB1, 0);
+ P54U_WRITE(NET2280_BRG_U32, NET2280_IRQSTAT1,
+ cpu_to_le32(NET2280_PCI_INTA_INTERRUPT));
+
+#undef P54U_WRITE
+#undef P54U_READ
+
+fail:
+ kfree(buf);
+ return err;
+}
+
+static int p54_find_type(struct p54u_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < __NUM_P54U_HWTYPES; i++)
+ if (p54u_fwlist[i].type == priv->hw_type)
+ break;
+ if (i == __NUM_P54U_HWTYPES)
+ return -EOPNOTSUPP;
+
+ return i;
+}
+
+static int p54u_start_ops(struct p54u_priv *priv)
+{
+ struct ieee80211_hw *dev = priv->common.hw;
+ int ret;
+
+ ret = p54_parse_firmware(dev, priv->fw);
+ if (ret)
+ goto err_out;
+
+ ret = p54_find_type(priv);
+ if (ret < 0)
+ goto err_out;
+
+ if (priv->common.fw_interface != p54u_fwlist[ret].intf) {
+ dev_err(&priv->udev->dev, "wrong firmware, please get "
+ "a firmware for \"%s\" and try again.\n",
+ p54u_fwlist[ret].hw);
+ ret = -ENODEV;
+ goto err_out;
+ }
+
+ ret = priv->upload_fw(dev);
+ if (ret)
+ goto err_out;
+
+ ret = p54u_open(dev);
+ if (ret)
+ goto err_out;
+
+ ret = p54_read_eeprom(dev);
+ if (ret)
+ goto err_stop;
+
+ p54u_stop(dev);
+
+ ret = p54_register_common(dev, &priv->udev->dev);
+ if (ret)
+ goto err_stop;
+
+ return 0;
+
+err_stop:
+ p54u_stop(dev);
+
+err_out:
+ /*
+ * p54u_disconnect will do the rest of the
+ * cleanup
+ */
+ return ret;
+}
+
+static void p54u_load_firmware_cb(const struct firmware *firmware,
+ void *context)
+{
+ struct p54u_priv *priv = context;
+ struct usb_device *udev = priv->udev;
+ int err;
+
+ complete(&priv->fw_wait_load);
+ if (firmware) {
+ priv->fw = firmware;
+ err = p54u_start_ops(priv);
+ } else {
+ err = -ENOENT;
+ dev_err(&udev->dev, "Firmware not found.\n");
+ }
+
+ if (err) {
+ struct device *parent = priv->udev->dev.parent;
+
+ dev_err(&udev->dev, "failed to initialize device (%d)\n", err);
+
+ if (parent)
+ device_lock(parent);
+
+ device_release_driver(&udev->dev);
+ /*
+ * At this point p54u_disconnect has already freed
+ * the "priv" context. Do not use it anymore!
+ */
+ priv = NULL;
+
+ if (parent)
+ device_unlock(parent);
+ }
+
+ usb_put_dev(udev);
+}
+
+static int p54u_load_firmware(struct ieee80211_hw *dev,
+ struct usb_interface *intf)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct p54u_priv *priv = dev->priv;
+ struct device *device = &udev->dev;
+ int err, i;
+
+ BUILD_BUG_ON(ARRAY_SIZE(p54u_fwlist) != __NUM_P54U_HWTYPES);
+
+ init_completion(&priv->fw_wait_load);
+ i = p54_find_type(priv);
+ if (i < 0)
+ return i;
+
+ dev_info(&priv->udev->dev, "Loading firmware file %s\n",
+ p54u_fwlist[i].fw);
+
+ usb_get_dev(udev);
+ err = request_firmware_nowait(THIS_MODULE, 1, p54u_fwlist[i].fw,
+ device, GFP_KERNEL, priv,
+ p54u_load_firmware_cb);
+ if (err) {
+ dev_err(&priv->udev->dev, "(p54usb) cannot load firmware %s "
+ "(%d)!\n", p54u_fwlist[i].fw, err);
+ usb_put_dev(udev);
+ }
+
+ return err;
+}
+
+static int p54u_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct ieee80211_hw *dev;
+ struct p54u_priv *priv;
+ int err;
+ unsigned int i, recognized_pipes;
+
+ dev = p54_init_common(sizeof(*priv));
+
+ if (!dev) {
+ dev_err(&udev->dev, "(p54usb) ieee80211 alloc failed\n");
+ return -ENOMEM;
+ }
+
+ priv = dev->priv;
+ priv->hw_type = P54U_INVALID_HW;
+
+ SET_IEEE80211_DEV(dev, &intf->dev);
+ usb_set_intfdata(intf, dev);
+ priv->udev = udev;
+ priv->intf = intf;
+ skb_queue_head_init(&priv->rx_queue);
+ init_usb_anchor(&priv->submitted);
+
+ usb_get_dev(udev);
+
+ /* really lazy and simple way of figuring out if we're a 3887 */
+ /* TODO: should just stick the identification in the device table */
+ i = intf->altsetting->desc.bNumEndpoints;
+ recognized_pipes = 0;
+ while (i--) {
+ switch (intf->altsetting->endpoint[i].desc.bEndpointAddress) {
+ case P54U_PIPE_DATA:
+ case P54U_PIPE_MGMT:
+ case P54U_PIPE_BRG:
+ case P54U_PIPE_DEV:
+ case P54U_PIPE_DATA | USB_DIR_IN:
+ case P54U_PIPE_MGMT | USB_DIR_IN:
+ case P54U_PIPE_BRG | USB_DIR_IN:
+ case P54U_PIPE_DEV | USB_DIR_IN:
+ case P54U_PIPE_INT | USB_DIR_IN:
+ recognized_pipes++;
+ }
+ }
+ priv->common.open = p54u_open;
+ priv->common.stop = p54u_stop;
+ if (recognized_pipes < P54U_PIPE_NUMBER) {
+#ifdef CONFIG_PM
+ /* ISL3887 needs a full reset on resume */
+ udev->reset_resume = 1;
+#endif /* CONFIG_PM */
+ err = p54u_device_reset(dev);
+
+ priv->hw_type = P54U_3887;
+ dev->extra_tx_headroom += sizeof(struct lm87_tx_hdr);
+ priv->common.tx_hdr_len = sizeof(struct lm87_tx_hdr);
+ priv->common.tx = p54u_tx_lm87;
+ priv->upload_fw = p54u_upload_firmware_3887;
+ } else {
+ priv->hw_type = P54U_NET2280;
+ dev->extra_tx_headroom += sizeof(struct net2280_tx_hdr);
+ priv->common.tx_hdr_len = sizeof(struct net2280_tx_hdr);
+ priv->common.tx = p54u_tx_net2280;
+ priv->upload_fw = p54u_upload_firmware_net2280;
+ }
+ err = p54u_load_firmware(dev, intf);
+ if (err) {
+ usb_put_dev(udev);
+ p54_free_common(dev);
+ }
+ return err;
+}
+
+static void p54u_disconnect(struct usb_interface *intf)
+{
+ struct ieee80211_hw *dev = usb_get_intfdata(intf);
+ struct p54u_priv *priv;
+
+ if (!dev)
+ return;
+
+ priv = dev->priv;
+ wait_for_completion(&priv->fw_wait_load);
+ p54_unregister_common(dev);
+
+ usb_put_dev(interface_to_usbdev(intf));
+ release_firmware(priv->fw);
+ p54_free_common(dev);
+}
+
+static int p54u_pre_reset(struct usb_interface *intf)
+{
+ struct ieee80211_hw *dev = usb_get_intfdata(intf);
+
+ if (!dev)
+ return -ENODEV;
+
+ p54u_stop(dev);
+ return 0;
+}
+
+static int p54u_resume(struct usb_interface *intf)
+{
+ struct ieee80211_hw *dev = usb_get_intfdata(intf);
+ struct p54u_priv *priv;
+
+ if (!dev)
+ return -ENODEV;
+
+ priv = dev->priv;
+ if (unlikely(!(priv->upload_fw && priv->fw)))
+ return 0;
+
+ return priv->upload_fw(dev);
+}
+
+static int p54u_post_reset(struct usb_interface *intf)
+{
+ struct ieee80211_hw *dev = usb_get_intfdata(intf);
+ struct p54u_priv *priv;
+ int err;
+
+ err = p54u_resume(intf);
+ if (err)
+ return err;
+
+ /* reinitialize old device state */
+ priv = dev->priv;
+ if (priv->common.mode != NL80211_IFTYPE_UNSPECIFIED)
+ ieee80211_restart_hw(dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int p54u_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ return p54u_pre_reset(intf);
+}
+
+#endif /* CONFIG_PM */
+
+static struct usb_driver p54u_driver = {
+ .name = "p54usb",
+ .id_table = p54u_table,
+ .probe = p54u_probe,
+ .disconnect = p54u_disconnect,
+ .pre_reset = p54u_pre_reset,
+ .post_reset = p54u_post_reset,
+#ifdef CONFIG_PM
+ .suspend = p54u_suspend,
+ .resume = p54u_resume,
+ .reset_resume = p54u_resume,
+#endif /* CONFIG_PM */
+ .soft_unbind = 1,
+#if LINUX_VERSION_IS_GEQ(3,5,0)
+ .disable_hub_initiated_lpm = 1,
+#endif
+};
+
+module_usb_driver(p54u_driver);
diff --git a/drivers/net/wireless/intersil/p54/p54usb.h b/drivers/net/wireless/intersil/p54/p54usb.h
new file mode 100644
index 0000000..a5f5f0f
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/p54usb.h
@@ -0,0 +1,162 @@
+#ifndef P54USB_H
+#define P54USB_H
+
+/*
+ * Defines for USB based mac80211 Prism54 driver
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ *
+ * Based on the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* for isl3886 register definitions used on ver 1 devices */
+#include "p54pci.h"
+#include <linux/usb/net2280.h>
+
+/* pci */
+#define NET2280_BASE 0x10000000
+#define NET2280_BASE2 0x20000000
+
+/* gpio */
+#define P54U_BRG_POWER_UP (1 << GPIO0_DATA)
+#define P54U_BRG_POWER_DOWN (1 << GPIO1_DATA)
+
+/* devinit */
+#define NET2280_CLK_4Mhz (15 << LOCAL_CLOCK_FREQUENCY)
+#define NET2280_CLK_30Mhz (2 << LOCAL_CLOCK_FREQUENCY)
+#define NET2280_CLK_60Mhz (1 << LOCAL_CLOCK_FREQUENCY)
+#define NET2280_CLK_STOP (0 << LOCAL_CLOCK_FREQUENCY)
+#define NET2280_PCI_ENABLE (1 << PCI_ENABLE)
+#define NET2280_PCI_SOFT_RESET (1 << PCI_SOFT_RESET)
+
+/* endpoints */
+#define NET2280_CLEAR_NAK_OUT_PACKETS_MODE (1 << CLEAR_NAK_OUT_PACKETS_MODE)
+#define NET2280_FIFO_FLUSH (1 << FIFO_FLUSH)
+
+/* irq */
+#define NET2280_USB_INTERRUPT_ENABLE (1 << USB_INTERRUPT_ENABLE)
+#define NET2280_PCI_INTA_INTERRUPT (1 << PCI_INTA_INTERRUPT)
+#define NET2280_PCI_INTA_INTERRUPT_ENABLE (1 << PCI_INTA_INTERRUPT_ENABLE)
+
+/* registers */
+#define NET2280_DEVINIT 0x00
+#define NET2280_USBIRQENB1 0x24
+#define NET2280_IRQSTAT1 0x2c
+#define NET2280_FIFOCTL 0x38
+#define NET2280_GPIOCTL 0x50
+#define NET2280_RELNUM 0x88
+#define NET2280_EPA_RSP 0x324
+#define NET2280_EPA_STAT 0x32c
+#define NET2280_EPB_STAT 0x34c
+#define NET2280_EPC_RSP 0x364
+#define NET2280_EPC_STAT 0x36c
+#define NET2280_EPD_STAT 0x38c
+
+#define NET2280_EPA_CFG 0x320
+#define NET2280_EPB_CFG 0x340
+#define NET2280_EPC_CFG 0x360
+#define NET2280_EPD_CFG 0x380
+#define NET2280_EPE_CFG 0x3A0
+#define NET2280_EPF_CFG 0x3C0
+#define P54U_DEV_BASE 0x40000000
+
+struct net2280_tx_hdr {
+ __le32 device_addr;
+ __le16 len;
+ __le16 follower; /* ? */
+ u8 padding[8];
+} __packed;
+
+struct lm87_tx_hdr {
+ __le32 device_addr;
+ __le32 chksum;
+} __packed;
+
+/* Some flags for the isl hardware registers controlling DMA inside the
+ * chip */
+#define ISL38XX_DMA_STATUS_DONE 0x00000001
+#define ISL38XX_DMA_STATUS_READY 0x00000002
+#define NET2280_EPA_FIFO_PCI_ADDR 0x20000000
+#define ISL38XX_DMA_MASTER_CONTROL_TRIGGER 0x00000004
+
+enum net2280_op_type {
+ NET2280_BRG_U32 = 0x001F,
+ NET2280_BRG_CFG_U32 = 0x000F,
+ NET2280_BRG_CFG_U16 = 0x0003,
+ NET2280_DEV_U32 = 0x080F,
+ NET2280_DEV_CFG_U32 = 0x088F,
+ NET2280_DEV_CFG_U16 = 0x0883
+};
+
+struct net2280_reg_write {
+ __le16 port;
+ __le32 addr;
+ __le32 val;
+} __packed;
+
+struct net2280_reg_read {
+ __le16 port;
+ __le32 addr;
+} __packed;
+
+#define P54U_FW_BLOCK 2048
+
+#define X2_SIGNATURE "x2 "
+#define X2_SIGNATURE_SIZE 4
+
+struct x2_header {
+ u8 signature[X2_SIGNATURE_SIZE];
+ __le32 fw_load_addr;
+ __le32 fw_length;
+ __le32 crc;
+} __packed;
+
+/* pipes 3 and 4 are not used by the driver */
+#define P54U_PIPE_NUMBER 9
+
+enum p54u_pipe_addr {
+ P54U_PIPE_DATA = 0x01,
+ P54U_PIPE_MGMT = 0x02,
+ P54U_PIPE_3 = 0x03,
+ P54U_PIPE_4 = 0x04,
+ P54U_PIPE_BRG = 0x0d,
+ P54U_PIPE_DEV = 0x0e,
+ P54U_PIPE_INT = 0x0f
+};
+
+struct p54u_rx_info {
+ struct urb *urb;
+ struct ieee80211_hw *dev;
+};
+
+enum p54u_hw_type {
+ P54U_INVALID_HW,
+ P54U_NET2280,
+ P54U_3887,
+
+ /* keep last */
+ __NUM_P54U_HWTYPES,
+};
+
+struct p54u_priv {
+ struct p54_common common;
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ int (*upload_fw)(struct ieee80211_hw *dev);
+
+ enum p54u_hw_type hw_type;
+ spinlock_t lock;
+ struct sk_buff_head rx_queue;
+ struct usb_anchor submitted;
+ const struct firmware *fw;
+
+ /* asynchronous firmware callback */
+ struct completion fw_wait_load;
+};
+
+#endif /* P54USB_H */
diff --git a/drivers/net/wireless/intersil/p54/txrx.c b/drivers/net/wireless/intersil/p54/txrx.c
new file mode 100644
index 0000000..3a4214d
--- /dev/null
+++ b/drivers/net/wireless/intersil/p54/txrx.c
@@ -0,0 +1,940 @@
+/*
+ * Common code for mac80211 Prism54 drivers
+ *
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ * Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
+ *
+ * Based on:
+ * - the islsm (softmac prism54) driver, which is:
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ * - stlc45xx driver
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/export.h>
+#include <linux/firmware.h>
+#include <linux/etherdevice.h>
+#include <asm/div64.h>
+
+#include <net/mac80211.h>
+
+#include "p54.h"
+#include "lmac.h"
+
+#ifdef P54_MM_DEBUG
+static void p54_dump_tx_queue(struct p54_common *priv)
+{
+ unsigned long flags;
+ struct ieee80211_tx_info *info;
+ struct p54_tx_info *range;
+ struct sk_buff *skb;
+ struct p54_hdr *hdr;
+ unsigned int i = 0;
+ u32 prev_addr;
+ u32 largest_hole = 0, free;
+
+ spin_lock_irqsave(&priv->tx_queue.lock, flags);
+ wiphy_debug(priv->hw->wiphy, "/ --- tx queue dump (%d entries) ---\n",
+ skb_queue_len(&priv->tx_queue));
+
+ prev_addr = priv->rx_start;
+ skb_queue_walk(&priv->tx_queue, skb) {
+ info = IEEE80211_SKB_CB(skb);
+ range = (void *) info->rate_driver_data;
+ hdr = (void *) skb->data;
+
+ free = range->start_addr - prev_addr;
+ wiphy_debug(priv->hw->wiphy,
+ "| [%02d] => [skb:%p skb_len:0x%04x "
+ "hdr:{flags:%02x len:%04x req_id:%04x type:%02x} "
+ "mem:{start:%04x end:%04x, free:%d}]\n",
+ i++, skb, skb->len,
+ le16_to_cpu(hdr->flags), le16_to_cpu(hdr->len),
+ le32_to_cpu(hdr->req_id), le16_to_cpu(hdr->type),
+ range->start_addr, range->end_addr, free);
+
+ prev_addr = range->end_addr;
+ largest_hole = max(largest_hole, free);
+ }
+ free = priv->rx_end - prev_addr;
+ largest_hole = max(largest_hole, free);
+ wiphy_debug(priv->hw->wiphy,
+ "\\ --- [free: %d], largest free block: %d ---\n",
+ free, largest_hole);
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+}
+#endif /* P54_MM_DEBUG */
+
+/*
+ * So, the firmware is somewhat stupid and doesn't know what places in its
+ * memory incoming data should go to. By poking around in the firmware, we
+ * can find some unused memory to upload our packets to. However, data that we
+ * want the card to TX needs to stay intact until the card has told us that
+ * it is done with it. This function finds empty places we can upload to and
+ * marks allocated areas as reserved if necessary. p54_find_and_unlink_skb or
+ * p54_free_skb frees allocated areas.
+ */
+static int p54_assign_address(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct sk_buff *entry, *target_skb = NULL;
+ struct ieee80211_tx_info *info;
+ struct p54_tx_info *range;
+ struct p54_hdr *data = (void *) skb->data;
+ unsigned long flags;
+ u32 last_addr = priv->rx_start;
+ u32 target_addr = priv->rx_start;
+ u16 len = priv->headroom + skb->len + priv->tailroom + 3;
+
+ info = IEEE80211_SKB_CB(skb);
+ range = (void *) info->rate_driver_data;
+ len = (range->extra_len + len) & ~0x3;
+
+ spin_lock_irqsave(&priv->tx_queue.lock, flags);
+ if (unlikely(skb_queue_len(&priv->tx_queue) == 32)) {
+ /*
+ * The tx_queue is now really full.
+ *
+ * TODO: check if the device has crashed and reset it.
+ */
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+ return -EBUSY;
+ }
+
+ skb_queue_walk(&priv->tx_queue, entry) {
+ u32 hole_size;
+ info = IEEE80211_SKB_CB(entry);
+ range = (void *) info->rate_driver_data;
+ hole_size = range->start_addr - last_addr;
+
+ if (!target_skb && hole_size >= len) {
+ target_skb = entry->prev;
+ hole_size -= len;
+ target_addr = last_addr;
+ break;
+ }
+ last_addr = range->end_addr;
+ }
+ if (unlikely(!target_skb)) {
+ if (priv->rx_end - last_addr >= len) {
+ target_skb = priv->tx_queue.prev;
+ if (!skb_queue_empty(&priv->tx_queue)) {
+ info = IEEE80211_SKB_CB(target_skb);
+ range = (void *)info->rate_driver_data;
+ target_addr = range->end_addr;
+ }
+ } else {
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+ return -ENOSPC;
+ }
+ }
+
+ info = IEEE80211_SKB_CB(skb);
+ range = (void *) info->rate_driver_data;
+ range->start_addr = target_addr;
+ range->end_addr = target_addr + len;
+ data->req_id = cpu_to_le32(target_addr + priv->headroom);
+ if (IS_DATA_FRAME(skb) &&
+ unlikely(GET_HW_QUEUE(skb) == P54_QUEUE_BEACON))
+ priv->beacon_req_id = data->req_id;
+
+ __skb_queue_after(&priv->tx_queue, target_skb, skb);
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+ return 0;
+}
+
+static void p54_tx_pending(struct p54_common *priv)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ skb = skb_dequeue(&priv->tx_pending);
+ if (unlikely(!skb))
+ return ;
+
+ ret = p54_assign_address(priv, skb);
+ if (unlikely(ret))
+ skb_queue_head(&priv->tx_pending, skb);
+ else
+ priv->tx(priv->hw, skb);
+}
+
+static void p54_wake_queues(struct p54_common *priv)
+{
+ unsigned long flags;
+ unsigned int i;
+
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED))
+ return ;
+
+ p54_tx_pending(priv);
+
+ spin_lock_irqsave(&priv->tx_stats_lock, flags);
+ for (i = 0; i < priv->hw->queues; i++) {
+ if (priv->tx_stats[i + P54_QUEUE_DATA].len <
+ priv->tx_stats[i + P54_QUEUE_DATA].limit)
+ ieee80211_wake_queue(priv->hw, i);
+ }
+ spin_unlock_irqrestore(&priv->tx_stats_lock, flags);
+}
+
+static int p54_tx_qos_accounting_alloc(struct p54_common *priv,
+ struct sk_buff *skb,
+ const u16 p54_queue)
+{
+ struct p54_tx_queue_stats *queue;
+ unsigned long flags;
+
+ if (WARN_ON(p54_queue >= P54_QUEUE_NUM))
+ return -EINVAL;
+
+ queue = &priv->tx_stats[p54_queue];
+
+ spin_lock_irqsave(&priv->tx_stats_lock, flags);
+ if (unlikely(queue->len >= queue->limit && IS_QOS_QUEUE(p54_queue))) {
+ spin_unlock_irqrestore(&priv->tx_stats_lock, flags);
+ return -ENOSPC;
+ }
+
+ queue->len++;
+ queue->count++;
+
+ if (unlikely(queue->len == queue->limit && IS_QOS_QUEUE(p54_queue))) {
+ u16 ac_queue = p54_queue - P54_QUEUE_DATA;
+ ieee80211_stop_queue(priv->hw, ac_queue);
+ }
+
+ spin_unlock_irqrestore(&priv->tx_stats_lock, flags);
+ return 0;
+}
+
+static void p54_tx_qos_accounting_free(struct p54_common *priv,
+ struct sk_buff *skb)
+{
+ if (IS_DATA_FRAME(skb)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->tx_stats_lock, flags);
+ priv->tx_stats[GET_HW_QUEUE(skb)].len--;
+ spin_unlock_irqrestore(&priv->tx_stats_lock, flags);
+
+ if (unlikely(GET_HW_QUEUE(skb) == P54_QUEUE_BEACON)) {
+ if (priv->beacon_req_id == GET_REQ_ID(skb)) {
+ /* this is the active beacon set anymore */
+ priv->beacon_req_id = 0;
+ }
+ complete(&priv->beacon_comp);
+ }
+ }
+ p54_wake_queues(priv);
+}
+
+void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ struct p54_common *priv = dev->priv;
+ if (unlikely(!skb))
+ return ;
+
+ skb_unlink(skb, &priv->tx_queue);
+ p54_tx_qos_accounting_free(priv, skb);
+ ieee80211_free_txskb(dev, skb);
+}
+EXPORT_SYMBOL_GPL(p54_free_skb);
+
+static struct sk_buff *p54_find_and_unlink_skb(struct p54_common *priv,
+ const __le32 req_id)
+{
+ struct sk_buff *entry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->tx_queue.lock, flags);
+ skb_queue_walk(&priv->tx_queue, entry) {
+ struct p54_hdr *hdr = (struct p54_hdr *) entry->data;
+
+ if (hdr->req_id == req_id) {
+ __skb_unlink(entry, &priv->tx_queue);
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+ p54_tx_qos_accounting_free(priv, entry);
+ return entry;
+ }
+ }
+ spin_unlock_irqrestore(&priv->tx_queue.lock, flags);
+ return NULL;
+}
+
+void p54_tx(struct p54_common *priv, struct sk_buff *skb)
+{
+ skb_queue_tail(&priv->tx_pending, skb);
+ p54_tx_pending(priv);
+}
+
+static int p54_rssi_to_dbm(struct p54_common *priv, int rssi)
+{
+ if (priv->rxhw != 5) {
+ return ((rssi * priv->cur_rssi->mul) / 64 +
+ priv->cur_rssi->add) / 4;
+ } else {
+ /*
+ * TODO: find the correct formula
+ */
+ return rssi / 2 - 110;
+ }
+}
+
+/*
+ * Even if the firmware is capable of dealing with incoming traffic,
+ * while dozing, we have to prepared in case mac80211 uses PS-POLL
+ * to retrieve outstanding frames from our AP.
+ * (see comment in net/mac80211/mlme.c @ line 1993)
+ */
+static void p54_pspoll_workaround(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr = (void *) skb->data;
+ struct ieee80211_tim_ie *tim_ie;
+ u8 *tim;
+ u8 tim_len;
+ bool new_psm;
+
+ /* only beacons have a TIM IE */
+ if (!ieee80211_is_beacon(hdr->frame_control))
+ return;
+
+ if (!priv->aid)
+ return;
+
+ /* only consider beacons from the associated BSSID */
+ if (!ether_addr_equal_64bits(hdr->addr3, priv->bssid))
+ return;
+
+ tim = p54_find_ie(skb, WLAN_EID_TIM);
+ if (!tim)
+ return;
+
+ tim_len = tim[1];
+ tim_ie = (struct ieee80211_tim_ie *) &tim[2];
+
+ new_psm = ieee80211_check_tim(tim_ie, tim_len, priv->aid);
+ if (new_psm != priv->powersave_override) {
+ priv->powersave_override = new_psm;
+ p54_set_ps(priv);
+ }
+}
+
+static int p54_rx_data(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct p54_rx_data *hdr = (struct p54_rx_data *) skb->data;
+ struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
+ u16 freq = le16_to_cpu(hdr->freq);
+ size_t header_len = sizeof(*hdr);
+ u32 tsf32;
+ u8 rate = hdr->rate & 0xf;
+
+ /*
+ * If the device is in a unspecified state we have to
+ * ignore all data frames. Else we could end up with a
+ * nasty crash.
+ */
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED))
+ return 0;
+
+ if (!(hdr->flags & cpu_to_le16(P54_HDR_FLAG_DATA_IN_FCS_GOOD)))
+ return 0;
+
+ if (hdr->decrypt_status == P54_DECRYPT_OK)
+ rx_status->flag |= RX_FLAG_DECRYPTED;
+ if ((hdr->decrypt_status == P54_DECRYPT_FAIL_MICHAEL) ||
+ (hdr->decrypt_status == P54_DECRYPT_FAIL_TKIP))
+ rx_status->flag |= RX_FLAG_MMIC_ERROR;
+
+ rx_status->signal = p54_rssi_to_dbm(priv, hdr->rssi);
+ if (hdr->rate & 0x10)
+ rx_status->enc_flags |= RX_ENC_FLAG_SHORTPRE;
+ if (priv->hw->conf.chandef.chan->band == NL80211_BAND_5GHZ)
+ rx_status->rate_idx = (rate < 4) ? 0 : rate - 4;
+ else
+ rx_status->rate_idx = rate;
+
+ rx_status->freq = freq;
+ rx_status->band = priv->hw->conf.chandef.chan->band;
+ rx_status->antenna = hdr->antenna;
+
+ tsf32 = le32_to_cpu(hdr->tsf32);
+ if (tsf32 < priv->tsf_low32)
+ priv->tsf_high32++;
+ rx_status->mactime = ((u64)priv->tsf_high32) << 32 | tsf32;
+ priv->tsf_low32 = tsf32;
+
+ /* LMAC API Page 10/29 - s_lm_data_in - clock
+ * "usec accurate timestamp of hardware clock
+ * at end of frame (before OFDM SIFS EOF padding"
+ */
+ rx_status->flag |= RX_FLAG_MACTIME_END;
+
+ if (hdr->flags & cpu_to_le16(P54_HDR_FLAG_DATA_ALIGN))
+ header_len += hdr->align[0];
+
+ skb_pull(skb, header_len);
+ skb_trim(skb, le16_to_cpu(hdr->len));
+ if (unlikely(priv->hw->conf.flags & IEEE80211_CONF_PS))
+ p54_pspoll_workaround(priv, skb);
+
+ ieee80211_rx_irqsafe(priv->hw, skb);
+
+ ieee80211_queue_delayed_work(priv->hw, &priv->work,
+ msecs_to_jiffies(P54_STATISTICS_UPDATE));
+
+ return -1;
+}
+
+static void p54_rx_frame_sent(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct p54_hdr *hdr = (struct p54_hdr *) skb->data;
+ struct p54_frame_sent *payload = (struct p54_frame_sent *) hdr->data;
+ struct ieee80211_tx_info *info;
+ struct p54_hdr *entry_hdr;
+ struct p54_tx_data *entry_data;
+ struct sk_buff *entry;
+ unsigned int pad = 0, frame_len;
+ int count, idx;
+
+ entry = p54_find_and_unlink_skb(priv, hdr->req_id);
+ if (unlikely(!entry))
+ return ;
+
+ frame_len = entry->len;
+ info = IEEE80211_SKB_CB(entry);
+ entry_hdr = (struct p54_hdr *) entry->data;
+ entry_data = (struct p54_tx_data *) entry_hdr->data;
+ priv->stats.dot11ACKFailureCount += payload->tries - 1;
+
+ /*
+ * Frames in P54_QUEUE_FWSCAN and P54_QUEUE_BEACON are
+ * generated by the driver. Therefore tx_status is bogus
+ * and we don't want to confuse the mac80211 stack.
+ */
+ if (unlikely(entry_data->hw_queue < P54_QUEUE_FWSCAN)) {
+ dev_kfree_skb_any(entry);
+ return ;
+ }
+
+ /*
+ * Clear manually, ieee80211_tx_info_clear_status would
+ * clear the counts too and we need them.
+ */
+ memset(&info->status.ack_signal, 0,
+ sizeof(struct ieee80211_tx_info) -
+ offsetof(struct ieee80211_tx_info, status.ack_signal));
+ BUILD_BUG_ON(offsetof(struct ieee80211_tx_info,
+ status.ack_signal) != 20);
+
+ if (entry_hdr->flags & cpu_to_le16(P54_HDR_FLAG_DATA_ALIGN))
+ pad = entry_data->align[0];
+
+ /* walk through the rates array and adjust the counts */
+ count = payload->tries;
+ for (idx = 0; idx < 4; idx++) {
+ if (count >= info->status.rates[idx].count) {
+ count -= info->status.rates[idx].count;
+ } else if (count > 0) {
+ info->status.rates[idx].count = count;
+ count = 0;
+ } else {
+ info->status.rates[idx].idx = -1;
+ info->status.rates[idx].count = 0;
+ }
+ }
+
+ if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) &&
+ !(payload->status & P54_TX_FAILED))
+ info->flags |= IEEE80211_TX_STAT_ACK;
+ if (payload->status & P54_TX_PSM_CANCELLED)
+ info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
+ info->status.ack_signal = p54_rssi_to_dbm(priv,
+ (int)payload->ack_rssi);
+
+ /* Undo all changes to the frame. */
+ switch (entry_data->key_type) {
+ case P54_CRYPTO_TKIPMICHAEL: {
+ u8 *iv = (u8 *)(entry_data->align + pad +
+ entry_data->crypt_offset);
+
+ /* Restore the original TKIP IV. */
+ iv[2] = iv[0];
+ iv[0] = iv[1];
+ iv[1] = (iv[0] | 0x20) & 0x7f; /* WEPSeed - 8.3.2.2 */
+
+ frame_len -= 12; /* remove TKIP_MMIC + TKIP_ICV */
+ break;
+ }
+ case P54_CRYPTO_AESCCMP:
+ frame_len -= 8; /* remove CCMP_MIC */
+ break;
+ case P54_CRYPTO_WEP:
+ frame_len -= 4; /* remove WEP_ICV */
+ break;
+ }
+
+ skb_trim(entry, frame_len);
+ skb_pull(entry, sizeof(*hdr) + pad + sizeof(*entry_data));
+ ieee80211_tx_status_irqsafe(priv->hw, entry);
+}
+
+static void p54_rx_eeprom_readback(struct p54_common *priv,
+ struct sk_buff *skb)
+{
+ struct p54_hdr *hdr = (struct p54_hdr *) skb->data;
+ struct p54_eeprom_lm86 *eeprom = (struct p54_eeprom_lm86 *) hdr->data;
+ struct sk_buff *tmp;
+
+ if (!priv->eeprom)
+ return ;
+
+ if (priv->fw_var >= 0x509) {
+ memcpy(priv->eeprom, eeprom->v2.data,
+ le16_to_cpu(eeprom->v2.len));
+ } else {
+ memcpy(priv->eeprom, eeprom->v1.data,
+ le16_to_cpu(eeprom->v1.len));
+ }
+
+ priv->eeprom = NULL;
+ tmp = p54_find_and_unlink_skb(priv, hdr->req_id);
+ dev_kfree_skb_any(tmp);
+ complete(&priv->eeprom_comp);
+}
+
+static void p54_rx_stats(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct p54_hdr *hdr = (struct p54_hdr *) skb->data;
+ struct p54_statistics *stats = (struct p54_statistics *) hdr->data;
+ struct sk_buff *tmp;
+ struct ieee80211_channel *chan;
+ unsigned int i, rssi, tx, cca, dtime, dtotal, dcca, dtx, drssi, unit;
+ u32 tsf32;
+
+ if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED))
+ return ;
+
+ tsf32 = le32_to_cpu(stats->tsf32);
+ if (tsf32 < priv->tsf_low32)
+ priv->tsf_high32++;
+ priv->tsf_low32 = tsf32;
+
+ priv->stats.dot11RTSFailureCount = le32_to_cpu(stats->rts_fail);
+ priv->stats.dot11RTSSuccessCount = le32_to_cpu(stats->rts_success);
+ priv->stats.dot11FCSErrorCount = le32_to_cpu(stats->rx_bad_fcs);
+
+ priv->noise = p54_rssi_to_dbm(priv, le32_to_cpu(stats->noise));
+
+ /*
+ * STSW450X LMAC API page 26 - 3.8 Statistics
+ * "The exact measurement period can be derived from the
+ * timestamp member".
+ */
+ dtime = tsf32 - priv->survey_raw.timestamp;
+
+ /*
+ * STSW450X LMAC API page 26 - 3.8.1 Noise histogram
+ * The LMAC samples RSSI, CCA and transmit state at regular
+ * periods (typically 8 times per 1k [as in 1024] usec).
+ */
+ cca = le32_to_cpu(stats->sample_cca);
+ tx = le32_to_cpu(stats->sample_tx);
+ rssi = 0;
+ for (i = 0; i < ARRAY_SIZE(stats->sample_noise); i++)
+ rssi += le32_to_cpu(stats->sample_noise[i]);
+
+ dcca = cca - priv->survey_raw.cached_cca;
+ drssi = rssi - priv->survey_raw.cached_rssi;
+ dtx = tx - priv->survey_raw.cached_tx;
+ dtotal = dcca + drssi + dtx;
+
+ /*
+ * update statistics when more than a second is over since the
+ * last call, or when a update is badly needed.
+ */
+ if (dtotal && (priv->update_stats || dtime >= USEC_PER_SEC) &&
+ dtime >= dtotal) {
+ priv->survey_raw.timestamp = tsf32;
+ priv->update_stats = false;
+ unit = dtime / dtotal;
+
+ if (dcca) {
+ priv->survey_raw.cca += dcca * unit;
+ priv->survey_raw.cached_cca = cca;
+ }
+ if (dtx) {
+ priv->survey_raw.tx += dtx * unit;
+ priv->survey_raw.cached_tx = tx;
+ }
+ if (drssi) {
+ priv->survey_raw.rssi += drssi * unit;
+ priv->survey_raw.cached_rssi = rssi;
+ }
+
+ /* 1024 usec / 8 times = 128 usec / time */
+ if (!(priv->phy_ps || priv->phy_idle))
+ priv->survey_raw.active += dtotal * unit;
+ else
+ priv->survey_raw.active += (dcca + dtx) * unit;
+ }
+
+ chan = priv->curchan;
+ if (chan) {
+ struct survey_info *survey = &priv->survey[chan->hw_value];
+ survey->noise = clamp(priv->noise, -128, 127);
+ survey->time = priv->survey_raw.active;
+ survey->time_tx = priv->survey_raw.tx;
+ survey->time_busy = priv->survey_raw.tx +
+ priv->survey_raw.cca;
+ do_div(survey->time, 1024);
+ do_div(survey->time_tx, 1024);
+ do_div(survey->time_busy, 1024);
+ }
+
+ tmp = p54_find_and_unlink_skb(priv, hdr->req_id);
+ dev_kfree_skb_any(tmp);
+ complete(&priv->stat_comp);
+}
+
+static void p54_rx_trap(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct p54_hdr *hdr = (struct p54_hdr *) skb->data;
+ struct p54_trap *trap = (struct p54_trap *) hdr->data;
+ u16 event = le16_to_cpu(trap->event);
+ u16 freq = le16_to_cpu(trap->frequency);
+
+ switch (event) {
+ case P54_TRAP_BEACON_TX:
+ break;
+ case P54_TRAP_RADAR:
+ wiphy_info(priv->hw->wiphy, "radar (freq:%d MHz)\n", freq);
+ break;
+ case P54_TRAP_NO_BEACON:
+ if (priv->vif)
+ ieee80211_beacon_loss(priv->vif);
+ break;
+ case P54_TRAP_SCAN:
+ break;
+ case P54_TRAP_TBTT:
+ break;
+ case P54_TRAP_TIMER:
+ break;
+ case P54_TRAP_FAA_RADIO_OFF:
+ wiphy_rfkill_set_hw_state(priv->hw->wiphy, true);
+ break;
+ case P54_TRAP_FAA_RADIO_ON:
+ wiphy_rfkill_set_hw_state(priv->hw->wiphy, false);
+ break;
+ default:
+ wiphy_info(priv->hw->wiphy, "received event:%x freq:%d\n",
+ event, freq);
+ break;
+ }
+}
+
+static int p54_rx_control(struct p54_common *priv, struct sk_buff *skb)
+{
+ struct p54_hdr *hdr = (struct p54_hdr *) skb->data;
+
+ switch (le16_to_cpu(hdr->type)) {
+ case P54_CONTROL_TYPE_TXDONE:
+ p54_rx_frame_sent(priv, skb);
+ break;
+ case P54_CONTROL_TYPE_TRAP:
+ p54_rx_trap(priv, skb);
+ break;
+ case P54_CONTROL_TYPE_BBP:
+ break;
+ case P54_CONTROL_TYPE_STAT_READBACK:
+ p54_rx_stats(priv, skb);
+ break;
+ case P54_CONTROL_TYPE_EEPROM_READBACK:
+ p54_rx_eeprom_readback(priv, skb);
+ break;
+ default:
+ wiphy_debug(priv->hw->wiphy,
+ "not handling 0x%02x type control frame\n",
+ le16_to_cpu(hdr->type));
+ break;
+ }
+ return 0;
+}
+
+/* returns zero if skb can be reused */
+int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb)
+{
+ struct p54_common *priv = dev->priv;
+ u16 type = le16_to_cpu(*((__le16 *)skb->data));
+
+ if (type & P54_HDR_FLAG_CONTROL)
+ return p54_rx_control(priv, skb);
+ else
+ return p54_rx_data(priv, skb);
+}
+EXPORT_SYMBOL_GPL(p54_rx);
+
+static void p54_tx_80211_header(struct p54_common *priv, struct sk_buff *skb,
+ struct ieee80211_tx_info *info,
+ struct ieee80211_sta *sta,
+ u8 *queue, u32 *extra_len, u16 *flags, u16 *aid,
+ bool *burst_possible)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+
+ if (ieee80211_is_data_qos(hdr->frame_control))
+ *burst_possible = true;
+ else
+ *burst_possible = false;
+
+ if (!(info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ))
+ *flags |= P54_HDR_FLAG_DATA_OUT_SEQNR;
+
+ if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER)
+ *flags |= P54_HDR_FLAG_DATA_OUT_NOCANCEL;
+
+ if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT)
+ *flags |= P54_HDR_FLAG_DATA_OUT_NOCANCEL;
+
+ *queue = skb_get_queue_mapping(skb) + P54_QUEUE_DATA;
+
+ switch (priv->mode) {
+ case NL80211_IFTYPE_MONITOR:
+ /*
+ * We have to set P54_HDR_FLAG_DATA_OUT_PROMISC for
+ * every frame in promiscuous/monitor mode.
+ * see STSW45x0C LMAC API - page 12.
+ */
+ *aid = 0;
+ *flags |= P54_HDR_FLAG_DATA_OUT_PROMISC;
+ break;
+ case NL80211_IFTYPE_STATION:
+ *aid = 1;
+ break;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MESH_POINT:
+ if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) {
+ *aid = 0;
+ *queue = P54_QUEUE_CAB;
+ return;
+ }
+
+ if (unlikely(ieee80211_is_mgmt(hdr->frame_control))) {
+ if (ieee80211_is_probe_resp(hdr->frame_control)) {
+ *aid = 0;
+ *flags |= P54_HDR_FLAG_DATA_OUT_TIMESTAMP |
+ P54_HDR_FLAG_DATA_OUT_NOCANCEL;
+ return;
+ } else if (ieee80211_is_beacon(hdr->frame_control)) {
+ *aid = 0;
+
+ if (info->flags & IEEE80211_TX_CTL_INJECTED) {
+ /*
+ * Injecting beacons on top of a AP is
+ * not a good idea... nevertheless,
+ * it should be doable.
+ */
+
+ return;
+ }
+
+ *flags |= P54_HDR_FLAG_DATA_OUT_TIMESTAMP;
+ *queue = P54_QUEUE_BEACON;
+ *extra_len = IEEE80211_MAX_TIM_LEN;
+ return;
+ }
+ }
+
+ if (sta)
+ *aid = sta->aid;
+ break;
+ }
+}
+
+static u8 p54_convert_algo(u32 cipher)
+{
+ switch (cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ return P54_CRYPTO_WEP;
+ case WLAN_CIPHER_SUITE_TKIP:
+ return P54_CRYPTO_TKIPMICHAEL;
+ case WLAN_CIPHER_SUITE_CCMP:
+ return P54_CRYPTO_AESCCMP;
+ default:
+ return 0;
+ }
+}
+
+void p54_tx_80211(struct ieee80211_hw *dev,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ struct p54_common *priv = dev->priv;
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct p54_tx_info *p54info;
+ struct p54_hdr *hdr;
+ struct p54_tx_data *txhdr;
+ unsigned int padding, len, extra_len = 0;
+ int i, j, ridx;
+ u16 hdr_flags = 0, aid = 0;
+ u8 rate, queue = 0, crypt_offset = 0;
+ u8 cts_rate = 0x20;
+ u8 rc_flags;
+ u8 calculated_tries[4];
+ u8 nrates = 0, nremaining = 8;
+ bool burst_allowed = false;
+
+ p54_tx_80211_header(priv, skb, info, control->sta, &queue, &extra_len,
+ &hdr_flags, &aid, &burst_allowed);
+
+ if (p54_tx_qos_accounting_alloc(priv, skb, queue)) {
+ ieee80211_free_txskb(dev, skb);
+ return;
+ }
+
+ padding = (unsigned long)(skb->data - (sizeof(*hdr) + sizeof(*txhdr))) & 3;
+ len = skb->len;
+
+ if (info->control.hw_key) {
+ crypt_offset = ieee80211_get_hdrlen_from_skb(skb);
+ if (info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) {
+ u8 *iv = (u8 *)(skb->data + crypt_offset);
+ /*
+ * The firmware excepts that the IV has to have
+ * this special format
+ */
+ iv[1] = iv[0];
+ iv[0] = iv[2];
+ iv[2] = 0;
+ }
+ }
+
+ txhdr = skb_push(skb, sizeof(*txhdr) + padding);
+ hdr = skb_push(skb, sizeof(*hdr));
+
+ if (padding)
+ hdr_flags |= P54_HDR_FLAG_DATA_ALIGN;
+ hdr->type = cpu_to_le16(aid);
+ hdr->rts_tries = info->control.rates[0].count;
+
+ /*
+ * we register the rates in perfect order, and
+ * RTS/CTS won't happen on 5 GHz
+ */
+ cts_rate = info->control.rts_cts_rate_idx;
+
+ memset(&txhdr->rateset, 0, sizeof(txhdr->rateset));
+
+ /* see how many rates got used */
+ for (i = 0; i < dev->max_rates; i++) {
+ if (info->control.rates[i].idx < 0)
+ break;
+ nrates++;
+ }
+
+ /* limit tries to 8/nrates per rate */
+ for (i = 0; i < nrates; i++) {
+ /*
+ * The magic expression here is equivalent to 8/nrates for
+ * all values that matter, but avoids division and jumps.
+ * Note that nrates can only take the values 1 through 4.
+ */
+ calculated_tries[i] = min_t(int, ((15 >> nrates) | 1) + 1,
+ info->control.rates[i].count);
+ nremaining -= calculated_tries[i];
+ }
+
+ /* if there are tries left, distribute from back to front */
+ for (i = nrates - 1; nremaining > 0 && i >= 0; i--) {
+ int tmp = info->control.rates[i].count - calculated_tries[i];
+
+ if (tmp <= 0)
+ continue;
+ /* RC requested more tries at this rate */
+
+ tmp = min_t(int, tmp, nremaining);
+ calculated_tries[i] += tmp;
+ nremaining -= tmp;
+ }
+
+ ridx = 0;
+ for (i = 0; i < nrates && ridx < 8; i++) {
+ /* we register the rates in perfect order */
+ rate = info->control.rates[i].idx;
+ if (info->band == NL80211_BAND_5GHZ)
+ rate += 4;
+
+ /* store the count we actually calculated for TX status */
+ info->control.rates[i].count = calculated_tries[i];
+
+ rc_flags = info->control.rates[i].flags;
+ if (rc_flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) {
+ rate |= 0x10;
+ cts_rate |= 0x10;
+ }
+ if (rc_flags & IEEE80211_TX_RC_USE_RTS_CTS) {
+ burst_allowed = false;
+ rate |= 0x40;
+ } else if (rc_flags & IEEE80211_TX_RC_USE_CTS_PROTECT) {
+ rate |= 0x20;
+ burst_allowed = false;
+ }
+ for (j = 0; j < calculated_tries[i] && ridx < 8; j++) {
+ txhdr->rateset[ridx] = rate;
+ ridx++;
+ }
+ }
+
+ if (burst_allowed)
+ hdr_flags |= P54_HDR_FLAG_DATA_OUT_BURST;
+
+ /* TODO: enable bursting */
+ hdr->flags = cpu_to_le16(hdr_flags);
+ hdr->tries = ridx;
+ txhdr->rts_rate_idx = 0;
+ if (info->control.hw_key) {
+ txhdr->key_type = p54_convert_algo(info->control.hw_key->cipher);
+ txhdr->key_len = min((u8)16, info->control.hw_key->keylen);
+ memcpy(txhdr->key, info->control.hw_key->key, txhdr->key_len);
+ if (info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) {
+ /* reserve space for the MIC key */
+ len += 8;
+ skb_put_data(skb,
+ &(info->control.hw_key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY]),
+ 8);
+ }
+ /* reserve some space for ICV */
+ len += info->control.hw_key->icv_len;
+ skb_put_zero(skb, info->control.hw_key->icv_len);
+ } else {
+ txhdr->key_type = 0;
+ txhdr->key_len = 0;
+ }
+ txhdr->crypt_offset = crypt_offset;
+ txhdr->hw_queue = queue;
+ txhdr->backlog = priv->tx_stats[queue].len - 1;
+ memset(txhdr->durations, 0, sizeof(txhdr->durations));
+ txhdr->tx_antenna = 2 & priv->tx_diversity_mask;
+ if (priv->rxhw == 5) {
+ txhdr->longbow.cts_rate = cts_rate;
+ txhdr->longbow.output_power = cpu_to_le16(priv->output_power);
+ } else {
+ txhdr->normal.output_power = priv->output_power;
+ txhdr->normal.cts_rate = cts_rate;
+ }
+ if (padding)
+ txhdr->align[0] = padding;
+
+ hdr->len = cpu_to_le16(len);
+ /* modifies skb->cb and with it info, so must be last! */
+ p54info = (void *) info->rate_driver_data;
+ p54info->extra_len = extra_len;
+
+ p54_tx(priv, skb);
+}