diff options
Diffstat (limited to 'drivers/net/wireless/digiPiper')
24 files changed, 8585 insertions, 0 deletions
diff --git a/drivers/net/wireless/digiPiper/Kconfig b/drivers/net/wireless/digiPiper/Kconfig new file mode 100644 index 000000000000..aa0659f0e499 --- /dev/null +++ b/drivers/net/wireless/digiPiper/Kconfig @@ -0,0 +1,27 @@ +config DIGI_PIPER_WIFI + bool "Digi Piper Wifi support" + depends on MAC80211 && WLAN_80211 && I2C && (MACH_CCW9P9215JS || MACH_CCW9M2443JS) + ---help--- + This driver is for the Piper 802.11 MAC by Digi. This MAC is + supported on the Digi ConnectCore Wi-9P 9215 and Digi ConnectCore Wi-9M 2443 + embedded modules. + +config PIPER_STATUS_LED + bool "Enable GPIO for Wifi status LED" + depends on DIGI_PIPER_WIFI && MACH_CCW9M2443JS + default y + help + ConnectCore Wi-9M 2443 does not provide any dedicated LED + in the module for showing the Wifi status. + This option lets you define one available CPU GPIO for this purpose. + Default value is set to USER LED 1 on JumpStart board. + +config PIPER_STATUS_LED_GPIO + int "GPIO for Wifi status LED (0-144)" + range 0 144 + depends on PIPER_STATUS_LED + default "141" + help + Set CPU GPIO for Wifi status LED. + + Default: User LED 1 on JumpStart board (LE5) diff --git a/drivers/net/wireless/digiPiper/Makefile b/drivers/net/wireless/digiPiper/Makefile new file mode 100644 index 000000000000..a3686b942088 --- /dev/null +++ b/drivers/net/wireless/digiPiper/Makefile @@ -0,0 +1,16 @@ + +mpiper-y := piper.o +mpiper-y += airoha.o +mpiper-y += digiDebug.o +mpiper-y += digiIsr.o +mpiper-y += digiTx.o +mpiper-y += digiRx.o +mpiper-y += digiMac80211.o +mpiper-y += piperDsp.o +mpiper-y += piperMacAssist.o +mpiper-y += phy.o +mpiper-y += adc121c027.o +mpiper-y += airohaCalibration.o +mpiper-y += digiPs.o +obj-$(CONFIG_DIGI_PIPER_WIFI) := mpiper.o + diff --git a/drivers/net/wireless/digiPiper/adc121c027.c b/drivers/net/wireless/digiPiper/adc121c027.c new file mode 100644 index 000000000000..17564bcd7a66 --- /dev/null +++ b/drivers/net/wireless/digiPiper/adc121c027.c @@ -0,0 +1,161 @@ +/* + adc121C027.c - Analog to Digital converter integrated into Piper. + + Copyright (C) 2009 Digi International <sales2@digi.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon-sysfs.h> + +#include "airohaCalibration.h" +#include "pipermain.h" + +/* Addresses to scan: none, device is not autodetected */ +/* static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; */ + +#define ADC_I2C_ADDR (0x51) +#define ADC_CYCLE_TIME (0x20) + +static const unsigned short normal_i2c[] = { ADC_I2C_ADDR, I2C_CLIENT_END }; +static const unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END }; + +static struct i2c_client_address_data addr = { + .normal_i2c = normal_i2c, + .probe = dummy_i2c_addrlist, + .ignore = dummy_i2c_addrlist, +}; + +enum adc121C027_cmd { + ADC_RESULT = 0, + ADC_ALERT_STATUS = 1, + ADC_CONFIGURATION = 2, + ADC_LOW_LIMIT = 3, + ADC_HIGH_LIMIT = 4, + ADC_HYSTERESIS = 5, + ADC_LOWEST_VALUE = 6, + ADC_HIGHEST_VALUE = 7, +}; + +static u16 adc121C027_read_peak(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + return be16_to_cpu(i2c_smbus_read_word_data(i2cclient, ADC_HIGHEST_VALUE)); +} + +static void adc121C027_clear_peak(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + i2c_smbus_write_word_data(i2cclient, ADC_HIGHEST_VALUE, 0); +} + +static u16 adc121C027_read_last_sample(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + return be16_to_cpu(i2c_smbus_read_word_data(i2cclient, ADC_RESULT)); +} + +static int adc121C027_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + return (client->addr != ADC_I2C_ADDR) ? -EINVAL : 0; +} + +static int adc121C027_remove(struct i2c_client *client) +{ + /* Real shut down will be done by adc121C027_shutdown() */ + return 0; +} + +static const struct i2c_device_id adc121C027_id[] = { + { "adc121C027", 0 }, + {} +}; + +static struct i2c_driver adc121C027_driver = { + .driver = { + .name = "adc121C027", + }, + .probe = adc121C027_probe, + .remove = __devexit_p(adc121C027_remove), + .id_table = adc121C027_id, + .address_data = &addr, +}; + +/* Turn on automatic A/D process by setting a non zero cycle time */ +static void adc121C027_hw_init(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + i2c_smbus_write_word_data(i2cclient, ADC_CONFIGURATION, ADC_CYCLE_TIME); +} + +void adc121C027_shutdown(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + if (i2cclient) { + i2c_unregister_device(i2cclient); + cal->priv = NULL; + } + i2c_del_driver(&adc121C027_driver); +} + +int adc121C027_init(struct airohaCalibrationData *cal, int i2cadapter) +{ + struct i2c_board_info board_info = { + .type = "adc121C027", + .addr = ADC_I2C_ADDR, + }; + struct i2c_adapter *adapter; + struct i2c_client *adc_i2c_client; + int ret; + + ret = i2c_add_driver(&adc121C027_driver); + if (ret) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error adding driver adc121C027_driver (%d)\n", ret); + return ret; + } + + adapter = i2c_get_adapter(i2cadapter); + if (!adapter) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error getting i2c adapter\n"); + return -EINVAL; + } + + adc_i2c_client = i2c_new_device(adapter, &board_info); + if (!adc_i2c_client) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error creating new i2c client\n"); + return -EINVAL; + } + + cal->priv = (void *)adc_i2c_client; + adc121C027_hw_init(cal); + + cal->cops = kmalloc(sizeof(struct calibration_ops), GFP_KERNEL); + if (!cal->cops) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": unable to allocate memory for cal->cops\n"); + return -ENOMEM; + } + cal->cops->adc_read_peak = adc121C027_read_peak; + cal->cops->adc_clear_peak = adc121C027_clear_peak; + cal->cops->adc_read_last_val = adc121C027_read_last_sample; + cal->cops->adc_shutdown = adc121C027_shutdown; + + return 0; +} +EXPORT_SYMBOL_GPL(adc121C027_init); + + diff --git a/drivers/net/wireless/digiPiper/adc121c027.h b/drivers/net/wireless/digiPiper/adc121c027.h new file mode 100644 index 000000000000..1453d8a66963 --- /dev/null +++ b/drivers/net/wireless/digiPiper/adc121c027.h @@ -0,0 +1,17 @@ +/* + adc121C027.h - Analog to Digital converter integrated into Piper. + + Copyright (C) 2009 Digi International <sales2@digi.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. +*/ + +#ifndef ADC121C027_H +#define ADC121C027_H + +int adc121C027_init(struct airohaCalibrationData *cal, int i2cadapter); + +#endif /* ADC121C027_H */ + diff --git a/drivers/net/wireless/digiPiper/airoha.c b/drivers/net/wireless/digiPiper/airoha.c new file mode 100644 index 000000000000..17ef8a812f98 --- /dev/null +++ b/drivers/net/wireless/digiPiper/airoha.c @@ -0,0 +1,986 @@ +/* + * Ubec AH7230 radio support. + * + * Copyright © 2009 Digi International, Inc + * + * Author: Contact support@digi.com for information about this software. + * + * 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/module.h> +#include <linux/delay.h> +#include <net/mac80211.h> +#include <net/wireless.h> + +#include "pipermain.h" +#include "mac.h" +#include "airohaCalibration.h" +#include "airoha.h" + +/* + * Number of us to change channels. I counted the number of udelays once + * and it was about 2030, plus the 1 us delays for each register write. + * So probably about 2200 in reality, I'm saying 2500 to be safe. + */ +#define CHANNEL_CHANGE_TIME (2500) + +/* + * Maximum possible receive signal strength in dbm. Most of the + * values will be negative. + */ +#define MAX_SIGNAL_IN_DBM (5) + +#define read_reg(reg) priv->ac->rd_reg(priv,reg) +#define write_reg(reg,val,op) priv->ac->wr_reg(priv,reg,val,op) +#define mac_set_tx_power(x) al7230_set_txpwr(hw,x) + +static unsigned int hw_revision = WCD_HW_REV_A; +static unsigned int hw_platform = WCD_CCW9P_PLATFORM; + +static void InitializeRF(struct ieee80211_hw *hw, int band_selection); +static int al7230_set_txpwr(struct ieee80211_hw *hw, uint8_t val); + +static const struct { + unsigned int integer; + unsigned int fraction; + unsigned int address4; + unsigned int tracking; +} freqTableAiroha_7230[] = { + { 0, 0, 0, 0 }, // 0 + + // 2.4 GHz band (802.11b/g) + { 0x00379, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-1 (2412 MHz) 1 + { 0x00379, 0x1B333, 0x7FD78, TRACK_BG_BAND }, // B-2 (2417 MHz) 2 + { 0x00379, 0x03333, 0x7FD78, TRACK_BG_BAND }, // B-3 (2422 MHz) 3 + { 0x00379, 0x0B333, 0x7FD78, TRACK_BG_BAND }, // B-4 (2427 MHz) 4 + { 0x0037A, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-5 (2432 MHz) 5 + { 0x0037A, 0x1B333, 0x7FD78, TRACK_BG_BAND }, // B-6 (2437 MHz) 6 + { 0x0037A, 0x03333, 0x7FD78, TRACK_BG_BAND }, // B-7 (2442 MHz) 7 + { 0x0037A, 0x0B333, 0x7FD78, TRACK_BG_BAND }, // B-8 (2447 MHz) 8 + { 0x0037B, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-9 (2452 MHz) 9 + { 0x0037B, 0x1B333, 0x7FD78, TRACK_BG_BAND }, // B-10 (2457 MHz) 10 + { 0x0037B, 0x03333, 0x7FD78, TRACK_BG_BAND }, // B-11 (2462 MHz) 11 + { 0x0037B, 0x0B333, 0x7FD78, TRACK_BG_BAND }, // B-12 (2467 MHz) 12 + { 0x0037C, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-13 (2472 MHz) 13 + { 0x0037C, 0x06666, 0x7FD78, TRACK_BG_BAND }, // B-14 (2484 MHz) 14 + + { 0, 0, 0, 0 }, // reserved for future b/g expansion 15 + { 0, 0, 0, 0 }, // reserved for future b/g expansion 16 + + // Extended 4 GHz bands (802.11a) - Lower Band + { 0x0FF52, 0x00000, 0x67F78, TRACK_4920_4980_A_BAND }, // L-184 (4920 MHz) 17 + { 0x0FF52, 0x0AAAA, 0x77F78, TRACK_4920_4980_A_BAND }, // L-188 (4940 MHz) 18 + { 0x0FF53, 0x15555, 0x77F78, TRACK_4920_4980_A_BAND }, // L-192 (4960 MHz) 19 + { 0x0FF53, 0x00000, 0x67F78, TRACK_4920_4980_A_BAND }, // L-196 (4980 MHz) 20 + + // Extended 5 GHz bands (802.11a) + { 0x0FF54, 0x00000, 0x67F78, TRACK_5150_5350_A_BAND }, // A-8 (5040 MHz) 21 tracking? + { 0x0FF54, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-12 (5060 MHz) 22 tracking? + { 0x0FF55, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-16 (5080 MHz) 23 tracking? + { 0x0FF56, 0x05555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-34 (5170 MHz) 24 + { 0x0FF56, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-36 (5180 MHz) 25 + { 0x0FF57, 0x10000, 0x77F78, TRACK_5150_5350_A_BAND }, // A-38 (5190 MHz) 26 + { 0x0FF57, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-40 (5200 MHz) 27 + { 0x0FF57, 0x1AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-42 (5210 MHz) 28 + { 0x0FF57, 0x00000, 0x67F78, TRACK_5150_5350_A_BAND }, // A-44 (5220 MHz) 29 + { 0x0FF57, 0x05555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-46 (5230 MHz) 30 + { 0x0FF57, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-48 (5240 MHz) 31 + + { 0x0FF58, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-52 (5260 MHz) 32 + { 0x0FF58, 0x00000, 0x67F78, TRACK_5150_5350_A_BAND }, // A-56 (5280 MHz) 33 + { 0x0FF58, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-60 (5300 MHz) 34 + { 0x0FF59, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-64 (5320 MHz) 35 + + { 0x0FF5C, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-100 (5500 MHz) 36 + { 0x0FF5C, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-104 (5520 MHz) 37 + { 0x0FF5C, 0x0AAAA, 0x77F78, TRACK_5470_5725_A_BAND }, // A-108 (5540 MHz) 38 + { 0x0FF5D, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-112 (5560 MHz) 39 + { 0x0FF5D, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-116 (5580 MHz) 40 + { 0x0FF5D, 0x0AAAA, 0x77F78, TRACK_5470_5725_A_BAND }, // A-120 (5600 MHz) 41 + { 0x0FF5E, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-124 (5620 MHz) 42 + { 0x0FF5E, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-128 (5640 MHz) 43 + { 0x0FF5E, 0x0AAAA, 0x77F78, TRACK_5470_5725_A_BAND }, // A-132 (5660 MHz) 44 + { 0x0FF5F, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-136 (5680 MHz) 45 + { 0x0FF5F, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-140 (5700 MHz) 46 + + { 0x0FF60, 0x18000, 0x77F78, TRACK_5725_5825_A_BAND }, // A-149 (5745 MHz) 47 + { 0x0FF60, 0x02AAA, 0x77F78, TRACK_5725_5825_A_BAND }, // A-153 (5765 MHz) 48 + { 0x0FF60, 0x0D555, 0x77F78, TRACK_5725_5825_A_BAND }, // A-157 (5785 MHz) 49 + { 0x0FF61, 0x18000, 0x77F78, TRACK_5725_5825_A_BAND }, // A-161 (5805 MHz) 50 + { 0x0FF61, 0x02AAA, 0x77F78, TRACK_5725_5825_A_BAND }, // A-165 (5825 MHz) 51 +}; + +#define CHAN4G(idx, _freq) \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = idx, \ + .max_antenna_gain = 0, \ + .max_power = 12 + +static struct ieee80211_channel al7230_bg_channels[] = { + { CHAN4G(1, 2412) }, + { CHAN4G(2, 2417) }, + { CHAN4G(3, 2422) }, + { CHAN4G(4, 2427) }, + { CHAN4G(5, 2432) }, + { CHAN4G(6, 2437) }, + { CHAN4G(7, 2442) }, + { CHAN4G(8, 2447) }, + { CHAN4G(9, 2452) }, + { CHAN4G(10, 2457) }, + { CHAN4G(11, 2462) }, + { CHAN4G(12, 2467) }, + { CHAN4G(13, 2472) }, + { CHAN4G(14, 2484) }, +}; + +static const struct ieee80211_rate al7230_bg_rates[] = { + /* psk/cck rates */ + { + .bitrate = 10, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + { + .bitrate = 20, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + { + .bitrate = 55, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + { + .bitrate = 110, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + /* ofdm rates */ + { + .bitrate = 60, + .hw_value = 0xb, + }, + { + .bitrate = 90, + .hw_value = 0xf, + }, + { + .bitrate = 120, + .hw_value = 0xa, + }, + { + .bitrate = 180, + .hw_value = 0xe, + }, + { + .bitrate = 240, + .hw_value = 0x9, + }, + { + .bitrate = 360, + .hw_value = 0xd, + }, + { + .bitrate = 480, + .hw_value = 0x8, + }, + { + .bitrate = 540, + .hw_value = 0xc, + }, +}; + +#define CHAN5G(idx, frequency) \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = frequency, \ + .max_antenna_gain = 0, \ + .max_power = 8, \ + .hw_value = idx + +static struct ieee80211_channel al7230_a_channels[] = { + { CHAN5G(17, 4920) }, + { CHAN5G(18, 4940) }, + { CHAN5G(19, 4960) }, + { CHAN5G(20, 4980) }, + + { CHAN5G(21, 5040) }, + { CHAN5G(22, 5060) }, + { CHAN5G(23, 5080) }, + { CHAN5G(24, 5170) }, + { CHAN5G(25, 5180) }, + { CHAN5G(26, 5190) }, + { CHAN5G(27, 5200) }, + { CHAN5G(28, 5210) }, + { CHAN5G(29, 5220) }, + { CHAN5G(30, 5230) }, + { CHAN5G(31, 5240) }, + + { CHAN5G(32, 5260) }, + { CHAN5G(33, 5280) }, + { CHAN5G(34, 5300) }, + { CHAN5G(35, 5320) }, + + { CHAN5G(36, 5500) }, + { CHAN5G(37, 5520) }, + { CHAN5G(38, 5540) }, + { CHAN5G(39, 5560) }, + { CHAN5G(40, 5580) }, + { CHAN5G(41, 5600) }, + { CHAN5G(42, 5620) }, + { CHAN5G(43, 5640) }, + { CHAN5G(44, 5660) }, + { CHAN5G(45, 5680) }, + { CHAN5G(46, 5700) }, + + { CHAN5G(47, 5745) }, + { CHAN5G(48, 5765) }, + { CHAN5G(49, 5785) }, + { CHAN5G(50, 5805) }, + { CHAN5G(51, 5825) } +}; + +static const struct ieee80211_rate al7230_a_rates[] = { + /* ofdm rates */ + { + .bitrate = 60, + .hw_value = 0xb, + }, + { + .bitrate = 90, + .hw_value = 0xf, + }, + { + .bitrate = 120, + .hw_value = 0xa, + }, + { + .bitrate = 180, + .hw_value = 0xe, + }, + { + .bitrate = 240, + .hw_value = 0x9, + }, + { + .bitrate = 360, + .hw_value = 0xd, + }, + { + .bitrate = 480, + .hw_value = 0x8, + }, + { + .bitrate = 540, + .hw_value = 0xc, + }, +}; + +static enum ieee80211_band getBand(int channelIndex) +{ + enum ieee80211_band result; + + if (channelIndex >= BAND_A_OFFSET) { + result = IEEE80211_BAND_5GHZ; + } else { + result = IEEE80211_BAND_2GHZ; + } + + return result; +} + +static int getFrequency(int channelIndex) +{ + int result; + + if (getBand(channelIndex) == IEEE80211_BAND_5GHZ) { + result = al7230_a_channels[channelIndex - BAND_A_OFFSET].center_freq; + } else { + result = al7230_bg_channels[channelIndex - 1].center_freq; + } + + return result; +} + +static int write_rf(struct ieee80211_hw *hw, unsigned char reg, unsigned int val) +{ + struct piper_priv *priv = hw->priv; + int err; + + err = write_reg(BB_SPI_DATA, val << 4 | reg, op_write); + udelay(3); /* Mike Schaffner says to allow 2 us or more between all writes */ + return err; +} + + +/* + * This function is called to set the value of Airoha register + * 0xc. This register must be set to different values depending + * on the H/W revision of the board due to changes in the board + * design. + */ +static void set_hw_specific_parameters(struct ieee80211_hw *hw, + unsigned int band, + unsigned int hw_revision, + unsigned int hw_platform) +{ + switch (hw_platform) { + case WCD_CCW9P_PLATFORM: + switch (hw_revision) { + case WCD_HW_REV_PROTOTYPE: + case WCD_HW_REV_PILOT: + case WCD_HW_REV_A: + default: + if (band == IEEE80211_BAND_2GHZ) { + write_rf(hw, 0xc, 0x2b); + } else { + write_rf(hw, 0xc, 0x00143 ); + } + break; + } + break; + case WCD_CCW9M_PLATFORM: + switch (hw_revision) { + case WCD_HW_REV_PROTOTYPE: + case WCD_HW_REV_PILOT: + if (band == IEEE80211_BAND_2GHZ) { + write_rf(hw, 0xc, 0xa3); + } else { + write_rf(hw, 0xc, 0x00143 ); + } + break; + + case WCD_HW_REV_A: + default: + if (band == IEEE80211_BAND_2GHZ) { + write_rf(hw, 0xc, 0x70); + } else { + write_rf(hw, 0xc, 0x00143 ); + } + break; + } + break; + default: + break; + } +} + +static int al7230_rf_set_chan_private(struct ieee80211_hw *hw, int channelIndex, bool enable_rx) +{ + struct piper_priv *priv = hw->priv; + static int rf_band; +#ifdef WANT_DEBUG + const char *channelLookup[] = { + "invalid 0", + "B-1", + "B-2", + "B-3", + "B-4", + "B-5", + "B-6", + "B-7", + "B-8", + "B-9", + "B-10", + "B-11", + "B-12", + "B-13", + "B-14", + "invalid 15", + "invalid 16", + "L-184", + "L-188", + "L-192", + "L-196", + "A-8", + "A-12", + "A-16", + "A-34", + "A-36", + "A-38", + "A-40", + "A-42", + "A-44", + "A-46", + "A-48", + "A-52", + "A-56", + "A-60", + "A-64", + "A-100", + "A-104", + "A-108", + "A-112", + "A-116", + "A-120", + "A-124", + "A-128", + "A-132", + "A-136", + "A-140", + "A-149", + "A-153", + "A-157", + "A-161", + "A-165" + }; +printk(KERN_ERR "Setting channel %s\n", channelLookup[channelIndex]); +#endif + if (channelIndex >= BAND_A_OFFSET) + rf_band = IEEE80211_BAND_5GHZ; + else + rf_band = IEEE80211_BAND_2GHZ; + /* Disable the rx processing path */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_RX_EN, op_and); + + write_reg(BB_OUTPUT_CONTROL, 0xfffff33f, op_and); + write_reg(BB_OUTPUT_CONTROL, 0x00000880, op_or); + + if (priv->pdata->rf_transceiver == RF_AIROHA_2236) { +/* TODO, when using this transceiver, resolve this commented code */ +#ifdef BUILD_THIS_CODE_SECTION + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_B_EN, op_or); + + if (macParams.band == WLN_BAND_B) { + /* turn off OFDM */ + write_reg(BB_GENERAL_STAT, ~BB_GENERAL_STAT_A_EN, op_and); + } else { + /* turn on OFDM */ + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_A_EN, op_or); + } + /* set the 802.11b/g frequency band specific tracking constant */ + write_reg(BB_TRACK_CONTROL, 0xff00ffff, op_and); + + write_reg(BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + + /* perform chip and frequency-band specific RF initialization */ + InitializeRF(hw, rf_band); + + mac_set_tx_power(priv->tx_power); + + write_rf(hw, 0, freqTableAiroha_2236[channelIndex].integer); + write_rf(hw, 1, freqTableAiroha_2236[channelIndex].fraction); + + /* critical delay for correct calibration */ + udelay(150); + + /* + * TXON, PAON and RXON should all be low before Calibration + * TXON and PAON will be low as long as no frames are written to the TX + * DATA fifo. + * RXON will be low as long as the receive path is not enabled (bit 0 of + * GEN CTL register is 0). + */ + + /* calibrate RF transceiver */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x00D87); + udelay(50); + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x00787); + udelay(50); + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x00587); + udelay(50); + + /* configure the baseband processing engine */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_GEN_5GEN, op_and); + + /*Re-enable the rx processing path */ + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_RX_EN, op_or); +#endif + } else if (priv->pdata->rf_transceiver == RF_AIROHA_7230) { + /* enable the frequency-band specific PA */ + if (rf_band == IEEE80211_BAND_2GHZ) { + //HW_GEN_CONTROL &= ~GEN_PA_ON; + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_A_EN | BB_GENERAL_STAT_B_EN, + op_or); + + /* set the 802.11b/g frequency band specific tracking constant */ + write_reg(BB_TRACK_CONTROL, 0xff00ffff, op_and); + + write_reg(BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + + } else { + //HW_GEN_CONTROL |= GEN_PA_ON; + + // turn off PSK/CCK + write_reg(BB_GENERAL_STAT, ~BB_GENERAL_STAT_B_EN, op_and); + + // turn on OFDM + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_A_EN, op_or); + + /* Set the 802.11a frequency sub-band specific tracking constant */ + /* All 8 supported 802.11a channels are in this 802.11a frequency sub-band */ + write_reg(BB_TRACK_CONTROL, 0xff00ffff, op_and); + + write_reg(BB_TRACK_CONTROL, freqTableAiroha_7230[channelIndex].tracking, + op_or); + } + + /* perform chip and frequency-band specific RF initialization */ + InitializeRF(hw, rf_band); + + mac_set_tx_power(priv->tx_power); + + /* Set the channel frequency */ + write_rf(hw, 0, freqTableAiroha_7230[channelIndex].integer); + udelay(150); /* Mike Schaffner says this is needed here */ + write_rf(hw, 1, freqTableAiroha_7230[channelIndex].fraction); + udelay(150); /* Mike Schaffner says this is needed here */ + write_rf(hw, 4, freqTableAiroha_7230[channelIndex].address4); + udelay(150); /* Mike Schaffner says this is needed here */ + + + // Select the frequency band: 5Ghz or 2.4Ghz + if (rf_band == IEEE80211_BAND_5GHZ) { + /* calibrate RF transceiver */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x12BAC); + udelay(50); + + /* configure the baseband processing engine */ + /* + * This bit always as to be turned off when we are using + * the Airoha chip, even though it's named the 5G EN bit. + * It has to do with how they hooked up the Airoha. + */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_GEN_5GEN, op_and); + } else { + /* calibrate RF transceiver */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x1ABA8); + udelay(50); + + /* configure the baseband processing engine */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_GEN_5GEN, op_and); + /* + * No short preambles allowed for ODFM. + */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_SH_PRE, op_and); + } + + /*Re-enable the rx processing path */ + if (enable_rx) + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_RX_EN, op_or); + + /* re-enable transmitter */ + write_reg(BB_OUTPUT_CONTROL, 0xfffff33f, op_and); + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME ": undefined rf transceiver!\n"); + return -EINVAL; + } + + /* + * This is a patch for a problem which should be corrected in + * hardware on new units. We are rewriting the MAC address + * because on units without the H/W patch the address can + * be corrupted when we change channels. + */ + piper_set_macaddr(priv); +digiWifiDumpRegisters(priv, MAIN_REGS); + + return 0; +} + +static int al7230_rf_set_chan(struct ieee80211_hw *hw, int channelIndex) +{ + return al7230_rf_set_chan_private(hw, channelIndex, true); +} + +static int al7230_rf_set_chan_no_rx(struct ieee80211_hw *hw, int channelIndex) +{ + return al7230_rf_set_chan_private(hw, channelIndex, false); +} + + + +static int al7230_set_txpwr(struct ieee80211_hw *hw, uint8_t value) +{ + struct piper_priv *priv = hw->priv; + + if (priv->pdata->rf_transceiver == RF_AIROHA_2236) { + const unsigned char powerTable_2236[] = { + 4, 10, 10, 18, 22, 22, 28, 28, + 33, 33, 36, 38, 40, 43, 45, 47 + }; + write_rf(hw, 9, 0x05440 | powerTable_2236[value & 0xf]); + } else if (priv->pdata->rf_transceiver == RF_AIROHA_7230) { + const unsigned char powerTable_7230[] = { + 0x14, 0x14, 0x14, 0x18, 0x18, 0x1c, 0x1c, 0x20, + 0x20, 0x24, 0x24, 0x29, 0x29, 0x2c, 0x2c, 0x30 + }; + int correctedPowerIndex = digiWifiCalibrationPowerIndex(priv); + + if (correctedPowerIndex != -1) { + write_rf(hw, 11, 0x08040 | correctedPowerIndex); + } else { + write_rf(hw, 11, 0x08040 | powerTable_7230[value & 0xf]); + } + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": undefined rf transceiver!\n"); + return -EINVAL; + } + return 0; +} + +static void al7230_set_power_index(struct ieee80211_hw *hw, unsigned int value) +{ + write_rf(hw, 11, 0x08040 | value); +} + +static void InitializeRF(struct ieee80211_hw *hw, int band_selection) +{ + struct piper_priv *priv = hw->priv; + + if (priv->pdata->rf_transceiver == RF_AIROHA_2236) { + digi_dbg("**** transceiver == RF_AIROHA_2236\n"); + /* Initial settings for 20 MHz reference frequency, 802.11b/g */ + + /* CH_integer: Frequency register 0 */ + write_rf(hw, 0, 0x01f79 ); + + /* CH_fraction: Frequency register 1 */ + write_rf(hw, 1, 0x03333 ); + + /*Config 1 = default value */ + write_rf(hw, 2, 0x00B80 ); + + /*Config 2 = default value */ + write_rf(hw, 3, 0x00E7F ); + + /*Config 3 = default value */ + write_rf(hw, 4, 0x0905A ); + + /*Config 4 = default value */ + write_rf(hw, 5, 0x0F4DC ); + + /*Config 5 = default value */ + write_rf(hw, 6, 0x0805B ); + + /*Config 6 = Crystal frequency /2 to pll reference divider */ + write_rf(hw, 7, 0x0116C ); + + /*Config 7 = RSSI = default value */ + write_rf(hw, 8, 0x05B68 ); + + /* TX gain control for LA2236 */ + write_rf(hw, 9, 0x05460 ); // sit at the middle + + /* RX Gain = digi specific value: AGC adjustment is done over the GC1-GC7 + IC pins interface. AGC MAX GAIN value is configured in the FPGA BB register + instead of the RF register here below */ + write_rf(hw, 10, 0x001BB ); + + /* TX Gain = digi specific vaue: TX GAIN set using the register */ + write_rf(hw, 11, 0x000f9 ); + + /* PA current = default value */ + write_rf(hw, 12, 0x039D8 ); + + /* Config 8 = default value */ + write_rf(hw, 13, 0x08000 ); + + /* Config 9 = default value */ + write_rf(hw, 14, 0x00000 ); + + /* Config 10 = default value */ + write_rf(hw, 15, 0x00587 ); + + //mac_set_tx_power (macParams.tx_power); + + /* Calibration procedure */ + write_reg(BB_OUTPUT_CONTROL, 0x00000300, op_or); + udelay(150); + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x00D87 ); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x00787 ); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x00587 ); + udelay(50); + } else if (priv->pdata->rf_transceiver == RF_AIROHA_7230) { + switch (band_selection) { + case IEEE80211_BAND_2GHZ: + /* Initial settings for 20 MHz reference frequency, 802.11b/g */ + write_reg(BB_OUTPUT_CONTROL, 0xfffffcff, op_and); + write_reg(BB_OUTPUT_CONTROL, 0x00000200, op_or); + udelay(150); + + /* Frequency register 0 */ + write_rf(hw, 0, 0x00379 ); + + /* Frequency register 1 */ + write_rf(hw, 1, 0x13333 ); + udelay(10); + + /*Config 1 = default value */ + write_rf(hw, 2, 0x841FF ); + + /*Config 2 = default value */ + write_rf(hw, 3, 0x3FDFA ); + + /*Config 3 = default value */ + write_rf(hw, 4, 0x7FD78 ); + + /*Config 4 = default value */ + write_rf(hw, 5, 0x802BF ); + + /*Config 5 = default value */ + write_rf(hw, 6, 0x56AF3 ); + + /*Config 6 = Crystal frequency /2 to pll reference divider */ + write_rf(hw, 7, 0xCE000 ); + + /*Config 7 = RSSI = default value */ + write_rf(hw, 8, 0x6EBC0 ); + + /* Filter BW = default value */ + write_rf(hw, 9, 0x221BB ); + + /* RX Gain = digi specific value: AGC adjustment is done over the GC1-GC7 + IC pins interface. AGC MAX GAIN value is configured in the FPGA BB register + instead of the RF register here below */ + write_rf(hw, 10, 0xE0040 ); + + /* TX Gain = digi specific vaue: TX GAIN set using the register */ + // write_rf(hw, 11, 0x08070); + mac_set_tx_power (priv->tx_power); //Digi value + + /* PA current = default value */ + set_hw_specific_parameters(hw, IEEE80211_BAND_2GHZ, hw_revision, hw_platform); + + /* Config 8 = default value */ + write_rf(hw, 13, 0xFFFFF ); + + /* Config 9 = default value */ + write_rf(hw, 14, 0x00000 ); + + /* Config 10 = default value */ + write_rf(hw, 15, 0x1ABA8 ); + + /* Calibration procedure */ + write_reg(BB_OUTPUT_CONTROL, 0x00000300, op_or); + + udelay(150); + + /* Calibration procedure */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x1ABA8 ); + udelay(50); + + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_MAX_GAIN_MASK, op_and); + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_DEFAULT_MAX_GAIN_BG, op_or); + break; + + case IEEE80211_BAND_5GHZ: + /* Initial settings for 20 MHz reference frequency, 802.11a */ + write_reg(BB_OUTPUT_CONTROL, 0xfffffcff, op_and); + write_reg(BB_OUTPUT_CONTROL, 0x00000200, op_or); + udelay(150); + + /* Frequency register 0 */ + write_rf(hw, 0, 0x0FF56 ); + + /* Frequency register 1 */ + write_rf(hw, 1, 0x0AAAA ); + + udelay(10); + + /*Config 1 = default value */ + write_rf(hw, 2, 0x451FE ); + + /*Config 2 = default value */ + write_rf(hw, 3, 0x5FDFA ); + + /*Config 3 = default value */ + write_rf(hw, 4, 0x67f78 ); + + /*Config 4 = default value */ + write_rf(hw, 5, 0x853FF ); + + /*Config 5 = default value */ + write_rf(hw, 6, 0x56AF3 ); + + /*Config 6 = Crystal frequency /2 to pll reference divider */ + write_rf(hw, 7, 0xCE000 ); + + /*Config 7 = RSSI = default value */ + write_rf(hw, 8, 0x6EBC0 ); + + /* Filter BW = default value */ + write_rf(hw, 9, 0x221BB ); + + /* RX Gain = digi value */ + write_rf(hw, 10, 0xE0600 ); + + /* TX Gain = digi specific vaue: TX GAIN set using the register */ + // write_rf(hw, 11, 0x08070 ); + mac_set_tx_power (priv->tx_power); //Digi value + + /* PA current = default value */ + set_hw_specific_parameters(hw, IEEE80211_BAND_5GHZ, hw_revision, hw_platform); + + /* Config 8 = default value */ + write_rf(hw, 13, 0xFFFFF ); + + /* Config 9 = default value */ + write_rf(hw, 14, 0x00000 ); + + /* Config 10 = default value */ + write_rf(hw, 15, 0x12BAC ); + + /* Calibration procedure */ + write_reg(BB_OUTPUT_CONTROL, 0x00000300, op_or); + + udelay(150); + + /* Calibration procedure */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x12BAC ); + udelay(50); + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_MAX_GAIN_MASK, op_and); + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_DEFAULT_MAX_GAIN_A, op_or); + break; + } + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": undefined rf transceiver!\n"); + } +} + +static int al7230_rf_stop(struct ieee80211_hw *hw) +{ + return 0; +} + +static void getOfdmBrs(int channelIndex, u64 brsBitMask, unsigned int *ofdm, unsigned int *psk) +{ + /* + * brsBitMask is a bit mask into the al7230_bg_rates array. Bit 0 refers + * to the first entry in the array, bit 1 the second, and so on. The first + * 4 bits/array entries refer to the PSK bit rates we support, the next 8 + * bits/array entries refer to the OFDM rates we support. So the PSK BRS + * mask is bits 0-3, the OFDM bit mask is bits 4-11. + */ + + if (getBand(channelIndex) == IEEE80211_BAND_2GHZ) + { + *psk = brsBitMask & 0xf; + *ofdm = (brsBitMask & 0xff0) >> 4; + } + else + { + *psk = 0; + *ofdm = (brsBitMask & 0xff); + } +} + +static struct ieee80211_supported_band al7230_bands[] = { + { + .band = IEEE80211_BAND_2GHZ, + .n_channels = ARRAY_SIZE(al7230_bg_channels), + .n_bitrates = ARRAY_SIZE(al7230_bg_rates), + .channels = (struct ieee80211_channel *) al7230_bg_channels, + .bitrates = (struct ieee80211_rate *) al7230_bg_rates, + }, + { + .band = IEEE80211_BAND_5GHZ, + .n_channels = ARRAY_SIZE(al7230_a_channels), + .n_bitrates = ARRAY_SIZE(al7230_a_rates), + .channels = (struct ieee80211_channel *) al7230_a_channels, + .bitrates = (struct ieee80211_rate *) al7230_a_rates, + }, +}; + +static const struct ieee80211_rate *getRate(unsigned int rateIndex) +{ + return &al7230_bg_rates[rateIndex]; +} + + +/* + * This routine can power up or power down the airoha transceiver. + * When the transceiver is powered back up, you must delay 1 ms and + * then call the set channel routine to make it operational again. + */ +static void power_on(struct ieee80211_hw *hw, bool want_power_on) +{ + if (want_power_on) { + write_rf(hw, 15, 0x1ABA8 ); /* this is actually for 2 Ghz */ + } else { + write_rf(hw, 15, 0x1ABAE ); + } +} + +static void al7230_set_hw_info(struct ieee80211_hw *hw, int channel, + u16 hw_platform_code) +{ + hw_revision = hw_platform_code & WCD_HW_REV_MASK; + hw_platform = (hw_platform_code & WCD_PLATFORM_MASK); + + set_hw_specific_parameters(hw, getBand(channel), hw_revision, hw_platform); +} + +struct digi_rf_ops al7230_rf_ops = { + .name = "Airoha 7230", + .init = InitializeRF, + .stop = al7230_rf_stop, + .set_chan = al7230_rf_set_chan, + .set_chan_no_rx = al7230_rf_set_chan_no_rx, + .set_pwr = al7230_set_txpwr, + .set_pwr_index = al7230_set_power_index, + .set_hw_info = al7230_set_hw_info, + .channelChangeTime = CHANNEL_CHANGE_TIME, + .maxSignal = MAX_SIGNAL_IN_DBM, + .getOfdmBrs = getOfdmBrs, + .getBand = getBand, + .getFrequency = getFrequency, + .getRate = getRate, + .bands = al7230_bands, + .power_on = power_on, + .n_bands = ARRAY_SIZE(al7230_bands), +}; +EXPORT_SYMBOL_GPL(al7230_rf_ops); diff --git a/drivers/net/wireless/digiPiper/airoha.h b/drivers/net/wireless/digiPiper/airoha.h new file mode 100644 index 000000000000..aa3b1d10fbe3 --- /dev/null +++ b/drivers/net/wireless/digiPiper/airoha.h @@ -0,0 +1,30 @@ +#ifndef DIGI_RF_AH7230_H_ +#define DIGI_RF_AH7230_H_ + +/* 0 is reserved for unknown transceiver */ +#define RF_AIROHA_7230 (1) +#define RF_AIROHA_2236 (2) + +#define SPI_INIT_AIROHA (0x00000018) /* AIROHA-specific SPI length */ +#define SPI_INIT_AIROHA2236 (0x00000014) /* AIROHA 2236-specific SPI length */ +#define GEN_INIT_AIROHA_24GHZ (0x31720005) /* Initial state; 2.4GHZ_PA_ON= active low; bit 25 */ +#define GEN_INIT_AIROHA_50GHZ (0x33760008) /* Initial state; 5.0GHZ_PA_ON= active high; bit 25 */ + +#define AIROHA_LOWEST_PSK_RATE_INDEX (0) +#define AIROHA_LOWEST_OFDM_RATE_INDEX (4) +#define AIROHA_55_MBPS_RATE_INDEX (2) + +/* + * Subtract this number from the channel index to index into + * the 802.11a channel array. + */ +#define BAND_A_OFFSET (17) + +struct digi_rf_freq { + uint16_t integer; + uint16_t fract; +}; + +extern struct digi_rf_ops al7230_rf_ops; + +#endif diff --git a/drivers/net/wireless/digiPiper/airohaCalibration.c b/drivers/net/wireless/digiPiper/airohaCalibration.c new file mode 100644 index 000000000000..9da912f320ba --- /dev/null +++ b/drivers/net/wireless/digiPiper/airohaCalibration.c @@ -0,0 +1,974 @@ +/* + * This file contains the code which performs automatic recalibration of the + * Airoha transceiver. + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free softbware; 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/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/timer.h> +#include <linux/crc32.h> + +#include "pipermain.h" +#include "mac.h" +#include "airoha.h" +#include "airohaCalibration.h" +#include "adc121c027.h" + +#define POWER_INDEX_STEP (1) /* TODO: come up with a rational value for this */ + +#define SAMPLE_TIMEOUT (HZ * 10) /* TODO: What is a good sample timeout? Do we need one? */ +#define RECALIBRATION_PERIOD (HZ * 15) /* amount of time to wait between recalibrations */ + +/* + * This defines the amount of time to wait for the power levels + * to settle down after making a large correction (user has + * changed power level). + */ +#define DEBOUNCE_DELAY (HZ * 2) + +#define MAX_TOLERATED_ERROR_HIGH (300) /* maximum over target we will allow */ +#define MAX_TOLERATED_ERROR_LOW (500) /* maximum under the target we will allow */ +#define CONVERT_TO_MDBM(x) (1000 * (x)) /* power levels are dBm externally, but dBm/1000 internally */ + +#define NVRAM_WCAL_SIGNATURE "WCALDATA" + +#define MINIMUM_POWER_INDEX (10) + +/* + * Set piperp->calibrationTxRate to this value to make the + * transmit routine use the data rate specified by the + * mac80211 library. + */ +#define USE_MAC80211_DATA_RATE (NULL) + +/* + * Events we will wait for, also return values for waitForEvent(). + */ +#define TIMED_OUT_EVENT (1 << 0) +#define TRANSMIT_DONE_EVENT (1 << 1) +#define SHUTDOWN_AUTOCALIBRATION_EVENT (1 << 2) +#define RESTART_AUTOCALIBRATION_EVENT (1 << 3) + +/* + * Set this constant to (1) if you want to force calibration status to + * be printed. + */ +#define WANT_CALIBRATION_STATUS (0) + + +static struct airohaCalibrationData calibration; + +static DECLARE_WAIT_QUEUE_HEAD(waitQueue); + + + +/* + * This routine is called to shut down the transmit ADC sampler. + */ +static void stopSampler(struct piper_priv *digi) +{ + digi->tx_calib_cb = NULL; +} + +/* + * This routine is called to update the state of the calibration state machine + * and wake up its thread. + */ +static void kickCalibrationThread(struct piper_priv *digi, unsigned int event) +{ + unsigned long spinlockFlags; + + spin_lock_irqsave(&calibration.lock, spinlockFlags); + calibration.events |= event; + spin_unlock_irqrestore(&calibration.lock, spinlockFlags); + wake_up_interruptible(&waitQueue); +} + + +/* + * This routine is called each time we complete a transmit while we are in + * the sampling state. We record the peak ADC reading. We kick the state + * machine if we now have all the samples we need. + */ +static void processSample(struct piper_priv *digi) +{ +#define MINIMUM_ADC_VALUE (10) /* if ADC is below this value, it's probably bad */ + if (calibration.sampleCount < MAX_SAMPLES) { + /* + * Read the ADC value. It is a 12-bit value. We shift it 4 bits to + * create an 8-bit value. + */ + calibration.sample[calibration.sampleCount].sample = + (calibration.cops->adc_read_peak(&calibration) >> 4); + if (calibration.sample[calibration.sampleCount].sample > + MINIMUM_ADC_VALUE) { + calibration.sampleCount++; + } + } +} + +static void transmitHasCompleted(struct piper_priv *digi) +{ + stopSampler(digi); + kickCalibrationThread(digi, TRANSMIT_DONE_EVENT); +} + + + +/* + * Determine the appropriate transmit rate to use during calibration. + */ +static struct ieee80211_rate *determineCalibrationTxRate(struct piper_priv + *digi) +{ + unsigned int rates = digi->ac->rd_reg(digi, MAC_SSID_LEN); + struct ieee80211_rate *calibrationTxRate; + + rates &= (MAC_PSK_BRS_MASK | MAC_OFDM_BRS_MASK); + + if ((digi->rf->getBand(digi->channel) == IEEE80211_BAND_2GHZ) + && (rates & MAC_PSK_BRS_MASK)) { + calibrationTxRate = + (struct ieee80211_rate *) digi->rf-> + getRate(AIROHA_LOWEST_PSK_RATE_INDEX); + } else { + calibrationTxRate = + (struct ieee80211_rate *) digi->rf-> + getRate(AIROHA_LOWEST_OFDM_RATE_INDEX); + } + + return calibrationTxRate; +} + + + + +/* + * Start collecting sample ADC peak measurements for calibration. Start + * the process by installing the callbacks which the transmit code will + * use to notify us when transmit frames go out. + */ +static void startSampleCollection(struct piper_priv *digi) +{ + calibration.cops->adc_clear_peak(&calibration); + digi->tx_calib_cb = transmitHasCompleted; +} + + +static unsigned int waitForEvent(unsigned int timeout, unsigned int eventToWaitFor) +{ +#define ALL_EVENTS_TO_WAIT_FOR(x) (eventToWaitFor \ + | SHUTDOWN_AUTOCALIBRATION_EVENT \ + | RESTART_AUTOCALIBRATION_EVENT) + + unsigned long spinlockFlags; + int ccode; + unsigned int event; + int result = TIMED_OUT_EVENT; + + if (timeout != 0) { + ccode = wait_event_interruptible_timeout(waitQueue, + ((calibration. + events & + ALL_EVENTS_TO_WAIT_FOR + (eventToWaitFor)) + != 0), timeout); + __set_current_state(TASK_RUNNING); + + } else { + ccode = 0; + } + spin_lock_irqsave(&calibration.lock, spinlockFlags); + event = calibration.events; + calibration.events = 0; + spin_unlock_irqrestore(&calibration.lock, spinlockFlags); + + if ((ccode < 0) || (event & SHUTDOWN_AUTOCALIBRATION_EVENT)) { + result = SHUTDOWN_AUTOCALIBRATION_EVENT; + } else if (event & RESTART_AUTOCALIBRATION_EVENT) { + result = RESTART_AUTOCALIBRATION_EVENT; + } else if (event & eventToWaitFor) { + result = (event & eventToWaitFor); + } else { + result = TIMED_OUT_EVENT; + } + + return result; +} + + +#ifdef WANT_CAL_DEBUG +static void printPoint(wcd_point_t * p) +{ + printk("(%d, %d, %d)", p->out_power, p->adc_val, p->power_index); +} +#endif + + +/* + * This routine finds the closest pair of points in a calibration curve. + * + * curve calibration curve + * value look for the pr of points closest to this value + * p1 storage for one point + * p2 storage for another point + * field tells us which field in the point struct to compare + */ +static void findClosestPoints(wcd_curve_t * curve, int value, + wcd_point_t ** p1, wcd_point_t ** p2, int field) +{ + if (value <= curve->points[0].out_power) { + *p1 = &curve->points[0]; + *p2 = &curve->points[1]; + } else if (value >= + curve->points[calibration.nvram->header.numcalpoints - 1].out_power) { + *p1 = &curve->points[calibration.nvram->header.numcalpoints - 2]; + *p2 = &curve->points[calibration.nvram->header.numcalpoints - 1]; + } else { + unsigned int idx; + + for (idx = 1; idx < calibration.nvram->header.numcalpoints; idx++) { + if ((value < curve->points[idx].out_power) + || (idx == (calibration.nvram->header.numcalpoints - 1))) { + *p1 = &curve->points[idx - 1]; + *p2 = &curve->points[idx]; + break; + } else if (value == curve->points[idx].out_power) { + /* + * Note that the if statement befpre the for loop already tested for the + * value being equal to the first or last point in the curve, so we don't + * have to worry about that condition in the code below. + */ + if ((value - + curve->points[idx - 1].out_power) >= + (curve->points[idx + 1].out_power - value)) { + /* + * If the two points are equal distant, then favor the larger pair + * because I think the values on the low end are screwy. + */ + *p1 = &curve->points[idx]; + *p2 = &curve->points[idx + 1]; + } else { + *p1 = &curve->points[idx - 1]; + *p2 = &curve->points[idx]; + } + break; + } + } + } +} + + +/* + * Compute the slope of a curve between 2 points. The slope is the rise over the run, + * or (Y2 - Y1)/(X2 - X1). This function handles more than one type of slope. + */ +static int computeSlopeTimes1000(wcd_point_t * p1, wcd_point_t * p2, int slopeType) +{ + int slope = 0; + int divisor; + + switch (slopeType) { + default: + digi_dbg("Unexpected slope type %d.\n", slopeType); + break; + case POWER_INDEX_OVER_OUT_POWER: + divisor = (p2->out_power - p1->out_power); + if (divisor != 0) { + slope = + (((p2->power_index - p1->power_index) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + case ADC_OVER_OUT_POWER: + divisor = (p2->out_power - p1->out_power); + if (divisor != 0) { + slope = + (((p2->adc_val - p1->adc_val) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + case OUT_POWER_OVER_ADC: + divisor = (p2->adc_val - p1->adc_val); + if (divisor != 0) { + slope = + (((p2->out_power - p1->out_power) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + case POWER_INDEX_OVER_ADC: + divisor = (p2->adc_val - p1->adc_val); + if (divisor != 0) { + slope = + (((p2->power_index - p1->power_index) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + } + + return slope; +} + + +/* + * If (x,y) is a point on a curve, then compute y given x, the slope of the curve, + * and a known point on the curve. + * + * If (Xd, Yd) is the desired point, p1 is the known point, and m the slope, then + * + * Yd - p1->y = m(Xd - p1->x) + * Yd = m(Xd - p1->x) + p1->y + * Yd = m(Xd) - m(p1->x) + p1->y + */ +static int computeY(wcd_point_t * p1, int slopeTimes1000, int x, int slopeType) +{ + int y = 0; + + switch (slopeType) { + default: + digi_dbg("Unexpected slope type %d.\n", slopeType); + break; + case POWER_INDEX_OVER_OUT_POWER: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->out_power) + 500) / 1000) + p1->power_index; + break; + case ADC_OVER_OUT_POWER: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->out_power) + 500) / 1000) + p1->adc_val; + break; + case OUT_POWER_OVER_ADC: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->adc_val) + 500) / 1000) + p1->out_power; + break; + case POWER_INDEX_OVER_ADC: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->adc_val) + 500) / 1000) + p1->power_index; + break; + } + + return y; +} + + +/* + * Return a pointer to the curve we should use for the currently selected + * channel. + */ +static wcd_curve_t *determineCurve(struct piper_priv *digi) +{ + unsigned int rates = digi->ac->rd_reg(digi, MAC_SSID_LEN); + wcd_curve_t *curve = NULL; + + if (digi->rf->getBand(digi->channel) == IEEE80211_BAND_2GHZ) { + if (rates & MAC_PSK_BRS_MASK) { + /* + * This is the normal case for 802.11b and 802.11bg. We select + * the PSK curve. + */ + digi_dbg("Using bg curve [%d][%d]\n", + digi->channel, WCD_B_CURVE_INDEX); + curve = &calibration.nvram->cal_curves_bg[digi->channel] + [WCD_B_CURVE_INDEX]; + } else { /* if associated with AP that only supports G rates */ + + /* + * This is a very unusual, but theoretically possible case. We + * are associated with an AP that only supports OFDM modulation + * (G only, no B). We determine this by looking at the BRS + * setting. If no PSK rates are set for BRS, then we assume that + * this must be a G only AP. Obviously, we must select the OFDM curve. + */ + digi_dbg("Using bg curve [%d][%d]\n", + digi->channel, WCD_G_CURVE_INDEX); + curve = &calibration.nvram->cal_curves_bg[digi->channel] + [WCD_G_CURVE_INDEX]; + } + } else { + /* + * An 802.11a channel is selected. + */ + curve = &calibration.nvram->cal_curves_a[digi->channel - BAND_A_OFFSET]; + digi_dbg("Using A curve [%d]\n", digi->channel - BAND_A_OFFSET); + } + + return curve; +} + +/* + * Determine the maximum mdBm allowed for the current channel. Return the + * lesser of the max and the mdBm value passed to us. + */ +static int getFilteredPower(struct piper_priv *digi, int mdBm) +{ + int max = 0; + + if (digi->channel < BAND_A_OFFSET) { + max = 16000; /* all BG channels can go to 16 dBm */ + } else if (digi->channel < (BAND_A_OFFSET + 4)) { + max = 3000; /* first 4 A channels max out at 3 dBm */ + } else { + max = 8000; /* all other A channels can handle 8 dBm */ + } + + if (mdBm > max) { + mdBm = max; + } + + return mdBm; +} + + +/* + * This routine performs open loop calibration for Airoha. It takes a value in mdbm + * and uses the factory calibration routines to determine the appropriate register + * value to write to airoha. + */ +static void setInitialPowerLevel(struct piper_priv *digi, int mdBm) +{ + wcd_point_t *p1, *p2; + + mdBm = getFilteredPower(digi, mdBm); + digi_dbg("Setting initial powerlevel %d milli dBm.\n", mdBm); + calibration.curve = determineCurve(digi); + findClosestPoints(calibration.curve, mdBm, &p1, &p2, OUT_POWER); + calibration.slopeTimes1000 = + computeSlopeTimes1000(p1, p2, POWER_INDEX_OVER_OUT_POWER); + if (abs(p1->out_power - mdBm) < abs(p2->out_power - mdBm)) { + calibration.p1 = p1; + } else { + calibration.p1 = p2; + } + calibration.powerIndex = + computeY(calibration.p1, calibration.slopeTimes1000, mdBm, + POWER_INDEX_OVER_OUT_POWER); + calibration.correctedPowerIndex = calibration.powerIndex; + + digi_dbg("Computed power index = %d.\n", calibration.powerIndex); + digi->rf->set_pwr_index(digi->hw, calibration.powerIndex); + + /* + * Let's compute and save the expected ADC value while we have all the necessary + * information handy. + */ +#ifdef WANT_CAL_DEBUG + digi_dbg("Using points "); + printPoint(p1); + printPoint(p2); + printk("\n"); + digi_dbg("Max ADC = %d.\n", calibration.curve->max_adc_value); +#endif + calibration.adcSlopeTimes1000 = computeSlopeTimes1000(p1, p2, ADC_OVER_OUT_POWER); + calibration.expectedAdc = + computeY(calibration.p1, calibration.adcSlopeTimes1000, mdBm, + ADC_OVER_OUT_POWER); + if (calibration.expectedAdc > calibration.curve->max_adc_value) { + calibration.expectedAdc = calibration.curve->max_adc_value; + } + digi_dbg("adcSlopeTimes1000 = %d, expectedAdc = %d\n", + calibration.adcSlopeTimes1000, calibration.expectedAdc); + calibration.outPowerSlopeTimes1000 = + computeSlopeTimes1000(p1, p2, OUT_POWER_OVER_ADC); + calibration.powerIndexSlopeTimes1000 = + computeSlopeTimes1000(p1, p2, POWER_INDEX_OVER_ADC); +} + + + +/* + * This routine performs closed loop recalibration. It is called periodically + * to adjust the transmit power level. It will be called after the ADC levels + * for several transmit frames have been sampled. It does the following: + * + * 1. Average the samples together. + * 2. If the measured ADC level is too low, then increase the power + * level one step. + * 3. If the measured ADC level is too high, then decrease the power + * level one step. + */ +static void recalibrate(struct piper_priv *digi) +{ + unsigned int idx; + int actualAdc = 0; + int errorInAdc, errorInMdbm; + wcd_point_t p = { + .out_power = 0, + .adc_val = 0 + }; + int needCorrection = 0; + +#ifdef WANT_CAL_DEBUG_1 + digi_dbg("Samples: "); +#endif + for (idx = 0; idx < calibration.sampleCount; idx++) { +#ifdef WANT_CAL_DEBUG_1 + printk("%d, ", calibration.sample[idx].sample); +#endif + actualAdc += calibration.sample[idx].sample; + } +#ifdef WANT_CAL_DEBUG_1 + printk("\n"); +#endif + actualAdc = actualAdc / calibration.sampleCount; + +#ifdef WANT_TO_FORCE_26 + + digi->rf->set_pwr_index(digi->hw, 26); +#else + + errorInAdc = actualAdc - calibration.expectedAdc; + errorInMdbm = + computeY(&p, calibration.outPowerSlopeTimes1000, errorInAdc, + OUT_POWER_OVER_ADC); + needCorrection = (errorInMdbm > MAX_TOLERATED_ERROR_HIGH); + if (errorInMdbm < 0) { + needCorrection = ((MAX_TOLERATED_ERROR_LOW + errorInMdbm) < 0); + } + if (needCorrection) { + int correction = computeY(calibration.p1, + calibration.powerIndexSlopeTimes1000, + actualAdc, POWER_INDEX_OVER_ADC); +#if defined(WANT_CAL_DEBUG) + int oldIndex = calibration.correctedPowerIndex; +#endif + correction = (3 * (calibration.powerIndex - correction)) / 4; + if (correction == 0) { + if (errorInAdc > 0) { + /* + * If power level is too high but the minimum correction would + * overshoot the target, do it anyway because we would rather + * be below the target rather than over the target. + */ + correction = -1; + } else { + /* + * But if the power level is too low, but the minimum correction + * would overshoot, then leave it be. Better too low than too high. + */ + } + } + calibration.correctedPowerIndex += correction; + if (calibration.correctedPowerIndex < MINIMUM_POWER_INDEX) { + calibration.correctedPowerIndex = MINIMUM_POWER_INDEX; + } + digi->rf->set_pwr_index(digi->hw, calibration.correctedPowerIndex); +#ifdef WANT_CAL_DEBUG + digi_dbg + ("actualAdc = %d, expectedAdc = %d, error mdbm = %d\n", + actualAdc, calibration.expectedAdc, errorInMdbm); + digi_dbg + ("Power index corrected by %d: was %d, set to %d.\n", + correction, oldIndex, calibration.correctedPowerIndex); +#endif +#if WANT_CALIBRATION_STATUS + if (correction != 0) + printk(KERN_ERR + "error mdBm = %d, correcting airoha index by %d\n", + errorInMdbm, correction); +#endif + } +#endif +} + + +/* + * This routine is called by the 80211mac library to set a new power level. We + * update the value in context memory and then kick the autocalibration thread. + */ +static int setNewPowerLevel(struct ieee80211_hw *hw, uint8_t newPowerLevel) +{ + struct piper_priv *digi = hw->priv; + + (void) newPowerLevel; /* has already been written into piper->tx_power */ + /* + * Kick the calibration thread. + */ + stopSampler(digi); + kickCalibrationThread(digi, RESTART_AUTOCALIBRATION_EVENT); + + return 0; +} + + + + +/* + * Compute the maximum ADC value for a curve given the maximum + * allowed power in dBm. + */ +static u8 getMaxAdcValue(wcd_curve_t * curve, int dBm) +{ + wcd_point_t *p1, *p2; + int slopeTimes1000; + u8 result = 0; + int mdBm = 1000 * dBm; + + findClosestPoints(curve, mdBm, &p1, &p2, OUT_POWER); + slopeTimes1000 = computeSlopeTimes1000(p1, p2, ADC_OVER_OUT_POWER); + + if (abs(p1->out_power - mdBm) < abs(p2->out_power - mdBm)) { + result = computeY(p1, slopeTimes1000, mdBm, ADC_OVER_OUT_POWER); + } else { + result = computeY(p2, slopeTimes1000, mdBm, ADC_OVER_OUT_POWER); + } + + return result; +} + + +/* + * The version 1.0 calibration data provided maximum Airoha index + * values, but later versions provided maximum ADC values. This + * routine replaces the max Airoha indexes with max ADC values. + */ +static void determineMaxAdcValues(wcd_data_t * cdata) +{ + int i; + + for (i = 0; i < WCD_CHANNELS_BG; i++) { + /* + * All BG channels are limited to 16 dBm. + */ + cdata->cal_curves_bg[i][0].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_bg[i][0], 16); + cdata->cal_curves_bg[i][1].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_bg[i][1], 16); + } + for (i = 0; i < 4; i++) { + /* + * First 4 802.11a channels are limited to 3 dBm. + */ + cdata->cal_curves_a[i].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_a[i], 3); + } + for (i = 4; i < WCD_CHANNELS_A; i++) { + /* + * All other 802.11a channels are limited to 8 dBm. + */ + cdata->cal_curves_a[i].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_a[i], 8); + } +} + + + +/* + * This routine writes a default set of calibration values into the + * calibration data structure. Maximum power output is severely limited. + */ +static void setDefaultCalibrationValues(struct piper_priv *piperp) +{ +#define MIN_MDBM (-2905) +#define MAX_BG_MDBM (6000) +#define DEFAULT_NUM_POINTS (DEFAULT_NUM_POINTS) +#define BAND_A_1_START (0) +#define BAND_A_2_START (4) +#define BAND_A_3_START (7) +#define BAND_A_4_START (15) +#define BAND_A_5_START (19) +#define BAND_A_6_START (30) + + int i; + + calibration.nvram->header.numcalpoints = 2; + + for (i = 0; i < WCD_CHANNELS_BG; i++) { + calibration.nvram->cal_curves_bg[i][0].max_adc_value = 52; + calibration.nvram->cal_curves_bg[i][0].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_bg[i][0].points[0].adc_val = 19; + calibration.nvram->cal_curves_bg[i][0].points[0].power_index = 0; + calibration.nvram->cal_curves_bg[i][0].points[1].out_power = 6000; + calibration.nvram->cal_curves_bg[i][0].points[1].adc_val = 52; + calibration.nvram->cal_curves_bg[i][0].points[1].power_index = 16; + + calibration.nvram->cal_curves_bg[i][0].max_adc_value = 48; + calibration.nvram->cal_curves_bg[i][1].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_bg[i][1].points[0].adc_val = 12; + calibration.nvram->cal_curves_bg[i][1].points[0].power_index = 0; + calibration.nvram->cal_curves_bg[i][1].points[1].out_power = 6000; + calibration.nvram->cal_curves_bg[i][1].points[1].adc_val = 48; + calibration.nvram->cal_curves_bg[i][1].points[1].power_index = 24; + } + + for (i = BAND_A_1_START; i < BAND_A_2_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 22; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 11; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 0; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 22; + calibration.nvram->cal_curves_a[i].points[0].power_index = 19; + } + for (i = BAND_A_2_START; i < BAND_A_3_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 29; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 13; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 29; + calibration.nvram->cal_curves_a[i].points[0].power_index = 20; + } + for (i = BAND_A_3_START; i < BAND_A_4_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 42; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 15; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 4000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 42; + calibration.nvram->cal_curves_a[i].points[0].power_index = 22; + } + for (i = BAND_A_4_START; i < BAND_A_5_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 54; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 21; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 54; + calibration.nvram->cal_curves_a[i].points[0].power_index = 18; + } + for (i = BAND_A_5_START; i < BAND_A_6_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 39; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 13; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 39; + calibration.nvram->cal_curves_a[i].points[0].power_index = 26; + } + for (i = BAND_A_6_START; i < WCD_CHANNELS_A; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 31; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 11; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 31; + calibration.nvram->cal_curves_a[i].points[0].power_index = 30; + } +} + + +/* + * The calibration data is passed to the kernel from U-Boot. The kernel + * start up routines will have copied the data into digi->pdata->wcd. + * We do a few sanity checks on the data and set up our own pointers to + * it. + */ +static int getCalibrationData(struct piper_priv *digi) +{ + int result = 0; + + calibration.nvram = &digi->pdata->wcd; + + if (strncmp(calibration.nvram->header.magic_string, + NVRAM_WCAL_SIGNATURE, strlen(NVRAM_WCAL_SIGNATURE)) == 0) { + unsigned int crc = ~crc32_le(~0, + (unsigned char const *) calibration.nvram-> + cal_curves_bg, + calibration.nvram->header.wcd_len); + + if (crc == calibration.nvram->header.wcd_crc) { + digi_dbg("CRC and signature for calibration data is okay\n"); + result = 0; + if ((calibration.nvram->header.ver_major == '1') + && (calibration.nvram->header.ver_minor == '0')) { + digi_dbg("Converting version 1.0 calibration data\n"); + determineMaxAdcValues(calibration.nvram); + /* + * Now that we have updated the format of the data, we need + * to recompute the check sum and set the new version. + */ + calibration.nvram->header.ver_minor = '1'; + calibration.nvram->header.wcd_crc = + ~crc32_le(~0, (unsigned char const *) + calibration.nvram-> + cal_curves_bg, + calibration.nvram->header.wcd_len); + } + digi->rf->set_hw_info(digi->hw, digi->channel, + calibration.nvram->header.hw_platform); + } else { + digi_dbg("Calibration data has invalid CRC.\n"); + setDefaultCalibrationValues(digi); + } + } else { + digi_dbg("Calibration data has invalid signature.\n"); + setDefaultCalibrationValues(digi); + } + + return result; +} + + + +/* + * This routine: + * + * 1. Loads the ADC driver. + * 2. Loads the calibration data. + * 3. Implements the automatic calibration state machine. + * + */ +static int digiWifiCalibrationThreadEntry(void *data) +{ + struct piper_priv *digi = data; + int state; + + __set_current_state(TASK_RUNNING); + + while (1) { + /* + * We, the wireless driver, may be loaded before the I2C core has + * loaded. Therefore we may not be able to load our ADC driver, + * which is an I2C client driver, when we load. This loop tries + * and retries to load the ADC driver until it succeeds. + */ + + /* TODO, FIXME make following code dependent on platform information + * allowign to initialize different adc */ + if (adc121C027_init(&calibration, digi->pdata->i2c_adapter_num) == 0) { + digi_dbg("ADC driver loaded...\n"); + break; + } + digi_dbg("Will try to load ADC driver again...\n"); + ssleep(10); + } + + if (getCalibrationData(digi) == 0) { + digi->rf->set_pwr = setNewPowerLevel; + + state = RESTART_STATE; + + digi_dbg("Starting autocalibration state machine.\n"); + do { + int event; + int timeout = 0; + int expectedEvent = TIMED_OUT_EVENT; + int nextState = RESTART_STATE; + + switch (state) { + case RESTART_STATE: + setInitialPowerLevel(digi, + CONVERT_TO_MDBM(digi->tx_power)); + calibration.initialized = true; + calibration.sampleCount = 0; + nextState = COLLECT_SAMPLES_STATE; + timeout = DEBOUNCE_DELAY; + expectedEvent = TIMED_OUT_EVENT; + break; + case COLLECT_SAMPLES_STATE: + digi->calibrationTxRate = + determineCalibrationTxRate(digi); + startSampleCollection(digi); + nextState = GOT_SAMPLE_STATE; + timeout = SAMPLE_TIMEOUT; + expectedEvent = TRANSMIT_DONE_EVENT; + break; + case GOT_SAMPLE_STATE: + processSample(digi); + if (calibration.sampleCount < MAX_SAMPLES) { + nextState = COLLECT_SAMPLES_STATE; + timeout = 0; + break; + } + /* fall through is intended operation */ + case RECALIBRATE_STATE: + recalibrate(digi); + calibration.sampleCount = 0; + digi->calibrationTxRate = USE_MAC80211_DATA_RATE; + nextState = COLLECT_SAMPLES_STATE; + timeout = RECALIBRATION_PERIOD; + expectedEvent = TIMED_OUT_EVENT; + break; + default: + digi_dbg("Unknown state %d\n", state); + nextState = COLLECT_SAMPLES_STATE; + timeout = RECALIBRATION_PERIOD; + expectedEvent = TIMED_OUT_EVENT; + break; + } + + state = nextState; + event = waitForEvent(timeout, expectedEvent); + + switch (event) { + case SHUTDOWN_AUTOCALIBRATION_EVENT: + digi_dbg("Received SHUTDOWN_AUTOCALIBRATION_EVENT\n"); + break; + case RESTART_AUTOCALIBRATION_EVENT: + digi_dbg("Received RESTART_AUTOCALIBRATION_EVENT\n"); + state = RESTART_STATE; + break; + case TRANSMIT_DONE_EVENT: + break; + case TIMED_OUT_EVENT: + if (state == GOT_SAMPLE_STATE) { + state = COLLECT_SAMPLES_STATE; + } + } + } while (!kthread_should_stop()); + } else { + printk(KERN_ERR + "\nWarning: Wireless interface calibration data is corrupted.\n"); + printk(KERN_ERR + " The wireless interface will operate at a low power level.\n"); + while (!kthread_should_stop()) { + ssleep(1); + } + } + calibration.cops->adc_shutdown(&calibration); + + return 0; +} + + + +/* + * This routine is called at initialization to set up the Airoha calibration routines. + */ +void digiWifiInitCalibration(struct piper_priv *digi) +{ + + calibration.events = 0; + calibration.sampleCount = 0; + calibration.initialized = false; + + spin_lock_init(&calibration.lock); + + calibration.threadCB = + kthread_run(digiWifiCalibrationThreadEntry, digi, + PIPER_DRIVER_NAME " - calibration"); +} + +int digiWifiCalibrationPowerIndex(struct piper_priv *piperp) +{ + if (calibration.initialized) + return calibration.correctedPowerIndex; + else + return -1; +} + + +void digiWifiDeInitCalibration(struct piper_priv *digi) +{ + calibration.events = SHUTDOWN_AUTOCALIBRATION_EVENT; + wake_up_interruptible(&waitQueue); + kthread_stop(calibration.threadCB); + calibration.initialized = false; +} + +EXPORT_SYMBOL_GPL(digiWifiDeInitCalibration); diff --git a/drivers/net/wireless/digiPiper/airohaCalibration.h b/drivers/net/wireless/digiPiper/airohaCalibration.h new file mode 100644 index 000000000000..36543c87fc51 --- /dev/null +++ b/drivers/net/wireless/digiPiper/airohaCalibration.h @@ -0,0 +1,108 @@ +/* + * This file contains the code which performs automatic recalibration of the + * Airoha transceiver. + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 AIROHA_CALIBRATION_H +#define AIROHA_CALIBRATION_H + +#include "pipermain.h" + + +#define MAX_SAMPLES (3) + +/* + * Field values used for computing ABS values. + */ +enum { + OUT_POWER, + ADC_VAL, + POWER_INDEX +}; + +#ifdef WANT_CAL_DEBUG +static const char *fieldDescription[] = { + "OUT_POWER", + "ADC_VAL", + "POWER_INDEX" +}; +#endif + +/* + * States for the auto calibration thread. + */ +enum { + RESTART_STATE, + COLLECT_SAMPLES_STATE, + GOT_SAMPLE_STATE, + RECALIBRATE_STATE +}; + +#ifdef WANT_CAL_DEBUG +static const char *stateText[] = { + "RESTART_STATE", + "COLLECT_SAMPLES_STATE", + "GOT_SAMPLE_STATE", + "RECALIBRATE_STATE" +}; +#endif + + +/* + * Slope types accepted by computeSlope(). + */ +enum { + POWER_INDEX_OVER_OUT_POWER, + ADC_OVER_OUT_POWER, + OUT_POWER_OVER_ADC, + POWER_INDEX_OVER_ADC +}; + + +typedef struct { + unsigned rate; /* rate packet transmitted at */ + unsigned int sample; /* measured sample */ +} sampleInfo_t; + +struct airohaCalibrationData { + struct task_struct *threadCB; + spinlock_t lock; + volatile unsigned int events; + unsigned int sampleCount; + sampleInfo_t sample[MAX_SAMPLES]; + wcd_data_t *nvram; + wcd_curve_t *curve; + int slopeTimes1000; + int adcSlopeTimes1000; + int outPowerSlopeTimes1000; + int powerIndexSlopeTimes1000; + int expectedAdc; + int powerIndex, correctedPowerIndex; + wcd_point_t *p1; + + + void *priv; + struct calibration_ops *cops; + bool initialized; +}; + +struct calibration_ops { + u16(*adc_read_peak) (struct airohaCalibrationData *); + void (*adc_clear_peak) (struct airohaCalibrationData *); + u16(*adc_read_last_val) (struct airohaCalibrationData *); + void (*adc_shutdown) (struct airohaCalibrationData *); +}; + + +void digiWifiInitCalibration(struct piper_priv *digi); +void digiWifiDeInitCalibration(struct piper_priv *digi); +int digiWifiCalibrationPowerIndex(struct piper_priv *piperp); + +#endif /* AIROHA_CALIBRATION_H */ diff --git a/drivers/net/wireless/digiPiper/digiDebug.c b/drivers/net/wireless/digiPiper/digiDebug.c new file mode 100644 index 000000000000..627dd8c698ec --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiDebug.c @@ -0,0 +1,205 @@ +/* + * digiDebug.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 file contains some debugging routines. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" + +#define DUMP_WORDS_MAX (700) +static unsigned int dumpWordsWord[DUMP_WORDS_MAX]; +static unsigned int dumpWordsCount = 0; + +void digiWifiDumpWordsAdd(unsigned int word) +{ + if (dumpWordsCount < DUMP_WORDS_MAX) + { + dumpWordsWord[dumpWordsCount++] = word; + } +} +void digiWifiDumpWordsDump(void) +{ + unsigned int *p = dumpWordsWord; + unsigned int wordsToGo = dumpWordsCount; + + dumpWordsCount = 0; + + while (wordsToGo >= 4) + { + digi_dbg("%8.8X %8.8X - %8.8X %8.8X\n", p[0], p[1], p[2], p[3]); + p += 4; + wordsToGo -= 4; + } + if (wordsToGo == 3) + { + digi_dbg("%8.8X %8.8X - %8.8X\n", p[0], p[1], p[2]); + } + else if (wordsToGo == 2) + { + digi_dbg("%8.8X %8.8X \n", p[0], p[1]); + } + else if (wordsToGo == 1) + { + digi_dbg("%8.8X \n", p[0]); + } + digi_dbg("--------------\n"); +} + +void digiWifiDumpWordsReset(void) +{ + dumpWordsCount = 0; +} + + +void digiWifiDumpBuffer(u8 *buffer, unsigned int length) +{ + unsigned int i, word; + + digiWifiDumpWordsReset(); + + for (i = 0; i < length / sizeof(unsigned int); i++) + { + memcpy(&word, &buffer[i*sizeof(unsigned int)], sizeof(word)); + digiWifiDumpWordsAdd(cpu_to_be32(word)); + } + + digiWifiDumpWordsDump(); +} + + +void digiWifiDumpSkb(struct sk_buff *skb) +{ + unsigned int bytesLeft = skb->len; + unsigned char *p = skb->data; + + digi_dbg("skb has %d bytes\n", skb->len); + while (bytesLeft >= 16) + { + digi_dbg("%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X - %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + p += 16; + bytesLeft -= 16; + } + if (bytesLeft >= 8) + { + digi_dbg("%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + p += 8; + bytesLeft -= 8; + } + if (bytesLeft >= 4) + { + digi_dbg("%2.2X %2.2X %2.2X %2.2X \n", + p[0], p[1], p[2], p[3]); + p += 4; + bytesLeft -= 4; + } + if (bytesLeft >= 2) + { + digi_dbg("%2.2X %2.2X \n", + p[0], p[1]); + p += 2; + bytesLeft -= 2; + } + if (bytesLeft >= 1) + { + digi_dbg("%2.2X \n", + p[0]); + p += 1; + bytesLeft -= 1; + } +} + +EXPORT_SYMBOL_GPL(digiWifiDumpSkb); + + +void digiWifiDumpRegisters(struct piper_priv *digi, unsigned int regs) +{ +#ifdef WANT_DEBUG + unsigned int i; + + if (regs & CTRL_STATUS_REGS) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", "Gen Ctrl", digi->ac->rd_reg(digi, BB_GENERAL_CTL), + "Gen Status", digi->ac->rd_reg(digi, BB_GENERAL_STAT)); + } + else if (regs & IRQ_REGS) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", "IRQ Mask", digi->ac->rd_reg(digi, BB_IRQ_MASK), + "IRQ Status", digi->ac->rd_reg(digi, BB_IRQ_STAT)); + } + else if (regs & MAIN_REGS) + { + const char *regNames[] = {"Version", "Gen Ctrl", "Gen Status", "RSSI/AES", + "Int Mask", "Int Status", "SPI Data", "SPI Ctrl", + "Data FIFO", "not used", "conf-1", "conf-2", "AES FIFO", + "not used", "AES Ctrl", "IO Ctrl"}; + printk(KERN_ERR "Main Registers:\n"); + for (i = BB_VERSION; i <= BB_OUTPUT_CONTROL; i = i+8) + { + if ((i != BB_DATA_FIFO) && (i != BB_AES_FIFO)) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", regNames[i>>2], digi->ac->rd_reg(digi, i), regNames[(i>>2) + 1], digi->ac->rd_reg(digi, i+4)); + } + } + } + if (regs & MAC_REGS) + { + const char *regNames[] = {"STA ID0", "STA ID1", "BSS ID0", "BSS ID1", + "OFDM/PSK", "Backoff", "DTIM/List", "B Int", + "Rev/M Stat", "C C/M CTL", "Measure", "Beac Fltr"}; + + printk(KERN_ERR "Secondary Registers:\n"); + for (i = MAC_STA_ID0; i <= MAC_BEACON_FILT; i = i+8) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", regNames[((i - MAC_STA_ID0) >>2)], digi->ac->rd_reg(digi, i), regNames[((i - MAC_STA_ID0)>>2) + 1], digi->ac->rd_reg(digi, i+4)); + } + } + if (regs & FRAME_BUFFER_REGS) + { + unsigned int word[4]; + printk(KERN_ERR "Real time frame buffer\n"); + + word[0] = be32_to_cpu(digi->ac->rd_reg(digi, 0xc0)); + word[1] = be32_to_cpu(digi->ac->rd_reg(digi, 0xc4)); + word[2] = be32_to_cpu(digi->ac->rd_reg(digi, 0xc8)); + word[3] = be32_to_cpu(digi->ac->rd_reg(digi, 0xcc)); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + word[0] = be32_to_cpu(digi->ac->rd_reg(digi, 0xd0)); + word[1] = be32_to_cpu(digi->ac->rd_reg(digi, 0xd4)); + word[2] = be32_to_cpu(digi->ac->rd_reg(digi, 0xd8)); + word[3] = be32_to_cpu(digi->ac->rd_reg(digi, 0xdc)); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + } + if (regs & FIFO_REGS) + { + unsigned int word[4]; + printk(KERN_ERR "FIFO contents\n"); + + word[0] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[1] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[2] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[3] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + word[0] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[1] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[2] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[3] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + } +#endif +} +EXPORT_SYMBOL_GPL(digiWifiDumpRegisters); diff --git a/drivers/net/wireless/digiPiper/digiIsr.c b/drivers/net/wireless/digiPiper/digiIsr.c new file mode 100644 index 000000000000..e2128e254055 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiIsr.c @@ -0,0 +1,159 @@ +/* + * digiIsr.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 file contains the routines that are related to processing interrupts + * from the MAC. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" + +#define IRQ_DEBUG (0) +#if IRQ_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * This routine handles interrupts from the MAC. + */ +irqreturn_t piper_irq_handler(int irq, void *dev_id) +{ + struct piper_priv *piperp = dev_id; + u32 status; + + /* Acknowledge pending interrupts */ + status = piperp->ac->rd_reg(piperp, BB_IRQ_STAT); + status &= piperp->ac->rd_reg(piperp, BB_IRQ_MASK); + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, status, op_write); + + if (status & BB_IRQ_MASK_RX_FIFO) { + /* + * This interrupt indicates we have a frame in the FIFO. + * Set up to receive the packet. Disable further interrupts + * until the receive is complete. + */ + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_RX_FIFO); + /* + * Call the receive routine directly inside the irq handler + * or in the tasklet, depending on configuration. + */ +#if WANT_TO_RECEIVE_FRAMES_IN_ISR + piper_rx_tasklet((unsigned long) piperp); +#else + tasklet_hi_schedule(&piperp->rx_tasklet); +#endif + } + + if (status & BB_IRQ_MASK_TX_FIFO_EMPTY) { + /* + * Transmit complete interrupt. This IRQ is only unmasked if we are + * not expecting the packet to be ACKed. This will be the case for + * broadcasts. In this case, tell mac80211 the transmit occurred and + * restart the tx queue. + */ + if (piper_tx_getqueue(piperp) != NULL) { + piperp->tx_signal_strength = 0; + piperp->tx_result = TX_COMPLETE; + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + dprintk(DWARNING, "BB_IRQ_MASK_TX_FIFO_EMPTY and null packet?\n"); + } + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | BB_IRQ_MASK_TX_ABORT); + } + + if (status & BB_IRQ_MASK_TIMEOUT) { + /* AP did not ACK our TX packet */ + if (piper_tx_getqueue(piperp) != NULL) { + /* Update retry counter */ + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + dprintk(DWARNING, "BB_IRQ_MASK_TIMEOUT and null packet?\n"); + } + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | BB_IRQ_MASK_TX_ABORT); + } + + if (unlikely(status & BB_IRQ_MASK_TX_ABORT)) { + dprintk(DWARNING, "TX abort\n"); + + /* Could not transmit a packet because the media was busy */ + if (piper_tx_getqueue(piperp) != NULL) { + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + dprintk(DWARNING, "BB_IRQ_MASK_TX_ABORT and null packet?\n"); + } + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | BB_IRQ_MASK_TX_ABORT); + } + + if (status & BB_IRQ_MASK_TBTT) { + /* + * This interrupt occurs at the start of a beacon period. The only thing + * we need to do is to write a new beacon backoff value. + */ + u32 reg = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT) & ~MAC_BEACON_BACKOFF_MASK; + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, + reg | piperp->get_next_beacon_backoff(), op_write); + /* + * TODO: + * Improve the way we keep track of whether or not we sent the last + * beacon. What we are doing now is to assume that we did until and + * unless we receive a beacon. What we should do is look for either + * a beacon or a TX end interrupt. However, since mac80211 doesn't + * tell us what the ATIM window is, we have to assume it is zero, + * which means we could be transmitting a frame at the same + * time we are sending the beacon, so there isn't really any easy + * way for us to do this. In fact, even if there was an ATIM + * window, we could have started a transmit just before we get this + * interrupt, so I'm not sure how we are really suppose to keep + * track of this. + */ + /* assume we sent last beacon unless we receive one */ + piperp->beacon.weSentLastOne = true; + } + + if (status & BB_IRQ_MASK_ATIM) { + /* + * This interrupt should not occur since we are not using it. When in + * IBSS mode, the beacon period starts at the TBTT interrupt and ends + * at this interrupt. We are not suppose to send packets between the + * two interrupts. However, mac80211 does not seem to provide a way + * for us to find out how long the ATIM period is, so we have to assume + * that there isn't one. + * + * If we were supporting this interrupt we would have to synchronize + * with the transmit routine so that transmit is paused during this + * time. + */ + dprintk(DWARNING, "BB_IRQ_MASK_ATIM irq (0x%08x)\n", status); + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_ATIM); + } + + if (unlikely(status & BB_IRQ_MASK_RX_OVERRUN)) + piperp->pstats.rx_overruns++; + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(piper_irq_handler); + + + diff --git a/drivers/net/wireless/digiPiper/digiMac80211.c b/drivers/net/wireless/digiPiper/digiMac80211.c new file mode 100644 index 000000000000..b9ca3ef10230 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiMac80211.c @@ -0,0 +1,1000 @@ +/* + * digiMac80211.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 file contains the routines that interface with the mac80211 + * library. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/etherdevice.h> +#include <net/mac80211.h> +#include <crypto/aes.h> +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define MAC_DEBUG (1) + +#if MAC_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * This constant determines how many times per second the led_timer_fn + * function will be called. (HZ >> 3) means 8 times a second. + */ +#define LED_TIMER_RATE (HZ >> 3) +#define LED_MAX_COUNT (15) + +/* + * This function is called from a timer to blink an LED in order to + * indicate what are current state is. + */ +static void led_timer_fn(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *) context; + static unsigned int count = 0; + + if(!piperp->pdata->set_led) + return; + + switch (piperp->led_state) { + case led_shutdown: + /* Turn LED off if we are shut down */ + piperp->pdata->set_led(piperp, STATUS_LED, 0); + break; + case led_adhoc: + /* Blink LED slowly in ad-hoc mode */ + piperp->pdata->set_led(piperp, STATUS_LED, (count & 8) ? 0 : 1); + break; + case led_not_associated: + /* Blink LED rapidly when not associated with an AP */ + piperp->pdata->set_led(piperp, STATUS_LED, (count & 1) ? 0 : 1); + break; + case led_associated: + /* LED steadily on when associated */ + piperp->pdata->set_led(piperp, STATUS_LED, 1); + break; + default: + /* Oops. How did we get here? */ + break; + } + count++; + if (count > LED_MAX_COUNT) { + count = 0; + } + + piperp->led_timer.expires += LED_TIMER_RATE; + add_timer(&piperp->led_timer); +} + +/* + * This function sets the current LED state. + */ +static int piper_set_status_led(struct ieee80211_hw *hw, enum led_states state) +{ + struct piper_priv *piperp = (struct piper_priv *)hw->priv; + + piperp->led_state = state; + + if(!piperp->pdata->set_led) + return -ENOSYS; + + if (state == led_shutdown) + piperp->pdata->set_led(piperp, STATUS_LED, 0); + + return 0; +} + +/* + * This routine is called to enable IBSS support whenever we receive + * configuration commands from mac80211 related to IBSS support. The + * routine examines the configuration settings to determine if IBSS + * support should be enabled and, if so, turns on automatic beacon + * generation. + * + * TODO: This code may need to be moved into piper.c since other + * H/W does not implement automatic generate of beacons. + */ +static void piper_enable_ibss(struct piper_priv *piperp, enum nl80211_iftype iftype) +{ + dprintk(DVVERBOSE, "\n"); + + if (((iftype == NL80211_IFTYPE_ADHOC) || (iftype == NL80211_IFTYPE_MESH_POINT)) + && (piperp->beacon.loaded) && (piperp->beacon.enabled) + && ((piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & MAC_BEACON_INTERVAL_MASK) != 0)) { + /* + * If we come here, then we are running in IBSS mode, beacons are enabled, + * and we have the information we need, so start sending beacons. + */ + /* TODO: Handle non-zero ATIM period. mac80211 currently has no way to + * tell us what the ATIM period is, but someday they might fix that.*/ + + u32 reg = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT) & ~MAC_BEACON_BACKOFF_MASK; + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, + reg | piperp->get_next_beacon_backoff(), op_write); + /* enable beacon interrupts*/ + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_TBTT); + piperp->ac->wr_reg(piperp, + MAC_CTL, MAC_CTL_BEACON_TX | MAC_CTL_IBSS, op_or); + dprintk(DVERBOSE, "IBSS turned ON\n"); + piper_set_status_led(piperp->hw, led_adhoc); + } else { + /* + * If we come here, then either we are not suppose to transmit beacons, + * or we do not yet have all the information we need to transmit + * beacons. Make sure the automatic beacon function is disabled. + */ + /* shut down beacons */ + piperp->ac->wr_reg(piperp, MAC_CTL, + ~(MAC_CTL_BEACON_TX | MAC_CTL_IBSS), op_and); + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_TBTT); + dprintk(DVERBOSE, "IBSS turned OFF\n"); + } +} + +/* + * Set the transmit power level. The real work is done in the + * transceiver code. + */ +static int piper_set_tx_power(struct ieee80211_hw *hw, int power) +{ + struct piper_priv *digi = hw->priv; + int err; + int oldTxPower = digi->tx_power; + + dprintk(DVVERBOSE, "\n"); + + if (power == digi->tx_power) + return 0; + + digi->tx_power = power; + err = digi->rf->set_pwr(hw, power); + if (err) + digi->tx_power = oldTxPower; + + return err; +} + +/* + * Utility routine that sets a sequence number for a data packet. + */ +static void assign_seq_number(struct sk_buff *skb, bool increment) +{ +#define SEQUENCE_NUMBER_MASK (0xfff0) + static u16 seq_number = 0; + _80211HeaderType *header = (_80211HeaderType *)skb->data; + + dprintk(DVVERBOSE, "\n"); + + if (skb->len >= sizeof(*header)) { + u16 seq_field; + + /* + * TODO: memcpy's are here because I am concerned we may get + * an unaligned frame. Is this a real possibility? Or + * am I just wasting CPU time? + */ + memcpy(&seq_field, &header->squ.sq16, sizeof(header->squ.sq16)); + seq_field &= ~SEQUENCE_NUMBER_MASK; + seq_field |= (SEQUENCE_NUMBER_MASK & (seq_number << 4)); + memcpy(&header->squ.sq16, &seq_field, sizeof(header->squ.sq16)); + if (increment) + seq_number++; + } +} + +#define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +/* Get 16 bits at byte pointer */ +#define GET16(bp) ((bp)[0] | ((bp)[1] << 8)) +/* Get 32 bits at byte pointer */ +#define GET32(bp) ((bp)[0] | ((bp)[1] << 8) | ((bp)[2] << 16) | ((bp)[3] << 24)) +/* Store 16 bits at byte pointer */ +#define SET16(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; } +/* Store 32 bits at byte pointer */ +#define SET32(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; \ + (bp)[2] = (data) >> 16; \ + (bp)[3] = (data) >> 24; } + +static inline void dw_inc_48(u48* n) +{ + (*n)++; + *n &= ((u64) 1 << 48) - 1; +} + +/* + * This function prepares a blob of data we will have to send to the AES + * H/W encryption engine. The data consists of the AES initialization + * vector and 2 16 byte headers. + * + * Returns true if successful, or false if something goes wrong + */ +bool piper_prepare_aes_datablob(struct piper_priv *piperp, unsigned int keyIndex, + u8 *aesBlob, u8 *frame, u32 length, bool isTransmit) +{ + _80211HeaderType *header = (_80211HeaderType *)frame; + u8 *body = &frame[sizeof(*header)]; + int dlen = length - (_80211_HEADER_LENGTH + PIPER_EXTIV_SIZE); + + dprintk(DVVERBOSE, "\n"); + + if (keyIndex >= PIPER_MAX_KEYS) { + dprintk(DWARNING, "encryption key index %d is out of range.\n", + keyIndex); + return false; + } + + if (piperp->key[keyIndex].valid == false) + return false; + + /* Set up CCM initial block for MIC IV */ + memset(aesBlob, 0, AES_BLOB_LENGTH); + aesBlob[0] = 0x59; + aesBlob[1] = 0; + memcpy (&aesBlob[2], header->addr2, ETH_ALEN); + aesBlob[8] = body[7]; + aesBlob[9] = body[6]; + aesBlob[10] = body[5]; + aesBlob[11] = body[4]; + aesBlob[12] = body[1]; + aesBlob[13] = body[0]; + aesBlob[14] = dlen >> 8; + aesBlob[15] = dlen; + + /* Set up MIC header blocks */ +#define AES_HEADER_0_OFFSET (16) +#define AES_HEADER_1_OFFSET (32) + aesBlob[AES_HEADER_0_OFFSET+0] = 0; + aesBlob[AES_HEADER_0_OFFSET+1] = 22; + aesBlob[AES_HEADER_0_OFFSET+2] = frame[0] & 0xcf; + aesBlob[AES_HEADER_0_OFFSET+3] = frame[1] & 0xd7; + /* + * This memcpy writes data into the last 12 bytes of the first header + * and the first 6 bytes of the 2nd header. I did it as one memcpy + * call for efficiency. + */ + memcpy(&aesBlob[AES_HEADER_0_OFFSET+4], header->addr1, 3*ETH_ALEN); + aesBlob[AES_HEADER_1_OFFSET+6] = header->squ.sq.frag; + aesBlob[AES_HEADER_1_OFFSET+7] = 0; + memset (&aesBlob[AES_HEADER_1_OFFSET+8], 0, 8); /* clear vector location in data */ + + return true; +} + +/* + * mac80211 calls this routine to transmit a frame. We set up + * up the information the trasmit tasklet will need, and then + * schedule the tasklet. + */ +int piper_hw_tx_private(struct ieee80211_hw *hw, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb) +{ + struct piper_priv *piperp = hw->priv; + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(skb); + unsigned long flags; + + dprintk(DVVERBOSE, "\n"); + + if (piperp->is_radio_on == false) { + dprintk(DERROR, "called with radio off\n"); + return -EBUSY; + } + + /* + * Our H/W can only transmit a single packet at a time. mac80211 + * already maintains a queue of packets, so there is no reason + * for us to set up another one. We stop the mac80211 queue everytime + * we get a transmit request and restart it when the transmit + * operation completes. + */ + ieee80211_stop_queues(hw); + + if (txInfo->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + assign_seq_number(skb, + !!(txInfo->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)); + } + + piperp->use_hw_aes = false; + if (txInfo->control.hw_key != NULL) { + /* + * We've been passed an encryption key, so mac80211 must want us + * to encrypt the packet with our fancy H/W encryptor. Let's get + * set up for that now. + */ + piperp->tx_aes_key = txInfo->control.hw_key->hw_key_idx; + piperp->use_hw_aes = + piper_prepare_aes_datablob(piperp, + txInfo->control.hw_key->hw_key_idx, + (u8 *)piperp->tx_aes_blob, skb->data, + skb->len, true); + } + piper_ps_set_header_flag(piperp, ((_80211HeaderType *) skb->data)); /* set power management bit as appropriate*/ + + /* + * Add space at the start of the frame for the H/W level transmit header. + * We can't generate the header now. It must be generated everytime we + * transmit because the transmit rate changes when we do retries. + */ + skb_push(skb, TX_HEADER_LENGTH); + + piperp->pstats.tx_retry_index = 0; + piperp->pstats.tx_total_tetries = 0; + memset(piperp->pstats.tx_retry_count, 0, sizeof(piperp->pstats.tx_retry_count)); + txInfo->flags &= ~(IEEE80211_TX_STAT_TX_FILTERED | + IEEE80211_TX_STAT_ACK | + IEEE80211_TX_STAT_AMPDU | + IEEE80211_TX_STAT_AMPDU_NO_BACK); + piperp->pstats.tx_queue.len++; + piperp->pstats.tx_queue.count++; + + if (piper_tx_enqueue(piperp, skb, skb_return_cb) == -1) { + skb_pull(skb, TX_HEADER_LENGTH); /* undo the skb_push above */ + return -EBUSY; + } + + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + if (!piperp->tx_tasklet_running) { + piperp->tx_tasklet_running = true; + tasklet_hi_schedule(&piperp->tx_tasklet); + } + + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + + piperp->pstats.tx_start_count++; + + return 0; +} +EXPORT_SYMBOL_GPL(piper_hw_tx_private); + + +int piper_hw_tx(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + return piper_hw_tx_private(hw, skb, ieee80211_tx_status_irqsafe); +} + + +/* + * mac80211 calls this routine to initialize the H/W. + */ +static int piper_hw_start(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + int ret = 0; + + dprintk(DVVERBOSE, "\n"); + piperp->if_type = __NL80211_IFTYPE_AFTER_LAST; + + /* Initialize the MAC H/W */ + if ((ret = piperp->init_hw(piperp, IEEE80211_BAND_2GHZ)) != 0) { + dprintk(DERROR, "unable to initialize piper HW (%d)\n", ret); + return ret; + } + + piperp->is_radio_on = true; + + /* + * Initialize the antenna with the defualt setting defined in the + * probe function. This can be changed, currently, through a sysfs + * entry in the device directory + */ + if ((ret = piperp->set_antenna(piperp, piperp->antenna)) != 0) { + dprintk(DERROR, "piper_set_antenna_div() failed (%d)\n", ret); + return ret; + } + + /* set status led to link off */ + piper_set_status_led(hw, led_shutdown); + + /* Get the tasklets ready to roll */ + piperp->tx_result = TX_NOT_DONE; + tasklet_enable(&piperp->rx_tasklet); + tasklet_enable(&piperp->tx_tasklet); + + /* + * Enable receive interrupts, but leave the transmit interrupts + * and beacon interrupts off for now. + */ + piperp->clear_irq_mask_bit(piperp, 0xffffffff); + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_RX_OVERRUN | BB_IRQ_MASK_RX_FIFO); + enable_irq(piperp->irq); + + memset(piperp->bssid, 0, ETH_ALEN); + + return 0; +} + +/* + * mac80211 calls this routine to shut us down. + */ +static void piper_hw_stop(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + + /* Initialize the MAC H/W */ + if (piperp->deinit_hw) + piperp->deinit_hw(piperp); + + /* set status led to link off */ + if (piper_set_status_led(hw, led_shutdown)) + return; /* hardware's probably gone, give up */ + + /* turn off phy */ + piperp->rf->stop(hw); + + /* Disable interrupts before turning off */ + disable_irq(piperp->irq); + + /* turn off MAX_GAIN, ADC clocks, and so on */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RESET, op_and); + + /* turn off MAC control/mac filt/aes key */ + piperp->ac->wr_reg(piperp, MAC_CTL, 0, op_write); + + /* turn off interrupts */ + tasklet_disable(&piperp->rx_tasklet); + tasklet_disable(&piperp->tx_tasklet); + piperp->clear_irq_mask_bit(piperp, 0xffffffff); +} + +/* + * mac80211 calls this routine to really start the H/W. + * The device type is also set here. + */ +static int piper_hw_add_intf(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "if type: %x\n", conf->type); + + /* __NL80211_IFTYPE_AFTER_LAST means no mode selected */ + if (piperp->if_type != __NL80211_IFTYPE_AFTER_LAST) { + dprintk(DERROR, "unsupported interface type %x, expected %x\n", + conf->type, __NL80211_IFTYPE_AFTER_LAST); + return -EOPNOTSUPP; + } + + switch (conf->type) { + case NL80211_IFTYPE_ADHOC: + piper_set_status_led(piperp->hw, led_adhoc); + piperp->if_type = conf->type; + break; + + case NL80211_IFTYPE_STATION: + piper_set_status_led(hw, led_not_associated); + piperp->if_type = conf->type; + break; + + case NL80211_IFTYPE_MESH_POINT: + piperp->if_type = conf->type; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * mac80211 calls this function to shut down us down. + */ +static void piper_hw_rm_intf(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct piper_priv *digi = hw->priv; + + dprintk(DVVERBOSE, "\n"); + digi->if_type = __NL80211_IFTYPE_AFTER_LAST; +} + +/* + * mac80211 calls this function to pass us configuration information. + */ +static int piper_config(struct ieee80211_hw *hw, u32 changed) +{ + struct piper_priv *piperp = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + u32 tempval; + int err; + + dprintk(DVVERBOSE, "\n"); + + if (changed & IEEE80211_CONF_CHANGE_PS) { + /* + * Enable power save mode if bit set in flags, and if we are in station + * mode. Power save is not supported in ad-hoc/mesh mode. + */ + piper_ps_scan_event(piperp); + piper_ps_set(piperp, ( (conf->flags & IEEE80211_CONF_PS) + && (piperp->if_type == NL80211_IFTYPE_STATION) + && (piperp->areWeAssociated))); + } + /* Should we turn the radio off? */ + if ((piperp->is_radio_on = (conf->radio_enabled != 0)) != 0) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_RX_EN, op_or); + } else { + dprintk(DNORMAL, "Turning radio off\n"); + return piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_RX_EN, op_and); + } + + /* Adjust the beacon interval and listen interval */ + tempval = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & ~MAC_BEACON_INTERVAL_MASK; + tempval |= conf->beacon_int << MAC_BEACON_INTERVAL_SHIFT; + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, tempval, op_write); + + tempval = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD) & ~MAC_LISTEN_INTERVAL_MASK; + tempval |= conf->listen_interval; + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, tempval, op_write); + + /* Adjust the power level */ + if ((err = piper_set_tx_power(hw, conf->power_level)) != 0) { + dprintk(DERROR, "unable to set tx power to %d\n", + conf->power_level); + return err; + } + + /* Set channel */ + if (conf->channel->hw_value != piperp->channel) { + piper_ps_scan_event(piperp); + if ((err = piperp->rf->set_chan(hw, conf->channel->hw_value)) !=0) { + dprintk(DERROR, "unable to set ch to %d\n", + conf->channel->hw_value); + return err; + } + piperp->channel = conf->channel->hw_value; + } + + return 0; +} + +/* + * mac80211 calls this routine to set BSS related configuration settings. + */ +static int piper_hw_config_intf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_if_conf *conf) +{ + struct piper_priv *piperp = hw->priv; + u32 bssid[2]; + + dprintk(DVVERBOSE, "\n"); + + if (conf->changed & IEEE80211_IFCC_BSSID && + !is_zero_ether_addr(conf->bssid) && + !is_multicast_ether_addr(conf->bssid)) { + + piper_ps_scan_event(piperp); + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + dprintk(DVERBOSE, "BSSID: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + conf->bssid[0], conf->bssid[1], conf->bssid[2], + conf->bssid[3], conf->bssid[4], conf->bssid[5]); + + bssid[0] = conf->bssid[3] | conf->bssid[2] << 8 | + conf->bssid[1] << 16 | conf->bssid[0] << 24; + bssid[1] = conf->bssid[5] << 16 | conf->bssid[4] << 24; + + if ((bssid[0] == 0) && (bssid[1] == 0)) { + /* + * If we come here, then the MAC layer is telling us to set a 0 + * SSID. In this case, we really want to set the SSID to the + * broadcast address so that we receive broadcasts. + */ + bssid[0] = 0xffffffff; + bssid[1] = 0xffffffff; + } + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, bssid[0], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, bssid[1], op_write); + memcpy(piperp->bssid, conf->bssid, ETH_ALEN); + break; + default: + break; + } + } + + if ((conf->changed & IEEE80211_IFCC_BEACON) && + (piperp->if_type == NL80211_IFTYPE_ADHOC)) { + struct sk_buff *beacon = ieee80211_beacon_get(hw, vif); + struct ieee80211_rate rate; + + if (!beacon) + return -ENOMEM; + + rate.bitrate = 10; /* beacons always sent at 1 Megabit*/ + skb_push(beacon, TX_HEADER_LENGTH); + phy_set_plcp(beacon->data, beacon->len - TX_HEADER_LENGTH, &rate, 0); + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_BEACON_TX, op_and); + piperp->load_beacon(piperp, beacon->data, beacon->len); + + /* TODO: digi->beacon.enabled should be set by IEEE80211_IFCC_BEACON_ENABLED + when we update to latest mac80211 */ + piperp->beacon.enabled = true; + piper_enable_ibss(piperp, vif->type); + dev_kfree_skb(beacon); /* we are responsible for freeing this buffer*/ + } + + return 0; +} + +/* + * mac80211 wants to change our frame filtering settings. We don't + * actually support this. + */ +static void piper_hw_config_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, unsigned int *total_flags, + int mc_count, struct dev_addr_list *mclist) +{ + dprintk(DVVERBOSE, "\n"); + + /* we don't support filtering so clear all flags; however, we also + * can't pass failed FCS/PLCP frames, so don't clear those. */ + *total_flags &= (FIF_FCSFAIL | FIF_PLCPFAIL); +} + +/* + * There are 1024 TU's (time units) to a second, and HZ jiffies to a + * second. This macro converts TUs to jiffies. + */ +#define TU_TO_JIFFIES(x) (((x*HZ) + 512) / 1024) + + +/* + * mac80211 calls this routine when something about our BSS has changed. + * Usually, this routine only gets called when we associate, or disassociate. + */ +static void piper_hw_bss_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, u32 changed) +{ + struct piper_priv *piperp = hw->priv; + u32 reg; + + dprintk(DVVERBOSE, " changed = 0x%08x\n", changed); + + if (changed & BSS_CHANGED_ASSOC) { + piper_ps_scan_event(piperp); + /* Our association status has changed */ + if (piperp->if_type == NL80211_IFTYPE_STATION) { + piper_set_status_led(hw, conf->assoc ? led_associated : led_not_associated); + } + piperp->areWeAssociated = conf->assoc; + piperp->ps.aid = conf->aid; + + digi_dbg(" AP %s\n", conf->assoc ? + "associated" : "disassociated"); + } + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + piperp->tx_cts = conf->use_cts_prot; + } + if (changed & BSS_CHANGED_ERP_PREAMBLE) { +#define WANT_SHORT_PREAMBLE_SUPPORT (1) +/* TODO: Determine if short preambles really hurt us, or if I'm just seeing things. */ +#if WANT_SHORT_PREAMBLE_SUPPORT + if (conf->use_short_preamble) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_SH_PRE, op_or); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_SH_PRE, op_and); + } + piperp->use_short_preamble = conf->use_short_preamble; +#else + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_SH_PRE, op_or); +#endif + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + /* + * The list of transmit rates has changed. Update the + * rates we will receive at to match those the AP will + * transmit at. This should improve our receive performance + * since we won't listen to junk at the wrong rate. + */ + unsigned int ofdm = 0, psk = 0; + + reg = piperp->ac->rd_reg(piperp, MAC_SSID_LEN) & + ~(MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK); + + piperp->rf->getOfdmBrs(piperp->channel, conf->basic_rates, &ofdm, &psk); + reg |= ofdm << MAC_OFDM_BRS_SHIFT; + reg |= psk << MAC_PSK_BRS_SHIFT; + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, reg, op_write); + + dprintk(DVERBOSE, "BRS mask set to 0x%8.8X\n", reg); + + if (ofdm == 0) { + /* Disable ofdm receiver if no ofdm rates supported */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, + ~BB_GENERAL_STAT_A_EN, op_and); + } else { + /* Enable ofdm receiver if any ofdm rates supported */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, + BB_GENERAL_STAT_A_EN, op_or); + } + } + + /* Save new DTIM period */ + reg = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD) & ~MAC_DTIM_PERIOD_MASK; + reg |= conf->dtim_period << MAC_DTIM_PERIOD_SHIFT; + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, reg, op_write); + reg = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & ~MAC_DTIM_CFP_MASK; + piperp->ps.beacon_int = conf->beacon_int; + reg |= conf->beacon_int << 16; + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, reg, op_write); +} + +/* + * Use the SSL library routines to expand the AES key. + */ +static int piper_expand_aes_key(struct ieee80211_key_conf *key, + u32 *expandedKey) +{ + struct crypto_aes_ctx aes; + int ret; + + dprintk(DVVERBOSE, "\n"); + + if (key->keylen != AES_KEYSIZE_128) + return -EOPNOTSUPP; + + ret = crypto_aes_expand_key(&aes, key->key, key->keylen); + if (ret) + return -EOPNOTSUPP; + + memcpy(expandedKey, aes.key_enc, EXPANDED_KEY_LENGTH); + + return 0; +} + +/* + * mac80211 calls this routine to set a new encryption key, or to + * retire an old one. We support H/W AES encryption/decryption, so + * save the AES related keys. + */ +static int piper_hw_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + const u8 *local_address, const u8 *address, + struct ieee80211_key_conf *key) +{ + struct piper_priv *piperp = hw->priv; + int ret = 0; + + dprintk(DVVERBOSE, "\n"); + + if ((key->alg != ALG_CCMP) || (key->keyidx >= PIPER_MAX_KEYS)) { + /* + * If we come here, then mac80211 was trying to set a key for some + * algorithm other than AES, or trying to set a key index greater + * than 3. We only support AES, and only 4 keys. + */ + ret = -EOPNOTSUPP; + goto set_key_error; + } + key->hw_key_idx = key->keyidx; + + if (cmd == SET_KEY) { + ret = piper_expand_aes_key(key, piperp->key[key->keyidx].expandedKey); + if (ret) + goto set_key_error; + + if (!piperp->key[key->keyidx].valid) + piperp->aes_key_count++; + piperp->key[key->keyidx].txPn = 0; + piperp->key[key->keyidx].rxPn = 0; + piperp->key[key->keyidx].valid = (ret == 0); + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } else { + /* disable key */ + if (piperp->key[key->keyidx].valid) + piperp->aes_key_count--; + piperp->key[key->keyidx].valid = false; + } + + if (piperp->aes_key_count > 0) + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_AES_DISABLE, op_and); + else + piperp->ac->wr_reg(piperp, MAC_CTL, MAC_CTL_AES_DISABLE, op_or); + +set_key_error: + if (ret) + dprintk(DVERBOSE, "unable to set AES key\n"); + + return ret; +} + +/* + * mac80211 calls this routine to determine if we transmitted the + * last beacon. Unfortunately, we can't tell for sure. We give + * mac80211 our best guess. + */ +static int piper_hw_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + return piperp->beacon.weSentLastOne ? 1 : 0; +} + +static int piper_get_tx_stats(struct ieee80211_hw *hw, + struct ieee80211_tx_queue_stats *stats) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + if (stats) + memcpy(stats, &piperp->pstats.tx_queue, sizeof(piperp->pstats.tx_queue)); + + return 0; +} + +static int piper_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + if (stats) + memcpy(stats, &piperp->pstats.ll_stats, sizeof(piperp->pstats.ll_stats)); + + return 0; +} + +const struct ieee80211_ops hw_ops = { + .tx = piper_hw_tx, + .start = piper_hw_start, + .stop = piper_hw_stop, + .add_interface = piper_hw_add_intf, + .remove_interface = piper_hw_rm_intf, + .config = piper_config, + .config_interface = piper_hw_config_intf, + .configure_filter = piper_hw_config_filter, + .bss_info_changed = piper_hw_bss_changed, + .tx_last_beacon = piper_hw_tx_last_beacon, + .set_key = piper_hw_set_key, + .get_tx_stats = piper_get_tx_stats, + .get_stats = piper_get_stats, +}; + +/* + * This routine is called by the probe routine to allocate the + * data structure we need to communicate with mac80211. + */ +int piper_alloc_hw(struct piper_priv **priv, size_t priv_sz) +{ + struct piper_priv *piperp; + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(priv_sz, &hw_ops); + if (!hw) + return -ENOMEM; + + hw->flags |= IEEE80211_HW_RX_INCLUDES_FCS + | IEEE80211_HW_SIGNAL_DBM + | IEEE80211_HW_NOISE_DBM + | IEEE80211_HW_SPECTRUM_MGMT + | IEEE80211_HW_NO_STACK_DYNAMIC_PS +#if !WANT_SHORT_PREAMBLE_SUPPORT + | IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE +#endif + /* | IEEE80211_HW_SPECTRUM_MGMT TODO: Turn this on when we are ready*/; + + hw->queues = 1; + hw->ampdu_queues = 0; + hw->extra_tx_headroom = 4 + sizeof(struct ofdm_hdr); + piperp = hw->priv; + *priv = piperp; + piperp->pstats.tx_queue.len = 0; + piperp->pstats.tx_queue.limit = 1; + piperp->pstats.tx_queue.count = 0; + piperp->areWeAssociated = false; + memset(&piperp->pstats.ll_stats, 0, sizeof(piperp->pstats.ll_stats)); + piperp->hw = hw; + + return 0; +} +EXPORT_SYMBOL_GPL(piper_alloc_hw); + +/* + * This routine is called by the remove function to free the memory + * allocated by piper_alloc_hw. + */ +void piper_free_hw(struct piper_priv *piperp) +{ + ieee80211_free_hw(piperp->hw); +} +EXPORT_SYMBOL_GPL(piper_free_hw); + +/* + * This routine is called by the probe routine to register + * with mac80211. + */ +int piper_register_hw(struct piper_priv *priv, struct device *dev, + struct digi_rf_ops *rf) +{ + struct ieee80211_hw *hw = priv->hw; + u8 macaddr[8]; + u32 temp; + int i, ret; + + dprintk(DVVERBOSE, "\n"); + + priv->rf = rf; + for (i = 0; i < rf->n_bands; i++) { + enum ieee80211_band b = rf->bands[i].band; + hw->wiphy->bands[b] = &rf->bands[i]; + } + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_ADHOC) + | BIT(NL80211_IFTYPE_STATION) +/* TODO: Enable this | BIT(NL80211_IFTYPE_MESH_POINT) */ + ; + hw->channel_change_time = rf->channelChangeTime; + hw->vif_data_size = 0; + hw->sta_data_size = 0; + hw->max_rates = IEEE80211_TX_MAX_RATES; + hw->max_rate_tries = 5; /* completely arbitrary, and apparently ignored by the rate algorithm */ + hw->max_signal = rf->maxSignal; + hw->max_listen_interval = 10; /* I don't think APs will work with values larger than 4 actually */ + + SET_IEEE80211_DEV(hw, dev); + + temp = cpu_to_be32(priv->ac->rd_reg(priv, MAC_STA_ID0)); + memcpy(macaddr, &temp, sizeof(temp)); + temp = cpu_to_be32(priv->ac->rd_reg(priv, MAC_STA_ID1)); + memcpy(&macaddr[4], &temp, sizeof(temp)); + SET_IEEE80211_PERM_ADDR(hw, macaddr); + + if ((ret = ieee80211_register_hw(hw)) != 0) { + dprintk(DERROR, "unable to register ieee80211 hw\n"); + goto error; + } + + printk(KERN_INFO PIPER_DRIVER_NAME ": registered as %s\n", + wiphy_name(hw->wiphy)); + + init_timer(&priv->led_timer); + priv->led_state = led_shutdown; + priv->led_timer.function = led_timer_fn; + priv->led_timer.data = (unsigned long) priv; + priv->led_timer.expires = jiffies + LED_TIMER_RATE; + add_timer(&priv->led_timer); + +error: + return ret; +} +EXPORT_SYMBOL_GPL(piper_register_hw); + + +void piper_unregister_hw(struct piper_priv *piperp) +{ + dprintk(DVVERBOSE, "\n"); + del_timer_sync(&piperp->led_timer); + piper_set_status_led(piperp->hw, led_shutdown); + ieee80211_unregister_hw(piperp->hw); +} +EXPORT_SYMBOL_GPL(piper_unregister_hw); + + +MODULE_DESCRIPTION("Digi Piper WLAN core"); +MODULE_AUTHOR("contact support@digi.com for information about this code"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/digiPiper/digiPs.c b/drivers/net/wireless/digiPiper/digiPs.c new file mode 100644 index 000000000000..c6ae6b0b656a --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiPs.c @@ -0,0 +1,1164 @@ +/* + * digiPs.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 file contains the routines that are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "airoha.h" +#include "digiPs.h" + +/* + * Macro converts milliseconds to HZ. + * + * TODO: Look for standard Linux version of this. + */ +#define MILLS_TO_JIFFIES(x) ((((x)*HZ) + 500) / 1000) + + + + +#define MINIMUM_SLEEP_PERIOD (20) + +/* + * Amount of time before shutdown deadline to start the shutdown process. This + * should allow enough time to get one last frame out, which will be the + * null-data frame notifying the AP that we are shutting down. + */ +#define PS_TRANSMITTER_SHUTDOWN_MS (10) + +/* + * Amount of time we will wake up before the next beacon. We try to wake up before + * the next beacon so that we don't miss it. + */ +#define PS_WAKE_BEFORE_BEACON_MS (20) + +/* + * Minimum amount of time we we keep awake for. + */ +#define PS_MINUMUM_POWER_UP_PERIOD_MS (10) + +/* + * Minimum amount of time we will sleep. If we will end up sleeping + * for less than this, then don't go to sleep. + */ +#define PS_MINIMUM_SLEEP_TIME (10) + +/* + * Length of one tick of the transmit clean up timer in ms. This is + * the minimum amount of time we will sleep for. + */ +#define PS_TRANSMIT_TIMER_TICK_MS ((1000/HZ) ? (1000/HZ) : 1) + +/* + * Length of time we will wait past the expected arrival of a beacon before assuming + * that we missed it. + */ +#define PS_BEACON_TIMEOUT_MS (40) + + +/* + * Minimum beacon interval we will support for duty cycling. There is so much overhead + * in duty cycling that it doesn't make sense to do it for short beacon intervals. + */ +#define PS_MINIMUM_BEACON_INT (100) + +/* + * Amount of time we will pause duty cycling for after receiving an event that suggests + * wpa_supplicant is attempting to reassociate with an AP. + */ +#define PS_SCAN_DELAY (5000) + + +// Powersave register index +#define INDX_GEN_CONTROL 0 // General control +#define INDX_GEN_STATUS 1 // General status +#define INDX_RSSI_AES 2 // RSSI and AES status +#define INDX_INTR_MASK 3 // Interrupt mask +#define INDX_SPI_CTRL 4 // RF SPI control +#define INDX_CONF1 5 // Configuration 1 +#define INDX_CONF2 6 // Configuration 2 +#define INDX_AES_MODE 7 // ARS mode +#define INDX_OUT_CTRL 8 // Output control +#define INDX_MAC_CONTROL 9 // MAC control +#define INDX_STAID_1 10 // first part of stations ID +#define INDX_STAID_2 11 // 2nd part of station ID +#define INDX_BSSID_1 12 // 1st part of BSS ID +#define INDX_BSSID_2 13 // 2nd part of BSS ID +#define INDX_BRS_SSID 14 // BRS mask and SSID length +#define INDX_BACKOFF 15 // backoff +#define INDX_DTIM_LISTEN 16 // DTIM perido and listen interval +#define INDX_BEACON_INT 17 // beacon interval +#define INDX_MAC_CTL 18 // MAC control register +#define INDX_BEACON_MASK 19 // beacon mask and backoff +#define INDX_TOTAL 20 + +static u32 savedRegs[INDX_TOTAL]; // Used to save registers for sleep mode + + +/* + * TODO: Delete this. + */ +struct ps_stats { + unsigned int modeStart; + unsigned int cycleStart; + unsigned int receivedBeacons; + unsigned int missedBeacons; + unsigned int jiffiesOn; + unsigned int jiffiesOff; +} stats; + + + +static void ps_free_frame(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct piper_priv *piperp = hw->priv; + + if (skb) { + dev_kfree_skb(skb); + piperp->ps.frames_pending--; + } +} + + + + + +#define ACK_SIZE (14) /* length of ACK in bytes */ + +// Length (in usecs) of a MAC frame of bytes at rate (in 500kbps units) +// not including SIFS and PLCP preamble/header +#define CCK_DURATION(bytes, rate) ((16*(bytes)+(rate)-1)/(rate)) + +#define USE_SHORTPREAMBLE(is_1_Mbit, use_short_preamble) ((!is_1_Mbit) & use_short_preamble) + +// Length (in usecs) of SIFS and PLCP preamble/header. +#define PRE_LEN(is_1_Mbit, use_short_preamble) (USE_SHORTPREAMBLE(is_1_Mbit, use_short_preamble) ? 106 : 202) + +// Duration (in usecs) of an OFDM frame at rate (in 500kbps units) +// including SIFS and PLCP preamble/header +#define OFDM_DURATION(bytes, rate) (36 + 4*((4*(bytes)+(rate)+10)/(rate))) + +static unsigned int getRateIndex(struct piper_priv *piperp) +{ + unsigned int rates = piperp->ac->rd_reg(piperp, MAC_SSID_LEN); + unsigned int result = AIROHA_LOWEST_OFDM_RATE_INDEX; + + if (piperp->rf->getBand(piperp->channel) == IEEE80211_BAND_2GHZ) { + if ((rates & MAC_PSK_BRS_MASK) != 0) { + result = AIROHA_LOWEST_PSK_RATE_INDEX; + } + } + + return result; +} + +static int getAckDuration(struct piper_priv *piperp) +{ + bool is_1_Mbit = (getRateIndex(piperp) == AIROHA_LOWEST_PSK_RATE_INDEX); + int duration = 0; + + if (is_1_Mbit) { + duration = CCK_DURATION(ACK_SIZE, 1); + } else { + duration = OFDM_DURATION(ACK_SIZE, 6); + } + + duration += PRE_LEN(is_1_Mbit, piperp->use_short_preamble); + + return duration; +} + + +/* + * This function is used to notify the AP about the current state of + * power save. One of the bits in the 802.11 header field is set to + * indicate the status of power save. This bit is actually set appropriately + * for all frames sent, we just send a null data frame just to make + * sure something is sent to the AP in a reasonable amount of time. + */ +/* + * Possible values for is_power_management_on argument + */ +#define POWERING_DOWN (true) +#define POWERED_UP (false) +void piper_sendNullDataFrame(struct piper_priv *piperp, bool is_power_management_on) +{ + struct sk_buff *skb = NULL; + _80211HeaderType *header; + struct ieee80211_tx_info *tx_info; + + skb = + __dev_alloc_skb(sizeof(_80211HeaderType) + + piperp->hw->extra_tx_headroom, GFP_ATOMIC); + if (skb == NULL) + goto piper_sendNullDataFrame_Exit; + + tx_info = (struct ieee80211_tx_info *) skb->cb; + + skb_reserve(skb, piperp->hw->extra_tx_headroom); + header = (_80211HeaderType *) skb_put(skb, sizeof(_80211HeaderType)); + memset(header, 0, sizeof(*header)); + header->fc.type = TYPE_NULL_DATA; + header->fc.pwrMgt = is_power_management_on; + header->duration = getAckDuration(piperp); + memcpy(header->addr1, piperp->bssid, sizeof(header->addr1)); + memcpy(header->addr2, piperp->pdata->macaddr, sizeof(header->addr2)); + memcpy(header->addr3, piperp->bssid, sizeof(header->addr3)); + + tx_info->flags = IEEE80211_TX_CTL_ASSIGN_SEQ | IEEE80211_TX_CTL_FIRST_FRAGMENT; + tx_info->band = piperp->rf->getBand(piperp->channel); + tx_info->antenna_sel_tx = 1; /* actually this is ignored for now */ + tx_info->control.rates[0].idx = 0; + tx_info->control.rates[0].count = 2; /* no retries. Don't tie us up waiting for an ACK */ + tx_info->control.rates[0].flags = 0; + tx_info->control.rates[1].idx = -1; + tx_info->control.rts_cts_rate_idx = -1; + piperp->ps.frames_pending++; + + if (piper_hw_tx_private(piperp->hw, skb, ps_free_frame) != 0) { + /* printk(KERN_ERR + "piper_hw_tx() failed unexpectedly when sending null data frame.\n"); */ + ps_free_frame(piperp->hw, skb); + } + +piper_sendNullDataFrame_Exit: + return; +} + + + + +#define RESET_PIPER (1) /* must be set to 1 (0 case is only for debugging)*/ +#define PS_DONT_FORCE (false) /* set force to this value if we want to be safe */ +#define PS_FORCE_POWER_DOWN (true) /* set force to this value to shut down the H/W reguardless*/ +/* + * This routine shuts down Piper and the Airoha transceiver. First we check to + * make sure the driver and H/W are idle. Then we save the state of the H/W. + * Then we shut down the Airoha and place piper into reset. + */ +int piper_MacEnterSleepMode(struct piper_priv *piperp, bool force) +{ + /* + * Interrupts are already disabled when we get here. + */ + + if (piperp->ps.poweredDown) + return 0; + + savedRegs[INDX_INTR_MASK] = piperp->ac->rd_reg(piperp, BB_IRQ_MASK); + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, 0, op_write); + + if (!force) { + if ( (piperp->ps.rxTaskletRunning) + || ((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY)) + || ( (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) + & BB_GENERAL_CTL_TX_FIFO_EMPTY) == 0) + || (piperp->tx_tasklet_running) + || ( (piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) + & BB_GENERAL_STAT_RX_FIFO_EMPTY) == 0) + || (piperp->ac->rd_reg(piperp, BB_IRQ_STAT) & savedRegs[INDX_INTR_MASK])) { + + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, savedRegs[INDX_INTR_MASK], op_write); + return -1; + } + } + + disable_irq(piperp->irq); + + savedRegs[INDX_GEN_CONTROL] = piperp->ac->rd_reg(piperp, BB_GENERAL_CTL); + savedRegs[INDX_GEN_STATUS] = piperp->ac->rd_reg(piperp, BB_GENERAL_STAT); + savedRegs[INDX_RSSI_AES] = piperp->ac->rd_reg(piperp, BB_RSSI) & ~BB_RSSI_EAS_BUSY; + savedRegs[INDX_SPI_CTRL] = piperp->ac->rd_reg(piperp, BB_SPI_CTRL); + savedRegs[INDX_CONF1] = piperp->ac->rd_reg(piperp, BB_TRACK_CONTROL); + savedRegs[INDX_CONF2] = piperp->ac->rd_reg(piperp, BB_CONF_2); + savedRegs[INDX_OUT_CTRL] = piperp->ac->rd_reg(piperp, BB_OUTPUT_CONTROL); + savedRegs[INDX_MAC_CONTROL] = piperp->ac->rd_reg(piperp, MAC_CTL); + + savedRegs[INDX_STAID_1] = piperp->ac->rd_reg(piperp, MAC_STA_ID0); + savedRegs[INDX_STAID_2] = piperp->ac->rd_reg(piperp, MAC_STA_ID1); + savedRegs[INDX_BSSID_1] = piperp->ac->rd_reg(piperp, MAC_BSS_ID0); + savedRegs[INDX_BSSID_2] = piperp->ac->rd_reg(piperp, MAC_BSS_ID1); + savedRegs[INDX_BRS_SSID] = piperp->ac->rd_reg(piperp, MAC_SSID_LEN); + savedRegs[INDX_BACKOFF] = piperp->ac->rd_reg(piperp, MAC_BACKOFF); + savedRegs[INDX_DTIM_LISTEN] = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD); + savedRegs[INDX_BEACON_INT] = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM); + savedRegs[INDX_MAC_CTL] = piperp->ac->rd_reg(piperp, MAC_CTL); + savedRegs[INDX_BEACON_MASK] = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT); + + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RX_EN, op_and); //disable receiving + piperp->ac->wr_reg(piperp, MAC_CTL, 0, op_write); + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, 0, op_write); + +#if RESET_PIPER + // Power down the airoha transceiver + piperp->rf->power_on(piperp->hw, false); + udelay(10); + // hold the transceiver in reset mode + if (piperp->pdata->reset) + piperp->pdata->reset(piperp, 1); +#endif + stats.jiffiesOn += jiffies - stats.cycleStart; + stats.cycleStart = jiffies; + piperp->ps.poweredDown = true; + + return 0; +} + + +#define PS_NO_SPIKE_SUPPRESSION (false) /* want_spike_suppression = don't want spike suppression*/ +#define PS_WANT_SPIKE_SUPPRESSION (true) /* want_spike_suppression = do spike suppression*/ +/* + * Turn the H/W back on after it was shutdown with piper_MacEnterSleepMode. + * + * 1. Power up the hardware. + * 2. Perform the spike suppression routine if desired. The spike suppression + * routine resyncs the clocks in Piper in order to prevent us from generating + * noise spikes at 1 and 2 MHz. Unfortunately it takes an indeterminate amount + * of time, so we normally don't do it and just make sure we do not send at + * at those data rates while duty cycling. + * 3. Set the channel. The Airoha was shut off so we have to set the channel + * again. + * 4. Restore the state of piper registers. + * 5. Zero out and reset the transmitter FIFO. Mike Schaffner claims this should + * not be necessary, but we seem to run into trouble when we don't do it. + * 6. Restore the interrupts. + */ +void piper_MacEnterActiveMode(struct piper_priv *piperp, bool want_spike_suppression) +{ + int i; +// #define WANT_DEBUG +#ifdef WANT_DEBUG + static unsigned int run = 0; +#endif + +#if RESET_PIPER + + if (piperp->pdata->reset) { +#ifdef WANT_DEBUG + if (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_TX_FIFO_FULL) { + printk(KERN_ERR "**** While in reset, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } + if (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + printk(KERN_ERR "**** While in reset AES busy set, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } +#endif + piperp->pdata->reset(piperp, 0); + udelay(10); + +#ifdef WANT_DEBUG + if (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_TX_FIFO_FULL) { + printk(KERN_ERR "**** After reset, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } + if (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + printk(KERN_ERR "**** After reset AES busy set, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } + run++; +#endif + piperp->rf->power_on(piperp->hw, true); + mdelay(1); + piper_spike_suppression(piperp, want_spike_suppression); + } +#endif + + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, 0xff, op_write); + + +#if RESET_PIPER + piperp->rf->set_chan_no_rx(piperp->hw, piperp->channel); +#endif + + // store the registers back + + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, 0x30000000, op_write); + piperp->ac->wr_reg(piperp, BB_RSSI, savedRegs[INDX_RSSI_AES] & ~BB_RSSI_EAS_BUSY, op_write); + +// piperp->ac->wr_reg(piperp, BB_IRQ_MASK, savedRegs[INDX_INTR_MASK], op_write); + piperp->ac->wr_reg(piperp, BB_SPI_CTRL, savedRegs[INDX_SPI_CTRL], op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, savedRegs[INDX_CONF1], op_write); + piperp->ac->wr_reg(piperp, BB_CONF_2, savedRegs[INDX_CONF2], op_write); + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, savedRegs[INDX_OUT_CTRL], op_write); + piperp->ac->wr_reg(piperp, MAC_CTL, savedRegs[INDX_MAC_CONTROL], op_write); + + piperp->ac->wr_reg(piperp, MAC_STA_ID0, savedRegs[INDX_STAID_1], op_write); + piperp->ac->wr_reg(piperp, MAC_STA_ID1, savedRegs[INDX_STAID_2], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, savedRegs[INDX_BSSID_1], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, savedRegs[INDX_BSSID_2], op_write); + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, savedRegs[INDX_BRS_SSID], op_write); + piperp->ac->wr_reg(piperp, MAC_BACKOFF, savedRegs[INDX_BACKOFF], op_write); + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, savedRegs[INDX_DTIM_LISTEN], op_write); + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, savedRegs[INDX_BEACON_INT], op_write); + piperp->ac->wr_reg(piperp, MAC_CTL, savedRegs[INDX_MAC_CTL], op_write); + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, savedRegs[INDX_BEACON_MASK], op_write); + +//**** + // set bit-11 in the general control register to a 1 to start the processors + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_MAC_ASSIST_ENABLE, op_or); + + // set the TX-hold bit + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720080, op_write); + + // clear the TX-FIFO memory + for (i = 0; i < 448; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, 0, op_write); + + // clear RX-FIFO memory + for (i = 0; i < 512; i++) + piperp->ac->rd_reg(piperp, BB_DATA_FIFO); + + // reset the TX-FIFO and RX-FIFO + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x377200E0, op_write); + + + // release the TX-hold and reset + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720000, op_write); + + +//*** + /* + * Reset the interrupt mask. We could have been receiving a frame when we + * powered down. This could cause us to store the wrong mask, so we want + * to make sure we enabe the RX interrupts. However, we should not have the + * TX interrupts enabled when we come out of power save mode. + */ + udelay(50); + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, 0xff, op_write); + piperp->clear_irq_mask_bit(piperp, 0xffffffff); + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_RX_OVERRUN | BB_IRQ_MASK_RX_FIFO); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + (savedRegs[INDX_GEN_CONTROL] | 0x37000000 | + BB_GENERAL_CTL_RX_EN), op_write); + + stats.jiffiesOff += jiffies - stats.cycleStart; + stats.cycleStart = jiffies; + + /* TODO, this is a temporary workaround and should be better analyzed in future. + * The problem is that the general power save code is not synchronized with the + * dynamic PW and this is causing that, the following line, unbalances the + * piper wireless irq */ + if (piperp->ps.poweredDown != false) + enable_irq(piperp->irq); + + piperp->ps.poweredDown = false; +} + + + + +/* + * So what crazy thing are we doing here? Well, Piper has a bug where it + * can send noise spikes at 1 Mbps and 2 Mbps if it is powered up without + * running a special spike suppression routine. The spike suppression code + * takes an average of 30 ms, and I have timed it taking as long as 300 ms. + * This is not something you want to use for duty cycling. The solution is + * to avoid sending at those two rates. After the transmit routine determines + * the rate mac80211 specified, it will call us and we will decide whether + * we like that rate. If it is one of our bad rates, then we will bump it + * up to a good rate. + */ +struct ieee80211_rate *piper_ps_check_rate(struct piper_priv *piperp, + struct ieee80211_rate *rate) +{ +#define BAD_RATE_1MBPS (10) +#define BAD_RATE_2MBPS (20) + if ((piperp->ps.mode == PS_MODE_LOW_POWER) && (rate != NULL)) { + if ((rate->bitrate == BAD_RATE_1MBPS) + || (rate->bitrate == BAD_RATE_2MBPS)) { + rate = + (struct ieee80211_rate *) piperp->rf-> + getRate(AIROHA_55_MBPS_RATE_INDEX); + } + } + + return rate; +} + +EXPORT_SYMBOL_GPL(piper_ps_check_rate); + + + +/* + * This routine restarts the transmitter after powering back + * up, or failing to power down. + * + * 1) Clear the power management bit so that frames will be + * sent indicating that we are poweredup. + * 2) Set the flag to allow transmits again. + * 3) If we stopped the mac80211 transmitter queue, then start + * it back up again. + * 4) Notify the AP that we are awake by sending a null-data frame + * with the power management bit clear. + */ +static void ps_resume_transmits(struct piper_priv *piperp) +{ + piperp->ps.power_management = POWERED_UP; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + piper_sendNullDataFrame(piperp, POWERED_UP); + ieee80211_wake_queues(piperp->hw); +} + + + + + + +/* + * This routine sets an event timer. The ps_state_machine routine + * will be executed when the event timer expires. + */ +static void ps_set_timer_event(struct piper_priv *piperp, + enum piper_ps_event next_event, + unsigned int timeout_ms) +{ + unsigned int delay_in_jiffies = MILLS_TO_JIFFIES(timeout_ms); + + if (delay_in_jiffies == 0) + delay_in_jiffies++; + + del_timer_sync(&piperp->ps.timer); + piperp->ps.this_event = next_event; + piperp->ps.timer.expires = delay_in_jiffies + jiffies; + add_timer(&piperp->ps.timer); +} + + +/* + * This routine cancels an event timer set by ps_set_timer_event. + */ +static void ps_cancel_timer_event(struct piper_priv *piperp) +{ + del_timer_sync(&piperp->ps.timer); +} + + +#define WANT_STATE_MACHINE_DEBUG (0) +#if WANT_STATE_MACHINE_DEBUG + +struct event_record { + enum piper_ps_event event; + enum piper_ps_state state; +}; + +#define MAX_EVENTS_RECORDED (15) +static unsigned int debug_events_index = 0; +static struct event_record debug_events[MAX_EVENTS_RECORDED]; + +void debug_track_event(enum piper_ps_event event, enum piper_ps_state state) +{ + debug_events[debug_events_index].event = event; + debug_events[debug_events_index].state = state; + if (debug_events_index == MAX_EVENTS_RECORDED) { + unsigned int i; + + printk(KERN_ERR); + for (i = 0; i < MAX_EVENTS_RECORDED; i++) { + printk("(%d,%d)", debug_events[i].event, + debug_events[i].state); + } + printk("\n"); + debug_events_index = 0; + } else { + debug_events_index++; + } +} + + +#else + #define debug_track_event(event, state) +#endif + +/* + * This is the entry point into the duty cycle state machine. It may be called + * by: + * + * 1) piper_ps_handle_beacon when a beacon frame is received. + * 2) the receiver task when we receive the ACK for the last pending frame + * when we are waiting to power down. + * 3) by ps_timer for many timer events. + */ +static void ps_state_machine(struct piper_priv *piperp, enum piper_ps_event event) +{ + unsigned long flags; + + debug_track_event(event, piperp->ps.state); + + spin_lock_irqsave(&piperp->ps.lock, flags); + + + switch (event) { + case PS_EVENT_BEACON_RECEIVED: + /* + * We just received a beacon. This is really the driving event in this state + * machine. Everything else is synchronized from it. + * + * We know we are powered up because we just received the beacon. The normal + * control path is to set a timer which will expire when we need to start + * preparations for powering down again. + */ + ps_cancel_timer_event(piperp); /* cancel missed beacon timer*/ + stats.receivedBeacons++; + if ( (!piperp->areWeAssociated) + || (piperp->ps.beacon_int < PS_MINIMUM_BEACON_INT) + || (piperp->ps.scan_timer != 0)) { + /* + * Don't duty cyle while we are trying to associate. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + if (piperp->ps.scan_timer) { + piperp->ps.scan_timer--; + } + break; + } + if (piperp->ps.state == PS_STATE_WAIT_FOR_BEACON) { + int timeout; + /* + * Calculate amount of time to sleep. + */ + piperp->ps.sleep_time = (piperp->ps.beacon_int * (100 - piperp->power_duty)) / 100; + + /* + * Now figure out how long we have before it's time to go to sleep. We + * have to wake up at least PS_WAKE_BEFORE_BEACON_MS before we expect to + * receive the next beacon, and we need to start powering down at least + * PS_TRANSMITTER_SHUTDOWN_MS ahead of time. + */ + timeout = piperp->ps.beacon_int - (piperp->ps.sleep_time + PS_TRANSMITTER_SHUTDOWN_MS + PS_WAKE_BEFORE_BEACON_MS); + if ((timeout > 0) && (piperp->ps.sleep_time > PS_MINIMUM_SLEEP_TIME)) { + /* + * If there is enough time left that it makes sense to duty + * cycle, then program the timer and advance to the next state. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT; + ps_set_timer_event(piperp, PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED, timeout); + + break; + } + } else { + if ( (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE) + || (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT)) { + ps_resume_transmits(piperp); + } + digi_dbg("*** Beacon event in state %d.\n", piperp->ps.state); + } + /* + * We will come here if we were in the wrong state when we received the + * beacon, or if the duty cycle is too short. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.beacon_int + PS_BEACON_TIMEOUT_MS); + break; + case PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED: + /* + * This event is hit when it's time to start powering down. Unfortunately, this is + * not a simple thing to do. The first things to do are: + * + * 1. Set the power save on flag. This will cause any transmit frame to be + * sent with the power management bit set. + * 2. Stop the mac80211 layer from sending us anymore frames. + * 3. Signal the AP that we are powering down by sending a null-data frame with the + * power management bit set. + */ + if (piperp->ps.state == PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT) { + if (piperp->ps.scan_timer) { + /* + * Don't shut down if we are scanning. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + break; + } + piperp->ps.power_management = POWERING_DOWN; + piperp->ps.allowTransmits = false; + ieee80211_stop_queues(piperp->hw); + piperp->ps.stopped_tx_queues = true; + piper_sendNullDataFrame(piperp, POWERING_DOWN); + piperp->ps.state = PS_STATE_WAIT_FOR_TRANSMITTER_DONE; + ps_set_timer_event(piperp, PS_EVENT_TRANSMITTER_DONE_TIMER_TICK, + PS_TRANSMIT_TIMER_TICK_MS); + } else { + /* + * This should never happen (famous last words) + */ + digi_dbg("** stop tx event, state = %d.\n", piperp->ps.state); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.beacon_int + PS_BEACON_TIMEOUT_MS); + } + break; + case PS_EVENT_TRANSMITTER_DONE: + /* + * This event is triggered when the receive task finishes processing the ACK + * from the null-data frame sent by the PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED event. + * We try to power down now. + */ + if (piperp->ps.scan_timer == 0) { + if ( (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE) + || (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT)) { + ps_cancel_timer_event(piperp); /* cancel transmitter done timeout timer*/ + if (piper_MacEnterSleepMode(piperp, PS_DONT_FORCE) == 0) { + piperp->ps.state = PS_STATE_WAIT_FOR_WAKEUP_ALARM; + /* + * Note that the value PS_EVENT_TRANSMITTER_DONE_TIMER_TICK is + * updated as necessary by the PS_EVENT_TRANSMITTER_DONE_TIMER_TICK + * event to take into account the amount of time it took for the + * transmitter to finish sending the last frame. + */ + ps_set_timer_event(piperp, PS_EVENT_WAKEUP, piperp->ps.sleep_time); + break; + } + } else { +#ifdef WANT_DEBUG + printk(KERN_ERR "couldn't sleep, rxt=%d, AES busy = %d, txfifo=%d, txt=%d, rxfifo=%d\n", + (piperp->ps.rxTaskletRunning),((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) != 0), + ( (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL)& BB_GENERAL_CTL_TX_FIFO_EMPTY) == 0), + (piperp->tx_tasklet_running), + ( (piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) + & BB_GENERAL_STAT_RX_FIFO_EMPTY) == 0)); +#endif + digi_dbg("** PS_EVENT_TRANSMITTER_DONE event, but state == %d.\n", piperp->ps.state); + } + } + /* + * If we fall through to here, then either we are in the wrong state, or we were + * not able to power down the H/W. + */ + ps_resume_transmits(piperp); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.beacon_int + PS_BEACON_TIMEOUT_MS); + break; + case PS_EVENT_TRANSMITTER_DONE_TIMER_TICK: + /* + * This event is triggered periodically while we are waiting for the + * transmitter to finish sending that last packet. We decrement + * piperp->ps.sleep_time (which is used by the PS_EVENT_TRANSMITTER_DONE + * event). If piperp->ps.sleep_time is still larger than our minimum + * required sleep time, then we just restart the timer. + */ + if (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE) { + piperp->ps.sleep_time -= PS_TRANSMIT_TIMER_TICK_MS; + if (piperp->ps.sleep_time >= PS_MINIMUM_SLEEP_TIME) { + piperp->ps.state = PS_STATE_WAIT_FOR_TRANSMITTER_DONE; + ps_set_timer_event(piperp, PS_EVENT_TRANSMITTER_DONE_TIMER_TICK, + PS_TRANSMIT_TIMER_TICK_MS); + } else { + /* + * Transmitter did not shut down in time. Resume normal operations + * and stay awake until the next beacon. + */ + ps_resume_transmits(piperp); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.sleep_time + PS_WAKE_BEFORE_BEACON_MS + + PS_BEACON_TIMEOUT_MS); + } + } else if (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT) { + piperp->ps.sleep_time -= PS_TRANSMIT_TIMER_TICK_MS; + /* + * The piper_ps_rx_task_exiting routine sets this state just before it + * releases the lock on ps.state and calls this event. If we ever + * come here, then the timer tick occurred just between the time + * piper_ps_rx_task_exiting released the lock and ps_state_machine + * reset it. Since the tx done event is in progress, we should ignore + * this tick. + */ + break; + } else { + digi_dbg("** done tick in state %d.\n", piperp->ps.state); + } + break; + case PS_EVENT_WAKEUP: + /* + * This event is called when we have powered down and it is time + * to power back up again. + * + * 1) Power up the H/W. + * 2) Resume normal operations + * 3) Update our state. + * 4) Set a timeout for receiving the next beacom frame. + */ + if (piperp->ps.state == PS_STATE_WAIT_FOR_WAKEUP_ALARM) { + piper_MacEnterActiveMode(piperp, PS_NO_SPIKE_SUPPRESSION); + ps_resume_transmits(piperp); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + PS_BEACON_TIMEOUT_MS + PS_WAKE_BEFORE_BEACON_MS); + } else { + digi_dbg("** wake event in state %d.\n", piperp->ps.state); + } + break; + case PS_EVENT_MISSED_BEACON: + /* + * This event is called when we miss a beacon. For now just update + * our statistics. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + if ((piperp->areWeAssociated) && (piperp->ps.scan_timer == 0)) { + stats.missedBeacons++; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, piperp->ps.beacon_int); + } + break; + default: + digi_dbg("**** ps_state_machine received unknown event %d.\n", event); + break; + } + spin_unlock_irqrestore(&piperp->ps.lock, flags); + +} + +/* + * This routine is called by the receiver task when it exits. We use it to generate + * the PS_EVENT_TRANSMITTER_DONE event. The event signifies that the transmitter has + * sent our null-data frame and is idle. We determine this by checking frames_pending, + * while will be nonzero if a null-data frame is waiting to be sent, and the machine's + * state. + */ +void piper_ps_rx_task_exiting(struct piper_priv *piperp) +{ + unsigned long flags; + + spin_lock_irqsave(&piperp->ps.lock, flags); + + if ( (piperp->ps.frames_pending == 0) + && (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE)) { + /* + * We have a race condition between the transmitter done tick and + * this routine. So this routine changes the state to + * PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT before it releases the + * lock so that we don't get confused. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT; + spin_unlock_irqrestore(&piperp->ps.lock, flags); + ps_state_machine(piperp, PS_EVENT_TRANSMITTER_DONE); + } else { + spin_unlock_irqrestore(&piperp->ps.lock, flags); + } +} + +/* + * Called when the event timer expires. Call the state machine to handle + * the event. + */ +static void ps_timer(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *) context; + + ps_state_machine(piperp, piperp->ps.this_event); +} + + + +/* + * This routine is called when we receive a beacon. We extract the beacon interval + * in case it has changed and then call the state machine. + */ +static void piper_ps_handle_beacon(struct piper_priv *piperp, struct sk_buff *skb) +{ +#define BEACON_INT_LSB (8) +#define BEACON_INT_MSB (9) + u32 beacon_int; + _80211HeaderType *header = (_80211HeaderType *) skb->data; + bool fromOurAp = piperp->areWeAssociated + && (memcmp(piperp->bssid, header->addr3, sizeof (header->addr3)) == 0); + + /* + * mac80211 does not inform us when the beacon interval changes, so we have + * to read this information from the beacon ourselves. + */ + + if (fromOurAp) { + beacon_int = skb->data[sizeof(_80211HeaderType) + BEACON_INT_LSB]; + beacon_int |= (skb->data[sizeof(_80211HeaderType) + BEACON_INT_MSB] << 8); + piperp->ps.beacon_int = beacon_int; + + if (piperp->ps.mode == PS_MODE_LOW_POWER) { + ps_state_machine(piperp, PS_EVENT_BEACON_RECEIVED); + } + } +} + + + +/* + * This routine is called when mac80211 starts doing things that might indicate it + * is attempting to scan or reassociate. Things like changing the channel or + * disassociating. When we receive an event like that, we stop duty cycling for + * a while since it may interfere with attempts to reassociate with an access point. + */ +void piper_ps_scan_event(struct piper_priv *piperp) +{ + (void) piperp; +#if 0 + /* + * It appears that pausing duty cycling during association events may actually + * worsen performance I suspect that either the AP or mac80211 is measuring + * our throughput and adjusting the load accordingly, and that momentary changes + * in performance caused by pausing duty cyling interfere with this. + * + * TODO: Consider removing this code. I left it in for now in case we decide + * to try it again, but if we're not going to use it, it just makes the + * driver more confusing and should be removed. + */ + if (piperp->ps.beacon_int != 0) { + piperp->ps.scan_timer = PS_SCAN_DELAY / piperp->ps.beacon_int; + } else { + piperp->ps.scan_timer = PS_SCAN_DELAY / 100; + } +#endif +} + + + +/* + * This routine is called so we can process incoming frames. We do the + * handshaking to receive buffered frames in PS mode here. + */ +void piper_ps_process_receive_frame(struct piper_priv *piperp, struct sk_buff *skb) +{ + _80211HeaderType *header = (_80211HeaderType *) skb->data; + + if (header->fc.type == TYPE_BEACON) { + piper_ps_handle_beacon(piperp, skb); + } else if ( (header->fc.type == TYPE_ASSOC_RESP) + || (header->fc.type == TYPE_REASSOC_RESP) + || (header->fc.type == TYPE_PROBE_RESP) + || (header->fc.type == TYPE_DISASSOC) + || (header->fc.type == TYPE_DEAUTH) + || (header->fc.type == TYPE_ACTION)) { + piper_ps_scan_event(piperp); + } +} + +EXPORT_SYMBOL_GPL(piper_ps_process_receive_frame); + + + +/* + * This function turns power save mode on or off. + */ +void piper_ps_set(struct piper_priv *piperp, bool powerSaveOn) +{ +#define MAX_SHUTDOWN_TIMEOUT (100) + unsigned long flags; + + spin_lock_irqsave(&piperp->ps.lock, flags); + + piper_ps_scan_event(piperp); + if (powerSaveOn) { + if (piperp->ps.beacon_int >= PS_MINIMUM_BEACON_INT) { + if (piperp->ps.mode != PS_MODE_LOW_POWER) { + piperp->ps.aid = 0; + piperp->ps.mode = PS_MODE_LOW_POWER; + piperp->ps.state= PS_STATE_WAIT_FOR_BEACON; + piperp->ps.power_management = POWERED_UP; + piperp->ps.poweredDown = false; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + stats.receivedBeacons = 0; + stats.missedBeacons = 0; + stats.modeStart = jiffies; + stats.cycleStart = jiffies; + stats.jiffiesOff = 0; + stats.jiffiesOn = 0; + piper_sendNullDataFrame(piperp, POWERED_UP); + /* + * Will start it the next time we receive a beacon. + */ + } + } else { + printk(KERN_ERR + "\nUnable to set power save mode because the beacon \n" + "interval set on this access point less than 100ms.\n"); + } + } else { + ps_cancel_timer_event(piperp); + if (piperp->ps.mode == PS_MODE_LOW_POWER) { + piperp->ps.mode = PS_MODE_FULL_POWER; /* stop duty cycle timer */ + if (piperp->ps.poweredDown) { + /* + * If we were powered down, then power up and do the spike suppression. + */ + piper_MacEnterActiveMode(piperp, PS_WANT_SPIKE_SUPPRESSION); + } else { + unsigned int timeout = 50; + int result; + /* + * If we branch here, then we were already powered up. You would think + * that we would be all set, but it's not that easy. Piper has a bug in + * it where we have to run a special spike suppression routine when we + * power it up. However, this routine takes an average of 30 ms to run, + * and I've see it take as long as 300 ms. This is not acceptable when + * we are duty cycling every 100 ms. To get around this, we do NOT do + * the spike suppression while duty cycling. Instead, we simply avoid + * transmitting at those rates which would cause spikes. Now, however, + * we are ending duty cycling and returning to normal operations so we + * have to do the spike suppression. Since we are powered up, the first + * thing to do is to power down. + */ + if (piperp->ps.state != PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT) { + /* + * If we come here, then we did not happen to be trying to power down + * just as we got the command from mac80211, so we have to start + * the procedure. This is the normal case. + * + * 1. Set the power save on flag. This will cause frames to be + * transmitted with the power management bit on. The reason + * for doing that is to tell the AP to stop sending us frames. + * 2. Stop the mac80211 layer from sending us more frames by stopping + * the transmit queues. + * 3. Send a null-data frame to the AP with the power management bit + * set. This should cause it to stop sending us frames. + */ + piperp->ps.power_management = POWERING_DOWN; + piperp->ps.allowTransmits = false; + ieee80211_stop_queues(piperp->hw); + piperp->ps.stopped_tx_queues = true; + piper_sendNullDataFrame(piperp, POWERING_DOWN); + } + /* + * Now wait for that last frame to go and and then shut down. + */ + result = -1; + for (timeout = 0; (timeout < MAX_SHUTDOWN_TIMEOUT) && (result != 0); timeout++) { + spin_unlock_irqrestore(&piperp->ps.lock, flags); + mdelay(10); + spin_lock_irqsave(&piperp->ps.lock, flags); + result = piper_MacEnterSleepMode(piperp, PS_DONT_FORCE); + } + if (result != 0) { + /* + * This is bad. For some reason we are not able to power down. We + * will try to force it now, but this may end up putting the driver + * or H/W into a bad state. However, we can't sit in the loop above + * forever either. + */ +#ifdef WANT_DEBUG + printk(KERN_ERR "Forcing Piper to power down\n"); + printk(KERN_ERR "BB_RSSI_EAS_BUSY = %d\n", piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY); + printk(KERN_ERR "BB_GENERAL_CTL_TX_FIFO_EMPTY = %d\n", + piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_TX_FIFO_EMPTY); + printk(KERN_ERR "BB_GENERAL_STAT_RX_FIFO_EMPTY = %d\n", + piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) & BB_GENERAL_STAT_RX_FIFO_EMPTY); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); +#endif + piper_MacEnterSleepMode(piperp, PS_FORCE_POWER_DOWN); + } + /* + * Wait a moment and then power the H/W back up and execute the spike suppression + * routine. + */ + spin_unlock_irqrestore(&piperp->ps.lock, flags); + mdelay(30); + spin_lock_irqsave(&piperp->ps.lock, flags); + piper_MacEnterActiveMode(piperp, PS_WANT_SPIKE_SUPPRESSION); + ps_resume_transmits(piperp); + } + stats.jiffiesOn += jiffies - stats.cycleStart; +#define WANT_STATS (0) +#if WANT_STATS + if ((piperp->ps.beacon_int != 0) + && ((jiffies - stats.modeStart) != 0)) { + printk(KERN_ERR + "jiffiesOff = %u, jiffiesOn = %u, total time = %lu\n", + stats.jiffiesOff, stats.jiffiesOn, + (jiffies - stats.modeStart)); + printk(KERN_ERR + "Powered down %ld percent of the time.\n", + (stats.jiffiesOff * 100) / (jiffies - stats.modeStart)); + printk(KERN_ERR + "Received %u of %lu beacons while in powersave mode.\n", + stats.receivedBeacons, + (jiffies - + stats.modeStart) / + MILLS_TO_JIFFIES(piperp->ps.beacon_int)); + printk(KERN_ERR "received %d beacons, missed %d\n", + stats.receivedBeacons, stats.missedBeacons); + printk(KERN_ERR "allowTransmits = %d, stopped_tx_queues = %d, q_count = %d\n", + piperp->ps.allowTransmits, piperp->ps.stopped_tx_queues, + piperp->tx_queue_count); + if ((stats.receivedBeacons + stats.missedBeacons) != 0) + printk(KERN_ERR "%d%% beacons were missed\n", + (100 * stats.missedBeacons) / (stats.receivedBeacons + stats.missedBeacons)); + } +#endif + } + piperp->ps.aid = 0; + piperp->ps.state= PS_STATE_WAIT_FOR_BEACON; + piperp->ps.power_management = POWERED_UP; + piperp->ps.poweredDown = false; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + ps_resume_transmits(piperp); + piper_sendNullDataFrame(piperp, POWERED_UP); + } + + spin_unlock_irqrestore(&piperp->ps.lock, flags); +} + +EXPORT_SYMBOL_GPL(piper_ps_set); + + + +/* + * Called when driver is loaded. Initialize our context. + */ +void piper_ps_init(struct piper_priv *piperp) +{ + memset(&piperp->ps, 0, sizeof(piperp->ps)); + piperp->ps.beacon_int = 100; + piperp->ps.aid = 0; + init_timer(&piperp->ps.timer); + piperp->ps.timer.function = ps_timer; + piperp->ps.timer.data = (unsigned long) piperp; + piperp->ps.mode = PS_MODE_FULL_POWER; + piperp->ps.state= PS_STATE_WAIT_FOR_BEACON; + spin_lock_init(&piperp->ps.lock); + piperp->ps.power_management = POWERED_UP; + piperp->ps.poweredDown = false; + piperp->ps.rxTaskletRunning; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + piperp->ps.frames_pending = 0; +} + +EXPORT_SYMBOL_GPL(piper_ps_init); + + +/* + * Called when driver is unloaded. Make sure the PS + * timer is shut down. + */ +void piper_ps_deinit(struct piper_priv *piperp) +{ + piper_ps_set(piperp, true); + piperp->ps.mode = PS_MODE_FULL_POWER; + del_timer_sync(&piperp->ps.timer); +} + +EXPORT_SYMBOL_GPL(piper_ps_deinit); diff --git a/drivers/net/wireless/digiPiper/digiPs.h b/drivers/net/wireless/digiPiper/digiPs.h new file mode 100644 index 000000000000..1feedd87e228 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiPs.h @@ -0,0 +1,50 @@ +/* + * Header file for digiPs.c. Declares functions used for power save mode. + */ + +#ifndef digiPs_h + +#define digiPS_h + +#include <net/mac80211.h> +#include "pipermain.h" + +enum piper_ps_event { + PS_EVENT_BEACON_RECEIVED, + PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED, + PS_EVENT_TRANSMITTER_DONE, + PS_EVENT_TRANSMITTER_DONE_TIMER_TICK, + PS_EVENT_WAKEUP, + PS_EVENT_MISSED_BEACON +}; + +enum piper_ps_tx_completion_result { + PS_RETURN_SKB_TO_MAC80211, + PS_DONT_RETURN_SKB_TO_MAC80211 +}; + +enum piper_ps_active_result { + PS_CONTINUE_TRANSMIT, + PS_STOP_TRANSMIT +}; + + +/* + * Current version of mac80211 doesn't set power management bit in frame headers, + * so I guess we have to for now. + * + * TODO: See if we still have to do this in the next drop. + */ +#define piper_ps_set_header_flag(piperp, header) \ + header->fc.pwrMgt = (piperp->ps.power_management) + +int piper_ps_active(struct piper_priv *piperp); +void piper_ps_process_receive_frame(struct piper_priv *piperp, + struct sk_buff *skb); +void piper_ps_init(struct piper_priv *piperp); +void piper_ps_deinit(struct piper_priv *piperp); +void piper_ps_set(struct piper_priv *piperp, bool powerSaveOn); +struct ieee80211_rate *piper_ps_check_rate(struct piper_priv *piperp, + struct ieee80211_rate *rate); + +#endif diff --git a/drivers/net/wireless/digiPiper/digiRx.c b/drivers/net/wireless/digiPiper/digiRx.c new file mode 100644 index 000000000000..01ff486311b1 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiRx.c @@ -0,0 +1,412 @@ +/* + * digiRx.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 file contains the routines that are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define WANT_RECEIVE_COUNT_SCROLL (0) +#define AES_TIMEOUT (200) +#define RX_DEBUG (1) + +#if RX_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + + +/* + * This routine is called to flush the receive and transmit FIFOs. It is used + * for error recovery when we detect corrupted data in the FIFO's. It should be + * called with the AES lock set. + */ +static void reset_fifo(struct piper_priv *piperp) +{ + unsigned int i; + + piperp->ac->rd_reg(piperp, BB_AES_CTL); + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + // clear the TX-FIFO memory + for (i = 0; i < 448; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, 0, op_write); + + // clear RX-FIFO memory + for (i = 0; i < 512; i++) + piperp->ac->rd_reg(piperp, BB_DATA_FIFO); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_RXFIFORST, op_or); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RXFIFORST, op_and); +} + +/* + * This routine is called to receive a frame. The hardware header has + * already been read from the FIFO. We need to read out the frame. If + * the frame was encrypted with AES and we have the correct key, then + * we use the AES H/W encryption engine to decrypt the frame. We also + * set up a ieee80211_rx_status structure with the appropriate info. + * + * Arguments + * digi context information + * skb empty buffer to receive packet into + * length number of bytes in FIFO + * fr_ctrl_field buffer to copy frame control header into + * status status structure we must write status into + * + * Returns + * true frame was received + * false an encryption error was detected + */ +static bool receive_packet(struct piper_priv *piperp, struct sk_buff *skb, int length, + frameControlFieldType_t * fr_ctrl_field, + struct ieee80211_rx_status *status) +{ + _80211HeaderType *header; + bool result = true; + int originalLength = length; + int headerlen; +#if WANT_RECEIVE_COUNT_SCROLL + static int packetCount = 0; +#endif + + headerlen = _80211_HEADER_LENGTH; + if (length < _80211_HEADER_LENGTH) { + /* + * If we branch here, then there is not enough data to make a + * complete header. This is possible if this is a control frame. + * Adjust our length so that we do not read too much data from + * the FIFO. + */ + headerlen = length; + } + + /* + * Read the frame header. This includes the frame control fields + * as well as the 802.11 header. + */ + header = (_80211HeaderType *) skb_put(skb, headerlen); + length -= headerlen; + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, (uint8_t *) header, headerlen); + memcpy(fr_ctrl_field, &header->fc, sizeof(fr_ctrl_field)); + + if (((u32)(skb->tail)) & 0x3) { + /* align data */ + skb_reserve(skb, 4 - ((u32)(skb->tail) & 0x3)); + } + + if (header->fc.protected) { + /* + * If we branch here, then the frame is encrypted. We need + * to figure out if we should try to decrypt it. + */ + unsigned char *rsnHeader; + unsigned int aesDataBlob[AES_BLOB_LENGTH / sizeof(unsigned int)]; + unsigned int keyIndex; + + rsnHeader = skb_put(skb, PIPER_EXTIV_SIZE); + + /* + * Step 1: Read the rest of the unencrypted data, which should + * consist of the extiv fields. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, rsnHeader, PIPER_EXTIV_SIZE); + length -= PIPER_EXTIV_SIZE; + keyIndex = rsnHeader[3] >> 6; + + if (piper_prepare_aes_datablob(piperp, keyIndex, (u8 *) aesDataBlob, + (unsigned char *)header, originalLength - 12, + false)) { + /* + * If we come here, then we have the correct encryption key for + * the frame and will now try to decrypt it. + */ + unsigned int timeout = AES_TIMEOUT; + unsigned long flags; + + spin_lock_irqsave(&piperp->aesLock, flags); + + /* + * Step 2: Wait for AES to become ready. + */ + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + /* + * If we come here, then AES busy appears to be stuck high. It should only be + * high for a maximum of about 80 us when it is encrypting a transmit frame. + * Our timeout value is high enough to guarantee that the engine has had enough + * time to complete the transmit. Apparently there is data stuck in the FIFO + * from either a previous transmit or receive. + */ + dprintk(DWARNING, "1st AES busy never became ready\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + /* + * Recover by flushing the FIFO and returning in error. + */ + reset_fifo(piperp); +#if 0 + /* + * TODO: Figure out why this code snippet doesn't work. I would think + * that if we reset the fifo, we should just return in error since we will + * have discarded the frame. However, when we do that the system hangs + * (after a while). This doesn't make sense. + */ + spin_unlock_irqrestore(&piperp->aesLock, flags); + result = false; + goto receive_packet_exit; +#else + break; +#endif + } + udelay(1); + } + + /* + * Step 3: Set the AES mode, and then read from the AES control + * register to put the AES engine into receive mode. + */ + piperp->ac->rd_reg(piperp, BB_AES_CTL); + + /* + * Step 4: Write the expanded AES key into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + (unsigned char *)piperp->key[keyIndex].expandedKey, + EXPANDED_KEY_LENGTH); + + /* + * Step 5: Write the AES IV and headers into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, (unsigned char *)aesDataBlob, + AES_BLOB_LENGTH); + + /* + * Step 6: Now, finally, read the unencrypted frame from the + * AES FIFO. Adjust the length so that we don't try + * to read the MIC or the ICV which the AES engine will + * process for us. + */ + length -= MIC_SIZE + ICV_SIZE; + piperp->ac->rd_fifo(piperp, BB_AES_FIFO, skb_put(skb, length), length); + /* + * mac80211 seems to expect there to be a MIC even if the packet + * has already been decrypted. It will drop off what it thinks + * are the extra MIC bytes, so add some extra bytes that it + * can drop off without losing any data. + */ + skb_put(skb, MIC_SIZE); /* add fake MIC */ + + /* + * Step 7: Wait for AES to become ready. + */ + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + dprintk(DWARNING, "2nd AES busy never became ready\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + } + udelay(1); + } + result = ((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_MIC) != 0); + timeout = 500; + while ((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_FIFO_EMPTY) == 0) { + timeout--; + piperp->ac->rd_reg(piperp, BB_AES_FIFO); + udelay(1); + } +#ifdef WANT_DEBUG + if (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + digi_dbg("AES busy set at end of rx\n"); + } +#endif + spin_unlock_irqrestore(&piperp->aesLock, flags); + + /* pad an extra 8 bytes for the MIC which the H/W strips */ + skb_put(skb, 8); + if (result) { + status->flag |= RX_FLAG_DECRYPTED; + } else { + digi_dbg("Error decrypting packet\n"); + } + } else { + /* + * If we branch here, then we are not able to decrypt the + * packet possibly because we don't have the key, or because + * the packet was encrypted using TKIP. Read the rest of the + * encrypted data. mac80211 will have to decrypt it in software. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, skb_put(skb, length), + length); + } + } else { + /* + * Frame is not encrypted, so just read it. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, skb_put(skb, length), length); + } + +#if WANT_RECEIVE_COUNT_SCROLL + if (((++packetCount) & 1023) == 0) { + printk(KERN_ERR "\n%d recd, tx_start_count = %d, tx_complete_count = %d.\n", + packetCount, piperp->pstats.tx_start_count, + piperp->pstats.tx_complete_count); + } +#endif +#if 0 +receive_packet_exit: +#endif + return result; +} + +/* + * This routine is called when we receive an ACK. This should be after + * we have transmitted a packet. We need to tell the upper layer we + * have a packet by calling ieee80211_tx_status_irqsafe with status + * information. The transmit routine also disables queuing whenever we + * transmit since we can only transmit one packet at a time, so we need + * to reenable to transmit queue too. + */ +static inline void handle_ack(struct piper_priv *piperp, int signal_strength) +{ + if (piper_tx_getqueue(piperp) && piperp->expectingAck) { + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + if ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0) { + piperp->clear_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + piperp->tx_signal_strength = signal_strength; + piperp->tx_result = RECEIVED_ACK; + tasklet_hi_schedule(&piperp->tx_tasklet); + } + } +} + + +/* + * This is the entry point for the receive tasklet. It is executed + * to process receive packets. It allocates an SKB and receives + * the packet into it. + * + * We may be called from the receive ISR if WANT_TO_RECEIVE_FRAMES_IN_ISR + * is set. + */ +void piper_rx_tasklet(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *)context; + + /* + * This while loop will keep executing as long as the H/W indicates there + * are more frames in the FIFO to be received. + */ + + piperp->ps.rxTaskletRunning = true; + + while (((piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) & + BB_GENERAL_STAT_RX_FIFO_EMPTY) == 0) + && (!piperp->ps.poweredDown)) { + struct sk_buff *skb = NULL; + struct ieee80211_rx_status status = { 0 }; + struct rx_frame_hdr header; + unsigned int length = 0; + frameControlFieldType_t fr_ctrl_field; + + /* + * Read and process the H/W header. This header is created by + * the hardware is is not part of the frame. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, (u8 *)&header, sizeof(header)); + phy_process_plcp(piperp, &header, &status, &length); + if ((length == 0) || (length > (RX_FIFO_SIZE - 48))) { /* 48 bytes for padding and related stuff */ + unsigned long flags; + + dprintk(DERROR, "bogus frame length (%d)\n", length); + dprintk(DERROR, "0x%08x 0x%08x\n", *(u32 *)&header, *(((u32 *)&header) + 1)); + spin_lock_irqsave(&piperp->aesLock, flags); + reset_fifo(piperp); + spin_unlock_irqrestore(&piperp->aesLock, flags); + continue; + } + + if (length != 0) { + + skb = __dev_alloc_skb(RX_FIFO_SIZE + 100, GFP_ATOMIC); + if (skb == NULL) { + /* Oops. Out of memory. Exit the tasklet */ + dprintk(DERROR, "__dev_alloc_skb failed\n"); + break; + } + + if (receive_packet(piperp, skb, length, &fr_ctrl_field, &status)) { + + if (length >= _80211_HEADER_LENGTH) + { + /* + * If using the Airoha transceiver, then we want to monitor + * the receive signal strength and continuously adjust the + * receive amplifier so that we get the best possible signal + * to noise ratio. + */ + unsigned int rssi = phy_determine_rssi(&header); + + piperp->adjust_max_agc(piperp, rssi, (_80211HeaderType *) skb->data); + } + + if (fr_ctrl_field.type == TYPE_ACK) + handle_ack(piperp, status.signal); + + if ((fr_ctrl_field.type == TYPE_ACK) + || (fr_ctrl_field.type == TYPE_RTS) + || (fr_ctrl_field.type == TYPE_CTS)) { + /* + * Don't pass up RTS, CTS, or ACK frames. They just + * confuse the stack + */ + dev_kfree_skb(skb); + } else { + if (fr_ctrl_field.type == TYPE_BEACON) { + piperp->beacon.weSentLastOne = false; + } + + piper_ps_process_receive_frame(piperp, skb); +#if WANT_TO_RECEIVE_FRAMES_IN_ISR + ieee80211_rx_irqsafe(piperp->hw, skb, &status); +#else + ieee80211_rx(piperp->hw, skb, &status); +#endif + } + } else { + /* Frame failed MIC, so discard it */ + dprintk(DWARNING, "dropping bad frame\n"); + dev_kfree_skb(skb); + } + } + } + + piperp->ps.rxTaskletRunning = false; + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_RX_FIFO); + piper_ps_rx_task_exiting(piperp); +} +EXPORT_SYMBOL_GPL(piper_rx_tasklet); + + + diff --git a/drivers/net/wireless/digiPiper/digiTx.c b/drivers/net/wireless/digiPiper/digiTx.c new file mode 100644 index 000000000000..4bea846a42a4 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiTx.c @@ -0,0 +1,604 @@ +/* + * digiTx.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * 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 file contains the routines that are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/jiffies.h> +#include <linux/timer.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define FRAME_CONTROL_FIELD_OFFSET (sizeof(struct tx_frame_hdr) + sizeof(struct psk_cck_hdr)) +#define AES_TIMEOUT (200) + +#define TX_DEBUG (1) + +#if TX_DEBUG +//static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * Adds an entry into the tx queue. + */ +int piper_tx_enqueue(struct piper_priv *piperp, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb) +{ + unsigned long flags; + int result = -1; + + spin_lock_irqsave(&piperp->tx_queue_lock, flags); + if (NEXT_TX_QUEUE_INDEX(piperp->tx_queue_head) != piperp->tx_queue_tail) { + piperp->tx_queue[piperp->tx_queue_head].skb = skb; + piperp->tx_queue[piperp->tx_queue_head].skb_return_cb = skb_return_cb; + piperp->tx_queue_head = NEXT_TX_QUEUE_INDEX(piperp->tx_queue_head); + piperp->tx_queue_count++; + result = 0; + } + spin_unlock_irqrestore(&piperp->tx_queue_lock, flags); + + return result; +} +EXPORT_SYMBOL_GPL(piper_tx_enqueue); + + +/* + * Returns the skb for the current element. + */ +struct sk_buff *piper_tx_getqueue(struct piper_priv *piperp) +{ + if (piperp->tx_queue_head == piperp->tx_queue_tail) { + return NULL; + } else { + return piperp->tx_queue[piperp->tx_queue_tail].skb; + } +} +EXPORT_SYMBOL_GPL(piper_tx_getqueue); + +/* + * Returns the skb buffer call back for the current element. + */ +static inline tx_skb_return_cb_t piper_tx_getqueue_return_fn(struct piper_priv *piperp) +{ + return piperp->tx_queue[piperp->tx_queue_tail].skb_return_cb; +} + + +/* + * Called to advance the queue tail. + */ +static inline void piper_tx_queue_next(struct piper_priv *piperp) +{ + unsigned long flags; + + spin_lock_irqsave(&piperp->tx_queue_lock, flags); + if (piperp->tx_queue_head != piperp->tx_queue_tail) { + piperp->tx_queue[piperp->tx_queue_tail].skb = NULL; + piperp->tx_queue[piperp->tx_queue_tail].skb_return_cb = NULL; + piperp->tx_queue_tail = NEXT_TX_QUEUE_INDEX(piperp->tx_queue_tail); + piperp->tx_queue_count--; + } + spin_unlock_irqrestore(&piperp->tx_queue_lock, flags); +} + +/* + * Called when we unload to clear any remaining queue entries. + */ +void piper_empty_tx_queue(struct piper_priv *piperp) +{ + while (piper_tx_getqueue(piperp)) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + + ieee80211_tx_info_clear_status(info); + piper_tx_getqueue_return_fn(piperp)(piperp->hw, piper_tx_getqueue(piperp)); + piper_tx_queue_next(piperp); + } +} +EXPORT_SYMBOL_GPL(piper_empty_tx_queue); + + +bool piper_tx_queue_half_full(struct piper_priv *piperp) +{ + return (piperp->tx_queue_count >= (PIPER_TX_QUEUE_SIZE >> 1)); +} +EXPORT_SYMBOL_GPL(piper_tx_queue_half_full); + + + +/* + * This routine writes a frame using H/W AES encryption. + * + * Arguments + * digi context + * buffer pointer to start of frame + * length number of bytes in frame + * + * Return Values + * 0 success + * !0 transmit failed + */ +static int piper_write_aes(struct piper_priv *piperp, unsigned char *buffer, + unsigned int length) +{ + int result; + int timeout = AES_TIMEOUT; + unsigned long spinLockFlags; + + /* + * Step 1: Wait for AES to become ready. + */ + spin_lock_irqsave(&piperp->aesLock, spinLockFlags); + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + /* + * If we come here, then AES busy appears to be stuck high. It should only be + * high for a maximum of about 80 us when it is encrypting a transmit frame. + * Our timeout value is high enough to guarantee that the engine has had enough + * time to complete the transmit. Apparently there is data stuck in the FIFO + * from either a previous transmit or receive. + */ + digi_dbg("write AES, AES busy stuck on\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + /* + * We recover by simply continuing on. Step 3 writes to the AES control + * register. This will reset the AES engine and clear the error condition. + */ + break; + } + udelay(1); + } + + /* + * Step 2: Write the unencrypted part of the frame into the normal + * data FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, buffer, + _80211_HEADER_LENGTH + TX_HEADER_LENGTH + PIPER_EXTIV_SIZE); + + /* + * Step 3: Write to the AES control register. Writing to it puts + * AES H/W engine into transmit mode. We also make sure + * the AES mode is set correctly. + */ + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + + /* + * Step 4: Write the expanded AES key into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + (unsigned char *)piperp->key[piperp->tx_aes_key].expandedKey, + EXPANDED_KEY_LENGTH); + + /* + * Step 5: Write the AES IV and headers into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, (unsigned char *)piperp->tx_aes_blob, + AES_BLOB_LENGTH); + + /* + * Step 6: Now, finally, write the part of the frame that needs to + * be encrypted into the AES FIFO. + */ + result = + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + &buffer[_80211_HEADER_LENGTH + TX_HEADER_LENGTH + PIPER_EXTIV_SIZE], + length - (_80211_HEADER_LENGTH + TX_HEADER_LENGTH + + PIPER_EXTIV_SIZE)); + + spin_unlock_irqrestore(&piperp->aesLock, spinLockFlags); + + return result; +} + +/* + * Determine what bit rate the next retry should be sent at. + * + * The mac80211 library passes us an array of tx bit rates. Each entry + * has a rate index and a limit (max number of retries at that rate). + * We use the rate index to build the H/W transmit header. The limit + * is decremented each time we retry. When it reaches zero, we try the + * next rate in the array. + */ +static struct ieee80211_rate *get_tx_rate(struct piper_priv *piperp, struct ieee80211_tx_info *info) +{ + struct ieee80211_rate *ret = NULL; + + if (piperp->pstats.tx_retry_count[piperp->pstats.tx_retry_index] >= + info->control.rates[piperp->pstats.tx_retry_index].count) { + piperp->pstats.tx_retry_index++; + } + + if (piperp->pstats.tx_retry_index >= IEEE80211_TX_MAX_RATES) { + return NULL; /* don't go beyond the end of the rates array */ + } + + if (piperp->pstats.tx_retry_index == 0) { + ret = ieee80211_get_tx_rate(piperp->hw, info); + } else { + ret = ieee80211_get_alt_retry_rate(piperp->hw, info, piperp->pstats.tx_retry_index - 1); + } + + if (ret != NULL) { + if (piperp->calibrationTxRate) { + ret = piperp->calibrationTxRate; + } + } + + ret = piper_ps_check_rate(piperp, ret); + + return ret; +} + +/* + * This function returns a value for the contention window in microseconds. We + * start with the contention window at CW_MIN and double it everytime we have to + * retry. + */ +static u16 piper_get_cw(struct piper_priv *piperp, bool isFirstTime) +{ + static u16 cw = DEFAULT_CW_MIN; + + if (isFirstTime) { + cw = DEFAULT_CW_MIN; + } else { + cw <<= 1; + if (cw > DEFAULT_CW_MAX) { + cw = DEFAULT_CW_MAX; + } + } + return (cw + (10 * (piperp->rand() & (cw - 1)))) & 0xffff; +} + +/* + * This function will prepend an RTS or CTS to self frame ahead of the current + * TX frame. This is done if the wantRts or wantCts flag is set. The mac80211 + * library determines if either of these flags is set. + * + * The RTS or CTS message is written into the transmit FIFO ahead of the + * data frame. Note that RTS and CTS messages are always sent in the clear + * so we do not have to worry about encryption. + * + * Our caller, the master transmit routine, is responsible for setting the + * transmit hold bit before calling us and clearing it after the data frame + * has been written into the FIFO. This ensures that the RTS/CTS frame is + * not transmitted until after the data frame is ready to go. + * + * Also note that if we are unable to send the RTS/CTS frame, then the H/W + * is smart enough to also about the data frame. So we will not send + * the data frame without the RTS/CTS frame. + */ +static void handle_rts_cts(struct piper_priv *piperp, + struct ieee80211_tx_info *txInfo, unsigned int frameType) +{ + piperp->tx_rts = false; + + if (frameType == TYPE_DATA) { + unsigned int header[2]; + struct ieee80211_rate *rate = NULL; + bool wantCts = (!!(txInfo->control.rates[piperp->pstats.tx_retry_index].flags + & IEEE80211_TX_RC_USE_CTS_PROTECT) + | piperp->tx_cts); + bool wantRts = !!(txInfo->control.rates[piperp->pstats.tx_retry_index].flags + & IEEE80211_TX_RC_USE_RTS_CTS); + + if ((wantRts) || (wantCts)) { + /* + * If we are sending an RTS or a CTS, then get the rate information. + */ + if (piperp->calibrationTxRate) { + rate = piperp->calibrationTxRate; + } else { + rate = ieee80211_get_rts_cts_rate(piperp->hw, txInfo); + } + if (rate == NULL) { + digi_dbg + ("ieee80211_get_rts_cts_rate(digi->hw, txInfo) returned NULL!\n"); + } + } + if ((wantRts) && (rate)) { + /* + * We're sending an RTS, so load it into the FIFO. + */ + struct ieee80211_rts rtsFrame; + + ieee80211_rts_get(piperp->hw, txInfo->control.vif, + piper_tx_getqueue(piperp)->data + TX_HEADER_LENGTH, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, txInfo, + &rtsFrame); + /* + * If we come here, then we need to send an RTS frame ahead of the + * current data frame. + */ + phy_set_plcp((unsigned char *)header, sizeof(struct ieee80211_rts), + rate, 0); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)header, + TX_HEADER_LENGTH); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)&rtsFrame, + sizeof(rtsFrame)); + if (piperp->pstats.tx_total_tetries != 0) { + piperp->pstats.ll_stats.dot11RTSFailureCount++; + } + piperp->tx_rts = true; + } else if ((wantCts) && (rate)) { + /* + * We're sending a CTS, so load it into the FIFO. + */ + struct ieee80211_cts ctsFrame; + + ieee80211_ctstoself_get(piperp->hw, txInfo->control.vif, + piper_tx_getqueue(piperp)->data + TX_HEADER_LENGTH, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, + txInfo, &ctsFrame); + /* + * At the time this code was written, the mac80211 library had + * a bug in the ieee80211_ctstoself_get which caused it to copy + * the wrong MAC address into the cts frame. So we copy the + * right one (ours) in now. + */ + memcpy(piperp->ctsFrame.ra, piperp->hw->wiphy->perm_addr, ETH_ALEN); + + /* + * If we come here, then we need to send a CTS to self frame ahead of the + * current data frame. + */ + phy_set_plcp((unsigned char *)header, sizeof(struct ieee80211_cts), + rate, 0); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)header, + TX_HEADER_LENGTH); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)&ctsFrame, + sizeof(ctsFrame)); + } + } +} + +/* + * This routine is called to report the result of a transmit operation to + * mac80211. It is used for both successful transmissions and failures. + * It sends the result to the stack, removes the current tx frame from the + * queue, and then wakes + * up the transmit queue. + */ +void packet_tx_done(struct piper_priv *piperp, tx_result_t result, + int signal_strength) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + int i; + struct sk_buff *skb; + unsigned long flags; + +#define WANT_TRANSMIT_RESULT (0) +#if WANT_TRANSMIT_RESULT + const char *resultText[] = + { + "RECEIVED_ACK", + "TX_COMPLETE", + "OUT_OF_RETRIES", + "TX_NOT_DONE" + }; +#endif + del_timer_sync(&piperp->tx_timer); + piperp->expectingAck = false; + +#if WANT_TRANSMIT_RESULT + printk(KERN_ERR "Transmit result %s\n", resultText[result]); +#endif + if (piperp->tx_calib_cb) + piperp->tx_calib_cb(piperp); + + if (piper_tx_getqueue(piperp) != NULL) { + skb_pull(piper_tx_getqueue(piperp), TX_HEADER_LENGTH); + + ieee80211_tx_info_clear_status(info); + + /* prepare statistics and pass them to the stack */ + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + info->status.rates[i].count = piperp->pstats.tx_retry_count[i]; + if (info->status.rates[i].count == 0) + info->status.rates[i].idx = -1; + } + + info->status.ack_signal = signal_strength; + info->flags |= (result == RECEIVED_ACK) ? IEEE80211_TX_STAT_ACK : 0; + piperp->pstats.tx_complete_count++; + piperp->pstats.tx_queue.len--; + if (piperp->tx_rts) + piperp->pstats.ll_stats.dot11RTSSuccessCount++; + + skb = piper_tx_getqueue(piperp); + piper_tx_getqueue_return_fn(piperp)(piperp->hw, skb); + + piper_tx_queue_next(piperp); + + if (piper_tx_getqueue(piperp) == NULL) { + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + piperp->tx_tasklet_running = false; + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + if (piperp->ps.allowTransmits) { + ieee80211_wake_queues(piperp->hw); + } else { + /* + * Do not wake up the mac80211 Tx queue if we are trying to power + * down. Make sure we set the stopped_tx_queues flag so that we + * know to restart the queues. + */ + piperp->ps.stopped_tx_queues = true; + } + } else { + if (result == OUT_OF_RETRIES) { + /* + * If we come here, then we are being called from the middle of the + * transmit routine and we have to reschedule the transmit task to + * start dequeueing the next frame. + */ + tasklet_hi_schedule(&piperp->tx_tasklet); + } + } + } else { + digi_dbg("packet_tx_done called with empty queue\n"); + } + + piperp->tx_result = TX_NOT_DONE; +} +EXPORT_SYMBOL_GPL(packet_tx_done); + +/* + * This function is the entry point for the transmit tasklet. It + * is called to transmit frames. It will first be called to transmit + * the frame and then to retry if the original transmit fails. So + * it does both the first transmit and the subsequent retries. + * + * Arguments + * context context information + */ +void piper_tx_tasklet(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *)context; + frameControlFieldType_t *fc; + int err; + + piperp->expectingAck = false; + del_timer_sync(&piperp->tx_timer); + if ((piperp->tx_result == RECEIVED_ACK) || (piperp->tx_result == TX_COMPLETE)) { + /* + * We will come here if the receiver task received an ACK, or if we got + * a tx fifo empty interrupt. In these cases the receiver thread or ISR + * schedule the tx tasklet to handle the event rather than calling + * packet_tx_done directly. + */ + packet_tx_done(piperp, piperp->tx_result, piperp->tx_signal_strength); + } + + + /* + * Clear flags here to cover ACK case. We do not clear the flags in the ACK + * routine since it is possible to receive an ACK after we have started the + * next packet. The appropriate interrupts will be reenabled if we decide + * to retransmit. + */ + piperp->clear_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + + if (piper_tx_getqueue(piperp) != NULL) { + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + struct ieee80211_rate *txRate = get_tx_rate(piperp, txInfo); + + if (txRate != NULL) { + fc = (frameControlFieldType_t *) + &piper_tx_getqueue(piperp)->data[FRAME_CONTROL_FIELD_OFFSET]; + + /* set the retry bit if this is not the first try */ + if (piperp->pstats.tx_retry_count[0] != 0) + fc->retry = 1; + + piperp->ac->wr_reg(piperp, MAC_BACKOFF, + piper_get_cw(piperp, + (piperp->pstats.tx_retry_count[0] == 0)), + op_write); + + /* + * Build the H/W transmit header. The transmit header is rebuilt on each + * retry because it has the TX rate information which may change for + * retries. + */ + phy_set_plcp(piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, + txRate, piperp->use_hw_aes ? 8 : 0); + + /* + * Pause the transmitter so that we don't start transmitting before we + * are ready. + */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_TX_HOLD, op_or); + + handle_rts_cts(piperp, txInfo, fc->type); + + if (piperp->use_hw_aes == true && txInfo->control.hw_key != NULL) { + err = + piper_write_aes(piperp, piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len); + } else { + err = + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len); + } + + /* Clear any pending TX interrupts */ + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, + BB_IRQ_MASK_TX_FIFO_EMPTY | BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT, op_write); + + /* + * Now start the transmitter. + */ + piperp->expectingAck = ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_TX_HOLD, + op_and); + + /* + * Set interrupt flags. Use the timeout interrupt if we expect + * an ACK. Use the FIFO empty interrupt if we do not expect an ACK. + */ + if (txInfo->flags & IEEE80211_TX_CTL_NO_ACK) { + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TX_ABORT); + } else { + /* + * We set up a timer to fire in 1/4 second. We should not need it, but somehow + * we seem to miss a timeout interrupt occasionally. Perhaps we encounter a receive + * overrun which causes the H/W to discard the ACK packet without generating + * a timeout. + */ + piperp->tx_timer.expires = jiffies + (HZ >> 2); + add_timer(&piperp->tx_timer); + + /* + * Also set the IRQ mask to listen for timeouts and TX aborts. We will receive + * an ACK (which is handled by the RX routine) if the TX is successful. + */ + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + } + if ((piperp->pstats.tx_total_tetries != 0) && + ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0)) { + piperp->pstats.ll_stats.dot11ACKFailureCount++; + } + piperp->pstats.tx_retry_count[piperp->pstats.tx_retry_index]++; + piperp->pstats.tx_total_tetries++; + } else { + packet_tx_done(piperp, OUT_OF_RETRIES, 0); + } + } else { + long unsigned int flags; + + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + piperp->tx_tasklet_running = false; + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + } +} +EXPORT_SYMBOL_GPL(piper_tx_tasklet); + + + diff --git a/drivers/net/wireless/digiPiper/mac.h b/drivers/net/wireless/digiPiper/mac.h new file mode 100644 index 000000000000..892a15a8b67c --- /dev/null +++ b/drivers/net/wireless/digiPiper/mac.h @@ -0,0 +1,375 @@ +#ifndef DIGI_MAC_H_ +#define DIGI_MAC_H_ + +enum baseband_control_regs { + BB_VERSION = 0x00, + BB_GENERAL_CTL = 0x04, + BB_GENERAL_STAT = 0x08, + BB_RSSI = 0x0c, + BB_IRQ_MASK = 0x10, + BB_IRQ_STAT = 0x14, + BB_SPI_DATA = 0x18, + BB_SPI_CTRL = 0x1c, + BB_DATA_FIFO = 0x20, + BB_TRACK_CONTROL = 0x28, + BB_CONF_2 = 0x2c, + BB_AES_FIFO = 0x30, + BB_AES_CTL = 0x38, + BB_OUTPUT_CONTROL = 0x3c +}; + +#define BB_VERSION_MASK(v) ((v) & 0xffff) + +#define BB_GENERAL_CTL_RX_EN (1<<0) +#define BB_GENERAL_CTL_ANT_DIV (1<<1) +#define BB_GENERAL_CTL_ANT_SEL (1<<2) +#define BB_GENERAL_CTL_GEN_5GEN (1<<3) // 5 GHz band enable +#define BB_GENERAL_CTL_SH_PRE (1<<4) +#define BB_GENERAL_CTL_RXFIFORST (1<<5) +#define BB_GENERAL_CTL_TXFIFORST (1<<6) +#define BB_GENERAL_CTL_TX_HOLD (1<<7) +#define BB_GENERAL_CTL_BEACON_EN (1<<8) +#define BB_GENERAL_CTL_FW_LOAD_ENABLE (1 << 9) +#define BB_GENERAL_CTL_DSP_LOAD_ENABLE (1 << 10) +#define BB_GENERAL_CTL_MAC_ASSIST_ENABLE (1 << 11) +#define BB_GENERAL_CTL_TX_FIFO_FULL (1<<15) +#define BB_GENERAL_CTL_TX_FIFO_EMPTY (1<<14) +/* TODO: verify max gain value for Piper and Wi9p*/ +#define BB_GENERAL_CTL_MAX_GAIN(g) (((g) & 0x7f)<<16) +#define BB_GENERAL_CTL_PWR_UP (1<<24) +#define BB_GENERAL_CTL_ADC_CLK_EN (1<<25) +#define BB_GENERAL_CTL_BOOT_STAT (1<<28) +#define BB_GENERAL_CTL_CLK_EN (1<<29) +#define BB_GENERAL_CTL_SPI_RST (1<<30) + +#define BB_GENERAL_CTL_MAX_GAIN_MASK (0x007F0000) +#define BB_GENERAL_CTL_DEFAULT_MAX_GAIN_A (0x00790000) +#define BB_GENERAL_CTL_DEFAULT_MAX_GAIN_BG (0x007c0000) + +#if defined(CONFIG_PIPER_WIFI) +#if 0 +#define BB_GENERAL_CTL_INIT (BB_GENERAL_CTL_MAX_GAIN(0x7a) | \ + BB_GENERAL_CTL_PWR_UP | BB_GENERAL_CTL_ADC_CLK_EN | \ + BB_GENERAL_CTL_BOOT_STAT | BB_GENERAL_CTL_CLK_EN) +#define BB_GENERAL_CTL_RESET (BB_GENERAL_CTL_MAX_GAIN(0x7f) | \ + BB_GENERAL_CTL_ADC_CLK_EN | BB_GENERAL_CTL_BOOT_STAT | \ + BB_GENERAL_CTL_SPI_RST) +#else +#define BB_GENERAL_CTL_INIT (BB_GENERAL_CTL_MAX_GAIN(0x7a) + +#define BB_GENERAL_CTL_RESET (BB_GENERAL_CTL_MAX_GAIN(0x7f) | \ + BB_GENERAL_CTL_SPI_RST +#endif +#else +#define BB_GENERAL_CTL_INIT (BB_GENERAL_CTL_MAX_GAIN(0x7a) | \ + BB_GENERAL_CTL_PWR_UP | BB_GENERAL_CTL_ADC_CLK_EN | \ + BB_GENERAL_CTL_BOOT_STAT | BB_GENERAL_CTL_CLK_EN) + +#define BB_GENERAL_CTL_RESET (BB_GENERAL_CTL_MAX_GAIN(0x7f) | \ + BB_GENERAL_CTL_ADC_CLK_EN | BB_GENERAL_CTL_BOOT_STAT | \ + BB_GENERAL_CTL_SPI_RST) +#endif +#define BB_RSSI_LED (1<<8) +#define BB_RSSI_EAS_FIFO_EMPTY (1 << 16) +#define BB_RSSI_EAS_FIFO_FULL (1 << 17) +#define BB_RSSI_EAS_BUSY (1 << 18) +#define BB_RSSI_EAS_MIC (1 << 19) +#define BB_RSSI_ANT_MASK (0xff<<24) +#define BB_RSSI_ANT_NO_DIV_MAP (0x96000000) +#define BB_RSSI_ANT_DIV_MAP (0x1E000000) + +#define BB_GENERAL_STAT_RESET (1<<30) +/* + * STAT_B_EN is a constant that defines a bit in the Wireless Controller FPGA Baseband Control Register + * for General Status, which enables the PSK/CCK receiver baseband circuitry (802.11b receiver). + * STAT_A_EN is a constant that defines a bit in the Wireless Controller FPGA Baseband Control Register + * for General Status, which enables the OFDM receive baseband circuitry (802.11a receiver). + */ + +#define BB_GENERAL_STAT_B_EN 0x10000000 // B EN (PSK/CCK) +#define BB_GENERAL_STAT_A_EN 0x20000000 // A EN (OFDM) +#define BB_GENERAL_STAT_RX_FIFO_EMPTY (1 << 4) +#define BB_GENERAL_STAT_DC_DIS (1 << 24) +#define BB_GENERAL_STAT_SRC_DIS (1 << 16) +#define BB_GENERAL_STAT_SPRD_DIS (1 << 17) +#define BB_GENERAL_STAT_DLL_DIS (1 << 18) +#define TRACK_TX_B_GAIN_MASK 0xff000000 // Mask word for B_TX_GAIN +#define TRACK_TX_B_GAIN_NORMAL 0xA0000000 // normal setting for B_TX_GAIN +#define TRACK_BG_BAND 0x00430000 // Tracking constant for 802.11 b/g frequency band +#define TRACK_CONSTANT_MASK 0x00ff0000 // mask for tracking constant +#define TRACK_4920_4980_A_BAND 0x00210000 // Tracking constant for 802.11 a sub-frequency band +#define TRACK_5150_5350_A_BAND 0x001F0000 // Tracking constant for 802.11 a sub-frequency band +#define TRACK_5470_5725_A_BAND 0x001D0000 // Tracking constant for 802.11 a sub-frequency band +#define TRACK_5725_5825_A_BAND 0x001C0000 // Tracking constant for 802.11 a sub-frequency band + + +#define BB_IRQ_MASK_RX_FIFO (1<<0) +#define BB_IRQ_MASK_TX_FIFO_EMPTY (1<<1) +#define BB_IRQ_MASK_TIMEOUT (1<<2) +#define BB_IRQ_MASK_TX_ABORT (1<<3) +#define BB_IRQ_MASK_TBTT (1<<4) +#define BB_IRQ_MASK_ATIM (1<<5) +#define BB_IRQ_MASK_RX_OVERRUN (1<<6) + +#define BB_AES_CTL_KEY_LOAD (1<<2) +#define BB_AES_CTL_AES_MODE (1<<4) + +enum mac_control_regs { + MAC_STA_ID0 = 0x40, + MAC_STA_ID1 = 0x44, + MAC_BSS_ID0 = 0x48, + MAC_BSS_ID1 = 0x4c, + MAC_SSID_LEN = 0x50, /* OFDM_BRS, PSK_BRS, TX_CTL, SSID_LEN */ + MAC_BACKOFF = 0x54, /* actually 0x56; 2 low order bytes are empty */ + MAC_DTIM_PERIOD = 0x58, + /*MAC_CFP_PERIOD = 0x59,*/ + /*MAC_LISTEN_INTERVAL = 0x5a,*/ + MAC_CFP_ATIM = 0x5c, /* beacon interval, CFP/ATIM duration */ + MAC_STATUS = 0x60, + /*MAC_TXP_TIMING = 0x62,*/ + /*MAC_STATUS = 0x63,*/ + MAC_CTL = 0x64, /* MAC_AES_KEY_DIS (8 bits), MAC_CTL (8 bits) */ + MAC_MEASURE = 0x68, /* actually 0x69 */ + /*MAC_REMAIN_BO = 0x6a,*/ + MAC_BEACON_FILT = 0x6c, /* actally 0x6d */ + /*MAC_BEACON_BO = 0x6e,*/ + MAC_STA2_ID0 = 0xb0, + MAC_STA2_ID1 = 0xb4, + MAC_STA3_ID0 = 0xb8, + MAC_STA3_ID1 = 0xbc, + + MAC_EEPROM_CTL = 0xf0, + MAC_EEPROM_DATA = 0xf8, + + MAC_SSID = 0x80, + + BEACON_FIFO = 0x85, /* dummy value used to select data fifo for beacon load */ +}; + + +#define MAC_SSID_LEN_MASK (0x000000ff) +#define MAC_REVISION_MASK(v) (((v) >> 16) & 0xffff) + +#define MAC_BEACON_INTERVAL_SHIFT (16) +#define MAC_BEACON_INTERVAL_MASK (0xffff0000) + +#define MAC_ATIM_PERIOD_MASK (0x0000ffff) + +#define MAC_LISTEN_INTERVAL_MASK (0x0000ffff) + +#define MAC_DTIM_PERIOD_SHIFT (24) +#define MAC_DTIM_PERIOD_MASK (0xff000000) + +#define MAC_DTIM_CFP_SHIFT (16) +#define MAC_DTIM_CFP_MASK (0x00ff0000) + +#define MAC_OFDM_BRS_MASK (0xff000000) +#define MAC_OFDM_BRS_SHIFT (24) +#define MAC_PSK_BRS_MASK (0x000f0000) +#define MAC_PSK_BRS_SHIFT (16) + +#define MAC_BEACON_BACKOFF_MASK (0x0000ffff) + +#define MAC_BRS_MASK (MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK) + +#define MAC_CTL_TX_REQ (1) +#define MAC_CTL_BEACON_TX (1<<2) +#define MAC_CTL_IBSS (1<<4) +#define MAC_CTL_AES_DISABLE (1<<5) +#define MAC_CTL_MAC_FLTR (1<<6) +#define MAC_CTL_KEY0_DISABLE (1<<8) +#define MAC_CTL_KEY1_DISABLE (1<<9) +#define MAC_CTL_KEY2_DISABLE (1<<10) +#define MAC_CTL_KEY3_DISABLE (1<<11) + +#define MAC_EEPROM_CTL_WAIT_MS 21 + +/* + * RX packets look something like: + * <custom bus protocol header(s)> - protocol-dependent + * <rx_frame_hdr> - 4 bytes + * <plcp; either psk_cck_hdr or ofdm_hdr> - 4 bytes + * <mac header> - dealt with by the mac80211 stack, not the driver + * <data> + * + * TX packets are similar: + * <custom bus protocol header(s)> + * <tx_frame_hdr> - 4 bytes + * <plcp; either psk_cck_hdr or ofdm_hdr> - 4 bytes + * <mac header> + * <data> + */ + + + +// MAC type field values +#define TYPE_ASSOC_REQ 0x00 // Association request +#define TYPE_ASSOC_RESP 0x10 // Association response +#define TYPE_REASSOC_REQ 0x20 // Reassociation request +#define TYPE_REASSOC_RESP 0x30 // Reassociation response +#define TYPE_PROBE_REQ 0x40 // Probe request +#define TYPE_PROBE_RESP 0x50 // Probe response + +#define TYPE_BEACON 0x80 // Beacon +#define TYPE_ATIM 0x90 // Annoucement traffice indication +#define TYPE_DISASSOC 0xa0 // Disassociation +#define TYPE_AUTH 0xb0 // Authentication +#define TYPE_DEAUTH 0xc0 // Deauthentication +#define TYPE_ACTION 0xd0 // Action + +#define TYPE_RTS 0xb4 // Request to send +#define TYPE_CTS 0xc4 // Clear to send +#define TYPE_ACK 0xd4 // Acknowledgement +#define TYPE_PSPOLL 0xa4 // Power Save(PS)-Poll + +#define TYPE_DATA 0x08 // Data +#define TYPE_NULL_DATA 0x48 // Null Data + +struct tx_frame_hdr { +#if 1 + unsigned int modulation_type:8; + unsigned int length:9; + unsigned int pad:15; +#else + uint8_t modulation_type; + __le16 length:9; + unsigned int pad:15; +#endif +} __attribute__((packed)); + + +#define MOD_TYPE_PSKCCK 0x00 +#define MOD_TYPE_OFDM 0xee + +struct psk_cck_hdr { + uint8_t signal; /* x100Kbps */ + uint8_t service; + __le16 length; /* usecs */ +} __attribute__((packed)); + +/* PSK/CCK PLCP service field bits */ +#define PLCP_SERVICE_LOCKED 0x04 /* locked clocks */ +#define PLCP_SERVICE_MODSEL 0x08 /* modulation selection */ +#define PLCP_SERVICE_LENEXT 0x80 /* length extension */ + +struct ofdm_hdr { + unsigned int rate:4; + unsigned int pad_a:1; + unsigned int length:12; /* in bytes */ + unsigned int parity:1; + unsigned int pad_b:14; +} __attribute__((packed)); + + +struct rx_frame_hdr { + uint8_t modulation_type; + unsigned int rssi_variable_gain_attenuator:5; + unsigned int rssi_low_noise_amp:2; + unsigned int antenna:1; + __be16 freq_offset; + union + { + struct psk_cck_hdr psk; + struct ofdm_hdr ofdm; + } mod; +} __attribute__((packed)); + +typedef struct +{ + unsigned type :8; // Type, subtype, version + unsigned toDS :1; // To distribution service (AP) + unsigned fromDS :1; // From distribution service (AP) + unsigned moreFrag :1; // More fragments + unsigned retry :1; // Retransmission + unsigned pwrMgt :1; // Power management state + unsigned moreData :1; // More data buffered + unsigned protectd :1; // Encrypted + unsigned order :1; // Strictly ordered +} frameControlFieldType_t; + +#define WLN_ADDR_SIZE (6) + +typedef unsigned char MacAddr[WLN_ADDR_SIZE]; + + +#define PACKED_H +#define PACKED_F __attribute__ ((packed)) + +typedef PACKED_H struct { + unsigned type :8; // Type, subtype, version +#ifdef BIG_ENDIAN + unsigned order :1; // Strictly ordered + unsigned protected :1; // Encrypted + unsigned moreData :1; // More data buffered + unsigned pwrMgt :1; // Power management state + unsigned retry :1; // Retransmission + unsigned moreFrag :1; // More fragments + unsigned fromDS :1; // From distribution service (AP) + unsigned toDS :1; // To distribution service (AP) +#else + unsigned toDS :1; // To distribution service (AP) + unsigned fromDS :1; // From distribution service (AP) + unsigned moreFrag :1; // More fragments + unsigned retry :1; // Retransmission + unsigned pwrMgt :1; // Power management state + unsigned moreData :1; // More data buffered + unsigned protected :1; // Encrypted + unsigned order :1; // Strictly ordered +#endif +} PACKED_F FrameControl_t; + + +// Sequence control field +// Need to swap bytes on BIG_ENDIAN +typedef PACKED_H struct { +#ifdef BIG_ENDIAN + unsigned seq :12; // Sequence number + unsigned frag :4; // Fragment number +#else + unsigned frag :4; // Fragment number + unsigned seq :12; // Sequence number +#endif +} PACKED_F SeqControl; + +// Union of sequence control types +typedef PACKED_H union { + SeqControl sq; // Sequence control fields + unsigned short sq16; // Sequence control as 16-bit int (needs byte swap) +} PACKED_F SeqControlU; + + + +typedef PACKED_H struct { + FrameControl_t fc; // Frame control + unsigned short duration; // Duration/ID (needs byte swap) + MacAddr addr1; // Address 1 + MacAddr addr2; // Address 2 + MacAddr addr3; // Address 3 + SeqControlU squ; // Sequence control fields +} PACKED_F _80211HeaderType; + +typedef PACKED_H struct { + FrameControl_t fc; // Frame control + unsigned short aid; // association identifier + MacAddr addr1; // Address 1 + MacAddr addr2; // Address 2 +} PACKED_F _80211PSPollType; + +#define _80211_HEADER_LENGTH (sizeof(_80211HeaderType)) +#define TX_HEADER_LENGTH (sizeof(struct ofdm_hdr) + sizeof(struct tx_frame_hdr)) +/* FIFO sizes in bytes */ +#define TX_FIFO_SIZE 1792 +#define RX_FIFO_SIZE 2048 + +#define RATE_MASK_BASIC 0x0153 +#define RATE_MASK_OFDM 0x0ff0 +#define RATE_MASK_PSK_CCK 0x000f + +#define BEACON_INT 100 /* in TU */ + + +#define DEFAULT_CW_MIN 32 // Min contention window size +#define DEFAULT_CW_MAX 1024 // Max contention window size + +#define ASLOT_TIME 20 +#endif diff --git a/drivers/net/wireless/digiPiper/phy.c b/drivers/net/wireless/digiPiper/phy.c new file mode 100644 index 000000000000..5a6c53638e81 --- /dev/null +++ b/drivers/net/wireless/digiPiper/phy.c @@ -0,0 +1,269 @@ +/* + * Linux device driver for Digi's WiWave WLAN card + * + * Copyright © 2008 Digi International, Inc + * + * Author: Andres Salomon <dilinger@debian.org> + * + * 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/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <net/mac80211.h> + +#include "pipermain.h" +#include "mac.h" +#include "airoha.h" +#include "phy.h" + +#define PHY_DEBUG (0) + +#if PHY_DEBUG +static int dlevel = DVERBOSE; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +#define NUMBER_OF_WORD32(x) ((x + 3) >> 2) + +static int is_ofdm_rate(int rate) +{ + return (rate % 3) == 0; +} + +void phy_set_plcp(unsigned char *frame, unsigned length, struct ieee80211_rate *rate, int aes_len) +{ + int ofdm = is_ofdm_rate(rate->bitrate); + int plcp_len = length + FCS_LEN + aes_len; + struct tx_frame_hdr *hdr; + + if (ofdm) { + /* OFDM header */ + struct ofdm_hdr *ofdm; + + ofdm = (struct ofdm_hdr *) &frame[sizeof(struct tx_frame_hdr)]; + memset(ofdm, 0, sizeof(*ofdm)); + ofdm->rate = rate->hw_value; + ofdm->length = cpu_to_le16(plcp_len); + } else { + /* PSK/CCK header */ + struct psk_cck_hdr *pskcck; + int us_len; + + pskcck = (struct psk_cck_hdr *) &frame[sizeof(struct tx_frame_hdr)]; + pskcck->signal = rate->bitrate; + pskcck->service = PLCP_SERVICE_LOCKED; + + /* convert length from bytes to usecs */ + switch (rate->bitrate) { + case 10: + us_len = plcp_len * 8; + break; + case 20: + us_len = plcp_len * 4; + break; + case 55: + us_len = (16 * plcp_len + 10) / 11; + break; + case 110: + us_len = (8 * plcp_len + 10) / 11; + + /* set length extension bit if needed */ + dprintk(DALL, "us_len = %d, plcp_len = %d, (11 * us_len) = %d, \ + (11 * us_len) / 8 = %d\n", us_len, plcp_len, + (11 * us_len), (11 * us_len) / 8); + + if ((11 * us_len) / 8 > plcp_len) { + pskcck->service |= PLCP_SERVICE_LENEXT; + dprintk(DALL, "Set PLCP_SERVICE_LENEXT, \ + pskcck->service = 0x%4.4X\n", pskcck->service); + } else { + dprintk(DALL, "Did not set PLCP_SERVICE_LENEXT, \ + pskcck->service = 0x%4.4X\n", pskcck->service); + } + break; + default: + digi_dbg("rate = %p, rate->bitrate%d\n", rate, rate->bitrate); + WARN_ON(1); + us_len = 0; + } + + pskcck->length = cpu_to_le16(us_len); + + dprintk(DALL, "pskcck .length = %d, signal = %d, service = %d\n", + pskcck->length, pskcck->signal, pskcck->service); + dprintk(DALL, "rate->bitrate=%x (@%dM), pckcck->length=%d\n", + rate->bitrate, rate->bitrate/10, pskcck->length); + } + + hdr = (struct tx_frame_hdr *) frame; + hdr->pad = 0; + hdr->length = NUMBER_OF_WORD32((length + aes_len + TX_HEADER_LENGTH)); + hdr->modulation_type = ofdm ? MOD_TYPE_OFDM : MOD_TYPE_PSKCCK; + + dprintk(DVVERBOSE, "frame hdr .length = %d, .modulation_type = %d\n", + hdr->length, hdr->modulation_type); + + dprintk(DVERBOSE, "TX: %d byte %s packet @ %dmbit\n", length, + ofdm ? "OFDM" : "PSK/CCK", rate->bitrate/10); +} +EXPORT_SYMBOL_GPL(phy_set_plcp); + + +static int get_signal(struct rx_frame_hdr *hdr, enum ieee80211_band rf_band, int transceiver) +{ + int gain; + int signal; + + if (transceiver == RF_AIROHA_2236) { + const u8 lnaTable_al2236[] = + { + 0, 0, 20, 36 + }; + + // Map high gain values to dbm + const signed char gainTable_al2236[] = + { + -85, -85, -88, -88, -92 + }; + // Convert received signal strength to dbm + gain = lnaTable_al2236[hdr->rssi_low_noise_amp] + 2*hdr->rssi_variable_gain_attenuator; + if (gain > 92) + signal = -96; + else if (gain > 87) + signal = gainTable_al2236[gain - 88]; + else + signal = 4 - gain; + } else { + static const u8 lnaTable_al7230_24ghz[] = + { + 0, 0, 18, 42 + }; + static const u8 lnaTable_al7230_50ghz[] = + { + 0, 0, 17, 37 + }; + /* Convert received signal strength to dbm for RF_AIROHA_7230 */ + if (rf_band == IEEE80211_BAND_2GHZ) { + gain = lnaTable_al7230_24ghz[hdr->rssi_low_noise_amp] + 2*hdr->rssi_variable_gain_attenuator; + signal = 2 - gain; + } else { + gain = lnaTable_al7230_50ghz[hdr->rssi_low_noise_amp] + 2*hdr->rssi_variable_gain_attenuator; + signal = -5 - gain; + } + } + + return signal; +} + + + + +/* FIXME, following limtis should depend on the platform */ +#define PIPER_MAX_SIGNAL_DBM (-30) +#define PIPER_MIN_SIGNAL_DBM (-90) + +static int calculate_link_quality(int signal) +{ + int quality; + + if (signal < PIPER_MIN_SIGNAL_DBM) + quality = 0; + else if (signal > PIPER_MAX_SIGNAL_DBM) + quality = 100; + else + quality = (signal - PIPER_MIN_SIGNAL_DBM) * 100 / + (PIPER_MAX_SIGNAL_DBM - PIPER_MIN_SIGNAL_DBM); + + dprintk(DVERBOSE, "signal: %d, quality: %d/100\n", signal, quality); + + return quality; +} + +unsigned int phy_determine_rssi(struct rx_frame_hdr *hdr) +{ + return (hdr->rssi_low_noise_amp << 5) | hdr->rssi_variable_gain_attenuator; +} +EXPORT_SYMBOL_GPL(phy_determine_rssi); + + + +void phy_process_plcp(struct piper_priv *piper, struct rx_frame_hdr *hdr, + struct ieee80211_rx_status *status, unsigned int *length) +{ + unsigned rate, i; + struct digi_rf_ops *rf = piper->rf; + + memset(status, 0, sizeof(*status)); + status->band = piper->rf->getBand(piper->channel); + status->signal = get_signal(hdr, status->band, piper->pdata->rf_transceiver); + status->antenna = hdr->antenna; + status->freq = piper->rf->getFrequency(piper->channel); + status->qual = calculate_link_quality(status->signal); + + if (hdr->modulation_type == MOD_TYPE_OFDM) { + /* OFDM */ + struct ofdm_hdr *ofdm = &hdr->mod.ofdm; + const int ofdm_rate[] = { + 480, 240, 120, 60, 540, 360, 180, 90 + }; + + rate = ofdm_rate[ofdm->rate & 0x7]; + *length = le16_to_cpu(ofdm->length); + dprintk(DVVERBOSE, "%d byte OFDM packet @ %dmbit\n", + *length, rate/10); + } else { + /* PSK/CCK */ + struct psk_cck_hdr *pskcck = &hdr->mod.psk; + + rate = pskcck->signal; + + *length = le16_to_cpu(pskcck->length); + switch (rate) { + case 10: + *length /= 8; + break; + case 20: + *length /= 4; + break; + case 55: + *length = (11 * (*length)) / 16; + break; + case 110: + *length = (11 * (*length)) / 8; + if (pskcck->service & PLCP_SERVICE_LENEXT) + (*length)--; + break; + default: + /* WARN_ON(1); This happens to often for us to generate that long error message */ + *length = 0; + } + + dprintk(DVVERBOSE, "%d byte PSK/CCK packet @ %dmbit\n", + *length, rate/10); + } + + /* match rate with the list of bitrates that we supplied the stack */ + for (i = 0; i < rf->bands[status->band].n_bitrates; i++) { + if (rf->bands[status->band].bitrates[i].bitrate == rate) + break; + } + + if (i != rf->bands[status->band].n_bitrates) + status->rate_idx = i; + else { + *length = 0; + digi_dbg(PIPER_DRIVER_NAME + ": couldn't find bitrate index for %d?\n", + rate); + status->flag |= RX_FLAG_FAILED_PLCP_CRC; + return; + } +} +EXPORT_SYMBOL_GPL(phy_process_plcp); diff --git a/drivers/net/wireless/digiPiper/phy.h b/drivers/net/wireless/digiPiper/phy.h new file mode 100644 index 000000000000..96500ab92e3e --- /dev/null +++ b/drivers/net/wireless/digiPiper/phy.h @@ -0,0 +1,28 @@ +/* + * Linux device driver for Digi's WiWave WLAN card + * + * Copyright © 2008 Digi International, 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 phy_h_ +#define phy_h_ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <net/mac80211.h> + + +#include "pipermain.h" +#include "mac.h" + +void phy_set_plcp(unsigned char *frame, unsigned length, struct ieee80211_rate *rate, int aes_len); +void phy_process_plcp(struct piper_priv *piper, struct rx_frame_hdr *hdr, + struct ieee80211_rx_status *status, unsigned int *length); +unsigned int phy_determine_rssi(struct rx_frame_hdr *hdr); + +#endif diff --git a/drivers/net/wireless/digiPiper/piper.c b/drivers/net/wireless/digiPiper/piper.c new file mode 100644 index 000000000000..a3c24720dd11 --- /dev/null +++ b/drivers/net/wireless/digiPiper/piper.c @@ -0,0 +1,1032 @@ +/* + * piper.c + * + * Copyright (C) 2008 by Digi International Inc. + * All rights reserved. + * + * 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/kernel.h> +#include <linux/module.h> +#include <net/mac80211.h> +#include <linux/usb.h> +#include <linux/kthread.h> +#include <linux/platform_device.h> +#include <asm/gpio.h> +#include <linux/timer.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "airoha.h" +#include "airohaCalibration.h" +#include "piperDsp.h" +#include "piperMacAssist.h" +#include "digiPs.h" + +#define WANT_AIROHA_CALIBRATION (1) +#define WANT_DEBUG_COMMANDS (1) + + +static void piper_clear_irq_mask(struct piper_priv *piperp, unsigned int bits) +{ + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, ~bits, op_and); +} + +static void piper_set_irq_mask(struct piper_priv *piperp, unsigned int bits) +{ + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, bits, op_or); +} + +/* Generate a random number */ +static int local_rand(void) +{ + static unsigned long next = 1; + + /* RAND_MAX assumed to be 32767 */ + next = next * 1103515245 + 12345; + return((unsigned)(next/65536) % 32768); +} + +/* + * Load the MAC Assist firmware into the chip. This is done by setting a bit + * in the control register to enable MAC Assist firmware download, and then + * writing the firmware into the data FIFO. + */ +void piper_load_mac_firmware(struct piper_priv *piperp) +{ + unsigned int i; + + printk(KERN_DEBUG PIPER_DRIVER_NAME ": loading MAC Assist firmware\n"); + + /* Zero out MAC assist SRAM (put into known state before enabling MAC assist) */ + for (i = 0; i < 0x100; i += 4) + piperp->ac->wr_reg(piperp, i, 0, op_write); + + /* Enable download the MAC Assist program RAM */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_FW_LOAD_ENABLE, op_or); + + /* load MAC Assist data */ + for (i = 0; i < piper_macassist_data_len; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, piper_wifi_macassist_ucode[i], + op_write); + + /* disable MAC Assist download */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_FW_LOAD_ENABLE, op_and); +} + +/* + * Load the DSP firmware into the chip. This is done by setting a bit + * in the control register to enable DSP firmware download, and then + * writing the firmware into the data FIFO. + */ +void piper_load_dsp_firmware(struct piper_priv *piperp) +{ + unsigned int i; + + printk(KERN_DEBUG PIPER_DRIVER_NAME ": loading DSP firmware\n"); + + /* Enable load of DSP firmware */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_DSP_LOAD_ENABLE, op_or); + + /* load DSP data */ + for (i = 0; i < piper_dsp_data_len; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, piper_wifi_dsp_ucode[i], + op_write); + + /* Disable load of DSP firmware */ + udelay(10); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_DSP_LOAD_ENABLE, op_and); + + /* Let her rip */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_MAC_ASSIST_ENABLE, op_or); +} + + +/* + * This routine corrects a bug in the Piper chip where internal clocks would + * be out of sync with each other and cause the chip to generate noise spikes. + * This problem should be fixed in the next chip (Chopper). + * + * I'm not sure exactly what this code is doing. It comes straight from the + * guy who designed the chip. + */ +int piper_spike_suppression(struct piper_priv *piperp, bool retry) +{ + int timeout1 = 300, timeout2 = 300; + int ret = 0; + + /* + * Initial timing measurement to avoid spike + * The new "magic" value is 0x63 at address 0xA62. Bit-0 indicates the + * timing measurement is complete. Bit-1 indicates that a second timing + * measurment was performed. The upper nibble is the timing measurement + * value. This code should eliminate the possibility of spikes at the + * beginning of all PSK/CCK frames and eliminate the spikes at the end of + * all PSK (1M, 2M) frames. + */ + + /* reset the timing value */ + piperp->ac->wr_reg(piperp, MAC_STATUS, 0xffff00ff, op_and); + + while ((piperp->ac->rd_reg(piperp, MAC_STATUS) & 0x0000ff00) != 0x00006300) { + + /* reset the timing value */ + piperp->ac->wr_reg(piperp, MAC_STATUS, 0xffff00ff, op_and); + + /* issue WiFi soft reset */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, 0x40000000, op_write); + + /* Set TX_ON Low */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0xffffff3f, op_and); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x00000080, op_or); + + /* Set PA_2G Low */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0xfffff0ff, op_and); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x00000a00, op_or); + + /* Set RX_ON low */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0xcfffffff, op_and); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x20000000, op_or); + + /* start the WiFi mac & dsp */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720820, op_write); + timeout1 = 500; + + /* Wait for timing measurement to finish */ + while ((piperp->ac->rd_reg(piperp, MAC_STATUS) & 0x0000ff00) != 0x00000100) { + udelay(2); + timeout1--; + if (!timeout1) + break; + } + + timeout2--; + if (!timeout2) { + ret = -EIO; + break; + } + + if (!retry) { + ret = -EIO; + break; + } + } + + /* Set TX_ON/RXHP_ON and RX to normal wifi, restore the reset value to HW_OUT_CTRL */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x1, op_write); + + return ret; +} +EXPORT_SYMBOL_GPL(piper_spike_suppression); + +void piper_reset_mac(struct piper_priv *piperp) +{ + int i; + + /* set the TX-hold bit */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720080, op_write); + + /* clear the TX-FIFO memory */ + for (i = 0; i < 448; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, 0, op_write); + + /* reset the TX-FIFO */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x377200C0, op_write); + + /* release the TX-hold and reset */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720000, op_write); + +/* iowrite32(ioread32(piperp->vbase + MAC_STATUS) & ~0x40000000, + piperp->vbase + BB_GENERAL_STAT);*/ + mdelay(1); +} + +/* + * Load the MAC address into the chip. Use the value stored in the + * environment, if there is one, otherwise use the default value. + */ +void piper_set_macaddr(struct piper_priv *piperp) +{ + /* Default MAC Addr used if the nvram parameters are corrupted */ + u8 mac[6] = {0x00, 0x04, 0xf3, 0x11, 0x43, 0x35}; + u8 *pmac = piperp->pdata->macaddr; + int i; + static bool firstTime = true; + + for (i = 0; i < 6; i++) { + if (*(pmac + i) != 0xff) + break; + if (i == 5) { + /* There is a problem with the parameters, use default */ + if (firstTime) { + printk(KERN_INFO PIPER_DRIVER_NAME + ": invalid mac address, using default\n"); + } + memcpy(piperp->pdata->macaddr, mac, sizeof(piperp->pdata->macaddr)); + } + } + + firstTime = false; + memcpy(piperp->hw->wiphy->perm_addr, piperp->pdata->macaddr, + sizeof(piperp->hw->wiphy->perm_addr)); + + /* configure ethernet address */ + piperp->ac->wr_reg(piperp, MAC_STA_ID0, *(pmac + 3) | *(pmac + 2) << 8 | + *(pmac + 1) << 16 | *(pmac + 0) << 24, op_write); + piperp->ac->wr_reg(piperp, MAC_STA_ID1, *(pmac + 5) << 16 | *(pmac + 4) << 24, + op_write); +} +EXPORT_SYMBOL_GPL(piper_set_macaddr); + + +/* Configure the H/W with the antenna settings */ +static int piper_set_antenna(struct piper_priv *piperp, enum antenna_select sel) +{ + if (sel == ANTENNA_BOTH) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_ANT_DIV, op_or); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_ANT_SEL, op_and); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_ANT_DIV, op_and); + /* select the antenna if !diversity */ + if (sel == ANTENNA_1) + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_ANT_SEL, op_and); + else + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_ANT_SEL, op_or); + } + + /* select which antenna to transmit on */ + piperp->ac->wr_reg(piperp, BB_RSSI, ~BB_RSSI_ANT_MASK, op_and); + if (sel == ANTENNA_BOTH) + piperp->ac->wr_reg(piperp, BB_RSSI, BB_RSSI_ANT_DIV_MAP, op_or); + else + piperp->ac->wr_reg(piperp, BB_RSSI, BB_RSSI_ANT_NO_DIV_MAP, op_or); + + return 0; +} + +/* + * Compute a beacon backoff time as described in section 11.1.2.2 of 802.11 spec. + * + */ +static u16 get_next_beacon_backoff(void) +{ +#define MAX_BEACON_BACKOFF (2 * ASLOT_TIME * DEFAULT_CW_MIN) + + /* + * We shift the result of local_rand() by 4 bits because the notes + * for the algorithm say that we shouldn't rely on the last few + * bits being random. Other than that, we just take the random + * value and make sure it is less than MAX_BEACON_BACKOFF. + */ + return (local_rand() >> 4) % MAX_BEACON_BACKOFF; +} + +static int load_beacon(struct piper_priv *digi, unsigned char *buffer, + unsigned int length) +{ + return digi->ac->wr_fifo(digi, BEACON_FIFO, buffer, length); +} + +static int piper_init_rx_tx(struct piper_priv *piperp) +{ + tasklet_init(&piperp->rx_tasklet, piper_rx_tasklet, (unsigned long)piperp); + tasklet_disable(&piperp->rx_tasklet); + piperp->expectingAck = false; + + spin_lock_init(&piperp->tx_tasklet_lock); + spin_lock_init(&piperp->tx_queue_lock); + piperp->tx_tasklet_running = false; + memset(&piperp->tx_queue, 0, sizeof(piperp->tx_queue)); + piperp->tx_queue_head = 0; + piperp->tx_queue_tail = 0; + piperp->tx_queue_count = 0; + tasklet_init(&piperp->tx_tasklet, piper_tx_tasklet, (unsigned long)piperp); + tasklet_disable(&piperp->tx_tasklet); + + return 0; +} + +static void piper_free_rx_tx(struct piper_priv *piperp) +{ + tasklet_disable(&piperp->rx_tasklet); + tasklet_kill(&piperp->rx_tasklet); + tasklet_disable(&piperp->tx_tasklet); + tasklet_kill(&piperp->tx_tasklet); + piper_empty_tx_queue(piperp); +} + +/* + * This function sets the tracking control according to a channel's + * frequency. + */ +static int piper_set_tracking_constant(struct piper_priv *piperp, unsigned megahertz) +{ + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, ~TRACK_CONSTANT_MASK, op_and); + if (megahertz < 4920) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + } + else if (megahertz <= 4980) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_4920_4980_A_BAND, op_or); + } + else if (megahertz <= 5350) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5150_5350_A_BAND, op_or); + } + else if (megahertz <= 5725) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5470_5725_A_BAND, op_or); + } + else + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5725_5825_A_BAND, op_or); + } + + return 0; +} + +/* + * This function is called to set the value of the B_TX_GAIN field of the + * HW_CONF1 mac register. This register must be set to different values depending + * on the H/W revision of the board due to changes in the board design. + */ +static unsigned int get_b_tx_gain(struct piper_priv *piperp) +{ + u16 platform = piperp->pdata->wcd.header.hw_platform & WCD_PLATFORM_MASK; + u16 hw_revision = piperp->pdata->wcd.header.hw_platform & WCD_HW_REV_MASK; + unsigned int tx_gain = 0; + + switch (platform) { + case WCD_CCW9P_PLATFORM: + tx_gain = TRACK_TX_B_GAIN_NORMAL; + break; + case WCD_CCW9M_PLATFORM: + switch (hw_revision) { + case WCD_HW_REV_PROTOTYPE: + case WCD_HW_REV_PILOT: + tx_gain = 0xc0000000; + break; + case WCD_HW_REV_A: + default: + tx_gain = 0x90000000; + break; + } + break; + } + return tx_gain; +} + + +static int piper_init_hw(struct piper_priv *piperp, enum ieee80211_band band) +{ + int ret = 0; + + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_INIT, op_write); + + /* Initialize baseband general control register for the specific transceiver */ + if (piperp->pdata->rf_transceiver == RF_AIROHA_7230) { + if (band == IEEE80211_BAND_2GHZ) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, GEN_INIT_AIROHA_24GHZ, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xff00ffff, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + digi_dbg("piper_init_hw Initialized for band B / BG\n"); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, GEN_INIT_AIROHA_50GHZ, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xff00ffff, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5150_5350_A_BAND, op_or); + digi_dbg("piper_init_hw Initialized for band A\n"); + } + piperp->ac->wr_reg(piperp, BB_CONF_2, 0x09325ad4, op_write); + /* Initialize the SPI word length */ + piperp->ac->wr_reg(piperp, BB_SPI_CTRL, SPI_INIT_AIROHA, op_write); + } else if (piperp->pdata->rf_transceiver == RF_AIROHA_2236) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, GEN_INIT_AIROHA_24GHZ, op_write); + piperp->ac->wr_reg(piperp, BB_CONF_2, 0x09325ad4, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xff00ffff, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + /* Initialize the SPI word length */ + piperp->ac->wr_reg(piperp, BB_SPI_CTRL, SPI_INIT_AIROHA2236, op_write); + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME ": undefined rf transceiver!\n"); + return -EINVAL; + } + + /* + *Clear the Intretupt Mask Register before enabling external intretupts. + * Also clear out any status bits in the Intretupt Status Register. + */ + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, 0, op_write); + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, 0xff, op_write); + + /* + * If this firmware supports additional MAC addresses. + */ + if (((piperp->ac->rd_reg(piperp, MAC_STATUS) >> 16) & 0xff) >= 8) { + /* Disable additional addresses to start with */ + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_MAC_FLTR, op_and); + piperp->ac->wr_reg(piperp, MAC_STA2_ID0, 0, op_write); + piperp->ac->wr_reg(piperp, MAC_STA2_ID1, 0, op_write); + piperp->ac->wr_reg(piperp, MAC_STA3_ID0, 0, op_write); + piperp->ac->wr_reg(piperp, MAC_STA3_ID1, 0, op_write); + } + /* TODO: Set this register programatically */ + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, 0x0, op_write); + + /* + * Note that antenna diversity will be set by hw_start, which is the + * caller of this function. + */ + + /* reset RX and TX FIFOs */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_RXFIFORST + | BB_GENERAL_CTL_TXFIFORST, op_or); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~(BB_GENERAL_CTL_RXFIFORST + | BB_GENERAL_CTL_TXFIFORST), op_and); + + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xC043002C, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, ~TRACK_TX_B_GAIN_MASK, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, get_b_tx_gain(piperp), op_or); + + /* Initialize RF transceiver */ + piperp->rf->init(piperp->hw, band); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x04000001, op_or); + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, 0x0, op_write); + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, ~(BB_GENERAL_STAT_DC_DIS + | BB_GENERAL_STAT_SPRD_DIS), op_and); + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, ~(BB_GENERAL_STAT_SRC_DIS + | BB_GENERAL_STAT_DLL_DIS), op_and); + + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, (MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK), + op_write); + + /* + * Set BSSID to the broadcast address so that we receive all packets. The stack + * will set a real BSSID when it's ready. + */ + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, 0xffffffff, op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, 0xffffffff, op_write); + + piperp->ps.poweredDown = false; + +#if WANT_AIROHA_CALIBRATION + digi_dbg("Calling digiWifiInitCalibration()\n"); + digiWifiInitCalibration(piperp); +#endif + + return ret; +} + +static int piper_deinit_hw(struct piper_priv *piperp) +{ + int ret = 0; +#if WANT_AIROHA_CALIBRATION + digi_dbg("Calling digiWifiDeInitCalibration()\n"); + digiWifiDeInitCalibration(piperp); +#endif + + return ret; +} + + +static void adjust_max_agc(struct piper_priv *piperp, unsigned int rssi, _80211HeaderType *header) +{ +#define LOWEST_MAXAGC_AL2236 0x76 +#define HIGHEST_MAXAGC_AL2236 0x7B +#define HIGHEST_MAXAGC_AL7230_24GHZ 0x7c +#define LOWEST_MAXAGC_AL7230_24GHZ 0x76 +#define HIGHEST_MAXAGC_AL7230_50GHZ 0x79 +#define LOWEST_MAXAGC_AL7230_50GHZ 0x73 +#define RSSI_AVG_COUNT 8 + + unsigned char maxgain = 0; + static unsigned char lowest = 0, highest = 0; + static int k=0, j=0, i =0, tempRssi=0; + static unsigned int savedRSSI[RSSI_AVG_COUNT]; /****/ + + savedRSSI[k % RSSI_AVG_COUNT] = rssi; + if ( (piperp->pdata->rf_transceiver == RF_AIROHA_2236) + || (piperp->pdata->rf_transceiver == RF_AIROHA_7230)) { + + if (piperp->pdata->rf_transceiver == RF_AIROHA_2236) + { + lowest = LOWEST_MAXAGC_AL2236; + highest = HIGHEST_MAXAGC_AL2236; + } + else + { + + if (piperp->rf->getBand(piperp->channel) == IEEE80211_BAND_5GHZ) { + highest = HIGHEST_MAXAGC_AL7230_50GHZ; + lowest = LOWEST_MAXAGC_AL7230_50GHZ; + } + else { + highest = HIGHEST_MAXAGC_AL7230_24GHZ; + lowest = LOWEST_MAXAGC_AL7230_24GHZ; + } + } + + if (piperp->areWeAssociated) + { + + if ( (piperp->if_type == NL80211_IFTYPE_ADHOC) + || (piperp->if_type == NL80211_IFTYPE_MESH_POINT)) + { + //Monitor the receive signal strength from Ad-hoc network + if (memcmp (piperp->bssid, header->addr3, sizeof(piperp->bssid)) == 0) + { + /* we don't do avareging on all the signals here because it may come from different + * unit in that Ad-hoc network. Instead, we do avareging on the signals with higher rssi + */ + + if ((rssi + 4) > lowest) + { + k++; + tempRssi += rssi; + + if (k >= RSSI_AVG_COUNT) + { + maxgain = (((tempRssi/k) + 4) > highest)? highest : ((tempRssi/k) + 4) ; + k = 0; + tempRssi = 0; + i =0; + } + } + else + { + i++; + if (i >= (RSSI_AVG_COUNT*4)) + { + maxgain = lowest; + i = 0; + } + + } + } + } + else + { + //Monitor the receive signal strength from the frames we received from the associated AP + if (memcmp (piperp->bssid, header->addr2, sizeof(piperp->bssid)) == 0) + { + //averaging all the signals because they come from the same AP + k++; + tempRssi += rssi; + + if (k >= RSSI_AVG_COUNT*2) + { + if (((tempRssi/k) + 4) > lowest) + maxgain = (((tempRssi/k) + 4) > highest)? highest : ((tempRssi/k) + 4) ; + else + maxgain = lowest; + + k = 0; + tempRssi = 0; + } + } + } + j = 0; + } + else + { + j++; + if (j >= (RSSI_AVG_COUNT*4)) + { + maxgain = highest; + j = 0; + } + k = 0; + tempRssi = 0; + } + + if( (maxgain != 0) + && (maxgain != ((piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_MAX_GAIN_MASK) >> 16))) + { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_MAX_GAIN_MASK, op_and); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, (maxgain << 16) & BB_GENERAL_CTL_MAX_GAIN_MASK, op_or); + } + } +} + + +/* Make sure all keys are disabled when we start */ +static void piper_init_keys(struct piper_priv *piperp) +{ + unsigned int i; + + for (i = 0; i < PIPER_MAX_KEYS; i++) + piperp->key[i].valid = false; + + piperp->aes_key_count = 0; +} + +static void tx_timer_timeout(unsigned long arg) +{ + struct piper_priv *piperp = (struct piper_priv *) arg; + + tasklet_hi_schedule(&piperp->tx_tasklet); +} + +/* sysfs entries to get/set antenna mode */ +static ssize_t show_antenna_sel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", piperp->antenna == ANTENNA_BOTH ? "diversity" : + piperp->antenna == ANTENNA_1 ? "primary" : "secondary"); +} + +static ssize_t store_antenna_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + enum antenna_select ant; + size_t len = count; + int ret; + + ant = piperp->antenna; + + if (buf[count - 1] == '\n') + len--; + + /* TODO check also string length */ + if (!strncmp("diversity", buf, len)) + ant = ANTENNA_BOTH; + else if (!strncmp("primary", buf, len)) + ant = ANTENNA_1; + else if (!strncmp("secondary", buf, len)) + ant = ANTENNA_2; + + if (ant != piperp->antenna) { + if ((ret = piperp->set_antenna(piperp, ant)) != 0) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error setting antenna to %d (err: %d)\n", ant, ret); + } else + piperp->antenna = ant; + } + + return count; +} +static DEVICE_ATTR(antenna_sel, S_IWUSR | S_IRUGO, show_antenna_sel, store_antenna_sel); + +static ssize_t show_power_duty(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", piperp->power_duty); +} + +static ssize_t store_power_duty(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#define MINIMUM_DUTY_CYCLE (33) +#define LIMIT_LINEAL_DUTY_CYCLE (75) + struct piper_priv *piperp = dev_get_drvdata(dev); + int pw_duty; + ssize_t ret = -EINVAL; + + ret = sscanf(buf, "%d\n", &pw_duty); + if (ret > 0) { + if (pw_duty < MINIMUM_DUTY_CYCLE) { + piperp->power_duty = MINIMUM_DUTY_CYCLE; + } else if (pw_duty > LIMIT_LINEAL_DUTY_CYCLE && pw_duty < 100) { + piperp->power_duty = LIMIT_LINEAL_DUTY_CYCLE; + } else if (pw_duty == 100 || + (pw_duty >= MINIMUM_DUTY_CYCLE && pw_duty <= LIMIT_LINEAL_DUTY_CYCLE)) { + piperp->power_duty = pw_duty; + } + } + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR(power_duty, S_IWUSR | S_IRUGO, show_power_duty, store_power_duty); + +#if WANT_DEBUG_COMMANDS + +static ssize_t show_debug_cmd(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", piperp->debug_cmd); +} + +static ssize_t store_debug_cmd(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + ssize_t ret = -EINVAL; + + if (strlen(buf) < sizeof(piperp->debug_cmd)) + { + if (strstr(buf, "dump") != NULL) { + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + ret = 1; + } else if (strstr(buf, "ps_state") != NULL) { + printk(KERN_ERR "rxTaskletRunning = %d, allowTransmits = %d, stopped_tx_queues = %d\n", + piperp->ps.rxTaskletRunning, piperp->ps.allowTransmits, piperp->ps.stopped_tx_queues); + ret = 1; + } else if (strstr(buf, "rssi_dump") != NULL) { + spinlock_t lock; + unsigned long flags; + unsigned int rssi; + + spin_lock_init(&lock); + spin_lock_irqsave(&piperp->ps.lock, flags); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RX_EN, op_and); + udelay(15); + rssi = piperp->ac->rd_reg(piperp, BB_RSSI); + printk(KERN_ERR "\n**rssi = 0x%8.8X\n", rssi); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + ret = 1; + spin_unlock_irqrestore(&lock, flags); + } else { + strcpy(piperp->debug_cmd, buf); + piperp->debug_cmd[strlen(buf)-1] = 0; /* truncate the \n */ + } + ret = count; + } + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR(debug_cmd, S_IWUSR | S_IRUGO, show_debug_cmd, store_debug_cmd); + +#endif + +#ifdef CONFIG_PM + +static int piper_suspend(struct platform_device *dev, pm_message_t state) +{ + struct piper_priv *piperp = platform_get_drvdata(dev); + unsigned long flags; + + /* TODO, use in future the ps.lock instead of fully disabling interrupts here */ + piperp->power_save_was_on_when_suspended = (piperp->ps.mode == PS_MODE_LOW_POWER); + if (piperp->power_save_was_on_when_suspended) + piper_ps_set(piperp, false); + mdelay(10); + piper_sendNullDataFrame(piperp, true); + ssleep(1); + + local_irq_save(flags); + /* + * Save power save state and then make sure power save is turned off. + */ + piper_MacEnterSleepMode(piperp, true); + local_irq_restore(flags); + + return 0; +} + +static int piper_resume(struct platform_device *dev) +{ + struct piper_priv *piperp = platform_get_drvdata(dev); + unsigned long flags; + + if (piperp->pdata->early_resume) + piperp->pdata->early_resume(piperp); + + /* TODO, use in future the ps.lock instead of fully disabling interrupts here */ + local_irq_save(flags); + piper_MacEnterActiveMode(piperp, true); + if (piperp->tx_tasklet_running) { + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + ieee80211_wake_queues(piperp->hw); + } + local_irq_restore(flags); + + /* + * Restore power save if it was on before + */ + if (piperp->power_save_was_on_when_suspended) { + piper_ps_set(piperp, true); + } else { + piper_sendNullDataFrame(piperp, false); + } + + return 0; +} +#else +#define piper_suspend NULL +#define piper_resume NULL +#endif + +static int __init piper_probe(struct platform_device* pdev) +{ + struct piper_pdata *pdata = pdev->dev.platform_data; + struct piper_priv *piperp; + int ret = 0; + + if (!pdata) + return -EINVAL; + + ret = piper_alloc_hw(&piperp, sizeof(*piperp)); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to alloc piper_priv\n"); + return ret; + } + + piperp->ac = kzalloc(sizeof(struct access_ops), GFP_KERNEL); + if (!piperp->ac){ + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to alloc memory for ac struct\n"); + ret = -ENOMEM; + goto error_alloc; + } + + piperp->drv_name = PIPER_DRIVER_NAME; + dev_set_drvdata(&pdev->dev, piperp); + piperp->pdata = pdata; + pdata->piperp = piperp; + spin_lock_init(&piperp->ac->reg_lock); + spin_lock_init(&piperp->aesLock); + + piperp->vbase = ioremap(pdev->resource[0].start, + pdev[0].resource->end - pdev->resource[0].start); + + if (!piperp->vbase) { + printk(KERN_ERR PIPER_DRIVER_NAME ": ioremap base %x, len %x error\n", + pdev->resource[0].start, pdev[0].resource->end - pdev->resource[0].start); + ret = -ENOMEM; + goto error_remap; + } + + piperp->pstats.tx_start_count = 0; + piperp->pstats.tx_complete_count = 0; + + /* + * Platform initialization. This will initialize the hardware, including the load + * of the mac and dsp firmware into the piper chip + */ + if (pdata->init) { + if ((ret = pdata->init(piperp)) != 0) { + printk(KERN_ERR PIPER_DRIVER_NAME + ": platform init() returned error (%d)\n", ret); + goto error_init; + } + } + + piper_ps_init(piperp); + init_timer(&piperp->tx_timer); + piperp->tx_timer.function = tx_timer_timeout; + piperp->tx_timer.data = (unsigned long) piperp; + piper_init_rx_tx(piperp); + piper_init_keys(piperp); + + piperp->init_hw = piper_init_hw; + piperp->deinit_hw = piper_deinit_hw; + piperp->set_irq_mask_bit = piper_set_irq_mask; + piperp->clear_irq_mask_bit = piper_clear_irq_mask; + piperp->load_beacon = load_beacon; + piperp->rand = local_rand; + piperp->get_next_beacon_backoff = get_next_beacon_backoff; + piperp->set_antenna = piper_set_antenna; + piperp->set_tracking_constant = piper_set_tracking_constant; + piperp->antenna = ANTENNA_1; + piperp->adjust_max_agc = adjust_max_agc; + + /* + * Set the default duty cycle value. Note that duty cycling + * is disabled reguardless of what this variable is set to until + * the user types "iwconfig wlan0 power on". I just love the + * "power on" syntax to turn *down* the power. + */ + piperp->power_duty = 100; + + /* TODO this should be read earlier and actions should be taken + * based on different revisions at driver initialization or runtime */ + piperp->version = piperp->ac->rd_reg(piperp, BB_VERSION); + + piperp->irq = pdev->resource[1].start; + piperp->tx_cts = false; + piperp->beacon.loaded = false; + piperp->beacon.enabled = false; + piperp->beacon.weSentLastOne = false; + + ret = request_irq(piperp->irq, piper_irq_handler, + IRQF_TRIGGER_HIGH, PIPER_DRIVER_NAME, piperp); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": unable to request irq %d (%d)", + piperp->irq, ret); + goto retor_irq; + } + + disable_irq(piperp->irq); + + ret = piper_register_hw(piperp, &pdev->dev, &al7230_rf_ops); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to register priv\n"); + goto error_reg_hw; + } + + if (pdata->late_init) + pdata->late_init(piperp); + + ret = device_create_file(&pdev->dev, &dev_attr_antenna_sel); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to create sysfs file\n"); + goto error_sysfs; + } + + ret = device_create_file(&pdev->dev, &dev_attr_power_duty); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to create sysfs file\n"); + goto error_sysfs; + } + + strcpy(piperp->debug_cmd, "off"); +#if WANT_DEBUG_COMMANDS + ret = device_create_file(&pdev->dev, &dev_attr_debug_cmd); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to create sysfs file\n"); + goto error_sysfs; + } +#endif + + printk(KERN_INFO PIPER_DRIVER_NAME ": driver loaded (fw ver = 0x%08x)\n", + piperp->version); + + return 0; + +error_sysfs: + piper_unregister_hw(piperp); +error_reg_hw: + piper_ps_deinit(piperp); + piper_free_rx_tx(piperp); +retor_irq: + free_irq(piperp->irq, piperp); +error_init: + iounmap(piperp->vbase); + piperp->vbase = NULL; +error_remap: + release_resource(pdev->resource); +error_alloc: + piper_free_hw(piperp); + return ret; +} + +static int piper_remove(struct platform_device *pdev) +{ + struct piper_priv *piperp = dev_get_drvdata(&pdev->dev); + + printk(KERN_DEBUG PIPER_DRIVER_NAME " %s\n", __func__); + + device_remove_file(&pdev->dev, &dev_attr_antenna_sel); + device_remove_file(&pdev->dev, &dev_attr_power_duty); +#if WANT_DEBUG_COMMANDS + device_remove_file(&pdev->dev, &dev_attr_debug_cmd); +#endif + + piper_ps_deinit(piperp); + piper_unregister_hw(piperp); + disable_irq(piperp->irq); + piper_clear_irq_mask(piperp, 0xffffffff); + free_irq(piperp->irq, piperp); + piper_free_rx_tx(piperp); + release_resource(pdev->resource); + piper_free_hw(piperp); + + return 0; +} + +/* describes the driver */ +static struct platform_driver piper_driver = { + .probe = piper_probe, + .remove = piper_remove, + .suspend = piper_suspend, + .resume = piper_resume, + .driver = { + .name = PIPER_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init piper_init_module(void) +{ + return platform_driver_register(&piper_driver); +} + +static void __exit piper_exit_module(void) +{ + platform_driver_unregister(&piper_driver); +} + +module_init(piper_init_module); +module_exit(piper_exit_module); + +MODULE_DESCRIPTION("Digi Piper WLAN Driver"); +MODULE_AUTHOR("Contact support@digi.com for questions on this code"); +MODULE_VERSION(DRV_VERS); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/digiPiper/piperDsp.c b/drivers/net/wireless/digiPiper/piperDsp.c new file mode 100644 index 000000000000..0f3d0e1b9b5e --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperDsp.c @@ -0,0 +1,285 @@ +/* + Copyright (c) 2007-2008 Digi International Inc., All Rights Reserved + + This software contains proprietary and confidential information of Digi + International Inc. By accepting transfer of this copy, Recipient agrees + to retain this software in confidence, to prevent disclosure to others, + and to make no use of this software other than that for which it was + delivered. This is an unpublished copyrighted work of Digi International + Inc. Except as permitted by federal law, 17 USC 117, copying is strictly + prohibited. + + Restricted Rights Legend + + Use, duplication, or disclosure by the Government is subject to + restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in + Technical Data and Computer Software clause at DFARS 252.227-7031 or + subparagraphs (c)(1) and (2) of the Commercial Computer Software - + Restricted Rights at 48 CFR 52.227-19, as applicable. + + Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + + WiFi DSP Code for Piper +*/ + + +const unsigned long piper_wifi_dsp_ucode[1024] = { + 0x32000, 0x3200F, 0x320AB, 0x0403F, + 0x0C000, 0x38000, 0x38060, 0x10004, + 0x10008, 0x10009, 0x1000A, 0x1000B, + 0x10001, 0x10010, 0x08000, 0x08000, + 0x10001, 0x08000, 0x08000, 0x08000, + 0x08000, 0x08000, 0x08000, 0x08000, + 0x32030, 0x39010, 0x39010, 0x30802, + 0x2B030, 0x10001, 0x10800, 0x38000, + 0x10004, 0x10040, 0x11002, 0x320D1, + 0x041AD, 0x0C000, 0x38000, 0x38160, + 0x38400, 0x061AD, 0x38100, 0x39171, + 0x39571, 0x32072, 0x11002, 0x320E1, + 0x38100, 0x39171, 0x39571, 0x36002, + 0x29039, 0x38000, 0x11002, 0x38000, + 0x38060, 0x10001, 0x083D5, 0x10050, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10104, 0x10013, 0x321D1, 0x0422D, + 0x0E000, 0x38000, 0x38260, 0x38400, + 0x0622D, 0x0C000, 0x38200, 0x39271, + 0x39671, 0x32002, 0x10003, 0x321E1, + 0x38200, 0x39271, 0x39671, 0x10013, + 0x321E1, 0x38200, 0x39271, 0x39671, + 0x36002, 0x29034, 0x38000, 0x10003, + 0x10001, 0x38060, 0x083B1, 0x10050, + 0x10104, 0x10040, 0x10213, 0x320D1, + 0x0422D, 0x0E000, 0x38000, 0x38260, + 0x38400, 0x0622D, 0x0C000, 0x38200, + 0x39271, 0x39671, 0x10003, 0x321E1, + 0x38200, 0x39271, 0x39671, 0x10013, + 0x321E1, 0x38200, 0x39271, 0x39671, + 0x10003, 0x10001, 0x38060, 0x10004, + 0x10000, 0x08392, 0x10010, 0x08380, + 0x30802, 0x29003, 0x37012, 0x082BF, + 0x38000, 0x29002, 0x37012, 0x08078, + 0x29002, 0x37012, 0x08105, 0x29002, + 0x37012, 0x081C2, 0x29002, 0x37172, + 0x0822F, 0x04236, 0x10801, 0x29002, + 0x370F2, 0x0838A, 0x29002, 0x370F2, + 0x083A7, 0x29002, 0x370F2, 0x083C4, + 0x38000, 0x08002, 0x38000, 0x38000, + 0x30822, 0x10104, 0x10040, 0x10213, + 0x320D1, 0x0422D, 0x0E000, 0x38000, + 0x38260, 0x38400, 0x0622D, 0x0C000, + 0x38200, 0x39271, 0x39671, 0x10003, + 0x321E1, 0x38200, 0x39271, 0x39671, + 0x10013, 0x321E1, 0x38200, 0x39271, + 0x39671, 0x10003, 0x38000, 0x38060, + 0x36002, 0x2A005, 0x0422D, 0x38400, + 0x0402D, 0x38060, 0x38400, 0x10004, + 0x10000, 0x10001, 0x08349, 0x10010, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x3601F, 0x30033, 0x23103, 0x38000, + 0x08030, 0x38000, 0x08031, 0x38000, + 0x327F0, 0x300F1, 0x37091, 0x21034, + 0x30803, 0x300F2, 0x23009, 0x32081, + 0x10002, 0x04180, 0x0C100, 0x38000, + 0x38040, 0x04400, 0x320A1, 0x38060, + 0x04900, 0x0E630, 0x38000, 0x38000, + 0x38060, 0x38000, 0x2103E, 0x38060, + 0x04000, 0x0C000, 0x300F1, 0x22004, + 0x37041, 0x23003, 0x04401, 0x38000, + 0x38020, 0x3601F, 0x30033, 0x2310C, + 0x38000, 0x082EA, 0x10010, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x38000, 0x10800, 0x38000, + 0x300F2, 0x32081, 0x35021, 0x20017, + 0x300F2, 0x37052, 0x28036, 0x0440A, + 0x0C000, 0x38000, 0x38060, 0x30011, + 0x20004, 0x0440A, 0x0E000, 0x2103F, + 0x38060, 0x05410, 0x0E000, 0x38000, + 0x38060, 0x08005, 0x38000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x04000, + 0x10008, 0x05C02, 0x0C000, 0x38000, + 0x38020, 0x04C12, 0x0E000, 0x10002, + 0x38020, 0x0C209, 0x04914, 0x38000, + 0x0C409, 0x38040, 0x38000, 0x38000, + 0x38020, 0x32011, 0x36001, 0x0E000, + 0x05E02, 0x38000, 0x38120, 0x0E201, + 0x04914, 0x38000, 0x0E401, 0x38040, + 0x38000, 0x38020, 0x21034, 0x38000, + 0x10202, 0x04924, 0x38000, 0x38400, + 0x10202, 0x041A4, 0x0C100, 0x38060, + 0x04424, 0x38060, 0x04924, 0x0E630, + 0x320A1, 0x38000, 0x38060, 0x38000, + 0x2103E, 0x38060, 0x04403, 0x0C000, + 0x300F0, 0x38020, 0x0E000, 0x37030, + 0x39030, 0x39030, 0x0401B, 0x10401, + 0x38040, 0x10001, 0x05C1C, 0x38000, + 0x38040, 0x08282, 0x10010, 0x00000, + 0x10023, 0x10104, 0x0422D, 0x0C080, + 0x32180, 0x38260, 0x0E080, 0x38200, + 0x39270, 0x39270, 0x05C2E, 0x0C000, + 0x38060, 0x100D3, 0x0422D, 0x0C080, + 0x32180, 0x38260, 0x0E080, 0x38200, + 0x39270, 0x39270, 0x05C2E, 0x0E000, + 0x38060, 0x10004, 0x10003, 0x10402, + 0x0422D, 0x0C000, 0x323F0, 0x38200, + 0x39270, 0x39770, 0x04401, 0x0C000, + 0x1000B, 0x38020, 0x06409, 0x38020, + 0x38020, 0x38020, 0x1000A, 0x08254, + 0x10010, 0x38000, 0x38000, 0x38000, + 0x10462, 0x10014, 0x041AD, 0x0C000, + 0x32180, 0x38160, 0x0E000, 0x38100, + 0x39170, 0x39170, 0x05C2E, 0x0C000, + 0x38060, 0x10512, 0x041AD, 0x0C000, + 0x32180, 0x38160, 0x0E000, 0x38100, + 0x39170, 0x39170, 0x05C2E, 0x0E000, + 0x38060, 0x10004, 0x05036, 0x0E008, + 0x10212, 0x38060, 0x38400, 0x04427, + 0x0C000, 0x38060, 0x0403F, 0x38000, + 0x38020, 0x0493F, 0x0E630, 0x32051, + 0x38000, 0x38060, 0x38000, 0x2103E, + 0x38060, 0x300F1, 0x37061, 0x0443E, + 0x0C000, 0x38020, 0x0643F, 0x0C000, + 0x38020, 0x38020, 0x38020, 0x0563F, + 0x0E000, 0x2000A, 0x38000, 0x21004, + 0x38020, 0x04C37, 0x08014, 0x38020, + 0x2000C, 0x38000, 0x21006, 0x38000, + 0x04637, 0x38020, 0x05C37, 0x0800B, + 0x38020, 0x38020, 0x21005, 0x04437, + 0x38020, 0x05637, 0x08004, 0x38020, + 0x05C37, 0x38020, 0x38000, 0x11102, + 0x041B6, 0x0C000, 0x38000, 0x38020, + 0x04E37, 0x0E000, 0x38020, 0x05C3F, + 0x0C000, 0x38020, 0x04438, 0x0E000, + 0x38020, 0x04000, 0x38000, 0x10008, + 0x04401, 0x0C000, 0x38020, 0x06439, + 0x0E000, 0x38020, 0x38020, 0x38020, + 0x38020, 0x38020, 0x38020, 0x04439, + 0x38000, 0x38020, 0x04409, 0x0C000, + 0x1000B, 0x06409, 0x38020, 0x38020, + 0x38020, 0x1000A, 0x10212, 0x04136, + 0x0C800, 0x38000, 0x38060, 0x11112, + 0x041B6, 0x0E000, 0x38000, 0x38060, + 0x25004, 0x38000, 0x10800, 0x38000, + 0x38000, 0x10400, 0x081CD, 0x38000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10422, 0x10023, 0x10114, 0x04BAD, + 0x0C080, 0x32190, 0x38300, 0x39370, + 0x39F70, 0x104D2, 0x100D3, 0x32190, + 0x38300, 0x39370, 0x39F70, 0x38000, + 0x10433, 0x0422D, 0x0C000, 0x32030, + 0x39270, 0x39A10, 0x10063, 0x10334, + 0x32030, 0x39270, 0x39A10, 0x10512, + 0x041AD, 0x32020, 0x39170, 0x39510, + 0x10782, 0x10114, 0x32020, 0x39170, + 0x39510, 0x104D3, 0x0422D, 0x0C070, + 0x10334, 0x38260, 0x0E070, 0x32060, + 0x39210, 0x39270, 0x107B2, 0x104D3, + 0x10020, 0x05BAD, 0x0E060, 0x32190, + 0x38300, 0x39370, 0x39B70, 0x13FC3, + 0x0422D, 0x0C070, 0x10114, 0x38260, + 0x0E070, 0x32060, 0x39210, 0x39270, + 0x104E2, 0x13FC3, 0x05BAD, 0x0E060, + 0x32190, 0x38300, 0x39370, 0x39B70, + 0x10004, 0x08326, 0x10000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10114, 0x10173, 0x10902, 0x0616D, + 0x0C800, 0x321B0, 0x38260, 0x38260, + 0x38260, 0x39270, 0x39770, 0x0416D, + 0x321B0, 0x10173, 0x10912, 0x38260, + 0x38260, 0x38260, 0x39270, 0x39770, + 0x10003, 0x10C42, 0x0616D, 0x0C800, + 0x321B0, 0x38260, 0x38260, 0x38260, + 0x39270, 0x39770, 0x0416D, 0x321B0, + 0x10003, 0x10C52, 0x38260, 0x38260, + 0x38260, 0x39270, 0x39770, 0x32028, + 0x0C070, 0x10973, 0x0423F, 0x38060, + 0x10B33, 0x0E070, 0x108E3, 0x38060, + 0x38060, 0x0E060, 0x10AA3, 0x38000, + 0x38060, 0x0C000, 0x0503F, 0x10222, + 0x38400, 0x080C6, 0x38000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x108D3, 0x10104, 0x10140, 0x040F6, + 0x0C202, 0x32181, 0x38200, 0x38080, + 0x382C0, 0x380A0, 0x216FF, 0x380A0, + 0x10823, 0x0C202, 0x32181, 0x38200, + 0x38080, 0x382C0, 0x380A0, 0x216FF, + 0x380A0, 0x080FA, 0x10010, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10902, 0x108D3, 0x10114, 0x10140, + 0x047F6, 0x0D202, 0x32181, 0x100E1, + 0x38280, 0x38040, 0x381A0, 0x216FE, + 0x38440, 0x10C42, 0x10823, 0x047F6, + 0x0D202, 0x32171, 0x38280, 0x38040, + 0x381A0, 0x216FE, 0x38440, 0x381A0, + 0x386E0, 0x321A1, 0x2103F, 0x10001, + 0x38400, 0x080D2, 0x10010, 0x00000, + 0x38000, 0x38000, 0x38000, 0x0423F, + 0x10973, 0x0C000, 0x38060, 0x10B33, + 0x0E000, 0x108E3, 0x38060, 0x38060, + 0x0E030, 0x10AA3, 0x38000, 0x38060, + 0x18083, 0x0C0C0, 0x0443F, 0x36018, + 0x38060, 0x21002, 0x0C000, 0x32018, + 0x0503F, 0x10222, 0x38400, 0x041A4, + 0x0C100, 0x38060, 0x04424, 0x38060, + 0x048A4, 0x0E630, 0x32061, 0x38000, + 0x38060, 0x38000, 0x2103E, 0x38060, + 0x10080, 0x04401, 0x0C000, 0x38020, + 0x06639, 0x0E000, 0x38020, 0x04409, + 0x0C000, 0x1000B, 0x06409, 0x38020, + 0x38020, 0x38020, 0x1000A, 0x30811, + 0x22007, 0x37021, 0x20003, 0x38000, + 0x08383, 0x38000, 0x083A1, 0x38000, + 0x10902, 0x108D3, 0x10104, 0x10140, + 0x047F6, 0x0D202, 0x32181, 0x100E1, + 0x38080, 0x38240, 0x381A0, 0x38460, + 0x214FD, 0x38740, 0x10C42, 0x10823, + 0x047F6, 0x0D202, 0x32171, 0x38080, + 0x38240, 0x381A0, 0x38460, 0x214FD, + 0x38740, 0x381A0, 0x38460, 0x384E0, + 0x38000, 0x38000, 0x38000, 0x38000, + 0x38000, 0x38000, 0x38000, 0x38000, + 0x38000, 0x38400, 0x10001, 0x08068, + 0x10010, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x041A4, 0x0C100, 0x38000, 0x38060, + 0x04424, 0x32061, 0x38060, 0x04924, + 0x0E630, 0x38000, 0x38000, 0x38060, + 0x38000, 0x2103E, 0x38060, 0x10080, + 0x108D3, 0x10104, 0x10040, 0x040F6, + 0x0C202, 0x32181, 0x38200, 0x38200, + 0x38200, 0x21240, 0x21640, 0x10823, + 0x32181, 0x38200, 0x38200, 0x38200, + 0x21240, 0x21640, 0x04401, 0x0C000, + 0x38020, 0x06639, 0x0E000, 0x38020, + 0x04409, 0x0C000, 0x1000B, 0x06409, + 0x38020, 0x38020, 0x38020, 0x1000A, + 0x0801F, 0x10010, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000 +}; + +const int piper_dsp_data_len = (sizeof piper_wifi_dsp_ucode)/4; diff --git a/drivers/net/wireless/digiPiper/piperDsp.h b/drivers/net/wireless/digiPiper/piperDsp.h new file mode 100644 index 000000000000..836ffcb96341 --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperDsp.h @@ -0,0 +1,8 @@ +#ifndef PIPER_DSP_H_ +#define PIPER_DSP_H_ + + +extern const unsigned long piper_wifi_dsp_ucode[]; +extern const int piper_dsp_data_len; + +#endif diff --git a/drivers/net/wireless/digiPiper/piperMacAssist.c b/drivers/net/wireless/digiPiper/piperMacAssist.c new file mode 100644 index 000000000000..1a8ada81b84b --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperMacAssist.c @@ -0,0 +1,285 @@ +/* + Copyright (c) 2007-2008 Digi International Inc., All Rights Reserved + + This software contains proprietary and confidential information of Digi + International Inc. By accepting transfer of this copy, Recipient agrees + to retain this software in confidence, to prevent disclosure to others, + and to make no use of this software other than that for which it was + delivered. This is an unpublished copyrighted work of Digi International + Inc. Except as permitted by federal law, 17 USC 117, copying is strictly + prohibited. + + Restricted Rights Legend + + Use, duplication, or disclosure by the Government is subject to + restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in + Technical Data and Computer Software clause at DFARS 252.227-7031 or + subparagraphs (c)(1) and (2) of the Commercial Computer Software - + Restricted Rights at 48 CFR 52.227-19, as applicable. + + Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + + WiFi MAC assist Code for Piper +*/ + +const unsigned long piper_wifi_macassist_ucode[2048] = { + 0x00F00, 0x2CF22, 0x3033B, 0x00E20, 0x2CED0, 0x2CFD2, 0x2CFD8, 0x2CFD9, + 0x2CF31, 0x2CFDB, 0x00E20, 0x2CEDA, 0x00E64, 0x2CE1D, 0x00EFF, 0x2CEEA, + 0x2CEEB, 0x00E01, 0x2CE20, 0x00E10, 0x2CE21, 0x04522, 0x00603, 0x2C6CE, + 0x00001, 0x2C040, 0x00000, 0x2C041, 0x2C042, 0x2C043, 0x0016E, 0x2C144, + 0x00000, 0x0010A, 0x00200, 0x0A5FF, 0x35027, 0x0010B, 0x00280, 0x2C245, + 0x2C146, 0x2C047, 0x00180, 0x2C148, 0x2C049, 0x2C0CE, 0x00000, 0x00100, + 0x00223, 0x040C0, 0x302E2, 0x3031A, 0x00030, 0x0011C, 0x00801, 0x042C0, + 0x0A204, 0x35037, 0x042C0, 0x0A5FF, 0x35040, 0x00031, 0x00117, 0x00806, + 0x30338, 0x1C001, 0x35C40, 0x1C101, 0x35C43, 0x00000, 0x042C0, 0x043C0, + 0x044C0, 0x045C0, 0x046C0, 0x047C0, 0x00000, 0x1C801, 0x35C4D, 0x048C1, + 0x049C1, 0x04AC1, 0x04BC1, 0x04CC1, 0x04DC1, 0x0D320, 0x0D430, 0x0D540, + 0x0D650, 0x0D760, 0x19230, 0x19240, 0x19250, 0x19260, 0x19270, 0x0A880, + 0x35062, 0x18208, 0x0A980, 0x35065, 0x18208, 0x0AA80, 0x35068, 0x18208, + 0x0AB80, 0x3506B, 0x18208, 0x0AC80, 0x3506E, 0x18208, 0x0AD80, 0x35071, + 0x18208, 0x20206, 0x04122, 0x0A1FF, 0x3547B, 0x2C222, 0x0E260, 0x35015, + 0x0E260, 0x00151, 0x3407C, 0x00103, 0x19120, 0x2C122, 0x00E00, 0x2CECE, + 0x3C001, 0x04EC3, 0x0AE20, 0x04FC2, 0x0AF80, 0x0DEF0, 0x04F30, 0x2CE30, + 0x0FFE0, 0x35095, 0x0EE20, 0x35495, 0x3C000, 0x04EC1, 0x0AE80, 0x3548D, + 0x04E17, 0x2CED4, 0x04E16, 0x2CED6, 0x3C001, 0x04E27, 0x0AE10, 0x04F33, + 0x2CE33, 0x0EF10, 0x0BFE0, 0x350AC, 0x00E00, 0x00FE0, 0x2DEF0, 0x18F01, + 0x0EFE8, 0x350A4, 0x0EFE8, 0x3409E, 0x04F1D, 0x00E00, 0x1DEF0, 0x2CEEA, + 0x04F1C, 0x00E00, 0x1FEF0, 0x2CEEB, 0x04E27, 0x0AE80, 0x350C8, 0x3C000, + 0x00E01, 0x2CECE, 0x04EC1, 0x0AE80, 0x354B2, 0x04EE0, 0x2CE27, 0x04EE1, + 0x2CE26, 0x04EE2, 0x2CE25, 0x04EE3, 0x2CE24, 0x04EE4, 0x2CE23, 0x04EE5, + 0x2CE22, 0x04EE6, 0x04FE7, 0x2CE21, 0x2CF20, 0x00E00, 0x2CECE, 0x3C001, + 0x04E35, 0x04F36, 0x0DEF0, 0x3507E, 0x04E3A, 0x04FEB, 0x15EF0, 0x3547E, + 0x04FEA, 0x04E37, 0x15EF0, 0x3547E, 0x3C000, 0x04EC1, 0x0AE80, 0x354D5, + 0x04E36, 0x12EFF, 0x354E7, 0x04F35, 0x14F40, 0x35CE7, 0x00E00, 0x2CED8, + 0x20F06, 0x20F06, 0x2CFD9, 0x3C001, 0x2CE36, 0x2CE35, 0x3407E, 0x04F35, + 0x1CF01, 0x1EE00, 0x2CF35, 0x2CE36, 0x00E0F, 0x2CED8, 0x2CED9, 0x3C001, + 0x04E37, 0x04F3A, 0x18E01, 0x1AF00, 0x2CE37, 0x2CF3A, 0x3407E, 0x2CF32, + 0x04FC0, 0x20F0E, 0x3190B, 0x20F0E, 0x31BFC, 0x20F0E, 0x3195B, 0x20F0E, + 0x31A72, 0x20F0E, 0x31ACA, 0x20F0E, 0x319DC, 0x20F0E, 0x319E3, 0x20F0E, + 0x31A63, 0x04F32, 0x38001, 0x04031, 0x0C001, 0x2C031, 0x040C2, 0x0A008, + 0x35120, 0x04031, 0x0A004, 0x35517, 0x04031, 0x0C080, 0x2C031, 0x302E2, + 0x040C3, 0x0A010, 0x3511C, 0x00016, 0x18011, 0x00100, 0x00200, 0x3031A, + 0x302FF, 0x0E3FF, 0x35127, 0x04031, 0x0A0BF, 0x2C031, 0x3412A, 0x04031, + 0x0C040, 0x2C031, 0x04244, 0x040C3, 0x0A010, 0x35547, 0x302E8, 0x00400, + 0x04510, 0x0A5FF, 0x35534, 0x0C515, 0x13350, 0x3513B, 0x01430, 0x1232F, + 0x35544, 0x2030E, 0x34134, 0x1232F, 0x3553F, 0x2030E, 0x34134, 0x124FF, + 0x35544, 0x2030E, 0x35D34, 0x00401, 0x302F4, 0x2C33B, 0x2A000, 0x04511, + 0x1426E, 0x3514F, 0x14237, 0x35152, 0x14214, 0x35155, 0x34158, 0x0026E, + 0x12508, 0x35559, 0x00237, 0x12504, 0x35559, 0x00214, 0x12502, 0x35559, + 0x0020A, 0x2C23B, 0x2A000, 0x04127, 0x00001, 0x2C0CE, 0x040C2, 0x0A040, + 0x35163, 0x00005, 0x2C0CE, 0x0A110, 0x35192, 0x04048, 0x0E080, 0x3516C, + 0x04048, 0x0E050, 0x3516C, 0x34192, 0x00012, 0x04140, 0x0E1EE, 0x35175, + 0x041C1, 0x0A120, 0x35574, 0x18020, 0x18007, 0x302FF, 0x00100, 0x19030, + 0x1A100, 0x30338, 0x042E0, 0x043E1, 0x044E2, 0x045E3, 0x046E4, 0x047E5, + 0x048E6, 0x049E7, 0x19200, 0x1B310, 0x1A400, 0x1A500, 0x1A600, 0x1A700, + 0x1A800, 0x1A900, 0x2C260, 0x2C361, 0x2C462, 0x2C563, 0x2C664, 0x2C765, + 0x2C866, 0x2C967, 0x0002C, 0x00100, 0x042C2, 0x044C1, 0x0A480, 0x35595, + 0x2C0D0, 0x2C1D2, 0x043C2, 0x0A3E0, 0x2C3C2, 0x04140, 0x04348, 0x00000, + 0x2C0CE, 0x04031, 0x0A0FB, 0x2C031, 0x0A204, 0x355B6, 0x0A30F, 0x0E304, + 0x351B6, 0x04012, 0x0A001, 0x351B6, 0x0E1EE, 0x355AF, 0x0C004, 0x0C00A, + 0x2C012, 0x00064, 0x1C001, 0x355B2, 0x00040, 0x2C0C4, 0x040C2, 0x0A040, + 0x2B400, 0x0403E, 0x0A0FF, 0x2B400, 0x00001, 0x2C0CE, 0x04040, 0x0A0FF, + 0x351C5, 0x0E001, 0x351C5, 0x0E0EF, 0x355D4, 0x04042, 0x0A0FF, 0x351CB, + 0x0E001, 0x355D4, 0x341CE, 0x04041, 0x0A0FF, 0x351D4, 0x04043, 0x0A0FF, + 0x355D4, 0x00000, 0x2C0CE, 0x2A000, 0x00080, 0x2C0C4, 0x0403F, 0x18001, + 0x2C03F, 0x00000, 0x2C0CE, 0x2A000, 0x00008, 0x2C0C4, 0x00100, 0x2C13E, + 0x00012, 0x00200, 0x3431A, 0x00101, 0x2C1CE, 0x0402B, 0x12001, 0x35203, + 0x04128, 0x121FF, 0x351EE, 0x1C101, 0x2C128, 0x35603, 0x04129, 0x0422D, + 0x0432C, 0x0442F, 0x0452E, 0x12002, 0x351F7, 0x2C128, 0x341F9, 0x0A0FE, + 0x2C02B, 0x00000, 0x2C0CE, 0x0401D, 0x1D200, 0x0401C, 0x1F300, 0x2C237, + 0x2C33A, 0x2C435, 0x2C536, 0x00000, 0x2C0CE, 0x04027, 0x0A010, 0x35236, + 0x3033B, 0x040D5, 0x2C02B, 0x040D7, 0x2C02A, 0x00100, 0x0401F, 0x1D100, + 0x2C1E8, 0x00100, 0x0401E, 0x1F100, 0x2C1E9, 0x040C2, 0x0A004, 0x3561D, + 0x3033B, 0x00004, 0x2C0D0, 0x00000, 0x2C0D2, 0x04027, 0x0A004, 0x3522C, + 0x30338, 0x30338, 0x040C2, 0x0C080, 0x2C0C2, 0x040C2, 0x0A040, 0x3522C, + 0x0402F, 0x2C0D4, 0x0402E, 0x2C0D6, 0x00110, 0x040C1, 0x0A010, 0x35234, + 0x04031, 0x0D010, 0x2C031, 0x34246, 0x2C1C4, 0x34246, 0x00010, 0x2C0C4, + 0x04034, 0x0C000, 0x35246, 0x040ED, 0x0C000, 0x35646, 0x3033B, 0x0421F, + 0x0431E, 0x2C2D8, 0x2C3D9, 0x04119, 0x1D010, 0x2C0ED, 0x0421D, 0x0431C, + 0x00000, 0x00100, 0x1D020, 0x1F130, 0x3033B, 0x2C0EA, 0x2C1EB, 0x040EC, + 0x0C000, 0x35655, 0x04118, 0x1D010, 0x2C0EC, 0x3033B, 0x040EE, 0x0C000, + 0x2B400, 0x041EF, 0x0C100, 0x2B400, 0x0421B, 0x0431A, 0x1D020, 0x1F130, + 0x2C0EE, 0x2C1EF, 0x2A000, 0x3033B, 0x040D5, 0x2C02B, 0x040D7, 0x2C02A, + 0x040C1, 0x0A010, 0x3566E, 0x00020, 0x2C0C4, 0x2A000, 0x04031, 0x0C020, + 0x2C031, 0x2A000, 0x04012, 0x0A0F7, 0x2C012, 0x00500, 0x00601, 0x00100, + 0x00700, 0x04031, 0x0A0F7, 0x2C031, 0x2C53E, 0x040C3, 0x0A0C0, 0x35287, + 0x00603, 0x0A080, 0x35684, 0x00605, 0x2C6CE, 0x04440, 0x342A4, 0x00702, + 0x2C6CE, 0x04440, 0x0424C, 0x0A201, 0x356A4, 0x04248, 0x0E2B4, 0x35293, + 0x04249, 0x0A204, 0x35298, 0x2C5CE, 0x04231, 0x0C208, 0x2C231, 0x2C6CE, + 0x00203, 0x04048, 0x2C5CE, 0x0E0C4, 0x352B9, 0x00030, 0x0A4EE, 0x356A2, + 0x00080, 0x1805E, 0x00209, 0x342B9, 0x2C5CE, 0x00000, 0x00100, 0x04231, + 0x0A240, 0x356AD, 0x042C1, 0x0A201, 0x356B2, 0x0A4EE, 0x356B0, 0x000FE, + 0x1803C, 0x1A100, 0x00200, 0x0A4EE, 0x356B7, 0x18016, 0x1A100, 0x18012, + 0x1A100, 0x3031A, 0x12702, 0x352C2, 0x0A202, 0x356C2, 0x04131, 0x12108, + 0x356C2, 0x0C780, 0x04131, 0x0A130, 0x0D710, 0x2C7C4, 0x04731, 0x0A7CF, + 0x2C731, 0x2A000, 0x04531, 0x12504, 0x352D4, 0x3033B, 0x0A5FB, 0x2C531, + 0x00500, 0x2C5D8, 0x2C5D9, 0x342DE, 0x04031, 0x0A080, 0x2B400, 0x04131, + 0x0A1F7, 0x2C131, 0x00184, 0x2C1C4, 0x00101, 0x2C1CE, 0x04440, 0x302E2, + 0x00500, 0x342A4, 0x040C1, 0x0A080, 0x356E2, 0x00020, 0x2C0DB, 0x2A000, + 0x00310, 0x12202, 0x352EC, 0x00301, 0x12201, 0x356F0, 0x20306, 0x20306, + 0x12204, 0x352F3, 0x20306, 0x2A000, 0x00308, 0x12455, 0x356F8, 0x0030C, + 0x12433, 0x352FB, 0x0C301, 0x1240F, 0x352FE, 0x0C302, 0x2A000, 0x04240, + 0x0E2EE, 0x35708, 0x04244, 0x003FF, 0x0A208, 0x2B000, 0x00300, 0x2A000, + 0x003FF, 0x04244, 0x0E26E, 0x3570D, 0x00311, 0x04244, 0x0E237, 0x35711, + 0x00323, 0x04244, 0x0E214, 0x35715, 0x00360, 0x04244, 0x0E20A, 0x2B400, + 0x003C0, 0x2A000, 0x0C000, 0x35322, 0x04325, 0x01430, 0x20306, 0x19430, + 0x19040, 0x1A100, 0x1C01A, 0x1E100, 0x04AC1, 0x0AA80, 0x35724, 0x043DB, + 0x19030, 0x1A100, 0x12180, 0x3532F, 0x00001, 0x00100, 0x34331, 0x18001, + 0x1A100, 0x2C0D0, 0x2C1D2, 0x043C2, 0x0A3E0, 0x0D230, 0x2C2C2, 0x2A000, + 0x04AC1, 0x0AA80, 0x35338, 0x04AC1, 0x0AA80, 0x3573B, 0x2A000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00001, 0x2C0CF, 0x2A000, 0x340F7, + 0x040C3, 0x0A002, 0x35008, 0x300EA, 0x04031, 0x0C002, 0x2C031, 0x343FC, + 0x04031, 0x12002, 0x3540E, 0x12001, 0x353FC, 0x300EA, 0x04031, 0x0A0F8, + 0x2C031, 0x30058, 0x04031, 0x0A0F7, 0x2C031, 0x04123, 0x01010, 0x0A00C, + 0x0E004, 0x35020, 0x04031, 0x0A080, 0x35042, 0x00084, 0x2C0C4, 0x34042, + 0x04023, 0x0C010, 0x2C023, 0x04027, 0x0A008, 0x35429, 0x01010, 0x0A003, + 0x3501A, 0x04031, 0x0A080, 0x35036, 0x04048, 0x0A0EC, 0x0E0C4, 0x3503A, + 0x00084, 0x2C0C4, 0x04031, 0x0A07F, 0x2C031, 0x3403A, 0x04048, 0x0A0EC, + 0x0E0C4, 0x35042, 0x040C1, 0x0A002, 0x35040, 0x00040, 0x2C0C4, 0x3401A, + 0x00001, 0x2C0C4, 0x04031, 0x0A07F, 0x2C031, 0x04023, 0x0A00C, 0x0E004, + 0x357FC, 0x0404C, 0x0A001, 0x3504E, 0x300B0, 0x343FC, 0x04023, 0x0A001, + 0x35453, 0x300BF, 0x343FC, 0x04048, 0x0E050, 0x357FC, 0x300DB, 0x343FC, + 0x04031, 0x0A040, 0x3505F, 0x04023, 0x0C020, 0x2C023, 0x3406B, 0x040C1, + 0x0A001, 0x3506B, 0x04023, 0x0C004, 0x2C023, 0x04048, 0x0A003, 0x35073, + 0x04023, 0x0C008, 0x2C023, 0x040C3, 0x0A010, 0x3506F, 0x00054, 0x1804E, + 0x00100, 0x00200, 0x34306, 0x040C2, 0x0A004, 0x2B400, 0x040C3, 0x0A010, + 0x3507A, 0x00016, 0x18012, 0x00100, 0x00200, 0x30306, 0x0004C, 0x05100, + 0x0A101, 0x2B000, 0x04008, 0x0A001, 0x35088, 0x04048, 0x0E080, 0x3509A, + 0x04049, 0x0A002, 0x3548C, 0x00008, 0x18050, 0x05100, 0x0A101, 0x35494, + 0x00208, 0x3032E, 0x0C400, 0x2B400, 0x04048, 0x0E040, 0x3549A, 0x04027, + 0x0A010, 0x2B000, 0x0402D, 0x0A0FF, 0x350AC, 0x04048, 0x0E080, 0x354AC, + 0x04063, 0x04139, 0x2C039, 0x0F010, 0x354AC, 0x04062, 0x04138, 0x0422D, + 0x0B020, 0x2C038, 0x0F010, 0x2B000, 0x04023, 0x0C002, 0x2C023, 0x2A000, + 0x04048, 0x0A0EC, 0x0E0E4, 0x354BC, 0x04036, 0x04135, 0x0D010, 0x2B400, + 0x3033C, 0x2C0D8, 0x2C0D9, 0x2A000, 0x04048, 0x0E080, 0x310DB, 0x0414B, + 0x0A180, 0x2B400, 0x0414A, 0x0424B, 0x1C10A, 0x1E200, 0x35CC9, 0x00100, + 0x00200, 0x30340, 0x2BC00, 0x04048, 0x0E0B4, 0x2B400, 0x2A000, 0x04031, + 0x0C004, 0x2C031, 0x00078, 0x00100, 0x042C3, 0x0A210, 0x350D9, 0x0006A, + 0x00101, 0x00209, 0x34306, 0x0466D, 0x1866F, 0x05160, 0x19610, 0x0416A, + 0x0A10C, 0x2C134, 0x350E9, 0x1860A, 0x05160, 0x18601, 0x05260, 0x30340, + 0x1C60B, 0x34213, 0x00000, 0x2C023, 0x04031, 0x0A040, 0x2B400, 0x040C3, + 0x0A010, 0x354FB, 0x04046, 0x0A001, 0x354FB, 0x04045, 0x14005, 0x35CFB, + 0x040C3, 0x0A002, 0x354F8, 0x04048, 0x0A003, 0x2B400, 0x0004C, 0x05100, + 0x0A101, 0x2B400, 0x0424C, 0x0434D, 0x0444E, 0x0454F, 0x04750, 0x04851, + 0x04100, 0x0F120, 0x04001, 0x0F030, 0x0D100, 0x04002, 0x0F040, 0x0D100, + 0x04003, 0x0F050, 0x0D100, 0x04004, 0x0F070, 0x0D100, 0x04005, 0x0F080, + 0x0D100, 0x35146, 0x04027, 0x0A040, 0x35145, 0x00001, 0x2C0CE, 0x04130, + 0x0F120, 0x04038, 0x0F200, 0x04031, 0x0F030, 0x0D100, 0x04039, 0x0F030, + 0x0D200, 0x04032, 0x0F040, 0x0D100, 0x0403A, 0x0F040, 0x0D200, 0x04033, + 0x0F050, 0x0D100, 0x0403B, 0x0F050, 0x0D200, 0x04034, 0x0F070, 0x0D100, + 0x0403C, 0x0F070, 0x0D200, 0x04035, 0x0433D, 0x00400, 0x2C4CE, 0x0F080, + 0x0D100, 0x35146, 0x0F380, 0x0D230, 0x35146, 0x2A000, 0x04023, 0x0C001, + 0x2C023, 0x040C1, 0x0A002, 0x2B400, 0x04048, 0x0A00C, 0x0E004, 0x35564, + 0x04048, 0x0A0E0, 0x0E0A0, 0x35164, 0x0E060, 0x2B400, 0x04131, 0x0A108, + 0x2B000, 0x3020A, 0x040C3, 0x0A002, 0x3555A, 0x040C1, 0x0A002, 0x35577, + 0x040C1, 0x0A001, 0x35177, 0x2A000, 0x3020A, 0x0413B, 0x040C3, 0x0A010, + 0x355C5, 0x0C1C0, 0x002EE, 0x00300, 0x00701, 0x00603, 0x040C3, 0x0A002, + 0x3556E, 0x040C1, 0x0A002, 0x35577, 0x040C1, 0x0A001, 0x3557C, 0x042C2, + 0x0A2E0, 0x0C203, 0x2C2C2, 0x2A000, 0x2C6CE, 0x2C240, 0x2C341, 0x2C342, + 0x2C343, 0x2C144, 0x2C745, 0x2C346, 0x2C347, 0x2C3CE, 0x04248, 0x0434A, + 0x0444B, 0x01540, 0x0E580, 0x35193, 0x01520, 0x0E5B4, 0x35195, 0x04549, + 0x0A504, 0x35595, 0x0A400, 0x0A300, 0x341AB, 0x00528, 0x01010, 0x0A007, + 0x351A9, 0x0E004, 0x351A9, 0x0E001, 0x351A9, 0x18504, 0x0E004, 0x351A9, + 0x0E007, 0x351A9, 0x18508, 0x0E004, 0x351A9, 0x18504, 0x0E005, 0x351A9, + 0x1850C, 0x1D350, 0x1E400, 0x00000, 0x2C6CE, 0x0E2B4, 0x351B1, 0x005D4, + 0x341B2, 0x005C4, 0x2C548, 0x2C049, 0x2C34A, 0x2C44B, 0x00306, 0x00152, + 0x0024C, 0x2C0CE, 0x05810, 0x2C6CE, 0x2D820, 0x18101, 0x18201, 0x1C301, + 0x355B9, 0x2C0CE, 0x00001, 0x2C03E, 0x2A000, 0x045C1, 0x0A540, 0x351CC, + 0x01510, 0x0E50A, 0x355CC, 0x00114, 0x01510, 0x00784, 0x00800, 0x0E56E, + 0x351D9, 0x00704, 0x0E559, 0x351D8, 0x0E523, 0x351D7, 0x18838, 0x18823, + 0x1880A, 0x1880B, 0x00603, 0x00300, 0x040C3, 0x0A002, 0x355DC, 0x040C1, + 0x0A002, 0x35577, 0x040C1, 0x0A001, 0x35177, 0x2C6CE, 0x2C340, 0x2C341, + 0x2C342, 0x2C343, 0x2C144, 0x2C745, 0x2C846, 0x2C347, 0x2C3CE, 0x04248, + 0x0434A, 0x0444B, 0x01540, 0x0E580, 0x351FC, 0x01520, 0x0E5B4, 0x351FE, + 0x04549, 0x0A504, 0x355FE, 0x0A400, 0x0A300, 0x341AB, 0x01580, 0x1850A, + 0x1D350, 0x1E400, 0x045C1, 0x0A540, 0x35606, 0x185A0, 0x18520, 0x1D350, + 0x1E400, 0x341AB, 0x00000, 0x00100, 0x00217, 0x04348, 0x0E3B4, 0x35211, + 0x00207, 0x30306, 0x2A000, 0x18603, 0x04027, 0x0A010, 0x0416A, 0x0A102, + 0x20106, 0x20106, 0x20106, 0x0F010, 0x2B400, 0x0C100, 0x35237, 0x0426D, + 0x04013, 0x0F020, 0x2B400, 0x0006E, 0x00100, 0x0A2FF, 0x2B000, 0x00500, + 0x05300, 0x00701, 0x2C7CE, 0x05410, 0x00700, 0x2C7CE, 0x0F340, 0x0D530, + 0x18001, 0x18101, 0x1C201, 0x35628, 0x0C500, 0x2B400, 0x34257, 0x00058, + 0x00108, 0x00206, 0x00500, 0x05300, 0x05410, 0x0F340, 0x0D530, 0x18001, + 0x18101, 0x1C201, 0x3563B, 0x0C500, 0x2B400, 0x04034, 0x0C000, 0x35257, + 0x18603, 0x05060, 0x00100, 0x1D100, 0x2C1ED, 0x18601, 0x05060, 0x2C019, + 0x18601, 0x05060, 0x2C01F, 0x18601, 0x05060, 0x2C01E, 0x18602, 0x04048, + 0x0A0EC, 0x0E080, 0x35676, 0x04023, 0x0C002, 0x2C023, 0x040C2, 0x0A080, + 0x35265, 0x040C2, 0x0A05F, 0x2C0C2, 0x2C0C2, 0x18603, 0x04027, 0x0A010, + 0x3526F, 0x05060, 0x2C01F, 0x18601, 0x05060, 0x2C01E, 0x34276, 0x05060, + 0x00100, 0x1D100, 0x2C1EC, 0x18601, 0x05060, 0x2C018, 0x040C3, 0x0A010, + 0x35685, 0x04044, 0x04145, 0x04246, 0x00304, 0x2020E, 0x20108, 0x20008, + 0x2010E, 0x20008, 0x1C301, 0x35680, 0x34294, 0x04046, 0x04147, 0x04244, + 0x003C0, 0x0E26E, 0x3568C, 0x00311, 0x04244, 0x0E237, 0x35690, 0x00323, + 0x04244, 0x0E214, 0x35694, 0x00360, 0x1D030, 0x1E100, 0x30339, 0x1801A, + 0x1A100, 0x04260, 0x19200, 0x04361, 0x1B310, 0x04462, 0x1A400, 0x04563, + 0x1A500, 0x04664, 0x1A600, 0x04765, 0x1A700, 0x04866, 0x1A800, 0x04967, + 0x1A900, 0x0406A, 0x0A002, 0x352BF, 0x040E0, 0x1D020, 0x040E1, 0x1F030, + 0x040E2, 0x1F040, 0x040E3, 0x1F050, 0x040E4, 0x1F060, 0x040E5, 0x1F070, + 0x040E6, 0x1F080, 0x040E7, 0x1F090, 0x2BC00, 0x00000, 0x342C0, 0x30339, + 0x2C2E0, 0x2C3E1, 0x2C4E2, 0x2C5E3, 0x2C6E4, 0x2C7E5, 0x2C8E6, 0x2C9E7, + 0x01230, 0x0401D, 0x0411C, 0x00D27, 0x00C01, 0x20006, 0x20100, 0x35AD5, + 0x18C01, 0x0EC10, 0x352F4, 0x0EC10, 0x342CD, 0x20108, 0x342D8, 0x2010E, + 0x20008, 0x1CC01, 0x352EC, 0x01A80, 0x01B90, 0x1DA00, 0x1FB10, 0x35AD7, + 0x018A0, 0x019B0, 0x342D7, 0x1CD01, 0x352F8, 0x20306, 0x20400, 0x20500, + 0x20600, 0x20700, 0x20800, 0x20900, 0x01A80, 0x01B90, 0x1DA00, 0x1FB10, + 0x35AE3, 0x018A0, 0x019B0, 0x342E3, 0x00100, 0x008FF, 0x009FF, 0x34301, + 0x30339, 0x0401D, 0x0411C, 0x04AE1, 0x0FA20, 0x0AAFC, 0x35301, 0x18801, + 0x1A900, 0x1D800, 0x1F910, 0x2C8EA, 0x2C9EB, 0x2A000, 0x0442C, 0x0C000, + 0x3530D, 0x04325, 0x01430, 0x20306, 0x19430, 0x19040, 0x1A100, 0x043C3, + 0x0A310, 0x35717, 0x04344, 0x0A302, 0x35317, 0x0031A, 0x34318, 0x0031E, + 0x1D030, 0x1E100, 0x04AC1, 0x0AA80, 0x3571A, 0x04ADB, 0x190A0, 0x1A100, + 0x12180, 0x35325, 0x00002, 0x00100, 0x34327, 0x18002, 0x1A100, 0x2C0D0, + 0x2C1D2, 0x043C2, 0x0A3E0, 0x0D230, 0x2C2C2, 0x2A000, 0x00506, 0x00400, + 0x05100, 0x05320, 0x0F130, 0x0D410, 0x18201, 0x18001, 0x1C501, 0x35730, + 0x2A000, 0x04AC1, 0x0AA80, 0x35339, 0x04AC1, 0x0AA80, 0x3573C, 0x2A000, + 0x30339, 0x043D8, 0x044D9, 0x18301, 0x1A400, 0x1D310, 0x1F420, 0x2BC00, + 0x2C1D8, 0x2C2D9, 0x2A000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x2C0CF, 0x34000, 0x00000 +}; + + +const int piper_macassist_data_len = (sizeof piper_wifi_macassist_ucode)/4; diff --git a/drivers/net/wireless/digiPiper/piperMacAssist.h b/drivers/net/wireless/digiPiper/piperMacAssist.h new file mode 100644 index 000000000000..27b2b77fb493 --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperMacAssist.h @@ -0,0 +1,9 @@ + +#ifndef PIPER_MACASSIST_H_ +#define PIPER_MACASSIST_H_ + +extern const unsigned long piper_wifi_macassist_ucode[]; +extern const int piper_macassist_data_len; + + +#endif diff --git a/drivers/net/wireless/digiPiper/pipermain.h b/drivers/net/wireless/digiPiper/pipermain.h new file mode 100644 index 000000000000..dba31309e081 --- /dev/null +++ b/drivers/net/wireless/digiPiper/pipermain.h @@ -0,0 +1,381 @@ +#ifndef __PIPER_H_ +#define __PIPER_H_ + +#include <linux/completion.h> +#include <linux/if_ether.h> +#include <linux/spinlock.h> +#include <net/mac80211.h> +#include <linux/i2c.h> +#include "mac.h" + + +// #define WANT_DEBUG +#ifdef WANT_DEBUG +#define digi_dbg(fmt, arg...) \ + printk(KERN_ERR PIPER_DRIVER_NAME ": %s - " fmt, __func__, ##arg) +#else +#define digi_dbg(fmt, arg...) \ + do { } while (0) +#endif + +#define ERROR(x) printk(KERN_ALERT x) + +/* Debug levels */ +#define DSILENT 0xffff +#define DERROR 0xfff0 +#define DWARNING 0xffe0 +#define DNORMAL 0xffd0 +#define DVERBOSE 0xffc0 +#define DVVERBOSE 0xffb0 +#define DALL 0xffa0 + + +#define PIPER_DRIVER_NAME "piper" +#define DRV_VERS "0.1" + +/* Useful defines for AES */ +#define PIPER_EXTIV_SIZE 8 /* IV and extended IV size */ +#define MIC_SIZE 8 /* Message integrity check size */ +#define ICV_SIZE 4 +#define DATA_SIZE 28 /* Data frame header+FCS size */ +#define CCMP_SIZE (PIPER_EXTIV_SIZE + MIC_SIZE) /* Total CCMP size */ +#define EXPANDED_KEY_LENGTH (176) /* length of expanded AES key */ +#define PIPER_MAX_KEYS (4) +#define AES_BLOB_LENGTH (48) /* length of AES IV and headers */ + +/* Calibration constants */ +#define WCD_MAGIC "WCALDATA" +#define WCD_MAX_CAL_POINTS (8) +#define WCD_CHANNELS_BG (14) +#define WCD_CHANNELS_A (35) +#define WCD_B_CURVE_INDEX (0) +#define WCD_G_CURVE_INDEX (1) + +/* + * Set this #define to receive frames in the ISR. This may improve + * performance under heavy load at the expense of interrupt latency. + */ +#define WANT_TO_RECEIVE_FRAMES_IN_ISR (0) + + +typedef u64 u48; + +/* + * This enum lists the possible LED states. + */ +enum led_states { + led_shutdown, + led_adhoc, + led_not_associated, + led_associated +}; + +/* Available leds */ +enum wireless_led { + STATUS_LED, + ACTIVITY_LED, +}; + +#define WCD_HW_REV_MASK 0xf000 +#define WCD_HW_REV_PROTOTYPE 0x0000 +#define WCD_HW_REV_PILOT 0x1000 +#define WCD_HW_REV_A 0x2000 +#define WCD_PLATFORM_MASK 0x0ff0 +#define WCD_CCW9P_PLATFORM 0x0010 +#define WCD_CCW9M_PLATFORM 0x0020 + + +typedef struct nv_wcd_header { + char magic_string[8]; /* WCALDATA */ + char ver_major; /* Major version in ascii */ + char ver_minor; /* Minor version in ascii */ + u16 hw_platform; /* Hardware Platform used for calibration */ + u8 numcalpoints; /* Number of points per curve */ + u8 padding[107]; /* Reserved for future use */ + u32 wcd_len; /* Total length of the data section */ + u32 wcd_crc; /* Data section crc32 */ +} nv_wcd_header_t; + +typedef struct wcd_point { + s16 out_power; /* Output Power */ + u16 adc_val; /* Measured ADC val */ + u8 power_index; /* Airoha Power Index */ + u8 reserved[3]; /* For future use */ +} wcd_point_t; + +typedef struct wcd_curve { + u8 max_adc_value; /* maximum allowed ADC value for this curve */ + u8 reserved[3]; /* Resered for future use */ + /* Calibration curve points */ + wcd_point_t points[WCD_MAX_CAL_POINTS]; +} wcd_curve_t; + +typedef struct wcd_data { + nv_wcd_header_t header; + wcd_curve_t cal_curves_bg[WCD_CHANNELS_BG][2]; + wcd_curve_t cal_curves_a[WCD_CHANNELS_A]; +} wcd_data_t; + +typedef enum { + op_write, + op_or, + op_and +} reg_op_t; + +enum antenna_select { + ANTENNA_BOTH = 0, + ANTENNA_1, + ANTENNA_2, +}; + +typedef struct { + bool loaded; + bool enabled; + bool weSentLastOne; +} piperBeaconInfo_t; + +typedef enum { + RECEIVED_ACK, + TX_COMPLETE, + OUT_OF_RETRIES, + TX_NOT_DONE +} tx_result_t; + + +/* Structure that holds the information we need to support H/W AES encryption */ +struct piperKeyInfo { + bool valid; /* indicates if this record is valid */ + u8 addr[ETH_ALEN]; /* MAC address associated with key */ + u32 expandedKey[EXPANDED_KEY_LENGTH / sizeof(u32)]; + u48 txPn; /* packet number for transmit */ + u48 rxPn; /* expected receive packet number */ +}; + +/* rf */ +struct digi_rf_ops { + const char *name; + void (*init) (struct ieee80211_hw *, int); + int (*stop) (struct ieee80211_hw *); + int (*set_chan) (struct ieee80211_hw *, int chan); + int (*set_chan_no_rx) (struct ieee80211_hw *, int chan); + int (*set_pwr) (struct ieee80211_hw *, uint8_t val); + void (*set_pwr_index) (struct ieee80211_hw *, unsigned int val); + void (*power_on) (struct ieee80211_hw *, bool want_power_on); + void (*getOfdmBrs) (int channel, u64 brsBitMask, unsigned int *ofdm, + unsigned int *psk); + enum ieee80211_band (*getBand) (int); + int (*getFrequency) (int); + void (*set_hw_info)(struct ieee80211_hw *, int channel, u16 hw_platform); + const struct ieee80211_rate *(*getRate) (unsigned int); + int channelChangeTime; + s8 maxSignal; + struct ieee80211_supported_band *bands; + u8 n_bands; +}; + +struct piper_stats { + u32 rx_overruns; + u32 tx_complete_count; + u32 tx_start_count; + u32 tx_total_tetries; + u32 tx_retry_count[IEEE80211_TX_MAX_RATES]; + u32 tx_retry_index; + struct ieee80211_tx_queue_stats tx_queue; + struct ieee80211_low_level_stats ll_stats; + spinlock_t lock; +}; + +enum piper_ps_mode { + PS_MODE_LOW_POWER, + PS_MODE_FULL_POWER +}; + +enum piper_ps_state { + PS_STATE_WAIT_FOR_BEACON, + PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT, + PS_STATE_WAIT_FOR_TRANSMITTER_DONE, + PS_STATE_WAIT_FOR_WAKEUP_ALARM, + PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT +}; + +struct piper_priv; + +struct piper_ps { + u32 beacon_int; + u16 aid; + volatile unsigned int scan_timer; + unsigned int sleep_time; + struct timer_list timer; + enum piper_ps_mode mode; + enum piper_ps_state state; + unsigned int this_event; + spinlock_t lock; + volatile bool power_management; + volatile bool poweredDown; + volatile bool rxTaskletRunning; + volatile bool allowTransmits; + volatile bool stopped_tx_queues; + volatile unsigned int frames_pending; +}; + +typedef void (*tx_skb_return_cb_t)(struct ieee80211_hw *hw, + struct sk_buff *skb); + +struct piper_queue { + struct sk_buff *skb; + tx_skb_return_cb_t skb_return_cb; +}; + +#define PIPER_TX_QUEUE_SIZE (16) +#define NEXT_TX_QUEUE_INDEX(x) ((x+1) & 0xf) + +struct piper_priv { + const char *drv_name; + char debug_cmd[32]; + u32 version; + struct piper_pdata *pdata; + struct ieee80211_hw *hw; + struct ieee80211_key_conf txKeyInfo; + struct ieee80211_cts ctsFrame; + struct ieee80211_rts rtsFrame; + struct ieee80211_rate *calibrationTxRate; + struct piper_stats pstats; + struct tasklet_struct rx_tasklet; + struct tasklet_struct tx_tasklet; + spinlock_t tx_tasklet_lock; + bool tx_tasklet_running; + spinlock_t tx_queue_lock; + struct piper_queue tx_queue[PIPER_TX_QUEUE_SIZE]; + unsigned int tx_queue_head; + unsigned int tx_queue_tail; + unsigned int tx_queue_count; + bool expectingAck; + struct timer_list tx_timer; + struct timer_list led_timer; + enum led_states led_state; + struct piper_ps ps; + bool power_save_was_on_when_suspended; + struct access_ops *ac; + spinlock_t aesLock; + struct digi_rf_ops *rf; + void *__iomem vbase; + int irq; + tx_result_t tx_result; + int tx_signal_strength; + + /* Function callbacks */ + int (*init_hw) (struct piper_priv *, enum ieee80211_band); + int (*deinit_hw) (struct piper_priv *); + void (*set_irq_mask_bit) (struct piper_priv *, u32); + void (*clear_irq_mask_bit) (struct piper_priv *, u32); + u16(*get_next_beacon_backoff) (void); + int (*load_beacon) (struct piper_priv *, u8 *, u32); + int (*rand) (void); + void (*tx_calib_cb) (struct piper_priv *); + int (*set_antenna) (struct piper_priv *, enum antenna_select); + int (*set_tracking_constant) (struct piper_priv * piperp, + unsigned megahertz); + void (*adjust_max_agc) (struct piper_priv * piperp, unsigned int rssi, + _80211HeaderType * header); + + /* General settings */ + enum nl80211_iftype if_type; + bool areWeAssociated; + bool is_radio_on; + bool use_short_preamble; + int channel; + int tx_power; + u8 bssid[ETH_ALEN]; + bool tx_cts; + bool tx_rts; + enum antenna_select antenna; + int power_duty; + + /* AES stuff */ + bool use_hw_aes; + u32 aes_key_count; + struct piperKeyInfo key[PIPER_MAX_KEYS]; + u32 tx_aes_key; + u32 tx_aes_blob[AES_BLOB_LENGTH / sizeof(u32)]; + + /* IBSS */ + piperBeaconInfo_t beacon; +}; + +struct access_ops { + spinlock_t reg_lock; + int (*wr_reg) (struct piper_priv *, u8 reg, u32 val, reg_op_t op); + u32(*rd_reg) (struct piper_priv *, u8 reg); + int (*wr_fifo) (struct piper_priv *, u8 addr, u8 * buf, int len); + int (*rd_fifo) (struct piper_priv *, u8 addr, u8 * buf, int len); +}; + +struct piper_pdata { + u8 macaddr[6]; + int rst_gpio; + int irq_gpio; + int status_led_gpio; + int rf_transceiver; + wcd_data_t wcd; + int i2c_adapter_num; + struct piper_priv *piperp; + + /* Platform callbacks */ + void (*reset) (struct piper_priv *, int); + int (*init) (struct piper_priv *); + int (*late_init) (struct piper_priv *); + void (*set_led) (struct piper_priv *, enum wireless_led, int); + void (*early_resume) (struct piper_priv *); +}; + +/* main */ +int piper_alloc_hw(struct piper_priv **priv, size_t priv_sz); +void piper_free_hw(struct piper_priv *priv); +int piper_register_hw(struct piper_priv *priv, struct device *dev, + struct digi_rf_ops *rf); +void piper_unregister_hw(struct piper_priv *priv); +irqreturn_t piper_irq_handler(int irq, void *dev_id); +void packet_tx_done(struct piper_priv *piperp, + tx_result_t result, int singalstrength); +void piper_rx_tasklet(unsigned long context); +void piper_tx_tasklet(unsigned long context); +bool piper_prepare_aes_datablob(struct piper_priv *digi, + unsigned int keyIndex, u8 * aesBlob, + u8 * frame, u32 length, bool isTransmit); +void piper_load_mac_firmware(struct piper_priv *piperp); +void piper_load_dsp_firmware(struct piper_priv *piperp); +int piper_spike_suppression(struct piper_priv *piperp, bool retry); +void piper_reset_mac(struct piper_priv *piperp); +void piper_set_macaddr(struct piper_priv *piperp); +int piper_hw_tx_private(struct ieee80211_hw *hw, struct sk_buff *skb, tx_skb_return_cb_t fn); +void piper_empty_tx_queue(struct piper_priv *piperp); +int piper_tx_enqueue(struct piper_priv *piperp, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb); +struct sk_buff *piper_tx_getqueue(struct piper_priv *piperp); +bool piper_tx_queue_half_full(struct piper_priv *piperp); +void piper_set_macaddr(struct piper_priv *piperp); +void piper_MacEnterActiveMode(struct piper_priv *piperp, bool want_spike_suppression); +int piper_MacEnterSleepMode(struct piper_priv *piperp, bool force); +void piper_sendNullDataFrame(struct piper_priv *piperp, bool isPowerSaveOn); +void piper_ps_rx_task_exiting(struct piper_priv *piperp); +void piper_ps_scan_event(struct piper_priv *piperp); + +/* + * Defines for debugging function dumpRegisters + */ +#define MAIN_REGS (1) +#define MAC_REGS (2) +#define RF_REGS (4) +#define FRAME_BUFFER_REGS (8) +#define CTRL_STATUS_REGS (0x10) +#define FIFO_REGS (0x20) +#define IRQ_REGS (0x40) +#define ALL_REGS (0xf) + +void digiWifiDumpRegisters(struct piper_priv *digi, unsigned int regs); +void digiWifiDumpSkb(struct sk_buff *skb); + +extern void digiWifiDumpWordsAdd(unsigned int word); +extern void digiWifiDumpWordsDump(void); +extern void digiWifiDumpWordsReset(void); + +#endif /* __PIPER_H_ */ |