netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Jason McMullan <jason.mcmullan@timesys.com>
To: Andy Fleming <afleming@freescale.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>,
	Netdev <netdev@oss.sgi.com>
Subject: [PATCH] MII bus API for PHY devices Rev 2.0
Date: Thu, 02 Dec 2004 13:29:22 -0500	[thread overview]
Message-ID: <1102012163.6056.39.camel@jmcmullan> (raw)
In-Reply-To: <B2AF8092-3A87-11D9-B023-000393C30512@freescale.com>

[-- Attachment #1: Type: text/plain, Size: 229 bytes --]

Ok, given previous input, I now release mii-bus 2.0

	* CIS8201 interrupt support
	* The PHY device api is now similar to 'sungem' 
	* Set up your PHY as autoneg or forced speeds.

-- 
Jason McMullan <jason.mcmullan@timesys.com>

[-- Attachment #2: driver-mii-bus.patch --]
[-- Type: text/x-patch, Size: 42946 bytes --]

#### Auto-generated patch ####
Signed-off-by: Jason McMullan <jmcmullan@timesys.com>
Date:          Thu, 02 Dec 2004 13:20:49 -0500
Description:   MII Bus interface
Depends:

###############################

Index of changes:

 drivers/net/Makefile            |    4 
 linux/drivers/net/mii_bitbang.c |  134 ++++++++
 linux/drivers/net/mii_bitbang.h |   40 ++
 linux/drivers/net/mii_bus.c     |  639 ++++++++++++++++++++++++++++++++++++++++
 linux/drivers/net/phy_cicada.c  |  177 +++++++++++
 linux/drivers/net/phy_davicom.c |  140 ++++++++
 linux/drivers/net/phy_lxt97x.c  |  210 +++++++++++++
 linux/drivers/net/phy_marvell.c |  125 +++++++
 linux/include/linux/mii_bus.h   |  191 +++++++++++
 9 files changed, 1659 insertions(+), 1 deletion(-)


--- linux-orig/drivers/net/Makefile
+++ linux/drivers/net/Makefile
@@ -62,7 +62,9 @@
 # end link order section
 #
 
-obj-$(CONFIG_MII) += mii.o
+obj-$(CONFIG_MII) += mii.o mii_bus.o mii_bitbang.o \
+		     phy_davicom.o phy_marvell.o phy_cicada.o \
+		     phy_lxt97x.o
 
 obj-$(CONFIG_SUNDANCE) += sundance.o
 obj-$(CONFIG_HAMACHI) += hamachi.o
--- /dev/null
+++ linux/drivers/net/mii_bitbang.c
@@ -0,0 +1,134 @@
+/* 
+ * drivers/net/mii_bitbang.c
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <asm/string.h>
+
+#include "mii_bitbang.h"
+
+#undef PHY_READ
+#undef PHY_WRITE
+#define PHY_READ	0
+#define PHY_WRITE	1
+
+/* 
+ * 1st byte: 0101PPPP on writes, where PPPP is the MSB of the phy-id
+ *           0110PPPP on reads, where  PPPP is the MSB of the phy-id
+ * 2nd byte: PRRRRR10, P is the LSB of the phy-id, R is the register
+ * 3rd,4th bytes: control on writes, values on reads
+ */
+
+static inline void mii_bitbang_mark(void *priv)
+{
+	struct mii_bitbang *info = priv;
+	int i;
+
+	/* Write preamble */
+	for (i = 0; i < 32; i++)
+		info->send(info->priv, 1);
+}
+
+static inline void mii_bitbang_phy_id(void *priv, int phy_id, int reg, int is_write)
+{
+	struct mii_bitbang *info = priv;
+	int i;
+
+	/* Preamble */
+	info->send(info->priv,0);
+	info->send(info->priv,1);
+	if (is_write) {
+		info->send(info->priv,0);
+		info->send(info->priv,1);
+	} else {
+		info->send(info->priv,1);
+		info->send(info->priv,0);
+	}
+
+	/* Write PHY addr */
+	for (i = 0; i < 5; i++)
+		info->send(info->priv, (phy_id >> (4-i)) & 1);
+
+	/* Write the register */
+	for (i = 0; i < 5; i++)
+		info->send(info->priv, (reg >> (4-i)) & 1);
+
+	info->send(info->priv,1);
+	info->send(info->priv,0);
+}
+
+static int mii_bitbang_read(void *priv, int phy_id, int reg)
+{
+	struct mii_bitbang *info = priv;
+	int i;
+	int retval=0;
+
+	mii_bitbang_mark(priv);
+	mii_bitbang_phy_id(priv, phy_id, reg, PHY_READ);
+
+	for (i = 0; i < 16; i++)
+		retval = (retval << 1) | (info->recv(info->priv) & 1);
+
+	mii_bitbang_mark(priv);
+
+	return retval;
+}
+
+static int mii_bitbang_write(void *priv, int phy_id, int reg, uint16_t val)
+{
+	struct mii_bitbang *info = priv;
+	int i;
+
+	mii_bitbang_mark(priv);
+	mii_bitbang_phy_id(priv, phy_id, reg, PHY_WRITE);
+
+	for (i=0; i < 16; i++)
+		info->send(info->priv, (val >> (15-i)) & 1);
+
+	mii_bitbang_mark(priv);
+
+	return 0;
+}
+
+static void mii_bitbang_reset(void *priv)
+{
+	struct mii_bitbang *info = priv;
+
+	info->reset(info->priv);
+}
+
+/* Creates a bitbang MII bus
+ * Returns < 0 on error, otherwise a bus ID
+ */
+int mii_bitbang_register(struct mii_bitbang *info)
+{
+	memset(&info->bus, 0, sizeof(info->bus));
+
+	info->bus.name = info->name;
+	info->bus.priv = info;
+	info->bus.read = mii_bitbang_read;
+	info->bus.write = mii_bitbang_write;
+	info->bus.reset = mii_bitbang_reset;
+
+	return mii_bus_register(&info->bus);
+}
+
+
+/* Unregisters a bitbang MII bus
+ */
+void mii_bitbang_unregister(struct mii_bitbang *info)
+{
+	mii_bus_unregister(&info->bus);
+}
+
+

--- /dev/null
+++ linux/drivers/net/mii_bitbang.h
@@ -0,0 +1,40 @@
+/* 
+ * drivers/net/mii_bitbang.h
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#ifndef _NET_MII_BITBANG_H
+#define _NET_MII_BITBANG_H
+
+#include <linux/mii_bus.h>
+
+struct mii_bitbang {
+	const char *name;	/* Name of device */
+	void *priv;		/* Private data */
+
+	void (*send)(void *priv, int bit);	/* Send one bit */
+	int (*recv)(void *priv);		/* Recv one bit */
+	void (*reset)(void *priv);
+
+	/* Auto-filled-in information */
+	struct mii_bus bus;
+};
+
+/* Creates a bitbang MII bus
+ * Returns < 0 on error, otherwise a bus ID
+ */
+extern int mii_bitbang_register(struct mii_bitbang *info);
+
+/* Unregisters a bitbang MII bus
+ */
+extern void mii_bitbang_unregister(struct mii_bitbang *info);
+
+#endif /* _NET_MII_BITBANG_H */

--- /dev/null
+++ linux/drivers/net/mii_bus.c
@@ -0,0 +1,639 @@
+/* 
+ * drivers/net/mii_bus.c
+ *
+ * Adapeted from drivers/net/gianfar_mii.c, by Andy Fleming
+ *
+ * Author: Jason McMullan (jason.mcmullan@timesys.com) to 
+ * 	   be a generic mii interface
+ *
+ * Copyright (c) 2004 Timesys Inc
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/interrupt.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/mm.h>
+#include <linux/mii_bus.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <linux/module.h>
+
+#undef DEBUG
+
+static struct mii_bus *mii_bus[8];
+
+#define MII_BUS_MAX	(sizeof(mii_bus)/sizeof(struct mii_bus *))
+
+static inline struct phy_info *mii_phy_of(struct mii_if_info *mii)
+{
+	if (mii != NULL) {
+		int bus,id;
+		bus = MII_BUS(mii->phy_id);
+		id  = MII_ID(mii->phy_id);
+		return mii_bus[bus]->phy[id];
+	}
+
+	return NULL;
+}
+
+/* Write value to the PHY for this device to the register at regnum,
+ * waiting until the write is done before it returns.  All PHY 
+ * configuration has to be done through the TSEC1 MIIM regs */
+EXPORT_SYMBOL(mii_bus_write);
+int mii_bus_write(int bus, int id, int regnum, uint16_t value)
+{
+	if (mii_bus[bus] == NULL)
+		return -EINVAL;
+
+#ifdef DEBUG
+	printk(KERN_INFO "phy%d.%d: Write 0x%.2x <- 0x%.4x\n", bus, id, regnum, value);
+#endif
+	mii_bus[bus]->write(mii_bus[bus]->priv, id, regnum, value);
+	return 0;
+}
+
+/* Reads from register regnum in the PHY for device dev,
+ * returning the value.  Clears miimcom first.  All PHY
+ * configuration has to be done through the TSEC1 MIIM regs */
+EXPORT_SYMBOL(mii_bus_read);
+int mii_bus_read(int bus, int id, int regnum)
+{
+	if (mii_bus[bus] == NULL)
+		return -EINVAL;
+
+#ifdef DEBUG
+	{
+		int rv;
+		rv = mii_bus[bus]->read(mii_bus[bus]->priv, id, regnum);
+		printk(KERN_INFO "phy%d.%d: Read  0x%.2x -> 0x%.4x\n", bus, id, regnum, rv);
+		return rv;
+	}
+#else
+	return mii_bus[bus]->read(mii_bus[bus]->priv, id, regnum);
+#endif
+}
+
+/* Helper function */
+static int phy_set_autoneg(struct phy_info *phy, uint32_t advertise)
+{
+	int err;
+
+	err = phy->ops->set_autoneg(phy, advertise);
+	if (err < 0)
+		return err;
+
+	phy->negotiate.advertise = advertise;
+	phy->negotiate.autoneg = AUTONEG_ENABLE;
+	phy->negotiate.timeout = jiffies + MII_TIMEOUT;
+	phy->state.autoneg = 1;
+
+	return 0;
+}
+
+#define BRIEF_MII_ERRORS
+EXPORT_SYMBOL(phy_gen_poll);
+/* Wait for auto-negotiation to complete */
+int phy_gen_poll(struct phy_info *phy)
+{
+	struct phy_state *pstate;
+	uint16_t val;
+
+	pstate = &phy->state;
+
+	phy_read(phy, MII_BMSR);	/* Dummy read */
+	val = phy_read(phy, MII_BMSR);
+
+	/* If the link just came up, restart the auto-neg procedure
+	 */
+	if (val & BMSR_LSTATUS) {
+		if (pstate->link == 0 && 
+		    pstate->autoneg == 0 && phy->negotiate.autoneg) {
+			phy_set_autoneg(phy, phy->negotiate.advertise);
+			return -EAGAIN;
+		}
+		pstate->link = 1;
+	} else {
+		pstate->link = 0;
+	}
+
+	/* Only auto-negotiate if the link has just gone up */
+	if (pstate->link && pstate->autoneg && 
+	    (time_after(jiffies,phy->negotiate.timeout) || 
+	     (val & BMSR_ANEGCOMPLETE))) {
+#ifdef BRIEF_MII_ERRORS
+		if (val & BMSR_ANEGCOMPLETE) {
+			printk(KERN_INFO "%s: Auto-negotiation done\n",
+			       phy->name);
+		} else {
+			printk(KERN_INFO
+			       "%s: Auto-negotiation timed out\n",
+			       phy->name);
+		}
+#endif
+
+		pstate->autoneg = 0;
+
+		if (val & BMSR_ANEGCOMPLETE) {
+			val = phy_read(phy, MII_LPA);
+			val &= phy_read(phy, MII_ADVERTISE);
+
+			/* According to IEEE 802.3, LPA decisions
+			 * must be done in this order
+			 */
+			if (val & LPA_100FULL) {
+				pstate->speed = SPEED_100;
+				pstate->duplex = DUPLEX_FULL;
+			} else if (val & LPA_100HALF) {
+				pstate->speed = SPEED_100;
+				pstate->duplex = DUPLEX_HALF;
+			} else if (val & LPA_10FULL) {
+				pstate->speed = SPEED_10;
+				pstate->duplex = DUPLEX_FULL;
+			} else if (val & LPA_10HALF) {
+				pstate->speed = SPEED_10;
+				pstate->duplex = DUPLEX_HALF;
+			} else {
+				pstate->speed = SPEED_10;
+				pstate->duplex = DUPLEX_HALF;
+			}
+		}
+	}
+
+	return (pstate->autoneg ? -EAGAIN : 0);
+}
+
+static struct phy_ops gen_ops = {
+	.set_autoneg = phy_gen_set_autoneg,
+	.poll = phy_gen_poll
+};
+
+static struct phy_info phy_info_generic = {
+	.id = 0x0,
+	.name = "Generic PHY",
+	.shift = 32,
+	.ops = &gen_ops
+};
+
+static LIST_HEAD(phy_list);
+
+/* Use the PHY ID registers to determine what type of PHY is attached
+ * to device dev.  return a struct phy_info structure describing that PHY
+ */
+struct phy_info *mii_phy_get_info(int bus, int id)
+{
+	struct list_head *lp;
+	uint16_t phy_reg;
+	uint32_t phy_id;
+	struct phy_info *info = NULL;
+
+	if (mii_bus[bus] == NULL)
+		return NULL;
+
+	/* Grab the bits from PHYIR1, and put them in the upper half */
+	phy_reg = mii_bus_read(bus, id, MII_PHYSID1);
+	phy_id = (phy_reg & 0xffff) << 16;
+
+	/* Grab the bits from PHYIR2, and put them in the lower half */
+	phy_reg = mii_bus_read(bus, id, MII_PHYSID2);
+	phy_id |= (phy_reg & 0xffff);
+
+	/* loop through all the known PHY types, and find one that
+	 * matches the ID we read from the PHY. */
+	list_for_each(lp, &phy_list) {
+		struct phy_info *phy = list_entry(lp, struct phy_info, list);
+		if ((phy->id >> phy->shift) == (phy_id >> phy->shift)) {
+			info = phy;
+			break;
+		}
+	}
+
+	if (info == NULL) {
+		printk(KERN_WARNING
+		       "phy%d.%d: PHY id 0x%x is not supported!\n", bus, id,
+		       phy_id);
+	} else {
+		printk(KERN_INFO "phy%d.%d: PHY is %s (%x)\n", bus, id,
+		       info->name, phy_id);
+	}
+
+	return info;
+}
+
+static int mdio_read(struct net_device *dev, int phy_id, int reg)
+{
+	return mii_bus_read(MII_BUS(phy_id), MII_ID(phy_id), reg);
+}
+
+static void mdio_write(struct net_device *dev, int phy_id, int reg, int val)
+{
+	mii_bus_write(MII_BUS(phy_id), MII_ID(phy_id), reg, val & 0xffff);
+}
+
+static inline void mii_phy_irq_ack(struct mii_if_info *mii)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	phy->ops->int_ack(phy);
+}
+
+static irqreturn_t mii_phy_irq(int irq, void *data, struct pt_regs *regs)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+
+	mii_phy_irq_ack(mii);
+
+	/* Schedule the bottom half */
+	schedule_work(&phy->delta.tq);
+
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(mii_phy_irq_enable);
+int mii_phy_irq_enable(struct mii_if_info *mii, int irq, void (*func) (void *),
+		       void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	int err;
+
+	if (phy == NULL)
+		return -EINVAL;
+
+	if (phy->delta.data != NULL)
+		return -EBUSY;
+
+	if (phy->ops->int_ack == NULL ||
+	    phy->ops->int_enable == NULL ||
+	    phy->ops->int_disable == NULL)
+		return -EINVAL;
+
+	phy->delta.irq = irq;
+	phy->delta.func = func;
+	phy->delta.data = data;
+
+	err = request_irq(irq, mii_phy_irq, SA_SHIRQ, phy->name, mii);
+	if (err < 0) {
+		phy->delta.irq = -1;
+		phy->delta.func = NULL;
+		phy->delta.data = NULL;
+		return err;
+	}
+
+	phy->ops->int_enable(phy);
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_irq_disable);
+void mii_phy_irq_disable(struct mii_if_info *mii, void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL || phy->delta.data != data)
+		return;
+
+	phy->ops->int_disable(phy);
+
+	free_irq(phy->delta.irq, mii);
+	phy->delta.irq = -1;
+	phy->delta.func = NULL;
+	phy->delta.data = NULL;
+}
+
+/* Scheduled by the task queue */
+static void mii_phy_delta(void *data)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+	struct phy_state old;
+
+	old=phy->state;
+
+	phy->ops->poll(phy);
+
+	if (memcmp(&old,&phy->state,sizeof(old)) != 0 && phy->delta.func)
+		phy->delta.func(phy->delta.data);
+}
+
+static void mii_phy_poll(unsigned long data)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+
+	schedule_work(&phy->delta.tq);
+
+	mod_timer(&phy->delta.timer, jiffies + HZ * phy->delta.msecs / 1000);
+}
+
+EXPORT_SYMBOL(mii_phy_poll_enable);
+int mii_phy_poll_enable(struct mii_if_info *mii, unsigned long msecs,
+			void (*func) (void *), void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL)
+		return -EINVAL;
+
+	if (HZ * msecs / 1000 <= 0 || func == NULL)
+		return -EINVAL;
+
+	if (phy->delta.data != NULL)
+		return -EINVAL;
+
+	init_timer(&phy->delta.timer);
+	phy->delta.timer.function = mii_phy_poll;
+	phy->delta.timer.data = (unsigned long)mii;
+	phy->delta.data = data;
+	phy->delta.func = func;
+	phy->delta.msecs = msecs;
+	mod_timer(&phy->delta.timer, jiffies + HZ * msecs / 1000);
+	schedule_work(&phy->delta.tq);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_poll_disable);
+void mii_phy_poll_disable(struct mii_if_info *mii, void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL || phy->delta.data == NULL)
+		return;
+
+	del_timer_sync(&phy->delta.timer);
+	phy->delta.func = NULL;
+	phy->delta.data = NULL;
+}
+
+EXPORT_SYMBOL(phy_gen_set_autoneg);
+int phy_gen_set_autoneg(struct phy_info *phy, uint32_t advertise)
+{
+	uint16_t adv, ctl;
+
+	adv = phy_read(phy, MII_ADVERTISE);
+	adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
+	if (advertise & ADVERTISED_10baseT_Half)
+		adv |= ADVERTISE_10HALF;
+	if (advertise & ADVERTISED_10baseT_Full)
+		adv |= ADVERTISE_10FULL;
+	if (advertise & ADVERTISED_100baseT_Half)
+		adv |= ADVERTISE_100HALF;
+	if (advertise & ADVERTISED_100baseT_Full)
+		adv |= ADVERTISE_100FULL;
+
+	/* Configure some basic stuff */
+	phy_write(phy, MII_ADVERTISE, adv);
+
+	/* Start auto negotiation */
+	ctl = phy_read(phy, MII_BMCR);
+	ctl |= (BMCR_ANENABLE | BMCR_ANRESTART);
+	phy_write(phy, MII_BMCR, ctl);
+
+	return 0;
+}
+
+
+EXPORT_SYMBOL(mii_phy_attach);
+int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev, int bus,
+		   int id)
+{
+	struct phy_info *phy, *info;
+
+	if (mii_bus[bus] == NULL) {
+		printk(KERN_ERR
+		       "mii_phy_attach: Can't attach %s, no MII bus %d present\n",
+		       dev->name, bus);
+		return -ENODEV;
+	}
+
+	if (mii_bus[bus]->phy[id] != NULL) {
+		printk(KERN_ERR
+		       "mii_phy_attach: phy%d.%d is already attached to %s\n",
+		       bus, id, dev->name);
+		return -EBUSY;
+	}
+
+	info = mii_phy_get_info(bus, id);
+	if (info == NULL)
+		return -ENODEV;
+
+	phy = kmalloc(sizeof(*phy), GFP_KERNEL);
+	if (phy == NULL)
+		return -ENOMEM;
+
+	memcpy(phy, info, sizeof(*phy));
+
+	INIT_WORK(&phy->delta.tq, mii_phy_delta, mii);
+	snprintf(&phy->name[0],sizeof(phy->name),"phy%d.%d",bus,id);
+	phy->phy_id = MII_PHY_ID(bus, id);
+	phy->delta.func = NULL;
+	phy->delta.data = NULL;
+	phy->delta.irq = -1;
+	phy->state.link = 0;
+	phy->state.duplex = DUPLEX_HALF;
+	phy->state.speed = SPEED_10;
+	phy->negotiate.autoneg = 0;
+	phy->negotiate.advertise = 0;
+
+	memset(mii, 0, sizeof(*mii));
+	mii->phy_id = (bus << 5) | id;
+	mii->phy_id_mask = 0xff;
+	mii->reg_num_mask = 0x1f;
+	mii->dev = dev;
+	mii->mdio_read = mdio_read;
+	mii->mdio_write = mdio_write;
+
+	mii_bus[bus]->phy[id] = phy;
+
+	if (phy->ops->init != NULL)
+		phy->ops->init(phy);
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_detach);
+void mii_phy_detach(struct mii_if_info *mii)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	struct mii_bus *pbus;
+
+	if (phy == NULL)
+		return;
+
+	pbus = mii_bus[MII_BUS(phy->phy_id)];
+
+	if (phy->delta.data != NULL) {
+		if (phy->delta.irq < 0)
+			mii_phy_poll_disable(mii, phy->delta.data);
+		else
+			mii_phy_irq_disable(mii, phy->delta.data);
+	}
+
+	pbus->phy[MII_ID(phy->phy_id)] = NULL;
+	kfree(phy);
+}
+
+EXPORT_SYMBOL(mii_phy_state);
+int mii_phy_state(struct mii_if_info *mii, struct phy_state *state)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	int err = 0;
+
+	if (phy == NULL)
+		return -EINVAL;
+
+	if (phy->delta.func == NULL)
+		err = phy->ops->poll(phy);
+
+	memcpy(state, &phy->state, sizeof(*state));
+
+	return err;
+}
+
+EXPORT_SYMBOL(mii_phy_set_autoneg);
+int mii_phy_set_autoneg(struct mii_if_info *mii, uint32_t advertise)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL || phy->ops->set_autoneg == NULL)
+		return -EINVAL;
+
+	return phy_set_autoneg(phy, advertise);
+}
+
+EXPORT_SYMBOL(mii_phy_set_forced);
+int mii_phy_set_forced(struct mii_if_info *mii, int speed, int duplex)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	int err = 0;
+
+	if (phy == NULL)
+		return -EINVAL;
+
+	if (phy->ops->set_forced)
+		err = phy->ops->set_forced(phy, speed, duplex);
+
+	if (err < 0)
+		return err;
+
+	phy->negotiate.autoneg = AUTONEG_DISABLE;
+	phy->state.speed = speed;
+	phy->state.duplex = duplex;
+	phy->state.autoneg = 0;
+
+	return 0;
+}
+
+static DECLARE_MUTEX(mii_bus_lock);
+
+EXPORT_SYMBOL(mii_bus_register);
+int mii_bus_register(struct mii_bus *bus)
+{
+	int bus_id;
+
+	if (bus == NULL || bus->name == NULL || bus->read == NULL ||
+	    bus->write == NULL)
+		return -EINVAL;
+
+	down(&mii_bus_lock);
+
+	for (bus_id = 0; bus_id < MII_BUS_MAX; bus_id++) {
+		if (mii_bus[bus_id] == NULL)
+			break;
+	}
+
+	if (bus_id >= MII_BUS_MAX) {
+		bus_id = -ENOMEM;
+		goto end;
+	}
+
+	mii_bus[bus_id] = bus;
+
+	if (bus->reset)
+		bus->reset(bus->priv);
+
+	printk(KERN_INFO "%s: registered as PHY bus %d\n", bus->name, bus_id);
+
+      end:
+	up(&mii_bus_lock);
+
+	return bus_id;
+}
+
+EXPORT_SYMBOL(mii_bus_unregister);
+void mii_bus_unregister(struct mii_bus *bus)
+{
+	int i;
+
+	down(&mii_bus_lock);
+
+	for (i = 0; i < MII_BUS_MAX; i++) {
+		if (mii_bus[i] == bus) {
+			mii_bus[i] = NULL;
+			break;
+		}
+	}
+
+	up(&mii_bus_lock);
+}
+
+/* Insert into 'phy_list' sorted by
+ * shift (smallest to largest)
+ */
+EXPORT_SYMBOL(phy_register);
+int phy_register(struct phy_info *info)
+{
+	struct list_head *lp;
+
+	if (info==NULL || info->ops == NULL || info->ops->poll == NULL)
+		return -EINVAL;
+
+	list_for_each(lp, &phy_list) {
+		struct phy_info *phy = list_entry(lp, struct phy_info, list);
+		if (phy->shift > info->shift)
+			break;
+
+		/* Check for duplicates */
+		if ((phy->shift==info->shift) && (info->id == phy->id))
+			return -EBUSY;
+	}
+
+	/* This does the 'right thing' even if lp == &phy_list
+	 */
+	list_add_tail(&info->list, lp);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(phy_unregister);
+void phy_unregister(struct phy_info *info)
+{
+	list_del_init(&info->list);
+}
+
+static int mii_bus_init(void)
+{
+	return phy_register(&phy_info_generic);
+}
+
+static void mii_bus_exit(void)
+{
+	phy_unregister(&phy_info_generic);
+}
+
+module_init(mii_bus_init);
+module_exit(mii_bus_exit);

--- /dev/null
+++ linux/drivers/net/phy_cicada.c
@@ -0,0 +1,177 @@
+/* 
+ * drivers/net/phy_cicada.c
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mii_bus.h>
+
+/* Cicada Auxiliary Control/Status Register */
+#define MIIM_CIS8201_AUX_CONSTAT        0x1c
+#define MIIM_CIS8201_AUXCONSTAT_INIT    0x0004
+#define MIIM_CIS8201_AUXCONSTAT_DUPLEX  0x0020
+#define MIIM_CIS8201_AUXCONSTAT_SPEED   0x0018
+#define MIIM_CIS8201_AUXCONSTAT_GBIT    0x0010
+#define MIIM_CIS8201_AUXCONSTAT_100     0x0008
+                                                                                
+/* Cicada Extended Control Register 1 */
+#define MIIM_CIS8201_EXT_CON1           0x17
+#define MIIM_CIS8201_EXTCON1_INIT       0x0000
+
+/* CIS8201 */
+#define MII_CIS8201_EPCR        0x17
+#define EPCR_MODE_MASK          0x3000
+#define EPCR_GMII_MODE          0x0000
+#define EPCR_RGMII_MODE         0x1000
+#define EPCR_TBI_MODE           0x2000
+#define EPCR_RTBI_MODE          0x3000
+
+static int cis8201_init(struct phy_info *phy)
+{
+	uint16_t epcr;
+	const char *mode = "Unknown";
+
+	epcr = phy_read(phy, MII_CIS8201_EPCR);
+
+	switch (epcr & EPCR_MODE_MASK) {
+		case EPCR_GMII_MODE: mode = "GMII"; break;
+		case EPCR_RGMII_MODE: mode = "RGMII"; break;
+		case EPCR_TBI_MODE: mode = "TBI"; break;
+		case EPCR_RTBI_MODE: mode = "RTBI"; break;
+	}
+
+	printk(KERN_INFO "%s: %s mode\n",phy->name, mode);
+
+	return 0;
+}
+
+#define MII_CIS8201_INTR_CTRL	0x19
+#define MII_CIS8201_INTR_STAT	0x1a
+
+#define MII_CIS8201_INTR_ENABLE	0x8000
+#define MII_CIS8201_INTR_SPEED	0x4000
+#define MII_CIS8201_INTR_LINK	0x2000
+#define MII_CIS8201_INTR_DUPLEX	0x1000
+#define MII_CIS8201_INTR_AN_ERR	0x0800
+#define MII_CIS8201_INTR_AN_DON	0x0400
+#define MII_CIS8201_INTR_ALL	0x7c00
+
+static int cis8201_int_enable(struct phy_info *phy)
+{
+	phy_write(phy, MII_CIS8201_INTR_CTRL, MII_CIS8201_INTR_ENABLE | MII_CIS8201_INTR_ALL);
+
+	return 0;
+}
+
+static int cis8201_int_disable(struct phy_info *phy)
+{
+	phy_write(phy, MII_CIS8201_INTR_CTRL, 0);
+
+	return 0;
+}
+
+static int cis8201_int_ack(struct phy_info *phy)
+{
+	phy_read(phy, MII_CIS8201_INTR_STAT);
+
+	return 0;
+}
+
+#define	MII_CIS8201_ACSR	0x1c
+#define  ACSR_ENABLE_1000BASET	0x0004
+#define  ACSR_DUPLEX_STATUS	0x0020
+#define  ACSR_SPEED_1000BASET	0x0010
+#define  ACSR_SPEED_100BASET	0x0008
+
+static int cis8201_poll(struct phy_info *phy)
+{
+	uint16_t acsr;
+	struct phy_state *pstate = &phy->state;
+	int autoneg = phy->state.autoneg;
+	int err;
+
+	err = phy_gen_poll(phy);
+	if (err < 0)
+		return err;
+
+	if (pstate->link == 0)
+		return 0;
+
+	/* We use the old copy of 'phy->state.autoneg'
+	 * as phy_gen_poll will have set it to 0
+	 */
+	if (autoneg) {
+		acsr = phy_read(phy, MII_CIS8201_ACSR);
+
+		if (acsr & ACSR_DUPLEX_STATUS)
+			pstate->duplex = DUPLEX_FULL;
+		else
+			pstate->duplex = DUPLEX_HALF;
+		if (acsr & ACSR_SPEED_1000BASET) {
+			pstate->speed = SPEED_1000;
+		} else if (acsr & ACSR_SPEED_100BASET)
+			pstate->speed = SPEED_100;
+		else
+			pstate->speed = SPEED_10;
+	}
+
+	/* On non-aneg, we assume what we put in BMCR is the speed,
+	 * though magic-aneg shouldn't prevent this case from occurring
+	 */
+
+	return 0;
+}
+
+static int cis8201_set_autoneg(struct phy_info *phy, uint32_t advertise)
+{
+	uint16_t val;
+
+	/* Do the 1000BT setup here. */
+	val = phy_read(phy, MII_CIS8201_ACSR);
+	if (advertise & ADVERTISED_1000baseT_Full)
+		phy_write(phy, MII_CIS8201_ACSR, val | ACSR_ENABLE_1000BASET);
+	else
+		phy_write(phy, MII_CIS8201_ACSR, val & ~ACSR_ENABLE_1000BASET);
+
+	return phy_gen_set_autoneg(phy, advertise);
+}
+
+
+struct phy_ops phy_ops_cis8201 = {
+	.init 		= cis8201_init,
+	.set_autoneg 	= cis8201_set_autoneg,
+	.poll		= cis8201_poll,
+	.int_enable	= cis8201_int_enable,
+	.int_disable	= cis8201_int_disable,
+	.int_ack	= cis8201_int_ack
+};
+
+/* Cicada 8201 */
+static struct phy_info phy_info_cis8201 = {
+	.id = 0x000fc440,
+	.name = "CIS8201",
+	.shift = 4,
+	.ops = &phy_ops_cis8201
+};
+
+static int phy_cicada_init(void)
+{
+	return phy_register(&phy_info_cis8201);
+}
+
+static void phy_cicada_exit(void)
+{
+	phy_unregister(&phy_info_cis8201);
+}
+
+module_init(phy_cicada_init);
+module_exit(phy_cicada_exit);

--- /dev/null
+++ linux/drivers/net/phy_davicom.c
@@ -0,0 +1,140 @@
+/* 
+ * drivers/net/phy_davicom.c
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/mii_bus.h>
+
+/* DM9161 Control register values */
+#define MIIM_DM9161_CR_STOP	0x0400
+#define MIIM_DM9161_CR_RSTAN	0x1200
+
+#define MIIM_DM9161_SCR		0x10
+#define MIIM_DM9161_SCR_INIT	0x0610
+
+/* DM9161 Specified Configuration and Status Register */
+#define MIIM_DM9161_SCSR	0x11
+#define MIIM_DM9161_SCSR_100F	0x8000
+#define MIIM_DM9161_SCSR_100H	0x4000
+#define MIIM_DM9161_SCSR_10F	0x2000
+#define MIIM_DM9161_SCSR_10H	0x1000
+
+/* DM9161 Interrupt Register */
+#define MIIM_DM9161_INTR	0x15
+#define MIIM_DM9161_INTR_PEND		0x8000
+#define MIIM_DM9161_INTR_DPLX_MASK	0x0800
+#define MIIM_DM9161_INTR_SPD_MASK	0x0400
+#define MIIM_DM9161_INTR_LINK_MASK	0x0200
+#define MIIM_DM9161_INTR_MASK		0x0100
+#define MIIM_DM9161_INTR_DPLX_CHANGE	0x0010
+#define MIIM_DM9161_INTR_SPD_CHANGE	0x0008
+#define MIIM_DM9161_INTR_LINK_CHANGE	0x0004
+#define MIIM_DM9161_INTR_INIT 		0x0000
+#define MIIM_DM9161_INTR_STOP	\
+(MIIM_DM9161_INTR_DPLX_MASK | MIIM_DM9161_INTR_SPD_MASK \
+ | MIIM_DM9161_INTR_LINK_MASK | MIIM_DM9161_INTR_MASK)
+
+/* DM9161 10BT Configuration/Status */
+#define MIIM_DM9161_10BTCSR	0x12
+#define MIIM_DM9161_10BTCSR_INIT	0x7800
+
+static int dm9161_init(struct phy_info *phy)
+{
+	mdelay(2000);
+
+	phy_write(phy, MII_BMCR, MIIM_DM9161_CR_STOP);
+	phy_write(phy, MIIM_DM9161_SCR, MIIM_DM9161_SCR_INIT);
+	phy_write(phy, MIIM_DM9161_10BTCSR, MIIM_DM9161_10BTCSR_INIT);
+
+	return 0;
+}
+
+static int dm9161_int_enable(struct phy_info *phy)
+{
+	/* Clear any pending interrupts */
+	phy_read(phy, MIIM_DM9161_INTR);
+
+	return 0;
+}
+
+static int dm9161_int_ack(struct phy_info *phy)
+{
+	/* Clear any pending interrupts */
+	phy_read(phy, MIIM_DM9161_INTR);
+
+	return 0;
+}
+
+static int dm9161_int_disable(struct phy_info *phy)
+{
+	/* Clear any pending interrupts */
+	phy_read(phy, MIIM_DM9161_INTR);
+
+	return 0;
+}
+
+static int dm9161_poll(struct phy_info *phy)
+{
+	int autoneg = phy->state.autoneg;
+	int err;
+	uint16_t val;
+
+	err = phy_gen_poll(phy);
+	if (err < 0)
+		return err;
+
+	if (phy->state.link && autoneg) {
+		val = phy_read(phy, MIIM_DM9161_SCSR);
+
+		if (val & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_100H))
+			phy->state.speed = 100;
+		else
+			phy->state.speed = 10;
+
+		if (val & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_10F))
+			phy->state.duplex = 1;
+		else
+			phy->state.duplex = 0;
+	}
+
+	return 0;
+}
+
+static struct phy_ops phy_ops_dm9161 = {
+	.init = dm9161_init,
+	.poll = dm9161_poll,
+	.int_enable = dm9161_int_enable,
+	.int_ack = dm9161_int_ack,
+	.int_disable = dm9161_int_disable,
+};
+
+static struct phy_info phy_info_dm9161 = {
+	.id = 0x0181b880,
+	.name = "Davicom DM9161E",
+	.shift = 4,
+	.ops = &phy_ops_dm9161,
+};
+
+static int phy_davicom_init(void)
+{
+	return phy_register(&phy_info_dm9161);
+}
+
+static void phy_davicom_exit(void)
+{
+	phy_unregister(&phy_info_dm9161);
+}
+
+module_init(phy_davicom_init);
+module_exit(phy_davicom_exit);

