2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* smc91x.c
|
|
|
|
* This is a driver for SMSC's 91C9x/91C1xx single-chip Ethernet devices.
|
|
|
|
*
|
|
|
|
* Copyright (C) 1996 by Erik Stahlman
|
|
|
|
* Copyright (C) 2001 Standard Microsystems Corporation
|
|
|
|
* Developed by Simple Network Magic Corporation
|
|
|
|
* Copyright (C) 2003 Monta Vista Software, Inc.
|
|
|
|
* Unified SMC91x driver by Nicolas Pitre
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*
|
|
|
|
* Arguments:
|
|
|
|
* io = for the base address
|
|
|
|
* irq = for the IRQ
|
|
|
|
* nowait = 0 for normal wait states, 1 eliminates additional wait states
|
|
|
|
*
|
|
|
|
* original author:
|
|
|
|
* Erik Stahlman <erik@vt.edu>
|
|
|
|
*
|
|
|
|
* hardware multicast code:
|
|
|
|
* Peter Cammaert <pc@denkart.be>
|
|
|
|
*
|
|
|
|
* contributors:
|
|
|
|
* Daris A Nevil <dnevil@snmc.com>
|
2009-09-14 15:25:28 +08:00
|
|
|
* Nicolas Pitre <nico@fluxnic.net>
|
2005-04-17 06:20:36 +08:00
|
|
|
* Russell King <rmk@arm.linux.org.uk>
|
|
|
|
*
|
|
|
|
* History:
|
|
|
|
* 08/20/00 Arnaldo Melo fix kfree(skb) in smc_hardware_send_packet
|
|
|
|
* 12/15/00 Christian Jullien fix "Warning: kfree_skb on hard IRQ"
|
|
|
|
* 03/16/01 Daris A Nevil modified smc9194.c for use with LAN91C111
|
|
|
|
* 08/22/01 Scott Anderson merge changes from smc9194 to smc91111
|
|
|
|
* 08/21/01 Pramod B Bhardwaj added support for RevB of LAN91C111
|
|
|
|
* 12/20/01 Jeff Sutherland initial port to Xscale PXA with DMA support
|
|
|
|
* 04/07/03 Nicolas Pitre unified SMC91x driver, killed irq races,
|
|
|
|
* more bus abstraction, big cleanup, etc.
|
|
|
|
* 29/09/03 Russell King - add driver model support
|
|
|
|
* - ethtool support
|
|
|
|
* - convert to use generic MII interface
|
|
|
|
* - add link up/down notification
|
|
|
|
* - don't try to handle full negotiation in
|
|
|
|
* smc_phy_configure
|
|
|
|
* - clean up (and fix stack overrun) in PHY
|
|
|
|
* MII read/write functions
|
|
|
|
* 22/09/04 Nicolas Pitre big update (see commit log for details)
|
|
|
|
*/
|
|
|
|
static const char version[] =
|
2009-09-14 15:25:28 +08:00
|
|
|
"smc91x.c: v1.1, sep 22 2004 by Nicolas Pitre <nico@fluxnic.net>\n";
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Debugging level */
|
|
|
|
#ifndef SMC_DEBUG
|
|
|
|
#define SMC_DEBUG 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/ioport.h>
|
|
|
|
#include <linux/crc32.h>
|
2005-10-30 02:07:23 +08:00
|
|
|
#include <linux/platform_device.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/ethtool.h>
|
|
|
|
#include <linux/mii.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
|
|
|
|
|
#include "smc91x.h"
|
|
|
|
|
|
|
|
#ifndef SMC_NOWAIT
|
|
|
|
# define SMC_NOWAIT 0
|
|
|
|
#endif
|
|
|
|
static int nowait = SMC_NOWAIT;
|
|
|
|
module_param(nowait, int, 0400);
|
|
|
|
MODULE_PARM_DESC(nowait, "set to 1 for no wait state");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Transmit timeout, default 5 seconds.
|
|
|
|
*/
|
2005-04-13 04:26:40 +08:00
|
|
|
static int watchdog = 1000;
|
2005-04-17 06:20:36 +08:00
|
|
|
module_param(watchdog, int, 0400);
|
|
|
|
MODULE_PARM_DESC(watchdog, "transmit timeout in milliseconds");
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
2008-04-19 04:50:44 +08:00
|
|
|
MODULE_ALIAS("platform:smc91x");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The internal workings of the driver. If you are changing anything
|
|
|
|
* here with the SMC stuff, you should have the datasheet and know
|
|
|
|
* what you are doing.
|
|
|
|
*/
|
|
|
|
#define CARDNAME "smc91x"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use power-down feature of the chip
|
|
|
|
*/
|
|
|
|
#define POWER_DOWN 1
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wait time for memory to be free. This probably shouldn't be
|
|
|
|
* tuned that much, as waiting for this means nothing else happens
|
|
|
|
* in the system
|
|
|
|
*/
|
|
|
|
#define MEMORY_WAIT_TIME 16
|
|
|
|
|
2005-11-18 03:02:48 +08:00
|
|
|
/*
|
|
|
|
* The maximum number of processing loops allowed for each call to the
|
2006-09-14 01:24:59 +08:00
|
|
|
* IRQ handler.
|
2005-11-18 03:02:48 +08:00
|
|
|
*/
|
|
|
|
#define MAX_IRQ_LOOPS 8
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* This selects whether TX packets are sent one by one to the SMC91x internal
|
|
|
|
* memory and throttled until transmission completes. This may prevent
|
|
|
|
* RX overruns a litle by keeping much of the memory free for RX packets
|
|
|
|
* but to the expense of reduced TX throughput and increased IRQ overhead.
|
|
|
|
* Note this is not a cure for a too slow data bus or too high IRQ latency.
|
|
|
|
*/
|
|
|
|
#define THROTTLE_TX_PKTS 0
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The MII clock high/low times. 2x this number gives the MII clock period
|
|
|
|
* in microseconds. (was 50, but this gives 6.4ms for each MII transaction!)
|
|
|
|
*/
|
|
|
|
#define MII_DELAY 1
|
|
|
|
|
|
|
|
#if SMC_DEBUG > 0
|
|
|
|
#define DBG(n, args...) \
|
|
|
|
do { \
|
|
|
|
if (SMC_DEBUG >= (n)) \
|
|
|
|
printk(args); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define PRINTK(args...) printk(args)
|
|
|
|
#else
|
|
|
|
#define DBG(n, args...) do { } while(0)
|
|
|
|
#define PRINTK(args...) printk(KERN_DEBUG args)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if SMC_DEBUG > 3
|
|
|
|
static void PRINT_PKT(u_char *buf, int length)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int remainder;
|
|
|
|
int lines;
|
|
|
|
|
|
|
|
lines = length / 16;
|
|
|
|
remainder = length % 16;
|
|
|
|
|
|
|
|
for (i = 0; i < lines ; i ++) {
|
|
|
|
int cur;
|
|
|
|
for (cur = 0; cur < 8; cur++) {
|
|
|
|
u_char a, b;
|
|
|
|
a = *buf++;
|
|
|
|
b = *buf++;
|
|
|
|
printk("%02x%02x ", a, b);
|
|
|
|
}
|
|
|
|
printk("\n");
|
|
|
|
}
|
|
|
|
for (i = 0; i < remainder/2 ; i++) {
|
|
|
|
u_char a, b;
|
|
|
|
a = *buf++;
|
|
|
|
b = *buf++;
|
|
|
|
printk("%02x%02x ", a, b);
|
|
|
|
}
|
|
|
|
printk("\n");
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define PRINT_PKT(x...) do { } while(0)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/* this enables an interrupt in the interrupt mask register */
|
2008-02-22 18:55:05 +08:00
|
|
|
#define SMC_ENABLE_INT(lp, x) do { \
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned char mask; \
|
2009-08-24 13:59:04 +08:00
|
|
|
unsigned long smc_enable_flags; \
|
|
|
|
spin_lock_irqsave(&lp->lock, smc_enable_flags); \
|
2008-02-22 18:55:05 +08:00
|
|
|
mask = SMC_GET_INT_MASK(lp); \
|
2005-04-17 06:20:36 +08:00
|
|
|
mask |= (x); \
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_INT_MASK(lp, mask); \
|
2009-08-24 13:59:04 +08:00
|
|
|
spin_unlock_irqrestore(&lp->lock, smc_enable_flags); \
|
2005-04-17 06:20:36 +08:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* this disables an interrupt from the interrupt mask register */
|
2008-02-22 18:55:05 +08:00
|
|
|
#define SMC_DISABLE_INT(lp, x) do { \
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned char mask; \
|
2009-08-24 13:59:04 +08:00
|
|
|
unsigned long smc_disable_flags; \
|
|
|
|
spin_lock_irqsave(&lp->lock, smc_disable_flags); \
|
2008-02-22 18:55:05 +08:00
|
|
|
mask = SMC_GET_INT_MASK(lp); \
|
2005-04-17 06:20:36 +08:00
|
|
|
mask &= ~(x); \
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_INT_MASK(lp, mask); \
|
2009-08-24 13:59:04 +08:00
|
|
|
spin_unlock_irqrestore(&lp->lock, smc_disable_flags); \
|
2005-04-17 06:20:36 +08:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wait while MMU is busy. This is usually in the order of a few nanosecs
|
|
|
|
* if at all, but let's avoid deadlocking the system if the hardware
|
|
|
|
* decides to go south.
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
#define SMC_WAIT_MMU_BUSY(lp) do { \
|
|
|
|
if (unlikely(SMC_GET_MMU_CMD(lp) & MC_BUSY)) { \
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned long timeout = jiffies + 2; \
|
2008-02-22 18:55:05 +08:00
|
|
|
while (SMC_GET_MMU_CMD(lp) & MC_BUSY) { \
|
2005-04-17 06:20:36 +08:00
|
|
|
if (time_after(jiffies, timeout)) { \
|
|
|
|
printk("%s: timeout %s line %d\n", \
|
|
|
|
dev->name, __FILE__, __LINE__); \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
cpu_relax(); \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this does a soft reset on the device
|
|
|
|
*/
|
|
|
|
static void smc_reset(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int ctl, cfg;
|
2005-06-20 11:56:21 +08:00
|
|
|
struct sk_buff *pending_skb;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-06-20 11:56:21 +08:00
|
|
|
/* Disable all interrupts, block TX tasklet */
|
2006-08-15 14:00:20 +08:00
|
|
|
spin_lock_irq(&lp->lock);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_INT_MASK(lp, 0);
|
2005-06-20 11:56:21 +08:00
|
|
|
pending_skb = lp->pending_tx_skb;
|
|
|
|
lp->pending_tx_skb = NULL;
|
2006-08-15 14:00:20 +08:00
|
|
|
spin_unlock_irq(&lp->lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-06-20 11:56:21 +08:00
|
|
|
/* free any pending tx skb */
|
|
|
|
if (pending_skb) {
|
|
|
|
dev_kfree_skb(pending_skb);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_errors++;
|
|
|
|
dev->stats.tx_aborted_errors++;
|
2005-06-20 11:56:21 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* This resets the registers mostly to defaults, but doesn't
|
|
|
|
* affect EEPROM. That seems unnecessary
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_RCR(lp, RCR_SOFTRST);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup the Configuration Register
|
|
|
|
* This is necessary because the CONFIG_REG is not affected
|
|
|
|
* by a soft reset
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
cfg = CONFIG_DEFAULT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup for fast accesses if requested. If the card/system
|
|
|
|
* can't handle it then there will be no recovery except for
|
|
|
|
* a hard reset or power cycle
|
|
|
|
*/
|
2008-06-19 17:39:03 +08:00
|
|
|
if (lp->cfg.flags & SMC91X_NOWAIT)
|
2005-04-17 06:20:36 +08:00
|
|
|
cfg |= CONFIG_NO_WAIT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release from possible power-down state
|
|
|
|
* Configuration register is not affected by Soft Reset
|
|
|
|
*/
|
|
|
|
cfg |= CONFIG_EPH_POWER_EN;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_CONFIG(lp, cfg);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* this should pause enough for the chip to be happy */
|
|
|
|
/*
|
|
|
|
* elaborate? What does the chip _need_? --jgarzik
|
|
|
|
*
|
|
|
|
* This seems to be undocumented, but something the original
|
|
|
|
* driver(s) have always done. Suspect undocumented timing
|
|
|
|
* info/determined empirically. --rmk
|
|
|
|
*/
|
|
|
|
udelay(1);
|
|
|
|
|
|
|
|
/* Disable transmit and receive functionality */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_RCR(lp, RCR_CLEAR);
|
|
|
|
SMC_SET_TCR(lp, TCR_CLEAR);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
ctl = SMC_GET_CTL(lp) | CTL_LE_ENABLE;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the control register to automatically release successfully
|
|
|
|
* transmitted packets, to make the best use out of our limited
|
|
|
|
* memory
|
|
|
|
*/
|
|
|
|
if(!THROTTLE_TX_PKTS)
|
|
|
|
ctl |= CTL_AUTO_RELEASE;
|
|
|
|
else
|
|
|
|
ctl &= ~CTL_AUTO_RELEASE;
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_CTL(lp, ctl);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Reset the MMU */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_MMU_CMD(lp, MC_RESET);
|
|
|
|
SMC_WAIT_MMU_BUSY(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Enable Interrupts, Receive, and Transmit
|
|
|
|
*/
|
|
|
|
static void smc_enable(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
int mask;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* see the header file for options in TCR/RCR DEFAULT */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_TCR(lp, lp->tcr_cur_mode);
|
|
|
|
SMC_SET_RCR(lp, lp->rcr_cur_mode);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
SMC_SET_MAC_ADDR(lp, dev->dev_addr);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* now, enable interrupts */
|
|
|
|
mask = IM_EPH_INT|IM_RX_OVRN_INT|IM_RCV_INT;
|
|
|
|
if (lp->version >= (CHIP_91100 << 4))
|
|
|
|
mask |= IM_MDINT;
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_INT_MASK(lp, mask);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* From this point the register bank must _NOT_ be switched away
|
|
|
|
* to something else than bank 2 without proper locking against
|
|
|
|
* races with any tasklet or interrupt handlers until smc_shutdown()
|
|
|
|
* or smc_reset() is called.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this puts the device in an inactive state
|
|
|
|
*/
|
|
|
|
static void smc_shutdown(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
2005-06-20 11:56:21 +08:00
|
|
|
struct sk_buff *pending_skb;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", CARDNAME, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* no more interrupts for me */
|
2006-08-15 14:00:20 +08:00
|
|
|
spin_lock_irq(&lp->lock);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_INT_MASK(lp, 0);
|
2005-06-20 11:56:21 +08:00
|
|
|
pending_skb = lp->pending_tx_skb;
|
|
|
|
lp->pending_tx_skb = NULL;
|
2006-08-15 14:00:20 +08:00
|
|
|
spin_unlock_irq(&lp->lock);
|
2005-06-20 11:56:21 +08:00
|
|
|
if (pending_skb)
|
|
|
|
dev_kfree_skb(pending_skb);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* and tell the card to stay away from that nasty outside world */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_RCR(lp, RCR_CLEAR);
|
|
|
|
SMC_SET_TCR(lp, TCR_CLEAR);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
#ifdef POWER_DOWN
|
|
|
|
/* finally, shut the chip down */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
SMC_SET_CONFIG(lp, SMC_GET_CONFIG(lp) & ~CONFIG_EPH_POWER_EN);
|
2005-04-17 06:20:36 +08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is the procedure to handle the receipt of a packet.
|
|
|
|
*/
|
|
|
|
static inline void smc_rcv(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int packet_number, status, packet_len;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(3, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
packet_number = SMC_GET_RXFIFO(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (unlikely(packet_number & RXFIFO_REMPTY)) {
|
|
|
|
PRINTK("%s: smc_rcv with nothing on FIFO.\n", dev->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read from start of packet */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_PTR(lp, PTR_READ | PTR_RCV | PTR_AUTOINC);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* First two words are status and packet length */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_GET_PKT_HDR(lp, status, packet_len);
|
2005-04-17 06:20:36 +08:00
|
|
|
packet_len &= 0x07ff; /* mask off top bits */
|
|
|
|
DBG(2, "%s: RX PNR 0x%x STATUS 0x%04x LENGTH 0x%04x (%d)\n",
|
|
|
|
dev->name, packet_number, status,
|
|
|
|
packet_len, packet_len);
|
|
|
|
|
|
|
|
back:
|
|
|
|
if (unlikely(packet_len < 6 || status & RS_ERRORS)) {
|
|
|
|
if (status & RS_TOOLONG && packet_len <= (1514 + 4 + 6)) {
|
|
|
|
/* accept VLAN packets */
|
|
|
|
status &= ~RS_TOOLONG;
|
|
|
|
goto back;
|
|
|
|
}
|
|
|
|
if (packet_len < 6) {
|
|
|
|
/* bloody hardware */
|
|
|
|
printk(KERN_ERR "%s: fubar (rxlen %u status %x\n",
|
|
|
|
dev->name, packet_len, status);
|
|
|
|
status |= RS_TOOSHORT;
|
|
|
|
}
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_WAIT_MMU_BUSY(lp);
|
|
|
|
SMC_SET_MMU_CMD(lp, MC_RELEASE);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (status & RS_ALGNERR)
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_frame_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (status & (RS_TOOSHORT | RS_TOOLONG))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_length_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (status & RS_BADCRC)
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_crc_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
struct sk_buff *skb;
|
|
|
|
unsigned char *data;
|
|
|
|
unsigned int data_len;
|
|
|
|
|
|
|
|
/* set multicast stats */
|
|
|
|
if (status & RS_MULTICAST)
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.multicast++;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Actual payload is packet_len - 6 (or 5 if odd byte).
|
|
|
|
* We want skb_reserve(2) and the final ctrl word
|
|
|
|
* (2 bytes, possibly containing the payload odd byte).
|
|
|
|
* Furthermore, we add 2 bytes to allow rounding up to
|
|
|
|
* multiple of 4 bytes on 32 bit buses.
|
|
|
|
* Hence packet_len - 6 + 2 + 2 + 2.
|
|
|
|
*/
|
|
|
|
skb = dev_alloc_skb(packet_len);
|
|
|
|
if (unlikely(skb == NULL)) {
|
|
|
|
printk(KERN_NOTICE "%s: Low memory, packet dropped.\n",
|
|
|
|
dev->name);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_WAIT_MMU_BUSY(lp);
|
|
|
|
SMC_SET_MMU_CMD(lp, MC_RELEASE);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_dropped++;
|
2005-04-17 06:20:36 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Align IP header to 32 bits */
|
|
|
|
skb_reserve(skb, 2);
|
|
|
|
|
|
|
|
/* BUG: the LAN91C111 rev A never sets this bit. Force it. */
|
|
|
|
if (lp->version == 0x90)
|
|
|
|
status |= RS_ODDFRAME;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If odd length: packet_len - 5,
|
|
|
|
* otherwise packet_len - 6.
|
|
|
|
* With the trailing ctrl byte it's packet_len - 4.
|
|
|
|
*/
|
|
|
|
data_len = packet_len - ((status & RS_ODDFRAME) ? 5 : 6);
|
|
|
|
data = skb_put(skb, data_len);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_PULL_DATA(lp, data, packet_len - 4);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_WAIT_MMU_BUSY(lp);
|
|
|
|
SMC_SET_MMU_CMD(lp, MC_RELEASE);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
PRINT_PKT(data, packet_len - 4);
|
|
|
|
|
|
|
|
skb->protocol = eth_type_trans(skb, dev);
|
|
|
|
netif_rx(skb);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_packets++;
|
|
|
|
dev->stats.rx_bytes += data_len;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
/*
|
|
|
|
* On SMP we have the following problem:
|
|
|
|
*
|
|
|
|
* A = smc_hardware_send_pkt()
|
|
|
|
* B = smc_hard_start_xmit()
|
|
|
|
* C = smc_interrupt()
|
|
|
|
*
|
|
|
|
* A and B can never be executed simultaneously. However, at least on UP,
|
|
|
|
* it is possible (and even desirable) for C to interrupt execution of
|
|
|
|
* A or B in order to have better RX reliability and avoid overruns.
|
|
|
|
* C, just like A and B, must have exclusive access to the chip and
|
|
|
|
* each of them must lock against any other concurrent access.
|
|
|
|
* Unfortunately this is not possible to have C suspend execution of A or
|
|
|
|
* B taking place on another CPU. On UP this is no an issue since A and B
|
|
|
|
* are run from softirq context and C from hard IRQ context, and there is
|
|
|
|
* no other CPU where concurrent access can happen.
|
|
|
|
* If ever there is a way to force at least B and C to always be executed
|
|
|
|
* on the same CPU then we could use read/write locks to protect against
|
|
|
|
* any other concurrent access and C would always interrupt B. But life
|
|
|
|
* isn't that easy in a SMP world...
|
|
|
|
*/
|
2009-08-24 13:59:04 +08:00
|
|
|
#define smc_special_trylock(lock, flags) \
|
2005-04-17 06:20:36 +08:00
|
|
|
({ \
|
|
|
|
int __ret; \
|
2009-08-24 13:59:04 +08:00
|
|
|
local_irq_save(flags); \
|
2005-04-17 06:20:36 +08:00
|
|
|
__ret = spin_trylock(lock); \
|
|
|
|
if (!__ret) \
|
2009-08-24 13:59:04 +08:00
|
|
|
local_irq_restore(flags); \
|
2005-04-17 06:20:36 +08:00
|
|
|
__ret; \
|
|
|
|
})
|
2009-08-27 03:03:35 +08:00
|
|
|
#define smc_special_lock(lock, flags) spin_lock_irqsave(lock, flags)
|
2009-08-24 13:59:04 +08:00
|
|
|
#define smc_special_unlock(lock, flags) spin_unlock_irqrestore(lock, flags)
|
2005-04-17 06:20:36 +08:00
|
|
|
#else
|
2009-08-24 13:59:04 +08:00
|
|
|
#define smc_special_trylock(lock, flags) (1)
|
|
|
|
#define smc_special_lock(lock, flags) do { } while (0)
|
|
|
|
#define smc_special_unlock(lock, flags) do { } while (0)
|
2005-04-17 06:20:36 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is called to actually send a packet to the chip.
|
|
|
|
*/
|
|
|
|
static void smc_hardware_send_pkt(unsigned long data)
|
|
|
|
{
|
|
|
|
struct net_device *dev = (struct net_device *)data;
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
unsigned int packet_no, len;
|
|
|
|
unsigned char *buf;
|
2009-08-24 13:59:04 +08:00
|
|
|
unsigned long flags;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(3, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2009-08-24 13:59:04 +08:00
|
|
|
if (!smc_special_trylock(&lp->lock, flags)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
netif_stop_queue(dev);
|
|
|
|
tasklet_schedule(&lp->tx_task);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
skb = lp->pending_tx_skb;
|
2005-06-20 11:56:21 +08:00
|
|
|
if (unlikely(!skb)) {
|
2009-08-24 13:59:04 +08:00
|
|
|
smc_special_unlock(&lp->lock, flags);
|
2005-06-20 11:56:21 +08:00
|
|
|
return;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->pending_tx_skb = NULL;
|
2005-06-20 11:56:21 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
packet_no = SMC_GET_AR(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (unlikely(packet_no & AR_FAILED)) {
|
|
|
|
printk("%s: Memory allocation failed.\n", dev->name);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_errors++;
|
|
|
|
dev->stats.tx_fifo_errors++;
|
2009-08-24 13:59:04 +08:00
|
|
|
smc_special_unlock(&lp->lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* point to the beginning of the packet */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_PN(lp, packet_no);
|
|
|
|
SMC_SET_PTR(lp, PTR_AUTOINC);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
buf = skb->data;
|
|
|
|
len = skb->len;
|
|
|
|
DBG(2, "%s: TX PNR 0x%x LENGTH 0x%04x (%d) BUF 0x%p\n",
|
|
|
|
dev->name, packet_no, len, len, buf);
|
|
|
|
PRINT_PKT(buf, len);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send the packet length (+6 for status words, length, and ctl.
|
|
|
|
* The card will pad to 64 bytes with zeroes if packet is too small.
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_PUT_PKT_HDR(lp, 0, len + 6);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* send the actual data */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_PUSH_DATA(lp, buf, len & ~1);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Send final ctl word with the last byte if there is one */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_outw(((len & 1) ? (0x2000 | buf[len-1]) : 0), ioaddr, DATA_REG(lp));
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
2005-04-13 04:26:40 +08:00
|
|
|
* If THROTTLE_TX_PKTS is set, we stop the queue here. This will
|
|
|
|
* have the effect of having at most one packet queued for TX
|
|
|
|
* in the chip's memory at all time.
|
|
|
|
*
|
|
|
|
* If THROTTLE_TX_PKTS is not set then the queue is stopped only
|
|
|
|
* when memory allocation (MC_ALLOC) does not succeed right away.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-04-13 04:26:40 +08:00
|
|
|
if (THROTTLE_TX_PKTS)
|
2005-04-17 06:20:36 +08:00
|
|
|
netif_stop_queue(dev);
|
|
|
|
|
|
|
|
/* queue the packet for TX */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MMU_CMD(lp, MC_ENQUEUE);
|
2009-08-24 13:59:04 +08:00
|
|
|
smc_special_unlock(&lp->lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
dev->trans_start = jiffies;
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_packets++;
|
|
|
|
dev->stats.tx_bytes += len;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_ENABLE_INT(lp, IM_TX_INT | IM_TX_EMPTY_INT);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
done: if (!THROTTLE_TX_PKTS)
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
|
|
|
|
dev_kfree_skb(skb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Since I am not sure if I will have enough room in the chip's ram
|
|
|
|
* to store the packet, I call this routine which either sends it
|
|
|
|
* now, or set the card to generates an interrupt when ready
|
|
|
|
* for the packet.
|
|
|
|
*/
|
|
|
|
static int smc_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int numPages, poll_count, status;
|
2009-08-24 13:59:04 +08:00
|
|
|
unsigned long flags;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(3, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
BUG_ON(lp->pending_tx_skb != NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The MMU wants the number of pages to be the number of 256 bytes
|
|
|
|
* 'pages', minus 1 (since a packet can't ever have 0 pages :))
|
|
|
|
*
|
|
|
|
* The 91C111 ignores the size bits, but earlier models don't.
|
|
|
|
*
|
|
|
|
* Pkt size for allocating is data length +6 (for additional status
|
|
|
|
* words, length and ctl)
|
|
|
|
*
|
|
|
|
* If odd size then last byte is included in ctl word.
|
|
|
|
*/
|
|
|
|
numPages = ((skb->len & ~1) + (6 - 1)) >> 8;
|
|
|
|
if (unlikely(numPages > 7)) {
|
|
|
|
printk("%s: Far too big packet error.\n", dev->name);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_errors++;
|
|
|
|
dev->stats.tx_dropped++;
|
2005-04-17 06:20:36 +08:00
|
|
|
dev_kfree_skb(skb);
|
2009-06-23 14:03:08 +08:00
|
|
|
return NETDEV_TX_OK;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2009-08-24 13:59:04 +08:00
|
|
|
smc_special_lock(&lp->lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* now, try to allocate the memory */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MMU_CMD(lp, MC_ALLOC | numPages);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Poll the chip for a short amount of time in case the
|
|
|
|
* allocation succeeds quickly.
|
|
|
|
*/
|
|
|
|
poll_count = MEMORY_WAIT_TIME;
|
|
|
|
do {
|
2008-02-22 18:55:05 +08:00
|
|
|
status = SMC_GET_INT(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (status & IM_ALLOC_INT) {
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_ACK_INT(lp, IM_ALLOC_INT);
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (--poll_count);
|
|
|
|
|
2009-08-24 13:59:04 +08:00
|
|
|
smc_special_unlock(&lp->lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-06-20 11:56:21 +08:00
|
|
|
lp->pending_tx_skb = skb;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!poll_count) {
|
|
|
|
/* oh well, wait until the chip finds memory later */
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
DBG(2, "%s: TX memory allocation deferred.\n", dev->name);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_ENABLE_INT(lp, IM_ALLOC_INT);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Allocation succeeded: push packet to the chip's own memory
|
|
|
|
* immediately.
|
2006-09-14 01:24:59 +08:00
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
smc_hardware_send_pkt((unsigned long)dev);
|
|
|
|
}
|
|
|
|
|
2009-06-23 14:03:08 +08:00
|
|
|
return NETDEV_TX_OK;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This handles a TX interrupt, which is only called when:
|
|
|
|
* - a TX error occurred, or
|
|
|
|
* - CTL_AUTO_RELEASE is not set and TX of a packet completed.
|
|
|
|
*/
|
|
|
|
static void smc_tx(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int saved_packet, packet_no, tx_status, pkt_len;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(3, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* If the TX FIFO is empty then nothing to do */
|
2008-02-22 18:55:05 +08:00
|
|
|
packet_no = SMC_GET_TXFIFO(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (unlikely(packet_no & TXFIFO_TEMPTY)) {
|
|
|
|
PRINTK("%s: smc_tx with nothing on FIFO.\n", dev->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* select packet to read from */
|
2008-02-22 18:55:05 +08:00
|
|
|
saved_packet = SMC_GET_PN(lp);
|
|
|
|
SMC_SET_PN(lp, packet_no);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* read the first word (status word) from this packet */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_PTR(lp, PTR_AUTOINC | PTR_READ);
|
|
|
|
SMC_GET_PKT_HDR(lp, tx_status, pkt_len);
|
2005-04-17 06:20:36 +08:00
|
|
|
DBG(2, "%s: TX STATUS 0x%04x PNR 0x%02x\n",
|
|
|
|
dev->name, tx_status, packet_no);
|
|
|
|
|
2005-04-13 04:21:11 +08:00
|
|
|
if (!(tx_status & ES_TX_SUC))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_errors++;
|
2005-04-13 04:21:11 +08:00
|
|
|
|
|
|
|
if (tx_status & ES_LOSTCARR)
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_carrier_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-04-13 04:21:11 +08:00
|
|
|
if (tx_status & (ES_LATCOL | ES_16COL)) {
|
|
|
|
PRINTK("%s: %s occurred on last xmit\n", dev->name,
|
|
|
|
(tx_status & ES_LATCOL) ?
|
|
|
|
"late collision" : "too many collisions");
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_window_errors++;
|
|
|
|
if (!(dev->stats.tx_window_errors & 63) && net_ratelimit()) {
|
2005-04-13 04:21:11 +08:00
|
|
|
printk(KERN_INFO "%s: unexpectedly large number of "
|
|
|
|
"bad collisions. Please check duplex "
|
2005-04-17 06:20:36 +08:00
|
|
|
"setting.\n", dev->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* kill the packet */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_WAIT_MMU_BUSY(lp);
|
|
|
|
SMC_SET_MMU_CMD(lp, MC_FREEPKT);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Don't restore Packet Number Reg until busy bit is cleared */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_WAIT_MMU_BUSY(lp);
|
|
|
|
SMC_SET_PN(lp, saved_packet);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* re-enable transmit */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_TCR(lp, lp->tcr_cur_mode);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*---PHY CONTROL AND CONFIGURATION-----------------------------------------*/
|
|
|
|
|
|
|
|
static void smc_mii_out(struct net_device *dev, unsigned int val, int bits)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int mii_reg, mask;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
mii_reg = SMC_GET_MII(lp) & ~(MII_MCLK | MII_MDOE | MII_MDO);
|
2005-04-17 06:20:36 +08:00
|
|
|
mii_reg |= MII_MDOE;
|
|
|
|
|
|
|
|
for (mask = 1 << (bits - 1); mask; mask >>= 1) {
|
|
|
|
if (val & mask)
|
|
|
|
mii_reg |= MII_MDO;
|
|
|
|
else
|
|
|
|
mii_reg &= ~MII_MDO;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MII(lp, mii_reg);
|
2005-04-17 06:20:36 +08:00
|
|
|
udelay(MII_DELAY);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MII(lp, mii_reg | MII_MCLK);
|
2005-04-17 06:20:36 +08:00
|
|
|
udelay(MII_DELAY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int smc_mii_in(struct net_device *dev, int bits)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int mii_reg, mask, val;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
mii_reg = SMC_GET_MII(lp) & ~(MII_MCLK | MII_MDOE | MII_MDO);
|
|
|
|
SMC_SET_MII(lp, mii_reg);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
for (mask = 1 << (bits - 1), val = 0; mask; mask >>= 1) {
|
2008-02-22 18:55:05 +08:00
|
|
|
if (SMC_GET_MII(lp) & MII_MDI)
|
2005-04-17 06:20:36 +08:00
|
|
|
val |= mask;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MII(lp, mii_reg);
|
2005-04-17 06:20:36 +08:00
|
|
|
udelay(MII_DELAY);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MII(lp, mii_reg | MII_MCLK);
|
2005-04-17 06:20:36 +08:00
|
|
|
udelay(MII_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reads a register from the MII Management serial interface
|
|
|
|
*/
|
|
|
|
static int smc_phy_read(struct net_device *dev, int phyaddr, int phyreg)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int phydata;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 3);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Idle - 32 ones */
|
|
|
|
smc_mii_out(dev, 0xffffffff, 32);
|
|
|
|
|
|
|
|
/* Start code (01) + read (10) + phyaddr + phyreg */
|
|
|
|
smc_mii_out(dev, 6 << 10 | phyaddr << 5 | phyreg, 14);
|
|
|
|
|
|
|
|
/* Turnaround (2bits) + phydata */
|
|
|
|
phydata = smc_mii_in(dev, 18);
|
|
|
|
|
|
|
|
/* Return to idle state */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MII(lp, SMC_GET_MII(lp) & ~(MII_MCLK|MII_MDOE|MII_MDO));
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
DBG(3, "%s: phyaddr=0x%x, phyreg=0x%x, phydata=0x%x\n",
|
2008-08-21 07:52:04 +08:00
|
|
|
__func__, phyaddr, phyreg, phydata);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
return phydata;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Writes a register to the MII Management serial interface
|
|
|
|
*/
|
|
|
|
static void smc_phy_write(struct net_device *dev, int phyaddr, int phyreg,
|
|
|
|
int phydata)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 3);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Idle - 32 ones */
|
|
|
|
smc_mii_out(dev, 0xffffffff, 32);
|
|
|
|
|
|
|
|
/* Start code (01) + write (01) + phyaddr + phyreg + turnaround + phydata */
|
|
|
|
smc_mii_out(dev, 5 << 28 | phyaddr << 23 | phyreg << 18 | 2 << 16 | phydata, 32);
|
|
|
|
|
|
|
|
/* Return to idle state */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MII(lp, SMC_GET_MII(lp) & ~(MII_MCLK|MII_MDOE|MII_MDO));
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
DBG(3, "%s: phyaddr=0x%x, phyreg=0x%x, phydata=0x%x\n",
|
2008-08-21 07:52:04 +08:00
|
|
|
__func__, phyaddr, phyreg, phydata);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finds and reports the PHY address
|
|
|
|
*/
|
|
|
|
static void smc_phy_detect(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
int phyaddr;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
lp->phy_type = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Scan all 32 PHY addresses if necessary, starting at
|
|
|
|
* PHY#1 to PHY#31, and then PHY#0 last.
|
|
|
|
*/
|
|
|
|
for (phyaddr = 1; phyaddr < 33; ++phyaddr) {
|
|
|
|
unsigned int id1, id2;
|
|
|
|
|
|
|
|
/* Read the PHY identifiers */
|
|
|
|
id1 = smc_phy_read(dev, phyaddr & 31, MII_PHYSID1);
|
|
|
|
id2 = smc_phy_read(dev, phyaddr & 31, MII_PHYSID2);
|
|
|
|
|
|
|
|
DBG(3, "%s: phy_id1=0x%x, phy_id2=0x%x\n",
|
|
|
|
dev->name, id1, id2);
|
|
|
|
|
|
|
|
/* Make sure it is a valid identifier */
|
|
|
|
if (id1 != 0x0000 && id1 != 0xffff && id1 != 0x8000 &&
|
|
|
|
id2 != 0x0000 && id2 != 0xffff && id2 != 0x8000) {
|
|
|
|
/* Save the PHY's address */
|
|
|
|
lp->mii.phy_id = phyaddr & 31;
|
|
|
|
lp->phy_type = id1 << 16 | id2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sets the PHY to a configuration as determined by the user
|
|
|
|
*/
|
|
|
|
static int smc_phy_fixed(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
int phyaddr = lp->mii.phy_id;
|
|
|
|
int bmcr, cfg1;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(3, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Enter Link Disable state */
|
|
|
|
cfg1 = smc_phy_read(dev, phyaddr, PHY_CFG1_REG);
|
|
|
|
cfg1 |= PHY_CFG1_LNKDIS;
|
|
|
|
smc_phy_write(dev, phyaddr, PHY_CFG1_REG, cfg1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set our fixed capabilities
|
|
|
|
* Disable auto-negotiation
|
|
|
|
*/
|
|
|
|
bmcr = 0;
|
|
|
|
|
|
|
|
if (lp->ctl_rfduplx)
|
|
|
|
bmcr |= BMCR_FULLDPLX;
|
|
|
|
|
|
|
|
if (lp->ctl_rspeed == 100)
|
|
|
|
bmcr |= BMCR_SPEED100;
|
|
|
|
|
|
|
|
/* Write our capabilities to the phy control register */
|
|
|
|
smc_phy_write(dev, phyaddr, MII_BMCR, bmcr);
|
|
|
|
|
|
|
|
/* Re-Configure the Receive/Phy Control register */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_RPC(lp, lp->rpc_cur_mode);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* smc_phy_reset - reset the phy
|
|
|
|
* @dev: net device
|
|
|
|
* @phy: phy address
|
|
|
|
*
|
|
|
|
* Issue a software reset for the specified PHY and
|
|
|
|
* wait up to 100ms for the reset to complete. We should
|
|
|
|
* not access the PHY for 50ms after issuing the reset.
|
|
|
|
*
|
|
|
|
* The time to wait appears to be dependent on the PHY.
|
|
|
|
*
|
|
|
|
* Must be called with lp->lock locked.
|
|
|
|
*/
|
|
|
|
static int smc_phy_reset(struct net_device *dev, int phy)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
unsigned int bmcr;
|
|
|
|
int timeout;
|
|
|
|
|
|
|
|
smc_phy_write(dev, phy, MII_BMCR, BMCR_RESET);
|
|
|
|
|
|
|
|
for (timeout = 2; timeout; timeout--) {
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
msleep(50);
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
|
|
|
|
bmcr = smc_phy_read(dev, phy, MII_BMCR);
|
|
|
|
if (!(bmcr & BMCR_RESET))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bmcr & BMCR_RESET;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* smc_phy_powerdown - powerdown phy
|
|
|
|
* @dev: net device
|
|
|
|
*
|
|
|
|
* Power down the specified PHY
|
|
|
|
*/
|
|
|
|
static void smc_phy_powerdown(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
unsigned int bmcr;
|
|
|
|
int phy = lp->mii.phy_id;
|
|
|
|
|
|
|
|
if (lp->phy_type == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* We need to ensure that no calls to smc_phy_configure are
|
|
|
|
pending.
|
|
|
|
*/
|
2008-06-12 17:22:02 +08:00
|
|
|
cancel_work_sync(&lp->phy_configure);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
bmcr = smc_phy_read(dev, phy, MII_BMCR);
|
|
|
|
smc_phy_write(dev, phy, MII_BMCR, bmcr | BMCR_PDOWN);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* smc_phy_check_media - check the media status and adjust TCR
|
|
|
|
* @dev: net device
|
|
|
|
* @init: set true for initialisation
|
|
|
|
*
|
|
|
|
* Select duplex mode depending on negotiation state. This
|
|
|
|
* also updates our carrier state.
|
|
|
|
*/
|
|
|
|
static void smc_phy_check_media(struct net_device *dev, int init)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
|
|
|
|
if (mii_check_media(&lp->mii, netif_msg_link(lp), init)) {
|
|
|
|
/* duplex state has changed */
|
|
|
|
if (lp->mii.full_duplex) {
|
|
|
|
lp->tcr_cur_mode |= TCR_SWFDUP;
|
|
|
|
} else {
|
|
|
|
lp->tcr_cur_mode &= ~TCR_SWFDUP;
|
|
|
|
}
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_TCR(lp, lp->tcr_cur_mode);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Configures the specified PHY through the MII management interface
|
|
|
|
* using Autonegotiation.
|
|
|
|
* Calls smc_phy_fixed() if the user has requested a certain config.
|
|
|
|
* If RPC ANEG bit is set, the media selection is dependent purely on
|
|
|
|
* the selection by the MII (either in the MII BMCR reg or the result
|
|
|
|
* of autonegotiation.) If the RPC ANEG bit is cleared, the selection
|
|
|
|
* is controlled by the RPC SPEED and RPC DPLX bits.
|
|
|
|
*/
|
2006-12-06 03:36:26 +08:00
|
|
|
static void smc_phy_configure(struct work_struct *work)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-12-06 03:36:26 +08:00
|
|
|
struct smc_local *lp =
|
|
|
|
container_of(work, struct smc_local, phy_configure);
|
|
|
|
struct net_device *dev = lp->dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
int phyaddr = lp->mii.phy_id;
|
|
|
|
int my_phy_caps; /* My PHY capabilities */
|
|
|
|
int my_ad_caps; /* My Advertised capabilities */
|
|
|
|
int status;
|
|
|
|
|
|
|
|
DBG(3, "%s:smc_program_phy()\n", dev->name);
|
|
|
|
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We should not be called if phy_type is zero.
|
|
|
|
*/
|
|
|
|
if (lp->phy_type == 0)
|
|
|
|
goto smc_phy_configure_exit;
|
|
|
|
|
|
|
|
if (smc_phy_reset(dev, phyaddr)) {
|
|
|
|
printk("%s: PHY reset timed out\n", dev->name);
|
|
|
|
goto smc_phy_configure_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Enable PHY Interrupts (for register 18)
|
|
|
|
* Interrupts listed here are disabled
|
|
|
|
*/
|
|
|
|
smc_phy_write(dev, phyaddr, PHY_MASK_REG,
|
|
|
|
PHY_INT_LOSSSYNC | PHY_INT_CWRD | PHY_INT_SSD |
|
|
|
|
PHY_INT_ESD | PHY_INT_RPOL | PHY_INT_JAB |
|
|
|
|
PHY_INT_SPDDET | PHY_INT_DPLXDET);
|
|
|
|
|
|
|
|
/* Configure the Receive/Phy Control register */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_RPC(lp, lp->rpc_cur_mode);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* If the user requested no auto neg, then go set his request */
|
|
|
|
if (lp->mii.force_media) {
|
|
|
|
smc_phy_fixed(dev);
|
|
|
|
goto smc_phy_configure_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy our capabilities from MII_BMSR to MII_ADVERTISE */
|
|
|
|
my_phy_caps = smc_phy_read(dev, phyaddr, MII_BMSR);
|
|
|
|
|
|
|
|
if (!(my_phy_caps & BMSR_ANEGCAPABLE)) {
|
|
|
|
printk(KERN_INFO "Auto negotiation NOT supported\n");
|
|
|
|
smc_phy_fixed(dev);
|
|
|
|
goto smc_phy_configure_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
my_ad_caps = ADVERTISE_CSMA; /* I am CSMA capable */
|
|
|
|
|
|
|
|
if (my_phy_caps & BMSR_100BASE4)
|
|
|
|
my_ad_caps |= ADVERTISE_100BASE4;
|
|
|
|
if (my_phy_caps & BMSR_100FULL)
|
|
|
|
my_ad_caps |= ADVERTISE_100FULL;
|
|
|
|
if (my_phy_caps & BMSR_100HALF)
|
|
|
|
my_ad_caps |= ADVERTISE_100HALF;
|
|
|
|
if (my_phy_caps & BMSR_10FULL)
|
|
|
|
my_ad_caps |= ADVERTISE_10FULL;
|
|
|
|
if (my_phy_caps & BMSR_10HALF)
|
|
|
|
my_ad_caps |= ADVERTISE_10HALF;
|
|
|
|
|
|
|
|
/* Disable capabilities not selected by our user */
|
|
|
|
if (lp->ctl_rspeed != 100)
|
|
|
|
my_ad_caps &= ~(ADVERTISE_100BASE4|ADVERTISE_100FULL|ADVERTISE_100HALF);
|
|
|
|
|
|
|
|
if (!lp->ctl_rfduplx)
|
|
|
|
my_ad_caps &= ~(ADVERTISE_100FULL|ADVERTISE_10FULL);
|
|
|
|
|
|
|
|
/* Update our Auto-Neg Advertisement Register */
|
|
|
|
smc_phy_write(dev, phyaddr, MII_ADVERTISE, my_ad_caps);
|
|
|
|
lp->mii.advertising = my_ad_caps;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the register back. Without this, it appears that when
|
|
|
|
* auto-negotiation is restarted, sometimes it isn't ready and
|
|
|
|
* the link does not come up.
|
|
|
|
*/
|
|
|
|
status = smc_phy_read(dev, phyaddr, MII_ADVERTISE);
|
|
|
|
|
|
|
|
DBG(2, "%s: phy caps=%x\n", dev->name, my_phy_caps);
|
|
|
|
DBG(2, "%s: phy advertised caps=%x\n", dev->name, my_ad_caps);
|
|
|
|
|
|
|
|
/* Restart auto-negotiation process in order to advertise my caps */
|
|
|
|
smc_phy_write(dev, phyaddr, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART);
|
|
|
|
|
|
|
|
smc_phy_check_media(dev, 1);
|
|
|
|
|
|
|
|
smc_phy_configure_exit:
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* smc_phy_interrupt
|
|
|
|
*
|
|
|
|
* Purpose: Handle interrupts relating to PHY register 18. This is
|
|
|
|
* called from the "hard" interrupt handler under our private spinlock.
|
|
|
|
*/
|
|
|
|
static void smc_phy_interrupt(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
int phyaddr = lp->mii.phy_id;
|
|
|
|
int phy18;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (lp->phy_type == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for(;;) {
|
|
|
|
smc_phy_check_media(dev, 0);
|
|
|
|
|
|
|
|
/* Read PHY Register 18, Status Output */
|
|
|
|
phy18 = smc_phy_read(dev, phyaddr, PHY_INT_REG);
|
|
|
|
if ((phy18 & PHY_INT_INT) == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*--- END PHY CONTROL AND CONFIGURATION-------------------------------------*/
|
|
|
|
|
|
|
|
static void smc_10bt_check_media(struct net_device *dev, int init)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int old_carrier, new_carrier;
|
|
|
|
|
|
|
|
old_carrier = netif_carrier_ok(dev) ? 1 : 0;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
new_carrier = (SMC_GET_EPH_STATUS(lp) & ES_LINK_OK) ? 1 : 0;
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (init || (old_carrier != new_carrier)) {
|
|
|
|
if (!new_carrier) {
|
|
|
|
netif_carrier_off(dev);
|
|
|
|
} else {
|
|
|
|
netif_carrier_on(dev);
|
|
|
|
}
|
|
|
|
if (netif_msg_link(lp))
|
|
|
|
printk(KERN_INFO "%s: link %s\n", dev->name,
|
|
|
|
new_carrier ? "up" : "down");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smc_eph_interrupt(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned int ctl;
|
|
|
|
|
|
|
|
smc_10bt_check_media(dev, 0);
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
ctl = SMC_GET_CTL(lp);
|
|
|
|
SMC_SET_CTL(lp, ctl & ~CTL_LE_ENABLE);
|
|
|
|
SMC_SET_CTL(lp, ctl);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is the main routine of the driver, to handle the device when
|
|
|
|
* it needs some attention.
|
|
|
|
*/
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t smc_interrupt(int irq, void *dev_id)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct net_device *dev = dev_id;
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
int status, mask, timeout, card_stats;
|
|
|
|
int saved_pointer;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(3, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
spin_lock(&lp->lock);
|
|
|
|
|
|
|
|
/* A preamble may be used when there is a potential race
|
|
|
|
* between the interruptible transmit functions and this
|
|
|
|
* ISR. */
|
|
|
|
SMC_INTERRUPT_PREAMBLE;
|
|
|
|
|
2008-02-22 18:55:05 +08:00
|
|
|
saved_pointer = SMC_GET_PTR(lp);
|
|
|
|
mask = SMC_GET_INT_MASK(lp);
|
|
|
|
SMC_SET_INT_MASK(lp, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* set a timeout value, so I don't stay here forever */
|
2005-11-18 03:02:48 +08:00
|
|
|
timeout = MAX_IRQ_LOOPS;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
do {
|
2008-02-22 18:55:05 +08:00
|
|
|
status = SMC_GET_INT(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
DBG(2, "%s: INT 0x%02x MASK 0x%02x MEM 0x%04x FIFO 0x%04x\n",
|
|
|
|
dev->name, status, mask,
|
2008-02-22 18:55:05 +08:00
|
|
|
({ int meminfo; SMC_SELECT_BANK(lp, 0);
|
|
|
|
meminfo = SMC_GET_MIR(lp);
|
|
|
|
SMC_SELECT_BANK(lp, 2); meminfo; }),
|
|
|
|
SMC_GET_FIFO(lp));
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
status &= mask;
|
|
|
|
if (!status)
|
|
|
|
break;
|
|
|
|
|
2005-04-13 04:26:40 +08:00
|
|
|
if (status & IM_TX_INT) {
|
|
|
|
/* do this before RX as it will free memory quickly */
|
2005-04-17 06:20:36 +08:00
|
|
|
DBG(3, "%s: TX int\n", dev->name);
|
|
|
|
smc_tx(dev);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_ACK_INT(lp, IM_TX_INT);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (THROTTLE_TX_PKTS)
|
|
|
|
netif_wake_queue(dev);
|
2005-04-13 04:26:40 +08:00
|
|
|
} else if (status & IM_RCV_INT) {
|
|
|
|
DBG(3, "%s: RX irq\n", dev->name);
|
|
|
|
smc_rcv(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (status & IM_ALLOC_INT) {
|
|
|
|
DBG(3, "%s: Allocation irq\n", dev->name);
|
|
|
|
tasklet_hi_schedule(&lp->tx_task);
|
|
|
|
mask &= ~IM_ALLOC_INT;
|
|
|
|
} else if (status & IM_TX_EMPTY_INT) {
|
|
|
|
DBG(3, "%s: TX empty\n", dev->name);
|
|
|
|
mask &= ~IM_TX_EMPTY_INT;
|
|
|
|
|
|
|
|
/* update stats */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
card_stats = SMC_GET_COUNTER(lp);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* single collisions */
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.collisions += card_stats & 0xF;
|
2005-04-17 06:20:36 +08:00
|
|
|
card_stats >>= 4;
|
|
|
|
|
|
|
|
/* multiple collisions */
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.collisions += card_stats & 0xF;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (status & IM_RX_OVRN_INT) {
|
2005-04-13 04:21:11 +08:00
|
|
|
DBG(1, "%s: RX overrun (EPH_ST 0x%04x)\n", dev->name,
|
2008-02-22 18:55:05 +08:00
|
|
|
({ int eph_st; SMC_SELECT_BANK(lp, 0);
|
|
|
|
eph_st = SMC_GET_EPH_STATUS(lp);
|
|
|
|
SMC_SELECT_BANK(lp, 2); eph_st; }));
|
|
|
|
SMC_ACK_INT(lp, IM_RX_OVRN_INT);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_errors++;
|
|
|
|
dev->stats.rx_fifo_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else if (status & IM_EPH_INT) {
|
|
|
|
smc_eph_interrupt(dev);
|
|
|
|
} else if (status & IM_MDINT) {
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_ACK_INT(lp, IM_MDINT);
|
2005-04-17 06:20:36 +08:00
|
|
|
smc_phy_interrupt(dev);
|
|
|
|
} else if (status & IM_ERCV_INT) {
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_ACK_INT(lp, IM_ERCV_INT);
|
2005-04-17 06:20:36 +08:00
|
|
|
PRINTK("%s: UNSUPPORTED: ERCV INTERRUPT \n", dev->name);
|
|
|
|
}
|
|
|
|
} while (--timeout);
|
|
|
|
|
|
|
|
/* restore register states */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_PTR(lp, saved_pointer);
|
|
|
|
SMC_SET_INT_MASK(lp, mask);
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock(&lp->lock);
|
|
|
|
|
2008-04-11 12:30:37 +08:00
|
|
|
#ifndef CONFIG_NET_POLL_CONTROLLER
|
2005-11-18 03:02:48 +08:00
|
|
|
if (timeout == MAX_IRQ_LOOPS)
|
|
|
|
PRINTK("%s: spurious interrupt (mask = 0x%02x)\n",
|
|
|
|
dev->name, mask);
|
2008-04-11 12:30:37 +08:00
|
|
|
#endif
|
2005-11-18 03:02:48 +08:00
|
|
|
DBG(3, "%s: Interrupt done (%d loops)\n",
|
|
|
|
dev->name, MAX_IRQ_LOOPS - timeout);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We return IRQ_HANDLED unconditionally here even if there was
|
|
|
|
* nothing to do. There is a possibility that a packet might
|
|
|
|
* get enqueued into the chip right after TX_EMPTY_INT is raised
|
|
|
|
* but just before the CPU acknowledges the IRQ.
|
|
|
|
* Better take an unneeded IRQ in some occasions than complexifying
|
|
|
|
* the code for all cases.
|
|
|
|
*/
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
/*
|
|
|
|
* Polling receive - used by netconsole and other diagnostic tools
|
|
|
|
* to allow network i/o with interrupts disabled.
|
|
|
|
*/
|
|
|
|
static void smc_poll_controller(struct net_device *dev)
|
|
|
|
{
|
|
|
|
disable_irq(dev->irq);
|
2006-10-07 23:29:18 +08:00
|
|
|
smc_interrupt(dev->irq, dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
enable_irq(dev->irq);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Our watchdog timed out. Called by the networking layer */
|
|
|
|
static void smc_timeout(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
2005-04-13 04:21:11 +08:00
|
|
|
int status, mask, eph_st, meminfo, fifo;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
spin_lock_irq(&lp->lock);
|
2008-02-22 18:55:05 +08:00
|
|
|
status = SMC_GET_INT(lp);
|
|
|
|
mask = SMC_GET_INT_MASK(lp);
|
|
|
|
fifo = SMC_GET_FIFO(lp);
|
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
eph_st = SMC_GET_EPH_STATUS(lp);
|
|
|
|
meminfo = SMC_GET_MIR(lp);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock_irq(&lp->lock);
|
2005-04-13 04:21:11 +08:00
|
|
|
PRINTK( "%s: TX timeout (INT 0x%02x INTMASK 0x%02x "
|
|
|
|
"MEM 0x%04x FIFO 0x%04x EPH_ST 0x%04x)\n",
|
|
|
|
dev->name, status, mask, meminfo, fifo, eph_st );
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
smc_reset(dev);
|
|
|
|
smc_enable(dev);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reconfiguring the PHY doesn't seem like a bad idea here, but
|
|
|
|
* smc_phy_configure() calls msleep() which calls schedule_timeout()
|
|
|
|
* which calls schedule(). Hence we use a work queue.
|
|
|
|
*/
|
2008-06-12 17:22:02 +08:00
|
|
|
if (lp->phy_type != 0)
|
|
|
|
schedule_work(&lp->phy_configure);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* We can accept TX packets again */
|
|
|
|
dev->trans_start = jiffies;
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This routine will, depending on the values passed to it,
|
|
|
|
* either make it accept multicast packets, go into
|
|
|
|
* promiscuous mode (for TCPDUMP and cousins) or accept
|
|
|
|
* a select set of multicast packets
|
|
|
|
*/
|
|
|
|
static void smc_set_multicast_list(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
unsigned char multicast_table[8];
|
|
|
|
int update_multicast = 0;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (dev->flags & IFF_PROMISC) {
|
|
|
|
DBG(2, "%s: RCR_PRMS\n", dev->name);
|
|
|
|
lp->rcr_cur_mode |= RCR_PRMS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* BUG? I never disable promiscuous mode if multicasting was turned on.
|
|
|
|
Now, I turn off promiscuous mode, but I don't do anything to multicasting
|
|
|
|
when promiscuous mode is turned on.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Here, I am setting this to accept all multicast packets.
|
|
|
|
* I don't need to zero the multicast table, because the flag is
|
|
|
|
* checked before the table is
|
|
|
|
*/
|
|
|
|
else if (dev->flags & IFF_ALLMULTI || dev->mc_count > 16) {
|
|
|
|
DBG(2, "%s: RCR_ALMUL\n", dev->name);
|
|
|
|
lp->rcr_cur_mode |= RCR_ALMUL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This sets the internal hardware table to filter out unwanted
|
|
|
|
* multicast packets before they take up memory.
|
|
|
|
*
|
|
|
|
* The SMC chip uses a hash table where the high 6 bits of the CRC of
|
|
|
|
* address are the offset into the table. If that bit is 1, then the
|
|
|
|
* multicast packet is accepted. Otherwise, it's dropped silently.
|
|
|
|
*
|
|
|
|
* To use the 6 bits as an offset into the table, the high 3 bits are
|
|
|
|
* the number of the 8 bit register, while the low 3 bits are the bit
|
|
|
|
* within that register.
|
|
|
|
*/
|
|
|
|
else if (dev->mc_count) {
|
|
|
|
int i;
|
|
|
|
struct dev_mc_list *cur_addr;
|
|
|
|
|
|
|
|
/* table for flipping the order of 3 bits */
|
|
|
|
static const unsigned char invert3[] = {0, 4, 2, 6, 1, 5, 3, 7};
|
|
|
|
|
|
|
|
/* start with a table of all zeros: reject all */
|
|
|
|
memset(multicast_table, 0, sizeof(multicast_table));
|
|
|
|
|
|
|
|
cur_addr = dev->mc_list;
|
|
|
|
for (i = 0; i < dev->mc_count; i++, cur_addr = cur_addr->next) {
|
|
|
|
int position;
|
|
|
|
|
|
|
|
/* do we have a pointer here? */
|
|
|
|
if (!cur_addr)
|
|
|
|
break;
|
|
|
|
/* make sure this is a multicast address -
|
|
|
|
shouldn't this be a given if we have it here ? */
|
|
|
|
if (!(*cur_addr->dmi_addr & 1))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* only use the low order bits */
|
|
|
|
position = crc32_le(~0, cur_addr->dmi_addr, 6) & 0x3f;
|
|
|
|
|
|
|
|
/* do some messy swapping to put the bit in the right spot */
|
|
|
|
multicast_table[invert3[position&7]] |=
|
|
|
|
(1<<invert3[(position>>3)&7]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* be sure I get rid of flags I might have set */
|
|
|
|
lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL);
|
|
|
|
|
|
|
|
/* now, the table can be loaded into the chipset */
|
|
|
|
update_multicast = 1;
|
|
|
|
} else {
|
|
|
|
DBG(2, "%s: ~(RCR_PRMS|RCR_ALMUL)\n", dev->name);
|
|
|
|
lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* since I'm disabling all multicast entirely, I need to
|
|
|
|
* clear the multicast list
|
|
|
|
*/
|
|
|
|
memset(multicast_table, 0, sizeof(multicast_table));
|
|
|
|
update_multicast = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irq(&lp->lock);
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
SMC_SET_RCR(lp, lp->rcr_cur_mode);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (update_multicast) {
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 3);
|
|
|
|
SMC_SET_MCAST(lp, multicast_table);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Open and Initialize the board
|
|
|
|
*
|
|
|
|
* Set up everything, reset the card, etc..
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
smc_open(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that the address is valid. If its not, refuse
|
|
|
|
* to bring the device up. The user must specify an
|
|
|
|
* address using ifconfig eth0 hw ether xx:xx:xx:xx:xx:xx
|
|
|
|
*/
|
|
|
|
if (!is_valid_ether_addr(dev->dev_addr)) {
|
2008-08-21 07:52:04 +08:00
|
|
|
PRINTK("%s: no valid ethernet hw addr\n", __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Setup the default Register Modes */
|
|
|
|
lp->tcr_cur_mode = TCR_DEFAULT;
|
|
|
|
lp->rcr_cur_mode = RCR_DEFAULT;
|
2008-09-05 04:13:37 +08:00
|
|
|
lp->rpc_cur_mode = RPC_DEFAULT |
|
|
|
|
lp->cfg.leda << RPC_LSXA_SHFT |
|
|
|
|
lp->cfg.ledb << RPC_LSXB_SHFT;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If we are not using a MII interface, we need to
|
|
|
|
* monitor our own carrier signal to detect faults.
|
|
|
|
*/
|
|
|
|
if (lp->phy_type == 0)
|
|
|
|
lp->tcr_cur_mode |= TCR_MON_CSN;
|
|
|
|
|
|
|
|
/* reset the hardware */
|
|
|
|
smc_reset(dev);
|
|
|
|
smc_enable(dev);
|
|
|
|
|
|
|
|
/* Configure the PHY, initialize the link state */
|
|
|
|
if (lp->phy_type != 0)
|
2006-12-06 03:36:26 +08:00
|
|
|
smc_phy_configure(&lp->phy_configure);
|
2005-04-17 06:20:36 +08:00
|
|
|
else {
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
smc_10bt_check_media(dev, 1);
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
netif_start_queue(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* smc_close
|
|
|
|
*
|
|
|
|
* this makes the board clean up everything that it can
|
|
|
|
* and not talk to the outside world. Caused by
|
|
|
|
* an 'ifconfig ethX down'
|
|
|
|
*/
|
|
|
|
static int smc_close(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", dev->name, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
netif_carrier_off(dev);
|
|
|
|
|
|
|
|
/* clear everything */
|
|
|
|
smc_shutdown(dev);
|
2005-06-20 11:56:21 +08:00
|
|
|
tasklet_kill(&lp->tx_task);
|
2005-04-17 06:20:36 +08:00
|
|
|
smc_phy_powerdown(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ethtool support
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
smc_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cmd->maxtxpkt = 1;
|
|
|
|
cmd->maxrxpkt = 1;
|
|
|
|
|
|
|
|
if (lp->phy_type != 0) {
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
ret = mii_ethtool_gset(&lp->mii, cmd);
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
} else {
|
|
|
|
cmd->supported = SUPPORTED_10baseT_Half |
|
|
|
|
SUPPORTED_10baseT_Full |
|
|
|
|
SUPPORTED_TP | SUPPORTED_AUI;
|
|
|
|
|
|
|
|
if (lp->ctl_rspeed == 10)
|
|
|
|
cmd->speed = SPEED_10;
|
|
|
|
else if (lp->ctl_rspeed == 100)
|
|
|
|
cmd->speed = SPEED_100;
|
|
|
|
|
|
|
|
cmd->autoneg = AUTONEG_DISABLE;
|
|
|
|
cmd->transceiver = XCVR_INTERNAL;
|
|
|
|
cmd->port = 0;
|
|
|
|
cmd->duplex = lp->tcr_cur_mode & TCR_SWFDUP ? DUPLEX_FULL : DUPLEX_HALF;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
smc_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (lp->phy_type != 0) {
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
ret = mii_ethtool_sset(&lp->mii, cmd);
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
} else {
|
|
|
|
if (cmd->autoneg != AUTONEG_DISABLE ||
|
|
|
|
cmd->speed != SPEED_10 ||
|
|
|
|
(cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL) ||
|
|
|
|
(cmd->port != PORT_TP && cmd->port != PORT_AUI))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
// lp->port = cmd->port;
|
|
|
|
lp->ctl_rfduplx = cmd->duplex == DUPLEX_FULL;
|
|
|
|
|
|
|
|
// if (netif_running(dev))
|
|
|
|
// smc_set_port(dev);
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
smc_ethtool_getdrvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
|
|
|
|
{
|
|
|
|
strncpy(info->driver, CARDNAME, sizeof(info->driver));
|
|
|
|
strncpy(info->version, version, sizeof(info->version));
|
2009-01-27 13:12:58 +08:00
|
|
|
strncpy(info->bus_info, dev_name(dev->dev.parent), sizeof(info->bus_info));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int smc_ethtool_nwayreset(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
int ret = -EINVAL;
|
|
|
|
|
|
|
|
if (lp->phy_type != 0) {
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
ret = mii_nway_restart(&lp->mii);
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 smc_ethtool_getmsglevel(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
return lp->msg_enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smc_ethtool_setmsglevel(struct net_device *dev, u32 level)
|
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
lp->msg_enable = level;
|
|
|
|
}
|
|
|
|
|
2009-01-16 21:23:19 +08:00
|
|
|
static int smc_write_eeprom_word(struct net_device *dev, u16 addr, u16 word)
|
|
|
|
{
|
|
|
|
u16 ctl;
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
/* load word into GP register */
|
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
SMC_SET_GP(lp, word);
|
|
|
|
/* set the address to put the data in EEPROM */
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_PTR(lp, addr);
|
|
|
|
/* tell it to write */
|
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
ctl = SMC_GET_CTL(lp);
|
|
|
|
SMC_SET_CTL(lp, ctl | (CTL_EEPROM_SELECT | CTL_STORE));
|
|
|
|
/* wait for it to finish */
|
|
|
|
do {
|
|
|
|
udelay(1);
|
|
|
|
} while (SMC_GET_CTL(lp) & CTL_STORE);
|
|
|
|
/* clean up */
|
|
|
|
SMC_SET_CTL(lp, ctl);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smc_read_eeprom_word(struct net_device *dev, u16 addr, u16 *word)
|
|
|
|
{
|
|
|
|
u16 ctl;
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
void __iomem *ioaddr = lp->base;
|
|
|
|
|
|
|
|
spin_lock_irq(&lp->lock);
|
|
|
|
/* set the EEPROM address to get the data from */
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_PTR(lp, addr | PTR_READ);
|
|
|
|
/* tell it to load */
|
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
SMC_SET_GP(lp, 0xffff); /* init to known */
|
|
|
|
ctl = SMC_GET_CTL(lp);
|
|
|
|
SMC_SET_CTL(lp, ctl | (CTL_EEPROM_SELECT | CTL_RELOAD));
|
|
|
|
/* wait for it to finish */
|
|
|
|
do {
|
|
|
|
udelay(1);
|
|
|
|
} while (SMC_GET_CTL(lp) & CTL_RELOAD);
|
|
|
|
/* read word from GP register */
|
|
|
|
*word = SMC_GET_GP(lp);
|
|
|
|
/* clean up */
|
|
|
|
SMC_SET_CTL(lp, ctl);
|
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
spin_unlock_irq(&lp->lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smc_ethtool_geteeprom_len(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return 0x23 * 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smc_ethtool_geteeprom(struct net_device *dev,
|
|
|
|
struct ethtool_eeprom *eeprom, u8 *data)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int imax;
|
|
|
|
|
|
|
|
DBG(1, "Reading %d bytes at %d(0x%x)\n",
|
|
|
|
eeprom->len, eeprom->offset, eeprom->offset);
|
|
|
|
imax = smc_ethtool_geteeprom_len(dev);
|
|
|
|
for (i = 0; i < eeprom->len; i += 2) {
|
|
|
|
int ret;
|
|
|
|
u16 wbuf;
|
|
|
|
int offset = i + eeprom->offset;
|
|
|
|
if (offset > imax)
|
|
|
|
break;
|
|
|
|
ret = smc_read_eeprom_word(dev, offset >> 1, &wbuf);
|
|
|
|
if (ret != 0)
|
|
|
|
return ret;
|
|
|
|
DBG(2, "Read 0x%x from 0x%x\n", wbuf, offset >> 1);
|
|
|
|
data[i] = (wbuf >> 8) & 0xff;
|
|
|
|
data[i+1] = wbuf & 0xff;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smc_ethtool_seteeprom(struct net_device *dev,
|
|
|
|
struct ethtool_eeprom *eeprom, u8 *data)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int imax;
|
|
|
|
|
|
|
|
DBG(1, "Writing %d bytes to %d(0x%x)\n",
|
|
|
|
eeprom->len, eeprom->offset, eeprom->offset);
|
|
|
|
imax = smc_ethtool_geteeprom_len(dev);
|
|
|
|
for (i = 0; i < eeprom->len; i += 2) {
|
|
|
|
int ret;
|
|
|
|
u16 wbuf;
|
|
|
|
int offset = i + eeprom->offset;
|
|
|
|
if (offset > imax)
|
|
|
|
break;
|
|
|
|
wbuf = (data[i] << 8) | data[i + 1];
|
|
|
|
DBG(2, "Writing 0x%x to 0x%x\n", wbuf, offset >> 1);
|
|
|
|
ret = smc_write_eeprom_word(dev, offset >> 1, wbuf);
|
|
|
|
if (ret != 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-09-14 02:30:00 +08:00
|
|
|
static const struct ethtool_ops smc_ethtool_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.get_settings = smc_ethtool_getsettings,
|
|
|
|
.set_settings = smc_ethtool_setsettings,
|
|
|
|
.get_drvinfo = smc_ethtool_getdrvinfo,
|
|
|
|
|
|
|
|
.get_msglevel = smc_ethtool_getmsglevel,
|
|
|
|
.set_msglevel = smc_ethtool_setmsglevel,
|
|
|
|
.nway_reset = smc_ethtool_nwayreset,
|
|
|
|
.get_link = ethtool_op_get_link,
|
2009-01-16 21:23:19 +08:00
|
|
|
.get_eeprom_len = smc_ethtool_geteeprom_len,
|
|
|
|
.get_eeprom = smc_ethtool_geteeprom,
|
|
|
|
.set_eeprom = smc_ethtool_seteeprom,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2009-01-27 13:32:25 +08:00
|
|
|
static const struct net_device_ops smc_netdev_ops = {
|
|
|
|
.ndo_open = smc_open,
|
|
|
|
.ndo_stop = smc_close,
|
|
|
|
.ndo_start_xmit = smc_hard_start_xmit,
|
|
|
|
.ndo_tx_timeout = smc_timeout,
|
|
|
|
.ndo_set_multicast_list = smc_set_multicast_list,
|
2009-07-10 01:59:01 +08:00
|
|
|
.ndo_change_mtu = eth_change_mtu,
|
2009-01-27 13:32:25 +08:00
|
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
|
|
.ndo_set_mac_address = eth_mac_addr,
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
.ndo_poll_controller = smc_poll_controller,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* smc_findirq
|
|
|
|
*
|
|
|
|
* This routine has a simple purpose -- make the SMC chip generate an
|
|
|
|
* interrupt, so an auto-detect routine can detect it, and find the IRQ,
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* does this still work?
|
|
|
|
*
|
|
|
|
* I just deleted auto_irq.c, since it was never built...
|
|
|
|
* --jgarzik
|
|
|
|
*/
|
2008-11-23 01:36:14 +08:00
|
|
|
static int __devinit smc_findirq(struct smc_local *lp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-02-22 18:55:05 +08:00
|
|
|
void __iomem *ioaddr = lp->base;
|
2005-04-17 06:20:36 +08:00
|
|
|
int timeout = 20;
|
|
|
|
unsigned long cookie;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", CARDNAME, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
cookie = probe_irq_on();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* What I try to do here is trigger an ALLOC_INT. This is done
|
|
|
|
* by allocating a small chunk of memory, which will give an interrupt
|
|
|
|
* when done.
|
|
|
|
*/
|
|
|
|
/* enable ALLOCation interrupts ONLY */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 2);
|
|
|
|
SMC_SET_INT_MASK(lp, IM_ALLOC_INT);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate 512 bytes of memory. Note that the chip was just
|
|
|
|
* reset so all the memory is available
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_MMU_CMD(lp, MC_ALLOC | 1);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Wait until positive that the interrupt has been generated
|
|
|
|
*/
|
|
|
|
do {
|
|
|
|
int int_status;
|
|
|
|
udelay(10);
|
2008-02-22 18:55:05 +08:00
|
|
|
int_status = SMC_GET_INT(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (int_status & IM_ALLOC_INT)
|
|
|
|
break; /* got the interrupt */
|
|
|
|
} while (--timeout);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* there is really nothing that I can do here if timeout fails,
|
|
|
|
* as autoirq_report will return a 0 anyway, which is what I
|
|
|
|
* want in this case. Plus, the clean up is needed in both
|
|
|
|
* cases.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* and disable all interrupts again */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SET_INT_MASK(lp, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* and return what I found */
|
|
|
|
return probe_irq_off(cookie);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function: smc_probe(unsigned long ioaddr)
|
|
|
|
*
|
|
|
|
* Purpose:
|
|
|
|
* Tests to see if a given ioaddr points to an SMC91x chip.
|
|
|
|
* Returns a 0 on success
|
|
|
|
*
|
|
|
|
* Algorithm:
|
|
|
|
* (1) see if the high byte of BANK_SELECT is 0x33
|
|
|
|
* (2) compare the ioaddr with the base register's address
|
|
|
|
* (3) see if I recognize the chip ID in the appropriate register
|
|
|
|
*
|
|
|
|
* Here I do typical initialization tasks.
|
|
|
|
*
|
|
|
|
* o Initialize the structure if needed
|
|
|
|
* o print out my vanity message if not done so already
|
|
|
|
* o print out what type of hardware is detected
|
|
|
|
* o print out the ethernet address
|
|
|
|
* o find the IRQ
|
|
|
|
* o set up my private data
|
|
|
|
* o configure the dev structure with my subroutines
|
|
|
|
* o actually GRAB the irq.
|
|
|
|
* o GRAB the region
|
|
|
|
*/
|
2008-11-23 01:36:14 +08:00
|
|
|
static int __devinit smc_probe(struct net_device *dev, void __iomem *ioaddr,
|
2008-01-15 06:30:10 +08:00
|
|
|
unsigned long irq_flags)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct smc_local *lp = netdev_priv(dev);
|
|
|
|
static int version_printed = 0;
|
2007-10-04 08:59:30 +08:00
|
|
|
int retval;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int val, revision_register;
|
|
|
|
const char *version_string;
|
|
|
|
|
2008-08-21 07:52:04 +08:00
|
|
|
DBG(2, "%s: %s\n", CARDNAME, __func__);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* First, see if the high byte is 0x33 */
|
2008-02-22 18:55:05 +08:00
|
|
|
val = SMC_CURRENT_BANK(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
DBG(2, "%s: bank signature probe returned 0x%04x\n", CARDNAME, val);
|
|
|
|
if ((val & 0xFF00) != 0x3300) {
|
|
|
|
if ((val & 0xFF) == 0x33) {
|
|
|
|
printk(KERN_WARNING
|
|
|
|
"%s: Detected possible byte-swapped interface"
|
|
|
|
" at IOADDR %p\n", CARDNAME, ioaddr);
|
|
|
|
}
|
|
|
|
retval = -ENODEV;
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The above MIGHT indicate a device, but I need to write to
|
|
|
|
* further test this.
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 0);
|
|
|
|
val = SMC_CURRENT_BANK(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if ((val & 0xFF00) != 0x3300) {
|
|
|
|
retval = -ENODEV;
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* well, we've already written once, so hopefully another
|
|
|
|
* time won't hurt. This time, I need to switch the bank
|
|
|
|
* register to bank 1, so I can access the base address
|
|
|
|
* register
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
val = SMC_GET_BASE(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
val = ((val & 0x1F00) >> 3) << SMC_IO_SHIFT;
|
2005-05-13 08:18:19 +08:00
|
|
|
if (((unsigned int)ioaddr & (0x3e0 << SMC_IO_SHIFT)) != val) {
|
2005-04-17 06:20:36 +08:00
|
|
|
printk("%s: IOADDR %p doesn't match configuration (%x).\n",
|
|
|
|
CARDNAME, ioaddr, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check if the revision register is something that I
|
|
|
|
* recognize. These might need to be added to later,
|
|
|
|
* as future revisions could be added.
|
|
|
|
*/
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 3);
|
|
|
|
revision_register = SMC_GET_REV(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
DBG(2, "%s: revision = 0x%04x\n", CARDNAME, revision_register);
|
|
|
|
version_string = chip_ids[ (revision_register >> 4) & 0xF];
|
|
|
|
if (!version_string || (revision_register & 0xff00) != 0x3300) {
|
|
|
|
/* I don't recognize this chip, so... */
|
|
|
|
printk("%s: IO %p: Unrecognized revision register 0x%04x"
|
|
|
|
", Contact author.\n", CARDNAME,
|
|
|
|
ioaddr, revision_register);
|
|
|
|
|
|
|
|
retval = -ENODEV;
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* At this point I'll assume that the chip is an SMC91x. */
|
|
|
|
if (version_printed++ == 0)
|
|
|
|
printk("%s", version);
|
|
|
|
|
|
|
|
/* fill in some of the fields */
|
|
|
|
dev->base_addr = (unsigned long)ioaddr;
|
|
|
|
lp->base = ioaddr;
|
|
|
|
lp->version = revision_register & 0xff;
|
|
|
|
spin_lock_init(&lp->lock);
|
|
|
|
|
|
|
|
/* Get the MAC address */
|
2008-02-22 18:55:05 +08:00
|
|
|
SMC_SELECT_BANK(lp, 1);
|
|
|
|
SMC_GET_MAC_ADDR(lp, dev->dev_addr);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* now, reset the chip, and put it into a known state */
|
|
|
|
smc_reset(dev);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If dev->irq is 0, then the device has to be banged on to see
|
|
|
|
* what the IRQ is.
|
|
|
|
*
|
|
|
|
* This banging doesn't always detect the IRQ, for unknown reasons.
|
|
|
|
* a workaround is to reset the chip and try again.
|
|
|
|
*
|
|
|
|
* Interestingly, the DOS packet driver *SETS* the IRQ on the card to
|
|
|
|
* be what is requested on the command line. I don't do that, mostly
|
|
|
|
* because the card that I have uses a non-standard method of accessing
|
|
|
|
* the IRQs, and because this _should_ work in most configurations.
|
|
|
|
*
|
|
|
|
* Specifying an IRQ is done with the assumption that the user knows
|
|
|
|
* what (s)he is doing. No checking is done!!!!
|
|
|
|
*/
|
|
|
|
if (dev->irq < 1) {
|
|
|
|
int trials;
|
|
|
|
|
|
|
|
trials = 3;
|
|
|
|
while (trials--) {
|
2008-02-22 18:55:05 +08:00
|
|
|
dev->irq = smc_findirq(lp);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (dev->irq)
|
|
|
|
break;
|
|
|
|
/* kick the card and try again */
|
|
|
|
smc_reset(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (dev->irq == 0) {
|
|
|
|
printk("%s: Couldn't autodetect your IRQ. Use irq=xx.\n",
|
|
|
|
dev->name);
|
|
|
|
retval = -ENODEV;
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
dev->irq = irq_canonicalize(dev->irq);
|
|
|
|
|
|
|
|
/* Fill in the fields of the device structure with ethernet values. */
|
|
|
|
ether_setup(dev);
|
|
|
|
|
|
|
|
dev->watchdog_timeo = msecs_to_jiffies(watchdog);
|
2009-01-27 13:32:25 +08:00
|
|
|
dev->netdev_ops = &smc_netdev_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
dev->ethtool_ops = &smc_ethtool_ops;
|
|
|
|
|
|
|
|
tasklet_init(&lp->tx_task, smc_hardware_send_pkt, (unsigned long)dev);
|
2006-12-06 03:36:26 +08:00
|
|
|
INIT_WORK(&lp->phy_configure, smc_phy_configure);
|
|
|
|
lp->dev = dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->mii.phy_id_mask = 0x1f;
|
|
|
|
lp->mii.reg_num_mask = 0x1f;
|
|
|
|
lp->mii.force_media = 0;
|
|
|
|
lp->mii.full_duplex = 0;
|
|
|
|
lp->mii.dev = dev;
|
|
|
|
lp->mii.mdio_read = smc_phy_read;
|
|
|
|
lp->mii.mdio_write = smc_phy_write;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Locate the phy, if any.
|
|
|
|
*/
|
|
|
|
if (lp->version >= (CHIP_91100 << 4))
|
|
|
|
smc_phy_detect(dev);
|
|
|
|
|
2005-10-05 23:10:24 +08:00
|
|
|
/* then shut everything down to save power */
|
|
|
|
smc_shutdown(dev);
|
|
|
|
smc_phy_powerdown(dev);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Set default parameters */
|
|
|
|
lp->msg_enable = NETIF_MSG_LINK;
|
|
|
|
lp->ctl_rfduplx = 0;
|
|
|
|
lp->ctl_rspeed = 10;
|
|
|
|
|
|
|
|
if (lp->version >= (CHIP_91100 << 4)) {
|
|
|
|
lp->ctl_rfduplx = 1;
|
|
|
|
lp->ctl_rspeed = 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Grab the IRQ */
|
2008-01-15 06:30:10 +08:00
|
|
|
retval = request_irq(dev->irq, &smc_interrupt, irq_flags, dev->name, dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (retval)
|
|
|
|
goto err_out;
|
|
|
|
|
2008-06-24 15:36:05 +08:00
|
|
|
#ifdef CONFIG_ARCH_PXA
|
|
|
|
# ifdef SMC_USE_PXA_DMA
|
|
|
|
lp->cfg.flags |= SMC91X_USE_DMA;
|
|
|
|
# endif
|
|
|
|
if (lp->cfg.flags & SMC91X_USE_DMA) {
|
2005-04-17 06:20:36 +08:00
|
|
|
int dma = pxa_request_dma(dev->name, DMA_PRIO_LOW,
|
|
|
|
smc_pxa_dma_irq, NULL);
|
|
|
|
if (dma >= 0)
|
|
|
|
dev->dma = dma;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
retval = register_netdev(dev);
|
|
|
|
if (retval == 0) {
|
|
|
|
/* now, print out the card info, in a short format.. */
|
|
|
|
printk("%s: %s (rev %d) at %p IRQ %d",
|
|
|
|
dev->name, version_string, revision_register & 0x0f,
|
|
|
|
lp->base, dev->irq);
|
|
|
|
|
|
|
|
if (dev->dma != (unsigned char)-1)
|
|
|
|
printk(" DMA %d", dev->dma);
|
|
|
|
|
2008-09-08 13:02:56 +08:00
|
|
|
printk("%s%s\n",
|
|
|
|
lp->cfg.flags & SMC91X_NOWAIT ? " [nowait]" : "",
|
2005-04-17 06:20:36 +08:00
|
|
|
THROTTLE_TX_PKTS ? " [throttle_tx]" : "");
|
|
|
|
|
|
|
|
if (!is_valid_ether_addr(dev->dev_addr)) {
|
|
|
|
printk("%s: Invalid ethernet MAC address. Please "
|
|
|
|
"set using ifconfig\n", dev->name);
|
|
|
|
} else {
|
|
|
|
/* Print the Ethernet address */
|
2008-10-28 06:59:26 +08:00
|
|
|
printk("%s: Ethernet addr: %pM\n",
|
|
|
|
dev->name, dev->dev_addr);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (lp->phy_type == 0) {
|
|
|
|
PRINTK("%s: No PHY found\n", dev->name);
|
|
|
|
} else if ((lp->phy_type & 0xfffffff0) == 0x0016f840) {
|
|
|
|
PRINTK("%s: PHY LAN83C183 (LAN91C111 Internal)\n", dev->name);
|
|
|
|
} else if ((lp->phy_type & 0xfffffff0) == 0x02821c50) {
|
|
|
|
PRINTK("%s: PHY LAN83C180\n", dev->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err_out:
|
2008-06-24 15:36:05 +08:00
|
|
|
#ifdef CONFIG_ARCH_PXA
|
2005-04-17 06:20:36 +08:00
|
|
|
if (retval && dev->dma != (unsigned char)-1)
|
|
|
|
pxa_free_dma(dev->dma);
|
|
|
|
#endif
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smc_enable_device(struct platform_device *pdev)
|
|
|
|
{
|
2008-02-22 18:55:15 +08:00
|
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
|
|
|
struct smc_local *lp = netdev_priv(ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned long flags;
|
|
|
|
unsigned char ecor, ecsr;
|
|
|
|
void __iomem *addr;
|
|
|
|
struct resource * res;
|
|
|
|
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-attrib");
|
|
|
|
if (!res)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Map the attribute space. This is overkill, but clean.
|
|
|
|
*/
|
|
|
|
addr = ioremap(res->start, ATTRIB_SIZE);
|
|
|
|
if (!addr)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reset the device. We must disable IRQs around this
|
|
|
|
* since a reset causes the IRQ line become active.
|
|
|
|
*/
|
|
|
|
local_irq_save(flags);
|
|
|
|
ecor = readb(addr + (ECOR << SMC_IO_SHIFT)) & ~ECOR_RESET;
|
|
|
|
writeb(ecor | ECOR_RESET, addr + (ECOR << SMC_IO_SHIFT));
|
|
|
|
readb(addr + (ECOR << SMC_IO_SHIFT));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wait 100us for the chip to reset.
|
|
|
|
*/
|
|
|
|
udelay(100);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The device will ignore all writes to the enable bit while
|
|
|
|
* reset is asserted, even if the reset bit is cleared in the
|
|
|
|
* same write. Must clear reset first, then enable the device.
|
|
|
|
*/
|
|
|
|
writeb(ecor, addr + (ECOR << SMC_IO_SHIFT));
|
|
|
|
writeb(ecor | ECOR_ENABLE, addr + (ECOR << SMC_IO_SHIFT));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the appropriate byte/word mode.
|
|
|
|
*/
|
|
|
|
ecsr = readb(addr + (ECSR << SMC_IO_SHIFT)) & ~ECSR_IOIS8;
|
2008-02-22 18:55:15 +08:00
|
|
|
if (!SMC_16BIT(lp))
|
2006-03-21 00:54:27 +08:00
|
|
|
ecsr |= ECSR_IOIS8;
|
2005-04-17 06:20:36 +08:00
|
|
|
writeb(ecsr, addr + (ECSR << SMC_IO_SHIFT));
|
|
|
|
local_irq_restore(flags);
|
|
|
|
|
|
|
|
iounmap(addr);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wait for the chip to wake up. We could poll the control
|
|
|
|
* register in the main register space, but that isn't mapped
|
|
|
|
* yet. We know this is going to take 750us.
|
|
|
|
*/
|
|
|
|
msleep(1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-06-24 13:38:50 +08:00
|
|
|
static int smc_request_attrib(struct platform_device *pdev,
|
|
|
|
struct net_device *ndev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-attrib");
|
2008-11-03 16:04:24 +08:00
|
|
|
struct smc_local *lp __maybe_unused = netdev_priv(ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!res)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!request_mem_region(res->start, ATTRIB_SIZE, CARDNAME))
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-06-24 13:38:50 +08:00
|
|
|
static void smc_release_attrib(struct platform_device *pdev,
|
|
|
|
struct net_device *ndev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-attrib");
|
2008-11-03 16:04:24 +08:00
|
|
|
struct smc_local *lp __maybe_unused = netdev_priv(ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (res)
|
|
|
|
release_mem_region(res->start, ATTRIB_SIZE);
|
|
|
|
}
|
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
static inline void smc_request_datacs(struct platform_device *pdev, struct net_device *ndev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-03-21 00:54:27 +08:00
|
|
|
if (SMC_CAN_USE_DATACS) {
|
|
|
|
struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-data32");
|
|
|
|
struct smc_local *lp = netdev_priv(ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
if (!res)
|
|
|
|
return;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
if(!request_mem_region(res->start, SMC_DATA_EXTENT, CARDNAME)) {
|
|
|
|
printk(KERN_INFO "%s: failed to request datacs memory region.\n", CARDNAME);
|
|
|
|
return;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
lp->datacs = ioremap(res->start, SMC_DATA_EXTENT);
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void smc_release_datacs(struct platform_device *pdev, struct net_device *ndev)
|
|
|
|
{
|
2006-03-21 00:54:27 +08:00
|
|
|
if (SMC_CAN_USE_DATACS) {
|
|
|
|
struct smc_local *lp = netdev_priv(ndev);
|
|
|
|
struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-data32");
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
if (lp->datacs)
|
|
|
|
iounmap(lp->datacs);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
lp->datacs = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 00:54:27 +08:00
|
|
|
if (res)
|
|
|
|
release_mem_region(res->start, SMC_DATA_EXTENT);
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* smc_init(void)
|
|
|
|
* Input parameters:
|
|
|
|
* dev->base_addr == 0, try to find all possible locations
|
|
|
|
* dev->base_addr > 0x1ff, this is the address to check
|
|
|
|
* dev->base_addr == <anything else>, return failure code
|
|
|
|
*
|
|
|
|
* Output:
|
|
|
|
* 0 --> there is a device
|
|
|
|
* anything else, error
|
|
|
|
*/
|
2008-11-23 01:36:14 +08:00
|
|
|
static int __devinit smc_drv_probe(struct platform_device *pdev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-02-22 18:55:15 +08:00
|
|
|
struct smc91x_platdata *pd = pdev->dev.platform_data;
|
|
|
|
struct smc_local *lp;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct net_device *ndev;
|
2008-01-15 06:30:10 +08:00
|
|
|
struct resource *res, *ires;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int __iomem *addr;
|
2008-06-06 17:13:02 +08:00
|
|
|
unsigned long irq_flags = SMC_IRQ_FLAGS;
|
2005-04-17 06:20:36 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ndev = alloc_etherdev(sizeof(struct smc_local));
|
|
|
|
if (!ndev) {
|
|
|
|
printk("%s: could not allocate device.\n", CARDNAME);
|
|
|
|
ret = -ENOMEM;
|
2008-06-24 13:38:50 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2005-11-10 06:32:44 +08:00
|
|
|
SET_NETDEV_DEV(ndev, &pdev->dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-02-22 18:55:15 +08:00
|
|
|
/* get configuration from platform data, only allow use of
|
|
|
|
* bus width if both SMC_CAN_USE_xxx and SMC91X_USE_xxx are set.
|
|
|
|
*/
|
|
|
|
|
|
|
|
lp = netdev_priv(ndev);
|
|
|
|
|
2008-06-24 13:38:50 +08:00
|
|
|
if (pd) {
|
2008-02-22 18:55:15 +08:00
|
|
|
memcpy(&lp->cfg, pd, sizeof(lp->cfg));
|
2008-06-24 13:38:50 +08:00
|
|
|
lp->io_shift = SMC91X_IO_SHIFT(lp->cfg.flags);
|
|
|
|
} else {
|
2008-06-19 17:19:57 +08:00
|
|
|
lp->cfg.flags |= (SMC_CAN_USE_8BIT) ? SMC91X_USE_8BIT : 0;
|
|
|
|
lp->cfg.flags |= (SMC_CAN_USE_16BIT) ? SMC91X_USE_16BIT : 0;
|
|
|
|
lp->cfg.flags |= (SMC_CAN_USE_32BIT) ? SMC91X_USE_32BIT : 0;
|
2008-06-19 17:39:03 +08:00
|
|
|
lp->cfg.flags |= (nowait) ? SMC91X_NOWAIT : 0;
|
2008-02-22 18:55:15 +08:00
|
|
|
}
|
|
|
|
|
2008-09-05 04:13:37 +08:00
|
|
|
if (!lp->cfg.leda && !lp->cfg.ledb) {
|
|
|
|
lp->cfg.leda = RPC_LSA_DEFAULT;
|
|
|
|
lp->cfg.ledb = RPC_LSB_DEFAULT;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
ndev->dma = (unsigned char)-1;
|
2008-01-15 06:30:10 +08:00
|
|
|
|
2008-06-24 13:38:50 +08:00
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-regs");
|
|
|
|
if (!res)
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
if (!res) {
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto out_free_netdev;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!request_mem_region(res->start, SMC_IO_EXTENT, CARDNAME)) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto out_free_netdev;
|
|
|
|
}
|
|
|
|
|
2008-01-15 06:30:10 +08:00
|
|
|
ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
|
|
if (!ires) {
|
2006-01-20 01:56:29 +08:00
|
|
|
ret = -ENODEV;
|
2008-06-24 13:38:50 +08:00
|
|
|
goto out_release_io;
|
2006-01-20 01:56:29 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-01-15 06:30:10 +08:00
|
|
|
ndev->irq = ires->start;
|
2008-06-06 17:13:02 +08:00
|
|
|
|
2009-11-28 08:13:23 +08:00
|
|
|
if (irq_flags == -1 || ires->flags & IRQF_TRIGGER_MASK)
|
2008-06-06 17:13:02 +08:00
|
|
|
irq_flags = ires->flags & IRQF_TRIGGER_MASK;
|
2008-01-15 06:30:10 +08:00
|
|
|
|
2008-06-24 13:38:50 +08:00
|
|
|
ret = smc_request_attrib(pdev, ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (ret)
|
2008-06-24 13:38:50 +08:00
|
|
|
goto out_release_io;
|
2005-04-17 06:20:36 +08:00
|
|
|
#if defined(CONFIG_SA1100_ASSABET)
|
|
|
|
NCR_0 |= NCR_ENET_OSC_EN;
|
|
|
|
#endif
|
2008-02-22 18:55:15 +08:00
|
|
|
platform_set_drvdata(pdev, ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
ret = smc_enable_device(pdev);
|
|
|
|
if (ret)
|
|
|
|
goto out_release_attrib;
|
|
|
|
|
|
|
|
addr = ioremap(res->start, SMC_IO_EXTENT);
|
|
|
|
if (!addr) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_release_attrib;
|
|
|
|
}
|
|
|
|
|
2008-06-24 15:36:05 +08:00
|
|
|
#ifdef CONFIG_ARCH_PXA
|
2007-09-02 04:27:18 +08:00
|
|
|
{
|
2005-04-17 06:20:36 +08:00
|
|
|
struct smc_local *lp = netdev_priv(ndev);
|
2007-09-02 04:27:18 +08:00
|
|
|
lp->device = &pdev->dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->physaddr = res->start;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-06-06 17:13:02 +08:00
|
|
|
ret = smc_probe(ndev, addr, irq_flags);
|
2007-09-02 04:27:18 +08:00
|
|
|
if (ret != 0)
|
|
|
|
goto out_iounmap;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
smc_request_datacs(pdev, ndev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_iounmap:
|
2005-11-10 06:32:44 +08:00
|
|
|
platform_set_drvdata(pdev, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
iounmap(addr);
|
|
|
|
out_release_attrib:
|
2008-06-24 13:38:50 +08:00
|
|
|
smc_release_attrib(pdev, ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
out_release_io:
|
|
|
|
release_mem_region(res->start, SMC_IO_EXTENT);
|
2008-06-24 13:38:50 +08:00
|
|
|
out_free_netdev:
|
|
|
|
free_netdev(ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
|
|
|
printk("%s: not found (%d).\n", CARDNAME, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-11-23 01:36:14 +08:00
|
|
|
static int __devexit smc_drv_remove(struct platform_device *pdev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-10 06:32:44 +08:00
|
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
2005-04-17 06:20:36 +08:00
|
|
|
struct smc_local *lp = netdev_priv(ndev);
|
|
|
|
struct resource *res;
|
|
|
|
|
2005-11-10 06:32:44 +08:00
|
|
|
platform_set_drvdata(pdev, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
unregister_netdev(ndev);
|
|
|
|
|
|
|
|
free_irq(ndev->irq, ndev);
|
|
|
|
|
2008-06-24 15:36:05 +08:00
|
|
|
#ifdef CONFIG_ARCH_PXA
|
2005-04-17 06:20:36 +08:00
|
|
|
if (ndev->dma != (unsigned char)-1)
|
|
|
|
pxa_free_dma(ndev->dma);
|
|
|
|
#endif
|
|
|
|
iounmap(lp->base);
|
|
|
|
|
|
|
|
smc_release_datacs(pdev,ndev);
|
2008-06-24 13:38:50 +08:00
|
|
|
smc_release_attrib(pdev,ndev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-regs");
|
|
|
|
if (!res)
|
2008-08-27 17:54:30 +08:00
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
release_mem_region(res->start, SMC_IO_EXTENT);
|
|
|
|
|
|
|
|
free_netdev(ndev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-10 06:32:44 +08:00
|
|
|
static int smc_drv_suspend(struct platform_device *dev, pm_message_t state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-10 06:32:44 +08:00
|
|
|
struct net_device *ndev = platform_get_drvdata(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-29 00:52:56 +08:00
|
|
|
if (ndev) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (netif_running(ndev)) {
|
|
|
|
netif_device_detach(ndev);
|
|
|
|
smc_shutdown(ndev);
|
|
|
|
smc_phy_powerdown(ndev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-10 06:32:44 +08:00
|
|
|
static int smc_drv_resume(struct platform_device *dev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-11-10 06:32:44 +08:00
|
|
|
struct net_device *ndev = platform_get_drvdata(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-29 00:52:56 +08:00
|
|
|
if (ndev) {
|
2005-04-17 06:20:36 +08:00
|
|
|
struct smc_local *lp = netdev_priv(ndev);
|
2005-11-10 06:32:44 +08:00
|
|
|
smc_enable_device(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (netif_running(ndev)) {
|
|
|
|
smc_reset(ndev);
|
|
|
|
smc_enable(ndev);
|
|
|
|
if (lp->phy_type != 0)
|
2006-12-06 03:36:26 +08:00
|
|
|
smc_phy_configure(&lp->phy_configure);
|
2005-04-17 06:20:36 +08:00
|
|
|
netif_device_attach(ndev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-10 06:32:44 +08:00
|
|
|
static struct platform_driver smc_driver = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.probe = smc_drv_probe,
|
2008-11-23 01:36:14 +08:00
|
|
|
.remove = __devexit_p(smc_drv_remove),
|
2005-04-17 06:20:36 +08:00
|
|
|
.suspend = smc_drv_suspend,
|
|
|
|
.resume = smc_drv_resume,
|
2005-11-10 06:32:44 +08:00
|
|
|
.driver = {
|
|
|
|
.name = CARDNAME,
|
2008-04-19 04:50:44 +08:00
|
|
|
.owner = THIS_MODULE,
|
2005-11-10 06:32:44 +08:00
|
|
|
},
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init smc_init(void)
|
|
|
|
{
|
2005-11-10 06:32:44 +08:00
|
|
|
return platform_driver_register(&smc_driver);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit smc_cleanup(void)
|
|
|
|
{
|
2005-11-10 06:32:44 +08:00
|
|
|
platform_driver_unregister(&smc_driver);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(smc_init);
|
|
|
|
module_exit(smc_cleanup);
|