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 */
prev 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).