#### Auto-generated patch #### Signed-off-by: Jason McMullan 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 +#include + +#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 + +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@xxxxxxxxxxx) 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 +#include +#include + +/* 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 +#include +#include +#include + +/* 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 +#include +#include + +/* ------------------------------------------------------------------------- */ +/* 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 +#include +#include + +/* 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 +#include +#include +#include + +#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 */