--- /dev/null
+++ linux/drivers/net/phy_lxt97x.c
@@ -0,0 +1,210 @@
+/* 
+ * drivers/net/phy_lxt97x.c
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mii_bus.h>
+
+/* ------------------------------------------------------------------------- */
+/* The Level one LXT970 is used by many boards				     */
+
+#define MII_LXT970_MIRROR    16  /* Mirror register           */
+#define MII_LXT970_IER       17  /* Interrupt Enable Register */
+#define MII_LXT970_ISR       18  /* Interrupt Status Register */
+#define MII_LXT970_CONFIG    19  /* Configuration Register    */
+#define MII_LXT970_CSR       20  /* Chip Status Register      */
+
+static int lxt970_int_enable(struct phy_info *phy)
+{
+	phy_write(phy, MII_LXT970_IER, 0x0002);
+
+	return 0;
+};
+
+static int lxt970_int_ack(struct phy_info *phy)
+{
+	phy_read(phy, MII_LXT970_ISR);
+
+	return 0;
+}
+
+static int lxt970_int_disable(struct phy_info *phy)
+{
+	phy_write(phy, MII_LXT970_IER, 0x0000);
+
+	return 0;
+};
+
+static int lxt970_poll(struct phy_info *phy)
+{
+	int autoneg = phy->state.autoneg;
+	int err;
+
+	err = phy_gen_poll(phy);
+	if (err < 0)
+		return err;
+
+	if (phy->state.link && autoneg) {
+		/* find out the current state */
+		uint16_t val;
+
+		val = phy_read(phy, MII_LXT970_CSR);
+		if (val & 0x1000)
+			phy->state.duplex = 1;
+		else
+			phy->state.duplex = 0;
+
+		if (val & 0x0800) {
+			phy->state.speed = 100;
+		} else {
+			phy->state.speed = 10;
+		}
+	}
+
+	return 0;
+}
+
+
+static struct phy_ops phy_ops_lxt970 = {
+	.poll 		= lxt970_poll,
+	.int_enable	= lxt970_int_enable,
+	.int_ack	= lxt970_int_ack,
+	.int_disable	= lxt970_int_disable
+};
+
+static struct phy_info phy_info_lxt970 = {
+	.id = 0x07810000,
+	.shift = 4,
+	.name = "LXT970",
+	.ops = &phy_ops_lxt970,
+};
+
+/* Same as the LXT970, but different ID
+ */
+static struct phy_info phy_info_lxt970a = {
+	.id = 0x00810000,
+	.shift = 4,
+	.name = "LXT970A",
+	.ops = &phy_ops_lxt970,
+};
+
+/* register definitions for the 971 */
+
+#define MII_LXT971_PCR       16  /* Port Control Register     */
+#define MII_LXT971_SR2       17  /* Status Register 2         */
+#define MII_LXT971_IER       18  /* Interrupt Enable Register */
+#define MII_LXT971_ISR       19  /* Interrupt Status Register */
+#define MII_LXT971_LCR       20  /* LED Control Register      */
+#define MII_LXT971_TCR       30  /* Transmit Control Register */
+
+static int lxt971_int_enable(struct phy_info *phy)
+{
+	phy_write(phy, MII_LXT971_IER, 0x00f2);
+
+	return 0;
+}
+
+static int lxt971_poll(struct phy_info *phy)
+{
+	int autoneg = phy->state.autoneg;
+	int err;
+
+	err = phy_gen_poll(phy);
+	if (err < 0)
+		return err;
+
+	if (phy->state.link && autoneg) {
+		/* find out the current state */
+		uint16_t val;
+
+		val = phy_read(phy, MII_LXT971_SR2);
+
+		if (val & 0x4000) {
+			phy->state.speed = 100;
+		} else {
+			phy->state.speed = 10;
+		}
+
+		if (val & 0x0200) {
+			phy->state.duplex = 1;
+		} else {
+			phy->state.duplex = 0;
+		}
+
+		if (val & 0x0008)
+			printk(KERN_DEBUG "%s: Fault detected\n",phy->name);
+	}
+
+	return 0;
+}
+
+static int lxt971_int_ack(struct phy_info *phy)
+{
+	phy_read(phy, MII_LXT971_ISR);
+
+	return 0;
+}
+
+static int lxt971_int_disable(struct phy_info *phy)
+{
+	phy_write(phy, MII_LXT971_IER, 0x0000);
+
+	return 0;
+};
+
+static struct phy_ops phy_ops_lxt971 = {
+	.poll 		= lxt971_poll,
+	.int_enable	= lxt971_int_enable,
+	.int_ack	= lxt971_int_ack,
+	.int_disable	= lxt971_int_disable,
+};
+
+static struct phy_info phy_info_lxt971 = {
+	.id = 0x001378e0,
+	.shift = 4,
+	.name = "LXT971",
+	.ops = &phy_ops_lxt971,
+};
+
+static int phy_lxt97x_init(void)
+{
+	int err;
+
+	err=phy_register(&phy_info_lxt970);
+	if (err)
+		return err;
+
+	err=phy_register(&phy_info_lxt970a);
+	if (err) {
+		phy_unregister(&phy_info_lxt970);
+		return err;
+	}
+
+	err=phy_register(&phy_info_lxt971);
+	if (err) {
+		phy_unregister(&phy_info_lxt970);
+		phy_unregister(&phy_info_lxt970a);
+	}
+
+	return err;
+}
+
+static void phy_lxt97x_exit(void)
+{
+	phy_unregister(&phy_info_lxt971);
+	phy_unregister(&phy_info_lxt970a);
+	phy_unregister(&phy_info_lxt970);
+}
+
+module_init(phy_lxt97x_init);
+module_exit(phy_lxt97x_exit);

