summaryrefslogtreecommitdiffstats
path: root/drivers/net/fs_enet/fs_enet-mii.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/fs_enet/fs_enet-mii.c')
-rw-r--r--drivers/net/fs_enet/fs_enet-mii.c507
1 files changed, 507 insertions, 0 deletions
diff --git a/drivers/net/fs_enet/fs_enet-mii.c b/drivers/net/fs_enet/fs_enet-mii.c
new file mode 100644
index 000000000000..c6770377ef87
--- /dev/null
+++ b/drivers/net/fs_enet/fs_enet-mii.c
@@ -0,0 +1,507 @@
+/*
+ * 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);
+}