From 14c0b3ed4cab5ea79de091e22d42d6677f7cfefa Mon Sep 17 00:00:00 2001 From: Kalle Valo Date: Wed, 7 Jan 2009 22:01:20 +0200 Subject: [PATCH] Staging: Add stlc45xx, wi-fi driver for stlc4550/4560 This patch adds a new driver called stlc45xx, which supports wi-fi chipsets stlc4550 and stlc4560 from ST-NXP Wireless. The chipset can be found, for example, from Nokia N800 and N810 products. The driver is implemented based on the firmware interface information published by ST-NXP Wireless here: http://wireless.kernel.org/en/developers/Documentation/specs#STMicroelectronicshardware Currently only SPI interface is supported. Signed-off-by: Kalle Valo Signed-off-by: Greg Kroah-Hartman --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/stlc45xx/Kconfig | 8 + drivers/staging/stlc45xx/Makefile | 1 + drivers/staging/stlc45xx/stlc45xx.c | 2606 ++++++++++++++++++++++ drivers/staging/stlc45xx/stlc45xx.h | 283 +++ drivers/staging/stlc45xx/stlc45xx_lmac.h | 434 ++++ 7 files changed, 3335 insertions(+) create mode 100644 drivers/staging/stlc45xx/Kconfig create mode 100644 drivers/staging/stlc45xx/Makefile create mode 100644 drivers/staging/stlc45xx/stlc45xx.c create mode 100644 drivers/staging/stlc45xx/stlc45xx.h create mode 100644 drivers/staging/stlc45xx/stlc45xx_lmac.h diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index f62c2d125b69..937ad6fea837 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -97,5 +97,7 @@ source "drivers/staging/dst/Kconfig" source "drivers/staging/pohmelfs/Kconfig" +source "drivers/staging/stlc45xx/Kconfig" + endif # !STAGING_EXCLUDE_BUILD endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 16a8d703319f..88be12e223c4 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_EPL) += epl/ obj-$(CONFIG_ANDROID) += android/ obj-$(CONFIG_DST) += dst/ obj-$(CONFIG_POHMELFS) += pohmelfs/ +obj-$(CONFIG_STLC45XX) += stlc45xx/ diff --git a/drivers/staging/stlc45xx/Kconfig b/drivers/staging/stlc45xx/Kconfig new file mode 100644 index 000000000000..8d3f46f190e8 --- /dev/null +++ b/drivers/staging/stlc45xx/Kconfig @@ -0,0 +1,8 @@ +config STLC45XX + tristate "stlc4550/4560 support" + depends on MAC80211 && WLAN_80211 && SPI_MASTER + ---help--- + This is a driver for stlc4550 and stlc4560 chipsets. + + To compile this driver as a module, choose M here: the module will be + called stlc45xx. If unsure, say N. diff --git a/drivers/staging/stlc45xx/Makefile b/drivers/staging/stlc45xx/Makefile new file mode 100644 index 000000000000..7ee32903055a --- /dev/null +++ b/drivers/staging/stlc45xx/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_STLC45XX) += stlc45xx.o diff --git a/drivers/staging/stlc45xx/stlc45xx.c b/drivers/staging/stlc45xx/stlc45xx.c new file mode 100644 index 000000000000..1defbb50219e --- /dev/null +++ b/drivers/staging/stlc45xx/stlc45xx.c @@ -0,0 +1,2606 @@ +/* + * This file is part of stlc45xx + * + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Kalle Valo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "stlc45xx.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stlc45xx_lmac.h" + +/* + * gpios should be handled in board files and provided via platform data, + * but because it's currently impossible for stlc45xx to have a header file + * in include/linux, let's use module paramaters for now + */ +static int stlc45xx_gpio_power = 97; +module_param(stlc45xx_gpio_power, int, 0444); +MODULE_PARM_DESC(stlc45xx_gpio_power, "stlc45xx gpio number for power line"); + +static int stlc45xx_gpio_irq = 87; +module_param(stlc45xx_gpio_irq, int, 0444); +MODULE_PARM_DESC(stlc45xx_gpio_irq, "stlc45xx gpio number for irq line"); + +static const u8 default_cal_channels[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x09, + 0x00, 0x00, 0xc9, 0xff, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xe0, 0x00, 0xe0, 0x00, + 0xe0, 0x00, 0xe0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, + 0x00, 0x54, 0x01, 0xab, 0xf6, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, + 0xc0, 0x42, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, + 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x22, 0x01, 0x37, 0xa9, + 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0x00, 0xbc, 0x00, + 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, + 0x00, 0xbc, 0xfb, 0x00, 0xca, 0x79, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, + 0x2b, 0xc0, 0x2b, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, + 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0xd0, 0x00, 0x5d, + 0x54, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0xa7, 0x00, 0xa9, 0x3d, 0xc0, 0x17, 0xc0, 0x17, + 0xc0, 0x17, 0xc0, 0x17, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x7a, 0x00, + 0x06, 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x09, 0x00, 0x00, 0xc9, 0xff, 0xd8, + 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0x10, 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xd0, + 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0x54, 0x01, 0xab, 0xf6, + 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0x00, 0xcb, 0x00, + 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, + 0x00, 0xcb, 0x22, 0x01, 0x37, 0xa9, 0xc0, 0x33, 0xc0, 0x33, 0xc0, + 0x33, 0xc0, 0x33, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, + 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0xfb, 0x00, 0xca, + 0x79, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0x00, 0xb4, + 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, + 0xb4, 0x00, 0xb4, 0xd0, 0x00, 0x5d, 0x54, 0xc0, 0x21, 0xc0, 0x21, + 0xc0, 0x21, 0xc0, 0x21, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0xa7, 0x00, + 0xa9, 0x3d, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0x00, + 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa0, 0x00, 0xa0, 0x7a, 0x00, 0x06, 0x2c, 0xc0, 0x0d, 0xc0, + 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, + 0x09, 0x00, 0x00, 0xc9, 0xff, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x01, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xf0, 0x00, 0xf0, + 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + 0xd0, 0x00, 0x54, 0x01, 0xab, 0xf6, 0xc0, 0x42, 0xc0, 0x42, 0xc0, + 0x42, 0xc0, 0x42, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, + 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x22, 0x01, 0x37, + 0xa9, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0x00, 0xbc, + 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, + 0xbc, 0x00, 0xbc, 0xfb, 0x00, 0xca, 0x79, 0xc0, 0x2b, 0xc0, 0x2b, + 0xc0, 0x2b, 0xc0, 0x2b, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, + 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0xd0, 0x00, + 0x5d, 0x54, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0xaa, 0xa7, 0x00, 0xa9, 0x3d, 0xc0, 0x17, 0xc0, + 0x17, 0xc0, 0x17, 0xc0, 0x17, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x7a, + 0x00, 0x06, 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x09, 0x00, 0x00, 0xc9, 0xff, + 0xd8, 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, + 0x01, 0x10, 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0x54, 0x01, 0xab, + 0xf6, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0x00, 0xcb, + 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, + 0xcb, 0x00, 0xcb, 0x22, 0x01, 0x37, 0xa9, 0xc0, 0x33, 0xc0, 0x33, + 0xc0, 0x33, 0xc0, 0x33, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, + 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0xfb, 0x00, + 0xca, 0x79, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0x00, + 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, + 0x00, 0xb4, 0x00, 0xb4, 0xd0, 0x00, 0x5d, 0x54, 0xc0, 0x21, 0xc0, + 0x21, 0xc0, 0x21, 0xc0, 0x21, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0xa7, + 0x00, 0xa9, 0x3d, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, + 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x7a, 0x00, 0x06, 0x2c, 0xc0, 0x0d, + 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x09, 0x00, 0x00, 0xc9, 0xff, 0xd8, 0xff, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xf0, 0x00, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, + 0x00, 0xd0, 0x00, 0x54, 0x01, 0xab, 0xf6, 0xc0, 0x42, 0xc0, 0x42, + 0xc0, 0x42, 0xc0, 0x42, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, + 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x22, 0x01, + 0x37, 0xa9, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0x00, + 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, + 0x00, 0xbc, 0x00, 0xbc, 0xfb, 0x00, 0xca, 0x79, 0xc0, 0x2b, 0xc0, + 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, + 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0xd0, + 0x00, 0x5d, 0x54, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, + 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0xa7, 0x00, 0xa9, 0x3d, 0xc0, 0x17, + 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0x00, 0xa0, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, + 0x7a, 0x00, 0x06, 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, + 0x0d, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x09, 0x00, 0x00, 0xc9, + 0xff, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, + 0x10, 0x01, 0x10, 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, + 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0x54, 0x01, + 0xab, 0xf6, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0x00, + 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, + 0x00, 0xcb, 0x00, 0xcb, 0x22, 0x01, 0x37, 0xa9, 0xc0, 0x33, 0xc0, + 0x33, 0xc0, 0x33, 0xc0, 0x33, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, + 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0xfb, + 0x00, 0xca, 0x79, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, + 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, + 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0xd0, 0x00, 0x5d, 0x54, 0xc0, 0x21, + 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, + 0xa7, 0x00, 0xa9, 0x3d, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0xc0, + 0x17, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x7a, 0x00, 0x06, 0x2c, 0xc0, + 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x09, 0x00, 0x00, 0xc9, 0xff, 0xd8, 0xff, 0x00, 0x00, + 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xf0, + 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0x54, 0x01, 0xab, 0xf6, 0xc0, 0x42, 0xc0, + 0x42, 0xc0, 0x42, 0xc0, 0x42, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, + 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x22, + 0x01, 0x37, 0xa9, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, + 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, + 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0xfb, 0x00, 0xca, 0x79, 0xc0, 0x2b, + 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0x00, 0xb4, 0x00, 0xb4, 0x00, + 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, + 0xd0, 0x00, 0x5d, 0x54, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0xc0, + 0x21, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0xa7, 0x00, 0xa9, 0x3d, 0xc0, + 0x17, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, + 0xa0, 0x7a, 0x00, 0x06, 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, + 0xc0, 0x0d, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x09, 0x00, 0x00, + 0xc9, 0xff, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x10, + 0x01, 0x10, 0x01, 0x10, 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xf0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0x54, + 0x01, 0xab, 0xf6, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, + 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, + 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x22, 0x01, 0x37, 0xa9, 0xc0, 0x33, + 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0x00, 0xbc, 0x00, 0xbc, 0x00, + 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, + 0xfb, 0x00, 0xca, 0x79, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, + 0x2b, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, + 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0xd0, 0x00, 0x5d, 0x54, 0xc0, + 0x21, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0xaa, 0xa7, 0x00, 0xa9, 0x3d, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, + 0xc0, 0x17, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x7a, 0x00, 0x06, 0x2c, + 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x94, 0x09, 0x00, 0x00, 0xc9, 0xff, 0xd8, 0xff, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xd0, 0x00, 0xd0, + 0x00, 0xd0, 0x00, 0xd0, 0x00, 0x54, 0x01, 0xab, 0xf6, 0xc0, 0x42, + 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0x00, 0xcb, 0x00, 0xcb, 0x00, + 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, + 0x22, 0x01, 0x37, 0xa9, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0xc0, + 0x33, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, + 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0xfb, 0x00, 0xca, 0x79, 0xc0, + 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0x00, 0xb4, 0x00, 0xb4, + 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, + 0xb4, 0xd0, 0x00, 0x5d, 0x54, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, + 0xc0, 0x21, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0xa7, 0x00, 0xa9, 0x3d, + 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa0, 0x7a, 0x00, 0x06, 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, + 0x0d, 0xc0, 0x0d, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x09, 0x00, + 0x00, 0xc9, 0xff, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, + 0x00, 0xf0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + 0x54, 0x01, 0xab, 0xf6, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0xc0, + 0x42, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, + 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x22, 0x01, 0x37, 0xa9, 0xc0, + 0x33, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, 0x00, 0xbc, 0x00, 0xbc, + 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, + 0xbc, 0xfb, 0x00, 0xca, 0x79, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, + 0xc0, 0x2b, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, + 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0xd0, 0x00, 0x5d, 0x54, + 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0xc0, 0x21, 0x00, 0xaa, 0x00, + 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0xa7, 0x00, 0xa9, 0x3d, 0xc0, 0x17, 0xc0, 0x17, 0xc0, + 0x17, 0xc0, 0x17, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x7a, 0x00, 0x06, + 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0xc0, 0x0d, 0x00, 0x96, + 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x9e, 0x09, 0x00, 0x00, 0xc9, 0xff, 0xd8, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, + 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xd0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0x54, 0x01, 0xab, 0xf6, 0xc0, + 0x42, 0xc0, 0x42, 0xc0, 0x42, 0xc0, 0x42, 0x00, 0xcb, 0x00, 0xcb, + 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, 0xcb, 0x00, + 0xcb, 0x22, 0x01, 0x37, 0xa9, 0xc0, 0x33, 0xc0, 0x33, 0xc0, 0x33, + 0xc0, 0x33, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, + 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0x00, 0xbc, 0xfb, 0x00, 0xca, 0x79, + 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0xc0, 0x2b, 0x00, 0xb4, 0x00, + 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, 0x00, 0xb4, + 0x00, 0xb4, 0xd0, 0x00, 0x5d, 0x54, 0xc0, 0x21, 0xc0, 0x21, 0xc0, + 0x21, 0xc0, 0x21, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0xa7, 0x00, 0xa9, + 0x3d, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0xc0, 0x17, 0x00, 0xa0, + 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, + 0xa0, 0x00, 0xa0, 0x7a, 0x00, 0x06, 0x2c, 0xc0, 0x0d, 0xc0, 0x0d, + 0xc0, 0x0d, 0xc0, 0x0d, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, + 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + +static const u8 default_cal_rssi[] = { + 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x72, + 0xfe, 0x1a, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, + 0x00, 0x00, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, 0x0a, + 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x72, 0xfe, + 0x1a, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, + 0x00, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, 0x0a, 0x01, + 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x72, 0xfe, 0x1a, + 0x00, 0x00, 0x00, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static void stlc45xx_tx_edcf(struct stlc45xx *stlc); +static void stlc45xx_tx_setup(struct stlc45xx *stlc); +static void stlc45xx_tx_scan(struct stlc45xx *stlc); +static void stlc45xx_tx_psm(struct stlc45xx *stlc, bool enable); +static int stlc45xx_tx_nullfunc(struct stlc45xx *stlc, bool powersave); +static int stlc45xx_tx_pspoll(struct stlc45xx *stlc, bool powersave); + +static ssize_t stlc45xx_sysfs_show_cal_rssi(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stlc45xx *stlc = dev_get_drvdata(dev); + ssize_t len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + len = PAGE_SIZE; + + mutex_lock(&stlc->mutex); + + if (stlc->cal_rssi) + hex_dump_to_buffer(stlc->cal_rssi, RSSI_CAL_ARRAY_LEN, 16, + 2, buf, len, 0); + mutex_unlock(&stlc->mutex); + + len = strlen(buf); + + return len; +} + +static ssize_t stlc45xx_sysfs_store_cal_rssi(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct stlc45xx *stlc = dev_get_drvdata(dev); + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + if (count != RSSI_CAL_ARRAY_LEN) { + stlc45xx_error("invalid cal_rssi length: %d", count); + count = 0; + goto out_unlock; + } + + kfree(stlc->cal_rssi); + + stlc->cal_rssi = kmemdup(buf, RSSI_CAL_ARRAY_LEN, GFP_KERNEL); + + if (!stlc->cal_rssi) { + stlc45xx_error("failed to allocate memory for cal_rssi"); + count = 0; + goto out_unlock; + } + + out_unlock: + mutex_unlock(&stlc->mutex); + + return count; +} + +static ssize_t stlc45xx_sysfs_show_cal_channels(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stlc45xx *stlc = dev_get_drvdata(dev); + ssize_t len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + len = PAGE_SIZE; + + mutex_lock(&stlc->mutex); + + if (stlc->cal_channels) + hex_dump_to_buffer(stlc->cal_channels, CHANNEL_CAL_ARRAY_LEN, + 16, 2, buf, len, 0); + + mutex_unlock(&stlc->mutex); + + len = strlen(buf); + + return len; +} + +static ssize_t stlc45xx_sysfs_store_cal_channels(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct stlc45xx *stlc = dev_get_drvdata(dev); + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + if (count != CHANNEL_CAL_ARRAY_LEN) { + stlc45xx_error("invalid cal_channels size: %d ", count); + count = 0; + goto out_unlock; + } + + kfree(stlc->cal_channels); + + stlc->cal_channels = kmemdup(buf, count, GFP_KERNEL); + + if (!stlc->cal_channels) { + stlc45xx_error("failed to allocate memory for cal_channels"); + count = 0; + goto out_unlock; + } + +out_unlock: + mutex_unlock(&stlc->mutex); + + return count; +} + +static ssize_t stlc45xx_sysfs_show_tx_buf(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stlc45xx *stlc = dev_get_drvdata(dev); + struct txbuffer *entry; + ssize_t len = 0; + + stlc45xx_debug(DEBUG_FUNC, "%s()", __func__); + + mutex_lock(&stlc->mutex); + + list_for_each_entry(entry, &stlc->tx_sent, tx_list) { + len += sprintf(buf + len, "0x%x: 0x%x-0x%x\n", + entry->handle, entry->start, + entry->end); + } + + mutex_unlock(&stlc->mutex); + + return len; +} + +static DEVICE_ATTR(cal_rssi, S_IRUGO | S_IWUSR, + stlc45xx_sysfs_show_cal_rssi, + stlc45xx_sysfs_store_cal_rssi); +static DEVICE_ATTR(cal_channels, S_IRUGO | S_IWUSR, + stlc45xx_sysfs_show_cal_channels, + stlc45xx_sysfs_store_cal_channels); +static DEVICE_ATTR(tx_buf, S_IRUGO, stlc45xx_sysfs_show_tx_buf, NULL); + +static void stlc45xx_spi_read(struct stlc45xx *stlc, unsigned long addr, + void *buf, size_t len) +{ + struct spi_transfer t[2]; + struct spi_message m; + + /* We first push the address */ + addr = (addr << 8) | ADDR_READ_BIT_15; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = &addr; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + + spi_sync(stlc->spi, &m); +} + + +static void stlc45xx_spi_write(struct stlc45xx *stlc, unsigned long addr, + void *buf, size_t len) +{ + struct spi_transfer t[3]; + struct spi_message m; + u16 last_word; + + /* We first push the address */ + addr = addr << 8; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = &addr; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + + if (len % 2) { + last_word = ((u8 *)buf)[len - 1]; + + t[2].tx_buf = &last_word; + t[2].len = 2; + spi_message_add_tail(&t[2], &m); + } + + spi_sync(stlc->spi, &m); +} + +static u16 stlc45xx_read16(struct stlc45xx *stlc, unsigned long addr) +{ + u16 val; + + stlc45xx_spi_read(stlc, addr, &val, sizeof(val)); + + return val; +} + +static u32 stlc45xx_read32(struct stlc45xx *stlc, unsigned long addr) +{ + u32 val; + + stlc45xx_spi_read(stlc, addr, &val, sizeof(val)); + + return val; +} + +static void stlc45xx_write16(struct stlc45xx *stlc, unsigned long addr, u16 val) +{ + stlc45xx_spi_write(stlc, addr, &val, sizeof(val)); +} + +static void stlc45xx_write32(struct stlc45xx *stlc, unsigned long addr, u32 val) +{ + stlc45xx_spi_write(stlc, addr, &val, sizeof(val)); +} + +struct stlc45xx_spi_reg { + u16 address; + u16 length; + char *name; +}; + +/* caller must hold tx_lock */ +static void stlc45xx_txbuffer_dump(struct stlc45xx *stlc) +{ + struct txbuffer *txbuffer; + char *buf, *pos; + int buf_len, l, count; + + if (!(DEBUG_LEVEL & DEBUG_TXBUFFER)) + return; + + stlc45xx_debug(DEBUG_FUNC, "%s()", __func__); + + buf_len = 500; + buf = kmalloc(buf_len, GFP_ATOMIC); + if (!buf) + return; + + pos = buf; + count = 0; + + list_for_each_entry(txbuffer, &stlc->txbuffer, buffer_list) { + l = snprintf(pos, buf_len, "0x%x-0x%x,", + txbuffer->start, txbuffer->end); + /* drop the null byte */ + pos += l; + buf_len -= l; + count++; + } + + if (count == 0) + *pos = '\0'; + else + *--pos = '\0'; + + stlc45xx_debug(DEBUG_TXBUFFER, "txbuffer: in buffer %d regions: %s", + count, buf); + + kfree(buf); +} + +/* caller must hold tx_lock */ +static int stlc45xx_txbuffer_find(struct stlc45xx *stlc, size_t len) +{ + struct txbuffer *txbuffer; + int pos; + + stlc45xx_debug(DEBUG_FUNC, "%s()", __func__); + + pos = FIRMWARE_TXBUFFER_START; + + if (list_empty(&stlc->txbuffer)) + goto out; + + /* + * the entries in txbuffer must be in the same order as they are in + * the real buffer + */ + list_for_each_entry(txbuffer, &stlc->txbuffer, buffer_list) { + if (pos + len < txbuffer->start) + break; + pos = ALIGN(txbuffer->end + 1, 4); + } + + if (pos + len > FIRMWARE_TXBUFFER_END) + /* not enough room */ + pos = -1; + + stlc45xx_debug(DEBUG_TXBUFFER, "txbuffer: find %d B: 0x%x", len, pos); + +out: + return pos; +} + +static int stlc45xx_txbuffer_add(struct stlc45xx *stlc, + struct txbuffer *txbuffer) +{ + struct txbuffer *r, *prev = NULL; + + if (list_empty(&stlc->txbuffer)) { + list_add(&txbuffer->buffer_list, &stlc->txbuffer); + return 0; + } + + r = list_first_entry(&stlc->txbuffer, struct txbuffer, buffer_list); + + if (txbuffer->start < r->start) { + /* add to the beginning of the list */ + list_add(&txbuffer->buffer_list, &stlc->txbuffer); + return 0; + } + + prev = NULL; + list_for_each_entry(r, &stlc->txbuffer, buffer_list) { + /* skip first entry, we checked for that above */ + if (!prev) { + prev = r; + continue; + } + + /* double-check overlaps */ + WARN_ON_ONCE(txbuffer->start >= r->start && + txbuffer->start <= r->end); + WARN_ON_ONCE(txbuffer->end >= r->start && + txbuffer->end <= r->end); + + if (prev->end < txbuffer->start && + txbuffer->end < r->start) { + /* insert at this spot */ + list_add_tail(&txbuffer->buffer_list, &r->buffer_list); + return 0; + } + + prev = r; + } + + /* not found */ + list_add_tail(&txbuffer->buffer_list, &stlc->txbuffer); + + return 0; + +} + +/* caller must hold tx_lock */ +static struct txbuffer *stlc45xx_txbuffer_alloc(struct stlc45xx *stlc, + size_t frame_len) +{ + struct txbuffer *entry = NULL; + size_t len; + int pos; + + stlc45xx_debug(DEBUG_FUNC, "%s()", __func__); + + len = FIRMWARE_TXBUFFER_HEADER + frame_len + FIRMWARE_TXBUFFER_TRAILER; + pos = stlc45xx_txbuffer_find(stlc, len); + + if (pos < 0) + return NULL; + + WARN_ON_ONCE(pos + len > FIRMWARE_TXBUFFER_END); + WARN_ON_ONCE(pos < FIRMWARE_TXBUFFER_START); + + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + entry->start = pos; + entry->frame_start = pos + FIRMWARE_TXBUFFER_HEADER; + entry->end = entry->start + len - 1; + + stlc45xx_debug(DEBUG_TXBUFFER, "txbuffer: allocated 0x%x-0x%x", + entry->start, entry->end); + + stlc45xx_txbuffer_add(stlc, entry); + + stlc45xx_txbuffer_dump(stlc); + + return entry; +} + +/* caller must hold tx_lock */ +static void stlc45xx_txbuffer_free(struct stlc45xx *stlc, + struct txbuffer *txbuffer) +{ + stlc45xx_debug(DEBUG_FUNC, "%s()", __func__); + + stlc45xx_debug(DEBUG_TXBUFFER, "txbuffer: freed 0x%x-0x%x", + txbuffer->start, txbuffer->end); + + list_del(&txbuffer->buffer_list); + kfree(txbuffer); +} + + +static int stlc45xx_wait_bit(struct stlc45xx *stlc, u16 reg, u32 mask, + u32 expected) +{ + int i; + char buffer[4]; + + for (i = 0; i < 2000; i++) { + stlc45xx_spi_read(stlc, reg, buffer, sizeof(buffer)); + if (((*(u32 *)buffer) & mask) == expected) + return 1; + msleep(1); + } + + return 0; +} + +static int stlc45xx_request_firmware(struct stlc45xx *stlc) +{ + const struct firmware *fw; + int ret; + + /* FIXME: should driver use it's own struct device? */ + ret = request_firmware(&fw, "3826.arm", &stlc->spi->dev); + + if (ret < 0) { + stlc45xx_error("request_firmware() failed: %d", ret); + return ret; + } + + if (fw->size % 4) { + stlc45xx_error("firmware size is not multiple of 32bit: %d", + fw->size); + return -EILSEQ; /* Illegal byte sequence */; + } + + if (fw->size < 1000) { + stlc45xx_error("firmware is too small: %d", fw->size); + return -EILSEQ; + } + + stlc->fw = kmemdup(fw->data, fw->size, GFP_KERNEL); + if (!stlc->fw) { + stlc45xx_error("could not allocate memory for firmware"); + return -ENOMEM; + } + + stlc->fw_len = fw->size; + + release_firmware(fw); + + return 0; +} + +static int stlc45xx_upload_firmware(struct stlc45xx *stlc) +{ + struct s_dma_regs dma_regs; + unsigned long fw_len, fw_addr; + long _fw_len; + int ret; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + if (!stlc->fw) { + ret = stlc45xx_request_firmware(stlc); + if (ret < 0) + return ret; + } + + /* stop the device */ + stlc45xx_write16(stlc, SPI_ADRS_DEV_CTRL_STAT, + SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET + | SPI_CTRL_STAT_START_HALTED); + + msleep(TARGET_BOOT_SLEEP); + + stlc45xx_write16(stlc, SPI_ADRS_DEV_CTRL_STAT, + SPI_CTRL_STAT_HOST_OVERRIDE + | SPI_CTRL_STAT_START_HALTED); + + msleep(TARGET_BOOT_SLEEP); + + fw_addr = FIRMWARE_ADDRESS; + fw_len = stlc->fw_len; + + while (fw_len > 0) { + _fw_len = (fw_len > SPI_MAX_PACKET_SIZE) + ? SPI_MAX_PACKET_SIZE : fw_len; + dma_regs.cmd = SPI_DMA_WRITE_CTRL_ENABLE; + dma_regs.len = cpu_to_le16(_fw_len); + dma_regs.addr = cpu_to_le32(fw_addr); + + fw_len -= _fw_len; + fw_addr += _fw_len; + + stlc45xx_write16(stlc, SPI_ADRS_DMA_WRITE_CTRL, dma_regs.cmd); + + if (stlc45xx_wait_bit(stlc, SPI_ADRS_DMA_WRITE_CTRL, + HOST_ALLOWED, HOST_ALLOWED) == 0) { + stlc45xx_error("fw_upload not allowed to DMA write"); + return -EAGAIN; + } + + stlc45xx_write16(stlc, SPI_ADRS_DMA_WRITE_LEN, dma_regs.len); + stlc45xx_write32(stlc, SPI_ADRS_DMA_WRITE_BASE, dma_regs.addr); + + stlc45xx_spi_write(stlc, SPI_ADRS_DMA_DATA, stlc->fw, _fw_len); + + /* FIXME: I think this doesn't work if firmware is large, + * this loop goes to second round. fw->data is not + * increased at all! */ + } + + BUG_ON(fw_len != 0); + + /* enable host interrupts */ + stlc45xx_write32(stlc, SPI_ADRS_HOST_INT_EN, SPI_HOST_INTS_DEFAULT); + + /* boot the device */ + stlc45xx_write16(stlc, SPI_ADRS_DEV_CTRL_STAT, + SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET + | SPI_CTRL_STAT_RAM_BOOT); + + msleep(TARGET_BOOT_SLEEP); + + stlc45xx_write16(stlc, SPI_ADRS_DEV_CTRL_STAT, + SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_RAM_BOOT); + msleep(TARGET_BOOT_SLEEP); + + return 0; +} + +/* caller must hold tx_lock */ +static void stlc45xx_check_txsent(struct stlc45xx *stlc) +{ + struct txbuffer *entry, *n; + + list_for_each_entry_safe(entry, n, &stlc->tx_sent, tx_list) { + if (time_after(jiffies, entry->lifetime)) { + if (net_ratelimit()) + stlc45xx_warning("frame 0x%x lifetime exceeded", + entry->start); + list_del(&entry->tx_list); + skb_pull(entry->skb, entry->header_len); + ieee80211_tx_status(stlc->hw, entry->skb); + stlc45xx_txbuffer_free(stlc, entry); + } + } +} + +static void stlc45xx_power_off(struct stlc45xx *stlc) +{ + disable_irq(gpio_to_irq(stlc45xx_gpio_irq)); + gpio_set_value(stlc45xx_gpio_power, 0); +} + +static void stlc45xx_power_on(struct stlc45xx *stlc) +{ + gpio_set_value(stlc45xx_gpio_power, 1); + enable_irq(gpio_to_irq(stlc45xx_gpio_irq)); + + /* + * need to wait a while before device can be accessed, the length + * is just a guess + */ + msleep(10); +} + +/* caller must hold tx_lock */ +static void stlc45xx_flush_queues(struct stlc45xx *stlc) +{ + struct txbuffer *entry; + + while (!list_empty(&stlc->tx_sent)) { + entry = list_first_entry(&stlc->tx_sent, + struct txbuffer, tx_list); + list_del(&entry->tx_list); + dev_kfree_skb(entry->skb); + stlc45xx_txbuffer_free(stlc, entry); + } + + WARN_ON(!list_empty(&stlc->tx_sent)); + + while (!list_empty(&stlc->tx_pending)) { + entry = list_first_entry(&stlc->tx_pending, + struct txbuffer, tx_list); + list_del(&entry->tx_list); + dev_kfree_skb(entry->skb); + stlc45xx_txbuffer_free(stlc, entry); + } + + WARN_ON(!list_empty(&stlc->tx_pending)); + WARN_ON(!list_empty(&stlc->txbuffer)); +} + +static void stlc45xx_work_reset(struct work_struct *work) +{ + struct stlc45xx *stlc = container_of(work, struct stlc45xx, + work_reset); + + mutex_lock(&stlc->mutex); + + if (stlc->fw_state != FW_STATE_RESET) + goto out; + + stlc45xx_power_off(stlc); + + mutex_unlock(&stlc->mutex); + + /* wait that all work_structs have finished, we can't hold + * stlc->mutex to avoid deadlock */ + cancel_work_sync(&stlc->work); + + /* FIXME: find out good value to wait for chip power down */ + msleep(100); + + mutex_lock(&stlc->mutex); + + /* FIXME: we should gracefully handle if the state has changed + * after re-acquiring mutex */ + WARN_ON(stlc->fw_state != FW_STATE_RESET); + + spin_lock_bh(&stlc->tx_lock); + stlc45xx_flush_queues(stlc); + spin_unlock_bh(&stlc->tx_lock); + + stlc->fw_state = FW_STATE_RESETTING; + + stlc45xx_power_on(stlc); + stlc45xx_upload_firmware(stlc); + +out: + mutex_unlock(&stlc->mutex); +} + +/* caller must hold mutex */ +static void stlc45xx_reset(struct stlc45xx *stlc) +{ + stlc45xx_warning("resetting firmware"); + stlc->fw_state = FW_STATE_RESET; + ieee80211_stop_queues(stlc->hw); + queue_work(stlc->hw->workqueue, &stlc->work_reset); +} + +static void stlc45xx_work_tx_timeout(struct work_struct *work) +{ + struct stlc45xx *stlc = container_of(work, struct stlc45xx, + work_tx_timeout.work); + + stlc45xx_warning("tx timeout"); + + mutex_lock(&stlc->mutex); + + if (stlc->fw_state != FW_STATE_READY) + goto out; + + stlc45xx_reset(stlc); + +out: + mutex_unlock(&stlc->mutex); +} + +static void stlc45xx_int_ack(struct stlc45xx *stlc, u32 val) +{ + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + stlc45xx_write32(stlc, SPI_ADRS_HOST_INT_ACK, val); +} + +static void stlc45xx_wakeup(struct stlc45xx *stlc) +{ + unsigned long timeout; + u32 ints; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + /* wake the chip */ + stlc45xx_write32(stlc, SPI_ADRS_ARM_INTERRUPTS, SPI_TARGET_INT_WAKEUP); + + /* And wait for the READY interrupt */ + timeout = jiffies + HZ; + + ints = stlc45xx_read32(stlc, SPI_ADRS_HOST_INTERRUPTS); + while (!(ints & SPI_HOST_INT_READY)) { + if (time_after(jiffies, timeout)) + goto out; + ints = stlc45xx_read32(stlc, SPI_ADRS_HOST_INTERRUPTS); + } + + stlc45xx_int_ack(stlc, SPI_HOST_INT_READY); + +out: + return; +} + +static void stlc45xx_sleep(struct stlc45xx *stlc) +{ + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + stlc45xx_write32(stlc, SPI_ADRS_ARM_INTERRUPTS, SPI_TARGET_INT_SLEEP); +} + +static void stlc45xx_int_ready(struct stlc45xx *stlc) +{ + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + stlc45xx_write32(stlc, SPI_ADRS_HOST_INT_EN, + SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE); + + switch (stlc->fw_state) { + case FW_STATE_BOOTING: + stlc->fw_state = FW_STATE_READY; + complete(&stlc->fw_comp); + break; + case FW_STATE_RESETTING: + stlc->fw_state = FW_STATE_READY; + + stlc45xx_tx_scan(stlc); + stlc45xx_tx_setup(stlc); + stlc45xx_tx_edcf(stlc); + + ieee80211_wake_queues(stlc->hw); + break; + default: + break; + } +} + +static int stlc45xx_rx_txack(struct stlc45xx *stlc, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info; + struct s_lm_control *control; + struct s_lmo_tx *tx; + struct txbuffer *entry; + int found = 0; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + control = (struct s_lm_control *) skb->data; + tx = (struct s_lmo_tx *) (control + 1); + + if (list_empty(&stlc->tx_sent)) { + if (net_ratelimit()) + stlc45xx_warning("no frames waiting for " + "acknowledgement"); + return -1; + } + + list_for_each_entry(entry, &stlc->tx_sent, tx_list) { + if (control->handle == entry->handle) { + found = 1; + break; + } + } + + if (!found) { + if (net_ratelimit()) + stlc45xx_warning("couldn't find frame for tx ack 0x%x", + control->handle); + return -1; + } + + stlc45xx_debug(DEBUG_TX, "TX ACK 0x%x", entry->handle); + + if (entry->status_needed) { + info = IEEE80211_SKB_CB(entry->skb); + + if (!(tx->flags & LM_TX_FAILED)) { + /* frame was acked */ + info->flags |= IEEE80211_TX_STAT_ACK; + info->status.ack_signal = tx->rcpi / 2 - 110; + } + + skb_pull(entry->skb, entry->header_len); + + ieee80211_tx_status(stlc->hw, entry->skb); + } + + list_del(&entry->tx_list); + + stlc45xx_check_txsent(stlc); + if (list_empty(&stlc->tx_sent)) + /* there are no pending frames, we can stop the tx timeout + * timer */ + cancel_delayed_work(&stlc->work_tx_timeout); + + spin_lock_bh(&stlc->tx_lock); + + stlc45xx_txbuffer_free(stlc, entry); + + if (stlc->tx_queue_stopped && + stlc45xx_txbuffer_find(stlc, MAX_FRAME_LEN) != -1) { + stlc45xx_debug(DEBUG_QUEUE, "room in tx buffer, waking queues"); + ieee80211_wake_queues(stlc->hw); + stlc->tx_queue_stopped = 0; + } + + spin_unlock_bh(&stlc->tx_lock); + + return 0; +} + +static int stlc45xx_rx_control(struct stlc45xx *stlc, struct sk_buff *skb) +{ + struct s_lm_control *control; + int ret = 0; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + control = (struct s_lm_control *) skb->data; + + switch (control->oid) { + case LM_OID_TX: + ret = stlc45xx_rx_txack(stlc, skb); + break; + case LM_OID_SETUP: + case LM_OID_SCAN: + case LM_OID_TRAP: + case LM_OID_EDCF: + case LM_OID_KEYCACHE: + case LM_OID_PSM: + case LM_OID_STATS: + case LM_OID_LED: + default: + stlc45xx_warning("unhandled rx control oid %d\n", + control->oid); + break; + } + + dev_kfree_skb(skb); + + return ret; +} + +/* copied from mac80211 */ +static void stlc45xx_parse_elems(u8 *start, size_t len, + struct stlc45xx_ie_tim **tim, + size_t *tim_len) +{ + size_t left = len; + u8 *pos = start; + + while (left >= 2) { + u8 id, elen; + + id = *pos++; + elen = *pos++; + left -= 2; + + if (elen > left) + return; + + switch (id) { + case WLAN_EID_TIM: + *tim = (struct stlc45xx_ie_tim *) pos; + *tim_len = elen; + break; + default: + break; + } + + left -= elen; + pos += elen; + } +} + +/* + * mac80211 doesn't have support for asking frames with PS-Poll, so let's + * implement in the driver for now. We have to add support to mac80211 + * later. + */ +static int stlc45xx_check_more_data(struct stlc45xx *stlc, struct sk_buff *skb) +{ + struct s_lm_data_in *data = (struct s_lm_data_in *) skb->data; + struct ieee80211_hdr *hdr; + size_t len; + u16 fc; + + hdr = (void *) skb->data + sizeof(*data); + len = skb->len - sizeof(*data); + + /* minimum frame length is the null frame length 24 bytes */ + if (len < 24) { + stlc45xx_warning("invalid frame length when checking for " + "more data"); + return -EINVAL; + } + + fc = le16_to_cpu(hdr->frame_control); + if (!(fc & IEEE80211_FCTL_FROMDS)) + /* this is not from DS */ + return 0; + + if (compare_ether_addr(hdr->addr1, stlc->mac_addr) != 0) + /* the frame was not for us */ + return 0; + + if (!(fc & IEEE80211_FCTL_MOREDATA)) { + /* AP has no more frames buffered for us */ + stlc45xx_debug(DEBUG_PSM, "all buffered frames retrieved"); + stlc->pspolling = false; + return 0; + } + + /* MOREDATA bit is set, let's ask for a new frame from the AP */ + stlc45xx_tx_pspoll(stlc, stlc->psm); + + return 0; +} + +/* + * mac80211 cannot read TIM from beacons, so let's add a hack to the + * driver. We have to add support to mac80211 later. + */ +static int stlc45xx_rx_data_beacon(struct stlc45xx *stlc, struct sk_buff *skb) +{ + struct s_lm_data_in *data = (struct s_lm_data_in *) skb->data; + size_t len = skb->len, tim_len = 0, baselen, pvbmap_len; + struct ieee80211_mgmt *mgmt; + struct stlc45xx_ie_tim *tim = NULL; + int bmap_offset, index, aid_bit; + + mgmt = (void *) skb->data + sizeof(*data); + + baselen = (u8 *) mgmt->u.beacon.variable - (u8 *) mgmt; + if (baselen > len) { + stlc45xx_warning("invalid baselen in beacon"); + return -EINVAL; + } + + stlc45xx_parse_elems(mgmt->u.beacon.variable, len - baselen, &tim, + &tim_len); + + if (!tim) { + stlc45xx_warning("didn't find tim from a beacon"); + return -EINVAL; + } + + bmap_offset = tim->bmap_control & 0xfe; + index = stlc->aid / 8 - bmap_offset; + + pvbmap_len = tim_len - 3; + if (index > pvbmap_len) + return -EINVAL; + + aid_bit = !!(tim->pvbmap[index] & (1 << stlc->aid % 8)); + + stlc45xx_debug(DEBUG_PSM, "fc 0x%x duration %d seq %d dtim %u " + "bmap_control 0x%x aid_bit %d", + mgmt->frame_control, mgmt->duration, mgmt->seq_ctrl >> 4, + tim->dtim_count, tim->bmap_control, aid_bit); + + if (!aid_bit) + return 0; + + stlc->pspolling = true; + stlc45xx_tx_pspoll(stlc, stlc->psm); + + return 0; +} + +static int stlc45xx_rx_data(struct stlc45xx *stlc, struct sk_buff *skb) +{ + struct ieee80211_rx_status status; + struct s_lm_data_in *data = (struct s_lm_data_in *) skb->data; + int align = 0; + u8 *p, align_len; + u16 len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + if (stlc->psm) { + if (data->flags & LM_IN_BEACON) + stlc45xx_rx_data_beacon(stlc, skb); + else if (stlc->pspolling && (data->flags & LM_IN_DATA)) + stlc45xx_check_more_data(stlc, skb); + } + + memset(&status, 0, sizeof(status)); + + status.freq = data->frequency; + status.signal = data->rcpi / 2 - 110; + + /* let's assume that maximum rcpi value is 140 (= 35 dBm) */ + status.qual = data->rcpi * 100 / 140; + + status.band = IEEE80211_BAND_2GHZ; + + /* + * FIXME: this gives warning from __ieee80211_rx() + * + * status.rate_idx = data->rate; + */ + + len = data->length; + + if (data->flags & LM_FLAG_ALIGN) + align = 1; + + skb_pull(skb, sizeof(*data)); + + if (align) { + p = skb->data; + align_len = *p; + skb_pull(skb, align_len); + } + + skb_trim(skb, len); + + stlc45xx_debug(DEBUG_RX, "rx data 0x%p %d B", skb->data, skb->len); + stlc45xx_dump(DEBUG_RX_CONTENT, skb->data, skb->len); + + ieee80211_rx(stlc->hw, skb, &status); + + return 0; +} + + + +static int stlc45xx_rx(struct stlc45xx *stlc) +{ + struct s_lm_control *control; + struct sk_buff *skb; + int ret; + u16 len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + stlc45xx_wakeup(stlc); + + /* dummy read to flush SPI DMA controller bug */ + stlc45xx_read16(stlc, SPI_ADRS_GEN_PURP_1); + + len = stlc45xx_read16(stlc, SPI_ADRS_DMA_DATA); + + if (len == 0) { + stlc45xx_warning("rx request of zero bytes"); + return 0; + } + + skb = dev_alloc_skb(len); + if (!skb) { + stlc45xx_warning("could not alloc skb"); + return 0; + } + + stlc45xx_spi_read(stlc, SPI_ADRS_DMA_DATA, skb_put(skb, len), len); + + stlc45xx_sleep(stlc); + + stlc45xx_debug(DEBUG_RX, "rx frame 0x%p %d B", skb->data, skb->len); + stlc45xx_dump(DEBUG_RX_CONTENT, skb->data, skb->len); + + control = (struct s_lm_control *) skb->data; + + if (control->flags & LM_FLAG_CONTROL) + ret = stlc45xx_rx_control(stlc, skb); + else + ret = stlc45xx_rx_data(stlc, skb); + + return ret; +} + + +static irqreturn_t stlc45xx_interrupt(int irq, void *config) +{ + struct spi_device *spi = config; + struct stlc45xx *stlc = dev_get_drvdata(&spi->dev); + + stlc45xx_debug(DEBUG_IRQ, "IRQ"); + + queue_work(stlc->hw->workqueue, &stlc->work); + + return IRQ_HANDLED; +} + +static int stlc45xx_tx_frame(struct stlc45xx *stlc, u32 address, + void *buf, size_t len) +{ + struct s_dma_regs dma_regs; + unsigned long timeout; + int ret = 0; + u32 ints; + + stlc->tx_frames++; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + stlc45xx_debug(DEBUG_TX, "tx frame 0x%p %d B", buf, len); + stlc45xx_dump(DEBUG_TX_CONTENT, buf, len); + + stlc45xx_wakeup(stlc); + + dma_regs.cmd = SPI_DMA_WRITE_CTRL_ENABLE; + dma_regs.len = cpu_to_le16(len); + dma_regs.addr = cpu_to_le32(address); + + stlc45xx_spi_write(stlc, SPI_ADRS_DMA_WRITE_CTRL, &dma_regs, + sizeof(dma_regs)); + + stlc45xx_spi_write(stlc, SPI_ADRS_DMA_DATA, buf, len); + + timeout = jiffies + 2 * HZ; + ints = stlc45xx_read32(stlc, SPI_ADRS_HOST_INTERRUPTS); + while (!(ints & SPI_HOST_INT_WR_READY)) { + if (time_after(jiffies, timeout)) { + stlc45xx_warning("WR_READY timeout"); + ret = -1; + goto out; + } + ints = stlc45xx_read32(stlc, SPI_ADRS_HOST_INTERRUPTS); + } + + stlc45xx_int_ack(stlc, SPI_HOST_INT_WR_READY); + + stlc45xx_sleep(stlc); + +out: + return ret; +} + +static int stlc45xx_wq_tx(struct stlc45xx *stlc) +{ + struct txbuffer *entry; + int ret = 0; + + spin_lock_bh(&stlc->tx_lock); + + while (!list_empty(&stlc->tx_pending)) { + entry = list_entry(stlc->tx_pending.next, + struct txbuffer, tx_list); + + list_del_init(&entry->tx_list); + + spin_unlock_bh(&stlc->tx_lock); + + ret = stlc45xx_tx_frame(stlc, entry->frame_start, + entry->skb->data, entry->skb->len); + + spin_lock_bh(&stlc->tx_lock); + + if (ret < 0) { + /* frame transfer to firmware buffer failed */ + /* FIXME: report this to mac80211 */ + dev_kfree_skb(entry->skb); + stlc45xx_txbuffer_free(stlc, entry); + goto out; + } + + list_add(&entry->tx_list, &stlc->tx_sent); + queue_delayed_work(stlc->hw->workqueue, + &stlc->work_tx_timeout, + msecs_to_jiffies(TX_TIMEOUT)); + } + +out: + spin_unlock_bh(&stlc->tx_lock); + return ret; +} + +static void stlc45xx_work(struct work_struct *work) +{ + struct stlc45xx *stlc = container_of(work, struct stlc45xx, work); + u32 ints; + int ret; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + if (stlc->fw_state == FW_STATE_OFF && + stlc->fw_state == FW_STATE_RESET) + goto out; + + ints = stlc45xx_read32(stlc, SPI_ADRS_HOST_INTERRUPTS); + stlc45xx_debug(DEBUG_BH, "begin host_ints 0x%08x", ints); + + if (ints & SPI_HOST_INT_READY) { + stlc45xx_int_ready(stlc); + stlc45xx_int_ack(stlc, SPI_HOST_INT_READY); + } + + if (stlc->fw_state != FW_STATE_READY) + goto out; + + if (ints & SPI_HOST_INT_UPDATE) { + stlc45xx_int_ack(stlc, SPI_HOST_INT_UPDATE); + ret = stlc45xx_rx(stlc); + if (ret < 0) { + stlc45xx_reset(stlc); + goto out; + } + } + if (ints & SPI_HOST_INT_SW_UPDATE) { + stlc45xx_int_ack(stlc, SPI_HOST_INT_SW_UPDATE); + ret = stlc45xx_rx(stlc); + if (ret < 0) { + stlc45xx_reset(stlc); + goto out; + } + } + + ret = stlc45xx_wq_tx(stlc); + if (ret < 0) { + stlc45xx_reset(stlc); + goto out; + } + + ints = stlc45xx_read32(stlc, SPI_ADRS_HOST_INTERRUPTS); + stlc45xx_debug(DEBUG_BH, "end host_ints 0x%08x", ints); + +out: + mutex_unlock(&stlc->mutex); +} + +static void stlc45xx_tx_edcf(struct stlc45xx *stlc) +{ + struct s_lm_control *control; + struct s_lmo_edcf *edcf; + size_t len, edcf_len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + edcf_len = sizeof(*edcf); + len = sizeof(*control) + edcf_len; + control = kzalloc(len, GFP_KERNEL); + edcf = (struct s_lmo_edcf *) (control + 1); + + control->flags = LM_FLAG_CONTROL | LM_CTRL_OPSET; + control->length = edcf_len; + control->oid = LM_OID_EDCF; + + edcf->slottime = 0x14; + edcf->sifs = 10; + edcf->eofpad = 6; + edcf->maxburst = 1500; + + edcf->queues[0].aifs = 2; + edcf->queues[0].pad0 = 1; + edcf->queues[0].cwmin = 3; + edcf->queues[0].cwmax = 7; + edcf->queues[0].txop = 47; + edcf->queues[1].aifs = 2; + edcf->queues[1].pad0 = 0; + edcf->queues[1].cwmin = 7; + edcf->queues[1].cwmax = 15; + edcf->queues[1].txop = 94; + edcf->queues[2].aifs = 3; + edcf->queues[2].pad0 = 0; + edcf->queues[2].cwmin = 15; + edcf->queues[2].cwmax = 1023; + edcf->queues[2].txop = 0; + edcf->queues[3].aifs = 7; + edcf->queues[3].pad0 = 0; + edcf->queues[3].cwmin = 15; + edcf->queues[3].cwmax = 1023; + edcf->queues[3].txop = 0; + edcf->queues[4].aifs = 13; + edcf->queues[4].pad0 = 99; + edcf->queues[4].cwmin = 3437; + edcf->queues[4].cwmax = 512; + edcf->queues[4].txop = 12; + edcf->queues[5].aifs = 142; + edcf->queues[5].pad0 = 109; + edcf->queues[5].cwmin = 8756; + edcf->queues[5].cwmax = 6; + edcf->queues[5].txop = 0; + edcf->queues[6].aifs = 4; + edcf->queues[6].pad0 = 0; + edcf->queues[6].cwmin = 0; + edcf->queues[6].cwmax = 58705; + edcf->queues[6].txop = 25716; + edcf->queues[7].aifs = 0; + edcf->queues[7].pad0 = 0; + edcf->queues[7].cwmin = 0; + edcf->queues[7].cwmax = 0; + edcf->queues[7].txop = 0; + + stlc45xx_tx_frame(stlc, FIRMWARE_CONFIG_START, control, len); + + kfree(control); +} + +static void stlc45xx_tx_setup(struct stlc45xx *stlc) +{ + struct s_lm_control *control; + struct s_lmo_setup *setup; + size_t len, setup_len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + setup_len = sizeof(*setup); + len = sizeof(*control) + setup_len; + control = kzalloc(len, GFP_KERNEL); + setup = (struct s_lmo_setup *) (control + 1); + + control->flags = LM_FLAG_CONTROL | LM_CTRL_OPSET; + control->length = setup_len; + control->oid = LM_OID_SETUP; + + setup->flags = LM_SETUP_INFRA; + setup->antenna = 2; + setup->rx_align = 0; + setup->rx_buffer = FIRMWARE_RXBUFFER_START; + setup->rx_mtu = FIRMWARE_MTU; + setup->frontend = 5; + setup->timeout = 0; + setup->truncate = 48896; + setup->bratemask = 0xffffffff; + setup->ref_clock = 644245094; + setup->lpf_bandwidth = 65535; + setup->osc_start_delay = 65535; + + memcpy(setup->macaddr, stlc->mac_addr, ETH_ALEN); + memcpy(setup->bssid, stlc->bssid, ETH_ALEN); + + stlc45xx_tx_frame(stlc, FIRMWARE_CONFIG_START, control, len); + + kfree(control); +} + +static void stlc45xx_tx_scan(struct stlc45xx *stlc) +{ + struct s_lm_control *control; + struct s_lmo_scan *scan; + size_t len, scan_len; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + scan_len = sizeof(*scan); + len = sizeof(*control) + scan_len; + control = kzalloc(len, GFP_KERNEL); + scan = (struct s_lmo_scan *) (control + 1); + + control->flags = LM_FLAG_CONTROL | LM_CTRL_OPSET; + control->length = scan_len; + control->oid = LM_OID_SCAN; + + scan->flags = LM_SCAN_EXIT; + scan->bratemask = 0x15f; + scan->aloft[0] = 3; + scan->aloft[1] = 3; + scan->aloft[2] = 1; + scan->aloft[3] = 0; + scan->aloft[4] = 0; + scan->aloft[5] = 0; + scan->aloft[6] = 0; + scan->aloft[7] = 0; + + memcpy(&scan->rssical, &stlc->cal_rssi[(stlc->channel - 1) * + RSSI_CAL_LEN], + RSSI_CAL_LEN); + memcpy(&scan->channel, &stlc->cal_channels[(stlc->channel - 1) * + CHANNEL_CAL_LEN], + CHANNEL_CAL_LEN); + + stlc45xx_tx_frame(stlc, FIRMWARE_CONFIG_START, control, len); + + kfree(control); +} + +/* + * caller must hold mutex + */ +static int stlc45xx_tx_pspoll(struct stlc45xx *stlc, bool powersave) +{ + struct ieee80211_hdr *pspoll; + int payload_len, padding, i; + struct s_lm_data_out *data; + struct txbuffer *entry; + DECLARE_MAC_BUF(mac); + struct sk_buff *skb; + char *payload; + u16 fc; + + skb = dev_alloc_skb(stlc->hw->extra_tx_headroom + 16); + if (!skb) { + stlc45xx_warning("failed to allocate pspoll frame"); + return -ENOMEM; + } + skb_reserve(skb, stlc->hw->extra_tx_headroom); + + pspoll = (struct ieee80211_hdr *) skb_put(skb, 16); + memset(pspoll, 0, 16); + fc = IEEE80211_FTYPE_CTL | IEEE80211_STYPE_PSPOLL; + if (powersave) + fc |= IEEE80211_FCTL_PM; + pspoll->frame_control = cpu_to_le16(fc); + pspoll->duration_id = cpu_to_le16(stlc->aid); + + /* aid in PS-Poll has its two MSBs each set to 1 */ + pspoll->duration_id |= cpu_to_le16(1 << 15) | cpu_to_le16(1 << 14); + + memcpy(pspoll->addr1, stlc->bssid, ETH_ALEN); + memcpy(pspoll->addr2, stlc->mac_addr, ETH_ALEN); + + stlc45xx_debug(DEBUG_PSM, "sending PS-Poll frame to %s (powersave %d, " + "fc 0x%x, aid %d)", print_mac(mac, pspoll->addr1), + powersave, fc, stlc->aid); + + spin_lock_bh(&stlc->tx_lock); + + entry = stlc45xx_txbuffer_alloc(stlc, skb->len); + + spin_unlock_bh(&stlc->tx_lock); + + if (!entry) { + /* + * The queue should be stopped before the firmware buffer + * is full, so firmware buffer should always have enough + * space. + * + * But I'm too lazy and omit it for now. + */ + if (net_ratelimit()) + stlc45xx_warning("firmware tx buffer full is full " + "for null frame"); + return -ENOSPC; + } + + payload = skb->data; + payload_len = skb->len; + padding = (int) (skb->data - sizeof(*data)) & 3; + entry->header_len = sizeof(*data) + padding; + + entry->skb = skb; + entry->status_needed = false; + entry->handle = (u32) skb; + entry->lifetime = jiffies + msecs_to_jiffies(TX_FRAME_LIFETIME); + + stlc45xx_debug(DEBUG_TX, "tx data 0x%x (0x%p payload %d B " + "padding %d header_len %d)", + entry->handle, payload, payload_len, padding, + entry->header_len); + stlc45xx_dump(DEBUG_TX_CONTENT, payload, payload_len); + + data = (struct s_lm_data_out *) skb_push(skb, entry->header_len); + + memset(data, 0, entry->header_len); + + if (padding) + data->flags = LM_FLAG_ALIGN; + + data->flags = LM_OUT_BURST; + data->length = payload_len; + data->handle = entry->handle; + data->aid = 1; + data->rts_retries = 7; + data->retries = 7; + data->aloft_ctrl = 0; + data->crypt_offset = 58; + data->keytype = 0; + data->keylen = 0; + data->queue = LM_QUEUE_DATA3; + data->backlog = 32; + data->antenna = 2; + data->cts = 3; + data->power = 127; + + for (i = 0; i < 8; i++) + data->aloft[i] = 0; + + /* + * check if there's enough space in tx buffer + * + * FIXME: ignored for now + */ + + stlc45xx_tx_frame(stlc, entry->start, skb->data, skb->len); + + list_add(&entry->tx_list, &stlc->tx_sent); + + return 0; +} + +/* + * caller must hold mutex + * + * shamelessly stolen from mac80211/ieee80211_send_nullfunc + */ +static int stlc45xx_tx_nullfunc(struct stlc45xx *stlc, bool powersave) +{ + struct ieee80211_hdr *nullfunc; + int payload_len, padding, i; + struct s_lm_data_out *data; + struct txbuffer *entry; + DECLARE_MAC_BUF(mac); + struct sk_buff *skb; + char *payload; + u16 fc; + + skb = dev_alloc_skb(stlc->hw->extra_tx_headroom + 24); + if (!skb) { + stlc45xx_warning("failed to allocate buffer for null frame\n"); + return -ENOMEM; + } + skb_reserve(skb, stlc->hw->extra_tx_headroom); + + nullfunc = (struct ieee80211_hdr *) skb_put(skb, 24); + memset(nullfunc, 0, 24); + fc = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC | + IEEE80211_FCTL_TODS; + + if (powersave) + fc |= IEEE80211_FCTL_PM; + + nullfunc->frame_control = cpu_to_le16(fc); + memcpy(nullfunc->addr1, stlc->bssid, ETH_ALEN); + memcpy(nullfunc->addr2, stlc->mac_addr, ETH_ALEN); + memcpy(nullfunc->addr3, stlc->bssid, ETH_ALEN); + + stlc45xx_debug(DEBUG_PSM, "sending Null frame to %s (powersave %d, " + "fc 0x%x)", + print_mac(mac, nullfunc->addr1), powersave, fc); + + spin_lock_bh(&stlc->tx_lock); + + entry = stlc45xx_txbuffer_alloc(stlc, skb->len); + + spin_unlock_bh(&stlc->tx_lock); + + if (!entry) { + /* + * The queue should be stopped before the firmware buffer + * is full, so firmware buffer should always have enough + * space. + * + * But I'm too lazy and omit it for now. + */ + if (net_ratelimit()) + stlc45xx_warning("firmware tx buffer full is full " + "for null frame"); + return -ENOSPC; + } + + payload = skb->data; + payload_len = skb->len; + padding = (int) (skb->data - sizeof(*data)) & 3; + entry->header_len = sizeof(*data) + padding; + + entry->skb = skb; + entry->status_needed = false; + entry->handle = (u32) skb; + entry->lifetime = jiffies + msecs_to_jiffies(TX_FRAME_LIFETIME); + + stlc45xx_debug(DEBUG_TX, "tx data 0x%x (0x%p payload %d B " + "padding %d header_len %d)", + entry->handle, payload, payload_len, padding, + entry->header_len); + stlc45xx_dump(DEBUG_TX_CONTENT, payload, payload_len); + + data = (struct s_lm_data_out *) skb_push(skb, entry->header_len); + + memset(data, 0, entry->header_len); + + if (padding) + data->flags = LM_FLAG_ALIGN; + + data->flags = LM_OUT_BURST; + data->length = payload_len; + data->handle = entry->handle; + data->aid = 1; + data->rts_retries = 7; + data->retries = 7; + data->aloft_ctrl = 0; + data->crypt_offset = 58; + data->keytype = 0; + data->keylen = 0; + data->queue = LM_QUEUE_DATA3; + data->backlog = 32; + data->antenna = 2; + data->cts = 3; + data->power = 127; + + for (i = 0; i < 8; i++) + data->aloft[i] = 0; + + /* + * check if there's enough space in tx buffer + * + * FIXME: ignored for now + */ + + stlc45xx_tx_frame(stlc, entry->start, skb->data, skb->len); + + list_add(&entry->tx_list, &stlc->tx_sent); + + return 0; +} + +/* caller must hold mutex */ +static void stlc45xx_tx_psm(struct stlc45xx *stlc, bool enable) +{ + struct s_lm_control *control; + struct s_lmo_psm *psm; + size_t len, psm_len; + + WARN_ON(!stlc->associated); + WARN_ON(stlc->aid < 1); + WARN_ON(stlc->aid > 2007); + + psm_len = sizeof(*psm); + len = sizeof(*control) + psm_len; + control = kzalloc(len, GFP_KERNEL); + psm = (struct s_lmo_psm *) (control + 1); + + control->flags = LM_FLAG_CONTROL | LM_CTRL_OPSET; + control->length = psm_len; + control->oid = LM_OID_PSM; + + if (enable) + psm->flags |= LM_PSM; + + psm->aid = stlc->aid; + + psm->beacon_rcpi_skip_max = 60; + + psm->intervals[0].interval = 1; + psm->intervals[0].periods = 1; + psm->intervals[1].interval = 1; + psm->intervals[1].periods = 1; + psm->intervals[2].interval = 1; + psm->intervals[2].periods = 1; + psm->intervals[3].interval = 1; + psm->intervals[3].periods = 1; + + psm->nr = 0; + psm->exclude[0] = 0; + + stlc45xx_debug(DEBUG_PSM, "sending LM_OID_PSM (aid %d, interval %d)", + psm->aid, psm->intervals[0].interval); + + stlc45xx_tx_frame(stlc, FIRMWARE_CONFIG_START, control, len); + + kfree(control); +} + +static int stlc45xx_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct stlc45xx *stlc = hw->priv; + struct ieee80211_tx_info *info; + struct ieee80211_rate *rate; + int payload_len, padding, i; + struct s_lm_data_out *data; + struct txbuffer *entry; + char *payload; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + spin_lock_bh(&stlc->tx_lock); + + entry = stlc45xx_txbuffer_alloc(stlc, skb->len); + if (!entry) { + /* the queue should be stopped before the firmware buffer + * is full, so firmware buffer should always have enough + * space */ + if (net_ratelimit()) + stlc45xx_warning("firmware buffer full"); + spin_unlock_bh(&stlc->tx_lock); + return NETDEV_TX_BUSY; + } + + info = IEEE80211_SKB_CB(skb); + + payload = skb->data; + payload_len = skb->len; + padding = (int) (skb->data - sizeof(*data)) & 3; + entry->header_len = sizeof(*data) + padding; + + entry->skb = skb; + entry->status_needed = true; + entry->handle = (u32) skb; + entry->lifetime = jiffies + msecs_to_jiffies(TX_FRAME_LIFETIME); + + stlc45xx_debug(DEBUG_TX, "tx data 0x%x (0x%p payload %d B " + "padding %d header_len %d)", + entry->handle, payload, payload_len, padding, + entry->header_len); + stlc45xx_dump(DEBUG_TX_CONTENT, payload, payload_len); + + data = (struct s_lm_data_out *) skb_push(skb, entry->header_len); + + memset(data, 0, entry->header_len); + + if (padding) + data->flags = LM_FLAG_ALIGN; + + data->flags = LM_OUT_BURST; + data->length = payload_len; + data->handle = entry->handle; + data->aid = 1; + data->rts_retries = 7; + data->retries = 7; + data->aloft_ctrl = 0; + data->crypt_offset = 58; + data->keytype = 0; + data->keylen = 0; + data->queue = 2; + data->backlog = 32; + data->antenna = 2; + data->cts = 3; + data->power = 127; + + for (i = 0; i < 8; i++) { + rate = ieee80211_get_tx_rate(stlc->hw, info); + data->aloft[i] = rate->hw_value; + } + + list_add_tail(&entry->tx_list, &stlc->tx_pending); + + /* check if there's enough space in tx buffer */ + if (stlc45xx_txbuffer_find(stlc, MAX_FRAME_LEN) == -1) { + stlc45xx_debug(DEBUG_QUEUE, "tx buffer full, stopping queues"); + stlc->tx_queue_stopped = 1; + ieee80211_stop_queues(stlc->hw); + } + + queue_work(stlc->hw->workqueue, &stlc->work); + + spin_unlock_bh(&stlc->tx_lock); + + return NETDEV_TX_OK; +} + +static int stlc45xx_op_start(struct ieee80211_hw *hw) +{ + struct stlc45xx *stlc = hw->priv; + unsigned long timeout; + int ret = 0; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + stlc->fw_state = FW_STATE_BOOTING; + stlc->channel = 1; + + stlc45xx_power_on(stlc); + + ret = stlc45xx_upload_firmware(stlc); + if (ret < 0) { + stlc45xx_power_off(stlc); + goto out_unlock; + } + + stlc->tx_queue_stopped = 0; + + mutex_unlock(&stlc->mutex); + + timeout = msecs_to_jiffies(2000); + timeout = wait_for_completion_interruptible_timeout(&stlc->fw_comp, + timeout); + if (!timeout) { + stlc45xx_error("firmware boot failed"); + stlc45xx_power_off(stlc); + ret = -1; + goto out; + } + + stlc45xx_debug(DEBUG_BOOT, "firmware booted"); + + /* FIXME: should we take mutex just after wait_for_completion()? */ + mutex_lock(&stlc->mutex); + + WARN_ON(stlc->fw_state != FW_STATE_READY); + +out_unlock: + mutex_unlock(&stlc->mutex); + +out: + return ret; +} + +static void stlc45xx_op_stop(struct ieee80211_hw *hw) +{ + struct stlc45xx *stlc = hw->priv; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + WARN_ON(stlc->fw_state != FW_STATE_READY); + + stlc45xx_power_off(stlc); + + /* FIXME: make sure that all work_structs have completed */ + + spin_lock_bh(&stlc->tx_lock); + stlc45xx_flush_queues(stlc); + spin_unlock_bh(&stlc->tx_lock); + + stlc->fw_state = FW_STATE_OFF; + + mutex_unlock(&stlc->mutex); +} + +static int stlc45xx_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct stlc45xx *stlc = hw->priv; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + switch (conf->type) { + case NL80211_IFTYPE_STATION: + break; + default: + return -EOPNOTSUPP; + } + + memcpy(stlc->mac_addr, conf->mac_addr, ETH_ALEN); + + return 0; +} + +static void stlc45xx_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); +} + +static int stlc45xx_op_config_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_if_conf *conf) +{ + struct stlc45xx *stlc = hw->priv; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + memcpy(stlc->bssid, conf->bssid, ETH_ALEN); + stlc45xx_tx_setup(stlc); + + mutex_unlock(&stlc->mutex); + + return 0; +} + +static int stlc45xx_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct stlc45xx *stlc = hw->priv; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + mutex_lock(&stlc->mutex); + + stlc->channel = hw->conf.channel->hw_value; + stlc45xx_tx_scan(stlc); + stlc45xx_tx_setup(stlc); + stlc45xx_tx_edcf(stlc); + + if ((hw->conf.flags & IEEE80211_CONF_PS) != stlc->psm) { + stlc->psm = hw->conf.flags & IEEE80211_CONF_PS; + if (stlc->associated) { + stlc45xx_tx_psm(stlc, stlc->psm); + stlc45xx_tx_nullfunc(stlc, stlc->psm); + } + } + + mutex_unlock(&stlc->mutex); + + return 0; +} + +static void stlc45xx_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + int mc_count, + struct dev_addr_list *mc_list) +{ + *total_flags = 0; +} + +static void stlc45xx_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct stlc45xx *stlc = hw->priv; + + if (changed & BSS_CHANGED_ASSOC) { + stlc->associated = info->assoc; + if (info->assoc) + stlc->aid = info->aid; + else + stlc->aid = -1; + + if (stlc->psm) { + stlc45xx_tx_psm(stlc, stlc->psm); + stlc45xx_tx_nullfunc(stlc, stlc->psm); + } + } +} + + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_rate stlc45xx_rates[] = { + { .bitrate = 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 20, .hw_value = 1, .hw_value_short = 1, }, + { .bitrate = 55, .hw_value = 2, .hw_value_short = 2, }, + { .bitrate = 110, .hw_value = 3, .hw_value_short = 3, }, + { .bitrate = 60, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 90, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 120, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 180, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 240, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 360, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 480, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 540, .hw_value = 11, .hw_value_short = 11, }, +}; + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_channel stlc45xx_channels[] = { + { .hw_value = 1, .center_freq = 2412}, + { .hw_value = 2, .center_freq = 2417}, + { .hw_value = 3, .center_freq = 2422}, + { .hw_value = 4, .center_freq = 2427}, + { .hw_value = 5, .center_freq = 2432}, + { .hw_value = 6, .center_freq = 2437}, + { .hw_value = 7, .center_freq = 2442}, + { .hw_value = 8, .center_freq = 2447}, + { .hw_value = 9, .center_freq = 2452}, + { .hw_value = 10, .center_freq = 2457}, + { .hw_value = 11, .center_freq = 2462}, + { .hw_value = 12, .center_freq = 2467}, + { .hw_value = 13, .center_freq = 2472}, +}; + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_supported_band stlc45xx_band_2ghz = { + .channels = stlc45xx_channels, + .n_channels = ARRAY_SIZE(stlc45xx_channels), + .bitrates = stlc45xx_rates, + .n_bitrates = ARRAY_SIZE(stlc45xx_rates), +}; + +static const struct ieee80211_ops stlc45xx_ops = { + .start = stlc45xx_op_start, + .stop = stlc45xx_op_stop, + .add_interface = stlc45xx_op_add_interface, + .remove_interface = stlc45xx_op_remove_interface, + .config = stlc45xx_op_config, + .config_interface = stlc45xx_op_config_interface, + .configure_filter = stlc45xx_op_configure_filter, + .tx = stlc45xx_op_tx, + .bss_info_changed = stlc45xx_op_bss_info_changed, +}; + +static int stlc45xx_register_mac80211(struct stlc45xx *stlc) +{ + /* FIXME: SET_IEEE80211_PERM_ADDR() requires default_mac_addr + to be non-const for some strange reason */ + static u8 default_mac_addr[ETH_ALEN] = { + 0x00, 0x02, 0xee, 0xc0, 0xff, 0xee + }; + int ret; + + SET_IEEE80211_PERM_ADDR(stlc->hw, default_mac_addr); + + ret = ieee80211_register_hw(stlc->hw); + if (ret) { + stlc45xx_error("unable to register mac80211 hw: %d", ret); + return ret; + } + + return 0; +} + +static void stlc45xx_device_release(struct device *dev) +{ + +} + +static struct platform_device stlc45xx_device = { + .name = "stlc45xx", + .id = -1, + + /* device model insists to have a release function */ + .dev = { + .release = stlc45xx_device_release, + }, +}; + +static int __devinit stlc45xx_probe(struct spi_device *spi) +{ + struct stlc45xx *stlc; + struct ieee80211_hw *hw; + int ret; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + /* mac80211 alloc */ + hw = ieee80211_alloc_hw(sizeof(*stlc), &stlc45xx_ops); + if (!hw) { + stlc45xx_error("could not alloc ieee80211_hw"); + ret = -ENOMEM; + goto out; + } + + /* mac80211 clears hw->priv */ + stlc = hw->priv; + + stlc->hw = hw; + dev_set_drvdata(&spi->dev, stlc); + stlc->spi = spi; + + spi->bits_per_word = 16; + spi->max_speed_hz = 24000000; + + ret = spi_setup(spi); + if (ret < 0) + stlc45xx_error("spi_setup failed"); + + ret = gpio_request(stlc45xx_gpio_power, "stlc45xx power"); + if (ret < 0) { + stlc45xx_error("power GPIO request failed: %d", ret); + return ret; + } + + ret = gpio_request(stlc45xx_gpio_irq, "stlc45xx irq"); + if (ret < 0) { + stlc45xx_error("irq GPIO request failed: %d", ret); + goto out; + } + + gpio_direction_output(stlc45xx_gpio_power, 0); + gpio_direction_input(stlc45xx_gpio_irq); + + ret = request_irq(gpio_to_irq(stlc45xx_gpio_irq), + stlc45xx_interrupt, IRQF_DISABLED, "stlc45xx", + stlc->spi); + if (ret < 0) + /* FIXME: handle the error */ + stlc45xx_error("request_irq() failed"); + + set_irq_type(gpio_to_irq(stlc45xx_gpio_irq), + IRQ_TYPE_EDGE_RISING); + + disable_irq(gpio_to_irq(stlc45xx_gpio_irq)); + + ret = platform_device_register(&stlc45xx_device); + if (ret) { + stlc45xx_error("Couldn't register wlan_omap device."); + return ret; + } + dev_set_drvdata(&stlc45xx_device.dev, stlc); + + INIT_WORK(&stlc->work, stlc45xx_work); + INIT_WORK(&stlc->work_reset, stlc45xx_work_reset); + INIT_DELAYED_WORK(&stlc->work_tx_timeout, stlc45xx_work_tx_timeout); + mutex_init(&stlc->mutex); + init_completion(&stlc->fw_comp); + spin_lock_init(&stlc->tx_lock); + INIT_LIST_HEAD(&stlc->txbuffer); + INIT_LIST_HEAD(&stlc->tx_pending); + INIT_LIST_HEAD(&stlc->tx_sent); + + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_NOISE_DBM; + /* four bytes for padding */ + hw->extra_tx_headroom = sizeof(struct s_lm_data_out) + 4; + + /* unit us */ + hw->channel_change_time = 1000; + + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &stlc45xx_band_2ghz; + + SET_IEEE80211_DEV(hw, &spi->dev); + + BUILD_BUG_ON(sizeof(default_cal_rssi) != RSSI_CAL_ARRAY_LEN); + BUILD_BUG_ON(sizeof(default_cal_channels) != CHANNEL_CAL_ARRAY_LEN); + + stlc->cal_rssi = kmemdup(default_cal_rssi, RSSI_CAL_ARRAY_LEN, + GFP_KERNEL); + stlc->cal_channels = kmemdup(default_cal_channels, + CHANNEL_CAL_ARRAY_LEN, + GFP_KERNEL); + + ret = device_create_file(&stlc45xx_device.dev, &dev_attr_cal_rssi); + if (ret < 0) { + stlc45xx_error("failed to create sysfs file cal_rssi"); + goto out; + } + + ret = device_create_file(&stlc45xx_device.dev, &dev_attr_cal_channels); + if (ret < 0) { + stlc45xx_error("failed to create sysfs file cal_channels"); + goto out; + } + + ret = device_create_file(&stlc45xx_device.dev, &dev_attr_tx_buf); + if (ret < 0) { + stlc45xx_error("failed to create sysfs file tx_buf"); + goto out; + } + + ret = stlc45xx_register_mac80211(stlc); + if (ret < 0) + goto out; + + stlc45xx_info("v" DRIVER_VERSION " loaded"); + + stlc45xx_info("config buffer 0x%x-0x%x", + FIRMWARE_CONFIG_START, FIRMWARE_CONFIG_END); + stlc45xx_info("tx 0x%x-0x%x, rx 0x%x-0x%x", + FIRMWARE_TXBUFFER_START, FIRMWARE_TXBUFFER_END, + FIRMWARE_RXBUFFER_START, FIRMWARE_RXBUFFER_END); + +out: + return ret; +} + +static int __devexit stlc45xx_remove(struct spi_device *spi) +{ + struct stlc45xx *stlc = dev_get_drvdata(&spi->dev); + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + platform_device_unregister(&stlc45xx_device); + + ieee80211_unregister_hw(stlc->hw); + + free_irq(gpio_to_irq(stlc45xx_gpio_irq), spi); + + gpio_free(stlc45xx_gpio_power); + gpio_free(stlc45xx_gpio_irq); + + /* FIXME: free cal_channels and cal_rssi? */ + + kfree(stlc->fw); + + mutex_destroy(&stlc->mutex); + + /* frees also stlc */ + ieee80211_free_hw(stlc->hw); + stlc = NULL; + + return 0; +} + + +static struct spi_driver stlc45xx_spi_driver = { + .driver = { + /* use cx3110x name because board-n800.c uses that for the + * SPI port */ + .name = "cx3110x", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = stlc45xx_probe, + .remove = __devexit_p(stlc45xx_remove), +}; + +static int __init stlc45xx_init(void) +{ + int ret; + + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + ret = spi_register_driver(&stlc45xx_spi_driver); + if (ret < 0) { + stlc45xx_error("failed to register SPI driver: %d", ret); + goto out; + } + +out: + return ret; +} + +static void __exit stlc45xx_exit(void) +{ + stlc45xx_debug(DEBUG_FUNC, "%s", __func__); + + spi_unregister_driver(&stlc45xx_spi_driver); + + stlc45xx_info("unloaded"); +} + +module_init(stlc45xx_init); +module_exit(stlc45xx_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kalle Valo "); diff --git a/drivers/staging/stlc45xx/stlc45xx.h b/drivers/staging/stlc45xx/stlc45xx.h new file mode 100644 index 000000000000..ac96bbbde79f --- /dev/null +++ b/drivers/staging/stlc45xx/stlc45xx.h @@ -0,0 +1,283 @@ +/* + * This file is part of stlc45xx + * + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Kalle Valo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "stlc45xx_lmac.h" + +#define DRIVER_NAME "stlc45xx" +#define DRIVER_VERSION "0.1.3" + +#define DRIVER_PREFIX DRIVER_NAME ": " + +enum { + DEBUG_NONE = 0, + DEBUG_FUNC = 1 << 0, + DEBUG_IRQ = 1 << 1, + DEBUG_BH = 1 << 2, + DEBUG_RX = 1 << 3, + DEBUG_RX_CONTENT = 1 << 5, + DEBUG_TX = 1 << 6, + DEBUG_TX_CONTENT = 1 << 8, + DEBUG_TXBUFFER = 1 << 9, + DEBUG_QUEUE = 1 << 10, + DEBUG_BOOT = 1 << 11, + DEBUG_PSM = 1 << 12, + DEBUG_ALL = ~0, +}; + +#define DEBUG_LEVEL DEBUG_NONE +/* #define DEBUG_LEVEL DEBUG_ALL */ +/* #define DEBUG_LEVEL (DEBUG_TX | DEBUG_RX | DEBUG_IRQ) */ +/* #define DEBUG_LEVEL (DEBUG_TX | DEBUG_MEMREGION | DEBUG_QUEUE) */ +/* #define DEBUG_LEVEL (DEBUG_MEMREGION | DEBUG_QUEUE) */ + +#define stlc45xx_error(fmt, arg...) \ + printk(KERN_ERR DRIVER_PREFIX "ERROR " fmt "\n", ##arg) + +#define stlc45xx_warning(fmt, arg...) \ + printk(KERN_WARNING DRIVER_PREFIX "WARNING " fmt "\n", ##arg) + +#define stlc45xx_info(fmt, arg...) \ + printk(KERN_INFO DRIVER_PREFIX fmt "\n", ##arg) + +#define stlc45xx_debug(level, fmt, arg...) \ + do { \ + if (level & DEBUG_LEVEL) \ + printk(KERN_DEBUG DRIVER_PREFIX fmt "\n", ##arg); \ + } while (0) + +#define stlc45xx_dump(level, buf, len) \ + do { \ + if (level & DEBUG_LEVEL) \ + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, \ + 16, 1, buf, len, 1); \ + } while (0) + +#define MAC2STR(a) ((a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]) +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + +/* Bit 15 is read/write bit; ON = READ, OFF = WRITE */ +#define ADDR_READ_BIT_15 0x8000 + +#define SPI_ADRS_ARM_INTERRUPTS 0x00 +#define SPI_ADRS_ARM_INT_EN 0x04 + +#define SPI_ADRS_HOST_INTERRUPTS 0x08 +#define SPI_ADRS_HOST_INT_EN 0x0c +#define SPI_ADRS_HOST_INT_ACK 0x10 + +#define SPI_ADRS_GEN_PURP_1 0x14 +#define SPI_ADRS_GEN_PURP_2 0x18 + +/* high word */ +#define SPI_ADRS_DEV_CTRL_STAT 0x26 + +#define SPI_ADRS_DMA_DATA 0x28 + +#define SPI_ADRS_DMA_WRITE_CTRL 0x2c +#define SPI_ADRS_DMA_WRITE_LEN 0x2e +#define SPI_ADRS_DMA_WRITE_BASE 0x30 + +#define SPI_ADRS_DMA_READ_CTRL 0x34 +#define SPI_ADRS_DMA_READ_LEN 0x36 +#define SPI_ADRS_DMA_READ_BASE 0x38 + +#define SPI_CTRL_STAT_HOST_OVERRIDE 0x8000 +#define SPI_CTRL_STAT_START_HALTED 0x4000 +#define SPI_CTRL_STAT_RAM_BOOT 0x2000 +#define SPI_CTRL_STAT_HOST_RESET 0x1000 +#define SPI_CTRL_STAT_HOST_CPU_EN 0x0800 + +#define SPI_DMA_WRITE_CTRL_ENABLE 0x0001 +#define SPI_DMA_READ_CTRL_ENABLE 0x0001 +#define HOST_ALLOWED (1 << 7) + +#define FIRMWARE_ADDRESS 0x20000 + +#define SPI_TIMEOUT 100 /* msec */ + +#define SPI_MAX_TX_PACKETS 32 + +#define SPI_MAX_PACKET_SIZE 32767 + +#define SPI_TARGET_INT_WAKEUP 0x00000001 +#define SPI_TARGET_INT_SLEEP 0x00000002 +#define SPI_TARGET_INT_RDDONE 0x00000004 + +#define SPI_TARGET_INT_CTS 0x00004000 +#define SPI_TARGET_INT_DR 0x00008000 + +#define SPI_HOST_INT_READY 0x00000001 +#define SPI_HOST_INT_WR_READY 0x00000002 +#define SPI_HOST_INT_SW_UPDATE 0x00000004 +#define SPI_HOST_INT_UPDATE 0x10000000 + +/* clear to send */ +#define SPI_HOST_INT_CTS 0x00004000 + +/* data ready */ +#define SPI_HOST_INT_DR 0x00008000 + +#define SPI_HOST_INTS_DEFAULT \ + (SPI_HOST_INT_READY | SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE) + +#define TARGET_BOOT_SLEEP 50 + +/* The firmware buffer is divided into three areas: + * + * o config area (for control commands) + * o tx buffer + * o rx buffer + */ +#define FIRMWARE_BUFFER_START 0x20200 +#define FIRMWARE_BUFFER_END 0x27c60 +#define FIRMWARE_BUFFER_LEN (FIRMWARE_BUFFER_END - FIRMWARE_BUFFER_START) +#define FIRMWARE_MTU 3240 +#define FIRMWARE_CONFIG_PAYLOAD_LEN 1024 +#define FIRMWARE_CONFIG_START FIRMWARE_BUFFER_START +#define FIRMWARE_CONFIG_LEN (sizeof(struct s_lm_control) + \ + FIRMWARE_CONFIG_PAYLOAD_LEN) +#define FIRMWARE_CONFIG_END (FIRMWARE_CONFIG_START + FIRMWARE_CONFIG_LEN - 1) +#define FIRMWARE_RXBUFFER_LEN (5 * FIRMWARE_MTU + 1024) +#define FIRMWARE_RXBUFFER_START (FIRMWARE_BUFFER_END - FIRMWARE_RXBUFFER_LEN) +#define FIRMWARE_RXBUFFER_END (FIRMWARE_RXBUFFER_START + \ + FIRMWARE_RXBUFFER_LEN - 1) +#define FIRMWARE_TXBUFFER_START (FIRMWARE_BUFFER_START + FIRMWARE_CONFIG_LEN) +#define FIRMWARE_TXBUFFER_LEN (FIRMWARE_BUFFER_LEN - FIRMWARE_CONFIG_LEN - \ + FIRMWARE_RXBUFFER_LEN) +#define FIRMWARE_TXBUFFER_END (FIRMWARE_TXBUFFER_START + \ + FIRMWARE_TXBUFFER_LEN - 1) + +#define FIRMWARE_TXBUFFER_HEADER 100 +#define FIRMWARE_TXBUFFER_TRAILER 4 + +/* FIXME: come up with a proper value */ +#define MAX_FRAME_LEN 2500 + +/* unit is ms */ +#define TX_FRAME_LIFETIME 2000 +#define TX_TIMEOUT 4000 + +#define SUPPORTED_CHANNELS 13 + +/* FIXME */ +/* #define CHANNEL_CAL_LEN offsetof(struct s_lmo_scan, bratemask) - \ */ +/* offsetof(struct s_lmo_scan, channel) */ +#define CHANNEL_CAL_LEN 292 +#define CHANNEL_CAL_ARRAY_LEN (SUPPORTED_CHANNELS * CHANNEL_CAL_LEN) +/* FIXME */ +/* #define RSSI_CAL_LEN sizeof(struct s_lmo_scan) - \ */ +/* offsetof(struct s_lmo_scan, rssical) */ +#define RSSI_CAL_LEN 8 +#define RSSI_CAL_ARRAY_LEN (SUPPORTED_CHANNELS * RSSI_CAL_LEN) + +struct s_dma_regs { + unsigned short cmd; + unsigned short len; + unsigned long addr; +}; + +struct stlc45xx_ie_tim { + u8 dtim_count; + u8 dtim_period; + u8 bmap_control; + u8 pvbmap[251]; +}; + +struct txbuffer { + /* can be removed when switched to skb queue */ + struct list_head tx_list; + + struct list_head buffer_list; + + int start; + int frame_start; + int end; + + struct sk_buff *skb; + u32 handle; + + bool status_needed; + + int header_len; + + /* unit jiffies */ + unsigned long lifetime; +}; + +enum fw_state { + FW_STATE_OFF, + FW_STATE_BOOTING, + FW_STATE_READY, + FW_STATE_RESET, + FW_STATE_RESETTING, +}; + +struct stlc45xx { + struct ieee80211_hw *hw; + struct spi_device *spi; + struct work_struct work; + struct work_struct work_reset; + struct delayed_work work_tx_timeout; + struct mutex mutex; + struct completion fw_comp; + + + u8 bssid[ETH_ALEN]; + u8 mac_addr[ETH_ALEN]; + int channel; + + u8 *cal_rssi; + u8 *cal_channels; + + enum fw_state fw_state; + + spinlock_t tx_lock; + + /* protected by tx_lock */ + struct list_head txbuffer; + + /* protected by tx_lock */ + struct list_head tx_pending; + + /* protected by tx_lock */ + int tx_queue_stopped; + + /* protected by mutex */ + struct list_head tx_sent; + + int tx_frames; + + u8 *fw; + int fw_len; + + bool psm; + bool associated; + int aid; + bool pspolling; +}; + + diff --git a/drivers/staging/stlc45xx/stlc45xx_lmac.h b/drivers/staging/stlc45xx/stlc45xx_lmac.h new file mode 100644 index 000000000000..af5db801347f --- /dev/null +++ b/drivers/staging/stlc45xx/stlc45xx_lmac.h @@ -0,0 +1,434 @@ +/************************************************************************ +* This is the LMAC API interface header file for STLC4560. * +* Copyright (C) 2007 Conexant Systems, Inc. * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU General Public License * +* as published by the Free Software Foundation; either version 2 * +* of the License, or (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program. If not, see .* +*************************************************************************/ + +#ifndef __lmac_h__ +#define __lmac_h__ + +#define LM_TOP_VARIANT 0x0506 +#define LM_BOTTOM_VARIANT 0x0506 + +/* + * LMAC - UMAC interface definition: + */ + +#define LM_FLAG_CONTROL 0x8000 +#define LM_FLAG_ALIGN 0x4000 + +#define LM_CTRL_OPSET 0x0001 + +#define LM_OUT_PROMISC 0x0001 +#define LM_OUT_TIMESTAMP 0x0002 +#define LM_OUT_SEQNR 0x0004 +#define LM_OUT_BURST 0x0010 +#define LM_OUT_NOCANCEL 0x0020 +#define LM_OUT_CLEARTIM 0x0040 +#define LM_OUT_HITCHHIKE 0x0080 +#define LM_OUT_COMPRESS 0x0100 +#define LM_OUT_CONCAT 0x0200 +#define LM_OUT_PCS_ACCEPT 0x0400 +#define LM_OUT_WAITEOSP 0x0800 + + +#define LM_ALOFT_SP 0x10 +#define LM_ALOFT_CTS 0x20 +#define LM_ALOFT_RTS 0x40 +#define LM_ALOFT_MASK 0x1f +#define LM_ALOFT_RATE 0x0f + +#define LM_IN_FCS_GOOD 0x0001 +#define LM_IN_MATCH_MAC 0x0002 +#define LM_IN_MCBC 0x0004 +#define LM_IN_BEACON 0x0008 +#define LM_IN_MATCH_BSS 0x0010 +#define LM_IN_BCAST_BSS 0x0020 +#define LM_IN_DATA 0x0040 +#define LM_IN_TRUNCATED 0x0080 + +#define LM_IN_TRANSPARENT 0x0200 + +#define LM_QUEUE_BEACON 0 +#define LM_QUEUE_SCAN 1 +#define LM_QUEUE_MGT 2 +#define LM_QUEUE_MCBC 3 +#define LM_QUEUE_DATA 4 +#define LM_QUEUE_DATA0 4 +#define LM_QUEUE_DATA1 5 +#define LM_QUEUE_DATA2 6 +#define LM_QUEUE_DATA3 7 + +#define LM_SETUP_INFRA 0x0001 +#define LM_SETUP_IBSS 0x0002 +#define LM_SETUP_TRANSPARENT 0x0008 +#define LM_SETUP_PROMISCUOUS 0x0010 +#define LM_SETUP_HIBERNATE 0x0020 +#define LM_SETUP_NOACK 0x0040 +#define LM_SETUP_RX_DISABLED 0x0080 + +#define LM_ANTENNA_0 0 +#define LM_ANTENNA_1 1 +#define LM_ANTENNA_DIVERSITY 2 + +#define LM_TX_FAILED 0x0001 +#define LM_TX_PSM 0x0002 +#define LM_TX_PSM_CANCELLED 0x0004 + +#define LM_SCAN_EXIT 0x0001 +#define LM_SCAN_TRAP 0x0002 +#define LM_SCAN_ACTIVE 0x0004 +#define LM_SCAN_FILTER 0x0008 + +#define LM_PSM 0x0001 +#define LM_PSM_DTIM 0x0002 +#define LM_PSM_MCBC 0x0004 +#define LM_PSM_CHECKSUM 0x0008 +#define LM_PSM_SKIP_MORE_DATA 0x0010 +#define LM_PSM_BEACON_TIMEOUT 0x0020 +#define LM_PSM_HFOSLEEP 0x0040 +#define LM_PSM_AUTOSWITCH_SLEEP 0x0080 +#define LM_PSM_LPIT 0x0100 +#define LM_PSM_BF_UCAST_SKIP 0x0200 +#define LM_PSM_BF_MCAST_SKIP 0x0400 + +/* hfosleep */ +#define LM_PSM_SLEEP_OPTION_MASK (LM_PSM_AUTOSWITCH_SLEEP | LM_PSM_HFOSLEEP) +#define LM_PSM_SLEEP_OPTION_SHIFT 6 +/* hfosleepend */ +#define LM_PSM_BF_OPTION_MASK (LM_PSM_BF_MCAST_SKIP | LM_PSM_BF_UCAST_SKIP) +#define LM_PSM_BF_OPTION_SHIFT 9 + + +#define LM_PRIVACC_WEP 0x01 +#define LM_PRIVACC_TKIP 0x02 +#define LM_PRIVACC_MICHAEL 0x04 +#define LM_PRIVACC_CCX_KP 0x08 +#define LM_PRIVACC_CCX_MIC 0x10 +#define LM_PRIVACC_AES_CCMP 0x20 + +/* size of s_lm_descr in words */ +#define LM_DESCR_SIZE_WORDS 11 + +#ifndef __ASSEMBLER__ + +enum { + LM_MODE_CLIENT = 0, + LM_MODE_AP +}; + +struct s_lm_descr { + uint16_t modes; + uint16_t flags; + uint32_t buffer_start; + uint32_t buffer_end; + uint8_t header; + uint8_t trailer; + uint8_t tx_queues; + uint8_t tx_depth; + uint8_t privacy; + uint8_t rx_keycache; + uint8_t tim_size; + uint8_t pad1; + uint8_t rates[16]; + uint32_t link; + uint16_t mtu; +}; + + +struct s_lm_control { + uint16_t flags; + uint16_t length; + uint32_t handle; + uint16_t oid; + uint16_t pad; + /* uint8_t data[]; */ +}; + +enum { + LM_PRIV_NONE = 0, + LM_PRIV_WEP, + LM_PRIV_TKIP, + LM_PRIV_TKIPMICHAEL, + LM_PRIV_CCX_WEPMIC, + LM_PRIV_CCX_KPMIC, + LM_PRIV_CCX_KP, + LM_PRIV_AES_CCMP +}; + +enum { + LM_DECRYPT_NONE, + LM_DECRYPT_OK, + LM_DECRYPT_NOKEY, + LM_DECRYPT_NOMICHAEL, + LM_DECRYPT_NOCKIPMIC, + LM_DECRYPT_FAIL_WEP, + LM_DECRYPT_FAIL_TKIP, + LM_DECRYPT_FAIL_MICHAEL, + LM_DECRYPT_FAIL_CKIPKP, + LM_DECRYPT_FAIL_CKIPMIC, + LM_DECRYPT_FAIL_AESCCMP +}; + +struct s_lm_data_out { + uint16_t flags; + uint16_t length; + uint32_t handle; + uint16_t aid; + uint8_t rts_retries; + uint8_t retries; + uint8_t aloft[8]; + uint8_t aloft_ctrl; + uint8_t crypt_offset; + uint8_t keytype; + uint8_t keylen; + uint8_t key[16]; + uint8_t queue; + uint8_t backlog; + uint16_t durations[4]; + uint8_t antenna; + uint8_t cts; + int16_t power; + uint8_t pad[2]; + /*uint8_t data[];*/ +}; + +#define LM_RCPI_INVALID (0xff) + +struct s_lm_data_in { + uint16_t flags; + uint16_t length; + uint16_t frequency; + uint8_t antenna; + uint8_t rate; + uint8_t rcpi; + uint8_t sq; + uint8_t decrypt; + uint8_t rssi_raw; + uint32_t clock[2]; + /*uint8_t data[];*/ +}; + +union u_lm_data { + struct s_lm_data_out out; + struct s_lm_data_in in; +}; + +enum { + LM_OID_SETUP = 0, + LM_OID_SCAN = 1, + LM_OID_TRAP = 2, + LM_OID_EDCF = 3, + LM_OID_KEYCACHE = 4, + LM_OID_PSM = 6, + LM_OID_TXCANCEL = 7, + LM_OID_TX = 8, + LM_OID_BURST = 9, + LM_OID_STATS = 10, + LM_OID_LED = 13, + LM_OID_TIMER = 15, + LM_OID_NAV = 20, + LM_OID_PCS = 22, + LM_OID_BT_BALANCER = 28, + LM_OID_GROUP_ADDRESS_TABLE = 30, + LM_OID_ARPTABLE = 31, + LM_OID_BT_OPTIONS = 35 +}; + +enum { + LM_FRONTEND_UNKNOWN = 0, + LM_FRONTEND_DUETTE3, + LM_FRONTEND_DUETTE2, + LM_FRONTEND_FRISBEE, + LM_FRONTEND_CROSSBOW, + LM_FRONTEND_LONGBOW +}; + + +#define INVALID_LPF_BANDWIDTH 0xffff +#define INVALID_OSC_START_DELAY 0xffff + +struct s_lmo_setup { + uint16_t flags; + uint8_t macaddr[6]; + uint8_t bssid[6]; + uint8_t antenna; + uint8_t rx_align; + uint32_t rx_buffer; + uint16_t rx_mtu; + uint16_t frontend; + uint16_t timeout; + uint16_t truncate; + uint32_t bratemask; + uint8_t sbss_offset; + uint8_t mcast_window; + uint8_t rx_rssi_threshold; + uint8_t rx_ed_threshold; + uint32_t ref_clock; + uint16_t lpf_bandwidth; + uint16_t osc_start_delay; +}; + + +struct s_lmo_scan { + uint16_t flags; + uint16_t dwell; + uint8_t channel[292]; + uint32_t bratemask; + uint8_t aloft[8]; + uint8_t rssical[8]; +}; + + +enum { + LM_TRAP_SCAN = 0, + LM_TRAP_TIMER, + LM_TRAP_BEACON_TX, + LM_TRAP_FAA_RADIO_ON, + LM_TRAP_FAA_RADIO_OFF, + LM_TRAP_RADAR, + LM_TRAP_NO_BEACON, + LM_TRAP_TBTT, + LM_TRAP_SCO_ENTER, + LM_TRAP_SCO_EXIT +}; + +struct s_lmo_trap { + uint16_t event; + uint16_t frequency; +}; + +struct s_lmo_timer { + uint32_t interval; +}; + +struct s_lmo_nav { + uint32_t period; +}; + + +struct s_lmo_edcf_queue; + +struct s_lmo_edcf { + uint8_t flags; + uint8_t slottime; + uint8_t sifs; + uint8_t eofpad; + struct s_lmo_edcf_queue { + uint8_t aifs; + uint8_t pad0; + uint16_t cwmin; + uint16_t cwmax; + uint16_t txop; + } queues[8]; + uint8_t mapping[4]; + uint16_t maxburst; + uint16_t round_trip_delay; +}; + +struct s_lmo_keycache { + uint8_t entry; + uint8_t keyid; + uint8_t address[6]; + uint8_t pad[2]; + uint8_t keytype; + uint8_t keylen; + uint8_t key[24]; +}; + + +struct s_lm_interval; + +struct s_lmo_psm { + uint16_t flags; + uint16_t aid; + struct s_lm_interval { + uint16_t interval; + uint16_t periods; + } intervals[4]; + /* uint16_t pad; */ + uint8_t beacon_rcpi_skip_max; + uint8_t rcpi_delta_threshold; + uint8_t nr; + uint8_t exclude[1]; +}; + +#define MC_FILTER_ADDRESS_NUM 4 + +struct s_lmo_group_address_table { + uint16_t filter_enable; + uint16_t num_address; + uint8_t macaddr_list[MC_FILTER_ADDRESS_NUM][6]; +}; + +struct s_lmo_txcancel { + uint32_t address[1]; +}; + + +struct s_lmo_tx { + uint8_t flags; + uint8_t retries; + uint8_t rcpi; + uint8_t sq; + uint16_t seqctrl; + uint8_t antenna; + uint8_t pad; +}; + +struct s_lmo_burst { + uint8_t flags; + uint8_t queue; + uint8_t backlog; + uint8_t pad; + uint16_t durations[32]; +}; + +struct s_lmo_stats { + uint32_t valid; + uint32_t fcs; + uint32_t abort; + uint32_t phyabort; + uint32_t rts_success; + uint32_t rts_fail; + uint32_t timestamp; + uint32_t time_tx; + uint32_t noisefloor; + uint32_t sample_noise[8]; + uint32_t sample_cca; + uint32_t sample_tx; +}; + + +struct s_lmo_led { + uint16_t flags; + uint16_t mask[2]; + uint16_t delay/*[2]*/; +}; + + +struct s_lmo_bt_balancer { + uint16_t prio_thresh; + uint16_t acl_thresh; +}; + + +struct s_lmo_arp_table { + uint16_t filter_enable; + uint32_t ipaddr; +}; + +#endif /* __ASSEMBLER__ */ + +#endif /* __lmac_h__ */ -- 2.20.1