/* * Combined Ethernet driver for Motorola MPC8xx and MPC82xx. * * Copyright (c) 2003 Intracom S.A. * by Pantelis Antoniou * * 2005 (c) MontaVista Software, Inc. * Vitaly Bordug * * Heavily based on original FEC driver by Dan Malek * and modifications by Joakim Tjernlund * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fs_enet.h" /*************************************************/ /* * Generic PHY support. * Should work for all PHYs, but link change is detected by polling */ static void generic_timer_callback(unsigned long data) { struct net_device *dev = (struct net_device *)data; struct fs_enet_private *fep = netdev_priv(dev); fep->phy_timer_list.expires = jiffies + HZ / 2; add_timer(&fep->phy_timer_list); fs_mii_link_status_change_check(dev, 0); } static void generic_startup(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); fep->phy_timer_list.expires = jiffies + HZ / 2; /* every 500ms */ fep->phy_timer_list.data = (unsigned long)dev; fep->phy_timer_list.function = generic_timer_callback; add_timer(&fep->phy_timer_list); } static void generic_shutdown(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); del_timer_sync(&fep->phy_timer_list); } /* ------------------------------------------------------------------------- */ /* The Davicom DM9161 is used on the NETTA board */ /* register definitions */ #define MII_DM9161_ANAR 4 /* Aux. Config Register */ #define MII_DM9161_ACR 16 /* Aux. Config Register */ #define MII_DM9161_ACSR 17 /* Aux. Config/Status Register */ #define MII_DM9161_10TCSR 18 /* 10BaseT Config/Status Reg. */ #define MII_DM9161_INTR 21 /* Interrupt Register */ #define MII_DM9161_RECR 22 /* Receive Error Counter Reg. */ #define MII_DM9161_DISCR 23 /* Disconnect Counter Register */ static void dm9161_startup(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); fs_mii_write(dev, fep->mii_if.phy_id, MII_DM9161_INTR, 0x0000); /* Start autonegotiation */ fs_mii_write(dev, fep->mii_if.phy_id, MII_BMCR, 0x1200); set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ*8); } static void dm9161_ack_int(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); fs_mii_read(dev, fep->mii_if.phy_id, MII_DM9161_INTR); } static void dm9161_shutdown(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); fs_mii_write(dev, fep->mii_if.phy_id, MII_DM9161_INTR, 0x0f00); } /**********************************************************************************/ static const struct phy_info phy_info[] = { { .id = 0x00181b88, .name = "DM9161", .startup = dm9161_startup, .ack_int = dm9161_ack_int, .shutdown = dm9161_shutdown, }, { .id = 0, .name = "GENERIC", .startup = generic_startup, .shutdown = generic_shutdown, }, }; /**********************************************************************************/ static int phy_id_detect(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); const struct fs_platform_info *fpi = fep->fpi; struct fs_enet_mii_bus *bus = fep->mii_bus; int i, r, start, end, phytype, physubtype; const struct phy_info *phy; int phy_hwid, phy_id; phy_hwid = -1; fep->phy = NULL; /* auto-detect? */ if (fpi->phy_addr == -1) { start = 1; end = 32; } else { /* direct */ start = fpi->phy_addr; end = start + 1; } for (phy_id = start; phy_id < end; phy_id++) { /* skip already used phy addresses on this bus */ if (bus->usage_map & (1 << phy_id)) continue; r = fs_mii_read(dev, phy_id, MII_PHYSID1); if (r == -1 || (phytype = (r & 0xffff)) == 0xffff) continue; r = fs_mii_read(dev, phy_id, MII_PHYSID2); if (r == -1 || (physubtype = (r & 0xffff)) == 0xffff) continue; phy_hwid = (phytype << 16) | physubtype; if (phy_hwid != -1) break; } if (phy_hwid == -1) { printk(KERN_ERR DRV_MODULE_NAME ": %s No PHY detected! range=0x%02x-0x%02x\n", dev->name, start, end); return -1; } for (i = 0, phy = phy_info; i < ARRAY_SIZE(phy_info); i++, phy++) if (phy->id == (phy_hwid >> 4) || phy->id == 0) break; if (i >= ARRAY_SIZE(phy_info)) { printk(KERN_ERR DRV_MODULE_NAME ": %s PHY id 0x%08x is not supported!\n", dev->name, phy_hwid); return -1; } fep->phy = phy; /* mark this address as used */ bus->usage_map |= (1 << phy_id); printk(KERN_INFO DRV_MODULE_NAME ": %s Phy @ 0x%x, type %s (0x%08x)%s\n", dev->name, phy_id, fep->phy->name, phy_hwid, fpi->phy_addr == -1 ? " (auto-detected)" : ""); return phy_id; } void fs_mii_startup(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); if (fep->phy->startup) (*fep->phy->startup) (dev); } void fs_mii_shutdown(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); if (fep->phy->shutdown) (*fep->phy->shutdown) (dev); } void fs_mii_ack_int(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); if (fep->phy->ack_int) (*fep->phy->ack_int) (dev); } #define MII_LINK 0x0001 #define MII_HALF 0x0002 #define MII_FULL 0x0004 #define MII_BASE4 0x0008 #define MII_10M 0x0010 #define MII_100M 0x0020 #define MII_1G 0x0040 #define MII_10G 0x0080 /* return full mii info at one gulp, with a usable form */ static unsigned int mii_full_status(struct mii_if_info *mii) { unsigned int status; int bmsr, adv, lpa, neg; struct fs_enet_private* fep = netdev_priv(mii->dev); /* first, a dummy read, needed to latch some MII phys */ (void)mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR); bmsr = mii->mdio_read(mii->dev, mii->phy_id, MII_BMSR); /* no link */ if ((bmsr & BMSR_LSTATUS) == 0) return 0; status = MII_LINK; /* Lets look what ANEG says if it's supported - otherwize we shall take the right values from the platform info*/ if(!mii->force_media) { /* autoneg not completed; don't bother */ if ((bmsr & BMSR_ANEGCOMPLETE) == 0) return 0; adv = (*mii->mdio_read)(mii->dev, mii->phy_id, MII_ADVERTISE); lpa = (*mii->mdio_read)(mii->dev, mii->phy_id, MII_LPA); neg = lpa & adv; } else { neg = fep->fpi->bus_info->lpa; } if (neg & LPA_100FULL) status |= MII_FULL | MII_100M; else if (neg & LPA_100BASE4) status |= MII_FULL | MII_BASE4 | MII_100M; else if (neg & LPA_100HALF) status |= MII_HALF | MII_100M; else if (neg & LPA_10FULL) status |= MII_FULL | MII_10M; else status |= MII_HALF | MII_10M; return status; } void fs_mii_link_status_change_check(struct net_device *dev, int init_media) { struct fs_enet_private *fep = netdev_priv(dev); struct mii_if_info *mii = &fep->mii_if; unsigned int mii_status; int ok_to_print, link, duplex, speed; unsigned long flags; ok_to_print = netif_msg_link(fep); mii_status = mii_full_status(mii); if (!init_media && mii_status == fep->last_mii_status) return; fep->last_mii_status = mii_status; link = !!(mii_status & MII_LINK); duplex = !!(mii_status & MII_FULL); speed = (mii_status & MII_100M) ? 100 : 10; if (link == 0) { netif_carrier_off(mii->dev); netif_stop_queue(dev); if (!init_media) { spin_lock_irqsave(&fep->lock, flags); (*fep->ops->stop)(dev); spin_unlock_irqrestore(&fep->lock, flags); } if (ok_to_print) printk(KERN_INFO "%s: link down\n", mii->dev->name); } else { mii->full_duplex = duplex; netif_carrier_on(mii->dev); spin_lock_irqsave(&fep->lock, flags); fep->duplex = duplex; fep->speed = speed; (*fep->ops->restart)(dev); spin_unlock_irqrestore(&fep->lock, flags); netif_start_queue(dev); if (ok_to_print) printk(KERN_INFO "%s: link up, %dMbps, %s-duplex\n", dev->name, speed, duplex ? "full" : "half"); } } /**********************************************************************************/ int fs_mii_read(struct net_device *dev, int phy_id, int location) { struct fs_enet_private *fep = netdev_priv(dev); struct fs_enet_mii_bus *bus = fep->mii_bus; unsigned long flags; int ret; spin_lock_irqsave(&bus->mii_lock, flags); ret = (*bus->mii_read)(bus, phy_id, location); spin_unlock_irqrestore(&bus->mii_lock, flags); return ret; } void fs_mii_write(struct net_device *dev, int phy_id, int location, int value) { struct fs_enet_private *fep = netdev_priv(dev); struct fs_enet_mii_bus *bus = fep->mii_bus; unsigned long flags; spin_lock_irqsave(&bus->mii_lock, flags); (*bus->mii_write)(bus, phy_id, location, value); spin_unlock_irqrestore(&bus->mii_lock, flags); } /*****************************************************************************/ /* list of all registered mii buses */ static LIST_HEAD(fs_mii_bus_list); static struct fs_enet_mii_bus *lookup_bus(int method, int id) { struct list_head *ptr; struct fs_enet_mii_bus *bus; list_for_each(ptr, &fs_mii_bus_list) { bus = list_entry(ptr, struct fs_enet_mii_bus, list); if (bus->bus_info->method == method && bus->bus_info->id == id) return bus; } return NULL; } static struct fs_enet_mii_bus *create_bus(const struct fs_mii_bus_info *bi) { struct fs_enet_mii_bus *bus; int ret = 0; bus = kmalloc(sizeof(*bus), GFP_KERNEL); if (bus == NULL) { ret = -ENOMEM; goto err; } memset(bus, 0, sizeof(*bus)); spin_lock_init(&bus->mii_lock); bus->bus_info = bi; bus->refs = 0; bus->usage_map = 0; /* perform initialization */ switch (bi->method) { case fsmii_fixed: ret = fs_mii_fixed_init(bus); if (ret != 0) goto err; break; case fsmii_bitbang: ret = fs_mii_bitbang_init(bus); if (ret != 0) goto err; break; #ifdef CONFIG_FS_ENET_HAS_FEC case fsmii_fec: ret = fs_mii_fec_init(bus); if (ret != 0) goto err; break; #endif default: ret = -EINVAL; goto err; } list_add(&bus->list, &fs_mii_bus_list); return bus; err: kfree(bus); return ERR_PTR(ret); } static void destroy_bus(struct fs_enet_mii_bus *bus) { /* remove from bus list */ list_del(&bus->list); /* nothing more needed */ kfree(bus); } int fs_mii_connect(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); const struct fs_platform_info *fpi = fep->fpi; struct fs_enet_mii_bus *bus = NULL; /* check method validity */ switch (fpi->bus_info->method) { case fsmii_fixed: case fsmii_bitbang: break; #ifdef CONFIG_FS_ENET_HAS_FEC case fsmii_fec: break; #endif default: printk(KERN_ERR DRV_MODULE_NAME ": %s Unknown MII bus method (%d)!\n", dev->name, fpi->bus_info->method); return -EINVAL; } bus = lookup_bus(fpi->bus_info->method, fpi->bus_info->id); /* if not found create new bus */ if (bus == NULL) { bus = create_bus(fpi->bus_info); if (IS_ERR(bus)) { printk(KERN_ERR DRV_MODULE_NAME ": %s MII bus creation failure!\n", dev->name); return PTR_ERR(bus); } } bus->refs++; fep->mii_bus = bus; fep->mii_if.dev = dev; fep->mii_if.phy_id_mask = 0x1f; fep->mii_if.reg_num_mask = 0x1f; fep->mii_if.mdio_read = fs_mii_read; fep->mii_if.mdio_write = fs_mii_write; fep->mii_if.force_media = fpi->bus_info->disable_aneg; fep->mii_if.phy_id = phy_id_detect(dev); return 0; } void fs_mii_disconnect(struct net_device *dev) { struct fs_enet_private *fep = netdev_priv(dev); struct fs_enet_mii_bus *bus = NULL; bus = fep->mii_bus; fep->mii_bus = NULL; if (--bus->refs <= 0) destroy_bus(bus); }