--- /dev/null
+++ linux/drivers/net/phy_marvell.c
@@ -0,0 +1,125 @@
+/* 
+ * drivers/net/phy_marvell.c
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mii_bus.h>
+
+/* 88E1011 PHY Status Register */
+#define MIIM_88E1011_PHY_STATUS         0x11
+#define MIIM_88E1011_PHYSTAT_SPEED      0xc000
+#define MIIM_88E1011_PHYSTAT_GBIT       0x8000
+#define MIIM_88E1011_PHYSTAT_100        0x4000
+#define MIIM_88E1011_PHYSTAT_DUPLEX     0x2000
+#define MIIM_88E1011_PHYSTAT_LINK	0x0400
+
+#define MIIM_88E1011_IEVENT		0x13
+#define MIIM_88E1011_IEVENT_CLEAR	0x0000
+
+#define MIIM_88E1011_IMASK		0x12
+#define MIIM_88E1011_IMASK_INIT		0x6400
+#define MIIM_88E1011_IMASK_CLEAR	0x0000
+
+static int marvell_int_enable(struct phy_info *phy)
+{
+	/* Clear the IEVENT register */
+	phy_read(phy, MIIM_88E1011_IEVENT);
+
+	/* Set up the mask */
+	phy_write(phy, MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT);
+
+	return 0;
+}
+
+static int marvell_int_ack(struct phy_info *phy)
+{
+	/* Clear the interrupt */
+	phy_read(phy, MIIM_88E1011_IEVENT);
+
+	return 0;
+}
+
+static int marvell_int_disable(struct phy_info *phy)
+{
+	/* Clear the interrupt */
+	phy_read(phy, MIIM_88E1011_IEVENT);
+	/* Disable Interrupts */
+	phy_write(phy, MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR);
+
+	return 0;
+}
+
+static int marvell_poll(struct phy_info *phy)
+{
+	int autoneg = phy->state.autoneg;
+	int err;
+
+	err = phy_gen_poll(phy);
+	if (err < 0)
+		return err;
+
+	if (phy->state.link && autoneg) {
+		uint16_t val;
+		unsigned int speed;
+
+		val = phy_read(phy, MIIM_88E1011_PHY_STATUS);
+
+		if (val & MIIM_88E1011_PHYSTAT_DUPLEX)
+			phy->state.duplex = 1;
+		else
+			phy->state.duplex = 0;
+
+		speed = (val & MIIM_88E1011_PHYSTAT_SPEED);
+
+		switch (speed) {
+			case MIIM_88E1011_PHYSTAT_GBIT:
+				phy->state.speed = 1000;
+				break;
+			case MIIM_88E1011_PHYSTAT_100:
+				phy->state.speed = 100;
+				break;
+			default:
+				phy->state.speed = 10;
+				break;
+		}
+	}
+
+	return 0;
+}
+
+static struct phy_ops phy_ops_marvell = {
+	.poll		= marvell_poll,
+	.int_enable	= marvell_int_enable,
+	.int_ack	= marvell_int_ack,
+	.int_disable	= marvell_int_disable,
+};
+
+static struct phy_info phy_info_M88E1011S = {
+	.id = 0x01410c60,
+	.name = "Marvell 88E1011S",
+	.shift = 4,
+	.ops = &phy_ops_marvell,
+};
+
+static int phy_marvell_init(void)
+{
+	return phy_register(&phy_info_M88E1011S);
+}
+
+static void phy_marvell_exit(void)
+{
+	phy_unregister(&phy_info_M88E1011S);
+}
+
+module_init(phy_marvell_init);
+module_exit(phy_marvell_exit);

