| /* |
| * Combined Ethernet driver for Motorola MPC8xx and MPC82xx. |
| * |
| * Copyright (c) 2003 Intracom S.A. |
| * by Pantelis Antoniou <panto@intracom.gr> |
| * |
| * 2005 (c) MontaVista Software, Inc. |
| * Vitaly Bordug <vbordug@ru.mvista.com> |
| * |
| * Heavily based on original FEC driver by Dan Malek <dan@embeddededge.com> |
| * and modifications by Joakim Tjernlund <joakim.tjernlund@lumentis.se> |
| * |
| * 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 <linux/config.h> |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/string.h> |
| #include <linux/ptrace.h> |
| #include <linux/errno.h> |
| #include <linux/ioport.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/spinlock.h> |
| #include <linux/mii.h> |
| #include <linux/ethtool.h> |
| #include <linux/bitops.h> |
| |
| #include <asm/pgtable.h> |
| #include <asm/irq.h> |
| #include <asm/uaccess.h> |
| |
| #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: |
| if (bus) |
| 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); |
| } |