diff options
Diffstat (limited to 'drivers/net/phy')
37 files changed, 13141 insertions, 857 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index fa39b944bc4..65de0cab8d0 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -2,48 +2,211 @@ # PHY Layer Configuration # -menu "PHY device support" - -config PHYLIB +menuconfig PHYLIB tristate "PHY Device support and infrastructure" - depends on NET_ETHERNET && (BROKEN || !S390) + depends on NETDEVICES help Ethernet controllers are usually attached to PHY devices. This option provides infrastructure for managing PHY devices. +if PHYLIB + comment "MII PHY device drivers" - depends on PHYLIB + +config AT803X_PHY + tristate "Drivers for Atheros AT803X PHYs" + ---help--- + Currently supports the AT8030 and AT8035 model + +config AMD_PHY + tristate "Drivers for the AMD PHYs" + ---help--- + Currently supports the am79c874 + +config AMD_XGBE_PHY + tristate "Driver for the AMD 10GbE (amd-xgbe) PHYs" + depends on OF + ---help--- + Currently supports the AMD 10GbE PHY config MARVELL_PHY tristate "Drivers for Marvell PHYs" - depends on PHYLIB ---help--- Currently has a driver for the 88E1011S config DAVICOM_PHY tristate "Drivers for Davicom PHYs" - depends on PHYLIB ---help--- Currently supports dm9161e and dm9131 config QSEMI_PHY tristate "Drivers for Quality Semiconductor PHYs" - depends on PHYLIB ---help--- Currently supports the qs6612 config LXT_PHY tristate "Drivers for the Intel LXT PHYs" - depends on PHYLIB ---help--- Currently supports the lxt970, lxt971 config CICADA_PHY tristate "Drivers for the Cicada PHYs" - depends on PHYLIB ---help--- Currently supports the cis8204 -endmenu +config VITESSE_PHY + tristate "Drivers for the Vitesse PHYs" + ---help--- + Currently supports the vsc8244 + +config SMSC_PHY + tristate "Drivers for SMSC PHYs" + ---help--- + Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs + +config BROADCOM_PHY + tristate "Drivers for Broadcom PHYs" + ---help--- + Currently supports the BCM5411, BCM5421, BCM5461, BCM5464, BCM5481 + and BCM5482 PHYs. + +config BCM63XX_PHY + tristate "Drivers for Broadcom 63xx SOCs internal PHY" + depends on BCM63XX + ---help--- + Currently supports the 6348 and 6358 PHYs. + +config BCM7XXX_PHY + tristate "Drivers for Broadcom 7xxx SOCs internal PHYs" + ---help--- + Currently supports the BCM7366, BCM7439, BCM7445, and + 40nm and 65nm generation of BCM7xxx Set Top Box SoCs. + +config BCM87XX_PHY + tristate "Driver for Broadcom BCM8706 and BCM8727 PHYs" + help + Currently supports the BCM8706 and BCM8727 10G Ethernet PHYs. + +config ICPLUS_PHY + tristate "Drivers for ICPlus PHYs" + ---help--- + Currently supports the IP175C and IP1001 PHYs. + +config REALTEK_PHY + tristate "Drivers for Realtek PHYs" + ---help--- + Supports the Realtek 821x PHY. + +config NATIONAL_PHY + tristate "Drivers for National Semiconductor PHYs" + ---help--- + Currently supports the DP83865 PHY. + +config STE10XP + tristate "Driver for STMicroelectronics STe10Xp PHYs" + ---help--- + This is the driver for the STe100p and STe101p PHYs. + +config LSI_ET1011C_PHY + tristate "Driver for LSI ET1011C PHY" + ---help--- + Supports the LSI ET1011C PHY. + +config MICREL_PHY + tristate "Driver for Micrel PHYs" + ---help--- + Supports the KSZ9021, VSC8201, KS8001 PHYs. + +config FIXED_PHY + bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs" + depends on PHYLIB=y + ---help--- + Adds the platform "fixed" MDIO Bus to cover the boards that use + PHYs that are not connected to the real MDIO bus. + + Currently tested with mpc866ads and mpc8349e-mitx. + +config MDIO_BITBANG + tristate "Support for bitbanged MDIO buses" + help + This module implements the MDIO bus protocol in software, + for use by low level drivers that export the ability to + drive the relevant pins. + + If in doubt, say N. + +config MDIO_GPIO + tristate "Support for GPIO lib-based bitbanged MDIO buses" + depends on MDIO_BITBANG && GPIOLIB + ---help--- + Supports GPIO lib-based MDIO busses. + + To compile this driver as a module, choose M here: the module + will be called mdio-gpio. + +config MDIO_OCTEON + tristate "Support for MDIO buses on Octeon SOCs" + depends on CAVIUM_OCTEON_SOC + default y + help + + This module provides a driver for the Octeon MDIO busses. + It is required by the Octeon Ethernet device drivers. + + If in doubt, say Y. + +config MDIO_SUN4I + tristate "Allwinner sun4i MDIO interface support" + depends on ARCH_SUNXI + select REGULATOR + select REGULATOR_FIXED_VOLTAGE + help + This driver supports the MDIO interface found in the network + interface units of the Allwinner SoC that have an EMAC (A10, + A12, A10s, etc.) + +config MDIO_MOXART + tristate "MOXA ART MDIO interface support" + depends on ARCH_MOXART + help + This driver supports the MDIO interface found in the network + interface units of the MOXA ART SoC + +config MDIO_BUS_MUX + tristate + depends on OF_MDIO + help + This module provides a driver framework for MDIO bus + multiplexers which connect one of several child MDIO busses + to a parent bus. Switching between child busses is done by + device specific drivers. + +config MDIO_BUS_MUX_GPIO + tristate "Support for GPIO controlled MDIO bus multiplexers" + depends on OF_GPIO && OF_MDIO + select MDIO_BUS_MUX + help + This module provides a driver for MDIO bus multiplexers that + are controlled via GPIO lines. The multiplexer connects one of + several child MDIO busses to a parent bus. Child bus + selection is under the control of GPIO lines. + +config MDIO_BUS_MUX_MMIOREG + tristate "Support for MMIO device-controlled MDIO bus multiplexers" + depends on OF_MDIO + select MDIO_BUS_MUX + help + This module provides a driver for MDIO bus multiplexers that + are controlled via a simple memory-mapped device, like an FPGA. + The multiplexer connects one of several child MDIO busses to a + parent bus. Child bus selection is under the control of one of + the FPGA's registers. + + Currently, only 8-bit registers are supported. + +endif # PHYLIB +config MICREL_KS8995MA + tristate "Micrel KS8995MA 5-ports 10/100 managed Ethernet switch" + depends on SPI diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index e4116a5fbb4..7dc3d5b304c 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -8,3 +8,29 @@ 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_SMSC_PHY) += smsc.o +obj-$(CONFIG_VITESSE_PHY) += vitesse.o +obj-$(CONFIG_BROADCOM_PHY) += broadcom.o +obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o +obj-$(CONFIG_BCM7XXX_PHY) += bcm7xxx.o +obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o +obj-$(CONFIG_ICPLUS_PHY) += icplus.o +obj-$(CONFIG_REALTEK_PHY) += realtek.o +obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o +obj-$(CONFIG_FIXED_PHY) += fixed.o +obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o +obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o +obj-$(CONFIG_NATIONAL_PHY) += national.o +obj-$(CONFIG_DP83640_PHY) += dp83640.o +obj-$(CONFIG_STE10XP) += ste10Xp.o +obj-$(CONFIG_MICREL_PHY) += micrel.o +obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o +obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o +obj-$(CONFIG_AT803X_PHY) += at803x.o +obj-$(CONFIG_AMD_PHY) += amd.o +obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o +obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o +obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o +obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o +obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o +obj-$(CONFIG_AMD_XGBE_PHY) += amd-xgbe-phy.o diff --git a/drivers/net/phy/amd-xgbe-phy.c b/drivers/net/phy/amd-xgbe-phy.c new file mode 100644 index 00000000000..b57c2244286 --- /dev/null +++ b/drivers/net/phy/amd-xgbe-phy.c @@ -0,0 +1,1357 @@ +/* + * AMD 10Gb Ethernet PHY driver + * + * This file is available to you under your choice of the following two + * licenses: + * + * License 1: GPLv2 + * + * Copyright (c) 2014 Advanced Micro Devices, Inc. + * + * This file is free software; you may copy, redistribute 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. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * + * License 2: Modified BSD + * + * Copyright (c) 2014 Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Advanced Micro Devices, Inc. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/mdio.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_device.h> +#include <linux/uaccess.h> +#include <asm/irq.h> + + +MODULE_AUTHOR("Tom Lendacky <thomas.lendacky@amd.com>"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_VERSION("1.0.0-a"); +MODULE_DESCRIPTION("AMD 10GbE (amd-xgbe) PHY driver"); + +#define XGBE_PHY_ID 0x000162d0 +#define XGBE_PHY_MASK 0xfffffff0 + +#define XGBE_AN_INT_CMPLT 0x01 +#define XGBE_AN_INC_LINK 0x02 +#define XGBE_AN_PG_RCV 0x04 + +#define XNP_MCF_NULL_MESSAGE 0x001 +#define XNP_ACK_PROCESSED (1 << 12) +#define XNP_MP_FORMATTED (1 << 13) +#define XNP_NP_EXCHANGE (1 << 15) + +#ifndef MDIO_PMA_10GBR_PMD_CTRL +#define MDIO_PMA_10GBR_PMD_CTRL 0x0096 +#endif +#ifndef MDIO_PMA_10GBR_FEC_CTRL +#define MDIO_PMA_10GBR_FEC_CTRL 0x00ab +#endif +#ifndef MDIO_AN_XNP +#define MDIO_AN_XNP 0x0016 +#endif + +#ifndef MDIO_AN_INTMASK +#define MDIO_AN_INTMASK 0x8001 +#endif +#ifndef MDIO_AN_INT +#define MDIO_AN_INT 0x8002 +#endif + +#ifndef MDIO_CTRL1_SPEED1G +#define MDIO_CTRL1_SPEED1G (MDIO_CTRL1_SPEED10G & ~BMCR_SPEED100) +#endif + +/* SerDes integration register offsets */ +#define SIR0_STATUS 0x0040 +#define SIR1_SPEED 0x0000 + +/* SerDes integration register entry bit positions and sizes */ +#define SIR0_STATUS_RX_READY_INDEX 0 +#define SIR0_STATUS_RX_READY_WIDTH 1 +#define SIR0_STATUS_TX_READY_INDEX 8 +#define SIR0_STATUS_TX_READY_WIDTH 1 +#define SIR1_SPEED_DATARATE_INDEX 4 +#define SIR1_SPEED_DATARATE_WIDTH 2 +#define SIR1_SPEED_PI_SPD_SEL_INDEX 12 +#define SIR1_SPEED_PI_SPD_SEL_WIDTH 4 +#define SIR1_SPEED_PLLSEL_INDEX 3 +#define SIR1_SPEED_PLLSEL_WIDTH 1 +#define SIR1_SPEED_RATECHANGE_INDEX 6 +#define SIR1_SPEED_RATECHANGE_WIDTH 1 +#define SIR1_SPEED_TXAMP_INDEX 8 +#define SIR1_SPEED_TXAMP_WIDTH 4 +#define SIR1_SPEED_WORDMODE_INDEX 0 +#define SIR1_SPEED_WORDMODE_WIDTH 3 + +#define SPEED_10000_CDR 0x7 +#define SPEED_10000_PLL 0x1 +#define SPEED_10000_RATE 0x0 +#define SPEED_10000_TXAMP 0xa +#define SPEED_10000_WORD 0x7 + +#define SPEED_2500_CDR 0x2 +#define SPEED_2500_PLL 0x0 +#define SPEED_2500_RATE 0x2 +#define SPEED_2500_TXAMP 0xf +#define SPEED_2500_WORD 0x1 + +#define SPEED_1000_CDR 0x2 +#define SPEED_1000_PLL 0x0 +#define SPEED_1000_RATE 0x3 +#define SPEED_1000_TXAMP 0xf +#define SPEED_1000_WORD 0x1 + + +/* SerDes RxTx register offsets */ +#define RXTX_REG20 0x0050 +#define RXTX_REG114 0x01c8 + +/* SerDes RxTx register entry bit positions and sizes */ +#define RXTX_REG20_BLWC_ENA_INDEX 2 +#define RXTX_REG20_BLWC_ENA_WIDTH 1 +#define RXTX_REG114_PQ_REG_INDEX 9 +#define RXTX_REG114_PQ_REG_WIDTH 7 + +#define RXTX_10000_BLWC 0 +#define RXTX_10000_PQ 0x1e + +#define RXTX_2500_BLWC 1 +#define RXTX_2500_PQ 0xa + +#define RXTX_1000_BLWC 1 +#define RXTX_1000_PQ 0xa + +/* Bit setting and getting macros + * The get macro will extract the current bit field value from within + * the variable + * + * The set macro will clear the current bit field value within the + * variable and then set the bit field of the variable to the + * specified value + */ +#define GET_BITS(_var, _index, _width) \ + (((_var) >> (_index)) & ((0x1 << (_width)) - 1)) + +#define SET_BITS(_var, _index, _width, _val) \ +do { \ + (_var) &= ~(((0x1 << (_width)) - 1) << (_index)); \ + (_var) |= (((_val) & ((0x1 << (_width)) - 1)) << (_index)); \ +} while (0) + +/* Macros for reading or writing SerDes integration registers + * The ioread macros will get bit fields or full values using the + * register definitions formed using the input names + * + * The iowrite macros will set bit fields or full values using the + * register definitions formed using the input names + */ +#define XSIR0_IOREAD(_priv, _reg) \ + ioread16((_priv)->sir0_regs + _reg) + +#define XSIR0_IOREAD_BITS(_priv, _reg, _field) \ + GET_BITS(XSIR0_IOREAD((_priv), _reg), \ + _reg##_##_field##_INDEX, \ + _reg##_##_field##_WIDTH) + +#define XSIR0_IOWRITE(_priv, _reg, _val) \ + iowrite16((_val), (_priv)->sir0_regs + _reg) + +#define XSIR0_IOWRITE_BITS(_priv, _reg, _field, _val) \ +do { \ + u16 reg_val = XSIR0_IOREAD((_priv), _reg); \ + SET_BITS(reg_val, \ + _reg##_##_field##_INDEX, \ + _reg##_##_field##_WIDTH, (_val)); \ + XSIR0_IOWRITE((_priv), _reg, reg_val); \ +} while (0) + +#define XSIR1_IOREAD(_priv, _reg) \ + ioread16((_priv)->sir1_regs + _reg) + +#define XSIR1_IOREAD_BITS(_priv, _reg, _field) \ + GET_BITS(XSIR1_IOREAD((_priv), _reg), \ + _reg##_##_field##_INDEX, \ + _reg##_##_field##_WIDTH) + +#define XSIR1_IOWRITE(_priv, _reg, _val) \ + iowrite16((_val), (_priv)->sir1_regs + _reg) + +#define XSIR1_IOWRITE_BITS(_priv, _reg, _field, _val) \ +do { \ + u16 reg_val = XSIR1_IOREAD((_priv), _reg); \ + SET_BITS(reg_val, \ + _reg##_##_field##_INDEX, \ + _reg##_##_field##_WIDTH, (_val)); \ + XSIR1_IOWRITE((_priv), _reg, reg_val); \ +} while (0) + + +/* Macros for reading or writing SerDes RxTx registers + * The ioread macros will get bit fields or full values using the + * register definitions formed using the input names + * + * The iowrite macros will set bit fields or full values using the + * register definitions formed using the input names + */ +#define XRXTX_IOREAD(_priv, _reg) \ + ioread16((_priv)->rxtx_regs + _reg) + +#define XRXTX_IOREAD_BITS(_priv, _reg, _field) \ + GET_BITS(XRXTX_IOREAD((_priv), _reg), \ + _reg##_##_field##_INDEX, \ + _reg##_##_field##_WIDTH) + +#define XRXTX_IOWRITE(_priv, _reg, _val) \ + iowrite16((_val), (_priv)->rxtx_regs + _reg) + +#define XRXTX_IOWRITE_BITS(_priv, _reg, _field, _val) \ +do { \ + u16 reg_val = XRXTX_IOREAD((_priv), _reg); \ + SET_BITS(reg_val, \ + _reg##_##_field##_INDEX, \ + _reg##_##_field##_WIDTH, (_val)); \ + XRXTX_IOWRITE((_priv), _reg, reg_val); \ +} while (0) + + +enum amd_xgbe_phy_an { + AMD_XGBE_AN_READY = 0, + AMD_XGBE_AN_START, + AMD_XGBE_AN_EVENT, + AMD_XGBE_AN_PAGE_RECEIVED, + AMD_XGBE_AN_INCOMPAT_LINK, + AMD_XGBE_AN_COMPLETE, + AMD_XGBE_AN_NO_LINK, + AMD_XGBE_AN_EXIT, + AMD_XGBE_AN_ERROR, +}; + +enum amd_xgbe_phy_rx { + AMD_XGBE_RX_READY = 0, + AMD_XGBE_RX_BPA, + AMD_XGBE_RX_XNP, + AMD_XGBE_RX_COMPLETE, +}; + +enum amd_xgbe_phy_mode { + AMD_XGBE_MODE_KR, + AMD_XGBE_MODE_KX, +}; + +struct amd_xgbe_phy_priv { + struct platform_device *pdev; + struct device *dev; + + struct phy_device *phydev; + + /* SerDes related mmio resources */ + struct resource *rxtx_res; + struct resource *sir0_res; + struct resource *sir1_res; + + /* SerDes related mmio registers */ + void __iomem *rxtx_regs; /* SerDes Rx/Tx CSRs */ + void __iomem *sir0_regs; /* SerDes integration registers (1/2) */ + void __iomem *sir1_regs; /* SerDes integration registers (2/2) */ + + /* Maintain link status for re-starting auto-negotiation */ + unsigned int link; + enum amd_xgbe_phy_mode mode; + + /* Auto-negotiation state machine support */ + struct mutex an_mutex; + enum amd_xgbe_phy_an an_result; + enum amd_xgbe_phy_an an_state; + enum amd_xgbe_phy_rx kr_state; + enum amd_xgbe_phy_rx kx_state; + struct work_struct an_work; + struct workqueue_struct *an_workqueue; +}; + +static int amd_xgbe_an_enable_kr_training(struct phy_device *phydev) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL); + if (ret < 0) + return ret; + + ret |= 0x02; + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL, ret); + + return 0; +} + +static int amd_xgbe_an_disable_kr_training(struct phy_device *phydev) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL); + if (ret < 0) + return ret; + + ret &= ~0x02; + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL, ret); + + return 0; +} + +static int amd_xgbe_phy_pcs_power_cycle(struct phy_device *phydev) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret |= MDIO_CTRL1_LPOWER; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + usleep_range(75, 100); + + ret &= ~MDIO_CTRL1_LPOWER; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + return 0; +} + +static void amd_xgbe_phy_serdes_start_ratechange(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + + /* Assert Rx and Tx ratechange */ + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, RATECHANGE, 1); +} + +static void amd_xgbe_phy_serdes_complete_ratechange(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + + /* Release Rx and Tx ratechange */ + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, RATECHANGE, 0); + + /* Wait for Rx and Tx ready */ + while (!XSIR0_IOREAD_BITS(priv, SIR0_STATUS, RX_READY) && + !XSIR0_IOREAD_BITS(priv, SIR0_STATUS, TX_READY)) + usleep_range(10, 20); +} + +static int amd_xgbe_phy_xgmii_mode(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + int ret; + + /* Enable KR training */ + ret = amd_xgbe_an_enable_kr_training(phydev); + if (ret < 0) + return ret; + + /* Set PCS to KR/10G speed */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + return ret; + + ret &= ~MDIO_PCS_CTRL2_TYPE; + ret |= MDIO_PCS_CTRL2_10GBR; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2, ret); + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret &= ~MDIO_CTRL1_SPEEDSEL; + ret |= MDIO_CTRL1_SPEED10G; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + ret = amd_xgbe_phy_pcs_power_cycle(phydev); + if (ret < 0) + return ret; + + /* Set SerDes to 10G speed */ + amd_xgbe_phy_serdes_start_ratechange(phydev); + + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, DATARATE, SPEED_10000_RATE); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, WORDMODE, SPEED_10000_WORD); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, TXAMP, SPEED_10000_TXAMP); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PLLSEL, SPEED_10000_PLL); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PI_SPD_SEL, SPEED_10000_CDR); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, RXTX_10000_BLWC); + XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_10000_PQ); + + amd_xgbe_phy_serdes_complete_ratechange(phydev); + + priv->mode = AMD_XGBE_MODE_KR; + + return 0; +} + +static int amd_xgbe_phy_gmii_2500_mode(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + int ret; + + /* Disable KR training */ + ret = amd_xgbe_an_disable_kr_training(phydev); + if (ret < 0) + return ret; + + /* Set PCS to KX/1G speed */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + return ret; + + ret &= ~MDIO_PCS_CTRL2_TYPE; + ret |= MDIO_PCS_CTRL2_10GBX; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2, ret); + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret &= ~MDIO_CTRL1_SPEEDSEL; + ret |= MDIO_CTRL1_SPEED1G; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + ret = amd_xgbe_phy_pcs_power_cycle(phydev); + if (ret < 0) + return ret; + + /* Set SerDes to 2.5G speed */ + amd_xgbe_phy_serdes_start_ratechange(phydev); + + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, DATARATE, SPEED_2500_RATE); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, WORDMODE, SPEED_2500_WORD); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, TXAMP, SPEED_2500_TXAMP); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PLLSEL, SPEED_2500_PLL); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PI_SPD_SEL, SPEED_2500_CDR); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, RXTX_2500_BLWC); + XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_2500_PQ); + + amd_xgbe_phy_serdes_complete_ratechange(phydev); + + priv->mode = AMD_XGBE_MODE_KX; + + return 0; +} + +static int amd_xgbe_phy_gmii_mode(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + int ret; + + /* Disable KR training */ + ret = amd_xgbe_an_disable_kr_training(phydev); + if (ret < 0) + return ret; + + /* Set PCS to KX/1G speed */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + return ret; + + ret &= ~MDIO_PCS_CTRL2_TYPE; + ret |= MDIO_PCS_CTRL2_10GBX; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2, ret); + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret &= ~MDIO_CTRL1_SPEEDSEL; + ret |= MDIO_CTRL1_SPEED1G; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + ret = amd_xgbe_phy_pcs_power_cycle(phydev); + if (ret < 0) + return ret; + + /* Set SerDes to 1G speed */ + amd_xgbe_phy_serdes_start_ratechange(phydev); + + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, DATARATE, SPEED_1000_RATE); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, WORDMODE, SPEED_1000_WORD); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, TXAMP, SPEED_1000_TXAMP); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PLLSEL, SPEED_1000_PLL); + XSIR1_IOWRITE_BITS(priv, SIR1_SPEED, PI_SPD_SEL, SPEED_1000_CDR); + + XRXTX_IOWRITE_BITS(priv, RXTX_REG20, BLWC_ENA, RXTX_1000_BLWC); + XRXTX_IOWRITE_BITS(priv, RXTX_REG114, PQ_REG, RXTX_1000_PQ); + + amd_xgbe_phy_serdes_complete_ratechange(phydev); + + priv->mode = AMD_XGBE_MODE_KX; + + return 0; +} + +static int amd_xgbe_phy_switch_mode(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + int ret; + + /* If we are in KR switch to KX, and vice-versa */ + if (priv->mode == AMD_XGBE_MODE_KR) + ret = amd_xgbe_phy_gmii_mode(phydev); + else + ret = amd_xgbe_phy_xgmii_mode(phydev); + + return ret; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_switch_mode(struct phy_device *phydev) +{ + int ret; + + ret = amd_xgbe_phy_switch_mode(phydev); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + return AMD_XGBE_AN_START; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_tx_training(struct phy_device *phydev, + enum amd_xgbe_phy_rx *state) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + int ad_reg, lp_reg, ret; + + *state = AMD_XGBE_RX_COMPLETE; + + /* If we're in KX mode then we're done */ + if (priv->mode == AMD_XGBE_MODE_KX) + return AMD_XGBE_AN_EVENT; + + /* Enable/Disable FEC */ + ad_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2); + if (ad_reg < 0) + return AMD_XGBE_AN_ERROR; + + lp_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA + 2); + if (lp_reg < 0) + return AMD_XGBE_AN_ERROR; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_FEC_CTRL); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + if ((ad_reg & 0xc000) && (lp_reg & 0xc000)) + ret |= 0x01; + else + ret &= ~0x01; + + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_FEC_CTRL, ret); + + /* Start KR training */ + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + ret |= 0x01; + phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10GBR_PMD_CTRL, ret); + + return AMD_XGBE_AN_EVENT; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_tx_xnp(struct phy_device *phydev, + enum amd_xgbe_phy_rx *state) +{ + u16 msg; + + *state = AMD_XGBE_RX_XNP; + + msg = XNP_MCF_NULL_MESSAGE; + msg |= XNP_MP_FORMATTED; + + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_XNP + 2, 0); + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_XNP + 1, 0); + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_XNP, msg); + + return AMD_XGBE_AN_EVENT; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_rx_bpa(struct phy_device *phydev, + enum amd_xgbe_phy_rx *state) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + unsigned int link_support; + int ret, ad_reg, lp_reg; + + /* Read Base Ability register 2 first */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA + 1); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + /* Check for a supported mode, otherwise restart in a different one */ + link_support = (priv->mode == AMD_XGBE_MODE_KR) ? 0x80 : 0x20; + if (!(ret & link_support)) + return amd_xgbe_an_switch_mode(phydev); + + /* Check Extended Next Page support */ + ad_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); + if (ad_reg < 0) + return AMD_XGBE_AN_ERROR; + + lp_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA); + if (lp_reg < 0) + return AMD_XGBE_AN_ERROR; + + return ((ad_reg & XNP_NP_EXCHANGE) || (lp_reg & XNP_NP_EXCHANGE)) ? + amd_xgbe_an_tx_xnp(phydev, state) : + amd_xgbe_an_tx_training(phydev, state); +} + +static enum amd_xgbe_phy_an amd_xgbe_an_rx_xnp(struct phy_device *phydev, + enum amd_xgbe_phy_rx *state) +{ + int ad_reg, lp_reg; + + /* Check Extended Next Page support */ + ad_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); + if (ad_reg < 0) + return AMD_XGBE_AN_ERROR; + + lp_reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA); + if (lp_reg < 0) + return AMD_XGBE_AN_ERROR; + + return ((ad_reg & XNP_NP_EXCHANGE) || (lp_reg & XNP_NP_EXCHANGE)) ? + amd_xgbe_an_tx_xnp(phydev, state) : + amd_xgbe_an_tx_training(phydev, state); +} + +static enum amd_xgbe_phy_an amd_xgbe_an_start(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + int ret; + + /* Be sure we aren't looping trying to negotiate */ + if (priv->mode == AMD_XGBE_MODE_KR) { + if (priv->kr_state != AMD_XGBE_RX_READY) + return AMD_XGBE_AN_NO_LINK; + priv->kr_state = AMD_XGBE_RX_BPA; + } else { + if (priv->kx_state != AMD_XGBE_RX_READY) + return AMD_XGBE_AN_NO_LINK; + priv->kx_state = AMD_XGBE_RX_BPA; + } + + /* Set up Advertisement register 3 first */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + if (phydev->supported & SUPPORTED_10000baseR_FEC) + ret |= 0xc000; + else + ret &= ~0xc000; + + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 2, ret); + + /* Set up Advertisement register 2 next */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + if (phydev->supported & SUPPORTED_10000baseKR_Full) + ret |= 0x80; + else + ret &= ~0x80; + + if (phydev->supported & SUPPORTED_1000baseKX_Full) + ret |= 0x20; + else + ret &= ~0x20; + + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE + 1, ret); + + /* Set up Advertisement register 1 last */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + if (phydev->supported & SUPPORTED_Pause) + ret |= 0x400; + else + ret &= ~0x400; + + if (phydev->supported & SUPPORTED_Asym_Pause) + ret |= 0x800; + else + ret &= ~0x800; + + /* We don't intend to perform XNP */ + ret &= ~XNP_NP_EXCHANGE; + + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE, ret); + + /* Enable and start auto-negotiation */ + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + ret |= MDIO_AN_CTRL1_ENABLE; + ret |= MDIO_AN_CTRL1_RESTART; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, ret); + + return AMD_XGBE_AN_EVENT; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_event(struct phy_device *phydev) +{ + enum amd_xgbe_phy_an new_state; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT); + if (ret < 0) + return AMD_XGBE_AN_ERROR; + + new_state = AMD_XGBE_AN_EVENT; + if (ret & XGBE_AN_PG_RCV) + new_state = AMD_XGBE_AN_PAGE_RECEIVED; + else if (ret & XGBE_AN_INC_LINK) + new_state = AMD_XGBE_AN_INCOMPAT_LINK; + else if (ret & XGBE_AN_INT_CMPLT) + new_state = AMD_XGBE_AN_COMPLETE; + + if (new_state != AMD_XGBE_AN_EVENT) + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + + return new_state; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_page_received(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + enum amd_xgbe_phy_rx *state; + int ret; + + state = (priv->mode == AMD_XGBE_MODE_KR) ? &priv->kr_state + : &priv->kx_state; + + switch (*state) { + case AMD_XGBE_RX_BPA: + ret = amd_xgbe_an_rx_bpa(phydev, state); + break; + + case AMD_XGBE_RX_XNP: + ret = amd_xgbe_an_rx_xnp(phydev, state); + break; + + default: + ret = AMD_XGBE_AN_ERROR; + } + + return ret; +} + +static enum amd_xgbe_phy_an amd_xgbe_an_incompat_link(struct phy_device *phydev) +{ + return amd_xgbe_an_switch_mode(phydev); +} + +static void amd_xgbe_an_state_machine(struct work_struct *work) +{ + struct amd_xgbe_phy_priv *priv = container_of(work, + struct amd_xgbe_phy_priv, + an_work); + struct phy_device *phydev = priv->phydev; + enum amd_xgbe_phy_an cur_state; + int sleep; + + while (1) { + mutex_lock(&priv->an_mutex); + + cur_state = priv->an_state; + + switch (priv->an_state) { + case AMD_XGBE_AN_START: + priv->an_state = amd_xgbe_an_start(phydev); + break; + + case AMD_XGBE_AN_EVENT: + priv->an_state = amd_xgbe_an_event(phydev); + break; + + case AMD_XGBE_AN_PAGE_RECEIVED: + priv->an_state = amd_xgbe_an_page_received(phydev); + break; + + case AMD_XGBE_AN_INCOMPAT_LINK: + priv->an_state = amd_xgbe_an_incompat_link(phydev); + break; + + case AMD_XGBE_AN_COMPLETE: + case AMD_XGBE_AN_NO_LINK: + case AMD_XGBE_AN_EXIT: + goto exit_unlock; + + default: + priv->an_state = AMD_XGBE_AN_ERROR; + } + + if (priv->an_state == AMD_XGBE_AN_ERROR) { + netdev_err(phydev->attached_dev, + "error during auto-negotiation, state=%u\n", + cur_state); + goto exit_unlock; + } + + sleep = (priv->an_state == AMD_XGBE_AN_EVENT) ? 1 : 0; + + mutex_unlock(&priv->an_mutex); + + if (sleep) + usleep_range(20, 50); + } + +exit_unlock: + priv->an_result = priv->an_state; + priv->an_state = AMD_XGBE_AN_READY; + + mutex_unlock(&priv->an_mutex); +} + +static int amd_xgbe_phy_soft_reset(struct phy_device *phydev) +{ + int count, ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret |= MDIO_CTRL1_RESET; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + count = 50; + do { + msleep(20); + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + return ret; + } while ((ret & MDIO_CTRL1_RESET) && --count); + + if (ret & MDIO_CTRL1_RESET) + return -ETIMEDOUT; + + return 0; +} + +static int amd_xgbe_phy_config_init(struct phy_device *phydev) +{ + /* Initialize supported features */ + phydev->supported = SUPPORTED_Autoneg; + phydev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause; + phydev->supported |= SUPPORTED_Backplane; + phydev->supported |= SUPPORTED_1000baseKX_Full | + SUPPORTED_2500baseX_Full; + phydev->supported |= SUPPORTED_10000baseKR_Full | + SUPPORTED_10000baseR_FEC; + phydev->advertising = phydev->supported; + + /* Turn off and clear interrupts */ + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INTMASK, 0); + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_INT, 0); + + return 0; +} + +static int amd_xgbe_phy_setup_forced(struct phy_device *phydev) +{ + int ret; + + /* Disable auto-negotiation */ + ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret &= ~MDIO_AN_CTRL1_ENABLE; + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, ret); + + /* Validate/Set specified speed */ + switch (phydev->speed) { + case SPEED_10000: + ret = amd_xgbe_phy_xgmii_mode(phydev); + break; + + case SPEED_2500: + ret = amd_xgbe_phy_gmii_2500_mode(phydev); + break; + + case SPEED_1000: + ret = amd_xgbe_phy_gmii_mode(phydev); + break; + + default: + ret = -EINVAL; + } + + if (ret < 0) + return ret; + + /* Validate duplex mode */ + if (phydev->duplex != DUPLEX_FULL) + return -EINVAL; + + phydev->pause = 0; + phydev->asym_pause = 0; + + return 0; +} + +static int amd_xgbe_phy_config_aneg(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + u32 mmd_mask = phydev->c45_ids.devices_in_package; + int ret; + + if (phydev->autoneg != AUTONEG_ENABLE) + return amd_xgbe_phy_setup_forced(phydev); + + /* Make sure we have the AN MMD present */ + if (!(mmd_mask & MDIO_DEVS_AN)) + return -EINVAL; + + /* Get the current speed mode */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + return ret; + + /* Start/Restart the auto-negotiation state machine */ + mutex_lock(&priv->an_mutex); + priv->an_result = AMD_XGBE_AN_READY; + priv->an_state = AMD_XGBE_AN_START; + priv->kr_state = AMD_XGBE_RX_READY; + priv->kx_state = AMD_XGBE_RX_READY; + mutex_unlock(&priv->an_mutex); + + queue_work(priv->an_workqueue, &priv->an_work); + + return 0; +} + +static int amd_xgbe_phy_aneg_done(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + enum amd_xgbe_phy_an state; + + mutex_lock(&priv->an_mutex); + state = priv->an_result; + mutex_unlock(&priv->an_mutex); + + return (state == AMD_XGBE_AN_COMPLETE); +} + +static int amd_xgbe_phy_update_link(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + enum amd_xgbe_phy_an state; + unsigned int check_again, autoneg; + int ret; + + /* If we're doing auto-negotiation don't report link down */ + mutex_lock(&priv->an_mutex); + state = priv->an_state; + mutex_unlock(&priv->an_mutex); + + if (state != AMD_XGBE_AN_READY) { + phydev->link = 1; + return 0; + } + + /* Since the device can be in the wrong mode when a link is + * (re-)established (cable connected after the interface is + * up, etc.), the link status may report no link. If there + * is no link, try switching modes and checking the status + * again. + */ + check_again = 1; +again: + /* Link status is latched low, so read once to clear + * and then read again to get current state + */ + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); + if (ret < 0) + return ret; + + phydev->link = (ret & MDIO_STAT1_LSTATUS) ? 1 : 0; + + if (!phydev->link) { + ret = amd_xgbe_phy_switch_mode(phydev); + if (check_again) { + check_again = 0; + goto again; + } + } + + autoneg = (phydev->link && !priv->link) ? 1 : 0; + priv->link = phydev->link; + if (autoneg) { + /* Link is (back) up, re-start auto-negotiation */ + ret = amd_xgbe_phy_config_aneg(phydev); + if (ret < 0) + return ret; + } + + return 0; +} + +static int amd_xgbe_phy_read_status(struct phy_device *phydev) +{ + u32 mmd_mask = phydev->c45_ids.devices_in_package; + int ret, mode, ad_ret, lp_ret; + + ret = amd_xgbe_phy_update_link(phydev); + if (ret) + return ret; + + mode = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (mode < 0) + return mode; + mode &= MDIO_PCS_CTRL2_TYPE; + + if (phydev->autoneg == AUTONEG_ENABLE) { + if (!(mmd_mask & MDIO_DEVS_AN)) + return -EINVAL; + + if (!amd_xgbe_phy_aneg_done(phydev)) + return 0; + + /* Compare Advertisement and Link Partner register 1 */ + ad_ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); + if (ad_ret < 0) + return ad_ret; + lp_ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA); + if (lp_ret < 0) + return lp_ret; + + ad_ret &= lp_ret; + phydev->pause = (ad_ret & 0x400) ? 1 : 0; + phydev->asym_pause = (ad_ret & 0x800) ? 1 : 0; + + /* Compare Advertisement and Link Partner register 2 */ + ad_ret = phy_read_mmd(phydev, MDIO_MMD_AN, + MDIO_AN_ADVERTISE + 1); + if (ad_ret < 0) + return ad_ret; + lp_ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA + 1); + if (lp_ret < 0) + return lp_ret; + + ad_ret &= lp_ret; + if (ad_ret & 0x80) { + phydev->speed = SPEED_10000; + if (mode != MDIO_PCS_CTRL2_10GBR) { + ret = amd_xgbe_phy_xgmii_mode(phydev); + if (ret < 0) + return ret; + } + } else { + phydev->speed = SPEED_1000; + if (mode == MDIO_PCS_CTRL2_10GBR) { + ret = amd_xgbe_phy_gmii_mode(phydev); + if (ret < 0) + return ret; + } + } + + phydev->duplex = DUPLEX_FULL; + } else { + phydev->speed = (mode == MDIO_PCS_CTRL2_10GBR) ? SPEED_10000 + : SPEED_1000; + phydev->duplex = DUPLEX_FULL; + phydev->pause = 0; + phydev->asym_pause = 0; + } + + return 0; +} + +static int amd_xgbe_phy_suspend(struct phy_device *phydev) +{ + int ret; + + mutex_lock(&phydev->lock); + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + goto unlock; + + ret |= MDIO_CTRL1_LPOWER; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + ret = 0; + +unlock: + mutex_unlock(&phydev->lock); + + return ret; +} + +static int amd_xgbe_phy_resume(struct phy_device *phydev) +{ + int ret; + + mutex_lock(&phydev->lock); + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (ret < 0) + goto unlock; + + ret &= ~MDIO_CTRL1_LPOWER; + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, ret); + + ret = 0; + +unlock: + mutex_unlock(&phydev->lock); + + return ret; +} + +static int amd_xgbe_phy_probe(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv; + struct platform_device *pdev; + struct device *dev; + char *wq_name; + int ret; + + if (!phydev->dev.of_node) + return -EINVAL; + + pdev = of_find_device_by_node(phydev->dev.of_node); + if (!pdev) + return -EINVAL; + dev = &pdev->dev; + + wq_name = kasprintf(GFP_KERNEL, "%s-amd-xgbe-phy", phydev->bus->name); + if (!wq_name) { + ret = -ENOMEM; + goto err_pdev; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto err_name; + } + + priv->pdev = pdev; + priv->dev = dev; + priv->phydev = phydev; + + /* Get the device mmio areas */ + priv->rxtx_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->rxtx_regs = devm_ioremap_resource(dev, priv->rxtx_res); + if (IS_ERR(priv->rxtx_regs)) { + dev_err(dev, "rxtx ioremap failed\n"); + ret = PTR_ERR(priv->rxtx_regs); + goto err_priv; + } + + priv->sir0_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->sir0_regs = devm_ioremap_resource(dev, priv->sir0_res); + if (IS_ERR(priv->sir0_regs)) { + dev_err(dev, "sir0 ioremap failed\n"); + ret = PTR_ERR(priv->sir0_regs); + goto err_rxtx; + } + + priv->sir1_res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + priv->sir1_regs = devm_ioremap_resource(dev, priv->sir1_res); + if (IS_ERR(priv->sir1_regs)) { + dev_err(dev, "sir1 ioremap failed\n"); + ret = PTR_ERR(priv->sir1_regs); + goto err_sir0; + } + + priv->link = 1; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL2); + if (ret < 0) + goto err_sir1; + if ((ret & MDIO_PCS_CTRL2_TYPE) == MDIO_PCS_CTRL2_10GBR) + priv->mode = AMD_XGBE_MODE_KR; + else + priv->mode = AMD_XGBE_MODE_KX; + + mutex_init(&priv->an_mutex); + INIT_WORK(&priv->an_work, amd_xgbe_an_state_machine); + priv->an_workqueue = create_singlethread_workqueue(wq_name); + if (!priv->an_workqueue) { + ret = -ENOMEM; + goto err_sir1; + } + + phydev->priv = priv; + + kfree(wq_name); + of_dev_put(pdev); + + return 0; + +err_sir1: + devm_iounmap(dev, priv->sir1_regs); + devm_release_mem_region(dev, priv->sir1_res->start, + resource_size(priv->sir1_res)); + +err_sir0: + devm_iounmap(dev, priv->sir0_regs); + devm_release_mem_region(dev, priv->sir0_res->start, + resource_size(priv->sir0_res)); + +err_rxtx: + devm_iounmap(dev, priv->rxtx_regs); + devm_release_mem_region(dev, priv->rxtx_res->start, + resource_size(priv->rxtx_res)); + +err_priv: + devm_kfree(dev, priv); + +err_name: + kfree(wq_name); + +err_pdev: + of_dev_put(pdev); + + return ret; +} + +static void amd_xgbe_phy_remove(struct phy_device *phydev) +{ + struct amd_xgbe_phy_priv *priv = phydev->priv; + struct device *dev = priv->dev; + + /* Stop any in process auto-negotiation */ + mutex_lock(&priv->an_mutex); + priv->an_state = AMD_XGBE_AN_EXIT; + mutex_unlock(&priv->an_mutex); + + flush_workqueue(priv->an_workqueue); + destroy_workqueue(priv->an_workqueue); + + /* Release resources */ + devm_iounmap(dev, priv->sir1_regs); + devm_release_mem_region(dev, priv->sir1_res->start, + resource_size(priv->sir1_res)); + + devm_iounmap(dev, priv->sir0_regs); + devm_release_mem_region(dev, priv->sir0_res->start, + resource_size(priv->sir0_res)); + + devm_iounmap(dev, priv->rxtx_regs); + devm_release_mem_region(dev, priv->rxtx_res->start, + resource_size(priv->rxtx_res)); + + devm_kfree(dev, priv); +} + +static int amd_xgbe_match_phy_device(struct phy_device *phydev) +{ + return phydev->c45_ids.device_ids[MDIO_MMD_PCS] == XGBE_PHY_ID; +} + +static struct phy_driver amd_xgbe_phy_driver[] = { + { + .phy_id = XGBE_PHY_ID, + .phy_id_mask = XGBE_PHY_MASK, + .name = "AMD XGBE PHY", + .features = 0, + .probe = amd_xgbe_phy_probe, + .remove = amd_xgbe_phy_remove, + .soft_reset = amd_xgbe_phy_soft_reset, + .config_init = amd_xgbe_phy_config_init, + .suspend = amd_xgbe_phy_suspend, + .resume = amd_xgbe_phy_resume, + .config_aneg = amd_xgbe_phy_config_aneg, + .aneg_done = amd_xgbe_phy_aneg_done, + .read_status = amd_xgbe_phy_read_status, + .match_phy_device = amd_xgbe_match_phy_device, + .driver = { + .owner = THIS_MODULE, + }, + }, +}; + +static int __init amd_xgbe_phy_init(void) +{ + return phy_drivers_register(amd_xgbe_phy_driver, + ARRAY_SIZE(amd_xgbe_phy_driver)); +} + +static void __exit amd_xgbe_phy_exit(void) +{ + phy_drivers_unregister(amd_xgbe_phy_driver, + ARRAY_SIZE(amd_xgbe_phy_driver)); +} + +module_init(amd_xgbe_phy_init); +module_exit(amd_xgbe_phy_exit); + +static struct mdio_device_id __maybe_unused amd_xgbe_phy_ids[] = { + { XGBE_PHY_ID, XGBE_PHY_MASK }, + { } +}; +MODULE_DEVICE_TABLE(mdio, amd_xgbe_phy_ids); diff --git a/drivers/net/phy/amd.c b/drivers/net/phy/amd.c new file mode 100644 index 00000000000..a3fb5ceb648 --- /dev/null +++ b/drivers/net/phy/amd.c @@ -0,0 +1,96 @@ +/* + * Driver for AMD am79c PHYs + * + * Author: Heiko Schocher <hs@denx.de> + * + * Copyright (c) 2011 DENX Software Engineering GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/phy.h> + +#define PHY_ID_AM79C874 0x0022561b + +#define MII_AM79C_IR 17 /* Interrupt Status/Control Register */ +#define MII_AM79C_IR_EN_LINK 0x0400 /* IR enable Linkstate */ +#define MII_AM79C_IR_EN_ANEG 0x0100 /* IR enable Aneg Complete */ +#define MII_AM79C_IR_IMASK_INIT (MII_AM79C_IR_EN_LINK | MII_AM79C_IR_EN_ANEG) + +MODULE_DESCRIPTION("AMD PHY driver"); +MODULE_AUTHOR("Heiko Schocher <hs@denx.de>"); +MODULE_LICENSE("GPL"); + +static int am79c_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, MII_BMSR); + if (err < 0) + return err; + + err = phy_read(phydev, MII_AM79C_IR); + if (err < 0) + return err; + + return 0; +} + +static int am79c_config_init(struct phy_device *phydev) +{ + return 0; +} + +static int am79c_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_AM79C_IR, MII_AM79C_IR_IMASK_INIT); + else + err = phy_write(phydev, MII_AM79C_IR, 0); + + return err; +} + +static struct phy_driver am79c_driver = { + .phy_id = PHY_ID_AM79C874, + .name = "AM79C874", + .phy_id_mask = 0xfffffff0, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = am79c_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = am79c_ack_interrupt, + .config_intr = am79c_config_intr, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init am79c_init(void) +{ + return phy_driver_register(&am79c_driver); +} + +static void __exit am79c_exit(void) +{ + phy_driver_unregister(&am79c_driver); +} + +module_init(am79c_init); +module_exit(am79c_exit); + +static struct mdio_device_id __maybe_unused amd_tbl[] = { + { PHY_ID_AM79C874, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, amd_tbl); diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c new file mode 100644 index 00000000000..fdc1b418fa6 --- /dev/null +++ b/drivers/net/phy/at803x.c @@ -0,0 +1,376 @@ +/* + * drivers/net/phy/at803x.c + * + * Driver for Atheros 803x PHY + * + * Author: Matus Ujhelyi <ujhelyi.m@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/phy.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> + +#define AT803X_INTR_ENABLE 0x12 +#define AT803X_INTR_STATUS 0x13 +#define AT803X_SMART_SPEED 0x14 +#define AT803X_LED_CONTROL 0x18 +#define AT803X_WOL_ENABLE 0x01 +#define AT803X_DEVICE_ADDR 0x03 +#define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C +#define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B +#define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A +#define AT803X_MMD_ACCESS_CONTROL 0x0D +#define AT803X_MMD_ACCESS_CONTROL_DATA 0x0E +#define AT803X_FUNC_DATA 0x4003 +#define AT803X_INER 0x0012 +#define AT803X_INER_INIT 0xec00 +#define AT803X_INSR 0x0013 +#define AT803X_DEBUG_ADDR 0x1D +#define AT803X_DEBUG_DATA 0x1E +#define AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05 +#define AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8) + +#define ATH8030_PHY_ID 0x004dd076 +#define ATH8031_PHY_ID 0x004dd074 +#define ATH8035_PHY_ID 0x004dd072 + +MODULE_DESCRIPTION("Atheros 803x PHY driver"); +MODULE_AUTHOR("Matus Ujhelyi"); +MODULE_LICENSE("GPL"); + +struct at803x_priv { + bool phy_reset:1; + struct gpio_desc *gpiod_reset; +}; + +struct at803x_context { + u16 bmcr; + u16 advertise; + u16 control1000; + u16 int_enable; + u16 smart_speed; + u16 led_control; +}; + +/* save relevant PHY registers to private copy */ +static void at803x_context_save(struct phy_device *phydev, + struct at803x_context *context) +{ + context->bmcr = phy_read(phydev, MII_BMCR); + context->advertise = phy_read(phydev, MII_ADVERTISE); + context->control1000 = phy_read(phydev, MII_CTRL1000); + context->int_enable = phy_read(phydev, AT803X_INTR_ENABLE); + context->smart_speed = phy_read(phydev, AT803X_SMART_SPEED); + context->led_control = phy_read(phydev, AT803X_LED_CONTROL); +} + +/* restore relevant PHY registers from private copy */ +static void at803x_context_restore(struct phy_device *phydev, + const struct at803x_context *context) +{ + phy_write(phydev, MII_BMCR, context->bmcr); + phy_write(phydev, MII_ADVERTISE, context->advertise); + phy_write(phydev, MII_CTRL1000, context->control1000); + phy_write(phydev, AT803X_INTR_ENABLE, context->int_enable); + phy_write(phydev, AT803X_SMART_SPEED, context->smart_speed); + phy_write(phydev, AT803X_LED_CONTROL, context->led_control); +} + +static int at803x_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + struct net_device *ndev = phydev->attached_dev; + const u8 *mac; + int ret; + u32 value; + unsigned int i, offsets[] = { + AT803X_LOC_MAC_ADDR_32_47_OFFSET, + AT803X_LOC_MAC_ADDR_16_31_OFFSET, + AT803X_LOC_MAC_ADDR_0_15_OFFSET, + }; + + if (!ndev) + return -ENODEV; + + if (wol->wolopts & WAKE_MAGIC) { + mac = (const u8 *) ndev->dev_addr; + + if (!is_valid_ether_addr(mac)) + return -EFAULT; + + for (i = 0; i < 3; i++) { + phy_write(phydev, AT803X_MMD_ACCESS_CONTROL, + AT803X_DEVICE_ADDR); + phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA, + offsets[i]); + phy_write(phydev, AT803X_MMD_ACCESS_CONTROL, + AT803X_FUNC_DATA); + phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA, + mac[(i * 2) + 1] | (mac[(i * 2)] << 8)); + } + + value = phy_read(phydev, AT803X_INTR_ENABLE); + value |= AT803X_WOL_ENABLE; + ret = phy_write(phydev, AT803X_INTR_ENABLE, value); + if (ret) + return ret; + value = phy_read(phydev, AT803X_INTR_STATUS); + } else { + value = phy_read(phydev, AT803X_INTR_ENABLE); + value &= (~AT803X_WOL_ENABLE); + ret = phy_write(phydev, AT803X_INTR_ENABLE, value); + if (ret) + return ret; + value = phy_read(phydev, AT803X_INTR_STATUS); + } + + return ret; +} + +static void at803x_get_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + u32 value; + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + + value = phy_read(phydev, AT803X_INTR_ENABLE); + if (value & AT803X_WOL_ENABLE) + wol->wolopts |= WAKE_MAGIC; +} + +static int at803x_suspend(struct phy_device *phydev) +{ + int value; + int wol_enabled; + + mutex_lock(&phydev->lock); + + value = phy_read(phydev, AT803X_INTR_ENABLE); + wol_enabled = value & AT803X_WOL_ENABLE; + + value = phy_read(phydev, MII_BMCR); + + if (wol_enabled) + value |= BMCR_ISOLATE; + else + value |= BMCR_PDOWN; + + phy_write(phydev, MII_BMCR, value); + + mutex_unlock(&phydev->lock); + + return 0; +} + +static int at803x_resume(struct phy_device *phydev) +{ + int value; + + mutex_lock(&phydev->lock); + + value = phy_read(phydev, MII_BMCR); + value &= ~(BMCR_PDOWN | BMCR_ISOLATE); + phy_write(phydev, MII_BMCR, value); + + mutex_unlock(&phydev->lock); + + return 0; +} + +static int at803x_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->dev; + struct at803x_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->gpiod_reset = devm_gpiod_get(dev, "reset"); + if (IS_ERR(priv->gpiod_reset)) + priv->gpiod_reset = NULL; + else + gpiod_direction_output(priv->gpiod_reset, 1); + + phydev->priv = priv; + + return 0; +} + +static int at803x_config_init(struct phy_device *phydev) +{ + int ret; + + ret = genphy_config_init(phydev); + if (ret < 0) + return ret; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { + ret = phy_write(phydev, AT803X_DEBUG_ADDR, + AT803X_DEBUG_SYSTEM_MODE_CTRL); + if (ret) + return ret; + ret = phy_write(phydev, AT803X_DEBUG_DATA, + AT803X_DEBUG_RGMII_TX_CLK_DLY); + if (ret) + return ret; + } + + return 0; +} + +static int at803x_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, AT803X_INSR); + + return (err < 0) ? err : 0; +} + +static int at803x_config_intr(struct phy_device *phydev) +{ + int err; + int value; + + value = phy_read(phydev, AT803X_INER); + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, AT803X_INER, + value | AT803X_INER_INIT); + else + err = phy_write(phydev, AT803X_INER, 0); + + return err; +} + +static void at803x_link_change_notify(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + + /* + * Conduct a hardware reset for AT8030 every time a link loss is + * signalled. This is necessary to circumvent a hardware bug that + * occurs when the cable is unplugged while TX packets are pending + * in the FIFO. In such cases, the FIFO enters an error mode it + * cannot recover from by software. + */ + if (phydev->drv->phy_id == ATH8030_PHY_ID) { + if (phydev->state == PHY_NOLINK) { + if (priv->gpiod_reset && !priv->phy_reset) { + struct at803x_context context; + + at803x_context_save(phydev, &context); + + gpiod_set_value(priv->gpiod_reset, 0); + msleep(1); + gpiod_set_value(priv->gpiod_reset, 1); + msleep(1); + + at803x_context_restore(phydev, &context); + + dev_dbg(&phydev->dev, "%s(): phy was reset\n", + __func__); + priv->phy_reset = true; + } + } else { + priv->phy_reset = false; + } + } +} + +static struct phy_driver at803x_driver[] = { +{ + /* ATHEROS 8035 */ + .phy_id = ATH8035_PHY_ID, + .name = "Atheros 8035 ethernet", + .phy_id_mask = 0xffffffef, + .probe = at803x_probe, + .config_init = at803x_config_init, + .link_change_notify = at803x_link_change_notify, + .set_wol = at803x_set_wol, + .get_wol = at803x_get_wol, + .suspend = at803x_suspend, + .resume = at803x_resume, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .driver = { + .owner = THIS_MODULE, + }, +}, { + /* ATHEROS 8030 */ + .phy_id = ATH8030_PHY_ID, + .name = "Atheros 8030 ethernet", + .phy_id_mask = 0xffffffef, + .probe = at803x_probe, + .config_init = at803x_config_init, + .link_change_notify = at803x_link_change_notify, + .set_wol = at803x_set_wol, + .get_wol = at803x_get_wol, + .suspend = at803x_suspend, + .resume = at803x_resume, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .driver = { + .owner = THIS_MODULE, + }, +}, { + /* ATHEROS 8031 */ + .phy_id = ATH8031_PHY_ID, + .name = "Atheros 8031 ethernet", + .phy_id_mask = 0xffffffef, + .probe = at803x_probe, + .config_init = at803x_config_init, + .link_change_notify = at803x_link_change_notify, + .set_wol = at803x_set_wol, + .get_wol = at803x_get_wol, + .suspend = at803x_suspend, + .resume = at803x_resume, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = &at803x_ack_interrupt, + .config_intr = &at803x_config_intr, + .driver = { + .owner = THIS_MODULE, + }, +} }; + +static int __init atheros_init(void) +{ + return phy_drivers_register(at803x_driver, + ARRAY_SIZE(at803x_driver)); +} + +static void __exit atheros_exit(void) +{ + phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver)); +} + +module_init(atheros_init); +module_exit(atheros_exit); + +static struct mdio_device_id __maybe_unused atheros_tbl[] = { + { ATH8030_PHY_ID, 0xffffffef }, + { ATH8031_PHY_ID, 0xffffffef }, + { ATH8035_PHY_ID, 0xffffffef }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, atheros_tbl); diff --git a/drivers/net/phy/bcm63xx.c b/drivers/net/phy/bcm63xx.c new file mode 100644 index 00000000000..ac55b080785 --- /dev/null +++ b/drivers/net/phy/bcm63xx.c @@ -0,0 +1,124 @@ +/* + * Driver for Broadcom 63xx SOCs integrated PHYs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <linux/module.h> +#include <linux/phy.h> + +#define MII_BCM63XX_IR 0x1a /* interrupt register */ +#define MII_BCM63XX_IR_EN 0x4000 /* global interrupt enable */ +#define MII_BCM63XX_IR_DUPLEX 0x0800 /* duplex changed */ +#define MII_BCM63XX_IR_SPEED 0x0400 /* speed changed */ +#define MII_BCM63XX_IR_LINK 0x0200 /* link changed */ +#define MII_BCM63XX_IR_GMASK 0x0100 /* global interrupt mask */ + +MODULE_DESCRIPTION("Broadcom 63xx internal PHY driver"); +MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>"); +MODULE_LICENSE("GPL"); + +static int bcm63xx_config_init(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, MII_BCM63XX_IR); + if (reg < 0) + return reg; + + /* Mask interrupts globally. */ + reg |= MII_BCM63XX_IR_GMASK; + err = phy_write(phydev, MII_BCM63XX_IR, reg); + if (err < 0) + return err; + + /* Unmask events we are interested in */ + reg = ~(MII_BCM63XX_IR_DUPLEX | + MII_BCM63XX_IR_SPEED | + MII_BCM63XX_IR_LINK) | + MII_BCM63XX_IR_EN; + return phy_write(phydev, MII_BCM63XX_IR, reg); +} + +static int bcm63xx_ack_interrupt(struct phy_device *phydev) +{ + int reg; + + /* Clear pending interrupts. */ + reg = phy_read(phydev, MII_BCM63XX_IR); + if (reg < 0) + return reg; + + return 0; +} + +static int bcm63xx_config_intr(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, MII_BCM63XX_IR); + if (reg < 0) + return reg; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + reg &= ~MII_BCM63XX_IR_GMASK; + else + reg |= MII_BCM63XX_IR_GMASK; + + err = phy_write(phydev, MII_BCM63XX_IR, reg); + return err; +} + +static struct phy_driver bcm63xx_driver[] = { +{ + .phy_id = 0x00406000, + .phy_id_mask = 0xfffffc00, + .name = "Broadcom BCM63XX (1)", + /* ASYM_PAUSE bit is marked RO in datasheet, so don't cheat */ + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_IS_INTERNAL, + .config_init = bcm63xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm63xx_ack_interrupt, + .config_intr = bcm63xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + /* same phy as above, with just a different OUI */ + .phy_id = 0x002bdc00, + .phy_id_mask = 0xfffffc00, + .name = "Broadcom BCM63XX (2)", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_IS_INTERNAL, + .config_init = bcm63xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm63xx_ack_interrupt, + .config_intr = bcm63xx_config_intr, + .driver = { .owner = THIS_MODULE }, +} }; + +static int __init bcm63xx_phy_init(void) +{ + return phy_drivers_register(bcm63xx_driver, + ARRAY_SIZE(bcm63xx_driver)); +} + +static void __exit bcm63xx_phy_exit(void) +{ + phy_drivers_unregister(bcm63xx_driver, + ARRAY_SIZE(bcm63xx_driver)); +} + +module_init(bcm63xx_phy_init); +module_exit(bcm63xx_phy_exit); + +static struct mdio_device_id __maybe_unused bcm63xx_tbl[] = { + { 0x00406000, 0xfffffc00 }, + { 0x002bdc00, 0xfffffc00 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, bcm63xx_tbl); diff --git a/drivers/net/phy/bcm7xxx.c b/drivers/net/phy/bcm7xxx.c new file mode 100644 index 00000000000..526b94cea56 --- /dev/null +++ b/drivers/net/phy/bcm7xxx.c @@ -0,0 +1,359 @@ +/* + * Broadcom BCM7xxx internal transceivers support. + * + * Copyright (C) 2014, Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/brcmphy.h> + +/* Broadcom BCM7xxx internal PHY registers */ +#define MII_BCM7XXX_CHANNEL_WIDTH 0x2000 + +/* 40nm only register definitions */ +#define MII_BCM7XXX_100TX_AUX_CTL 0x10 +#define MII_BCM7XXX_100TX_FALSE_CAR 0x13 +#define MII_BCM7XXX_100TX_DISC 0x14 +#define MII_BCM7XXX_AUX_MODE 0x1d +#define MII_BCM7XX_64CLK_MDIO BIT(12) +#define MII_BCM7XXX_CORE_BASE1E 0x1e +#define MII_BCM7XXX_TEST 0x1f +#define MII_BCM7XXX_SHD_MODE_2 BIT(2) + +/* 28nm only register definitions */ +#define MISC_ADDR(base, channel) base, channel + +#define DSP_TAP10 MISC_ADDR(0x0a, 0) +#define PLL_PLLCTRL_1 MISC_ADDR(0x32, 1) +#define PLL_PLLCTRL_2 MISC_ADDR(0x32, 2) +#define PLL_PLLCTRL_4 MISC_ADDR(0x33, 0) + +#define AFE_RXCONFIG_0 MISC_ADDR(0x38, 0) +#define AFE_RXCONFIG_1 MISC_ADDR(0x38, 1) +#define AFE_RX_LP_COUNTER MISC_ADDR(0x38, 3) +#define AFE_TX_CONFIG MISC_ADDR(0x39, 0) +#define AFE_HPF_TRIM_OTHERS MISC_ADDR(0x3a, 0) + +#define CORE_EXPB0 0xb0 + +static int bcm7445_config_init(struct phy_device *phydev) +{ + int ret; + const struct bcm7445_regs { + int reg; + u16 value; + } bcm7445_regs_cfg[] = { + /* increases ADC latency by 24ns */ + { MII_BCM54XX_EXP_SEL, 0x0038 }, + { MII_BCM54XX_EXP_DATA, 0xAB95 }, + /* increases internal 1V LDO voltage by 5% */ + { MII_BCM54XX_EXP_SEL, 0x2038 }, + { MII_BCM54XX_EXP_DATA, 0xBB22 }, + /* reduce RX low pass filter corner frequency */ + { MII_BCM54XX_EXP_SEL, 0x6038 }, + { MII_BCM54XX_EXP_DATA, 0xFFC5 }, + /* reduce RX high pass filter corner frequency */ + { MII_BCM54XX_EXP_SEL, 0x003a }, + { MII_BCM54XX_EXP_DATA, 0x2002 }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bcm7445_regs_cfg); i++) { + ret = phy_write(phydev, + bcm7445_regs_cfg[i].reg, + bcm7445_regs_cfg[i].value); + if (ret) + return ret; + } + + return 0; +} + +static void phy_write_exp(struct phy_device *phydev, + u16 reg, u16 value) +{ + phy_write(phydev, MII_BCM54XX_EXP_SEL, MII_BCM54XX_EXP_SEL_ER | reg); + phy_write(phydev, MII_BCM54XX_EXP_DATA, value); +} + +static void phy_write_misc(struct phy_device *phydev, + u16 reg, u16 chl, u16 value) +{ + int tmp; + + phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); + + tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); + tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; + phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp); + + tmp = (chl * MII_BCM7XXX_CHANNEL_WIDTH) | reg; + phy_write(phydev, MII_BCM54XX_EXP_SEL, tmp); + + phy_write(phydev, MII_BCM54XX_EXP_DATA, value); +} + +static int bcm7xxx_28nm_afe_config_init(struct phy_device *phydev) +{ + /* Increase VCO range to prevent unlocking problem of PLL at low + * temp + */ + phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048); + + /* Change Ki to 011 */ + phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b); + + /* Disable loading of TVCO buffer to bandgap, set bandgap trim + * to 111 + */ + phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20); + + /* Adjust bias current trim by -3 */ + phy_write_misc(phydev, DSP_TAP10, 0x690b); + + /* Switch to CORE_BASE1E */ + phy_write(phydev, MII_BCM7XXX_CORE_BASE1E, 0xd); + + /* Reset R_CAL/RC_CAL Engine */ + phy_write_exp(phydev, CORE_EXPB0, 0x0010); + + /* Disable Reset R_CAL/RC_CAL Engine */ + phy_write_exp(phydev, CORE_EXPB0, 0x0000); + + /* write AFE_RXCONFIG_0 */ + phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19); + + /* write AFE_RXCONFIG_1 */ + phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f); + + /* write AFE_RX_LP_COUNTER */ + phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0); + + /* write AFE_HPF_TRIM_OTHERS */ + phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b); + + /* write AFTE_TX_CONFIG */ + phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800); + + return 0; +} + +static int bcm7xxx_28nm_config_init(struct phy_device *phydev) +{ + int ret; + + ret = bcm7445_config_init(phydev); + if (ret) + return ret; + + return bcm7xxx_28nm_afe_config_init(phydev); +} + +static int phy_set_clr_bits(struct phy_device *dev, int location, + int set_mask, int clr_mask) +{ + int v, ret; + + v = phy_read(dev, location); + if (v < 0) + return v; + + v &= ~clr_mask; + v |= set_mask; + + ret = phy_write(dev, location, v); + if (ret < 0) + return ret; + + return v; +} + +static int bcm7xxx_config_init(struct phy_device *phydev) +{ + int ret; + + /* Enable 64 clock MDIO */ + phy_write(phydev, MII_BCM7XXX_AUX_MODE, MII_BCM7XX_64CLK_MDIO); + phy_read(phydev, MII_BCM7XXX_AUX_MODE); + + /* Workaround only required for 100Mbits/sec */ + if (!(phydev->dev_flags & PHY_BRCM_100MBPS_WAR)) + return 0; + + /* set shadow mode 2 */ + ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, + MII_BCM7XXX_SHD_MODE_2, MII_BCM7XXX_SHD_MODE_2); + if (ret < 0) + return ret; + + /* set iddq_clkbias */ + phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0F00); + udelay(10); + + /* reset iddq_clkbias */ + phy_write(phydev, MII_BCM7XXX_100TX_DISC, 0x0C00); + + phy_write(phydev, MII_BCM7XXX_100TX_FALSE_CAR, 0x7555); + + /* reset shadow mode 2 */ + ret = phy_set_clr_bits(phydev, MII_BCM7XXX_TEST, MII_BCM7XXX_SHD_MODE_2, 0); + if (ret < 0) + return ret; + + return 0; +} + +/* Workaround for putting the PHY in IDDQ mode, required + * for all BCM7XXX PHYs + */ +static int bcm7xxx_suspend(struct phy_device *phydev) +{ + int ret; + const struct bcm7xxx_regs { + int reg; + u16 value; + } bcm7xxx_suspend_cfg[] = { + { MII_BCM7XXX_TEST, 0x008b }, + { MII_BCM7XXX_100TX_AUX_CTL, 0x01c0 }, + { MII_BCM7XXX_100TX_DISC, 0x7000 }, + { MII_BCM7XXX_TEST, 0x000f }, + { MII_BCM7XXX_100TX_AUX_CTL, 0x20d0 }, + { MII_BCM7XXX_TEST, 0x000b }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bcm7xxx_suspend_cfg); i++) { + ret = phy_write(phydev, + bcm7xxx_suspend_cfg[i].reg, + bcm7xxx_suspend_cfg[i].value); + if (ret) + return ret; + } + + return 0; +} + +static int bcm7xxx_dummy_config_init(struct phy_device *phydev) +{ + return 0; +} + +static struct phy_driver bcm7xxx_driver[] = { +{ + .phy_id = PHY_ID_BCM7366, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM7366", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_IS_INTERNAL, + .config_init = bcm7xxx_28nm_afe_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = bcm7xxx_suspend, + .resume = bcm7xxx_28nm_afe_config_init, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM7439, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM7439", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_IS_INTERNAL, + .config_init = bcm7xxx_28nm_afe_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = bcm7xxx_suspend, + .resume = bcm7xxx_28nm_afe_config_init, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM7445, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM7445", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_IS_INTERNAL, + .config_init = bcm7xxx_28nm_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = bcm7xxx_suspend, + .resume = bcm7xxx_28nm_config_init, + .driver = { .owner = THIS_MODULE }, +}, { + .name = "Broadcom BCM7XXX 28nm", + .phy_id = PHY_ID_BCM7XXX_28, + .phy_id_mask = PHY_BCM_OUI_MASK, + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_IS_INTERNAL, + .config_init = bcm7xxx_28nm_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = bcm7xxx_suspend, + .resume = bcm7xxx_28nm_config_init, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_BCM_OUI_4, + .phy_id_mask = 0xffff0000, + .name = "Broadcom BCM7XXX 40nm", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_IS_INTERNAL, + .config_init = bcm7xxx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = bcm7xxx_suspend, + .resume = bcm7xxx_config_init, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_BCM_OUI_5, + .phy_id_mask = 0xffffff00, + .name = "Broadcom BCM7XXX 65nm", + .features = PHY_BASIC_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_IS_INTERNAL, + .config_init = bcm7xxx_dummy_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = bcm7xxx_suspend, + .resume = bcm7xxx_config_init, + .driver = { .owner = THIS_MODULE }, +} }; + +static struct mdio_device_id __maybe_unused bcm7xxx_tbl[] = { + { PHY_ID_BCM7366, 0xfffffff0, }, + { PHY_ID_BCM7439, 0xfffffff0, }, + { PHY_ID_BCM7445, 0xfffffff0, }, + { PHY_ID_BCM7XXX_28, 0xfffffc00 }, + { PHY_BCM_OUI_4, 0xffff0000 }, + { PHY_BCM_OUI_5, 0xffffff00 }, + { } +}; + +static int __init bcm7xxx_phy_init(void) +{ + return phy_drivers_register(bcm7xxx_driver, + ARRAY_SIZE(bcm7xxx_driver)); +} + +static void __exit bcm7xxx_phy_exit(void) +{ + phy_drivers_unregister(bcm7xxx_driver, + ARRAY_SIZE(bcm7xxx_driver)); +} + +module_init(bcm7xxx_phy_init); +module_exit(bcm7xxx_phy_exit); + +MODULE_DEVICE_TABLE(mdio, bcm7xxx_tbl); + +MODULE_DESCRIPTION("Broadcom BCM7xxx internal PHY driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Broadcom Corporation"); diff --git a/drivers/net/phy/bcm87xx.c b/drivers/net/phy/bcm87xx.c new file mode 100644 index 00000000000..799789518e8 --- /dev/null +++ b/drivers/net/phy/bcm87xx.c @@ -0,0 +1,233 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2011 - 2012 Cavium, Inc. + */ + +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/of.h> + +#define PHY_ID_BCM8706 0x0143bdc1 +#define PHY_ID_BCM8727 0x0143bff0 + +#define BCM87XX_PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x1000a) +#define BCM87XX_10GBASER_PCS_STATUS (MII_ADDR_C45 | 0x30020) +#define BCM87XX_XGXS_LANE_STATUS (MII_ADDR_C45 | 0x40018) + +#define BCM87XX_LASI_CONTROL (MII_ADDR_C45 | 0x39002) +#define BCM87XX_LASI_STATUS (MII_ADDR_C45 | 0x39005) + +#if IS_ENABLED(CONFIG_OF_MDIO) +/* Set and/or override some configuration registers based on the + * broadcom,c45-reg-init property stored in the of_node for the phydev. + * + * broadcom,c45-reg-init = <devid reg mask value>,...; + * + * There may be one or more sets of <devid reg mask value>: + * + * devid: which sub-device to use. + * reg: the register. + * mask: if non-zero, ANDed with existing register value. + * value: ORed with the masked value and written to the regiser. + * + */ +static int bcm87xx_of_reg_init(struct phy_device *phydev) +{ + const __be32 *paddr; + const __be32 *paddr_end; + int len, ret; + + if (!phydev->dev.of_node) + return 0; + + paddr = of_get_property(phydev->dev.of_node, + "broadcom,c45-reg-init", &len); + if (!paddr) + return 0; + + paddr_end = paddr + (len /= sizeof(*paddr)); + + ret = 0; + + while (paddr + 3 < paddr_end) { + u16 devid = be32_to_cpup(paddr++); + u16 reg = be32_to_cpup(paddr++); + u16 mask = be32_to_cpup(paddr++); + u16 val_bits = be32_to_cpup(paddr++); + int val; + u32 regnum = MII_ADDR_C45 | (devid << 16) | reg; + val = 0; + if (mask) { + val = phy_read(phydev, regnum); + if (val < 0) { + ret = val; + goto err; + } + val &= mask; + } + val |= val_bits; + + ret = phy_write(phydev, regnum, val); + if (ret < 0) + goto err; + } +err: + return ret; +} +#else +static int bcm87xx_of_reg_init(struct phy_device *phydev) +{ + return 0; +} +#endif /* CONFIG_OF_MDIO */ + +static int bcm87xx_config_init(struct phy_device *phydev) +{ + phydev->supported = SUPPORTED_10000baseR_FEC; + phydev->advertising = ADVERTISED_10000baseR_FEC; + phydev->state = PHY_NOLINK; + phydev->autoneg = AUTONEG_DISABLE; + + bcm87xx_of_reg_init(phydev); + + return 0; +} + +static int bcm87xx_config_aneg(struct phy_device *phydev) +{ + return -EINVAL; +} + +static int bcm87xx_read_status(struct phy_device *phydev) +{ + int rx_signal_detect; + int pcs_status; + int xgxs_lane_status; + + rx_signal_detect = phy_read(phydev, BCM87XX_PMD_RX_SIGNAL_DETECT); + if (rx_signal_detect < 0) + return rx_signal_detect; + + if ((rx_signal_detect & 1) == 0) + goto no_link; + + pcs_status = phy_read(phydev, BCM87XX_10GBASER_PCS_STATUS); + if (pcs_status < 0) + return pcs_status; + + if ((pcs_status & 1) == 0) + goto no_link; + + xgxs_lane_status = phy_read(phydev, BCM87XX_XGXS_LANE_STATUS); + if (xgxs_lane_status < 0) + return xgxs_lane_status; + + if ((xgxs_lane_status & 0x1000) == 0) + goto no_link; + + phydev->speed = 10000; + phydev->link = 1; + phydev->duplex = 1; + return 0; + +no_link: + phydev->link = 0; + return 0; +} + +static int bcm87xx_config_intr(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, BCM87XX_LASI_CONTROL); + + if (reg < 0) + return reg; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + reg |= 1; + else + reg &= ~1; + + err = phy_write(phydev, BCM87XX_LASI_CONTROL, reg); + return err; +} + +static int bcm87xx_did_interrupt(struct phy_device *phydev) +{ + int reg; + + reg = phy_read(phydev, BCM87XX_LASI_STATUS); + + if (reg < 0) { + dev_err(&phydev->dev, + "Error: Read of BCM87XX_LASI_STATUS failed: %d\n", reg); + return 0; + } + return (reg & 1) != 0; +} + +static int bcm87xx_ack_interrupt(struct phy_device *phydev) +{ + /* Reading the LASI status clears it. */ + bcm87xx_did_interrupt(phydev); + return 0; +} + +static int bcm8706_match_phy_device(struct phy_device *phydev) +{ + return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706; +} + +static int bcm8727_match_phy_device(struct phy_device *phydev) +{ + return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727; +} + +static struct phy_driver bcm87xx_driver[] = { +{ + .phy_id = PHY_ID_BCM8706, + .phy_id_mask = 0xffffffff, + .name = "Broadcom BCM8706", + .flags = PHY_HAS_INTERRUPT, + .config_init = bcm87xx_config_init, + .config_aneg = bcm87xx_config_aneg, + .read_status = bcm87xx_read_status, + .ack_interrupt = bcm87xx_ack_interrupt, + .config_intr = bcm87xx_config_intr, + .did_interrupt = bcm87xx_did_interrupt, + .match_phy_device = bcm8706_match_phy_device, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM8727, + .phy_id_mask = 0xffffffff, + .name = "Broadcom BCM8727", + .flags = PHY_HAS_INTERRUPT, + .config_init = bcm87xx_config_init, + .config_aneg = bcm87xx_config_aneg, + .read_status = bcm87xx_read_status, + .ack_interrupt = bcm87xx_ack_interrupt, + .config_intr = bcm87xx_config_intr, + .did_interrupt = bcm87xx_did_interrupt, + .match_phy_device = bcm8727_match_phy_device, + .driver = { .owner = THIS_MODULE }, +} }; + +static int __init bcm87xx_init(void) +{ + return phy_drivers_register(bcm87xx_driver, + ARRAY_SIZE(bcm87xx_driver)); +} +module_init(bcm87xx_init); + +static void __exit bcm87xx_exit(void) +{ + phy_drivers_unregister(bcm87xx_driver, + ARRAY_SIZE(bcm87xx_driver)); +} +module_exit(bcm87xx_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c new file mode 100644 index 00000000000..34088d60da7 --- /dev/null +++ b/drivers/net/phy/broadcom.c @@ -0,0 +1,809 @@ +/* + * drivers/net/phy/broadcom.c + * + * Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet + * transceivers. + * + * Copyright (c) 2006 Maciej W. Rozycki + * + * Inspired by code written by Amy Fong. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/brcmphy.h> + + +#define BRCM_PHY_MODEL(phydev) \ + ((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask) + +#define BRCM_PHY_REV(phydev) \ + ((phydev)->drv->phy_id & ~((phydev)->drv->phy_id_mask)) + +/* + * Broadcom LED source encodings. These are used in BCM5461, BCM5481, + * BCM5482, and possibly some others. + */ +#define BCM_LED_SRC_LINKSPD1 0x0 +#define BCM_LED_SRC_LINKSPD2 0x1 +#define BCM_LED_SRC_XMITLED 0x2 +#define BCM_LED_SRC_ACTIVITYLED 0x3 +#define BCM_LED_SRC_FDXLED 0x4 +#define BCM_LED_SRC_SLAVE 0x5 +#define BCM_LED_SRC_INTR 0x6 +#define BCM_LED_SRC_QUALITY 0x7 +#define BCM_LED_SRC_RCVLED 0x8 +#define BCM_LED_SRC_MULTICOLOR1 0xa +#define BCM_LED_SRC_OPENSHORT 0xb +#define BCM_LED_SRC_OFF 0xe /* Tied high */ +#define BCM_LED_SRC_ON 0xf /* Tied low */ + + +/* + * BCM5482: Shadow registers + * Shadow values go into bits [14:10] of register 0x1c to select a shadow + * register to access. + */ +/* 00101: Spare Control Register 3 */ +#define BCM54XX_SHD_SCR3 0x05 +#define BCM54XX_SHD_SCR3_DEF_CLK125 0x0001 +#define BCM54XX_SHD_SCR3_DLLAPD_DIS 0x0002 +#define BCM54XX_SHD_SCR3_TRDDAPD 0x0004 + +/* 01010: Auto Power-Down */ +#define BCM54XX_SHD_APD 0x0a +#define BCM54XX_SHD_APD_EN 0x0020 + +#define BCM5482_SHD_LEDS1 0x0d /* 01101: LED Selector 1 */ + /* LED3 / ~LINKSPD[2] selector */ +#define BCM5482_SHD_LEDS1_LED3(src) ((src & 0xf) << 4) + /* LED1 / ~LINKSPD[1] selector */ +#define BCM5482_SHD_LEDS1_LED1(src) ((src & 0xf) << 0) +#define BCM54XX_SHD_RGMII_MODE 0x0b /* 01011: RGMII Mode Selector */ +#define BCM5482_SHD_SSD 0x14 /* 10100: Secondary SerDes control */ +#define BCM5482_SHD_SSD_LEDM 0x0008 /* SSD LED Mode enable */ +#define BCM5482_SHD_SSD_EN 0x0001 /* SSD enable */ +#define BCM5482_SHD_MODE 0x1f /* 11111: Mode Control Register */ +#define BCM5482_SHD_MODE_1000BX 0x0001 /* Enable 1000BASE-X registers */ + + +/* + * EXPANSION SHADOW ACCESS REGISTERS. (PHY REG 0x15, 0x16, and 0x17) + */ +#define MII_BCM54XX_EXP_AADJ1CH0 0x001f +#define MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN 0x0200 +#define MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF 0x0100 +#define MII_BCM54XX_EXP_AADJ1CH3 0x601f +#define MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ 0x0002 +#define MII_BCM54XX_EXP_EXP08 0x0F08 +#define MII_BCM54XX_EXP_EXP08_RJCT_2MHZ 0x0001 +#define MII_BCM54XX_EXP_EXP08_EARLY_DAC_WAKE 0x0200 +#define MII_BCM54XX_EXP_EXP75 0x0f75 +#define MII_BCM54XX_EXP_EXP75_VDACCTRL 0x003c +#define MII_BCM54XX_EXP_EXP75_CM_OSC 0x0001 +#define MII_BCM54XX_EXP_EXP96 0x0f96 +#define MII_BCM54XX_EXP_EXP96_MYST 0x0010 +#define MII_BCM54XX_EXP_EXP97 0x0f97 +#define MII_BCM54XX_EXP_EXP97_MYST 0x0c0c + +/* + * BCM5482: Secondary SerDes registers + */ +#define BCM5482_SSD_1000BX_CTL 0x00 /* 1000BASE-X Control */ +#define BCM5482_SSD_1000BX_CTL_PWRDOWN 0x0800 /* Power-down SSD */ +#define BCM5482_SSD_SGMII_SLAVE 0x15 /* SGMII Slave Register */ +#define BCM5482_SSD_SGMII_SLAVE_EN 0x0002 /* Slave mode enable */ +#define BCM5482_SSD_SGMII_SLAVE_AD 0x0001 /* Slave auto-detection */ + + +/*****************************************************************************/ +/* Fast Ethernet Transceiver definitions. */ +/*****************************************************************************/ + +#define MII_BRCM_FET_INTREG 0x1a /* Interrupt register */ +#define MII_BRCM_FET_IR_MASK 0x0100 /* Mask all interrupts */ +#define MII_BRCM_FET_IR_LINK_EN 0x0200 /* Link status change enable */ +#define MII_BRCM_FET_IR_SPEED_EN 0x0400 /* Link speed change enable */ +#define MII_BRCM_FET_IR_DUPLEX_EN 0x0800 /* Duplex mode change enable */ +#define MII_BRCM_FET_IR_ENABLE 0x4000 /* Interrupt enable */ + +#define MII_BRCM_FET_BRCMTEST 0x1f /* Brcm test register */ +#define MII_BRCM_FET_BT_SRE 0x0080 /* Shadow register enable */ + + +/*** Shadow register definitions ***/ + +#define MII_BRCM_FET_SHDW_MISCCTRL 0x10 /* Shadow misc ctrl */ +#define MII_BRCM_FET_SHDW_MC_FAME 0x4000 /* Force Auto MDIX enable */ + +#define MII_BRCM_FET_SHDW_AUXMODE4 0x1a /* Auxiliary mode 4 */ +#define MII_BRCM_FET_SHDW_AM4_LED_MASK 0x0003 +#define MII_BRCM_FET_SHDW_AM4_LED_MODE1 0x0001 + +#define MII_BRCM_FET_SHDW_AUXSTAT2 0x1b /* Auxiliary status 2 */ +#define MII_BRCM_FET_SHDW_AS2_APDE 0x0020 /* Auto power down enable */ + + +MODULE_DESCRIPTION("Broadcom PHY driver"); +MODULE_AUTHOR("Maciej W. Rozycki"); +MODULE_LICENSE("GPL"); + +/* + * Indirect register access functions for the 1000BASE-T/100BASE-TX/10BASE-T + * 0x1c shadow registers. + */ +static int bcm54xx_shadow_read(struct phy_device *phydev, u16 shadow) +{ + phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow)); + return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD)); +} + +static int bcm54xx_shadow_write(struct phy_device *phydev, u16 shadow, u16 val) +{ + return phy_write(phydev, MII_BCM54XX_SHD, + MII_BCM54XX_SHD_WRITE | + MII_BCM54XX_SHD_VAL(shadow) | + MII_BCM54XX_SHD_DATA(val)); +} + +/* Indirect register access functions for the Expansion Registers */ +static int bcm54xx_exp_read(struct phy_device *phydev, u16 regnum) +{ + int val; + + val = phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum); + if (val < 0) + return val; + + val = phy_read(phydev, MII_BCM54XX_EXP_DATA); + + /* Restore default value. It's O.K. if this write fails. */ + phy_write(phydev, MII_BCM54XX_EXP_SEL, 0); + + return val; +} + +static int bcm54xx_exp_write(struct phy_device *phydev, u16 regnum, u16 val) +{ + int ret; + + ret = phy_write(phydev, MII_BCM54XX_EXP_SEL, regnum); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MII_BCM54XX_EXP_DATA, val); + + /* Restore default value. It's O.K. if this write fails. */ + phy_write(phydev, MII_BCM54XX_EXP_SEL, 0); + + return ret; +} + +static int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val) +{ + return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val); +} + +/* Needs SMDSP clock enabled via bcm54xx_phydsp_config() */ +static int bcm50610_a0_workaround(struct phy_device *phydev) +{ + int err; + + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_AADJ1CH0, + MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN | + MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF); + if (err < 0) + return err; + + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_AADJ1CH3, + MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ); + if (err < 0) + return err; + + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP75, + MII_BCM54XX_EXP_EXP75_VDACCTRL); + if (err < 0) + return err; + + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP96, + MII_BCM54XX_EXP_EXP96_MYST); + if (err < 0) + return err; + + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP97, + MII_BCM54XX_EXP_EXP97_MYST); + + return err; +} + +static int bcm54xx_phydsp_config(struct phy_device *phydev) +{ + int err, err2; + + /* Enable the SMDSP clock */ + err = bcm54xx_auxctl_write(phydev, + MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL, + MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA | + MII_BCM54XX_AUXCTL_ACTL_TX_6DB); + if (err < 0) + return err; + + if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 || + BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) { + /* Clear bit 9 to fix a phy interop issue. */ + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP08, + MII_BCM54XX_EXP_EXP08_RJCT_2MHZ); + if (err < 0) + goto error; + + if (phydev->drv->phy_id == PHY_ID_BCM50610) { + err = bcm50610_a0_workaround(phydev); + if (err < 0) + goto error; + } + } + + if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM57780) { + int val; + + val = bcm54xx_exp_read(phydev, MII_BCM54XX_EXP_EXP75); + if (val < 0) + goto error; + + val |= MII_BCM54XX_EXP_EXP75_CM_OSC; + err = bcm54xx_exp_write(phydev, MII_BCM54XX_EXP_EXP75, val); + } + +error: + /* Disable the SMDSP clock */ + err2 = bcm54xx_auxctl_write(phydev, + MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL, + MII_BCM54XX_AUXCTL_ACTL_TX_6DB); + + /* Return the first error reported. */ + return err ? err : err2; +} + +static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev) +{ + u32 orig; + int val; + bool clk125en = true; + + /* Abort if we are using an untested phy. */ + if (BRCM_PHY_MODEL(phydev) != PHY_ID_BCM57780 && + BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610 && + BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M) + return; + + val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_SCR3); + if (val < 0) + return; + + orig = val; + + if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 || + BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) && + BRCM_PHY_REV(phydev) >= 0x3) { + /* + * Here, bit 0 _disables_ CLK125 when set. + * This bit is set by default. + */ + clk125en = false; + } else { + if (phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) { + /* Here, bit 0 _enables_ CLK125 when set */ + val &= ~BCM54XX_SHD_SCR3_DEF_CLK125; + clk125en = false; + } + } + + if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE)) + val &= ~BCM54XX_SHD_SCR3_DLLAPD_DIS; + else + val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; + + if (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) + val |= BCM54XX_SHD_SCR3_TRDDAPD; + + if (orig != val) + bcm54xx_shadow_write(phydev, BCM54XX_SHD_SCR3, val); + + val = bcm54xx_shadow_read(phydev, BCM54XX_SHD_APD); + if (val < 0) + return; + + orig = val; + + if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE)) + val |= BCM54XX_SHD_APD_EN; + else + val &= ~BCM54XX_SHD_APD_EN; + + if (orig != val) + bcm54xx_shadow_write(phydev, BCM54XX_SHD_APD, val); +} + +static int bcm54xx_config_init(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, MII_BCM54XX_ECR); + if (reg < 0) + return reg; + + /* Mask interrupts globally. */ + reg |= MII_BCM54XX_ECR_IM; + err = phy_write(phydev, MII_BCM54XX_ECR, reg); + if (err < 0) + return err; + + /* Unmask events we are interested in. */ + reg = ~(MII_BCM54XX_INT_DUPLEX | + MII_BCM54XX_INT_SPEED | + MII_BCM54XX_INT_LINK); + err = phy_write(phydev, MII_BCM54XX_IMR, reg); + if (err < 0) + return err; + + if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 || + BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610M) && + (phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE)) + bcm54xx_shadow_write(phydev, BCM54XX_SHD_RGMII_MODE, 0); + + if ((phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) || + (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) || + (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE)) + bcm54xx_adjust_rxrefclk(phydev); + + bcm54xx_phydsp_config(phydev); + + return 0; +} + +static int bcm5482_config_init(struct phy_device *phydev) +{ + int err, reg; + + err = bcm54xx_config_init(phydev); + + if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) { + /* + * Enable secondary SerDes and its use as an LED source + */ + reg = bcm54xx_shadow_read(phydev, BCM5482_SHD_SSD); + bcm54xx_shadow_write(phydev, BCM5482_SHD_SSD, + reg | + BCM5482_SHD_SSD_LEDM | + BCM5482_SHD_SSD_EN); + + /* + * Enable SGMII slave mode and auto-detection + */ + reg = BCM5482_SSD_SGMII_SLAVE | MII_BCM54XX_EXP_SEL_SSD; + err = bcm54xx_exp_read(phydev, reg); + if (err < 0) + return err; + err = bcm54xx_exp_write(phydev, reg, err | + BCM5482_SSD_SGMII_SLAVE_EN | + BCM5482_SSD_SGMII_SLAVE_AD); + if (err < 0) + return err; + + /* + * Disable secondary SerDes powerdown + */ + reg = BCM5482_SSD_1000BX_CTL | MII_BCM54XX_EXP_SEL_SSD; + err = bcm54xx_exp_read(phydev, reg); + if (err < 0) + return err; + err = bcm54xx_exp_write(phydev, reg, + err & ~BCM5482_SSD_1000BX_CTL_PWRDOWN); + if (err < 0) + return err; + + /* + * Select 1000BASE-X register set (primary SerDes) + */ + reg = bcm54xx_shadow_read(phydev, BCM5482_SHD_MODE); + bcm54xx_shadow_write(phydev, BCM5482_SHD_MODE, + reg | BCM5482_SHD_MODE_1000BX); + + /* + * LED1=ACTIVITYLED, LED3=LINKSPD[2] + * (Use LED1 as secondary SerDes ACTIVITY LED) + */ + bcm54xx_shadow_write(phydev, BCM5482_SHD_LEDS1, + BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_ACTIVITYLED) | + BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_LINKSPD2)); + + /* + * Auto-negotiation doesn't seem to work quite right + * in this mode, so we disable it and force it to the + * right speed/duplex setting. Only 'link status' + * is important. + */ + phydev->autoneg = AUTONEG_DISABLE; + phydev->speed = SPEED_1000; + phydev->duplex = DUPLEX_FULL; + } + + return err; +} + +static int bcm5482_read_status(struct phy_device *phydev) +{ + int err; + + err = genphy_read_status(phydev); + + if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) { + /* + * Only link status matters for 1000Base-X mode, so force + * 1000 Mbit/s full-duplex status + */ + if (phydev->link) { + phydev->speed = SPEED_1000; + phydev->duplex = DUPLEX_FULL; + } + } + + return err; +} + +static int bcm54xx_ack_interrupt(struct phy_device *phydev) +{ + int reg; + + /* Clear pending interrupts. */ + reg = phy_read(phydev, MII_BCM54XX_ISR); + if (reg < 0) + return reg; + + return 0; +} + +static int bcm54xx_config_intr(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, MII_BCM54XX_ECR); + if (reg < 0) + return reg; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + reg &= ~MII_BCM54XX_ECR_IM; + else + reg |= MII_BCM54XX_ECR_IM; + + err = phy_write(phydev, MII_BCM54XX_ECR, reg); + return err; +} + +static int bcm5481_config_aneg(struct phy_device *phydev) +{ + int ret; + + /* Aneg firsly. */ + ret = genphy_config_aneg(phydev); + + /* Then we can set up the delay. */ + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { + u16 reg; + + /* + * There is no BCM5481 specification available, so down + * here is everything we know about "register 0x18". This + * at least helps BCM5481 to successfully receive packets + * on MPC8360E-RDK board. Peter Barada <peterb@logicpd.com> + * says: "This sets delay between the RXD and RXC signals + * instead of using trace lengths to achieve timing". + */ + + /* Set RDX clk delay. */ + reg = 0x7 | (0x7 << 12); + phy_write(phydev, 0x18, reg); + + reg = phy_read(phydev, 0x18); + /* Set RDX-RXC skew. */ + reg |= (1 << 8); + /* Write bits 14:0. */ + reg |= (1 << 15); + phy_write(phydev, 0x18, reg); + } + + return ret; +} + +static int brcm_phy_setbits(struct phy_device *phydev, int reg, int set) +{ + int val; + + val = phy_read(phydev, reg); + if (val < 0) + return val; + + return phy_write(phydev, reg, val | set); +} + +static int brcm_fet_config_init(struct phy_device *phydev) +{ + int reg, err, err2, brcmtest; + + /* Reset the PHY to bring it to a known state. */ + err = phy_write(phydev, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + + reg = phy_read(phydev, MII_BRCM_FET_INTREG); + if (reg < 0) + return reg; + + /* Unmask events we are interested in and mask interrupts globally. */ + reg = MII_BRCM_FET_IR_DUPLEX_EN | + MII_BRCM_FET_IR_SPEED_EN | + MII_BRCM_FET_IR_LINK_EN | + MII_BRCM_FET_IR_ENABLE | + MII_BRCM_FET_IR_MASK; + + err = phy_write(phydev, MII_BRCM_FET_INTREG, reg); + if (err < 0) + return err; + + /* Enable shadow register access */ + brcmtest = phy_read(phydev, MII_BRCM_FET_BRCMTEST); + if (brcmtest < 0) + return brcmtest; + + reg = brcmtest | MII_BRCM_FET_BT_SRE; + + err = phy_write(phydev, MII_BRCM_FET_BRCMTEST, reg); + if (err < 0) + return err; + + /* Set the LED mode */ + reg = phy_read(phydev, MII_BRCM_FET_SHDW_AUXMODE4); + if (reg < 0) { + err = reg; + goto done; + } + + reg &= ~MII_BRCM_FET_SHDW_AM4_LED_MASK; + reg |= MII_BRCM_FET_SHDW_AM4_LED_MODE1; + + err = phy_write(phydev, MII_BRCM_FET_SHDW_AUXMODE4, reg); + if (err < 0) + goto done; + + /* Enable auto MDIX */ + err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_MISCCTRL, + MII_BRCM_FET_SHDW_MC_FAME); + if (err < 0) + goto done; + + if (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE) { + /* Enable auto power down */ + err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2, + MII_BRCM_FET_SHDW_AS2_APDE); + } + +done: + /* Disable shadow register access */ + err2 = phy_write(phydev, MII_BRCM_FET_BRCMTEST, brcmtest); + if (!err) + err = err2; + + return err; +} + +static int brcm_fet_ack_interrupt(struct phy_device *phydev) +{ + int reg; + + /* Clear pending interrupts. */ + reg = phy_read(phydev, MII_BRCM_FET_INTREG); + if (reg < 0) + return reg; + + return 0; +} + +static int brcm_fet_config_intr(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, MII_BRCM_FET_INTREG); + if (reg < 0) + return reg; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + reg &= ~MII_BRCM_FET_IR_MASK; + else + reg |= MII_BRCM_FET_IR_MASK; + + err = phy_write(phydev, MII_BRCM_FET_INTREG, reg); + return err; +} + +static struct phy_driver broadcom_drivers[] = { +{ + .phy_id = PHY_ID_BCM5411, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5411", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM5421, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5421", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM5461, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5461", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM5464, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5464", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM5481, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5481", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = bcm5481_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM5482, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5482", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm5482_config_init, + .config_aneg = genphy_config_aneg, + .read_status = bcm5482_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM50610, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM50610", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM50610M, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM50610M", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM57780, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM57780", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54xx_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = bcm54xx_ack_interrupt, + .config_intr = bcm54xx_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCMAC131, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCMAC131", + .features = PHY_BASIC_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = brcm_fet_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = brcm_fet_ack_interrupt, + .config_intr = brcm_fet_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { + .phy_id = PHY_ID_BCM5241, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM5241", + .features = PHY_BASIC_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = brcm_fet_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = brcm_fet_ack_interrupt, + .config_intr = brcm_fet_config_intr, + .driver = { .owner = THIS_MODULE }, +} }; + +static int __init broadcom_init(void) +{ + return phy_drivers_register(broadcom_drivers, + ARRAY_SIZE(broadcom_drivers)); +} + +static void __exit broadcom_exit(void) +{ + phy_drivers_unregister(broadcom_drivers, + ARRAY_SIZE(broadcom_drivers)); +} + +module_init(broadcom_init); +module_exit(broadcom_exit); + +static struct mdio_device_id __maybe_unused broadcom_tbl[] = { + { PHY_ID_BCM5411, 0xfffffff0 }, + { PHY_ID_BCM5421, 0xfffffff0 }, + { PHY_ID_BCM5461, 0xfffffff0 }, + { PHY_ID_BCM5464, 0xfffffff0 }, + { PHY_ID_BCM5482, 0xfffffff0 }, + { PHY_ID_BCM5482, 0xfffffff0 }, + { PHY_ID_BCM50610, 0xfffffff0 }, + { PHY_ID_BCM50610M, 0xfffffff0 }, + { PHY_ID_BCM57780, 0xfffffff0 }, + { PHY_ID_BCMAC131, 0xfffffff0 }, + { PHY_ID_BCM5241, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, broadcom_tbl); diff --git a/drivers/net/phy/cicada.c b/drivers/net/phy/cicada.c index 7d8d534255c..b57ce0cc965 100644 --- a/drivers/net/phy/cicada.c +++ b/drivers/net/phy/cicada.c @@ -13,13 +13,10 @@ * option) any later version. * */ -#include <linux/config.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/slab.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> @@ -33,9 +30,9 @@ #include <linux/ethtool.h> #include <linux/phy.h> -#include <asm/io.h> +#include <linux/io.h> #include <asm/irq.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> /* Cicada Extended Control Register 1 */ #define MII_CIS8201_EXT_CON1 0x17 @@ -95,8 +92,8 @@ static int cis820x_config_intr(struct phy_device *phydev) { int err; - if(phydev->interrupts == PHY_INTERRUPT_ENABLED) - err = phy_write(phydev, MII_CIS8201_IMASK, + 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); @@ -104,8 +101,21 @@ static int cis820x_config_intr(struct phy_device *phydev) return err; } -/* Cicada 820x */ -static struct phy_driver cis8204_driver = { +/* Cicada 8201, a.k.a Vitesse VSC8201 */ +static struct phy_driver cis820x_driver[] = { +{ + .phy_id = 0x000fc410, + .name = "Cicada Cis8201", + .phy_id_mask = 0x000ffff0, + .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,}, +}, { .phy_id = 0x000fc440, .name = "Cicada Cis8204", .phy_id_mask = 0x000fffc0, @@ -116,18 +126,28 @@ static struct phy_driver cis8204_driver = { .read_status = &genphy_read_status, .ack_interrupt = &cis820x_ack_interrupt, .config_intr = &cis820x_config_intr, - .driver = { .owner = THIS_MODULE,}, -}; + .driver = { .owner = THIS_MODULE,}, +} }; -static int __init cis8204_init(void) +static int __init cicada_init(void) { - return phy_driver_register(&cis8204_driver); + return phy_drivers_register(cis820x_driver, + ARRAY_SIZE(cis820x_driver)); } -static void __exit cis8204_exit(void) +static void __exit cicada_exit(void) { - phy_driver_unregister(&cis8204_driver); + phy_drivers_unregister(cis820x_driver, + ARRAY_SIZE(cis820x_driver)); } -module_init(cis8204_init); -module_exit(cis8204_exit); +module_init(cicada_init); +module_exit(cicada_exit); + +static struct mdio_device_id __maybe_unused cicada_tbl[] = { + { 0x000fc410, 0x000ffff0 }, + { 0x000fc440, 0x000fffc0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, cicada_tbl); diff --git a/drivers/net/phy/davicom.c b/drivers/net/phy/davicom.c index 5e9002e444c..d2c08f625a4 100644 --- a/drivers/net/phy/davicom.c +++ b/drivers/net/phy/davicom.c @@ -13,13 +13,10 @@ * option) any later version. * */ -#include <linux/config.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/slab.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> @@ -39,6 +36,7 @@ #define MII_DM9161_SCR 0x10 #define MII_DM9161_SCR_INIT 0x0610 +#define MII_DM9161_SCR_RMII 0x0100 /* DM9161 Interrupt Register */ #define MII_DM9161_INTR 0x15 @@ -74,7 +72,7 @@ static int dm9161_config_intr(struct phy_device *phydev) if (temp < 0) return temp; - if(PHY_INTERRUPT_ENABLED == phydev->interrupts ) + if (PHY_INTERRUPT_ENABLED == phydev->interrupts) temp &= ~(MII_DM9161_INTR_STOP); else temp |= MII_DM9161_INTR_STOP; @@ -105,7 +103,7 @@ static int dm9161_config_aneg(struct phy_device *phydev) static int dm9161_config_init(struct phy_device *phydev) { - int err; + int err, temp; /* Isolate the PHY */ err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE); @@ -113,9 +111,19 @@ static int dm9161_config_init(struct phy_device *phydev) if (err < 0) return err; - /* Do not bypass the scrambler/descrambler */ - err = phy_write(phydev, MII_DM9161_SCR, MII_DM9161_SCR_INIT); + switch (phydev->interface) { + case PHY_INTERFACE_MODE_MII: + temp = MII_DM9161_SCR_INIT; + break; + case PHY_INTERFACE_MODE_RMII: + temp = MII_DM9161_SCR_INIT | MII_DM9161_SCR_RMII; + break; + default: + return -EINVAL; + } + /* Do not bypass the scrambler/descrambler */ + err = phy_write(phydev, MII_DM9161_SCR, temp); if (err < 0) return err; @@ -126,12 +134,7 @@ static int dm9161_config_init(struct phy_device *phydev) return err; /* Reconnect the PHY, and enable Autonegotiation */ - err = phy_write(phydev, MII_BMCR, BMCR_ANENABLE); - - if (err < 0) - return err; - - return 0; + return phy_write(phydev, MII_BMCR, BMCR_ANENABLE); } static int dm9161_ack_interrupt(struct phy_device *phydev) @@ -141,18 +144,32 @@ static int dm9161_ack_interrupt(struct phy_device *phydev) return (err < 0) ? err : 0; } -static struct phy_driver dm9161_driver = { +static struct phy_driver dm91xx_driver[] = { +{ .phy_id = 0x0181b880, .name = "Davicom DM9161E", .phy_id_mask = 0x0ffffff0, .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, .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 = { + .ack_interrupt = dm9161_ack_interrupt, + .config_intr = dm9161_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = 0x0181b8a0, + .name = "Davicom DM9161A", + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = dm9161_config_init, + .config_aneg = dm9161_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = dm9161_ack_interrupt, + .config_intr = dm9161_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { .phy_id = 0x00181b80, .name = "Davicom DM9131", .phy_id_mask = 0x0ffffff0, @@ -162,33 +179,29 @@ static struct phy_driver dm9131_driver = { .read_status = genphy_read_status, .ack_interrupt = dm9161_ack_interrupt, .config_intr = dm9161_config_intr, - .driver = { .owner = THIS_MODULE,}, -}; + .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; + return phy_drivers_register(dm91xx_driver, + ARRAY_SIZE(dm91xx_driver)); } static void __exit davicom_exit(void) { - phy_driver_unregister(&dm9161_driver); - phy_driver_unregister(&dm9131_driver); + phy_drivers_unregister(dm91xx_driver, + ARRAY_SIZE(dm91xx_driver)); } module_init(davicom_init); module_exit(davicom_exit); + +static struct mdio_device_id __maybe_unused davicom_tbl[] = { + { 0x0181b880, 0x0ffffff0 }, + { 0x0181b8a0, 0x0ffffff0 }, + { 0x00181b80, 0x0ffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, davicom_tbl); diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c new file mode 100644 index 00000000000..9408157a246 --- /dev/null +++ b/drivers/net/phy/dp83640.c @@ -0,0 +1,1441 @@ +/* + * Driver for the National Semiconductor DP83640 PHYTER + * + * Copyright (C) 2010 OMICRON electronics GmbH + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/ethtool.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/net_tstamp.h> +#include <linux/netdevice.h> +#include <linux/if_vlan.h> +#include <linux/phy.h> +#include <linux/ptp_classify.h> +#include <linux/ptp_clock_kernel.h> + +#include "dp83640_reg.h" + +#define DP83640_PHY_ID 0x20005ce1 +#define PAGESEL 0x13 +#define LAYER4 0x02 +#define LAYER2 0x01 +#define MAX_RXTS 64 +#define N_EXT_TS 6 +#define PSF_PTPVER 2 +#define PSF_EVNT 0x4000 +#define PSF_RX 0x2000 +#define PSF_TX 0x1000 +#define EXT_EVENT 1 +#define CAL_EVENT 7 +#define CAL_TRIGGER 7 +#define PER_TRIGGER 6 +#define DP83640_N_PINS 12 + +#define MII_DP83640_MICR 0x11 +#define MII_DP83640_MISR 0x12 + +#define MII_DP83640_MICR_OE 0x1 +#define MII_DP83640_MICR_IE 0x2 + +#define MII_DP83640_MISR_RHF_INT_EN 0x01 +#define MII_DP83640_MISR_FHF_INT_EN 0x02 +#define MII_DP83640_MISR_ANC_INT_EN 0x04 +#define MII_DP83640_MISR_DUP_INT_EN 0x08 +#define MII_DP83640_MISR_SPD_INT_EN 0x10 +#define MII_DP83640_MISR_LINK_INT_EN 0x20 +#define MII_DP83640_MISR_ED_INT_EN 0x40 +#define MII_DP83640_MISR_LQ_INT_EN 0x80 + +/* phyter seems to miss the mark by 16 ns */ +#define ADJTIME_FIX 16 + +#if defined(__BIG_ENDIAN) +#define ENDIAN_FLAG 0 +#elif defined(__LITTLE_ENDIAN) +#define ENDIAN_FLAG PSF_ENDIAN +#endif + +#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb)) + +struct phy_rxts { + u16 ns_lo; /* ns[15:0] */ + u16 ns_hi; /* overflow[1:0], ns[29:16] */ + u16 sec_lo; /* sec[15:0] */ + u16 sec_hi; /* sec[31:16] */ + u16 seqid; /* sequenceId[15:0] */ + u16 msgtype; /* messageType[3:0], hash[11:0] */ +}; + +struct phy_txts { + u16 ns_lo; /* ns[15:0] */ + u16 ns_hi; /* overflow[1:0], ns[29:16] */ + u16 sec_lo; /* sec[15:0] */ + u16 sec_hi; /* sec[31:16] */ +}; + +struct rxts { + struct list_head list; + unsigned long tmo; + u64 ns; + u16 seqid; + u8 msgtype; + u16 hash; +}; + +struct dp83640_clock; + +struct dp83640_private { + struct list_head list; + struct dp83640_clock *clock; + struct phy_device *phydev; + struct work_struct ts_work; + int hwts_tx_en; + int hwts_rx_en; + int layer; + int version; + /* remember state of cfg0 during calibration */ + int cfg0; + /* remember the last event time stamp */ + struct phy_txts edata; + /* list of rx timestamps */ + struct list_head rxts; + struct list_head rxpool; + struct rxts rx_pool_data[MAX_RXTS]; + /* protects above three fields from concurrent access */ + spinlock_t rx_lock; + /* queues of incoming and outgoing packets */ + struct sk_buff_head rx_queue; + struct sk_buff_head tx_queue; +}; + +struct dp83640_clock { + /* keeps the instance in the 'phyter_clocks' list */ + struct list_head list; + /* we create one clock instance per MII bus */ + struct mii_bus *bus; + /* protects extended registers from concurrent access */ + struct mutex extreg_lock; + /* remembers which page was last selected */ + int page; + /* our advertised capabilities */ + struct ptp_clock_info caps; + /* protects the three fields below from concurrent access */ + struct mutex clock_lock; + /* the one phyter from which we shall read */ + struct dp83640_private *chosen; + /* list of the other attached phyters, not chosen */ + struct list_head phylist; + /* reference to our PTP hardware clock */ + struct ptp_clock *ptp_clock; +}; + +/* globals */ + +enum { + CALIBRATE_GPIO, + PEROUT_GPIO, + EXTTS0_GPIO, + EXTTS1_GPIO, + EXTTS2_GPIO, + EXTTS3_GPIO, + EXTTS4_GPIO, + EXTTS5_GPIO, + GPIO_TABLE_SIZE +}; + +static int chosen_phy = -1; +static ushort gpio_tab[GPIO_TABLE_SIZE] = { + 1, 2, 3, 4, 8, 9, 10, 11 +}; + +module_param(chosen_phy, int, 0444); +module_param_array(gpio_tab, ushort, NULL, 0444); + +MODULE_PARM_DESC(chosen_phy, \ + "The address of the PHY to use for the ancillary clock features"); +MODULE_PARM_DESC(gpio_tab, \ + "Which GPIO line to use for which purpose: cal,perout,extts1,...,extts6"); + +static void dp83640_gpio_defaults(struct ptp_pin_desc *pd) +{ + int i, index; + + for (i = 0; i < DP83640_N_PINS; i++) { + snprintf(pd[i].name, sizeof(pd[i].name), "GPIO%d", 1 + i); + pd[i].index = i; + } + + for (i = 0; i < GPIO_TABLE_SIZE; i++) { + if (gpio_tab[i] < 1 || gpio_tab[i] > DP83640_N_PINS) { + pr_err("gpio_tab[%d]=%hu out of range", i, gpio_tab[i]); + return; + } + } + + index = gpio_tab[CALIBRATE_GPIO] - 1; + pd[index].func = PTP_PF_PHYSYNC; + pd[index].chan = 0; + + index = gpio_tab[PEROUT_GPIO] - 1; + pd[index].func = PTP_PF_PEROUT; + pd[index].chan = 0; + + for (i = EXTTS0_GPIO; i < GPIO_TABLE_SIZE; i++) { + index = gpio_tab[i] - 1; + pd[index].func = PTP_PF_EXTTS; + pd[index].chan = i - EXTTS0_GPIO; + } +} + +/* a list of clocks and a mutex to protect it */ +static LIST_HEAD(phyter_clocks); +static DEFINE_MUTEX(phyter_clocks_lock); + +static void rx_timestamp_work(struct work_struct *work); + +/* extended register access functions */ + +#define BROADCAST_ADDR 31 + +static inline int broadcast_write(struct mii_bus *bus, u32 regnum, u16 val) +{ + return mdiobus_write(bus, BROADCAST_ADDR, regnum, val); +} + +/* Caller must hold extreg_lock. */ +static int ext_read(struct phy_device *phydev, int page, u32 regnum) +{ + struct dp83640_private *dp83640 = phydev->priv; + int val; + + if (dp83640->clock->page != page) { + broadcast_write(phydev->bus, PAGESEL, page); + dp83640->clock->page = page; + } + val = phy_read(phydev, regnum); + + return val; +} + +/* Caller must hold extreg_lock. */ +static void ext_write(int broadcast, struct phy_device *phydev, + int page, u32 regnum, u16 val) +{ + struct dp83640_private *dp83640 = phydev->priv; + + if (dp83640->clock->page != page) { + broadcast_write(phydev->bus, PAGESEL, page); + dp83640->clock->page = page; + } + if (broadcast) + broadcast_write(phydev->bus, regnum, val); + else + phy_write(phydev, regnum, val); +} + +/* Caller must hold extreg_lock. */ +static int tdr_write(int bc, struct phy_device *dev, + const struct timespec *ts, u16 cmd) +{ + ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_nsec & 0xffff);/* ns[15:0] */ + ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_nsec >> 16); /* ns[31:16] */ + ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_sec & 0xffff); /* sec[15:0] */ + ext_write(bc, dev, PAGE4, PTP_TDR, ts->tv_sec >> 16); /* sec[31:16]*/ + + ext_write(bc, dev, PAGE4, PTP_CTL, cmd); + + return 0; +} + +/* convert phy timestamps into driver timestamps */ + +static void phy2rxts(struct phy_rxts *p, struct rxts *rxts) +{ + u32 sec; + + sec = p->sec_lo; + sec |= p->sec_hi << 16; + + rxts->ns = p->ns_lo; + rxts->ns |= (p->ns_hi & 0x3fff) << 16; + rxts->ns += ((u64)sec) * 1000000000ULL; + rxts->seqid = p->seqid; + rxts->msgtype = (p->msgtype >> 12) & 0xf; + rxts->hash = p->msgtype & 0x0fff; + rxts->tmo = jiffies + 2; +} + +static u64 phy2txts(struct phy_txts *p) +{ + u64 ns; + u32 sec; + + sec = p->sec_lo; + sec |= p->sec_hi << 16; + + ns = p->ns_lo; + ns |= (p->ns_hi & 0x3fff) << 16; + ns += ((u64)sec) * 1000000000ULL; + + return ns; +} + +static int periodic_output(struct dp83640_clock *clock, + struct ptp_clock_request *clkreq, bool on) +{ + struct dp83640_private *dp83640 = clock->chosen; + struct phy_device *phydev = dp83640->phydev; + u32 sec, nsec, pwidth; + u16 gpio, ptp_trig, trigger, val; + + if (on) { + gpio = 1 + ptp_find_pin(clock->ptp_clock, PTP_PF_PEROUT, 0); + if (gpio < 1) + return -EINVAL; + } else { + gpio = 0; + } + + trigger = PER_TRIGGER; + + ptp_trig = TRIG_WR | + (trigger & TRIG_CSEL_MASK) << TRIG_CSEL_SHIFT | + (gpio & TRIG_GPIO_MASK) << TRIG_GPIO_SHIFT | + TRIG_PER | + TRIG_PULSE; + + val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT; + + if (!on) { + val |= TRIG_DIS; + mutex_lock(&clock->extreg_lock); + ext_write(0, phydev, PAGE5, PTP_TRIG, ptp_trig); + ext_write(0, phydev, PAGE4, PTP_CTL, val); + mutex_unlock(&clock->extreg_lock); + return 0; + } + + sec = clkreq->perout.start.sec; + nsec = clkreq->perout.start.nsec; + pwidth = clkreq->perout.period.sec * 1000000000UL; + pwidth += clkreq->perout.period.nsec; + pwidth /= 2; + + mutex_lock(&clock->extreg_lock); + + ext_write(0, phydev, PAGE5, PTP_TRIG, ptp_trig); + + /*load trigger*/ + val |= TRIG_LOAD; + ext_write(0, phydev, PAGE4, PTP_CTL, val); + ext_write(0, phydev, PAGE4, PTP_TDR, nsec & 0xffff); /* ns[15:0] */ + ext_write(0, phydev, PAGE4, PTP_TDR, nsec >> 16); /* ns[31:16] */ + ext_write(0, phydev, PAGE4, PTP_TDR, sec & 0xffff); /* sec[15:0] */ + ext_write(0, phydev, PAGE4, PTP_TDR, sec >> 16); /* sec[31:16] */ + ext_write(0, phydev, PAGE4, PTP_TDR, pwidth & 0xffff); /* ns[15:0] */ + ext_write(0, phydev, PAGE4, PTP_TDR, pwidth >> 16); /* ns[31:16] */ + + /*enable trigger*/ + val &= ~TRIG_LOAD; + val |= TRIG_EN; + ext_write(0, phydev, PAGE4, PTP_CTL, val); + + mutex_unlock(&clock->extreg_lock); + return 0; +} + +/* ptp clock methods */ + +static int ptp_dp83640_adjfreq(struct ptp_clock_info *ptp, s32 ppb) +{ + struct dp83640_clock *clock = + container_of(ptp, struct dp83640_clock, caps); + struct phy_device *phydev = clock->chosen->phydev; + u64 rate; + int neg_adj = 0; + u16 hi, lo; + + if (ppb < 0) { + neg_adj = 1; + ppb = -ppb; + } + rate = ppb; + rate <<= 26; + rate = div_u64(rate, 1953125); + + hi = (rate >> 16) & PTP_RATE_HI_MASK; + if (neg_adj) + hi |= PTP_RATE_DIR; + + lo = rate & 0xffff; + + mutex_lock(&clock->extreg_lock); + + ext_write(1, phydev, PAGE4, PTP_RATEH, hi); + ext_write(1, phydev, PAGE4, PTP_RATEL, lo); + + mutex_unlock(&clock->extreg_lock); + + return 0; +} + +static int ptp_dp83640_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct dp83640_clock *clock = + container_of(ptp, struct dp83640_clock, caps); + struct phy_device *phydev = clock->chosen->phydev; + struct timespec ts; + int err; + + delta += ADJTIME_FIX; + + ts = ns_to_timespec(delta); + + mutex_lock(&clock->extreg_lock); + + err = tdr_write(1, phydev, &ts, PTP_STEP_CLK); + + mutex_unlock(&clock->extreg_lock); + + return err; +} + +static int ptp_dp83640_gettime(struct ptp_clock_info *ptp, struct timespec *ts) +{ + struct dp83640_clock *clock = + container_of(ptp, struct dp83640_clock, caps); + struct phy_device *phydev = clock->chosen->phydev; + unsigned int val[4]; + + mutex_lock(&clock->extreg_lock); + + ext_write(0, phydev, PAGE4, PTP_CTL, PTP_RD_CLK); + + val[0] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[15:0] */ + val[1] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[31:16] */ + val[2] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[15:0] */ + val[3] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[31:16] */ + + mutex_unlock(&clock->extreg_lock); + + ts->tv_nsec = val[0] | (val[1] << 16); + ts->tv_sec = val[2] | (val[3] << 16); + + return 0; +} + +static int ptp_dp83640_settime(struct ptp_clock_info *ptp, + const struct timespec *ts) +{ + struct dp83640_clock *clock = + container_of(ptp, struct dp83640_clock, caps); + struct phy_device *phydev = clock->chosen->phydev; + int err; + + mutex_lock(&clock->extreg_lock); + + err = tdr_write(1, phydev, ts, PTP_LOAD_CLK); + + mutex_unlock(&clock->extreg_lock); + + return err; +} + +static int ptp_dp83640_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct dp83640_clock *clock = + container_of(ptp, struct dp83640_clock, caps); + struct phy_device *phydev = clock->chosen->phydev; + unsigned int index; + u16 evnt, event_num, gpio_num; + + switch (rq->type) { + case PTP_CLK_REQ_EXTTS: + index = rq->extts.index; + if (index >= N_EXT_TS) + return -EINVAL; + event_num = EXT_EVENT + index; + evnt = EVNT_WR | (event_num & EVNT_SEL_MASK) << EVNT_SEL_SHIFT; + if (on) { + gpio_num = 1 + ptp_find_pin(clock->ptp_clock, + PTP_PF_EXTTS, index); + if (gpio_num < 1) + return -EINVAL; + evnt |= (gpio_num & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT; + if (rq->extts.flags & PTP_FALLING_EDGE) + evnt |= EVNT_FALL; + else + evnt |= EVNT_RISE; + } + ext_write(0, phydev, PAGE5, PTP_EVNT, evnt); + return 0; + + case PTP_CLK_REQ_PEROUT: + if (rq->perout.index != 0) + return -EINVAL; + return periodic_output(clock, rq, on); + + default: + break; + } + + return -EOPNOTSUPP; +} + +static int ptp_dp83640_verify(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + return 0; +} + +static u8 status_frame_dst[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 }; +static u8 status_frame_src[6] = { 0x08, 0x00, 0x17, 0x0B, 0x6B, 0x0F }; + +static void enable_status_frames(struct phy_device *phydev, bool on) +{ + u16 cfg0 = 0, ver; + + if (on) + cfg0 = PSF_EVNT_EN | PSF_RXTS_EN | PSF_TXTS_EN | ENDIAN_FLAG; + + ver = (PSF_PTPVER & VERSIONPTP_MASK) << VERSIONPTP_SHIFT; + + ext_write(0, phydev, PAGE5, PSF_CFG0, cfg0); + ext_write(0, phydev, PAGE6, PSF_CFG1, ver); + + if (!phydev->attached_dev) { + pr_warn("expected to find an attached netdevice\n"); + return; + } + + if (on) { + if (dev_mc_add(phydev->attached_dev, status_frame_dst)) + pr_warn("failed to add mc address\n"); + } else { + if (dev_mc_del(phydev->attached_dev, status_frame_dst)) + pr_warn("failed to delete mc address\n"); + } +} + +static bool is_status_frame(struct sk_buff *skb, int type) +{ + struct ethhdr *h = eth_hdr(skb); + + if (PTP_CLASS_V2_L2 == type && + !memcmp(h->h_source, status_frame_src, sizeof(status_frame_src))) + return true; + else + return false; +} + +static int expired(struct rxts *rxts) +{ + return time_after(jiffies, rxts->tmo); +} + +/* Caller must hold rx_lock. */ +static void prune_rx_ts(struct dp83640_private *dp83640) +{ + struct list_head *this, *next; + struct rxts *rxts; + + list_for_each_safe(this, next, &dp83640->rxts) { + rxts = list_entry(this, struct rxts, list); + if (expired(rxts)) { + list_del_init(&rxts->list); + list_add(&rxts->list, &dp83640->rxpool); + } + } +} + +/* synchronize the phyters so they act as one clock */ + +static void enable_broadcast(struct phy_device *phydev, int init_page, int on) +{ + int val; + phy_write(phydev, PAGESEL, 0); + val = phy_read(phydev, PHYCR2); + if (on) + val |= BC_WRITE; + else + val &= ~BC_WRITE; + phy_write(phydev, PHYCR2, val); + phy_write(phydev, PAGESEL, init_page); +} + +static void recalibrate(struct dp83640_clock *clock) +{ + s64 now, diff; + struct phy_txts event_ts; + struct timespec ts; + struct list_head *this; + struct dp83640_private *tmp; + struct phy_device *master = clock->chosen->phydev; + u16 cal_gpio, cfg0, evnt, ptp_trig, trigger, val; + + trigger = CAL_TRIGGER; + cal_gpio = gpio_tab[CALIBRATE_GPIO]; + + mutex_lock(&clock->extreg_lock); + + /* + * enable broadcast, disable status frames, enable ptp clock + */ + list_for_each(this, &clock->phylist) { + tmp = list_entry(this, struct dp83640_private, list); + enable_broadcast(tmp->phydev, clock->page, 1); + tmp->cfg0 = ext_read(tmp->phydev, PAGE5, PSF_CFG0); + ext_write(0, tmp->phydev, PAGE5, PSF_CFG0, 0); + ext_write(0, tmp->phydev, PAGE4, PTP_CTL, PTP_ENABLE); + } + enable_broadcast(master, clock->page, 1); + cfg0 = ext_read(master, PAGE5, PSF_CFG0); + ext_write(0, master, PAGE5, PSF_CFG0, 0); + ext_write(0, master, PAGE4, PTP_CTL, PTP_ENABLE); + + /* + * enable an event timestamp + */ + evnt = EVNT_WR | EVNT_RISE | EVNT_SINGLE; + evnt |= (CAL_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT; + evnt |= (cal_gpio & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT; + + list_for_each(this, &clock->phylist) { + tmp = list_entry(this, struct dp83640_private, list); + ext_write(0, tmp->phydev, PAGE5, PTP_EVNT, evnt); + } + ext_write(0, master, PAGE5, PTP_EVNT, evnt); + + /* + * configure a trigger + */ + ptp_trig = TRIG_WR | TRIG_IF_LATE | TRIG_PULSE; + ptp_trig |= (trigger & TRIG_CSEL_MASK) << TRIG_CSEL_SHIFT; + ptp_trig |= (cal_gpio & TRIG_GPIO_MASK) << TRIG_GPIO_SHIFT; + ext_write(0, master, PAGE5, PTP_TRIG, ptp_trig); + + /* load trigger */ + val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT; + val |= TRIG_LOAD; + ext_write(0, master, PAGE4, PTP_CTL, val); + + /* enable trigger */ + val &= ~TRIG_LOAD; + val |= TRIG_EN; + ext_write(0, master, PAGE4, PTP_CTL, val); + + /* disable trigger */ + val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT; + val |= TRIG_DIS; + ext_write(0, master, PAGE4, PTP_CTL, val); + + /* + * read out and correct offsets + */ + val = ext_read(master, PAGE4, PTP_STS); + pr_info("master PTP_STS 0x%04hx\n", val); + val = ext_read(master, PAGE4, PTP_ESTS); + pr_info("master PTP_ESTS 0x%04hx\n", val); + event_ts.ns_lo = ext_read(master, PAGE4, PTP_EDATA); + event_ts.ns_hi = ext_read(master, PAGE4, PTP_EDATA); + event_ts.sec_lo = ext_read(master, PAGE4, PTP_EDATA); + event_ts.sec_hi = ext_read(master, PAGE4, PTP_EDATA); + now = phy2txts(&event_ts); + + list_for_each(this, &clock->phylist) { + tmp = list_entry(this, struct dp83640_private, list); + val = ext_read(tmp->phydev, PAGE4, PTP_STS); + pr_info("slave PTP_STS 0x%04hx\n", val); + val = ext_read(tmp->phydev, PAGE4, PTP_ESTS); + pr_info("slave PTP_ESTS 0x%04hx\n", val); + event_ts.ns_lo = ext_read(tmp->phydev, PAGE4, PTP_EDATA); + event_ts.ns_hi = ext_read(tmp->phydev, PAGE4, PTP_EDATA); + event_ts.sec_lo = ext_read(tmp->phydev, PAGE4, PTP_EDATA); + event_ts.sec_hi = ext_read(tmp->phydev, PAGE4, PTP_EDATA); + diff = now - (s64) phy2txts(&event_ts); + pr_info("slave offset %lld nanoseconds\n", diff); + diff += ADJTIME_FIX; + ts = ns_to_timespec(diff); + tdr_write(0, tmp->phydev, &ts, PTP_STEP_CLK); + } + + /* + * restore status frames + */ + list_for_each(this, &clock->phylist) { + tmp = list_entry(this, struct dp83640_private, list); + ext_write(0, tmp->phydev, PAGE5, PSF_CFG0, tmp->cfg0); + } + ext_write(0, master, PAGE5, PSF_CFG0, cfg0); + + mutex_unlock(&clock->extreg_lock); +} + +/* time stamping methods */ + +static inline u16 exts_chan_to_edata(int ch) +{ + return 1 << ((ch + EXT_EVENT) * 2); +} + +static int decode_evnt(struct dp83640_private *dp83640, + void *data, u16 ests) +{ + struct phy_txts *phy_txts; + struct ptp_clock_event event; + int i, parsed; + int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK; + u16 ext_status = 0; + + if (ests & MULT_EVNT) { + ext_status = *(u16 *) data; + data += sizeof(ext_status); + } + + phy_txts = data; + + switch (words) { /* fall through in every case */ + case 3: + dp83640->edata.sec_hi = phy_txts->sec_hi; + case 2: + dp83640->edata.sec_lo = phy_txts->sec_lo; + case 1: + dp83640->edata.ns_hi = phy_txts->ns_hi; + case 0: + dp83640->edata.ns_lo = phy_txts->ns_lo; + } + + if (ext_status) { + parsed = words + 2; + } else { + parsed = words + 1; + i = ((ests >> EVNT_NUM_SHIFT) & EVNT_NUM_MASK) - EXT_EVENT; + ext_status = exts_chan_to_edata(i); + } + + event.type = PTP_CLOCK_EXTTS; + event.timestamp = phy2txts(&dp83640->edata); + + for (i = 0; i < N_EXT_TS; i++) { + if (ext_status & exts_chan_to_edata(i)) { + event.index = i; + ptp_clock_event(dp83640->clock->ptp_clock, &event); + } + } + + return parsed * sizeof(u16); +} + +static void decode_rxts(struct dp83640_private *dp83640, + struct phy_rxts *phy_rxts) +{ + struct rxts *rxts; + unsigned long flags; + + spin_lock_irqsave(&dp83640->rx_lock, flags); + + prune_rx_ts(dp83640); + + if (list_empty(&dp83640->rxpool)) { + pr_debug("rx timestamp pool is empty\n"); + goto out; + } + rxts = list_first_entry(&dp83640->rxpool, struct rxts, list); + list_del_init(&rxts->list); + phy2rxts(phy_rxts, rxts); + list_add_tail(&rxts->list, &dp83640->rxts); +out: + spin_unlock_irqrestore(&dp83640->rx_lock, flags); +} + +static void decode_txts(struct dp83640_private *dp83640, + struct phy_txts *phy_txts) +{ + struct skb_shared_hwtstamps shhwtstamps; + struct sk_buff *skb; + u64 ns; + + /* We must already have the skb that triggered this. */ + + skb = skb_dequeue(&dp83640->tx_queue); + + if (!skb) { + pr_debug("have timestamp but tx_queue empty\n"); + return; + } + ns = phy2txts(phy_txts); + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + shhwtstamps.hwtstamp = ns_to_ktime(ns); + skb_complete_tx_timestamp(skb, &shhwtstamps); +} + +static void decode_status_frame(struct dp83640_private *dp83640, + struct sk_buff *skb) +{ + struct phy_rxts *phy_rxts; + struct phy_txts *phy_txts; + u8 *ptr; + int len, size; + u16 ests, type; + + ptr = skb->data + 2; + + for (len = skb_headlen(skb) - 2; len > sizeof(type); len -= size) { + + type = *(u16 *)ptr; + ests = type & 0x0fff; + type = type & 0xf000; + len -= sizeof(type); + ptr += sizeof(type); + + if (PSF_RX == type && len >= sizeof(*phy_rxts)) { + + phy_rxts = (struct phy_rxts *) ptr; + decode_rxts(dp83640, phy_rxts); + size = sizeof(*phy_rxts); + + } else if (PSF_TX == type && len >= sizeof(*phy_txts)) { + + phy_txts = (struct phy_txts *) ptr; + decode_txts(dp83640, phy_txts); + size = sizeof(*phy_txts); + + } else if (PSF_EVNT == type && len >= sizeof(*phy_txts)) { + + size = decode_evnt(dp83640, ptr, ests); + + } else { + size = 0; + break; + } + ptr += size; + } +} + +static int is_sync(struct sk_buff *skb, int type) +{ + u8 *data = skb->data, *msgtype; + unsigned int offset = 0; + + switch (type) { + case PTP_CLASS_V1_IPV4: + case PTP_CLASS_V2_IPV4: + offset = ETH_HLEN + IPV4_HLEN(data) + UDP_HLEN; + break; + case PTP_CLASS_V1_IPV6: + case PTP_CLASS_V2_IPV6: + offset = OFF_PTP6; + break; + case PTP_CLASS_V2_L2: + offset = ETH_HLEN; + break; + case PTP_CLASS_V2_VLAN: + offset = ETH_HLEN + VLAN_HLEN; + break; + default: + return 0; + } + + if (type & PTP_CLASS_V1) + offset += OFF_PTP_CONTROL; + + if (skb->len < offset + 1) + return 0; + + msgtype = data + offset; + + return (*msgtype & 0xf) == 0; +} + +static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts) +{ + u16 *seqid; + unsigned int offset; + u8 *msgtype, *data = skb_mac_header(skb); + + /* check sequenceID, messageType, 12 bit hash of offset 20-29 */ + + switch (type) { + case PTP_CLASS_V1_IPV4: + case PTP_CLASS_V2_IPV4: + offset = ETH_HLEN + IPV4_HLEN(data) + UDP_HLEN; + break; + case PTP_CLASS_V1_IPV6: + case PTP_CLASS_V2_IPV6: + offset = OFF_PTP6; + break; + case PTP_CLASS_V2_L2: + offset = ETH_HLEN; + break; + case PTP_CLASS_V2_VLAN: + offset = ETH_HLEN + VLAN_HLEN; + break; + default: + return 0; + } + + if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid)) + return 0; + + if (unlikely(type & PTP_CLASS_V1)) + msgtype = data + offset + OFF_PTP_CONTROL; + else + msgtype = data + offset; + + seqid = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); + + return rxts->msgtype == (*msgtype & 0xf) && + rxts->seqid == ntohs(*seqid); +} + +static void dp83640_free_clocks(void) +{ + struct dp83640_clock *clock; + struct list_head *this, *next; + + mutex_lock(&phyter_clocks_lock); + + list_for_each_safe(this, next, &phyter_clocks) { + clock = list_entry(this, struct dp83640_clock, list); + if (!list_empty(&clock->phylist)) { + pr_warn("phy list non-empty while unloading\n"); + BUG(); + } + list_del(&clock->list); + mutex_destroy(&clock->extreg_lock); + mutex_destroy(&clock->clock_lock); + put_device(&clock->bus->dev); + kfree(clock->caps.pin_config); + kfree(clock); + } + + mutex_unlock(&phyter_clocks_lock); +} + +static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus) +{ + INIT_LIST_HEAD(&clock->list); + clock->bus = bus; + mutex_init(&clock->extreg_lock); + mutex_init(&clock->clock_lock); + INIT_LIST_HEAD(&clock->phylist); + clock->caps.owner = THIS_MODULE; + sprintf(clock->caps.name, "dp83640 timer"); + clock->caps.max_adj = 1953124; + clock->caps.n_alarm = 0; + clock->caps.n_ext_ts = N_EXT_TS; + clock->caps.n_per_out = 1; + clock->caps.n_pins = DP83640_N_PINS; + clock->caps.pps = 0; + clock->caps.adjfreq = ptp_dp83640_adjfreq; + clock->caps.adjtime = ptp_dp83640_adjtime; + clock->caps.gettime = ptp_dp83640_gettime; + clock->caps.settime = ptp_dp83640_settime; + clock->caps.enable = ptp_dp83640_enable; + clock->caps.verify = ptp_dp83640_verify; + /* + * Convert the module param defaults into a dynamic pin configuration. + */ + dp83640_gpio_defaults(clock->caps.pin_config); + /* + * Get a reference to this bus instance. + */ + get_device(&bus->dev); +} + +static int choose_this_phy(struct dp83640_clock *clock, + struct phy_device *phydev) +{ + if (chosen_phy == -1 && !clock->chosen) + return 1; + + if (chosen_phy == phydev->addr) + return 1; + + return 0; +} + +static struct dp83640_clock *dp83640_clock_get(struct dp83640_clock *clock) +{ + if (clock) + mutex_lock(&clock->clock_lock); + return clock; +} + +/* + * Look up and lock a clock by bus instance. + * If there is no clock for this bus, then create it first. + */ +static struct dp83640_clock *dp83640_clock_get_bus(struct mii_bus *bus) +{ + struct dp83640_clock *clock = NULL, *tmp; + struct list_head *this; + + mutex_lock(&phyter_clocks_lock); + + list_for_each(this, &phyter_clocks) { + tmp = list_entry(this, struct dp83640_clock, list); + if (tmp->bus == bus) { + clock = tmp; + break; + } + } + if (clock) + goto out; + + clock = kzalloc(sizeof(struct dp83640_clock), GFP_KERNEL); + if (!clock) + goto out; + + clock->caps.pin_config = kzalloc(sizeof(struct ptp_pin_desc) * + DP83640_N_PINS, GFP_KERNEL); + if (!clock->caps.pin_config) { + kfree(clock); + clock = NULL; + goto out; + } + dp83640_clock_init(clock, bus); + list_add_tail(&phyter_clocks, &clock->list); +out: + mutex_unlock(&phyter_clocks_lock); + + return dp83640_clock_get(clock); +} + +static void dp83640_clock_put(struct dp83640_clock *clock) +{ + mutex_unlock(&clock->clock_lock); +} + +static int dp83640_probe(struct phy_device *phydev) +{ + struct dp83640_clock *clock; + struct dp83640_private *dp83640; + int err = -ENOMEM, i; + + if (phydev->addr == BROADCAST_ADDR) + return 0; + + clock = dp83640_clock_get_bus(phydev->bus); + if (!clock) + goto no_clock; + + dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL); + if (!dp83640) + goto no_memory; + + dp83640->phydev = phydev; + INIT_WORK(&dp83640->ts_work, rx_timestamp_work); + + INIT_LIST_HEAD(&dp83640->rxts); + INIT_LIST_HEAD(&dp83640->rxpool); + for (i = 0; i < MAX_RXTS; i++) + list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool); + + phydev->priv = dp83640; + + spin_lock_init(&dp83640->rx_lock); + skb_queue_head_init(&dp83640->rx_queue); + skb_queue_head_init(&dp83640->tx_queue); + + dp83640->clock = clock; + + if (choose_this_phy(clock, phydev)) { + clock->chosen = dp83640; + clock->ptp_clock = ptp_clock_register(&clock->caps, &phydev->dev); + if (IS_ERR(clock->ptp_clock)) { + err = PTR_ERR(clock->ptp_clock); + goto no_register; + } + } else + list_add_tail(&dp83640->list, &clock->phylist); + + dp83640_clock_put(clock); + return 0; + +no_register: + clock->chosen = NULL; + kfree(dp83640); +no_memory: + dp83640_clock_put(clock); +no_clock: + return err; +} + +static void dp83640_remove(struct phy_device *phydev) +{ + struct dp83640_clock *clock; + struct list_head *this, *next; + struct dp83640_private *tmp, *dp83640 = phydev->priv; + struct sk_buff *skb; + + if (phydev->addr == BROADCAST_ADDR) + return; + + enable_status_frames(phydev, false); + cancel_work_sync(&dp83640->ts_work); + + while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL) + kfree_skb(skb); + + while ((skb = skb_dequeue(&dp83640->tx_queue)) != NULL) + skb_complete_tx_timestamp(skb, NULL); + + clock = dp83640_clock_get(dp83640->clock); + + if (dp83640 == clock->chosen) { + ptp_clock_unregister(clock->ptp_clock); + clock->chosen = NULL; + } else { + list_for_each_safe(this, next, &clock->phylist) { + tmp = list_entry(this, struct dp83640_private, list); + if (tmp == dp83640) { + list_del_init(&tmp->list); + break; + } + } + } + + dp83640_clock_put(clock); + kfree(dp83640); +} + +static int dp83640_config_init(struct phy_device *phydev) +{ + struct dp83640_private *dp83640 = phydev->priv; + struct dp83640_clock *clock = dp83640->clock; + + if (clock->chosen && !list_empty(&clock->phylist)) + recalibrate(clock); + else + enable_broadcast(phydev, clock->page, 1); + + enable_status_frames(phydev, true); + ext_write(0, phydev, PAGE4, PTP_CTL, PTP_ENABLE); + return 0; +} + +static int dp83640_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, MII_DP83640_MISR); + + if (err < 0) + return err; + + return 0; +} + +static int dp83640_config_intr(struct phy_device *phydev) +{ + int micr; + int misr; + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + misr = phy_read(phydev, MII_DP83640_MISR); + if (misr < 0) + return misr; + misr |= + (MII_DP83640_MISR_ANC_INT_EN | + MII_DP83640_MISR_DUP_INT_EN | + MII_DP83640_MISR_SPD_INT_EN | + MII_DP83640_MISR_LINK_INT_EN); + err = phy_write(phydev, MII_DP83640_MISR, misr); + if (err < 0) + return err; + + micr = phy_read(phydev, MII_DP83640_MICR); + if (micr < 0) + return micr; + micr |= + (MII_DP83640_MICR_OE | + MII_DP83640_MICR_IE); + return phy_write(phydev, MII_DP83640_MICR, micr); + } else { + micr = phy_read(phydev, MII_DP83640_MICR); + if (micr < 0) + return micr; + micr &= + ~(MII_DP83640_MICR_OE | + MII_DP83640_MICR_IE); + err = phy_write(phydev, MII_DP83640_MICR, micr); + if (err < 0) + return err; + + misr = phy_read(phydev, MII_DP83640_MISR); + if (misr < 0) + return misr; + misr &= + ~(MII_DP83640_MISR_ANC_INT_EN | + MII_DP83640_MISR_DUP_INT_EN | + MII_DP83640_MISR_SPD_INT_EN | + MII_DP83640_MISR_LINK_INT_EN); + return phy_write(phydev, MII_DP83640_MISR, misr); + } +} + +static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr) +{ + struct dp83640_private *dp83640 = phydev->priv; + struct hwtstamp_config cfg; + u16 txcfg0, rxcfg0; + + if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) + return -EFAULT; + + if (cfg.flags) /* reserved for future extensions */ + return -EINVAL; + + if (cfg.tx_type < 0 || cfg.tx_type > HWTSTAMP_TX_ONESTEP_SYNC) + return -ERANGE; + + dp83640->hwts_tx_en = cfg.tx_type; + + switch (cfg.rx_filter) { + case HWTSTAMP_FILTER_NONE: + dp83640->hwts_rx_en = 0; + dp83640->layer = 0; + dp83640->version = 0; + break; + case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: + dp83640->hwts_rx_en = 1; + dp83640->layer = LAYER4; + dp83640->version = 1; + break; + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: + dp83640->hwts_rx_en = 1; + dp83640->layer = LAYER4; + dp83640->version = 2; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + dp83640->hwts_rx_en = 1; + dp83640->layer = LAYER2; + dp83640->version = 2; + break; + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + dp83640->hwts_rx_en = 1; + dp83640->layer = LAYER4|LAYER2; + dp83640->version = 2; + break; + default: + return -ERANGE; + } + + txcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT; + rxcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT; + + if (dp83640->layer & LAYER2) { + txcfg0 |= TX_L2_EN; + rxcfg0 |= RX_L2_EN; + } + if (dp83640->layer & LAYER4) { + txcfg0 |= TX_IPV6_EN | TX_IPV4_EN; + rxcfg0 |= RX_IPV6_EN | RX_IPV4_EN; + } + + if (dp83640->hwts_tx_en) + txcfg0 |= TX_TS_EN; + + if (dp83640->hwts_tx_en == HWTSTAMP_TX_ONESTEP_SYNC) + txcfg0 |= SYNC_1STEP | CHK_1STEP; + + if (dp83640->hwts_rx_en) + rxcfg0 |= RX_TS_EN; + + mutex_lock(&dp83640->clock->extreg_lock); + + ext_write(0, phydev, PAGE5, PTP_TXCFG0, txcfg0); + ext_write(0, phydev, PAGE5, PTP_RXCFG0, rxcfg0); + + mutex_unlock(&dp83640->clock->extreg_lock); + + return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0; +} + +static void rx_timestamp_work(struct work_struct *work) +{ + struct dp83640_private *dp83640 = + container_of(work, struct dp83640_private, ts_work); + struct list_head *this, *next; + struct rxts *rxts; + struct skb_shared_hwtstamps *shhwtstamps; + struct sk_buff *skb; + unsigned int type; + unsigned long flags; + + /* Deliver each deferred packet, with or without a time stamp. */ + + while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL) { + type = SKB_PTP_TYPE(skb); + spin_lock_irqsave(&dp83640->rx_lock, flags); + list_for_each_safe(this, next, &dp83640->rxts) { + rxts = list_entry(this, struct rxts, list); + if (match(skb, type, rxts)) { + shhwtstamps = skb_hwtstamps(skb); + memset(shhwtstamps, 0, sizeof(*shhwtstamps)); + shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns); + list_del_init(&rxts->list); + list_add(&rxts->list, &dp83640->rxpool); + break; + } + } + spin_unlock_irqrestore(&dp83640->rx_lock, flags); + netif_rx_ni(skb); + } + + /* Clear out expired time stamps. */ + + spin_lock_irqsave(&dp83640->rx_lock, flags); + prune_rx_ts(dp83640); + spin_unlock_irqrestore(&dp83640->rx_lock, flags); +} + +static bool dp83640_rxtstamp(struct phy_device *phydev, + struct sk_buff *skb, int type) +{ + struct dp83640_private *dp83640 = phydev->priv; + + if (is_status_frame(skb, type)) { + decode_status_frame(dp83640, skb); + kfree_skb(skb); + return true; + } + + if (!dp83640->hwts_rx_en) + return false; + + SKB_PTP_TYPE(skb) = type; + skb_queue_tail(&dp83640->rx_queue, skb); + schedule_work(&dp83640->ts_work); + + return true; +} + +static void dp83640_txtstamp(struct phy_device *phydev, + struct sk_buff *skb, int type) +{ + struct dp83640_private *dp83640 = phydev->priv; + + switch (dp83640->hwts_tx_en) { + + case HWTSTAMP_TX_ONESTEP_SYNC: + if (is_sync(skb, type)) { + skb_complete_tx_timestamp(skb, NULL); + return; + } + /* fall through */ + case HWTSTAMP_TX_ON: + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + skb_queue_tail(&dp83640->tx_queue, skb); + schedule_work(&dp83640->ts_work); + break; + + case HWTSTAMP_TX_OFF: + default: + skb_complete_tx_timestamp(skb, NULL); + break; + } +} + +static int dp83640_ts_info(struct phy_device *dev, struct ethtool_ts_info *info) +{ + struct dp83640_private *dp83640 = dev->priv; + + info->so_timestamping = + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->phc_index = ptp_clock_index(dp83640->clock->ptp_clock); + info->tx_types = + (1 << HWTSTAMP_TX_OFF) | + (1 << HWTSTAMP_TX_ON) | + (1 << HWTSTAMP_TX_ONESTEP_SYNC); + info->rx_filters = + (1 << HWTSTAMP_FILTER_NONE) | + (1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V1_L4_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) | + (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) | + (1 << HWTSTAMP_FILTER_PTP_V2_EVENT) | + (1 << HWTSTAMP_FILTER_PTP_V2_SYNC) | + (1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ); + return 0; +} + +static struct phy_driver dp83640_driver = { + .phy_id = DP83640_PHY_ID, + .phy_id_mask = 0xfffffff0, + .name = "NatSemi DP83640", + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .probe = dp83640_probe, + .remove = dp83640_remove, + .config_init = dp83640_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = dp83640_ack_interrupt, + .config_intr = dp83640_config_intr, + .ts_info = dp83640_ts_info, + .hwtstamp = dp83640_hwtstamp, + .rxtstamp = dp83640_rxtstamp, + .txtstamp = dp83640_txtstamp, + .driver = {.owner = THIS_MODULE,} +}; + +static int __init dp83640_init(void) +{ + return phy_driver_register(&dp83640_driver); +} + +static void __exit dp83640_exit(void) +{ + dp83640_free_clocks(); + phy_driver_unregister(&dp83640_driver); +} + +MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver"); +MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); +MODULE_LICENSE("GPL"); + +module_init(dp83640_init); +module_exit(dp83640_exit); + +static struct mdio_device_id __maybe_unused dp83640_tbl[] = { + { DP83640_PHY_ID, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, dp83640_tbl); diff --git a/drivers/net/phy/dp83640_reg.h b/drivers/net/phy/dp83640_reg.h new file mode 100644 index 00000000000..e7fe4111700 --- /dev/null +++ b/drivers/net/phy/dp83640_reg.h @@ -0,0 +1,267 @@ +/* dp83640_reg.h + * Generated by regen.tcl on Thu Feb 17 10:02:48 AM CET 2011 + */ +#ifndef HAVE_DP83640_REGISTERS +#define HAVE_DP83640_REGISTERS + +#define PAGE0 0x0000 +#define PHYCR2 0x001c /* PHY Control Register 2 */ + +#define PAGE4 0x0004 +#define PTP_CTL 0x0014 /* PTP Control Register */ +#define PTP_TDR 0x0015 /* PTP Time Data Register */ +#define PTP_STS 0x0016 /* PTP Status Register */ +#define PTP_TSTS 0x0017 /* PTP Trigger Status Register */ +#define PTP_RATEL 0x0018 /* PTP Rate Low Register */ +#define PTP_RATEH 0x0019 /* PTP Rate High Register */ +#define PTP_RDCKSUM 0x001a /* PTP Read Checksum */ +#define PTP_WRCKSUM 0x001b /* PTP Write Checksum */ +#define PTP_TXTS 0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */ +#define PTP_RXTS 0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */ +#define PTP_ESTS 0x001e /* PTP Event Status Register */ +#define PTP_EDATA 0x001f /* PTP Event Data Register */ + +#define PAGE5 0x0005 +#define PTP_TRIG 0x0014 /* PTP Trigger Configuration Register */ +#define PTP_EVNT 0x0015 /* PTP Event Configuration Register */ +#define PTP_TXCFG0 0x0016 /* PTP Transmit Configuration Register 0 */ +#define PTP_TXCFG1 0x0017 /* PTP Transmit Configuration Register 1 */ +#define PSF_CFG0 0x0018 /* PHY Status Frame Configuration Register 0 */ +#define PTP_RXCFG0 0x0019 /* PTP Receive Configuration Register 0 */ +#define PTP_RXCFG1 0x001a /* PTP Receive Configuration Register 1 */ +#define PTP_RXCFG2 0x001b /* PTP Receive Configuration Register 2 */ +#define PTP_RXCFG3 0x001c /* PTP Receive Configuration Register 3 */ +#define PTP_RXCFG4 0x001d /* PTP Receive Configuration Register 4 */ +#define PTP_TRDL 0x001e /* PTP Temporary Rate Duration Low Register */ +#define PTP_TRDH 0x001f /* PTP Temporary Rate Duration High Register */ + +#define PAGE6 0x0006 +#define PTP_COC 0x0014 /* PTP Clock Output Control Register */ +#define PSF_CFG1 0x0015 /* PHY Status Frame Configuration Register 1 */ +#define PSF_CFG2 0x0016 /* PHY Status Frame Configuration Register 2 */ +#define PSF_CFG3 0x0017 /* PHY Status Frame Configuration Register 3 */ +#define PSF_CFG4 0x0018 /* PHY Status Frame Configuration Register 4 */ +#define PTP_SFDCFG 0x0019 /* PTP SFD Configuration Register */ +#define PTP_INTCTL 0x001a /* PTP Interrupt Control Register */ +#define PTP_CLKSRC 0x001b /* PTP Clock Source Register */ +#define PTP_ETR 0x001c /* PTP Ethernet Type Register */ +#define PTP_OFF 0x001d /* PTP Offset Register */ +#define PTP_GPIOMON 0x001e /* PTP GPIO Monitor Register */ +#define PTP_RXHASH 0x001f /* PTP Receive Hash Register */ + +/* Bit definitions for the PHYCR2 register */ +#define BC_WRITE (1<<11) /* Broadcast Write Enable */ + +/* Bit definitions for the PTP_CTL register */ +#define TRIG_SEL_SHIFT (10) /* PTP Trigger Select */ +#define TRIG_SEL_MASK (0x7) +#define TRIG_DIS (1<<9) /* Disable PTP Trigger */ +#define TRIG_EN (1<<8) /* Enable PTP Trigger */ +#define TRIG_READ (1<<7) /* Read PTP Trigger */ +#define TRIG_LOAD (1<<6) /* Load PTP Trigger */ +#define PTP_RD_CLK (1<<5) /* Read PTP Clock */ +#define PTP_LOAD_CLK (1<<4) /* Load PTP Clock */ +#define PTP_STEP_CLK (1<<3) /* Step PTP Clock */ +#define PTP_ENABLE (1<<2) /* Enable PTP Clock */ +#define PTP_DISABLE (1<<1) /* Disable PTP Clock */ +#define PTP_RESET (1<<0) /* Reset PTP Clock */ + +/* Bit definitions for the PTP_STS register */ +#define TXTS_RDY (1<<11) /* Transmit Timestamp Ready */ +#define RXTS_RDY (1<<10) /* Receive Timestamp Ready */ +#define TRIG_DONE (1<<9) /* PTP Trigger Done */ +#define EVENT_RDY (1<<8) /* PTP Event Timestamp Ready */ +#define TXTS_IE (1<<3) /* Transmit Timestamp Interrupt Enable */ +#define RXTS_IE (1<<2) /* Receive Timestamp Interrupt Enable */ +#define TRIG_IE (1<<1) /* Trigger Interrupt Enable */ +#define EVENT_IE (1<<0) /* Event Interrupt Enable */ + +/* Bit definitions for the PTP_TSTS register */ +#define TRIG7_ERROR (1<<15) /* Trigger 7 Error */ +#define TRIG7_ACTIVE (1<<14) /* Trigger 7 Active */ +#define TRIG6_ERROR (1<<13) /* Trigger 6 Error */ +#define TRIG6_ACTIVE (1<<12) /* Trigger 6 Active */ +#define TRIG5_ERROR (1<<11) /* Trigger 5 Error */ +#define TRIG5_ACTIVE (1<<10) /* Trigger 5 Active */ +#define TRIG4_ERROR (1<<9) /* Trigger 4 Error */ +#define TRIG4_ACTIVE (1<<8) /* Trigger 4 Active */ +#define TRIG3_ERROR (1<<7) /* Trigger 3 Error */ +#define TRIG3_ACTIVE (1<<6) /* Trigger 3 Active */ +#define TRIG2_ERROR (1<<5) /* Trigger 2 Error */ +#define TRIG2_ACTIVE (1<<4) /* Trigger 2 Active */ +#define TRIG1_ERROR (1<<3) /* Trigger 1 Error */ +#define TRIG1_ACTIVE (1<<2) /* Trigger 1 Active */ +#define TRIG0_ERROR (1<<1) /* Trigger 0 Error */ +#define TRIG0_ACTIVE (1<<0) /* Trigger 0 Active */ + +/* Bit definitions for the PTP_RATEH register */ +#define PTP_RATE_DIR (1<<15) /* PTP Rate Direction */ +#define PTP_TMP_RATE (1<<14) /* PTP Temporary Rate */ +#define PTP_RATE_HI_SHIFT (0) /* PTP Rate High 10-bits */ +#define PTP_RATE_HI_MASK (0x3ff) + +/* Bit definitions for the PTP_ESTS register */ +#define EVNTS_MISSED_SHIFT (8) /* Indicates number of events missed */ +#define EVNTS_MISSED_MASK (0x7) +#define EVNT_TS_LEN_SHIFT (6) /* Indicates length of the Timestamp field in 16-bit words minus 1 */ +#define EVNT_TS_LEN_MASK (0x3) +#define EVNT_RF (1<<5) /* Indicates whether the event is a rise or falling event */ +#define EVNT_NUM_SHIFT (2) /* Indicates Event Timestamp Unit which detected an event */ +#define EVNT_NUM_MASK (0x7) +#define MULT_EVNT (1<<1) /* Indicates multiple events were detected at the same time */ +#define EVENT_DET (1<<0) /* PTP Event Detected */ + +/* Bit definitions for the PTP_EDATA register */ +#define E7_RISE (1<<15) /* Indicates direction of Event 7 */ +#define E7_DET (1<<14) /* Indicates Event 7 detected */ +#define E6_RISE (1<<13) /* Indicates direction of Event 6 */ +#define E6_DET (1<<12) /* Indicates Event 6 detected */ +#define E5_RISE (1<<11) /* Indicates direction of Event 5 */ +#define E5_DET (1<<10) /* Indicates Event 5 detected */ +#define E4_RISE (1<<9) /* Indicates direction of Event 4 */ +#define E4_DET (1<<8) /* Indicates Event 4 detected */ +#define E3_RISE (1<<7) /* Indicates direction of Event 3 */ +#define E3_DET (1<<6) /* Indicates Event 3 detected */ +#define E2_RISE (1<<5) /* Indicates direction of Event 2 */ +#define E2_DET (1<<4) /* Indicates Event 2 detected */ +#define E1_RISE (1<<3) /* Indicates direction of Event 1 */ +#define E1_DET (1<<2) /* Indicates Event 1 detected */ +#define E0_RISE (1<<1) /* Indicates direction of Event 0 */ +#define E0_DET (1<<0) /* Indicates Event 0 detected */ + +/* Bit definitions for the PTP_TRIG register */ +#define TRIG_PULSE (1<<15) /* generate a Pulse rather than a single edge */ +#define TRIG_PER (1<<14) /* generate a periodic signal */ +#define TRIG_IF_LATE (1<<13) /* trigger immediately if already past */ +#define TRIG_NOTIFY (1<<12) /* Trigger Notification Enable */ +#define TRIG_GPIO_SHIFT (8) /* Trigger GPIO Connection, value 1-12 */ +#define TRIG_GPIO_MASK (0xf) +#define TRIG_TOGGLE (1<<7) /* Trigger Toggle Mode Enable */ +#define TRIG_CSEL_SHIFT (1) /* Trigger Configuration Select */ +#define TRIG_CSEL_MASK (0x7) +#define TRIG_WR (1<<0) /* Trigger Configuration Write */ + +/* Bit definitions for the PTP_EVNT register */ +#define EVNT_RISE (1<<14) /* Event Rise Detect Enable */ +#define EVNT_FALL (1<<13) /* Event Fall Detect Enable */ +#define EVNT_SINGLE (1<<12) /* enable single event capture operation */ +#define EVNT_GPIO_SHIFT (8) /* Event GPIO Connection, value 1-12 */ +#define EVNT_GPIO_MASK (0xf) +#define EVNT_SEL_SHIFT (1) /* Event Select */ +#define EVNT_SEL_MASK (0x7) +#define EVNT_WR (1<<0) /* Event Configuration Write */ + +/* Bit definitions for the PTP_TXCFG0 register */ +#define SYNC_1STEP (1<<15) /* insert timestamp into transmit Sync Messages */ +#define DR_INSERT (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */ +#define NTP_TS_EN (1<<12) /* Enable Timestamping of NTP Packets */ +#define IGNORE_2STEP (1<<11) /* Ignore Two_Step flag for One-Step operation */ +#define CRC_1STEP (1<<10) /* Disable checking of CRC for One-Step operation */ +#define CHK_1STEP (1<<9) /* Enable UDP Checksum correction for One-Step Operation */ +#define IP1588_EN (1<<8) /* Enable IEEE 1588 defined IP address filter */ +#define TX_L2_EN (1<<7) /* Layer2 Timestamp Enable */ +#define TX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */ +#define TX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */ +#define TX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */ +#define TX_PTP_VER_MASK (0xf) +#define TX_TS_EN (1<<0) /* Transmit Timestamp Enable */ + +/* Bit definitions for the PTP_TXCFG1 register */ +#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */ +#define BYTE0_MASK_MASK (0xff) +#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */ +#define BYTE0_DATA_MASK (0xff) + +/* Bit definitions for the PSF_CFG0 register */ +#define MAC_SRC_ADD_SHIFT (11) /* Status Frame Mac Source Address */ +#define MAC_SRC_ADD_MASK (0x3) +#define MIN_PRE_SHIFT (8) /* Status Frame Minimum Preamble */ +#define MIN_PRE_MASK (0x7) +#define PSF_ENDIAN (1<<7) /* Status Frame Endian Control */ +#define PSF_IPV4 (1<<6) /* Status Frame IPv4 Enable */ +#define PSF_PCF_RD (1<<5) /* Control Frame Read PHY Status Frame Enable */ +#define PSF_ERR_EN (1<<4) /* Error PHY Status Frame Enable */ +#define PSF_TXTS_EN (1<<3) /* Transmit Timestamp PHY Status Frame Enable */ +#define PSF_RXTS_EN (1<<2) /* Receive Timestamp PHY Status Frame Enable */ +#define PSF_TRIG_EN (1<<1) /* Trigger PHY Status Frame Enable */ +#define PSF_EVNT_EN (1<<0) /* Event PHY Status Frame Enable */ + +/* Bit definitions for the PTP_RXCFG0 register */ +#define DOMAIN_EN (1<<15) /* Domain Match Enable */ +#define ALT_MAST_DIS (1<<14) /* Alternate Master Timestamp Disable */ +#define USER_IP_SEL (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */ +#define USER_IP_EN (1<<12) /* Enable User-programmed IP address filter */ +#define RX_SLAVE (1<<11) /* Receive Slave Only */ +#define IP1588_EN_SHIFT (8) /* Enable IEEE 1588 defined IP address filters */ +#define IP1588_EN_MASK (0xf) +#define RX_L2_EN (1<<7) /* Layer2 Timestamp Enable */ +#define RX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */ +#define RX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */ +#define RX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */ +#define RX_PTP_VER_MASK (0xf) +#define RX_TS_EN (1<<0) /* Receive Timestamp Enable */ + +/* Bit definitions for the PTP_RXCFG1 register */ +#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */ +#define BYTE0_MASK_MASK (0xff) +#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */ +#define BYTE0_DATA_MASK (0xff) + +/* Bit definitions for the PTP_RXCFG3 register */ +#define TS_MIN_IFG_SHIFT (12) /* Minimum Inter-frame Gap */ +#define TS_MIN_IFG_MASK (0xf) +#define ACC_UDP (1<<11) /* Record Timestamp if UDP Checksum Error */ +#define ACC_CRC (1<<10) /* Record Timestamp if CRC Error */ +#define TS_APPEND (1<<9) /* Append Timestamp for L2 */ +#define TS_INSERT (1<<8) /* Enable Timestamp Insertion */ +#define PTP_DOMAIN_SHIFT (0) /* PTP Message domainNumber field */ +#define PTP_DOMAIN_MASK (0xff) + +/* Bit definitions for the PTP_RXCFG4 register */ +#define IPV4_UDP_MOD (1<<15) /* Enable IPV4 UDP Modification */ +#define TS_SEC_EN (1<<14) /* Enable Timestamp Seconds */ +#define TS_SEC_LEN_SHIFT (12) /* Inserted Timestamp Seconds Length */ +#define TS_SEC_LEN_MASK (0x3) +#define RXTS_NS_OFF_SHIFT (6) /* Receive Timestamp Nanoseconds offset */ +#define RXTS_NS_OFF_MASK (0x3f) +#define RXTS_SEC_OFF_SHIFT (0) /* Receive Timestamp Seconds offset */ +#define RXTS_SEC_OFF_MASK (0x3f) + +/* Bit definitions for the PTP_COC register */ +#define PTP_CLKOUT_EN (1<<15) /* PTP Clock Output Enable */ +#define PTP_CLKOUT_SEL (1<<14) /* PTP Clock Output Source Select */ +#define PTP_CLKOUT_SPEEDSEL (1<<13) /* PTP Clock Output I/O Speed Select */ +#define PTP_CLKDIV_SHIFT (0) /* PTP Clock Divide-by Value */ +#define PTP_CLKDIV_MASK (0xff) + +/* Bit definitions for the PSF_CFG1 register */ +#define PTPRESERVED_SHIFT (12) /* PTP v2 reserved field */ +#define PTPRESERVED_MASK (0xf) +#define VERSIONPTP_SHIFT (8) /* PTP v2 versionPTP field */ +#define VERSIONPTP_MASK (0xf) +#define TRANSPORT_SPECIFIC_SHIFT (4) /* PTP v2 Header transportSpecific field */ +#define TRANSPORT_SPECIFIC_MASK (0xf) +#define MESSAGETYPE_SHIFT (0) /* PTP v2 messageType field */ +#define MESSAGETYPE_MASK (0xf) + +/* Bit definitions for the PTP_SFDCFG register */ +#define TX_SFD_GPIO_SHIFT (4) /* TX SFD GPIO Select, value 1-12 */ +#define TX_SFD_GPIO_MASK (0xf) +#define RX_SFD_GPIO_SHIFT (0) /* RX SFD GPIO Select, value 1-12 */ +#define RX_SFD_GPIO_MASK (0xf) + +/* Bit definitions for the PTP_INTCTL register */ +#define PTP_INT_GPIO_SHIFT (0) /* PTP Interrupt GPIO Select */ +#define PTP_INT_GPIO_MASK (0xf) + +/* Bit definitions for the PTP_CLKSRC register */ +#define CLK_SRC_SHIFT (14) /* PTP Clock Source Select */ +#define CLK_SRC_MASK (0x3) +#define CLK_SRC_PER_SHIFT (0) /* PTP Clock Source Period */ +#define CLK_SRC_PER_MASK (0x7f) + +/* Bit definitions for the PTP_OFF register */ +#define PTP_OFFSET_SHIFT (0) /* PTP Message offset from preceding header */ +#define PTP_OFFSET_MASK (0xff) + +#endif diff --git a/drivers/net/phy/et1011c.c b/drivers/net/phy/et1011c.c new file mode 100644 index 00000000000..a8eb19ec318 --- /dev/null +++ b/drivers/net/phy/et1011c.c @@ -0,0 +1,119 @@ +/* + * drivers/net/phy/et1011c.c + * + * Driver for LSI ET1011C PHYs + * + * Author: Chaithrika U S + * + * Copyright (c) 2008 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <asm/irq.h> + +#define ET1011C_STATUS_REG (0x1A) +#define ET1011C_CONFIG_REG (0x16) +#define ET1011C_SPEED_MASK (0x0300) +#define ET1011C_GIGABIT_SPEED (0x0200) +#define ET1011C_TX_FIFO_MASK (0x3000) +#define ET1011C_TX_FIFO_DEPTH_8 (0x0000) +#define ET1011C_TX_FIFO_DEPTH_16 (0x1000) +#define ET1011C_INTERFACE_MASK (0x0007) +#define ET1011C_GMII_INTERFACE (0x0002) +#define ET1011C_SYS_CLK_EN (0x01 << 4) + + +MODULE_DESCRIPTION("LSI ET1011C PHY driver"); +MODULE_AUTHOR("Chaithrika U S"); +MODULE_LICENSE("GPL"); + +static int et1011c_config_aneg(struct phy_device *phydev) +{ + int ctl = 0; + ctl = phy_read(phydev, MII_BMCR); + if (ctl < 0) + return ctl; + ctl &= ~(BMCR_FULLDPLX | BMCR_SPEED100 | BMCR_SPEED1000 | + BMCR_ANENABLE); + /* First clear the PHY */ + phy_write(phydev, MII_BMCR, ctl | BMCR_RESET); + + return genphy_config_aneg(phydev); +} + +static int et1011c_read_status(struct phy_device *phydev) +{ + int ret; + u32 val; + static int speed; + ret = genphy_read_status(phydev); + + if (speed != phydev->speed) { + speed = phydev->speed; + val = phy_read(phydev, ET1011C_STATUS_REG); + if ((val & ET1011C_SPEED_MASK) == + ET1011C_GIGABIT_SPEED) { + val = phy_read(phydev, ET1011C_CONFIG_REG); + val &= ~ET1011C_TX_FIFO_MASK; + phy_write(phydev, ET1011C_CONFIG_REG, val\ + | ET1011C_GMII_INTERFACE\ + | ET1011C_SYS_CLK_EN\ + | ET1011C_TX_FIFO_DEPTH_16); + + } + } + return ret; +} + +static struct phy_driver et1011c_driver = { + .phy_id = 0x0282f014, + .name = "ET1011C", + .phy_id_mask = 0xfffffff0, + .features = (PHY_BASIC_FEATURES | SUPPORTED_1000baseT_Full), + .flags = PHY_POLL, + .config_aneg = et1011c_config_aneg, + .read_status = et1011c_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init et1011c_init(void) +{ + return phy_driver_register(&et1011c_driver); +} + +static void __exit et1011c_exit(void) +{ + phy_driver_unregister(&et1011c_driver); +} + +module_init(et1011c_init); +module_exit(et1011c_exit); + +static struct mdio_device_id __maybe_unused et1011c_tbl[] = { + { 0x0282f014, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, et1011c_tbl); diff --git a/drivers/net/phy/fixed.c b/drivers/net/phy/fixed.c new file mode 100644 index 00000000000..d60d875cb44 --- /dev/null +++ b/drivers/net/phy/fixed.c @@ -0,0 +1,325 @@ +/* + * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) + * + * Author: Vitaly Bordug <vbordug@ru.mvista.com> + * Anton Vorontsov <avorontsov@ru.mvista.com> + * + * Copyright (c) 2006-2007 MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/mii.h> +#include <linux/phy.h> +#include <linux/phy_fixed.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/of.h> + +#define MII_REGS_NUM 29 + +struct fixed_mdio_bus { + int irqs[PHY_MAX_ADDR]; + struct mii_bus *mii_bus; + struct list_head phys; +}; + +struct fixed_phy { + int addr; + u16 regs[MII_REGS_NUM]; + struct phy_device *phydev; + struct fixed_phy_status status; + int (*link_update)(struct net_device *, struct fixed_phy_status *); + struct list_head node; +}; + +static struct platform_device *pdev; +static struct fixed_mdio_bus platform_fmb = { + .phys = LIST_HEAD_INIT(platform_fmb.phys), +}; + +static int fixed_phy_update_regs(struct fixed_phy *fp) +{ + u16 bmsr = BMSR_ANEGCAPABLE; + u16 bmcr = 0; + u16 lpagb = 0; + u16 lpa = 0; + + if (fp->status.duplex) { + bmcr |= BMCR_FULLDPLX; + + switch (fp->status.speed) { + case 1000: + bmsr |= BMSR_ESTATEN; + bmcr |= BMCR_SPEED1000; + lpagb |= LPA_1000FULL; + break; + case 100: + bmsr |= BMSR_100FULL; + bmcr |= BMCR_SPEED100; + lpa |= LPA_100FULL; + break; + case 10: + bmsr |= BMSR_10FULL; + lpa |= LPA_10FULL; + break; + default: + pr_warn("fixed phy: unknown speed\n"); + return -EINVAL; + } + } else { + switch (fp->status.speed) { + case 1000: + bmsr |= BMSR_ESTATEN; + bmcr |= BMCR_SPEED1000; + lpagb |= LPA_1000HALF; + break; + case 100: + bmsr |= BMSR_100HALF; + bmcr |= BMCR_SPEED100; + lpa |= LPA_100HALF; + break; + case 10: + bmsr |= BMSR_10HALF; + lpa |= LPA_10HALF; + break; + default: + pr_warn("fixed phy: unknown speed\n"); + return -EINVAL; + } + } + + if (fp->status.link) + bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; + + if (fp->status.pause) + lpa |= LPA_PAUSE_CAP; + + if (fp->status.asym_pause) + lpa |= LPA_PAUSE_ASYM; + + fp->regs[MII_PHYSID1] = 0; + fp->regs[MII_PHYSID2] = 0; + + fp->regs[MII_BMSR] = bmsr; + fp->regs[MII_BMCR] = bmcr; + fp->regs[MII_LPA] = lpa; + fp->regs[MII_STAT1000] = lpagb; + + return 0; +} + +static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +{ + struct fixed_mdio_bus *fmb = bus->priv; + struct fixed_phy *fp; + + if (reg_num >= MII_REGS_NUM) + return -1; + + list_for_each_entry(fp, &fmb->phys, node) { + if (fp->addr == phy_addr) { + /* Issue callback if user registered it. */ + if (fp->link_update) { + fp->link_update(fp->phydev->attached_dev, + &fp->status); + fixed_phy_update_regs(fp); + } + return fp->regs[reg_num]; + } + } + + return 0xFFFF; +} + +static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, + u16 val) +{ + return 0; +} + +/* + * If something weird is required to be done with link/speed, + * network driver is able to assign a function to implement this. + * May be useful for PHY's that need to be software-driven. + */ +int fixed_phy_set_link_update(struct phy_device *phydev, + int (*link_update)(struct net_device *, + struct fixed_phy_status *)) +{ + struct fixed_mdio_bus *fmb = &platform_fmb; + struct fixed_phy *fp; + + if (!link_update || !phydev || !phydev->bus) + return -EINVAL; + + list_for_each_entry(fp, &fmb->phys, node) { + if (fp->addr == phydev->addr) { + fp->link_update = link_update; + fp->phydev = phydev; + return 0; + } + } + + return -ENOENT; +} +EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); + +int fixed_phy_add(unsigned int irq, int phy_addr, + struct fixed_phy_status *status) +{ + int ret; + struct fixed_mdio_bus *fmb = &platform_fmb; + struct fixed_phy *fp; + + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return -ENOMEM; + + memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM); + + fmb->irqs[phy_addr] = irq; + + fp->addr = phy_addr; + fp->status = *status; + + ret = fixed_phy_update_regs(fp); + if (ret) + goto err_regs; + + list_add_tail(&fp->node, &fmb->phys); + + return 0; + +err_regs: + kfree(fp); + return ret; +} +EXPORT_SYMBOL_GPL(fixed_phy_add); + +void fixed_phy_del(int phy_addr) +{ + struct fixed_mdio_bus *fmb = &platform_fmb; + struct fixed_phy *fp, *tmp; + + list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { + if (fp->addr == phy_addr) { + list_del(&fp->node); + kfree(fp); + return; + } + } +} +EXPORT_SYMBOL_GPL(fixed_phy_del); + +static int phy_fixed_addr; +static DEFINE_SPINLOCK(phy_fixed_addr_lock); + +int fixed_phy_register(unsigned int irq, + struct fixed_phy_status *status, + struct device_node *np) +{ + struct fixed_mdio_bus *fmb = &platform_fmb; + struct phy_device *phy; + int phy_addr; + int ret; + + /* Get the next available PHY address, up to PHY_MAX_ADDR */ + spin_lock(&phy_fixed_addr_lock); + if (phy_fixed_addr == PHY_MAX_ADDR) { + spin_unlock(&phy_fixed_addr_lock); + return -ENOSPC; + } + phy_addr = phy_fixed_addr++; + spin_unlock(&phy_fixed_addr_lock); + + ret = fixed_phy_add(PHY_POLL, phy_addr, status); + if (ret < 0) + return ret; + + phy = get_phy_device(fmb->mii_bus, phy_addr, false); + if (!phy || IS_ERR(phy)) { + fixed_phy_del(phy_addr); + return -EINVAL; + } + + of_node_get(np); + phy->dev.of_node = np; + + ret = phy_device_register(phy); + if (ret) { + phy_device_free(phy); + of_node_put(np); + fixed_phy_del(phy_addr); + return ret; + } + + return 0; +} + +static int __init fixed_mdio_bus_init(void) +{ + struct fixed_mdio_bus *fmb = &platform_fmb; + int ret; + + pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto err_pdev; + } + + fmb->mii_bus = mdiobus_alloc(); + if (fmb->mii_bus == NULL) { + ret = -ENOMEM; + goto err_mdiobus_reg; + } + + snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); + fmb->mii_bus->name = "Fixed MDIO Bus"; + fmb->mii_bus->priv = fmb; + fmb->mii_bus->parent = &pdev->dev; + fmb->mii_bus->read = &fixed_mdio_read; + fmb->mii_bus->write = &fixed_mdio_write; + fmb->mii_bus->irq = fmb->irqs; + + ret = mdiobus_register(fmb->mii_bus); + if (ret) + goto err_mdiobus_alloc; + + return 0; + +err_mdiobus_alloc: + mdiobus_free(fmb->mii_bus); +err_mdiobus_reg: + platform_device_unregister(pdev); +err_pdev: + return ret; +} +module_init(fixed_mdio_bus_init); + +static void __exit fixed_mdio_bus_exit(void) +{ + struct fixed_mdio_bus *fmb = &platform_fmb; + struct fixed_phy *fp, *tmp; + + mdiobus_unregister(fmb->mii_bus); + mdiobus_free(fmb->mii_bus); + platform_device_unregister(pdev); + + list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { + list_del(&fp->node); + kfree(fp); + } +} +module_exit(fixed_mdio_bus_exit); + +MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); +MODULE_AUTHOR("Vitaly Bordug"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/icplus.c b/drivers/net/phy/icplus.c new file mode 100644 index 00000000000..97bf58bf493 --- /dev/null +++ b/drivers/net/phy/icplus.c @@ -0,0 +1,278 @@ +/* + * Driver for ICPlus PHYs + * + * Copyright (c) 2007 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 <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("ICPlus IP175C/IP101A/IP101G/IC1001 PHY drivers"); +MODULE_AUTHOR("Michael Barkowski"); +MODULE_LICENSE("GPL"); + +/* IP101A/G - IP1001 */ +#define IP10XX_SPEC_CTRL_STATUS 16 /* Spec. Control Register */ +#define IP1001_RXPHASE_SEL (1<<0) /* Add delay on RX_CLK */ +#define IP1001_TXPHASE_SEL (1<<1) /* Add delay on TX_CLK */ +#define IP1001_SPEC_CTRL_STATUS_2 20 /* IP1001 Spec. Control Reg 2 */ +#define IP1001_APS_ON 11 /* IP1001 APS Mode bit */ +#define IP101A_G_APS_ON 2 /* IP101A/G APS Mode bit */ +#define IP101A_G_IRQ_CONF_STATUS 0x11 /* Conf Info IRQ & Status Reg */ +#define IP101A_G_IRQ_PIN_USED (1<<15) /* INTR pin used */ +#define IP101A_G_IRQ_DEFAULT IP101A_G_IRQ_PIN_USED + +static int ip175c_config_init(struct phy_device *phydev) +{ + int err, i; + static int full_reset_performed; + + if (full_reset_performed == 0) { + + /* master reset */ + err = mdiobus_write(phydev->bus, 30, 0, 0x175c); + if (err < 0) + return err; + + /* ensure no bus delays overlap reset period */ + err = mdiobus_read(phydev->bus, 30, 0); + + /* data sheet specifies reset period is 2 msec */ + mdelay(2); + + /* enable IP175C mode */ + err = mdiobus_write(phydev->bus, 29, 31, 0x175c); + if (err < 0) + return err; + + /* Set MII0 speed and duplex (in PHY mode) */ + err = mdiobus_write(phydev->bus, 29, 22, 0x420); + if (err < 0) + return err; + + /* reset switch ports */ + for (i = 0; i < 5; i++) { + err = mdiobus_write(phydev->bus, i, + MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + } + + for (i = 0; i < 5; i++) + err = mdiobus_read(phydev->bus, i, MII_BMCR); + + mdelay(2); + + full_reset_performed = 1; + } + + if (phydev->addr != 4) { + phydev->state = PHY_RUNNING; + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + netif_carrier_on(phydev->attached_dev); + } + + return 0; +} + +static int ip1xx_reset(struct phy_device *phydev) +{ + int bmcr; + + /* Software Reset PHY */ + bmcr = phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + bmcr |= BMCR_RESET; + bmcr = phy_write(phydev, MII_BMCR, bmcr); + if (bmcr < 0) + return bmcr; + + do { + bmcr = phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + } while (bmcr & BMCR_RESET); + + return 0; +} + +static int ip1001_config_init(struct phy_device *phydev) +{ + int c; + + c = ip1xx_reset(phydev); + if (c < 0) + return c; + + /* Enable Auto Power Saving mode */ + c = phy_read(phydev, IP1001_SPEC_CTRL_STATUS_2); + if (c < 0) + return c; + c |= IP1001_APS_ON; + c = phy_write(phydev, IP1001_SPEC_CTRL_STATUS_2, c); + if (c < 0) + return c; + + if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)) { + + c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS); + if (c < 0) + return c; + + c &= ~(IP1001_RXPHASE_SEL | IP1001_TXPHASE_SEL); + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) + c |= (IP1001_RXPHASE_SEL | IP1001_TXPHASE_SEL); + else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) + c |= IP1001_RXPHASE_SEL; + else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) + c |= IP1001_TXPHASE_SEL; + + c = phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c); + if (c < 0) + return c; + } + + return 0; +} + +static int ip101a_g_config_init(struct phy_device *phydev) +{ + int c; + + c = ip1xx_reset(phydev); + if (c < 0) + return c; + + /* INTR pin used: speed/link/duplex will cause an interrupt */ + c = phy_write(phydev, IP101A_G_IRQ_CONF_STATUS, IP101A_G_IRQ_DEFAULT); + if (c < 0) + return c; + + /* Enable Auto Power Saving mode */ + c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS); + c |= IP101A_G_APS_ON; + + return phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c); +} + +static int ip175c_read_status(struct phy_device *phydev) +{ + if (phydev->addr == 4) /* WAN port */ + genphy_read_status(phydev); + else + /* Don't need to read status for switch ports */ + phydev->irq = PHY_IGNORE_INTERRUPT; + + return 0; +} + +static int ip175c_config_aneg(struct phy_device *phydev) +{ + if (phydev->addr == 4) /* WAN port */ + genphy_config_aneg(phydev); + + return 0; +} + +static int ip101a_g_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, IP101A_G_IRQ_CONF_STATUS); + if (err < 0) + return err; + + return 0; +} + +static struct phy_driver icplus_driver[] = { +{ + .phy_id = 0x02430d80, + .name = "ICPlus IP175C", + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES, + .config_init = &ip175c_config_init, + .config_aneg = &ip175c_config_aneg, + .read_status = &ip175c_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = 0x02430d90, + .name = "ICPlus IP1001", + .phy_id_mask = 0x0ffffff0, + .features = PHY_GBIT_FEATURES | SUPPORTED_Pause | + SUPPORTED_Asym_Pause, + .config_init = &ip1001_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = 0x02430c54, + .name = "ICPlus IP101A/G", + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES | SUPPORTED_Pause | + SUPPORTED_Asym_Pause, + .flags = PHY_HAS_INTERRUPT, + .ack_interrupt = ip101a_g_ack_interrupt, + .config_init = &ip101a_g_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +} }; + +static int __init icplus_init(void) +{ + return phy_drivers_register(icplus_driver, + ARRAY_SIZE(icplus_driver)); +} + +static void __exit icplus_exit(void) +{ + phy_drivers_unregister(icplus_driver, + ARRAY_SIZE(icplus_driver)); +} + +module_init(icplus_init); +module_exit(icplus_exit); + +static struct mdio_device_id __maybe_unused icplus_tbl[] = { + { 0x02430d80, 0x0ffffff0 }, + { 0x02430d90, 0x0ffffff0 }, + { 0x02430c54, 0x0ffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, icplus_tbl); diff --git a/drivers/net/phy/lxt.c b/drivers/net/phy/lxt.c index bef79e454c3..9108f319170 100644 --- a/drivers/net/phy/lxt.c +++ b/drivers/net/phy/lxt.c @@ -13,13 +13,10 @@ * option) any later version. * */ -#include <linux/config.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/slab.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> @@ -56,6 +53,9 @@ #define MII_LXT971_ISR 19 /* Interrupt Status Register */ +/* register definitions for the 973 */ +#define MII_LXT973_PCR 16 /* Port Configuration Register */ +#define PCR_FIBER_SELECT 1 MODULE_DESCRIPTION("Intel LXT PHY driver"); MODULE_AUTHOR("Andy Fleming"); @@ -82,7 +82,7 @@ static int lxt970_config_intr(struct phy_device *phydev) { int err; - if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + 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); @@ -114,7 +114,7 @@ static int lxt971_config_intr(struct phy_device *phydev) { int err; - if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + 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); @@ -122,10 +122,155 @@ static int lxt971_config_intr(struct phy_device *phydev) return err; } -static struct phy_driver lxt970_driver = { - .phy_id = 0x07810000, +/* + * A2 version of LXT973 chip has an ERRATA: it randomly return the contents + * of the previous even register when you read a odd register regularly + */ + +static int lxt973a2_update_link(struct phy_device *phydev) +{ + int status; + int control; + int retry = 8; /* we try 8 times */ + + /* Do a fake read */ + status = phy_read(phydev, MII_BMSR); + + if (status < 0) + return status; + + control = phy_read(phydev, MII_BMCR); + if (control < 0) + return control; + + do { + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BMSR); + } while (status >= 0 && retry-- && status == control); + + if (status < 0) + return status; + + if ((status & BMSR_LSTATUS) == 0) + phydev->link = 0; + else + phydev->link = 1; + + return 0; +} + +static int lxt973a2_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 = lxt973a2_update_link(phydev); + if (err) + return err; + + if (AUTONEG_ENABLE == phydev->autoneg) { + int retry = 1; + + adv = phy_read(phydev, MII_ADVERTISE); + + if (adv < 0) + return adv; + + do { + lpa = phy_read(phydev, MII_LPA); + + if (lpa < 0) + return lpa; + + /* If both registers are equal, it is suspect but not + * impossible, hence a new try + */ + } while (lpa == adv && retry--); + + 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; +} + +static int lxt973_probe(struct phy_device *phydev) +{ + int val = phy_read(phydev, MII_LXT973_PCR); + + if (val & PCR_FIBER_SELECT) { + /* + * If fiber is selected, then the only correct setting + * is 100Mbps, full duplex, and auto negotiation off. + */ + val = phy_read(phydev, MII_BMCR); + val |= (BMCR_SPEED100 | BMCR_FULLDPLX); + val &= ~BMCR_ANENABLE; + phy_write(phydev, MII_BMCR, val); + /* Remember that the port is in fiber mode. */ + phydev->priv = lxt973_probe; + } else { + phydev->priv = NULL; + } + return 0; +} + +static int lxt973_config_aneg(struct phy_device *phydev) +{ + /* Do nothing if port is in fiber mode. */ + return phydev->priv ? 0 : genphy_config_aneg(phydev); +} + +static struct phy_driver lxt97x_driver[] = { +{ + .phy_id = 0x78100000, .name = "LXT970", - .phy_id_mask = 0x0fffffff, + .phy_id_mask = 0xfffffff0, .features = PHY_BASIC_FEATURES, .flags = PHY_HAS_INTERRUPT, .config_init = lxt970_config_init, @@ -133,46 +278,60 @@ static struct phy_driver lxt970_driver = { .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, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = 0x001378e0, .name = "LXT971", - .phy_id_mask = 0x0fffffff, + .phy_id_mask = 0xfffffff0, .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,}, -}; + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = 0x00137a10, + .name = "LXT973-A2", + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .flags = 0, + .probe = lxt973_probe, + .config_aneg = lxt973_config_aneg, + .read_status = lxt973a2_read_status, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = 0x00137a10, + .name = "LXT973", + .phy_id_mask = 0xfffffff0, + .features = PHY_BASIC_FEATURES, + .flags = 0, + .probe = lxt973_probe, + .config_aneg = lxt973_config_aneg, + .read_status = genphy_read_status, + .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; + return phy_drivers_register(lxt97x_driver, + ARRAY_SIZE(lxt97x_driver)); } static void __exit lxt_exit(void) { - phy_driver_unregister(&lxt970_driver); - phy_driver_unregister(&lxt971_driver); + phy_drivers_unregister(lxt97x_driver, + ARRAY_SIZE(lxt97x_driver)); } module_init(lxt_init); module_exit(lxt_exit); + +static struct mdio_device_id __maybe_unused lxt_tbl[] = { + { 0x78100000, 0xfffffff0 }, + { 0x001378e0, 0xfffffff0 }, + { 0x00137a10, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, lxt_tbl); diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index a2d6386d13b..bd37e45c89c 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -7,19 +7,18 @@ * * Copyright (c) 2004 Freescale Semiconductor, Inc. * + * Copyright (c) 2013 Michael Stapelberg <michael@stapelberg.de> + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ -#include <linux/config.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/slab.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> @@ -32,10 +31,14 @@ #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/phy.h> +#include <linux/marvell_phy.h> +#include <linux/of.h> -#include <asm/io.h> +#include <linux/io.h> #include <asm/irq.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> + +#define MII_MARVELL_PHY_PAGE 22 #define MII_M1011_IEVENT 0x13 #define MII_M1011_IEVENT_CLEAR 0x0000 @@ -44,6 +47,78 @@ #define MII_M1011_IMASK_INIT 0x6400 #define MII_M1011_IMASK_CLEAR 0x0000 +#define MII_M1011_PHY_SCR 0x10 +#define MII_M1011_PHY_SCR_AUTO_CROSS 0x0060 + +#define MII_M1145_PHY_EXT_CR 0x14 +#define MII_M1145_RGMII_RX_DELAY 0x0080 +#define MII_M1145_RGMII_TX_DELAY 0x0002 + +#define MII_M1111_PHY_LED_CONTROL 0x18 +#define MII_M1111_PHY_LED_DIRECT 0x4100 +#define MII_M1111_PHY_LED_COMBINE 0x411c +#define MII_M1111_PHY_EXT_CR 0x14 +#define MII_M1111_RX_DELAY 0x80 +#define MII_M1111_TX_DELAY 0x2 +#define MII_M1111_PHY_EXT_SR 0x1b + +#define MII_M1111_HWCFG_MODE_MASK 0xf +#define MII_M1111_HWCFG_MODE_COPPER_RGMII 0xb +#define MII_M1111_HWCFG_MODE_FIBER_RGMII 0x3 +#define MII_M1111_HWCFG_MODE_SGMII_NO_CLK 0x4 +#define MII_M1111_HWCFG_MODE_COPPER_RTBI 0x9 +#define MII_M1111_HWCFG_FIBER_COPPER_AUTO 0x8000 +#define MII_M1111_HWCFG_FIBER_COPPER_RES 0x2000 + +#define MII_M1111_COPPER 0 +#define MII_M1111_FIBER 1 + +#define MII_88E1121_PHY_MSCR_PAGE 2 +#define MII_88E1121_PHY_MSCR_REG 21 +#define MII_88E1121_PHY_MSCR_RX_DELAY BIT(5) +#define MII_88E1121_PHY_MSCR_TX_DELAY BIT(4) +#define MII_88E1121_PHY_MSCR_DELAY_MASK (~(0x3 << 4)) + +#define MII_88E1318S_PHY_MSCR1_REG 16 +#define MII_88E1318S_PHY_MSCR1_PAD_ODD BIT(6) + +/* Copper Specific Interrupt Enable Register */ +#define MII_88E1318S_PHY_CSIER 0x12 +/* WOL Event Interrupt Enable */ +#define MII_88E1318S_PHY_CSIER_WOL_EIE BIT(7) + +/* LED Timer Control Register */ +#define MII_88E1318S_PHY_LED_PAGE 0x03 +#define MII_88E1318S_PHY_LED_TCR 0x12 +#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15) +#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7) +#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11) + +/* Magic Packet MAC address registers */ +#define MII_88E1318S_PHY_MAGIC_PACKET_WORD2 0x17 +#define MII_88E1318S_PHY_MAGIC_PACKET_WORD1 0x18 +#define MII_88E1318S_PHY_MAGIC_PACKET_WORD0 0x19 + +#define MII_88E1318S_PHY_WOL_PAGE 0x11 +#define MII_88E1318S_PHY_WOL_CTRL 0x10 +#define MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS BIT(12) +#define MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE BIT(14) + +#define MII_88E1121_PHY_LED_CTRL 16 +#define MII_88E1121_PHY_LED_PAGE 3 +#define MII_88E1121_PHY_LED_DEF 0x0030 + +#define MII_M1011_PHY_STATUS 0x11 +#define MII_M1011_PHY_STATUS_1000 0x8000 +#define MII_M1011_PHY_STATUS_100 0x4000 +#define MII_M1011_PHY_STATUS_SPD_MASK 0xc000 +#define MII_M1011_PHY_STATUS_FULLDUPLEX 0x2000 +#define MII_M1011_PHY_STATUS_RESOLVED 0x0800 +#define MII_M1011_PHY_STATUS_LINK 0x0400 + +#define MII_M1116R_CONTROL_REG_MAC 21 + + MODULE_DESCRIPTION("Marvell PHY driver"); MODULE_AUTHOR("Andy Fleming"); MODULE_LICENSE("GPL"); @@ -65,7 +140,7 @@ static int marvell_config_intr(struct phy_device *phydev) { int err; - if(phydev->interrupts == PHY_INTERRUPT_ENABLED) + 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); @@ -105,35 +180,906 @@ static int marvell_config_aneg(struct phy_device *phydev) if (err < 0) return err; + err = phy_write(phydev, MII_M1011_PHY_SCR, + MII_M1011_PHY_SCR_AUTO_CROSS); + if (err < 0) + return err; + + err = phy_write(phydev, MII_M1111_PHY_LED_CONTROL, + MII_M1111_PHY_LED_DIRECT); + if (err < 0) + return err; + + err = genphy_config_aneg(phydev); + if (err < 0) + return err; + + if (phydev->autoneg != AUTONEG_ENABLE) { + int bmcr; + + /* + * A write to speed/duplex bits (that is performed by + * genphy_config_aneg() call above) must be followed by + * a software reset. Otherwise, the write has no effect. + */ + bmcr = phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + + err = phy_write(phydev, MII_BMCR, bmcr | BMCR_RESET); + if (err < 0) + return err; + } + + return 0; +} + +#ifdef CONFIG_OF_MDIO +/* + * Set and/or override some configuration registers based on the + * marvell,reg-init property stored in the of_node for the phydev. + * + * marvell,reg-init = <reg-page reg mask value>,...; + * + * There may be one or more sets of <reg-page reg mask value>: + * + * reg-page: which register bank to use. + * reg: the register. + * mask: if non-zero, ANDed with existing register value. + * value: ORed with the masked value and written to the regiser. + * + */ +static int marvell_of_reg_init(struct phy_device *phydev) +{ + const __be32 *paddr; + int len, i, saved_page, current_page, page_changed, ret; + + if (!phydev->dev.of_node) + return 0; + + paddr = of_get_property(phydev->dev.of_node, "marvell,reg-init", &len); + if (!paddr || len < (4 * sizeof(*paddr))) + return 0; + + saved_page = phy_read(phydev, MII_MARVELL_PHY_PAGE); + if (saved_page < 0) + return saved_page; + page_changed = 0; + current_page = saved_page; + + ret = 0; + len /= sizeof(*paddr); + for (i = 0; i < len - 3; i += 4) { + u16 reg_page = be32_to_cpup(paddr + i); + u16 reg = be32_to_cpup(paddr + i + 1); + u16 mask = be32_to_cpup(paddr + i + 2); + u16 val_bits = be32_to_cpup(paddr + i + 3); + int val; + + if (reg_page != current_page) { + current_page = reg_page; + page_changed = 1; + ret = phy_write(phydev, MII_MARVELL_PHY_PAGE, reg_page); + if (ret < 0) + goto err; + } + + val = 0; + if (mask) { + val = phy_read(phydev, reg); + if (val < 0) { + ret = val; + goto err; + } + val &= mask; + } + val |= val_bits; + + ret = phy_write(phydev, reg, val); + if (ret < 0) + goto err; + + } +err: + if (page_changed) { + i = phy_write(phydev, MII_MARVELL_PHY_PAGE, saved_page); + if (ret == 0) + ret = i; + } + return ret; +} +#else +static int marvell_of_reg_init(struct phy_device *phydev) +{ + return 0; +} +#endif /* CONFIG_OF_MDIO */ + +static int m88e1121_config_aneg(struct phy_device *phydev) +{ + int err, oldpage, mscr; + + oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, + MII_88E1121_PHY_MSCR_PAGE); + if (err < 0) + return err; + + if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)) { + + mscr = phy_read(phydev, MII_88E1121_PHY_MSCR_REG) & + MII_88E1121_PHY_MSCR_DELAY_MASK; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) + mscr |= (MII_88E1121_PHY_MSCR_RX_DELAY | + MII_88E1121_PHY_MSCR_TX_DELAY); + else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) + mscr |= MII_88E1121_PHY_MSCR_RX_DELAY; + else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) + mscr |= MII_88E1121_PHY_MSCR_TX_DELAY; + + err = phy_write(phydev, MII_88E1121_PHY_MSCR_REG, mscr); + if (err < 0) + return err; + } + + phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage); + + err = phy_write(phydev, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + + err = phy_write(phydev, MII_M1011_PHY_SCR, + MII_M1011_PHY_SCR_AUTO_CROSS); + if (err < 0) + return err; + + oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); + + phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_88E1121_PHY_LED_PAGE); + phy_write(phydev, MII_88E1121_PHY_LED_CTRL, MII_88E1121_PHY_LED_DEF); + phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage); err = genphy_config_aneg(phydev); return err; } +static int m88e1318_config_aneg(struct phy_device *phydev) +{ + int err, oldpage, mscr; + + oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, + MII_88E1121_PHY_MSCR_PAGE); + if (err < 0) + return err; + + mscr = phy_read(phydev, MII_88E1318S_PHY_MSCR1_REG); + mscr |= MII_88E1318S_PHY_MSCR1_PAD_ODD; + + err = phy_write(phydev, MII_88E1318S_PHY_MSCR1_REG, mscr); + if (err < 0) + return err; + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage); + if (err < 0) + return err; + + return m88e1121_config_aneg(phydev); +} + +static int m88e1510_config_aneg(struct phy_device *phydev) +{ + int err; + + err = m88e1318_config_aneg(phydev); + if (err < 0) + return err; + + return marvell_of_reg_init(phydev); +} + +static int m88e1116r_config_init(struct phy_device *phydev) +{ + int temp; + int err; + + temp = phy_read(phydev, MII_BMCR); + temp |= BMCR_RESET; + err = phy_write(phydev, MII_BMCR, temp); + if (err < 0) + return err; + + mdelay(500); + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0); + if (err < 0) + return err; + + temp = phy_read(phydev, MII_M1011_PHY_SCR); + temp |= (7 << 12); /* max number of gigabit attempts */ + temp |= (1 << 11); /* enable downshift */ + temp |= MII_M1011_PHY_SCR_AUTO_CROSS; + err = phy_write(phydev, MII_M1011_PHY_SCR, temp); + if (err < 0) + return err; + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 2); + if (err < 0) + return err; + temp = phy_read(phydev, MII_M1116R_CONTROL_REG_MAC); + temp |= (1 << 5); + temp |= (1 << 4); + err = phy_write(phydev, MII_M1116R_CONTROL_REG_MAC, temp); + if (err < 0) + return err; + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0); + if (err < 0) + return err; + + temp = phy_read(phydev, MII_BMCR); + temp |= BMCR_RESET; + err = phy_write(phydev, MII_BMCR, temp); + if (err < 0) + return err; + + mdelay(500); + + return 0; +} + +static int m88e1111_config_init(struct phy_device *phydev) +{ + int err; + int temp; + + if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)) { + + temp = phy_read(phydev, MII_M1111_PHY_EXT_CR); + if (temp < 0) + return temp; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) { + temp |= (MII_M1111_RX_DELAY | MII_M1111_TX_DELAY); + } else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { + temp &= ~MII_M1111_TX_DELAY; + temp |= MII_M1111_RX_DELAY; + } else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { + temp &= ~MII_M1111_RX_DELAY; + temp |= MII_M1111_TX_DELAY; + } + + err = phy_write(phydev, MII_M1111_PHY_EXT_CR, temp); + if (err < 0) + return err; + + temp = phy_read(phydev, MII_M1111_PHY_EXT_SR); + if (temp < 0) + return temp; + + temp &= ~(MII_M1111_HWCFG_MODE_MASK); + + if (temp & MII_M1111_HWCFG_FIBER_COPPER_RES) + temp |= MII_M1111_HWCFG_MODE_FIBER_RGMII; + else + temp |= MII_M1111_HWCFG_MODE_COPPER_RGMII; + + err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); + if (err < 0) + return err; + } + + if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { + temp = phy_read(phydev, MII_M1111_PHY_EXT_SR); + if (temp < 0) + return temp; + + temp &= ~(MII_M1111_HWCFG_MODE_MASK); + temp |= MII_M1111_HWCFG_MODE_SGMII_NO_CLK; + temp |= MII_M1111_HWCFG_FIBER_COPPER_AUTO; + + err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); + if (err < 0) + return err; + } + + if (phydev->interface == PHY_INTERFACE_MODE_RTBI) { + temp = phy_read(phydev, MII_M1111_PHY_EXT_CR); + if (temp < 0) + return temp; + temp |= (MII_M1111_RX_DELAY | MII_M1111_TX_DELAY); + err = phy_write(phydev, MII_M1111_PHY_EXT_CR, temp); + if (err < 0) + return err; + + temp = phy_read(phydev, MII_M1111_PHY_EXT_SR); + if (temp < 0) + return temp; + temp &= ~(MII_M1111_HWCFG_MODE_MASK | MII_M1111_HWCFG_FIBER_COPPER_RES); + temp |= 0x7 | MII_M1111_HWCFG_FIBER_COPPER_AUTO; + err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); + if (err < 0) + return err; + + /* soft reset */ + err = phy_write(phydev, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + do + temp = phy_read(phydev, MII_BMCR); + while (temp & BMCR_RESET); + + temp = phy_read(phydev, MII_M1111_PHY_EXT_SR); + if (temp < 0) + return temp; + temp &= ~(MII_M1111_HWCFG_MODE_MASK | MII_M1111_HWCFG_FIBER_COPPER_RES); + temp |= MII_M1111_HWCFG_MODE_COPPER_RTBI | MII_M1111_HWCFG_FIBER_COPPER_AUTO; + err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); + if (err < 0) + return err; + } + + err = marvell_of_reg_init(phydev); + if (err < 0) + return err; + + return phy_write(phydev, MII_BMCR, BMCR_RESET); +} + +static int m88e1118_config_aneg(struct phy_device *phydev) +{ + int err; + + err = phy_write(phydev, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + + err = phy_write(phydev, MII_M1011_PHY_SCR, + MII_M1011_PHY_SCR_AUTO_CROSS); + if (err < 0) + return err; + + err = genphy_config_aneg(phydev); + return 0; +} + +static int m88e1118_config_init(struct phy_device *phydev) +{ + int err; + + /* Change address */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0002); + if (err < 0) + return err; + + /* Enable 1000 Mbit */ + err = phy_write(phydev, 0x15, 0x1070); + if (err < 0) + return err; + + /* Change address */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0003); + if (err < 0) + return err; + + /* Adjust LED Control */ + if (phydev->dev_flags & MARVELL_PHY_M1118_DNS323_LEDS) + err = phy_write(phydev, 0x10, 0x1100); + else + err = phy_write(phydev, 0x10, 0x021e); + if (err < 0) + return err; + + err = marvell_of_reg_init(phydev); + if (err < 0) + return err; + + /* Reset address */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0); + if (err < 0) + return err; + + return phy_write(phydev, MII_BMCR, BMCR_RESET); +} + +static int m88e1149_config_init(struct phy_device *phydev) +{ + int err; + + /* Change address */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0002); + if (err < 0) + return err; + + /* Enable 1000 Mbit */ + err = phy_write(phydev, 0x15, 0x1048); + if (err < 0) + return err; + + err = marvell_of_reg_init(phydev); + if (err < 0) + return err; + + /* Reset address */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0); + if (err < 0) + return err; + + return phy_write(phydev, MII_BMCR, BMCR_RESET); +} + +static int m88e1145_config_init(struct phy_device *phydev) +{ + int err; + + /* Take care of errata E0 & E1 */ + err = phy_write(phydev, 0x1d, 0x001b); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1e, 0x418f); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1d, 0x0016); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1e, 0xa2da); + if (err < 0) + return err; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) { + int temp = phy_read(phydev, MII_M1145_PHY_EXT_CR); + if (temp < 0) + return temp; -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,}, + temp |= (MII_M1145_RGMII_RX_DELAY | MII_M1145_RGMII_TX_DELAY); + + err = phy_write(phydev, MII_M1145_PHY_EXT_CR, temp); + if (err < 0) + return err; + + if (phydev->dev_flags & MARVELL_PHY_M1145_FLAGS_RESISTANCE) { + err = phy_write(phydev, 0x1d, 0x0012); + if (err < 0) + return err; + + temp = phy_read(phydev, 0x1e); + if (temp < 0) + return temp; + + temp &= 0xf03f; + temp |= 2 << 9; /* 36 ohm */ + temp |= 2 << 6; /* 39 ohm */ + + err = phy_write(phydev, 0x1e, temp); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1d, 0x3); + if (err < 0) + return err; + + err = phy_write(phydev, 0x1e, 0x8000); + if (err < 0) + return err; + } + } + + err = marvell_of_reg_init(phydev); + if (err < 0) + return err; + + return 0; +} + +/* marvell_read_status + * + * Generic status code does not detect Fiber correctly! + * 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. + */ +static int marvell_read_status(struct phy_device *phydev) +{ + int adv; + int err; + int lpa; + int status = 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) { + status = phy_read(phydev, MII_M1011_PHY_STATUS); + if (status < 0) + return status; + + lpa = phy_read(phydev, MII_LPA); + if (lpa < 0) + return lpa; + + adv = phy_read(phydev, MII_ADVERTISE); + if (adv < 0) + return adv; + + lpa &= adv; + + if (status & MII_M1011_PHY_STATUS_FULLDUPLEX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + + status = status & MII_M1011_PHY_STATUS_SPD_MASK; + phydev->pause = phydev->asym_pause = 0; + + switch (status) { + case MII_M1011_PHY_STATUS_1000: + phydev->speed = SPEED_1000; + break; + + case MII_M1011_PHY_STATUS_100: + phydev->speed = SPEED_100; + break; + + default: + phydev->speed = SPEED_10; + break; + } + + 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; +} + +static int m88e1121_did_interrupt(struct phy_device *phydev) +{ + int imask; + + imask = phy_read(phydev, MII_M1011_IEVENT); + + if (imask & MII_M1011_IMASK_INIT) + return 1; + + return 0; +} + +static void m88e1318_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + + if (phy_write(phydev, MII_MARVELL_PHY_PAGE, + MII_88E1318S_PHY_WOL_PAGE) < 0) + return; + + if (phy_read(phydev, MII_88E1318S_PHY_WOL_CTRL) & + MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE) + wol->wolopts |= WAKE_MAGIC; + + if (phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x00) < 0) + return; +} + +static int m88e1318_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + int err, oldpage, temp; + + oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); + + if (wol->wolopts & WAKE_MAGIC) { + /* Explicitly switch to page 0x00, just to be sure */ + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x00); + if (err < 0) + return err; + + /* Enable the WOL interrupt */ + temp = phy_read(phydev, MII_88E1318S_PHY_CSIER); + temp |= MII_88E1318S_PHY_CSIER_WOL_EIE; + err = phy_write(phydev, MII_88E1318S_PHY_CSIER, temp); + if (err < 0) + return err; + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, + MII_88E1318S_PHY_LED_PAGE); + if (err < 0) + return err; + + /* Setup LED[2] as interrupt pin (active low) */ + temp = phy_read(phydev, MII_88E1318S_PHY_LED_TCR); + temp &= ~MII_88E1318S_PHY_LED_TCR_FORCE_INT; + temp |= MII_88E1318S_PHY_LED_TCR_INTn_ENABLE; + temp |= MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW; + err = phy_write(phydev, MII_88E1318S_PHY_LED_TCR, temp); + if (err < 0) + return err; + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, + MII_88E1318S_PHY_WOL_PAGE); + if (err < 0) + return err; + + /* Store the device address for the magic packet */ + err = phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD2, + ((phydev->attached_dev->dev_addr[5] << 8) | + phydev->attached_dev->dev_addr[4])); + if (err < 0) + return err; + err = phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD1, + ((phydev->attached_dev->dev_addr[3] << 8) | + phydev->attached_dev->dev_addr[2])); + if (err < 0) + return err; + err = phy_write(phydev, MII_88E1318S_PHY_MAGIC_PACKET_WORD0, + ((phydev->attached_dev->dev_addr[1] << 8) | + phydev->attached_dev->dev_addr[0])); + if (err < 0) + return err; + + /* Clear WOL status and enable magic packet matching */ + temp = phy_read(phydev, MII_88E1318S_PHY_WOL_CTRL); + temp |= MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS; + temp |= MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE; + err = phy_write(phydev, MII_88E1318S_PHY_WOL_CTRL, temp); + if (err < 0) + return err; + } else { + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, + MII_88E1318S_PHY_WOL_PAGE); + if (err < 0) + return err; + + /* Clear WOL status and disable magic packet matching */ + temp = phy_read(phydev, MII_88E1318S_PHY_WOL_CTRL); + temp |= MII_88E1318S_PHY_WOL_CTRL_CLEAR_WOL_STATUS; + temp &= ~MII_88E1318S_PHY_WOL_CTRL_MAGIC_PACKET_MATCH_ENABLE; + err = phy_write(phydev, MII_88E1318S_PHY_WOL_CTRL, temp); + if (err < 0) + return err; + } + + err = phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage); + if (err < 0) + return err; + + return 0; +} + +static struct phy_driver marvell_drivers[] = { + { + .phy_id = MARVELL_PHY_ID_88E1101, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .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, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1112, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1112", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1111_config_init, + .config_aneg = &marvell_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1111, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1111", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1111_config_init, + .config_aneg = &marvell_config_aneg, + .read_status = &marvell_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1118, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1118", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1118_config_init, + .config_aneg = &m88e1118_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = {.owner = THIS_MODULE,}, + }, + { + .phy_id = MARVELL_PHY_ID_88E1121R, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1121R", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &m88e1121_config_aneg, + .read_status = &marvell_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .did_interrupt = &m88e1121_did_interrupt, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1318S, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1318S", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &m88e1318_config_aneg, + .read_status = &marvell_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .did_interrupt = &m88e1121_did_interrupt, + .get_wol = &m88e1318_get_wol, + .set_wol = &m88e1318_set_wol, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1145, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1145", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1145_config_init, + .config_aneg = &marvell_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1149R, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1149R", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1149_config_init, + .config_aneg = &m88e1118_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1240, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1240", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1111_config_init, + .config_aneg = &marvell_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1116R, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1116R", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &m88e1116r_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, + { + .phy_id = MARVELL_PHY_ID_88E1510, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1510", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &m88e1510_config_aneg, + .read_status = &marvell_read_status, + .ack_interrupt = &marvell_ack_interrupt, + .config_intr = &marvell_config_intr, + .did_interrupt = &m88e1121_did_interrupt, + .resume = &genphy_resume, + .suspend = &genphy_suspend, + .driver = { .owner = THIS_MODULE }, + }, }; static int __init marvell_init(void) { - return phy_driver_register(&m88e1101_driver); + return phy_drivers_register(marvell_drivers, + ARRAY_SIZE(marvell_drivers)); } static void __exit marvell_exit(void) { - phy_driver_unregister(&m88e1101_driver); + phy_drivers_unregister(marvell_drivers, + ARRAY_SIZE(marvell_drivers)); } module_init(marvell_init); module_exit(marvell_exit); + +static struct mdio_device_id __maybe_unused marvell_tbl[] = { + { MARVELL_PHY_ID_88E1101, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1112, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1111, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1118, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1121R, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1145, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1149R, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1240, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1318S, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1116R, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E1510, MARVELL_PHY_ID_MASK }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, marvell_tbl); diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c new file mode 100644 index 00000000000..daec9b05d16 --- /dev/null +++ b/drivers/net/phy/mdio-bitbang.c @@ -0,0 +1,241 @@ +/* + * Bitbanged MDIO support. + * + * Author: Scott Wood <scottwood@freescale.com> + * Copyright (c) 2007 Freescale Semiconductor + * + * Based on CPM2 MDIO code which is: + * + * Copyright (c) 2003 Intracom S.A. + * by Pantelis Antoniou <panto@intracom.gr> + * + * 2005 (c) MontaVista Software, Inc. + * Vitaly Bordug <vbordug@ru.mvista.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/mdio-bitbang.h> +#include <linux/types.h> +#include <linux/delay.h> + +#define MDIO_READ 2 +#define MDIO_WRITE 1 + +#define MDIO_C45 (1<<15) +#define MDIO_C45_ADDR (MDIO_C45 | 0) +#define MDIO_C45_READ (MDIO_C45 | 3) +#define MDIO_C45_WRITE (MDIO_C45 | 1) + +#define MDIO_SETUP_TIME 10 +#define MDIO_HOLD_TIME 10 + +/* Minimum MDC period is 400 ns, plus some margin for error. MDIO_DELAY + * is done twice per period. + */ +#define MDIO_DELAY 250 + +/* The PHY may take up to 300 ns to produce data, plus some margin + * for error. + */ +#define MDIO_READ_DELAY 350 + +/* MDIO must already be configured as output. */ +static void mdiobb_send_bit(struct mdiobb_ctrl *ctrl, int val) +{ + const struct mdiobb_ops *ops = ctrl->ops; + + ops->set_mdio_data(ctrl, val); + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 1); + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 0); +} + +/* MDIO must already be configured as input. */ +static int mdiobb_get_bit(struct mdiobb_ctrl *ctrl) +{ + const struct mdiobb_ops *ops = ctrl->ops; + + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 1); + ndelay(MDIO_READ_DELAY); + ops->set_mdc(ctrl, 0); + + return ops->get_mdio_data(ctrl); +} + +/* MDIO must already be configured as output. */ +static void mdiobb_send_num(struct mdiobb_ctrl *ctrl, u16 val, int bits) +{ + int i; + + for (i = bits - 1; i >= 0; i--) + mdiobb_send_bit(ctrl, (val >> i) & 1); +} + +/* MDIO must already be configured as input. */ +static u16 mdiobb_get_num(struct mdiobb_ctrl *ctrl, int bits) +{ + int i; + u16 ret = 0; + + for (i = bits - 1; i >= 0; i--) { + ret <<= 1; + ret |= mdiobb_get_bit(ctrl); + } + + return ret; +} + +/* Utility to send the preamble, address, and + * register (common to read and write). + */ +static void mdiobb_cmd(struct mdiobb_ctrl *ctrl, int op, u8 phy, u8 reg) +{ + const struct mdiobb_ops *ops = ctrl->ops; + int i; + + ops->set_mdio_dir(ctrl, 1); + + /* + * Send a 32 bit preamble ('1's) with an extra '1' bit for good + * measure. The IEEE spec says this is a PHY optional + * requirement. The AMD 79C874 requires one after power up and + * one after a MII communications error. This means that we are + * doing more preambles than we need, but it is safer and will be + * much more robust. + */ + + for (i = 0; i < 32; i++) + mdiobb_send_bit(ctrl, 1); + + /* send the start bit (01) and the read opcode (10) or write (10). + Clause 45 operation uses 00 for the start and 11, 10 for + read/write */ + mdiobb_send_bit(ctrl, 0); + if (op & MDIO_C45) + mdiobb_send_bit(ctrl, 0); + else + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, (op >> 1) & 1); + mdiobb_send_bit(ctrl, (op >> 0) & 1); + + mdiobb_send_num(ctrl, phy, 5); + mdiobb_send_num(ctrl, reg, 5); +} + +/* In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the + lower 16 bits of the 21 bit address. This transfer is done identically to a + MDIO_WRITE except for a different code. To enable clause 45 mode or + MII_ADDR_C45 into the address. Theoretically clause 45 and normal devices + can exist on the same bus. Normal devices should ignore the MDIO_ADDR + phase. */ +static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr) +{ + unsigned int dev_addr = (addr >> 16) & 0x1F; + unsigned int reg = addr & 0xFFFF; + mdiobb_cmd(ctrl, MDIO_C45_ADDR, phy, dev_addr); + + /* send the turnaround (10) */ + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, 0); + + mdiobb_send_num(ctrl, reg, 16); + + ctrl->ops->set_mdio_dir(ctrl, 0); + mdiobb_get_bit(ctrl); + + return dev_addr; +} + +static int mdiobb_read(struct mii_bus *bus, int phy, int reg) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + int ret, i; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); + mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg); + } else + mdiobb_cmd(ctrl, MDIO_READ, phy, reg); + + ctrl->ops->set_mdio_dir(ctrl, 0); + + /* check the turnaround bit: the PHY should be driving it to zero */ + if (mdiobb_get_bit(ctrl) != 0) { + /* PHY didn't drive TA low -- flush any bits it + * may be trying to send. + */ + for (i = 0; i < 32; i++) + mdiobb_get_bit(ctrl); + + return 0xffff; + } + + ret = mdiobb_get_num(ctrl, 16); + mdiobb_get_bit(ctrl); + return ret; +} + +static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); + mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg); + } else + mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg); + + /* send the turnaround (10) */ + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, 0); + + mdiobb_send_num(ctrl, val, 16); + + ctrl->ops->set_mdio_dir(ctrl, 0); + mdiobb_get_bit(ctrl); + return 0; +} + +static int mdiobb_reset(struct mii_bus *bus) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + if (ctrl->reset) + ctrl->reset(bus); + return 0; +} + +struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl) +{ + struct mii_bus *bus; + + bus = mdiobus_alloc(); + if (!bus) + return NULL; + + __module_get(ctrl->ops->owner); + + bus->read = mdiobb_read; + bus->write = mdiobb_write; + bus->reset = mdiobb_reset; + bus->priv = ctrl; + + return bus; +} +EXPORT_SYMBOL(alloc_mdio_bitbang); + +void free_mdio_bitbang(struct mii_bus *bus) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + module_put(ctrl->ops->owner); + mdiobus_free(bus); +} +EXPORT_SYMBOL(free_mdio_bitbang); + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c new file mode 100644 index 00000000000..5f1a2250018 --- /dev/null +++ b/drivers/net/phy/mdio-gpio.c @@ -0,0 +1,272 @@ +/* + * GPIO based MDIO bitbang driver. + * Supports OpenFirmware. + * + * Copyright (c) 2008 CSE Semaphore Belgium. + * by Laurent Pinchart <laurentp@cse-semaphore.com> + * + * Copyright (C) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt> + * + * Based on earlier work by + * + * Copyright (c) 2003 Intracom S.A. + * by Pantelis Antoniou <panto@intracom.gr> + * + * 2005 (c) MontaVista Software, Inc. + * Vitaly Bordug <vbordug@ru.mvista.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/mdio-gpio.h> + +#include <linux/of_gpio.h> +#include <linux/of_mdio.h> + +struct mdio_gpio_info { + struct mdiobb_ctrl ctrl; + int mdc, mdio, mdo; + int mdc_active_low, mdio_active_low, mdo_active_low; +}; + +static void *mdio_gpio_of_get_data(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mdio_gpio_platform_data *pdata; + enum of_gpio_flags flags; + int ret; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + ret = of_get_gpio_flags(np, 0, &flags); + if (ret < 0) + return NULL; + + pdata->mdc = ret; + pdata->mdc_active_low = flags & OF_GPIO_ACTIVE_LOW; + + ret = of_get_gpio_flags(np, 1, &flags); + if (ret < 0) + return NULL; + pdata->mdio = ret; + pdata->mdio_active_low = flags & OF_GPIO_ACTIVE_LOW; + + ret = of_get_gpio_flags(np, 2, &flags); + if (ret > 0) { + pdata->mdo = ret; + pdata->mdo_active_low = flags & OF_GPIO_ACTIVE_LOW; + } + + return pdata; +} + +static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + if (bitbang->mdo) { + /* Separate output pin. Always set its value to high + * when changing direction. If direction is input, + * assume the pin serves as pull-up. If direction is + * output, the default value is high. + */ + gpio_set_value(bitbang->mdo, 1 ^ bitbang->mdo_active_low); + return; + } + + if (dir) + gpio_direction_output(bitbang->mdio, + 1 ^ bitbang->mdio_active_low); + else + gpio_direction_input(bitbang->mdio); +} + +static int mdio_get(struct mdiobb_ctrl *ctrl) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + return gpio_get_value(bitbang->mdio) ^ bitbang->mdio_active_low; +} + +static void mdio_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + if (bitbang->mdo) + gpio_set_value(bitbang->mdo, what ^ bitbang->mdo_active_low); + else + gpio_set_value(bitbang->mdio, what ^ bitbang->mdio_active_low); +} + +static void mdc_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + gpio_set_value(bitbang->mdc, what ^ bitbang->mdc_active_low); +} + +static struct mdiobb_ops mdio_gpio_ops = { + .owner = THIS_MODULE, + .set_mdc = mdc_set, + .set_mdio_dir = mdio_dir, + .set_mdio_data = mdio_set, + .get_mdio_data = mdio_get, +}; + +static struct mii_bus *mdio_gpio_bus_init(struct device *dev, + struct mdio_gpio_platform_data *pdata, + int bus_id) +{ + struct mii_bus *new_bus; + struct mdio_gpio_info *bitbang; + int i; + + bitbang = devm_kzalloc(dev, sizeof(*bitbang), GFP_KERNEL); + if (!bitbang) + goto out; + + bitbang->ctrl.ops = &mdio_gpio_ops; + bitbang->ctrl.reset = pdata->reset; + bitbang->mdc = pdata->mdc; + bitbang->mdc_active_low = pdata->mdc_active_low; + bitbang->mdio = pdata->mdio; + bitbang->mdio_active_low = pdata->mdio_active_low; + bitbang->mdo = pdata->mdo; + bitbang->mdo_active_low = pdata->mdo_active_low; + + new_bus = alloc_mdio_bitbang(&bitbang->ctrl); + if (!new_bus) + goto out; + + new_bus->name = "GPIO Bitbanged MDIO", + + new_bus->phy_mask = pdata->phy_mask; + new_bus->irq = pdata->irqs; + new_bus->parent = dev; + + if (new_bus->phy_mask == ~0) + goto out_free_bus; + + for (i = 0; i < PHY_MAX_ADDR; i++) + if (!new_bus->irq[i]) + new_bus->irq[i] = PHY_POLL; + + snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id); + + if (devm_gpio_request(dev, bitbang->mdc, "mdc")) + goto out_free_bus; + + if (devm_gpio_request(dev, bitbang->mdio, "mdio")) + goto out_free_bus; + + if (bitbang->mdo) { + if (devm_gpio_request(dev, bitbang->mdo, "mdo")) + goto out_free_bus; + gpio_direction_output(bitbang->mdo, 1); + gpio_direction_input(bitbang->mdio); + } + + gpio_direction_output(bitbang->mdc, 0); + + dev_set_drvdata(dev, new_bus); + + return new_bus; + +out_free_bus: + free_mdio_bitbang(new_bus); +out: + return NULL; +} + +static void mdio_gpio_bus_deinit(struct device *dev) +{ + struct mii_bus *bus = dev_get_drvdata(dev); + + free_mdio_bitbang(bus); +} + +static void mdio_gpio_bus_destroy(struct device *dev) +{ + struct mii_bus *bus = dev_get_drvdata(dev); + + mdiobus_unregister(bus); + mdio_gpio_bus_deinit(dev); +} + +static int mdio_gpio_probe(struct platform_device *pdev) +{ + struct mdio_gpio_platform_data *pdata; + struct mii_bus *new_bus; + int ret, bus_id; + + if (pdev->dev.of_node) { + pdata = mdio_gpio_of_get_data(pdev); + bus_id = of_alias_get_id(pdev->dev.of_node, "mdio-gpio"); + if (bus_id < 0) { + dev_warn(&pdev->dev, "failed to get alias id\n"); + bus_id = 0; + } + } else { + pdata = dev_get_platdata(&pdev->dev); + bus_id = pdev->id; + } + + if (!pdata) + return -ENODEV; + + new_bus = mdio_gpio_bus_init(&pdev->dev, pdata, bus_id); + if (!new_bus) + return -ENODEV; + + if (pdev->dev.of_node) + ret = of_mdiobus_register(new_bus, pdev->dev.of_node); + else + ret = mdiobus_register(new_bus); + + if (ret) + mdio_gpio_bus_deinit(&pdev->dev); + + return ret; +} + +static int mdio_gpio_remove(struct platform_device *pdev) +{ + mdio_gpio_bus_destroy(&pdev->dev); + + return 0; +} + +static struct of_device_id mdio_gpio_of_match[] = { + { .compatible = "virtual,mdio-gpio", }, + { /* sentinel */ } +}; + +static struct platform_driver mdio_gpio_driver = { + .probe = mdio_gpio_probe, + .remove = mdio_gpio_remove, + .driver = { + .name = "mdio-gpio", + .owner = THIS_MODULE, + .of_match_table = mdio_gpio_of_match, + }, +}; + +module_platform_driver(mdio_gpio_driver); + +MODULE_ALIAS("platform:mdio-gpio"); +MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic driver for MDIO bus emulation using GPIO"); diff --git a/drivers/net/phy/mdio-moxart.c b/drivers/net/phy/mdio-moxart.c new file mode 100644 index 00000000000..f1fc51f655d --- /dev/null +++ b/drivers/net/phy/mdio-moxart.c @@ -0,0 +1,200 @@ +/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver + * + * Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_address.h> +#include <linux/of_mdio.h> +#include <linux/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#define REG_PHY_CTRL 0 +#define REG_PHY_WRITE_DATA 4 + +/* REG_PHY_CTRL */ +#define MIIWR BIT(27) /* init write sequence (auto cleared)*/ +#define MIIRD BIT(26) +#define REGAD_MASK 0x3e00000 +#define PHYAD_MASK 0x1f0000 +#define MIIRDATA_MASK 0xffff + +/* REG_PHY_WRITE_DATA */ +#define MIIWDATA_MASK 0xffff + +struct moxart_mdio_data { + void __iomem *base; +}; + +static int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct moxart_mdio_data *data = bus->priv; + u32 ctrl = 0; + unsigned int count = 5; + + dev_dbg(&bus->dev, "%s\n", __func__); + + ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) | + ((regnum << 21) & REGAD_MASK); + + writel(ctrl, data->base + REG_PHY_CTRL); + + do { + ctrl = readl(data->base + REG_PHY_CTRL); + + if (!(ctrl & MIIRD)) + return ctrl & MIIRDATA_MASK; + + mdelay(10); + count--; + } while (count > 0); + + dev_dbg(&bus->dev, "%s timed out\n", __func__); + + return -ETIMEDOUT; +} + +static int moxart_mdio_write(struct mii_bus *bus, int mii_id, + int regnum, u16 value) +{ + struct moxart_mdio_data *data = bus->priv; + u32 ctrl = 0; + unsigned int count = 5; + + dev_dbg(&bus->dev, "%s\n", __func__); + + ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) | + ((regnum << 21) & REGAD_MASK); + + value &= MIIWDATA_MASK; + + writel(value, data->base + REG_PHY_WRITE_DATA); + writel(ctrl, data->base + REG_PHY_CTRL); + + do { + ctrl = readl(data->base + REG_PHY_CTRL); + + if (!(ctrl & MIIWR)) + return 0; + + mdelay(10); + count--; + } while (count > 0); + + dev_dbg(&bus->dev, "%s timed out\n", __func__); + + return -ETIMEDOUT; +} + +static int moxart_mdio_reset(struct mii_bus *bus) +{ + int data, i; + + for (i = 0; i < PHY_MAX_ADDR; i++) { + data = moxart_mdio_read(bus, i, MII_BMCR); + if (data < 0) + continue; + + data |= BMCR_RESET; + if (moxart_mdio_write(bus, i, MII_BMCR, data) < 0) + continue; + } + + return 0; +} + +static int moxart_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mii_bus *bus; + struct moxart_mdio_data *data; + struct resource *res; + int ret, i; + + bus = mdiobus_alloc_size(sizeof(*data)); + if (!bus) + return -ENOMEM; + + bus->name = "MOXA ART Ethernet MII"; + bus->read = &moxart_mdio_read; + bus->write = &moxart_mdio_write; + bus->reset = &moxart_mdio_reset; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d-mii", pdev->name, pdev->id); + bus->parent = &pdev->dev; + + bus->irq = devm_kzalloc(&pdev->dev, sizeof(int) * PHY_MAX_ADDR, + GFP_KERNEL); + if (!bus->irq) { + ret = -ENOMEM; + goto err_out_free_mdiobus; + } + + /* Setting PHY_IGNORE_INTERRUPT here even if it has no effect, + * of_mdiobus_register() sets these PHY_POLL. + * Ideally, the interrupt from MAC controller could be used to + * detect link state changes, not polling, i.e. if there was + * a way phy_driver could set PHY_HAS_INTERRUPT but have that + * interrupt handled in ethernet drivercode. + */ + for (i = 0; i < PHY_MAX_ADDR; i++) + bus->irq[i] = PHY_IGNORE_INTERRUPT; + + data = bus->priv; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->base)) { + ret = PTR_ERR(data->base); + goto err_out_free_mdiobus; + } + + ret = of_mdiobus_register(bus, np); + if (ret < 0) + goto err_out_free_mdiobus; + + platform_set_drvdata(pdev, bus); + + return 0; + +err_out_free_mdiobus: + mdiobus_free(bus); + return ret; +} + +static int moxart_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + mdiobus_free(bus); + + return 0; +} + +static const struct of_device_id moxart_mdio_dt_ids[] = { + { .compatible = "moxa,moxart-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids); + +static struct platform_driver moxart_mdio_driver = { + .probe = moxart_mdio_probe, + .remove = moxart_mdio_remove, + .driver = { + .name = "moxart-mdio", + .of_match_table = moxart_mdio_dt_ids, + }, +}; + +module_platform_driver(moxart_mdio_driver); + +MODULE_DESCRIPTION("MOXA ART MDIO interface driver"); +MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-mux-gpio.c b/drivers/net/phy/mdio-mux-gpio.c new file mode 100644 index 00000000000..09669516349 --- /dev/null +++ b/drivers/net/phy/mdio-mux-gpio.c @@ -0,0 +1,140 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2011, 2012 Cavium, Inc. + */ + +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/of_mdio.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/mdio-mux.h> +#include <linux/of_gpio.h> + +#define DRV_VERSION "1.0" +#define DRV_DESCRIPTION "GPIO controlled MDIO bus multiplexer driver" + +#define MDIO_MUX_GPIO_MAX_BITS 8 + +struct mdio_mux_gpio_state { + int gpio[MDIO_MUX_GPIO_MAX_BITS]; + unsigned int num_gpios; + void *mux_handle; +}; + +static int mdio_mux_gpio_switch_fn(int current_child, int desired_child, + void *data) +{ + int change; + unsigned int n; + struct mdio_mux_gpio_state *s = data; + + if (current_child == desired_child) + return 0; + + change = current_child == -1 ? -1 : current_child ^ desired_child; + + for (n = 0; n < s->num_gpios; n++) { + if (change & 1) + gpio_set_value_cansleep(s->gpio[n], + (desired_child & 1) != 0); + change >>= 1; + desired_child >>= 1; + } + + return 0; +} + +static int mdio_mux_gpio_probe(struct platform_device *pdev) +{ + enum of_gpio_flags f; + struct mdio_mux_gpio_state *s; + int num_gpios; + unsigned int n; + int r; + + if (!pdev->dev.of_node) + return -ENODEV; + + num_gpios = of_gpio_count(pdev->dev.of_node); + if (num_gpios <= 0 || num_gpios > MDIO_MUX_GPIO_MAX_BITS) + return -ENODEV; + + s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + s->num_gpios = num_gpios; + + for (n = 0; n < num_gpios; ) { + int gpio = of_get_gpio_flags(pdev->dev.of_node, n, &f); + if (gpio < 0) { + r = (gpio == -ENODEV) ? -EPROBE_DEFER : gpio; + goto err; + } + s->gpio[n] = gpio; + + n++; + + r = gpio_request(gpio, "mdio_mux_gpio"); + if (r) + goto err; + + r = gpio_direction_output(gpio, 0); + if (r) + goto err; + } + + r = mdio_mux_init(&pdev->dev, + mdio_mux_gpio_switch_fn, &s->mux_handle, s); + + if (r == 0) { + pdev->dev.platform_data = s; + return 0; + } +err: + while (n) { + n--; + gpio_free(s->gpio[n]); + } + return r; +} + +static int mdio_mux_gpio_remove(struct platform_device *pdev) +{ + struct mdio_mux_gpio_state *s = dev_get_platdata(&pdev->dev); + mdio_mux_uninit(s->mux_handle); + return 0; +} + +static struct of_device_id mdio_mux_gpio_match[] = { + { + .compatible = "mdio-mux-gpio", + }, + { + /* Legacy compatible property. */ + .compatible = "cavium,mdio-mux-sn74cbtlv3253", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mdio_mux_gpio_match); + +static struct platform_driver mdio_mux_gpio_driver = { + .driver = { + .name = "mdio-mux-gpio", + .owner = THIS_MODULE, + .of_match_table = mdio_mux_gpio_match, + }, + .probe = mdio_mux_gpio_probe, + .remove = mdio_mux_gpio_remove, +}; + +module_platform_driver(mdio_mux_gpio_driver); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-mux-mmioreg.c b/drivers/net/phy/mdio-mux-mmioreg.c new file mode 100644 index 00000000000..1656785ff33 --- /dev/null +++ b/drivers/net/phy/mdio-mux-mmioreg.c @@ -0,0 +1,170 @@ +/* + * Simple memory-mapped device MDIO MUX driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2012 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/of_address.h> +#include <linux/of_mdio.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/mdio-mux.h> + +struct mdio_mux_mmioreg_state { + void *mux_handle; + phys_addr_t phys; + uint8_t mask; +}; + +/* + * MDIO multiplexing switch function + * + * This function is called by the mdio-mux layer when it thinks the mdio bus + * multiplexer needs to switch. + * + * 'current_child' is the current value of the mux register (masked via + * s->mask). + * + * 'desired_child' is the value of the 'reg' property of the target child MDIO + * node. + * + * The first time this function is called, current_child == -1. + * + * If current_child == desired_child, then the mux is already set to the + * correct bus. + */ +static int mdio_mux_mmioreg_switch_fn(int current_child, int desired_child, + void *data) +{ + struct mdio_mux_mmioreg_state *s = data; + + if (current_child ^ desired_child) { + void __iomem *p = ioremap(s->phys, 1); + uint8_t x, y; + + if (!p) + return -ENOMEM; + + x = ioread8(p); + y = (x & ~s->mask) | desired_child; + if (x != y) { + iowrite8((x & ~s->mask) | desired_child, p); + pr_debug("%s: %02x -> %02x\n", __func__, x, y); + } + + iounmap(p); + } + + return 0; +} + +static int mdio_mux_mmioreg_probe(struct platform_device *pdev) +{ + struct device_node *np2, *np = pdev->dev.of_node; + struct mdio_mux_mmioreg_state *s; + struct resource res; + const __be32 *iprop; + int len, ret; + + dev_dbg(&pdev->dev, "probing node %s\n", np->full_name); + + s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + ret = of_address_to_resource(np, 0, &res); + if (ret) { + dev_err(&pdev->dev, "could not obtain memory map for node %s\n", + np->full_name); + return ret; + } + s->phys = res.start; + + if (resource_size(&res) != sizeof(uint8_t)) { + dev_err(&pdev->dev, "only 8-bit registers are supported\n"); + return -EINVAL; + } + + iprop = of_get_property(np, "mux-mask", &len); + if (!iprop || len != sizeof(uint32_t)) { + dev_err(&pdev->dev, "missing or invalid mux-mask property\n"); + return -ENODEV; + } + if (be32_to_cpup(iprop) > 255) { + dev_err(&pdev->dev, "only 8-bit registers are supported\n"); + return -EINVAL; + } + s->mask = be32_to_cpup(iprop); + + /* + * Verify that the 'reg' property of each child MDIO bus does not + * set any bits outside of the 'mask'. + */ + for_each_available_child_of_node(np, np2) { + iprop = of_get_property(np2, "reg", &len); + if (!iprop || len != sizeof(uint32_t)) { + dev_err(&pdev->dev, "mdio-mux child node %s is " + "missing a 'reg' property\n", np2->full_name); + return -ENODEV; + } + if (be32_to_cpup(iprop) & ~s->mask) { + dev_err(&pdev->dev, "mdio-mux child node %s has " + "a 'reg' value with unmasked bits\n", + np2->full_name); + return -ENODEV; + } + } + + ret = mdio_mux_init(&pdev->dev, mdio_mux_mmioreg_switch_fn, + &s->mux_handle, s); + if (ret) { + dev_err(&pdev->dev, "failed to register mdio-mux bus %s\n", + np->full_name); + return ret; + } + + pdev->dev.platform_data = s; + + return 0; +} + +static int mdio_mux_mmioreg_remove(struct platform_device *pdev) +{ + struct mdio_mux_mmioreg_state *s = dev_get_platdata(&pdev->dev); + + mdio_mux_uninit(s->mux_handle); + + return 0; +} + +static struct of_device_id mdio_mux_mmioreg_match[] = { + { + .compatible = "mdio-mux-mmioreg", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mdio_mux_mmioreg_match); + +static struct platform_driver mdio_mux_mmioreg_driver = { + .driver = { + .name = "mdio-mux-mmioreg", + .owner = THIS_MODULE, + .of_match_table = mdio_mux_mmioreg_match, + }, + .probe = mdio_mux_mmioreg_probe, + .remove = mdio_mux_mmioreg_remove, +}; + +module_platform_driver(mdio_mux_mmioreg_driver); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Memory-mapped device MDIO MUX driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-mux.c b/drivers/net/phy/mdio-mux.c new file mode 100644 index 00000000000..4d4d25efc1e --- /dev/null +++ b/drivers/net/phy/mdio-mux.c @@ -0,0 +1,198 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2011, 2012 Cavium, Inc. + */ + +#include <linux/platform_device.h> +#include <linux/mdio-mux.h> +#include <linux/of_mdio.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/phy.h> + +#define DRV_VERSION "1.0" +#define DRV_DESCRIPTION "MDIO bus multiplexer driver" + +struct mdio_mux_child_bus; + +struct mdio_mux_parent_bus { + struct mii_bus *mii_bus; + int current_child; + int parent_id; + void *switch_data; + int (*switch_fn)(int current_child, int desired_child, void *data); + + /* List of our children linked through their next fields. */ + struct mdio_mux_child_bus *children; +}; + +struct mdio_mux_child_bus { + struct mii_bus *mii_bus; + struct mdio_mux_parent_bus *parent; + struct mdio_mux_child_bus *next; + int bus_number; + int phy_irq[PHY_MAX_ADDR]; +}; + +/* + * The parent bus' lock is used to order access to the switch_fn. + */ +static int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct mdio_mux_child_bus *cb = bus->priv; + struct mdio_mux_parent_bus *pb = cb->parent; + int r; + + /* In theory multiple mdio_mux could be stacked, thus creating + * more than a single level of nesting. But in practice, + * SINGLE_DEPTH_NESTING will cover the vast majority of use + * cases. We use it, instead of trying to handle the general + * case. + */ + mutex_lock_nested(&pb->mii_bus->mdio_lock, SINGLE_DEPTH_NESTING); + r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data); + if (r) + goto out; + + pb->current_child = cb->bus_number; + + r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum); +out: + mutex_unlock(&pb->mii_bus->mdio_lock); + + return r; +} + +/* + * The parent bus' lock is used to order access to the switch_fn. + */ +static int mdio_mux_write(struct mii_bus *bus, int phy_id, + int regnum, u16 val) +{ + struct mdio_mux_child_bus *cb = bus->priv; + struct mdio_mux_parent_bus *pb = cb->parent; + + int r; + + mutex_lock_nested(&pb->mii_bus->mdio_lock, SINGLE_DEPTH_NESTING); + r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data); + if (r) + goto out; + + pb->current_child = cb->bus_number; + + r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val); +out: + mutex_unlock(&pb->mii_bus->mdio_lock); + + return r; +} + +static int parent_count; + +int mdio_mux_init(struct device *dev, + int (*switch_fn)(int cur, int desired, void *data), + void **mux_handle, + void *data) +{ + struct device_node *parent_bus_node; + struct device_node *child_bus_node; + int r, ret_val; + struct mii_bus *parent_bus; + struct mdio_mux_parent_bus *pb; + struct mdio_mux_child_bus *cb; + + if (!dev->of_node) + return -ENODEV; + + parent_bus_node = of_parse_phandle(dev->of_node, "mdio-parent-bus", 0); + + if (!parent_bus_node) + return -ENODEV; + + parent_bus = of_mdio_find_bus(parent_bus_node); + if (parent_bus == NULL) { + ret_val = -EPROBE_DEFER; + goto err_parent_bus; + } + + pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL); + if (pb == NULL) { + ret_val = -ENOMEM; + goto err_parent_bus; + } + + pb->switch_data = data; + pb->switch_fn = switch_fn; + pb->current_child = -1; + pb->parent_id = parent_count++; + pb->mii_bus = parent_bus; + + ret_val = -ENODEV; + for_each_available_child_of_node(dev->of_node, child_bus_node) { + u32 v; + + r = of_property_read_u32(child_bus_node, "reg", &v); + if (r) + continue; + + cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL); + if (cb == NULL) { + dev_err(dev, + "Error: Failed to allocate memory for child\n"); + ret_val = -ENOMEM; + break; + } + cb->bus_number = v; + cb->parent = pb; + cb->mii_bus = mdiobus_alloc(); + cb->mii_bus->priv = cb; + + cb->mii_bus->irq = cb->phy_irq; + cb->mii_bus->name = "mdio_mux"; + snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%x.%x", + pb->parent_id, v); + cb->mii_bus->parent = dev; + cb->mii_bus->read = mdio_mux_read; + cb->mii_bus->write = mdio_mux_write; + r = of_mdiobus_register(cb->mii_bus, child_bus_node); + if (r) { + mdiobus_free(cb->mii_bus); + devm_kfree(dev, cb); + } else { + of_node_get(child_bus_node); + cb->next = pb->children; + pb->children = cb; + } + } + if (pb->children) { + *mux_handle = pb; + dev_info(dev, "Version " DRV_VERSION "\n"); + return 0; + } +err_parent_bus: + of_node_put(parent_bus_node); + return ret_val; +} +EXPORT_SYMBOL_GPL(mdio_mux_init); + +void mdio_mux_uninit(void *mux_handle) +{ + struct mdio_mux_parent_bus *pb = mux_handle; + struct mdio_mux_child_bus *cb = pb->children; + + while (cb) { + mdiobus_unregister(cb->mii_bus); + mdiobus_free(cb->mii_bus); + cb = cb->next; + } +} +EXPORT_SYMBOL_GPL(mdio_mux_uninit); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-octeon.c b/drivers/net/phy/mdio-octeon.c new file mode 100644 index 00000000000..a51ed92fbad --- /dev/null +++ b/drivers/net/phy/mdio-octeon.c @@ -0,0 +1,284 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2009-2012 Cavium, Inc. + */ + +#include <linux/platform_device.h> +#include <linux/of_mdio.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/gfp.h> +#include <linux/phy.h> +#include <linux/io.h> + +#include <asm/octeon/octeon.h> +#include <asm/octeon/cvmx-smix-defs.h> + +#define DRV_VERSION "1.0" +#define DRV_DESCRIPTION "Cavium Networks Octeon SMI/MDIO driver" + +#define SMI_CMD 0x0 +#define SMI_WR_DAT 0x8 +#define SMI_RD_DAT 0x10 +#define SMI_CLK 0x18 +#define SMI_EN 0x20 + +enum octeon_mdiobus_mode { + UNINIT = 0, + C22, + C45 +}; + +struct octeon_mdiobus { + struct mii_bus *mii_bus; + u64 register_base; + resource_size_t mdio_phys; + resource_size_t regsize; + enum octeon_mdiobus_mode mode; + int phy_irq[PHY_MAX_ADDR]; +}; + +static void octeon_mdiobus_set_mode(struct octeon_mdiobus *p, + enum octeon_mdiobus_mode m) +{ + union cvmx_smix_clk smi_clk; + + if (m == p->mode) + return; + + smi_clk.u64 = cvmx_read_csr(p->register_base + SMI_CLK); + smi_clk.s.mode = (m == C45) ? 1 : 0; + smi_clk.s.preamble = 1; + cvmx_write_csr(p->register_base + SMI_CLK, smi_clk.u64); + p->mode = m; +} + +static int octeon_mdiobus_c45_addr(struct octeon_mdiobus *p, + int phy_id, int regnum) +{ + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_wr_dat smi_wr; + int timeout = 1000; + + octeon_mdiobus_set_mode(p, C45); + + smi_wr.u64 = 0; + smi_wr.s.dat = regnum & 0xffff; + cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64); + + regnum = (regnum >> 16) & 0x1f; + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */ + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT); + } while (smi_wr.s.pending && --timeout); + + if (timeout <= 0) + return -EIO; + return 0; +} + +static int octeon_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct octeon_mdiobus *p = bus->priv; + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_rd_dat smi_rd; + unsigned int op = 1; /* MDIO_CLAUSE_22_READ */ + int timeout = 1000; + + if (regnum & MII_ADDR_C45) { + int r = octeon_mdiobus_c45_addr(p, phy_id, regnum); + if (r < 0) + return r; + + regnum = (regnum >> 16) & 0x1f; + op = 3; /* MDIO_CLAUSE_45_READ */ + } else { + octeon_mdiobus_set_mode(p, C22); + } + + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = op; + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_rd.u64 = cvmx_read_csr(p->register_base + SMI_RD_DAT); + } while (smi_rd.s.pending && --timeout); + + if (smi_rd.s.val) + return smi_rd.s.dat; + else + return -EIO; +} + +static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id, + int regnum, u16 val) +{ + struct octeon_mdiobus *p = bus->priv; + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_wr_dat smi_wr; + unsigned int op = 0; /* MDIO_CLAUSE_22_WRITE */ + int timeout = 1000; + + + if (regnum & MII_ADDR_C45) { + int r = octeon_mdiobus_c45_addr(p, phy_id, regnum); + if (r < 0) + return r; + + regnum = (regnum >> 16) & 0x1f; + op = 1; /* MDIO_CLAUSE_45_WRITE */ + } else { + octeon_mdiobus_set_mode(p, C22); + } + + smi_wr.u64 = 0; + smi_wr.s.dat = val; + cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64); + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = op; + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT); + } while (smi_wr.s.pending && --timeout); + + if (timeout <= 0) + return -EIO; + + return 0; +} + +static int octeon_mdiobus_probe(struct platform_device *pdev) +{ + struct octeon_mdiobus *bus; + struct resource *res_mem; + union cvmx_smix_en smi_en; + int err = -ENOENT; + + bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); + if (!bus) + return -ENOMEM; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (res_mem == NULL) { + dev_err(&pdev->dev, "found no memory resource\n"); + err = -ENXIO; + goto fail; + } + bus->mdio_phys = res_mem->start; + bus->regsize = resource_size(res_mem); + if (!devm_request_mem_region(&pdev->dev, bus->mdio_phys, bus->regsize, + res_mem->name)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + goto fail; + } + bus->register_base = + (u64)devm_ioremap(&pdev->dev, bus->mdio_phys, bus->regsize); + + bus->mii_bus = mdiobus_alloc(); + + if (!bus->mii_bus) + goto fail; + + smi_en.u64 = 0; + smi_en.s.en = 1; + cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); + + bus->mii_bus->priv = bus; + bus->mii_bus->irq = bus->phy_irq; + bus->mii_bus->name = "mdio-octeon"; + snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", bus->register_base); + bus->mii_bus->parent = &pdev->dev; + + bus->mii_bus->read = octeon_mdiobus_read; + bus->mii_bus->write = octeon_mdiobus_write; + + platform_set_drvdata(pdev, bus); + + err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node); + if (err) + goto fail_register; + + dev_info(&pdev->dev, "Version " DRV_VERSION "\n"); + + return 0; +fail_register: + mdiobus_free(bus->mii_bus); +fail: + smi_en.u64 = 0; + cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); + return err; +} + +static int octeon_mdiobus_remove(struct platform_device *pdev) +{ + struct octeon_mdiobus *bus; + union cvmx_smix_en smi_en; + + bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus->mii_bus); + mdiobus_free(bus->mii_bus); + smi_en.u64 = 0; + cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); + return 0; +} + +static struct of_device_id octeon_mdiobus_match[] = { + { + .compatible = "cavium,octeon-3860-mdio", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, octeon_mdiobus_match); + +static struct platform_driver octeon_mdiobus_driver = { + .driver = { + .name = "mdio-octeon", + .owner = THIS_MODULE, + .of_match_table = octeon_mdiobus_match, + }, + .probe = octeon_mdiobus_probe, + .remove = octeon_mdiobus_remove, +}; + +void octeon_mdiobus_force_mod_depencency(void) +{ + /* Let ethernet drivers force us to be loaded. */ +} +EXPORT_SYMBOL(octeon_mdiobus_force_mod_depencency); + +module_platform_driver(octeon_mdiobus_driver); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-sun4i.c b/drivers/net/phy/mdio-sun4i.c new file mode 100644 index 00000000000..15bc7f9ea22 --- /dev/null +++ b/drivers/net/phy/mdio-sun4i.c @@ -0,0 +1,188 @@ +/* + * Allwinner EMAC MDIO interface driver + * + * Copyright 2012-2013 Stefan Roese <sr@denx.de> + * Copyright 2013 Maxime Ripard <maxime.ripard@free-electrons.com> + * + * Based on the Linux driver provided by Allwinner: + * Copyright (C) 1997 Sten Wang + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_address.h> +#include <linux/of_mdio.h> +#include <linux/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#define EMAC_MAC_MCMD_REG (0x00) +#define EMAC_MAC_MADR_REG (0x04) +#define EMAC_MAC_MWTD_REG (0x08) +#define EMAC_MAC_MRDD_REG (0x0c) +#define EMAC_MAC_MIND_REG (0x10) +#define EMAC_MAC_SSRR_REG (0x14) + +#define MDIO_TIMEOUT (msecs_to_jiffies(100)) + +struct sun4i_mdio_data { + void __iomem *membase; + struct regulator *regulator; +}; + +static int sun4i_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct sun4i_mdio_data *data = bus->priv; + unsigned long timeout_jiffies; + int value; + + /* issue the phy address and reg */ + writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); + /* pull up the phy io line */ + writel(0x1, data->membase + EMAC_MAC_MCMD_REG); + + /* Wait read complete */ + timeout_jiffies = jiffies + MDIO_TIMEOUT; + while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { + if (time_is_before_jiffies(timeout_jiffies)) + return -ETIMEDOUT; + msleep(1); + } + + /* push down the phy io line */ + writel(0x0, data->membase + EMAC_MAC_MCMD_REG); + /* and read data */ + value = readl(data->membase + EMAC_MAC_MRDD_REG); + + return value; +} + +static int sun4i_mdio_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct sun4i_mdio_data *data = bus->priv; + unsigned long timeout_jiffies; + + /* issue the phy address and reg */ + writel((mii_id << 8) | regnum, data->membase + EMAC_MAC_MADR_REG); + /* pull up the phy io line */ + writel(0x1, data->membase + EMAC_MAC_MCMD_REG); + + /* Wait read complete */ + timeout_jiffies = jiffies + MDIO_TIMEOUT; + while (readl(data->membase + EMAC_MAC_MIND_REG) & 0x1) { + if (time_is_before_jiffies(timeout_jiffies)) + return -ETIMEDOUT; + msleep(1); + } + + /* push down the phy io line */ + writel(0x0, data->membase + EMAC_MAC_MCMD_REG); + /* and write data */ + writel(value, data->membase + EMAC_MAC_MWTD_REG); + + return 0; +} + +static int sun4i_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mii_bus *bus; + struct sun4i_mdio_data *data; + struct resource *res; + int ret, i; + + bus = mdiobus_alloc_size(sizeof(*data)); + if (!bus) + return -ENOMEM; + + bus->name = "sun4i_mii_bus"; + bus->read = &sun4i_mdio_read; + bus->write = &sun4i_mdio_write; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); + bus->parent = &pdev->dev; + + bus->irq = devm_kzalloc(&pdev->dev, sizeof(int) * PHY_MAX_ADDR, + GFP_KERNEL); + if (!bus->irq) { + ret = -ENOMEM; + goto err_out_free_mdiobus; + } + + for (i = 0; i < PHY_MAX_ADDR; i++) + bus->irq[i] = PHY_POLL; + + data = bus->priv; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->membase)) { + ret = PTR_ERR(data->membase); + goto err_out_free_mdiobus; + } + + data->regulator = devm_regulator_get(&pdev->dev, "phy"); + if (IS_ERR(data->regulator)) { + if (PTR_ERR(data->regulator) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_info(&pdev->dev, "no regulator found\n"); + } else { + ret = regulator_enable(data->regulator); + if (ret) + goto err_out_free_mdiobus; + } + + ret = of_mdiobus_register(bus, np); + if (ret < 0) + goto err_out_disable_regulator; + + platform_set_drvdata(pdev, bus); + + return 0; + +err_out_disable_regulator: + regulator_disable(data->regulator); +err_out_free_mdiobus: + mdiobus_free(bus); + return ret; +} + +static int sun4i_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + mdiobus_free(bus); + + return 0; +} + +static const struct of_device_id sun4i_mdio_dt_ids[] = { + { .compatible = "allwinner,sun4i-a10-mdio" }, + + /* Deprecated */ + { .compatible = "allwinner,sun4i-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_mdio_dt_ids); + +static struct platform_driver sun4i_mdio_driver = { + .probe = sun4i_mdio_probe, + .remove = sun4i_mdio_remove, + .driver = { + .name = "sun4i-mdio", + .of_match_table = sun4i_mdio_dt_ids, + }, +}; + +module_platform_driver(sun4i_mdio_driver); + +MODULE_DESCRIPTION("Allwinner EMAC MDIO interface driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 459443b572c..203651ebccb 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -1,7 +1,4 @@ -/* - * drivers/net/phy/mdio_bus.c - * - * MDIO Bus interface +/* MDIO Bus interface * * Author: Andy Fleming * @@ -13,9 +10,10 @@ * option) any later version. * */ -#include <linux/config.h> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> @@ -23,6 +21,9 @@ #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> +#include <linux/device.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> @@ -32,71 +33,266 @@ #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/phy.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> #include <asm/irq.h> -#include <asm/uaccess.h> -/* mdiobus_register +/** + * mdiobus_alloc_size - allocate a mii_bus structure + * @size: extra amount of memory to allocate for private storage. + * If non-zero, then bus->priv is points to that memory. * - * description: Called by a bus driver to bring up all the PHYs - * on a given bus, and attach them to the bus + * Description: called by a bus driver to allocate an mii_bus + * structure to fill in. */ -int mdiobus_register(struct mii_bus *bus) +struct mii_bus *mdiobus_alloc_size(size_t size) { - int i; - int err = 0; + struct mii_bus *bus; + size_t aligned_size = ALIGN(sizeof(*bus), NETDEV_ALIGN); + size_t alloc_size; - spin_lock_init(&bus->mdio_lock); + /* If we alloc extra space, it should be aligned */ + if (size) + alloc_size = aligned_size + size; + else + alloc_size = sizeof(*bus); - if (NULL == bus || NULL == bus->name || - NULL == bus->read || - NULL == bus->write) - return -EINVAL; + bus = kzalloc(alloc_size, GFP_KERNEL); + if (bus) { + bus->state = MDIOBUS_ALLOCATED; + if (size) + bus->priv = (void *)bus + aligned_size; + } - if (bus->reset) - bus->reset(bus); + return bus; +} +EXPORT_SYMBOL(mdiobus_alloc_size); - for (i = 0; i < PHY_MAX_ADDR; i++) { - struct phy_device *phydev; +static void _devm_mdiobus_free(struct device *dev, void *res) +{ + mdiobus_free(*(struct mii_bus **)res); +} - if (bus->phy_mask & (1 << i)) - continue; +static int devm_mdiobus_match(struct device *dev, void *res, void *data) +{ + struct mii_bus **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +/** + * devm_mdiobus_alloc_size - Resource-managed mdiobus_alloc_size() + * @dev: Device to allocate mii_bus for + * @sizeof_priv: Space to allocate for private structure. + * + * Managed mdiobus_alloc_size. mii_bus allocated with this function is + * automatically freed on driver detach. + * + * If an mii_bus allocated with this function needs to be freed separately, + * devm_mdiobus_free() must be used. + * + * RETURNS: + * Pointer to allocated mii_bus on success, NULL on failure. + */ +struct mii_bus *devm_mdiobus_alloc_size(struct device *dev, int sizeof_priv) +{ + struct mii_bus **ptr, *bus; + + ptr = devres_alloc(_devm_mdiobus_free, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + /* use raw alloc_dr for kmalloc caller tracing */ + bus = mdiobus_alloc_size(sizeof_priv); + if (bus) { + *ptr = bus; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return bus; +} +EXPORT_SYMBOL_GPL(devm_mdiobus_alloc_size); + +/** + * devm_mdiobus_free - Resource-managed mdiobus_free() + * @dev: Device this mii_bus belongs to + * @bus: the mii_bus associated with the device + * + * Free mii_bus allocated with devm_mdiobus_alloc_size(). + */ +void devm_mdiobus_free(struct device *dev, struct mii_bus *bus) +{ + int rc; + + rc = devres_release(dev, _devm_mdiobus_free, + devm_mdiobus_match, bus); + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_mdiobus_free); + +/** + * mdiobus_release - mii_bus device release callback + * @d: the target struct device that contains the mii_bus + * + * Description: called when the last reference to an mii_bus is + * dropped, to free the underlying memory. + */ +static void mdiobus_release(struct device *d) +{ + struct mii_bus *bus = to_mii_bus(d); + BUG_ON(bus->state != MDIOBUS_RELEASED && + /* for compatibility with error handling in drivers */ + bus->state != MDIOBUS_ALLOCATED); + kfree(bus); +} + +static struct class mdio_bus_class = { + .name = "mdio_bus", + .dev_release = mdiobus_release, +}; + +#if IS_ENABLED(CONFIG_OF_MDIO) +/* Helper function for of_mdio_find_bus */ +static int of_mdio_bus_match(struct device *dev, const void *mdio_bus_np) +{ + return dev->of_node == mdio_bus_np; +} +/** + * of_mdio_find_bus - Given an mii_bus node, find the mii_bus. + * @mdio_bus_np: Pointer to the mii_bus. + * + * Returns a pointer to the mii_bus, or NULL if none found. + * + * Because the association of a device_node and mii_bus is made via + * of_mdiobus_register(), the mii_bus cannot be found before it is + * registered with of_mdiobus_register(). + * + */ +struct mii_bus *of_mdio_find_bus(struct device_node *mdio_bus_np) +{ + struct device *d; - phydev = get_phy_device(bus, i); + if (!mdio_bus_np) + return NULL; - if (IS_ERR(phydev)) - return PTR_ERR(phydev); + d = class_find_device(&mdio_bus_class, NULL, mdio_bus_np, + of_mdio_bus_match); - /* 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]; + return d ? to_mii_bus(d) : NULL; +} +EXPORT_SYMBOL(of_mdio_find_bus); - phydev->dev.parent = bus->dev; - phydev->dev.bus = &mdio_bus_type; - snprintf(phydev->dev.bus_id, BUS_ID_SIZE, PHY_ID_FMT, bus->id, i); +/* Walk the list of subnodes of a mdio bus and look for a node that matches the + * phy's address with its 'reg' property. If found, set the of_node pointer for + * the phy. This allows auto-probed pyh devices to be supplied with information + * passed in via DT. + */ +static void of_mdiobus_link_phydev(struct mii_bus *mdio, + struct phy_device *phydev) +{ + struct device *dev = &phydev->dev; + struct device_node *child; - phydev->bus = bus; + if (dev->of_node || !mdio->dev.of_node) + return; - err = device_register(&phydev->dev); + for_each_available_child_of_node(mdio->dev.of_node, child) { + int addr; + int ret; - if (err) - printk(KERN_ERR "phy %d failed to register\n", - i); + ret = of_property_read_u32(child, "reg", &addr); + if (ret < 0) { + dev_err(dev, "%s has invalid PHY address\n", + child->full_name); + continue; } - bus->phy_map[i] = phydev; + /* A PHY must have a reg property in the range [0-31] */ + if (addr >= PHY_MAX_ADDR) { + dev_err(dev, "%s PHY address %i is too large\n", + child->full_name, addr); + continue; + } + + if (addr == phydev->addr) { + dev->of_node = child; + return; + } } +} +#else /* !IS_ENABLED(CONFIG_OF_MDIO) */ +static inline void of_mdiobus_link_phydev(struct mii_bus *mdio, + struct phy_device *phydev) +{ +} +#endif +/** + * mdiobus_register - bring up all the PHYs on a given bus and attach them to bus + * @bus: target mii_bus + * + * Description: Called by a bus driver to bring up all the PHYs + * on a given bus, and attach them to the bus. + * + * Returns 0 on success or < 0 on error. + */ +int mdiobus_register(struct mii_bus *bus) +{ + int i, err; + + if (NULL == bus || NULL == bus->name || + NULL == bus->read || NULL == bus->write) + return -EINVAL; + + BUG_ON(bus->state != MDIOBUS_ALLOCATED && + bus->state != MDIOBUS_UNREGISTERED); + + bus->dev.parent = bus->parent; + bus->dev.class = &mdio_bus_class; + bus->dev.driver = bus->parent->driver; + bus->dev.groups = NULL; + dev_set_name(&bus->dev, "%s", bus->id); + + err = device_register(&bus->dev); + if (err) { + pr_err("mii_bus %s failed to register\n", bus->id); + put_device(&bus->dev); + return -EINVAL; + } + + mutex_init(&bus->mdio_lock); + + if (bus->reset) + bus->reset(bus); + + for (i = 0; i < PHY_MAX_ADDR; i++) { + if ((bus->phy_mask & (1 << i)) == 0) { + struct phy_device *phydev; + + phydev = mdiobus_scan(bus, i); + if (IS_ERR(phydev)) { + err = PTR_ERR(phydev); + goto error; + } + } + } + + bus->state = MDIOBUS_REGISTERED; pr_info("%s: probed\n", bus->name); + return 0; +error: + while (--i >= 0) { + if (bus->phy_map[i]) + device_unregister(&bus->phy_map[i]->dev); + } + device_del(&bus->dev); return err; } EXPORT_SYMBOL(mdiobus_register); @@ -105,66 +301,305 @@ void mdiobus_unregister(struct mii_bus *bus) { int i; + BUG_ON(bus->state != MDIOBUS_REGISTERED); + bus->state = MDIOBUS_UNREGISTERED; + + device_del(&bus->dev); for (i = 0; i < PHY_MAX_ADDR; i++) { - if (bus->phy_map[i]) { + if (bus->phy_map[i]) device_unregister(&bus->phy_map[i]->dev); - kfree(bus->phy_map[i]); - } + bus->phy_map[i] = NULL; } } EXPORT_SYMBOL(mdiobus_unregister); -/* mdio_bus_match +/** + * mdiobus_free - free a struct mii_bus + * @bus: mii_bus to free * - * description: Given a PHY device, and a PHY driver, return 1 if - * the driver supports the device. Otherwise, return 0 + * This function releases the reference to the underlying device + * object in the mii_bus. If this is the last reference, the mii_bus + * will be freed. + */ +void mdiobus_free(struct mii_bus *bus) +{ + /* For compatibility with error handling in drivers. */ + if (bus->state == MDIOBUS_ALLOCATED) { + kfree(bus); + return; + } + + BUG_ON(bus->state != MDIOBUS_UNREGISTERED); + bus->state = MDIOBUS_RELEASED; + + put_device(&bus->dev); +} +EXPORT_SYMBOL(mdiobus_free); + +struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr) +{ + struct phy_device *phydev; + int err; + + phydev = get_phy_device(bus, addr, false); + if (IS_ERR(phydev) || phydev == NULL) + return phydev; + + /* + * For DT, see if the auto-probed phy has a correspoding child + * in the bus node, and set the of_node pointer in this case. + */ + of_mdiobus_link_phydev(bus, phydev); + + err = phy_device_register(phydev); + if (err) { + phy_device_free(phydev); + return NULL; + } + + return phydev; +} +EXPORT_SYMBOL(mdiobus_scan); + +/** + * mdiobus_read - Convenience function for reading a given MII mgmt register + * @bus: the mii_bus struct + * @addr: the phy address + * @regnum: register number to read + * + * NOTE: MUST NOT be called from interrupt context, + * because the bus read/write functions may wait for an interrupt + * to conclude the operation. + */ +int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum) +{ + int retval; + + BUG_ON(in_interrupt()); + + mutex_lock(&bus->mdio_lock); + retval = bus->read(bus, addr, regnum); + mutex_unlock(&bus->mdio_lock); + + return retval; +} +EXPORT_SYMBOL(mdiobus_read); + +/** + * mdiobus_write - Convenience function for writing a given MII mgmt register + * @bus: the mii_bus struct + * @addr: the phy address + * @regnum: register number to write + * @val: value to write to @regnum + * + * NOTE: MUST NOT be called from interrupt context, + * because the bus read/write functions may wait for an interrupt + * to conclude the operation. + */ +int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val) +{ + int err; + + BUG_ON(in_interrupt()); + + mutex_lock(&bus->mdio_lock); + err = bus->write(bus, addr, regnum, val); + mutex_unlock(&bus->mdio_lock); + + return err; +} +EXPORT_SYMBOL(mdiobus_write); + +/** + * mdio_bus_match - determine if given PHY driver supports the given PHY device + * @dev: target PHY device + * @drv: given PHY driver + * + * 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)); + if (of_driver_match_device(dev, drv)) + return 1; + + if (phydrv->match_phy_device) + return phydrv->match_phy_device(phydev); + + return (phydrv->phy_id & phydrv->phy_id_mask) == + (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, pm_message_t state) +#ifdef CONFIG_PM + +static bool mdio_bus_phy_may_suspend(struct phy_device *phydev) { - int ret = 0; - struct device_driver *drv = dev->driver; + struct device_driver *drv = phydev->dev.driver; + struct phy_driver *phydrv = to_phy_driver(drv); + struct net_device *netdev = phydev->attached_dev; - if (drv && drv->suspend) - ret = drv->suspend(dev, state); + if (!drv || !phydrv->suspend) + return false; - return ret; + /* PHY not attached? May suspend. */ + if (!netdev) + return true; + + /* Don't suspend PHY if the attched netdev parent may wakeup. + * The parent may point to a PCI device, as in tg3 driver. + */ + if (netdev->dev.parent && device_may_wakeup(netdev->dev.parent)) + return false; + + /* Also don't suspend PHY if the netdev itself may wakeup. This + * is the case for devices w/o underlaying pwr. mgmt. aware bus, + * e.g. SoC devices. + */ + if (device_may_wakeup(&netdev->dev)) + return false; + + return true; } -static int mdio_bus_resume(struct device * dev) +static int mdio_bus_suspend(struct device *dev) { - int ret = 0; - struct device_driver *drv = dev->driver; + struct phy_driver *phydrv = to_phy_driver(dev->driver); + struct phy_device *phydev = to_phy_device(dev); - if (drv && drv->resume) - ret = drv->resume(dev); + /* We must stop the state machine manually, otherwise it stops out of + * control, possibly with the phydev->lock held. Upon resume, netdev + * may call phy routines that try to grab the same lock, and that may + * lead to a deadlock. + */ + if (phydev->attached_dev && phydev->adjust_link) + phy_stop_machine(phydev); - return ret; + if (!mdio_bus_phy_may_suspend(phydev)) + return 0; + + return phydrv->suspend(phydev); } +static int mdio_bus_resume(struct device *dev) +{ + struct phy_driver *phydrv = to_phy_driver(dev->driver); + struct phy_device *phydev = to_phy_device(dev); + int ret; + + if (!mdio_bus_phy_may_suspend(phydev)) + goto no_resume; + + ret = phydrv->resume(phydev); + if (ret < 0) + return ret; + +no_resume: + if (phydev->attached_dev && phydev->adjust_link) + phy_start_machine(phydev); + + return 0; +} + +static int mdio_bus_restore(struct device *dev) +{ + struct phy_device *phydev = to_phy_device(dev); + struct net_device *netdev = phydev->attached_dev; + int ret; + + if (!netdev) + return 0; + + ret = phy_init_hw(phydev); + if (ret < 0) + return ret; + + /* The PHY needs to renegotiate. */ + phydev->link = 0; + phydev->state = PHY_UP; + + phy_start_machine(phydev); + + return 0; +} + +static const struct dev_pm_ops mdio_bus_pm_ops = { + .suspend = mdio_bus_suspend, + .resume = mdio_bus_resume, + .freeze = mdio_bus_suspend, + .thaw = mdio_bus_resume, + .restore = mdio_bus_restore, +}; + +#define MDIO_BUS_PM_OPS (&mdio_bus_pm_ops) + +#else + +#define MDIO_BUS_PM_OPS NULL + +#endif /* CONFIG_PM */ + +static ssize_t +phy_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct phy_device *phydev = to_phy_device(dev); + + return sprintf(buf, "0x%.8lx\n", (unsigned long)phydev->phy_id); +} +static DEVICE_ATTR_RO(phy_id); + +static ssize_t +phy_interface_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct phy_device *phydev = to_phy_device(dev); + + return sprintf(buf, "%s\n", phy_modes(phydev->interface)); +} +static DEVICE_ATTR_RO(phy_interface); + +static ssize_t +phy_has_fixups_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct phy_device *phydev = to_phy_device(dev); + + return sprintf(buf, "%d\n", phydev->has_fixups); +} +static DEVICE_ATTR_RO(phy_has_fixups); + +static struct attribute *mdio_dev_attrs[] = { + &dev_attr_phy_id.attr, + &dev_attr_phy_interface.attr, + &dev_attr_phy_has_fixups.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mdio_dev); + struct bus_type mdio_bus_type = { .name = "mdio_bus", .match = mdio_bus_match, - .suspend = mdio_bus_suspend, - .resume = mdio_bus_resume, + .pm = MDIO_BUS_PM_OPS, + .dev_groups = mdio_dev_groups, }; +EXPORT_SYMBOL(mdio_bus_type); int __init mdio_bus_init(void) { - return bus_register(&mdio_bus_type); + int ret; + + ret = class_register(&mdio_bus_class); + if (!ret) { + ret = bus_register(&mdio_bus_type); + if (ret) + class_unregister(&mdio_bus_class); + } + + return ret; } void mdio_bus_exit(void) { + class_unregister(&mdio_bus_class); bus_unregister(&mdio_bus_type); } diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c new file mode 100644 index 00000000000..bc7c7d2f75f --- /dev/null +++ b/drivers/net/phy/micrel.c @@ -0,0 +1,645 @@ +/* + * drivers/net/phy/micrel.c + * + * Driver for Micrel PHYs + * + * Author: David J. Choi + * + * Copyright (c) 2010-2013 Micrel, 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. + * + * Support : Micrel Phys: + * Giga phys: ksz9021, ksz9031 + * 100/10 Phys : ksz8001, ksz8721, ksz8737, ksz8041 + * ksz8021, ksz8031, ksz8051, + * ksz8081, ksz8091, + * ksz8061, + * Switch : ksz8873, ksz886x + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/micrel_phy.h> +#include <linux/of.h> + +/* Operation Mode Strap Override */ +#define MII_KSZPHY_OMSO 0x16 +#define KSZPHY_OMSO_B_CAST_OFF (1 << 9) +#define KSZPHY_OMSO_RMII_OVERRIDE (1 << 1) +#define KSZPHY_OMSO_MII_OVERRIDE (1 << 0) + +/* general Interrupt control/status reg in vendor specific block. */ +#define MII_KSZPHY_INTCS 0x1B +#define KSZPHY_INTCS_JABBER (1 << 15) +#define KSZPHY_INTCS_RECEIVE_ERR (1 << 14) +#define KSZPHY_INTCS_PAGE_RECEIVE (1 << 13) +#define KSZPHY_INTCS_PARELLEL (1 << 12) +#define KSZPHY_INTCS_LINK_PARTNER_ACK (1 << 11) +#define KSZPHY_INTCS_LINK_DOWN (1 << 10) +#define KSZPHY_INTCS_REMOTE_FAULT (1 << 9) +#define KSZPHY_INTCS_LINK_UP (1 << 8) +#define KSZPHY_INTCS_ALL (KSZPHY_INTCS_LINK_UP |\ + KSZPHY_INTCS_LINK_DOWN) + +/* general PHY control reg in vendor specific block. */ +#define MII_KSZPHY_CTRL 0x1F +/* bitmap of PHY register to set interrupt mode */ +#define KSZPHY_CTRL_INT_ACTIVE_HIGH (1 << 9) +#define KSZ9021_CTRL_INT_ACTIVE_HIGH (1 << 14) +#define KS8737_CTRL_INT_ACTIVE_HIGH (1 << 14) +#define KSZ8051_RMII_50MHZ_CLK (1 << 7) + +/* Write/read to/from extended registers */ +#define MII_KSZPHY_EXTREG 0x0b +#define KSZPHY_EXTREG_WRITE 0x8000 + +#define MII_KSZPHY_EXTREG_WRITE 0x0c +#define MII_KSZPHY_EXTREG_READ 0x0d + +/* Extended registers */ +#define MII_KSZPHY_CLK_CONTROL_PAD_SKEW 0x104 +#define MII_KSZPHY_RX_DATA_PAD_SKEW 0x105 +#define MII_KSZPHY_TX_DATA_PAD_SKEW 0x106 + +#define PS_TO_REG 200 + +static int ksz_config_flags(struct phy_device *phydev) +{ + int regval; + + if (phydev->dev_flags & MICREL_PHY_50MHZ_CLK) { + regval = phy_read(phydev, MII_KSZPHY_CTRL); + regval |= KSZ8051_RMII_50MHZ_CLK; + return phy_write(phydev, MII_KSZPHY_CTRL, regval); + } + return 0; +} + +static int kszphy_extended_write(struct phy_device *phydev, + u32 regnum, u16 val) +{ + phy_write(phydev, MII_KSZPHY_EXTREG, KSZPHY_EXTREG_WRITE | regnum); + return phy_write(phydev, MII_KSZPHY_EXTREG_WRITE, val); +} + +static int kszphy_extended_read(struct phy_device *phydev, + u32 regnum) +{ + phy_write(phydev, MII_KSZPHY_EXTREG, regnum); + return phy_read(phydev, MII_KSZPHY_EXTREG_READ); +} + +static int kszphy_ack_interrupt(struct phy_device *phydev) +{ + /* bit[7..0] int status, which is a read and clear register. */ + int rc; + + rc = phy_read(phydev, MII_KSZPHY_INTCS); + + return (rc < 0) ? rc : 0; +} + +static int kszphy_set_interrupt(struct phy_device *phydev) +{ + int temp; + temp = (PHY_INTERRUPT_ENABLED == phydev->interrupts) ? + KSZPHY_INTCS_ALL : 0; + return phy_write(phydev, MII_KSZPHY_INTCS, temp); +} + +static int kszphy_config_intr(struct phy_device *phydev) +{ + int temp, rc; + + /* set the interrupt pin active low */ + temp = phy_read(phydev, MII_KSZPHY_CTRL); + temp &= ~KSZPHY_CTRL_INT_ACTIVE_HIGH; + phy_write(phydev, MII_KSZPHY_CTRL, temp); + rc = kszphy_set_interrupt(phydev); + return rc < 0 ? rc : 0; +} + +static int ksz9021_config_intr(struct phy_device *phydev) +{ + int temp, rc; + + /* set the interrupt pin active low */ + temp = phy_read(phydev, MII_KSZPHY_CTRL); + temp &= ~KSZ9021_CTRL_INT_ACTIVE_HIGH; + phy_write(phydev, MII_KSZPHY_CTRL, temp); + rc = kszphy_set_interrupt(phydev); + return rc < 0 ? rc : 0; +} + +static int ks8737_config_intr(struct phy_device *phydev) +{ + int temp, rc; + + /* set the interrupt pin active low */ + temp = phy_read(phydev, MII_KSZPHY_CTRL); + temp &= ~KS8737_CTRL_INT_ACTIVE_HIGH; + phy_write(phydev, MII_KSZPHY_CTRL, temp); + rc = kszphy_set_interrupt(phydev); + return rc < 0 ? rc : 0; +} + +static int kszphy_setup_led(struct phy_device *phydev, + unsigned int reg, unsigned int shift) +{ + + struct device *dev = &phydev->dev; + struct device_node *of_node = dev->of_node; + int rc, temp; + u32 val; + + if (!of_node && dev->parent->of_node) + of_node = dev->parent->of_node; + + if (of_property_read_u32(of_node, "micrel,led-mode", &val)) + return 0; + + temp = phy_read(phydev, reg); + if (temp < 0) + return temp; + + temp &= ~(3 << shift); + temp |= val << shift; + rc = phy_write(phydev, reg, temp); + + return rc < 0 ? rc : 0; +} + +static int kszphy_config_init(struct phy_device *phydev) +{ + return 0; +} + +static int kszphy_config_init_led8041(struct phy_device *phydev) +{ + /* single led control, register 0x1e bits 15..14 */ + return kszphy_setup_led(phydev, 0x1e, 14); +} + +static int ksz8021_config_init(struct phy_device *phydev) +{ + const u16 val = KSZPHY_OMSO_B_CAST_OFF | KSZPHY_OMSO_RMII_OVERRIDE; + int rc; + + rc = kszphy_setup_led(phydev, 0x1f, 4); + if (rc) + dev_err(&phydev->dev, "failed to set led mode\n"); + + phy_write(phydev, MII_KSZPHY_OMSO, val); + rc = ksz_config_flags(phydev); + return rc < 0 ? rc : 0; +} + +static int ks8051_config_init(struct phy_device *phydev) +{ + int rc; + + rc = kszphy_setup_led(phydev, 0x1f, 4); + if (rc) + dev_err(&phydev->dev, "failed to set led mode\n"); + + rc = ksz_config_flags(phydev); + return rc < 0 ? rc : 0; +} + +static int ksz9021_load_values_from_of(struct phy_device *phydev, + struct device_node *of_node, u16 reg, + char *field1, char *field2, + char *field3, char *field4) +{ + int val1 = -1; + int val2 = -2; + int val3 = -3; + int val4 = -4; + int newval; + int matches = 0; + + if (!of_property_read_u32(of_node, field1, &val1)) + matches++; + + if (!of_property_read_u32(of_node, field2, &val2)) + matches++; + + if (!of_property_read_u32(of_node, field3, &val3)) + matches++; + + if (!of_property_read_u32(of_node, field4, &val4)) + matches++; + + if (!matches) + return 0; + + if (matches < 4) + newval = kszphy_extended_read(phydev, reg); + else + newval = 0; + + if (val1 != -1) + newval = ((newval & 0xfff0) | ((val1 / PS_TO_REG) & 0xf) << 0); + + if (val2 != -2) + newval = ((newval & 0xff0f) | ((val2 / PS_TO_REG) & 0xf) << 4); + + if (val3 != -3) + newval = ((newval & 0xf0ff) | ((val3 / PS_TO_REG) & 0xf) << 8); + + if (val4 != -4) + newval = ((newval & 0x0fff) | ((val4 / PS_TO_REG) & 0xf) << 12); + + return kszphy_extended_write(phydev, reg, newval); +} + +static int ksz9021_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->dev; + struct device_node *of_node = dev->of_node; + + if (!of_node && dev->parent->of_node) + of_node = dev->parent->of_node; + + if (of_node) { + ksz9021_load_values_from_of(phydev, of_node, + MII_KSZPHY_CLK_CONTROL_PAD_SKEW, + "txen-skew-ps", "txc-skew-ps", + "rxdv-skew-ps", "rxc-skew-ps"); + ksz9021_load_values_from_of(phydev, of_node, + MII_KSZPHY_RX_DATA_PAD_SKEW, + "rxd0-skew-ps", "rxd1-skew-ps", + "rxd2-skew-ps", "rxd3-skew-ps"); + ksz9021_load_values_from_of(phydev, of_node, + MII_KSZPHY_TX_DATA_PAD_SKEW, + "txd0-skew-ps", "txd1-skew-ps", + "txd2-skew-ps", "txd3-skew-ps"); + } + return 0; +} + +#define MII_KSZ9031RN_MMD_CTRL_REG 0x0d +#define MII_KSZ9031RN_MMD_REGDATA_REG 0x0e +#define OP_DATA 1 +#define KSZ9031_PS_TO_REG 60 + +/* Extended registers */ +#define MII_KSZ9031RN_CONTROL_PAD_SKEW 4 +#define MII_KSZ9031RN_RX_DATA_PAD_SKEW 5 +#define MII_KSZ9031RN_TX_DATA_PAD_SKEW 6 +#define MII_KSZ9031RN_CLK_PAD_SKEW 8 + +static int ksz9031_extended_write(struct phy_device *phydev, + u8 mode, u32 dev_addr, u32 regnum, u16 val) +{ + phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, dev_addr); + phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, regnum); + phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, (mode << 14) | dev_addr); + return phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, val); +} + +static int ksz9031_extended_read(struct phy_device *phydev, + u8 mode, u32 dev_addr, u32 regnum) +{ + phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, dev_addr); + phy_write(phydev, MII_KSZ9031RN_MMD_REGDATA_REG, regnum); + phy_write(phydev, MII_KSZ9031RN_MMD_CTRL_REG, (mode << 14) | dev_addr); + return phy_read(phydev, MII_KSZ9031RN_MMD_REGDATA_REG); +} + +static int ksz9031_of_load_skew_values(struct phy_device *phydev, + struct device_node *of_node, + u16 reg, size_t field_sz, + char *field[], u8 numfields) +{ + int val[4] = {-1, -2, -3, -4}; + int matches = 0; + u16 mask; + u16 maxval; + u16 newval; + int i; + + for (i = 0; i < numfields; i++) + if (!of_property_read_u32(of_node, field[i], val + i)) + matches++; + + if (!matches) + return 0; + + if (matches < numfields) + newval = ksz9031_extended_read(phydev, OP_DATA, 2, reg); + else + newval = 0; + + maxval = (field_sz == 4) ? 0xf : 0x1f; + for (i = 0; i < numfields; i++) + if (val[i] != -(i + 1)) { + mask = 0xffff; + mask ^= maxval << (field_sz * i); + newval = (newval & mask) | + (((val[i] / KSZ9031_PS_TO_REG) & maxval) + << (field_sz * i)); + } + + return ksz9031_extended_write(phydev, OP_DATA, 2, reg, newval); +} + +static int ksz9031_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->dev; + struct device_node *of_node = dev->of_node; + char *clk_skews[2] = {"rxc-skew-ps", "txc-skew-ps"}; + char *rx_data_skews[4] = { + "rxd0-skew-ps", "rxd1-skew-ps", + "rxd2-skew-ps", "rxd3-skew-ps" + }; + char *tx_data_skews[4] = { + "txd0-skew-ps", "txd1-skew-ps", + "txd2-skew-ps", "txd3-skew-ps" + }; + char *control_skews[2] = {"txen-skew-ps", "rxdv-skew-ps"}; + + if (!of_node && dev->parent->of_node) + of_node = dev->parent->of_node; + + if (of_node) { + ksz9031_of_load_skew_values(phydev, of_node, + MII_KSZ9031RN_CLK_PAD_SKEW, 5, + clk_skews, 2); + + ksz9031_of_load_skew_values(phydev, of_node, + MII_KSZ9031RN_CONTROL_PAD_SKEW, 4, + control_skews, 2); + + ksz9031_of_load_skew_values(phydev, of_node, + MII_KSZ9031RN_RX_DATA_PAD_SKEW, 4, + rx_data_skews, 4); + + ksz9031_of_load_skew_values(phydev, of_node, + MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4, + tx_data_skews, 4); + } + return 0; +} + +#define KSZ8873MLL_GLOBAL_CONTROL_4 0x06 +#define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX (1 << 6) +#define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED (1 << 4) +static int ksz8873mll_read_status(struct phy_device *phydev) +{ + int regval; + + /* dummy read */ + regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4); + + regval = phy_read(phydev, KSZ8873MLL_GLOBAL_CONTROL_4); + + if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX) + phydev->duplex = DUPLEX_HALF; + else + phydev->duplex = DUPLEX_FULL; + + if (regval & KSZ8873MLL_GLOBAL_CONTROL_4_SPEED) + phydev->speed = SPEED_10; + else + phydev->speed = SPEED_100; + + phydev->link = 1; + phydev->pause = phydev->asym_pause = 0; + + return 0; +} + +static int ksz8873mll_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static struct phy_driver ksphy_driver[] = { +{ + .phy_id = PHY_ID_KS8737, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KS8737", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = ks8737_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8021, + .phy_id_mask = 0x00ffffff, + .name = "Micrel KSZ8021 or KSZ8031", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause | + SUPPORTED_Asym_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = ksz8021_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8031, + .phy_id_mask = 0x00ffffff, + .name = "Micrel KSZ8031", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause | + SUPPORTED_Asym_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = ksz8021_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8041, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KSZ8041", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init_led8041, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8041RNLI, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KSZ8041RNLI", + .features = PHY_BASIC_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init_led8041, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8051, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KSZ8051", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = ks8051_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8001, + .name = "Micrel KSZ8001 or KS8721", + .phy_id_mask = 0x00ffffff, + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init_led8041, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8081, + .name = "Micrel KSZ8081 or KSZ8091", + .phy_id_mask = 0x00fffff0, + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ8061, + .name = "Micrel KSZ8061", + .phy_id_mask = 0x00fffff0, + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = kszphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_KSZ9021, + .phy_id_mask = 0x000ffffe, + .name = "Micrel KSZ9021 Gigabit PHY", + .features = (PHY_GBIT_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = ksz9021_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = ksz9021_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE, }, +}, { + .phy_id = PHY_ID_KSZ9031, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KSZ9031 Gigabit PHY", + .features = (PHY_GBIT_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = ksz9031_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = kszphy_ack_interrupt, + .config_intr = ksz9021_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE, }, +}, { + .phy_id = PHY_ID_KSZ8873MLL, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KSZ8873MLL Switch", + .features = (SUPPORTED_Pause | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_MAGICANEG, + .config_init = kszphy_config_init, + .config_aneg = ksz8873mll_config_aneg, + .read_status = ksz8873mll_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE, }, +}, { + .phy_id = PHY_ID_KSZ886X, + .phy_id_mask = 0x00fffff0, + .name = "Micrel KSZ886X Switch", + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = kszphy_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE, }, +} }; + +static int __init ksphy_init(void) +{ + return phy_drivers_register(ksphy_driver, + ARRAY_SIZE(ksphy_driver)); +} + +static void __exit ksphy_exit(void) +{ + phy_drivers_unregister(ksphy_driver, + ARRAY_SIZE(ksphy_driver)); +} + +module_init(ksphy_init); +module_exit(ksphy_exit); + +MODULE_DESCRIPTION("Micrel PHY driver"); +MODULE_AUTHOR("David J. Choi"); +MODULE_LICENSE("GPL"); + +static struct mdio_device_id __maybe_unused micrel_tbl[] = { + { PHY_ID_KSZ9021, 0x000ffffe }, + { PHY_ID_KSZ9031, 0x00fffff0 }, + { PHY_ID_KSZ8001, 0x00ffffff }, + { PHY_ID_KS8737, 0x00fffff0 }, + { PHY_ID_KSZ8021, 0x00ffffff }, + { PHY_ID_KSZ8031, 0x00ffffff }, + { PHY_ID_KSZ8041, 0x00fffff0 }, + { PHY_ID_KSZ8051, 0x00fffff0 }, + { PHY_ID_KSZ8061, 0x00fffff0 }, + { PHY_ID_KSZ8081, 0x00fffff0 }, + { PHY_ID_KSZ8873MLL, 0x00fffff0 }, + { PHY_ID_KSZ886X, 0x00fffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, micrel_tbl); diff --git a/drivers/net/phy/national.c b/drivers/net/phy/national.c new file mode 100644 index 00000000000..9a5f234d95b --- /dev/null +++ b/drivers/net/phy/national.c @@ -0,0 +1,168 @@ +/* + * drivers/net/phy/national.c + * + * Driver for National Semiconductor PHYs + * + * Author: Stuart Menefy <stuart.menefy@st.com> + * Maintainer: Giuseppe Cavallaro <peppe.cavallaro@st.com> + * + * Copyright (c) 2008 STMicroelectronics Limited + * + * 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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/netdevice.h> + +#define DEBUG + +/* DP83865 phy identifier values */ +#define DP83865_PHY_ID 0x20005c7a + +#define DP83865_INT_STATUS 0x14 +#define DP83865_INT_MASK 0x15 +#define DP83865_INT_CLEAR 0x17 + +#define DP83865_INT_REMOTE_FAULT 0x0008 +#define DP83865_INT_ANE_COMPLETED 0x0010 +#define DP83865_INT_LINK_CHANGE 0xe000 +#define DP83865_INT_MASK_DEFAULT (DP83865_INT_REMOTE_FAULT | \ + DP83865_INT_ANE_COMPLETED | \ + DP83865_INT_LINK_CHANGE) + +/* Advanced proprietary configuration */ +#define NS_EXP_MEM_CTL 0x16 +#define NS_EXP_MEM_DATA 0x1d +#define NS_EXP_MEM_ADD 0x1e + +#define LED_CTRL_REG 0x13 +#define AN_FALLBACK_AN 0x0001 +#define AN_FALLBACK_CRC 0x0002 +#define AN_FALLBACK_IE 0x0004 +#define ALL_FALLBACK_ON (AN_FALLBACK_AN | AN_FALLBACK_CRC | AN_FALLBACK_IE) + +enum hdx_loopback { + hdx_loopback_on = 0, + hdx_loopback_off = 1, +}; + +static u8 ns_exp_read(struct phy_device *phydev, u16 reg) +{ + phy_write(phydev, NS_EXP_MEM_ADD, reg); + return phy_read(phydev, NS_EXP_MEM_DATA); +} + +static void ns_exp_write(struct phy_device *phydev, u16 reg, u8 data) +{ + phy_write(phydev, NS_EXP_MEM_ADD, reg); + phy_write(phydev, NS_EXP_MEM_DATA, data); +} + +static int ns_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, DP83865_INT_MASK, + DP83865_INT_MASK_DEFAULT); + else + err = phy_write(phydev, DP83865_INT_MASK, 0); + + return err; +} + +static int ns_ack_interrupt(struct phy_device *phydev) +{ + int ret = phy_read(phydev, DP83865_INT_STATUS); + if (ret < 0) + return ret; + + /* Clear the interrupt status bit by writing a “1” + * to the corresponding bit in INT_CLEAR (2:0 are reserved) */ + ret = phy_write(phydev, DP83865_INT_CLEAR, ret & ~0x7); + + return ret; +} + +static void ns_giga_speed_fallback(struct phy_device *phydev, int mode) +{ + int bmcr = phy_read(phydev, MII_BMCR); + + phy_write(phydev, MII_BMCR, (bmcr | BMCR_PDOWN)); + + /* Enable 8 bit expended memory read/write (no auto increment) */ + phy_write(phydev, NS_EXP_MEM_CTL, 0); + phy_write(phydev, NS_EXP_MEM_ADD, 0x1C0); + phy_write(phydev, NS_EXP_MEM_DATA, 0x0008); + phy_write(phydev, MII_BMCR, (bmcr & ~BMCR_PDOWN)); + phy_write(phydev, LED_CTRL_REG, mode); +} + +static void ns_10_base_t_hdx_loopack(struct phy_device *phydev, int disable) +{ + if (disable) + ns_exp_write(phydev, 0x1c0, ns_exp_read(phydev, 0x1c0) | 1); + else + ns_exp_write(phydev, 0x1c0, + ns_exp_read(phydev, 0x1c0) & 0xfffe); + + pr_debug("10BASE-T HDX loopback %s\n", + (ns_exp_read(phydev, 0x1c0) & 0x0001) ? "off" : "on"); +} + +static int ns_config_init(struct phy_device *phydev) +{ + ns_giga_speed_fallback(phydev, ALL_FALLBACK_ON); + /* In the latest MAC or switches design, the 10 Mbps loopback + is desired to be turned off. */ + ns_10_base_t_hdx_loopack(phydev, hdx_loopback_off); + return ns_ack_interrupt(phydev); +} + +static struct phy_driver dp83865_driver = { + .phy_id = DP83865_PHY_ID, + .phy_id_mask = 0xfffffff0, + .name = "NatSemi DP83865", + .features = PHY_GBIT_FEATURES | SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_INTERRUPT, + .config_init = ns_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = ns_ack_interrupt, + .config_intr = ns_config_intr, + .driver = {.owner = THIS_MODULE,} +}; + +static int __init ns_init(void) +{ + return phy_driver_register(&dp83865_driver); +} + +static void __exit ns_exit(void) +{ + phy_driver_unregister(&dp83865_driver); +} + +MODULE_DESCRIPTION("NatSemi PHY driver"); +MODULE_AUTHOR("Stuart Menefy"); +MODULE_LICENSE("GPL"); + +module_init(ns_init); +module_exit(ns_exit); + +static struct mdio_device_id __maybe_unused ns_tbl[] = { + { DP83865_PHY_ID, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, ns_tbl); diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 1474b7c5ac0..f7c61812ea4 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1,12 +1,10 @@ -/* - * drivers/net/phy/phy.c - * - * Framework for configuring and reading PHY devices +/* 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. + * Copyright (c) 2006, 2007 Maciej W. Rozycki * * 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 @@ -14,117 +12,123 @@ * option) any later version. * */ -#include <linux/config.h> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/slab.h> #include <linux/interrupt.h> -#include <linux/init.h> #include <linux/delay.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> -#include <linux/spinlock.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/phy.h> +#include <linux/timer.h> +#include <linux/workqueue.h> +#include <linux/mdio.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/atomic.h> -#include <asm/io.h> #include <asm/irq.h> -#include <asm/uaccess.h> - -/* Convenience function to print out the current phy status - */ -void phy_print_status(struct phy_device *phydev) -{ - pr_info("PHY: %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) +static const char *phy_speed_to_str(int speed) { - 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; + switch (speed) { + case SPEED_10: + return "10Mbps"; + case SPEED_100: + return "100Mbps"; + case SPEED_1000: + return "1Gbps"; + case SPEED_2500: + return "2.5Gbps"; + case SPEED_10000: + return "10Gbps"; + case SPEED_UNKNOWN: + return "Unknown"; + default: + return "Unsupported (update phy.c)"; + } } -EXPORT_SYMBOL(phy_read); -int phy_write(struct phy_device *phydev, u16 regnum, u16 val) +/** + * phy_print_status - Convenience function to print out the current phy status + * @phydev: the phy_device struct + */ +void phy_print_status(struct phy_device *phydev) { - 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; + if (phydev->link) { + netdev_info(phydev->attached_dev, + "Link is Up - %s/%s - flow control %s\n", + phy_speed_to_str(phydev->speed), + DUPLEX_FULL == phydev->duplex ? "Full" : "Half", + phydev->pause ? "rx/tx" : "off"); + } else { + netdev_info(phydev->attached_dev, "Link is Down\n"); + } } -EXPORT_SYMBOL(phy_write); - +EXPORT_SYMBOL(phy_print_status); -int phy_clear_interrupt(struct phy_device *phydev) +/** + * phy_clear_interrupt - Ack the phy device's interrupt + * @phydev: the phy_device struct + * + * If the @phydev driver has an ack_interrupt function, call it to + * ack and clear the phy device's interrupt. + * + * Returns 0 on success or < 0 on error. + */ +static int phy_clear_interrupt(struct phy_device *phydev) { - int err = 0; - if (phydev->drv->ack_interrupt) - err = phydev->drv->ack_interrupt(phydev); + return phydev->drv->ack_interrupt(phydev); - return err; + return 0; } - -int phy_config_interrupt(struct phy_device *phydev, u32 interrupts) +/** + * phy_config_interrupt - configure the PHY device for the requested interrupts + * @phydev: the phy_device struct + * @interrupts: interrupt flags to configure for this @phydev + * + * Returns 0 on success or < 0 on error. + */ +static 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 phydev->drv->config_intr(phydev); - return err; + return 0; } -/* phy_aneg_done +/** + * phy_aneg_done - return auto-negotiation status + * @phydev: target phy_device struct * - * 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. + * Description: Return the auto-negotiation status from this @phydev + * Returns > 0 on success or < 0 on error. 0 means that auto-negotiation + * is still pending. */ static inline int phy_aneg_done(struct phy_device *phydev) { - int retval; - - retval = phy_read(phydev, MII_BMSR); + if (phydev->drv->aneg_done) + return phydev->drv->aneg_done(phydev); - return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); + return genphy_aneg_done(phydev); } /* A structure for mapping a particular speed and duplex - * combination to a particular SUPPORTED and ADVERTISED value */ + * combination to a particular SUPPORTED and ADVERTISED value + */ struct phy_setting { int speed; int duplex; @@ -132,7 +136,7 @@ struct phy_setting { }; /* A mapping of all SUPPORTED settings to speed/duplex */ -static struct phy_setting settings[] = { +static const struct phy_setting settings[] = { { .speed = 10000, .duplex = DUPLEX_FULL, @@ -170,37 +174,40 @@ static struct phy_setting settings[] = { }, }; -#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting)) +#define MAX_NUM_SETTINGS ARRAY_SIZE(settings) -/* phy_find_setting +/** + * phy_find_setting - find a PHY settings array entry that matches speed & duplex + * @speed: speed to match + * @duplex: duplex to match * - * description: Searches the settings array for the setting which + * 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) +static inline unsigned int phy_find_setting(int speed, int duplex) { - int idx = 0; + unsigned int idx = 0; while (idx < ARRAY_SIZE(settings) && - (settings[idx].speed != speed || - settings[idx].duplex != duplex)) + (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 +/** + * phy_find_valid - find a PHY setting that matches the requested features mask + * @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 + * 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) +static inline unsigned int phy_find_valid(unsigned int idx, u32 features) { while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features)) idx++; @@ -208,20 +215,22 @@ static inline int phy_find_valid(int idx, u32 features) return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; } -/* phy_sanitize_settings +/** + * phy_sanitize_settings - make sure the PHY is set to supported speed and duplex + * @phydev: the target phy_device struct * - * description: Make sure the PHY is set to supported speeds and + * 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 + * 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; + unsigned int idx; /* Sanitize settings based on PHY capabilities */ if ((features & SUPPORTED_Autoneg) == 0) - phydev->autoneg = 0; + phydev->autoneg = AUTONEG_DISABLE; idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex), features); @@ -229,26 +238,27 @@ void phy_sanitize_settings(struct phy_device *phydev) phydev->speed = settings[idx].speed; phydev->duplex = settings[idx].duplex; } -EXPORT_SYMBOL(phy_sanitize_settings); -/* phy_ethtool_sset: - * A generic ethtool sset function. Handles all the details +/** + * phy_ethtool_sset - generic ethtool sset function, handles all the details + * @phydev: target phy_device struct + * @cmd: ethtool_cmd * * 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 - * + * care if ethtool tries to give us bad values. */ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) { + u32 speed = ethtool_cmd_speed(cmd); + if (cmd->phy_address != phydev->addr) return -EINVAL; - /* We make sure that we don't pass unsupported - * values in to the PHY */ + /* We make sure that we don't pass unsupported values in to the PHY */ cmd->advertising &= phydev->supported; /* Verify the settings we care about. */ @@ -258,17 +268,17 @@ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) 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))) + if (cmd->autoneg == AUTONEG_DISABLE && + ((speed != SPEED_1000 && + speed != SPEED_100 && + speed != SPEED_10) || + (cmd->duplex != DUPLEX_HALF && + cmd->duplex != DUPLEX_FULL))) return -EINVAL; phydev->autoneg = cmd->autoneg; - phydev->speed = cmd->speed; + phydev->speed = speed; phydev->advertising = cmd->advertising; @@ -284,56 +294,72 @@ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) return 0; } +EXPORT_SYMBOL(phy_ethtool_sset); int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd) { cmd->supported = phydev->supported; cmd->advertising = phydev->advertising; + cmd->lp_advertising = phydev->lp_advertising; - cmd->speed = phydev->speed; + ethtool_cmd_speed_set(cmd, phydev->speed); cmd->duplex = phydev->duplex; - cmd->port = PORT_MII; + if (phydev->interface == PHY_INTERFACE_MODE_MOCA) + cmd->port = PORT_BNC; + else + cmd->port = PORT_MII; cmd->phy_address = phydev->addr; - cmd->transceiver = XCVR_EXTERNAL; + cmd->transceiver = phy_is_internal(phydev) ? + XCVR_INTERNAL : XCVR_EXTERNAL; cmd->autoneg = phydev->autoneg; return 0; } +EXPORT_SYMBOL(phy_ethtool_gset); - -/* Note that this function is currently incompatible with the +/** + * phy_mii_ioctl - generic PHY MII ioctl interface + * @phydev: the phy_device struct + * @ifr: &struct ifreq for socket ioctl's + * @cmd: ioctl cmd to execute + * + * Note that this function is currently incompatible with the * PHYCONTROL layer. It changes registers without regard to - * current state. Use at own risk + * current state. Use at own risk. */ -int phy_mii_ioctl(struct phy_device *phydev, - struct mii_ioctl_data *mii_data, int cmd) +int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd) { + struct mii_ioctl_data *mii_data = if_mii(ifr); u16 val = mii_data->val_in; switch (cmd) { case SIOCGMIIPHY: mii_data->phy_id = phydev->addr; - break; + /* fall through */ + case SIOCGMIIREG: - mii_data->val_out = phy_read(phydev, mii_data->reg_num); - break; + mii_data->val_out = mdiobus_read(phydev->bus, mii_data->phy_id, + mii_data->reg_num); + return 0; case SIOCSMIIREG: - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - if (mii_data->phy_id == phydev->addr) { - switch(mii_data->reg_num) { + switch (mii_data->reg_num) { case MII_BMCR: - if (val & (BMCR_RESET|BMCR_ANENABLE)) + if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) phydev->autoneg = AUTONEG_DISABLE; else phydev->autoneg = AUTONEG_ENABLE; - if ((!phydev->autoneg) && (val & BMCR_FULLDPLX)) + if (!phydev->autoneg && (val & BMCR_FULLDPLX)) phydev->duplex = DUPLEX_FULL; else phydev->duplex = DUPLEX_HALF; + if (!phydev->autoneg && (val & BMCR_SPEED1000)) + phydev->speed = SPEED_1000; + else if (!phydev->autoneg && + (val & BMCR_SPEED100)) + phydev->speed = SPEED_100; break; case MII_ADVERTISE: phydev->advertising = val; @@ -344,37 +370,44 @@ int phy_mii_ioctl(struct phy_device *phydev, } } - 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; - } + mdiobus_write(phydev->bus, mii_data->phy_id, + mii_data->reg_num, val); - return 0; + if (mii_data->reg_num == MII_BMCR && + val & BMCR_RESET) + return phy_init_hw(phydev); + return 0; + + case SIOCSHWTSTAMP: + if (phydev->drv->hwtstamp) + return phydev->drv->hwtstamp(phydev, ifr); + /* fall through */ + + default: + return -EOPNOTSUPP; + } } +EXPORT_SYMBOL(phy_mii_ioctl); -/* phy_start_aneg +/** + * phy_start_aneg - start auto-negotiation for this PHY device + * @phydev: the phy_device struct * - * 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. + * 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); + mutex_lock(&phydev->lock); if (AUTONEG_DISABLE == phydev->autoneg) phy_sanitize_settings(phydev); err = phydev->drv->config_aneg(phydev); - if (err < 0) goto out_unlock; @@ -389,147 +422,117 @@ int phy_start_aneg(struct phy_device *phydev) } out_unlock: - spin_unlock(&phydev->lock); + mutex_unlock(&phydev->lock); return err; } EXPORT_SYMBOL(phy_start_aneg); - -static void phy_change(void *data); -static void phy_timer(unsigned long data); - -/* phy_start_machine: +/** + * phy_start_machine - start PHY state machine tracking + * @phydev: the phy_device struct * - * description: The PHY infrastructure can run a state 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 *)) + * of the PHY. If you want to maintain your own state machine, + * do not call this function. + */ +void phy_start_machine(struct phy_device *phydev) { - 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); + queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ); } -/* phy_stop_machine +/** + * phy_stop_machine - stop the PHY state machine tracking + * @phydev: target phy_device struct * - * 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 + * Description: Stops the state machine timer, sets the state to UP + * (unless it wasn't up yet). This function must be called BEFORE * phy_detach. */ void phy_stop_machine(struct phy_device *phydev) { - del_timer_sync(&phydev->phy_timer); + cancel_delayed_work_sync(&phydev->state_queue); - spin_lock(&phydev->lock); + mutex_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; -} - -/* 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"); + mutex_unlock(&phydev->lock); } - -/* phy_error: +/** + * phy_error - enter HALTED state for this PHY device + * @phydev: target phy_device struct * * 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) +static void phy_error(struct phy_device *phydev) { - spin_lock(&phydev->lock); + mutex_lock(&phydev->lock); phydev->state = PHY_HALTED; - spin_unlock(&phydev->lock); + mutex_unlock(&phydev->lock); } -/* phy_interrupt +/** + * phy_interrupt - PHY interrupt handler + * @irq: interrupt line + * @phy_dat: phy_device pointer * - * description: When a PHY interrupt occurs, the handler disables + * 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) +static irqreturn_t phy_interrupt(int irq, void *phy_dat) { struct phy_device *phydev = phy_dat; + if (PHY_HALTED == phydev->state) + return IRQ_NONE; /* It can't be ours. */ + /* 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. */ + * interrupt, and then reenable the irq line. + */ disable_irq_nosync(irq); + atomic_inc(&phydev->irq_disable); - schedule_work(&phydev->phy_queue); + queue_work(system_power_efficient_wq, &phydev->phy_queue); return IRQ_HANDLED; } -/* Enable the interrupts from the PHY side */ -int phy_enable_interrupts(struct phy_device *phydev) +/** + * phy_enable_interrupts - Enable the interrupts from the PHY side + * @phydev: target phy_device struct + */ +static int phy_enable_interrupts(struct phy_device *phydev) { - int err; - - err = phy_clear_interrupt(phydev); + int err = phy_clear_interrupt(phydev); if (err < 0) return err; - err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); - - return err; + return phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); } -EXPORT_SYMBOL(phy_enable_interrupts); -/* Disable the PHY interrupts from the PHY side */ -int phy_disable_interrupts(struct phy_device *phydev) +/** + * phy_disable_interrupts - Disable the PHY interrupts from the PHY side + * @phydev: target phy_device struct + */ +static 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; @@ -540,113 +543,144 @@ phy_err: return err; } -EXPORT_SYMBOL(phy_disable_interrupts); -/* phy_start_interrupts +/** + * phy_start_interrupts - request and enable interrupts for a PHY device + * @phydev: target phy_device struct * - * description: Request the interrupt for the given PHY. If - * this fails, then we set irq to PHY_POLL. + * 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. + * Returns 0 on success or < 0 on error. */ 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); + atomic_set(&phydev->irq_disable, 0); + if (request_irq(phydev->irq, phy_interrupt, 0, "phy_interrupt", + phydev) < 0) { + pr_warn("%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; + return phy_enable_interrupts(phydev); } EXPORT_SYMBOL(phy_start_interrupts); +/** + * phy_stop_interrupts - disable interrupts from a PHY device + * @phydev: target phy_device struct + */ int phy_stop_interrupts(struct phy_device *phydev) { - int err; - - err = phy_disable_interrupts(phydev); + int err = phy_disable_interrupts(phydev); if (err) phy_error(phydev); free_irq(phydev->irq, phydev); + /* Cannot call flush_scheduled_work() here as desired because + * of rtnl_lock(), but we do not really care about what would + * be done, except from enable_irq(), so cancel any work + * possibly pending and take care of the matter below. + */ + cancel_work_sync(&phydev->phy_queue); + /* If work indeed has been cancelled, disable_irq() will have + * been left unbalanced from phy_interrupt() and enable_irq() + * has to be called so that other devices on the line work. + */ + while (atomic_dec_return(&phydev->irq_disable) >= 0) + enable_irq(phydev->irq); + return err; } EXPORT_SYMBOL(phy_stop_interrupts); - -/* Scheduled by the phy_interrupt/timer to handle PHY changes */ -static void phy_change(void *data) +/** + * phy_change - Scheduled by the phy_interrupt/timer to handle PHY changes + * @work: work_struct that describes the work to be done + */ +void phy_change(struct work_struct *work) { - int err; - struct phy_device *phydev = data; + struct phy_device *phydev = + container_of(work, struct phy_device, phy_queue); - err = phy_disable_interrupts(phydev); + if (phydev->drv->did_interrupt && + !phydev->drv->did_interrupt(phydev)) + goto ignore; - if (err) + if (phy_disable_interrupts(phydev)) goto phy_err; - spin_lock(&phydev->lock); + mutex_lock(&phydev->lock); if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state)) phydev->state = PHY_CHANGELINK; - spin_unlock(&phydev->lock); + mutex_unlock(&phydev->lock); + atomic_dec(&phydev->irq_disable); enable_irq(phydev->irq); /* Reenable interrupts */ - err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); - - if (err) + if (PHY_HALTED != phydev->state && + phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED)) goto irq_enable_err; + /* reschedule state queue work to run as soon as possible */ + cancel_delayed_work_sync(&phydev->state_queue); + queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, 0); + return; + +ignore: + atomic_dec(&phydev->irq_disable); + enable_irq(phydev->irq); return; irq_enable_err: disable_irq(phydev->irq); + atomic_inc(&phydev->irq_disable); phy_err: phy_error(phydev); } -/* Bring down the PHY link, and stop checking the status. */ +/** + * phy_stop - Bring down the PHY link, and stop checking the status + * @phydev: target phy_device struct + */ void phy_stop(struct phy_device *phydev) { - spin_lock(&phydev->lock); + mutex_lock(&phydev->lock); if (PHY_HALTED == phydev->state) goto out_unlock; - if (phydev->irq != PHY_POLL) { - /* Clear any pending interrupts */ - phy_clear_interrupt(phydev); - + if (phy_interrupt_is_valid(phydev)) { /* Disable PHY Interrupts */ phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); + + /* Clear any pending interrupts */ + phy_clear_interrupt(phydev); } phydev->state = PHY_HALTED; out_unlock: - spin_unlock(&phydev->lock); -} + mutex_unlock(&phydev->lock); + /* Cannot call flush_scheduled_work() here as desired because + * of rtnl_lock(), but PHY_HALTED shall guarantee phy_change() + * will not reenable interrupts. + */ +} +EXPORT_SYMBOL(phy_stop); -/* phy_start +/** + * phy_start - start or restart a PHY device + * @phydev: target phy_device struct * - * description: Indicates the attached device's readiness to + * 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 @@ -654,209 +688,449 @@ out_unlock: */ void phy_start(struct phy_device *phydev) { - spin_lock(&phydev->lock); + mutex_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; + 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); + mutex_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) +/** + * phy_state_machine - Handle the state machine + * @work: work_struct that describes the work to be done + */ +void phy_state_machine(struct work_struct *work) { - struct phy_device *phydev = (struct phy_device *)data; - int needs_aneg = 0; + struct delayed_work *dwork = to_delayed_work(work); + struct phy_device *phydev = + container_of(dwork, struct phy_device, state_queue); + bool needs_aneg = false, do_suspend = false, do_resume = false; int err = 0; - spin_lock(&phydev->lock); + mutex_lock(&phydev->lock); - if (phydev->adjust_state) - phydev->adjust_state(phydev->attached_dev); + if (phydev->drv->link_change_notify) + phydev->drv->link_change_notify(phydev); - switch(phydev->state) { - case PHY_DOWN: - case PHY_STARTING: - case PHY_READY: - case PHY_PENDING: + switch (phydev->state) { + case PHY_DOWN: + case PHY_STARTING: + case PHY_READY: + case PHY_PENDING: + break; + case PHY_UP: + needs_aneg = true; + + phydev->link_timeout = PHY_AN_TIMEOUT; + + break; + case PHY_AN: + err = phy_read_status(phydev); + if (err < 0) break; - case PHY_UP: - needs_aneg = 1; - phydev->link_timeout = PHY_AN_TIMEOUT; + /* If the link is down, give up on negotiation for now */ + if (!phydev->link) { + phydev->state = PHY_NOLINK; + netif_carrier_off(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + break; + } + + /* Check if negotiation is done. Break if there's an error */ + err = phy_aneg_done(phydev); + if (err < 0) + break; + + /* If AN is done, we're running */ + if (err > 0) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + } else if (0 == phydev->link_timeout--) + needs_aneg = true; + break; + case PHY_NOLINK: + err = phy_read_status(phydev); + if (err) + break; + + if (phydev->link) { + if (AUTONEG_ENABLE == phydev->autoneg) { + err = phy_aneg_done(phydev); + if (err < 0) + break; + + if (!err) { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; + break; + } + } + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + } + break; + case PHY_FORCING: + err = genphy_update_link(phydev); + if (err) + break; + + if (phydev->link) { + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + } else { + if (0 == phydev->link_timeout--) + needs_aneg = true; + } + + phydev->adjust_link(phydev->attached_dev); + break; + case PHY_RUNNING: + /* Only register a CHANGE if we are + * polling or ignoring interrupts + */ + if (!phy_interrupt_is_valid(phydev)) + 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_interrupt_is_valid(phydev)) + 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); + do_suspend = true; + } + break; + case PHY_RESUMING: + err = phy_clear_interrupt(phydev); + if (err) + break; + err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); + if (err) break; - case PHY_AN: - /* Check if negotiation is done. Break - * if there's an error */ + + if (AUTONEG_ENABLE == phydev->autoneg) { err = phy_aneg_done(phydev); if (err < 0) break; - /* If auto-negotiation is done, we change to - * either RUNNING, or NOLINK */ + /* err > 0 if AN is done. + * Otherwise, it's 0, and we're still waiting for AN + */ 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 { + } 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; + } else { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; } - break; - case PHY_NOLINK: + } else { 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); + } else { + phydev->state = PHY_NOLINK; } - break; - case PHY_FORCING: - err = phy_read_status(phydev); + phydev->adjust_link(phydev->attached_dev); + } + do_resume = true; + break; + } - if (err) - break; + mutex_unlock(&phydev->lock); - 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; - } - } + if (needs_aneg) + err = phy_start_aneg(phydev); + else if (do_suspend) + phy_suspend(phydev); + else if (do_resume) + phy_resume(phydev); - 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 < 0) + phy_error(phydev); - if (err) - break; + queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, + PHY_STATE_TIME * HZ); +} - if (phydev->link) { - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - } else { - phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); - } +void phy_mac_interrupt(struct phy_device *phydev, int new_link) +{ + cancel_work_sync(&phydev->phy_queue); + phydev->link = new_link; + schedule_work(&phydev->phy_queue); +} +EXPORT_SYMBOL(phy_mac_interrupt); - phydev->adjust_link(phydev->attached_dev); +static inline void mmd_phy_indirect(struct mii_bus *bus, int prtad, int devad, + int addr) +{ + /* Write the desired MMD Devad */ + bus->write(bus, addr, MII_MMD_CTRL, devad); - 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: + /* Write the desired MMD register address */ + bus->write(bus, addr, MII_MMD_DATA, prtad); - err = phy_clear_interrupt(phydev); + /* Select the Function : DATA with no post increment */ + bus->write(bus, addr, MII_MMD_CTRL, (devad | MII_MMD_CTRL_NOINCR)); +} - if (err) - break; +/** + * phy_read_mmd_indirect - reads data from the MMD registers + * @bus: the target MII bus + * @prtad: MMD Address + * @devad: MMD DEVAD + * @addr: PHY address on the MII bus + * + * Description: it reads data from the MMD registers (clause 22 to access to + * clause 45) of the specified phy address. + * To read these register we have: + * 1) Write reg 13 // DEVAD + * 2) Write reg 14 // MMD Address + * 3) Write reg 13 // MMD Data Command for MMD DEVAD + * 3) Read reg 14 // Read MMD data + */ +static int phy_read_mmd_indirect(struct mii_bus *bus, int prtad, int devad, + int addr) +{ + mmd_phy_indirect(bus, prtad, devad, addr); - err = phy_config_interrupt(phydev, - PHY_INTERRUPT_ENABLED); + /* Read the content of the MMD's selected register */ + return bus->read(bus, addr, MII_MMD_DATA); +} - if (err) - break; +/** + * phy_write_mmd_indirect - writes data to the MMD registers + * @bus: the target MII bus + * @prtad: MMD Address + * @devad: MMD DEVAD + * @addr: PHY address on the MII bus + * @data: data to write in the MMD register + * + * Description: Write data from the MMD registers of the specified + * phy address. + * To write these register we have: + * 1) Write reg 13 // DEVAD + * 2) Write reg 14 // MMD Address + * 3) Write reg 13 // MMD Data Command for MMD DEVAD + * 3) Write reg 14 // Write MMD data + */ +static void phy_write_mmd_indirect(struct mii_bus *bus, int prtad, int devad, + int addr, u32 data) +{ + mmd_phy_indirect(bus, prtad, devad, addr); - if (AUTONEG_ENABLE == phydev->autoneg) { - err = phy_aneg_done(phydev); - if (err < 0) - break; + /* Write the data into MMD's selected register */ + bus->write(bus, addr, MII_MMD_DATA, data); +} - /* 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; +/** + * phy_init_eee - init and check the EEE feature + * @phydev: target phy_device struct + * @clk_stop_enable: PHY may stop the clock during LPI + * + * Description: it checks if the Energy-Efficient Ethernet (EEE) + * is supported by looking at the MMD registers 3.20 and 7.60/61 + * and it programs the MMD register 3.0 setting the "Clock stop enable" + * bit if required. + */ +int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) +{ + /* According to 802.3az,the EEE is supported only in full duplex-mode. + * Also EEE feature is active when core is operating with MII, GMII + * or RGMII. + */ + if ((phydev->duplex == DUPLEX_FULL) && + ((phydev->interface == PHY_INTERFACE_MODE_MII) || + (phydev->interface == PHY_INTERFACE_MODE_GMII) || + (phydev->interface == PHY_INTERFACE_MODE_RGMII))) { + int eee_lp, eee_cap, eee_adv; + u32 lp, cap, adv; + int status; + unsigned int idx; + + /* Read phy status to properly get the right settings */ + status = phy_read_status(phydev); + if (status) + return status; + + /* First check if the EEE ability is supported */ + eee_cap = phy_read_mmd_indirect(phydev->bus, MDIO_PCS_EEE_ABLE, + MDIO_MMD_PCS, phydev->addr); + if (eee_cap < 0) + return eee_cap; + + cap = mmd_eee_cap_to_ethtool_sup_t(eee_cap); + if (!cap) + return -EPROTONOSUPPORT; + + /* Check which link settings negotiated and verify it in + * the EEE advertising registers. + */ + eee_lp = phy_read_mmd_indirect(phydev->bus, MDIO_AN_EEE_LPABLE, + MDIO_MMD_AN, phydev->addr); + if (eee_lp < 0) + return eee_lp; + + eee_adv = phy_read_mmd_indirect(phydev->bus, MDIO_AN_EEE_ADV, + MDIO_MMD_AN, phydev->addr); + if (eee_adv < 0) + return eee_adv; + + adv = mmd_eee_adv_to_ethtool_adv_t(eee_adv); + lp = mmd_eee_adv_to_ethtool_adv_t(eee_lp); + idx = phy_find_setting(phydev->speed, phydev->duplex); + if (!(lp & adv & settings[idx].setting)) + return -EPROTONOSUPPORT; + + if (clk_stop_enable) { + /* Configure the PHY to stop receiving xMII + * clock while it is signaling LPI. + */ + int val = phy_read_mmd_indirect(phydev->bus, MDIO_CTRL1, + MDIO_MMD_PCS, + phydev->addr); + if (val < 0) + return val; + + val |= MDIO_PCS_CTRL1_CLKSTOP_EN; + phy_write_mmd_indirect(phydev->bus, MDIO_CTRL1, + MDIO_MMD_PCS, phydev->addr, val); + } + + return 0; /* EEE supported */ } - spin_unlock(&phydev->lock); + return -EPROTONOSUPPORT; +} +EXPORT_SYMBOL(phy_init_eee); - if (needs_aneg) - err = phy_start_aneg(phydev); +/** + * phy_get_eee_err - report the EEE wake error count + * @phydev: target phy_device struct + * + * Description: it is to report the number of time where the PHY + * failed to complete its normal wake sequence. + */ +int phy_get_eee_err(struct phy_device *phydev) +{ + return phy_read_mmd_indirect(phydev->bus, MDIO_PCS_EEE_WK_ERR, + MDIO_MMD_PCS, phydev->addr); +} +EXPORT_SYMBOL(phy_get_eee_err); - if (err < 0) - phy_error(phydev); +/** + * phy_ethtool_get_eee - get EEE supported and status + * @phydev: target phy_device struct + * @data: ethtool_eee data + * + * Description: it reportes the Supported/Advertisement/LP Advertisement + * capabilities. + */ +int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_eee *data) +{ + int val; + + /* Get Supported EEE */ + val = phy_read_mmd_indirect(phydev->bus, MDIO_PCS_EEE_ABLE, + MDIO_MMD_PCS, phydev->addr); + if (val < 0) + return val; + data->supported = mmd_eee_cap_to_ethtool_sup_t(val); + + /* Get advertisement EEE */ + val = phy_read_mmd_indirect(phydev->bus, MDIO_AN_EEE_ADV, + MDIO_MMD_AN, phydev->addr); + if (val < 0) + return val; + data->advertised = mmd_eee_adv_to_ethtool_adv_t(val); + + /* Get LP advertisement EEE */ + val = phy_read_mmd_indirect(phydev->bus, MDIO_AN_EEE_LPABLE, + MDIO_MMD_AN, phydev->addr); + if (val < 0) + return val; + data->lp_advertised = mmd_eee_adv_to_ethtool_adv_t(val); - mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ); + return 0; } +EXPORT_SYMBOL(phy_ethtool_get_eee); +/** + * phy_ethtool_set_eee - set EEE supported and status + * @phydev: target phy_device struct + * @data: ethtool_eee data + * + * Description: it is to program the Advertisement EEE register. + */ +int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) +{ + int val = ethtool_adv_to_mmd_eee_adv_t(data->advertised); + + phy_write_mmd_indirect(phydev->bus, MDIO_AN_EEE_ADV, MDIO_MMD_AN, + phydev->addr, val); + + return 0; +} +EXPORT_SYMBOL(phy_ethtool_set_eee); + +int phy_ethtool_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + if (phydev->drv->set_wol) + return phydev->drv->set_wol(phydev, wol); + + return -EOPNOTSUPP; +} +EXPORT_SYMBOL(phy_ethtool_set_wol); + +void phy_ethtool_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + if (phydev->drv->get_wol) + phydev->drv->get_wol(phydev, wol); +} +EXPORT_SYMBOL(phy_ethtool_get_wol); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 7da0e3dd5fe..22c57be4dfa 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1,7 +1,4 @@ -/* - * drivers/net/phy/phy_device.c - * - * Framework for finding and configuring PHYs. +/* Framework for finding and configuring PHYs. * Also contains generic PHY driver * * Author: Andy Fleming @@ -14,9 +11,10 @@ * option) any later version. * */ -#include <linux/config.h> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> @@ -27,100 +25,424 @@ #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> -#include <linux/spinlock.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/phy.h> +#include <linux/mdio.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/of.h> -#include <asm/io.h> #include <asm/irq.h> -#include <asm/uaccess.h> MODULE_DESCRIPTION("PHY library"); MODULE_AUTHOR("Andy Fleming"); MODULE_LICENSE("GPL"); -static struct phy_driver genphy_driver; -extern int mdio_bus_init(void); -extern void mdio_bus_exit(void); +void phy_device_free(struct phy_device *phydev) +{ + put_device(&phydev->dev); +} +EXPORT_SYMBOL(phy_device_free); -/* 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. +static void phy_device_release(struct device *dev) +{ + kfree(to_phy_device(dev)); +} + +enum genphy_driver { + GENPHY_DRV_1G, + GENPHY_DRV_10G, + GENPHY_DRV_MAX +}; + +static struct phy_driver genphy_driver[GENPHY_DRV_MAX]; + +static LIST_HEAD(phy_fixup_list); +static DEFINE_MUTEX(phy_fixup_lock); + +/** + * phy_register_fixup - creates a new phy_fixup and adds it to the list + * @bus_id: A string which matches phydev->dev.bus_id (or PHY_ANY_ID) + * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY) + * It can also be PHY_ANY_UID + * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before + * comparison + * @run: The actual code to be run when a matching PHY is found */ -struct phy_device * get_phy_device(struct mii_bus *bus, int addr) +int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, + int (*run)(struct phy_device *)) { - int phy_reg; - u32 phy_id; - struct phy_device *dev = NULL; + struct phy_fixup *fixup = kzalloc(sizeof(*fixup), GFP_KERNEL); - /* Grab the bits from PHYIR1, and put them - * in the upper half */ - phy_reg = bus->read(bus, addr, MII_PHYSID1); + if (!fixup) + return -ENOMEM; - if (phy_reg < 0) - return ERR_PTR(phy_reg); + strlcpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id)); + fixup->phy_uid = phy_uid; + fixup->phy_uid_mask = phy_uid_mask; + fixup->run = run; - phy_id = (phy_reg & 0xffff) << 16; + mutex_lock(&phy_fixup_lock); + list_add_tail(&fixup->list, &phy_fixup_list); + mutex_unlock(&phy_fixup_lock); - /* Grab the bits from PHYIR2, and put them in the lower half */ - phy_reg = bus->read(bus, addr, MII_PHYSID2); + return 0; +} +EXPORT_SYMBOL(phy_register_fixup); - if (phy_reg < 0) - return ERR_PTR(phy_reg); +/* Registers a fixup to be run on any PHY with the UID in phy_uid */ +int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, + int (*run)(struct phy_device *)) +{ + return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run); +} +EXPORT_SYMBOL(phy_register_fixup_for_uid); - phy_id |= (phy_reg & 0xffff); +/* Registers a fixup to be run on the PHY with id string bus_id */ +int phy_register_fixup_for_id(const char *bus_id, + int (*run)(struct phy_device *)) +{ + return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run); +} +EXPORT_SYMBOL(phy_register_fixup_for_id); - /* If the phy_id is all Fs, there is no device there */ - if (0xffffffff == phy_id) - return NULL; +/* Returns 1 if fixup matches phydev in bus_id and phy_uid. + * Fixups can be set to match any in one or more fields. + */ +static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) +{ + if (strcmp(fixup->bus_id, dev_name(&phydev->dev)) != 0) + if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) + return 0; + + if ((fixup->phy_uid & fixup->phy_uid_mask) != + (phydev->phy_id & fixup->phy_uid_mask)) + if (fixup->phy_uid != PHY_ANY_UID) + return 0; + + return 1; +} + +/* Runs any matching fixups for this phydev */ +static int phy_scan_fixups(struct phy_device *phydev) +{ + struct phy_fixup *fixup; + + mutex_lock(&phy_fixup_lock); + list_for_each_entry(fixup, &phy_fixup_list, list) { + if (phy_needs_fixup(phydev, fixup)) { + int err = fixup->run(phydev); + + if (err < 0) { + mutex_unlock(&phy_fixup_lock); + return err; + } + phydev->has_fixups = true; + } + } + mutex_unlock(&phy_fixup_lock); + + return 0; +} - /* Otherwise, we allocate the device, and initialize the - * default values */ - dev = kcalloc(1, sizeof(*dev), GFP_KERNEL); +struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, + bool is_c45, + struct phy_c45_device_ids *c45_ids) +{ + struct phy_device *dev; + /* We allocate the device, and initialize the default values */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (NULL == dev) - return ERR_PTR(-ENOMEM); + return (struct phy_device *)PTR_ERR((void *)-ENOMEM); + + dev->dev.release = phy_device_release; dev->speed = 0; dev->duplex = -1; - dev->pause = dev->asym_pause = 0; + dev->pause = 0; + dev->asym_pause = 0; dev->link = 1; + dev->interface = PHY_INTERFACE_MODE_GMII; dev->autoneg = AUTONEG_ENABLE; + dev->is_c45 = is_c45; dev->addr = addr; dev->phy_id = phy_id; + if (c45_ids) + dev->c45_ids = *c45_ids; dev->bus = bus; + dev->dev.parent = bus->parent; + dev->dev.bus = &mdio_bus_type; + dev->irq = bus->irq != NULL ? bus->irq[addr] : PHY_POLL; + dev_set_name(&dev->dev, PHY_ID_FMT, bus->id, addr); dev->state = PHY_DOWN; - spin_lock_init(&dev->lock); + mutex_init(&dev->lock); + INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); + INIT_WORK(&dev->phy_queue, phy_change); + + /* Request the appropriate module unconditionally; don't + * bother trying to do so only if it isn't already loaded, + * because that gets complicated. A hotplug event would have + * done an unconditional modprobe anyway. + * We don't do normal hotplug because it won't work for MDIO + * -- because it relies on the device staying around for long + * enough for the driver to get loaded. With MDIO, the NIC + * driver will get bored and give up as soon as it finds that + * there's no driver _already_ loaded. + */ + request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id)); + + device_initialize(&dev->dev); return dev; } +EXPORT_SYMBOL(phy_device_create); + +/** + * get_phy_c45_ids - reads the specified addr for its 802.3-c45 IDs. + * @bus: the target MII bus + * @addr: PHY address on the MII bus + * @phy_id: where to store the ID retrieved. + * @c45_ids: where to store the c45 ID information. + * + * If the PHY devices-in-package appears to be valid, it and the + * corresponding identifiers are stored in @c45_ids, zero is stored + * in @phy_id. Otherwise 0xffffffff is stored in @phy_id. Returns + * zero on success. + * + */ +static int get_phy_c45_ids(struct mii_bus *bus, int addr, u32 *phy_id, + struct phy_c45_device_ids *c45_ids) { + int phy_reg; + int i, reg_addr; + const int num_ids = ARRAY_SIZE(c45_ids->device_ids); + + /* Find first non-zero Devices In package. Device + * zero is reserved, so don't probe it. + */ + for (i = 1; + i < num_ids && c45_ids->devices_in_package == 0; + i++) { + reg_addr = MII_ADDR_C45 | i << 16 | 6; + phy_reg = mdiobus_read(bus, addr, reg_addr); + if (phy_reg < 0) + return -EIO; + c45_ids->devices_in_package = (phy_reg & 0xffff) << 16; + + reg_addr = MII_ADDR_C45 | i << 16 | 5; + phy_reg = mdiobus_read(bus, addr, reg_addr); + if (phy_reg < 0) + return -EIO; + c45_ids->devices_in_package |= (phy_reg & 0xffff); + + /* If mostly Fs, there is no device there, + * let's get out of here. + */ + if ((c45_ids->devices_in_package & 0x1fffffff) == 0x1fffffff) { + *phy_id = 0xffffffff; + return 0; + } + } + + /* Now probe Device Identifiers for each device present. */ + for (i = 1; i < num_ids; i++) { + if (!(c45_ids->devices_in_package & (1 << i))) + continue; + + reg_addr = MII_ADDR_C45 | i << 16 | MII_PHYSID1; + phy_reg = mdiobus_read(bus, addr, reg_addr); + if (phy_reg < 0) + return -EIO; + c45_ids->device_ids[i] = (phy_reg & 0xffff) << 16; + + reg_addr = MII_ADDR_C45 | i << 16 | MII_PHYSID2; + phy_reg = mdiobus_read(bus, addr, reg_addr); + if (phy_reg < 0) + return -EIO; + c45_ids->device_ids[i] |= (phy_reg & 0xffff); + } + *phy_id = 0; + return 0; +} + +/** + * get_phy_id - reads the specified addr for its ID. + * @bus: the target MII bus + * @addr: PHY address on the MII bus + * @phy_id: where to store the ID retrieved. + * @is_c45: If true the PHY uses the 802.3 clause 45 protocol + * @c45_ids: where to store the c45 ID information. + * + * Description: In the case of a 802.3-c22 PHY, reads the ID registers + * of the PHY at @addr on the @bus, stores it in @phy_id and returns + * zero on success. + * + * In the case of a 802.3-c45 PHY, get_phy_c45_ids() is invoked, and + * its return value is in turn returned. + * + */ +static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id, + bool is_c45, struct phy_c45_device_ids *c45_ids) +{ + int phy_reg; + + if (is_c45) + return get_phy_c45_ids(bus, addr, phy_id, c45_ids); + + /* Grab the bits from PHYIR1, and put them in the upper half */ + phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); + if (phy_reg < 0) + return -EIO; + + *phy_id = (phy_reg & 0xffff) << 16; + + /* Grab the bits from PHYIR2, and put them in the lower half */ + phy_reg = mdiobus_read(bus, addr, MII_PHYSID2); + if (phy_reg < 0) + return -EIO; + + *phy_id |= (phy_reg & 0xffff); + + return 0; +} + +/** + * get_phy_device - reads the specified PHY device and returns its @phy_device + * struct + * @bus: the target MII bus + * @addr: PHY address on the MII bus + * @is_c45: If true the PHY uses the 802.3 clause 45 protocol + * + * 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, bool is_c45) +{ + struct phy_c45_device_ids c45_ids = {0}; + u32 phy_id = 0; + int r; + + r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids); + if (r) + return ERR_PTR(r); + + /* If the phy_id is mostly Fs, there is no device there */ + if ((phy_id & 0x1fffffff) == 0x1fffffff) + return NULL; + + return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids); +} +EXPORT_SYMBOL(get_phy_device); + +/** + * phy_device_register - Register the phy device on the MDIO bus + * @phydev: phy_device structure to be added to the MDIO bus + */ +int phy_device_register(struct phy_device *phydev) +{ + int err; + + /* Don't register a phy if one is already registered at this address */ + if (phydev->bus->phy_map[phydev->addr]) + return -EINVAL; + phydev->bus->phy_map[phydev->addr] = phydev; + + /* Run all of the fixups for this PHY */ + err = phy_scan_fixups(phydev); + if (err) { + pr_err("PHY %d failed to initialize\n", phydev->addr); + goto out; + } + + err = device_add(&phydev->dev); + if (err) { + pr_err("PHY %d failed to add\n", phydev->addr); + goto out; + } + + return 0; + + out: + phydev->bus->phy_map[phydev->addr] = NULL; + return err; +} +EXPORT_SYMBOL(phy_device_register); -/* phy_prepare_link: +/** + * phy_find_first - finds the first PHY device on the bus + * @bus: the target MII bus + */ +struct phy_device *phy_find_first(struct mii_bus *bus) +{ + int addr; + + for (addr = 0; addr < PHY_MAX_ADDR; addr++) { + if (bus->phy_map[addr]) + return bus->phy_map[addr]; + } + return NULL; +} +EXPORT_SYMBOL(phy_find_first); + +/** + * phy_prepare_link - prepares the PHY layer to monitor link status + * @phydev: target phy_device struct + * @handler: callback function for link status change notifications * - * description: Tells the PHY infrastructure to handle the + * 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 *)) + * this function. + */ +static void phy_prepare_link(struct phy_device *phydev, + void (*handler)(struct net_device *)) { phydev->adjust_link = handler; } -/* phy_connect: +/** + * phy_connect_direct - connect an ethernet device to a specific phy_device + * @dev: the network device to connect + * @phydev: the pointer to the phy device + * @handler: callback function for state change notifications + * @interface: PHY device's interface + */ +int phy_connect_direct(struct net_device *dev, struct phy_device *phydev, + void (*handler)(struct net_device *), + phy_interface_t interface) +{ + int rc; + + rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface); + if (rc) + return rc; + + phy_prepare_link(phydev, handler); + phy_start_machine(phydev); + if (phydev->irq > 0) + phy_start_interrupts(phydev); + + return 0; +} +EXPORT_SYMBOL(phy_connect_direct); + +/** + * phy_connect - connect an ethernet device to a PHY device + * @dev: the network device to connect + * @bus_id: the id string of the PHY device to connect + * @handler: callback function for state change notifications + * @interface: PHY device's interface * - * description: Convenience function for connecting ethernet + * 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 @@ -128,198 +450,361 @@ void phy_prepare_link(struct phy_device *phydev, * 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 *phy_connect(struct net_device *dev, const char *bus_id, + void (*handler)(struct net_device *), + phy_interface_t interface) { struct phy_device *phydev; + struct device *d; + int rc; - phydev = phy_attach(dev, phy_id, flags); - - if (IS_ERR(phydev)) - return phydev; - - phy_prepare_link(phydev, handler); - - phy_start_machine(phydev, NULL); + /* Search the list of PHY devices on the mdio bus for the + * PHY with the requested name + */ + d = bus_find_device_by_name(&mdio_bus_type, NULL, bus_id); + if (!d) { + pr_err("PHY %s not found\n", bus_id); + return ERR_PTR(-ENODEV); + } + phydev = to_phy_device(d); - if (phydev->irq > 0) - phy_start_interrupts(phydev); + rc = phy_connect_direct(dev, phydev, handler, interface); + if (rc) + return ERR_PTR(rc); return phydev; } EXPORT_SYMBOL(phy_connect); +/** + * phy_disconnect - disable interrupts, stop state machine, and detach a PHY + * device + * @phydev: target phy_device struct + */ 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); -/* phy_attach: +/** + * phy_poll_reset - Safely wait until a PHY reset has properly completed + * @phydev: The PHY device to poll * - * 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. + * Description: According to IEEE 802.3, Section 2, Subsection 22.2.4.1.1, as + * published in 2008, a PHY reset may take up to 0.5 seconds. The MII BMCR + * register must be polled until the BMCR_RESET bit clears. + * + * Furthermore, any attempts to write to PHY registers may have no effect + * or even generate MDIO bus errors until this is complete. + * + * Some PHYs (such as the Marvell 88E1111) don't entirely conform to the + * standard and do not fully reset after the BMCR_RESET bit is set, and may + * even *REQUIRE* a soft-reset to properly restart autonegotiation. In an + * effort to support such broken PHYs, this function is separate from the + * standard phy_init_hw() which will zero all the other bits in the BMCR + * and reapply all driver-specific and board-specific fixups. */ -static int phy_compare_id(struct device *dev, void *data) +static int phy_poll_reset(struct phy_device *phydev) { - return strcmp((char *)data, dev->bus_id) ? 0 : 1; + /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ + unsigned int retries = 12; + int ret; + + do { + msleep(50); + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + } while (ret & BMCR_RESET && --retries); + if (ret & BMCR_RESET) + return -ETIMEDOUT; + + /* Some chips (smsc911x) may still need up to another 1ms after the + * BMCR_RESET bit is cleared before they are usable. + */ + msleep(1); + return 0; } -struct phy_device *phy_attach(struct net_device *dev, - const char *phy_id, u32 flags) +int phy_init_hw(struct phy_device *phydev) { - struct bus_type *bus = &mdio_bus_type; - struct phy_device *phydev; - struct device *d; + int ret = 0; - /* 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 (!phydev->drv || !phydev->drv->config_init) + return 0; - if (d) { - phydev = to_phy_device(d); - } else { - printk(KERN_ERR "%s not found\n", phy_id); - return ERR_PTR(-ENODEV); - } + if (phydev->drv->soft_reset) + ret = phydev->drv->soft_reset(phydev); + else + ret = genphy_soft_reset(phydev); + + if (ret < 0) + return ret; + + ret = phy_scan_fixups(phydev); + if (ret < 0) + return ret; + + return phydev->drv->config_init(phydev); +} +EXPORT_SYMBOL(phy_init_hw); + +/** + * phy_attach_direct - attach a network device to a given PHY device pointer + * @dev: network device to attach + * @phydev: Pointer to phy_device to attach + * @flags: PHY device's dev_flags + * @interface: PHY device's interface + * + * 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 a + * generic 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. + */ +int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, + u32 flags, phy_interface_t interface) +{ + struct device *d = &phydev->dev; + struct module *bus_module; + int err; /* Assume that if there is no driver, that it doesn't - * exist, and we should use the genphy driver. */ + * 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; + if (phydev->is_c45) + d->driver = &genphy_driver[GENPHY_DRV_10G].driver; + else + d->driver = &genphy_driver[GENPHY_DRV_1G].driver; err = d->driver->probe(d); + if (err >= 0) + err = device_bind_driver(d); - if (err < 0) - return ERR_PTR(err); - - device_bind_driver(d); - up_write(&d->bus->subsys.rwsem); + if (err) + return err; } if (phydev->attached_dev) { - printk(KERN_ERR "%s: %s already attached\n", - dev->name, phy_id); - return ERR_PTR(-EBUSY); + dev_err(&dev->dev, "PHY already attached\n"); + return -EBUSY; + } + + /* Increment the bus module reference count */ + bus_module = phydev->bus->dev.driver ? + phydev->bus->dev.driver->owner : NULL; + if (!try_module_get(bus_module)) { + dev_err(&dev->dev, "failed to get the bus module\n"); + return -EIO; } phydev->attached_dev = dev; + dev->phydev = phydev; phydev->dev_flags = flags; + phydev->interface = interface; + + phydev->state = PHY_READY; + + /* Do initial configuration here, now that + * we have certain key parameters + * (dev_flags and interface) + */ + err = phy_init_hw(phydev); + if (err) + phy_detach(phydev); + else + phy_resume(phydev); + + return err; +} +EXPORT_SYMBOL(phy_attach_direct); + +/** + * phy_attach - attach a network device to a particular PHY device + * @dev: network device to attach + * @bus_id: Bus ID of PHY device to attach + * @interface: PHY device's interface + * + * Description: Same as phy_attach_direct() except that a PHY bus_id + * string is passed instead of a pointer to a struct phy_device. + */ +struct phy_device *phy_attach(struct net_device *dev, const char *bus_id, + phy_interface_t interface) +{ + struct bus_type *bus = &mdio_bus_type; + struct phy_device *phydev; + struct device *d; + int rc; + + /* Search the list of PHY devices on the mdio bus for the + * PHY with the requested name + */ + d = bus_find_device_by_name(bus, NULL, bus_id); + if (!d) { + pr_err("PHY %s not found\n", bus_id); + return ERR_PTR(-ENODEV); + } + phydev = to_phy_device(d); + + rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface); + if (rc) + return ERR_PTR(rc); + return phydev; } EXPORT_SYMBOL(phy_attach); +/** + * phy_detach - detach a PHY device from its network device + * @phydev: target phy_device struct + */ void phy_detach(struct phy_device *phydev) { + int i; + + if (phydev->bus->dev.driver) + module_put(phydev->bus->dev.driver->owner); + + phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; + phy_suspend(phydev); /* 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); + * real driver could be loaded + */ + for (i = 0; i < ARRAY_SIZE(genphy_driver); i++) { + if (phydev->dev.driver == &genphy_driver[i].driver) { + device_release_driver(&phydev->dev); + break; + } } } EXPORT_SYMBOL(phy_detach); +int phy_suspend(struct phy_device *phydev) +{ + struct phy_driver *phydrv = to_phy_driver(phydev->dev.driver); + struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL }; + + /* If the device has WOL enabled, we cannot suspend the PHY */ + phy_ethtool_get_wol(phydev, &wol); + if (wol.wolopts) + return -EBUSY; + + if (phydrv->suspend) + return phydrv->suspend(phydev); + return 0; +} + +int phy_resume(struct phy_device *phydev) +{ + struct phy_driver *phydrv = to_phy_driver(phydev->dev.driver); + + if (phydrv->resume) + return phydrv->resume(phydev); + return 0; +} /* Generic PHY support and helper functions */ -/* genphy_config_advert +/** + * genphy_config_advert - sanitize and advertise auto-negotiation parameters + * @phydev: target phy_device struct * - * description: Writes MII_ADVERTISE with the appropriate values, + * Description: Writes MII_ADVERTISE with the appropriate values, * after sanitizing the values to make sure we only advertise - * what is supported + * what is supported. Returns < 0 on error, 0 if the PHY's advertisement + * hasn't changed, and > 0 if it has changed. */ -int genphy_config_advert(struct phy_device *phydev) +static int genphy_config_advert(struct phy_device *phydev) { u32 advertise; - int adv; - int err; + int oldadv, adv, bmsr; + int err, changed = 0; - /* Only allow advertising what - * this PHY supports */ + /* 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 | + oldadv = 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); + adv |= ethtool_adv_to_mii_adv_t(advertise); - if (err < 0) - return err; + if (adv != oldadv) { + err = phy_write(phydev, MII_ADVERTISE, adv); - /* Configure gigabit if it's supported */ - if (phydev->supported & (SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full)) { - adv = phy_read(phydev, MII_CTRL1000); + if (err < 0) + return err; + changed = 1; + } - if (adv < 0) - return adv; + bmsr = phy_read(phydev, MII_BMSR); + if (bmsr < 0) + return bmsr; - 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); + /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all + * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a + * logical 1. + */ + if (!(bmsr & BMSR_ESTATEN)) + return changed; - if (err < 0) - return err; + /* Configure gigabit if it's supported */ + adv = phy_read(phydev, MII_CTRL1000); + if (adv < 0) + return adv; + + oldadv = adv; + adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF); + + if (phydev->supported & (SUPPORTED_1000baseT_Half | + SUPPORTED_1000baseT_Full)) { + adv |= ethtool_adv_to_mii_ctrl1000_t(advertise); + if (adv != oldadv) + changed = 1; } - return adv; + err = phy_write(phydev, MII_CTRL1000, adv); + if (err < 0) + return err; + + return changed; } -EXPORT_SYMBOL(genphy_config_advert); -/* genphy_setup_forced +/** + * genphy_setup_forced - configures/forces speed/duplex from @phydev + * @phydev: target phy_device struct * - * description: Configures MII_BMCR to force speed/duplex + * Description: Configures MII_BMCR to force speed/duplex * to the values in phydev. Assumes that the values are valid. - * Please see phy_sanitize_settings() */ + * Please see phy_sanitize_settings(). + */ int genphy_setup_forced(struct phy_device *phydev) { - int ctl = BMCR_RESET; + int ctl = 0; - phydev->pause = phydev->asym_pause = 0; + phydev->pause = 0; + phydev->asym_pause = 0; if (SPEED_1000 == phydev->speed) ctl |= BMCR_SPEED1000; @@ -328,71 +813,100 @@ int genphy_setup_forced(struct phy_device *phydev) 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; + return phy_write(phydev, MII_BMCR, ctl); } +EXPORT_SYMBOL(genphy_setup_forced); - -/* Enable and Restart Autonegotiation */ +/** + * genphy_restart_aneg - Enable and Restart Autonegotiation + * @phydev: target phy_device struct + */ int genphy_restart_aneg(struct phy_device *phydev) { - int ctl; - - ctl = phy_read(phydev, MII_BMCR); + int ctl = phy_read(phydev, MII_BMCR); if (ctl < 0) return ctl; - ctl |= (BMCR_ANENABLE | BMCR_ANRESTART); + ctl |= BMCR_ANENABLE | BMCR_ANRESTART; /* Don't isolate the PHY if we're negotiating */ - ctl &= ~(BMCR_ISOLATE); - - ctl = phy_write(phydev, MII_BMCR, ctl); + ctl &= ~BMCR_ISOLATE; - return ctl; + return phy_write(phydev, MII_BMCR, ctl); } +EXPORT_SYMBOL(genphy_restart_aneg); - -/* genphy_config_aneg +/** + * genphy_config_aneg - restart auto-negotiation or write BMCR + * @phydev: target phy_device struct * - * description: If auto-negotiation is enabled, we configure the + * 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 + * enabled, then we write the BMCR. */ int genphy_config_aneg(struct phy_device *phydev) { - int err = 0; + int result; - if (AUTONEG_ENABLE == phydev->autoneg) { - err = genphy_config_advert(phydev); + if (AUTONEG_ENABLE != phydev->autoneg) + return genphy_setup_forced(phydev); - if (err < 0) - return err; + result = genphy_config_advert(phydev); + if (result < 0) /* error */ + return result; + if (result == 0) { + /* Advertisement hasn't changed, but maybe aneg was never on to + * begin with? Or maybe phy was isolated? + */ + int ctl = phy_read(phydev, MII_BMCR); - err = genphy_restart_aneg(phydev); - } else - err = genphy_setup_forced(phydev); + if (ctl < 0) + return ctl; - return err; + if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) + result = 1; /* do restart aneg */ + } + + /* Only restart aneg if we are advertising something different + * than we were before. + */ + if (result > 0) + result = genphy_restart_aneg(phydev); + + return result; } EXPORT_SYMBOL(genphy_config_aneg); -/* genphy_update_link +/** + * genphy_aneg_done - return auto-negotiation status + * @phydev: target phy_device struct + * + * 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. + */ +int genphy_aneg_done(struct phy_device *phydev) +{ + int retval = phy_read(phydev, MII_BMSR); + + return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); +} +EXPORT_SYMBOL(genphy_aneg_done); + +static int gen10g_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +/** + * genphy_update_link - update link status in @phydev + * @phydev: target phy_device struct * - * description: Update the value in phydev->link to reflect the + * 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 + * the status register twice, keeping the second value. */ int genphy_update_link(struct phy_device *phydev) { @@ -400,13 +914,11 @@ int genphy_update_link(struct phy_device *phydev) /* 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; @@ -417,10 +929,13 @@ int genphy_update_link(struct phy_device *phydev) return 0; } +EXPORT_SYMBOL(genphy_update_link); -/* genphy_read_status +/** + * genphy_read_status - check the link status and update current link state + * @phydev: target phy_device struct * - * description: Check the link, then figure out the current state + * 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. @@ -431,65 +946,70 @@ int genphy_read_status(struct phy_device *phydev) int err; int lpa; int lpagb = 0; + int common_adv; + int common_adv_gb = 0; - /* Update the link, but return if there - * was an error */ + /* Update the link, but return if there was an error */ err = genphy_update_link(phydev); if (err) return err; + phydev->lp_advertising = 0; + 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; + phydev->lp_advertising = + mii_stat1000_to_ethtool_lpa_t(lpagb); + common_adv_gb = lpagb & adv << 2; } lpa = phy_read(phydev, MII_LPA); - if (lpa < 0) return lpa; - adv = phy_read(phydev, MII_ADVERTISE); + phydev->lp_advertising |= mii_lpa_to_ethtool_lpa_t(lpa); + adv = phy_read(phydev, MII_ADVERTISE); if (adv < 0) return adv; - lpa &= adv; + common_adv = lpa & adv; phydev->speed = SPEED_10; phydev->duplex = DUPLEX_HALF; - phydev->pause = phydev->asym_pause = 0; + phydev->pause = 0; + phydev->asym_pause = 0; - if (lpagb & (LPA_1000FULL | LPA_1000HALF)) { + if (common_adv_gb & (LPA_1000FULL | LPA_1000HALF)) { phydev->speed = SPEED_1000; - if (lpagb & LPA_1000FULL) + if (common_adv_gb & LPA_1000FULL) phydev->duplex = DUPLEX_FULL; - } else if (lpa & (LPA_100FULL | LPA_100HALF)) { + } else if (common_adv & (LPA_100FULL | LPA_100HALF)) { phydev->speed = SPEED_100; - - if (lpa & LPA_100FULL) + + if (common_adv & LPA_100FULL) phydev->duplex = DUPLEX_FULL; } else - if (lpa & LPA_10FULL) + if (common_adv & LPA_10FULL) phydev->duplex = DUPLEX_FULL; - if (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; @@ -505,27 +1025,73 @@ int genphy_read_status(struct phy_device *phydev) else phydev->speed = SPEED_10; - phydev->pause = phydev->asym_pause = 0; + phydev->pause = 0; + phydev->asym_pause = 0; } return 0; } EXPORT_SYMBOL(genphy_read_status); -static int genphy_config_init(struct phy_device *phydev) +static int gen10g_read_status(struct phy_device *phydev) { - u32 val; + int devad, reg; + u32 mmd_mask = phydev->c45_ids.devices_in_package; + + phydev->link = 1; + + /* For now just lie and say it's 10G all the time */ + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + + for (devad = 0; mmd_mask; devad++, mmd_mask = mmd_mask >> 1) { + if (!(mmd_mask & 1)) + continue; + + /* Read twice because link state is latched and a + * read moves the current state into the register + */ + phy_read_mmd(phydev, devad, MDIO_STAT1); + reg = phy_read_mmd(phydev, devad, MDIO_STAT1); + if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS)) + phydev->link = 0; + } + + return 0; +} + +/** + * genphy_soft_reset - software reset the PHY via BMCR_RESET bit + * @phydev: target phy_device struct + * + * Description: Perform a software PHY reset using the standard + * BMCR_RESET bit and poll for the reset bit to be cleared. + * + * Returns: 0 on success, < 0 on failure + */ +int genphy_soft_reset(struct phy_device *phydev) +{ + int ret; + + ret = phy_write(phydev, MII_BMCR, BMCR_RESET); + if (ret < 0) + return ret; + + return phy_poll_reset(phydev); +} +EXPORT_SYMBOL(genphy_soft_reset); + +int genphy_config_init(struct phy_device *phydev) +{ + int 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; @@ -543,7 +1109,6 @@ static int genphy_config_init(struct phy_device *phydev) if (val & BMSR_ESTATEN) { val = phy_read(phydev, MII_ESTATUS); - if (val < 0) return val; @@ -553,45 +1118,136 @@ static int genphy_config_init(struct phy_device *phydev) features |= SUPPORTED_1000baseT_Half; } - phydev->supported = features; - phydev->advertising = features; + phydev->supported &= features; + phydev->advertising &= features; + + return 0; +} + +static int gen10g_soft_reset(struct phy_device *phydev) +{ + /* Do nothing for now */ + return 0; +} +EXPORT_SYMBOL(genphy_config_init); + +static int gen10g_config_init(struct phy_device *phydev) +{ + /* Temporarily just say we support everything */ + phydev->supported = SUPPORTED_10000baseT_Full; + phydev->advertising = SUPPORTED_10000baseT_Full; + + return 0; +} + +int genphy_suspend(struct phy_device *phydev) +{ + int value; + + mutex_lock(&phydev->lock); + + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value | BMCR_PDOWN); + + mutex_unlock(&phydev->lock); + + return 0; +} +EXPORT_SYMBOL(genphy_suspend); + +static int gen10g_suspend(struct phy_device *phydev) +{ + return 0; +} + +int genphy_resume(struct phy_device *phydev) +{ + int value; + + mutex_lock(&phydev->lock); + + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN); + + mutex_unlock(&phydev->lock); return 0; } +EXPORT_SYMBOL(genphy_resume); +static int gen10g_resume(struct phy_device *phydev) +{ + return 0; +} + +static void of_set_phy_supported(struct phy_device *phydev) +{ + struct device_node *node = phydev->dev.of_node; + u32 max_speed; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return; + + if (!node) + return; + + if (!of_property_read_u32(node, "max-speed", &max_speed)) { + /* The default values for phydev->supported are provided by the PHY + * driver "features" member, we want to reset to sane defaults fist + * before supporting higher speeds. + */ + phydev->supported &= PHY_DEFAULT_FEATURES; + + switch (max_speed) { + default: + return; + + case SPEED_1000: + phydev->supported |= PHY_1000BT_FEATURES; + case SPEED_100: + phydev->supported |= PHY_100BT_FEATURES; + case SPEED_10: + phydev->supported |= PHY_10BT_FEATURES; + } + } +} -/* phy_probe +/** + * phy_probe - probe and init a PHY device + * @dev: device to probe and init * - * description: Take care of setting up the phy_device structure, + * 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; + struct phy_device *phydev = to_phy_device(dev); + struct device_driver *drv = phydev->dev.driver; + struct phy_driver *phydrv = to_phy_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)) + /* Disable the interrupt if the PHY doesn't support it + * but the interrupt is still a valid one + */ + if (!(phydrv->flags & PHY_HAS_INTERRUPT) && + phy_interrupt_is_valid(phydev)) phydev->irq = PHY_POLL; - spin_lock(&phydev->lock); + if (phydrv->flags & PHY_IS_INTERNAL) + phydev->is_internal = true; + + mutex_lock(&phydev->lock); /* Start out supporting everything. Eventually, * a controller will attach, and may modify one - * or both of these values */ + * or both of these values + */ phydev->supported = phydrv->features; - phydev->advertising = phydrv->features; + of_set_phy_supported(phydev); + phydev->advertising = phydev->supported; /* Set the state to READY by default */ phydev->state = PHY_READY; @@ -599,77 +1255,113 @@ static int phy_probe(struct device *dev) 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); + mutex_unlock(&phydev->lock); return err; } static int phy_remove(struct device *dev) { - struct phy_device *phydev; + struct phy_device *phydev = to_phy_device(dev); - phydev = to_phy_device(dev); - - spin_lock(&phydev->lock); + mutex_lock(&phydev->lock); phydev->state = PHY_DOWN; - spin_unlock(&phydev->lock); + mutex_unlock(&phydev->lock); if (phydev->drv->remove) phydev->drv->remove(phydev); - - put_driver(dev->driver); phydev->drv = NULL; return 0; } +/** + * phy_driver_register - register a phy_driver with the PHY layer + * @new_driver: new phy_driver to register + */ 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); + pr_err("%s: Error %d in registering driver\n", + new_driver->name, retval); return retval; } - pr_info("%s: Registered new driver\n", new_driver->name); + pr_debug("%s: Registered new driver\n", new_driver->name); return 0; } EXPORT_SYMBOL(phy_driver_register); +int phy_drivers_register(struct phy_driver *new_driver, int n) +{ + int i, ret = 0; + + for (i = 0; i < n; i++) { + ret = phy_driver_register(new_driver + i); + if (ret) { + while (i-- > 0) + phy_driver_unregister(new_driver + i); + break; + } + } + return ret; +} +EXPORT_SYMBOL(phy_drivers_register); + void phy_driver_unregister(struct phy_driver *drv) { driver_unregister(&drv->driver); } EXPORT_SYMBOL(phy_driver_unregister); -static struct phy_driver genphy_driver = { +void phy_drivers_unregister(struct phy_driver *drv, int n) +{ + int i; + + for (i = 0; i < n; i++) + phy_driver_unregister(drv + i); +} +EXPORT_SYMBOL(phy_drivers_unregister); + +static struct phy_driver genphy_driver[] = { +{ .phy_id = 0xffffffff, .phy_id_mask = 0xffffffff, .name = "Generic PHY", + .soft_reset = genphy_soft_reset, .config_init = genphy_config_init, - .features = 0, + .features = PHY_GBIT_FEATURES | SUPPORTED_MII | + SUPPORTED_AUI | SUPPORTED_FIBRE | + SUPPORTED_BNC, .config_aneg = genphy_config_aneg, + .aneg_done = genphy_aneg_done, .read_status = genphy_read_status, - .driver = {.owner= THIS_MODULE, }, -}; + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE, }, +}, { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic 10G PHY", + .soft_reset = gen10g_soft_reset, + .config_init = gen10g_config_init, + .features = 0, + .config_aneg = gen10g_config_aneg, + .read_status = gen10g_read_status, + .suspend = gen10g_suspend, + .resume = gen10g_resume, + .driver = {.owner = THIS_MODULE, }, +} }; static int __init phy_init(void) { @@ -679,7 +1371,8 @@ static int __init phy_init(void) if (rc) return rc; - rc = phy_driver_register(&genphy_driver); + rc = phy_drivers_register(genphy_driver, + ARRAY_SIZE(genphy_driver)); if (rc) mdio_bus_exit(); @@ -688,7 +1381,8 @@ static int __init phy_init(void) static void __exit phy_exit(void) { - phy_driver_unregister(&genphy_driver); + phy_drivers_unregister(genphy_driver, + ARRAY_SIZE(genphy_driver)); mdio_bus_exit(); } diff --git a/drivers/net/phy/qsemi.c b/drivers/net/phy/qsemi.c index 65d995b02b2..fe0d0a15d5e 100644 --- a/drivers/net/phy/qsemi.c +++ b/drivers/net/phy/qsemi.c @@ -13,13 +13,10 @@ * option) any later version. * */ -#include <linux/config.h> #include <linux/kernel.h> -#include <linux/sched.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> -#include <linux/slab.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> @@ -140,3 +137,10 @@ static void __exit qs6612_exit(void) module_init(qs6612_init); module_exit(qs6612_exit); + +static struct mdio_device_id __maybe_unused qs6612_tbl[] = { + { 0x00181440, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, qs6612_tbl); diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c new file mode 100644 index 00000000000..45483fdfbe0 --- /dev/null +++ b/drivers/net/phy/realtek.c @@ -0,0 +1,123 @@ +/* + * drivers/net/phy/realtek.c + * + * Driver for Realtek PHYs + * + * Author: Johnson Leung <r58129@freescale.com> + * + * 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 <linux/phy.h> +#include <linux/module.h> + +#define RTL821x_PHYSR 0x11 +#define RTL821x_PHYSR_DUPLEX 0x2000 +#define RTL821x_PHYSR_SPEED 0xc000 +#define RTL821x_INER 0x12 +#define RTL821x_INER_INIT 0x6400 +#define RTL821x_INSR 0x13 + +#define RTL8211E_INER_LINK_STATUS 0x400 + +MODULE_DESCRIPTION("Realtek PHY driver"); +MODULE_AUTHOR("Johnson Leung"); +MODULE_LICENSE("GPL"); + +static int rtl821x_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, RTL821x_INSR); + + return (err < 0) ? err : 0; +} + +static int rtl8211b_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, RTL821x_INER, + RTL821x_INER_INIT); + else + err = phy_write(phydev, RTL821x_INER, 0); + + return err; +} + +static int rtl8211e_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, RTL821x_INER, + RTL8211E_INER_LINK_STATUS); + else + err = phy_write(phydev, RTL821x_INER, 0); + + return err; +} + +static struct phy_driver realtek_drvs[] = { + { + .phy_id = 0x00008201, + .name = "RTL8201CP Ethernet", + .phy_id_mask = 0x0000ffff, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .driver = { .owner = THIS_MODULE,}, + }, { + .phy_id = 0x001cc912, + .name = "RTL8211B Gigabit Ethernet", + .phy_id_mask = 0x001fffff, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &rtl821x_ack_interrupt, + .config_intr = &rtl8211b_config_intr, + .driver = { .owner = THIS_MODULE,}, + }, { + .phy_id = 0x001cc915, + .name = "RTL8211E Gigabit Ethernet", + .phy_id_mask = 0x001fffff, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &rtl821x_ack_interrupt, + .config_intr = &rtl8211e_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, + }, +}; + +static int __init realtek_init(void) +{ + return phy_drivers_register(realtek_drvs, ARRAY_SIZE(realtek_drvs)); +} + +static void __exit realtek_exit(void) +{ + phy_drivers_unregister(realtek_drvs, ARRAY_SIZE(realtek_drvs)); +} + +module_init(realtek_init); +module_exit(realtek_exit); + +static struct mdio_device_id __maybe_unused realtek_tbl[] = { + { 0x001cc912, 0x001fffff }, + { 0x001cc915, 0x001fffff }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, realtek_tbl); diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c new file mode 100644 index 00000000000..180c49479c4 --- /dev/null +++ b/drivers/net/phy/smsc.c @@ -0,0 +1,271 @@ +/* + * drivers/net/phy/smsc.c + * + * Driver for SMSC PHYs + * + * Author: Herbert Valerio Riedel + * + * Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.org> + * + * 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. + * + * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/netdevice.h> +#include <linux/smscphy.h> + +static int smsc_phy_config_intr(struct phy_device *phydev) +{ + int rc = phy_write (phydev, MII_LAN83C185_IM, + ((PHY_INTERRUPT_ENABLED == phydev->interrupts) + ? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS + : 0)); + + return rc < 0 ? rc : 0; +} + +static int smsc_phy_ack_interrupt(struct phy_device *phydev) +{ + int rc = phy_read (phydev, MII_LAN83C185_ISF); + + return rc < 0 ? rc : 0; +} + +static int smsc_phy_config_init(struct phy_device *phydev) +{ + int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES); + if (rc < 0) + return rc; + + /* If the SMSC PHY is in power down mode, then set it + * in all capable mode before using it. + */ + if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) { + int timeout = 50000; + + /* set "all capable" mode and reset the phy */ + rc |= MII_LAN83C185_MODE_ALL; + phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc); + phy_write(phydev, MII_BMCR, BMCR_RESET); + + /* wait end of reset (max 500 ms) */ + do { + udelay(10); + if (timeout-- == 0) + return -1; + rc = phy_read(phydev, MII_BMCR); + } while (rc & BMCR_RESET); + } + + rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS); + if (rc < 0) + return rc; + + /* Enable energy detect mode for this SMSC Transceivers */ + rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS, + rc | MII_LAN83C185_EDPWRDOWN); + if (rc < 0) + return rc; + + return smsc_phy_ack_interrupt (phydev); +} + +static int lan911x_config_init(struct phy_device *phydev) +{ + return smsc_phy_ack_interrupt(phydev); +} + +/* + * The LAN8710/LAN8720 requires a minimum of 2 link pulses within 64ms of each + * other in order to set the ENERGYON bit and exit EDPD mode. If a link partner + * does send the pulses within this interval, the PHY will remained powered + * down. + * + * This workaround will manually toggle the PHY on/off upon calls to read_status + * in order to generate link test pulses if the link is down. If a link partner + * is present, it will respond to the pulses, which will cause the ENERGYON bit + * to be set and will cause the EDPD mode to be exited. + */ +static int lan87xx_read_status(struct phy_device *phydev) +{ + int err = genphy_read_status(phydev); + + if (!phydev->link) { + /* Disable EDPD to wake up PHY */ + int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS); + if (rc < 0) + return rc; + + rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS, + rc & ~MII_LAN83C185_EDPWRDOWN); + if (rc < 0) + return rc; + + /* Sleep 64 ms to allow ~5 link test pulses to be sent */ + msleep(64); + + /* Re-enable EDPD */ + rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS); + if (rc < 0) + return rc; + + rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS, + rc | MII_LAN83C185_EDPWRDOWN); + if (rc < 0) + return rc; + } + + return err; +} + +static struct phy_driver smsc_phy_driver[] = { +{ + .phy_id = 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */ + .phy_id_mask = 0xfffffff0, + .name = "SMSC LAN83C185", + + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG, + + /* basic functions */ + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .config_init = smsc_phy_config_init, + + /* IRQ related */ + .ack_interrupt = smsc_phy_ack_interrupt, + .config_intr = smsc_phy_config_intr, + + .suspend = genphy_suspend, + .resume = genphy_resume, + + .driver = { .owner = THIS_MODULE, } +}, { + .phy_id = 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */ + .phy_id_mask = 0xfffffff0, + .name = "SMSC LAN8187", + + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG, + + /* basic functions */ + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .config_init = smsc_phy_config_init, + + /* IRQ related */ + .ack_interrupt = smsc_phy_ack_interrupt, + .config_intr = smsc_phy_config_intr, + + .suspend = genphy_suspend, + .resume = genphy_resume, + + .driver = { .owner = THIS_MODULE, } +}, { + .phy_id = 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */ + .phy_id_mask = 0xfffffff0, + .name = "SMSC LAN8700", + + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG, + + /* basic functions */ + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .config_init = smsc_phy_config_init, + + /* IRQ related */ + .ack_interrupt = smsc_phy_ack_interrupt, + .config_intr = smsc_phy_config_intr, + + .suspend = genphy_suspend, + .resume = genphy_resume, + + .driver = { .owner = THIS_MODULE, } +}, { + .phy_id = 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */ + .phy_id_mask = 0xfffffff0, + .name = "SMSC LAN911x Internal PHY", + + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG, + + /* basic functions */ + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .config_init = lan911x_config_init, + + /* IRQ related */ + .ack_interrupt = smsc_phy_ack_interrupt, + .config_intr = smsc_phy_config_intr, + + .suspend = genphy_suspend, + .resume = genphy_resume, + + .driver = { .owner = THIS_MODULE, } +}, { + .phy_id = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */ + .phy_id_mask = 0xfffffff0, + .name = "SMSC LAN8710/LAN8720", + + .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause + | SUPPORTED_Asym_Pause), + .flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG, + + /* basic functions */ + .config_aneg = genphy_config_aneg, + .read_status = lan87xx_read_status, + .config_init = smsc_phy_config_init, + + /* IRQ related */ + .ack_interrupt = smsc_phy_ack_interrupt, + .config_intr = smsc_phy_config_intr, + + .suspend = genphy_suspend, + .resume = genphy_resume, + + .driver = { .owner = THIS_MODULE, } +} }; + +static int __init smsc_init(void) +{ + return phy_drivers_register(smsc_phy_driver, + ARRAY_SIZE(smsc_phy_driver)); +} + +static void __exit smsc_exit(void) +{ + phy_drivers_unregister(smsc_phy_driver, ARRAY_SIZE(smsc_phy_driver)); +} + +MODULE_DESCRIPTION("SMSC PHY driver"); +MODULE_AUTHOR("Herbert Valerio Riedel"); +MODULE_LICENSE("GPL"); + +module_init(smsc_init); +module_exit(smsc_exit); + +static struct mdio_device_id __maybe_unused smsc_tbl[] = { + { 0x0007c0a0, 0xfffffff0 }, + { 0x0007c0b0, 0xfffffff0 }, + { 0x0007c0c0, 0xfffffff0 }, + { 0x0007c0d0, 0xfffffff0 }, + { 0x0007c0f0, 0xfffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, smsc_tbl); diff --git a/drivers/net/phy/spi_ks8995.c b/drivers/net/phy/spi_ks8995.c new file mode 100644 index 00000000000..22b047f1fcd --- /dev/null +++ b/drivers/net/phy/spi_ks8995.c @@ -0,0 +1,387 @@ +/* + * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches + * + * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org> + * + * This file was based on: drivers/spi/at25.c + * Copyright (C) 2006 David Brownell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/device.h> + +#include <linux/spi/spi.h> + +#define DRV_VERSION "0.1.1" +#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver" + +/* ------------------------------------------------------------------------ */ + +#define KS8995_REG_ID0 0x00 /* Chip ID0 */ +#define KS8995_REG_ID1 0x01 /* Chip ID1 */ + +#define KS8995_REG_GC0 0x02 /* Global Control 0 */ +#define KS8995_REG_GC1 0x03 /* Global Control 1 */ +#define KS8995_REG_GC2 0x04 /* Global Control 2 */ +#define KS8995_REG_GC3 0x05 /* Global Control 3 */ +#define KS8995_REG_GC4 0x06 /* Global Control 4 */ +#define KS8995_REG_GC5 0x07 /* Global Control 5 */ +#define KS8995_REG_GC6 0x08 /* Global Control 6 */ +#define KS8995_REG_GC7 0x09 /* Global Control 7 */ +#define KS8995_REG_GC8 0x0a /* Global Control 8 */ +#define KS8995_REG_GC9 0x0b /* Global Control 9 */ + +#define KS8995_REG_PC(p, r) ((0x10 * p) + r) /* Port Control */ +#define KS8995_REG_PS(p, r) ((0x10 * p) + r + 0xe) /* Port Status */ + +#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */ +#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */ +#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */ +#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */ +#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */ +#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */ +#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */ +#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */ + +#define KS8995_REG_MAC0 0x68 /* MAC address 0 */ +#define KS8995_REG_MAC1 0x69 /* MAC address 1 */ +#define KS8995_REG_MAC2 0x6a /* MAC address 2 */ +#define KS8995_REG_MAC3 0x6b /* MAC address 3 */ +#define KS8995_REG_MAC4 0x6c /* MAC address 4 */ +#define KS8995_REG_MAC5 0x6d /* MAC address 5 */ + +#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */ +#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */ +#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */ +#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */ +#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */ +#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */ +#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */ +#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */ +#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */ +#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */ + +#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */ + +#define KS8995_REGS_SIZE 0x80 +#define KSZ8864_REGS_SIZE 0x100 + +#define ID1_CHIPID_M 0xf +#define ID1_CHIPID_S 4 +#define ID1_REVISION_M 0x7 +#define ID1_REVISION_S 1 +#define ID1_START_SW 1 /* start the switch */ + +#define FAMILY_KS8995 0x95 +#define CHIPID_M 0 + +#define KS8995_CMD_WRITE 0x02U +#define KS8995_CMD_READ 0x03U + +#define KS8995_RESET_DELAY 10 /* usec */ + +struct ks8995_pdata { + /* not yet implemented */ +}; + +struct ks8995_switch { + struct spi_device *spi; + struct mutex lock; + struct ks8995_pdata *pdata; + struct bin_attribute regs_attr; +}; + +static inline u8 get_chip_id(u8 val) +{ + return (val >> ID1_CHIPID_S) & ID1_CHIPID_M; +} + +static inline u8 get_chip_rev(u8 val) +{ + return (val >> ID1_REVISION_S) & ID1_REVISION_M; +} + +/* ------------------------------------------------------------------------ */ +static int ks8995_read(struct ks8995_switch *ks, char *buf, + unsigned offset, size_t count) +{ + u8 cmd[2]; + struct spi_transfer t[2]; + struct spi_message m; + int err; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = count; + spi_message_add_tail(&t[1], &m); + + cmd[0] = KS8995_CMD_READ; + cmd[1] = offset; + + mutex_lock(&ks->lock); + err = spi_sync(ks->spi, &m); + mutex_unlock(&ks->lock); + + return err ? err : count; +} + + +static int ks8995_write(struct ks8995_switch *ks, char *buf, + unsigned offset, size_t count) +{ + u8 cmd[2]; + struct spi_transfer t[2]; + struct spi_message m; + int err; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = count; + spi_message_add_tail(&t[1], &m); + + cmd[0] = KS8995_CMD_WRITE; + cmd[1] = offset; + + mutex_lock(&ks->lock); + err = spi_sync(ks->spi, &m); + mutex_unlock(&ks->lock); + + return err ? err : count; +} + +static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf) +{ + return ks8995_read(ks, buf, addr, 1) != 1; +} + +static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val) +{ + char buf = val; + + return ks8995_write(ks, &buf, addr, 1) != 1; +} + +/* ------------------------------------------------------------------------ */ + +static int ks8995_stop(struct ks8995_switch *ks) +{ + return ks8995_write_reg(ks, KS8995_REG_ID1, 0); +} + +static int ks8995_start(struct ks8995_switch *ks) +{ + return ks8995_write_reg(ks, KS8995_REG_ID1, 1); +} + +static int ks8995_reset(struct ks8995_switch *ks) +{ + int err; + + err = ks8995_stop(ks); + if (err) + return err; + + udelay(KS8995_RESET_DELAY); + + return ks8995_start(ks); +} + +/* ------------------------------------------------------------------------ */ + +static ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) +{ + struct device *dev; + struct ks8995_switch *ks8995; + + dev = container_of(kobj, struct device, kobj); + ks8995 = dev_get_drvdata(dev); + + if (unlikely(off > ks8995->regs_attr.size)) + return 0; + + if ((off + count) > ks8995->regs_attr.size) + count = ks8995->regs_attr.size - off; + + if (unlikely(!count)) + return count; + + return ks8995_read(ks8995, buf, off, count); +} + + +static ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) +{ + struct device *dev; + struct ks8995_switch *ks8995; + + dev = container_of(kobj, struct device, kobj); + ks8995 = dev_get_drvdata(dev); + + if (unlikely(off >= ks8995->regs_attr.size)) + return -EFBIG; + + if ((off + count) > ks8995->regs_attr.size) + count = ks8995->regs_attr.size - off; + + if (unlikely(!count)) + return count; + + return ks8995_write(ks8995, buf, off, count); +} + + +static const struct bin_attribute ks8995_registers_attr = { + .attr = { + .name = "registers", + .mode = S_IRUSR | S_IWUSR, + }, + .size = KS8995_REGS_SIZE, + .read = ks8995_registers_read, + .write = ks8995_registers_write, +}; + +/* ------------------------------------------------------------------------ */ + +static int ks8995_probe(struct spi_device *spi) +{ + struct ks8995_switch *ks; + struct ks8995_pdata *pdata; + u8 ids[2]; + int err; + + /* Chip description */ + pdata = spi->dev.platform_data; + + ks = kzalloc(sizeof(*ks), GFP_KERNEL); + if (!ks) + return -ENOMEM; + + mutex_init(&ks->lock); + ks->pdata = pdata; + ks->spi = spi_dev_get(spi); + spi_set_drvdata(spi, ks); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); + goto err_drvdata; + } + + err = ks8995_read(ks, ids, KS8995_REG_ID0, sizeof(ids)); + if (err < 0) { + dev_err(&spi->dev, "unable to read id registers, err=%d\n", + err); + goto err_drvdata; + } + + switch (ids[0]) { + case FAMILY_KS8995: + break; + default: + dev_err(&spi->dev, "unknown family id:%02x\n", ids[0]); + err = -ENODEV; + goto err_drvdata; + } + + memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr)); + if (get_chip_id(ids[1]) != CHIPID_M) { + u8 val; + + /* Check if this is a KSZ8864RMN */ + err = ks8995_read(ks, &val, KSZ8864_REG_ID1, sizeof(val)); + if (err < 0) { + dev_err(&spi->dev, + "unable to read chip id register, err=%d\n", + err); + goto err_drvdata; + } + if ((val & 0x80) == 0) { + dev_err(&spi->dev, "unknown chip:%02x,0\n", ids[1]); + goto err_drvdata; + } + ks->regs_attr.size = KSZ8864_REGS_SIZE; + } + + err = ks8995_reset(ks); + if (err) + goto err_drvdata; + + err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr); + if (err) { + dev_err(&spi->dev, "unable to create sysfs file, err=%d\n", + err); + goto err_drvdata; + } + + if (get_chip_id(ids[1]) == CHIPID_M) { + dev_info(&spi->dev, + "KS8995 device found, Chip ID:%x, Revision:%x\n", + get_chip_id(ids[1]), get_chip_rev(ids[1])); + } else { + dev_info(&spi->dev, "KSZ8864 device found, Revision:%x\n", + get_chip_rev(ids[1])); + } + + return 0; + +err_drvdata: + kfree(ks); + return err; +} + +static int ks8995_remove(struct spi_device *spi) +{ + struct ks8995_data *ks8995; + + ks8995 = spi_get_drvdata(spi); + sysfs_remove_bin_file(&spi->dev.kobj, &ks8995_registers_attr); + + kfree(ks8995); + + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static struct spi_driver ks8995_driver = { + .driver = { + .name = "spi-ks8995", + .owner = THIS_MODULE, + }, + .probe = ks8995_probe, + .remove = ks8995_remove, +}; + +module_spi_driver(ks8995_driver); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/ste10Xp.c b/drivers/net/phy/ste10Xp.c new file mode 100644 index 00000000000..5e1eb138916 --- /dev/null +++ b/drivers/net/phy/ste10Xp.c @@ -0,0 +1,140 @@ +/* + * drivers/net/phy/ste10Xp.c + * + * Driver for STMicroelectronics STe10Xp PHYs + * + * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> + * + * Copyright (c) 2008 STMicroelectronics Limited + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/phy.h> + +#define MII_XCIIS 0x11 /* Configuration Info IRQ & Status Reg */ +#define MII_XIE 0x12 /* Interrupt Enable Register */ +#define MII_XIE_DEFAULT_MASK 0x0070 /* ANE complete, Remote Fault, Link Down */ + +#define STE101P_PHY_ID 0x00061c50 +#define STE100P_PHY_ID 0x1c040011 + +static int ste10Xp_config_init(struct phy_device *phydev) +{ + int value, err; + + /* Software Reset PHY */ + value = phy_read(phydev, MII_BMCR); + if (value < 0) + return value; + + value |= BMCR_RESET; + err = phy_write(phydev, MII_BMCR, value); + if (err < 0) + return err; + + do { + value = phy_read(phydev, MII_BMCR); + } while (value & BMCR_RESET); + + return 0; +} + +static int ste10Xp_config_intr(struct phy_device *phydev) +{ + int err, value; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + /* Enable all STe101P interrupts (PR12) */ + err = phy_write(phydev, MII_XIE, MII_XIE_DEFAULT_MASK); + /* clear any pending interrupts */ + if (err == 0) { + value = phy_read(phydev, MII_XCIIS); + if (value < 0) + err = value; + } + } else + err = phy_write(phydev, MII_XIE, 0); + + return err; +} + +static int ste10Xp_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, MII_XCIIS); + if (err < 0) + return err; + + return 0; +} + +static struct phy_driver ste10xp_pdriver[] = { +{ + .phy_id = STE101P_PHY_ID, + .phy_id_mask = 0xfffffff0, + .name = "STe101p", + .features = PHY_BASIC_FEATURES | SUPPORTED_Pause, + .flags = PHY_HAS_INTERRUPT, + .config_init = ste10Xp_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = ste10Xp_ack_interrupt, + .config_intr = ste10Xp_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = {.owner = THIS_MODULE,} +}, { + .phy_id = STE100P_PHY_ID, + .phy_id_mask = 0xffffffff, + .name = "STe100p", + .features = PHY_BASIC_FEATURES | SUPPORTED_Pause, + .flags = PHY_HAS_INTERRUPT, + .config_init = ste10Xp_config_init, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = ste10Xp_ack_interrupt, + .config_intr = ste10Xp_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = {.owner = THIS_MODULE,} +} }; + +static int __init ste10Xp_init(void) +{ + return phy_drivers_register(ste10xp_pdriver, + ARRAY_SIZE(ste10xp_pdriver)); +} + +static void __exit ste10Xp_exit(void) +{ + phy_drivers_unregister(ste10xp_pdriver, + ARRAY_SIZE(ste10xp_pdriver)); +} + +module_init(ste10Xp_init); +module_exit(ste10Xp_exit); + +static struct mdio_device_id __maybe_unused ste10Xp_tbl[] = { + { STE101P_PHY_ID, 0xfffffff0 }, + { STE100P_PHY_ID, 0xffffffff }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, ste10Xp_tbl); + +MODULE_DESCRIPTION("STMicroelectronics STe10Xp PHY driver"); +MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/vitesse.c b/drivers/net/phy/vitesse.c new file mode 100644 index 00000000000..5dc0935da99 --- /dev/null +++ b/drivers/net/phy/vitesse.c @@ -0,0 +1,339 @@ +/* + * Driver for Vitesse PHYs + * + * Author: Kriston Carson + * + * Copyright (c) 2005, 2009, 2011 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> + +/* Vitesse Extended Page Magic Register(s) */ +#define MII_VSC82X4_EXT_PAGE_16E 0x10 +#define MII_VSC82X4_EXT_PAGE_17E 0x11 +#define MII_VSC82X4_EXT_PAGE_18E 0x12 + +/* Vitesse Extended Control Register 1 */ +#define MII_VSC8244_EXT_CON1 0x17 +#define MII_VSC8244_EXTCON1_INIT 0x0000 +#define MII_VSC8244_EXTCON1_TX_SKEW_MASK 0x0c00 +#define MII_VSC8244_EXTCON1_RX_SKEW_MASK 0x0300 +#define MII_VSC8244_EXTCON1_TX_SKEW 0x0800 +#define MII_VSC8244_EXTCON1_RX_SKEW 0x0200 + +/* Vitesse Interrupt Mask Register */ +#define MII_VSC8244_IMASK 0x19 +#define MII_VSC8244_IMASK_IEN 0x8000 +#define MII_VSC8244_IMASK_SPEED 0x4000 +#define MII_VSC8244_IMASK_LINK 0x2000 +#define MII_VSC8244_IMASK_DUPLEX 0x1000 +#define MII_VSC8244_IMASK_MASK 0xf000 + +#define MII_VSC8221_IMASK_MASK 0xa000 + +/* Vitesse Interrupt Status Register */ +#define MII_VSC8244_ISTAT 0x1a +#define MII_VSC8244_ISTAT_STATUS 0x8000 +#define MII_VSC8244_ISTAT_SPEED 0x4000 +#define MII_VSC8244_ISTAT_LINK 0x2000 +#define MII_VSC8244_ISTAT_DUPLEX 0x1000 + +/* Vitesse Auxiliary Control/Status Register */ +#define MII_VSC8244_AUX_CONSTAT 0x1c +#define MII_VSC8244_AUXCONSTAT_INIT 0x0000 +#define MII_VSC8244_AUXCONSTAT_DUPLEX 0x0020 +#define MII_VSC8244_AUXCONSTAT_SPEED 0x0018 +#define MII_VSC8244_AUXCONSTAT_GBIT 0x0010 +#define MII_VSC8244_AUXCONSTAT_100 0x0008 + +#define MII_VSC8221_AUXCONSTAT_INIT 0x0004 /* need to set this bit? */ +#define MII_VSC8221_AUXCONSTAT_RESERVED 0x0004 + +/* Vitesse Extended Page Access Register */ +#define MII_VSC82X4_EXT_PAGE_ACCESS 0x1f + +#define PHY_ID_VSC8234 0x000fc620 +#define PHY_ID_VSC8244 0x000fc6c0 +#define PHY_ID_VSC8514 0x00070670 +#define PHY_ID_VSC8574 0x000704a0 +#define PHY_ID_VSC8662 0x00070660 +#define PHY_ID_VSC8221 0x000fc550 +#define PHY_ID_VSC8211 0x000fc4b0 + +MODULE_DESCRIPTION("Vitesse PHY driver"); +MODULE_AUTHOR("Kriston Carson"); +MODULE_LICENSE("GPL"); + +static int vsc824x_add_skew(struct phy_device *phydev) +{ + int err; + int extcon; + + extcon = phy_read(phydev, MII_VSC8244_EXT_CON1); + + if (extcon < 0) + return extcon; + + extcon &= ~(MII_VSC8244_EXTCON1_TX_SKEW_MASK | + MII_VSC8244_EXTCON1_RX_SKEW_MASK); + + extcon |= (MII_VSC8244_EXTCON1_TX_SKEW | + MII_VSC8244_EXTCON1_RX_SKEW); + + err = phy_write(phydev, MII_VSC8244_EXT_CON1, extcon); + + return err; +} + +static int vsc824x_config_init(struct phy_device *phydev) +{ + int err; + + err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT, + MII_VSC8244_AUXCONSTAT_INIT); + if (err < 0) + return err; + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) + err = vsc824x_add_skew(phydev); + + return err; +} + +static int vsc824x_ack_interrupt(struct phy_device *phydev) +{ + int err = 0; + + /* Don't bother to ACK the interrupts if interrupts + * are disabled. The 824x cannot clear the interrupts + * if they are disabled. + */ + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_read(phydev, MII_VSC8244_ISTAT); + + return (err < 0) ? err : 0; +} + +static int vsc82xx_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, MII_VSC8244_IMASK, + (phydev->drv->phy_id == PHY_ID_VSC8234 || + phydev->drv->phy_id == PHY_ID_VSC8244 || + phydev->drv->phy_id == PHY_ID_VSC8514 || + phydev->drv->phy_id == PHY_ID_VSC8574) ? + MII_VSC8244_IMASK_MASK : + MII_VSC8221_IMASK_MASK); + else { + /* The Vitesse PHY cannot clear the interrupt + * once it has disabled them, so we clear them first + */ + err = phy_read(phydev, MII_VSC8244_ISTAT); + + if (err < 0) + return err; + + err = phy_write(phydev, MII_VSC8244_IMASK, 0); + } + + return err; +} + +static int vsc8221_config_init(struct phy_device *phydev) +{ + int err; + + err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT, + MII_VSC8221_AUXCONSTAT_INIT); + return err; + + /* Perhaps we should set EXT_CON1 based on the interface? + * Options are 802.3Z SerDes or SGMII + */ +} + +/* vsc82x4_config_autocross_enable - Enable auto MDI/MDI-X for forced links + * @phydev: target phy_device struct + * + * Enable auto MDI/MDI-X when in 10/100 forced link speeds by writing + * special values in the VSC8234/VSC8244 extended reserved registers + */ +static int vsc82x4_config_autocross_enable(struct phy_device *phydev) +{ + int ret; + + if (phydev->autoneg == AUTONEG_ENABLE || phydev->speed > SPEED_100) + return 0; + + /* map extended registers set 0x10 - 0x1e */ + ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x52b5); + if (ret >= 0) + ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_18E, 0x0012); + if (ret >= 0) + ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_17E, 0x2803); + if (ret >= 0) + ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_16E, 0x87fa); + /* map standard registers set 0x10 - 0x1e */ + if (ret >= 0) + ret = phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x0000); + else + phy_write(phydev, MII_VSC82X4_EXT_PAGE_ACCESS, 0x0000); + + return ret; +} + +/* vsc82x4_config_aneg - restart auto-negotiation or write BMCR + * @phydev: target phy_device struct + * + * 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 and also start the auto + * MDI/MDI-X feature + */ +static int vsc82x4_config_aneg(struct phy_device *phydev) +{ + int ret; + + /* Enable auto MDI/MDI-X when in 10/100 forced link speeds by + * writing special values in the VSC8234 extended reserved registers + */ + if (phydev->autoneg != AUTONEG_ENABLE && phydev->speed <= SPEED_100) { + ret = genphy_setup_forced(phydev); + + if (ret < 0) /* error */ + return ret; + + return vsc82x4_config_autocross_enable(phydev); + } + + return genphy_config_aneg(phydev); +} + +/* Vitesse 82xx */ +static struct phy_driver vsc82xx_driver[] = { +{ + .phy_id = PHY_ID_VSC8234, + .name = "Vitesse VSC8234", + .phy_id_mask = 0x000ffff0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc824x_config_init, + .config_aneg = &vsc82x4_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_VSC8244, + .name = "Vitesse VSC8244", + .phy_id_mask = 0x000fffc0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc824x_config_init, + .config_aneg = &vsc82x4_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_VSC8514, + .name = "Vitesse VSC8514", + .phy_id_mask = 0x000ffff0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc824x_config_init, + .config_aneg = &vsc82x4_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_VSC8574, + .name = "Vitesse VSC8574", + .phy_id_mask = 0x000ffff0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc824x_config_init, + .config_aneg = &vsc82x4_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_VSC8662, + .name = "Vitesse VSC8662", + .phy_id_mask = 0x000ffff0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc824x_config_init, + .config_aneg = &vsc82x4_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + /* Vitesse 8221 */ + .phy_id = PHY_ID_VSC8221, + .phy_id_mask = 0x000ffff0, + .name = "Vitesse VSC8221", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc8221_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +}, { + /* Vitesse 8211 */ + .phy_id = PHY_ID_VSC8211, + .phy_id_mask = 0x000ffff0, + .name = "Vitesse VSC8211", + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &vsc8221_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, +} }; + +static int __init vsc82xx_init(void) +{ + return phy_drivers_register(vsc82xx_driver, + ARRAY_SIZE(vsc82xx_driver)); +} + +static void __exit vsc82xx_exit(void) +{ + phy_drivers_unregister(vsc82xx_driver, ARRAY_SIZE(vsc82xx_driver)); +} + +module_init(vsc82xx_init); +module_exit(vsc82xx_exit); + +static struct mdio_device_id __maybe_unused vitesse_tbl[] = { + { PHY_ID_VSC8234, 0x000ffff0 }, + { PHY_ID_VSC8244, 0x000fffc0 }, + { PHY_ID_VSC8514, 0x000ffff0 }, + { PHY_ID_VSC8574, 0x000ffff0 }, + { PHY_ID_VSC8662, 0x000ffff0 }, + { PHY_ID_VSC8221, 0x000ffff0 }, + { PHY_ID_VSC8211, 0x000ffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, vitesse_tbl); |
