First patch for consolidation of PHY handling into one location.
Signed-off-by: Jason McMullan <[email protected]>
Date: Thu, 11 Nov 2004 14:12:46 -0500
Depends: linux-2.6.9
Description: This add a new API, in include/linux/mii_bus.h,
that allows common PHY handing code, and wrappers
for IRQ and polling based PHY status. It's based
off of the sungem.c interface.
###############################
Index of changes:
drivers/net/Makefile | 2
linux/drivers/net/mii_bus.c | 857 ++++++++++++++++++++++++++++++++++++++++++
linux/include/linux/mii_bus.h | 132 ++++++
3 files changed, 990 insertions(+), 1 deletion(-)
--- linux-orig/drivers/net/Makefile
+++ linux/drivers/net/Makefile
@@ -62,7 +62,7 @@
# end link order section
#
-obj-$(CONFIG_MII) += mii.o
+obj-$(CONFIG_MII) += mii.o mii_bus.o
obj-$(CONFIG_SUNDANCE) += sundance.o
obj-$(CONFIG_HAMACHI) += hamachi.o
--- /dev/null
+++ linux/drivers/net/mii_bus.c
@@ -0,0 +1,857 @@
+/*
+ * drivers/net/mii_bus.c
+ *
+ * Adapeted from drivers/net/gianfar_mii.c, by Andy Fleming
+ *
+ * Author: Jason McMullan ([email protected]) 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/sched.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>
+
+/*
+ * struct phy_cmd: A command for reading or writing a PHY register
+ *
+ * mii_reg: The register to read or write
+ *
+ * mii_data: For writes, the value to put in the register.
+ * A value of -1 indicates this is a read.
+ *
+ * funct: A function pointer which is invoked for each command.
+ * For reads, this function will be passed the value read
+ * from the PHY, and process it.
+ * For writes, the result of this function will be written
+ * to the PHY register
+ */
+struct phy_cmd {
+ u32 mii_reg;
+ u32 mii_data;
+ u16 (*funct) (u16 mii_reg, int bus, int id);
+};
+
+/* 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 {
+ u32 id;
+ const char *name;
+ unsigned int shift;
+ /* Called to configure the PHY, and modify the controller
+ * based on the results */
+ const struct phy_cmd *config;
+
+ /* Called when starting up the controller. Usually sets
+ * up the interrupt for state changes */
+ const struct phy_cmd *startup;
+
+ /* Called inside the interrupt handler to acknowledge
+ * the interrupt */
+ const struct phy_cmd *ack_int;
+
+ /* Called in the bottom half to get the state */
+ const struct phy_cmd *poll;
+
+ /* Called to re-enable interrupts */
+ const struct phy_cmd *end_int;
+
+ /* Called when bringing down the controller. Usually stops
+ * the interrupts from being generated */
+ const struct phy_cmd *shutdown;
+
+ /* Local state information */
+ struct {
+ int irq;
+ unsigned long msecs;
+ void (*func)(void *data);
+ void *data;
+ struct work_struct tq;
+ struct timer_list timer;
+ } delta;
+
+ struct phy_state state;
+};
+
+static struct mii_bus *mii_bus[4];
+
+#define MII_BUS_MAX (sizeof(mii_bus)/sizeof(struct mii_bus *))
+
+#define VALID_BUS(bus) (mii_bus[bus]!=NULL)
+#define VALID(bus,x) (mii_bus[bus]!=NULL && (mii_bus[bus]->phy[id] != NULL))
+#define PHY_ID(phy_id) (phy_id & 0x1f)
+#define PHY_BUS(phy_id) ((phy_id & 0x20) ? 1 : 0)
+#define MII_ID(mii) PHY_ID(mii->phy_id)
+#define MII_BUS(mii) PHY_BUS(mii->phy_id)
+#define BUS_ID(phy_id) int bus = PHY_BUS(phy_id); int id = PHY_ID(phy_id);
+
+static inline struct phy_info *mii_phy_of(struct mii_if_info *mii)
+{
+ if (mii != NULL) {
+ BUS_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 (!VALID_BUS(bus))
+ return -EINVAL;
+
+ 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 (!VALID_BUS(bus))
+ return -EINVAL;
+
+ return mii_bus[bus]->read(mii_bus[bus]->priv,id,regnum);
+}
+
+/* returns which value to write to the control register. */
+/* For 10/100 the value is slightly different. */
+static u16 mii_cr_init(u16 mii_reg, int bus, int id)
+{
+ return BMCR_ANRESTART;
+}
+
+#define BRIEF_MII_ERRORS
+/* Wait for auto-negotiation to complete */
+u16 mii_parse_sr(u16 mii_reg, int bus, int id)
+{
+ unsigned int timeout = MII_TIMEOUT;
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (mii_reg & BMSR_LSTATUS) {
+ pstate->link = 1;
+ } else {
+ pstate->link = 0;
+ pstate->auto_neg = 1;
+ }
+
+ /* Only auto-negotiate if the link has just gone up */
+ if (pstate->link && pstate->auto_neg) {
+ while ((!(mii_reg & BMSR_ANEGCOMPLETE)) && timeout--)
+ mii_reg = mii_bus_read(bus, id, MII_BMSR);
+
+#if defined(BRIEF_MII_ERRORS)
+ if (mii_reg & BMSR_ANEGCOMPLETE)
+ printk(KERN_INFO "phy %d.%d: Auto-negotiation done\n",
+ bus,id);
+ else
+ printk(KERN_INFO "phy %d.%d: Auto-negotiation timed out\n",
+ bus,id);
+#endif
+ pstate->auto_neg = 0;
+
+ if (mii_reg & BMSR_ANEGCOMPLETE) {
+ mii_reg = mii_bus_read(bus, id, MII_LPA);
+ mii_reg &= mii_bus_read(bus, id, MII_ADVERTISE);
+ /* Get the speed */
+ if (mii_reg & (LPA_100FULL | LPA_100HALF))
+ pstate->speed = 100;
+ else if (mii_reg & (LPA_10FULL | LPA_10HALF))
+ pstate->speed = 10;
+
+ /* Get the duplex */
+ if (mii_reg & (LPA_100FULL | LPA_10FULL))
+ pstate->duplex = 1;
+ else if (mii_reg & (LPA_100HALF | LPA_10HALF))
+ pstate->duplex = 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Determine the speed and duplex which was negotiated */
+u16 mii_parse_88E1011_psr(u16 mii_reg, int bus, int id)
+{
+ unsigned int speed;
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (pstate->link) {
+ if (mii_reg & MIIM_88E1011_PHYSTAT_DUPLEX)
+ pstate->duplex = 1;
+ else
+ pstate->duplex = 0;
+
+ speed = (mii_reg & MIIM_88E1011_PHYSTAT_SPEED);
+
+ switch (speed) {
+ case MIIM_88E1011_PHYSTAT_GBIT:
+ pstate->speed = 1000;
+ break;
+ case MIIM_88E1011_PHYSTAT_100:
+ pstate->speed = 100;
+ break;
+ default:
+ pstate->speed = 10;
+ break;
+ }
+ } else {
+ pstate->speed = 0;
+ pstate->duplex = 0;
+ }
+
+ return 0;
+}
+
+u16 mii_parse_cis8201(u16 mii_reg, int bus, int id)
+{
+ unsigned int speed;
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (pstate->link) {
+ if (mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX)
+ pstate->duplex = 1;
+ else
+ pstate->duplex = 0;
+
+ speed = mii_reg & MIIM_CIS8201_AUXCONSTAT_SPEED;
+
+ switch (speed) {
+ case MIIM_CIS8201_AUXCONSTAT_GBIT:
+ pstate->speed = 1000;
+ break;
+ case MIIM_CIS8201_AUXCONSTAT_100:
+ pstate->speed = 100;
+ break;
+ default:
+ pstate->speed = 10;
+ break;
+ }
+ } else {
+ pstate->speed = 0;
+ pstate->duplex = 0;
+ }
+
+ return 0;
+}
+
+u16 mii_parse_dm9161_scsr(u16 mii_reg, int bus, int id)
+{
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_100H))
+ pstate->speed = 100;
+ else
+ pstate->speed = 10;
+
+ if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_10F))
+ pstate->duplex = 1;
+ else
+ pstate->duplex = 0;
+
+ return 0;
+}
+
+u16 dm9161_wait(u16 mii_reg, int bus, int id)
+{
+ int timeout = 3*HZ;
+
+ if (!VALID(bus,id)) return 0xffff;
+
+ /* Davicom takes a bit to come up after a reset,
+ * so wait here for a bit */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(timeout);
+
+ return 0;
+}
+
+static struct phy_info phy_info_M88E1011S = {
+ .id = 0x01410c6,
+ .name = "Marvell 88E1011S",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Reset and configure the PHY */
+ {MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+ {miim_end,}
+ },
+ .startup = (const struct phy_cmd[]) { /* startup */
+ /* Clear the IEVENT register */
+ {MIIM_88E1011_IEVENT, miim_read, NULL},
+ /* Set up the mask */
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+ {miim_end,}
+ },
+ .ack_int = (const struct phy_cmd[]) { /* ack_int */
+ /* Clear the interrupt */
+ {MIIM_88E1011_IEVENT, miim_read, NULL},
+ /* Disable interrupts */
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ /* Read the Status (2x to make sure link is right) */
+ {MII_BMSR, miim_read, NULL},
+ /* Check the status */
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {MIIM_88E1011_PHY_STATUS, miim_read, mii_parse_88E1011_psr},
+ {miim_end,}
+ },
+ .end_int = (const struct phy_cmd[]) { /* end_int */
+ /* Enable Interrupts */
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+ {miim_end,}
+ },
+ .shutdown = (const struct phy_cmd[]) { /* shutdown */
+ {MIIM_88E1011_IEVENT, miim_read, NULL},
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+ {miim_end,}
+ },
+};
+
+/* Cicada 8201 */
+static struct phy_info phy_info_cis8201 = {
+ .id = 0xfc41,
+ .name = "CIS8201",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Override PHY config settings */
+ {MIIM_CIS8201_AUX_CONSTAT, MIIM_CIS8201_AUXCONSTAT_INIT, NULL},
+ /* Set up the interface mode */
+ {MIIM_CIS8201_EXT_CON1, MIIM_CIS8201_EXTCON1_INIT, NULL},
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ /* Read the Status (2x to make sure link is right) */
+ {MII_BMSR, miim_read, NULL},
+ /* Auto-negotiate */
+ {MII_BMSR, miim_read, mii_parse_sr},
+ /* Read the status */
+ {MIIM_CIS8201_AUX_CONSTAT, miim_read, mii_parse_cis8201},
+ {miim_end,}
+ },
+};
+
+static struct phy_info phy_info_dm9161 = {
+ .id = 0x0181b88,
+ .name = "Davicom DM9161E",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ {MII_BMCR, MIIM_DM9161_CR_STOP, NULL},
+ /* Do not bypass the scrambler/descrambler */
+ {MIIM_DM9161_SCR, MIIM_DM9161_SCR_INIT, NULL},
+ /* Clear 10BTCSR to default */
+ {MIIM_DM9161_10BTCSR, MIIM_DM9161_10BTCSR_INIT, NULL},
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, NULL},
+ /* Restart Auto Negotiation */
+ {MII_BMCR, MIIM_DM9161_CR_RSTAN, NULL},
+ {miim_end,}
+ },
+ .startup = (const struct phy_cmd[]) { /* startup */
+ /* Status is read once to clear old link state */
+ {MII_BMSR, miim_read, dm9161_wait},
+ /* Clear any pending interrupts */
+ {MIIM_DM9161_INTR, miim_read, NULL},
+ {miim_end,}
+ },
+ .ack_int = (const struct phy_cmd[]) { /* ack_int */
+ {MIIM_DM9161_INTR, miim_read, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ {MII_BMSR, miim_read, NULL},
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {MIIM_DM9161_SCSR, miim_read, mii_parse_dm9161_scsr},
+ {miim_end,}
+ },
+ .shutdown = (const struct phy_cmd[]) { /* shutdown */
+ {MIIM_DM9161_INTR, miim_read, NULL},
+ {miim_end,}
+ },
+};
+
+static struct phy_info phy_info_bcm5222 = {
+ .id = 0x0040632,
+ .name = "Broadcom BCM5222",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, NULL},
+ /* Status is read once to clear old link state */
+ {MII_BMSR, miim_read, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ {MII_BMSR, miim_read, NULL},
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {miim_end,}
+ },
+};
+
+static struct phy_info phy_info_generic = {
+ .id = 0x0,
+ .name = "Generic PHY",
+ .shift = 32,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, NULL},
+ /* Ignore old link state */
+ {MII_BMSR, miim_read, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* handle_int */
+ {MII_BMSR, miim_read, NULL},
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {miim_end,}
+ },
+};
+
+static struct phy_info *phy_info[] = {
+ &phy_info_generic,
+ &phy_info_cis8201,
+ &phy_info_M88E1011S,
+ &phy_info_dm9161,
+ &phy_info_bcm5222,
+ NULL
+};
+
+/* 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)
+{
+ u16 phy_reg;
+ u32 phy_ID;
+ int i;
+ struct phy_info *theInfo = 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. */
+ for (i = 0; phy_info[i]; i++)
+ if (phy_info[i]->id == (phy_ID >> phy_info[i]->shift))
+ theInfo = phy_info[i];
+
+ if (theInfo == NULL) {
+ printk("phy %d.%d: PHY id 0x%x is not supported!\n", bus, id, phy_ID);
+ return NULL;
+ } else {
+ printk("phy %d.%d: PHY is %s (%x)\n", bus, id, theInfo->name,
+ phy_ID);
+ }
+
+ return theInfo;
+}
+
+/* Take a list of struct phy_cmd, and, depending on the values, either */
+/* read or write, using a helper function if provided */
+/* It is assumed that all lists of struct phy_cmd will be terminated by */
+/* mii_end. */
+static void phy_run_commands(int bus, int id, const struct phy_cmd *cmd)
+{
+ int i;
+ u16 result;
+
+ if (!VALID(bus, id)) return;
+
+ if (cmd == NULL)
+ return;
+
+ for (i = 0; cmd->mii_reg != miim_end; i++) {
+ /* The command is a read if mii_data is miim_read */
+ if (cmd->mii_data == miim_read) {
+ /* Read the value of the PHY reg */
+ result = mii_bus_read(bus, id, cmd->mii_reg);
+
+ /* If a function was supplied, we need to let it process */
+ /* the result. */
+ if (cmd->funct != NULL)
+ (*(cmd->funct)) (result, bus, id);
+ } else { /* Otherwise, it's a write */
+ /* If a function was supplied, it will provide
+ * the value to write */
+ /* Otherwise, the value was supplied in cmd->mii_data */
+ if (cmd->funct != NULL)
+ result = (*(cmd->funct)) (0, bus, id);
+ else
+ result = cmd->mii_data;
+
+ /* Write the appropriate value to the PHY reg */
+ mii_bus_write(bus, id, cmd->mii_reg, result);
+ }
+ cmd++;
+ }
+}
+
+static int mdio_read(struct net_device *dev, int phy, int reg)
+{
+ BUS_ID(phy);
+
+ return mii_bus_read(bus, id, reg);
+}
+
+static void mdio_write(struct net_device *dev, int phy, int reg, int val)
+{
+ BUS_ID(phy);
+
+ mii_bus_write(bus, id, reg, val & 0xffff);
+}
+
+static inline void mii_phy_irq_ack(struct mii_if_info *mii)
+{
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->ack_int);
+}
+
+static inline void mii_phy_irq_handle(struct mii_if_info *mii)
+{
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->end_int);
+}
+
+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;
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return -EINVAL;
+
+ if (phy->delta.data != NULL)
+ return -EBUSY;
+
+ 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_run_commands(bus, id, mii_bus[bus]->phy[id]->startup);
+ 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);
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ if (phy->delta.data != data)
+ return;
+
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->shutdown);
+
+ free_irq(phy->delta.irq, mii);
+ phy->delta.irq = -1;
+}
+
+/* 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);
+ int timeout = HZ / 1000 + 1;
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(timeout);
+
+ if (phy->delta.irq >= 0)
+ mii_phy_irq_handle(mii);
+ else {
+ BUS_ID(mii->phy_id);
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+ }
+
+ if (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);
+
+ BUG_ON(phy == NULL);
+ 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(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 (VALID(bus,id)) {
+ 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);
+ memcpy(phy,info,sizeof(*phy));
+
+ INIT_WORK(&phy->delta.tq, mii_phy_delta, mii);
+ phy->delta.data = NULL;
+ phy->delta.irq = -1;
+ phy->state.link=0;
+ phy->state.duplex=0;
+ phy->state.auto_neg=1;
+ phy->state.speed=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;
+
+ phy_run_commands(bus, id, phy->startup);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_detach);
+void mii_phy_detach(struct mii_if_info *mii)
+{
+ struct phy_info *phy;
+ struct mii_bus *pbus;
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ pbus = mii_bus[bus];
+ phy = pbus->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);
+ }
+
+ phy_run_commands(bus, id, phy->shutdown);
+ pbus->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);
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return -ENODEV;
+
+ if (phy->delta.data == NULL)
+ phy_run_commands(bus, id, phy->poll);
+
+ memcpy(state,&phy->state,sizeof(*state));
+
+ 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) {
+ up(&mii_bus_lock);
+ return -ENOMEM;
+ }
+
+ mii_bus[bus_id] = bus;
+
+ if (mii_bus[bus_id] == NULL) {
+ up(&mii_bus_lock);
+ return -EINVAL;
+ }
+
+ if (mii_bus[bus_id]->reset)
+ mii_bus[bus_id]->reset(mii_bus[bus_id]->priv);
+
+ up(&mii_bus_lock);
+
+ printk(KERN_INFO "%s: registered as PHY bus %d\n",bus->name,bus_id);
+
+ 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;
+
+ up(&mii_bus_lock);
+}
--- /dev/null
+++ linux/include/linux/mii_bus.h
@@ -0,0 +1,132 @@
+/*
+ * 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
+
+#include <linux/mii.h>
+
+#define MII_TIMEOUT (1*HZ)
+
+#define miim_end ((u32)-2)
+#define miim_read ((u32)-1)
+
+/* 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
+
+/* 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
+
+/* 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
+
+struct phy_state {
+ unsigned int link:1;
+ unsigned int duplex:1;
+ unsigned int auto_neg:1;
+ unsigned int speed:29;
+};
+
+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);
+
+/* 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);
+
+#endif /* __MII_BUS_H */
--
Jason McMullan <[email protected]>
TimeSys Corporation
Jason McMullan <[email protected]> :
> First patch for consolidation of PHY handling into one location.
A little pass of polish could make it more cool imho:
- macro abuse;
- unchecked malloc;
- use plain old style multi-lines C comments (/* ...\n * ... \n * ... \n */) ?
- whitespace/tabulation damage (search for series of 2 or more spaces);
- hidden return: please put them on a separate line;
- no need to BUG_ON when there is an immediate dereference;
- mixed case variables/names (pLEAse DO not dO tHAT);
- unneeded checks ? How could VALID_BUS() in mii_bus_write() fail ?
- use goto when locking primitives are used (btw the last check in
mii_bus_register is not needed and you can s/mii_bus[bus_id]/bus/
a few times in this function) ?
- add a break in mii_bus_unregister() ?
- I'd probably favor ternary operators here and there (your choice, really)
- u32 and uint16_t in the same file: choose one style ?
--
Ueimor
Jason McMullan <[email protected]> :
[...]
> --- /dev/null
> +++ linux/drivers/net/mii_bus.c
[...]
> +struct phy_cmd {
> + u32 mii_reg;
> + u32 mii_data;
> + u16 (*funct) (u16 mii_reg, int bus, int id);
^^^^
-> spaces
[...]
> + /* Local state information */
> + struct {
> + int irq;
> + unsigned long msecs;
> + void (*func)(void *data);
> + void *data;
> + struct work_struct tq;
> + struct timer_list timer;
^^^^^
-> tab...
> + } delta;
^^^^
-> ...spaces
[...]
> +/* 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)
^^^^^^^^
-> the code of this file has already used some u16/u32 before this point.
> +{
> + if (!VALID_BUS(bus))
> + return -EINVAL;
> +
> + mii_bus[bus]->write(mii_bus[bus]->priv,id,regnum,value);
^^^^^^^^^^^^^
-> spaces instead of tab
-> please add a space after a colon
-> AFAIKS, the code guarantees that VALID_BUS will not fail.
> + 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 (!VALID_BUS(bus))
> + return -EINVAL;
> +
> + return mii_bus[bus]->read(mii_bus[bus]->priv,id,regnum);
-> see above.
[...]
> +#define BRIEF_MII_ERRORS
> +/* Wait for auto-negotiation to complete */
> +u16 mii_parse_sr(u16 mii_reg, int bus, int id)
> +{
> + unsigned int timeout = MII_TIMEOUT;
> + struct phy_state *pstate;
> +
> + if (!VALID(bus, id)) return 0xffff;
-> the return on a separate line only costs an extra character
and makes the style more consistent (see the code above).
[...]
> +u16 mii_parse_cis8201(u16 mii_reg, int bus, int id)
> +{
> + unsigned int speed;
> + struct phy_state *pstate;
> +
> + if (!VALID(bus, id)) return 0xffff;
> + pstate = &mii_bus[bus]->phy[id]->state;
> +
> + if (pstate->link) {
> + if (mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX)
> + pstate->duplex = 1;
> + else
> + pstate->duplex = 0;
-> If you are really concerned about the size of the source code:
pstate->duplex =
(mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX) ? 1 : 0;
> + NULL
> +};
> +
> +/* 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)
> +{
> + u16 phy_reg;
> + u32 phy_ID;
> + int i;
> + struct phy_info *theInfo = NULL;
-> s/theInfo/info/ ?
> + if (mii_bus[bus] == NULL)
> + return NULL;
-> This function is not exported and the caller has already
issued a VALID_BUS.
[...]
> + /* loop through all the known PHY types, and find one that */
> + /* matches the ID we read from the PHY. */
> + for (i = 0; phy_info[i]; i++)
> + if (phy_info[i]->id == (phy_ID >> phy_info[i]->shift))
> + theInfo = phy_info[i];
-> add braces around the for block + break ?
> +
> + if (theInfo == NULL) {
> + printk("phy %d.%d: PHY id 0x%x is not supported!\n", bus, id, phy_ID);
> + return NULL;
-> no need to return here.
> + } else {
> + printk("phy %d.%d: PHY is %s (%x)\n", bus, id, theInfo->name,
> + phy_ID);
> + }
> +
> + return theInfo;
> +}
[...]
> +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 (VALID(bus,id)) {
> + 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);
> + memcpy(phy,info,sizeof(*phy));
-> kmalloc can fail.
[...]
> +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;
[...]
> + mii_bus[bus_id] = bus;
> +
> + if (mii_bus[bus_id] == NULL) {
-> bus is not NULL, neither is mii_bus[bus_id] (see above).
> + up(&mii_bus_lock);
> + return -EINVAL;
> + }
> +
> + if (mii_bus[bus_id]->reset)
> + mii_bus[bus_id]->reset(mii_bus[bus_id]->priv);
if (bus->reset)
bus->reset(bus->priv);
Please, pretty please, more polish (I did not say that it could
be done instantly :o) ).
--
Ueimor
Second attempt, more polish.
(Though I'm leaving my macro abuse in for now!)
Description: MII Bus interface
Date: Thu, 11 Nov 2004 17:44:57 -0500
Signed-off-by: Jason McMullan <[email protected]>
Depends:
linux-2.6.9
###############################
Index of changes:
drivers/net/Makefile | 2
linux/drivers/net/mii_bus.c | 857 ++++++++++++++++++++++++++++++++++++++++++
linux/include/linux/mii_bus.h | 132 ++++++
3 files changed, 990 insertions(+), 1 deletion(-)
--- linux-orig/drivers/net/Makefile
+++ linux/drivers/net/Makefile
@@ -62,7 +62,7 @@
# end link order section
#
-obj-$(CONFIG_MII) += mii.o
+obj-$(CONFIG_MII) += mii.o mii_bus.o
obj-$(CONFIG_SUNDANCE) += sundance.o
obj-$(CONFIG_HAMACHI) += hamachi.o
--- /dev/null
+++ linux/drivers/net/mii_bus.c
@@ -0,0 +1,857 @@
+/*
+ * drivers/net/mii_bus.c
+ *
+ * Adapeted from drivers/net/gianfar_mii.c, by Andy Fleming
+ *
+ * Author: Jason McMullan ([email protected]) 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/sched.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>
+
+/*
+ * struct phy_cmd: A command for reading or writing a PHY register
+ *
+ * mii_reg: The register to read or write
+ *
+ * mii_data: For writes, the value to put in the register.
+ * A value of -1 indicates this is a read.
+ *
+ * funct: A function pointer which is invoked for each command.
+ * For reads, this function will be passed the value read
+ * from the PHY, and process it.
+ * For writes, the result of this function will be written
+ * to the PHY register
+ */
+struct phy_cmd {
+ u32 mii_reg;
+ u32 mii_data;
+ u16 (*funct) (u16 mii_reg, int bus, int id);
+};
+
+/* 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 {
+ u32 id;
+ const char *name;
+ unsigned int shift;
+ /* Called to configure the PHY, and modify the controller
+ * based on the results */
+ const struct phy_cmd *config;
+
+ /* Called when starting up the controller. Usually sets
+ * up the interrupt for state changes */
+ const struct phy_cmd *startup;
+
+ /* Called inside the interrupt handler to acknowledge
+ * the interrupt */
+ const struct phy_cmd *ack_int;
+
+ /* Called in the bottom half to get the state */
+ const struct phy_cmd *poll;
+
+ /* Called to re-enable interrupts */
+ const struct phy_cmd *end_int;
+
+ /* Called when bringing down the controller. Usually stops
+ * the interrupts from being generated */
+ const struct phy_cmd *shutdown;
+
+ /* Local state information */
+ struct {
+ int irq;
+ unsigned long msecs;
+ void (*func)(void *data);
+ void *data;
+ struct work_struct tq;
+ struct timer_list timer;
+ } delta;
+
+ struct phy_state state;
+};
+
+static struct mii_bus *mii_bus[4];
+
+#define MII_BUS_MAX (sizeof(mii_bus)/sizeof(struct mii_bus *))
+
+#define VALID_BUS(bus) (mii_bus[bus]!=NULL)
+#define VALID(bus,x) (mii_bus[bus]!=NULL && (mii_bus[bus]->phy[id] != NULL))
+#define PHY_ID(phy_id) (phy_id & 0x1f)
+#define PHY_BUS(phy_id) ((phy_id & 0x20) ? 1 : 0)
+#define MII_ID(mii) PHY_ID(mii->phy_id)
+#define MII_BUS(mii) PHY_BUS(mii->phy_id)
+#define BUS_ID(phy_id) int bus = PHY_BUS(phy_id); int id = PHY_ID(phy_id);
+
+static inline struct phy_info *mii_phy_of(struct mii_if_info *mii)
+{
+ if (mii != NULL) {
+ BUS_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 (!VALID_BUS(bus))
+ return -EINVAL;
+
+ 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 (!VALID_BUS(bus))
+ return -EINVAL;
+
+ return mii_bus[bus]->read(mii_bus[bus]->priv,id,regnum);
+}
+
+/* returns which value to write to the control register. */
+/* For 10/100 the value is slightly different. */
+static u16 mii_cr_init(u16 mii_reg, int bus, int id)
+{
+ return BMCR_ANRESTART;
+}
+
+#define BRIEF_MII_ERRORS
+/* Wait for auto-negotiation to complete */
+u16 mii_parse_sr(u16 mii_reg, int bus, int id)
+{
+ unsigned int timeout = MII_TIMEOUT;
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (mii_reg & BMSR_LSTATUS) {
+ pstate->link = 1;
+ } else {
+ pstate->link = 0;
+ pstate->auto_neg = 1;
+ }
+
+ /* Only auto-negotiate if the link has just gone up */
+ if (pstate->link && pstate->auto_neg) {
+ while ((!(mii_reg & BMSR_ANEGCOMPLETE)) && timeout--)
+ mii_reg = mii_bus_read(bus, id, MII_BMSR);
+
+#if defined(BRIEF_MII_ERRORS)
+ if (mii_reg & BMSR_ANEGCOMPLETE)
+ printk(KERN_INFO "phy %d.%d: Auto-negotiation done\n",
+ bus,id);
+ else
+ printk(KERN_INFO "phy %d.%d: Auto-negotiation timed out\n",
+ bus,id);
+#endif
+ pstate->auto_neg = 0;
+
+ if (mii_reg & BMSR_ANEGCOMPLETE) {
+ mii_reg = mii_bus_read(bus, id, MII_LPA);
+ mii_reg &= mii_bus_read(bus, id, MII_ADVERTISE);
+ /* Get the speed */
+ if (mii_reg & (LPA_100FULL | LPA_100HALF))
+ pstate->speed = 100;
+ else if (mii_reg & (LPA_10FULL | LPA_10HALF))
+ pstate->speed = 10;
+
+ /* Get the duplex */
+ if (mii_reg & (LPA_100FULL | LPA_10FULL))
+ pstate->duplex = 1;
+ else if (mii_reg & (LPA_100HALF | LPA_10HALF))
+ pstate->duplex = 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Determine the speed and duplex which was negotiated */
+u16 mii_parse_88E1011_psr(u16 mii_reg, int bus, int id)
+{
+ unsigned int speed;
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (pstate->link) {
+ if (mii_reg & MIIM_88E1011_PHYSTAT_DUPLEX)
+ pstate->duplex = 1;
+ else
+ pstate->duplex = 0;
+
+ speed = (mii_reg & MIIM_88E1011_PHYSTAT_SPEED);
+
+ switch (speed) {
+ case MIIM_88E1011_PHYSTAT_GBIT:
+ pstate->speed = 1000;
+ break;
+ case MIIM_88E1011_PHYSTAT_100:
+ pstate->speed = 100;
+ break;
+ default:
+ pstate->speed = 10;
+ break;
+ }
+ } else {
+ pstate->speed = 0;
+ pstate->duplex = 0;
+ }
+
+ return 0;
+}
+
+u16 mii_parse_cis8201(u16 mii_reg, int bus, int id)
+{
+ unsigned int speed;
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (pstate->link) {
+ if (mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX)
+ pstate->duplex = 1;
+ else
+ pstate->duplex = 0;
+
+ speed = mii_reg & MIIM_CIS8201_AUXCONSTAT_SPEED;
+
+ switch (speed) {
+ case MIIM_CIS8201_AUXCONSTAT_GBIT:
+ pstate->speed = 1000;
+ break;
+ case MIIM_CIS8201_AUXCONSTAT_100:
+ pstate->speed = 100;
+ break;
+ default:
+ pstate->speed = 10;
+ break;
+ }
+ } else {
+ pstate->speed = 0;
+ pstate->duplex = 0;
+ }
+
+ return 0;
+}
+
+u16 mii_parse_dm9161_scsr(u16 mii_reg, int bus, int id)
+{
+ struct phy_state *pstate;
+
+ if (!VALID(bus, id)) return 0xffff;
+ pstate = &mii_bus[bus]->phy[id]->state;
+
+ if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_100H))
+ pstate->speed = 100;
+ else
+ pstate->speed = 10;
+
+ if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_10F))
+ pstate->duplex = 1;
+ else
+ pstate->duplex = 0;
+
+ return 0;
+}
+
+u16 dm9161_wait(u16 mii_reg, int bus, int id)
+{
+ int timeout = 3*HZ;
+
+ if (!VALID(bus,id)) return 0xffff;
+
+ /* Davicom takes a bit to come up after a reset,
+ * so wait here for a bit */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(timeout);
+
+ return 0;
+}
+
+static struct phy_info phy_info_M88E1011S = {
+ .id = 0x01410c6,
+ .name = "Marvell 88E1011S",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Reset and configure the PHY */
+ {MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+ {miim_end,}
+ },
+ .startup = (const struct phy_cmd[]) { /* startup */
+ /* Clear the IEVENT register */
+ {MIIM_88E1011_IEVENT, miim_read, NULL},
+ /* Set up the mask */
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+ {miim_end,}
+ },
+ .ack_int = (const struct phy_cmd[]) { /* ack_int */
+ /* Clear the interrupt */
+ {MIIM_88E1011_IEVENT, miim_read, NULL},
+ /* Disable interrupts */
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ /* Read the Status (2x to make sure link is right) */
+ {MII_BMSR, miim_read, NULL},
+ /* Check the status */
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {MIIM_88E1011_PHY_STATUS, miim_read, mii_parse_88E1011_psr},
+ {miim_end,}
+ },
+ .end_int = (const struct phy_cmd[]) { /* end_int */
+ /* Enable Interrupts */
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+ {miim_end,}
+ },
+ .shutdown = (const struct phy_cmd[]) { /* shutdown */
+ {MIIM_88E1011_IEVENT, miim_read, NULL},
+ {MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+ {miim_end,}
+ },
+};
+
+/* Cicada 8201 */
+static struct phy_info phy_info_cis8201 = {
+ .id = 0xfc41,
+ .name = "CIS8201",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Override PHY config settings */
+ {MIIM_CIS8201_AUX_CONSTAT, MIIM_CIS8201_AUXCONSTAT_INIT, NULL},
+ /* Set up the interface mode */
+ {MIIM_CIS8201_EXT_CON1, MIIM_CIS8201_EXTCON1_INIT, NULL},
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ /* Read the Status (2x to make sure link is right) */
+ {MII_BMSR, miim_read, NULL},
+ /* Auto-negotiate */
+ {MII_BMSR, miim_read, mii_parse_sr},
+ /* Read the status */
+ {MIIM_CIS8201_AUX_CONSTAT, miim_read, mii_parse_cis8201},
+ {miim_end,}
+ },
+};
+
+static struct phy_info phy_info_dm9161 = {
+ .id = 0x0181b88,
+ .name = "Davicom DM9161E",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ {MII_BMCR, MIIM_DM9161_CR_STOP, NULL},
+ /* Do not bypass the scrambler/descrambler */
+ {MIIM_DM9161_SCR, MIIM_DM9161_SCR_INIT, NULL},
+ /* Clear 10BTCSR to default */
+ {MIIM_DM9161_10BTCSR, MIIM_DM9161_10BTCSR_INIT, NULL},
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, NULL},
+ /* Restart Auto Negotiation */
+ {MII_BMCR, MIIM_DM9161_CR_RSTAN, NULL},
+ {miim_end,}
+ },
+ .startup = (const struct phy_cmd[]) { /* startup */
+ /* Status is read once to clear old link state */
+ {MII_BMSR, miim_read, dm9161_wait},
+ /* Clear any pending interrupts */
+ {MIIM_DM9161_INTR, miim_read, NULL},
+ {miim_end,}
+ },
+ .ack_int = (const struct phy_cmd[]) { /* ack_int */
+ {MIIM_DM9161_INTR, miim_read, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ {MII_BMSR, miim_read, NULL},
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {MIIM_DM9161_SCSR, miim_read, mii_parse_dm9161_scsr},
+ {miim_end,}
+ },
+ .shutdown = (const struct phy_cmd[]) { /* shutdown */
+ {MIIM_DM9161_INTR, miim_read, NULL},
+ {miim_end,}
+ },
+};
+
+static struct phy_info phy_info_bcm5222 = {
+ .id = 0x0040632,
+ .name = "Broadcom BCM5222",
+ .shift = 4,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, NULL},
+ /* Status is read once to clear old link state */
+ {MII_BMSR, miim_read, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* poll */
+ {MII_BMSR, miim_read, NULL},
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {miim_end,}
+ },
+};
+
+static struct phy_info phy_info_generic = {
+ .id = 0x0,
+ .name = "Generic PHY",
+ .shift = 32,
+ .config = (const struct phy_cmd[]) { /* config */
+ /* Configure some basic stuff */
+ {MII_BMCR, BMCR_ANRESTART, NULL},
+ /* Ignore old link state */
+ {MII_BMSR, miim_read, NULL},
+ {miim_end,}
+ },
+ .poll = (const struct phy_cmd[]) { /* handle_int */
+ {MII_BMSR, miim_read, NULL},
+ {MII_BMSR, miim_read, mii_parse_sr},
+ {miim_end,}
+ },
+};
+
+static struct phy_info *phy_info[] = {
+ &phy_info_generic,
+ &phy_info_cis8201,
+ &phy_info_M88E1011S,
+ &phy_info_dm9161,
+ &phy_info_bcm5222,
+ NULL
+};
+
+/* 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)
+{
+ u16 phy_reg;
+ u32 phy_ID;
+ int i;
+ struct phy_info *theInfo = 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. */
+ for (i = 0; phy_info[i]; i++)
+ if (phy_info[i]->id == (phy_ID >> phy_info[i]->shift))
+ theInfo = phy_info[i];
+
+ if (theInfo == NULL) {
+ printk("phy %d.%d: PHY id 0x%x is not supported!\n", bus, id, phy_ID);
+ return NULL;
+ } else {
+ printk("phy %d.%d: PHY is %s (%x)\n", bus, id, theInfo->name,
+ phy_ID);
+ }
+
+ return theInfo;
+}
+
+/* Take a list of struct phy_cmd, and, depending on the values, either */
+/* read or write, using a helper function if provided */
+/* It is assumed that all lists of struct phy_cmd will be terminated by */
+/* mii_end. */
+static void phy_run_commands(int bus, int id, const struct phy_cmd *cmd)
+{
+ int i;
+ u16 result;
+
+ if (!VALID(bus, id)) return;
+
+ if (cmd == NULL)
+ return;
+
+ for (i = 0; cmd->mii_reg != miim_end; i++) {
+ /* The command is a read if mii_data is miim_read */
+ if (cmd->mii_data == miim_read) {
+ /* Read the value of the PHY reg */
+ result = mii_bus_read(bus, id, cmd->mii_reg);
+
+ /* If a function was supplied, we need to let it process */
+ /* the result. */
+ if (cmd->funct != NULL)
+ (*(cmd->funct)) (result, bus, id);
+ } else { /* Otherwise, it's a write */
+ /* If a function was supplied, it will provide
+ * the value to write */
+ /* Otherwise, the value was supplied in cmd->mii_data */
+ if (cmd->funct != NULL)
+ result = (*(cmd->funct)) (0, bus, id);
+ else
+ result = cmd->mii_data;
+
+ /* Write the appropriate value to the PHY reg */
+ mii_bus_write(bus, id, cmd->mii_reg, result);
+ }
+ cmd++;
+ }
+}
+
+static int mdio_read(struct net_device *dev, int phy, int reg)
+{
+ BUS_ID(phy);
+
+ return mii_bus_read(bus, id, reg);
+}
+
+static void mdio_write(struct net_device *dev, int phy, int reg, int val)
+{
+ BUS_ID(phy);
+
+ mii_bus_write(bus, id, reg, val & 0xffff);
+}
+
+static inline void mii_phy_irq_ack(struct mii_if_info *mii)
+{
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->ack_int);
+}
+
+static inline void mii_phy_irq_handle(struct mii_if_info *mii)
+{
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->end_int);
+}
+
+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;
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return -EINVAL;
+
+ if (phy->delta.data != NULL)
+ return -EBUSY;
+
+ 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_run_commands(bus, id, mii_bus[bus]->phy[id]->startup);
+ 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);
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ if (phy->delta.data != data)
+ return;
+
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->shutdown);
+
+ free_irq(phy->delta.irq, mii);
+ phy->delta.irq = -1;
+}
+
+/* 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);
+ int timeout = HZ / 1000 + 1;
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(timeout);
+
+ if (phy->delta.irq >= 0)
+ mii_phy_irq_handle(mii);
+ else {
+ BUS_ID(mii->phy_id);
+ phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+ }
+
+ if (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);
+
+ BUG_ON(phy == NULL);
+ 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(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 (VALID(bus,id)) {
+ 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);
+ memcpy(phy,info,sizeof(*phy));
+
+ INIT_WORK(&phy->delta.tq, mii_phy_delta, mii);
+ phy->delta.data = NULL;
+ phy->delta.irq = -1;
+ phy->state.link=0;
+ phy->state.duplex=0;
+ phy->state.auto_neg=1;
+ phy->state.speed=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;
+
+ phy_run_commands(bus, id, phy->startup);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_detach);
+void mii_phy_detach(struct mii_if_info *mii)
+{
+ struct phy_info *phy;
+ struct mii_bus *pbus;
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return;
+
+ pbus = mii_bus[bus];
+ phy = pbus->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);
+ }
+
+ phy_run_commands(bus, id, phy->shutdown);
+ pbus->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);
+ BUS_ID(mii->phy_id);
+
+ if (!VALID(bus, id))
+ return -ENODEV;
+
+ if (phy->delta.data == NULL)
+ phy_run_commands(bus, id, phy->poll);
+
+ memcpy(state,&phy->state,sizeof(*state));
+
+ 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) {
+ up(&mii_bus_lock);
+ return -ENOMEM;
+ }
+
+ mii_bus[bus_id] = bus;
+
+ if (mii_bus[bus_id] == NULL) {
+ up(&mii_bus_lock);
+ return -EINVAL;
+ }
+
+ if (mii_bus[bus_id]->reset)
+ mii_bus[bus_id]->reset(mii_bus[bus_id]->priv);
+
+ up(&mii_bus_lock);
+
+ printk(KERN_INFO "%s: registered as PHY bus %d\n",bus->name,bus_id);
+
+ 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;
+
+ up(&mii_bus_lock);
+}
--- /dev/null
+++ linux/include/linux/mii_bus.h
@@ -0,0 +1,132 @@
+/*
+ * 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
+
+#include <linux/mii.h>
+
+#define MII_TIMEOUT (1*HZ)
+
+#define miim_end ((u32)-2)
+#define miim_read ((u32)-1)
+
+/* 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
+
+/* 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
+
+/* 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
+
+struct phy_state {
+ unsigned int link:1;
+ unsigned int duplex:1;
+ unsigned int auto_neg:1;
+ unsigned int speed:29;
+};
+
+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);
+
+/* 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);
+
+#endif /* __MII_BUS_H */
--
Jason McMullan <[email protected]>
TimeSys Corporation
Francois Romieu <[email protected]> :
[...]
Okayyy, I should have issued a diff to check that you had not sent
the exact same patch twice.
--
Ueimor
On Thu, 2004-11-11 at 17:48 -0500, Jason McMullan wrote:
> Second attempt, more polish.
>
> (Though I'm leaving my macro abuse in for now!)
>
> Description: MII Bus interface
> Date: Thu, 11 Nov 2004 17:44:57 -0500
> Signed-off-by: Jason McMullan <[email protected]>
> Depends:
> linux-2.6.9
Have you looked at what sungem/sungem_phy does ?
Among others, sungem has an algorithm for automatically testing fallback
forced speeds when aneg fails, which has proven useful with a variety of
PHY/hub combos, plus a "magic_aneg" bit in the PHY definition for PHYs
taht can do that themselves automatically.
Also, besides shutdown(), you probably want a suspend() callback used by
the MAC driver when the machine is entering a suspend() state (I
definitely need that with various PHYs on powermacs) along with the
various WOL parameters.
Ben.
On Fri, 2004-11-12 at 17:15 +1100, Benjamin Herrenschmidt wrote:
> Have you looked at what sungem/sungem_phy does ?
>
> Among others, sungem has an algorithm for automatically testing fallback
> forced speeds when aneg fails, which has proven useful with a variety of
> PHY/hub combos, plus a "magic_aneg" bit in the PHY definition for PHYs
> taht can do that themselves automatically.
I'll look into it.
> Also, besides shutdown(), you probably want a suspend() callback used by
> the MAC driver when the machine is entering a suspend() state (I
> definitely need that with various PHYs on powermacs) along with the
> various WOL parameters.
For the 'wake on lan' stuff, could you give me a list of the
types of features you'd need? I haven't really looked at WOL just yet.
We can add WOL, suspend, etc. as needed. I just want to
get the base infrastructure in first, and gradually start migrating
phys to the mii_bus on embedded systems.
--
Jason McMullan <[email protected]>
On Fri, 2004-11-12 at 11:47 -0500, Jason McMullan wrote:
> For the 'wake on lan' stuff, could you give me a list of the
> types of features you'd need? I haven't really looked at WOL just yet.
Me neither, though Colin Leroy has, I'll check out his stuff next week.
> We can add WOL, suspend, etc. as needed. I just want to
> get the base infrastructure in first, and gradually start migrating
> phys to the mii_bus on embedded systems.
>
--
Benjamin Herrenschmidt <[email protected]>
Replying this to the netdev list for their perusal.
First, I'm flattered that you've based this on the gianfar phy code,
which I stole shamelessly from Benh's code, but Benh changed his code,
and so I stole once more (the sungem_phy code he mentioned). The
current 2.6.9 gianfar driver has a completely different PHY
infrastructure. One which is a better model, I think, for abstraction
than my previous code.
Which brings me to item #2: I have taken the code from the previous
patch, and combined it with my newer PHY code, which should ease the
way for someone to add some of the WOL features, and such. I have
compiled, and tested this code, and it works with the gianfar driver.
However, I've got a few questions for the network device community, on
how best to finish this off.
The primary issue is how to classify the MII bus. Should it be a
proper bus, fitting within the new driver model? This seems like the
proper method to me, since the MII bus is, in fact, a bus. But it's a
bit of a strange bus. The bus is attached at two ends. One end has
some number of PHY devices, and the corresponding drivers for them.
The other end has some number of ethernet controllers, and their
drivers.
So we have 3 implementation decisions which are affected by this:
1) How should we pass initialization information from the system to the
bus. Information like which irq to use for each PHY, and what the
address space for the bus's controls is. I would like to enforce
encapsulation so that the ethernet drivers don't need to know this
information, or pass it to the bus.
2) How should we reflect the dependency of the ethernet driver on the
mii bus driver?
3) How should we bind ethernet drivers to PHY drivers?
Oh, and a 4th side-issue:
Should each PHY have its own file? Or should we dump all the PHY
drivers in one file? And if so, should THAT file be separate from the
mii bus implementation file?
Andy Fleming
Open Source Team
Freescale Semiconductor, Inc
On Thu, 2004-11-18 at 11:52 -0600, Andy Fleming wrote:
> 1) How should we pass initialization information from the system to the
> bus. Information like which irq to use for each PHY, and what the
> address space for the bus's controls is. I would like to enforce
> encapsulation so that the ethernet drivers don't need to know this
> information, or pass it to the bus.
(Just an off-the-cuff answer here)
In line with the OCP->platform work I've been doing, I would think
that creating 'phy' devices on the platform bus would be appropriate,
with 'platform_data' that describes (a) the platform device ethernet
it's bus is on and (b) it's PHY ID on that bus. The PHY's IRQ would
be in it's platform resources.
> 2) How should we reflect the dependency of the ethernet driver on the
> mii bus driver?
Hmm. Don't really know from a sysfs perspective...
> 3) How should we bind ethernet drivers to PHY drivers?
A PHY 'platform_data' struct like:
struct phy_device_data {
struct {
const char *name;
int id;
} ethernet_platform_device_parent;
int phy_id;
}
> Oh, and a 4th side-issue:
> Should each PHY have its own file?
Actually, each PHY should have it's own device directory, like every
other device. Eventually, PHYs should have /dev/phy* entries, where
user-space can read/write PHY registers.
--
Jason McMullan <[email protected]>
On Nov 18, 2004, at 13:34, Jason McMullan wrote:
>> 3) How should we bind ethernet drivers to PHY drivers?
>
> A PHY 'platform_data' struct like:
>
> struct phy_device_data {
> struct {
> const char *name;
> int id;
> } ethernet_platform_device_parent;
> int phy_id;
> }
So you would have each PHY know the controller to which it's attached?
I would have thought the other way around... Hm. I will definitely
have to read up on my driver model stuff
>
>> Oh, and a 4th side-issue:
>> Should each PHY have its own file?
>
> Actually, each PHY should have it's own device directory, like every
> other device. Eventually, PHYs should have /dev/phy* entries, where
> user-space can read/write PHY registers.
I think you misunderstood. Are you talking about sysfs? I was talking
about actual source files. i.e. should there be dm9161.c, m88e1101.c,
cis8201.c, etc.
Also, do we need user-space to read/write PHY registers. ethtool has
this capability, I believe, and the interfaces there are settled.
Andy Fleming
On Thu, 2004-11-18 at 13:50 -0600, Andy Fleming wrote:
> Jason McMullan said:
> >
> > Actually, each PHY should have it's own device directory, like every
> > other device. Eventually, PHYs should have /dev/phy* entries, where
> > user-space can read/write PHY registers.
>
> I think you misunderstood. Are you talking about sysfs? I was talking
> about actual source files. i.e. should there be dm9161.c, m88e1101.c,
> cis8201.c, etc.
Yes, I am talking about sysfs. And yes, I think every PHY should have
it's own .c file. (although most people could get away with
using a non-IRQ 'drivers/net/phy/phy-generic.c'
> Also, do we need user-space to read/write PHY registers. ethtool has
> this capability, I believe, and the interfaces there are settled.
Doh! I forgot.
--
Jason McMullan <[email protected]>
On Thu, 2004-11-18 at 11:52 -0600, Andy Fleming wrote:
> 1) How should we pass initialization information from the system to the
> bus. Information like which irq to use for each PHY, and what the
> address space for the bus's controls is. I would like to enforce
> encapsulation so that the ethernet drivers don't need to know this
> information, or pass it to the bus.
Unfortunately, this is all quite platform specific and the ethernet
driver may be the only one to know what to do here... add to that
various special cases of the way the PHY is wired or controlled, I think
we can't completely avoid special casing...
> 2) How should we reflect the dependency of the ethernet driver on the
> mii bus driver?
The ethernet driver can instanciate the PHYs at it's childs, though the
case of several MACs sharing PHYs will be difficult to represent...
> 3) How should we bind ethernet drivers to PHY drivers?
I would have them instanciated by the ethernet driver. Besides, the PHY
driver will need to be able to identify it's "parent" driver in some
ways to deal with special cases. It would be nice to have a library of
utility code to independently deal with link tracking (basically what
drivers like sungem do independently), with a callback to the ethernet
driver to inform it of actual changes (notifier ?). MACs often have
autopoll features and PHY often have interrupts, but from experience,
that's not very useful and a good old timer based polling tend to do a
better job most of the time.
> Oh, and a 4th side-issue:
> Should each PHY have its own file? Or should we dump all the PHY
> drivers in one file? And if so, should THAT file be separate from the
> mii bus implementation file?
I'd put all bcm5xxx in the same file ... they can be put together by
families...
Also,
On Fri, 2004-11-19 at 10:26 +1100, Benjamin Herrenschmidt wrote:
> The ethernet driver can instanciate the PHYs at it's childs, though the
> case of several MACs sharing PHYs will be difficult to represent...
I think *requiring* a binding to ethernet devices is a bad idea.
For example, on many MPC8260 embedded systems, the MII bus is controlled
by GPIO pins, not an ethernet MDIO system.
Some applications may use the MII bus to control non-PHY devices, such
as Broadcom ethernet switches. I'm working for a general MII bus that
ethernet PHYs and other devices can all use.
--
Jason McMullan <[email protected]>
Hi,
I don't like the polling/interrupt setup part:
- for a nic driver, there is no irq line that could be requested by
mii_phy_irq_enable().
- if the mii bus driver uses it's own timers, then locking within the
nics will be more difficult.
Could you make that part optional? For a nic driver, I would prefer if I
could just call the ->startup part without the request_irq. If the nic
irq handler notices that the nic got an event, then it would call an
appropriate mii_bus function.
This also applies for something like /dev/phy/xy: With natsemi, it would
be very tricky to add proper locking. The nic as an internal phy and an
external mii bus. The internal phy is partially visible on the external
bus and any accesses to the phy id of the internal phy on the external
bus cause lockups. No big deal, I just move the internal phy around [the
phy id doesn't matter], but I would prefer if I have to do that just for
ethtool, not for multiple interfaces.
--
Manfred
On Nov 19, 2004, at 14:18, Manfred Spraul wrote:
> Hi,
>
> I don't like the polling/interrupt setup part:
> - for a nic driver, there is no irq line that could be requested by
> mii_phy_irq_enable().
> - if the mii bus driver uses it's own timers, then locking within the
> nics will be more difficult.
I'm not sure I accept the argument that locking will be more difficult.
Jason's patch requires that a callback be registered for the interrupt
or the polling (My update has a similar scheme). The function you
register is essentially like an extra interrupt, except it is never
invoked at interrupt time. All the function has to do is react
properly to link state. If, previously, you checked link state in your
interrupt handler, you could still do it there, I suspect.
>
> Could you make that part optional? For a nic driver, I would prefer if
> I could just call the ->startup part without the request_irq. If the
> nic irq handler notices that the nic got an event, then it would call
> an appropriate mii_bus function.
I think it would be doable to arrange the interface such that drivers
could adopt only the PHY configuration infrastructure, and not any of
the polling/interrupt infrastructure. Of course, as it is, it is at
least WHOLLY optional, so no driver has to use it at all.
>
> This also applies for something like /dev/phy/xy: With natsemi, it
> would be very tricky to add proper locking. The nic as an internal phy
> and an external mii bus. The internal phy is partially visible on the
> external bus and any accesses to the phy id of the internal phy on the
> external bus cause lockups. No big deal, I just move the internal phy
> around [the phy id doesn't matter], but I would prefer if I have to do
> that just for ethtool, not for multiple interfaces.
I agree with this point -- Accessing the PHY through /dev registers is
a recipe for some mess. Though I could be convinced that it is
manageable. I do think, however, that the ethtool interface is
sufficient to the task.
On Nov 18, 2004, at 17:26, Benjamin Herrenschmidt wrote:
> On Thu, 2004-11-18 at 11:52 -0600, Andy Fleming wrote:
>
>> 1) How should we pass initialization information from the system to
>> the
>> bus. Information like which irq to use for each PHY, and what the
>> address space for the bus's controls is. I would like to enforce
>> encapsulation so that the ethernet drivers don't need to know this
>> information, or pass it to the bus.
>
> Unfortunately, this is all quite platform specific and the ethernet
> driver may be the only one to know what to do here... add to that
> various special cases of the way the PHY is wired or controlled, I
> think
> we can't completely avoid special casing...
Well, under the system I'm currently envisioning, the driver would be
able to provide the data needed by the mii bus, but the hope would be
to enable board files (for when the PHY is soldered on the motherboard,
and the enet is not -- like on an MPC85xx) to provide this information
instead, and leave out the enet as middleman.
>
>> 2) How should we reflect the dependency of the ethernet driver on the
>> mii bus driver?
>
> The ethernet driver can instanciate the PHYs at it's childs, though the
> case of several MACs sharing PHYs will be difficult to represent...
I really don't want the driver to intantiate PHYs directly. The PHY is
its own device, and the less net drivers have to understand their inner
workings, the better. However, I hadn't considered the possibility of
multiple MACs sharing the same PHY. It does, as you say, support my
argument, though. With some careful design, the mii bus should be able
to handle this type of setup easily.
One of my goals, personally, is to allow multiple net drivers to share
the same mii bus, as in the case of the FCC enet controllers' PHYs on
an 8560 ADS, which can be accessed through TSEC1's MII Management bus.
>
>> 3) How should we bind ethernet drivers to PHY drivers?
>
> I would have them instanciated by the ethernet driver. Besides, the PHY
> driver will need to be able to identify it's "parent" driver in some
> ways to deal with special cases. It would be nice to have a library of
> utility code to independently deal with link tracking (basically what
> drivers like sungem do independently), with a callback to the ethernet
> driver to inform it of actual changes (notifier ?). MACs often have
> autopoll features and PHY often have interrupts, but from experience,
> that's not very useful and a good old timer based polling tend to do a
> better job most of the time.
So when you say instantiated, would you consider calling an "attach"
function with the phy_id and bus_id of the desired PHY instantiation?
I'm fine with that. The PHY would need to be able to send
notifications to the enet controller (currently done through a
callback). I'm interested in ideas on how the notifier could be used
(I have a distaste for callbacks).
Autopoll features sound pretty neat. I think the system should support
that. PHY interrupts are supported (they work quite well on my 85xx
system), as is timer-based polling. Do you really think that there are
special cases which can't be handled using a library similar to the
sungem_phy one?
>
>> Oh, and a 4th side-issue:
>> Should each PHY have its own file? Or should we dump all the PHY
>> drivers in one file? And if so, should THAT file be separate from the
>> mii bus implementation file?
>
> I'd put all bcm5xxx in the same file ... they can be put together by
> families...
Yeah, that sounds good.
Andy Fleming
Open Source Team
Freescale Semiconductor, Inc
On Fri, 2004-11-19 at 15:18 -0600, Andy Fleming wrote:
> So when you say instantiated, would you consider calling an "attach"
> function with the phy_id and bus_id of the desired PHY instantiation?
> I'm fine with that. The PHY would need to be able to send
> notifications to the enet controller (currently done through a
> callback). I'm interested in ideas on how the notifier could be used
> (I have a distaste for callbacks).
Look at the notifier lists in include/linux/notifier.h
> Autopoll features sound pretty neat. I think the system should support
> that.
But that becomes MAC-dependant again... That means you'd need 1) a way
for the MAC driver to ask the PHY driver what register it wants
autopolled, and a function in the PHY driver for the MAC to call when it
detects a change. Also, autopoll is broken in some MACs...
> PHY interrupts are supported (they work quite well on my 85xx
> system), as is timer-based polling. Do you really think that there are
> special cases which can't be handled using a library similar to the
> sungem_phy one?
Nope. I think timer based polling with a sungem-like fallback mecanism
to forced speeds would be nice.
Ben.
On Nov 19, 2004, at 16:43, Benjamin Herrenschmidt wrote:
> On Fri, 2004-11-19 at 15:18 -0600, Andy Fleming wrote:
>
>> So when you say instantiated, would you consider calling an "attach"
>> function with the phy_id and bus_id of the desired PHY instantiation?
>> I'm fine with that. The PHY would need to be able to send
>> notifications to the enet controller (currently done through a
>> callback). I'm interested in ideas on how the notifier could be used
>> (I have a distaste for callbacks).
>
> Look at the notifier lists in include/linux/notifier.h
Ok, will do.
>
>> Autopoll features sound pretty neat. I think the system should
>> support
>> that.
>
> But that becomes MAC-dependant again... That means you'd need 1) a way
> for the MAC driver to ask the PHY driver what register it wants
> autopolled, and a function in the PHY driver for the MAC to call when
> it
> detects a change. Also, autopoll is broken in some MACs...
What I'm envisioning here is that the driver would be able to tell the
PHY infrastructure that it's going to do its own thing, and then make
use of the reading/configuring part of the infrastructure, similar to
how sungem and gianfar are currently set up. But they would have the
option of letting the infrastructure also handle the status updates.
And, of course, the driver would not go through the effort to use
autopoll if it were broken.
>
>> PHY interrupts are supported (they work quite well on my 85xx
>> system), as is timer-based polling. Do you really think that there
>> are
>> special cases which can't be handled using a library similar to the
>> sungem_phy one?
>
> Nope. I think timer based polling with a sungem-like fallback mecanism
> to forced speeds would be nice.
Yes, I agree. The system I currently have does fallback to forced,
though it doesn't yet support the "magic aneg" feature you mentioned.
But that should be easy to add, and so it shall be.
Andy Fleming