diff options
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 6 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/phy/amd-xgbe-phy.c | 1357 | ||||
-rw-r--r-- | drivers/net/phy/at803x.c | 39 | ||||
-rw-r--r-- | drivers/net/phy/fixed.c | 81 | ||||
-rw-r--r-- | drivers/net/phy/mdio_bus.c | 73 | ||||
-rw-r--r-- | drivers/net/phy/micrel.c | 106 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 50 | ||||
-rw-r--r-- | drivers/net/phy/realtek.c | 88 | ||||
-rw-r--r-- | drivers/net/phy/smsc.c | 3 | ||||
-rw-r--r-- | drivers/net/phy/vitesse.c | 3 |
11 files changed, 1698 insertions, 109 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 6a17f92153b3..65de0cab8d07 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -24,6 +24,12 @@ config AMD_PHY ---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" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 07d24024863e..7dc3d5b304cf 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -33,3 +33,4 @@ 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 000000000000..b57c22442867 --- /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/at803x.c b/drivers/net/phy/at803x.c index 643464d5a727..6c622aedbae1 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -144,41 +144,11 @@ static int at803x_resume(struct phy_device *phydev) static int at803x_config_init(struct phy_device *phydev) { - int val; int ret; - u32 features; - - features = SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_AUI | - SUPPORTED_FIBRE | SUPPORTED_BNC; - - val = phy_read(phydev, MII_BMSR); - if (val < 0) - return val; - - if (val & BMSR_ANEGCAPABLE) - features |= SUPPORTED_Autoneg; - if (val & BMSR_100FULL) - features |= SUPPORTED_100baseT_Full; - if (val & BMSR_100HALF) - features |= SUPPORTED_100baseT_Half; - if (val & BMSR_10FULL) - features |= SUPPORTED_10baseT_Full; - if (val & BMSR_10HALF) - features |= SUPPORTED_10baseT_Half; - - if (val & BMSR_ESTATEN) { - val = phy_read(phydev, MII_ESTATUS); - if (val < 0) - return val; - - if (val & ESTATUS_1000_TFULL) - features |= SUPPORTED_1000baseT_Full; - if (val & ESTATUS_1000_THALF) - features |= SUPPORTED_1000baseT_Half; - } - phydev->supported = features; - phydev->advertising = features; + 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, @@ -283,8 +253,7 @@ static int __init atheros_init(void) static void __exit atheros_exit(void) { - return phy_drivers_unregister(at803x_driver, - ARRAY_SIZE(at803x_driver)); + phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver)); } module_init(atheros_init); diff --git a/drivers/net/phy/fixed.c b/drivers/net/phy/fixed.c index ba55adfc7aae..d60d875cb445 100644 --- a/drivers/net/phy/fixed.c +++ b/drivers/net/phy/fixed.c @@ -21,6 +21,7 @@ #include <linux/phy_fixed.h> #include <linux/err.h> #include <linux/slab.h> +#include <linux/of.h> #define MII_REGS_NUM 29 @@ -31,7 +32,7 @@ struct fixed_mdio_bus { }; struct fixed_phy { - int id; + int addr; u16 regs[MII_REGS_NUM]; struct phy_device *phydev; struct fixed_phy_status status; @@ -104,8 +105,8 @@ static int fixed_phy_update_regs(struct fixed_phy *fp) if (fp->status.asym_pause) lpa |= LPA_PAUSE_ASYM; - fp->regs[MII_PHYSID1] = fp->id >> 16; - fp->regs[MII_PHYSID2] = fp->id; + fp->regs[MII_PHYSID1] = 0; + fp->regs[MII_PHYSID2] = 0; fp->regs[MII_BMSR] = bmsr; fp->regs[MII_BMCR] = bmcr; @@ -115,7 +116,7 @@ static int fixed_phy_update_regs(struct fixed_phy *fp) return 0; } -static int fixed_mdio_read(struct mii_bus *bus, int phy_id, int reg_num) +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; @@ -124,7 +125,7 @@ static int fixed_mdio_read(struct mii_bus *bus, int phy_id, int reg_num) return -1; list_for_each_entry(fp, &fmb->phys, node) { - if (fp->id == phy_id) { + if (fp->addr == phy_addr) { /* Issue callback if user registered it. */ if (fp->link_update) { fp->link_update(fp->phydev->attached_dev, @@ -138,7 +139,7 @@ static int fixed_mdio_read(struct mii_bus *bus, int phy_id, int reg_num) return 0xFFFF; } -static int fixed_mdio_write(struct mii_bus *bus, int phy_id, int reg_num, +static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, u16 val) { return 0; @@ -160,7 +161,7 @@ int fixed_phy_set_link_update(struct phy_device *phydev, return -EINVAL; list_for_each_entry(fp, &fmb->phys, node) { - if (fp->id == phydev->phy_id) { + if (fp->addr == phydev->addr) { fp->link_update = link_update; fp->phydev = phydev; return 0; @@ -171,7 +172,7 @@ int fixed_phy_set_link_update(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); -int fixed_phy_add(unsigned int irq, int phy_id, +int fixed_phy_add(unsigned int irq, int phy_addr, struct fixed_phy_status *status) { int ret; @@ -184,9 +185,9 @@ int fixed_phy_add(unsigned int irq, int phy_id, memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM); - fmb->irqs[phy_id] = irq; + fmb->irqs[phy_addr] = irq; - fp->id = phy_id; + fp->addr = phy_addr; fp->status = *status; ret = fixed_phy_update_regs(fp); @@ -203,6 +204,66 @@ err_regs: } 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; diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 76f54b32a120..2e58aa54484c 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -69,6 +69,73 @@ struct mii_bus *mdiobus_alloc_size(size_t size) } EXPORT_SYMBOL(mdiobus_alloc_size); +static void _devm_mdiobus_free(struct device *dev, void *res) +{ + mdiobus_free(*(struct mii_bus **)res); +} + +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 @@ -233,6 +300,12 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr) 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); diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index d849684231c1..bc7c7d2f75f2 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -283,6 +283,110 @@ static int ksz9021_config_init(struct phy_device *phydev) 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) @@ -469,7 +573,7 @@ static struct phy_driver ksphy_driver[] = { .features = (PHY_GBIT_FEATURES | SUPPORTED_Pause | SUPPORTED_Asym_Pause), .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, - .config_init = kszphy_config_init, + .config_init = ksz9031_config_init, .config_aneg = genphy_config_aneg, .read_status = genphy_read_status, .ack_interrupt = kszphy_ack_interrupt, diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 4987a1c6dc52..35d753d22f78 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -33,6 +33,7 @@ #include <linux/mdio.h> #include <linux/io.h> #include <linux/uaccess.h> +#include <linux/of.h> #include <asm/irq.h> @@ -1067,14 +1068,11 @@ int genphy_soft_reset(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_soft_reset); -static int genphy_config_init(struct phy_device *phydev) +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); @@ -1107,8 +1105,8 @@ 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; } @@ -1118,6 +1116,7 @@ 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) { @@ -1168,6 +1167,38 @@ 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 - probe and init a PHY device * @dev: device to probe and init @@ -1202,7 +1233,8 @@ static int phy_probe(struct device *dev) * 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; @@ -1295,7 +1327,9 @@ static struct phy_driver genphy_driver[] = { .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, diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c index fa1d69a38ccf..45483fdfbe06 100644 --- a/drivers/net/phy/realtek.c +++ b/drivers/net/phy/realtek.c @@ -64,65 +64,51 @@ static int rtl8211e_config_intr(struct phy_device *phydev) return err; } -/* RTL8201CP */ -static struct phy_driver rtl8201cp_driver = { - .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,}, -}; - -/* RTL8211B */ -static struct phy_driver rtl8211b_driver = { - .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,}, -}; - -/* RTL8211E */ -static struct phy_driver rtl8211e_driver = { - .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 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) { - int ret; - - ret = phy_driver_register(&rtl8201cp_driver); - if (ret < 0) - return -ENODEV; - ret = phy_driver_register(&rtl8211b_driver); - if (ret < 0) - return -ENODEV; - return phy_driver_register(&rtl8211e_driver); + return phy_drivers_register(realtek_drvs, ARRAY_SIZE(realtek_drvs)); } static void __exit realtek_exit(void) { - phy_driver_unregister(&rtl8211b_driver); - phy_driver_unregister(&rtl8211e_driver); + phy_drivers_unregister(realtek_drvs, ARRAY_SIZE(realtek_drvs)); } module_init(realtek_init); diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c index 11f34813e23f..180c49479c42 100644 --- a/drivers/net/phy/smsc.c +++ b/drivers/net/phy/smsc.c @@ -249,8 +249,7 @@ static int __init smsc_init(void) static void __exit smsc_exit(void) { - return phy_drivers_unregister(smsc_phy_driver, - ARRAY_SIZE(smsc_phy_driver)); + phy_drivers_unregister(smsc_phy_driver, ARRAY_SIZE(smsc_phy_driver)); } MODULE_DESCRIPTION("SMSC PHY driver"); diff --git a/drivers/net/phy/vitesse.c b/drivers/net/phy/vitesse.c index 14372c65a7e8..5dc0935da99c 100644 --- a/drivers/net/phy/vitesse.c +++ b/drivers/net/phy/vitesse.c @@ -319,8 +319,7 @@ static int __init vsc82xx_init(void) static void __exit vsc82xx_exit(void) { - return phy_drivers_unregister(vsc82xx_driver, - ARRAY_SIZE(vsc82xx_driver)); + phy_drivers_unregister(vsc82xx_driver, ARRAY_SIZE(vsc82xx_driver)); } module_init(vsc82xx_init); |