Commit 9b425022 authored by Zwane Mwaikambo's avatar Zwane Mwaikambo Committed by Jeff Garzik

Add ethtool media support to smc91c92_cs net driver.

Also fixes a bug when UTP port is unplugged.
parent eda607d8
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net
smc91c92_cs.c 1.113 2001/10/13 00:08:53 smc91c92_cs.c 1.2 2002/09/28 15:00:00
This driver contains code written by Donald Becker This driver contains code written by Donald Becker
(becker@scyld.com), Rowan Hughes (x-csrdh@jcu.edu.au), (becker@scyld.com), Rowan Hughes (x-csrdh@jcu.edu.au),
...@@ -37,12 +37,15 @@ ...@@ -37,12 +37,15 @@
#include <linux/crc32.h> #include <linux/crc32.h>
#include <asm/io.h> #include <asm/io.h>
#include <asm/system.h> #include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/if_arp.h> #include <linux/if_arp.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/ethtool.h>
#include <linux/mii.h>
#include <pcmcia/version.h> #include <pcmcia/version.h>
#include <pcmcia/cs_types.h> #include <pcmcia/cs_types.h>
...@@ -88,6 +91,9 @@ static const char *version = ...@@ -88,6 +91,9 @@ static const char *version =
#define DEBUG(n, args...) #define DEBUG(n, args...)
#endif #endif
#define DRV_NAME "smc91c92_cs"
#define DRV_VERSION "1.2"
/*====================================================================*/ /*====================================================================*/
/* Operational parameter that usually are not changed. */ /* Operational parameter that usually are not changed. */
...@@ -109,6 +115,7 @@ static dev_link_t *dev_list; ...@@ -109,6 +115,7 @@ static dev_link_t *dev_list;
struct smc_private { struct smc_private {
dev_link_t link; dev_link_t link;
struct net_device dev; struct net_device dev;
spinlock_t lock;
u_short manfid; u_short manfid;
u_short cardid; u_short cardid;
struct net_device_stats stats; struct net_device_stats stats;
...@@ -122,7 +129,7 @@ struct smc_private { ...@@ -122,7 +129,7 @@ struct smc_private {
u_short media_status; u_short media_status;
u_short fast_poll; u_short fast_poll;
u_short link_status; u_short link_status;
int phy_id; struct mii_if_info mii_if;
}; };
/* Special definitions for Megahertz multifunction cards */ /* Special definitions for Megahertz multifunction cards */
...@@ -292,9 +299,11 @@ static int s9k_config(struct net_device *dev, struct ifmap *map); ...@@ -292,9 +299,11 @@ static int s9k_config(struct net_device *dev, struct ifmap *map);
static void smc_set_xcvr(struct net_device *dev, int if_port); static void smc_set_xcvr(struct net_device *dev, int if_port);
static void smc_reset(struct net_device *dev); static void smc_reset(struct net_device *dev);
static void media_check(u_long arg); static void media_check(u_long arg);
static void mdio_sync(ioaddr_t addr); static void smc_mdio_sync(ioaddr_t addr);
static int mdio_read(ioaddr_t addr, int phy_id, int loc); static int smc_mdio_read(struct net_device *dev, int phy_id, int loc);
static void mdio_write(ioaddr_t addr, int phy_id, int loc, int value); static void smc_mdio_write(struct net_device *dev, int phy_id, int loc, int value);
static int smc_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
static int smc_link_ok(struct net_device *dev);
/*====================================================================== /*======================================================================
...@@ -346,7 +355,7 @@ static dev_link_t *smc91c92_attach(void) ...@@ -346,7 +355,7 @@ static dev_link_t *smc91c92_attach(void)
if (!smc) return NULL; if (!smc) return NULL;
memset(smc, 0, sizeof(struct smc_private)); memset(smc, 0, sizeof(struct smc_private));
link = &smc->link; dev = &smc->dev; link = &smc->link; dev = &smc->dev;
spin_lock_init(&smc->lock);
link->release.function = &smc91c92_release; link->release.function = &smc91c92_release;
link->release.data = (u_long)link; link->release.data = (u_long)link;
link->io.NumPorts1 = 16; link->io.NumPorts1 = 16;
...@@ -369,6 +378,7 @@ static dev_link_t *smc91c92_attach(void) ...@@ -369,6 +378,7 @@ static dev_link_t *smc91c92_attach(void)
dev->get_stats = &smc91c92_get_stats; dev->get_stats = &smc91c92_get_stats;
dev->set_config = &s9k_config; dev->set_config = &s9k_config;
dev->set_multicast_list = &set_rx_mode; dev->set_multicast_list = &set_rx_mode;
dev->do_ioctl = &smc_ioctl;
ether_setup(dev); ether_setup(dev);
dev->open = &smc91c92_open; dev->open = &smc91c92_open;
dev->stop = &smc91c92_close; dev->stop = &smc91c92_close;
...@@ -378,6 +388,12 @@ static dev_link_t *smc91c92_attach(void) ...@@ -378,6 +388,12 @@ static dev_link_t *smc91c92_attach(void)
#endif #endif
dev->priv = link->priv = link->irq.Instance = smc; dev->priv = link->priv = link->irq.Instance = smc;
smc->mii_if.dev = dev;
smc->mii_if.mdio_read = smc_mdio_read;
smc->mii_if.mdio_write = smc_mdio_write;
smc->mii_if.phy_id_mask = 0x1f;
smc->mii_if.reg_num_mask = 0x1f;
/* Register with Card Services */ /* Register with Card Services */
link->next = dev_list; link->next = dev_list;
dev_list = link; dev_list = link;
...@@ -1044,10 +1060,10 @@ static void smc91c92_config(dev_link_t *link) ...@@ -1044,10 +1060,10 @@ static void smc91c92_config(dev_link_t *link)
SMC_SELECT_BANK(3); SMC_SELECT_BANK(3);
for (i = 0; i < 32; i++) { for (i = 0; i < 32; i++) {
j = mdio_read(dev->base_addr + MGMT, i, 1); j = smc_mdio_read(dev, i, 1);
if ((j != 0) && (j != 0xffff)) break; if ((j != 0) && (j != 0xffff)) break;
} }
smc->phy_id = (i < 32) ? i : -1; smc->mii_if.phy_id = (i < 32) ? i : -1;
if (i < 32) { if (i < 32) {
DEBUG(0, " MII transceiver at index %d, status %x.\n", i, j); DEBUG(0, " MII transceiver at index %d, status %x.\n", i, j);
} else { } else {
...@@ -1190,7 +1206,7 @@ static int smc91c92_event(event_t event, int priority, ...@@ -1190,7 +1206,7 @@ static int smc91c92_event(event_t event, int priority,
#define MDIO_DATA_WRITE1 (MDIO_DIR_WRITE | MDIO_DATA_OUT) #define MDIO_DATA_WRITE1 (MDIO_DIR_WRITE | MDIO_DATA_OUT)
#define MDIO_DATA_READ 0x02 #define MDIO_DATA_READ 0x02
static void mdio_sync(ioaddr_t addr) static void smc_mdio_sync(ioaddr_t addr)
{ {
int bits; int bits;
for (bits = 0; bits < 32; bits++) { for (bits = 0; bits < 32; bits++) {
...@@ -1199,12 +1215,13 @@ static void mdio_sync(ioaddr_t addr) ...@@ -1199,12 +1215,13 @@ static void mdio_sync(ioaddr_t addr)
} }
} }
static int mdio_read(ioaddr_t addr, int phy_id, int loc) static int smc_mdio_read(struct net_device *dev, int phy_id, int loc)
{ {
ioaddr_t addr = dev->base_addr + MGMT;
u_int cmd = (0x06<<10)|(phy_id<<5)|loc; u_int cmd = (0x06<<10)|(phy_id<<5)|loc;
int i, retval = 0; int i, retval = 0;
mdio_sync(addr); smc_mdio_sync(addr);
for (i = 13; i >= 0; i--) { for (i = 13; i >= 0; i--) {
int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
outb(dat, addr); outb(dat, addr);
...@@ -1218,12 +1235,13 @@ static int mdio_read(ioaddr_t addr, int phy_id, int loc) ...@@ -1218,12 +1235,13 @@ static int mdio_read(ioaddr_t addr, int phy_id, int loc)
return (retval>>1) & 0xffff; return (retval>>1) & 0xffff;
} }
static void mdio_write(ioaddr_t addr, int phy_id, int loc, int value) static void smc_mdio_write(struct net_device *dev, int phy_id, int loc, int value)
{ {
ioaddr_t addr = dev->base_addr + MGMT;
u_int cmd = (0x05<<28)|(phy_id<<23)|(loc<<18)|(1<<17)|value; u_int cmd = (0x05<<28)|(phy_id<<23)|(loc<<18)|(1<<17)|value;
int i; int i;
mdio_sync(addr); smc_mdio_sync(addr);
for (i = 31; i >= 0; i--) { for (i = 31; i >= 0; i--) {
int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
outb(dat, addr); outb(dat, addr);
...@@ -1777,6 +1795,7 @@ static void fill_multicast_tbl(int count, struct dev_mc_list *addrs, ...@@ -1777,6 +1795,7 @@ static void fill_multicast_tbl(int count, struct dev_mc_list *addrs,
static void set_rx_mode(struct net_device *dev) static void set_rx_mode(struct net_device *dev)
{ {
ioaddr_t ioaddr = dev->base_addr; ioaddr_t ioaddr = dev->base_addr;
struct smc_private *smc = dev->priv;
u_int multicast_table[ 2 ] = { 0, }; u_int multicast_table[ 2 ] = { 0, };
unsigned long flags; unsigned long flags;
u_short rx_cfg_setting; u_short rx_cfg_setting;
...@@ -1795,15 +1814,14 @@ static void set_rx_mode(struct net_device *dev) ...@@ -1795,15 +1814,14 @@ static void set_rx_mode(struct net_device *dev)
} }
/* Load MC table and Rx setting into the chip without interrupts. */ /* Load MC table and Rx setting into the chip without interrupts. */
save_flags(flags); spin_lock_irqsave(&smc->lock, flags);
cli();
SMC_SELECT_BANK(3); SMC_SELECT_BANK(3);
outl(multicast_table[0], ioaddr + MULTICAST0); outl(multicast_table[0], ioaddr + MULTICAST0);
outl(multicast_table[1], ioaddr + MULTICAST4); outl(multicast_table[1], ioaddr + MULTICAST4);
SMC_SELECT_BANK(0); SMC_SELECT_BANK(0);
outw(rx_cfg_setting, ioaddr + RCR); outw(rx_cfg_setting, ioaddr + RCR);
SMC_SELECT_BANK(2); SMC_SELECT_BANK(2);
restore_flags(flags); spin_unlock_irqrestore(&smc->lock, flags);
return; return;
} }
...@@ -1917,11 +1935,11 @@ static void smc_reset(struct net_device *dev) ...@@ -1917,11 +1935,11 @@ static void smc_reset(struct net_device *dev)
SMC_SELECT_BANK(3); SMC_SELECT_BANK(3);
/* Reset MII */ /* Reset MII */
mdio_write(ioaddr + MGMT, smc->phy_id, 0, 0x8000); smc_mdio_write(dev, smc->mii_if.phy_id, 0, 0x8000);
/* Restart MII autonegotiation */ /* Restart MII autonegotiation */
mdio_write(ioaddr + MGMT, smc->phy_id, 0, 0x0000); smc_mdio_write(dev, smc->mii_if.phy_id, 0, 0x0000);
mdio_write(ioaddr + MGMT, smc->phy_id, 0, 0x1200); smc_mdio_write(dev, smc->mii_if.phy_id, 0, 0x1200);
} }
/* Enable interrupts. */ /* Enable interrupts. */
...@@ -1942,7 +1960,6 @@ static void media_check(u_long arg) ...@@ -1942,7 +1960,6 @@ static void media_check(u_long arg)
struct net_device *dev = &smc->dev; struct net_device *dev = &smc->dev;
ioaddr_t ioaddr = dev->base_addr; ioaddr_t ioaddr = dev->base_addr;
u_short i, media, saved_bank; u_short i, media, saved_bank;
ioaddr_t mii_addr = dev->base_addr + MGMT;
u_short link; u_short link;
saved_bank = inw(ioaddr + BANK_SELECT); saved_bank = inw(ioaddr + BANK_SELECT);
...@@ -1974,20 +1991,20 @@ static void media_check(u_long arg) ...@@ -1974,20 +1991,20 @@ static void media_check(u_long arg)
} }
if (smc->cfg & CFG_MII_SELECT) { if (smc->cfg & CFG_MII_SELECT) {
if (smc->phy_id < 0) if (smc->mii_if.phy_id < 0)
goto reschedule; goto reschedule;
SMC_SELECT_BANK(3); SMC_SELECT_BANK(3);
link = mdio_read(mii_addr, smc->phy_id, 1); link = smc_mdio_read(dev, smc->mii_if.phy_id, 1);
if (!link || (link == 0xffff)) { if (!link || (link == 0xffff)) {
printk(KERN_INFO "%s: MII is missing!\n", dev->name); printk(KERN_INFO "%s: MII is missing!\n", dev->name);
smc->phy_id = -1; smc->mii_if.phy_id = -1;
goto reschedule; goto reschedule;
} }
link &= 0x0004; link &= 0x0004;
if (link != smc->link_status) { if (link != smc->link_status) {
u_short p = mdio_read(mii_addr, smc->phy_id, 5); u_short p = smc_mdio_read(dev, smc->mii_if.phy_id, 5);
printk(KERN_INFO "%s: %s link beat\n", dev->name, printk(KERN_INFO "%s: %s link beat\n", dev->name,
(link) ? "found" : "lost"); (link) ? "found" : "lost");
if (link) { if (link) {
...@@ -2043,6 +2060,191 @@ static void media_check(u_long arg) ...@@ -2043,6 +2060,191 @@ static void media_check(u_long arg)
SMC_SELECT_BANK(saved_bank); SMC_SELECT_BANK(saved_bank);
} }
static int smc_link_ok(struct net_device *dev)
{
ioaddr_t ioaddr = dev->base_addr;
struct smc_private *smc = dev->priv;
if (smc->cfg & CFG_MII_SELECT) {
return mii_link_ok(&smc->mii_if);
} else {
SMC_SELECT_BANK(0);
return inw(ioaddr + EPH) & EPH_LINK_OK;
}
}
static int smc_netdev_get_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd)
{
u16 tmp;
ioaddr_t ioaddr = dev->base_addr;
ecmd->supported = (SUPPORTED_TP | SUPPORTED_AUI |
SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full);
SMC_SELECT_BANK(1);
tmp = inw(ioaddr + CONFIG);
ecmd->port = (tmp & CFG_AUI_SELECT) ? PORT_AUI : PORT_TP;
ecmd->transceiver = XCVR_INTERNAL;
ecmd->speed = SPEED_10;
ecmd->phy_address = ioaddr + MGMT;
SMC_SELECT_BANK(0);
tmp = inw(ioaddr + TCR);
ecmd->duplex = (tmp & TCR_FDUPLX) ? DUPLEX_FULL : DUPLEX_HALF;
return 0;
}
static int smc_netdev_set_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd)
{
u16 tmp;
ioaddr_t ioaddr = dev->base_addr;
if (ecmd->speed != SPEED_10)
return -EINVAL;
if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL)
return -EINVAL;
if (ecmd->port != PORT_TP && ecmd->port != PORT_AUI)
return -EINVAL;
if (ecmd->transceiver != XCVR_INTERNAL)
return -EINVAL;
if (ecmd->port == PORT_AUI)
smc_set_xcvr(dev, 1);
else
smc_set_xcvr(dev, 0);
SMC_SELECT_BANK(0);
tmp = inw(ioaddr + TCR);
if (ecmd->duplex == DUPLEX_FULL)
tmp |= TCR_FDUPLX;
else
tmp &= ~TCR_FDUPLX;
outw(ioaddr + TCR, tmp);
return 0;
}
static int smc_ethtool_ioctl (struct net_device *dev, void *useraddr)
{
u32 ethcmd;
struct smc_private *smc = dev->priv;
if (get_user(ethcmd, (u32 *)useraddr))
return -EFAULT;
switch (ethcmd) {
case ETHTOOL_GDRVINFO: {
struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO };
strcpy(info.driver, DRV_NAME);
strcpy(info.version, DRV_VERSION);
if (copy_to_user(useraddr, &info, sizeof(info)))
return -EFAULT;
return 0;
}
/* get settings */
case ETHTOOL_GSET: {
int ret;
struct ethtool_cmd ecmd = { ETHTOOL_GSET };
spin_lock_irq(&smc->lock);
if (smc->cfg & CFG_MII_SELECT)
ret = mii_ethtool_gset(&smc->mii_if, &ecmd);
else
ret = smc_netdev_get_ecmd(dev, &ecmd);
spin_unlock_irq(&smc->lock);
if (copy_to_user(useraddr, &ecmd, sizeof(ecmd)))
return -EFAULT;
return ret;
}
/* set settings */
case ETHTOOL_SSET: {
int ret;
struct ethtool_cmd ecmd;
if (copy_from_user(&ecmd, useraddr, sizeof(ecmd)))
return -EFAULT;
spin_lock_irq(&smc->lock);
if (smc->cfg & CFG_MII_SELECT)
ret = mii_ethtool_sset(&smc->mii_if, &ecmd);
else
ret = smc_netdev_set_ecmd(dev, &ecmd);
spin_unlock_irq(&smc->lock);
return ret;
}
/* get link status */
case ETHTOOL_GLINK: {
struct ethtool_value edata = { ETHTOOL_GLINK };
spin_lock_irq(&smc->lock);
edata.data = smc_link_ok(dev);
spin_unlock_irq(&smc->lock);
if (copy_to_user(useraddr, &edata, sizeof(edata)))
return -EFAULT;
return 0;
}
#ifdef PCMCIA_DEBUG
/* get message-level */
case ETHTOOL_GMSGLVL: {
struct ethtool_value edata = { ETHTOOL_GMSGLVL };
edata.data = pc_debug;
if (copy_to_user(useraddr, &edata, sizeof(edata)))
return -EFAULT;
return 0;
}
/* set message-level */
case ETHTOOL_SMSGLVL: {
struct ethtool_value edata;
if (copy_from_user(&edata, useraddr, sizeof(edata)))
return -EFAULT;
pc_debug = edata.data;
return 0;
}
#endif
/* restart autonegotiation */
case ETHTOOL_NWAY_RST: {
if (smc->cfg & CFG_MII_SELECT)
return mii_nway_restart(&smc->mii_if);
else
return -EOPNOTSUPP;
}
default:
break;
}
return -EOPNOTSUPP;
}
static int smc_ioctl (struct net_device *dev, struct ifreq *rq, int cmd)
{
struct smc_private *smc = dev->priv;
struct mii_ioctl_data *mii;
int rc = 0;
mii = (struct mii_ioctl_data *) &rq->ifr_data;
if (!netif_running(dev))
return -EINVAL;
switch (cmd) {
case SIOCETHTOOL:
rc = smc_ethtool_ioctl(dev, (void *) rq->ifr_data);
break;
default:
spin_lock_irq(&smc->lock);
rc = generic_mii_ioctl(&smc->mii_if, mii, cmd, NULL);
spin_unlock_irq(&smc->lock);
break;
}
return rc;
}
/*====================================================================*/ /*====================================================================*/
static int __init init_smc91c92_cs(void) static int __init init_smc91c92_cs(void)
...@@ -2069,3 +2271,4 @@ static void __exit exit_smc91c92_cs(void) ...@@ -2069,3 +2271,4 @@ static void __exit exit_smc91c92_cs(void)
module_init(init_smc91c92_cs); module_init(init_smc91c92_cs);
module_exit(exit_smc91c92_cs); module_exit(exit_smc91c92_cs);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment