From 00db8189d984d6c51226dafbbe4a667ce9b7d5da Mon Sep 17 00:00:00 2001 From: Andy Fleming Date: Sat, 30 Jul 2005 19:31:23 -0400 Subject: This patch adds a PHY Abstraction Layer to the Linux Kernel, enabling ethernet drivers to remain as ignorant as is reasonable of the connected PHY's design and operation details. Signed-off-by: Andy Fleming Signed-off-by: Jeff Garzik --- drivers/net/phy/Kconfig | 57 +++ drivers/net/phy/Makefile | 9 + drivers/net/phy/cicada.c | 134 +++++++ drivers/net/phy/davicom.c | 195 ++++++++++ drivers/net/phy/lxt.c | 179 +++++++++ drivers/net/phy/marvell.c | 140 +++++++ drivers/net/phy/mdio_bus.c | 173 +++++++++ drivers/net/phy/phy.c | 862 +++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy.c.orig | 860 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy_device.c | 682 ++++++++++++++++++++++++++++++++++ drivers/net/phy/qsemi.c | 143 +++++++ 11 files changed, 3434 insertions(+) create mode 100644 drivers/net/phy/Kconfig create mode 100644 drivers/net/phy/Makefile create mode 100644 drivers/net/phy/cicada.c create mode 100644 drivers/net/phy/davicom.c create mode 100644 drivers/net/phy/lxt.c create mode 100644 drivers/net/phy/marvell.c create mode 100644 drivers/net/phy/mdio_bus.c create mode 100644 drivers/net/phy/phy.c create mode 100644 drivers/net/phy/phy.c.orig create mode 100644 drivers/net/phy/phy_device.c create mode 100644 drivers/net/phy/qsemi.c (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig new file mode 100644 index 000000000000..8b5db2343cc3 --- /dev/null +++ b/drivers/net/phy/Kconfig @@ -0,0 +1,57 @@ +# +# PHY Layer Configuration +# + +menu "PHY device support" + +config PHYLIB + bool "PHY Device support and infrastructure" + depends on NET_ETHERNET + help + Ethernet controllers are usually attached to PHY + devices. This option provides infrastructure for + managing PHY devices. + +config PHYCONTROL + bool "Support for automatically handling PHY state changes" + depends on PHYLIB + help + Adds code to perform all the work for keeping PHY link + state (speed/duplex/etc) up-to-date. Also handles + interrupts. + +comment "MII PHY device drivers" + depends on PHYLIB + +config MARVELL_PHY + bool "Drivers for Marvell PHYs" + depends on PHYLIB + ---help--- + Currently has a driver for the 88E1011S + +config DAVICOM_PHY + bool "Drivers for Davicom PHYs" + depends on PHYLIB + ---help--- + Currently supports dm9161e and dm9131 + +config QSEMI_PHY + bool "Drivers for Quality Semiconductor PHYs" + depends on PHYLIB + ---help--- + Currently supports the qs6612 + +config LXT_PHY + bool "Drivers for the Intel LXT PHYs" + depends on PHYLIB + ---help--- + Currently supports the lxt970, lxt971 + +config CICADA_PHY + bool "Drivers for the Cicada PHYs" + depends on PHYLIB + ---help--- + Currently supports the cis8204 + +endmenu + diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile new file mode 100644 index 000000000000..1af05de6ced0 --- /dev/null +++ b/drivers/net/phy/Makefile @@ -0,0 +1,9 @@ +# Makefile for Linux PHY drivers + +obj-$(CONFIG_PHYLIB) += phy.o phy_device.o mdio_bus.o + +obj-$(CONFIG_MARVELL_PHY) += marvell.o +obj-$(CONFIG_DAVICOM_PHY) += davicom.o +obj-$(CONFIG_CICADA_PHY) += cicada.o +obj-$(CONFIG_LXT_PHY) += lxt.o +obj-$(CONFIG_QSEMI_PHY) += qsemi.o diff --git a/drivers/net/phy/cicada.c b/drivers/net/phy/cicada.c new file mode 100644 index 000000000000..c47fb2ecd147 --- /dev/null +++ b/drivers/net/phy/cicada.c @@ -0,0 +1,134 @@ +/* + * drivers/net/phy/cicada.c + * + * Driver for Cicada PHYs + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +/* Cicada Extended Control Register 1 */ +#define MII_CIS8201_EXT_CON1 0x17 +#define MII_CIS8201_EXTCON1_INIT 0x0000 + +/* Cicada Interrupt Mask Register */ +#define MII_CIS8201_IMASK 0x19 +#define MII_CIS8201_IMASK_IEN 0x8000 +#define MII_CIS8201_IMASK_SPEED 0x4000 +#define MII_CIS8201_IMASK_LINK 0x2000 +#define MII_CIS8201_IMASK_DUPLEX 0x1000 +#define MII_CIS8201_IMASK_MASK 0xf000 + +/* Cicada Interrupt Status Register */ +#define MII_CIS8201_ISTAT 0x1a +#define MII_CIS8201_ISTAT_STATUS 0x8000 +#define MII_CIS8201_ISTAT_SPEED 0x4000 +#define MII_CIS8201_ISTAT_LINK 0x2000 +#define MII_CIS8201_ISTAT_DUPLEX 0x1000 + +/* Cicada Auxiliary Control/Status Register */ +#define MII_CIS8201_AUX_CONSTAT 0x1c +#define MII_CIS8201_AUXCONSTAT_INIT 0x0004 +#define MII_CIS8201_AUXCONSTAT_DUPLEX 0x0020 +#define MII_CIS8201_AUXCONSTAT_SPEED 0x0018 +#define MII_CIS8201_AUXCONSTAT_GBIT 0x0010 +#define MII_CIS8201_AUXCONSTAT_100 0x0008 + +MODULE_DESCRIPTION("Cicadia PHY driver"); +MODULE_AUTHOR("Andy Fleming"); +MODULE_LICENSE("GPL"); + +static int cis820x_config_init(struct phy_device *phydev) +{ + int err; + + err = phy_write(phydev, MII_CIS8201_AUX_CONSTAT, + MII_CIS8201_AUXCONSTAT_INIT); + + if (err < 0) + return err; + + err = phy_write(phydev, MII_CIS8201_EXT_CON1, + MII_CIS8201_EXTCON1_INIT); + + return err; +} + +static int cis820x_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, MII_CIS8201_ISTAT); + + return (err < 0) ? err : 0; +} + +static int cis820x_config_intr(struct phy_device *phydev) +{ + int err; + + if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_CIS8201_IMASK, + MII_CIS8201_IMASK_MASK); + else + err = phy_write(phydev, MII_CIS8201_IMASK, 0); + + return err; +} + +/* Cicada 820x */ +static struct phy_driver cis8204_driver = { + .phy_id = 0x000fc440, + .name = "Cicada Cis8204", + .phy_id_mask = 0x000fffc0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &cis820x_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &cis820x_ack_interrupt, + .config_intr = &cis820x_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init cis8204_init(void) +{ + return phy_driver_register(&cis8204_driver); +} + +static void __exit cis8204_exit(void) +{ + phy_driver_unregister(&cis8204_driver); +} + +module_init(cis8204_init); +module_exit(cis8204_exit); diff --git a/drivers/net/phy/davicom.c b/drivers/net/phy/davicom.c new file mode 100644 index 000000000000..6caf499fae32 --- /dev/null +++ b/drivers/net/phy/davicom.c @@ -0,0 +1,195 @@ +/* + * drivers/net/phy/davicom.c + * + * Driver for Davicom PHYs + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +#define MII_DM9161_SCR 0x10 +#define MII_DM9161_SCR_INIT 0x0610 + +/* DM9161 Interrupt Register */ +#define MII_DM9161_INTR 0x15 +#define MII_DM9161_INTR_PEND 0x8000 +#define MII_DM9161_INTR_DPLX_MASK 0x0800 +#define MII_DM9161_INTR_SPD_MASK 0x0400 +#define MII_DM9161_INTR_LINK_MASK 0x0200 +#define MII_DM9161_INTR_MASK 0x0100 +#define MII_DM9161_INTR_DPLX_CHANGE 0x0010 +#define MII_DM9161_INTR_SPD_CHANGE 0x0008 +#define MII_DM9161_INTR_LINK_CHANGE 0x0004 +#define MII_DM9161_INTR_INIT 0x0000 +#define MII_DM9161_INTR_STOP \ +(MII_DM9161_INTR_DPLX_MASK | MII_DM9161_INTR_SPD_MASK \ + | MII_DM9161_INTR_LINK_MASK | MII_DM9161_INTR_MASK) + +/* DM9161 10BT Configuration/Status */ +#define MII_DM9161_10BTCSR 0x12 +#define MII_DM9161_10BTCSR_INIT 0x7800 + +MODULE_DESCRIPTION("Davicom PHY driver"); +MODULE_AUTHOR("Andy Fleming"); +MODULE_LICENSE("GPL"); + + +#define DM9161_DELAY 1 +static int dm9161_config_intr(struct phy_device *phydev) +{ + int temp; + + temp = phy_read(phydev, MII_DM9161_INTR); + + if (temp < 0) + return temp; + + if(PHY_INTERRUPT_ENABLED == phydev->interrupts ) + temp &= ~(MII_DM9161_INTR_STOP); + else + temp |= MII_DM9161_INTR_STOP; + + temp = phy_write(phydev, MII_DM9161_INTR, temp); + + return temp; +} + +static int dm9161_config_aneg(struct phy_device *phydev) +{ + int err; + + /* Isolate the PHY */ + err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE); + + if (err < 0) + return err; + + /* Configure the new settings */ + err = genphy_config_aneg(phydev); + + if (err < 0) + return err; + + return 0; +} + +static int dm9161_config_init(struct phy_device *phydev) +{ + int err; + + /* Isolate the PHY */ + err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE); + + if (err < 0) + return err; + + /* Do not bypass the scrambler/descrambler */ + err = phy_write(phydev, MII_DM9161_SCR, MII_DM9161_SCR_INIT); + + if (err < 0) + return err; + + /* Clear 10BTCSR to default */ + err = phy_write(phydev, MII_DM9161_10BTCSR, MII_DM9161_10BTCSR_INIT); + + if (err < 0) + return err; + + /* Reconnect the PHY, and enable Autonegotiation */ + err = phy_write(phydev, MII_BMCR, BMCR_ANENABLE); + + if (err < 0) + return err; + + return 0; +} + +static int dm9161_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, MII_DM9161_INTR); + + return (err < 0) ? err : 0; +} + +static struct phy_driver dm9161_driver = { + .phy_id = 0x0181b880, + .name = "Davicom DM9161E", + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES, + .config_init = dm9161_config_init, + .config_aneg = dm9161_config_aneg, + .read_status = genphy_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + +static struct phy_driver dm9131_driver = { + .phy_id = 0x00181b80, + .name = "Davicom DM9131", + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = dm9161_ack_interrupt, + .config_intr = dm9161_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init davicom_init(void) +{ + int ret; + + ret = phy_driver_register(&dm9161_driver); + if (ret) + goto err1; + + ret = phy_driver_register(&dm9131_driver); + if (ret) + goto err2; + return 0; + + err2: + phy_driver_unregister(&dm9161_driver); + err1: + return ret; +} + +static void __exit davicom_exit(void) +{ + phy_driver_unregister(&dm9161_driver); + phy_driver_unregister(&dm9131_driver); +} + +module_init(davicom_init); +module_exit(davicom_exit); diff --git a/drivers/net/phy/lxt.c b/drivers/net/phy/lxt.c new file mode 100644 index 000000000000..4c840448ec86 --- /dev/null +++ b/drivers/net/phy/lxt.c @@ -0,0 +1,179 @@ +/* + * drivers/net/phy/lxt.c + * + * Driver for Intel LXT PHYs + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +/* The Level one LXT970 is used by many boards */ + +#define MII_LXT970_IER 17 /* Interrupt Enable Register */ + +#define MII_LXT970_IER_IEN 0x0002 + +#define MII_LXT970_ISR 18 /* Interrupt Status Register */ + +#define MII_LXT970_CONFIG 19 /* Configuration Register */ + +/* ------------------------------------------------------------------------- */ +/* The Level one LXT971 is used on some of my custom boards */ + +/* register definitions for the 971 */ +#define MII_LXT971_IER 18 /* Interrupt Enable Register */ +#define MII_LXT971_IER_IEN 0x00f2 + +#define MII_LXT971_ISR 19 /* Interrupt Status Register */ + + +MODULE_DESCRIPTION("Intel LXT PHY driver"); +MODULE_AUTHOR("Andy Fleming"); +MODULE_LICENSE("GPL"); + +static int lxt970_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, MII_BMSR); + + if (err < 0) + return err; + + err = phy_read(phydev, MII_LXT970_ISR); + + if (err < 0) + return err; + + return 0; +} + +static int lxt970_config_intr(struct phy_device *phydev) +{ + int err; + + if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN); + else + err = phy_write(phydev, MII_LXT970_IER, 0); + + return err; +} + +static int lxt970_config_init(struct phy_device *phydev) +{ + int err; + + err = phy_write(phydev, MII_LXT970_CONFIG, 0); + + return err; +} + + +static int lxt971_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, MII_LXT971_ISR); + + if (err < 0) + return err; + + return 0; +} + +static int lxt971_config_intr(struct phy_device *phydev) +{ + int err; + + if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN); + else + err = phy_write(phydev, MII_LXT971_IER, 0); + + return err; +} + +static struct phy_driver lxt970_driver = { + .phy_id = 0x07810000, + .name = "LXT970", + .phy_id_mask = 0x0fffffff, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = lxt970_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = lxt970_ack_interrupt, + .config_intr = lxt970_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static struct phy_driver lxt971_driver = { + .phy_id = 0x0001378e, + .name = "LXT971", + .phy_id_mask = 0x0fffffff, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = lxt971_ack_interrupt, + .config_intr = lxt971_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init lxt_init(void) +{ + int ret; + + ret = phy_driver_register(&lxt970_driver); + if (ret) + goto err1; + + ret = phy_driver_register(&lxt971_driver); + if (ret) + goto err2; + return 0; + + err2: + phy_driver_unregister(&lxt970_driver); + err1: + return ret; +} + +static void __exit lxt_exit(void) +{ + phy_driver_unregister(&lxt970_driver); + phy_driver_unregister(&lxt971_driver); +} + +module_init(lxt_init); +module_exit(lxt_exit); diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c new file mode 100644 index 000000000000..4a72b025006b --- /dev/null +++ b/drivers/net/phy/marvell.c @@ -0,0 +1,140 @@ +/* + * drivers/net/phy/marvell.c + * + * Driver for Marvell PHYs + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +#define MII_M1011_IEVENT 0x13 +#define MII_M1011_IEVENT_CLEAR 0x0000 + +#define MII_M1011_IMASK 0x12 +#define MII_M1011_IMASK_INIT 0x6400 +#define MII_M1011_IMASK_CLEAR 0x0000 + +MODULE_DESCRIPTION("Marvell PHY driver"); +MODULE_AUTHOR("Andy Fleming"); +MODULE_LICENSE("GPL"); + +static int marvell_ack_interrupt(struct phy_device *phydev) +{ + int err; + + /* Clear the interrupts by reading the reg */ + err = phy_read(phydev, MII_M1011_IEVENT); + + if (err < 0) + return err; + + return 0; +} + +static int marvell_config_intr(struct phy_device *phydev) +{ + int err; + + if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_M1011_IMASK, MII_M1011_IMASK_INIT); + else + err = phy_write(phydev, MII_M1011_IMASK, MII_M1011_IMASK_CLEAR); + + return err; +} + +static int marvell_config_aneg(struct phy_device *phydev) +{ + int err; + + /* The Marvell PHY has an errata which requires + * that certain registers get written in order + * to restart autonegotiation */ + err = phy_write(phydev, MII_BMCR, BMCR_RESET); + + if (err < 0) + return err; + + err = phy_write(phydev, 0x1d, 0x1f); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1e, 0x200c); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1d, 0x5); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1e, 0); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1e, 0x100); + if (err < 0) + return err; + + + err = genphy_config_aneg(phydev); + + return err; +} + + +static struct phy_driver m88e1101_driver = { + .phy_id = 0x01410c00, + .phy_id_mask = 0xffffff00, + .name = "Marvell 88E1101", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &marvell_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init marvell_init(void) +{ + return phy_driver_register(&m88e1101_driver); +} + +static void __exit marvell_exit(void) +{ + phy_driver_unregister(&m88e1101_driver); +} + +module_init(marvell_init); +module_exit(marvell_exit); diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c new file mode 100644 index 000000000000..e75103ba6f86 --- /dev/null +++ b/drivers/net/phy/mdio_bus.c @@ -0,0 +1,173 @@ +/* + * drivers/net/phy/mdio_bus.c + * + * MDIO Bus interface + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +/* mdiobus_register + * + * description: Called by a bus driver to bring up all the PHYs + * on a given bus, and attach them to the bus + */ +int mdiobus_register(struct mii_bus *bus) +{ + int i; + int err = 0; + + spin_lock_init(&bus->mdio_lock); + + if (NULL == bus || NULL == bus->name || + NULL == bus->read || + NULL == bus->write) + return -EINVAL; + + if (bus->reset) + bus->reset(bus); + + for (i = 0; i < PHY_MAX_ADDR; i++) { + struct phy_device *phydev; + + phydev = get_phy_device(bus, i); + + if (IS_ERR(phydev)) + return PTR_ERR(phydev); + + /* There's a PHY at this address + * We need to set: + * 1) IRQ + * 2) bus_id + * 3) parent + * 4) bus + * 5) mii_bus + * And, we need to register it */ + if (phydev) { + phydev->irq = bus->irq[i]; + + phydev->dev.parent = bus->dev; + phydev->dev.bus = &mdio_bus_type; + sprintf(phydev->dev.bus_id, "phy%d:%d", bus->id, i); + + phydev->bus = bus; + + err = device_register(&phydev->dev); + + if (err) + printk(KERN_ERR "phy %d failed to register\n", + i); + } + + bus->phy_map[i] = phydev; + } + + pr_info("%s: probed\n", bus->name); + + return err; +} +EXPORT_SYMBOL(mdiobus_register); + +void mdiobus_unregister(struct mii_bus *bus) +{ + int i; + + for (i = 0; i < PHY_MAX_ADDR; i++) { + if (bus->phy_map[i]) { + device_unregister(&bus->phy_map[i]->dev); + kfree(bus->phy_map[i]); + } + } +} +EXPORT_SYMBOL(mdiobus_unregister); + +/* mdio_bus_match + * + * description: Given a PHY device, and a PHY driver, return 1 if + * the driver supports the device. Otherwise, return 0 + */ +static int mdio_bus_match(struct device *dev, struct device_driver *drv) +{ + struct phy_device *phydev = to_phy_device(dev); + struct phy_driver *phydrv = to_phy_driver(drv); + + return (phydrv->phy_id == (phydev->phy_id & phydrv->phy_id_mask)); +} + +/* Suspend and resume. Copied from platform_suspend and + * platform_resume + */ +static int mdio_bus_suspend(struct device * dev, u32 state) +{ + int ret = 0; + struct device_driver *drv = dev->driver; + + if (drv && drv->suspend) { + ret = drv->suspend(dev, state, SUSPEND_DISABLE); + if (ret == 0) + ret = drv->suspend(dev, state, SUSPEND_SAVE_STATE); + if (ret == 0) + ret = drv->suspend(dev, state, SUSPEND_POWER_DOWN); + } + return ret; +} + +static int mdio_bus_resume(struct device * dev) +{ + int ret = 0; + struct device_driver *drv = dev->driver; + + if (drv && drv->resume) { + ret = drv->resume(dev, RESUME_POWER_ON); + if (ret == 0) + ret = drv->resume(dev, RESUME_RESTORE_STATE); + if (ret == 0) + ret = drv->resume(dev, RESUME_ENABLE); + } + return ret; +} + +struct bus_type mdio_bus_type = { + .name = "mdio_bus", + .match = mdio_bus_match, + .suspend = mdio_bus_suspend, + .resume = mdio_bus_resume, +}; + +static int __init mdio_bus_init(void) +{ + return bus_register(&mdio_bus_type); +} + +subsys_initcall(mdio_bus_init); diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c new file mode 100644 index 000000000000..e2c6896b92d2 --- /dev/null +++ b/drivers/net/phy/phy.c @@ -0,0 +1,862 @@ +/* + * drivers/net/phy/phy.c + * + * Framework for configuring and reading PHY devices + * Based on code in sungem_phy.c and gianfar_phy.c + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +static void phy_change(void *data); +static void phy_timer(unsigned long data); + +/* Convenience function to print out the current phy status + */ +void phy_print_status(struct phy_device *phydev) +{ + pr_info("%s: Link is %s", phydev->dev.bus_id, + phydev->link ? "Up" : "Down"); + if (phydev->link) + printk(" - %d/%s", phydev->speed, + DUPLEX_FULL == phydev->duplex ? + "Full" : "Half"); + + printk("\n"); +} +EXPORT_SYMBOL(phy_print_status); + + +/* Convenience functions for reading/writing a given PHY + * register. They MUST NOT be called from interrupt context, + * because the bus read/write functions may wait for an interrupt + * to conclude the operation. */ +int phy_read(struct phy_device *phydev, u16 regnum) +{ + int retval; + struct mii_bus *bus = phydev->bus; + + spin_lock_bh(&bus->mdio_lock); + retval = bus->read(bus, phydev->addr, regnum); + spin_unlock_bh(&bus->mdio_lock); + + return retval; +} +EXPORT_SYMBOL(phy_read); + +int phy_write(struct phy_device *phydev, u16 regnum, u16 val) +{ + int err; + struct mii_bus *bus = phydev->bus; + + spin_lock_bh(&bus->mdio_lock); + err = bus->write(bus, phydev->addr, regnum, val); + spin_unlock_bh(&bus->mdio_lock); + + return err; +} +EXPORT_SYMBOL(phy_write); + + +int phy_clear_interrupt(struct phy_device *phydev) +{ + int err = 0; + + if (phydev->drv->ack_interrupt) + err = phydev->drv->ack_interrupt(phydev); + + return err; +} + + +int phy_config_interrupt(struct phy_device *phydev, u32 interrupts) +{ + int err = 0; + + phydev->interrupts = interrupts; + if (phydev->drv->config_intr) + err = phydev->drv->config_intr(phydev); + + return err; +} + + +/* phy_aneg_done + * + * description: Reads the status register and returns 0 either if + * auto-negotiation is incomplete, or if there was an error. + * Returns BMSR_ANEGCOMPLETE if auto-negotiation is done. + */ +static inline int phy_aneg_done(struct phy_device *phydev) +{ + int retval; + + retval = phy_read(phydev, MII_BMSR); + + return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); +} + +/* phy_start_aneg + * + * description: Calls the PHY driver's config_aneg, and then + * sets the PHY state to PHY_AN if auto-negotiation is enabled, + * and to PHY_FORCING if auto-negotiation is disabled. Unless + * the PHY is currently HALTED. + */ +int phy_start_aneg(struct phy_device *phydev) +{ + int err; + + spin_lock(&phydev->lock); + + if (AUTONEG_DISABLE == phydev->autoneg) + phy_sanitize_settings(phydev); + + err = phydev->drv->config_aneg(phydev); + + if (err < 0) + goto out_unlock; + + if (phydev->state != PHY_HALTED) { + if (AUTONEG_ENABLE == phydev->autoneg) { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; + } else { + phydev->state = PHY_FORCING; + phydev->link_timeout = PHY_FORCE_TIMEOUT; + } + } + +out_unlock: + spin_unlock(&phydev->lock); + return err; +} +EXPORT_SYMBOL(phy_start_aneg); + + +/* A structure for mapping a particular speed and duplex + * combination to a particular SUPPORTED and ADVERTISED value */ +struct phy_setting { + int speed; + int duplex; + u32 setting; +}; + +/* A mapping of all SUPPORTED settings to speed/duplex */ +static struct phy_setting settings[] = { + { + .speed = 10000, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_10000baseT_Full, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_1000baseT_Full, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_HALF, + .setting = SUPPORTED_1000baseT_Half, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_100baseT_Full, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_HALF, + .setting = SUPPORTED_100baseT_Half, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_10baseT_Full, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_HALF, + .setting = SUPPORTED_10baseT_Half, + }, +}; + +#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting)) + +/* phy_find_setting + * + * description: Searches the settings array for the setting which + * matches the desired speed and duplex, and returns the index + * of that setting. Returns the index of the last setting if + * none of the others match. + */ +static inline int phy_find_setting(int speed, int duplex) +{ + int idx = 0; + + while (idx < ARRAY_SIZE(settings) && + (settings[idx].speed != speed || + settings[idx].duplex != duplex)) + idx++; + + return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; +} + +/* phy_find_valid + * idx: The first index in settings[] to search + * features: A mask of the valid settings + * + * description: Returns the index of the first valid setting less + * than or equal to the one pointed to by idx, as determined by + * the mask in features. Returns the index of the last setting + * if nothing else matches. + */ +static inline int phy_find_valid(int idx, u32 features) +{ + while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features)) + idx++; + + return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; +} + +/* phy_sanitize_settings + * + * description: Make sure the PHY is set to supported speeds and + * duplexes. Drop down by one in this order: 1000/FULL, + * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF + */ +void phy_sanitize_settings(struct phy_device *phydev) +{ + u32 features = phydev->supported; + int idx; + + /* Sanitize settings based on PHY capabilities */ + if ((features & SUPPORTED_Autoneg) == 0) + phydev->autoneg = 0; + + idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex), + features); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; +} +EXPORT_SYMBOL(phy_sanitize_settings); + +/* phy_force_reduction + * + * description: Reduces the speed/duplex settings by + * one notch. The order is so: + * 1000/FULL, 1000/HALF, 100/FULL, 100/HALF, + * 10/FULL, 10/HALF. The function bottoms out at 10/HALF. + */ +static void phy_force_reduction(struct phy_device *phydev) +{ + int idx; + + idx = phy_find_setting(phydev->speed, phydev->duplex); + + idx++; + + idx = phy_find_valid(idx, phydev->supported); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; + + pr_info("Trying %d/%s\n", phydev->speed, + DUPLEX_FULL == phydev->duplex ? + "FULL" : "HALF"); +} + +/* phy_ethtool_sset: + * A generic ethtool sset function. Handles all the details + * + * A few notes about parameter checking: + * - We don't set port or transceiver, so we don't care what they + * were set to. + * - phy_start_aneg() will make sure forced settings are sane, and + * choose the next best ones from the ones selected, so we don't + * care if ethtool tries to give us bad values + */ +int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) +{ + if (cmd->phy_address != phydev->addr) + return -EINVAL; + + /* We make sure that we don't pass unsupported + * values in to the PHY */ + cmd->advertising &= phydev->supported; + + /* Verify the settings we care about. */ + if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE) + return -EINVAL; + + if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0) + return -EINVAL; + + if (cmd->autoneg == AUTONEG_DISABLE + && ((cmd->speed != SPEED_1000 + && cmd->speed != SPEED_100 + && cmd->speed != SPEED_10) + || (cmd->duplex != DUPLEX_HALF + && cmd->duplex != DUPLEX_FULL))) + return -EINVAL; + + phydev->autoneg = cmd->autoneg; + + phydev->speed = cmd->speed; + + phydev->advertising = cmd->advertising; + + if (AUTONEG_ENABLE == cmd->autoneg) + phydev->advertising |= ADVERTISED_Autoneg; + else + phydev->advertising &= ~ADVERTISED_Autoneg; + + phydev->duplex = cmd->duplex; + + /* Restart the PHY */ + phy_start_aneg(phydev); + + return 0; +} + +int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd) +{ + cmd->supported = phydev->supported; + + cmd->advertising = phydev->advertising; + + cmd->speed = phydev->speed; + cmd->duplex = phydev->duplex; + cmd->port = PORT_MII; + cmd->phy_address = phydev->addr; + cmd->transceiver = XCVR_EXTERNAL; + cmd->autoneg = phydev->autoneg; + + return 0; +} + + +/* Note that this function is currently incompatible with the + * PHYCONTROL layer. It changes registers without regard to + * current state. Use at own risk + */ +int phy_mii_ioctl(struct phy_device *phydev, + struct mii_ioctl_data *mii_data, int cmd) +{ + u16 val = mii_data->val_in; + + switch (cmd) { + case SIOCGMIIPHY: + mii_data->phy_id = phydev->addr; + break; + case SIOCGMIIREG: + mii_data->val_out = phy_read(phydev, mii_data->reg_num); + break; + + case SIOCSMIIREG: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (mii_data->phy_id == phydev->addr) { + switch(mii_data->reg_num) { + case MII_BMCR: + if (val & (BMCR_RESET|BMCR_ANENABLE)) + phydev->autoneg = AUTONEG_DISABLE; + else + phydev->autoneg = AUTONEG_ENABLE; + if ((!phydev->autoneg) && (val & BMCR_FULLDPLX)) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + break; + case MII_ADVERTISE: + phydev->advertising = val; + break; + default: + /* do nothing */ + break; + } + } + + phy_write(phydev, mii_data->reg_num, val); + + if (mii_data->reg_num == MII_BMCR + && val & BMCR_RESET + && phydev->drv->config_init) + phydev->drv->config_init(phydev); + break; + } + + return 0; +} + +/* phy_start_machine: + * + * description: The PHY infrastructure can run a state machine + * which tracks whether the PHY is starting up, negotiating, + * etc. This function starts the timer which tracks the state + * of the PHY. If you want to be notified when the state + * changes, pass in the callback, otherwise, pass NULL. If you + * want to maintain your own state machine, do not call this + * function. */ +void phy_start_machine(struct phy_device *phydev, + void (*handler)(struct net_device *)) +{ + phydev->adjust_state = handler; + + init_timer(&phydev->phy_timer); + phydev->phy_timer.function = &phy_timer; + phydev->phy_timer.data = (unsigned long) phydev; + mod_timer(&phydev->phy_timer, jiffies + HZ); +} + +/* phy_stop_machine + * + * description: Stops the state machine timer, sets the state to + * UP (unless it wasn't up yet), and then frees the interrupt, + * if it is in use. This function must be called BEFORE + * phy_detach. + */ +void phy_stop_machine(struct phy_device *phydev) +{ + del_timer_sync(&phydev->phy_timer); + + spin_lock(&phydev->lock); + if (phydev->state > PHY_UP) + phydev->state = PHY_UP; + spin_unlock(&phydev->lock); + + if (phydev->irq != PHY_POLL) + phy_stop_interrupts(phydev); + + phydev->adjust_state = NULL; +} + +#ifdef CONFIG_PHYCONTROL +/* phy_error: + * + * Moves the PHY to the HALTED state in response to a read + * or write error, and tells the controller the link is down. + * Must not be called from interrupt context, or while the + * phydev->lock is held. + */ +void phy_error(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + phydev->state = PHY_HALTED; + spin_unlock(&phydev->lock); +} + +/* phy_interrupt + * + * description: When a PHY interrupt occurs, the handler disables + * interrupts, and schedules a work task to clear the interrupt. + */ +static irqreturn_t phy_interrupt(int irq, void *phy_dat, struct pt_regs *regs) +{ + struct phy_device *phydev = phy_dat; + + /* The MDIO bus is not allowed to be written in interrupt + * context, so we need to disable the irq here. A work + * queue will write the PHY to disable and clear the + * interrupt, and then reenable the irq line. */ + disable_irq_nosync(irq); + + schedule_work(&phydev->phy_queue); + + return IRQ_HANDLED; +} + +/* Enable the interrupts from the PHY side */ +int phy_enable_interrupts(struct phy_device *phydev) +{ + int err; + + err = phy_clear_interrupt(phydev); + + if (err < 0) + return err; + + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + + return err; +} +EXPORT_SYMBOL(phy_enable_interrupts); + +/* Disable the PHY interrupts from the PHY side */ +int phy_disable_interrupts(struct phy_device *phydev) +{ + int err; + + /* Disable PHY interrupts */ + err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + + if (err) + goto phy_err; + + /* Clear the interrupt */ + err = phy_clear_interrupt(phydev); + + if (err) + goto phy_err; + + return 0; + +phy_err: + phy_error(phydev); + + return err; +} +EXPORT_SYMBOL(phy_disable_interrupts); + +/* phy_start_interrupts + * + * description: Request the interrupt for the given PHY. If + * this fails, then we set irq to PHY_POLL. + * Otherwise, we enable the interrupts in the PHY. + * Returns 0 on success. + * This should only be called with a valid IRQ number. + */ +int phy_start_interrupts(struct phy_device *phydev) +{ + int err = 0; + + INIT_WORK(&phydev->phy_queue, phy_change, phydev); + + if (request_irq(phydev->irq, phy_interrupt, + SA_SHIRQ, + "phy_interrupt", + phydev) < 0) { + printk(KERN_WARNING "%s: Can't get IRQ %d (PHY)\n", + phydev->bus->name, + phydev->irq); + phydev->irq = PHY_POLL; + return 0; + } + + err = phy_enable_interrupts(phydev); + + return err; +} +EXPORT_SYMBOL(phy_start_interrupts); + +int phy_stop_interrupts(struct phy_device *phydev) +{ + int err; + + err = phy_disable_interrupts(phydev); + + if (err) + phy_error(phydev); + + free_irq(phydev->irq, phydev); + + return err; +} +EXPORT_SYMBOL(phy_stop_interrupts); + + +/* Scheduled by the phy_interrupt/timer to handle PHY changes */ +static void phy_change(void *data) +{ + int err; + struct phy_device *phydev = data; + + err = phy_disable_interrupts(phydev); + + if (err) + goto phy_err; + + spin_lock(&phydev->lock); + if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) + phydev->state = PHY_CHANGELINK; + spin_unlock(&phydev->lock); + + enable_irq(phydev->irq); + + /* Reenable interrupts */ + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + + if (err) + goto irq_enable_err; + + return; + +irq_enable_err: + disable_irq(phydev->irq); +phy_err: + phy_error(phydev); +} + +/* Bring down the PHY link, and stop checking the status. */ +void phy_stop(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + + if (PHY_HALTED == phydev->state) + goto out_unlock; + + if (phydev->irq != PHY_POLL) { + /* Clear any pending interrupts */ + phy_clear_interrupt(phydev); + + /* Disable PHY Interrupts */ + phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + } + + phydev->state = PHY_HALTED; + +out_unlock: + spin_unlock(&phydev->lock); +} + + +/* phy_start + * + * description: Indicates the attached device's readiness to + * handle PHY-related work. Used during startup to start the + * PHY, and after a call to phy_stop() to resume operation. + * Also used to indicate the MDIO bus has cleared an error + * condition. + */ +void phy_start(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + + switch (phydev->state) { + case PHY_STARTING: + phydev->state = PHY_PENDING; + break; + case PHY_READY: + phydev->state = PHY_UP; + break; + case PHY_HALTED: + phydev->state = PHY_RESUMING; + default: + break; + } + spin_unlock(&phydev->lock); +} +EXPORT_SYMBOL(phy_stop); +EXPORT_SYMBOL(phy_start); + +/* PHY timer which handles the state machine */ +static void phy_timer(unsigned long data) +{ + struct phy_device *phydev = (struct phy_device *)data; + int needs_aneg = 0; + int err = 0; + + spin_lock(&phydev->lock); + + if (phydev->adjust_state) + phydev->adjust_state(phydev->attached_dev); + + switch(phydev->state) { + case PHY_DOWN: + case PHY_STARTING: + case PHY_READY: + case PHY_PENDING: + break; + case PHY_UP: + needs_aneg = 1; + + phydev->link_timeout = PHY_AN_TIMEOUT; + + break; + case PHY_AN: + /* Check if negotiation is done. Break + * if there's an error */ + err = phy_aneg_done(phydev); + if (err < 0) + break; + + /* If auto-negotiation is done, we change to + * either RUNNING, or NOLINK */ + if (err > 0) { + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + phydev->state = PHY_NOLINK; + netif_carrier_off(phydev->attached_dev); + } + + phydev->adjust_link(phydev->attached_dev); + + } else if (0 == phydev->link_timeout--) { + /* The counter expired, so either we + * switch to forced mode, or the + * magic_aneg bit exists, and we try aneg + * again */ + if (!(phydev->drv->flags & PHY_HAS_MAGICANEG)) { + int idx; + + /* We'll start from the + * fastest speed, and work + * our way down */ + idx = phy_find_valid(0, + phydev->supported); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; + + phydev->autoneg = AUTONEG_DISABLE; + phydev->state = PHY_FORCING; + phydev->link_timeout = + PHY_FORCE_TIMEOUT; + + pr_info("Trying %d/%s\n", + phydev->speed, + DUPLEX_FULL == + phydev->duplex ? + "FULL" : "HALF"); + } + + needs_aneg = 1; + } + break; + case PHY_NOLINK: + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + } + break; + case PHY_FORCING: + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + if (0 == phydev->link_timeout--) { + phy_force_reduction(phydev); + needs_aneg = 1; + } + } + + phydev->adjust_link(phydev->attached_dev); + break; + case PHY_RUNNING: + /* Only register a CHANGE if we are + * polling */ + if (PHY_POLL == phydev->irq) + phydev->state = PHY_CHANGELINK; + break; + case PHY_CHANGELINK: + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + phydev->state = PHY_NOLINK; + netif_carrier_off(phydev->attached_dev); + } + + phydev->adjust_link(phydev->attached_dev); + + if (PHY_POLL != phydev->irq) + err = phy_config_interrupt(phydev, + PHY_INTERRUPT_ENABLED); + break; + case PHY_HALTED: + if (phydev->link) { + phydev->link = 0; + netif_carrier_off(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + } + break; + case PHY_RESUMING: + + err = phy_clear_interrupt(phydev); + + if (err) + break; + + err = phy_config_interrupt(phydev, + PHY_INTERRUPT_ENABLED); + + if (err) + break; + + if (AUTONEG_ENABLE == phydev->autoneg) { + err = phy_aneg_done(phydev); + if (err < 0) + break; + + /* err > 0 if AN is done. + * Otherwise, it's 0, and we're + * still waiting for AN */ + if (err > 0) { + phydev->state = PHY_RUNNING; + } else { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; + } + } else + phydev->state = PHY_RUNNING; + break; + } + + spin_unlock(&phydev->lock); + + if (needs_aneg) + err = phy_start_aneg(phydev); + + if (err < 0) + phy_error(phydev); + + mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); +} + +#endif /* CONFIG_PHYCONTROL */ diff --git a/drivers/net/phy/phy.c.orig b/drivers/net/phy/phy.c.orig new file mode 100644 index 000000000000..6af17cec9ace --- /dev/null +++ b/drivers/net/phy/phy.c.orig @@ -0,0 +1,860 @@ +/* + * drivers/net/phy/phy.c + * + * Framework for configuring and reading PHY devices + * Based on code in sungem_phy.c and gianfar_phy.c + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +static void phy_change(void *data); +static void phy_timer(unsigned long data); + +/* Convenience function to print out the current phy status + */ +void phy_print_status(struct phy_device *phydev) +{ + pr_info("%s: Link is %s", phydev->dev.bus_id, + phydev->link ? "Up" : "Down"); + if (phydev->link) + printk(" - %d/%s", phydev->speed, + DUPLEX_FULL == phydev->duplex ? + "Full" : "Half"); + + printk("\n"); +} +EXPORT_SYMBOL(phy_print_status); + + +/* Convenience functions for reading/writing a given PHY + * register. They MUST NOT be called from interrupt context, + * because the bus read/write functions may wait for an interrupt + * to conclude the operation. */ +int phy_read(struct phy_device *phydev, u16 regnum) +{ + int retval; + struct mii_bus *bus = phydev->bus; + + spin_lock_bh(&bus->mdio_lock); + retval = bus->read(bus, phydev->addr, regnum); + spin_unlock_bh(&bus->mdio_lock); + + return retval; +} +EXPORT_SYMBOL(phy_read); + +int phy_write(struct phy_device *phydev, u16 regnum, u16 val) +{ + int err; + struct mii_bus *bus = phydev->bus; + + spin_lock_bh(&bus->mdio_lock); + err = bus->write(bus, phydev->addr, regnum, val); + spin_unlock_bh(&bus->mdio_lock); + + return err; +} +EXPORT_SYMBOL(phy_write); + + +int phy_clear_interrupt(struct phy_device *phydev) +{ + int err = 0; + + if (phydev->drv->ack_interrupt) + err = phydev->drv->ack_interrupt(phydev); + + return err; +} + + +int phy_config_interrupt(struct phy_device *phydev, u32 interrupts) +{ + int err = 0; + + phydev->interrupts = interrupts; + if (phydev->drv->config_intr) + err = phydev->drv->config_intr(phydev); + + return err; +} + + +/* phy_aneg_done + * + * description: Reads the status register and returns 0 either if + * auto-negotiation is incomplete, or if there was an error. + * Returns BMSR_ANEGCOMPLETE if auto-negotiation is done. + */ +static inline int phy_aneg_done(struct phy_device *phydev) +{ + int retval; + + retval = phy_read(phydev, MII_BMSR); + + return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); +} + +/* phy_start_aneg + * + * description: Calls the PHY driver's config_aneg, and then + * sets the PHY state to PHY_AN if auto-negotiation is enabled, + * and to PHY_FORCING if auto-negotiation is disabled. Unless + * the PHY is currently HALTED. + */ +int phy_start_aneg(struct phy_device *phydev) +{ + int err; + + spin_lock(&phydev->lock); + + if (AUTONEG_DISABLE == phydev->autoneg) + phy_sanitize_settings(phydev); + + err = phydev->drv->config_aneg(phydev); + + if (err < 0) + goto out_unlock; + + if (phydev->state != PHY_HALTED) { + if (AUTONEG_ENABLE == phydev->autoneg) { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; + } else { + phydev->state = PHY_FORCING; + phydev->link_timeout = PHY_FORCE_TIMEOUT; + } + } + +out_unlock: + spin_unlock(&phydev->lock); + return err; +} +EXPORT_SYMBOL(phy_start_aneg); + + +/* A structure for mapping a particular speed and duplex + * combination to a particular SUPPORTED and ADVERTISED value */ +struct phy_setting { + int speed; + int duplex; + u32 setting; +}; + +/* A mapping of all SUPPORTED settings to speed/duplex */ +static struct phy_setting settings[] = { + { + .speed = 10000, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_10000baseT_Full, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_1000baseT_Full, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_HALF, + .setting = SUPPORTED_1000baseT_Half, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_100baseT_Full, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_HALF, + .setting = SUPPORTED_100baseT_Half, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_FULL, + .setting = SUPPORTED_10baseT_Full, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_HALF, + .setting = SUPPORTED_10baseT_Half, + }, +}; + +#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting)) + +/* phy_find_setting + * + * description: Searches the settings array for the setting which + * matches the desired speed and duplex, and returns the index + * of that setting. Returns the index of the last setting if + * none of the others match. + */ +static inline int phy_find_setting(int speed, int duplex) +{ + int idx = 0; + + while (idx < ARRAY_SIZE(settings) && + (settings[idx].speed != speed || + settings[idx].duplex != duplex)) + idx++; + + return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; +} + +/* phy_find_valid + * idx: The first index in settings[] to search + * features: A mask of the valid settings + * + * description: Returns the index of the first valid setting less + * than or equal to the one pointed to by idx, as determined by + * the mask in features. Returns the index of the last setting + * if nothing else matches. + */ +static inline int phy_find_valid(int idx, u32 features) +{ + while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features)) + idx++; + + return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; +} + +/* phy_sanitize_settings + * + * description: Make sure the PHY is set to supported speeds and + * duplexes. Drop down by one in this order: 1000/FULL, + * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF + */ +void phy_sanitize_settings(struct phy_device *phydev) +{ + u32 features = phydev->supported; + int idx; + + /* Sanitize settings based on PHY capabilities */ + if ((features & SUPPORTED_Autoneg) == 0) + phydev->autoneg = 0; + + idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex), + features); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; +} +EXPORT_SYMBOL(phy_sanitize_settings); + +/* phy_force_reduction + * + * description: Reduces the speed/duplex settings by + * one notch. The order is so: + * 1000/FULL, 1000/HALF, 100/FULL, 100/HALF, + * 10/FULL, 10/HALF. The function bottoms out at 10/HALF. + */ +static void phy_force_reduction(struct phy_device *phydev) +{ + int idx; + + idx = phy_find_setting(phydev->speed, phydev->duplex); + + idx++; + + idx = phy_find_valid(idx, phydev->supported); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; + + pr_info("Trying %d/%s\n", phydev->speed, + DUPLEX_FULL == phydev->duplex ? + "FULL" : "HALF"); +} + +/* phy_ethtool_sset: + * A generic ethtool sset function. Handles all the details + * + * A few notes about parameter checking: + * - We don't set port or transceiver, so we don't care what they + * were set to. + * - phy_start_aneg() will make sure forced settings are sane, and + * choose the next best ones from the ones selected, so we don't + * care if ethtool tries to give us bad values + */ +int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) +{ + if (cmd->phy_address != phydev->addr) + return -EINVAL; + + /* We make sure that we don't pass unsupported + * values in to the PHY */ + cmd->advertising &= phydev->supported; + + /* Verify the settings we care about. */ + if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE) + return -EINVAL; + + if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0) + return -EINVAL; + + if (cmd->autoneg == AUTONEG_DISABLE + && ((cmd->speed != SPEED_1000 + && cmd->speed != SPEED_100 + && cmd->speed != SPEED_10) + || (cmd->duplex != DUPLEX_HALF + && cmd->duplex != DUPLEX_FULL))) + return -EINVAL; + + phydev->autoneg = cmd->autoneg; + + phydev->speed = cmd->speed; + + phydev->advertising = cmd->advertising; + + if (AUTONEG_ENABLE == cmd->autoneg) + phydev->advertising |= ADVERTISED_Autoneg; + else + phydev->advertising &= ~ADVERTISED_Autoneg; + + phydev->duplex = cmd->duplex; + + /* Restart the PHY */ + phy_start_aneg(phydev); + + return 0; +} + +int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd) +{ + cmd->supported = phydev->supported; + + cmd->advertising = phydev->advertising; + + cmd->speed = phydev->speed; + cmd->duplex = phydev->duplex; + cmd->port = PORT_MII; + cmd->phy_address = phydev->addr; + cmd->transceiver = XCVR_EXTERNAL; + cmd->autoneg = phydev->autoneg; + + return 0; +} + + +/* Note that this function is currently incompatible with the + * PHYCONTROL layer. It changes registers without regard to + * current state. Use at own risk + */ +int phy_mii_ioctl(struct phy_device *phydev, + struct mii_ioctl_data *mii_data, int cmd) +{ + u16 val = mii_data->val_in; + + switch (cmd) { + case SIOCGMIIPHY: + mii_data->phy_id = phydev->addr; + break; + case SIOCGMIIREG: + mii_data->val_out = phy_read(phydev, mii_data->reg_num); + break; + + case SIOCSMIIREG: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (mii_data->phy_id == phydev->addr) { + switch(mii_data->reg_num) { + case MII_BMCR: + if (val & (BMCR_RESET|BMCR_ANENABLE)) + phydev->autoneg = AUTONEG_DISABLE; + else + phydev->autoneg = AUTONEG_ENABLE; + if ((!phydev->autoneg) && (val & BMCR_FULLDPLX)) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + break; + case MII_ADVERTISE: + phydev->advertising = val; + break; + default: + /* do nothing */ + break; + } + } + + phy_write(phydev, mii_data->reg_num, val); + + if (mii_data->reg_num == MII_BMCR + && val & BMCR_RESET + && phydev->drv->config_init) + phydev->drv->config_init(phydev); + break; + } + + return 0; +} + +/* phy_start_machine: + * + * description: The PHY infrastructure can run a state machine + * which tracks whether the PHY is starting up, negotiating, + * etc. This function starts the timer which tracks the state + * of the PHY. If you want to be notified when the state + * changes, pass in the callback, otherwise, pass NULL. If you + * want to maintain your own state machine, do not call this + * function. */ +void phy_start_machine(struct phy_device *phydev, + void (*handler)(struct net_device *)) +{ + phydev->adjust_state = handler; + + init_timer(&phydev->phy_timer); + phydev->phy_timer.function = &phy_timer; + phydev->phy_timer.data = (unsigned long) phydev; + mod_timer(&phydev->phy_timer, jiffies + HZ); +} + +/* phy_stop_machine + * + * description: Stops the state machine timer, sets the state to + * UP (unless it wasn't up yet), and then frees the interrupt, + * if it is in use. This function must be called BEFORE + * phy_detach. + */ +void phy_stop_machine(struct phy_device *phydev) +{ + del_timer_sync(&phydev->phy_timer); + + spin_lock(&phydev->lock); + if (phydev->state > PHY_UP) + phydev->state = PHY_UP; + spin_unlock(&phydev->lock); + + if (phydev->irq != PHY_POLL) + phy_stop_interrupts(phydev); + + phydev->adjust_state = NULL; +} + +#ifdef CONFIG_PHYCONTROL +/* phy_error: + * + * Moves the PHY to the HALTED state in response to a read + * or write error, and tells the controller the link is down. + * Must not be called from interrupt context, or while the + * phydev->lock is held. + */ +void phy_error(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + phydev->state = PHY_HALTED; + spin_unlock(&phydev->lock); +} + +/* phy_interrupt + * + * description: When a PHY interrupt occurs, the handler disables + * interrupts, and schedules a work task to clear the interrupt. + */ +static irqreturn_t phy_interrupt(int irq, void *phy_dat, struct pt_regs *regs) +{ + struct phy_device *phydev = phy_dat; + + /* The MDIO bus is not allowed to be written in interrupt + * context, so we need to disable the irq here. A work + * queue will write the PHY to disable and clear the + * interrupt, and then reenable the irq line. */ + disable_irq_nosync(irq); + + schedule_work(&phydev->phy_queue); + + return IRQ_HANDLED; +} + +/* Enable the interrupts from the PHY side */ +int phy_enable_interrupts(struct phy_device *phydev) +{ + int err; + + err = phy_clear_interrupt(phydev); + + if (err < 0) + return err; + + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + + return err; +} + +/* Disable the PHY interrupts from the PHY side */ +int phy_disable_interrupts(struct phy_device *phydev) +{ + int err; + + /* Disable PHY interrupts */ + err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + + if (err) + goto phy_err; + + /* Clear the interrupt */ + err = phy_clear_interrupt(phydev); + + if (err) + goto phy_err; + + return 0; + +phy_err: + phy_error(phydev); + + return err; +} + +/* phy_start_interrupts + * + * description: Request the interrupt for the given PHY. If + * this fails, then we set irq to PHY_POLL. + * Otherwise, we enable the interrupts in the PHY. + * Returns 0 on success. + * This should only be called with a valid IRQ number. + */ +int phy_start_interrupts(struct phy_device *phydev) +{ + int err = 0; + + INIT_WORK(&phydev->phy_queue, phy_change, phydev); + + if (request_irq(phydev->irq, phy_interrupt, + SA_SHIRQ, + "phy_interrupt", + phydev) < 0) { + printk(KERN_WARNING "%s: Can't get IRQ %d (PHY)\n", + phydev->bus->name, + phydev->irq); + phydev->irq = PHY_POLL; + return 0; + } + + err = phy_enable_interrupts(phydev); + + return err; +} +EXPORT_SYMBOL(phy_start_interrupts); + +int phy_stop_interrupts(struct phy_device *phydev) +{ + int err; + + err = phy_disable_interrupts(phydev); + + if (err) + phy_error(phydev); + + free_irq(phydev->irq, phydev); + + return err; +} +EXPORT_SYMBOL(phy_stop_interrupts); + + +/* Scheduled by the phy_interrupt/timer to handle PHY changes */ +static void phy_change(void *data) +{ + int err; + struct phy_device *phydev = data; + + err = phy_disable_interrupts(phydev); + + if (err) + goto phy_err; + + spin_lock(&phydev->lock); + if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) + phydev->state = PHY_CHANGELINK; + spin_unlock(&phydev->lock); + + enable_irq(phydev->irq); + + /* Reenable interrupts */ + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + + if (err) + goto irq_enable_err; + + return; + +irq_enable_err: + disable_irq(phydev->irq); +phy_err: + phy_error(phydev); +} + +/* Bring down the PHY link, and stop checking the status. */ +void phy_stop(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + + if (PHY_HALTED == phydev->state) + goto out_unlock; + + if (phydev->irq != PHY_POLL) { + /* Clear any pending interrupts */ + phy_clear_interrupt(phydev); + + /* Disable PHY Interrupts */ + phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + } + + phydev->state = PHY_HALTED; + +out_unlock: + spin_unlock(&phydev->lock); +} + + +/* phy_start + * + * description: Indicates the attached device's readiness to + * handle PHY-related work. Used during startup to start the + * PHY, and after a call to phy_stop() to resume operation. + * Also used to indicate the MDIO bus has cleared an error + * condition. + */ +void phy_start(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + + switch (phydev->state) { + case PHY_STARTING: + phydev->state = PHY_PENDING; + break; + case PHY_READY: + phydev->state = PHY_UP; + break; + case PHY_HALTED: + phydev->state = PHY_RESUMING; + default: + break; + } + spin_unlock(&phydev->lock); +} +EXPORT_SYMBOL(phy_stop); +EXPORT_SYMBOL(phy_start); + +/* PHY timer which handles the state machine */ +static void phy_timer(unsigned long data) +{ + struct phy_device *phydev = (struct phy_device *)data; + int needs_aneg = 0; + int err = 0; + + spin_lock(&phydev->lock); + + if (phydev->adjust_state) + phydev->adjust_state(phydev->attached_dev); + + switch(phydev->state) { + case PHY_DOWN: + case PHY_STARTING: + case PHY_READY: + case PHY_PENDING: + break; + case PHY_UP: + needs_aneg = 1; + + phydev->link_timeout = PHY_AN_TIMEOUT; + + break; + case PHY_AN: + /* Check if negotiation is done. Break + * if there's an error */ + err = phy_aneg_done(phydev); + if (err < 0) + break; + + /* If auto-negotiation is done, we change to + * either RUNNING, or NOLINK */ + if (err > 0) { + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + phydev->state = PHY_NOLINK; + netif_carrier_off(phydev->attached_dev); + } + + phydev->adjust_link(phydev->attached_dev); + + } else if (0 == phydev->link_timeout--) { + /* The counter expired, so either we + * switch to forced mode, or the + * magic_aneg bit exists, and we try aneg + * again */ + if (!(phydev->drv->flags & PHY_HAS_MAGICANEG)) { + int idx; + + /* We'll start from the + * fastest speed, and work + * our way down */ + idx = phy_find_valid(0, + phydev->supported); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; + + phydev->autoneg = AUTONEG_DISABLE; + phydev->state = PHY_FORCING; + phydev->link_timeout = + PHY_FORCE_TIMEOUT; + + pr_info("Trying %d/%s\n", + phydev->speed, + DUPLEX_FULL == + phydev->duplex ? + "FULL" : "HALF"); + } + + needs_aneg = 1; + } + break; + case PHY_NOLINK: + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + } + break; + case PHY_FORCING: + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + if (0 == phydev->link_timeout--) { + phy_force_reduction(phydev); + needs_aneg = 1; + } + } + + phydev->adjust_link(phydev->attached_dev); + break; + case PHY_RUNNING: + /* Only register a CHANGE if we are + * polling */ + if (PHY_POLL == phydev->irq) + phydev->state = PHY_CHANGELINK; + break; + case PHY_CHANGELINK: + err = phy_read_status(phydev); + + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + phydev->state = PHY_NOLINK; + netif_carrier_off(phydev->attached_dev); + } + + phydev->adjust_link(phydev->attached_dev); + + if (PHY_POLL != phydev->irq) + err = phy_config_interrupt(phydev, + PHY_INTERRUPT_ENABLED); + break; + case PHY_HALTED: + if (phydev->link) { + phydev->link = 0; + netif_carrier_off(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + } + break; + case PHY_RESUMING: + + err = phy_clear_interrupt(phydev); + + if (err) + break; + + err = phy_config_interrupt(phydev, + PHY_INTERRUPT_ENABLED); + + if (err) + break; + + if (AUTONEG_ENABLE == phydev->autoneg) { + err = phy_aneg_done(phydev); + if (err < 0) + break; + + /* err > 0 if AN is done. + * Otherwise, it's 0, and we're + * still waiting for AN */ + if (err > 0) { + phydev->state = PHY_RUNNING; + } else { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; + } + } else + phydev->state = PHY_RUNNING; + break; + } + + spin_unlock(&phydev->lock); + + if (needs_aneg) + err = phy_start_aneg(phydev); + + if (err < 0) + phy_error(phydev); + + mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); +} + +#endif /* CONFIG_PHYCONTROL */ diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c new file mode 100644 index 000000000000..f0595af4c837 --- /dev/null +++ b/drivers/net/phy/phy_device.c @@ -0,0 +1,682 @@ +/* + * drivers/net/phy/phy_device.c + * + * Framework for finding and configuring PHYs. + * Also contains generic PHY driver + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +/* get_phy_device + * + * description: Reads the ID registers of the PHY at addr on the + * bus, then allocates and returns the phy_device to + * represent it. + */ +struct phy_device * get_phy_device(struct mii_bus *bus, int addr) +{ + int phy_reg; + u32 phy_id; + struct phy_device *dev = NULL; + + /* Grab the bits from PHYIR1, and put them + * in the upper half */ + phy_reg = bus->read(bus, addr, MII_PHYSID1); + + if (phy_reg < 0) + return ERR_PTR(phy_reg); + + phy_id = (phy_reg & 0xffff) << 16; + + /* Grab the bits from PHYIR2, and put them in the lower half */ + phy_reg = bus->read(bus, addr, MII_PHYSID2); + + if (phy_reg < 0) + return ERR_PTR(phy_reg); + + phy_id |= (phy_reg & 0xffff); + + /* If the phy_id is all Fs, there is no device there */ + if (0xffffffff == phy_id) + return NULL; + + /* Otherwise, we allocate the device, and initialize the + * default values */ + dev = kcalloc(1, sizeof(*dev), GFP_KERNEL); + + if (NULL == dev) + return ERR_PTR(-ENOMEM); + + dev->speed = 0; + dev->duplex = -1; + dev->pause = dev->asym_pause = 0; + dev->link = 1; + + dev->autoneg = AUTONEG_ENABLE; + + dev->addr = addr; + dev->phy_id = phy_id; + dev->bus = bus; + + dev->state = PHY_DOWN; + + spin_lock_init(&dev->lock); + + return dev; +} + +/* phy_prepare_link: + * + * description: Tells the PHY infrastructure to handle the + * gory details on monitoring link status (whether through + * polling or an interrupt), and to call back to the + * connected device driver when the link status changes. + * If you want to monitor your own link state, don't call + * this function */ +void phy_prepare_link(struct phy_device *phydev, + void (*handler)(struct net_device *)) +{ + phydev->adjust_link = handler; +} + +#ifdef CONFIG_PHYCONTROL +/* phy_connect: + * + * description: Convenience function for connecting ethernet + * devices to PHY devices. The default behavior is for + * the PHY infrastructure to handle everything, and only notify + * the connected driver when the link status changes. If you + * don't want, or can't use the provided functionality, you may + * choose to call only the subset of functions which provide + * the desired functionality. + */ +struct phy_device * phy_connect(struct net_device *dev, const char *phy_id, + void (*handler)(struct net_device *), u32 flags) +{ + struct phy_device *phydev; + + phydev = phy_attach(dev, phy_id, flags); + + if (IS_ERR(phydev)) + return phydev; + + phy_prepare_link(phydev, handler); + + phy_start_machine(phydev, NULL); + + if (phydev->irq > 0) + phy_start_interrupts(phydev); + + return phydev; +} +EXPORT_SYMBOL(phy_connect); + +void phy_disconnect(struct phy_device *phydev) +{ + if (phydev->irq > 0) + phy_stop_interrupts(phydev); + + phy_stop_machine(phydev); + + phydev->adjust_link = NULL; + + phy_detach(phydev); +} +EXPORT_SYMBOL(phy_disconnect); + +#endif /* CONFIG_PHYCONTROL */ + +/* phy_attach: + * + * description: Called by drivers to attach to a particular PHY + * device. The phy_device is found, and properly hooked up + * to the phy_driver. If no driver is attached, then the + * genphy_driver is used. The phy_device is given a ptr to + * the attaching device, and given a callback for link status + * change. The phy_device is returned to the attaching + * driver. + */ +static int phy_compare_id(struct device *dev, void *data) +{ + return strcmp((char *)data, dev->bus_id) ? 0 : 1; +} + +struct phy_device *phy_attach(struct net_device *dev, + const char *phy_id, u32 flags) +{ + struct bus_type *bus = &mdio_bus_type; + struct phy_device *phydev; + struct device *d; + + /* Search the list of PHY devices on the mdio bus for the + * PHY with the requested name */ + d = bus_find_device(bus, NULL, (void *)phy_id, phy_compare_id); + + if (d) { + phydev = to_phy_device(d); + } else { + printk(KERN_ERR "%s not found\n", phy_id); + return ERR_PTR(-ENODEV); + } + + /* Assume that if there is no driver, that it doesn't + * exist, and we should use the genphy driver. */ + if (NULL == d->driver) { + int err; + down_write(&d->bus->subsys.rwsem); + d->driver = &genphy_driver.driver; + + err = d->driver->probe(d); + + if (err < 0) + return ERR_PTR(err); + + device_bind_driver(d); + up_write(&d->bus->subsys.rwsem); + } + + if (phydev->attached_dev) { + printk(KERN_ERR "%s: %s already attached\n", + dev->name, phy_id); + return ERR_PTR(-EBUSY); + } + + phydev->attached_dev = dev; + + phydev->dev_flags = flags; + + return phydev; +} +EXPORT_SYMBOL(phy_attach); + +void phy_detach(struct phy_device *phydev) +{ + phydev->attached_dev = NULL; + + /* If the device had no specific driver before (i.e. - it + * was using the generic driver), we unbind the device + * from the generic driver so that there's a chance a + * real driver could be loaded */ + if (phydev->dev.driver == &genphy_driver.driver) { + down_write(&phydev->dev.bus->subsys.rwsem); + device_release_driver(&phydev->dev); + up_write(&phydev->dev.bus->subsys.rwsem); + } +} +EXPORT_SYMBOL(phy_detach); + + +/* Generic PHY support and helper functions */ + +/* genphy_config_advert + * + * description: Writes MII_ADVERTISE with the appropriate values, + * after sanitizing the values to make sure we only advertise + * what is supported + */ +int genphy_config_advert(struct phy_device *phydev) +{ + u32 advertise; + int adv; + int err; + + /* Only allow advertising what + * this PHY supports */ + phydev->advertising &= phydev->supported; + advertise = phydev->advertising; + + /* Setup standard advertisement */ + adv = phy_read(phydev, MII_ADVERTISE); + + if (adv < 0) + return adv; + + adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + 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; + if (advertise & ADVERTISED_Pause) + adv |= ADVERTISE_PAUSE_CAP; + if (advertise & ADVERTISED_Asym_Pause) + adv |= ADVERTISE_PAUSE_ASYM; + + err = phy_write(phydev, MII_ADVERTISE, adv); + + if (err < 0) + return err; + + /* Configure gigabit if it's supported */ + if (phydev->supported & (SUPPORTED_1000baseT_Half | + SUPPORTED_1000baseT_Full)) { + adv = phy_read(phydev, MII_CTRL1000); + + if (adv < 0) + return adv; + + adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF); + if (advertise & SUPPORTED_1000baseT_Half) + adv |= ADVERTISE_1000HALF; + if (advertise & SUPPORTED_1000baseT_Full) + adv |= ADVERTISE_1000FULL; + err = phy_write(phydev, MII_CTRL1000, adv); + + if (err < 0) + return err; + } + + return adv; +} +EXPORT_SYMBOL(genphy_config_advert); + +/* genphy_setup_forced + * + * description: Configures MII_BMCR to force speed/duplex + * to the values in phydev. Assumes that the values are valid. + * Please see phy_sanitize_settings() */ +int genphy_setup_forced(struct phy_device *phydev) +{ + int ctl = BMCR_RESET; + + phydev->pause = phydev->asym_pause = 0; + + if (SPEED_1000 == phydev->speed) + ctl |= BMCR_SPEED1000; + else if (SPEED_100 == phydev->speed) + ctl |= BMCR_SPEED100; + + if (DUPLEX_FULL == phydev->duplex) + ctl |= BMCR_FULLDPLX; + + ctl = phy_write(phydev, MII_BMCR, ctl); + + if (ctl < 0) + return ctl; + + /* We just reset the device, so we'd better configure any + * settings the PHY requires to operate */ + if (phydev->drv->config_init) + ctl = phydev->drv->config_init(phydev); + + return ctl; +} + + +/* Enable and Restart Autonegotiation */ +int genphy_restart_aneg(struct phy_device *phydev) +{ + int ctl; + + ctl = phy_read(phydev, MII_BMCR); + + if (ctl < 0) + return ctl; + + ctl |= (BMCR_ANENABLE | BMCR_ANRESTART); + + /* Don't isolate the PHY if we're negotiating */ + ctl &= ~(BMCR_ISOLATE); + + ctl = phy_write(phydev, MII_BMCR, ctl); + + return ctl; +} + + +/* genphy_config_aneg + * + * description: If auto-negotiation is enabled, we configure the + * advertising, and then restart auto-negotiation. If it is not + * enabled, then we write the BMCR + */ +int genphy_config_aneg(struct phy_device *phydev) +{ + int err = 0; + + if (AUTONEG_ENABLE == phydev->autoneg) { + err = genphy_config_advert(phydev); + + if (err < 0) + return err; + + err = genphy_restart_aneg(phydev); + } else + err = genphy_setup_forced(phydev); + + return err; +} +EXPORT_SYMBOL(genphy_config_aneg); + +/* genphy_update_link + * + * description: Update the value in phydev->link to reflect the + * current link value. In order to do this, we need to read + * the status register twice, keeping the second value + */ +int genphy_update_link(struct phy_device *phydev) +{ + int status; + + /* Do a fake read */ + status = phy_read(phydev, MII_BMSR); + + if (status < 0) + return status; + + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BMSR); + + if (status < 0) + return status; + + if ((status & BMSR_LSTATUS) == 0) + phydev->link = 0; + else + phydev->link = 1; + + return 0; +} + +/* genphy_read_status + * + * description: Check the link, then figure out the current state + * by comparing what we advertise with what the link partner + * advertises. Start by checking the gigabit possibilities, + * then move on to 10/100. + */ +int genphy_read_status(struct phy_device *phydev) +{ + int adv; + int err; + int lpa; + int lpagb = 0; + + /* Update the link, but return if there + * was an error */ + err = genphy_update_link(phydev); + if (err) + return err; + + if (AUTONEG_ENABLE == phydev->autoneg) { + if (phydev->supported & (SUPPORTED_1000baseT_Half + | SUPPORTED_1000baseT_Full)) { + lpagb = phy_read(phydev, MII_STAT1000); + + if (lpagb < 0) + return lpagb; + + adv = phy_read(phydev, MII_CTRL1000); + + if (adv < 0) + return adv; + + lpagb &= adv << 2; + } + + lpa = phy_read(phydev, MII_LPA); + + if (lpa < 0) + return lpa; + + adv = phy_read(phydev, MII_ADVERTISE); + + if (adv < 0) + return adv; + + lpa &= adv; + + phydev->speed = SPEED_10; + phydev->duplex = DUPLEX_HALF; + phydev->pause = phydev->asym_pause = 0; + + if (lpagb & (LPA_1000FULL | LPA_1000HALF)) { + phydev->speed = SPEED_1000; + + if (lpagb & LPA_1000FULL) + phydev->duplex = DUPLEX_FULL; + } else if (lpa & (LPA_100FULL | LPA_100HALF)) { + phydev->speed = SPEED_100; + + if (lpa & LPA_100FULL) + phydev->duplex = DUPLEX_FULL; + } else + if (lpa & LPA_10FULL) + phydev->duplex = DUPLEX_FULL; + + if (phydev->duplex == DUPLEX_FULL){ + phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0; + phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0; + } + } else { + int bmcr = phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + + if (bmcr & BMCR_FULLDPLX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + + if (bmcr & BMCR_SPEED1000) + phydev->speed = SPEED_1000; + else if (bmcr & BMCR_SPEED100) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + + phydev->pause = phydev->asym_pause = 0; + } + + return 0; +} +EXPORT_SYMBOL(genphy_read_status); + +static int genphy_config_init(struct phy_device *phydev) +{ + u32 val; + u32 features; + + /* For now, I'll claim that the generic driver supports + * all possible port types */ + features = (SUPPORTED_TP | SUPPORTED_MII + | SUPPORTED_AUI | SUPPORTED_FIBRE | + SUPPORTED_BNC); + + /* Do we support autonegotiation? */ + val = phy_read(phydev, MII_BMSR); + + if (val < 0) + return val; + + if (val & BMSR_ANEGCAPABLE) + features |= SUPPORTED_Autoneg; + + if (val & BMSR_100FULL) + features |= SUPPORTED_100baseT_Full; + if (val & BMSR_100HALF) + features |= SUPPORTED_100baseT_Half; + if (val & BMSR_10FULL) + features |= SUPPORTED_10baseT_Full; + if (val & BMSR_10HALF) + features |= SUPPORTED_10baseT_Half; + + if (val & BMSR_ESTATEN) { + val = phy_read(phydev, MII_ESTATUS); + + if (val < 0) + return val; + + if (val & ESTATUS_1000_TFULL) + features |= SUPPORTED_1000baseT_Full; + if (val & ESTATUS_1000_THALF) + features |= SUPPORTED_1000baseT_Half; + } + + phydev->supported = features; + phydev->advertising = features; + + return 0; +} + + +/* phy_probe + * + * description: Take care of setting up the phy_device structure, + * set the state to READY (the driver's init function should + * set it to STARTING if needed). + */ +static int phy_probe(struct device *dev) +{ + struct phy_device *phydev; + struct phy_driver *phydrv; + struct device_driver *drv; + int err = 0; + + phydev = to_phy_device(dev); + + /* Make sure the driver is held. + * XXX -- Is this correct? */ + drv = get_driver(phydev->dev.driver); + phydrv = to_phy_driver(drv); + phydev->drv = phydrv; + + /* Disable the interrupt if the PHY doesn't support it */ + if (!(phydrv->flags & PHY_HAS_INTERRUPT)) + phydev->irq = PHY_POLL; + + spin_lock(&phydev->lock); + + /* Start out supporting everything. Eventually, + * a controller will attach, and may modify one + * or both of these values */ + phydev->supported = phydrv->features; + phydev->advertising = phydrv->features; + + /* Set the state to READY by default */ + phydev->state = PHY_READY; + + if (phydev->drv->probe) + err = phydev->drv->probe(phydev); + + spin_unlock(&phydev->lock); + + if (err < 0) + return err; + + if (phydev->drv->config_init) + err = phydev->drv->config_init(phydev); + + return err; +} + +static int phy_remove(struct device *dev) +{ + struct phy_device *phydev; + + phydev = to_phy_device(dev); + + spin_lock(&phydev->lock); + phydev->state = PHY_DOWN; + spin_unlock(&phydev->lock); + + if (phydev->drv->remove) + phydev->drv->remove(phydev); + + put_driver(dev->driver); + phydev->drv = NULL; + + return 0; +} + +int phy_driver_register(struct phy_driver *new_driver) +{ + int retval; + + memset(&new_driver->driver, 0, sizeof(new_driver->driver)); + new_driver->driver.name = new_driver->name; + new_driver->driver.bus = &mdio_bus_type; + new_driver->driver.probe = phy_probe; + new_driver->driver.remove = phy_remove; + + retval = driver_register(&new_driver->driver); + + if (retval) { + printk(KERN_ERR "%s: Error %d in registering driver\n", + new_driver->name, retval); + + return retval; + } + + pr_info("%s: Registered new driver\n", new_driver->name); + + return 0; +} +EXPORT_SYMBOL(phy_driver_register); + +void phy_driver_unregister(struct phy_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(phy_driver_unregister); + +static struct phy_driver genphy_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic PHY", + .config_init = genphy_config_init, + .features = 0, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .driver = {.owner = THIS_MODULE, }, +}; + +static int __init genphy_init(void) +{ + return phy_driver_register(&genphy_driver); + +} + +static void __exit genphy_exit(void) +{ + phy_driver_unregister(&genphy_driver); +} + +module_init(genphy_init); +module_exit(genphy_exit); diff --git a/drivers/net/phy/qsemi.c b/drivers/net/phy/qsemi.c new file mode 100644 index 000000000000..d461ba457631 --- /dev/null +++ b/drivers/net/phy/qsemi.c @@ -0,0 +1,143 @@ +/* + * drivers/net/phy/qsemi.c + * + * Driver for Quality Semiconductor PHYs + * + * Author: Andy Fleming + * + * Copyright (c) 2004 Freescale Semiconductor, 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 +#include +#include + +#include +#include +#include + +/* ------------------------------------------------------------------------- */ +/* The Quality Semiconductor QS6612 is used on the RPX CLLF */ + +/* register definitions */ + +#define MII_QS6612_MCR 17 /* Mode Control Register */ +#define MII_QS6612_FTR 27 /* Factory Test Register */ +#define MII_QS6612_MCO 28 /* Misc. Control Register */ +#define MII_QS6612_ISR 29 /* Interrupt Source Register */ +#define MII_QS6612_IMR 30 /* Interrupt Mask Register */ +#define MII_QS6612_IMR_INIT 0x003a +#define MII_QS6612_PCR 31 /* 100BaseTx PHY Control Reg. */ + +#define QS6612_PCR_AN_COMPLETE 0x1000 +#define QS6612_PCR_RLBEN 0x0200 +#define QS6612_PCR_DCREN 0x0100 +#define QS6612_PCR_4B5BEN 0x0040 +#define QS6612_PCR_TX_ISOLATE 0x0020 +#define QS6612_PCR_MLT3_DIS 0x0002 +#define QS6612_PCR_SCRM_DESCRM 0x0001 + +MODULE_DESCRIPTION("Quality Semiconductor PHY driver"); +MODULE_AUTHOR("Andy Fleming"); +MODULE_LICENSE("GPL"); + +/* Returns 0, unless there's a write error */ +static int qs6612_config_init(struct phy_device *phydev) +{ + /* The PHY powers up isolated on the RPX, + * so send a command to allow operation. + * XXX - My docs indicate this should be 0x0940 + * ...or something. The current value sets three + * reserved bits, bit 11, which specifies it should be + * set to one, bit 10, which specifies it should be set + * to 0, and bit 7, which doesn't specify. However, my + * docs are preliminary, and I will leave it like this + * until someone more knowledgable corrects me or it. + * -- Andy Fleming + */ + return phy_write(phydev, MII_QS6612_PCR, 0x0dc0); +} + +static int qs6612_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, MII_QS6612_ISR); + + if (err < 0) + return err; + + err = phy_read(phydev, MII_BMSR); + + if (err < 0) + return err; + + err = phy_read(phydev, MII_EXPANSION); + + if (err < 0) + return err; + + return 0; +} + +static int qs6612_config_intr(struct phy_device *phydev) +{ + int err; + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_QS6612_IMR, + MII_QS6612_IMR_INIT); + else + err = phy_write(phydev, MII_QS6612_IMR, 0); + + return err; + +} + +static struct phy_driver qs6612_driver = { + .phy_id = 0x00181440, + .name = "QS6612", + .phy_id_mask = 0xfffffff0, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = qs6612_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = qs6612_ack_interrupt, + .config_intr = qs6612_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init qs6612_init(void) +{ + return phy_driver_register(&qs6612_driver); +} + +static void __exit qs6612_exit(void) +{ + phy_driver_unregister(&qs6612_driver); +} + +module_init(qs6612_init); +module_exit(qs6612_exit); -- cgit v1.2.3 From 67c4f3fa25502ce7ed82fb0307e09cf36f1f81da Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Thu, 11 Aug 2005 02:07:25 -0400 Subject: Fix numerous minor problems with new phy subsystem. Includes fixes for problems noted by Adrian Bunk, Andrew Morton, and one other person lost in the annals of history (and email folders). --- drivers/net/phy/Kconfig | 12 +- drivers/net/phy/Makefile | 12 +- drivers/net/phy/mdio_bus.c | 4 +- drivers/net/phy/phy.c | 9 +- drivers/net/phy/phy.c.orig | 860 ------------------------------------------- drivers/net/phy/phy_device.c | 48 ++- 6 files changed, 53 insertions(+), 892 deletions(-) delete mode 100644 drivers/net/phy/phy.c.orig (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 8b5db2343cc3..c2f1bf1d02d2 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -5,7 +5,7 @@ menu "PHY device support" config PHYLIB - bool "PHY Device support and infrastructure" + tristate "PHY Device support and infrastructure" depends on NET_ETHERNET help Ethernet controllers are usually attached to PHY @@ -24,31 +24,31 @@ comment "MII PHY device drivers" depends on PHYLIB config MARVELL_PHY - bool "Drivers for Marvell PHYs" + tristate "Drivers for Marvell PHYs" depends on PHYLIB ---help--- Currently has a driver for the 88E1011S config DAVICOM_PHY - bool "Drivers for Davicom PHYs" + tristate "Drivers for Davicom PHYs" depends on PHYLIB ---help--- Currently supports dm9161e and dm9131 config QSEMI_PHY - bool "Drivers for Quality Semiconductor PHYs" + tristate "Drivers for Quality Semiconductor PHYs" depends on PHYLIB ---help--- Currently supports the qs6612 config LXT_PHY - bool "Drivers for the Intel LXT PHYs" + tristate "Drivers for the Intel LXT PHYs" depends on PHYLIB ---help--- Currently supports the lxt970, lxt971 config CICADA_PHY - bool "Drivers for the Cicada PHYs" + tristate "Drivers for the Cicada PHYs" depends on PHYLIB ---help--- Currently supports the cis8204 diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 1af05de6ced0..fb7cb385a659 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -1,9 +1,9 @@ # Makefile for Linux PHY drivers -obj-$(CONFIG_PHYLIB) += phy.o phy_device.o mdio_bus.o +libphy-objs := phy.o phy_device.o mdio_bus.o -obj-$(CONFIG_MARVELL_PHY) += marvell.o -obj-$(CONFIG_DAVICOM_PHY) += davicom.o -obj-$(CONFIG_CICADA_PHY) += cicada.o -obj-$(CONFIG_LXT_PHY) += lxt.o -obj-$(CONFIG_QSEMI_PHY) += qsemi.o +obj-$(CONFIG_MARVELL_PHY) += libphy.o marvell.o +obj-$(CONFIG_DAVICOM_PHY) += libphy.o davicom.o +obj-$(CONFIG_CICADA_PHY) += libphy.o cicada.o +obj-$(CONFIG_LXT_PHY) += libphy.o lxt.o +obj-$(CONFIG_QSEMI_PHY) += libphy.o qsemi.o diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index e75103ba6f86..5fbea6acfe80 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -165,9 +165,9 @@ struct bus_type mdio_bus_type = { .resume = mdio_bus_resume, }; -static int __init mdio_bus_init(void) +int __init mdio_bus_init(void) { return bus_register(&mdio_bus_type); } -subsys_initcall(mdio_bus_init); + diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index e2c6896b92d2..934065dd6371 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -39,7 +39,6 @@ #include #include -static void phy_change(void *data); static void phy_timer(unsigned long data); /* Convenience function to print out the current phy status @@ -464,7 +463,6 @@ void phy_stop_machine(struct phy_device *phydev) phydev->adjust_state = NULL; } -#ifdef CONFIG_PHYCONTROL /* phy_error: * * Moves the PHY to the HALTED state in response to a read @@ -479,6 +477,10 @@ void phy_error(struct phy_device *phydev) spin_unlock(&phydev->lock); } +#ifdef CONFIG_PHYCONTROL + +static void phy_change(void *data); + /* phy_interrupt * * description: When a PHY interrupt occurs, the handler disables @@ -672,6 +674,8 @@ void phy_start(struct phy_device *phydev) EXPORT_SYMBOL(phy_stop); EXPORT_SYMBOL(phy_start); +#endif /* CONFIG_PHYCONTROL */ + /* PHY timer which handles the state machine */ static void phy_timer(unsigned long data) { @@ -859,4 +863,3 @@ static void phy_timer(unsigned long data) mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); } -#endif /* CONFIG_PHYCONTROL */ diff --git a/drivers/net/phy/phy.c.orig b/drivers/net/phy/phy.c.orig deleted file mode 100644 index 6af17cec9ace..000000000000 --- a/drivers/net/phy/phy.c.orig +++ /dev/null @@ -1,860 +0,0 @@ -/* - * drivers/net/phy/phy.c - * - * Framework for configuring and reading PHY devices - * Based on code in sungem_phy.c and gianfar_phy.c - * - * Author: Andy Fleming - * - * Copyright (c) 2004 Freescale Semiconductor, 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 -#include -#include - -#include -#include -#include - -static void phy_change(void *data); -static void phy_timer(unsigned long data); - -/* Convenience function to print out the current phy status - */ -void phy_print_status(struct phy_device *phydev) -{ - pr_info("%s: Link is %s", phydev->dev.bus_id, - phydev->link ? "Up" : "Down"); - if (phydev->link) - printk(" - %d/%s", phydev->speed, - DUPLEX_FULL == phydev->duplex ? - "Full" : "Half"); - - printk("\n"); -} -EXPORT_SYMBOL(phy_print_status); - - -/* Convenience functions for reading/writing a given PHY - * register. They MUST NOT be called from interrupt context, - * because the bus read/write functions may wait for an interrupt - * to conclude the operation. */ -int phy_read(struct phy_device *phydev, u16 regnum) -{ - int retval; - struct mii_bus *bus = phydev->bus; - - spin_lock_bh(&bus->mdio_lock); - retval = bus->read(bus, phydev->addr, regnum); - spin_unlock_bh(&bus->mdio_lock); - - return retval; -} -EXPORT_SYMBOL(phy_read); - -int phy_write(struct phy_device *phydev, u16 regnum, u16 val) -{ - int err; - struct mii_bus *bus = phydev->bus; - - spin_lock_bh(&bus->mdio_lock); - err = bus->write(bus, phydev->addr, regnum, val); - spin_unlock_bh(&bus->mdio_lock); - - return err; -} -EXPORT_SYMBOL(phy_write); - - -int phy_clear_interrupt(struct phy_device *phydev) -{ - int err = 0; - - if (phydev->drv->ack_interrupt) - err = phydev->drv->ack_interrupt(phydev); - - return err; -} - - -int phy_config_interrupt(struct phy_device *phydev, u32 interrupts) -{ - int err = 0; - - phydev->interrupts = interrupts; - if (phydev->drv->config_intr) - err = phydev->drv->config_intr(phydev); - - return err; -} - - -/* phy_aneg_done - * - * description: Reads the status register and returns 0 either if - * auto-negotiation is incomplete, or if there was an error. - * Returns BMSR_ANEGCOMPLETE if auto-negotiation is done. - */ -static inline int phy_aneg_done(struct phy_device *phydev) -{ - int retval; - - retval = phy_read(phydev, MII_BMSR); - - return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); -} - -/* phy_start_aneg - * - * description: Calls the PHY driver's config_aneg, and then - * sets the PHY state to PHY_AN if auto-negotiation is enabled, - * and to PHY_FORCING if auto-negotiation is disabled. Unless - * the PHY is currently HALTED. - */ -int phy_start_aneg(struct phy_device *phydev) -{ - int err; - - spin_lock(&phydev->lock); - - if (AUTONEG_DISABLE == phydev->autoneg) - phy_sanitize_settings(phydev); - - err = phydev->drv->config_aneg(phydev); - - if (err < 0) - goto out_unlock; - - if (phydev->state != PHY_HALTED) { - if (AUTONEG_ENABLE == phydev->autoneg) { - phydev->state = PHY_AN; - phydev->link_timeout = PHY_AN_TIMEOUT; - } else { - phydev->state = PHY_FORCING; - phydev->link_timeout = PHY_FORCE_TIMEOUT; - } - } - -out_unlock: - spin_unlock(&phydev->lock); - return err; -} -EXPORT_SYMBOL(phy_start_aneg); - - -/* A structure for mapping a particular speed and duplex - * combination to a particular SUPPORTED and ADVERTISED value */ -struct phy_setting { - int speed; - int duplex; - u32 setting; -}; - -/* A mapping of all SUPPORTED settings to speed/duplex */ -static struct phy_setting settings[] = { - { - .speed = 10000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseT_Full, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_1000baseT_Full, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_HALF, - .setting = SUPPORTED_1000baseT_Half, - }, - { - .speed = SPEED_100, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_100baseT_Full, - }, - { - .speed = SPEED_100, - .duplex = DUPLEX_HALF, - .setting = SUPPORTED_100baseT_Half, - }, - { - .speed = SPEED_10, - .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10baseT_Full, - }, - { - .speed = SPEED_10, - .duplex = DUPLEX_HALF, - .setting = SUPPORTED_10baseT_Half, - }, -}; - -#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting)) - -/* phy_find_setting - * - * description: Searches the settings array for the setting which - * matches the desired speed and duplex, and returns the index - * of that setting. Returns the index of the last setting if - * none of the others match. - */ -static inline int phy_find_setting(int speed, int duplex) -{ - int idx = 0; - - while (idx < ARRAY_SIZE(settings) && - (settings[idx].speed != speed || - settings[idx].duplex != duplex)) - idx++; - - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; -} - -/* phy_find_valid - * idx: The first index in settings[] to search - * features: A mask of the valid settings - * - * description: Returns the index of the first valid setting less - * than or equal to the one pointed to by idx, as determined by - * the mask in features. Returns the index of the last setting - * if nothing else matches. - */ -static inline int phy_find_valid(int idx, u32 features) -{ - while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features)) - idx++; - - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; -} - -/* phy_sanitize_settings - * - * description: Make sure the PHY is set to supported speeds and - * duplexes. Drop down by one in this order: 1000/FULL, - * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF - */ -void phy_sanitize_settings(struct phy_device *phydev) -{ - u32 features = phydev->supported; - int idx; - - /* Sanitize settings based on PHY capabilities */ - if ((features & SUPPORTED_Autoneg) == 0) - phydev->autoneg = 0; - - idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex), - features); - - phydev->speed = settings[idx].speed; - phydev->duplex = settings[idx].duplex; -} -EXPORT_SYMBOL(phy_sanitize_settings); - -/* phy_force_reduction - * - * description: Reduces the speed/duplex settings by - * one notch. The order is so: - * 1000/FULL, 1000/HALF, 100/FULL, 100/HALF, - * 10/FULL, 10/HALF. The function bottoms out at 10/HALF. - */ -static void phy_force_reduction(struct phy_device *phydev) -{ - int idx; - - idx = phy_find_setting(phydev->speed, phydev->duplex); - - idx++; - - idx = phy_find_valid(idx, phydev->supported); - - phydev->speed = settings[idx].speed; - phydev->duplex = settings[idx].duplex; - - pr_info("Trying %d/%s\n", phydev->speed, - DUPLEX_FULL == phydev->duplex ? - "FULL" : "HALF"); -} - -/* phy_ethtool_sset: - * A generic ethtool sset function. Handles all the details - * - * A few notes about parameter checking: - * - We don't set port or transceiver, so we don't care what they - * were set to. - * - phy_start_aneg() will make sure forced settings are sane, and - * choose the next best ones from the ones selected, so we don't - * care if ethtool tries to give us bad values - */ -int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) -{ - if (cmd->phy_address != phydev->addr) - return -EINVAL; - - /* We make sure that we don't pass unsupported - * values in to the PHY */ - cmd->advertising &= phydev->supported; - - /* Verify the settings we care about. */ - if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE) - return -EINVAL; - - if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0) - return -EINVAL; - - if (cmd->autoneg == AUTONEG_DISABLE - && ((cmd->speed != SPEED_1000 - && cmd->speed != SPEED_100 - && cmd->speed != SPEED_10) - || (cmd->duplex != DUPLEX_HALF - && cmd->duplex != DUPLEX_FULL))) - return -EINVAL; - - phydev->autoneg = cmd->autoneg; - - phydev->speed = cmd->speed; - - phydev->advertising = cmd->advertising; - - if (AUTONEG_ENABLE == cmd->autoneg) - phydev->advertising |= ADVERTISED_Autoneg; - else - phydev->advertising &= ~ADVERTISED_Autoneg; - - phydev->duplex = cmd->duplex; - - /* Restart the PHY */ - phy_start_aneg(phydev); - - return 0; -} - -int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd) -{ - cmd->supported = phydev->supported; - - cmd->advertising = phydev->advertising; - - cmd->speed = phydev->speed; - cmd->duplex = phydev->duplex; - cmd->port = PORT_MII; - cmd->phy_address = phydev->addr; - cmd->transceiver = XCVR_EXTERNAL; - cmd->autoneg = phydev->autoneg; - - return 0; -} - - -/* Note that this function is currently incompatible with the - * PHYCONTROL layer. It changes registers without regard to - * current state. Use at own risk - */ -int phy_mii_ioctl(struct phy_device *phydev, - struct mii_ioctl_data *mii_data, int cmd) -{ - u16 val = mii_data->val_in; - - switch (cmd) { - case SIOCGMIIPHY: - mii_data->phy_id = phydev->addr; - break; - case SIOCGMIIREG: - mii_data->val_out = phy_read(phydev, mii_data->reg_num); - break; - - case SIOCSMIIREG: - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - - if (mii_data->phy_id == phydev->addr) { - switch(mii_data->reg_num) { - case MII_BMCR: - if (val & (BMCR_RESET|BMCR_ANENABLE)) - phydev->autoneg = AUTONEG_DISABLE; - else - phydev->autoneg = AUTONEG_ENABLE; - if ((!phydev->autoneg) && (val & BMCR_FULLDPLX)) - phydev->duplex = DUPLEX_FULL; - else - phydev->duplex = DUPLEX_HALF; - break; - case MII_ADVERTISE: - phydev->advertising = val; - break; - default: - /* do nothing */ - break; - } - } - - phy_write(phydev, mii_data->reg_num, val); - - if (mii_data->reg_num == MII_BMCR - && val & BMCR_RESET - && phydev->drv->config_init) - phydev->drv->config_init(phydev); - break; - } - - return 0; -} - -/* phy_start_machine: - * - * description: The PHY infrastructure can run a state machine - * which tracks whether the PHY is starting up, negotiating, - * etc. This function starts the timer which tracks the state - * of the PHY. If you want to be notified when the state - * changes, pass in the callback, otherwise, pass NULL. If you - * want to maintain your own state machine, do not call this - * function. */ -void phy_start_machine(struct phy_device *phydev, - void (*handler)(struct net_device *)) -{ - phydev->adjust_state = handler; - - init_timer(&phydev->phy_timer); - phydev->phy_timer.function = &phy_timer; - phydev->phy_timer.data = (unsigned long) phydev; - mod_timer(&phydev->phy_timer, jiffies + HZ); -} - -/* phy_stop_machine - * - * description: Stops the state machine timer, sets the state to - * UP (unless it wasn't up yet), and then frees the interrupt, - * if it is in use. This function must be called BEFORE - * phy_detach. - */ -void phy_stop_machine(struct phy_device *phydev) -{ - del_timer_sync(&phydev->phy_timer); - - spin_lock(&phydev->lock); - if (phydev->state > PHY_UP) - phydev->state = PHY_UP; - spin_unlock(&phydev->lock); - - if (phydev->irq != PHY_POLL) - phy_stop_interrupts(phydev); - - phydev->adjust_state = NULL; -} - -#ifdef CONFIG_PHYCONTROL -/* phy_error: - * - * Moves the PHY to the HALTED state in response to a read - * or write error, and tells the controller the link is down. - * Must not be called from interrupt context, or while the - * phydev->lock is held. - */ -void phy_error(struct phy_device *phydev) -{ - spin_lock(&phydev->lock); - phydev->state = PHY_HALTED; - spin_unlock(&phydev->lock); -} - -/* phy_interrupt - * - * description: When a PHY interrupt occurs, the handler disables - * interrupts, and schedules a work task to clear the interrupt. - */ -static irqreturn_t phy_interrupt(int irq, void *phy_dat, struct pt_regs *regs) -{ - struct phy_device *phydev = phy_dat; - - /* The MDIO bus is not allowed to be written in interrupt - * context, so we need to disable the irq here. A work - * queue will write the PHY to disable and clear the - * interrupt, and then reenable the irq line. */ - disable_irq_nosync(irq); - - schedule_work(&phydev->phy_queue); - - return IRQ_HANDLED; -} - -/* Enable the interrupts from the PHY side */ -int phy_enable_interrupts(struct phy_device *phydev) -{ - int err; - - err = phy_clear_interrupt(phydev); - - if (err < 0) - return err; - - err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); - - return err; -} - -/* Disable the PHY interrupts from the PHY side */ -int phy_disable_interrupts(struct phy_device *phydev) -{ - int err; - - /* Disable PHY interrupts */ - err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); - - if (err) - goto phy_err; - - /* Clear the interrupt */ - err = phy_clear_interrupt(phydev); - - if (err) - goto phy_err; - - return 0; - -phy_err: - phy_error(phydev); - - return err; -} - -/* phy_start_interrupts - * - * description: Request the interrupt for the given PHY. If - * this fails, then we set irq to PHY_POLL. - * Otherwise, we enable the interrupts in the PHY. - * Returns 0 on success. - * This should only be called with a valid IRQ number. - */ -int phy_start_interrupts(struct phy_device *phydev) -{ - int err = 0; - - INIT_WORK(&phydev->phy_queue, phy_change, phydev); - - if (request_irq(phydev->irq, phy_interrupt, - SA_SHIRQ, - "phy_interrupt", - phydev) < 0) { - printk(KERN_WARNING "%s: Can't get IRQ %d (PHY)\n", - phydev->bus->name, - phydev->irq); - phydev->irq = PHY_POLL; - return 0; - } - - err = phy_enable_interrupts(phydev); - - return err; -} -EXPORT_SYMBOL(phy_start_interrupts); - -int phy_stop_interrupts(struct phy_device *phydev) -{ - int err; - - err = phy_disable_interrupts(phydev); - - if (err) - phy_error(phydev); - - free_irq(phydev->irq, phydev); - - return err; -} -EXPORT_SYMBOL(phy_stop_interrupts); - - -/* Scheduled by the phy_interrupt/timer to handle PHY changes */ -static void phy_change(void *data) -{ - int err; - struct phy_device *phydev = data; - - err = phy_disable_interrupts(phydev); - - if (err) - goto phy_err; - - spin_lock(&phydev->lock); - if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) - phydev->state = PHY_CHANGELINK; - spin_unlock(&phydev->lock); - - enable_irq(phydev->irq); - - /* Reenable interrupts */ - err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); - - if (err) - goto irq_enable_err; - - return; - -irq_enable_err: - disable_irq(phydev->irq); -phy_err: - phy_error(phydev); -} - -/* Bring down the PHY link, and stop checking the status. */ -void phy_stop(struct phy_device *phydev) -{ - spin_lock(&phydev->lock); - - if (PHY_HALTED == phydev->state) - goto out_unlock; - - if (phydev->irq != PHY_POLL) { - /* Clear any pending interrupts */ - phy_clear_interrupt(phydev); - - /* Disable PHY Interrupts */ - phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); - } - - phydev->state = PHY_HALTED; - -out_unlock: - spin_unlock(&phydev->lock); -} - - -/* phy_start - * - * description: Indicates the attached device's readiness to - * handle PHY-related work. Used during startup to start the - * PHY, and after a call to phy_stop() to resume operation. - * Also used to indicate the MDIO bus has cleared an error - * condition. - */ -void phy_start(struct phy_device *phydev) -{ - spin_lock(&phydev->lock); - - switch (phydev->state) { - case PHY_STARTING: - phydev->state = PHY_PENDING; - break; - case PHY_READY: - phydev->state = PHY_UP; - break; - case PHY_HALTED: - phydev->state = PHY_RESUMING; - default: - break; - } - spin_unlock(&phydev->lock); -} -EXPORT_SYMBOL(phy_stop); -EXPORT_SYMBOL(phy_start); - -/* PHY timer which handles the state machine */ -static void phy_timer(unsigned long data) -{ - struct phy_device *phydev = (struct phy_device *)data; - int needs_aneg = 0; - int err = 0; - - spin_lock(&phydev->lock); - - if (phydev->adjust_state) - phydev->adjust_state(phydev->attached_dev); - - switch(phydev->state) { - case PHY_DOWN: - case PHY_STARTING: - case PHY_READY: - case PHY_PENDING: - break; - case PHY_UP: - needs_aneg = 1; - - phydev->link_timeout = PHY_AN_TIMEOUT; - - break; - case PHY_AN: - /* Check if negotiation is done. Break - * if there's an error */ - err = phy_aneg_done(phydev); - if (err < 0) - break; - - /* If auto-negotiation is done, we change to - * either RUNNING, or NOLINK */ - if (err > 0) { - err = phy_read_status(phydev); - - if (err) - break; - - if (phydev->link) { - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - } else { - phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); - } - - phydev->adjust_link(phydev->attached_dev); - - } else if (0 == phydev->link_timeout--) { - /* The counter expired, so either we - * switch to forced mode, or the - * magic_aneg bit exists, and we try aneg - * again */ - if (!(phydev->drv->flags & PHY_HAS_MAGICANEG)) { - int idx; - - /* We'll start from the - * fastest speed, and work - * our way down */ - idx = phy_find_valid(0, - phydev->supported); - - phydev->speed = settings[idx].speed; - phydev->duplex = settings[idx].duplex; - - phydev->autoneg = AUTONEG_DISABLE; - phydev->state = PHY_FORCING; - phydev->link_timeout = - PHY_FORCE_TIMEOUT; - - pr_info("Trying %d/%s\n", - phydev->speed, - DUPLEX_FULL == - phydev->duplex ? - "FULL" : "HALF"); - } - - needs_aneg = 1; - } - break; - case PHY_NOLINK: - err = phy_read_status(phydev); - - if (err) - break; - - if (phydev->link) { - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - } - break; - case PHY_FORCING: - err = phy_read_status(phydev); - - if (err) - break; - - if (phydev->link) { - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - } else { - if (0 == phydev->link_timeout--) { - phy_force_reduction(phydev); - needs_aneg = 1; - } - } - - phydev->adjust_link(phydev->attached_dev); - break; - case PHY_RUNNING: - /* Only register a CHANGE if we are - * polling */ - if (PHY_POLL == phydev->irq) - phydev->state = PHY_CHANGELINK; - break; - case PHY_CHANGELINK: - err = phy_read_status(phydev); - - if (err) - break; - - if (phydev->link) { - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - } else { - phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); - } - - phydev->adjust_link(phydev->attached_dev); - - if (PHY_POLL != phydev->irq) - err = phy_config_interrupt(phydev, - PHY_INTERRUPT_ENABLED); - break; - case PHY_HALTED: - if (phydev->link) { - phydev->link = 0; - netif_carrier_off(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - } - break; - case PHY_RESUMING: - - err = phy_clear_interrupt(phydev); - - if (err) - break; - - err = phy_config_interrupt(phydev, - PHY_INTERRUPT_ENABLED); - - if (err) - break; - - if (AUTONEG_ENABLE == phydev->autoneg) { - err = phy_aneg_done(phydev); - if (err < 0) - break; - - /* err > 0 if AN is done. - * Otherwise, it's 0, and we're - * still waiting for AN */ - if (err > 0) { - phydev->state = PHY_RUNNING; - } else { - phydev->state = PHY_AN; - phydev->link_timeout = PHY_AN_TIMEOUT; - } - } else - phydev->state = PHY_RUNNING; - break; - } - - spin_unlock(&phydev->lock); - - if (needs_aneg) - err = phy_start_aneg(phydev); - - if (err < 0) - phy_error(phydev); - - mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); -} - -#endif /* CONFIG_PHYCONTROL */ diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index f0595af4c837..c11138330fed 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -39,6 +39,19 @@ #include #include +static int genphy_config_init(struct phy_device *phydev); + +static struct phy_driver genphy_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic PHY", + .config_init = genphy_config_init, + .features = 0, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .driver = {.owner = THIS_MODULE, }, +}; + /* get_phy_device * * description: Reads the ID registers of the PHY at addr on the @@ -656,27 +669,32 @@ void phy_driver_unregister(struct phy_driver *drv) } EXPORT_SYMBOL(phy_driver_unregister); -static struct phy_driver genphy_driver = { - .phy_id = 0xffffffff, - .phy_id_mask = 0xffffffff, - .name = "Generic PHY", - .config_init = genphy_config_init, - .features = 0, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .driver = {.owner = THIS_MODULE, }, -}; -static int __init genphy_init(void) +static int __init phy_init(void) { - return phy_driver_register(&genphy_driver); + int rc; + extern int mdio_bus_init(void); + + rc = phy_driver_register(&genphy_driver); + if (rc) + goto out; + + rc = mdio_bus_init(); + if (rc) + goto out_unreg; + return 0; + +out_unreg: + phy_driver_unregister(&genphy_driver); +out: + return rc; } -static void __exit genphy_exit(void) +static void __exit phy_exit(void) { phy_driver_unregister(&genphy_driver); } -module_init(genphy_init); -module_exit(genphy_exit); +module_init(phy_init); +module_exit(phy_exit); -- cgit v1.2.3 From 2bf69b5fe90b3246ab50064c5a690a363e8c53e2 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Thu, 11 Aug 2005 02:47:54 -0400 Subject: phy subsystem: more cleanups - unexport symbols never used outside of home module - remove dead code - remove CONFIG_PHYCONTROL, make it unconditionally enabled --- drivers/net/phy/Kconfig | 8 -- drivers/net/phy/mdio_bus.c | 74 ---------------- drivers/net/phy/phy.c | 197 +++---------------------------------------- drivers/net/phy/phy_device.c | 130 +--------------------------- 4 files changed, 12 insertions(+), 397 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index c2f1bf1d02d2..6450bd71deb4 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -12,14 +12,6 @@ config PHYLIB devices. This option provides infrastructure for managing PHY devices. -config PHYCONTROL - bool "Support for automatically handling PHY state changes" - depends on PHYLIB - help - Adds code to perform all the work for keeping PHY link - state (speed/duplex/etc) up-to-date. Also handles - interrupts. - comment "MII PHY device drivers" depends on PHYLIB diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 5fbea6acfe80..d5a05be28818 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -38,80 +38,6 @@ #include #include -/* mdiobus_register - * - * description: Called by a bus driver to bring up all the PHYs - * on a given bus, and attach them to the bus - */ -int mdiobus_register(struct mii_bus *bus) -{ - int i; - int err = 0; - - spin_lock_init(&bus->mdio_lock); - - if (NULL == bus || NULL == bus->name || - NULL == bus->read || - NULL == bus->write) - return -EINVAL; - - if (bus->reset) - bus->reset(bus); - - for (i = 0; i < PHY_MAX_ADDR; i++) { - struct phy_device *phydev; - - phydev = get_phy_device(bus, i); - - if (IS_ERR(phydev)) - return PTR_ERR(phydev); - - /* There's a PHY at this address - * We need to set: - * 1) IRQ - * 2) bus_id - * 3) parent - * 4) bus - * 5) mii_bus - * And, we need to register it */ - if (phydev) { - phydev->irq = bus->irq[i]; - - phydev->dev.parent = bus->dev; - phydev->dev.bus = &mdio_bus_type; - sprintf(phydev->dev.bus_id, "phy%d:%d", bus->id, i); - - phydev->bus = bus; - - err = device_register(&phydev->dev); - - if (err) - printk(KERN_ERR "phy %d failed to register\n", - i); - } - - bus->phy_map[i] = phydev; - } - - pr_info("%s: probed\n", bus->name); - - return err; -} -EXPORT_SYMBOL(mdiobus_register); - -void mdiobus_unregister(struct mii_bus *bus) -{ - int i; - - for (i = 0; i < PHY_MAX_ADDR; i++) { - if (bus->phy_map[i]) { - device_unregister(&bus->phy_map[i]->dev); - kfree(bus->phy_map[i]); - } - } -} -EXPORT_SYMBOL(mdiobus_unregister); - /* mdio_bus_match * * description: Given a PHY device, and a PHY driver, return 1 if diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 934065dd6371..d3e43631b89b 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -40,21 +40,9 @@ #include static void phy_timer(unsigned long data); - -/* Convenience function to print out the current phy status - */ -void phy_print_status(struct phy_device *phydev) -{ - pr_info("%s: Link is %s", phydev->dev.bus_id, - phydev->link ? "Up" : "Down"); - if (phydev->link) - printk(" - %d/%s", phydev->speed, - DUPLEX_FULL == phydev->duplex ? - "Full" : "Half"); - - printk("\n"); -} -EXPORT_SYMBOL(phy_print_status); +static int phy_disable_interrupts(struct phy_device *phydev); +static void phy_sanitize_settings(struct phy_device *phydev); +static int phy_stop_interrupts(struct phy_device *phydev); /* Convenience functions for reading/writing a given PHY @@ -133,7 +121,7 @@ static inline int phy_aneg_done(struct phy_device *phydev) * and to PHY_FORCING if auto-negotiation is disabled. Unless * the PHY is currently HALTED. */ -int phy_start_aneg(struct phy_device *phydev) +static int phy_start_aneg(struct phy_device *phydev) { int err; @@ -161,8 +149,6 @@ out_unlock: spin_unlock(&phydev->lock); return err; } -EXPORT_SYMBOL(phy_start_aneg); - /* A structure for mapping a particular speed and duplex * combination to a particular SUPPORTED and ADVERTISED value */ @@ -255,7 +241,7 @@ static inline int phy_find_valid(int idx, u32 features) * duplexes. Drop down by one in this order: 1000/FULL, * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF */ -void phy_sanitize_settings(struct phy_device *phydev) +static void phy_sanitize_settings(struct phy_device *phydev) { u32 features = phydev->supported; int idx; @@ -270,7 +256,6 @@ void phy_sanitize_settings(struct phy_device *phydev) phydev->speed = settings[idx].speed; phydev->duplex = settings[idx].duplex; } -EXPORT_SYMBOL(phy_sanitize_settings); /* phy_force_reduction * @@ -477,48 +462,22 @@ void phy_error(struct phy_device *phydev) spin_unlock(&phydev->lock); } -#ifdef CONFIG_PHYCONTROL - -static void phy_change(void *data); - -/* phy_interrupt - * - * description: When a PHY interrupt occurs, the handler disables - * interrupts, and schedules a work task to clear the interrupt. - */ -static irqreturn_t phy_interrupt(int irq, void *phy_dat, struct pt_regs *regs) -{ - struct phy_device *phydev = phy_dat; - - /* The MDIO bus is not allowed to be written in interrupt - * context, so we need to disable the irq here. A work - * queue will write the PHY to disable and clear the - * interrupt, and then reenable the irq line. */ - disable_irq_nosync(irq); - - schedule_work(&phydev->phy_queue); - - return IRQ_HANDLED; -} - -/* Enable the interrupts from the PHY side */ -int phy_enable_interrupts(struct phy_device *phydev) +static int phy_stop_interrupts(struct phy_device *phydev) { int err; - err = phy_clear_interrupt(phydev); + err = phy_disable_interrupts(phydev); - if (err < 0) - return err; + if (err) + phy_error(phydev); - err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + free_irq(phydev->irq, phydev); return err; } -EXPORT_SYMBOL(phy_enable_interrupts); /* Disable the PHY interrupts from the PHY side */ -int phy_disable_interrupts(struct phy_device *phydev) +static int phy_disable_interrupts(struct phy_device *phydev) { int err; @@ -541,140 +500,6 @@ phy_err: return err; } -EXPORT_SYMBOL(phy_disable_interrupts); - -/* phy_start_interrupts - * - * description: Request the interrupt for the given PHY. If - * this fails, then we set irq to PHY_POLL. - * Otherwise, we enable the interrupts in the PHY. - * Returns 0 on success. - * This should only be called with a valid IRQ number. - */ -int phy_start_interrupts(struct phy_device *phydev) -{ - int err = 0; - - INIT_WORK(&phydev->phy_queue, phy_change, phydev); - - if (request_irq(phydev->irq, phy_interrupt, - SA_SHIRQ, - "phy_interrupt", - phydev) < 0) { - printk(KERN_WARNING "%s: Can't get IRQ %d (PHY)\n", - phydev->bus->name, - phydev->irq); - phydev->irq = PHY_POLL; - return 0; - } - - err = phy_enable_interrupts(phydev); - - return err; -} -EXPORT_SYMBOL(phy_start_interrupts); - -int phy_stop_interrupts(struct phy_device *phydev) -{ - int err; - - err = phy_disable_interrupts(phydev); - - if (err) - phy_error(phydev); - - free_irq(phydev->irq, phydev); - - return err; -} -EXPORT_SYMBOL(phy_stop_interrupts); - - -/* Scheduled by the phy_interrupt/timer to handle PHY changes */ -static void phy_change(void *data) -{ - int err; - struct phy_device *phydev = data; - - err = phy_disable_interrupts(phydev); - - if (err) - goto phy_err; - - spin_lock(&phydev->lock); - if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) - phydev->state = PHY_CHANGELINK; - spin_unlock(&phydev->lock); - - enable_irq(phydev->irq); - - /* Reenable interrupts */ - err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); - - if (err) - goto irq_enable_err; - - return; - -irq_enable_err: - disable_irq(phydev->irq); -phy_err: - phy_error(phydev); -} - -/* Bring down the PHY link, and stop checking the status. */ -void phy_stop(struct phy_device *phydev) -{ - spin_lock(&phydev->lock); - - if (PHY_HALTED == phydev->state) - goto out_unlock; - - if (phydev->irq != PHY_POLL) { - /* Clear any pending interrupts */ - phy_clear_interrupt(phydev); - - /* Disable PHY Interrupts */ - phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); - } - - phydev->state = PHY_HALTED; - -out_unlock: - spin_unlock(&phydev->lock); -} - - -/* phy_start - * - * description: Indicates the attached device's readiness to - * handle PHY-related work. Used during startup to start the - * PHY, and after a call to phy_stop() to resume operation. - * Also used to indicate the MDIO bus has cleared an error - * condition. - */ -void phy_start(struct phy_device *phydev) -{ - spin_lock(&phydev->lock); - - switch (phydev->state) { - case PHY_STARTING: - phydev->state = PHY_PENDING; - break; - case PHY_READY: - phydev->state = PHY_UP; - break; - case PHY_HALTED: - phydev->state = PHY_RESUMING; - default: - break; - } - spin_unlock(&phydev->lock); -} -EXPORT_SYMBOL(phy_stop); -EXPORT_SYMBOL(phy_start); - -#endif /* CONFIG_PHYCONTROL */ /* PHY timer which handles the state machine */ static void phy_timer(unsigned long data) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index c11138330fed..c44d54f6310a 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -124,133 +124,6 @@ void phy_prepare_link(struct phy_device *phydev, phydev->adjust_link = handler; } -#ifdef CONFIG_PHYCONTROL -/* phy_connect: - * - * description: Convenience function for connecting ethernet - * devices to PHY devices. The default behavior is for - * the PHY infrastructure to handle everything, and only notify - * the connected driver when the link status changes. If you - * don't want, or can't use the provided functionality, you may - * choose to call only the subset of functions which provide - * the desired functionality. - */ -struct phy_device * phy_connect(struct net_device *dev, const char *phy_id, - void (*handler)(struct net_device *), u32 flags) -{ - struct phy_device *phydev; - - phydev = phy_attach(dev, phy_id, flags); - - if (IS_ERR(phydev)) - return phydev; - - phy_prepare_link(phydev, handler); - - phy_start_machine(phydev, NULL); - - if (phydev->irq > 0) - phy_start_interrupts(phydev); - - return phydev; -} -EXPORT_SYMBOL(phy_connect); - -void phy_disconnect(struct phy_device *phydev) -{ - if (phydev->irq > 0) - phy_stop_interrupts(phydev); - - phy_stop_machine(phydev); - - phydev->adjust_link = NULL; - - phy_detach(phydev); -} -EXPORT_SYMBOL(phy_disconnect); - -#endif /* CONFIG_PHYCONTROL */ - -/* phy_attach: - * - * description: Called by drivers to attach to a particular PHY - * device. The phy_device is found, and properly hooked up - * to the phy_driver. If no driver is attached, then the - * genphy_driver is used. The phy_device is given a ptr to - * the attaching device, and given a callback for link status - * change. The phy_device is returned to the attaching - * driver. - */ -static int phy_compare_id(struct device *dev, void *data) -{ - return strcmp((char *)data, dev->bus_id) ? 0 : 1; -} - -struct phy_device *phy_attach(struct net_device *dev, - const char *phy_id, u32 flags) -{ - struct bus_type *bus = &mdio_bus_type; - struct phy_device *phydev; - struct device *d; - - /* Search the list of PHY devices on the mdio bus for the - * PHY with the requested name */ - d = bus_find_device(bus, NULL, (void *)phy_id, phy_compare_id); - - if (d) { - phydev = to_phy_device(d); - } else { - printk(KERN_ERR "%s not found\n", phy_id); - return ERR_PTR(-ENODEV); - } - - /* Assume that if there is no driver, that it doesn't - * exist, and we should use the genphy driver. */ - if (NULL == d->driver) { - int err; - down_write(&d->bus->subsys.rwsem); - d->driver = &genphy_driver.driver; - - err = d->driver->probe(d); - - if (err < 0) - return ERR_PTR(err); - - device_bind_driver(d); - up_write(&d->bus->subsys.rwsem); - } - - if (phydev->attached_dev) { - printk(KERN_ERR "%s: %s already attached\n", - dev->name, phy_id); - return ERR_PTR(-EBUSY); - } - - phydev->attached_dev = dev; - - phydev->dev_flags = flags; - - return phydev; -} -EXPORT_SYMBOL(phy_attach); - -void phy_detach(struct phy_device *phydev) -{ - phydev->attached_dev = NULL; - - /* If the device had no specific driver before (i.e. - it - * was using the generic driver), we unbind the device - * from the generic driver so that there's a chance a - * real driver could be loaded */ - if (phydev->dev.driver == &genphy_driver.driver) { - down_write(&phydev->dev.bus->subsys.rwsem); - device_release_driver(&phydev->dev); - up_write(&phydev->dev.bus->subsys.rwsem); - } -} -EXPORT_SYMBOL(phy_detach); - - /* Generic PHY support and helper functions */ /* genphy_config_advert @@ -259,7 +132,7 @@ EXPORT_SYMBOL(phy_detach); * after sanitizing the values to make sure we only advertise * what is supported */ -int genphy_config_advert(struct phy_device *phydev) +static int genphy_config_advert(struct phy_device *phydev) { u32 advertise; int adv; @@ -317,7 +190,6 @@ int genphy_config_advert(struct phy_device *phydev) return adv; } -EXPORT_SYMBOL(genphy_config_advert); /* genphy_setup_forced * -- cgit v1.2.3 From e13934563db047043ccead26412f552375cea90c Mon Sep 17 00:00:00 2001 From: Andy Fleming Date: Wed, 24 Aug 2005 18:46:21 -0500 Subject: [PATCH] PHY Layer fixup This patch adds back the code that was taken out, thus re-enabling: * The PHY Layer to initialize without crashing * Drivers to actually connect to PHYs * The entire PHY Control Layer This patch is used by the gianfar driver, and other drivers which are in development. Signed-off-by: Andy Fleming Signed-off-by: Jeff Garzik --- drivers/net/phy/Kconfig | 8 ++ drivers/net/phy/Makefile | 11 +- drivers/net/phy/mdio_bus.c | 79 ++++++++++- drivers/net/phy/phy.c | 325 +++++++++++++++++++++++++++++++++---------- drivers/net/phy/phy_device.c | 172 +++++++++++++++++++---- 5 files changed, 493 insertions(+), 102 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 6450bd71deb4..6a2fe3583478 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -12,6 +12,14 @@ config PHYLIB devices. This option provides infrastructure for managing PHY devices. +config PHYCONTROL + bool " Support for automatically handling PHY state changes" + depends on PHYLIB + help + Adds code to perform all the work for keeping PHY link + state (speed/duplex/etc) up-to-date. Also handles + interrupts. + comment "MII PHY device drivers" depends on PHYLIB diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index fb7cb385a659..e4116a5fbb4c 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -2,8 +2,9 @@ libphy-objs := phy.o phy_device.o mdio_bus.o -obj-$(CONFIG_MARVELL_PHY) += libphy.o marvell.o -obj-$(CONFIG_DAVICOM_PHY) += libphy.o davicom.o -obj-$(CONFIG_CICADA_PHY) += libphy.o cicada.o -obj-$(CONFIG_LXT_PHY) += libphy.o lxt.o -obj-$(CONFIG_QSEMI_PHY) += libphy.o qsemi.o +obj-$(CONFIG_PHYLIB) += libphy.o +obj-$(CONFIG_MARVELL_PHY) += marvell.o +obj-$(CONFIG_DAVICOM_PHY) += davicom.o +obj-$(CONFIG_CICADA_PHY) += cicada.o +obj-$(CONFIG_LXT_PHY) += lxt.o +obj-$(CONFIG_QSEMI_PHY) += qsemi.o diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index d5a05be28818..41f62c0c5fcb 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -38,6 +38,80 @@ #include #include +/* mdiobus_register + * + * description: Called by a bus driver to bring up all the PHYs + * on a given bus, and attach them to the bus + */ +int mdiobus_register(struct mii_bus *bus) +{ + int i; + int err = 0; + + spin_lock_init(&bus->mdio_lock); + + if (NULL == bus || NULL == bus->name || + NULL == bus->read || + NULL == bus->write) + return -EINVAL; + + if (bus->reset) + bus->reset(bus); + + for (i = 0; i < PHY_MAX_ADDR; i++) { + struct phy_device *phydev; + + phydev = get_phy_device(bus, i); + + if (IS_ERR(phydev)) + return PTR_ERR(phydev); + + /* There's a PHY at this address + * We need to set: + * 1) IRQ + * 2) bus_id + * 3) parent + * 4) bus + * 5) mii_bus + * And, we need to register it */ + if (phydev) { + phydev->irq = bus->irq[i]; + + phydev->dev.parent = bus->dev; + phydev->dev.bus = &mdio_bus_type; + sprintf(phydev->dev.bus_id, "phy%d:%d", bus->id, i); + + phydev->bus = bus; + + err = device_register(&phydev->dev); + + if (err) + printk(KERN_ERR "phy %d failed to register\n", + i); + } + + bus->phy_map[i] = phydev; + } + + pr_info("%s: probed\n", bus->name); + + return err; +} +EXPORT_SYMBOL(mdiobus_register); + +void mdiobus_unregister(struct mii_bus *bus) +{ + int i; + + for (i = 0; i < PHY_MAX_ADDR; i++) { + if (bus->phy_map[i]) { + device_unregister(&bus->phy_map[i]->dev); + kfree(bus->phy_map[i]); + } + } +} +EXPORT_SYMBOL(mdiobus_unregister); + /* mdio_bus_match * * description: Given a PHY device, and a PHY driver, return 1 if @@ -96,4 +170,7 @@ int __init mdio_bus_init(void) return bus_register(&mdio_bus_type); } - +void __exit mdio_bus_exit(void) +{ + bus_unregister(&mdio_bus_type); +} diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d3e43631b89b..d9e11f93bf3a 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -39,10 +39,20 @@ #include #include -static void phy_timer(unsigned long data); -static int phy_disable_interrupts(struct phy_device *phydev); -static void phy_sanitize_settings(struct phy_device *phydev); -static int phy_stop_interrupts(struct phy_device *phydev); +/* Convenience function to print out the current phy status + */ +void phy_print_status(struct phy_device *phydev) +{ + pr_info("%s: Link is %s", phydev->dev.bus_id, + phydev->link ? "Up" : "Down"); + if (phydev->link) + printk(" - %d/%s", phydev->speed, + DUPLEX_FULL == phydev->duplex ? + "Full" : "Half"); + + printk("\n"); +} +EXPORT_SYMBOL(phy_print_status); /* Convenience functions for reading/writing a given PHY @@ -114,42 +124,6 @@ static inline int phy_aneg_done(struct phy_device *phydev) return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); } -/* phy_start_aneg - * - * description: Calls the PHY driver's config_aneg, and then - * sets the PHY state to PHY_AN if auto-negotiation is enabled, - * and to PHY_FORCING if auto-negotiation is disabled. Unless - * the PHY is currently HALTED. - */ -static int phy_start_aneg(struct phy_device *phydev) -{ - int err; - - spin_lock(&phydev->lock); - - if (AUTONEG_DISABLE == phydev->autoneg) - phy_sanitize_settings(phydev); - - err = phydev->drv->config_aneg(phydev); - - if (err < 0) - goto out_unlock; - - if (phydev->state != PHY_HALTED) { - if (AUTONEG_ENABLE == phydev->autoneg) { - phydev->state = PHY_AN; - phydev->link_timeout = PHY_AN_TIMEOUT; - } else { - phydev->state = PHY_FORCING; - phydev->link_timeout = PHY_FORCE_TIMEOUT; - } - } - -out_unlock: - spin_unlock(&phydev->lock); - return err; -} - /* A structure for mapping a particular speed and duplex * combination to a particular SUPPORTED and ADVERTISED value */ struct phy_setting { @@ -241,7 +215,7 @@ static inline int phy_find_valid(int idx, u32 features) * duplexes. Drop down by one in this order: 1000/FULL, * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF */ -static void phy_sanitize_settings(struct phy_device *phydev) +void phy_sanitize_settings(struct phy_device *phydev) { u32 features = phydev->supported; int idx; @@ -256,31 +230,7 @@ static void phy_sanitize_settings(struct phy_device *phydev) phydev->speed = settings[idx].speed; phydev->duplex = settings[idx].duplex; } - -/* phy_force_reduction - * - * description: Reduces the speed/duplex settings by - * one notch. The order is so: - * 1000/FULL, 1000/HALF, 100/FULL, 100/HALF, - * 10/FULL, 10/HALF. The function bottoms out at 10/HALF. - */ -static void phy_force_reduction(struct phy_device *phydev) -{ - int idx; - - idx = phy_find_setting(phydev->speed, phydev->duplex); - - idx++; - - idx = phy_find_valid(idx, phydev->supported); - - phydev->speed = settings[idx].speed; - phydev->duplex = settings[idx].duplex; - - pr_info("Trying %d/%s\n", phydev->speed, - DUPLEX_FULL == phydev->duplex ? - "FULL" : "HALF"); -} +EXPORT_SYMBOL(phy_sanitize_settings); /* phy_ethtool_sset: * A generic ethtool sset function. Handles all the details @@ -291,6 +241,11 @@ static void phy_force_reduction(struct phy_device *phydev) * - phy_start_aneg() will make sure forced settings are sane, and * choose the next best ones from the ones selected, so we don't * care if ethtool tries to give us bad values + * + * A note about the PHYCONTROL Layer. If you turn off + * CONFIG_PHYCONTROL, you will need to read the PHY status + * registers after this function completes, and update your + * controller manually. */ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) { @@ -406,6 +361,51 @@ int phy_mii_ioctl(struct phy_device *phydev, return 0; } +/* phy_start_aneg + * + * description: Sanitizes the settings (if we're not + * autonegotiating them), and then calls the driver's + * config_aneg function. If the PHYCONTROL Layer is operating, + * we change the state to reflect the beginning of + * Auto-negotiation or forcing. + */ +int phy_start_aneg(struct phy_device *phydev) +{ + int err; + + spin_lock(&phydev->lock); + + if (AUTONEG_DISABLE == phydev->autoneg) + phy_sanitize_settings(phydev); + + err = phydev->drv->config_aneg(phydev); + +#ifdef CONFIG_PHYCONTROL + if (err < 0) + goto out_unlock; + + if (phydev->state != PHY_HALTED) { + if (AUTONEG_ENABLE == phydev->autoneg) { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; + } else { + phydev->state = PHY_FORCING; + phydev->link_timeout = PHY_FORCE_TIMEOUT; + } + } + +out_unlock: +#endif + spin_unlock(&phydev->lock); + return err; +} +EXPORT_SYMBOL(phy_start_aneg); + + +#ifdef CONFIG_PHYCONTROL +static void phy_change(void *data); +static void phy_timer(unsigned long data); + /* phy_start_machine: * * description: The PHY infrastructure can run a state machine @@ -448,6 +448,32 @@ void phy_stop_machine(struct phy_device *phydev) phydev->adjust_state = NULL; } +/* phy_force_reduction + * + * description: Reduces the speed/duplex settings by + * one notch. The order is so: + * 1000/FULL, 1000/HALF, 100/FULL, 100/HALF, + * 10/FULL, 10/HALF. The function bottoms out at 10/HALF. + */ +static void phy_force_reduction(struct phy_device *phydev) +{ + int idx; + + idx = phy_find_setting(phydev->speed, phydev->duplex); + + idx++; + + idx = phy_find_valid(idx, phydev->supported); + + phydev->speed = settings[idx].speed; + phydev->duplex = settings[idx].duplex; + + pr_info("Trying %d/%s\n", phydev->speed, + DUPLEX_FULL == phydev->duplex ? + "FULL" : "HALF"); +} + + /* phy_error: * * Moves the PHY to the HALTED state in response to a read @@ -462,22 +488,44 @@ void phy_error(struct phy_device *phydev) spin_unlock(&phydev->lock); } -static int phy_stop_interrupts(struct phy_device *phydev) +/* phy_interrupt + * + * description: When a PHY interrupt occurs, the handler disables + * interrupts, and schedules a work task to clear the interrupt. + */ +static irqreturn_t phy_interrupt(int irq, void *phy_dat, struct pt_regs *regs) +{ + struct phy_device *phydev = phy_dat; + + /* The MDIO bus is not allowed to be written in interrupt + * context, so we need to disable the irq here. A work + * queue will write the PHY to disable and clear the + * interrupt, and then reenable the irq line. */ + disable_irq_nosync(irq); + + schedule_work(&phydev->phy_queue); + + return IRQ_HANDLED; +} + +/* Enable the interrupts from the PHY side */ +int phy_enable_interrupts(struct phy_device *phydev) { int err; - err = phy_disable_interrupts(phydev); + err = phy_clear_interrupt(phydev); - if (err) - phy_error(phydev); + if (err < 0) + return err; - free_irq(phydev->irq, phydev); + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); return err; } +EXPORT_SYMBOL(phy_enable_interrupts); /* Disable the PHY interrupts from the PHY side */ -static int phy_disable_interrupts(struct phy_device *phydev) +int phy_disable_interrupts(struct phy_device *phydev) { int err; @@ -500,6 +548,138 @@ phy_err: return err; } +EXPORT_SYMBOL(phy_disable_interrupts); + +/* phy_start_interrupts + * + * description: Request the interrupt for the given PHY. If + * this fails, then we set irq to PHY_POLL. + * Otherwise, we enable the interrupts in the PHY. + * Returns 0 on success. + * This should only be called with a valid IRQ number. + */ +int phy_start_interrupts(struct phy_device *phydev) +{ + int err = 0; + + INIT_WORK(&phydev->phy_queue, phy_change, phydev); + + if (request_irq(phydev->irq, phy_interrupt, + SA_SHIRQ, + "phy_interrupt", + phydev) < 0) { + printk(KERN_WARNING "%s: Can't get IRQ %d (PHY)\n", + phydev->bus->name, + phydev->irq); + phydev->irq = PHY_POLL; + return 0; + } + + err = phy_enable_interrupts(phydev); + + return err; +} +EXPORT_SYMBOL(phy_start_interrupts); + +int phy_stop_interrupts(struct phy_device *phydev) +{ + int err; + + err = phy_disable_interrupts(phydev); + + if (err) + phy_error(phydev); + + free_irq(phydev->irq, phydev); + + return err; +} +EXPORT_SYMBOL(phy_stop_interrupts); + + +/* Scheduled by the phy_interrupt/timer to handle PHY changes */ +static void phy_change(void *data) +{ + int err; + struct phy_device *phydev = data; + + err = phy_disable_interrupts(phydev); + + if (err) + goto phy_err; + + spin_lock(&phydev->lock); + if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) + phydev->state = PHY_CHANGELINK; + spin_unlock(&phydev->lock); + + enable_irq(phydev->irq); + + /* Reenable interrupts */ + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + + if (err) + goto irq_enable_err; + + return; + +irq_enable_err: + disable_irq(phydev->irq); +phy_err: + phy_error(phydev); +} + +/* Bring down the PHY link, and stop checking the status. */ +void phy_stop(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + + if (PHY_HALTED == phydev->state) + goto out_unlock; + + if (phydev->irq != PHY_POLL) { + /* Clear any pending interrupts */ + phy_clear_interrupt(phydev); + + /* Disable PHY Interrupts */ + phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + } + + phydev->state = PHY_HALTED; + +out_unlock: + spin_unlock(&phydev->lock); +} + + +/* phy_start + * + * description: Indicates the attached device's readiness to + * handle PHY-related work. Used during startup to start the + * PHY, and after a call to phy_stop() to resume operation. + * Also used to indicate the MDIO bus has cleared an error + * condition. + */ +void phy_start(struct phy_device *phydev) +{ + spin_lock(&phydev->lock); + + switch (phydev->state) { + case PHY_STARTING: + phydev->state = PHY_PENDING; + break; + case PHY_READY: + phydev->state = PHY_UP; + break; + case PHY_HALTED: + phydev->state = PHY_RESUMING; + default: + break; + } + spin_unlock(&phydev->lock); +} +EXPORT_SYMBOL(phy_stop); +EXPORT_SYMBOL(phy_start); /* PHY timer which handles the state machine */ static void phy_timer(unsigned long data) @@ -688,3 +868,4 @@ static void phy_timer(unsigned long data) mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); } +#endif /* CONFIG_PHYCONTROL */ diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index c44d54f6310a..33f7bdb5857c 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -39,18 +39,9 @@ #include #include -static int genphy_config_init(struct phy_device *phydev); - -static struct phy_driver genphy_driver = { - .phy_id = 0xffffffff, - .phy_id_mask = 0xffffffff, - .name = "Generic PHY", - .config_init = genphy_config_init, - .features = 0, - .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, - .driver = {.owner = THIS_MODULE, }, -}; +static struct phy_driver genphy_driver; +extern int mdio_bus_init(void); +extern void mdio_bus_exit(void); /* get_phy_device * @@ -110,6 +101,7 @@ struct phy_device * get_phy_device(struct mii_bus *bus, int addr) return dev; } +#ifdef CONFIG_PHYCONTROL /* phy_prepare_link: * * description: Tells the PHY infrastructure to handle the @@ -124,6 +116,132 @@ void phy_prepare_link(struct phy_device *phydev, phydev->adjust_link = handler; } +/* phy_connect: + * + * description: Convenience function for connecting ethernet + * devices to PHY devices. The default behavior is for + * the PHY infrastructure to handle everything, and only notify + * the connected driver when the link status changes. If you + * don't want, or can't use the provided functionality, you may + * choose to call only the subset of functions which provide + * the desired functionality. + */ +struct phy_device * phy_connect(struct net_device *dev, const char *phy_id, + void (*handler)(struct net_device *), u32 flags) +{ + struct phy_device *phydev; + + phydev = phy_attach(dev, phy_id, flags); + + if (IS_ERR(phydev)) + return phydev; + + phy_prepare_link(phydev, handler); + + phy_start_machine(phydev, NULL); + + if (phydev->irq > 0) + phy_start_interrupts(phydev); + + return phydev; +} +EXPORT_SYMBOL(phy_connect); + +void phy_disconnect(struct phy_device *phydev) +{ + if (phydev->irq > 0) + phy_stop_interrupts(phydev); + + phy_stop_machine(phydev); + + phydev->adjust_link = NULL; + + phy_detach(phydev); +} +EXPORT_SYMBOL(phy_disconnect); + +#endif /* CONFIG_PHYCONTROL */ + +/* phy_attach: + * + * description: Called by drivers to attach to a particular PHY + * device. The phy_device is found, and properly hooked up + * to the phy_driver. If no driver is attached, then the + * genphy_driver is used. The phy_device is given a ptr to + * the attaching device, and given a callback for link status + * change. The phy_device is returned to the attaching + * driver. + */ +static int phy_compare_id(struct device *dev, void *data) +{ + return strcmp((char *)data, dev->bus_id) ? 0 : 1; +} + +struct phy_device *phy_attach(struct net_device *dev, + const char *phy_id, u32 flags) +{ + struct bus_type *bus = &mdio_bus_type; + struct phy_device *phydev; + struct device *d; + + /* Search the list of PHY devices on the mdio bus for the + * PHY with the requested name */ + d = bus_find_device(bus, NULL, (void *)phy_id, phy_compare_id); + + if (d) { + phydev = to_phy_device(d); + } else { + printk(KERN_ERR "%s not found\n", phy_id); + return ERR_PTR(-ENODEV); + } + + /* Assume that if there is no driver, that it doesn't + * exist, and we should use the genphy driver. */ + if (NULL == d->driver) { + int err; + down_write(&d->bus->subsys.rwsem); + d->driver = &genphy_driver.driver; + + err = d->driver->probe(d); + + if (err < 0) + return ERR_PTR(err); + + device_bind_driver(d); + up_write(&d->bus->subsys.rwsem); + } + + if (phydev->attached_dev) { + printk(KERN_ERR "%s: %s already attached\n", + dev->name, phy_id); + return ERR_PTR(-EBUSY); + } + + phydev->attached_dev = dev; + + phydev->dev_flags = flags; + + return phydev; +} +EXPORT_SYMBOL(phy_attach); + +void phy_detach(struct phy_device *phydev) +{ + phydev->attached_dev = NULL; + + /* If the device had no specific driver before (i.e. - it + * was using the generic driver), we unbind the device + * from the generic driver so that there's a chance a + * real driver could be loaded */ + if (phydev->dev.driver == &genphy_driver.driver) { + down_write(&phydev->dev.bus->subsys.rwsem); + device_release_driver(&phydev->dev); + up_write(&phydev->dev.bus->subsys.rwsem); + } +} +EXPORT_SYMBOL(phy_detach); + + /* Generic PHY support and helper functions */ /* genphy_config_advert @@ -132,7 +250,7 @@ void phy_prepare_link(struct phy_device *phydev, * after sanitizing the values to make sure we only advertise * what is supported */ -static int genphy_config_advert(struct phy_device *phydev) +int genphy_config_advert(struct phy_device *phydev) { u32 advertise; int adv; @@ -190,6 +308,7 @@ static int genphy_config_advert(struct phy_device *phydev) return adv; } +EXPORT_SYMBOL(genphy_config_advert); /* genphy_setup_forced * @@ -541,32 +660,37 @@ void phy_driver_unregister(struct phy_driver *drv) } EXPORT_SYMBOL(phy_driver_unregister); +static struct phy_driver genphy_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic PHY", + .config_init = genphy_config_init, + .features = 0, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .driver = {.owner= THIS_MODULE, }, +}; static int __init phy_init(void) { int rc; - extern int mdio_bus_init(void); - - rc = phy_driver_register(&genphy_driver); - if (rc) - goto out; rc = mdio_bus_init(); if (rc) - goto out_unreg; + return rc; - return 0; + rc = phy_driver_register(&genphy_driver); + if (rc) + mdio_bus_exit(); -out_unreg: - phy_driver_unregister(&genphy_driver); -out: return rc; } static void __exit phy_exit(void) { phy_driver_unregister(&genphy_driver); + mdio_bus_exit(); } -module_init(phy_init); +subsys_initcall(phy_init); module_exit(phy_exit); -- cgit v1.2.3 From 829ca9a30a2ddb727981d80fabdbff2ea86bc9ea Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 3 Sep 2005 15:56:56 -0700 Subject: [PATCH] swsusp: fix remaining u32 vs. pm_message_t confusion Fix remaining bits of u32 vs. pm_message confusion. Should not break anything. Signed-off-by: Pavel Machek Signed-off-by: Rafael J. Wysocki Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/net/phy/mdio_bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 41f62c0c5fcb..5e81494e9a9a 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -128,7 +128,7 @@ static int mdio_bus_match(struct device *dev, struct device_driver *drv) /* Suspend and resume. Copied from platform_suspend and * platform_resume */ -static int mdio_bus_suspend(struct device * dev, u32 state) +static int mdio_bus_suspend(struct device * dev, pm_message_t state) { int ret = 0; struct device_driver *drv = dev->driver; -- cgit v1.2.3 From dc85dec68880176c8ba05f68218a161964cada46 Mon Sep 17 00:00:00 2001 From: Peter Chubb Date: Sat, 3 Sep 2005 14:05:06 -0700 Subject: [PATCH] 'mdio_bus_exit' in discarded section .text.exit When building with CONFIG_PHYLIB=y on Itanium, I see: `mdio_bus_exit' referenced in section `.init.text' of drivers/built-in.o: defined in discarded section `.exit.text' of drivers/built-in.o I believe that mdio_bus_exit should not be declared __exit, because it is referencesd from __init sections in, say, phy_init(). Signed-off-by: Peter Chubb Signed-off-by: Andrew Morton Signed-off-by: Jeff Garzik --- drivers/net/phy/mdio_bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 5e81494e9a9a..90630672703d 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -170,7 +170,7 @@ int __init mdio_bus_init(void) return bus_register(&mdio_bus_type); } -void __exit mdio_bus_exit(void) +void mdio_bus_exit(void) { bus_unregister(&mdio_bus_type); } -- cgit v1.2.3 From 7cda62455c800cf46bb86487d4f32ac6e4e8c519 Mon Sep 17 00:00:00 2001 From: "viro@ZenIV.linux.org.uk" Date: Fri, 2 Sep 2005 20:13:40 +0100 Subject: [PATCH] Kconfig fix (PHYLIB vs. s390) drivers/net/phy/phy.c is broken on s390; it uses enable_irq() and friends and these do not exist on s390. Marked as broken for now. Signed-off-by: Al Viro Signed-off-by: Jeff Garzik --- drivers/net/phy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 6a2fe3583478..14f4de1a8180 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -6,7 +6,7 @@ menu "PHY device support" config PHYLIB tristate "PHY Device support and infrastructure" - depends on NET_ETHERNET + depends on NET_ETHERNET && (BROKEN || !ARCH_S390) help Ethernet controllers are usually attached to PHY devices. This option provides infrastructure for -- cgit v1.2.3 From 8cee0cd5bec53b78dd3c43a73b27821a688133e6 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Fri, 23 Sep 2005 22:58:49 -0400 Subject: [netdrvr] delete CONFIG_PHYCONTROL --- drivers/net/phy/Kconfig | 8 -------- drivers/net/phy/phy.c | 8 -------- drivers/net/phy/phy_device.c | 3 --- 3 files changed, 19 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 14f4de1a8180..c782a6329805 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -12,14 +12,6 @@ config PHYLIB devices. This option provides infrastructure for managing PHY devices. -config PHYCONTROL - bool " Support for automatically handling PHY state changes" - depends on PHYLIB - help - Adds code to perform all the work for keeping PHY link - state (speed/duplex/etc) up-to-date. Also handles - interrupts. - comment "MII PHY device drivers" depends on PHYLIB diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d9e11f93bf3a..9209da9dde0d 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -242,10 +242,6 @@ EXPORT_SYMBOL(phy_sanitize_settings); * choose the next best ones from the ones selected, so we don't * care if ethtool tries to give us bad values * - * A note about the PHYCONTROL Layer. If you turn off - * CONFIG_PHYCONTROL, you will need to read the PHY status - * registers after this function completes, and update your - * controller manually. */ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) { @@ -380,7 +376,6 @@ int phy_start_aneg(struct phy_device *phydev) err = phydev->drv->config_aneg(phydev); -#ifdef CONFIG_PHYCONTROL if (err < 0) goto out_unlock; @@ -395,14 +390,12 @@ int phy_start_aneg(struct phy_device *phydev) } out_unlock: -#endif spin_unlock(&phydev->lock); return err; } EXPORT_SYMBOL(phy_start_aneg); -#ifdef CONFIG_PHYCONTROL static void phy_change(void *data); static void phy_timer(unsigned long data); @@ -868,4 +861,3 @@ static void phy_timer(unsigned long data) mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); } -#endif /* CONFIG_PHYCONTROL */ diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 33f7bdb5857c..6da1aa0706a1 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -101,7 +101,6 @@ struct phy_device * get_phy_device(struct mii_bus *bus, int addr) return dev; } -#ifdef CONFIG_PHYCONTROL /* phy_prepare_link: * * description: Tells the PHY infrastructure to handle the @@ -160,8 +159,6 @@ void phy_disconnect(struct phy_device *phydev) } EXPORT_SYMBOL(phy_disconnect); -#endif /* CONFIG_PHYCONTROL */ - /* phy_attach: * * description: Called by drivers to attach to a particular PHY -- cgit v1.2.3 From 9480e307cd88ef09ec9294c7d97ebec18e6d2221 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 28 Oct 2005 09:52:56 -0700 Subject: [PATCH] DRIVER MODEL: Get rid of the obsolete tri-level suspend/resume callbacks In PM v1, all devices were called at SUSPEND_DISABLE level. Then all devices were called at SUSPEND_SAVE_STATE level, and finally SUSPEND_POWER_DOWN level. However, with PM v2, to maintain compatibility for platform devices, I arranged for the PM v2 suspend/resume callbacks to call the old PM v1 suspend/resume callbacks three times with each level in order so that existing drivers continued to work. Since this is obsolete infrastructure which is no longer necessary, we can remove it. Here's an (untested) patch to do exactly that. Signed-off-by: Russell King Signed-off-by: Greg Kroah-Hartman --- drivers/net/phy/mdio_bus.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) (limited to 'drivers/net/phy') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 90630672703d..ad93b0da87f0 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -133,13 +133,9 @@ static int mdio_bus_suspend(struct device * dev, pm_message_t state) int ret = 0; struct device_driver *drv = dev->driver; - if (drv && drv->suspend) { - ret = drv->suspend(dev, state, SUSPEND_DISABLE); - if (ret == 0) - ret = drv->suspend(dev, state, SUSPEND_SAVE_STATE); - if (ret == 0) - ret = drv->suspend(dev, state, SUSPEND_POWER_DOWN); - } + if (drv && drv->suspend) + ret = drv->suspend(dev, state); + return ret; } @@ -148,13 +144,9 @@ static int mdio_bus_resume(struct device * dev) int ret = 0; struct device_driver *drv = dev->driver; - if (drv && drv->resume) { - ret = drv->resume(dev, RESUME_POWER_ON); - if (ret == 0) - ret = drv->resume(dev, RESUME_RESTORE_STATE); - if (ret == 0) - ret = drv->resume(dev, RESUME_ENABLE); - } + if (drv && drv->resume) + ret = drv->resume(dev); + return ret; } -- cgit v1.2.3