--- /dev/null
+++ linux/include/linux/mii_bus.h
@@ -0,0 +1,191 @@
+/* 
+ * include/linux/mii_bus.h
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#ifndef __MII_BUS_H
+#define __MII_BUS_H
+
+#ifdef __KERNEL__
+
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+
+#define MII_TIMEOUT 	(2*HZ)
+
+#define miim_end (-2)
+#define miim_read (-1)
+
+/* Macros for 'phy_id's used elsewhere.
+ * A PHY ID is 3 bits of bus, followed by 5 bits of id
+ */
+#define MII_BUS(phy_id)		(((phy_id) >> 5) & 0x7)
+#define MII_ID(phy_id)		((phy_id) & 0x1f)
+#define MII_PHY_ID(bus, id)	((((bus) & 0x7) << 5) | ((id) & 0x1f))
+
+/*
+ * Current PHY state
+ */
+struct phy_state {
+	unsigned int link:1;
+	unsigned int duplex:1;
+	unsigned int autoneg:1;
+	unsigned int loopback:1;
+	unsigned int speed:28;
+};
+
+/* PHY operations - borrowed from sungem_phy.h
+ *
+ * wol_options:	See WAKE_* in include/linux/ethtool.h
+ * advertise  : See ADVERTISED_* in include/linux/ethtool.h
+ */
+struct phy_info;
+
+struct phy_ops {
+	int	(*init)(struct phy_info *phy);
+	int	(*suspend)(struct phy_info *phy, uint32_t wol_options);
+	int	(*set_autoneg)(struct phy_info *phy, uint32_t advertise);
+	int	(*set_forced)(struct phy_info *phy, int speed, int duplex);
+
+	/* Polling */
+	int	(*poll)(struct phy_info *phy);
+
+	/* Interrupt-based */
+	int	(*int_enable)(struct phy_info *phy);
+	int	(*int_ack)(struct phy_info *phy);
+	int	(*int_disable)(struct phy_info *phy);
+};
+
+/* struct phy_info: a structure which defines attributes for a PHY
+ *
+ * id will contain a number which represents the PHY.  During
+ * startup, the driver will poll the PHY to find out what its
+ * UID--as defined by registers 2 and 3--is.  The 32-bit result
+ * gotten from the PHY will be shifted right by "shift" bits to
+ * discard any bits which may change based on revision numbers
+ * unimportant to functionality
+ *
+ * The struct phy_cmd entries represent pointers to an arrays of
+ * commands which tell the driver what to do to the PHY.
+ */
+struct phy_info {
+	struct list_head list;
+
+	uint32_t id;
+	char name[32];
+	unsigned int shift;
+
+	struct phy_ops *ops;
+
+	/* Per-PHY driver data goes here */
+	void *priv;
+
+	/* Your poll() routine should modify this.
+	 */
+	struct phy_state state;
+
+	/* Everything from here on down will
+	 * be filled in during registration
+	 */
+	int phy_id;
+
+	struct {
+		int irq;
+		unsigned long msecs;
+		void (*func) (void *data);
+		void *data;
+		struct work_struct tq;
+		struct timer_list timer;
+	} delta;
+
+	struct {
+		int autoneg;		/* 1=auto, 0=forced */
+		uint32_t advertise;	/* mask to allow */
+		unsigned long timeout;	/* jiffie stamp */
+	} negotiate;
+
+};
+
+struct mii_bus {
+	const char *name;
+	void *priv;
+	int (*read) (void *priv, int phy_id, int location);
+	int (*write) (void *priv, int phy_id, int location, uint16_t val);
+	void (*reset) (void *priv);
+
+	/* Auto-filled in values */
+	struct phy_info *phy[32];
+};
+
+/* MII bus registration
+ */
+extern int mii_bus_register(struct mii_bus *bus);
+extern void mii_bus_unregister(struct mii_bus *bus);
+
+/* Raw read/write routines
+ * Returns a 16-bit register value, or < 0 error code
+ */
+extern int mii_bus_read(int bus_id, int phy_id, int reg);
+extern int mii_bus_write(int bus_id, int phy_id, int reg, uint16_t val);
+
+/* Routines used by network devices that use the MII bus
+ */
+extern int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev,
+			  int phy_bus, int phy_id);
+extern void mii_phy_detach(struct mii_if_info *mii);
+
+/* Read current phy state
+ */
+extern int mii_phy_state(struct mii_if_info *mii, struct phy_state *state);
+
+/* Reset MII, renegotiate link
+ */
+extern int mii_phy_set_autoneg(struct mii_if_info *mii, uint32_t advertise);
+extern int mii_phy_set_forced(struct mii_if_info *mii, int speed, int duplex);
+extern int mii_phy_suspend(struct mii_if_info *mii, uint32_t wol_options);
+
+/* Use an IRQ to determine when the PHY changes
+ */
+extern int mii_phy_irq_enable(struct mii_if_info *mii, int irq,
+			      void (*func) (void *), void *data);
+extern void mii_phy_irq_disable(struct mii_if_info *mii, void *data);
+
+/* Poll the PHY
+ */
+extern int mii_phy_poll_enable(struct mii_if_info *mii, unsigned long msecs,
+			       void (*func) (void *), void *data);
+extern void mii_phy_poll_disable(struct mii_if_info *mii, void *data);
+
+/*
+ * PHY device registration
+ */
+extern int phy_register(struct phy_info *phy);
+extern void phy_unregister(struct phy_info *phy);
+
+static inline int phy_read(struct phy_info *phy, int regnum)
+{
+	return mii_bus_read(MII_BUS(phy->phy_id), MII_ID(phy->phy_id), regnum);
+}
+
+static inline int phy_write(struct phy_info *phy, int reg, uint16_t val)
+{
+	return mii_bus_write(MII_BUS(phy->phy_id), MII_ID(phy->phy_id), reg, val);	
+}
+
+/* Generic 'struct phy_ops' device routines */
+extern int phy_gen_set_autoneg(struct phy_info *phy, u32 advertise);
+extern int phy_gen_poll(struct phy_info *phy);
+
+#endif /* __KERNEL__ */
+
+#endif /* __MII_BUS_H */


      parent reply	other threads:[~2004-12-02 18:29 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <069B6F33-341C-11D9-9652-000393DBC2E8@freescale.com>
2004-11-18 17:52 ` [PATCH] MII bus API for PHY devices Andy Fleming
2004-11-18 19:34   ` Jason McMullan
2004-11-18 19:50     ` Andy Fleming
2004-11-18 21:00       ` Jason McMullan
2004-11-18 23:26   ` Benjamin Herrenschmidt
2004-11-19 16:41     ` Jason McMullan
2004-11-19 21:18     ` Andy Fleming
2004-11-19 22:43       ` Benjamin Herrenschmidt
2004-11-20  0:04         ` Andy Fleming
2004-11-23 18:18           ` Jason McMullan
2004-12-02 18:29           ` Jason McMullan [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1102012163.6056.39.camel@jmcmullan \
    --to=jason.mcmullan@timesys.com \
    --cc=afleming@freescale.com \
    --cc=benh@kernel.crashing.org \
    --cc=netdev@oss.sgi.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).