]> git.neil.brown.name Git - history.git/commitdiff
Add ethtool media support to smc91c92_cs net driver.
authorZwane Mwaikambo <zwane@linuxpower.ca>
Fri, 11 Oct 2002 09:07:22 +0000 (05:07 -0400)
committerJeff Garzik <jgarzik@mandrakesoft.com>
Fri, 11 Oct 2002 09:07:22 +0000 (05:07 -0400)
Also fixes a bug when UTP port is unplugged.

drivers/net/pcmcia/smc91c92_cs.c

index 8b6d3851fe3d2eee357a9f7d54cb2f4aade97be2..aabba81220777ec3b415f7b63bd1434c63212f9d 100644 (file)
@@ -8,7 +8,7 @@
 
     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
     (becker@scyld.com), Rowan Hughes (x-csrdh@jcu.edu.au),
 #include <linux/crc32.h>
 #include <asm/io.h>
 #include <asm/system.h>
+#include <asm/uaccess.h>
 
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/skbuff.h>
 #include <linux/if_arp.h>
 #include <linux/ioport.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
 
 #include <pcmcia/version.h>
 #include <pcmcia/cs_types.h>
@@ -88,6 +91,9 @@ static const char *version =
 #define DEBUG(n, args...)
 #endif
 
+#define DRV_NAME       "smc91c92_cs"
+#define DRV_VERSION    "1.2"
+
 /*====================================================================*/
 
 /* Operational parameter that usually are not changed. */
@@ -109,6 +115,7 @@ static dev_link_t *dev_list;
 struct smc_private {
     dev_link_t                 link;
     struct net_device          dev;
+    spinlock_t                 lock;
     u_short                    manfid;
     u_short                    cardid;
     struct net_device_stats    stats;
@@ -122,7 +129,7 @@ struct smc_private {
     u_short                    media_status;
     u_short                    fast_poll;
     u_short                    link_status;
-    int                                phy_id;
+    struct mii_if_info         mii_if;
 };
 
 /* Special definitions for Megahertz multifunction cards */
@@ -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_reset(struct net_device *dev);
 static void media_check(u_long arg);
-static void mdio_sync(ioaddr_t addr);
-static int mdio_read(ioaddr_t addr, int phy_id, int loc);
-static void mdio_write(ioaddr_t addr, int phy_id, int loc, int value);
+static void smc_mdio_sync(ioaddr_t addr);
+static int smc_mdio_read(struct net_device *dev, int phy_id, int loc);
+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)
     if (!smc) return NULL;
     memset(smc, 0, sizeof(struct smc_private));
     link = &smc->link; dev = &smc->dev;
-
+    spin_lock_init(&smc->lock);
     link->release.function = &smc91c92_release;
     link->release.data = (u_long)link;
     link->io.NumPorts1 = 16;
@@ -369,6 +378,7 @@ static dev_link_t *smc91c92_attach(void)
     dev->get_stats = &smc91c92_get_stats;
     dev->set_config = &s9k_config;
     dev->set_multicast_list = &set_rx_mode;
+    dev->do_ioctl = &smc_ioctl;
     ether_setup(dev);
     dev->open = &smc91c92_open;
     dev->stop = &smc91c92_close;
@@ -377,6 +387,12 @@ static dev_link_t *smc91c92_attach(void)
     dev->watchdog_timeo = TX_TIMEOUT;
 #endif
     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 */
     link->next = dev_list;
@@ -1044,10 +1060,10 @@ static void smc91c92_config(dev_link_t *link)
        SMC_SELECT_BANK(3);
 
        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;
        }
-       smc->phy_id = (i < 32) ? i : -1;
+       smc->mii_if.phy_id = (i < 32) ? i : -1;
        if (i < 32) {
            DEBUG(0, "  MII transceiver at index %d, status %x.\n", i, j);
        } else {
@@ -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_READ         0x02
 
-static void mdio_sync(ioaddr_t addr)
+static void smc_mdio_sync(ioaddr_t addr)
 {
     int bits;
     for (bits = 0; bits < 32; bits++) {
@@ -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;
     int i, retval = 0;
 
-    mdio_sync(addr);
+    smc_mdio_sync(addr);
     for (i = 13; i >= 0; i--) {
        int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
        outb(dat, addr);
@@ -1218,12 +1235,13 @@ static int mdio_read(ioaddr_t addr, int phy_id, int loc)
     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;
     int i;
 
-    mdio_sync(addr);
+    smc_mdio_sync(addr);
     for (i = 31; i >= 0; i--) {
        int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
        outb(dat, addr);
@@ -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)
 {
     ioaddr_t ioaddr = dev->base_addr;
+    struct smc_private *smc = dev->priv;
     u_int multicast_table[ 2 ] = { 0, };
     unsigned long flags;
     u_short rx_cfg_setting;
@@ -1795,16 +1814,15 @@ static void set_rx_mode(struct net_device *dev)
     }
     
     /* Load MC table and Rx setting into the chip without interrupts. */
-    save_flags(flags);
-    cli();
+    spin_lock_irqsave(&smc->lock, flags);
     SMC_SELECT_BANK(3);
     outl(multicast_table[0], ioaddr + MULTICAST0);
     outl(multicast_table[1], ioaddr + MULTICAST4);
     SMC_SELECT_BANK(0);
     outw(rx_cfg_setting, ioaddr + RCR);
     SMC_SELECT_BANK(2);
-    restore_flags(flags);
-    
+    spin_unlock_irqrestore(&smc->lock, flags);
     return;
 }
 
@@ -1917,11 +1935,11 @@ static void smc_reset(struct net_device *dev)
        SMC_SELECT_BANK(3);
 
        /* 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 */
-       mdio_write(ioaddr + MGMT, smc->phy_id, 0, 0x0000);
-       mdio_write(ioaddr + MGMT, smc->phy_id, 0, 0x1200);
+       smc_mdio_write(dev, smc->mii_if.phy_id, 0, 0x0000);
+       smc_mdio_write(dev, smc->mii_if.phy_id, 0, 0x1200);
     }
 
     /* Enable interrupts. */
@@ -1942,7 +1960,6 @@ static void media_check(u_long arg)
     struct net_device *dev = &smc->dev;
     ioaddr_t ioaddr = dev->base_addr;
     u_short i, media, saved_bank;
-    ioaddr_t mii_addr = dev->base_addr + MGMT;
     u_short link;
 
     saved_bank = inw(ioaddr + BANK_SELECT);
@@ -1974,20 +1991,20 @@ static void media_check(u_long arg)
     }
 
     if (smc->cfg & CFG_MII_SELECT) {
-       if (smc->phy_id < 0)
+       if (smc->mii_if.phy_id < 0)
            goto reschedule;
 
        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)) {
            printk(KERN_INFO "%s: MII is missing!\n", dev->name);
-           smc->phy_id = -1;
+           smc->mii_if.phy_id = -1;
            goto reschedule;
        }
 
        link &= 0x0004;
        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,
                (link) ? "found" : "lost");
            if (link) {
@@ -2043,6 +2060,191 @@ reschedule:
     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)
@@ -2069,3 +2271,4 @@ static void __exit exit_smc91c92_cs(void)
 
 module_init(init_smc91c92_cs);
 module_exit(exit_smc91c92_cs);
+