8cfd9e923b
The 8390 driver was structured by Al Viro to allow the flexibility required by platforms. lib8390.c contains the core code which drivers explicitly include: - 8390.c includes lib8390.c to provide the standard ISA based driver. - etherh.c includes it with the accessors defined for RiscPC platforms, where it is addressed via the MMIO accessors with a device dependent register spacing. Other platform drivers do something similar. However,b9a9b4b
caused the kernel to contain not only the etherh private build of lib8390 (included in etherh.c) but also lib8390.c itself, and referred the new net_device_ops methods to the ISA version. The result of this is is not pretty: Unable to handle kernel paging request at virtual address 12032030 pgd = c8330000 [12032030] *pgd=00000000 Internal error: Oops: 18331805 [#1] Modules linked in: ipv6 CPU: 0 Not tainted (2.6.29-rc3 #167) PC is at do_set_multicast_list+0xd0/0x190 LR is at bitrev32+0x28/0x34 pc : [<c017aab4>] lr : [<c0139120>] psr: a0000093 sp : c8321d9c ip : c8321d84 fp : c8321dbc r10: c80c6800 r9 : 00000000 r8 : c80c6b60 r7 : c80c6b80 r6 : cc80c800 r5 : c80c6800 r4 : 00000000 r3 : cc80c80c r2 : 00000004 r1 : 00000007 r0 : e0000000 Flags: NzCv IRQs off FIQs on Mode SVC_32 ISA ARM Segment user ... Fix upb9a9b4b
by making etherh's net_device_ops refer to the internal lib8390 functions, and remove the build of the ISA 8390.c driver. Acked-by: David S. Miller <davem@davemloft.net> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
867 lines
20 KiB
C
867 lines
20 KiB
C
/*
|
|
* linux/drivers/acorn/net/etherh.c
|
|
*
|
|
* Copyright (C) 2000-2002 Russell King
|
|
*
|
|
* 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.
|
|
*
|
|
* NS8390 I-cubed EtherH and ANT EtherM specific driver
|
|
* Thanks to I-Cubed for information on their cards.
|
|
* EtherM conversion (C) 1999 Chris Kemp and Tim Watterton
|
|
* EtherM integration (C) 2000 Aleph One Ltd (Tak-Shing Chan)
|
|
* EtherM integration re-engineered by Russell King.
|
|
*
|
|
* Changelog:
|
|
* 08-12-1996 RMK 1.00 Created
|
|
* RMK 1.03 Added support for EtherLan500 cards
|
|
* 23-11-1997 RMK 1.04 Added media autodetection
|
|
* 16-04-1998 RMK 1.05 Improved media autodetection
|
|
* 10-02-2000 RMK 1.06 Updated for 2.3.43
|
|
* 13-05-2000 RMK 1.07 Updated for 2.3.99-pre8
|
|
* 12-10-1999 CK/TEW EtherM driver first release
|
|
* 21-12-2000 TTC EtherH/EtherM integration
|
|
* 25-12-2000 RMK 1.08 Clean integration of EtherM into this driver.
|
|
* 03-01-2002 RMK 1.09 Always enable IRQs if we're in the nic slot.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/in.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <asm/system.h>
|
|
#include <asm/ecard.h>
|
|
#include <asm/io.h>
|
|
|
|
#define EI_SHIFT(x) (ei_local->reg_offset[x])
|
|
|
|
#define ei_inb(_p) readb((void __iomem *)_p)
|
|
#define ei_outb(_v,_p) writeb(_v,(void __iomem *)_p)
|
|
#define ei_inb_p(_p) readb((void __iomem *)_p)
|
|
#define ei_outb_p(_v,_p) writeb(_v,(void __iomem *)_p)
|
|
|
|
#define NET_DEBUG 0
|
|
#define DEBUG_INIT 2
|
|
|
|
#define DRV_NAME "etherh"
|
|
#define DRV_VERSION "1.11"
|
|
|
|
static char version[] __initdata =
|
|
"EtherH/EtherM Driver (c) 2002-2004 Russell King " DRV_VERSION "\n";
|
|
|
|
#include "../lib8390.c"
|
|
|
|
static unsigned int net_debug = NET_DEBUG;
|
|
|
|
struct etherh_priv {
|
|
void __iomem *ioc_fast;
|
|
void __iomem *memc;
|
|
void __iomem *dma_base;
|
|
unsigned int id;
|
|
void __iomem *ctrl_port;
|
|
unsigned char ctrl;
|
|
u32 supported;
|
|
};
|
|
|
|
struct etherh_data {
|
|
unsigned long ns8390_offset;
|
|
unsigned long dataport_offset;
|
|
unsigned long ctrlport_offset;
|
|
int ctrl_ioc;
|
|
const char name[16];
|
|
u32 supported;
|
|
unsigned char tx_start_page;
|
|
unsigned char stop_page;
|
|
};
|
|
|
|
MODULE_AUTHOR("Russell King");
|
|
MODULE_DESCRIPTION("EtherH/EtherM driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
#define ETHERH500_DATAPORT 0x800 /* MEMC */
|
|
#define ETHERH500_NS8390 0x000 /* MEMC */
|
|
#define ETHERH500_CTRLPORT 0x800 /* IOC */
|
|
|
|
#define ETHERH600_DATAPORT 0x040 /* MEMC */
|
|
#define ETHERH600_NS8390 0x800 /* MEMC */
|
|
#define ETHERH600_CTRLPORT 0x200 /* MEMC */
|
|
|
|
#define ETHERH_CP_IE 1
|
|
#define ETHERH_CP_IF 2
|
|
#define ETHERH_CP_HEARTBEAT 2
|
|
|
|
#define ETHERH_TX_START_PAGE 1
|
|
#define ETHERH_STOP_PAGE 127
|
|
|
|
/*
|
|
* These came from CK/TEW
|
|
*/
|
|
#define ETHERM_DATAPORT 0x200 /* MEMC */
|
|
#define ETHERM_NS8390 0x800 /* MEMC */
|
|
#define ETHERM_CTRLPORT 0x23c /* MEMC */
|
|
|
|
#define ETHERM_TX_START_PAGE 64
|
|
#define ETHERM_STOP_PAGE 127
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
#define etherh_priv(dev) \
|
|
((struct etherh_priv *)(((char *)netdev_priv(dev)) + sizeof(struct ei_device)))
|
|
|
|
static inline void etherh_set_ctrl(struct etherh_priv *eh, unsigned char mask)
|
|
{
|
|
unsigned char ctrl = eh->ctrl | mask;
|
|
eh->ctrl = ctrl;
|
|
writeb(ctrl, eh->ctrl_port);
|
|
}
|
|
|
|
static inline void etherh_clr_ctrl(struct etherh_priv *eh, unsigned char mask)
|
|
{
|
|
unsigned char ctrl = eh->ctrl & ~mask;
|
|
eh->ctrl = ctrl;
|
|
writeb(ctrl, eh->ctrl_port);
|
|
}
|
|
|
|
static inline unsigned int etherh_get_stat(struct etherh_priv *eh)
|
|
{
|
|
return readb(eh->ctrl_port);
|
|
}
|
|
|
|
|
|
|
|
|
|
static void etherh_irq_enable(ecard_t *ec, int irqnr)
|
|
{
|
|
struct etherh_priv *eh = ec->irq_data;
|
|
|
|
etherh_set_ctrl(eh, ETHERH_CP_IE);
|
|
}
|
|
|
|
static void etherh_irq_disable(ecard_t *ec, int irqnr)
|
|
{
|
|
struct etherh_priv *eh = ec->irq_data;
|
|
|
|
etherh_clr_ctrl(eh, ETHERH_CP_IE);
|
|
}
|
|
|
|
static expansioncard_ops_t etherh_ops = {
|
|
.irqenable = etherh_irq_enable,
|
|
.irqdisable = etherh_irq_disable,
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
etherh_setif(struct net_device *dev)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
unsigned long flags;
|
|
void __iomem *addr;
|
|
|
|
local_irq_save(flags);
|
|
|
|
/* set the interface type */
|
|
switch (etherh_priv(dev)->id) {
|
|
case PROD_I3_ETHERLAN600:
|
|
case PROD_I3_ETHERLAN600A:
|
|
addr = (void __iomem *)dev->base_addr + EN0_RCNTHI;
|
|
|
|
switch (dev->if_port) {
|
|
case IF_PORT_10BASE2:
|
|
writeb((readb(addr) & 0xf8) | 1, addr);
|
|
break;
|
|
case IF_PORT_10BASET:
|
|
writeb((readb(addr) & 0xf8), addr);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case PROD_I3_ETHERLAN500:
|
|
switch (dev->if_port) {
|
|
case IF_PORT_10BASE2:
|
|
etherh_clr_ctrl(etherh_priv(dev), ETHERH_CP_IF);
|
|
break;
|
|
|
|
case IF_PORT_10BASET:
|
|
etherh_set_ctrl(etherh_priv(dev), ETHERH_CP_IF);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
static int
|
|
etherh_getifstat(struct net_device *dev)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
void __iomem *addr;
|
|
int stat = 0;
|
|
|
|
switch (etherh_priv(dev)->id) {
|
|
case PROD_I3_ETHERLAN600:
|
|
case PROD_I3_ETHERLAN600A:
|
|
addr = (void __iomem *)dev->base_addr + EN0_RCNTHI;
|
|
switch (dev->if_port) {
|
|
case IF_PORT_10BASE2:
|
|
stat = 1;
|
|
break;
|
|
case IF_PORT_10BASET:
|
|
stat = readb(addr) & 4;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case PROD_I3_ETHERLAN500:
|
|
switch (dev->if_port) {
|
|
case IF_PORT_10BASE2:
|
|
stat = 1;
|
|
break;
|
|
case IF_PORT_10BASET:
|
|
stat = etherh_get_stat(etherh_priv(dev)) & ETHERH_CP_HEARTBEAT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
stat = 0;
|
|
break;
|
|
}
|
|
|
|
return stat != 0;
|
|
}
|
|
|
|
/*
|
|
* Configure the interface. Note that we ignore the other
|
|
* parts of ifmap, since its mostly meaningless for this driver.
|
|
*/
|
|
static int etherh_set_config(struct net_device *dev, struct ifmap *map)
|
|
{
|
|
switch (map->port) {
|
|
case IF_PORT_10BASE2:
|
|
case IF_PORT_10BASET:
|
|
/*
|
|
* If the user explicitly sets the interface
|
|
* media type, turn off automedia detection.
|
|
*/
|
|
dev->flags &= ~IFF_AUTOMEDIA;
|
|
dev->if_port = map->port;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
etherh_setif(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Reset the 8390 (hard reset). Note that we can't actually do this.
|
|
*/
|
|
static void
|
|
etherh_reset(struct net_device *dev)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
void __iomem *addr = (void __iomem *)dev->base_addr;
|
|
|
|
writeb(E8390_NODMA+E8390_PAGE0+E8390_STOP, addr);
|
|
|
|
/*
|
|
* See if we need to change the interface type.
|
|
* Note that we use 'interface_num' as a flag
|
|
* to indicate that we need to change the media.
|
|
*/
|
|
if (dev->flags & IFF_AUTOMEDIA && ei_local->interface_num) {
|
|
ei_local->interface_num = 0;
|
|
|
|
if (dev->if_port == IF_PORT_10BASET)
|
|
dev->if_port = IF_PORT_10BASE2;
|
|
else
|
|
dev->if_port = IF_PORT_10BASET;
|
|
|
|
etherh_setif(dev);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write a block of data out to the 8390
|
|
*/
|
|
static void
|
|
etherh_block_output (struct net_device *dev, int count, const unsigned char *buf, int start_page)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
unsigned long dma_start;
|
|
void __iomem *dma_base, *addr;
|
|
|
|
if (ei_local->dmaing) {
|
|
printk(KERN_ERR "%s: DMAing conflict in etherh_block_input: "
|
|
" DMAstat %d irqlock %d\n", dev->name,
|
|
ei_local->dmaing, ei_local->irqlock);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Make sure we have a round number of bytes if we're in word mode.
|
|
*/
|
|
if (count & 1 && ei_local->word16)
|
|
count++;
|
|
|
|
ei_local->dmaing = 1;
|
|
|
|
addr = (void __iomem *)dev->base_addr;
|
|
dma_base = etherh_priv(dev)->dma_base;
|
|
|
|
count = (count + 1) & ~1;
|
|
writeb (E8390_NODMA | E8390_PAGE0 | E8390_START, addr + E8390_CMD);
|
|
|
|
writeb (0x42, addr + EN0_RCNTLO);
|
|
writeb (0x00, addr + EN0_RCNTHI);
|
|
writeb (0x42, addr + EN0_RSARLO);
|
|
writeb (0x00, addr + EN0_RSARHI);
|
|
writeb (E8390_RREAD | E8390_START, addr + E8390_CMD);
|
|
|
|
udelay (1);
|
|
|
|
writeb (ENISR_RDC, addr + EN0_ISR);
|
|
writeb (count, addr + EN0_RCNTLO);
|
|
writeb (count >> 8, addr + EN0_RCNTHI);
|
|
writeb (0, addr + EN0_RSARLO);
|
|
writeb (start_page, addr + EN0_RSARHI);
|
|
writeb (E8390_RWRITE | E8390_START, addr + E8390_CMD);
|
|
|
|
if (ei_local->word16)
|
|
writesw (dma_base, buf, count >> 1);
|
|
else
|
|
writesb (dma_base, buf, count);
|
|
|
|
dma_start = jiffies;
|
|
|
|
while ((readb (addr + EN0_ISR) & ENISR_RDC) == 0)
|
|
if (time_after(jiffies, dma_start + 2*HZ/100)) { /* 20ms */
|
|
printk(KERN_ERR "%s: timeout waiting for TX RDC\n",
|
|
dev->name);
|
|
etherh_reset (dev);
|
|
__NS8390_init (dev, 1);
|
|
break;
|
|
}
|
|
|
|
writeb (ENISR_RDC, addr + EN0_ISR);
|
|
ei_local->dmaing = 0;
|
|
}
|
|
|
|
/*
|
|
* Read a block of data from the 8390
|
|
*/
|
|
static void
|
|
etherh_block_input (struct net_device *dev, int count, struct sk_buff *skb, int ring_offset)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
unsigned char *buf;
|
|
void __iomem *dma_base, *addr;
|
|
|
|
if (ei_local->dmaing) {
|
|
printk(KERN_ERR "%s: DMAing conflict in etherh_block_input: "
|
|
" DMAstat %d irqlock %d\n", dev->name,
|
|
ei_local->dmaing, ei_local->irqlock);
|
|
return;
|
|
}
|
|
|
|
ei_local->dmaing = 1;
|
|
|
|
addr = (void __iomem *)dev->base_addr;
|
|
dma_base = etherh_priv(dev)->dma_base;
|
|
|
|
buf = skb->data;
|
|
writeb (E8390_NODMA | E8390_PAGE0 | E8390_START, addr + E8390_CMD);
|
|
writeb (count, addr + EN0_RCNTLO);
|
|
writeb (count >> 8, addr + EN0_RCNTHI);
|
|
writeb (ring_offset, addr + EN0_RSARLO);
|
|
writeb (ring_offset >> 8, addr + EN0_RSARHI);
|
|
writeb (E8390_RREAD | E8390_START, addr + E8390_CMD);
|
|
|
|
if (ei_local->word16) {
|
|
readsw (dma_base, buf, count >> 1);
|
|
if (count & 1)
|
|
buf[count - 1] = readb (dma_base);
|
|
} else
|
|
readsb (dma_base, buf, count);
|
|
|
|
writeb (ENISR_RDC, addr + EN0_ISR);
|
|
ei_local->dmaing = 0;
|
|
}
|
|
|
|
/*
|
|
* Read a header from the 8390
|
|
*/
|
|
static void
|
|
etherh_get_header (struct net_device *dev, struct e8390_pkt_hdr *hdr, int ring_page)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
void __iomem *dma_base, *addr;
|
|
|
|
if (ei_local->dmaing) {
|
|
printk(KERN_ERR "%s: DMAing conflict in etherh_get_header: "
|
|
" DMAstat %d irqlock %d\n", dev->name,
|
|
ei_local->dmaing, ei_local->irqlock);
|
|
return;
|
|
}
|
|
|
|
ei_local->dmaing = 1;
|
|
|
|
addr = (void __iomem *)dev->base_addr;
|
|
dma_base = etherh_priv(dev)->dma_base;
|
|
|
|
writeb (E8390_NODMA | E8390_PAGE0 | E8390_START, addr + E8390_CMD);
|
|
writeb (sizeof (*hdr), addr + EN0_RCNTLO);
|
|
writeb (0, addr + EN0_RCNTHI);
|
|
writeb (0, addr + EN0_RSARLO);
|
|
writeb (ring_page, addr + EN0_RSARHI);
|
|
writeb (E8390_RREAD | E8390_START, addr + E8390_CMD);
|
|
|
|
if (ei_local->word16)
|
|
readsw (dma_base, hdr, sizeof (*hdr) >> 1);
|
|
else
|
|
readsb (dma_base, hdr, sizeof (*hdr));
|
|
|
|
writeb (ENISR_RDC, addr + EN0_ISR);
|
|
ei_local->dmaing = 0;
|
|
}
|
|
|
|
/*
|
|
* Open/initialize the board. This is called (in the current kernel)
|
|
* sometime after booting when the 'ifconfig' program is run.
|
|
*
|
|
* This routine should set everything up anew at each open, even
|
|
* registers that "should" only need to be set once at boot, so that
|
|
* there is non-reboot way to recover if something goes wrong.
|
|
*/
|
|
static int
|
|
etherh_open(struct net_device *dev)
|
|
{
|
|
struct ei_device *ei_local = netdev_priv(dev);
|
|
|
|
if (!is_valid_ether_addr(dev->dev_addr)) {
|
|
printk(KERN_WARNING "%s: invalid ethernet MAC address\n",
|
|
dev->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (request_irq(dev->irq, __ei_interrupt, 0, dev->name, dev))
|
|
return -EAGAIN;
|
|
|
|
/*
|
|
* Make sure that we aren't going to change the
|
|
* media type on the next reset - we are about to
|
|
* do automedia manually now.
|
|
*/
|
|
ei_local->interface_num = 0;
|
|
|
|
/*
|
|
* If we are doing automedia detection, do it now.
|
|
* This is more reliable than the 8390's detection.
|
|
*/
|
|
if (dev->flags & IFF_AUTOMEDIA) {
|
|
dev->if_port = IF_PORT_10BASET;
|
|
etherh_setif(dev);
|
|
mdelay(1);
|
|
if (!etherh_getifstat(dev)) {
|
|
dev->if_port = IF_PORT_10BASE2;
|
|
etherh_setif(dev);
|
|
}
|
|
} else
|
|
etherh_setif(dev);
|
|
|
|
etherh_reset(dev);
|
|
__ei_open(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The inverse routine to etherh_open().
|
|
*/
|
|
static int
|
|
etherh_close(struct net_device *dev)
|
|
{
|
|
__ei_close (dev);
|
|
free_irq (dev->irq, dev);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialisation
|
|
*/
|
|
|
|
static void __init etherh_banner(void)
|
|
{
|
|
static int version_printed;
|
|
|
|
if (net_debug && version_printed++ == 0)
|
|
printk(KERN_INFO "%s", version);
|
|
}
|
|
|
|
/*
|
|
* Read the ethernet address string from the on board rom.
|
|
* This is an ascii string...
|
|
*/
|
|
static int __init etherh_addr(char *addr, struct expansion_card *ec)
|
|
{
|
|
struct in_chunk_dir cd;
|
|
char *s;
|
|
|
|
if (!ecard_readchunk(&cd, ec, 0xf5, 0)) {
|
|
printk(KERN_ERR "%s: unable to read podule description string\n",
|
|
dev_name(&ec->dev));
|
|
goto no_addr;
|
|
}
|
|
|
|
s = strchr(cd.d.string, '(');
|
|
if (s) {
|
|
int i;
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
addr[i] = simple_strtoul(s + 1, &s, 0x10);
|
|
if (*s != (i == 5? ')' : ':'))
|
|
break;
|
|
}
|
|
|
|
if (i == 6)
|
|
return 0;
|
|
}
|
|
|
|
printk(KERN_ERR "%s: unable to parse MAC address: %s\n",
|
|
dev_name(&ec->dev), cd.d.string);
|
|
|
|
no_addr:
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* Create an ethernet address from the system serial number.
|
|
*/
|
|
static int __init etherm_addr(char *addr)
|
|
{
|
|
unsigned int serial;
|
|
|
|
if (system_serial_low == 0 && system_serial_high == 0)
|
|
return -ENODEV;
|
|
|
|
serial = system_serial_low | system_serial_high;
|
|
|
|
addr[0] = 0;
|
|
addr[1] = 0;
|
|
addr[2] = 0xa4;
|
|
addr[3] = 0x10 + (serial >> 24);
|
|
addr[4] = serial >> 16;
|
|
addr[5] = serial >> 8;
|
|
return 0;
|
|
}
|
|
|
|
static void etherh_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
|
|
{
|
|
strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
|
|
strlcpy(info->version, DRV_VERSION, sizeof(info->version));
|
|
strlcpy(info->bus_info, dev_name(dev->dev.parent),
|
|
sizeof(info->bus_info));
|
|
}
|
|
|
|
static int etherh_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
cmd->supported = etherh_priv(dev)->supported;
|
|
cmd->speed = SPEED_10;
|
|
cmd->duplex = DUPLEX_HALF;
|
|
cmd->port = dev->if_port == IF_PORT_10BASET ? PORT_TP : PORT_BNC;
|
|
cmd->autoneg = dev->flags & IFF_AUTOMEDIA ? AUTONEG_ENABLE : AUTONEG_DISABLE;
|
|
return 0;
|
|
}
|
|
|
|
static int etherh_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
switch (cmd->autoneg) {
|
|
case AUTONEG_ENABLE:
|
|
dev->flags |= IFF_AUTOMEDIA;
|
|
break;
|
|
|
|
case AUTONEG_DISABLE:
|
|
switch (cmd->port) {
|
|
case PORT_TP:
|
|
dev->if_port = IF_PORT_10BASET;
|
|
break;
|
|
|
|
case PORT_BNC:
|
|
dev->if_port = IF_PORT_10BASE2;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
dev->flags &= ~IFF_AUTOMEDIA;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
etherh_setif(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct ethtool_ops etherh_ethtool_ops = {
|
|
.get_settings = etherh_get_settings,
|
|
.set_settings = etherh_set_settings,
|
|
.get_drvinfo = etherh_get_drvinfo,
|
|
};
|
|
|
|
static const struct net_device_ops etherh_netdev_ops = {
|
|
.ndo_open = etherh_open,
|
|
.ndo_stop = etherh_close,
|
|
.ndo_set_config = etherh_set_config,
|
|
.ndo_start_xmit = __ei_start_xmit,
|
|
.ndo_tx_timeout = __ei_tx_timeout,
|
|
.ndo_get_stats = __ei_get_stats,
|
|
.ndo_set_multicast_list = __ei_set_multicast_list,
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
.ndo_set_mac_address = eth_mac_addr,
|
|
.ndo_change_mtu = eth_change_mtu,
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
.ndo_poll_controller = __ei_poll,
|
|
#endif
|
|
};
|
|
|
|
static u32 etherh_regoffsets[16];
|
|
static u32 etherm_regoffsets[16];
|
|
|
|
static int __init
|
|
etherh_probe(struct expansion_card *ec, const struct ecard_id *id)
|
|
{
|
|
const struct etherh_data *data = id->data;
|
|
struct ei_device *ei_local;
|
|
struct net_device *dev;
|
|
struct etherh_priv *eh;
|
|
int ret;
|
|
|
|
etherh_banner();
|
|
|
|
ret = ecard_request_resources(ec);
|
|
if (ret)
|
|
goto out;
|
|
|
|
dev = ____alloc_ei_netdev(sizeof(struct etherh_priv));
|
|
if (!dev) {
|
|
ret = -ENOMEM;
|
|
goto release;
|
|
}
|
|
|
|
SET_NETDEV_DEV(dev, &ec->dev);
|
|
|
|
dev->netdev_ops = ðerh_netdev_ops;
|
|
dev->irq = ec->irq;
|
|
dev->ethtool_ops = ðerh_ethtool_ops;
|
|
|
|
if (data->supported & SUPPORTED_Autoneg)
|
|
dev->flags |= IFF_AUTOMEDIA;
|
|
if (data->supported & SUPPORTED_TP) {
|
|
dev->flags |= IFF_PORTSEL;
|
|
dev->if_port = IF_PORT_10BASET;
|
|
} else if (data->supported & SUPPORTED_BNC) {
|
|
dev->flags |= IFF_PORTSEL;
|
|
dev->if_port = IF_PORT_10BASE2;
|
|
} else
|
|
dev->if_port = IF_PORT_UNKNOWN;
|
|
|
|
eh = etherh_priv(dev);
|
|
eh->supported = data->supported;
|
|
eh->ctrl = 0;
|
|
eh->id = ec->cid.product;
|
|
eh->memc = ecardm_iomap(ec, ECARD_RES_MEMC, 0, PAGE_SIZE);
|
|
if (!eh->memc) {
|
|
ret = -ENOMEM;
|
|
goto free;
|
|
}
|
|
|
|
eh->ctrl_port = eh->memc;
|
|
if (data->ctrl_ioc) {
|
|
eh->ioc_fast = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, PAGE_SIZE);
|
|
if (!eh->ioc_fast) {
|
|
ret = -ENOMEM;
|
|
goto free;
|
|
}
|
|
eh->ctrl_port = eh->ioc_fast;
|
|
}
|
|
|
|
dev->base_addr = (unsigned long)eh->memc + data->ns8390_offset;
|
|
eh->dma_base = eh->memc + data->dataport_offset;
|
|
eh->ctrl_port += data->ctrlport_offset;
|
|
|
|
/*
|
|
* IRQ and control port handling - only for non-NIC slot cards.
|
|
*/
|
|
if (ec->slot_no != 8) {
|
|
ecard_setirq(ec, ðerh_ops, eh);
|
|
} else {
|
|
/*
|
|
* If we're in the NIC slot, make sure the IRQ is enabled
|
|
*/
|
|
etherh_set_ctrl(eh, ETHERH_CP_IE);
|
|
}
|
|
|
|
ei_local = netdev_priv(dev);
|
|
spin_lock_init(&ei_local->page_lock);
|
|
|
|
if (ec->cid.product == PROD_ANT_ETHERM) {
|
|
etherm_addr(dev->dev_addr);
|
|
ei_local->reg_offset = etherm_regoffsets;
|
|
} else {
|
|
etherh_addr(dev->dev_addr, ec);
|
|
ei_local->reg_offset = etherh_regoffsets;
|
|
}
|
|
|
|
ei_local->name = dev->name;
|
|
ei_local->word16 = 1;
|
|
ei_local->tx_start_page = data->tx_start_page;
|
|
ei_local->rx_start_page = ei_local->tx_start_page + TX_PAGES;
|
|
ei_local->stop_page = data->stop_page;
|
|
ei_local->reset_8390 = etherh_reset;
|
|
ei_local->block_input = etherh_block_input;
|
|
ei_local->block_output = etherh_block_output;
|
|
ei_local->get_8390_hdr = etherh_get_header;
|
|
ei_local->interface_num = 0;
|
|
|
|
etherh_reset(dev);
|
|
__NS8390_init(dev, 0);
|
|
|
|
ret = register_netdev(dev);
|
|
if (ret)
|
|
goto free;
|
|
|
|
printk(KERN_INFO "%s: %s in slot %d, %pM\n",
|
|
dev->name, data->name, ec->slot_no, dev->dev_addr);
|
|
|
|
ecard_set_drvdata(ec, dev);
|
|
|
|
return 0;
|
|
|
|
free:
|
|
free_netdev(dev);
|
|
release:
|
|
ecard_release_resources(ec);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static void __devexit etherh_remove(struct expansion_card *ec)
|
|
{
|
|
struct net_device *dev = ecard_get_drvdata(ec);
|
|
|
|
ecard_set_drvdata(ec, NULL);
|
|
|
|
unregister_netdev(dev);
|
|
|
|
free_netdev(dev);
|
|
|
|
ecard_release_resources(ec);
|
|
}
|
|
|
|
static struct etherh_data etherm_data = {
|
|
.ns8390_offset = ETHERM_NS8390,
|
|
.dataport_offset = ETHERM_NS8390 + ETHERM_DATAPORT,
|
|
.ctrlport_offset = ETHERM_NS8390 + ETHERM_CTRLPORT,
|
|
.name = "ANT EtherM",
|
|
.supported = SUPPORTED_10baseT_Half,
|
|
.tx_start_page = ETHERM_TX_START_PAGE,
|
|
.stop_page = ETHERM_STOP_PAGE,
|
|
};
|
|
|
|
static struct etherh_data etherlan500_data = {
|
|
.ns8390_offset = ETHERH500_NS8390,
|
|
.dataport_offset = ETHERH500_NS8390 + ETHERH500_DATAPORT,
|
|
.ctrlport_offset = ETHERH500_CTRLPORT,
|
|
.ctrl_ioc = 1,
|
|
.name = "i3 EtherH 500",
|
|
.supported = SUPPORTED_10baseT_Half,
|
|
.tx_start_page = ETHERH_TX_START_PAGE,
|
|
.stop_page = ETHERH_STOP_PAGE,
|
|
};
|
|
|
|
static struct etherh_data etherlan600_data = {
|
|
.ns8390_offset = ETHERH600_NS8390,
|
|
.dataport_offset = ETHERH600_NS8390 + ETHERH600_DATAPORT,
|
|
.ctrlport_offset = ETHERH600_NS8390 + ETHERH600_CTRLPORT,
|
|
.name = "i3 EtherH 600",
|
|
.supported = SUPPORTED_10baseT_Half | SUPPORTED_TP | SUPPORTED_BNC | SUPPORTED_Autoneg,
|
|
.tx_start_page = ETHERH_TX_START_PAGE,
|
|
.stop_page = ETHERH_STOP_PAGE,
|
|
};
|
|
|
|
static struct etherh_data etherlan600a_data = {
|
|
.ns8390_offset = ETHERH600_NS8390,
|
|
.dataport_offset = ETHERH600_NS8390 + ETHERH600_DATAPORT,
|
|
.ctrlport_offset = ETHERH600_NS8390 + ETHERH600_CTRLPORT,
|
|
.name = "i3 EtherH 600A",
|
|
.supported = SUPPORTED_10baseT_Half | SUPPORTED_TP | SUPPORTED_BNC | SUPPORTED_Autoneg,
|
|
.tx_start_page = ETHERH_TX_START_PAGE,
|
|
.stop_page = ETHERH_STOP_PAGE,
|
|
};
|
|
|
|
static const struct ecard_id etherh_ids[] = {
|
|
{ MANU_ANT, PROD_ANT_ETHERM, ðerm_data },
|
|
{ MANU_I3, PROD_I3_ETHERLAN500, ðerlan500_data },
|
|
{ MANU_I3, PROD_I3_ETHERLAN600, ðerlan600_data },
|
|
{ MANU_I3, PROD_I3_ETHERLAN600A, ðerlan600a_data },
|
|
{ 0xffff, 0xffff }
|
|
};
|
|
|
|
static struct ecard_driver etherh_driver = {
|
|
.probe = etherh_probe,
|
|
.remove = __devexit_p(etherh_remove),
|
|
.id_table = etherh_ids,
|
|
.drv = {
|
|
.name = DRV_NAME,
|
|
},
|
|
};
|
|
|
|
static int __init etherh_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
etherh_regoffsets[i] = i << 2;
|
|
etherm_regoffsets[i] = i << 5;
|
|
}
|
|
|
|
return ecard_register_driver(ðerh_driver);
|
|
}
|
|
|
|
static void __exit etherh_exit(void)
|
|
{
|
|
ecard_remove_driver(ðerh_driver);
|
|
}
|
|
|
|
module_init(etherh_init);
|
|
module_exit(etherh_exit);
|