diff options
Diffstat (limited to 'drivers/net')
103 files changed, 43450 insertions, 143 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 231eeaf1d552..601215803b79 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -962,6 +962,17 @@ config ENC28J60_WRITEVERIFY Enable the verify after the buffer write useful for debugging purpose. If unsure, say N. +config NS9XXX_ETH + tristate "Digi NS9xxx ethernet support" + depends on ARM && ARCH_NS9XXX + depends on PHYLIB + depends on CRC32 + help + If you have a Digi NS9xxx based system and wish to use its ethernet + port. + If you choose to build this driver as a module, it will be called + ns9xxx-eth. + config SMC911X tristate "SMSC LAN911[5678] support" select CRC32 @@ -970,14 +981,35 @@ config SMC911X help This is a driver for SMSC's LAN911x series of Ethernet chipsets including the new LAN9115, LAN9116, LAN9117, and LAN9118. - Say Y if you want it compiled into the kernel, + Say Y if you want it compiled into the kernel, and read the Ethernet-HOWTO, available from <http://www.linuxdoc.org/docs.html#howto>. - This driver is also available as a module. The module will be - called smc911x. If you want to compile it as a module, say M + This driver is also available as a module. The module will be + called smc911x. If you want to compile it as a module, say M here and read <file:Documentation/kbuild/modules.txt> +config SMSC911X + tristate "SMSC LAN911x/LAN921x families embedded ethernet support" + depends on NET_ETHERNET + select CRC32 + select MII + ---help--- + Say Y here if you want support for SMSC LAN911x and LAN921x families + of ethernet controllers. + To compile this driver as a module, choose M here and read + <file:Documentation/networking/net-modules.txt>. The module + will be called smsc911x. + +config SMSC9118 + tristate "SMSC LAN9218 support" + depends on NET_ETHERNET && (MACH_CC9M2443JS || MACH_CCW9M2443JS || MACH_CCWMX51JS || MACH_CCMX51JS) + select CRC32 + select MII + ---help--- + Say Y here if you want support for SMSC LAN921x families + of ethernet controllers. + config NET_VENDOR_RACAL bool "Racal-Interlan (Micom) NI cards" depends on ISA @@ -1393,7 +1425,7 @@ config FORCEDETH_NAPI config CS89x0 tristate "CS89x0 support" depends on NET_ETHERNET && (ISA || EISA || MACH_IXDP2351 \ - || ARCH_IXDP2X01 || ARCH_PNX010X || MACH_MX31ADS) + || ARCH_IXDP2X01 || ARCH_PNX010X || ARCH_MXC) ---help--- Support for CS89x0 chipset based Ethernet cards. If you have a network (Ethernet) card of this type, say Y and read the @@ -1407,7 +1439,7 @@ config CS89x0 config CS89x0_NONISA_IRQ def_bool y depends on CS89x0 != n - depends on MACH_IXDP2351 || ARCH_IXDP2X01 || ARCH_PNX010X || MACH_MX31ADS + depends on MACH_IXDP2351 || ARCH_IXDP2X01 || ARCH_PNX010X || ARCH_MXC config TC35815 tristate "TOSHIBA TC35815 Ethernet support" @@ -1433,9 +1465,9 @@ config E100 select MII ---help--- This driver supports Intel(R) PRO/100 family of adapters. - To verify that your adapter is supported, find the board ID number - on the adapter. Look for a label that has a barcode and a number - in the format 123456-001 (six digits hyphen three digits). + To verify that your adapter is supported, find the board ID number + on the adapter. Look for a label that has a barcode and a number + in the format 123456-001 (six digits hyphen three digits). Use the above information and the Adapter & Driver ID Guide at: @@ -1447,7 +1479,7 @@ config E100 <http://appsr.intel.com/scripts-df/support_intel.asp> - More specific information on configuring the driver is in + More specific information on configuring the driver is in <file:Documentation/networking/e100.txt>. To compile this driver as a module, choose M here. The module @@ -1810,11 +1842,11 @@ config 68360_ENET the Motorola 68360 processor. config FEC - bool "FEC ethernet controller (of ColdFire CPUs)" - depends on M523x || M527x || M5272 || M528x || M520x + tristate "FEC ethernet controller" + depends on M523x || M527x || M5272 || M528x || M520x || ARCH_MX27 || ARCH_MX37 || ARCH_MX35 || ARCH_MX51 || ARCH_MX25 help Say Y here if you want to use the built-in 10/100 Fast ethernet - controller on some Motorola ColdFire processors. + controller on some Motorola/Freescale processors. config FEC2 bool "Second FEC ethernet controller (on some ColdFire CPUs)" @@ -1935,7 +1967,7 @@ config E1000 depends on PCI ---help--- This driver supports Intel(R) PRO/1000 gigabit ethernet family of - adapters. For more information on how to identify your adapter, go + adapters. For more information on how to identify your adapter, go to the Adapter & Driver ID Guide at: <http://support.intel.com/support/network/adapter/pro100/21397.htm> @@ -1945,7 +1977,7 @@ config E1000 <http://support.intel.com> - More specific information on configuring the driver is in + More specific information on configuring the driver is in <file:Documentation/networking/e1000.txt>. To compile this driver as a module, choose M here. The module @@ -2122,7 +2154,7 @@ config SKGE and related Gigabit Ethernet adapters. It is a new smaller driver with better performance and more complete ethtool support. - It does not support the link failover and network management + It does not support the link failover and network management features that "portable" vendor supplied sk98lin driver does. This driver supports adapters based on the original Yukon chipset: @@ -2466,7 +2498,7 @@ config IXGB <http://support.intel.com> - More specific information on configuring the driver is in + More specific information on configuring the driver is in <file:Documentation/networking/ixgb.txt>. To compile this driver as a module, choose M here. The module @@ -2476,8 +2508,8 @@ config S2IO tristate "S2IO 10Gbe XFrame NIC" depends on PCI ---help--- - This driver supports the 10Gbe XFrame NIC of S2IO. - More specific information on configuring the driver is in + This driver supports the 10Gbe XFrame NIC of S2IO. + More specific information on configuring the driver is in <file:Documentation/networking/s2io.txt>. config MYRI10GE @@ -2897,9 +2929,9 @@ config PPPOE Support for PPP over Ethernet. This driver requires the latest version of pppd from the CVS - repository at cvs.samba.org. Alternatively, see the + repository at cvs.samba.org. Alternatively, see the RoaringPenguin package (<http://www.roaringpenguin.com/pppoe>) - which contains instruction on how to use this driver (under + which contains instruction on how to use this driver (under the heading "Kernel mode PPPoE"). config PPPOATM diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 017383ad5ec6..c7cf052d08cf 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -220,18 +220,21 @@ obj-$(CONFIG_S2IO) += s2io.o obj-$(CONFIG_MYRI10GE) += myri10ge/ obj-$(CONFIG_SMC91X) += smc91x.o obj-$(CONFIG_SMC911X) += smc911x.o +obj-$(CONFIG_SMSC911X) += smsc911x.o obj-$(CONFIG_BFIN_MAC) += bfin_mac.o obj-$(CONFIG_DM9000) += dm9000.o obj-$(CONFIG_PASEMI_MAC) += pasemi_mac_driver.o pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o obj-$(CONFIG_MLX4_CORE) += mlx4/ obj-$(CONFIG_ENC28J60) += enc28j60.o +obj-$(CONFIG_NS9XXX_ETH) += ns9xxx-eth.o obj-$(CONFIG_XTENSA_XT2000_SONIC) += xtsonic.o obj-$(CONFIG_MACB) += macb.o obj-$(CONFIG_ARM) += arm/ +obj-$(CONFIG_SMSC9118) += smsc9118/ obj-$(CONFIG_DEV_APPLETALK) += appletalk/ obj-$(CONFIG_TR) += tokenring/ obj-$(CONFIG_WAN) += wan/ diff --git a/drivers/net/can/Kconfig b/drivers/net/can/Kconfig index 57def0d57371..3ac5a12a0c8b 100644 --- a/drivers/net/can/Kconfig +++ b/drivers/net/can/Kconfig @@ -12,6 +12,164 @@ config CAN_VCAN This driver can also be built as a module. If so, the module will be called vcan. +config CAN_SLCAN + tristate "Serial / USB serial CAN Adaptors (slcan)" + depends on CAN && EXPERIMENTAL + default N + ---help--- + CAN driver for several 'low cost' CAN interfaces that are attached + via serial lines or via USB-to-serial adapters using the LAWICEL + ASCII protocol. The driver implements the tty linediscipline N_SLCAN. + + This driver can also be built as a module. If so, the module + will be called slcan. + +config CAN_OLD_DRIVERS + tristate "Prompt for old CAN drivers (e.g. no sysfs support)" + depends on CAN + default N + ---help--- + The old drivers do not support sysfs nor proper platform device + support. Some of the old drivers might only be configured by + module commandline options. + +if CAN_OLD_DRIVERS +source "drivers/net/can/old/Kconfig" +endif + +config CAN_DEV + tristate "Prompt for platform CAN drivers with sysfs support" + depends on CAN && SYSFS + default Y + ---help--- + Enables the common framework for platform CAN drivers with sysfs + support. This is the standard library for CAN drivers. + If unsure, say Y. + +config CAN_CALC_BITTIMING + bool "CAN bit-timing calculation" + depends on CAN_DEV + default Y + ---help--- + If enabled, CAN bit-timing parameters will be calculated for the + bit-rate specified via SYSFS file "bitrate" when the device gets + started. This works fine for the most common CAN controllers + with standard bit-rates but may fail for exotic bit-rates or CAN + source clock frequencies. Disabling saves some space, but then the + bit-timing parameters must be specified directly using the SYSFS + files "tq", "prop_seg", "phase_seg1", "phase_seg2" and "sjw". + If unsure, say Y. + +config CAN_SJA1000 + depends on CAN_DEV + tristate "Philips SJA1000" + ---help--- + The SJA1000 is one of the top CAN controllers out there. As it + has a multiplexed interface it fits directly to 8051 + microcontrollers or into the PC I/O port space. The SJA1000 + is a full CAN controller, with shadow registers for RX and TX. + It can send and receive any kinds of CAN frames (SFF/EFF/RTR) + with a single (simple) filter setup. + + This driver will use the new device interface. + +config CAN_SJA1000_PLATFORM + depends on CAN_SJA1000 + tristate "generic Platform Bus based SJA1000 driver" + ---help--- + This driver adds support for the SJA1000 chips connected to + the "platform bus" (Linux abstraction for directly to the + processor attached devices). Which can be found on various + boards from Phytec (http://www.phytec.de) like the PCM027, + PCM038. + +config CAN_SJA1000_OF_PLATFORM + depends on CAN_SJA1000 && PPC_OF + tristate "generic OF Platform Bus based SJA1000 driver" + ---help--- + This driver adds support for the SJA1000 chips connected to + the OpenFirmware "platform bus" found on embedded systems with + OpenFirmware bindings, e.g. if you have a PowerPC based system + you should enable this option. + +config CAN_EMS_PCI + tristate "EMS CPC-PCI and CPC-PCIe Card" + depends on PCI && CAN_SJA1000 + ---help--- + This driver is for the one or two channel CPC-PCI and CPC-PCIe + cards from EMS Dr. Thomas Wuensche (http://www.ems-wuensche.de). + +config CAN_EMS_PCMCIA + tristate "EMS CPC-CARD Card" + depends on PCMCIA && CAN_SJA1000 + ---help--- + This driver is for the one or two channel CPC-CARD cards from + EMS Dr. Thomas Wuensche (http://www.ems-wuensche.de). + +config CAN_IXXAT_PCI + tristate "IXXAT PCI Card" + depends on PCI && CAN_SJA1000 + ---help--- + This driver is for the IXXAT PC-I 04/PCI card (1 or 2 channel) + from the IXXAT Automation GmbH (http://www.ixxat.de). + +config CAN_PEAK_PCI + tristate "PEAK PCAN PCI Card" + depends on PCI && CAN_SJA1000 + ---help--- + This driver is for the PCAN PCI, the PC-PCI CAN plug-in card (1 or + 2 channel) from PEAK Systems (http://www.peak-system.com). + +config CAN_PIPCAN + depends on CAN_SJA1000 + tristate "MPL PIPCAN CAN module driver (SJA1000)" + ---help--- + This driver adds support for the PIPCAN module used on some SBC + boards from MPL AG (http://www.mpl.ch). + +config CAN_KVASER_PCI + tristate "Kvaser PCIcanx and Kvaser PCIcan PCI Cards" + depends on PCI && CAN_SJA1000 + ---help--- + This driver is for the the PCIcanx and PCIcan cards (1, 2 or + 4 channel) from Kvaser (http://www.kvaser.com). + +config CAN_SOFTING + tristate "Softing Gmbh CAN generic support" + depends on CAN_DEV + ---help--- + generic softing CAN cards + +config CAN_SOFTING_CS + tristate "Softing CAN pcmcia cards" + depends on CAN_SOFTING && PCMCIA + +config CAN_MSCAN + depends on CAN_DEV && (PPC || M68K || M68KNOMMU) + tristate "Support for a Freescale MSCAN based chips" + ---help--- + The Motorola Scalable Controller Area Network (MSCAN) definition + is based on the MSCAN12 definition which is the specific + implementation of the Motorola Scalable CAN concept targeted for + the Motorola MC68HC12 Microcontroller Family. + +config CAN_MPC52XX + tristate "Freescale MPC5200 onboard CAN controller" + depends on CAN_MSCAN && (PPC_MPC52xx || PPC_52xx) + default LITE5200 + ---help--- + If you say yes here you get support for Freescale MPC5200 + onboard dualCAN controller. + + This driver can also be built as a module. If so, the module + will be called mpc52xx_can. + +config CAN_MCP251X + tristate "Microchip MCP251x SPI CAN controllers" + depends on CAN_DEV && SPI + ---help--- + Driver for the Microchip MCP251x SPI CAN controllers. + config CAN_DEBUG_DEVICES bool "CAN devices debugging messages" depends on CAN @@ -22,4 +180,13 @@ config CAN_DEBUG_DEVICES a problem with CAN support and want to see more of what is going on. +config CAN_FLEXCAN + tristate "Freescale FlexCAN" + depends on CAN && (ARCH_MX25 || ARCH_MX35) + default m + ---help--- + This select the support of Freescale CAN(FlexCAN). + This driver can also be built as a module. + If unsure, say N. + endmenu diff --git a/drivers/net/can/Makefile b/drivers/net/can/Makefile index c4bead705cd9..60c7ecca200d 100644 --- a/drivers/net/can/Makefile +++ b/drivers/net/can/Makefile @@ -1,5 +1,49 @@ # -# Makefile for the Linux Controller Area Network drivers. # + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../.. + +export CONFIG_CAN_VCAN=m +export CONFIG_CAN_DEV=m +#export CONFIG_CAN_SJA1000_OLD=m +#export CONFIG_CAN_I82527_OLD=m +export CONFIG_CAN_SJA1000=m +export CONFIG_CAN_SJA1000_PLATFORM=m +export CONFIG_CAN_EMS_PCI=m +export CONFIG_CAN_EMS_PCMCIA=m +export CONFIG_CAN_PIPCAN=m +export CONFIG_CAN_SOFTING=m +export CONFIG_CAN_SOFTING_CS=m +export CONFIG_CAN_MCP251X=m + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common + obj-$(CONFIG_CAN_VCAN) += vcan.o +obj-$(CONFIG_CAN_SLCAN) += slcan.o + +obj-$(CONFIG_CAN_DEV) += can-dev.o +can-dev-y := dev.o sysfs.o + +obj-$(CONFIG_CAN_SJA1000) += sja1000/ +obj-$(CONFIG_CAN_SOFTING) += softing/ +obj-$(CONFIG_CAN_MSCAN) += mscan/ +obj-$(CONFIG_CAN_SJA1000_OLD) += old/sja1000/ +obj-$(CONFIG_CAN_I82527_OLD) += old/i82527/ +obj-$(CONFIG_CAN_MSCAN_OLD) += old/mscan/ +obj-$(CONFIG_CAN_CCAN_OLD) += old/ccan/ +obj-$(CONFIG_CAN_MCP251X) += mcp251x.o + +ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG + +endif +obj-$(CONFIG_CAN_FLEXCAN) += flexcan/ diff --git a/drivers/net/can/dev.c b/drivers/net/can/dev.c new file mode 100644 index 000000000000..faa75d14cf29 --- /dev/null +++ b/drivers/net/can/dev.c @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2005 Marc Kleine-Budde, Pengutronix + * Copyright (C) 2006 Andrey Volkov, Varma Electronics + * Copyright (C) 2008 Wolfgang Grandegger <wg@grandegger.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <net/rtnetlink.h> + +#include "sysfs.h" + +#define MOD_DESC "CAN device driver interface" + +MODULE_DESCRIPTION(MOD_DESC); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>"); + +#ifdef CONFIG_CAN_CALC_BITTIMING +#define CAN_CALC_MAX_ERROR 50 /* in one-tenth of a percent */ + +/* + * Bit-timing calculation derived from: + * + * Code based on LinCAN sources and H8S2638 project + * Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz + * Copyright 2005 Stanislav Marek + * email: pisa@cmp.felk.cvut.cz + */ +static int can_update_spt(const struct can_bittiming_const *btc, + int sampl_pt, int tseg, int *tseg1, int *tseg2) +{ + *tseg2 = tseg + 1 - (sampl_pt * (tseg + 1)) / 1000; + if (*tseg2 < btc->tseg2_min) + *tseg2 = btc->tseg2_min; + if (*tseg2 > btc->tseg2_max) + *tseg2 = btc->tseg2_max; + *tseg1 = tseg - *tseg2; + if (*tseg1 > btc->tseg1_max) { + *tseg1 = btc->tseg1_max; + *tseg2 = tseg - *tseg1; + } + return 1000 * (tseg + 1 - *tseg2) / (tseg + 1); +} + +static int can_calc_bittiming(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->bittiming; + const struct can_bittiming_const *btc = priv->bittiming_const; + long rate, best_rate = 0; + long best_error = 1000000000, error = 0; + int best_tseg = 0, best_brp = 0, brp = 0; + int tsegall, tseg = 0, tseg1 = 0, tseg2 = 0; + int spt_error = 1000, spt = 0, sampl_pt; + u64 v64; + + if (!priv->bittiming_const) + return -ENOTSUPP; + + /* Use CIA recommended sample points */ + if (bt->sample_point) { + sampl_pt = bt->sample_point; + } else { + if (bt->bitrate > 800000) + sampl_pt = 750; + else if (bt->bitrate > 500000) + sampl_pt = 800; + else + sampl_pt = 875; + } + + /* tseg even = round down, odd = round up */ + for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1; + tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) { + tsegall = 1 + tseg / 2; + /* Compute all possible tseg choices (tseg=tseg1+tseg2) */ + brp = bt->clock / (tsegall * bt->bitrate) + tseg % 2; + /* chose brp step which is possible in system */ + brp = (brp / btc->brp_inc) * btc->brp_inc; + if ((brp < btc->brp_min) || (brp > btc->brp_max)) + continue; + rate = bt->clock / (brp * tsegall); + error = bt->bitrate - rate; + /* tseg brp biterror */ + if (error < 0) + error = -error; + if (error > best_error) + continue; + best_error = error; + if (error == 0) { + spt = can_update_spt(btc, sampl_pt, tseg / 2, + &tseg1, &tseg2); + error = sampl_pt - spt; + if (error < 0) + error = -error; + if (error > spt_error) + continue; + spt_error = error; + } + best_tseg = tseg / 2; + best_brp = brp; + best_rate = rate; + if (error == 0) + break; + } + + if (best_error) { + /* Error in one-tenth of a percent */ + error = (best_error * 1000) / bt->bitrate; + if (error > CAN_CALC_MAX_ERROR) { + dev_err(ND2D(dev), "bitrate error %ld.%ld%% too high\n", + error / 10, error % 10); + return -EDOM; + } else { + dev_warn(ND2D(dev), "bitrate error %ld.%ld%%\n", + error / 10, error % 10); + } + } + + spt = can_update_spt(btc, sampl_pt, best_tseg, &tseg1, &tseg2); + + v64 = (u64)best_brp * 1000000000UL; + do_div(v64, bt->clock); + bt->tq = (u32)v64; + bt->prop_seg = tseg1 / 2; + bt->phase_seg1 = tseg1 - bt->prop_seg; + bt->phase_seg2 = tseg2; + bt->sjw = 1; + bt->brp = best_brp; + + return 0; +} +#else /* !CONFIG_CAN_CALC_BITTIMING */ +static int can_calc_bittiming(struct net_device *dev) +{ + dev_err(ND2D(dev), "bit-timing calculation not available\n"); + return -EINVAL; +} +#endif /* CONFIG_CAN_CALC_BITTIMING */ + +int can_sample_point(struct can_bittiming *bt) +{ + return ((bt->prop_seg + bt->phase_seg1 + 1) * 1000) / + (bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1); +} + +static int can_fixup_bittiming(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->bittiming; + const struct can_bittiming_const *btc = priv->bittiming_const; + int tseg1, alltseg; + u32 bitrate; + u64 brp64; + + if (!priv->bittiming_const) + return -ENOTSUPP; + + tseg1 = bt->prop_seg + bt->phase_seg1; + if (bt->sjw > btc->sjw_max || + tseg1 < btc->tseg1_min || tseg1 > btc->tseg1_max || + bt->phase_seg2 < btc->tseg2_min || bt->phase_seg2 > btc->tseg2_max) + return -EINVAL; + + brp64 = (u64)bt->clock * (u64)bt->tq; + if (btc->brp_inc > 1) + do_div(brp64, btc->brp_inc); + brp64 += 500000000UL - 1; + do_div(brp64, 1000000000UL); /* the practicable BRP */ + if (btc->brp_inc > 1) + brp64 *= btc->brp_inc; + bt->brp = (u32)brp64; + + if (bt->brp < btc->brp_min || bt->brp > btc->brp_max) + return -EINVAL; + + alltseg = bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1; + bitrate = bt->clock / (bt->brp * alltseg); + bt->bitrate = bitrate; + + return 0; +} + +/* + * Set CAN bit-timing for the device + * + * This functions should be called in the open function of the device + * driver to determine, check and set appropriate bit-timing parameters. + */ +int can_set_bittiming(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + int err; + + /* Check if bit-timing parameters have been pre-defined */ + if (!priv->bittiming.tq && !priv->bittiming.bitrate) { + dev_err(ND2D(dev), "bit-timing not yet defined\n"); + return -EINVAL; + } + + /* Check if the CAN device has bit-timing parameters */ + if (priv->bittiming_const) { + + /* Check if bit-timing parameters have already been set */ + if (priv->bittiming.tq && priv->bittiming.bitrate) + return 0; + + /* Non-expert mode? Check if the bitrate has been pre-defined */ + if (!priv->bittiming.tq) + /* Determine bit-timing parameters */ + err = can_calc_bittiming(dev); + else + /* Check bit-timing params and calculate proper brp */ + err = can_fixup_bittiming(dev); + if (err) + return err; + } + + if (priv->do_set_bittiming) { + /* Finally, set the bit-timing registers */ + err = priv->do_set_bittiming(dev); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(can_set_bittiming); + +static void can_setup(struct net_device *dev) +{ + dev->type = ARPHRD_CAN; + dev->mtu = sizeof(struct can_frame); + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->tx_queue_len = 10; + + /* New-style flags. */ + dev->flags = IFF_NOARP; + dev->features = NETIF_F_NO_CSUM; +} + +/* + * Allocate and setup space for the CAN network device + */ +struct net_device *alloc_candev(int sizeof_priv) +{ + struct net_device *dev; + struct can_priv *priv; + + dev = alloc_netdev(sizeof_priv, "can%d", can_setup); + if (!dev) + return NULL; + + priv = netdev_priv(dev); + + priv->state = CAN_STATE_STOPPED; + spin_lock_init(&priv->irq_lock); + + init_timer(&priv->timer); + priv->timer.expires = 0; + + return dev; +} +EXPORT_SYMBOL_GPL(alloc_candev); + +/* + * Allocate space of the CAN network device + */ +void free_candev(struct net_device *dev) +{ + free_netdev(dev); +} +EXPORT_SYMBOL_GPL(free_candev); + +/* + * Register the CAN network device + */ +int register_candev(struct net_device *dev) +{ + int err; + + err = register_netdev(dev); + if (err) + return err; + + can_create_sysfs(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(register_candev); + +/* + * Unregister the CAN network device + */ +void unregister_candev(struct net_device *dev) +{ + can_remove_sysfs(dev); + unregister_netdev(dev); +} +EXPORT_SYMBOL_GPL(unregister_candev); + +/* + * Local echo of CAN messages + * + * CAN network devices *should* support a local echo functionality + * (see Documentation/networking/can.txt). To test the handling of CAN + * interfaces that do not support the local echo both driver types are + * implemented. In the case that the driver does not support the echo + * the IFF_ECHO remains clear in dev->flags. This causes the PF_CAN core + * to perform the echo as a fallback solution. + */ + +static void can_flush_echo_skb(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + int i; + + for (i = 0; i < CAN_ECHO_SKB_MAX; i++) { + if (priv->echo_skb[i]) { + kfree_skb(priv->echo_skb[i]); + priv->echo_skb[i] = NULL; + stats->tx_dropped++; + stats->tx_aborted_errors++; + } + } +} + +/* + * Put the skb on the stack to be looped backed locally lateron + * + * The function is typically called in the start_xmit function + * of the device driver. + */ +void can_put_echo_skb(struct sk_buff *skb, struct net_device *dev, int idx) +{ + struct can_priv *priv = netdev_priv(dev); + + /* set flag whether this packet has to be looped back */ + if (!(dev->flags & IFF_ECHO) || skb->pkt_type != PACKET_LOOPBACK) { + kfree_skb(skb); + return; + } + + if (!priv->echo_skb[idx]) { + struct sock *srcsk = skb->sk; + + if (atomic_read(&skb->users) != 1) { + struct sk_buff *old_skb = skb; + + skb = skb_clone(old_skb, GFP_ATOMIC); + kfree_skb(old_skb); + if (!skb) + return; + } else + skb_orphan(skb); + + skb->sk = srcsk; + + /* make settings for echo to reduce code in irq context */ + skb->protocol = htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->dev = dev; + + /* save this skb for tx interrupt echo handling */ + priv->echo_skb[idx] = skb; + } else { + /* locking problem with netif_stop_queue() ?? */ + printk(KERN_ERR "%s: %s: BUG! echo_skb is occupied!\n", + dev->name, __func__); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(can_put_echo_skb); + +/* + * Get the skb from the stack and loop it back locally + * + * The function is typically called when the TX done interrupt + * is handled in the device driver. + */ +void can_get_echo_skb(struct net_device *dev, int idx) +{ + struct can_priv *priv = netdev_priv(dev); + + if ((dev->flags & IFF_ECHO) && priv->echo_skb[idx]) { + netif_rx(priv->echo_skb[idx]); + priv->echo_skb[idx] = NULL; + } +} +EXPORT_SYMBOL_GPL(can_get_echo_skb); + +/* + * CAN device restart for bus-off recovery + */ +int can_restart_now(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct sk_buff *skb; + struct can_frame *cf; + int err; + + if (netif_carrier_ok(dev)) + netif_carrier_off(dev); + + /* Cancel restart in progress */ + if (priv->timer.expires) { + del_timer(&priv->timer); + priv->timer.expires = 0; /* mark inactive timer */ + } + + can_flush_echo_skb(dev); + + err = priv->do_set_mode(dev, CAN_MODE_START); + if (err) + return err; + + netif_carrier_on(dev); + + dev_dbg(ND2D(dev), "restarted\n"); + priv->can_stats.restarts++; + + /* send restart message upstream */ + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb == NULL) + return -ENOMEM; + skb->dev = dev; + skb->protocol = htons(ETH_P_CAN); + cf = (struct can_frame *)skb_put(skb, sizeof(struct can_frame)); + memset(cf, 0, sizeof(struct can_frame)); + cf->can_id = CAN_ERR_FLAG | CAN_ERR_RESTARTED; + cf->can_dlc = CAN_ERR_DLC; + + netif_rx(skb); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + + return 0; +} + +static void can_restart_after(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct can_priv *priv = netdev_priv(dev); + + priv->timer.expires = 0; /* mark inactive timer */ + can_restart_now(dev); +} + +/* + * CAN bus-off + * + * This functions should be called when the device goes bus-off to + * tell the netif layer that no more packets can be sent or received. + * If enabled, a timer is started to trigger bus-off recovery. + */ +void can_bus_off(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + dev_dbg(ND2D(dev), "bus-off\n"); + + netif_carrier_off(dev); + + if (priv->restart_ms > 0 && !priv->timer.expires) { + + priv->timer.function = can_restart_after; + priv->timer.data = (unsigned long)dev; + priv->timer.expires = + jiffies + (priv->restart_ms * HZ) / 1000; + add_timer(&priv->timer); + } +} +EXPORT_SYMBOL_GPL(can_bus_off); + +/* + * Cleanup function before the device gets closed. + * + * This functions should be called in the close function of the device + * driver. + */ +void can_close_cleanup(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + if (priv->timer.expires) { + del_timer(&priv->timer); + priv->timer.expires = 0; + } + + can_flush_echo_skb(dev); +} +EXPORT_SYMBOL_GPL(can_close_cleanup); + +static __init int can_dev_init(void) +{ + printk(KERN_INFO MOD_DESC "\n"); + + return 0; +} +module_init(can_dev_init); + +static __exit void can_dev_exit(void) +{ +} +module_exit(can_dev_exit); diff --git a/drivers/net/can/flexcan/Makefile b/drivers/net/can/flexcan/Makefile new file mode 100644 index 000000000000..b2dbb4fb2793 --- /dev/null +++ b/drivers/net/can/flexcan/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_CAN_FLEXCAN) += flexcan.o + +flexcan-y := dev.o drv.o mbm.o diff --git a/drivers/net/can/flexcan/dev.c b/drivers/net/can/flexcan/dev.c new file mode 100644 index 000000000000..f2040c135a89 --- /dev/null +++ b/drivers/net/can/flexcan/dev.c @@ -0,0 +1,619 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dev.c + * + * @brief Driver for Freescale CAN Controller FlexCAN. + * + * @ingroup can + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/spinlock.h> +#include <linux/device.h> + +#include <linux/module.h> +#include "flexcan.h" + +enum { + FLEXCAN_ATTR_STATE = 0, + FLEXCAN_ATTR_BITRATE, + FLEXCAN_ATTR_BR_PRESDIV, + FLEXCAN_ATTR_BR_RJW, + FLEXCAN_ATTR_BR_PROPSEG, + FLEXCAN_ATTR_BR_PSEG1, + FLEXCAN_ATTR_BR_PSEG2, + FLEXCAN_ATTR_BR_CLKSRC, + FLEXCAN_ATTR_MAXMB, + FLEXCAN_ATTR_XMIT_MAXMB, + FLEXCAN_ATTR_FIFO, + FLEXCAN_ATTR_WAKEUP, + FLEXCAN_ATTR_SRX_DIS, + FLEXCAN_ATTR_WAK_SRC, + FLEXCAN_ATTR_BCC, + FLEXCAN_ATTR_LOCAL_PRIORITY, + FLEXCAN_ATTR_ABORT, + FLEXCAN_ATTR_LOOPBACK, + FLEXCAN_ATTR_SMP, + FLEXCAN_ATTR_BOFF_REC, + FLEXCAN_ATTR_TSYN, + FLEXCAN_ATTR_LISTEN, + FLEXCAN_ATTR_EXTEND_MSG, + FLEXCAN_ATTR_STANDARD_MSG, +#ifdef CONFIG_CAN_DEBUG_DEVICES + FLEXCAN_ATTR_DUMP_REG, + FLEXCAN_ATTR_DUMP_XMIT_MB, + FLEXCAN_ATTR_DUMP_RX_MB, +#endif + FLEXCAN_ATTR_MAX +}; + +static ssize_t flexcan_show_attr(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t flexcan_set_attr(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count); + +static struct device_attribute flexcan_dev_attr[FLEXCAN_ATTR_MAX] = { + [FLEXCAN_ATTR_STATE] = __ATTR(state, 0444, flexcan_show_attr, NULL), + [FLEXCAN_ATTR_BITRATE] = + __ATTR(bitrate, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PRESDIV] = + __ATTR(br_presdiv, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_RJW] = + __ATTR(br_rjw, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PROPSEG] = + __ATTR(br_propseg, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PSEG1] = + __ATTR(br_pseg1, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_PSEG2] = + __ATTR(br_pseg2, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BR_CLKSRC] = + __ATTR(br_clksrc, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_MAXMB] = + __ATTR(maxmb, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_XMIT_MAXMB] = + __ATTR(xmit_maxmb, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_FIFO] = + __ATTR(fifo, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_WAKEUP] = + __ATTR(wakeup, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_SRX_DIS] = + __ATTR(srx_dis, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_WAK_SRC] = + __ATTR(wak_src, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BCC] = + __ATTR(bcc, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_LOCAL_PRIORITY] = + __ATTR(local_priority, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_ABORT] = + __ATTR(abort, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_LOOPBACK] = + __ATTR(loopback, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_SMP] = + __ATTR(smp, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_BOFF_REC] = + __ATTR(boff_rec, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_TSYN] = + __ATTR(tsyn, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_LISTEN] = + __ATTR(listen, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_EXTEND_MSG] = + __ATTR(ext_msg, 0644, flexcan_show_attr, flexcan_set_attr), + [FLEXCAN_ATTR_STANDARD_MSG] = + __ATTR(std_msg, 0644, flexcan_show_attr, flexcan_set_attr), +#ifdef CONFIG_CAN_DEBUG_DEVICES + [FLEXCAN_ATTR_DUMP_REG] = + __ATTR(dump_reg, 0444, flexcan_show_attr, NULL), + [FLEXCAN_ATTR_DUMP_XMIT_MB] = + __ATTR(dump_xmit_mb, 0444, flexcan_show_attr, NULL), + [FLEXCAN_ATTR_DUMP_RX_MB] = + __ATTR(dump_rx_mb, 0444, flexcan_show_attr, NULL), +#endif +}; + +static void flexcan_set_bitrate(struct flexcan_device *flexcan, int bitrate) +{ + /* TODO:: implement in future + * based on the bitrate to get the timing of + * presdiv, pseg1, pseg2, propseg + */ +} + +static void flexcan_update_bitrate(struct flexcan_device *flexcan) +{ + int rate, div; + + if (flexcan->br_clksrc) + rate = clk_get_rate(flexcan->clk); + else { + struct clk *clk; + clk = clk_get(NULL, "ckih"); + if (!clk) + return; + rate = clk_get_rate(clk); + clk_put(clk); + } + if (!rate) + return; + + div = (flexcan->br_presdiv + 1); + div *= + (flexcan->br_propseg + flexcan->br_pseg1 + flexcan->br_pseg2 + 4); + flexcan->bitrate = (rate + div - 1) / div; +} + +#ifdef CONFIG_CAN_DEBUG_DEVICES +static int flexcan_dump_reg(struct flexcan_device *flexcan, char *buf) +{ + int ret = 0; + unsigned int reg; + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + ret += sprintf(buf + ret, "MCR::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); + ret += sprintf(buf + ret, "CTRL::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RXGMASK); + ret += sprintf(buf + ret, "RXGMASK::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RX14MASK); + ret += sprintf(buf + ret, "RX14MASK::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_RX15MASK); + ret += sprintf(buf + ret, "RX15MASK::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); + ret += sprintf(buf + ret, "ECR::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + ret += sprintf(buf + ret, "ESR::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK2); + ret += sprintf(buf + ret, "IMASK2::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); + ret += sprintf(buf + ret, "IMASK1::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG2); + ret += sprintf(buf + ret, "IFLAG2::0x%x\n", reg); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1); + ret += sprintf(buf + ret, "IFLAG1::0x%x\n", reg); + return ret; +} + +static int flexcan_dump_xmit_mb(struct flexcan_device *flexcan, char *buf) +{ + int ret = 0, i; + i = flexcan->xmit_maxmb + 1; + for (; i <= flexcan->maxmb; i++) + ret += + sprintf(buf + ret, + "mb[%d]::CS:0x%x ID:0x%x DATA[1~2]:0x%02x,0x%02x\n", + i, flexcan->hwmb[i].mb_cs.data, + flexcan->hwmb[i].mb_id, flexcan->hwmb[i].mb_data[1], + flexcan->hwmb[i].mb_data[2]); + return ret; +} + +static int flexcan_dump_rx_mb(struct flexcan_device *flexcan, char *buf) +{ + int ret = 0, i; + for (i = 0; i <= flexcan->xmit_maxmb; i++) + ret += + sprintf(buf + ret, + "mb[%d]::CS:0x%x ID:0x%x DATA[1~2]:0x%02x,0x%02x\n", + i, flexcan->hwmb[i].mb_cs.data, + flexcan->hwmb[i].mb_id, flexcan->hwmb[i].mb_data[1], + flexcan->hwmb[i].mb_data[2]); + return ret; +} +#endif + +static ssize_t flexcan_show_state(struct net_device *net, char *buf) +{ + int ret, esr; + struct flexcan_device *flexcan = netdev_priv(net); + ret = sprintf(buf, "%s::", netif_running(net) ? "Start" : "Stop"); + if (netif_carrier_ok(net)) { + esr = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + switch ((esr & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) { + case 0: + ret += sprintf(buf + ret, "normal\n"); + break; + case 1: + ret += sprintf(buf + ret, "error passive\n"); + break; + default: + ret += sprintf(buf + ret, "bus off\n"); + } + } else + ret += sprintf(buf + ret, "bus off\n"); + return ret; +} + +static ssize_t flexcan_show_attr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int attr_id; + struct net_device *net; + struct flexcan_device *flexcan; + + net = dev_get_drvdata(dev); + BUG_ON(!net); + flexcan = netdev_priv(net); + BUG_ON(!flexcan); + + attr_id = attr - flexcan_dev_attr; + switch (attr_id) { + case FLEXCAN_ATTR_STATE: + return flexcan_show_state(net, buf); + case FLEXCAN_ATTR_BITRATE: + return sprintf(buf, "%d\n", flexcan->bitrate); + case FLEXCAN_ATTR_BR_PRESDIV: + return sprintf(buf, "%d\n", flexcan->br_presdiv + 1); + case FLEXCAN_ATTR_BR_RJW: + return sprintf(buf, "%d\n", flexcan->br_rjw); + case FLEXCAN_ATTR_BR_PROPSEG: + return sprintf(buf, "%d\n", flexcan->br_propseg + 1); + case FLEXCAN_ATTR_BR_PSEG1: + return sprintf(buf, "%d\n", flexcan->br_pseg1 + 1); + case FLEXCAN_ATTR_BR_PSEG2: + return sprintf(buf, "%d\n", flexcan->br_pseg2 + 1); + case FLEXCAN_ATTR_BR_CLKSRC: + return sprintf(buf, "%s\n", flexcan->br_clksrc ? "bus" : "osc"); + case FLEXCAN_ATTR_MAXMB: + return sprintf(buf, "%d\n", flexcan->maxmb + 1); + case FLEXCAN_ATTR_XMIT_MAXMB: + return sprintf(buf, "%d\n", flexcan->xmit_maxmb + 1); + case FLEXCAN_ATTR_FIFO: + return sprintf(buf, "%d\n", flexcan->fifo); + case FLEXCAN_ATTR_WAKEUP: + return sprintf(buf, "%d\n", flexcan->wakeup); + case FLEXCAN_ATTR_SRX_DIS: + return sprintf(buf, "%d\n", flexcan->srx_dis); + case FLEXCAN_ATTR_WAK_SRC: + return sprintf(buf, "%d\n", flexcan->wak_src); + case FLEXCAN_ATTR_BCC: + return sprintf(buf, "%d\n", flexcan->bcc); + case FLEXCAN_ATTR_LOCAL_PRIORITY: + return sprintf(buf, "%d\n", flexcan->lprio); + case FLEXCAN_ATTR_ABORT: + return sprintf(buf, "%d\n", flexcan->abort); + case FLEXCAN_ATTR_LOOPBACK: + return sprintf(buf, "%d\n", flexcan->loopback); + case FLEXCAN_ATTR_SMP: + return sprintf(buf, "%d\n", flexcan->smp); + case FLEXCAN_ATTR_BOFF_REC: + return sprintf(buf, "%d\n", flexcan->boff_rec); + case FLEXCAN_ATTR_TSYN: + return sprintf(buf, "%d\n", flexcan->tsyn); + case FLEXCAN_ATTR_LISTEN: + return sprintf(buf, "%d\n", flexcan->listen); + case FLEXCAN_ATTR_EXTEND_MSG: + return sprintf(buf, "%d\n", flexcan->ext_msg); + case FLEXCAN_ATTR_STANDARD_MSG: + return sprintf(buf, "%d\n", flexcan->std_msg); +#ifdef CONFIG_CAN_DEBUG_DEVICES + case FLEXCAN_ATTR_DUMP_REG: + return flexcan_dump_reg(flexcan, buf); + case FLEXCAN_ATTR_DUMP_XMIT_MB: + return flexcan_dump_xmit_mb(flexcan, buf); + case FLEXCAN_ATTR_DUMP_RX_MB: + return flexcan_dump_rx_mb(flexcan, buf); +#endif + default: + return sprintf(buf, "%s:%p->%p\n", __func__, flexcan_dev_attr, + attr); + } +} + +static ssize_t flexcan_set_attr(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int attr_id, tmp; + struct net_device *net; + struct flexcan_device *flexcan; + + net = dev_get_drvdata(dev); + BUG_ON(!net); + flexcan = netdev_priv(net); + BUG_ON(!flexcan); + + attr_id = attr - flexcan_dev_attr; + + if (mutex_lock_interruptible(&flexcan->mutex)) + return count; + + if (netif_running(net)) + goto set_finish; + + if (attr_id == FLEXCAN_ATTR_BR_CLKSRC) { + if (!strcasecmp(buf, "bus")) + flexcan->br_clksrc = 1; + else if (!strcasecmp(buf, "osc")) + flexcan->br_clksrc = 0; + goto set_finish; + } + + tmp = simple_strtoul(buf, NULL, 0); + switch (attr_id) { + case FLEXCAN_ATTR_BITRATE: + flexcan_set_bitrate(flexcan, tmp); + break; + case FLEXCAN_ATTR_BR_PRESDIV: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PRESDIV)) { + flexcan->br_presdiv = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_BR_RJW: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_RJW)) + flexcan->br_rjw = tmp - 1; + break; + case FLEXCAN_ATTR_BR_PROPSEG: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PROPSEG)) { + flexcan->br_propseg = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_BR_PSEG1: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PSEG1)) { + flexcan->br_pseg1 = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_BR_PSEG2: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_PSEG2)) { + flexcan->br_pseg2 = tmp - 1; + flexcan_update_bitrate(flexcan); + } + break; + case FLEXCAN_ATTR_MAXMB: + if ((tmp > 0) && (tmp <= FLEXCAN_MAX_MB)) { + if (flexcan->maxmb != (tmp - 1)) { + flexcan->maxmb = tmp - 1; + if (flexcan->xmit_maxmb < flexcan->maxmb) + flexcan->xmit_maxmb = flexcan->maxmb; + } + } + break; + case FLEXCAN_ATTR_XMIT_MAXMB: + if ((tmp > 0) && (tmp <= (flexcan->maxmb + 1))) { + if (flexcan->xmit_maxmb != (tmp - 1)) + flexcan->xmit_maxmb = tmp - 1; + } + break; + case FLEXCAN_ATTR_FIFO: + flexcan->fifo = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_WAKEUP: + flexcan->wakeup = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_SRX_DIS: + flexcan->srx_dis = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_WAK_SRC: + flexcan->wak_src = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_BCC: + flexcan->bcc = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_LOCAL_PRIORITY: + flexcan->lprio = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_ABORT: + flexcan->abort = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_LOOPBACK: + flexcan->loopback = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_SMP: + flexcan->smp = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_BOFF_REC: + flexcan->boff_rec = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_TSYN: + flexcan->tsyn = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_LISTEN: + flexcan->listen = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_EXTEND_MSG: + flexcan->ext_msg = tmp ? 1 : 0; + break; + case FLEXCAN_ATTR_STANDARD_MSG: + flexcan->std_msg = tmp ? 1 : 0; + break; + } + set_finish: + mutex_unlock(&flexcan->mutex); + return count; +} + +static void flexcan_device_default(struct flexcan_device *dev) +{ + dev->br_clksrc = 1; + dev->br_rjw = 2; + dev->br_presdiv = 6; + dev->br_propseg = 4; + dev->br_pseg1 = 4; + dev->br_pseg2 = 7; + + dev->bcc = 1; + dev->srx_dis = 1; + dev->smp = 1; + dev->boff_rec = 1; + + dev->maxmb = FLEXCAN_MAX_MB - 1; + dev->xmit_maxmb = (FLEXCAN_MAX_MB >> 1) - 1; + dev->xmit_mb = dev->maxmb - dev->xmit_maxmb; + + dev->ext_msg = 1; + dev->std_msg = 1; +} + +static int flexcan_device_attach(struct flexcan_device *flexcan) +{ + int ret; + struct resource *res; + struct platform_device *pdev = flexcan->dev; + struct flexcan_platform_data *plat_data = (pdev->dev).platform_data; + + res = platform_get_resource(flexcan->dev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + flexcan->io_base = ioremap(res->start, res->end - res->start + 1); + if (!flexcan->io_base) + return -ENOMEM; + + flexcan->irq = platform_get_irq(flexcan->dev, 0); + if (!flexcan->irq) { + ret = -ENODEV; + goto no_irq_err; + } + + ret = -EINVAL; + if (plat_data) { + if (plat_data->core_reg) { + flexcan->core_reg = regulator_get(&pdev->dev, + plat_data->core_reg); + if (!flexcan->core_reg) + goto plat_err; + } + + if (plat_data->io_reg) { + flexcan->io_reg = regulator_get(&pdev->dev, + plat_data->io_reg); + if (!flexcan->io_reg) + goto plat_err; + } + } + flexcan->clk = clk_get(&(flexcan->dev)->dev, "can_clk"); + flexcan->hwmb = (struct can_hw_mb *)(flexcan->io_base + CAN_MB_BASE); + flexcan->rx_mask = (unsigned int *)(flexcan->io_base + CAN_RXMASK_BASE); + + return 0; + plat_err: + if (flexcan->core_reg) { + regulator_put(flexcan->core_reg); + flexcan->core_reg = NULL; + } + no_irq_err: + if (flexcan->io_base) + iounmap(flexcan->io_base); + return ret; +} + +static void flexcan_device_detach(struct flexcan_device *flexcan) +{ + struct platform_device *pdev = flexcan->dev; + if (flexcan->clk) { + clk_put(flexcan->clk); + flexcan->clk = NULL; + } + + if (flexcan->io_reg) { + regulator_put(flexcan->io_reg); + flexcan->io_reg = NULL; + } + + if (flexcan->core_reg) { + regulator_put(flexcan->core_reg); + flexcan->core_reg = NULL; + } + + if (flexcan->io_base) + iounmap(flexcan->io_base); +} + +/*! + * @brief The function allocates can device. + * + * @param pdev the pointer of platform device. + * @param setup the initial function pointer of network device. + * + * @return none + */ +struct net_device *flexcan_device_alloc(struct platform_device *pdev, + void (*setup) (struct net_device *dev)) +{ + struct flexcan_device *flexcan; + struct net_device *net; + int i, num; + + net = alloc_netdev(sizeof(*flexcan), "can%d", setup); + if (net == NULL) { + printk(KERN_ERR "Allocate netdevice for FlexCAN fail!\n"); + return NULL; + } + flexcan = netdev_priv(net); + memset(flexcan, 0, sizeof(*flexcan)); + + mutex_init(&flexcan->mutex); + init_timer(&flexcan->timer); + + flexcan->dev = pdev; + if (flexcan_device_attach(flexcan)) { + printk(KERN_ERR "Attach FlexCAN fail!\n"); + free_netdev(net); + return NULL; + } + flexcan_device_default(flexcan); + flexcan_update_bitrate(flexcan); + + num = ARRAY_SIZE(flexcan_dev_attr); + + for (i = 0; i < num; i++) { + if (device_create_file(&pdev->dev, flexcan_dev_attr + i)) { + printk(KERN_ERR "Create attribute file fail!\n"); + break; + } + } + + if (i != num) { + for (; i >= 0; i--) + device_remove_file(&pdev->dev, flexcan_dev_attr + i); + free_netdev(net); + return NULL; + } + dev_set_drvdata(&pdev->dev, net); + return net; +} + +/*! + * @brief The function frees can device. + * + * @param pdev the pointer of platform device. + * + * @return none + */ +void flexcan_device_free(struct platform_device *pdev) +{ + struct net_device *net; + struct flexcan_device *flexcan; + int i, num; + net = (struct net_device *)dev_get_drvdata(&pdev->dev); + + unregister_netdev(net); + flexcan = netdev_priv(net); + del_timer(&flexcan->timer); + + num = ARRAY_SIZE(flexcan_dev_attr); + + for (i = 0; i < num; i++) + device_remove_file(&pdev->dev, flexcan_dev_attr + i); + + flexcan_device_detach(netdev_priv(net)); + free_netdev(net); +} diff --git a/drivers/net/can/flexcan/drv.c b/drivers/net/can/flexcan/drv.c new file mode 100644 index 000000000000..1eaeac50d6ee --- /dev/null +++ b/drivers/net/can/flexcan/drv.c @@ -0,0 +1,624 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drv.c + * + * @brief Driver for Freescale CAN Controller FlexCAN. + * + * @ingroup can + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <mach/hardware.h> +#include "flexcan.h" + +static void flexcan_hw_start(struct flexcan_device *flexcan) +{ + unsigned int reg; + if ((flexcan->maxmb + 1) > 32) { + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IMASK1); + reg = (1 << (flexcan->maxmb - 31)) - 1; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_IMASK2); + } else { + reg = (1 << (flexcan->maxmb + 1)) - 1; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_IMASK1); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK2); + } + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR) & (~__MCR_HALT); + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); +} + +static void flexcan_hw_stop(struct flexcan_device *flexcan) +{ + unsigned int reg; + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + __raw_writel(reg | __MCR_HALT, flexcan->io_base + CAN_HW_REG_MCR); +} + +static int flexcan_hw_reset(struct flexcan_device *flexcan) +{ + unsigned int reg; + int timeout = 100000; + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + __raw_writel(reg | __MCR_MDIS, flexcan->io_base + CAN_HW_REG_MCR); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); + if (flexcan->br_clksrc) + reg |= __CTRL_CLK_SRC; + else + reg &= ~__CTRL_CLK_SRC; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_CTRL); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR) & (~__MCR_MDIS); + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); + reg |= __MCR_SOFT_RST; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + while (reg & __MCR_SOFT_RST) { + if (--timeout <= 0) { + printk(KERN_ERR "Flexcan software Reset Timeouted\n"); + return -1; + } + udelay(10); + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + } + return 0; +} + +static inline void flexcan_mcr_setup(struct flexcan_device *flexcan) +{ + unsigned int reg; + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + reg &= ~(__MCR_MAX_MB_MASK | __MCR_WAK_MSK | __MCR_MAX_IDAM_MASK); + + if (flexcan->fifo) + reg |= __MCR_FEN; + else + reg &= ~__MCR_FEN; + + if (flexcan->wakeup) + reg |= __MCR_SLF_WAK | __MCR_WAK_MSK; + else + reg &= ~(__MCR_SLF_WAK | __MCR_WAK_MSK); + + if (flexcan->wak_src) + reg |= __MCR_WAK_SRC; + else + reg &= ~__MCR_WAK_SRC; + + if (flexcan->srx_dis) + reg |= __MCR_SRX_DIS; + else + reg &= ~__MCR_SRX_DIS; + + if (flexcan->bcc) + reg |= __MCR_BCC; + else + reg &= ~__MCR_BCC; + + if (flexcan->lprio) + reg |= __MCR_LPRIO_EN; + else + reg &= ~__MCR_LPRIO_EN; + + if (flexcan->abort) + reg |= __MCR_AEN; + else + reg &= ~__MCR_AEN; + + reg |= (flexcan->maxmb << __MCR_MAX_MB_OFFSET); + reg |= __MCR_DOZE | __MCR_MAX_IDAM_C; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); +} + +static inline void flexcan_ctrl_setup(struct flexcan_device *flexcan) +{ + unsigned int reg; + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_CTRL); + reg &= ~(__CTRL_PRESDIV_MASK | __CTRL_RJW_MASK | __CTRL_PSEG1_MASK | + __CTRL_PSEG2_MASK | __CTRL_PROPSEG_MASK); + + if (flexcan->loopback) + reg |= __CTRL_LPB; + else + reg &= ~__CTRL_LPB; + + if (flexcan->smp) + reg |= __CTRL_SMP; + else + reg &= ~__CTRL_SMP; + + if (flexcan->boff_rec) + reg |= __CTRL_BOFF_REC; + else + reg &= ~__CTRL_BOFF_REC; + + if (flexcan->tsyn) + reg |= __CTRL_TSYN; + else + reg &= ~__CTRL_TSYN; + + if (flexcan->listen) + reg |= __CTRL_LOM; + else + reg &= ~__CTRL_LOM; + + reg |= (flexcan->br_presdiv << __CTRL_PRESDIV_OFFSET) | + (flexcan->br_rjw << __CTRL_RJW_OFFSET) | + (flexcan->br_pseg1 << __CTRL_PSEG1_OFFSET) | + (flexcan->br_pseg2 << __CTRL_PSEG2_OFFSET) | + (flexcan->br_propseg << __CTRL_PROPSEG_OFFSET); + + reg &= ~__CTRL_LBUF; + + reg |= __CTRL_TWRN_MSK | __CTRL_RWRN_MSK | __CTRL_BOFF_MSK | + __CTRL_ERR_MSK; + + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_CTRL); +} + +static int flexcan_hw_restart(struct net_device *dev) +{ + unsigned int reg; + struct flexcan_device *flexcan = netdev_priv(dev); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + if (reg & __MCR_SOFT_RST) + return 1; + + flexcan_mcr_setup(flexcan); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK2); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK1); + + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG2); + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG1); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_ECR); + + flexcan_mbm_init(flexcan); + netif_carrier_on(dev); + flexcan_hw_start(flexcan); + + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + + return 0; +} + +static void flexcan_hw_watch(unsigned long data) +{ + unsigned int reg, ecr; + struct net_device *dev = (struct net_device *)data; + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + + BUG_ON(!flexcan); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + if (reg & __MCR_MDIS) { + if (flexcan_hw_restart(dev)) + mod_timer(&flexcan->timer, HZ / 20); + return; + } + ecr = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); + if (flexcan->boff_rec) { + if (((reg & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) > 1) { + reg |= __MCR_SOFT_RST; + __raw_writel(reg, flexcan->io_base + CAN_HW_REG_MCR); + mod_timer(&flexcan->timer, HZ / 20); + return; + } + netif_carrier_on(dev); + } +} + +static void flexcan_hw_busoff(struct net_device *dev) +{ + struct flexcan_device *flexcan = netdev_priv(dev); + unsigned int reg; + + netif_carrier_off(dev); + + flexcan->timer.function = flexcan_hw_watch; + flexcan->timer.data = (unsigned long)dev; + + if (flexcan->boff_rec) { + mod_timer(&flexcan->timer, HZ / 10); + return; + } + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_MCR); + __raw_writel(reg | __MCR_SOFT_RST, flexcan->io_base + CAN_HW_REG_MCR); + mod_timer(&flexcan->timer, HZ / 20); +} + +static int flexcan_hw_open(struct flexcan_device *flexcan) +{ + if (flexcan_hw_reset(flexcan)) + return -EFAULT; + + flexcan_mcr_setup(flexcan); + flexcan_ctrl_setup(flexcan); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK2); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_IMASK1); + + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG2); + __raw_writel(0xFFFFFFFF, flexcan->io_base + CAN_HW_REG_IFLAG1); + + __raw_writel(0, flexcan->io_base + CAN_HW_REG_ECR); + return 0; +} + +static void flexcan_err_handler(struct net_device *dev) +{ + struct flexcan_device *flexcan = netdev_priv(dev); + struct sk_buff *skb; + struct can_frame *frame; + unsigned int esr, ecr; + + esr = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + __raw_writel(esr & __ESR_INTERRUPTS, flexcan->io_base + CAN_HW_REG_ESR); + + if (esr & __ESR_WAK_INT) + return; + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) { + printk(KERN_ERR "%s: allocates skb fail in\n", __func__); + return; + } + frame = (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + frame->can_id = CAN_ERR_FLAG | CAN_ERR_CRTL; + frame->can_dlc = CAN_ERR_DLC; + + if (esr & __ESR_TWRN_INT) + frame->data[1] |= CAN_ERR_CRTL_TX_WARNING; + + if (esr & __ESR_RWRN_INT) + frame->data[1] |= CAN_ERR_CRTL_RX_WARNING; + + if (esr & __ESR_BOFF_INT) + frame->can_id |= CAN_ERR_BUSOFF; + + if (esr & __ESR_ERR_INT) { + if (esr & __ESR_BIT1_ERR) + frame->data[2] |= CAN_ERR_PROT_BIT1; + + if (esr & __ESR_BIT0_ERR) + frame->data[2] |= CAN_ERR_PROT_BIT0; + + if (esr & __ESR_ACK_ERR) + frame->can_id |= CAN_ERR_ACK; + + /*TODO:// if (esr & __ESR_CRC_ERR) */ + + if (esr & __ESR_FRM_ERR) + frame->data[2] |= CAN_ERR_PROT_FORM; + + if (esr & __ESR_STF_ERR) + frame->data[2] |= CAN_ERR_PROT_STUFF; + + ecr = __raw_readl(flexcan->io_base + CAN_HW_REG_ECR); + switch ((esr & __ESR_FLT_CONF_MASK) >> __ESR_FLT_CONF_OFF) { + case 0: + if (__ECR_TX_ERR_COUNTER(ecr) >= __ECR_ACTIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_TX_WARNING; + if (__ECR_RX_ERR_COUNTER(ecr) >= __ECR_ACTIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_RX_WARNING; + break; + case 1: + if (__ECR_TX_ERR_COUNTER(ecr) >= + __ECR_PASSIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_TX_PASSIVE; + + if (__ECR_RX_ERR_COUNTER(ecr) >= + __ECR_PASSIVE_THRESHOLD) + frame->data[1] |= CAN_ERR_CRTL_RX_PASSIVE; + break; + default: + frame->can_id |= CAN_ERR_BUSOFF; + } + } + + if (frame->can_id & CAN_ERR_BUSOFF) + flexcan_hw_busoff(dev); + + skb->dev = dev; + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_receive_skb(skb); +} + +static irqreturn_t flexcan_irq_handler(int irq, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + unsigned int reg; + + BUG_ON(!flexcan); + + reg = __raw_readl(flexcan->io_base + CAN_HW_REG_ESR); + if (reg & __ESR_INTERRUPTS) { + flexcan_err_handler(dev); + return IRQ_HANDLED; + } + + flexcan_mbm_isr(dev); + return IRQ_HANDLED; +} + +static int flexcan_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct can_frame *frame = (struct can_frame *)skb->data; + struct flexcan_device *flexcan = netdev_priv(dev); + struct net_device_stats *stats = dev->get_stats(dev); + + BUG_ON(!flexcan); + + if (frame->can_dlc > 8) + return -EINVAL; + + if (!flexcan_mbm_xmit(flexcan, frame)) { + dev_kfree_skb(skb); + stats->tx_bytes += frame->can_dlc; + stats->tx_packets++; + dev->trans_start = jiffies; + return NETDEV_TX_OK; + } + netif_stop_queue(dev); + return NETDEV_TX_BUSY; +} + +static int flexcan_open(struct net_device *dev) +{ + struct flexcan_device *flexcan; + struct platform_device *pdev; + struct flexcan_platform_data *plat_data; + + flexcan = netdev_priv(dev); + BUG_ON(!flexcan); + + pdev = flexcan->dev; + plat_data = (pdev->dev).platform_data; + if (plat_data && plat_data->active) + plat_data->active(pdev->id); + + if (flexcan->clk) + if (clk_enable(flexcan->clk)) + goto clk_err; + + if (flexcan->core_reg) + if (regulator_enable(flexcan->core_reg)) + goto core_reg_err; + + if (flexcan->io_reg) + if (regulator_enable(flexcan->io_reg)) + goto io_reg_err; + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 1); + + if (request_irq(flexcan->irq, flexcan_irq_handler, IRQF_SAMPLE_RANDOM, + dev->name, dev)) + goto irq_err; + + if (flexcan_hw_open(flexcan)) + goto open_err; + + flexcan_mbm_init(flexcan); + netif_carrier_on(dev); + flexcan_hw_start(flexcan); + return 0; + open_err: + free_irq(flexcan->irq, dev); + irq_err: + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 0); + + if (flexcan->io_reg) + regulator_disable(flexcan->io_reg); + io_reg_err: + if (flexcan->core_reg) + regulator_disable(flexcan->core_reg); + core_reg_err: + if (flexcan->clk) + clk_disable(flexcan->clk); + clk_err: + if (plat_data && plat_data->inactive) + plat_data->inactive(pdev->id); + return -ENODEV; +} + +static int flexcan_stop(struct net_device *dev) +{ + struct flexcan_device *flexcan; + struct platform_device *pdev; + struct flexcan_platform_data *plat_data; + + flexcan = netdev_priv(dev); + + BUG_ON(!flexcan); + + pdev = flexcan->dev; + plat_data = (pdev->dev).platform_data; + + flexcan_hw_stop(flexcan); + + free_irq(flexcan->irq, dev); + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 0); + + if (flexcan->io_reg) + regulator_disable(flexcan->io_reg); + if (flexcan->core_reg) + regulator_disable(flexcan->core_reg); + if (flexcan->clk) + clk_disable(flexcan->clk); + if (plat_data && plat_data->inactive) + plat_data->inactive(pdev->id); + return 0; +} + +static void flexcan_setup(struct net_device *dev) +{ + dev->type = ARPHRD_CAN; + dev->mtu = sizeof(struct can_frame); + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->tx_queue_len = FLEXCAN_MAX_MB; + dev->flags = IFF_NOARP; + dev->features = NETIF_F_NO_CSUM; + + dev->open = flexcan_open; + dev->stop = flexcan_stop; + dev->hard_start_xmit = flexcan_start_xmit; +} + +static int flexcan_probe(struct platform_device *pdev) +{ + struct net_device *net; + net = flexcan_device_alloc(pdev, flexcan_setup); + if (!net) + return -ENOMEM; + + if (register_netdev(net)) { + flexcan_device_free(pdev); + return -ENODEV; + } + return 0; +} + +static int flexcan_remove(struct platform_device *pdev) +{ + flexcan_device_free(pdev); + return 0; +} + +static int flexcan_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *net; + struct flexcan_device *flexcan; + struct flexcan_platform_data *plat_data; + net = (struct net_device *)dev_get_drvdata(&pdev->dev); + flexcan = netdev_priv(net); + + BUG_ON(!flexcan); + + if (!(net->flags & IFF_UP)) + return 0; + if (flexcan->wakeup) + set_irq_wake(flexcan->irq, 1); + else { + plat_data = (pdev->dev).platform_data; + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 0); + + if (flexcan->io_reg) + regulator_disable(flexcan->io_reg); + if (flexcan->core_reg) + regulator_disable(flexcan->core_reg); + if (flexcan->clk) + clk_disable(flexcan->clk); + if (plat_data && plat_data->inactive) + plat_data->inactive(pdev->id); + } + return 0; +} + +static int flexcan_resume(struct platform_device *pdev) +{ + struct net_device *net; + struct flexcan_device *flexcan; + struct flexcan_platform_data *plat_data; + net = (struct net_device *)dev_get_drvdata(&pdev->dev); + flexcan = netdev_priv(net); + + BUG_ON(!flexcan); + + if (!(net->flags & IFF_UP)) + return 0; + + if (flexcan->wakeup) + set_irq_wake(flexcan->irq, 0); + else { + plat_data = (pdev->dev).platform_data; + if (plat_data && plat_data->active) + plat_data->active(pdev->id); + + if (flexcan->clk) { + if (clk_enable(flexcan->clk)) + printk(KERN_ERR "%s:enable clock fail\n", + __func__); + } + + if (flexcan->core_reg) { + if (regulator_enable(flexcan->core_reg)) + printk(KERN_ERR "%s:enable core voltage\n", + __func__); + } + if (flexcan->io_reg) { + if (regulator_enable(flexcan->io_reg)) + printk(KERN_ERR "%s:enable io voltage\n", + __func__); + } + + if (plat_data && plat_data->xcvr_enable) + plat_data->xcvr_enable(pdev->id, 1); + } + return 0; +} + +static struct platform_driver flexcan_driver = { + .driver = { + .name = FLEXCAN_DEVICE_NAME, + }, + .probe = flexcan_probe, + .remove = flexcan_remove, + .suspend = flexcan_suspend, + .resume = flexcan_resume, +}; + +static __init int flexcan_init(void) +{ + pr_info("Freescale FlexCAN Driver \n"); + return platform_driver_register(&flexcan_driver); +} + +static __exit void flexcan_exit(void) +{ + return platform_driver_unregister(&flexcan_driver); +} + +module_init(flexcan_init); +module_exit(flexcan_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/can/flexcan/flexcan.h b/drivers/net/can/flexcan/flexcan.h new file mode 100644 index 000000000000..d19cc1ee0620 --- /dev/null +++ b/drivers/net/can/flexcan/flexcan.h @@ -0,0 +1,223 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file flexcan.h + * + * @brief FlexCan definitions. + * + * @ingroup can + */ + +#ifndef __CAN_FLEXCAN_H__ +#define __CAN_FLEXCAN_H__ + +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> +#include <linux/can.h> +#include <linux/can/core.h> +#include <linux/can/error.h> + +#define FLEXCAN_DEVICE_NAME "FlexCAN" + +struct can_mb_cs { + unsigned int time_stamp:16; + unsigned int length:4; + unsigned int rtr:1; + unsigned int ide:1; + unsigned int srr:1; + unsigned int nouse1:1; + unsigned int code:4; + unsigned int nouse2:4; +}; + +#define CAN_MB_RX_INACTIVE 0x0 +#define CAN_MB_RX_EMPTY 0x4 +#define CAN_MB_RX_FULL 0x2 +#define CAN_MB_RX_OVERRUN 0x6 +#define CAN_MB_RX_BUSY 0x1 + +#define CAN_MB_TX_INACTIVE 0x8 +#define CAN_MB_TX_ABORT 0x9 +#define CAN_MB_TX_ONCE 0xC +#define CAN_MB_TX_REMOTE 0xA + +struct can_hw_mb { + union { + struct can_mb_cs cs; + unsigned int data; + } mb_cs; + unsigned int mb_id; + unsigned char mb_data[8]; +}; + +#define CAN_HW_REG_MCR 0x00 +#define CAN_HW_REG_CTRL 0x04 +#define CAN_HW_REG_TIMER 0x08 +#define CAN_HW_REG_RXGMASK 0x10 +#define CAN_HW_REG_RX14MASK 0x14 +#define CAN_HW_REG_RX15MASK 0x18 +#define CAN_HW_REG_ECR 0x1C +#define CAN_HW_REG_ESR 0x20 +#define CAN_HW_REG_IMASK2 0x24 +#define CAN_HW_REG_IMASK1 0x28 +#define CAN_HW_REG_IFLAG2 0x2C +#define CAN_HW_REG_IFLAG1 0x30 + +#define CAN_MB_BASE 0x0080 +#define CAN_RXMASK_BASE 0x0880 +#define CAN_FIFO_BASE 0xE0 + +#define __MCR_MDIS (1 << 31) +#define __MCR_FRZ (1 << 30) +#define __MCR_FEN (1 << 29) +#define __MCR_HALT (1 << 28) +#define __MCR_NOTRDY (1 << 27) +#define __MCR_WAK_MSK (1 << 26) +#define __MCR_SOFT_RST (1 << 25) +#define __MCR_FRZ_ACK (1 << 24) +#define __MCR_SLF_WAK (1 << 22) +#define __MCR_WRN_EN (1 << 21) +#define __MCR_LPM_ACK (1 << 20) +#define __MCR_WAK_SRC (1 << 19) +#define __MCR_DOZE (1 << 18) +#define __MCR_SRX_DIS (1 << 17) +#define __MCR_BCC (1 << 16) +#define __MCR_LPRIO_EN (1 << 13) +#define __MCR_AEN (1 << 12) +#define __MCR_MAX_IDAM_OFFSET 8 +#define __MCR_MAX_IDAM_MASK (0x3 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_A (0x0 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_B (0x1 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_C (0x2 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_IDAM_D (0x3 << __MCR_MAX_IDAM_OFFSET) +#define __MCR_MAX_MB_OFFSET 0 +#define __MCR_MAX_MB_MASK (0x3F) + +#define __CTRL_PRESDIV_OFFSET 24 +#define __CTRL_PRESDIV_MASK (0xFF << __CTRL_PRESDIV_OFFSET) +#define __CTRL_RJW_OFFSET 22 +#define __CTRL_RJW_MASK (0x3 << __CTRL_RJW_OFFSET) +#define __CTRL_PSEG1_OFFSET 19 +#define __CTRL_PSEG1_MASK (0x7 << __CTRL_PSEG1_OFFSET) +#define __CTRL_PSEG2_OFFSET 16 +#define __CTRL_PSEG2_MASK (0x7 << __CTRL_PSEG2_OFFSET) +#define __CTRL_BOFF_MSK (0x1 << 15) +#define __CTRL_ERR_MSK (0x1 << 14) +#define __CTRL_CLK_SRC (0x1 << 13) +#define __CTRL_LPB (0x1 << 12) +#define __CTRL_TWRN_MSK (0x1 << 11) +#define __CTRL_RWRN_MSK (0x1 << 10) +#define __CTRL_SMP (0x1 << 7) +#define __CTRL_BOFF_REC (0x1 << 6) +#define __CTRL_TSYN (0x1 << 5) +#define __CTRL_LBUF (0x1 << 4) +#define __CTRL_LOM (0x1 << 3) +#define __CTRL_PROPSEG_OFFSET 0 +#define __CTRL_PROPSEG_MASK (0x7) + +#define __ECR_TX_ERR_COUNTER(x) ((x) & 0xFF) +#define __ECR_RX_ERR_COUNTER(x) (((x) >> 8) & 0xFF) +#define __ECR_PASSIVE_THRESHOLD 128 +#define __ECR_ACTIVE_THRESHOLD 96 + +#define __ESR_TWRN_INT (0x1 << 17) +#define __ESR_RWRN_INT (0x1 << 16) +#define __ESR_BIT1_ERR (0x1 << 15) +#define __ESR_BIT0_ERR (0x1 << 14) +#define __ESR_ACK_ERR (0x1 << 13) +#define __ESR_CRC_ERR (0x1 << 12) +#define __ESR_FRM_ERR (0x1 << 11) +#define __ESR_STF_ERR (0x1 << 10) +#define __ESR_TX_WRN (0x1 << 9) +#define __ESR_RX_WRN (0x1 << 8) +#define __ESR_IDLE (0x1 << 7) +#define __ESR_TXRX (0x1 << 6) +#define __ESR_FLT_CONF_OFF 4 +#define __ESR_FLT_CONF_MASK (0x3 << __ESR_FLT_CONF_OFF) +#define __ESR_BOFF_INT (0x1 << 2) +#define __ESR_ERR_INT (0x1 << 1) +#define __ESR_WAK_INT (0x1) + +#define __ESR_INTERRUPTS (__ESR_WAK_INT | __ESR_ERR_INT | \ + __ESR_BOFF_INT | __ESR_TWRN_INT | \ + __ESR_RWRN_INT) + +#define __FIFO_OV_INT 0x0080 +#define __FIFO_WARN_INT 0x0040 +#define __FIFO_RDY_INT 0x0020 + +struct flexcan_device { + struct mutex mutex; + void *io_base; + struct can_hw_mb *hwmb; + unsigned int *rx_mask; + unsigned int xmit_mb; + unsigned int bitrate; + /* word 1 */ + unsigned int br_presdiv:8; + unsigned int br_rjw:2; + unsigned int br_propseg:3; + unsigned int br_pseg1:3; + unsigned int br_pseg2:3; + unsigned int maxmb:6; + unsigned int xmit_maxmb:6; + unsigned int wd1_resv:1; + + /* word 2 */ + unsigned int fifo:1; + unsigned int wakeup:1; + unsigned int srx_dis:1; + unsigned int wak_src:1; + unsigned int bcc:1; + unsigned int lprio:1; + unsigned int abort:1; + unsigned int br_clksrc:1; + unsigned int loopback:1; + unsigned int smp:1; + unsigned int boff_rec:1; + unsigned int tsyn:1; + unsigned int listen:1; + + unsigned int ext_msg:1; + unsigned int std_msg:1; + + struct timer_list timer; + struct platform_device *dev; + struct regulator *core_reg; + struct regulator *io_reg; + struct clk *clk; + int irq; +}; + +#define FLEXCAN_MAX_FIFO_MB 8 +#define FLEXCAN_MAX_MB 64 +#define FLEXCAN_MAX_PRESDIV 256 +#define FLEXCAN_MAX_RJW 4 +#define FLEXCAN_MAX_PSEG1 8 +#define FLEXCAN_MAX_PSEG2 8 +#define FLEXCAN_MAX_PROPSEG 8 +#define FLEXCAN_MAX_BITRATE 1000000 + +extern struct net_device *flexcan_device_alloc(struct platform_device *pdev, + void (*setup) (struct net_device + *dev)); +extern void flexcan_device_free(struct platform_device *pdev); + +extern void flexcan_mbm_init(struct flexcan_device *flexcan); +extern void flexcan_mbm_isr(struct net_device *dev); +extern int flexcan_mbm_xmit(struct flexcan_device *flexcan, + struct can_frame *frame); +#endif /* __CAN_FLEXCAN_H__ */ diff --git a/drivers/net/can/flexcan/mbm.c b/drivers/net/can/flexcan/mbm.c new file mode 100644 index 000000000000..ca1300503e02 --- /dev/null +++ b/drivers/net/can/flexcan/mbm.c @@ -0,0 +1,347 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mbm.c + * + * @brief Driver for Freescale CAN Controller FlexCAN. + * + * @ingroup can + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include "flexcan.h" + +#define flexcan_swab32(x) \ + (((x) << 24) | ((x) >> 24) |\ + (((x) & (__u32)0x0000ff00UL) << 8) |\ + (((x) & (__u32)0x00ff0000UL) >> 8)) + +static inline void flexcan_memcpy(void *dst, void *src, int len) +{ + int i; + unsigned int *d = (unsigned int *)dst, *s = (unsigned int *)src; + len = (len + 3) >> 2; + for (i = 0; i < len; i++, s++, d++) + *d = flexcan_swab32(*s); +} + +static void flexcan_mb_bottom(struct net_device *dev, int index) +{ + struct flexcan_device *flexcan = netdev_priv(dev); + struct net_device_stats *stats = dev->get_stats(dev); + struct can_hw_mb *hwmb; + struct can_frame *frame; + struct sk_buff *skb; + unsigned int tmp; + + hwmb = flexcan->hwmb + index; + if (flexcan->fifo || (index >= (flexcan->maxmb - flexcan->xmit_maxmb))) { + if (hwmb->mb_cs.cs.code == CAN_MB_TX_ABORT) + hwmb->mb_cs.cs.code = CAN_MB_TX_INACTIVE; + + if (hwmb->mb_cs.cs.code & CAN_MB_TX_INACTIVE) { + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + return; + } + } + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + if (hwmb->mb_cs.cs.ide) + frame->can_id = + (hwmb->mb_id & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + frame->can_id = (hwmb->mb_id >> 18) & CAN_SFF_MASK; + + if (hwmb->mb_cs.cs.rtr) + frame->can_id |= CAN_RTR_FLAG; + + frame->can_dlc = hwmb->mb_cs.cs.length; + + if (frame->can_dlc && frame->can_dlc) + flexcan_memcpy(frame->data, hwmb->mb_data, + frame->can_dlc); + + if (flexcan->fifo + || (index >= (flexcan->maxmb - flexcan->xmit_maxmb))) { + hwmb->mb_cs.cs.code = CAN_MB_TX_INACTIVE; + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + } + + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += frame->can_dlc; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + } else { + tmp = hwmb->mb_cs.data; + tmp = hwmb->mb_id; + tmp = hwmb->mb_data[0]; + if (flexcan->fifo + || (index >= (flexcan->maxmb - flexcan->xmit_maxmb))) { + + hwmb->mb_cs.cs.code = CAN_MB_TX_INACTIVE; + if (netif_queue_stopped(dev)) + netif_start_queue(dev); + } + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + stats->rx_dropped++; + } +} + +static void flexcan_fifo_isr(struct net_device *dev, unsigned int iflag1) +{ + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + struct net_device_stats *stats = dev->get_stats(dev); + struct sk_buff *skb; + struct can_hw_mb *hwmb = flexcan->hwmb; + struct can_frame *frame; + unsigned int tmp; + + if (iflag1 & __FIFO_RDY_INT) { + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = + (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + if (hwmb->mb_cs.cs.ide) + frame->can_id = + (hwmb->mb_id & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + frame->can_id = + (hwmb->mb_id >> 18) & CAN_SFF_MASK; + + if (hwmb->mb_cs.cs.rtr) + frame->can_id |= CAN_RTR_FLAG; + + frame->can_dlc = hwmb->mb_cs.cs.length; + + if (frame->can_dlc && (frame->can_dlc <= 8)) + flexcan_memcpy(frame->data, hwmb->mb_data, + frame->can_dlc); + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + + dev->last_rx = jiffies; + + stats->rx_packets++; + stats->rx_bytes += frame->can_dlc; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + } else { + tmp = hwmb->mb_cs.data; + tmp = hwmb->mb_id; + tmp = hwmb->mb_data[0]; + tmp = __raw_readl(flexcan->io_base + CAN_HW_REG_TIMER); + } + } + + if (iflag1 & (__FIFO_OV_INT | __FIFO_WARN_INT)) { + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = + (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + frame->can_id = CAN_ERR_FLAG | CAN_ERR_CRTL; + frame->can_dlc = CAN_ERR_DLC; + if (iflag1 & __FIFO_WARN_INT) + frame->data[1] |= + CAN_ERR_CRTL_TX_WARNING | + CAN_ERR_CRTL_RX_WARNING; + if (iflag1 & __FIFO_OV_INT) + frame->data[1] |= CAN_ERR_CRTL_RX_OVERFLOW; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + } + } +} + +/*! + * @brief The function call by CAN ISR to handle mb events. + * + * @param dev the pointer of network device. + * + * @return none + */ +void flexcan_mbm_isr(struct net_device *dev) +{ + int i, iflag1, iflag2, maxmb; + struct flexcan_device *flexcan = dev ? netdev_priv(dev) : NULL; + + if (flexcan->maxmb > 31) { + maxmb = flexcan->maxmb + 1 - 32; + iflag1 = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1) & + __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); + iflag2 = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG2) & + __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK2); + iflag2 &= (1 << maxmb) - 1; + maxmb = 32; + } else { + maxmb = flexcan->maxmb + 1; + iflag1 = __raw_readl(flexcan->io_base + CAN_HW_REG_IFLAG1) & + __raw_readl(flexcan->io_base + CAN_HW_REG_IMASK1); + iflag1 &= (1 << maxmb) - 1; + iflag2 = 0; + } + + __raw_writel(iflag1, flexcan->io_base + CAN_HW_REG_IFLAG1); + __raw_writel(iflag2, flexcan->io_base + CAN_HW_REG_IFLAG2); + + if (flexcan->fifo) { + flexcan_fifo_isr(dev, iflag1); + iflag1 &= 0xFFFFFF00; + } + for (i = 0; iflag1 && (i < maxmb); i++) { + if (iflag1 & (1 << i)) { + iflag1 &= ~(1 << i); + flexcan_mb_bottom(dev, i); + } + } + + for (i = maxmb; iflag2 && (i <= flexcan->maxmb); i++) { + if (iflag2 & (1 << (i - 32))) { + iflag2 &= ~(1 << (i - 32)); + flexcan_mb_bottom(dev, i); + } + } +} + +/*! + * @brief function to xmit message buffer + * + * @param flexcan the pointer of can hardware device. + * @param frame the pointer of can message frame. + * + * @return Returns 0 if xmit is success. otherwise returns non-zero. + */ +int flexcan_mbm_xmit(struct flexcan_device *flexcan, struct can_frame *frame) +{ + int i = flexcan->xmit_mb; + struct can_hw_mb *hwmb = flexcan->hwmb; + + do { + if (hwmb[i].mb_cs.cs.code == CAN_MB_TX_INACTIVE) + break; + if ((++i) > flexcan->maxmb) { + if (flexcan->fifo) + i = FLEXCAN_MAX_FIFO_MB; + else + i = flexcan->xmit_maxmb + 1; + } + if (i == flexcan->xmit_mb) + return -1; + } while (1); + + flexcan->xmit_mb = i + 1; + if (flexcan->xmit_mb > flexcan->maxmb) { + if (flexcan->fifo) + flexcan->xmit_mb = FLEXCAN_MAX_FIFO_MB; + else + flexcan->xmit_mb = flexcan->xmit_maxmb + 1; + } + + if (frame->can_id & CAN_RTR_FLAG) + hwmb[i].mb_cs.cs.rtr = 1; + else + hwmb[i].mb_cs.cs.rtr = 0; + + if (frame->can_id & CAN_EFF_FLAG) { + hwmb[i].mb_cs.cs.ide = 1; + hwmb[i].mb_cs.cs.srr = 1; + hwmb[i].mb_id = frame->can_id & CAN_EFF_MASK; + } else { + hwmb[i].mb_cs.cs.ide = 0; + hwmb[i].mb_id = (frame->can_id & CAN_SFF_MASK) << 18; + } + + hwmb[i].mb_cs.cs.length = frame->can_dlc; + flexcan_memcpy(hwmb[i].mb_data, frame->data, frame->can_dlc); + hwmb[i].mb_cs.cs.code = CAN_MB_TX_ONCE; + return 0; +} + +/*! + * @brief function to initial message buffer + * + * @param flexcan the pointer of can hardware device. + * + * @return none + */ +void flexcan_mbm_init(struct flexcan_device *flexcan) +{ + struct can_hw_mb *hwmb; + int rx_mb, i; + + /* Set global mask to receive all messages */ + __raw_writel(0, flexcan->io_base + CAN_HW_REG_RXGMASK); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_RX14MASK); + __raw_writel(0, flexcan->io_base + CAN_HW_REG_RX15MASK); + + memset(flexcan->hwmb, 0, sizeof(*hwmb) * FLEXCAN_MAX_MB); + /* Set individual mask to receive all messages */ + memset(flexcan->rx_mask, 0, sizeof(unsigned int) * FLEXCAN_MAX_MB); + + if (flexcan->fifo) + rx_mb = FLEXCAN_MAX_FIFO_MB; + else + rx_mb = flexcan->maxmb - flexcan->xmit_maxmb; + + hwmb = flexcan->hwmb; + if (flexcan->fifo) { + unsigned long *id_table = flexcan->io_base + CAN_FIFO_BASE; + for (i = 0; i < rx_mb; i++) + id_table[i] = 0; + } else { + for (i = 0; i < rx_mb; i++) { + hwmb[i].mb_cs.cs.code = CAN_MB_RX_EMPTY; + /* + * IDE bit can not control by mask registers + * So set message buffer to receive extend + * or standard message. + */ + if (flexcan->ext_msg && flexcan->std_msg) + hwmb[i].mb_cs.cs.ide = i & 1; + else { + if (flexcan->ext_msg) + hwmb[i].mb_cs.cs.ide = 1; + } + } + } + + for (; i <= flexcan->maxmb; i++) + hwmb[i].mb_cs.cs.code = CAN_MB_TX_INACTIVE; + + flexcan->xmit_mb = rx_mb; +} diff --git a/drivers/net/can/mcp251x.c b/drivers/net/can/mcp251x.c new file mode 100644 index 000000000000..1dcda35e0d7d --- /dev/null +++ b/drivers/net/can/mcp251x.c @@ -0,0 +1,1239 @@ +/* + * + * CAN bus driver for Microchip 251x CAN Controller with SPI Interface + * + * MCP2510 support and bug fixes by Christian Pellegrin + * <chripell@evolware.org> + * + * Copyright 2007 Raymarine UK, Ltd. All Rights Reserved. + * Written under contract by: + * Chris Elston, Katalix Systems, Ltd. + * + * Based on Microchip MCP251x CAN controller driver written by + * David Vrabel, Copyright 2006 Arcom Control Systems Ltd. + * + * Based on CAN bus driver for the CCAN controller written by + * - Sascha Hauer, Marc Kleine-Budde, Pengutronix + * - Simon Kallweit, intefo AG + * Copyright 2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * + * Your platform definition file should specify something like: + * + * static struct mcp251x_platform_data mcp251x_info = { + * .oscillator_frequency = 8000000, + * .board_specific_setup = &mcp251x_setup, + * .model = CAN_MCP251X_MCP2510, + * .power_enable = mcp251x_power_enable, + * .transceiver_enable = NULL, + * }; + * + * static struct spi_board_info spi_board_info[] = { + * { + * .modalias = "mcp251x", + * .platform_data = &mcp251x_info, + * .irq = IRQ_EINT13, + * .max_speed_hz = 2*1000*1000, + * .chip_select = 2, + * }, + * }; + * + * Please see mcp251x.h for a description of the fields in + * struct mcp251x_platform_data. + * + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/netdevice.h> +#include <linux/can.h> +#include <linux/spi/spi.h> +#include <linux/can/dev.h> +#include <linux/can/core.h> +#include <linux/if_arp.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/freezer.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/can/platform/mcp251x.h> + +/* SPI interface instruction set */ +#define INSTRUCTION_WRITE 0x02 +#define INSTRUCTION_READ 0x03 +#define INSTRUCTION_BIT_MODIFY 0x05 +#define INSTRUCTION_LOAD_TXB(n) (0x40 + 2 * (n)) +#define INSTRUCTION_READ_RXB(n) (((n) == 0) ? 0x90 : 0x94) +#define INSTRUCTION_RESET 0xC0 + +/* MPC251x registers */ +#define CANSTAT 0x0e +#define CANCTRL 0x0f +# define CANCTRL_REQOP_MASK 0xe0 +# define CANCTRL_REQOP_CONF 0x80 +# define CANCTRL_REQOP_LISTEN_ONLY 0x60 +# define CANCTRL_REQOP_LOOPBACK 0x40 +# define CANCTRL_REQOP_SLEEP 0x20 +# define CANCTRL_REQOP_NORMAL 0x00 +# define CANCTRL_OSM 0x08 +# define CANCTRL_ABAT 0x10 +#define TEC 0x1c +#define REC 0x1d +#define CNF1 0x2a +#define CNF2 0x29 +# define CNF2_BTLMODE 0x80 +#define CNF3 0x28 +# define CNF3_SOF 0x08 +# define CNF3_WAKFIL 0x04 +# define CNF3_PHSEG2_MASK 0x07 +#define CANINTE 0x2b +# define CANINTE_MERRE 0x80 +# define CANINTE_WAKIE 0x40 +# define CANINTE_ERRIE 0x20 +# define CANINTE_TX2IE 0x10 +# define CANINTE_TX1IE 0x08 +# define CANINTE_TX0IE 0x04 +# define CANINTE_RX1IE 0x02 +# define CANINTE_RX0IE 0x01 +#define CANINTF 0x2c +# define CANINTF_MERRF 0x80 +# define CANINTF_WAKIF 0x40 +# define CANINTF_ERRIF 0x20 +# define CANINTF_TX2IF 0x10 +# define CANINTF_TX1IF 0x08 +# define CANINTF_TX0IF 0x04 +# define CANINTF_RX1IF 0x02 +# define CANINTF_RX0IF 0x01 +#define EFLG 0x2d +# define EFLG_EWARN 0x01 +# define EFLG_RXWAR 0x02 +# define EFLG_TXWAR 0x04 +# define EFLG_RXEP 0x08 +# define EFLG_TXEP 0x10 +# define EFLG_TXBO 0x20 +# define EFLG_RX0OVR 0x40 +# define EFLG_RX1OVR 0x80 +#define TXBCTRL(n) ((n * 0x10) + 0x30) +# define TXBCTRL_ABTF 0x40 +# define TXBCTRL_MLOA 0x20 +# define TXBCTRL_TXERR 0x10 +# define TXBCTRL_TXREQ 0x08 +#define RXBCTRL(n) ((n * 0x10) + 0x60) +# define RXBCTRL_BUKT 0x04 +# define RXBCTRL_RXM0 0x20 +# define RXBCTRL_RXM1 0x40 + +/* Buffer size required for the largest SPI transfer (i.e., reading a + * frame). */ +#define CAN_FRAME_MAX_DATA_LEN 8 +#define SPI_TRANSFER_BUF_LEN (2*(6 + CAN_FRAME_MAX_DATA_LEN)) +#define CAN_FRAME_MAX_BITS 128 + +#define DEVICE_NAME "mcp251x" + +static int mcp251x_enable_dma; /* Enable SPI DMA. Default: 0 (Off) */ +module_param(mcp251x_enable_dma, int, S_IRUGO); +MODULE_PARM_DESC(mcp251x_enable_dma, "Enable SPI DMA. Default: 0 (Off)"); + +static struct can_bittiming_const mcp251x_bittiming_const = { + .tseg1_min = 3, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 64, + .brp_inc = 1, +}; + +struct mcp251x_priv { + struct can_priv can; + struct net_device *net; + struct spi_device *spi; + + struct mutex spi_lock; /* SPI buffer lock */ + u8 *spi_tx_buf; + u8 *spi_rx_buf; + dma_addr_t spi_tx_dma; + dma_addr_t spi_rx_dma; + + struct sk_buff *tx_skb; + struct workqueue_struct *wq; + struct work_struct tx_work; + struct work_struct irq_work; + struct completion awake; + int wake; + int force_quit; + int after_suspend; +#define AFTER_SUSPEND_UP 1 +#define AFTER_SUSPEND_DOWN 2 +#define AFTER_SUSPEND_POWER 4 + int restart_tx; +}; + +static u8 mcp251x_read_reg(struct spi_device *spi, uint8_t reg) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + struct spi_transfer t = { + .tx_buf = priv->spi_tx_buf, + .rx_buf = priv->spi_rx_buf, + .len = 3, + .cs_change = 0, + }; + struct spi_message m; + u8 val = 0; + int ret; + + mutex_lock(&priv->spi_lock); + + priv->spi_tx_buf[0] = INSTRUCTION_READ; + priv->spi_tx_buf[1] = reg; + + spi_message_init(&m); + + if (mcp251x_enable_dma) { + t.tx_dma = priv->spi_tx_dma; + t.rx_dma = priv->spi_rx_dma; + m.is_dma_mapped = 1; + } + + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + if (ret < 0) + dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __func__, ret); + else + val = priv->spi_rx_buf[2]; + + mutex_unlock(&priv->spi_lock); + + dev_dbg(&spi->dev, "%s: read %02x = %02x\n", __func__, reg, val); + return val; +} + +static void mcp251x_write_reg(struct spi_device *spi, u8 reg, uint8_t val) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + struct spi_transfer t = { + .tx_buf = priv->spi_tx_buf, + .rx_buf = priv->spi_rx_buf, + .len = 3, + .cs_change = 0, + }; + struct spi_message m; + int ret; + + mutex_lock(&priv->spi_lock); + + priv->spi_tx_buf[0] = INSTRUCTION_WRITE; + priv->spi_tx_buf[1] = reg; + priv->spi_tx_buf[2] = val; + + spi_message_init(&m); + + if (mcp251x_enable_dma) { + t.tx_dma = priv->spi_tx_dma; + t.rx_dma = priv->spi_rx_dma; + m.is_dma_mapped = 1; + } + + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + + mutex_unlock(&priv->spi_lock); + + if (ret < 0) + dev_dbg(&spi->dev, "%s: failed\n", __func__); +} + +static void mcp251x_write_bits(struct spi_device *spi, u8 reg, + u8 mask, uint8_t val) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + struct spi_transfer t = { + .tx_buf = priv->spi_tx_buf, + .rx_buf = priv->spi_rx_buf, + .len = 4, + .cs_change = 0, + }; + struct spi_message m; + int ret; + + mutex_lock(&priv->spi_lock); + + priv->spi_tx_buf[0] = INSTRUCTION_BIT_MODIFY; + priv->spi_tx_buf[1] = reg; + priv->spi_tx_buf[2] = mask; + priv->spi_tx_buf[3] = val; + + spi_message_init(&m); + + if (mcp251x_enable_dma) { + t.tx_dma = priv->spi_tx_dma; + t.rx_dma = priv->spi_rx_dma; + m.is_dma_mapped = 1; + } + + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + + mutex_unlock(&priv->spi_lock); + + if (ret < 0) + dev_dbg(&spi->dev, "%s: failed\n", __func__); +} + +static int mcp251x_hw_tx(struct spi_device *spi, struct can_frame *frame, + int tx_buf_idx) +{ + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + u32 sid, eid, exide, rtr; + + dev_dbg(&spi->dev, "%s\n", __func__); + + exide = (frame->can_id & CAN_EFF_FLAG) ? 1 : 0; /* Extended ID Enable */ + if (exide) + sid = (frame->can_id & CAN_EFF_MASK) >> 18; + else + sid = frame->can_id & CAN_SFF_MASK; /* Standard ID */ + eid = frame->can_id & CAN_EFF_MASK; /* Extended ID */ + rtr = (frame->can_id & CAN_RTR_FLAG) ? 1 : 0; /* Remote transmission */ + + if (pdata->model == CAN_MCP251X_MCP2510) { + int i; + + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx) + 1, sid >> 3); + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx) + 2, + ((sid & 7) << 5) | (exide << 3) | + ((eid >> 16) & 3)); + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx) + 3, + (eid >> 8) & 0xff); + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx) + 4, eid & 0xff); + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx) + 5, + (rtr << 6) | frame->can_dlc); + + for (i = 0; i < frame->can_dlc ; i++) { + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx) + 6 + i, + frame->data[i]); + } + } else { + struct spi_transfer t = { + .tx_buf = priv->spi_tx_buf, + .rx_buf = priv->spi_rx_buf, + .cs_change = 0, + .len = 6 + CAN_FRAME_MAX_DATA_LEN, + }; + struct spi_message m; + int ret; + u8 *tx_buf = priv->spi_tx_buf; + + mutex_lock(&priv->spi_lock); + + tx_buf[0] = INSTRUCTION_LOAD_TXB(tx_buf_idx); + tx_buf[1] = sid >> 3; + tx_buf[2] = ((sid & 7) << 5) | (exide << 3) | + ((eid >> 16) & 3); + tx_buf[3] = (eid >> 8) & 0xff; + tx_buf[4] = eid & 0xff; + tx_buf[5] = (rtr << 6) | frame->can_dlc; + + memcpy(tx_buf + 6, frame->data, frame->can_dlc); + + spi_message_init(&m); + + if (mcp251x_enable_dma) { + t.tx_dma = priv->spi_tx_dma; + t.rx_dma = priv->spi_rx_dma; + m.is_dma_mapped = 1; + } + + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + + mutex_unlock(&priv->spi_lock); + + if (ret < 0) { + dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __func__, + ret); + return -1; + } + } + mcp251x_write_reg(spi, TXBCTRL(tx_buf_idx), TXBCTRL_TXREQ); + return 0; +} + +static void mcp251x_hw_rx(struct spi_device *spi, int buf_idx) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + struct sk_buff *skb; + struct can_frame *frame; + + dev_dbg(&spi->dev, "%s\n", __func__); + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) { + dev_dbg(&spi->dev, "%s: out of memory for Rx'd frame\n", + __func__); + priv->net->stats.rx_dropped++; + return; + } + skb->dev = priv->net; + frame = (struct can_frame *)skb_put(skb, sizeof(struct can_frame)); + + if (pdata->model == CAN_MCP251X_MCP2510) { + int i; + u8 rx_buf[6]; + + rx_buf[1] = mcp251x_read_reg(spi, RXBCTRL(buf_idx) + 1); + rx_buf[2] = mcp251x_read_reg(spi, RXBCTRL(buf_idx) + 2); + rx_buf[3] = mcp251x_read_reg(spi, RXBCTRL(buf_idx) + 3); + rx_buf[4] = mcp251x_read_reg(spi, RXBCTRL(buf_idx) + 4); + rx_buf[5] = mcp251x_read_reg(spi, RXBCTRL(buf_idx) + 5); + + if ((rx_buf[2] >> 3) & 0x1) { + /* Extended ID format */ + frame->can_id = CAN_EFF_FLAG; + frame->can_id |= ((rx_buf[2] & 3) << 16) | + (rx_buf[3] << 8) | rx_buf[4] | + (((rx_buf[1] << 3) | (rx_buf[2] >> 5)) << 18); + } else { + /* Standard ID format */ + frame->can_id = (rx_buf[1] << 3) | (rx_buf[2] >> 5); + } + + if ((rx_buf[5] >> 6) & 0x1) { + /* Remote transmission request */ + frame->can_id |= CAN_RTR_FLAG; + } + + /* Data length */ + frame->can_dlc = rx_buf[5] & 0x0f; + if (frame->can_dlc > 8) { + dev_warn(&spi->dev, "invalid frame recevied\n"); + priv->net->stats.rx_errors++; + dev_kfree_skb(skb); + return; + } + + for (i = 0; i < frame->can_dlc; i++) { + frame->data[i] = mcp251x_read_reg(spi, + RXBCTRL(buf_idx) + + 6 + i); + } + } else { + struct spi_transfer t = { + .tx_buf = priv->spi_tx_buf, + .rx_buf = priv->spi_rx_buf, + .cs_change = 0, + .len = 14, /* RX buffer: RXBnCTRL to RXBnD7 */ + }; + struct spi_message m; + int ret; + u8 *tx_buf = priv->spi_tx_buf; + u8 *rx_buf = priv->spi_rx_buf; + + mutex_lock(&priv->spi_lock); + + tx_buf[0] = INSTRUCTION_READ_RXB(buf_idx); + + spi_message_init(&m); + + if (mcp251x_enable_dma) { + t.tx_dma = priv->spi_tx_dma; + t.rx_dma = priv->spi_rx_dma; + m.is_dma_mapped = 1; + } + + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + + if (ret < 0) { + dev_dbg(&spi->dev, "%s: failed: ret = %d\n", + __func__, ret); + priv->net->stats.rx_errors++; + mutex_unlock(&priv->spi_lock); + return; + } + + if ((rx_buf[2] >> 3) & 0x1) { + /* Extended ID format */ + frame->can_id = CAN_EFF_FLAG; + frame->can_id |= ((rx_buf[2] & 3) << 16) | + (rx_buf[3] << 8) | rx_buf[4] | + (((rx_buf[1] << 3) | (rx_buf[2] >> 5)) << 18); + } else { + /* Standard ID format */ + frame->can_id = (rx_buf[1] << 3) | (rx_buf[2] >> 5); + } + + if ((rx_buf[5] >> 6) & 0x1) { + /* Remote transmission request */ + frame->can_id |= CAN_RTR_FLAG; + } + + /* Data length */ + frame->can_dlc = rx_buf[5] & 0x0f; + if (frame->can_dlc > 8) { + dev_warn(&spi->dev, "invalid frame recevied\n"); + priv->net->stats.rx_errors++; + dev_kfree_skb(skb); + mutex_unlock(&priv->spi_lock); + return; + } + + memcpy(frame->data, rx_buf + 6, CAN_FRAME_MAX_DATA_LEN); + + mutex_unlock(&priv->spi_lock); + } + + priv->net->stats.rx_packets++; + priv->net->stats.rx_bytes += frame->can_dlc; + + skb->protocol = __constant_htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); +} + +static void mcp251x_hw_sleep(struct spi_device *spi) +{ + mcp251x_write_reg(spi, CANCTRL, CANCTRL_REQOP_SLEEP); +} + +static void mcp251x_hw_wakeup(struct spi_device *spi) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + + priv->wake = 1; + + /* Can only wake up by generating a wake-up interrupt. */ + mcp251x_write_bits(spi, CANINTE, CANINTE_WAKIE, CANINTE_WAKIE); + mcp251x_write_bits(spi, CANINTF, CANINTF_WAKIF, CANINTF_WAKIF); + + /* Wait until the device is awake */ + if (!wait_for_completion_timeout(&priv->awake, HZ)) + dev_err(&spi->dev, "MCP251x didn't wake-up\n"); +} + +static int mcp251x_hard_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct mcp251x_priv *priv = netdev_priv(net); + struct spi_device *spi = priv->spi; + + dev_dbg(&spi->dev, "%s\n", __func__); + + if (priv->tx_skb) { + dev_warn(&spi->dev, "hard_xmit called with not null tx_skb\n"); + return NETDEV_TX_BUSY; + } + + if (skb->len != sizeof(struct can_frame)) { + dev_dbg(&spi->dev, "dropping packet - bad length\n"); + dev_kfree_skb(skb); + net->stats.tx_dropped++; + return 0; + } + + netif_stop_queue(net); + priv->tx_skb = skb; + net->trans_start = jiffies; + queue_work(priv->wq, &priv->tx_work); + + return NETDEV_TX_OK; +} + +static int mcp251x_do_set_mode(struct net_device *net, enum can_mode mode) +{ + struct mcp251x_priv *priv = netdev_priv(net); + struct spi_device *spi = priv->spi; + + dev_dbg(&spi->dev, "%s (unimplemented)\n", __func__); + + switch (mode) { + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static void mcp251x_set_normal_mode(struct spi_device *spi) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + unsigned long timeout; + + /* Enable interrupts */ + mcp251x_write_reg(spi, CANINTE, + CANINTE_ERRIE | CANINTE_TX2IE | CANINTE_TX1IE | + CANINTE_TX0IE | CANINTE_RX1IE | CANINTE_RX0IE); + + if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) { + /* Put device into loopback mode */ + mcp251x_write_reg(spi, CANCTRL, CANCTRL_REQOP_LOOPBACK); + } else { + /* Put device into normal mode */ + mcp251x_write_reg(spi, CANCTRL, CANCTRL_REQOP_NORMAL); + + /* Wait for the device to enter normal mode */ + timeout = jiffies + HZ; + while (mcp251x_read_reg(spi, CANSTAT) & 0xE0) { + udelay(10); + if (time_after(jiffies, timeout)) { + dev_err(&spi->dev, "MCP251x didn't" + " enter in normal mode\n"); + break; + } + } + } +} + +static int mcp251x_do_set_bittiming(struct net_device *net) +{ + struct mcp251x_priv *priv = netdev_priv(net); + struct can_bittiming *bt = &priv->can.bittiming; + struct spi_device *spi = priv->spi; + u8 state; + + dev_dbg(&spi->dev, "%s: BRP = %d, PropSeg = %d, PS1 = %d," + " PS2 = %d, SJW = %d\n", __func__, bt->brp, + bt->prop_seg, bt->phase_seg1, bt->phase_seg2, + bt->sjw); + + /* Store original mode and set mode to config */ + state = mcp251x_read_reg(spi, CANCTRL); + state = mcp251x_read_reg(spi, CANSTAT) & CANCTRL_REQOP_MASK; + mcp251x_write_bits(spi, CANCTRL, CANCTRL_REQOP_MASK, + CANCTRL_REQOP_CONF); + + mcp251x_write_reg(spi, CNF1, ((bt->sjw - 1) << 6) | (bt->brp - 1)); + mcp251x_write_reg(spi, CNF2, CNF2_BTLMODE | + ((bt->phase_seg1 - 1) << 3) | + (bt->prop_seg - 1)); + mcp251x_write_bits(spi, CNF3, CNF3_PHSEG2_MASK, + (bt->phase_seg2 - 1)); + + /* Restore original state */ + mcp251x_write_bits(spi, CANCTRL, CANCTRL_REQOP_MASK, state); + + return 0; +} + +static void mcp251x_setup(struct net_device *net, struct mcp251x_priv *priv, + struct spi_device *spi) +{ + int ret; + + /* Set initial baudrate. Make sure that registers are updated + always by explicitly calling mcp251x_do_set_bittiming */ + ret = can_set_bittiming(net); + if (ret) + dev_err(&spi->dev, "unable to set initial baudrate!\n"); + else + mcp251x_do_set_bittiming(net); + + /* Enable RX0->RX1 buffer roll over and disable filters */ + mcp251x_write_bits(spi, RXBCTRL(0), + RXBCTRL_BUKT | RXBCTRL_RXM0 | RXBCTRL_RXM1, + RXBCTRL_BUKT | RXBCTRL_RXM0 | RXBCTRL_RXM1); + mcp251x_write_bits(spi, RXBCTRL(1), + RXBCTRL_RXM0 | RXBCTRL_RXM1, + RXBCTRL_RXM0 | RXBCTRL_RXM1); + + dev_dbg(&spi->dev, "%s RXBCTL 0 and 1: %02x %02x\n", __func__, + mcp251x_read_reg(spi, RXBCTRL(0)), + mcp251x_read_reg(spi, RXBCTRL(1))); +} + +static void mcp251x_hw_reset(struct spi_device *spi) +{ + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + int ret; + + mutex_lock(&priv->spi_lock); + + priv->spi_tx_buf[0] = INSTRUCTION_RESET; + + ret = spi_write(spi, priv->spi_tx_buf, 1); + + mutex_unlock(&priv->spi_lock); + + if (ret < 0) + dev_dbg(&spi->dev, "%s: failed: ret = %d\n", __func__, ret); + /* wait for reset to finish */ + mdelay(10); +} + +static int mcp251x_hw_probe(struct spi_device *spi) +{ + int st1, st2; + + mcp251x_hw_reset(spi); + + st1 = mcp251x_read_reg(spi, CANSTAT) & 0xEE; + st2 = mcp251x_read_reg(spi, CANCTRL) & 0x17; + + dev_dbg(&spi->dev, "%s: 0x%02x - 0x%02x\n", __func__, + st1, st2); + + /* check for power up default values */ + return (st1 == 0x80 && st2 == 0x07) ? 1 : 0; +} + +static int mcp251x_open(struct net_device *net) +{ + struct mcp251x_priv *priv = netdev_priv(net); + struct spi_device *spi = priv->spi; + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + + dev_dbg(&spi->dev, "%s\n", __func__); + + if (pdata->transceiver_enable) + pdata->transceiver_enable(1); + + priv->force_quit = 0; + priv->tx_skb = NULL; + enable_irq(spi->irq); + mcp251x_hw_wakeup(spi); + mcp251x_hw_reset(spi); + mcp251x_setup(net, priv, spi); + mcp251x_set_normal_mode(spi); + netif_wake_queue(net); + + return 0; +} + +static int mcp251x_stop(struct net_device *net) +{ + struct mcp251x_priv *priv = netdev_priv(net); + struct spi_device *spi = priv->spi; + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + + dev_dbg(&spi->dev, "%s\n", __func__); + + /* Disable and clear pending interrupts */ + mcp251x_write_reg(spi, CANINTE, 0x00); + mcp251x_write_reg(spi, CANINTF, 0x00); + + priv->force_quit = 1; + disable_irq(spi->irq); + flush_workqueue(priv->wq); + + mcp251x_write_reg(spi, TXBCTRL(0), 0); + if (priv->tx_skb) { + net->stats.tx_errors++; + dev_kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + } + + mcp251x_hw_sleep(spi); + + if (pdata->transceiver_enable) + pdata->transceiver_enable(0); + + return 0; +} + +static int mcp251x_do_get_state(struct net_device *net, enum can_state *state) +{ + struct mcp251x_priv *priv = netdev_priv(net); + struct spi_device *spi = priv->spi; + u8 eflag; + + eflag = mcp251x_read_reg(spi, EFLG); + + if (eflag & EFLG_TXBO) + *state = CAN_STATE_BUS_OFF; + else if (eflag & (EFLG_RXEP | EFLG_TXEP)) + *state = CAN_STATE_BUS_PASSIVE; + else if (eflag & EFLG_EWARN) + *state = CAN_STATE_BUS_WARNING; + else + *state = CAN_STATE_ACTIVE; + + return 0; +} + +static void mcp251x_tx_work_handler(struct work_struct *ws) +{ + struct mcp251x_priv *priv = container_of(ws, struct mcp251x_priv, + tx_work); + struct spi_device *spi = priv->spi; + struct can_frame *frame; + + dev_dbg(&spi->dev, "%s\n", __func__); + + if (priv->tx_skb) { + frame = (struct can_frame *)priv->tx_skb->data; + if (frame->can_dlc > CAN_FRAME_MAX_DATA_LEN) + frame->can_dlc = CAN_FRAME_MAX_DATA_LEN; + mcp251x_hw_tx(spi, frame, 0); + } +} + +static void mcp251x_irq_work_handler(struct work_struct *ws) +{ + struct mcp251x_priv *priv = container_of(ws, struct mcp251x_priv, + irq_work); + struct spi_device *spi = priv->spi; + struct net_device *net = priv->net; + u8 intf; + u8 txbnctrl; + + if (priv->after_suspend) { + /* Wait whilst the device wakes up */ + mdelay(10); + mcp251x_hw_reset(spi); + mcp251x_setup(net, priv, spi); + if (priv->after_suspend & AFTER_SUSPEND_UP) { + netif_device_attach(net); + /* clear since we lost tx buffer */ + if (priv->tx_skb) { + net->stats.tx_errors++; + dev_kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + netif_wake_queue(net); + } + mcp251x_set_normal_mode(spi); + } else + mcp251x_hw_sleep(spi); + priv->after_suspend = 0; + return; + } + + while (!priv->force_quit && !freezing(current)) { + if (priv->restart_tx) { + priv->restart_tx = 0; + dev_warn(&spi->dev, + "timeout in txing a packet, restarting\n"); + mcp251x_write_reg(spi, TXBCTRL(0), 0); + if (priv->tx_skb) { + net->stats.tx_errors++; + dev_kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + } + netif_wake_queue(net); + } + + if (priv->wake) { + /* Wait whilst the device wakes up */ + mdelay(10); + priv->wake = 0; + } + + intf = mcp251x_read_reg(spi, CANINTF); + if (intf == 0x00) + break; + mcp251x_write_bits(spi, CANINTF, intf, 0x00); + + dev_dbg(&spi->dev, "interrupt:%s%s%s%s%s%s%s%s\n", + (intf & CANINTF_MERRF) ? " MERR" : "", + (intf & CANINTF_WAKIF) ? " WAK" : "", + (intf & CANINTF_ERRIF) ? " ERR" : "", + (intf & CANINTF_TX2IF) ? " TX2" : "", + (intf & CANINTF_TX1IF) ? " TX1" : "", + (intf & CANINTF_TX0IF) ? " TX0" : "", + (intf & CANINTF_RX1IF) ? " RX1" : "", + (intf & CANINTF_RX0IF) ? " RX0" : ""); + + if (intf & CANINTF_WAKIF) + complete(&priv->awake); + + if (intf & CANINTF_MERRF) { + /* if there are no pending Tx buffers, restart queue */ + txbnctrl = mcp251x_read_reg(spi, TXBCTRL(0)); + if (!(txbnctrl & TXBCTRL_TXREQ)) { + if (priv->tx_skb) { + net->stats.tx_errors++; + dev_kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + } + netif_wake_queue(net); + } + } + + if (intf & CANINTF_ERRIF) { + struct sk_buff *skb; + struct can_frame *frame = NULL; + u8 eflag = mcp251x_read_reg(spi, EFLG); + + dev_dbg(&spi->dev, "EFLG = 0x%02x\n", eflag); + + /* Create error frame */ + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb) { + frame = (struct can_frame *) + skb_put(skb, sizeof(struct can_frame)); + *(unsigned long long *)&frame->data = 0ULL; + frame->can_id = CAN_ERR_FLAG; + frame->can_dlc = CAN_ERR_DLC; + + skb->dev = net; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + + /* Set error frame flags based on bus state */ + if (eflag & EFLG_TXBO) { + frame->can_id |= CAN_ERR_BUSOFF; + } else if (eflag & EFLG_TXEP) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] |= + CAN_ERR_CRTL_TX_PASSIVE; + } else if (eflag & EFLG_RXEP) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] |= + CAN_ERR_CRTL_RX_PASSIVE; + } else if (eflag & EFLG_TXWAR) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] |= + CAN_ERR_CRTL_TX_WARNING; + } else if (eflag & EFLG_RXWAR) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] |= + CAN_ERR_CRTL_RX_WARNING; + } + } + + if (eflag & (EFLG_RX0OVR | EFLG_RX1OVR)) { + if (eflag & EFLG_RX0OVR) + net->stats.rx_over_errors++; + if (eflag & EFLG_RX1OVR) + net->stats.rx_over_errors++; + if (frame) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] = + CAN_ERR_CRTL_RX_OVERFLOW; + } + } + mcp251x_write_reg(spi, EFLG, 0x00); + + if (skb) + netif_rx(skb); + } + + if (intf & (CANINTF_TX2IF | CANINTF_TX1IF | CANINTF_TX0IF)) { + if (priv->tx_skb) { + net->stats.tx_packets++; + net->stats.tx_bytes += + ((struct can_frame *) + (priv->tx_skb->data))->can_dlc; + dev_kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + } + netif_wake_queue(net); + } + + if (intf & CANINTF_RX0IF) + mcp251x_hw_rx(spi, 0); + + if (intf & CANINTF_RX1IF) + mcp251x_hw_rx(spi, 1); + + } + + mcp251x_read_reg(spi, CANSTAT); + + dev_dbg(&spi->dev, "interrupt ended\n"); +} + +static irqreturn_t mcp251x_can_isr(int irq, void *dev_id) +{ + struct net_device *net = (struct net_device *)dev_id; + struct mcp251x_priv *priv = netdev_priv(net); + + dev_dbg(&priv->spi->dev, "%s: irq\n", __func__); + /* Schedule bottom half */ + if (!work_pending(&priv->irq_work)) + queue_work(priv->wq, &priv->irq_work); + + return IRQ_HANDLED; +} + +static void mcp251x_tx_timeout(struct net_device *net) +{ + struct mcp251x_priv *priv = netdev_priv(net); + + priv->restart_tx = 1; + queue_work(priv->wq, &priv->irq_work); +} + +static struct net_device *alloc_mcp251x_netdev(int sizeof_priv) +{ + struct net_device *net; + struct mcp251x_priv *priv; + + net = alloc_candev(sizeof_priv); + if (!net) + return NULL; + + priv = netdev_priv(net); + + net->open = mcp251x_open; + net->stop = mcp251x_stop; + net->hard_start_xmit = mcp251x_hard_start_xmit; + net->tx_timeout = mcp251x_tx_timeout; + net->watchdog_timeo = HZ; + + priv->can.bittiming_const = &mcp251x_bittiming_const; + priv->can.do_get_state = mcp251x_do_get_state; + priv->can.do_set_mode = mcp251x_do_set_mode; + + priv->net = net; + + return net; +} + +static int __devinit mcp251x_can_probe(struct spi_device *spi) +{ + struct net_device *net; + struct mcp251x_priv *priv; + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + int ret = -ENODEV; + + if (!pdata) { + /* Platform data is required for osc freq */ + goto error_out; + } + + /* Allocate can/net device */ + net = alloc_mcp251x_netdev(sizeof(struct mcp251x_priv)); + if (!net) { + ret = -ENOMEM; + goto error_alloc; + } + + priv = netdev_priv(net); + dev_set_drvdata(&spi->dev, priv); + + priv->spi = spi; + mutex_init(&priv->spi_lock); + + priv->can.bittiming.clock = pdata->oscillator_frequency / 2; + + /* If requested, allocate DMA buffers */ + if (mcp251x_enable_dma) { + spi->dev.coherent_dma_mask = DMA_32BIT_MASK; + + /* Minimum coherent DMA allocation is PAGE_SIZE, so allocate + that much and share it between Tx and Rx DMA buffers. */ + priv->spi_tx_buf = dma_alloc_coherent(&spi->dev, + PAGE_SIZE, &priv->spi_tx_dma, GFP_DMA); + + if (priv->spi_tx_buf) { + priv->spi_rx_buf = (u8 *)(priv->spi_tx_buf + + (PAGE_SIZE / 2)); + priv->spi_rx_dma = (dma_addr_t)(priv->spi_tx_dma + + (PAGE_SIZE / 2)); + } else { + /* Fall back to non-DMA */ + mcp251x_enable_dma = 0; + } + } + + /* Allocate non-DMA buffers */ + if (!mcp251x_enable_dma) { + priv->spi_tx_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL); + if (!priv->spi_tx_buf) { + ret = -ENOMEM; + goto error_tx_buf; + } + priv->spi_rx_buf = kmalloc(SPI_TRANSFER_BUF_LEN, GFP_KERNEL); + if (!priv->spi_tx_buf) { + ret = -ENOMEM; + goto error_rx_buf; + } + } + + if (pdata->power_enable) + pdata->power_enable(1); + + /* Call out to platform specific setup */ + if (pdata->board_specific_setup) + pdata->board_specific_setup(spi); + + SET_NETDEV_DEV(net, &spi->dev); + + priv->wq = create_freezeable_workqueue("mcp251x_wq"); + + INIT_WORK(&priv->tx_work, mcp251x_tx_work_handler); + INIT_WORK(&priv->irq_work, mcp251x_irq_work_handler); + + init_completion(&priv->awake); + + /* Configure the SPI bus */ + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + spi_setup(spi); + + /* Register IRQ */ + if (request_irq(spi->irq, mcp251x_can_isr, + IRQF_TRIGGER_FALLING, DEVICE_NAME, net) < 0) { + dev_err(&spi->dev, "failed to acquire irq %d\n", spi->irq); + goto error_irq; + } + disable_irq(spi->irq); + + if (!mcp251x_hw_probe(spi)) { + dev_info(&spi->dev, "Probe failed\n"); + goto error_probe; + } + mcp251x_hw_sleep(spi); + + if (pdata->transceiver_enable) + pdata->transceiver_enable(0); + + ret = register_candev(net); + if (ret >= 0) { + dev_info(&spi->dev, "probed\n"); + return ret; + } +error_probe: + free_irq(spi->irq, net); +error_irq: + if (!mcp251x_enable_dma) + kfree(priv->spi_rx_buf); +error_rx_buf: + if (!mcp251x_enable_dma) + kfree(priv->spi_tx_buf); +error_tx_buf: + free_candev(net); + if (mcp251x_enable_dma) { + dma_free_coherent(&spi->dev, PAGE_SIZE, + priv->spi_tx_buf, priv->spi_tx_dma); + } +error_alloc: + dev_err(&spi->dev, "probe failed\n"); +error_out: + return ret; +} + +static int __devexit mcp251x_can_remove(struct spi_device *spi) +{ + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + struct net_device *net = priv->net; + + unregister_candev(net); + free_candev(net); + + free_irq(spi->irq, net); + priv->force_quit = 1; + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + if (mcp251x_enable_dma) { + dma_free_coherent(&spi->dev, PAGE_SIZE, + priv->spi_tx_buf, priv->spi_tx_dma); + } else { + kfree(priv->spi_tx_buf); + kfree(priv->spi_rx_buf); + } + + if (pdata->power_enable) + pdata->power_enable(0); + + return 0; +} + +#ifdef CONFIG_PM +static int mcp251x_can_suspend(struct spi_device *spi, pm_message_t state) +{ + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + struct net_device *net = priv->net; + + if (netif_running(net)) { + netif_device_detach(net); + + mcp251x_hw_sleep(spi); + if (pdata->transceiver_enable) + pdata->transceiver_enable(0); + priv->after_suspend = AFTER_SUSPEND_UP; + } else + priv->after_suspend = AFTER_SUSPEND_DOWN; + + if (pdata->power_enable) { + pdata->power_enable(0); + priv->after_suspend |= AFTER_SUSPEND_POWER; + } + + return 0; +} + +static int mcp251x_can_resume(struct spi_device *spi) +{ + struct mcp251x_platform_data *pdata = spi->dev.platform_data; + struct mcp251x_priv *priv = dev_get_drvdata(&spi->dev); + + if (priv->after_suspend & AFTER_SUSPEND_POWER) { + pdata->power_enable(1); + queue_work(priv->wq, &priv->irq_work); + } else { + if (priv->after_suspend & AFTER_SUSPEND_UP) { + if (pdata->transceiver_enable) + pdata->transceiver_enable(1); + queue_work(priv->wq, &priv->irq_work); + } else + priv->after_suspend = 0; + } + return 0; +} +#else +#define mcp251x_can_suspend NULL +#define mcp251x_can_resume NULL +#endif + +static struct spi_driver mcp251x_can_driver = { + .driver = { + .name = DEVICE_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = mcp251x_can_probe, + .remove = __devexit_p(mcp251x_can_remove), + .suspend = mcp251x_can_suspend, + .resume = mcp251x_can_resume, +}; + +static int __init mcp251x_can_init(void) +{ + return spi_register_driver(&mcp251x_can_driver); +} + +static void __exit mcp251x_can_exit(void) +{ + spi_unregister_driver(&mcp251x_can_driver); +} + +module_init(mcp251x_can_init); +module_exit(mcp251x_can_exit); + +MODULE_AUTHOR("Chris Elston <celston@katalix.com>, " + "Christian Pellegrin <chripell@evolware.org>"); +MODULE_DESCRIPTION("Microchip 251x CAN driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/mscan/Makefile b/drivers/net/can/mscan/Makefile new file mode 100644 index 000000000000..9f61b13db90e --- /dev/null +++ b/drivers/net/can/mscan/Makefile @@ -0,0 +1,23 @@ +# +# + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common + +obj-$(CONFIG_CAN_MPC52XX) += mscan-mpc52xx.o + +mscan-mpc52xx-objs := mscan.o mpc52xx_can.o + +ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG + +endif diff --git a/drivers/net/can/mscan/mpc52xx_can.c b/drivers/net/can/mscan/mpc52xx_can.c new file mode 100644 index 000000000000..8878a5901aa5 --- /dev/null +++ b/drivers/net/can/mscan/mpc52xx_can.c @@ -0,0 +1,293 @@ +/* + * CAN bus driver for the Freescale MPC52xx embedded CPU. + * + * Copyright (C) 2004-2005 Andrey Volkov <avolkov@varma-el.com>, + * Varma Electronics Oy + * Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/netdevice.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/of_platform.h> +#include <sysdev/fsl_soc.h> +#include <linux/io.h> +#include <asm/mpc52xx.h> + +#include "mscan.h" + + +#define DRV_NAME "mpc52xx_can" + +static struct of_device_id mpc52xx_cdm_ids[] __devinitdata = { + { .compatible = "fsl,mpc5200-cdm", }, + { .compatible = "fsl,mpc5200b-cdm", }, + {} +}; + +/* + * Get the frequency of the external oscillator clock connected + * to the SYS_XTAL_IN pin, or retrun 0 if it cannot be determined. + */ +static unsigned int __devinit mpc52xx_can_xtal_freq(struct device_node *np) +{ + struct mpc52xx_cdm __iomem *cdm; + struct device_node *np_cdm; + unsigned int freq; + u32 val; + + freq = mpc52xx_find_ipb_freq(np); + if (!freq) + return 0; + + /* + * Detemine SYS_XTAL_IN frequency from the clock domain settings + */ + np_cdm = of_find_matching_node(NULL, mpc52xx_cdm_ids); + cdm = of_iomap(np_cdm, 0); + of_node_put(np_cdm); + if (!np_cdm) { + printk(KERN_ERR "%s() failed abnormally\n", __func__); + return 0; + } + + if (in_8(&cdm->ipb_clk_sel) & 0x1) + freq *= 2; + val = in_be32(&cdm->rstcfg); + if (val & (1 << 5)) + freq *= 8; + else + freq *= 4; + if (val & (1 << 6)) + freq /= 12; + else + freq /= 16; + + iounmap(cdm); + + return freq; +} + +/* + * Get frequency of the MSCAN clock source + * + * Either the oscillator clock (SYS_XTAL_IN) or the IP bus clock (IP_CLK) + * can be selected. According to the MPC5200 user's manual, the oscillator + * clock is the better choice as it has less jitter but due to a hardware + * bug, it can not be selected for the old MPC5200 Rev. A chips. + */ + +static unsigned int __devinit mpc52xx_can_clock_freq(struct device_node *np, + int clock_src) +{ + unsigned int pvr; + + pvr = mfspr(SPRN_PVR); + + if (clock_src == MSCAN_CLKSRC_BUS || pvr == 0x80822011) + return mpc52xx_find_ipb_freq(np); + + return mpc52xx_can_xtal_freq(np); +} + +static int __devinit mpc52xx_can_probe(struct of_device *ofdev, + const struct of_device_id *id) +{ + struct device_node *np = ofdev->node; + struct net_device *dev; + struct can_priv *priv; + struct resource res; + void __iomem *base; + int err, irq, res_size, clock_src; + + err = of_address_to_resource(np, 0, &res); + if (err) { + dev_err(&ofdev->dev, "invalid address\n"); + return err; + } + + res_size = res.end - res.start + 1; + + if (!request_mem_region(res.start, res_size, DRV_NAME)) { + dev_err(&ofdev->dev, "couldn't request %#x..%#x\n", + res.start, res.end); + return -EBUSY; + } + + base = ioremap_nocache(res.start, res_size); + if (!base) { + dev_err(&ofdev->dev, "couldn't ioremap %#x..%#x\n", + res.start, res.end); + err = -ENOMEM; + goto exit_release_mem; + } + + irq = irq_of_parse_and_map(np, 0); + if (irq == NO_IRQ) { + dev_err(&ofdev->dev, "no irq found\n"); + err = -ENODEV; + goto exit_unmap_mem; + } + + dev = alloc_mscandev(); + if (!dev) { + err = -ENOMEM; + goto exit_dispose_irq; + } + + dev->base_addr = (unsigned long)base; + dev->irq = irq; + + priv = netdev_priv(dev); + + /* + * Either the oscillator clock (SYS_XTAL_IN) or the IP bus clock + * (IP_CLK) can be selected as MSCAN clock source. According to + * the MPC5200 user's manual, the oscillator clock is the better + * choice as it has less jitter. For this reason, it is selected + * by default. + */ + if (of_get_property(np, "clock-ipb", NULL)) + clock_src = MSCAN_CLKSRC_BUS; + else + clock_src = MSCAN_CLKSRC_XTAL; + priv->bittiming.clock = mpc52xx_can_clock_freq(np, clock_src); + if (!priv->bittiming.clock) { + dev_err(&ofdev->dev, "couldn't get MSCAN clock frequency\n"); + err = -ENODEV; + goto exit_free_mscan; + } + + SET_NETDEV_DEV(dev, &ofdev->dev); + + err = register_mscandev(dev, clock_src); + if (err) { + dev_err(&ofdev->dev, "registering %s failed (err=%d)\n", + DRV_NAME, err); + goto exit_free_mscan; + } + + dev_set_drvdata(&ofdev->dev, dev); + + dev_info(&ofdev->dev, "MSCAN at 0x%lx, irq %d, clock %d Hz\n", + dev->base_addr, dev->irq, priv->bittiming.clock); + + return 0; + +exit_free_mscan: + free_candev(dev); +exit_dispose_irq: + irq_dispose_mapping(irq); +exit_unmap_mem: + iounmap(base); +exit_release_mem: + release_mem_region(res.start, res_size); + + return err; +} + +static int __devexit mpc52xx_can_remove(struct of_device *ofdev) +{ + struct net_device *dev = dev_get_drvdata(&ofdev->dev); + struct device_node *np = ofdev->node; + struct resource res; + + dev_set_drvdata(&ofdev->dev, NULL); + + unregister_mscandev(dev); + iounmap((void __iomem *)dev->base_addr); + irq_dispose_mapping(dev->irq); + free_candev(dev); + + of_address_to_resource(np, 0, &res); + release_mem_region(res.start, res.end - res.start + 1); + + return 0; +} + +static struct mscan_regs saved_regs; +static int mpc52xx_can_suspend(struct of_device *ofdev, pm_message_t state) +{ + struct net_device *dev = dev_get_drvdata(&ofdev->dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + _memcpy_fromio(&saved_regs, regs, sizeof(*regs)); + + return 0; +} + +static int mpc52xx_can_resume(struct of_device *ofdev) +{ + struct net_device *dev = dev_get_drvdata(&ofdev->dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + regs->canctl0 |= MSCAN_INITRQ; + while ((regs->canctl1 & MSCAN_INITAK) == 0) + udelay(10); + + regs->canctl1 = saved_regs.canctl1; + regs->canbtr0 = saved_regs.canbtr0; + regs->canbtr1 = saved_regs.canbtr1; + regs->canidac = saved_regs.canidac; + + /* restore masks, buffers etc. */ + _memcpy_toio(®s->canidar1_0, (void *)&saved_regs.canidar1_0, + sizeof(*regs) - offsetof(struct mscan_regs, canidar1_0)); + + regs->canctl0 &= ~MSCAN_INITRQ; + regs->cantbsel = saved_regs.cantbsel; + regs->canrier = saved_regs.canrier; + regs->cantier = saved_regs.cantier; + regs->canctl0 = saved_regs.canctl0; + + return 0; +} + +static struct of_device_id __devinitdata mpc52xx_can_table[] = { + {.compatible = "fsl,mpc5200-mscan"}, + {.compatible = "fsl,mpc5200b-mscan"}, + {}, +}; + +static struct of_platform_driver mpc52xx_can_driver = { + .owner = THIS_MODULE, + .name = "mpc52xx_can", + .probe = mpc52xx_can_probe, + .remove = __devexit_p(mpc52xx_can_remove), + .suspend = mpc52xx_can_suspend, + .resume = mpc52xx_can_resume, + .match_table = mpc52xx_can_table, +}; + +static int __init mpc52xx_can_init(void) +{ + return of_register_platform_driver(&mpc52xx_can_driver); +} +module_init(mpc52xx_can_init); + +static void __exit mpc52xx_can_exit(void) +{ + return of_unregister_platform_driver(&mpc52xx_can_driver); +}; +module_exit(mpc52xx_can_exit); + +MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>"); +MODULE_DESCRIPTION("Freescale MPC5200 CAN driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/mscan/mscan.c b/drivers/net/can/mscan/mscan.c new file mode 100644 index 000000000000..fe42b9e7dfb7 --- /dev/null +++ b/drivers/net/can/mscan/mscan.c @@ -0,0 +1,679 @@ +/* + * CAN bus driver for the alone generic (as possible as) MSCAN controller. + * + * Copyright (C) 2005-2006 Andrey Volkov <avolkov@varma-el.com>, + * Varma Electronics Oy + * Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/list.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include <linux/io.h> + +#include "mscan.h" + +#define MSCAN_NORMAL_MODE 0 +#define MSCAN_SLEEP_MODE MSCAN_SLPRQ +#define MSCAN_INIT_MODE (MSCAN_INITRQ | MSCAN_SLPRQ) +#define MSCAN_POWEROFF_MODE (MSCAN_CSWAI | MSCAN_SLPRQ) +#define MSCAN_SET_MODE_RETRIES 255 + +#define BTR0_BRP_MASK 0x3f +#define BTR0_SJW_SHIFT 6 +#define BTR0_SJW_MASK (0x3 << BTR0_SJW_SHIFT) + +#define BTR1_TSEG1_MASK 0xf +#define BTR1_TSEG2_SHIFT 4 +#define BTR1_TSEG2_MASK (0x7 << BTR1_TSEG2_SHIFT) +#define BTR1_SAM_SHIFT 7 + +#define BTR0_SET_BRP(brp) (((brp) - 1) & BTR0_BRP_MASK) +#define BTR0_SET_SJW(sjw) ((((sjw) - 1) << BTR0_SJW_SHIFT) & \ + BTR0_SJW_MASK) + +#define BTR1_SET_TSEG1(tseg1) (((tseg1) - 1) & BTR1_TSEG1_MASK) +#define BTR1_SET_TSEG2(tseg2) ((((tseg2) - 1) << BTR1_TSEG2_SHIFT) & \ + BTR1_TSEG2_MASK) +#define BTR1_SET_SAM(sam) (((sam) & 1) << BTR1_SAM_SHIFT) + +static struct can_bittiming_const mscan_bittiming_const = { + .tseg1_min = 4, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 64, + .brp_inc = 1, +}; + +struct mscan_state { + u8 mode; + u8 canrier; + u8 cantier; +}; + +#define TX_QUEUE_SIZE 3 + +struct tx_queue_entry { + struct list_head list; + u8 mask; + u8 id; +}; + +struct mscan_priv { + struct can_priv can; + long open_time; + unsigned long flags; + u8 shadow_statflg; + u8 shadow_canrier; + u8 cur_pri; + u8 tx_active; + + struct list_head tx_head; + struct tx_queue_entry tx_queue[TX_QUEUE_SIZE]; + struct napi_struct napi; + struct net_device *dev; +}; + +#define F_RX_PROGRESS 0 +#define F_TX_PROGRESS 1 +#define F_TX_WAIT_ALL 2 + +static enum can_state state_map[] = { + CAN_STATE_ACTIVE, + CAN_STATE_BUS_WARNING, + CAN_STATE_BUS_PASSIVE, + CAN_STATE_BUS_OFF +}; + +static int mscan_set_mode(struct net_device *dev, u8 mode) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct mscan_priv *priv = netdev_priv(dev); + int ret = 0; + int i; + u8 canctl1; + + if (mode != MSCAN_NORMAL_MODE) { + + if (priv->tx_active) { + /* Abort transfers before going to sleep */ + out_8(®s->cantier, 0); + out_8(®s->cantarq, priv->tx_active); + out_8(®s->cantier, priv->tx_active); + } + + canctl1 = in_8(®s->canctl1); + if ((mode & MSCAN_SLPRQ) && (canctl1 & MSCAN_SLPAK) == 0) { + out_8(®s->canctl0, + in_8(®s->canctl0) | MSCAN_SLPRQ); + for (i = 0; i < MSCAN_SET_MODE_RETRIES; i++) { + if (in_8(®s->canctl1) & MSCAN_SLPAK) + break; + udelay(100); + } + if (i >= MSCAN_SET_MODE_RETRIES) + ret = -ENODEV; + } + if (!ret) + priv->can.state = CAN_STATE_SLEEPING; + + if (!ret && (mode & MSCAN_INITRQ) + && (canctl1 & MSCAN_INITAK) == 0) { + out_8(®s->canctl0, + in_8(®s->canctl0) | MSCAN_INITRQ); + for (i = 0; i < MSCAN_SET_MODE_RETRIES; i++) { + if (in_8(®s->canctl1) & MSCAN_INITAK) + break; + } + if (i >= MSCAN_SET_MODE_RETRIES) + ret = -ENODEV; + } + if (!ret) + priv->can.state = CAN_STATE_STOPPED; + + if (!ret && (mode & MSCAN_CSWAI)) + out_8(®s->canctl0, + in_8(®s->canctl0) | MSCAN_CSWAI); + + } else { + canctl1 = in_8(®s->canctl1); + if (canctl1 & (MSCAN_SLPAK | MSCAN_INITAK)) { + out_8(®s->canctl0, in_8(®s->canctl0) & + ~(MSCAN_SLPRQ | MSCAN_INITRQ)); + for (i = 0; i < MSCAN_SET_MODE_RETRIES; i++) { + canctl1 = in_8(®s->canctl1); + if (!(canctl1 & (MSCAN_INITAK | MSCAN_SLPAK))) + break; + } + if (i >= MSCAN_SET_MODE_RETRIES) + ret = -ENODEV; + else + priv->can.state = CAN_STATE_ACTIVE; + } + } + return ret; +} + +static int mscan_start(struct net_device *dev) +{ + struct mscan_priv *priv = netdev_priv(dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + u8 canrflg; + int err; + + out_8(®s->canrier, 0); + + INIT_LIST_HEAD(&priv->tx_head); + priv->cur_pri = 0; + priv->tx_active = 0; + priv->shadow_canrier = 0; + priv->flags = 0; + + err = mscan_set_mode(dev, MSCAN_NORMAL_MODE); + if (err) + return err; + + canrflg = in_8(®s->canrflg); + priv->shadow_statflg = canrflg & MSCAN_STAT_MSK; + priv->can.state = state_map[max(MSCAN_STATE_RX(canrflg), + MSCAN_STATE_TX(canrflg))]; + out_8(®s->cantier, 0); + + /* Enable receive interrupts. */ + out_8(®s->canrier, MSCAN_OVRIE | MSCAN_RXFIE | MSCAN_CSCIE | + MSCAN_RSTATE1 | MSCAN_RSTATE0 | MSCAN_TSTATE1 | MSCAN_TSTATE0); + + return 0; +} + +static int mscan_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct can_frame *frame = (struct can_frame *)skb->data; + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct mscan_priv *priv = netdev_priv(dev); + int i, rtr, buf_id; + u32 can_id; + + if (frame->can_dlc > 8) + return -EINVAL; + + out_8(®s->cantier, 0); + + i = ~priv->tx_active & MSCAN_TXE; + buf_id = ffs(i) - 1; + switch (hweight8(i)) { + case 0: + netif_stop_queue(dev); + dev_err(ND2D(dev), "BUG! Tx Ring full when queue awake!\n"); + return NETDEV_TX_BUSY; + case 1: + /* if buf_id < 3, then current frame will be send out of order, + since buffer with lower id have higher priority (hell..) */ + if (buf_id < 3) + priv->cur_pri++; + if (priv->cur_pri == 0xff) + set_bit(F_TX_WAIT_ALL, &priv->flags); + netif_stop_queue(dev); + case 2: + set_bit(F_TX_PROGRESS, &priv->flags); + } + out_8(®s->cantbsel, i); + + rtr = frame->can_id & CAN_RTR_FLAG; + + if (frame->can_id & CAN_EFF_FLAG) { + can_id = (frame->can_id & CAN_EFF_MASK) << 1; + if (rtr) + can_id |= 1; + out_be16(®s->tx.idr3_2, can_id); + + can_id >>= 16; + can_id = (can_id & 0x7) | ((can_id << 2) & 0xffe0) | (3 << 3); + } else { + can_id = (frame->can_id & CAN_SFF_MASK) << 5; + if (rtr) + can_id |= 1 << 4; + } + out_be16(®s->tx.idr1_0, can_id); + + if (!rtr) { + void __iomem *data = ®s->tx.dsr1_0; + u16 *payload = (u16 *) frame->data; + /*Its safe to write into dsr[dlc+1] */ + for (i = 0; i < (frame->can_dlc + 1) / 2; i++) { + out_be16(data, *payload++); + data += 2 + _MSCAN_RESERVED_DSR_SIZE; + } + } + + out_8(®s->tx.dlr, frame->can_dlc); + out_8(®s->tx.tbpr, priv->cur_pri); + + /* Start transmission. */ + out_8(®s->cantflg, 1 << buf_id); + + if (!test_bit(F_TX_PROGRESS, &priv->flags)) + dev->trans_start = jiffies; + + list_add_tail(&priv->tx_queue[buf_id].list, &priv->tx_head); + + can_put_echo_skb(skb, dev, buf_id); + + /* Enable interrupt. */ + priv->tx_active |= 1 << buf_id; + out_8(®s->cantier, priv->tx_active); + + return NETDEV_TX_OK; +} + +static inline int check_set_state(struct net_device *dev, u8 canrflg) +{ + struct mscan_priv *priv = netdev_priv(dev); + enum can_state state; + int ret = 0; + + if (!(canrflg & MSCAN_CSCIF) || priv->can.state > CAN_STATE_BUS_OFF) + return 0; + + state = state_map[max(MSCAN_STATE_RX(canrflg), + MSCAN_STATE_TX(canrflg))]; + if (priv->can.state < state) + ret = 1; + if (state == CAN_STATE_BUS_OFF) + can_bus_off(dev); + priv->can.state = state; + return ret; +} + +static int mscan_rx_poll(struct napi_struct *napi, int quota) +{ + struct mscan_priv *priv = container_of(napi, struct mscan_priv, napi); + struct net_device *dev = priv->dev; + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct net_device_stats *stats = &dev->stats; + int npackets = 0; + int ret = 1; + struct sk_buff *skb; + struct can_frame *frame; + u32 can_id; + u8 canrflg; + int i; + + while (npackets < quota && ((canrflg = in_8(®s->canrflg)) & + (MSCAN_RXF | MSCAN_ERR_IF))) { + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) { + if (printk_ratelimit()) + dev_notice(ND2D(dev), "packet dropped\n"); + stats->rx_dropped++; + out_8(®s->canrflg, canrflg); + continue; + } + + frame = (struct can_frame *)skb_put(skb, sizeof(*frame)); + memset(frame, 0, sizeof(*frame)); + + if (canrflg & MSCAN_RXF) { + can_id = in_be16(®s->rx.idr1_0); + if (can_id & (1 << 3)) { + frame->can_id = CAN_EFF_FLAG; + can_id = ((can_id << 16) | + in_be16(®s->rx.idr3_2)); + can_id = ((can_id & 0xffe00000) | + ((can_id & 0x7ffff) << 2)) >> 2; + } else { + can_id >>= 4; + frame->can_id = 0; + } + + frame->can_id |= can_id >> 1; + if (can_id & 1) + frame->can_id |= CAN_RTR_FLAG; + frame->can_dlc = in_8(®s->rx.dlr) & 0xf; + + if (!(frame->can_id & CAN_RTR_FLAG)) { + void __iomem *data = ®s->rx.dsr1_0; + u16 *payload = (u16 *) frame->data; + for (i = 0; i < (frame->can_dlc + 1) / 2; i++) { + *payload++ = in_be16(data); + data += 2 + _MSCAN_RESERVED_DSR_SIZE; + } + } + + out_8(®s->canrflg, MSCAN_RXF); + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += frame->can_dlc; + } else if (canrflg & MSCAN_ERR_IF) { + dev_dbg(ND2D(dev), "error interrupt (canrflg=%#x)\n", + canrflg); + frame->can_id = CAN_ERR_FLAG; + + if (canrflg & MSCAN_OVRIF) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW; + stats->rx_over_errors++; + } else + frame->data[1] = 0; + + if (check_set_state(dev, canrflg)) { + frame->can_id |= CAN_ERR_CRTL; + switch (priv->can.state) { + case CAN_STATE_BUS_WARNING: + if ((priv->shadow_statflg & + MSCAN_RSTAT_MSK) < + (canrflg & MSCAN_RSTAT_MSK)) + frame->data[1] |= + CAN_ERR_CRTL_RX_WARNING; + + if ((priv->shadow_statflg & + MSCAN_TSTAT_MSK) < + (canrflg & MSCAN_TSTAT_MSK)) + frame->data[1] |= + CAN_ERR_CRTL_TX_WARNING; + break; + case CAN_STATE_BUS_PASSIVE: + frame->data[1] |= + CAN_ERR_CRTL_RX_PASSIVE; + break; + case CAN_STATE_BUS_OFF: + frame->can_id |= CAN_ERR_BUSOFF; + frame->can_id &= ~CAN_ERR_CRTL; + break; + default: + break; + } + } + priv->shadow_statflg = canrflg & MSCAN_STAT_MSK; + frame->can_dlc = CAN_ERR_DLC; + out_8(®s->canrflg, MSCAN_ERR_IF); + } + + npackets++; + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_receive_skb(skb); + } + + if (!(in_8(®s->canrflg) & (MSCAN_RXF | MSCAN_ERR_IF))) { + netif_rx_complete(dev, &priv->napi); + clear_bit(F_RX_PROGRESS, &priv->flags); + if (priv->can.state < CAN_STATE_BUS_OFF) + out_8(®s->canrier, priv->shadow_canrier); + ret = 0; + } + return ret; +} + +static irqreturn_t mscan_isr(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct mscan_priv *priv = netdev_priv(dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct net_device_stats *stats = &dev->stats; + u8 cantier, cantflg, canrflg; + irqreturn_t ret = IRQ_NONE; + + cantier = in_8(®s->cantier) & MSCAN_TXE; + cantflg = in_8(®s->cantflg) & cantier; + + if (cantier && cantflg) { + + struct list_head *tmp, *pos; + + list_for_each_safe(pos, tmp, &priv->tx_head) { + struct tx_queue_entry *entry = + list_entry(pos, struct tx_queue_entry, list); + u8 mask = entry->mask; + + if (!(cantflg & mask)) + continue; + + out_8(®s->cantbsel, mask); + stats->tx_bytes += in_8(®s->tx.dlr); + stats->tx_packets++; + can_get_echo_skb(dev, entry->id); + priv->tx_active &= ~mask; + list_del(pos); + } + + if (list_empty(&priv->tx_head)) { + clear_bit(F_TX_WAIT_ALL, &priv->flags); + clear_bit(F_TX_PROGRESS, &priv->flags); + priv->cur_pri = 0; + } else + dev->trans_start = jiffies; + + if (!test_bit(F_TX_WAIT_ALL, &priv->flags)) + netif_wake_queue(dev); + + out_8(®s->cantier, priv->tx_active); + ret = IRQ_HANDLED; + } + + canrflg = in_8(®s->canrflg); + if ((canrflg & ~MSCAN_STAT_MSK) && + !test_and_set_bit(F_RX_PROGRESS, &priv->flags)) { + if (canrflg & ~MSCAN_STAT_MSK) { + priv->shadow_canrier = in_8(®s->canrier); + out_8(®s->canrier, 0); + netif_rx_schedule(dev, &priv->napi); + ret = IRQ_HANDLED; + } else + clear_bit(F_RX_PROGRESS, &priv->flags); + } + return ret; +} + +static int mscan_do_set_mode(struct net_device *dev, enum can_mode mode) +{ + + struct mscan_priv *priv = netdev_priv(dev); + int ret = 0; + + if (!priv->open_time) + return -EINVAL; + + switch (mode) { + case CAN_MODE_SLEEP: + case CAN_MODE_STOP: + netif_stop_queue(dev); + mscan_set_mode(dev, + (mode == + CAN_MODE_STOP) ? MSCAN_INIT_MODE : + MSCAN_SLEEP_MODE); + break; + case CAN_MODE_START: + if (priv->can.state <= CAN_STATE_BUS_OFF) + mscan_set_mode(dev, MSCAN_INIT_MODE); + ret = mscan_start(dev); + if (ret) + break; + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + break; + + default: + ret = -EOPNOTSUPP; + break; + } + return ret; +} + +static int mscan_do_set_bittiming(struct net_device *dev) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct mscan_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->can.bittiming; + u8 btr0, btr1; + + btr0 = BTR0_SET_BRP(bt->brp) | BTR0_SET_SJW(bt->sjw); + btr1 = (BTR1_SET_TSEG1(bt->prop_seg + bt->phase_seg1) | + BTR1_SET_TSEG2(bt->phase_seg2) | + BTR1_SET_SAM(priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)); + + dev_info(ND2D(dev), "setting BTR0=0x%02x BTR1=0x%02x\n", btr0, btr1); + + out_8(®s->canbtr0, btr0); + out_8(®s->canbtr1, btr1); + + return 0; +} + +static int mscan_open(struct net_device *dev) +{ + int ret; + struct mscan_priv *priv = netdev_priv(dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + /* determine and set bittime */ + ret = can_set_bittiming(dev); + if (ret) + return ret; + + napi_enable(&priv->napi); + ret = request_irq(dev->irq, mscan_isr, 0, dev->name, dev); + + if (ret < 0) { + napi_disable(&priv->napi); + printk(KERN_ERR "%s - failed to attach interrupt\n", + dev->name); + return ret; + } + + priv->open_time = jiffies; + + out_8(®s->canctl1, in_8(®s->canctl1) & ~MSCAN_LISTEN); + + ret = mscan_start(dev); + if (ret) + return ret; + + netif_start_queue(dev); + + return 0; +} + +static int mscan_close(struct net_device *dev) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct mscan_priv *priv = netdev_priv(dev); + + napi_disable(&priv->napi); + + out_8(®s->cantier, 0); + out_8(®s->canrier, 0); + free_irq(dev->irq, dev); + mscan_set_mode(dev, MSCAN_INIT_MODE); + can_close_cleanup(dev); + netif_stop_queue(dev); + priv->open_time = 0; + + return 0; +} + +int register_mscandev(struct net_device *dev, int clock_src) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + u8 ctl1; + + ctl1 = in_8(®s->canctl1); + if (clock_src) + ctl1 |= MSCAN_CLKSRC; + else + ctl1 &= ~MSCAN_CLKSRC; + + ctl1 |= MSCAN_CANE; + out_8(®s->canctl1, ctl1); + udelay(100); + + /* acceptance mask/acceptance code (accept everything) */ + out_be16(®s->canidar1_0, 0); + out_be16(®s->canidar3_2, 0); + out_be16(®s->canidar5_4, 0); + out_be16(®s->canidar7_6, 0); + + out_be16(®s->canidmr1_0, 0xffff); + out_be16(®s->canidmr3_2, 0xffff); + out_be16(®s->canidmr5_4, 0xffff); + out_be16(®s->canidmr7_6, 0xffff); + /* Two 32 bit Acceptance Filters */ + out_8(®s->canidac, MSCAN_AF_32BIT); + + mscan_set_mode(dev, MSCAN_INIT_MODE); + + return register_candev(dev); +} +EXPORT_SYMBOL_GPL(register_mscandev); + +void unregister_mscandev(struct net_device *dev) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + mscan_set_mode(dev, MSCAN_INIT_MODE); + out_8(®s->canctl1, in_8(®s->canctl1) & ~MSCAN_CANE); + unregister_candev(dev); +} +EXPORT_SYMBOL_GPL(unregister_mscandev); + +struct net_device *alloc_mscandev(void) +{ + struct net_device *dev; + struct mscan_priv *priv; + int i; + + dev = alloc_candev(sizeof(struct mscan_priv)); + if (!dev) + return NULL; + priv = netdev_priv(dev); + + dev->open = mscan_open; + dev->stop = mscan_close; + dev->hard_start_xmit = mscan_start_xmit; + + dev->flags |= IFF_ECHO; /* we support local echo */ + + priv->dev = dev; + netif_napi_add(dev, &priv->napi, mscan_rx_poll, 8); + + priv->can.bittiming_const = &mscan_bittiming_const; + priv->can.do_set_bittiming = mscan_do_set_bittiming; + priv->can.do_set_mode = mscan_do_set_mode; + + for (i = 0; i < TX_QUEUE_SIZE; i++) { + priv->tx_queue[i].id = i; + priv->tx_queue[i].mask = 1 << i; + } + + return dev; +} +EXPORT_SYMBOL_GPL(alloc_mscandev); + +MODULE_AUTHOR("Andrey Volkov <avolkov@varma-el.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CAN port driver for a MSCAN based chips"); diff --git a/drivers/net/can/mscan/mscan.h b/drivers/net/can/mscan/mscan.h new file mode 100644 index 000000000000..81a2eb5a19e5 --- /dev/null +++ b/drivers/net/can/mscan/mscan.h @@ -0,0 +1,237 @@ +/* + * Definitions of consts/structs to drive the Freescale MSCAN. + * + * Copyright (C) 2005-2006 Andrey Volkov <avolkov@varma-el.com>, + * Varma Electronics Oy + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __MSCAN_H__ +#define __MSCAN_H__ + +#include <linux/types.h> + +/* MSCAN control register 0 (CANCTL0) bits */ +#define MSCAN_RXFRM 0x80 +#define MSCAN_RXACT 0x40 +#define MSCAN_CSWAI 0x20 +#define MSCAN_SYNCH 0x10 +#define MSCAN_TIME 0x08 +#define MSCAN_WUPE 0x04 +#define MSCAN_SLPRQ 0x02 +#define MSCAN_INITRQ 0x01 + +/* MSCAN control register 1 (CANCTL1) bits */ +#define MSCAN_CANE 0x80 +#define MSCAN_CLKSRC 0x40 +#define MSCAN_LOOPB 0x20 +#define MSCAN_LISTEN 0x10 +#define MSCAN_WUPM 0x04 +#define MSCAN_SLPAK 0x02 +#define MSCAN_INITAK 0x01 + +/* Use the MPC5200 MSCAN variant? */ +#ifdef CONFIG_PPC +#define MSCAN_FOR_MPC5200 +#endif + +#ifdef MSCAN_FOR_MPC5200 +#define MSCAN_CLKSRC_BUS 0 +#define MSCAN_CLKSRC_XTAL MSCAN_CLKSRC +#else +#define MSCAN_CLKSRC_BUS MSCAN_CLKSRC +#define MSCAN_CLKSRC_XTAL 0 +#endif + +/* MSCAN receiver flag register (CANRFLG) bits */ +#define MSCAN_WUPIF 0x80 +#define MSCAN_CSCIF 0x40 +#define MSCAN_RSTAT1 0x20 +#define MSCAN_RSTAT0 0x10 +#define MSCAN_TSTAT1 0x08 +#define MSCAN_TSTAT0 0x04 +#define MSCAN_OVRIF 0x02 +#define MSCAN_RXF 0x01 +#define MSCAN_ERR_IF (MSCAN_OVRIF | MSCAN_CSCIF) +#define MSCAN_RSTAT_MSK (MSCAN_RSTAT1 | MSCAN_RSTAT0) +#define MSCAN_TSTAT_MSK (MSCAN_TSTAT1 | MSCAN_TSTAT0) +#define MSCAN_STAT_MSK (MSCAN_RSTAT_MSK | MSCAN_TSTAT_MSK) + +#define MSCAN_STATE_BUS_OFF (MSCAN_RSTAT1 | MSCAN_RSTAT0 | \ + MSCAN_TSTAT1 | MSCAN_TSTAT0) +#define MSCAN_STATE_TX(canrflg) (((canrflg)&MSCAN_TSTAT_MSK)>>2) +#define MSCAN_STATE_RX(canrflg) (((canrflg)&MSCAN_RSTAT_MSK)>>4) +#define MSCAN_STATE_ACTIVE 0 +#define MSCAN_STATE_WARNING 1 +#define MSCAN_STATE_PASSIVE 2 +#define MSCAN_STATE_BUSOFF 3 + +/* MSCAN receiver interrupt enable register (CANRIER) bits */ +#define MSCAN_WUPIE 0x80 +#define MSCAN_CSCIE 0x40 +#define MSCAN_RSTATE1 0x20 +#define MSCAN_RSTATE0 0x10 +#define MSCAN_TSTATE1 0x08 +#define MSCAN_TSTATE0 0x04 +#define MSCAN_OVRIE 0x02 +#define MSCAN_RXFIE 0x01 + +/* MSCAN transmitter flag register (CANTFLG) bits */ +#define MSCAN_TXE2 0x04 +#define MSCAN_TXE1 0x02 +#define MSCAN_TXE0 0x01 +#define MSCAN_TXE (MSCAN_TXE2 | MSCAN_TXE1 | MSCAN_TXE0) + +/* MSCAN transmitter interrupt enable register (CANTIER) bits */ +#define MSCAN_TXIE2 0x04 +#define MSCAN_TXIE1 0x02 +#define MSCAN_TXIE0 0x01 +#define MSCAN_TXIE (MSCAN_TXIE2 | MSCAN_TXIE1 | MSCAN_TXIE0) + +/* MSCAN transmitter message abort request (CANTARQ) bits */ +#define MSCAN_ABTRQ2 0x04 +#define MSCAN_ABTRQ1 0x02 +#define MSCAN_ABTRQ0 0x01 + +/* MSCAN transmitter message abort ack (CANTAAK) bits */ +#define MSCAN_ABTAK2 0x04 +#define MSCAN_ABTAK1 0x02 +#define MSCAN_ABTAK0 0x01 + +/* MSCAN transmit buffer selection (CANTBSEL) bits */ +#define MSCAN_TX2 0x04 +#define MSCAN_TX1 0x02 +#define MSCAN_TX0 0x01 + +/* MSCAN ID acceptance control register (CANIDAC) bits */ +#define MSCAN_IDAM1 0x20 +#define MSCAN_IDAM0 0x10 +#define MSCAN_IDHIT2 0x04 +#define MSCAN_IDHIT1 0x02 +#define MSCAN_IDHIT0 0x01 + +#define MSCAN_AF_32BIT 0x00 +#define MSCAN_AF_16BIT MSCAN_IDAM0 +#define MSCAN_AF_8BIT MSCAN_IDAM1 +#define MSCAN_AF_CLOSED (MSCAN_IDAM0|MSCAN_IDAM1) +#define MSCAN_AF_MASK (~(MSCAN_IDAM0|MSCAN_IDAM1)) + +/* MSCAN Miscellaneous Register (CANMISC) bits */ +#define MSCAN_BOHOLD 0x01 + +#ifdef MSCAN_FOR_MPC5200 +#define _MSCAN_RESERVED_(n, num) u8 _res##n[num] +#define _MSCAN_RESERVED_DSR_SIZE 2 +#else +#define _MSCAN_RESERVED_(n, num) +#define _MSCAN_RESERVED_DSR_SIZE 0 +#endif + +/* Structure of the hardware registers */ +struct mscan_regs { + /* (see doco S12MSCANV3/D) MPC5200 MSCAN */ + u8 canctl0; /* + 0x00 0x00 */ + u8 canctl1; /* + 0x01 0x01 */ + _MSCAN_RESERVED_(1, 2); /* + 0x02 */ + u8 canbtr0; /* + 0x04 0x02 */ + u8 canbtr1; /* + 0x05 0x03 */ + _MSCAN_RESERVED_(2, 2); /* + 0x06 */ + u8 canrflg; /* + 0x08 0x04 */ + u8 canrier; /* + 0x09 0x05 */ + _MSCAN_RESERVED_(3, 2); /* + 0x0a */ + u8 cantflg; /* + 0x0c 0x06 */ + u8 cantier; /* + 0x0d 0x07 */ + _MSCAN_RESERVED_(4, 2); /* + 0x0e */ + u8 cantarq; /* + 0x10 0x08 */ + u8 cantaak; /* + 0x11 0x09 */ + _MSCAN_RESERVED_(5, 2); /* + 0x12 */ + u8 cantbsel; /* + 0x14 0x0a */ + u8 canidac; /* + 0x15 0x0b */ + u8 reserved; /* + 0x16 0x0c */ + _MSCAN_RESERVED_(6, 5); /* + 0x17 */ +#ifndef MSCAN_FOR_MPC5200 + u8 canmisc; /* 0x0d */ +#endif + u8 canrxerr; /* + 0x1c 0x0e */ + u8 cantxerr; /* + 0x1d 0x0f */ + _MSCAN_RESERVED_(7, 2); /* + 0x1e */ + u16 canidar1_0; /* + 0x20 0x10 */ + _MSCAN_RESERVED_(8, 2); /* + 0x22 */ + u16 canidar3_2; /* + 0x24 0x12 */ + _MSCAN_RESERVED_(9, 2); /* + 0x26 */ + u16 canidmr1_0; /* + 0x28 0x14 */ + _MSCAN_RESERVED_(10, 2); /* + 0x2a */ + u16 canidmr3_2; /* + 0x2c 0x16 */ + _MSCAN_RESERVED_(11, 2); /* + 0x2e */ + u16 canidar5_4; /* + 0x30 0x18 */ + _MSCAN_RESERVED_(12, 2); /* + 0x32 */ + u16 canidar7_6; /* + 0x34 0x1a */ + _MSCAN_RESERVED_(13, 2); /* + 0x36 */ + u16 canidmr5_4; /* + 0x38 0x1c */ + _MSCAN_RESERVED_(14, 2); /* + 0x3a */ + u16 canidmr7_6; /* + 0x3c 0x1e */ + _MSCAN_RESERVED_(15, 2); /* + 0x3e */ + struct { + u16 idr1_0; /* + 0x40 0x20 */ + _MSCAN_RESERVED_(16, 2); /* + 0x42 */ + u16 idr3_2; /* + 0x44 0x22 */ + _MSCAN_RESERVED_(17, 2); /* + 0x46 */ + u16 dsr1_0; /* + 0x48 0x24 */ + _MSCAN_RESERVED_(18, 2); /* + 0x4a */ + u16 dsr3_2; /* + 0x4c 0x26 */ + _MSCAN_RESERVED_(19, 2); /* + 0x4e */ + u16 dsr5_4; /* + 0x50 0x28 */ + _MSCAN_RESERVED_(20, 2); /* + 0x52 */ + u16 dsr7_6; /* + 0x54 0x2a */ + _MSCAN_RESERVED_(21, 2); /* + 0x56 */ + u8 dlr; /* + 0x58 0x2c */ + u8:8; /* + 0x59 0x2d */ + _MSCAN_RESERVED_(22, 2); /* + 0x5a */ + u16 time; /* + 0x5c 0x2e */ + } rx; + _MSCAN_RESERVED_(23, 2); /* + 0x5e */ + struct { + u16 idr1_0; /* + 0x60 0x30 */ + _MSCAN_RESERVED_(24, 2); /* + 0x62 */ + u16 idr3_2; /* + 0x64 0x32 */ + _MSCAN_RESERVED_(25, 2); /* + 0x66 */ + u16 dsr1_0; /* + 0x68 0x34 */ + _MSCAN_RESERVED_(26, 2); /* + 0x6a */ + u16 dsr3_2; /* + 0x6c 0x36 */ + _MSCAN_RESERVED_(27, 2); /* + 0x6e */ + u16 dsr5_4; /* + 0x70 0x38 */ + _MSCAN_RESERVED_(28, 2); /* + 0x72 */ + u16 dsr7_6; /* + 0x74 0x3a */ + _MSCAN_RESERVED_(29, 2); /* + 0x76 */ + u8 dlr; /* + 0x78 0x3c */ + u8 tbpr; /* + 0x79 0x3d */ + _MSCAN_RESERVED_(30, 2); /* + 0x7a */ + u16 time; /* + 0x7c 0x3e */ + } tx; + _MSCAN_RESERVED_(31, 2); /* + 0x7e */ +} __attribute__ ((packed)); + +#undef _MSCAN_RESERVED_ +#define MSCAN_REGION sizeof(struct mscan) + +struct net_device *alloc_mscandev(void); +/* clock_src: + * 1 = The MSCAN clock source is the onchip Bus Clock. + * 0 = The MSCAN clock source is the chip Oscillator Clock. + */ +extern int register_mscandev(struct net_device *dev, int clock_src); +extern void unregister_mscandev(struct net_device *dev); + +#endif /* __MSCAN_H__ */ diff --git a/drivers/net/can/old/Kconfig b/drivers/net/can/old/Kconfig new file mode 100644 index 000000000000..4b8567a20d03 --- /dev/null +++ b/drivers/net/can/old/Kconfig @@ -0,0 +1,67 @@ + +config CAN_SJA1000_OLD + depends on CAN_OLD_DRIVERS + tristate "Philips SJA1000 (old)" + ---help--- + The SJA1000 is one of the top CAN controllers out there. As it + has a multiplexed interface it fits directly to 8051 + microcontrollers or into the PC I/O port space. The SJA1000 + is a full CAN controller, with shadow registers for RX and TX. + It can send and receive any kinds of CAN frames (SFF/EFF/RTR) + with a single (simple) filter setup. + REMARK: This is the 'old' driver originally written by Matthias + Brukner and Oliver Hartkopp which uses a non-standard hardware + abstaction layer (HAL) inspired by the OCAN driver. + +config CAN_I82527_OLD + depends on CAN_OLD_DRIVERS + tristate "Intel 82527 (old)" + ---help--- + The i82527 is a complex CAN controller that can handle RTR + frame replies on it's own. This feature (and diffent RX filters) + lead to an amount of 15 message objects (for RX & TX). Message + object 15 has (as only) a shadow register for a reliable + receiption of EFF or(!) SFF frames at high CAN traffic. + This driver can send each type of CAN frames (SFF/EFF/RTR). + Using 4 message objects it can also receive each type of CAN + frames. But due to the onchip filter matching trigger method + it is not possible to determine the received RTR CAN-ID. + The reliable message object 15 receives SFF frames by default. + This message object 15 usage maybe changed with the mo15 param. + REMARK: This is the 'old' driver originally written by Oliver + Hartkopp which uses a non-standard hardware abstaction layer (HAL) + inspired by the OCAN driver. http://ar.linux.it/software/#ocan + +config CAN_MSCAN_OLD + depends on CAN_OLD_DRIVERS && (PPC || M68K || M68KNOMMU) + tristate "Support for a Freescale MSCAN based chips (old)" + ---help--- + The Motorola Scalable Controller Area Network (MSCAN) definition + is based on the MSCAN12 definition which is the specific + implementation of the Motorola Scalable CAN concept targeted for + the Motorola MC68HC12 Microcontroller Family. + +config CAN_MPC52XX_OLD + tristate "Freescale MPC5200 onboard CAN controller (old)" + depends on CAN_MSCAN_OLD && (PPC_MPC52xx || PPC_52xx) + default LITE5200 + ---help--- + If you say yes here you get support for Freescale MPC5200 + onboard dualCAN controller. + + This driver can also be built as a module. If so, the module + will be called mpc52xx_can. + +config CAN_CCAN_OLD + depends on CAN_OLD_DRIVERS + tristate "Bosch CCAN driver (old)" + ---help--- + This is a driver for the Bosch CCAN controller found for example + on the hynix h7202 chip. + +config CAN_H7202_OLD + tristate "Hynix H7202 onboard CAN controller (old)" + depends on CAN_CCAN_OLD + ---help--- + This is a driver for the hynix h7202 can controller. + diff --git a/drivers/net/can/old/ccan/Makefile b/drivers/net/can/old/ccan/Makefile new file mode 100644 index 000000000000..c3db1800e6d7 --- /dev/null +++ b/drivers/net/can/old/ccan/Makefile @@ -0,0 +1,20 @@ +# +# + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common + +obj-$(CONFIG_CAN_CCAN) += ccan.o +obj-$(CONFIG_CAN_H7202) += h7202_can.o + +endif diff --git a/drivers/net/can/old/ccan/ccan.c b/drivers/net/can/old/ccan/ccan.c new file mode 100644 index 000000000000..6a908a14eee4 --- /dev/null +++ b/drivers/net/can/old/ccan/ccan.c @@ -0,0 +1,557 @@ +/* + * drivers/can/c_can.c + * + * Copyright (C) 2007 + * + * - Sascha Hauer, Marc Kleine-Budde, Pengutronix + * - Simon Kallweit, intefo AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef CONFIG_CAN_DEBUG_DEVICES +#define DBG(args...) printk(args) +#else +#define DBG(args...) +#endif + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/can.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/io.h> + +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include "ccan.h" + +static u32 ccan_read_reg32(struct net_device *dev, enum c_regs reg) +{ + struct ccan_priv *priv = netdev_priv(dev); + + u32 val = priv->read_reg(dev, reg); + val |= ((u32) priv->read_reg(dev, reg + 2)) << 16; + + return val; +} + +static void ccan_write_reg32(struct net_device *dev, enum c_regs reg, u32 val) +{ + struct ccan_priv *priv = netdev_priv(dev); + + priv->write_reg(dev, reg, val & 0xffff); + priv->write_reg(dev, reg + 2, val >> 16); +} + +static inline void ccan_object_get(struct net_device *dev, + int iface, int objno, int mask) +{ + struct ccan_priv *priv = netdev_priv(dev); + + priv->write_reg(dev, CAN_IF_COMM(iface), mask); + priv->write_reg(dev, CAN_IF_COMR(iface), objno + 1); + while (priv->read_reg(dev, CAN_IF_COMR(iface)) & IF_COMR_BUSY) + DBG("busy\n"); +} + +static inline void ccan_object_put(struct net_device *dev, + int iface, int objno, int mask) +{ + struct ccan_priv *priv = netdev_priv(dev); + + priv->write_reg(dev, CAN_IF_COMM(iface), IF_COMM_WR | mask); + priv->write_reg(dev, CAN_IF_COMR(iface), objno + 1); + while (priv->read_reg(dev, CAN_IF_COMR(iface)) & IF_COMR_BUSY) + DBG("busy\n"); +} + +static int ccan_write_object(struct net_device *dev, + int iface, struct can_frame *frame, int objno) +{ + struct ccan_priv *priv = netdev_priv(dev); + unsigned int val; + + if (frame->can_id & CAN_EFF_FLAG) + val = IF_ARB_MSGXTD | (frame->can_id & CAN_EFF_MASK); + else + val = ((frame->can_id & CAN_SFF_MASK) << 18); + + if (!(frame->can_id & CAN_RTR_FLAG)) + val |= IF_ARB_TRANSMIT; + + val |= IF_ARB_MSGVAL; + ccan_write_reg32(dev, CAN_IF_ARB(iface), val); + + memcpy(&val, &frame->data[0], 4); + ccan_write_reg32(dev, CAN_IF_DATAA(iface), val); + memcpy(&val, &frame->data[4], 4); + ccan_write_reg32(dev, CAN_IF_DATAB(iface), val); + priv->write_reg(dev, CAN_IF_MCONT(iface), + IF_MCONT_TXIE | IF_MCONT_TXRQST | IF_MCONT_EOB | + (frame->can_dlc & 0xf)); + + ccan_object_put(dev, 0, objno, IF_COMM_ALL); + + return 0; +} + +static int ccan_read_object(struct net_device *dev, int iface, int objno) +{ + struct ccan_priv *priv = netdev_priv(dev); + unsigned int val, ctrl, data; + struct sk_buff *skb; + struct can_frame *frame; + + skb = dev_alloc_skb(sizeof(struct can_frame)); + skb->dev = dev; + + ccan_object_get(dev, 0, objno, IF_COMM_ALL & ~IF_COMM_TXRQST); +#ifdef CCAN_DEBUG + priv->bufstat[objno]++; +#endif + frame = (struct can_frame *)skb_put(skb, sizeof(struct can_frame)); + + ctrl = priv->read_reg(dev, CAN_IF_MCONT(iface)); + + if (ctrl & IF_MCONT_MSGLST) { + priv->can.net_stats.rx_errors++; + DBG("%s: msg lost in buffer %d\n", __func__, objno); + } + + frame->can_dlc = ctrl & 0xf; + + val = ccan_read_reg32(dev, CAN_IF_ARB(iface)); + + data = ccan_read_reg32(dev, CAN_IF_DATAA(iface)); + memcpy(&frame->data[0], &data, 4); + data = ccan_read_reg32(dev, CAN_IF_DATAB(iface)); + memcpy(&frame->data[4], &data, 4); + + if (val & IF_ARB_MSGXTD) + frame->can_id = (val & CAN_EFF_MASK) | CAN_EFF_FLAG; + else + frame->can_id = (val >> 18) & CAN_SFF_MASK; + + if (val & IF_ARB_TRANSMIT) + frame->can_id |= CAN_RTR_FLAG; + + priv->write_reg(dev, CAN_IF_MCONT(iface), ctrl & + ~(IF_MCONT_MSGLST | IF_MCONT_INTPND | IF_MCONT_NEWDAT)); + + ccan_object_put(dev, 0, objno, IF_COMM_CONTROL); + + skb->protocol = __constant_htons(ETH_P_CAN); + netif_rx(skb); + + priv->can.net_stats.rx_packets++; + priv->can.net_stats.rx_bytes += frame->can_dlc; + + return 0; +} + +static int ccan_setup_receive_object(struct net_device *dev, int iface, + int objno, unsigned int mask, + unsigned int id, unsigned int mcont) +{ + struct ccan_priv *priv = netdev_priv(dev); + + ccan_write_reg32(dev, CAN_IF_MASK(iface), mask); + ccan_write_reg32(dev, CAN_IF_ARB(iface), IF_ARB_MSGVAL | id); + + priv->write_reg(dev, CAN_IF_MCONT(iface), mcont); + + ccan_object_put(dev, 0, objno, IF_COMM_ALL & ~IF_COMM_TXRQST); + + DBG("%s: obj no %d msgval: 0x%08x\n", __func__, + objno, ccan_read_reg32(dev, CAN_MSGVAL)); + + return 0; +} + +static int ccan_inval_object(struct net_device *dev, int iface, int objno) +{ + struct ccan_priv *priv = netdev_priv(dev); + + ccan_write_reg32(dev, CAN_IF_ARB(iface), 0); + priv->write_reg(dev, CAN_IF_MCONT(iface), 0); + ccan_object_put(dev, 0, objno, IF_COMM_ARB | IF_COMM_CONTROL); + + DBG("%s: obj no %d msgval: 0x%08x\n", __func__, + objno, ccan_read_reg32(dev, CAN_MSGVAL)); + + return 0; +} + +static int ccan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + struct can_frame *frame = (struct can_frame *)skb->data; + + spin_lock_irq(&priv->can.irq_lock); + + ccan_write_object(dev, 0, frame, priv->tx_object); +#ifdef CCAN_DEBUG + priv->bufstat[priv->tx_object]++; +#endif + priv->tx_object++; + if (priv->tx_object > 5) + netif_stop_queue(dev); + + spin_unlock_irq(&priv->can.irq_lock); + + priv->can.net_stats.tx_packets++; + priv->can.net_stats.tx_bytes += frame->can_dlc; + + dev->trans_start = jiffies; + dev_kfree_skb(skb); + + return 0; +} + +static void ccan_tx_timeout(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + + priv->can.net_stats.tx_errors++; +} + +static int ccan_set_bittime(struct net_device *dev, struct can_bittime *br) +{ + struct ccan_priv *priv = netdev_priv(dev); + unsigned int reg_timing, ctrl_save; + u8 brp, sjw, tseg1, tseg2; + + if (br->type != CAN_BITTIME_STD) + return -EINVAL; + + brp = br->std.brp - 1; + sjw = br->std.sjw - 1; + tseg1 = br->std.prop_seg + br->std.phase_seg1 - 1; + tseg2 = br->std.phase_seg2 - 1; + + reg_timing = (brp & BTR_BRP_MASK) | + ((sjw << BTR_SJW_SHIFT) & BTR_SJW_MASK) | + ((tseg1 << BTR_TSEG1_SHIFT) & BTR_TSEG1_MASK) | + ((tseg2 << BTR_TSEG2_SHIFT) & BTR_TSEG2_MASK); + + DBG("%s: brp = %d sjw = %d seg1 = %d seg2 = %d\n", __func__, + brp, sjw, tseg1, tseg2); + DBG("%s: setting BTR to %04x\n", __func__, reg_timing); + + spin_lock_irq(&priv->can.irq_lock); + + ctrl_save = priv->read_reg(dev, CAN_CONTROL); + priv->write_reg(dev, CAN_CONTROL, + ctrl_save | CONTROL_CCE | CONTROL_INIT); + priv->write_reg(dev, CAN_BTR, reg_timing); + priv->write_reg(dev, CAN_CONTROL, ctrl_save); + + spin_unlock_irq(&priv->can.irq_lock); + + return 0; +} + +static int ccan_set_mode(struct net_device *dev, enum can_mode mode) +{ + switch (mode) { + case CAN_MODE_START: + DBG("%s: CAN_MODE_START requested\n", __func__); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ccan_get_state(struct net_device *dev, enum can_state *state) +{ + struct ccan_priv *priv = netdev_priv(dev); + u32 reg_status; +#ifdef CCAN_DEBUG + int i; +#endif + + reg_status = priv->read_reg(dev, CAN_STATUS); + + if (reg_status & STATUS_EPASS) + *state = CAN_STATE_BUS_PASSIVE; + else if (reg_status & STATUS_EWARN) + *state = CAN_STATE_BUS_WARNING; + else if (reg_status & STATUS_BOFF) + *state = CAN_STATE_BUS_OFF; + else + *state = CAN_STATE_ACTIVE; +#ifdef CCAN_DEBUG + DBG("buffer statistic:\n"); + for (i = 0; i <= MAX_OBJECT; i++) + DBG("%d: %d\n", i, priv->bufstat[i]); +#endif + return 0; +} + +static int ccan_do_status_irq(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + int status, diff; + + status = priv->read_reg(dev, CAN_STATUS); + status &= ~(STATUS_TXOK | STATUS_RXOK); + diff = status ^ priv->last_status; + + if (diff & STATUS_EPASS) { + if (status & STATUS_EPASS) + dev_info(ND2D(dev), "entered error passive state\n"); + else + dev_info(ND2D(dev), "left error passive state\n"); + } + if (diff & STATUS_EWARN) { + if (status & STATUS_EWARN) + dev_info(ND2D(dev), "entered error warning state\n"); + else + dev_info(ND2D(dev), "left error warning state\n"); + } + if (diff & STATUS_BOFF) { + if (status & STATUS_BOFF) + dev_info(ND2D(dev), "entered busoff state\n"); + else + dev_info(ND2D(dev), "left busoff state\n"); + } + + if (diff & STATUS_LEC_MASK) { + switch (status & STATUS_LEC_MASK) { + case LEC_STUFF_ERROR: + dev_info(ND2D(dev), "suffing error\n"); + break; + case LEC_FORM_ERROR: + dev_info(ND2D(dev), "form error\n"); + break; + case LEC_ACK_ERROR: + dev_info(ND2D(dev), "ack error\n"); + break; + case LEC_BIT1_ERROR: + dev_info(ND2D(dev), "bit1 error\n"); + break; + } + } + + priv->write_reg(dev, CAN_STATUS, 0); + priv->last_status = status; + + return diff ? 1 : 0; +} + +static void ccan_do_object_irq(struct net_device *dev, u16 irqstatus) +{ + struct ccan_priv *priv = netdev_priv(dev); + int i; + u32 val; + + if (irqstatus > MAX_TRANSMIT_OBJECT) { + val = ccan_read_reg32(dev, CAN_NEWDAT); + while (val & RECEIVE_OBJECT_BITS) { + for (i = MAX_TRANSMIT_OBJECT + 1; i <= MAX_OBJECT; i++) + if (val & (1<<i)) + ccan_read_object(dev, 0, i); + val = ccan_read_reg32(dev, CAN_NEWDAT); + } + } else { + ccan_inval_object(dev, 0, irqstatus - 1); + val = ccan_read_reg32(dev, CAN_TXRQST); + if (!val) { + priv->tx_object = 0; + netif_wake_queue(dev); + } + } +} + +static void do_statuspoll(struct work_struct *work) +{ + struct ccan_priv *priv = container_of(((struct delayed_work *) work), + struct ccan_priv, work); + + priv->write_reg(priv->dev, CAN_CONTROL, + CONTROL_SIE | CONTROL_EIE | CONTROL_IE); +} + +static irqreturn_t ccan_isr(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct ccan_priv *priv = netdev_priv(dev); + u16 irqstatus; + unsigned long flags; + + spin_lock_irqsave(&priv->can.irq_lock, flags); + + irqstatus = priv->read_reg(dev, CAN_IR); + while (irqstatus) { + if (irqstatus == 0x8000) { + if (ccan_do_status_irq(dev)) { + /* The c_can core tends to flood us with + * interrupts when certain error states don't + * disappear. Disable interrupts and see if it's + * getting better later. This is at least the + * case on the Magnachip h7202. + */ + priv->write_reg(dev, CAN_CONTROL, CONTROL_EIE | + CONTROL_IE); + schedule_delayed_work(&priv->work, HZ / 10); + goto exit; + } + } else { + ccan_do_object_irq(dev, irqstatus); + } + irqstatus = priv->read_reg(dev, CAN_IR); + } + +exit: + spin_unlock_irqrestore(&priv->can.irq_lock, flags); + + return IRQ_HANDLED; +} + +static int ccan_open(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + + if (request_irq(dev->irq, &ccan_isr, 0, dev->name, dev)) { + dev_err(ND2D(dev), "failed to attach interrupt\n"); + return -EAGAIN; + } + + priv->write_reg(dev, CAN_CONTROL, + CONTROL_EIE | CONTROL_SIE | CONTROL_IE); + + netif_wake_queue(dev); + + return 0; +} + +static int ccan_stop(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + unsigned long flags; + + netif_stop_queue(dev); + + cancel_delayed_work(&priv->work); + flush_scheduled_work(); + + /* mask all IRQs */ + spin_lock_irqsave(&priv->can.irq_lock, flags); + priv->write_reg(dev, CAN_CONTROL, 0); + spin_unlock_irqrestore(&priv->can.irq_lock, flags); + + free_irq(dev->irq, dev); + + return 0; +} + +static int ccan_chip_config(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + int i; + + /* setup message objects */ + for (i = 0; i <= MAX_OBJECT; i++) + ccan_inval_object(dev, 0, i); + + for (i = MAX_TRANSMIT_OBJECT + 1; i < MAX_OBJECT; i++) + ccan_setup_receive_object(dev, 0, i, 0, 0, + IF_MCONT_RXIE | IF_MCONT_UMASK); + + ccan_setup_receive_object(dev, 0, MAX_OBJECT, 0, 0, IF_MCONT_EOB | + IF_MCONT_RXIE | IF_MCONT_UMASK); + +#ifdef CCAN_DEBUG + for (i = 0; i <= MAX_OBJECT; i++) + priv->bufstat[i] = 0; +#endif + + return 0; +} + +struct net_device *alloc_ccandev(int sizeof_priv) +{ + struct net_device *dev; + struct ccan_priv *priv; + + dev = alloc_candev(sizeof_priv); + if (!dev) + return NULL; + + priv = netdev_priv(dev); + + dev->open = ccan_open; + dev->stop = ccan_stop; + dev->hard_start_xmit = ccan_hard_start_xmit; + dev->tx_timeout = ccan_tx_timeout; + + priv->can.bitrate = 500000; + + priv->can.do_set_bittime = ccan_set_bittime; + priv->can.do_get_state = ccan_get_state; + priv->can.do_set_mode = ccan_set_mode; + + priv->dev = dev; + priv->tx_object = 0; + + return dev; +} +EXPORT_SYMBOL(alloc_ccandev); + +void free_ccandev(struct net_device *dev) +{ + free_candev(dev); +} +EXPORT_SYMBOL(free_ccandev); + +int register_ccandev(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + + ccan_set_mode(dev, CAN_MODE_START); + + ccan_chip_config(dev); + INIT_DELAYED_WORK(&priv->work, do_statuspoll); + + return register_netdev(dev); +} +EXPORT_SYMBOL(register_ccandev); + +void unregister_ccandev(struct net_device *dev) +{ + struct ccan_priv *priv = netdev_priv(dev); + + ccan_set_mode(dev, CAN_MODE_START); + + cancel_delayed_work(&priv->work); + flush_scheduled_work(); + + unregister_netdev(dev); +} +EXPORT_SYMBOL(unregister_ccandev); + + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_AUTHOR("Simon Kallweit <simon.kallweit@intefo.ch>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CAN port driver for C_CAN based chips"); diff --git a/drivers/net/can/old/ccan/ccan.h b/drivers/net/can/old/ccan/ccan.h new file mode 100644 index 000000000000..b7a5460195e1 --- /dev/null +++ b/drivers/net/can/old/ccan/ccan.h @@ -0,0 +1,140 @@ +/* + * drivers/can/c_can.h + * + * Copyright (C) 2007 + * + * - Sascha Hauer, Marc Kleine-Budde, Pengutronix + * - Simon Kallweit, intefo AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __CCAN_H__ +#define __CCAN_H__ + +#include <linux/can.h> +#include <linux/platform_device.h> + +#undef CCAN_DEBUG + +enum c_regs { + CAN_CONTROL = 0x00, + CAN_STATUS = 0x02, + CAN_ERROR = 0x04, + CAN_BTR = 0x06, + CAN_IR = 0x08, + CAN_TEST = 0x0a, + CAN_BRP_EXT = 0x0c, + CAN_IF1 = 0x10, + CAN_IF2 = 0x40, + CAN_TXRQST = 0x80, /* 32bit */ + CAN_NEWDAT = 0x90, /* 32bit */ + CAN_INTPND = 0xa0, /* 32bit */ + CAN_MSGVAL = 0xb0, /* 32bit */ +}; + +#define CAN_IF_COMR(x) (CAN_IF1 + (x) * 0x30 + 0x00) +#define CAN_IF_COMM(x) (CAN_IF1 + (x) * 0x30 + 0x02) +#define CAN_IF_MASK(x) (CAN_IF1 + (x) * 0x30 + 0x04) /* 32bit */ +#define CAN_IF_ARB(x) (CAN_IF1 + (x) * 0x30 + 0x08) /* 32bit */ +#define CAN_IF_MCONT(x) (CAN_IF1 + (x) * 0x30 + 0x0c) +#define CAN_IF_DATAA(x) (CAN_IF1 + (x) * 0x30 + 0x0e) /* 32bit */ +#define CAN_IF_DATAB(x) (CAN_IF1 + (x) * 0x30 + 0x12) /* 32bit */ + +#define CONTROL_TEST (1<<7) +#define CONTROL_CCE (1<<6) +#define CONTROL_DAR (1<<5) +#define CONTROL_EIE (1<<3) +#define CONTROL_SIE (1<<2) +#define CONTROL_IE (1<<1) +#define CONTROL_INIT (1<<0) + +#define TEST_RX (1<<7) +#define TEST_TX1 (1<<6) +#define TEST_TX2 (1<<5) +#define TEST_LBACK (1<<4) +#define TEST_SILENT (1<<3) +#define TEST_BASIC (1<<2) + +#define STATUS_BOFF (1<<7) +#define STATUS_EWARN (1<<6) +#define STATUS_EPASS (1<<5) +#define STATUS_RXOK (1<<4) +#define STATUS_TXOK (1<<3) +#define STATUS_LEC_MASK (1<<2) +#define LEC_STUFF_ERROR 1 +#define LEC_FORM_ERROR 2 +#define LEC_ACK_ERROR 3 +#define LEC_BIT1_ERROR 4 + +#define BTR_BRP_MASK 0x3f +#define BTR_BRP_SHIFT 0 +#define BTR_SJW_SHIFT 6 +#define BTR_SJW_MASK (0x3 << BTR_SJW_SHIFT) +#define BTR_TSEG1_SHIFT 8 +#define BTR_TSEG1_MASK (0xf << BTR_TSEG1_SHIFT) +#define BTR_TSEG2_SHIFT 12 +#define BTR_TSEG2_MASK (0x7 << BTR_TSEG2_SHIFT) + +#define IF_COMR_BUSY (1<<15) + +#define IF_COMM_WR (1<<7) +#define IF_COMM_MASK (1<<6) +#define IF_COMM_ARB (1<<5) +#define IF_COMM_CONTROL (1<<4) +#define IF_COMM_CLR_INT_PND (1<<3) +#define IF_COMM_TXRQST (1<<2) +#define IF_COMM_DATAA (1<<1) +#define IF_COMM_DATAB (1<<0) + +#define IF_COMM_ALL (IF_COMM_MASK | IF_COMM_ARB | IF_COMM_CONTROL | \ + IF_COMM_TXRQST | IF_COMM_DATAA | IF_COMM_DATAB) + +#define IF_ARB_MSGVAL (1<<31) +#define IF_ARB_MSGXTD (1<<30) +#define IF_ARB_TRANSMIT (1<<29) + +#define IF_MCONT_NEWDAT (1<<15) +#define IF_MCONT_MSGLST (1<<14) +#define IF_MCONT_INTPND (1<<13) +#define IF_MCONT_UMASK (1<<12) +#define IF_MCONT_TXIE (1<<11) +#define IF_MCONT_RXIE (1<<10) +#define IF_MCONT_RMTEN (1<<9) +#define IF_MCONT_TXRQST (1<<8) +#define IF_MCONT_EOB (1<<7) + +#define MAX_OBJECT 31 +#define MAX_TRANSMIT_OBJECT 15 +#define RECEIVE_OBJECT_BITS 0xffff0000 + +struct ccan_priv { + struct can_priv can; + struct net_device *dev; + int tx_object; + int last_status; + struct delayed_work work; + u16 (*read_reg)(struct net_device *dev, enum c_regs reg); + void (*write_reg)(struct net_device *dev, enum c_regs reg, u16 val); +#ifdef CCAN_DEBUG + unsigned int bufstat[MAX_OBJECT + 1]; +#endif +}; + +extern struct net_device *alloc_ccandev(int sizeof_priv); +extern void free_ccandev(struct net_device *dev); +extern int register_ccandev(struct net_device *dev); +extern void unregister_ccandev(struct net_device *dev); + +#endif /* __CCAN_H__ */ diff --git a/drivers/net/can/old/ccan/h7202_can.c b/drivers/net/can/old/ccan/h7202_can.c new file mode 100644 index 000000000000..7dcc3e749248 --- /dev/null +++ b/drivers/net/can/old/ccan/h7202_can.c @@ -0,0 +1,199 @@ +/* + * drivers/can/h7202_can.c + * + * Copyright (C) 2007 + * + * - Sascha Hauer, Marc Kleine-Budde, Pengutronix + * - Simon Kallweit, intefo AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/netdevice.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/io.h> +#include <asm/hardware.h> + +#include "ccan.h" + +#define DRV_NAME "h7202can" +#define DELAY 5 +#define CAN_ENABLE 0x0e + +static u16 h7202can_read_reg(struct net_device *dev, enum c_regs reg) +{ + u16 val; + volatile int i; + + /* The big kernel lock is used to prevent any other AMBA devices from + * interfering with the current register read operation. The register + * is read twice because of braindamaged hynix cpu. + */ + lock_kernel(); + val = inw(dev->base_addr + (reg<<1)); + for (i = 0; i < DELAY; i++); + val = inw(dev->base_addr + (reg<<1)); + for (i = 0; i < DELAY; i++); + unlock_kernel(); + + return val; +} + +static void h7202can_write_reg(struct net_device *dev, enum c_regs reg, u16 val) +{ + volatile int i; + + lock_kernel(); + outw(val, dev->base_addr + (reg<<1)); + for (i = 0; i < DELAY; i++); + unlock_kernel(); +} + +static int h7202can_drv_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct ccan_priv *priv; + struct resource *mem; + u32 mem_size; + int ret = -ENODEV; + + dev = alloc_ccandev(sizeof(struct ccan_priv)); + if (!dev) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->irq = platform_get_irq(pdev, 0); + if (!mem || !dev->irq) + goto req_error; + + mem_size = mem->end - mem->start + 1; + if (!request_mem_region(mem->start, mem_size, pdev->dev.driver->name)) { + dev_err(&pdev->dev, "resource unavailable\n"); + goto req_error; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + dev->base_addr = (unsigned long)ioremap_nocache(mem->start, mem_size); + + if (!dev->base_addr) { + dev_err(&pdev->dev, "failed to map can port\n"); + ret = -ENOMEM; + goto fail_map; + } + + priv = netdev_priv(dev); + priv->can.can_sys_clock = 8000000; + priv->read_reg = h7202can_read_reg; + priv->write_reg = h7202can_write_reg; + + platform_set_drvdata(pdev, dev); + + /* configure ports */ + switch (mem->start) { + case CAN0_PHYS: + CPU_REG(GPIO_C_VIRT, GPIO_EN) &= ~(3<<1); + CPU_REG(GPIO_C_VIRT, GPIO_DIR) &= ~(1<<1); + CPU_REG(GPIO_C_VIRT, GPIO_DIR) |= (1<<2); + break; + case CAN1_PHYS: + CPU_REG(GPIO_E_VIRT, GPIO_EN) &= ~(3<<16); + CPU_REG(GPIO_E_VIRT, GPIO_DIR) |= (1<<16); + CPU_REG(GPIO_E_VIRT, GPIO_DIR) &= ~(1<<17); + break; + } + + /* enable can */ + h7202can_write_reg(dev, CAN_ENABLE, 1); + + ret = register_ccandev(dev); + if (ret >= 0) { + dev_info(&pdev->dev, "probe for a port 0x%lX done\n", + dev->base_addr); + return ret; + } + + iounmap((unsigned long *)dev->base_addr); +fail_map: + release_mem_region(mem->start, mem_size); +req_error: + free_ccandev(dev); + dev_err(&pdev->dev, "probe failed\n"); + return ret; +} + +static int h7202can_drv_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct resource *mem; + + platform_set_drvdata(pdev, NULL); + unregister_ccandev(dev); + + iounmap((volatile void __iomem *)(dev->base_addr)); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, mem->end - mem->start + 1); + free_ccandev(dev); + return 0; +} + +#ifdef CONFIG_PM +static int h7202can_drv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return 0; +} + +static int h7202can_drv_resume(struct platform_device *pdev) +{ + return 0; +} +#endif /* CONFIG_PM */ + +static struct platform_driver h7202can_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = h7202can_drv_probe, + .remove = h7202can_drv_remove, +#ifdef CONFIG_PM + .suspend = h7202can_drv_suspend, + .resume = h7202can_drv_resume, +#endif /* CONFIG_PM */ +}; + +static int __init h7202can_init(void) +{ + printk(KERN_INFO "%s initializing\n", h7202can_driver.driver.name); + return platform_driver_register(&h7202can_driver); +} + +static void __exit h7202can_cleanup(void) +{ + platform_driver_unregister(&h7202can_driver); + printk(KERN_INFO "%s unloaded\n", h7202can_driver.driver.name); +} + +module_init(h7202can_init); +module_exit(h7202can_cleanup); + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_AUTHOR("Simon Kallweit <simon.kallweit@intefo.ch>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CAN port driver Hynix H7202 processor"); diff --git a/drivers/net/can/old/hal/c200.c b/drivers/net/can/old/hal/c200.c new file mode 100644 index 000000000000..47ea74ca51b0 --- /dev/null +++ b/drivers/net/can/old/hal/c200.c @@ -0,0 +1,206 @@ +/* + * c200.c - low cost parallelport CAN adaptor hardware abstraction layer + * ( direct register access without parport subsystem support ) + * + * CAN200 project homepage http://private.addcom.de/horo/can200 + * + * This hal is based on a patch from Uwe Bonnes. + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include "hal.h" + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "c200"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + + base[0] = 0x378UL; + irq[0] = 7; +} + +#define ECR_REGS_OFFSET 0x400 +#define ECR_CTRL_OFFSET (ECR_REGS_OFFSET + 2) + +static u8 ecr_crtl_save; + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + /* set for device base_addr */ + rbase[dev_num] = base[dev_num]; + + /* grab ECR control registers and set parport to 'byte mode' */ + if (request_region(rbase[dev_num] + ECR_REGS_OFFSET, 3, drv_name)) { + + ecr_crtl_save = inb(rbase[dev_num] + ECR_CTRL_OFFSET); + + outb((ecr_crtl_save & 0x1F) | 0x20, + rbase[dev_num] + ECR_CTRL_OFFSET); + } else + return 0; + + if (request_region(rbase[dev_num], 4, drv_name)) + return 1; + + release_region(rbase[dev_num] + ECR_REGS_OFFSET, 3); + + return 0; +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + + release_region(base[dev_num], 4); + + /* restore original ECR control register value */ + outb(ecr_crtl_save, base[dev_num] + ECR_CTRL_OFFSET); + release_region(base[dev_num] + ECR_REGS_OFFSET, 3); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) +{ + extern unsigned long rbase[]; + unsigned long pc = rbase[dev_num] + 2; + + /* enable irq */ + outb(inb(pc) | 0x10, pc); + + return 0; +} + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) +{ + extern unsigned long rbase[]; + unsigned long pc = rbase[dev_num] + 2; + + /* disable irq */ + outb(inb(pc) & ~0x10, pc); + + return 0; +} + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +#define WRITEP 0x01 /* inverted at port */ +#define DATASTB 0x02 /* inverted at port and at device*/ +#define ADDRSTB 0x08 /* inverted at port and at device*/ +#define PORTREAD 0x20 + +static DEFINE_SPINLOCK(c200_lock); + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) +{ + unsigned long pa = base; + unsigned long pc = pa + 2; + unsigned long flags; + u8 irqstatus = (inb(pc) & 0x10) | 0x04; + u8 val; + + spin_lock_irqsave(&c200_lock, flags); + + outb(irqstatus | ADDRSTB, pc); + outb((reg & 0x1F) | 0x80, pa); + outb(irqstatus, pc); + outb(irqstatus | PORTREAD, pc); + outb(irqstatus | DATASTB | PORTREAD, pc); + val = inb(pa); + outb(irqstatus, pc); + + spin_unlock_irqrestore(&c200_lock, flags); + + return val; +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) +{ + unsigned long pa = base; + unsigned long pc = pa + 2; + unsigned long flags; + u8 irqstatus = (inb(pc) & 0x10) | 0x04; + + spin_lock_irqsave(&c200_lock, flags); + + outb(irqstatus | ADDRSTB, pc); + outb(reg & 0x1F, pa); + outb(irqstatus, pc); + outb(irqstatus | WRITEP, pc); + outb(irqstatus | DATASTB | WRITEP, pc); + outb(val, pa); + outb(irqstatus, pc); + + spin_unlock_irqrestore(&c200_lock, flags); +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { return; } diff --git a/drivers/net/can/old/hal/esdio.c b/drivers/net/can/old/hal/esdio.c new file mode 100644 index 000000000000..a0132e2ed545 --- /dev/null +++ b/drivers/net/can/old/hal/esdio.c @@ -0,0 +1,186 @@ +/* + * esdio.c - multiplex register access CAN hardware abstraction layer + * for the esd 3xCAN pc104 board + * http://www.esd-electronics.de/products/CAN/can-pc104-200_e.htm + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2007 Fraunhofer FOKUS + * + * Provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + * History: + * 2007-05-22 Bjoern Riemer: initial release + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include "hal.h" + +#ifdef CONFIG_CAN_DEBUG_DEVICES +#define DBG(args...) printk(args) +#else +#define DBG(args...) +#endif + +//#define DBG(args...) printk(args) + +int esd_ale_offset = 1; //default for the sja1000 chip +int esd_cs_offset = 0; //default for the sja1000 chip + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "esdio"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + + base[0] = 0x1e8UL; + irq[0] = 5; +} + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + if (!memcmp(drv_name,"i82527-esdio",sizeof("i82527-esdio"))){ + esd_ale_offset = 7; + esd_cs_offset = 4; + } else if (!memcmp(drv_name,"sja1000-esdio",sizeof("sja1000-esdio"))){ + esd_ale_offset = 1; + esd_cs_offset = 0; + } + + /* set for device base_addr */ + rbase[dev_num] = base[dev_num]; + + /* ignore num_regs and create the 2 register region: */ + /* address register = base + esd_ale_offset */ + /* data register = base + esd_cs_offset */ + if (request_region(base[dev_num] + esd_ale_offset, 1, drv_name)){ + if (request_region(base[dev_num] + esd_cs_offset, 1,drv_name)){ + return 1; + } else { + release_region(base[dev_num]+esd_ale_offset, 1); + return 0; // error + } + } + + return 0; // error +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + + /* ignore num_regs and create the 2 register region: */ + /* address register = base + esd_ale_offset */ + /* data register = base + esd_cs_offset */ + release_region(base[dev_num] + esd_cs_offset, 1); + release_region(base[dev_num] + esd_ale_offset, 1); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) +{ + int i, stat, i1; + extern unsigned long base[]; + extern unsigned int irq[]; + + i1 = irq[dev_num]; //get IRQ number + DBG(KERN_INFO "esdio.c: enabling IRQ %d for dev_num %d\n",i1,dev_num); + + for (i=0; i<4; i++){ + stat=i; // bit 0,1 selects the latch bit to write + if (i1 & 0x01){ + stat |= 0x80; //bit7 carrys the value of the latch bit + } + outb(stat,base[dev_num]+3); + i1 = i1>>1; + } + + outb(0x87,base[dev_num]+3); //enable irq selection + outb(0x86,base[dev_num]+3); //enable irq tristate buffer + + return 1; +} + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) +{ + int i; + extern unsigned long base[]; + + DBG(KERN_INFO "esdio.c: diabling IRQ for dev_num %d\n",dev_num); + + outb(0x07,base[dev_num]+3); //disable irq selection + outb(0x06,base[dev_num]+3); //disable irq tristate buffer + + for (i=0; i<4; i++) + outb(i,base[dev_num]+3); + + return 1; +} + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) { + + outb(reg, base + esd_ale_offset); /* address */ + return inb(base + esd_cs_offset); /* data */ +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) { + + outb(reg, base + esd_ale_offset); /* address */ + outb(val, base + esd_cs_offset); /* data */ +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { + + outb(0x86,dev->base_addr+3); //enable irq tristate buffer + return; +} diff --git a/drivers/net/can/old/hal/gw2.c b/drivers/net/can/old/hal/gw2.c new file mode 100644 index 000000000000..893b983e2ec2 --- /dev/null +++ b/drivers/net/can/old/hal/gw2.c @@ -0,0 +1,161 @@ +/* + * gw2.c - Trajet GW2 register access CAN hardware abstraction layer + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include "hal.h" + +#define ADDR_GAP 1 + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "gw2"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + extern unsigned int speed[]; + + base[0] = 0xF0100200UL; + irq[0] = 26; + speed[0] = 500; + + base[1] = 0xF0100300UL; + irq[1] = 26; + speed[1] = 100; + + base[2] = 0xF0100400UL; + irq[2] = 26; + speed[2] = 100; + + base[3] = 0xF0100500UL; + irq[3] = 26; + speed[3] = 500; +} + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + unsigned int gw2_regs = num_regs * (ADDR_GAP + 1); + + /* creating the region for IOMEM is pretty easy */ + if (!request_mem_region(base[dev_num], gw2_regs, drv_name)) + return 0; /* failed */ + + /* set device base_addr */ + rbase[dev_num] = (unsigned long)ioremap(base[dev_num], gw2_regs); + + if (rbase[dev_num]) + return 1; /* success */ + + /* cleanup due to failed ioremap() */ + release_mem_region(base[dev_num], gw2_regs); + return 0; /* failed */ +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + unsigned int gw2_regs = num_regs * (ADDR_GAP + 1); + + iounmap((void *)rbase[dev_num]); + release_mem_region(base[dev_num], gw2_regs); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) { return 0; } + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) { return 0; } + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) { + + static u8 val; + void __iomem *addr = (void __iomem *)base + + reg * (ADDR_GAP + 1) + ADDR_GAP; + + val = (u8)readw(addr); + rmb(); + + return val; +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) { + + void __iomem *addr = (void __iomem *)base + + reg * (ADDR_GAP + 1) + ADDR_GAP; + + writew(val, addr); + wmb(); +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { return; } + diff --git a/drivers/net/can/old/hal/hal.h b/drivers/net/can/old/hal/hal.h new file mode 100644 index 000000000000..1c59b54cdb6a --- /dev/null +++ b/drivers/net/can/old/hal/hal.h @@ -0,0 +1,99 @@ +/* + * hal.h - definitions for CAN controller hardware abstraction layer + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#ifndef CAN_HAL_H +#define CAN_HAL_H + +#include <linux/types.h> +#include <linux/netdevice.h> + +/* Number of supported CAN devices for each HAL (default) */ +#define MAXDEV 8 + +/* general function prototypes for CAN HAL */ + +/* init the HAL - call at driver module init */ +int hal_init(void); + +/* exit the HAL - call at driver module exit */ +int hal_exit(void); + +/* get name of this CAN HAL */ +char *hal_name(void); + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void); + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name); + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs); + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num); + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num); + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num); + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg); + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val); + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev); + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev); + +#endif /* CAN_HAL_H */ diff --git a/drivers/net/can/old/hal/io.c b/drivers/net/can/old/hal/io.c new file mode 100644 index 000000000000..85e3b1edc1bc --- /dev/null +++ b/drivers/net/can/old/hal/io.c @@ -0,0 +1,123 @@ +/* + * io.c - linear register access CAN hardware abstraction layer + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include "hal.h" + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "io"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + + base[0] = 0x2C0UL; + irq[0] = 10; + + base[1] = 0x320UL; + irq[1] = 5; +} + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + /* set for device base_addr */ + rbase[dev_num] = base[dev_num]; + + /* creating the region for IO is pretty easy */ + return (request_region(base[dev_num], num_regs, drv_name))? 1 : 0; +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + + release_region(base[dev_num], num_regs); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) { return 0; } + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) { return 0; } + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) { + + return inb(base + reg); +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) { + + outb(val, base + reg); +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { return; } + diff --git a/drivers/net/can/old/hal/iomem.c b/drivers/net/can/old/hal/iomem.c new file mode 100644 index 000000000000..6a89a9cc2fe0 --- /dev/null +++ b/drivers/net/can/old/hal/iomem.c @@ -0,0 +1,142 @@ +/* + * iomem.c - linear register access CAN hardware abstraction layer + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include "hal.h" + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "iomem"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + + base[0] = 0xd8000UL; + irq[0] = 5; + + base[1] = 0xd8100UL; + irq[1] = 15; +} + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + /* creating the region for IOMEM is pretty easy */ + if (!request_mem_region(base[dev_num], num_regs, drv_name)) + return 0; /* failed */ + + /* set device base_addr */ + rbase[dev_num] = (unsigned long)ioremap(base[dev_num], num_regs); + + if (rbase[dev_num]) + return 1; /* success */ + + /* cleanup due to failed ioremap() */ + release_mem_region(base[dev_num], num_regs); + return 0; /* failed */ +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + iounmap((void *)rbase[dev_num]); + release_mem_region(base[dev_num], num_regs); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) { return 0; } + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) { return 0; } + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) { + + static u8 val; + void __iomem *addr = (void __iomem *)base + reg; + + val = (u8)readb(addr); + rmb(); + + return val; +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) { + + void __iomem *addr = (void __iomem *)base + reg; + + writeb(val, addr); + wmb(); +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { return; } + diff --git a/drivers/net/can/old/hal/iomux.c b/drivers/net/can/old/hal/iomux.c new file mode 100644 index 000000000000..a4e8fba82ddd --- /dev/null +++ b/drivers/net/can/old/hal/iomux.c @@ -0,0 +1,125 @@ +/* + * iomux.c - multiplex register access CAN hardware abstraction layer + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include "hal.h" + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "iomux"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + + base[0] = 0x300UL; + irq[0] = 5; +} + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + /* set for device base_addr */ + rbase[dev_num] = base[dev_num]; + + /* ignore num_regs and create the 2 register region: */ + /* address register = base / data register = base + 1 */ + return (request_region(base[dev_num], 2, drv_name))? 1 : 0; +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + + /* ignore num_regs and create the 2 register region: */ + /* address register = base / data register = base + 1 */ + release_region(base[dev_num], 2); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) { return 0; } + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) { return 0; } + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) { + + outb(reg, base); /* address */ + return inb(base + 1); /* data */ +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) { + + outb(reg, base); /* address */ + outb(val, base + 1); /* data */ +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { return; } + diff --git a/drivers/net/can/old/hal/pc7io.c b/drivers/net/can/old/hal/pc7io.c new file mode 100644 index 000000000000..b223efd25f9f --- /dev/null +++ b/drivers/net/can/old/hal/pc7io.c @@ -0,0 +1,126 @@ +/* + * pc7io.c - linear register access CAN hardware abstraction layer + * + * Inspired by the OCAN driver http://ar.linux.it/software/#ocan + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include "hal.h" + +/* init the HAL - call at driver module init */ +int hal_init(void) { return 0; } + +/* exit the HAL - call at driver module exit */ +int hal_exit(void) { return 0; } + +/* get name of this CAN HAL */ +char *hal_name(void) { return "pc7io"; } + +/* fill arrays base[] and irq[] with HAL specific defaults */ +void hal_use_defaults(void) +{ + extern unsigned long base[]; + extern unsigned int irq[]; + + base[0] = 0x1000UL; + irq[0] = 9; +} + +/* request controller register access space */ +int hal_request_region(int dev_num, + unsigned int num_regs, + char *drv_name) +{ + extern unsigned long base[]; + extern unsigned long rbase[]; + + /* set for device base_addr */ + rbase[dev_num] = base[dev_num]; + + /* creating the region for IO is pretty easy */ + return (request_region(base[dev_num], num_regs, drv_name))? 1 : 0; +} + +/* release controller register access space */ +void hal_release_region(int dev_num, + unsigned int num_regs) +{ + extern unsigned long base[]; + + release_region(base[dev_num], num_regs); +} + +/* enable non controller hardware (e.g. irq routing, etc.) */ +int hw_attach(int dev_num) { + + /* Unlock special function register */ + outb(5, 0x169); + + return 0; +} + +/* disable non controller hardware (e.g. irq routing, etc.) */ +int hw_detach(int dev_num) { return 0; } + +/* reset controller hardware (with specific non controller hardware) */ +int hw_reset_dev(int dev_num) { return 0; } + +/* read from controller register */ +u8 hw_readreg(unsigned long base, int reg) { + + return inb(base + reg); +} + +/* write to controller register */ +void hw_writereg(unsigned long base, int reg, u8 val) { + + outb(val, base + reg); +} + +/* hardware specific work to do at start of irq handler */ +void hw_preirq(struct net_device *dev) { return; } + +/* hardware specific work to do at end of irq handler */ +void hw_postirq(struct net_device *dev) { return; } + diff --git a/drivers/net/can/old/i82527/Makefile b/drivers/net/can/old/i82527/Makefile new file mode 100644 index 000000000000..eb025a8a10c5 --- /dev/null +++ b/drivers/net/can/old/i82527/Makefile @@ -0,0 +1,24 @@ +# +# + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common +EXTRA_CFLAGS += -I$(TOPDIR)/drivers/net/can/old/hal + +obj-m := i82527-pc7io.o i82527-iomem.o i82527-esdio.o + +i82527-pc7io-objs := i82527.o proc.o ../hal/pc7io.o +i82527-iomem-objs := i82527.o proc.o ../hal/iomem.o +i82527-esdio-objs := i82527.o proc.o ../hal/esdio.o + +endif diff --git a/drivers/net/can/old/i82527/i82527.c b/drivers/net/can/old/i82527/i82527.c new file mode 100644 index 000000000000..d45c4fab22de --- /dev/null +++ b/drivers/net/can/old/i82527/i82527.c @@ -0,0 +1,1251 @@ +/* + * i82527.c - Intel I82527 network device driver + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> + +#include <linux/can.h> +#include <linux/can/ioctl.h> /* for struct can_device_stats */ +#include "hal.h" +#include "i82527.h" + +MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("LLCF/socketcan '" CHIP_NAME "' network device driver"); + +#ifdef CONFIG_CAN_DEBUG_DEVICES +#define DBG(args...) ((priv->debug > 0) ? printk(args) : 0) +/* logging in interrupt context! */ +#define iDBG(args...) ((priv->debug > 1) ? printk(args) : 0) +#define iiDBG(args...) ((priv->debug > 2) ? printk(args) : 0) +#else +#define DBG(args...) +#define iDBG(args...) +#define iiDBG(args...) +#endif + +char drv_name[DRV_NAME_LEN] = "undefined"; + +/* driver and version information */ +static const char *drv_version = "0.0.4"; +static const char *drv_reldate = "2007-08-03"; + +static const canid_t rxobjflags[] = {0, CAN_EFF_FLAG, + CAN_RTR_FLAG, CAN_RTR_FLAG | CAN_EFF_FLAG, + 0, CAN_EFF_FLAG}; +#define RXOBJBASE 10 + +/* array of all can chips */ +struct net_device *can_dev[MAXDEV]; + +/* module parameters */ +unsigned long base[MAXDEV] = { 0 }; /* hardware address */ +unsigned long rbase[MAXDEV] = { 0 }; /* (remapped) device address */ +unsigned int irq[MAXDEV] = { 0 }; + +unsigned int speed[MAXDEV] = { DEFAULT_SPEED, DEFAULT_SPEED }; +unsigned int btr[MAXDEV] = { 0 }; +unsigned int bcr[MAXDEV] = { 0 }; /* bus configuration register */ +unsigned int cdv[MAXDEV] = { 0 }; /* CLKOUT clock divider */ +unsigned int mo15[MAXDEV] = { MO15_DEFLT, MO15_DEFLT }; /* msg obj 15 */ + +static int rx_probe[MAXDEV] = { 0 }; +static int clk = DEFAULT_HW_CLK; +static int force_dmc = DEFAULT_FORCE_DMC; +static int irq_mode = DEFAULT_IRQ_MODE; +static int debug = 0; +static int restart_ms = 100; + +static int base_n; +static int irq_n; +static int speed_n; +static int btr_n; +static int bcr_n; +static int cdv_n; +static int mo15_n; +static int rx_probe_n; + +static u8 dsc; /* devide system clock */ +static u8 dmc; /* devide memory clock */ +static unsigned long irqflags; /* for shared / disabled local interrupts */ + +module_param_array(base, int, &base_n, 0); +module_param_array(irq, int, &irq_n, 0); +module_param_array(speed, int, &speed_n, 0); +module_param_array(btr, int, &btr_n, 0); +module_param_array(bcr, int, &bcr_n, 0); +module_param_array(cdv, int, &cdv_n, 0); +module_param_array(mo15, int, &mo15_n, 0); +module_param_array(rx_probe, int, &rx_probe_n, 0); + +module_param(clk, int, 0); +module_param(force_dmc, int, 0); +module_param(irq_mode, int, 0); +module_param(debug, int, 0); +module_param(restart_ms, int, 0); + +MODULE_PARM_DESC(base, "CAN controller base address"); +MODULE_PARM_DESC(irq, "CAN controller interrupt"); +MODULE_PARM_DESC(speed, "CAN bus bitrate"); +MODULE_PARM_DESC(btr, "Bit Timing Register value 0x<btr0><btr1>, e.g. 0x4014"); +MODULE_PARM_DESC(bcr, "i82527 bus configuration register value (default: 0)"); +MODULE_PARM_DESC(cdv, "clockout devider value (0-14) (default: 0)"); +MODULE_PARM_DESC(mo15, "rx message object 15 usage. 0:none 1:sff(default) 2:eff"); +MODULE_PARM_DESC(rx_probe, "switch to trx mode after correct msg receiption. (default off)"); + +MODULE_PARM_DESC(clk, "CAN controller chip clock (default: 16MHz)"); +MODULE_PARM_DESC(force_dmc, "set i82527 DMC bit (default: calculate from clk)"); +MODULE_PARM_DESC(irq_mode, "specify irq setup bits (1:shared 2:disable local irqs while processing) (default: 1)"); +MODULE_PARM_DESC(debug, "set debug mask (default: 0)"); +MODULE_PARM_DESC(restart_ms, "restart chip on heavy bus errors / bus off after x ms (default 100ms)"); + +/* function declarations */ + +static void chipset_init(struct net_device *dev, int wake); +static void chipset_init_rx(struct net_device *dev); +static void chipset_init_trx(struct net_device *dev); +static void can_netdev_setup(struct net_device *dev); +static struct net_device* can_create_netdev(int dev_num, int hw_regs); +static int can_set_drv_name(void); +int set_reset_mode(struct net_device *dev); + +static int i82527_probe_chip(unsigned long base) +{ + // Check if hardware reset is still inactive OR + // maybe there is no chip in this address space + if (CANin(base, cpuInterfaceReg) & iCPU_RST) { + printk(KERN_INFO "%s: probing @ 0x%lX failed (reset)\n", + drv_name, base); + return 0; + } + + // Write test pattern + CANout(base, message1Reg.dataReg[1], 0x25); + CANout(base, message2Reg.dataReg[3], 0x52); + CANout(base, message10Reg.dataReg[6], 0xc3); + + // Read back test pattern + if ((CANin(base, message1Reg.dataReg[1]) != 0x25 ) || + (CANin(base, message2Reg.dataReg[3]) != 0x52 ) || + (CANin(base, message10Reg.dataReg[6]) != 0xc3 )) { + printk(KERN_INFO "%s: probing @ 0x%lX failed (pattern)\n", + drv_name, base); + return 0; + } + + return 1; +} + +/* + * set baud rate divisor values + */ +static void set_btr(struct net_device *dev, int btr0, int btr1) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + /* no bla bla when restarting the device */ + if (priv->state == STATE_UNINITIALIZED) + printk(KERN_INFO "%s: setting BTR0=%02X BTR1=%02X\n", + dev->name, btr0, btr1); + + CANout(base, bitTiming0Reg, btr0); + CANout(base, bitTiming1Reg, btr1); +} + +/* + * calculate baud rate divisor values + */ +static void set_baud(struct net_device *dev, int baud, int clock) +{ + struct can_priv *priv = netdev_priv(dev); + + int error; + int brp; + int tseg; + int tseg1 = 0; + int tseg2 = 0; + + int best_error = 1000000000; + int best_tseg = 0; + int best_brp = 0; + int best_baud = 0; + + int SAM = (baud > 100000 ? 0 : 1); + + if (dsc) /* devide system clock */ + clock >>= 1; /* calculate BTR with this value */ + + for (tseg = (0 + 0 + 2) * 2; + tseg <= (MAX_TSEG2 + MAX_TSEG1 + 2) * 2 + 1; + tseg++) { + brp = clock / ((1 + tseg / 2) * baud) + tseg % 2; + if ((brp > 0) && (brp <= 64)) { + error = baud - clock / (brp * (1 + tseg / 2)); + if (error < 0) { + error = -error; + } + if (error <= best_error) { + best_error = error; + best_tseg = tseg / 2; + best_brp = brp - 1; + best_baud = clock / (brp * (1 + tseg / 2)); + } + } + } + if (best_error && (baud / best_error < 10)) { + printk("%s: unable to set baud rate %d (ext clock %dHz)\n", + dev->name, baud, clock * 2); + return; +// return -EINVAL; + } + tseg2 = best_tseg - (SAMPLE_POINT * (best_tseg + 1)) / 100; + if (tseg2 < 0) { + tseg2 = 0; + } else if (tseg2 > MAX_TSEG2) { + tseg2 = MAX_TSEG2; + } + tseg1 = best_tseg - tseg2 - 2; + if (tseg1 > MAX_TSEG1) { + tseg1 = MAX_TSEG1; + tseg2 = best_tseg - tseg1 - 2; + } + + priv->btr = ((best_brp | JUMPWIDTH)<<8) + + ((SAM << 7) | (tseg2 << 4) | tseg1); + + printk(KERN_INFO "%s: calculated best baudrate: %d / btr is 0x%04X\n", + dev->name, best_baud, priv->btr); + + set_btr(dev, (priv->btr>>8) & 0xFF, priv->btr & 0xFF); +// set_btr(dev, best_brp | JUMPWIDTH, (SAM << 7) | (tseg2 << 4) | tseg1); +} + +static inline int obj2rxo(int obj) +{ + /* obj4 = obj15 SFF, obj5 = obj15 EFF */ + if (obj < 4) + return RXOBJBASE + obj; + else + return 15; +} + +void enable_rx_obj(unsigned long base, int obj) +{ + u8 mcfg = 0; + int rxo = obj2rxo(obj); + + // Configure message object for receiption + if (rxobjflags[obj] & CAN_EFF_FLAG) + mcfg = MCFG_XTD; + + if (rxobjflags[obj] & CAN_RTR_FLAG) { + CANout(base, msgArr[rxo].messageReg.messageConfigReg, + mcfg | MCFG_DIR); + CANout(base, msgArr[rxo].messageReg.msgCtrl0Reg, + MVAL_SET | TXIE_RES | RXIE_SET | INTPD_RES); + CANout(base, msgArr[rxo].messageReg.msgCtrl1Reg, + NEWD_RES | CPUU_SET | TXRQ_RES | RMPD_RES); + } else { + CANout(base, msgArr[rxo].messageReg.messageConfigReg, mcfg); + CANout(base, msgArr[rxo].messageReg.msgCtrl0Reg, + MVAL_SET | TXIE_RES | RXIE_SET | INTPD_RES); + CANout(base, msgArr[rxo].messageReg.msgCtrl1Reg, + NEWD_RES | MLST_RES | TXRQ_RES | RMPD_RES); + } +} + +void disable_rx_obj(unsigned long base, int obj) +{ + int rxo = obj2rxo(obj); + + CANout(base, msgArr[rxo].messageReg.msgCtrl1Reg, + NEWD_RES | MLST_RES | TXRQ_RES | RMPD_RES); + CANout(base, msgArr[rxo].messageReg.msgCtrl0Reg, + MVAL_RES | TXIE_RES | RXIE_RES | INTPD_RES); +} + +int set_reset_mode(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + // Configure cpu interface + CANout(base, cpuInterfaceReg,(dsc | dmc | iCPU_CEN)); + + // Enable configuration and puts chip in bus-off, disable interrupts + CANout(base, controlReg, iCTL_CCE | iCTL_INI); + + // Clear interrupts + CANin(base, interruptReg); + + // Clear status register + CANout(base, statusReg, 0); + + // Clear message objects for receiption + if (priv->mo15 == MO15_SFF) + disable_rx_obj(base, 4); /* rx via obj15 SFF */ + else + disable_rx_obj(base, 0); /* rx via obj10 SFF */ + + if (priv->mo15 == MO15_EFF) + disable_rx_obj(base, 5); /* rx via obj15 EFF */ + else + disable_rx_obj(base, 1); /* rx via obj11 EFF */ + + disable_rx_obj(base, 2); + disable_rx_obj(base, 3); + + // Clear message object for send + CANout(base, message1Reg.msgCtrl1Reg, + RMPD_RES | TXRQ_RES | CPUU_RES | NEWD_RES); + CANout(base, message1Reg.msgCtrl0Reg, + MVAL_RES | TXIE_RES | RXIE_RES | INTPD_RES); + + DBG(KERN_INFO "%s: %s: CtrlReg 0x%x CPUifReg 0x%x\n", + dev->name, __FUNCTION__, + CANin(base, controlReg), CANin(base, cpuInterfaceReg)); + + return 0; +} + +static int set_normal_mode(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + // Clear interrupts + CANin(base, interruptReg); + + // Clear status register + CANout(base, statusReg, 0); + + // Configure message objects for receiption + if (priv->mo15 == MO15_SFF) { + enable_rx_obj(base, 4); /* rx via obj15 SFF */ + printk(KERN_INFO "%s: %s: using msg object 15 for " + "SFF receiption.\n", + dev->name, CHIP_NAME); + } else + enable_rx_obj(base, 0); /* rx via obj10 SFF */ + + if (priv->mo15 == MO15_EFF) { + enable_rx_obj(base, 5); /* rx via obj15 EFF */ + printk(KERN_INFO "%s: %s: using msg object 15 for " + "EFF receiption.\n", + dev->name, CHIP_NAME); + } else + enable_rx_obj(base, 1); /* rx via obj11 EFF */ + + enable_rx_obj(base, 2); + enable_rx_obj(base, 3); + + // Clear message object for send + CANout(base, message1Reg.msgCtrl1Reg, + RMPD_RES | TXRQ_RES | CPUU_RES | NEWD_RES); + CANout(base, message1Reg.msgCtrl0Reg, + MVAL_RES | TXIE_RES | RXIE_RES | INTPD_RES); + + return 0; +} + +static int set_listen_mode(struct net_device *dev) +{ + return set_normal_mode(dev); /* for now */ +} + +/* + * Clear and invalidate message objects + */ +int i82527_clear_msg_objects(unsigned long base) +{ + int i; + int id; + int data; + + for (i = 1; i <= 15; i++) { + CANout(base, msgArr[i].messageReg.msgCtrl0Reg, + INTPD_UNC | RXIE_RES | TXIE_RES | MVAL_RES); + CANout(base, msgArr[i].messageReg.msgCtrl0Reg, + INTPD_RES | RXIE_RES | TXIE_RES | MVAL_RES); + CANout(base, msgArr[i].messageReg.msgCtrl1Reg, + NEWD_RES | MLST_RES | TXRQ_RES | RMPD_RES); + for (data = 0; data < 8; data++) + CANout(base, msgArr[i].messageReg.dataReg[data], 0); + for (id = 0; id < 4; id++) + CANout(base, msgArr[i].messageReg.idReg[id], 0); + CANout(base, msgArr[i].messageReg.messageConfigReg, 0); + } + + return 0; +} + +/* + * initialize I82527 chip: + * - reset chip + * - set output mode + * - set baudrate + * - enable interrupts + * - start operating mode + */ +static void chipset_init_regs(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + // Enable configuration and puts chip in bus-off, disable interrupts + CANout(base, controlReg, (iCTL_CCE | iCTL_INI)); + + // Set CLKOUT devider and slew rates is was done in i82527_init_module + + // Bus configuration was done in i82527_init_module + + // Clear interrupts + CANin(base, interruptReg); + + // Clear status register + CANout(base, statusReg, 0); + + i82527_clear_msg_objects(base); + + // Set all global ID masks to "don't care" + CANout(base, globalMaskStandardReg[0], 0); + CANout(base, globalMaskStandardReg[1], 0); + CANout(base, globalMaskExtendedReg[0], 0); + CANout(base, globalMaskExtendedReg[1], 0); + CANout(base, globalMaskExtendedReg[2], 0); + CANout(base, globalMaskExtendedReg[3], 0); + + DBG(KERN_INFO "%s: %s: CtrlReg 0x%x CPUifReg 0x%x\n", + dev->name, __FUNCTION__, + CANin(base, controlReg), CANin(base, cpuInterfaceReg)); + + // Note: At this stage the CAN ship is still in bus-off condition + // and must be started using StartChip() + + /* set baudrate */ + if (priv->btr) { /* no calculation when btr is provided */ + set_btr(dev, (priv->btr>>8) & 0xFF, priv->btr & 0xFF); + } else { + if (priv->speed == 0) { + priv->speed = DEFAULT_SPEED; + } + set_baud(dev, priv->speed * 1000, priv->clock); + } + +} + +static void chipset_init(struct net_device *dev, int wake) +{ + struct can_priv *priv = netdev_priv(dev); + + if (priv->rx_probe) + chipset_init_rx(dev); /* wait for valid reception first */ + else + chipset_init_trx(dev); + + if ((wake) && netif_queue_stopped(dev)) + netif_wake_queue(dev); +} + +static void chipset_init_rx(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + iDBG(KERN_INFO "%s: %s()\n", dev->name, __FUNCTION__); + + /* set chip into reset mode */ + set_reset_mode(dev); + + /* set registers */ + chipset_init_regs(dev); + + /* automatic bit rate detection */ + set_listen_mode(dev); + + priv->state = STATE_PROBE; + + // Clear bus-off, Interrupts only for errors, not for status change + CANout(base, controlReg, iCTL_IE | iCTL_EIE); + + DBG(KERN_INFO "%s: %s: CtrlReg 0x%x CPUifReg 0x%x\n", + dev->name, __FUNCTION__, + CANin(base, controlReg), CANin(base, cpuInterfaceReg)); +} + +static void chipset_init_trx(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + iDBG(KERN_INFO "%s: %s()\n", dev->name, __FUNCTION__); + + /* set chip into reset mode */ + set_reset_mode(dev); + + /* set registers */ + chipset_init_regs(dev); + + /* leave reset mode */ + set_normal_mode(dev); + + priv->state = STATE_ACTIVE; + + // Clear bus-off, Interrupts only for errors, not for status change + CANout(base, controlReg, iCTL_IE | iCTL_EIE); + + DBG(KERN_INFO "%s: %s: CtrlReg 0x%x CPUifReg 0x%x\n", + dev->name, __FUNCTION__, + CANin(base, controlReg), CANin(base, cpuInterfaceReg)); +} + +/* + * transmit a CAN message + * message layout in the sk_buff should be like this: + * xx xx xx xx ll 00 11 22 33 44 55 66 77 + * [ can-id ] [len] [can data (up to 8 bytes] + */ +static int can_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf = (struct can_frame*)skb->data; + unsigned long base = dev->base_addr; + uint8_t dlc; + uint8_t rtr; + canid_t id; + int i; + + if ((CANin(base, message1Reg.msgCtrl1Reg) & TXRQ_UNC) == TXRQ_SET) { + printk(KERN_ERR "%s: %s: TX register is occupied!\n", + dev->name, drv_name); + return 0; + } + + netif_stop_queue(dev); + + dlc = cf->can_dlc; + id = cf->can_id; + + if ( cf->can_id & CAN_RTR_FLAG ) + rtr = 0; + else + rtr = MCFG_DIR; + + CANout(base, message1Reg.msgCtrl1Reg, + RMPD_RES | TXRQ_RES | CPUU_SET | NEWD_RES); + CANout(base, message1Reg.msgCtrl0Reg, + MVAL_SET | TXIE_SET | RXIE_RES | INTPD_RES); + + if (id & CAN_EFF_FLAG) { + id &= CAN_EFF_MASK; + CANout(base, message1Reg.messageConfigReg, + (dlc << 4) + rtr + MCFG_XTD); + CANout(base, message1Reg.idReg[3], (id << 3) & 0xFFU); + CANout(base, message1Reg.idReg[2], (id >> 5) & 0xFFU); + CANout(base, message1Reg.idReg[1], (id >> 13) & 0xFFU); + CANout(base, message1Reg.idReg[0], (id >> 21) & 0xFFU); + } + else { + id &= CAN_SFF_MASK; + CANout(base, message1Reg.messageConfigReg, + ( dlc << 4 ) + rtr); + CANout(base, message1Reg.idReg[0], (id >> 3) & 0xFFU); + CANout(base, message1Reg.idReg[1], (id << 5) & 0xFFU); + } + + dlc &= 0x0f; //restore length only + for ( i=0; i < dlc; i++ ) { + CANout(base, message1Reg.dataReg[i], + cf->data[i]); + } + + CANout(base, message1Reg.msgCtrl1Reg, + (RMPD_RES | TXRQ_SET | CPUU_RES | NEWD_UNC)); + + // HM: We had some cases of repeated IRQs + // so make sure the INT is acknowledged + // I know it's already further up, but doing again fixed the issue + CANout(base, message1Reg.msgCtrl0Reg, + (MVAL_UNC | TXIE_UNC | RXIE_UNC | INTPD_RES)); + + stats->tx_bytes += dlc; + + dev->trans_start = jiffies; + + kfree_skb(skb); + + return 0; +} + +static void can_tx_timeout(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + + stats->tx_errors++; + + /* do not conflict with e.g. bus error handling */ + if (!(priv->timer.expires)){ /* no restart on the run */ + chipset_init_trx(dev); /* no tx queue wakeup */ + netif_wake_queue(dev); /* wakeup here */ + } + else + DBG(KERN_INFO "%s: %s: can_restart_dev already active.\n", + dev->name, __FUNCTION__ ); + +} + +# if 0 +static void can_restart_on(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + if (!(priv->timer.expires)){ /* no restart on the run */ + + set_reset_mode(dev); + + priv->timer.function = can_restart_dev; + priv->timer.data = (unsigned long) dev; + + /* restart chip on persistent error in <xxx> ms */ + priv->timer.expires = jiffies + (priv->restart_ms * HZ) / 1000; + add_timer(&priv->timer); + + iDBG(KERN_INFO "%s: %s start (%ld)\n", + dev->name, __FUNCTION__ , jiffies); + } else + iDBG(KERN_INFO "%s: %s already (%ld)\n", + dev->name, __FUNCTION__ , jiffies); +} + +static void can_restart_dev(unsigned long data) +{ + struct net_device *dev = (struct net_device*) data; + struct can_priv *priv = netdev_priv(dev); + + DBG(KERN_INFO "%s: can_restart_dev (%ld)\n", + dev->name, jiffies); + + /* mark inactive timer */ + priv->timer.expires = 0; + + if (priv->state != STATE_UNINITIALIZED) { + + /* count number of restarts */ + priv->can_stats.restarts++; + + chipset_init(dev, 1); + } +} +#endif + +#if 0 +/* the timerless version */ + +static void can_restart_now(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + if (priv->state != STATE_UNINITIALIZED) { + + /* count number of restarts */ + priv->can_stats.restarts++; + + chipset_init(dev, 1); + } +} +#endif + +/* + * Subroutine of ISR for RX interrupts. + * + */ +static void can_rx(struct net_device *dev, int obj) +{ + struct net_device_stats *stats = &dev->stats; + unsigned long base = dev->base_addr; + struct can_frame *cf; + struct sk_buff *skb; + uint8_t msgctlreg; + uint8_t ctl1reg; + canid_t id; + uint8_t dlc; + int i; + int rxo = obj2rxo(obj); + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb == NULL) { + return; + } + skb->dev = dev; + skb->protocol = htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + + ctl1reg = CANin(base, msgArr[rxo].messageReg.msgCtrl1Reg); + msgctlreg = CANin(base, msgArr[rxo].messageReg.messageConfigReg); + + if( msgctlreg & MCFG_XTD ) { + id = CANin(base, msgArr[rxo].messageReg.idReg[3]) + | (CANin(base, msgArr[rxo].messageReg.idReg[2]) << 8) + | (CANin(base, msgArr[rxo].messageReg.idReg[1]) << 16) + | (CANin(base, msgArr[rxo].messageReg.idReg[0]) << 24); + id >>= 3; + id |= CAN_EFF_FLAG; + } else { + id = CANin(base, msgArr[rxo].messageReg.idReg[1]) + |(CANin(base, msgArr[rxo].messageReg.idReg[0]) << 8); + id >>= 5; + } + + if (ctl1reg & RMPD_SET) { + id |= CAN_RTR_FLAG; + } + + msgctlreg &= 0xf0;/* strip length code */ + dlc = msgctlreg >> 4; + dlc %= 9; /* limit count to 8 bytes */ + + cf = (struct can_frame*)skb_put(skb, sizeof(struct can_frame)); + memset(cf, 0, sizeof(struct can_frame)); + cf->can_id = id; + cf->can_dlc = dlc; + for (i = 0; i < dlc; i++) { + cf->data[i] = CANin(base, msgArr[rxo].messageReg.dataReg[i]); + } + + // Make the chip ready to receive the next message + enable_rx_obj(base, obj); + + netif_rx(skb); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += dlc; +} + +/* + * I82527 interrupt handler + */ +static irqreturn_t can_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device*)dev_id; + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + unsigned long base = dev->base_addr; + uint8_t irqreg; + uint8_t lastIrqreg; + int n = 0; + + hw_preirq(dev); + + iiDBG(KERN_INFO "%s: interrupt\n", dev->name); + + if (priv->state == STATE_UNINITIALIZED) { + printk(KERN_ERR "%s: %s: uninitialized controller!\n", + dev->name, __FUNCTION__); + //chipset_init(dev, 1); /* should be possible at this stage */ + return IRQ_NONE; + } + + if (priv->state == STATE_RESET_MODE) { + iiDBG(KERN_ERR "%s: %s: controller is in reset mode!\n", + dev->name, __FUNCTION__); + return IRQ_NONE; + } + + + // Read the highest pending interrupt request + irqreg = CANin(base, interruptReg); + lastIrqreg = irqreg; + + while ( irqreg ) { + n++; + switch (irqreg) { + + case 1: // Status register + { + uint8_t status; + + // Read the STATUS reg + status = CANin(base, statusReg); + CANout (base, statusReg, 0); + + if ( status & iSTAT_RXOK ) { + // Intel: Software must clear this bit in ISR + CANout (base, statusReg, status & ~iSTAT_RXOK); + } + if ( status & iSTAT_TXOK ) { + // Intel: Software must clear this bit in ISR + CANout (base, statusReg, status & ~iSTAT_TXOK); + } + if ( status & iSTAT_WARN ) { + // Note: status bit is read-only, don't clear + /* error warning interrupt */ + iDBG(KERN_INFO "%s: error warning\n", + dev->name); + priv->can_stats.error_warning++; + } + if ( status & iSTAT_BOFF ) { + uint8_t flags; + + // Note: status bit is read-only, don't clear + + priv->can_stats.bus_error++; + + // Clear init flag and reenable interrupts + flags = CANin(base, controlReg) | + ( iCTL_IE | iCTL_EIE ); + + flags &= ~iCTL_INI; // Reset init flag + CANout(base, controlReg, flags); + } + } + break; + + case 0x2: // Receiption, message object 15 + { + uint8_t ctl1reg; + + ctl1reg = CANin(base, message15Reg.msgCtrl1Reg); + while (ctl1reg & NEWD_SET) { + if (ctl1reg & MLST_SET) + priv->can_stats.data_overrun++; + + if (priv->mo15 == MO15_SFF) + can_rx(dev, 4); /* rx via obj15 SFF */ + else + can_rx(dev, 5); /* rx via obj15 EFF */ + + ctl1reg = CANin(base, message15Reg.msgCtrl1Reg); + } + + if (priv->state == STATE_PROBE) { + /* valid RX -> switch to trx-mode */ + chipset_init_trx(dev); /* no tx queue wakeup */ + break; /* check again after init controller */ + } + } + break; + + case 0xC: // Receiption, message object 10 + case 0xD: // Receiption, message object 11 + { + int obj = irqreg - 0xC; + int rxo = obj2rxo(obj); + uint8_t ctl1reg; + ctl1reg = CANin(base, msgArr[rxo].messageReg.msgCtrl1Reg); + while (ctl1reg & NEWD_SET) { + if (ctl1reg & MLST_SET) + priv->can_stats.data_overrun++; + CANout(base, msgArr[rxo].messageReg.msgCtrl1Reg, + NEWD_RES | MLST_RES | TXRQ_UNC | RMPD_UNC); + can_rx(dev, obj); + ctl1reg = CANin(base, + msgArr[rxo].messageReg.msgCtrl1Reg); + } + + if (priv->state == STATE_PROBE) { + /* valid RX -> switch to trx-mode */ + chipset_init_trx(dev); /* no tx queue wakeup */ + break; /* check again after init controller */ + } + } + break; + + case 0xE: // Receiption, message object 12 (RTR) + case 0xF: // Receiption, message object 13 (RTR) + { + int obj = irqreg - 0xC; + int rxo = obj2rxo(obj); + uint8_t ctl0reg; + ctl0reg = CANin(base, msgArr[rxo].messageReg.msgCtrl0Reg); + while (ctl0reg & INTPD_SET) { + can_rx(dev, obj); + ctl0reg = CANin(base, msgArr[rxo].messageReg.msgCtrl0Reg); + } + + if (priv->state == STATE_PROBE) { + /* valid RX -> switch to trx-mode */ + chipset_init_trx(dev); /* no tx queue wakeup */ + break; /* check again after init controller */ + } + } + break; + + case 3: // Message object 1 (our write object) + /* transmission complete interrupt */ + + // Nothing more to send, switch off interrupts + CANout(base, message1Reg.msgCtrl0Reg, + (MVAL_RES | TXIE_RES | RXIE_RES | INTPD_RES)); + // We had some cases of repeated IRQ + // so make sure the INT is acknowledged + CANout(base, message1Reg.msgCtrl0Reg, + (MVAL_UNC | TXIE_UNC | RXIE_UNC | INTPD_RES)); + + stats->tx_packets++; + netif_wake_queue(dev); + break; + + default: // Unexpected + iDBG(KERN_INFO "%s: Unexpected i82527 interrupt: " + "irqreq=0x%X\n", dev->name, irqreg); + break; + } + + // Get irq status again for next loop iteration + irqreg = CANin(base, interruptReg); + if (irqreg == lastIrqreg) + iDBG(KERN_INFO "%s: i82527 interrupt repeated: " + "irqreq=0x%X\n", dev->name, irqreg); + + lastIrqreg = irqreg; + } /* end while (irqreq) */ + + if (n > 1) { + iDBG(KERN_INFO "%s: handled %d IRQs\n", dev->name, n); + } + + hw_postirq(dev); + + return n == 0 ? IRQ_NONE : IRQ_HANDLED; +} + +/* + * initialize CAN bus driver + */ +static int can_open(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + /* set chip into reset mode */ + set_reset_mode(dev); + + priv->state = STATE_UNINITIALIZED; + + /* register interrupt handler */ + if (request_irq(dev->irq, &can_interrupt, irqflags, + dev->name, (void*)dev)) + return -EAGAIN; + + /* init chip */ + chipset_init(dev, 0); + priv->open_time = jiffies; + + netif_start_queue(dev); + + return 0; +} + +/* + * stop CAN bus activity + */ +static int can_close(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + /* set chip into reset mode */ + set_reset_mode(dev); + + priv->open_time = 0; + + if (priv->timer.expires) { + del_timer(&priv->timer); + priv->timer.expires = 0; + } + + free_irq(dev->irq, (void*)dev); + priv->state = STATE_UNINITIALIZED; + + netif_stop_queue(dev); + + return 0; +} + +void can_netdev_setup(struct net_device *dev) +{ + /* Fill in the the fields of the device structure + with CAN netdev generic values */ + + dev->change_mtu = NULL; + dev->set_mac_address = NULL; + dev->header_ops = NULL; + + dev->type = ARPHRD_CAN; + dev->hard_header_len = 0; + dev->mtu = sizeof(struct can_frame); + dev->addr_len = 0; + dev->tx_queue_len = 10; + + dev->flags = IFF_NOARP; + dev->features = NETIF_F_NO_CSUM; + + dev->open = can_open; + dev->stop = can_close; + dev->hard_start_xmit = can_start_xmit; + + dev->tx_timeout = can_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +} + +static struct net_device* can_create_netdev(int dev_num, int hw_regs) +{ + struct net_device *dev; + struct can_priv *priv; + + const char mo15mode [3][6] = {"none", "sff", "eff"}; + + if (!(dev = alloc_netdev(sizeof(struct can_priv), CAN_NETDEV_NAME, + can_netdev_setup))) { + printk(KERN_ERR "%s: out of memory\n", CHIP_NAME); + return NULL; + } + + printk(KERN_INFO "%s: base 0x%lX / irq %d / speed %d / " + "btr 0x%X / rx_probe %d / mo15 %s\n", + drv_name, rbase[dev_num], irq[dev_num], + speed[dev_num], btr[dev_num], rx_probe[dev_num], + mo15mode[mo15[dev_num]]); + + /* fill net_device structure */ + + priv = netdev_priv(dev); + + dev->irq = irq[dev_num]; + dev->base_addr = rbase[dev_num]; + + priv->speed = speed[dev_num]; + priv->btr = btr[dev_num]; + priv->rx_probe = rx_probe[dev_num]; + priv->mo15 = mo15[dev_num]; + priv->clock = clk; + priv->hw_regs = hw_regs; + priv->restart_ms = restart_ms; + priv->debug = debug; + + init_timer(&priv->timer); + priv->timer.expires = 0; + + if (register_netdev(dev)) { + printk(KERN_INFO "%s: register netdev failed\n", CHIP_NAME); + free_netdev(dev); + return NULL; + } + + return dev; +} + +int can_set_drv_name(void) +{ + char *hname = hal_name(); + + if (strlen(CHIP_NAME) + strlen(hname) >= DRV_NAME_LEN-1) { + printk(KERN_ERR "%s: driver name too long!\n", CHIP_NAME); + return -EINVAL; + } + sprintf(drv_name, "%s-%s", CHIP_NAME, hname); + return 0; +} + +static void i82527_exit_module(void) +{ + int i, ret; + + for (i = 0; i < MAXDEV; i++) { + if (can_dev[i] != NULL) { + struct can_priv *priv = netdev_priv(can_dev[i]); + unregister_netdev(can_dev[i]); + del_timer(&priv->timer); + hw_detach(i); + hal_release_region(i, I82527_IO_SIZE); + free_netdev(can_dev[i]); + } + } + can_proc_remove(drv_name); + + if ((ret = hal_exit())) + printk(KERN_INFO "%s: hal_exit error %d.\n", drv_name, ret); +} + +static __init int i82527_init_module(void) +{ + int i, ret; + struct net_device *dev; + + if ((sizeof(canmessage_t) != 15) || (sizeof(canregs_t) != 256)) { + printk(KERN_WARNING "%s sizes: canmessage_t %d canregs_t %d\n", + CHIP_NAME, (int)sizeof(canmessage_t), + (int)sizeof(canregs_t)); + return -EBUSY; + } + + if ((ret = hal_init())) + return ret; + + if ((ret = can_set_drv_name())) + return ret; + + if (clk < 1000 ) /* MHz command line value */ + clk *= 1000000; + + if (clk < 1000000 ) /* kHz command line value */ + clk *= 1000; + + printk(KERN_INFO "%s driver v%s (%s)\n", + drv_name, drv_version, drv_reldate); + printk(KERN_INFO "%s - options [clk %d.%06d MHz] [restart_ms %dms]" + " [debug %d]\n", + drv_name, clk/1000000, clk%1000000, restart_ms, debug); + printk(KERN_INFO "%s - options [force_dmc %d] [irq_mode %d]\n", + drv_name, force_dmc, irq_mode); + + if (!base[0]) { + printk(KERN_INFO "%s: loading defaults.\n", drv_name); + hal_use_defaults(); + } + + /* to ensure the proper access to the i82527 registers */ + /* the timing dependend settings have to be done first */ + if (clk > 10000000) + dsc = iCPU_DSC; /* devide system clock => MCLK is 8MHz save */ + else if (clk > 8000000) /* 8MHz < clk <= 10MHz */ + dmc = iCPU_DMC; /* devide memory clock */ + + /* devide memory clock even if it's not needed (regarding the spec) */ + if (force_dmc) + dmc = iCPU_DMC; + + if (irq_mode & IRQ_MODE_SHARED) + irqflags |= IRQF_SHARED; + if (irq_mode & IRQ_MODE_DISABLE_LOCAL_IRQS) + irqflags |= IRQF_DISABLED; + + for (i = 0; base[i]; i++) { + int clkout; + u8 clockdiv; + + printk(KERN_DEBUG "%s: checking for %s on address 0x%lX ...\n", + drv_name, CHIP_NAME, base[i]); + + if (!hal_request_region(i, I82527_IO_SIZE, drv_name)) { + printk(KERN_ERR "%s: memory already in use\n", + drv_name); + i82527_exit_module(); + return -EBUSY; + } + + hw_attach(i); + hw_reset_dev(i); + + // Enable configuration, put chip in bus-off, disable ints + CANout(rbase[i], controlReg, iCTL_CCE | iCTL_INI); + + // Configure cpu interface / CLKOUT disable + CANout(rbase[i], cpuInterfaceReg,(dsc | dmc)); + + if (!i82527_probe_chip(rbase[i])) { + printk(KERN_ERR "%s: probably missing controller" + " hardware\n", drv_name); + hw_detach(i); + hal_release_region(i, I82527_IO_SIZE); + i82527_exit_module(); + return -ENODEV; + } + + /* CLKOUT devider and slew rate calculation */ + if ((cdv[i] < 0) || (cdv[i] > 14)) { + printk(KERN_WARNING "%s: adjusted cdv[%d]=%d to 0.\n", + drv_name, i, cdv[i]); + cdv[i] = 0; + } + + clkout = clk / (cdv[i] + 1); /* CLKOUT frequency */ + clockdiv = (u8)cdv[i]; /* devider value (see i82527 spec) */ + + if (clkout <= 16000000) { + clockdiv |= iCLK_SL1; + if (clkout <= 8000000) + clockdiv |= iCLK_SL0; + } else if (clkout <= 24000000) + clockdiv |= iCLK_SL0; + + // Set CLKOUT devider and slew rates + CANout(rbase[i], clkOutReg, clockdiv); + + // Configure cpu interface / CLKOUT enable + CANout(rbase[i], cpuInterfaceReg,(dsc | dmc | iCPU_CEN)); + + CANout(rbase[i], busConfigReg, bcr[i]); + + dev = can_create_netdev(i, I82527_IO_SIZE); + + if (dev != NULL) { + can_dev[i] = dev; + set_reset_mode(dev); + can_proc_create(drv_name); + } else { + can_dev[i] = NULL; + hw_detach(i); + hal_release_region(i, I82527_IO_SIZE); + } + } + return 0; +} + +module_init(i82527_init_module); +module_exit(i82527_exit_module); + diff --git a/drivers/net/can/old/i82527/i82527.h b/drivers/net/can/old/i82527/i82527.h new file mode 100644 index 000000000000..0484b21406f9 --- /dev/null +++ b/drivers/net/can/old/i82527/i82527.h @@ -0,0 +1,300 @@ +/* + * i82527.h - Intel I82527 network device driver + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Original version Written by Arnaud Westenberg email:arnaud@wanadoo.nl + * This software is released under the GPL-License. + * + * Major Refactoring and Integration into can4linux version 3.1 by + * Henrik W Maier of FOCUS Software Engineering Pty Ltd <www.focus-sw.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#ifndef I82527_H +#define I82527_H + +#define I82527_IO_SIZE 0x100 + +#define CHIP_NAME "i82527" + +#define DRV_NAME_LEN 30 /* for "<chip_name>-<hal_name>" */ + +#define PROCBASE "driver" /* /proc/ ... */ + +#define DEFAULT_HW_CLK 16000000 +#define DEFAULT_SPEED 500 /* kBit/s */ +#define DEFAULT_FORCE_DMC 0 /* for critical register access, e.g. ser1274 */ + +#define IRQ_MODE_SHARED 1 /* enable shared interrupts */ +#define IRQ_MODE_DISABLE_LOCAL_IRQS 2 /* when processing the irq handler */ +#define DEFAULT_IRQ_MODE IRQ_MODE_SHARED + +/* The message object 15 has a shadow register for reliable data receiption */ +/* under heavy bus load. Therefore it makes sense to use this message object */ +/* (mo15) for the needed use case. The frame type (EFF/SFF) for the mo15 can */ +/* be defined on the module command line. The default is 11 bit SFF format. */ + +#define MO15_NONE 0 +#define MO15_SFF 1 +#define MO15_EFF 2 + +#define MO15_DEFLT MO15_SFF /* the default */ + +#define CAN_NETDEV_NAME "can%d" + +#define TX_TIMEOUT (50*HZ/1000) /* 50ms */ +#define RESTART_MS 100 /* restart chip on persistent errors in 100ms */ +#define MAX_BUS_ERRORS 200 /* prevent from flooding bus error interrupts */ + +/* bus timing */ +#define MAX_TSEG1 15 +#define MAX_TSEG2 7 +#define SAMPLE_POINT 62 +#define JUMPWIDTH 0x40 + +typedef struct canmessage { + uint8_t msgCtrl0Reg; + uint8_t msgCtrl1Reg; + uint8_t idReg[4]; + uint8_t messageConfigReg; + uint8_t dataReg[8]; +} canmessage_t; // __attribute__ ((packed)); + +typedef struct canregs { + union + { + struct + { + canmessage_t messageReg; + uint8_t someOtherReg; // padding + } msgArr[16]; + struct + { + uint8_t controlReg; // Control Register + uint8_t statusReg; // Status Register + uint8_t cpuInterfaceReg; // CPU Interface Register + uint8_t reserved1Reg; + uint8_t highSpeedReadReg[2]; // High Speed Read + uint8_t globalMaskStandardReg[2]; // Standard Global Mask byte 0 + uint8_t globalMaskExtendedReg[4]; // Extended Global Mask bytes + uint8_t message15MaskReg[4]; // Message 15 Mask bytes + canmessage_t message1Reg; + uint8_t clkOutReg; // Clock Out Register + canmessage_t message2Reg; + uint8_t busConfigReg; // Bus Configuration Register + canmessage_t message3Reg; + uint8_t bitTiming0Reg; // Bit Timing Register byte 0 + canmessage_t message4Reg; + uint8_t bitTiming1Reg; // Bit Timing Register byte 1 + canmessage_t message5Reg; + uint8_t interruptReg; // Interrupt Register + canmessage_t message6Reg; + uint8_t reserved2Reg; + canmessage_t message7Reg; + uint8_t reserved3Reg; + canmessage_t message8Reg; + uint8_t reserved4Reg; + canmessage_t message9Reg; + uint8_t p1ConfReg; + canmessage_t message10Reg; + uint8_t p2ConfReg; + canmessage_t message11Reg; + uint8_t p1InReg; + canmessage_t message12Reg; + uint8_t p2InReg; + canmessage_t message13Reg; + uint8_t p1OutReg; + canmessage_t message14Reg; + uint8_t p2OutReg; + canmessage_t message15Reg; + uint8_t serialResetAddressReg; + }; + }; +} canregs_t; // __attribute__ ((packed)); + +/* Control Register (0x00) */ +enum i82527_iCTL { + iCTL_INI = 1, // Initialization + iCTL_IE = 1<<1, // Interrupt Enable + iCTL_SIE = 1<<2, // Status Interrupt Enable + iCTL_EIE = 1<<3, // Error Interrupt Enable + iCTL_CCE = 1<<6 // Change Configuration Enable +}; + +/* Status Register (0x01) */ +enum i82527_iSTAT { + iSTAT_TXOK = 1<<3, // Transmit Message Successfully + iSTAT_RXOK = 1<<4, // Receive Message Successfully + iSTAT_WAKE = 1<<5, // Wake Up Status + iSTAT_WARN = 1<<6, // Warning Status + iSTAT_BOFF = 1<<7 // Bus Off Status +}; + +/* CPU Interface Register (0x02) */ +enum i82527_iCPU { + iCPU_CEN = 1, // Clock Out Enable + iCPU_MUX = 1<<2, // Multiplex + iCPU_SLP = 1<<3, // Sleep + iCPU_PWD = 1<<4, // Power Down Mode + iCPU_DMC = 1<<5, // Divide Memory Clock + iCPU_DSC = 1<<6, // Divide System Clock + iCPU_RST = 1<<7, // Hardware Reset Status +}; + +/* Clock Out Register (0x1f) */ +enum i82527_iCLK { + iCLK_CD0 = 1, // Clock Divider bit 0 + iCLK_CD1 = 1<<1, + iCLK_CD2 = 1<<2, + iCLK_CD3 = 1<<3, + iCLK_SL0 = 1<<4, // Slew Rate bit 0 + iCLK_SL1 = 1<<5 +}; + +/* Bus Configuration Register (0x2f) */ +enum i82527_iBUS { + iBUS_DR0 = 1, // Disconnect RX0 Input + iBUS_DR1 = 1<<1, // Disconnect RX1 Input + iBUS_DT1 = 1<<3, // Disconnect TX1 Output + iBUS_POL = 1<<5, // Polarity + iBUS_CBY = 1<<6 // Comparator Bypass +}; + +#define RESET 1 // Bit Pair Reset Status +#define SET 2 // Bit Pair Set Status +#define UNCHANGED 3 // Bit Pair Unchanged + +/* Message Control Register 0 (Base Address + 0x0) */ +enum i82527_iMSGCTL0 { + INTPD_SET = SET, // Interrupt pending + INTPD_RES = RESET, // No Interrupt pending + INTPD_UNC = UNCHANGED, + RXIE_SET = SET<<2, // Receive Interrupt Enable + RXIE_RES = RESET<<2, // Receive Interrupt Disable + RXIE_UNC = UNCHANGED<<2, + TXIE_SET = SET<<4, // Transmit Interrupt Enable + TXIE_RES = RESET<<4, // Transmit Interrupt Disable + TXIE_UNC = UNCHANGED<<4, + MVAL_SET = SET<<6, // Message Valid + MVAL_RES = RESET<<6, // Message Invalid + MVAL_UNC = UNCHANGED<<6 +}; + +/* Message Control Register 1 (Base Address + 0x01) */ +enum i82527_iMSGCTL1 { + NEWD_SET = SET, // New Data + NEWD_RES = RESET, // No New Data + NEWD_UNC = UNCHANGED, + MLST_SET = SET<<2, // Message Lost + MLST_RES = RESET<<2, // No Message Lost + MLST_UNC = UNCHANGED<<2, + CPUU_SET = SET<<2, // CPU Updating + CPUU_RES = RESET<<2, // No CPU Updating + CPUU_UNC = UNCHANGED<<2, + TXRQ_SET = SET<<4, // Transmission Request + TXRQ_RES = RESET<<4, // No Transmission Request + TXRQ_UNC = UNCHANGED<<4, + RMPD_SET = SET<<6, // Remote Request Pending + RMPD_RES = RESET<<6, // No Remote Request Pending + RMPD_UNC = UNCHANGED<<6 +}; + +/* Message Configuration Register (Base Address + 0x06) */ +enum i82527_iMSGCFG { + MCFG_XTD = 1<<2, // Extended Identifier + MCFG_DIR = 1<<3 // Direction is Transmit +}; + +#undef IOPRINT +#undef IODEBUG + +#ifdef IOPRINT +#define CANout(base,adr,v) \ + printk("CANout: (%lx+%x)=%x\n", base,\ + (int)(long)&((canregs_t *)0)->adr,v) + +#define CANin(base,adr) \ + printk("CANin: (%lx+%x)\n", base, (int)(long)&((canregs_t *)0)->adr) + +#else /* IOPRINT */ + +#ifdef IODEBUG +#define CANout(base,adr,v) \ + (printk("CANout: (%lx+%x)=%x\n", base,\ + (int)(long)&((canregs_t *)0)->adr,v),\ + hw_writereg(base, (int)(long)&((canregs_t *)0)->adr, v)) +#else +#define CANout(base,adr,v) hw_writereg(base,\ + (int)(long)&((canregs_t *)0)->adr, v) +#endif + +#define CANin(base,adr) hw_readreg(base, (int)(long)&((canregs_t *)0)->adr) + +#endif /* IOPRINT */ + +/* CAN private data structure */ + +struct can_priv { + struct can_device_stats can_stats; + long open_time; + int clock; + int hw_regs; + int restart_ms; + int debug; + int speed; + int btr; + int rx_probe; + int mo15; + struct timer_list timer; + int state; +}; + +#define STATE_UNINITIALIZED 0 +#define STATE_PROBE 1 +#define STATE_ACTIVE 2 +#define STATE_ERROR_ACTIVE 3 +#define STATE_ERROR_PASSIVE 4 +#define STATE_BUS_OFF 5 +#define STATE_RESET_MODE 6 + +void can_proc_create(const char *drv_name); +void can_proc_remove(const char *drv_name); + +#endif /* I82527_H */ diff --git a/drivers/net/can/old/i82527/proc.c b/drivers/net/can/old/i82527/proc.c new file mode 100644 index 000000000000..e11628fe387c --- /dev/null +++ b/drivers/net/can/old/i82527/proc.c @@ -0,0 +1,209 @@ +/* + * proc.c - proc file system functions for I82527 CAN driver. + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/netdevice.h> + +#include <linux/can.h> +#include <linux/can/ioctl.h> +#include "i82527.h" +#include "hal.h" + +extern struct net_device *can_dev[]; + +static struct proc_dir_entry *pde = NULL; +static struct proc_dir_entry *pde_regs = NULL; +static struct proc_dir_entry *pde_reset = NULL; + +static int can_proc_read_stats(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + int i; + + len += snprintf(page + len, PAGE_SIZE - len, + "CAN bus device statistics:\n"); + len += snprintf(page + len, PAGE_SIZE - len, + " errwarn overrun wakeup buserr " + "errpass arbitr restarts clock baud\n"); + for (i = 0; (i < MAXDEV) && (len < PAGE_SIZE - 200); i++) { + if (can_dev[i]) { + struct net_device *dev = can_dev[i]; + struct can_priv *priv = netdev_priv(dev); + len += snprintf(page + len, PAGE_SIZE - len, + "%s: %8d %8d %8d %8d %8d " + "%8d %8d %10d %8d\n", dev->name, + priv->can_stats.error_warning, + priv->can_stats.data_overrun, + priv->can_stats.wakeup, + priv->can_stats.bus_error, + priv->can_stats.error_passive, + priv->can_stats.arbitration_lost, + priv->can_stats.restarts, + priv->clock, + priv->speed + ); + + } + } + + *eof = 1; + return len; +} + + +static int can_proc_dump_regs(char *page, int len, struct net_device *dev) +{ + int r,s; + struct can_priv *priv = netdev_priv(dev); + int regs = priv->hw_regs; + + len += snprintf(page + len, PAGE_SIZE - len, + "%s registers:\n", dev->name); + + for (r = 0; r < regs; r += 0x10) { + len += snprintf(page + len, PAGE_SIZE - len, "%02X: ", r); + for (s = 0; s < 0x10; s++) { + if (r+s < regs) + len += snprintf(page + len, PAGE_SIZE-len, + "%02X ", + hw_readreg(dev->base_addr, + r+s)); + } + len += snprintf(page + len, PAGE_SIZE - len, "\n"); + } + + return len; +} + +static int can_proc_read_regs(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + int i; + + for (i = 0; (i < MAXDEV) && (len < PAGE_SIZE - 200); i++) { + if (can_dev[i]) + len = can_proc_dump_regs(page, len, can_dev[i]); + } + + *eof = 1; + return len; +} + +static int can_proc_read_reset(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + struct net_device *dev; + int i; + struct can_priv *priv; + + len += snprintf(page + len, PAGE_SIZE - len, "resetting "); + for (i = 0; (i < MAXDEV) && (len < PAGE_SIZE - 200); i++) { + if (can_dev[i]) { + dev = can_dev[i]; + priv = netdev_priv(can_dev[i]); + if ((priv->state != STATE_UNINITIALIZED) + && (priv->state != STATE_RESET_MODE)) { + len += snprintf(page + len, PAGE_SIZE - len, + "%s ", dev->name); + dev->stop(dev); + dev->open(dev); + /* count number of restarts */ + priv->can_stats.restarts++; + + } else { + len += snprintf(page + len, PAGE_SIZE - len, + "(%s|%d) ", dev->name, + priv->state); + } + } + } + + len += snprintf(page + len, PAGE_SIZE - len, "done\n"); + + *eof = 1; + return len; +} + +void can_proc_create(const char *drv_name) +{ + char fname[256]; + + if (pde == NULL) { + sprintf(fname, PROCBASE "/%s_stats", drv_name); + pde = create_proc_read_entry(fname, 0644, NULL, + can_proc_read_stats, NULL); + } + if (pde_regs == NULL) { + sprintf(fname, PROCBASE "/%s_regs", drv_name); + pde_regs = create_proc_read_entry(fname, 0644, NULL, + can_proc_read_regs, NULL); + } + if (pde_reset == NULL) { + sprintf(fname, PROCBASE "/%s_reset", drv_name); + pde_reset = create_proc_read_entry(fname, 0644, NULL, + can_proc_read_reset, NULL); + } +} + +void can_proc_remove(const char *drv_name) +{ + char fname[256]; + + if (pde) { + sprintf(fname, PROCBASE "/%s_stats", drv_name); + remove_proc_entry(fname, NULL); + } + if (pde_regs) { + sprintf(fname, PROCBASE "/%s_regs", drv_name); + remove_proc_entry(fname, NULL); + } + if (pde_reset) { + sprintf(fname, PROCBASE "/%s_reset", drv_name); + remove_proc_entry(fname, NULL); + } +} diff --git a/drivers/net/can/old/mscan/Makefile b/drivers/net/can/old/mscan/Makefile new file mode 100644 index 000000000000..854012e9174d --- /dev/null +++ b/drivers/net/can/old/mscan/Makefile @@ -0,0 +1,21 @@ +# +# + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common + +obj-$(CONFIG_CAN_MPC52XX_OLD) += mscan-mpc52xx-old.o + +mscan-mpc52xx-old-objs := mscan.o mpc52xx_can.o + +endif diff --git a/drivers/net/can/old/mscan/mpc52xx_can.c b/drivers/net/can/old/mscan/mpc52xx_can.c new file mode 100644 index 000000000000..da1278fa630a --- /dev/null +++ b/drivers/net/can/old/mscan/mpc52xx_can.c @@ -0,0 +1,248 @@ +/* + * DESCRIPTION: + * CAN bus driver for the Freescale MPC52xx embedded CPU. + * + * AUTHOR: + * Andrey Volkov <avolkov@varma-el.com> + * + * COPYRIGHT: + * 2004-2005, Varma Electronics Oy + * + * LICENCE: + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * HISTORY: + * 2005-02-03 created + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/netdevice.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <asm/io.h> +#include <asm/mpc52xx.h> + +#include "mscan.h" + + +#define PDEV_MAX 2 + +struct platform_device *pdev[PDEV_MAX]; + +static int __devinit mpc52xx_can_probe(struct platform_device *pdev) +{ + struct resource *mem; + struct net_device *dev; + struct mscan_platform_data *pdata = pdev->dev.platform_data; + struct can_priv *can; + u32 mem_size; + int ret = -ENODEV; + + if (!pdata) + return ret; + + dev = alloc_mscandev(); + if (!dev) + return -ENOMEM; + can = netdev_priv(dev); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->irq = platform_get_irq(pdev, 0); + if (!mem || !dev->irq) + goto req_error; + + mem_size = mem->end - mem->start + 1; + if (!request_mem_region(mem->start, mem_size, pdev->dev.driver->name)) { + dev_err(&pdev->dev, "resource unavailable\n"); + goto req_error; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + dev->base_addr = (unsigned long)ioremap_nocache(mem->start, mem_size); + + if (!dev->base_addr) { + dev_err(&pdev->dev, "failed to map can port\n"); + ret = -ENOMEM; + goto fail_map; + } + + can->can_sys_clock = pdata->clock_frq; + + platform_set_drvdata(pdev, dev); + + ret = register_mscandev(dev, pdata->clock_src); + if (ret >= 0) { + dev_info(&pdev->dev, "probe for a port 0x%lX done\n", + dev->base_addr); + return ret; + } + + iounmap((unsigned long *)dev->base_addr); + fail_map: + release_mem_region(mem->start, mem_size); + req_error: + free_candev(dev); + dev_err(&pdev->dev, "probe failed\n"); + return ret; +} + +static int __devexit mpc52xx_can_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct resource *mem; + + platform_set_drvdata(pdev, NULL); + unregister_mscandev(dev); + + iounmap((volatile void __iomem *)dev->base_addr); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, mem->end - mem->start + 1); + free_candev(dev); + return 0; +} + +#ifdef CONFIG_PM +static struct mscan_regs saved_regs; +static int mpc52xx_can_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + _memcpy_fromio(&saved_regs, regs, sizeof(*regs)); + + return 0; +} + +static int mpc52xx_can_resume(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + regs->canctl0 |= MSCAN_INITRQ; + while ((regs->canctl1 & MSCAN_INITAK) == 0) + udelay(10); + + regs->canctl1 = saved_regs.canctl1; + regs->canbtr0 = saved_regs.canbtr0; + regs->canbtr1 = saved_regs.canbtr1; + regs->canidac = saved_regs.canidac; + + /* restore masks, buffers etc. */ + _memcpy_toio(®s->canidar1_0, (void *)&saved_regs.canidar1_0, + sizeof(*regs) - offsetof(struct mscan_regs, canidar1_0)); + + regs->canctl0 &= ~MSCAN_INITRQ; + regs->cantbsel = saved_regs.cantbsel; + regs->canrier = saved_regs.canrier; + regs->cantier = saved_regs.cantier; + regs->canctl0 = saved_regs.canctl0; + + return 0; +} +#endif + +static struct platform_driver mpc52xx_can_driver = { + .driver = { + .name = "mpc52xx-mscan", + }, + .probe = mpc52xx_can_probe, + .remove = __devexit_p(mpc52xx_can_remove), +#ifdef CONFIG_PM + .suspend = mpc52xx_can_suspend, + .resume = mpc52xx_can_resume, +#endif +}; + +#ifdef CONFIG_PPC_MERGE +static int __init mpc52xx_of_to_pdev(void) +{ + struct device_node *np = NULL; + unsigned int i; + int err = -ENODEV; + + for (i = 0; + (np = of_find_compatible_node(np, NULL, "fsl,mpc5200-mscan")); + i++) { + struct resource r[2] = { }; + struct mscan_platform_data pdata; + + if (i >= PDEV_MAX) { + printk(KERN_WARNING "%s: increase PDEV_MAX for more " + "than %i devices\n", __func__, PDEV_MAX); + break; + } + + err = of_address_to_resource(np, 0, &r[0]); + if (err) + break; + + of_irq_to_resource(np, 0, &r[1]); + + pdev[i] = + platform_device_register_simple("mpc52xx-mscan", i, r, 2); + if (IS_ERR(pdev[i])) { + err = PTR_ERR(pdev[i]); + break; + } + + pdata.clock_src = MSCAN_CLKSRC_BUS; + pdata.clock_frq = mpc52xx_find_ipb_freq(np); + err = platform_device_add_data(pdev[i], &pdata, sizeof(pdata)); + if (err) + break; + } + return err; +} +#else +#define mscan_of_to_pdev() +#endif + +int __init mpc52xx_can_init(void) +{ + printk(KERN_WARNING + "This %s driver is DEPRECATED, please switch!\n", + mpc52xx_can_driver.driver.name); +#ifdef CONFIG_PPC_MERGE + int err = mpc52xx_of_to_pdev(); + + if (err) { + printk(KERN_ERR "%s init failed with err=%d\n", + mpc52xx_can_driver.driver.name, err); + return err; + } +#endif + return platform_driver_register(&mpc52xx_can_driver); +} + +void __exit mpc52xx_can_exit(void) +{ + int i; + platform_driver_unregister(&mpc52xx_can_driver); + for (i = 0; i < PDEV_MAX; i++) + platform_device_unregister(pdev[i]); + printk(KERN_INFO "%s unloaded\n", mpc52xx_can_driver.driver.name); +} + +module_init(mpc52xx_can_init); +module_exit(mpc52xx_can_exit); + +MODULE_AUTHOR("Andrey Volkov <avolkov@varma-el.com>"); +MODULE_DESCRIPTION("Freescale MPC5200 CAN driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/old/mscan/mscan.c b/drivers/net/can/old/mscan/mscan.c new file mode 100644 index 000000000000..05afeae1e2fe --- /dev/null +++ b/drivers/net/can/old/mscan/mscan.c @@ -0,0 +1,708 @@ +/* + * mscan.c + * + * DESCRIPTION: + * CAN bus driver for the alone generic (as possible as) MSCAN controller. + * + * AUTHOR: + * Andrey Volkov <avolkov@varma-el.com> + * + * COPYRIGHT: + * 2005-2006, Varma Electronics Oy + * + * LICENCE: + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/can.h> +#include <linux/list.h> +#include <asm/io.h> + +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include "mscan.h" + +#define MSCAN_NORMAL_MODE 0 +#define MSCAN_SLEEP_MODE MSCAN_SLPRQ +#define MSCAN_INIT_MODE (MSCAN_INITRQ | MSCAN_SLPRQ) +#define MSCAN_POWEROFF_MODE (MSCAN_CSWAI | MSCAN_SLPRQ) +#define MSCAN_SET_MODE_RETRIES 255 + + +#define BTR0_BRP_MASK 0x3f +#define BTR0_SJW_SHIFT 6 +#define BTR0_SJW_MASK (0x3 << BTR0_SJW_SHIFT) + +#define BTR1_TSEG1_MASK 0xf +#define BTR1_TSEG2_SHIFT 4 +#define BTR1_TSEG2_MASK (0x7 << BTR1_TSEG2_SHIFT) +#define BTR1_SAM_SHIFT 7 + +#define BTR0_SET_BRP(brp) (((brp) - 1) & BTR0_BRP_MASK) +#define BTR0_SET_SJW(sjw) ((((sjw) - 1) << BTR0_SJW_SHIFT) & \ + BTR0_SJW_MASK) + +#define BTR1_SET_TSEG1(tseg1) (((tseg1) - 1) & BTR1_TSEG1_MASK) +#define BTR1_SET_TSEG2(tseg2) ((((tseg2) - 1) << BTR1_TSEG2_SHIFT) & \ + BTR1_TSEG2_MASK) +#define BTR1_SET_SAM(sam) (((sam) & 1) << BTR1_SAM_SHIFT) + +struct mscan_state { + u8 mode; + u8 canrier; + u8 cantier; +}; + +#define TX_QUEUE_SIZE 3 + +typedef struct { + struct list_head list; + u8 mask; +} tx_queue_entry_t; + +struct mscan_priv { + struct can_priv can; + volatile unsigned long flags; + u8 shadow_statflg; + u8 shadow_canrier; + u8 cur_pri; + u8 tx_active; + + struct list_head tx_head; + tx_queue_entry_t tx_queue[TX_QUEUE_SIZE]; + struct napi_struct napi; + struct net_device *dev; +}; + +#define F_RX_PROGRESS 0 +#define F_TX_PROGRESS 1 +#define F_TX_WAIT_ALL 2 + +static int mscan_set_mode(struct net_device *dev, u8 mode) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + int ret = 0; + int i; + u8 canctl1; + + if (mode != MSCAN_NORMAL_MODE) { + canctl1 = in_8(®s->canctl1); + if ((mode & MSCAN_SLPRQ) && (canctl1 & MSCAN_SLPAK) == 0) { + out_8(®s->canctl0, + in_8(®s->canctl0) | MSCAN_SLPRQ); + for (i = 0; i < MSCAN_SET_MODE_RETRIES; i++) { + if (in_8(®s->canctl1) & MSCAN_SLPAK) + break; + udelay(100); + } + if (i >= MSCAN_SET_MODE_RETRIES) + ret = -ENODEV; + } + + if (!ret && (mode & MSCAN_INITRQ) + && (canctl1 & MSCAN_INITAK) == 0) { + out_8(®s->canctl0, + in_8(®s->canctl0) | MSCAN_INITRQ); + for (i = 0; i < MSCAN_SET_MODE_RETRIES; i++) { + if (in_8(®s->canctl1) & MSCAN_INITAK) + break; + } + if (i >= MSCAN_SET_MODE_RETRIES) + ret = -ENODEV; + } + + if (!ret && (mode & MSCAN_CSWAI)) + out_8(®s->canctl0, + in_8(®s->canctl0) | MSCAN_CSWAI); + + } else { + canctl1 = in_8(®s->canctl1); + if (canctl1 & (MSCAN_SLPAK | MSCAN_INITAK)) { + out_8(®s->canctl0, in_8(®s->canctl0) & + ~(MSCAN_SLPRQ | MSCAN_INITRQ)); + for (i = 0; i < MSCAN_SET_MODE_RETRIES; i++) { + canctl1 = in_8(®s->canctl1); + if (!(canctl1 & (MSCAN_INITAK | MSCAN_SLPAK))) + break; + } + if (i >= MSCAN_SET_MODE_RETRIES) + ret = -ENODEV; + } + } + return ret; +} + +static void mscan_push_state(struct net_device *dev, struct mscan_state *state) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + state->mode = in_8(®s->canctl0) & (MSCAN_SLPRQ | MSCAN_INITRQ | + MSCAN_CSWAI); + state->canrier = in_8(®s->canrier); + state->cantier = in_8(®s->cantier); +} + +static int mscan_pop_state(struct net_device *dev, struct mscan_state *state) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + int ret; + ret = mscan_set_mode(dev, state->mode); + if (!ret) { + out_8(®s->canrier, state->canrier); + out_8(®s->cantier, state->cantier); + } + return ret; +} + +static int mscan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct can_frame *frame = (struct can_frame *)skb->data; + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct mscan_priv *priv = netdev_priv(dev); + + int i, rtr, buf_id; + u32 can_id; + + if (frame->can_dlc > 8) + return -EINVAL; + + dev_dbg(ND2D(dev), "%s\n", __FUNCTION__); + out_8(®s->cantier, 0); + + i = ~priv->tx_active & MSCAN_TXE; + buf_id = ffs(i) - 1; + switch (hweight8(i)) { + case 0: + netif_stop_queue(dev); + dev_err(ND2D(dev), "BUG! Tx Ring full when queue awake!\n"); + return NETDEV_TX_BUSY; + case 1: + /* if buf_id < 3, then current frame will be send out of order, + since buffer with lower id have higher priority (hell..) */ + if (buf_id < 3) + priv->cur_pri++; + if (priv->cur_pri == 0xff) + set_bit(F_TX_WAIT_ALL, &priv->flags); + netif_stop_queue(dev); + case 2: + set_bit(F_TX_PROGRESS, &priv->flags); + } + out_8(®s->cantbsel, i); + + rtr = frame->can_id & CAN_RTR_FLAG; + + if (frame->can_id & CAN_EFF_FLAG) { + dev_dbg(ND2D(dev), "sending extended frame\n"); + + can_id = (frame->can_id & CAN_EFF_MASK) << 1; + if (rtr) + can_id |= 1; + out_be16(®s->tx.idr3_2, can_id); + + can_id >>= 16; + can_id = (can_id & 0x7) | ((can_id << 2) & 0xffe0) | (3 << 3); + } else { + dev_dbg(ND2D(dev), "sending standard frame\n"); + can_id = (frame->can_id & CAN_SFF_MASK) << 5; + if (rtr) + can_id |= 1 << 4; + } + out_be16(®s->tx.idr1_0, can_id); + + if (!rtr) { + volatile void __iomem *data = ®s->tx.dsr1_0; + u16 *payload = (u16 *) frame->data; + /*Its safe to write into dsr[dlc+1] */ + for (i = 0; i < (frame->can_dlc + 1) / 2; i++) { + out_be16(data, *payload++); + data += 2 + _MSCAN_RESERVED_DSR_SIZE; + } + } + + out_8(®s->tx.dlr, frame->can_dlc); + out_8(®s->tx.tbpr, priv->cur_pri); + + /* Start transmission. */ + out_8(®s->cantflg, 1 << buf_id); + + if (!test_bit(F_TX_PROGRESS, &priv->flags)) + dev->trans_start = jiffies; + + list_add_tail(&priv->tx_queue[buf_id].list, &priv->tx_head); + + kfree_skb(skb); + + /* Enable interrupt. */ + priv->tx_active |= 1 << buf_id; + out_8(®s->cantier, priv->tx_active); + + return NETDEV_TX_OK; +} + +static void mscan_tx_timeout(struct net_device *dev) +{ + struct sk_buff *skb; + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct mscan_priv *priv = netdev_priv(dev); + struct can_frame *frame; + u8 mask; + + printk("%s\n", __FUNCTION__); + + out_8(®s->cantier, 0); + + mask = list_entry(priv->tx_head.next, tx_queue_entry_t, list)->mask; + dev->trans_start = jiffies; + out_8(®s->cantarq, mask); + out_8(®s->cantier, priv->tx_active); + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) { + if (printk_ratelimit()) + dev_notice(ND2D(dev), "TIMEOUT packet dropped\n"); + return; + } + frame = (struct can_frame *)skb_put(skb, sizeof(struct can_frame)); + + frame->can_id = CAN_ERR_FLAG | CAN_ERR_TX_TIMEOUT; + frame->can_dlc = CAN_ERR_DLC; + + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + + netif_rx(skb); + +} + +static can_state_t state_map[] = { + CAN_STATE_ACTIVE, + CAN_STATE_BUS_WARNING, + CAN_STATE_BUS_PASSIVE, + CAN_STATE_BUS_OFF +}; + +static inline int check_set_state(struct net_device *dev, u8 canrflg) +{ + struct mscan_priv *priv = netdev_priv(dev); + can_state_t state; + int ret = 0; + + if (!(canrflg & MSCAN_CSCIF) || priv->can.state > CAN_STATE_BUS_OFF) + return 0; + + state = + state_map[max(MSCAN_STATE_RX(canrflg), MSCAN_STATE_TX(canrflg))]; + if (priv->can.state < state) + ret = 1; + if (state == CAN_STATE_BUS_OFF) + netif_carrier_off(dev); + else if (priv->can.state == CAN_STATE_BUS_OFF + && state != CAN_STATE_BUS_OFF) + netif_carrier_on(dev); + priv->can.state = state; + return ret; +} + +static int mscan_rx_poll(struct napi_struct *napi, int quota) +{ + struct mscan_priv *priv = container_of(napi, struct mscan_priv, napi); + struct net_device *dev = priv->dev; + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct net_device_stats *stats = dev->get_stats(dev); + int npackets = 0; + int ret = 1; + struct sk_buff *skb; + struct can_frame *frame; + u32 can_id; + u8 canrflg; + int i; + + while (npackets < quota && ((canrflg = in_8(®s->canrflg)) & + (MSCAN_RXF | MSCAN_ERR_IF))) { + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) { + if (printk_ratelimit()) + dev_notice(ND2D(dev), "packet dropped\n"); + stats->rx_dropped++; + out_8(®s->canrflg, canrflg); + continue; + } + + frame = (struct can_frame *)skb_put(skb, + sizeof(struct can_frame)); + + if (canrflg & MSCAN_RXF) { + can_id = in_be16(®s->rx.idr1_0); + if (can_id & (1 << 3)) { + frame->can_id = CAN_EFF_FLAG; + can_id = ((can_id << 16) | + in_be16(®s->rx.idr3_2)); + can_id = ((can_id & 0xffe00000) | + ((can_id & 0x7ffff) << 2)) >> 2; + } else { + can_id >>= 4; + frame->can_id = 0; + } + + frame->can_id |= can_id >> 1; + if (can_id & 1) + frame->can_id |= CAN_RTR_FLAG; + frame->can_dlc = in_8(®s->rx.dlr) & 0xf; + + if (!(frame->can_id & CAN_RTR_FLAG)) { + volatile void __iomem *data = ®s->rx.dsr1_0; + u16 *payload = (u16 *) frame->data; + for (i = 0; i < (frame->can_dlc + 1) / 2; i++) { + *payload++ = in_be16(data); + data += 2 + _MSCAN_RESERVED_DSR_SIZE; + } + } + + dev_dbg(ND2D(dev), + "received pkt: id: %u dlc: %u data: ", + frame->can_id, frame->can_dlc); +#ifdef DEBUG + for (i = 0; + i < frame->can_dlc && !(frame->can_id & + CAN_FLAG_RTR); i++) + printk("%2x ", frame->data[i]); + printk("\n"); +#endif + + out_8(®s->canrflg, MSCAN_RXF); + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += frame->can_dlc; + } else if (canrflg & MSCAN_ERR_IF) { + frame->can_id = CAN_ERR_FLAG; + + if (canrflg & MSCAN_OVRIF) { + frame->can_id |= CAN_ERR_CRTL; + frame->data[1] = CAN_ERR_CRTL_RX_OVERFLOW; + stats->rx_over_errors++; + } else + frame->data[1] = 0; + + if (check_set_state(dev, canrflg)) { + frame->can_id |= CAN_ERR_CRTL; + switch (priv->can.state) { + case CAN_STATE_BUS_WARNING: + if ((priv->shadow_statflg & + MSCAN_RSTAT_MSK) < + (canrflg & MSCAN_RSTAT_MSK)) + frame->data[1] |= + CAN_ERR_CRTL_RX_WARNING; + + if ((priv->shadow_statflg & + MSCAN_TSTAT_MSK) < + (canrflg & MSCAN_TSTAT_MSK)) + frame->data[1] |= + CAN_ERR_CRTL_TX_WARNING; + break; + case CAN_STATE_BUS_PASSIVE: + frame->data[1] |= + CAN_ERR_CRTL_RX_PASSIVE; + break; + case CAN_STATE_BUS_OFF: + frame->can_id |= CAN_ERR_BUSOFF; + frame->can_id &= ~CAN_ERR_CRTL; + break; + default: + break; + } + } + priv->shadow_statflg = canrflg & MSCAN_STAT_MSK; + frame->can_dlc = CAN_ERR_DLC; + out_8(®s->canrflg, MSCAN_ERR_IF); + } + + npackets++; + skb->dev = dev; + skb->protocol = __constant_htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_receive_skb(skb); + } + + if (!(in_8(®s->canrflg) & (MSCAN_RXF | MSCAN_ERR_IF))) { + netif_rx_complete(dev, &priv->napi); + clear_bit(F_RX_PROGRESS, &priv->flags); + out_8(®s->canrier, + in_8(®s->canrier) | MSCAN_ERR_IF | MSCAN_RXFIE); + ret = 0; + } + return ret; +} + + +static irqreturn_t mscan_isr(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct mscan_priv *priv = netdev_priv(dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + struct net_device_stats *stats = dev->get_stats(dev); + u8 cantier, cantflg, canrflg; + irqreturn_t ret = IRQ_NONE; + + if ((cantier = in_8(®s->cantier) & MSCAN_TXE) && + (cantflg = in_8(®s->cantflg) & cantier)) { + struct list_head *tmp, *pos; + + list_for_each_safe(pos, tmp, &priv->tx_head) { + tx_queue_entry_t *entry = + list_entry(pos, tx_queue_entry_t, list); + u8 mask = entry->mask; + + if (!(cantflg & mask)) + continue; + + if (in_8(®s->cantaak) & mask) { + stats->tx_dropped++; + stats->tx_aborted_errors++; + } else { + out_8(®s->cantbsel, mask); + stats->tx_bytes += + in_8(®s->tx.dlr); + stats->tx_packets++; + } + priv->tx_active &= ~mask; + list_del(pos); + } + + if (list_empty(&priv->tx_head)) { + clear_bit(F_TX_WAIT_ALL, &priv->flags); + clear_bit(F_TX_PROGRESS, &priv->flags); + priv->cur_pri = 0; + } else + dev->trans_start = jiffies; + + if (!test_bit(F_TX_WAIT_ALL, &priv->flags)) + netif_wake_queue(dev); + + out_8(®s->cantier, priv->tx_active); + ret = IRQ_HANDLED; + } + + if ((((canrflg = in_8(®s->canrflg)) & ~MSCAN_STAT_MSK)) && + !test_and_set_bit(F_RX_PROGRESS, &priv->flags)) { + if (check_set_state(dev, canrflg)) { + out_8(®s->canrflg, MSCAN_CSCIF); + ret = IRQ_HANDLED; + } + if (canrflg & ~MSCAN_STAT_MSK) { + priv->shadow_canrier = in_8(®s->canrier); + out_8(®s->canrier, 0); + netif_rx_schedule(dev, &priv->napi); + ret = IRQ_HANDLED; + } else + clear_bit(F_RX_PROGRESS, &priv->flags); + } + return ret; +} + +static int mscan_do_set_mode(struct net_device *dev, can_mode_t mode) +{ + switch (mode) { + case CAN_MODE_SLEEP: + case CAN_MODE_STOP: + netif_stop_queue(dev); + mscan_set_mode(dev, + (mode == + CAN_MODE_STOP) ? MSCAN_INIT_MODE : + MSCAN_SLEEP_MODE); + break; + case CAN_MODE_START: + printk("%s: CAN_MODE_START requested\n", __FUNCTION__); + mscan_set_mode(dev, MSCAN_NORMAL_MODE); + netif_wake_queue(dev); + break; + + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int mscan_do_set_bit_time(struct net_device *dev, + struct can_bittime *bt) +{ + struct mscan_priv *priv = netdev_priv(dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + int ret = 0; + u8 reg; + struct mscan_state state; + + if (bt->type != CAN_BITTIME_STD) + return -EINVAL; + + spin_lock_irq(&priv->can.irq_lock); + + mscan_push_state(dev, &state); + ret = mscan_set_mode(dev, MSCAN_INIT_MODE); + if (!ret) { + reg = BTR0_SET_BRP(bt->std.brp) | BTR0_SET_SJW(bt->std.sjw); + out_8(®s->canbtr0, reg); + + reg = (BTR1_SET_TSEG1(bt->std.prop_seg + bt->std.phase_seg1) | + BTR1_SET_TSEG2(bt->std.phase_seg2) | + BTR1_SET_SAM(bt->std.sam)); + out_8(®s->canbtr1, reg); + + ret = mscan_pop_state(dev, &state); + } + + spin_unlock_irq(&priv->can.irq_lock); + return ret; +} + +static int mscan_open(struct net_device *dev) +{ + int ret; + struct mscan_priv *priv = netdev_priv(dev); + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + napi_enable(&priv->napi); + ret = request_irq(dev->irq, mscan_isr, IRQF_SHARED, dev->name, dev); + + if (ret < 0) { + napi_disable(&priv->napi); + printk(KERN_ERR "%s - failed to attach interrupt\n", + dev->name); + return ret; + } + + INIT_LIST_HEAD(&priv->tx_head); + /* acceptance mask/acceptance code (accept everything) */ + out_be16(®s->canidar1_0, 0); + out_be16(®s->canidar3_2, 0); + out_be16(®s->canidar5_4, 0); + out_be16(®s->canidar7_6, 0); + + out_be16(®s->canidmr1_0, 0xffff); + out_be16(®s->canidmr3_2, 0xffff); + out_be16(®s->canidmr5_4, 0xffff); + out_be16(®s->canidmr7_6, 0xffff); + /* Two 32 bit Acceptance Filters */ + out_8(®s->canidac, MSCAN_AF_32BIT); + + out_8(®s->canctl1, in_8(®s->canctl1) & ~MSCAN_LISTEN); + mscan_set_mode(dev, MSCAN_NORMAL_MODE); + + priv->shadow_statflg = in_8(®s->canrflg) & MSCAN_STAT_MSK; + priv->cur_pri = 0; + priv->tx_active = 0; + + out_8(®s->cantier, 0); + /* Enable receive interrupts. */ + out_8(®s->canrier, MSCAN_OVRIE | MSCAN_RXFIE | MSCAN_CSCIE | + MSCAN_RSTATE1 | MSCAN_RSTATE0 | MSCAN_TSTATE1 | MSCAN_TSTATE0); + + netif_start_queue(dev); + + return 0; +} + +static int mscan_close(struct net_device *dev) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + + netif_stop_queue(dev); + + /* disable interrupts */ + out_8(®s->cantier, 0); + out_8(®s->canrier, 0); + free_irq(dev->irq, dev); + + mscan_set_mode(dev, MSCAN_INIT_MODE); + return 0; +} + +int register_mscandev(struct net_device *dev, int clock_src) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + u8 ctl1; + + ctl1 = in_8(®s->canctl1); + if (clock_src) + ctl1 |= MSCAN_CLKSRC; + else + ctl1 &= ~MSCAN_CLKSRC; + + ctl1 |= MSCAN_CANE; + out_8(®s->canctl1, ctl1); + udelay(100); + + mscan_set_mode(dev, MSCAN_INIT_MODE); + + return register_netdev(dev); +} + +EXPORT_SYMBOL(register_mscandev); + +void unregister_mscandev(struct net_device *dev) +{ + struct mscan_regs *regs = (struct mscan_regs *)dev->base_addr; + mscan_set_mode(dev, MSCAN_INIT_MODE); + out_8(®s->canctl1, in_8(®s->canctl1) & ~MSCAN_CANE); + unregister_netdev(dev); +} + +EXPORT_SYMBOL(unregister_mscandev); + +struct net_device *alloc_mscandev(void) +{ + struct net_device *dev; + struct mscan_priv *priv; + int i; + + dev = alloc_candev(sizeof(struct mscan_priv)); + if (!dev) + return NULL; + priv = netdev_priv(dev); + + dev->watchdog_timeo = MSCAN_WATCHDOG_TIMEOUT; + dev->open = mscan_open; + dev->stop = mscan_close; + dev->hard_start_xmit = mscan_hard_start_xmit; + dev->tx_timeout = mscan_tx_timeout; + + priv->dev = dev; + netif_napi_add(dev, &priv->napi, mscan_rx_poll, 8); + + priv->can.do_set_bittime = mscan_do_set_bit_time; + priv->can.do_set_mode = mscan_do_set_mode; + + for (i = 0; i < TX_QUEUE_SIZE; i++) + priv->tx_queue[i].mask = 1 << i; + + return dev; +} + +EXPORT_SYMBOL(alloc_mscandev); + +MODULE_AUTHOR("Andrey Volkov <avolkov@varma-el.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CAN port driver for a mscan based chips"); diff --git a/drivers/net/can/old/mscan/mscan.h b/drivers/net/can/old/mscan/mscan.h new file mode 100644 index 000000000000..807ec3fae437 --- /dev/null +++ b/drivers/net/can/old/mscan/mscan.h @@ -0,0 +1,247 @@ +/* + * DESCRIPTION: + * Definitions of consts/structs to drive the Freescale MSCAN. + * + * AUTHOR: + * Andrey Volkov <avolkov@varma-el.com> + * + * COPYRIGHT: + * 2004-2006, Varma Electronics Oy + * + * LICENCE: + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __MSCAN_H__ +#define __MSCAN_H__ + +#include <linux/autoconf.h> +#include <asm/types.h> + +/* MSCAN control register 0 (CANCTL0) bits */ +#define MSCAN_RXFRM 0x80 +#define MSCAN_RXACT 0x40 +#define MSCAN_CSWAI 0x20 +#define MSCAN_SYNCH 0x10 +#define MSCAN_TIME 0x08 +#define MSCAN_WUPE 0x04 +#define MSCAN_SLPRQ 0x02 +#define MSCAN_INITRQ 0x01 + +/* MSCAN control register 1 (CANCTL1) bits */ +#define MSCAN_CANE 0x80 +#define MSCAN_CLKSRC 0x40 +#define MSCAN_LOOPB 0x20 +#define MSCAN_LISTEN 0x10 +#define MSCAN_WUPM 0x04 +#define MSCAN_SLPAK 0x02 +#define MSCAN_INITAK 0x01 + +#ifdef CONFIG_PPC_MPC52xx +#define MSCAN_CLKSRC_BUS 0 +#define MSCAN_CLKSRC_XTAL MSCAN_CLKSRC +#else +#define MSCAN_CLKSRC_BUS MSCAN_CLKSRC +#define MSCAN_CLKSRC_XTAL 0 +#endif + +/* MSCAN receiver flag register (CANRFLG) bits */ +#define MSCAN_WUPIF 0x80 +#define MSCAN_CSCIF 0x40 +#define MSCAN_RSTAT1 0x20 +#define MSCAN_RSTAT0 0x10 +#define MSCAN_TSTAT1 0x08 +#define MSCAN_TSTAT0 0x04 +#define MSCAN_OVRIF 0x02 +#define MSCAN_RXF 0x01 +#define MSCAN_ERR_IF (MSCAN_OVRIF | MSCAN_CSCIF) +#define MSCAN_RSTAT_MSK (MSCAN_RSTAT1 | MSCAN_RSTAT0) +#define MSCAN_TSTAT_MSK (MSCAN_TSTAT1 | MSCAN_TSTAT0) +#define MSCAN_STAT_MSK (MSCAN_RSTAT_MSK | MSCAN_TSTAT_MSK) + +#define MSCAN_STATE_BUS_OFF (MSCAN_RSTAT1 | MSCAN_RSTAT0 | \ + MSCAN_TSTAT1 | MSCAN_TSTAT0) +#define MSCAN_STATE_TX(canrflg) (((canrflg)&MSCAN_TSTAT_MSK)>>2) +#define MSCAN_STATE_RX(canrflg) (((canrflg)&MSCAN_RSTAT_MSK)>>4) +#define MSCAN_STATE_ACTIVE 0 +#define MSCAN_STATE_WARNING 1 +#define MSCAN_STATE_PASSIVE 2 +#define MSCAN_STATE_BUSOFF 3 + +/* MSCAN receiver interrupt enable register (CANRIER) bits */ +#define MSCAN_WUPIE 0x80 +#define MSCAN_CSCIE 0x40 +#define MSCAN_RSTATE1 0x20 +#define MSCAN_RSTATE0 0x10 +#define MSCAN_TSTATE1 0x08 +#define MSCAN_TSTATE0 0x04 +#define MSCAN_OVRIE 0x02 +#define MSCAN_RXFIE 0x01 + +/* MSCAN transmitter flag register (CANTFLG) bits */ +#define MSCAN_TXE2 0x04 +#define MSCAN_TXE1 0x02 +#define MSCAN_TXE0 0x01 +#define MSCAN_TXE (MSCAN_TXE2 | MSCAN_TXE1 | MSCAN_TXE0) + +/* MSCAN transmitter interrupt enable register (CANTIER) bits */ +#define MSCAN_TXIE2 0x04 +#define MSCAN_TXIE1 0x02 +#define MSCAN_TXIE0 0x01 +#define MSCAN_TXIE (MSCAN_TXIE2 | MSCAN_TXIE1 | MSCAN_TXIE0) + +/* MSCAN transmitter message abort request (CANTARQ) bits */ +#define MSCAN_ABTRQ2 0x04 +#define MSCAN_ABTRQ1 0x02 +#define MSCAN_ABTRQ0 0x01 + +/* MSCAN transmitter message abort ack (CANTAAK) bits */ +#define MSCAN_ABTAK2 0x04 +#define MSCAN_ABTAK1 0x02 +#define MSCAN_ABTAK0 0x01 + +/* MSCAN transmit buffer selection (CANTBSEL) bits */ +#define MSCAN_TX2 0x04 +#define MSCAN_TX1 0x02 +#define MSCAN_TX0 0x01 + +/* MSCAN ID acceptance control register (CANIDAC) bits */ +#define MSCAN_IDAM1 0x20 +#define MSCAN_IDAM0 0x10 +#define MSCAN_IDHIT2 0x04 +#define MSCAN_IDHIT1 0x02 +#define MSCAN_IDHIT0 0x01 + +#define MSCAN_AF_32BIT 0x00 +#define MSCAN_AF_16BIT MSCAN_IDAM0 +#define MSCAN_AF_8BIT MSCAN_IDAM1 +#define MSCAN_AF_CLOSED (MSCAN_IDAM0|MSCAN_IDAM1) +#define MSCAN_AF_MASK (~(MSCAN_IDAM0|MSCAN_IDAM1)) + +/* MSCAN Miscellaneous Register (CANMISC) bits */ +#define MSCAN_BOHOLD 0x01 + +#ifdef CONFIG_PPC_MPC52xx +#define _MSCAN_RESERVED_(n,num) u8 _res##n[num] +#define _MSCAN_RESERVED_DSR_SIZE 2 +#else +#define _MSCAN_RESERVED_(n,num) +#define _MSCAN_RESERVED_DSR_SIZE 0 +#endif + +/* Structure of the hardware registers */ +struct mscan_regs { + /* (see doco S12MSCANV3/D) MPC5200 MSCAN */ + u8 canctl0; /* + 0x00 0x00 */ + u8 canctl1; /* + 0x01 0x01 */ + _MSCAN_RESERVED_(1, 2); /* + 0x02 */ + u8 canbtr0; /* + 0x04 0x02 */ + u8 canbtr1; /* + 0x05 0x03 */ + _MSCAN_RESERVED_(2, 2); /* + 0x06 */ + u8 canrflg; /* + 0x08 0x04 */ + u8 canrier; /* + 0x09 0x05 */ + _MSCAN_RESERVED_(3, 2); /* + 0x0a */ + u8 cantflg; /* + 0x0c 0x06 */ + u8 cantier; /* + 0x0d 0x07 */ + _MSCAN_RESERVED_(4, 2); /* + 0x0e */ + u8 cantarq; /* + 0x10 0x08 */ + u8 cantaak; /* + 0x11 0x09 */ + _MSCAN_RESERVED_(5, 2); /* + 0x12 */ + u8 cantbsel; /* + 0x14 0x0a */ + u8 canidac; /* + 0x15 0x0b */ + u8 reserved; /* + 0x16 0x0c */ + _MSCAN_RESERVED_(6, 5); /* + 0x17 */ +#ifndef CONFIG_PPC_MPC52xx + u8 canmisc; /* 0x0d */ +#endif + u8 canrxerr; /* + 0x1c 0x0e */ + u8 cantxerr; /* + 0x1d 0x0f */ + _MSCAN_RESERVED_(7, 2); /* + 0x1e */ + u16 canidar1_0; /* + 0x20 0x10 */ + _MSCAN_RESERVED_(8, 2); /* + 0x22 */ + u16 canidar3_2; /* + 0x24 0x12 */ + _MSCAN_RESERVED_(9, 2); /* + 0x26 */ + u16 canidmr1_0; /* + 0x28 0x14 */ + _MSCAN_RESERVED_(10, 2); /* + 0x2a */ + u16 canidmr3_2; /* + 0x2c 0x16 */ + _MSCAN_RESERVED_(11, 2); /* + 0x2e */ + u16 canidar5_4; /* + 0x30 0x18 */ + _MSCAN_RESERVED_(12, 2); /* + 0x32 */ + u16 canidar7_6; /* + 0x34 0x1a */ + _MSCAN_RESERVED_(13, 2); /* + 0x36 */ + u16 canidmr5_4; /* + 0x38 0x1c */ + _MSCAN_RESERVED_(14, 2); /* + 0x3a */ + u16 canidmr7_6; /* + 0x3c 0x1e */ + _MSCAN_RESERVED_(15, 2); /* + 0x3e */ + struct { + u16 idr1_0; /* + 0x40 0x20 */ + _MSCAN_RESERVED_(16, 2); /* + 0x42 */ + u16 idr3_2; /* + 0x44 0x22 */ + _MSCAN_RESERVED_(17, 2); /* + 0x46 */ + u16 dsr1_0; /* + 0x48 0x24 */ + _MSCAN_RESERVED_(18, 2); /* + 0x4a */ + u16 dsr3_2; /* + 0x4c 0x26 */ + _MSCAN_RESERVED_(19, 2); /* + 0x4e */ + u16 dsr5_4; /* + 0x50 0x28 */ + _MSCAN_RESERVED_(20, 2); /* + 0x52 */ + u16 dsr7_6; /* + 0x54 0x2a */ + _MSCAN_RESERVED_(21, 2); /* + 0x56 */ + u8 dlr; /* + 0x58 0x2c */ + u8:8; /* + 0x59 0x2d */ + _MSCAN_RESERVED_(22, 2); /* + 0x5a */ + u16 time; /* + 0x5c 0x2e */ + } rx; + _MSCAN_RESERVED_(23, 2); /* + 0x5e */ + struct { + u16 idr1_0; /* + 0x60 0x30 */ + _MSCAN_RESERVED_(24, 2); /* + 0x62 */ + u16 idr3_2; /* + 0x64 0x32 */ + _MSCAN_RESERVED_(25, 2); /* + 0x66 */ + u16 dsr1_0; /* + 0x68 0x34 */ + _MSCAN_RESERVED_(26, 2); /* + 0x6a */ + u16 dsr3_2; /* + 0x6c 0x36 */ + _MSCAN_RESERVED_(27, 2); /* + 0x6e */ + u16 dsr5_4; /* + 0x70 0x38 */ + _MSCAN_RESERVED_(28, 2); /* + 0x72 */ + u16 dsr7_6; /* + 0x74 0x3a */ + _MSCAN_RESERVED_(29, 2); /* + 0x76 */ + u8 dlr; /* + 0x78 0x3c */ + u8 tbpr; /* + 0x79 0x3d */ + _MSCAN_RESERVED_(30, 2); /* + 0x7a */ + u16 time; /* + 0x7c 0x3e */ + } tx; + _MSCAN_RESERVED_(31, 2); /* + 0x7e */ +} __attribute__ ((packed)); + +#undef _MSCAN_RESERVED_ +#define MSCAN_REGION sizeof(struct mscan) + +#define MSCAN_WATCHDOG_TIMEOUT ((500*HZ)/1000) + +struct mscan_platform_data { + u8 clock_src; /* MSCAN_CLKSRC_BUS or MSCAN_CLKSRC_XTAL */ + u32 clock_frq; /* can ref. clock, in Hz */ +}; + +struct net_device *alloc_mscandev(void); +/* @clock_src: + 1 = The MSCAN clock source is the onchip Bus Clock. + 0 = The MSCAN clock source is the chip Oscillator Clock. +*/ +extern int register_mscandev(struct net_device *dev, int clock_src); +extern void unregister_mscandev(struct net_device *dev); + +#endif /* __MSCAN_H__ */ diff --git a/drivers/net/can/old/sja1000/Makefile b/drivers/net/can/old/sja1000/Makefile new file mode 100644 index 000000000000..cc8f1f917e99 --- /dev/null +++ b/drivers/net/can/old/sja1000/Makefile @@ -0,0 +1,27 @@ +# +# + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common +EXTRA_CFLAGS += -I$(TOPDIR)/drivers/net/can/old/hal + +obj-m := sja1000-io.o sja1000-iomem.o sja1000-iomux.o sja1000-gw2.o sja1000-esdio.o sja1000-c200.o + +sja1000-io-objs := sja1000.o proc.o ../hal/io.o +sja1000-iomem-objs := sja1000.o proc.o ../hal/iomem.o +sja1000-iomux-objs := sja1000.o proc.o ../hal/iomux.o +sja1000-gw2-objs := sja1000.o proc.o ../hal/gw2.o +sja1000-esdio-objs := sja1000.o proc.o ../hal/esdio.o +sja1000-c200-objs := sja1000.o proc.o ../hal/c200.o + +endif diff --git a/drivers/net/can/old/sja1000/proc.c b/drivers/net/can/old/sja1000/proc.c new file mode 100644 index 000000000000..b98aa6180784 --- /dev/null +++ b/drivers/net/can/old/sja1000/proc.c @@ -0,0 +1,230 @@ +/* + * proc.c - proc file system functions for SJA1000 CAN driver. + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/netdevice.h> + +#include <linux/can.h> +#include <linux/can/ioctl.h> +#include "sja1000.h" +#include "hal.h" + +extern struct net_device *can_dev[]; + +static struct proc_dir_entry *pde = NULL; +static struct proc_dir_entry *pde_regs = NULL; +static struct proc_dir_entry *pde_reset = NULL; + +static int can_proc_read_stats(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + int i; + + len += snprintf(page + len, PAGE_SIZE - len, + "CAN bus device statistics:\n"); + len += snprintf(page + len, PAGE_SIZE - len, + " errwarn overrun wakeup buserr " + "errpass arbitr restarts clock baud\n"); + for (i = 0; (i < MAXDEV) && (len < PAGE_SIZE - 200); i++) { + if (can_dev[i]) { + struct net_device *dev = can_dev[i]; + struct can_priv *priv = netdev_priv(dev); +#ifdef SJA1000_H + u8 stat = hw_readreg(dev->base_addr, REG_SR); + + if (stat & 0x80) { + len += snprintf(page + len, PAGE_SIZE - len, + "%s: bus status: " + "BUS OFF, ", dev->name); + } else if (stat & 0x40) { + len += snprintf(page + len, PAGE_SIZE - len, + "%s: bus status: ERROR " + "PASSIVE, ", dev->name); + } else { + len += snprintf(page + len, PAGE_SIZE - len, + "%s: bus status: OK, ", + dev->name); + } + len += snprintf(page + len, PAGE_SIZE - len, + "RXERR: %d, TXERR: %d\n", + hw_readreg(dev->base_addr, REG_RXERR), + hw_readreg(dev->base_addr, REG_TXERR)); +#endif + len += snprintf(page + len, PAGE_SIZE - len, + "%s: %8d %8d %8d %8d %8d " + "%8d %8d %10d %8d\n", dev->name, + priv->can_stats.error_warning, + priv->can_stats.data_overrun, + priv->can_stats.wakeup, + priv->can_stats.bus_error, + priv->can_stats.error_passive, + priv->can_stats.arbitration_lost, + priv->can_stats.restarts, + priv->clock, + priv->speed + ); + + } + } + + *eof = 1; + return len; +} + + +static int can_proc_dump_regs(char *page, int len, struct net_device *dev) +{ + int r,s; + struct can_priv *priv = netdev_priv(dev); + int regs = priv->hw_regs; + + len += snprintf(page + len, PAGE_SIZE - len, + "%s registers:\n", dev->name); + + for (r = 0; r < regs; r += 0x10) { + len += snprintf(page + len, PAGE_SIZE - len, "%02X: ", r); + for (s = 0; s < 0x10; s++) { + if (r+s < regs) + len += snprintf(page + len, PAGE_SIZE-len, + "%02X ", + hw_readreg(dev->base_addr, + r+s)); + } + len += snprintf(page + len, PAGE_SIZE - len, "\n"); + } + + return len; +} + +static int can_proc_read_regs(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + int i; + + for (i = 0; (i < MAXDEV) && (len < PAGE_SIZE - 200); i++) { + if (can_dev[i]) + len = can_proc_dump_regs(page, len, can_dev[i]); + } + + *eof = 1; + return len; +} + +static int can_proc_read_reset(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + struct net_device *dev; + int i; + struct can_priv *priv; + + len += snprintf(page + len, PAGE_SIZE - len, "resetting "); + for (i = 0; (i < MAXDEV) && (len < PAGE_SIZE - 200); i++) { + if (can_dev[i]) { + dev = can_dev[i]; + priv = netdev_priv(can_dev[i]); + if ((priv->state != STATE_UNINITIALIZED) + && (priv->state != STATE_RESET_MODE)) { + len += snprintf(page + len, PAGE_SIZE - len, + "%s ", dev->name); + dev->stop(dev); + dev->open(dev); + /* count number of restarts */ + priv->can_stats.restarts++; + + } else { + len += snprintf(page + len, PAGE_SIZE - len, + "(%s|%d) ", dev->name, + priv->state); + } + } + } + + len += snprintf(page + len, PAGE_SIZE - len, "done\n"); + + *eof = 1; + return len; +} + +void can_proc_create(const char *drv_name) +{ + char fname[256]; + + if (pde == NULL) { + sprintf(fname, PROCBASE "/%s_stats", drv_name); + pde = create_proc_read_entry(fname, 0644, NULL, + can_proc_read_stats, NULL); + } + if (pde_regs == NULL) { + sprintf(fname, PROCBASE "/%s_regs", drv_name); + pde_regs = create_proc_read_entry(fname, 0644, NULL, + can_proc_read_regs, NULL); + } + if (pde_reset == NULL) { + sprintf(fname, PROCBASE "/%s_reset", drv_name); + pde_reset = create_proc_read_entry(fname, 0644, NULL, + can_proc_read_reset, NULL); + } +} + +void can_proc_remove(const char *drv_name) +{ + char fname[256]; + + if (pde) { + sprintf(fname, PROCBASE "/%s_stats", drv_name); + remove_proc_entry(fname, NULL); + } + if (pde_regs) { + sprintf(fname, PROCBASE "/%s_regs", drv_name); + remove_proc_entry(fname, NULL); + } + if (pde_reset) { + sprintf(fname, PROCBASE "/%s_reset", drv_name); + remove_proc_entry(fname, NULL); + } +} diff --git a/drivers/net/can/old/sja1000/sja1000.c b/drivers/net/can/old/sja1000/sja1000.c new file mode 100644 index 000000000000..8d7185a2962b --- /dev/null +++ b/drivers/net/can/old/sja1000/sja1000.c @@ -0,0 +1,1140 @@ +/* + * sja1000.c - Philips SJA1000 network device driver + * + * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33, + * 38106 Braunschweig, GERMANY + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> + +#include <linux/can.h> +#include <linux/can/ioctl.h> /* for struct can_device_stats */ +#include "sja1000.h" +#include "hal.h" + +MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("LLCF/socketcan '" CHIP_NAME "' network device driver"); + +#ifdef CONFIG_CAN_DEBUG_DEVICES +#define DBG(args...) ((priv->debug > 0) ? printk(args) : 0) +/* logging in interrupt context! */ +#define iDBG(args...) ((priv->debug > 1) ? printk(args) : 0) +#define iiDBG(args...) ((priv->debug > 2) ? printk(args) : 0) +#else +#define DBG(args...) +#define iDBG(args...) +#define iiDBG(args...) +#endif + +char drv_name[DRV_NAME_LEN] = "undefined"; + +/* driver and version information */ +static const char *drv_version = "0.1.1"; +static const char *drv_reldate = "2007-04-13"; + +#ifdef CONFIG_CAN_DEBUG_DEVICES +static const char *ecc_errors[] = { + NULL, + NULL, + "ID.28 to ID.28", + "start of frame", + "bit SRTR", + "bit IDE", + "ID.20 to ID.18", + "ID.17 to ID.13", + "CRC sequence", + "reserved bit 0", + "data field", + "data length code", + "bit RTR", + "reserved bit 1", + "ID.4 to ID.0", + "ID.12 to ID.5", + NULL, + "active error flag", + "intermission", + "tolerate dominant bits", + NULL, + NULL, + "passive error flag", + "error delimiter", + "CRC delimiter", + "acknowledge slot", + "end of frame", + "acknowledge delimiter", + "overload flag", + NULL, + NULL, + NULL +}; + +static const char *ecc_types[] = { + "bit error", + "form error", + "stuff error", + "other type of error" +}; +#endif + +/* array of all can chips */ +struct net_device *can_dev[MAXDEV]; + +/* module parameters */ +unsigned long base[MAXDEV] = { 0 }; /* hardware address */ +unsigned long rbase[MAXDEV] = { 0 }; /* (remapped) device address */ +unsigned int irq[MAXDEV] = { 0 }; + +unsigned int speed[MAXDEV] = { DEFAULT_SPEED, DEFAULT_SPEED }; +unsigned int btr[MAXDEV] = { 0 }; + +static int rx_probe[MAXDEV] = { 0 }; +static int clk = DEFAULT_HW_CLK; +static int debug = 0; +static int restart_ms = 100; +static int echo = 1; + +static int base_n; +static int irq_n; +static int speed_n; +static int btr_n; +static int rx_probe_n; + +module_param_array(base, int, &base_n, 0); +module_param_array(irq, int, &irq_n, 0); +module_param_array(speed, int, &speed_n, 0); +module_param_array(btr, int, &btr_n, 0); +module_param_array(rx_probe, int, &rx_probe_n, 0); + +module_param(clk, int, 0); +module_param(debug, int, 0); +module_param(restart_ms, int, 0); +module_param(echo, int, S_IRUGO); + +MODULE_PARM_DESC(base, "CAN controller base address"); +MODULE_PARM_DESC(irq, "CAN controller interrupt"); +MODULE_PARM_DESC(speed, "CAN bus bitrate"); +MODULE_PARM_DESC(btr, "Bit Timing Register value 0x<btr0><btr1>, e.g. 0x4014"); +MODULE_PARM_DESC(rx_probe, "switch to trx mode after correct msg receiption. (default off)"); + +MODULE_PARM_DESC(clk, "CAN controller chip clock (default: 16MHz)"); +MODULE_PARM_DESC(debug, "set debug mask (default: 0)"); +MODULE_PARM_DESC(restart_ms, "restart chip on heavy bus errors / bus off after x ms (default 100ms)"); +MODULE_PARM_DESC(echo, "Echo sent frames. default: 1 (On)"); + +/* + * CAN network devices *should* support a local echo functionality + * (see Documentation/networking/can.txt). To test the handling of CAN + * interfaces that do not support the local echo both driver types are + * implemented inside this sja1000 driver. In the case that the driver does + * not support the echo the IFF_ECHO remains clear in dev->flags. + * This causes the PF_CAN core to perform the echo as a fallback solution. + */ + +/* function declarations */ + +static void can_restart_dev(unsigned long data); +static void chipset_init(struct net_device *dev, int wake); +static void chipset_init_rx(struct net_device *dev); +static void chipset_init_trx(struct net_device *dev); +static void can_netdev_setup(struct net_device *dev); +static struct net_device* can_create_netdev(int dev_num, int hw_regs); +static int can_set_drv_name(void); +int set_reset_mode(struct net_device *dev); + +static int sja1000_probe_chip(unsigned long base) +{ + if (base && (hw_readreg(base, 0) == 0xFF)) { + printk(KERN_INFO "%s: probing @0x%lX failed\n", + drv_name, base); + return 0; + } + return 1; +} + +/* + * set baud rate divisor values + */ +static void set_btr(struct net_device *dev, int btr0, int btr1) +{ + struct can_priv *priv = netdev_priv(dev); + + /* no bla bla when restarting the device */ + if (priv->state == STATE_UNINITIALIZED) + printk(KERN_INFO "%s: setting BTR0=%02X BTR1=%02X\n", + dev->name, btr0, btr1); + + hw_writereg(dev->base_addr, REG_BTR0, btr0); + hw_writereg(dev->base_addr, REG_BTR1, btr1); +} + +/* + * calculate baud rate divisor values + */ +static void set_baud(struct net_device *dev, int baud, int clock) +{ + struct can_priv *priv = netdev_priv(dev); + + int error; + int brp; + int tseg; + int tseg1 = 0; + int tseg2 = 0; + + int best_error = 1000000000; + int best_tseg = 0; + int best_brp = 0; + int best_baud = 0; + + int SAM = (baud > 100000 ? 0 : 1); + + clock >>= 1; + + for (tseg = (0 + 0 + 2) * 2; + tseg <= (MAX_TSEG2 + MAX_TSEG1 + 2) * 2 + 1; + tseg++) { + brp = clock / ((1 + tseg / 2) * baud) + tseg % 2; + if ((brp > 0) && (brp <= 64)) { + error = baud - clock / (brp * (1 + tseg / 2)); + if (error < 0) { + error = -error; + } + if (error <= best_error) { + best_error = error; + best_tseg = tseg / 2; + best_brp = brp - 1; + best_baud = clock / (brp * (1 + tseg / 2)); + } + } + } + if (best_error && (baud / best_error < 10)) { + printk("%s: unable to set baud rate %d (ext clock %dHz)\n", + dev->name, baud, clock * 2); + return; +// return -EINVAL; + } + tseg2 = best_tseg - (SAMPLE_POINT * (best_tseg + 1)) / 100; + if (tseg2 < 0) { + tseg2 = 0; + } else if (tseg2 > MAX_TSEG2) { + tseg2 = MAX_TSEG2; + } + tseg1 = best_tseg - tseg2 - 2; + if (tseg1 > MAX_TSEG1) { + tseg1 = MAX_TSEG1; + tseg2 = best_tseg - tseg1 - 2; + } + + priv->btr = ((best_brp | JUMPWIDTH)<<8) + + ((SAM << 7) | (tseg2 << 4) | tseg1); + + printk(KERN_INFO "%s: calculated best baudrate: %d / btr is 0x%04X\n", + dev->name, best_baud, priv->btr); + + set_btr(dev, (priv->btr>>8) & 0xFF, priv->btr & 0xFF); +// set_btr(dev, best_brp | JUMPWIDTH, (SAM << 7) | (tseg2 << 4) | tseg1); +} + +int set_reset_mode(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned char status = hw_readreg(dev->base_addr, REG_MOD); + int i; + + priv->can_stats.bus_error_at_init = priv->can_stats.bus_error; + + /* disable interrupts */ + hw_writereg(dev->base_addr, REG_IER, IRQ_OFF); + + for (i = 0; i < 10; i++) { + /* check reset bit */ + if (status & MOD_RM) { + if (i > 1) { + iDBG(KERN_INFO "%s: %s looped %d times\n", + dev->name, __FUNCTION__, i); + } + priv->state = STATE_RESET_MODE; + return 0; + } + + hw_writereg(dev->base_addr, REG_MOD, MOD_RM); /* reset chip */ + status = hw_readreg(dev->base_addr, REG_MOD); + + } + + printk(KERN_ERR "%s: setting sja1000 into reset mode failed!\n", + dev->name); + return 1; + +} + +static int set_normal_mode(struct net_device *dev) +{ + unsigned char status = hw_readreg(dev->base_addr, REG_MOD); + int i; + + for (i = 0; i < 10; i++) { + /* check reset bit */ + if ((status & MOD_RM) == 0) { +#ifdef CONFIG_CAN_DEBUG_DEVICES + if (i > 1) { + struct can_priv *priv = netdev_priv(dev); + iDBG(KERN_INFO "%s: %s looped %d times\n", + dev->name, __FUNCTION__, i); + } +#endif + return 0; + } + + /* set chip to normal mode */ + hw_writereg(dev->base_addr, REG_MOD, 0x00); + status = hw_readreg(dev->base_addr, REG_MOD); + } + + printk(KERN_ERR "%s: setting sja1000 into normal mode failed!\n", + dev->name); + return 1; + +} + +static int set_listen_mode(struct net_device *dev) +{ + unsigned char status = hw_readreg(dev->base_addr, REG_MOD); + int i; + + for (i = 0; i < 10; i++) { + /* check reset mode bit */ + if ((status & MOD_RM) == 0) { +#ifdef CONFIG_CAN_DEBUG_DEVICES + if (i > 1) { + struct can_priv *priv = netdev_priv(dev); + iDBG(KERN_INFO "%s: %s looped %d times\n", + dev->name, __FUNCTION__, i); + } +#endif + return 0; + } + + /* set listen only mode, clear reset */ + hw_writereg(dev->base_addr, REG_MOD, MOD_LOM); + status = hw_readreg(dev->base_addr, REG_MOD); + } + + printk(KERN_ERR "%s: setting sja1000 into listen mode failed!\n", + dev->name); + return 1; + +} + +/* + * initialize SJA1000 chip: + * - reset chip + * - set output mode + * - set baudrate + * - enable interrupts + * - start operating mode + */ +static void chipset_init_regs(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + unsigned long base = dev->base_addr; + + /* go into Pelican mode, disable clkout, disable comparator */ + hw_writereg(base, REG_CDR, 0xCF); + + /* output control */ + /* connected to external transceiver */ + hw_writereg(base, REG_OCR, 0x1A); + + /* set acceptance filter (accept all) */ + hw_writereg(base, REG_ACCC0, 0x00); + hw_writereg(base, REG_ACCC1, 0x00); + hw_writereg(base, REG_ACCC2, 0x00); + hw_writereg(base, REG_ACCC3, 0x00); + + hw_writereg(base, REG_ACCM0, 0xFF); + hw_writereg(base, REG_ACCM1, 0xFF); + hw_writereg(base, REG_ACCM2, 0xFF); + hw_writereg(base, REG_ACCM3, 0xFF); + + /* set baudrate */ + if (priv->btr) { /* no calculation when btr is provided */ + set_btr(dev, (priv->btr>>8) & 0xFF, priv->btr & 0xFF); + } else { + if (priv->speed == 0) { + priv->speed = DEFAULT_SPEED; + } + set_baud(dev, priv->speed * 1000, priv->clock); + } + + /* output control */ + /* connected to external transceiver */ + hw_writereg(base, REG_OCR, 0x1A); +} + +static void chipset_init(struct net_device *dev, int wake) +{ + struct can_priv *priv = netdev_priv(dev); + + if (priv->rx_probe) + chipset_init_rx(dev); /* wait for valid reception first */ + else + chipset_init_trx(dev); + + if ((wake) && netif_queue_stopped(dev)) { + if (priv->echo_skb) { /* pending echo? */ + kfree_skb(priv->echo_skb); + priv->echo_skb = NULL; + } + netif_wake_queue(dev); + } +} + +static void chipset_init_rx(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + iDBG(KERN_INFO "%s: %s()\n", dev->name, __FUNCTION__); + + /* set chip into reset mode */ + set_reset_mode(dev); + + /* set registers */ + chipset_init_regs(dev); + + /* automatic bit rate detection */ + set_listen_mode(dev); + + priv->state = STATE_PROBE; + + /* enable receive and error interrupts */ + hw_writereg(dev->base_addr, REG_IER, IRQ_RI | IRQ_EI); +} + +static void chipset_init_trx(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + iDBG(KERN_INFO "%s: %s()\n", dev->name, __FUNCTION__); + + /* set chip into reset mode */ + set_reset_mode(dev); + + /* set registers */ + chipset_init_regs(dev); + + /* leave reset mode */ + set_normal_mode(dev); + + priv->state = STATE_ACTIVE; + + /* enable all interrupts */ + hw_writereg(dev->base_addr, REG_IER, IRQ_ALL); +} + +/* + * transmit a CAN message + * message layout in the sk_buff should be like this: + * xx xx xx xx ff ll 00 11 22 33 44 55 66 77 + * [ can-id ] [flags] [len] [can data (up to 8 bytes] + */ +static int can_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf = (struct can_frame*)skb->data; + unsigned long base = dev->base_addr; + uint8_t fi; + uint8_t dlc; + canid_t id; + uint8_t dreg; + int loop; + int i; + + netif_stop_queue(dev); + + fi = dlc = cf->can_dlc; + id = cf->can_id; + + if (id & CAN_RTR_FLAG) + fi |= FI_RTR; + + if (id & CAN_EFF_FLAG) { + fi |= FI_FF; + dreg = EFF_BUF; + hw_writereg(base, REG_FI, fi); + hw_writereg(base, REG_ID1, (id & 0x1fe00000) >> (5 + 16)); + hw_writereg(base, REG_ID2, (id & 0x001fe000) >> (5 + 8)); + hw_writereg(base, REG_ID3, (id & 0x00001fe0) >> 5); + hw_writereg(base, REG_ID4, (id & 0x0000001f) << 3); + } else { + dreg = SFF_BUF; + hw_writereg(base, REG_FI, fi); + hw_writereg(base, REG_ID1, (id & 0x000007f8) >> 3); + hw_writereg(base, REG_ID2, (id & 0x00000007) << 5); + } + + for (i = 0; i < dlc; i++) { + hw_writereg(base, dreg++, cf->data[i]); + } + + hw_writereg(base, REG_CMR, CMD_TR); + + stats->tx_bytes += dlc; + + dev->trans_start = jiffies; + + /* set flag whether this packet has to be looped back */ + loop = skb->pkt_type == PACKET_LOOPBACK; + + if (!echo || !loop) { + kfree_skb(skb); + return 0; + } + + if (!priv->echo_skb) { + struct sock *srcsk = skb->sk; + + if (atomic_read(&skb->users) != 1) { + struct sk_buff *old_skb = skb; + + skb = skb_clone(old_skb, GFP_ATOMIC); + DBG(KERN_INFO "%s: %s: freeing old skbuff %p, " + "using new skbuff %p\n", + dev->name, __FUNCTION__, old_skb, skb); + kfree_skb(old_skb); + if (!skb) { + return 0; + } + } else + skb_orphan(skb); + + skb->sk = srcsk; + + /* make settings for echo to reduce code in irq context */ + skb->protocol = htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->dev = dev; + + /* save this skb for tx interrupt echo handling */ + priv->echo_skb = skb; + + } else { + /* locking problem with netif_stop_queue() ?? */ + printk(KERN_ERR "%s: %s: occupied echo_skb!\n", + dev->name, __FUNCTION__ ); + kfree_skb(skb); + } + + return 0; +} + +static void can_tx_timeout(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + + stats->tx_errors++; + + /* do not conflict with e.g. bus error handling */ + if (!(priv->timer.expires)){ /* no restart on the run */ + chipset_init_trx(dev); /* no tx queue wakeup */ + if (priv->echo_skb) { /* pending echo? */ + kfree_skb(priv->echo_skb); + priv->echo_skb = NULL; + } + netif_wake_queue(dev); /* wakeup here */ + } + else + DBG(KERN_INFO "%s: %s: can_restart_dev already active.\n", + dev->name, __FUNCTION__ ); + +} + +static void can_restart_on(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + if (!(priv->timer.expires)){ /* no restart on the run */ + + set_reset_mode(dev); + + priv->timer.function = can_restart_dev; + priv->timer.data = (unsigned long) dev; + + /* restart chip on persistent error in <xxx> ms */ + priv->timer.expires = jiffies + (priv->restart_ms * HZ) / 1000; + add_timer(&priv->timer); + + iDBG(KERN_INFO "%s: %s start (%ld)\n", + dev->name, __FUNCTION__ , jiffies); + } else + iDBG(KERN_INFO "%s: %s already (%ld)\n", + dev->name, __FUNCTION__ , jiffies); +} + +static void can_restart_dev(unsigned long data) +{ + struct net_device *dev = (struct net_device*) data; + struct can_priv *priv = netdev_priv(dev); + + DBG(KERN_INFO "%s: can_restart_dev (%ld)\n", + dev->name, jiffies); + + /* mark inactive timer */ + priv->timer.expires = 0; + + if (priv->state != STATE_UNINITIALIZED) { + + /* count number of restarts */ + priv->can_stats.restarts++; + + chipset_init(dev, 1); + } +} + +#if 0 +/* the timerless version */ + +static void can_restart_now(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + if (priv->state != STATE_UNINITIALIZED) { + + /* count number of restarts */ + priv->can_stats.restarts++; + + chipset_init(dev, 1); + } +} +#endif + +static void can_rx(struct net_device *dev) +{ + struct net_device_stats *stats = &dev->stats; + unsigned long base = dev->base_addr; + struct can_frame *cf; + struct sk_buff *skb; + uint8_t fi; + uint8_t dreg; + canid_t id; + uint8_t dlc; + int i; + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb == NULL) { + return; + } + skb->dev = dev; + skb->protocol = htons(ETH_P_CAN); + + fi = hw_readreg(base, REG_FI); + dlc = fi & 0x0F; + + if (fi & FI_FF) { + /* extended frame format (EFF) */ + dreg = EFF_BUF; + id = (hw_readreg(base, REG_ID1) << (5+16)) + | (hw_readreg(base, REG_ID2) << (5+8)) + | (hw_readreg(base, REG_ID3) << 5) + | (hw_readreg(base, REG_ID4) >> 3); + id |= CAN_EFF_FLAG; + } else { + /* standard frame format (SFF) */ + dreg = SFF_BUF; + id = (hw_readreg(base, REG_ID1) << 3) + | (hw_readreg(base, REG_ID2) >> 5); + } + + if (fi & FI_RTR) + id |= CAN_RTR_FLAG; + + cf = (struct can_frame*)skb_put(skb, sizeof(struct can_frame)); + memset(cf, 0, sizeof(struct can_frame)); + cf->can_id = id; + cf->can_dlc = dlc; + for (i = 0; i < dlc; i++) { + cf->data[i] = hw_readreg(base, dreg++); + } + while (i < 8) + cf->data[i++] = 0; + + /* release receive buffer */ + hw_writereg(base, REG_CMR, CMD_RRB); + + netif_rx(skb); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += dlc; +} + +/* + * SJA1000 interrupt handler + */ +static irqreturn_t can_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device*)dev_id; + struct can_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + unsigned long base = dev->base_addr; + uint8_t isrc, status, ecc, alc; + int n = 0; + + hw_preirq(dev); + + iiDBG(KERN_INFO "%s: interrupt\n", dev->name); + + if (priv->state == STATE_UNINITIALIZED) { + printk(KERN_ERR "%s: %s: uninitialized controller!\n", + dev->name, __FUNCTION__); + chipset_init(dev, 1); /* should be possible at this stage */ + return IRQ_NONE; + } + + if (priv->state == STATE_RESET_MODE) { + iiDBG(KERN_ERR "%s: %s: controller is in reset mode! " + "MOD=0x%02X IER=0x%02X IR=0x%02X SR=0x%02X!\n", + dev->name, __FUNCTION__, hw_readreg(base, REG_MOD), + hw_readreg(base, REG_IER), hw_readreg(base, REG_IR), + hw_readreg(base, REG_SR)); + return IRQ_NONE; + } + + while ((isrc = hw_readreg(base, REG_IR)) && (n < 20)) { + n++; + status = hw_readreg(base, REG_SR); + + if (isrc & IRQ_WUI) { + /* wake-up interrupt */ + priv->can_stats.wakeup++; + } + if (isrc & IRQ_TI) { + /* transmission complete interrupt */ + stats->tx_packets++; + + if (echo && priv->echo_skb) { + netif_rx(priv->echo_skb); + priv->echo_skb = NULL; + } + + netif_wake_queue(dev); + } + if (isrc & IRQ_RI) { + /* receive interrupt */ + + while (status & SR_RBS) { + can_rx(dev); + status = hw_readreg(base, REG_SR); + } + if (priv->state == STATE_PROBE) { + /* valid RX -> switch to trx-mode */ + iDBG(KERN_INFO "%s: RI #%d#\n", dev->name, n); + chipset_init_trx(dev); /* no tx queue wakeup */ + break; /* check again after init controller */ + } + } + if (isrc & IRQ_DOI) { + /* data overrun interrupt */ + iiDBG(KERN_INFO "%s: data overrun isrc=0x%02X " + "status=0x%02X\n", + dev->name, isrc, status); + iDBG(KERN_INFO "%s: DOI #%d#\n", dev->name, n); + priv->can_stats.data_overrun++; + hw_writereg(base, REG_CMR, CMD_CDO); /* clear bit */ + } + if (isrc & IRQ_EI) { + /* error warning interrupt */ + iiDBG(KERN_INFO "%s: error warning isrc=0x%02X " + "status=0x%02X\n", + dev->name, isrc, status); + iDBG(KERN_INFO "%s: EI #%d#\n", dev->name, n); + priv->can_stats.error_warning++; + if (status & SR_BS) { + printk(KERN_INFO "%s: BUS OFF, " + "restarting device\n", dev->name); + can_restart_on(dev); + /* controller has been restarted: leave here */ + goto out; + } else if (status & SR_ES) { + iDBG(KERN_INFO "%s: error\n", dev->name); + } + } + if (isrc & IRQ_BEI) { + /* bus error interrupt */ + iiDBG(KERN_INFO "%s: bus error isrc=0x%02X " + "status=0x%02X\n", + dev->name, isrc, status); + iDBG(KERN_INFO "%s: BEI #%d# [%d]\n", dev->name, n, + priv->can_stats.bus_error - + priv->can_stats.bus_error_at_init); + priv->can_stats.bus_error++; + ecc = hw_readreg(base, REG_ECC); + iDBG(KERN_INFO "%s: ECC = 0x%02X (%s, %s, %s)\n", + dev->name, ecc, + (ecc & ECC_DIR) ? "RX" : "TX", + ecc_types[ecc >> ECC_ERR], + ecc_errors[ecc & ECC_SEG]); + + /* when the bus errors flood the system, */ + /* restart the controller */ + if (priv->can_stats.bus_error_at_init + + MAX_BUS_ERRORS < priv->can_stats.bus_error) { + iDBG(KERN_INFO "%s: heavy bus errors," + " restarting device\n", dev->name); + can_restart_on(dev); + /* controller has been restarted: leave here */ + goto out; + } +#if 1 + /* don't know, if this is a good idea, */ + /* but it works fine ... */ + if (hw_readreg(base, REG_RXERR) > 128) { + iDBG(KERN_INFO "%s: RX_ERR > 128," + " restarting device\n", dev->name); + can_restart_on(dev); + /* controller has been restarted: leave here */ + goto out; + } +#endif + } + if (isrc & IRQ_EPI) { + /* error passive interrupt */ + iiDBG(KERN_INFO "%s: error passive isrc=0x%02X" + " status=0x%02X\n", + dev->name, isrc, status); + iDBG(KERN_INFO "%s: EPI #%d#\n", dev->name, n); + priv->can_stats.error_passive++; + if (status & SR_ES) { + iDBG(KERN_INFO "%s: -> ERROR PASSIVE, " + "restarting device\n", dev->name); + can_restart_on(dev); + /* controller has been restarted: leave here */ + goto out; + } else { + iDBG(KERN_INFO "%s: -> ERROR ACTIVE\n", + dev->name); + } + } + if (isrc & IRQ_ALI) { + /* arbitration lost interrupt */ + iiDBG(KERN_INFO "%s: error arbitration lost " + "isrc=0x%02X status=0x%02X\n", + dev->name, isrc, status); + iDBG(KERN_INFO "%s: ALI #%d#\n", dev->name, n); + priv->can_stats.arbitration_lost++; + alc = hw_readreg(base, REG_ALC); + iDBG(KERN_INFO "%s: ALC = 0x%02X\n", dev->name, alc); + } + } + if (n > 1) { + iDBG(KERN_INFO "%s: handled %d IRQs\n", dev->name, n); + } +out: + hw_postirq(dev); + + return n == 0 ? IRQ_NONE : IRQ_HANDLED; +} + +/* + * initialize CAN bus driver + */ +static int can_open(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + /* set chip into reset mode */ + set_reset_mode(dev); + + priv->state = STATE_UNINITIALIZED; + + /* register interrupt handler */ + if (request_irq(dev->irq, &can_interrupt, IRQF_SHARED, + dev->name, (void*)dev)) { + return -EAGAIN; + } + + /* init chip */ + chipset_init(dev, 0); + priv->open_time = jiffies; + + netif_start_queue(dev); + + return 0; +} + +/* + * stop CAN bus activity + */ +static int can_close(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + /* set chip into reset mode */ + set_reset_mode(dev); + + priv->open_time = 0; + + if (priv->timer.expires) { + del_timer(&priv->timer); + priv->timer.expires = 0; + } + + free_irq(dev->irq, (void*)dev); + priv->state = STATE_UNINITIALIZED; + + netif_stop_queue(dev); + + return 0; +} + +#if 0 +static void test_if(struct net_device *dev) +{ + int i; + int j; + int x; + + hw_writereg(base, REG_CDR, 0xCF); + for (i = 0; i < 10000; i++) { + for (j = 0; j < 256; j++) { + hw_writereg(base, REG_EWL, j); + x = hw_readreg(base, REG_EWL); + if (x != j) { + printk(KERN_INFO "%s: is: %02X expected: " + "%02X (%d)\n", dev->name, x, j, i); + } + } + } +} +#endif + +void can_netdev_setup(struct net_device *dev) +{ + /* Fill in the the fields of the device structure + with CAN netdev generic values */ + + dev->change_mtu = NULL; + dev->set_mac_address = NULL; + dev->header_ops = NULL; + + dev->type = ARPHRD_CAN; + dev->hard_header_len = 0; + dev->mtu = sizeof(struct can_frame); + dev->addr_len = 0; + dev->tx_queue_len = 10; + + dev->flags = IFF_NOARP; + + /* set flags according to driver capabilities */ + if (echo) + dev->flags |= IFF_ECHO; + + dev->features = NETIF_F_NO_CSUM; + + dev->open = can_open; + dev->stop = can_close; + dev->hard_start_xmit = can_start_xmit; + + dev->tx_timeout = can_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +} + +static struct net_device* can_create_netdev(int dev_num, int hw_regs) +{ + struct net_device *dev; + struct can_priv *priv; + + if (!(dev = alloc_netdev(sizeof(struct can_priv), CAN_NETDEV_NAME, + can_netdev_setup))) { + printk(KERN_ERR "%s: out of memory\n", CHIP_NAME); + return NULL; + } + + printk(KERN_INFO "%s: base 0x%lX / irq %d / speed %d / " + "btr 0x%X / rx_probe %d\n", + drv_name, rbase[dev_num], irq[dev_num], + speed[dev_num], btr[dev_num], rx_probe[dev_num]); + + /* fill net_device structure */ + + priv = netdev_priv(dev); + + dev->irq = irq[dev_num]; + dev->base_addr = rbase[dev_num]; + + priv->speed = speed[dev_num]; + priv->btr = btr[dev_num]; + priv->rx_probe = rx_probe[dev_num]; + priv->clock = clk; + priv->hw_regs = hw_regs; + priv->restart_ms = restart_ms; + priv->debug = debug; + + init_timer(&priv->timer); + priv->timer.expires = 0; + + if (register_netdev(dev)) { + printk(KERN_INFO "%s: register netdev failed\n", CHIP_NAME); + free_netdev(dev); + return NULL; + } + + return dev; +} + +int can_set_drv_name(void) +{ + char *hname = hal_name(); + + if (strlen(CHIP_NAME) + strlen(hname) >= DRV_NAME_LEN-1) { + printk(KERN_ERR "%s: driver name too long!\n", CHIP_NAME); + return -EINVAL; + } + sprintf(drv_name, "%s-%s", CHIP_NAME, hname); + return 0; +} + +static void sja1000_exit_module(void) +{ + int i, ret; + + for (i = 0; i < MAXDEV; i++) { + if (can_dev[i] != NULL) { + struct can_priv *priv = netdev_priv(can_dev[i]); + unregister_netdev(can_dev[i]); + del_timer(&priv->timer); + hw_detach(i); + hal_release_region(i, SJA1000_IO_SIZE_BASIC); + free_netdev(can_dev[i]); + } + } + can_proc_remove(drv_name); + + if ((ret = hal_exit())) + printk(KERN_INFO "%s: hal_exit error %d.\n", drv_name, ret); +} + +static __init int sja1000_init_module(void) +{ + int i, ret; + struct net_device *dev; + + if ((ret = hal_init())) + return ret; + + if ((ret = can_set_drv_name())) + return ret; + + if (clk < 1000 ) /* MHz command line value */ + clk *= 1000000; + + if (clk < 1000000 ) /* kHz command line value */ + clk *= 1000; + + printk(KERN_INFO "%s driver v%s (%s)\n", + drv_name, drv_version, drv_reldate); + printk(KERN_INFO "%s - options [clk %d.%06d MHz] [restart_ms %dms]" + " [debug %d]\n", + drv_name, clk/1000000, clk%1000000, restart_ms, debug); + + if (!base[0]) { + printk(KERN_INFO "%s: loading defaults.\n", drv_name); + hal_use_defaults(); + } + + for (i = 0; base[i]; i++) { + printk(KERN_DEBUG "%s: checking for %s on address 0x%lX ...\n", + drv_name, CHIP_NAME, base[i]); + + if (!hal_request_region(i, SJA1000_IO_SIZE_BASIC, drv_name)) { + printk(KERN_ERR "%s: memory already in use\n", + drv_name); + sja1000_exit_module(); + return -EBUSY; + } + + hw_attach(i); + hw_reset_dev(i); + + if (!sja1000_probe_chip(rbase[i])) { + printk(KERN_ERR "%s: probably missing controller" + " hardware\n", drv_name); + hw_detach(i); + hal_release_region(i, SJA1000_IO_SIZE_BASIC); + sja1000_exit_module(); + return -ENODEV; + } + + dev = can_create_netdev(i, SJA1000_IO_SIZE_BASIC); + + if (dev != NULL) { + can_dev[i] = dev; + set_reset_mode(dev); + can_proc_create(drv_name); + } else { + can_dev[i] = NULL; + hw_detach(i); + hal_release_region(i, SJA1000_IO_SIZE_BASIC); + } + } + return 0; +} + +module_init(sja1000_init_module); +module_exit(sja1000_exit_module); + diff --git a/drivers/net/can/old/sja1000/sja1000.h b/drivers/net/can/old/sja1000/sja1000.h new file mode 100644 index 000000000000..a47b2f629136 --- /dev/null +++ b/drivers/net/can/old/sja1000/sja1000.h @@ -0,0 +1,187 @@ +/* + * sja1000.h - Philips SJA1000 network device driver + * + * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33, + * 38106 Braunschweig, GERMANY + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#ifndef SJA1000_H +#define SJA1000_H + +#define SJA1000_IO_SIZE_BASIC 0x20 +#define SJA1000_IO_SIZE_PELICAN 0x80 /* unused */ + +#define CHIP_NAME "sja1000" + +#define DRV_NAME_LEN 30 /* for "<chip_name>-<hal_name>" */ + +#define PROCBASE "driver" /* /proc/ ... */ + +#define DEFAULT_HW_CLK 16000000 +#define DEFAULT_SPEED 500 /* kBit/s */ + +#define CAN_NETDEV_NAME "can%d" + +#define TX_TIMEOUT (50*HZ/1000) /* 50ms */ +#define RESTART_MS 100 /* restart chip on persistent errors in 100ms */ +#define MAX_BUS_ERRORS 200 /* prevent from flooding bus error interrupts */ + +/* SJA1000 registers - manual section 6.4 (Pelican Mode) */ +#define REG_MOD 0x00 +#define REG_CMR 0x01 +#define REG_SR 0x02 +#define REG_IR 0x03 +#define REG_IER 0x04 +#define REG_ALC 0x0B +#define REG_ECC 0x0C +#define REG_EWL 0x0D +#define REG_RXERR 0x0E +#define REG_TXERR 0x0F +#define REG_ACCC0 0x10 +#define REG_ACCC1 0x11 +#define REG_ACCC2 0x12 +#define REG_ACCC3 0x13 +#define REG_ACCM0 0x14 +#define REG_ACCM1 0x15 +#define REG_ACCM2 0x16 +#define REG_ACCM3 0x17 +#define REG_RMC 0x1D +#define REG_RBSA 0x1E + +/* Common registers - manual section 6.5 */ +#define REG_BTR0 0x06 +#define REG_BTR1 0x07 +#define REG_OCR 0x08 +#define REG_CDR 0x1F + +#define REG_FI 0x10 +#define SFF_BUF 0x13 +#define EFF_BUF 0x15 + +#define FI_FF 0x80 +#define FI_RTR 0x40 + +#define REG_ID1 0x11 +#define REG_ID2 0x12 +#define REG_ID3 0x13 +#define REG_ID4 0x14 + +#define CAN_RAM 0x20 + +/* mode register */ +#define MOD_RM 0x01 +#define MOD_LOM 0x02 +#define MOD_STM 0x04 +#define MOD_AFM 0x08 +#define MOD_SM 0x10 + +/* commands */ +#define CMD_SRR 0x10 +#define CMD_CDO 0x08 +#define CMD_RRB 0x04 +#define CMD_AT 0x02 +#define CMD_TR 0x01 + +/* interrupt sources */ +#define IRQ_BEI 0x80 +#define IRQ_ALI 0x40 +#define IRQ_EPI 0x20 +#define IRQ_WUI 0x10 +#define IRQ_DOI 0x08 +#define IRQ_EI 0x04 +#define IRQ_TI 0x02 +#define IRQ_RI 0x01 +#define IRQ_ALL 0xFF +#define IRQ_OFF 0x00 + +/* status register content */ +#define SR_BS 0x80 +#define SR_ES 0x40 +#define SR_TS 0x20 +#define SR_RS 0x10 +#define SR_TCS 0x08 +#define SR_TBS 0x04 +#define SR_DOS 0x02 +#define SR_RBS 0x01 + +#define SR_CRIT (SR_BS|SR_ES) + +/* ECC register */ +#define ECC_DIR 0x20 +#define ECC_SEG 0x1F +#define ECC_ERR 6 + +/* bus timing */ +#define MAX_TSEG1 15 +#define MAX_TSEG2 7 +#define SAMPLE_POINT 75 +#define JUMPWIDTH 0x40 + +/* CAN private data structure */ + +struct can_priv { + struct can_device_stats can_stats; + long open_time; + int clock; + int hw_regs; + int restart_ms; + int debug; + int speed; + int btr; + int rx_probe; + struct timer_list timer; + int state; + struct sk_buff *echo_skb; +}; + +#define STATE_UNINITIALIZED 0 +#define STATE_PROBE 1 +#define STATE_ACTIVE 2 +#define STATE_ERROR_ACTIVE 3 +#define STATE_ERROR_PASSIVE 4 +#define STATE_BUS_OFF 5 +#define STATE_RESET_MODE 6 + +void can_proc_create(const char *drv_name); +void can_proc_remove(const char *drv_name); + +#endif /* SJA1000_H */ diff --git a/drivers/net/can/sja1000/Makefile b/drivers/net/can/sja1000/Makefile new file mode 100644 index 000000000000..176393a0001d --- /dev/null +++ b/drivers/net/can/sja1000/Makefile @@ -0,0 +1,30 @@ +# +# + +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common +#EXTRA_CFLAGS += -I$(TOPDIR)/drivers/net/can/hal + +obj-$(CONFIG_CAN_SJA1000) += sja1000.o +obj-$(CONFIG_CAN_SJA1000_PLATFORM) += sja1000_platform.o +obj-$(CONFIG_CAN_SJA1000_OF_PLATFORM) += sja1000_of_platform.o +obj-$(CONFIG_CAN_EMS_PCI) += ems_pci.o +obj-$(CONFIG_CAN_EMS_PCMCIA) += ems_pcmcia.o +obj-$(CONFIG_CAN_IXXAT_PCI) += ixxat_pci.o +obj-$(CONFIG_CAN_PEAK_PCI) += peak_pci.o +obj-$(CONFIG_CAN_PIPCAN) += pipcan.o +obj-$(CONFIG_CAN_KVASER_PCI) += kvaser_pci.o + +ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG + +endif diff --git a/drivers/net/can/sja1000/ems_pci.c b/drivers/net/can/sja1000/ems_pci.c new file mode 100644 index 000000000000..6076375e2a96 --- /dev/null +++ b/drivers/net/can/sja1000/ems_pci.c @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com> + * Copyright (C) 2008 Markus Plessing <plessing@ems-wuensche.com> + * Copyright (C) 2008 Sebastian Haas <haas@ems-wuensche.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/io.h> + +#include "sja1000.h" + +#define DRV_NAME "ems_pci" + +MODULE_AUTHOR("Sebastian Haas <haas@ems-wuenche.com>"); +MODULE_DESCRIPTION("Socket-CAN driver for EMS CPC-PCI/PCIe CAN cards"); +MODULE_SUPPORTED_DEVICE("EMS CPC-PCI/PCIe CAN card"); +MODULE_LICENSE("GPL v2"); + +#define EMS_PCI_MAX_CHAN 2 + +struct ems_pci_card { + int channels; + + struct pci_dev *pci_dev; + struct net_device *net_dev[EMS_PCI_MAX_CHAN]; + + void __iomem *conf_addr; + void __iomem *base_addr; +}; + +#define EMS_PCI_CAN_CLOCK (16000000 / 2) + +/* + * Register definitions and descriptions are from LinCAN 0.3.3. + * + * PSB4610 PITA-2 bridge control registers + */ +#define PITA2_ICR 0x00 /* Interrupt Control Register */ +#define PITA2_ICR_INT0 0x00000002 /* [RC] INT0 Active/Clear */ +#define PITA2_ICR_INT0_EN 0x00020000 /* [RW] Enable INT0 */ + +#define PITA2_MISC 0x1c /* Miscellaneous Register */ +#define PITA2_MISC_CONFIG 0x04000000 /* Multiplexed parallel interface */ + +/* + * The board configuration is probably following: + * RX1 is connected to ground. + * TX1 is not connected. + * CLKO is not connected. + * Setting the OCR register to 0xDA is a good idea. + * This means normal output mode , push-pull and the correct polarity. + */ +#define EMS_PCI_OCR (OCR_TX0_PUSHPULL | OCR_TX1_PUSHPULL) + +/* + * In the CDR register, you should set CBP to 1. + * You will probably also want to set the clock divider value to 7 + * (meaning direct oscillator output) because the second SJA1000 chip + * is driven by the first one CLKOUT output. + */ +#define EMS_PCI_CDR (CDR_CBP | CDR_CLKOUT_MASK) +#define EMS_PCI_MEM_SIZE 4096 /* Size of the remapped io-memory */ +#define EMS_PCI_CAN_BASE_OFFSET 0x400 /* offset where the controllers starts */ +#define EMS_PCI_CAN_CTRL_SIZE 0x200 /* memory size for each controller */ + +#define EMS_PCI_PORT_BYTES 0x4 /* Each register occupies 4 bytes */ + +#define EMS_PCI_VENDOR_ID 0x110a /* PCI device and vendor ID */ +#define EMS_PCI_DEVICE_ID 0x2104 + +static struct pci_device_id ems_pci_tbl[] = { + {EMS_PCI_VENDOR_ID, EMS_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID,}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, ems_pci_tbl); + +/* + * Helper to read internal registers from card logic (not CAN) + */ +static u8 ems_pci_readb(struct ems_pci_card *card, unsigned int port) +{ + return readb((void __iomem *)card->base_addr + + (port * EMS_PCI_PORT_BYTES)); +} + +static u8 ems_pci_read_reg(struct net_device *dev, int port) +{ + return readb((void __iomem *)dev->base_addr + + (port * EMS_PCI_PORT_BYTES)); +} + +static void ems_pci_write_reg(struct net_device *dev, int port, u8 val) +{ + writeb(val, (void __iomem *)dev->base_addr + + (port * EMS_PCI_PORT_BYTES)); +} + +static void ems_pci_post_irq(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct ems_pci_card *card = (struct ems_pci_card *)priv->priv; + + /* reset int flag of pita */ + writel(PITA2_ICR_INT0_EN | PITA2_ICR_INT0, card->conf_addr + + PITA2_ICR); +} + +/* + * Check if a CAN controller is present at the specified location + * by trying to set 'em into the PeliCAN mode + */ +static inline int ems_pci_check_chan(struct net_device *dev) +{ + unsigned char res; + + /* Make sure SJA1000 is in reset mode */ + ems_pci_write_reg(dev, REG_MOD, 1); + + ems_pci_write_reg(dev, REG_CDR, CDR_PELICAN); + + /* read reset-values */ + res = ems_pci_read_reg(dev, REG_CDR); + + if (res == CDR_PELICAN) + return 1; + + return 0; +} + +static void ems_pci_del_card(struct pci_dev *pdev) +{ + struct ems_pci_card *card = pci_get_drvdata(pdev); + struct net_device *dev; + int i = 0; + + for (i = 0; i < card->channels; i++) { + dev = card->net_dev[i]; + + if (!dev) + continue; + + dev_info(&pdev->dev, "Removing %s.\n", dev->name); + unregister_sja1000dev(dev); + free_sja1000dev(dev); + } + + if (card->base_addr != NULL) + pci_iounmap(card->pci_dev, card->base_addr); + + if (card->conf_addr != NULL) + pci_iounmap(card->pci_dev, card->conf_addr); + + kfree(card); + + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +} + +static void ems_pci_card_reset(struct ems_pci_card *card) +{ + /* Request board reset */ + writeb(0, card->base_addr); +} + +/* + * Probe PCI device for EMS CAN signature and register each available + * CAN channel to SJA1000 Socket-CAN subsystem. + */ +static int __devinit ems_pci_add_card(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct sja1000_priv *priv; + struct net_device *dev; + struct ems_pci_card *card; + int err, i; + + /* Enabling PCI device */ + if (pci_enable_device(pdev) < 0) { + dev_err(&pdev->dev, "Enabling PCI device failed\n"); + return -ENODEV; + } + + /* Allocating card structures to hold addresses, ... */ + card = kzalloc(sizeof(struct ems_pci_card), GFP_KERNEL); + if (card == NULL) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + pci_disable_device(pdev); + return -ENOMEM; + } + + pci_set_drvdata(pdev, card); + + card->pci_dev = pdev; + + card->channels = 0; + + /* Remap PITA configuration space, and controller memory area */ + card->conf_addr = pci_iomap(pdev, 0, EMS_PCI_MEM_SIZE); + if (card->conf_addr == NULL) { + err = -ENOMEM; + + goto failure_cleanup; + } + + card->base_addr = pci_iomap(pdev, 1, EMS_PCI_MEM_SIZE); + if (card->base_addr == NULL) { + err = -ENOMEM; + + goto failure_cleanup; + } + + /* Configure PITA-2 parallel interface (enable MUX) */ + writel(PITA2_MISC_CONFIG, card->conf_addr + PITA2_MISC); + + /* Check for unique EMS CAN signature */ + if (ems_pci_readb(card, 0) != 0x55 || + ems_pci_readb(card, 1) != 0xAA || + ems_pci_readb(card, 2) != 0x01 || + ems_pci_readb(card, 3) != 0xCB || + ems_pci_readb(card, 4) != 0x11) { + dev_err(&pdev->dev, "Not EMS Dr. Thomas Wuensche interface\n"); + + err = -ENODEV; + goto failure_cleanup; + } + + ems_pci_card_reset(card); + + /* Detect available channels */ + for (i = 0; i < EMS_PCI_MAX_CHAN; i++) { + dev = alloc_sja1000dev(0); + if (dev == NULL) { + err = -ENOMEM; + goto failure_cleanup; + } + + card->net_dev[i] = dev; + priv = netdev_priv(dev); + priv->priv = card; + + dev->irq = pdev->irq; + dev->base_addr = (unsigned long)(card->base_addr + + EMS_PCI_CAN_BASE_OFFSET + + (i * EMS_PCI_CAN_CTRL_SIZE)); + + /* Check if channel is present */ + if (ems_pci_check_chan(dev)) { + priv->read_reg = ems_pci_read_reg; + priv->write_reg = ems_pci_write_reg; + priv->post_irq = ems_pci_post_irq; + priv->can.bittiming.clock = EMS_PCI_CAN_CLOCK; + priv->ocr = EMS_PCI_OCR; + priv->cdr = EMS_PCI_CDR; + + SET_NETDEV_DEV(dev, &pdev->dev); + + /* Enable interrupts from card */ + writel(PITA2_ICR_INT0_EN, card->conf_addr + PITA2_ICR); + + /* Register SJA1000 device */ + err = register_sja1000dev(dev); + if (err) { + dev_err(&pdev->dev, "Registering device failed " + "(err=%d)\n", err); + free_sja1000dev(dev); + goto failure_cleanup; + } + + card->channels++; + + dev_info(&pdev->dev, "Channel #%d at %#lX, irq %d\n", + i + 1, dev->base_addr, + dev->irq); + } else { + free_sja1000dev(dev); + } + } + + return 0; + +failure_cleanup: + dev_err(&pdev->dev, "Error: %d. Cleaning Up.\n", err); + + ems_pci_del_card(pdev); + + return err; +} + +static struct pci_driver ems_pci_driver = { + .name = DRV_NAME, + .id_table = ems_pci_tbl, + .probe = ems_pci_add_card, + .remove = ems_pci_del_card, +}; + +static int __init ems_pci_init(void) +{ + return pci_register_driver(&ems_pci_driver); +} + +static void __exit ems_pci_exit(void) +{ + pci_unregister_driver(&ems_pci_driver); +} + +module_init(ems_pci_init); +module_exit(ems_pci_exit); + diff --git a/drivers/net/can/sja1000/ems_pcmcia.c b/drivers/net/can/sja1000/ems_pcmcia.c new file mode 100644 index 000000000000..7c9b88b30301 --- /dev/null +++ b/drivers/net/can/sja1000/ems_pcmcia.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2008 Sebastian Haas <haas@ems-wuensche.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <asm/io.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include "sja1000.h" + +#define DRV_NAME "ems_pcmcia" + +MODULE_AUTHOR("Sebastian Haas <haas@ems-wuenche.com>"); +MODULE_DESCRIPTION("Socket-CAN driver for EMS CPC-CARD cards"); +MODULE_SUPPORTED_DEVICE("EMS CPC-CARD CAN card"); +MODULE_LICENSE("GPL v2"); + +static int debug; + +module_param(debug, int, S_IRUGO | S_IWUSR); + +MODULE_PARM_DESC(debug, "Set debug level (default: 0)"); + +#define EMS_PCMCIA_MAX_CHAN 2 + +struct ems_pcmcia_card { + int channels; + + struct pcmcia_device *pcmcia_dev; + struct net_device *net_dev[EMS_PCMCIA_MAX_CHAN]; + + void __iomem *base_addr; +}; + +#define EMS_PCMCIA_CAN_CLOCK (16000000 / 2) + +/* + * The board configuration is probably following: + * RX1 is connected to ground. + * TX1 is not connected. + * CLKO is not connected. + * Setting the OCR register to 0xDA is a good idea. + * This means normal output mode , push-pull and the correct polarity. + */ +#define EMS_PCMCIA_OCR (OCR_TX0_PUSHPULL | OCR_TX1_PUSHPULL) + +/* + * In the CDR register, you should set CBP to 1. + * You will probably also want to set the clock divider value to 7 + * (meaning direct oscillator output) because the second SJA1000 chip + * is driven by the first one CLKOUT output. + */ +#define EMS_PCMCIA_CDR (CDR_CBP | CDR_CLKOUT_MASK) +#define EMS_PCMCIA_MEM_SIZE 4096 /* Size of the remapped io-memory */ +#define EMS_PCMCIA_CAN_BASE_OFFSET 0x100 /* Offset where controllers starts */ +#define EMS_PCMCIA_CAN_CTRL_SIZE 0x80 /* Memory size for each controller */ + +#define EMS_CMD_RESET 0x00 /* Perform a reset of the card */ +#define EMS_CMD_MAP 0x03 /* Map CAN controllers into card' memory */ +#define EMS_CMD_UMAP 0x02 /* Unmap CAN controllers from card' memory */ + +static struct pcmcia_device_id ems_pcmcia_tbl[] = { + PCMCIA_DEVICE_PROD_ID123("EMS_T_W", "CPC-Card", "V2.0", 0xeab1ea23, + 0xa338573f, 0xe4575800), + PCMCIA_DEVICE_NULL, +}; + +MODULE_DEVICE_TABLE (pcmcia, ems_pcmcia_tbl); + +static void ems_pcmcia_config(struct pcmcia_device *dev); + +static u8 ems_pcmcia_read_reg(struct net_device *dev, int port) +{ + return readb((void __iomem *)dev->base_addr + port); +} + +static void ems_pcmcia_write_reg(struct net_device *dev, int port, u8 val) +{ + writeb(val, (void __iomem *)dev->base_addr + port); +} + +static irqreturn_t ems_pcmcia_interrupt(int irq, void *dev_id) +{ + struct ems_pcmcia_card *card = dev_id; + struct net_device *dev; + irqreturn_t retval = IRQ_NONE; + int i, again; + + do { + again = 0; + + /* Check interrupt for each channel */ + for (i = 0; i < EMS_PCMCIA_MAX_CHAN; i++) { + dev = card->net_dev[i]; + if (!dev) + continue; + + if (sja1000_interrupt(irq, dev) == IRQ_HANDLED) + again = 1; + } + /* At least one channel handled the interrupt */ + if (again) + retval = IRQ_HANDLED; + + } while (again); + + return retval; +} + +/* + * Check if a CAN controller is present at the specified location + * by trying to set 'em into the PeliCAN mode + */ +static inline int ems_pcmcia_check_chan(struct net_device *dev) +{ + unsigned char res; + + /* Make sure SJA1000 is in reset mode */ + ems_pcmcia_write_reg(dev, REG_MOD, 1); + + ems_pcmcia_write_reg(dev, REG_CDR, CDR_PELICAN); + + /* read reset-values */ + res = ems_pcmcia_read_reg(dev, REG_CDR); + + if (res == CDR_PELICAN) + return 1; + + return 0; +} + +static void ems_pcmcia_del_card(struct pcmcia_device *pdev) +{ + struct ems_pcmcia_card *card = pdev->priv; + struct net_device *dev; + int i = 0; + + if (!card) + return; + + free_irq(pdev->irq.AssignedIRQ, card); + + for (i = 0; i < card->channels; i++) { + dev = card->net_dev[i]; + + if (!dev) + continue; + + printk(KERN_INFO "%s: removing %s on channel #%d\n", + DRV_NAME, dev->name, i); + unregister_sja1000dev(dev); + free_sja1000dev(dev); + } + + writeb(EMS_CMD_UMAP, card->base_addr); + + if (card->base_addr != NULL ) + iounmap(card->base_addr); + + kfree(card); + + pdev->priv = NULL; +} + +static void ems_pcmcia_card_reset(struct ems_pcmcia_card *card) +{ + /* Request board reset */ + writeb(EMS_CMD_RESET, card->base_addr); +} + +/* + * Probe PCI device for EMS CAN signature and register each available + * CAN channel to SJA1000 Socket-CAN subsystem. + */ +static int __devinit ems_pcmcia_add_card(struct pcmcia_device *pdev, + unsigned long base) +{ + struct sja1000_priv *priv; + struct net_device *dev; + struct ems_pcmcia_card *card; + int err, i; + + /* Allocating card structures to hold addresses, ... */ + card = kzalloc(sizeof(struct ems_pcmcia_card), GFP_KERNEL); + if (card == NULL) { + printk(KERN_ERR "%s: unable to allocate memory\n", DRV_NAME); + return -ENOMEM; + } + + pdev->priv = card; + + card->channels = 0; + + card->base_addr = ioremap(base, EMS_PCMCIA_MEM_SIZE); + if (card->base_addr == NULL) { + err = -ENOMEM; + goto failure_cleanup; + } + + /* Check for unique EMS CAN signature */ + if (readw(card->base_addr) != 0xAA55) { + printk(KERN_ERR "%s: No EMS CPC Card hardware found.\n", + DRV_NAME); + + err = -ENODEV; + goto failure_cleanup; + } + + ems_pcmcia_card_reset(card); + + /* Make sure CAN controllers are mapped into card's memory space */ + writeb(EMS_CMD_MAP, card->base_addr); + + /* Detect available channels */ + for (i = 0; i < EMS_PCMCIA_MAX_CHAN; i++) { + dev = alloc_sja1000dev(0); + if (dev == NULL) { + err = -ENOMEM; + goto failure_cleanup; + } + + card->net_dev[i] = dev; + priv = netdev_priv(dev); + priv->priv = card; + SET_NETDEV_DEV(dev, &pdev->dev); + + dev->irq = pdev->irq.AssignedIRQ; + dev->base_addr = (unsigned long)(card->base_addr + + EMS_PCMCIA_CAN_BASE_OFFSET + + (i * EMS_PCMCIA_CAN_CTRL_SIZE)); + + /* Check if channel is present */ + if (ems_pcmcia_check_chan(dev)) { + priv->read_reg = ems_pcmcia_read_reg; + priv->write_reg = ems_pcmcia_write_reg; + priv->can.bittiming.clock = EMS_PCMCIA_CAN_CLOCK; + priv->ocr = EMS_PCMCIA_OCR; + priv->cdr = EMS_PCMCIA_CDR; + priv->flags |= SJA1000_CUSTOM_IRQ_HANDLER; + + /* Register SJA1000 device */ + err = register_sja1000dev(dev); + if (err) { + printk(KERN_INFO "%s: registering device " + "failed (err=%d)\n", DRV_NAME, err); + free_sja1000dev(dev); + goto failure_cleanup; + } + + card->channels++; + + printk(KERN_INFO "%s: registered %s on channel " + "#%d at %lX, irq %d\n", DRV_NAME, dev->name, + i, dev->base_addr, dev->irq); + } else { + free_sja1000dev(dev); + } + } + + err = request_irq(dev->irq, &ems_pcmcia_interrupt, IRQF_SHARED, + DRV_NAME, (void *)card); + if (err) { + printk(KERN_INFO "Registering device failed (err=%d)\n", err); + + goto failure_cleanup; + } + + return 0; + +failure_cleanup: + printk(KERN_ERR "Error: %d. Cleaning Up.\n", err); + + ems_pcmcia_del_card(pdev); + + return err; +} + +/* + * Setup PCMCIA socket and probe for EMS CPC-CARD + */ +static int __devinit ems_pcmcia_probe(struct pcmcia_device *dev) +{ + /* The io structure describes IO port mapping */ + dev->io.NumPorts1 = 16; + dev->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + dev->io.NumPorts2 = 16; + dev->io.Attributes2 = IO_DATA_PATH_WIDTH_16; + dev->io.IOAddrLines = 5; + + /* Interrupt setup */ + dev->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + dev->irq.IRQInfo1 = IRQ_LEVEL_ID; + + /* General socket configuration */ + dev->conf.Attributes = CONF_ENABLE_IRQ; + dev->conf.IntType = INT_MEMORY_AND_IO; + dev->conf.ConfigIndex = 1; + dev->conf.Present = PRESENT_OPTION; + + dev->win = NULL; + + ems_pcmcia_config(dev); + + return 0; +} + +/* + * Configure PCMCIA socket + */ +static void ems_pcmcia_config(struct pcmcia_device *dev) +{ + win_req_t req; + memreq_t mem; + + int csval; + + /* Allocate a memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_CM|WIN_ENABLE; + req.Base = req.Size = 0; + req.AccessSpeed = 0; + + csval = pcmcia_request_window(&dev, &req, &dev->win); + if (csval) { + cs_error(dev, RequestWindow, csval); + return; + } + + mem.CardOffset = mem.Page = 0; + mem.CardOffset = dev->conf.ConfigBase; + + csval = pcmcia_map_mem_page(dev->win, &mem); + if (csval) { + cs_error(dev, MapMemPage, csval); + return; + } + + csval = pcmcia_request_irq(dev, &dev->irq); + if (csval) { + cs_error(dev, RequestIRQ, csval); + return; + } + + /* This actually configures the PCMCIA socket */ + csval = pcmcia_request_configuration(dev, &dev->conf); + if (csval) { + cs_error(dev, RequestConfiguration, csval); + return; + } + + ems_pcmcia_add_card(dev, req.Base); +} + +/* + * Release claimed resources + */ +static void ems_pcmcia_remove(struct pcmcia_device *dev) +{ + ems_pcmcia_del_card(dev); + + pcmcia_disable_device(dev); +} + +/* + * The dev_info variable is the "key" that is used to match up this + * device driver with appropriate cards, through the card configuration + * database. + */ +static dev_info_t dev_info = "can-ems-pcmcia"; + +static struct pcmcia_driver ems_pcmcia_driver = { + .drv = { + .name = dev_info, + }, + + .probe = ems_pcmcia_probe, + .remove = ems_pcmcia_remove, + + .id_table = ems_pcmcia_tbl, +}; + +static int __init ems_pcmcia_init(void) +{ + return pcmcia_register_driver(&ems_pcmcia_driver); +} + +static void __exit ems_pcmcia_exit(void) +{ + pcmcia_unregister_driver(&ems_pcmcia_driver); +} + +module_init(ems_pcmcia_init); +module_exit(ems_pcmcia_exit); + diff --git a/drivers/net/can/sja1000/ixxat_pci.c b/drivers/net/can/sja1000/ixxat_pci.c new file mode 100644 index 000000000000..ab98a9d160ec --- /dev/null +++ b/drivers/net/can/sja1000/ixxat_pci.c @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com> + * Copyright (C) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/io.h> + +#include "sja1000.h" + +#define DRV_NAME "ixxat_pci" + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de"); +MODULE_DESCRIPTION("Socket-CAN driver for IXXAT PC-I 04/PCI PCI cards"); +MODULE_SUPPORTED_DEVICE("IXXAT PC-I 04/PCI card"); +MODULE_LICENSE("GPL v2"); + +/* Maximum number of interfaces supported on one card. Currently + * we only support a maximum of two interfaces, which is the maximum + * of what Ixxat sells anyway. + */ +#define IXXAT_PCI_MAX_CAN 2 + +struct ixxat_pci { + struct pci_dev *pci_dev; + struct net_device *dev[IXXAT_PCI_MAX_CAN]; + int conf_addr; + void __iomem *base_addr; +}; + +#define IXXAT_PCI_CAN_CLOCK (16000000 / 2) + +#define IXXAT_PCI_OCR (OCR_TX0_PUSHPULL | OCR_TX0_INVERT | \ + OCR_TX1_PUSHPULL) +#define IXXAT_PCI_CDR 0 + +#define CHANNEL_RESET_OFFSET 0x110 +#define CHANNEL_OFFSET 0x200 + +#define INTCSR_OFFSET 0x4c /* Offset in PLX9050 conf registers */ +#define INTCSR_LINTI1 (1 << 0) +#define INTCSR_LINTI2 (1 << 3) +#define INTCSR_PCI (1 << 6) + +/* PCI vender, device and sub-device ID */ +#define IXXAT_PCI_VENDOR_ID 0x10b5 +#define IXXAT_PCI_DEVICE_ID 0x9050 +#define IXXAT_PCI_SUB_SYS_ID 0x2540 + +#define IXXAT_PCI_BASE_SIZE 0x400 + +static struct pci_device_id ixxat_pci_tbl[] = { + {IXXAT_PCI_VENDOR_ID, IXXAT_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID,}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, ixxat_pci_tbl); + +static u8 ixxat_pci_read_reg(struct net_device *ndev, int port) +{ + u8 val; + val = readb((void __iomem *)(ndev->base_addr + port)); + return val; +} + +static void ixxat_pci_write_reg(struct net_device *ndev, int port, u8 val) +{ + writeb(val, (void __iomem *)(ndev->base_addr + port)); +} + +static void ixxat_pci_del_chan(struct pci_dev *pdev, struct net_device *ndev) +{ + dev_info(&pdev->dev, "Removing device %s\n", ndev->name); + + unregister_sja1000dev(ndev); + + free_sja1000dev(ndev); +} + +static struct net_device *ixxat_pci_add_chan(struct pci_dev *pdev, + void __iomem *base_addr) +{ + struct net_device *ndev; + struct sja1000_priv *priv; + int err; + + ndev = alloc_sja1000dev(0); + if (ndev == NULL) + return ERR_PTR(-ENOMEM); + + priv = netdev_priv(ndev); + + ndev->base_addr = (unsigned long)base_addr; + + priv->read_reg = ixxat_pci_read_reg; + priv->write_reg = ixxat_pci_write_reg; + + priv->can.bittiming.clock = IXXAT_PCI_CAN_CLOCK; + + priv->ocr = IXXAT_PCI_OCR; + priv->cdr = IXXAT_PCI_CDR; + + /* Set and enable PCI interrupts */ + ndev->irq = pdev->irq; + + dev_dbg(&pdev->dev, "base_addr=%#lx irq=%d\n", + ndev->base_addr, ndev->irq); + + SET_NETDEV_DEV(ndev, &pdev->dev); + + err = register_sja1000dev(ndev); + if (err) { + dev_err(&pdev->dev, "Failed to register (err=%d)\n", err); + goto failure; + } + + return ndev; + +failure: + free_sja1000dev(ndev); + return ERR_PTR(err); +} + +static int __devinit ixxat_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct ixxat_pci *board; + int err, intcsr = INTCSR_LINTI1 | INTCSR_PCI; + u16 sub_sys_id; + void __iomem *base_addr; + + dev_info(&pdev->dev, "Initializing device %04x:%04x\n", + pdev->vendor, pdev->device); + + board = kzalloc(sizeof(*board), GFP_KERNEL); + if (!board) + return -ENOMEM; + + err = pci_enable_device(pdev); + if (err) + goto failure; + + err = pci_request_regions(pdev, DRV_NAME); + if (err) + goto failure; + + err = pci_read_config_word(pdev, 0x2e, &sub_sys_id); + if (err) + goto failure_release_pci; + + if (sub_sys_id != IXXAT_PCI_SUB_SYS_ID) + return -ENODEV; + + /* Enable memory and I/O space */ + err = pci_write_config_word(pdev, 0x04, 0x3); + if (err) + goto failure_release_pci; + + board->conf_addr = pci_resource_start(pdev, 1); + + base_addr = pci_iomap(pdev, 2, IXXAT_PCI_BASE_SIZE); + if (base_addr == NULL) { + err = -ENODEV; + goto failure_release_pci; + } + + board->base_addr = base_addr; + + writeb(0x1, base_addr + CHANNEL_RESET_OFFSET); + writeb(0x1, base_addr + CHANNEL_OFFSET + CHANNEL_RESET_OFFSET); + udelay(100); + + board->dev[0] = ixxat_pci_add_chan(pdev, base_addr); + if (IS_ERR(board->dev[0])) + goto failure_iounmap; + + /* Check if second channel is available */ + if ((readb(base_addr + CHANNEL_OFFSET + REG_MOD) & 0xa1) == 0x21 && + readb(base_addr + CHANNEL_OFFSET + REG_SR) == 0x0c && + readb(base_addr + CHANNEL_OFFSET + REG_IR) == 0xe0) { + board->dev[1] = ixxat_pci_add_chan(pdev, + base_addr + CHANNEL_OFFSET); + if (IS_ERR(board->dev[1])) + goto failure_unreg_dev0; + + intcsr |= INTCSR_LINTI2; + } + + /* enable interrupt(s) in PLX9050 */ + outb(intcsr, board->conf_addr + INTCSR_OFFSET); + + pci_set_drvdata(pdev, board); + + return 0; + +failure_unreg_dev0: + ixxat_pci_del_chan(pdev, board->dev[0]); + +failure_iounmap: + pci_iounmap(pdev, board->base_addr); + +failure_release_pci: + pci_release_regions(pdev); + +failure: + kfree(board); + + return err; +} + +static void __devexit ixxat_pci_remove_one(struct pci_dev *pdev) +{ + struct ixxat_pci *board = pci_get_drvdata(pdev); + int i; + + /* Disable interrupts in PLX9050*/ + outb(0, board->conf_addr + INTCSR_OFFSET); + + for (i = 0; i < IXXAT_PCI_MAX_CAN; i++) { + if (!board->dev[i]) + break; + ixxat_pci_del_chan(pdev, board->dev[i]); + } + + pci_iounmap(pdev, board->base_addr); + + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + + kfree(board); +} + +static struct pci_driver ixxat_pci_driver = { + .name = DRV_NAME, + .id_table = ixxat_pci_tbl, + .probe = ixxat_pci_init_one, + .remove = __devexit_p(ixxat_pci_remove_one), +}; + +static int __init ixxat_pci_init(void) +{ + return pci_register_driver(&ixxat_pci_driver); +} + +static void __exit ixxat_pci_exit(void) +{ + pci_unregister_driver(&ixxat_pci_driver); +} + +module_init(ixxat_pci_init); +module_exit(ixxat_pci_exit); diff --git a/drivers/net/can/sja1000/kvaser_pci.c b/drivers/net/can/sja1000/kvaser_pci.c new file mode 100644 index 000000000000..0b01ea3d5560 --- /dev/null +++ b/drivers/net/can/sja1000/kvaser_pci.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2008 Per Dalen <per.dalen@cnw.se> + * + * Parts of this software are based on (derived) the following: + * + * - Kvaser linux driver, version 4.72 BETA + * Copyright (C) 2002-2007 KVASER AB + * + * - Lincan driver, version 0.3.3, OCERA project + * Copyright (C) 2004 Pavel Pisa + * Copyright (C) 2001 Arnaud Westenberg + * + * - Socketcan SJA1000 drivers + * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com> + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33, + * 38106 Braunschweig, GERMANY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/io.h> + +#include "sja1000.h" + +#define DRV_NAME "kvaser_pci" + +MODULE_AUTHOR("Per Dalen <per.dalen@cnw.se>"); +MODULE_DESCRIPTION("Socket-CAN driver for KVASER PCAN PCI cards"); +MODULE_SUPPORTED_DEVICE("KVASER PCAN PCI CAN card"); +MODULE_LICENSE("GPL v2"); + +#define MAX_NO_OF_CHANNELS 4 /* max no of channels on + a single card */ + +struct kvaser_pci { + int channel; + struct pci_dev *pci_dev; + struct net_device *slave_dev[MAX_NO_OF_CHANNELS-1]; + void __iomem *conf_addr; + void __iomem *res_addr; + int no_channels; + u8 xilinx_ver; +}; + +#define KVASER_PCI_CAN_CLOCK (16000000 / 2) + +/* + * The board configuration is probably following: + * RX1 is connected to ground. + * TX1 is not connected. + * CLKO is not connected. + * Setting the OCR register to 0xDA is a good idea. + * This means normal output mode , push-pull and the correct polarity. + */ +#define KVASER_PCI_OCR (OCR_TX0_PUSHPULL | OCR_TX1_PUSHPULL) + +/* + * In the CDR register, you should set CBP to 1. + * You will probably also want to set the clock divider value to 0 + * (meaning divide-by-2), the Pelican bit, and the clock-off bit + * (you will have no need for CLKOUT anyway). + */ +#define KVASER_PCI_CDR (CDR_CBP | CDR_CLKOUT_MASK) + +/* + * These register values are valid for revision 14 of the Xilinx logic. + */ +#define XILINX_VERINT 7 /* Lower nibble simulate interrupts, + high nibble version number. */ + +#define XILINX_PRESUMED_VERSION 14 + +/* + * Important S5920 registers + */ +#define S5920_INTCSR 0x38 +#define S5920_PTCR 0x60 +#define INTCSR_ADDON_INTENABLE_M 0x2000 + + +#define KVASER_PCI_PORT_BYTES 0x20 + +#define PCI_CONFIG_PORT_SIZE 0x80 /* size of the config io-memory */ +#define PCI_PORT_SIZE 0x80 /* size of a channel io-memory */ +#define PCI_PORT_XILINX_SIZE 0x08 /* size of a xilinx io-memory */ + +#define KVASER_PCI_VENDOR_ID1 0x10e8 /* the PCI device and vendor IDs */ +#define KVASER_PCI_DEVICE_ID1 0x8406 + +#define KVASER_PCI_VENDOR_ID2 0x1a07 /* the PCI device and vendor IDs */ +#define KVASER_PCI_DEVICE_ID2 0x0008 + +static struct pci_device_id kvaser_pci_tbl[] = { + {KVASER_PCI_VENDOR_ID1, KVASER_PCI_DEVICE_ID1, PCI_ANY_ID, PCI_ANY_ID,}, + {KVASER_PCI_VENDOR_ID2, KVASER_PCI_DEVICE_ID2, PCI_ANY_ID, PCI_ANY_ID,}, + { 0,} +}; + +MODULE_DEVICE_TABLE(pci, kvaser_pci_tbl); + +static u8 kvaser_pci_read_reg(struct net_device *dev, int port) +{ + return ioread8((void __iomem *)(dev->base_addr + port)); +} + +static void kvaser_pci_write_reg(struct net_device *dev, int port, u8 val) +{ + iowrite8(val, (void __iomem *)(dev->base_addr + port)); +} + +static void kvaser_pci_disable_irq(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct kvaser_pci *board = priv->priv; + u32 tmp; + + /* Disable interrupts from card */ + tmp = ioread32(board->conf_addr + S5920_INTCSR); + tmp &= ~INTCSR_ADDON_INTENABLE_M; + iowrite32(tmp, board->conf_addr + S5920_INTCSR); +} + +static void kvaser_pci_enable_irq(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct kvaser_pci *board = priv->priv; + u32 tmp; + + /* Enable interrupts from card */ + tmp = ioread32(board->conf_addr + S5920_INTCSR); + tmp |= INTCSR_ADDON_INTENABLE_M; + iowrite32(tmp, board->conf_addr + S5920_INTCSR); +} + +static int number_of_sja1000_chip(void __iomem *base_addr) +{ + u8 status; + int i; + + for (i = 0; i < MAX_NO_OF_CHANNELS; i++) { + /* reset chip */ + iowrite8(MOD_RM, base_addr + + (i * KVASER_PCI_PORT_BYTES) + REG_MOD); + status = ioread8(base_addr + + (i * KVASER_PCI_PORT_BYTES) + REG_MOD); + udelay(10); + /* check reset bit */ + if (!(status & MOD_RM)) + break; + } + + return i; +} + +static void kvaser_pci_del_chan(struct net_device *dev) +{ + struct sja1000_priv *priv; + struct kvaser_pci *board; + int i; + + if (!dev) + return; + priv = netdev_priv(dev); + if (!priv) + return; + board = priv->priv; + if (!board) + return; + + dev_info(&board->pci_dev->dev, "Removing device %s\n", + dev->name); + + for (i = 0; i < board->no_channels - 1; i++) { + if (board->slave_dev[i]) { + dev_info(&board->pci_dev->dev, "Removing device %s\n", + board->slave_dev[i]->name); + unregister_sja1000dev(board->slave_dev[i]); + free_sja1000dev(board->slave_dev[i]); + } + } + unregister_sja1000dev(dev); + + /* Disable PCI interrupts */ + kvaser_pci_disable_irq(dev); + + pci_iounmap(board->pci_dev, (void __iomem *)dev->base_addr); + pci_iounmap(board->pci_dev, board->conf_addr); + pci_iounmap(board->pci_dev, board->res_addr); + + free_sja1000dev(dev); +} + +static int kvaser_pci_add_chan(struct pci_dev *pdev, int channel, + struct net_device **master_dev, + void __iomem *conf_addr, + void __iomem *res_addr, + unsigned long base_addr) +{ + struct net_device *dev; + struct sja1000_priv *priv; + struct kvaser_pci *board; + int err, init_step; + + dev = alloc_sja1000dev(sizeof(struct kvaser_pci)); + if (dev == NULL) + return -ENOMEM; + + priv = netdev_priv(dev); + board = priv->priv; + + board->pci_dev = pdev; + board->channel = channel; + + /*S5920*/ + board->conf_addr = conf_addr; + + /*XILINX board wide address*/ + board->res_addr = res_addr; + + if (channel == 0) { + board->xilinx_ver = + ioread8(board->res_addr + XILINX_VERINT) >> 4; + init_step = 2; + + /* Assert PTADR# - we're in passive mode so the other bits are + not important */ + iowrite32(0x80808080UL, board->conf_addr + S5920_PTCR); + + /* Disable interrupts from card */ + kvaser_pci_disable_irq(dev); + /* Enable interrupts from card */ + kvaser_pci_enable_irq(dev); + } else { + struct sja1000_priv *master_priv = netdev_priv(*master_dev); + struct kvaser_pci *master_board = master_priv->priv; + master_board->slave_dev[channel - 1] = dev; + master_board->no_channels = channel + 1; + board->xilinx_ver = master_board->xilinx_ver; + } + + dev->base_addr = base_addr + channel * KVASER_PCI_PORT_BYTES; + + priv->read_reg = kvaser_pci_read_reg; + priv->write_reg = kvaser_pci_write_reg; + + priv->can.bittiming.clock = KVASER_PCI_CAN_CLOCK; + + priv->ocr = KVASER_PCI_OCR; + priv->cdr = KVASER_PCI_CDR; + + /* Register and setup interrupt handling */ + dev->irq = pdev->irq; + init_step = 4; + + dev_info(&pdev->dev, "base_addr=%#lx conf_addr=%p irq=%d\n", + dev->base_addr, board->conf_addr, dev->irq); + + SET_NETDEV_DEV(dev, &pdev->dev); + + /* Register SJA1000 device */ + err = register_sja1000dev(dev); + if (err) { + dev_err(&pdev->dev, "Registering device failed (err=%d)\n", + err); + goto failure; + } + + if (channel == 0) + *master_dev = dev; + + return 0; + +failure: + kvaser_pci_del_chan(dev); + return err; +} + +static int __devinit kvaser_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + struct net_device *master_dev = NULL; + struct sja1000_priv *priv; + struct kvaser_pci *board; + int no_channels; + void __iomem *base_addr = NULL; + void __iomem *conf_addr = NULL; + void __iomem *res_addr = NULL; + int i; + + dev_info(&pdev->dev, "initializing device %04x:%04x\n", + pdev->vendor, pdev->device); + + err = pci_enable_device(pdev); + if (err) + goto failure; + + err = pci_request_regions(pdev, DRV_NAME); + if (err) + goto failure_release_pci; + + /*S5920*/ + conf_addr = pci_iomap(pdev, 0, PCI_CONFIG_PORT_SIZE); + if (conf_addr == NULL) { + err = -ENODEV; + goto failure_iounmap; + } + + /*XILINX board wide address*/ + res_addr = pci_iomap(pdev, 2, PCI_PORT_XILINX_SIZE); + if (res_addr == NULL) { + err = -ENOMEM; + goto failure_iounmap; + } + + base_addr = pci_iomap(pdev, 1, PCI_PORT_SIZE); + if (base_addr == NULL) { + err = -ENOMEM; + goto failure_iounmap; + } + + no_channels = number_of_sja1000_chip(base_addr); + if (no_channels == 0) { + err = -ENOMEM; + goto failure_iounmap; + } + + for (i = 0; i < no_channels; i++) { + err = kvaser_pci_add_chan(pdev, i, &master_dev, + conf_addr, res_addr, + (unsigned long)base_addr); + if (err) + goto failure_cleanup; + } + + priv = netdev_priv(master_dev); + board = priv->priv; + + dev_info(&pdev->dev, "xilinx version=%d number of channels=%d\n", + board->xilinx_ver, board->no_channels); + + pci_set_drvdata(pdev, master_dev); + return 0; + +failure_cleanup: + kvaser_pci_del_chan(master_dev); + +failure_iounmap: + if (conf_addr == NULL) + pci_iounmap(pdev, conf_addr); + if (res_addr == NULL) + pci_iounmap(pdev, res_addr); + if (base_addr == NULL) + pci_iounmap(pdev, base_addr); + + pci_release_regions(pdev); + +failure_release_pci: + pci_disable_device(pdev); + +failure: + return err; + +} + +static void __devexit kvaser_pci_remove_one(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + + kvaser_pci_del_chan(dev); + + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +} + +static struct pci_driver kvaser_pci_driver = { + .name = DRV_NAME, + .id_table = kvaser_pci_tbl, + .probe = kvaser_pci_init_one, + .remove = __devexit_p(kvaser_pci_remove_one), +}; + +static int __init kvaser_pci_init(void) +{ + return pci_register_driver(&kvaser_pci_driver); +} + +static void __exit kvaser_pci_exit(void) +{ + pci_unregister_driver(&kvaser_pci_driver); +} + +module_init(kvaser_pci_init); +module_exit(kvaser_pci_exit); diff --git a/drivers/net/can/sja1000/peak_pci.c b/drivers/net/can/sja1000/peak_pci.c new file mode 100644 index 000000000000..d1089a3d62e0 --- /dev/null +++ b/drivers/net/can/sja1000/peak_pci.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com> + * + * Derived from the PCAN project file driver/src/pcan_pci.c: + * + * Copyright (C) 2001-2006 PEAK System-Technik GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/io.h> + +#include "sja1000.h" + +#define DRV_NAME "peak_pci" + +MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>"); +MODULE_DESCRIPTION("Socket-CAN driver for PEAK PCAN PCI cards"); +MODULE_SUPPORTED_DEVICE("PEAK PCAN PCI CAN card"); +MODULE_LICENSE("GPL v2"); + +struct peak_pci { + int channel; + struct pci_dev *pci_dev; + struct net_device *slave_dev; + volatile void __iomem *conf_addr; +}; + +#define PEAK_PCI_SINGLE 0 /* single channel device */ +#define PEAK_PCI_MASTER 1 /* multi channel master device */ +#define PEAK_PCI_SLAVE 2 /* multi channel slave device */ + +#define PEAK_PCI_CAN_CLOCK (16000000 / 2) + +#define PEAK_PCI_CDR_SINGLE (CDR_CBP | CDR_CLKOUT_MASK | CDR_CLK_OFF) +#define PEAK_PCI_CDR_MASTER (CDR_CBP | CDR_CLKOUT_MASK) + +#define PEAK_PCI_OCR OCR_TX0_PUSHPULL + +/* + * Important PITA registers + */ +#define PITA_ICR 0x00 /* interrupt control register */ +#define PITA_GPIOICR 0x18 /* general purpose I/O interface + control register */ +#define PITA_MISC 0x1C /* miscellanoes register */ + +#define PCI_CONFIG_PORT_SIZE 0x1000 /* size of the config io-memory */ +#define PCI_PORT_SIZE 0x0400 /* size of a channel io-memory */ + +#define PEAK_PCI_VENDOR_ID 0x001C /* the PCI device and vendor IDs */ +#define PEAK_PCI_DEVICE_ID 0x0001 + +static struct pci_device_id peak_pci_tbl[] = { + {PEAK_PCI_VENDOR_ID, PEAK_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID,}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, peak_pci_tbl); + +static u8 peak_pci_read_reg(struct net_device *dev, int port) +{ + u8 val; + val = readb((const volatile void __iomem *) + (dev->base_addr + (port << 2))); + return val; +} + +static void peak_pci_write_reg(struct net_device *dev, int port, u8 val) +{ + writeb(val, (volatile void __iomem *) + (dev->base_addr + (port << 2))); +} + +static void peak_pci_post_irq(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct peak_pci *board = priv->priv; + u16 icr_low; + + /* Select and clear in Pita stored interrupt */ + icr_low = readw(board->conf_addr + PITA_ICR); + if (board->channel == PEAK_PCI_SLAVE) { + if (icr_low & 0x0001) + writew(0x0001, board->conf_addr + PITA_ICR); + } else { + if (icr_low & 0x0002) + writew(0x0002, board->conf_addr + PITA_ICR); + } +} + +static void peak_pci_del_chan(struct net_device *dev, int init_step) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct peak_pci *board; + u16 icr_high; + + if (!dev) + return; + priv = netdev_priv(dev); + if (!priv) + return; + board = priv->priv; + if (!board) + return; + + switch (init_step) { + case 0: /* Full cleanup */ + printk(KERN_INFO "Removing %s device %s\n", + DRV_NAME, dev->name); + unregister_sja1000dev(dev); + case 4: + icr_high = readw(board->conf_addr + PITA_ICR + 2); + if (board->channel == PEAK_PCI_SLAVE) + icr_high &= ~0x0001; + else + icr_high &= ~0x0002; + writew(icr_high, board->conf_addr + PITA_ICR + 2); + case 3: + iounmap((void *)dev->base_addr); + case 2: + if (board->channel != PEAK_PCI_SLAVE) + iounmap((void *)board->conf_addr); + case 1: + free_sja1000dev(dev); + break; + } + +} + +static int peak_pci_add_chan(struct pci_dev *pdev, int channel, + struct net_device **master_dev) +{ + struct net_device *dev; + struct sja1000_priv *priv; + struct peak_pci *board; + u16 icr_high; + unsigned long addr; + int err, init_step; + + dev = alloc_sja1000dev(sizeof(struct peak_pci)); + if (dev == NULL) + return -ENOMEM; + init_step = 1; + + priv = netdev_priv(dev); + board = priv->priv; + + board->pci_dev = pdev; + board->channel = channel; + + if (channel != PEAK_PCI_SLAVE) { + + addr = pci_resource_start(pdev, 0); + board->conf_addr = ioremap(addr, PCI_CONFIG_PORT_SIZE); + if (board->conf_addr == 0) { + err = -ENODEV; + goto failure; + } + init_step = 2; + + /* Set GPIO control register */ + writew(0x0005, board->conf_addr + PITA_GPIOICR + 2); + + /* Enable single or dual channel */ + if (channel == PEAK_PCI_MASTER) + writeb(0x00, board->conf_addr + PITA_GPIOICR); + else + writeb(0x04, board->conf_addr + PITA_GPIOICR); + /* Toggle reset */ + writeb(0x05, board->conf_addr + PITA_MISC + 3); + mdelay(5); + /* Leave parport mux mode */ + writeb(0x04, board->conf_addr + PITA_MISC + 3); + } else { + struct sja1000_priv *master_priv = netdev_priv(*master_dev); + struct peak_pci *master_board = master_priv->priv; + master_board->slave_dev = dev; + board->conf_addr = master_board->conf_addr; + } + + addr = pci_resource_start(pdev, 1); + if (channel == PEAK_PCI_SLAVE) + addr += PCI_PORT_SIZE; + + dev->base_addr = (unsigned long)ioremap(addr, PCI_PORT_SIZE); + if (dev->base_addr == 0) { + err = -ENOMEM; + goto failure; + } + init_step = 3; + + priv->read_reg = peak_pci_read_reg; + priv->write_reg = peak_pci_write_reg; + priv->post_irq = peak_pci_post_irq; + + priv->can.bittiming.clock = PEAK_PCI_CAN_CLOCK; + + priv->ocr = PEAK_PCI_OCR; + + if (channel == PEAK_PCI_MASTER) + priv->cdr = PEAK_PCI_CDR_MASTER; + else + priv->cdr = PEAK_PCI_CDR_SINGLE; + + /* Register and setup interrupt handling */ + dev->irq = pdev->irq; + icr_high = readw(board->conf_addr + PITA_ICR + 2); + if (channel == PEAK_PCI_SLAVE) + icr_high |= 0x0001; + else + icr_high |= 0x0002; + writew(icr_high, board->conf_addr + PITA_ICR + 2); + init_step = 4; + + SET_NETDEV_DEV(dev, &pdev->dev); + + /* Register SJA1000 device */ + err = register_sja1000dev(dev); + if (err) { + printk(KERN_ERR "Registering %s device failed (err=%d)\n", + DRV_NAME, err); + goto failure; + } + + if (channel != PEAK_PCI_SLAVE) + *master_dev = dev; + + printk(KERN_INFO "%s: %s at base_addr=%#lx conf_addr=%p irq=%d\n", + DRV_NAME, dev->name, dev->base_addr, board->conf_addr, dev->irq); + + return 0; + +failure: + peak_pci_del_chan(dev, init_step); + return err; +} + +static int __devinit peak_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + u16 sub_sys_id; + struct net_device *master_dev = NULL; + + printk(KERN_INFO "%s: initializing device %04x:%04x\n", + DRV_NAME, pdev->vendor, pdev->device); + + err = pci_enable_device(pdev); + if (err) + goto failure; + + err = pci_request_regions(pdev, DRV_NAME); + if (err) + goto failure; + + err = pci_read_config_word(pdev, 0x2e, &sub_sys_id); + if (err) + goto failure_cleanup; + + err = pci_write_config_word(pdev, 0x44, 0); + if (err) + goto failure_cleanup; + + if (sub_sys_id > 3) { + err = peak_pci_add_chan(pdev, + PEAK_PCI_MASTER, &master_dev); + if (err) + goto failure_cleanup; + + err = peak_pci_add_chan(pdev, + PEAK_PCI_SLAVE, &master_dev); + if (err) + goto failure_cleanup; + } else { + err = peak_pci_add_chan(pdev, PEAK_PCI_SINGLE, + &master_dev); + if (err) + goto failure_cleanup; + } + + pci_set_drvdata(pdev, master_dev); + return 0; + +failure_cleanup: + if (master_dev) + peak_pci_del_chan(master_dev, 0); + + pci_release_regions(pdev); + +failure: + return err; + +} + +static void __devexit peak_pci_remove_one(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + struct sja1000_priv *priv = netdev_priv(dev); + struct peak_pci *board = priv->priv; + + if (board->slave_dev) + peak_pci_del_chan(board->slave_dev, 0); + peak_pci_del_chan(dev, 0); + + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +} + +static struct pci_driver peak_pci_driver = { + .name = DRV_NAME, + .id_table = peak_pci_tbl, + .probe = peak_pci_init_one, + .remove = __devexit_p(peak_pci_remove_one), +}; + +static int __init peak_pci_init(void) +{ + return pci_register_driver(&peak_pci_driver); +} + +static void __exit peak_pci_exit(void) +{ + pci_unregister_driver(&peak_pci_driver); +} + +module_init(peak_pci_init); +module_exit(peak_pci_exit); diff --git a/drivers/net/can/sja1000/pipcan.c b/drivers/net/can/sja1000/pipcan.c new file mode 100644 index 000000000000..b7414b218697 --- /dev/null +++ b/drivers/net/can/sja1000/pipcan.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2008 David Müller, <d.mueller@elsoft.ch> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/io.h> + +#include "sja1000.h" + +#define DRV_NAME "pipcan" + +MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>"); +MODULE_DESCRIPTION("Socket-CAN driver for MPL PIPCAN module"); +MODULE_SUPPORTED_DEVICE("MPL PIPCAN module"); +MODULE_LICENSE("GPL v2"); + +#define PIPCAN_CAN_CLOCK (16000000 / 2) + +#define PIPCAN_OCR (OCR_TX1_PUSHPULL) +#define PIPCAN_CDR (CDR_CBP | CDR_CLK_OFF) + +#define PIPCAN_IOSIZE (0x100) + +#define PIPCAN_RES (0x804) +#define PIPCAN_RST (0x805) + +static u8 pc_read_reg(struct net_device *dev, int reg) +{ + return inb(dev->base_addr + reg); +} + +static void pc_write_reg(struct net_device *dev, int reg, u8 val) +{ + outb(val, dev->base_addr + reg); +} + +static int __init pc_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct sja1000_priv *priv; + struct resource *res; + int rc, irq; + + rc = -ENODEV; + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + irq = platform_get_irq(pdev, 0); + if (!res || !irq) + goto exit; + + rc = -EBUSY; + if (!request_region(res->start, res->end - res->start + 1, DRV_NAME)) + goto exit; + + rc = -ENOMEM; + dev = alloc_sja1000dev(0); + if (!dev) + goto exit_release; + + priv = netdev_priv(dev); + + priv->read_reg = pc_read_reg; + priv->write_reg = pc_write_reg; + priv->can.bittiming.clock = PIPCAN_CAN_CLOCK; + priv->ocr = PIPCAN_OCR; + priv->cdr = PIPCAN_CDR; + + dev->irq = irq; + dev->base_addr = res->start; + + dev_set_drvdata(&pdev->dev, dev); + SET_NETDEV_DEV(dev, &pdev->dev); + + /* deactivate RST */ + outb(inb(PIPCAN_RST) & ~0x01, PIPCAN_RST); + + rc = register_sja1000dev(dev); + if (rc) { + dev_err(&pdev->dev, "registering %s failed (err=%d)\n", + DRV_NAME, rc); + goto exit_free; + } + + dev_info(&pdev->dev, "device registered (base_addr=%#lx, irq=%d)\n", + dev->base_addr, dev->irq); + return 0; + +exit_free: + free_sja1000dev(dev); + +exit_release: + release_region(res->start, res->end - res->start + 1); + +exit: + return rc; +} + +static int __exit pc_remove(struct platform_device *pdev) +{ + struct net_device *dev = dev_get_drvdata(&pdev->dev); + struct resource *res; + + dev_set_drvdata(&pdev->dev, NULL); + unregister_sja1000dev(dev); + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + + free_sja1000dev(dev); + + release_region(res->start, res->end - res->start + 1); + + /* activate RST */ + outb(inb(PIPCAN_RST) | 0x01, PIPCAN_RST); + + return 0; +} + +static struct platform_driver pc_driver = { + .remove = __exit_p(pc_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static struct platform_device *pc_pdev; +static const u16 pipcan_ioport[] = {0x1000, 0x8000, 0xE000}; + +static int __init pc_init(void) +{ + struct resource r[2]; + int rc, addr, irq, idx; + u8 pc_res; + + /* get PIPCAN resources from EPLD */ + pc_res = inb(PIPCAN_RES); + + idx = (pc_res & 0x0F); + if ((idx <= 0) || (idx > ARRAY_SIZE(pipcan_ioport))) { + printk(KERN_ERR DRV_NAME " invalid base address\n"); + return -EINVAL; + } + addr = pipcan_ioport[idx-1]; + + irq = (pc_res >> 4) & 0x0F; + if ((irq < 3) || (irq == 8) || (irq == 13)) { + printk(KERN_ERR DRV_NAME " invalid IRQ\n"); + return -EINVAL; + } + + /* fill in resources */ + memset(&r, 0, sizeof(r)); + r[0].start = addr; + r[0].end = addr + PIPCAN_IOSIZE - 1; + r[0].name = DRV_NAME; + r[0].flags = IORESOURCE_IO; + r[1].start = r[1].end = irq; + r[1].name = DRV_NAME; + r[1].flags = IORESOURCE_IRQ; + + pc_pdev = platform_device_register_simple(DRV_NAME, 0, r, + ARRAY_SIZE(r)); + if (IS_ERR(pc_pdev)) + return PTR_ERR(pc_pdev); + + rc = platform_driver_probe(&pc_driver, pc_probe); + if (rc) { + platform_device_unregister(pc_pdev); + printk(KERN_ERR DRV_NAME + " platform_driver_probe() failed (%d)\n", rc); + } + + return rc; +} + +static void __exit pc_exit(void) +{ + platform_driver_unregister(&pc_driver); + platform_device_unregister(pc_pdev); +} + +module_init(pc_init); +module_exit(pc_exit); diff --git a/drivers/net/can/sja1000/sja1000.c b/drivers/net/can/sja1000/sja1000.c new file mode 100644 index 000000000000..02f49fa703ce --- /dev/null +++ b/drivers/net/can/sja1000/sja1000.c @@ -0,0 +1,677 @@ +/* + * sja1000.c - Philips SJA1000 network device driver + * + * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33, + * 38106 Braunschweig, GERMANY + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/delay.h> + +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include <linux/can/dev.h> + +#include "sja1000.h" + +#define DRV_NAME "sja1000" + +MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION(DRV_NAME " CAN netdevice driver"); + +static struct can_bittiming_const sja1000_bittiming_const = { + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 64, + .brp_inc = 1, +}; + +static int sja1000_probe_chip(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + + if (dev->base_addr && (priv->read_reg(dev, 0) == 0xFF)) { + printk(KERN_INFO "%s: probing @0x%lX failed\n", + DRV_NAME, dev->base_addr); + return 0; + } + return 1; +} + +static int set_reset_mode(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + unsigned char status = priv->read_reg(dev, REG_MOD); + int i; + + /* disable interrupts */ + priv->write_reg(dev, REG_IER, IRQ_OFF); + + for (i = 0; i < 100; i++) { + /* check reset bit */ + if (status & MOD_RM) { + priv->can.state = CAN_STATE_STOPPED; + return 0; + } + + priv->write_reg(dev, REG_MOD, MOD_RM); /* reset chip */ + status = priv->read_reg(dev, REG_MOD); + udelay(10); + } + + dev_err(ND2D(dev), "setting SJA1000 into reset mode failed!\n"); + return 1; + +} + +static int set_normal_mode(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + unsigned char status = priv->read_reg(dev, REG_MOD); + int i; + + for (i = 0; i < 100; i++) { + /* check reset bit */ + if ((status & MOD_RM) == 0) { + priv->can.state = CAN_STATE_ACTIVE; + /* enable all interrupts */ + priv->write_reg(dev, REG_IER, IRQ_ALL); + + return 0; + } + + /* set chip to normal mode */ + priv->write_reg(dev, REG_MOD, 0x00); + status = priv->read_reg(dev, REG_MOD); + udelay(10); + } + + dev_err(ND2D(dev), "setting SJA1000 into normal mode failed!\n"); + return 1; + +} + +static void sja1000_start(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + + /* leave reset mode */ + if (priv->can.state != CAN_STATE_STOPPED) + set_reset_mode(dev); + + /* Clear error counters and error code capture */ + priv->write_reg(dev, REG_TXERR, 0x0); + priv->write_reg(dev, REG_RXERR, 0x0); + priv->read_reg(dev, REG_ECC); + + /* leave reset mode */ + set_normal_mode(dev); +} + +static int sja1000_set_mode(struct net_device *dev, enum can_mode mode) +{ + struct sja1000_priv *priv = netdev_priv(dev); + + switch (mode) { + case CAN_MODE_START: + if (!priv->open_time) + return -EINVAL; + + sja1000_start(dev); + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int sja1000_get_state(struct net_device *dev, enum can_state *state) +{ + struct sja1000_priv *priv = netdev_priv(dev); + u8 status; + + /* FIXME: inspecting the status register to get the current state + * is not really necessary, because state changes are handled by + * in the ISR and the variable priv->can.state gets updated. The + * CAN devicde interface needs fixing! + */ + + spin_lock_irq(&priv->can.irq_lock); + + if (priv->can.state == CAN_STATE_STOPPED) { + *state = CAN_STATE_STOPPED; + } else { + status = priv->read_reg(dev, REG_SR); + if (status & SR_BS) + *state = CAN_STATE_BUS_OFF; + else if (status & SR_ES) { + if (priv->read_reg(dev, REG_TXERR) > 127 || + priv->read_reg(dev, REG_RXERR) > 127) + *state = CAN_STATE_BUS_PASSIVE; + else + *state = CAN_STATE_BUS_WARNING; + } else + *state = CAN_STATE_ACTIVE; + } + /* Check state */ + if (*state != priv->can.state) + dev_err(ND2D(dev), + "Oops, state mismatch: hard %d != soft %d\n", + *state, priv->can.state); + spin_unlock_irq(&priv->can.irq_lock); + return 0; +} + +static int sja1000_set_bittiming(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->can.bittiming; + u8 btr0, btr1; + + btr0 = ((bt->brp - 1) & 0x3f) | (((bt->sjw - 1) & 0x3) << 6); + btr1 = ((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) | + (((bt->phase_seg2 - 1) & 0x7) << 4) | + ((priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) << 7); + + dev_info(ND2D(dev), "setting BTR0=0x%02x BTR1=0x%02x\n", btr0, btr1); + + priv->write_reg(dev, REG_BTR0, btr0); + priv->write_reg(dev, REG_BTR1, btr1); + + return 0; +} + +/* + * initialize SJA1000 chip: + * - reset chip + * - set output mode + * - set baudrate + * - enable interrupts + * - start operating mode + */ +static void chipset_init(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + + /* set clock divider and output control register */ + priv->write_reg(dev, REG_CDR, priv->cdr | CDR_PELICAN); + + /* set acceptance filter (accept all) */ + priv->write_reg(dev, REG_ACCC0, 0x00); + priv->write_reg(dev, REG_ACCC1, 0x00); + priv->write_reg(dev, REG_ACCC2, 0x00); + priv->write_reg(dev, REG_ACCC3, 0x00); + + priv->write_reg(dev, REG_ACCM0, 0xFF); + priv->write_reg(dev, REG_ACCM1, 0xFF); + priv->write_reg(dev, REG_ACCM2, 0xFF); + priv->write_reg(dev, REG_ACCM3, 0xFF); + + priv->write_reg(dev, REG_OCR, priv->ocr | OCR_MODE_NORMAL); +} + +/* + * transmit a CAN message + * message layout in the sk_buff should be like this: + * xx xx xx xx ff ll 00 11 22 33 44 55 66 77 + * [ can-id ] [flags] [len] [can data (up to 8 bytes] + */ +static int sja1000_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf = (struct can_frame *)skb->data; + uint8_t fi; + uint8_t dlc; + canid_t id; + uint8_t dreg; + int i; + + netif_stop_queue(dev); + + fi = dlc = cf->can_dlc; + id = cf->can_id; + + if (id & CAN_RTR_FLAG) + fi |= FI_RTR; + + if (id & CAN_EFF_FLAG) { + fi |= FI_FF; + dreg = EFF_BUF; + priv->write_reg(dev, REG_FI, fi); + priv->write_reg(dev, REG_ID1, (id & 0x1fe00000) >> (5 + 16)); + priv->write_reg(dev, REG_ID2, (id & 0x001fe000) >> (5 + 8)); + priv->write_reg(dev, REG_ID3, (id & 0x00001fe0) >> 5); + priv->write_reg(dev, REG_ID4, (id & 0x0000001f) << 3); + } else { + dreg = SFF_BUF; + priv->write_reg(dev, REG_FI, fi); + priv->write_reg(dev, REG_ID1, (id & 0x000007f8) >> 3); + priv->write_reg(dev, REG_ID2, (id & 0x00000007) << 5); + } + + for (i = 0; i < dlc; i++) + priv->write_reg(dev, dreg++, cf->data[i]); + + stats->tx_bytes += dlc; + dev->trans_start = jiffies; + + can_put_echo_skb(skb, dev, 0); + + priv->write_reg(dev, REG_CMR, CMD_TR); + + return 0; +} + +static void sja1000_rx(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf; + struct sk_buff *skb; + uint8_t fi; + uint8_t dreg; + canid_t id; + uint8_t dlc; + int i; + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb == NULL) + return; + skb->dev = dev; + skb->protocol = htons(ETH_P_CAN); + + fi = priv->read_reg(dev, REG_FI); + dlc = fi & 0x0F; + + if (fi & FI_FF) { + /* extended frame format (EFF) */ + dreg = EFF_BUF; + id = (priv->read_reg(dev, REG_ID1) << (5 + 16)) + | (priv->read_reg(dev, REG_ID2) << (5 + 8)) + | (priv->read_reg(dev, REG_ID3) << 5) + | (priv->read_reg(dev, REG_ID4) >> 3); + id |= CAN_EFF_FLAG; + } else { + /* standard frame format (SFF) */ + dreg = SFF_BUF; + id = (priv->read_reg(dev, REG_ID1) << 3) + | (priv->read_reg(dev, REG_ID2) >> 5); + } + + if (fi & FI_RTR) + id |= CAN_RTR_FLAG; + + cf = (struct can_frame *)skb_put(skb, sizeof(struct can_frame)); + memset(cf, 0, sizeof(struct can_frame)); + cf->can_id = id; + cf->can_dlc = dlc; + for (i = 0; i < dlc; i++) + cf->data[i] = priv->read_reg(dev, dreg++); + + while (i < 8) + cf->data[i++] = 0; + + /* release receive buffer */ + priv->write_reg(dev, REG_CMR, CMD_RRB); + + netif_rx(skb); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += dlc; +} + +static int sja1000_err(struct net_device *dev, uint8_t isrc, uint8_t status) +{ + struct sja1000_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + struct can_frame *cf; + struct sk_buff *skb; + enum can_state state = priv->can.state; + uint8_t ecc, alc; + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (skb == NULL) + return -ENOMEM; + skb->dev = dev; + skb->protocol = htons(ETH_P_CAN); + cf = (struct can_frame *)skb_put(skb, sizeof(struct can_frame)); + memset(cf, 0, sizeof(struct can_frame)); + cf->can_id = CAN_ERR_FLAG; + cf->can_dlc = CAN_ERR_DLC; + + if (isrc & IRQ_DOI) { + /* data overrun interrupt */ + dev_dbg(ND2D(dev), "data overrun interrupt\n"); + cf->can_id |= CAN_ERR_CRTL; + cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW; + priv->can.can_stats.data_overrun++; + priv->write_reg(dev, REG_CMR, CMD_CDO); /* clear bit */ + } + + if (isrc & IRQ_EI) { + /* error warning interrupt */ + priv->can.can_stats.error_warning++; + dev_dbg(ND2D(dev), "error warning interrupt\n"); + + if (status & SR_BS) { + state = CAN_STATE_BUS_OFF; + cf->can_id |= CAN_ERR_BUSOFF; + can_bus_off(dev); + } else if (status & SR_ES) { + state = CAN_STATE_BUS_WARNING; + } else + state = CAN_STATE_ACTIVE; + } + if (isrc & IRQ_BEI) { + /* bus error interrupt */ + priv->can.can_stats.bus_error++; + ecc = priv->read_reg(dev, REG_ECC); + + cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR; + + switch (ecc & ECC_MASK) { + case ECC_BIT: + cf->data[2] |= CAN_ERR_PROT_BIT; + break; + case ECC_FORM: + cf->data[2] |= CAN_ERR_PROT_FORM; + break; + case ECC_STUFF: + cf->data[2] |= CAN_ERR_PROT_STUFF; + break; + default: + cf->data[2] |= CAN_ERR_PROT_UNSPEC; + cf->data[3] = ecc & ECC_SEG; + break; + } + /* Error occured during transmission? */ + if ((ecc & ECC_DIR) == 0) + cf->data[2] |= CAN_ERR_PROT_TX; + } + if (isrc & IRQ_EPI) { + /* error passive interrupt */ + dev_dbg(ND2D(dev), "error passive interrupt\n"); + priv->can.can_stats.error_passive++; + if (status & SR_ES) + state = CAN_STATE_BUS_PASSIVE; + else + state = CAN_STATE_ACTIVE; + } + if (isrc & IRQ_ALI) { + /* arbitration lost interrupt */ + dev_dbg(ND2D(dev), "arbitration lost interrupt\n"); + alc = priv->read_reg(dev, REG_ALC); + priv->can.can_stats.arbitration_lost++; + cf->can_id |= CAN_ERR_LOSTARB; + cf->data[0] = alc & 0x1f; + } + + if (state != priv->can.state && (state == CAN_STATE_BUS_WARNING || + state == CAN_STATE_BUS_PASSIVE)) { + uint8_t rxerr = priv->read_reg(dev, REG_RXERR); + uint8_t txerr = priv->read_reg(dev, REG_TXERR); + cf->can_id |= CAN_ERR_CRTL; + if (state == CAN_STATE_BUS_WARNING) + cf->data[1] = (txerr > rxerr) ? + CAN_ERR_CRTL_TX_WARNING : + CAN_ERR_CRTL_RX_WARNING; + else + cf->data[1] = (txerr > rxerr) ? + CAN_ERR_CRTL_TX_PASSIVE : + CAN_ERR_CRTL_RX_PASSIVE; + } + + priv->can.state = state; + + netif_rx(skb); + + dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; + + return 0; +} + +irqreturn_t sja1000_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct sja1000_priv *priv = netdev_priv(dev); + struct net_device_stats *stats = &dev->stats; + uint8_t isrc, status; + int n = 0; + + /* Shared interrupts and IRQ off? */ + if (priv->read_reg(dev, REG_IER) == IRQ_OFF) + return IRQ_NONE; + + if (priv->pre_irq) + priv->pre_irq(dev); + + while ((isrc = priv->read_reg(dev, REG_IR)) && (n < SJA1000_MAX_IRQ)) { + n++; + status = priv->read_reg(dev, REG_SR); + + if (isrc & IRQ_WUI) { + /* wake-up interrupt */ + priv->can.can_stats.wakeup++; + } + if (isrc & IRQ_TI) { + /* transmission complete interrupt */ + stats->tx_packets++; + can_get_echo_skb(dev, 0); + netif_wake_queue(dev); + } + if (isrc & IRQ_RI) { + /* receive interrupt */ + while (status & SR_RBS) { + sja1000_rx(dev); + status = priv->read_reg(dev, REG_SR); + } + } + if (isrc & (IRQ_DOI | IRQ_EI | IRQ_BEI | IRQ_EPI | IRQ_ALI)) { + /* error interrupt */ + if (sja1000_err(dev, isrc, status)) + break; + } + } + + if (priv->post_irq) + priv->post_irq(dev); + + if (n >= SJA1000_MAX_IRQ) + dev_dbg(ND2D(dev), "%d messages handled in ISR", n); + + return (n) ? IRQ_HANDLED : IRQ_NONE; +} +EXPORT_SYMBOL_GPL(sja1000_interrupt); + +static int sja1000_open(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + int err; + + /* set chip into reset mode */ + set_reset_mode(dev); + + /* determine and set bittime */ + err = can_set_bittiming(dev); + if (err) + return err; + + /* register interrupt handler, if not done by the device driver */ + if (!(priv->flags & SJA1000_CUSTOM_IRQ_HANDLER)) { + err = request_irq(dev->irq, &sja1000_interrupt, IRQF_SHARED, + dev->name, (void *)dev); + if (err) + return -EAGAIN; + } + + /* init and start chi */ + sja1000_start(dev); + priv->open_time = jiffies; + + netif_start_queue(dev); + + return 0; +} + +static int sja1000_close(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + + set_reset_mode(dev); + netif_stop_queue(dev); + priv->open_time = 0; + can_close_cleanup(dev); + + if (!(priv->flags & SJA1000_CUSTOM_IRQ_HANDLER)) + free_irq(dev->irq, (void *)dev); + + return 0; +} + +struct net_device *alloc_sja1000dev(int sizeof_priv) +{ + struct net_device *dev; + struct sja1000_priv *priv; + + dev = alloc_candev(sizeof(struct sja1000_priv) + sizeof_priv); + if (!dev) + return NULL; + + priv = netdev_priv(dev); + priv->dev = dev; + + if (sizeof_priv) + priv->priv = (void *)priv + sizeof(struct sja1000_priv); + + return dev; +} +EXPORT_SYMBOL_GPL(alloc_sja1000dev); + +void free_sja1000dev(struct net_device *dev) +{ + free_candev(dev); +} +EXPORT_SYMBOL_GPL(free_sja1000dev); + +int register_sja1000dev(struct net_device *dev) +{ + struct sja1000_priv *priv = netdev_priv(dev); + int err; + + if (!sja1000_probe_chip(dev)) + return -ENODEV; + + dev->flags |= IFF_ECHO; /* we support local echo */ + + dev->open = sja1000_open; + dev->stop = sja1000_close; + dev->hard_start_xmit = sja1000_start_xmit; + + priv->can.bittiming_const = &sja1000_bittiming_const; + priv->can.do_set_bittiming = sja1000_set_bittiming; + priv->can.do_get_state = sja1000_get_state; + priv->can.do_set_mode = sja1000_set_mode; + priv->dev = dev; + + err = register_candev(dev); + if (err) { + printk(KERN_INFO + "%s: registering netdev failed\n", DRV_NAME); + free_netdev(dev); + return err; + } + + set_reset_mode(dev); + chipset_init(dev); + return 0; +} +EXPORT_SYMBOL_GPL(register_sja1000dev); + +void unregister_sja1000dev(struct net_device *dev) +{ + set_reset_mode(dev); + unregister_candev(dev); +} +EXPORT_SYMBOL_GPL(unregister_sja1000dev); + +static __init int sja1000_init(void) +{ + printk(KERN_INFO "%s CAN netdevice driver\n", DRV_NAME); + + return 0; +} + +module_init(sja1000_init); + +static __exit void sja1000_exit(void) +{ + printk(KERN_INFO "%s: driver removed\n", DRV_NAME); +} + +module_exit(sja1000_exit); diff --git a/drivers/net/can/sja1000/sja1000.h b/drivers/net/can/sja1000/sja1000.h new file mode 100644 index 000000000000..60d4cd690a24 --- /dev/null +++ b/drivers/net/can/sja1000/sja1000.h @@ -0,0 +1,177 @@ +/* + * sja1000.h - Philips SJA1000 network device driver + * + * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33, + * 38106 Braunschweig, GERMANY + * + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +#ifndef SJA1000_DEV_H +#define SJA1000_DEV_H + +#include <linux/can/dev.h> +#include <linux/can/platform/sja1000.h> + +#define SJA1000_MAX_IRQ 20 /* max. number of interrupts handled in ISR */ + +/* SJA1000 registers - manual section 6.4 (Pelican Mode) */ +#define REG_MOD 0x00 +#define REG_CMR 0x01 +#define REG_SR 0x02 +#define REG_IR 0x03 +#define REG_IER 0x04 +#define REG_ALC 0x0B +#define REG_ECC 0x0C +#define REG_EWL 0x0D +#define REG_RXERR 0x0E +#define REG_TXERR 0x0F +#define REG_ACCC0 0x10 +#define REG_ACCC1 0x11 +#define REG_ACCC2 0x12 +#define REG_ACCC3 0x13 +#define REG_ACCM0 0x14 +#define REG_ACCM1 0x15 +#define REG_ACCM2 0x16 +#define REG_ACCM3 0x17 +#define REG_RMC 0x1D +#define REG_RBSA 0x1E + +/* Common registers - manual section 6.5 */ +#define REG_BTR0 0x06 +#define REG_BTR1 0x07 +#define REG_OCR 0x08 +#define REG_CDR 0x1F + +#define REG_FI 0x10 +#define SFF_BUF 0x13 +#define EFF_BUF 0x15 + +#define FI_FF 0x80 +#define FI_RTR 0x40 + +#define REG_ID1 0x11 +#define REG_ID2 0x12 +#define REG_ID3 0x13 +#define REG_ID4 0x14 + +#define CAN_RAM 0x20 + +/* mode register */ +#define MOD_RM 0x01 +#define MOD_LOM 0x02 +#define MOD_STM 0x04 +#define MOD_AFM 0x08 +#define MOD_SM 0x10 + +/* commands */ +#define CMD_SRR 0x10 +#define CMD_CDO 0x08 +#define CMD_RRB 0x04 +#define CMD_AT 0x02 +#define CMD_TR 0x01 + +/* interrupt sources */ +#define IRQ_BEI 0x80 +#define IRQ_ALI 0x40 +#define IRQ_EPI 0x20 +#define IRQ_WUI 0x10 +#define IRQ_DOI 0x08 +#define IRQ_EI 0x04 +#define IRQ_TI 0x02 +#define IRQ_RI 0x01 +#define IRQ_ALL 0xFF +#define IRQ_OFF 0x00 + +/* status register content */ +#define SR_BS 0x80 +#define SR_ES 0x40 +#define SR_TS 0x20 +#define SR_RS 0x10 +#define SR_TCS 0x08 +#define SR_TBS 0x04 +#define SR_DOS 0x02 +#define SR_RBS 0x01 + +#define SR_CRIT (SR_BS|SR_ES) + +/* ECC register */ +#define ECC_SEG 0x1F +#define ECC_DIR 0x20 +#define ECC_ERR 6 +#define ECC_BIT 0x00 +#define ECC_FORM 0x40 +#define ECC_STUFF 0x80 +#define ECC_MASK 0xc0 + +/* + * Flags for sja1000priv.flags + */ +#define SJA1000_CUSTOM_IRQ_HANDLER 0x1 + +/* + * SJA1000 private data structure + */ +struct sja1000_priv { + struct can_priv can; /* must be the first member! */ + long open_time; + struct sk_buff *echo_skb; + + u8 (*read_reg) (struct net_device *dev, int reg); + void (*write_reg) (struct net_device *dev, int reg, u8 val); + void (*pre_irq) (struct net_device *dev); + void (*post_irq) (struct net_device *dev); + + void *priv; /* for board-specific data */ + struct net_device *dev; + + u8 ocr; + u8 cdr; + u32 flags; +}; + +struct net_device *alloc_sja1000dev(int sizeof_priv); +void free_sja1000dev(struct net_device *dev); +int register_sja1000dev(struct net_device *dev); +void unregister_sja1000dev(struct net_device *dev); + +irqreturn_t sja1000_interrupt(int irq, void *dev_id); + +#endif /* SJA1000_DEV_H */ diff --git a/drivers/net/can/sja1000/sja1000_platform.c b/drivers/net/can/sja1000/sja1000_platform.c new file mode 100644 index 000000000000..a6c5d760895f --- /dev/null +++ b/drivers/net/can/sja1000/sja1000_platform.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005 Sascha Hauer, Pengutronix + * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/can/platform/sja1000.h> +#include <linux/io.h> + +#include "sja1000.h" + +#define DRV_NAME "sja1000_platform" + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_DESCRIPTION("Socket-CAN driver for SJA1000 on the platform bus"); +MODULE_LICENSE("GPL v2"); + +static u8 sp_read_reg(struct net_device *dev, int reg) +{ + return ioread8((void __iomem *)(dev->base_addr + reg)); +} + +static void sp_write_reg(struct net_device *dev, int reg, u8 val) +{ + iowrite8(val, (void __iomem *)(dev->base_addr + reg)); +} + +static int sp_probe(struct platform_device *pdev) +{ + int err, irq; + void __iomem *addr; + struct net_device *dev; + struct sja1000_priv *priv; + struct resource *res_mem, *res_irq; + struct sja1000_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform data provided!\n"); + err = -ENODEV; + goto exit; + } + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_mem || !res_irq) { + err = -ENODEV; + goto exit; + } + + if (!request_mem_region(res_mem->start, + res_mem->end - res_mem->start + 1, + DRV_NAME)) { + err = -EBUSY; + goto exit; + } + + addr = ioremap_nocache(res_mem->start, + res_mem->end - res_mem->start + 1); + if (!addr) { + err = -ENOMEM; + goto exit_release; + } + + irq = res_irq->start; + if (res_irq->flags & IRQF_TRIGGER_MASK) + set_irq_type(irq, res_irq->flags & IRQF_TRIGGER_MASK); + + dev = alloc_sja1000dev(0); + if (!dev) { + err = -ENOMEM; + goto exit_iounmap; + } + priv = netdev_priv(dev); + + priv->read_reg = sp_read_reg; + priv->write_reg = sp_write_reg; + priv->can.bittiming.clock = pdata->clock; + priv->ocr = pdata->ocr; + priv->cdr = pdata->cdr; + + dev->irq = irq; + dev->base_addr = (unsigned long)addr; + + dev_set_drvdata(&pdev->dev, dev); + SET_NETDEV_DEV(dev, &pdev->dev); + + err = register_sja1000dev(dev); + if (err) { + dev_err(&pdev->dev, "registering %s failed (err=%d)\n", + DRV_NAME, err); + goto exit_free; + } + + dev_info(&pdev->dev, "%s device registered (base_addr=%#lx, irq=%d)\n", + DRV_NAME, dev->base_addr, dev->irq); + return 0; + + exit_free: + free_sja1000dev(dev); + exit_iounmap: + iounmap(addr); + exit_release: + release_mem_region(res_mem->start, res_mem->end - res_mem->start + 1); + exit: + return err; +} + +static int sp_remove(struct platform_device *pdev) +{ + struct net_device *dev = dev_get_drvdata(&pdev->dev); + struct resource *res; + + unregister_sja1000dev(dev); + dev_set_drvdata(&pdev->dev, NULL); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, res->end - res->start + 1); + + if (dev->base_addr) + iounmap((void __iomem *)dev->base_addr); + + free_sja1000dev(dev); + + return 0; +} + +static struct platform_driver sp_driver = { + .probe = sp_probe, + .remove = sp_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init sp_init(void) +{ + return platform_driver_register(&sp_driver); +} + +static void __exit sp_exit(void) +{ + platform_driver_unregister(&sp_driver); +} + +module_init(sp_init); +module_exit(sp_exit); diff --git a/drivers/net/can/slcan.c b/drivers/net/can/slcan.c new file mode 100644 index 000000000000..80fcff3e69d8 --- /dev/null +++ b/drivers/net/can/slcan.c @@ -0,0 +1,904 @@ +/* + * slcan.c - serial line CAN interface driver (using tty line discipline) + * + * Copyright (c) 2007 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Send feedback to <socketcan-users@lists.berlios.de> + * + */ + +/* + * This file is derived from linux/drivers/net/slip.c + * + * Therefore it has the same (strange?) behaviour not to unregister the + * netdevice when detaching the tty. Is there any better solution? + * + * Do not try to attach, detach and re-attach a tty for this reason ... + * + * slip.c Authors: Laurence Culhane, <loz@holmes.demon.co.uk> + * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <asm/system.h> +#include <linux/uaccess.h> +#include <linux/bitops.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/rtnetlink.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/if_slip.h> +#include <linux/delay.h> +#include <linux/init.h> + +#include <linux/can.h> + +static __initdata const char banner[] = + KERN_INFO "slcan: serial line CAN interface driver\n"; + +MODULE_ALIAS_LDISC(N_SLCAN); +MODULE_DESCRIPTION("serial line CAN interface"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>"); + +#ifdef CONFIG_CAN_DEBUG_DEVICES +static int debug; +module_param(debug, int, S_IRUGO); +#define DBG(args...) (debug & 1 ? \ + (printk(KERN_DEBUG "slcan %s: ", __func__), \ + printk(args)) : 0) +#else +#define DBG(args...) +#endif + +#ifndef N_SLCAN +#error Your kernel does not support tty line discipline N_SLCAN +#endif +/* + * As there is currently no line discipline N_SLCAN in the mainstream kernel + * you will have to modify two kernel includes & recompile the kernel. + * + * Add this in include/asm/termios.h after the definition of N_HCI: + * #define N_SLCAN 16 + * + * Increment NR_LDICS in include/linux/tty.h from 16 to 17 + * + * NEW: Since Kernel 2.6.21 you only have to change include/linux/tty.h + * + */ + +#define SLC_CHECK_TRANSMIT +#define SLCAN_MAGIC 0x53CA + +static int maxdev = 10; /* MAX number of SLCAN channels; + This can be overridden with + insmod slcan.ko maxdev=nnn */ +module_param(maxdev, int, 0); +MODULE_PARM_DESC(maxdev, "Maximum number of slcan interfaces"); + +/* maximum rx buffer len: extended CAN frame with timestamp */ +#define SLC_MTU (sizeof("T1111222281122334455667788EA5F\r")+1) + +struct slcan { + int magic; + + /* Various fields. */ + struct tty_struct *tty; /* ptr to TTY structure */ + struct net_device *dev; /* easy for intr handling */ + spinlock_t lock; + + /* These are pointers to the malloc()ed frame buffers. */ + unsigned char rbuff[SLC_MTU]; /* receiver buffer */ + int rcount; /* received chars counter */ + unsigned char xbuff[SLC_MTU]; /* transmitter buffer */ + unsigned char *xhead; /* pointer to next XMIT byte */ + int xleft; /* bytes left in XMIT queue */ + + unsigned long flags; /* Flag values/ mode etc */ +#define SLF_INUSE 0 /* Channel in use */ +#define SLF_ERROR 1 /* Parity, etc. error */ + + unsigned char leased; + dev_t line; + pid_t pid; +}; + +static struct net_device **slcan_devs; + + + /************************************************************************ + * SLCAN ENCAPSULATION FORMAT * + ************************************************************************/ + +/* + * A CAN frame has a can_id (11 bit standard frame format OR 29 bit extended + * frame format) a data length code (can_dlc) which can be from 0 to 8 + * and up to <can_dlc> data bytes as payload. + * Additionally a CAN frame may become a remote transmission frame if the + * RTR-bit is set. This causes another ECU to send a CAN frame with the + * given can_id. + * + * The SLCAN ASCII representation of these different frame types is: + * <type> <id> <dlc> <data>* + * + * Extended frames (29 bit) are defined by capital characters in the type. + * RTR frames are defined as 'r' types - normal frames have 't' type: + * t => 11 bit data frame + * r => 11 bit RTR frame + * T => 29 bit data frame + * R => 29 bit RTR frame + * + * The <id> is 3 (standard) or 8 (extended) bytes in ASCII Hex (base64). + * The <dlc> is a one byte ASCII number ('0' - '8') + * The <data> section has at much ASCII Hex bytes as defined by the <dlc> + * + * Examples: + * + * t1230 : can_id 0x123, can_dlc 0, no data + * t4563112233 : can_id 0x456, can_dlc 3, data 0x11 0x22 0x33 + * T12ABCDEF2AA55 : extended can_id 0x12ABCDEF, can_dlc 2, data 0xAA 0x55 + * r1230 : can_id 0x123, can_dlc 0, no data, remote transmission request + * + */ + + /************************************************************************ + * STANDARD SLCAN DECAPSULATION * + ************************************************************************/ + +static int asc2nibble(char c) +{ + + if ((c >= '0') && (c <= '9')) + return c - '0'; + + if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + + if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + + return 16; /* error */ +} + +/* Send one completely decapsulated can_frame to the network layer */ +static void slc_bump(struct slcan *sl) +{ + struct net_device_stats *stats = &sl->dev->stats; + struct sk_buff *skb; + struct can_frame cf; + int i, dlc_pos, tmp; + unsigned long ultmp; + char cmd = sl->rbuff[0]; + + if ((cmd != 't') && (cmd != 'T') && (cmd != 'r') && (cmd != 'R')) + return; + + if (cmd & 0x20) /* tiny chars 'r' 't' => standard frame format */ + dlc_pos = 4; /* dlc position tiiid */ + else + dlc_pos = 9; /* dlc position Tiiiiiiiid */ + + if (!((sl->rbuff[dlc_pos] >= '0') && (sl->rbuff[dlc_pos] < '9'))) + return; + + cf.can_dlc = sl->rbuff[dlc_pos] - '0'; /* get can_dlc from ASCII val */ + + sl->rbuff[dlc_pos] = 0; /* terminate can_id string */ + + if (strict_strtoul(sl->rbuff+1, 16, &ultmp)) + return; + cf.can_id = ultmp; + + if (!(cmd & 0x20)) /* NO tiny chars => extended frame format */ + cf.can_id |= CAN_EFF_FLAG; + + if ((cmd | 0x20) == 'r') /* RTR frame */ + cf.can_id |= CAN_RTR_FLAG; + + *(u64 *) (&cf.data) = 0; /* clear payload */ + + for (i = 0, dlc_pos++; i < cf.can_dlc; i++) { + + tmp = asc2nibble(sl->rbuff[dlc_pos++]); + if (tmp > 0x0F) + return; + cf.data[i] = (tmp << 4); + tmp = asc2nibble(sl->rbuff[dlc_pos++]); + if (tmp > 0x0F) + return; + cf.data[i] |= tmp; + } + + + skb = dev_alloc_skb(sizeof(struct can_frame)); + if (!skb) + return; + + skb->dev = sl->dev; + skb->protocol = htons(ETH_P_CAN); + skb->pkt_type = PACKET_BROADCAST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + memcpy(skb_put(skb, sizeof(struct can_frame)), + &cf, sizeof(struct can_frame)); + netif_rx(skb); + + sl->dev->last_rx = jiffies; + stats->rx_packets++; + stats->rx_bytes += cf.can_dlc; +} + +/* parse tty input stream */ +static void slcan_unesc(struct slcan *sl, unsigned char s) +{ + struct net_device_stats *stats = &sl->dev->stats; + + if ((s == '\r') || (s == '\a')) { /* CR or BEL ends the pdu */ + if (!test_and_clear_bit(SLF_ERROR, &sl->flags) && + (sl->rcount > 4)) { + slc_bump(sl); + } + sl->rcount = 0; + } else { + if (!test_bit(SLF_ERROR, &sl->flags)) { + if (sl->rcount < SLC_MTU) { + sl->rbuff[sl->rcount++] = s; + return; + } else { + stats->rx_over_errors++; + set_bit(SLF_ERROR, &sl->flags); + } + } + } +} + + /************************************************************************ + * STANDARD SLCAN ENCAPSULATION * + ************************************************************************/ + +/* Encapsulate one can_frame and stuff into a TTY queue. */ +static void slc_encaps(struct slcan *sl, struct can_frame *cf) +{ + struct net_device_stats *stats = &sl->dev->stats; + int actual, idx, i; + char cmd; + + if (cf->can_id & CAN_RTR_FLAG) + cmd = 'R'; /* becomes 'r' in standard frame format */ + else + cmd = 'T'; /* becomes 't' in standard frame format */ + + if (cf->can_id & CAN_EFF_FLAG) + sprintf(sl->xbuff, "%c%08X%d", cmd, + cf->can_id & CAN_EFF_MASK, cf->can_dlc); + else + sprintf(sl->xbuff, "%c%03X%d", cmd | 0x20, + cf->can_id & CAN_SFF_MASK, cf->can_dlc); + + idx = strlen(sl->xbuff); + + for (i = 0; i < cf->can_dlc; i++) + sprintf(&sl->xbuff[idx + 2*i], "%02X", cf->data[i]); + + DBG("ASCII frame = '%s'\n", sl->xbuff); + + strcat(sl->xbuff, "\r"); /* add terminating character */ + + /* Order of next two lines is *very* important. + * When we are sending a little amount of data, + * the transfer may be completed inside driver.write() + * routine, because it's running with interrupts enabled. + * In this case we *never* got WRITE_WAKEUP event, + * if we did not request it before write operation. + * 14 Oct 1994 Dmitry Gorodchanin. + */ + sl->tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); + actual = sl->tty->ops->write(sl->tty, sl->xbuff, strlen(sl->xbuff)); +#ifdef SLC_CHECK_TRANSMIT + sl->dev->trans_start = jiffies; +#endif + sl->xleft = strlen(sl->xbuff) - actual; + sl->xhead = sl->xbuff + actual; + stats->tx_bytes += cf->can_dlc; +} + +/* + * Called by the driver when there's room for more data. If we have + * more packets to send, we send them here. + */ +static void slcan_write_wakeup(struct tty_struct *tty) +{ + int actual; + struct slcan *sl = (struct slcan *) tty->disc_data; + struct net_device_stats *stats = &sl->dev->stats; + + /* First make sure we're connected. */ + if (!sl || sl->magic != SLCAN_MAGIC || !netif_running(sl->dev)) + return; + + if (sl->xleft <= 0) { + /* Now serial buffer is almost free & we can start + * transmission of another packet */ + stats->tx_packets++; + tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + netif_wake_queue(sl->dev); + return; + } + + actual = tty->ops->write(tty, sl->xhead, sl->xleft); + sl->xleft -= actual; + sl->xhead += actual; +} + +static void slc_tx_timeout(struct net_device *dev) +{ + struct slcan *sl = netdev_priv(dev); + + spin_lock(&sl->lock); + + if (netif_queue_stopped(dev)) { + if (!netif_running(dev)) + goto out; + + /* May be we must check transmitter timeout here ? + * 14 Oct 1994 Dmitry Gorodchanin. + */ +#ifdef SLC_CHECK_TRANSMIT + if (time_before(jiffies, dev->trans_start + 20 * HZ)) { + /* 20 sec timeout not reached */ + goto out; + } + printk(KERN_WARNING "%s: transmit timed out, %s?\n", dev->name, + (tty_chars_in_buffer(sl->tty) || sl->xleft) + ? "bad line quality" : "driver error"); + sl->xleft = 0; + sl->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + netif_wake_queue(sl->dev); +#endif + } +out: + spin_unlock(&sl->lock); +} + + +/****************************************** + * Routines looking at netdevice side. + ******************************************/ + +/* Send a can_frame to a TTY queue. */ +static int slc_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct slcan *sl = netdev_priv(dev); + + if (skb->len != sizeof(struct can_frame)) + goto out; + + spin_lock(&sl->lock); + if (!netif_running(dev)) { + spin_unlock(&sl->lock); + printk(KERN_WARNING "%s: xmit: iface is down\n", dev->name); + goto out; + } + + if (sl->tty == NULL) { + spin_unlock(&sl->lock); + goto out; + } + + netif_stop_queue(sl->dev); + slc_encaps(sl, (struct can_frame *) skb->data); /* encaps & send */ + spin_unlock(&sl->lock); + +out: + kfree_skb(skb); + return 0; +} + + +/* Netdevice UP -> DOWN routine */ +static int slc_close(struct net_device *dev) +{ + struct slcan *sl = netdev_priv(dev); + + spin_lock_bh(&sl->lock); + if (sl->tty) { + /* TTY discipline is running. */ + sl->tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); + } + netif_stop_queue(dev); + sl->rcount = 0; + sl->xleft = 0; + spin_unlock_bh(&sl->lock); + + return 0; +} + +/* Netdevice DOWN -> UP routine */ +static int slc_open(struct net_device *dev) +{ + struct slcan *sl = netdev_priv(dev); + + if (sl->tty == NULL) + return -ENODEV; + + sl->flags &= (1 << SLF_INUSE); + netif_start_queue(dev); + return 0; +} + +/* Netdevice register callback */ +static void slc_setup(struct net_device *dev) +{ + dev->open = slc_open; + dev->destructor = free_netdev; + dev->stop = slc_close; + dev->hard_start_xmit = slc_xmit; + + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->tx_queue_len = 10; + + dev->mtu = sizeof(struct can_frame); + dev->type = ARPHRD_CAN; +#ifdef SLC_CHECK_TRANSMIT + dev->tx_timeout = slc_tx_timeout; + dev->watchdog_timeo = 20*HZ; +#endif + + /* New-style flags. */ + dev->flags = IFF_NOARP; + dev->features = NETIF_F_NO_CSUM; +} + +/****************************************** + * Routines looking at TTY side. + ******************************************/ + +/* + * Handle the 'receiver data ready' interrupt. + * This function is called by the 'tty_io' module in the kernel when + * a block of SLCAN data has been received, which can now be decapsulated + * and sent on to some IP layer for further processing. This will not + * be re-entered while running but other ldisc functions may be called + * in parallel + */ + +static void slcan_receive_buf(struct tty_struct *tty, + const unsigned char *cp, char *fp, int count) +{ + struct slcan *sl = (struct slcan *) tty->disc_data; + struct net_device_stats *stats = &sl->dev->stats; + + if (!sl || sl->magic != SLCAN_MAGIC || + !netif_running(sl->dev)) + return; + + /* Read the characters out of the buffer */ + while (count--) { + if (fp && *fp++) { + if (!test_and_set_bit(SLF_ERROR, &sl->flags)) + stats->rx_errors++; + cp++; + continue; + } + slcan_unesc(sl, *cp++); + } +} + +/************************************ + * slcan_open helper routines. + ************************************/ + +/* Collect hanged up channels */ + +static void slc_sync(void) +{ + int i; + struct net_device *dev; + struct slcan *sl; + + for (i = 0; i < maxdev; i++) { + dev = slcan_devs[i]; + if (dev == NULL) + break; + + sl = netdev_priv(dev); + if (sl->tty || sl->leased) + continue; + if (dev->flags&IFF_UP) + dev_close(dev); + } +} + + +/* Find a free SLCAN channel, and link in this `tty' line. */ +static struct slcan *slc_alloc(dev_t line) +{ + int i; + int sel = -1; + int score = -1; + struct net_device *dev = NULL; + struct slcan *sl; + + if (slcan_devs == NULL) + return NULL; /* Master array missing ! */ + + for (i = 0; i < maxdev; i++) { + dev = slcan_devs[i]; + if (dev == NULL) + break; + + sl = netdev_priv(dev); + if (sl->leased) { + if (sl->line != line) + continue; + if (sl->tty) + return NULL; + + /* Clear ESCAPE & ERROR flags */ + sl->flags &= (1 << SLF_INUSE); + return sl; + } + + if (sl->tty) + continue; + + if (current->pid == sl->pid) { + if (sl->line == line && score < 3) { + sel = i; + score = 3; + continue; + } + if (score < 2) { + sel = i; + score = 2; + } + continue; + } + if (sl->line == line && score < 1) { + sel = i; + score = 1; + continue; + } + if (score < 0) { + sel = i; + score = 0; + } + } + + if (sel >= 0) { + i = sel; + dev = slcan_devs[i]; + if (score > 1) { + sl = netdev_priv(dev); + sl->flags &= (1 << SLF_INUSE); + return sl; + } + } + + /* Sorry, too many, all slots in use */ + if (i >= maxdev) + return NULL; + + if (dev) { + sl = netdev_priv(dev); + if (test_bit(SLF_INUSE, &sl->flags)) { + unregister_netdevice(dev); + dev = NULL; + slcan_devs[i] = NULL; + } + } + + if (!dev) { + char name[IFNAMSIZ]; + sprintf(name, "slc%d", i); + + dev = alloc_netdev(sizeof(*sl), name, slc_setup); + if (!dev) + return NULL; + dev->base_addr = i; + } + + sl = netdev_priv(dev); + + /* Initialize channel control data */ + sl->magic = SLCAN_MAGIC; + sl->dev = dev; + spin_lock_init(&sl->lock); + slcan_devs[i] = dev; + + return sl; +} + +/* + * Open the high-level part of the SLCAN channel. + * This function is called by the TTY module when the + * SLCAN line discipline is called for. Because we are + * sure the tty line exists, we only have to link it to + * a free SLCAN channel... + * + * Called in process context serialized from other ldisc calls. + */ + +static int slcan_open(struct tty_struct *tty) +{ + struct slcan *sl; + int err; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + /* RTnetlink lock is misused here to serialize concurrent + opens of slcan channels. There are better ways, but it is + the simplest one. + */ + rtnl_lock(); + + /* Collect hanged up channels. */ + slc_sync(); + + sl = (struct slcan *) tty->disc_data; + + err = -EEXIST; + /* First make sure we're not already connected. */ + if (sl && sl->magic == SLCAN_MAGIC) + goto err_exit; + + /* OK. Find a free SLCAN channel to use. */ + err = -ENFILE; + sl = slc_alloc(tty_devnum(tty)); + if (sl == NULL) + goto err_exit; + + sl->tty = tty; + tty->disc_data = sl; + sl->line = tty_devnum(tty); + sl->pid = current->pid; + + if (!test_bit(SLF_INUSE, &sl->flags)) { + /* Perform the low-level SLCAN initialization. */ + sl->rcount = 0; + sl->xleft = 0; + + set_bit(SLF_INUSE, &sl->flags); + + err = register_netdevice(sl->dev); + if (err) + goto err_free_chan; + } + + /* Done. We have linked the TTY line to a channel. */ + rtnl_unlock(); + + tty->receive_room = 65536; /* We don't flow control */ + + return sl->dev->base_addr; + +err_free_chan: + sl->tty = NULL; + tty->disc_data = NULL; + clear_bit(SLF_INUSE, &sl->flags); + +err_exit: + rtnl_unlock(); + + /* Count references from TTY module */ + return err; +} + +/* + + FIXME: 1,2 are fixed 3 was never true anyway. + + Let me to blame a bit. + 1. TTY module calls this funstion on soft interrupt. + 2. TTY module calls this function WITH MASKED INTERRUPTS! + 3. TTY module does not notify us about line discipline + shutdown, + + Seems, now it is clean. The solution is to consider netdevice and + line discipline sides as two independent threads. + + By-product (not desired): slc? does not feel hangups and remains open. + It is supposed, that user level program (dip, diald, slattach...) + will catch SIGHUP and make the rest of work. + + I see no way to make more with current tty code. --ANK + */ + +/* + * Close down a SLCAN channel. + * This means flushing out any pending queues, and then returning. This + * call is serialized against other ldisc functions. + */ +static void slcan_close(struct tty_struct *tty) +{ + struct slcan *sl = (struct slcan *) tty->disc_data; + + /* First make sure we're connected. */ + if (!sl || sl->magic != SLCAN_MAGIC || sl->tty != tty) + return; + + tty->disc_data = NULL; + sl->tty = NULL; + if (!sl->leased) + sl->line = 0; + + /* Count references from TTY module */ +} + +/* Perform I/O control on an active SLCAN channel. */ +static int slcan_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct slcan *sl = (struct slcan *) tty->disc_data; + unsigned int tmp; + + /* First make sure we're connected. */ + if (!sl || sl->magic != SLCAN_MAGIC) + return -EINVAL; + + switch (cmd) { + case SIOCGIFNAME: + tmp = strlen(sl->dev->name) + 1; + if (copy_to_user((void __user *)arg, sl->dev->name, tmp)) + return -EFAULT; + return 0; + + case SIOCSIFHWADDR: + return -EINVAL; + + default: + return tty_mode_ioctl(tty, file, cmd, arg); + } +} + +static struct tty_ldisc_ops slc_ldisc = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "slcan", + .open = slcan_open, + .close = slcan_close, + .ioctl = slcan_ioctl, + .receive_buf = slcan_receive_buf, + .write_wakeup = slcan_write_wakeup, +}; + +/************************************ + * general slcan module init/exit + ************************************/ + +static int __init slcan_init(void) +{ + int status; + + if (maxdev < 4) + maxdev = 4; /* Sanity */ + + printk(banner); + printk(KERN_INFO "slcan: %d dynamic interface channels.\n", maxdev); + + slcan_devs = kmalloc(sizeof(struct net_device *)*maxdev, GFP_KERNEL); + if (!slcan_devs) { + printk(KERN_ERR "slcan: can't allocate slcan device array!\n"); + return -ENOMEM; + } + + /* Clear the pointer array, we allocate devices when we need them */ + memset(slcan_devs, 0, sizeof(struct net_device *)*maxdev); + + /* Fill in our line protocol discipline, and register it */ + status = tty_register_ldisc(N_SLCAN, &slc_ldisc); + if (status != 0) { + printk(KERN_ERR "slcan: can't register line discipline\n"); + kfree(slcan_devs); + } + return status; +} + +static void __exit slcan_exit(void) +{ + int i; + struct net_device *dev; + struct slcan *sl; + unsigned long timeout = jiffies + HZ; + int busy = 0; + + if (slcan_devs == NULL) + return; + + /* First of all: check for active disciplines and hangup them. + */ + do { + if (busy) + msleep_interruptible(100); + + busy = 0; + for (i = 0; i < maxdev; i++) { + dev = slcan_devs[i]; + if (!dev) + continue; + sl = netdev_priv(dev); + spin_lock_bh(&sl->lock); + if (sl->tty) { + busy++; + tty_hangup(sl->tty); + } + spin_unlock_bh(&sl->lock); + } + } while (busy && time_before(jiffies, timeout)); + + + for (i = 0; i < maxdev; i++) { + dev = slcan_devs[i]; + if (!dev) + continue; + slcan_devs[i] = NULL; + + sl = netdev_priv(dev); + if (sl->tty) { + printk(KERN_ERR "%s: tty discipline still running\n", + dev->name); + /* Intentionally leak the control block. */ + dev->destructor = NULL; + } + + unregister_netdev(dev); + } + + kfree(slcan_devs); + slcan_devs = NULL; + + i = tty_unregister_ldisc(N_SLCAN); + if (i) + printk(KERN_ERR "slcan: can't unregister ldisc (err %d)\n", i); +} + +module_init(slcan_init); +module_exit(slcan_exit); diff --git a/drivers/net/can/softing/Makefile b/drivers/net/can/softing/Makefile new file mode 100644 index 000000000000..df3fceca7bd0 --- /dev/null +++ b/drivers/net/can/softing/Makefile @@ -0,0 +1,20 @@ +# Makefile for softing CAN driver + +ifeq ($(KERNELRELEASE),) +# necessary when used outside kernel +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common + +softing-y := softing_main.o softing_fw.o +obj-$(CONFIG_CAN_SOFTING) += softing.o +obj-$(CONFIG_CAN_SOFTING_CS) += softing_cs.o + +endif diff --git a/drivers/net/can/softing/softing.h b/drivers/net/can/softing/softing.h new file mode 100644 index 000000000000..e43c7f116799 --- /dev/null +++ b/drivers/net/can/softing/softing.h @@ -0,0 +1,268 @@ +/* + * softing common interfaces + * + * by Kurt Van Dijck, 06-2008 + */ + +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/can.h> +#include <linux/can/dev.h> + +struct softing; +struct sofing_desc; + +/* special attribute, so we should not rely on the ->priv pointers + * before knowing how to interpret these + */ +struct softing_attribute; + +struct softing_priv { + struct can_priv can; /* must be the first member! */ + struct net_device *netdev; + struct softing *card; + struct { + int pending; + /* variables wich hold the circular buffer */ + int echo_put; + int echo_get; + } tx; + struct can_bittiming_const btr_const; + int index; + u8 output; + u16 chip; + struct attribute_group sysfs; +}; +#define netdev2softing(netdev) ((struct softing_priv *)netdev_priv(netdev)) + +/* the 'all cards have the same' fields definition */ +extern const struct can_bittiming_const softing_btr_const; + +struct softing_desc { + unsigned int manf; + unsigned int prod; + /* generation + * 1st with NEC or SJA1000 + * 8bit, exclusive interrupt, ... + * 2nd only SJA11000 + * 16bit, shared interrupt + */ + int generation; + unsigned int freq; /*crystal in MHz */ + unsigned int max_brp; + unsigned int max_sjw; + unsigned long dpram_size; + char name[32]; + struct { + unsigned long offs; + unsigned long addr; + char fw[32]; + } boot, load, app; +}; + +struct softing { + int nbus; + struct softing_priv *bus[2]; + spinlock_t spin; /* protect this structure & DPRAM access */ + + struct { + /* indication of firmware status */ + int up; + /* protection of the 'up' variable */ + struct mutex lock; + } fw; + struct { + int nr; + int requested; + struct tasklet_struct bh; + int svc_count; + } irq; + struct { + int pending; + int last_bus; + /* keep the bus that last tx'd a message, + * in order to let every netdev queue resume + */ + } tx; + struct { + unsigned long phys; + unsigned long size; + unsigned char *virt; + unsigned char *end; + struct softing_fct *fct; + struct softing_info *info; + struct softing_rx *rx; + struct softing_tx *tx; + struct softing_irq *irq; + unsigned short *command; + unsigned short *receipt; + } dpram; + struct { + unsigned short manf; + unsigned short prod; + u32 serial, fw, hw, lic; + u16 chip [2]; + u32 freq; + const char *name; + } id; + const struct softing_desc *desc; + struct { + int (*reset) (struct softing *, int); + int (*enable_irq)(struct softing *, int); + } fn; + struct device *dev; + /* sysfs */ + struct attribute_group sysfs; + struct softing_attribute *attr; + struct attribute **grp; +}; + +extern int mk_softing(struct softing *); +/* fields that must be set already are : + * ncan + * id.manf + * id.prod + * fn.reset + * fn.enable_irq + */ +extern void rm_softing(struct softing *); +/* usefull functions during operation */ + +extern const struct softing_desc * + softing_lookup_desc(unsigned int manf, unsigned int prod); + +extern int softing_default_output(struct softing *card + , struct softing_priv *priv); +extern u32 softing_time2usec(struct softing *card, u32 raw); + +extern int softing_fct_cmd(struct softing *card + , int cmd, int vector, const char *msg); + +extern int softing_bootloader_command(struct softing *card + , int command, const char *msg); + +/* Load firmware after reset */ +extern int softing_load_fw(const char *file, struct softing *card, + unsigned char *virt, unsigned int size, int offset); + +/* Load final application firmware after bootloader */ +extern int softing_load_app_fw(const char *file, struct softing *card); + +extern int softing_reset_chip(struct softing *card); + +/* enable or disable irq + * only called with fw.lock locked + */ +extern int softing_card_irq(struct softing *card, int enable); + +/* called when tx queue is flushed */ +extern void softing_flush_echo_skb(struct softing_priv *priv); + +/* reinitaliase the card, apply -1 for bus[01] for 'no change' */ +extern int softing_reinit(struct softing *card, int bus0, int bus1); + +/* SOFTING DPRAM mappings */ +struct softing_rx { + u8 fifo[16][32]; + u8 dummy1; + u16 rd; + u16 dummy2; + u16 wr; + u16 dummy3; + u16 lost_msg; +} __attribute__((packed)); + +#define TXMAX 31 +struct softing_tx { + u8 fifo[32][16]; + u8 dummy1; + u16 rd; + u16 dummy2; + u16 wr; + u8 dummy3; +} __attribute__((packed)); + +struct softing_irq { + u8 to_host; + u8 to_card; +} __attribute__((packed)); + +struct softing_fct { + s16 param[20]; /* 0 is index */ + s16 returned; + u8 dummy; + u16 host_access; +} __attribute__((packed)); + +struct softing_info { + u8 dummy1; + u16 bus_state; + u16 dummy2; + u16 bus_state2; + u16 dummy3; + u16 error_state; + u16 dummy4; + u16 error_state2; + u16 dummy5; + u16 reset; + u16 dummy6; + u16 clear_rcv_fifo; + u16 dummy7; + u16 dummyxx; + u16 dummy8; + u16 time_reset; + u8 dummy9; + u32 time; + u32 time_wrap; + u8 wr_start; + u8 wr_end; + u8 dummy10; + u16 dummy12; + u16 dummy12x; + u16 dummy13; + u16 reset_rcv_fifo; + u8 dummy14; + u8 reset_xmt_fifo; + u8 read_fifo_levels; + u16 rcv_fifo_level; + u16 xmt_fifo_level; +} __attribute__((packed)); + +/* DPRAM return codes */ +#define RES_NONE 0 +#define RES_OK 1 +#define RES_NOK 2 +#define RES_UNKNOWN 3 +/* DPRAM flags */ +#define CMD_TX 0x01 +#define CMD_ACK 0x02 +#define CMD_XTD 0x04 +#define CMD_RTR 0x08 +#define CMD_ERR 0x10 +#define CMD_BUS2 0x80 + +/* debug */ +extern int softing_debug; + +#define mod_alert(fmt,arg...) { \ + if (softing_debug >= 0) \ + printk(KERN_ALERT "[%s] %s:" fmt "\n" \ + , THIS_MODULE->name \ + , __func__ \ + , ##arg); \ + } +#define mod_info(fmt,arg...) { \ + if (softing_debug >= 1) \ + printk(KERN_INFO "[%s] %s:" fmt "\n"\ + , THIS_MODULE->name \ + , __func__ \ + , ##arg); \ + } +#define mod_trace(fmt,arg...) { \ + if (softing_debug >= 2) \ + printk(KERN_DEBUG "[%s] %s:" fmt "\n" \ + , THIS_MODULE->name \ + , __func__ \ + , ##arg); \ + } + diff --git a/drivers/net/can/softing/softing_cs.c b/drivers/net/can/softing/softing_cs.c new file mode 100644 index 000000000000..e33b614de28f --- /dev/null +++ b/drivers/net/can/softing/softing_cs.c @@ -0,0 +1,427 @@ +/* +* drivers/net/can/softing/softing_cs.c +* +* Copyright (C) 2008 +* +* - Kurt Van Dijck, EIA Electronics +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the version 2 of the GNU General Public License +* as published by the Free Software Foundation +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/major.h> +#include <linux/io.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <asm/system.h> + +#include "softing.h" + +struct softing_cs { + struct softing softing; + win_req_t win; +}; +#define softing2cs(x) container_of((x), struct softing_cs, softing) + +struct lookup { + int i; + const char *a; +}; + +static const char __devinit *lookup_mask(const struct lookup *lp, int *i) +{ + for (; lp->a; ++lp) { + if (lp->i & *i) { + *i &= ~lp->i; + return lp->a; + } + } + return 0; +} + +static int card_reset_via_pcmcia(struct softing *sdev, int v) +{ + struct pcmcia_device *pcmcia = to_pcmcia_dev(sdev->dev); + conf_reg_t reg; + reg.Function = 0; /* socket */ + reg.Action = CS_WRITE; + reg.Offset = 2; + reg.Value = v ? 0 : 0x20; + return pcmcia_access_configuration_register(pcmcia, ®); +} + +static int card_reset_via_dpram(struct softing *sdev, int v) +{ + if (v) { + spin_lock_bh(&sdev->spin); + sdev->dpram.virt[0xe00] &= ~1; + spin_unlock_bh(&sdev->spin); + card_reset_via_pcmcia(sdev, v); + } else { + card_reset_via_pcmcia(sdev, v); + spin_lock_bh(&sdev->spin); + sdev->dpram.virt[0xe00] |= 1; + spin_unlock_bh(&sdev->spin); + } + return 0; +} + +static int card_enable_irq_via_pcmcia(struct softing *sdev, int v) +{ + int ret; + struct pcmcia_device *pcmcia = to_pcmcia_dev(sdev->dev); + conf_reg_t reg; + memset(®, 0, sizeof(reg)); + reg.Function = 0; /* socket */ + reg.Action = CS_WRITE; + reg.Offset = 0; + reg.Value = v ? 0x60 : 0; + ret = pcmcia_access_configuration_register(pcmcia, ®); + if (ret) + mod_alert("failed %u", ret); + return ret; +} + +/* TODO: in 2.6.26, __devinitconst works*/ +static const __devinitdata struct lookup pcmcia_io_attr[] = { + { IO_DATA_PATH_WIDTH_AUTO , "[auto]" , }, + { IO_DATA_PATH_WIDTH_8 , "8bit" , }, + { IO_DATA_PATH_WIDTH_16 , "16bit" , }, + { 0, 0, }, +}; + +static const __devinitdata struct lookup pcmcia_mem_attr[] = { + { WIN_ADDR_SPACE_IO , "IO" , }, + { WIN_MEMORY_TYPE_AM , "typeAM" , }, + { WIN_ENABLE , "enable" , }, + { WIN_DATA_WIDTH_8 , "8bit" , }, + { WIN_DATA_WIDTH_16 , "16bit" , }, + { WIN_DATA_WIDTH_32 , "32bit" , }, + { WIN_PAGED , "paged" , }, + { WIN_SHARED , "shared" , }, + { WIN_FIRST_SHARED , "first_shared", }, + { WIN_USE_WAIT , "wait" , }, + { WIN_STRICT_ALIGN , "strict_align", }, + { WIN_MAP_BELOW_1MB , "below_1MB" , }, + { WIN_PREFETCH , "prefetch" , }, + { WIN_CACHEABLE , "cacheable" , }, + { 0, 0, }, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28) +/* backported */ +struct pcmcia_cfg_mem { + tuple_t tuple; + cisparse_t parse; + u8 buf[256]; + cistpl_cftable_entry_t dflt; +}; +static int pcmcia_loop_config(struct pcmcia_device *p_dev, + int (*conf_check) (struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cfg, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data), + void *priv_data) +{ + struct pcmcia_cfg_mem *cfg_mem; + + tuple_t *tuple; + int ret = -ENODEV; + unsigned int vcc; + + cfg_mem = kzalloc(sizeof(*cfg_mem), GFP_KERNEL); + if (cfg_mem == NULL) + return -ENOMEM; + + /* get the current Vcc setting */ + vcc = p_dev->socket->socket.Vcc; + + tuple = &cfg_mem->tuple; + tuple->TupleData = cfg_mem->buf; + tuple->TupleDataMax = sizeof(cfg_mem->buf)-1; + tuple->TupleOffset = 0; + tuple->DesiredTuple = CISTPL_CFTABLE_ENTRY; + tuple->Attributes = 0; + + ret = pcmcia_get_first_tuple(p_dev, tuple); + while (!ret) { + cistpl_cftable_entry_t *cfg = &cfg_mem->parse.cftable_entry; + + if (pcmcia_get_tuple_data(p_dev, tuple)) + goto next_entry; + + if (pcmcia_parse_tuple(p_dev, tuple, &cfg_mem->parse)) + goto next_entry; + + /* default values */ + p_dev->conf.ConfigIndex = cfg->index; + if (cfg->flags & CISTPL_CFTABLE_DEFAULT) + cfg_mem->dflt = *cfg; + + ret = conf_check(p_dev, cfg, &cfg_mem->dflt, vcc, priv_data); + if (!ret) + break; + +next_entry: + ret = pcmcia_get_next_tuple(p_dev, tuple); + } + kfree(cfg_mem); + return ret; +} +#endif + +static int dev_conf_check(struct pcmcia_device *pdev, + cistpl_cftable_entry_t *cf, cistpl_cftable_entry_t *def_cf, + unsigned int vcc, void *priv_data) +{ + struct softing_cs *csdev = priv_data; + struct softing *sdev = &csdev->softing; + int ret; + + if (!cf->index) + goto do_next; + /* power settings (Vcc & Vpp) */ + if (cf->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (vcc != cf->vcc.param[CISTPL_POWER_VNOM]/10000) { + mod_alert("%s: cf->Vcc mismatch\n", __FILE__); + goto do_next; + } + } else if (def_cf->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (vcc != def_cf->vcc.param[CISTPL_POWER_VNOM]/10000) { + mod_alert("%s: cf->Vcc mismatch\n", __FILE__); + goto do_next; + } + } + if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) + pdev->conf.Vpp + = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; + + else if (def_cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) + pdev->conf.Vpp + = def_cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; + + /* interrupt ? */ + if (cf->irq.IRQInfo1 || def_cf->irq.IRQInfo1) + pdev->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window */ + pdev->io.NumPorts1 + = pdev->io.NumPorts2 + = 0; + /* Memory window */ + if ((cf->mem.nwin > 0) || (def_cf->mem.nwin > 0)) { + memreq_t map; + cistpl_mem_t *mem + = (cf->mem.nwin) ? &cf->mem : &def_cf->mem; + /* softing specific: choose 8 or 16bit access */ + csdev->win.Attributes = ((sdev->desc->generation >= 2) + ? WIN_DATA_WIDTH_16 : WIN_DATA_WIDTH_8) + | WIN_MEMORY_TYPE_CM + | WIN_ENABLE; + csdev->win.Base = mem->win[0].host_addr; + csdev->win.Size = mem->win[0].len; + csdev->win.AccessSpeed = 0; + ret = pcmcia_request_window(&pdev, &csdev->win, &pdev->win); + if (ret) { + mod_alert("pcmcia_request_window() mismatch\n"); + goto do_next; + } + /* softing specific: choose slower access for old cards */ + if (sdev->desc->generation < 2) { + pdev->win->ctl.flags + = MAP_ACTIVE | MAP_USE_WAIT; + pdev->win->ctl.speed = 3; + } + map.Page = 0; + map.CardOffset = mem->win[0].card_addr; + if (pcmcia_map_mem_page(pdev->win, &map)) { + mod_alert("pcmcia_map_mem_page() mismatch\n"); + goto do_next_win; + } + } else { + mod_info("no memory window in tuple %u", cf->index); + goto do_next; + } + return 0; +do_next_win: +do_next: + pcmcia_disable_device(pdev); + return -ENODEV; +} + +static void driver_remove(struct pcmcia_device *pcmcia) +{ + struct softing *card = (struct softing *)pcmcia->priv; + struct softing_cs *cs = softing2cs(card); + mod_trace("%s,device'%s'", card->id.name, pcmcia->devname); + rm_softing(card); + /* release pcmcia stuff */ + pcmcia_disable_device(pcmcia); + /* free bits */ + kfree(cs); +} + +static int __devinit driver_probe(struct pcmcia_device *pcmcia) +{ + struct softing_cs *cs; + struct softing *card; + + mod_trace("on %s", pcmcia->devname); + + /* Create new softing device */ + cs = kzalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) + goto no_mem; + /* setup links */ + card = &cs->softing; + pcmcia->priv = card; + card->dev = &pcmcia->dev; + /* properties */ + card->id.manf = pcmcia->manf_id; + card->id.prod = pcmcia->card_id; + card->desc = softing_lookup_desc(card->id.manf, card->id.prod); + if (card->desc->generation >= 2) { + card->fn.reset = card_reset_via_dpram; + } else { + card->fn.reset = card_reset_via_pcmcia; + card->fn.enable_irq = card_enable_irq_via_pcmcia; + } + + card->nbus = 2; + /* pcmcia presets */ + pcmcia->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + pcmcia->irq.IRQInfo1 = IRQ_LEVEL_ID; + pcmcia->irq.Handler = 0; + pcmcia->conf.Attributes = 0; + pcmcia->conf.IntType = INT_MEMORY_AND_IO; + + if (pcmcia_loop_config(pcmcia, dev_conf_check, cs)) + goto config_failed; + + if (pcmcia_request_irq(pcmcia, &pcmcia->irq)) + goto config_failed; + + if (pcmcia_request_configuration(pcmcia, &pcmcia->conf)) + goto config_failed; + + card->dpram.phys = cs->win.Base; + card->dpram.size = cs->win.Size; + + if (card->dpram.size != 0x1000) { + mod_alert("dpram size 0x%lx mismatch\n", card->dpram.size); + goto wrong_dpram; + } + + /* Finally, report what we've done */ + printk(KERN_INFO "[%s] %s: index 0x%02x", + THIS_MODULE->name, + pcmcia->devname, + pcmcia->conf.ConfigIndex); + if (pcmcia->conf.Vpp) + printk(", Vpp %d.%d", pcmcia->conf.Vpp/10, pcmcia->conf.Vpp%10); + if (pcmcia->conf.Attributes & CONF_ENABLE_IRQ) { + printk(", irq %d", pcmcia->irq.AssignedIRQ); + card->irq.nr = pcmcia->irq.AssignedIRQ; + } + if (pcmcia->win) { + int tmp; + const char *p; + printk(", mem 0x%08lx-0x%08lx" + , card->dpram.phys + , card->dpram.phys + card->dpram.size-1); + tmp = cs->win.Attributes; + while (tmp) { + p = lookup_mask(pcmcia_mem_attr, &tmp); + if (p) + printk(" %s", p); + } + } + printk("\n"); + + if (mk_softing(card)) + goto softing_failed; + return 0; + +softing_failed: +wrong_dpram: +config_failed: + kfree(cs); +no_mem: + pcmcia_disable_device(pcmcia); + return -ENODEV; +} + +static struct pcmcia_device_id driver_ids[] = { + /* softing */ + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0001), + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0002), + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0004), + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0005), + /* vector , manufacturer? */ + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0081), + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0084), + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0085), + /* EDIC */ + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0102), + PCMCIA_DEVICE_MANF_CARD(0x0168, 0x0105), + PCMCIA_DEVICE_NULL, +}; + +MODULE_DEVICE_TABLE(pcmcia, driver_ids); + +static struct pcmcia_driver softing_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "softing_cs", + }, + .probe = driver_probe, + .remove = driver_remove, + .id_table = driver_ids, +}; + +static int __init mod_start(void) +{ + mod_trace(""); + return pcmcia_register_driver(&softing_cs_driver); +} + +static void __exit mod_stop(void) +{ + mod_trace(""); + pcmcia_unregister_driver(&softing_cs_driver); +} + +module_init(mod_start); +module_exit(mod_stop); + +MODULE_DESCRIPTION("softing CANcard driver" + ", links PCMCIA card to softing driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("softing CANcard2"); + diff --git a/drivers/net/can/softing/softing_fw.c b/drivers/net/can/softing/softing_fw.c new file mode 100644 index 000000000000..4d20774c9cb9 --- /dev/null +++ b/drivers/net/can/softing/softing_fw.c @@ -0,0 +1,685 @@ +/* +* drivers/net/can/softing/softing_fw.c +* +* Copyright (C) 2008 +* +* - Kurt Van Dijck, EIA Electronics +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the version 2 of the GNU General Public License +* as published by the Free Software Foundation +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/io.h> + +#include "softing.h" + +#define fw_dir "softing-4.6/" + +const struct can_bittiming_const softing_btr_const = { + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 8, + .sjw_max = 4, /* overruled */ + .brp_min = 1, + .brp_max = 32, /* overruled */ + .brp_inc = 1, +}; + +static const struct softing_desc carddescs[] = { +{ + .name = "CANcard", + .manf = 0x0168, .prod = 0x001, + .generation = 1, + .freq = 16, .max_brp = 32, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",}, +}, { + .name = "CANcard-NEC", + .manf = 0x0168, .prod = 0x002, + .generation = 1, + .freq = 16, .max_brp = 32, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",}, +}, { + .name = "CANcard-SJA", + .manf = 0x0168, .prod = 0x004, + .generation = 1, + .freq = 20, .max_brp = 32, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cansja.bin",}, +}, { + .name = "CANcard-2", + .manf = 0x0168, .prod = 0x005, + .generation = 2, + .freq = 24, .max_brp = 64, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard2.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard2.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancrd2.bin",}, +}, { + .name = "Vector-CANcard", + .manf = 0x0168, .prod = 0x081, + .generation = 1, + .freq = 16, .max_brp = 64, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",}, +}, { + .name = "Vector-CANcard-SJA", + .manf = 0x0168, .prod = 0x084, + .generation = 1, + .freq = 20, .max_brp = 32, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cansja.bin",}, +}, { + .name = "Vector-CANcard-2", + .manf = 0x0168, .prod = 0x085, + .generation = 2, + .freq = 24, .max_brp = 64, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard2.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard2.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancrd2.bin",}, +}, { + .name = "EDICcard-NEC", + .manf = 0x0168, .prod = 0x102, + .generation = 1, + .freq = 16, .max_brp = 64, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancard.bin",}, +}, { + .name = "EDICcard-2", + .manf = 0x0168, .prod = 0x105, + .generation = 2, + .freq = 24, .max_brp = 64, .max_sjw = 4, + .dpram_size = 0x0800, + .boot = {0x0000, 0x000000, fw_dir "bcard2.bin",}, + .load = {0x0120, 0x00f600, fw_dir "ldcard2.bin",}, + .app = {0x0010, 0x0d0000, fw_dir "cancrd2.bin",}, + }, + +/* never tested, but taken from original softing */ +{ .name = "CAN-AC2-104", + .manf = 0x0000, .prod = 0x009, + .generation = 1, + .freq = 25, .max_brp = 64, .max_sjw = 4, + .dpram_size = 0x1000, + .boot = {0x0000, 0x000000, fw_dir "boot104.bin",}, + .load = {0x0800, 0x035000, fw_dir "ld104.bin",}, + .app = {0x0010, 0x120000, fw_dir "canpc104.bin",}, + }, +{0, 0,}, +}; + +const struct softing_desc *softing_lookup_desc + (unsigned int manf, unsigned int prod) +{ + const struct softing_desc *lp = carddescs; + for (; lp->name; ++lp) { + if ((lp->manf == manf) && (lp->prod == prod)) + return lp; + } + return 0; +} +EXPORT_SYMBOL(softing_lookup_desc); + +int softing_fct_cmd(struct softing *card, int cmd, int vector, const char *msg) +{ + int ret; + unsigned long stamp; + if (vector == RES_OK) + vector = RES_NONE; + card->dpram.fct->param[0] = cmd; + card->dpram.fct->host_access = vector; + /* be sure to flush this to the card */ + wmb(); + stamp = jiffies; + /*wait for card */ + do { + ret = card->dpram.fct->host_access; + /* don't have any cached variables */ + rmb(); + if (ret == RES_OK) { + /*don't read return-value now */ + ret = card->dpram.fct->returned; + if (ret) + mod_alert("%s returned %u", msg, ret); + return 0; + } + if ((jiffies - stamp) >= 1 * HZ) + break; + if (in_interrupt()) + /* go as fast as possible */ + continue; + /* process context => relax */ + schedule(); + } while (!signal_pending(current)); + + if (ret == RES_NONE) { + mod_alert("%s, no response from card on %u/0x%02x" + , msg, cmd, vector); + return 1; + } else { + mod_alert("%s, bad response from card on %u/0x%02x, 0x%04x" + , msg, cmd, vector, ret); + /*make sure to return something not 0 */ + return ret ? ret : 1; + } +} + +int softing_bootloader_command(struct softing *card + , int command, const char *msg) +{ + int ret; + unsigned long stamp; + card->dpram.receipt[0] = RES_NONE; + card->dpram.command[0] = command; + /* be sure to flush this to the card */ + wmb(); + stamp = jiffies; + /*wait for card */ + do { + ret = card->dpram.receipt[0]; + /* don't have any cached variables */ + rmb(); + if (ret == RES_OK) + return 0; + if ((jiffies - stamp) >= (3 * HZ)) + break; + schedule(); + } while (!signal_pending(current)); + + switch (ret) { + case RES_NONE: + mod_alert("%s: no response from card", msg); + break; + case RES_NOK: + mod_alert("%s: response from card nok", msg); + break; + case RES_UNKNOWN: + mod_alert("%s: command 0x%04x unknown", msg, command); + break; + default: + mod_alert("%s: bad response from card (%u)]", msg, ret); + break; + } + return ret ? ret : 1; +} + +struct fw_hdr { + u16 type; + u32 addr; + u16 len; + u16 checksum; + const unsigned char *base; +} __attribute__ ((packed)); + +static int fw_parse(const unsigned char **pmem, struct fw_hdr *hdr) +{ + u16 tmp; + const unsigned char *mem; + const unsigned char *end; + mem = *pmem; + hdr->type = (mem[0] << 0) | (mem[1] << 8); + hdr->addr = (mem[2] << 0) | (mem[3] << 8) + | (mem[4] << 16) | (mem[5] << 24); + hdr->len = (mem[6] << 0) | (mem[7] << 8); + hdr->base = &mem[8]; + hdr->checksum = + (hdr->base[hdr->len] << 0) | (hdr->base[hdr->len + 1] << 8); + for (tmp = 0, mem = *pmem, end = &hdr->base[hdr->len]; mem < end; ++mem) + tmp += *mem; + if (tmp != hdr->checksum) + return EINVAL; + *pmem += 10 + hdr->len; + return 0; +} + +int softing_load_fw(const char *file, struct softing *card, + unsigned char *virt, unsigned int size, int offset) +{ + const struct firmware *fw; + const unsigned char *mem; + const unsigned char *end; + int ret; + u32 start_addr; + struct fw_hdr rec; + int ok = 0; + unsigned char buf[256]; + + ret = request_firmware(&fw, file, card->dev); + if (ret) { + mod_alert("request_firmware(%s) got %i", file, ret); + return ret; + } + mod_trace("%s, firmware(%s) got %u bytes, offset %c0x%04x" + , card->id.name, file, (unsigned int)fw->size, + (offset >= 0) ? '+' : '-', abs(offset)); + /* parse the firmware */ + mem = fw->data; + end = &mem[fw->size]; + /* look for header record */ + if (fw_parse(&mem, &rec)) + goto fw_end; + if (rec.type != 0xffff) { + mod_alert("firware starts with type 0x%04x", rec.type); + goto fw_end; + } + if (strncmp("Structured Binary Format, Softing GmbH" + , rec.base, rec.len)) { + mod_info("firware string '%.*s'", rec.len, rec.base); + goto fw_end; + } + ok |= 1; + /* ok, we had a header */ + while (mem < end) { + if (fw_parse(&mem, &rec)) + break; + if (rec.type == 3) { + /*start address */ + start_addr = rec.addr; + ok |= 2; + continue; + } else if (rec.type == 1) { + /*eof */ + ok |= 4; + goto fw_end; + } else if (rec.type != 0) { + mod_alert("unknown record type 0x%04x", rec.type); + break; + } + + if ((rec.addr + rec.len + offset) > size) { + mod_alert("firmware out of range (0x%08x / 0x%08x)" + , (rec.addr + rec.len + offset), size); + goto fw_end; + } + memcpy_toio(&virt[rec.addr + offset], + rec.base, rec.len); + /* be sure to flush caches from IO space */ + mb(); + if (rec.len > sizeof(buf)) { + mod_info("record is big (%u bytes), not verifying" + , rec.len); + continue; + } + /* verify record data */ + memcpy_fromio(buf, &virt[rec.addr + offset], rec.len); + if (!memcmp(buf, rec.base, rec.len)) + /* is ok */ + continue; + mod_alert("0x%08x:0x%03x at 0x%p failed", rec.addr, rec.len + , &virt[rec.addr + offset]); + goto fw_end; + } +fw_end: + release_firmware(fw); + if (0x5 == (ok & 0x5)) { + /*got eof & start */ + return 0; + } + mod_alert("failed"); + return EINVAL; +} + +int softing_load_app_fw(const char *file, struct softing *card) +{ + const struct firmware *fw; + const unsigned char *mem; + const unsigned char *end; + int ret; + struct fw_hdr rec; + int ok = 0; + u32 start_addr = 0; + u16 rx_sum; + unsigned int sum; + const unsigned char *mem_lp; + const unsigned char *mem_end; + struct cpy { + u32 src; + u32 dst; + u16 len; + u8 do_cs; + } __attribute__((packed)) *pcpy = + (struct cpy *)&card->dpram.command[1]; + + ret = request_firmware(&fw, file, card->dev); + if (ret) { + mod_alert("request_firmware(%s) got %i", file, ret); + return ret; + } + mod_trace("%s, firmware(%s) got %lu bytes", card->id.name, file, + (unsigned long)fw->size); + /* parse the firmware */ + mem = fw->data; + end = &mem[fw->size]; + /* look for header record */ + if (fw_parse(&mem, &rec)) + goto fw_end; + if (rec.type != 0xffff) { + mod_alert("firware starts with type 0x%04x", rec.type); + goto fw_end; + } + if (strncmp("Structured Binary Format, Softing GmbH" + , rec.base, rec.len)) { + mod_info("firware string '%.*s'", rec.len, rec.base); + goto fw_end; + } + ok |= 1; + /* ok, we had a header */ + while (mem < end) { + if (fw_parse(&mem, &rec)) + break; + + if (rec.type == 3) { + /*start address */ + start_addr = rec.addr; + ok |= 2; + continue; + } else if (rec.type == 1) { + /*eof */ + ok |= 4; + goto fw_end; + } else if (rec.type != 0) { + mod_alert("unknown record type 0x%04x", rec.type); + break; + } + /* regualar data */ + for (sum = 0, mem_lp = rec.base, mem_end = &mem_lp[rec.len]; + mem_lp < mem_end; ++mem_lp) + sum += *mem_lp; + + memcpy_toio(&card->dpram. virt[card->desc->app.offs], + rec.base, rec.len); + pcpy->src = card->desc->app.offs + card->desc->app.addr; + pcpy->dst = rec.addr; + pcpy->len = rec.len; + pcpy->do_cs = 1; + if (softing_bootloader_command(card, 1, "loading app.")) + goto fw_end; + /*verify checksum */ + rx_sum = card->dpram.receipt[1]; + if (rx_sum != (sum & 0xffff)) { + mod_alert("SRAM seems to be damaged" + ", wanted 0x%04x, got 0x%04x", sum, rx_sum); + goto fw_end; + } + } +fw_end: + release_firmware(fw); + if (ok == 7) { + /*got start, start_addr, & eof */ + struct cmd { + u32 start; + u8 autorestart; + } *pcmd = (struct cmd *)&card->dpram.command[1]; + pcmd->start = start_addr; + pcmd->autorestart = 1; + if (!softing_bootloader_command(card, 3, "start app.")) { + mod_trace("%s: card app. run at 0x%06x" + , card->id.name, start_addr); + return 0; + } + } + mod_alert("failed"); + return EINVAL; +} + +int softing_reset_chip(struct softing *card) +{ + mod_trace("%s", card->id.name); + do { + /*reset chip */ + card->dpram.info->reset_rcv_fifo = 0; + card->dpram.info->reset = 1; + if (!softing_fct_cmd(card, 0, 0, "reset_chip")) + break; + if (signal_pending(current)) + goto failed; + /*sync */ + if (softing_fct_cmd(card, 99, 0x55, "sync-a")) + goto failed; + if (softing_fct_cmd(card, 99, 0xaa, "sync-a")) + goto failed; + } while (1); + card->tx.pending = 0; + return 0; +failed: + return -EIO; +} + +int softing_reinit(struct softing *card, int bus0, int bus1) +{ + int ret; + int restarted_bus = -1; + mod_trace("%s", card->id.name); + if (!card->fw.up) + return -EIO; + if (bus0 < 0) { + bus0 = (card->bus[0]->netdev->flags & IFF_UP) ? 1 : 0; + if (bus0) + restarted_bus = 0; + } else if (bus1 < 0) { + bus1 = (card->bus[1]->netdev->flags & IFF_UP) ? 1 : 0; + if (bus1) + restarted_bus = 1; + } + /* collect info */ + if (card->bus[0]) { + card->bus[0]->can.state = CAN_STATE_STOPPED; + softing_flush_echo_skb(card->bus[0]); + } + if (card->bus[1]) { + card->bus[1]->can.state = CAN_STATE_STOPPED; + softing_flush_echo_skb(card->bus[1]); + } + + /* start acting */ + if (!bus0 && !bus1) { + softing_card_irq(card, 0); + softing_reset_chip(card); + if (card->bus[0]) + netif_carrier_off(card->bus[0]->netdev); + if (card->bus[1]) + netif_carrier_off(card->bus[1]->netdev); + return 0; + } + ret = softing_reset_chip(card); + if (ret) { + softing_card_irq(card, 0); + return ret; + } + if (bus0) { + /*init chip */ + card->dpram.fct->param[1] = card->bus[0]->can.bittiming.brp; + card->dpram.fct->param[2] = card->bus[0]->can.bittiming.sjw; + card->dpram.fct->param[3] = + card->bus[0]->can.bittiming.phase_seg1 + + card->bus[0]->can.bittiming.prop_seg; + card->dpram.fct->param[4] = + card->bus[0]->can.bittiming.phase_seg2; + card->dpram.fct->param[5] = (card->bus[0]->can.ctrlmode & + CAN_CTRLMODE_3_SAMPLES)?1:0; + if (softing_fct_cmd(card, 1, 0, "initialize_chip[0]")) + goto failed; + /*set mode */ + card->dpram.fct->param[1] = 0; + card->dpram.fct->param[2] = 0; + if (softing_fct_cmd(card, 3, 0, "set_mode[0]")) + goto failed; + /*set filter */ + card->dpram.fct->param[1] = 0x0000;/*card->bus[0].s.msg; */ + card->dpram.fct->param[2] = 0x07ff;/*card->bus[0].s.msk; */ + card->dpram.fct->param[3] = 0x0000;/*card->bus[0].l.msg; */ + card->dpram.fct->param[4] = 0xffff;/*card->bus[0].l.msk; */ + card->dpram.fct->param[5] = 0x0000;/*card->bus[0].l.msg >> 16;*/ + card->dpram.fct->param[6] = 0x1fff;/*card->bus[0].l.msk >> 16;*/ + if (softing_fct_cmd(card, 7, 0, "set_filter[0]")) + goto failed; + /*set output control */ + card->dpram.fct->param[1] = card->bus[0]->output; + if (softing_fct_cmd(card, 5, 0, "set_output[0]")) + goto failed; + } + if (bus1) { + /*init chip2 */ + card->dpram.fct->param[1] = card->bus[1]->can.bittiming.brp; + card->dpram.fct->param[2] = card->bus[1]->can.bittiming.sjw; + card->dpram.fct->param[3] = + card->bus[1]->can.bittiming.phase_seg1 + + card->bus[1]->can.bittiming.prop_seg; + card->dpram.fct->param[4] = + card->bus[1]->can.bittiming.phase_seg2; + card->dpram.fct->param[5] = (card->bus[1]->can.ctrlmode & + CAN_CTRLMODE_3_SAMPLES)?1:0; + if (softing_fct_cmd(card, 2, 0, "initialize_chip[1]")) + goto failed; + /*set mode2 */ + card->dpram.fct->param[1] = 0; + card->dpram.fct->param[2] = 0; + if (softing_fct_cmd(card, 4, 0, "set_mode[1]")) + goto failed; + /*set filter2 */ + card->dpram.fct->param[1] = 0x0000;/*card->bus[1].s.msg; */ + card->dpram.fct->param[2] = 0x07ff;/*card->bus[1].s.msk; */ + card->dpram.fct->param[3] = 0x0000;/*card->bus[1].l.msg; */ + card->dpram.fct->param[4] = 0xffff;/*card->bus[1].l.msk; */ + card->dpram.fct->param[5] = 0x0000;/*card->bus[1].l.msg >> 16;*/ + card->dpram.fct->param[6] = 0x1fff;/*card->bus[1].l.msk >> 16;*/ + if (softing_fct_cmd(card, 8, 0, "set_filter[1]")) + goto failed; + /*set output control2 */ + card->dpram.fct->param[1] = card->bus[1]->output; + if (softing_fct_cmd(card, 6, 0, "set_output[1]")) + goto failed; + } + /*set interrupt */ + /*enable_error_frame */ + if (softing_fct_cmd(card, 51, 0, "enable_error_frame")) + goto failed; + /*initialize interface */ + card->dpram.fct->param[1] = 1; + card->dpram.fct->param[2] = 1; + card->dpram.fct->param[3] = 1; + card->dpram.fct->param[4] = 1; + card->dpram.fct->param[5] = 1; + card->dpram.fct->param[6] = 1; + card->dpram.fct->param[7] = 1; + card->dpram.fct->param[8] = 1; + card->dpram.fct->param[9] = 1; + card->dpram.fct->param[10] = 1; + if (softing_fct_cmd(card, 17, 0, "initialize_interface")) + goto failed; + /*enable_fifo */ + if (softing_fct_cmd(card, 36, 0, "enable_fifo")) + goto failed; + /*enable fifo tx ack */ + if (softing_fct_cmd(card, 13, 0, "fifo_tx_ack[0]")) + goto failed; + /*enable fifo tx ack2 */ + if (softing_fct_cmd(card, 14, 0, "fifo_tx_ack[1]")) + goto failed; + /*enable timestamps */ + /*is default, no code found */ + /*start_chip */ + if (softing_fct_cmd(card, 11, 0, "start_chip")) + goto failed; + card->dpram.info->bus_state = 0; + card->dpram.info->bus_state2 = 0; + mod_info("ok for %s, %s/%s\n", card->bus[0]->netdev->name, + card->bus[1]->netdev->name, card->id.name); + if (card->desc->generation < 2) { + card->dpram.irq->to_host = 0; + /* flush the DPRAM caches */ + wmb(); + } + /*run once */ + /*the bottom halve will start flushing the tx-queue too */ + tasklet_schedule(&card->irq.bh); + + ret = softing_card_irq(card, 1); + if (ret) + goto failed; + + /*TODO: generate RESTARTED messages */ + + if (card->bus[0] && bus0) { + card->bus[0]->can.state = CAN_STATE_ACTIVE; + netif_carrier_on(card->bus[0]->netdev); + } + if (card->bus[1] && bus1) { + card->bus[1]->can.state = CAN_STATE_ACTIVE; + netif_carrier_on(card->bus[1]->netdev); + } + return 0; +failed: + softing_card_irq(card, 0); + softing_reset_chip(card); + if (card->bus[0]) + netif_carrier_off(card->bus[0]->netdev); + if (card->bus[1]) + netif_carrier_off(card->bus[1]->netdev); + return -EIO; +} + + +int softing_default_output(struct softing *card, struct softing_priv *priv) +{ + switch (priv->chip) { + case 1000: + if (card->desc->generation < 2) + return 0xfb; + return 0xfa; + case 5: + return 0x60; + default: + return 0x40; + } +} + +u32 softing_time2usec(struct softing *card, u32 raw) +{ + /*TODO : don't loose higher order bits in computation */ + switch (card->desc->freq) { + case 20: + return raw * 4 / 5; + case 24: + return raw * 2 / 3; + case 25: + return raw * 16 / 25; + case 0: + case 16: + default: + return raw; + } +} + + diff --git a/drivers/net/can/softing/softing_main.c b/drivers/net/can/softing/softing_main.c new file mode 100644 index 000000000000..955f9c85a3a4 --- /dev/null +++ b/drivers/net/can/softing/softing_main.c @@ -0,0 +1,1058 @@ +/* +* drivers/net/can/softing/softing_main.c +* +* Copyright (C) 2008 +* +* - Kurt Van Dijck, EIA Electronics +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the version 2 of the GNU General Public License +* as published by the Free Software Foundation +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/io.h> + +#include "softing.h" + +/* this is the worst thing on the softing API + * 2 busses are driven together, I don't know how + * to recover a single of them. + * Therefore, when one bus is modified, the other + * is flushed too + */ +void softing_flush_echo_skb(struct softing_priv *priv) +{ + can_close_cleanup(priv->netdev); + priv->tx.pending = 0; + priv->tx.echo_put = 0; + priv->tx.echo_get = 0; +} + +/*softing_unlocked_tx_run:*/ +/*trigger the tx queue-ing*/ +/*no locks are grabbed, so be sure to have the spin spinlock*/ +static int netdev_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct softing_priv *priv = (struct softing_priv *)netdev_priv(dev); + struct softing *card = priv->card; + int ret; + int bhlock; + u8 *ptr; + u8 cmd; + unsigned int fifo_wr; + struct can_frame msg; + + ret = -ENOTTY; + if (in_interrupt()) { + bhlock = 0; + spin_lock(&card->spin); + } else { + bhlock = 1; + spin_lock_bh(&card->spin); + } + if (!card->fw.up) { + ret = -EIO; + goto xmit_done; + } + if (netif_carrier_ok(priv->netdev) <= 0) { + ret = -EBADF; + goto xmit_done; + } + if (card->tx.pending >= TXMAX) { + ret = -EBUSY; + goto xmit_done; + } + if (priv->tx.pending >= CAN_ECHO_SKB_MAX) { + ret = -EBUSY; + goto xmit_done; + } + fifo_wr = card->dpram.tx->wr; + if (fifo_wr == card->dpram.tx->rd) { + /*fifo full */ + ret = -EAGAIN; + goto xmit_done; + } + memcpy(&msg, skb->data, sizeof(msg)); + ptr = &card->dpram.tx->fifo[fifo_wr][0]; + cmd = CMD_TX; + if (msg.can_id & CAN_RTR_FLAG) + cmd |= CMD_RTR; + if (msg.can_id & CAN_EFF_FLAG) + cmd |= CMD_XTD; + if (priv->index) + cmd |= CMD_BUS2; + *ptr++ = cmd; + *ptr++ = msg.can_dlc; + *ptr++ = (msg.can_id >> 0); + *ptr++ = (msg.can_id >> 8); + if (msg.can_id & CAN_EFF_FLAG) { + *ptr++ = (msg.can_id >> 16); + *ptr++ = (msg.can_id >> 24); + } else { + /*increment 1, not 2 as you might think */ + ptr += 1; + } + if (!(msg.can_id & CAN_RTR_FLAG)) + memcpy_toio(ptr, &msg.data[0], msg.can_dlc); + if (++fifo_wr >= + sizeof(card->dpram.tx->fifo) / + sizeof(card->dpram.tx->fifo[0])) + fifo_wr = 0; + card->dpram.tx->wr = fifo_wr; + ret = 0; + ++card->tx.pending; + ++priv->tx.pending; + can_put_echo_skb(skb, dev, priv->tx.echo_put); + ++priv->tx.echo_put; + if (priv->tx.echo_put >= CAN_ECHO_SKB_MAX) + priv->tx.echo_put = 0; + /* clear pointer, so don't erase later */ + skb = 0; +xmit_done: + if (bhlock) + spin_unlock_bh(&card->spin); + else + spin_unlock(&card->spin); + if (card->tx.pending >= TXMAX) { + struct softing_priv *bus; + int j; + for (j = 0; j < card->nbus; ++j) { + bus = card->bus[j]; + if (!bus) + continue; + netif_stop_queue(bus->netdev); + } + } + + /* free skb, if not handled by the driver */ + if (skb) + dev_kfree_skb(skb); + return ret; +} + +static int softing_dev_svc_once(struct softing *card) +{ + int j; + struct softing_priv *bus; + struct sk_buff *skb; + struct can_frame msg; + + unsigned int fifo_rd; + unsigned int cnt = 0; + struct net_device_stats *stats; + + memset(&msg, 0, sizeof(msg)); + if (card->dpram.rx->lost_msg) { + /*reset condition */ + card->dpram.rx->lost_msg = 0; + /* prepare msg */ + msg.can_id = CAN_ERR_FLAG | CAN_ERR_CRTL; + msg.can_dlc = CAN_ERR_DLC; + msg.data[1] = CAN_ERR_CRTL_RX_OVERFLOW; + /*service to both busses, we don't know which one generated */ + for (j = 0; j < card->nbus; ++j) { + bus = card->bus[j]; + if (!bus) + continue; + if (!netif_carrier_ok(bus->netdev)) + continue; + ++bus->can.can_stats.data_overrun; + skb = dev_alloc_skb(sizeof(msg)); + if (!skb) + return -ENOMEM; + skb->dev = bus->netdev; + skb->protocol = htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + memcpy(skb_put(skb, sizeof(msg)), &msg, sizeof(msg)); + if (netif_rx(skb)) + dev_kfree_skb_irq(skb); + } + memset(&msg, 0, sizeof(msg)); + ++cnt; + } + + fifo_rd = card->dpram.rx->rd; + if (++fifo_rd >= + sizeof(card->dpram.rx->fifo) / sizeof(card->dpram.rx->fifo[0])) + fifo_rd = 0; + if (card->dpram.rx->wr != fifo_rd) { + u8 *ptr; + u32 tmp; + u8 cmd; + int do_skb; + + ptr = &card->dpram.rx->fifo[fifo_rd][0]; + + cmd = *ptr++; + if (cmd == 0xff) { + /*not quite usefull, probably the card has got out */ + mod_alert("got cmd 0x%02x, I suspect the card is lost" + , cmd); + } + /*mod_trace("0x%02x", cmd);*/ + bus = card->bus[0]; + if (cmd & CMD_BUS2) + bus = card->bus[1]; + + stats = &bus->netdev->stats; + if (cmd & CMD_ERR) { + u8 can_state; + u8 state; + state = *ptr++; + + msg.can_id = CAN_ERR_FLAG; + msg.can_dlc = CAN_ERR_DLC; + + if (state & 0x80) { + can_state = CAN_STATE_BUS_OFF; + msg.can_id |= CAN_ERR_BUSOFF; + state = 2; + } else if (state & 0x60) { + can_state = CAN_STATE_BUS_PASSIVE; + msg.can_id |= CAN_ERR_BUSERROR; + state = 1; + } else { + can_state = CAN_STATE_ACTIVE; + state = 0; + do_skb = 0; + } + /*update DPRAM */ + if (!bus->index) + card->dpram.info->bus_state = state; + else + card->dpram.info->bus_state2 = state; + /*timestamp */ + tmp = (ptr[0] << 0) + |(ptr[1] << 8) + |(ptr[2] << 16) + |(ptr[3] << 24); + ptr += 4; + /*msg.time = */ softing_time2usec(card, tmp); + /*trigger dual port RAM */ + mb(); + card->dpram.rx->rd = fifo_rd; + /*update internal status */ + if (can_state != bus->can.state) { + bus->can.state = can_state; + if (can_state == 1) + bus->can.can_stats.error_passive += 1; + } + bus->can.can_stats.bus_error += 1; + + /*trigger socketcan */ + if (state == 2) { + /* this calls can_close_cleanup() */ + softing_flush_echo_skb(bus); + can_bus_off(bus->netdev); + netif_stop_queue(bus->netdev); + } + if ((state == CAN_STATE_BUS_OFF) + || (state == CAN_STATE_BUS_PASSIVE)) { + skb = dev_alloc_skb(sizeof(msg)); + if (!skb) + return -ENOMEM; + skb->dev = bus->netdev; + skb->protocol = htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + memcpy(skb_put(skb, sizeof(msg)), &msg, + sizeof(msg)); + if (netif_rx(skb)) + dev_kfree_skb_irq(skb); + } + } else { + if (cmd & CMD_RTR) + msg.can_id |= CAN_RTR_FLAG; + /* acknowledge, was tx msg + * no real tx flag to set + if (cmd & CMD_ACK) { + } + */ + msg.can_dlc = *ptr++; + if (msg.can_dlc > 8) + msg.can_dlc = 8; + if (cmd & CMD_XTD) { + msg.can_id |= CAN_EFF_FLAG; + msg.can_id |= + (ptr[0] << 0) + | (ptr[1] << 8) + | (ptr[2] << 16) + | (ptr[3] << 24); + ptr += 4; + } else { + msg.can_id |= (ptr[0] << 0) | (ptr[1] << 8); + ptr += 2; + } + tmp = (ptr[0] << 0) + | (ptr[1] << 8) + | (ptr[2] << 16) + | (ptr[3] << 24); + ptr += 4; + /*msg.time = */ softing_time2usec(card, tmp); + memcpy_fromio(&msg.data[0], ptr, 8); + ptr += 8; + /*trigger dual port RAM */ + mb(); + card->dpram.rx->rd = fifo_rd; + /*update socket */ + if (cmd & CMD_ACK) { + can_get_echo_skb(bus->netdev, bus->tx.echo_get); + ++bus->tx.echo_get; + if (bus->tx.echo_get >= CAN_ECHO_SKB_MAX) + bus->tx.echo_get = 0; + if (bus->tx.pending) + --bus->tx.pending; + if (card->tx.pending) + --card->tx.pending; + stats->tx_packets += 1; + stats->tx_bytes += msg.can_dlc; + } else { + stats->rx_packets += 1; + stats->rx_bytes += msg.can_dlc; + bus->netdev->last_rx = jiffies; + skb = dev_alloc_skb(sizeof(msg)); + if (skb) { + skb->dev = bus->netdev; + skb->protocol = htons(ETH_P_CAN); + skb->ip_summed = CHECKSUM_UNNECESSARY; + memcpy(skb_put(skb, sizeof(msg)), &msg, + sizeof(msg)); + if (netif_rx(skb)) + dev_kfree_skb_irq(skb); + } + } + } + ++cnt; + } + return cnt; +} + +static void softing_dev_svc(unsigned long param) +{ + struct softing *card = (struct softing *)param; + struct softing_priv *bus; + int j; + int offset; + + spin_lock(&card->spin); + while (softing_dev_svc_once(card) > 0) + ++card->irq.svc_count; + /*resume tx queue's */ + offset = card->tx.last_bus; + for (j = 0; j < card->nbus; ++j) { + if (card->tx.pending >= TXMAX) + break; + bus = card->bus[(j + offset) % card->nbus]; + if (netif_carrier_ok(bus->netdev)) + netif_wake_queue(bus->netdev); + } + spin_unlock(&card->spin); +} + +static void card_seems_down(struct softing *card) +{ + /* free interrupt, but probably + * in wrong (interrupt) context + if (card->irq.requested) { + free_irq(card->irq.nr, card); + card->irq.requested = 0; + card->fw.up = 0; + } + */ + mod_alert("I think the card is vanished"); +} + +static +irqreturn_t dev_interrupt_shared(int irq, void *dev_id) +{ + struct softing *card = (struct softing *)dev_id; + unsigned char ir; + ir = card->dpram.virt[0xe02]; + card->dpram.virt[0xe02] = 0; + if (card->dpram.rx->rd == 0xffff) { + card_seems_down(card); + return IRQ_NONE; + } + if (ir == 1) { + tasklet_schedule(&card->irq.bh); + return IRQ_HANDLED; + } else if (ir == 0x10) { + return IRQ_NONE; + } else { + return IRQ_NONE; + } +} + +static +irqreturn_t dev_interrupt_nshared(int irq, void *dev_id) +{ + struct softing *card = (struct softing *)dev_id; + unsigned char irq_host; + irq_host = card->dpram.irq->to_host; + /* make sure we have a copy, before clearing the variable in DPRAM */ + rmb(); + card->dpram.irq->to_host = 0; + /* make sure we cleared it */ + wmb(); + mod_trace("0x%02x", irq_host); + if (card->dpram.rx->rd == 0xffff) { + card_seems_down(card); + return IRQ_NONE; + } + tasklet_schedule(&card->irq.bh); + return IRQ_HANDLED; +} + +static int netdev_open(struct net_device *ndev) +{ + struct softing_priv *priv = netdev_priv(ndev); + struct softing *card = priv->card; + int fw; + int ret; + + mod_trace("%s", ndev->name); + /* determine and set bittime */ + ret = can_set_bittiming(ndev); + if (ret) + return ret; + if (mutex_lock_interruptible(&card->fw.lock)) + return -ERESTARTSYS; + fw = card->fw.up; + if (fw) + softing_reinit(card + , (card->bus[0] == priv) ? 1 : -1 + , (card->bus[1] == priv) ? 1 : -1); + mutex_unlock(&card->fw.lock); + if (!fw) + return -EIO; + netif_start_queue(ndev); + return 0; +} + +static int netdev_stop(struct net_device *ndev) +{ + struct softing_priv *priv = netdev_priv(ndev); + struct softing *card = priv->card; + int fw; + + mod_trace("%s", ndev->name); + netif_stop_queue(ndev); + netif_carrier_off(ndev); + softing_flush_echo_skb(priv); + can_close_cleanup(ndev); + if (mutex_lock_interruptible(&card->fw.lock)) + return -ERESTARTSYS; + fw = card->fw.up; + if (fw) + softing_reinit(card + , (card->bus[0] == priv) ? 0 : -1 + , (card->bus[1] == priv) ? 0 : -1); + mutex_unlock(&card->fw.lock); + if (!fw) + return -EIO; + return 0; +} + +static int candev_get_state(struct net_device *ndev, enum can_state *state) +{ + struct softing_priv *priv = netdev_priv(ndev); + mod_trace("%s", ndev->name); + if (priv->netdev->flags & IFF_UP) + *state = CAN_STATE_STOPPED; + else if (priv->can.state == CAN_STATE_STOPPED) + *state = CAN_STATE_STOPPED; + else + *state = CAN_STATE_ACTIVE; + return 0; +} + +static int candev_set_mode(struct net_device *ndev, enum can_mode mode) +{ + struct softing_priv *priv = netdev_priv(ndev); + struct softing *card = priv->card; + mod_trace("%s %u", ndev->name, mode); + switch (mode) { + case CAN_MODE_START: + /*recovery from busoff? */ + if (mutex_lock_interruptible(&card->fw.lock)) + return -ERESTARTSYS; + softing_reinit(card, -1, -1); + mutex_unlock(&card->fw.lock); + break; + case CAN_MODE_STOP: + case CAN_MODE_SLEEP: + return -EOPNOTSUPP; + } + return 0; +} + +/*assume the card->lock is held*/ + +int softing_card_irq(struct softing *card, int enable) +{ + int ret; + if (!enable) { + if (card->irq.requested && card->irq.nr) { + free_irq(card->irq.nr, card); + card->irq.requested = 0; + } + return 0; + } + if (!card->irq.requested && (card->irq.nr)) { + irqreturn_t(*fn) (int, void *); + unsigned int flags; + flags = IRQF_DISABLED | IRQF_SHARED;/*| IRQF_TRIGGER_LOW; */ + fn = dev_interrupt_nshared; + if (card->desc->generation >= 2) + fn = dev_interrupt_shared; + ret = request_irq(card->irq.nr, fn, flags, card->id.name, card); + if (ret) { + mod_alert("%s, request_irq(%u) failed" + , card->id.name, card->irq.nr + ); + return ret; + } + card->irq.requested = 1; + } + return 0; +} + +static void shutdown_card(struct softing *card) +{ + int fw_up = 0; + mod_trace("%s", card->id.name); + if (mutex_lock_interruptible(&card->fw.lock)) + /* return -ERESTARTSYS*/; + fw_up = card->fw.up; + card->fw.up = 0; + + if (card->irq.requested && card->irq.nr) { + free_irq(card->irq.nr, card); + card->irq.requested = 0; + } + if (fw_up) { + if (card->fn.enable_irq) + card->fn.enable_irq(card, 0); + if (card->fn.reset) + card->fn.reset(card, 1); + } + mutex_unlock(&card->fw.lock); + tasklet_kill(&card->irq.bh); +} + +static int boot_card(struct softing *card) +{ + mod_trace("%s", card->id.name); + if (mutex_lock_interruptible(&card->fw.lock)) + return -ERESTARTSYS; + if (card->fw.up) { + mutex_unlock(&card->fw.lock); + return 0; + } + /*reset board */ + + if (card->fn.enable_irq) + card->fn.enable_irq(card, 1); + /*boot card */ + if (card->fn.reset) + card->fn.reset(card, 1); + /*test dp ram */ + if (card->dpram.virt) { + unsigned char *lp; + static const unsigned char stream[] + = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, }; + unsigned char back[sizeof(stream)]; + for (lp = card->dpram.virt; + &lp[sizeof(stream)] <= card->dpram.end; + lp += sizeof(stream)) { + memcpy_toio(lp, stream, sizeof(stream)); + /* flush IO cache */ + mb(); + memcpy_fromio(back, lp, sizeof(stream)); + + if (memcmp(back, stream, sizeof(stream))) { + char line[3 * sizeof(stream) + / sizeof(stream[0]) + 1]; + char *pline = line; + unsigned char *addr = lp; + for (lp = back; lp < &back[sizeof(stream) + / sizeof(stream[0])]; ++lp) + pline += sprintf(pline, " %02x", *lp); + + mod_alert("write to dpram failed at 0x%p, %s" + , addr, line); + goto open_failed; + } + } + /*fill dpram with 0x55 */ + /*for (lp = card->dpram.virt; lp <= card->dpram.end; ++lp) { + *lp = 0x55; + }*/ + wmb(); + } else { + goto open_failed; + } + /*load boot firmware */ + if (softing_load_fw(card->desc->boot.fw, card, card->dpram.virt, + card->dpram.size, + card->desc->boot.offs - + card->desc->boot.addr)) + goto open_failed; + /*load load firmware */ + if (softing_load_fw(card->desc->load.fw, card, card->dpram.virt, + card->dpram.size, + card->desc->load.offs - + card->desc->load.addr)) + goto open_failed; + + if (card->fn.reset) + card->fn.reset(card, 0); + if (softing_bootloader_command(card, 0, "card boot")) + goto open_failed; + if (softing_load_app_fw(card->desc->app.fw, card)) + goto open_failed; + /*reset chip */ + card->dpram.info->reset_rcv_fifo = 0; + card->dpram.info->reset = 1; + /*sync */ + if (softing_fct_cmd(card, 99, 0x55, "sync-a")) + goto open_failed; + if (softing_fct_cmd(card, 99, 0xaa, "sync-a")) + goto open_failed; + /*reset chip */ + if (softing_fct_cmd(card, 0, 0, "reset_chip")) + goto open_failed; + /*get_serial */ + if (softing_fct_cmd(card, 43, 0, "get_serial_number")) + goto open_failed; + card->id.serial = + (u16) card->dpram.fct->param[1] + + (((u16) card->dpram.fct->param[2]) << 16); + /*get_version */ + if (softing_fct_cmd(card, 12, 0, "get_version")) + goto open_failed; + card->id.fw = (u16) card->dpram.fct->param[1]; + card->id.hw = (u16) card->dpram.fct->param[2]; + card->id.lic = (u16) card->dpram.fct->param[3]; + card->id.chip[0] = (u16) card->dpram.fct->param[4]; + card->id.chip[1] = (u16) card->dpram.fct->param[5]; + + mod_info("%s, card booted, " + "serial %u, fw %u, hw %u, lic %u, chip (%u,%u)", + card->id.name, card->id.serial, card->id.fw, card->id.hw, + card->id.lic, card->id.chip[0], card->id.chip[1]); + + card->fw.up = 1; + mutex_unlock(&card->fw.lock); + return 0; +open_failed: + card->fw.up = 0; + if (card->fn.enable_irq) + card->fn.enable_irq(card, 0); + if (card->fn.reset) + card->fn.reset(card, 1); + mutex_unlock(&card->fw.lock); + return EINVAL; +} + +/*sysfs stuff*/ + +/* Because the struct softing may be used by pcmcia devices + * as well as pci devices, * we have no clue how to get + * from a struct device * towards the struct softing *. + * It may go over a pci_device->priv or over a pcmcia_device->priv. + * Therefore, provide the struct softing pointer within the attribute. + * Then we don't need driver/bus specific things in these attributes + */ +struct softing_attribute { + struct device_attribute dev; + ssize_t (*show) (struct softing *card, char *buf); + ssize_t (*store)(struct softing *card, const char *buf, size_t count); + struct softing *card; +}; + +static ssize_t rd_card_attr(struct device *dev, struct device_attribute *attr + , char *buf) { + struct softing_attribute *cattr + = container_of(attr, struct softing_attribute, dev); + return cattr->show ? cattr->show(cattr->card, buf) : 0; +} +static ssize_t wr_card_attr(struct device *dev, struct device_attribute *attr + , const char *buf, size_t count) { + struct softing_attribute *cattr + = container_of(attr, struct softing_attribute, dev); + return cattr->store ? cattr->store(cattr->card, buf, count) : 0; +} + +#define declare_attr(_name, _mode, _show, _store) { \ + .dev = { \ + .attr = { \ + .name = __stringify(_name), \ + .mode = _mode, \ + }, \ + .show = rd_card_attr, \ + .store = wr_card_attr, \ + }, \ + .show = _show, \ + .store = _store, \ +} + +#define CARD_SHOW(name, member) \ +static ssize_t show_##name(struct softing *card, char *buf) { \ + return sprintf(buf, "%u\n", card->member); \ +} +CARD_SHOW(serial , id.serial); +CARD_SHOW(firmware , id.fw); +CARD_SHOW(hardware , id.hw); +CARD_SHOW(license , id.lic); +CARD_SHOW(freq , id.freq); +CARD_SHOW(txpending , tx.pending); + +static const struct softing_attribute card_attr_proto [] = { + declare_attr(serial , 0444, show_serial , 0), + declare_attr(firmware , 0444, show_firmware , 0), + declare_attr(hardware , 0444, show_hardware , 0), + declare_attr(license , 0444, show_license , 0), + declare_attr(freq , 0444, show_freq , 0), + declare_attr(txpending , 0644, show_txpending , 0), +}; + +static int mk_card_sysfs(struct softing *card) +{ + int size; + int j; + + size = sizeof(card_attr_proto)/sizeof(card_attr_proto[0]); + card->attr = kmalloc((size+1)*sizeof(card->attr[0]), GFP_KERNEL); + if (!card->attr) + goto attr_mem_failed; + memcpy(card->attr, card_attr_proto, size * sizeof(card->attr[0])); + memset(&card->attr[size], 0, sizeof(card->attr[0])); + + card->grp = kmalloc((size+1)*sizeof(card->grp [0]), GFP_KERNEL); + if (!card->grp) + goto grp_mem_failed; + + for (j = 0; j < size; ++j) { + card->attr[j].card = card; + card->grp[j] = &card->attr[j].dev.attr; + if (!card->attr[j].show) + card->attr[j].dev.attr.mode &= ~(S_IRUGO); + if (!card->attr[j].store) + card->attr[j].dev.attr.mode &= ~(S_IWUGO); + } + card->grp[size] = 0; + card->sysfs.name = "softing"; + card->sysfs.attrs = card->grp; + if (sysfs_create_group(&card->dev->kobj, &card->sysfs) < 0) + goto sysfs_failed; + + return 0; + +sysfs_failed: + kfree(card->grp); +grp_mem_failed: + kfree(card->attr); +attr_mem_failed: + return -1; +} +static void rm_card_sysfs(struct softing *card) +{ + sysfs_remove_group(&card->dev->kobj, &card->sysfs); + kfree(card->grp); + kfree(card->attr); +} + +static ssize_t show_chip(struct device *dev + , struct device_attribute *attr, char *buf) +{ + struct net_device *ndev = to_net_dev(dev); + struct softing_priv *priv = netdev2softing(ndev); + return sprintf(buf, "%i\n", priv->chip); +} + +static ssize_t show_output(struct device *dev + , struct device_attribute *attr, char *buf) +{ + struct net_device *ndev = to_net_dev(dev); + struct softing_priv *priv = netdev2softing(ndev); + return sprintf(buf, "0x%02x\n", priv->output); +} + +static ssize_t store_output(struct device *dev + , struct device_attribute *attr + , const char *buf, size_t count) +{ + struct net_device *ndev = to_net_dev(dev); + struct softing_priv *priv = netdev2softing(ndev); + struct softing *card = priv->card; + + u8 v = simple_strtoul(buf, NULL, 10) & 0xFFU; + + if (mutex_lock_interruptible(&card->fw.lock)) + return -ERESTARTSYS; + if (ndev->flags & IFF_UP) { + int j; + /* we will need a restart */ + for (j = 0; j < card->nbus; ++j) { + if (j == priv->index) + /* me, myself & I */ + continue; + if (card->bus[j]->netdev->flags & IFF_UP) { + mutex_unlock(&card->fw.lock); + return -EBUSY; + } + } + priv->output = v; + softing_reinit(card, -1, -1); + } else { + priv->output = v; + } + mutex_unlock(&card->fw.lock); + return count; +} +/* TODO + * the latest softing cards support sleep mode too + */ + +static const DEVICE_ATTR(chip, S_IRUGO, show_chip, 0); +static const DEVICE_ATTR(output, S_IRUGO | S_IWUSR, show_output, store_output); + +static const struct attribute *const netdev_sysfs_entries [] = { + &dev_attr_chip .attr, + &dev_attr_output .attr, + 0, +}; +static const struct attribute_group netdev_sysfs = { + .name = 0, + .attrs = (struct attribute **)netdev_sysfs_entries, +}; + +static int mk_netdev_sysfs(struct softing_priv *priv) +{ + if (!priv->netdev->dev.kobj.sd) { + mod_alert("sysfs_create_group would fail"); + return ENODEV; + } + return sysfs_create_group(&priv->netdev->dev.kobj, &netdev_sysfs); +} +static void rm_netdev_sysfs(struct softing_priv *priv) +{ + sysfs_remove_group(&priv->netdev->dev.kobj, &netdev_sysfs); +} + +static struct softing_priv *mk_netdev(struct softing *card, u16 chip_id) +{ + struct net_device *ndev; + struct softing_priv *priv; + + ndev = alloc_candev(sizeof(*priv)); + if (!ndev) { + mod_alert("alloc_candev failed"); + return 0; + } + priv = netdev_priv(ndev); + priv->netdev = ndev; + priv->card = card; + memcpy(&priv->btr_const, &softing_btr_const, sizeof(priv->btr_const)); + priv->btr_const.brp_max = card->desc->max_brp; + priv->btr_const.sjw_max = card->desc->max_sjw; + priv->can.bittiming_const = &priv->btr_const; + priv->can.bittiming.clock = 8000000; + priv->chip = chip_id; + priv->output = softing_default_output(card, priv); + SET_NETDEV_DEV(ndev, card->dev); + + ndev->flags |= IFF_ECHO; + ndev->open = netdev_open; + ndev->stop = netdev_stop; + ndev->hard_start_xmit = netdev_start_xmit; + priv->can.do_get_state = candev_get_state; + priv->can.do_set_mode = candev_set_mode; + + return priv; +} + +static void rm_netdev(struct softing_priv *priv) +{ + free_candev(priv->netdev); +} + +static int reg_netdev(struct softing_priv *priv) +{ + int ret; + netif_carrier_off(priv->netdev); + ret = register_candev(priv->netdev); + if (ret) { + mod_alert("%s, register failed", priv->card->id.name); + goto reg_failed; + } + ret = mk_netdev_sysfs(priv); + if (ret) { + mod_alert("%s, sysfs failed", priv->card->id.name); + goto sysfs_failed; + } + return 0; +sysfs_failed: + unregister_candev(priv->netdev); +reg_failed: + return EINVAL; +} + +static void unreg_netdev(struct softing_priv *priv) +{ + rm_netdev_sysfs(priv); + unregister_candev(priv->netdev); +} + +void rm_softing(struct softing *card) +{ + int j; + + /*first, disable card*/ + shutdown_card(card); + + for (j = 0; j < card->nbus; ++j) { + unreg_netdev(card->bus[j]); + rm_netdev(card->bus[j]); + } + + rm_card_sysfs(card); + + iounmap(card->dpram.virt); +} +EXPORT_SYMBOL(rm_softing); + +int mk_softing(struct softing *card) +{ + int j; + + /* try_module_get(THIS_MODULE); */ + mutex_init(&card->fw.lock); + spin_lock_init(&card->spin); + tasklet_init(&card->irq.bh, softing_dev_svc, (unsigned long)card); + + card->desc = softing_lookup_desc(card->id.manf, card->id.prod); + if (!card->desc) { + mod_alert("0x%04x:0x%04x not supported\n", card->id.manf, + card->id.prod); + goto lookup_failed; + } + card->id.name = card->desc->name; + mod_trace("can (%s)", card->id.name); + + card->dpram.virt = ioremap(card->dpram.phys, card->dpram.size); + if (!card->dpram.virt) { + mod_alert("dpram ioremap failed\n"); + goto ioremap_failed; + } + + card->dpram.size = card->desc->dpram_size; + card->dpram.end = &card->dpram.virt[card->dpram.size]; + /*initialize_board */ + card->dpram.rx = (struct softing_rx *)&card->dpram.virt[0x0000]; + card->dpram.tx = (struct softing_tx *)&card->dpram.virt[0x0400]; + card->dpram.fct = (struct softing_fct *)&card->dpram.virt[0x0300]; + card->dpram.info = (struct softing_info *)&card->dpram.virt[0x0330]; + card->dpram.command = (unsigned short *)&card->dpram.virt[0x07e0]; + card->dpram.receipt = (unsigned short *)&card->dpram.virt[0x07f0]; + card->dpram.irq = (struct softing_irq *)&card->dpram.virt[0x07fe]; + + /*reset card */ + if (card->fn.reset) + card->fn.reset(card, 1); + if (boot_card(card)) { + mod_alert("%s, failed to boot", card->id.name); + goto boot_failed; + } + + /*only now, the chip's are known */ + card->id.freq = card->desc->freq * 1000000UL; + + if (mk_card_sysfs(card)) { + mod_alert("%s, sysfs failed", card->id.name); + goto sysfs_failed; + } + + if (card->nbus > (sizeof(card->bus) / sizeof(card->bus[0]))) { + card->nbus = sizeof(card->bus) / sizeof(card->bus[0]); + mod_alert("%s, going for %u busses", card->id.name, card->nbus); + } + + for (j = 0; j < card->nbus; ++j) { + card->bus[j] = mk_netdev(card, card->id.chip[j]); + if (!card->bus[j]) { + mod_alert("%s: failed to make can[%i]", card->id.name, + j); + goto netdev_failed; + } + card->bus[j]->index = j; + } + for (j = 0; j < card->nbus; ++j) { + if (reg_netdev(card->bus[j])) { + mod_alert("%s: failed to register can[%i]", + card->id.name, j); + goto reg_failed; + } + } + mod_trace("card initialised"); + return 0; + +reg_failed: + for (j = 0; j < card->nbus; ++j) + unreg_netdev(card->bus[j]); +netdev_failed: + for (j = 0; j < card->nbus; ++j) { + if (card->bus[j]) + rm_netdev(card->bus[j]); + } + rm_card_sysfs(card); +sysfs_failed: + shutdown_card(card); +boot_failed: + iounmap(card->dpram.virt); + card->dpram.virt = 0; + card->dpram.end = 0; +ioremap_failed: +lookup_failed: + tasklet_kill(&card->irq.bh); + return EINVAL; +} +EXPORT_SYMBOL(mk_softing); + +static int __init mod_start(void) +{ + mod_trace(""); + return 0; +} + +static void __exit mod_stop(void) +{ + mod_trace(""); +} + +module_init(mod_start); +module_exit(mod_stop); + +MODULE_DESCRIPTION("socketcan softing driver"); +MODULE_AUTHOR("Kurt Van Dijck <kurt.van.dijck@eia.be>"); +MODULE_LICENSE("GPL"); + +int softing_debug = 1; +EXPORT_SYMBOL(softing_debug); +module_param(softing_debug, int , S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(softing_debug, "trace softing functions"); diff --git a/drivers/net/can/sysfs.c b/drivers/net/can/sysfs.c new file mode 100644 index 000000000000..746f6410cbeb --- /dev/null +++ b/drivers/net/can/sysfs.c @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2007-2008 Wolfgang Grandegger <wg@grandegger.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/capability.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <net/sock.h> +#include <linux/rtnetlink.h> + +#include <linux/can.h> +#include <linux/can/dev.h> + +#include "sysfs.h" + +#ifdef CONFIG_SYSFS + +/* + * SYSFS access functions and attributes. Use same locking as + * net/core/net-sysfs.c does. + */ +static inline int dev_isalive(const struct net_device *dev) +{ + return dev->reg_state <= NETREG_REGISTERED; +} + +/* use same locking rules as GIF* ioctl's */ +static ssize_t can_dev_show(struct device *d, + struct device_attribute *attr, char *buf, + ssize_t (*fmt)(struct net_device *, char *)) +{ + struct net_device *dev = to_net_dev(d); + ssize_t ret = -EINVAL; + + read_lock(&dev_base_lock); + if (dev_isalive(dev)) + ret = (*fmt)(dev, buf); + read_unlock(&dev_base_lock); + + return ret; +} + +/* generate a show function for simple field */ +#define CAN_DEV_SHOW(field, fmt_string) \ +static ssize_t fmt_can_##field(struct net_device *dev, char *buf) \ +{ \ + struct can_priv *priv = netdev_priv(dev); \ + return sprintf(buf, fmt_string, priv->field); \ +} \ +static ssize_t show_can_##field(struct device *d, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return can_dev_show(d, attr, buf, fmt_can_##field); \ +} + +/* use same locking and permission rules as SIF* ioctl's */ +static ssize_t can_dev_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t len, + int (*set)(struct net_device *, unsigned long)) +{ + struct net_device *dev = to_net_dev(d); + unsigned long new; + int ret = -EINVAL; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + ret = strict_strtoul(buf, 0, &new); + if (ret) + goto out; + + rtnl_lock(); + if (dev_isalive(dev)) { + ret = (*set)(dev, new); + if (!ret) + ret = len; + } + rtnl_unlock(); +out: + return ret; +} + +#define CAN_CREATE_FILE(_dev, _name) \ + if (device_create_file(&_dev->dev, &dev_attr_##_name)) \ + dev_err(ND2D(_dev), \ + "Couldn't create device file for ##_name\n") + +#define CAN_REMOVE_FILE(_dev, _name) \ + device_remove_file(&_dev->dev, &dev_attr_##_name) \ + +CAN_DEV_SHOW(ctrlmode, "0x%x\n"); + +static int change_can_ctrlmode(struct net_device *dev, unsigned long ctrlmode) +{ + struct can_priv *priv = netdev_priv(dev); + int err = 0; + + if (priv->state != CAN_STATE_STOPPED) + return -EBUSY; + + if (priv->do_set_ctrlmode) + err = priv->do_set_ctrlmode(dev, ctrlmode); + + if (!err) + priv->ctrlmode = ctrlmode; + + return err; +} + +static ssize_t store_can_ctrlmode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return can_dev_store(dev, attr, buf, len, change_can_ctrlmode); +} + +static DEVICE_ATTR(can_ctrlmode, S_IRUGO | S_IWUSR, + show_can_ctrlmode, store_can_ctrlmode); + +static const char *can_state_names[] = { + "active", "bus-warn", "bus-pass" , "bus-off", + "stopped", "sleeping", "unkown" +}; + +static ssize_t printf_can_state(struct net_device *dev, char *buf) +{ + struct can_priv *priv = netdev_priv(dev); + enum can_state state; + int err = 0; + + if (priv->do_get_state) { + err = priv->do_get_state(dev, &state); + if (err) + goto out; + priv->state = state; + } else + state = priv->state; + + if (state >= ARRAY_SIZE(can_state_names)) + state = ARRAY_SIZE(can_state_names) - 1; + err = sprintf(buf, "%s\n", can_state_names[state]); +out: + return err; +} + +static ssize_t show_can_state(struct device *d, + struct device_attribute *attr, char *buf) +{ + return can_dev_show(d, attr, buf, printf_can_state); +} + +static DEVICE_ATTR(can_state, S_IRUGO, show_can_state, NULL); + +CAN_DEV_SHOW(restart_ms, "%d\n"); + +static int change_can_restart_ms(struct net_device *dev, unsigned long ms) +{ + struct can_priv *priv = netdev_priv(dev); + + if (priv->restart_ms < 0) + return -EOPNOTSUPP; + priv->restart_ms = ms; + return 0; +} + +static ssize_t store_can_restart_ms(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return can_dev_store(dev, attr, buf, len, change_can_restart_ms); +} + +static DEVICE_ATTR(can_restart_ms, S_IRUGO | S_IWUSR, + show_can_restart_ms, store_can_restart_ms); + +static ssize_t printf_can_echo(struct net_device *dev, char *buf) +{ + return sprintf(buf, "%d\n", dev->flags & IFF_ECHO ? 1 : 0); +} + +static ssize_t show_can_echo(struct device *d, + struct device_attribute *attr, char *buf) +{ + return can_dev_show(d, attr, buf, printf_can_echo); +} + +static int change_can_echo(struct net_device *dev, unsigned long on) +{ + if (on) + dev->flags |= IFF_ECHO; + else + dev->flags &= ~IFF_ECHO; + return 0; +} + +static ssize_t store_can_echo(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return can_dev_store(dev, attr, buf, len, change_can_echo); +} + +static DEVICE_ATTR(can_echo, S_IRUGO | S_IWUSR, show_can_echo, store_can_echo); + +static int change_can_restart(struct net_device *dev, unsigned long on) +{ + return can_restart_now(dev); +} + +static ssize_t store_can_restart(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return can_dev_store(dev, attr, buf, len, change_can_restart); +} + +static DEVICE_ATTR(can_restart, S_IWUSR, NULL, store_can_restart); + +/* Show a given attribute if the CAN bittiming group */ +static ssize_t can_btc_show(const struct device *d, + struct device_attribute *attr, char *buf, + unsigned long offset) +{ + struct net_device *dev = to_net_dev(d); + struct can_priv *priv = netdev_priv(dev); + struct can_bittiming_const *btc = priv->bittiming_const; + ssize_t ret = -EINVAL; + + WARN_ON(offset >= sizeof(struct can_bittiming_const) || + offset % sizeof(u32) != 0); + + read_lock(&dev_base_lock); + if (dev_isalive(dev) && btc) + ret = sprintf(buf, "%d\n", + *(u32 *)(((u8 *)btc) + offset)); + + read_unlock(&dev_base_lock); + return ret; +} + +/* Generate a read-only bittiming const attribute */ +#define CAN_BT_CONST_ENTRY(name) \ +static ssize_t show_##name(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + return can_btc_show(d, attr, buf, \ + offsetof(struct can_bittiming_const, name));\ +} \ +static DEVICE_ATTR(hw_##name, S_IRUGO, show_##name, NULL) + +CAN_BT_CONST_ENTRY(tseg1_min); +CAN_BT_CONST_ENTRY(tseg1_max); +CAN_BT_CONST_ENTRY(tseg2_min); +CAN_BT_CONST_ENTRY(tseg2_max); +CAN_BT_CONST_ENTRY(sjw_max); +CAN_BT_CONST_ENTRY(brp_min); +CAN_BT_CONST_ENTRY(brp_max); +CAN_BT_CONST_ENTRY(brp_inc); + +static ssize_t can_bt_show(const struct device *d, + struct device_attribute *attr, char *buf, + unsigned long offset) +{ + struct net_device *dev = to_net_dev(d); + struct can_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->bittiming; + ssize_t ret = -EINVAL; + u32 *ptr, val; + + WARN_ON(offset >= sizeof(struct can_bittiming) || + offset % sizeof(u32) != 0); + + read_lock(&dev_base_lock); + if (dev_isalive(dev)) { + ptr = (u32 *)(((u8 *)bt) + offset); + if (ptr == &bt->sample_point && + priv->state != CAN_STATE_STOPPED) + val = can_sample_point(bt); + else + val = *ptr; + ret = sprintf(buf, "%d\n", val); + } + read_unlock(&dev_base_lock); + return ret; +} + +static ssize_t can_bt_store(const struct device *d, + struct device_attribute *attr, + const char *buf, size_t count, + unsigned long offset) +{ + struct net_device *dev = to_net_dev(d); + struct can_priv *priv = netdev_priv(dev); + struct can_bittiming *bt = &priv->bittiming; + unsigned long new; + ssize_t ret = -EINVAL; + u32 *ptr; + + if (priv->state != CAN_STATE_STOPPED) + return -EBUSY; + + WARN_ON(offset >= sizeof(struct can_bittiming) || + offset % sizeof(u32) != 0); + + ret = strict_strtoul(buf, 0, &new); + if (ret) + goto out; + + ptr = (u32 *)(((u8 *)bt) + offset); + rtnl_lock(); + if (dev_isalive(dev)) { + *ptr = (u32)new; + + if ((ptr == &bt->bitrate) || (ptr == &bt->sample_point)) { + bt->tq = 0; + bt->brp = 0; + bt->sjw = 0; + bt->prop_seg = 0; + bt->phase_seg1 = 0; + bt->phase_seg2 = 0; + } else { + bt->bitrate = 0; + bt->sample_point = 0; + } + ret = count; + } + rtnl_unlock(); +out: + return ret; +} + +#define CAN_BT_ENTRY_RO(name) \ +static ssize_t show_##name(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + return can_bt_show(d, attr, buf, \ + offsetof(struct can_bittiming, name)); \ +} \ +static DEVICE_ATTR(hw_##name, S_IRUGO, show_##name, NULL) + +CAN_BT_ENTRY_RO(clock); + +#define CAN_BT_ENTRY(name) \ +static ssize_t show_##name(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + return can_bt_show(d, attr, buf, \ + offsetof(struct can_bittiming, name)); \ +} \ +static ssize_t store_##name(struct device *d, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + return can_bt_store(d, attr, buf, count, \ + offsetof(struct can_bittiming, name)); \ +} \ +static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, show_##name, store_##name) + +CAN_BT_ENTRY(bitrate); +CAN_BT_ENTRY(sample_point); +CAN_BT_ENTRY(tq); +CAN_BT_ENTRY(prop_seg); +CAN_BT_ENTRY(phase_seg1); +CAN_BT_ENTRY(phase_seg2); +CAN_BT_ENTRY(sjw); + +static struct attribute *can_bittiming_attrs[] = { + &dev_attr_hw_tseg1_min.attr, + &dev_attr_hw_tseg1_max.attr, + &dev_attr_hw_tseg2_max.attr, + &dev_attr_hw_tseg2_min.attr, + &dev_attr_hw_sjw_max.attr, + &dev_attr_hw_brp_min.attr, + &dev_attr_hw_brp_max.attr, + &dev_attr_hw_brp_inc.attr, + &dev_attr_hw_clock.attr, + &dev_attr_bitrate.attr, + &dev_attr_sample_point.attr, + &dev_attr_tq.attr, + &dev_attr_prop_seg.attr, + &dev_attr_phase_seg1.attr, + &dev_attr_phase_seg2.attr, + &dev_attr_sjw.attr, + NULL +}; + +static struct attribute_group can_bittiming_group = { + .name = "can_bittiming", + .attrs = can_bittiming_attrs, +}; + +/* Show a given attribute in the CAN statistics group */ +static ssize_t can_stat_show(const struct device *d, + struct device_attribute *attr, char *buf, + unsigned long offset) +{ + struct net_device *dev = to_net_dev(d); + struct can_priv *priv = netdev_priv(dev); + struct can_device_stats *stats = &priv->can_stats; + ssize_t ret = -EINVAL; + + WARN_ON(offset >= sizeof(struct can_device_stats) || + offset % sizeof(unsigned long) != 0); + + read_lock(&dev_base_lock); + if (dev_isalive(dev)) + ret = sprintf(buf, "%ld\n", + *(unsigned long *)(((u8 *)stats) + offset)); + + read_unlock(&dev_base_lock); + return ret; +} + +/* Generate a read-only CAN statistics attribute */ +#define CAN_STAT_ENTRY(name) \ +static ssize_t show_##name(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + return can_stat_show(d, attr, buf, \ + offsetof(struct can_device_stats, name)); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL) + +CAN_STAT_ENTRY(error_warning); +CAN_STAT_ENTRY(error_passive); +CAN_STAT_ENTRY(bus_error); +CAN_STAT_ENTRY(arbitration_lost); +CAN_STAT_ENTRY(data_overrun); +CAN_STAT_ENTRY(wakeup); +CAN_STAT_ENTRY(restarts); + +static struct attribute *can_statistics_attrs[] = { + &dev_attr_error_warning.attr, + &dev_attr_error_passive.attr, + &dev_attr_bus_error.attr, + &dev_attr_arbitration_lost.attr, + &dev_attr_data_overrun.attr, + &dev_attr_wakeup.attr, + &dev_attr_restarts.attr, + NULL +}; + +static struct attribute_group can_statistics_group = { + .name = "can_statistics", + .attrs = can_statistics_attrs, +}; + +void can_create_sysfs(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + int err; + + CAN_CREATE_FILE(dev, can_ctrlmode); + CAN_CREATE_FILE(dev, can_echo); + CAN_CREATE_FILE(dev, can_restart); + CAN_CREATE_FILE(dev, can_state); + CAN_CREATE_FILE(dev, can_restart_ms); + + err = sysfs_create_group(&(dev->dev.kobj), + &can_statistics_group); + if (err) { + printk(KERN_EMERG + "couldn't create sysfs group for CAN statistics\n"); + } + + if (priv->bittiming_const) { + err = sysfs_create_group(&(dev->dev.kobj), + &can_bittiming_group); + if (err) { + printk(KERN_EMERG "couldn't create sysfs " + "group for CAN bittiming\n"); + } + } +} + +void can_remove_sysfs(struct net_device *dev) +{ + struct can_priv *priv = netdev_priv(dev); + + CAN_REMOVE_FILE(dev, can_ctrlmode); + CAN_REMOVE_FILE(dev, can_echo); + CAN_REMOVE_FILE(dev, can_state); + CAN_REMOVE_FILE(dev, can_restart); + CAN_REMOVE_FILE(dev, can_restart_ms); + + sysfs_remove_group(&(dev->dev.kobj), &can_statistics_group); + if (priv->bittiming_const) + sysfs_remove_group(&(dev->dev.kobj), &can_bittiming_group); +} + +#endif /* CONFIG_SYSFS */ + + + diff --git a/drivers/net/can/sysfs.h b/drivers/net/can/sysfs.h new file mode 100644 index 000000000000..e21f2fa4b158 --- /dev/null +++ b/drivers/net/can/sysfs.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 Wolfgang Grandegger <wg@grandegger.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the version 2 of the GNU General Public License + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CAN_SYSFS_H +#define CAN_SYSFS_H + +void can_create_sysfs(struct net_device *dev); +void can_remove_sysfs(struct net_device *dev); + +#endif /* CAN_SYSFS_H */ diff --git a/drivers/net/can/vcan.c b/drivers/net/can/vcan.c index 103f0f1df280..69e6b804c477 100644 --- a/drivers/net/can/vcan.c +++ b/drivers/net/can/vcan.c @@ -128,26 +128,27 @@ static int vcan_tx(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_OK; } + static void vcan_setup(struct net_device *dev) { - dev->type = ARPHRD_CAN; - dev->mtu = sizeof(struct can_frame); - dev->hard_header_len = 0; - dev->addr_len = 0; - dev->tx_queue_len = 0; - dev->flags = IFF_NOARP; + dev->type = ARPHRD_CAN; + dev->mtu = sizeof(struct can_frame); + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->tx_queue_len = 0; + dev->flags = IFF_NOARP; /* set flags according to driver capabilities */ if (echo) dev->flags |= IFF_ECHO; - dev->hard_start_xmit = vcan_tx; - dev->destructor = free_netdev; + dev->hard_start_xmit = vcan_tx; + dev->destructor = free_netdev; } static struct rtnl_link_ops vcan_link_ops __read_mostly = { - .kind = "vcan", - .setup = vcan_setup, + .kind = "vcan", + .setup = vcan_setup, }; static __init int vcan_init_module(void) diff --git a/drivers/net/cs89x0.c b/drivers/net/cs89x0.c index 7107620f615d..d56846d8d5b4 100644 --- a/drivers/net/cs89x0.c +++ b/drivers/net/cs89x0.c @@ -193,12 +193,17 @@ static unsigned int cs8900_irq_map[] = {IRQ_IXDP2X01_CS8900, 0, 0, 0}; #define CIRRUS_DEFAULT_IRQ VH_INTC_INT_NUM_CASCADED_INTERRUPT_1 /* Event inputs bank 1 - ID 35/bit 3 */ static unsigned int netcard_portlist[] __used __initdata = {CIRRUS_DEFAULT_BASE, 0}; static unsigned int cs8900_irq_map[] = {CIRRUS_DEFAULT_IRQ, 0, 0, 0}; -#elif defined(CONFIG_MACH_MX31ADS) -#include <mach/board-mx31ads.h> -static unsigned int netcard_portlist[] __used __initdata = { - PBC_BASE_ADDRESS + PBC_CS8900A_IOBASE + 0x300, 0 -}; -static unsigned cs8900_irq_map[] = {EXPIO_INT_ENET_INT, 0, 0, 0}; +#elif defined(CONFIG_ARCH_MXC) +/*! Null terminated portlist used to probe for the CS8900A device on ISA Bus + * Add 3 to reset the page window before probing (fixes eth probe when deployed + * using nand_boot) + */ +extern unsigned int netcard_portlist[2]; +/*! + * The CS8900A has 4 IRQ pins, which is software selectable, CS8900A interrupt + * pin 0 is used for interrupt generation. + */ +extern unsigned int cs8900_irq_map[4]; #else static unsigned int netcard_portlist[] __used __initdata = { 0x300, 0x320, 0x340, 0x360, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0}; @@ -1034,7 +1039,7 @@ skip_this_frame: void __init reset_chip(struct net_device *dev) { -#if !defined(CONFIG_MACH_MX31ADS) +#if !defined(CONFIG_ARCH_MXC) #if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) struct net_local *lp = netdev_priv(dev); int ioaddr = dev->base_addr; @@ -1063,7 +1068,7 @@ void __init reset_chip(struct net_device *dev) reset_start_time = jiffies; while( (readreg(dev, PP_SelfST) & INIT_DONE) == 0 && jiffies - reset_start_time < 2) ; -#endif /* !CONFIG_MACH_MX31ADS */ +#endif /* !CONFIG_ARCH_MXC */ } diff --git a/drivers/net/enc28j60.c b/drivers/net/enc28j60.c index 36cb6e95b465..98c07af8a2f9 100644 --- a/drivers/net/enc28j60.c +++ b/drivers/net/enc28j60.c @@ -36,6 +36,7 @@ #define DRV_VERSION "1.01" #define SPI_OPLEN 1 +#define MAX_ENC_CARDS 1 #define ENC28J60_MSG_DEFAULT \ (NETIF_MSG_PROBE | NETIF_MSG_IFUP | NETIF_MSG_IFDOWN | NETIF_MSG_LINK) @@ -49,6 +50,10 @@ /* Max TX retries in case of collision as suggested by errata datasheet */ #define MAX_TX_RETRYCOUNT 16 +#ifdef CONFIG_ARCH_STMP3XXX +#include <mach/stmp3xxx.h> +#include <mach/regs-ocotp.h> +#endif enum { RXFILTER_NORMAL, RXFILTER_MULTI, @@ -81,6 +86,69 @@ static struct { u32 msg_enable; } debug = { -1 }; +static int random_mac; /* = 0 */ +static char *mac[MAX_ENC_CARDS]; + +static int enc28j60_get_mac(unsigned char *dev_addr, int idx) +{ + int i, r; + char *p, *item; + unsigned long v; + unsigned char sv[10]; + + if (idx < 0) + idx = 0; + if (idx > MAX_ENC_CARDS) + return false; + + if (!mac[idx]) { +#ifdef CONFIG_ARCH_STMP3XXX + if (get_evk_board_version() >= 1) { + int mac1 , mac2 , retry = 0; + + HW_OCOTP_CTRL_SET(BM_OCOTP_CTRL_RD_BANK_OPEN); + while (HW_OCOTP_CTRL_RD() & BM_OCOTP_CTRL_BUSY) { + msleep(10); + retry++; + if (retry > 10) + return false; + } + + mac1 = HW_OCOTP_CUSTn_RD(0); + mac2 = HW_OCOTP_CUSTn_RD(1); + if (MAX_ADDR_LEN < 6) + return false; + + dev_addr[0] = (mac1 >> 24) & 0xFF; + dev_addr[1] = (mac1 >> 16) & 0xFF; + dev_addr[2] = (mac1 >> 8) & 0xFF; + dev_addr[3] = (mac1 >> 0) & 0xFF; + dev_addr[4] = (mac2 >> 8) & 0xFF; + dev_addr[5] = (mac2 >> 0) & 0xFF; + return true; + } +#endif + return false; + } + + item = mac[idx]; + for (i = 0; i < MAX_ADDR_LEN; i++) { + p = strchr(item, ':'); + if (!p) + sprintf(sv, "0x%s", item); + else + sprintf(sv, "0x%*.*s", p - item, p-item, item); + r = strict_strtoul(sv, 0, &v); + dev_addr[i] = v; + if (p) + item = p + 1; + else + break; + if (r < 0) + return false; + } + return true; +} /* * SPI read buffer * wait for the SPI transfer and copy received data to destination @@ -90,10 +158,13 @@ spi_read_buf(struct enc28j60_net *priv, int len, u8 *data) { u8 *rx_buf = priv->spi_transfer_buf + 4; u8 *tx_buf = priv->spi_transfer_buf; - struct spi_transfer t = { - .tx_buf = tx_buf, - .rx_buf = rx_buf, - .len = SPI_OPLEN + len, + struct spi_transfer tt = { + .tx_buf = tx_buf, + .len = SPI_OPLEN, + }; + struct spi_transfer tr = { + .rx_buf = rx_buf, + .len = len, }; struct spi_message msg; int ret; @@ -102,10 +173,11 @@ spi_read_buf(struct enc28j60_net *priv, int len, u8 *data) tx_buf[1] = tx_buf[2] = tx_buf[3] = 0; /* don't care */ spi_message_init(&msg); - spi_message_add_tail(&t, &msg); + spi_message_add_tail(&tt, &msg); + spi_message_add_tail(&tr, &msg); ret = spi_sync(priv->spi, &msg); if (ret == 0) { - memcpy(data, &rx_buf[SPI_OPLEN], len); + memcpy(data, rx_buf, len); ret = msg.status; } if (ret && netif_msg_drv(priv)) @@ -196,16 +268,32 @@ static void enc28j60_soft_reset(struct enc28j60_net *priv) */ static void enc28j60_set_bank(struct enc28j60_net *priv, u8 addr) { - if ((addr & BANK_MASK) != priv->bank) { - u8 b = (addr & BANK_MASK) >> 5; + u8 b = (addr & BANK_MASK) >> 5; + + /* These registers (EIE, EIR, ESTAT, ECON2, ECON1) + * are present in all banks, no need to switch bank + */ + if (addr >= EIE && addr <= ECON1) + return; - if (b != (ECON1_BSEL1 | ECON1_BSEL0)) + /* Clear or set each bank selection bit as needed */ + if ((b & ECON1_BSEL0) != (priv->bank & ECON1_BSEL0)) { + if (b & ECON1_BSEL0) + spi_write_op(priv, ENC28J60_BIT_FIELD_SET, ECON1, + ECON1_BSEL0); + else + spi_write_op(priv, ENC28J60_BIT_FIELD_CLR, ECON1, + ECON1_BSEL0); + } + if ((b & ECON1_BSEL1) != (priv->bank & ECON1_BSEL1)) { + if (b & ECON1_BSEL1) + spi_write_op(priv, ENC28J60_BIT_FIELD_SET, ECON1, + ECON1_BSEL1); + else spi_write_op(priv, ENC28J60_BIT_FIELD_CLR, ECON1, - ECON1_BSEL1 | ECON1_BSEL0); - if (b != 0) - spi_write_op(priv, ENC28J60_BIT_FIELD_SET, ECON1, b); - priv->bank = (addr & BANK_MASK); + ECON1_BSEL1); } + priv->bank = b; } /* @@ -930,7 +1018,7 @@ static void enc28j60_hw_rx(struct net_device *ndev) if (netif_msg_rx_status(priv)) enc28j60_dump_rsv(priv, __func__, next_packet, len, rxstat); - if (!RSV_GETBIT(rxstat, RSV_RXOK)) { + if (!RSV_GETBIT(rxstat, RSV_RXOK) || len > MAX_FRAMELEN) { if (netif_msg_rx_err(priv)) dev_err(&ndev->dev, "Rx Error (%04x)\n", rxstat); ndev->stats.rx_errors++; @@ -938,6 +1026,8 @@ static void enc28j60_hw_rx(struct net_device *ndev) ndev->stats.rx_crc_errors++; if (RSV_GETBIT(rxstat, RSV_LENCHECKERR)) ndev->stats.rx_frame_errors++; + if (len > MAX_FRAMELEN) + ndev->stats.rx_over_errors++; } else { skb = dev_alloc_skb(len + NET_IP_ALIGN); if (!skb) { @@ -1093,8 +1183,24 @@ static int enc28j60_rx_interrupt(struct net_device *ndev) priv->max_pk_counter); } ret = pk_counter; - while (pk_counter-- > 0) + while (pk_counter-- > 0) { + if (!priv->full_duplex) { + /* + * This works only in HALF DUPLEX mode: + * when more than 2 packets are available, start + * transmission of 11111.. frame by setting + * FCON0 (0x01) in EFLOCON + * + * This bit can be cleared either explicitly, or by + * trasmitting the packet in enc28j60_hw_tx. + */ + if (pk_counter > 2) + locked_reg_bfset(priv, EFLOCON, 0x01); + if (pk_counter == 1) + locked_reg_bfclr(priv, EFLOCON, 0x01); + } enc28j60_hw_rx(ndev); + } return ret; } @@ -1220,6 +1326,11 @@ static void enc28j60_irq_work_handler(struct work_struct *work) */ static void enc28j60_hw_tx(struct enc28j60_net *priv) { + if (!priv->tx_skb) { + enc28j60_tx_clear(priv->netdev, false); + return; + } + if (netif_msg_tx_queued(priv)) printk(KERN_DEBUG DRV_NAME ": Tx Packet Len:%d\n", priv->tx_skb->len); @@ -1523,6 +1634,7 @@ static int __devinit enc28j60_probe(struct spi_device *spi) struct net_device *dev; struct enc28j60_net *priv; int ret = 0; + int set; if (netif_msg_drv(&debug)) dev_info(&spi->dev, DRV_NAME " Ethernet driver %s loaded\n", @@ -1556,7 +1668,11 @@ static int __devinit enc28j60_probe(struct spi_device *spi) ret = -EIO; goto error_irq; } - random_ether_addr(dev->dev_addr); + + /* need a counter here, to count instances of enc28j60 devices */ + set = enc28j60_get_mac(dev->dev_addr, -1); + if (!set || random_mac) + random_ether_addr(dev->dev_addr); enc28j60_set_hw_macaddr(dev); /* Board setup must set the relevant edge trigger type; @@ -1616,6 +1732,40 @@ static int __devexit enc28j60_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int +enc28j60_suspend(struct spi_device *spi, pm_message_t state) +{ + struct enc28j60_net *priv = dev_get_drvdata(&spi->dev); + struct net_device *net_dev = priv ? priv->netdev : NULL; + + if (net_dev && netif_running(net_dev)) { + netif_stop_queue(net_dev); + netif_device_detach(net_dev); + disable_irq(spi->irq); + } + return 0; +} + +static int +enc28j60_resume(struct spi_device *spi) +{ + struct enc28j60_net *priv = dev_get_drvdata(&spi->dev); + struct net_device *net_dev = priv ? priv->netdev : NULL; + + if (net_dev && netif_running(net_dev)) { + enable_irq(spi->irq); + netif_device_attach(net_dev); + netif_start_queue(net_dev); + schedule_work(&priv->restart_work); + } + return 0; +} +#else +#define enc28j60_resume NULL +#define enc28j60_suspend NULL +#endif + static struct spi_driver enc28j60_driver = { .driver = { .name = DRV_NAME, @@ -1623,6 +1773,8 @@ static struct spi_driver enc28j60_driver = { }, .probe = enc28j60_probe, .remove = __devexit_p(enc28j60_remove), + .suspend = enc28j60_suspend, + .resume = enc28j60_resume, }; static int __init enc28j60_init(void) @@ -1645,4 +1797,6 @@ MODULE_DESCRIPTION(DRV_NAME " ethernet driver"); MODULE_AUTHOR("Claudio Lanconelli <lanconelli.claudio@eptar.com>"); MODULE_LICENSE("GPL"); module_param_named(debug, debug.msg_enable, int, 0); +module_param(random_mac, int, 0444); +module_param_array(mac, charp, NULL, 0); MODULE_PARM_DESC(debug, "Debug verbosity level (0=none, ..., ffff=all)"); diff --git a/drivers/net/fec.c b/drivers/net/fec.c index ecd5c71a7a8a..94d55a17d11c 100644 --- a/drivers/net/fec.c +++ b/drivers/net/fec.c @@ -18,6 +18,9 @@ * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be) * Copyright (c) 2004-2006 Macq Electronique SA. */ +/* + * Copyright 2006-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ #include <linux/module.h> #include <linux/kernel.h> @@ -36,16 +39,29 @@ #include <linux/spinlock.h> #include <linux/workqueue.h> #include <linux/bitops.h> +#include <linux/clk.h> #include <asm/irq.h> #include <asm/uaccess.h> #include <asm/io.h> #include <asm/pgtable.h> #include <asm/cacheflush.h> +#include <asm/mach-types.h> +#if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ + defined(CONFIG_M520x) || defined(CONFIG_M532x) #include <asm/coldfire.h> #include <asm/mcfsim.h> #include "fec.h" +#define FEC_ALIGNMENT (0x03) /*FEC needs 4bytes alignment*/ +#elif defined(CONFIG_ARCH_MXC) +#include <mach/hardware.h> +#include <mach/iim.h> +#include "fec.h" +#define FEC_ALIGNMENT (0x0F) /*FEC needs 128bits(32bytes) alignment*/ +#endif + +#define FEC_ADDR_ALIGNMENT(x) ((unsigned char *)(((unsigned long )(x) + (FEC_ALIGNMENT)) & (~FEC_ALIGNMENT))) #if defined(CONFIG_FEC2) #define FEC_MAX_PORTS 2 @@ -53,7 +69,7 @@ #define FEC_MAX_PORTS 1 #endif -#if defined(CONFIG_M5272) +#if defined(CONFIG_M5272) || defined(CONFIG_ARCH_MXC) #define HAVE_mii_link_interrupt #endif @@ -72,6 +88,8 @@ static unsigned int fec_hw[] = { (MCF_MBAR+0x30000), #elif defined(CONFIG_M532x) (MCF_MBAR+0xfc030000), +#elif defined(CONFIG_ARCH_MXC) + (IO_ADDRESS(FEC_BASE_ADDR)), #else &(((immap_t *)IMAP_ADDR)->im_cpm.cp_fec), #endif @@ -149,6 +167,12 @@ typedef struct { #define FEC_ENET_MII ((uint)0x00800000) /* MII interrupt */ #define FEC_ENET_EBERR ((uint)0x00400000) /* SDMA bus error */ +#ifndef CONFIG_ARCH_MXC +#define FEC_ENET_MASK ((uint)0xffc00000) +#else +#define FEC_ENET_MASK ((uint)0xfff80000) +#endif + /* The FEC stores dest/src/type, data, and checksum for receive packets. */ #define PKT_MAXBUF_SIZE 1518 @@ -162,7 +186,7 @@ typedef struct { * account when setting it. */ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ - defined(CONFIG_M520x) || defined(CONFIG_M532x) + defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) #define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) #else #define OPT_FRAME_SIZE 0 @@ -185,11 +209,13 @@ struct fec_enet_private { /* The saved address of a sent-in-place packet/buffer, for skfree(). */ unsigned char *tx_bounce[TX_RING_SIZE]; struct sk_buff* tx_skbuff[TX_RING_SIZE]; + struct sk_buff* rx_skbuff[RX_RING_SIZE]; ushort skb_cur; ushort skb_dirty; /* CPM dual port RAM relative addresses. */ + void * cbd_mem_base; /* save the virtual base address of rx&tx buffer descripter */ cbd_t *rx_bd_base; /* Address of Rx and Tx buffers. */ cbd_t *tx_bd_base; cbd_t *cur_rx, *cur_tx; /* The next free ring entry */ @@ -206,6 +232,7 @@ struct fec_enet_private { uint phy_speed; phy_info_t const *phy; struct work_struct phy_task; + struct net_device *net; uint sequence_done; uint mii_phy_task_queued; @@ -217,6 +244,8 @@ struct fec_enet_private { int link; int old_link; int full_duplex; + + struct clk *clk; }; static int fec_enet_open(struct net_device *dev); @@ -231,6 +260,17 @@ static void fec_restart(struct net_device *dev, int duplex); static void fec_stop(struct net_device *dev); static void fec_set_mac_address(struct net_device *dev); +static void __inline__ fec_dcache_inv_range(void * start, void * end); +static void __inline__ fec_dcache_flush_range(void * start, void * end); + +/* + * fec_copy_threshold controls the copy when recieving ethernet frame. + * If ethernet header aligns 4bytes, the ip header and upper header will not aligns 4bytes. + * The resean is ethernet header is 14bytes. + * And the max size of tcp & ip header is 128bytes. Normally it is 40bytes. + * So I set the default value between 128 to 256. + */ +static int fec_copy_threshold = -1; /* MII processing. We keep this as simple as possible. Requests are * placed on the list (if there is room). When the request is finished @@ -342,10 +382,10 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) * 4-byte boundaries. Use bounce buffers to copy data * and get it aligned. Ugh. */ - if (bdp->cbd_bufaddr & 0x3) { + if ((bdp->cbd_bufaddr) & FEC_ALIGNMENT) { unsigned int index; index = bdp - fep->tx_bd_base; - memcpy(fep->tx_bounce[index], (void *) bdp->cbd_bufaddr, bdp->cbd_datlen); + memcpy(fep->tx_bounce[index], (void *) skb->data, skb->len); bdp->cbd_bufaddr = __pa(fep->tx_bounce[index]); } @@ -359,8 +399,8 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) /* Push the data cache so the CPM does not get stale memory * data. */ - flush_dcache_range((unsigned long)skb->data, - (unsigned long)skb->data + skb->len); + fec_dcache_flush_range(__va(bdp->cbd_bufaddr), __va(bdp->cbd_bufaddr) + + bdp->cbd_datlen); /* Send it on its way. Tell FEC it's ready, interrupt when done, * it's the last BD of the frame, and to put the CRC on the end. @@ -373,7 +413,7 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) dev->trans_start = jiffies; /* Trigger transmission start */ - fecp->fec_x_des_active = 0; + fecp->fec_x_des_active = 0x01000000; /* If this was the last BD in the ring, start at the beginning again. */ @@ -460,7 +500,7 @@ fec_enet_interrupt(int irq, void * dev_id) /* Handle receive event in its own function. */ - if (int_events & FEC_ENET_RXF) { + if (int_events & (FEC_ENET_RXF | FEC_ENET_RXB)) { ret = IRQ_HANDLED; fec_enet_rx(dev); } @@ -469,7 +509,7 @@ fec_enet_interrupt(int irq, void * dev_id) descriptors. FEC handles all errors, we just discover them as part of the transmit process. */ - if (int_events & FEC_ENET_TXF) { + if (int_events & (FEC_ENET_TXF | FEC_ENET_TXB)) { ret = IRQ_HANDLED; fec_enet_tx(dev); } @@ -572,6 +612,7 @@ fec_enet_rx(struct net_device *dev) struct sk_buff *skb; ushort pkt_len; __u8 *data; + int rx_index ; #ifdef CONFIG_M532x flush_cache_all(); @@ -588,7 +629,7 @@ fec_enet_rx(struct net_device *dev) bdp = fep->cur_rx; while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { - + rx_index = bdp - fep->rx_bd_base; #ifndef final_version /* Since we have allocated space to hold a complete frame, * the last indicator should be set. @@ -638,14 +679,31 @@ while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { * include that when passing upstream as it messes up * bridging applications. */ - skb = dev_alloc_skb(pkt_len-4); + if ((pkt_len - 4) < fec_copy_threshold) { + skb = dev_alloc_skb(pkt_len); + } else { + skb = dev_alloc_skb(FEC_ENET_RX_FRSIZE); + } if (skb == NULL) { printk("%s: Memory squeeze, dropping packet.\n", dev->name); dev->stats.rx_dropped++; } else { - skb_put(skb,pkt_len-4); /* Make room */ - skb_copy_to_linear_data(skb, data, pkt_len-4); + if ((pkt_len - 4) < fec_copy_threshold) { + skb_reserve(skb, 2); /*skip 2bytes, so ipheader is align 4bytes*/ + skb_put(skb,pkt_len-4); /* Make room */ + skb_copy_to_linear_data(skb, data, pkt_len-4); + } else { + struct sk_buff * pskb = fep->rx_skbuff[rx_index]; + + fec_dcache_inv_range(skb->data, skb->data + + FEC_ENET_RX_FRSIZE); + fep->rx_skbuff[rx_index] = skb; + skb->data = FEC_ADDR_ALIGNMENT(skb->data); + bdp->cbd_bufaddr = __pa(skb->data); + skb_put(pskb,pkt_len-4); /* Make room */ + skb = pskb; + } skb->protocol=eth_type_trans(skb,dev); netif_rx(skb); } @@ -672,7 +730,7 @@ while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { * incoming frames. On a heavily loaded network, we should be * able to keep up at the expense of system resources. */ - fecp->fec_r_des_active = 0; + fecp->fec_r_des_active = 0x01000000; #endif } /* while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) */ fep->cur_rx = (cbd_t *)bdp; @@ -1207,6 +1265,48 @@ static phy_info_t phy_info_dp83848= { }, }; +static phy_info_t phy_info_lan8700 = { + 0x0007C0C, + "LAN8700", + (const phy_cmd_t []) { /* config */ + { mk_mii_read(MII_REG_CR), mii_parse_cr }, + { mk_mii_read(MII_REG_ANAR), mii_parse_anar }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* startup */ + { mk_mii_write(MII_REG_CR, 0x1200), NULL }, /* autonegotiate */ + { mk_mii_read(MII_REG_SR), mii_parse_sr }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* act_int */ + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* shutdown */ + { mk_mii_end, } + }, +}; + +static phy_info_t phy_info_lan8710 = { + 0x0007C0F, + "LAN8710", + (const phy_cmd_t []) { /* config */ + { mk_mii_read(MII_REG_CR), mii_parse_cr }, + { mk_mii_read(MII_REG_ANAR), mii_parse_anar }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* startup */ + { mk_mii_write(MII_REG_CR, 0x1200), NULL }, /* autonegotiate */ + { mk_mii_read(MII_REG_SR), mii_parse_sr }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* act_int */ + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* shutdown */ + { mk_mii_end, } + }, +}; + /* ------------------------------------------------------------------------- */ static phy_info_t const * const phy_info[] = { @@ -1216,6 +1316,8 @@ static phy_info_t const * const phy_info[] = { &phy_info_am79c874, &phy_info_ks8721bl, &phy_info_dp83848, + &phy_info_lan8700, + &phy_info_lan8710, NULL }; @@ -1227,6 +1329,21 @@ mii_link_interrupt(int irq, void * dev_id); #if defined(CONFIG_M5272) /* + * * do some initializtion based architecture of this chip + * */ +static void __inline__ fec_arch_init(void) +{ + return; +} +/* + * * do some cleanup based architecture of this chip + * */ +static void __inline__ fec_arch_exit(void) +{ + return; +} + +/* * Code specific to Coldfire 5272 setup. */ static void __inline__ fec_request_intrs(struct net_device *dev) @@ -1327,15 +1444,40 @@ static void __inline__ fec_phy_ack_intr(void) *icrp = 0x0d000000; } -static void __inline__ fec_localhw_setup(void) +static void __inline__ fec_localhw_setup(struct net_device *dev) +{ +} + +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) { + return addr; } /* - * Do not need to make region uncached on 5272. + * unmap memory erea started with addr from uncachable erea. */ -static void __inline__ fec_uncache(unsigned long addr) +static void __inline__ fec_unmap_uncache(void * addr) { + return ; } /* ------------------------------------------------------------------------- */ @@ -1343,6 +1485,22 @@ static void __inline__ fec_uncache(unsigned long addr) #elif defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) /* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} + +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} + +/* * Code specific to Coldfire 5230/5231/5232/5234/5235, * the 5270/5271/5274/5275 and 5280/5282 setups. */ @@ -1489,20 +1647,58 @@ static void __inline__ fec_phy_ack_intr(void) { } -static void __inline__ fec_localhw_setup(void) +static void __inline__ fec_localhw_setup(struct net_device *dev) { } +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} /* - * Do not need to make region uncached on 5272. + * flush dcache related with the virtual memory range(start, end) */ -static void __inline__ fec_uncache(unsigned long addr) +static void __inline__ fec_dcache_flush_range(void * start, void * end) { + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) +{ + return addr; +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } /* ------------------------------------------------------------------------- */ #elif defined(CONFIG_M520x) +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} /* * Code specific to Coldfire 520x @@ -1610,17 +1806,63 @@ static void __inline__ fec_phy_ack_intr(void) { } -static void __inline__ fec_localhw_setup(void) +static void __inline__ fec_localhw_setup(struct net_device *dev) +{ +} + +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) { + return ; } -static void __inline__ fec_uncache(unsigned long addr) +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) { + return addr; } +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; +} + + /* ------------------------------------------------------------------------- */ #elif defined(CONFIG_M532x) + +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} + +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} + /* * Code specific for M532x */ @@ -1749,21 +1991,297 @@ static void __inline__ fec_phy_ack_intr(void) { } -static void __inline__ fec_localhw_setup(void) +static void __inline__ fec_localhw_setup(struct net_device *dev) { } /* - * Do not need to make region uncached on 532x. + * invalidate dcache related with the virtual memory range(start, end) */ -static void __inline__ fec_uncache(unsigned long addr) +static void __inline__ fec_dcache_inv_range(void * start, void * end) { + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) +{ + return addr; +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } /* ------------------------------------------------------------------------- */ +#elif defined(CONFIG_ARCH_MXC) + +extern void gpio_fec_active(void); +extern void gpio_fec_inactive(void); +extern unsigned int expio_intr_fec; + +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + struct clk *clk; + gpio_fec_active(); + clk = clk_get(NULL, "fec_clk"); + clk_enable(clk); + clk_put(clk); + return; +} +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + struct clk *clk; + clk = clk_get(NULL, "fec_clk"); + clk_disable(clk); + clk_put(clk); + gpio_fec_inactive(); + return; +} + +/* + * Code specific to Freescale i.MXC + */ +static void __inline__ fec_request_intrs(struct net_device *dev) +{ + /* Setup interrupt handlers. */ + if (request_irq(MXC_INT_FEC, fec_enet_interrupt, 0, "fec", dev) != 0) + panic("FEC: Could not allocate FEC IRQ(%d)!\n", MXC_INT_FEC); + /* TODO: disable now due to CPLD issue */ + if ((expio_intr_fec > 0) && + (request_irq(expio_intr_fec, mii_link_interrupt, 0, "fec(MII)", dev) != 0)) + panic("FEC: Could not allocate FEC(MII) IRQ(%d)!\n", expio_intr_fec); + disable_irq(expio_intr_fec); +} + +static void __inline__ fec_set_mii(struct net_device *dev, struct fec_enet_private *fep) +{ + u32 rate; + struct clk *clk; + volatile fec_t *fecp; + fecp = fep->hwp; + fecp->fec_r_cntrl = OPT_FRAME_SIZE | 0x04; + fecp->fec_x_cntrl = 0x00; + + /* + * Set MII speed to 2.5 MHz + */ + clk = clk_get(NULL, "fec_clk"); + rate = clk_get_rate(clk); + clk_put(clk); + + fep->phy_speed = + ((((rate / 2 + 4999999) / 2500000) / 2) & 0x3F) << 1; + fecp->fec_mii_speed = fep->phy_speed; + fec_restart(dev, 0); +} + +#define FEC_IIM_BASE IO_ADDRESS(IIM_BASE_ADDR) +static void __inline__ fec_get_mac(struct net_device *dev) +{ + struct fec_enet_private *fep = netdev_priv(dev); + volatile fec_t *fecp; + unsigned char *iap, tmpaddr[ETH_ALEN]; + int i; + unsigned long fec_mac_base = FEC_IIM_BASE + MXC_IIMKEY0; + fecp = fep->hwp; + + if (fecp->fec_addr_low || fecp->fec_addr_high) { + *((unsigned long *) &tmpaddr[0]) = + be32_to_cpu(fecp->fec_addr_low); + *((unsigned short *) &tmpaddr[4]) = + be32_to_cpu(fecp->fec_addr_high); + iap = &tmpaddr[0]; + } else { + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + fec_mac_base = FEC_IIM_BASE + MXC_IIMMAC; + + memset(tmpaddr, 0, ETH_ALEN); + if (!(machine_is_mx35_3ds() || cpu_is_mx51())) { + /* + * Get MAC address from IIM. + * If it is all 1's or 0's, use the default. + */ + for (i = 0; i < ETH_ALEN; i++) + tmpaddr[ETH_ALEN-1-i] = + __raw_readb(fec_mac_base + i * 4); + } + iap = &tmpaddr[0]; + + if ((iap[0] == 0) && (iap[1] == 0) && (iap[2] == 0) && + (iap[3] == 0) && (iap[4] == 0) && (iap[5] == 0)) + iap = fec_mac_default; + if ((iap[0] == 0xff) && (iap[1] == 0xff) && (iap[2] == 0xff) && + (iap[3] == 0xff) && (iap[4] == 0xff) && (iap[5] == 0xff)) + iap = fec_mac_default; + } + + memcpy(dev->dev_addr, iap, ETH_ALEN); + + /* Adjust MAC if using default MAC address */ + if (iap == fec_mac_default) + dev->dev_addr[ETH_ALEN-1] = fec_mac_default[ETH_ALEN-1] + fep->index; +} + +#ifndef MODULE +static int fec_mac_setup(char *new_mac) +{ + char *ptr, *p = new_mac; + int i = 0; + + while (p && (*p) && i < 6) { + ptr = strchr(p, ':'); + if (ptr) + *ptr++ = '\0'; + + if (strlen(p)) { + unsigned long tmp = simple_strtoul(p, NULL, 16); + if (tmp > 0xff) + break; + fec_mac_default[i++] = tmp; + } + p = ptr; + } + + return 0; +} + +__setup("fec_mac=", fec_mac_setup); +#endif + +static void __inline__ fec_enable_phy_intr(void) +{ + if (expio_intr_fec > 0) + enable_irq(expio_intr_fec); +} + +static void __inline__ fec_disable_phy_intr(void) +{ + if (expio_intr_fec > 0) + disable_irq(expio_intr_fec); +} + +static void __inline__ fec_phy_ack_intr(void) +{ + if (expio_intr_fec > 0) + disable_irq(expio_intr_fec); +} + +#ifdef CONFIG_ARCH_MX25 +/* + * i.MX25 allows RMII mode to be configured via a gasket + */ +#define FEC_MIIGSK_CFGR_FRCONT (1 << 6) +#define FEC_MIIGSK_CFGR_LBMODE (1 << 4) +#define FEC_MIIGSK_CFGR_EMODE (1 << 3) +#define FEC_MIIGSK_CFGR_IF_MODE_MASK (3 << 0) +#define FEC_MIIGSK_CFGR_IF_MODE_MII (0 << 0) +#define FEC_MIIGSK_CFGR_IF_MODE_RMII (1 << 0) + +#define FEC_MIIGSK_ENR_READY (1 << 2) +#define FEC_MIIGSK_ENR_EN (1 << 1) + +static void __inline__ fec_localhw_setup(struct net_device *dev) +{ + struct fec_enet_private *fep = netdev_priv(dev); + volatile fec_t *fecp = fep->hwp; + /* + * Set up the MII gasket for RMII mode + */ + printk("%s: enable RMII gasket\n", dev->name); + /* disable the gasket and wait */ + fecp->fec_miigsk_enr = 0; + while (fecp->fec_miigsk_enr & FEC_MIIGSK_ENR_READY) + udelay(1); + + /* configure the gasket for RMII, 50 MHz, no loopback, no echo */ + fecp->fec_miigsk_cfgr = FEC_MIIGSK_CFGR_IF_MODE_RMII; + + /* re-enable the gasket */ + fecp->fec_miigsk_enr = FEC_MIIGSK_ENR_EN; +} #else +static void __inline__ fec_localhw_setup(struct net_device *dev) +{ +} +#endif + +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + dma_sync_single_for_device(NULL, (unsigned long)__pa(start), + (unsigned long)(end - start), + DMA_FROM_DEVICE); + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + dma_sync_single_for_device(NULL, (unsigned long)__pa(start), + (unsigned long)(end - start), DMA_TO_DEVICE); + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) +{ + return (unsigned long)ioremap(__pa(addr), size); +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return iounmap(addr); +} + +/* ------------------------------------------------------------------------- */ + +#else +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} /* * Code specific to the MPC860T setup. @@ -1831,7 +2349,7 @@ static void __inline__ fec_phy_ack_intr(void) { } -static void __inline__ fec_localhw_setup(void) +static void __inline__ fec_localhw_setup(struct net_device *dev) { volatile fec_t *fecp; @@ -1842,12 +2360,40 @@ static void __inline__ fec_localhw_setup(void) fecp->fec_fun_code = 0x78000000; } -static void __inline__ fec_uncache(unsigned long addr) +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) { pte_t *pte; pte = va_to_pte(mem_addr); pte_val(*pte) |= _PAGE_NO_CACHE; flush_tlb_page(init_mm.mmap, mem_addr); + return addr; +} + +/* + * * unmap memory erea started with addr from uncachable erea. + * */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } #endif @@ -2073,10 +2619,14 @@ mii_link_interrupt(int irq, void * dev_id) #if 0 disable_irq(fep->mii_irq); /* disable now, enable later */ #endif - - mii_do_cmd(dev, fep->phy->ack_int); - mii_do_cmd(dev, phy_cmd_relink); /* restart and display status */ - + /* + * Some board will trigger phy interrupt before phy enable. + * And at that moment , fep->phy is not initialized. + */ + if (fep->phy) { + mii_do_cmd(dev, fep->phy->ack_int); + mii_do_cmd(dev, phy_cmd_relink); /* restart and display status */ + } return IRQ_HANDLED; } #endif @@ -2086,6 +2636,7 @@ fec_enet_open(struct net_device *dev) { struct fec_enet_private *fep = netdev_priv(dev); + fec_arch_init(); /* I should reset the ring buffers here, but I don't yet know * a simple way to do that. */ @@ -2094,6 +2645,8 @@ fec_enet_open(struct net_device *dev) fep->sequence_done = 0; fep->link = 0; + fec_restart(dev, 1); + if (fep->phy) { mii_do_cmd(dev, fep->phy->ack_int); mii_do_cmd(dev, fep->phy->config); @@ -2110,18 +2663,14 @@ fec_enet_open(struct net_device *dev) schedule(); mii_do_cmd(dev, fep->phy->startup); - - /* Set the initial link state to true. A lot of hardware - * based on this device does not implement a PHY interrupt, - * so we are never notified of link change. - */ - fep->link = 1; - } else { - fep->link = 1; /* lets just try it and see */ - /* no phy, go full duplex, it's most likely a hub chip */ - fec_restart(dev, 1); } + /* Set the initial link state to true. A lot of hardware + * based on this device does not implement a PHY interrupt, + * so we are never notified of link change. + */ + fep->link = 1; + netif_start_queue(dev); fep->opened = 1; return 0; /* Success */ @@ -2135,9 +2684,10 @@ fec_enet_close(struct net_device *dev) /* Don't know what to do yet. */ fep->opened = 0; - netif_stop_queue(dev); - fec_stop(dev); - + if (fep->link) { + fec_stop(dev); + } + fec_arch_exit(); return 0; } @@ -2248,6 +2798,7 @@ int __init fec_enet_init(struct net_device *dev) unsigned long mem_addr; volatile cbd_t *bdp; cbd_t *cbd_base; + struct sk_buff* pskb; volatile fec_t *fecp; int i, j; static int index = 0; @@ -2256,6 +2807,8 @@ int __init fec_enet_init(struct net_device *dev) if (index >= FEC_MAX_PORTS) return -ENXIO; + fep->net = dev; + /* Allocate memory for buffer descriptors. */ mem_addr = __get_free_page(GFP_KERNEL); @@ -2264,6 +2817,7 @@ int __init fec_enet_init(struct net_device *dev) return -ENOMEM; } + fep->cbd_mem_base = (void *)mem_addr; spin_lock_init(&fep->hw_lock); spin_lock_init(&fep->mii_lock); @@ -2288,10 +2842,14 @@ int __init fec_enet_init(struct net_device *dev) */ fec_get_mac(dev); - cbd_base = (cbd_t *)mem_addr; - /* XXX: missing check for allocation failure */ + cbd_base = (cbd_t *)fec_map_uncache(mem_addr, PAGE_SIZE); + if (cbd_base == NULL) { + free_page(mem_addr); + printk("FEC: map descriptor memory to uncacheable failed?\n"); + return -ENOMEM; + } - fec_uncache(mem_addr); + /* XXX: missing check for allocation failure */ /* Set receive and transmit descriptor base. */ @@ -2306,25 +2864,26 @@ int __init fec_enet_init(struct net_device *dev) /* Initialize the receive buffer descriptors. */ bdp = fep->rx_bd_base; - for (i=0; i<FEC_ENET_RX_PAGES; i++) { - - /* Allocate a page. - */ - mem_addr = __get_free_page(GFP_KERNEL); - /* XXX: missing check for allocation failure */ - - fec_uncache(mem_addr); - - /* Initialize the BD for every fragment in the page. - */ - for (j=0; j<FEC_ENET_RX_FRPPG; j++) { - bdp->cbd_sc = BD_ENET_RX_EMPTY; - bdp->cbd_bufaddr = __pa(mem_addr); - mem_addr += FEC_ENET_RX_FRSIZE; - bdp++; + for (i=0; i<RX_RING_SIZE; i++, bdp++) { + pskb = dev_alloc_skb(FEC_ENET_RX_FRSIZE); + if(pskb == NULL) { + for(; i>0; i--) { + if( fep->rx_skbuff[i-1] ) { + kfree_skb(fep->rx_skbuff[i-1]); + fep->rx_skbuff[i-1] = NULL; + } + } + printk("FEC: allocate skb fail when initializing rx buffer \n"); + free_page(mem_addr); + return -ENOMEM; } + fep->rx_skbuff[i] = pskb; + fec_dcache_inv_range(pskb->data, pskb->data + + FEC_ENET_RX_FRSIZE); + pskb->data = FEC_ADDR_ALIGNMENT(pskb->data); + bdp->cbd_sc = BD_ENET_RX_EMPTY; + bdp->cbd_bufaddr = __pa(pskb->data); } - /* Set the last buffer to wrap. */ bdp--; @@ -2357,19 +2916,23 @@ int __init fec_enet_init(struct net_device *dev) /* Set receive and transmit descriptor base. */ - fecp->fec_r_des_start = __pa((uint)(fep->rx_bd_base)); - fecp->fec_x_des_start = __pa((uint)(fep->tx_bd_base)); + fecp->fec_r_des_start = __pa((uint)(fep->cbd_mem_base)); + fecp->fec_x_des_start = __pa((uint)(fep->cbd_mem_base + RX_RING_SIZE*sizeof(cbd_t))); /* Install our interrupt handlers. This varies depending on * the architecture. */ fec_request_intrs(dev); + /* Clear and enable interrupts */ + fecp->fec_ievent = FEC_ENET_MASK; + fecp->fec_imask = FEC_ENET_TXF | FEC_ENET_TXB | FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII; + fecp->fec_grp_hash_table_high = 0; fecp->fec_grp_hash_table_low = 0; fecp->fec_r_buff_size = PKT_MAXBLR_SIZE; fecp->fec_ecntrl = 2; - fecp->fec_r_des_active = 0; + fecp->fec_r_des_active = 0x01000000; #ifndef CONFIG_M5272 fecp->fec_hash_table_high = 0; fecp->fec_hash_table_low = 0; @@ -2392,10 +2955,6 @@ int __init fec_enet_init(struct net_device *dev) /* setup MII interface */ fec_set_mii(dev, fep); - /* Clear and enable interrupts */ - fecp->fec_ievent = 0xffc00000; - fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII); - /* Queue up command to detect the PHY and initialize the * remainder of the interface. */ @@ -2427,9 +2986,15 @@ fec_restart(struct net_device *dev, int duplex) fecp->fec_ecntrl = 1; udelay(10); + /* Enable interrupts we wish to service. + */ + fecp->fec_imask = FEC_ENET_TXF | FEC_ENET_TXB | FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII; + /* Clear any outstanding interrupt. - */ - fecp->fec_ievent = 0xffc00000; + * + */ + fecp->fec_ievent = FEC_ENET_MASK; + fec_enable_phy_intr(); /* Set station address. @@ -2445,12 +3010,12 @@ fec_restart(struct net_device *dev, int duplex) */ fecp->fec_r_buff_size = PKT_MAXBLR_SIZE; - fec_localhw_setup(); + fec_localhw_setup(dev); /* Set receive and transmit descriptor base. */ - fecp->fec_r_des_start = __pa((uint)(fep->rx_bd_base)); - fecp->fec_x_des_start = __pa((uint)(fep->tx_bd_base)); + fecp->fec_r_des_start = __pa((uint)(fep->cbd_mem_base)); + fecp->fec_x_des_start = __pa((uint)(fep->cbd_mem_base + RX_RING_SIZE*sizeof(cbd_t))); fep->dirty_tx = fep->cur_tx = fep->tx_bd_base; fep->cur_rx = fep->rx_bd_base; @@ -2517,11 +3082,7 @@ fec_restart(struct net_device *dev, int duplex) /* And last, enable the transmit and receive processing. */ fecp->fec_ecntrl = 2; - fecp->fec_r_des_active = 0; - - /* Enable interrupts we wish to service. - */ - fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII); + fecp->fec_r_des_active = 0x01000000; } static void @@ -2530,6 +3091,8 @@ fec_stop(struct net_device *dev) volatile fec_t *fecp; struct fec_enet_private *fep; + netif_stop_queue(dev); + fep = netdev_priv(dev); fecp = fep->hwp; @@ -2561,15 +3124,18 @@ fec_stop(struct net_device *dev) static int __init fec_enet_module_init(void) { struct net_device *dev; - int i, err; + int i, err, ret = 0; DECLARE_MAC_BUF(mac); printk("FEC ENET Version 0.2\n"); + fec_arch_init(); for (i = 0; (i < FEC_MAX_PORTS); i++) { dev = alloc_etherdev(sizeof(struct fec_enet_private)); - if (!dev) - return -ENOMEM; + if (!dev) { + ret = -ENOMEM; + goto exit; + } err = fec_enet_init(dev); if (err) { free_netdev(dev); @@ -2578,13 +3144,19 @@ static int __init fec_enet_module_init(void) if (register_netdev(dev) != 0) { /* XXX: missing cleanup here */ free_netdev(dev); - return -EIO; + ret = -EIO; + goto exit; } printk("%s: ethernet %s\n", dev->name, print_mac(mac, dev->dev_addr)); } - return 0; + +exit: + fec_arch_exit(); + return ret; + + } module_init(fec_enet_module_init); diff --git a/drivers/net/fec.h b/drivers/net/fec.h index 292719daceff..cb21ef1a8052 100644 --- a/drivers/net/fec.h +++ b/drivers/net/fec.h @@ -14,7 +14,7 @@ /****************************************************************************/ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ - defined(CONFIG_M520x) || defined(CONFIG_M532x) + defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) /* * Just figures, Motorola would have to change the offsets for * registers in the same peripheral device on different models @@ -56,6 +56,10 @@ typedef struct fec { unsigned long fec_r_des_start; /* Receive descriptor ring */ unsigned long fec_x_des_start; /* Transmit descriptor ring */ unsigned long fec_r_buff_size; /* Maximum receive buff size */ + unsigned long fec_reserved12[93]; + unsigned long fec_miigsk_cfgr; /* MIIGSK config register */ + unsigned long fec_reserved13; + unsigned long fec_miigsk_enr; /* MIIGSK enable register */ } fec_t; #else @@ -103,12 +107,22 @@ typedef struct fec { /* * Define the buffer descriptor structure. */ +/* Please see "Receive Buffer Descriptor Field Definitions" in Specification. + * It's LE. + */ +#ifdef CONFIG_ARCH_MXC +typedef struct bufdesc { + unsigned short cbd_datlen; /* Data length */ + unsigned short cbd_sc; /* Control and status info */ + unsigned long cbd_bufaddr; /* Buffer address */ +} cbd_t; +#else typedef struct bufdesc { unsigned short cbd_sc; /* Control and status info */ unsigned short cbd_datlen; /* Data length */ unsigned long cbd_bufaddr; /* Buffer address */ } cbd_t; - +#endif /* * The following definitions courtesy of commproc.h, which where diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index e6317557a531..e372f35ae038 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -342,5 +342,9 @@ config MCS_FIR To compile it as a module, choose M here: the module will be called mcs7780. +config MXC_FIR + tristate "Freescale MXC FIR driver" + depends on ARCH_MXC && IRDA + endmenu diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile index 5d20fde32a24..d32839cf9e73 100644 --- a/drivers/net/irda/Makefile +++ b/drivers/net/irda/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_VLSI_FIR) += vlsi_ir.o obj-$(CONFIG_VIA_FIR) += via-ircc.o obj-$(CONFIG_PXA_FICP) += pxaficp_ir.o obj-$(CONFIG_MCS_FIR) += mcs7780.o +obj-$(CONFIG_MXC_FIR) += mxc_ir.o obj-$(CONFIG_AU1000_FIR) += au1k_ir.o # SIR drivers obj-$(CONFIG_IRTTY_SIR) += irtty-sir.o sir-dev.o diff --git a/drivers/net/irda/mxc_ir.c b/drivers/net/irda/mxc_ir.c new file mode 100644 index 000000000000..7adcf5363078 --- /dev/null +++ b/drivers/net/irda/mxc_ir.c @@ -0,0 +1,1777 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * Based on sa1100_ir.c - Copyright 2000-2001 Russell King + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_ir.c + * + * @brief Driver for the Freescale Semiconductor MXC FIRI. + * + * This driver is based on drivers/net/irda/sa1100_ir.c, by Russell King. + * + * @ingroup FIRI + */ + +/* + * Include Files + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> + +#include <net/irda/irda.h> +#include <net/irda/wrapper.h> +#include <net/irda/irda_device.h> + +#include <asm/irq.h> +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/mxc_uart.h> +#include "mxc_ir.h" + +#define IS_SIR(mi) ( (mi)->speed <= 115200 ) +#define IS_MIR(mi) ( (mi)->speed < 4000000 && (mi)->speed >= 576000 ) +#define IS_FIR(mi) ( (mi)->speed >= 4000000 ) + +#define SDMA_START_DELAY() { \ + volatile int j,k;\ + int i;\ + for(i=0;i<10000;i++)\ + k=j;\ + } + +#define IRDA_FRAME_SIZE_LIMIT 2047 +#define UART_BUFF_SIZE 14384 + +#define UART4_UFCR_TXTL 16 +#define UART4_UFCR_RXTL 1 + +#define FIRI_SDMA_TX +#define FIRI_SDMA_RX + +/*! + * This structure is a way for the low level driver to define their own + * \b mxc_irda structure. This structure includes SK buffers, DMA buffers. + * and has other elements that are specifically required by this driver. + */ +struct mxc_irda { + /*! + * This keeps track of device is running or not + */ + unsigned char open; + + /*! + * This holds current FIRI communication speed + */ + int speed; + + /*! + * This holds FIRI communication speed for next packet + */ + int newspeed; + + /*! + * SK buffer for transmitter + */ + struct sk_buff *txskb; + + /*! + * SK buffer for receiver + */ + struct sk_buff *rxskb; + +#ifdef FIRI_SDMA_RX + /*! + * SK buffer for tasklet + */ + struct sk_buff *tskb; +#endif + + /*! + * DMA address for transmitter + */ + dma_addr_t dma_rx_buff_phy; + + /*! + * DMA address for receiver + */ + dma_addr_t dma_tx_buff_phy; + + /*! + * DMA Transmit buffer length + */ + unsigned int dma_tx_buff_len; + + /*! + * DMA channel for transmitter + */ + int txdma_ch; + + /*! + * DMA channel for receiver + */ + int rxdma_ch; + + /*! + * IrDA network device statistics + */ + struct net_device_stats stats; + + /*! + * The device structure used to get FIRI information + */ + struct device *dev; + + /*! + * Resource structure for UART, which will maintain base addresses and IRQs. + */ + struct resource *uart_res; + + /*! + * Base address of UART, used in readl and writel. + */ + void *uart_base; + + /*! + * Resource structure for FIRI, which will maintain base addresses and IRQs. + */ + struct resource *firi_res; + + /*! + * Base address of FIRI, used in readl and writel. + */ + void *firi_base; + + /*! + * UART IRQ number. + */ + int uart_irq; + + /*! + * Second UART IRQ number in case the interrupt lines are not muxed. + */ + int uart_irq1; + + /*! + * UART clock needed for baud rate calculations + */ + struct clk *uart_clk; + + /*! + * UART clock needed for baud rate calculations + */ + unsigned long uart_clk_rate; + + /*! + * FIRI clock needed for baud rate calculations + */ + struct clk *firi_clk; + + /*! + * FIRI IRQ number. + */ + int firi_irq; + + /*! + * IrLAP layer instance + */ + struct irlap_cb *irlap; + + /*! + * Driver supported baudrate capabilities + */ + struct qos_info qos; + + /*! + * Temporary transmit buffer used by the driver + */ + iobuff_t tx_buff; + + /*! + * Temporary receive buffer used by the driver + */ + iobuff_t rx_buff; + + /*! + * Pointer to platform specific data structure. + */ + struct mxc_ir_platform_data *mxc_ir_plat; + + /*! + * This holds the power management status of this module. + */ + int suspend; + +}; + +extern void gpio_firi_active(void *, unsigned int); +extern void gpio_firi_inactive(void); +extern void gpio_firi_init(void); + +void mxc_irda_firi_init(struct mxc_irda *si); +#ifdef FIRI_SDMA_RX +static void mxc_irda_fir_dma_rx_irq(void *id, int error_status, + unsigned int count); +#endif +#ifdef FIRI_SDMA_TX +static void mxc_irda_fir_dma_tx_irq(void *id, int error_status, + unsigned int count); +#endif + +/*! + * This function allocates and maps the receive buffer, + * unless it is already allocated. + * + * @param si FIRI device specific structure. + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_rx_alloc(struct mxc_irda *si) +{ +#ifdef FIRI_SDMA_RX + mxc_dma_requestbuf_t dma_request; +#endif + if (si->rxskb) { + return 0; + } + + si->rxskb = alloc_skb(IRDA_FRAME_SIZE_LIMIT + 1, GFP_ATOMIC); + + if (!si->rxskb) { + dev_err(si->dev, "mxc_ir: out of memory for RX SKB\n"); + return -ENOMEM; + } + + /* + * Align any IP headers that may be contained + * within the frame. + */ + skb_reserve(si->rxskb, 1); + +#ifdef FIRI_SDMA_RX + si->dma_rx_buff_phy = + dma_map_single(si->dev, si->rxskb->data, IRDA_FRAME_SIZE_LIMIT, + DMA_FROM_DEVICE); + + dma_request.num_of_bytes = IRDA_FRAME_SIZE_LIMIT; + dma_request.dst_addr = si->dma_rx_buff_phy; + dma_request.src_addr = si->firi_res->start; + + mxc_dma_config(si->rxdma_ch, &dma_request, 1, MXC_DMA_MODE_READ); +#endif + return 0; +} + +/*! + * This function is called to disable the FIRI dma + * + * @param si FIRI port specific structure. + */ +static void mxc_irda_disabledma(struct mxc_irda *si) +{ + /* Stop all DMA activity. */ +#ifdef FIRI_SDMA_TX + mxc_dma_disable(si->txdma_ch); +#endif +#ifdef FIRI_SDMA_RX + mxc_dma_disable(si->rxdma_ch); +#endif +} + +/*! + * This function is called to set the IrDA communications speed. + * + * @param si FIRI specific structure. + * @param speed new Speed to be configured for. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_set_speed(struct mxc_irda *si, int speed) +{ + unsigned long flags; + int ret = 0; + unsigned int num, denom, baud; + unsigned int cr; + + dev_dbg(si->dev, "speed:%d\n", speed); + switch (speed) { + case 9600: + case 19200: + case 38400: + case 57600: + case 115200: + dev_dbg(si->dev, "starting SIR\n"); + baud = speed; + if (IS_FIR(si)) { +#ifdef FIRI_SDMA_RX + mxc_dma_disable(si->rxdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + } + local_irq_save(flags); + + /* Disable Tx and Rx */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + gpio_firi_inactive(); + + num = baud / 100 - 1; + denom = si->uart_clk_rate / 1600 - 1; + if ((denom < 65536) && (si->uart_clk_rate > 1600)) { + writel(num, si->uart_base + MXC_UARTUBIR); + writel(denom, si->uart_base + MXC_UARTUBMR); + } + + si->speed = speed; + + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + + /* Enable Receive Overrun and Data Ready interrupts. */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr |= (MXC_UARTUCR4_OREN | MXC_UARTUCR4_DREN); + writel(cr, si->uart_base + MXC_UARTUCR4); + + cr = readl(si->uart_base + MXC_UARTUCR2); + cr |= (MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + local_irq_restore(flags); + break; + case 4000000: + local_irq_save(flags); + + /* Disable Receive Overrun and Data Ready interrupts. */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr &= ~(MXC_UARTUCR4_OREN | MXC_UARTUCR4_DREN); + writel(cr, si->uart_base + MXC_UARTUCR4); + + /* Disable Tx and Rx */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + /* + * FIR configuration + */ + mxc_irda_disabledma(si); + + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + + gpio_firi_active(si->firi_base + FIRITCR, FIRITCR_TPP); + + si->speed = speed; + + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + dev_dbg(si->dev, "Going for fast IRDA ...\n"); + ret = mxc_irda_rx_alloc(si); + + /* clear RX status register */ + writel(0xFFFF, si->firi_base + FIRIRSR); +#ifdef FIRI_SDMA_RX + if (si->rxskb) { + mxc_dma_enable(si->rxdma_ch); + } +#endif + local_irq_restore(flags); + + break; + default: + dev_err(si->dev, "speed not supported by FIRI\n"); + break; + } + + return ret; +} + +/*! + * This function is called to set the IrDA communications speed. + * + * @param si FIRI specific structure. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static inline int mxc_irda_fir_error(struct mxc_irda *si) +{ + struct sk_buff *skb = si->rxskb; + unsigned int dd_error, crc_error, overrun_error; + unsigned int sr; + + if (!skb) { + dev_err(si->dev, "no skb!\n"); + return -1; + } + + sr = readl(si->firi_base + FIRIRSR); + dd_error = sr & FIRIRSR_DDE; + crc_error = sr & FIRIRSR_CRCE; + overrun_error = sr & FIRIRSR_RFO; + + if (!(dd_error | crc_error | overrun_error)) { + return 0; + } + dev_err(si->dev, "dde,crce,rfo=%d,%d,%d.\n", dd_error, crc_error, + overrun_error); + si->stats.rx_errors++; + if (crc_error) { + si->stats.rx_crc_errors++; + } + if (dd_error) { + si->stats.rx_frame_errors++; + } + if (overrun_error) { + si->stats.rx_frame_errors++; + } + writel(sr, si->firi_base + FIRIRSR); + + return -1; +} + +#ifndef FIRI_SDMA_RX +/*! + * FIR interrupt service routine to handle receive. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq_rx(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->rxskb; + unsigned int sr, len; + int i; + unsigned char *p = skb->data; + + /* + * Deal with any receive errors. + */ + if (mxc_irda_fir_error(si) != 0) { + return; + } + + sr = readl(si->firi_base + FIRIRSR); + + if (!(sr & FIRIRSR_RPE)) { + return; + } + + /* + * Coming here indicates that fir rx packet has been successfully recieved. + * And No error happened so far. + */ + writel(sr | FIRIRSR_RPE, si->firi_base + FIRIRSR); + + len = (sr & FIRIRSR_RFP) >> 8; + + /* 4 bytes of CRC */ + len -= 4; + + skb_put(skb, len); + + for (i = 0; i < len; i++) { + *p++ = readb(si->firi_base + FIRIRXFIFO); + } + + /* Discard the four CRC bytes */ + for (i = 0; i < 4; i++) { + readb(si->firi_base + FIRIRXFIFO); + } + + /* + * Deal with the case of packet complete. + */ + skb->dev = dev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + si->stats.rx_packets++; + si->stats.rx_bytes += len; + netif_rx(skb); + + si->rxskb = NULL; + mxc_irda_rx_alloc(si); + + writel(0xFFFF, si->firi_base + FIRIRSR); + +} +#endif + +/*! + * FIR interrupt service routine to handle transmit. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq_tx(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->txskb; + unsigned int cr, sr; + + sr = readl(si->firi_base + FIRITSR); + writel(sr, si->firi_base + FIRITSR); + + if (sr & FIRITSR_TC) { + +#ifdef FIRI_SDMA_TX + mxc_dma_disable(si->txdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr &= ~(FIRITCR_TCIE | FIRITCR_TE); + writel(cr, si->firi_base + FIRITCR); + + if (si->newspeed) { + mxc_irda_set_speed(si, si->newspeed); + si->newspeed = 0; + } + si->txskb = NULL; + + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + writel(0xFFFF, si->firi_base + FIRIRSR); + /* + * Account and free the packet. + */ + if (skb) { +#ifdef FIRI_SDMA_TX + dma_unmap_single(si->dev, si->dma_tx_buff_phy, skb->len, + DMA_TO_DEVICE); +#endif + si->stats.tx_packets++; + si->stats.tx_bytes += skb->len; + dev_kfree_skb_irq(skb); + } + /* + * Make sure that the TX queue is available for sending + * (for retries). TX has priority over RX at all times. + */ + netif_wake_queue(dev); + } +} + +/*! + * This is FIRI interrupt handler. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + unsigned int sr1, sr2; + + sr1 = readl(si->firi_base + FIRIRSR); + sr2 = readl(si->firi_base + FIRITSR); + + if (sr2 & FIRITSR_TC) + mxc_irda_fir_irq_tx(dev); +#ifndef FIRI_SDMA_RX + if (sr1 & (FIRIRSR_RPE | FIRIRSR_RFO)) + mxc_irda_fir_irq_rx(dev); +#endif + +} + +/*! + * This is the SIR transmit routine. + * + * @param si FIRI specific structure. + * + * @param dev pointer to the net_device structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_sir_txirq(struct mxc_irda *si, struct net_device *dev) +{ + unsigned int sr1, sr2, cr; + unsigned int status; + + sr1 = readl(si->uart_base + MXC_UARTUSR1); + sr2 = readl(si->uart_base + MXC_UARTUSR2); + cr = readl(si->uart_base + MXC_UARTUCR2); + + /* + * Echo cancellation for IRDA Transmit chars + * Disable the receiver and enable Transmit complete. + */ + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, si->uart_base + MXC_UARTUCR2); + cr = readl(si->uart_base + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, si->uart_base + MXC_UARTUCR4); + + while ((sr1 & MXC_UARTUSR1_TRDY) && si->tx_buff.len) { + + writel(*si->tx_buff.data++, si->uart_base + MXC_UARTUTXD); + si->tx_buff.len -= 1; + sr1 = readl(si->uart_base + MXC_UARTUSR1); + } + + if (si->tx_buff.len == 0) { + si->stats.tx_packets++; + si->stats.tx_bytes += si->tx_buff.data - si->tx_buff.head; + + /*Yoohoo...we are done...Lets stop Tx */ + cr = readl(si->uart_base + MXC_UARTUCR1); + cr &= ~MXC_UARTUCR1_TRDYEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + + do { + status = readl(si->uart_base + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + if (si->newspeed) { + mxc_irda_set_speed(si, si->newspeed); + si->newspeed = 0; + } + /* I'm hungry! */ + netif_wake_queue(dev); + + /* Is the transmit complete to reenable the receiver? */ + if (status & MXC_UARTUSR2_TXDC) { + + cr = readl(si->uart_base + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, si->uart_base + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, si->uart_base + MXC_UARTUCR4); + } + } + + return 0; +} + +/*! + * This is the SIR receive routine. + * + * @param si FIRI specific structure. + * + * @param dev pointer to the net_device structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_sir_rxirq(struct mxc_irda *si, struct net_device *dev) +{ + unsigned int data, status; + volatile unsigned int sr2; + + sr2 = readl(si->uart_base + MXC_UARTUSR2); + while ((sr2 & MXC_UARTUSR2_RDR) == 1) { + data = readl(si->uart_base + MXC_UARTURXD); + status = data & 0xf400; + if (status & MXC_UARTURXD_ERR) { + dev_err(si->dev, "Receive an incorrect data =0x%x.\n", + data); + si->stats.rx_errors++; + if (status & MXC_UARTURXD_OVRRUN) { + si->stats.rx_fifo_errors++; + dev_err(si->dev, "Rx overrun.\n"); + } + if (status & MXC_UARTURXD_FRMERR) { + si->stats.rx_frame_errors++; + dev_err(si->dev, "Rx frame error.\n"); + } + if (status & MXC_UARTURXD_PRERR) { + dev_err(si->dev, "Rx parity error.\n"); + } + /* Other: it is the Break char. + * Do nothing for it. throw out the data. + */ + async_unwrap_char(dev, &si->stats, &si->rx_buff, + (data & 0xFF)); + } else { + /* It is correct data. */ + data &= 0xFF; + async_unwrap_char(dev, &si->stats, &si->rx_buff, data); + + dev->last_rx = jiffies; + } + sr2 = readl(si->uart_base + MXC_UARTUSR2); + + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + } /*while */ + return 0; + +} + +static irqreturn_t mxc_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct mxc_irda *si = dev->priv; + + if (IS_FIR(si)) { + mxc_irda_fir_irq(dev); + return IRQ_HANDLED; + } + + if (readl(si->uart_base + MXC_UARTUCR2) & MXC_UARTUCR2_RXEN) { + mxc_irda_sir_rxirq(si, dev); + } + if ((readl(si->uart_base + MXC_UARTUCR1) & MXC_UARTUCR1_TRDYEN) && + (readl(si->uart_base + MXC_UARTUSR1) & MXC_UARTUSR1_TRDY)) { + mxc_irda_sir_txirq(si, dev); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_irda_tx_irq(int irq, void *dev_id) +{ + + struct net_device *dev = dev_id; + struct mxc_irda *si = dev->priv; + + mxc_irda_sir_txirq(si, dev); + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_irda_rx_irq(int irq, void *dev_id) +{ + + struct net_device *dev = dev_id; + struct mxc_irda *si = dev->priv; + + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, si->uart_base + MXC_UARTUSR1); + + mxc_irda_sir_rxirq(si, dev); + + return IRQ_HANDLED; +} + +#ifdef FIRI_SDMA_RX +struct tasklet_struct dma_rx_tasklet; + +static void mxc_irda_rx_task(unsigned long tparam) +{ + struct mxc_irda *si = (struct mxc_irda *)tparam; + struct sk_buff *lskb = si->tskb; + + si->tskb = NULL; + if (lskb) { + lskb->mac_header = lskb->data; + lskb->protocol = htons(ETH_P_IRDA); + netif_rx(lskb); + } +} + +/*! + * Receiver DMA callback routine. + * + * @param id pointer to network device structure + * @param error_status used to pass error status to this callback function + * @param count number of bytes received + */ +static void mxc_irda_fir_dma_rx_irq(void *id, int error_status, + unsigned int count) +{ + struct net_device *dev = id; + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->rxskb; + unsigned int cr; + unsigned int len; + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + len = count - 4; /* remove 4 bytes for CRC */ + skb_put(skb, len); + skb->dev = dev; + si->tskb = skb; + tasklet_schedule(&dma_rx_tasklet); + + if (si->dma_rx_buff_phy != 0) + dma_unmap_single(si->dev, si->dma_rx_buff_phy, + IRDA_FRAME_SIZE_LIMIT, DMA_FROM_DEVICE); + + si->rxskb = NULL; + mxc_irda_rx_alloc(si); + + SDMA_START_DELAY(); + writel(0xFFFF, si->firi_base + FIRIRSR); + + if (si->rxskb) { + mxc_dma_enable(si->rxdma_ch); + } +} +#endif + +#ifdef FIRI_SDMA_TX +/*! + * This function is called by SDMA Interrupt Service Routine to indicate + * requested DMA transfer is completed. + * + * @param id pointer to network device structure + * @param error_status used to pass error status to this callback function + * @param count number of bytes sent + */ +static void mxc_irda_fir_dma_tx_irq(void *id, int error_status, + unsigned int count) +{ + struct net_device *dev = id; + struct mxc_irda *si = dev->priv; + + mxc_dma_disable(si->txdma_ch); +} +#endif + +/*! + * This function is called by Linux IrDA network subsystem to + * transmit the Infrared data packet. The TX DMA channel is configured + * to transfer SK buffer data to FIRI TX FIFO along with DMA transfer + * completion routine. + * + * @param skb The packet that is queued to be sent + * @param dev net_device structure. + * + * @return The function returns 0 on success and a negative value on + * failure. + */ +static int mxc_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + int speed = irda_get_next_speed(skb); + unsigned int cr; + + /* + * Does this packet contain a request to change the interface + * speed? If so, remember it until we complete the transmission + * of this frame. + */ + if (speed != si->speed && speed != -1) { + si->newspeed = speed; + } + + /* If this is an empty frame, we can bypass a lot. */ + if (skb->len == 0) { + if (si->newspeed) { + si->newspeed = 0; + mxc_irda_set_speed(si, speed); + } + dev_kfree_skb(skb); + return 0; + } + + /* We must not be transmitting... */ + netif_stop_queue(dev); + if (IS_SIR(si)) { + + si->tx_buff.data = si->tx_buff.head; + si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, + si->tx_buff.truesize); + cr = readl(si->uart_base + MXC_UARTUCR1); + cr |= MXC_UARTUCR1_TRDYEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + dev_kfree_skb(skb); + } else { + unsigned int mtt = irda_get_mtt(skb); + unsigned char *p = skb->data; + unsigned int skb_len = skb->len; +#ifdef FIRI_SDMA_TX + mxc_dma_requestbuf_t dma_request; +#else + unsigned int i, sr; +#endif + + skb_len = skb_len + ((4 - (skb_len % 4)) % 4); + + if (si->txskb) { + BUG(); + } + si->txskb = skb; + + /* + * If we have a mean turn-around time, impose the specified + * specified delay. We could shorten this by timing from + * the point we received the packet. + */ + if (mtt) { + udelay(mtt); + } + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + writel(skb->len - 1, si->firi_base + FIRITCTR); + +#ifdef FIRI_SDMA_TX + /* + * Configure DMA Tx Channel for source and destination addresses, + * Number of bytes in SK buffer to transfer and Transfer complete + * callback function. + */ + si->dma_tx_buff_len = skb_len; + si->dma_tx_buff_phy = + dma_map_single(si->dev, p, skb_len, DMA_TO_DEVICE); + + dma_request.num_of_bytes = skb_len; + dma_request.dst_addr = si->firi_res->start + FIRITXFIFO; + dma_request.src_addr = si->dma_tx_buff_phy; + + mxc_dma_config(si->txdma_ch, &dma_request, 1, + MXC_DMA_MODE_WRITE); + + mxc_dma_enable(si->txdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr |= FIRITCR_TCIE; + writel(cr, si->firi_base + FIRITCR); + + cr |= FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + +#ifndef FIRI_SDMA_TX + for (i = 0; i < skb->len;) { + sr = readl(si->firi_base + FIRITSR); + /* TFP = number of bytes in the TX FIFO for the + * Transmitter + * */ + if ((sr >> 8) < 128) { + writeb(*p, si->firi_base + FIRITXFIFO); + p++; + i++; + } + } +#endif + } + + dev->trans_start = jiffies; + return 0; +} + +/*! + * This function handles network interface ioctls passed to this driver.. + * + * @param dev net device structure + * @param ifreq user request data + * @param cmd command issued + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) +{ + struct if_irda_req *rq = (struct if_irda_req *)ifreq; + struct mxc_irda *si = dev->priv; + int ret = -EOPNOTSUPP; + + switch (cmd) { + /* This function will be used by IrLAP to change the speed */ + case SIOCSBANDWIDTH: + dev_dbg(si->dev, "%s:with cmd SIOCSBANDWIDTH\n", __FUNCTION__); + if (capable(CAP_NET_ADMIN)) { + /* + * We are unable to set the speed if the + * device is not running. + */ + if (si->open) { + ret = mxc_irda_set_speed(si, rq->ifr_baudrate); + } else { + dev_err(si->dev, "mxc_ir_ioctl: SIOCSBANDWIDTH:\ + !netif_running\n"); + ret = 0; + } + } + break; + case SIOCSMEDIABUSY: + dev_dbg(si->dev, "%s:with cmd SIOCSMEDIABUSY\n", __FUNCTION__); + ret = -EPERM; + if (capable(CAP_NET_ADMIN)) { + irda_device_set_media_busy(dev, TRUE); + ret = 0; + } + break; + case SIOCGRECEIVING: + rq->ifr_receiving = + IS_SIR(si) ? si->rx_buff.state != OUTSIDE_FRAME : 0; + ret = 0; + break; + default: + break; + } + return ret; +} + +/*! + * Kernel interface routine to get current statistics of the device + * which includes the number bytes/packets transmitted/received, + * receive errors, CRC errors, framing errors etc. + * + * @param dev the net_device structure + * + * @return This function returns IrDA network statistics + */ +static struct net_device_stats *mxc_irda_stats(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + return &si->stats; +} + +/*! + * FIRI init function + * + * @param si FIRI device specific structure. + */ +void mxc_irda_firi_init(struct mxc_irda *si) +{ + unsigned int firi_baud, osf = 6; + unsigned int tcr, rcr, cr; + + si->firi_clk = clk_get(si->dev, "firi_clk"); + firi_baud = clk_round_rate(si->firi_clk, 48004500); + if ((firi_baud < 47995500) || + (clk_set_rate(si->firi_clk, firi_baud) < 0)) { + dev_err(si->dev, "Unable to set FIR clock to 48MHz.\n"); + return; + } + clk_enable(si->firi_clk); + + writel(0xFFFF, si->firi_base + FIRITSR); + writel(0xFFFF, si->firi_base + FIRIRSR); + writel(0x00, si->firi_base + FIRITCR); + writel(0x00, si->firi_base + FIRIRCR); + + /* set _BL & _OSF */ + cr = (osf - 1) | (16 << 5); + writel(cr, si->firi_base + FIRICR); + +#ifdef FIRI_SDMA_TX + tcr = + FIRITCR_TDT_FIR | FIRITCR_TM_FIR | FIRITCR_TCIE | + FIRITCR_PCF | FIRITCR_PC; +#else + tcr = FIRITCR_TM_FIR | FIRITCR_TCIE | FIRITCR_PCF | FIRITCR_PC; +#endif + +#ifdef FIRI_SDMA_RX + rcr = + FIRIRCR_RPEDE | FIRIRCR_RM_FIR | FIRIRCR_RDT_FIR | + FIRIRCR_RPA | FIRIRCR_RPP; +#else + rcr = + FIRIRCR_RPEDE | FIRIRCR_RM_FIR | FIRIRCR_RDT_FIR | FIRIRCR_RPEIE | + FIRIRCR_RPA | FIRIRCR_PAIE | FIRIRCR_RFOIE | FIRIRCR_RPP; +#endif + + writel(tcr, si->firi_base + FIRITCR); + writel(rcr, si->firi_base + FIRIRCR); + cr = 0; + writel(cr, si->firi_base + FIRITCTR); +} + +/*! + * This function initialises the UART. + * + * @param si FIRI port specific structure. + * + * @return The function returns 0 on success. + */ +static int mxc_irda_uart_init(struct mxc_irda *si) +{ + unsigned int per_clk; + unsigned int num, denom, baud, ufcr = 0; + unsigned int cr; + int d = 1; + int uart_ir_mux = 0; + + if (si->mxc_ir_plat) + uart_ir_mux = si->mxc_ir_plat->uart_ir_mux; + /* + * Clear Status Registers 1 and 2 + **/ + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + + /* Configure the IOMUX for the UART */ + gpio_firi_init(); + + per_clk = clk_get_rate(si->uart_clk); + baud = per_clk / 16; + if (baud > 1500000) { + baud = 1500000; + d = per_clk / ((baud * 16) + 1000); + if (d > 6) { + d = 6; + } + } + clk_enable(si->uart_clk); + + si->uart_clk_rate = per_clk / d; + writel(si->uart_clk_rate / 1000, si->uart_base + MXC_UARTONEMS); + + writel(si->mxc_ir_plat->ir_rx_invert | MXC_UARTUCR4_IRSC, + si->uart_base + MXC_UARTUCR4); + + if (uart_ir_mux) { + writel(MXC_UARTUCR3_RXDMUXSEL | si->mxc_ir_plat->ir_tx_invert | + MXC_UARTUCR3_DSR, si->uart_base + MXC_UARTUCR3); + } else { + writel(si->mxc_ir_plat->ir_tx_invert | MXC_UARTUCR3_DSR, + si->uart_base + MXC_UARTUCR3); + } + + writel(MXC_UARTUCR2_IRTS | MXC_UARTUCR2_CTS | MXC_UARTUCR2_WS | + MXC_UARTUCR2_ATEN | MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN, + si->uart_base + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(si->uart_base + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + ufcr |= (UART4_UFCR_TXTL << MXC_UARTUFCR_TXTL_OFFSET) | + ((6 - d) << MXC_UARTUFCR_RFDIV_OFFSET) | UART4_UFCR_RXTL; + writel(ufcr, si->uart_base + MXC_UARTUFCR); + + writel(MXC_UARTUCR1_UARTEN | MXC_UARTUCR1_IREN, + si->uart_base + MXC_UARTUCR1); + + baud = 9600; + num = baud / 100 - 1; + denom = si->uart_clk_rate / 1600 - 1; + + if ((denom < 65536) && (si->uart_clk_rate > 1600)) { + writel(num, si->uart_base + MXC_UARTUBIR); + writel(denom, si->uart_base + MXC_UARTUBMR); + } + + writel(0x0000, si->uart_base + MXC_UARTUTS); + return 0; + +} + +/*! + * This function enables FIRI port. + * + * @param si FIRI port specific structure. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_startup(struct mxc_irda *si) +{ + int ret = 0; + + mxc_irda_uart_init(si); + mxc_irda_firi_init(si); + + /* configure FIRI device for speed */ + ret = mxc_irda_set_speed(si, si->speed = 9600); + + return ret; +} + +/*! + * When an ifconfig is issued which changes the device flag to include + * IFF_UP this function is called. It is only called when the change + * occurs, not when the interface remains up. The function grabs the interrupt + * resources and registers FIRI interrupt service routines, requests for DMA + * channels, configures the DMA channel. It then initializes the IOMUX + * registers to configure the pins for FIRI signals and finally initializes the + * various FIRI registers and enables the port for reception. + * + * @param dev net device structure that is being opened + * + * @return The function returns 0 for a successful open and non-zero value + * on failure. + */ +static int mxc_irda_start(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + int err; + int ints_muxed = 0; + mxc_dma_device_t dev_id = 0; + + if (si->uart_irq == si->uart_irq1) + ints_muxed = 1; + + si->speed = 9600; + + if (si->uart_irq == si->firi_irq) { + err = + request_irq(si->uart_irq, mxc_irda_irq, 0, dev->name, dev); + if (err) { + dev_err(si->dev, "%s:Failed to request the IRQ\n", + __FUNCTION__); + return err; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->uart_irq); + } else { + err = + request_irq(si->firi_irq, mxc_irda_irq, 0, dev->name, dev); + if (err) { + dev_err(si->dev, "%s:Failed to request FIRI IRQ\n", + __FUNCTION__); + return err; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->firi_irq); + if (ints_muxed) { + + err = request_irq(si->uart_irq, mxc_irda_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART IRQ\n", + __FUNCTION__); + goto err_irq1; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->uart_irq); + } else { + err = request_irq(si->uart_irq, mxc_irda_tx_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART IRQ\n", + __FUNCTION__); + goto err_irq1; + } + err = request_irq(si->uart_irq1, mxc_irda_rx_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART1 IRQ\n", + __FUNCTION__); + goto err_irq2; + } + /* + * The interrupts must remain disabled for now. + */ + disable_irq(si->uart_irq); + disable_irq(si->uart_irq1); + } + } +#ifdef FIRI_SDMA_RX + dev_id = MXC_DMA_FIR_RX; + si->rxdma_ch = mxc_dma_request(dev_id, "MXC FIRI RX"); + if (si->rxdma_ch < 0) { + dev_err(si->dev, "Cannot allocate FIR DMA channel\n"); + goto err_rx_dma; + } + mxc_dma_callback_set(si->rxdma_ch, mxc_irda_fir_dma_rx_irq, + (void *)dev_get_drvdata(si->dev)); +#endif +#ifdef FIRI_SDMA_TX + + dev_id = MXC_DMA_FIR_TX; + si->txdma_ch = mxc_dma_request(dev_id, "MXC FIRI TX"); + if (si->txdma_ch < 0) { + dev_err(si->dev, "Cannot allocate FIR DMA channel\n"); + goto err_tx_dma; + } + mxc_dma_callback_set(si->txdma_ch, mxc_irda_fir_dma_tx_irq, + (void *)dev_get_drvdata(si->dev)); +#endif + /* Setup the serial port port for the initial speed. */ + err = mxc_irda_startup(si); + if (err) { + goto err_startup; + } + + /* Open a new IrLAP layer instance. */ + si->irlap = irlap_open(dev, &si->qos, "mxc"); + err = -ENOMEM; + if (!si->irlap) { + goto err_irlap; + } + + /* Now enable the interrupt and start the queue */ + si->open = 1; + si->suspend = 0; + + if (si->uart_irq == si->firi_irq) { + enable_irq(si->uart_irq); + } else { + enable_irq(si->firi_irq); + if (ints_muxed == 1) { + enable_irq(si->uart_irq); + } else { + enable_irq(si->uart_irq); + enable_irq(si->uart_irq1); + } + } + + netif_start_queue(dev); + return 0; + + err_irlap: + si->open = 0; + mxc_irda_disabledma(si); + err_startup: +#ifdef FIRI_SDMA_TX + mxc_dma_free(si->txdma_ch); + err_tx_dma: +#endif +#ifdef FIRI_SDMA_RX + mxc_dma_free(si->rxdma_ch); + err_rx_dma: +#endif + if (si->uart_irq1 && !ints_muxed) + free_irq(si->uart_irq1, dev); + err_irq2: + if (si->uart_irq != si->firi_irq) + free_irq(si->uart_irq, dev); + err_irq1: + if (si->firi_irq) + free_irq(si->firi_irq, dev); + return err; +} + +/*! + * This function is called when IFF_UP flag has been cleared by the user via + * the ifconfig irda0 down command. This function stops any further + * transmissions being queued, and then disables the interrupts. + * Finally it resets the device. + * @param dev the net_device structure + * + * @return int the function always returns 0 indicating a success. + */ +static int mxc_irda_stop(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + unsigned long flags; + + /* Stop IrLAP */ + if (si->irlap) { + irlap_close(si->irlap); + si->irlap = NULL; + } + + netif_stop_queue(dev); + + /*Save flags and disable the FIRI interrupts.. */ + if (si->open) { + local_irq_save(flags); + disable_irq(si->uart_irq); + free_irq(si->uart_irq, dev); + if (si->uart_irq != si->firi_irq) { + disable_irq(si->firi_irq); + free_irq(si->firi_irq, dev); + if (si->uart_irq1 != si->uart_irq) { + disable_irq(si->uart_irq1); + free_irq(si->uart_irq1, dev); + } + } + local_irq_restore(flags); + si->open = 0; + } +#ifdef FIRI_SDMA_RX + if (si->rxdma_ch) { + mxc_dma_disable(si->rxdma_ch); + mxc_dma_free(si->rxdma_ch); + if (si->dma_rx_buff_phy) { + dma_unmap_single(si->dev, si->dma_rx_buff_phy, + IRDA_FRAME_SIZE_LIMIT, + DMA_FROM_DEVICE); + si->dma_rx_buff_phy = 0; + } + si->rxdma_ch = 0; + } + tasklet_kill(&dma_rx_tasklet); +#endif +#ifdef FIRI_SDMA_TX + if (si->txdma_ch) { + mxc_dma_disable(si->txdma_ch); + mxc_dma_free(si->txdma_ch); + if (si->dma_tx_buff_phy) { + dma_unmap_single(si->dev, si->dma_tx_buff_phy, + si->dma_tx_buff_len, DMA_TO_DEVICE); + si->dma_tx_buff_phy = 0; + } + si->txdma_ch = 0; + } +#endif + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the FIRI in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which FIRI + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(ndev); + unsigned int cr; + unsigned long flags; + + if (!si) { + return 0; + } + if (si->suspend == 1) { + dev_err(si->dev, + " suspend - Device is already suspended ... \n"); + return 0; + } + if (si->open) { + + netif_device_detach(ndev); + mxc_irda_disabledma(si); + + /*Save flags and disable the FIRI interrupts.. */ + local_irq_save(flags); + disable_irq(si->uart_irq); + if (si->uart_irq != si->firi_irq) { + disable_irq(si->firi_irq); + if (si->uart_irq != si->uart_irq1) { + disable_irq(si->uart_irq1); + } + } + local_irq_restore(flags); + + /* Disable Tx and Rx and then disable the UART clock */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + cr = readl(si->uart_base + MXC_UARTUCR1); + cr &= ~MXC_UARTUCR1_UARTEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + clk_disable(si->uart_clk); + + /*Disable Tx and Rx for FIRI and then disable the FIRI clock.. */ + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + clk_disable(si->firi_clk); + + gpio_firi_inactive(); + + si->suspend = 1; + si->open = 0; + } + return 0; +} + +/*! + * This function is called to bring the FIRI back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which FIRI + * to resume + * + * @return The function always returns 0. + */ +static int mxc_irda_resume(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(ndev); + unsigned long flags; + + if (!si) { + return 0; + } + + if (si->suspend == 1 && !si->open) { + + /*Initialise the UART first */ + clk_enable(si->uart_clk); + + /*Now init FIRI */ + gpio_firi_active(si->firi_base + FIRITCR, FIRITCR_TPP); + mxc_irda_startup(si); + + /* Enable the UART and FIRI interrupts.. */ + local_irq_save(flags); + enable_irq(si->uart_irq); + if (si->uart_irq != si->firi_irq) { + enable_irq(si->firi_irq); + if (si->uart_irq != si->uart_irq1) { + enable_irq(si->uart_irq1); + } + } + local_irq_restore(flags); + + /* Let the kernel know that we are alive and kicking.. */ + netif_device_attach(ndev); + + si->suspend = 0; + si->open = 1; + } + return 0; +} +#else +#define mxc_irda_suspend NULL +#define mxc_irda_resume NULL +#endif + +static int mxc_irda_init_iobuf(iobuff_t * io, int size) +{ + io->head = kmalloc(size, GFP_KERNEL | GFP_DMA); + if (io->head != NULL) { + io->truesize = size; + io->in_frame = FALSE; + io->state = OUTSIDE_FRAME; + io->data = io->head; + } + return io->head ? 0 : -ENOMEM; + +} + +/*! + * This function is called during the driver binding process. + * This function requests for memory, initializes net_device structure and + * registers with kernel. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 on success and a non-zero value on failure + */ +static int mxc_irda_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct mxc_irda *si; + struct resource *uart_res, *firi_res; + int uart_irq, firi_irq, uart_irq1; + unsigned int baudrate_mask = 0; + int err; + + uart_res = &pdev->resource[0]; + uart_irq = pdev->resource[1].start; + + firi_res = &pdev->resource[2]; + firi_irq = pdev->resource[3].start; + + uart_irq1 = pdev->resource[4].start; + + if (!uart_res || uart_irq == NO_IRQ || !firi_res || firi_irq == NO_IRQ) { + dev_err(&pdev->dev, "Unable to find resources\n"); + return -ENXIO; + } + + err = + request_mem_region(uart_res->start, SZ_16K, + "MXC_IRDA") ? 0 : -EBUSY; + if (err) { + dev_err(&pdev->dev, "Failed to request UART memory region\n"); + return -ENOMEM; + } + + err = + request_mem_region(firi_res->start, SZ_16K, + "MXC_IRDA") ? 0 : -EBUSY; + if (err) { + dev_err(&pdev->dev, "Failed to request FIRI memory region\n"); + goto err_mem_1; + } + + dev = alloc_irdadev(sizeof(struct mxc_irda)); + if (!dev) { + goto err_mem_2; + } + + si = netdev_priv(dev); + si->dev = &pdev->dev; + + si->mxc_ir_plat = pdev->dev.platform_data; + si->uart_clk = si->mxc_ir_plat->uart_clk; + + si->uart_res = uart_res; + si->firi_res = firi_res; + si->uart_irq = uart_irq; + si->firi_irq = firi_irq; + si->uart_irq1 = uart_irq1; + + si->uart_base = ioremap(uart_res->start, SZ_16K); + si->firi_base = ioremap(firi_res->start, SZ_16K); + + if (!(si->uart_base || si->firi_base)) { + err = -ENOMEM; + goto err_mem_3; + } + + /* + * Initialise the SIR buffers + */ + err = mxc_irda_init_iobuf(&si->rx_buff, UART_BUFF_SIZE); + if (err) { + goto err_mem_4; + } + + err = mxc_irda_init_iobuf(&si->tx_buff, UART_BUFF_SIZE); + if (err) { + goto err_mem_5; + } + + dev->hard_start_xmit = mxc_irda_hard_xmit; + dev->open = mxc_irda_start; + dev->stop = mxc_irda_stop; + dev->do_ioctl = mxc_irda_ioctl; + dev->get_stats = mxc_irda_stats; + + irda_init_max_qos_capabilies(&si->qos); + + /* + * We support + * SIR(9600, 19200,38400, 57600 and 115200 bps) + * FIR(4 Mbps) + * Min Turn Time set to 1ms or greater. + */ + baudrate_mask |= IR_9600 | IR_19200 | IR_38400 | IR_57600 | IR_115200; + baudrate_mask |= IR_4000000 << 8; + + si->qos.baud_rate.bits &= baudrate_mask; + si->qos.min_turn_time.bits = 0x7; + + irda_qos_bits_to_value(&si->qos); + +#ifdef FIRI_SDMA_RX + si->tskb = NULL; + tasklet_init(&dma_rx_tasklet, mxc_irda_rx_task, (unsigned long)si); +#endif + err = register_netdev(dev); + if (err == 0) { + platform_set_drvdata(pdev, dev); + } else { + kfree(si->tx_buff.head); + err_mem_5: + kfree(si->rx_buff.head); + err_mem_4: + iounmap(si->uart_base); + iounmap(si->firi_base); + err_mem_3: + free_netdev(dev); + err_mem_2: + release_mem_region(firi_res->start, SZ_16K); + err_mem_1: + release_mem_region(uart_res->start, SZ_16K); + } + return err; +} + +/*! + * Dissociates the driver from the FIRI device. Removes the appropriate FIRI + * port structure from the kernel. + * + * @param pdev the device structure used to give information on which FIRI + * to remove + * + * @return The function always returns 0. + */ +static int mxc_irda_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(dev); + + if (si->uart_base) + iounmap(si->uart_base); + if (si->firi_base) + iounmap(si->firi_base); + if (si->firi_res->start) + release_mem_region(si->firi_res->start, SZ_16K); + if (si->uart_res->start) + release_mem_region(si->uart_res->start, SZ_16K); + if (si->tx_buff.head) + kfree(si->tx_buff.head); + if (si->rx_buff.head) + kfree(si->rx_buff.head); + + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_netdev(dev); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcir_driver = { + .driver = { + .name = "mxcir", + }, + .probe = mxc_irda_probe, + .remove = mxc_irda_remove, + .suspend = mxc_irda_suspend, + .resume = mxc_irda_resume, +}; + +/*! + * This function is used to initialize the FIRI driver module. The function + * registers the power management callback functions with the kernel and also + * registers the FIRI callback functions. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxc_irda_init(void) +{ + return platform_driver_register(&mxcir_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxc_irda_exit(void) +{ + platform_driver_unregister(&mxcir_driver); +} + +module_init(mxc_irda_init); +module_exit(mxc_irda_exit); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("MXC IrDA(SIR/FIR) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/irda/mxc_ir.h b/drivers/net/irda/mxc_ir.h new file mode 100644 index 000000000000..6b22ca129f27 --- /dev/null +++ b/drivers/net/irda/mxc_ir.h @@ -0,0 +1,133 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_FIRI_REG_H__ +#define __MXC_FIRI_REG_H__ + +#include <mach/hardware.h> + +/*! + * @defgroup FIRI Fast IR Driver + */ + +/*! + * @file mxc_ir.h + * + * @brief MXC FIRI header file + * + * This file defines base address and bits of FIRI registers + * + * @ingroup FIRI + */ + +/*! + * FIRI maximum packet length + */ +#define FIR_MAX_RXLEN 2047 + +/* + * FIRI Transmitter Control Register + */ +#define FIRITCR 0x00 +/* + * FIRI Transmitter Count Register + */ +#define FIRITCTR 0x04 +/* + * FIRI Receiver Control Register + */ +#define FIRIRCR 0x08 +/* + * FIRI Transmitter Status Register + */ +#define FIRITSR 0x0C +/* + * FIRI Receiver Status Register + */ +#define FIRIRSR 0x10 +/* + * FIRI Transmitter FIFO + */ +#define FIRITXFIFO 0x14 +/* + * FIRI Receiver FIFO + */ +#define FIRIRXFIFO 0x18 +/* + * FIRI Control Register + */ +#define FIRICR 0x1C + +/* + * Bit definitions of Transmitter Controller Register + */ +#define FIRITCR_HAG (1<<24) /* H/W address generator */ +#define FIRITCR_SRF_FIR (0<<13) /* Start field repeat factor */ +#define FIRITCR_SRF_MIR (1<<13) /* Start field Repeat Factor */ +#define FIRITCR_TDT_MIR (2<<10) /* TX trigger for MIR is set to 32 bytes) */ +#define FIRITCR_TDT_FIR (1<<10) /* TX trigger for FIR is set to 16 bytes) */ +#define FIRITCR_TCIE (1<<9) /* TX Complete Interrupt Enable */ +#define FIRITCR_TPEIE (1<<8) /* TX Packet End Interrupt Enable */ +#define FIRITCR_TFUIE (1<<7) /* TX FIFO Under-run Interrupt Enable */ +#define FIRITCR_PCF (1<<6) /* Packet Complete by FIFO */ +#define FIRITCR_PC (1<<5) /* Packet Complete */ +#define FIRITCR_SIP (1<<4) /* TX Enable of SIP */ +#define FIRITCR_TPP (1<<3) /* TX Pulse Polarity bit */ +#define FIRITCR_TM_FIR (0<<1) /* TX Mode 4 Mbps */ +#define FIRITCR_TM_MIR1 (1<<1) /* TX Mode 0.576 Mbps */ +#define FIRITCR_TM_MIR2 (1<<2) /* TX Mode 1.152 Mbps */ +#define FIRITCR_TE (1<<0) /* TX Enable */ + +/* + * Bit definitions of Transmitter Count Register + */ +#define FIRITCTR_TPL 511 /* TX Packet Length set to 512 bytes */ + +/* + * Bit definitions of Receiver Control Register + */ +#define FIRIRCR_RAM (1<<24) /* RX Address Match */ +#define FIRIRCR_RPEDE (1<<11) /* Packet End DMA request Enable */ +#define FIRIRCR_RDT_MIR (2<<8) /* DMA Trigger level(64 bytes in RXFIFO) */ +#define FIRIRCR_RDT_FIR (1<<8) /* DMA Trigger level(16 bytes in RXFIFO) */ +#define FIRIRCR_RPA (1<<7) /* RX Packet Abort */ +#define FIRIRCR_RPEIE (1<<6) /* RX Packet End Interrupt Enable */ +#define FIRIRCR_PAIE (1<<5) /* Packet Abort Interrupt Enable */ +#define FIRIRCR_RFOIE (1<<4) /* RX FIFO Overrun Interrupt Enable */ +#define FIRIRCR_RPP (1<<3) /* RX Pulse Polarity bit */ +#define FIRIRCR_RM_FIR (0<<1) /* 4 Mbps */ +#define FIRIRCR_RM_MIR1 (1<<1) /* 0.576 Mbps */ +#define FIRIRCR_RM_MIR2 (1<<2) /* 1.152 Mbps */ +#define FIRIRCR_RE (1<<0) /* RX Enable */ + +/* Transmitter Status Register */ +#define FIRITSR_TFP 0xFF00 /* Mask for available bytes in TX FIFO */ +#define FIRITSR_TC (1<<3) /* Transmit Complete bit */ +#define FIRITSR_SIPE (1<<2) /* SIP End bit */ +#define FIRITSR_TPE (1<<1) /* Transmit Packet End */ +#define FIRITSR_TFU (1<<0) /* TX FIFO Under-run */ + +/* Receiver Status Register */ +#define FIRIRSR_RFP 0xFF00 /* mask for available bytes RX FIFO */ +#define FIRIRSR_PAS (1<<5) /* preamble search */ +#define FIRIRSR_RPE (1<<4) /* RX Packet End */ +#define FIRIRSR_RFO (1<<3) /* RX FIFO Overrun */ +#define FIRIRSR_BAM (1<<2) /* Broadcast Address Match */ +#define FIRIRSR_CRCE (1<<1) /* CRC error */ +#define FIRIRSR_DDE (1<<0) /* Address, control or data field error */ + +/* FIRI Control Register */ +#define FIRICR_BL (32<<5) /* Burst Length is set to 32 */ +#define FIRICR_OSF (0<<1) /* Over Sampling Factor */ + +#endif /* __MXC_FIRI_REG_H__ */ diff --git a/drivers/net/ns9xxx-eth.c b/drivers/net/ns9xxx-eth.c new file mode 100755 index 000000000000..4fc84c7a6d41 --- /dev/null +++ b/drivers/net/ns9xxx-eth.c @@ -0,0 +1,1499 @@ +/* + * drivers/net/ns9xxx-eth.c + * + * Copyright (C) 2007,2008 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* open issues + * - use phy irq + * - VLAN + * - clk_enable only at open time? + * - PM for hibernate + */ +#include <linux/clk.h> +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/mii.h> +#include <linux/ns9xxx-eth.h> +#include <linux/phy.h> +#include <linux/platform_device.h> +#include <linux/timer.h> +#include <asm/gpio.h> + +#define DRIVER_NAME "ns9xxx-eth" + +#define ETH_EGCR1 0x0000 +#define ETH_EGCR1_ERX (1 << 31) +#define ETH_EGCR1_ERXDMA (1 << 30) +#define ETH_EGCR1_ERXSHT (1 << 28) +#define ETH_EGCR1_ETX (1 << 23) +#define ETH_EGCR1_ETXDMA (1 << 22) +#define ETH_EGCR1_ERXINIT (1 << 19) +#define ETH_EGCR1_PM (3 << 14) +#define ETH_EGCR1_PM_MII 0 +#define ETH_EGCR1_MACHRST (1 << 9) +#define ETH_EGCR1_ITXA (1 << 8) +#define ETH_EGCR1_MB1 (1 << 21) + +#define ETH_EGCR2 0x0004 +/* TCLER is different comparing the ns9360 vs ns921x */ +/* FIXME TCLER should be used differently per processor */ +#define ETH_EGCR2_TCLER_NS921X (1 << 7) +#define ETH_EGCR2_TCLER_NS9360 (1 << 3) +#define ETH_EGCR2_TCLER (1 << 7) +#define ETH_EGCR2_TKICK (1 << 3) +#define ETH_EGCR2_AUTOZ (1 << 2) +#define ETH_EGCR2_CLRCNT (1 << 1) +#define ETH_EGCR2_STEN (1 << 0) + +#define ETH_EGSR 0x0008 +#define ETH_EGSR_RXINIT (1 << 20) + +#define ETH_TS 0x0018 +#define ETH_TS_OK ( 1 << 15) +#define ETH_TS_BR ( 1 << 14) +#define ETH_TS_MC ( 1 << 13) +#define ETH_TS_AL ( 1 << 12) +#define ETH_TS_AED ( 1 << 11) +#define ETH_TS_AEC ( 1 << 10) +#define ETH_TS_AUR ( 1 << 9) +#define ETH_TS_AJ ( 1 << 8) +#define ETH_TS_DEF ( 1 << 6) +#define ETH_TS_CRC ( 1 << 5) +#define ETH_TS_COLC (15 << 0) + +#define ETH_MAC1 0x0400 +#define ETH_MAC1_SRST (1 << 15) +#define ETH_MAC1_RXEN (1 << 0) + +#define ETH_MAC2 0x0404 +#define ETH_MAC2_PADEN (1 << 5) +#define ETH_MAC2_CRCEN (1 << 4) +#define ETH_MAC2_FULLD (1 << 0) + +#define ETH_B2BIPG 0x0408 + +#define ETH_EPSR 0x0418 +#define ETH_EPSR_SPEED_MASK (1 << 8) +#define ETH_EPSR_SPEED_100 (1 << 8) +#define ETH_EPSR_SPEED_10 (0 << 8) + + +#define ETH_MIIMCFG 0x0420 +#define ETH_MIIMCFG_RMIIM (1 << 15) +#define ETH_MIIMCFG_CLKS (7 << 2) +#define ETH_MIIMCFG_CLKS_DIV40 (7 << 2) + +#define ETH_MIIMCMD 0x0424 +#define ETH_MIIMCMD_READ (1 << 0) +#define ETH_MIIMADDR 0x0428 +#define ETH_MIIMWD 0x042c +#define ETH_MIIMRD 0x0430 +#define ETH_MIIMIR 0x0434 +#define ETH_MIIMIR_LF (1 << 3) +#define ETH_MIIMIR_BUSY (1 << 0) + +#define ETH_SA1 0x0440 +#define ETH_SA2 0x0444 +#define ETH_SA3 0x0448 + +#define ETH_SAF 0x0500 +#define ETH_SAF_PRO (1 << 3) +#define ETH_SAF_PRM (1 << 2) +#define ETH_SAF_PRA (1 << 1) +#define ETH_SAF_BROAD (1 << 0) + +#define ETH_HT1 0x0504 +#define ETH_HT2 0x0508 + +#define ETH_STAT_TR64 0x0680 +#define ETH_STAT_TR127 0x0684 +#define ETH_STAT_TR255 0x0688 +#define ETH_STAT_TR511 0x068C +#define ETH_STAT_TR1K 0x0690 +#define ETH_STAT_TRMAX 0x0694 +#define ETH_STAT_TRMGV 0x0698 +#define ETH_STAT_RBYT 0x069C +#define ETH_STAT_RPKT 0x06A0 +#define ETH_STAT_RFCS 0x06A4 +#define ETH_STAT_RMCA 0x06A8 +#define ETH_STAT_RBCA 0x06AC +#define ETH_STAT_RXCF 0x06B0 +#define ETH_STAT_RXPF 0x06B4 +#define ETH_STAT_RXUO 0x06B8 +#define ETH_STAT_RALN 0x06BC +#define ETH_STAT_RFLR 0x06C0 +#define ETH_STAT_RCDE 0x06C4 +#define ETH_STAT_RCSE 0x06C8 +#define ETH_STAT_RUND 0x06CC +#define ETH_STAT_ROVR 0x06D0 +#define ETH_STAT_RFRG 0x06D4 +#define ETH_STAT_RJBR 0x06D8 +#define ETH_STAT_TBYT 0x06E0 +#define ETH_STAT_TPKT 0x06E4 +#define ETH_STAT_TMCA 0x06E8 +#define ETH_STAT_TBCA 0x06EC +#define ETH_STAT_TDFR 0x06F4 +#define ETH_STAT_TEDF 0x06F8 +#define ETH_STAT_TSCL 0x06FC +#define ETH_STAT_TMCL 0x0700 +#define ETH_STAT_TLCL 0x0704 +#define ETH_STAT_TXCL 0x0708 +#define ETH_STAT_TNCL 0x070C +#define ETH_STAT_TJBR 0x0718 +#define ETH_STAT_TFCS 0x071C +#define ETH_STAT_TOVR 0x0724 +#define ETH_STAT_TUND 0x0728 +#define ETH_STAT_TFRG 0x072C + +#define ETH_RXABDP 0x0a00 +#define ETH_RXBBDP 0x0a04 +#define ETH_RXCBDP 0x0a08 +#define ETH_RXDBDP 0x0a0c + +#define ETH_IS 0x0a10 +#define ETH_IS_RXOVFLDATA (1 << 25) +#define ETH_IS_RXOVFLSTAT (1 << 24) +#define ETH_IS_RXDONEA (1 << 22) +#define ETH_IS_RXDONEB (1 << 21) +#define ETH_IS_RXDONEC (1 << 20) +#define ETH_IS_RXDONED (1 << 19) +#define ETH_IS_RXNOBUF (1 << 18) +#define ETH_IS_RXBUFFUL (1 << 17) +#define ETH_IS_RXBR (1 << 16) +#define ETH_IS_TXDONE (1 << 2) +#define ETH_IS_TXERR (1 << 1) +#define ETH_IS_TXIDLE (1 << 0) + +#define ETH_IE 0x0a14 +#define ETH_IE_RXOVFLDATA (1 << 25) +#define ETH_IE_RXOVFLSTAT (1 << 24) +#define ETH_IE_RXDONEA (1 << 22) +#define ETH_IE_RXDONEB (1 << 21) +#define ETH_IE_RXDONEC (1 << 20) +#define ETH_IE_RXDONED (1 << 19) +#define ETH_IE_RXNOBUF (1 << 18) +#define ETH_IE_RXBUFFUL (1 << 17) +#define ETH_IE_RXBR (1 << 16) +#define ETH_IE_TXDONE (1 << 2) +#define ETH_IE_TXERR (1 << 1) +#define ETH_IE_TXIDLE (1 << 0) + +#define ETH_RX_IRQS (ETH_IE_RXOVFLDATA | \ + ETH_IE_RXDONEA | \ + ETH_IE_RXDONEB | \ + ETH_IE_RXDONEC | \ + ETH_IE_RXDONED | \ + ETH_IE_RXNOBUF | \ + ETH_IE_RXBUFFUL) + +#define ETH_TX_IRQS (ETH_IE_TXDONE | ETH_IE_TXERR) + +#define ETH_TXBDP 0x0a18 + +#define ETH_TRBDP 0x0a1c + +#define ETH_RXFREE 0x0a3c +#define ETH_RXFREE_A (1 << 0) + +#define ETH_TXBDR 0x1000 + +/* hardware limits sets from ethernet controller */ +#define HW_RX_RINGS (4) +#define MAX_ETH_FRAME_LEN (1522) + +/* software limits sets by driver */ +#define TOTAL_NR_TXDESC (64) +#define NR_RXDESC_PER_RING (45) +#define TOTAL_NR_RXDESC (HW_RX_RINGS*NR_RXDESC_PER_RING) /* total number of descriptors */ + +/* masks for DMA descriptors handling */ +#define DMADESC_WRAP (1 << 15) +#define DMADESC_INTR (1 << 14) +#define DMATXDESC_LAST (1 << 13) +#define DMARXDESC_EN (1 << 13) +#define DMADESC_FULL (1 << 12) + +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED + #define ACTIVITYLED_TOGGLE_TIMEOUT 2 /* in jiffies */ + #define ACTIVITYLED_OFF_TIMEOUT 20 /* in jiffies */ + unsigned int rxtx_activity = 0; + struct timer_list activityled_timer; +#endif + +union ns9xxx_dma_desc { + struct { + u32 source; + u16 len; + u16 reserved; + u32 dest; + u16 status; + u16 flags; + }; + u32 data[4]; +}; + +struct ns9xxx_eth_priv { + unsigned char __iomem *membase; + resource_size_t mapbase; + spinlock_t lock; + struct net_device_stats stats; + struct clk *clk; + unsigned int irqtx; + unsigned int irqrx; +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED + unsigned int activityled; +#endif + + /* phy management */ + int lastlink; + int lastduplex; + int lastspeed; + struct mii_bus *mdiobus; + struct phy_device *phy; + + /* rx stuff */ + struct sk_buff **rxskb; + union ns9xxx_dma_desc *rxdesc; + dma_addr_t rxdeschandle; + + /* tx stuff */ + struct sk_buff **txskb; + u16 txbusy; + u16 txfree; + + /* work to recover from rx stall condition that can happen at 100 Mbps HD */ + struct delayed_work recover_from_rx_stall; +}; + +static inline u32 ethread32(struct net_device *dev, unsigned int offset) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + u32 ret = ioread32(priv->membase + offset); + + dev_vdbg(&dev->dev, "read 0x%p -> 0x%08x\n", + priv->membase + offset, ret); + + return ret; +} + +static inline void ethwrite32(struct net_device *dev, + u32 value, unsigned int offset) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + + dev_vdbg(&dev->dev, "write 0x%p <- 0x%08x\n", + priv->membase + offset, value); + iowrite32(value, priv->membase + offset); +} + +static inline void ethupdate32(struct net_device *dev, + u32 and, u32 or, unsigned int offset) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + u32 reg; + + reg = ioread32(priv->membase + offset) & and; + iowrite32(reg | or, priv->membase + offset); + dev_vdbg(&dev->dev, "update 0x%p <- 0x%08x\n", + priv->membase + offset, reg | or); +} + +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED +static void toggle_activityled(void) +{ + /* set activity flag */ + rxtx_activity = 1; + /* run timer if not already running */ + if(!timer_pending(&activityled_timer)) + mod_timer(&activityled_timer, jiffies + ACTIVITYLED_TOGGLE_TIMEOUT); +} + +static void activityled_timer_fn(unsigned long gpio) +{ + static int cnt = 0; + + if(rxtx_activity) { + /* toggle RX/TX Ethernet activity LED */ + gpio_set_value(gpio, !gpio_get_value(gpio)); + mod_timer(&activityled_timer, jiffies + ACTIVITYLED_TOGGLE_TIMEOUT); + cnt = 0; + rxtx_activity = 0; + } else { + if(cnt++ < ACTIVITYLED_OFF_TIMEOUT / ACTIVITYLED_TOGGLE_TIMEOUT) + mod_timer(&activityled_timer, jiffies + ACTIVITYLED_TOGGLE_TIMEOUT); + else { + gpio_set_value(gpio, 1); /* switch LED off */ + cnt = 0; + } + } + +} +#endif + +static int ns9xxx_eth_miibus_pollbusy(struct mii_bus *bus) +{ + unsigned int timeout = 0x3000; + struct net_device *dev = bus->priv; + + while (timeout--) { + u32 miimir; + + miimir = ethread32(dev, ETH_MIIMIR); + + if (!(miimir & ETH_MIIMIR_BUSY)) + break; + + cpu_relax(); + } + + return timeout ? 0 : -EBUSY; +} + +static int ns9xxx_eth_mdiobus_read(struct mii_bus *bus, int phyid, int regnum) +{ + struct net_device *dev = bus->priv; + int ret; + + if ((phyid & ~0x1f) || (regnum & ~0x1f)) + return -EINVAL; + + ret = ns9xxx_eth_miibus_pollbusy(bus); + if (ret) + goto out; + + ethwrite32(dev, phyid << 8 | regnum, ETH_MIIMADDR); + ethwrite32(dev, 0, ETH_MIIMCMD); + ethwrite32(dev, ETH_MIIMCMD_READ, ETH_MIIMCMD); + + ret = ns9xxx_eth_miibus_pollbusy(bus); + if (ret) + goto out; + + ret = ethread32(dev, ETH_MIIMRD) & 0xffff; + +out: + dev_vdbg(bus->parent, "%s, phyid = %d, regnum = %d -> %04x\n", + __func__, phyid, regnum, ret); + + return ret; +} + +static int ns9xxx_eth_mdiobus_write(struct mii_bus *bus, + int phyid, int regnum, u16 val) +{ + struct net_device *dev = bus->priv; + int ret; + + if ((phyid & ~0x1f) || (regnum & ~0x1f)) + return -EINVAL; + + ret = ns9xxx_eth_miibus_pollbusy(bus); + if (ret) + goto out; + + ethwrite32(dev, phyid << 8 | regnum, ETH_MIIMADDR); + ethwrite32(dev, val, ETH_MIIMWD); + + ret = ns9xxx_eth_miibus_pollbusy(bus); + +out: + dev_vdbg(bus->parent, "%s: phyid = %d, regnum = %d, val = %04hx -> %04x\n", + __func__, phyid, regnum, val, ret); + + return ret; +} + +static int ns9xxx_eth_mdiobus_reset(struct mii_bus *bus) +{ + struct net_device *dev = bus->priv; + + dev_dbg(bus->parent, "%s\n", __func__); + + ethwrite32(dev, ETH_MIIMCFG_RMIIM, ETH_MIIMCFG); + + /* TODO: currently the biggest divider (40) is used. This could be + * tuned depending on the PHY. phylib doesn't provide the needed + * information, though :-( */ + ethwrite32(dev, ETH_MIIMCFG_CLKS_DIV40, ETH_MIIMCFG); + + return 0; +} + +static inline int ns9xxx_eth_create_skbuff(struct net_device* dev, const int descr) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + struct sk_buff *skb; + int ret; + + skb = dev_alloc_skb(MAX_ETH_FRAME_LEN); + if (likely(skb)) { + priv->rxskb[descr] = skb; + skb->dev = dev; + + priv->rxdesc[descr].source = dma_map_single(&dev->dev, skb->data, + skb->len, DMA_FROM_DEVICE); + + priv->rxdesc[descr].len = MAX_ETH_FRAME_LEN; + priv->rxdesc[descr].flags = DMADESC_INTR | DMARXDESC_EN | + (descr == (NR_RXDESC_PER_RING - 1) ? DMADESC_WRAP : 0); + ret = 0; + } else { + printk(KERN_ERR "%s: out of memory\n", __func__); + priv->rxdesc[descr].flags = 0; + ret = -ENOMEM; + } + + return ret; +} + +static inline void ns9xxx_eth_rx_process_ring(struct net_device* dev, unsigned int ring) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + union ns9xxx_dma_desc *desc; + unsigned int i, endring; + + endring = (ring + 1) * NR_RXDESC_PER_RING; + desc = &priv->rxdesc[ring * NR_RXDESC_PER_RING]; + + for (i = ring * NR_RXDESC_PER_RING; i < endring; i++, desc++) { + if (desc->flags & DMADESC_FULL) { + struct sk_buff *skb; + skb = dev_alloc_skb(MAX_ETH_FRAME_LEN); + if (likely(skb)) { + skb_reserve(skb, 2); /* 16 byte IP header align */ + memcpy(skb_put(skb, desc->len), (unsigned char *)priv->rxskb[i]->data, + desc->len); + skb->protocol = eth_type_trans(skb, dev); + priv->stats.rx_packets++; + priv->stats.rx_bytes += desc->len; + dev->last_rx = jiffies; + netif_rx(skb); + } else { + printk(KERN_ERR "%s: out of memory, dropping packet\n", __func__); + dev->stats.rx_dropped++; + } + desc->len = MAX_ETH_FRAME_LEN; + desc->flags = DMADESC_INTR | DMARXDESC_EN | (i == (NR_RXDESC_PER_RING - 1) ? DMADESC_WRAP : 0); + } + } +} + +static irqreturn_t ns9xxx_eth_rx_int(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + unsigned long flags; + u32 is, rxdonemask, ring; + + spin_lock_irqsave(&priv->lock, flags); + + is = ethread32(dev, ETH_IS); + /* Acknowledge interrupts */ + ethwrite32(dev, is & ETH_RX_IRQS, ETH_IS); + dev_vdbg(&dev->dev, "%s: ETH_IS=%08x\n", __func__, is); + + if (is & ETH_IS_RXOVFLDATA) { + if (!(is & (ETH_IS_RXDONEA | ETH_IS_RXDONEB | + ETH_IS_RXDONEC | ETH_IS_RXDONEA))) { + /* The ETH_IS_RXOVFLDATA bit is set, then the receiver front + * end has apparently locked up. We schedule a work that resets + * the interface. We check the DONE bits to try to empty the + * receive rings of packets before the receiver reset. Note + * that once we get into this lockup state, the ETH_IS_RXOVFLDATA + * interrupt will happen continously until we reset the receiver. + */ + ethupdate32(dev, ~(ETH_EGCR1_ERX | ETH_EGCR1_ERXDMA), 0, ETH_EGCR1); + ethupdate32(dev, ~ETH_MAC1_RXEN, 0, ETH_MAC1); + ethupdate32(dev, ~ETH_IE_RXOVFLDATA, 0, ETH_IE); + + schedule_delayed_work(&priv->recover_from_rx_stall, 0); + } + } + + if (is & (ETH_IS_RXNOBUF | ETH_IS_RXBUFFUL)) { + priv->stats.rx_dropped++; + } + + for (ring = 0, rxdonemask = ETH_IS_RXDONEA; ring < HW_RX_RINGS; ring++) { + if (is & rxdonemask) { + ns9xxx_eth_rx_process_ring(dev, ring); + ethwrite32(dev, 1 << ring, ETH_RXFREE); + } + rxdonemask >>= 1; + } + + spin_unlock_irqrestore(&priv->lock, flags); + +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED + toggle_activityled(); +#endif + return IRQ_HANDLED; +} + +static void ns9xxx_eth_recover_from_rx_stall(struct work_struct *work) +{ + struct ns9xxx_eth_priv *priv = + container_of(work, struct ns9xxx_eth_priv, + recover_from_rx_stall.work); + + struct net_device *dev = + container_of((void *)priv, struct net_device, priv); + unsigned long flags; + int i, timeout = 20; + + spin_lock_irqsave(&priv->lock, flags); + + for (i = 0; i < HW_RX_RINGS; i++) { + ethwrite32(dev, priv->rxdeschandle + (i * NR_RXDESC_PER_RING), + ETH_RXABDP + (i * 4)); + } + + ethupdate32(dev, 0xffffffff, ETH_EGSR_RXINIT, ETH_EGSR); + ethupdate32(dev, 0xffffffff, ETH_EGCR1_ERX, ETH_EGCR1); + ethupdate32(dev, 0xffffffff, ETH_EGCR1_ERXINIT, ETH_EGCR1); + + while (!(ethread32(dev, ETH_EGSR) & ETH_EGSR_RXINIT) && timeout--) + udelay(1); + + ethupdate32(dev, ~ETH_EGCR1_ERXINIT, 0, ETH_EGCR1); + + /* Re-enable the overflow interrupt */ + ethupdate32(dev, 0xffffffff, ETH_IE_RXOVFLDATA, ETH_IE); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static inline int ns9xxx_eth_num_txbusy(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + + return (TOTAL_NR_TXDESC + priv->txfree - priv->txbusy) % + TOTAL_NR_TXDESC; +} + +static inline int ns9xxx_eth_num_txfree(struct net_device *dev) +{ + return TOTAL_NR_TXDESC - ns9xxx_eth_num_txbusy(dev); +} + +static void ns9xxx_eth_read_txdesc(struct net_device *dev, + union ns9xxx_dma_desc *txbuffer) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + int i; + + for (i = 0; i < 4; ++i) + txbuffer->data[i] = ethread32(dev, + ETH_TXBDR + 16 * priv->txbusy + 4 * i); +} + +static void ns9xxx_eth_start_tx_dma(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + u32 egcr2, ie; + u32 start; + + dev_vdbg(&dev->dev, "%s\n", __func__); + + dev->trans_start = jiffies; + + /* XXX: really kick TCLER? */ + + egcr2 = ethread32(dev, ETH_EGCR2); + if (egcr2 & (ETH_EGCR2_TCLER | ETH_EGCR2_TKICK)) { + egcr2 &= ~(ETH_EGCR2_TCLER | ETH_EGCR2_TKICK); + ethwrite32(dev, egcr2, ETH_EGCR2); + } + + start = 4 * priv->txbusy; + + ie = ethread32(dev, ETH_IE); + if ((ie & ETH_TX_IRQS) == 0) { + u32 egcr1 = ethread32(dev, ETH_EGCR1); + ethwrite32(dev, start, ETH_TXBDP); + + ethwrite32(dev, ie | ETH_TX_IRQS, ETH_IE); + ethwrite32(dev, egcr1 | ETH_EGCR1_ETXDMA, ETH_EGCR1); + } else + ethwrite32(dev, start, ETH_TRBDP); + + egcr2 |= ETH_EGCR2_TCLER | ETH_EGCR2_TKICK; + ethwrite32(dev, egcr2, ETH_EGCR2); +} + +static irqreturn_t ns9xxx_eth_tx_int(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + u32 is; + struct sk_buff *skb; + union ns9xxx_dma_desc txbuffer; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + is = ethread32(dev, ETH_IS); + dev_vdbg(&dev->dev, "%s: ETH_IS=%08x\n", __func__, is); + + /* ack */ + ethwrite32(dev, is & (ETH_IS_TXDONE | ETH_IS_TXERR), ETH_IS); + + while (1) { + if (!ns9xxx_eth_num_txbusy(dev)) + break; + + skb = priv->txskb[priv->txbusy]; + + ns9xxx_eth_read_txdesc(dev, &txbuffer); + + if (txbuffer.flags & DMADESC_FULL) + break; + + dma_unmap_single(&dev->dev, txbuffer.source, + skb->len, DMA_TO_DEVICE); + + priv->txbusy = (priv->txbusy + 1) % TOTAL_NR_TXDESC; + + if (txbuffer.status & ETH_TS_OK) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + } else { + priv->stats.tx_errors++; + + /* XXX: fill in tx_aborted_errors etc. */ + /* kick TCLER? */ + } + + dev_kfree_skb_irq(skb); + } + + if (ns9xxx_eth_num_txbusy(dev) && + (is & ETH_IS_TXIDLE)) + ns9xxx_eth_start_tx_dma(dev); + + spin_unlock_irqrestore(&priv->lock, flags); + +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED + toggle_activityled(); +#endif + + return IRQ_HANDLED; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void ns9xxx_eth_netpoll(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + unsigned long flags; + + local_irq_save(flags); + ns9xxx_eth_rx_int(priv->irqrx, dev); + ns9xxx_eth_tx_int(priv->irqtx, dev); + local_irq_restore(flags); +} +#endif + +static void ns9xxx_eth_adjust_link(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + struct phy_device *phydev = priv->phy; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + if ((phydev->link != priv->lastlink) || + (priv->lastspeed != phydev->speed) || + (priv->lastduplex != phydev->duplex)){ + + if (phydev->link) { + u32 mac2 = ethread32(dev, ETH_MAC2); + u32 epsr = ethread32(dev, ETH_EPSR); + + /* Adjsut speed */ + epsr &= ~ETH_EPSR_SPEED_MASK; + epsr |= (phydev->speed == SPEED_100) ? + ETH_EPSR_SPEED_100 : 0; + ethwrite32(dev, epsr, ETH_EPSR); + priv->lastspeed = phydev->speed; + + /* Adjsut duplex */ + mac2 &= ~ETH_MAC2_FULLD; + mac2 |= phydev->duplex ? ETH_MAC2_FULLD : 0; + ethwrite32(dev, mac2, ETH_MAC2); + ethwrite32(dev, phydev->duplex ? 0x15 : 0x12, + ETH_B2BIPG); + priv->lastduplex = phydev->duplex; + + dev_info(&dev->dev, "link up (%d/%s)\n", phydev->speed, + (phydev->duplex == DUPLEX_FULL) ? + "full" : "half"); + } else { + /* link down */ + priv->lastspeed = 0; + priv->lastduplex = -1; + dev_info(&dev->dev, "link down\n"); + } + priv->lastlink = phydev->link; + } + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static int ns9xxx_eth_hwinit(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + u32 egcr1, ie; + int timeout, i; + + dev_dbg(&dev->dev, "%s\n", __func__); + + /* disable everything */ + ethwrite32(dev, ETH_EGCR1_PM_MII, ETH_EGCR1); + ethwrite32(dev, 0, ETH_IE); + + /* ack any pending irq */ + ethwrite32(dev, ethread32(dev, ETH_IS), ETH_IS); + + /* program station address */ + ethwrite32(dev, dev->dev_addr[0] | dev->dev_addr[1] << 8, ETH_SA3); + ethwrite32(dev, dev->dev_addr[2] | dev->dev_addr[3] << 8, ETH_SA2); + ethwrite32(dev, dev->dev_addr[4] | dev->dev_addr[5] << 8, ETH_SA1); + + for (i = 0; i < HW_RX_RINGS; i++) { + ethwrite32(dev, priv->rxdeschandle + (i * NR_RXDESC_PER_RING), + ETH_RXABDP + (i * 4)); + } + + ethwrite32(dev, ETH_SAF_BROAD, ETH_SAF); + + egcr1 = ETH_EGCR1_ERX | ETH_EGCR1_ERXDMA | ETH_EGCR1_ETX | + ETH_EGCR1_PM_MII | ETH_EGCR1_ITXA | ETH_EGCR1_MB1; + ethwrite32(dev, egcr1 | ETH_EGCR1_ERXINIT, ETH_EGCR1); + + timeout = 6; + while (!(ethread32(dev, ETH_EGSR) & ETH_EGSR_RXINIT) && timeout--) + udelay(1); + + if (!timeout) + return -EBUSY; + + ethwrite32(dev, ETH_EGSR_RXINIT, ETH_EGSR); + ethwrite32(dev, egcr1, ETH_EGCR1); + + ethwrite32(dev, ETH_MAC1_RXEN, ETH_MAC1); + ethwrite32(dev, ETH_MAC2_CRCEN | ETH_MAC2_PADEN, ETH_MAC2); + ethwrite32(dev, 0x12, ETH_B2BIPG); + + /* clear and enable statistics */ + ethupdate32(dev, 0xffffffff, ETH_EGCR2_CLRCNT, ETH_EGCR2); + ethupdate32(dev, ~ETH_EGCR2_CLRCNT, 0, ETH_EGCR2); + ethupdate32(dev, 0xffffffff, ETH_EGCR2_AUTOZ | ETH_EGCR2_STEN, ETH_EGCR2); + + ie = ethread32(dev, ETH_IE); + ethwrite32(dev, ie | ETH_RX_IRQS, ETH_IE); + + ethwrite32(dev, 0xf, ETH_RXFREE); + return 0; +} + +static void ns9xxx_eth_hwdisable(struct net_device *dev) +{ + dev_dbg(&dev->dev, "%s\n", __func__); + + ethwrite32(dev, 0, ETH_EGCR1); + ethwrite32(dev, 0, ETH_IE); + ethwrite32(dev, 0, ETH_MAC1); + ethwrite32(dev, 0, ETH_MAC2); +} + +static int ns9xxx_eth_open(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + struct resource *res; + int ret = -ENOMEM; + int i; + + dev_dbg(&dev->dev, "%s\n", __func__); + + res = request_mem_region(priv->mapbase, 0x2800, DRIVER_NAME); + if (!res) { + dev_dbg(&dev->dev, "%s: err_request_mem\n", __func__); + goto err_request_mem; + } + + phy_start(priv->phy); + + priv->txfree = 1; + priv->txbusy = 1; + + priv->rxdesc = dma_alloc_coherent(&dev->dev, + sizeof(*priv->rxdesc) * TOTAL_NR_RXDESC, + &priv->rxdeschandle, GFP_KERNEL); + if (!priv->rxdesc) { + dev_dbg(&dev->dev, "%s: err_alloc_rxdesc\n", __func__); + ret = -ENOMEM; + goto err_alloc_rxdesc; + } + + priv->rxskb = kmalloc(sizeof(*priv->rxskb) * TOTAL_NR_RXDESC, + GFP_KERNEL); + if (!priv->rxskb) { + dev_dbg(&dev->dev, "%s: err_alloc_rxskb\n", __func__); + goto err_alloc_rxskb; + } + + for (i = 0; i < TOTAL_NR_RXDESC; ++i) { + ret = ns9xxx_eth_create_skbuff(dev, i); + + if (ret) { + dev_dbg(&dev->dev, "%s: err_setup_rxskb (i = %d)\n", + __func__, i); + goto err_setup_rxskb; + } + } + + priv->txskb = kmalloc(sizeof(*priv->txskb) * TOTAL_NR_TXDESC, + GFP_KERNEL); + if (!priv->txskb) { + dev_dbg(&dev->dev, "%s: err_alloc_txskb\n", __func__); + goto err_alloc_txskb; + } + + ret = ns9xxx_eth_hwinit(dev); + if (ret) { + dev_dbg(&dev->dev, "%s: err_hwinit -> %d\n", __func__, ret); + goto err_hwinit; + } + + ret = request_irq(priv->irqtx, ns9xxx_eth_tx_int, 0, DRIVER_NAME, dev); + if (ret) { + dev_dbg(&dev->dev, "%s: err_request_irq_tx -> %d\n", + __func__, ret); + goto err_request_irq_tx; + } + + ret = request_irq(priv->irqrx, ns9xxx_eth_rx_int, 0, DRIVER_NAME, dev); + if (ret) { + dev_dbg(&dev->dev, "%s: err_request_irq_rx -> %d\n", + __func__, ret); + + free_irq(priv->irqtx, dev); +err_request_irq_tx: + + ns9xxx_eth_hwdisable(dev); +err_hwinit: + +err_setup_rxskb: + for (i = 0; priv->rxskb[i]; ++i) { + dma_unmap_single(&dev->dev, priv->rxdesc[i].source, + priv->rxskb[i]->len, DMA_FROM_DEVICE); + kfree_skb(priv->rxskb[i]); + } + + kfree(priv->txskb); +err_alloc_txskb: + + kfree(priv->rxskb); +err_alloc_rxskb: + + dma_free_coherent(&dev->dev, + sizeof(*priv->rxdesc) * TOTAL_NR_RXDESC, + priv->rxdesc, priv->rxdeschandle); +err_alloc_rxdesc: + + release_mem_region(priv->mapbase, 0x2800); +err_request_mem: + + return ret; + } + + netif_start_queue(dev); + + return 0; +} + +static int ns9xxx_eth_stop(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + + dev_dbg(&dev->dev, "%s\n", __func__); + + free_irq(priv->irqrx, dev); + free_irq(priv->irqtx, dev); + + cancel_delayed_work(&priv->recover_from_rx_stall); + + ns9xxx_eth_hwdisable(dev); + kfree(priv->txskb); + kfree(priv->rxskb); + dma_free_coherent(&dev->dev, + sizeof(*priv->rxdesc) * TOTAL_NR_RXDESC, + priv->rxdesc, priv->rxdeschandle); + + release_mem_region(priv->mapbase, 0x2800); + + return 0; +} + +static int ns9xxx_eth_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + unsigned long flags; + int ret; + + if (!netif_running(dev)) + return -EINVAL; + + if (!priv->phy) + return -EINVAL; + + spin_lock_irqsave(&priv->lock, flags); + ret = phy_mii_ioctl(priv->phy, if_mii(ifr), cmd); + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static void ns9xxx_eth_add_txdesc(struct net_device *dev, + union ns9xxx_dma_desc *txbuffer) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + int i; + + dev_vdbg(&dev->dev, "%s: txfree=%hu, txbusy=%hu\n", + __func__, priv->txfree, priv->txbusy); + + for (i = 0; i < 4; ++i) + ethwrite32(dev, txbuffer->data[i], + ETH_TXBDR + 16 * priv->txfree + 4 * i); + + priv->txfree = (priv->txfree + 1) % TOTAL_NR_TXDESC; +} + +static int ns9xxx_eth_hard_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + union ns9xxx_dma_desc txbuffer = {}; + unsigned long flags; + int ret; + + /* do I need to lock already here? */ + spin_lock_irqsave(&priv->lock, flags); + + dev_vdbg(&dev->dev, "%s(skb=%p): skb->data=%p\n", + __func__, skb, skb->data); + + if (likely(ns9xxx_eth_num_txfree(dev) > 0)) { + txbuffer.source = dma_map_single(&dev->dev, skb->data, + skb->len, DMA_TO_DEVICE); + txbuffer.len = skb->len; + txbuffer.flags = DMATXDESC_LAST | DMADESC_FULL; + + if (unlikely(priv->txfree == TOTAL_NR_TXDESC - 1)) + txbuffer.flags |= DMADESC_WRAP; + + priv->txskb[priv->txfree] = skb; + ns9xxx_eth_add_txdesc(dev, &txbuffer); + + if (ns9xxx_eth_num_txbusy(dev) == 1) + ns9xxx_eth_start_tx_dma(dev); + + ret = NETDEV_TX_OK; + } else { + netif_stop_queue(dev); + ret = NETDEV_TX_BUSY; + } + + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static void ns9xxx_eth_tx_timeout(struct net_device *dev) +{ + /* XXX */ + dev_warn(&dev->dev, "%s unimplemented\n", __func__); +} + +static struct net_device_stats *ns9xxx_eth_get_stats(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + + priv->stats.rx_length_errors += ethread32(dev, ETH_STAT_RFLR); + priv->stats.rx_over_errors += ethread32(dev, ETH_STAT_ROVR); + priv->stats.rx_crc_errors += ethread32(dev, ETH_STAT_RFCS); + priv->stats.rx_frame_errors += ethread32(dev, ETH_STAT_RALN); + priv->stats.rx_errors += ethread32(dev, ETH_STAT_RCDE) + + ethread32(dev, ETH_STAT_RCSE) + + ethread32(dev, ETH_STAT_RUND) + + ethread32(dev, ETH_STAT_RFRG) + + ethread32(dev, ETH_STAT_RJBR); + priv->stats.multicast += ethread32(dev, ETH_STAT_RMCA); + priv->stats.tx_aborted_errors += ethread32(dev, ETH_STAT_TXCL); + priv->stats.collisions += ethread32(dev, ETH_STAT_TNCL); + + return &priv->stats; +} + +static void ns9xxx_set_multicast_list(struct net_device *dev) +{ + u32 saf = ETH_SAF_BROAD; + + dev_dbg(&dev->dev, "%s\n", __func__); + + /* TODO: use the hash tables to improve multicast reception */ + if (dev->flags & IFF_PROMISC) + /* get everything */ + saf |= ETH_SAF_PRO; + + else if (dev->flags & IFF_ALLMULTI) + /* get all multicast traffic */ + saf |= ETH_SAF_PRM; + + else if (dev->mc_count > 0) { + struct dev_addr_list *m; + u32 ht[2] = {0, 0}; + + for (m = dev->mc_list; m; m = m->next) { + /* + * The HRM of ns9360 and ns9215 state that the upper 6 + * bits are used to calculate the bit in the hash table, + * but the sample code (and the NET+OS driver) uses bits + * 28:23 ... + */ + u32 crc = ether_crc(ETH_ALEN, m->dmi_addr) >> 23; + crc &= 0x3f; + + ht[crc & 0x20 ? 1 : 0] |= 1 << (crc & 0x1f); + } + + saf |= ETH_SAF_PRA; + + ethwrite32(dev, ht[0], ETH_HT1); + ethwrite32(dev, ht[1], ETH_HT2); + } + + ethwrite32(dev, saf, ETH_SAF); +} + +static int ns9xxx_eth_mdiobus_init(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + struct plat_ns9xxx_eth *pdata = pdev->dev.platform_data; + int i; + char phyid[BUS_ID_SIZE]; + int ret = -ENOMEM; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + priv->mdiobus = mdiobus_alloc(); + if (priv->mdiobus == NULL) + goto err_out; + + priv->mdiobus->name = DRIVER_NAME "-mii"; + snprintf(priv->mdiobus->id, MII_BUS_ID_SIZE, "0"); + priv->mdiobus->priv = dev; + priv->mdiobus->read = ns9xxx_eth_mdiobus_read; + priv->mdiobus->write = ns9xxx_eth_mdiobus_write; + priv->mdiobus->reset = ns9xxx_eth_mdiobus_reset; + priv->mdiobus->phy_mask = pdata->phy_mask; + priv->mdiobus->parent = &pdev->dev; + priv->mdiobus->irq = kmalloc(sizeof(*priv->mdiobus->irq) * PHY_MAX_ADDR, + GFP_KERNEL); + + if (!priv->mdiobus->irq) { + dev_dbg(&pdev->dev, "%s: err_alloc_irq\n", __func__); + goto err_alloc_irq; + } + + for (i = 0; i < PHY_MAX_ADDR; ++i) + priv->mdiobus->irq[i] = PHY_POLL; + + ret = mdiobus_register(priv->mdiobus); + if (ret) { + dev_dbg(&pdev->dev, "%s: err_mdiobus_register -> %d\n", + __func__, ret); + goto err_mdiobus_register; + } + + for (i = 0; i < PHY_MAX_ADDR; ++i) + if (priv->mdiobus->phy_map[i]) + break; + + if (i >= PHY_MAX_ADDR) { + dev_dbg(&pdev->dev, "%s: no phy found\n", __func__); + ret = -ENODEV; + goto err_find_phy; + } + + dev_dbg(&pdev->dev, "%s: using phy at address %d\n", __func__, i); + + snprintf(phyid, sizeof(phyid), PHY_ID_FMT, + priv->mdiobus->id, i); + + priv->phy = phy_connect(dev, phyid, &ns9xxx_eth_adjust_link, + 0, PHY_INTERFACE_MODE_MII); + + if (IS_ERR(priv->phy)) { + ret = PTR_ERR(priv->phy); + dev_dbg(&pdev->dev, "%s: err_phy_connect -> %d\n", + __func__, ret); +err_find_phy: + + mdiobus_unregister(priv->mdiobus); +err_mdiobus_register: + + kfree(priv->mdiobus->irq); +err_alloc_irq: + mdiobus_free(priv->mdiobus); +err_out: + return ret; + } + + priv->phy->supported &= SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_MII; + + priv->phy->advertising = priv->phy->supported; + + return 0; +} + +static int ns9xxx_eth_mdiobus_disable(struct net_device *dev) +{ + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + + dev_dbg(&dev->dev, "%s\n", __func__); + + phy_disconnect(priv->phy); + mdiobus_unregister(priv->mdiobus); + kfree(priv->mdiobus->irq); + mdiobus_free(priv->mdiobus); + + return 0; +} + +static __devinit int ns9xxx_eth_pdrv_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct ns9xxx_eth_priv *priv; + + struct resource *mem; + struct plat_ns9xxx_eth *pdata = pdev->dev.platform_data; + unsigned char __iomem *membase; + u32 sa; + int ret = -ENODEV; + int i; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (!pdata) + goto err_pdata; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_dbg(&pdev->dev, "%s: err_get_mem\n", __func__); + goto err_get_mem; + } + + ret = -ENOMEM; + + membase = ioremap(mem->start, 0x2800); + if (!membase) { + dev_dbg(&pdev->dev, "%s: err_ioremap\n", __func__); + goto err_ioremap; + } + + dev = alloc_etherdev(sizeof(*priv)); + if (!dev) { + dev_dbg(&pdev->dev, "%s: err_alloc_etherdev\n", __func__); + goto err_alloc_etherdev; + } + + dev->dev.coherent_dma_mask = (u32)-1; + + SET_NETDEV_DEV(dev, &pdev->dev); + platform_set_drvdata(pdev, dev); + + dev->open = ns9xxx_eth_open; + dev->stop = ns9xxx_eth_stop; + dev->hard_start_xmit = ns9xxx_eth_hard_start_xmit; + dev->tx_timeout = ns9xxx_eth_tx_timeout; + dev->get_stats = ns9xxx_eth_get_stats; + dev->set_multicast_list = ns9xxx_set_multicast_list; + dev->do_ioctl = ns9xxx_eth_ioctl; +#ifdef CONFIG_NET_POLL_CONTROLLER + dev->poll_controller = ns9xxx_eth_netpoll; +#endif + + /* TODO: implement VLAN */ + dev->features = 0; + + priv = netdev_priv(dev); + + spin_lock_init(&priv->lock); + INIT_DELAYED_WORK(&priv->recover_from_rx_stall, ns9xxx_eth_recover_from_rx_stall); + + priv->membase = membase; + priv->mapbase = mem->start; + priv->irqrx = pdata->irqrx; + priv->irqtx = pdata->irqtx; +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED + priv->activityled = pdata->activityled; + /* init kernel timer for toggling + * the activity LED */ + activityled_timer.data = (unsigned long)priv->activityled; + activityled_timer.function = activityled_timer_fn; + activityled_timer.expires = jiffies + ACTIVITYLED_TOGGLE_TIMEOUT; + init_timer(&activityled_timer); + add_timer(&activityled_timer); +#endif + + priv->clk = clk_get(&pdev->dev, DRIVER_NAME); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + dev_dbg(&pdev->dev, "%s: err_clk_get -> %d\n", __func__, ret); + goto err_clk_get; + } + + ret = clk_enable(priv->clk); + if (ret) { + dev_dbg(&pdev->dev, "%s: err_clk_enable -> %d\n", + __func__, ret); + goto err_clk_enable; + } + + sa = ethread32(dev, ETH_SA1); + if (sa == 0) { + dev_warn(&pdev->dev, "Warning: Using default mac address " + "00:04:f3:ff:ff:fa\n"); + memcpy(dev->dev_addr, "\x00\x04\xf3\xff\xff\xfa", ETH_ALEN); + } else { + DECLARE_MAC_BUF(mac); + + /* assume the bootloader has provided a valid mac address */ + dev->dev_addr[4] = sa; + dev->dev_addr[5] = sa >> 8; + sa = ethread32(dev, ETH_SA2); + dev->dev_addr[2] = sa; + dev->dev_addr[3] = sa >> 8; + sa = ethread32(dev, ETH_SA3); + dev->dev_addr[0] = sa; + dev->dev_addr[1] = sa >> 8; + dev_dbg(&pdev->dev, "mac address: %s\n", + print_mac(mac, dev->dev_addr)); + } + + ethwrite32(dev, ETH_EGCR1_PM_MII | ETH_EGCR1_MACHRST, ETH_EGCR1); + udelay(5); + ethwrite32(dev, ETH_EGCR1_PM_MII | ETH_EGCR1_ETX, ETH_EGCR1); + + ethwrite32(dev, 0, ETH_MAC1); + + dev_dbg(&pdev->dev, "%s: clear tx DMA descs\n", __func__); + for (i = 0; i < 4 * TOTAL_NR_TXDESC; ++i) + ethwrite32(dev, 0, ETH_TXBDR + 4 * i); + + ret = ns9xxx_eth_mdiobus_init(pdev); + if (ret) { + dev_dbg(&pdev->dev, "%s: err_mdiobus_init -> %d\n", + __func__, ret); + goto err_mdiobus_init; + } + + /* Make the device wakeup capable, but disabled by default */ + device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_enable(&pdev->dev, 0); + + ret = register_netdev(dev); + if (ret) { + dev_dbg(&pdev->dev, "%s: err_register_netdev -> %d\n", + __func__, ret); + + ns9xxx_eth_mdiobus_disable(dev); +err_mdiobus_init: + + clk_disable(priv->clk); +err_clk_enable: + + clk_put(priv->clk); +err_clk_get: + + platform_set_drvdata(pdev, NULL); + free_netdev(dev); +err_alloc_etherdev: + + iounmap(membase); +err_ioremap: +err_get_mem: +err_pdata: + return ret; + } + + /* When using nfsroot, there are RPC requests which can be sent + * before the link is up (long autonegotiation...). Then the + * RPC is not completed until the autonegotiation timeouts (which + * adds long time to the boot process) + * Following line tries to workaround that situation by starting + * the phy here, so we have some worked completed in advance to + * the driver open call */ + phy_start(priv->phy); + + dev_info(&pdev->dev, "%s at MMIO %p\n", dev->name, membase); + dev_dbg(&pdev->dev, "&priv = %p\n", &priv); + dev_dbg(&pdev->dev, "&priv->txfree = %p\n", &priv->txfree); + dev_dbg(&pdev->dev, "&priv->txbusy = %p\n", &priv->txbusy); + + return 0; +} + +static __devexit int ns9xxx_eth_pdrv_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + + dev_dbg(&pdev->dev, "%s\n", __func__); + + unregister_netdev(dev); + + ns9xxx_eth_mdiobus_disable(dev); + + clk_disable(priv->clk); + + clk_put(priv->clk); + + platform_set_drvdata(pdev, NULL); + + iounmap(priv->membase); + + free_netdev(dev); + +#ifdef CONFIG_GPIO_ETH_ACTIVITY_LED + del_timer(&activityled_timer); +#endif + + return 0; +} + +#if defined(CONFIG_PM) +static int ns9xxx_eth_pdrv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (state.event != PM_EVENT_SUSPEND) + /* XXX: implement full state saving */ + return -EBUSY; + + netif_device_detach(dev); + + if (device_may_wakeup(&pdev->dev)) { + + ret = enable_irq_wake(priv->irqrx); + if (ret) { + dev_dbg(&pdev->dev, "%s: err_enable_irq_wake -> %d\n", + __func__, ret); + goto err_enable_irq_wake; + } + + } else { + dev_dbg(&pdev->dev, "%s: !device_may_wakeup\n", __func__); + clk_disable(priv->clk); + } + + return 0; + +err_enable_irq_wake: + netif_device_attach(dev); + + return ret; +} + +static int ns9xxx_eth_pdrv_resume(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct ns9xxx_eth_priv *priv = netdev_priv(dev); + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(priv->irqrx); + else { + ret = clk_enable(priv->clk); + if (ret) { + dev_dbg(&pdev->dev, "%s: err_clk_enable -> %d", + __func__, ret); + return ret; + } + } + + netif_device_attach(dev); + + return 0; +} +#endif + +static struct platform_driver ns9xxx_eth_pdriver = { + .probe = ns9xxx_eth_pdrv_probe, + .remove = __devexit_p(ns9xxx_eth_pdrv_remove), +#if defined(CONFIG_PM) + .suspend = ns9xxx_eth_pdrv_suspend, + .resume = ns9xxx_eth_pdrv_resume, +#endif + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ns9xxx_eth_init(void) +{ + int ret; + + /* catch compiler bugs, endian issues etc pp */ + BUG_ON(offsetof(union ns9xxx_dma_desc, data) != 0x0); + BUG_ON(offsetof(union ns9xxx_dma_desc, source) != 0x0); + BUG_ON(offsetof(union ns9xxx_dma_desc, len) != 0x4); + BUG_ON(offsetof(union ns9xxx_dma_desc, dest) != 0x8); + BUG_ON(offsetof(union ns9xxx_dma_desc, flags) != 0xe); + BUG_ON(offsetof(union ns9xxx_dma_desc, status) != 0xc); + + ret = platform_driver_register(&ns9xxx_eth_pdriver); + if (ret) { + pr_debug("%s: err_pdrv_register\n", __func__); + + return ret; + } + pr_info("Digi NS9XXX Ethernet driver\n"); + + return 0; +} + +static void __exit ns9xxx_eth_exit(void) +{ + platform_driver_unregister(&ns9xxx_eth_pdriver); +} + +module_init(ns9xxx_eth_init); +module_exit(ns9xxx_eth_exit); + +MODULE_AUTHOR("Uwe Kleine-Koenig"); +MODULE_DESCRIPTION("Digi NS9XXX ethernet driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/net/smc911x.h b/drivers/net/smc911x.h index cc7d85bdfb3e..2ea8f4aa1d91 100644 --- a/drivers/net/smc911x.h +++ b/drivers/net/smc911x.h @@ -52,6 +52,14 @@ #ifdef SMC_USE_PXA_DMA #define SMC_USE_DMA + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW +#endif + +#ifdef CONFIG_ARCH_MXC + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW #endif /* store this information for the driver.. */ diff --git a/drivers/net/smsc9118/Makefile b/drivers/net/smsc9118/Makefile new file mode 100644 index 000000000000..d33fe1dc8e38 --- /dev/null +++ b/drivers/net/smsc9118/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SMSC9118) += smsc911x.o diff --git a/drivers/net/smsc9118/smsc911x.c b/drivers/net/smsc9118/smsc911x.c new file mode 100644 index 000000000000..3a55f78cee8a --- /dev/null +++ b/drivers/net/smsc9118/smsc911x.c @@ -0,0 +1,2711 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2007 SMSC + * Copyright (C) 2005 ARM + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *************************************************************************** + * Rewritten, heavily based on smsc911x simple driver by SMSC. + * Partly uses io macros from smc91x.c by Nicolas Pitre + * + * Supported devices: + * LAN9115, LAN9116, LAN9117, LAN9118 + * LAN9215, LAN9216, LAN9217, LAN9218 + * + * History: + * 05/05/2005 bahadir.balban@arm.com + * - Transition to linux coding style + * - Platform driver and module interface + * + * 17/07/2006 steve.glendinning@smsc.com + * - Added support for LAN921x family + * - Added workaround for multicast filters + * + * 31/07/2006 steve.glendinning@smsc.com + * - Removed tasklet, using NAPI poll instead + * - Multiple device support + * - Large tidy-up following feedback from netdev list + * + * 03/08/2006 steve.glendinning@smsc.com + * - Added ethtool support + * - Convert to use generic MII interface + * + * 04/08/2006 bahadir.balban@arm.com + * - Added ethtool eeprom r/w support + * + * 17/06/2007 steve.glendinning@smsc.com + * - Incorporate changes from Bill Gatliff and Russell King + * + * 04/07/2007 steve.glendinning@smsc.com + * - move irq configuration to platform_device + * - fix link poller after interface is stopped and restarted + * + * 13/07/2007 bahadir.balban@arm.com + * - set irq polarity before requesting irq + * + * 26/06/2007 hennerich@blackfin.uclinux.org + * - Fixed minor style issue to pass checkpatch.pl + */ + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/version.h> +#include <linux/bug.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <asm/io.h> + +/* For having the same platform-data as in the Vanilla kernel */ +#include <linux/smc911x.h> + +#include "smsc911x.h" + +#define SMSC_CHIPNAME "smsc911x" +#define SMSC_DRV_VERSION "2007-07-13" + +MODULE_LICENSE("GPL"); + + +/* Base address of the connected controller: S3C2410_CS5 = 0x28000000 */ +#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] smsc911x: " fmt, ## args) +#define printk_info(fmt, args...) printk(KERN_INFO "smsc911x: " fmt, ## args) + +#if 0 +#define SMSC911X_DEBUG +#endif + +#ifdef SMSC911X_DEBUG +# define printk_debug(fmt, args...) printk(KERN_DEBUG "smsc911x: " fmt, ## args) +#else +# define printk_debug(fmt, args...) +#endif + +/* Enables the debug messages for the PM-operations (WOL, suspend, etc.) */ +#if 0 +#define SMSC911X_PM_DEBUG +#endif + +#ifdef SMSC911X_PM_DEBUG +# define printk_pmdbg(fmt, args...) printk(KERN_DEBUG "smsc911x: " fmt, ## args) +#else +# define printk_pmdbg(fmt, args...) +#endif + +struct smsc911x_data { + void __iomem *ioaddr; + + unsigned int idrev; + unsigned int generation; /* used to decide which workarounds apply */ + + /* device configuration */ + unsigned int irq_polarity; + unsigned int irq_type; + unsigned int irq_flags; + + /* This needs to be acquired before calling any of below: + * smsc911x_mac_read(), smsc911x_mac_write() + * smsc911x_phy_read(), smsc911x_phy_write() + */ + spinlock_t phy_lock; + + struct mii_if_info mii; + unsigned int using_extphy; + u32 msg_enable; +#ifdef USE_LED1_WORK_AROUND + unsigned int gpio_setting; + unsigned int gpio_orig_setting; +#endif + struct net_device *netdev; + struct napi_struct napi; + struct timer_list link_poll_timer; + unsigned int stop_link_poll; + + unsigned int software_irq_signal; + +#ifdef USE_PHY_WORK_AROUND +#define MIN_PACKET_SIZE (64) + char loopback_tx_pkt[MIN_PACKET_SIZE]; + char loopback_rx_pkt[MIN_PACKET_SIZE]; + unsigned int resetcount; +#endif + + /* Members for Multicast filter workaround */ + unsigned int multicast_update_pending; + unsigned int set_bits_mask; + unsigned int clear_bits_mask; + unsigned int hashhi; + unsigned int hashlo; + unsigned int last_rxstat; + + /* Registers for the internal PM */ + unsigned long mac_wucsr; + unsigned long pmt_ctrl; + unsigned long phy_intmsk; +}; + + +static int smsc911x_set_mac(struct net_device *dev, void *addr); + +#if SMSC_CAN_USE_32BIT + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + return readl(pdata->ioaddr + reg); +} + +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + writel(val, pdata->ioaddr + reg); +} + +#else /* SMSC_CAN_USE_32BIT */ + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + u32 reg_val; + unsigned long flags; + + /* these two 16-bit reads must be performed consecutively, so must + * not be interrupted by our own ISR (which would start another + * read operation) */ + local_irq_save(flags); + reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) | + ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16)); + local_irq_restore(flags); + + return reg_val; +} + +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + unsigned long flags; + + /* these two 16-bit writes must be performed consecutively, so must + * not be interrupted by our own ISR (which would start another + * read operation) */ + local_irq_save(flags); + writew(val & 0xFFFF, pdata->ioaddr + reg); + writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2); + local_irq_restore(flags); +} + +#endif /* SMSC_CAN_USE_32BIT */ + +#ifndef CONFIG_BLACKFIN +/* Writes a packet to the TX_DATA_FIFO */ +static inline void +smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + while (wordcount--) + smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO); +} + +/* Reads a packet out of the RX_DATA_FIFO */ +static inline void +smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + while (wordcount--) + *buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO); +} +#else +/* Writes a packet to the TX_DATA_FIFO */ +static inline void +smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + if (wordcount > 24) + dma_outsl((u_long)pdata->ioaddr + TX_DATA_FIFO, buf, wordcount); + else + outsl((u_long)pdata->ioaddr + TX_DATA_FIFO, buf, wordcount); +} + +/* Reads a packet out of the RX_DATA_FIFO */ +static inline void +smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + if (wordcount > 24) + dma_insl((u_long)pdata->ioaddr + RX_DATA_FIFO, buf, wordcount); + else + insl((u_long)pdata->ioaddr + RX_DATA_FIFO, buf, wordcount); +} +#endif + +/* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read + * and smsc911x_mac_write, so assumes phy_lock is held */ +static int smsc911x_mac_notbusy(struct smsc911x_data *pdata) +{ + int i; + u32 val; + + for (i = 0; i < 40; i++) { + val = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (!(val & MAC_CSR_CMD_CSR_BUSY_)) + return 1; + } + SMSC_WARNING("Timed out waiting for MAC not BUSY. " + "MAC_CSR_CMD: 0x%08X", val); + return 0; +} + +/* Fetches a MAC register value. Assumes phy_lock is acquired */ +static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset) +{ + unsigned int temp; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry"); + return 0xFFFFFFFF; + } + + /* Send the MAC cmd */ + smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_ + | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the read to happen */ + if (likely(smsc911x_mac_notbusy(pdata))) + return smsc911x_reg_read(pdata, MAC_CSR_DATA); + + SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read"); + return 0xFFFFFFFF; +} + +/* Set a mac register, phy_lock must be acquired before calling */ +static void smsc911x_mac_write(struct smsc911x_data *pdata, + unsigned int offset, u32 val) +{ + unsigned int temp; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry"); + return; + } + + /* Send data to write */ + smsc911x_reg_write(val, pdata, MAC_CSR_DATA); + + /* Write the actual data */ + smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata, + MAC_CSR_CMD); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the write to complete */ + if (likely(smsc911x_mac_notbusy(pdata))) + return; + + SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write"); +} + +/* Gets a phy register, phy_lock must be acquired before calling */ +static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index) +{ + unsigned int addr; + int i; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARNING("MII is busy in smsc911x_phy_read???"); + return 0; + } + + /* Set the address, index & direction (read from PHY) */ + addr = (((pdata->mii.phy_id) & 0x1F) << 11) + | ((index & 0x1F) << 6); + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for read to complete w/ timeout */ + for (i = 0; i < 100; i++) { + /* See if MII is finished yet */ + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + return smsc911x_mac_read(pdata, MII_DATA); + } + } + SMSC_WARNING("Timed out waiting for MII write to finish"); + return 0xFFFF; +} + +/* Sets a phy register, phy_lock must be acquired before calling */ +static void smsc911x_phy_write(struct smsc911x_data *pdata, + unsigned int index, u16 val) +{ + unsigned int addr; + int i; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARNING("MII is busy in smsc911x_write_phy???"); + return; + } + + /* Put the data to write in the MAC */ + smsc911x_mac_write(pdata, MII_DATA, val); + + /* Set the address, index & direction (write to PHY) */ + addr = (((pdata->mii.phy_id) & 0x1F) << 11) | + ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_; + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for write to complete w/ timeout */ + for (i = 0; i < 100; i++) { + /* See if MII is finished yet */ + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) + return; + } + SMSC_WARNING("Timed out waiting for MII write to finish"); +} + +static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + int reg; + + spin_lock_irqsave(&pdata->phy_lock, flags); + reg = smsc911x_phy_read(pdata, location); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + return reg; +} + +static void smsc911x_mdio_write(struct net_device *dev, int phy_id, + int location, int val) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, location, val); + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors. + * If something goes wrong, returns -ENODEV to revert back to internal phy. + * Performed at initialisation only, so interrupts are enabled */ +static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata) +{ + unsigned int address; + unsigned int hwcfg; + unsigned int phyid1; + unsigned int phyid2; + + hwcfg = smsc911x_reg_read(pdata, HW_CFG); + + /* External phy is requested, supported, and detected */ + if (hwcfg & HW_CFG_EXT_PHY_DET_) { + + /* Attempt to switch to external phy for auto-detecting + * its address. Assuming tx and rx are stopped because + * smsc911x_phy_initialise is called before + * smsc911x_rx_initialise and tx_initialise. + */ + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to external phy */ + hwcfg |= HW_CFG_EXT_PHY_EN_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg |= HW_CFG_SMI_SEL_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Auto-detect PHY */ + spin_lock_irq(&pdata->phy_lock); + for (address = 0; address <= 31; address++) { + pdata->mii.phy_id = address; + phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1); + phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2); + if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) { + SMSC_TRACE("Detected PHY at address = " + "0x%02X = %d", address, address); + break; + } + } + spin_unlock_irq(&pdata->phy_lock); + + if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) { + SMSC_WARNING("External PHY is not accessable, " + "using internal PHY instead"); + /* Revert back to internal phy settings. */ + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to internal phy */ + hwcfg &= (~HW_CFG_EXT_PHY_EN_); + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg &= (~HW_CFG_SMI_SEL_); + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + /* Use internal phy */ + return -ENODEV; + } else { + SMSC_TRACE("Successfully switched to external PHY"); + pdata->using_extphy = 1; + } + } else { + SMSC_WARNING("No external PHY detected."); + SMSC_WARNING("Using internal PHY instead."); + /* Use internal phy */ + return -ENODEV; + } + return 0; +} + +/* called by phy_initialise and loopback test */ +static int smsc911x_phy_reset(struct smsc911x_data *pdata) +{ + unsigned int temp; + unsigned int i = 100000; + unsigned long flags; + + SMSC_TRACE("Performing PHY BCR Reset"); + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET); + do { + udelay(10); + temp = smsc911x_phy_read(pdata, MII_BMCR); + } while ((i--) && (temp & BMCR_RESET)); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + if (temp & BMCR_RESET) { + SMSC_WARNING("PHY reset failed to complete."); + return 0; + } + /* Extra delay required because the phy may not be completed with + * its reset when BMCR_RESET is cleared. Specs say 256 uS is + * enough delay but using 1ms here to be safe + */ + msleep(1); + + return 1; +} + +/* Fetches a tx status out of the status fifo */ +static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, TX_STATUS_FIFO); + + return result; +} + +/* Fetches the next rx status */ +static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, RX_STATUS_FIFO); + + return result; +} + +#ifdef USE_PHY_WORK_AROUND +static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata) +{ + unsigned int tries; + u32 wrsz; + u32 rdsz; + u32 bufp; + + for (tries = 0; tries < 10; tries++) { + unsigned int txcmd_a; + unsigned int txcmd_b; + unsigned int status; + unsigned int pktlength; + unsigned int i; + + /* Zero-out rx packet memory */ + memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE); + + /* Write tx packet to 118 */ + txcmd_a = (((unsigned int)pdata->loopback_tx_pkt) + & 0x03) << 16; + txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + txcmd_a |= MIN_PACKET_SIZE; + + txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE; + + smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO); + smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO); + + bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC; + wrsz = MIN_PACKET_SIZE + 3; + wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3); + wrsz >>= 2; + + smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + + /* Wait till transmit is done */ + i = 60; + do { + udelay(5); + status = smsc911x_tx_get_txstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARNING("Failed to transmit during loopback test"); + continue; + } + if (status & TX_STS_ES_) { + SMSC_WARNING("Transmit encountered errors during " + "loopback test"); + continue; + } + + /* Wait till receive is done */ + i = 60; + do { + udelay(5); + status = smsc911x_rx_get_rxstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARNING("Failed to receive during loopback test"); + continue; + } + if (status & RX_STS_ES_) { + SMSC_WARNING("Receive encountered errors during " + "loopback test"); + continue; + } + + pktlength = ((status & 0x3FFF0000UL) >> 16); + bufp = (u32)pdata->loopback_rx_pkt; + rdsz = pktlength + 3; + rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3; + rdsz >>= 2; + + smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz); + + if (pktlength != (MIN_PACKET_SIZE + 4)) { + SMSC_WARNING("Unexpected packet size during " + "loop back test, size=%d, " + "will retry", pktlength); + } else { + unsigned int j; + int mismatch = 0; + for (j = 0; j < MIN_PACKET_SIZE; j++) { + if (pdata->loopback_tx_pkt[j] + != pdata->loopback_rx_pkt[j]) { + mismatch = 1; + break; + } + } + if (!mismatch) { + SMSC_TRACE("Successfully verified " + "loopback packet"); + return 1; + } else { + SMSC_WARNING("Data miss match during " + "loop back test, will retry."); + } + } + } + + return 0; +} + +static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata) +{ + int result = 0; + unsigned int i; + unsigned int val; + unsigned long flags; + + /* Initialise tx packet */ + for (i = 0; i < 6; i++) { + /* Use broadcast destination address */ + pdata->loopback_tx_pkt[i] = (char)0xFF; + } + + for (i = 6; i < 12; i++) { + /* Use incrementing source address */ + pdata->loopback_tx_pkt[i] = (char)i; + } + + /* Set length type field */ + pdata->loopback_tx_pkt[12] = 0x00; + pdata->loopback_tx_pkt[13] = 0x00; + for (i = 14; i < MIN_PACKET_SIZE; i++) { + pdata->loopback_tx_pkt[i] = (char)i; + } + + val = smsc911x_reg_read(pdata, HW_CFG); + val &= HW_CFG_TX_FIF_SZ_; + val |= HW_CFG_SF_; + smsc911x_reg_write(val, pdata, HW_CFG); + + smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG); + smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt) + & 0x03) << 8, pdata, RX_CFG); + + for (i = 0; i < 10; i++) { + /* Set PHY to 10/FD, no ANEG, and loopback mode */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, 0x4100); + + /* Enable MAC tx/rx, FD */ + smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_ + | MAC_CR_TXEN_ | MAC_CR_RXEN_); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + if (smsc911x_phy_check_loopbackpkt(pdata)) { + result = 1; + break; + } + pdata->resetcount++; + + /* Disable MAC rx */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_phy_reset(pdata); + } + + /* Disable MAC */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + + /* Cancel PHY loopback mode */ + smsc911x_phy_write(pdata, MII_BMCR, 0); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_reg_write(0, pdata, TX_CFG); + smsc911x_reg_write(0, pdata, RX_CFG); + + return result; +} +#endif /* USE_PHY_WORK_AROUND */ + + +inline static void smsc911x_phy_dump_regs(struct smsc911x_data *pdata) +{ + printk("BCR = 0x%04x\n", smsc911x_phy_read(pdata, MII_BMCR)); + printk("BSR = 0x%04x\n", smsc911x_phy_read(pdata, MII_BMSR)); + printk("ID1 = 0x%04x\n", smsc911x_phy_read(pdata, MII_PHYSID1)); + printk("ID2 = 0x%04x\n", smsc911x_phy_read(pdata, MII_PHYSID2)); + printk("ADVER = 0x%04x\n", smsc911x_phy_read(pdata, MII_ADVERTISE)); + printk("LPA = 0x%04x\n", smsc911x_phy_read(pdata, MII_LPA)); + printk("EXP = 0x%04x\n", smsc911x_phy_read(pdata, MII_EXPANSION)); +} + +/* assumes phy_lock is held */ +static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata) +{ + unsigned int temp; + + if (pdata->mii.full_duplex) { + unsigned int phy_adv; + unsigned int phy_lpa; + phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE); + phy_lpa = smsc911x_phy_read(pdata, MII_LPA); + if (phy_adv & phy_lpa & LPA_PAUSE_CAP) { + /* Both ends support symmetric pause, enable + * PAUSE receive and transmit */ + smsc911x_mac_write(pdata, FLOW, 0xFFFF0002); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp |= 0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } else if (((phy_adv & ADVERTISE_PAUSE_ALL) == + ADVERTISE_PAUSE_ALL) && + ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) { + /* We support symmetric and asym pause, the + * other end only supports asym, Enable PAUSE + * receive, disable PAUSE transmit */ + smsc911x_mac_write(pdata, FLOW, 0xFFFF0002); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp &= ~0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } else { + /* Disable PAUSE receive and transmit */ + smsc911x_mac_write(pdata, FLOW, 0); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp &= ~0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } + } else { + smsc911x_mac_write(pdata, FLOW, 0); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp |= 0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } +} + +static void smsc911x_phy_update_duplex(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + unsigned int mac_cr; + + spin_lock_irqsave(&pdata->phy_lock, flags); + + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + if (pdata->mii.full_duplex) { + SMSC_TRACE("configuring for full duplex mode"); + mac_cr |= MAC_CR_FDPX_; + } else { + SMSC_TRACE("configuring for half duplex mode"); + mac_cr &= ~MAC_CR_FDPX_; + } + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + smsc911x_phy_update_flowcontrol(pdata); + + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + + +/* Update link mode if any thing has changed */ +static void smsc911x_phy_update_linkmode(struct net_device *dev, int init) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) + smsc911x_phy_update_duplex(dev); + /* mii_check_media() exists if the media is forced... */ + if (pdata->mii.force_media) { + int cur_link = mii_link_ok(&pdata->mii); + int prev_link = netif_carrier_ok(dev); + + if (!prev_link && cur_link) { + printk(KERN_INFO "%s: link up\n", dev->name); + netif_carrier_on(dev); + } else if (prev_link && !cur_link) { + printk(KERN_INFO "%s: link down\n", dev->name); + netif_carrier_off(dev); + } + } + +#ifdef USE_LED1_WORK_AROUND + if (netif_carrier_ok(dev)) { + if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) && + (!pdata->using_extphy)) { + /* Restore orginal GPIO configuration */ + pdata->gpio_setting = pdata->gpio_orig_setting; + smsc911x_reg_write(pdata->gpio_setting, pdata, + GPIO_CFG); + } + } else { + /* Check global setting that LED1 + * usage is 10/100 indicator */ + pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG); + if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_) + && (!pdata->using_extphy)) { + /* Force 10/100 LED off, after saving + * orginal GPIO configuration */ + pdata->gpio_orig_setting = pdata->gpio_setting; + + pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_; + pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_ + | GPIO_CFG_GPIODIR0_ + | GPIO_CFG_GPIOD0_); + smsc911x_reg_write(pdata->gpio_setting, pdata, + GPIO_CFG); + } + } +#endif /* USE_LED1_WORK_AROUND */ +} + +/* Entry point for the link poller */ +static void smsc911x_phy_checklink(unsigned long ptr) +{ + struct net_device *dev = (struct net_device *)ptr; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_phy_update_linkmode(dev, 0); + + if (!(pdata->stop_link_poll)) { + pdata->link_poll_timer.expires = jiffies + 2 * HZ; + add_timer(&pdata->link_poll_timer); + } else { + pdata->stop_link_poll = 0; + } +} + +static void smsc911x_phy_set_automdx(struct smsc911x_data *pdata) +{ + u16 ctrlstatus; + + ctrlstatus = smsc911x_phy_read(pdata, 27); + ctrlstatus &= 0x1fff; + ctrlstatus |= (0x6 << 13); + smsc911x_phy_write(pdata, 27, ctrlstatus); +} + +/* Initialises the PHY layer. Called at initialisation by open() so + * interrupts are enabled */ +static int smsc911x_phy_initialise(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int phyid1 = 0; + unsigned int phyid2 = 0; + unsigned int temp; + + printk_debug("Calling phy_initialise()\n"); + + pdata->using_extphy = 0; + + switch (pdata->idrev & 0xFFFF0000) { + case 0x01170000: + case 0x01150000: + /* External PHY supported, try to autodetect */ + if (smsc911x_phy_initialise_external(pdata) < 0) { + SMSC_TRACE("External PHY is not detected, using " + "internal PHY instead"); + pdata->mii.phy_id = 1; + } + break; + default: + SMSC_TRACE("External PHY is not supported, using internal PHY " + "instead"); + pdata->mii.phy_id = 1; + break; + } + + spin_lock_irq(&pdata->phy_lock); + phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1); + phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2); + spin_unlock_irq(&pdata->phy_lock); + + if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) { + SMSC_WARNING("Internal PHY not detected!"); + return 0; + } + + /* Reset the phy */ + if (!smsc911x_phy_reset(pdata)) { + SMSC_WARNING("PHY reset failed to complete."); + return 0; + } +#ifdef USE_PHY_WORK_AROUND + if (!smsc911x_phy_loopbacktest(pdata)) { + SMSC_WARNING("Failed Loop Back Test"); + return 0; + } else { + SMSC_TRACE("Passed Loop Back Test"); + } +#endif /* USE_PHY_WORK_AROUND */ + + + smsc911x_phy_set_automdx(pdata); + + /* Advertise all speeds and pause capabilities */ + spin_lock_irq(&pdata->phy_lock); + temp = smsc911x_phy_read(pdata, MII_ADVERTISE); + temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + smsc911x_phy_write(pdata, MII_ADVERTISE, temp); + pdata->mii.advertising = temp; + + if (!pdata->using_extphy) { + /* using internal phy, enable PHY interrupts */ + smsc911x_phy_read(pdata, MII_INTSTS); + smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_); + } + + /* begin to establish link */ + smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + spin_unlock_irq(&pdata->phy_lock); + + smsc911x_phy_update_linkmode(dev, 1); + + pdata->stop_link_poll = 0; + setup_timer(&pdata->link_poll_timer, smsc911x_phy_checklink, + (unsigned long)dev); + pdata->link_poll_timer.expires = jiffies + 2 * HZ; + add_timer(&pdata->link_poll_timer); + + printk_debug("PHY initialised succesfully\n"); + return 1; +} + +/* Gets the number of tx statuses in the fifo */ +static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata) +{ + unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF) + & TX_FIFO_INF_TSUSED_) >> 16; + return result; +} + +/* Reads tx statuses and increments counters where necessary */ +static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata) +{ + struct net_device *netdev = pdata->netdev; + unsigned int tx_stat; + + while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) { + if (unlikely(tx_stat & 0x80000000)) { + /* In this driver the packet tag is used as the packet + * length. Since a packet length can never reach the + * size of 0x8000, this bit is reserved. It is worth + * noting that the "reserved bit" in the warning above + * does not reference a hardware defined reserved bit + * but rather a driver defined one. + */ + SMSC_WARNING("Packet tag reserved bit is high"); + } else { + if (unlikely(tx_stat & 0x00008000)) { + printk_debug("TX status error: 0x%08x (MAC 0x%08x)\n", + tx_stat, smsc911x_mac_read(pdata, MAC_CR)); + netdev->stats.tx_errors++; + } else { + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += (tx_stat >> 16); + } + if (unlikely(tx_stat & 0x00000100)) { + netdev->stats.collisions += 16; + netdev->stats.tx_aborted_errors += 1; + } else { + netdev->stats.collisions += + ((tx_stat >> 3) & 0xF); + } + if (unlikely(tx_stat & 0x00000800)) { + netdev->stats.tx_carrier_errors += 1; + } + if (unlikely(tx_stat & 0x00000200)) { + netdev->stats.collisions++; + netdev->stats.tx_aborted_errors++; + } + } + } +} + +/* Increments the Rx error counters */ +static void +smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat) +{ + struct net_device *netdev = pdata->netdev; + int crc_err = 0; + + if (unlikely(rxstat & 0x00008000)) { + netdev->stats.rx_errors++; + if (unlikely(rxstat & 0x00000002)) { + netdev->stats.rx_crc_errors++; + crc_err = 1; + } + } + if (likely(!crc_err)) { + if (unlikely((rxstat & 0x00001020) == 0x00001020)) { + /* Frame type indicates length, + * and length error is set */ + netdev->stats.rx_length_errors++; + } + if (rxstat & RX_STS_MCAST_) + netdev->stats.multicast++; + } +} + +/* Quickly dumps bad packets */ +static void +smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes) +{ + unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2; + + if (likely(pktwords >= 4)) { + unsigned int timeout = 500; + unsigned int val; + smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL); + do { + udelay(1); + val = smsc911x_reg_read(pdata, RX_DP_CTRL); + } while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_)); + + if (unlikely(timeout == 0)) + SMSC_WARNING("Timed out waiting for RX FFWD " + "to finish, RX_DP_CTRL: 0x%08X", val); + } else { + unsigned int temp; + while (pktwords--) + temp = smsc911x_reg_read(pdata, RX_DATA_FIFO); + } +} + +/* NAPI poll function */ +static int smsc911x_poll(struct napi_struct *napi, int budget) +{ + struct smsc911x_data *pdata = container_of(napi, struct smsc911x_data, napi); + struct net_device *dev = pdata->netdev; + int npackets = 0; + + while (npackets < budget) { + unsigned int pktlength; + unsigned int pktwords; + unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata); + + /* break out of while loop if there are no more packets waiting */ + if (!rxstat) { + printk_debug("Stopping the RX poll\n"); + break; + } + + pktlength = ((rxstat & 0x3FFF0000) >> 16); + pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2; + printk_debug("Going to read %i words (pktlen %i)\n", + pktwords, pktlength); + + smsc911x_rx_counterrors(pdata, rxstat); + + if (likely((rxstat & RX_STS_ES_) == 0)) { + struct sk_buff *skb; + skb = dev_alloc_skb(pktlength + NET_IP_ALIGN); + if (likely(skb)) { + skb->data = skb->head; + skb->tail = skb->head; + /* Align IP on 16B boundary */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put(skb, pktlength - 4); + smsc911x_rx_readfifo(pdata, + (unsigned int *)skb->head, + pktwords); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_NONE; + netif_receive_skb(skb); + + /* Update counters */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += (pktlength - 4); + dev->last_rx = jiffies; + npackets++; + continue; + } else { + SMSC_WARNING("Unable to allocate sk_buff " + "for rx packet, in PIO path"); + dev->stats.rx_dropped++; + } + } + /* At this point, the packet is to be read out + * of the fifo and discarded */ + smsc911x_rx_fastforward(pdata, pktlength); + } + + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS); + + if (npackets < budget) { + unsigned int temp; + /* We processed all packets available. Tell NAPI it can + * stop polling then re-enable rx interrupts */ + netif_rx_complete(dev, napi); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RSFL_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + } + + /* Return total received packets */ + return npackets; +} + +/* Returns hash bit number for given MAC address + * Example: + * 01 00 5E 00 00 01 -> returns bit number 31 */ +static unsigned int smsc911x_hash(char addr[ETH_ALEN]) +{ + unsigned int crc; + unsigned int result; + + crc = ether_crc(ETH_ALEN, addr); + result = (crc >> 26) & 0x3f; + + return result; +} + +static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata) +{ + /* Performs the multicast & mac_cr update. This is called when + * safe on the current hardware, and with the phy_lock held */ + unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= pdata->set_bits_mask; + mac_cr &= ~(pdata->clear_bits_mask); + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + smsc911x_mac_write(pdata, HASHH, pdata->hashhi); + smsc911x_mac_write(pdata, HASHL, pdata->hashlo); +} + +static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata) +{ + unsigned int mac_cr; + + /* This function is only called for older LAN911x devices + * (revA or revB), where MAC_CR, HASHH and HASHL should not + * be modified during Rx - newer devices immediately update the + * registers. + * + * This is called from interrupt context */ + + spin_lock(&pdata->phy_lock); + + /* Check Rx has stopped */ + if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_) + SMSC_WARNING("Rx not stopped\n"); + + /* Perform the update - safe to do now Rx has stopped */ + smsc911x_rx_multicast_update(pdata); + + /* Re-enable Rx */ + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= MAC_CR_RXEN_; + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + + pdata->multicast_update_pending = 0; + + spin_unlock(&pdata->phy_lock); +} + +/* Sets the device MAC address to dev_addr, called with phy_lock held */ +static void +smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6]) +{ + u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4]; + u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) | + (dev_addr[1] << 8) | dev_addr[0]; + + smsc911x_mac_write(pdata, ADDRH, mac_high16); + smsc911x_mac_write(pdata, ADDRL, mac_low32); +} + +static int smsc911x_open(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int timeout; + unsigned int temp; + unsigned int intcfg = 0; + struct sockaddr addr; + + /* Reset the LAN911x */ + smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG); + timeout = 10; + do { + udelay(10); + temp = smsc911x_reg_read(pdata, HW_CFG); + } while ((--timeout) && (temp & HW_CFG_SRST_)); + + if (unlikely(temp & HW_CFG_SRST_)) { + printk_err("Failed to complete reset"); + return -ENODEV; + } + + smsc911x_reg_write(0x00050000, pdata, HW_CFG); + smsc911x_reg_write(0x006E3740, pdata, AFC_CFG); + + /* Make sure EEPROM has finished loading before setting GPIO_CFG */ + timeout = 50; + while ((timeout--) && + (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) { + udelay(10); + } + + if (unlikely(timeout == 0)) { + SMSC_WARNING("Timed out waiting for EEPROM " + "busy bit to clear"); + } +#if USE_DEBUG >= 1 + smsc911x_reg_write(0x00670700, pdata, GPIO_CFG); +#else + smsc911x_reg_write(0x70070000, pdata, GPIO_CFG); +#endif + + /* Initialise irqs, but leave all sources disabled */ + smsc911x_reg_write(0, pdata, INT_EN); + smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS); + + /* Set interrupt deassertion to 100uS */ + //intcfg = ((0x38 << 24) | INT_CFG_IRQ_EN_); + intcfg = ((0x00 << 24) | INT_CFG_IRQ_EN_); + // PPH modified intcfg = ((10 << 24) | INT_CFG_IRQ_EN_); + + if (pdata->irq_polarity) { + SMSC_TRACE("irq polarity: active high"); + intcfg |= INT_CFG_IRQ_POL_; + } else { + SMSC_TRACE("irq polarity: active low"); + } + + if (pdata->irq_type) { + SMSC_TRACE("irq type: push-pull"); + intcfg |= INT_CFG_IRQ_TYPE_; + } else { + SMSC_TRACE("irq type: open drain"); + } + + smsc911x_reg_write(intcfg, pdata, INT_CFG); + + printk_debug("Testing irq handler using IRQ %d\n", dev->irq); + pdata->software_irq_signal = 0; + smp_wmb(); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_SW_INT_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + + timeout = 1000; + while (timeout--) { + smp_rmb(); + if (pdata->software_irq_signal) + break; + msleep(1); + } + + if (!pdata->software_irq_signal) { + printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n", + dev->name, dev->irq); + return -ENODEV; + } + + printk_debug("IRQ handler passed test using IRQ %d\n", dev->irq); + netif_carrier_off(dev); + + if (!smsc911x_phy_initialise(dev)) { + printk_err("Failed to initialize the PHY"); + return -ENODEV; + } + + temp = smsc911x_reg_read(pdata, HW_CFG); + temp &= HW_CFG_TX_FIF_SZ_; + temp |= HW_CFG_SF_; + smsc911x_reg_write(temp, pdata, HW_CFG); + + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + temp &= ~(FIFO_INT_RX_STS_LEVEL_); + smsc911x_reg_write(temp, pdata, FIFO_INT); + + /* set RX Data offset to 2 bytes for alignment */ + smsc911x_reg_write((2 << 8), pdata, RX_CFG); + + /* enable the polling before enabling the interrupts */ + napi_enable(&pdata->napi); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_RDFL_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + + spin_lock_irq(&pdata->phy_lock); + + /* + * Reenable the full duplex mode, otherwise the TX engine will generate + * status errors (Luis Galdos) + */ + temp = smsc911x_mac_read(pdata, MAC_CR); + temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_ | MAC_CR_FDPX_); + smsc911x_mac_write(pdata, MAC_CR, temp); + spin_unlock_irq(&pdata->phy_lock); + + smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG); + + /* Set the MAC once again */ + memcpy(addr.sa_data, dev->dev_addr, dev->addr_len); + if(smsc911x_set_mac(dev, &addr)) + printk_err("Couldn't set the MAC address.\n"); + + netif_start_queue(dev); + return 0; +} + +/* Entry point for stopping the interface */ +static int smsc911x_stop(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + printk_info("Stopping the interface\n"); + + napi_disable(&pdata->napi); + + /* disable interrupts */ + smsc911x_reg_write(0, pdata, INT_EN); + + pdata->stop_link_poll = 1; + del_timer_sync(&pdata->link_poll_timer); + + netif_stop_queue(dev); + + /* At this point all Rx and Tx activity is stopped */ + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_tx_update_txcounters(pdata); + + /* Stop sending data after the last transmission */ + smsc911x_reg_write(TX_CFG_STOP_TX_, pdata, TX_CFG); + + SMSC_TRACE("Interface stopped"); + return 0; +} + +/* Entry point for transmitting a packet */ +static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int freespace; + unsigned int tx_cmd_a; + unsigned int tx_cmd_b; + unsigned int temp; + u32 wrsz; + u32 bufp; + + freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_; + + if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD)) + SMSC_WARNING("Tx data fifo low, space available: %d", + freespace); + + /* Word alignment adjustment */ + tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16); + tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + tx_cmd_a |= (unsigned int)skb->len; + + tx_cmd_b = ((unsigned int)skb->len) << 16; + tx_cmd_b |= (unsigned int)skb->len; + + smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO); + smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO); + + bufp = ((u32)skb->data) & 0xFFFFFFFC; + wrsz = (u32)skb->len + 3; + wrsz += ((u32)skb->data) & 0x3; + wrsz >>= 2; + + smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + freespace -= (skb->len + 32); + dev_kfree_skb(skb); + dev->trans_start = jiffies; + + if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30)) + smsc911x_tx_update_txcounters(pdata); + + if (freespace < TX_FIFO_LOW_THRESHOLD) { + netif_stop_queue(dev); + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp &= 0x00FFFFFF; + temp |= 0x32000000; + smsc911x_reg_write(temp, pdata, FIFO_INT); + } + + return NETDEV_TX_OK; +} + +/* Entry point for getting status counters */ +static struct net_device_stats *smsc911x_get_stats(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + smsc911x_tx_update_txcounters(pdata); + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + return &dev->stats; +} + +/* Entry point for setting addressing modes */ +static void smsc911x_set_multicast_list(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + if (dev->flags & IFF_PROMISC) { + /* Enabling promiscuous mode */ + pdata->set_bits_mask = MAC_CR_PRMS_; + pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->flags & IFF_ALLMULTI) { + /* Enabling all multicast mode */ + pdata->set_bits_mask = MAC_CR_MCPAS_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->mc_count > 0) { + /* Enabling specific multicast addresses */ + unsigned int hash_high = 0; + unsigned int hash_low = 0; + unsigned int count = 0; + struct dev_mc_list *mc_list = dev->mc_list; + + pdata->set_bits_mask = MAC_CR_HPFILT_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + while (mc_list) { + count++; + if ((mc_list->dmi_addrlen) == ETH_ALEN) { + unsigned int bitnum = + smsc911x_hash(mc_list->dmi_addr); + unsigned int mask = 0x01 << (bitnum & 0x1F); + if (bitnum & 0x20) + hash_high |= mask; + else + hash_low |= mask; + } else { + SMSC_WARNING("dmi_addrlen != 6"); + } + mc_list = mc_list->next; + } + if (count != (unsigned int)dev->mc_count) + SMSC_WARNING("mc_count != dev->mc_count"); + + pdata->hashhi = hash_high; + pdata->hashlo = hash_low; + } else { + /* Enabling local MAC address only */ + pdata->set_bits_mask = 0; + pdata->clear_bits_mask = + (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } + + spin_lock_irqsave(&pdata->phy_lock, flags); + + if (pdata->generation <= 1) { + /* Older hardware revision - cannot change these flags while + * receiving data */ + if (!pdata->multicast_update_pending) { + unsigned int temp; + SMSC_TRACE("scheduling mcast update"); + pdata->multicast_update_pending = 1; + + /* Request the hardware to stop, then perform the + * update when we get an RX_STOP interrupt */ + smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RXSTOP_INT_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + + temp = smsc911x_mac_read(pdata, MAC_CR); + temp &= ~(MAC_CR_RXEN_); + smsc911x_mac_write(pdata, MAC_CR, temp); + } else { + /* There is another update pending, this should now + * use the newer values */ + } + } else { + /* Newer hardware revision - can write immediately */ + smsc911x_rx_multicast_update(pdata); + } + + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int intsts; + unsigned int inten; + unsigned int temp; + unsigned int intcfg; + int serviced = IRQ_NONE; + + intcfg = smsc911x_reg_read(pdata, INT_CFG); + intsts = smsc911x_reg_read(pdata, INT_STS); + inten = smsc911x_reg_read(pdata, INT_EN); + + printk_debug("New IRQ: intsts 0x%08x\n", intsts); + + if ((intcfg & (INT_CFG_IRQ_INT_ | INT_CFG_IRQ_EN_)) != (INT_CFG_IRQ_INT_ | + INT_CFG_IRQ_EN_)) + return serviced; + + + if (unlikely(intsts & inten & INT_STS_SW_INT_)) { + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_SW_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS); + pdata->software_irq_signal = 1; + smp_wmb(); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) { + /* Called when there is a multicast update scheduled and + * it is now safe to complete the update */ + SMSC_TRACE("RX Stop interrupt"); + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RXSTOP_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS); + smsc911x_rx_multicast_update_workaround(pdata); + serviced = IRQ_HANDLED; + } + + if (intsts & inten & INT_STS_TDFA_) { + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + smsc911x_reg_write(temp, pdata, FIFO_INT); + smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS); + netif_wake_queue(dev); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXE_)) { + smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS); + serviced = IRQ_HANDLED; + } + + if (likely(intsts & inten & INT_STS_RSFL_)) { + if (likely(netif_rx_schedule_prep(dev, &pdata->napi))) { + /* Disable Rx interrupts and schedule NAPI poll */ + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RSFL_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + __netif_rx_schedule(dev, &pdata->napi); + } + + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_PHY_INT_)) { + smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS); + spin_lock(&pdata->phy_lock); + temp = smsc911x_phy_read(pdata, MII_INTSTS); + spin_unlock(&pdata->phy_lock); + SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp); + smsc911x_phy_update_linkmode(dev, 0); + serviced = IRQ_HANDLED; + } + return serviced; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +void smsc911x_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smsc911x_irqhandler(0, dev); + enable_irq(dev->irq); +} +#endif /* CONFIG_NET_POLL_CONTROLLER */ + +/* Standard ioctls for mii-tool */ +static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int chg_in_duplex; + int ret; + + if (!netif_running(dev)) + return -EINVAL; + ret = generic_mii_ioctl(&pdata->mii, if_mii(ifr), cmd, &chg_in_duplex); + if ((ret == 0) && (chg_in_duplex != 0)) + smsc911x_phy_update_duplex(dev); + + return ret; +} + +static int +smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + return mii_ethtool_gset(&pdata->mii, cmd); +} + +static int +smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return mii_ethtool_sset(&pdata->mii, cmd); +} + +static void smsc911x_ethtool_getdrvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver)); + strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version)); + strncpy(info->bus_info, dev->dev.bus_id, sizeof(info->bus_info)); +} + +static int smsc911x_ethtool_nwayreset(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return mii_nway_restart(&pdata->mii); +} + +static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + return pdata->msg_enable; +} + +static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + pdata->msg_enable = level; +} + +static int smsc911x_ethtool_getregslen(struct net_device *dev) +{ + return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) * + sizeof(u32); +} + +static void +smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, + void *buf) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + unsigned int i; + unsigned int j = 0; + u32 *data = buf; + + regs->version = pdata->idrev; + for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32))) + data[j++] = smsc911x_reg_read(pdata, i); + + spin_lock_irqsave(&pdata->phy_lock, flags); + for (i = MAC_CR; i <= WUCSR; i++) + data[j++] = smsc911x_mac_read(pdata, i); + for (i = 0; i <= 31; i++) + data[j++] = smsc911x_phy_read(pdata, i); + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata) +{ + unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG); + temp &= ~GPIO_CFG_EEPR_EN_; + smsc911x_reg_write(temp, pdata, GPIO_CFG); + msleep(1); +} + +static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op) +{ + int timeout = 100; + u32 e2cmd; + + SMSC_TRACE("op 0x%08x", op); + if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) { + SMSC_WARNING("Busy at start"); + return -EBUSY; + } + + e2cmd = op | E2P_CMD_EPC_BUSY_; + smsc911x_reg_write(e2cmd, pdata, E2P_CMD); + + do { + msleep(1); + e2cmd = smsc911x_reg_read(pdata, E2P_CMD); + } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--)); + + if (!timeout) { + SMSC_TRACE("TIMED OUT"); + return -EAGAIN; + } + + if (e2cmd & E2P_CMD_EPC_TIMEOUT_) { + SMSC_TRACE("Error occured during eeprom operation"); + return -EINVAL; + } + + return 0; +} + +static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata, + u8 address, u8 *data) +{ + u32 op = E2P_CMD_EPC_CMD_READ_ | address; + int ret; + + SMSC_TRACE("address 0x%x", address); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) + data[address] = smsc911x_reg_read(pdata, E2P_DATA); + + return ret; +} + +static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata, + u8 address, u8 data) +{ + u32 op = E2P_CMD_EPC_CMD_ERASE_ | address; + int ret; + + SMSC_TRACE("address 0x%x, data 0x%x", address, data); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) { + op = E2P_CMD_EPC_CMD_WRITE_ | address; + smsc911x_reg_write((u32)data, pdata, E2P_DATA); + ret = smsc911x_eeprom_send_cmd(pdata, op); + } + + return ret; +} + +static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev) +{ + return SMSC911X_EEPROM_SIZE; +} + +static int smsc911x_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + u8 eeprom_data[SMSC911X_EEPROM_SIZE]; + int len; + int i; + + smsc911x_eeprom_enable_access(pdata); + + len = min(eeprom->len, SMSC911X_EEPROM_SIZE); + for (i = 0; i < len; i++) { + int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data); + if (ret < 0) { + eeprom->len = 0; + return ret; + } + } + + memcpy(data, &eeprom_data[eeprom->offset], len); + eeprom->len = len; + return 0; +} + +static int smsc911x_ethtool_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int ret; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_eeprom_enable_access(pdata); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_); + ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_); + + /* Single byte write, according to man page */ + eeprom->len = 1; + + return ret; +} + +static int smsc911x_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct smsc911x_data *pdata; + + /* Check for unsupported options */ + if (wol->wolopts & (WAKE_MAGICSECURE | WAKE_UCAST | WAKE_MCAST + | WAKE_BCAST | WAKE_ARP)) + return -EINVAL; + + pdata = netdev_priv(dev); + + /* When disable the WOL options need to disable the PHY-interrupts too */ + if (!wol->wolopts) { + printk_pmdbg("[ WOL ] Disabling all sources\n"); + pdata->pmt_ctrl &= ~(PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_); + pdata->phy_intmsk &= ~PHY_INTMSK_ENERGYON_; + pdata->mac_wucsr = 0; + goto exit_set_wol; + } + + /* + * For the magic packet we MUST configure the MAC too, but we can't do it + * at this point, cause the controller stops working. + */ + if (wol->wolopts & WAKE_MAGIC) { + printk_pmdbg("WOL: Enabling magic frame\n"); + pdata->mac_wucsr |= WUCSR_MPEN_; + pdata->pmt_ctrl |= PMT_CTRL_WOL_EN_; + } + + /* For the PHY-wakeup we must use the energy detection */ + if (wol->wolopts & WAKE_PHY) { + printk_pmdbg("[ WOL ] Enabling PHY energy\n"); + pdata->phy_intmsk |= PHY_INTMSK_ENERGYON_; + pdata->pmt_ctrl |= PMT_CTRL_ED_EN_; + } + + exit_set_wol: + return 0; +} + +/* Function for getting the infos about the WOL */ +static void smsc911x_ethtool_get_wol(struct net_device *net_dev, + struct ethtool_wolinfo *wol) +{ + /* Only for magic and PHY power detection available up now */ + wol->supported = WAKE_MAGIC | WAKE_PHY; +} + +static struct ethtool_ops smsc911x_ethtool_ops = { + .get_settings = smsc911x_ethtool_getsettings, + .set_settings = smsc911x_ethtool_setsettings, + .get_link = ethtool_op_get_link, + .get_drvinfo = smsc911x_ethtool_getdrvinfo, + .nway_reset = smsc911x_ethtool_nwayreset, + .get_msglevel = smsc911x_ethtool_getmsglevel, + .set_msglevel = smsc911x_ethtool_setmsglevel, + .get_regs_len = smsc911x_ethtool_getregslen, + .get_regs = smsc911x_ethtool_getregs, + .get_eeprom_len = smsc911x_ethtool_get_eeprom_len, + .get_eeprom = smsc911x_ethtool_get_eeprom, + .set_eeprom = smsc911x_ethtool_set_eeprom, + .get_wol = smsc911x_ethtool_get_wol, + .set_wol = smsc911x_ethtool_set_wol, +}; + + +static int smsc911x_set_mac(struct net_device *dev, void *addr) +{ + unsigned int reg; + int retval; + unsigned long flags; + struct smsc911x_data *pdata; + unsigned int low, high; + struct sockaddr *paddr = addr; + + printk_debug("Set mac called\n"); + + pdata = netdev_priv(dev); + + spin_lock_irqsave(&pdata->phy_lock, flags); + + /* First check that the MAC is not busy */ + reg = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(reg & MAC_CSR_CMD_CSR_BUSY_)) { + printk_err("smsc911x_mac_read failed, MAC busy at entry"); + retval = -EBUSY; + goto exit_unlock; + } + + /* Get the MAC address */ + high = 0; + memcpy(&low, &(paddr->sa_data[0]), 4); + memcpy(&high, &(paddr->sa_data[4]), 2); + printk_debug("Going to set the MAC %04X%08X\n", high, low); + + /* Now set the high address */ + smsc911x_reg_write(high, pdata, MAC_CSR_DATA); + smsc911x_reg_write(ADDRH | MAC_CSR_CMD_CSR_BUSY_, pdata, MAC_CSR_CMD); + reg = smsc911x_reg_read(pdata, BYTE_TEST); + if (!smsc911x_mac_notbusy(pdata)) { + retval = -EBUSY; + goto exit_unlock; + } + + /* First set the low address */ + smsc911x_reg_write(low, pdata, MAC_CSR_DATA); + smsc911x_reg_write(ADDRL | MAC_CSR_CMD_CSR_BUSY_, pdata, MAC_CSR_CMD); + reg = smsc911x_reg_read(pdata, BYTE_TEST); + if (!smsc911x_mac_notbusy(pdata)) { + retval = -EBUSY; + goto exit_unlock; + } + + /* And save the IP inside the driver structure */ + memcpy(dev->dev_addr, paddr->sa_data, dev->addr_len); + + printk_debug("MAC successful changed to %02X%08X\n", + smsc911x_mac_read(pdata, ADDRH), + smsc911x_mac_read(pdata, ADDRL)); + + retval = 0; + + exit_unlock: + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + return retval; +} + +/* Initializing private device structures */ +static int smsc911x_init(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + SMSC_TRACE("Driver Parameters:"); + SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr); + SMSC_TRACE("IRQ: %d", dev->irq); + SMSC_TRACE("PHY will be autodetected."); + + if (pdata->ioaddr == 0) { + SMSC_WARNING("pdata->ioaddr: 0x00000000"); + return -ENODEV; + } + + /* Default generation to zero (all workarounds apply) */ + pdata->generation = 0; + + pdata->idrev = smsc911x_reg_read(pdata, ID_REV); + if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) { + SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, " + "idrev: 0x%08X", pdata->idrev); + SMSC_TRACE("This may mean the chip is set for 32 bit while " + "the bus is reading as 16 bit"); + return -ENODEV; + } + switch (pdata->idrev & 0xFFFF0000) { + case 0x01180000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 0; + break; + case 1UL: + SMSC_TRACE + ("LAN9118 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9118 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01170000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 0; + break; + case 1UL: + SMSC_TRACE + ("LAN9117 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9117 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01160000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + case 1UL: + SMSC_TRACE + ("LAN9116 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9116 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01150000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + case 1UL: + SMSC_TRACE + ("LAN9115 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9115 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x118A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9218 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9218 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x117A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9217 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9217 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x116A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9216 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9216 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x115A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9215 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9215 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x92100000UL: + case 0x92110000UL: + case 0x92200000UL: + case 0x92210000UL: + /* LAN9210/LAN9211/LAN9220/LAN9221 */ + pdata->generation = 4; + break; + + default: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + } + + if (pdata->generation == 0) + SMSC_WARNING("This driver is not intended " + "for this chip revision"); + + ether_setup(dev); + dev->open = smsc911x_open; + dev->stop = smsc911x_stop; + dev->hard_start_xmit = smsc911x_hard_start_xmit; + dev->get_stats = smsc911x_get_stats; + dev->set_multicast_list = smsc911x_set_multicast_list; + dev->flags |= IFF_MULTICAST; + dev->do_ioctl = smsc911x_do_ioctl; + dev->set_mac_address = smsc911x_set_mac; + netif_napi_add(dev, &pdata->napi, smsc911x_poll, 64); + dev->ethtool_ops = &smsc911x_ethtool_ops; + +#ifdef CONFIG_NET_POLL_CONTROLLER + dev->poll_controller = smsc911x_poll_controller; +#endif /* CONFIG_NET_POLL_CONTROLLER */ + + pdata->mii.phy_id_mask = 0x1f; + pdata->mii.reg_num_mask = 0x1f; + pdata->mii.force_media = 0; + pdata->mii.full_duplex = 0; + pdata->mii.dev = dev; + pdata->mii.mdio_read = smsc911x_mdio_read; + pdata->mii.mdio_write = smsc911x_mdio_write; + + pdata->msg_enable = NETIF_MSG_LINK; + + return 0; +} + +static int smsc911x_drv_remove(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + + dev = platform_get_drvdata(pdev); + BUG_ON(!dev); + pdata = netdev_priv(dev); + BUG_ON(!pdata); + BUG_ON(!pdata->ioaddr); + + SMSC_TRACE("Stopping driver."); + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_irq(dev->irq, dev); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + platform_get_resource(pdev, IORESOURCE_MEM, 0); + + release_mem_region(res->start, res->end - res->start); + + iounmap(pdata->ioaddr); + + free_netdev(dev); + + return 0; +} + +static int smsc911x_drv_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + unsigned int intcfg = 0; + int res_size; + int retval; + + printk(KERN_INFO "%s: Driver version %s.\n", SMSC_CHIPNAME, + SMSC_DRV_VERSION); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_WARNING "%s: Could not allocate resource.\n", + SMSC_CHIPNAME); + retval = -ENODEV; + goto out_0; + } + res_size = res->end - res->start; + + if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) { + retval = -EBUSY; + goto out_0; + } + + dev = alloc_etherdev(sizeof(struct smsc911x_data)); + if (!dev) { + printk(KERN_WARNING "%s: Could not allocate device.\n", + SMSC_CHIPNAME); + retval = -ENOMEM; + goto out_release_io_1; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + pdata = netdev_priv(dev); + pdata->netdev = dev; + + dev->irq = platform_get_irq(pdev, 0); + pdata->ioaddr = ioremap_nocache(res->start, res_size); + + /* copy config parameters across if present, otherwise pdata + * defaults to zeros */ + if (pdev->dev.platform_data) { + struct smc911x_platdata *config = pdev->dev.platform_data; + pdata->irq_polarity = config->irq_polarity; + pdata->irq_flags = config->irq_flags; + } + + if (pdata->ioaddr == NULL) { + SMSC_WARNING("Error smsc911x base address invalid"); + retval = -ENOMEM; + goto out_free_netdev_2; + } + + retval = smsc911x_init(dev); + if (retval < 0) + goto out_unmap_io_3; + + /* configure irq polarity and type before connecting isr */ + if (pdata->irq_polarity) + intcfg |= INT_CFG_IRQ_POL_; + + /* + * @XXX: The "irq_type" is not used at this moment, because we are using + * the same platform-data as the driver from the Vanilla-kernel. + * (Luis Galdos) + */ + if (pdata->irq_type) + intcfg |= INT_CFG_IRQ_TYPE_; + + smsc911x_reg_write(intcfg, pdata, INT_CFG); + + retval = request_irq(dev->irq, smsc911x_irqhandler, + pdata->irq_flags, + SMSC_CHIPNAME, dev); + if (retval) { + SMSC_WARNING("Unable to claim requested irq: %d", dev->irq); + goto out_unmap_io_3; + } + + platform_set_drvdata(pdev, dev); + + retval = register_netdev(dev); + if (retval) { + SMSC_WARNING("Error %i registering device", retval); + goto out_unset_drvdata_4; + } else { + SMSC_TRACE("Network interface: \"%s\"", dev->name); + } + + spin_lock_init(&pdata->phy_lock); + + spin_lock_irq(&pdata->phy_lock); + + /* Check if mac address has been specified when bringing interface up */ + if (is_valid_ether_addr(dev->dev_addr)) { + smsc911x_set_mac(dev, dev->dev_addr); + SMSC_TRACE("MAC Address is specified by configuration"); + } else { + /* Try reading mac address from device. if EEPROM is present + * it will already have been set */ + u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH); + u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL); + dev->dev_addr[0] = (u8)(mac_low32); + dev->dev_addr[1] = (u8)(mac_low32 >> 8); + dev->dev_addr[2] = (u8)(mac_low32 >> 16); + dev->dev_addr[3] = (u8)(mac_low32 >> 24); + dev->dev_addr[4] = (u8)(mac_high16); + dev->dev_addr[5] = (u8)(mac_high16 >> 8); + + if (is_valid_ether_addr(dev->dev_addr)) { + /* eeprom values are valid so use them */ + SMSC_TRACE("Mac Address is read from LAN911x EEPROM"); + } else { + /* eeprom values are invalid, generate random MAC */ + random_ether_addr(dev->dev_addr); + smsc911x_set_mac_address(pdata, dev->dev_addr); + SMSC_TRACE("MAC Address is set to random_ether_addr"); + } + } + + spin_unlock_irq(&pdata->phy_lock); + + printk_info("%s: MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + + /* Enable the wakeup over this device (Luis Galdos) */ + device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_enable(&pdev->dev, 0); + return 0; + +out_unset_drvdata_4: + platform_set_drvdata(pdev, NULL); + free_irq(dev->irq, dev); +out_unmap_io_3: + iounmap(pdata->ioaddr); +out_free_netdev_2: + free_netdev(dev); +out_release_io_1: + release_mem_region(res->start, res->end - res->start); +out_0: + return retval; +} + +/* Enter in the suspend mode */ +#if defined(CONFIG_PM) + +/* + * For the mode D1 we MUST left the interrupts enabled + */ +static int smsc911x_drv_state_wakeup(struct smsc911x_data *pdata, int mode) +{ + int retval; + unsigned long regval; + + retval = 0; + + if (mode != 1 && mode != 2) + return -EINVAL; + + /* Clear already received WUs */ + regval = smsc911x_mac_read(pdata, WUCSR); + regval &= ~(WUCSR_MPR_ | WUCSR_WUFR_); + regval |= pdata->mac_wucsr; /* Magic packet enable 'WUCSR_MPEN_' */ + printk_pmdbg("[ SUSP ] WUCSR 0x%08lx\n", regval); + smsc911x_mac_write(pdata, WUCSR, regval); + + /* For the D2 we must enable the PHY interrupt for the energy detection */ + regval = smsc911x_reg_read(pdata, INT_EN); + regval |= (INT_EN_PME_INT_EN_ | INT_EN_PHY_INT_EN_); + printk_pmdbg("[ SUSP ] INT_EN 0x%08lx\n", regval); + smsc911x_reg_write(regval, pdata, INT_EN); + + if (mode /* @FIXME: Enabled only for D2 */) { + u16 phy_mode; + + phy_mode = smsc911x_phy_read(pdata, MII_INTMSK); + phy_mode |= PHY_INTMSK_ENERGYON_; + smsc911x_phy_write(pdata, MII_INTMSK, phy_mode); + } + + /* Clear the PM mode and clear the current wakeup status */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_WUPS_; + printk_pmdbg("[ SUSP ] PMT_CTRL 0x%08lx\n", regval); + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + /* Enable the PME at prior and the wake on LAN */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval |= pdata->pmt_ctrl; /* Enable the ENERGY detect or WOL interrupt */ + regval |= PMT_CTRL_PME_EN_; + + if (mode == 1) + regval |= PMT_CTRL_PM_MODE_D1_; + else + regval |= PMT_CTRL_PM_MODE_D2_; + + printk_pmdbg("[ SUSP ] PMT_CTRL 0x%08lx\n", regval); + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + return retval; +} + +/* For the state D2 we must disable the host-interrupts */ +static int smsc911x_drv_state_d2(struct smsc911x_data *pdata) +{ + unsigned long regval; + + /* Disable the interrupts of the controller */ + regval = smsc911x_reg_read(pdata, INT_CFG); + regval &= ~INT_CFG_IRQ_EN_; + smsc911x_reg_write(regval, pdata, INT_CFG); + + /* Set the phy to the power down mode */ + regval = smsc911x_phy_read(pdata, MII_BMCR); + regval |= BMCR_PDOWN; + smsc911x_phy_write(pdata, MII_BMCR, regval); + + /* + * Enter into the power mode D2 (the controller doesn't + * support the mode D3) + */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_PM_MODE_D2_; + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + return 0; +} + +static int smsc911x_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *ndev; + struct smsc911x_data *pdata; + int retval; + + ndev = platform_get_drvdata(pdev); + pdata = netdev_priv(ndev); + + if (!ndev) + return -ENODEV; + + /* @FIXME: Implement the other supported power modes of the smsc911x */ + if (state.event != PM_EVENT_SUSPEND) + return -ENOTSUPP; + + if (netif_running(ndev)) { + + /* The below code is coming from the WinCE guys */ + netif_device_detach(ndev); + + /* + * If configured as wakeup-source enter the mode D1 for packet + * detection using the standard IRQ-line + */ + if (device_may_wakeup(&pdev->dev)) { + + /* + * Sanity check for verifying that a wakeup-source was + * configured from the user space. If the energy-detect + * wakeup was enabled, then use the D2 for entering into the + * power mode + */ + if (!(pdata->pmt_ctrl & (PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_))) { + printk_err("[ SUSP ] No WOL source defined.\n"); + retval = -EINVAL; + goto err_attach; + } + + /* + * By the WOL (magic packet, etc.) we can ONLY use the D1, but + * for the energy detect over the PHY we can change into D2 + */ + if (pdata->pmt_ctrl & PMT_CTRL_WOL_EN_) { + printk_pmdbg("[ SUSP ] Preparing D1 with wakeup\n"); + smsc911x_drv_state_wakeup(pdata, 1); + } else { + /* @TEST: Use first only D1 for the wakups */ + printk_pmdbg("[ SUSP ] Preparing D2 with wakeup\n"); + smsc911x_drv_state_wakeup(pdata, 2); + } + + enable_irq_wake(ndev->irq); + + } else { + /* + * Enter into the power mode D2 (the controller doesn't + * support the mode D3) + */ + smsc911x_drv_state_d2(pdata); + } + } + + return 0; + +err_attach: + netif_device_attach(ndev); + return retval; +} + +static int smsc911x_drv_resume(struct platform_device *pdev) +{ + int retval; + struct net_device *ndev; + unsigned long pmt_ctrl; + + pmt_ctrl = 0; + ndev = platform_get_drvdata(pdev); + retval = 0; + if (ndev) { + struct smsc911x_data *pdata = netdev_priv(ndev); + + if (netif_running(ndev)) { + unsigned long timeout; + unsigned long regval, pmt_ctrl; + + /* Assert the byte test register for waking up */ + smsc911x_reg_write(0x0, pdata, BYTE_TEST); + + timeout = 100000; + do { + timeout--; + regval = smsc911x_reg_read(pdata, PMT_CTRL); + udelay(1); + } while (timeout && !(regval & PMT_CTRL_READY_)); + + if (!timeout) { + printk_err("Wakeup timeout by the controller\n"); + retval = -EBUSY; + goto exit_resume; + } + + /* + * Check if we received a PM interrupt + * Please take note that we are supporting ONLY the Wake On LAN + * interrupts, and not the energy-on + * (Luis Galdos) + */ + pmt_ctrl = smsc911x_reg_read(pdata, PMT_CTRL); + regval = smsc911x_reg_read(pdata, INT_STS); + printk_pmdbg("[ WAKE ] PMT_CTRL 0x%08lx\n", pmt_ctrl); + printk_pmdbg("[ WAKE ] INT_STS 0x%08lx\n", regval); + if (regval & (INT_STS_PME_INT_ | INT_STS_PHY_INT_)) { + + /* Disable the power management interrupts */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + pmt_ctrl = regval & (PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_); + regval &= ~(PMT_CTRL_WOL_EN_ | PMT_CTRL_PME_EN_ | + PMT_CTRL_ED_EN_); + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + /* Disable the PM interrupts */ + regval = smsc911x_reg_read(pdata, INT_EN); + regval &= ~(INT_EN_PME_INT_EN_ | INT_EN_PHY_INT_EN_); + smsc911x_reg_write(regval, pdata, INT_EN); + + /* Disable the wakeup-events on the MAC */ + regval = smsc911x_mac_read(pdata, WUCSR); + regval &= ~(WUCSR_MPEN_); + smsc911x_mac_write(pdata, WUCSR, regval); + } + + /* @XXX: Clear only the interrupts that were generated */ + regval = (INT_STS_PME_INT_ | INT_STS_PHY_INT_); + smsc911x_reg_write(regval, pdata, INT_STS); + + /* Set the controller into the state D0 */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_PM_MODE_D0_; + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + /* Paranoic sanity checks */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + if (regval & PMT_CTRL_PM_MODE_) + printk_err("PM mode isn't disabled (0x%04lx)\n", regval); + + if (!(regval & PMT_CTRL_READY_)) { + retval = -EBUSY; + printk_err("Device is still NOT ready.\n"); + goto exit_resume; + } + + regval = smsc911x_phy_read(pdata, MII_BMCR); + regval &= ~BMCR_PDOWN; + smsc911x_phy_write(pdata, MII_BMCR, regval); + + /* Reenable the interrupts now */ + regval = smsc911x_reg_read(pdata, INT_CFG); + regval |= INT_CFG_IRQ_EN_; + smsc911x_reg_write(regval, pdata, INT_CFG); + + /* Reset the wakeup control and status register */ + smsc911x_mac_write(pdata, WUCSR, 0x00); + + netif_device_attach(ndev); + } + } + +exit_resume: + return retval; +} +#else +#define smsc911x_drv_suspend NULL +#define smsc911x_drv_resume NULL +#endif /* defined(CONFIG_PM) */ + +static struct platform_driver smsc911x_driver = { + .probe = smsc911x_drv_probe, + .remove = smsc911x_drv_remove, + .suspend = smsc911x_drv_suspend, + .resume = smsc911x_drv_resume, + .driver = { + .name = SMSC_CHIPNAME, + }, +}; + +/* Entry point for loading the module */ +static int __init smsc911x_init_module(void) +{ + printk(KERN_INFO "SMSC 911X: Starting the platform driver.\n"); + return platform_driver_register(&smsc911x_driver); +} + +/* entry point for unloading the module */ +static void __exit smsc911x_cleanup_module(void) +{ + platform_driver_unregister(&smsc911x_driver); +} + +module_init(smsc911x_init_module); +module_exit(smsc911x_cleanup_module); diff --git a/drivers/net/smsc9118/smsc911x.h b/drivers/net/smsc9118/smsc911x.h new file mode 100644 index 000000000000..a2ee96aeb989 --- /dev/null +++ b/drivers/net/smsc9118/smsc911x.h @@ -0,0 +1,393 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2007 SMSC + * Copyright (C) 2005 ARM + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ***************************************************************************/ +#ifndef __SMSC911X_H__ +#define __SMSC911X_H__ + +#if defined(CONFIG_MACH_CC9M2443JS) || defined(CONFIG_MACH_CCW9M2443JS) +# define SMSC_CAN_USE_32BIT 0 +#else +# define SMSC_CAN_USE_32BIT 1 +#endif + + +//#define SMSC_CAN_USE_32BIT 1 +#define TX_FIFO_LOW_THRESHOLD (u32)1600 +#define SMSC911X_EEPROM_SIZE (u32)7 +#define USE_DEBUG 0 +//#define USE_DEBUG 2 + +/* implements a PHY loopback test at initialisation time, to ensure a packet + * can be succesfully looped back */ +#define USE_PHY_WORK_AROUND + +/* 10/100 LED link-state inversion when media is disconnected */ +/* #define USE_LED1_WORK_AROUND */ + +/* platform_device configuration data, should be assigned to + * the platform_device's dev.platform_data */ +struct smsc911x_platform_config { + unsigned int irq_polarity; + unsigned int irq_type; +}; + +#if USE_DEBUG >= 1 +#define SMSC_WARNING(fmt, args...) \ + printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \ + __FUNCTION__ , ## args) +#else +#define SMSC_WARNING(msg, args...) +#endif /* USE_DEBUG >= 1 */ + +#if USE_DEBUG >= 2 +#define SMSC_TRACE(fmt,args...) \ + printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \ + __FUNCTION__ , ## args) +#else +#define SMSC_TRACE(msg, args...) +#endif /* USE_DEBUG >= 2 */ + +/* SMSC911x registers and bitfields */ +#define RX_DATA_FIFO 0x00 + +#define TX_DATA_FIFO 0x20 +#define TX_CMD_A_ON_COMP_ 0x80000000 +#define TX_CMD_A_BUF_END_ALGN_ 0x03000000 +#define TX_CMD_A_4_BYTE_ALGN_ 0x00000000 +#define TX_CMD_A_16_BYTE_ALGN_ 0x01000000 +#define TX_CMD_A_32_BYTE_ALGN_ 0x02000000 +#define TX_CMD_A_DATA_OFFSET_ 0x001F0000 +#define TX_CMD_A_FIRST_SEG_ 0x00002000 +#define TX_CMD_A_LAST_SEG_ 0x00001000 +#define TX_CMD_A_BUF_SIZE_ 0x000007FF +#define TX_CMD_B_PKT_TAG_ 0xFFFF0000 +#define TX_CMD_B_ADD_CRC_DISABLE_ 0x00002000 +#define TX_CMD_B_DISABLE_PADDING_ 0x00001000 +#define TX_CMD_B_PKT_BYTE_LENGTH_ 0x000007FF + +#define RX_STATUS_FIFO 0x40 +#define RX_STS_ES_ 0x00008000 +#define RX_STS_MCAST_ 0x00000400 + +#define RX_STATUS_FIFO_PEEK 0x44 + +#define TX_STATUS_FIFO 0x48 +#define TX_STS_ES_ 0x00008000 + +#define TX_STATUS_FIFO_PEEK 0x4C + +#define ID_REV 0x50 +#define ID_REV_CHIP_ID_ 0xFFFF0000 +#define ID_REV_REV_ID_ 0x0000FFFF + +#define INT_CFG 0x54 +#define INT_CFG_INT_DEAS_ 0xFF000000 +#define INT_CFG_INT_DEAS_CLR_ 0x00004000 +#define INT_CFG_INT_DEAS_STS_ 0x00002000 +#define INT_CFG_IRQ_INT_ 0x00001000 +#define INT_CFG_IRQ_EN_ 0x00000100 +#define INT_CFG_IRQ_POL_ 0x00000010 +#define INT_CFG_IRQ_TYPE_ 0x00000001 + +#define INT_STS 0x58 +#define INT_STS_SW_INT_ 0x80000000 +#define INT_STS_TXSTOP_INT_ 0x02000000 +#define INT_STS_RXSTOP_INT_ 0x01000000 +#define INT_STS_RXDFH_INT_ 0x00800000 +#define INT_STS_RXDF_INT_ 0x00400000 +#define INT_STS_TX_IOC_ 0x00200000 +#define INT_STS_RXD_INT_ 0x00100000 +#define INT_STS_GPT_INT_ 0x00080000 +#define INT_STS_PHY_INT_ 0x00040000 +#define INT_STS_PME_INT_ 0x00020000 +#define INT_STS_TXSO_ 0x00010000 +#define INT_STS_RWT_ 0x00008000 +#define INT_STS_RXE_ 0x00004000 +#define INT_STS_TXE_ 0x00002000 +#define INT_STS_TDFU_ 0x00000800 +#define INT_STS_TDFO_ 0x00000400 +#define INT_STS_TDFA_ 0x00000200 +#define INT_STS_TSFF_ 0x00000100 +#define INT_STS_TSFL_ 0x00000080 +#define INT_STS_RXDF_ 0x00000040 +#define INT_STS_RDFL_ 0x00000020 +#define INT_STS_RSFF_ 0x00000010 +#define INT_STS_RSFL_ 0x00000008 +#define INT_STS_GPIO2_INT_ 0x00000004 +#define INT_STS_GPIO1_INT_ 0x00000002 +#define INT_STS_GPIO0_INT_ 0x00000001 + +#define INT_EN 0x5C +#define INT_EN_SW_INT_EN_ 0x80000000 +#define INT_EN_TXSTOP_INT_EN_ 0x02000000 +#define INT_EN_RXSTOP_INT_EN_ 0x01000000 +#define INT_EN_RXDFH_INT_EN_ 0x00800000 +#define INT_EN_TIOC_INT_EN_ 0x00200000 +#define INT_EN_RXD_INT_EN_ 0x00100000 +#define INT_EN_GPT_INT_EN_ 0x00080000 +#define INT_EN_PHY_INT_EN_ 0x00040000 +#define INT_EN_PME_INT_EN_ 0x00020000 +#define INT_EN_TXSO_EN_ 0x00010000 +#define INT_EN_RWT_EN_ 0x00008000 +#define INT_EN_RXE_EN_ 0x00004000 +#define INT_EN_TXE_EN_ 0x00002000 +#define INT_EN_TDFU_EN_ 0x00000800 +#define INT_EN_TDFO_EN_ 0x00000400 +#define INT_EN_TDFA_EN_ 0x00000200 +#define INT_EN_TSFF_EN_ 0x00000100 +#define INT_EN_TSFL_EN_ 0x00000080 +#define INT_EN_RXDF_EN_ 0x00000040 +#define INT_EN_RDFL_EN_ 0x00000020 +#define INT_EN_RSFF_EN_ 0x00000010 +#define INT_EN_RSFL_EN_ 0x00000008 +#define INT_EN_GPIO2_INT_ 0x00000004 +#define INT_EN_GPIO1_INT_ 0x00000002 +#define INT_EN_GPIO0_INT_ 0x00000001 + +#define BYTE_TEST 0x64 + +#define FIFO_INT 0x68 +#define FIFO_INT_TX_AVAIL_LEVEL_ 0xFF000000 +#define FIFO_INT_TX_STS_LEVEL_ 0x00FF0000 +#define FIFO_INT_RX_AVAIL_LEVEL_ 0x0000FF00 +#define FIFO_INT_RX_STS_LEVEL_ 0x000000FF + +#define RX_CFG 0x6C +#define RX_CFG_RX_END_ALGN_ 0xC0000000 +#define RX_CFG_RX_END_ALGN4_ 0x00000000 +#define RX_CFG_RX_END_ALGN16_ 0x40000000 +#define RX_CFG_RX_END_ALGN32_ 0x80000000 +#define RX_CFG_RX_DMA_CNT_ 0x0FFF0000 +#define RX_CFG_RX_DUMP_ 0x00008000 +#define RX_CFG_RXDOFF_ 0x00001F00 + +#define TX_CFG 0x70 +#define TX_CFG_TXS_DUMP_ 0x00008000 +#define TX_CFG_TXD_DUMP_ 0x00004000 +#define TX_CFG_TXSAO_ 0x00000004 +#define TX_CFG_TX_ON_ 0x00000002 +#define TX_CFG_STOP_TX_ 0x00000001 + +#define HW_CFG 0x74 +#define HW_CFG_TTM_ 0x00200000 +#define HW_CFG_SF_ 0x00100000 +#define HW_CFG_TX_FIF_SZ_ 0x000F0000 +#define HW_CFG_TR_ 0x00003000 +#define HW_CFG_SRST_ 0x00000001 + +/* only available on 115/117 */ +#define HW_CFG_PHY_CLK_SEL_ 0x00000060 +#define HW_CFG_PHY_CLK_SEL_INT_PHY_ 0x00000000 +#define HW_CFG_PHY_CLK_SEL_EXT_PHY_ 0x00000020 +#define HW_CFG_PHY_CLK_SEL_CLK_DIS_ 0x00000040 +#define HW_CFG_SMI_SEL_ 0x00000010 +#define HW_CFG_EXT_PHY_DET_ 0x00000008 +#define HW_CFG_EXT_PHY_EN_ 0x00000004 +#define HW_CFG_SRST_TO_ 0x00000002 + +/* only available on 116/118 */ +#define HW_CFG_32_16_BIT_MODE_ 0x00000004 + +#define RX_DP_CTRL 0x78 +#define RX_DP_CTRL_RX_FFWD_ 0x80000000 + +#define RX_FIFO_INF 0x7C +#define RX_FIFO_INF_RXSUSED_ 0x00FF0000 +#define RX_FIFO_INF_RXDUSED_ 0x0000FFFF + +#define TX_FIFO_INF 0x80 +#define TX_FIFO_INF_TSUSED_ 0x00FF0000 +#define TX_FIFO_INF_TDFREE_ 0x0000FFFF + +#define PMT_CTRL 0x84 +#define PMT_CTRL_PM_MODE_ 0x00003000 +#define PMT_CTRL_PM_MODE_D0_ 0x00000000 +#define PMT_CTRL_PM_MODE_D1_ 0x00001000 +#define PMT_CTRL_PM_MODE_D2_ 0x00002000 +#define PMT_CTRL_PM_MODE_D3_ 0x00003000 +#define PMT_CTRL_PHY_RST_ 0x00000400 +#define PMT_CTRL_WOL_EN_ 0x00000200 +#define PMT_CTRL_ED_EN_ 0x00000100 +#define PMT_CTRL_PME_TYPE_ 0x00000040 +#define PMT_CTRL_WUPS_ 0x00000030 +#define PMT_CTRL_WUPS_NOWAKE_ 0x00000000 +#define PMT_CTRL_WUPS_ED_ 0x00000010 +#define PMT_CTRL_WUPS_WOL_ 0x00000020 +#define PMT_CTRL_WUPS_MULTI_ 0x00000030 +#define PMT_CTRL_PME_IND_ 0x00000008 +#define PMT_CTRL_PME_POL_ 0x00000004 +#define PMT_CTRL_PME_EN_ 0x00000002 +#define PMT_CTRL_READY_ 0x00000001 + +#define GPIO_CFG 0x88 +#define GPIO_CFG_LED3_EN_ 0x40000000 +#define GPIO_CFG_LED2_EN_ 0x20000000 +#define GPIO_CFG_LED1_EN_ 0x10000000 +#define GPIO_CFG_GPIO2_INT_POL_ 0x04000000 +#define GPIO_CFG_GPIO1_INT_POL_ 0x02000000 +#define GPIO_CFG_GPIO0_INT_POL_ 0x01000000 +#define GPIO_CFG_EEPR_EN_ 0x00700000 +#define GPIO_CFG_GPIOBUF2_ 0x00040000 +#define GPIO_CFG_GPIOBUF1_ 0x00020000 +#define GPIO_CFG_GPIOBUF0_ 0x00010000 +#define GPIO_CFG_GPIODIR2_ 0x00000400 +#define GPIO_CFG_GPIODIR1_ 0x00000200 +#define GPIO_CFG_GPIODIR0_ 0x00000100 +#define GPIO_CFG_GPIOD4_ 0x00000020 +#define GPIO_CFG_GPIOD3_ 0x00000010 +#define GPIO_CFG_GPIOD2_ 0x00000004 +#define GPIO_CFG_GPIOD1_ 0x00000002 +#define GPIO_CFG_GPIOD0_ 0x00000001 + +#define GPT_CFG 0x8C +#define GPT_CFG_TIMER_EN_ 0x20000000 +#define GPT_CFG_GPT_LOAD_ 0x0000FFFF + +#define GPT_CNT 0x90 +#define GPT_CNT_GPT_CNT_ 0x0000FFFF + +#define ENDIAN 0x98 + +#define FREE_RUN 0x9C + +#define RX_DROP 0xA0 + +#define MAC_CSR_CMD 0xA4 +#define MAC_CSR_CMD_CSR_BUSY_ 0x80000000 +#define MAC_CSR_CMD_R_NOT_W_ 0x40000000 +#define MAC_CSR_CMD_CSR_ADDR_ 0x000000FF + +#define MAC_CSR_DATA 0xA8 + +#define AFC_CFG 0xAC +#define AFC_CFG_AFC_HI_ 0x00FF0000 +#define AFC_CFG_AFC_LO_ 0x0000FF00 +#define AFC_CFG_BACK_DUR_ 0x000000F0 +#define AFC_CFG_FCMULT_ 0x00000008 +#define AFC_CFG_FCBRD_ 0x00000004 +#define AFC_CFG_FCADD_ 0x00000002 +#define AFC_CFG_FCANY_ 0x00000001 + +#define E2P_CMD 0xB0 +#define E2P_CMD_EPC_BUSY_ 0x80000000 +#define E2P_CMD_EPC_CMD_ 0x70000000 +#define E2P_CMD_EPC_CMD_READ_ 0x00000000 +#define E2P_CMD_EPC_CMD_EWDS_ 0x10000000 +#define E2P_CMD_EPC_CMD_EWEN_ 0x20000000 +#define E2P_CMD_EPC_CMD_WRITE_ 0x30000000 +#define E2P_CMD_EPC_CMD_WRAL_ 0x40000000 +#define E2P_CMD_EPC_CMD_ERASE_ 0x50000000 +#define E2P_CMD_EPC_CMD_ERAL_ 0x60000000 +#define E2P_CMD_EPC_CMD_RELOAD_ 0x70000000 +#define E2P_CMD_EPC_TIMEOUT_ 0x00000200 +#define E2P_CMD_MAC_ADDR_LOADED_ 0x00000100 +#define E2P_CMD_EPC_ADDR_ 0x000000FF + +#define E2P_DATA 0xB4 +#define E2P_DATA_EEPROM_DATA_ 0x000000FF +#define LAN_REGISTER_EXTENT 0x00000100 + +/* + * MAC Control and Status Register (Indirect Address) + * Offset (through the MAC_CSR CMD and DATA port) + */ +#define MAC_CR 0x01 +#define MAC_CR_RXALL_ 0x80000000 +#define MAC_CR_HBDIS_ 0x10000000 +#define MAC_CR_RCVOWN_ 0x00800000 +#define MAC_CR_LOOPBK_ 0x00200000 +#define MAC_CR_FDPX_ 0x00100000 +#define MAC_CR_MCPAS_ 0x00080000 +#define MAC_CR_PRMS_ 0x00040000 +#define MAC_CR_INVFILT_ 0x00020000 +#define MAC_CR_PASSBAD_ 0x00010000 +#define MAC_CR_HFILT_ 0x00008000 +#define MAC_CR_HPFILT_ 0x00002000 +#define MAC_CR_LCOLL_ 0x00001000 +#define MAC_CR_BCAST_ 0x00000800 +#define MAC_CR_DISRTY_ 0x00000400 +#define MAC_CR_PADSTR_ 0x00000100 +#define MAC_CR_BOLMT_MASK_ 0x000000C0 +#define MAC_CR_DFCHK_ 0x00000020 +#define MAC_CR_TXEN_ 0x00000008 +#define MAC_CR_RXEN_ 0x00000004 + +#define ADDRH 0x02 + +#define ADDRL 0x03 + +#define HASHH 0x04 + +#define HASHL 0x05 + +#define MII_ACC 0x06 +#define MII_ACC_PHY_ADDR_ 0x0000F800 +#define MII_ACC_MIIRINDA_ 0x000007C0 +#define MII_ACC_MII_WRITE_ 0x00000002 +#define MII_ACC_MII_BUSY_ 0x00000001 + +#define MII_DATA 0x07 + +#define FLOW 0x08 +#define FLOW_FCPT_ 0xFFFF0000 +#define FLOW_FCPASS_ 0x00000004 +#define FLOW_FCEN_ 0x00000002 +#define FLOW_FCBSY_ 0x00000001 + +#define VLAN1 0x09 + +#define VLAN2 0x0A + +#define WUFF 0x0B + +#define WUCSR 0x0C +#define WUCSR_GUE_ 0x00000200 +#define WUCSR_WUFR_ 0x00000040 +#define WUCSR_MPR_ 0x00000020 +#define WUCSR_WAKE_EN_ 0x00000004 +#define WUCSR_MPEN_ 0x00000002 + +/* + * Phy definitions (vendor-specific) + */ +#define LAN9118_PHY_ID 0x00C0001C + +#define MII_INTSTS 0x1D + +#define MII_INTMSK 0x1E +#define PHY_INTMSK_AN_RCV_ (1 << 1) +#define PHY_INTMSK_PDFAULT_ (1 << 2) +#define PHY_INTMSK_AN_ACK_ (1 << 3) +#define PHY_INTMSK_LNKDOWN_ (1 << 4) +#define PHY_INTMSK_RFAULT_ (1 << 5) +#define PHY_INTMSK_AN_COMP_ (1 << 6) +#define PHY_INTMSK_ENERGYON_ (1 << 7) +#define PHY_INTMSK_DEFAULT_ (PHY_INTMSK_ENERGYON_ | \ + PHY_INTMSK_AN_COMP_ | \ + PHY_INTMSK_RFAULT_ | \ + PHY_INTMSK_LNKDOWN_) + +#define ADVERTISE_PAUSE_ALL (ADVERTISE_PAUSE_CAP | \ + ADVERTISE_PAUSE_ASYM) + +#define LPA_PAUSE_ALL (LPA_PAUSE_CAP | \ + LPA_PAUSE_ASYM) + +#endif /* __SMSC911X_H__ */ diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c new file mode 100644 index 000000000000..6af63187bd3a --- /dev/null +++ b/drivers/net/smsc911x.c @@ -0,0 +1,2253 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2007 SMSC + * Copyright (C) 2005 ARM + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *************************************************************************** + * Rewritten, heavily based on smsc911x simple driver by SMSC. + * Partly uses io macros from smc91x.c by Nicolas Pitre + * + * Supported devices: + * LAN9115, LAN9116, LAN9117, LAN9118 + * LAN9215, LAN9216, LAN9217, LAN9218 + * + * History: + * 05/05/2005 bahadir.balban@arm.com + * - Transition to linux coding style + * - Platform driver and module interface + * + * 17/07/2006 steve.glendinning@smsc.com + * - Added support for LAN921x family + * - Added workaround for multicast filters + * + * 31/07/2006 steve.glendinning@smsc.com + * - Removed tasklet, using NAPI poll instead + * - Multiple device support + * - Large tidy-up following feedback from netdev list + * + * 03/08/2006 steve.glendinning@smsc.com + * - Added ethtool support + * - Convert to use generic MII interface + * + * 04/08/2006 bahadir.balban@arm.com + * - Added ethtool eeprom r/w support + * + * 17/06/2007 steve.glendinning@smsc.com + * - Incorporate changes from Bill Gatliff and Russell King + * + * 04/07/2007 steve.glendinning@smsc.com + * - move irq configuration to platform_device + * - fix link poller after interface is stopped and restarted + * + * 13/07/2007 bahadir.balban@arm.com + * - set irq polarity before requesting irq + */ + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/version.h> +#include <linux/bug.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <asm/io.h> +#include "smsc911x.h" + +#define SMSC_CHIPNAME "smsc911x" +#define SMSC_DRV_VERSION "2007-07-13" + +MODULE_LICENSE("GPL"); + +struct smsc911x_data { + void __iomem *ioaddr; + + unsigned int idrev; + unsigned int generation; /* used to decide which workarounds apply */ + + /* device configuration */ + unsigned int irq_polarity; + unsigned int irq_type; + + /* This needs to be acquired before calling any of below: + * smsc911x_mac_read(), smsc911x_mac_write() + * smsc911x_phy_read(), smsc911x_phy_write() + */ + spinlock_t phy_lock; + + struct net_device_stats stats; + struct mii_if_info mii; + unsigned int using_extphy; + u32 msg_enable; +#ifdef USE_LED1_WORK_AROUND + unsigned int gpio_setting; + unsigned int gpio_orig_setting; +#endif + struct net_device *netdev; + struct napi_struct napi; + struct timer_list link_poll_timer; + unsigned int stop_link_poll; + + unsigned int software_irq_signal; + +#ifdef USE_PHY_WORK_AROUND +#define MIN_PACKET_SIZE (64) + char loopback_tx_pkt[MIN_PACKET_SIZE]; + char loopback_rx_pkt[MIN_PACKET_SIZE]; + unsigned int resetcount; +#endif + + /* Members for Multicast filter workaround */ + unsigned int multicast_update_pending; + unsigned int set_bits_mask; + unsigned int clear_bits_mask; + unsigned int hashhi; + unsigned int hashlo; +}; + +#if SMSC_CAN_USE_32BIT + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + return readl(pdata->ioaddr + reg); +} + +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + writel(val, pdata->ioaddr + reg); +} + +#elif SMSC_CAN_USE_SPI + +static u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + u32 reg_val; + unsigned long flags; + + local_irq_save(flags); + reg_val = spi_cpld_read(reg); + local_irq_restore(flags); + + return reg_val; +} + +static void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + unsigned long flags; + + local_irq_save(flags); + spi_cpld_write(reg, val); + local_irq_restore(flags); +} + +#else /* SMSC_CAN_USE_32BIT */ + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + u32 reg_val; + unsigned long flags; + + /* these two 16-bit reads must be performed consecutively, so must + * not be interrupted by our own ISR (which would start another + * read operation) */ + local_irq_save(flags); + reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) | + ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16)); + local_irq_restore(flags); + + return reg_val; +} + +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + unsigned long flags; + + /* these two 16-bit writes must be performed consecutively, so must + * not be interrupted by our own ISR (which would start another + * read operation) */ + local_irq_save(flags); + writew(val & 0xFFFF, pdata->ioaddr + reg); + writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2); + local_irq_restore(flags); +} + +#endif /* SMSC_CAN_USE_32BIT */ + +/* Writes a packet to the TX_DATA_FIFO */ +static inline void +smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + while (wordcount--) + smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO); +} + +/* Reads a packet out of the RX_DATA_FIFO */ +static inline void +smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + while (wordcount--) + *buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO); +} + +/* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read + * and smsc911x_mac_write, so assumes phy_lock is held */ +static int smsc911x_mac_notbusy(struct smsc911x_data *pdata) +{ + int i; + u32 val; + + for (i = 0; i < 40; i++) { + val = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (!(val & MAC_CSR_CMD_CSR_BUSY_)) + return 1; + } + SMSC_WARNING("Timed out waiting for MAC not BUSY. " + "MAC_CSR_CMD: 0x%08X", val); + return 0; +} + +/* Fetches a MAC register value. Assumes phy_lock is acquired */ +static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset) +{ + unsigned int temp; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry"); + return 0xFFFFFFFF; + } + + /* Send the MAC cmd */ + smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_ + | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the read to happen */ + if (likely(smsc911x_mac_notbusy(pdata))) + return smsc911x_reg_read(pdata, MAC_CSR_DATA); + + SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read"); + return 0xFFFFFFFF; +} + +/* Set a mac register, phy_lock must be acquired before calling */ +static void smsc911x_mac_write(struct smsc911x_data *pdata, + unsigned int offset, u32 val) +{ + unsigned int temp; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry"); + return; + } + + /* Send data to write */ + smsc911x_reg_write(val, pdata, MAC_CSR_DATA); + + /* Write the actual data */ + smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata, + MAC_CSR_CMD); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the write to complete */ + if (likely(smsc911x_mac_notbusy(pdata))) + return; + + SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write"); +} + +/* Gets a phy register, phy_lock must be acquired before calling */ +static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index) +{ + unsigned int addr; + int i; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARNING("MII is busy in smsc911x_phy_read???"); + return 0; + } + + /* Set the address, index & direction (read from PHY) */ + addr = (((pdata->mii.phy_id) & 0x1F) << 11) + | ((index & 0x1F) << 6); + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for read to complete w/ timeout */ + for (i = 0; i < 100; i++) { + /* See if MII is finished yet */ + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + return smsc911x_mac_read(pdata, MII_DATA); + } + } + SMSC_WARNING("Timed out waiting for MII write to finish"); + return 0xFFFF; +} + +/* Sets a phy register, phy_lock must be acquired before calling */ +static void smsc911x_phy_write(struct smsc911x_data *pdata, + unsigned int index, u16 val) +{ + unsigned int addr; + int i; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARNING("MII is busy in smsc911x_write_phy???"); + return; + } + + /* Put the data to write in the MAC */ + smsc911x_mac_write(pdata, MII_DATA, val); + + /* Set the address, index & direction (write to PHY) */ + addr = (((pdata->mii.phy_id) & 0x1F) << 11) | + ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_; + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for write to complete w/ timeout */ + for (i = 0; i < 100; i++) { + /* See if MII is finished yet */ + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) + return; + } + SMSC_WARNING("Timed out waiting for MII write to finish"); +} + +static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + int reg; + + spin_lock_irqsave(&pdata->phy_lock, flags); + reg = smsc911x_phy_read(pdata, location); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + return reg; +} + +static void smsc911x_mdio_write(struct net_device *dev, int phy_id, + int location, int val) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, location, val); + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors. + * If something goes wrong, returns -ENODEV to revert back to internal phy. + * Performed at initialisation only, so interrupts are enabled */ +static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata) +{ + unsigned int address; + unsigned int hwcfg; + unsigned int phyid1; + unsigned int phyid2; + + hwcfg = smsc911x_reg_read(pdata, HW_CFG); + + /* External phy is requested, supported, and detected */ + if (hwcfg & HW_CFG_EXT_PHY_DET_) { + + /* Attempt to switch to external phy for auto-detecting + * its address. Assuming tx and rx are stopped because + * smsc911x_phy_initialise is called before + * smsc911x_rx_initialise and tx_initialise. + */ + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to external phy */ + hwcfg |= HW_CFG_EXT_PHY_EN_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg |= HW_CFG_SMI_SEL_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Auto-detect PHY */ + spin_lock_irq(&pdata->phy_lock); + for (address = 0; address <= 31; address++) { + pdata->mii.phy_id = address; + phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1); + phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2); + if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) { + SMSC_TRACE("Detected PHY at address = " + "0x%02X = %d", address, address); + break; + } + } + spin_unlock_irq(&pdata->phy_lock); + + if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) { + SMSC_WARNING("External PHY is not accessable, " + "using internal PHY instead"); + /* Revert back to internal phy settings. */ + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to internal phy */ + hwcfg &= (~HW_CFG_EXT_PHY_EN_); + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg &= (~HW_CFG_SMI_SEL_); + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + /* Use internal phy */ + return -ENODEV; + } else { + SMSC_TRACE("Successfully switched to external PHY"); + pdata->using_extphy = 1; + } + } else { + SMSC_WARNING("No external PHY detected."); + SMSC_WARNING("Using internal PHY instead."); + /* Use internal phy */ + return -ENODEV; + } + return 0; +} + +/* called by phy_initialise and loopback test */ +static int smsc911x_phy_reset(struct smsc911x_data *pdata) +{ + unsigned int temp; + unsigned int i = 100000; + unsigned long flags; + + SMSC_TRACE("Performing PHY BCR Reset"); + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET); + do { + udelay(10); + temp = smsc911x_phy_read(pdata, MII_BMCR); + } while ((i--) && (temp & BMCR_RESET)); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + if (temp & BMCR_RESET) { + SMSC_WARNING("PHY reset failed to complete."); + return 0; + } + /* Extra delay required because the phy may not be completed with + * its reset when BMCR_RESET is cleared. Specs say 256 uS is + * enough delay but using 1ms here to be safe + */ + msleep(1); + + return 1; +} + +/* Fetches a tx status out of the status fifo */ +static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, TX_STATUS_FIFO); + + return result; +} + +/* Fetches the next rx status */ +static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, RX_STATUS_FIFO); + + return result; +} + +#ifdef USE_PHY_WORK_AROUND +static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata) +{ + unsigned int tries; + u32 wrsz; + u32 rdsz; + u32 bufp; + + for (tries = 0; tries < 10; tries++) { + unsigned int txcmd_a; + unsigned int txcmd_b; + unsigned int status; + unsigned int pktlength; + unsigned int i; + + /* Zero-out rx packet memory */ + memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE); + + /* Write tx packet to 118 */ + txcmd_a = (((unsigned int)pdata->loopback_tx_pkt) + & 0x03) << 16; + txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + txcmd_a |= MIN_PACKET_SIZE; + + txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE; + + smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO); + smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO); + + bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC; + wrsz = MIN_PACKET_SIZE + 3; + wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3); + wrsz >>= 2; + + smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + + /* Wait till transmit is done */ + i = 60; + do { + udelay(5); + status = smsc911x_tx_get_txstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARNING("Failed to transmit during loopback test"); + continue; + } + if (status & TX_STS_ES_) { + SMSC_WARNING("Transmit encountered errors during " + "loopback test"); + continue; + } + + /* Wait till receive is done */ + i = 60; + do { + udelay(5); + status = smsc911x_rx_get_rxstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARNING("Failed to receive during loopback test"); + continue; + } + if (status & RX_STS_ES_) { + SMSC_WARNING("Receive encountered errors during " + "loopback test"); + continue; + } + + pktlength = ((status & 0x3FFF0000UL) >> 16); + bufp = (u32)pdata->loopback_rx_pkt; + rdsz = pktlength + 3; + rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3; + rdsz >>= 2; + + smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz); + + if (pktlength != (MIN_PACKET_SIZE + 4)) { + SMSC_WARNING("Unexpected packet size during " + "loop back test, size=%d, " + "will retry", pktlength); + } else { + unsigned int j; + int mismatch = 0; + for (j = 0; j < MIN_PACKET_SIZE; j++) { + if (pdata->loopback_tx_pkt[j] + != pdata->loopback_rx_pkt[j]) { + mismatch = 1; + break; + } + } + if (!mismatch) { + SMSC_TRACE("Successfully verified " + "loopback packet"); + return 1; + } else { + SMSC_WARNING("Data miss match during " + "loop back test, will retry."); + } + } + } + + return 0; +} + +static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata) +{ + int result = 0; + unsigned int i; + unsigned int val; + unsigned long flags; + + /* Initialise tx packet */ + for (i = 0; i < 6; i++) { + /* Use broadcast destination address */ + pdata->loopback_tx_pkt[i] = (char)0xFF; + } + + for (i = 6; i < 12; i++) { + /* Use incrementing source address */ + pdata->loopback_tx_pkt[i] = (char)i; + } + + /* Set length type field */ + pdata->loopback_tx_pkt[12] = 0x00; + pdata->loopback_tx_pkt[13] = 0x00; + for (i = 14; i < MIN_PACKET_SIZE; i++) { + pdata->loopback_tx_pkt[i] = (char)i; + } + + val = smsc911x_reg_read(pdata, HW_CFG); + val &= HW_CFG_TX_FIF_SZ_; + val |= HW_CFG_SF_; + smsc911x_reg_write(val, pdata, HW_CFG); + + smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG); + smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt) + & 0x03) << 8, pdata, RX_CFG); + + for (i = 0; i < 10; i++) { + /* Set PHY to 10/FD, no ANEG, and loopback mode */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, 0x4100); + + /* Enable MAC tx/rx, FD */ + smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_ + | MAC_CR_TXEN_ | MAC_CR_RXEN_); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + if (smsc911x_phy_check_loopbackpkt(pdata)) { + result = 1; + break; + } + pdata->resetcount++; + + /* Disable MAC rx */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_phy_reset(pdata); + } + + /* Disable MAC */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + + /* Cancel PHY loopback mode */ + smsc911x_phy_write(pdata, MII_BMCR, 0); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_reg_write(0, pdata, TX_CFG); + smsc911x_reg_write(0, pdata, RX_CFG); + + return result; +} +#endif /* USE_PHY_WORK_AROUND */ + +/* assumes phy_lock is held */ +static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata) +{ + unsigned int temp; + + if (pdata->mii.full_duplex) { + unsigned int phy_adv; + unsigned int phy_lpa; + phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE); + phy_lpa = smsc911x_phy_read(pdata, MII_LPA); + if (phy_adv & phy_lpa & LPA_PAUSE_CAP) { + /* Both ends support symmetric pause, enable + * PAUSE receive and transmit */ + smsc911x_mac_write(pdata, FLOW, 0xFFFF0002); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp |= 0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } else if (((phy_adv & ADVERTISE_PAUSE_ALL) == + ADVERTISE_PAUSE_ALL) && + ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) { + /* We support symmetric and asym pause, the + * other end only supports asym, Enable PAUSE + * receive, disable PAUSE transmit */ + smsc911x_mac_write(pdata, FLOW, 0xFFFF0002); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp &= ~0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } else { + /* Disable PAUSE receive and transmit */ + smsc911x_mac_write(pdata, FLOW, 0); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp &= ~0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } + } else { + smsc911x_mac_write(pdata, FLOW, 0); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp |= 0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } +} + +/* Update link mode if any thing has changed */ +static void smsc911x_phy_update_linkmode(struct net_device *dev, int init) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) { + /* duplex state has changed */ + unsigned int mac_cr; + + spin_lock_irqsave(&pdata->phy_lock, flags); + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + if (pdata->mii.full_duplex) { + SMSC_TRACE("configuring for full duplex mode"); + mac_cr |= MAC_CR_FDPX_; + } else { + SMSC_TRACE("configuring for half duplex mode"); + mac_cr &= ~MAC_CR_FDPX_; + } + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + + smsc911x_phy_update_flowcontrol(pdata); + + spin_unlock_irqrestore(&pdata->phy_lock, flags); + } +#ifdef USE_LED1_WORK_AROUND + if (netif_carrier_ok(dev)) { + if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) && + (!pdata->using_extphy)) { + /* Restore orginal GPIO configuration */ + pdata->gpio_setting = pdata->gpio_orig_setting; + smsc911x_reg_write(pdata->gpio_setting, pdata, + GPIO_CFG); + } + } else { + /* Check global setting that LED1 + * usage is 10/100 indicator */ + pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG); + if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_) + && (!pdata->using_extphy)) { + /* Force 10/100 LED off, after saving + * orginal GPIO configuration */ + pdata->gpio_orig_setting = pdata->gpio_setting; + + pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_; + pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_ + | GPIO_CFG_GPIODIR0_ + | GPIO_CFG_GPIOD0_); + smsc911x_reg_write(pdata->gpio_setting, pdata, + GPIO_CFG); + } + } +#endif /* USE_LED1_WORK_AROUND */ +} + +/* Entry point for the link poller */ +static void smsc911x_phy_checklink(unsigned long ptr) +{ + struct net_device *dev = (struct net_device *)ptr; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_phy_update_linkmode(dev, 0); + + if (!(pdata->stop_link_poll)) { + pdata->link_poll_timer.expires = jiffies + 2 * HZ; + add_timer(&pdata->link_poll_timer); + } else { + pdata->stop_link_poll = 0; + } +} + +/* Initialises the PHY layer. Called at initialisation by open() so + * interrupts are enabled */ +static int smsc911x_phy_initialise(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int phyid1 = 0; + unsigned int phyid2 = 0; + unsigned int temp; + + pdata->using_extphy = 0; + + switch (pdata->idrev & 0xFFFF0000) { + case 0x01170000: + case 0x01150000: + /* External PHY supported, try to autodetect */ + if (smsc911x_phy_initialise_external(pdata) < 0) { + SMSC_TRACE("External PHY is not detected, using " + "internal PHY instead"); + pdata->mii.phy_id = 1; + } + break; + default: + SMSC_TRACE("External PHY is not supported, using internal PHY " + "instead"); + pdata->mii.phy_id = 1; + break; + } + + spin_lock_irq(&pdata->phy_lock); + phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1); + phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2); + spin_unlock_irq(&pdata->phy_lock); + + if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) { + SMSC_WARNING("Internal PHY not detected!"); + return 0; + } + + /* Reset the phy */ + if (!smsc911x_phy_reset(pdata)) { + SMSC_WARNING("PHY reset failed to complete."); + return 0; + } +#ifdef USE_PHY_WORK_AROUND + if (!smsc911x_phy_loopbacktest(pdata)) { + SMSC_WARNING("Failed Loop Back Test"); + return 0; + } else { + SMSC_TRACE("Passed Loop Back Test"); + } +#endif /* USE_PHY_WORK_AROUND */ + + /* Advertise all speeds and pause capabilities */ + spin_lock_irq(&pdata->phy_lock); + temp = smsc911x_phy_read(pdata, MII_ADVERTISE); + temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + smsc911x_phy_write(pdata, MII_ADVERTISE, temp); + pdata->mii.advertising = temp; + + if (!pdata->using_extphy) { + /* using internal phy, enable PHY interrupts */ + smsc911x_phy_read(pdata, MII_INTSTS); + smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_); + } + + /* begin to establish link */ + smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + spin_unlock_irq(&pdata->phy_lock); + + smsc911x_phy_update_linkmode(dev, 1); + + setup_timer(&pdata->link_poll_timer, smsc911x_phy_checklink, + (unsigned long)dev); + pdata->link_poll_timer.expires = jiffies + 2 * HZ; + add_timer(&pdata->link_poll_timer); + + SMSC_TRACE("phy initialised succesfully"); + return 1; +} + +/* Gets the number of tx statuses in the fifo */ +static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata) +{ + unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF) + & TX_FIFO_INF_TSUSED_) >> 16; + return result; +} + +/* Reads tx statuses and increments counters where necessary */ +static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata) +{ + unsigned int tx_stat; + + while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) { + if (unlikely(tx_stat & 0x80000000)) { + /* In this driver the packet tag is used as the packet + * length. Since a packet length can never reach the + * size of 0x8000, this bit is reserved. It is worth + * noting that the "reserved bit" in the warning above + * does not reference a hardware defined reserved bit + * but rather a driver defined one. + */ + SMSC_WARNING("Packet tag reserved bit is high"); + } else { + if (unlikely(tx_stat & 0x00008000)) { + pdata->stats.tx_errors++; + } else { + pdata->stats.tx_packets++; + pdata->stats.tx_bytes += (tx_stat >> 16); + } + if (unlikely(tx_stat & 0x00000100)) { + pdata->stats.collisions += 16; + pdata->stats.tx_aborted_errors += 1; + } else { + pdata->stats.collisions += + ((tx_stat >> 3) & 0xF); + } + if (unlikely(tx_stat & 0x00000800)) { + pdata->stats.tx_carrier_errors += 1; + } + if (unlikely(tx_stat & 0x00000200)) { + pdata->stats.collisions++; + pdata->stats.tx_aborted_errors++; + } + } + } +} + +/* Increments the Rx error counters */ +static void +smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat) +{ + int crc_err = 0; + + if (unlikely(rxstat & 0x00008000)) { + pdata->stats.rx_errors++; + if (unlikely(rxstat & 0x00000002)) { + pdata->stats.rx_crc_errors++; + crc_err = 1; + } + } + if (likely(!crc_err)) { + if (unlikely((rxstat & 0x00001020) == 0x00001020)) { + /* Frame type indicates length, + * and length error is set */ + pdata->stats.rx_length_errors++; + } + if (rxstat & RX_STS_MCAST_) + pdata->stats.multicast++; + } +} + +/* Quickly dumps bad packets */ +static void +smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes) +{ + unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2; + + if (likely(pktwords >= 4)) { + unsigned int timeout = 500; + unsigned int val; + smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL); + do { + udelay(1); + val = smsc911x_reg_read(pdata, RX_DP_CTRL); + } while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_)); + + if (unlikely(timeout == 0)) + SMSC_WARNING("Timed out waiting for RX FFWD " + "to finish, RX_DP_CTRL: 0x%08X", val); + } else { + unsigned int temp; + while (pktwords--) + temp = smsc911x_reg_read(pdata, RX_DATA_FIFO); + } +} + +/* NAPI poll function */ +static int smsc911x_poll(struct napi_struct *napi, int budget) +{ + struct smsc911x_data *pdata = container_of(napi, struct smsc911x_data, napi); + struct net_device *dev = pdata->netdev; + int npackets = 0; + + while (npackets < budget) { + unsigned int pktlength; + unsigned int pktwords; + unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata); + + /* break out of while loop if there are no more packets waiting */ + if (!rxstat) + break; + + pktlength = ((rxstat & 0x3FFF0000) >> 16); + pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2; + smsc911x_rx_counterrors(pdata, rxstat); + + if (likely((rxstat & RX_STS_ES_) == 0)) { + struct sk_buff *skb; + skb = dev_alloc_skb(pktlength + NET_IP_ALIGN); + if (likely(skb)) { + skb->data = skb->head; + skb->tail = skb->head; + /* Align IP on 16B boundary */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put(skb, pktlength - 4); + smsc911x_rx_readfifo(pdata, + (unsigned int *)skb->head, + pktwords); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_NONE; + netif_receive_skb(skb); + + /* Update counters */ + pdata->stats.rx_packets++; + pdata->stats.rx_bytes += (pktlength - 4); + dev->last_rx = jiffies; + npackets++; + continue; + } else { + SMSC_WARNING("Unable to allocate sk_buff " + "for rx packet, in PIO path"); + pdata->stats.rx_dropped++; + } + } + /* At this point, the packet is to be read out + * of the fifo and discarded */ + smsc911x_rx_fastforward(pdata, pktlength); + } + + pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS); + + if (npackets < budget) { + unsigned int temp; + /* We processed all packets available. Tell NAPI it can + * stop polling then re-enable rx interrupts */ + netif_rx_complete(dev, napi); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RSFL_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + } + + /* There are still packets waiting */ + return npackets; +} + +/* Returns hash bit number for given MAC address + * Example: + * 01 00 5E 00 00 01 -> returns bit number 31 */ +static unsigned int smsc911x_hash(char addr[ETH_ALEN]) +{ + unsigned int crc; + unsigned int result; + + crc = ether_crc(ETH_ALEN, addr); + result = (crc >> 26) & 0x3f; + + return result; +} + +static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata) +{ + /* Performs the multicast & mac_cr update. This is called when + * safe on the current hardware, and with the phy_lock held */ + unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= pdata->set_bits_mask; + mac_cr &= ~(pdata->clear_bits_mask); + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + smsc911x_mac_write(pdata, HASHH, pdata->hashhi); + smsc911x_mac_write(pdata, HASHL, pdata->hashlo); + SMSC_TRACE("maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", mac_cr, + pdata->hashhi, pdata->hashlo); +} + +static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata) +{ + unsigned int mac_cr; + + /* This function is only called for older LAN911x devices + * (revA or revB), where MAC_CR, HASHH and HASHL should not + * be modified during Rx - newer devices immediately update the + * registers. + * + * This is called from interrupt context */ + + spin_lock(&pdata->phy_lock); + + /* Check Rx has stopped */ + if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_) + SMSC_WARNING("Rx not stopped\n"); + + /* Perform the update - safe to do now Rx has stopped */ + smsc911x_rx_multicast_update(pdata); + + /* Re-enable Rx */ + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= MAC_CR_RXEN_; + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + + pdata->multicast_update_pending = 0; + + spin_unlock(&pdata->phy_lock); +} + +/* Sets the device MAC address to dev_addr, called with phy_lock held */ +static void +smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6]) +{ + u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4]; + u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) | + (dev_addr[1] << 8) | dev_addr[0]; + + smsc911x_mac_write(pdata, ADDRH, mac_high16); + smsc911x_mac_write(pdata, ADDRL, mac_low32); +} + +static int smsc911x_open(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int timeout; + unsigned int temp; + unsigned int intcfg; + + spin_lock_init(&pdata->phy_lock); + + /* Reset the LAN911x */ + smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG); + timeout = 10; + do { + udelay(10); + temp = smsc911x_reg_read(pdata, HW_CFG); + } while ((--timeout) && (temp & HW_CFG_SRST_)); + + if (unlikely(temp & HW_CFG_SRST_)) { + SMSC_WARNING("Failed to complete reset"); + return -ENODEV; + } + + smsc911x_reg_write(0x00050000, pdata, HW_CFG); + smsc911x_reg_write(0x006E3740, pdata, AFC_CFG); + + /* Make sure EEPROM has finished loading before setting GPIO_CFG */ + timeout = 50; + while ((timeout--) && + (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) { + udelay(10); + } + + if (unlikely(timeout == 0)) { + SMSC_WARNING("Timed out waiting for EEPROM " + "busy bit to clear"); + } +#if USE_DEBUG >= 1 + smsc911x_reg_write(0x00670700, pdata, GPIO_CFG); +#else + smsc911x_reg_write(0x70070000, pdata, GPIO_CFG); +#endif + + /* Initialise irqs, but leave all sources disabled */ + smsc911x_reg_write(0, pdata, INT_EN); + smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS); + + /* Set interrupt deassertion to 100uS */ + intcfg = ((10 << 24) | INT_CFG_IRQ_EN_); + + if (pdata->irq_polarity) { + SMSC_TRACE("irq polarity: active high"); + intcfg |= INT_CFG_IRQ_POL_; + } else { + SMSC_TRACE("irq polarity: active low"); + } + + if (pdata->irq_type) { + SMSC_TRACE("irq type: push-pull"); + intcfg |= INT_CFG_IRQ_TYPE_; + } else { + SMSC_TRACE("irq type: open drain"); + } + + smsc911x_reg_write(intcfg, pdata, INT_CFG); + + SMSC_TRACE("Testing irq handler using IRQ %d", dev->irq); + pdata->software_irq_signal = 0; + smp_wmb(); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_SW_INT_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + + timeout = 1000; + while (timeout--) { + smp_rmb(); + if (pdata->software_irq_signal) + break; + msleep(1); + } + + if (!pdata->software_irq_signal) { + printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n", + dev->name, dev->irq); + return -ENODEV; + } + SMSC_TRACE("IRQ handler passed test using IRQ %d", dev->irq); + + printk(KERN_INFO "%s: SMSC911x/921x identified at %#08lx, IRQ: %d\n", + dev->name, (unsigned long)pdata->ioaddr, dev->irq); + + netif_carrier_off(dev); + + if (!smsc911x_phy_initialise(dev)) { + SMSC_WARNING("Failed to initialize PHY"); + return -ENODEV; + } + + smsc911x_set_mac_address(pdata, dev->dev_addr); + + temp = smsc911x_reg_read(pdata, HW_CFG); + temp &= HW_CFG_TX_FIF_SZ_; + temp |= HW_CFG_SF_; + smsc911x_reg_write(temp, pdata, HW_CFG); + + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + temp &= ~(FIFO_INT_RX_STS_LEVEL_); + smsc911x_reg_write(temp, pdata, FIFO_INT); + + /* set RX Data offset to 2 bytes for alignment */ + smsc911x_reg_write((2 << 8), pdata, RX_CFG); + + napi_enable(&pdata->napi); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_PHY_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + + spin_lock_irq(&pdata->phy_lock); + temp = smsc911x_mac_read(pdata, MAC_CR); + temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_); + smsc911x_mac_write(pdata, MAC_CR, temp); + spin_unlock_irq(&pdata->phy_lock); + + smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG); + + netif_start_queue(dev); + return 0; +} + +/* Entry point for stopping the interface */ +static int smsc911x_stop(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + napi_disable(&pdata->napi); + + pdata->stop_link_poll = 1; + del_timer_sync(&pdata->link_poll_timer); + + smsc911x_reg_write((smsc911x_reg_read(pdata, INT_CFG) & + (~INT_CFG_IRQ_EN_)), pdata, INT_CFG); + netif_stop_queue(dev); + + /* At this point all Rx and Tx activity is stopped */ + pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_tx_update_txcounters(pdata); + + SMSC_TRACE("Interface stopped"); + return 0; +} + +/* Entry point for transmitting a packet */ +static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int freespace; + unsigned int tx_cmd_a; + unsigned int tx_cmd_b; + unsigned int temp; + u32 wrsz; + u32 bufp; + + freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_; + + if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD)) + SMSC_WARNING("Tx data fifo low, space available: %d", + freespace); + + /* Word alignment adjustment */ + tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16); + tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + tx_cmd_a |= (unsigned int)skb->len; + + tx_cmd_b = ((unsigned int)skb->len) << 16; + tx_cmd_b |= (unsigned int)skb->len; + + smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO); + smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO); + + bufp = ((u32)skb->data) & 0xFFFFFFFC; + wrsz = (u32)skb->len + 3; + wrsz += ((u32)skb->data) & 0x3; + wrsz >>= 2; + + smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + freespace -= (skb->len + 32); + dev_kfree_skb(skb); + dev->trans_start = jiffies; + + if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30)) + smsc911x_tx_update_txcounters(pdata); + + if (freespace < TX_FIFO_LOW_THRESHOLD) { + netif_stop_queue(dev); + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp &= 0x00FFFFFF; + temp |= 0x32000000; + smsc911x_reg_write(temp, pdata, FIFO_INT); + } + + return NETDEV_TX_OK; +} + +/* Entry point for getting status counters */ +static struct net_device_stats *smsc911x_get_stats(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + smsc911x_tx_update_txcounters(pdata); + pdata->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + return &pdata->stats; +} + +/* Entry point for setting addressing modes */ +static void smsc911x_set_multicast_list(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + if (dev->flags & IFF_PROMISC) { + /* Enabling promiscuous mode */ + pdata->set_bits_mask = MAC_CR_PRMS_; + pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->flags & IFF_ALLMULTI) { + /* Enabling all multicast mode */ + pdata->set_bits_mask = MAC_CR_MCPAS_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->mc_count > 0) { + /* Enabling specific multicast addresses */ + unsigned int hash_high = 0; + unsigned int hash_low = 0; + unsigned int count = 0; + struct dev_mc_list *mc_list = dev->mc_list; + + pdata->set_bits_mask = MAC_CR_HPFILT_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + while (mc_list) { + count++; + if ((mc_list->dmi_addrlen) == ETH_ALEN) { + unsigned int bitnum = + smsc911x_hash(mc_list->dmi_addr); + unsigned int mask = 0x01 << (bitnum & 0x1F); + if (bitnum & 0x20) + hash_high |= mask; + else + hash_low |= mask; + } else { + SMSC_WARNING("dmi_addrlen != 6"); + } + mc_list = mc_list->next; + } + if (count != (unsigned int)dev->mc_count) + SMSC_WARNING("mc_count != dev->mc_count"); + + pdata->hashhi = hash_high; + pdata->hashlo = hash_low; + } else { + /* Enabling local MAC address only */ + pdata->set_bits_mask = 0; + pdata->clear_bits_mask = + (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } + + spin_lock_irqsave(&pdata->phy_lock, flags); + + if (pdata->generation <= 1) { + /* Older hardware revision - cannot change these flags while + * receiving data */ + if (!pdata->multicast_update_pending) { + unsigned int temp; + SMSC_TRACE("scheduling mcast update"); + pdata->multicast_update_pending = 1; + + /* Request the hardware to stop, then perform the + * update when we get an RX_STOP interrupt */ + smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RXSTOP_INT_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + + temp = smsc911x_mac_read(pdata, MAC_CR); + temp &= ~(MAC_CR_RXEN_); + smsc911x_mac_write(pdata, MAC_CR, temp); + } else { + /* There is another update pending, this should now + * use the newer values */ + } + } else { + /* Newer hardware revision - can write immediately */ + smsc911x_rx_multicast_update(pdata); + } + + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int intsts; + unsigned int inten; + unsigned int temp; + int serviced = IRQ_NONE; + + intsts = smsc911x_reg_read(pdata, INT_STS); + inten = smsc911x_reg_read(pdata, INT_EN); + + if (unlikely(intsts & inten & INT_STS_SW_INT_)) { + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_SW_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS); + pdata->software_irq_signal = 1; + smp_wmb(); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) { + /* Called when there is a multicast update scheduled and + * it is now safe to complete the update */ + SMSC_TRACE("RX Stop interrupt"); + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RXSTOP_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS); + smsc911x_rx_multicast_update_workaround(pdata); + serviced = IRQ_HANDLED; + } + + if (intsts & inten & INT_STS_TDFA_) { + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + smsc911x_reg_write(temp, pdata, FIFO_INT); + smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS); + netif_wake_queue(dev); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXE_)) { + smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS); + serviced = IRQ_HANDLED; + } + + if (likely(intsts & inten & INT_STS_RSFL_)) { + /* Disable Rx interrupts and schedule NAPI poll */ + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RSFL_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + netif_rx_schedule(dev, &pdata->napi); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_PHY_INT_)) { + smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS); + spin_lock(&pdata->phy_lock); + temp = smsc911x_phy_read(pdata, MII_INTSTS); + spin_unlock(&pdata->phy_lock); + SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp); + smsc911x_phy_update_linkmode(dev, 0); + serviced = IRQ_HANDLED; + } + return serviced; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +void smsc911x_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smsc911x_irqhandler(0, dev); + enable_irq(dev->irq); +} +#endif /* CONFIG_NET_POLL_CONTROLLER */ + +/* Standard ioctls for mii-tool */ +static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + struct mii_ioctl_data *data = if_mii(ifr); + unsigned long flags; + + SMSC_TRACE("ioctl cmd 0x%x", cmd); + switch (cmd) { + case SIOCGMIIPHY: + data->phy_id = pdata->mii.phy_id; + return 0; + case SIOCGMIIREG: + spin_lock_irqsave(&pdata->phy_lock, flags); + data->val_out = smsc911x_phy_read(pdata, data->reg_num); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + return 0; + case SIOCSMIIREG: + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, data->reg_num, data->val_in); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + return 0; + } + + SMSC_TRACE("unsupported ioctl cmd"); + return -1; +} + +static int +smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + return mii_ethtool_gset(&pdata->mii, cmd); +} + +static int +smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return mii_ethtool_sset(&pdata->mii, cmd); +} + +static void smsc911x_ethtool_getdrvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver)); + strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version)); + strncpy(info->bus_info, dev->dev.bus_id, sizeof(info->bus_info)); +} + +static int smsc911x_ethtool_nwayreset(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return mii_nway_restart(&pdata->mii); +} + +static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + return pdata->msg_enable; +} + +static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + pdata->msg_enable = level; +} + +static int smsc911x_ethtool_getregslen(struct net_device *dev) +{ + return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) * + sizeof(u32); +} + +static void +smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, + void *buf) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + unsigned int i; + unsigned int j = 0; + u32 *data = buf; + + regs->version = pdata->idrev; + for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32))) + data[j++] = smsc911x_reg_read(pdata, i); + + spin_lock_irqsave(&pdata->phy_lock, flags); + for (i = MAC_CR; i <= WUCSR; i++) + data[j++] = smsc911x_mac_read(pdata, i); + for (i = 0; i <= 31; i++) + data[j++] = smsc911x_phy_read(pdata, i); + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata) +{ + unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG); + temp &= ~GPIO_CFG_EEPR_EN_; + smsc911x_reg_write(temp, pdata, GPIO_CFG); + msleep(1); +} + +static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op) +{ + int timeout = 100; + u32 e2cmd; + + SMSC_TRACE("op 0x%08x", op); + if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) { + SMSC_WARNING("Busy at start"); + return -EBUSY; + } + + e2cmd = op | E2P_CMD_EPC_BUSY_; + smsc911x_reg_write(e2cmd, pdata, E2P_CMD); + + do { + msleep(1); + e2cmd = smsc911x_reg_read(pdata, E2P_CMD); + } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--)); + + if (!timeout) { + SMSC_TRACE("TIMED OUT"); + return -EAGAIN; + } + + if (e2cmd & E2P_CMD_EPC_TIMEOUT_) { + SMSC_TRACE("Error occured during eeprom operation"); + return -EINVAL; + } + + return 0; +} + +static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata, + u8 address, u8 *data) +{ + u32 op = E2P_CMD_EPC_CMD_READ_ | address; + int ret; + + SMSC_TRACE("address 0x%x", address); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) + data[address] = smsc911x_reg_read(pdata, E2P_DATA); + + return ret; +} + +static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata, + u8 address, u8 data) +{ + u32 op = E2P_CMD_EPC_CMD_ERASE_ | address; + int ret; + + SMSC_TRACE("address 0x%x, data 0x%x", address, data); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) { + op = E2P_CMD_EPC_CMD_WRITE_ | address; + smsc911x_reg_write((u32)data, pdata, E2P_DATA); + ret = smsc911x_eeprom_send_cmd(pdata, op); + } + + return ret; +} + +static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev) +{ + return SMSC911X_EEPROM_SIZE; +} + +static int smsc911x_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + u8 eeprom_data[SMSC911X_EEPROM_SIZE]; + int len; + int i; + + smsc911x_eeprom_enable_access(pdata); + + len = min(eeprom->len, SMSC911X_EEPROM_SIZE); + for (i = 0; i < len; i++) { + int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data); + if (ret < 0) { + eeprom->len = 0; + return ret; + } + } + + memcpy(data, &eeprom_data[eeprom->offset], len); + eeprom->len = len; + return 0; +} + +static int smsc911x_ethtool_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int ret; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_eeprom_enable_access(pdata); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_); + ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_); + + /* Single byte write, according to man page */ + eeprom->len = 1; + + return ret; +} + +static struct ethtool_ops smsc911x_ethtool_ops = { + .get_settings = smsc911x_ethtool_getsettings, + .set_settings = smsc911x_ethtool_setsettings, + .get_link = ethtool_op_get_link, + .get_drvinfo = smsc911x_ethtool_getdrvinfo, + .nway_reset = smsc911x_ethtool_nwayreset, + .get_msglevel = smsc911x_ethtool_getmsglevel, + .set_msglevel = smsc911x_ethtool_setmsglevel, + .get_regs_len = smsc911x_ethtool_getregslen, + .get_regs = smsc911x_ethtool_getregs, + .get_eeprom_len = smsc911x_ethtool_get_eeprom_len, + .get_eeprom = smsc911x_ethtool_get_eeprom, + .set_eeprom = smsc911x_ethtool_set_eeprom, +}; + +/* Initializing private device structures */ +static int smsc911x_init(struct net_device *dev) +{ + u32 mac_high16; + u32 mac_low32; + struct smsc911x_data *pdata = netdev_priv(dev); + + SMSC_TRACE("Driver Parameters:"); + SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr); + SMSC_TRACE("IRQ: %d", dev->irq); + SMSC_TRACE("PHY will be autodetected."); + + if (pdata->ioaddr == 0) { + SMSC_WARNING("pdata->ioaddr: 0x00000000"); + return -ENODEV; + } + + /* Default generation to zero (all workarounds apply) */ + pdata->generation = 0; + + pdata->idrev = smsc911x_reg_read(pdata, ID_REV); + if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) { + SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, " + "idrev: 0x%08X", pdata->idrev); + SMSC_TRACE("This may mean the chip is set for 32 bit while " + "the bus is reading as 16 bit"); + return -ENODEV; + } + switch (pdata->idrev & 0xFFFF0000) { + case 0x01180000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 0; + break; + case 1UL: + SMSC_TRACE + ("LAN9118 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9118 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01170000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 0; + break; + case 1UL: + SMSC_TRACE + ("LAN9117 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9117 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01160000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + case 1UL: + SMSC_TRACE + ("LAN9116 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9116 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01150000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + case 1UL: + SMSC_TRACE + ("LAN9115 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9115 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x118A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9218 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9218 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x117A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9217 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9217 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x116A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9216 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9216 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x115A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9215 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9215 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x92100000UL: + case 0x92110000UL: + case 0x92200000UL: + case 0x92210000UL: + /* LAN9210/LAN9211/LAN9220/LAN9221 */ + pdata->generation = 4; + break; + + default: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + } + + if (pdata->generation == 0) + SMSC_WARNING("This driver is not intended " + "for this chip revision"); + + ether_setup(dev); + dev->open = smsc911x_open; + dev->stop = smsc911x_stop; + dev->hard_start_xmit = smsc911x_hard_start_xmit; + dev->get_stats = smsc911x_get_stats; + dev->set_multicast_list = smsc911x_set_multicast_list; + dev->flags |= IFF_MULTICAST; + dev->do_ioctl = smsc911x_do_ioctl; + netif_napi_add(dev, &pdata->napi, smsc911x_poll, 64); + dev->ethtool_ops = &smsc911x_ethtool_ops; + + /* Check if mac address has been specified when bringing interface up */ + if (is_valid_ether_addr(dev->dev_addr)) { + smsc911x_set_mac_address(pdata, dev->dev_addr); + SMSC_TRACE("MAC Address is specified by configuration"); + } else { + /* Try reading mac address from device. if EEPROM is present + * it will already have been set */ + u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH); + u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL); + dev->dev_addr[0] = (u8)(mac_low32); + dev->dev_addr[1] = (u8)(mac_low32 >> 8); + dev->dev_addr[2] = (u8)(mac_low32 >> 16); + dev->dev_addr[3] = (u8)(mac_low32 >> 24); + dev->dev_addr[4] = (u8)(mac_high16); + dev->dev_addr[5] = (u8)(mac_high16 >> 8); + + if (is_valid_ether_addr(dev->dev_addr)) { + /* eeprom values are valid so use them */ + SMSC_TRACE("Mac Address is read from LAN911x EEPROM"); + } else { + /* eeprom values are invalid, generate random MAC */ + random_ether_addr(dev->dev_addr); + smsc911x_set_mac_address(pdata, dev->dev_addr); + SMSC_TRACE("MAC Address is set to random_ether_addr"); + } + } + + printk(KERN_INFO + "%s: SMSC911x MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + +#ifdef CONFIG_NET_POLL_CONTROLLER + dev->poll_controller = smsc911x_poll_controller; +#endif /* CONFIG_NET_POLL_CONTROLLER */ + + pdata->mii.phy_id_mask = 0x1f; + pdata->mii.reg_num_mask = 0x1f; + pdata->mii.force_media = 0; + pdata->mii.full_duplex = 0; + pdata->mii.dev = dev; + pdata->mii.mdio_read = smsc911x_mdio_read; + pdata->mii.mdio_write = smsc911x_mdio_write; + + pdata->msg_enable = NETIF_MSG_LINK; + + return 0; +} + +static int smsc911x_drv_remove(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + + dev = platform_get_drvdata(pdev); + BUG_ON(!dev); + pdata = netdev_priv(dev); + BUG_ON(!pdata); + BUG_ON(!pdata->ioaddr); + + SMSC_TRACE("Stopping driver."); + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_irq(dev->irq, dev); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + platform_get_resource(pdev, IORESOURCE_MEM, 0); + + release_mem_region(res->start, res->end - res->start); + + iounmap(pdata->ioaddr); + + free_netdev(dev); + + return 0; +} + +static int smsc911x_drv_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + unsigned int intcfg = 0; + int res_size; + int retval; + + printk(KERN_INFO "%s: Driver version %s.\n", SMSC_CHIPNAME, + SMSC_DRV_VERSION); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_WARNING "%s: Could not allocate resource.\n", + SMSC_CHIPNAME); + retval = -ENODEV; + goto out_0; + } + res_size = res->end - res->start; + + if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) { + retval = -EBUSY; + goto out_0; + } + + dev = alloc_etherdev(sizeof(struct smsc911x_data)); + if (!dev) { + printk(KERN_WARNING "%s: Could not allocate device.\n", + SMSC_CHIPNAME); + retval = -ENOMEM; + goto out_release_io_1; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + pdata = netdev_priv(dev); + pdata->netdev = dev; + + dev->irq = platform_get_irq(pdev, 0); + pdata->ioaddr = ioremap_nocache(res->start, res_size); + + /* copy config parameters across if present, otherwise pdata + * defaults to zeros */ + if (pdev->dev.platform_data) { + struct smsc911x_platform_config *config = pdev->dev.platform_data; + pdata->irq_polarity = config->irq_polarity; + pdata->irq_type = config->irq_type; + } + + if (pdata->ioaddr == NULL) { + SMSC_WARNING("Error smsc911x base address invalid"); + retval = -ENOMEM; + goto out_free_netdev_2; + } + + if ((retval = smsc911x_init(dev)) < 0) + goto out_unmap_io_3; + + /* configure irq polarity and type before connecting isr */ + if (pdata->irq_polarity) + intcfg |= INT_CFG_IRQ_POL_; + + if (pdata->irq_type) + intcfg |= INT_CFG_IRQ_TYPE_; + + smsc911x_reg_write(intcfg, pdata, INT_CFG); + + retval = request_irq(dev->irq, smsc911x_irqhandler, IRQF_DISABLED, + SMSC_CHIPNAME, dev); + if (retval) { + SMSC_WARNING("Unable to claim requested irq: %d", dev->irq); + goto out_unmap_io_3; + } + + platform_set_drvdata(pdev, dev); + + retval = register_netdev(dev); + if (retval) { + SMSC_WARNING("Error %i registering device", retval); + goto out_unset_drvdata_4; + } else { + SMSC_TRACE("Network interface: \"%s\"", dev->name); + } + + return 0; + +out_unset_drvdata_4: + platform_set_drvdata(pdev, NULL); + free_irq(dev->irq, dev); +out_unmap_io_3: + iounmap(pdata->ioaddr); +out_free_netdev_2: + free_netdev(dev); +out_release_io_1: + release_mem_region(res->start, res->end - res->start); +out_0: + return retval; +} + +static int smsc911x_drv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + unsigned long flags; + struct net_device *dev; + struct smsc911x_data *pdata; + + dev = platform_get_drvdata(pdev); + BUG_ON(!dev); + pdata = netdev_priv(dev); + BUG_ON(!pdata); + BUG_ON(!pdata->ioaddr); + + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, BMCR_PDOWN); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_reg_write(PMT_CTRL_PM_MODE_D2_, pdata, PMT_CTRL); + + return 0; +} + +static int smsc911x_drv_resume(struct platform_device *pdev) +{ + unsigned long flags; + struct net_device *dev; + struct smsc911x_data *pdata; + unsigned int temp; + + dev = platform_get_drvdata(pdev); + BUG_ON(!dev); + pdata = netdev_priv(dev); + BUG_ON(!pdata); + BUG_ON(!pdata->ioaddr); + + smsc911x_reg_write(0xFFFFFFFF, pdata, BYTE_TEST); + while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_)) + ; + + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + return 0; +} + +static struct platform_driver smsc911x_driver = { + .probe = smsc911x_drv_probe, + .remove = smsc911x_drv_remove, + .suspend = smsc911x_drv_suspend, + .resume = smsc911x_drv_resume, + .driver = { + .name = SMSC_CHIPNAME, + }, +}; + +/* Entry point for loading the module */ +static int __init smsc911x_init_module(void) +{ + return platform_driver_register(&smsc911x_driver); +} + +/* entry point for unloading the module */ +static void __exit smsc911x_cleanup_module(void) +{ + platform_driver_unregister(&smsc911x_driver); +} + +module_init(smsc911x_init_module); +module_exit(smsc911x_cleanup_module); diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h new file mode 100644 index 000000000000..d67c9971b136 --- /dev/null +++ b/drivers/net/smsc911x.h @@ -0,0 +1,395 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2007 SMSC + * Copyright (C) 2005 ARM + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ***************************************************************************/ +#ifndef __SMSC911X_H__ +#define __SMSC911X_H__ + +#if defined(CONFIG_MACH_MX37_3DS) || defined(CONFIG_MACH_MX25_3DS) +#define SMSC_CAN_USE_SPI 1 +#define SMSC_CAN_USE_32BIT 0 +#elif defined(CONFIG_MACH_MX31_3DS) || defined(CONFIG_MACH_MX35_3DS) +#define SMSC_CAN_USE_32BIT 0 +#define SMSC_CAN_USE_SPI 0 +#else +#define SMSC_CAN_USE_32BIT 1 +#define SMSC_CAN_USE_SPI 0 +#endif + +#define TX_FIFO_LOW_THRESHOLD (u32)1600 +#define SMSC911X_EEPROM_SIZE (u32)7 +#define USE_DEBUG 0 + +/* implements a PHY loopback test at initialisation time, to ensure a packet + * can be succesfully looped back */ +#define USE_PHY_WORK_AROUND + +/* 10/100 LED link-state inversion when media is disconnected */ +#define USE_LED1_WORK_AROUND + +/* platform_device configuration data, should be assigned to + * the platform_device's dev.platform_data */ +struct smsc911x_platform_config { + unsigned int irq_polarity; + unsigned int irq_type; +}; + +#if USE_DEBUG >= 1 +#define SMSC_WARNING(fmt, args...) \ + printk(KERN_EMERG "SMSC_WARNING: %s: " fmt "\n", \ + __FUNCTION__ , ## args) +#else +#define SMSC_WARNING(msg, args...) +#endif /* USE_DEBUG >= 1 */ + +#if USE_DEBUG >= 2 +#define SMSC_TRACE(fmt,args...) \ + printk(KERN_EMERG "SMSC_TRACE: %s: " fmt "\n", \ + __FUNCTION__ , ## args) +#else +#define SMSC_TRACE(msg,args...) +#endif /* USE_DEBUG >= 2 */ + +/* SMSC911x registers and bitfields */ +#define RX_DATA_FIFO 0x00 + +#define TX_DATA_FIFO 0x20 +#define TX_CMD_A_ON_COMP_ 0x80000000 +#define TX_CMD_A_BUF_END_ALGN_ 0x03000000 +#define TX_CMD_A_4_BYTE_ALGN_ 0x00000000 +#define TX_CMD_A_16_BYTE_ALGN_ 0x01000000 +#define TX_CMD_A_32_BYTE_ALGN_ 0x02000000 +#define TX_CMD_A_DATA_OFFSET_ 0x001F0000 +#define TX_CMD_A_FIRST_SEG_ 0x00002000 +#define TX_CMD_A_LAST_SEG_ 0x00001000 +#define TX_CMD_A_BUF_SIZE_ 0x000007FF +#define TX_CMD_B_PKT_TAG_ 0xFFFF0000 +#define TX_CMD_B_ADD_CRC_DISABLE_ 0x00002000 +#define TX_CMD_B_DISABLE_PADDING_ 0x00001000 +#define TX_CMD_B_PKT_BYTE_LENGTH_ 0x000007FF + +#define RX_STATUS_FIFO 0x40 +#define RX_STS_ES_ 0x00008000 +#define RX_STS_MCAST_ 0x00000400 + +#define RX_STATUS_FIFO_PEEK 0x44 + +#define TX_STATUS_FIFO 0x48 +#define TX_STS_ES_ 0x00008000 + +#define TX_STATUS_FIFO_PEEK 0x4C + +#define ID_REV 0x50 +#define ID_REV_CHIP_ID_ 0xFFFF0000 +#define ID_REV_REV_ID_ 0x0000FFFF + +#define INT_CFG 0x54 +#define INT_CFG_INT_DEAS_ 0xFF000000 +#define INT_CFG_INT_DEAS_CLR_ 0x00004000 +#define INT_CFG_INT_DEAS_STS_ 0x00002000 +#define INT_CFG_IRQ_INT_ 0x00001000 +#define INT_CFG_IRQ_EN_ 0x00000100 +#define INT_CFG_IRQ_POL_ 0x00000010 +#define INT_CFG_IRQ_TYPE_ 0x00000001 + +#define INT_STS 0x58 +#define INT_STS_SW_INT_ 0x80000000 +#define INT_STS_TXSTOP_INT_ 0x02000000 +#define INT_STS_RXSTOP_INT_ 0x01000000 +#define INT_STS_RXDFH_INT_ 0x00800000 +#define INT_STS_RXDF_INT_ 0x00400000 +#define INT_STS_TX_IOC_ 0x00200000 +#define INT_STS_RXD_INT_ 0x00100000 +#define INT_STS_GPT_INT_ 0x00080000 +#define INT_STS_PHY_INT_ 0x00040000 +#define INT_STS_PME_INT_ 0x00020000 +#define INT_STS_TXSO_ 0x00010000 +#define INT_STS_RWT_ 0x00008000 +#define INT_STS_RXE_ 0x00004000 +#define INT_STS_TXE_ 0x00002000 +#define INT_STS_TDFU_ 0x00000800 +#define INT_STS_TDFO_ 0x00000400 +#define INT_STS_TDFA_ 0x00000200 +#define INT_STS_TSFF_ 0x00000100 +#define INT_STS_TSFL_ 0x00000080 +#define INT_STS_RXDF_ 0x00000040 +#define INT_STS_RDFL_ 0x00000020 +#define INT_STS_RSFF_ 0x00000010 +#define INT_STS_RSFL_ 0x00000008 +#define INT_STS_GPIO2_INT_ 0x00000004 +#define INT_STS_GPIO1_INT_ 0x00000002 +#define INT_STS_GPIO0_INT_ 0x00000001 + +#define INT_EN 0x5C +#define INT_EN_SW_INT_EN_ 0x80000000 +#define INT_EN_TXSTOP_INT_EN_ 0x02000000 +#define INT_EN_RXSTOP_INT_EN_ 0x01000000 +#define INT_EN_RXDFH_INT_EN_ 0x00800000 +#define INT_EN_TIOC_INT_EN_ 0x00200000 +#define INT_EN_RXD_INT_EN_ 0x00100000 +#define INT_EN_GPT_INT_EN_ 0x00080000 +#define INT_EN_PHY_INT_EN_ 0x00040000 +#define INT_EN_PME_INT_EN_ 0x00020000 +#define INT_EN_TXSO_EN_ 0x00010000 +#define INT_EN_RWT_EN_ 0x00008000 +#define INT_EN_RXE_EN_ 0x00004000 +#define INT_EN_TXE_EN_ 0x00002000 +#define INT_EN_TDFU_EN_ 0x00000800 +#define INT_EN_TDFO_EN_ 0x00000400 +#define INT_EN_TDFA_EN_ 0x00000200 +#define INT_EN_TSFF_EN_ 0x00000100 +#define INT_EN_TSFL_EN_ 0x00000080 +#define INT_EN_RXDF_EN_ 0x00000040 +#define INT_EN_RDFL_EN_ 0x00000020 +#define INT_EN_RSFF_EN_ 0x00000010 +#define INT_EN_RSFL_EN_ 0x00000008 +#define INT_EN_GPIO2_INT_ 0x00000004 +#define INT_EN_GPIO1_INT_ 0x00000002 +#define INT_EN_GPIO0_INT_ 0x00000001 + +#define BYTE_TEST 0x64 + +#define FIFO_INT 0x68 +#define FIFO_INT_TX_AVAIL_LEVEL_ 0xFF000000 +#define FIFO_INT_TX_STS_LEVEL_ 0x00FF0000 +#define FIFO_INT_RX_AVAIL_LEVEL_ 0x0000FF00 +#define FIFO_INT_RX_STS_LEVEL_ 0x000000FF + +#define RX_CFG 0x6C +#define RX_CFG_RX_END_ALGN_ 0xC0000000 +#define RX_CFG_RX_END_ALGN4_ 0x00000000 +#define RX_CFG_RX_END_ALGN16_ 0x40000000 +#define RX_CFG_RX_END_ALGN32_ 0x80000000 +#define RX_CFG_RX_DMA_CNT_ 0x0FFF0000 +#define RX_CFG_RX_DUMP_ 0x00008000 +#define RX_CFG_RXDOFF_ 0x00001F00 + +#define TX_CFG 0x70 +#define TX_CFG_TXS_DUMP_ 0x00008000 +#define TX_CFG_TXD_DUMP_ 0x00004000 +#define TX_CFG_TXSAO_ 0x00000004 +#define TX_CFG_TX_ON_ 0x00000002 +#define TX_CFG_STOP_TX_ 0x00000001 + +#define HW_CFG 0x74 +#define HW_CFG_TTM_ 0x00200000 +#define HW_CFG_SF_ 0x00100000 +#define HW_CFG_TX_FIF_SZ_ 0x000F0000 +#define HW_CFG_TR_ 0x00003000 +#define HW_CFG_SRST_ 0x00000001 + +/* only available on 115/117 */ +#define HW_CFG_PHY_CLK_SEL_ 0x00000060 +#define HW_CFG_PHY_CLK_SEL_INT_PHY_ 0x00000000 +#define HW_CFG_PHY_CLK_SEL_EXT_PHY_ 0x00000020 +#define HW_CFG_PHY_CLK_SEL_CLK_DIS_ 0x00000040 +#define HW_CFG_SMI_SEL_ 0x00000010 +#define HW_CFG_EXT_PHY_DET_ 0x00000008 +#define HW_CFG_EXT_PHY_EN_ 0x00000004 +#define HW_CFG_SRST_TO_ 0x00000002 + +/* only available on 116/118 */ +#define HW_CFG_32_16_BIT_MODE_ 0x00000004 + +#define RX_DP_CTRL 0x78 +#define RX_DP_CTRL_RX_FFWD_ 0x80000000 + +#define RX_FIFO_INF 0x7C +#define RX_FIFO_INF_RXSUSED_ 0x00FF0000 +#define RX_FIFO_INF_RXDUSED_ 0x0000FFFF + +#define TX_FIFO_INF 0x80 +#define TX_FIFO_INF_TSUSED_ 0x00FF0000 +#define TX_FIFO_INF_TDFREE_ 0x0000FFFF + +#define PMT_CTRL 0x84 +#define PMT_CTRL_PM_MODE_ 0x00003000 +#define PMT_CTRL_PM_MODE_D0_ 0x00000000 +#define PMT_CTRL_PM_MODE_D1_ 0x00001000 +#define PMT_CTRL_PM_MODE_D2_ 0x00002000 +#define PMT_CTRL_PM_MODE_D3_ 0x00003000 +#define PMT_CTRL_PHY_RST_ 0x00000400 +#define PMT_CTRL_WOL_EN_ 0x00000200 +#define PMT_CTRL_ED_EN_ 0x00000100 +#define PMT_CTRL_PME_TYPE_ 0x00000040 +#define PMT_CTRL_WUPS_ 0x00000030 +#define PMT_CTRL_WUPS_NOWAKE_ 0x00000000 +#define PMT_CTRL_WUPS_ED_ 0x00000010 +#define PMT_CTRL_WUPS_WOL_ 0x00000020 +#define PMT_CTRL_WUPS_MULTI_ 0x00000030 +#define PMT_CTRL_PME_IND_ 0x00000008 +#define PMT_CTRL_PME_POL_ 0x00000004 +#define PMT_CTRL_PME_EN_ 0x00000002 +#define PMT_CTRL_READY_ 0x00000001 + +#define GPIO_CFG 0x88 +#define GPIO_CFG_LED3_EN_ 0x40000000 +#define GPIO_CFG_LED2_EN_ 0x20000000 +#define GPIO_CFG_LED1_EN_ 0x10000000 +#define GPIO_CFG_GPIO2_INT_POL_ 0x04000000 +#define GPIO_CFG_GPIO1_INT_POL_ 0x02000000 +#define GPIO_CFG_GPIO0_INT_POL_ 0x01000000 +#define GPIO_CFG_EEPR_EN_ 0x00700000 +#define GPIO_CFG_GPIOBUF2_ 0x00040000 +#define GPIO_CFG_GPIOBUF1_ 0x00020000 +#define GPIO_CFG_GPIOBUF0_ 0x00010000 +#define GPIO_CFG_GPIODIR2_ 0x00000400 +#define GPIO_CFG_GPIODIR1_ 0x00000200 +#define GPIO_CFG_GPIODIR0_ 0x00000100 +#define GPIO_CFG_GPIOD4_ 0x00000020 +#define GPIO_CFG_GPIOD3_ 0x00000010 +#define GPIO_CFG_GPIOD2_ 0x00000004 +#define GPIO_CFG_GPIOD1_ 0x00000002 +#define GPIO_CFG_GPIOD0_ 0x00000001 + +#define GPT_CFG 0x8C +#define GPT_CFG_TIMER_EN_ 0x20000000 +#define GPT_CFG_GPT_LOAD_ 0x0000FFFF + +#define GPT_CNT 0x90 +#define GPT_CNT_GPT_CNT_ 0x0000FFFF + +#define ENDIAN 0x98 + +#define FREE_RUN 0x9C + +#define RX_DROP 0xA0 + +#define MAC_CSR_CMD 0xA4 +#define MAC_CSR_CMD_CSR_BUSY_ 0x80000000 +#define MAC_CSR_CMD_R_NOT_W_ 0x40000000 +#define MAC_CSR_CMD_CSR_ADDR_ 0x000000FF + +#define MAC_CSR_DATA 0xA8 + +#define AFC_CFG 0xAC +#define AFC_CFG_AFC_HI_ 0x00FF0000 +#define AFC_CFG_AFC_LO_ 0x0000FF00 +#define AFC_CFG_BACK_DUR_ 0x000000F0 +#define AFC_CFG_FCMULT_ 0x00000008 +#define AFC_CFG_FCBRD_ 0x00000004 +#define AFC_CFG_FCADD_ 0x00000002 +#define AFC_CFG_FCANY_ 0x00000001 + +#define E2P_CMD 0xB0 +#define E2P_CMD_EPC_BUSY_ 0x80000000 +#define E2P_CMD_EPC_CMD_ 0x70000000 +#define E2P_CMD_EPC_CMD_READ_ 0x00000000 +#define E2P_CMD_EPC_CMD_EWDS_ 0x10000000 +#define E2P_CMD_EPC_CMD_EWEN_ 0x20000000 +#define E2P_CMD_EPC_CMD_WRITE_ 0x30000000 +#define E2P_CMD_EPC_CMD_WRAL_ 0x40000000 +#define E2P_CMD_EPC_CMD_ERASE_ 0x50000000 +#define E2P_CMD_EPC_CMD_ERAL_ 0x60000000 +#define E2P_CMD_EPC_CMD_RELOAD_ 0x70000000 +#define E2P_CMD_EPC_TIMEOUT_ 0x00000200 +#define E2P_CMD_MAC_ADDR_LOADED_ 0x00000100 +#define E2P_CMD_EPC_ADDR_ 0x000000FF + +#define E2P_DATA 0xB4 +#define E2P_DATA_EEPROM_DATA_ 0x000000FF +#define LAN_REGISTER_EXTENT 0x00000100 + +/* + * MAC Control and Status Register (Indirect Address) + * Offset (through the MAC_CSR CMD and DATA port) + */ +#define MAC_CR 0x01 +#define MAC_CR_RXALL_ 0x80000000 +#define MAC_CR_HBDIS_ 0x10000000 +#define MAC_CR_RCVOWN_ 0x00800000 +#define MAC_CR_LOOPBK_ 0x00200000 +#define MAC_CR_FDPX_ 0x00100000 +#define MAC_CR_MCPAS_ 0x00080000 +#define MAC_CR_PRMS_ 0x00040000 +#define MAC_CR_INVFILT_ 0x00020000 +#define MAC_CR_PASSBAD_ 0x00010000 +#define MAC_CR_HFILT_ 0x00008000 +#define MAC_CR_HPFILT_ 0x00002000 +#define MAC_CR_LCOLL_ 0x00001000 +#define MAC_CR_BCAST_ 0x00000800 +#define MAC_CR_DISRTY_ 0x00000400 +#define MAC_CR_PADSTR_ 0x00000100 +#define MAC_CR_BOLMT_MASK_ 0x000000C0 +#define MAC_CR_DFCHK_ 0x00000020 +#define MAC_CR_TXEN_ 0x00000008 +#define MAC_CR_RXEN_ 0x00000004 + +#define ADDRH 0x02 + +#define ADDRL 0x03 + +#define HASHH 0x04 + +#define HASHL 0x05 + +#define MII_ACC 0x06 +#define MII_ACC_PHY_ADDR_ 0x0000F800 +#define MII_ACC_MIIRINDA_ 0x000007C0 +#define MII_ACC_MII_WRITE_ 0x00000002 +#define MII_ACC_MII_BUSY_ 0x00000001 + +#define MII_DATA 0x07 + +#define FLOW 0x08 +#define FLOW_FCPT_ 0xFFFF0000 +#define FLOW_FCPASS_ 0x00000004 +#define FLOW_FCEN_ 0x00000002 +#define FLOW_FCBSY_ 0x00000001 + +#define VLAN1 0x09 + +#define VLAN2 0x0A + +#define WUFF 0x0B + +#define WUCSR 0x0C +#define WUCSR_GUE_ 0x00000200 +#define WUCSR_WUFR_ 0x00000040 +#define WUCSR_MPR_ 0x00000020 +#define WUCSR_WAKE_EN_ 0x00000004 +#define WUCSR_MPEN_ 0x00000002 + +/* + * Phy definitions (vendor-specific) + */ +#define LAN9118_PHY_ID 0x00C0001C + +#define MII_INTSTS 0x1D + +#define MII_INTMSK 0x1E +#define PHY_INTMSK_AN_RCV_ (1 << 1) +#define PHY_INTMSK_PDFAULT_ (1 << 2) +#define PHY_INTMSK_AN_ACK_ (1 << 3) +#define PHY_INTMSK_LNKDOWN_ (1 << 4) +#define PHY_INTMSK_RFAULT_ (1 << 5) +#define PHY_INTMSK_AN_COMP_ (1 << 6) +#define PHY_INTMSK_ENERGYON_ (1 << 7) +#define PHY_INTMSK_DEFAULT_ (PHY_INTMSK_ENERGYON_ | \ + PHY_INTMSK_AN_COMP_ | \ + PHY_INTMSK_RFAULT_ | \ + PHY_INTMSK_LNKDOWN_) + +#define ADVERTISE_PAUSE_ALL (ADVERTISE_PAUSE_CAP | \ + ADVERTISE_PAUSE_ASYM) + +#define LPA_PAUSE_ALL (LPA_PAUSE_CAP | \ + LPA_PAUSE_ASYM) + +#endif /* __SMSC911X_H__ */ diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index 45bdf0b339bb..46bcaca38b46 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -717,5 +717,44 @@ source "drivers/net/wireless/b43/Kconfig" source "drivers/net/wireless/b43legacy/Kconfig" source "drivers/net/wireless/zd1211rw/Kconfig" source "drivers/net/wireless/rt2x00/Kconfig" +source "drivers/net/wireless/digiPiper/Kconfig" + +config DIGI_WI_G + tristate "Digi Wireless Module 802.11ab/g" + depends on WLAN_80211 && MACH_CCW9C + select IEEE80211 + select IEEE80211_SOFTMAC + ---help--- + A 802.11ab/g wireless driver for Digi ConnectCore Wi-9C modules. + +config DIGI_WI_G_HW_ENCRYPTION + bool "Use HW encryption" + depends on DIGI_WI_G + default y + help + AES/CCMP decoding is performed by using hardware logic instead of + software emulation. This can also be adjusted at run time with the + kernel command line option dw_sw_aes=0 + +config DIGI_WI_G_DEBUG + bool "Debugging support" + depends on DIGI_WI_G + default n + help + The digi_wi_g driver is compiled with debugging support. + Only the driver maintainer may need to enable it. + +choice + prompt "UBEC Transceiver Revision" + depends on DIGI_WI_G + default DIGI_WI_G_UBEC_JD + +config DIGI_WI_G_UBEC_JD + bool "UBEC Revision JD" + +config DIGI_WI_G_UBEC_HC + bool "UBEC Revision HC" + +endchoice endmenu diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 59d2d805f60b..5fcf43bbf8c5 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_HOSTAP) += hostap/ obj-$(CONFIG_B43) += b43/ obj-$(CONFIG_B43LEGACY) += b43legacy/ obj-$(CONFIG_ZD1211RW) += zd1211rw/ +obj-$(CONFIG_DIGI_PIPER_WIFI) += digiPiper/ # 16-bit wireless PCMCIA client drivers obj-$(CONFIG_PCMCIA_RAYCS) += ray_cs.o @@ -67,3 +68,4 @@ obj-$(CONFIG_ATH5K) += ath5k/ obj-$(CONFIG_ATH9K) += ath9k/ obj-$(CONFIG_MAC80211_HWSIM) += mac80211_hwsim.o +obj-$(CONFIG_DIGI_WI_G) += digi_wi_g.o diff --git a/drivers/net/wireless/digiPiper/Kconfig b/drivers/net/wireless/digiPiper/Kconfig new file mode 100644 index 000000000000..aa0659f0e499 --- /dev/null +++ b/drivers/net/wireless/digiPiper/Kconfig @@ -0,0 +1,27 @@ +config DIGI_PIPER_WIFI + bool "Digi Piper Wifi support" + depends on MAC80211 && WLAN_80211 && I2C && (MACH_CCW9P9215JS || MACH_CCW9M2443JS) + ---help--- + This driver is for the Piper 802.11 MAC by Digi. This MAC is + supported on the Digi ConnectCore Wi-9P 9215 and Digi ConnectCore Wi-9M 2443 + embedded modules. + +config PIPER_STATUS_LED + bool "Enable GPIO for Wifi status LED" + depends on DIGI_PIPER_WIFI && MACH_CCW9M2443JS + default y + help + ConnectCore Wi-9M 2443 does not provide any dedicated LED + in the module for showing the Wifi status. + This option lets you define one available CPU GPIO for this purpose. + Default value is set to USER LED 1 on JumpStart board. + +config PIPER_STATUS_LED_GPIO + int "GPIO for Wifi status LED (0-144)" + range 0 144 + depends on PIPER_STATUS_LED + default "141" + help + Set CPU GPIO for Wifi status LED. + + Default: User LED 1 on JumpStart board (LE5) diff --git a/drivers/net/wireless/digiPiper/Makefile b/drivers/net/wireless/digiPiper/Makefile new file mode 100644 index 000000000000..a3686b942088 --- /dev/null +++ b/drivers/net/wireless/digiPiper/Makefile @@ -0,0 +1,16 @@ + +mpiper-y := piper.o +mpiper-y += airoha.o +mpiper-y += digiDebug.o +mpiper-y += digiIsr.o +mpiper-y += digiTx.o +mpiper-y += digiRx.o +mpiper-y += digiMac80211.o +mpiper-y += piperDsp.o +mpiper-y += piperMacAssist.o +mpiper-y += phy.o +mpiper-y += adc121c027.o +mpiper-y += airohaCalibration.o +mpiper-y += digiPs.o +obj-$(CONFIG_DIGI_PIPER_WIFI) := mpiper.o + diff --git a/drivers/net/wireless/digiPiper/adc121c027.c b/drivers/net/wireless/digiPiper/adc121c027.c new file mode 100644 index 000000000000..17564bcd7a66 --- /dev/null +++ b/drivers/net/wireless/digiPiper/adc121c027.c @@ -0,0 +1,161 @@ +/* + adc121C027.c - Analog to Digital converter integrated into Piper. + + Copyright (C) 2009 Digi International <sales2@digi.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon-sysfs.h> + +#include "airohaCalibration.h" +#include "pipermain.h" + +/* Addresses to scan: none, device is not autodetected */ +/* static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; */ + +#define ADC_I2C_ADDR (0x51) +#define ADC_CYCLE_TIME (0x20) + +static const unsigned short normal_i2c[] = { ADC_I2C_ADDR, I2C_CLIENT_END }; +static const unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END }; + +static struct i2c_client_address_data addr = { + .normal_i2c = normal_i2c, + .probe = dummy_i2c_addrlist, + .ignore = dummy_i2c_addrlist, +}; + +enum adc121C027_cmd { + ADC_RESULT = 0, + ADC_ALERT_STATUS = 1, + ADC_CONFIGURATION = 2, + ADC_LOW_LIMIT = 3, + ADC_HIGH_LIMIT = 4, + ADC_HYSTERESIS = 5, + ADC_LOWEST_VALUE = 6, + ADC_HIGHEST_VALUE = 7, +}; + +static u16 adc121C027_read_peak(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + return be16_to_cpu(i2c_smbus_read_word_data(i2cclient, ADC_HIGHEST_VALUE)); +} + +static void adc121C027_clear_peak(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + i2c_smbus_write_word_data(i2cclient, ADC_HIGHEST_VALUE, 0); +} + +static u16 adc121C027_read_last_sample(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + return be16_to_cpu(i2c_smbus_read_word_data(i2cclient, ADC_RESULT)); +} + +static int adc121C027_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + return (client->addr != ADC_I2C_ADDR) ? -EINVAL : 0; +} + +static int adc121C027_remove(struct i2c_client *client) +{ + /* Real shut down will be done by adc121C027_shutdown() */ + return 0; +} + +static const struct i2c_device_id adc121C027_id[] = { + { "adc121C027", 0 }, + {} +}; + +static struct i2c_driver adc121C027_driver = { + .driver = { + .name = "adc121C027", + }, + .probe = adc121C027_probe, + .remove = __devexit_p(adc121C027_remove), + .id_table = adc121C027_id, + .address_data = &addr, +}; + +/* Turn on automatic A/D process by setting a non zero cycle time */ +static void adc121C027_hw_init(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + i2c_smbus_write_word_data(i2cclient, ADC_CONFIGURATION, ADC_CYCLE_TIME); +} + +void adc121C027_shutdown(struct airohaCalibrationData *cal) +{ + struct i2c_client *i2cclient = (struct i2c_client *)cal->priv; + + if (i2cclient) { + i2c_unregister_device(i2cclient); + cal->priv = NULL; + } + i2c_del_driver(&adc121C027_driver); +} + +int adc121C027_init(struct airohaCalibrationData *cal, int i2cadapter) +{ + struct i2c_board_info board_info = { + .type = "adc121C027", + .addr = ADC_I2C_ADDR, + }; + struct i2c_adapter *adapter; + struct i2c_client *adc_i2c_client; + int ret; + + ret = i2c_add_driver(&adc121C027_driver); + if (ret) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error adding driver adc121C027_driver (%d)\n", ret); + return ret; + } + + adapter = i2c_get_adapter(i2cadapter); + if (!adapter) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error getting i2c adapter\n"); + return -EINVAL; + } + + adc_i2c_client = i2c_new_device(adapter, &board_info); + if (!adc_i2c_client) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error creating new i2c client\n"); + return -EINVAL; + } + + cal->priv = (void *)adc_i2c_client; + adc121C027_hw_init(cal); + + cal->cops = kmalloc(sizeof(struct calibration_ops), GFP_KERNEL); + if (!cal->cops) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": unable to allocate memory for cal->cops\n"); + return -ENOMEM; + } + cal->cops->adc_read_peak = adc121C027_read_peak; + cal->cops->adc_clear_peak = adc121C027_clear_peak; + cal->cops->adc_read_last_val = adc121C027_read_last_sample; + cal->cops->adc_shutdown = adc121C027_shutdown; + + return 0; +} +EXPORT_SYMBOL_GPL(adc121C027_init); + + diff --git a/drivers/net/wireless/digiPiper/adc121c027.h b/drivers/net/wireless/digiPiper/adc121c027.h new file mode 100644 index 000000000000..1453d8a66963 --- /dev/null +++ b/drivers/net/wireless/digiPiper/adc121c027.h @@ -0,0 +1,17 @@ +/* + adc121C027.h - Analog to Digital converter integrated into Piper. + + Copyright (C) 2009 Digi International <sales2@digi.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. +*/ + +#ifndef ADC121C027_H +#define ADC121C027_H + +int adc121C027_init(struct airohaCalibrationData *cal, int i2cadapter); + +#endif /* ADC121C027_H */ + diff --git a/drivers/net/wireless/digiPiper/airoha.c b/drivers/net/wireless/digiPiper/airoha.c new file mode 100644 index 000000000000..17ef8a812f98 --- /dev/null +++ b/drivers/net/wireless/digiPiper/airoha.c @@ -0,0 +1,986 @@ +/* + * Ubec AH7230 radio support. + * + * Copyright © 2009 Digi International, Inc + * + * Author: Contact support@digi.com for information about this software. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <net/mac80211.h> +#include <net/wireless.h> + +#include "pipermain.h" +#include "mac.h" +#include "airohaCalibration.h" +#include "airoha.h" + +/* + * Number of us to change channels. I counted the number of udelays once + * and it was about 2030, plus the 1 us delays for each register write. + * So probably about 2200 in reality, I'm saying 2500 to be safe. + */ +#define CHANNEL_CHANGE_TIME (2500) + +/* + * Maximum possible receive signal strength in dbm. Most of the + * values will be negative. + */ +#define MAX_SIGNAL_IN_DBM (5) + +#define read_reg(reg) priv->ac->rd_reg(priv,reg) +#define write_reg(reg,val,op) priv->ac->wr_reg(priv,reg,val,op) +#define mac_set_tx_power(x) al7230_set_txpwr(hw,x) + +static unsigned int hw_revision = WCD_HW_REV_A; +static unsigned int hw_platform = WCD_CCW9P_PLATFORM; + +static void InitializeRF(struct ieee80211_hw *hw, int band_selection); +static int al7230_set_txpwr(struct ieee80211_hw *hw, uint8_t val); + +static const struct { + unsigned int integer; + unsigned int fraction; + unsigned int address4; + unsigned int tracking; +} freqTableAiroha_7230[] = { + { 0, 0, 0, 0 }, // 0 + + // 2.4 GHz band (802.11b/g) + { 0x00379, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-1 (2412 MHz) 1 + { 0x00379, 0x1B333, 0x7FD78, TRACK_BG_BAND }, // B-2 (2417 MHz) 2 + { 0x00379, 0x03333, 0x7FD78, TRACK_BG_BAND }, // B-3 (2422 MHz) 3 + { 0x00379, 0x0B333, 0x7FD78, TRACK_BG_BAND }, // B-4 (2427 MHz) 4 + { 0x0037A, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-5 (2432 MHz) 5 + { 0x0037A, 0x1B333, 0x7FD78, TRACK_BG_BAND }, // B-6 (2437 MHz) 6 + { 0x0037A, 0x03333, 0x7FD78, TRACK_BG_BAND }, // B-7 (2442 MHz) 7 + { 0x0037A, 0x0B333, 0x7FD78, TRACK_BG_BAND }, // B-8 (2447 MHz) 8 + { 0x0037B, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-9 (2452 MHz) 9 + { 0x0037B, 0x1B333, 0x7FD78, TRACK_BG_BAND }, // B-10 (2457 MHz) 10 + { 0x0037B, 0x03333, 0x7FD78, TRACK_BG_BAND }, // B-11 (2462 MHz) 11 + { 0x0037B, 0x0B333, 0x7FD78, TRACK_BG_BAND }, // B-12 (2467 MHz) 12 + { 0x0037C, 0x13333, 0x7FD78, TRACK_BG_BAND }, // B-13 (2472 MHz) 13 + { 0x0037C, 0x06666, 0x7FD78, TRACK_BG_BAND }, // B-14 (2484 MHz) 14 + + { 0, 0, 0, 0 }, // reserved for future b/g expansion 15 + { 0, 0, 0, 0 }, // reserved for future b/g expansion 16 + + // Extended 4 GHz bands (802.11a) - Lower Band + { 0x0FF52, 0x00000, 0x67F78, TRACK_4920_4980_A_BAND }, // L-184 (4920 MHz) 17 + { 0x0FF52, 0x0AAAA, 0x77F78, TRACK_4920_4980_A_BAND }, // L-188 (4940 MHz) 18 + { 0x0FF53, 0x15555, 0x77F78, TRACK_4920_4980_A_BAND }, // L-192 (4960 MHz) 19 + { 0x0FF53, 0x00000, 0x67F78, TRACK_4920_4980_A_BAND }, // L-196 (4980 MHz) 20 + + // Extended 5 GHz bands (802.11a) + { 0x0FF54, 0x00000, 0x67F78, TRACK_5150_5350_A_BAND }, // A-8 (5040 MHz) 21 tracking? + { 0x0FF54, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-12 (5060 MHz) 22 tracking? + { 0x0FF55, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-16 (5080 MHz) 23 tracking? + { 0x0FF56, 0x05555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-34 (5170 MHz) 24 + { 0x0FF56, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-36 (5180 MHz) 25 + { 0x0FF57, 0x10000, 0x77F78, TRACK_5150_5350_A_BAND }, // A-38 (5190 MHz) 26 + { 0x0FF57, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-40 (5200 MHz) 27 + { 0x0FF57, 0x1AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-42 (5210 MHz) 28 + { 0x0FF57, 0x00000, 0x67F78, TRACK_5150_5350_A_BAND }, // A-44 (5220 MHz) 29 + { 0x0FF57, 0x05555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-46 (5230 MHz) 30 + { 0x0FF57, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-48 (5240 MHz) 31 + + { 0x0FF58, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-52 (5260 MHz) 32 + { 0x0FF58, 0x00000, 0x67F78, TRACK_5150_5350_A_BAND }, // A-56 (5280 MHz) 33 + { 0x0FF58, 0x0AAAA, 0x77F78, TRACK_5150_5350_A_BAND }, // A-60 (5300 MHz) 34 + { 0x0FF59, 0x15555, 0x77F78, TRACK_5150_5350_A_BAND }, // A-64 (5320 MHz) 35 + + { 0x0FF5C, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-100 (5500 MHz) 36 + { 0x0FF5C, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-104 (5520 MHz) 37 + { 0x0FF5C, 0x0AAAA, 0x77F78, TRACK_5470_5725_A_BAND }, // A-108 (5540 MHz) 38 + { 0x0FF5D, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-112 (5560 MHz) 39 + { 0x0FF5D, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-116 (5580 MHz) 40 + { 0x0FF5D, 0x0AAAA, 0x77F78, TRACK_5470_5725_A_BAND }, // A-120 (5600 MHz) 41 + { 0x0FF5E, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-124 (5620 MHz) 42 + { 0x0FF5E, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-128 (5640 MHz) 43 + { 0x0FF5E, 0x0AAAA, 0x77F78, TRACK_5470_5725_A_BAND }, // A-132 (5660 MHz) 44 + { 0x0FF5F, 0x15555, 0x77F78, TRACK_5470_5725_A_BAND }, // A-136 (5680 MHz) 45 + { 0x0FF5F, 0x00000, 0x67F78, TRACK_5470_5725_A_BAND }, // A-140 (5700 MHz) 46 + + { 0x0FF60, 0x18000, 0x77F78, TRACK_5725_5825_A_BAND }, // A-149 (5745 MHz) 47 + { 0x0FF60, 0x02AAA, 0x77F78, TRACK_5725_5825_A_BAND }, // A-153 (5765 MHz) 48 + { 0x0FF60, 0x0D555, 0x77F78, TRACK_5725_5825_A_BAND }, // A-157 (5785 MHz) 49 + { 0x0FF61, 0x18000, 0x77F78, TRACK_5725_5825_A_BAND }, // A-161 (5805 MHz) 50 + { 0x0FF61, 0x02AAA, 0x77F78, TRACK_5725_5825_A_BAND }, // A-165 (5825 MHz) 51 +}; + +#define CHAN4G(idx, _freq) \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = idx, \ + .max_antenna_gain = 0, \ + .max_power = 12 + +static struct ieee80211_channel al7230_bg_channels[] = { + { CHAN4G(1, 2412) }, + { CHAN4G(2, 2417) }, + { CHAN4G(3, 2422) }, + { CHAN4G(4, 2427) }, + { CHAN4G(5, 2432) }, + { CHAN4G(6, 2437) }, + { CHAN4G(7, 2442) }, + { CHAN4G(8, 2447) }, + { CHAN4G(9, 2452) }, + { CHAN4G(10, 2457) }, + { CHAN4G(11, 2462) }, + { CHAN4G(12, 2467) }, + { CHAN4G(13, 2472) }, + { CHAN4G(14, 2484) }, +}; + +static const struct ieee80211_rate al7230_bg_rates[] = { + /* psk/cck rates */ + { + .bitrate = 10, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + { + .bitrate = 20, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + { + .bitrate = 55, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + { + .bitrate = 110, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, + }, + /* ofdm rates */ + { + .bitrate = 60, + .hw_value = 0xb, + }, + { + .bitrate = 90, + .hw_value = 0xf, + }, + { + .bitrate = 120, + .hw_value = 0xa, + }, + { + .bitrate = 180, + .hw_value = 0xe, + }, + { + .bitrate = 240, + .hw_value = 0x9, + }, + { + .bitrate = 360, + .hw_value = 0xd, + }, + { + .bitrate = 480, + .hw_value = 0x8, + }, + { + .bitrate = 540, + .hw_value = 0xc, + }, +}; + +#define CHAN5G(idx, frequency) \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = frequency, \ + .max_antenna_gain = 0, \ + .max_power = 8, \ + .hw_value = idx + +static struct ieee80211_channel al7230_a_channels[] = { + { CHAN5G(17, 4920) }, + { CHAN5G(18, 4940) }, + { CHAN5G(19, 4960) }, + { CHAN5G(20, 4980) }, + + { CHAN5G(21, 5040) }, + { CHAN5G(22, 5060) }, + { CHAN5G(23, 5080) }, + { CHAN5G(24, 5170) }, + { CHAN5G(25, 5180) }, + { CHAN5G(26, 5190) }, + { CHAN5G(27, 5200) }, + { CHAN5G(28, 5210) }, + { CHAN5G(29, 5220) }, + { CHAN5G(30, 5230) }, + { CHAN5G(31, 5240) }, + + { CHAN5G(32, 5260) }, + { CHAN5G(33, 5280) }, + { CHAN5G(34, 5300) }, + { CHAN5G(35, 5320) }, + + { CHAN5G(36, 5500) }, + { CHAN5G(37, 5520) }, + { CHAN5G(38, 5540) }, + { CHAN5G(39, 5560) }, + { CHAN5G(40, 5580) }, + { CHAN5G(41, 5600) }, + { CHAN5G(42, 5620) }, + { CHAN5G(43, 5640) }, + { CHAN5G(44, 5660) }, + { CHAN5G(45, 5680) }, + { CHAN5G(46, 5700) }, + + { CHAN5G(47, 5745) }, + { CHAN5G(48, 5765) }, + { CHAN5G(49, 5785) }, + { CHAN5G(50, 5805) }, + { CHAN5G(51, 5825) } +}; + +static const struct ieee80211_rate al7230_a_rates[] = { + /* ofdm rates */ + { + .bitrate = 60, + .hw_value = 0xb, + }, + { + .bitrate = 90, + .hw_value = 0xf, + }, + { + .bitrate = 120, + .hw_value = 0xa, + }, + { + .bitrate = 180, + .hw_value = 0xe, + }, + { + .bitrate = 240, + .hw_value = 0x9, + }, + { + .bitrate = 360, + .hw_value = 0xd, + }, + { + .bitrate = 480, + .hw_value = 0x8, + }, + { + .bitrate = 540, + .hw_value = 0xc, + }, +}; + +static enum ieee80211_band getBand(int channelIndex) +{ + enum ieee80211_band result; + + if (channelIndex >= BAND_A_OFFSET) { + result = IEEE80211_BAND_5GHZ; + } else { + result = IEEE80211_BAND_2GHZ; + } + + return result; +} + +static int getFrequency(int channelIndex) +{ + int result; + + if (getBand(channelIndex) == IEEE80211_BAND_5GHZ) { + result = al7230_a_channels[channelIndex - BAND_A_OFFSET].center_freq; + } else { + result = al7230_bg_channels[channelIndex - 1].center_freq; + } + + return result; +} + +static int write_rf(struct ieee80211_hw *hw, unsigned char reg, unsigned int val) +{ + struct piper_priv *priv = hw->priv; + int err; + + err = write_reg(BB_SPI_DATA, val << 4 | reg, op_write); + udelay(3); /* Mike Schaffner says to allow 2 us or more between all writes */ + return err; +} + + +/* + * This function is called to set the value of Airoha register + * 0xc. This register must be set to different values depending + * on the H/W revision of the board due to changes in the board + * design. + */ +static void set_hw_specific_parameters(struct ieee80211_hw *hw, + unsigned int band, + unsigned int hw_revision, + unsigned int hw_platform) +{ + switch (hw_platform) { + case WCD_CCW9P_PLATFORM: + switch (hw_revision) { + case WCD_HW_REV_PROTOTYPE: + case WCD_HW_REV_PILOT: + case WCD_HW_REV_A: + default: + if (band == IEEE80211_BAND_2GHZ) { + write_rf(hw, 0xc, 0x2b); + } else { + write_rf(hw, 0xc, 0x00143 ); + } + break; + } + break; + case WCD_CCW9M_PLATFORM: + switch (hw_revision) { + case WCD_HW_REV_PROTOTYPE: + case WCD_HW_REV_PILOT: + if (band == IEEE80211_BAND_2GHZ) { + write_rf(hw, 0xc, 0xa3); + } else { + write_rf(hw, 0xc, 0x00143 ); + } + break; + + case WCD_HW_REV_A: + default: + if (band == IEEE80211_BAND_2GHZ) { + write_rf(hw, 0xc, 0x70); + } else { + write_rf(hw, 0xc, 0x00143 ); + } + break; + } + break; + default: + break; + } +} + +static int al7230_rf_set_chan_private(struct ieee80211_hw *hw, int channelIndex, bool enable_rx) +{ + struct piper_priv *priv = hw->priv; + static int rf_band; +#ifdef WANT_DEBUG + const char *channelLookup[] = { + "invalid 0", + "B-1", + "B-2", + "B-3", + "B-4", + "B-5", + "B-6", + "B-7", + "B-8", + "B-9", + "B-10", + "B-11", + "B-12", + "B-13", + "B-14", + "invalid 15", + "invalid 16", + "L-184", + "L-188", + "L-192", + "L-196", + "A-8", + "A-12", + "A-16", + "A-34", + "A-36", + "A-38", + "A-40", + "A-42", + "A-44", + "A-46", + "A-48", + "A-52", + "A-56", + "A-60", + "A-64", + "A-100", + "A-104", + "A-108", + "A-112", + "A-116", + "A-120", + "A-124", + "A-128", + "A-132", + "A-136", + "A-140", + "A-149", + "A-153", + "A-157", + "A-161", + "A-165" + }; +printk(KERN_ERR "Setting channel %s\n", channelLookup[channelIndex]); +#endif + if (channelIndex >= BAND_A_OFFSET) + rf_band = IEEE80211_BAND_5GHZ; + else + rf_band = IEEE80211_BAND_2GHZ; + /* Disable the rx processing path */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_RX_EN, op_and); + + write_reg(BB_OUTPUT_CONTROL, 0xfffff33f, op_and); + write_reg(BB_OUTPUT_CONTROL, 0x00000880, op_or); + + if (priv->pdata->rf_transceiver == RF_AIROHA_2236) { +/* TODO, when using this transceiver, resolve this commented code */ +#ifdef BUILD_THIS_CODE_SECTION + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_B_EN, op_or); + + if (macParams.band == WLN_BAND_B) { + /* turn off OFDM */ + write_reg(BB_GENERAL_STAT, ~BB_GENERAL_STAT_A_EN, op_and); + } else { + /* turn on OFDM */ + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_A_EN, op_or); + } + /* set the 802.11b/g frequency band specific tracking constant */ + write_reg(BB_TRACK_CONTROL, 0xff00ffff, op_and); + + write_reg(BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + + /* perform chip and frequency-band specific RF initialization */ + InitializeRF(hw, rf_band); + + mac_set_tx_power(priv->tx_power); + + write_rf(hw, 0, freqTableAiroha_2236[channelIndex].integer); + write_rf(hw, 1, freqTableAiroha_2236[channelIndex].fraction); + + /* critical delay for correct calibration */ + udelay(150); + + /* + * TXON, PAON and RXON should all be low before Calibration + * TXON and PAON will be low as long as no frames are written to the TX + * DATA fifo. + * RXON will be low as long as the receive path is not enabled (bit 0 of + * GEN CTL register is 0). + */ + + /* calibrate RF transceiver */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x00D87); + udelay(50); + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x00787); + udelay(50); + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x00587); + udelay(50); + + /* configure the baseband processing engine */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_GEN_5GEN, op_and); + + /*Re-enable the rx processing path */ + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_RX_EN, op_or); +#endif + } else if (priv->pdata->rf_transceiver == RF_AIROHA_7230) { + /* enable the frequency-band specific PA */ + if (rf_band == IEEE80211_BAND_2GHZ) { + //HW_GEN_CONTROL &= ~GEN_PA_ON; + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_A_EN | BB_GENERAL_STAT_B_EN, + op_or); + + /* set the 802.11b/g frequency band specific tracking constant */ + write_reg(BB_TRACK_CONTROL, 0xff00ffff, op_and); + + write_reg(BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + + } else { + //HW_GEN_CONTROL |= GEN_PA_ON; + + // turn off PSK/CCK + write_reg(BB_GENERAL_STAT, ~BB_GENERAL_STAT_B_EN, op_and); + + // turn on OFDM + write_reg(BB_GENERAL_STAT, BB_GENERAL_STAT_A_EN, op_or); + + /* Set the 802.11a frequency sub-band specific tracking constant */ + /* All 8 supported 802.11a channels are in this 802.11a frequency sub-band */ + write_reg(BB_TRACK_CONTROL, 0xff00ffff, op_and); + + write_reg(BB_TRACK_CONTROL, freqTableAiroha_7230[channelIndex].tracking, + op_or); + } + + /* perform chip and frequency-band specific RF initialization */ + InitializeRF(hw, rf_band); + + mac_set_tx_power(priv->tx_power); + + /* Set the channel frequency */ + write_rf(hw, 0, freqTableAiroha_7230[channelIndex].integer); + udelay(150); /* Mike Schaffner says this is needed here */ + write_rf(hw, 1, freqTableAiroha_7230[channelIndex].fraction); + udelay(150); /* Mike Schaffner says this is needed here */ + write_rf(hw, 4, freqTableAiroha_7230[channelIndex].address4); + udelay(150); /* Mike Schaffner says this is needed here */ + + + // Select the frequency band: 5Ghz or 2.4Ghz + if (rf_band == IEEE80211_BAND_5GHZ) { + /* calibrate RF transceiver */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x12BAC); + udelay(50); + + /* configure the baseband processing engine */ + /* + * This bit always as to be turned off when we are using + * the Airoha chip, even though it's named the 5G EN bit. + * It has to do with how they hooked up the Airoha. + */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_GEN_5GEN, op_and); + } else { + /* calibrate RF transceiver */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x1ABA8); + udelay(50); + + /* configure the baseband processing engine */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_GEN_5GEN, op_and); + /* + * No short preambles allowed for ODFM. + */ + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_SH_PRE, op_and); + } + + /*Re-enable the rx processing path */ + if (enable_rx) + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_RX_EN, op_or); + + /* re-enable transmitter */ + write_reg(BB_OUTPUT_CONTROL, 0xfffff33f, op_and); + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME ": undefined rf transceiver!\n"); + return -EINVAL; + } + + /* + * This is a patch for a problem which should be corrected in + * hardware on new units. We are rewriting the MAC address + * because on units without the H/W patch the address can + * be corrupted when we change channels. + */ + piper_set_macaddr(priv); +digiWifiDumpRegisters(priv, MAIN_REGS); + + return 0; +} + +static int al7230_rf_set_chan(struct ieee80211_hw *hw, int channelIndex) +{ + return al7230_rf_set_chan_private(hw, channelIndex, true); +} + +static int al7230_rf_set_chan_no_rx(struct ieee80211_hw *hw, int channelIndex) +{ + return al7230_rf_set_chan_private(hw, channelIndex, false); +} + + + +static int al7230_set_txpwr(struct ieee80211_hw *hw, uint8_t value) +{ + struct piper_priv *priv = hw->priv; + + if (priv->pdata->rf_transceiver == RF_AIROHA_2236) { + const unsigned char powerTable_2236[] = { + 4, 10, 10, 18, 22, 22, 28, 28, + 33, 33, 36, 38, 40, 43, 45, 47 + }; + write_rf(hw, 9, 0x05440 | powerTable_2236[value & 0xf]); + } else if (priv->pdata->rf_transceiver == RF_AIROHA_7230) { + const unsigned char powerTable_7230[] = { + 0x14, 0x14, 0x14, 0x18, 0x18, 0x1c, 0x1c, 0x20, + 0x20, 0x24, 0x24, 0x29, 0x29, 0x2c, 0x2c, 0x30 + }; + int correctedPowerIndex = digiWifiCalibrationPowerIndex(priv); + + if (correctedPowerIndex != -1) { + write_rf(hw, 11, 0x08040 | correctedPowerIndex); + } else { + write_rf(hw, 11, 0x08040 | powerTable_7230[value & 0xf]); + } + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": undefined rf transceiver!\n"); + return -EINVAL; + } + return 0; +} + +static void al7230_set_power_index(struct ieee80211_hw *hw, unsigned int value) +{ + write_rf(hw, 11, 0x08040 | value); +} + +static void InitializeRF(struct ieee80211_hw *hw, int band_selection) +{ + struct piper_priv *priv = hw->priv; + + if (priv->pdata->rf_transceiver == RF_AIROHA_2236) { + digi_dbg("**** transceiver == RF_AIROHA_2236\n"); + /* Initial settings for 20 MHz reference frequency, 802.11b/g */ + + /* CH_integer: Frequency register 0 */ + write_rf(hw, 0, 0x01f79 ); + + /* CH_fraction: Frequency register 1 */ + write_rf(hw, 1, 0x03333 ); + + /*Config 1 = default value */ + write_rf(hw, 2, 0x00B80 ); + + /*Config 2 = default value */ + write_rf(hw, 3, 0x00E7F ); + + /*Config 3 = default value */ + write_rf(hw, 4, 0x0905A ); + + /*Config 4 = default value */ + write_rf(hw, 5, 0x0F4DC ); + + /*Config 5 = default value */ + write_rf(hw, 6, 0x0805B ); + + /*Config 6 = Crystal frequency /2 to pll reference divider */ + write_rf(hw, 7, 0x0116C ); + + /*Config 7 = RSSI = default value */ + write_rf(hw, 8, 0x05B68 ); + + /* TX gain control for LA2236 */ + write_rf(hw, 9, 0x05460 ); // sit at the middle + + /* RX Gain = digi specific value: AGC adjustment is done over the GC1-GC7 + IC pins interface. AGC MAX GAIN value is configured in the FPGA BB register + instead of the RF register here below */ + write_rf(hw, 10, 0x001BB ); + + /* TX Gain = digi specific vaue: TX GAIN set using the register */ + write_rf(hw, 11, 0x000f9 ); + + /* PA current = default value */ + write_rf(hw, 12, 0x039D8 ); + + /* Config 8 = default value */ + write_rf(hw, 13, 0x08000 ); + + /* Config 9 = default value */ + write_rf(hw, 14, 0x00000 ); + + /* Config 10 = default value */ + write_rf(hw, 15, 0x00587 ); + + //mac_set_tx_power (macParams.tx_power); + + /* Calibration procedure */ + write_reg(BB_OUTPUT_CONTROL, 0x00000300, op_or); + udelay(150); + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x00D87 ); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x00787 ); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x00587 ); + udelay(50); + } else if (priv->pdata->rf_transceiver == RF_AIROHA_7230) { + switch (band_selection) { + case IEEE80211_BAND_2GHZ: + /* Initial settings for 20 MHz reference frequency, 802.11b/g */ + write_reg(BB_OUTPUT_CONTROL, 0xfffffcff, op_and); + write_reg(BB_OUTPUT_CONTROL, 0x00000200, op_or); + udelay(150); + + /* Frequency register 0 */ + write_rf(hw, 0, 0x00379 ); + + /* Frequency register 1 */ + write_rf(hw, 1, 0x13333 ); + udelay(10); + + /*Config 1 = default value */ + write_rf(hw, 2, 0x841FF ); + + /*Config 2 = default value */ + write_rf(hw, 3, 0x3FDFA ); + + /*Config 3 = default value */ + write_rf(hw, 4, 0x7FD78 ); + + /*Config 4 = default value */ + write_rf(hw, 5, 0x802BF ); + + /*Config 5 = default value */ + write_rf(hw, 6, 0x56AF3 ); + + /*Config 6 = Crystal frequency /2 to pll reference divider */ + write_rf(hw, 7, 0xCE000 ); + + /*Config 7 = RSSI = default value */ + write_rf(hw, 8, 0x6EBC0 ); + + /* Filter BW = default value */ + write_rf(hw, 9, 0x221BB ); + + /* RX Gain = digi specific value: AGC adjustment is done over the GC1-GC7 + IC pins interface. AGC MAX GAIN value is configured in the FPGA BB register + instead of the RF register here below */ + write_rf(hw, 10, 0xE0040 ); + + /* TX Gain = digi specific vaue: TX GAIN set using the register */ + // write_rf(hw, 11, 0x08070); + mac_set_tx_power (priv->tx_power); //Digi value + + /* PA current = default value */ + set_hw_specific_parameters(hw, IEEE80211_BAND_2GHZ, hw_revision, hw_platform); + + /* Config 8 = default value */ + write_rf(hw, 13, 0xFFFFF ); + + /* Config 9 = default value */ + write_rf(hw, 14, 0x00000 ); + + /* Config 10 = default value */ + write_rf(hw, 15, 0x1ABA8 ); + + /* Calibration procedure */ + write_reg(BB_OUTPUT_CONTROL, 0x00000300, op_or); + + udelay(150); + + /* Calibration procedure */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x1ABA8 ); + udelay(50); + + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_MAX_GAIN_MASK, op_and); + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_DEFAULT_MAX_GAIN_BG, op_or); + break; + + case IEEE80211_BAND_5GHZ: + /* Initial settings for 20 MHz reference frequency, 802.11a */ + write_reg(BB_OUTPUT_CONTROL, 0xfffffcff, op_and); + write_reg(BB_OUTPUT_CONTROL, 0x00000200, op_or); + udelay(150); + + /* Frequency register 0 */ + write_rf(hw, 0, 0x0FF56 ); + + /* Frequency register 1 */ + write_rf(hw, 1, 0x0AAAA ); + + udelay(10); + + /*Config 1 = default value */ + write_rf(hw, 2, 0x451FE ); + + /*Config 2 = default value */ + write_rf(hw, 3, 0x5FDFA ); + + /*Config 3 = default value */ + write_rf(hw, 4, 0x67f78 ); + + /*Config 4 = default value */ + write_rf(hw, 5, 0x853FF ); + + /*Config 5 = default value */ + write_rf(hw, 6, 0x56AF3 ); + + /*Config 6 = Crystal frequency /2 to pll reference divider */ + write_rf(hw, 7, 0xCE000 ); + + /*Config 7 = RSSI = default value */ + write_rf(hw, 8, 0x6EBC0 ); + + /* Filter BW = default value */ + write_rf(hw, 9, 0x221BB ); + + /* RX Gain = digi value */ + write_rf(hw, 10, 0xE0600 ); + + /* TX Gain = digi specific vaue: TX GAIN set using the register */ + // write_rf(hw, 11, 0x08070 ); + mac_set_tx_power (priv->tx_power); //Digi value + + /* PA current = default value */ + set_hw_specific_parameters(hw, IEEE80211_BAND_5GHZ, hw_revision, hw_platform); + + /* Config 8 = default value */ + write_rf(hw, 13, 0xFFFFF ); + + /* Config 9 = default value */ + write_rf(hw, 14, 0x00000 ); + + /* Config 10 = default value */ + write_rf(hw, 15, 0x12BAC ); + + /* Calibration procedure */ + write_reg(BB_OUTPUT_CONTROL, 0x00000300, op_or); + + udelay(150); + + /* Calibration procedure */ + + /* TXDCOC->active; RCK->disable */ + write_rf(hw, 15, 0x9ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->enable */ + write_rf(hw, 15, 0x3ABA8 ); + udelay(50); + + /* TXDCOC->disable; RCK->disable */ + write_rf(hw, 15, 0x12BAC ); + udelay(50); + write_reg(BB_GENERAL_CTL, ~BB_GENERAL_CTL_MAX_GAIN_MASK, op_and); + write_reg(BB_GENERAL_CTL, BB_GENERAL_CTL_DEFAULT_MAX_GAIN_A, op_or); + break; + } + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": undefined rf transceiver!\n"); + } +} + +static int al7230_rf_stop(struct ieee80211_hw *hw) +{ + return 0; +} + +static void getOfdmBrs(int channelIndex, u64 brsBitMask, unsigned int *ofdm, unsigned int *psk) +{ + /* + * brsBitMask is a bit mask into the al7230_bg_rates array. Bit 0 refers + * to the first entry in the array, bit 1 the second, and so on. The first + * 4 bits/array entries refer to the PSK bit rates we support, the next 8 + * bits/array entries refer to the OFDM rates we support. So the PSK BRS + * mask is bits 0-3, the OFDM bit mask is bits 4-11. + */ + + if (getBand(channelIndex) == IEEE80211_BAND_2GHZ) + { + *psk = brsBitMask & 0xf; + *ofdm = (brsBitMask & 0xff0) >> 4; + } + else + { + *psk = 0; + *ofdm = (brsBitMask & 0xff); + } +} + +static struct ieee80211_supported_band al7230_bands[] = { + { + .band = IEEE80211_BAND_2GHZ, + .n_channels = ARRAY_SIZE(al7230_bg_channels), + .n_bitrates = ARRAY_SIZE(al7230_bg_rates), + .channels = (struct ieee80211_channel *) al7230_bg_channels, + .bitrates = (struct ieee80211_rate *) al7230_bg_rates, + }, + { + .band = IEEE80211_BAND_5GHZ, + .n_channels = ARRAY_SIZE(al7230_a_channels), + .n_bitrates = ARRAY_SIZE(al7230_a_rates), + .channels = (struct ieee80211_channel *) al7230_a_channels, + .bitrates = (struct ieee80211_rate *) al7230_a_rates, + }, +}; + +static const struct ieee80211_rate *getRate(unsigned int rateIndex) +{ + return &al7230_bg_rates[rateIndex]; +} + + +/* + * This routine can power up or power down the airoha transceiver. + * When the transceiver is powered back up, you must delay 1 ms and + * then call the set channel routine to make it operational again. + */ +static void power_on(struct ieee80211_hw *hw, bool want_power_on) +{ + if (want_power_on) { + write_rf(hw, 15, 0x1ABA8 ); /* this is actually for 2 Ghz */ + } else { + write_rf(hw, 15, 0x1ABAE ); + } +} + +static void al7230_set_hw_info(struct ieee80211_hw *hw, int channel, + u16 hw_platform_code) +{ + hw_revision = hw_platform_code & WCD_HW_REV_MASK; + hw_platform = (hw_platform_code & WCD_PLATFORM_MASK); + + set_hw_specific_parameters(hw, getBand(channel), hw_revision, hw_platform); +} + +struct digi_rf_ops al7230_rf_ops = { + .name = "Airoha 7230", + .init = InitializeRF, + .stop = al7230_rf_stop, + .set_chan = al7230_rf_set_chan, + .set_chan_no_rx = al7230_rf_set_chan_no_rx, + .set_pwr = al7230_set_txpwr, + .set_pwr_index = al7230_set_power_index, + .set_hw_info = al7230_set_hw_info, + .channelChangeTime = CHANNEL_CHANGE_TIME, + .maxSignal = MAX_SIGNAL_IN_DBM, + .getOfdmBrs = getOfdmBrs, + .getBand = getBand, + .getFrequency = getFrequency, + .getRate = getRate, + .bands = al7230_bands, + .power_on = power_on, + .n_bands = ARRAY_SIZE(al7230_bands), +}; +EXPORT_SYMBOL_GPL(al7230_rf_ops); diff --git a/drivers/net/wireless/digiPiper/airoha.h b/drivers/net/wireless/digiPiper/airoha.h new file mode 100644 index 000000000000..aa3b1d10fbe3 --- /dev/null +++ b/drivers/net/wireless/digiPiper/airoha.h @@ -0,0 +1,30 @@ +#ifndef DIGI_RF_AH7230_H_ +#define DIGI_RF_AH7230_H_ + +/* 0 is reserved for unknown transceiver */ +#define RF_AIROHA_7230 (1) +#define RF_AIROHA_2236 (2) + +#define SPI_INIT_AIROHA (0x00000018) /* AIROHA-specific SPI length */ +#define SPI_INIT_AIROHA2236 (0x00000014) /* AIROHA 2236-specific SPI length */ +#define GEN_INIT_AIROHA_24GHZ (0x31720005) /* Initial state; 2.4GHZ_PA_ON= active low; bit 25 */ +#define GEN_INIT_AIROHA_50GHZ (0x33760008) /* Initial state; 5.0GHZ_PA_ON= active high; bit 25 */ + +#define AIROHA_LOWEST_PSK_RATE_INDEX (0) +#define AIROHA_LOWEST_OFDM_RATE_INDEX (4) +#define AIROHA_55_MBPS_RATE_INDEX (2) + +/* + * Subtract this number from the channel index to index into + * the 802.11a channel array. + */ +#define BAND_A_OFFSET (17) + +struct digi_rf_freq { + uint16_t integer; + uint16_t fract; +}; + +extern struct digi_rf_ops al7230_rf_ops; + +#endif diff --git a/drivers/net/wireless/digiPiper/airohaCalibration.c b/drivers/net/wireless/digiPiper/airohaCalibration.c new file mode 100644 index 000000000000..9da912f320ba --- /dev/null +++ b/drivers/net/wireless/digiPiper/airohaCalibration.c @@ -0,0 +1,974 @@ +/* + * This file contains the code which performs automatic recalibration of the + * Airoha transceiver. + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free softbware; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/timer.h> +#include <linux/crc32.h> + +#include "pipermain.h" +#include "mac.h" +#include "airoha.h" +#include "airohaCalibration.h" +#include "adc121c027.h" + +#define POWER_INDEX_STEP (1) /* TODO: come up with a rational value for this */ + +#define SAMPLE_TIMEOUT (HZ * 10) /* TODO: What is a good sample timeout? Do we need one? */ +#define RECALIBRATION_PERIOD (HZ * 15) /* amount of time to wait between recalibrations */ + +/* + * This defines the amount of time to wait for the power levels + * to settle down after making a large correction (user has + * changed power level). + */ +#define DEBOUNCE_DELAY (HZ * 2) + +#define MAX_TOLERATED_ERROR_HIGH (300) /* maximum over target we will allow */ +#define MAX_TOLERATED_ERROR_LOW (500) /* maximum under the target we will allow */ +#define CONVERT_TO_MDBM(x) (1000 * (x)) /* power levels are dBm externally, but dBm/1000 internally */ + +#define NVRAM_WCAL_SIGNATURE "WCALDATA" + +#define MINIMUM_POWER_INDEX (10) + +/* + * Set piperp->calibrationTxRate to this value to make the + * transmit routine use the data rate specified by the + * mac80211 library. + */ +#define USE_MAC80211_DATA_RATE (NULL) + +/* + * Events we will wait for, also return values for waitForEvent(). + */ +#define TIMED_OUT_EVENT (1 << 0) +#define TRANSMIT_DONE_EVENT (1 << 1) +#define SHUTDOWN_AUTOCALIBRATION_EVENT (1 << 2) +#define RESTART_AUTOCALIBRATION_EVENT (1 << 3) + +/* + * Set this constant to (1) if you want to force calibration status to + * be printed. + */ +#define WANT_CALIBRATION_STATUS (0) + + +static struct airohaCalibrationData calibration; + +static DECLARE_WAIT_QUEUE_HEAD(waitQueue); + + + +/* + * This routine is called to shut down the transmit ADC sampler. + */ +static void stopSampler(struct piper_priv *digi) +{ + digi->tx_calib_cb = NULL; +} + +/* + * This routine is called to update the state of the calibration state machine + * and wake up its thread. + */ +static void kickCalibrationThread(struct piper_priv *digi, unsigned int event) +{ + unsigned long spinlockFlags; + + spin_lock_irqsave(&calibration.lock, spinlockFlags); + calibration.events |= event; + spin_unlock_irqrestore(&calibration.lock, spinlockFlags); + wake_up_interruptible(&waitQueue); +} + + +/* + * This routine is called each time we complete a transmit while we are in + * the sampling state. We record the peak ADC reading. We kick the state + * machine if we now have all the samples we need. + */ +static void processSample(struct piper_priv *digi) +{ +#define MINIMUM_ADC_VALUE (10) /* if ADC is below this value, it's probably bad */ + if (calibration.sampleCount < MAX_SAMPLES) { + /* + * Read the ADC value. It is a 12-bit value. We shift it 4 bits to + * create an 8-bit value. + */ + calibration.sample[calibration.sampleCount].sample = + (calibration.cops->adc_read_peak(&calibration) >> 4); + if (calibration.sample[calibration.sampleCount].sample > + MINIMUM_ADC_VALUE) { + calibration.sampleCount++; + } + } +} + +static void transmitHasCompleted(struct piper_priv *digi) +{ + stopSampler(digi); + kickCalibrationThread(digi, TRANSMIT_DONE_EVENT); +} + + + +/* + * Determine the appropriate transmit rate to use during calibration. + */ +static struct ieee80211_rate *determineCalibrationTxRate(struct piper_priv + *digi) +{ + unsigned int rates = digi->ac->rd_reg(digi, MAC_SSID_LEN); + struct ieee80211_rate *calibrationTxRate; + + rates &= (MAC_PSK_BRS_MASK | MAC_OFDM_BRS_MASK); + + if ((digi->rf->getBand(digi->channel) == IEEE80211_BAND_2GHZ) + && (rates & MAC_PSK_BRS_MASK)) { + calibrationTxRate = + (struct ieee80211_rate *) digi->rf-> + getRate(AIROHA_LOWEST_PSK_RATE_INDEX); + } else { + calibrationTxRate = + (struct ieee80211_rate *) digi->rf-> + getRate(AIROHA_LOWEST_OFDM_RATE_INDEX); + } + + return calibrationTxRate; +} + + + + +/* + * Start collecting sample ADC peak measurements for calibration. Start + * the process by installing the callbacks which the transmit code will + * use to notify us when transmit frames go out. + */ +static void startSampleCollection(struct piper_priv *digi) +{ + calibration.cops->adc_clear_peak(&calibration); + digi->tx_calib_cb = transmitHasCompleted; +} + + +static unsigned int waitForEvent(unsigned int timeout, unsigned int eventToWaitFor) +{ +#define ALL_EVENTS_TO_WAIT_FOR(x) (eventToWaitFor \ + | SHUTDOWN_AUTOCALIBRATION_EVENT \ + | RESTART_AUTOCALIBRATION_EVENT) + + unsigned long spinlockFlags; + int ccode; + unsigned int event; + int result = TIMED_OUT_EVENT; + + if (timeout != 0) { + ccode = wait_event_interruptible_timeout(waitQueue, + ((calibration. + events & + ALL_EVENTS_TO_WAIT_FOR + (eventToWaitFor)) + != 0), timeout); + __set_current_state(TASK_RUNNING); + + } else { + ccode = 0; + } + spin_lock_irqsave(&calibration.lock, spinlockFlags); + event = calibration.events; + calibration.events = 0; + spin_unlock_irqrestore(&calibration.lock, spinlockFlags); + + if ((ccode < 0) || (event & SHUTDOWN_AUTOCALIBRATION_EVENT)) { + result = SHUTDOWN_AUTOCALIBRATION_EVENT; + } else if (event & RESTART_AUTOCALIBRATION_EVENT) { + result = RESTART_AUTOCALIBRATION_EVENT; + } else if (event & eventToWaitFor) { + result = (event & eventToWaitFor); + } else { + result = TIMED_OUT_EVENT; + } + + return result; +} + + +#ifdef WANT_CAL_DEBUG +static void printPoint(wcd_point_t * p) +{ + printk("(%d, %d, %d)", p->out_power, p->adc_val, p->power_index); +} +#endif + + +/* + * This routine finds the closest pair of points in a calibration curve. + * + * curve calibration curve + * value look for the pr of points closest to this value + * p1 storage for one point + * p2 storage for another point + * field tells us which field in the point struct to compare + */ +static void findClosestPoints(wcd_curve_t * curve, int value, + wcd_point_t ** p1, wcd_point_t ** p2, int field) +{ + if (value <= curve->points[0].out_power) { + *p1 = &curve->points[0]; + *p2 = &curve->points[1]; + } else if (value >= + curve->points[calibration.nvram->header.numcalpoints - 1].out_power) { + *p1 = &curve->points[calibration.nvram->header.numcalpoints - 2]; + *p2 = &curve->points[calibration.nvram->header.numcalpoints - 1]; + } else { + unsigned int idx; + + for (idx = 1; idx < calibration.nvram->header.numcalpoints; idx++) { + if ((value < curve->points[idx].out_power) + || (idx == (calibration.nvram->header.numcalpoints - 1))) { + *p1 = &curve->points[idx - 1]; + *p2 = &curve->points[idx]; + break; + } else if (value == curve->points[idx].out_power) { + /* + * Note that the if statement befpre the for loop already tested for the + * value being equal to the first or last point in the curve, so we don't + * have to worry about that condition in the code below. + */ + if ((value - + curve->points[idx - 1].out_power) >= + (curve->points[idx + 1].out_power - value)) { + /* + * If the two points are equal distant, then favor the larger pair + * because I think the values on the low end are screwy. + */ + *p1 = &curve->points[idx]; + *p2 = &curve->points[idx + 1]; + } else { + *p1 = &curve->points[idx - 1]; + *p2 = &curve->points[idx]; + } + break; + } + } + } +} + + +/* + * Compute the slope of a curve between 2 points. The slope is the rise over the run, + * or (Y2 - Y1)/(X2 - X1). This function handles more than one type of slope. + */ +static int computeSlopeTimes1000(wcd_point_t * p1, wcd_point_t * p2, int slopeType) +{ + int slope = 0; + int divisor; + + switch (slopeType) { + default: + digi_dbg("Unexpected slope type %d.\n", slopeType); + break; + case POWER_INDEX_OVER_OUT_POWER: + divisor = (p2->out_power - p1->out_power); + if (divisor != 0) { + slope = + (((p2->power_index - p1->power_index) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + case ADC_OVER_OUT_POWER: + divisor = (p2->out_power - p1->out_power); + if (divisor != 0) { + slope = + (((p2->adc_val - p1->adc_val) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + case OUT_POWER_OVER_ADC: + divisor = (p2->adc_val - p1->adc_val); + if (divisor != 0) { + slope = + (((p2->out_power - p1->out_power) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + case POWER_INDEX_OVER_ADC: + divisor = (p2->adc_val - p1->adc_val); + if (divisor != 0) { + slope = + (((p2->power_index - p1->power_index) * 1000) + + (divisor / 2)) / divisor; + } else { + digi_dbg("divisor is zero\n"); + } + break; + } + + return slope; +} + + +/* + * If (x,y) is a point on a curve, then compute y given x, the slope of the curve, + * and a known point on the curve. + * + * If (Xd, Yd) is the desired point, p1 is the known point, and m the slope, then + * + * Yd - p1->y = m(Xd - p1->x) + * Yd = m(Xd - p1->x) + p1->y + * Yd = m(Xd) - m(p1->x) + p1->y + */ +static int computeY(wcd_point_t * p1, int slopeTimes1000, int x, int slopeType) +{ + int y = 0; + + switch (slopeType) { + default: + digi_dbg("Unexpected slope type %d.\n", slopeType); + break; + case POWER_INDEX_OVER_OUT_POWER: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->out_power) + 500) / 1000) + p1->power_index; + break; + case ADC_OVER_OUT_POWER: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->out_power) + 500) / 1000) + p1->adc_val; + break; + case OUT_POWER_OVER_ADC: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->adc_val) + 500) / 1000) + p1->out_power; + break; + case POWER_INDEX_OVER_ADC: + y = (((slopeTimes1000 * x) - + (slopeTimes1000 * p1->adc_val) + 500) / 1000) + p1->power_index; + break; + } + + return y; +} + + +/* + * Return a pointer to the curve we should use for the currently selected + * channel. + */ +static wcd_curve_t *determineCurve(struct piper_priv *digi) +{ + unsigned int rates = digi->ac->rd_reg(digi, MAC_SSID_LEN); + wcd_curve_t *curve = NULL; + + if (digi->rf->getBand(digi->channel) == IEEE80211_BAND_2GHZ) { + if (rates & MAC_PSK_BRS_MASK) { + /* + * This is the normal case for 802.11b and 802.11bg. We select + * the PSK curve. + */ + digi_dbg("Using bg curve [%d][%d]\n", + digi->channel, WCD_B_CURVE_INDEX); + curve = &calibration.nvram->cal_curves_bg[digi->channel] + [WCD_B_CURVE_INDEX]; + } else { /* if associated with AP that only supports G rates */ + + /* + * This is a very unusual, but theoretically possible case. We + * are associated with an AP that only supports OFDM modulation + * (G only, no B). We determine this by looking at the BRS + * setting. If no PSK rates are set for BRS, then we assume that + * this must be a G only AP. Obviously, we must select the OFDM curve. + */ + digi_dbg("Using bg curve [%d][%d]\n", + digi->channel, WCD_G_CURVE_INDEX); + curve = &calibration.nvram->cal_curves_bg[digi->channel] + [WCD_G_CURVE_INDEX]; + } + } else { + /* + * An 802.11a channel is selected. + */ + curve = &calibration.nvram->cal_curves_a[digi->channel - BAND_A_OFFSET]; + digi_dbg("Using A curve [%d]\n", digi->channel - BAND_A_OFFSET); + } + + return curve; +} + +/* + * Determine the maximum mdBm allowed for the current channel. Return the + * lesser of the max and the mdBm value passed to us. + */ +static int getFilteredPower(struct piper_priv *digi, int mdBm) +{ + int max = 0; + + if (digi->channel < BAND_A_OFFSET) { + max = 16000; /* all BG channels can go to 16 dBm */ + } else if (digi->channel < (BAND_A_OFFSET + 4)) { + max = 3000; /* first 4 A channels max out at 3 dBm */ + } else { + max = 8000; /* all other A channels can handle 8 dBm */ + } + + if (mdBm > max) { + mdBm = max; + } + + return mdBm; +} + + +/* + * This routine performs open loop calibration for Airoha. It takes a value in mdbm + * and uses the factory calibration routines to determine the appropriate register + * value to write to airoha. + */ +static void setInitialPowerLevel(struct piper_priv *digi, int mdBm) +{ + wcd_point_t *p1, *p2; + + mdBm = getFilteredPower(digi, mdBm); + digi_dbg("Setting initial powerlevel %d milli dBm.\n", mdBm); + calibration.curve = determineCurve(digi); + findClosestPoints(calibration.curve, mdBm, &p1, &p2, OUT_POWER); + calibration.slopeTimes1000 = + computeSlopeTimes1000(p1, p2, POWER_INDEX_OVER_OUT_POWER); + if (abs(p1->out_power - mdBm) < abs(p2->out_power - mdBm)) { + calibration.p1 = p1; + } else { + calibration.p1 = p2; + } + calibration.powerIndex = + computeY(calibration.p1, calibration.slopeTimes1000, mdBm, + POWER_INDEX_OVER_OUT_POWER); + calibration.correctedPowerIndex = calibration.powerIndex; + + digi_dbg("Computed power index = %d.\n", calibration.powerIndex); + digi->rf->set_pwr_index(digi->hw, calibration.powerIndex); + + /* + * Let's compute and save the expected ADC value while we have all the necessary + * information handy. + */ +#ifdef WANT_CAL_DEBUG + digi_dbg("Using points "); + printPoint(p1); + printPoint(p2); + printk("\n"); + digi_dbg("Max ADC = %d.\n", calibration.curve->max_adc_value); +#endif + calibration.adcSlopeTimes1000 = computeSlopeTimes1000(p1, p2, ADC_OVER_OUT_POWER); + calibration.expectedAdc = + computeY(calibration.p1, calibration.adcSlopeTimes1000, mdBm, + ADC_OVER_OUT_POWER); + if (calibration.expectedAdc > calibration.curve->max_adc_value) { + calibration.expectedAdc = calibration.curve->max_adc_value; + } + digi_dbg("adcSlopeTimes1000 = %d, expectedAdc = %d\n", + calibration.adcSlopeTimes1000, calibration.expectedAdc); + calibration.outPowerSlopeTimes1000 = + computeSlopeTimes1000(p1, p2, OUT_POWER_OVER_ADC); + calibration.powerIndexSlopeTimes1000 = + computeSlopeTimes1000(p1, p2, POWER_INDEX_OVER_ADC); +} + + + +/* + * This routine performs closed loop recalibration. It is called periodically + * to adjust the transmit power level. It will be called after the ADC levels + * for several transmit frames have been sampled. It does the following: + * + * 1. Average the samples together. + * 2. If the measured ADC level is too low, then increase the power + * level one step. + * 3. If the measured ADC level is too high, then decrease the power + * level one step. + */ +static void recalibrate(struct piper_priv *digi) +{ + unsigned int idx; + int actualAdc = 0; + int errorInAdc, errorInMdbm; + wcd_point_t p = { + .out_power = 0, + .adc_val = 0 + }; + int needCorrection = 0; + +#ifdef WANT_CAL_DEBUG_1 + digi_dbg("Samples: "); +#endif + for (idx = 0; idx < calibration.sampleCount; idx++) { +#ifdef WANT_CAL_DEBUG_1 + printk("%d, ", calibration.sample[idx].sample); +#endif + actualAdc += calibration.sample[idx].sample; + } +#ifdef WANT_CAL_DEBUG_1 + printk("\n"); +#endif + actualAdc = actualAdc / calibration.sampleCount; + +#ifdef WANT_TO_FORCE_26 + + digi->rf->set_pwr_index(digi->hw, 26); +#else + + errorInAdc = actualAdc - calibration.expectedAdc; + errorInMdbm = + computeY(&p, calibration.outPowerSlopeTimes1000, errorInAdc, + OUT_POWER_OVER_ADC); + needCorrection = (errorInMdbm > MAX_TOLERATED_ERROR_HIGH); + if (errorInMdbm < 0) { + needCorrection = ((MAX_TOLERATED_ERROR_LOW + errorInMdbm) < 0); + } + if (needCorrection) { + int correction = computeY(calibration.p1, + calibration.powerIndexSlopeTimes1000, + actualAdc, POWER_INDEX_OVER_ADC); +#if defined(WANT_CAL_DEBUG) + int oldIndex = calibration.correctedPowerIndex; +#endif + correction = (3 * (calibration.powerIndex - correction)) / 4; + if (correction == 0) { + if (errorInAdc > 0) { + /* + * If power level is too high but the minimum correction would + * overshoot the target, do it anyway because we would rather + * be below the target rather than over the target. + */ + correction = -1; + } else { + /* + * But if the power level is too low, but the minimum correction + * would overshoot, then leave it be. Better too low than too high. + */ + } + } + calibration.correctedPowerIndex += correction; + if (calibration.correctedPowerIndex < MINIMUM_POWER_INDEX) { + calibration.correctedPowerIndex = MINIMUM_POWER_INDEX; + } + digi->rf->set_pwr_index(digi->hw, calibration.correctedPowerIndex); +#ifdef WANT_CAL_DEBUG + digi_dbg + ("actualAdc = %d, expectedAdc = %d, error mdbm = %d\n", + actualAdc, calibration.expectedAdc, errorInMdbm); + digi_dbg + ("Power index corrected by %d: was %d, set to %d.\n", + correction, oldIndex, calibration.correctedPowerIndex); +#endif +#if WANT_CALIBRATION_STATUS + if (correction != 0) + printk(KERN_ERR + "error mdBm = %d, correcting airoha index by %d\n", + errorInMdbm, correction); +#endif + } +#endif +} + + +/* + * This routine is called by the 80211mac library to set a new power level. We + * update the value in context memory and then kick the autocalibration thread. + */ +static int setNewPowerLevel(struct ieee80211_hw *hw, uint8_t newPowerLevel) +{ + struct piper_priv *digi = hw->priv; + + (void) newPowerLevel; /* has already been written into piper->tx_power */ + /* + * Kick the calibration thread. + */ + stopSampler(digi); + kickCalibrationThread(digi, RESTART_AUTOCALIBRATION_EVENT); + + return 0; +} + + + + +/* + * Compute the maximum ADC value for a curve given the maximum + * allowed power in dBm. + */ +static u8 getMaxAdcValue(wcd_curve_t * curve, int dBm) +{ + wcd_point_t *p1, *p2; + int slopeTimes1000; + u8 result = 0; + int mdBm = 1000 * dBm; + + findClosestPoints(curve, mdBm, &p1, &p2, OUT_POWER); + slopeTimes1000 = computeSlopeTimes1000(p1, p2, ADC_OVER_OUT_POWER); + + if (abs(p1->out_power - mdBm) < abs(p2->out_power - mdBm)) { + result = computeY(p1, slopeTimes1000, mdBm, ADC_OVER_OUT_POWER); + } else { + result = computeY(p2, slopeTimes1000, mdBm, ADC_OVER_OUT_POWER); + } + + return result; +} + + +/* + * The version 1.0 calibration data provided maximum Airoha index + * values, but later versions provided maximum ADC values. This + * routine replaces the max Airoha indexes with max ADC values. + */ +static void determineMaxAdcValues(wcd_data_t * cdata) +{ + int i; + + for (i = 0; i < WCD_CHANNELS_BG; i++) { + /* + * All BG channels are limited to 16 dBm. + */ + cdata->cal_curves_bg[i][0].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_bg[i][0], 16); + cdata->cal_curves_bg[i][1].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_bg[i][1], 16); + } + for (i = 0; i < 4; i++) { + /* + * First 4 802.11a channels are limited to 3 dBm. + */ + cdata->cal_curves_a[i].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_a[i], 3); + } + for (i = 4; i < WCD_CHANNELS_A; i++) { + /* + * All other 802.11a channels are limited to 8 dBm. + */ + cdata->cal_curves_a[i].max_adc_value = + getMaxAdcValue(&cdata->cal_curves_a[i], 8); + } +} + + + +/* + * This routine writes a default set of calibration values into the + * calibration data structure. Maximum power output is severely limited. + */ +static void setDefaultCalibrationValues(struct piper_priv *piperp) +{ +#define MIN_MDBM (-2905) +#define MAX_BG_MDBM (6000) +#define DEFAULT_NUM_POINTS (DEFAULT_NUM_POINTS) +#define BAND_A_1_START (0) +#define BAND_A_2_START (4) +#define BAND_A_3_START (7) +#define BAND_A_4_START (15) +#define BAND_A_5_START (19) +#define BAND_A_6_START (30) + + int i; + + calibration.nvram->header.numcalpoints = 2; + + for (i = 0; i < WCD_CHANNELS_BG; i++) { + calibration.nvram->cal_curves_bg[i][0].max_adc_value = 52; + calibration.nvram->cal_curves_bg[i][0].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_bg[i][0].points[0].adc_val = 19; + calibration.nvram->cal_curves_bg[i][0].points[0].power_index = 0; + calibration.nvram->cal_curves_bg[i][0].points[1].out_power = 6000; + calibration.nvram->cal_curves_bg[i][0].points[1].adc_val = 52; + calibration.nvram->cal_curves_bg[i][0].points[1].power_index = 16; + + calibration.nvram->cal_curves_bg[i][0].max_adc_value = 48; + calibration.nvram->cal_curves_bg[i][1].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_bg[i][1].points[0].adc_val = 12; + calibration.nvram->cal_curves_bg[i][1].points[0].power_index = 0; + calibration.nvram->cal_curves_bg[i][1].points[1].out_power = 6000; + calibration.nvram->cal_curves_bg[i][1].points[1].adc_val = 48; + calibration.nvram->cal_curves_bg[i][1].points[1].power_index = 24; + } + + for (i = BAND_A_1_START; i < BAND_A_2_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 22; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 11; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 0; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 22; + calibration.nvram->cal_curves_a[i].points[0].power_index = 19; + } + for (i = BAND_A_2_START; i < BAND_A_3_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 29; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 13; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 29; + calibration.nvram->cal_curves_a[i].points[0].power_index = 20; + } + for (i = BAND_A_3_START; i < BAND_A_4_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 42; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 15; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 4000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 42; + calibration.nvram->cal_curves_a[i].points[0].power_index = 22; + } + for (i = BAND_A_4_START; i < BAND_A_5_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 54; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 21; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 54; + calibration.nvram->cal_curves_a[i].points[0].power_index = 18; + } + for (i = BAND_A_5_START; i < BAND_A_6_START; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 39; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 13; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 39; + calibration.nvram->cal_curves_a[i].points[0].power_index = 26; + } + for (i = BAND_A_6_START; i < WCD_CHANNELS_A; i++) { + calibration.nvram->cal_curves_a[i].max_adc_value = 31; + calibration.nvram->cal_curves_a[i].points[0].out_power = MIN_MDBM; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 11; + calibration.nvram->cal_curves_a[i].points[0].power_index = 0; + calibration.nvram->cal_curves_a[i].points[0].out_power = 2000; + calibration.nvram->cal_curves_a[i].points[0].adc_val = 31; + calibration.nvram->cal_curves_a[i].points[0].power_index = 30; + } +} + + +/* + * The calibration data is passed to the kernel from U-Boot. The kernel + * start up routines will have copied the data into digi->pdata->wcd. + * We do a few sanity checks on the data and set up our own pointers to + * it. + */ +static int getCalibrationData(struct piper_priv *digi) +{ + int result = 0; + + calibration.nvram = &digi->pdata->wcd; + + if (strncmp(calibration.nvram->header.magic_string, + NVRAM_WCAL_SIGNATURE, strlen(NVRAM_WCAL_SIGNATURE)) == 0) { + unsigned int crc = ~crc32_le(~0, + (unsigned char const *) calibration.nvram-> + cal_curves_bg, + calibration.nvram->header.wcd_len); + + if (crc == calibration.nvram->header.wcd_crc) { + digi_dbg("CRC and signature for calibration data is okay\n"); + result = 0; + if ((calibration.nvram->header.ver_major == '1') + && (calibration.nvram->header.ver_minor == '0')) { + digi_dbg("Converting version 1.0 calibration data\n"); + determineMaxAdcValues(calibration.nvram); + /* + * Now that we have updated the format of the data, we need + * to recompute the check sum and set the new version. + */ + calibration.nvram->header.ver_minor = '1'; + calibration.nvram->header.wcd_crc = + ~crc32_le(~0, (unsigned char const *) + calibration.nvram-> + cal_curves_bg, + calibration.nvram->header.wcd_len); + } + digi->rf->set_hw_info(digi->hw, digi->channel, + calibration.nvram->header.hw_platform); + } else { + digi_dbg("Calibration data has invalid CRC.\n"); + setDefaultCalibrationValues(digi); + } + } else { + digi_dbg("Calibration data has invalid signature.\n"); + setDefaultCalibrationValues(digi); + } + + return result; +} + + + +/* + * This routine: + * + * 1. Loads the ADC driver. + * 2. Loads the calibration data. + * 3. Implements the automatic calibration state machine. + * + */ +static int digiWifiCalibrationThreadEntry(void *data) +{ + struct piper_priv *digi = data; + int state; + + __set_current_state(TASK_RUNNING); + + while (1) { + /* + * We, the wireless driver, may be loaded before the I2C core has + * loaded. Therefore we may not be able to load our ADC driver, + * which is an I2C client driver, when we load. This loop tries + * and retries to load the ADC driver until it succeeds. + */ + + /* TODO, FIXME make following code dependent on platform information + * allowign to initialize different adc */ + if (adc121C027_init(&calibration, digi->pdata->i2c_adapter_num) == 0) { + digi_dbg("ADC driver loaded...\n"); + break; + } + digi_dbg("Will try to load ADC driver again...\n"); + ssleep(10); + } + + if (getCalibrationData(digi) == 0) { + digi->rf->set_pwr = setNewPowerLevel; + + state = RESTART_STATE; + + digi_dbg("Starting autocalibration state machine.\n"); + do { + int event; + int timeout = 0; + int expectedEvent = TIMED_OUT_EVENT; + int nextState = RESTART_STATE; + + switch (state) { + case RESTART_STATE: + setInitialPowerLevel(digi, + CONVERT_TO_MDBM(digi->tx_power)); + calibration.initialized = true; + calibration.sampleCount = 0; + nextState = COLLECT_SAMPLES_STATE; + timeout = DEBOUNCE_DELAY; + expectedEvent = TIMED_OUT_EVENT; + break; + case COLLECT_SAMPLES_STATE: + digi->calibrationTxRate = + determineCalibrationTxRate(digi); + startSampleCollection(digi); + nextState = GOT_SAMPLE_STATE; + timeout = SAMPLE_TIMEOUT; + expectedEvent = TRANSMIT_DONE_EVENT; + break; + case GOT_SAMPLE_STATE: + processSample(digi); + if (calibration.sampleCount < MAX_SAMPLES) { + nextState = COLLECT_SAMPLES_STATE; + timeout = 0; + break; + } + /* fall through is intended operation */ + case RECALIBRATE_STATE: + recalibrate(digi); + calibration.sampleCount = 0; + digi->calibrationTxRate = USE_MAC80211_DATA_RATE; + nextState = COLLECT_SAMPLES_STATE; + timeout = RECALIBRATION_PERIOD; + expectedEvent = TIMED_OUT_EVENT; + break; + default: + digi_dbg("Unknown state %d\n", state); + nextState = COLLECT_SAMPLES_STATE; + timeout = RECALIBRATION_PERIOD; + expectedEvent = TIMED_OUT_EVENT; + break; + } + + state = nextState; + event = waitForEvent(timeout, expectedEvent); + + switch (event) { + case SHUTDOWN_AUTOCALIBRATION_EVENT: + digi_dbg("Received SHUTDOWN_AUTOCALIBRATION_EVENT\n"); + break; + case RESTART_AUTOCALIBRATION_EVENT: + digi_dbg("Received RESTART_AUTOCALIBRATION_EVENT\n"); + state = RESTART_STATE; + break; + case TRANSMIT_DONE_EVENT: + break; + case TIMED_OUT_EVENT: + if (state == GOT_SAMPLE_STATE) { + state = COLLECT_SAMPLES_STATE; + } + } + } while (!kthread_should_stop()); + } else { + printk(KERN_ERR + "\nWarning: Wireless interface calibration data is corrupted.\n"); + printk(KERN_ERR + " The wireless interface will operate at a low power level.\n"); + while (!kthread_should_stop()) { + ssleep(1); + } + } + calibration.cops->adc_shutdown(&calibration); + + return 0; +} + + + +/* + * This routine is called at initialization to set up the Airoha calibration routines. + */ +void digiWifiInitCalibration(struct piper_priv *digi) +{ + + calibration.events = 0; + calibration.sampleCount = 0; + calibration.initialized = false; + + spin_lock_init(&calibration.lock); + + calibration.threadCB = + kthread_run(digiWifiCalibrationThreadEntry, digi, + PIPER_DRIVER_NAME " - calibration"); +} + +int digiWifiCalibrationPowerIndex(struct piper_priv *piperp) +{ + if (calibration.initialized) + return calibration.correctedPowerIndex; + else + return -1; +} + + +void digiWifiDeInitCalibration(struct piper_priv *digi) +{ + calibration.events = SHUTDOWN_AUTOCALIBRATION_EVENT; + wake_up_interruptible(&waitQueue); + kthread_stop(calibration.threadCB); + calibration.initialized = false; +} + +EXPORT_SYMBOL_GPL(digiWifiDeInitCalibration); diff --git a/drivers/net/wireless/digiPiper/airohaCalibration.h b/drivers/net/wireless/digiPiper/airohaCalibration.h new file mode 100644 index 000000000000..36543c87fc51 --- /dev/null +++ b/drivers/net/wireless/digiPiper/airohaCalibration.h @@ -0,0 +1,108 @@ +/* + * This file contains the code which performs automatic recalibration of the + * Airoha transceiver. + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef AIROHA_CALIBRATION_H +#define AIROHA_CALIBRATION_H + +#include "pipermain.h" + + +#define MAX_SAMPLES (3) + +/* + * Field values used for computing ABS values. + */ +enum { + OUT_POWER, + ADC_VAL, + POWER_INDEX +}; + +#ifdef WANT_CAL_DEBUG +static const char *fieldDescription[] = { + "OUT_POWER", + "ADC_VAL", + "POWER_INDEX" +}; +#endif + +/* + * States for the auto calibration thread. + */ +enum { + RESTART_STATE, + COLLECT_SAMPLES_STATE, + GOT_SAMPLE_STATE, + RECALIBRATE_STATE +}; + +#ifdef WANT_CAL_DEBUG +static const char *stateText[] = { + "RESTART_STATE", + "COLLECT_SAMPLES_STATE", + "GOT_SAMPLE_STATE", + "RECALIBRATE_STATE" +}; +#endif + + +/* + * Slope types accepted by computeSlope(). + */ +enum { + POWER_INDEX_OVER_OUT_POWER, + ADC_OVER_OUT_POWER, + OUT_POWER_OVER_ADC, + POWER_INDEX_OVER_ADC +}; + + +typedef struct { + unsigned rate; /* rate packet transmitted at */ + unsigned int sample; /* measured sample */ +} sampleInfo_t; + +struct airohaCalibrationData { + struct task_struct *threadCB; + spinlock_t lock; + volatile unsigned int events; + unsigned int sampleCount; + sampleInfo_t sample[MAX_SAMPLES]; + wcd_data_t *nvram; + wcd_curve_t *curve; + int slopeTimes1000; + int adcSlopeTimes1000; + int outPowerSlopeTimes1000; + int powerIndexSlopeTimes1000; + int expectedAdc; + int powerIndex, correctedPowerIndex; + wcd_point_t *p1; + + + void *priv; + struct calibration_ops *cops; + bool initialized; +}; + +struct calibration_ops { + u16(*adc_read_peak) (struct airohaCalibrationData *); + void (*adc_clear_peak) (struct airohaCalibrationData *); + u16(*adc_read_last_val) (struct airohaCalibrationData *); + void (*adc_shutdown) (struct airohaCalibrationData *); +}; + + +void digiWifiInitCalibration(struct piper_priv *digi); +void digiWifiDeInitCalibration(struct piper_priv *digi); +int digiWifiCalibrationPowerIndex(struct piper_priv *piperp); + +#endif /* AIROHA_CALIBRATION_H */ diff --git a/drivers/net/wireless/digiPiper/digiDebug.c b/drivers/net/wireless/digiPiper/digiDebug.c new file mode 100644 index 000000000000..627dd8c698ec --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiDebug.c @@ -0,0 +1,205 @@ +/* + * digiDebug.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains some debugging routines. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" + +#define DUMP_WORDS_MAX (700) +static unsigned int dumpWordsWord[DUMP_WORDS_MAX]; +static unsigned int dumpWordsCount = 0; + +void digiWifiDumpWordsAdd(unsigned int word) +{ + if (dumpWordsCount < DUMP_WORDS_MAX) + { + dumpWordsWord[dumpWordsCount++] = word; + } +} +void digiWifiDumpWordsDump(void) +{ + unsigned int *p = dumpWordsWord; + unsigned int wordsToGo = dumpWordsCount; + + dumpWordsCount = 0; + + while (wordsToGo >= 4) + { + digi_dbg("%8.8X %8.8X - %8.8X %8.8X\n", p[0], p[1], p[2], p[3]); + p += 4; + wordsToGo -= 4; + } + if (wordsToGo == 3) + { + digi_dbg("%8.8X %8.8X - %8.8X\n", p[0], p[1], p[2]); + } + else if (wordsToGo == 2) + { + digi_dbg("%8.8X %8.8X \n", p[0], p[1]); + } + else if (wordsToGo == 1) + { + digi_dbg("%8.8X \n", p[0]); + } + digi_dbg("--------------\n"); +} + +void digiWifiDumpWordsReset(void) +{ + dumpWordsCount = 0; +} + + +void digiWifiDumpBuffer(u8 *buffer, unsigned int length) +{ + unsigned int i, word; + + digiWifiDumpWordsReset(); + + for (i = 0; i < length / sizeof(unsigned int); i++) + { + memcpy(&word, &buffer[i*sizeof(unsigned int)], sizeof(word)); + digiWifiDumpWordsAdd(cpu_to_be32(word)); + } + + digiWifiDumpWordsDump(); +} + + +void digiWifiDumpSkb(struct sk_buff *skb) +{ + unsigned int bytesLeft = skb->len; + unsigned char *p = skb->data; + + digi_dbg("skb has %d bytes\n", skb->len); + while (bytesLeft >= 16) + { + digi_dbg("%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X - %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + p += 16; + bytesLeft -= 16; + } + if (bytesLeft >= 8) + { + digi_dbg("%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + p += 8; + bytesLeft -= 8; + } + if (bytesLeft >= 4) + { + digi_dbg("%2.2X %2.2X %2.2X %2.2X \n", + p[0], p[1], p[2], p[3]); + p += 4; + bytesLeft -= 4; + } + if (bytesLeft >= 2) + { + digi_dbg("%2.2X %2.2X \n", + p[0], p[1]); + p += 2; + bytesLeft -= 2; + } + if (bytesLeft >= 1) + { + digi_dbg("%2.2X \n", + p[0]); + p += 1; + bytesLeft -= 1; + } +} + +EXPORT_SYMBOL_GPL(digiWifiDumpSkb); + + +void digiWifiDumpRegisters(struct piper_priv *digi, unsigned int regs) +{ +#ifdef WANT_DEBUG + unsigned int i; + + if (regs & CTRL_STATUS_REGS) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", "Gen Ctrl", digi->ac->rd_reg(digi, BB_GENERAL_CTL), + "Gen Status", digi->ac->rd_reg(digi, BB_GENERAL_STAT)); + } + else if (regs & IRQ_REGS) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", "IRQ Mask", digi->ac->rd_reg(digi, BB_IRQ_MASK), + "IRQ Status", digi->ac->rd_reg(digi, BB_IRQ_STAT)); + } + else if (regs & MAIN_REGS) + { + const char *regNames[] = {"Version", "Gen Ctrl", "Gen Status", "RSSI/AES", + "Int Mask", "Int Status", "SPI Data", "SPI Ctrl", + "Data FIFO", "not used", "conf-1", "conf-2", "AES FIFO", + "not used", "AES Ctrl", "IO Ctrl"}; + printk(KERN_ERR "Main Registers:\n"); + for (i = BB_VERSION; i <= BB_OUTPUT_CONTROL; i = i+8) + { + if ((i != BB_DATA_FIFO) && (i != BB_AES_FIFO)) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", regNames[i>>2], digi->ac->rd_reg(digi, i), regNames[(i>>2) + 1], digi->ac->rd_reg(digi, i+4)); + } + } + } + if (regs & MAC_REGS) + { + const char *regNames[] = {"STA ID0", "STA ID1", "BSS ID0", "BSS ID1", + "OFDM/PSK", "Backoff", "DTIM/List", "B Int", + "Rev/M Stat", "C C/M CTL", "Measure", "Beac Fltr"}; + + printk(KERN_ERR "Secondary Registers:\n"); + for (i = MAC_STA_ID0; i <= MAC_BEACON_FILT; i = i+8) + { + printk(KERN_ERR " %10.10s = 0x%8.8X %10.10s = 0x%8.8X\n", regNames[((i - MAC_STA_ID0) >>2)], digi->ac->rd_reg(digi, i), regNames[((i - MAC_STA_ID0)>>2) + 1], digi->ac->rd_reg(digi, i+4)); + } + } + if (regs & FRAME_BUFFER_REGS) + { + unsigned int word[4]; + printk(KERN_ERR "Real time frame buffer\n"); + + word[0] = be32_to_cpu(digi->ac->rd_reg(digi, 0xc0)); + word[1] = be32_to_cpu(digi->ac->rd_reg(digi, 0xc4)); + word[2] = be32_to_cpu(digi->ac->rd_reg(digi, 0xc8)); + word[3] = be32_to_cpu(digi->ac->rd_reg(digi, 0xcc)); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + word[0] = be32_to_cpu(digi->ac->rd_reg(digi, 0xd0)); + word[1] = be32_to_cpu(digi->ac->rd_reg(digi, 0xd4)); + word[2] = be32_to_cpu(digi->ac->rd_reg(digi, 0xd8)); + word[3] = be32_to_cpu(digi->ac->rd_reg(digi, 0xdc)); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + } + if (regs & FIFO_REGS) + { + unsigned int word[4]; + printk(KERN_ERR "FIFO contents\n"); + + word[0] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[1] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[2] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[3] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + word[0] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[1] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[2] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + word[3] = digi->ac->rd_reg(digi, BB_DATA_FIFO); + printk(KERN_ERR " %8.8X %8.8X - %8.8X %8.8X\n", word[0], word[1], word[2], word[3]); + } +#endif +} +EXPORT_SYMBOL_GPL(digiWifiDumpRegisters); diff --git a/drivers/net/wireless/digiPiper/digiIsr.c b/drivers/net/wireless/digiPiper/digiIsr.c new file mode 100644 index 000000000000..e2128e254055 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiIsr.c @@ -0,0 +1,159 @@ +/* + * digiIsr.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains the routines that are related to processing interrupts + * from the MAC. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" + +#define IRQ_DEBUG (0) +#if IRQ_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * This routine handles interrupts from the MAC. + */ +irqreturn_t piper_irq_handler(int irq, void *dev_id) +{ + struct piper_priv *piperp = dev_id; + u32 status; + + /* Acknowledge pending interrupts */ + status = piperp->ac->rd_reg(piperp, BB_IRQ_STAT); + status &= piperp->ac->rd_reg(piperp, BB_IRQ_MASK); + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, status, op_write); + + if (status & BB_IRQ_MASK_RX_FIFO) { + /* + * This interrupt indicates we have a frame in the FIFO. + * Set up to receive the packet. Disable further interrupts + * until the receive is complete. + */ + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_RX_FIFO); + /* + * Call the receive routine directly inside the irq handler + * or in the tasklet, depending on configuration. + */ +#if WANT_TO_RECEIVE_FRAMES_IN_ISR + piper_rx_tasklet((unsigned long) piperp); +#else + tasklet_hi_schedule(&piperp->rx_tasklet); +#endif + } + + if (status & BB_IRQ_MASK_TX_FIFO_EMPTY) { + /* + * Transmit complete interrupt. This IRQ is only unmasked if we are + * not expecting the packet to be ACKed. This will be the case for + * broadcasts. In this case, tell mac80211 the transmit occurred and + * restart the tx queue. + */ + if (piper_tx_getqueue(piperp) != NULL) { + piperp->tx_signal_strength = 0; + piperp->tx_result = TX_COMPLETE; + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + dprintk(DWARNING, "BB_IRQ_MASK_TX_FIFO_EMPTY and null packet?\n"); + } + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | BB_IRQ_MASK_TX_ABORT); + } + + if (status & BB_IRQ_MASK_TIMEOUT) { + /* AP did not ACK our TX packet */ + if (piper_tx_getqueue(piperp) != NULL) { + /* Update retry counter */ + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + dprintk(DWARNING, "BB_IRQ_MASK_TIMEOUT and null packet?\n"); + } + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | BB_IRQ_MASK_TX_ABORT); + } + + if (unlikely(status & BB_IRQ_MASK_TX_ABORT)) { + dprintk(DWARNING, "TX abort\n"); + + /* Could not transmit a packet because the media was busy */ + if (piper_tx_getqueue(piperp) != NULL) { + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + dprintk(DWARNING, "BB_IRQ_MASK_TX_ABORT and null packet?\n"); + } + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | BB_IRQ_MASK_TX_ABORT); + } + + if (status & BB_IRQ_MASK_TBTT) { + /* + * This interrupt occurs at the start of a beacon period. The only thing + * we need to do is to write a new beacon backoff value. + */ + u32 reg = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT) & ~MAC_BEACON_BACKOFF_MASK; + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, + reg | piperp->get_next_beacon_backoff(), op_write); + /* + * TODO: + * Improve the way we keep track of whether or not we sent the last + * beacon. What we are doing now is to assume that we did until and + * unless we receive a beacon. What we should do is look for either + * a beacon or a TX end interrupt. However, since mac80211 doesn't + * tell us what the ATIM window is, we have to assume it is zero, + * which means we could be transmitting a frame at the same + * time we are sending the beacon, so there isn't really any easy + * way for us to do this. In fact, even if there was an ATIM + * window, we could have started a transmit just before we get this + * interrupt, so I'm not sure how we are really suppose to keep + * track of this. + */ + /* assume we sent last beacon unless we receive one */ + piperp->beacon.weSentLastOne = true; + } + + if (status & BB_IRQ_MASK_ATIM) { + /* + * This interrupt should not occur since we are not using it. When in + * IBSS mode, the beacon period starts at the TBTT interrupt and ends + * at this interrupt. We are not suppose to send packets between the + * two interrupts. However, mac80211 does not seem to provide a way + * for us to find out how long the ATIM period is, so we have to assume + * that there isn't one. + * + * If we were supporting this interrupt we would have to synchronize + * with the transmit routine so that transmit is paused during this + * time. + */ + dprintk(DWARNING, "BB_IRQ_MASK_ATIM irq (0x%08x)\n", status); + piperp->clear_irq_mask_bit(piperp, BB_IRQ_MASK_ATIM); + } + + if (unlikely(status & BB_IRQ_MASK_RX_OVERRUN)) + piperp->pstats.rx_overruns++; + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(piper_irq_handler); + + + diff --git a/drivers/net/wireless/digiPiper/digiMac80211.c b/drivers/net/wireless/digiPiper/digiMac80211.c new file mode 100644 index 000000000000..b9ca3ef10230 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiMac80211.c @@ -0,0 +1,1000 @@ +/* + * digiMac80211.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains the routines that interface with the mac80211 + * library. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/etherdevice.h> +#include <net/mac80211.h> +#include <crypto/aes.h> +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define MAC_DEBUG (1) + +#if MAC_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * This constant determines how many times per second the led_timer_fn + * function will be called. (HZ >> 3) means 8 times a second. + */ +#define LED_TIMER_RATE (HZ >> 3) +#define LED_MAX_COUNT (15) + +/* + * This function is called from a timer to blink an LED in order to + * indicate what are current state is. + */ +static void led_timer_fn(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *) context; + static unsigned int count = 0; + + if(!piperp->pdata->set_led) + return; + + switch (piperp->led_state) { + case led_shutdown: + /* Turn LED off if we are shut down */ + piperp->pdata->set_led(piperp, STATUS_LED, 0); + break; + case led_adhoc: + /* Blink LED slowly in ad-hoc mode */ + piperp->pdata->set_led(piperp, STATUS_LED, (count & 8) ? 0 : 1); + break; + case led_not_associated: + /* Blink LED rapidly when not associated with an AP */ + piperp->pdata->set_led(piperp, STATUS_LED, (count & 1) ? 0 : 1); + break; + case led_associated: + /* LED steadily on when associated */ + piperp->pdata->set_led(piperp, STATUS_LED, 1); + break; + default: + /* Oops. How did we get here? */ + break; + } + count++; + if (count > LED_MAX_COUNT) { + count = 0; + } + + piperp->led_timer.expires += LED_TIMER_RATE; + add_timer(&piperp->led_timer); +} + +/* + * This function sets the current LED state. + */ +static int piper_set_status_led(struct ieee80211_hw *hw, enum led_states state) +{ + struct piper_priv *piperp = (struct piper_priv *)hw->priv; + + piperp->led_state = state; + + if(!piperp->pdata->set_led) + return -ENOSYS; + + if (state == led_shutdown) + piperp->pdata->set_led(piperp, STATUS_LED, 0); + + return 0; +} + +/* + * This routine is called to enable IBSS support whenever we receive + * configuration commands from mac80211 related to IBSS support. The + * routine examines the configuration settings to determine if IBSS + * support should be enabled and, if so, turns on automatic beacon + * generation. + * + * TODO: This code may need to be moved into piper.c since other + * H/W does not implement automatic generate of beacons. + */ +static void piper_enable_ibss(struct piper_priv *piperp, enum nl80211_iftype iftype) +{ + dprintk(DVVERBOSE, "\n"); + + if (((iftype == NL80211_IFTYPE_ADHOC) || (iftype == NL80211_IFTYPE_MESH_POINT)) + && (piperp->beacon.loaded) && (piperp->beacon.enabled) + && ((piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & MAC_BEACON_INTERVAL_MASK) != 0)) { + /* + * If we come here, then we are running in IBSS mode, beacons are enabled, + * and we have the information we need, so start sending beacons. + */ + /* TODO: Handle non-zero ATIM period. mac80211 currently has no way to + * tell us what the ATIM period is, but someday they might fix that.*/ + + u32 reg = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT) & ~MAC_BEACON_BACKOFF_MASK; + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, + reg | piperp->get_next_beacon_backoff(), op_write); + /* enable beacon interrupts*/ + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_TBTT); + piperp->ac->wr_reg(piperp, + MAC_CTL, MAC_CTL_BEACON_TX | MAC_CTL_IBSS, op_or); + dprintk(DVERBOSE, "IBSS turned ON\n"); + piper_set_status_led(piperp->hw, led_adhoc); + } else { + /* + * If we come here, then either we are not suppose to transmit beacons, + * or we do not yet have all the information we need to transmit + * beacons. Make sure the automatic beacon function is disabled. + */ + /* shut down beacons */ + piperp->ac->wr_reg(piperp, MAC_CTL, + ~(MAC_CTL_BEACON_TX | MAC_CTL_IBSS), op_and); + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_TBTT); + dprintk(DVERBOSE, "IBSS turned OFF\n"); + } +} + +/* + * Set the transmit power level. The real work is done in the + * transceiver code. + */ +static int piper_set_tx_power(struct ieee80211_hw *hw, int power) +{ + struct piper_priv *digi = hw->priv; + int err; + int oldTxPower = digi->tx_power; + + dprintk(DVVERBOSE, "\n"); + + if (power == digi->tx_power) + return 0; + + digi->tx_power = power; + err = digi->rf->set_pwr(hw, power); + if (err) + digi->tx_power = oldTxPower; + + return err; +} + +/* + * Utility routine that sets a sequence number for a data packet. + */ +static void assign_seq_number(struct sk_buff *skb, bool increment) +{ +#define SEQUENCE_NUMBER_MASK (0xfff0) + static u16 seq_number = 0; + _80211HeaderType *header = (_80211HeaderType *)skb->data; + + dprintk(DVVERBOSE, "\n"); + + if (skb->len >= sizeof(*header)) { + u16 seq_field; + + /* + * TODO: memcpy's are here because I am concerned we may get + * an unaligned frame. Is this a real possibility? Or + * am I just wasting CPU time? + */ + memcpy(&seq_field, &header->squ.sq16, sizeof(header->squ.sq16)); + seq_field &= ~SEQUENCE_NUMBER_MASK; + seq_field |= (SEQUENCE_NUMBER_MASK & (seq_number << 4)); + memcpy(&header->squ.sq16, &seq_field, sizeof(header->squ.sq16)); + if (increment) + seq_number++; + } +} + +#define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +/* Get 16 bits at byte pointer */ +#define GET16(bp) ((bp)[0] | ((bp)[1] << 8)) +/* Get 32 bits at byte pointer */ +#define GET32(bp) ((bp)[0] | ((bp)[1] << 8) | ((bp)[2] << 16) | ((bp)[3] << 24)) +/* Store 16 bits at byte pointer */ +#define SET16(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; } +/* Store 32 bits at byte pointer */ +#define SET32(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; \ + (bp)[2] = (data) >> 16; \ + (bp)[3] = (data) >> 24; } + +static inline void dw_inc_48(u48* n) +{ + (*n)++; + *n &= ((u64) 1 << 48) - 1; +} + +/* + * This function prepares a blob of data we will have to send to the AES + * H/W encryption engine. The data consists of the AES initialization + * vector and 2 16 byte headers. + * + * Returns true if successful, or false if something goes wrong + */ +bool piper_prepare_aes_datablob(struct piper_priv *piperp, unsigned int keyIndex, + u8 *aesBlob, u8 *frame, u32 length, bool isTransmit) +{ + _80211HeaderType *header = (_80211HeaderType *)frame; + u8 *body = &frame[sizeof(*header)]; + int dlen = length - (_80211_HEADER_LENGTH + PIPER_EXTIV_SIZE); + + dprintk(DVVERBOSE, "\n"); + + if (keyIndex >= PIPER_MAX_KEYS) { + dprintk(DWARNING, "encryption key index %d is out of range.\n", + keyIndex); + return false; + } + + if (piperp->key[keyIndex].valid == false) + return false; + + /* Set up CCM initial block for MIC IV */ + memset(aesBlob, 0, AES_BLOB_LENGTH); + aesBlob[0] = 0x59; + aesBlob[1] = 0; + memcpy (&aesBlob[2], header->addr2, ETH_ALEN); + aesBlob[8] = body[7]; + aesBlob[9] = body[6]; + aesBlob[10] = body[5]; + aesBlob[11] = body[4]; + aesBlob[12] = body[1]; + aesBlob[13] = body[0]; + aesBlob[14] = dlen >> 8; + aesBlob[15] = dlen; + + /* Set up MIC header blocks */ +#define AES_HEADER_0_OFFSET (16) +#define AES_HEADER_1_OFFSET (32) + aesBlob[AES_HEADER_0_OFFSET+0] = 0; + aesBlob[AES_HEADER_0_OFFSET+1] = 22; + aesBlob[AES_HEADER_0_OFFSET+2] = frame[0] & 0xcf; + aesBlob[AES_HEADER_0_OFFSET+3] = frame[1] & 0xd7; + /* + * This memcpy writes data into the last 12 bytes of the first header + * and the first 6 bytes of the 2nd header. I did it as one memcpy + * call for efficiency. + */ + memcpy(&aesBlob[AES_HEADER_0_OFFSET+4], header->addr1, 3*ETH_ALEN); + aesBlob[AES_HEADER_1_OFFSET+6] = header->squ.sq.frag; + aesBlob[AES_HEADER_1_OFFSET+7] = 0; + memset (&aesBlob[AES_HEADER_1_OFFSET+8], 0, 8); /* clear vector location in data */ + + return true; +} + +/* + * mac80211 calls this routine to transmit a frame. We set up + * up the information the trasmit tasklet will need, and then + * schedule the tasklet. + */ +int piper_hw_tx_private(struct ieee80211_hw *hw, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb) +{ + struct piper_priv *piperp = hw->priv; + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(skb); + unsigned long flags; + + dprintk(DVVERBOSE, "\n"); + + if (piperp->is_radio_on == false) { + dprintk(DERROR, "called with radio off\n"); + return -EBUSY; + } + + /* + * Our H/W can only transmit a single packet at a time. mac80211 + * already maintains a queue of packets, so there is no reason + * for us to set up another one. We stop the mac80211 queue everytime + * we get a transmit request and restart it when the transmit + * operation completes. + */ + ieee80211_stop_queues(hw); + + if (txInfo->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + assign_seq_number(skb, + !!(txInfo->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)); + } + + piperp->use_hw_aes = false; + if (txInfo->control.hw_key != NULL) { + /* + * We've been passed an encryption key, so mac80211 must want us + * to encrypt the packet with our fancy H/W encryptor. Let's get + * set up for that now. + */ + piperp->tx_aes_key = txInfo->control.hw_key->hw_key_idx; + piperp->use_hw_aes = + piper_prepare_aes_datablob(piperp, + txInfo->control.hw_key->hw_key_idx, + (u8 *)piperp->tx_aes_blob, skb->data, + skb->len, true); + } + piper_ps_set_header_flag(piperp, ((_80211HeaderType *) skb->data)); /* set power management bit as appropriate*/ + + /* + * Add space at the start of the frame for the H/W level transmit header. + * We can't generate the header now. It must be generated everytime we + * transmit because the transmit rate changes when we do retries. + */ + skb_push(skb, TX_HEADER_LENGTH); + + piperp->pstats.tx_retry_index = 0; + piperp->pstats.tx_total_tetries = 0; + memset(piperp->pstats.tx_retry_count, 0, sizeof(piperp->pstats.tx_retry_count)); + txInfo->flags &= ~(IEEE80211_TX_STAT_TX_FILTERED | + IEEE80211_TX_STAT_ACK | + IEEE80211_TX_STAT_AMPDU | + IEEE80211_TX_STAT_AMPDU_NO_BACK); + piperp->pstats.tx_queue.len++; + piperp->pstats.tx_queue.count++; + + if (piper_tx_enqueue(piperp, skb, skb_return_cb) == -1) { + skb_pull(skb, TX_HEADER_LENGTH); /* undo the skb_push above */ + return -EBUSY; + } + + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + if (!piperp->tx_tasklet_running) { + piperp->tx_tasklet_running = true; + tasklet_hi_schedule(&piperp->tx_tasklet); + } + + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + + piperp->pstats.tx_start_count++; + + return 0; +} +EXPORT_SYMBOL_GPL(piper_hw_tx_private); + + +int piper_hw_tx(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + return piper_hw_tx_private(hw, skb, ieee80211_tx_status_irqsafe); +} + + +/* + * mac80211 calls this routine to initialize the H/W. + */ +static int piper_hw_start(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + int ret = 0; + + dprintk(DVVERBOSE, "\n"); + piperp->if_type = __NL80211_IFTYPE_AFTER_LAST; + + /* Initialize the MAC H/W */ + if ((ret = piperp->init_hw(piperp, IEEE80211_BAND_2GHZ)) != 0) { + dprintk(DERROR, "unable to initialize piper HW (%d)\n", ret); + return ret; + } + + piperp->is_radio_on = true; + + /* + * Initialize the antenna with the defualt setting defined in the + * probe function. This can be changed, currently, through a sysfs + * entry in the device directory + */ + if ((ret = piperp->set_antenna(piperp, piperp->antenna)) != 0) { + dprintk(DERROR, "piper_set_antenna_div() failed (%d)\n", ret); + return ret; + } + + /* set status led to link off */ + piper_set_status_led(hw, led_shutdown); + + /* Get the tasklets ready to roll */ + piperp->tx_result = TX_NOT_DONE; + tasklet_enable(&piperp->rx_tasklet); + tasklet_enable(&piperp->tx_tasklet); + + /* + * Enable receive interrupts, but leave the transmit interrupts + * and beacon interrupts off for now. + */ + piperp->clear_irq_mask_bit(piperp, 0xffffffff); + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_RX_OVERRUN | BB_IRQ_MASK_RX_FIFO); + enable_irq(piperp->irq); + + memset(piperp->bssid, 0, ETH_ALEN); + + return 0; +} + +/* + * mac80211 calls this routine to shut us down. + */ +static void piper_hw_stop(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + + /* Initialize the MAC H/W */ + if (piperp->deinit_hw) + piperp->deinit_hw(piperp); + + /* set status led to link off */ + if (piper_set_status_led(hw, led_shutdown)) + return; /* hardware's probably gone, give up */ + + /* turn off phy */ + piperp->rf->stop(hw); + + /* Disable interrupts before turning off */ + disable_irq(piperp->irq); + + /* turn off MAX_GAIN, ADC clocks, and so on */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RESET, op_and); + + /* turn off MAC control/mac filt/aes key */ + piperp->ac->wr_reg(piperp, MAC_CTL, 0, op_write); + + /* turn off interrupts */ + tasklet_disable(&piperp->rx_tasklet); + tasklet_disable(&piperp->tx_tasklet); + piperp->clear_irq_mask_bit(piperp, 0xffffffff); +} + +/* + * mac80211 calls this routine to really start the H/W. + * The device type is also set here. + */ +static int piper_hw_add_intf(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "if type: %x\n", conf->type); + + /* __NL80211_IFTYPE_AFTER_LAST means no mode selected */ + if (piperp->if_type != __NL80211_IFTYPE_AFTER_LAST) { + dprintk(DERROR, "unsupported interface type %x, expected %x\n", + conf->type, __NL80211_IFTYPE_AFTER_LAST); + return -EOPNOTSUPP; + } + + switch (conf->type) { + case NL80211_IFTYPE_ADHOC: + piper_set_status_led(piperp->hw, led_adhoc); + piperp->if_type = conf->type; + break; + + case NL80211_IFTYPE_STATION: + piper_set_status_led(hw, led_not_associated); + piperp->if_type = conf->type; + break; + + case NL80211_IFTYPE_MESH_POINT: + piperp->if_type = conf->type; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * mac80211 calls this function to shut down us down. + */ +static void piper_hw_rm_intf(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct piper_priv *digi = hw->priv; + + dprintk(DVVERBOSE, "\n"); + digi->if_type = __NL80211_IFTYPE_AFTER_LAST; +} + +/* + * mac80211 calls this function to pass us configuration information. + */ +static int piper_config(struct ieee80211_hw *hw, u32 changed) +{ + struct piper_priv *piperp = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + u32 tempval; + int err; + + dprintk(DVVERBOSE, "\n"); + + if (changed & IEEE80211_CONF_CHANGE_PS) { + /* + * Enable power save mode if bit set in flags, and if we are in station + * mode. Power save is not supported in ad-hoc/mesh mode. + */ + piper_ps_scan_event(piperp); + piper_ps_set(piperp, ( (conf->flags & IEEE80211_CONF_PS) + && (piperp->if_type == NL80211_IFTYPE_STATION) + && (piperp->areWeAssociated))); + } + /* Should we turn the radio off? */ + if ((piperp->is_radio_on = (conf->radio_enabled != 0)) != 0) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_RX_EN, op_or); + } else { + dprintk(DNORMAL, "Turning radio off\n"); + return piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_RX_EN, op_and); + } + + /* Adjust the beacon interval and listen interval */ + tempval = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & ~MAC_BEACON_INTERVAL_MASK; + tempval |= conf->beacon_int << MAC_BEACON_INTERVAL_SHIFT; + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, tempval, op_write); + + tempval = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD) & ~MAC_LISTEN_INTERVAL_MASK; + tempval |= conf->listen_interval; + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, tempval, op_write); + + /* Adjust the power level */ + if ((err = piper_set_tx_power(hw, conf->power_level)) != 0) { + dprintk(DERROR, "unable to set tx power to %d\n", + conf->power_level); + return err; + } + + /* Set channel */ + if (conf->channel->hw_value != piperp->channel) { + piper_ps_scan_event(piperp); + if ((err = piperp->rf->set_chan(hw, conf->channel->hw_value)) !=0) { + dprintk(DERROR, "unable to set ch to %d\n", + conf->channel->hw_value); + return err; + } + piperp->channel = conf->channel->hw_value; + } + + return 0; +} + +/* + * mac80211 calls this routine to set BSS related configuration settings. + */ +static int piper_hw_config_intf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_if_conf *conf) +{ + struct piper_priv *piperp = hw->priv; + u32 bssid[2]; + + dprintk(DVVERBOSE, "\n"); + + if (conf->changed & IEEE80211_IFCC_BSSID && + !is_zero_ether_addr(conf->bssid) && + !is_multicast_ether_addr(conf->bssid)) { + + piper_ps_scan_event(piperp); + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + dprintk(DVERBOSE, "BSSID: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + conf->bssid[0], conf->bssid[1], conf->bssid[2], + conf->bssid[3], conf->bssid[4], conf->bssid[5]); + + bssid[0] = conf->bssid[3] | conf->bssid[2] << 8 | + conf->bssid[1] << 16 | conf->bssid[0] << 24; + bssid[1] = conf->bssid[5] << 16 | conf->bssid[4] << 24; + + if ((bssid[0] == 0) && (bssid[1] == 0)) { + /* + * If we come here, then the MAC layer is telling us to set a 0 + * SSID. In this case, we really want to set the SSID to the + * broadcast address so that we receive broadcasts. + */ + bssid[0] = 0xffffffff; + bssid[1] = 0xffffffff; + } + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, bssid[0], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, bssid[1], op_write); + memcpy(piperp->bssid, conf->bssid, ETH_ALEN); + break; + default: + break; + } + } + + if ((conf->changed & IEEE80211_IFCC_BEACON) && + (piperp->if_type == NL80211_IFTYPE_ADHOC)) { + struct sk_buff *beacon = ieee80211_beacon_get(hw, vif); + struct ieee80211_rate rate; + + if (!beacon) + return -ENOMEM; + + rate.bitrate = 10; /* beacons always sent at 1 Megabit*/ + skb_push(beacon, TX_HEADER_LENGTH); + phy_set_plcp(beacon->data, beacon->len - TX_HEADER_LENGTH, &rate, 0); + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_BEACON_TX, op_and); + piperp->load_beacon(piperp, beacon->data, beacon->len); + + /* TODO: digi->beacon.enabled should be set by IEEE80211_IFCC_BEACON_ENABLED + when we update to latest mac80211 */ + piperp->beacon.enabled = true; + piper_enable_ibss(piperp, vif->type); + dev_kfree_skb(beacon); /* we are responsible for freeing this buffer*/ + } + + return 0; +} + +/* + * mac80211 wants to change our frame filtering settings. We don't + * actually support this. + */ +static void piper_hw_config_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, unsigned int *total_flags, + int mc_count, struct dev_addr_list *mclist) +{ + dprintk(DVVERBOSE, "\n"); + + /* we don't support filtering so clear all flags; however, we also + * can't pass failed FCS/PLCP frames, so don't clear those. */ + *total_flags &= (FIF_FCSFAIL | FIF_PLCPFAIL); +} + +/* + * There are 1024 TU's (time units) to a second, and HZ jiffies to a + * second. This macro converts TUs to jiffies. + */ +#define TU_TO_JIFFIES(x) (((x*HZ) + 512) / 1024) + + +/* + * mac80211 calls this routine when something about our BSS has changed. + * Usually, this routine only gets called when we associate, or disassociate. + */ +static void piper_hw_bss_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, u32 changed) +{ + struct piper_priv *piperp = hw->priv; + u32 reg; + + dprintk(DVVERBOSE, " changed = 0x%08x\n", changed); + + if (changed & BSS_CHANGED_ASSOC) { + piper_ps_scan_event(piperp); + /* Our association status has changed */ + if (piperp->if_type == NL80211_IFTYPE_STATION) { + piper_set_status_led(hw, conf->assoc ? led_associated : led_not_associated); + } + piperp->areWeAssociated = conf->assoc; + piperp->ps.aid = conf->aid; + + digi_dbg(" AP %s\n", conf->assoc ? + "associated" : "disassociated"); + } + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + piperp->tx_cts = conf->use_cts_prot; + } + if (changed & BSS_CHANGED_ERP_PREAMBLE) { +#define WANT_SHORT_PREAMBLE_SUPPORT (1) +/* TODO: Determine if short preambles really hurt us, or if I'm just seeing things. */ +#if WANT_SHORT_PREAMBLE_SUPPORT + if (conf->use_short_preamble) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_SH_PRE, op_or); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_SH_PRE, op_and); + } + piperp->use_short_preamble = conf->use_short_preamble; +#else + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_SH_PRE, op_or); +#endif + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + /* + * The list of transmit rates has changed. Update the + * rates we will receive at to match those the AP will + * transmit at. This should improve our receive performance + * since we won't listen to junk at the wrong rate. + */ + unsigned int ofdm = 0, psk = 0; + + reg = piperp->ac->rd_reg(piperp, MAC_SSID_LEN) & + ~(MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK); + + piperp->rf->getOfdmBrs(piperp->channel, conf->basic_rates, &ofdm, &psk); + reg |= ofdm << MAC_OFDM_BRS_SHIFT; + reg |= psk << MAC_PSK_BRS_SHIFT; + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, reg, op_write); + + dprintk(DVERBOSE, "BRS mask set to 0x%8.8X\n", reg); + + if (ofdm == 0) { + /* Disable ofdm receiver if no ofdm rates supported */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, + ~BB_GENERAL_STAT_A_EN, op_and); + } else { + /* Enable ofdm receiver if any ofdm rates supported */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, + BB_GENERAL_STAT_A_EN, op_or); + } + } + + /* Save new DTIM period */ + reg = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD) & ~MAC_DTIM_PERIOD_MASK; + reg |= conf->dtim_period << MAC_DTIM_PERIOD_SHIFT; + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, reg, op_write); + reg = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & ~MAC_DTIM_CFP_MASK; + piperp->ps.beacon_int = conf->beacon_int; + reg |= conf->beacon_int << 16; + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, reg, op_write); +} + +/* + * Use the SSL library routines to expand the AES key. + */ +static int piper_expand_aes_key(struct ieee80211_key_conf *key, + u32 *expandedKey) +{ + struct crypto_aes_ctx aes; + int ret; + + dprintk(DVVERBOSE, "\n"); + + if (key->keylen != AES_KEYSIZE_128) + return -EOPNOTSUPP; + + ret = crypto_aes_expand_key(&aes, key->key, key->keylen); + if (ret) + return -EOPNOTSUPP; + + memcpy(expandedKey, aes.key_enc, EXPANDED_KEY_LENGTH); + + return 0; +} + +/* + * mac80211 calls this routine to set a new encryption key, or to + * retire an old one. We support H/W AES encryption/decryption, so + * save the AES related keys. + */ +static int piper_hw_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + const u8 *local_address, const u8 *address, + struct ieee80211_key_conf *key) +{ + struct piper_priv *piperp = hw->priv; + int ret = 0; + + dprintk(DVVERBOSE, "\n"); + + if ((key->alg != ALG_CCMP) || (key->keyidx >= PIPER_MAX_KEYS)) { + /* + * If we come here, then mac80211 was trying to set a key for some + * algorithm other than AES, or trying to set a key index greater + * than 3. We only support AES, and only 4 keys. + */ + ret = -EOPNOTSUPP; + goto set_key_error; + } + key->hw_key_idx = key->keyidx; + + if (cmd == SET_KEY) { + ret = piper_expand_aes_key(key, piperp->key[key->keyidx].expandedKey); + if (ret) + goto set_key_error; + + if (!piperp->key[key->keyidx].valid) + piperp->aes_key_count++; + piperp->key[key->keyidx].txPn = 0; + piperp->key[key->keyidx].rxPn = 0; + piperp->key[key->keyidx].valid = (ret == 0); + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } else { + /* disable key */ + if (piperp->key[key->keyidx].valid) + piperp->aes_key_count--; + piperp->key[key->keyidx].valid = false; + } + + if (piperp->aes_key_count > 0) + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_AES_DISABLE, op_and); + else + piperp->ac->wr_reg(piperp, MAC_CTL, MAC_CTL_AES_DISABLE, op_or); + +set_key_error: + if (ret) + dprintk(DVERBOSE, "unable to set AES key\n"); + + return ret; +} + +/* + * mac80211 calls this routine to determine if we transmitted the + * last beacon. Unfortunately, we can't tell for sure. We give + * mac80211 our best guess. + */ +static int piper_hw_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + return piperp->beacon.weSentLastOne ? 1 : 0; +} + +static int piper_get_tx_stats(struct ieee80211_hw *hw, + struct ieee80211_tx_queue_stats *stats) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + if (stats) + memcpy(stats, &piperp->pstats.tx_queue, sizeof(piperp->pstats.tx_queue)); + + return 0; +} + +static int piper_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + if (stats) + memcpy(stats, &piperp->pstats.ll_stats, sizeof(piperp->pstats.ll_stats)); + + return 0; +} + +const struct ieee80211_ops hw_ops = { + .tx = piper_hw_tx, + .start = piper_hw_start, + .stop = piper_hw_stop, + .add_interface = piper_hw_add_intf, + .remove_interface = piper_hw_rm_intf, + .config = piper_config, + .config_interface = piper_hw_config_intf, + .configure_filter = piper_hw_config_filter, + .bss_info_changed = piper_hw_bss_changed, + .tx_last_beacon = piper_hw_tx_last_beacon, + .set_key = piper_hw_set_key, + .get_tx_stats = piper_get_tx_stats, + .get_stats = piper_get_stats, +}; + +/* + * This routine is called by the probe routine to allocate the + * data structure we need to communicate with mac80211. + */ +int piper_alloc_hw(struct piper_priv **priv, size_t priv_sz) +{ + struct piper_priv *piperp; + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(priv_sz, &hw_ops); + if (!hw) + return -ENOMEM; + + hw->flags |= IEEE80211_HW_RX_INCLUDES_FCS + | IEEE80211_HW_SIGNAL_DBM + | IEEE80211_HW_NOISE_DBM + | IEEE80211_HW_SPECTRUM_MGMT + | IEEE80211_HW_NO_STACK_DYNAMIC_PS +#if !WANT_SHORT_PREAMBLE_SUPPORT + | IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE +#endif + /* | IEEE80211_HW_SPECTRUM_MGMT TODO: Turn this on when we are ready*/; + + hw->queues = 1; + hw->ampdu_queues = 0; + hw->extra_tx_headroom = 4 + sizeof(struct ofdm_hdr); + piperp = hw->priv; + *priv = piperp; + piperp->pstats.tx_queue.len = 0; + piperp->pstats.tx_queue.limit = 1; + piperp->pstats.tx_queue.count = 0; + piperp->areWeAssociated = false; + memset(&piperp->pstats.ll_stats, 0, sizeof(piperp->pstats.ll_stats)); + piperp->hw = hw; + + return 0; +} +EXPORT_SYMBOL_GPL(piper_alloc_hw); + +/* + * This routine is called by the remove function to free the memory + * allocated by piper_alloc_hw. + */ +void piper_free_hw(struct piper_priv *piperp) +{ + ieee80211_free_hw(piperp->hw); +} +EXPORT_SYMBOL_GPL(piper_free_hw); + +/* + * This routine is called by the probe routine to register + * with mac80211. + */ +int piper_register_hw(struct piper_priv *priv, struct device *dev, + struct digi_rf_ops *rf) +{ + struct ieee80211_hw *hw = priv->hw; + u8 macaddr[8]; + u32 temp; + int i, ret; + + dprintk(DVVERBOSE, "\n"); + + priv->rf = rf; + for (i = 0; i < rf->n_bands; i++) { + enum ieee80211_band b = rf->bands[i].band; + hw->wiphy->bands[b] = &rf->bands[i]; + } + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_ADHOC) + | BIT(NL80211_IFTYPE_STATION) +/* TODO: Enable this | BIT(NL80211_IFTYPE_MESH_POINT) */ + ; + hw->channel_change_time = rf->channelChangeTime; + hw->vif_data_size = 0; + hw->sta_data_size = 0; + hw->max_rates = IEEE80211_TX_MAX_RATES; + hw->max_rate_tries = 5; /* completely arbitrary, and apparently ignored by the rate algorithm */ + hw->max_signal = rf->maxSignal; + hw->max_listen_interval = 10; /* I don't think APs will work with values larger than 4 actually */ + + SET_IEEE80211_DEV(hw, dev); + + temp = cpu_to_be32(priv->ac->rd_reg(priv, MAC_STA_ID0)); + memcpy(macaddr, &temp, sizeof(temp)); + temp = cpu_to_be32(priv->ac->rd_reg(priv, MAC_STA_ID1)); + memcpy(&macaddr[4], &temp, sizeof(temp)); + SET_IEEE80211_PERM_ADDR(hw, macaddr); + + if ((ret = ieee80211_register_hw(hw)) != 0) { + dprintk(DERROR, "unable to register ieee80211 hw\n"); + goto error; + } + + printk(KERN_INFO PIPER_DRIVER_NAME ": registered as %s\n", + wiphy_name(hw->wiphy)); + + init_timer(&priv->led_timer); + priv->led_state = led_shutdown; + priv->led_timer.function = led_timer_fn; + priv->led_timer.data = (unsigned long) priv; + priv->led_timer.expires = jiffies + LED_TIMER_RATE; + add_timer(&priv->led_timer); + +error: + return ret; +} +EXPORT_SYMBOL_GPL(piper_register_hw); + + +void piper_unregister_hw(struct piper_priv *piperp) +{ + dprintk(DVVERBOSE, "\n"); + del_timer_sync(&piperp->led_timer); + piper_set_status_led(piperp->hw, led_shutdown); + ieee80211_unregister_hw(piperp->hw); +} +EXPORT_SYMBOL_GPL(piper_unregister_hw); + + +MODULE_DESCRIPTION("Digi Piper WLAN core"); +MODULE_AUTHOR("contact support@digi.com for information about this code"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/digiPiper/digiPs.c b/drivers/net/wireless/digiPiper/digiPs.c new file mode 100644 index 000000000000..c6ae6b0b656a --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiPs.c @@ -0,0 +1,1164 @@ +/* + * digiPs.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains the routines that are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "airoha.h" +#include "digiPs.h" + +/* + * Macro converts milliseconds to HZ. + * + * TODO: Look for standard Linux version of this. + */ +#define MILLS_TO_JIFFIES(x) ((((x)*HZ) + 500) / 1000) + + + + +#define MINIMUM_SLEEP_PERIOD (20) + +/* + * Amount of time before shutdown deadline to start the shutdown process. This + * should allow enough time to get one last frame out, which will be the + * null-data frame notifying the AP that we are shutting down. + */ +#define PS_TRANSMITTER_SHUTDOWN_MS (10) + +/* + * Amount of time we will wake up before the next beacon. We try to wake up before + * the next beacon so that we don't miss it. + */ +#define PS_WAKE_BEFORE_BEACON_MS (20) + +/* + * Minimum amount of time we we keep awake for. + */ +#define PS_MINUMUM_POWER_UP_PERIOD_MS (10) + +/* + * Minimum amount of time we will sleep. If we will end up sleeping + * for less than this, then don't go to sleep. + */ +#define PS_MINIMUM_SLEEP_TIME (10) + +/* + * Length of one tick of the transmit clean up timer in ms. This is + * the minimum amount of time we will sleep for. + */ +#define PS_TRANSMIT_TIMER_TICK_MS ((1000/HZ) ? (1000/HZ) : 1) + +/* + * Length of time we will wait past the expected arrival of a beacon before assuming + * that we missed it. + */ +#define PS_BEACON_TIMEOUT_MS (40) + + +/* + * Minimum beacon interval we will support for duty cycling. There is so much overhead + * in duty cycling that it doesn't make sense to do it for short beacon intervals. + */ +#define PS_MINIMUM_BEACON_INT (100) + +/* + * Amount of time we will pause duty cycling for after receiving an event that suggests + * wpa_supplicant is attempting to reassociate with an AP. + */ +#define PS_SCAN_DELAY (5000) + + +// Powersave register index +#define INDX_GEN_CONTROL 0 // General control +#define INDX_GEN_STATUS 1 // General status +#define INDX_RSSI_AES 2 // RSSI and AES status +#define INDX_INTR_MASK 3 // Interrupt mask +#define INDX_SPI_CTRL 4 // RF SPI control +#define INDX_CONF1 5 // Configuration 1 +#define INDX_CONF2 6 // Configuration 2 +#define INDX_AES_MODE 7 // ARS mode +#define INDX_OUT_CTRL 8 // Output control +#define INDX_MAC_CONTROL 9 // MAC control +#define INDX_STAID_1 10 // first part of stations ID +#define INDX_STAID_2 11 // 2nd part of station ID +#define INDX_BSSID_1 12 // 1st part of BSS ID +#define INDX_BSSID_2 13 // 2nd part of BSS ID +#define INDX_BRS_SSID 14 // BRS mask and SSID length +#define INDX_BACKOFF 15 // backoff +#define INDX_DTIM_LISTEN 16 // DTIM perido and listen interval +#define INDX_BEACON_INT 17 // beacon interval +#define INDX_MAC_CTL 18 // MAC control register +#define INDX_BEACON_MASK 19 // beacon mask and backoff +#define INDX_TOTAL 20 + +static u32 savedRegs[INDX_TOTAL]; // Used to save registers for sleep mode + + +/* + * TODO: Delete this. + */ +struct ps_stats { + unsigned int modeStart; + unsigned int cycleStart; + unsigned int receivedBeacons; + unsigned int missedBeacons; + unsigned int jiffiesOn; + unsigned int jiffiesOff; +} stats; + + + +static void ps_free_frame(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct piper_priv *piperp = hw->priv; + + if (skb) { + dev_kfree_skb(skb); + piperp->ps.frames_pending--; + } +} + + + + + +#define ACK_SIZE (14) /* length of ACK in bytes */ + +// Length (in usecs) of a MAC frame of bytes at rate (in 500kbps units) +// not including SIFS and PLCP preamble/header +#define CCK_DURATION(bytes, rate) ((16*(bytes)+(rate)-1)/(rate)) + +#define USE_SHORTPREAMBLE(is_1_Mbit, use_short_preamble) ((!is_1_Mbit) & use_short_preamble) + +// Length (in usecs) of SIFS and PLCP preamble/header. +#define PRE_LEN(is_1_Mbit, use_short_preamble) (USE_SHORTPREAMBLE(is_1_Mbit, use_short_preamble) ? 106 : 202) + +// Duration (in usecs) of an OFDM frame at rate (in 500kbps units) +// including SIFS and PLCP preamble/header +#define OFDM_DURATION(bytes, rate) (36 + 4*((4*(bytes)+(rate)+10)/(rate))) + +static unsigned int getRateIndex(struct piper_priv *piperp) +{ + unsigned int rates = piperp->ac->rd_reg(piperp, MAC_SSID_LEN); + unsigned int result = AIROHA_LOWEST_OFDM_RATE_INDEX; + + if (piperp->rf->getBand(piperp->channel) == IEEE80211_BAND_2GHZ) { + if ((rates & MAC_PSK_BRS_MASK) != 0) { + result = AIROHA_LOWEST_PSK_RATE_INDEX; + } + } + + return result; +} + +static int getAckDuration(struct piper_priv *piperp) +{ + bool is_1_Mbit = (getRateIndex(piperp) == AIROHA_LOWEST_PSK_RATE_INDEX); + int duration = 0; + + if (is_1_Mbit) { + duration = CCK_DURATION(ACK_SIZE, 1); + } else { + duration = OFDM_DURATION(ACK_SIZE, 6); + } + + duration += PRE_LEN(is_1_Mbit, piperp->use_short_preamble); + + return duration; +} + + +/* + * This function is used to notify the AP about the current state of + * power save. One of the bits in the 802.11 header field is set to + * indicate the status of power save. This bit is actually set appropriately + * for all frames sent, we just send a null data frame just to make + * sure something is sent to the AP in a reasonable amount of time. + */ +/* + * Possible values for is_power_management_on argument + */ +#define POWERING_DOWN (true) +#define POWERED_UP (false) +void piper_sendNullDataFrame(struct piper_priv *piperp, bool is_power_management_on) +{ + struct sk_buff *skb = NULL; + _80211HeaderType *header; + struct ieee80211_tx_info *tx_info; + + skb = + __dev_alloc_skb(sizeof(_80211HeaderType) + + piperp->hw->extra_tx_headroom, GFP_ATOMIC); + if (skb == NULL) + goto piper_sendNullDataFrame_Exit; + + tx_info = (struct ieee80211_tx_info *) skb->cb; + + skb_reserve(skb, piperp->hw->extra_tx_headroom); + header = (_80211HeaderType *) skb_put(skb, sizeof(_80211HeaderType)); + memset(header, 0, sizeof(*header)); + header->fc.type = TYPE_NULL_DATA; + header->fc.pwrMgt = is_power_management_on; + header->duration = getAckDuration(piperp); + memcpy(header->addr1, piperp->bssid, sizeof(header->addr1)); + memcpy(header->addr2, piperp->pdata->macaddr, sizeof(header->addr2)); + memcpy(header->addr3, piperp->bssid, sizeof(header->addr3)); + + tx_info->flags = IEEE80211_TX_CTL_ASSIGN_SEQ | IEEE80211_TX_CTL_FIRST_FRAGMENT; + tx_info->band = piperp->rf->getBand(piperp->channel); + tx_info->antenna_sel_tx = 1; /* actually this is ignored for now */ + tx_info->control.rates[0].idx = 0; + tx_info->control.rates[0].count = 2; /* no retries. Don't tie us up waiting for an ACK */ + tx_info->control.rates[0].flags = 0; + tx_info->control.rates[1].idx = -1; + tx_info->control.rts_cts_rate_idx = -1; + piperp->ps.frames_pending++; + + if (piper_hw_tx_private(piperp->hw, skb, ps_free_frame) != 0) { + /* printk(KERN_ERR + "piper_hw_tx() failed unexpectedly when sending null data frame.\n"); */ + ps_free_frame(piperp->hw, skb); + } + +piper_sendNullDataFrame_Exit: + return; +} + + + + +#define RESET_PIPER (1) /* must be set to 1 (0 case is only for debugging)*/ +#define PS_DONT_FORCE (false) /* set force to this value if we want to be safe */ +#define PS_FORCE_POWER_DOWN (true) /* set force to this value to shut down the H/W reguardless*/ +/* + * This routine shuts down Piper and the Airoha transceiver. First we check to + * make sure the driver and H/W are idle. Then we save the state of the H/W. + * Then we shut down the Airoha and place piper into reset. + */ +int piper_MacEnterSleepMode(struct piper_priv *piperp, bool force) +{ + /* + * Interrupts are already disabled when we get here. + */ + + if (piperp->ps.poweredDown) + return 0; + + savedRegs[INDX_INTR_MASK] = piperp->ac->rd_reg(piperp, BB_IRQ_MASK); + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, 0, op_write); + + if (!force) { + if ( (piperp->ps.rxTaskletRunning) + || ((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY)) + || ( (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) + & BB_GENERAL_CTL_TX_FIFO_EMPTY) == 0) + || (piperp->tx_tasklet_running) + || ( (piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) + & BB_GENERAL_STAT_RX_FIFO_EMPTY) == 0) + || (piperp->ac->rd_reg(piperp, BB_IRQ_STAT) & savedRegs[INDX_INTR_MASK])) { + + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, savedRegs[INDX_INTR_MASK], op_write); + return -1; + } + } + + disable_irq(piperp->irq); + + savedRegs[INDX_GEN_CONTROL] = piperp->ac->rd_reg(piperp, BB_GENERAL_CTL); + savedRegs[INDX_GEN_STATUS] = piperp->ac->rd_reg(piperp, BB_GENERAL_STAT); + savedRegs[INDX_RSSI_AES] = piperp->ac->rd_reg(piperp, BB_RSSI) & ~BB_RSSI_EAS_BUSY; + savedRegs[INDX_SPI_CTRL] = piperp->ac->rd_reg(piperp, BB_SPI_CTRL); + savedRegs[INDX_CONF1] = piperp->ac->rd_reg(piperp, BB_TRACK_CONTROL); + savedRegs[INDX_CONF2] = piperp->ac->rd_reg(piperp, BB_CONF_2); + savedRegs[INDX_OUT_CTRL] = piperp->ac->rd_reg(piperp, BB_OUTPUT_CONTROL); + savedRegs[INDX_MAC_CONTROL] = piperp->ac->rd_reg(piperp, MAC_CTL); + + savedRegs[INDX_STAID_1] = piperp->ac->rd_reg(piperp, MAC_STA_ID0); + savedRegs[INDX_STAID_2] = piperp->ac->rd_reg(piperp, MAC_STA_ID1); + savedRegs[INDX_BSSID_1] = piperp->ac->rd_reg(piperp, MAC_BSS_ID0); + savedRegs[INDX_BSSID_2] = piperp->ac->rd_reg(piperp, MAC_BSS_ID1); + savedRegs[INDX_BRS_SSID] = piperp->ac->rd_reg(piperp, MAC_SSID_LEN); + savedRegs[INDX_BACKOFF] = piperp->ac->rd_reg(piperp, MAC_BACKOFF); + savedRegs[INDX_DTIM_LISTEN] = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD); + savedRegs[INDX_BEACON_INT] = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM); + savedRegs[INDX_MAC_CTL] = piperp->ac->rd_reg(piperp, MAC_CTL); + savedRegs[INDX_BEACON_MASK] = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT); + + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RX_EN, op_and); //disable receiving + piperp->ac->wr_reg(piperp, MAC_CTL, 0, op_write); + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, 0, op_write); + +#if RESET_PIPER + // Power down the airoha transceiver + piperp->rf->power_on(piperp->hw, false); + udelay(10); + // hold the transceiver in reset mode + if (piperp->pdata->reset) + piperp->pdata->reset(piperp, 1); +#endif + stats.jiffiesOn += jiffies - stats.cycleStart; + stats.cycleStart = jiffies; + piperp->ps.poweredDown = true; + + return 0; +} + + +#define PS_NO_SPIKE_SUPPRESSION (false) /* want_spike_suppression = don't want spike suppression*/ +#define PS_WANT_SPIKE_SUPPRESSION (true) /* want_spike_suppression = do spike suppression*/ +/* + * Turn the H/W back on after it was shutdown with piper_MacEnterSleepMode. + * + * 1. Power up the hardware. + * 2. Perform the spike suppression routine if desired. The spike suppression + * routine resyncs the clocks in Piper in order to prevent us from generating + * noise spikes at 1 and 2 MHz. Unfortunately it takes an indeterminate amount + * of time, so we normally don't do it and just make sure we do not send at + * at those data rates while duty cycling. + * 3. Set the channel. The Airoha was shut off so we have to set the channel + * again. + * 4. Restore the state of piper registers. + * 5. Zero out and reset the transmitter FIFO. Mike Schaffner claims this should + * not be necessary, but we seem to run into trouble when we don't do it. + * 6. Restore the interrupts. + */ +void piper_MacEnterActiveMode(struct piper_priv *piperp, bool want_spike_suppression) +{ + int i; +// #define WANT_DEBUG +#ifdef WANT_DEBUG + static unsigned int run = 0; +#endif + +#if RESET_PIPER + + if (piperp->pdata->reset) { +#ifdef WANT_DEBUG + if (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_TX_FIFO_FULL) { + printk(KERN_ERR "**** While in reset, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } + if (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + printk(KERN_ERR "**** While in reset AES busy set, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } +#endif + piperp->pdata->reset(piperp, 0); + udelay(10); + +#ifdef WANT_DEBUG + if (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_TX_FIFO_FULL) { + printk(KERN_ERR "**** After reset, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } + if (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + printk(KERN_ERR "**** After reset AES busy set, run = %d\n", run); + digiWifiDumpRegisters(piperp, MAIN_REGS); + while(1); + } + run++; +#endif + piperp->rf->power_on(piperp->hw, true); + mdelay(1); + piper_spike_suppression(piperp, want_spike_suppression); + } +#endif + + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, 0xff, op_write); + + +#if RESET_PIPER + piperp->rf->set_chan_no_rx(piperp->hw, piperp->channel); +#endif + + // store the registers back + + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, 0x30000000, op_write); + piperp->ac->wr_reg(piperp, BB_RSSI, savedRegs[INDX_RSSI_AES] & ~BB_RSSI_EAS_BUSY, op_write); + +// piperp->ac->wr_reg(piperp, BB_IRQ_MASK, savedRegs[INDX_INTR_MASK], op_write); + piperp->ac->wr_reg(piperp, BB_SPI_CTRL, savedRegs[INDX_SPI_CTRL], op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, savedRegs[INDX_CONF1], op_write); + piperp->ac->wr_reg(piperp, BB_CONF_2, savedRegs[INDX_CONF2], op_write); + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, savedRegs[INDX_OUT_CTRL], op_write); + piperp->ac->wr_reg(piperp, MAC_CTL, savedRegs[INDX_MAC_CONTROL], op_write); + + piperp->ac->wr_reg(piperp, MAC_STA_ID0, savedRegs[INDX_STAID_1], op_write); + piperp->ac->wr_reg(piperp, MAC_STA_ID1, savedRegs[INDX_STAID_2], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, savedRegs[INDX_BSSID_1], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, savedRegs[INDX_BSSID_2], op_write); + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, savedRegs[INDX_BRS_SSID], op_write); + piperp->ac->wr_reg(piperp, MAC_BACKOFF, savedRegs[INDX_BACKOFF], op_write); + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, savedRegs[INDX_DTIM_LISTEN], op_write); + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, savedRegs[INDX_BEACON_INT], op_write); + piperp->ac->wr_reg(piperp, MAC_CTL, savedRegs[INDX_MAC_CTL], op_write); + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, savedRegs[INDX_BEACON_MASK], op_write); + +//**** + // set bit-11 in the general control register to a 1 to start the processors + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_MAC_ASSIST_ENABLE, op_or); + + // set the TX-hold bit + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720080, op_write); + + // clear the TX-FIFO memory + for (i = 0; i < 448; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, 0, op_write); + + // clear RX-FIFO memory + for (i = 0; i < 512; i++) + piperp->ac->rd_reg(piperp, BB_DATA_FIFO); + + // reset the TX-FIFO and RX-FIFO + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x377200E0, op_write); + + + // release the TX-hold and reset + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720000, op_write); + + +//*** + /* + * Reset the interrupt mask. We could have been receiving a frame when we + * powered down. This could cause us to store the wrong mask, so we want + * to make sure we enabe the RX interrupts. However, we should not have the + * TX interrupts enabled when we come out of power save mode. + */ + udelay(50); + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, 0xff, op_write); + piperp->clear_irq_mask_bit(piperp, 0xffffffff); + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_RX_OVERRUN | BB_IRQ_MASK_RX_FIFO); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + (savedRegs[INDX_GEN_CONTROL] | 0x37000000 | + BB_GENERAL_CTL_RX_EN), op_write); + + stats.jiffiesOff += jiffies - stats.cycleStart; + stats.cycleStart = jiffies; + + /* TODO, this is a temporary workaround and should be better analyzed in future. + * The problem is that the general power save code is not synchronized with the + * dynamic PW and this is causing that, the following line, unbalances the + * piper wireless irq */ + if (piperp->ps.poweredDown != false) + enable_irq(piperp->irq); + + piperp->ps.poweredDown = false; +} + + + + +/* + * So what crazy thing are we doing here? Well, Piper has a bug where it + * can send noise spikes at 1 Mbps and 2 Mbps if it is powered up without + * running a special spike suppression routine. The spike suppression code + * takes an average of 30 ms, and I have timed it taking as long as 300 ms. + * This is not something you want to use for duty cycling. The solution is + * to avoid sending at those two rates. After the transmit routine determines + * the rate mac80211 specified, it will call us and we will decide whether + * we like that rate. If it is one of our bad rates, then we will bump it + * up to a good rate. + */ +struct ieee80211_rate *piper_ps_check_rate(struct piper_priv *piperp, + struct ieee80211_rate *rate) +{ +#define BAD_RATE_1MBPS (10) +#define BAD_RATE_2MBPS (20) + if ((piperp->ps.mode == PS_MODE_LOW_POWER) && (rate != NULL)) { + if ((rate->bitrate == BAD_RATE_1MBPS) + || (rate->bitrate == BAD_RATE_2MBPS)) { + rate = + (struct ieee80211_rate *) piperp->rf-> + getRate(AIROHA_55_MBPS_RATE_INDEX); + } + } + + return rate; +} + +EXPORT_SYMBOL_GPL(piper_ps_check_rate); + + + +/* + * This routine restarts the transmitter after powering back + * up, or failing to power down. + * + * 1) Clear the power management bit so that frames will be + * sent indicating that we are poweredup. + * 2) Set the flag to allow transmits again. + * 3) If we stopped the mac80211 transmitter queue, then start + * it back up again. + * 4) Notify the AP that we are awake by sending a null-data frame + * with the power management bit clear. + */ +static void ps_resume_transmits(struct piper_priv *piperp) +{ + piperp->ps.power_management = POWERED_UP; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + piper_sendNullDataFrame(piperp, POWERED_UP); + ieee80211_wake_queues(piperp->hw); +} + + + + + + +/* + * This routine sets an event timer. The ps_state_machine routine + * will be executed when the event timer expires. + */ +static void ps_set_timer_event(struct piper_priv *piperp, + enum piper_ps_event next_event, + unsigned int timeout_ms) +{ + unsigned int delay_in_jiffies = MILLS_TO_JIFFIES(timeout_ms); + + if (delay_in_jiffies == 0) + delay_in_jiffies++; + + del_timer_sync(&piperp->ps.timer); + piperp->ps.this_event = next_event; + piperp->ps.timer.expires = delay_in_jiffies + jiffies; + add_timer(&piperp->ps.timer); +} + + +/* + * This routine cancels an event timer set by ps_set_timer_event. + */ +static void ps_cancel_timer_event(struct piper_priv *piperp) +{ + del_timer_sync(&piperp->ps.timer); +} + + +#define WANT_STATE_MACHINE_DEBUG (0) +#if WANT_STATE_MACHINE_DEBUG + +struct event_record { + enum piper_ps_event event; + enum piper_ps_state state; +}; + +#define MAX_EVENTS_RECORDED (15) +static unsigned int debug_events_index = 0; +static struct event_record debug_events[MAX_EVENTS_RECORDED]; + +void debug_track_event(enum piper_ps_event event, enum piper_ps_state state) +{ + debug_events[debug_events_index].event = event; + debug_events[debug_events_index].state = state; + if (debug_events_index == MAX_EVENTS_RECORDED) { + unsigned int i; + + printk(KERN_ERR); + for (i = 0; i < MAX_EVENTS_RECORDED; i++) { + printk("(%d,%d)", debug_events[i].event, + debug_events[i].state); + } + printk("\n"); + debug_events_index = 0; + } else { + debug_events_index++; + } +} + + +#else + #define debug_track_event(event, state) +#endif + +/* + * This is the entry point into the duty cycle state machine. It may be called + * by: + * + * 1) piper_ps_handle_beacon when a beacon frame is received. + * 2) the receiver task when we receive the ACK for the last pending frame + * when we are waiting to power down. + * 3) by ps_timer for many timer events. + */ +static void ps_state_machine(struct piper_priv *piperp, enum piper_ps_event event) +{ + unsigned long flags; + + debug_track_event(event, piperp->ps.state); + + spin_lock_irqsave(&piperp->ps.lock, flags); + + + switch (event) { + case PS_EVENT_BEACON_RECEIVED: + /* + * We just received a beacon. This is really the driving event in this state + * machine. Everything else is synchronized from it. + * + * We know we are powered up because we just received the beacon. The normal + * control path is to set a timer which will expire when we need to start + * preparations for powering down again. + */ + ps_cancel_timer_event(piperp); /* cancel missed beacon timer*/ + stats.receivedBeacons++; + if ( (!piperp->areWeAssociated) + || (piperp->ps.beacon_int < PS_MINIMUM_BEACON_INT) + || (piperp->ps.scan_timer != 0)) { + /* + * Don't duty cyle while we are trying to associate. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + if (piperp->ps.scan_timer) { + piperp->ps.scan_timer--; + } + break; + } + if (piperp->ps.state == PS_STATE_WAIT_FOR_BEACON) { + int timeout; + /* + * Calculate amount of time to sleep. + */ + piperp->ps.sleep_time = (piperp->ps.beacon_int * (100 - piperp->power_duty)) / 100; + + /* + * Now figure out how long we have before it's time to go to sleep. We + * have to wake up at least PS_WAKE_BEFORE_BEACON_MS before we expect to + * receive the next beacon, and we need to start powering down at least + * PS_TRANSMITTER_SHUTDOWN_MS ahead of time. + */ + timeout = piperp->ps.beacon_int - (piperp->ps.sleep_time + PS_TRANSMITTER_SHUTDOWN_MS + PS_WAKE_BEFORE_BEACON_MS); + if ((timeout > 0) && (piperp->ps.sleep_time > PS_MINIMUM_SLEEP_TIME)) { + /* + * If there is enough time left that it makes sense to duty + * cycle, then program the timer and advance to the next state. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT; + ps_set_timer_event(piperp, PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED, timeout); + + break; + } + } else { + if ( (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE) + || (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT)) { + ps_resume_transmits(piperp); + } + digi_dbg("*** Beacon event in state %d.\n", piperp->ps.state); + } + /* + * We will come here if we were in the wrong state when we received the + * beacon, or if the duty cycle is too short. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.beacon_int + PS_BEACON_TIMEOUT_MS); + break; + case PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED: + /* + * This event is hit when it's time to start powering down. Unfortunately, this is + * not a simple thing to do. The first things to do are: + * + * 1. Set the power save on flag. This will cause any transmit frame to be + * sent with the power management bit set. + * 2. Stop the mac80211 layer from sending us anymore frames. + * 3. Signal the AP that we are powering down by sending a null-data frame with the + * power management bit set. + */ + if (piperp->ps.state == PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT) { + if (piperp->ps.scan_timer) { + /* + * Don't shut down if we are scanning. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + break; + } + piperp->ps.power_management = POWERING_DOWN; + piperp->ps.allowTransmits = false; + ieee80211_stop_queues(piperp->hw); + piperp->ps.stopped_tx_queues = true; + piper_sendNullDataFrame(piperp, POWERING_DOWN); + piperp->ps.state = PS_STATE_WAIT_FOR_TRANSMITTER_DONE; + ps_set_timer_event(piperp, PS_EVENT_TRANSMITTER_DONE_TIMER_TICK, + PS_TRANSMIT_TIMER_TICK_MS); + } else { + /* + * This should never happen (famous last words) + */ + digi_dbg("** stop tx event, state = %d.\n", piperp->ps.state); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.beacon_int + PS_BEACON_TIMEOUT_MS); + } + break; + case PS_EVENT_TRANSMITTER_DONE: + /* + * This event is triggered when the receive task finishes processing the ACK + * from the null-data frame sent by the PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED event. + * We try to power down now. + */ + if (piperp->ps.scan_timer == 0) { + if ( (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE) + || (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT)) { + ps_cancel_timer_event(piperp); /* cancel transmitter done timeout timer*/ + if (piper_MacEnterSleepMode(piperp, PS_DONT_FORCE) == 0) { + piperp->ps.state = PS_STATE_WAIT_FOR_WAKEUP_ALARM; + /* + * Note that the value PS_EVENT_TRANSMITTER_DONE_TIMER_TICK is + * updated as necessary by the PS_EVENT_TRANSMITTER_DONE_TIMER_TICK + * event to take into account the amount of time it took for the + * transmitter to finish sending the last frame. + */ + ps_set_timer_event(piperp, PS_EVENT_WAKEUP, piperp->ps.sleep_time); + break; + } + } else { +#ifdef WANT_DEBUG + printk(KERN_ERR "couldn't sleep, rxt=%d, AES busy = %d, txfifo=%d, txt=%d, rxfifo=%d\n", + (piperp->ps.rxTaskletRunning),((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) != 0), + ( (piperp->ac->rd_reg(piperp, BB_GENERAL_CTL)& BB_GENERAL_CTL_TX_FIFO_EMPTY) == 0), + (piperp->tx_tasklet_running), + ( (piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) + & BB_GENERAL_STAT_RX_FIFO_EMPTY) == 0)); +#endif + digi_dbg("** PS_EVENT_TRANSMITTER_DONE event, but state == %d.\n", piperp->ps.state); + } + } + /* + * If we fall through to here, then either we are in the wrong state, or we were + * not able to power down the H/W. + */ + ps_resume_transmits(piperp); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.beacon_int + PS_BEACON_TIMEOUT_MS); + break; + case PS_EVENT_TRANSMITTER_DONE_TIMER_TICK: + /* + * This event is triggered periodically while we are waiting for the + * transmitter to finish sending that last packet. We decrement + * piperp->ps.sleep_time (which is used by the PS_EVENT_TRANSMITTER_DONE + * event). If piperp->ps.sleep_time is still larger than our minimum + * required sleep time, then we just restart the timer. + */ + if (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE) { + piperp->ps.sleep_time -= PS_TRANSMIT_TIMER_TICK_MS; + if (piperp->ps.sleep_time >= PS_MINIMUM_SLEEP_TIME) { + piperp->ps.state = PS_STATE_WAIT_FOR_TRANSMITTER_DONE; + ps_set_timer_event(piperp, PS_EVENT_TRANSMITTER_DONE_TIMER_TICK, + PS_TRANSMIT_TIMER_TICK_MS); + } else { + /* + * Transmitter did not shut down in time. Resume normal operations + * and stay awake until the next beacon. + */ + ps_resume_transmits(piperp); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + piperp->ps.sleep_time + PS_WAKE_BEFORE_BEACON_MS + + PS_BEACON_TIMEOUT_MS); + } + } else if (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT) { + piperp->ps.sleep_time -= PS_TRANSMIT_TIMER_TICK_MS; + /* + * The piper_ps_rx_task_exiting routine sets this state just before it + * releases the lock on ps.state and calls this event. If we ever + * come here, then the timer tick occurred just between the time + * piper_ps_rx_task_exiting released the lock and ps_state_machine + * reset it. Since the tx done event is in progress, we should ignore + * this tick. + */ + break; + } else { + digi_dbg("** done tick in state %d.\n", piperp->ps.state); + } + break; + case PS_EVENT_WAKEUP: + /* + * This event is called when we have powered down and it is time + * to power back up again. + * + * 1) Power up the H/W. + * 2) Resume normal operations + * 3) Update our state. + * 4) Set a timeout for receiving the next beacom frame. + */ + if (piperp->ps.state == PS_STATE_WAIT_FOR_WAKEUP_ALARM) { + piper_MacEnterActiveMode(piperp, PS_NO_SPIKE_SUPPRESSION); + ps_resume_transmits(piperp); + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, + PS_BEACON_TIMEOUT_MS + PS_WAKE_BEFORE_BEACON_MS); + } else { + digi_dbg("** wake event in state %d.\n", piperp->ps.state); + } + break; + case PS_EVENT_MISSED_BEACON: + /* + * This event is called when we miss a beacon. For now just update + * our statistics. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_BEACON; + if ((piperp->areWeAssociated) && (piperp->ps.scan_timer == 0)) { + stats.missedBeacons++; + ps_set_timer_event(piperp, PS_EVENT_MISSED_BEACON, piperp->ps.beacon_int); + } + break; + default: + digi_dbg("**** ps_state_machine received unknown event %d.\n", event); + break; + } + spin_unlock_irqrestore(&piperp->ps.lock, flags); + +} + +/* + * This routine is called by the receiver task when it exits. We use it to generate + * the PS_EVENT_TRANSMITTER_DONE event. The event signifies that the transmitter has + * sent our null-data frame and is idle. We determine this by checking frames_pending, + * while will be nonzero if a null-data frame is waiting to be sent, and the machine's + * state. + */ +void piper_ps_rx_task_exiting(struct piper_priv *piperp) +{ + unsigned long flags; + + spin_lock_irqsave(&piperp->ps.lock, flags); + + if ( (piperp->ps.frames_pending == 0) + && (piperp->ps.state == PS_STATE_WAIT_FOR_TRANSMITTER_DONE)) { + /* + * We have a race condition between the transmitter done tick and + * this routine. So this routine changes the state to + * PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT before it releases the + * lock so that we don't get confused. + */ + piperp->ps.state = PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT; + spin_unlock_irqrestore(&piperp->ps.lock, flags); + ps_state_machine(piperp, PS_EVENT_TRANSMITTER_DONE); + } else { + spin_unlock_irqrestore(&piperp->ps.lock, flags); + } +} + +/* + * Called when the event timer expires. Call the state machine to handle + * the event. + */ +static void ps_timer(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *) context; + + ps_state_machine(piperp, piperp->ps.this_event); +} + + + +/* + * This routine is called when we receive a beacon. We extract the beacon interval + * in case it has changed and then call the state machine. + */ +static void piper_ps_handle_beacon(struct piper_priv *piperp, struct sk_buff *skb) +{ +#define BEACON_INT_LSB (8) +#define BEACON_INT_MSB (9) + u32 beacon_int; + _80211HeaderType *header = (_80211HeaderType *) skb->data; + bool fromOurAp = piperp->areWeAssociated + && (memcmp(piperp->bssid, header->addr3, sizeof (header->addr3)) == 0); + + /* + * mac80211 does not inform us when the beacon interval changes, so we have + * to read this information from the beacon ourselves. + */ + + if (fromOurAp) { + beacon_int = skb->data[sizeof(_80211HeaderType) + BEACON_INT_LSB]; + beacon_int |= (skb->data[sizeof(_80211HeaderType) + BEACON_INT_MSB] << 8); + piperp->ps.beacon_int = beacon_int; + + if (piperp->ps.mode == PS_MODE_LOW_POWER) { + ps_state_machine(piperp, PS_EVENT_BEACON_RECEIVED); + } + } +} + + + +/* + * This routine is called when mac80211 starts doing things that might indicate it + * is attempting to scan or reassociate. Things like changing the channel or + * disassociating. When we receive an event like that, we stop duty cycling for + * a while since it may interfere with attempts to reassociate with an access point. + */ +void piper_ps_scan_event(struct piper_priv *piperp) +{ + (void) piperp; +#if 0 + /* + * It appears that pausing duty cycling during association events may actually + * worsen performance I suspect that either the AP or mac80211 is measuring + * our throughput and adjusting the load accordingly, and that momentary changes + * in performance caused by pausing duty cyling interfere with this. + * + * TODO: Consider removing this code. I left it in for now in case we decide + * to try it again, but if we're not going to use it, it just makes the + * driver more confusing and should be removed. + */ + if (piperp->ps.beacon_int != 0) { + piperp->ps.scan_timer = PS_SCAN_DELAY / piperp->ps.beacon_int; + } else { + piperp->ps.scan_timer = PS_SCAN_DELAY / 100; + } +#endif +} + + + +/* + * This routine is called so we can process incoming frames. We do the + * handshaking to receive buffered frames in PS mode here. + */ +void piper_ps_process_receive_frame(struct piper_priv *piperp, struct sk_buff *skb) +{ + _80211HeaderType *header = (_80211HeaderType *) skb->data; + + if (header->fc.type == TYPE_BEACON) { + piper_ps_handle_beacon(piperp, skb); + } else if ( (header->fc.type == TYPE_ASSOC_RESP) + || (header->fc.type == TYPE_REASSOC_RESP) + || (header->fc.type == TYPE_PROBE_RESP) + || (header->fc.type == TYPE_DISASSOC) + || (header->fc.type == TYPE_DEAUTH) + || (header->fc.type == TYPE_ACTION)) { + piper_ps_scan_event(piperp); + } +} + +EXPORT_SYMBOL_GPL(piper_ps_process_receive_frame); + + + +/* + * This function turns power save mode on or off. + */ +void piper_ps_set(struct piper_priv *piperp, bool powerSaveOn) +{ +#define MAX_SHUTDOWN_TIMEOUT (100) + unsigned long flags; + + spin_lock_irqsave(&piperp->ps.lock, flags); + + piper_ps_scan_event(piperp); + if (powerSaveOn) { + if (piperp->ps.beacon_int >= PS_MINIMUM_BEACON_INT) { + if (piperp->ps.mode != PS_MODE_LOW_POWER) { + piperp->ps.aid = 0; + piperp->ps.mode = PS_MODE_LOW_POWER; + piperp->ps.state= PS_STATE_WAIT_FOR_BEACON; + piperp->ps.power_management = POWERED_UP; + piperp->ps.poweredDown = false; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + stats.receivedBeacons = 0; + stats.missedBeacons = 0; + stats.modeStart = jiffies; + stats.cycleStart = jiffies; + stats.jiffiesOff = 0; + stats.jiffiesOn = 0; + piper_sendNullDataFrame(piperp, POWERED_UP); + /* + * Will start it the next time we receive a beacon. + */ + } + } else { + printk(KERN_ERR + "\nUnable to set power save mode because the beacon \n" + "interval set on this access point less than 100ms.\n"); + } + } else { + ps_cancel_timer_event(piperp); + if (piperp->ps.mode == PS_MODE_LOW_POWER) { + piperp->ps.mode = PS_MODE_FULL_POWER; /* stop duty cycle timer */ + if (piperp->ps.poweredDown) { + /* + * If we were powered down, then power up and do the spike suppression. + */ + piper_MacEnterActiveMode(piperp, PS_WANT_SPIKE_SUPPRESSION); + } else { + unsigned int timeout = 50; + int result; + /* + * If we branch here, then we were already powered up. You would think + * that we would be all set, but it's not that easy. Piper has a bug in + * it where we have to run a special spike suppression routine when we + * power it up. However, this routine takes an average of 30 ms to run, + * and I've see it take as long as 300 ms. This is not acceptable when + * we are duty cycling every 100 ms. To get around this, we do NOT do + * the spike suppression while duty cycling. Instead, we simply avoid + * transmitting at those rates which would cause spikes. Now, however, + * we are ending duty cycling and returning to normal operations so we + * have to do the spike suppression. Since we are powered up, the first + * thing to do is to power down. + */ + if (piperp->ps.state != PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT) { + /* + * If we come here, then we did not happen to be trying to power down + * just as we got the command from mac80211, so we have to start + * the procedure. This is the normal case. + * + * 1. Set the power save on flag. This will cause frames to be + * transmitted with the power management bit on. The reason + * for doing that is to tell the AP to stop sending us frames. + * 2. Stop the mac80211 layer from sending us more frames by stopping + * the transmit queues. + * 3. Send a null-data frame to the AP with the power management bit + * set. This should cause it to stop sending us frames. + */ + piperp->ps.power_management = POWERING_DOWN; + piperp->ps.allowTransmits = false; + ieee80211_stop_queues(piperp->hw); + piperp->ps.stopped_tx_queues = true; + piper_sendNullDataFrame(piperp, POWERING_DOWN); + } + /* + * Now wait for that last frame to go and and then shut down. + */ + result = -1; + for (timeout = 0; (timeout < MAX_SHUTDOWN_TIMEOUT) && (result != 0); timeout++) { + spin_unlock_irqrestore(&piperp->ps.lock, flags); + mdelay(10); + spin_lock_irqsave(&piperp->ps.lock, flags); + result = piper_MacEnterSleepMode(piperp, PS_DONT_FORCE); + } + if (result != 0) { + /* + * This is bad. For some reason we are not able to power down. We + * will try to force it now, but this may end up putting the driver + * or H/W into a bad state. However, we can't sit in the loop above + * forever either. + */ +#ifdef WANT_DEBUG + printk(KERN_ERR "Forcing Piper to power down\n"); + printk(KERN_ERR "BB_RSSI_EAS_BUSY = %d\n", piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY); + printk(KERN_ERR "BB_GENERAL_CTL_TX_FIFO_EMPTY = %d\n", + piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_TX_FIFO_EMPTY); + printk(KERN_ERR "BB_GENERAL_STAT_RX_FIFO_EMPTY = %d\n", + piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) & BB_GENERAL_STAT_RX_FIFO_EMPTY); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); +#endif + piper_MacEnterSleepMode(piperp, PS_FORCE_POWER_DOWN); + } + /* + * Wait a moment and then power the H/W back up and execute the spike suppression + * routine. + */ + spin_unlock_irqrestore(&piperp->ps.lock, flags); + mdelay(30); + spin_lock_irqsave(&piperp->ps.lock, flags); + piper_MacEnterActiveMode(piperp, PS_WANT_SPIKE_SUPPRESSION); + ps_resume_transmits(piperp); + } + stats.jiffiesOn += jiffies - stats.cycleStart; +#define WANT_STATS (0) +#if WANT_STATS + if ((piperp->ps.beacon_int != 0) + && ((jiffies - stats.modeStart) != 0)) { + printk(KERN_ERR + "jiffiesOff = %u, jiffiesOn = %u, total time = %lu\n", + stats.jiffiesOff, stats.jiffiesOn, + (jiffies - stats.modeStart)); + printk(KERN_ERR + "Powered down %ld percent of the time.\n", + (stats.jiffiesOff * 100) / (jiffies - stats.modeStart)); + printk(KERN_ERR + "Received %u of %lu beacons while in powersave mode.\n", + stats.receivedBeacons, + (jiffies - + stats.modeStart) / + MILLS_TO_JIFFIES(piperp->ps.beacon_int)); + printk(KERN_ERR "received %d beacons, missed %d\n", + stats.receivedBeacons, stats.missedBeacons); + printk(KERN_ERR "allowTransmits = %d, stopped_tx_queues = %d, q_count = %d\n", + piperp->ps.allowTransmits, piperp->ps.stopped_tx_queues, + piperp->tx_queue_count); + if ((stats.receivedBeacons + stats.missedBeacons) != 0) + printk(KERN_ERR "%d%% beacons were missed\n", + (100 * stats.missedBeacons) / (stats.receivedBeacons + stats.missedBeacons)); + } +#endif + } + piperp->ps.aid = 0; + piperp->ps.state= PS_STATE_WAIT_FOR_BEACON; + piperp->ps.power_management = POWERED_UP; + piperp->ps.poweredDown = false; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + ps_resume_transmits(piperp); + piper_sendNullDataFrame(piperp, POWERED_UP); + } + + spin_unlock_irqrestore(&piperp->ps.lock, flags); +} + +EXPORT_SYMBOL_GPL(piper_ps_set); + + + +/* + * Called when driver is loaded. Initialize our context. + */ +void piper_ps_init(struct piper_priv *piperp) +{ + memset(&piperp->ps, 0, sizeof(piperp->ps)); + piperp->ps.beacon_int = 100; + piperp->ps.aid = 0; + init_timer(&piperp->ps.timer); + piperp->ps.timer.function = ps_timer; + piperp->ps.timer.data = (unsigned long) piperp; + piperp->ps.mode = PS_MODE_FULL_POWER; + piperp->ps.state= PS_STATE_WAIT_FOR_BEACON; + spin_lock_init(&piperp->ps.lock); + piperp->ps.power_management = POWERED_UP; + piperp->ps.poweredDown = false; + piperp->ps.rxTaskletRunning; + piperp->ps.allowTransmits = true; + piperp->ps.stopped_tx_queues = false; + piperp->ps.frames_pending = 0; +} + +EXPORT_SYMBOL_GPL(piper_ps_init); + + +/* + * Called when driver is unloaded. Make sure the PS + * timer is shut down. + */ +void piper_ps_deinit(struct piper_priv *piperp) +{ + piper_ps_set(piperp, true); + piperp->ps.mode = PS_MODE_FULL_POWER; + del_timer_sync(&piperp->ps.timer); +} + +EXPORT_SYMBOL_GPL(piper_ps_deinit); diff --git a/drivers/net/wireless/digiPiper/digiPs.h b/drivers/net/wireless/digiPiper/digiPs.h new file mode 100644 index 000000000000..1feedd87e228 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiPs.h @@ -0,0 +1,50 @@ +/* + * Header file for digiPs.c. Declares functions used for power save mode. + */ + +#ifndef digiPs_h + +#define digiPS_h + +#include <net/mac80211.h> +#include "pipermain.h" + +enum piper_ps_event { + PS_EVENT_BEACON_RECEIVED, + PS_EVENT_STOP_TRANSMIT_TIMER_EXPIRED, + PS_EVENT_TRANSMITTER_DONE, + PS_EVENT_TRANSMITTER_DONE_TIMER_TICK, + PS_EVENT_WAKEUP, + PS_EVENT_MISSED_BEACON +}; + +enum piper_ps_tx_completion_result { + PS_RETURN_SKB_TO_MAC80211, + PS_DONT_RETURN_SKB_TO_MAC80211 +}; + +enum piper_ps_active_result { + PS_CONTINUE_TRANSMIT, + PS_STOP_TRANSMIT +}; + + +/* + * Current version of mac80211 doesn't set power management bit in frame headers, + * so I guess we have to for now. + * + * TODO: See if we still have to do this in the next drop. + */ +#define piper_ps_set_header_flag(piperp, header) \ + header->fc.pwrMgt = (piperp->ps.power_management) + +int piper_ps_active(struct piper_priv *piperp); +void piper_ps_process_receive_frame(struct piper_priv *piperp, + struct sk_buff *skb); +void piper_ps_init(struct piper_priv *piperp); +void piper_ps_deinit(struct piper_priv *piperp); +void piper_ps_set(struct piper_priv *piperp, bool powerSaveOn); +struct ieee80211_rate *piper_ps_check_rate(struct piper_priv *piperp, + struct ieee80211_rate *rate); + +#endif diff --git a/drivers/net/wireless/digiPiper/digiRx.c b/drivers/net/wireless/digiPiper/digiRx.c new file mode 100644 index 000000000000..01ff486311b1 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiRx.c @@ -0,0 +1,412 @@ +/* + * digiRx.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains the routines that are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define WANT_RECEIVE_COUNT_SCROLL (0) +#define AES_TIMEOUT (200) +#define RX_DEBUG (1) + +#if RX_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + + +/* + * This routine is called to flush the receive and transmit FIFOs. It is used + * for error recovery when we detect corrupted data in the FIFO's. It should be + * called with the AES lock set. + */ +static void reset_fifo(struct piper_priv *piperp) +{ + unsigned int i; + + piperp->ac->rd_reg(piperp, BB_AES_CTL); + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + // clear the TX-FIFO memory + for (i = 0; i < 448; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, 0, op_write); + + // clear RX-FIFO memory + for (i = 0; i < 512; i++) + piperp->ac->rd_reg(piperp, BB_DATA_FIFO); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_RXFIFORST, op_or); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RXFIFORST, op_and); +} + +/* + * This routine is called to receive a frame. The hardware header has + * already been read from the FIFO. We need to read out the frame. If + * the frame was encrypted with AES and we have the correct key, then + * we use the AES H/W encryption engine to decrypt the frame. We also + * set up a ieee80211_rx_status structure with the appropriate info. + * + * Arguments + * digi context information + * skb empty buffer to receive packet into + * length number of bytes in FIFO + * fr_ctrl_field buffer to copy frame control header into + * status status structure we must write status into + * + * Returns + * true frame was received + * false an encryption error was detected + */ +static bool receive_packet(struct piper_priv *piperp, struct sk_buff *skb, int length, + frameControlFieldType_t * fr_ctrl_field, + struct ieee80211_rx_status *status) +{ + _80211HeaderType *header; + bool result = true; + int originalLength = length; + int headerlen; +#if WANT_RECEIVE_COUNT_SCROLL + static int packetCount = 0; +#endif + + headerlen = _80211_HEADER_LENGTH; + if (length < _80211_HEADER_LENGTH) { + /* + * If we branch here, then there is not enough data to make a + * complete header. This is possible if this is a control frame. + * Adjust our length so that we do not read too much data from + * the FIFO. + */ + headerlen = length; + } + + /* + * Read the frame header. This includes the frame control fields + * as well as the 802.11 header. + */ + header = (_80211HeaderType *) skb_put(skb, headerlen); + length -= headerlen; + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, (uint8_t *) header, headerlen); + memcpy(fr_ctrl_field, &header->fc, sizeof(fr_ctrl_field)); + + if (((u32)(skb->tail)) & 0x3) { + /* align data */ + skb_reserve(skb, 4 - ((u32)(skb->tail) & 0x3)); + } + + if (header->fc.protected) { + /* + * If we branch here, then the frame is encrypted. We need + * to figure out if we should try to decrypt it. + */ + unsigned char *rsnHeader; + unsigned int aesDataBlob[AES_BLOB_LENGTH / sizeof(unsigned int)]; + unsigned int keyIndex; + + rsnHeader = skb_put(skb, PIPER_EXTIV_SIZE); + + /* + * Step 1: Read the rest of the unencrypted data, which should + * consist of the extiv fields. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, rsnHeader, PIPER_EXTIV_SIZE); + length -= PIPER_EXTIV_SIZE; + keyIndex = rsnHeader[3] >> 6; + + if (piper_prepare_aes_datablob(piperp, keyIndex, (u8 *) aesDataBlob, + (unsigned char *)header, originalLength - 12, + false)) { + /* + * If we come here, then we have the correct encryption key for + * the frame and will now try to decrypt it. + */ + unsigned int timeout = AES_TIMEOUT; + unsigned long flags; + + spin_lock_irqsave(&piperp->aesLock, flags); + + /* + * Step 2: Wait for AES to become ready. + */ + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + /* + * If we come here, then AES busy appears to be stuck high. It should only be + * high for a maximum of about 80 us when it is encrypting a transmit frame. + * Our timeout value is high enough to guarantee that the engine has had enough + * time to complete the transmit. Apparently there is data stuck in the FIFO + * from either a previous transmit or receive. + */ + dprintk(DWARNING, "1st AES busy never became ready\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + /* + * Recover by flushing the FIFO and returning in error. + */ + reset_fifo(piperp); +#if 0 + /* + * TODO: Figure out why this code snippet doesn't work. I would think + * that if we reset the fifo, we should just return in error since we will + * have discarded the frame. However, when we do that the system hangs + * (after a while). This doesn't make sense. + */ + spin_unlock_irqrestore(&piperp->aesLock, flags); + result = false; + goto receive_packet_exit; +#else + break; +#endif + } + udelay(1); + } + + /* + * Step 3: Set the AES mode, and then read from the AES control + * register to put the AES engine into receive mode. + */ + piperp->ac->rd_reg(piperp, BB_AES_CTL); + + /* + * Step 4: Write the expanded AES key into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + (unsigned char *)piperp->key[keyIndex].expandedKey, + EXPANDED_KEY_LENGTH); + + /* + * Step 5: Write the AES IV and headers into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, (unsigned char *)aesDataBlob, + AES_BLOB_LENGTH); + + /* + * Step 6: Now, finally, read the unencrypted frame from the + * AES FIFO. Adjust the length so that we don't try + * to read the MIC or the ICV which the AES engine will + * process for us. + */ + length -= MIC_SIZE + ICV_SIZE; + piperp->ac->rd_fifo(piperp, BB_AES_FIFO, skb_put(skb, length), length); + /* + * mac80211 seems to expect there to be a MIC even if the packet + * has already been decrypted. It will drop off what it thinks + * are the extra MIC bytes, so add some extra bytes that it + * can drop off without losing any data. + */ + skb_put(skb, MIC_SIZE); /* add fake MIC */ + + /* + * Step 7: Wait for AES to become ready. + */ + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + dprintk(DWARNING, "2nd AES busy never became ready\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + } + udelay(1); + } + result = ((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_MIC) != 0); + timeout = 500; + while ((piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_FIFO_EMPTY) == 0) { + timeout--; + piperp->ac->rd_reg(piperp, BB_AES_FIFO); + udelay(1); + } +#ifdef WANT_DEBUG + if (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + digi_dbg("AES busy set at end of rx\n"); + } +#endif + spin_unlock_irqrestore(&piperp->aesLock, flags); + + /* pad an extra 8 bytes for the MIC which the H/W strips */ + skb_put(skb, 8); + if (result) { + status->flag |= RX_FLAG_DECRYPTED; + } else { + digi_dbg("Error decrypting packet\n"); + } + } else { + /* + * If we branch here, then we are not able to decrypt the + * packet possibly because we don't have the key, or because + * the packet was encrypted using TKIP. Read the rest of the + * encrypted data. mac80211 will have to decrypt it in software. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, skb_put(skb, length), + length); + } + } else { + /* + * Frame is not encrypted, so just read it. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, skb_put(skb, length), length); + } + +#if WANT_RECEIVE_COUNT_SCROLL + if (((++packetCount) & 1023) == 0) { + printk(KERN_ERR "\n%d recd, tx_start_count = %d, tx_complete_count = %d.\n", + packetCount, piperp->pstats.tx_start_count, + piperp->pstats.tx_complete_count); + } +#endif +#if 0 +receive_packet_exit: +#endif + return result; +} + +/* + * This routine is called when we receive an ACK. This should be after + * we have transmitted a packet. We need to tell the upper layer we + * have a packet by calling ieee80211_tx_status_irqsafe with status + * information. The transmit routine also disables queuing whenever we + * transmit since we can only transmit one packet at a time, so we need + * to reenable to transmit queue too. + */ +static inline void handle_ack(struct piper_priv *piperp, int signal_strength) +{ + if (piper_tx_getqueue(piperp) && piperp->expectingAck) { + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + if ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0) { + piperp->clear_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + piperp->tx_signal_strength = signal_strength; + piperp->tx_result = RECEIVED_ACK; + tasklet_hi_schedule(&piperp->tx_tasklet); + } + } +} + + +/* + * This is the entry point for the receive tasklet. It is executed + * to process receive packets. It allocates an SKB and receives + * the packet into it. + * + * We may be called from the receive ISR if WANT_TO_RECEIVE_FRAMES_IN_ISR + * is set. + */ +void piper_rx_tasklet(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *)context; + + /* + * This while loop will keep executing as long as the H/W indicates there + * are more frames in the FIFO to be received. + */ + + piperp->ps.rxTaskletRunning = true; + + while (((piperp->ac->rd_reg(piperp, BB_GENERAL_STAT) & + BB_GENERAL_STAT_RX_FIFO_EMPTY) == 0) + && (!piperp->ps.poweredDown)) { + struct sk_buff *skb = NULL; + struct ieee80211_rx_status status = { 0 }; + struct rx_frame_hdr header; + unsigned int length = 0; + frameControlFieldType_t fr_ctrl_field; + + /* + * Read and process the H/W header. This header is created by + * the hardware is is not part of the frame. + */ + piperp->ac->rd_fifo(piperp, BB_DATA_FIFO, (u8 *)&header, sizeof(header)); + phy_process_plcp(piperp, &header, &status, &length); + if ((length == 0) || (length > (RX_FIFO_SIZE - 48))) { /* 48 bytes for padding and related stuff */ + unsigned long flags; + + dprintk(DERROR, "bogus frame length (%d)\n", length); + dprintk(DERROR, "0x%08x 0x%08x\n", *(u32 *)&header, *(((u32 *)&header) + 1)); + spin_lock_irqsave(&piperp->aesLock, flags); + reset_fifo(piperp); + spin_unlock_irqrestore(&piperp->aesLock, flags); + continue; + } + + if (length != 0) { + + skb = __dev_alloc_skb(RX_FIFO_SIZE + 100, GFP_ATOMIC); + if (skb == NULL) { + /* Oops. Out of memory. Exit the tasklet */ + dprintk(DERROR, "__dev_alloc_skb failed\n"); + break; + } + + if (receive_packet(piperp, skb, length, &fr_ctrl_field, &status)) { + + if (length >= _80211_HEADER_LENGTH) + { + /* + * If using the Airoha transceiver, then we want to monitor + * the receive signal strength and continuously adjust the + * receive amplifier so that we get the best possible signal + * to noise ratio. + */ + unsigned int rssi = phy_determine_rssi(&header); + + piperp->adjust_max_agc(piperp, rssi, (_80211HeaderType *) skb->data); + } + + if (fr_ctrl_field.type == TYPE_ACK) + handle_ack(piperp, status.signal); + + if ((fr_ctrl_field.type == TYPE_ACK) + || (fr_ctrl_field.type == TYPE_RTS) + || (fr_ctrl_field.type == TYPE_CTS)) { + /* + * Don't pass up RTS, CTS, or ACK frames. They just + * confuse the stack + */ + dev_kfree_skb(skb); + } else { + if (fr_ctrl_field.type == TYPE_BEACON) { + piperp->beacon.weSentLastOne = false; + } + + piper_ps_process_receive_frame(piperp, skb); +#if WANT_TO_RECEIVE_FRAMES_IN_ISR + ieee80211_rx_irqsafe(piperp->hw, skb, &status); +#else + ieee80211_rx(piperp->hw, skb, &status); +#endif + } + } else { + /* Frame failed MIC, so discard it */ + dprintk(DWARNING, "dropping bad frame\n"); + dev_kfree_skb(skb); + } + } + } + + piperp->ps.rxTaskletRunning = false; + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_RX_FIFO); + piper_ps_rx_task_exiting(piperp); +} +EXPORT_SYMBOL_GPL(piper_rx_tasklet); + + + diff --git a/drivers/net/wireless/digiPiper/digiTx.c b/drivers/net/wireless/digiPiper/digiTx.c new file mode 100644 index 000000000000..4bea846a42a4 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiTx.c @@ -0,0 +1,604 @@ +/* + * digiTx.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains the routines that are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/jiffies.h> +#include <linux/timer.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define FRAME_CONTROL_FIELD_OFFSET (sizeof(struct tx_frame_hdr) + sizeof(struct psk_cck_hdr)) +#define AES_TIMEOUT (200) + +#define TX_DEBUG (1) + +#if TX_DEBUG +//static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * Adds an entry into the tx queue. + */ +int piper_tx_enqueue(struct piper_priv *piperp, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb) +{ + unsigned long flags; + int result = -1; + + spin_lock_irqsave(&piperp->tx_queue_lock, flags); + if (NEXT_TX_QUEUE_INDEX(piperp->tx_queue_head) != piperp->tx_queue_tail) { + piperp->tx_queue[piperp->tx_queue_head].skb = skb; + piperp->tx_queue[piperp->tx_queue_head].skb_return_cb = skb_return_cb; + piperp->tx_queue_head = NEXT_TX_QUEUE_INDEX(piperp->tx_queue_head); + piperp->tx_queue_count++; + result = 0; + } + spin_unlock_irqrestore(&piperp->tx_queue_lock, flags); + + return result; +} +EXPORT_SYMBOL_GPL(piper_tx_enqueue); + + +/* + * Returns the skb for the current element. + */ +struct sk_buff *piper_tx_getqueue(struct piper_priv *piperp) +{ + if (piperp->tx_queue_head == piperp->tx_queue_tail) { + return NULL; + } else { + return piperp->tx_queue[piperp->tx_queue_tail].skb; + } +} +EXPORT_SYMBOL_GPL(piper_tx_getqueue); + +/* + * Returns the skb buffer call back for the current element. + */ +static inline tx_skb_return_cb_t piper_tx_getqueue_return_fn(struct piper_priv *piperp) +{ + return piperp->tx_queue[piperp->tx_queue_tail].skb_return_cb; +} + + +/* + * Called to advance the queue tail. + */ +static inline void piper_tx_queue_next(struct piper_priv *piperp) +{ + unsigned long flags; + + spin_lock_irqsave(&piperp->tx_queue_lock, flags); + if (piperp->tx_queue_head != piperp->tx_queue_tail) { + piperp->tx_queue[piperp->tx_queue_tail].skb = NULL; + piperp->tx_queue[piperp->tx_queue_tail].skb_return_cb = NULL; + piperp->tx_queue_tail = NEXT_TX_QUEUE_INDEX(piperp->tx_queue_tail); + piperp->tx_queue_count--; + } + spin_unlock_irqrestore(&piperp->tx_queue_lock, flags); +} + +/* + * Called when we unload to clear any remaining queue entries. + */ +void piper_empty_tx_queue(struct piper_priv *piperp) +{ + while (piper_tx_getqueue(piperp)) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + + ieee80211_tx_info_clear_status(info); + piper_tx_getqueue_return_fn(piperp)(piperp->hw, piper_tx_getqueue(piperp)); + piper_tx_queue_next(piperp); + } +} +EXPORT_SYMBOL_GPL(piper_empty_tx_queue); + + +bool piper_tx_queue_half_full(struct piper_priv *piperp) +{ + return (piperp->tx_queue_count >= (PIPER_TX_QUEUE_SIZE >> 1)); +} +EXPORT_SYMBOL_GPL(piper_tx_queue_half_full); + + + +/* + * This routine writes a frame using H/W AES encryption. + * + * Arguments + * digi context + * buffer pointer to start of frame + * length number of bytes in frame + * + * Return Values + * 0 success + * !0 transmit failed + */ +static int piper_write_aes(struct piper_priv *piperp, unsigned char *buffer, + unsigned int length) +{ + int result; + int timeout = AES_TIMEOUT; + unsigned long spinLockFlags; + + /* + * Step 1: Wait for AES to become ready. + */ + spin_lock_irqsave(&piperp->aesLock, spinLockFlags); + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + /* + * If we come here, then AES busy appears to be stuck high. It should only be + * high for a maximum of about 80 us when it is encrypting a transmit frame. + * Our timeout value is high enough to guarantee that the engine has had enough + * time to complete the transmit. Apparently there is data stuck in the FIFO + * from either a previous transmit or receive. + */ + digi_dbg("write AES, AES busy stuck on\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + /* + * We recover by simply continuing on. Step 3 writes to the AES control + * register. This will reset the AES engine and clear the error condition. + */ + break; + } + udelay(1); + } + + /* + * Step 2: Write the unencrypted part of the frame into the normal + * data FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, buffer, + _80211_HEADER_LENGTH + TX_HEADER_LENGTH + PIPER_EXTIV_SIZE); + + /* + * Step 3: Write to the AES control register. Writing to it puts + * AES H/W engine into transmit mode. We also make sure + * the AES mode is set correctly. + */ + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + + /* + * Step 4: Write the expanded AES key into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + (unsigned char *)piperp->key[piperp->tx_aes_key].expandedKey, + EXPANDED_KEY_LENGTH); + + /* + * Step 5: Write the AES IV and headers into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, (unsigned char *)piperp->tx_aes_blob, + AES_BLOB_LENGTH); + + /* + * Step 6: Now, finally, write the part of the frame that needs to + * be encrypted into the AES FIFO. + */ + result = + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + &buffer[_80211_HEADER_LENGTH + TX_HEADER_LENGTH + PIPER_EXTIV_SIZE], + length - (_80211_HEADER_LENGTH + TX_HEADER_LENGTH + + PIPER_EXTIV_SIZE)); + + spin_unlock_irqrestore(&piperp->aesLock, spinLockFlags); + + return result; +} + +/* + * Determine what bit rate the next retry should be sent at. + * + * The mac80211 library passes us an array of tx bit rates. Each entry + * has a rate index and a limit (max number of retries at that rate). + * We use the rate index to build the H/W transmit header. The limit + * is decremented each time we retry. When it reaches zero, we try the + * next rate in the array. + */ +static struct ieee80211_rate *get_tx_rate(struct piper_priv *piperp, struct ieee80211_tx_info *info) +{ + struct ieee80211_rate *ret = NULL; + + if (piperp->pstats.tx_retry_count[piperp->pstats.tx_retry_index] >= + info->control.rates[piperp->pstats.tx_retry_index].count) { + piperp->pstats.tx_retry_index++; + } + + if (piperp->pstats.tx_retry_index >= IEEE80211_TX_MAX_RATES) { + return NULL; /* don't go beyond the end of the rates array */ + } + + if (piperp->pstats.tx_retry_index == 0) { + ret = ieee80211_get_tx_rate(piperp->hw, info); + } else { + ret = ieee80211_get_alt_retry_rate(piperp->hw, info, piperp->pstats.tx_retry_index - 1); + } + + if (ret != NULL) { + if (piperp->calibrationTxRate) { + ret = piperp->calibrationTxRate; + } + } + + ret = piper_ps_check_rate(piperp, ret); + + return ret; +} + +/* + * This function returns a value for the contention window in microseconds. We + * start with the contention window at CW_MIN and double it everytime we have to + * retry. + */ +static u16 piper_get_cw(struct piper_priv *piperp, bool isFirstTime) +{ + static u16 cw = DEFAULT_CW_MIN; + + if (isFirstTime) { + cw = DEFAULT_CW_MIN; + } else { + cw <<= 1; + if (cw > DEFAULT_CW_MAX) { + cw = DEFAULT_CW_MAX; + } + } + return (cw + (10 * (piperp->rand() & (cw - 1)))) & 0xffff; +} + +/* + * This function will prepend an RTS or CTS to self frame ahead of the current + * TX frame. This is done if the wantRts or wantCts flag is set. The mac80211 + * library determines if either of these flags is set. + * + * The RTS or CTS message is written into the transmit FIFO ahead of the + * data frame. Note that RTS and CTS messages are always sent in the clear + * so we do not have to worry about encryption. + * + * Our caller, the master transmit routine, is responsible for setting the + * transmit hold bit before calling us and clearing it after the data frame + * has been written into the FIFO. This ensures that the RTS/CTS frame is + * not transmitted until after the data frame is ready to go. + * + * Also note that if we are unable to send the RTS/CTS frame, then the H/W + * is smart enough to also about the data frame. So we will not send + * the data frame without the RTS/CTS frame. + */ +static void handle_rts_cts(struct piper_priv *piperp, + struct ieee80211_tx_info *txInfo, unsigned int frameType) +{ + piperp->tx_rts = false; + + if (frameType == TYPE_DATA) { + unsigned int header[2]; + struct ieee80211_rate *rate = NULL; + bool wantCts = (!!(txInfo->control.rates[piperp->pstats.tx_retry_index].flags + & IEEE80211_TX_RC_USE_CTS_PROTECT) + | piperp->tx_cts); + bool wantRts = !!(txInfo->control.rates[piperp->pstats.tx_retry_index].flags + & IEEE80211_TX_RC_USE_RTS_CTS); + + if ((wantRts) || (wantCts)) { + /* + * If we are sending an RTS or a CTS, then get the rate information. + */ + if (piperp->calibrationTxRate) { + rate = piperp->calibrationTxRate; + } else { + rate = ieee80211_get_rts_cts_rate(piperp->hw, txInfo); + } + if (rate == NULL) { + digi_dbg + ("ieee80211_get_rts_cts_rate(digi->hw, txInfo) returned NULL!\n"); + } + } + if ((wantRts) && (rate)) { + /* + * We're sending an RTS, so load it into the FIFO. + */ + struct ieee80211_rts rtsFrame; + + ieee80211_rts_get(piperp->hw, txInfo->control.vif, + piper_tx_getqueue(piperp)->data + TX_HEADER_LENGTH, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, txInfo, + &rtsFrame); + /* + * If we come here, then we need to send an RTS frame ahead of the + * current data frame. + */ + phy_set_plcp((unsigned char *)header, sizeof(struct ieee80211_rts), + rate, 0); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)header, + TX_HEADER_LENGTH); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)&rtsFrame, + sizeof(rtsFrame)); + if (piperp->pstats.tx_total_tetries != 0) { + piperp->pstats.ll_stats.dot11RTSFailureCount++; + } + piperp->tx_rts = true; + } else if ((wantCts) && (rate)) { + /* + * We're sending a CTS, so load it into the FIFO. + */ + struct ieee80211_cts ctsFrame; + + ieee80211_ctstoself_get(piperp->hw, txInfo->control.vif, + piper_tx_getqueue(piperp)->data + TX_HEADER_LENGTH, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, + txInfo, &ctsFrame); + /* + * At the time this code was written, the mac80211 library had + * a bug in the ieee80211_ctstoself_get which caused it to copy + * the wrong MAC address into the cts frame. So we copy the + * right one (ours) in now. + */ + memcpy(piperp->ctsFrame.ra, piperp->hw->wiphy->perm_addr, ETH_ALEN); + + /* + * If we come here, then we need to send a CTS to self frame ahead of the + * current data frame. + */ + phy_set_plcp((unsigned char *)header, sizeof(struct ieee80211_cts), + rate, 0); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)header, + TX_HEADER_LENGTH); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)&ctsFrame, + sizeof(ctsFrame)); + } + } +} + +/* + * This routine is called to report the result of a transmit operation to + * mac80211. It is used for both successful transmissions and failures. + * It sends the result to the stack, removes the current tx frame from the + * queue, and then wakes + * up the transmit queue. + */ +void packet_tx_done(struct piper_priv *piperp, tx_result_t result, + int signal_strength) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + int i; + struct sk_buff *skb; + unsigned long flags; + +#define WANT_TRANSMIT_RESULT (0) +#if WANT_TRANSMIT_RESULT + const char *resultText[] = + { + "RECEIVED_ACK", + "TX_COMPLETE", + "OUT_OF_RETRIES", + "TX_NOT_DONE" + }; +#endif + del_timer_sync(&piperp->tx_timer); + piperp->expectingAck = false; + +#if WANT_TRANSMIT_RESULT + printk(KERN_ERR "Transmit result %s\n", resultText[result]); +#endif + if (piperp->tx_calib_cb) + piperp->tx_calib_cb(piperp); + + if (piper_tx_getqueue(piperp) != NULL) { + skb_pull(piper_tx_getqueue(piperp), TX_HEADER_LENGTH); + + ieee80211_tx_info_clear_status(info); + + /* prepare statistics and pass them to the stack */ + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + info->status.rates[i].count = piperp->pstats.tx_retry_count[i]; + if (info->status.rates[i].count == 0) + info->status.rates[i].idx = -1; + } + + info->status.ack_signal = signal_strength; + info->flags |= (result == RECEIVED_ACK) ? IEEE80211_TX_STAT_ACK : 0; + piperp->pstats.tx_complete_count++; + piperp->pstats.tx_queue.len--; + if (piperp->tx_rts) + piperp->pstats.ll_stats.dot11RTSSuccessCount++; + + skb = piper_tx_getqueue(piperp); + piper_tx_getqueue_return_fn(piperp)(piperp->hw, skb); + + piper_tx_queue_next(piperp); + + if (piper_tx_getqueue(piperp) == NULL) { + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + piperp->tx_tasklet_running = false; + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + if (piperp->ps.allowTransmits) { + ieee80211_wake_queues(piperp->hw); + } else { + /* + * Do not wake up the mac80211 Tx queue if we are trying to power + * down. Make sure we set the stopped_tx_queues flag so that we + * know to restart the queues. + */ + piperp->ps.stopped_tx_queues = true; + } + } else { + if (result == OUT_OF_RETRIES) { + /* + * If we come here, then we are being called from the middle of the + * transmit routine and we have to reschedule the transmit task to + * start dequeueing the next frame. + */ + tasklet_hi_schedule(&piperp->tx_tasklet); + } + } + } else { + digi_dbg("packet_tx_done called with empty queue\n"); + } + + piperp->tx_result = TX_NOT_DONE; +} +EXPORT_SYMBOL_GPL(packet_tx_done); + +/* + * This function is the entry point for the transmit tasklet. It + * is called to transmit frames. It will first be called to transmit + * the frame and then to retry if the original transmit fails. So + * it does both the first transmit and the subsequent retries. + * + * Arguments + * context context information + */ +void piper_tx_tasklet(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *)context; + frameControlFieldType_t *fc; + int err; + + piperp->expectingAck = false; + del_timer_sync(&piperp->tx_timer); + if ((piperp->tx_result == RECEIVED_ACK) || (piperp->tx_result == TX_COMPLETE)) { + /* + * We will come here if the receiver task received an ACK, or if we got + * a tx fifo empty interrupt. In these cases the receiver thread or ISR + * schedule the tx tasklet to handle the event rather than calling + * packet_tx_done directly. + */ + packet_tx_done(piperp, piperp->tx_result, piperp->tx_signal_strength); + } + + + /* + * Clear flags here to cover ACK case. We do not clear the flags in the ACK + * routine since it is possible to receive an ACK after we have started the + * next packet. The appropriate interrupts will be reenabled if we decide + * to retransmit. + */ + piperp->clear_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + + if (piper_tx_getqueue(piperp) != NULL) { + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + struct ieee80211_rate *txRate = get_tx_rate(piperp, txInfo); + + if (txRate != NULL) { + fc = (frameControlFieldType_t *) + &piper_tx_getqueue(piperp)->data[FRAME_CONTROL_FIELD_OFFSET]; + + /* set the retry bit if this is not the first try */ + if (piperp->pstats.tx_retry_count[0] != 0) + fc->retry = 1; + + piperp->ac->wr_reg(piperp, MAC_BACKOFF, + piper_get_cw(piperp, + (piperp->pstats.tx_retry_count[0] == 0)), + op_write); + + /* + * Build the H/W transmit header. The transmit header is rebuilt on each + * retry because it has the TX rate information which may change for + * retries. + */ + phy_set_plcp(piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, + txRate, piperp->use_hw_aes ? 8 : 0); + + /* + * Pause the transmitter so that we don't start transmitting before we + * are ready. + */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_TX_HOLD, op_or); + + handle_rts_cts(piperp, txInfo, fc->type); + + if (piperp->use_hw_aes == true && txInfo->control.hw_key != NULL) { + err = + piper_write_aes(piperp, piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len); + } else { + err = + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len); + } + + /* Clear any pending TX interrupts */ + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, + BB_IRQ_MASK_TX_FIFO_EMPTY | BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT, op_write); + + /* + * Now start the transmitter. + */ + piperp->expectingAck = ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_TX_HOLD, + op_and); + + /* + * Set interrupt flags. Use the timeout interrupt if we expect + * an ACK. Use the FIFO empty interrupt if we do not expect an ACK. + */ + if (txInfo->flags & IEEE80211_TX_CTL_NO_ACK) { + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TX_ABORT); + } else { + /* + * We set up a timer to fire in 1/4 second. We should not need it, but somehow + * we seem to miss a timeout interrupt occasionally. Perhaps we encounter a receive + * overrun which causes the H/W to discard the ACK packet without generating + * a timeout. + */ + piperp->tx_timer.expires = jiffies + (HZ >> 2); + add_timer(&piperp->tx_timer); + + /* + * Also set the IRQ mask to listen for timeouts and TX aborts. We will receive + * an ACK (which is handled by the RX routine) if the TX is successful. + */ + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + } + if ((piperp->pstats.tx_total_tetries != 0) && + ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0)) { + piperp->pstats.ll_stats.dot11ACKFailureCount++; + } + piperp->pstats.tx_retry_count[piperp->pstats.tx_retry_index]++; + piperp->pstats.tx_total_tetries++; + } else { + packet_tx_done(piperp, OUT_OF_RETRIES, 0); + } + } else { + long unsigned int flags; + + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + piperp->tx_tasklet_running = false; + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + } +} +EXPORT_SYMBOL_GPL(piper_tx_tasklet); + + + diff --git a/drivers/net/wireless/digiPiper/mac.h b/drivers/net/wireless/digiPiper/mac.h new file mode 100644 index 000000000000..892a15a8b67c --- /dev/null +++ b/drivers/net/wireless/digiPiper/mac.h @@ -0,0 +1,375 @@ +#ifndef DIGI_MAC_H_ +#define DIGI_MAC_H_ + +enum baseband_control_regs { + BB_VERSION = 0x00, + BB_GENERAL_CTL = 0x04, + BB_GENERAL_STAT = 0x08, + BB_RSSI = 0x0c, + BB_IRQ_MASK = 0x10, + BB_IRQ_STAT = 0x14, + BB_SPI_DATA = 0x18, + BB_SPI_CTRL = 0x1c, + BB_DATA_FIFO = 0x20, + BB_TRACK_CONTROL = 0x28, + BB_CONF_2 = 0x2c, + BB_AES_FIFO = 0x30, + BB_AES_CTL = 0x38, + BB_OUTPUT_CONTROL = 0x3c +}; + +#define BB_VERSION_MASK(v) ((v) & 0xffff) + +#define BB_GENERAL_CTL_RX_EN (1<<0) +#define BB_GENERAL_CTL_ANT_DIV (1<<1) +#define BB_GENERAL_CTL_ANT_SEL (1<<2) +#define BB_GENERAL_CTL_GEN_5GEN (1<<3) // 5 GHz band enable +#define BB_GENERAL_CTL_SH_PRE (1<<4) +#define BB_GENERAL_CTL_RXFIFORST (1<<5) +#define BB_GENERAL_CTL_TXFIFORST (1<<6) +#define BB_GENERAL_CTL_TX_HOLD (1<<7) +#define BB_GENERAL_CTL_BEACON_EN (1<<8) +#define BB_GENERAL_CTL_FW_LOAD_ENABLE (1 << 9) +#define BB_GENERAL_CTL_DSP_LOAD_ENABLE (1 << 10) +#define BB_GENERAL_CTL_MAC_ASSIST_ENABLE (1 << 11) +#define BB_GENERAL_CTL_TX_FIFO_FULL (1<<15) +#define BB_GENERAL_CTL_TX_FIFO_EMPTY (1<<14) +/* TODO: verify max gain value for Piper and Wi9p*/ +#define BB_GENERAL_CTL_MAX_GAIN(g) (((g) & 0x7f)<<16) +#define BB_GENERAL_CTL_PWR_UP (1<<24) +#define BB_GENERAL_CTL_ADC_CLK_EN (1<<25) +#define BB_GENERAL_CTL_BOOT_STAT (1<<28) +#define BB_GENERAL_CTL_CLK_EN (1<<29) +#define BB_GENERAL_CTL_SPI_RST (1<<30) + +#define BB_GENERAL_CTL_MAX_GAIN_MASK (0x007F0000) +#define BB_GENERAL_CTL_DEFAULT_MAX_GAIN_A (0x00790000) +#define BB_GENERAL_CTL_DEFAULT_MAX_GAIN_BG (0x007c0000) + +#if defined(CONFIG_PIPER_WIFI) +#if 0 +#define BB_GENERAL_CTL_INIT (BB_GENERAL_CTL_MAX_GAIN(0x7a) | \ + BB_GENERAL_CTL_PWR_UP | BB_GENERAL_CTL_ADC_CLK_EN | \ + BB_GENERAL_CTL_BOOT_STAT | BB_GENERAL_CTL_CLK_EN) +#define BB_GENERAL_CTL_RESET (BB_GENERAL_CTL_MAX_GAIN(0x7f) | \ + BB_GENERAL_CTL_ADC_CLK_EN | BB_GENERAL_CTL_BOOT_STAT | \ + BB_GENERAL_CTL_SPI_RST) +#else +#define BB_GENERAL_CTL_INIT (BB_GENERAL_CTL_MAX_GAIN(0x7a) + +#define BB_GENERAL_CTL_RESET (BB_GENERAL_CTL_MAX_GAIN(0x7f) | \ + BB_GENERAL_CTL_SPI_RST +#endif +#else +#define BB_GENERAL_CTL_INIT (BB_GENERAL_CTL_MAX_GAIN(0x7a) | \ + BB_GENERAL_CTL_PWR_UP | BB_GENERAL_CTL_ADC_CLK_EN | \ + BB_GENERAL_CTL_BOOT_STAT | BB_GENERAL_CTL_CLK_EN) + +#define BB_GENERAL_CTL_RESET (BB_GENERAL_CTL_MAX_GAIN(0x7f) | \ + BB_GENERAL_CTL_ADC_CLK_EN | BB_GENERAL_CTL_BOOT_STAT | \ + BB_GENERAL_CTL_SPI_RST) +#endif +#define BB_RSSI_LED (1<<8) +#define BB_RSSI_EAS_FIFO_EMPTY (1 << 16) +#define BB_RSSI_EAS_FIFO_FULL (1 << 17) +#define BB_RSSI_EAS_BUSY (1 << 18) +#define BB_RSSI_EAS_MIC (1 << 19) +#define BB_RSSI_ANT_MASK (0xff<<24) +#define BB_RSSI_ANT_NO_DIV_MAP (0x96000000) +#define BB_RSSI_ANT_DIV_MAP (0x1E000000) + +#define BB_GENERAL_STAT_RESET (1<<30) +/* + * STAT_B_EN is a constant that defines a bit in the Wireless Controller FPGA Baseband Control Register + * for General Status, which enables the PSK/CCK receiver baseband circuitry (802.11b receiver). + * STAT_A_EN is a constant that defines a bit in the Wireless Controller FPGA Baseband Control Register + * for General Status, which enables the OFDM receive baseband circuitry (802.11a receiver). + */ + +#define BB_GENERAL_STAT_B_EN 0x10000000 // B EN (PSK/CCK) +#define BB_GENERAL_STAT_A_EN 0x20000000 // A EN (OFDM) +#define BB_GENERAL_STAT_RX_FIFO_EMPTY (1 << 4) +#define BB_GENERAL_STAT_DC_DIS (1 << 24) +#define BB_GENERAL_STAT_SRC_DIS (1 << 16) +#define BB_GENERAL_STAT_SPRD_DIS (1 << 17) +#define BB_GENERAL_STAT_DLL_DIS (1 << 18) +#define TRACK_TX_B_GAIN_MASK 0xff000000 // Mask word for B_TX_GAIN +#define TRACK_TX_B_GAIN_NORMAL 0xA0000000 // normal setting for B_TX_GAIN +#define TRACK_BG_BAND 0x00430000 // Tracking constant for 802.11 b/g frequency band +#define TRACK_CONSTANT_MASK 0x00ff0000 // mask for tracking constant +#define TRACK_4920_4980_A_BAND 0x00210000 // Tracking constant for 802.11 a sub-frequency band +#define TRACK_5150_5350_A_BAND 0x001F0000 // Tracking constant for 802.11 a sub-frequency band +#define TRACK_5470_5725_A_BAND 0x001D0000 // Tracking constant for 802.11 a sub-frequency band +#define TRACK_5725_5825_A_BAND 0x001C0000 // Tracking constant for 802.11 a sub-frequency band + + +#define BB_IRQ_MASK_RX_FIFO (1<<0) +#define BB_IRQ_MASK_TX_FIFO_EMPTY (1<<1) +#define BB_IRQ_MASK_TIMEOUT (1<<2) +#define BB_IRQ_MASK_TX_ABORT (1<<3) +#define BB_IRQ_MASK_TBTT (1<<4) +#define BB_IRQ_MASK_ATIM (1<<5) +#define BB_IRQ_MASK_RX_OVERRUN (1<<6) + +#define BB_AES_CTL_KEY_LOAD (1<<2) +#define BB_AES_CTL_AES_MODE (1<<4) + +enum mac_control_regs { + MAC_STA_ID0 = 0x40, + MAC_STA_ID1 = 0x44, + MAC_BSS_ID0 = 0x48, + MAC_BSS_ID1 = 0x4c, + MAC_SSID_LEN = 0x50, /* OFDM_BRS, PSK_BRS, TX_CTL, SSID_LEN */ + MAC_BACKOFF = 0x54, /* actually 0x56; 2 low order bytes are empty */ + MAC_DTIM_PERIOD = 0x58, + /*MAC_CFP_PERIOD = 0x59,*/ + /*MAC_LISTEN_INTERVAL = 0x5a,*/ + MAC_CFP_ATIM = 0x5c, /* beacon interval, CFP/ATIM duration */ + MAC_STATUS = 0x60, + /*MAC_TXP_TIMING = 0x62,*/ + /*MAC_STATUS = 0x63,*/ + MAC_CTL = 0x64, /* MAC_AES_KEY_DIS (8 bits), MAC_CTL (8 bits) */ + MAC_MEASURE = 0x68, /* actually 0x69 */ + /*MAC_REMAIN_BO = 0x6a,*/ + MAC_BEACON_FILT = 0x6c, /* actally 0x6d */ + /*MAC_BEACON_BO = 0x6e,*/ + MAC_STA2_ID0 = 0xb0, + MAC_STA2_ID1 = 0xb4, + MAC_STA3_ID0 = 0xb8, + MAC_STA3_ID1 = 0xbc, + + MAC_EEPROM_CTL = 0xf0, + MAC_EEPROM_DATA = 0xf8, + + MAC_SSID = 0x80, + + BEACON_FIFO = 0x85, /* dummy value used to select data fifo for beacon load */ +}; + + +#define MAC_SSID_LEN_MASK (0x000000ff) +#define MAC_REVISION_MASK(v) (((v) >> 16) & 0xffff) + +#define MAC_BEACON_INTERVAL_SHIFT (16) +#define MAC_BEACON_INTERVAL_MASK (0xffff0000) + +#define MAC_ATIM_PERIOD_MASK (0x0000ffff) + +#define MAC_LISTEN_INTERVAL_MASK (0x0000ffff) + +#define MAC_DTIM_PERIOD_SHIFT (24) +#define MAC_DTIM_PERIOD_MASK (0xff000000) + +#define MAC_DTIM_CFP_SHIFT (16) +#define MAC_DTIM_CFP_MASK (0x00ff0000) + +#define MAC_OFDM_BRS_MASK (0xff000000) +#define MAC_OFDM_BRS_SHIFT (24) +#define MAC_PSK_BRS_MASK (0x000f0000) +#define MAC_PSK_BRS_SHIFT (16) + +#define MAC_BEACON_BACKOFF_MASK (0x0000ffff) + +#define MAC_BRS_MASK (MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK) + +#define MAC_CTL_TX_REQ (1) +#define MAC_CTL_BEACON_TX (1<<2) +#define MAC_CTL_IBSS (1<<4) +#define MAC_CTL_AES_DISABLE (1<<5) +#define MAC_CTL_MAC_FLTR (1<<6) +#define MAC_CTL_KEY0_DISABLE (1<<8) +#define MAC_CTL_KEY1_DISABLE (1<<9) +#define MAC_CTL_KEY2_DISABLE (1<<10) +#define MAC_CTL_KEY3_DISABLE (1<<11) + +#define MAC_EEPROM_CTL_WAIT_MS 21 + +/* + * RX packets look something like: + * <custom bus protocol header(s)> - protocol-dependent + * <rx_frame_hdr> - 4 bytes + * <plcp; either psk_cck_hdr or ofdm_hdr> - 4 bytes + * <mac header> - dealt with by the mac80211 stack, not the driver + * <data> + * + * TX packets are similar: + * <custom bus protocol header(s)> + * <tx_frame_hdr> - 4 bytes + * <plcp; either psk_cck_hdr or ofdm_hdr> - 4 bytes + * <mac header> + * <data> + */ + + + +// MAC type field values +#define TYPE_ASSOC_REQ 0x00 // Association request +#define TYPE_ASSOC_RESP 0x10 // Association response +#define TYPE_REASSOC_REQ 0x20 // Reassociation request +#define TYPE_REASSOC_RESP 0x30 // Reassociation response +#define TYPE_PROBE_REQ 0x40 // Probe request +#define TYPE_PROBE_RESP 0x50 // Probe response + +#define TYPE_BEACON 0x80 // Beacon +#define TYPE_ATIM 0x90 // Annoucement traffice indication +#define TYPE_DISASSOC 0xa0 // Disassociation +#define TYPE_AUTH 0xb0 // Authentication +#define TYPE_DEAUTH 0xc0 // Deauthentication +#define TYPE_ACTION 0xd0 // Action + +#define TYPE_RTS 0xb4 // Request to send +#define TYPE_CTS 0xc4 // Clear to send +#define TYPE_ACK 0xd4 // Acknowledgement +#define TYPE_PSPOLL 0xa4 // Power Save(PS)-Poll + +#define TYPE_DATA 0x08 // Data +#define TYPE_NULL_DATA 0x48 // Null Data + +struct tx_frame_hdr { +#if 1 + unsigned int modulation_type:8; + unsigned int length:9; + unsigned int pad:15; +#else + uint8_t modulation_type; + __le16 length:9; + unsigned int pad:15; +#endif +} __attribute__((packed)); + + +#define MOD_TYPE_PSKCCK 0x00 +#define MOD_TYPE_OFDM 0xee + +struct psk_cck_hdr { + uint8_t signal; /* x100Kbps */ + uint8_t service; + __le16 length; /* usecs */ +} __attribute__((packed)); + +/* PSK/CCK PLCP service field bits */ +#define PLCP_SERVICE_LOCKED 0x04 /* locked clocks */ +#define PLCP_SERVICE_MODSEL 0x08 /* modulation selection */ +#define PLCP_SERVICE_LENEXT 0x80 /* length extension */ + +struct ofdm_hdr { + unsigned int rate:4; + unsigned int pad_a:1; + unsigned int length:12; /* in bytes */ + unsigned int parity:1; + unsigned int pad_b:14; +} __attribute__((packed)); + + +struct rx_frame_hdr { + uint8_t modulation_type; + unsigned int rssi_variable_gain_attenuator:5; + unsigned int rssi_low_noise_amp:2; + unsigned int antenna:1; + __be16 freq_offset; + union + { + struct psk_cck_hdr psk; + struct ofdm_hdr ofdm; + } mod; +} __attribute__((packed)); + +typedef struct +{ + unsigned type :8; // Type, subtype, version + unsigned toDS :1; // To distribution service (AP) + unsigned fromDS :1; // From distribution service (AP) + unsigned moreFrag :1; // More fragments + unsigned retry :1; // Retransmission + unsigned pwrMgt :1; // Power management state + unsigned moreData :1; // More data buffered + unsigned protectd :1; // Encrypted + unsigned order :1; // Strictly ordered +} frameControlFieldType_t; + +#define WLN_ADDR_SIZE (6) + +typedef unsigned char MacAddr[WLN_ADDR_SIZE]; + + +#define PACKED_H +#define PACKED_F __attribute__ ((packed)) + +typedef PACKED_H struct { + unsigned type :8; // Type, subtype, version +#ifdef BIG_ENDIAN + unsigned order :1; // Strictly ordered + unsigned protected :1; // Encrypted + unsigned moreData :1; // More data buffered + unsigned pwrMgt :1; // Power management state + unsigned retry :1; // Retransmission + unsigned moreFrag :1; // More fragments + unsigned fromDS :1; // From distribution service (AP) + unsigned toDS :1; // To distribution service (AP) +#else + unsigned toDS :1; // To distribution service (AP) + unsigned fromDS :1; // From distribution service (AP) + unsigned moreFrag :1; // More fragments + unsigned retry :1; // Retransmission + unsigned pwrMgt :1; // Power management state + unsigned moreData :1; // More data buffered + unsigned protected :1; // Encrypted + unsigned order :1; // Strictly ordered +#endif +} PACKED_F FrameControl_t; + + +// Sequence control field +// Need to swap bytes on BIG_ENDIAN +typedef PACKED_H struct { +#ifdef BIG_ENDIAN + unsigned seq :12; // Sequence number + unsigned frag :4; // Fragment number +#else + unsigned frag :4; // Fragment number + unsigned seq :12; // Sequence number +#endif +} PACKED_F SeqControl; + +// Union of sequence control types +typedef PACKED_H union { + SeqControl sq; // Sequence control fields + unsigned short sq16; // Sequence control as 16-bit int (needs byte swap) +} PACKED_F SeqControlU; + + + +typedef PACKED_H struct { + FrameControl_t fc; // Frame control + unsigned short duration; // Duration/ID (needs byte swap) + MacAddr addr1; // Address 1 + MacAddr addr2; // Address 2 + MacAddr addr3; // Address 3 + SeqControlU squ; // Sequence control fields +} PACKED_F _80211HeaderType; + +typedef PACKED_H struct { + FrameControl_t fc; // Frame control + unsigned short aid; // association identifier + MacAddr addr1; // Address 1 + MacAddr addr2; // Address 2 +} PACKED_F _80211PSPollType; + +#define _80211_HEADER_LENGTH (sizeof(_80211HeaderType)) +#define TX_HEADER_LENGTH (sizeof(struct ofdm_hdr) + sizeof(struct tx_frame_hdr)) +/* FIFO sizes in bytes */ +#define TX_FIFO_SIZE 1792 +#define RX_FIFO_SIZE 2048 + +#define RATE_MASK_BASIC 0x0153 +#define RATE_MASK_OFDM 0x0ff0 +#define RATE_MASK_PSK_CCK 0x000f + +#define BEACON_INT 100 /* in TU */ + + +#define DEFAULT_CW_MIN 32 // Min contention window size +#define DEFAULT_CW_MAX 1024 // Max contention window size + +#define ASLOT_TIME 20 +#endif diff --git a/drivers/net/wireless/digiPiper/phy.c b/drivers/net/wireless/digiPiper/phy.c new file mode 100644 index 000000000000..5a6c53638e81 --- /dev/null +++ b/drivers/net/wireless/digiPiper/phy.c @@ -0,0 +1,269 @@ +/* + * Linux device driver for Digi's WiWave WLAN card + * + * Copyright © 2008 Digi International, Inc + * + * Author: Andres Salomon <dilinger@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <net/mac80211.h> + +#include "pipermain.h" +#include "mac.h" +#include "airoha.h" +#include "phy.h" + +#define PHY_DEBUG (0) + +#if PHY_DEBUG +static int dlevel = DVERBOSE; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +#define NUMBER_OF_WORD32(x) ((x + 3) >> 2) + +static int is_ofdm_rate(int rate) +{ + return (rate % 3) == 0; +} + +void phy_set_plcp(unsigned char *frame, unsigned length, struct ieee80211_rate *rate, int aes_len) +{ + int ofdm = is_ofdm_rate(rate->bitrate); + int plcp_len = length + FCS_LEN + aes_len; + struct tx_frame_hdr *hdr; + + if (ofdm) { + /* OFDM header */ + struct ofdm_hdr *ofdm; + + ofdm = (struct ofdm_hdr *) &frame[sizeof(struct tx_frame_hdr)]; + memset(ofdm, 0, sizeof(*ofdm)); + ofdm->rate = rate->hw_value; + ofdm->length = cpu_to_le16(plcp_len); + } else { + /* PSK/CCK header */ + struct psk_cck_hdr *pskcck; + int us_len; + + pskcck = (struct psk_cck_hdr *) &frame[sizeof(struct tx_frame_hdr)]; + pskcck->signal = rate->bitrate; + pskcck->service = PLCP_SERVICE_LOCKED; + + /* convert length from bytes to usecs */ + switch (rate->bitrate) { + case 10: + us_len = plcp_len * 8; + break; + case 20: + us_len = plcp_len * 4; + break; + case 55: + us_len = (16 * plcp_len + 10) / 11; + break; + case 110: + us_len = (8 * plcp_len + 10) / 11; + + /* set length extension bit if needed */ + dprintk(DALL, "us_len = %d, plcp_len = %d, (11 * us_len) = %d, \ + (11 * us_len) / 8 = %d\n", us_len, plcp_len, + (11 * us_len), (11 * us_len) / 8); + + if ((11 * us_len) / 8 > plcp_len) { + pskcck->service |= PLCP_SERVICE_LENEXT; + dprintk(DALL, "Set PLCP_SERVICE_LENEXT, \ + pskcck->service = 0x%4.4X\n", pskcck->service); + } else { + dprintk(DALL, "Did not set PLCP_SERVICE_LENEXT, \ + pskcck->service = 0x%4.4X\n", pskcck->service); + } + break; + default: + digi_dbg("rate = %p, rate->bitrate%d\n", rate, rate->bitrate); + WARN_ON(1); + us_len = 0; + } + + pskcck->length = cpu_to_le16(us_len); + + dprintk(DALL, "pskcck .length = %d, signal = %d, service = %d\n", + pskcck->length, pskcck->signal, pskcck->service); + dprintk(DALL, "rate->bitrate=%x (@%dM), pckcck->length=%d\n", + rate->bitrate, rate->bitrate/10, pskcck->length); + } + + hdr = (struct tx_frame_hdr *) frame; + hdr->pad = 0; + hdr->length = NUMBER_OF_WORD32((length + aes_len + TX_HEADER_LENGTH)); + hdr->modulation_type = ofdm ? MOD_TYPE_OFDM : MOD_TYPE_PSKCCK; + + dprintk(DVVERBOSE, "frame hdr .length = %d, .modulation_type = %d\n", + hdr->length, hdr->modulation_type); + + dprintk(DVERBOSE, "TX: %d byte %s packet @ %dmbit\n", length, + ofdm ? "OFDM" : "PSK/CCK", rate->bitrate/10); +} +EXPORT_SYMBOL_GPL(phy_set_plcp); + + +static int get_signal(struct rx_frame_hdr *hdr, enum ieee80211_band rf_band, int transceiver) +{ + int gain; + int signal; + + if (transceiver == RF_AIROHA_2236) { + const u8 lnaTable_al2236[] = + { + 0, 0, 20, 36 + }; + + // Map high gain values to dbm + const signed char gainTable_al2236[] = + { + -85, -85, -88, -88, -92 + }; + // Convert received signal strength to dbm + gain = lnaTable_al2236[hdr->rssi_low_noise_amp] + 2*hdr->rssi_variable_gain_attenuator; + if (gain > 92) + signal = -96; + else if (gain > 87) + signal = gainTable_al2236[gain - 88]; + else + signal = 4 - gain; + } else { + static const u8 lnaTable_al7230_24ghz[] = + { + 0, 0, 18, 42 + }; + static const u8 lnaTable_al7230_50ghz[] = + { + 0, 0, 17, 37 + }; + /* Convert received signal strength to dbm for RF_AIROHA_7230 */ + if (rf_band == IEEE80211_BAND_2GHZ) { + gain = lnaTable_al7230_24ghz[hdr->rssi_low_noise_amp] + 2*hdr->rssi_variable_gain_attenuator; + signal = 2 - gain; + } else { + gain = lnaTable_al7230_50ghz[hdr->rssi_low_noise_amp] + 2*hdr->rssi_variable_gain_attenuator; + signal = -5 - gain; + } + } + + return signal; +} + + + + +/* FIXME, following limtis should depend on the platform */ +#define PIPER_MAX_SIGNAL_DBM (-30) +#define PIPER_MIN_SIGNAL_DBM (-90) + +static int calculate_link_quality(int signal) +{ + int quality; + + if (signal < PIPER_MIN_SIGNAL_DBM) + quality = 0; + else if (signal > PIPER_MAX_SIGNAL_DBM) + quality = 100; + else + quality = (signal - PIPER_MIN_SIGNAL_DBM) * 100 / + (PIPER_MAX_SIGNAL_DBM - PIPER_MIN_SIGNAL_DBM); + + dprintk(DVERBOSE, "signal: %d, quality: %d/100\n", signal, quality); + + return quality; +} + +unsigned int phy_determine_rssi(struct rx_frame_hdr *hdr) +{ + return (hdr->rssi_low_noise_amp << 5) | hdr->rssi_variable_gain_attenuator; +} +EXPORT_SYMBOL_GPL(phy_determine_rssi); + + + +void phy_process_plcp(struct piper_priv *piper, struct rx_frame_hdr *hdr, + struct ieee80211_rx_status *status, unsigned int *length) +{ + unsigned rate, i; + struct digi_rf_ops *rf = piper->rf; + + memset(status, 0, sizeof(*status)); + status->band = piper->rf->getBand(piper->channel); + status->signal = get_signal(hdr, status->band, piper->pdata->rf_transceiver); + status->antenna = hdr->antenna; + status->freq = piper->rf->getFrequency(piper->channel); + status->qual = calculate_link_quality(status->signal); + + if (hdr->modulation_type == MOD_TYPE_OFDM) { + /* OFDM */ + struct ofdm_hdr *ofdm = &hdr->mod.ofdm; + const int ofdm_rate[] = { + 480, 240, 120, 60, 540, 360, 180, 90 + }; + + rate = ofdm_rate[ofdm->rate & 0x7]; + *length = le16_to_cpu(ofdm->length); + dprintk(DVVERBOSE, "%d byte OFDM packet @ %dmbit\n", + *length, rate/10); + } else { + /* PSK/CCK */ + struct psk_cck_hdr *pskcck = &hdr->mod.psk; + + rate = pskcck->signal; + + *length = le16_to_cpu(pskcck->length); + switch (rate) { + case 10: + *length /= 8; + break; + case 20: + *length /= 4; + break; + case 55: + *length = (11 * (*length)) / 16; + break; + case 110: + *length = (11 * (*length)) / 8; + if (pskcck->service & PLCP_SERVICE_LENEXT) + (*length)--; + break; + default: + /* WARN_ON(1); This happens to often for us to generate that long error message */ + *length = 0; + } + + dprintk(DVVERBOSE, "%d byte PSK/CCK packet @ %dmbit\n", + *length, rate/10); + } + + /* match rate with the list of bitrates that we supplied the stack */ + for (i = 0; i < rf->bands[status->band].n_bitrates; i++) { + if (rf->bands[status->band].bitrates[i].bitrate == rate) + break; + } + + if (i != rf->bands[status->band].n_bitrates) + status->rate_idx = i; + else { + *length = 0; + digi_dbg(PIPER_DRIVER_NAME + ": couldn't find bitrate index for %d?\n", + rate); + status->flag |= RX_FLAG_FAILED_PLCP_CRC; + return; + } +} +EXPORT_SYMBOL_GPL(phy_process_plcp); diff --git a/drivers/net/wireless/digiPiper/phy.h b/drivers/net/wireless/digiPiper/phy.h new file mode 100644 index 000000000000..96500ab92e3e --- /dev/null +++ b/drivers/net/wireless/digiPiper/phy.h @@ -0,0 +1,28 @@ +/* + * Linux device driver for Digi's WiWave WLAN card + * + * Copyright © 2008 Digi International, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef phy_h_ +#define phy_h_ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <net/mac80211.h> + + +#include "pipermain.h" +#include "mac.h" + +void phy_set_plcp(unsigned char *frame, unsigned length, struct ieee80211_rate *rate, int aes_len); +void phy_process_plcp(struct piper_priv *piper, struct rx_frame_hdr *hdr, + struct ieee80211_rx_status *status, unsigned int *length); +unsigned int phy_determine_rssi(struct rx_frame_hdr *hdr); + +#endif diff --git a/drivers/net/wireless/digiPiper/piper.c b/drivers/net/wireless/digiPiper/piper.c new file mode 100644 index 000000000000..a3c24720dd11 --- /dev/null +++ b/drivers/net/wireless/digiPiper/piper.c @@ -0,0 +1,1032 @@ +/* + * piper.c + * + * Copyright (C) 2008 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <net/mac80211.h> +#include <linux/usb.h> +#include <linux/kthread.h> +#include <linux/platform_device.h> +#include <asm/gpio.h> +#include <linux/timer.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "airoha.h" +#include "airohaCalibration.h" +#include "piperDsp.h" +#include "piperMacAssist.h" +#include "digiPs.h" + +#define WANT_AIROHA_CALIBRATION (1) +#define WANT_DEBUG_COMMANDS (1) + + +static void piper_clear_irq_mask(struct piper_priv *piperp, unsigned int bits) +{ + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, ~bits, op_and); +} + +static void piper_set_irq_mask(struct piper_priv *piperp, unsigned int bits) +{ + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, bits, op_or); +} + +/* Generate a random number */ +static int local_rand(void) +{ + static unsigned long next = 1; + + /* RAND_MAX assumed to be 32767 */ + next = next * 1103515245 + 12345; + return((unsigned)(next/65536) % 32768); +} + +/* + * Load the MAC Assist firmware into the chip. This is done by setting a bit + * in the control register to enable MAC Assist firmware download, and then + * writing the firmware into the data FIFO. + */ +void piper_load_mac_firmware(struct piper_priv *piperp) +{ + unsigned int i; + + printk(KERN_DEBUG PIPER_DRIVER_NAME ": loading MAC Assist firmware\n"); + + /* Zero out MAC assist SRAM (put into known state before enabling MAC assist) */ + for (i = 0; i < 0x100; i += 4) + piperp->ac->wr_reg(piperp, i, 0, op_write); + + /* Enable download the MAC Assist program RAM */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_FW_LOAD_ENABLE, op_or); + + /* load MAC Assist data */ + for (i = 0; i < piper_macassist_data_len; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, piper_wifi_macassist_ucode[i], + op_write); + + /* disable MAC Assist download */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_FW_LOAD_ENABLE, op_and); +} + +/* + * Load the DSP firmware into the chip. This is done by setting a bit + * in the control register to enable DSP firmware download, and then + * writing the firmware into the data FIFO. + */ +void piper_load_dsp_firmware(struct piper_priv *piperp) +{ + unsigned int i; + + printk(KERN_DEBUG PIPER_DRIVER_NAME ": loading DSP firmware\n"); + + /* Enable load of DSP firmware */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_DSP_LOAD_ENABLE, op_or); + + /* load DSP data */ + for (i = 0; i < piper_dsp_data_len; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, piper_wifi_dsp_ucode[i], + op_write); + + /* Disable load of DSP firmware */ + udelay(10); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_DSP_LOAD_ENABLE, op_and); + + /* Let her rip */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_MAC_ASSIST_ENABLE, op_or); +} + + +/* + * This routine corrects a bug in the Piper chip where internal clocks would + * be out of sync with each other and cause the chip to generate noise spikes. + * This problem should be fixed in the next chip (Chopper). + * + * I'm not sure exactly what this code is doing. It comes straight from the + * guy who designed the chip. + */ +int piper_spike_suppression(struct piper_priv *piperp, bool retry) +{ + int timeout1 = 300, timeout2 = 300; + int ret = 0; + + /* + * Initial timing measurement to avoid spike + * The new "magic" value is 0x63 at address 0xA62. Bit-0 indicates the + * timing measurement is complete. Bit-1 indicates that a second timing + * measurment was performed. The upper nibble is the timing measurement + * value. This code should eliminate the possibility of spikes at the + * beginning of all PSK/CCK frames and eliminate the spikes at the end of + * all PSK (1M, 2M) frames. + */ + + /* reset the timing value */ + piperp->ac->wr_reg(piperp, MAC_STATUS, 0xffff00ff, op_and); + + while ((piperp->ac->rd_reg(piperp, MAC_STATUS) & 0x0000ff00) != 0x00006300) { + + /* reset the timing value */ + piperp->ac->wr_reg(piperp, MAC_STATUS, 0xffff00ff, op_and); + + /* issue WiFi soft reset */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, 0x40000000, op_write); + + /* Set TX_ON Low */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0xffffff3f, op_and); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x00000080, op_or); + + /* Set PA_2G Low */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0xfffff0ff, op_and); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x00000a00, op_or); + + /* Set RX_ON low */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0xcfffffff, op_and); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x20000000, op_or); + + /* start the WiFi mac & dsp */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720820, op_write); + timeout1 = 500; + + /* Wait for timing measurement to finish */ + while ((piperp->ac->rd_reg(piperp, MAC_STATUS) & 0x0000ff00) != 0x00000100) { + udelay(2); + timeout1--; + if (!timeout1) + break; + } + + timeout2--; + if (!timeout2) { + ret = -EIO; + break; + } + + if (!retry) { + ret = -EIO; + break; + } + } + + /* Set TX_ON/RXHP_ON and RX to normal wifi, restore the reset value to HW_OUT_CTRL */ + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x1, op_write); + + return ret; +} +EXPORT_SYMBOL_GPL(piper_spike_suppression); + +void piper_reset_mac(struct piper_priv *piperp) +{ + int i; + + /* set the TX-hold bit */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720080, op_write); + + /* clear the TX-FIFO memory */ + for (i = 0; i < 448; i++) + piperp->ac->wr_reg(piperp, BB_DATA_FIFO, 0, op_write); + + /* reset the TX-FIFO */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x377200C0, op_write); + + /* release the TX-hold and reset */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, 0x37720000, op_write); + +/* iowrite32(ioread32(piperp->vbase + MAC_STATUS) & ~0x40000000, + piperp->vbase + BB_GENERAL_STAT);*/ + mdelay(1); +} + +/* + * Load the MAC address into the chip. Use the value stored in the + * environment, if there is one, otherwise use the default value. + */ +void piper_set_macaddr(struct piper_priv *piperp) +{ + /* Default MAC Addr used if the nvram parameters are corrupted */ + u8 mac[6] = {0x00, 0x04, 0xf3, 0x11, 0x43, 0x35}; + u8 *pmac = piperp->pdata->macaddr; + int i; + static bool firstTime = true; + + for (i = 0; i < 6; i++) { + if (*(pmac + i) != 0xff) + break; + if (i == 5) { + /* There is a problem with the parameters, use default */ + if (firstTime) { + printk(KERN_INFO PIPER_DRIVER_NAME + ": invalid mac address, using default\n"); + } + memcpy(piperp->pdata->macaddr, mac, sizeof(piperp->pdata->macaddr)); + } + } + + firstTime = false; + memcpy(piperp->hw->wiphy->perm_addr, piperp->pdata->macaddr, + sizeof(piperp->hw->wiphy->perm_addr)); + + /* configure ethernet address */ + piperp->ac->wr_reg(piperp, MAC_STA_ID0, *(pmac + 3) | *(pmac + 2) << 8 | + *(pmac + 1) << 16 | *(pmac + 0) << 24, op_write); + piperp->ac->wr_reg(piperp, MAC_STA_ID1, *(pmac + 5) << 16 | *(pmac + 4) << 24, + op_write); +} +EXPORT_SYMBOL_GPL(piper_set_macaddr); + + +/* Configure the H/W with the antenna settings */ +static int piper_set_antenna(struct piper_priv *piperp, enum antenna_select sel) +{ + if (sel == ANTENNA_BOTH) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_ANT_DIV, op_or); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_ANT_SEL, op_and); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_ANT_DIV, op_and); + /* select the antenna if !diversity */ + if (sel == ANTENNA_1) + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_ANT_SEL, op_and); + else + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_ANT_SEL, op_or); + } + + /* select which antenna to transmit on */ + piperp->ac->wr_reg(piperp, BB_RSSI, ~BB_RSSI_ANT_MASK, op_and); + if (sel == ANTENNA_BOTH) + piperp->ac->wr_reg(piperp, BB_RSSI, BB_RSSI_ANT_DIV_MAP, op_or); + else + piperp->ac->wr_reg(piperp, BB_RSSI, BB_RSSI_ANT_NO_DIV_MAP, op_or); + + return 0; +} + +/* + * Compute a beacon backoff time as described in section 11.1.2.2 of 802.11 spec. + * + */ +static u16 get_next_beacon_backoff(void) +{ +#define MAX_BEACON_BACKOFF (2 * ASLOT_TIME * DEFAULT_CW_MIN) + + /* + * We shift the result of local_rand() by 4 bits because the notes + * for the algorithm say that we shouldn't rely on the last few + * bits being random. Other than that, we just take the random + * value and make sure it is less than MAX_BEACON_BACKOFF. + */ + return (local_rand() >> 4) % MAX_BEACON_BACKOFF; +} + +static int load_beacon(struct piper_priv *digi, unsigned char *buffer, + unsigned int length) +{ + return digi->ac->wr_fifo(digi, BEACON_FIFO, buffer, length); +} + +static int piper_init_rx_tx(struct piper_priv *piperp) +{ + tasklet_init(&piperp->rx_tasklet, piper_rx_tasklet, (unsigned long)piperp); + tasklet_disable(&piperp->rx_tasklet); + piperp->expectingAck = false; + + spin_lock_init(&piperp->tx_tasklet_lock); + spin_lock_init(&piperp->tx_queue_lock); + piperp->tx_tasklet_running = false; + memset(&piperp->tx_queue, 0, sizeof(piperp->tx_queue)); + piperp->tx_queue_head = 0; + piperp->tx_queue_tail = 0; + piperp->tx_queue_count = 0; + tasklet_init(&piperp->tx_tasklet, piper_tx_tasklet, (unsigned long)piperp); + tasklet_disable(&piperp->tx_tasklet); + + return 0; +} + +static void piper_free_rx_tx(struct piper_priv *piperp) +{ + tasklet_disable(&piperp->rx_tasklet); + tasklet_kill(&piperp->rx_tasklet); + tasklet_disable(&piperp->tx_tasklet); + tasklet_kill(&piperp->tx_tasklet); + piper_empty_tx_queue(piperp); +} + +/* + * This function sets the tracking control according to a channel's + * frequency. + */ +static int piper_set_tracking_constant(struct piper_priv *piperp, unsigned megahertz) +{ + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, ~TRACK_CONSTANT_MASK, op_and); + if (megahertz < 4920) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + } + else if (megahertz <= 4980) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_4920_4980_A_BAND, op_or); + } + else if (megahertz <= 5350) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5150_5350_A_BAND, op_or); + } + else if (megahertz <= 5725) + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5470_5725_A_BAND, op_or); + } + else + { + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5725_5825_A_BAND, op_or); + } + + return 0; +} + +/* + * This function is called to set the value of the B_TX_GAIN field of the + * HW_CONF1 mac register. This register must be set to different values depending + * on the H/W revision of the board due to changes in the board design. + */ +static unsigned int get_b_tx_gain(struct piper_priv *piperp) +{ + u16 platform = piperp->pdata->wcd.header.hw_platform & WCD_PLATFORM_MASK; + u16 hw_revision = piperp->pdata->wcd.header.hw_platform & WCD_HW_REV_MASK; + unsigned int tx_gain = 0; + + switch (platform) { + case WCD_CCW9P_PLATFORM: + tx_gain = TRACK_TX_B_GAIN_NORMAL; + break; + case WCD_CCW9M_PLATFORM: + switch (hw_revision) { + case WCD_HW_REV_PROTOTYPE: + case WCD_HW_REV_PILOT: + tx_gain = 0xc0000000; + break; + case WCD_HW_REV_A: + default: + tx_gain = 0x90000000; + break; + } + break; + } + return tx_gain; +} + + +static int piper_init_hw(struct piper_priv *piperp, enum ieee80211_band band) +{ + int ret = 0; + + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_INIT, op_write); + + /* Initialize baseband general control register for the specific transceiver */ + if (piperp->pdata->rf_transceiver == RF_AIROHA_7230) { + if (band == IEEE80211_BAND_2GHZ) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, GEN_INIT_AIROHA_24GHZ, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xff00ffff, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + digi_dbg("piper_init_hw Initialized for band B / BG\n"); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, GEN_INIT_AIROHA_50GHZ, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xff00ffff, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_5150_5350_A_BAND, op_or); + digi_dbg("piper_init_hw Initialized for band A\n"); + } + piperp->ac->wr_reg(piperp, BB_CONF_2, 0x09325ad4, op_write); + /* Initialize the SPI word length */ + piperp->ac->wr_reg(piperp, BB_SPI_CTRL, SPI_INIT_AIROHA, op_write); + } else if (piperp->pdata->rf_transceiver == RF_AIROHA_2236) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, GEN_INIT_AIROHA_24GHZ, op_write); + piperp->ac->wr_reg(piperp, BB_CONF_2, 0x09325ad4, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xff00ffff, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, TRACK_BG_BAND, op_or); + /* Initialize the SPI word length */ + piperp->ac->wr_reg(piperp, BB_SPI_CTRL, SPI_INIT_AIROHA2236, op_write); + } else { + printk(KERN_WARNING PIPER_DRIVER_NAME ": undefined rf transceiver!\n"); + return -EINVAL; + } + + /* + *Clear the Intretupt Mask Register before enabling external intretupts. + * Also clear out any status bits in the Intretupt Status Register. + */ + piperp->ac->wr_reg(piperp, BB_IRQ_MASK, 0, op_write); + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, 0xff, op_write); + + /* + * If this firmware supports additional MAC addresses. + */ + if (((piperp->ac->rd_reg(piperp, MAC_STATUS) >> 16) & 0xff) >= 8) { + /* Disable additional addresses to start with */ + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_MAC_FLTR, op_and); + piperp->ac->wr_reg(piperp, MAC_STA2_ID0, 0, op_write); + piperp->ac->wr_reg(piperp, MAC_STA2_ID1, 0, op_write); + piperp->ac->wr_reg(piperp, MAC_STA3_ID0, 0, op_write); + piperp->ac->wr_reg(piperp, MAC_STA3_ID1, 0, op_write); + } + /* TODO: Set this register programatically */ + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, 0x0, op_write); + + /* + * Note that antenna diversity will be set by hw_start, which is the + * caller of this function. + */ + + /* reset RX and TX FIFOs */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_RXFIFORST + | BB_GENERAL_CTL_TXFIFORST, op_or); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~(BB_GENERAL_CTL_RXFIFORST + | BB_GENERAL_CTL_TXFIFORST), op_and); + + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, 0xC043002C, op_write); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, ~TRACK_TX_B_GAIN_MASK, op_and); + piperp->ac->wr_reg(piperp, BB_TRACK_CONTROL, get_b_tx_gain(piperp), op_or); + + /* Initialize RF transceiver */ + piperp->rf->init(piperp->hw, band); + piperp->ac->wr_reg(piperp, BB_OUTPUT_CONTROL, 0x04000001, op_or); + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, 0x0, op_write); + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, ~(BB_GENERAL_STAT_DC_DIS + | BB_GENERAL_STAT_SPRD_DIS), op_and); + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, ~(BB_GENERAL_STAT_SRC_DIS + | BB_GENERAL_STAT_DLL_DIS), op_and); + + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, (MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK), + op_write); + + /* + * Set BSSID to the broadcast address so that we receive all packets. The stack + * will set a real BSSID when it's ready. + */ + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, 0xffffffff, op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, 0xffffffff, op_write); + + piperp->ps.poweredDown = false; + +#if WANT_AIROHA_CALIBRATION + digi_dbg("Calling digiWifiInitCalibration()\n"); + digiWifiInitCalibration(piperp); +#endif + + return ret; +} + +static int piper_deinit_hw(struct piper_priv *piperp) +{ + int ret = 0; +#if WANT_AIROHA_CALIBRATION + digi_dbg("Calling digiWifiDeInitCalibration()\n"); + digiWifiDeInitCalibration(piperp); +#endif + + return ret; +} + + +static void adjust_max_agc(struct piper_priv *piperp, unsigned int rssi, _80211HeaderType *header) +{ +#define LOWEST_MAXAGC_AL2236 0x76 +#define HIGHEST_MAXAGC_AL2236 0x7B +#define HIGHEST_MAXAGC_AL7230_24GHZ 0x7c +#define LOWEST_MAXAGC_AL7230_24GHZ 0x76 +#define HIGHEST_MAXAGC_AL7230_50GHZ 0x79 +#define LOWEST_MAXAGC_AL7230_50GHZ 0x73 +#define RSSI_AVG_COUNT 8 + + unsigned char maxgain = 0; + static unsigned char lowest = 0, highest = 0; + static int k=0, j=0, i =0, tempRssi=0; + static unsigned int savedRSSI[RSSI_AVG_COUNT]; /****/ + + savedRSSI[k % RSSI_AVG_COUNT] = rssi; + if ( (piperp->pdata->rf_transceiver == RF_AIROHA_2236) + || (piperp->pdata->rf_transceiver == RF_AIROHA_7230)) { + + if (piperp->pdata->rf_transceiver == RF_AIROHA_2236) + { + lowest = LOWEST_MAXAGC_AL2236; + highest = HIGHEST_MAXAGC_AL2236; + } + else + { + + if (piperp->rf->getBand(piperp->channel) == IEEE80211_BAND_5GHZ) { + highest = HIGHEST_MAXAGC_AL7230_50GHZ; + lowest = LOWEST_MAXAGC_AL7230_50GHZ; + } + else { + highest = HIGHEST_MAXAGC_AL7230_24GHZ; + lowest = LOWEST_MAXAGC_AL7230_24GHZ; + } + } + + if (piperp->areWeAssociated) + { + + if ( (piperp->if_type == NL80211_IFTYPE_ADHOC) + || (piperp->if_type == NL80211_IFTYPE_MESH_POINT)) + { + //Monitor the receive signal strength from Ad-hoc network + if (memcmp (piperp->bssid, header->addr3, sizeof(piperp->bssid)) == 0) + { + /* we don't do avareging on all the signals here because it may come from different + * unit in that Ad-hoc network. Instead, we do avareging on the signals with higher rssi + */ + + if ((rssi + 4) > lowest) + { + k++; + tempRssi += rssi; + + if (k >= RSSI_AVG_COUNT) + { + maxgain = (((tempRssi/k) + 4) > highest)? highest : ((tempRssi/k) + 4) ; + k = 0; + tempRssi = 0; + i =0; + } + } + else + { + i++; + if (i >= (RSSI_AVG_COUNT*4)) + { + maxgain = lowest; + i = 0; + } + + } + } + } + else + { + //Monitor the receive signal strength from the frames we received from the associated AP + if (memcmp (piperp->bssid, header->addr2, sizeof(piperp->bssid)) == 0) + { + //averaging all the signals because they come from the same AP + k++; + tempRssi += rssi; + + if (k >= RSSI_AVG_COUNT*2) + { + if (((tempRssi/k) + 4) > lowest) + maxgain = (((tempRssi/k) + 4) > highest)? highest : ((tempRssi/k) + 4) ; + else + maxgain = lowest; + + k = 0; + tempRssi = 0; + } + } + } + j = 0; + } + else + { + j++; + if (j >= (RSSI_AVG_COUNT*4)) + { + maxgain = highest; + j = 0; + } + k = 0; + tempRssi = 0; + } + + if( (maxgain != 0) + && (maxgain != ((piperp->ac->rd_reg(piperp, BB_GENERAL_CTL) & BB_GENERAL_CTL_MAX_GAIN_MASK) >> 16))) + { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_MAX_GAIN_MASK, op_and); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, (maxgain << 16) & BB_GENERAL_CTL_MAX_GAIN_MASK, op_or); + } + } +} + + +/* Make sure all keys are disabled when we start */ +static void piper_init_keys(struct piper_priv *piperp) +{ + unsigned int i; + + for (i = 0; i < PIPER_MAX_KEYS; i++) + piperp->key[i].valid = false; + + piperp->aes_key_count = 0; +} + +static void tx_timer_timeout(unsigned long arg) +{ + struct piper_priv *piperp = (struct piper_priv *) arg; + + tasklet_hi_schedule(&piperp->tx_tasklet); +} + +/* sysfs entries to get/set antenna mode */ +static ssize_t show_antenna_sel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", piperp->antenna == ANTENNA_BOTH ? "diversity" : + piperp->antenna == ANTENNA_1 ? "primary" : "secondary"); +} + +static ssize_t store_antenna_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + enum antenna_select ant; + size_t len = count; + int ret; + + ant = piperp->antenna; + + if (buf[count - 1] == '\n') + len--; + + /* TODO check also string length */ + if (!strncmp("diversity", buf, len)) + ant = ANTENNA_BOTH; + else if (!strncmp("primary", buf, len)) + ant = ANTENNA_1; + else if (!strncmp("secondary", buf, len)) + ant = ANTENNA_2; + + if (ant != piperp->antenna) { + if ((ret = piperp->set_antenna(piperp, ant)) != 0) { + printk(KERN_WARNING PIPER_DRIVER_NAME + ": error setting antenna to %d (err: %d)\n", ant, ret); + } else + piperp->antenna = ant; + } + + return count; +} +static DEVICE_ATTR(antenna_sel, S_IWUSR | S_IRUGO, show_antenna_sel, store_antenna_sel); + +static ssize_t show_power_duty(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", piperp->power_duty); +} + +static ssize_t store_power_duty(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ +#define MINIMUM_DUTY_CYCLE (33) +#define LIMIT_LINEAL_DUTY_CYCLE (75) + struct piper_priv *piperp = dev_get_drvdata(dev); + int pw_duty; + ssize_t ret = -EINVAL; + + ret = sscanf(buf, "%d\n", &pw_duty); + if (ret > 0) { + if (pw_duty < MINIMUM_DUTY_CYCLE) { + piperp->power_duty = MINIMUM_DUTY_CYCLE; + } else if (pw_duty > LIMIT_LINEAL_DUTY_CYCLE && pw_duty < 100) { + piperp->power_duty = LIMIT_LINEAL_DUTY_CYCLE; + } else if (pw_duty == 100 || + (pw_duty >= MINIMUM_DUTY_CYCLE && pw_duty <= LIMIT_LINEAL_DUTY_CYCLE)) { + piperp->power_duty = pw_duty; + } + } + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR(power_duty, S_IWUSR | S_IRUGO, show_power_duty, store_power_duty); + +#if WANT_DEBUG_COMMANDS + +static ssize_t show_debug_cmd(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", piperp->debug_cmd); +} + +static ssize_t store_debug_cmd(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct piper_priv *piperp = dev_get_drvdata(dev); + ssize_t ret = -EINVAL; + + if (strlen(buf) < sizeof(piperp->debug_cmd)) + { + if (strstr(buf, "dump") != NULL) { + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + ret = 1; + } else if (strstr(buf, "ps_state") != NULL) { + printk(KERN_ERR "rxTaskletRunning = %d, allowTransmits = %d, stopped_tx_queues = %d\n", + piperp->ps.rxTaskletRunning, piperp->ps.allowTransmits, piperp->ps.stopped_tx_queues); + ret = 1; + } else if (strstr(buf, "rssi_dump") != NULL) { + spinlock_t lock; + unsigned long flags; + unsigned int rssi; + + spin_lock_init(&lock); + spin_lock_irqsave(&piperp->ps.lock, flags); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RX_EN, op_and); + udelay(15); + rssi = piperp->ac->rd_reg(piperp, BB_RSSI); + printk(KERN_ERR "\n**rssi = 0x%8.8X\n", rssi); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + ret = 1; + spin_unlock_irqrestore(&lock, flags); + } else { + strcpy(piperp->debug_cmd, buf); + piperp->debug_cmd[strlen(buf)-1] = 0; /* truncate the \n */ + } + ret = count; + } + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR(debug_cmd, S_IWUSR | S_IRUGO, show_debug_cmd, store_debug_cmd); + +#endif + +#ifdef CONFIG_PM + +static int piper_suspend(struct platform_device *dev, pm_message_t state) +{ + struct piper_priv *piperp = platform_get_drvdata(dev); + unsigned long flags; + + /* TODO, use in future the ps.lock instead of fully disabling interrupts here */ + piperp->power_save_was_on_when_suspended = (piperp->ps.mode == PS_MODE_LOW_POWER); + if (piperp->power_save_was_on_when_suspended) + piper_ps_set(piperp, false); + mdelay(10); + piper_sendNullDataFrame(piperp, true); + ssleep(1); + + local_irq_save(flags); + /* + * Save power save state and then make sure power save is turned off. + */ + piper_MacEnterSleepMode(piperp, true); + local_irq_restore(flags); + + return 0; +} + +static int piper_resume(struct platform_device *dev) +{ + struct piper_priv *piperp = platform_get_drvdata(dev); + unsigned long flags; + + if (piperp->pdata->early_resume) + piperp->pdata->early_resume(piperp); + + /* TODO, use in future the ps.lock instead of fully disabling interrupts here */ + local_irq_save(flags); + piper_MacEnterActiveMode(piperp, true); + if (piperp->tx_tasklet_running) { + tasklet_hi_schedule(&piperp->tx_tasklet); + } else { + ieee80211_wake_queues(piperp->hw); + } + local_irq_restore(flags); + + /* + * Restore power save if it was on before + */ + if (piperp->power_save_was_on_when_suspended) { + piper_ps_set(piperp, true); + } else { + piper_sendNullDataFrame(piperp, false); + } + + return 0; +} +#else +#define piper_suspend NULL +#define piper_resume NULL +#endif + +static int __init piper_probe(struct platform_device* pdev) +{ + struct piper_pdata *pdata = pdev->dev.platform_data; + struct piper_priv *piperp; + int ret = 0; + + if (!pdata) + return -EINVAL; + + ret = piper_alloc_hw(&piperp, sizeof(*piperp)); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to alloc piper_priv\n"); + return ret; + } + + piperp->ac = kzalloc(sizeof(struct access_ops), GFP_KERNEL); + if (!piperp->ac){ + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to alloc memory for ac struct\n"); + ret = -ENOMEM; + goto error_alloc; + } + + piperp->drv_name = PIPER_DRIVER_NAME; + dev_set_drvdata(&pdev->dev, piperp); + piperp->pdata = pdata; + pdata->piperp = piperp; + spin_lock_init(&piperp->ac->reg_lock); + spin_lock_init(&piperp->aesLock); + + piperp->vbase = ioremap(pdev->resource[0].start, + pdev[0].resource->end - pdev->resource[0].start); + + if (!piperp->vbase) { + printk(KERN_ERR PIPER_DRIVER_NAME ": ioremap base %x, len %x error\n", + pdev->resource[0].start, pdev[0].resource->end - pdev->resource[0].start); + ret = -ENOMEM; + goto error_remap; + } + + piperp->pstats.tx_start_count = 0; + piperp->pstats.tx_complete_count = 0; + + /* + * Platform initialization. This will initialize the hardware, including the load + * of the mac and dsp firmware into the piper chip + */ + if (pdata->init) { + if ((ret = pdata->init(piperp)) != 0) { + printk(KERN_ERR PIPER_DRIVER_NAME + ": platform init() returned error (%d)\n", ret); + goto error_init; + } + } + + piper_ps_init(piperp); + init_timer(&piperp->tx_timer); + piperp->tx_timer.function = tx_timer_timeout; + piperp->tx_timer.data = (unsigned long) piperp; + piper_init_rx_tx(piperp); + piper_init_keys(piperp); + + piperp->init_hw = piper_init_hw; + piperp->deinit_hw = piper_deinit_hw; + piperp->set_irq_mask_bit = piper_set_irq_mask; + piperp->clear_irq_mask_bit = piper_clear_irq_mask; + piperp->load_beacon = load_beacon; + piperp->rand = local_rand; + piperp->get_next_beacon_backoff = get_next_beacon_backoff; + piperp->set_antenna = piper_set_antenna; + piperp->set_tracking_constant = piper_set_tracking_constant; + piperp->antenna = ANTENNA_1; + piperp->adjust_max_agc = adjust_max_agc; + + /* + * Set the default duty cycle value. Note that duty cycling + * is disabled reguardless of what this variable is set to until + * the user types "iwconfig wlan0 power on". I just love the + * "power on" syntax to turn *down* the power. + */ + piperp->power_duty = 100; + + /* TODO this should be read earlier and actions should be taken + * based on different revisions at driver initialization or runtime */ + piperp->version = piperp->ac->rd_reg(piperp, BB_VERSION); + + piperp->irq = pdev->resource[1].start; + piperp->tx_cts = false; + piperp->beacon.loaded = false; + piperp->beacon.enabled = false; + piperp->beacon.weSentLastOne = false; + + ret = request_irq(piperp->irq, piper_irq_handler, + IRQF_TRIGGER_HIGH, PIPER_DRIVER_NAME, piperp); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": unable to request irq %d (%d)", + piperp->irq, ret); + goto retor_irq; + } + + disable_irq(piperp->irq); + + ret = piper_register_hw(piperp, &pdev->dev, &al7230_rf_ops); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to register priv\n"); + goto error_reg_hw; + } + + if (pdata->late_init) + pdata->late_init(piperp); + + ret = device_create_file(&pdev->dev, &dev_attr_antenna_sel); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to create sysfs file\n"); + goto error_sysfs; + } + + ret = device_create_file(&pdev->dev, &dev_attr_power_duty); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to create sysfs file\n"); + goto error_sysfs; + } + + strcpy(piperp->debug_cmd, "off"); +#if WANT_DEBUG_COMMANDS + ret = device_create_file(&pdev->dev, &dev_attr_debug_cmd); + if (ret) { + printk(KERN_ERR PIPER_DRIVER_NAME ": failed to create sysfs file\n"); + goto error_sysfs; + } +#endif + + printk(KERN_INFO PIPER_DRIVER_NAME ": driver loaded (fw ver = 0x%08x)\n", + piperp->version); + + return 0; + +error_sysfs: + piper_unregister_hw(piperp); +error_reg_hw: + piper_ps_deinit(piperp); + piper_free_rx_tx(piperp); +retor_irq: + free_irq(piperp->irq, piperp); +error_init: + iounmap(piperp->vbase); + piperp->vbase = NULL; +error_remap: + release_resource(pdev->resource); +error_alloc: + piper_free_hw(piperp); + return ret; +} + +static int piper_remove(struct platform_device *pdev) +{ + struct piper_priv *piperp = dev_get_drvdata(&pdev->dev); + + printk(KERN_DEBUG PIPER_DRIVER_NAME " %s\n", __func__); + + device_remove_file(&pdev->dev, &dev_attr_antenna_sel); + device_remove_file(&pdev->dev, &dev_attr_power_duty); +#if WANT_DEBUG_COMMANDS + device_remove_file(&pdev->dev, &dev_attr_debug_cmd); +#endif + + piper_ps_deinit(piperp); + piper_unregister_hw(piperp); + disable_irq(piperp->irq); + piper_clear_irq_mask(piperp, 0xffffffff); + free_irq(piperp->irq, piperp); + piper_free_rx_tx(piperp); + release_resource(pdev->resource); + piper_free_hw(piperp); + + return 0; +} + +/* describes the driver */ +static struct platform_driver piper_driver = { + .probe = piper_probe, + .remove = piper_remove, + .suspend = piper_suspend, + .resume = piper_resume, + .driver = { + .name = PIPER_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init piper_init_module(void) +{ + return platform_driver_register(&piper_driver); +} + +static void __exit piper_exit_module(void) +{ + platform_driver_unregister(&piper_driver); +} + +module_init(piper_init_module); +module_exit(piper_exit_module); + +MODULE_DESCRIPTION("Digi Piper WLAN Driver"); +MODULE_AUTHOR("Contact support@digi.com for questions on this code"); +MODULE_VERSION(DRV_VERS); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/digiPiper/piperDsp.c b/drivers/net/wireless/digiPiper/piperDsp.c new file mode 100644 index 000000000000..0f3d0e1b9b5e --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperDsp.c @@ -0,0 +1,285 @@ +/* + Copyright (c) 2007-2008 Digi International Inc., All Rights Reserved + + This software contains proprietary and confidential information of Digi + International Inc. By accepting transfer of this copy, Recipient agrees + to retain this software in confidence, to prevent disclosure to others, + and to make no use of this software other than that for which it was + delivered. This is an unpublished copyrighted work of Digi International + Inc. Except as permitted by federal law, 17 USC 117, copying is strictly + prohibited. + + Restricted Rights Legend + + Use, duplication, or disclosure by the Government is subject to + restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in + Technical Data and Computer Software clause at DFARS 252.227-7031 or + subparagraphs (c)(1) and (2) of the Commercial Computer Software - + Restricted Rights at 48 CFR 52.227-19, as applicable. + + Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + + WiFi DSP Code for Piper +*/ + + +const unsigned long piper_wifi_dsp_ucode[1024] = { + 0x32000, 0x3200F, 0x320AB, 0x0403F, + 0x0C000, 0x38000, 0x38060, 0x10004, + 0x10008, 0x10009, 0x1000A, 0x1000B, + 0x10001, 0x10010, 0x08000, 0x08000, + 0x10001, 0x08000, 0x08000, 0x08000, + 0x08000, 0x08000, 0x08000, 0x08000, + 0x32030, 0x39010, 0x39010, 0x30802, + 0x2B030, 0x10001, 0x10800, 0x38000, + 0x10004, 0x10040, 0x11002, 0x320D1, + 0x041AD, 0x0C000, 0x38000, 0x38160, + 0x38400, 0x061AD, 0x38100, 0x39171, + 0x39571, 0x32072, 0x11002, 0x320E1, + 0x38100, 0x39171, 0x39571, 0x36002, + 0x29039, 0x38000, 0x11002, 0x38000, + 0x38060, 0x10001, 0x083D5, 0x10050, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10104, 0x10013, 0x321D1, 0x0422D, + 0x0E000, 0x38000, 0x38260, 0x38400, + 0x0622D, 0x0C000, 0x38200, 0x39271, + 0x39671, 0x32002, 0x10003, 0x321E1, + 0x38200, 0x39271, 0x39671, 0x10013, + 0x321E1, 0x38200, 0x39271, 0x39671, + 0x36002, 0x29034, 0x38000, 0x10003, + 0x10001, 0x38060, 0x083B1, 0x10050, + 0x10104, 0x10040, 0x10213, 0x320D1, + 0x0422D, 0x0E000, 0x38000, 0x38260, + 0x38400, 0x0622D, 0x0C000, 0x38200, + 0x39271, 0x39671, 0x10003, 0x321E1, + 0x38200, 0x39271, 0x39671, 0x10013, + 0x321E1, 0x38200, 0x39271, 0x39671, + 0x10003, 0x10001, 0x38060, 0x10004, + 0x10000, 0x08392, 0x10010, 0x08380, + 0x30802, 0x29003, 0x37012, 0x082BF, + 0x38000, 0x29002, 0x37012, 0x08078, + 0x29002, 0x37012, 0x08105, 0x29002, + 0x37012, 0x081C2, 0x29002, 0x37172, + 0x0822F, 0x04236, 0x10801, 0x29002, + 0x370F2, 0x0838A, 0x29002, 0x370F2, + 0x083A7, 0x29002, 0x370F2, 0x083C4, + 0x38000, 0x08002, 0x38000, 0x38000, + 0x30822, 0x10104, 0x10040, 0x10213, + 0x320D1, 0x0422D, 0x0E000, 0x38000, + 0x38260, 0x38400, 0x0622D, 0x0C000, + 0x38200, 0x39271, 0x39671, 0x10003, + 0x321E1, 0x38200, 0x39271, 0x39671, + 0x10013, 0x321E1, 0x38200, 0x39271, + 0x39671, 0x10003, 0x38000, 0x38060, + 0x36002, 0x2A005, 0x0422D, 0x38400, + 0x0402D, 0x38060, 0x38400, 0x10004, + 0x10000, 0x10001, 0x08349, 0x10010, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x3601F, 0x30033, 0x23103, 0x38000, + 0x08030, 0x38000, 0x08031, 0x38000, + 0x327F0, 0x300F1, 0x37091, 0x21034, + 0x30803, 0x300F2, 0x23009, 0x32081, + 0x10002, 0x04180, 0x0C100, 0x38000, + 0x38040, 0x04400, 0x320A1, 0x38060, + 0x04900, 0x0E630, 0x38000, 0x38000, + 0x38060, 0x38000, 0x2103E, 0x38060, + 0x04000, 0x0C000, 0x300F1, 0x22004, + 0x37041, 0x23003, 0x04401, 0x38000, + 0x38020, 0x3601F, 0x30033, 0x2310C, + 0x38000, 0x082EA, 0x10010, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x38000, 0x10800, 0x38000, + 0x300F2, 0x32081, 0x35021, 0x20017, + 0x300F2, 0x37052, 0x28036, 0x0440A, + 0x0C000, 0x38000, 0x38060, 0x30011, + 0x20004, 0x0440A, 0x0E000, 0x2103F, + 0x38060, 0x05410, 0x0E000, 0x38000, + 0x38060, 0x08005, 0x38000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x04000, + 0x10008, 0x05C02, 0x0C000, 0x38000, + 0x38020, 0x04C12, 0x0E000, 0x10002, + 0x38020, 0x0C209, 0x04914, 0x38000, + 0x0C409, 0x38040, 0x38000, 0x38000, + 0x38020, 0x32011, 0x36001, 0x0E000, + 0x05E02, 0x38000, 0x38120, 0x0E201, + 0x04914, 0x38000, 0x0E401, 0x38040, + 0x38000, 0x38020, 0x21034, 0x38000, + 0x10202, 0x04924, 0x38000, 0x38400, + 0x10202, 0x041A4, 0x0C100, 0x38060, + 0x04424, 0x38060, 0x04924, 0x0E630, + 0x320A1, 0x38000, 0x38060, 0x38000, + 0x2103E, 0x38060, 0x04403, 0x0C000, + 0x300F0, 0x38020, 0x0E000, 0x37030, + 0x39030, 0x39030, 0x0401B, 0x10401, + 0x38040, 0x10001, 0x05C1C, 0x38000, + 0x38040, 0x08282, 0x10010, 0x00000, + 0x10023, 0x10104, 0x0422D, 0x0C080, + 0x32180, 0x38260, 0x0E080, 0x38200, + 0x39270, 0x39270, 0x05C2E, 0x0C000, + 0x38060, 0x100D3, 0x0422D, 0x0C080, + 0x32180, 0x38260, 0x0E080, 0x38200, + 0x39270, 0x39270, 0x05C2E, 0x0E000, + 0x38060, 0x10004, 0x10003, 0x10402, + 0x0422D, 0x0C000, 0x323F0, 0x38200, + 0x39270, 0x39770, 0x04401, 0x0C000, + 0x1000B, 0x38020, 0x06409, 0x38020, + 0x38020, 0x38020, 0x1000A, 0x08254, + 0x10010, 0x38000, 0x38000, 0x38000, + 0x10462, 0x10014, 0x041AD, 0x0C000, + 0x32180, 0x38160, 0x0E000, 0x38100, + 0x39170, 0x39170, 0x05C2E, 0x0C000, + 0x38060, 0x10512, 0x041AD, 0x0C000, + 0x32180, 0x38160, 0x0E000, 0x38100, + 0x39170, 0x39170, 0x05C2E, 0x0E000, + 0x38060, 0x10004, 0x05036, 0x0E008, + 0x10212, 0x38060, 0x38400, 0x04427, + 0x0C000, 0x38060, 0x0403F, 0x38000, + 0x38020, 0x0493F, 0x0E630, 0x32051, + 0x38000, 0x38060, 0x38000, 0x2103E, + 0x38060, 0x300F1, 0x37061, 0x0443E, + 0x0C000, 0x38020, 0x0643F, 0x0C000, + 0x38020, 0x38020, 0x38020, 0x0563F, + 0x0E000, 0x2000A, 0x38000, 0x21004, + 0x38020, 0x04C37, 0x08014, 0x38020, + 0x2000C, 0x38000, 0x21006, 0x38000, + 0x04637, 0x38020, 0x05C37, 0x0800B, + 0x38020, 0x38020, 0x21005, 0x04437, + 0x38020, 0x05637, 0x08004, 0x38020, + 0x05C37, 0x38020, 0x38000, 0x11102, + 0x041B6, 0x0C000, 0x38000, 0x38020, + 0x04E37, 0x0E000, 0x38020, 0x05C3F, + 0x0C000, 0x38020, 0x04438, 0x0E000, + 0x38020, 0x04000, 0x38000, 0x10008, + 0x04401, 0x0C000, 0x38020, 0x06439, + 0x0E000, 0x38020, 0x38020, 0x38020, + 0x38020, 0x38020, 0x38020, 0x04439, + 0x38000, 0x38020, 0x04409, 0x0C000, + 0x1000B, 0x06409, 0x38020, 0x38020, + 0x38020, 0x1000A, 0x10212, 0x04136, + 0x0C800, 0x38000, 0x38060, 0x11112, + 0x041B6, 0x0E000, 0x38000, 0x38060, + 0x25004, 0x38000, 0x10800, 0x38000, + 0x38000, 0x10400, 0x081CD, 0x38000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10422, 0x10023, 0x10114, 0x04BAD, + 0x0C080, 0x32190, 0x38300, 0x39370, + 0x39F70, 0x104D2, 0x100D3, 0x32190, + 0x38300, 0x39370, 0x39F70, 0x38000, + 0x10433, 0x0422D, 0x0C000, 0x32030, + 0x39270, 0x39A10, 0x10063, 0x10334, + 0x32030, 0x39270, 0x39A10, 0x10512, + 0x041AD, 0x32020, 0x39170, 0x39510, + 0x10782, 0x10114, 0x32020, 0x39170, + 0x39510, 0x104D3, 0x0422D, 0x0C070, + 0x10334, 0x38260, 0x0E070, 0x32060, + 0x39210, 0x39270, 0x107B2, 0x104D3, + 0x10020, 0x05BAD, 0x0E060, 0x32190, + 0x38300, 0x39370, 0x39B70, 0x13FC3, + 0x0422D, 0x0C070, 0x10114, 0x38260, + 0x0E070, 0x32060, 0x39210, 0x39270, + 0x104E2, 0x13FC3, 0x05BAD, 0x0E060, + 0x32190, 0x38300, 0x39370, 0x39B70, + 0x10004, 0x08326, 0x10000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10114, 0x10173, 0x10902, 0x0616D, + 0x0C800, 0x321B0, 0x38260, 0x38260, + 0x38260, 0x39270, 0x39770, 0x0416D, + 0x321B0, 0x10173, 0x10912, 0x38260, + 0x38260, 0x38260, 0x39270, 0x39770, + 0x10003, 0x10C42, 0x0616D, 0x0C800, + 0x321B0, 0x38260, 0x38260, 0x38260, + 0x39270, 0x39770, 0x0416D, 0x321B0, + 0x10003, 0x10C52, 0x38260, 0x38260, + 0x38260, 0x39270, 0x39770, 0x32028, + 0x0C070, 0x10973, 0x0423F, 0x38060, + 0x10B33, 0x0E070, 0x108E3, 0x38060, + 0x38060, 0x0E060, 0x10AA3, 0x38000, + 0x38060, 0x0C000, 0x0503F, 0x10222, + 0x38400, 0x080C6, 0x38000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x108D3, 0x10104, 0x10140, 0x040F6, + 0x0C202, 0x32181, 0x38200, 0x38080, + 0x382C0, 0x380A0, 0x216FF, 0x380A0, + 0x10823, 0x0C202, 0x32181, 0x38200, + 0x38080, 0x382C0, 0x380A0, 0x216FF, + 0x380A0, 0x080FA, 0x10010, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x10902, 0x108D3, 0x10114, 0x10140, + 0x047F6, 0x0D202, 0x32181, 0x100E1, + 0x38280, 0x38040, 0x381A0, 0x216FE, + 0x38440, 0x10C42, 0x10823, 0x047F6, + 0x0D202, 0x32171, 0x38280, 0x38040, + 0x381A0, 0x216FE, 0x38440, 0x381A0, + 0x386E0, 0x321A1, 0x2103F, 0x10001, + 0x38400, 0x080D2, 0x10010, 0x00000, + 0x38000, 0x38000, 0x38000, 0x0423F, + 0x10973, 0x0C000, 0x38060, 0x10B33, + 0x0E000, 0x108E3, 0x38060, 0x38060, + 0x0E030, 0x10AA3, 0x38000, 0x38060, + 0x18083, 0x0C0C0, 0x0443F, 0x36018, + 0x38060, 0x21002, 0x0C000, 0x32018, + 0x0503F, 0x10222, 0x38400, 0x041A4, + 0x0C100, 0x38060, 0x04424, 0x38060, + 0x048A4, 0x0E630, 0x32061, 0x38000, + 0x38060, 0x38000, 0x2103E, 0x38060, + 0x10080, 0x04401, 0x0C000, 0x38020, + 0x06639, 0x0E000, 0x38020, 0x04409, + 0x0C000, 0x1000B, 0x06409, 0x38020, + 0x38020, 0x38020, 0x1000A, 0x30811, + 0x22007, 0x37021, 0x20003, 0x38000, + 0x08383, 0x38000, 0x083A1, 0x38000, + 0x10902, 0x108D3, 0x10104, 0x10140, + 0x047F6, 0x0D202, 0x32181, 0x100E1, + 0x38080, 0x38240, 0x381A0, 0x38460, + 0x214FD, 0x38740, 0x10C42, 0x10823, + 0x047F6, 0x0D202, 0x32171, 0x38080, + 0x38240, 0x381A0, 0x38460, 0x214FD, + 0x38740, 0x381A0, 0x38460, 0x384E0, + 0x38000, 0x38000, 0x38000, 0x38000, + 0x38000, 0x38000, 0x38000, 0x38000, + 0x38000, 0x38400, 0x10001, 0x08068, + 0x10010, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x041A4, 0x0C100, 0x38000, 0x38060, + 0x04424, 0x32061, 0x38060, 0x04924, + 0x0E630, 0x38000, 0x38000, 0x38060, + 0x38000, 0x2103E, 0x38060, 0x10080, + 0x108D3, 0x10104, 0x10040, 0x040F6, + 0x0C202, 0x32181, 0x38200, 0x38200, + 0x38200, 0x21240, 0x21640, 0x10823, + 0x32181, 0x38200, 0x38200, 0x38200, + 0x21240, 0x21640, 0x04401, 0x0C000, + 0x38020, 0x06639, 0x0E000, 0x38020, + 0x04409, 0x0C000, 0x1000B, 0x06409, + 0x38020, 0x38020, 0x38020, 0x1000A, + 0x0801F, 0x10010, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000 +}; + +const int piper_dsp_data_len = (sizeof piper_wifi_dsp_ucode)/4; diff --git a/drivers/net/wireless/digiPiper/piperDsp.h b/drivers/net/wireless/digiPiper/piperDsp.h new file mode 100644 index 000000000000..836ffcb96341 --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperDsp.h @@ -0,0 +1,8 @@ +#ifndef PIPER_DSP_H_ +#define PIPER_DSP_H_ + + +extern const unsigned long piper_wifi_dsp_ucode[]; +extern const int piper_dsp_data_len; + +#endif diff --git a/drivers/net/wireless/digiPiper/piperMacAssist.c b/drivers/net/wireless/digiPiper/piperMacAssist.c new file mode 100644 index 000000000000..1a8ada81b84b --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperMacAssist.c @@ -0,0 +1,285 @@ +/* + Copyright (c) 2007-2008 Digi International Inc., All Rights Reserved + + This software contains proprietary and confidential information of Digi + International Inc. By accepting transfer of this copy, Recipient agrees + to retain this software in confidence, to prevent disclosure to others, + and to make no use of this software other than that for which it was + delivered. This is an unpublished copyrighted work of Digi International + Inc. Except as permitted by federal law, 17 USC 117, copying is strictly + prohibited. + + Restricted Rights Legend + + Use, duplication, or disclosure by the Government is subject to + restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in + Technical Data and Computer Software clause at DFARS 252.227-7031 or + subparagraphs (c)(1) and (2) of the Commercial Computer Software - + Restricted Rights at 48 CFR 52.227-19, as applicable. + + Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 + + WiFi MAC assist Code for Piper +*/ + +const unsigned long piper_wifi_macassist_ucode[2048] = { + 0x00F00, 0x2CF22, 0x3033B, 0x00E20, 0x2CED0, 0x2CFD2, 0x2CFD8, 0x2CFD9, + 0x2CF31, 0x2CFDB, 0x00E20, 0x2CEDA, 0x00E64, 0x2CE1D, 0x00EFF, 0x2CEEA, + 0x2CEEB, 0x00E01, 0x2CE20, 0x00E10, 0x2CE21, 0x04522, 0x00603, 0x2C6CE, + 0x00001, 0x2C040, 0x00000, 0x2C041, 0x2C042, 0x2C043, 0x0016E, 0x2C144, + 0x00000, 0x0010A, 0x00200, 0x0A5FF, 0x35027, 0x0010B, 0x00280, 0x2C245, + 0x2C146, 0x2C047, 0x00180, 0x2C148, 0x2C049, 0x2C0CE, 0x00000, 0x00100, + 0x00223, 0x040C0, 0x302E2, 0x3031A, 0x00030, 0x0011C, 0x00801, 0x042C0, + 0x0A204, 0x35037, 0x042C0, 0x0A5FF, 0x35040, 0x00031, 0x00117, 0x00806, + 0x30338, 0x1C001, 0x35C40, 0x1C101, 0x35C43, 0x00000, 0x042C0, 0x043C0, + 0x044C0, 0x045C0, 0x046C0, 0x047C0, 0x00000, 0x1C801, 0x35C4D, 0x048C1, + 0x049C1, 0x04AC1, 0x04BC1, 0x04CC1, 0x04DC1, 0x0D320, 0x0D430, 0x0D540, + 0x0D650, 0x0D760, 0x19230, 0x19240, 0x19250, 0x19260, 0x19270, 0x0A880, + 0x35062, 0x18208, 0x0A980, 0x35065, 0x18208, 0x0AA80, 0x35068, 0x18208, + 0x0AB80, 0x3506B, 0x18208, 0x0AC80, 0x3506E, 0x18208, 0x0AD80, 0x35071, + 0x18208, 0x20206, 0x04122, 0x0A1FF, 0x3547B, 0x2C222, 0x0E260, 0x35015, + 0x0E260, 0x00151, 0x3407C, 0x00103, 0x19120, 0x2C122, 0x00E00, 0x2CECE, + 0x3C001, 0x04EC3, 0x0AE20, 0x04FC2, 0x0AF80, 0x0DEF0, 0x04F30, 0x2CE30, + 0x0FFE0, 0x35095, 0x0EE20, 0x35495, 0x3C000, 0x04EC1, 0x0AE80, 0x3548D, + 0x04E17, 0x2CED4, 0x04E16, 0x2CED6, 0x3C001, 0x04E27, 0x0AE10, 0x04F33, + 0x2CE33, 0x0EF10, 0x0BFE0, 0x350AC, 0x00E00, 0x00FE0, 0x2DEF0, 0x18F01, + 0x0EFE8, 0x350A4, 0x0EFE8, 0x3409E, 0x04F1D, 0x00E00, 0x1DEF0, 0x2CEEA, + 0x04F1C, 0x00E00, 0x1FEF0, 0x2CEEB, 0x04E27, 0x0AE80, 0x350C8, 0x3C000, + 0x00E01, 0x2CECE, 0x04EC1, 0x0AE80, 0x354B2, 0x04EE0, 0x2CE27, 0x04EE1, + 0x2CE26, 0x04EE2, 0x2CE25, 0x04EE3, 0x2CE24, 0x04EE4, 0x2CE23, 0x04EE5, + 0x2CE22, 0x04EE6, 0x04FE7, 0x2CE21, 0x2CF20, 0x00E00, 0x2CECE, 0x3C001, + 0x04E35, 0x04F36, 0x0DEF0, 0x3507E, 0x04E3A, 0x04FEB, 0x15EF0, 0x3547E, + 0x04FEA, 0x04E37, 0x15EF0, 0x3547E, 0x3C000, 0x04EC1, 0x0AE80, 0x354D5, + 0x04E36, 0x12EFF, 0x354E7, 0x04F35, 0x14F40, 0x35CE7, 0x00E00, 0x2CED8, + 0x20F06, 0x20F06, 0x2CFD9, 0x3C001, 0x2CE36, 0x2CE35, 0x3407E, 0x04F35, + 0x1CF01, 0x1EE00, 0x2CF35, 0x2CE36, 0x00E0F, 0x2CED8, 0x2CED9, 0x3C001, + 0x04E37, 0x04F3A, 0x18E01, 0x1AF00, 0x2CE37, 0x2CF3A, 0x3407E, 0x2CF32, + 0x04FC0, 0x20F0E, 0x3190B, 0x20F0E, 0x31BFC, 0x20F0E, 0x3195B, 0x20F0E, + 0x31A72, 0x20F0E, 0x31ACA, 0x20F0E, 0x319DC, 0x20F0E, 0x319E3, 0x20F0E, + 0x31A63, 0x04F32, 0x38001, 0x04031, 0x0C001, 0x2C031, 0x040C2, 0x0A008, + 0x35120, 0x04031, 0x0A004, 0x35517, 0x04031, 0x0C080, 0x2C031, 0x302E2, + 0x040C3, 0x0A010, 0x3511C, 0x00016, 0x18011, 0x00100, 0x00200, 0x3031A, + 0x302FF, 0x0E3FF, 0x35127, 0x04031, 0x0A0BF, 0x2C031, 0x3412A, 0x04031, + 0x0C040, 0x2C031, 0x04244, 0x040C3, 0x0A010, 0x35547, 0x302E8, 0x00400, + 0x04510, 0x0A5FF, 0x35534, 0x0C515, 0x13350, 0x3513B, 0x01430, 0x1232F, + 0x35544, 0x2030E, 0x34134, 0x1232F, 0x3553F, 0x2030E, 0x34134, 0x124FF, + 0x35544, 0x2030E, 0x35D34, 0x00401, 0x302F4, 0x2C33B, 0x2A000, 0x04511, + 0x1426E, 0x3514F, 0x14237, 0x35152, 0x14214, 0x35155, 0x34158, 0x0026E, + 0x12508, 0x35559, 0x00237, 0x12504, 0x35559, 0x00214, 0x12502, 0x35559, + 0x0020A, 0x2C23B, 0x2A000, 0x04127, 0x00001, 0x2C0CE, 0x040C2, 0x0A040, + 0x35163, 0x00005, 0x2C0CE, 0x0A110, 0x35192, 0x04048, 0x0E080, 0x3516C, + 0x04048, 0x0E050, 0x3516C, 0x34192, 0x00012, 0x04140, 0x0E1EE, 0x35175, + 0x041C1, 0x0A120, 0x35574, 0x18020, 0x18007, 0x302FF, 0x00100, 0x19030, + 0x1A100, 0x30338, 0x042E0, 0x043E1, 0x044E2, 0x045E3, 0x046E4, 0x047E5, + 0x048E6, 0x049E7, 0x19200, 0x1B310, 0x1A400, 0x1A500, 0x1A600, 0x1A700, + 0x1A800, 0x1A900, 0x2C260, 0x2C361, 0x2C462, 0x2C563, 0x2C664, 0x2C765, + 0x2C866, 0x2C967, 0x0002C, 0x00100, 0x042C2, 0x044C1, 0x0A480, 0x35595, + 0x2C0D0, 0x2C1D2, 0x043C2, 0x0A3E0, 0x2C3C2, 0x04140, 0x04348, 0x00000, + 0x2C0CE, 0x04031, 0x0A0FB, 0x2C031, 0x0A204, 0x355B6, 0x0A30F, 0x0E304, + 0x351B6, 0x04012, 0x0A001, 0x351B6, 0x0E1EE, 0x355AF, 0x0C004, 0x0C00A, + 0x2C012, 0x00064, 0x1C001, 0x355B2, 0x00040, 0x2C0C4, 0x040C2, 0x0A040, + 0x2B400, 0x0403E, 0x0A0FF, 0x2B400, 0x00001, 0x2C0CE, 0x04040, 0x0A0FF, + 0x351C5, 0x0E001, 0x351C5, 0x0E0EF, 0x355D4, 0x04042, 0x0A0FF, 0x351CB, + 0x0E001, 0x355D4, 0x341CE, 0x04041, 0x0A0FF, 0x351D4, 0x04043, 0x0A0FF, + 0x355D4, 0x00000, 0x2C0CE, 0x2A000, 0x00080, 0x2C0C4, 0x0403F, 0x18001, + 0x2C03F, 0x00000, 0x2C0CE, 0x2A000, 0x00008, 0x2C0C4, 0x00100, 0x2C13E, + 0x00012, 0x00200, 0x3431A, 0x00101, 0x2C1CE, 0x0402B, 0x12001, 0x35203, + 0x04128, 0x121FF, 0x351EE, 0x1C101, 0x2C128, 0x35603, 0x04129, 0x0422D, + 0x0432C, 0x0442F, 0x0452E, 0x12002, 0x351F7, 0x2C128, 0x341F9, 0x0A0FE, + 0x2C02B, 0x00000, 0x2C0CE, 0x0401D, 0x1D200, 0x0401C, 0x1F300, 0x2C237, + 0x2C33A, 0x2C435, 0x2C536, 0x00000, 0x2C0CE, 0x04027, 0x0A010, 0x35236, + 0x3033B, 0x040D5, 0x2C02B, 0x040D7, 0x2C02A, 0x00100, 0x0401F, 0x1D100, + 0x2C1E8, 0x00100, 0x0401E, 0x1F100, 0x2C1E9, 0x040C2, 0x0A004, 0x3561D, + 0x3033B, 0x00004, 0x2C0D0, 0x00000, 0x2C0D2, 0x04027, 0x0A004, 0x3522C, + 0x30338, 0x30338, 0x040C2, 0x0C080, 0x2C0C2, 0x040C2, 0x0A040, 0x3522C, + 0x0402F, 0x2C0D4, 0x0402E, 0x2C0D6, 0x00110, 0x040C1, 0x0A010, 0x35234, + 0x04031, 0x0D010, 0x2C031, 0x34246, 0x2C1C4, 0x34246, 0x00010, 0x2C0C4, + 0x04034, 0x0C000, 0x35246, 0x040ED, 0x0C000, 0x35646, 0x3033B, 0x0421F, + 0x0431E, 0x2C2D8, 0x2C3D9, 0x04119, 0x1D010, 0x2C0ED, 0x0421D, 0x0431C, + 0x00000, 0x00100, 0x1D020, 0x1F130, 0x3033B, 0x2C0EA, 0x2C1EB, 0x040EC, + 0x0C000, 0x35655, 0x04118, 0x1D010, 0x2C0EC, 0x3033B, 0x040EE, 0x0C000, + 0x2B400, 0x041EF, 0x0C100, 0x2B400, 0x0421B, 0x0431A, 0x1D020, 0x1F130, + 0x2C0EE, 0x2C1EF, 0x2A000, 0x3033B, 0x040D5, 0x2C02B, 0x040D7, 0x2C02A, + 0x040C1, 0x0A010, 0x3566E, 0x00020, 0x2C0C4, 0x2A000, 0x04031, 0x0C020, + 0x2C031, 0x2A000, 0x04012, 0x0A0F7, 0x2C012, 0x00500, 0x00601, 0x00100, + 0x00700, 0x04031, 0x0A0F7, 0x2C031, 0x2C53E, 0x040C3, 0x0A0C0, 0x35287, + 0x00603, 0x0A080, 0x35684, 0x00605, 0x2C6CE, 0x04440, 0x342A4, 0x00702, + 0x2C6CE, 0x04440, 0x0424C, 0x0A201, 0x356A4, 0x04248, 0x0E2B4, 0x35293, + 0x04249, 0x0A204, 0x35298, 0x2C5CE, 0x04231, 0x0C208, 0x2C231, 0x2C6CE, + 0x00203, 0x04048, 0x2C5CE, 0x0E0C4, 0x352B9, 0x00030, 0x0A4EE, 0x356A2, + 0x00080, 0x1805E, 0x00209, 0x342B9, 0x2C5CE, 0x00000, 0x00100, 0x04231, + 0x0A240, 0x356AD, 0x042C1, 0x0A201, 0x356B2, 0x0A4EE, 0x356B0, 0x000FE, + 0x1803C, 0x1A100, 0x00200, 0x0A4EE, 0x356B7, 0x18016, 0x1A100, 0x18012, + 0x1A100, 0x3031A, 0x12702, 0x352C2, 0x0A202, 0x356C2, 0x04131, 0x12108, + 0x356C2, 0x0C780, 0x04131, 0x0A130, 0x0D710, 0x2C7C4, 0x04731, 0x0A7CF, + 0x2C731, 0x2A000, 0x04531, 0x12504, 0x352D4, 0x3033B, 0x0A5FB, 0x2C531, + 0x00500, 0x2C5D8, 0x2C5D9, 0x342DE, 0x04031, 0x0A080, 0x2B400, 0x04131, + 0x0A1F7, 0x2C131, 0x00184, 0x2C1C4, 0x00101, 0x2C1CE, 0x04440, 0x302E2, + 0x00500, 0x342A4, 0x040C1, 0x0A080, 0x356E2, 0x00020, 0x2C0DB, 0x2A000, + 0x00310, 0x12202, 0x352EC, 0x00301, 0x12201, 0x356F0, 0x20306, 0x20306, + 0x12204, 0x352F3, 0x20306, 0x2A000, 0x00308, 0x12455, 0x356F8, 0x0030C, + 0x12433, 0x352FB, 0x0C301, 0x1240F, 0x352FE, 0x0C302, 0x2A000, 0x04240, + 0x0E2EE, 0x35708, 0x04244, 0x003FF, 0x0A208, 0x2B000, 0x00300, 0x2A000, + 0x003FF, 0x04244, 0x0E26E, 0x3570D, 0x00311, 0x04244, 0x0E237, 0x35711, + 0x00323, 0x04244, 0x0E214, 0x35715, 0x00360, 0x04244, 0x0E20A, 0x2B400, + 0x003C0, 0x2A000, 0x0C000, 0x35322, 0x04325, 0x01430, 0x20306, 0x19430, + 0x19040, 0x1A100, 0x1C01A, 0x1E100, 0x04AC1, 0x0AA80, 0x35724, 0x043DB, + 0x19030, 0x1A100, 0x12180, 0x3532F, 0x00001, 0x00100, 0x34331, 0x18001, + 0x1A100, 0x2C0D0, 0x2C1D2, 0x043C2, 0x0A3E0, 0x0D230, 0x2C2C2, 0x2A000, + 0x04AC1, 0x0AA80, 0x35338, 0x04AC1, 0x0AA80, 0x3573B, 0x2A000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00001, 0x2C0CF, 0x2A000, 0x340F7, + 0x040C3, 0x0A002, 0x35008, 0x300EA, 0x04031, 0x0C002, 0x2C031, 0x343FC, + 0x04031, 0x12002, 0x3540E, 0x12001, 0x353FC, 0x300EA, 0x04031, 0x0A0F8, + 0x2C031, 0x30058, 0x04031, 0x0A0F7, 0x2C031, 0x04123, 0x01010, 0x0A00C, + 0x0E004, 0x35020, 0x04031, 0x0A080, 0x35042, 0x00084, 0x2C0C4, 0x34042, + 0x04023, 0x0C010, 0x2C023, 0x04027, 0x0A008, 0x35429, 0x01010, 0x0A003, + 0x3501A, 0x04031, 0x0A080, 0x35036, 0x04048, 0x0A0EC, 0x0E0C4, 0x3503A, + 0x00084, 0x2C0C4, 0x04031, 0x0A07F, 0x2C031, 0x3403A, 0x04048, 0x0A0EC, + 0x0E0C4, 0x35042, 0x040C1, 0x0A002, 0x35040, 0x00040, 0x2C0C4, 0x3401A, + 0x00001, 0x2C0C4, 0x04031, 0x0A07F, 0x2C031, 0x04023, 0x0A00C, 0x0E004, + 0x357FC, 0x0404C, 0x0A001, 0x3504E, 0x300B0, 0x343FC, 0x04023, 0x0A001, + 0x35453, 0x300BF, 0x343FC, 0x04048, 0x0E050, 0x357FC, 0x300DB, 0x343FC, + 0x04031, 0x0A040, 0x3505F, 0x04023, 0x0C020, 0x2C023, 0x3406B, 0x040C1, + 0x0A001, 0x3506B, 0x04023, 0x0C004, 0x2C023, 0x04048, 0x0A003, 0x35073, + 0x04023, 0x0C008, 0x2C023, 0x040C3, 0x0A010, 0x3506F, 0x00054, 0x1804E, + 0x00100, 0x00200, 0x34306, 0x040C2, 0x0A004, 0x2B400, 0x040C3, 0x0A010, + 0x3507A, 0x00016, 0x18012, 0x00100, 0x00200, 0x30306, 0x0004C, 0x05100, + 0x0A101, 0x2B000, 0x04008, 0x0A001, 0x35088, 0x04048, 0x0E080, 0x3509A, + 0x04049, 0x0A002, 0x3548C, 0x00008, 0x18050, 0x05100, 0x0A101, 0x35494, + 0x00208, 0x3032E, 0x0C400, 0x2B400, 0x04048, 0x0E040, 0x3549A, 0x04027, + 0x0A010, 0x2B000, 0x0402D, 0x0A0FF, 0x350AC, 0x04048, 0x0E080, 0x354AC, + 0x04063, 0x04139, 0x2C039, 0x0F010, 0x354AC, 0x04062, 0x04138, 0x0422D, + 0x0B020, 0x2C038, 0x0F010, 0x2B000, 0x04023, 0x0C002, 0x2C023, 0x2A000, + 0x04048, 0x0A0EC, 0x0E0E4, 0x354BC, 0x04036, 0x04135, 0x0D010, 0x2B400, + 0x3033C, 0x2C0D8, 0x2C0D9, 0x2A000, 0x04048, 0x0E080, 0x310DB, 0x0414B, + 0x0A180, 0x2B400, 0x0414A, 0x0424B, 0x1C10A, 0x1E200, 0x35CC9, 0x00100, + 0x00200, 0x30340, 0x2BC00, 0x04048, 0x0E0B4, 0x2B400, 0x2A000, 0x04031, + 0x0C004, 0x2C031, 0x00078, 0x00100, 0x042C3, 0x0A210, 0x350D9, 0x0006A, + 0x00101, 0x00209, 0x34306, 0x0466D, 0x1866F, 0x05160, 0x19610, 0x0416A, + 0x0A10C, 0x2C134, 0x350E9, 0x1860A, 0x05160, 0x18601, 0x05260, 0x30340, + 0x1C60B, 0x34213, 0x00000, 0x2C023, 0x04031, 0x0A040, 0x2B400, 0x040C3, + 0x0A010, 0x354FB, 0x04046, 0x0A001, 0x354FB, 0x04045, 0x14005, 0x35CFB, + 0x040C3, 0x0A002, 0x354F8, 0x04048, 0x0A003, 0x2B400, 0x0004C, 0x05100, + 0x0A101, 0x2B400, 0x0424C, 0x0434D, 0x0444E, 0x0454F, 0x04750, 0x04851, + 0x04100, 0x0F120, 0x04001, 0x0F030, 0x0D100, 0x04002, 0x0F040, 0x0D100, + 0x04003, 0x0F050, 0x0D100, 0x04004, 0x0F070, 0x0D100, 0x04005, 0x0F080, + 0x0D100, 0x35146, 0x04027, 0x0A040, 0x35145, 0x00001, 0x2C0CE, 0x04130, + 0x0F120, 0x04038, 0x0F200, 0x04031, 0x0F030, 0x0D100, 0x04039, 0x0F030, + 0x0D200, 0x04032, 0x0F040, 0x0D100, 0x0403A, 0x0F040, 0x0D200, 0x04033, + 0x0F050, 0x0D100, 0x0403B, 0x0F050, 0x0D200, 0x04034, 0x0F070, 0x0D100, + 0x0403C, 0x0F070, 0x0D200, 0x04035, 0x0433D, 0x00400, 0x2C4CE, 0x0F080, + 0x0D100, 0x35146, 0x0F380, 0x0D230, 0x35146, 0x2A000, 0x04023, 0x0C001, + 0x2C023, 0x040C1, 0x0A002, 0x2B400, 0x04048, 0x0A00C, 0x0E004, 0x35564, + 0x04048, 0x0A0E0, 0x0E0A0, 0x35164, 0x0E060, 0x2B400, 0x04131, 0x0A108, + 0x2B000, 0x3020A, 0x040C3, 0x0A002, 0x3555A, 0x040C1, 0x0A002, 0x35577, + 0x040C1, 0x0A001, 0x35177, 0x2A000, 0x3020A, 0x0413B, 0x040C3, 0x0A010, + 0x355C5, 0x0C1C0, 0x002EE, 0x00300, 0x00701, 0x00603, 0x040C3, 0x0A002, + 0x3556E, 0x040C1, 0x0A002, 0x35577, 0x040C1, 0x0A001, 0x3557C, 0x042C2, + 0x0A2E0, 0x0C203, 0x2C2C2, 0x2A000, 0x2C6CE, 0x2C240, 0x2C341, 0x2C342, + 0x2C343, 0x2C144, 0x2C745, 0x2C346, 0x2C347, 0x2C3CE, 0x04248, 0x0434A, + 0x0444B, 0x01540, 0x0E580, 0x35193, 0x01520, 0x0E5B4, 0x35195, 0x04549, + 0x0A504, 0x35595, 0x0A400, 0x0A300, 0x341AB, 0x00528, 0x01010, 0x0A007, + 0x351A9, 0x0E004, 0x351A9, 0x0E001, 0x351A9, 0x18504, 0x0E004, 0x351A9, + 0x0E007, 0x351A9, 0x18508, 0x0E004, 0x351A9, 0x18504, 0x0E005, 0x351A9, + 0x1850C, 0x1D350, 0x1E400, 0x00000, 0x2C6CE, 0x0E2B4, 0x351B1, 0x005D4, + 0x341B2, 0x005C4, 0x2C548, 0x2C049, 0x2C34A, 0x2C44B, 0x00306, 0x00152, + 0x0024C, 0x2C0CE, 0x05810, 0x2C6CE, 0x2D820, 0x18101, 0x18201, 0x1C301, + 0x355B9, 0x2C0CE, 0x00001, 0x2C03E, 0x2A000, 0x045C1, 0x0A540, 0x351CC, + 0x01510, 0x0E50A, 0x355CC, 0x00114, 0x01510, 0x00784, 0x00800, 0x0E56E, + 0x351D9, 0x00704, 0x0E559, 0x351D8, 0x0E523, 0x351D7, 0x18838, 0x18823, + 0x1880A, 0x1880B, 0x00603, 0x00300, 0x040C3, 0x0A002, 0x355DC, 0x040C1, + 0x0A002, 0x35577, 0x040C1, 0x0A001, 0x35177, 0x2C6CE, 0x2C340, 0x2C341, + 0x2C342, 0x2C343, 0x2C144, 0x2C745, 0x2C846, 0x2C347, 0x2C3CE, 0x04248, + 0x0434A, 0x0444B, 0x01540, 0x0E580, 0x351FC, 0x01520, 0x0E5B4, 0x351FE, + 0x04549, 0x0A504, 0x355FE, 0x0A400, 0x0A300, 0x341AB, 0x01580, 0x1850A, + 0x1D350, 0x1E400, 0x045C1, 0x0A540, 0x35606, 0x185A0, 0x18520, 0x1D350, + 0x1E400, 0x341AB, 0x00000, 0x00100, 0x00217, 0x04348, 0x0E3B4, 0x35211, + 0x00207, 0x30306, 0x2A000, 0x18603, 0x04027, 0x0A010, 0x0416A, 0x0A102, + 0x20106, 0x20106, 0x20106, 0x0F010, 0x2B400, 0x0C100, 0x35237, 0x0426D, + 0x04013, 0x0F020, 0x2B400, 0x0006E, 0x00100, 0x0A2FF, 0x2B000, 0x00500, + 0x05300, 0x00701, 0x2C7CE, 0x05410, 0x00700, 0x2C7CE, 0x0F340, 0x0D530, + 0x18001, 0x18101, 0x1C201, 0x35628, 0x0C500, 0x2B400, 0x34257, 0x00058, + 0x00108, 0x00206, 0x00500, 0x05300, 0x05410, 0x0F340, 0x0D530, 0x18001, + 0x18101, 0x1C201, 0x3563B, 0x0C500, 0x2B400, 0x04034, 0x0C000, 0x35257, + 0x18603, 0x05060, 0x00100, 0x1D100, 0x2C1ED, 0x18601, 0x05060, 0x2C019, + 0x18601, 0x05060, 0x2C01F, 0x18601, 0x05060, 0x2C01E, 0x18602, 0x04048, + 0x0A0EC, 0x0E080, 0x35676, 0x04023, 0x0C002, 0x2C023, 0x040C2, 0x0A080, + 0x35265, 0x040C2, 0x0A05F, 0x2C0C2, 0x2C0C2, 0x18603, 0x04027, 0x0A010, + 0x3526F, 0x05060, 0x2C01F, 0x18601, 0x05060, 0x2C01E, 0x34276, 0x05060, + 0x00100, 0x1D100, 0x2C1EC, 0x18601, 0x05060, 0x2C018, 0x040C3, 0x0A010, + 0x35685, 0x04044, 0x04145, 0x04246, 0x00304, 0x2020E, 0x20108, 0x20008, + 0x2010E, 0x20008, 0x1C301, 0x35680, 0x34294, 0x04046, 0x04147, 0x04244, + 0x003C0, 0x0E26E, 0x3568C, 0x00311, 0x04244, 0x0E237, 0x35690, 0x00323, + 0x04244, 0x0E214, 0x35694, 0x00360, 0x1D030, 0x1E100, 0x30339, 0x1801A, + 0x1A100, 0x04260, 0x19200, 0x04361, 0x1B310, 0x04462, 0x1A400, 0x04563, + 0x1A500, 0x04664, 0x1A600, 0x04765, 0x1A700, 0x04866, 0x1A800, 0x04967, + 0x1A900, 0x0406A, 0x0A002, 0x352BF, 0x040E0, 0x1D020, 0x040E1, 0x1F030, + 0x040E2, 0x1F040, 0x040E3, 0x1F050, 0x040E4, 0x1F060, 0x040E5, 0x1F070, + 0x040E6, 0x1F080, 0x040E7, 0x1F090, 0x2BC00, 0x00000, 0x342C0, 0x30339, + 0x2C2E0, 0x2C3E1, 0x2C4E2, 0x2C5E3, 0x2C6E4, 0x2C7E5, 0x2C8E6, 0x2C9E7, + 0x01230, 0x0401D, 0x0411C, 0x00D27, 0x00C01, 0x20006, 0x20100, 0x35AD5, + 0x18C01, 0x0EC10, 0x352F4, 0x0EC10, 0x342CD, 0x20108, 0x342D8, 0x2010E, + 0x20008, 0x1CC01, 0x352EC, 0x01A80, 0x01B90, 0x1DA00, 0x1FB10, 0x35AD7, + 0x018A0, 0x019B0, 0x342D7, 0x1CD01, 0x352F8, 0x20306, 0x20400, 0x20500, + 0x20600, 0x20700, 0x20800, 0x20900, 0x01A80, 0x01B90, 0x1DA00, 0x1FB10, + 0x35AE3, 0x018A0, 0x019B0, 0x342E3, 0x00100, 0x008FF, 0x009FF, 0x34301, + 0x30339, 0x0401D, 0x0411C, 0x04AE1, 0x0FA20, 0x0AAFC, 0x35301, 0x18801, + 0x1A900, 0x1D800, 0x1F910, 0x2C8EA, 0x2C9EB, 0x2A000, 0x0442C, 0x0C000, + 0x3530D, 0x04325, 0x01430, 0x20306, 0x19430, 0x19040, 0x1A100, 0x043C3, + 0x0A310, 0x35717, 0x04344, 0x0A302, 0x35317, 0x0031A, 0x34318, 0x0031E, + 0x1D030, 0x1E100, 0x04AC1, 0x0AA80, 0x3571A, 0x04ADB, 0x190A0, 0x1A100, + 0x12180, 0x35325, 0x00002, 0x00100, 0x34327, 0x18002, 0x1A100, 0x2C0D0, + 0x2C1D2, 0x043C2, 0x0A3E0, 0x0D230, 0x2C2C2, 0x2A000, 0x00506, 0x00400, + 0x05100, 0x05320, 0x0F130, 0x0D410, 0x18201, 0x18001, 0x1C501, 0x35730, + 0x2A000, 0x04AC1, 0x0AA80, 0x35339, 0x04AC1, 0x0AA80, 0x3573C, 0x2A000, + 0x30339, 0x043D8, 0x044D9, 0x18301, 0x1A400, 0x1D310, 0x1F420, 0x2BC00, + 0x2C1D8, 0x2C2D9, 0x2A000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x2C0CF, 0x34000, 0x00000 +}; + + +const int piper_macassist_data_len = (sizeof piper_wifi_macassist_ucode)/4; diff --git a/drivers/net/wireless/digiPiper/piperMacAssist.h b/drivers/net/wireless/digiPiper/piperMacAssist.h new file mode 100644 index 000000000000..27b2b77fb493 --- /dev/null +++ b/drivers/net/wireless/digiPiper/piperMacAssist.h @@ -0,0 +1,9 @@ + +#ifndef PIPER_MACASSIST_H_ +#define PIPER_MACASSIST_H_ + +extern const unsigned long piper_wifi_macassist_ucode[]; +extern const int piper_macassist_data_len; + + +#endif diff --git a/drivers/net/wireless/digiPiper/pipermain.h b/drivers/net/wireless/digiPiper/pipermain.h new file mode 100644 index 000000000000..dba31309e081 --- /dev/null +++ b/drivers/net/wireless/digiPiper/pipermain.h @@ -0,0 +1,381 @@ +#ifndef __PIPER_H_ +#define __PIPER_H_ + +#include <linux/completion.h> +#include <linux/if_ether.h> +#include <linux/spinlock.h> +#include <net/mac80211.h> +#include <linux/i2c.h> +#include "mac.h" + + +// #define WANT_DEBUG +#ifdef WANT_DEBUG +#define digi_dbg(fmt, arg...) \ + printk(KERN_ERR PIPER_DRIVER_NAME ": %s - " fmt, __func__, ##arg) +#else +#define digi_dbg(fmt, arg...) \ + do { } while (0) +#endif + +#define ERROR(x) printk(KERN_ALERT x) + +/* Debug levels */ +#define DSILENT 0xffff +#define DERROR 0xfff0 +#define DWARNING 0xffe0 +#define DNORMAL 0xffd0 +#define DVERBOSE 0xffc0 +#define DVVERBOSE 0xffb0 +#define DALL 0xffa0 + + +#define PIPER_DRIVER_NAME "piper" +#define DRV_VERS "0.1" + +/* Useful defines for AES */ +#define PIPER_EXTIV_SIZE 8 /* IV and extended IV size */ +#define MIC_SIZE 8 /* Message integrity check size */ +#define ICV_SIZE 4 +#define DATA_SIZE 28 /* Data frame header+FCS size */ +#define CCMP_SIZE (PIPER_EXTIV_SIZE + MIC_SIZE) /* Total CCMP size */ +#define EXPANDED_KEY_LENGTH (176) /* length of expanded AES key */ +#define PIPER_MAX_KEYS (4) +#define AES_BLOB_LENGTH (48) /* length of AES IV and headers */ + +/* Calibration constants */ +#define WCD_MAGIC "WCALDATA" +#define WCD_MAX_CAL_POINTS (8) +#define WCD_CHANNELS_BG (14) +#define WCD_CHANNELS_A (35) +#define WCD_B_CURVE_INDEX (0) +#define WCD_G_CURVE_INDEX (1) + +/* + * Set this #define to receive frames in the ISR. This may improve + * performance under heavy load at the expense of interrupt latency. + */ +#define WANT_TO_RECEIVE_FRAMES_IN_ISR (0) + + +typedef u64 u48; + +/* + * This enum lists the possible LED states. + */ +enum led_states { + led_shutdown, + led_adhoc, + led_not_associated, + led_associated +}; + +/* Available leds */ +enum wireless_led { + STATUS_LED, + ACTIVITY_LED, +}; + +#define WCD_HW_REV_MASK 0xf000 +#define WCD_HW_REV_PROTOTYPE 0x0000 +#define WCD_HW_REV_PILOT 0x1000 +#define WCD_HW_REV_A 0x2000 +#define WCD_PLATFORM_MASK 0x0ff0 +#define WCD_CCW9P_PLATFORM 0x0010 +#define WCD_CCW9M_PLATFORM 0x0020 + + +typedef struct nv_wcd_header { + char magic_string[8]; /* WCALDATA */ + char ver_major; /* Major version in ascii */ + char ver_minor; /* Minor version in ascii */ + u16 hw_platform; /* Hardware Platform used for calibration */ + u8 numcalpoints; /* Number of points per curve */ + u8 padding[107]; /* Reserved for future use */ + u32 wcd_len; /* Total length of the data section */ + u32 wcd_crc; /* Data section crc32 */ +} nv_wcd_header_t; + +typedef struct wcd_point { + s16 out_power; /* Output Power */ + u16 adc_val; /* Measured ADC val */ + u8 power_index; /* Airoha Power Index */ + u8 reserved[3]; /* For future use */ +} wcd_point_t; + +typedef struct wcd_curve { + u8 max_adc_value; /* maximum allowed ADC value for this curve */ + u8 reserved[3]; /* Resered for future use */ + /* Calibration curve points */ + wcd_point_t points[WCD_MAX_CAL_POINTS]; +} wcd_curve_t; + +typedef struct wcd_data { + nv_wcd_header_t header; + wcd_curve_t cal_curves_bg[WCD_CHANNELS_BG][2]; + wcd_curve_t cal_curves_a[WCD_CHANNELS_A]; +} wcd_data_t; + +typedef enum { + op_write, + op_or, + op_and +} reg_op_t; + +enum antenna_select { + ANTENNA_BOTH = 0, + ANTENNA_1, + ANTENNA_2, +}; + +typedef struct { + bool loaded; + bool enabled; + bool weSentLastOne; +} piperBeaconInfo_t; + +typedef enum { + RECEIVED_ACK, + TX_COMPLETE, + OUT_OF_RETRIES, + TX_NOT_DONE +} tx_result_t; + + +/* Structure that holds the information we need to support H/W AES encryption */ +struct piperKeyInfo { + bool valid; /* indicates if this record is valid */ + u8 addr[ETH_ALEN]; /* MAC address associated with key */ + u32 expandedKey[EXPANDED_KEY_LENGTH / sizeof(u32)]; + u48 txPn; /* packet number for transmit */ + u48 rxPn; /* expected receive packet number */ +}; + +/* rf */ +struct digi_rf_ops { + const char *name; + void (*init) (struct ieee80211_hw *, int); + int (*stop) (struct ieee80211_hw *); + int (*set_chan) (struct ieee80211_hw *, int chan); + int (*set_chan_no_rx) (struct ieee80211_hw *, int chan); + int (*set_pwr) (struct ieee80211_hw *, uint8_t val); + void (*set_pwr_index) (struct ieee80211_hw *, unsigned int val); + void (*power_on) (struct ieee80211_hw *, bool want_power_on); + void (*getOfdmBrs) (int channel, u64 brsBitMask, unsigned int *ofdm, + unsigned int *psk); + enum ieee80211_band (*getBand) (int); + int (*getFrequency) (int); + void (*set_hw_info)(struct ieee80211_hw *, int channel, u16 hw_platform); + const struct ieee80211_rate *(*getRate) (unsigned int); + int channelChangeTime; + s8 maxSignal; + struct ieee80211_supported_band *bands; + u8 n_bands; +}; + +struct piper_stats { + u32 rx_overruns; + u32 tx_complete_count; + u32 tx_start_count; + u32 tx_total_tetries; + u32 tx_retry_count[IEEE80211_TX_MAX_RATES]; + u32 tx_retry_index; + struct ieee80211_tx_queue_stats tx_queue; + struct ieee80211_low_level_stats ll_stats; + spinlock_t lock; +}; + +enum piper_ps_mode { + PS_MODE_LOW_POWER, + PS_MODE_FULL_POWER +}; + +enum piper_ps_state { + PS_STATE_WAIT_FOR_BEACON, + PS_STATE_WAIT_FOR_STOP_TRANSMIT_EVENT, + PS_STATE_WAIT_FOR_TRANSMITTER_DONE, + PS_STATE_WAIT_FOR_WAKEUP_ALARM, + PS_STATE_WAIT_FOR_TRANSMITTER_DONE_EVENT +}; + +struct piper_priv; + +struct piper_ps { + u32 beacon_int; + u16 aid; + volatile unsigned int scan_timer; + unsigned int sleep_time; + struct timer_list timer; + enum piper_ps_mode mode; + enum piper_ps_state state; + unsigned int this_event; + spinlock_t lock; + volatile bool power_management; + volatile bool poweredDown; + volatile bool rxTaskletRunning; + volatile bool allowTransmits; + volatile bool stopped_tx_queues; + volatile unsigned int frames_pending; +}; + +typedef void (*tx_skb_return_cb_t)(struct ieee80211_hw *hw, + struct sk_buff *skb); + +struct piper_queue { + struct sk_buff *skb; + tx_skb_return_cb_t skb_return_cb; +}; + +#define PIPER_TX_QUEUE_SIZE (16) +#define NEXT_TX_QUEUE_INDEX(x) ((x+1) & 0xf) + +struct piper_priv { + const char *drv_name; + char debug_cmd[32]; + u32 version; + struct piper_pdata *pdata; + struct ieee80211_hw *hw; + struct ieee80211_key_conf txKeyInfo; + struct ieee80211_cts ctsFrame; + struct ieee80211_rts rtsFrame; + struct ieee80211_rate *calibrationTxRate; + struct piper_stats pstats; + struct tasklet_struct rx_tasklet; + struct tasklet_struct tx_tasklet; + spinlock_t tx_tasklet_lock; + bool tx_tasklet_running; + spinlock_t tx_queue_lock; + struct piper_queue tx_queue[PIPER_TX_QUEUE_SIZE]; + unsigned int tx_queue_head; + unsigned int tx_queue_tail; + unsigned int tx_queue_count; + bool expectingAck; + struct timer_list tx_timer; + struct timer_list led_timer; + enum led_states led_state; + struct piper_ps ps; + bool power_save_was_on_when_suspended; + struct access_ops *ac; + spinlock_t aesLock; + struct digi_rf_ops *rf; + void *__iomem vbase; + int irq; + tx_result_t tx_result; + int tx_signal_strength; + + /* Function callbacks */ + int (*init_hw) (struct piper_priv *, enum ieee80211_band); + int (*deinit_hw) (struct piper_priv *); + void (*set_irq_mask_bit) (struct piper_priv *, u32); + void (*clear_irq_mask_bit) (struct piper_priv *, u32); + u16(*get_next_beacon_backoff) (void); + int (*load_beacon) (struct piper_priv *, u8 *, u32); + int (*rand) (void); + void (*tx_calib_cb) (struct piper_priv *); + int (*set_antenna) (struct piper_priv *, enum antenna_select); + int (*set_tracking_constant) (struct piper_priv * piperp, + unsigned megahertz); + void (*adjust_max_agc) (struct piper_priv * piperp, unsigned int rssi, + _80211HeaderType * header); + + /* General settings */ + enum nl80211_iftype if_type; + bool areWeAssociated; + bool is_radio_on; + bool use_short_preamble; + int channel; + int tx_power; + u8 bssid[ETH_ALEN]; + bool tx_cts; + bool tx_rts; + enum antenna_select antenna; + int power_duty; + + /* AES stuff */ + bool use_hw_aes; + u32 aes_key_count; + struct piperKeyInfo key[PIPER_MAX_KEYS]; + u32 tx_aes_key; + u32 tx_aes_blob[AES_BLOB_LENGTH / sizeof(u32)]; + + /* IBSS */ + piperBeaconInfo_t beacon; +}; + +struct access_ops { + spinlock_t reg_lock; + int (*wr_reg) (struct piper_priv *, u8 reg, u32 val, reg_op_t op); + u32(*rd_reg) (struct piper_priv *, u8 reg); + int (*wr_fifo) (struct piper_priv *, u8 addr, u8 * buf, int len); + int (*rd_fifo) (struct piper_priv *, u8 addr, u8 * buf, int len); +}; + +struct piper_pdata { + u8 macaddr[6]; + int rst_gpio; + int irq_gpio; + int status_led_gpio; + int rf_transceiver; + wcd_data_t wcd; + int i2c_adapter_num; + struct piper_priv *piperp; + + /* Platform callbacks */ + void (*reset) (struct piper_priv *, int); + int (*init) (struct piper_priv *); + int (*late_init) (struct piper_priv *); + void (*set_led) (struct piper_priv *, enum wireless_led, int); + void (*early_resume) (struct piper_priv *); +}; + +/* main */ +int piper_alloc_hw(struct piper_priv **priv, size_t priv_sz); +void piper_free_hw(struct piper_priv *priv); +int piper_register_hw(struct piper_priv *priv, struct device *dev, + struct digi_rf_ops *rf); +void piper_unregister_hw(struct piper_priv *priv); +irqreturn_t piper_irq_handler(int irq, void *dev_id); +void packet_tx_done(struct piper_priv *piperp, + tx_result_t result, int singalstrength); +void piper_rx_tasklet(unsigned long context); +void piper_tx_tasklet(unsigned long context); +bool piper_prepare_aes_datablob(struct piper_priv *digi, + unsigned int keyIndex, u8 * aesBlob, + u8 * frame, u32 length, bool isTransmit); +void piper_load_mac_firmware(struct piper_priv *piperp); +void piper_load_dsp_firmware(struct piper_priv *piperp); +int piper_spike_suppression(struct piper_priv *piperp, bool retry); +void piper_reset_mac(struct piper_priv *piperp); +void piper_set_macaddr(struct piper_priv *piperp); +int piper_hw_tx_private(struct ieee80211_hw *hw, struct sk_buff *skb, tx_skb_return_cb_t fn); +void piper_empty_tx_queue(struct piper_priv *piperp); +int piper_tx_enqueue(struct piper_priv *piperp, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb); +struct sk_buff *piper_tx_getqueue(struct piper_priv *piperp); +bool piper_tx_queue_half_full(struct piper_priv *piperp); +void piper_set_macaddr(struct piper_priv *piperp); +void piper_MacEnterActiveMode(struct piper_priv *piperp, bool want_spike_suppression); +int piper_MacEnterSleepMode(struct piper_priv *piperp, bool force); +void piper_sendNullDataFrame(struct piper_priv *piperp, bool isPowerSaveOn); +void piper_ps_rx_task_exiting(struct piper_priv *piperp); +void piper_ps_scan_event(struct piper_priv *piperp); + +/* + * Defines for debugging function dumpRegisters + */ +#define MAIN_REGS (1) +#define MAC_REGS (2) +#define RF_REGS (4) +#define FRAME_BUFFER_REGS (8) +#define CTRL_STATUS_REGS (0x10) +#define FIFO_REGS (0x20) +#define IRQ_REGS (0x40) +#define ALL_REGS (0xf) + +void digiWifiDumpRegisters(struct piper_priv *digi, unsigned int regs); +void digiWifiDumpSkb(struct sk_buff *skb); + +extern void digiWifiDumpWordsAdd(unsigned int word); +extern void digiWifiDumpWordsDump(void); +extern void digiWifiDumpWordsReset(void); + +#endif /* __PIPER_H_ */ diff --git a/drivers/net/wireless/digi_wi_g.c b/drivers/net/wireless/digi_wi_g.c new file mode 100644 index 000000000000..af9dd8eb4694 --- /dev/null +++ b/drivers/net/wireless/digi_wi_g.c @@ -0,0 +1,5021 @@ +/* + * wifi/digi_wi_g.c + * + * Copyright (C) 2007 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version2 as published by + * the Free Software Foundation. +*/ +/* + * !Revision: $Revision: 1.147 $ + * !Author: Bernd Westermann/Markus Pietrek/Miriam Ruiz + * !Descr: + * !References: [1] 802.11G_Programming_Guide_1.0 + * [2] /net/m/ISO_STANDARDS/802.11g-2003.pdf + * [3] Wireless LANs: 802.11 WLAN Technologie und praktische + * Umsetzung im Detail +*/ + +#include <linux/module.h> +#include <linux/platform_device.h> /* platform_get */ +#include <linux/init.h> /* __init */ +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ioport.h> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <linux/crc32.h> +#include <linux/vmalloc.h> /* vmalloc */ +#include <linux/random.h> /* get_random_bytes */ +#include <linux/workqueue.h> + +#include <asm/uaccess.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/irq.h> /* NO_IRQ */ +#include <mach/regs-mem.h> +#include <mach/regs-sys-common.h> + +/* for supported_rates */ +#include <linux/../../net/ieee80211/softmac/ieee80211softmac_priv.h> +#undef assert + +#include "digi_wi_g.h" + +#define REMOVE_ME + +#ifdef CONFIG_DIGI_WI_G_DEBUG +# define DBG_INIT 0x0001 +# define DBG_MINOR 0x0002 +# define DBG_RX 0x0004 +# define DBG_TX 0x0008 +# define DBG_INT 0x0010 +# define DBG_INTERFACE 0x0020 +# define DBG_LINK 0x0040 +# define DBG_SECURITY 0x0080 +# define DBG_UPDATE_RATE 0x0100 +# define DBG_TIMEOUT 0x0200 +# define DBG_TIME 0x0800 +# define DBG_MAJOR 0x1000 +# define DBG_HW_IO 0x2000 +# define DBG_ERROR 0x4000 +# define DBG_ERROR_CRIT 0x8000 + +static unsigned int dw_dbg_level = DBG_ERROR_CRIT; + +# define DBG(flag, format, ...) \ + do { \ + if ((dw_dbg_level & (flag)) == (flag)) \ + printk(KERN_INFO format "\n", ##__VA_ARGS__); \ + } while (0) +# define DBG_FN(flag) \ + do { \ + if ((dw_dbg_level & (flag)) == (flag)) \ + printk(KERN_INFO DRIVER_NAME "@%s:line %d\n", __func__, __LINE__); \ + } while (0) +# define DBG_EXEC(expr) \ + do { \ + expr; \ + } while (0) + +# define ASSERT(expr) \ + do { \ + if (!(expr)) { \ + ERROR("Assertion failed! line %d %s", \ + __LINE__,#expr); \ + } \ + } while (0) +# define REQUIRE_LOCKED_ANY(lock) \ + do { \ + if (unlikely(!spin_is_locked(&lock))) { \ + ERROR(#lock " should be locked\n"); \ + dump_stack(); \ + } \ + } while (0) + +# define REQUIRE_UNLOCKED_ANY(lock) \ + do { \ + if (unlikely(spin_is_locked(&lock))) { \ + ERROR(#lock " should be unlocked\n"); \ + dump_stack(); \ + } \ + } while (0) + +# define REQUIRE_LOCKED(priv) REQUIRE_LOCKED_ANY(priv->lock) +# define REQUIRE_UNLOCKED(priv) REQUIRE_UNLOCKED_ANY(priv->lock) + +/* for timing collection */ +enum { + INT = 0, + INT_DONE, + INT_TASKLET, + INT_TASKLET_DONE, + RX_FIFO, + RX_AES_FIFO, + TX_FIFO, + TX_AES_FIFO, + RX_TASKLET, + RX_TASKLET_DONE, + RX_FRAME_TO_STACK, + RX_FRAME_TO_STACK_DONE, + RX_PROCESS_FRAME, + RX_PROCESS_FRAME_RX, + RX_PROCESS_FRAME_DONE, + RX_DECRYPT, + RX_DECRYPT_DONE, + RX_OVERRUN, + RX_WORK, + RX_WORK_DONE, + RX_PAUSE, + START_XMIT, + START_XMIT_DONE, + TX_TASKLET, + TX_TASKLET_DONE, + TX_SEND, + INTERMEDIATE, + INTERMEDIATE2, + SET_CHANNEL, +}; + +# define NAME(x) [x] = #x + +static const char* ns_hperf_type_names[] = { + NAME(INT), + NAME(INT_DONE), + NAME(INT_TASKLET), + NAME(INT_TASKLET_DONE), + NAME(RX_FIFO), + NAME(RX_AES_FIFO), + NAME(TX_FIFO), + NAME(TX_AES_FIFO), + NAME(RX_TASKLET), + NAME(RX_TASKLET_DONE), + NAME(RX_FRAME_TO_STACK), + NAME(RX_FRAME_TO_STACK_DONE), + NAME(RX_PROCESS_FRAME), + NAME(RX_PROCESS_FRAME_RX), + NAME(RX_PROCESS_FRAME_DONE), + NAME(RX_DECRYPT), + NAME(RX_DECRYPT_DONE), + NAME(RX_OVERRUN), + NAME(RX_WORK), + NAME(RX_WORK_DONE), + NAME(RX_PAUSE), + NAME(START_XMIT), + NAME(START_XMIT_DONE), + NAME(TX_TASKLET), + NAME(TX_TASKLET_DONE), + NAME(TX_SEND), + NAME(INTERMEDIATE), + NAME(INTERMEDIATE2), + NAME(SET_CHANNEL), +}; + +#define NS_HPERF_SIZE 2048 + +/* ns_hperf_type_names needs to be define first before including */ +//# include <asm/arch-ns9xxx/ns9xxx_hperf.h> + +/* only in debug case we are interested in revision */ +# define REVISION " $Revision: 1.147 $" +#else /* CONFIG_DIGI_WI_G_DEBUG */ +# define DBG(flag, format, ...) do {} while (0) +# define DBG_FN(flag) do {} while (0) +# define DBG_EXEC(flag) do {} while (0) +# define ASSERT(expr) do {} while (0) +# define REQUIRE_LOCKED(lock) do {} while (0) +# define REQUIRE_LOCKED_ANY(lock) do {} while (0) +# define REQUIRE_UNLOCKED(lock) do {} while (0) +# define REQUIRE_UNLOCKED_ANY(lock) do {} while (0) +# define REVISION "" +#endif /* CONFIG_DIGI_WI_G_DEBUG */ + +#define CLEAR(x) memset(&(x), 0, sizeof((x))) +#define dw_iosetbits32(offs, mask) dw_iowrite32(dw_ioread32(offs)|(mask),(offs)) +#define dw_iocleanbits32(offs, mask) dw_iowrite32(dw_ioread32(offs)&~(mask),(offs)) + +#define BASIC_RATE_MASK(x) ((x) & ((unsigned char) ~IEEE80211_BASIC_RATE_MASK)) +#define BASIC_RATE(x) ((x) | IEEE80211_BASIC_RATE_MASK) + +#define ERROR(format, ...) printk(KERN_ERR "*** ERROR " DRIVER_NAME\ + " @ %s: " format "\n", \ + __func__, \ + ##__VA_ARGS__) +#define ERRORL(format, ...) printkl(KERN_ERR "*** ERROR " DRIVER_NAME\ + " @ %s: " format "\n", \ + __func__, \ + ##__VA_ARGS__) +#define WARNL(format, ...) printkl(KERN_INFO DRIVER_NAME\ + " @ %s: " format "\n", \ + __func__, \ + ##__VA_ARGS__) +#define to_dev(pdev) platform_get_drvdata(pdev) + +#define rate_is_enough(priv) ((priv)->rate.tx_data_any >= 4) +/* at least 3/4 are acknowledged */ +#define rate_is_success(priv) \ + (4 * (priv)->rate.tx_data_ack > 3 * (priv)->rate.tx_data_any) +/* less than 1/4 are not acknowledged */ +#define rate_is_failure(priv) \ + (4 * (priv)->rate.tx_data_ack < (priv)->rate.tx_data_any) + +#define MAC_GROUP 0x01 /* broadcast or multicast address */ +#define IS_GROUP_ADDR(addr) (addr[ 0 ] & MAC_GROUP) + +#define IS_FRAME(fc, ftype, stype) \ + (((fc) & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) == (ftype | stype)) +#define IS_DATA(fc) IS_FRAME(fc, IEEE80211_FTYPE_DATA, IEEE80211_STYPE_DATA) +#define IS_MGMT(fc) IS_FRAME(fc, IEEE80211_FTYPE_MGMT, 0 /* any */) + + +#define ACK_SIZE 14 /* ACK frame size */ + +#define USE_SHORTPRE(fi, rate) \ + (((rate) != IEEE80211_CCK_RATE_1MB) && fi->use_short_preamble) +/* from Net+OS: mac_rate.c */ +/* not including SIFS and PLCP preamble/header */ +#define LENGTH(bytes, rate) ((16 * (bytes) + (rate) - 1) / (rate)) +/* Length (in usecs) of SIFS and PLCP preamble/header. */ +#define PRE_LEN( fi, rate) (USE_SHORTPRE(fi, rate) ? 106 : 202) +/* Duration (in usecs) of an OFDM frame at rate (in 500kbps units) + * including SIFS and PLCP preamble/header */ +#define OFDM_DUR(bytes, rate) (36 + 4 * ((4 * (bytes) + (rate) + 10) / (rate))) + +typedef struct { + u8 bps; /* bit rate in 500kbps units */ + u8 ofdm_code; /* ofdm rate code, 0 if not ofdm */ + u16 ack_len; /* duration of ack or cts in usecs */ +} rate_info_t; + +typedef int dw_rate_index_t; + +/* separate maintained static because of polling performance */ +static void* __iomem vbase = NULL; + +static void dw_cw_set(const dw_priv_t* priv, u16 fc); +static void dw_rx_fifo_error(dw_priv_t* priv); +static int dw_rx_frame_get_length(const dw_frame_rx_t* frame); +static int dw_rx_frame_is_duplicate(dw_priv_t* priv, + const struct ieee80211_hdr_3addr* hdr); +static void dw_rx_frame_give_to_stack(dw_priv_t* priv, dw_frame_rx_t* frame); +static void dw_rx_frame_fetch(dw_priv_t* priv); +static void dw_rx_tasklet_handler(unsigned long data); +static void dw_tx_set_plcp(dw_frame_tx_info_t* fi, int fragment, int rate); +static int dw_tx_frame_prepare(dw_priv_t* priv, + dw_frame_tx_info_t* fi, struct ieee80211_txb* txb); +static void dw_tx_fragment_send(dw_priv_t* priv, dw_frame_tx_t* frame); +static void dw_tx_tasklet_handler(unsigned long data); +static irqreturn_t dw_int(int irq, void *dev_id); +static void dw_tx_reset(dw_priv_t* priv); +static void dw_tx_wait_for_idle_and_pause(dw_priv_t* priv); +static void dw_tx_continue_queue(dw_priv_t* priv); +static int dw_ieee80211_hard_start_xmit(struct ieee80211_txb* txb, + struct net_device* dev, int priority); +static void dw_update_status_led(dw_priv_t* priv); +static void dw_rate_reset(dw_priv_t* priv); +static void dw_rate_update(dw_priv_t* priv); +static void dw_management_timer(u_long a); + +/* initialization stuff */ +static int __init dw_hw_init_card(struct net_device* dev); +static int __init dw_start_dev(struct platform_device* pdev); +static int __init dw_probe( struct platform_device* pdev); +static void dw_stop_dev(struct platform_device* pdev); +static int dw_remove(struct platform_device* pdev); +static void dw_release_device(struct device* dev); + +static void dw_set_channel(dw_priv_t*priv, u8 channel); + +/* softmac/ieee interface stuff */ +static void dw_softmac_txrates_change(struct net_device* dev, u32 changes); +static int dw_wx_set_encode(struct net_device* dev, + struct iw_request_info* info, + union iwreq_data* data, char* extra); +static int dw_wx_set_encodeext(struct net_device* dev, + struct iw_request_info* info, union iwreq_data* data, + char* extra); +static void dw_softmac_notify_authenticated( + struct net_device* dev, int event_type, void* context); +static void dw_softmac_set_chan(struct net_device* dev, u8 channel); +static void dw_softmac_set_bssid_filter( + struct net_device *dev, const u8* bssid); +static void dw_ieee80211_set_security(struct net_device* dev, + struct ieee80211_security* sec); +static int dw_geo_init(dw_priv_t* priv); +static int dw_open(struct net_device* dev); +static int dw_close(struct net_device* dev); +static void dw_set_multicast_list(struct net_device* dev); + +/* ********** Local Variables ********** */ + +static const char* dw_version = + "WiFi: " DRIVER_NAME " driver " COMPILE_TIME REVISION; + +/* define the resources the driver will use */ +static struct resource dw_mem = { + .name = DRIVER_NAME, + .start = MAC_BASE_PHYS, + .end = MAC_BASE_PHYS + MAC_BASE_SIZE, + .flags = IORESOURCE_MEM, +}; + +/* describes the device */ +static struct platform_device dw_device = { + .id = -1, + .name = DRIVER_NAME,/* must be equal to platform-driver.driver.name*/ + .resource = &dw_mem, + .dev = { + .release = dw_release_device, + }, +}; + +/* describes the driver */ +static struct platform_driver dw_driver = { + .probe = dw_probe, + .remove = dw_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +/* RF transceiver frequency divider for each channel */ +#if defined(CONFIG_DIGI_WI_G_UBEC_JD) +static const struct { + u16 integer; + u16 fraction; +} freq_table[] = { + { 0, 0 }, + { 0x6847, 0x0999 }, /* 1 (2412 MHz) */ + { 0x6847, 0x099b }, /* 2 (2417 MHz) */ + { 0x6867, 0x0998 }, /* 3 */ + { 0x6867, 0x099a }, /* 4 */ + { 0x6867, 0x0999 }, /* 5 */ + { 0x6867, 0x099b }, /* 6 */ + { 0x6857, 0x0998 }, /* 7 */ + { 0x6857, 0x099a }, /* 8 */ + { 0x6857, 0x0999 }, /* 9 */ + { 0x6857, 0x099b }, /* 10 */ + { 0x6877, 0x0998 }, /* 11 */ + { 0x6877, 0x099a }, /* 12 */ + { 0x6877, 0x0999 }, /* 13 (2472 MHz) */ + { 0x684f, 0x0ccc }, /* 14 (2484 MHz) */ +}; +#elif defined(CONFIG_DIGI_WI_G_UBEC_HC) +static const struct { + u16 integer; + u16 fraction; +} freq_table[] = { + { 0, 0 }, + { 0x04c7, 0x0999 }, /* 1 (2412 MHz) */ + { 0x04c7, 0x099b }, /* 2 (2417 MHz) */ + { 0x04e7, 0x0998 }, /* 3 */ + { 0x04e7, 0x099a }, /* 4 */ + { 0x04e7, 0x0999 }, /* 5 */ + { 0x04e7, 0x099b }, /* 6 */ + { 0x04d7, 0x0998 }, /* 7 */ + { 0x04d7, 0x099a }, /* 8 */ + { 0x04d7, 0x0999 }, /* 9 */ + { 0x04d7, 0x099b }, /* 10 */ + { 0x04f7, 0x0998 }, /* 11 */ + { 0x04f7, 0x099a }, /* 12 */ + { 0x04f7, 0x0999 }, /* 13 (2472 MHz) */ + { 0x04cf, 0x0ccc }, /* 14 (2484 MHz) */ +}; +#endif +/* see [2] 10.4.42. */ +static const u8 dw_rates[ RATES_SUPPORTED ] = { + BASIC_RATE(IEEE80211_CCK_RATE_1MB), + BASIC_RATE(IEEE80211_CCK_RATE_2MB), + BASIC_RATE(IEEE80211_CCK_RATE_5MB), + /* we need to give 11MB also to AP, as otherwise we are not + * authenticated or softmac ignores it because rates don't match */ + BASIC_RATE(IEEE80211_CCK_RATE_11MB), + IEEE80211_OFDM_RATE_6MB, + IEEE80211_OFDM_RATE_9MB, + IEEE80211_OFDM_RATE_12MB, + IEEE80211_OFDM_RATE_18MB, + IEEE80211_OFDM_RATE_24MB, + IEEE80211_OFDM_RATE_36MB, + IEEE80211_OFDM_RATE_48MB, + IEEE80211_OFDM_RATE_54MB, +}; + +/* basic rate will be calculated */ +#define MK_CCK(rate, ofdm) \ + { .bps = rate, .ofdm_code = ofdm, .ack_len = LENGTH(ACK_SIZE, rate) } +#define MK_OFDM(rate, ofdm) \ + { .bps = rate, .ofdm_code = ofdm, .ack_len = OFDM_DUR(ACK_SIZE, rate) } +/* they need to be ordered in their bitrate, because softmac returns us + * ap_ri.rate this way, and rates_info uses the indexes. */ +static const rate_info_t rates_info[ RATES_SUPPORTED ] = { + MK_CCK( IEEE80211_CCK_RATE_1MB, 0 ), + MK_CCK( IEEE80211_CCK_RATE_2MB, 0 ), + MK_CCK( IEEE80211_CCK_RATE_5MB, 0 ), + MK_OFDM(IEEE80211_OFDM_RATE_6MB, 0xb), + MK_OFDM(IEEE80211_OFDM_RATE_9MB, 0xf), + MK_CCK( IEEE80211_CCK_RATE_11MB, 0 ), + MK_OFDM(IEEE80211_OFDM_RATE_12MB, 0xa), + MK_OFDM(IEEE80211_OFDM_RATE_18MB, 0xe), + MK_OFDM(IEEE80211_OFDM_RATE_24MB, 0x9), + MK_OFDM(IEEE80211_OFDM_RATE_36MB, 0xd), + MK_OFDM(IEEE80211_OFDM_RATE_48MB, 0x8), + MK_OFDM(IEEE80211_OFDM_RATE_54MB, 0xc), +}; +#undef MK_OFDM +#undef MK_CCK + +#ifdef CONFIG_DIGI_WI_G_HW_ENCRYPTION +# define DW_SW_AES_DEFAULT 0 +#else +# define DW_SW_AES_DEFAULT 1 +#endif /* CONFIG_DIGI_WI_G_HW_ENCRYPTION */ + +static unsigned int dw_sw_aes = DW_SW_AES_DEFAULT; +static unsigned int dw_cfg_vco = 0; /* use built-in */ + +/* Tables for the conversion of the signal strenght to dBm */ +/* Map LNA value to gain value */ +static const unsigned char lnatable[] = {0, 0, 23, 39}; +/* Map high gain values to dBm */ +static const char gaintable[] = {-82, -84, -85, -86, -87, -89, -90, -92, -94, -98}; + +/* ********** Local Functions ********** */ + +/* ********** inline stuff ********** */ + +/** + * dw_to_48 - converts to numbers to an 48bit number + */ +static inline u48 dw_to_48(u32 n1, u16 n0) +{ + return (((u64) n1) << 16) | n0; +} + +/** + * dw_48_inc - increments a 48bit number, wrapping aroung on 1<<48 + */ +static inline void dw_inc_48(u48* n) +{ + (*n)++; + *n &= ((u64) 1 << 48) - 1; +} + +/** + * dw_ioread32 - reads from memory mapped FPGA + */ +static inline u32 dw_ioread32(u32 offs) +{ + u32 val = ioread32(vbase + offs); + + DBG(DBG_HW_IO, "R %04x = %x", offs, val); + + return val; +} + +/** + * dw_write32 - writes to memory mapped FPGA + */ +static inline void dw_iowrite32(u32 val, u32 offs) +{ + DBG(DBG_HW_IO, "W %04x = %x", offs, val); + iowrite32(val, vbase + offs); +} + +/** + * dw_hw_get_status - returns the FPGA's status + */ +static inline u32 dw_hw_get_status(const dw_priv_t* priv) +{ + REQUIRE_LOCKED(priv); + + return dw_ioread32(HW_GEN_STATUS); +} + +/** + * dw_hw_memcpy_to - copies memory to FPGA @ offs + */ +static inline void dw_hw_memcpy_to(u32 offs, const void* _src, int len) +{ + u32* src = (u32 *) _src; + + for (; len > 0; len -= 4, offs += 4) + dw_iowrite32(cpu_to_be32(*src++), offs); +} + +/** + * dw_hw_memcpy_from - copies memory from FPGA @ offs + */ +static inline void dw_hw_memcpy_from(void* _dst, u32 offs, int len) +{ + u32* dst = (u32 *) _dst; + + for (; len > 0; len -= 4, offs += 4) + *dst++ = be32_to_cpu(dw_ioread32(offs)); +} + +static inline void dw_hw_write_rf(u8 addr, u32 data) +{ + dw_iowrite32((((u32) addr) << 20) | data, HW_SPI_DATA); + + udelay(10); +} + +/** + * dw_hw_read_fifo - read's the FIFO's and swaps data + * + * no check for underrun is performed + */ +static inline void dw_hw_read_fifo(void * _dst, int _len) +{ + u32* dst = (u32 *) _dst; + int len = _len; + + if (dw_ioread32(HW_GEN_STATUS) & STAT_RXFE) + printk("Reading from an EMPTY RX FIFO\n"); + + for (; len >= 32; len -= 32) { + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); + } + + for (; len > 0; len -= 4) + *dst++ = be32_to_cpu(dw_ioread32(HW_DATA_FIFO)); +} + +/** + * dw_hw_write_fifo - writes swapped data to FIFO + * + * no check for overrun is performed + */ +static inline void dw_hw_write_fifo(const void* _src, int _len) +{ + const u32* src = (const u32 *) _src; + int len = _len; + + for (; len >= 32; len -= 32) { + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); + } + + for (; len > 0; len -= 4) + dw_iowrite32(cpu_to_be32(*src++), HW_DATA_FIFO); +} + +/** + * dw_hw_aes_read_fifo - read's the AES FIFO's and swaps data + * + */ +static inline void dw_hw_aes_read_fifo(void* _dst, int len) +{ + int timeout = AES_BUSY_TIMEOUT; + u32* dst = (u32 *) _dst; + + while (len > 0) { + if (!(dw_ioread32(HW_RSSI_AES) & AES_EMPTY)) { + *dst++ = be32_to_cpu(dw_ioread32(HW_AES_FIFO)); + len -= 4; + } else { + /* !TODO. No calibration. When interrupts are enabled, use jiffies */ + if (!timeout) { + ERROR("Timeout on read AES FIFO @ %i", len); + break; + } + timeout--; + } + } /* while (len > 0) */ +} + +/** + * dw_hw_aes_read_fifo_noswap - read's the AES FIFO's + * + */ +static inline void dw_hw_aes_read_fifo_noswap(void* _dst, int len) +{ + int timeout = AES_BUSY_TIMEOUT; + u32* dst = (u32 *) _dst; + + while (len > 0) { + if (!(dw_ioread32(HW_RSSI_AES) & AES_EMPTY)) { + *dst++ = dw_ioread32(HW_AES_FIFO); + len -= 4; + } else { + /* !TODO: No calibration. When interrupts are enabled, use jiffies */ + if (!timeout) { + ERROR("Timeout on read AES FIFO @ %i", len); + break; + } + timeout--; + } + } /* while (len > 0) */ +} + +/** + * dw_hw_aes_write_fifo - writes swapped data to AES FIFO + * + * no check for overrun is performed + */ +static inline void dw_hw_aes_write_fifo(const void* _src, int len) +{ + const u32* src = (const u32 *) _src; + int timeout = AES_BUSY_TIMEOUT; + + while (len > 0) { + if (!(dw_ioread32(HW_RSSI_AES) & AES_FULL)) { + dw_iowrite32(cpu_to_be32(*src++), HW_AES_FIFO); + len -= 4; + } else { + /* !TODO: No calibration. When interrupts are enabled, use jiffies */ + if (!timeout) { + ERROR("Timeout on write AES FIFO"); + break; + } + timeout--; + } + } /* while (len > 0) */ +} + +/** + * dw_hw_aes_write_fifo_noswap - writes to AES FIFO + * + * no check for overrun is performed + */ +static inline void dw_hw_aes_write_fifo_noswap(const void* _src, int len) +{ + const u32* src = (const u32 *) _src; + int timeout = AES_BUSY_TIMEOUT; + + while (len > 0) { + if (!(dw_ioread32(HW_RSSI_AES) & AES_FULL)) { + dw_iowrite32(*src++, HW_AES_FIFO); + len -= 4; + } else { + /* !TODO: No calibration. When interrupts are enabled, use jiffies */ + if (!timeout) { + ERROR("Timeout on write AES FIFO"); + break; + } + timeout--; + } + } /* while (len > 0) */ +} + +/** + * dw_hw_aes_wait - waits until AES is finished + * + * @return: 0 on timeout, otherwise > 1 + */ +static inline int dw_hw_aes_wait(void) +{ + int timeout = AES_BUSY_TIMEOUT; + + /* !TODO: redesign it to run with interrupts enabled, then use jiffies */ + DBG_FN(DBG_TIMEOUT); + + while (timeout && (dw_ioread32(HW_RSSI_AES) & AES_BUSY)) { + timeout--; + ndelay(1); + } + + if (!timeout) + ERROR("Timedout on AES"); + + return timeout; +} + +/** + * dw_channel_to_freq_a - calculates frequency out of channel (for 802.11a) + */ +static inline int dw_channel_to_freq_a(u8 channel) +{ + return (5000 + (5 * channel)); +} + +/** + * dw_channel_to_freq_bg - calculates frequency out of channel (for 802.11b/g) + */ +static inline int dw_channel_to_freq_bg(u8 channel) +{ + int freq; + + if (14 == channel) + freq = 2484; + else + freq = 2407 + (5 * channel); + + return freq; +} + +static inline void dw_set_led_on(int led, u8 on) +{ + gpio_set_value(led, !on); +} + +/** + * dw_list_move - moves a list from src and appends it to dst + */ +static inline void dw_list_move(struct list_head* dst, struct list_head* src) +{ + struct list_head* cursor; + struct list_head* next; + + list_for_each_safe(cursor, next, src) + list_move_tail(cursor, dst); +} + +static inline void dw_hw_set_vco(int channel) +{ + u32 vco = dw_cfg_vco; + + if (!vco) +#if defined(CONFIG_DIGI_WI_G_UBEC_JD) + vco = 0x46662; +#elif defined(CONFIG_DIGI_WI_G_UBEC_HC) + vco = 0x3020; +#else + BUG(); +#endif + dw_hw_write_rf(3, vco); +} + +/** + * dw_rate_info - returns the index to rates_basic/rates_info + * + * bitrate is in 500kbps + */ +static inline dw_rate_index_t dw_rate_info_index(int bitrate) +{ + dw_rate_index_t i = 0; + + while (i < ARRAY_SIZE(rates_info)) { + if (rates_info[ i ].bps == bitrate) + return i; + i++; + } + + ERROR("Unsupported rate %i\n", bitrate); + + return 0; +} + +/** + * dw_rx_pause - pauses the receiver + * + * This will lead probably to FIFO overruns. In this case, the FPGA will not + * send the acknowledgment, so the sender will try again. + * And this gives the stack time to clear the queue. + */ +static inline void dw_rx_pause(dw_priv_t* priv, char pause) +{ + DBG_FN(DBG_RX | DBG_MINOR); + REQUIRE_LOCKED(priv); + + priv->rx.pause = pause; + + if (pause) + dw_iocleanbits32(HW_INTR_MASK, INTR_RXFIFO); + else + dw_iosetbits32(HW_INTR_MASK, INTR_RXFIFO); +} + +/* ********** now the not inline stuff ********** */ + +/*********************************************************************** + * @Function: dw_ccmp_get_data_tx + * @Return: + * @Descr: Get AES encryption data for a frame + ***********************************************************************/ +static void dw_ccmp_get_data_tx(dw_priv_t *priv, struct ieee80211_hdr_3addr * hdr, + const dw_fragment_tx_t* frag, ccmp_data_t* data, + u8* extiv, int dlen) +{ + ccmp_key_t *key = frag->crypt.key; + u8 *bp; + + DBG_FN(DBG_TX); + + /* Increment packet number */ + dw_inc_48(&key->tx_pn); + + CLEAR(*data); + + memset(extiv, 0, EXTIV_SIZE); + + SET16(extiv, key->tx_pn & 0xffff); + extiv[3] = priv->ieee->tx_keyidx << 6 | EXT_IV; + + SET32(&extiv[4], key->tx_pn >> 16); + + /* Set up CCM initial block for MIC IV */ + data->init[0] = 0x59; + data->init[1] = 0; + memcpy (&data->init[2], hdr->addr2, ETH_ALEN); + data->init[8] = extiv[7]; + data->init[9] = extiv[6]; + data->init[10] = extiv[5]; + data->init[11] = extiv[4]; + data->init[12] = extiv[1]; + data->init[13] = extiv[0]; + data->init[14] = dlen >> 8; + data->init[15] = dlen; + + /* Set up MIC header blocks */ + bp = (u8 *) &hdr->frame_ctl; + + data->header[0] = 0; + data->header[1] = 22; + data->header[2] = bp[0] & 0xcf; + data->header[3] = bp[1] & 0xd7; + memcpy(&data->header[4], hdr->addr1, 3*ETH_ALEN); + data->header[22] = WLAN_GET_SEQ_FRAG(le16_to_cpu(hdr->seq_ctl)); + data->header[23] = 0; + memset (&data->header[24], 0, 8); +} + +/*********************************************************************** + * @Function: dw_ccmp_get_data_rx + * @Return: + * @Descr: Get AES encryption data for a frame + ***********************************************************************/ +static int dw_ccmp_get_data_rx(dw_priv_t *priv, + const struct ieee80211_hdr_3addr * hdr, + int dlen, ccmp_data_t *data) +{ + ccmp_key_t *key; + u8 *bp; + + DBG_FN(DBG_RX); + + /* Not encrypted */ + if (dlen < 0 || !(hdr->payload[3] & EXT_IV)) { + return 0; + } + + /* Key not set */ + key = &priv->aeskeys[hdr->payload[3] >> 6]; + if (!key->valid) { + return 0; + } + + CLEAR(*data); + + /* Set up CCM initial block for MIC IV */ + data->init[0] = 0x59; + data->init[1] = 0; + memcpy (data->init+2, hdr->addr2, ETH_ALEN); + + /* extiv */ + data->init[8] = hdr->payload[7]; + data->init[9] = hdr->payload[6]; + data->init[10] = hdr->payload[5]; + data->init[11] = hdr->payload[4]; + data->init[12] = hdr->payload[1]; + data->init[13] = hdr->payload[0]; + data->init[14] = dlen >> 8; + data->init[15] = dlen; + + /* Set up MIC header blocks */ + bp = (u8 *) &hdr->frame_ctl; + + data->header[0] = 0; + data->header[1] = 22; + data->header[2] = bp[0] & 0xcf; + data->header[3] = bp[1] & 0xd7; + memcpy (data->header+4, hdr->addr1, 3*ETH_ALEN); + data->header[22] = WLAN_GET_SEQ_FRAG(le16_to_cpu(hdr->seq_ctl)); + data->header[23] = 0; + memset (data->header+24, 0, 8); + + return 1; +} + +/*********************************************************************** + * Function: dump_hex_buffer + * Return: nothing + * Descr: prints a buffer hexadecimal and with character if printable + ***********************************************************************/ +static void dump_hex_buffer(const void* buffer, const int len) +{ + const unsigned char* hexbuffer = (const unsigned char*)buffer; + int i; + const int colcount = 16; + const int colnum = 4; + const int colcut = colcount / colnum; + + for (i = 0; i < len; i += colcount) { + /* print one row*/ + int j, rowlen; + + if (i+colcount <= len) + rowlen = colcount; + else + rowlen = len - i; + + printk("%08X ", (int) hexbuffer); + printk(" "); + + /* print hexadecimal representation */ + for (j = 0; j < rowlen; j++) { + printk("%02X ", *(hexbuffer+j)); + if ((j + 1) % colcut == 0) + /* additional separator*/ + printk(" "); + } + + for (j = rowlen; j < colcount; j++) + printk(" "); + + if (rowlen != colcount) + for (j = 0; j <= (colcount - rowlen - 1) / colcut; j++) + printk(" "); + + printk(" "); + + /* print character representation row */ + for (j=0; j < rowlen; j++) { + unsigned char c = *(hexbuffer+j); + if (c < 32 || c > 127) + c = '.'; + + printk("%c", c); + } + printk("\n"); + hexbuffer += colcount; + } +} + +/** + * dw_plcp_get_bitrate_cck - returns the bitrate of HW's internal rate code + * + * retval is rate_code/5. But division is 1us slower than switch (5us to 4us) + */ +static int dw_plcp_get_bitrate_cck(int rate_code) +{ + volatile int ret; + + DBG_FN(DBG_RX | DBG_MINOR); + + switch(rate_code) { + case 0x0A: ret = IEEE80211_CCK_RATE_1MB; break; + case 0x14: ret = IEEE80211_CCK_RATE_2MB; break; + case 0x37: ret = IEEE80211_CCK_RATE_5MB; break; + case 0x6E: ret = IEEE80211_CCK_RATE_11MB; break; + default: +// ERROR("Unknown rate_code %i cck, using 1MB\n", rate_code); + ret = IEEE80211_CCK_RATE_1MB; break; + } + + return ret; +} + +/** + * dw_plcp_get_ratecode_cck - returns the HW's internal rate code from bitrate + * + * retval is 5*bitrate. But switch is 1us slower than multiplication (2us to 3us) + */ +static int dw_plcp_get_ratecode_cck(int bitrate) +{ + DBG_FN(DBG_TX | DBG_MINOR); + + /* no check for unsupported bitrates, but upper layer should have handled it */ + return 5 * bitrate; +} + +/** + * dw_plcp_get_bitrate_ofdm - returns the bitrate of HW's internal rate code. + * + * see [1], 2.5.2 + */ +static int dw_plcp_get_bitrate_ofdm(int rate_code) +{ + int ret; + + DBG_FN(DBG_RX | DBG_MINOR); + + switch(rate_code) { + case 0xB: ret = IEEE80211_OFDM_RATE_6MB; break; + case 0xF: ret = IEEE80211_OFDM_RATE_9MB; break; + case 0xA: ret = IEEE80211_OFDM_RATE_12MB; break; + case 0xE: ret = IEEE80211_OFDM_RATE_18MB; break; + case 0x9: ret = IEEE80211_OFDM_RATE_24MB; break; + case 0xD: ret = IEEE80211_OFDM_RATE_36MB; break; + case 0x8: ret = IEEE80211_OFDM_RATE_48MB; break; + case 0xC: ret = IEEE80211_OFDM_RATE_54MB; break; + default: + ERROR("Unknown rate_code %i ofdm, using 6MB\n", rate_code); + ret = IEEE80211_OFDM_RATE_6MB; break; + } + + return ret; +} + +/** + * dw_plcp_get_ratecode_ofdm - returns the HW's internal rate code from bitrate + * + * see [1], 2.5.2 + */ +static int dw_plcp_get_ratecode_ofdm(int bitrate) +{ + DBG_FN(DBG_TX | DBG_MINOR); + + return rates_info[ dw_rate_info_index(bitrate) ].ofdm_code; +} + +/** + * dw_cw_set - updates the contention window depending on the frame type + */ +static void dw_cw_set(const dw_priv_t* priv, u16 fc) +{ + int cw; + + DBG_FN(DBG_INIT); + REQUIRE_LOCKED(priv); + + if (fc & IEEE80211_STYPE_BEACON) + cw = 2 * CW_MIN + 1; + else if (fc & IEEE80211_STYPE_PROBE_RESP) + cw = CW_MIN; + else + cw = priv->cw; + + /* Set backoff timer */ + if (cw) { + u16 rand; + get_random_bytes(&rand, 2); + + /* Pick a random time up to cw, convert to usecs */ + cw = 10 * (rand & cw); + if (fc & IEEE80211_STYPE_BEACON) + dw_iowrite32(cw, HW_BEACON_BO); + else + dw_iowrite32(cw, HW_BACKOFF); + } +} + +/** + * RF transceiver transmitter gain for each power level. + * This is the 0-15 power level, bit reversed. + */ +static unsigned char powerTable[] = { + 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf +}; + +/** + * This is used to set the transmit power. Values can range + * from 0-15, where 8 is the default and I am told provides about + * 12dBm (16mW) output power. + */ +static int dw_set_tx_power(struct dw_priv *priv, unsigned char value) +{ +#if defined(CONFIG_DIGI_WI_G_UBEC_JD) + if (value > DW_TX_POWER_MAX_DBM) + value = DW_TX_POWER_MAX_DBM; + + dw_hw_write_rf(5, 0x19e40 | powerTable[value]); + priv->tx_power = value; + return 1; +#elif defined(CONFIG_DIGI_WI_G_UBEC_HC) + if (value < 0) + value = 0; + // Map max value (15) to 14 to avoid hardware problems + else if (value > 14) + value = 14; +#ifdef _UNDEFINED_ + dw_hw_write_rf(5, 0x09e40 | powerTable[value]); + priv->tx_power = value; + return 1; +#else + dw_hw_write_rf(5, 0x09ee0); + priv->tx_power = 0; + return -EIO; +#endif + +#endif // defined(CONFIG_DIGI_WI_G_UBEC_JD) +} + +/** + * dw_rx_fifo_error - reports a fifo error and resets it + */ +static void dw_rx_fifo_error(dw_priv_t* priv) +{ + DBG_FN(DBG_RX); + REQUIRE_LOCKED(priv); + REQUIRE_LOCKED_ANY(priv->ieee->lock); + + /* the reason of the error should have been reported already */ + + /* give a pulse */ + dw_iosetbits32(HW_GEN_CONTROL, GEN_RXFIFORST); + wmb(); + dw_iocleanbits32(HW_GEN_CONTROL, GEN_RXFIFORST); + + priv->wstats.discard.misc++; +} + +/** + * dw_rx_frame_get_length - determines the frame length from hardware values + * + * @return frame length in bytes or -1 on failure + */ +static int dw_rx_frame_get_length(const dw_frame_rx_t* frame) +{ + const dw_hw_hdr_rx_t* hdr = &frame->hdr; + int len = 0; + + DBG_FN(DBG_RX); + + if (MOD_OFDM == hdr->mod.mod_type) { + const dw_hw_ofdm_t* ofdm = &hdr->plcp.ofdm; + /* switch and no table because check for bad data from FPGA */ + switch(ofdm->rate) { + case 0xB: + case 0xF: + case 0xA: + case 0xE: + case 0x9: + case 0xD: + case 0x8: + case 0xC: + break; + default: + ERROR("Wrong rate %i", ofdm->rate); + goto error; + } + len = ofdm->length; + } else { + const dw_hw_pskcck_t* pskcck = &hdr->plcp.pskcck; + int service; + + /* service check */ + service = pskcck->service; + service &= ~SERVICE_LOCKED; + service &= ~SERVICE_MODSEL; + service &= ~SERVICE_LENEXT; + + /* Use a switch to avoid doing a divide operation.*/ + len = pskcck->length; + switch(pskcck->signal) { + case 10: + len /= 8; + break; + case 20: + len /= 4; + break; + case 55: + len = (11 * len) / 16; + break; + case 110: + len = (11 * len) / 8; + if (pskcck->service & SERVICE_LENEXT) + len--; + break; + default: + ERRORL("Signal not defined %i %i", pskcck->signal, + pskcck->length); + + /* !TODO: Remove me when solved */ + dump_hex_buffer(hdr, sizeof(*hdr)); + goto error; + } + } + + /* check length for integrity? */ + if (unlikely(len > HW_RX_FIFO_SIZE)) { + ERRORL("Wrong size %i", len); + goto error; + } + + return len; + +error: + return -1; +} + +/** + * dw_rx_frame_is_duplicate - checks whether the frame has been already + * received. This is not done by hardware. + * + * @return 1 if duplicate otherwise 1 + */ +static int dw_rx_frame_is_duplicate(dw_priv_t* priv, + const struct ieee80211_hdr_3addr* hdr) +{ + struct list_head* it; + dw_duplicate_t* sender = NULL; + int is_duplicate = 0; + + REQUIRE_LOCKED(priv); + + /* do we had anything from that sender already? */ + list_for_each(it, &priv->rx.dups.known.list) { + /* addr2 is sender */ + dw_duplicate_t* entry = list_entry(it, dw_duplicate_t, list); + if (!memcmp(entry->src, hdr->addr2, ARRAY_SIZE(entry->src))) { + /* found sender */ + sender = entry; + break; + } + } /* list_for_each */ + + if (NULL == sender) { + /* create an entry for the new sender */ + struct list_head* new; + + if (unlikely(list_empty(&priv->rx.dups.free.list))) + /* the last one in known list was the first + * added and so may possible be the oldest. + * Using jiffies is probably not necessary */ + new = priv->rx.dups.known.list.prev; + else + new = priv->rx.dups.free.list.next; + + /* move it to head of known entries */ + list_del(new); + list_add(new, &priv->rx.dups.known.list); + + sender = list_entry(new, dw_duplicate_t, list); + + memcpy(sender->src, hdr->addr2, ARRAY_SIZE(sender->src)); + } else { + /* did we receive the frame already? */ + u16 fc; + + fc = le16_to_cpu(hdr->frame_ctl); + if ((fc & IEEE80211_FCTL_RETRY) && + (sender->seq_ctl == hdr->seq_ctl)) + /* we did see already the sequence number */ + is_duplicate = 1; + } /* if (NULL == sender) */ + + if (!is_duplicate) + /* update sequence control field */ + sender->seq_ctl = hdr->seq_ctl; + + return is_duplicate; +} + +/** + * dw_rx_frame_decrypt - decrypts a frame when necessary. + * + * skb->data contains undecrypted data on return + */ +#ifndef REMOVE_ME +static void dw_rx_frame_decrypt(dw_priv_t* priv, struct sk_buff* skb) +{ + DBG_FN(DBG_RX | DBG_MINOR); +} +#endif + +/** + * dw_rx_frame_give_to_stack - give the kernel/user the data + */ +static void dw_rx_frame_give_to_stack(dw_priv_t* priv, dw_frame_rx_t* frame) +{ + static struct ieee80211_rx_stats stats; + int gain; /* For received signal strength to dBm conversion */ + + DBG_FN(DBG_RX | DBG_MAJOR); + + /* processing needs time, but we want to be able to stil copy frames + * from Rx FIFO. Therefore we are unlocked. */ + REQUIRE_UNLOCKED(priv); + +#ifndef REMOVE_ME + dw_rx_frame_decrypt(priv, frame->skb); +#endif + + /* update stats */ + CLEAR(stats); + stats.mask = + IEEE80211_STATMASK_RSSI | + IEEE80211_STATMASK_SIGNAL | + IEEE80211_STATMASK_RATE; + stats.mac_time = jiffies; + stats.rate = ((MOD_OFDM == frame->hdr.mod.mod_type) ? + dw_plcp_get_bitrate_ofdm(frame->hdr.plcp.ofdm.rate) : + dw_plcp_get_bitrate_cck(frame->hdr.plcp.pskcck.signal)); + stats.freq = IEEE80211_24GHZ_BAND; + stats.len = frame->skb->len; + + + /* Convert received signal strength to dBm */ + gain = lnatable[frame->hdr.mod.rssi_lna] + 2 * frame->hdr.mod.rssi_vga; + if (gain > 96) + stats.signal = -98; + else if (gain > 86) + stats.signal = gaintable[gain-87]; + else + stats.signal = 5 - gain; + + /* RSSI is used only internally to determine best network and not reported back */ + + /* RSSI (Received Signal Strength Indication) is a measurement of the + * power present in a received radio signal. In an IEEE 802.11 system + * RSSI is the received signal strength in a wireless environment, in + * arbitrary units. RSSI measurements will vary from 0 to 255 depending + * on the vendor. It consists of a one byte integer value. A value of 1 + * will indicate the minimum signal strength detectable by the wireless + * card, while 0 indicates no signal. The value has a maximum of + * RSSI_Max. - http://en.wikipedia.org/wiki/RSSI + * + * See also: http://www.ces.clemson.edu/linux/dbm-rssi.shtml + */ + stats.rssi = MAC_RSSI_MAX + - 15 * (frame->hdr.mod.rssi_lna - 1) + - 2 * (frame->hdr.mod.rssi_vga); + + /* we don't know it here, but ieee80211 stack will look into it with + our patch in ieee80211_rx.c before + + if (255 == stats->received_channel) + + stats->received_channel = network->channel; + memcpy(&network->stats, stats, sizeof(network->stats)); + */ + stats.received_channel = 255; + + /* put it on stack */ + ieee80211_rx_any(priv->ieee, frame->skb, &stats); + + /* allocate next buffer */ + frame->skb = dev_alloc_skb(DW_MTU); + if (unlikely(NULL == frame->skb)) { + dw_iocleanbits32(HW_GEN_CONTROL, GEN_RXEN); + panic(DRIVER_NAME ": Out of memory\n"); + } +} + +/** + * dw_rx_frame_fetch - Copies a frame from FIFO + */ +static void dw_rx_frame_fetch(dw_priv_t* priv) +{ + struct list_head* element; + dw_frame_rx_t* frame; + int len; + int ignore_frame = 0; + u16 fc; + char spy_buf[2312]; + unsigned int spy_len = 0; + + DBG_FN(DBG_RX | DBG_INT); + REQUIRE_LOCKED(priv); + + if (unlikely(list_empty(&priv->rx.queue.free.list))) { + ERROR("Frame received, but queue empty. Receiver should be paused"); + len = 0xffff; /* for DBG_MARK */ + + /* nowhere to store. Reset FIFO */ + spin_lock(&priv->ieee->lock); + priv->ieee->stats.rx_over_errors++; + dw_rx_fifo_error(priv); + spin_unlock(&priv->ieee->lock); + + return; + } + + /* use a free skb and fill it with the frame */ + element = priv->rx.queue.free.list.next; + frame = list_entry(element, dw_frame_rx_t, list); + + memset(spy_buf, 0, sizeof(spy_buf)); spy_len = 0; + + /* copy frame header, swapped, we need it's header for length */ + dw_hw_read_fifo(&frame->hdr, sizeof(frame->hdr)); + memcpy(spy_buf + spy_len, &frame->hdr, sizeof(frame->hdr)); spy_len += sizeof(frame->hdr); + + len = dw_rx_frame_get_length(frame); + + if ((len >= sizeof(struct ieee80211_hdr)) && (len <= DW_MTU)) { + /* read data and if necessary decrypt it */ + size_t remaining_len = len; + /* */ + size_t delta_decrypt_header = sizeof(struct ieee80211_hdr_3addr) + EXTIV_SIZE - sizeof(struct ieee80211_hdr); + + struct ieee80211_hdr* hdr = (struct ieee80211_hdr*) frame->skb->data; + struct ieee80211_hdr_3addr* hdr3 = (struct ieee80211_hdr_3addr*) hdr; + char* data = frame->skb->data; + + + /* reads data and keeps track of remaining_len */ +#define READ_FIFO(to_read) do { \ + dw_hw_read_fifo(data, to_read); \ + memcpy(spy_buf + spy_len, data, to_read); spy_len += to_read; \ + data += to_read; \ + remaining_len -= to_read; \ + ASSERT(remaining_len >= 0); } while (0) + + /* read header */ + READ_FIFO(sizeof(struct ieee80211_hdr)); + + fc = le16_to_cpu(hdr->frame_ctl); + + if (!dw_sw_aes && + (fc & IEEE80211_FCTL_PROTECTED) && + (remaining_len > delta_decrypt_header)) { + /* there is a encrypted frame present */ + ccmp_data_t cdata; + ccmp_key_t* key; + size_t data_len; + int index; + u48 pn; + + /* get frame headers and init vector from rx FIFO */ + READ_FIFO(delta_decrypt_header); + + /* get key index from message */ + index = (hdr3->payload[ 3 ] >> 6) & (WEP_KEYS - 1); + key = &priv->aeskeys[ index ]; + + /* get packet number from IV and check for replay. + packet number must be greated or equal than + expected one. Takes care of wrap around. */ + pn = dw_to_48(GET32(&hdr3->payload[ 4 ]), + GET16(&hdr3->payload[ 0 ])); + + data_len = remaining_len - IEEE80211_FCS_LEN - MIC_SIZE; + + if (key->valid && /* we know the key */ + (pn - key->rx_pn >= 0) && + dw_ccmp_get_data_rx(priv, hdr3, data_len, &cdata)) { + /* payload doesn't include MIC or CCMP */ + len -= CCMP_SIZE; + + /* !TODO. Convert AES_wait into a non-busy + polling function */ + /* retreive and decrypt encoded payload data */ + dw_hw_aes_wait(); + + /* configure mode and key */ + dw_iowrite32(HW_AES_MODE_1 | (index & 0xf), + HW_AES_MODE); + + /* this read puts AES into decrypt mode */ + dw_ioread32(HW_AES_MODE); + + /* write key and init vector to AES engine */ + dw_hw_aes_write_fifo(&cdata, sizeof(cdata)); + + /* get decrypted payload data. + * We will overvwrite the CCMP header + * previously read. But the stack doesn't want + * to see it anyway because it expects unencrypted data */ + dw_hw_aes_read_fifo(hdr3->payload, data_len); + + /* wait for MIC calculation to finish. + !TODO: convert to non-busy */ + dw_hw_aes_wait(); + + if (dw_ioread32(HW_RSSI_AES) & AES_MIC) { + /* frame was ok */ + dw_inc_48(&key->rx_pn); + /* the stack should not convert it */ + hdr->frame_ctl = cpu_to_le16(fc & ~IEEE80211_FCTL_PROTECTED); + } else { + ignore_frame = 1; + spin_lock(&priv->ieee->lock); + priv->ieee->ieee_stats.rx_discards_undecryptable++; + spin_unlock(&priv->ieee->lock); + ERROR("Wrong MIC"); + } + } else { + /* TKIP decrypted? Le'ts handle it by SW */ + READ_FIFO(remaining_len); + } +#undef READ_FIFO + } else { + /* retrieve remaining unencrypted data. + We read FCS, but stack will ignore it. */ + dw_hw_read_fifo(data, remaining_len); + memcpy(spy_buf + spy_len, data, remaining_len); spy_len += remaining_len; + } + } else { + if (len > DW_MTU) + { + ERROR("Oversized frame with %i bytes, ignoring it\n", len); + + } + + spin_lock(&priv->ieee->lock); + priv->ieee->stats.rx_length_errors++; + dw_rx_fifo_error(priv); + spin_unlock(&priv->ieee->lock); + + ignore_frame = 1; + } + + if (!ignore_frame) { + /* process frame */ + + int skblen = len - IEEE80211_FCS_LEN;/* need no FCS further */ + int ignore = 0; + int control = ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_CTL); + + switch(fc & IEEE80211_FCTL_FTYPE) { + case IEEE80211_FTYPE_MGMT: /* no break */ + case IEEE80211_FTYPE_DATA: + break; + + case IEEE80211_FTYPE_CTL: /* no break */ + default: + ignore = 1; + break; + } + + if (!ignore && + (len >= sizeof(struct ieee80211_hdr_3addr)) && + dw_rx_frame_is_duplicate(priv, + (const struct ieee80211_hdr_3addr*) frame->skb->data)) + ignore = 1; + + if (!ignore) { + /* the layer ignores the above frames, so we don't + * need to alloc/free skb's for them. CTL are + * ignored anyway and not freed by layer (bug)? + * -> out of memory + * + * We have read the whole frame because a CTL frame is + * typically 10 Bytes large, the header 4 Bytes. + * Not much gain for reading only header, but a loose + * for all other frames and still some overhead of + * calculation */ + skb_put(frame->skb, skblen); + + if (IS_DATA(fc)) + priv->rate.have_activity = 1; + + list_move_tail(element, &priv->rx.queue.filled.list); + + if (unlikely(list_empty(&priv->rx.queue.free.list))) + /* no room to store any longer. So, FIFO may + * overrun, but in this case, no 802.11 + * acknowledgements are transmitted and the + * frame is repeatedly sent => less errors */ + + dw_rx_pause(priv, 1); + } else { + if (priv->tx.last_was_data && control && + ((fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_ACK)) + { + priv->tx.data_pending_ack = NULL; + priv->rate.tx_data_ack++; + } + + } /* fc */ + } else { + spin_lock(&priv->ieee->lock); + priv->ieee->stats.rx_dropped++; + spin_unlock(&priv->ieee->lock); + } +} + +/** + * dw_rx_tasklet_handler - Read's all frames in fifo + */ +static void dw_rx_tasklet_handler(unsigned long data) +{ + dw_priv_t* priv = (dw_priv_t*) data; + struct list_head frames; + struct list_head* cursor; + unsigned long flags; + + DBG_FN(DBG_RX | DBG_INT); + + /* move the filled list to our context, so the tasklet is able to + * continue mostly unlocked. */ + INIT_LIST_HEAD(&frames); + + dw_lock(priv, flags); + dw_list_move(&frames, &priv->rx.queue.filled.list); + dw_unlock(priv, flags); + + list_for_each(cursor, &frames) { + dw_frame_rx_t* frame = list_entry(cursor, dw_frame_rx_t, list); + dw_rx_frame_give_to_stack(priv, frame); + } + + /* now move the processed frame list that is now empty again back to + free */ + dw_lock(priv, flags); + dw_list_move(&priv->rx.queue.free.list, &frames); + dw_rx_pause(priv, 0); + dw_unlock(priv, flags); +} + +/** + * dw_tx_reset - reset's the Tx FIFO when a timeout is detected + */ +static void dw_tx_reset(dw_priv_t* priv) +{ + DBG_FN(DBG_TX | DBG_ERROR); + REQUIRE_LOCKED(priv); + + ERRORL("TX Reset and retrying transmission"); + + /* reset FIFO */ + + dw_iosetbits32(HW_GEN_CONTROL, GEN_TXFIFORST); + wmb(); + dw_iocleanbits32(HW_GEN_CONTROL, GEN_TXFIFORST); + + priv->tx.data_pending_ack = NULL; + + /* retransmit it */ + + if (likely(!list_empty(&priv->tx.queued))) { + dw_frame_tx_t* frame = list_entry(priv->tx.queued.prev, dw_frame_tx_t, list); + dw_tx_fragment_send(priv, frame); + } else { + ERROR("Tx Queue is empty, but we have a tx timeout????"); + } +} + +/** + * dw_tx_wait_for_idle_and_pause - on return, no frame is processed any more by + * Tx + * + * After the return it is safe to configure the transmitter + */ +static void dw_tx_wait_for_idle_and_pause(dw_priv_t* priv) +{ + unsigned long flags; + int sleep; + + DBG_FN(DBG_TX); + REQUIRE_UNLOCKED(priv); + + /* no more new transmits until dw_tx_continue_queue */ + dw_lock(priv, flags); + priv->tx.pause = 1; + sleep = priv->tx.pending; + netif_stop_queue(priv->dev); + dw_unlock(priv, flags); + + if (sleep) + /* wait for queue to be emptied. + * we mustn't be locked, because we can sleep. But that's not a + * problem. If the interrupt handler is faster, it will + * already have freed semaphore, so we run through */ + down(&priv->tx.pause_sem); +} + +static void dw_tx_continue_queue(dw_priv_t* priv) +{ + unsigned long flags; + + DBG_FN(DBG_TX); + REQUIRE_UNLOCKED(priv); + + dw_lock(priv, flags); + + priv->tx.pause = 0; + netif_wake_queue(priv->dev); + if (!list_empty(&priv->tx.queued)) { + /* transmitter is paused, so nothing is pending. Send next + queued entry */ + dw_frame_tx_t* frame = list_entry(priv->tx.queued.prev, dw_frame_tx_t, list); + dw_tx_fragment_send(priv, frame); + } + + dw_unlock(priv, flags); +} + + +/** + * dw_tx_set_plcp - set's PLCP Header of the frame. + */ +static void dw_tx_set_plcp(dw_frame_tx_info_t* fi, int fragment, int rate) +{ + dw_fragment_tx_t* frag = &fi->frags[ fragment ]; + dw_hw_hdr_tx_t* hdr = &frag->hdr; + size_t length = frag->phys_len; + + DBG_FN(DBG_TX); + + CLEAR(*hdr); + /* FIFO length in words of complete frame with header, + rounded up. FCS is added automatically */ + hdr->mod.length = (sizeof(*hdr) + length + 3) / 4; + + /* FCS length is required for signal */ + length += IEEE80211_FCS_LEN; + if (!ieee80211_is_cck_rate(rate)) { + hdr->mod.mod_type = MOD_OFDM; + hdr->plcp.ofdm.rate = dw_plcp_get_ratecode_ofdm(rate); + hdr->plcp.ofdm.length = length; + hdr->plcp.ofdm.raw32 = cpu_to_le32(hdr->plcp.ofdm.raw32); + } else { + int signal = dw_plcp_get_ratecode_cck(rate); + hdr->mod.mod_type = MOD_PSKCCK; + hdr->plcp.pskcck.signal = signal; + hdr->plcp.pskcck.service = SERVICE_LOCKED; + + /* convert length from bytes to microseconds */ + switch(signal) { + case 10: length *= 8; break; + case 20: length *= 4; break; + case 55: length = (16 * length + 10) / 11; break; + case 110: + length = (8 * length + 10) / 11; + /* set length extension bit if needed */ + if ((11 * length) / 8 > ( frag->phys_len + IEEE80211_FCS_LEN)) + hdr->plcp.pskcck.service |= SERVICE_LENEXT; + break; + default: + ERRORL("Unsupported signal/rate %i/%i", signal,rate); + break; + } + hdr->plcp.pskcck.length = cpu_to_le16(length); + hdr->plcp.pskcck.raw32 = cpu_to_le32(hdr->plcp.pskcck.raw32); + } + hdr->mod.raw32 = cpu_to_le32(hdr->mod.raw32); +} + +/** + * dw_tx_ack_duration - + * + * @return the duration for the acknowledgement of our data package in us + */ +static int dw_tx_ack_duration( + dw_priv_t* priv, + const dw_frame_tx_info_t* fi, + dw_rate_index_t index) +{ + const rate_info_t* rate_info = &rates_info[ priv->tx.basics[ index ] ]; + /* ack/crts is sent at equal or lower basic rate */ + int dur = rate_info->ack_len; + + REQUIRE_LOCKED(priv); + + /* add psk/cck preamble time */ + if (!rate_info->ofdm_code) + dur += PRE_LEN(fi, rate_info->bps); + + return dur; +} + +/** + * dw_tx_duration - + * + * PLCP must be set. + * @return the duration for the frame in us + */ +static int dw_tx_duration( + dw_priv_t* priv, + const dw_frame_tx_info_t* fi, + int fragment, + int rate) +{ + int index = dw_rate_info_index(rate); + int dur; + + /* frame duration */ + if (rates_info[ index ].ofdm_code) + dur = OFDM_DUR(fi->frags[ fragment ].phys_len, rate); + else + dur = PRE_LEN(fi, rates_info[ index ].bps) + le16_to_cpu(fi->frags[ fragment ].hdr.plcp.pskcck.length); + + return dur + dw_tx_ack_duration(priv, fi, index); +} + + +/** + * dw_tx_frame_prepare - prepare everything that is needed to send linux frame + * + * Determine PLCP, rate, sequence/fragment number + * + * @return 0 on failure. Frame needs to be dropped + */ +static int dw_tx_frame_prepare( + dw_priv_t* priv, + dw_frame_tx_info_t* fi, + struct ieee80211_txb* txb) +{ + struct ieee80211_hdr_3addr* hdr3 = NULL; + const struct ieee80211_hdr_1addr* hdr1 = NULL; + struct ieee80211_hdr* hdr = (struct ieee80211_hdr *)txb->fragments[ 0 ]->data; + int fc = le16_to_cpu(hdr->frame_ctl); + int i; + int rate; + int rate_last_fragment; + int rate_index; + int duration; + + REQUIRE_LOCKED(priv); + + if (txb->fragments[ 0 ]->len >= sizeof(struct ieee80211_hdr_3addr)) + /* we need it for sequence number */ + hdr3 = (struct ieee80211_hdr_3addr*) hdr; + if (txb->fragments[ 0 ]->len >= sizeof(struct ieee80211_hdr_1addr)) + /* we need it for group addressing */ + hdr1 = (struct ieee80211_hdr_1addr*) hdr; + + CLEAR(*fi); + fi->txb = txb; + fi->is_data = IS_DATA(fc); + + /* the stack sets encrypted whenenver the frame needs to be encrypted, + * but set's FCTL_PROTECTED only when it has encrypted it itself. This + * leaves CCMP for us. + * encrypted can also be set to 1 if we don't provide hardware. */ + fi->use_hw_encryption = fi->txb->encrypted && !(fc & IEEE80211_FCTL_PROTECTED); + if (fi->use_hw_encryption) { + fc |= IEEE80211_FCTL_PROTECTED; + hdr->frame_ctl = cpu_to_le16(fc); + } + + /* determine bit rate for fragment */ + spin_lock(&priv->softmac->lock); + rate = rate_last_fragment = priv->softmac->txrates.default_rate; + + if (IS_MGMT(fc)) + rate_last_fragment = dw_rates[ 0 ]; + else if ((NULL != hdr3) && is_multicast_ether_addr(hdr3->addr1)) + rate = rate_last_fragment = priv->softmac->txrates.mcast_rate; + else if (fc & IEEE80211_FCTL_PROTECTED) + /* send all but the last at broadcast_rate */ + rate_last_fragment = priv->softmac->txrates.mcast_rate; + spin_unlock(&priv->softmac->lock); + + /* generate sequence number, encryption and plcp */ + for (i = 0; i < fi->txb->nr_frags; i++) { + dw_fragment_tx_t* frag = &fi->frags[ i ]; + struct sk_buff* skb = txb->fragments[ i ]; + int last_frag = (i == fi->txb->nr_frags - 1); + int rate_frag = BASIC_RATE_MASK(last_frag ? rate_last_fragment : rate); + + if (txb->fragments[ i ]->len >= sizeof(struct ieee80211_hdr_3addr)) { + int fragment = (fi->txb->rts_included ? (i - 1) : i); + /* it holds a sequence/fragment number */ + struct ieee80211_hdr_3addr* frag_hdr3 = (struct ieee80211_hdr_3addr*) txb->fragments[ i ]->data; + /* see ieee80211.h: WLAN_GET_SEQ_FRAG(seq) */ + frag_hdr3->seq_ctl = cpu_to_le16((atomic_read(&priv->tx.seq_nr) << 4) | + (fragment & 0xf)); + } + + fi->use_short_preamble = + (rate_frag != IEEE80211_CCK_RATE_1MB) && priv->softmac->bssinfo.short_preamble; + + frag->phys_len = skb->len; + + /* provide encryption. Requires sequence number for MIC */ + if (fi->use_hw_encryption) { + frag->crypt.key_index = priv->ieee->tx_keyidx; + frag->crypt.key = &priv->aeskeys[ frag->crypt.key_index ]; + + /* Key not set */ + if (!frag->crypt.key->valid) + goto error; + + /* EXTIV and MIC are automatically appended */ + frag->phys_len += CCMP_SIZE; + } + + /* requires fragment length with encryption */ + dw_tx_set_plcp(fi, i, rate_frag); + } /* plcp */ + + /* add duration, for initial fragments */ + rate_index = dw_rate_info_index(BASIC_RATE_MASK(rate)); + for (i = 0; i < fi->txb->nr_frags - 1; i++) { + struct ieee80211_hdr* frag_hdr = (struct ieee80211_hdr *)txb->fragments[ i ]->data; + + /* ack of this frame + data of next frame */ + duration = dw_tx_ack_duration(priv, fi, rate_index) + + dw_tx_duration(priv, fi, i + 1, rate); + + frag_hdr->duration_id = cpu_to_le16(duration); + } + + /* set duration for final or last fragment */ + if ((NULL != hdr1) && IS_GROUP_ADDR(hdr1->addr1)) + duration = 0; + else + duration = dw_tx_ack_duration(priv, fi, + dw_rate_info_index(BASIC_RATE_MASK(rate_last_fragment))); + + hdr->duration_id = cpu_to_le16(duration); + + if (NULL != hdr3) + /* no need for cutting it at 0xffff, will be done on + assignment to seq_ctl */ + atomic_inc(&priv->tx.seq_nr); + + return 1; + +error: + return 0; +} + +/** + * dw_tx_fragment_send - copies a fragment to FIFO and sets all registers. + */ +static void dw_tx_fragment_send( + dw_priv_t* priv, + dw_frame_tx_t* frame) +{ + const dw_fragment_tx_t* frag = &frame->s.frags[ priv->tx.fragment ]; + const struct sk_buff* skb = frame->s.txb->fragments[ priv->tx.fragment ]; + struct ieee80211_hdr* hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_hdr_3addr* hdr3 = (struct ieee80211_hdr_3addr*) hdr; + int hw_gen_ctrl = dw_ioread32(HW_GEN_CONTROL); + u16 fc = le16_to_cpu(hdr->frame_ctl); + + DBG_FN(DBG_TX); + REQUIRE_LOCKED(priv); + + //if (priv->tx.retries) { + if (priv->tx.times_sent) { + /* Add RETRY flag, sequence number is already set */ + fc |= IEEE80211_FCTL_RETRY; + hdr->frame_ctl = cpu_to_le16(fc); + if (priv->cw < CW_MAX) + /* code starts always with 2^n-1. Therefore, we still + * always have 2^n-1 and never overrun CW_MAX. */ + priv->cw = priv->cw * 2 + 1; + + priv->wstats.discard.retries++; + } else { + priv->cw = CW_MIN; + } + + dw_cw_set(priv, fc); + + if (frame->s.use_short_preamble) + hw_gen_ctrl |= GEN_SHPRE; + else + /* may be set from a previous frame */ + hw_gen_ctrl &= ~GEN_SHPRE; + + priv->tx.last_was_data = frame->s.is_data; + if (frame->s.is_data) { + priv->rate.have_activity = 1; + priv->rate.tx_data_any++; + } + + /* sent frame, either bye AES or unencrypted engine */ + if (!frame->s.use_hw_encryption) { + /* Prevent transmitting until all data have been written into the tx fifo */ + dw_iowrite32(hw_gen_ctrl | GEN_TXHOLD, HW_GEN_CONTROL); + dw_hw_write_fifo(&frag->hdr, sizeof(frag->hdr)); + dw_hw_write_fifo(skb->data, skb->len); + dw_iowrite32(hw_gen_ctrl, HW_GEN_CONTROL); + } else { + ccmp_data_t cdata; + u8 extiv[ EXTIV_SIZE ]; + + dw_ccmp_get_data_tx(priv, hdr3, frag, &cdata, extiv, skb->len - sizeof(*hdr3)); + + /* frame ctl is part of data used for MIC. */ + dw_hw_aes_wait(); + + /* prevent transmitting until encrypted data is ready */ + dw_iosetbits32(HW_GEN_CONTROL, GEN_TXHOLD); + + /* write MAC, Frame Header and IV FIFO */ + dw_hw_write_fifo(&frag->hdr, sizeof(frag->hdr) ); + dw_hw_write_fifo(hdr3, sizeof(*hdr3) ); + dw_hw_write_fifo(&extiv, sizeof(extiv)); + + /* configure mode and key */ + dw_iowrite32(HW_AES_MODE_1 | (frag->crypt.key_index & 0xf), + HW_AES_MODE); + + /* write init block to AES FIFO */ + dw_hw_aes_write_fifo(&cdata, sizeof(cdata)); + + /* start transmit */ + dw_iocleanbits32(HW_GEN_CONTROL, GEN_TXHOLD); + + /* write plaintext data to AES FIFO */ + dw_hw_aes_write_fifo(hdr3->payload, skb->len - sizeof(*hdr3)); + } + priv->dev->trans_start = jiffies; + priv->tx.pending = 1; + priv->tx.times_sent++; + + if (priv->ieee->iw_mode != IW_MODE_ADHOC) { + if (((*(char *)(skb->data)) & 0x0C) == 0x08) { /* If it is a data frame */ + priv->tx.data_pending_ack = (void *)frag; + priv->tx.jiffies_pending_ack = jiffies; + } + } +} + +/** + * dw_tx_tasklet_handler - One fragment has been completed. + * + * is only called when entries are in tx.queued list + */ +static void dw_tx_tasklet_handler(unsigned long data) +{ + dw_priv_t* priv = (dw_priv_t*) data; + dw_frame_tx_t* frame = NULL; + unsigned int retry_frame = 0; + unsigned long flags; + + DBG_FN(DBG_TX | DBG_INT); + + dw_lock(priv, flags); + + /* check if there are further fragments left */ + if (list_empty(&priv->tx.queued) && !priv->tx.timeout) + /* Spurious Interrupt. May happen if we slow everything down + * with debug messages */ + goto out; + + spin_lock(&priv->ieee->lock); /* interrupts already disabled */ + + frame = list_entry(priv->tx.queued.next, dw_frame_tx_t, list); + if (priv->tx.timeout) { + int max_retries = + ((frame->s.txb->fragments[ priv->tx.fragment ]->len >= priv->ieee->rts) ? + priv->long_retry_limit : + priv->short_retry_limit); + + priv->tx.retries++; + priv->tx.timeout = 0; + + if (priv->tx.retries <= max_retries) { + retry_frame = 1; + } else { + priv->tx.data_pending_ack = NULL; + priv->ieee->ieee_stats.tx_retry_limit_exceeded++; + } + } /* if (priv->tx.timeout) */ + + if (!retry_frame && priv->tx.data_pending_ack == NULL) { + /* send next fragment */ + priv->tx.fragment++; + if (priv->tx.fragment == frame->s.txb->nr_frags) { + /* frame is complete. Free resources */ + ieee80211_txb_free(frame->s.txb); + frame->s.txb = NULL; + + /* queue entry can be reused */ + list_move_tail(priv->tx.queued.next, &priv->tx.free); + if (!list_empty(&priv->tx.queued)) + /* take next frame's first fragment */ + frame = list_entry(priv->tx.queued.next, dw_frame_tx_t, list); + else + frame = NULL; + + priv->tx.fragment = 0; + priv->tx.retries = 0; + priv->tx.times_sent = 0; + + if (!priv->tx.pause && + netif_queue_stopped(priv->dev)) + /* we have space again */ + netif_wake_queue(priv->dev); + + } + } + + if (priv->tx.data_pending_ack && time_after(jiffies, priv->tx.jiffies_pending_ack + ACK_TIMEOUT)) { + priv->tx.data_pending_ack = NULL; + } + + spin_unlock(&priv->ieee->lock); + + if (NULL != frame) + { + dw_tx_fragment_send(priv, frame); + } else { + /* no more frames */ + priv->tx.pending = 0; + } + + if (priv->tx.pause && list_empty(&priv->tx.queued)) + /* nothing is being processed any longer. + Awake listener */ + up(&priv->tx.pause_sem); + +out: + dw_unlock(priv, flags); +} + +/* Supported rates info elements */ +static const u8 ratesA[] = { DW_ELEM_SUPRATES, 8, DW_RATE_BASIC+12, 18, DW_RATE_BASIC+24, 36, DW_RATE_BASIC+48, 72, 96, 108 }; +static const u8 ratesB[] = { DW_ELEM_SUPRATES, 4, DW_RATE_BASIC+2, DW_RATE_BASIC+4, 11, 22 }; +static const u8 ratesG[] = { DW_ELEM_SUPRATES, 8, DW_RATE_BASIC+2, DW_RATE_BASIC+4, 11, 22, 12, 18, 24, 36 }; +static const u8 ratesGx[] = { DW_ELEM_EXTSUPRATES, 4, 48, 72, 96, 108 }; + +static void dw_beacon_set_plcp(dw_hw_hdr_tx_t * hdr, int rate, size_t phys_len) +{ + size_t length = phys_len; + memset(hdr, 0, sizeof(*hdr)); + + /** + * Length in words, including Frame Header and PLCP Header, and excluding FCS, rounded up + * FCS is added automatically + */ + hdr->mod.length = (phys_len - FCS_SIZE + 3) / 4; + + /* FCS length is required for signal */ + length += IEEE80211_FCS_LEN; + if (!ieee80211_is_cck_rate(rate)) { + hdr->mod.mod_type = MOD_OFDM; + hdr->plcp.ofdm.rate = dw_plcp_get_ratecode_ofdm(rate); + hdr->plcp.ofdm.length = length; + hdr->plcp.ofdm.raw32 = cpu_to_le32(hdr->plcp.ofdm.raw32); + } else { + int signal = dw_plcp_get_ratecode_cck(rate); + hdr->mod.mod_type = MOD_PSKCCK; + hdr->plcp.pskcck.signal = signal; + hdr->plcp.pskcck.service = SERVICE_LOCKED; + + /* convert length from bytes to microseconds */ + switch(signal) { + case 10: length *= 8; break; + case 20: length *= 4; break; + case 55: length = (16 * length + 10) / 11; break; + case 110: + length = (8 * length + 10) / 11; + /* set length extension bit if needed */ + if ((11 * length) / 8 > ( phys_len + IEEE80211_FCS_LEN)) + hdr->plcp.pskcck.service |= SERVICE_LENEXT; + break; + default: + ERRORL("Unsupported signal/rate %i/%i", signal, rate); + break; + } + hdr->plcp.pskcck.length = cpu_to_le16(length); + hdr->plcp.pskcck.raw32 = cpu_to_le32(hdr->plcp.pskcck.raw32); + } + hdr->mod.raw32 = cpu_to_le32(hdr->mod.raw32); +} + +/** + * Store supported rates elements into a buffer + * @param bp Pointer into buffer + * @param elem Element to store: DW_ELEM_SUPRATES, DW_ELEM_EXTSUPRATES, or 0 for both + * @param channel Channel number + * @return Updated buffer pointer + */ +static u8 *dw_beacon_set_rates(u8 *bp, int elem, int channel) +{ + const u8 *sr; + + if (DW_CHAN_5G (channel)) + sr = ratesA; + //else if (OPT_BONLY) + // sr = ratesB; + else + sr = ratesG; + + /* Store up to 8 supported rates */ + if (elem != DW_ELEM_EXTSUPRATES) { + memcpy (bp, sr, sr[1]+2); + bp += bp[1] + 2; + } + + /* Store remaining extended supported rates */ + if (elem != DW_ELEM_SUPRATES && sr == ratesG) { + memcpy (bp, ratesGx, ratesGx[1]+2); + bp += bp[1] + 2; + } + + return bp; +} + +/** + * Create beacon and probe response frames to send in an IBSS + * @param interval Beacon interval in TU + * @return 1 if success, 0 if error + */ +static void dw_beacon_make_beacon(dw_priv_t * priv, int interval) +{ + dw_beacon_frame * bcnFrame = &priv->beacon_frame; + u16 atimWindow = 0; /* ATIM window size, 0 if none */ + + u8 *bp = bcnFrame->body; + u16 bss_caps = 0, caps; + + u8 bss_addr[ ETH_ALEN ]; + + priv->beacon_body_length = 0; + + bss_caps = DW_CAP_IBSS; + //if (!(macParams.encrypt & WLN_ENCR_OPEN)) + // bss_caps |= CAP_PRIVACY; + //if (OPT_SHORTPRE) + // bss_caps |= CAP_SHORTPRE; + + memcpy(bss_addr, priv->adhoc.bssid, ETH_ALEN); + + /* Init beacon MAC header */ + memset (bcnFrame, 0, sizeof (dw_beacon_frame)); + bcnFrame->fc = IEEE80211_STYPE_BEACON; + memset (bcnFrame->addr1, 0xff, ETH_ALEN); + DW_SET_ADDR (bcnFrame->addr2, priv->dev->dev_addr); /* station MAC address */ + DW_SET_ADDR (bcnFrame->addr3, bss_addr); /* BSS to associate with */ + + /** Set fixed params + * Timestamp is set by hardware */ + SET16 (&bp[8], interval); + + /* Set capabilities */ + caps = bss_caps & (DW_CAP_ESS|DW_CAP_IBSS|DW_CAP_PRIVACY); + /* Use short preamble if allowed in BSS and params and rate > 1 mbps. */ + /* caps |= DW_CAP_SHORTPRE; */ + SET16 (&bp[10], caps); + bp += 12; + + /* Set SSID */ + bp[0] = DW_ELEM_SSID; + bp[1] = priv->softmac->associnfo.req_essid.len; + memcpy (&bp[2], priv->softmac->associnfo.req_essid.data, priv->softmac->associnfo.req_essid.len); + bp += bp[1] + 2; + + /* Set supported rates */ + bp = dw_beacon_set_rates(bp, DW_ELEM_SUPRATES, priv->adhoc.channel); + + /* Set channel number */ + if (!DW_CHAN_5G (priv->adhoc.channel)) { + bp[0] = DW_ELEM_DSPARAM; + bp[1] = 1; + bp[2] = priv->adhoc.channel; + bp += bp[1] + 2; + } + + /* Set IBSS ATIM window */ + bp[0] = DW_ELEM_IBSSPARAM; + bp[1] = 2; + SET16 (&bp[2], atimWindow); + bp += bp[1] + 2; + + /* Set ERP info. */ + //if (!DW_CHAN_5G (priv->adhoc.channel) && !(OPT_BONLY))) + { + bp[0] = DW_ELEM_ERPINFO; + bp[1] = 1; + bp[2] = 0; + bp += bp[1] + 2; + } + + /* Set extended supported rates */ + bp = dw_beacon_set_rates(bp, DW_ELEM_EXTSUPRATES, priv->adhoc.channel); + priv->beacon_body_length = ((u8 *)bp - (u8 *)bcnFrame->body); + dw_beacon_set_plcp(&bcnFrame->hwHdr, IEEE80211_CCK_RATE_1MB , priv->beacon_body_length + sizeof(dw_beacon_frame) - BEACON_BODY_SIZE + FCS_SIZE); + bcnFrame->hwHdr.plcp.pskcck.raw32 = cpu_to_be32(0x0a046802); + priv->beacon_ready = 1; +} + +static void dw_beacon_start_IBSS(struct dw_priv *priv) +{ + int atim = 0; + + dw_beacon_make_beacon(priv, DW_BEACON_INT); + /* If starting IBSS, set beacon and ATIM intervals */ + dw_iowrite32(atim | (DW_BEACON_INT << 16), HW_CFP_ATIM); + dw_cw_set(priv, IEEE80211_STYPE_BEACON); + /* Write beacon frame to beacon buffer */ + dw_iosetbits32(HW_GEN_CONTROL, GEN_BEACEN); + dw_hw_write_fifo((void *)&priv->beacon_frame, priv->beacon_body_length + sizeof(dw_beacon_frame) - BEACON_BODY_SIZE); + dw_iocleanbits32(HW_GEN_CONTROL, GEN_BEACEN); + /* Set interrupt mask to enable TBTT and ATIM interrupts */ + dw_iosetbits32(HW_INTR_MASK, INTR_TBTT|INTR_ATIM); + /* Enable IBSS mode */ + dw_iosetbits32(HW_MAC_CONTROL, CTRL_IBSS|CTRL_BEACONTX); +} + +/** + * dw_beacon_associate - does whatever is necessary for association + */ +static void dw_beacon_associate(struct ieee80211softmac_device *mac, + struct ieee80211_assoc_response *resp, + struct ieee80211softmac_network *net) +{ + u16 cap = 0; + u8 erp_value = net->erp_value; + + if (resp != NULL) + cap = le16_to_cpu(resp->capability); + mac->associnfo.associating = 0; + mac->bssinfo.supported_rates = net->supported_rates; + ieee80211softmac_recalc_txrates(mac); + + mac->associnfo.associated = 1; + + if (resp != NULL) + mac->associnfo.short_preamble_available = + (cap & WLAN_CAPABILITY_SHORT_PREAMBLE) != 0; + ieee80211softmac_process_erp(mac, erp_value); + + if (mac->set_bssid_filter) + mac->set_bssid_filter(mac->dev, net->bssid); + memcpy(mac->ieee->bssid, net->bssid, ETH_ALEN); + netif_carrier_on(mac->dev); + + if (resp != NULL) + mac->association_id = le16_to_cpup(&resp->aid); +} + +static void dw_beacon_start(struct work_struct *work) +{ + dw_priv_t *priv = container_of((struct delayed_work *)work, struct dw_priv, beacon_work); + unsigned long flags; + int channel; + struct ieee80211softmac_network *net; + struct ieee80211softmac_device *mac = priv->softmac; + + if (IW_MODE_ADHOC != priv->ieee->iw_mode + || mac->associnfo.associating + || mac->scanning + || priv->beacon_ready + ) return; + + if (!mac->associnfo.req_essid.len + || *mac->associnfo.req_essid.data == '\0' + ) return; + + channel = priv->adhoc.channel; + if (!channel) channel = priv->channel; + if (!channel) channel = DW_IBSS_DEFAULT_CHANNEL; + + memset(&priv->adhoc, 0, sizeof(priv->adhoc)); + get_random_bytes(priv->adhoc.bssid, ETH_ALEN); // Select a random BSSID + priv->adhoc.bssid[0] &= ~DW_MAC_GROUP; // clear group bit + priv->adhoc.bssid[0] |= DW_MAC_LOCAL; // set local bit + priv->adhoc.channel = channel; + priv->adhoc.mode=IW_MODE_ADHOC; + priv->adhoc.essid.len = mac->associnfo.req_essid.len; + memcpy(priv->adhoc.essid.data, mac->associnfo.req_essid.data, IW_ESSID_MAX_SIZE + 1); + + net = ieee80211softmac_get_network_by_essid_locked(mac, &mac->associnfo.associate_essid); + if (!net) { + net = &priv->adhoc; + + mac->set_channel(mac->dev, net->channel); + if (mac->set_bssid_filter) + mac->set_bssid_filter(mac->dev, net->bssid); + + spin_lock_irqsave(&mac->lock, flags); + dw_beacon_associate(mac, NULL, net); + ieee80211softmac_call_events_locked(mac, IEEE80211SOFTMAC_EVENT_ASSOCIATED, net); + spin_unlock_irqrestore(&mac->lock, flags); + + mac->associnfo.scan_retry = IEEE80211SOFTMAC_ASSOC_SCAN_RETRY_LIMIT; + mac->associnfo.bssvalid = 1; + mac->associnfo.channel = net->channel; + memcpy(mac->associnfo.bssid, net->bssid, ETH_ALEN); + mac->associnfo.associate_essid.len = net->essid.len; + memcpy(mac->associnfo.associate_essid.data, net->essid.data, IW_ESSID_MAX_SIZE + 1); + } else { + memcpy(priv->adhoc.bssid, net->bssid, ETH_ALEN); + priv->adhoc.essid.len = net->essid.len; + memcpy(priv->adhoc.essid.data, net->essid.data, IW_ESSID_MAX_SIZE + 1); + } + + dw_beacon_start_IBSS(priv); +} + +/** + * dw_int - interrupt handler for WiFi FPGA + */ +static irqreturn_t dw_int(int irq, void *dev_id) +{ + dw_priv_t* priv = dev_id; + u32 status; + + DBG_FN(DBG_INT); + + /* acknowledge interrupt */ + spin_lock(&priv->lock); + status = dw_ioread32(HW_INTR_STATUS); + dw_iowrite32(status, HW_INTR_STATUS); + + if (status & INTR_RXFIFO) { + int frame_received = 0; + /* process all frames */ + while (!priv->rx.pause && + (!(dw_hw_get_status(priv) & STAT_RXFE))) { + frame_received = 1; + dw_rx_frame_fetch(priv); + } + + if (frame_received) + /* the FIFO is prone to run full, do it with high + priority */ + tasklet_hi_schedule(&priv->rx.tasklet); + + DBG_EXEC(status &= ~INTR_RXFIFO); + } /* if (status & INTR_RXFIFO) */ + + if (status & INTR_TIMEOUT) { + priv->tx.timeout = 1; + DBG_EXEC(status &= ~INTR_TIMEOUT); + } + + if (unlikely(status & INTR_ABORT)) { + priv->tx.timeout = 1; + /* retransmit it */ + DBG_EXEC(status &= ~INTR_ABORT); + } + + if (status & INTR_TXEND) { + tasklet_schedule(&priv->tx.tasklet); + DBG_EXEC(status &= ~INTR_TXEND); + } + + if (status & (INTR_TBTT | INTR_ATIM)) { + /* is emitted by hardware even if masked out :-(. */ + + /** Beacon frame is already in beacon buffer. + * Only set backoff timer here. */ + dw_cw_set(priv, IEEE80211_STYPE_BEACON); + DBG_EXEC(status &= ~(INTR_TBTT | INTR_ATIM)); + } + + if (unlikely(status & INTR_RXOVERRUN)) { + DBG(DBG_RX, "Receiver overrun"); + spin_lock(&priv->ieee->lock); + priv->ieee->stats.rx_over_errors++; + spin_unlock(&priv->ieee->lock); + DBG_EXEC(status &= ~INTR_RXOVERRUN); + } + +#ifdef CONFIG_DIGI_WI_G_DEBUG + /* when not developing/debugging, we don't run extra tests. */ + if (unlikely(status)) + /* check that we had everything handled*/ + ERRORL("Unhandled interrupt 0x%08x, mask is 0x%08x", status, + dw_ioread32(HW_INTR_MASK)); +#endif /* CONFIG_DIGI_WI_G_DEBUG */ + + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static int dw_ieee80211_handle_beacon ( + struct net_device * dev, + struct ieee80211_beacon * beacon, + struct ieee80211_network * network) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + unsigned long flags; + + if ( !priv->softmac->associnfo.associated && + !priv->softmac->associnfo.associating && + IW_MODE_INFRA == priv->ieee->iw_mode && + priv->jiffies_last_beacon + RESCAN_TIMEOUT < jiffies && + (strncmp(priv->softmac->associnfo.req_essid.data, + beacon->info_element->data, beacon->info_element->len) == 0) && + priv->reconnection_attempts && + priv->reconnection_attempts < MAX_RECONNECTION_ATTEMPTS ) { + dw_lock(priv, flags); + dw_set_channel(priv, network->channel); + ieee80211softmac_try_reassoc(priv->softmac); + priv->jiffies_last_beacon = jiffies; + priv->reconnection_attempts++; + dw_unlock(priv, flags); + } + + if ( priv->softmac->associnfo.associated && + IW_MODE_INFRA == priv->ieee->iw_mode && + memcmp(network->bssid, beacon->header.addr3, ETH_ALEN) == 0 ) { + priv->jiffies_last_beacon = jiffies; + priv->reconnection_attempts = 0; + } + return ieee80211softmac_handle_beacon(dev, beacon, network); +} + +/* Allocate a management frame */ +static u8 * ieee80211softmac_alloc_mgt(u32 size) +{ + u8 * data; + + /* Add the header and FCS to the size */ + size = size + IEEE80211_3ADDR_LEN; + if (size > IEEE80211_DATA_LEN) + return NULL; + /* Allocate the frame */ + data = kzalloc(size, GFP_ATOMIC); + return data; +} + +static void +ieee80211softmac_hdr_2addr(struct ieee80211softmac_device *mac, + struct ieee80211_hdr_2addr *header, u32 type, u8 *dest) +{ + /* Fill in the frame control flags */ + header->frame_ctl = cpu_to_le16(type); + /* Control packets always have WEP turned off */ + if (type > IEEE80211_STYPE_CFENDACK && type < IEEE80211_STYPE_PSPOLL) + header->frame_ctl |= mac->ieee->sec.level ? cpu_to_le16(IEEE80211_FCTL_PROTECTED) : 0; + + /* Fill in the duration */ + header->duration_id = 0; + /* FIXME: How do I find this? + * calculate. But most drivers just fill in 0 (except if it's a station id of course) */ + + /* Fill in the Destination Address */ + if (dest == NULL) + memset(header->addr1, 0xFF, ETH_ALEN); + else + memcpy(header->addr1, dest, ETH_ALEN); + /* Fill in the Source Address */ + memcpy(header->addr2, mac->ieee->dev->dev_addr, ETH_ALEN); +} + +static void +ieee80211softmac_hdr_3addr(struct ieee80211softmac_device *mac, + struct ieee80211_hdr_3addr *header, u32 type, u8 *dest, u8 *bssid) +{ + /* This is common with 2addr, so use that instead */ + ieee80211softmac_hdr_2addr(mac, (struct ieee80211_hdr_2addr *)header, type, dest); + + /* Fill in the BSS ID */ + if (bssid == NULL) + memset(header->addr3, 0xFF, ETH_ALEN); + else + memcpy(header->addr3, bssid, ETH_ALEN); + + /* Fill in the sequence # */ + /* FIXME: I need to add this to the softmac struct + * shouldn't the sequence number be in ieee80211? */ +} + +static int dw_ieee80211_handle_probe_request ( + struct net_device * dev, + struct ieee80211_probe_request * req, + struct ieee80211_rx_stats * stats) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + + if (priv->softmac->associnfo.associate_essid.len == req->info_element->len && + memcmp(priv->softmac->associnfo.associate_essid.data, req->info_element->data, priv->softmac->associnfo.associate_essid.len) == 0) { + struct ieee80211_probe_response *pkt = NULL; + struct ieee80211softmac_device *mac = priv->softmac; + struct ieee80211softmac_network *net = &priv->adhoc; + u8 *data; + u32 pkt_size = 0; + int encrypt_mpdu = 0; + + pkt = (struct ieee80211_probe_response *)ieee80211softmac_alloc_mgt(priv->beacon_body_length); + + if (unlikely(pkt == NULL)) { + printk("Error, packet is nonexistant or 0 length\n"); + return -ENOMEM; + } + + ieee80211softmac_hdr_3addr(mac, &(pkt->header), IEEE80211_STYPE_PROBE_RESP, net->bssid, priv->adhoc.bssid); + data = (u8 *)pkt->info_element; + memcpy(pkt->info_element, priv->beacon_frame.body, priv->beacon_body_length); + data += priv->beacon_body_length; + pkt_size = (data - (u8 *)pkt); + ieee80211_tx_frame(priv->ieee, (struct ieee80211_hdr *)pkt, IEEE80211_3ADDR_LEN, pkt_size, encrypt_mpdu); + kfree(pkt); + } + + return 0; +} + +/** + * dw_ieee80211_hard_start_xmit - prepares and sends a frame or put it in tx + * queue for transmission when current frames are completed. + */ +static int dw_ieee80211_hard_start_xmit( + struct ieee80211_txb* txb, + struct net_device* dev, + int priority) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + dw_frame_tx_t* frame = NULL; + int err = -ENOMEM; + unsigned long flags; + unsigned long ieeeflags; + int i; + dw_frame_tx_info_t info; + + DBG_FN(DBG_TX); + + /* sanity checks of txb */ + BUG_ON(!txb->nr_frags); /* nothing to do */ + if (txb->nr_frags >= TX_MAX_FRAGS) { + /* we are lazy and use a fixed-size array */ + ERROR("Too many fragments, dropping it: %i", txb->nr_frags); + goto error; + } + + /* Check if the device queue is big enough for every fragment. If not, + * drop the whole packet. */ + for (i = 0; i < txb->nr_frags; i++) { + if (unlikely(txb->fragments[ i ]->len > + (HW_TX_FIFO_SIZE + sizeof(dw_hw_hdr_tx_t)))) { + ERROR("PIO Tx Device queue too small, dropping it"); + + goto error; + } + } + + if (netif_queue_stopped(dev)) { + /* apperently, softmac doesn't honor netif_queue_stopped, + at least for control messages. Block it ourself. */ + err = -EBUSY; + goto error; + } + + /* take the next free queue entry and provide it's data */ + dw_lock(priv, flags); + + /* prepare everything. */ + if (!dw_tx_frame_prepare(priv, &info, txb)) + goto error_unlock; + + frame = list_entry(priv->tx.free.next, dw_frame_tx_t, list); + frame->s = info; + + /* put it to tx queue*/ + if (list_empty(&priv->tx.queued) && !priv->tx.pending) { + /* no transmission is running yet, so the queue won't be + processed by int handler. Write it directly to FIFO + start with first fragment. */ + priv->tx.retries = 0; + priv->tx.times_sent = 0; + dw_tx_fragment_send(priv, frame); + } + /* int handler is locked, so we can manage queue list after sending */ + list_move_tail(priv->tx.free.next, &priv->tx.queued); + + if (list_empty(&priv->tx.free)) { + /* Driver starts always with free entries. + But now they are all in use. Wait until we have some free + entries */ + netif_stop_queue(dev); + } + + dw_unlock(priv, flags); + + return 0; + +error_unlock: + dw_unlock(priv, flags); + +error: + /* txb is free'd on !0 return */ + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + priv->ieee->stats.tx_dropped++; + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + + return err; +} + +/** + * dw_update_status_led - updates the LED depending on association info + */ +static void dw_update_status_led(dw_priv_t* priv) +{ + static int reduce_rate = 0; + reduce_rate++; /* slow it down to 500ms, don't divide */ + + if (reduce_rate == 9) { + static int count = 0; + reduce_rate = 0; + + count++; + spin_lock(&priv->ieee->lock); + spin_lock(&priv->softmac->lock); + if (priv->softmac->associnfo.associated) { + if (IW_MODE_ADHOC == priv->ieee->iw_mode) + /* slow blink, 7/8 on */ + dw_set_led_on(PIN_LED, count & 7); + else + dw_set_led_on(PIN_LED, 1); /* solid on */ + } else + dw_set_led_on(PIN_LED, count & 1); /* faster blink */ + spin_unlock(&priv->softmac->lock); + spin_unlock(&priv->ieee->lock); + } +} + +/** + * dw_rate_reset - resets rate statistics + */ +static void dw_rate_reset(dw_priv_t* priv) +{ + int i; + + DBG_FN(DBG_INTERFACE | DBG_UPDATE_RATE); + REQUIRE_LOCKED(priv); + + CLEAR(priv->rate); + + priv->rate.success_threshold = THRESH_MIN; + + /* find index in ap_ri that matches default_rate */ + for (i = 0; i < priv->ap_ri.count; i++) { + if (priv->softmac->txrates.default_rate == BASIC_RATE_MASK(priv->ap_ri.rates[ i ])) { + priv->rate.index = i; + break; + } + } +} + +/** + * dw_rate_update - selects a better rate depending on frame error count + * + * taken from wifi_mac:mac_rate.c:UpdateRate + */ +static void dw_rate_update(dw_priv_t* priv) +{ + int changed = 0; + + DBG_FN(DBG_UPDATE_RATE); + REQUIRE_LOCKED(priv); + + if (atomic_read(&priv->fix_rate) || !rate_is_enough(priv)) + /* user requested fix rate or not enough data*/ + return; + + spin_lock(&priv->softmac->lock); + if (!priv->softmac->associnfo.associated) + goto out; + + if (rate_is_success(priv)) { + /* try to increase rate */ + priv->rate.success++; + if ((priv->rate.success >= priv->rate.success_threshold) && + (priv->rate.index < (priv->ap_ri.count - 1))) { + /* we are successfull long enough */ + priv->rate.recovery = 1; + priv->rate.success = 0; + priv->rate.index++; + changed = 1; + } else + priv->rate.recovery = 0; + } else if (rate_is_failure(priv)) { + /* decrease rate */ + if (priv->rate.index > 0) { + if (priv->rate.recovery) { + /* errors resulted from a rate increase. + double successfull intervals needed to + incrase next time */ + priv->rate.success_threshold *= 2; + if (priv->rate.success_threshold > THRESH_MAX) + priv->rate.success_threshold = THRESH_MAX; + } else + priv->rate.success_threshold = THRESH_MIN; + + priv->rate.index--; + changed = 1; + } + + priv->rate.success = 0; + priv->rate.recovery = 0; + } + + if (changed) { + priv->softmac->txrates.default_rate = BASIC_RATE_MASK(priv->ap_ri.rates[ priv->rate.index ]); + DBG(DBG_UPDATE_RATE, "Rate changed to %i", + priv->softmac->txrates.default_rate); + } + + /* reset counter so we work on the next time slice */ + priv->rate.tx_data_any = priv->rate.tx_data_ack = 0; + +out: + spin_unlock(&priv->softmac->lock); +} + +/** + * dw_management_timer - performs all periodic background non tx/rx work + */ +static void dw_management_timer(unsigned long a) +{ + dw_priv_t* priv = (dw_priv_t*) a; + unsigned long flags; + + DBG_FN(DBG_UPDATE_RATE); + + dw_lock(priv, flags); + + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: + if (!priv->softmac->scanning && !priv->beacon_ready) { + schedule_delayed_work(&priv->beacon_work, 0); + } + break; + case IW_MODE_INFRA: + if (priv->softmac->associnfo.associated && priv->jiffies_last_beacon + BEACON_TIMEOUT < jiffies) + { + priv->softmac->associnfo.associated = 0; + ieee80211softmac_start_scan(priv->softmac); + priv->jiffies_last_beacon = jiffies; + ieee80211softmac_try_reassoc(priv->softmac); + priv->reconnection_attempts = 1; + } else if ( + !priv->softmac->associnfo.associated + && !priv->softmac->associnfo.associating + && priv->reconnection_attempts + && priv->softmac->associnfo.req_essid.len + && priv->softmac->associnfo.req_essid.data + && *priv->softmac->associnfo.req_essid.data + && priv->jiffies_last_beacon + RESCAN_TIMEOUT < jiffies) + { + if (priv->reconnection_attempts < MAX_RECONNECTION_ATTEMPTS) { + priv->softmac->associnfo.associated = 0; + ieee80211softmac_start_scan(priv->softmac); + priv->jiffies_last_beacon = jiffies; + ieee80211softmac_try_reassoc(priv->softmac); + priv->reconnection_attempts++; + } else { + ieee80211softmac_disassoc(priv->softmac); + memset(priv->softmac->associnfo.bssid, 0, ETH_ALEN); + priv->reconnection_attempts = 0; + } + } + break; + default: + break; + } + + dw_update_status_led(priv); + + if (priv->rate.have_activity) { + priv->rate.have_activity = 0; + /* blink when data arrives */ + dw_set_led_on(PIN_ACTIVITY_LED, + priv->activity.counter & 1); + + priv->activity.counter++; + } else + /* turn it off */ + dw_set_led_on(PIN_ACTIVITY_LED, 0); + + priv->rate.counter++; + if (!(priv->rate.counter % MANAGEMENT_TICKS_FOR_UPDATE)) + /* we don't need to update rate each LED timer event */ + dw_rate_update(priv); + + if (priv->tx.pending && time_after(jiffies, priv->dev->trans_start + TX_TIMEOUT)) { + /* we can't use tx_timeout. It only works when + * netif_carrier_ok, but that is set only when associated. + * But we could loose association and run into a tx + * hanger. Therefore we;d never get associated, and never + * reset.So do it homemade. */ + dw_tx_reset(priv); + } + + mod_timer(&priv->management_timer, jiffies + MANAGEMENT_JIFFIES); + dw_unlock(priv, flags); +} + +/** + * dw_hw_init_card - initializes card and memory settings + */ +static int __init dw_hw_init_card(struct net_device* dev) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + unsigned long flags; + u32 firmware_id; + u32 ctrl; + u32 mac_status; + + DBG_FN(DBG_INIT); + + dw_lock(priv, flags); + + /* Configure PIO pins */ + gpio_direction_output(PIN_LED, 0); + gpio_direction_output(PIN_ACTIVITY_LED, 0); + dw_set_led_on(PIN_LED, 0); + dw_set_led_on(PIN_ACTIVITY_LED, 0); + gpio_configure_ns9360(PIN_INTR, 0, 1, 0); + + /* CS2 is used for wireless baseband registers. + Map to MAC_BASE address, 32 bit width, 8 wait states.*/ + iowrite32(0x82, MEM_DMCONF(2)); + iowrite32(0, MEM_SMWED(2)); + iowrite32(2, MEM_SMOED(2)); + iowrite32(8, MEM_SMRD(2)); + iowrite32(0, MEM_SMPMRD(2)); + iowrite32(4, MEM_SMWD(2)); + iowrite32(2, MEM_SWT(2)); + + iowrite32(MAC_BASE_PHYS, SYS_SMCSSMB(2)); + iowrite32(MAC_MASK, SYS_SMCSSMM(2)); + + /* Init baseband processor and MAC assist */ + + ctrl = GEN_INIT | GEN_ANTDIV; + + /* we can't do a softreset here */ + + /* go back to normal state */ + + dw_iowrite32(ctrl, HW_GEN_CONTROL); + dw_iowrite32(0, HW_MAC_CONTROL); + dw_iowrite32(0, HW_INTR_MASK); + /* Mike's recommendation for antenna diversity and antenna map value. + Transmit only on primary antenna, receive on diversity */ + dw_iowrite32(0x88000000, HW_RSSI_AES); + + /* get MAC from fpga */ + dw_hw_memcpy_from(dev->dev_addr, HW_STAID0, ETH_ALEN); + + /* Initialize RF tranceiver */ +#if defined(CONFIG_DIGI_WI_G_UBEC_JD) + dw_hw_write_rf(7, 0x27ffe); /* TEST register */ + dw_hw_write_rf(6, 0xf81ac); /* Filter Register */ + dw_set_tx_power(priv, DW_TX_POWER_DEFAULT); /* Transmitter Gain */ + dw_hw_write_rf(4, 0x0002b); /* Receiver Gain */ + + dw_hw_set_vco(0); /* no channel selected yet */ + + dw_hw_write_rf(0, 0x25f9c); /* Mode Control - Calibrate Filter */ + udelay(10); +#elif defined(CONFIG_DIGI_WI_G_UBEC_HC) + dw_hw_set_vco(0); /* no channel selected yet */ + dw_hw_write_rf(4, 0x0007b); /* Receiver Gain */ + dw_set_tx_power(priv, DW_TX_POWER_DEFAULT); /* Transmitter Gain */ + dw_hw_write_rf(6, 0xf81ac); /* Filter Register */ + dw_hw_write_rf(7, 0x3fffe); /* TEST register */ + + dw_hw_write_rf(0, 0x27fdc); /* Mode Control - Calibrate Filter */ + udelay(10); + dw_hw_write_rf(0, 0x27fd4); /* Mode Control - RX/TX mode */ +#endif + + /* Firmware version */ + firmware_id = dw_ioread32(HW_VERSION); + mac_status = dw_ioread32(HW_MAC_STATUS); + printk(KERN_DEBUG DRIVER_NAME ": FPGA HW version: %i.%02i FW Version: %i.%02i\n", + (firmware_id >> 8) & 0xff, firmware_id & 0xff, + (mac_status >> 24) & 0xff, (mac_status >> 16) & 0xff); + + dw_unlock(priv, flags); + + return 1; +} + +/** + * dw_reset_rx_dups - resets the duplicate list and forgets the senders + */ +static void dw_reset_rx_dups(dw_priv_t* priv) +{ + unsigned long flags; + int i; + + REQUIRE_UNLOCKED(priv); + + dw_lock(priv, flags); + + CLEAR(priv->rx.dups); + + /* for detecting duplicates */ + INIT_LIST_HEAD(&priv->rx.dups.known.list); + INIT_LIST_HEAD(&priv->rx.dups.free.list); + for (i = 0; i < ARRAY_SIZE(priv->rx.dups.entries); i++) { + struct list_head* entry = &priv->rx.dups.entries[ i ].list; + INIT_LIST_HEAD(entry); + list_add_tail(entry, &priv->rx.dups.free.list); + } /* for (i = 0) */ + + dw_unlock(priv, flags); +} + +/** + * dw_setup_rx_queue - creates the list structure + * + * @return: -ENOMEM on no failures otherwise 0 + */ +static int __init dw_setup_rx_queue(dw_priv_t* priv) +{ + dw_frame_rx_t* tmp = NULL; + size_t aligned_size; + size_t buffer_size; + int i; + + INIT_LIST_HEAD(&priv->rx.queue.free.list); + INIT_LIST_HEAD(&priv->rx.queue.filled.list); + + aligned_size = ((char*) &tmp[ 1 ]) - ((char*) &tmp[ 0 ]); + buffer_size = aligned_size * RX_QUEUE_SIZE; + priv->rx.queue.buffer = + (dw_frame_rx_t*) vmalloc(buffer_size); + if (NULL == priv->rx.queue.buffer) + goto error; + + memset(priv->rx.queue.buffer, 0, buffer_size); + + /* create frame skb and list for moving unswapped frames */ + for (i = 0; i < RX_QUEUE_SIZE; i++) { + dw_frame_rx_t* frame = &priv->rx.queue.buffer[ i ]; + struct list_head* entry = &frame->list; + + frame->skb = dev_alloc_skb(DW_MTU); + if (NULL == frame->skb) { + for (; i > 0; i--) { + dev_kfree_skb(frame->skb); + frame->skb = NULL; + } + + goto error_skb; + } + + INIT_LIST_HEAD(entry); + list_add(entry, &priv->rx.queue.free.list); + } + + return 0; + +error_skb: + vfree(priv->rx.queue.buffer); + priv->rx.queue.buffer = NULL; +error: + ERROR("No Memory for Rx Queue"); + return -ENOMEM; +} + +/** + * dw_start_dev - starts resource + */ +static int dw_start_dev(struct platform_device* pdev) +{ + struct net_device* dev = to_dev(pdev); + struct dw_priv* priv = ieee80211softmac_priv(dev); + struct list_head* cursor; + int err = -ENODEV; + int i; + + DBG_FN(DBG_INIT); + + CLEAR(*priv); + + spin_lock_init(&priv->lock); + priv->dev = dev; + priv->short_retry_limit = DW_DEFAULT_SHORT_RETRY_LIMIT; + priv->long_retry_limit = DW_DEFAULT_LONG_RETRY_LIMIT; + snprintf(priv->nick, IW_ESSID_MAX_SIZE, "Digi Wireless b/g"); + + atomic_set(&priv->fix_rate, 0); + + /* the stack may later reduce the number of supported rates if the + * access point can't provide them */ + ASSERT(ARRAY_SIZE(dw_rates) <= ARRAY_SIZE(priv->ap_ri.rates)); + for (i = 0; i < ARRAY_SIZE(dw_rates); i++) + priv->ap_ri.rates[ i ] = dw_rates[ i ]; + priv->ap_ri.count = i; + + /* rx */ + err = dw_setup_rx_queue(priv); + if (err < 0) + goto error_rx_queue; + + tasklet_init(&priv->rx.tasklet, dw_rx_tasklet_handler, + (unsigned long) priv); + tasklet_disable(&priv->rx.tasklet); /* will be enabled in open */ + + /* tx */ + atomic_set(&priv->tx.seq_nr, 0); + /* first down should sleep */ + init_MUTEX_LOCKED(&priv->tx.pause_sem); + INIT_LIST_HEAD(&priv->tx.queued); + INIT_LIST_HEAD(&priv->tx.free); + for (i = 0; i < ARRAY_SIZE(priv->tx.frames); i++) { + struct list_head* entry = &priv->tx.frames[ i ].list; + INIT_LIST_HEAD(entry); + list_add_tail(entry, &priv->tx.free); + } + + tasklet_init(&priv->tx.tasklet, dw_tx_tasklet_handler, + (unsigned long) priv); + tasklet_disable(&priv->tx.tasklet); + + init_timer(&priv->management_timer); + priv->management_timer.function = dw_management_timer; + priv->management_timer.data = (unsigned long) priv; + + if (!dw_hw_init_card(dev)) + goto error_init; + + /* card is setup, now we can register the irq */ + err = request_irq(dev->irq, dw_int, 0, dev->name, + priv); + if (err) { + ERROR("register interrupt %d failed, err %d", dev->irq, err); + goto error_irq; + } + disable_irq(dev->irq); + + priv->softmac = ieee80211_priv(dev); + priv->softmac->set_channel = dw_softmac_set_chan; + priv->softmac->set_bssid_filter = dw_softmac_set_bssid_filter; + priv->softmac->txrates_change = dw_softmac_txrates_change; + priv->ieee = netdev_priv(dev); + priv->ieee->modulation = IEEE80211_OFDM_MODULATION | IEEE80211_CCK_MODULATION; + priv->ieee->hard_start_xmit = dw_ieee80211_hard_start_xmit; + priv->ieee->set_security = dw_ieee80211_set_security; + priv->ieee->handle_beacon = dw_ieee80211_handle_beacon; + priv->ieee->handle_probe_request = dw_ieee80211_handle_probe_request; + priv->ieee->iw_mode = IW_MODE_INFRA; + priv->ieee->freq_band = IEEE80211_24GHZ_BAND; + priv->ieee->rts = DW_MAX_RTS_THRESHOLD; + priv->ieee->perfect_rssi = MAC_RSSI_MAX; + priv->ieee->worst_rssi = 0; + priv->ieee->config |= CFG_IEEE80211_RTS; + + if (dw_geo_init(priv) < 0) + goto error_init2; + + if (register_netdev(dev)) + goto error_init2; + + ieee80211softmac_notify(dev, IEEE80211SOFTMAC_EVENT_AUTHENTICATED, + dw_softmac_notify_authenticated, NULL); + + return 0; + +error_init2: + free_irq(dev->irq, priv); +error_irq: +error_init: + list_for_each(cursor, &priv->rx.queue.free.list) { + dw_frame_rx_t* frame = list_entry(cursor, dw_frame_rx_t, list); + dev_kfree_skb(frame->skb); + frame->skb = NULL; + } + vfree(priv->rx.queue.buffer); + priv->rx.queue.buffer = NULL; + +error_rx_queue: + return err; +} + +/** + * dw_stop_dev - stops device + */ +static void dw_stop_dev(struct platform_device* pdev) +{ + struct net_device* dev = to_dev(pdev); + struct dw_priv* priv = ieee80211softmac_priv(dev); + unsigned long flags; + struct list_head* it; + + DBG_FN(DBG_INIT); + + dw_lock(priv, flags); + dw_iowrite32(0, HW_MAC_CONTROL); + dw_unlock(priv, flags); + + list_for_each(it, &priv->rx.queue.free.list) { + dw_frame_rx_t* frame = list_entry(it, dw_frame_rx_t, list); + dev_kfree_skb(frame->skb); + frame->skb = NULL; + } + vfree(priv->rx.queue.buffer); + + unregister_netdev(dev); + + dw_set_led_on(PIN_LED, 0); + dw_set_led_on(PIN_ACTIVITY_LED, 0); + + gpio_free(PIN_LED); + gpio_free(PIN_ACTIVITY_LED); + gpio_free(PIN_INTR); +} + +/** + * dw_remove - stops device and removes all resources + */ +static int dw_remove(struct platform_device* pdev) +{ + struct net_device* dev = to_dev(pdev); + + DBG_FN(DBG_INIT); + + dw_stop_dev(pdev); + + /* unmap resources */ + if (0 != dev->base_addr) { + iounmap(vbase); + vbase = NULL; + } + free_irq(dev->irq, ieee80211softmac_priv(dev)); + + free_ieee80211softmac(dev); + dev = NULL; + + platform_set_drvdata(pdev, NULL); + + release_resource(pdev->resource); + + return 0; +} + +/** + * dw_release_device - dummy function + */ +static void dw_release_device(struct device* dev) +{ + DBG_FN(DBG_INIT); + + /* nothing to do. But we need a !NULL function */ +} + +/** + * dw_softmac_txrates_change - adjusts rate to allowed values. + * + * Select a better on. + */ +static void dw_softmac_txrates_change(struct net_device* dev, u32 changes) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + int was_locked = spin_is_locked(&priv->lock); + unsigned long flags = 0; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + if (!was_locked) + /* we are called either directly from open/user interface, + or from our tasklet context when processing a frame.*/ + dw_lock(priv, flags); + + /* new rate */ + CLEAR(priv->rate); + + dw_rate_reset(priv); + + if (!was_locked) + dw_unlock(priv, flags); +} + +/** + * dw_wx_set_encode - changes the encoding + * + * Used by wireless tools <= 28 for WEP-1. Not AES + */ +static int dw_wx_set_encode(struct net_device* dev, + struct iw_request_info* info, union iwreq_data* data, + char* extra) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + + priv->ieee->host_encrypt = 1; + priv->ieee->host_encrypt_msdu = 1; + priv->ieee->host_decrypt = 1; + priv->ieee->host_mc_decrypt = 1; + + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + + return ieee80211_wx_set_encode(priv->ieee, info, data, extra); +} + +/** + * dw_wx_set_encodeext - changes the encoding + * + * Only used by wpa_supplicant or wireless_tool >= 29 + */ +static int dw_wx_set_encodeext(struct net_device* dev, + struct iw_request_info* info, union iwreq_data* data, + char* extra) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + struct iw_encode_ext* ext = (struct iw_encode_ext*) extra; + unsigned long ieeeflags; + int host = (dw_sw_aes || (IW_ENCODE_ALG_CCMP != ext->alg)); + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + + priv->ieee->host_encrypt = host; + priv->ieee->host_encrypt_msdu = host; + priv->ieee->host_decrypt = host; + priv->ieee->host_mc_decrypt = host; + + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + + /* wx_set_encodeext will initialize itself depending on host_encrypt. + * After wx_set_encodeext, it will be of no further use. */ + return ieee80211_wx_set_encodeext(priv->ieee, info, data, extra); +} + +/** + * dw_softmac_notify_authenticated - called when authenticated + * + * Resets statistics + */ +static void dw_softmac_notify_authenticated(struct net_device* dev, + int event_type, void* context) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INTERFACE); + + dw_lock(priv, flags); + dw_rate_reset(priv); + dw_unlock(priv, flags); +} + +/*********************************************************************** + * @Function: dw_ieee80211_get_network_by_bssid + * @Return: + * @Descr: Get a network from the list by BSSID with locking + ***********************************************************************/ +struct ieee80211_network * +dw_ieee80211_get_network_by_bssid(struct dw_priv *priv, u8 *bssid) +{ + unsigned long ieeeflags; + struct ieee80211_network *ieee_net = NULL, *tmp_net; + struct list_head *list_ptr; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + list_for_each(list_ptr, &priv->ieee->network_list) { + tmp_net = list_entry(list_ptr, struct ieee80211_network, list); + if (!memcmp(tmp_net->bssid, bssid, ETH_ALEN)) { + if (ieee_net==NULL) { + ieee_net = tmp_net; + } else { + if (tmp_net->last_scanned > ieee_net->last_scanned) + ieee_net = tmp_net; + } + } + } + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + return ieee_net; +} + +/* CCMP encryption data */ +static const u32 te4[256] = { + 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU, + 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U, + 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU, + 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U, + 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU, + 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U, + 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU, + 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U, + 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U, + 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU, + 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U, + 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U, + 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U, + 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU, + 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U, + 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U, + 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU, + 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U, + 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U, + 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U, + 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU, + 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU, + 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U, + 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU, + 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU, + 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U, + 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU, + 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U, + 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU, + 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U, + 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U, + 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U, + 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU, + 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U, + 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU, + 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U, + 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU, + 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U, + 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U, + 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU, + 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU, + 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU, + 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U, + 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U, + 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU, + 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U, + 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU, + 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U, + 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU, + 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U, + 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU, + 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU, + 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U, + 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU, + 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U, + 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU, + 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U, + 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U, + 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U, + 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU, + 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU, + 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U, + 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU, + 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U, +}; + +static const u32 rcon[] = { + /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ + 0x01000000, 0x02000000, + 0x04000000, 0x08000000, + 0x10000000, 0x20000000, + 0x40000000, 0x80000000, + 0x1B000000, 0x36000000, +}; + +/*********************************************************************** + * @Function: dw_aes_set_encrypt_key + * @Return: + * @Descr: Expands the cipher key into the encryption key schedule + ***********************************************************************/ +static int dw_aes_set_encrypt_key(const unsigned char * user_key, + const int bits, struct aes_key_st * key) +{ + u32 *rk; + int i = 0; + u32 temp; + + DBG_FN(DBG_SECURITY); + + if (!user_key || !key) + return -1; + if (bits != 128 && bits != 192 && bits != 256) + return -2; + + rk = (u32*)key->rd_key; + + rk[0] = GETU32(user_key ); + rk[1] = GETU32(user_key + 4); + rk[2] = GETU32(user_key + 8); + rk[3] = GETU32(user_key + 12); + if (bits == 128) { + while (1) { + temp = rk[3]; + rk[4] = rk[0] ^ + (te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (te4[(temp ) & 0xff] & 0x0000ff00) ^ + (te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[5] = rk[1] ^ rk[4]; + rk[6] = rk[2] ^ rk[5]; + rk[7] = rk[3] ^ rk[6]; + if (++i == 10) { + return 0; + } + rk += 4; + } + } + rk[4] = GETU32(user_key + 16); + rk[5] = GETU32(user_key + 20); + if (bits == 192) { + while (1) { + temp = rk[ 5]; + rk[ 6] = rk[ 0] ^ + (te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (te4[(temp ) & 0xff] & 0x0000ff00) ^ + (te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 7] = rk[ 1] ^ rk[ 6]; + rk[ 8] = rk[ 2] ^ rk[ 7]; + rk[ 9] = rk[ 3] ^ rk[ 8]; + if (++i == 8) { + return 0; + } + rk[10] = rk[ 4] ^ rk[ 9]; + rk[11] = rk[ 5] ^ rk[10]; + rk += 6; + } + } + rk[6] = GETU32(user_key + 24); + rk[7] = GETU32(user_key + 28); + if (bits == 256) { + while (1) { + temp = rk[ 7]; + rk[ 8] = rk[ 0] ^ + (te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (te4[(temp ) & 0xff] & 0x0000ff00) ^ + (te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 9] = rk[ 1] ^ rk[ 8]; + rk[10] = rk[ 2] ^ rk[ 9]; + rk[11] = rk[ 3] ^ rk[10]; + if (++i == 7) { + return 0; + } + temp = rk[11]; + rk[12] = rk[ 4] ^ + (te4[(temp >> 24) ] & 0xff000000) ^ + (te4[(temp >> 16) & 0xff] & 0x00ff0000) ^ + (te4[(temp >> 8) & 0xff] & 0x0000ff00) ^ + (te4[(temp ) & 0xff] & 0x000000ff); + rk[13] = rk[ 5] ^ rk[12]; + rk[14] = rk[ 6] ^ rk[13]; + rk[15] = rk[ 7] ^ rk[14]; + + rk += 8; + } + } + return 0; +} + + +/*********************************************************************** + * @Function: dw_ccmp_set_key + * @Return: + * @Descr: + ***********************************************************************/ +static u8 dw_ccmp_set_key(struct dw_priv *priv, int id, u8 *data, int len, u8 *seq) +{ + ccmp_key_t *key; + + DBG_FN(DBG_SECURITY); + + key = &priv->aeskeys[id]; + + if (len == CCMP_KEY_SIZE) + { + CLEAR(*key); + key->valid = 1; + /* Set sequence counter if given */ + if (seq) + key->rx_pn = dw_to_48(GET32(seq+2), GET16(seq)); + + /* Store only AES key schedule for this key */ + dw_aes_set_encrypt_key(data, AES_BITS, &key->rk); + } + else if (len == 0) { + key->valid = 0; + } + else + return 0; + + return 1; +} + + +/*********************************************************************** + * @Function: dw_send_key + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_send_key(int index, unsigned long *key) +{ + DBG_FN(DBG_SECURITY); + + if (!dw_hw_aes_wait()) + goto error; + + /* Set key load mode */ + dw_iowrite32(0x14 | index, HW_AES_MODE); + + /* Write key to hw */ + dw_hw_aes_write_fifo_noswap(key, 4*44); + + return 0; + +error: + return -1; +} + + +/*********************************************************************** + * @Function: dw_send_tx_key + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_send_tx_key(struct dw_priv *priv, int index) +{ + ccmp_key_t *key; + + DBG_FN(DBG_SECURITY); + + if (!(priv->ieee->sec.flags & (1 << index))) + return 0; + + /* expand key */ + if (0 == dw_ccmp_set_key(priv, index, priv->ieee->sec.keys[index], priv->ieee->sec.key_sizes[index], 0)) { + printk(KERN_ERR "Error while expanding tx Key!!!\n"); + return -1; + } + key = &priv->aeskeys[index]; + + return dw_send_key(index, key->rk.rd_key); +} + + +/*********************************************************************** + * @Function: dw_send_keys + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_send_keys(struct dw_priv *priv) +{ + ccmp_key_t *key; + int i, len, err = 0; + + DBG_FN(DBG_SECURITY); + REQUIRE_LOCKED(priv); + + /* Note: AES keys cannot be set for multiple times. + * Only set it at the first time. */ + for (i = 0; i < 4; i++) { + key = &priv->aeskeys[i]; + len = priv->ieee->sec.key_sizes[i]; + if (!(priv->ieee->sec.flags & (1 << i))) { + continue; + } + + /* expand key */ + if (0 == dw_ccmp_set_key(priv, i, priv->ieee->sec.keys[i], len, 0)) { + printk(KERN_ERR "Error while expanding Key!!!\n"); + continue; + } + + if (dw_send_key(i, key->rk.rd_key)<0) + err = -1; + } + return err; +} + + +/*********************************************************************** + * @Function: dw_set_hwcrypto_keys + * @Return: + * @Descr: + ***********************************************************************/ +static void dw_set_hwcrypto_keys(struct dw_priv *priv) +{ + int err = 0; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + switch (priv->ieee->sec.level) { + case SEC_LEVEL_3: + if (priv->ieee->sec.flags & SEC_ACTIVE_KEY) + if (dw_send_tx_key(priv, priv->ieee->sec.active_key) < 0) + err = -1; + + if (!dw_sw_aes) + if (dw_send_keys(priv) < 0) + err = -1; + break; + default: + break; + } + if (err < 0) + printk(KERN_ERR "Error while sending keys to hardware!!!\n"); +} + + +/*********************************************************************** + * @Function: dw_ieee80211_set_security + * @Return: + * @Descr: set_security() callback in struct ieee80211_device + ***********************************************************************/ +static void dw_ieee80211_set_security(struct net_device *dev, + struct ieee80211_security *sec) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + int i, flag; + unsigned long flags; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + for (i = 0; i < 4; i++) { + flag = 1 << i; + if (sec->flags & (flag)) { + priv->ieee->sec.encode_alg[i] = sec->encode_alg[i]; + priv->ieee->sec.key_sizes[i] = sec->key_sizes[i]; + if (sec->key_sizes[i] == 0) + priv->ieee->sec.flags &= ~(flag); + else { + memcpy(priv->ieee->sec.keys[i], sec->keys[i], + sec->key_sizes[i]); + priv->ieee->sec.flags |= (flag); + } + } else if (sec->level != SEC_LEVEL_1) + priv->ieee->sec.flags &= ~(flag); + } + + if (sec->flags & SEC_ACTIVE_KEY) { + if (sec->active_key <= 3) { + priv->ieee->sec.active_key = sec->active_key; + priv->ieee->sec.flags |= SEC_ACTIVE_KEY; + } else + priv->ieee->sec.flags &= ~SEC_ACTIVE_KEY; + } else + priv->ieee->sec.flags &= ~SEC_ACTIVE_KEY; + + if ((sec->flags & SEC_AUTH_MODE) && + (priv->ieee->sec.auth_mode != sec->auth_mode)) { + priv->ieee->sec.auth_mode = sec->auth_mode; + priv->ieee->sec.flags |= SEC_AUTH_MODE; + } + + if (sec->flags & SEC_ENABLED && priv->ieee->sec.enabled != sec->enabled) { + priv->ieee->sec.flags |= SEC_ENABLED; + priv->ieee->sec.enabled = sec->enabled; + } + + if (sec->flags & SEC_ENCRYPT) + priv->ieee->sec.encrypt = sec->encrypt; + + if (sec->flags & SEC_LEVEL && priv->ieee->sec.level != sec->level) { + priv->ieee->sec.level = sec->level; + priv->ieee->sec.flags |= SEC_LEVEL; + } + + if (!priv->ieee->host_encrypt && (sec->flags & SEC_ENCRYPT)) + dw_set_hwcrypto_keys(priv); + + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + dw_unlock(priv, flags); +} + + +/*********************************************************************** + * @Function: dw_softmac_set_bssid_filter + * @Return: + * @Descr: Set BSS mode and IDs (irq) + ***********************************************************************/ +static void dw_softmac_set_bssid_filter(struct net_device *dev, const u8 *bssid) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + struct ieee80211softmac_network *net; + int i; + int was_locked = spin_is_locked(&priv->lock); + unsigned long flags = 0; + int last_basic = 0; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + if (!was_locked) + dw_lock(priv, flags); + + priv->beacon_ready = 0; + + /* Set BSSID in hardware */ + dw_hw_memcpy_to(HW_BSSID0, bssid, ETH_ALEN); + + if (priv->softmac->associnfo.associated) { + /* Set SSID in hardware */ + dw_iowrite32(priv->softmac->associnfo.associate_essid.len | + 0x000e0000, HW_SSID_LEN); + /*TODO set rates*/ + + /* get ratesinfo from assocnetwork */ + net = ieee80211softmac_get_network_by_essid_locked(priv->softmac, &priv->softmac->associnfo.associate_essid); + + if (net) { + /* provide only the subset of the rates we support */ + priv->ap_ri.count = 0; + + for (i=0; i < net->supported_rates.count; i++) { + int j = 0; + for (j = 0; j < ARRAY_SIZE(dw_rates); j++) { + if ((net->supported_rates.rates[ i ] & 0x7F) == (dw_rates[ j ] & 0x7F)) { + priv->ap_ri.rates[ priv->ap_ri.count++ ] = net->supported_rates.rates[ i ]; + + break; + } + } + } + } + + + dw_hw_memcpy_to(HW_SSID, priv->softmac->associnfo.associate_essid.data, priv->softmac->associnfo.associate_essid.len); + /* Disable IBSS mode */ + //dw_iocleanbits32(HW_MAC_CONTROL, CTRL_IBSS | CTRL_BEACONTX); + } else { + /* reset it */ + for (i = 0; i < ARRAY_SIZE(dw_rates); i++) + priv->ap_ri.rates[ i ] = dw_rates[ i ]; + priv->ap_ri.count = i; + } + + /* determine equal or lower basic rate for the rate being used. + The acknowledge of AP is send with + api_ri.rates[ basics[ data_rate_index ] ]*/ + for (i = 0; i < ARRAY_SIZE(priv->tx.basics); i++) { + if ((priv->ap_ri.rates[ i ] & IEEE80211_BASIC_RATE_MASK) == IEEE80211_BASIC_RATE_MASK) + last_basic = i; + priv->tx.basics[ i ] = last_basic; + } + + priv->softmac->txrates.default_rate = BASIC_RATE_MASK(priv->ap_ri.rates[ priv->rate.index ]); + DBG(DBG_UPDATE_RATE, "Rate set to %i", priv->softmac->txrates.default_rate); + priv->rate.tx_data_any = priv->rate.tx_data_ack = 0; // reset counter so we work on the next time slice + + if (!was_locked) + dw_unlock(priv, flags); + +} + +/*********************************************************************** + * @Function: dw_set_channel + * @Return: + * @Descr: Select a channel + ***********************************************************************/ +static void dw_set_channel(dw_priv_t* priv, u8 channel) +{ + DBG_FN(DBG_INTERFACE | DBG_MINOR); + REQUIRE_LOCKED(priv); + + dw_iocleanbits32(HW_GEN_CONTROL, GEN_RXEN); + priv->beacon_ready = 0; + + /* Set frequency divider for channel */ + dw_hw_write_rf (1, freq_table[channel].integer); + dw_hw_write_rf (2, freq_table[channel].fraction); + +#if defined(CONFIG_DIGI_WI_G_UBEC_JD) + /* Filter calibration */ + dw_hw_write_rf (0, 0x25f9c); + udelay (10); + + /* VCO calibration */ + dw_hw_write_rf (0, 0x25f9a); + udelay (80); + + /* Mode Control - RX/TX mode */ + dw_hw_write_rf (0, 0x25f94); + + /* Allow the trannsceiver to settle in the new mode of operation */ + udelay (10); +#elif defined(CONFIG_DIGI_WI_G_UBEC_HC) + /* VCO Calibration */ + dw_hw_write_rf (0, 0x27fda); + udelay(40); + + /* Set frequency divider for channel again */ + dw_hw_write_rf (1, freq_table[channel].integer); + dw_hw_write_rf (2, freq_table[channel].fraction); + + /* Mode Control - RX/TX mode */ + dw_hw_write_rf (0, 0x27fd4); + + dw_hw_set_vco(channel); +#endif + priv->channel = channel; + dw_iosetbits32(HW_GEN_CONTROL, GEN_RXEN); +} + +/*********************************************************************** + * @Function: dw_softmac_set_chan + * @Return: + * @Descr: Select a channel) + ***********************************************************************/ +static void dw_softmac_set_chan(struct net_device* dev, u8 channel) +{ + dw_priv_t* priv = ieee80211softmac_priv(dev); + //struct ieee80211softmac_device *mac = ieee80211_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + //if (IW_MODE_ADHOC == priv->ieee->iw_mode && !mac->scanning) + // printk("Selected channel = %d\n", channel); + + /* we really don't want to change the channel while there is something + * in the queue for a different channel */ + dw_tx_wait_for_idle_and_pause(priv); + + dw_lock(priv, flags); + dw_set_channel( priv, channel); + dw_unlock(priv, flags); + + dw_tx_continue_queue(priv); +} + + +/*********************************************************************** + * @Function: dw_geo_init + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_geo_init(dw_priv_t *priv) +{ + struct ieee80211_geo *geo; + struct ieee80211_channel *chan; + int i; + u8 channel; + const char *iso_country; + + DBG_FN(DBG_INIT); + + geo = kzalloc(sizeof(*geo), GFP_KERNEL); + if (!geo) + return -ENOMEM; + + iso_country = "XX"; + + for (i = 0, channel = IEEE80211_52GHZ_MIN_CHANNEL; + channel <= IEEE80211_52GHZ_MAX_CHANNEL; channel++) { + chan = &geo->a[i++]; + chan->freq = dw_channel_to_freq_a(channel); + chan->channel = channel; + } + geo->a_channels = i; + for (i = 0, channel = IEEE80211_24GHZ_MIN_CHANNEL; + channel <= IEEE80211_24GHZ_MAX_CHANNEL; channel++) { + chan = &geo->bg[i++]; + chan->freq = dw_channel_to_freq_bg(channel); + chan->channel = channel; + } + geo->bg_channels = i; + memcpy(geo->name, iso_country, 2); + if (0 /*TODO: Outdoor use only */) + geo->name[2] = 'O'; + else if (0 /*TODO: Indoor use only */) + geo->name[2] = 'I'; + else + geo->name[2] = ' '; + geo->name[3] = '\0'; + + ieee80211_set_geo(priv->ieee, geo); + kfree(geo); + + return 1; +} + + +/*********************************************************************** + * @Function: dw_get_name + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_get_name(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + strcpy(data->name, "IEEE 802.11b/g"); + return 0; +} + +/*********************************************************************** + * @Function: dw_wx_get_encode + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_encode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + int err; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + err = ieee80211_wx_get_encode(priv->ieee, info, data, extra); + + return err; +} + +/*********************************************************************** + * @Function: dw_wx_get_encodeext + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_encodeext(struct net_device *dev, + struct iw_request_info *info, union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + int err; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + err = ieee80211_wx_get_encodeext(priv->ieee, info, data, extra); + + return err; +} + + +/*********************************************************************** + * @Function: dw_wx_set_channelfreq + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_channelfreq(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + u8 channel; + int err = -EINVAL; + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_tx_wait_for_idle_and_pause(priv); + + dw_lock(priv, flags); + + if ((data->freq.m >= 0) && (data->freq.m <= 1000)) { + channel = data->freq.m; + } else { + channel = ieee80211_freq_to_channel(priv->ieee, data->freq.m); + } + if (ieee80211_is_valid_channel(priv->ieee, channel)) { + dw_set_channel(priv, channel); + err = 0; + } + + dw_unlock(priv, flags); + + dw_tx_continue_queue(priv); + + return err; +} + + +/*********************************************************************** + * @Function: dw_wx_get_channelfreq + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_channelfreq(struct net_device *dev, + struct iw_request_info *info, union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + data->freq.e = 1; + data->freq.m = ieee80211_channel_to_freq(priv->ieee, priv->channel) * 100000; + data->freq.flags = IW_FREQ_FIXED; + dw_unlock(priv, flags); + return 0; +} + +/*********************************************************************** + * @Function: dw_wx_set_mode + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_mode(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + + priv->ieee->iw_mode = data->mode; + + if (IW_MODE_ADHOC == priv->ieee->iw_mode) { + if (priv->channel) + priv->adhoc.channel = priv->channel; + } else { + dw_iocleanbits32(HW_MAC_CONTROL, CTRL_IBSS|CTRL_BEACONTX); /* Disable IBSS mode */ + } + + priv->beacon_ready = 0; + priv->beacon_body_length=0; + + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_get_mode + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_mode(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + data->mode = priv->ieee->iw_mode; + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_get_rangeparams + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_rangeparams(struct net_device *dev, + struct iw_request_info *info, union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + struct iw_range *range = (struct iw_range *)extra; + const struct ieee80211_geo *geo; + int i, j, level; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + data->data.length = sizeof(*range); + memset(range, 0, sizeof(*range)); + + /*TODO: What about 802.11b? Have to meassure!!! */ + /* 54Mb/s == ~27Mb/s payload throughput (802.11g) */ + range->throughput = 27 * 1000 * 1000; + range->min_nwid = 0x0000; + range->max_nwid = 0x0020; + + range->max_qual.qual = 100; + range->max_qual.level = MAC_RSSI_MAX; + range->max_qual.noise = 0; + range->max_qual.updated = + IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED; + + range->avg_qual.qual = 0; + range->avg_qual.level = 0; + range->avg_qual.noise = 0; + range->avg_qual.updated = IW_QUAL_NOISE_INVALID; + + range->min_rts = DW_MIN_RTS_THRESHOLD; + range->max_rts = DW_MAX_RTS_THRESHOLD; + range->min_frag = MIN_FRAG_THRESHOLD; + range->max_frag = MAX_FRAG_THRESHOLD; + + range->encoding_size[0] = 5; + range->encoding_size[1] = 13; + range->num_encoding_sizes = 2; + range->max_encoding_tokens = WEP_KEYS; + + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 19; + + range->enc_capa = IW_ENC_CAPA_WPA | + IW_ENC_CAPA_WPA2 | + IW_ENC_CAPA_CIPHER_TKIP | + IW_ENC_CAPA_CIPHER_CCMP; + + for (i = 0; i < ARRAY_SIZE(dw_rates); i++) + range->bitrate[ i ] = RATE_IN_BS(BASIC_RATE_MASK(dw_rates[ i ])); + range->num_bitrates = i; + + geo = ieee80211_get_geo(priv->ieee); + range->num_channels = geo->bg_channels; + j = 0; + for (i = 0; i < geo->bg_channels; i++) { + if (j == IW_MAX_FREQUENCIES) + break; + range->freq[j].i = j + 1; + range->freq[j].m = geo->bg[i].freq * 100000; + range->freq[j].e = 1; + j++; + } + range->num_frequency = j; + + /* retry limit capabilities */ + range->retry_capa = IW_RETRY_LIMIT | IW_RETRY_LIFETIME; + range->retry_flags = IW_RETRY_LIMIT; + range->r_time_flags = IW_RETRY_LIFETIME; + + /* I don't know the range.*/ + range->min_retry = 1; + range->max_retry = 65535; + range->min_r_time = 1024; + range->max_r_time = 65535 * 1024; + + /* txpower is supported in dBm's */ +// if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + range->txpower_capa = IW_TXPOW_DBM; + range->num_txpower = IW_MAX_TXPOWER; + for (i = 0, level = (DW_TX_POWER_MAX_DBM * 16); + i < IW_MAX_TXPOWER; + i++, level -= + ((DW_TX_POWER_MAX_DBM - + DW_TX_POWER_MIN_DBM) * 16) / (IW_MAX_TXPOWER - 1)) + range->txpower[i] = level / 16; +// } else { +// range->txpower_capa = 0; +// range->num_txpower = 0; +// } + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + + return 0; +} + + +/*********************************************************************** + * @Function: dw_get_wireless_stats + * @Return: + * @Descr: Get wireless statistics. Called by /proc/net/wireless and by SIOCGIWSTATS + ***********************************************************************/ +static struct iw_statistics *dw_get_wireless_stats(struct net_device *dev) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + struct ieee80211softmac_device *mac = ieee80211_priv(dev); + struct iw_statistics *wstats; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + wstats = &priv->wstats; + if (!mac->associnfo.associated) { + wstats->miss.beacon = 0; + wstats->discard.retries = 0; + wstats->discard.nwid = 0; + wstats->discard.code = 0; + wstats->discard.fragment = 0; + wstats->discard.misc = 0; + wstats->qual.qual = 0; + wstats->qual.level = 0; + wstats->qual.noise = 0; + wstats->qual.updated = IW_QUAL_NOISE_INVALID | + IW_QUAL_QUAL_INVALID | IW_QUAL_LEVEL_INVALID; + } else { + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + /* fill in the real statistics when iface associated */ + wstats->qual.qual = + (100 * + (priv->ieee->perfect_rssi - priv->ieee->worst_rssi) * + (priv->ieee->perfect_rssi - priv->ieee->worst_rssi) - + (priv->ieee->perfect_rssi - priv->ieee->networks->stats.rssi) * + (15 * (priv->ieee->perfect_rssi - priv->ieee->worst_rssi) + + 62 * (priv->ieee->perfect_rssi - + priv->ieee->networks->stats.rssi))) / + ((priv->ieee->perfect_rssi - + priv->ieee->worst_rssi) * (priv->ieee->perfect_rssi - + priv->ieee->worst_rssi)); + /* + * !TODO. Seems they are not calculated. Take + * one from the last frame */ + wstats->qual.level = priv->ieee->networks->stats.signal; + wstats->qual.noise = priv->ieee->networks->stats.noise; + wstats->qual.updated = IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED; + wstats->discard.code = priv->ieee->ieee_stats.rx_discards_undecryptable; + wstats->discard.retries = priv->ieee->ieee_stats.tx_retry_limit_exceeded; + wstats->discard.nwid = priv->ieee->ieee_stats.tx_discards_wrong_sa; + wstats->discard.fragment = priv->ieee->ieee_stats.rx_fragments; + wstats->discard.misc = priv->wstats.discard.misc; + wstats->miss.beacon = 0; /* FIXME */ + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + } + return wstats; +} + + +/*********************************************************************** + * @Function: dw_wx_set_nick + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_nick(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + size_t len; + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + len = min((size_t)data->data.length, (size_t)IW_ESSID_MAX_SIZE); + memcpy(priv->nick, extra, len); + priv->nick[len] = '\0'; + dw_unlock(priv, flags); + + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_get_nick + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_nick(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + size_t len; + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + len = strlen(priv->nick) + 1; + memcpy(extra, priv->nick, len); + data->data.length = (__u16)len; + dw_unlock(priv, flags); + + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_set_rts + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_rts(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + int err = -EINVAL; + unsigned long flags; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + if (data->rts.disabled) { + priv->ieee->rts = DW_MAX_RTS_THRESHOLD; + err = 0; + } else { + if (data->rts.value >= DW_MIN_RTS_THRESHOLD && + data->rts.value <= DW_MAX_RTS_THRESHOLD) { + priv->ieee->rts = data->rts.value; + err = 0; + } + } + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + dw_unlock(priv, flags); + + return err; +} + + +/*********************************************************************** + * @Function: dw_wx_get_rts + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_rts(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + data->rts.value = priv->ieee->rts; + data->rts.fixed = 0; + data->rts.disabled = (priv->ieee->rts == DW_MAX_RTS_THRESHOLD); + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + dw_unlock(priv, flags); + + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_set_rate + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_rate(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + s32 in_rate = data->bitrate.value; + int err = 0; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + + if (in_rate == -1) + /* auto */ + atomic_set(&priv->fix_rate, 0); + else + atomic_set(&priv->fix_rate, 1); + + err = ieee80211softmac_wx_set_rate(dev, info, data, extra); + + dw_unlock(priv, flags); + + return err; +} + + +/*********************************************************************** + * @Function: dw_wx_set_frag + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_frag(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + int err = -EINVAL; + unsigned long flags; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + if (data->frag.disabled) { + priv->ieee->fts = MAX_FRAG_THRESHOLD; + err = 0; + } else { + if (data->frag.value >= MIN_FRAG_THRESHOLD && + data->frag.value <= MAX_FRAG_THRESHOLD) { + priv->ieee->fts = data->frag.value & ~0x1; + err = 0; + } + } + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + dw_unlock(priv, flags); + + return err; +} + + +/*********************************************************************** + * @Function: dw_wx_get_frag + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_frag(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + unsigned long ieeeflags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + spin_lock_irqsave(&priv->ieee->lock, ieeeflags); + data->frag.value = priv->ieee->fts; + data->frag.fixed = 0; + data->frag.disabled = (priv->ieee->fts == MAX_FRAG_THRESHOLD); + spin_unlock_irqrestore(&priv->ieee->lock, ieeeflags); + dw_unlock(priv, flags); + + return 0; +} + +/*********************************************************************** + * @Function: dw_wx_set_xmitpower + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_xmitpower(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + int err = 0, value; + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + if ((data->txpower.flags & IW_TXPOW_TYPE) != IW_TXPOW_DBM) return -EINVAL; + if (data->txpower.fixed == 0) return -EINVAL; + if (data->txpower.value < DW_TX_POWER_MIN_DBM || data->txpower.value > DW_TX_POWER_MAX_DBM) return -EINVAL; + + value = data->txpower.value; + dw_lock(priv, flags); + err = dw_set_tx_power(priv, value); + dw_unlock(priv, flags); + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_get_xmitpower + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_xmitpower(struct net_device *dev, + struct iw_request_info *info, union iwreq_data *data, + char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + data->txpower.fixed = 1; + data->txpower.value = priv->tx_power; + data->txpower.flags = IW_TXPOW_DBM; + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_set_retry + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_set_retry(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + if (wrqu->retry.flags & IW_RETRY_LIFETIME || wrqu->retry.disabled) + return -EINVAL; + + if (!(wrqu->retry.flags & IW_RETRY_LIMIT)) + return 0; + + if (wrqu->retry.value < 0 || wrqu->retry.value > 255) + return -EINVAL; + + dw_lock(priv, flags); + if (wrqu->retry.flags & IW_RETRY_MIN) + priv->short_retry_limit = (u8) wrqu->retry.value; + else if (wrqu->retry.flags & IW_RETRY_MAX) + priv->long_retry_limit = (u8) wrqu->retry.value; + else { + priv->short_retry_limit = (u8) wrqu->retry.value; + priv->long_retry_limit = (u8) wrqu->retry.value; + } + + dw_unlock(priv, flags); + return 0; +} + + +/*********************************************************************** + * @Function: dw_wx_get_retry + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_wx_get_retry(struct net_device *dev, struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + wrqu->retry.disabled = 0; + + if ((wrqu->retry.flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME) { + dw_unlock(priv, flags); + return -EINVAL; + } + + if (wrqu->retry.flags & IW_RETRY_MAX) { + wrqu->retry.flags = IW_RETRY_LIMIT | IW_RETRY_MAX; + wrqu->retry.value = priv->long_retry_limit; + } else if (wrqu->retry.flags & IW_RETRY_MIN) { + wrqu->retry.flags = IW_RETRY_LIMIT | IW_RETRY_MIN; + wrqu->retry.value = priv->short_retry_limit; + } else { + wrqu->retry.flags = IW_RETRY_LIMIT; + wrqu->retry.value = priv->short_retry_limit; + } + dw_unlock(priv, flags); + + return 0; +} + +#ifdef WX +# undef WX +#endif +#define WX(ioctl) [(ioctl) - SIOCSIWCOMMIT] +static const iw_handler dw_wx_handlers[] = { + /* Wireless Identification */ + WX(SIOCGIWNAME) = dw_get_name, + /* Basic operations */ + WX(SIOCSIWFREQ) = dw_wx_set_channelfreq, + WX(SIOCGIWFREQ) = dw_wx_get_channelfreq, + WX(SIOCSIWMODE) = dw_wx_set_mode, + WX(SIOCGIWMODE) = dw_wx_get_mode, + /* Informative stuff */ + WX(SIOCGIWRANGE) = dw_wx_get_rangeparams, + /* Spy support (statistics per MAC address - used for Mobile IP support) */ + WX(SIOCSIWSPY) = iw_handler_set_spy, + WX(SIOCGIWSPY) = iw_handler_get_spy, + WX(SIOCSIWTHRSPY) = iw_handler_set_thrspy, + WX(SIOCGIWTHRSPY) = iw_handler_get_thrspy, + /* Access Point manipulation */ + WX(SIOCSIWAP) = ieee80211softmac_wx_set_wap, + WX(SIOCGIWAP) = ieee80211softmac_wx_get_wap, + WX(SIOCSIWSCAN) = ieee80211softmac_wx_trigger_scan, + WX(SIOCGIWSCAN) = ieee80211softmac_wx_get_scan_results, + /* 802.11 specific support */ + WX(SIOCSIWESSID) = ieee80211softmac_wx_set_essid, + WX(SIOCGIWESSID) = ieee80211softmac_wx_get_essid, + WX(SIOCSIWNICKN) = dw_wx_set_nick, + WX(SIOCGIWNICKN) = dw_wx_get_nick, + /* Other parameters */ + WX(SIOCSIWRATE) = dw_wx_set_rate, + WX(SIOCGIWRATE) = ieee80211softmac_wx_get_rate, + WX(SIOCSIWRTS) = dw_wx_set_rts, + WX(SIOCGIWRTS) = dw_wx_get_rts, + WX(SIOCSIWFRAG) = dw_wx_set_frag, + WX(SIOCGIWFRAG) = dw_wx_get_frag, + WX(SIOCSIWTXPOW) = dw_wx_set_xmitpower, + WX(SIOCGIWTXPOW) = dw_wx_get_xmitpower, + WX(SIOCSIWRETRY) = dw_wx_set_retry, + WX(SIOCGIWRETRY) = dw_wx_get_retry, + /* Encoding */ + WX(SIOCSIWENCODE) = dw_wx_set_encode, + WX(SIOCGIWENCODE) = dw_wx_get_encode, + WX(SIOCSIWENCODEEXT) = dw_wx_set_encodeext, + WX(SIOCGIWENCODEEXT) = dw_wx_get_encodeext, + /* Power saving */ +/*TODO WX(SIOCSIWPOWER) = dw_wx_set_power, */ +/*TODO WX(SIOCGIWPOWER) = dw_wx_get_power, */ + WX(SIOCSIWGENIE) = ieee80211softmac_wx_set_genie, + WX(SIOCGIWGENIE) = ieee80211softmac_wx_get_genie, + WX(SIOCSIWMLME) = ieee80211softmac_wx_set_mlme, + WX(SIOCSIWAUTH) = ieee80211_wx_set_auth, + WX(SIOCGIWAUTH) = ieee80211_wx_get_auth, +}; +#undef WX + +#ifdef NEED_PRIVATE_HANDLER +# include "dw_priv_handler.c" +#else +static const iw_handler dw_priv_wx_handlers[] = {}; +static const struct iw_priv_args dw_priv_wx_args[] = {}; +#endif + +const struct iw_handler_def dw_wx_handlers_def = { + .standard = dw_wx_handlers, + .num_standard = ARRAY_SIZE(dw_wx_handlers), + .num_private = ARRAY_SIZE(dw_priv_wx_handlers), + .num_private_args = ARRAY_SIZE(dw_priv_wx_args), + .private = dw_priv_wx_handlers, + .private_args = dw_priv_wx_args, + .get_wireless_stats = dw_get_wireless_stats, +}; + +/*********************************************************************** + * @Function: dw_close + * @Return: + * @Descr: + ***********************************************************************/ +static int dw_close(struct net_device* dev) +{ + struct dw_priv* priv = ieee80211softmac_priv(dev); + unsigned long flags; + struct list_head* it; + + DBG_FN(DBG_INIT); + + ieee80211softmac_stop(dev); + + /* don't disable anything before softmac_stop. Maybe SoftMAC will run a + * disassoc cycle somewhat later. */ + del_timer_sync(&priv->management_timer); + + cancel_delayed_work(&priv->beacon_work); + + disable_irq(dev->irq); + + tasklet_disable(&priv->rx.tasklet); + /* from this point, rx queue.filled is empty, and it is not paused */ + tasklet_disable(&priv->tx.tasklet); + + dw_lock(priv, flags); + + priv->tx.data_pending_ack = NULL; + + /* delete unsend frames */ + list_for_each(it, &priv->tx.queued) { + dw_frame_tx_t* frame = list_entry(it, dw_frame_tx_t, list); + ieee80211_txb_free(frame->s.txb); + frame->s.txb = NULL; + } + dw_list_move(&priv->tx.free, &priv->tx.queued); + + dw_iocleanbits32(HW_GEN_CONTROL, GEN_RXEN); + dw_iowrite32(0, HW_INTR_MASK); + dw_iowrite32(dw_ioread32(HW_INTR_STATUS), HW_INTR_STATUS); + + dw_set_led_on(PIN_LED, 0); + dw_set_led_on(PIN_ACTIVITY_LED, 0); + + dw_unlock(priv, flags); + + return 0; +} + +/*********************************************************************** + * @Function: dw_open + * @Return: + * @Descr: init/reinit + ***********************************************************************/ +static int dw_open(struct net_device* dev) +{ + struct dw_priv* priv = ieee80211softmac_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INIT); + + dw_reset_rx_dups(priv); + + priv->tx.data_pending_ack = NULL; + + priv->jiffies_last_beacon = jiffies; + priv->reconnection_attempts = 0; + + tasklet_enable(&priv->rx.tasklet); + tasklet_enable(&priv->tx.tasklet); + + ieee80211softmac_start(dev); + + ieee80211softmac_set_rates(dev, ARRAY_SIZE(dw_rates), (u8*) dw_rates); /* cast away const */ + + /* softmac sets user_rate to 24MB, but we start much slower to work + always */ + priv->softmac->txrates.default_rate = priv->softmac->txrates.user_rate = IEEE80211_CCK_RATE_1MB; + + dw_lock(priv, flags); + + priv->beacon_ready = 0; + INIT_DELAYED_WORK(&priv->beacon_work, dw_beacon_start); + priv->beacon_body_length=0; + priv->adhoc.channel = DW_IBSS_DEFAULT_CHANNEL; + + /* acknowledge pending interrupts */ + dw_iowrite32(dw_ioread32(HW_INTR_STATUS), HW_INTR_STATUS); + dw_iowrite32(INTR_ALL, HW_INTR_MASK); + dw_iowrite32(HW_AES_MODE_1, HW_AES_MODE ); + mod_timer(&priv->management_timer, jiffies + MANAGEMENT_JIFFIES); + + enable_irq(dev->irq); + + dw_iosetbits32(HW_GEN_CONTROL, GEN_RXEN); + + dw_unlock(priv, flags); + + return 0; +} + +/*********************************************************************** + * @Function: dw_set_multicast_list + * @Return: + * @Descr: + ***********************************************************************/ +static void dw_set_multicast_list(struct net_device* dev) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + unsigned long flags; + + DBG_FN(DBG_INTERFACE | DBG_MINOR); + + dw_lock(priv, flags); + if (dev->flags & IFF_PROMISC) { + dw_iosetbits32(HW_MAC_CONTROL, CTRL_PROMISC); + } else { + dw_iocleanbits32(HW_MAC_CONTROL, CTRL_PROMISC); + } + dw_unlock(priv, flags); +} + +/*********************************************************************** + * @Function: dw_net_get_stats + * @Return: + * @Descr: + ***********************************************************************/ +static struct net_device_stats * dw_net_get_stats(struct net_device *dev) +{ + struct dw_priv *priv = ieee80211softmac_priv(dev); + + DBG_FN(DBG_INTERFACE); + + return &priv->ieee->stats; +} + +/** + * dw_probe - probe for resources and for chip being present + * + * @return <0 on failure + */ +static int __init dw_probe(struct platform_device* pdev) +{ + struct net_device* dev = NULL; + int err = -ENODEV; + + DBG_FN(DBG_INIT); + + /* Create the network device object. */ + dev = alloc_ieee80211softmac(sizeof(dw_priv_t)); + if (NULL == dev) { + ERROR(": Couldn't alloc_ieee80211softmac "); + err = -ENOMEM; + goto error_alloc; + } + platform_set_drvdata(pdev, dev); + dev->irq = INTR_ID; + + if (dev_alloc_name(dev, "wlan%d") < 0) { + ERROR("Couldn't get name"); + goto error_name; + } + + /* allocate resources and map memory */ + if (gpio_request(PIN_LED, DRIVER_NAME) != 0) { + ERROR("GPIO %i already in use", PIN_LED); + goto error_pin_led; + } + if (gpio_request(PIN_ACTIVITY_LED, DRIVER_NAME) != 0) { + ERROR("GPIO %i already in use", PIN_ACTIVITY_LED); + goto error_pin_activity_led; + } + if (gpio_request(PIN_INTR, DRIVER_NAME) != 0) { + ERROR("GPIO %i already in use", PIN_INTR); + goto error_pin_intr; + } + + err = request_resource(&iomem_resource, pdev->resource); + if (err) { + ERROR("Memory already in used: 0x%08x...0x%08x", + pdev->resource->start, pdev->resource->end); + goto error_res; + } + + vbase = ioremap(pdev->resource->start, + pdev->resource->end - pdev->resource->start); + if (NULL == vbase) { + ERROR("ioremap failed"); + err = -ENOMEM; + goto error_remap; + } + dev->base_addr = (int) vbase; + + /* test if fpga is programmed */ + if (dw_ioread32(HW_VERSION) == 0xffffffff) { + ERROR("FPGA not present"); + goto error_fpga; + } + + /* all resources available, initializw remaining stuff */ + dev->open = dw_open; + dev->stop = dw_close; + dev->set_multicast_list = dw_set_multicast_list; + dev->wireless_handlers = &dw_wx_handlers_def; + dev->get_stats = dw_net_get_stats; + + /* initialize hardware/private stuff */ + err = dw_start_dev(pdev); + if (err) + goto error; + + return 0; + +error_fpga: + iounmap(vbase); + vbase = NULL; +error_remap: + release_resource(pdev->resource); +error_res: + gpio_free(PIN_INTR); +error_pin_intr: + gpio_free(PIN_ACTIVITY_LED); +error_pin_activity_led: + gpio_free(PIN_LED); +error_pin_led: +error_name: + /* the IRQ hasn't been setup, so unwind a part and don't go to + * dw_remove */ + free_ieee80211softmac(dev); +error_alloc: + return err; + +error: + /* dev memory is setup, so we can use the remove function */ + dw_remove(pdev); + + return err; +} + +/*********************************************************************** + * @Function: dw_init_module + * @Return: 0 if successfull; -ENODEV if interrupt or device is not available + * @Descr: initialize module + ***********************************************************************/ +static int __init dw_init_module(void) +{ + int err; + + DBG_FN(DBG_INIT); + + printk(KERN_INFO "%s\n", dw_version); + + if (dw_sw_aes) + printk(KERN_NOTICE "AES encoding/decoding is done in software\n"); + + err = platform_device_register(&dw_device); + if (err) { + ERROR("Device Register Failed"); + goto error; + } + + err = platform_driver_register(&dw_driver); + if (err) { + ERROR("Driver Register Failed"); + goto error; + } + + return 0; + +error: + return err; +} + + +/*********************************************************************** + * @Function: dw_cleanup_module + * @Return: nothing + * @Descr: deinitializes module. Actually will never be called due to + * MOD_INC_USE_COUNT > 1. Will be fixed if hardware watchdog can be disabled. + ***********************************************************************/ +static void __exit dw_cleanup_module(void) +{ + DBG_FN(DBG_INIT); + + platform_driver_unregister(&dw_driver); + platform_device_unregister(&dw_device); +} + +#ifndef MODULE +/** + * dw_sw_cmdline - parses kernel commandline + * + * @return always 1 + */ +static int __init dw_sw_cmdline(char* arg) +{ + if ((NULL != arg) && *arg) + dw_sw_aes = simple_strtoul(arg, NULL, 10); + + return 1; +} +#endif /* MODULE */ + +module_init(dw_init_module); +module_exit(dw_cleanup_module); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bernd Westermann"); +MODULE_AUTHOR("Markus Pietrek"); +MODULE_AUTHOR("Miriam Ruiz"); +MODULE_DESCRIPTION("WiFi driver for ConnectCore Wi-9C"); + +module_param(dw_sw_aes, int, 0x00); +MODULE_PARM_DESC(dw_sw_aes, "If set, AES encoding/decoding is done in software."); + +module_param(dw_cfg_vco, int, 0x00); +MODULE_PARM_DESC(dw_cfg_vco, "Config Value of VCO (0 use built-in)"); + +#ifdef CONFIG_DIGI_WI_G_DEBUG +module_param(dw_dbg_level, int, 0x00); +MODULE_PARM_DESC(dw_dbg_level, "debug output level"); +#endif /* CONFIG_DIGI_WI_G_DEBUG */ + +#ifndef MODULE +__setup("dw_sw_aes=", dw_sw_cmdline); +#endif /* MODULE */ diff --git a/drivers/net/wireless/digi_wi_g.h b/drivers/net/wireless/digi_wi_g.h new file mode 100644 index 000000000000..95bc6f06b1a5 --- /dev/null +++ b/drivers/net/wireless/digi_wi_g.h @@ -0,0 +1,660 @@ +/******************************************************************************* + * digi_wi_g.h + * + * Support for DIGI Wireless Module. + * + * $Id: digi_wi_g.h,v 1.64 2008-02-13 11:02:34 mruiz Exp $ + * @Author: Bernd Westermann + * @References: [1] NET+OS code mac_hw_wi9c.c + * + * Copyright (C) 2007 by FS Forth-Systeme GmbH + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ******************************************************************************* + * History: + * 14/03/06 Initial Version + * + *******************************************************************************/ + +#ifndef FS_DIGI_WI_G_DRIVER_H +#define FS_DIGI_WI_G_DRIVER_H + +#include <net/ieee80211.h> +#include <net/ieee80211softmac.h> +#include <net/ieee80211softmac_wx.h> + +#include <net/iw_handler.h> /* New driver API */ +#include <mach/regs-bbu.h> +#include <mach/hardware.h> /* BBUS_CLK_FREQ */ +#include <linux/spinlock.h> +#include <linux/workqueue.h> + +#ifdef dprintkl +#undef dprintkl +#endif +#ifdef dprintk +#undef dprintk +#endif + + +#define DRIVER_MAJOR 0 +#define DRIVER_MINOR 60 +#define DRIVER_NAME "digi_wi_g" + +/* for LEDs, update rate etc. */ +/* LED time is 50ms */ +#define MANAGEMENT_TIME_MS 50 +#define MANAGEMENT_JIFFIES ( ( MANAGEMENT_TIME_MS * HZ ) / 1000 ) +/* update rate only every 500ms */ +#define MANAGEMENT_TICKS_FOR_UPDATE ( 500 / MANAGEMENT_TIME_MS ) +#define TX_TIMEOUT (4*HZ/2) +#define ACK_TIMEOUT (HZ/2) +#define BEACON_TIMEOUT (HZ) +#define RESCAN_TIMEOUT (15*HZ) + +#define MAX_RECONNECTION_ATTEMPTS 5 + +/* Min/max successful intervals to increase rate */ +#define THRESH_MIN 1 +#define THRESH_MAX 10 + +#define TX_QUEUE_SIZE 8 /* not tuned yet */ +#define TX_MAX_FRAGS 16 +#define RX_QUEUE_SIZE 44 /* one ICMP frame */ + +#define RX_DUP_SENDER_SIZE 16 /* somewhat lower than MAX_NETWORK_COUNT + * because in worst case we need to scan + * RX_DUP_SENDER_SIZE until we found the + * active one */ + +#define DW_PIO_MAXTXPACKETS 4 + +#define DW_DEFAULT_SHORT_RETRY_LIMIT 7 +#define DW_DEFAULT_LONG_RETRY_LIMIT 4 +#define AES_BUSY_TIMEOUT 1000000 /* in ns */ + +#define printkl(f, x...) do { if (printk_ratelimit()) printk(f ,##x); } while (0) + +#ifdef CONFIG_DIGI_WI_G_DEBUG +# define assert(expr) \ + do { \ + if (unlikely(!(expr))) { \ + printk(KERN_ERR PFX "ASSERTION FAILED (%s) \nat: %s:%d:%s()\n", \ + #expr, __FILE__, __LINE__, __FUNCTION__); \ + } \ + } while (0) +# define dprintkl printkl +# define dprintk(f, x...) do { printk(f ,##x); } while (0) +# define COMPILE_TIME " from ("__TIME__ ") " +#else +# define assert(expr) do { /* nothing */ } while (0) +# define dprintkl(f, x...) do { /* nothing */ } while (0) +# define dprintk(f, x...) do { /* nothing */ } while (0) +# define COMPILE_TIME "" +#endif /* CONFIG_DIGI_WI_G_DEBUG */ + +#define DIGI_WIRELESS_G_REG_PHYS NS9XXX_CSxSTAT_PHYS(2) + +/* Hardware register defines */ +#define MAC_BASE_PHYS DIGI_WIRELESS_G_REG_PHYS +#define MAC_BASE_SIZE 0xe0000/*100U*/ /* Register segment size */ +#define MAC_MASK 0xffffc001 /* Size mask and enable bit */ + +/* Baseband control registers */ +#define HW_VERSION (0x00) /* Version */ +#define HW_GEN_CONTROL (0x04) /* General control */ +#define HW_GEN_STATUS (0x08) /* General status */ +#define HW_RSSI_AES (0x0c) /* RSSI and AES status */ +#define HW_INTR_MASK (0x10) /* Interrupt mask */ +#define HW_INTR_STATUS (0x14) /* Interrupt status */ +#define HW_SPI_DATA (0x18) /* RF SPI data register */ +#define HW_SPI_CONTROL (0x1c) /* RF SPI control register */ +#define HW_DATA_FIFO (0x20) /* Data FIFO */ +#define HW_AES_FIFO (0x30) /* AES FIFO */ +#define HW_AES_MODE (0x38) /* AES mode */ + +/* MAC control registers */ +#define HW_STAID0 (0x40) /* Station ID (6 bytes) */ +#define HW_STAID1 (0x44) +#define HW_BSSID0 (0x48) /* BSS ID (6 bytes) */ +#define HW_BSSID1 (0x4c) +#define HW_SSID_LEN (0x50) /* SSID length (8 bits) */ +#define HW_BACKOFF (0x54) /* Backoff period (16 bits) */ +#define HW_LISTEN (0x58) /* Listen interval (16 bits), CFP (8 bits), DTIM (8 bits) */ +#define HW_CFP_ATIM (0x5c) /* CFP max duration/ATIM period (16 bits), beacon interval (16 bits) */ +#define HW_MAC_STATUS (0x60) /* MAC status (8 bits) */ +#define HW_MAC_CONTROL (0x64) /* MAC control (8 bits) */ +#define HW_REMAIN_BO (0x68) /* Remaining backoff (16 bits) */ +#define HW_BEACON_BO (0x6c) /* Beacon backoff (16 bits), beacon mask (8 bits) */ +#define HW_SSID (0x80) /* Service set ID (32 bytes) */ + +/* FIFO sizes in bytes */ +#define HW_TX_FIFO_SIZE 1792 +#define HW_RX_FIFO_SIZE 2048 + + +/* General control register bits */ +#define GEN_RXEN 0x00000001 /* Receive enable */ +#define GEN_ANTDIV 0x00000002 /* Antenna diversity */ +#define GEN_ANTSEL 0x00000004 /* Antenna select */ +#define GEN_5GEN 0x00000008 /* 5 GHz band enable */ +#define GEN_SHPRE 0x00000010 /* Transmit short preamble */ +#define GEN_RXFIFORST 0x00000020 /* Receive FIFO reset */ +#define GEN_TXFIFORST 0x00000040 /* Transmit FIFO reset */ +#define GEN_TXHOLD 0x00000080 /* Transmit FIFO hold */ +#define GEN_BEACEN 0x00000100 /* Beacon enable */ +#define GEN_BST 0x00001000 /* Boot status */ +#define GEN_CLKEN 0x00002000 /* Clock enable */ +#define GEN_TXFIFOEMPTY 0x00004000 /* Transmit FIFO empty */ +#define GEN_TXFIFOFULL 0x00008000 /* Transmit FIFO full */ + +#if defined(CONFIG_DIGI_WI_G_UBEC_JD) +# define GEN_INIT 0x377a0000 /* Initial state */ +#elif defined(CONFIG_DIGI_WI_G_UBEC_HC) +# define GEN_INIT 0x37700000 /* Initial state */ +#else +# error "You need to choose an UBEC Transceiver Revision" +#endif + +/* General status register bits */ +#define STAT_RXFE 0x00000010 /* Receive FIFO empty */ +#define SFT_RESET 0x04000000 /* WiFi Baseband soft reset */ +/* AES status register bits */ +#define AES_EMPTY 0x00010000 /* AES receive FIFO empty */ +#define AES_FULL 0x00020000 /* AES transmit FIFO full */ +#define AES_BUSY 0x00040000 /* AES engine busy */ +#define AES_MIC 0x00080000 /* AES MIC correct */ + +/* Interrupt mask and status register bits */ +#define INTR_RXFIFO 0x00000001 /* Receive FIFO not empty */ +#define INTR_TXEND 0x00000002 /* Transmit complete */ +#define INTR_TIMEOUT 0x00000004 /* CTS/ACK receive timeout */ +#define INTR_ABORT 0x00000008 /* CTS transmit abort */ +#define INTR_TBTT 0x00000010 /* Beacon transmission time */ +#define INTR_ATIM 0x00000020 /* ATIM interval end */ +#define INTR_RXOVERRUN 0x00000040 /* Receive FIFO overrun */ +#define INTR_ALL ( INTR_RXFIFO | \ + INTR_TIMEOUT | \ + INTR_RXOVERRUN | \ + INTR_TXEND ) + +/* MAC control register bits */ +#define CTRL_TXREQ 0x00000001 /* Transmit request */ +#define CTRL_AUTOTXDIS 0x00000002 /* Auto-transmit disable */ +#define CTRL_BEACONTX 0x00000004 /* Beacon transmit enable */ +#define CTRL_PROMISC 0x00000008 /* Promiscuous mode */ +#define CTRL_IBSS 0x00000010 /* IBBS mode */ + +#define HW_AES_MODE_0 0x00000000 +#define HW_AES_MODE_1 0x00000010 + +/* BOOTMUX bit */ +#define BOOTMUX_LOW 0x00000001 /* Bootmux,bit 0, must go low after load */ + +/* PIO pins */ +#define PIN_INIT 58 /* FPGA program init */ +#define PIN_INTR 65 /* Interrupt (same as done) */ +#define PIN_LED 67 /* Link status LED */ +#define PIN_ACTIVITY_LED 66 /* Link activity LED */ +#define PIN_BOOTMUX 66 /* Enables serial ports */ + +#define INTR_ID IRQ_NS9XXX_ETHPHY /* Interrupt ID for PIN_INTR */ + +/* Get/set/clear a PIO pin */ +#define PIO_SET(pin) ns9xxx_gpio_setpin(pin, 1) +#define PIO_CLR(pin) ns9xxx_gpio_setpin(pin, 0) + +#define CW_MIN 31 /* Min contention window size */ +#define CW_MAX 1023 /* Max contention window size */ + +/* Maximum rxsignal strength we expect to see (this is in dB) */ +#define MAC_RSSI_MAX 0x4f + +/* the CCK/OFDM base is 2 * Mb/s */ +#define RATE_IN_BS(x) ((x) * 500000) + +/* see email from Mike Schaffner from 16.03.07 (RE: Verification of + * initialization to mpietrek and hbujanda */ +#define VCO_DEFAULT_CHANNEL_12 0x7160 +#define VCO_DEFAULT 0x7020 + +#define RATES_SUPPORTED 12 + +#define AES_MAXNR 14 + +#define CCMP_KEY_SIZE 16 /* CCMP key size */ +#define AES_BLOCK_SIZE 16 +#define EXTIV_SIZE 8 /* IV and extended IV size */ +#define MIC_SIZE 8 /* Message integrity check size */ +#define CCMP_SIZE (EXTIV_SIZE+MIC_SIZE) /* Total CCMP size */ +#define FCS_SIZE 4 // FCS (CRC-32) size +#define DATA_SIZE 28 /* Data frame header+FCS size */ +/* Key ID byte in data frame body */ +#define EXT_IV 0x20 /* Extended IV is present */ + +#define DW_TX_POWER_MIN_DBM 0 +#define DW_TX_POWER_MAX_DBM 15 +#define DW_TX_POWER_DEFAULT 10 + +typedef u64 u48; + +struct aes_key_st { + unsigned long rd_key[4 *(AES_MAXNR + 1)]; +}; + +typedef union { + struct { + u8 signal; /* (rate in 100 kbps) */ + u8 service; /* Service: OR of SERVICE_xxx */ + u16 length; /* Length in usecs (needs byte swap) */ + }; + u8 raw[4]; + u32 raw32; +} __attribute__((__packed__)) dw_hw_pskcck_t; + +typedef union { + struct { + unsigned rate :4; /* Data rate */ + unsigned reserved :1; + unsigned length :12; /* Length in bytes */ + unsigned parity :1; /* Even parity bit */ + unsigned _pad :14; /* Service field */ + }; + u8 raw[4]; + u32 raw32; +} __attribute__((__packed__)) dw_hw_ofdm_t; + +/* modulation header (rx ) */ +typedef union { + struct { + unsigned mod_type : 8; + unsigned rssi_vga : 5; + unsigned rssi_lna : 2; + unsigned ant : 1; + unsigned freq_off : 16; /* Frequency offset (needs byte swap) */ + }; + u32 raw32; +} __attribute__((__packed__)) dw_hw_mod_rx_t; + +/* modulation header ( tx ) */ +typedef union { + struct { + unsigned int mod_type :8; + unsigned int length :9; + unsigned int pad :15; + }; + u32 raw32; +} __attribute__((__packed__)) dw_hw_mod_tx_t; + +typedef union { + dw_hw_pskcck_t pskcck; + dw_hw_ofdm_t ofdm; +} __attribute__((__packed__)) dw_hw_plcp_t; + +typedef struct { + dw_hw_mod_tx_t mod; + dw_hw_plcp_t plcp; +} __attribute__((__packed__)) dw_hw_hdr_tx_t; + +typedef struct { + dw_hw_mod_rx_t mod; + dw_hw_plcp_t plcp; +} __attribute__((__packed__)) dw_hw_hdr_rx_t; + +/* CCMP key data */ + +typedef struct { + u8 init[AES_BLOCK_SIZE]; + u8 header[2*AES_BLOCK_SIZE]; +} __attribute__ ((__packed__)) ccmp_data_t; + +typedef struct { + u8 valid; /* TRUE if key is valid */ + u48 tx_pn; /* transmit packet number */ + u48 rx_pn; /* next receive packet number */ + struct aes_key_st rk; /* AES key schedule */ +} ccmp_key_t; + +/* stores the necessary data to detect duplicate frames */ +typedef struct { + struct list_head list; + u8 src[ ETH_ALEN ]; /* addr2/sender */ + u16 seq_ctl; /* seq_ctl of last received frame */ +} dw_duplicate_t; + +typedef struct { + struct list_head list; + struct sk_buff* skb; /* message data */ + dw_hw_hdr_rx_t hdr; +} dw_frame_rx_t; + +typedef struct { + dw_frame_rx_t free; + dw_frame_rx_t filled; + dw_frame_rx_t* buffer; +} dw_frame_rx_queue_t; + +typedef struct { + dw_hw_hdr_tx_t hdr; + + /* physical length of data, including CCMP. Is not included skb->len if + * hardware encryption is performed. */ + size_t phys_len; + + struct { + ccmp_key_t* key; + int key_index; + } crypt; +} dw_fragment_tx_t; + +typedef struct { + struct ieee80211_txb* txb; + dw_fragment_tx_t frags[ TX_MAX_FRAGS ]; + + u8 is_data : 1; + u8 use_hw_encryption : 1; + u8 use_short_preamble : 1; +} dw_frame_tx_info_t; + +typedef struct { + struct list_head list; + dw_frame_tx_info_t s; +} dw_frame_tx_t; + +// IBSS + +#define DW_BEACON_INT 100 // IBSS Beacon interval (in TU) + +// +// 802.11 MIB constants +// +#define DW_SHORT_RETRY_LIMIT 7 // Small frame transmit retry limit +#define DW_LONG_RETRY_LIMIT 4 // Large frame transmit retry limit + +#define DW_TU 1024L/1000 // Time unit (in msecs) +#define DW_MAX_TX_LIFETIME (512*TU) // Transmit lifetime limit (in msecs) +#define DW_MAX_RX_LIFETIME (512*TU) // Receive lifetime limit (in msecs) + +// Max number of fragments +#define DW_MAX_FRAGS 16 + +// Frame header modulation type field +#define DW_MOD_PSKCCK 0x00 // PSK/CCK modulation +#define DW_MOD_OFDM 0xee // OFDM modulation + +// PSK/CCK PLCP service field bits +#define DW_SERVICE_LOCKED 0x04 // Locked clocks +#define DW_SERVICE_MODSEL 0x08 // Modulation selection +#define DW_SERVICE_LENEXT 0x80 // Length extension + +// MAC type field values +#define DW_TYPE_ASSOC_REQ 0x00 // Association request +#define DW_TYPE_ASSOC_RESP 0x10 // Association response +#define DW_TYPE_REASSOC_REQ 0x20 // Reassociation request +#define DW_TYPE_REASSOC_RESP 0x30 // Reassociation response +#define DW_TYPE_PROBE_REQ 0x40 // Probe request +#define DW_TYPE_PROBE_RESP 0x50 // Probe response + +#define DW_TYPE_BEACON 0x80 // Beacon +#define DW_TYPE_ATIM 0x90 // Annoucement traffice indication +#define DW_TYPE_DISASSOC 0xa0 // Disassociation +#define DW_TYPE_AUTH 0xb0 // Authentication +#define DW_TYPE_DEAUTH 0xc0 // Deauthentication + +#define DW_TYPE_RTS 0xb4 // Request to send +#define DW_TYPE_CTS 0xc4 // Clear to send +#define DW_TYPE_ACK 0xd4 // Acknowledgement +#define DW_TYPE_PSPOLL 0xa4 // Power Save(PS)-Poll + +#define DW_TYPE_DATA 0x08 // Data + +// TRUE if buf is data or management frame +#define DW_IS_DATA(buf) (((buf)->macHdr.fc.type & 0xcf) == TYPE_DATA) +#define DW_IS_MGMT(buf) (((buf)->macHdr.fc.type & 0x0f) == 0) + +// MAC address macros +#define DW_MAC_GROUP 0x01 // Broadcast or multicast address +#define DW_MAC_LOCAL 0x02 // Locally administered address + +#define DW_EQUAL_ADDR(a1, a2) (memcmp (a1, a2, ETH_ALEN) == 0) +#define DW_SET_ADDR(a1, a2) (memcpy (a1, a2, ETH_ALEN)) + +// Authentication algorithm number field values +#define DW_AUTH_OPEN 0x00 // Open system +#define DW_AUTH_SHAREDKEY 0x01 // Shared key +#define DW_AUTH_LEAP 0x80 // LEAP + +// Capability information field bits +#define DW_CAP_ESS 0x0001 // Extended service set (infrastructure) +#define DW_CAP_IBSS 0x0002 // Independent BSS (ad hoc) +#define DW_CAP_POLLABLE 0x0004 // Contention free pollable +#define DW_CAP_POLLREQ 0x0008 // Contention free poll request +#define DW_CAP_PRIVACY 0x0010 // Privacy (WEP) required +#define DW_CAP_SHORTPRE 0x0020 // Short preambles allowed +#define DW_CAP_PBCC 0x0040 // PBCC modulation allowed +#define DW_CAP_AGILITY 0x0080 // Channel agility in use +#define DW_CAP_SHORTSLOT 0x0400 // Short slot time in use +#define DW_CAP_DSSSOFDM 0x2000 // DSSS-OFDM in use + +// Status code field values +#define DW_STAT_SUCCESS 0 + +// Reason code field values +#define DW_REAS_NOLONGERVALID 2 +#define DW_REAS_DEAUTH_LEAVING 3 +#define DW_REAS_INACTIVITY 4 +#define DW_REAS_INCORRECT_FRAME_UNAUTH 6 +#define DW_REAS_INCORRECT_FRAME_UNASSO 7 + +// Information element IDs +#define DW_ELEM_SSID 0 // Service set ID +#define DW_ELEM_SUPRATES 1 // Supported rates +#define DW_ELEM_DSPARAM 3 // DS parameter set +#define DW_ELEM_IBSSPARAM 6 // IBSS parameter set +#define DW_ELEM_COUNTRY 7 // Country information +#define DW_ELEM_CHALLENGE 16 // Challenge text +#define DW_ELEM_ERPINFO 42 // Extended rate PHY info +#define DW_ELEM_RSN 48 // Robust security network (WPA2) +#define DW_ELEM_EXTSUPRATES 50 // Extended supported rates +#define DW_ELEM_VENDOR 221 // Vendor extension (WPA) + +// 802.11d related defines +// minimum length field value in country information elelment +#define DW_COUNTRY_INFO_MIN_LEN 6 + +// Supported rates bits +#define DW_RATE_BASIC 0x80 // Bit set if basic rate + +// TRUE if channel number in 5 GHz band +#define DW_CHAN_5G(chan) ((chan) > 14) + +// ERP info bits +#define DW_ERP_NONERP 0x01 // Non-ERP present +#define DW_ERP_USEPROTECT 0x02 // Use protection +#define DW_ERP_BARKER 0x04 // Barker (long) preamble mode + +// Key ID byte in data frame body +#define DW_EXT_IV 0x20 // Extended IV is present + +// Correct CRC-32 check value +#define DW_GOOD_CRC32 0x2144df1c + +#pragma pack() + +#define BEACON_BODY_SIZE 64 + +// MAC buffer, including complete MAC frame +typedef struct { + dw_hw_hdr_tx_t hwHdr; // Frame and PLCP headers + u16 fc; // Frame control + u16 duration; // Duration/ID (needs byte swap) + u8 addr1[ ETH_ALEN ]; // Address 1 + u8 addr2[ ETH_ALEN ]; // Address 2 + u8 addr3[ ETH_ALEN ]; // Address 3 + u16 seq_ctl; // Sequence control fields + u8 body[BEACON_BODY_SIZE]; +} __attribute__ ((packed)) dw_beacon_frame; + +// Length (in usecs) of a MAC frame of bytes at rate (in 500kbps units) +// not including SIFS and PLCP preamble/header +#define DW_LENGTH_uS(bytes, rate) ((16*(bytes)+(rate)-1)/(rate)) + +// Length (in usecs) of SIFS and PLCP preamble/header. +#define DW_PRE_LEN_uS(rate) (USE_SHORTPRE(rate) ? 106 : 202) + +// Duration (in usecs) of an OFDM frame at rate (in 500kbps units) +// including SIFS and PLCP preamble/header +#define DW_OFDM_DUR(bytes, rate) (36 + 4*((4*(bytes)+(rate)+10)/(rate))) + +// Information on each supported rate +typedef struct { + u8 bps; // Bit rate in 500kbps units + u8 ofdmCode; // OFDM rate code, 0 if not OFDM + u16 ackLen; // Duration of ACK or CTS in usecs +} RateInfo; + +#define DW_IBSS_DEFAULT_CHANNEL 6 + +// End of IBSS + +typedef struct dw_priv { + struct net_device* dev; + struct ieee80211_device* ieee; + struct ieee80211softmac_device* softmac; + + struct iw_statistics wstats; + + spinlock_t lock; + + struct timer_list management_timer; + + /* Additional information, specific to the 80211 cores. */ + /* Driver status flags. */ + u32 recovery:1; /* TRUE if interval follows rate increase */ + /* Interrupt Service Routine tasklet (bottom-half) */ + u8 channel; + /* Informational stuff. */ + char nick[IW_ESSID_MAX_SIZE + 1]; + int short_retry_limit; + int long_retry_limit; + ccmp_key_t aeskeys[WEP_KEYS]; + int cw; /* Contention window size */ + int success; /* Successful intervals */ + int success_threshold; /* Successful intervals needed to increase rate */ + atomic_t fix_rate; + + struct ieee80211softmac_ratesinfo ap_ri; + + struct { + int counter; + } activity; + + struct { + int index; + int counter; + int recovery; + int tx_data_any; + int tx_data_ack; + int have_activity; + int success; + int success_threshold; + } rate; + + struct { + struct tasklet_struct tasklet; + dw_frame_rx_queue_t queue; + char pause; + + /* maintains the list of received frames */ + struct { + dw_duplicate_t known; + dw_duplicate_t free; + dw_duplicate_t entries[ RX_DUP_SENDER_SIZE ]; + } dups; + } rx; + + struct { + struct tasklet_struct tasklet; + dw_frame_tx_t frames[ TX_QUEUE_SIZE ]; + struct list_head queued; + struct list_head free; + atomic_t seq_nr; + int timeout; + int times_sent; + int retries; + int fragment; + char pending; + char pause; + struct semaphore pause_sem; + char last_was_data; + int basics[ RATES_SUPPORTED ]; + void * data_pending_ack; + unsigned long jiffies_pending_ack; + } tx; + + unsigned char tx_power; + + int beacon_ready; + struct delayed_work beacon_work; + int beacon_body_length; + dw_beacon_frame beacon_frame; + struct ieee80211softmac_network adhoc; + + unsigned long jiffies_last_beacon; + int reconnection_attempts; +} dw_priv_t; + +/* dw_(un)lock() protect struct dw_private. + */ +#define dw_lock(priv, flags) spin_lock_irqsave(&(priv)->lock, flags) +#define dw_unlock(priv, flags) spin_unlock_irqrestore(&(priv)->lock, flags) + +/* Frame header modulation type field */ +#define MOD_PSKCCK 0x00 /* PSK/CCK modulation */ +#define MOD_OFDM 0xee /* OFDM modulation */ + +/* PLCP service field bits */ +#define SERVICE_LOCKED 0x04 /* Locked clocks */ +#define SERVICE_MODSEL 0x08 /* Modulation selection */ +#define SERVICE_LENEXT 0x80 /* Length extension */ + +/* Threshold values. */ +#define DW_MIN_RTS_THRESHOLD 1U +#define DW_MAX_RTS_THRESHOLD 2304U +/* 1536 is default for most routers, but our FIFO is larger, so we could accept + * more data, e.g. for improvements when doing AES etc. This has been reported + * being done with Apple iTunes */ +#define DW_MTU 2048 + +#define AES_BITS 128 /* 128 bit keys, 10 rounds */ + +# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +/* Get 16 bits at byte pointer */ +#define GET16(bp) ((bp)[0] | ((bp)[1] << 8)) +/* Get 32 bits at byte pointer */ +#define GET32(bp) ((bp)[0] | ((bp)[1] << 8) | ((bp)[2] << 16) | ((bp)[3] << 24)) +/* Store 16 bits at byte pointer */ +#define SET16(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; } +/* Store 32 bits at byte pointer */ +#define SET32(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; \ + (bp)[2] = (data) >> 16; \ + (bp)[3] = (data) >> 24; } + +#endif /* FS_DIGI_WI_G_DRIVER_H */ diff --git a/drivers/net/wireless/digi_wi_g_priv_handler.c b/drivers/net/wireless/digi_wi_g_priv_handler.c new file mode 100644 index 000000000000..c7ab82a2e335 --- /dev/null +++ b/drivers/net/wireless/digi_wi_g_priv_handler.c @@ -0,0 +1,345 @@ +/* + * net/wireless/digi_wi_g_priv_handler.c + * + * Copyright (C) 2007 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version2 as published by + * the Free Software Foundation. +*/ +/* + * !Revision: $Revision: 1.1 $ + * !Author: Markus Pietrek + * !Descr: Contains private user handler stuff. +*/ + +#warning fix the getter functions, they compare 0 instead of '0' + +/*********************************************************************** + * @Function: digi_wi_g_wx_set_swencryption + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_set_swencryption(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + char on; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + on = *extra; + + digi_wi_g_lock(priv, flags); + if (on) + priv->hw_aes = 0; + else + priv->hw_aes = 1; + digi_wi_g_unlock(priv, flags); + + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_get_swencryption + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_get_swencryption(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + int on; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + digi_wi_g_lock(priv, flags); + if (priv->hw_aes) + on = 0; + else + on = 1; + digi_wi_g_unlock(priv, flags); + + if (on) + strncpy(extra, "1 (SW encryption enabled) ", MAX_WX_STRING); + else + strncpy(extra, "0 (SW encryption disabled) ", MAX_WX_STRING); + data->data.length = strlen(extra + 1); + + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_set_ant_div + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_set_ant_div(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + char on; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + on = *extra; + + digi_wi_g_lock(priv, flags); + if (on) { + priv->ant_div = 1; + HW_GEN_CONTROL |= GEN_ANTDIV; + } else { + priv->ant_div = 0; + HW_GEN_CONTROL &= ~GEN_ANTDIV; + } + digi_wi_g_unlock(priv, flags); + + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_get_ant_div + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_get_ant_div(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + int on; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + digi_wi_g_lock(priv, flags); + if (priv->ant_div) + on = 1; + else + on = 0; + digi_wi_g_unlock(priv, flags); + + if (on) + strncpy(extra, "1 (Antenna Diversity is enabled) ", MAX_WX_STRING); + else + strncpy(extra, "0 (Antenna Diversity is disabled) ", MAX_WX_STRING); + data->data.length = strlen(extra + 1); + + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_set_antenna + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_set_antenna(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + char ant; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + ant = *extra; + + if ((ant == 0) || (ant == 1)) { + digi_wi_g_lock(priv, flags); + if (ant == 0) { + priv->antenna = 0; + HW_GEN_CONTROL &= ~GEN_ANTSEL; + } else { + priv->antenna = 1; + HW_GEN_CONTROL |= GEN_ANTSEL; + } + digi_wi_g_unlock(priv, flags); + } else { + printk(PFX KERN_ERR "Value should be 0 or 1.\n"); + return -EOPNOTSUPP; + } + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_get_antenna + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_get_antenna(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + int ant; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + digi_wi_g_lock(priv, flags); + ant = priv->antenna; + digi_wi_g_unlock(priv, flags); + + if (ant == 0) + strncpy(extra, "0 (Antenna 1 selected) ", MAX_WX_STRING); + else + strncpy(extra, "1 (Antenna 2 selected) ", MAX_WX_STRING); + data->data.length = strlen(extra + 1); + + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_set_rx_off + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_set_rx_off(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + char rx_off; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + rx_off = *extra; + + if ((rx_off == 0) || (rx_off == 1)) { + digi_wi_g_lock(priv, flags); + if (rx_off == 0) { + priv->rx_off = 0; + } else { + priv->rx_off = 1; + } + digi_wi_g_unlock(priv, flags); + } else { + printk(PFX KERN_ERR "Value should be 0 or 1.\n"); + return -EOPNOTSUPP; + } + return 0; +} + + +/*********************************************************************** + * @Function: digi_wi_g_wx_get_rx_off + * @Return: + * @Descr: + ***********************************************************************/ +static int digi_wi_g_wx_get_rx_off(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *data, + char *extra) +{ + struct digi_wi_g_private *priv = ieee80211softmac_priv(dev); + unsigned long flags; + int rx_off; + + DBG_FN( DBG_INTERFACE | DBG_MINOR ); + + digi_wi_g_lock(priv, flags); + rx_off = priv->rx_off; + digi_wi_g_unlock(priv, flags); + + if (rx_off == 0) + strncpy(extra, "0 (rx is on) ", MAX_WX_STRING); + else + strncpy(extra, "1 (rx is off) ", MAX_WX_STRING); + data->data.length = strlen(extra + 1); + + return 0; +} + +static const iw_handler digi_wi_g_priv_wx_handlers[] = { + /* Enable/Disable Software Encryption mode */ + digi_wi_g_wx_set_swencryption, + /* Get Software Encryption mode */ + digi_wi_g_wx_get_swencryption, + /* Enable/Disable Antenna Diversity mode */ + digi_wi_g_wx_set_ant_div, + /* Get Antenna Diversity mode */ + digi_wi_g_wx_get_ant_div, + /* Select Antenna */ + digi_wi_g_wx_set_antenna, + /* Get Antenna */ + digi_wi_g_wx_get_antenna, + /* Set rx off */ + digi_wi_g_wx_set_rx_off, + /* Get rx off */ + digi_wi_g_wx_get_rx_off, +}; + +#define PRIV_WX_SET_SWENCRYPTION (SIOCIWFIRSTPRIV + 0) +#define PRIV_WX_GET_SWENCRYPTION (SIOCIWFIRSTPRIV + 1) +#define PRIV_WX_SET_ANTDIV (SIOCIWFIRSTPRIV + 2) +#define PRIV_WX_GET_ANTDIV (SIOCIWFIRSTPRIV + 3) +#define PRIV_WX_SET_ANTSEL (SIOCIWFIRSTPRIV + 4) +#define PRIV_WX_GET_ANTSEL (SIOCIWFIRSTPRIV + 5) +#define PRIV_WX_SET_RX_OFF (SIOCIWFIRSTPRIV + 6) +#define PRIV_WX_GET_RX_OFF (SIOCIWFIRSTPRIV + 7) + +static const struct iw_priv_args digi_wi_g_priv_wx_args[] = { + { + .cmd = PRIV_WX_SET_SWENCRYPTION, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_swencrypt", + }, + { + .cmd = PRIV_WX_GET_SWENCRYPTION, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + .name = "get_swencrypt", + }, + { + .cmd = PRIV_WX_SET_ANTDIV, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_ant_div", + }, + { + .cmd = PRIV_WX_GET_ANTDIV, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + .name = "get_ant_div", + }, + { + .cmd = PRIV_WX_SET_ANTSEL, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_ant_sel", + }, + { + .cmd = PRIV_WX_GET_ANTSEL, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + .name = "get_ant_sel", + }, + { + .cmd = PRIV_WX_SET_RX_OFF, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_rx_off", + }, + { + .cmd = PRIV_WX_GET_RX_OFF, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + .name = "get_rx_off", + }, +}; + |