diff options
author | Lennert Buytenhek <buytenh@wantstofly.org> | 2005-11-11 18:23:13 +0100 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2005-11-18 13:32:22 -0500 |
commit | 15d014d13149aedd76cbff1b5c3bbfe839391457 (patch) | |
tree | 748af38a44021cf286016b179e58190b685c1630 | |
parent | 7f7f53168dbee6d6a462acea666fddd18aad4f08 (diff) |
[PATCH] intel ixp2000 network driver
The way the hardware and firmware work is that there is one shared RX
queue and IRQ for a number of different network interfaces. Due to this,
we would like to process received packets for every interface in the same
NAPI poll handler, so we need a pseudo-device to schedule polling on.
What the driver currently does is that it always schedules polling for
the first network interface in the list, and processes packets for every
interface in the poll handler for that first interface -- however, this
scheme breaks down if the first network interface happens to not be up,
since netif_rx_schedule_prep() checks netif_running().
sky2 apparently has the same issue, and Stephen Hemminger suggested a
way to work around this: create a variant of netif_rx_schedule_prep()
that does not check netif_running(). I implemented this locally and
called it netif_rx_schedule_prep_notup(), and it seems to work well,
but it's something that probably not everyone would be happy with.
The ixp2000 is an ARM CPU with a high-speed network interface in the
CPU itself (full duplex 4Gb/s or 10Gb/s depending on the IXP model.)
The CPU package also contains 8 or 16 (again depending on the IXP
model) 'microengines', which are somewhat primitive but very fast
and efficient processor cores which can be used to offload various
things from the main CPU.
This driver makes the high-speed network interface in the CPU visible
and usable as a regular linux network device. Currently, it only
supports the Radisys ENP2611 IXP board, but adding support for other
board types should be fairly easy.
Signed-off-by: Lennert Buytenhek <buytenh@wantstofly.org>
Signed-off-by: Jeff Garzik <jgarzik@pobox.com>
-rw-r--r-- | drivers/net/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/ixp2000/Kconfig | 6 | ||||
-rw-r--r-- | drivers/net/ixp2000/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/ixp2000/caleb.c | 136 | ||||
-rw-r--r-- | drivers/net/ixp2000/caleb.h | 22 | ||||
-rw-r--r-- | drivers/net/ixp2000/enp2611.c | 238 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixp2400-msf.c | 213 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixp2400-msf.h | 115 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixp2400_rx.uc | 408 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixp2400_rx.ucode | 130 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixp2400_tx.uc | 272 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixp2400_tx.ucode | 98 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixpdev.c | 404 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixpdev.h | 27 | ||||
-rw-r--r-- | drivers/net/ixp2000/ixpdev_priv.h | 57 | ||||
-rw-r--r-- | drivers/net/ixp2000/pm3386.c | 304 | ||||
-rw-r--r-- | drivers/net/ixp2000/pm3386.h | 26 |
18 files changed, 2462 insertions, 0 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 39415b5c456..fca6000d6e0 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1901,6 +1901,8 @@ config E1000_NAPI If in doubt, say N. +source "drivers/net/ixp2000/Kconfig" + config MYRI_SBUS tristate "MyriCOM Gigabit Ethernet support" depends on SBUS diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 50568140631..b74a7cb5bae 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -206,6 +206,7 @@ obj-$(CONFIG_NET_TULIP) += tulip/ obj-$(CONFIG_HAMRADIO) += hamradio/ obj-$(CONFIG_IRDA) += irda/ obj-$(CONFIG_ETRAX_ETHERNET) += cris/ +obj-$(CONFIG_ENP2611_MSF_NET) += ixp2000/ obj-$(CONFIG_NETCONSOLE) += netconsole.o diff --git a/drivers/net/ixp2000/Kconfig b/drivers/net/ixp2000/Kconfig new file mode 100644 index 00000000000..2fec2415651 --- /dev/null +++ b/drivers/net/ixp2000/Kconfig @@ -0,0 +1,6 @@ +config ENP2611_MSF_NET + tristate "Radisys ENP2611 MSF network interface support" + depends on ARCH_ENP2611 + help + This is a driver for the MSF network interface unit in + the IXP2400 on the Radisys ENP2611 platform. diff --git a/drivers/net/ixp2000/Makefile b/drivers/net/ixp2000/Makefile new file mode 100644 index 00000000000..fd38351ceaa --- /dev/null +++ b/drivers/net/ixp2000/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_ENP2611_MSF_NET) += enp2611_mod.o + +enp2611_mod-objs := caleb.o enp2611.o ixp2400-msf.o ixpdev.o pm3386.o diff --git a/drivers/net/ixp2000/caleb.c b/drivers/net/ixp2000/caleb.c new file mode 100644 index 00000000000..d70530adb1e --- /dev/null +++ b/drivers/net/ixp2000/caleb.c @@ -0,0 +1,136 @@ +/* + * Helper functions for the SPI-3 bridge FPGA on the Radisys ENP2611 + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <asm/io.h> + +#define CALEB_IDLO 0x00 +#define CALEB_IDHI 0x01 +#define CALEB_RID 0x02 +#define CALEB_RESET 0x03 +#define CALEB_INTREN0 0x04 +#define CALEB_INTREN1 0x05 +#define CALEB_INTRSTAT0 0x06 +#define CALEB_INTRSTAT1 0x07 +#define CALEB_PORTEN 0x08 +#define CALEB_BURST 0x09 +#define CALEB_PORTPAUS 0x0A +#define CALEB_PORTPAUSD 0x0B +#define CALEB_PHY0RX 0x10 +#define CALEB_PHY1RX 0x11 +#define CALEB_PHY0TX 0x12 +#define CALEB_PHY1TX 0x13 +#define CALEB_IXPRX_HI_CNTR 0x15 +#define CALEB_PHY0RX_HI_CNTR 0x16 +#define CALEB_PHY1RX_HI_CNTR 0x17 +#define CALEB_IXPRX_CNTR 0x18 +#define CALEB_PHY0RX_CNTR 0x19 +#define CALEB_PHY1RX_CNTR 0x1A +#define CALEB_IXPTX_CNTR 0x1B +#define CALEB_PHY0TX_CNTR 0x1C +#define CALEB_PHY1TX_CNTR 0x1D +#define CALEB_DEBUG0 0x1E +#define CALEB_DEBUG1 0x1F + + +static u8 caleb_reg_read(int reg) +{ + u8 value; + + value = *((volatile u8 *)(ENP2611_CALEB_VIRT_BASE + reg)); + +// printk(KERN_INFO "caleb_reg_read(%d) = %.2x\n", reg, value); + + return value; +} + +static void caleb_reg_write(int reg, u8 value) +{ + u8 dummy; + +// printk(KERN_INFO "caleb_reg_write(%d, %.2x)\n", reg, value); + + *((volatile u8 *)(ENP2611_CALEB_VIRT_BASE + reg)) = value; + + dummy = *((volatile u8 *)ENP2611_CALEB_VIRT_BASE); + __asm__ __volatile__("mov %0, %0" : "+r" (dummy)); +} + + +void caleb_reset(void) +{ + /* + * Perform a chip reset. + */ + caleb_reg_write(CALEB_RESET, 0x02); + udelay(1); + + /* + * Enable all interrupt sources. This is needed to get + * meaningful results out of the status bits (register 6 + * and 7.) + */ + caleb_reg_write(CALEB_INTREN0, 0xff); + caleb_reg_write(CALEB_INTREN1, 0x07); + + /* + * Set RX and TX FIFO thresholds to 1.5kb. + */ + caleb_reg_write(CALEB_PHY0RX, 0x11); + caleb_reg_write(CALEB_PHY1RX, 0x11); + caleb_reg_write(CALEB_PHY0TX, 0x11); + caleb_reg_write(CALEB_PHY1TX, 0x11); + + /* + * Program SPI-3 burst size. + */ + caleb_reg_write(CALEB_BURST, 0); // 64-byte RBUF mpackets +// caleb_reg_write(CALEB_BURST, 1); // 128-byte RBUF mpackets +// caleb_reg_write(CALEB_BURST, 2); // 256-byte RBUF mpackets +} + +void caleb_enable_rx(int port) +{ + u8 temp; + + temp = caleb_reg_read(CALEB_PORTEN); + temp |= 1 << port; + caleb_reg_write(CALEB_PORTEN, temp); +} + +void caleb_disable_rx(int port) +{ + u8 temp; + + temp = caleb_reg_read(CALEB_PORTEN); + temp &= ~(1 << port); + caleb_reg_write(CALEB_PORTEN, temp); +} + +void caleb_enable_tx(int port) +{ + u8 temp; + + temp = caleb_reg_read(CALEB_PORTEN); + temp |= 1 << (port + 4); + caleb_reg_write(CALEB_PORTEN, temp); +} + +void caleb_disable_tx(int port) +{ + u8 temp; + + temp = caleb_reg_read(CALEB_PORTEN); + temp &= ~(1 << (port + 4)); + caleb_reg_write(CALEB_PORTEN, temp); +} diff --git a/drivers/net/ixp2000/caleb.h b/drivers/net/ixp2000/caleb.h new file mode 100644 index 00000000000..e93a1ef5b8a --- /dev/null +++ b/drivers/net/ixp2000/caleb.h @@ -0,0 +1,22 @@ +/* + * Helper functions for the SPI-3 bridge FPGA on the Radisys ENP2611 + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#ifndef __CALEB_H +#define __CALEB_H + +void caleb_reset(void); +void caleb_enable_rx(int port); +void caleb_disable_rx(int port); +void caleb_enable_tx(int port); +void caleb_disable_tx(int port); + + +#endif diff --git a/drivers/net/ixp2000/enp2611.c b/drivers/net/ixp2000/enp2611.c new file mode 100644 index 00000000000..3262e70ede6 --- /dev/null +++ b/drivers/net/ixp2000/enp2611.c @@ -0,0 +1,238 @@ +/* + * IXP2400 MSF network device driver for the Radisys ENP2611 + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <asm/arch/uengine.h> +#include <asm/mach-types.h> +#include <asm/io.h> +#include "ixpdev.h" +#include "caleb.h" +#include "ixp2400-msf.h" +#include "pm3386.h" + +/*********************************************************************** + * The Radisys ENP2611 is a PCI form factor board with three SFP GBIC + * slots, connected via two PMC/Sierra 3386s and an SPI-3 bridge FPGA + * to the IXP2400. + * + * +-------------+ + * SFP GBIC #0 ---+ | +---------+ + * | PM3386 #0 +-------+ | + * SFP GBIC #1 ---+ | | "Caleb" | +---------+ + * +-------------+ | | | | + * | SPI-3 +---------+ IXP2400 | + * +-------------+ | bridge | | | + * SFP GBIC #2 ---+ | | FPGA | +---------+ + * | PM3386 #1 +-------+ | + * | | +---------+ + * +-------------+ + * ^ ^ ^ + * | 1.25Gbaud | 104MHz | 104MHz + * | SERDES ea. | SPI-3 ea. | SPI-3 + * + ***********************************************************************/ +static struct ixp2400_msf_parameters enp2611_msf_parameters = +{ + .rx_mode = IXP2400_RX_MODE_UTOPIA_POS | + IXP2400_RX_MODE_1x32 | + IXP2400_RX_MODE_MPHY | + IXP2400_RX_MODE_MPHY_32 | + IXP2400_RX_MODE_MPHY_POLLED_STATUS | + IXP2400_RX_MODE_MPHY_LEVEL3 | + IXP2400_RX_MODE_RBUF_SIZE_64, + + .rxclk01_multiplier = IXP2400_PLL_MULTIPLIER_16, + + .rx_poll_ports = 3, + + .rx_channel_mode = { + IXP2400_PORT_RX_MODE_MASTER | + IXP2400_PORT_RX_MODE_POS_PHY | + IXP2400_PORT_RX_MODE_POS_PHY_L3 | + IXP2400_PORT_RX_MODE_ODD_PARITY | + IXP2400_PORT_RX_MODE_2_CYCLE_DECODE, + + IXP2400_PORT_RX_MODE_MASTER | + IXP2400_PORT_RX_MODE_POS_PHY | + IXP2400_PORT_RX_MODE_POS_PHY_L3 | + IXP2400_PORT_RX_MODE_ODD_PARITY | + IXP2400_PORT_RX_MODE_2_CYCLE_DECODE, + + IXP2400_PORT_RX_MODE_MASTER | + IXP2400_PORT_RX_MODE_POS_PHY | + IXP2400_PORT_RX_MODE_POS_PHY_L3 | + IXP2400_PORT_RX_MODE_ODD_PARITY | + IXP2400_PORT_RX_MODE_2_CYCLE_DECODE, + + IXP2400_PORT_RX_MODE_MASTER | + IXP2400_PORT_RX_MODE_POS_PHY | + IXP2400_PORT_RX_MODE_POS_PHY_L3 | + IXP2400_PORT_RX_MODE_ODD_PARITY | + IXP2400_PORT_RX_MODE_2_CYCLE_DECODE + }, + + .tx_mode = IXP2400_TX_MODE_UTOPIA_POS | + IXP2400_TX_MODE_1x32 | + IXP2400_TX_MODE_MPHY | + IXP2400_TX_MODE_MPHY_32 | + IXP2400_TX_MODE_MPHY_POLLED_STATUS | + IXP2400_TX_MODE_MPHY_LEVEL3 | + IXP2400_TX_MODE_TBUF_SIZE_64, + + .txclk01_multiplier = IXP2400_PLL_MULTIPLIER_16, + + .tx_poll_ports = 3, + + .tx_channel_mode = { + IXP2400_PORT_TX_MODE_MASTER | + IXP2400_PORT_TX_MODE_POS_PHY | + IXP2400_PORT_TX_MODE_ODD_PARITY | + IXP2400_PORT_TX_MODE_2_CYCLE_DECODE, + + IXP2400_PORT_TX_MODE_MASTER | + IXP2400_PORT_TX_MODE_POS_PHY | + IXP2400_PORT_TX_MODE_ODD_PARITY | + IXP2400_PORT_TX_MODE_2_CYCLE_DECODE, + + IXP2400_PORT_TX_MODE_MASTER | + IXP2400_PORT_TX_MODE_POS_PHY | + IXP2400_PORT_TX_MODE_ODD_PARITY | + IXP2400_PORT_TX_MODE_2_CYCLE_DECODE, + + IXP2400_PORT_TX_MODE_MASTER | + IXP2400_PORT_TX_MODE_POS_PHY | + IXP2400_PORT_TX_MODE_ODD_PARITY | + IXP2400_PORT_TX_MODE_2_CYCLE_DECODE + } +}; + +struct enp2611_ixpdev_priv +{ + struct ixpdev_priv ixpdev_priv; + struct net_device_stats stats; +}; + +static struct net_device *nds[3]; +static struct timer_list link_check_timer; + +static struct net_device_stats *enp2611_get_stats(struct net_device *dev) +{ + struct enp2611_ixpdev_priv *ip = netdev_priv(dev); + + pm3386_get_stats(ip->ixpdev_priv.channel, &(ip->stats)); + + return &(ip->stats); +} + +/* @@@ Poll the SFP moddef0 line too. */ +/* @@@ Try to use the pm3386 DOOL interrupt as well. */ +static void enp2611_check_link_status(unsigned long __dummy) +{ + int i; + + for (i = 0; i < 3; i++) { + struct net_device *dev; + int status; + + if (!netif_running(nds[i])) + continue; + + dev = nds[i]; + + status = pm3386_is_link_up(i); + if (status && !netif_carrier_ok(nds[i])) { + pm3386_enable_tx(i); + caleb_enable_tx(i); + netif_carrier_on(nds[i]); + } else if (!status && netif_carrier_ok(nds[i])) { + netif_carrier_off(nds[i]); + caleb_disable_tx(i); + pm3386_disable_tx(i); + } + } + + link_check_timer.expires = jiffies + HZ / 10; + add_timer(&link_check_timer); +} + +static void enp2611_set_port_admin_status(int port, int up) +{ + if (up) { + caleb_enable_rx(port); + pm3386_enable_rx(port); + } else { + caleb_disable_tx(port); + pm3386_disable_tx(port); + pm3386_disable_rx(port); + caleb_disable_rx(port); + } +} + +static int __init enp2611_init_module(void) +{ + int i; + + if (!machine_is_enp2611()) + return -ENODEV; + + caleb_reset(); + pm3386_reset(); + + for (i = 0; i < 3; i++) { + nds[i] = ixpdev_alloc(i, sizeof(struct enp2611_ixpdev_priv)); + if (nds[i] == NULL) { + while (--i >= 0) + free_netdev(nds[i]); + return -ENOMEM; + } + + SET_MODULE_OWNER(nds[i]); + nds[i]->get_stats = enp2611_get_stats; + pm3386_init_port(i); + pm3386_get_mac(i, nds[i]->dev_addr); + } + + ixp2400_msf_init(&enp2611_msf_parameters); + + if (ixpdev_init(3, nds, enp2611_set_port_admin_status)) { + for (i = 0; i < 3; i++) + free_netdev(nds[i]); + return -EINVAL; + } + + init_timer(&link_check_timer); + link_check_timer.function = enp2611_check_link_status; + link_check_timer.expires = jiffies; + add_timer(&link_check_timer); + + return 0; +} + +static void __exit enp2611_cleanup_module(void) +{ + int i; + + del_timer_sync(&link_check_timer); + + ixpdev_deinit(); + for (i = 0; i < 3; i++) + free_netdev(nds[i]); +} + +module_init(enp2611_init_module); +module_exit(enp2611_cleanup_module); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ixp2000/ixp2400-msf.c b/drivers/net/ixp2000/ixp2400-msf.c new file mode 100644 index 00000000000..48a3a891d3a --- /dev/null +++ b/drivers/net/ixp2000/ixp2400-msf.c @@ -0,0 +1,213 @@ +/* + * Generic library functions for the MSF (Media and Switch Fabric) unit + * found on the Intel IXP2400 network processor. + * + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <asm/hardware.h> +#include <asm/arch/ixp2000-regs.h> +#include <asm/delay.h> +#include <asm/io.h> +#include "ixp2400-msf.h" + +/* + * This is the Intel recommended PLL init procedure as described on + * page 340 of the IXP2400/IXP2800 Programmer's Reference Manual. + */ +static void ixp2400_pll_init(struct ixp2400_msf_parameters *mp) +{ + int rx_dual_clock; + int tx_dual_clock; + u32 value; + + /* + * If the RX mode is not 1x32, we have to enable both RX PLLs + * (#0 and #1.) The same thing for the TX direction. + */ + rx_dual_clock = !!(mp->rx_mode & IXP2400_RX_MODE_WIDTH_MASK); + tx_dual_clock = !!(mp->tx_mode & IXP2400_TX_MODE_WIDTH_MASK); + + /* + * Read initial value. + */ + value = ixp2000_reg_read(IXP2000_MSF_CLK_CNTRL); + + /* + * Put PLLs in powerdown and bypass mode. + */ + value |= 0x0000f0f0; + ixp2000_reg_write(IXP2000_MSF_CLK_CNTRL, value); + + /* + * Set single or dual clock mode bits. + */ + value &= ~0x03000000; + value |= (rx_dual_clock << 24) | (tx_dual_clock << 25); + + /* + * Set multipliers. + */ + value &= ~0x00ff0000; + value |= mp->rxclk01_multiplier << 16; + value |= mp->rxclk23_multiplier << 18; + value |= mp->txclk01_multiplier << 20; + value |= mp->txclk23_multiplier << 22; + + /* + * And write value. + */ + ixp2000_reg_write(IXP2000_MSF_CLK_CNTRL, value); + + /* + * Disable PLL bypass mode. + */ + value &= ~(0x00005000 | rx_dual_clock << 13 | tx_dual_clock << 15); + ixp2000_reg_write(IXP2000_MSF_CLK_CNTRL, value); + + /* + * Turn on PLLs. + */ + value &= ~(0x00000050 | rx_dual_clock << 5 | tx_dual_clock << 7); + ixp2000_reg_write(IXP2000_MSF_CLK_CNTRL, value); + + /* + * Wait for PLLs to lock. There are lock status bits, but IXP2400 + * erratum #65 says that these lock bits should not be relied upon + * as they might not accurately reflect the true state of the PLLs. + */ + udelay(100); +} + +/* + * Needed according to p480 of Programmer's Reference Manual. + */ +static void ixp2400_msf_free_rbuf_entries(struct ixp2400_msf_parameters *mp) +{ + int size_bits; + int i; + + /* + * Work around IXP2400 erratum #69 (silent RBUF-to-DRAM transfer + * corruption) in the Intel-recommended way: do not add the RBUF + * elements susceptible to corruption to the freelist. + */ + size_bits = mp->rx_mode & IXP2400_RX_MODE_RBUF_SIZE_MASK; + if (size_bits == IXP2400_RX_MODE_RBUF_SIZE_64) { + for (i = 1; i < 128; i++) { + if (i == 9 || i == 18 || i == 27) + continue; + ixp2000_reg_write(IXP2000_MSF_RBUF_ELEMENT_DONE, i); + } + } else if (size_bits == IXP2400_RX_MODE_RBUF_SIZE_128) { + for (i = 1; i < 64; i++) { + if (i == 4 || i == 9 || i == 13) + continue; + ixp2000_reg_write(IXP2000_MSF_RBUF_ELEMENT_DONE, i); + } + } else if (size_bits == IXP2400_RX_MODE_RBUF_SIZE_256) { + for (i = 1; i < 32; i++) { + if (i == 2 || i == 4 || i == 6) + continue; + ixp2000_reg_write(IXP2000_MSF_RBUF_ELEMENT_DONE, i); + } + } +} + +static u32 ixp2400_msf_valid_channels(u32 reg) +{ + u32 channels; + + channels = 0; + switch (reg & IXP2400_RX_MODE_WIDTH_MASK) { + case IXP2400_RX_MODE_1x32: + channels = 0x1; + if (reg & IXP2400_RX_MODE_MPHY && + !(reg & IXP2400_RX_MODE_MPHY_32)) + channels = 0xf; + break; + + case IXP2400_RX_MODE_2x16: + channels = 0x5; + break; + + case IXP2400_RX_MODE_4x8: + channels = 0xf; + break; + + case IXP2400_RX_MODE_1x16_2x8: + channels = 0xd; + break; + } + + return channels; +} + +static void ixp2400_msf_enable_rx(struct ixp2400_msf_parameters *mp) +{ + u32 value; + + value = ixp2000_reg_read(IXP2000_MSF_RX_CONTROL) & 0x0fffffff; + value |= ixp2400_msf_valid_channels(mp->rx_mode) << 28; + ixp2000_reg_write(IXP2000_MSF_RX_CONTROL, value); +} + +static void ixp2400_msf_enable_tx(struct ixp2400_msf_parameters *mp) +{ + u32 value; + + value = ixp2000_reg_read(IXP2000_MSF_TX_CONTROL) & 0x0fffffff; + value |= ixp2400_msf_valid_channels(mp->tx_mode) << 28; + ixp2000_reg_write(IXP2000_MSF_TX_CONTROL, value); +} + + +void ixp2400_msf_init(struct ixp2400_msf_parameters *mp) +{ + u32 value; + int i; + + /* + * Init the RX/TX PLLs based on the passed parameter block. + */ + ixp2400_pll_init(mp); + + /* + * Reset MSF. Bit 7 in IXP_RESET_0 resets the MSF. + */ + value = ixp2000_reg_read(IXP2000_RESET0); + ixp2000_reg_write(IXP2000_RESET0, value | 0x80); + ixp2000_reg_write(IXP2000_RESET0, value & ~0x80); + + /* + * Initialise the RX section. + */ + ixp2000_reg_write(IXP2000_MSF_RX_MPHY_POLL_LIMIT, mp->rx_poll_ports - 1); + ixp2000_reg_write(IXP2000_MSF_RX_CONTROL, mp->rx_mode); + for (i = 0; i < 4; i++) { + ixp2000_reg_write(IXP2000_MSF_RX_UP_CONTROL_0 + i, + mp->rx_channel_mode[i]); + } + ixp2400_msf_free_rbuf_entries(mp); + ixp2400_msf_enable_rx(mp); + + /* + * Initialise the TX section. + */ + ixp2000_reg_write(IXP2000_MSF_TX_MPHY_POLL_LIMIT, mp->tx_poll_ports - 1); + ixp2000_reg_write(IXP2000_MSF_TX_CONTROL, mp->tx_mode); + for (i = 0; i < 4; i++) { + ixp2000_reg_write(IXP2000_MSF_TX_UP_CONTROL_0 + i, + mp->tx_channel_mode[i]); + } + ixp2400_msf_enable_tx(mp); +} diff --git a/drivers/net/ixp2000/ixp2400-msf.h b/drivers/net/ixp2000/ixp2400-msf.h new file mode 100644 index 00000000000..3ac1af2771d --- /dev/null +++ b/drivers/net/ixp2000/ixp2400-msf.h @@ -0,0 +1,115 @@ +/* + * Generic library functions for the MSF (Media and Switch Fabric) unit + * found on the Intel IXP2400 network processor. + * + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + */ + +#ifndef __IXP2400_MSF_H +#define __IXP2400_MSF_H + +struct ixp2400_msf_parameters +{ + u32 rx_mode; + unsigned rxclk01_multiplier:2; + unsigned rxclk23_multiplier:2; + unsigned rx_poll_ports:6; + u32 rx_channel_mode[4]; + + u32 tx_mode; + unsigned txclk01_multiplier:2; + unsigned txclk23_multiplier:2; + unsigned tx_poll_ports:6; + u32 tx_channel_mode[4]; +}; + +void ixp2400_msf_init(struct ixp2400_msf_parameters *mp); + +#define IXP2400_PLL_MULTIPLIER_48 0x00 +#define IXP2400_PLL_MULTIPLIER_24 0x01 +#define IXP2400_PLL_MULTIPLIER_16 0x02 +#define IXP2400_PLL_MULTIPLIER_12 0x03 + +#define IXP2400_RX_MODE_CSIX 0x00400000 +#define IXP2400_RX_MODE_UTOPIA_POS 0x00000000 +#define IXP2400_RX_MODE_WIDTH_MASK 0x00300000 +#define IXP2400_RX_MODE_1x16_2x8 0x00300000 +#define IXP2400_RX_MODE_4x8 0x00200000 +#define IXP2400_RX_MODE_2x16 0x00100000 +#define IXP2400_RX_MODE_1x32 0x00000000 +#define IXP2400_RX_MODE_MPHY 0x00080000 +#define IXP2400_RX_MODE_SPHY 0x00000000 +#define IXP2400_RX_MODE_MPHY_32 0x00040000 +#define IXP2400_RX_MODE_MPHY_4 0x00000000 +#define IXP2400_RX_MODE_MPHY_POLLED_STATUS 0x00020000 +#define IXP2400_RX_MODE_MPHY_DIRECT_STATUS 0x00000000 +#define IXP2400_RX_MODE_CBUS_FULL_DUPLEX 0x00010000 +#define IXP2400_RX_MODE_CBUS_SIMPLEX 0x00000000 +#define IXP2400_RX_MODE_MPHY_LEVEL2 0x00004000 +#define IXP2400_RX_MODE_MPHY_LEVEL3 0x00000000 +#define IXP2400_RX_MODE_CBUS_8BIT 0x00002000 +#define IXP2400_RX_MODE_CBUS_4BIT 0x00000000 +#define IXP2400_RX_MODE_CSIX_SINGLE_FREELIST 0x00000200 +#define IXP2400_RX_MODE_CSIX_SPLIT_FREELISTS 0x00000000 +#define IXP2400_RX_MODE_RBUF_SIZE_MASK 0x0000000c +#define IXP2400_RX_MODE_RBUF_SIZE_256 0x00000008 +#define IXP2400_RX_MODE_RBUF_SIZE_128 0x00000004 +#define IXP2400_RX_MODE_RBUF_SIZE_64 0x00000000 + +#define IXP2400_PORT_RX_MODE_SLAVE 0x00000040 +#define IXP2400_PORT_RX_MODE_MASTER 0x00000000 +#define IXP2400_PORT_RX_MODE_POS_PHY_L3 0x00000020 +#define IXP2400_PORT_RX_MODE_POS_PHY_L2 0x00000000 +#define IXP2400_PORT_RX_MODE_POS_PHY 0x00000010 +#define IXP2400_PORT_RX_MODE_UTOPIA 0x00000000 +#define IXP2400_PORT_RX_MODE_EVEN_PARITY 0x0000000c +#define IXP2400_PORT_RX_MODE_ODD_PARITY 0x00000008 +#define IXP2400_PORT_RX_MODE_NO_PARITY 0x00000000 +#define IXP2400_PORT_RX_MODE_UTOPIA_BIG_CELLS 0x00000002 +#define IXP2400_PORT_RX_MODE_UTOPIA_NORMAL_CELLS 0x00000000 +#define IXP2400_PORT_RX_MODE_2_CYCLE_DECODE 0x00000001 +#define IXP2400_PORT_RX_MODE_1_CYCLE_DECODE 0x00000000 + +#define IXP2400_TX_MODE_CSIX 0x00400000 +#define IXP2400_TX_MODE_UTOPIA_POS 0x00000000 +#define IXP2400_TX_MODE_WIDTH_MASK 0x00300000 +#define IXP2400_TX_MODE_1x16_2x8 0x00300000 +#define IXP2400_TX_MODE_4x8 0x00200000 +#define IXP2400_TX_MODE_2x16 0x00100000 +#define IXP2400_TX_MODE_1x32 0x00000000 +#define IXP2400_TX_MODE_MPHY 0x00080000 +#define IXP2400_TX_MODE_SPHY 0x00000000 +#define IXP2400_TX_MODE_MPHY_32 0x00040000 +#define IXP2400_TX_MODE_MPHY_4 0x00000000 +#define IXP2400_TX_MODE_MPHY_POLLED_STATUS 0x00020000 +#define IXP2400_TX_MODE_MPHY_DIRECT_STATUS 0x00000000 +#define IXP2400_TX_MODE_CBUS_FULL_DUPLEX 0x00010000 +#define IXP2400_TX_MODE_CBUS_SIMPLEX 0x00000000 +#define IXP2400_TX_MODE_MPHY_LEVEL2 0x00004000 +#define IXP2400_TX_MODE_MPHY_LEVEL3 0x00000000 +#define IXP2400_TX_MODE_CBUS_8BIT 0x00002000 +#define IXP2400_TX_MODE_CBUS_4BIT 0x00000000 +#define IXP2400_TX_MODE_TBUF_SIZE_MASK 0x0000000c +#define IXP2400_TX_MODE_TBUF_SIZE_256 0x00000008 +#define IXP2400_TX_MODE_TBUF_SIZE_128 0x00000004 +#define IXP2400_TX_MODE_TBUF_SIZE_64 0x00000000 + +#define IXP2400_PORT_TX_MODE_SLAVE 0x00000040 +#define IXP2400_PORT_TX_MODE_MASTER 0x00000000 +#define IXP2400_PORT_TX_MODE_POS_PHY 0x00000010 +#define IXP2400_PORT_TX_MODE_UTOPIA 0x00000000 +#define IXP2400_PORT_TX_MODE_EVEN_PARITY 0x0000000c +#define IXP2400_PORT_TX_MODE_ODD_PARITY 0x00000008 +#define IXP2400_PORT_TX_MODE_NO_PARITY 0x00000000 +#define IXP2400_PORT_TX_MODE_UTOPIA_BIG_CELLS 0x00000002 +#define IXP2400_PORT_TX_MODE_2_CYCLE_DECODE 0x00000001 +#define IXP2400_PORT_TX_MODE_1_CYCLE_DECODE 0x00000000 + + +#endif diff --git a/drivers/net/ixp2000/ixp2400_rx.uc b/drivers/net/ixp2000/ixp2400_rx.uc new file mode 100644 index 00000000000..42a73e357af --- /dev/null +++ b/drivers/net/ixp2000/ixp2400_rx.uc @@ -0,0 +1,408 @@ +/* + * RX ucode for the Intel IXP2400 in POS-PHY mode. + * Copyright (C) 2004, 2005 Lennert Buytenhek + * Dedicated to Marija Kulikova. + * + * 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. + * + * Assumptions made in this code: + * - The IXP2400 MSF is configured for POS-PHY mode, in a mode where + * only one full element list is used. This includes, for example, + * 1x32 SPHY and 1x32 MPHY32, but not 4x8 SPHY or 1x32 MPHY4. (This + * is not an exhaustive list.) + * - The RBUF uses 64-byte mpackets. + * - RX descriptors reside in SRAM, and have the following format: + * struct rx_desc + * { + * // to uengine + * u32 buf_phys_addr; + * u32 buf_length; + * + * // from uengine + * u32 channel; + * u32 pkt_length; + * }; + * - Packet data resides in DRAM. + * - Packet buffer addresses are 8-byte aligned. + * - Scratch ring 0 is rx_pending. + * - Scratch ring 1 is rx_done, and has status condition 'full'. + * - The host triggers rx_done flush and rx_pending refill on seeing INTA. + * - This code is run on all eight threads of the microengine it runs on. + * + * Local memory is used for per-channel RX state. + */ + +#define RX_THREAD_FREELIST_0 0x0030 +#define RBUF_ELEMENT_DONE 0x0044 + +#define CHANNEL_FLAGS *l$index0[0] +#define CHANNEL_FLAG_RECEIVING 1 +#define PACKET_LENGTH *l$index0[1] +#define PACKET_CHECKSUM *l$index0[2] +#define BUFFER_HANDLE *l$index0[3] +#define BUFFER_START *l$index0[4] +#define BUFFER_LENGTH *l$index0[5] + +#define CHANNEL_STATE_SIZE 24 // in bytes +#define CHANNEL_STATE_SHIFT 5 // ceil(log2(state size)) + + + .sig volatile sig1 + .sig volatile sig2 + .sig volatile sig3 + + .sig mpacket_arrived + .reg add_to_rx_freelist + .reg read $rsw0, $rsw1 + .xfer_order $rsw0 $rsw1 + + .reg zero + + /* + * Initialise add_to_rx_freelist. + */ + .begin + .reg temp + .reg temp2 + + immed[add_to_rx_freelist, RX_THREAD_FREELIST_0] + immed_w1[add_to_rx_freelist, (&$rsw0 | (&mpacket_arrived << 12))] + + local_csr_rd[ACTIVE_CTX_STS] + immed[temp, 0] + alu[temp2, temp, and, 0x1f] + alu_shf[add_to_rx_freelist, add_to_rx_freelist, or, temp2, <<20] + alu[temp2, temp, and, 0x80] + alu_shf[add_to_rx_freelist, add_to_rx_freelist, or, temp2, <<18] + .end + + immed[zero, 0] + + /* + * Skip context 0 initialisation? + */ + .begin + br!=ctx[0, mpacket_receive_loop#] + .end + + /* + * Initialise local memory. + */ + .begin + .reg addr + .reg temp + + immed[temp, 0] + init_local_mem_loop#: + alu_shf[addr, --, b, temp, <<CHANNEL_STATE_SHIFT] + local_csr_wr[ACTIVE_LM_ADDR_0, addr] + nop + nop + nop + + immed[CHANNEL_FLAGS, 0] + + alu[temp, temp, +, 1] + alu[--, temp, and, 0x20] + beq[init_local_mem_loop#] + .end + + /* + * Initialise signal pipeline. + */ + .begin + local_csr_wr[SAME_ME_SIGNAL, (&sig1 << 3)] + .set_sig sig1 + + local_csr_wr[SAME_ME_SIGNAL, (&sig2 << 3)] + .set_sig sig2 + + local_csr_wr[SAME_ME_SIGNAL, (&sig3 << 3)] + .set_sig sig3 + .end + +mpacket_receive_loop#: + /* + * Synchronise and wait for mpacket. + */ + .begin + ctx_arb[sig1] + local_csr_wr[SAME_ME_SIGNAL, (0x80 | (&sig1 << 3))] + + msf[fast_wr, --, add_to_rx_freelist, 0] + .set_sig mpacket_arrived + ctx_arb[mpacket_arrived] + .set $rsw0 $rsw1 + .end + + /* + * We halt if we see {inbparerr,parerr,null,soperror}. + */ + .begin + alu_shf[--, 0x1b, and, $rsw0, >>8] + bne[abort_rswerr#] + .end + + /* + * Point local memory pointer to this channel's state area. + */ + .begin + .reg chanaddr + + alu[chanaddr, $rsw0, and, 0x1f] + alu_shf[chanaddr, --, b, chanaddr, <<CHANNEL_STATE_SHIFT] + local_csr_wr[ACTIVE_LM_ADDR_0, chanaddr] + nop + nop + nop + .end + + /* + * Check whether we received a SOP mpacket while we were already + * working on a packet, or a non-SOP mpacket while there was no + * packet pending. (SOP == RECEIVING -> abort) If everything's + * okay, update the RECEIVING flag to reflect our new state. + */ + .begin + .reg temp + .reg eop + + #if CHANNEL_FLAG_RECEIVING != 1 + #error CHANNEL_FLAG_RECEIVING is not 1 + #endif + + alu_shf[temp, 1, and, $rsw0, >>15] + alu[temp, temp, xor, CHANNEL_FLAGS] + alu[--, temp, and, CHANNEL_FLAG_RECEIVING] + beq[abort_proterr#] + + alu_shf[eop, 1, and, $rsw0, >>14] + alu[CHANNEL_FLAGS, temp, xor, eop] + .end + + /* + * Copy the mpacket into the right spot, and in case of EOP, + * write back the descriptor and pass the packet on. + */ + .begin + .reg buffer_offset + .reg _packet_length + .reg _packet_checksum + .reg _buffer_handle + .reg _buffer_start + .reg _buffer_length + + /* + * Determine buffer_offset, _packet_length and + * _packet_checksum. + */ + .begin + .reg temp + + alu[--, 1, and, $rsw0, >>15] + beq[not_sop#] + + immed[PACKET_LENGTH, 0] + immed[PACKET_CHECKSUM, 0] + + not_sop#: + alu[buffer_offset, --, b, PACKET_LENGTH] + alu_shf[temp, 0xff, and, $rsw0, >>16] + alu[_packet_length, buffer_offset, +, temp] + alu[PACKET_LENGTH, --, b, _packet_length] + + immed[temp, 0xffff] + alu[temp, $rsw1, and, temp] + alu[_packet_checksum, PACKET_CHECKSUM, +, temp] + alu[PACKET_CHECKSUM, --, b, _packet_checksum] + .end + + /* + * Allocate buffer in case of SOP. + */ + .begin + .reg temp + + alu[temp, 1, and, $rsw0, >>15] + beq[skip_buffer_alloc#] + + .begin + .sig zzz + .reg read $stemp $stemp2 + .xfer_order $stemp $stemp2 + + rx_nobufs#: + scratch[get, $stemp, zero, 0, 1], ctx_swap[zzz] + alu[_buffer_handle, --, b, $stemp] + beq[rx_nobufs#] + + sram[read, $stemp, _buffer_handle, 0, 2], + ctx_swap[zzz] + alu[_buffer_start, --, b, $stemp] + alu[_buffer_length, --, b, $stemp2] + .end + + skip_buffer_alloc#: + .end + + /* + * Resynchronise. + */ + .begin + ctx_arb[sig2] + local_csr_wr[SAME_ME_SIGNAL, (0x80 | (&sig2 << 3))] + .end + + /* + * Synchronise buffer state. + */ + .begin + .reg temp + + alu[temp, 1, and, $rsw0, >>15] + beq[copy_from_local_mem#] + + alu[BUFFER_HANDLE, --, b, _buffer_handle] + alu[BUFFER_START, --, b, _buffer_start] + alu[BUFFER_LENGTH, --, b, _buffer_length] + br[sync_state_done#] + + copy_from_local_mem#: + alu[_buffer_handle, --, b, BUFFER_HANDLE] + alu[_buffer_start, --, b, BUFFER_START] + alu[_buffer_length, --, b, BUFFER_LENGTH] + + sync_state_done#: + .end + +#if 0 + /* + * Debug buffer state management. + */ + .begin + .reg temp + + alu[temp, 1, and, $rsw0, >>14] + beq[no_poison#] + immed[BUFFER_HANDLE, 0xdead] + immed[BUFFER_START, 0xdead] + immed[BUFFER_LENGTH, 0xdead] + no_poison#: + + immed[temp, 0xdead] + alu[--, _buffer_handle, -, temp] + beq[state_corrupted#] + alu[--, _buffer_start, -, temp] + beq[state_corrupted#] + alu[--, _buffer_length, -, temp] + beq[state_corrupted#] + .end +#endif + + /* + * Check buffer length. + */ + .begin + alu[--, _buffer_length, -, _packet_length] + blo[buffer_overflow#] + .end + + /* + * Copy the mpacket and give back the RBUF element. + */ + .begin + .reg element + .reg xfer_size + .reg temp + .sig copy_sig + + alu_shf[element, 0x7f, and, $rsw0, >>24] + alu_shf[xfer_size, 0xff, and, $rsw0, >>16] + + alu[xfer_size, xfer_size, -, 1] + alu_shf[xfer_size, 0x10, or, xfer_size, >>3] + alu_shf[temp, 0x10, or, xfer_size, <<21] + alu_shf[temp, temp, or, element, <<11] + alu_shf[--, temp, or, 1, <<18] + + dram[rbuf_rd, --, _buffer_start, buffer_offset, max_8], + indirect_ref, sig_done[copy_sig] + ctx_arb[copy_sig] + + alu[temp, RBUF_ELEMENT_DONE, or, element, <<16] + msf[fast_wr, --, temp, 0] + .end + + /* + * If EOP, write back the packet descriptor. + */ + .begin + .reg write $stemp $stemp2 + .xfer_order $stemp $stemp2 + .sig zzz + + alu_shf[--, 1, and, $rsw0, >>14] + beq[no_writeback#] + + alu[$stemp, $rsw0, and, 0x1f] + alu[$stemp2, --, b, _packet_length] + sram[write, $stemp, _buffer_handle, 8, 2], ctx_swap[zzz] + + no_writeback#: + .end + + /* + * Resynchronise. + */ + .begin + ctx_arb[sig3] + local_csr_wr[SAME_ME_SIGNAL, (0x80 | (&sig3 << 3))] + .end + + /* + * If EOP, put the buffer back onto the scratch ring. + */ + .begin + .reg write $stemp + .sig zzz + + br_inp_state[SCR_Ring1_Status, rx_done_ring_overflow#] + + alu_shf[--, 1, and, $rsw0, >>14] + beq[mpacket_receive_loop#] + + alu[--, 1, and, $rsw0, >>10] + bne[rxerr#] + + alu[$stemp, --, b, _buffer_handle] + scratch[put, $stemp, zero, 4, 1], ctx_swap[zzz] + cap[fast_wr, 0, XSCALE_INT_A] + br[mpacket_receive_loop#] + + rxerr#: + alu[$stemp, --, b, _buffer_handle] + scratch[put, $stemp, zero, 0, 1], ctx_swap[zzz] + br[mpacket_receive_loop#] + .end + .end + + +abort_rswerr#: + halt + +abort_proterr#: + halt + +state_corrupted#: + halt + +buffer_overflow#: + halt + +rx_done_ring_overflow#: + halt + + diff --git a/drivers/net/ixp2000/ixp2400_rx.ucode b/drivers/net/ixp2000/ixp2400_rx.ucode new file mode 100644 index 00000000000..e8aee2f81aa --- /dev/null +++ b/drivers/net/ixp2000/ixp2400_rx.ucode @@ -0,0 +1,130 @@ +static struct ixp2000_uengine_code ixp2400_rx = +{ + .cpu_model_bitmask = 0x000003fe, + .cpu_min_revision = 0, + .cpu_max_revision = 255, + + .uengine_parameters = IXP2000_UENGINE_8_CONTEXTS | + IXP2000_UENGINE_PRN_UPDATE_EVERY | + IXP2000_UENGINE_NN_FROM_PREVIOUS | + IXP2000_UENGINE_ASSERT_EMPTY_AT_0 | + IXP2000_UENGINE_LM_ADDR1_PER_CONTEXT | + IXP2000_UENGINE_LM_ADDR0_PER_CONTEXT, + + .initial_reg_values = (struct ixp2000_reg_value []) { + { -1, -1 } + }, + + .num_insns = 109, + .insns = (u8 []) { + 0xf0, 0x00, 0x0c, 0xc0, 0x05, + 0xf4, 0x44, 0x0c, 0x00, 0x05, + 0xfc, 0x04, 0x4c, 0x00, 0x00, + 0xf0, 0x00, 0x00, 0x3b, 0x00, + 0xb4, 0x40, 0xf0, 0x3b, 0x1f, + 0x8a, 0xc0, 0x50, 0x3e, 0x05, + 0xb4, 0x40, 0xf0, 0x3b, 0x80, + 0x9a, 0xe0, 0x00, 0x3e, 0x05, + 0xf0, 0x00, 0x00, 0x07, 0x00, + 0xd8, 0x05, 0xc0, 0x00, 0x11, + 0xf0, 0x00, 0x00, 0x0f, 0x00, + 0x91, 0xb0, 0x20, 0x0e, 0x00, + 0xfc, 0x06, 0x60, 0x0b, 0x00, + 0xf0, 0x00, 0x0c, 0x03, 0x00, + 0xf0, 0x00, 0x0c, 0x03, 0x00, + 0xf0, 0x00, 0x0c, 0x03, 0x00, + 0xf0, 0x00, 0x0c, 0x02, 0x00, + 0xb0, 0xc0, 0x30, 0x0f, 0x01, + 0xa4, 0x70, 0x00, 0x0f, 0x20, + 0xd8, 0x02, 0xc0, 0x01, 0x00, + 0xfc, 0x10, 0xac, 0x23, 0x08, + 0xfc, 0x10, 0xac, 0x43, 0x10, + 0xfc, 0x10, 0xac, 0x63, 0x18, + 0xe0, 0x00, 0x00, 0x00, 0x02, + 0xfc, 0x10, 0xae, 0x23, 0x88, + 0x3d, 0x00, 0x04, 0x03, 0x20, + 0xe0, 0x00, 0x00, 0x00, 0x10, + 0x84, 0x82, 0x02, 0x01, 0x3b, + 0xd8, 0x1a, 0x00, 0x01, 0x01, + 0xb4, 0x00, 0x8c, 0x7d, 0x80, + 0x91, 0xb0, 0x80, 0x22, 0x00, + 0xfc, 0x06, 0x60, 0x23, 0x00, + 0xf0, 0x00, 0x0c, 0x03, 0x00, + 0xf0, 0x00, 0x0c, 0x03, 0x00, + 0xf0, 0x00, 0x0c, 0x03, 0x00, + 0x94, 0xf0, 0x92, 0x01, 0x21, + 0xac, 0x40, 0x60, 0x26, 0x00, + 0xa4, 0x30, 0x0c, 0x04, 0x06, + 0xd8, 0x1a, 0x40, 0x01, 0x00, + 0x94, 0xe0, 0xa2, 0x01, 0x21, + 0xac, 0x20, 0x00, 0x28, 0x06, + 0x84, 0xf2, 0x02, 0x01, 0x21, + 0xd8, 0x0b, 0x40, 0x01, 0x00, + 0xf0, 0x00, 0x0c, 0x02, 0x01, + 0xf0, 0x00, 0x0c, 0x02, 0x02, + 0xa0, 0x00, 0x08, 0x04, 0x00, + 0x95, 0x00, 0xc6, 0x01, 0xff, + 0xa0, 0x80, 0x10, 0x30, 0x00, + 0xa0, 0x60, 0x1c, 0x00, 0x01, + 0xf0, 0x0f, 0xf0, 0x33, 0xff, + 0xb4, 0x00, 0xc0, 0x31, 0x81, + 0xb0, 0x80, 0xb0, 0x32, 0x02, + 0xa0, 0x20, 0x20, 0x2c, 0x00, + 0x94, 0xf0, 0xd2, 0x01, 0x21, + 0xd8, 0x0f, 0x40, 0x01, 0x00, + 0x19, 0x40, 0x10, 0x04, 0x20, + 0xa0, 0x00, 0x26, 0x04, 0x00, + 0xd8, 0x0d, 0xc0, 0x01, 0x00, + 0x00, 0x42, 0x10, 0x80, 0x02, + 0xb0, 0x00, 0x46, 0x04, 0x00, + 0xb0, 0x00, 0x56, 0x08, 0x00, + 0xe0, 0x00, 0x00, 0x00, 0x04, + 0xfc, 0x10, 0xae, 0x43, 0x90, + 0x84, 0xf0, 0x32, 0x01, 0x21, + 0xd8, 0x11, 0x40, 0x01, 0x00, + 0xa0, 0x60, 0x3c, 0x00, 0x02, + 0xa0, 0x20, 0x40, 0x10, 0x00, + 0xa0, 0x20, 0x50, 0x14, 0x00, + 0xd8, 0x12, 0x00, 0x00, 0x18, + 0xa0, 0x00, 0x28, 0x0c, 0x00, + 0xb0, 0x00, 0x48, 0x10, 0x00, + 0xb0, 0x00, 0x58, 0x14, 0x00, + 0xaa, 0xf0, 0x00, 0x14, 0x01, + 0xd8, 0x1a, 0xc0, 0x01, 0x05, + 0x85, 0x80, 0x42, 0x01, 0xff, + 0x95, 0x00, 0x66, 0x01, 0xff, + 0xba, 0xc0, 0x60, 0x1b, 0x01, + 0x9a, 0x30, 0x60, 0x19, 0x30, + 0x9a, 0xb0, 0x70, 0x1a, 0x30, + 0x9b, 0x50, 0x78, 0x1e, 0x04, + 0x8a, 0xe2, 0x08, 0x1e, 0x21, + 0x6a, 0x4e, 0x00, 0x13, 0x00, + 0xe0, 0x00, 0x00, 0x00, 0x30, + 0x9b, 0x00, 0x7a, 0x92, 0x04, + 0x3d, 0x00, 0x04, 0x1f, 0x20, + 0x84, 0xe2, 0x02, 0x01, 0x21, + 0xd8, 0x16, 0x80, 0x01, 0x00, + 0xa4, 0x18, 0x0c, 0x7d, 0x80, + 0xa0, 0x58, 0x1c, 0x00, 0x01, + 0x01, 0x42, 0x00, 0xa0, 0x02, + 0xe0, 0x00, 0x00, 0x00, 0x08, + 0xfc, 0x10, 0xae, 0x63, 0x98, + 0xd8, 0x1b, 0x00, 0xc2, 0x14, + 0x84, 0xe2, 0x02, 0x01, 0x21, + 0xd8, 0x05, 0xc0, 0x01, 0x00, + 0x84, 0xa2, 0x02, 0x01, 0x21, + 0xd8, 0x19, 0x40, 0x01, 0x01, + 0xa0, 0x58, 0x0c, 0x00, 0x02, + 0x1a, 0x40, 0x00, 0x04, 0x24, + 0x33, 0x00, 0x01, 0x2f, 0x20, + 0xd8, 0x05, 0xc0, 0x00, 0x18, + 0xa0, 0x58, 0x0c, 0x00, 0x02, + 0x1a, 0x40, 0x00, 0x04, 0x20, + 0xd8, 0x05, 0xc0, 0x00, 0x18, + 0xe0, 0x00, 0x02, 0x00, 0x00, + 0xe0, 0x00, 0x02, 0x00, 0x00, + 0xe0, 0x00, 0x02, 0x00, 0x00, + 0xe0, 0x00, 0x02, 0x00, 0x00, + 0xe0, 0x00, 0x02, 0x00, 0x00, + } +}; diff --git a/drivers/net/ixp2000/ixp2400_tx.uc b/drivers/net/ixp2000/ixp2400_tx.uc new file mode 100644 index 00000000000..d090d1884fb --- /dev/null +++ b/drivers/net/ixp2000/ixp2400_tx.uc @@ -0,0 +1,272 @@ +/* + * TX ucode for the Intel IXP2400 in POS-PHY mode. + * Copyright (C) 2004, 2005 Lennert Buytenhek + * Dedicated to Marija Kulikova. + * + * 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. + * + * Assumptions made in this code: + * - The IXP2400 MSF is configured for POS-PHY mode, in a mode where + * only one TBUF partition is used. This includes, for example, + * 1x32 SPHY and 1x32 MPHY32, but not 4x8 SPHY or 1x32 MPHY4. (This + * is not an exhaustive list.) + * - The TBUF uses 64-byte mpackets. + * - TX descriptors reside in SRAM, and have the following format: + * struct tx_desc + * { + * // to uengine + * u32 buf_phys_addr; + * u32 pkt_length; + * u32 channel; + * }; + * - Packet data resides in DRAM. + * - Packet buffer addresses are 8-byte aligned. + * - Scratch ring 2 is tx_pending. + * - Scratch ring 3 is tx_done, and has status condition 'full'. + * - This code is run on all eight threads of the microengine it runs on. + */ + +#define TX_SEQUENCE_0 0x0060 +#define TBUF_CTRL 0x1800 + +#define PARTITION_SIZE 128 +#define PARTITION_THRESH 96 + + + .sig volatile sig1 + .sig volatile sig2 + .sig volatile sig3 + + .reg @old_tx_seq_0 + .reg @mpkts_in_flight + .reg @next_tbuf_mpacket + + .reg @buffer_handle + .reg @buffer_start + .reg @packet_length + .reg @channel + .reg @packet_offset + + .reg zero + + immed[zero, 0] + + /* + * Skip context 0 initialisation? + */ + .begin + br!=ctx[0, mpacket_tx_loop#] + .end + + /* + * Wait until all pending TBUF elements have been transmitted. + */ + .begin + .reg read $tx + .sig zzz + + loop_empty#: + msf[read, $tx, zero, TX_SEQUENCE_0, 1], ctx_swap[zzz] + alu_shf[--, --, b, $tx, >>31] + beq[loop_empty#] + + alu[@old_tx_seq_0, --, b, $tx] + .end + + immed[@mpkts_in_flight, 0] + alu[@next_tbuf_mpacket, @old_tx_seq_0, and, (PARTITION_SIZE - 1)] + + immed[@buffer_handle, 0] + + /* + * Initialise signal pipeline. + */ + .begin + local_csr_wr[SAME_ME_SIGNAL, (&sig1 << 3)] + .set_sig sig1 + + local_csr_wr[SAME_ME_SIGNAL, (&sig2 << 3)] + .set_sig sig2 + + local_csr_wr[SAME_ME_SIGNAL, (&sig3 << 3)] + .set_sig sig3 + .end + +mpacket_tx_loop#: + .begin + .reg tbuf_element_index + .reg buffer_handle + .reg sop_eop + .reg packet_data + .reg channel + .reg mpacket_size + + /* + * If there is no packet currently being transmitted, + * dequeue the next TX descriptor, and fetch the buffer + * address, packet length and destination channel number. + */ + .begin + .reg read $stemp $stemp2 $stemp3 + .xfer_order $stemp $stemp2 $stemp3 + .sig zzz + + ctx_arb[sig1] + + alu[--, --, b, @buffer_handle] + bne[already_got_packet#] + + tx_nobufs#: + scratch[get, $stemp, zero, 8, 1], ctx_swap[zzz] + alu[@buffer_handle, --, b, $stemp] + beq[tx_nobufs#] + + sram[read, $stemp, $stemp, 0, 3], ctx_swap[zzz] + alu[@buffer_start, --, b, $stemp] + alu[@packet_length, --, b, $stemp2] + beq[zero_byte_packet#] + alu[@channel, --, b, $stemp3] + immed[@packet_offset, 0] + + already_got_packet#: + local_csr_wr[SAME_ME_SIGNAL, (0x80 | (&sig1 << 3))] + .end + + /* + * Determine tbuf element index, SOP/EOP flags, mpacket + * offset and mpacket size and cache buffer_handle and + * channel number. + */ + .begin + alu[tbuf_element_index, --, b, @next_tbuf_mpacket] + alu[@next_tbuf_mpacket, @next_tbuf_mpacket, +, 1] + alu[@next_tbuf_mpacket, @next_tbuf_mpacket, and, + (PARTITION_SIZE - 1)] + + alu[buffer_handle, --, b, @buffer_handle] + immed[@buffer_handle, 0] + + immed[sop_eop, 1] + + alu[packet_data, --, b, @packet_offset] + bne[no_sop#] + alu[sop_eop, sop_eop, or, 2] + no_sop#: + alu[packet_data, packet_data, +, @buffer_start] + + alu[channel, --, b, @channel] + + alu[mpacket_size, @packet_length, -, @packet_offset] + alu[--, 64, -, mpacket_size] + bhs[eop#] + alu[@buffer_handle, --, b, buffer_handle] + immed[mpacket_size, 64] + alu[sop_eop, sop_eop, and, 2] + eop#: + + alu[@packet_offset, @packet_offset, +, mpacket_size] + .end + + /* + * Wait until there's enough space in the TBUF. + */ + .begin + .reg read $tx + .reg temp + .sig zzz + + ctx_arb[sig2] + + br[test_space#] + + loop_space#: + msf[read, $tx, zero, TX_SEQUENCE_0, 1], ctx_swap[zzz] + + alu[temp, $tx, -, @old_tx_seq_0] + alu[temp, temp, and, 0xff] + alu[@mpkts_in_flight, @mpkts_in_flight, -, temp] + + alu[@old_tx_seq_0, --, b, $tx] + + test_space#: + alu[--, PARTITION_THRESH, -, @mpkts_in_flight] + blo[loop_space#] + + alu[@mpkts_in_flight, @mpkts_in_flight, +, 1] + + local_csr_wr[SAME_ME_SIGNAL, (0x80 | (&sig2 << 3))] + .end + + /* + * Copy the packet data to the TBUF. + */ + .begin + .reg temp + .sig copy_sig + + alu[temp, mpacket_size, -, 1] + alu_shf[temp, 0x10, or, temp, >>3] + alu_shf[temp, 0x10, or, temp, <<21] + alu_shf[temp, temp, or, tbuf_element_index, <<11] + alu_shf[--, temp, or, 1, <<18] + + dram[tbuf_wr, --, packet_data, 0, max_8], + indirect_ref, sig_done[copy_sig] + ctx_arb[copy_sig] + .end + + /* + * Mark TBUF element as ready-to-be-transmitted. + */ + .begin + .reg write $tsw $tsw2 + .xfer_order $tsw $tsw2 + .reg temp + .sig zzz + + alu_shf[temp, channel, or, mpacket_size, <<24] + alu_shf[$tsw, temp, or, sop_eop, <<8] + immed[$tsw2, 0] + + immed[temp, TBUF_CTRL] + alu_shf[temp, temp, or, tbuf_element_index, <<3] + msf[write, $tsw, temp, 0, 2], ctx_swap[zzz] + .end + + /* + * Resynchronise. + */ + .begin + ctx_arb[sig3] + local_csr_wr[SAME_ME_SIGNAL, (0x80 | (&sig3 << 3))] + .end + + /* + * If this was an EOP mpacket, recycle the TX buffer + * and signal the host. + */ + .begin + .reg write $stemp + .sig zzz + + alu[--, sop_eop, and, 1] + beq[mpacket_tx_loop#] + + tx_done_ring_full#: + br_inp_state[SCR_Ring3_Status, tx_done_ring_full#] + + alu[$stemp, --, b, buffer_handle] + scratch[put, $stemp, zero, 12, 1], ctx_swap[zzz] + cap[fast_wr, 0, XSCALE_INT_A] + br[mpacket_tx_loop#] + .end + .end + + +zero_byte_packet#: + halt + + diff --git a/drivers/net/ixp2000/ixp2400_tx.ucode b/drivers/net/ixp2000/ixp2400_tx.ucode new file mode 100644 index 00000000000..a433e24b0a5 --- /dev/null +++ b/drivers/net/ixp2000/ixp2400_tx.ucode @@ -0,0 +1,98 @@ +static struct ixp2000_uengine_code ixp2400_tx = +{ + .cpu_model_bitmask = 0x000003fe, + .cpu_min_revision = 0, + .cpu_max_revision = 255, + + .uengine_parameters = IXP2000_UENGINE_8_CONTEXTS | + IXP2000_UENGINE_PRN_UPDATE_EVERY | + IXP2000_UENGINE_NN_FROM_PREVIOUS | + IXP2000_UENGINE_ASSERT_EMPTY_AT_0 | + IXP2000_UENGINE_LM_ADDR1_PER_CONTEXT | + IXP2000_UENGINE_LM_ADDR0_PER_CONTEXT, + + .initial_reg_values = (struct ixp2000_reg_value []) { + { -1, -1 } + }, + + .num_insns = 77, + .insns = (u8 []) { + 0xf0, 0x00, 0x00, 0x07, 0x00, + 0xd8, 0x03, 0x00, 0x00, 0x11, + 0x3c, 0x40, 0x00, 0x04, 0xe0, + 0x81, 0xf2, 0x02, 0x01, 0x00, + 0xd8, 0x00, 0x80, 0x01, 0x00, + 0xb0, 0x08, 0x06, 0x00, 0x00, + 0xf0, 0x00, 0x0c, 0x00, 0x80, + 0xb4, 0x49, 0x02, 0x03, 0x7f, + 0xf0, 0x00, 0x02, 0x83, 0x00, + 0xfc, 0x10, 0xac, 0x23, 0x08, + 0xfc, 0x10, 0xac, 0x43, 0x10, + 0xfc, 0x10, 0xac, 0x63, 0x18, + 0xe0, 0x00, 0x00, 0x00, 0x02, + 0xa0, 0x30, 0x02, 0x80, 0x00, + 0xd8, 0x06, 0x00, 0x01, 0x01, + 0x19, 0x40, 0x00, 0x04, 0x28, + 0xb0, 0x0a, 0x06, 0x00, 0x00, + 0xd8, 0x03, 0xc0, 0x01, 0x00, + 0x00, 0x44, 0x00, 0x80, 0x80, + 0xa0, 0x09, 0x06, 0x00, 0x00, + 0xb0, 0x0b, 0x06, 0x04, 0x00, + 0xd8, 0x13, 0x00, 0x01, 0x00, + 0xb0, 0x0c, 0x06, 0x08, 0x00, + 0xf0, 0x00, 0x0c, 0x00, 0xa0, + 0xfc, 0x10, 0xae, 0x23, 0x88, + 0xa0, 0x00, 0x12, 0x40, 0x00, + 0xb0, 0xc9, 0x02, 0x43, 0x01, + 0xb4, 0x49, 0x02, 0x43, 0x7f, + 0xb0, 0x00, 0x22, 0x80, 0x00, + 0xf0, 0x00, 0x02, 0x83, 0x00, + 0xf0, 0x00, 0x0c, 0x04, 0x02, + 0xb0, 0x40, 0x6c, 0x00, 0xa0, + 0xd8, 0x08, 0x80, 0x01, 0x01, + 0xaa, 0x00, 0x2c, 0x08, 0x02, + 0xa0, 0xc0, 0x30, 0x18, 0x90, + 0xa0, 0x00, 0x43, 0x00, 0x00, + 0xba, 0xc0, 0x32, 0xc0, 0xa0, + 0xaa, 0xb0, 0x00, 0x0f, 0x40, + 0xd8, 0x0a, 0x80, 0x01, 0x04, + 0xb0, 0x0a, 0x00, 0x08, 0x00, + 0xf0, 0x00, 0x00, 0x0f, 0x40, + 0xa4, 0x00, 0x2c, 0x08, 0x02, + 0xa0, 0x8a, 0x00, 0x0c, 0xa0, + 0xe0, 0x00, 0x00, 0x00, 0x04, + 0xd8, 0x0c, 0x80, 0x00, 0x18, + 0x3c, 0x40, 0x00, 0x04, 0xe0, + 0xba, 0x80, 0x42, 0x01, 0x80, + 0xb4, 0x40, 0x40, 0x13, 0xff, + 0xaa, 0x88, 0x00, 0x10, 0x80, + 0xb0, 0x08, 0x06, 0x00, 0x00, + 0xaa, 0xf0, 0x0d, 0x80, 0x80, + 0xd8, 0x0b, 0x40, 0x01, 0x05, + 0xa0, 0x88, 0x0c, 0x04, 0x80, + 0xfc, 0x10, 0xae, 0x43, 0x90, + 0xba, 0xc0, 0x50, 0x0f, 0x01, + 0x9a, 0x30, 0x50, 0x15, 0x30, + 0x9a, 0xb0, 0x50, 0x16, 0x30, + 0x9b, 0x50, 0x58, 0x16, 0x01, + 0x8a, 0xe2, 0x08, 0x16, 0x21, + 0x6b, 0x4e, 0x00, 0x83, 0x03, + 0xe0, 0x00, 0x00, 0x00, 0x30, + 0x9a, 0x80, 0x70, 0x0e, 0x04, + 0x8b, 0x88, 0x08, 0x1e, 0x02, + 0xf0, 0x00, 0x0c, 0x01, 0x81, + 0xf0, 0x01, 0x80, 0x1f, 0x00, + 0x9b, 0xd0, 0x78, 0x1e, 0x01, + 0x3d, 0x42, 0x00, 0x1c, 0x20, + 0xe0, 0x00, 0x00, 0x00, 0x08, + 0xfc, 0x10, 0xae, 0x63, 0x98, + 0xa4, 0x30, 0x0c, 0x04, 0x02, + 0xd8, 0x03, 0x00, 0x01, 0x00, + 0xd8, 0x11, 0xc1, 0x42, 0x14, + 0xa0, 0x18, 0x00, 0x08, 0x00, + 0x1a, 0x40, 0x00, 0x04, 0x2c, + 0x33, 0x00, 0x01, 0x2f, 0x20, + 0xd8, 0x03, 0x00, 0x00, 0x18, + 0xe0, 0x00, 0x02, 0x00, 0x00, + } +}; diff --git a/drivers/net/ixp2000/ixpdev.c b/drivers/net/ixp2000/ixpdev.c new file mode 100644 index 00000000000..216aad1911e --- /dev/null +++ b/drivers/net/ixp2000/ixpdev.c @@ -0,0 +1,404 @@ +/* + * IXP2000 MSF network device driver + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <asm/arch/uengine.h> +#include <asm/mach-types.h> +#include <asm/io.h> +#include "ixp2400_rx.ucode" +#include "ixp2400_tx.ucode" +#include "ixpdev_priv.h" +#include "ixpdev.h" + +static int nds_count; +static struct net_device **nds; +static int nds_open; +static void (*set_port_admin_status)(int port, int up); + +static struct ixpdev_rx_desc * const rx_desc = + (struct ixpdev_rx_desc *)(IXP2000_SRAM0_VIRT_BASE + RX_BUF_DESC_BASE); +static struct ixpdev_tx_desc * const tx_desc = + (struct ixpdev_tx_desc *)(IXP2000_SRAM0_VIRT_BASE + TX_BUF_DESC_BASE); +static int tx_pointer; + + +static int ixpdev_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ixpdev_priv *ip = netdev_priv(dev); + struct ixpdev_tx_desc *desc; + int entry; + + if (unlikely(skb->len > PAGE_SIZE)) { + /* @@@ Count drops. */ + dev_kfree_skb(skb); + return 0; + } + + entry = tx_pointer; + tx_pointer = (tx_pointer + 1) % TX_BUF_COUNT; + + desc = tx_desc + entry; + desc->pkt_length = skb->len; + desc->channel = ip->channel; + + skb_copy_and_csum_dev(skb, phys_to_virt(desc->buf_addr)); + dev_kfree_skb(skb); + + ixp2000_reg_write(RING_TX_PENDING, + TX_BUF_DESC_BASE + (entry * sizeof(struct ixpdev_tx_desc))); + + dev->trans_start = jiffies; + + local_irq_disable(); + ip->tx_queue_entries++; + if (ip->tx_queue_entries == TX_BUF_COUNT_PER_CHAN) + netif_stop_queue(dev); + local_irq_enable(); + + return 0; +} + + +static int ixpdev_rx(struct net_device *dev, int *budget) +{ + while (*budget > 0) { + struct ixpdev_rx_desc *desc; + struct sk_buff *skb; + void *buf; + u32 _desc; + + _desc = ixp2000_reg_read(RING_RX_DONE); + if (_desc == 0) + return 0; + + desc = rx_desc + + ((_desc - RX_BUF_DESC_BASE) / sizeof(struct ixpdev_rx_desc)); + buf = phys_to_virt(desc->buf_addr); + + if (desc->pkt_length < 4 || desc->pkt_length > PAGE_SIZE) { + printk(KERN_ERR "ixp2000: rx err, length %d\n", + desc->pkt_length); + goto err; + } + + if (desc->channel < 0 || desc->channel >= nds_count) { + printk(KERN_ERR "ixp2000: rx err, channel %d\n", + desc->channel); + goto err; + } + + /* @@@ Make FCS stripping configurable. */ + desc->pkt_length -= 4; + + if (unlikely(!netif_running(nds[desc->channel]))) + goto err; + + skb = dev_alloc_skb(desc->pkt_length + 2); + if (likely(skb != NULL)) { + skb->dev = nds[desc->channel]; + skb_reserve(skb, 2); + eth_copy_and_sum(skb, buf, desc->pkt_length, 0); + skb_put(skb, desc->pkt_length); + skb->protocol = eth_type_trans(skb, skb->dev); + + skb->dev->last_rx = jiffies; + + netif_receive_skb(skb); + } + +err: + ixp2000_reg_write(RING_RX_PENDING, _desc); + dev->quota--; + (*budget)--; + } + + return 1; +} + +/* dev always points to nds[0]. */ +static int ixpdev_poll(struct net_device *dev, int *budget) +{ + /* @@@ Have to stop polling when nds[0] is administratively + * downed while we are polling. */ + do { + ixp2000_reg_write(IXP2000_IRQ_THD_RAW_STATUS_A_0, 0x00ff); + + if (ixpdev_rx(dev, budget)) + return 1; + } while (ixp2000_reg_read(IXP2000_IRQ_THD_RAW_STATUS_A_0) & 0x00ff); + + netif_rx_complete(dev); + ixp2000_reg_write(IXP2000_IRQ_THD_ENABLE_SET_A_0, 0x00ff); + + return 0; +} + +/* @@@ Ugly hack. */ +static inline int netif_rx_schedule_prep_notup(struct net_device *dev) +{ + return !test_and_set_bit(__LINK_STATE_RX_SCHED, &dev->state); +} + +static void ixpdev_tx_complete(void) +{ + int channel; + u32 wake; + + wake = 0; + while (1) { + struct ixpdev_priv *ip; + u32 desc; + int entry; + + desc = ixp2000_reg_read(RING_TX_DONE); + if (desc == 0) + break; + + /* @@@ Check whether entries come back in order. */ + entry = (desc - TX_BUF_DESC_BASE) / sizeof(struct ixpdev_tx_desc); + channel = tx_desc[entry].channel; + + if (channel < 0 || channel >= nds_count) { + printk(KERN_ERR "ixp2000: txcomp channel index " + "out of bounds (%d, %.8i, %d)\n", + channel, (unsigned int)desc, entry); + continue; + } + + ip = netdev_priv(nds[channel]); + if (ip->tx_queue_entries == TX_BUF_COUNT_PER_CHAN) + wake |= 1 << channel; + ip->tx_queue_entries--; + } + + for (channel = 0; wake != 0; channel++) { + if (wake & (1 << channel)) { + netif_wake_queue(nds[channel]); + wake &= ~(1 << channel); + } + } +} + +static irqreturn_t ixpdev_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 status; + + status = ixp2000_reg_read(IXP2000_IRQ_THD_STATUS_A_0); + if (status == 0) + return IRQ_NONE; + + /* + * Any of the eight receive units signaled RX? + */ + if (status & 0x00ff) { + ixp2000_reg_wrb(IXP2000_IRQ_THD_ENABLE_CLEAR_A_0, 0x00ff); + if (likely(netif_rx_schedule_prep_notup(nds[0]))) { + __netif_rx_schedule(nds[0]); + } else { + printk(KERN_CRIT "ixp2000: irq while polling!!\n"); + } + } + + /* + * Any of the eight transmit units signaled TXdone? + */ + if (status & 0xff00) { + ixp2000_reg_wrb(IXP2000_IRQ_THD_RAW_STATUS_A_0, 0xff00); + ixpdev_tx_complete(); + } + + return IRQ_HANDLED; +} + +static int ixpdev_open(struct net_device *dev) +{ + struct ixpdev_priv *ip = netdev_priv(dev); + int err; + + if (!nds_open++) { + err = request_irq(IRQ_IXP2000_THDA0, ixpdev_interrupt, + SA_SHIRQ, "ixp2000_eth", nds); + if (err) { + nds_open--; + return err; + } + + ixp2000_reg_write(IXP2000_IRQ_THD_ENABLE_SET_A_0, 0xffff); + } + + set_port_admin_status(ip->channel, 1); + netif_start_queue(dev); + + return 0; +} + +static int ixpdev_close(struct net_device *dev) +{ + struct ixpdev_priv *ip = netdev_priv(dev); + + netif_stop_queue(dev); + set_port_admin_status(ip->channel, 0); + + if (!--nds_open) { + ixp2000_reg_write(IXP2000_IRQ_THD_ENABLE_CLEAR_A_0, 0xffff); + free_irq(IRQ_IXP2000_THDA0, nds); + } + + return 0; +} + +struct net_device *ixpdev_alloc(int channel, int sizeof_priv) +{ + struct net_device *dev; + struct ixpdev_priv *ip; + + dev = alloc_etherdev(sizeof_priv); + if (dev == NULL) + return NULL; + + dev->hard_start_xmit = ixpdev_xmit; + dev->poll = ixpdev_poll; + dev->open = ixpdev_open; + dev->stop = ixpdev_close; + + dev->features |= NETIF_F_SG | NETIF_F_HW_CSUM; + dev->weight = 64; + + ip = netdev_priv(dev); + ip->channel = channel; + ip->tx_queue_entries = 0; + + return dev; +} + +int ixpdev_init(int __nds_count, struct net_device **__nds, + void (*__set_port_admin_status)(int port, int up)) +{ + int i; + int err; + + if (RX_BUF_COUNT > 192 || TX_BUF_COUNT > 192) { + static void __too_many_rx_or_tx_buffers(void); + __too_many_rx_or_tx_buffers(); + } + + nds_count = __nds_count; + nds = __nds; + set_port_admin_status = __set_port_admin_status; + + for (i = 0; i < nds_count; i++) { + err = register_netdev(nds[i]); + if (err) { + while (--i >= 0) + unregister_netdev(nds[i]); + goto err_out; + } + } + + for (i = 0; i < RX_BUF_COUNT; i++) { + void *buf; + + buf = (void *)get_zeroed_page(GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + while (--i >= 0) + free_page((unsigned long)phys_to_virt(rx_desc[i].buf_addr)); + goto err_unregister; + } + rx_desc[i].buf_addr = virt_to_phys(buf); + rx_desc[i].buf_length = PAGE_SIZE; + } + + /* @@@ Maybe we shouldn't be preallocating TX buffers. */ + for (i = 0; i < TX_BUF_COUNT; i++) { + void *buf; + + buf = (void *)get_zeroed_page(GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + while (--i >= 0) + free_page((unsigned long)phys_to_virt(tx_desc[i].buf_addr)); + goto err_free_rx; + } + tx_desc[i].buf_addr = virt_to_phys(buf); + } + + /* 256 entries, ring status set means 'empty', base address 0x0000. */ + ixp2000_reg_write(RING_RX_PENDING_BASE, 0x44000000); + ixp2000_reg_write(RING_RX_PENDING_HEAD, 0x00000000); + ixp2000_reg_write(RING_RX_PENDING_TAIL, 0x00000000); + + /* 256 entries, ring status set means 'full', base address 0x0400. */ + ixp2000_reg_write(RING_RX_DONE_BASE, 0x40000400); + ixp2000_reg_write(RING_RX_DONE_HEAD, 0x00000000); + ixp2000_reg_write(RING_RX_DONE_TAIL, 0x00000000); + + for (i = 0; i < RX_BUF_COUNT; i++) { + ixp2000_reg_write(RING_RX_PENDING, + RX_BUF_DESC_BASE + (i * sizeof(struct ixpdev_rx_desc))); + } + + ixp2000_uengine_load(0, &ixp2400_rx); + ixp2000_uengine_start_contexts(0, 0xff); + + + /* 256 entries, ring status set means 'empty', base address 0x0800. */ + ixp2000_reg_write(RING_TX_PENDING_BASE, 0x44000800); + ixp2000_reg_write(RING_TX_PENDING_HEAD, 0x00000000); + ixp2000_reg_write(RING_TX_PENDING_TAIL, 0x00000000); + + /* 256 entries, ring status set means 'full', base address 0x0c00. */ + ixp2000_reg_write(RING_TX_DONE_BASE, 0x40000c00); + ixp2000_reg_write(RING_TX_DONE_HEAD, 0x00000000); + ixp2000_reg_write(RING_TX_DONE_TAIL, 0x00000000); + + ixp2000_uengine_load(1, &ixp2400_tx); + ixp2000_uengine_start_contexts(1, 0xff); + + return 0; + +err_free_rx: + for (i = 0; i < RX_BUF_COUNT; i++) + free_page((unsigned long)phys_to_virt(rx_desc[i].buf_addr)); + +err_unregister: + for (i = 0; i < nds_count; i++) + unregister_netdev(nds[i]); + +err_out: + return err; +} + +void ixpdev_deinit(void) +{ + int i; + + /* @@@ Flush out pending packets. */ + + ixp2000_uengine_stop_contexts(1, 0xff); + ixp2000_uengine_stop_contexts(0, 0xff); + ixp2000_uengine_reset(0x3); + + for (i = 0; i < TX_BUF_COUNT; i++) + free_page((unsigned long)phys_to_virt(tx_desc[i].buf_addr)); + + for (i = 0; i < RX_BUF_COUNT; i++) + free_page((unsigned long)phys_to_virt(rx_desc[i].buf_addr)); + + for (i = 0; i < nds_count; i++) + unregister_netdev(nds[i]); +} diff --git a/drivers/net/ixp2000/ixpdev.h b/drivers/net/ixp2000/ixpdev.h new file mode 100644 index 00000000000..bd686cb6305 --- /dev/null +++ b/drivers/net/ixp2000/ixpdev.h @@ -0,0 +1,27 @@ +/* + * IXP2000 MSF network device driver + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#ifndef __IXPDEV_H +#define __IXPDEV_H + +struct ixpdev_priv +{ + int channel; + int tx_queue_entries; +}; + +struct net_device *ixpdev_alloc(int channel, int sizeof_priv); +int ixpdev_init(int num_ports, struct net_device **nds, + void (*set_port_admin_status)(int port, int up)); +void ixpdev_deinit(void); + + +#endif diff --git a/drivers/net/ixp2000/ixpdev_priv.h b/drivers/net/ixp2000/ixpdev_priv.h new file mode 100644 index 00000000000..86aa08ea0c3 --- /dev/null +++ b/drivers/net/ixp2000/ixpdev_priv.h @@ -0,0 +1,57 @@ +/* + * IXP2000 MSF network device driver + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#ifndef __IXPDEV_PRIV_H +#define __IXPDEV_PRIV_H + +#define RX_BUF_DESC_BASE 0x00001000 +#define RX_BUF_COUNT ((3 * PAGE_SIZE) / (4 * sizeof(struct ixpdev_rx_desc))) +#define TX_BUF_DESC_BASE 0x00002000 +#define TX_BUF_COUNT ((3 * PAGE_SIZE) / (4 * sizeof(struct ixpdev_tx_desc))) +#define TX_BUF_COUNT_PER_CHAN (TX_BUF_COUNT / 4) + +#define RING_RX_PENDING ((u32 *)IXP2000_SCRATCH_RING_VIRT_BASE) +#define RING_RX_DONE ((u32 *)(IXP2000_SCRATCH_RING_VIRT_BASE + 4)) +#define RING_TX_PENDING ((u32 *)(IXP2000_SCRATCH_RING_VIRT_BASE + 8)) +#define RING_TX_DONE ((u32 *)(IXP2000_SCRATCH_RING_VIRT_BASE + 12)) + +#define SCRATCH_REG(x) ((u32 *)(IXP2000_GLOBAL_REG_VIRT_BASE | 0x0800 | (x))) +#define RING_RX_PENDING_BASE SCRATCH_REG(0x00) +#define RING_RX_PENDING_HEAD SCRATCH_REG(0x04) +#define RING_RX_PENDING_TAIL SCRATCH_REG(0x08) +#define RING_RX_DONE_BASE SCRATCH_REG(0x10) +#define RING_RX_DONE_HEAD SCRATCH_REG(0x14) +#define RING_RX_DONE_TAIL SCRATCH_REG(0x18) +#define RING_TX_PENDING_BASE SCRATCH_REG(0x20) +#define RING_TX_PENDING_HEAD SCRATCH_REG(0x24) +#define RING_TX_PENDING_TAIL SCRATCH_REG(0x28) +#define RING_TX_DONE_BASE SCRATCH_REG(0x30) +#define RING_TX_DONE_HEAD SCRATCH_REG(0x34) +#define RING_TX_DONE_TAIL SCRATCH_REG(0x38) + +struct ixpdev_rx_desc +{ + u32 buf_addr; + u32 buf_length; + u32 channel; + u32 pkt_length; +}; + +struct ixpdev_tx_desc +{ + u32 buf_addr; + u32 pkt_length; + u32 channel; + u32 unused; +}; + + +#endif diff --git a/drivers/net/ixp2000/pm3386.c b/drivers/net/ixp2000/pm3386.c new file mode 100644 index 00000000000..cf0681fb127 --- /dev/null +++ b/drivers/net/ixp2000/pm3386.c @@ -0,0 +1,304 @@ +/* + * Helper functions for the PM3386s on the Radisys ENP2611 + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <asm/io.h> + +/* + * Read from register 'reg' of PM3386 device 'pm'. + */ +static u16 pm3386_reg_read(int pm, int reg) +{ + void *_reg; + u16 value; + + _reg = (void *)ENP2611_PM3386_0_VIRT_BASE; + if (pm == 1) + _reg = (void *)ENP2611_PM3386_1_VIRT_BASE; + + value = *((volatile u16 *)(_reg + (reg << 1))); + +// printk(KERN_INFO "pm3386_reg_read(%d, %.3x) = %.8x\n", pm, reg, value); + + return value; +} + +/* + * Write to register 'reg' of PM3386 device 'pm', and perform + * a readback from the identification register. + */ +static void pm3386_reg_write(int pm, int reg, u16 value) +{ + void *_reg; + u16 dummy; + +// printk(KERN_INFO "pm3386_reg_write(%d, %.3x, %.8x)\n", pm, reg, value); + + _reg = (void *)ENP2611_PM3386_0_VIRT_BASE; + if (pm == 1) + _reg = (void *)ENP2611_PM3386_1_VIRT_BASE; + + *((volatile u16 *)(_reg + (reg << 1))) = value; + + dummy = *((volatile u16 *)_reg); + __asm__ __volatile__("mov %0, %0" : "+r" (dummy)); +} + +/* + * Read from port 'port' register 'reg', where the registers + * for the different ports are 'spacing' registers apart. + */ +static u16 pm3386_port_reg_read(int port, int _reg, int spacing) +{ + int reg; + + reg = _reg; + if (port & 1) + reg += spacing; + + return pm3386_reg_read(port >> 1, reg); +} + +/* + * Write to port 'port' register 'reg', where the registers + * for the different ports are 'spacing' registers apart. + */ +static void pm3386_port_reg_write(int port, int _reg, int spacing, u16 value) +{ + int reg; + + reg = _reg; + if (port & 1) + reg += spacing; + + pm3386_reg_write(port >> 1, reg, value); +} + + +void pm3386_reset(void) +{ + /* @@@ Implement me. */ +} + +static u16 swaph(u16 x) +{ + return ((x << 8) | (x >> 8)) & 0xffff; +} + +void pm3386_init_port(int port) +{ + int pm = port >> 1; + + /* + * Work around ENP2611 bootloader programming MAC address + * in reverse. + */ + if (pm3386_port_reg_read(port, 0x30a, 0x100) == 0x0000 && + (pm3386_port_reg_read(port, 0x309, 0x100) & 0xff00) == 0x5000) { + u16 temp[3]; + + temp[0] = pm3386_port_reg_read(port, 0x308, 0x100); + temp[1] = pm3386_port_reg_read(port, 0x309, 0x100); + temp[2] = pm3386_port_reg_read(port, 0x30a, 0x100); + pm3386_port_reg_write(port, 0x308, 0x100, swaph(temp[2])); + pm3386_port_reg_write(port, 0x309, 0x100, swaph(temp[1])); + pm3386_port_reg_write(port, 0x30a, 0x100, swaph(temp[0])); + } + + /* + * Initialise narrowbanding mode. See application note 2010486 + * for more information. (@@@ We also need to issue a reset + * when ROOL or DOOL are detected.) + */ + pm3386_port_reg_write(port, 0x708, 0x10, 0xd055); + udelay(500); + pm3386_port_reg_write(port, 0x708, 0x10, 0x5055); + + /* + * SPI-3 ingress block. Set 64 bytes SPI-3 burst size + * towards SPI-3 bridge. + */ + pm3386_port_reg_write(port, 0x122, 0x20, 0x0002); + + /* + * Enable ingress protocol checking, and soft reset the + * SPI-3 ingress block. + */ + pm3386_reg_write(pm, 0x103, 0x0003); + while (!(pm3386_reg_read(pm, 0x103) & 0x80)) + ; + + /* + * SPI-3 egress block. Gather 12288 bytes of the current + * packet in the TX fifo before initiating transmit on the + * SERDES interface. (Prevents TX underflows.) + */ + pm3386_port_reg_write(port, 0x221, 0x20, 0x0007); + + /* + * Enforce odd parity from the SPI-3 bridge, and soft reset + * the SPI-3 egress block. + */ + pm3386_reg_write(pm, 0x203, 0x000d & ~(4 << (port & 1))); + while ((pm3386_reg_read(pm, 0x203) & 0x000c) != 0x000c) + ; + + /* + * EGMAC block. Set this channels to reject long preambles, + * not send or transmit PAUSE frames, enable preamble checking, + * disable frame length checking, enable FCS appending, enable + * TX frame padding. + */ + pm3386_port_reg_write(port, 0x302, 0x100, 0x0113); + + /* + * Soft reset the EGMAC block. + */ + pm3386_port_reg_write(port, 0x301, 0x100, 0x8000); + udelay(10); + pm3386_port_reg_write(port, 0x301, 0x100, 0x0000); + udelay(10); + + /* + * Auto-sense autonegotiation status. + */ + pm3386_port_reg_write(port, 0x306, 0x100, 0x0100); + + /* + * Allow reception of jumbo frames. + */ + pm3386_port_reg_write(port, 0x310, 0x100, 9018); + + /* + * Allow transmission of jumbo frames. + */ + pm3386_port_reg_write(port, 0x336, 0x100, 9018); + + /* @@@ Should set 0x337/0x437 (RX forwarding threshold.) */ + + /* + * Set autonegotiation parameters to 'no PAUSE, full duplex.' + */ + pm3386_port_reg_write(port, 0x31c, 0x100, 0x0020); + udelay(10); + + /* + * Enable and restart autonegotiation. + */ + pm3386_port_reg_write(port, 0x318, 0x100, 0x0003); + udelay(1000); + pm3386_port_reg_write(port, 0x318, 0x100, 0x0002); + udelay(10); +} + +void pm3386_get_mac(int port, u8 *mac) +{ + u16 temp; + + temp = pm3386_port_reg_read(port, 0x308, 0x100); + mac[0] = temp & 0xff; + mac[1] = (temp >> 8) & 0xff; + + temp = pm3386_port_reg_read(port, 0x309, 0x100); + mac[2] = temp & 0xff; + mac[3] = (temp >> 8) & 0xff; + + temp = pm3386_port_reg_read(port, 0x30a, 0x100); + mac[4] = temp & 0xff; + mac[5] = (temp >> 8) & 0xff; +} + +static u32 pm3386_get_stat(int port, u16 base) +{ + u32 value; + + value = pm3386_port_reg_read(port, base, 0x100); + value |= pm3386_port_reg_read(port, base + 1, 0x100) << 16; + + return value; +} + +void pm3386_get_stats(int port, struct net_device_stats *stats) +{ + /* + * Snapshot statistics counters. + */ + pm3386_port_reg_write(port, 0x500, 0x100, 0x0001); + while (pm3386_port_reg_read(port, 0x500, 0x100) & 0x0001) + ; + + memset(stats, 0, sizeof(stats)); + + stats->rx_packets = pm3386_get_stat(port, 0x510); + stats->tx_packets = pm3386_get_stat(port, 0x590); + stats->rx_bytes = pm3386_get_stat(port, 0x514); + stats->tx_bytes = pm3386_get_stat(port, 0x594); + /* @@@ Add other stats. */ +} + +int pm3386_is_link_up(int port) +{ + u16 temp; + + temp = pm3386_port_reg_read(port, 0x31a, 0x100); + temp = pm3386_port_reg_read(port, 0x31a, 0x100); + + return !!(temp & 0x0002); +} + +void pm3386_enable_rx(int port) +{ + u16 temp; + + temp = pm3386_port_reg_read(port, 0x303, 0x100); + temp |= 0x1000; + pm3386_port_reg_write(port, 0x303, 0x100, temp); + + udelay(10); +} + +void pm3386_disable_rx(int port) +{ + u16 temp; + + temp = pm3386_port_reg_read(port, 0x303, 0x100); + temp &= 0xefff; + pm3386_port_reg_write(port, 0x303, 0x100, temp); + + udelay(10); +} + +void pm3386_enable_tx(int port) +{ + u16 temp; + + temp = pm3386_port_reg_read(port, 0x303, 0x100); + temp |= 0x4000; + pm3386_port_reg_write(port, 0x303, 0x100, temp); + + udelay(10); +} + +void pm3386_disable_tx(int port) +{ + u16 temp; + + temp = pm3386_port_reg_read(port, 0x303, 0x100); + temp &= 0xbfff; + pm3386_port_reg_write(port, 0x303, 0x100, temp); + + udelay(10); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ixp2000/pm3386.h b/drivers/net/ixp2000/pm3386.h new file mode 100644 index 00000000000..55ecb183421 --- /dev/null +++ b/drivers/net/ixp2000/pm3386.h @@ -0,0 +1,26 @@ +/* + * Helper functions for the PM3386s on the Radisys ENP2611 + * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org> + * Dedicated to Marija Kulikova. + * + * 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. + */ + +#ifndef __PM3386_H +#define __PM3386_H + +void pm3386_reset(void); +void pm3386_init_port(int port); +void pm3386_get_mac(int port, u8 *mac); +void pm3386_get_stats(int port, struct net_device_stats *stats); +int pm3386_is_link_up(int port); +void pm3386_enable_rx(int port); +void pm3386_disable_rx(int port); +void pm3386_enable_tx(int port); +void pm3386_disable_tx(int port); + + +#endif |