diff options
Diffstat (limited to 'drivers/net/phy')
35 files changed, 8699 insertions, 1372 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index cb3d13e4e07..65de0cab8d0 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -4,7 +4,6 @@  menuconfig PHYLIB  	tristate "PHY Device support and infrastructure" -	depends on !S390  	depends on NETDEVICES  	help  	  Ethernet controllers are usually attached to PHY @@ -15,6 +14,22 @@ if PHYLIB  comment "MII PHY device drivers" +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"  	---help--- @@ -58,13 +73,25 @@ config BROADCOM_PHY  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 PHY. +	  Currently supports the IP175C and IP1001 PHYs.  config REALTEK_PHY  	tristate "Drivers for Realtek PHYs" @@ -77,7 +104,6 @@ config NATIONAL_PHY  	  Currently supports the DP83865 PHY.  config STE10XP -	depends on PHYLIB  	tristate "Driver for STMicroelectronics STe10Xp PHYs"  	---help---  	  This is the driver for the STe100p and STe101p PHYs. @@ -112,7 +138,7 @@ config MDIO_BITBANG  config MDIO_GPIO  	tristate "Support for GPIO lib-based bitbanged MDIO buses" -	depends on MDIO_BITBANG && GENERIC_GPIO +	depends on MDIO_BITBANG && GPIOLIB  	---help---  	  Supports GPIO lib-based MDIO busses. @@ -121,7 +147,7 @@ config MDIO_GPIO  config MDIO_OCTEON  	tristate "Support for MDIO buses on Octeon SOCs" -	depends on  CPU_CAVIUM_OCTEON +	depends on CAVIUM_OCTEON_SOC  	default y  	help @@ -130,4 +156,57 @@ config MDIO_OCTEON  	  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 13bebab65d0..7dc3d5b304c 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -12,6 +12,8 @@ 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 @@ -19,6 +21,16 @@ 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 index e16f98cb4f0..ac55b080785 100644 --- a/drivers/net/phy/bcm63xx.c +++ b/drivers/net/phy/bcm63xx.c @@ -39,10 +39,7 @@ static int bcm63xx_config_init(struct phy_device *phydev)  		MII_BCM63XX_IR_SPEED |  		MII_BCM63XX_IR_LINK) |  		MII_BCM63XX_IR_EN; -	err = phy_write(phydev, MII_BCM63XX_IR, reg); -	if (err < 0) -		return err; -	return 0; +	return phy_write(phydev, MII_BCM63XX_IR, reg);  }  static int bcm63xx_ack_interrupt(struct phy_device *phydev) @@ -74,58 +71,45 @@ static int bcm63xx_config_intr(struct phy_device *phydev)  	return err;  } -static struct phy_driver bcm63xx_1_driver = { +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, +	.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 */ -static struct phy_driver bcm63xx_2_driver = { +}, { +	/* 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, +	.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)  { -	int ret; - -	ret = phy_driver_register(&bcm63xx_1_driver); -	if (ret) -		goto out_63xx_1; -	ret = phy_driver_register(&bcm63xx_2_driver); -	if (ret) -		goto out_63xx_2; -	return ret; - -out_63xx_2: -	phy_driver_unregister(&bcm63xx_1_driver); -out_63xx_1: -	return ret; +	return phy_drivers_register(bcm63xx_driver, +		ARRAY_SIZE(bcm63xx_driver));  }  static void __exit bcm63xx_phy_exit(void)  { -	phy_driver_unregister(&bcm63xx_1_driver); -	phy_driver_unregister(&bcm63xx_2_driver); +	phy_drivers_unregister(bcm63xx_driver, +		ARRAY_SIZE(bcm63xx_driver));  }  module_init(bcm63xx_phy_init); 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 index d84c4224dd1..34088d60da7 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -25,58 +25,6 @@  #define BRCM_PHY_REV(phydev) \  	((phydev)->drv->phy_id & ~((phydev)->drv->phy_id_mask)) - -#define MII_BCM54XX_ECR		0x10	/* BCM54xx extended control register */ -#define MII_BCM54XX_ECR_IM	0x1000	/* Interrupt mask */ -#define MII_BCM54XX_ECR_IF	0x0800	/* Interrupt force */ - -#define MII_BCM54XX_ESR		0x11	/* BCM54xx extended status register */ -#define MII_BCM54XX_ESR_IS	0x1000	/* Interrupt status */ - -#define MII_BCM54XX_EXP_DATA	0x15	/* Expansion register data */ -#define MII_BCM54XX_EXP_SEL	0x17	/* Expansion register select */ -#define MII_BCM54XX_EXP_SEL_SSD	0x0e00	/* Secondary SerDes select */ -#define MII_BCM54XX_EXP_SEL_ER	0x0f00	/* Expansion register select */ - -#define MII_BCM54XX_AUX_CTL	0x18	/* Auxiliary control register */ -#define MII_BCM54XX_ISR		0x1a	/* BCM54xx interrupt status register */ -#define MII_BCM54XX_IMR		0x1b	/* BCM54xx interrupt mask register */ -#define MII_BCM54XX_INT_CRCERR	0x0001	/* CRC error */ -#define MII_BCM54XX_INT_LINK	0x0002	/* Link status changed */ -#define MII_BCM54XX_INT_SPEED	0x0004	/* Link speed change */ -#define MII_BCM54XX_INT_DUPLEX	0x0008	/* Duplex mode changed */ -#define MII_BCM54XX_INT_LRS	0x0010	/* Local receiver status changed */ -#define MII_BCM54XX_INT_RRS	0x0020	/* Remote receiver status changed */ -#define MII_BCM54XX_INT_SSERR	0x0040	/* Scrambler synchronization error */ -#define MII_BCM54XX_INT_UHCD	0x0080	/* Unsupported HCD negotiated */ -#define MII_BCM54XX_INT_NHCD	0x0100	/* No HCD */ -#define MII_BCM54XX_INT_NHCDL	0x0200	/* No HCD link */ -#define MII_BCM54XX_INT_ANPR	0x0400	/* Auto-negotiation page received */ -#define MII_BCM54XX_INT_LC	0x0800	/* All counters below 128 */ -#define MII_BCM54XX_INT_HC	0x1000	/* Counter above 32768 */ -#define MII_BCM54XX_INT_MDIX	0x2000	/* MDIX status change */ -#define MII_BCM54XX_INT_PSERR	0x4000	/* Pair swap error */ - -#define MII_BCM54XX_SHD		0x1c	/* 0x1c shadow registers */ -#define MII_BCM54XX_SHD_WRITE	0x8000 -#define MII_BCM54XX_SHD_VAL(x)	((x & 0x1f) << 10) -#define MII_BCM54XX_SHD_DATA(x)	((x & 0x3ff) << 0) - -/* - * AUXILIARY CONTROL SHADOW ACCESS REGISTERS.  (PHY REG 0x18) - */ -#define MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL	0x0000 -#define MII_BCM54XX_AUXCTL_ACTL_TX_6DB		0x0400 -#define MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA	0x0800 - -#define MII_BCM54XX_AUXCTL_MISC_WREN	0x8000 -#define MII_BCM54XX_AUXCTL_MISC_FORCE_AMDIX	0x0200 -#define MII_BCM54XX_AUXCTL_MISC_RDSEL_MISC	0x7000 -#define MII_BCM54XX_AUXCTL_SHDWSEL_MISC	0x0007 - -#define MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL	0x0000 - -  /*   * Broadcom LED source encodings.  These are used in BCM5461, BCM5481,   * BCM5482, and possibly some others. @@ -355,8 +303,7 @@ static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev)  		}  	} -	if (clk125en == false || -	    (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE)) +	if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))  		val &= ~BCM54XX_SHD_SCR3_DLLAPD_DIS;  	else  		val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; @@ -373,8 +320,7 @@ static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev)  	orig = val; -	if (clk125en == false || -	    (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE)) +	if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))  		val |= BCM54XX_SHD_APD_EN;  	else  		val &= ~BCM54XX_SHD_APD_EN; @@ -553,7 +499,7 @@ static int bcm5481_config_aneg(struct phy_device *phydev)  		/*  		 * There is no BCM5481 specification available, so down  		 * here is everything we know about "register 0x18". This -		 * at least helps BCM5481 to successfuly receive packets +		 * 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". @@ -684,7 +630,8 @@ static int brcm_fet_config_intr(struct phy_device *phydev)  	return err;  } -static struct phy_driver bcm5411_driver = { +static struct phy_driver broadcom_drivers[] = { +{  	.phy_id		= PHY_ID_BCM5411,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5411", @@ -697,9 +644,7 @@ static struct phy_driver bcm5411_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm5421_driver = { +}, {  	.phy_id		= PHY_ID_BCM5421,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5421", @@ -712,9 +657,7 @@ static struct phy_driver bcm5421_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm5461_driver = { +}, {  	.phy_id		= PHY_ID_BCM5461,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5461", @@ -727,9 +670,7 @@ static struct phy_driver bcm5461_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm5464_driver = { +}, {  	.phy_id		= PHY_ID_BCM5464,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5464", @@ -742,9 +683,7 @@ static struct phy_driver bcm5464_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm5481_driver = { +}, {  	.phy_id		= PHY_ID_BCM5481,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5481", @@ -757,9 +696,7 @@ static struct phy_driver bcm5481_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm5482_driver = { +}, {  	.phy_id		= PHY_ID_BCM5482,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5482", @@ -772,9 +709,7 @@ static struct phy_driver bcm5482_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm50610_driver = { +}, {  	.phy_id		= PHY_ID_BCM50610,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM50610", @@ -787,9 +722,7 @@ static struct phy_driver bcm50610_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm50610m_driver = { +}, {  	.phy_id		= PHY_ID_BCM50610M,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM50610M", @@ -802,9 +735,7 @@ static struct phy_driver bcm50610m_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm57780_driver = { +}, {  	.phy_id		= PHY_ID_BCM57780,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM57780", @@ -817,9 +748,7 @@ static struct phy_driver bcm57780_driver = {  	.ack_interrupt	= bcm54xx_ack_interrupt,  	.config_intr	= bcm54xx_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcmac131_driver = { +}, {  	.phy_id		= PHY_ID_BCMAC131,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCMAC131", @@ -832,9 +761,7 @@ static struct phy_driver bcmac131_driver = {  	.ack_interrupt	= brcm_fet_ack_interrupt,  	.config_intr	= brcm_fet_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; - -static struct phy_driver bcm5241_driver = { +}, {  	.phy_id		= PHY_ID_BCM5241,  	.phy_id_mask	= 0xfffffff0,  	.name		= "Broadcom BCM5241", @@ -847,84 +774,18 @@ static struct phy_driver bcm5241_driver = {  	.ack_interrupt	= brcm_fet_ack_interrupt,  	.config_intr	= brcm_fet_config_intr,  	.driver		= { .owner = THIS_MODULE }, -}; +} };  static int __init broadcom_init(void)  { -	int ret; - -	ret = phy_driver_register(&bcm5411_driver); -	if (ret) -		goto out_5411; -	ret = phy_driver_register(&bcm5421_driver); -	if (ret) -		goto out_5421; -	ret = phy_driver_register(&bcm5461_driver); -	if (ret) -		goto out_5461; -	ret = phy_driver_register(&bcm5464_driver); -	if (ret) -		goto out_5464; -	ret = phy_driver_register(&bcm5481_driver); -	if (ret) -		goto out_5481; -	ret = phy_driver_register(&bcm5482_driver); -	if (ret) -		goto out_5482; -	ret = phy_driver_register(&bcm50610_driver); -	if (ret) -		goto out_50610; -	ret = phy_driver_register(&bcm50610m_driver); -	if (ret) -		goto out_50610m; -	ret = phy_driver_register(&bcm57780_driver); -	if (ret) -		goto out_57780; -	ret = phy_driver_register(&bcmac131_driver); -	if (ret) -		goto out_ac131; -	ret = phy_driver_register(&bcm5241_driver); -	if (ret) -		goto out_5241; -	return ret; - -out_5241: -	phy_driver_unregister(&bcmac131_driver); -out_ac131: -	phy_driver_unregister(&bcm57780_driver); -out_57780: -	phy_driver_unregister(&bcm50610m_driver); -out_50610m: -	phy_driver_unregister(&bcm50610_driver); -out_50610: -	phy_driver_unregister(&bcm5482_driver); -out_5482: -	phy_driver_unregister(&bcm5481_driver); -out_5481: -	phy_driver_unregister(&bcm5464_driver); -out_5464: -	phy_driver_unregister(&bcm5461_driver); -out_5461: -	phy_driver_unregister(&bcm5421_driver); -out_5421: -	phy_driver_unregister(&bcm5411_driver); -out_5411: -	return ret; +	return phy_drivers_register(broadcom_drivers, +		ARRAY_SIZE(broadcom_drivers));  }  static void __exit broadcom_exit(void)  { -	phy_driver_unregister(&bcm5241_driver); -	phy_driver_unregister(&bcmac131_driver); -	phy_driver_unregister(&bcm57780_driver); -	phy_driver_unregister(&bcm50610m_driver); -	phy_driver_unregister(&bcm50610_driver); -	phy_driver_unregister(&bcm5482_driver); -	phy_driver_unregister(&bcm5481_driver); -	phy_driver_unregister(&bcm5464_driver); -	phy_driver_unregister(&bcm5461_driver); -	phy_driver_unregister(&bcm5421_driver); -	phy_driver_unregister(&bcm5411_driver); +	phy_drivers_unregister(broadcom_drivers, +		ARRAY_SIZE(broadcom_drivers));  }  module_init(broadcom_init); diff --git a/drivers/net/phy/cicada.c b/drivers/net/phy/cicada.c index d28173161c2..b57ce0cc965 100644 --- a/drivers/net/phy/cicada.c +++ b/drivers/net/phy/cicada.c @@ -30,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 @@ -92,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); @@ -102,7 +102,8 @@ static int cis820x_config_intr(struct phy_device *phydev)  }  /* Cicada 8201, a.k.a Vitesse VSC8201 */ -static struct phy_driver cis8201_driver = { +static struct phy_driver cis820x_driver[] = { +{  	.phy_id		= 0x000fc410,  	.name		= "Cicada Cis8201",  	.phy_id_mask	= 0x000ffff0, @@ -113,11 +114,8 @@ static struct phy_driver cis8201_driver = {  	.read_status	= &genphy_read_status,  	.ack_interrupt	= &cis820x_ack_interrupt,  	.config_intr	= &cis820x_config_intr, -	.driver 	= { .owner = THIS_MODULE,}, -}; - -/* Cicada 8204 */ -static struct phy_driver cis8204_driver = { +	.driver		= { .owner = THIS_MODULE,}, +}, {  	.phy_id		= 0x000fc440,  	.name		= "Cicada Cis8204",  	.phy_id_mask	= 0x000fffc0, @@ -128,32 +126,19 @@ 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 cicada_init(void)  { -	int ret; - -	ret = phy_driver_register(&cis8204_driver); -	if (ret) -		goto err1; - -	ret = phy_driver_register(&cis8201_driver); -	if (ret) -		goto err2; -	return 0; - -err2: -	phy_driver_unregister(&cis8204_driver); -err1: -	return ret; +	return phy_drivers_register(cis820x_driver, +		ARRAY_SIZE(cis820x_driver));  }  static void __exit cicada_exit(void)  { -	phy_driver_unregister(&cis8204_driver); -	phy_driver_unregister(&cis8201_driver); +	phy_drivers_unregister(cis820x_driver, +		ARRAY_SIZE(cis820x_driver));  }  module_init(cicada_init); diff --git a/drivers/net/phy/davicom.c b/drivers/net/phy/davicom.c index 2f774acdb55..d2c08f625a4 100644 --- a/drivers/net/phy/davicom.c +++ b/drivers/net/phy/davicom.c @@ -72,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; @@ -134,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) @@ -149,29 +144,32 @@ static int dm9161_ack_interrupt(struct phy_device *phydev)  	return (err < 0) ? err : 0;  } -static struct phy_driver dm9161e_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, +	.ack_interrupt	= dm9161_ack_interrupt, +	.config_intr	= dm9161_config_intr,  	.driver		= { .owner = THIS_MODULE,}, -}; - -static struct phy_driver dm9161a_driver = { +}, {  	.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,}, -}; - -static struct phy_driver dm9131_driver = { +}, {  	.phy_id		= 0x00181b80,  	.name		= "Davicom DM9131",  	.phy_id_mask	= 0x0ffffff0, @@ -182,38 +180,18 @@ static struct phy_driver dm9131_driver = {  	.ack_interrupt	= dm9161_ack_interrupt,  	.config_intr	= dm9161_config_intr,  	.driver		= { .owner = THIS_MODULE,}, -}; +} };  static int __init davicom_init(void)  { -	int ret; - -	ret = phy_driver_register(&dm9161e_driver); -	if (ret) -		goto err1; - -	ret = phy_driver_register(&dm9161a_driver); -	if (ret) -		goto err2; - -	ret = phy_driver_register(&dm9131_driver); -	if (ret) -		goto err3; -	return 0; - - err3: -	phy_driver_unregister(&dm9161a_driver); - err2: -	phy_driver_unregister(&dm9161e_driver); - err1: -	return ret; +	return phy_drivers_register(dm91xx_driver, +		ARRAY_SIZE(dm91xx_driver));  }  static void __exit davicom_exit(void)  { -	phy_driver_unregister(&dm9161e_driver); -	phy_driver_unregister(&dm9161a_driver); -	phy_driver_unregister(&dm9131_driver); +	phy_drivers_unregister(dm91xx_driver, +		ARRAY_SIZE(dm91xx_driver));  }  module_init(davicom_init); 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/fixed.c b/drivers/net/phy/fixed.c index 1fa4d73c3cc..d60d875cb44 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; @@ -70,7 +71,7 @@ static int fixed_phy_update_regs(struct fixed_phy *fp)  			lpa |= LPA_10FULL;  			break;  		default: -			printk(KERN_WARNING "fixed phy: unknown speed\n"); +			pr_warn("fixed phy: unknown speed\n");  			return -EINVAL;  		}  	} else { @@ -90,7 +91,7 @@ static int fixed_phy_update_regs(struct fixed_phy *fp)  			lpa |= LPA_10HALF;  			break;  		default: -			printk(KERN_WARNING "fixed phy: unknown speed\n"); +			pr_warn("fixed phy: unknown speed\n");  			return -EINVAL;  		}  	} @@ -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; @@ -220,7 +281,7 @@ static int __init fixed_mdio_bus_init(void)  		goto err_mdiobus_reg;  	} -	snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "0"); +	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; diff --git a/drivers/net/phy/icplus.c b/drivers/net/phy/icplus.c index c1d2d251fe8..97bf58bf493 100644 --- a/drivers/net/phy/icplus.c +++ b/drivers/net/phy/icplus.c @@ -30,48 +30,59 @@  #include <asm/irq.h>  #include <asm/uaccess.h> -MODULE_DESCRIPTION("ICPlus IP175C PHY driver"); +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 = 0; +	static int full_reset_performed;  	if (full_reset_performed == 0) {  		/* master reset */ -		err = phydev->bus->write(phydev->bus, 30, 0, 0x175c); +		err = mdiobus_write(phydev->bus, 30, 0, 0x175c);  		if (err < 0)  			return err;  		/* ensure no bus delays overlap reset period */ -		err = phydev->bus->read(phydev->bus, 30, 0); +		err = mdiobus_read(phydev->bus, 30, 0);  		/* data sheet specifies reset period is 2 msec */  		mdelay(2);  		/* enable IP175C mode */ -		err = phydev->bus->write(phydev->bus, 29, 31, 0x175c); +		err = mdiobus_write(phydev->bus, 29, 31, 0x175c);  		if (err < 0)  			return err;  		/* Set MII0 speed and duplex (in PHY mode) */ -		err = phydev->bus->write(phydev->bus, 29, 22, 0x420); +		err = mdiobus_write(phydev->bus, 29, 22, 0x420);  		if (err < 0)  			return err;  		/* reset switch ports */  		for (i = 0; i < 5; i++) { -			err = phydev->bus->write(phydev->bus, i, -						 MII_BMCR, BMCR_RESET); +			err = mdiobus_write(phydev->bus, i, +					    MII_BMCR, BMCR_RESET);  			if (err < 0)  				return err;  		}  		for (i = 0; i < 5; i++) -			err = phydev->bus->read(phydev->bus, i, MII_BMCR); +			err = mdiobus_read(phydev->bus, i, MII_BMCR);  		mdelay(2); @@ -89,6 +100,91 @@ static int ip175c_config_init(struct phy_device *phydev)  	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 */ @@ -108,7 +204,17 @@ static int ip175c_config_aneg(struct phy_device *phydev)  	return 0;  } -static struct phy_driver ip175c_driver = { +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, @@ -119,23 +225,53 @@ static struct phy_driver ip175c_driver = {  	.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 ip175c_init(void) +static int __init icplus_init(void)  { -	return phy_driver_register(&ip175c_driver); +	return phy_drivers_register(icplus_driver, +		ARRAY_SIZE(icplus_driver));  } -static void __exit ip175c_exit(void) +static void __exit icplus_exit(void)  { -	phy_driver_unregister(&ip175c_driver); +	phy_drivers_unregister(icplus_driver, +		ARRAY_SIZE(icplus_driver));  } -module_init(ip175c_init); -module_exit(ip175c_exit); +module_init(icplus_init); +module_exit(icplus_exit);  static struct mdio_device_id __maybe_unused icplus_tbl[] = {  	{ 0x02430d80, 0x0ffffff0 }, +	{ 0x02430d90, 0x0ffffff0 }, +	{ 0x02430c54, 0x0ffffff0 },  	{ }  }; diff --git a/drivers/net/phy/lxt.c b/drivers/net/phy/lxt.c index 6f6e8b616a6..9108f319170 100644 --- a/drivers/net/phy/lxt.c +++ b/drivers/net/phy/lxt.c @@ -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,6 +122,123 @@ static int lxt971_config_intr(struct phy_device *phydev)  	return err;  } +/* + * 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); @@ -149,7 +266,8 @@ static int lxt973_config_aneg(struct phy_device *phydev)  	return phydev->priv ? 0 : genphy_config_aneg(phydev);  } -static struct phy_driver lxt970_driver = { +static struct phy_driver lxt97x_driver[] = { +{  	.phy_id		= 0x78100000,  	.name		= "LXT970",  	.phy_id_mask	= 0xfffffff0, @@ -160,10 +278,8 @@ 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 = { +	.driver		= { .owner = THIS_MODULE,}, +}, {  	.phy_id		= 0x001378e0,  	.name		= "LXT971",  	.phy_id_mask	= 0xfffffff0, @@ -173,10 +289,18 @@ static struct phy_driver lxt971_driver = {  	.read_status	= genphy_read_status,  	.ack_interrupt	= lxt971_ack_interrupt,  	.config_intr	= lxt971_config_intr, -	.driver 	= { .owner = THIS_MODULE,}, -}; - -static struct phy_driver lxt973_driver = { +	.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, @@ -185,39 +309,19 @@ static struct phy_driver lxt973_driver = {  	.probe		= lxt973_probe,  	.config_aneg	= lxt973_config_aneg,  	.read_status	= genphy_read_status, -	.driver 	= { .owner = THIS_MODULE,}, -}; +	.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; - -	ret = phy_driver_register(&lxt973_driver); -	if (ret) -		goto err3; -	return 0; - - err3: -	phy_driver_unregister(&lxt971_driver); - 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_driver_unregister(&lxt973_driver); +	phy_drivers_unregister(lxt97x_driver, +		ARRAY_SIZE(lxt97x_driver));  }  module_init(lxt_init); diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index f0bd1a1aba3..bd37e45c89c 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -7,6 +7,8 @@   *   * 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 @@ -30,10 +32,13 @@  #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 @@ -77,10 +82,31 @@  #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_88E1121_PHY_PAGE		22  #define MII_M1011_PHY_STATUS		0x11  #define MII_M1011_PHY_STATUS_1000	0x8000 @@ -90,6 +116,8 @@  #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"); @@ -186,13 +214,94 @@ static int marvell_config_aneg(struct phy_device *phydev)  	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_88E1121_PHY_PAGE); +	oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); -	err = phy_write(phydev, MII_88E1121_PHY_PAGE, +	err = phy_write(phydev, MII_MARVELL_PHY_PAGE,  			MII_88E1121_PHY_MSCR_PAGE);  	if (err < 0)  		return err; @@ -218,7 +327,7 @@ static int m88e1121_config_aneg(struct phy_device *phydev)  			return err;  	} -	phy_write(phydev, MII_88E1121_PHY_PAGE, oldpage); +	phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage);  	err = phy_write(phydev, MII_BMCR, BMCR_RESET);  	if (err < 0) @@ -229,11 +338,11 @@ static int m88e1121_config_aneg(struct phy_device *phydev)  	if (err < 0)  		return err; -	oldpage = phy_read(phydev, MII_88E1121_PHY_PAGE); +	oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); -	phy_write(phydev, MII_88E1121_PHY_PAGE, MII_88E1121_PHY_LED_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_88E1121_PHY_PAGE, oldpage); +	phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage);  	err = genphy_config_aneg(phydev); @@ -244,9 +353,9 @@ static int m88e1318_config_aneg(struct phy_device *phydev)  {  	int err, oldpage, mscr; -	oldpage = phy_read(phydev, MII_88E1121_PHY_PAGE); +	oldpage = phy_read(phydev, MII_MARVELL_PHY_PAGE); -	err = phy_write(phydev, MII_88E1121_PHY_PAGE, +	err = phy_write(phydev, MII_MARVELL_PHY_PAGE,  			MII_88E1121_PHY_MSCR_PAGE);  	if (err < 0)  		return err; @@ -258,26 +367,77 @@ static int m88e1318_config_aneg(struct phy_device *phydev)  	if (err < 0)  		return err; -	err = phy_write(phydev, MII_88E1121_PHY_PAGE, oldpage); +	err = phy_write(phydev, MII_MARVELL_PHY_PAGE, oldpage);  	if (err < 0)  		return err;  	return m88e1121_config_aneg(phydev);  } -static int m88e1111_config_init(struct phy_device *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; -	/* Enable Fiber/Copper auto selection */ -	temp = phy_read(phydev, MII_M1111_PHY_EXT_SR); -	temp &= ~MII_M1111_HWCFG_FIBER_COPPER_AUTO; -	phy_write(phydev, MII_M1111_PHY_EXT_SR, temp); +	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; -	phy_write(phydev, MII_BMCR, temp); +	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) || @@ -368,12 +528,11 @@ static int m88e1111_config_init(struct phy_device *phydev)  			return err;  	} - -	err = phy_write(phydev, MII_BMCR, BMCR_RESET); +	err = marvell_of_reg_init(phydev);  	if (err < 0)  		return err; -	return 0; +	return phy_write(phydev, MII_BMCR, BMCR_RESET);  }  static int m88e1118_config_aneg(struct phy_device *phydev) @@ -398,7 +557,7 @@ static int m88e1118_config_init(struct phy_device *phydev)  	int err;  	/* Change address */ -	err = phy_write(phydev, 0x16, 0x0002); +	err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0002);  	if (err < 0)  		return err; @@ -408,7 +567,7 @@ static int m88e1118_config_init(struct phy_device *phydev)  		return err;  	/* Change address */ -	err = phy_write(phydev, 0x16, 0x0003); +	err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0003);  	if (err < 0)  		return err; @@ -420,16 +579,42 @@ static int m88e1118_config_init(struct phy_device *phydev)  	if (err < 0)  		return err; +	err = marvell_of_reg_init(phydev); +	if (err < 0) +		return err; +  	/* Reset address */ -	err = phy_write(phydev, 0x16, 0x0); +	err = phy_write(phydev, MII_MARVELL_PHY_PAGE, 0x0);  	if (err < 0)  		return err; -	err = phy_write(phydev, MII_BMCR, BMCR_RESET); +	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; -	return 0; +	/* 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) @@ -491,6 +676,10 @@ static int m88e1145_config_init(struct phy_device *phydev)  		}  	} +	err = marvell_of_reg_init(phydev); +	if (err < 0) +		return err; +  	return 0;  } @@ -593,6 +782,107 @@ static int m88e1121_did_interrupt(struct phy_device *phydev)  	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, @@ -604,6 +894,8 @@ static struct phy_driver marvell_drivers[] = {  		.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 },  	},  	{ @@ -617,6 +909,8 @@ static struct phy_driver marvell_drivers[] = {  		.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 },  	},  	{ @@ -630,6 +924,8 @@ static struct phy_driver marvell_drivers[] = {  		.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 },  	},  	{ @@ -643,6 +939,8 @@ static struct phy_driver marvell_drivers[] = {  		.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,},  	},  	{ @@ -656,6 +954,8 @@ static struct phy_driver marvell_drivers[] = {  		.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 },  	},  	{ @@ -669,6 +969,10 @@ static struct phy_driver marvell_drivers[] = {  		.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 },  	},  	{ @@ -682,6 +986,23 @@ static struct phy_driver marvell_drivers[] = {  		.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 },  	},  	{ @@ -695,48 +1016,69 @@ static struct phy_driver marvell_drivers[] = {  		.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)  { -	int ret; -	int i; - -	for (i = 0; i < ARRAY_SIZE(marvell_drivers); i++) { -		ret = phy_driver_register(&marvell_drivers[i]); - -		if (ret) { -			while (i-- > 0) -				phy_driver_unregister(&marvell_drivers[i]); -			return ret; -		} -	} - -	return 0; +	return phy_drivers_register(marvell_drivers, +		 ARRAY_SIZE(marvell_drivers));  }  static void __exit marvell_exit(void)  { -	int i; - -	for (i = 0; i < ARRAY_SIZE(marvell_drivers); i++) -		phy_driver_unregister(&marvell_drivers[i]); +	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[] = { -	{ 0x01410c60, 0xfffffff0 }, -	{ 0x01410c90, 0xfffffff0 }, -	{ 0x01410cc0, 0xfffffff0 }, -	{ 0x01410e10, 0xfffffff0 }, -	{ 0x01410cb0, 0xfffffff0 }, -	{ 0x01410cd0, 0xfffffff0 }, -	{ 0x01410e30, 0xfffffff0 }, -	{ 0x01410e90, 0xfffffff0 }, +	{ 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 },  	{ }  }; diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c index 65391891d8c..daec9b05d16 100644 --- a/drivers/net/phy/mdio-bitbang.c +++ b/drivers/net/phy/mdio-bitbang.c @@ -202,6 +202,14 @@ static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)  	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; @@ -214,6 +222,7 @@ struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)  	bus->read = mdiobb_read;  	bus->write = mdiobb_write; +	bus->reset = mdiobb_reset;  	bus->priv = ctrl;  	return bus; diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c index f62c7b717bc..5f1a2250018 100644 --- a/drivers/net/phy/mdio-gpio.c +++ b/drivers/net/phy/mdio-gpio.c @@ -22,30 +22,71 @@  #include <linux/module.h>  #include <linux/slab.h> -#include <linux/init.h>  #include <linux/interrupt.h>  #include <linux/platform_device.h>  #include <linux/gpio.h>  #include <linux/mdio-gpio.h> -#ifdef CONFIG_OF_GPIO  #include <linux/of_gpio.h>  #include <linux/of_mdio.h> -#include <linux/of_platform.h> -#endif  struct mdio_gpio_info {  	struct mdiobb_ctrl ctrl; -	int mdc, mdio; +	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); +		gpio_direction_output(bitbang->mdio, +				      1 ^ bitbang->mdio_active_low);  	else  		gpio_direction_input(bitbang->mdio);  } @@ -55,7 +96,7 @@ 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); +	return gpio_get_value(bitbang->mdio) ^ bitbang->mdio_active_low;  }  static void mdio_set(struct mdiobb_ctrl *ctrl, int what) @@ -63,7 +104,10 @@ static void mdio_set(struct mdiobb_ctrl *ctrl, int what)  	struct mdio_gpio_info *bitbang =  		container_of(ctrl, struct mdio_gpio_info, ctrl); -	gpio_set_value(bitbang->mdio, what); +	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) @@ -71,7 +115,7 @@ 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); +	gpio_set_value(bitbang->mdc, what ^ bitbang->mdc_active_low);  }  static struct mdiobb_ops mdio_gpio_ops = { @@ -82,25 +126,30 @@ static struct mdiobb_ops mdio_gpio_ops = {  	.get_mdio_data = mdio_get,  }; -static struct mii_bus * __devinit mdio_gpio_bus_init(struct device *dev, -					struct mdio_gpio_platform_data *pdata, -					int bus_id) +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 = kzalloc(sizeof(*bitbang), GFP_KERNEL); +	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_free_bitbang; +		goto out;  	new_bus->name = "GPIO Bitbanged MDIO", @@ -115,13 +164,20 @@ static struct mii_bus * __devinit mdio_gpio_bus_init(struct device *dev,  		if (!new_bus->irq[i])  			new_bus->irq[i] = PHY_POLL; -	snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", bus_id); +	snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id); -	if (gpio_request(bitbang->mdc, "mdc")) +	if (devm_gpio_request(dev, bitbang->mdc, "mdc"))  		goto out_free_bus; -	if (gpio_request(bitbang->mdio, "mdio")) -		goto out_free_mdc; +	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); @@ -129,12 +185,8 @@ static struct mii_bus * __devinit mdio_gpio_bus_init(struct device *dev,  	return new_bus; -out_free_mdc: -	gpio_free(bitbang->mdc);  out_free_bus:  	free_mdio_bitbang(new_bus); -out_free_bitbang: -	kfree(bitbang);  out:  	return NULL;  } @@ -142,16 +194,11 @@ out:  static void mdio_gpio_bus_deinit(struct device *dev)  {  	struct mii_bus *bus = dev_get_drvdata(dev); -	struct mdio_gpio_info *bitbang = bus->priv; -	dev_set_drvdata(dev, NULL); -	gpio_free(bitbang->mdio); -	gpio_free(bitbang->mdc);  	free_mdio_bitbang(bus); -	kfree(bitbang);  } -static void __devexit mdio_gpio_bus_destroy(struct device *dev) +static void mdio_gpio_bus_destroy(struct device *dev)  {  	struct mii_bus *bus = dev_get_drvdata(dev); @@ -159,142 +206,65 @@ static void __devexit mdio_gpio_bus_destroy(struct device *dev)  	mdio_gpio_bus_deinit(dev);  } -static int __devinit mdio_gpio_probe(struct platform_device *pdev) +static int mdio_gpio_probe(struct platform_device *pdev)  { -	struct mdio_gpio_platform_data *pdata = pdev->dev.platform_data; +	struct mdio_gpio_platform_data *pdata;  	struct mii_bus *new_bus; -	int ret; +	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, pdev->id); +	new_bus = mdio_gpio_bus_init(&pdev->dev, pdata, bus_id);  	if (!new_bus)  		return -ENODEV; -	ret = mdiobus_register(new_bus); +	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 __devexit mdio_gpio_remove(struct platform_device *pdev) +static int mdio_gpio_remove(struct platform_device *pdev)  {  	mdio_gpio_bus_destroy(&pdev->dev);  	return 0;  } -#ifdef CONFIG_OF_GPIO - -static int __devinit mdio_ofgpio_probe(struct platform_device *ofdev, -                                        const struct of_device_id *match) -{ -	struct mdio_gpio_platform_data *pdata; -	struct mii_bus *new_bus; -	int ret; - -	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); -	if (!pdata) -		return -ENOMEM; - -	ret = of_get_gpio(ofdev->dev.of_node, 0); -	if (ret < 0) -		goto out_free; -	pdata->mdc = ret; - -	ret = of_get_gpio(ofdev->dev.of_node, 1); -	if (ret < 0) -		goto out_free; -	pdata->mdio = ret; - -	new_bus = mdio_gpio_bus_init(&ofdev->dev, pdata, pdata->mdc); -	if (!new_bus) -		goto out_free; - -	ret = of_mdiobus_register(new_bus, ofdev->dev.of_node); -	if (ret) -		mdio_gpio_bus_deinit(&ofdev->dev); - -	return ret; - -out_free: -	kfree(pdata); -	return -ENODEV; -} - -static int __devexit mdio_ofgpio_remove(struct platform_device *ofdev) -{ -	mdio_gpio_bus_destroy(&ofdev->dev); -	kfree(ofdev->dev.platform_data); - -	return 0; -} - -static struct of_device_id mdio_ofgpio_match[] = { -	{ -		.compatible = "virtual,mdio-gpio", -	}, -	{}, -}; -MODULE_DEVICE_TABLE(of, mdio_ofgpio_match); - -static struct of_platform_driver mdio_ofgpio_driver = { -	.driver = { -		.name = "mdio-gpio", -		.owner = THIS_MODULE, -		.of_match_table = mdio_ofgpio_match, -	}, -	.probe = mdio_ofgpio_probe, -	.remove = __devexit_p(mdio_ofgpio_remove), +static struct of_device_id mdio_gpio_of_match[] = { +	{ .compatible = "virtual,mdio-gpio", }, +	{ /* sentinel */ }  }; -static inline int __init mdio_ofgpio_init(void) -{ -	return of_register_platform_driver(&mdio_ofgpio_driver); -} - -static inline void __exit mdio_ofgpio_exit(void) -{ -	of_unregister_platform_driver(&mdio_ofgpio_driver); -} -#else -static inline int __init mdio_ofgpio_init(void) { return 0; } -static inline void __exit mdio_ofgpio_exit(void) { } -#endif /* CONFIG_OF_GPIO */ -  static struct platform_driver mdio_gpio_driver = {  	.probe = mdio_gpio_probe, -	.remove = __devexit_p(mdio_gpio_remove), +	.remove = mdio_gpio_remove,  	.driver		= {  		.name	= "mdio-gpio",  		.owner	= THIS_MODULE, +		.of_match_table = mdio_gpio_of_match,  	},  }; -static int __init mdio_gpio_init(void) -{ -	int ret; - -	ret = mdio_ofgpio_init(); -	if (ret) -		return ret; - -	ret = platform_driver_register(&mdio_gpio_driver); -	if (ret) -		mdio_ofgpio_exit(); - -	return ret; -} -module_init(mdio_gpio_init); - -static void __exit mdio_gpio_exit(void) -{ -	platform_driver_unregister(&mdio_gpio_driver); -	mdio_ofgpio_exit(); -} -module_exit(mdio_gpio_exit); +module_platform_driver(mdio_gpio_driver);  MODULE_ALIAS("platform:mdio-gpio");  MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas"); 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 index bd12ba941be..a51ed92fbad 100644 --- a/drivers/net/phy/mdio-octeon.c +++ b/drivers/net/phy/mdio-octeon.c @@ -3,14 +3,16 @@   * License.  See the file "COPYING" in the main directory of this archive   * for more details.   * - * Copyright (C) 2009 Cavium Networks + * Copyright (C) 2009-2012 Cavium, Inc.   */ -#include <linux/gfp.h> -#include <linux/init.h> -#include <linux/module.h>  #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> @@ -18,32 +20,108 @@  #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; -	int unit; +	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 = 1; /* MDIO_CLAUSE_22_READ */ +	smi_cmd.s.phy_op = op;  	smi_cmd.s.phy_adr = phy_id;  	smi_cmd.s.reg_adr = regnum; -	cvmx_write_csr(CVMX_SMIX_CMD(p->unit), smi_cmd.u64); +	cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64);  	do { -		/* -		 * Wait 1000 clocks so we don't saturate the RSL bus +		/* Wait 1000 clocks so we don't saturate the RSL bus  		 * doing reads.  		 */ -		cvmx_wait(1000); -		smi_rd.u64 = cvmx_read_csr(CVMX_SMIX_RD_DAT(p->unit)); +		__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) @@ -58,25 +136,37 @@ static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id,  	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(CVMX_SMIX_WR_DAT(p->unit), smi_wr.u64); +	cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64);  	smi_cmd.u64 = 0; -	smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_22_WRITE */ +	smi_cmd.s.phy_op = op;  	smi_cmd.s.phy_adr = phy_id;  	smi_cmd.s.reg_adr = regnum; -	cvmx_write_csr(CVMX_SMIX_CMD(p->unit), smi_cmd.u64); +	cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64);  	do { -		/* -		 * Wait 1000 clocks so we don't saturate the RSL bus +		/* Wait 1000 clocks so we don't saturate the RSL bus  		 * doing reads.  		 */ -		cvmx_wait(1000); -		smi_wr.u64 = cvmx_read_csr(CVMX_SMIX_WR_DAT(p->unit)); +		__delay(1000); +		smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT);  	} while (smi_wr.s.pending && --timeout);  	if (timeout <= 0) @@ -85,85 +175,99 @@ static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id,  	return 0;  } -static int __devinit octeon_mdiobus_probe(struct platform_device *pdev) +static int octeon_mdiobus_probe(struct platform_device *pdev)  {  	struct octeon_mdiobus *bus; +	struct resource *res_mem;  	union cvmx_smix_en smi_en; -	int i;  	int err = -ENOENT;  	bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);  	if (!bus)  		return -ENOMEM; -	/* The platform_device id is our unit number.  */ -	bus->unit = pdev->id; +	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 err; +		goto fail;  	smi_en.u64 = 0;  	smi_en.s.en = 1; -	cvmx_write_csr(CVMX_SMIX_EN(bus->unit), smi_en.u64); - -	/* -	 * Standard Octeon evaluation boards don't support phy -	 * interrupts, we need to poll. -	 */ -	for (i = 0; i < PHY_MAX_ADDR; i++) -		bus->phy_irq[i] = PHY_POLL; +	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, "%x", bus->unit); +	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; -	dev_set_drvdata(&pdev->dev, bus); +	platform_set_drvdata(pdev, bus); -	err = mdiobus_register(bus->mii_bus); +	err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node);  	if (err) -		goto err_register; +		goto fail_register;  	dev_info(&pdev->dev, "Version " DRV_VERSION "\n");  	return 0; -err_register: +fail_register:  	mdiobus_free(bus->mii_bus); - -err: -	devm_kfree(&pdev->dev, bus); +fail:  	smi_en.u64 = 0; -	cvmx_write_csr(CVMX_SMIX_EN(bus->unit), smi_en.u64); +	cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64);  	return err;  } -static int __devexit octeon_mdiobus_remove(struct platform_device *pdev) +static int octeon_mdiobus_remove(struct platform_device *pdev)  {  	struct octeon_mdiobus *bus;  	union cvmx_smix_en smi_en; -	bus = dev_get_drvdata(&pdev->dev); +	bus = platform_get_drvdata(pdev);  	mdiobus_unregister(bus->mii_bus);  	mdiobus_free(bus->mii_bus);  	smi_en.u64 = 0; -	cvmx_write_csr(CVMX_SMIX_EN(bus->unit), smi_en.u64); +	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		= __devexit_p(octeon_mdiobus_remove), +	.remove		= octeon_mdiobus_remove,  };  void octeon_mdiobus_force_mod_depencency(void) @@ -172,18 +276,7 @@ void octeon_mdiobus_force_mod_depencency(void)  }  EXPORT_SYMBOL(octeon_mdiobus_force_mod_depencency); -static int __init octeon_mdiobus_mod_init(void) -{ -	return platform_driver_register(&octeon_mdiobus_driver); -} - -static void __exit octeon_mdiobus_mod_exit(void) -{ -	platform_driver_unregister(&octeon_mdiobus_driver); -} - -module_init(octeon_mdiobus_mod_init); -module_exit(octeon_mdiobus_mod_exit); +module_platform_driver(octeon_mdiobus_driver);  MODULE_DESCRIPTION(DRV_DESCRIPTION);  MODULE_VERSION(DRV_VERSION); 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 6c58da2b882..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,6 +10,9 @@   * option) any later version.   *   */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kernel.h>  #include <linux/string.h>  #include <linux/errno.h> @@ -22,6 +22,8 @@  #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> @@ -31,28 +33,108 @@  #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_alloc - allocate a mii_bus structure + * 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 allocate an mii_bus   * structure to fill in.   */ -struct mii_bus *mdiobus_alloc(void) +struct mii_bus *mdiobus_alloc_size(size_t size)  {  	struct mii_bus *bus; +	size_t aligned_size = ALIGN(sizeof(*bus), NETDEV_ALIGN); +	size_t alloc_size; -	bus = kzalloc(sizeof(*bus), GFP_KERNEL); -	if (bus != NULL) +	/* If we alloc extra space, it should be aligned */ +	if (size) +		alloc_size = aligned_size + size; +	else +		alloc_size = sizeof(*bus); + +	bus = kzalloc(alloc_size, GFP_KERNEL); +	if (bus) {  		bus->state = MDIOBUS_ALLOCATED; +		if (size) +			bus->priv = (void *)bus + aligned_size; +	} + +	return bus; +} +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(mdiobus_alloc); +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 @@ -75,6 +157,82 @@ static struct class mdio_bus_class = {  	.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; + +	if (!mdio_bus_np) +		return NULL; + +	d = class_find_device(&mdio_bus_class, NULL,  mdio_bus_np, +			      of_mdio_bus_match); + +	return d ? to_mii_bus(d) : NULL; +} +EXPORT_SYMBOL(of_mdio_find_bus); + +/* 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; + +	if (dev->of_node || !mdio->dev.of_node) +		return; + +	for_each_available_child_of_node(mdio->dev.of_node, child) { +		int addr; +		int ret; + +		ret = of_property_read_u32(child, "reg", &addr); +		if (ret < 0) { +			dev_err(dev, "%s has invalid PHY address\n", +				child->full_name); +			continue; +		} + +		/* 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 @@ -89,8 +247,7 @@ int mdiobus_register(struct mii_bus *bus)  	int i, err;  	if (NULL == bus || NULL == bus->name || -			NULL == bus->read || -			NULL == bus->write) +	    NULL == bus->read || NULL == bus->write)  		return -EINVAL;  	BUG_ON(bus->state != MDIOBUS_ALLOCATED && @@ -98,12 +255,14 @@ int mdiobus_register(struct mii_bus *bus)  	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) { -		printk(KERN_ERR "mii_bus %s failed to register\n", bus->id); +		pr_err("mii_bus %s failed to register\n", bus->id); +		put_device(&bus->dev);  		return -EINVAL;  	} @@ -164,9 +323,7 @@ EXPORT_SYMBOL(mdiobus_unregister);   */  void mdiobus_free(struct mii_bus *bus)  { -	/* -	 * For compatibility with error handling in drivers. -	 */ +	/* For compatibility with error handling in drivers. */  	if (bus->state == MDIOBUS_ALLOCATED) {  		kfree(bus);  		return; @@ -184,10 +341,16 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr)  	struct phy_device *phydev;  	int err; -	phydev = get_phy_device(bus, addr); +	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); @@ -260,8 +423,14 @@ 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 & phydrv->phy_id_mask) == -		(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);  }  #ifdef CONFIG_PM @@ -279,15 +448,13 @@ static bool mdio_bus_phy_may_suspend(struct phy_device *phydev)  	if (!netdev)  		return true; -	/* -	 * Don't suspend PHY if the attched netdev parent may wakeup. +	/* 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 +	/* 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.  	 */ @@ -302,8 +469,7 @@ static int mdio_bus_suspend(struct device *dev)  	struct phy_driver *phydrv = to_phy_driver(dev->driver);  	struct phy_device *phydev = to_phy_device(dev); -	/* -	 * We must stop the state machine manually, otherwise it stops out of +	/* 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. @@ -332,7 +498,7 @@ static int mdio_bus_resume(struct device *dev)  no_resume:  	if (phydev->attached_dev && phydev->adjust_link) -		phy_start_machine(phydev, NULL); +		phy_start_machine(phydev);  	return 0;  } @@ -354,12 +520,12 @@ static int mdio_bus_restore(struct device *dev)  	phydev->link = 0;  	phydev->state = PHY_UP; -	phy_start_machine(phydev, NULL); +	phy_start_machine(phydev);  	return 0;  } -static struct dev_pm_ops mdio_bus_pm_ops = { +static const struct dev_pm_ops mdio_bus_pm_ops = {  	.suspend = mdio_bus_suspend,  	.resume = mdio_bus_resume,  	.freeze = mdio_bus_suspend, @@ -375,10 +541,46 @@ static struct dev_pm_ops mdio_bus_pm_ops = {  #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,  	.pm		= MDIO_BUS_PM_OPS, +	.dev_groups	= mdio_dev_groups,  };  EXPORT_SYMBOL(mdio_bus_type); diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 0fd1678bc5a..bc7c7d2f75f 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -5,27 +5,33 @@   *   * Author: David J. Choi   * - * Copyright (c) 2010 Micrel, Inc. + * 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 : ksz9021 1000/100/10 phy from Micrel - *		ks8001, ks8737, ks8721, ks8041, ks8051 100/10 phy + * 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> -#define	PHY_ID_KSZ9021			0x00221611 -#define	PHY_ID_KS8737			0x00221720 -#define	PHY_ID_KS8041			0x00221510 -#define	PHY_ID_KS8051			0x00221550 -/* both for ks8001 Rev. A/B, and for ks8721 Rev 3. */ -#define	PHY_ID_KS8001			0x0022161A +/* 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 @@ -46,6 +52,47 @@  #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)  { @@ -101,12 +148,280 @@ static int ks8737_config_intr(struct phy_device *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 struct phy_driver ks8737_driver = { +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", @@ -117,42 +432,115 @@ static struct phy_driver ks8737_driver = {  	.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,}, -}; - -static struct phy_driver ks8041_driver = { -	.phy_id		= PHY_ID_KS8041, +}, { +	.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 KS8041", +	.name		= "Micrel KSZ8041",  	.features	= (PHY_BASIC_FEATURES | SUPPORTED_Pause  				| SUPPORTED_Asym_Pause),  	.flags		= PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, -	.config_init	= kszphy_config_init, +	.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,}, -}; - -static struct phy_driver ks8051_driver = { -	.phy_id		= PHY_ID_KS8051, +}, { +	.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 KS8051", +	.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,}, -}; - -static struct phy_driver ks8001_driver = { -	.phy_id		= PHY_ID_KS8001, -	.name		= "Micrel KS8001 or KS8721", +}, { +	.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, @@ -161,67 +549,74 @@ static struct phy_driver ks8001_driver = {  	.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,}, -}; - -static struct phy_driver ksz9021_driver = { +}, {  	.phy_id		= PHY_ID_KSZ9021, -	.phy_id_mask	= 0x000fff10, +	.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	= kszphy_config_init, +	.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)  { -	int ret; - -	ret = phy_driver_register(&ks8001_driver); -	if (ret) -		goto err1; - -	ret = phy_driver_register(&ksz9021_driver); -	if (ret) -		goto err2; - -	ret = phy_driver_register(&ks8737_driver); -	if (ret) -		goto err3; -	ret = phy_driver_register(&ks8041_driver); -	if (ret) -		goto err4; -	ret = phy_driver_register(&ks8051_driver); -	if (ret) -		goto err5; - -	return 0; - -err5: -	phy_driver_unregister(&ks8041_driver); -err4: -	phy_driver_unregister(&ks8737_driver); -err3: -	phy_driver_unregister(&ksz9021_driver); -err2: -	phy_driver_unregister(&ks8001_driver); -err1: -	return ret; +	return phy_drivers_register(ksphy_driver, +		ARRAY_SIZE(ksphy_driver));  }  static void __exit ksphy_exit(void)  { -	phy_driver_unregister(&ks8001_driver); -	phy_driver_unregister(&ks8737_driver); -	phy_driver_unregister(&ksz9021_driver); -	phy_driver_unregister(&ks8041_driver); -	phy_driver_unregister(&ks8051_driver); +	phy_drivers_unregister(ksphy_driver, +		ARRAY_SIZE(ksphy_driver));  }  module_init(ksphy_init); @@ -232,11 +627,18 @@ MODULE_AUTHOR("David J. Choi");  MODULE_LICENSE("GPL");  static struct mdio_device_id __maybe_unused micrel_tbl[] = { -	{ PHY_ID_KSZ9021, 0x000fff10 }, -	{ PHY_ID_KS8001, 0x00fffff0 }, +	{ PHY_ID_KSZ9021, 0x000ffffe }, +	{ PHY_ID_KSZ9031, 0x00fffff0 }, +	{ PHY_ID_KSZ8001, 0x00ffffff },  	{ PHY_ID_KS8737, 0x00fffff0 }, -	{ PHY_ID_KS8041, 0x00fffff0 }, -	{ PHY_ID_KS8051, 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 },  	{ }  }; diff --git a/drivers/net/phy/national.c b/drivers/net/phy/national.c index 0620ba96350..9a5f234d95b 100644 --- a/drivers/net/phy/national.c +++ b/drivers/net/phy/national.c @@ -15,6 +15,8 @@   *   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kernel.h>  #include <linux/module.h>  #include <linux/mii.h> @@ -22,11 +24,14 @@  #include <linux/phy.h>  #include <linux/netdevice.h> +#define DEBUG +  /* DP83865 phy identifier values */  #define DP83865_PHY_ID	0x20005c7a -#define DP83865_INT_MASK_REG 0x15 -#define DP83865_INT_MASK_STATUS 0x14 +#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 @@ -68,21 +73,25 @@ static int ns_config_intr(struct phy_device *phydev)  	int err;  	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) -		err = phy_write(phydev, DP83865_INT_MASK_REG, +		err = phy_write(phydev, DP83865_INT_MASK,  				DP83865_INT_MASK_DEFAULT);  	else -		err = phy_write(phydev, DP83865_INT_MASK_REG, 0); +		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_MASK_STATUS); +	int ret = phy_read(phydev, DP83865_INT_STATUS);  	if (ret < 0)  		return ret; -	return 0; +	/* 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) @@ -107,8 +116,8 @@ static void ns_10_base_t_hdx_loopack(struct phy_device *phydev, int disable)  		ns_exp_write(phydev, 0x1c0,  			     ns_exp_read(phydev, 0x1c0) & 0xfffe); -	printk(KERN_DEBUG "DP83865 PHY: 10BASE-T HDX loopback %s\n", -	       (ns_exp_read(phydev, 0x1c0) & 0x0001) ? "off" : "on"); +	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) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index a8445c72fc1..f7c61812ea4 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1,7 +1,4 @@ -/* - * 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 @@ -15,12 +12,14 @@   * option) any later version.   *   */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #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> @@ -32,11 +31,32 @@  #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/atomic.h> -#include <asm/io.h>  #include <asm/irq.h> -#include <asm/uaccess.h> + +static const char *phy_speed_to_str(int speed) +{ +	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)"; +	} +}  /**   * phy_print_status - Convenience function to print out the current phy status @@ -44,18 +64,18 @@   */  void phy_print_status(struct phy_device *phydev)  { -	pr_info("PHY: %s - Link is %s", dev_name(&phydev->dev), -			phydev->link ? "Up" : "Down"); -	if (phydev->link) -		printk(KERN_CONT " - %d/%s", phydev->speed, -				DUPLEX_FULL == phydev->duplex ? -				"Full" : "Half"); - -	printk(KERN_CONT "\n"); +	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_print_status); -  /**   * phy_clear_interrupt - Ack the phy device's interrupt   * @phydev: the phy_device struct @@ -63,16 +83,14 @@ EXPORT_SYMBOL(phy_print_status);   * If the @phydev driver has an ack_interrupt function, call it to   * ack and clear the phy device's interrupt.   * - * Returns 0 on success on < 0 on error. + * 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;  }  /** @@ -80,17 +98,15 @@ static int phy_clear_interrupt(struct phy_device *phydev)   * @phydev: the phy_device struct   * @interrupts: interrupt flags to configure for this @phydev   * - * Returns 0 on success on < 0 on error. + * 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;  } @@ -98,21 +114,21 @@ static int phy_config_interrupt(struct phy_device *phydev, u32 interrupts)   * 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; @@ -170,13 +186,12 @@ static const struct phy_setting settings[] = {   *   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; @@ -192,7 +207,7 @@ static inline int phy_find_setting(int speed, int duplex)   *   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++; @@ -211,7 +226,7 @@ static inline int phy_find_valid(int idx, u32 features)  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) @@ -238,11 +253,12 @@ static void phy_sanitize_settings(struct phy_device *phydev)   */  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. */ @@ -253,16 +269,16 @@ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd)  		return -EINVAL;  	if (cmd->autoneg == AUTONEG_DISABLE && -	    ((cmd->speed != SPEED_1000 && -	      cmd->speed != SPEED_100 && -	      cmd->speed != SPEED_10) || +	    ((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; @@ -285,12 +301,17 @@ 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; @@ -307,8 +328,7 @@ EXPORT_SYMBOL(phy_ethtool_gset);   * PHYCONTROL layer.  It changes registers without regard to   * current state.  Use at own risk.   */ -int phy_mii_ioctl(struct phy_device *phydev, -		struct ifreq *ifr, 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; @@ -319,26 +339,26 @@ int phy_mii_ioctl(struct phy_device *phydev,  		/* 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 (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)) == 0) +				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)) +				if (!phydev->autoneg && (val & BMCR_SPEED1000))  					phydev->speed = SPEED_1000; -				else if ((!phydev->autoneg) && -						(val & BMCR_SPEED100)) +				else if (!phydev->autoneg && +					 (val & BMCR_SPEED100))  					phydev->speed = SPEED_100;  				break;  			case MII_ADVERTISE: @@ -350,15 +370,13 @@ int phy_mii_ioctl(struct phy_device *phydev,  			}  		} -		phy_write(phydev, mii_data->reg_num, val); -		 +		mdiobus_write(phydev->bus, mii_data->phy_id, +			      mii_data->reg_num, val); +  		if (mii_data->reg_num == MII_BMCR && -		    val & BMCR_RESET && -		    phydev->drv->config_init) { -			phy_scan_fixups(phydev); -			phydev->drv->config_init(phydev); -		} -		break; +		    val & BMCR_RESET) +			return phy_init_hw(phydev); +		return 0;  	case SIOCSHWTSTAMP:  		if (phydev->drv->hwtstamp) @@ -368,8 +386,6 @@ int phy_mii_ioctl(struct phy_device *phydev,  	default:  		return -EOPNOTSUPP;  	} - -	return 0;  }  EXPORT_SYMBOL(phy_mii_ioctl); @@ -392,7 +408,6 @@ int phy_start_aneg(struct phy_device *phydev)  		phy_sanitize_settings(phydev);  	err = phydev->drv->config_aneg(phydev); -  	if (err < 0)  		goto out_unlock; @@ -412,28 +427,19 @@ out_unlock:  }  EXPORT_SYMBOL(phy_start_aneg); - -static void phy_change(struct work_struct *work); -  /**   * phy_start_machine - start PHY state machine tracking   * @phydev: the phy_device struct - * @handler: callback function for state change notifications   *   * 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 @handler, otherwise, pass NULL.  If you - *   want to maintain your own state machine, do not call this - *   function. + *   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, -		void (*handler)(struct net_device *)) +void phy_start_machine(struct phy_device *phydev)  { -	phydev->adjust_state = handler; - -	schedule_delayed_work(&phydev->state_queue, HZ); +	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ);  }  /** @@ -452,39 +458,9 @@ void phy_stop_machine(struct phy_device *phydev)  	if (phydev->state > PHY_UP)  		phydev->state = PHY_UP;  	mutex_unlock(&phydev->lock); - -	phydev->adjust_state = NULL;  }  /** - * phy_force_reduction - reduce PHY speed/duplex settings by one step - * @phydev: target phy_device struct - * - * Description: Reduces the speed/duplex settings by one notch, - *   in this order-- - *   1000/FULL, 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF. - *   The function bottoms out at 10/HALF. - */ -static void phy_force_reduction(struct phy_device *phydev) -{ -	int idx; - -	idx = phy_find_setting(phydev->speed, phydev->duplex); -	 -	idx++; - -	idx = phy_find_valid(idx, phydev->supported); - -	phydev->speed = settings[idx].speed; -	phydev->duplex = settings[idx].duplex; - -	pr_info("Trying %d/%s\n", phydev->speed, -			DUPLEX_FULL == phydev->duplex ? -			"FULL" : "HALF"); -} - - -/**   * phy_error - enter HALTED state for this PHY device   * @phydev: target phy_device struct   * @@ -518,11 +494,12 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)  	/* The MDIO bus is not allowed to be written in interrupt  	 * context, so we need to disable the irq here.  A work  	 * queue will write the PHY to disable and clear the -	 * interrupt, and then reenable the irq line. */ +	 * 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;  } @@ -533,16 +510,12 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)   */  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);  }  /** @@ -555,13 +528,11 @@ static int phy_disable_interrupts(struct phy_device *phydev)  	/* 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; @@ -585,25 +556,16 @@ phy_err:   */  int phy_start_interrupts(struct phy_device *phydev)  { -	int err = 0; - -	INIT_WORK(&phydev->phy_queue, phy_change); -  	atomic_set(&phydev->irq_disable, 0); -	if (request_irq(phydev->irq, phy_interrupt, -				IRQF_SHARED, -				"phy_interrupt", -				phydev) < 0) { -		printk(KERN_WARNING "%s: Can't get IRQ %d (PHY)\n", -				phydev->bus->name, -				phydev->irq); +	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); @@ -613,24 +575,20 @@ EXPORT_SYMBOL(phy_start_interrupts);   */  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 +	/* 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 +	/* 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.  	 */ @@ -641,14 +599,12 @@ int phy_stop_interrupts(struct phy_device *phydev)  }  EXPORT_SYMBOL(phy_stop_interrupts); -  /**   * phy_change - Scheduled by the phy_interrupt/timer to handle PHY changes   * @work: work_struct that describes the work to be done   */ -static void phy_change(struct work_struct *work) +void phy_change(struct work_struct *work)  { -	int err;  	struct phy_device *phydev =  		container_of(work, struct phy_device, phy_queue); @@ -656,9 +612,7 @@ static void phy_change(struct work_struct *work)  	    !phydev->drv->did_interrupt(phydev))  		goto ignore; -	err = phy_disable_interrupts(phydev); - -	if (err) +	if (phy_disable_interrupts(phydev))  		goto phy_err;  	mutex_lock(&phydev->lock); @@ -670,16 +624,13 @@ static void phy_change(struct work_struct *work)  	enable_irq(phydev->irq);  	/* Reenable interrupts */ -	if (PHY_HALTED != phydev->state) -		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); -	schedule_delayed_work(&phydev->state_queue, 0); - +	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, 0);  	return;  ignore: @@ -705,7 +656,7 @@ void phy_stop(struct phy_device *phydev)  	if (PHY_HALTED == phydev->state)  		goto out_unlock; -	if (phydev->irq != PHY_POLL) { +	if (phy_interrupt_is_valid(phydev)) {  		/* Disable PHY Interrupts */  		phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED); @@ -718,13 +669,12 @@ void phy_stop(struct phy_device *phydev)  out_unlock:  	mutex_unlock(&phydev->lock); -	/* -	 * Cannot call flush_scheduled_work() here as desired because +	/* 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 - start or restart a PHY device @@ -741,20 +691,19 @@ void phy_start(struct phy_device *phydev)  	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;  	}  	mutex_unlock(&phydev->lock);  } -EXPORT_SYMBOL(phy_stop);  EXPORT_SYMBOL(phy_start);  /** @@ -766,180 +715,142 @@ void phy_state_machine(struct work_struct *work)  	struct delayed_work *dwork = to_delayed_work(work);  	struct phy_device *phydev =  			container_of(dwork, struct phy_device, state_queue); -	int needs_aneg = 0; +	bool needs_aneg = false, do_suspend = false, do_resume = false;  	int err = 0;  	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: -			break; -		case PHY_UP: -			needs_aneg = 1; +	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; +		phydev->link_timeout = PHY_AN_TIMEOUT; +		break; +	case PHY_AN: +		err = phy_read_status(phydev); +		if (err < 0)  			break; -		case PHY_AN: -			err = phy_read_status(phydev); -			if (err < 0) -				break; - -			/* 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--) { -				int idx; - -				needs_aneg = 1; -				/* If we have the magic_aneg bit, -				 * we try again */ -				if (phydev->drv->flags & PHY_HAS_MAGICANEG) -					break; - -				/* The timer expired, and we still -				 * don't have a setting, so we try -				 * forcing it until we find one that -				 * works, starting from the fastest speed, -				 * and working our way down */ -				idx = phy_find_valid(0, phydev->supported); - -				phydev->speed = settings[idx].speed; -				phydev->duplex = settings[idx].duplex; - -				phydev->autoneg = AUTONEG_DISABLE; +		/* 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; +		} -				pr_info("Trying %d/%s\n", phydev->speed, -						DUPLEX_FULL == -						phydev->duplex ? -						"FULL" : "HALF"); -			} +		/* Check if negotiation is done.  Break if there's an error */ +		err = phy_aneg_done(phydev); +		if (err < 0)  			break; -		case PHY_NOLINK: -			err = phy_read_status(phydev); -			if (err) -				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); -			if (phydev->link) { -				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; -		case PHY_FORCING: -			err = genphy_update_link(phydev); -			if (err) -				break; +		if (phydev->link) { +			if (AUTONEG_ENABLE == phydev->autoneg) { +				err = phy_aneg_done(phydev); +				if (err < 0) +					break; -			if (phydev->link) { -				phydev->state = PHY_RUNNING; -				netif_carrier_on(phydev->attached_dev); -			} else { -				if (0 == phydev->link_timeout--) { -					phy_force_reduction(phydev); -					needs_aneg = 1; +				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; -		case PHY_RUNNING: -			/* Only register a CHANGE if we are -			 * polling */ -			if (PHY_POLL == phydev->irq) -				phydev->state = PHY_CHANGELINK; -			break; -		case PHY_CHANGELINK: -			err = phy_read_status(phydev); - -			if (err) -				break; -			if (phydev->link) { -				phydev->state = PHY_RUNNING; -				netif_carrier_on(phydev->attached_dev); -			} else { -				phydev->state = PHY_NOLINK; -				netif_carrier_off(phydev->attached_dev); -			} - -			phydev->adjust_link(phydev->attached_dev); +		if (phydev->link) { +			phydev->state = PHY_RUNNING; +			netif_carrier_on(phydev->attached_dev); +		} else { +			if (0 == phydev->link_timeout--) +				needs_aneg = true; +		} -			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); -			} +		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; -		case PHY_RESUMING: -			err = phy_clear_interrupt(phydev); +		if (phydev->link) { +			phydev->state = PHY_RUNNING; +			netif_carrier_on(phydev->attached_dev); +		} else { +			phydev->state = PHY_NOLINK; +			netif_carrier_off(phydev->attached_dev); +		} -			if (err) -				break; +		phydev->adjust_link(phydev->attached_dev); +		if (phy_interrupt_is_valid(phydev))  			err = phy_config_interrupt(phydev, -					PHY_INTERRUPT_ENABLED); +						   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; -			if (err) -				break; +		err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); +		if (err) +			break; -			if (AUTONEG_ENABLE == phydev->autoneg) { -				err = phy_aneg_done(phydev); -				if (err < 0) -					break; +		if (AUTONEG_ENABLE == phydev->autoneg) { +			err = phy_aneg_done(phydev); +			if (err < 0) +				break; -				/* err > 0 if AN is done. -				 * Otherwise, it's 0, and we're -				 * still waiting for AN */ -				if (err > 0) { -					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; -					phydev->adjust_link(phydev->attached_dev); -				} else { -					phydev->state = PHY_AN; -					phydev->link_timeout = PHY_AN_TIMEOUT; -				} -			} else { +			/* 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; @@ -947,20 +858,279 @@ void phy_state_machine(struct work_struct *work)  				if (phydev->link) {  					phydev->state = PHY_RUNNING;  					netif_carrier_on(phydev->attached_dev); -				} else +				} else	{  					phydev->state = PHY_NOLINK; +				}  				phydev->adjust_link(phydev->attached_dev); +			} else { +				phydev->state = PHY_AN; +				phydev->link_timeout = PHY_AN_TIMEOUT;  			} -			break; +		} else { +			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; +			} +			phydev->adjust_link(phydev->attached_dev); +		} +		do_resume = true; +		break;  	}  	mutex_unlock(&phydev->lock);  	if (needs_aneg)  		err = phy_start_aneg(phydev); +	else if (do_suspend) +		phy_suspend(phydev); +	else if (do_resume) +		phy_resume(phydev);  	if (err < 0)  		phy_error(phydev); -	schedule_delayed_work(&phydev->state_queue, PHY_STATE_TIME * HZ); +	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, +			   PHY_STATE_TIME * HZ); +} + +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); + +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); + +	/* Write the desired MMD register address */ +	bus->write(bus, addr, MII_MMD_DATA, prtad); + +	/* Select the Function : DATA with no post increment */ +	bus->write(bus, addr, MII_MMD_CTRL, (devad | MII_MMD_CTRL_NOINCR)); +} + +/** + * 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); + +	/* Read the content of the MMD's selected register */ +	return bus->read(bus, addr, MII_MMD_DATA); +} + +/** + * 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); + +	/* Write the data into MMD's selected register */ +	bus->write(bus, addr, MII_MMD_DATA, data); +} + +/** + * 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 */ +	} + +	return -EPROTONOSUPPORT; +} +EXPORT_SYMBOL(phy_init_eee); + +/** + * 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); + +/** + * 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); + +	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 993c52c82ae..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,6 +11,9 @@   * option) any later version.   *   */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kernel.h>  #include <linux/string.h>  #include <linux/errno.h> @@ -30,10 +30,12 @@  #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"); @@ -41,40 +43,40 @@ MODULE_LICENSE("GPL");  void phy_device_free(struct phy_device *phydev)  { -	kfree(phydev); +	put_device(&phydev->dev);  }  EXPORT_SYMBOL(phy_device_free);  static void phy_device_release(struct device *dev)  { -	phy_device_free(to_phy_device(dev)); +	kfree(to_phy_device(dev));  } -static struct phy_driver genphy_driver; -extern int mdio_bus_init(void); -extern void mdio_bus_exit(void); +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); -static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, -			     u32 flags, phy_interface_t interface); - -/* - * Creates a new phy_fixup and adds it to the list +/** + * 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 + *	It can also be PHY_ANY_UID   * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before - * 	comparison + *	comparison   * @run: The actual code to be run when a matching PHY is found   */  int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, -		int (*run)(struct phy_device *)) +		       int (*run)(struct phy_device *))  { -	struct phy_fixup *fixup; +	struct phy_fixup *fixup = kzalloc(sizeof(*fixup), GFP_KERNEL); -	fixup = kzalloc(sizeof(struct phy_fixup), GFP_KERNEL);  	if (!fixup)  		return -ENOMEM; @@ -93,7 +95,7 @@ EXPORT_SYMBOL(phy_register_fixup);  /* 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 *)) +			       int (*run)(struct phy_device *))  {  	return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run);  } @@ -101,14 +103,13 @@ EXPORT_SYMBOL(phy_register_fixup_for_uid);  /* 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 *)) +			      int (*run)(struct phy_device *))  {  	return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run);  }  EXPORT_SYMBOL(phy_register_fixup_for_id); -/* - * Returns 1 if fixup matches phydev in bus_id and phy_uid. +/* 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) @@ -118,7 +119,7 @@ static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup)  			return 0;  	if ((fixup->phy_uid & fixup->phy_uid_mask) != -			(phydev->phy_id & fixup->phy_uid_mask)) +	    (phydev->phy_id & fixup->phy_uid_mask))  		if (fixup->phy_uid != PHY_ANY_UID)  			return 0; @@ -126,53 +127,54 @@ static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup)  }  /* Runs any matching fixups for this phydev */ -int phy_scan_fixups(struct phy_device *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; - -			err = fixup->run(phydev); +			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;  } -EXPORT_SYMBOL(phy_scan_fixups); -static struct phy_device* phy_device_create(struct mii_bus *bus, -					    int addr, int phy_id) +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 */ +	/* We allocate the device, and initialize the default values */  	dev = kzalloc(sizeof(*dev), GFP_KERNEL); -  	if (NULL == dev) -		return (struct phy_device*) PTR_ERR((void*)-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; @@ -183,46 +185,126 @@ static struct phy_device* phy_device_create(struct mii_bus *bus,  	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. */ +	 * 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.   * - * Description: Reads the ID registers of the PHY at @addr on the - *   @bus, stores it in @phy_id and returns zero on success.   */ -int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id) +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; -	/* Grab the bits from PHYIR1, and put them -	 * in the upper half */ -	phy_reg = bus->read(bus, addr, MII_PHYSID1); +	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 = bus->read(bus, addr, MII_PHYSID2); - +	phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);  	if (phy_reg < 0)  		return -EIO; @@ -230,23 +312,24 @@ int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id)  	return 0;  } -EXPORT_SYMBOL(get_phy_id);  /** - * get_phy_device - reads the specified PHY device and returns its @phy_device struct + * 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) +struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)  { -	struct phy_device *dev = NULL; -	u32 phy_id; +	struct phy_c45_device_ids c45_ids = {0}; +	u32 phy_id = 0;  	int r; -	r = get_phy_id(bus, addr, &phy_id); +	r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);  	if (r)  		return ERR_PTR(r); @@ -254,9 +337,7 @@ struct phy_device * get_phy_device(struct mii_bus *bus, int addr)  	if ((phy_id & 0x1fffffff) == 0x1fffffff)  		return NULL; -	dev = phy_device_create(bus, addr, phy_id); - -	return dev; +	return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);  }  EXPORT_SYMBOL(get_phy_device); @@ -268,18 +349,21 @@ int phy_device_register(struct phy_device *phydev)  {  	int err; -	/* Don't register a phy if one is already registered at this -	 * address */ +	/* 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 */ -	phy_scan_fixups(phydev); +	err = phy_scan_fixups(phydev); +	if (err) { +		pr_err("PHY %d failed to initialize\n", phydev->addr); +		goto out; +	} -	err = device_register(&phydev->dev); +	err = device_add(&phydev->dev);  	if (err) { -		pr_err("phy %d failed to register\n", phydev->addr); +		pr_err("PHY %d failed to add\n", phydev->addr);  		goto out;  	} @@ -320,7 +404,7 @@ EXPORT_SYMBOL(phy_find_first);   *   this function.   */  static void phy_prepare_link(struct phy_device *phydev, -		void (*handler)(struct net_device *)) +			     void (*handler)(struct net_device *))  {  	phydev->adjust_link = handler;  } @@ -330,21 +414,20 @@ static void phy_prepare_link(struct phy_device *phydev,   * @dev: the network device to connect   * @phydev: the pointer to the phy device   * @handler: callback function for state change notifications - * @flags: PHY device's dev_flags   * @interface: PHY device's interface   */  int phy_connect_direct(struct net_device *dev, struct phy_device *phydev, -		       void (*handler)(struct net_device *), u32 flags, +		       void (*handler)(struct net_device *),  		       phy_interface_t interface)  {  	int rc; -	rc = phy_attach_direct(dev, phydev, flags, interface); +	rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);  	if (rc)  		return rc;  	phy_prepare_link(phydev, handler); -	phy_start_machine(phydev, NULL); +	phy_start_machine(phydev);  	if (phydev->irq > 0)  		phy_start_interrupts(phydev); @@ -357,7 +440,6 @@ EXPORT_SYMBOL(phy_connect_direct);   * @dev: the network device to connect   * @bus_id: the id string of the PHY device to connect   * @handler: callback function for state change notifications - * @flags: PHY device's dev_flags   * @interface: PHY device's interface   *   * Description: Convenience function for connecting ethernet @@ -368,16 +450,17 @@ EXPORT_SYMBOL(phy_connect_direct);   *   choose to call only the subset of functions which provide   *   the desired functionality.   */ -struct phy_device * phy_connect(struct net_device *dev, const char *bus_id, -		void (*handler)(struct net_device *), u32 flags, -		phy_interface_t interface) +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;  	/* Search the list of PHY devices on the mdio bus for the -	 * PHY with the requested name */ +	 * 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); @@ -385,7 +468,7 @@ struct phy_device * phy_connect(struct net_device *dev, const char *bus_id,  	}  	phydev = to_phy_device(d); -	rc = phy_connect_direct(dev, phydev, handler, flags, interface); +	rc = phy_connect_direct(dev, phydev, handler, interface);  	if (rc)  		return ERR_PTR(rc); @@ -394,7 +477,8 @@ struct phy_device * phy_connect(struct net_device *dev, const char *bus_id,  EXPORT_SYMBOL(phy_connect);  /** - * phy_disconnect - disable interrupts, stop state machine, and detach a PHY device + * phy_disconnect - disable interrupts, stop state machine, and detach a PHY + *		    device   * @phydev: target phy_device struct   */  void phy_disconnect(struct phy_device *phydev) @@ -403,26 +487,75 @@ void phy_disconnect(struct phy_device *phydev)  		phy_stop_interrupts(phydev);  	phy_stop_machine(phydev); -	 +  	phydev->adjust_link = NULL;  	phy_detach(phydev);  }  EXPORT_SYMBOL(phy_disconnect); -int phy_init_hw(struct phy_device *phydev) +/** + * phy_poll_reset - Safely wait until a PHY reset has properly completed + * @phydev: The PHY device to poll + * + * 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_poll_reset(struct phy_device *phydev)  { +	/* 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; +} + +int phy_init_hw(struct phy_device *phydev) +{ +	int ret = 0; +  	if (!phydev->drv || !phydev->drv->config_init)  		return 0; +	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 @@ -433,21 +566,26 @@ int phy_init_hw(struct phy_device *phydev)   *   * 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 + *     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.   */ -static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, -			     u32 flags, phy_interface_t interface) +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; -		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) @@ -462,6 +600,14 @@ static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,  		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; @@ -473,22 +619,29 @@ static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,  	/* Do initial configuration here, now that  	 * we have certain key parameters -	 * (dev_flags and interface) */ -	return phy_init_hw(phydev); +	 * (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 - * @flags: PHY device's dev_flags   * @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, u32 flags, phy_interface_t interface) +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; @@ -496,7 +649,8 @@ struct phy_device *phy_attach(struct net_device *dev,  	int rc;  	/* Search the list of PHY devices on the mdio bus for the -	 * PHY with the requested name */ +	 * 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); @@ -504,7 +658,7 @@ struct phy_device *phy_attach(struct net_device *dev,  	}  	phydev = to_phy_device(d); -	rc = phy_attach_direct(dev, phydev, flags, interface); +	rc = phy_attach_direct(dev, phydev, phydev->dev_flags, interface);  	if (rc)  		return ERR_PTR(rc); @@ -518,23 +672,57 @@ EXPORT_SYMBOL(phy_attach);   */  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) -		device_release_driver(&phydev->dev); +	 * 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 - sanitize and advertise auto-negotation parameters + * genphy_config_advert - sanitize and advertise auto-negotiation parameters   * @phydev: target phy_device struct   *   * Description: Writes MII_ADVERTISE with the appropriate values, @@ -545,34 +733,22 @@ EXPORT_SYMBOL(phy_detach);  static int genphy_config_advert(struct phy_device *phydev)  {  	u32 advertise; -	int oldadv, adv; +	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 */ -	oldadv = adv = phy_read(phydev, MII_ADVERTISE); - +	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; +	adv |= ethtool_adv_to_mii_adv_t(advertise);  	if (adv != oldadv) {  		err = phy_write(phydev, MII_ADVERTISE, adv); @@ -582,29 +758,36 @@ static int genphy_config_advert(struct phy_device *phydev)  		changed = 1;  	} -	/* Configure gigabit if it's supported */ -	if (phydev->supported & (SUPPORTED_1000baseT_Half | -				SUPPORTED_1000baseT_Full)) { -		oldadv = adv = phy_read(phydev, MII_CTRL1000); +	bmsr = phy_read(phydev, MII_BMSR); +	if (bmsr < 0) +		return bmsr; -		if (adv < 0) -			return 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; -		adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF); -		if (advertise & SUPPORTED_1000baseT_Half) -			adv |= ADVERTISE_1000HALF; -		if (advertise & SUPPORTED_1000baseT_Full) -			adv |= ADVERTISE_1000FULL; +	/* Configure gigabit if it's supported */ +	adv = phy_read(phydev, MII_CTRL1000); +	if (adv < 0) +		return adv; -		if (adv != oldadv) { -			err = phy_write(phydev, MII_CTRL1000, adv); +	oldadv = adv; +	adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF); -			if (err < 0) -				return err; +	if (phydev->supported & (SUPPORTED_1000baseT_Half | +				 SUPPORTED_1000baseT_Full)) { +		adv |= ethtool_adv_to_mii_ctrl1000_t(advertise); +		if (adv != oldadv)  			changed = 1; -		}  	} +	err = phy_write(phydev, MII_CTRL1000, adv); +	if (err < 0) +		return err; +  	return changed;  } @@ -616,12 +799,12 @@ static int genphy_config_advert(struct phy_device *phydev)   *   to the values in phydev. Assumes that the values are valid.   *   Please see phy_sanitize_settings().   */ -static int genphy_setup_forced(struct phy_device *phydev) +int genphy_setup_forced(struct phy_device *phydev)  { -	int err;  	int ctl = 0; -	phydev->pause = phydev->asym_pause = 0; +	phydev->pause = 0; +	phydev->asym_pause = 0;  	if (SPEED_1000 == phydev->speed)  		ctl |= BMCR_SPEED1000; @@ -630,12 +813,10 @@ static int genphy_setup_forced(struct phy_device *phydev)  	if (DUPLEX_FULL == phydev->duplex)  		ctl |= BMCR_FULLDPLX; -	 -	err = phy_write(phydev, MII_BMCR, ctl); -	return err; +	return phy_write(phydev, MII_BMCR, ctl);  } - +EXPORT_SYMBOL(genphy_setup_forced);  /**   * genphy_restart_aneg - Enable and Restart Autonegotiation @@ -643,25 +824,20 @@ static int genphy_setup_forced(struct phy_device *phydev)   */  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 - restart auto-negotiation or write BMCR   * @phydev: target phy_device struct @@ -678,13 +854,12 @@ int genphy_config_aneg(struct phy_device *phydev)  		return genphy_setup_forced(phydev);  	result = genphy_config_advert(phydev); -  	if (result < 0) /* error */  		return result; -  	if (result == 0) { -		/* Advertisment hasn't changed, but maybe aneg was never on to -		 * begin with?  Or maybe phy was isolated? */ +		/* 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);  		if (ctl < 0) @@ -695,7 +870,8 @@ int genphy_config_aneg(struct phy_device *phydev)  	}  	/* Only restart aneg if we are advertising something different -	 * than we were before.	 */ +	 * than we were before. +	 */  	if (result > 0)  		result = genphy_restart_aneg(phydev); @@ -704,6 +880,27 @@ int genphy_config_aneg(struct phy_device *phydev)  EXPORT_SYMBOL(genphy_config_aneg);  /** + * 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   * @@ -717,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; @@ -751,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; @@ -825,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) +{ +	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; @@ -863,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; @@ -873,11 +1118,28 @@ 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; @@ -885,7 +1147,7 @@ int genphy_suspend(struct phy_device *phydev)  	mutex_lock(&phydev->lock);  	value = phy_read(phydev, MII_BMCR); -	phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN)); +	phy_write(phydev, MII_BMCR, value | BMCR_PDOWN);  	mutex_unlock(&phydev->lock); @@ -893,6 +1155,11 @@ int genphy_suspend(struct phy_device *phydev)  }  EXPORT_SYMBOL(genphy_suspend); +static int gen10g_suspend(struct phy_device *phydev) +{ +	return 0; +} +  int genphy_resume(struct phy_device *phydev)  {  	int value; @@ -900,7 +1167,7 @@ int genphy_resume(struct phy_device *phydev)  	mutex_lock(&phydev->lock);  	value = phy_read(phydev, MII_BMCR); -	phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN)); +	phy_write(phydev, MII_BMCR, value & ~BMCR_PDOWN);  	mutex_unlock(&phydev->lock); @@ -908,6 +1175,43 @@ int genphy_resume(struct phy_device *phydev)  }  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 - probe and init a PHY device   * @dev: device to probe and init @@ -918,30 +1222,32 @@ EXPORT_SYMBOL(genphy_resume);   */  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; +	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; @@ -952,14 +1258,11 @@ static int phy_probe(struct device *dev)  	mutex_unlock(&phydev->lock);  	return err; -  }  static int phy_remove(struct device *dev)  { -	struct phy_device *phydev; - -	phydev = to_phy_device(dev); +	struct phy_device *phydev = to_phy_device(dev);  	mutex_lock(&phydev->lock);  	phydev->state = PHY_DOWN; @@ -967,8 +1270,6 @@ static int phy_remove(struct device *dev)  	if (phydev->drv->remove)  		phydev->drv->remove(phydev); - -	put_driver(dev->driver);  	phydev->drv = NULL;  	return 0; @@ -988,10 +1289,9 @@ int phy_driver_register(struct phy_driver *new_driver)  	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;  	} @@ -1002,24 +1302,66 @@ int phy_driver_register(struct phy_driver *new_driver)  }  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,  	.suspend	= genphy_suspend,  	.resume		= genphy_resume, -	.driver		= {.owner= THIS_MODULE, }, -}; +	.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)  { @@ -1029,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(); @@ -1038,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/realtek.c b/drivers/net/phy/realtek.c index a4eae750a41..45483fdfbe0 100644 --- a/drivers/net/phy/realtek.c +++ b/drivers/net/phy/realtek.c @@ -14,6 +14,7 @@   *   */  #include <linux/phy.h> +#include <linux/module.h>  #define RTL821x_PHYSR		0x11  #define RTL821x_PHYSR_DUPLEX	0x2000 @@ -22,6 +23,8 @@  #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"); @@ -35,7 +38,7 @@ static int rtl821x_ack_interrupt(struct phy_device *phydev)  	return (err < 0) ? err : 0;  } -static int rtl821x_config_intr(struct phy_device *phydev) +static int rtl8211b_config_intr(struct phy_device *phydev)  {  	int err; @@ -48,32 +51,64 @@ static int rtl821x_config_intr(struct phy_device *phydev)  	return err;  } -/* RTL8211B */ -static struct phy_driver rtl821x_driver = { -	.phy_id		= 0x001cc912, -	.name		= "RTL821x 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	= &rtl821x_config_intr, -	.driver		= { .owner = THIS_MODULE,}, +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)  { -	int ret; - -	ret = phy_driver_register(&rtl821x_driver); - -	return ret; +	return phy_drivers_register(realtek_drvs, ARRAY_SIZE(realtek_drvs));  }  static void __exit realtek_exit(void)  { -	phy_driver_unregister(&rtl821x_driver); +	phy_drivers_unregister(realtek_drvs, ARRAY_SIZE(realtek_drvs));  }  module_init(realtek_init); @@ -81,6 +116,7 @@ module_exit(realtek_exit);  static struct mdio_device_id __maybe_unused realtek_tbl[] = {  	{ 0x001cc912, 0x001fffff }, +	{ 0x001cc915, 0x001fffff },  	{ }  }; diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c index 342505c976d..180c49479c4 100644 --- a/drivers/net/phy/smsc.c +++ b/drivers/net/phy/smsc.c @@ -12,7 +12,7 @@   * 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@smsc.com + * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net   *   */ @@ -22,26 +22,7 @@  #include <linux/ethtool.h>  #include <linux/phy.h>  #include <linux/netdevice.h> - -#define MII_LAN83C185_ISF 29 /* Interrupt Source Flags */ -#define MII_LAN83C185_IM  30 /* Interrupt Mask */ -#define MII_LAN83C185_CTRL_STATUS 17 /* Mode/Status Register */ - -#define MII_LAN83C185_ISF_INT1 (1<<1) /* Auto-Negotiation Page Received */ -#define MII_LAN83C185_ISF_INT2 (1<<2) /* Parallel Detection Fault */ -#define MII_LAN83C185_ISF_INT3 (1<<3) /* Auto-Negotiation LP Ack */ -#define MII_LAN83C185_ISF_INT4 (1<<4) /* Link Down */ -#define MII_LAN83C185_ISF_INT5 (1<<5) /* Remote Fault Detected */ -#define MII_LAN83C185_ISF_INT6 (1<<6) /* Auto-Negotiation complete */ -#define MII_LAN83C185_ISF_INT7 (1<<7) /* ENERGYON */ - -#define MII_LAN83C185_ISF_INT_ALL (0x0e) - -#define MII_LAN83C185_ISF_INT_PHYLIB_EVENTS \ -	(MII_LAN83C185_ISF_INT6 | MII_LAN83C185_ISF_INT4 | \ -	 MII_LAN83C185_ISF_INT7) - -#define MII_LAN83C185_EDPWRDOWN	(1 << 13) /* EDPWRDOWN */ +#include <linux/smscphy.h>  static int smsc_phy_config_intr(struct phy_device *phydev)  { @@ -62,7 +43,31 @@ static int smsc_phy_ack_interrupt(struct phy_device *phydev)  static int smsc_phy_config_init(struct phy_device *phydev)  { -	int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS); +	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; @@ -80,7 +85,51 @@ static int lan911x_config_init(struct phy_device *phydev)  	return smsc_phy_ack_interrupt(phydev);  } -static struct phy_driver lan83c185_driver = { +/* + * 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", @@ -102,9 +151,7 @@ static struct phy_driver lan83c185_driver = {  	.resume		= genphy_resume,  	.driver		= { .owner = THIS_MODULE, } -}; - -static struct phy_driver lan8187_driver = { +}, {  	.phy_id		= 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */  	.phy_id_mask	= 0xfffffff0,  	.name		= "SMSC LAN8187", @@ -126,9 +173,7 @@ static struct phy_driver lan8187_driver = {  	.resume		= genphy_resume,  	.driver		= { .owner = THIS_MODULE, } -}; - -static struct phy_driver lan8700_driver = { +}, {  	.phy_id		= 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */  	.phy_id_mask	= 0xfffffff0,  	.name		= "SMSC LAN8700", @@ -150,9 +195,7 @@ static struct phy_driver lan8700_driver = {  	.resume		= genphy_resume,  	.driver		= { .owner = THIS_MODULE, } -}; - -static struct phy_driver lan911x_int_driver = { +}, {  	.phy_id		= 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */  	.phy_id_mask	= 0xfffffff0,  	.name		= "SMSC LAN911x Internal PHY", @@ -174,9 +217,7 @@ static struct phy_driver lan911x_int_driver = {  	.resume		= genphy_resume,  	.driver		= { .owner = THIS_MODULE, } -}; - -static struct phy_driver lan8710_driver = { +}, {  	.phy_id		= 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */  	.phy_id_mask	= 0xfffffff0,  	.name		= "SMSC LAN8710/LAN8720", @@ -187,7 +228,7 @@ static struct phy_driver lan8710_driver = {  	/* basic functions */  	.config_aneg	= genphy_config_aneg, -	.read_status	= genphy_read_status, +	.read_status	= lan87xx_read_status,  	.config_init	= smsc_phy_config_init,  	/* IRQ related */ @@ -198,53 +239,17 @@ static struct phy_driver lan8710_driver = {  	.resume		= genphy_resume,  	.driver		= { .owner = THIS_MODULE, } -}; +} };  static int __init smsc_init(void)  { -	int ret; - -	ret = phy_driver_register (&lan83c185_driver); -	if (ret) -		goto err1; - -	ret = phy_driver_register (&lan8187_driver); -	if (ret) -		goto err2; - -	ret = phy_driver_register (&lan8700_driver); -	if (ret) -		goto err3; - -	ret = phy_driver_register (&lan911x_int_driver); -	if (ret) -		goto err4; - -	ret = phy_driver_register (&lan8710_driver); -	if (ret) -		goto err5; - -	return 0; - -err5: -	phy_driver_unregister (&lan911x_int_driver); -err4: -	phy_driver_unregister (&lan8700_driver); -err3: -	phy_driver_unregister (&lan8187_driver); -err2: -	phy_driver_unregister (&lan83c185_driver); -err1: -	return ret; +	return phy_drivers_register(smsc_phy_driver, +		ARRAY_SIZE(smsc_phy_driver));  }  static void __exit smsc_exit(void)  { -	phy_driver_unregister (&lan8710_driver); -	phy_driver_unregister (&lan911x_int_driver); -	phy_driver_unregister (&lan8700_driver); -	phy_driver_unregister (&lan8187_driver); -	phy_driver_unregister (&lan83c185_driver); +	phy_drivers_unregister(smsc_phy_driver, ARRAY_SIZE(smsc_phy_driver));  }  MODULE_DESCRIPTION("SMSC PHY driver"); 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 index 187a2fa814f..5e1eb138916 100644 --- a/drivers/net/phy/ste10Xp.c +++ b/drivers/net/phy/ste10Xp.c @@ -81,7 +81,8 @@ static int ste10Xp_ack_interrupt(struct phy_device *phydev)  	return 0;  } -static struct phy_driver ste101p_pdriver = { +static struct phy_driver ste10xp_pdriver[] = { +{  	.phy_id = STE101P_PHY_ID,  	.phy_id_mask = 0xfffffff0,  	.name = "STe101p", @@ -95,9 +96,7 @@ static struct phy_driver ste101p_pdriver = {  	.suspend = genphy_suspend,  	.resume = genphy_resume,  	.driver = {.owner = THIS_MODULE,} -}; - -static struct phy_driver ste100p_pdriver = { +}, {  	.phy_id = STE100P_PHY_ID,  	.phy_id_mask = 0xffffffff,  	.name = "STe100p", @@ -111,22 +110,18 @@ static struct phy_driver ste100p_pdriver = {  	.suspend = genphy_suspend,  	.resume = genphy_resume,  	.driver = {.owner = THIS_MODULE,} -}; +} };  static int __init ste10Xp_init(void)  { -	int retval; - -	retval = phy_driver_register(&ste100p_pdriver); -	if (retval < 0) -		return retval; -	return phy_driver_register(&ste101p_pdriver); +	return phy_drivers_register(ste10xp_pdriver, +		ARRAY_SIZE(ste10xp_pdriver));  }  static void __exit ste10Xp_exit(void)  { -	phy_driver_unregister(&ste100p_pdriver); -	phy_driver_unregister(&ste101p_pdriver); +	phy_drivers_unregister(ste10xp_pdriver, +		ARRAY_SIZE(ste10xp_pdriver));  }  module_init(ste10Xp_init); diff --git a/drivers/net/phy/vitesse.c b/drivers/net/phy/vitesse.c index 5d8f6e17bd5..5dc0935da99 100644 --- a/drivers/net/phy/vitesse.c +++ b/drivers/net/phy/vitesse.c @@ -3,7 +3,7 @@   *   * Author: Kriston Carson   * - * Copyright (c) 2005 Freescale Semiconductor, Inc. + * 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 @@ -18,6 +18,11 @@  #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 @@ -44,56 +49,72 @@  #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_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_config_init(struct phy_device *phydev) +static int vsc824x_add_skew(struct phy_device *phydev)  { -	int extcon;  	int err; - -	err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT, -			MII_VSC8244_AUXCONSTAT_INIT); -	if (err < 0) -		return err; +	int extcon;  	extcon = phy_read(phydev, MII_VSC8244_EXT_CON1);  	if (extcon < 0) -		return err; +		return extcon;  	extcon &= ~(MII_VSC8244_EXTCON1_TX_SKEW_MASK |  			MII_VSC8244_EXTCON1_RX_SKEW_MASK); -	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) -		extcon |= (MII_VSC8244_EXTCON1_TX_SKEW | -				MII_VSC8244_EXTCON1_RX_SKEW); +	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 + +	/* Don't bother to ACK the interrupts if interrupts  	 * are disabled.  The 824x cannot clear the interrupts  	 * if they are disabled.  	 */ @@ -109,12 +130,14 @@ static int vsc82xx_config_intr(struct phy_device *phydev)  	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)  		err = phy_write(phydev, MII_VSC8244_IMASK, -			phydev->drv->phy_id == PHY_ID_VSC8244 ? +			(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 +		/* The Vitesse PHY cannot clear the interrupt  		 * once it has disabled them, so we clear them first  		 */  		err = phy_read(phydev, MII_VSC8244_ISTAT); @@ -128,21 +151,6 @@ static int vsc82xx_config_intr(struct phy_device *phydev)  	return err;  } -/* Vitesse 824x */ -static struct phy_driver vsc8244_driver = { -	.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	= &genphy_config_aneg, -	.read_status	= &genphy_read_status, -	.ack_interrupt	= &vsc824x_ack_interrupt, -	.config_intr	= &vsc82xx_config_intr, -	.driver 	= { .owner = THIS_MODULE,}, -}; -  static int vsc8221_config_init(struct phy_device *phydev)  {  	int err; @@ -152,11 +160,131 @@ static int vsc8221_config_init(struct phy_device *phydev)  	return err;  	/* Perhaps we should set EXT_CON1 based on the interface? -	   Options are 802.3Z SerDes or SGMII */ +	 * 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 8221 */ -static struct phy_driver vsc8221_driver = { +/* 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", @@ -167,34 +295,44 @@ static struct phy_driver vsc8221_driver = {  	.read_status	= &genphy_read_status,  	.ack_interrupt	= &vsc824x_ack_interrupt,  	.config_intr	= &vsc82xx_config_intr, -	.driver 	= { .owner = THIS_MODULE,}, -}; +	.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)  { -	int err; - -	err = phy_driver_register(&vsc8244_driver); -	if (err < 0) -		return err; -	err = phy_driver_register(&vsc8221_driver); -	if (err < 0) -		phy_driver_unregister(&vsc8244_driver); -	return err; +	return phy_drivers_register(vsc82xx_driver, +		ARRAY_SIZE(vsc82xx_driver));  }  static void __exit vsc82xx_exit(void)  { -	phy_driver_unregister(&vsc8244_driver); -	phy_driver_unregister(&vsc8221_driver); +	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 },  	{ }  };  | 
