From 44fd1c1fd821955118ecb518f46076b98343e591 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Yadav Date: Wed, 19 Aug 2020 19:31:20 +0530 Subject: chelsio/chtls: separate chelsio tls driver from crypto driver chelsio inline tls driver(chtls) is mostly overlaps with NIC drivers but currenty it is part of crypto driver, so move it out to appropriate directory for better maintenance. Signed-off-by: Vinay Kumar Yadav Signed-off-by: David S. Miller --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index deaafb617361..57afa6532824 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4692,6 +4692,15 @@ S: Supported W: http://www.chelsio.com F: drivers/crypto/chelsio +CXGB4 INLINE CRYPTO DRIVER +M: Ayush Sawal +M: Vinay Kumar Yadav +M: Rohit Maheshwari +L: netdev@vger.kernel.org +S: Supported +W: http://www.chelsio.com +F: drivers/net/ethernet/chelsio/inline_crypto/ + CXGB4 ETHERNET DRIVER (CXGB4) M: Vishal Kulkarni L: netdev@vger.kernel.org -- cgit v1.2.3 From 2fa4e4b799e191530edbae4b96b85d4486e55053 Mon Sep 17 00:00:00 2001 From: Andrew Lunn Date: Thu, 27 Aug 2020 04:00:28 +0200 Subject: net: pcs: Move XPCS into new PCS subdirectory Create drivers/net/pcs and move the Synopsys DesignWare XPCS into the new directory. Move the header file into a subdirectory include/linux/pcs Start a naming convention of all PCS files use the prefix pcs-, and rename the XPCS files to fit. v2: Add include/linux/pcs v4: Fix include path in stmmac. Remove PCS_DEVICES to avoid new prompts Cc: Jose Abreu Reviewed-by: Florian Fainelli Signed-off-by: Andrew Lunn Signed-off-by: David S. Miller --- MAINTAINERS | 5 +- drivers/net/Kconfig | 2 + drivers/net/Makefile | 1 + drivers/net/ethernet/stmicro/stmmac/Kconfig | 2 +- drivers/net/ethernet/stmicro/stmmac/common.h | 2 +- drivers/net/pcs/Kconfig | 16 + drivers/net/pcs/Makefile | 4 + drivers/net/pcs/pcs-xpcs.c | 716 +++++++++++++++++++++++++++ drivers/net/phy/Kconfig | 6 - drivers/net/phy/Makefile | 1 - drivers/net/phy/mdio-xpcs.c | 716 --------------------------- include/linux/mdio-xpcs.h | 41 -- include/linux/pcs/pcs-xpcs.h | 41 ++ 13 files changed, 785 insertions(+), 768 deletions(-) create mode 100644 drivers/net/pcs/Kconfig create mode 100644 drivers/net/pcs/Makefile create mode 100644 drivers/net/pcs/pcs-xpcs.c delete mode 100644 drivers/net/phy/mdio-xpcs.c delete mode 100644 include/linux/mdio-xpcs.h create mode 100644 include/linux/pcs/pcs-xpcs.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 36ec0bd50a8f..347ed6904fdf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6513,6 +6513,7 @@ F: Documentation/devicetree/bindings/net/ethernet-phy.yaml F: Documentation/devicetree/bindings/net/mdio* F: Documentation/devicetree/bindings/net/qca,ar803x.yaml F: Documentation/networking/phy.rst +F: drivers/net/pcs/ F: drivers/net/phy/ F: drivers/of/of_mdio.c F: drivers/of/of_net.c @@ -16730,8 +16731,8 @@ SYNOPSYS DESIGNWARE ETHERNET XPCS DRIVER M: Jose Abreu L: netdev@vger.kernel.org S: Supported -F: drivers/net/phy/mdio-xpcs.c -F: include/linux/mdio-xpcs.h +F: drivers/net/pcs/pcs-xpcs.c +F: include/linux/pcs/pcs-xpcs.h SYNOPSYS DESIGNWARE I2C DRIVER M: Jarkko Nikula diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 1368d1d6a114..2b07566de78c 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -473,6 +473,8 @@ config NET_SB1000 source "drivers/net/phy/Kconfig" +source "drivers/net/pcs/Kconfig" + source "drivers/net/plip/Kconfig" source "drivers/net/ppp/Kconfig" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 94b60800887a..f7402d766b67 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MDIO) += mdio.o obj-$(CONFIG_NET) += Space.o loopback.o obj-$(CONFIG_NETCONSOLE) += netconsole.o obj-y += phy/ +obj-y += pcs/ obj-$(CONFIG_RIONET) += rionet.o obj-$(CONFIG_NET_TEAM) += team/ obj-$(CONFIG_TUN) += tun.o diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 7572cea9d59e..53f14c5a9e02 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -3,7 +3,7 @@ config STMMAC_ETH tristate "STMicroelectronics Multi-Gigabit Ethernet driver" depends on HAS_IOMEM && HAS_DMA select MII - select MDIO_XPCS + select PCS_XPCS select PAGE_POOL select PHYLINK select CRC32 diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 127f75862962..acc5e3fc1c2f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #if IS_ENABLED(CONFIG_VLAN_8021Q) #define STMMAC_VLAN_TAG_USED diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig new file mode 100644 index 000000000000..9d6e2be32060 --- /dev/null +++ b/drivers/net/pcs/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PCS Layer Configuration +# + +menu "PCS device drivers" + +config PCS_XPCS + tristate "Synopsys DesignWare XPCS controller" + select MDIO_BUS + depends on MDIO_DEVICE + help + This module provides helper functions for Synopsys DesignWare XPCS + controllers. + +endmenu diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile new file mode 100644 index 000000000000..f0480afc7157 --- /dev/null +++ b/drivers/net/pcs/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Linux PCS drivers + +obj-$(CONFIG_PCS_XPCS) += pcs-xpcs.o diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c new file mode 100644 index 000000000000..1aa9903d602e --- /dev/null +++ b/drivers/net/pcs/pcs-xpcs.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare XPCS helpers + * + * Author: Jose Abreu + */ + +#include +#include +#include +#include +#include + +#define SYNOPSYS_XPCS_USXGMII_ID 0x7996ced0 +#define SYNOPSYS_XPCS_10GKR_ID 0x7996ced0 +#define SYNOPSYS_XPCS_XLGMII_ID 0x7996ced0 +#define SYNOPSYS_XPCS_MASK 0xffffffff + +/* Vendor regs access */ +#define DW_VENDOR BIT(15) + +/* VR_XS_PCS */ +#define DW_USXGMII_RST BIT(10) +#define DW_USXGMII_EN BIT(9) +#define DW_VR_XS_PCS_DIG_STS 0x0010 +#define DW_RXFIFO_ERR GENMASK(6, 5) + +/* SR_MII */ +#define DW_USXGMII_FULL BIT(8) +#define DW_USXGMII_SS_MASK (BIT(13) | BIT(6) | BIT(5)) +#define DW_USXGMII_10000 (BIT(13) | BIT(6)) +#define DW_USXGMII_5000 (BIT(13) | BIT(5)) +#define DW_USXGMII_2500 (BIT(5)) +#define DW_USXGMII_1000 (BIT(6)) +#define DW_USXGMII_100 (BIT(13)) +#define DW_USXGMII_10 (0) + +/* SR_AN */ +#define DW_SR_AN_ADV1 0x10 +#define DW_SR_AN_ADV2 0x11 +#define DW_SR_AN_ADV3 0x12 +#define DW_SR_AN_LP_ABL1 0x13 +#define DW_SR_AN_LP_ABL2 0x14 +#define DW_SR_AN_LP_ABL3 0x15 + +/* Clause 73 Defines */ +/* AN_LP_ABL1 */ +#define DW_C73_PAUSE BIT(10) +#define DW_C73_ASYM_PAUSE BIT(11) +#define DW_C73_AN_ADV_SF 0x1 +/* AN_LP_ABL2 */ +#define DW_C73_1000KX BIT(5) +#define DW_C73_10000KX4 BIT(6) +#define DW_C73_10000KR BIT(7) +/* AN_LP_ABL3 */ +#define DW_C73_2500KX BIT(0) +#define DW_C73_5000KR BIT(1) + +static const int xpcs_usxgmii_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_10gkr_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_xlgmii_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const phy_interface_t xpcs_usxgmii_interfaces[] = { + PHY_INTERFACE_MODE_USXGMII, + PHY_INTERFACE_MODE_MAX, +}; + +static const phy_interface_t xpcs_10gkr_interfaces[] = { + PHY_INTERFACE_MODE_10GKR, + PHY_INTERFACE_MODE_MAX, +}; + +static const phy_interface_t xpcs_xlgmii_interfaces[] = { + PHY_INTERFACE_MODE_XLGMII, + PHY_INTERFACE_MODE_MAX, +}; + +static struct xpcs_id { + u32 id; + u32 mask; + const int *supported; + const phy_interface_t *interface; +} xpcs_id_list[] = { + { + .id = SYNOPSYS_XPCS_USXGMII_ID, + .mask = SYNOPSYS_XPCS_MASK, + .supported = xpcs_usxgmii_features, + .interface = xpcs_usxgmii_interfaces, + }, { + .id = SYNOPSYS_XPCS_10GKR_ID, + .mask = SYNOPSYS_XPCS_MASK, + .supported = xpcs_10gkr_features, + .interface = xpcs_10gkr_interfaces, + }, { + .id = SYNOPSYS_XPCS_XLGMII_ID, + .mask = SYNOPSYS_XPCS_MASK, + .supported = xpcs_xlgmii_features, + .interface = xpcs_xlgmii_interfaces, + }, +}; + +static int xpcs_read(struct mdio_xpcs_args *xpcs, int dev, u32 reg) +{ + u32 reg_addr = MII_ADDR_C45 | dev << 16 | reg; + + return mdiobus_read(xpcs->bus, xpcs->addr, reg_addr); +} + +static int xpcs_write(struct mdio_xpcs_args *xpcs, int dev, u32 reg, u16 val) +{ + u32 reg_addr = MII_ADDR_C45 | dev << 16 | reg; + + return mdiobus_write(xpcs->bus, xpcs->addr, reg_addr, val); +} + +static int xpcs_read_vendor(struct mdio_xpcs_args *xpcs, int dev, u32 reg) +{ + return xpcs_read(xpcs, dev, DW_VENDOR | reg); +} + +static int xpcs_write_vendor(struct mdio_xpcs_args *xpcs, int dev, int reg, + u16 val) +{ + return xpcs_write(xpcs, dev, DW_VENDOR | reg, val); +} + +static int xpcs_read_vpcs(struct mdio_xpcs_args *xpcs, int reg) +{ + return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg); +} + +static int xpcs_write_vpcs(struct mdio_xpcs_args *xpcs, int reg, u16 val) +{ + return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val); +} + +static int xpcs_poll_reset(struct mdio_xpcs_args *xpcs, int dev) +{ + /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ + unsigned int retries = 12; + int ret; + + do { + msleep(50); + ret = xpcs_read(xpcs, dev, MDIO_CTRL1); + if (ret < 0) + return ret; + } while (ret & MDIO_CTRL1_RESET && --retries); + + return (ret & MDIO_CTRL1_RESET) ? -ETIMEDOUT : 0; +} + +static int xpcs_soft_reset(struct mdio_xpcs_args *xpcs, int dev) +{ + int ret; + + ret = xpcs_write(xpcs, dev, MDIO_CTRL1, MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return xpcs_poll_reset(xpcs, dev); +} + +#define xpcs_warn(__xpcs, __state, __args...) \ +({ \ + if ((__state)->link) \ + dev_warn(&(__xpcs)->bus->dev, ##__args); \ +}) + +static int xpcs_read_fault(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1); + if (ret < 0) + return ret; + + if (ret & MDIO_STAT1_FAULT) { + xpcs_warn(xpcs, state, "Link fault condition detected!\n"); + return -EFAULT; + } + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2); + if (ret < 0) + return ret; + + if (ret & MDIO_STAT2_RXFAULT) + xpcs_warn(xpcs, state, "Receiver fault detected!\n"); + if (ret & MDIO_STAT2_TXFAULT) + xpcs_warn(xpcs, state, "Transmitter fault detected!\n"); + + ret = xpcs_read_vendor(xpcs, MDIO_MMD_PCS, DW_VR_XS_PCS_DIG_STS); + if (ret < 0) + return ret; + + if (ret & DW_RXFIFO_ERR) { + xpcs_warn(xpcs, state, "FIFO fault condition detected!\n"); + return -EFAULT; + } + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_PCS_10GBRT_STAT1_BLKLK)) + xpcs_warn(xpcs, state, "Link is not locked!\n"); + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT2); + if (ret < 0) + return ret; + + if (ret & MDIO_PCS_10GBRT_STAT2_ERR) { + xpcs_warn(xpcs, state, "Link has errors!\n"); + return -EFAULT; + } + + return 0; +} + +static int xpcs_read_link(struct mdio_xpcs_args *xpcs, bool an) +{ + bool link = true; + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_STAT1_LSTATUS)) + link = false; + + if (an) { + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_STAT1_LSTATUS)) + link = false; + } + + return link; +} + +static int xpcs_get_max_usxgmii_speed(const unsigned long *supported) +{ + int max = SPEED_UNKNOWN; + + if (phylink_test(supported, 1000baseKX_Full)) + max = SPEED_1000; + if (phylink_test(supported, 2500baseX_Full)) + max = SPEED_2500; + if (phylink_test(supported, 10000baseKX4_Full)) + max = SPEED_10000; + if (phylink_test(supported, 10000baseKR_Full)) + max = SPEED_10000; + + return max; +} + +static int xpcs_config_usxgmii(struct mdio_xpcs_args *xpcs, int speed) +{ + int ret, speed_sel; + + switch (speed) { + case SPEED_10: + speed_sel = DW_USXGMII_10; + break; + case SPEED_100: + speed_sel = DW_USXGMII_100; + break; + case SPEED_1000: + speed_sel = DW_USXGMII_1000; + break; + case SPEED_2500: + speed_sel = DW_USXGMII_2500; + break; + case SPEED_5000: + speed_sel = DW_USXGMII_5000; + break; + case SPEED_10000: + speed_sel = DW_USXGMII_10000; + break; + default: + /* Nothing to do here */ + return -EINVAL; + } + + ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_EN); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret &= ~DW_USXGMII_SS_MASK; + ret |= speed_sel | DW_USXGMII_FULL; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret); + if (ret < 0) + return ret; + + ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); + if (ret < 0) + return ret; + + return xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_RST); +} + +static int xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) +{ + int ret, adv; + + /* By default, in USXGMII mode XPCS operates at 10G baud and + * replicates data to achieve lower speeds. Hereby, in this + * default configuration we need to advertise all supported + * modes and not only the ones we want to use. + */ + + /* SR_AN_ADV3 */ + adv = 0; + if (phylink_test(xpcs->supported, 2500baseX_Full)) + adv |= DW_C73_2500KX; + + /* TODO: 5000baseKR */ + + ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV3, adv); + if (ret < 0) + return ret; + + /* SR_AN_ADV2 */ + adv = 0; + if (phylink_test(xpcs->supported, 1000baseKX_Full)) + adv |= DW_C73_1000KX; + if (phylink_test(xpcs->supported, 10000baseKX4_Full)) + adv |= DW_C73_10000KX4; + if (phylink_test(xpcs->supported, 10000baseKR_Full)) + adv |= DW_C73_10000KR; + + ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV2, adv); + if (ret < 0) + return ret; + + /* SR_AN_ADV1 */ + adv = DW_C73_AN_ADV_SF; + if (phylink_test(xpcs->supported, Pause)) + adv |= DW_C73_PAUSE; + if (phylink_test(xpcs->supported, Asym_Pause)) + adv |= DW_C73_ASYM_PAUSE; + + return xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV1, adv); +} + +static int xpcs_config_aneg(struct mdio_xpcs_args *xpcs) +{ + int ret; + + ret = xpcs_config_aneg_c73(xpcs); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART; + + return xpcs_write(xpcs, MDIO_MMD_AN, MDIO_CTRL1, ret); +} + +static int xpcs_aneg_done(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); + if (ret < 0) + return ret; + + if (ret & MDIO_AN_STAT1_COMPLETE) { + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL1); + if (ret < 0) + return ret; + + /* Check if Aneg outcome is valid */ + if (!(ret & DW_C73_AN_ADV_SF)) { + xpcs_config_aneg(xpcs); + return 0; + } + + return 1; + } + + return 0; +} + +static int xpcs_read_lpa(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_AN_STAT1_LPABLE)) { + phylink_clear(state->lp_advertising, Autoneg); + return 0; + } + + phylink_set(state->lp_advertising, Autoneg); + + /* Clause 73 outcome */ + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL3); + if (ret < 0) + return ret; + + if (ret & DW_C73_2500KX) + phylink_set(state->lp_advertising, 2500baseX_Full); + + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL2); + if (ret < 0) + return ret; + + if (ret & DW_C73_1000KX) + phylink_set(state->lp_advertising, 1000baseKX_Full); + if (ret & DW_C73_10000KX4) + phylink_set(state->lp_advertising, 10000baseKX4_Full); + if (ret & DW_C73_10000KR) + phylink_set(state->lp_advertising, 10000baseKR_Full); + + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL1); + if (ret < 0) + return ret; + + if (ret & DW_C73_PAUSE) + phylink_set(state->lp_advertising, Pause); + if (ret & DW_C73_ASYM_PAUSE) + phylink_set(state->lp_advertising, Asym_Pause); + + linkmode_and(state->lp_advertising, state->lp_advertising, + state->advertising); + return 0; +} + +static void xpcs_resolve_lpa(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + int max_speed = xpcs_get_max_usxgmii_speed(state->lp_advertising); + + state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + state->speed = max_speed; + state->duplex = DUPLEX_FULL; +} + +static int xpcs_get_max_xlgmii_speed(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + unsigned long *adv = state->advertising; + int speed = SPEED_UNKNOWN; + int bit; + + for_each_set_bit(bit, adv, __ETHTOOL_LINK_MODE_MASK_NBITS) { + int new_speed = SPEED_UNKNOWN; + + switch (bit) { + case ETHTOOL_LINK_MODE_25000baseCR_Full_BIT: + case ETHTOOL_LINK_MODE_25000baseKR_Full_BIT: + case ETHTOOL_LINK_MODE_25000baseSR_Full_BIT: + new_speed = SPEED_25000; + break; + case ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT: + case ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT: + case ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT: + case ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT: + new_speed = SPEED_40000; + break; + case ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseKR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseSR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseCR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseDR_Full_BIT: + new_speed = SPEED_50000; + break; + case ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT: + new_speed = SPEED_100000; + break; + default: + continue; + } + + if (new_speed > speed) + speed = new_speed; + } + + return speed; +} + +static void xpcs_resolve_pma(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + state->duplex = DUPLEX_FULL; + + switch (state->interface) { + case PHY_INTERFACE_MODE_10GKR: + state->speed = SPEED_10000; + break; + case PHY_INTERFACE_MODE_XLGMII: + state->speed = xpcs_get_max_xlgmii_speed(xpcs, state); + break; + default: + state->speed = SPEED_UNKNOWN; + break; + } +} + +static int xpcs_validate(struct mdio_xpcs_args *xpcs, + unsigned long *supported, + struct phylink_link_state *state) +{ + linkmode_and(supported, supported, xpcs->supported); + linkmode_and(state->advertising, state->advertising, xpcs->supported); + return 0; +} + +static int xpcs_config(struct mdio_xpcs_args *xpcs, + const struct phylink_link_state *state) +{ + int ret; + + if (state->an_enabled) { + ret = xpcs_config_aneg(xpcs); + if (ret) + return ret; + } + + return 0; +} + +static int xpcs_get_state(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state) +{ + int ret; + + /* Link needs to be read first ... */ + state->link = xpcs_read_link(xpcs, state->an_enabled) > 0 ? 1 : 0; + + /* ... and then we check the faults. */ + ret = xpcs_read_fault(xpcs, state); + if (ret) { + ret = xpcs_soft_reset(xpcs, MDIO_MMD_PCS); + if (ret) + return ret; + + state->link = 0; + + return xpcs_config(xpcs, state); + } + + if (state->an_enabled && xpcs_aneg_done(xpcs, state)) { + state->an_complete = true; + xpcs_read_lpa(xpcs, state); + xpcs_resolve_lpa(xpcs, state); + } else if (state->an_enabled) { + state->link = 0; + } else if (state->link) { + xpcs_resolve_pma(xpcs, state); + } + + return 0; +} + +static int xpcs_link_up(struct mdio_xpcs_args *xpcs, int speed, + phy_interface_t interface) +{ + if (interface == PHY_INTERFACE_MODE_USXGMII) + return xpcs_config_usxgmii(xpcs, speed); + + return 0; +} + +static u32 xpcs_get_id(struct mdio_xpcs_args *xpcs) +{ + int ret; + u32 id; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID1); + if (ret < 0) + return 0xffffffff; + + id = ret << 16; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID2); + if (ret < 0) + return 0xffffffff; + + return id | ret; +} + +static bool xpcs_check_features(struct mdio_xpcs_args *xpcs, + struct xpcs_id *match, + phy_interface_t interface) +{ + int i; + + for (i = 0; match->interface[i] != PHY_INTERFACE_MODE_MAX; i++) { + if (match->interface[i] == interface) + break; + } + + if (match->interface[i] == PHY_INTERFACE_MODE_MAX) + return false; + + for (i = 0; match->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) + set_bit(match->supported[i], xpcs->supported); + + return true; +} + +static int xpcs_probe(struct mdio_xpcs_args *xpcs, phy_interface_t interface) +{ + u32 xpcs_id = xpcs_get_id(xpcs); + struct xpcs_id *match = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(xpcs_id_list); i++) { + struct xpcs_id *entry = &xpcs_id_list[i]; + + if ((xpcs_id & entry->mask) == entry->id) { + match = entry; + + if (xpcs_check_features(xpcs, match, interface)) + return xpcs_soft_reset(xpcs, MDIO_MMD_PCS); + } + } + + return -ENODEV; +} + +static struct mdio_xpcs_ops xpcs_ops = { + .validate = xpcs_validate, + .config = xpcs_config, + .get_state = xpcs_get_state, + .link_up = xpcs_link_up, + .probe = xpcs_probe, +}; + +struct mdio_xpcs_ops *mdio_xpcs_get_ops(void) +{ + return &xpcs_ops; +} +EXPORT_SYMBOL_GPL(mdio_xpcs_get_ops); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 726e4b240e7e..c69cc806f064 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -234,12 +234,6 @@ config MDIO_XGENE This module provides a driver for the MDIO busses found in the APM X-Gene SoC's. -config MDIO_XPCS - tristate "Synopsys DesignWare XPCS controller" - help - This module provides helper functions for Synopsys DesignWare XPCS - controllers. - endif endif diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index d84bab489a53..7cd8a0d1c0d0 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -47,7 +47,6 @@ obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o -obj-$(CONFIG_MDIO_XPCS) += mdio-xpcs.o obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o diff --git a/drivers/net/phy/mdio-xpcs.c b/drivers/net/phy/mdio-xpcs.c deleted file mode 100644 index 0d66a8ba7eb6..000000000000 --- a/drivers/net/phy/mdio-xpcs.c +++ /dev/null @@ -1,716 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates. - * Synopsys DesignWare XPCS helpers - * - * Author: Jose Abreu - */ - -#include -#include -#include -#include -#include - -#define SYNOPSYS_XPCS_USXGMII_ID 0x7996ced0 -#define SYNOPSYS_XPCS_10GKR_ID 0x7996ced0 -#define SYNOPSYS_XPCS_XLGMII_ID 0x7996ced0 -#define SYNOPSYS_XPCS_MASK 0xffffffff - -/* Vendor regs access */ -#define DW_VENDOR BIT(15) - -/* VR_XS_PCS */ -#define DW_USXGMII_RST BIT(10) -#define DW_USXGMII_EN BIT(9) -#define DW_VR_XS_PCS_DIG_STS 0x0010 -#define DW_RXFIFO_ERR GENMASK(6, 5) - -/* SR_MII */ -#define DW_USXGMII_FULL BIT(8) -#define DW_USXGMII_SS_MASK (BIT(13) | BIT(6) | BIT(5)) -#define DW_USXGMII_10000 (BIT(13) | BIT(6)) -#define DW_USXGMII_5000 (BIT(13) | BIT(5)) -#define DW_USXGMII_2500 (BIT(5)) -#define DW_USXGMII_1000 (BIT(6)) -#define DW_USXGMII_100 (BIT(13)) -#define DW_USXGMII_10 (0) - -/* SR_AN */ -#define DW_SR_AN_ADV1 0x10 -#define DW_SR_AN_ADV2 0x11 -#define DW_SR_AN_ADV3 0x12 -#define DW_SR_AN_LP_ABL1 0x13 -#define DW_SR_AN_LP_ABL2 0x14 -#define DW_SR_AN_LP_ABL3 0x15 - -/* Clause 73 Defines */ -/* AN_LP_ABL1 */ -#define DW_C73_PAUSE BIT(10) -#define DW_C73_ASYM_PAUSE BIT(11) -#define DW_C73_AN_ADV_SF 0x1 -/* AN_LP_ABL2 */ -#define DW_C73_1000KX BIT(5) -#define DW_C73_10000KX4 BIT(6) -#define DW_C73_10000KR BIT(7) -/* AN_LP_ABL3 */ -#define DW_C73_2500KX BIT(0) -#define DW_C73_5000KR BIT(1) - -static const int xpcs_usxgmii_features[] = { - ETHTOOL_LINK_MODE_Pause_BIT, - ETHTOOL_LINK_MODE_Asym_Pause_BIT, - ETHTOOL_LINK_MODE_Autoneg_BIT, - ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, - ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, - ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, - ETHTOOL_LINK_MODE_2500baseX_Full_BIT, - __ETHTOOL_LINK_MODE_MASK_NBITS, -}; - -static const int xpcs_10gkr_features[] = { - ETHTOOL_LINK_MODE_Pause_BIT, - ETHTOOL_LINK_MODE_Asym_Pause_BIT, - ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, - __ETHTOOL_LINK_MODE_MASK_NBITS, -}; - -static const int xpcs_xlgmii_features[] = { - ETHTOOL_LINK_MODE_Pause_BIT, - ETHTOOL_LINK_MODE_Asym_Pause_BIT, - ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, - ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, - ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, - ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, - ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, - ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, - ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, - ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, - ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, - ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, - ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, - ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, - ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, - ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, - ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, - ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, - ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, - ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, - ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT, - ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, - ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, - ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, - ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, - ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, - __ETHTOOL_LINK_MODE_MASK_NBITS, -}; - -static const phy_interface_t xpcs_usxgmii_interfaces[] = { - PHY_INTERFACE_MODE_USXGMII, - PHY_INTERFACE_MODE_MAX, -}; - -static const phy_interface_t xpcs_10gkr_interfaces[] = { - PHY_INTERFACE_MODE_10GKR, - PHY_INTERFACE_MODE_MAX, -}; - -static const phy_interface_t xpcs_xlgmii_interfaces[] = { - PHY_INTERFACE_MODE_XLGMII, - PHY_INTERFACE_MODE_MAX, -}; - -static struct xpcs_id { - u32 id; - u32 mask; - const int *supported; - const phy_interface_t *interface; -} xpcs_id_list[] = { - { - .id = SYNOPSYS_XPCS_USXGMII_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_usxgmii_features, - .interface = xpcs_usxgmii_interfaces, - }, { - .id = SYNOPSYS_XPCS_10GKR_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_10gkr_features, - .interface = xpcs_10gkr_interfaces, - }, { - .id = SYNOPSYS_XPCS_XLGMII_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_xlgmii_features, - .interface = xpcs_xlgmii_interfaces, - }, -}; - -static int xpcs_read(struct mdio_xpcs_args *xpcs, int dev, u32 reg) -{ - u32 reg_addr = MII_ADDR_C45 | dev << 16 | reg; - - return mdiobus_read(xpcs->bus, xpcs->addr, reg_addr); -} - -static int xpcs_write(struct mdio_xpcs_args *xpcs, int dev, u32 reg, u16 val) -{ - u32 reg_addr = MII_ADDR_C45 | dev << 16 | reg; - - return mdiobus_write(xpcs->bus, xpcs->addr, reg_addr, val); -} - -static int xpcs_read_vendor(struct mdio_xpcs_args *xpcs, int dev, u32 reg) -{ - return xpcs_read(xpcs, dev, DW_VENDOR | reg); -} - -static int xpcs_write_vendor(struct mdio_xpcs_args *xpcs, int dev, int reg, - u16 val) -{ - return xpcs_write(xpcs, dev, DW_VENDOR | reg, val); -} - -static int xpcs_read_vpcs(struct mdio_xpcs_args *xpcs, int reg) -{ - return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg); -} - -static int xpcs_write_vpcs(struct mdio_xpcs_args *xpcs, int reg, u16 val) -{ - return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val); -} - -static int xpcs_poll_reset(struct mdio_xpcs_args *xpcs, int dev) -{ - /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ - unsigned int retries = 12; - int ret; - - do { - msleep(50); - ret = xpcs_read(xpcs, dev, MDIO_CTRL1); - if (ret < 0) - return ret; - } while (ret & MDIO_CTRL1_RESET && --retries); - - return (ret & MDIO_CTRL1_RESET) ? -ETIMEDOUT : 0; -} - -static int xpcs_soft_reset(struct mdio_xpcs_args *xpcs, int dev) -{ - int ret; - - ret = xpcs_write(xpcs, dev, MDIO_CTRL1, MDIO_CTRL1_RESET); - if (ret < 0) - return ret; - - return xpcs_poll_reset(xpcs, dev); -} - -#define xpcs_warn(__xpcs, __state, __args...) \ -({ \ - if ((__state)->link) \ - dev_warn(&(__xpcs)->bus->dev, ##__args); \ -}) - -static int xpcs_read_fault(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - int ret; - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1); - if (ret < 0) - return ret; - - if (ret & MDIO_STAT1_FAULT) { - xpcs_warn(xpcs, state, "Link fault condition detected!\n"); - return -EFAULT; - } - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2); - if (ret < 0) - return ret; - - if (ret & MDIO_STAT2_RXFAULT) - xpcs_warn(xpcs, state, "Receiver fault detected!\n"); - if (ret & MDIO_STAT2_TXFAULT) - xpcs_warn(xpcs, state, "Transmitter fault detected!\n"); - - ret = xpcs_read_vendor(xpcs, MDIO_MMD_PCS, DW_VR_XS_PCS_DIG_STS); - if (ret < 0) - return ret; - - if (ret & DW_RXFIFO_ERR) { - xpcs_warn(xpcs, state, "FIFO fault condition detected!\n"); - return -EFAULT; - } - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT1); - if (ret < 0) - return ret; - - if (!(ret & MDIO_PCS_10GBRT_STAT1_BLKLK)) - xpcs_warn(xpcs, state, "Link is not locked!\n"); - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT2); - if (ret < 0) - return ret; - - if (ret & MDIO_PCS_10GBRT_STAT2_ERR) { - xpcs_warn(xpcs, state, "Link has errors!\n"); - return -EFAULT; - } - - return 0; -} - -static int xpcs_read_link(struct mdio_xpcs_args *xpcs, bool an) -{ - bool link = true; - int ret; - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1); - if (ret < 0) - return ret; - - if (!(ret & MDIO_STAT1_LSTATUS)) - link = false; - - if (an) { - ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); - if (ret < 0) - return ret; - - if (!(ret & MDIO_STAT1_LSTATUS)) - link = false; - } - - return link; -} - -static int xpcs_get_max_usxgmii_speed(const unsigned long *supported) -{ - int max = SPEED_UNKNOWN; - - if (phylink_test(supported, 1000baseKX_Full)) - max = SPEED_1000; - if (phylink_test(supported, 2500baseX_Full)) - max = SPEED_2500; - if (phylink_test(supported, 10000baseKX4_Full)) - max = SPEED_10000; - if (phylink_test(supported, 10000baseKR_Full)) - max = SPEED_10000; - - return max; -} - -static int xpcs_config_usxgmii(struct mdio_xpcs_args *xpcs, int speed) -{ - int ret, speed_sel; - - switch (speed) { - case SPEED_10: - speed_sel = DW_USXGMII_10; - break; - case SPEED_100: - speed_sel = DW_USXGMII_100; - break; - case SPEED_1000: - speed_sel = DW_USXGMII_1000; - break; - case SPEED_2500: - speed_sel = DW_USXGMII_2500; - break; - case SPEED_5000: - speed_sel = DW_USXGMII_5000; - break; - case SPEED_10000: - speed_sel = DW_USXGMII_10000; - break; - default: - /* Nothing to do here */ - return -EINVAL; - } - - ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); - if (ret < 0) - return ret; - - ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_EN); - if (ret < 0) - return ret; - - ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); - if (ret < 0) - return ret; - - ret &= ~DW_USXGMII_SS_MASK; - ret |= speed_sel | DW_USXGMII_FULL; - - ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret); - if (ret < 0) - return ret; - - ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); - if (ret < 0) - return ret; - - return xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_RST); -} - -static int xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) -{ - int ret, adv; - - /* By default, in USXGMII mode XPCS operates at 10G baud and - * replicates data to achieve lower speeds. Hereby, in this - * default configuration we need to advertise all supported - * modes and not only the ones we want to use. - */ - - /* SR_AN_ADV3 */ - adv = 0; - if (phylink_test(xpcs->supported, 2500baseX_Full)) - adv |= DW_C73_2500KX; - - /* TODO: 5000baseKR */ - - ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV3, adv); - if (ret < 0) - return ret; - - /* SR_AN_ADV2 */ - adv = 0; - if (phylink_test(xpcs->supported, 1000baseKX_Full)) - adv |= DW_C73_1000KX; - if (phylink_test(xpcs->supported, 10000baseKX4_Full)) - adv |= DW_C73_10000KX4; - if (phylink_test(xpcs->supported, 10000baseKR_Full)) - adv |= DW_C73_10000KR; - - ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV2, adv); - if (ret < 0) - return ret; - - /* SR_AN_ADV1 */ - adv = DW_C73_AN_ADV_SF; - if (phylink_test(xpcs->supported, Pause)) - adv |= DW_C73_PAUSE; - if (phylink_test(xpcs->supported, Asym_Pause)) - adv |= DW_C73_ASYM_PAUSE; - - return xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV1, adv); -} - -static int xpcs_config_aneg(struct mdio_xpcs_args *xpcs) -{ - int ret; - - ret = xpcs_config_aneg_c73(xpcs); - if (ret < 0) - return ret; - - ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_CTRL1); - if (ret < 0) - return ret; - - ret |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART; - - return xpcs_write(xpcs, MDIO_MMD_AN, MDIO_CTRL1, ret); -} - -static int xpcs_aneg_done(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - int ret; - - ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); - if (ret < 0) - return ret; - - if (ret & MDIO_AN_STAT1_COMPLETE) { - ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL1); - if (ret < 0) - return ret; - - /* Check if Aneg outcome is valid */ - if (!(ret & DW_C73_AN_ADV_SF)) { - xpcs_config_aneg(xpcs); - return 0; - } - - return 1; - } - - return 0; -} - -static int xpcs_read_lpa(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - int ret; - - ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); - if (ret < 0) - return ret; - - if (!(ret & MDIO_AN_STAT1_LPABLE)) { - phylink_clear(state->lp_advertising, Autoneg); - return 0; - } - - phylink_set(state->lp_advertising, Autoneg); - - /* Clause 73 outcome */ - ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL3); - if (ret < 0) - return ret; - - if (ret & DW_C73_2500KX) - phylink_set(state->lp_advertising, 2500baseX_Full); - - ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL2); - if (ret < 0) - return ret; - - if (ret & DW_C73_1000KX) - phylink_set(state->lp_advertising, 1000baseKX_Full); - if (ret & DW_C73_10000KX4) - phylink_set(state->lp_advertising, 10000baseKX4_Full); - if (ret & DW_C73_10000KR) - phylink_set(state->lp_advertising, 10000baseKR_Full); - - ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL1); - if (ret < 0) - return ret; - - if (ret & DW_C73_PAUSE) - phylink_set(state->lp_advertising, Pause); - if (ret & DW_C73_ASYM_PAUSE) - phylink_set(state->lp_advertising, Asym_Pause); - - linkmode_and(state->lp_advertising, state->lp_advertising, - state->advertising); - return 0; -} - -static void xpcs_resolve_lpa(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - int max_speed = xpcs_get_max_usxgmii_speed(state->lp_advertising); - - state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; - state->speed = max_speed; - state->duplex = DUPLEX_FULL; -} - -static int xpcs_get_max_xlgmii_speed(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - unsigned long *adv = state->advertising; - int speed = SPEED_UNKNOWN; - int bit; - - for_each_set_bit(bit, adv, __ETHTOOL_LINK_MODE_MASK_NBITS) { - int new_speed = SPEED_UNKNOWN; - - switch (bit) { - case ETHTOOL_LINK_MODE_25000baseCR_Full_BIT: - case ETHTOOL_LINK_MODE_25000baseKR_Full_BIT: - case ETHTOOL_LINK_MODE_25000baseSR_Full_BIT: - new_speed = SPEED_25000; - break; - case ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT: - case ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT: - case ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT: - case ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT: - new_speed = SPEED_40000; - break; - case ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseKR_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseSR_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseCR_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT: - case ETHTOOL_LINK_MODE_50000baseDR_Full_BIT: - new_speed = SPEED_50000; - break; - case ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT: - case ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT: - new_speed = SPEED_100000; - break; - default: - continue; - } - - if (new_speed > speed) - speed = new_speed; - } - - return speed; -} - -static void xpcs_resolve_pma(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; - state->duplex = DUPLEX_FULL; - - switch (state->interface) { - case PHY_INTERFACE_MODE_10GKR: - state->speed = SPEED_10000; - break; - case PHY_INTERFACE_MODE_XLGMII: - state->speed = xpcs_get_max_xlgmii_speed(xpcs, state); - break; - default: - state->speed = SPEED_UNKNOWN; - break; - } -} - -static int xpcs_validate(struct mdio_xpcs_args *xpcs, - unsigned long *supported, - struct phylink_link_state *state) -{ - linkmode_and(supported, supported, xpcs->supported); - linkmode_and(state->advertising, state->advertising, xpcs->supported); - return 0; -} - -static int xpcs_config(struct mdio_xpcs_args *xpcs, - const struct phylink_link_state *state) -{ - int ret; - - if (state->an_enabled) { - ret = xpcs_config_aneg(xpcs); - if (ret) - return ret; - } - - return 0; -} - -static int xpcs_get_state(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) -{ - int ret; - - /* Link needs to be read first ... */ - state->link = xpcs_read_link(xpcs, state->an_enabled) > 0 ? 1 : 0; - - /* ... and then we check the faults. */ - ret = xpcs_read_fault(xpcs, state); - if (ret) { - ret = xpcs_soft_reset(xpcs, MDIO_MMD_PCS); - if (ret) - return ret; - - state->link = 0; - - return xpcs_config(xpcs, state); - } - - if (state->an_enabled && xpcs_aneg_done(xpcs, state)) { - state->an_complete = true; - xpcs_read_lpa(xpcs, state); - xpcs_resolve_lpa(xpcs, state); - } else if (state->an_enabled) { - state->link = 0; - } else if (state->link) { - xpcs_resolve_pma(xpcs, state); - } - - return 0; -} - -static int xpcs_link_up(struct mdio_xpcs_args *xpcs, int speed, - phy_interface_t interface) -{ - if (interface == PHY_INTERFACE_MODE_USXGMII) - return xpcs_config_usxgmii(xpcs, speed); - - return 0; -} - -static u32 xpcs_get_id(struct mdio_xpcs_args *xpcs) -{ - int ret; - u32 id; - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID1); - if (ret < 0) - return 0xffffffff; - - id = ret << 16; - - ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID2); - if (ret < 0) - return 0xffffffff; - - return id | ret; -} - -static bool xpcs_check_features(struct mdio_xpcs_args *xpcs, - struct xpcs_id *match, - phy_interface_t interface) -{ - int i; - - for (i = 0; match->interface[i] != PHY_INTERFACE_MODE_MAX; i++) { - if (match->interface[i] == interface) - break; - } - - if (match->interface[i] == PHY_INTERFACE_MODE_MAX) - return false; - - for (i = 0; match->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) - set_bit(match->supported[i], xpcs->supported); - - return true; -} - -static int xpcs_probe(struct mdio_xpcs_args *xpcs, phy_interface_t interface) -{ - u32 xpcs_id = xpcs_get_id(xpcs); - struct xpcs_id *match = NULL; - int i; - - for (i = 0; i < ARRAY_SIZE(xpcs_id_list); i++) { - struct xpcs_id *entry = &xpcs_id_list[i]; - - if ((xpcs_id & entry->mask) == entry->id) { - match = entry; - - if (xpcs_check_features(xpcs, match, interface)) - return xpcs_soft_reset(xpcs, MDIO_MMD_PCS); - } - } - - return -ENODEV; -} - -static struct mdio_xpcs_ops xpcs_ops = { - .validate = xpcs_validate, - .config = xpcs_config, - .get_state = xpcs_get_state, - .link_up = xpcs_link_up, - .probe = xpcs_probe, -}; - -struct mdio_xpcs_ops *mdio_xpcs_get_ops(void) -{ - return &xpcs_ops; -} -EXPORT_SYMBOL_GPL(mdio_xpcs_get_ops); - -MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mdio-xpcs.h b/include/linux/mdio-xpcs.h deleted file mode 100644 index 9a841aa5982d..000000000000 --- a/include/linux/mdio-xpcs.h +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates. - * Synopsys DesignWare XPCS helpers - */ - -#ifndef __LINUX_MDIO_XPCS_H -#define __LINUX_MDIO_XPCS_H - -#include -#include - -struct mdio_xpcs_args { - __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); - struct mii_bus *bus; - int addr; -}; - -struct mdio_xpcs_ops { - int (*validate)(struct mdio_xpcs_args *xpcs, - unsigned long *supported, - struct phylink_link_state *state); - int (*config)(struct mdio_xpcs_args *xpcs, - const struct phylink_link_state *state); - int (*get_state)(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state); - int (*link_up)(struct mdio_xpcs_args *xpcs, int speed, - phy_interface_t interface); - int (*probe)(struct mdio_xpcs_args *xpcs, phy_interface_t interface); -}; - -#if IS_ENABLED(CONFIG_MDIO_XPCS) -struct mdio_xpcs_ops *mdio_xpcs_get_ops(void); -#else -static inline struct mdio_xpcs_ops *mdio_xpcs_get_ops(void) -{ - return NULL; -} -#endif - -#endif /* __LINUX_MDIO_XPCS_H */ diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h new file mode 100644 index 000000000000..351c1c9aedc5 --- /dev/null +++ b/include/linux/pcs/pcs-xpcs.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare XPCS helpers + */ + +#ifndef __LINUX_PCS_XPCS_H +#define __LINUX_PCS_XPCS_H + +#include +#include + +struct mdio_xpcs_args { + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + struct mii_bus *bus; + int addr; +}; + +struct mdio_xpcs_ops { + int (*validate)(struct mdio_xpcs_args *xpcs, + unsigned long *supported, + struct phylink_link_state *state); + int (*config)(struct mdio_xpcs_args *xpcs, + const struct phylink_link_state *state); + int (*get_state)(struct mdio_xpcs_args *xpcs, + struct phylink_link_state *state); + int (*link_up)(struct mdio_xpcs_args *xpcs, int speed, + phy_interface_t interface); + int (*probe)(struct mdio_xpcs_args *xpcs, phy_interface_t interface); +}; + +#if IS_ENABLED(CONFIG_PCS_XPCS) +struct mdio_xpcs_ops *mdio_xpcs_get_ops(void); +#else +static inline struct mdio_xpcs_ops *mdio_xpcs_get_ops(void) +{ + return NULL; +} +#endif + +#endif /* __LINUX_PCS_XPCS_H */ -- cgit v1.2.3 From fcba68bd75bb1d42b3aec7f471d382a9e639a672 Mon Sep 17 00:00:00 2001 From: Andrew Lunn Date: Thu, 27 Aug 2020 04:00:29 +0200 Subject: net/phy/mdio-i2c: Move header file to include/linux/mdio In preparation for moving all MDIO drivers into drivers/net/mdio, move the mdio-i2c header file into include/linux/mdio so it can be used by both the MDIO driver and the SFP code which instantiates I2C MDIO busses. v2: Add include/linux/mdio Reviewed-by: Florian Fainelli Signed-off-by: Andrew Lunn Signed-off-by: David S. Miller --- MAINTAINERS | 1 + drivers/net/phy/mdio-i2c.c | 3 +-- drivers/net/phy/mdio-i2c.h | 16 ---------------- drivers/net/phy/sfp.c | 2 +- include/linux/mdio/mdio-i2c.h | 16 ++++++++++++++++ 5 files changed, 19 insertions(+), 19 deletions(-) delete mode 100644 drivers/net/phy/mdio-i2c.h create mode 100644 include/linux/mdio/mdio-i2c.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 347ed6904fdf..af25e8d003e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15647,6 +15647,7 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/phy/phylink.c F: drivers/net/phy/sfp* +F: include/linux/mdio/mdio-i2c.h F: include/linux/phylink.h F: include/linux/sfp.h K: phylink\.h|struct\s+phylink|\.phylink|>phylink_|phylink_(autoneg|clear|connect|create|destroy|disconnect|ethtool|helper|mac|mii|of|set|start|stop|test|validate) diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c index 0746e2cc39ae..09200a70b315 100644 --- a/drivers/net/phy/mdio-i2c.c +++ b/drivers/net/phy/mdio-i2c.c @@ -10,10 +10,9 @@ * of their settings. */ #include +#include #include -#include "mdio-i2c.h" - /* * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is * specified to be present in SFP modules. These correspond with PHY diff --git a/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h deleted file mode 100644 index b1d27f7cd23f..000000000000 --- a/drivers/net/phy/mdio-i2c.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * MDIO I2C bridge - * - * Copyright (C) 2015 Russell King - */ -#ifndef MDIO_I2C_H -#define MDIO_I2C_H - -struct device; -struct i2c_adapter; -struct mii_bus; - -struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c); - -#endif diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index c24b0e83dd32..5250dcdf46a4 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -16,7 +17,6 @@ #include #include -#include "mdio-i2c.h" #include "sfp.h" #include "swphy.h" diff --git a/include/linux/mdio/mdio-i2c.h b/include/linux/mdio/mdio-i2c.h new file mode 100644 index 000000000000..b1d27f7cd23f --- /dev/null +++ b/include/linux/mdio/mdio-i2c.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015 Russell King + */ +#ifndef MDIO_I2C_H +#define MDIO_I2C_H + +struct device; +struct i2c_adapter; +struct mii_bus; + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c); + +#endif -- cgit v1.2.3 From a9770eac511ad82390b9f4a3c1728e078c387ac7 Mon Sep 17 00:00:00 2001 From: Andrew Lunn Date: Thu, 27 Aug 2020 04:00:31 +0200 Subject: net: mdio: Move MDIO drivers into a new subdirectory Move all the MDIO drivers and multiplexers into drivers/net/mdio. The mdio core is however left in the phy directory, due to mutual dependencies between the MDIO core and the PHY core. Take this opportunity to sort the Kconfig based on the menuconfig strings, and move the multiplexers to the end with a separating comment. v2: Fix typo in commit message Acked-by: Florian Fainelli Signed-off-by: Andrew Lunn Signed-off-by: David S. Miller --- MAINTAINERS | 6 +- drivers/net/Kconfig | 2 + drivers/net/Makefile | 1 + drivers/net/mdio/Kconfig | 241 +++++++++++++++++ drivers/net/mdio/Makefile | 27 ++ drivers/net/mdio/mdio-aspeed.c | 157 +++++++++++ drivers/net/mdio/mdio-bcm-iproc.c | 221 +++++++++++++++ drivers/net/mdio/mdio-bcm-unimac.c | 363 +++++++++++++++++++++++++ drivers/net/mdio/mdio-bitbang.c | 232 ++++++++++++++++ drivers/net/mdio/mdio-cavium.c | 150 ++++++++++ drivers/net/mdio/mdio-cavium.h | 118 ++++++++ drivers/net/mdio/mdio-gpio.c | 217 +++++++++++++++ drivers/net/mdio/mdio-hisi-femac.c | 152 +++++++++++ drivers/net/mdio/mdio-i2c.c | 117 ++++++++ drivers/net/mdio/mdio-ipq4019.c | 160 +++++++++++ drivers/net/mdio/mdio-ipq8064.c | 166 ++++++++++++ drivers/net/mdio/mdio-moxart.c | 187 +++++++++++++ drivers/net/mdio/mdio-mscc-miim.c | 212 +++++++++++++++ drivers/net/mdio/mdio-mux-bcm-iproc.c | 323 ++++++++++++++++++++++ drivers/net/mdio/mdio-mux-gpio.c | 98 +++++++ drivers/net/mdio/mdio-mux-meson-g12a.c | 380 ++++++++++++++++++++++++++ drivers/net/mdio/mdio-mux-mmioreg.c | 204 ++++++++++++++ drivers/net/mdio/mdio-mux-multiplexer.c | 122 +++++++++ drivers/net/mdio/mdio-mux.c | 210 ++++++++++++++ drivers/net/mdio/mdio-mvusb.c | 120 ++++++++ drivers/net/mdio/mdio-octeon.c | 115 ++++++++ drivers/net/mdio/mdio-sun4i.c | 180 ++++++++++++ drivers/net/mdio/mdio-thunder.c | 152 +++++++++++ drivers/net/mdio/mdio-xgene.c | 466 ++++++++++++++++++++++++++++++++ drivers/net/phy/Kconfig | 234 ---------------- drivers/net/phy/Makefile | 26 +- drivers/net/phy/mdio-aspeed.c | 157 ----------- drivers/net/phy/mdio-bcm-iproc.c | 221 --------------- drivers/net/phy/mdio-bcm-unimac.c | 363 ------------------------- drivers/net/phy/mdio-bitbang.c | 232 ---------------- drivers/net/phy/mdio-cavium.c | 150 ---------- drivers/net/phy/mdio-cavium.h | 118 -------- drivers/net/phy/mdio-gpio.c | 217 --------------- drivers/net/phy/mdio-hisi-femac.c | 152 ----------- drivers/net/phy/mdio-i2c.c | 117 -------- drivers/net/phy/mdio-ipq4019.c | 160 ----------- drivers/net/phy/mdio-ipq8064.c | 166 ------------ drivers/net/phy/mdio-moxart.c | 187 ------------- drivers/net/phy/mdio-mscc-miim.c | 212 --------------- drivers/net/phy/mdio-mux-bcm-iproc.c | 323 ---------------------- drivers/net/phy/mdio-mux-gpio.c | 98 ------- drivers/net/phy/mdio-mux-meson-g12a.c | 380 -------------------------- drivers/net/phy/mdio-mux-mmioreg.c | 204 -------------- drivers/net/phy/mdio-mux-multiplexer.c | 122 --------- drivers/net/phy/mdio-mux.c | 210 -------------- drivers/net/phy/mdio-mvusb.c | 120 -------- drivers/net/phy/mdio-octeon.c | 115 -------- drivers/net/phy/mdio-sun4i.c | 180 ------------ drivers/net/phy/mdio-thunder.c | 152 ----------- drivers/net/phy/mdio-xgene.c | 466 -------------------------------- 55 files changed, 5098 insertions(+), 5083 deletions(-) create mode 100644 drivers/net/mdio/Kconfig create mode 100644 drivers/net/mdio/Makefile create mode 100644 drivers/net/mdio/mdio-aspeed.c create mode 100644 drivers/net/mdio/mdio-bcm-iproc.c create mode 100644 drivers/net/mdio/mdio-bcm-unimac.c create mode 100644 drivers/net/mdio/mdio-bitbang.c create mode 100644 drivers/net/mdio/mdio-cavium.c create mode 100644 drivers/net/mdio/mdio-cavium.h create mode 100644 drivers/net/mdio/mdio-gpio.c create mode 100644 drivers/net/mdio/mdio-hisi-femac.c create mode 100644 drivers/net/mdio/mdio-i2c.c create mode 100644 drivers/net/mdio/mdio-ipq4019.c create mode 100644 drivers/net/mdio/mdio-ipq8064.c create mode 100644 drivers/net/mdio/mdio-moxart.c create mode 100644 drivers/net/mdio/mdio-mscc-miim.c create mode 100644 drivers/net/mdio/mdio-mux-bcm-iproc.c create mode 100644 drivers/net/mdio/mdio-mux-gpio.c create mode 100644 drivers/net/mdio/mdio-mux-meson-g12a.c create mode 100644 drivers/net/mdio/mdio-mux-mmioreg.c create mode 100644 drivers/net/mdio/mdio-mux-multiplexer.c create mode 100644 drivers/net/mdio/mdio-mux.c create mode 100644 drivers/net/mdio/mdio-mvusb.c create mode 100644 drivers/net/mdio/mdio-octeon.c create mode 100644 drivers/net/mdio/mdio-sun4i.c create mode 100644 drivers/net/mdio/mdio-thunder.c create mode 100644 drivers/net/mdio/mdio-xgene.c delete mode 100644 drivers/net/phy/mdio-aspeed.c delete mode 100644 drivers/net/phy/mdio-bcm-iproc.c delete mode 100644 drivers/net/phy/mdio-bcm-unimac.c delete mode 100644 drivers/net/phy/mdio-bitbang.c delete mode 100644 drivers/net/phy/mdio-cavium.c delete mode 100644 drivers/net/phy/mdio-cavium.h delete mode 100644 drivers/net/phy/mdio-gpio.c delete mode 100644 drivers/net/phy/mdio-hisi-femac.c delete mode 100644 drivers/net/phy/mdio-i2c.c delete mode 100644 drivers/net/phy/mdio-ipq4019.c delete mode 100644 drivers/net/phy/mdio-ipq8064.c delete mode 100644 drivers/net/phy/mdio-moxart.c delete mode 100644 drivers/net/phy/mdio-mscc-miim.c delete mode 100644 drivers/net/phy/mdio-mux-bcm-iproc.c delete mode 100644 drivers/net/phy/mdio-mux-gpio.c delete mode 100644 drivers/net/phy/mdio-mux-meson-g12a.c delete mode 100644 drivers/net/phy/mdio-mux-mmioreg.c delete mode 100644 drivers/net/phy/mdio-mux-multiplexer.c delete mode 100644 drivers/net/phy/mdio-mux.c delete mode 100644 drivers/net/phy/mdio-mvusb.c delete mode 100644 drivers/net/phy/mdio-octeon.c delete mode 100644 drivers/net/phy/mdio-sun4i.c delete mode 100644 drivers/net/phy/mdio-thunder.c delete mode 100644 drivers/net/phy/mdio-xgene.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index af25e8d003e7..b0e909937499 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1286,7 +1286,7 @@ S: Supported F: Documentation/devicetree/bindings/net/apm-xgene-enet.txt F: Documentation/devicetree/bindings/net/apm-xgene-mdio.txt F: drivers/net/ethernet/apm/xgene/ -F: drivers/net/phy/mdio-xgene.c +F: drivers/net/mdio/mdio-xgene.c APPLIED MICRO (APM) X-GENE SOC PMU M: Khuong Dinh @@ -6513,12 +6513,14 @@ F: Documentation/devicetree/bindings/net/ethernet-phy.yaml F: Documentation/devicetree/bindings/net/mdio* F: Documentation/devicetree/bindings/net/qca,ar803x.yaml F: Documentation/networking/phy.rst +F: drivers/net/mdio/ F: drivers/net/pcs/ F: drivers/net/phy/ F: drivers/of/of_mdio.c F: drivers/of/of_net.c F: include/dt-bindings/net/qca-ar803x.h F: include/linux/*mdio*.h +F: include/linux/mdio/*.h F: include/linux/of_net.h F: include/linux/phy.h F: include/linux/phy_fixed.h @@ -10498,7 +10500,7 @@ M: Tobias Waldekranz L: netdev@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/marvell,mvusb.yaml -F: drivers/net/phy/mdio-mvusb.c +F: drivers/net/mdio/mdio-mvusb.c MARVELL XENON MMC/SD/SDIO HOST CONTROLLER DRIVER M: Hu Ziji diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 2b07566de78c..c3dbe64e628e 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -473,6 +473,8 @@ config NET_SB1000 source "drivers/net/phy/Kconfig" +source "drivers/net/mdio/Kconfig" + source "drivers/net/pcs/Kconfig" source "drivers/net/plip/Kconfig" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index f7402d766b67..72e18d505d1a 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MDIO) += mdio.o obj-$(CONFIG_NET) += Space.o loopback.o obj-$(CONFIG_NETCONSOLE) += netconsole.o obj-y += phy/ +obj-y += mdio/ obj-y += pcs/ obj-$(CONFIG_RIONET) += rionet.o obj-$(CONFIG_NET_TEAM) += team/ diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig new file mode 100644 index 000000000000..1299880dfe74 --- /dev/null +++ b/drivers/net/mdio/Kconfig @@ -0,0 +1,241 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# MDIO Layer Configuration +# + +menuconfig MDIO_DEVICE + tristate "MDIO bus device drivers" + help + MDIO devices and driver infrastructure code. + +if MDIO_DEVICE + +config MDIO_BUS + tristate + default m if PHYLIB=m + default MDIO_DEVICE + help + This internal symbol is used for link time dependencies and it + reflects whether the mdio_bus/mdio_device code is built as a + loadable module or built-in. + +if MDIO_BUS + +config MDIO_DEVRES + tristate + +config MDIO_SUN4I + tristate "Allwinner sun4i MDIO interface support" + depends on ARCH_SUNXI || COMPILE_TEST + 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_XGENE + tristate "APM X-Gene SoC MDIO bus controller" + depends on ARCH_XGENE || COMPILE_TEST + help + This module provides a driver for the MDIO busses found in the + APM X-Gene SoC's. + +config MDIO_ASPEED + tristate "ASPEED MDIO bus controller" + depends on ARCH_ASPEED || COMPILE_TEST + depends on OF_MDIO && HAS_IOMEM + help + This module provides a driver for the independent MDIO bus + controllers found in the ASPEED AST2600 SoC. This is a driver for the + third revision of the ASPEED MDIO register interface - the first two + revisions are the "old" and "new" interfaces found in the AST2400 and + AST2500, embedded in the MAC. For legacy reasons, FTGMAC100 driver + continues to drive the embedded MDIO controller for the AST2400 and + AST2500 SoCs, so say N if AST2600 support is not required. + +config MDIO_BITBANG + tristate "Bitbanged MDIO buses" + help + This module implements the MDIO bus protocol in software, + for use by low level drivers that export the ability to + drive the relevant pins. + + If in doubt, say N. + +config MDIO_BCM_IPROC + tristate "Broadcom iProc MDIO bus controller" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF_MDIO + default ARCH_BCM_IPROC + help + This module provides a driver for the MDIO busses found in the + Broadcom iProc SoC's. + +config MDIO_BCM_UNIMAC + tristate "Broadcom UniMAC MDIO bus controller" + depends on HAS_IOMEM + help + This module provides a driver for the Broadcom UniMAC MDIO busses. + This hardware can be found in the Broadcom GENET Ethernet MAC + controllers as well as some Broadcom Ethernet switches such as the + Starfighter 2 switches. + +config MDIO_CAVIUM + tristate + +config MDIO_GPIO + tristate "GPIO lib-based bitbanged MDIO buses" + depends on MDIO_BITBANG + depends on GPIOLIB || COMPILE_TEST + help + Supports GPIO lib-based MDIO busses. + + To compile this driver as a module, choose M here: the module + will be called mdio-gpio. + +config MDIO_HISI_FEMAC + tristate "Hisilicon FEMAC MDIO bus controller" + depends on HAS_IOMEM && OF_MDIO + help + This module provides a driver for the MDIO busses found in the + Hisilicon SoC that have an Fast Ethernet MAC. + +config MDIO_I2C + tristate + depends on I2C + help + Support I2C based PHYs. This provides a MDIO bus bridged + to I2C to allow PHYs connected in I2C mode to be accessed + using the existing infrastructure. + + This is library mode. + +config MDIO_MVUSB + tristate "Marvell USB to MDIO Adapter" + depends on USB + select MDIO_DEVRES + help + A USB to MDIO converter present on development boards for + Marvell's Link Street family of Ethernet switches. + +config MDIO_MSCC_MIIM + tristate "Microsemi MIIM interface support" + depends on HAS_IOMEM + select MDIO_DEVRES + help + This driver supports the MIIM (MDIO) interface found in the network + switches of the Microsemi SoCs; it is recommended to switch on + CONFIG_HIGH_RES_TIMERS + +config MDIO_MOXART + tristate "MOXA ART MDIO interface support" + depends on ARCH_MOXART || COMPILE_TEST + help + This driver supports the MDIO interface found in the network + interface units of the MOXA ART SoC + +config MDIO_OCTEON + tristate "Octeon and some ThunderX SOCs MDIO buses" + depends on (64BIT && OF_MDIO) || COMPILE_TEST + depends on HAS_IOMEM + select MDIO_CAVIUM + help + This module provides a driver for the Octeon and ThunderX MDIO + buses. It is required by the Octeon and ThunderX ethernet device + drivers on some systems. + +config MDIO_IPQ4019 + tristate "Qualcomm IPQ4019 MDIO interface support" + depends on HAS_IOMEM && OF_MDIO + help + This driver supports the MDIO interface found in Qualcomm + IPQ40xx series Soc-s. + +config MDIO_IPQ8064 + tristate "Qualcomm IPQ8064 MDIO interface support" + depends on HAS_IOMEM && OF_MDIO + depends on MFD_SYSCON + help + This driver supports the MDIO interface found in the network + interface units of the IPQ8064 SoC + +config MDIO_THUNDER + tristate "ThunderX SOCs MDIO buses" + depends on 64BIT + depends on PCI + select MDIO_CAVIUM + help + This driver supports the MDIO interfaces found on Cavium + ThunderX SoCs when the MDIO bus device appears as a PCI + device. + +comment "MDIO Multiplexers" + +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_MESON_G12A + tristate "Amlogic G12a based MDIO bus multiplexer" + depends on ARCH_MESON || COMPILE_TEST + depends on OF_MDIO && HAS_IOMEM && COMMON_CLK + select MDIO_BUS_MUX + default m if ARCH_MESON + help + This module provides a driver for the MDIO multiplexer/glue of + the amlogic g12a SoC. The multiplexers connects either the external + or the internal MDIO bus to the parent bus. + +config MDIO_BUS_MUX_BCM_IPROC + tristate "Broadcom iProc based MDIO bus multiplexers" + depends on OF && OF_MDIO && (ARCH_BCM_IPROC || COMPILE_TEST) + select MDIO_BUS_MUX + default ARCH_BCM_IPROC + help + This module provides a driver for MDIO bus multiplexers found in + iProc based Broadcom SoCs. This multiplexer connects one of several + child MDIO bus to a parent bus. Buses could be internal as well as + external and selection logic lies inside the same multiplexer. + +config MDIO_BUS_MUX_GPIO + tristate "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_MULTIPLEXER + tristate "MDIO bus multiplexer using kernel multiplexer subsystem" + depends on OF_MDIO + select MULTIPLEXER + select MDIO_BUS_MUX + help + This module provides a driver for MDIO bus multiplexer + that is controlled via the kernel multiplexer subsystem. The + bus multiplexer connects one of several child MDIO busses to + a parent bus. Child bus selection is under the control of + the kernel multiplexer subsystem. + +config MDIO_BUS_MUX_MMIOREG + tristate "MMIO device-controlled MDIO bus multiplexers" + depends on OF_MDIO && HAS_IOMEM + 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/16/32 bits registers are supported. + + +endif +endif diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile new file mode 100644 index 000000000000..14d1beb633c9 --- /dev/null +++ b/drivers/net/mdio/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Linux MDIO bus drivers + +obj-$(CONFIG_MDIO_ASPEED) += mdio-aspeed.o +obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o +obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o +obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o +obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o +obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o +obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o +obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o +obj-$(CONFIG_MDIO_IPQ4019) += mdio-ipq4019.o +obj-$(CONFIG_MDIO_IPQ8064) += mdio-ipq8064.o +obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o +obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o +obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o +obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o +obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o +obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o +obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o + +obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o +obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC) += mdio-mux-bcm-iproc.o +obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o +obj-$(CONFIG_MDIO_BUS_MUX_MESON_G12A) += mdio-mux-meson-g12a.o +obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o +obj-$(CONFIG_MDIO_BUS_MUX_MULTIPLEXER) += mdio-mux-multiplexer.o diff --git a/drivers/net/mdio/mdio-aspeed.c b/drivers/net/mdio/mdio-aspeed.c new file mode 100644 index 000000000000..cad820568f75 --- /dev/null +++ b/drivers/net/mdio/mdio-aspeed.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2019 IBM Corp. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "mdio-aspeed" + +#define ASPEED_MDIO_CTRL 0x0 +#define ASPEED_MDIO_CTRL_FIRE BIT(31) +#define ASPEED_MDIO_CTRL_ST BIT(28) +#define ASPEED_MDIO_CTRL_ST_C45 0 +#define ASPEED_MDIO_CTRL_ST_C22 1 +#define ASPEED_MDIO_CTRL_OP GENMASK(27, 26) +#define MDIO_C22_OP_WRITE 0b01 +#define MDIO_C22_OP_READ 0b10 +#define ASPEED_MDIO_CTRL_PHYAD GENMASK(25, 21) +#define ASPEED_MDIO_CTRL_REGAD GENMASK(20, 16) +#define ASPEED_MDIO_CTRL_MIIWDATA GENMASK(15, 0) + +#define ASPEED_MDIO_DATA 0x4 +#define ASPEED_MDIO_DATA_MDC_THRES GENMASK(31, 24) +#define ASPEED_MDIO_DATA_MDIO_EDGE BIT(23) +#define ASPEED_MDIO_DATA_MDIO_LATCH GENMASK(22, 20) +#define ASPEED_MDIO_DATA_IDLE BIT(16) +#define ASPEED_MDIO_DATA_MIIRDATA GENMASK(15, 0) + +#define ASPEED_MDIO_INTERVAL_US 100 +#define ASPEED_MDIO_TIMEOUT_US (ASPEED_MDIO_INTERVAL_US * 10) + +struct aspeed_mdio { + void __iomem *base; +}; + +static int aspeed_mdio_read(struct mii_bus *bus, int addr, int regnum) +{ + struct aspeed_mdio *ctx = bus->priv; + u32 ctrl; + u32 data; + int rc; + + dev_dbg(&bus->dev, "%s: addr: %d, regnum: %d\n", __func__, addr, + regnum); + + /* Just clause 22 for the moment */ + if (regnum & MII_ADDR_C45) + return -EOPNOTSUPP; + + ctrl = ASPEED_MDIO_CTRL_FIRE + | FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22) + | FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_READ) + | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr) + | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regnum); + + iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); + + rc = readl_poll_timeout(ctx->base + ASPEED_MDIO_DATA, data, + data & ASPEED_MDIO_DATA_IDLE, + ASPEED_MDIO_INTERVAL_US, + ASPEED_MDIO_TIMEOUT_US); + if (rc < 0) + return rc; + + return FIELD_GET(ASPEED_MDIO_DATA_MIIRDATA, data); +} + +static int aspeed_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val) +{ + struct aspeed_mdio *ctx = bus->priv; + u32 ctrl; + + dev_dbg(&bus->dev, "%s: addr: %d, regnum: %d, val: 0x%x\n", + __func__, addr, regnum, val); + + /* Just clause 22 for the moment */ + if (regnum & MII_ADDR_C45) + return -EOPNOTSUPP; + + ctrl = ASPEED_MDIO_CTRL_FIRE + | FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22) + | FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_WRITE) + | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr) + | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regnum) + | FIELD_PREP(ASPEED_MDIO_CTRL_MIIWDATA, val); + + iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); + + return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl, + !(ctrl & ASPEED_MDIO_CTRL_FIRE), + ASPEED_MDIO_INTERVAL_US, + ASPEED_MDIO_TIMEOUT_US); +} + +static int aspeed_mdio_probe(struct platform_device *pdev) +{ + struct aspeed_mdio *ctx; + struct mii_bus *bus; + int rc; + + bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctx)); + if (!bus) + return -ENOMEM; + + ctx = bus->priv; + ctx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + + bus->name = DRV_NAME; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); + bus->parent = &pdev->dev; + bus->read = aspeed_mdio_read; + bus->write = aspeed_mdio_write; + + rc = of_mdiobus_register(bus, pdev->dev.of_node); + if (rc) { + dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); + return rc; + } + + platform_set_drvdata(pdev, bus); + + return 0; +} + +static int aspeed_mdio_remove(struct platform_device *pdev) +{ + mdiobus_unregister(platform_get_drvdata(pdev)); + + return 0; +} + +static const struct of_device_id aspeed_mdio_of_match[] = { + { .compatible = "aspeed,ast2600-mdio", }, + { }, +}; + +static struct platform_driver aspeed_mdio_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = aspeed_mdio_of_match, + }, + .probe = aspeed_mdio_probe, + .remove = aspeed_mdio_remove, +}; + +module_platform_driver(aspeed_mdio_driver); + +MODULE_AUTHOR("Andrew Jeffery "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-bcm-iproc.c b/drivers/net/mdio/mdio-bcm-iproc.c new file mode 100644 index 000000000000..77fc970cdfde --- /dev/null +++ b/drivers/net/mdio/mdio-bcm-iproc.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015 Broadcom Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPROC_GPHY_MDCDIV 0x1a + +#define MII_CTRL_OFFSET 0x000 + +#define MII_CTRL_DIV_SHIFT 0 +#define MII_CTRL_PRE_SHIFT 7 +#define MII_CTRL_BUSY_SHIFT 8 + +#define MII_DATA_OFFSET 0x004 +#define MII_DATA_MASK 0xffff +#define MII_DATA_TA_SHIFT 16 +#define MII_DATA_TA_VAL 2 +#define MII_DATA_RA_SHIFT 18 +#define MII_DATA_PA_SHIFT 23 +#define MII_DATA_OP_SHIFT 28 +#define MII_DATA_OP_WRITE 1 +#define MII_DATA_OP_READ 2 +#define MII_DATA_SB_SHIFT 30 + +struct iproc_mdio_priv { + struct mii_bus *mii_bus; + void __iomem *base; +}; + +static inline int iproc_mdio_wait_for_idle(void __iomem *base) +{ + u32 val; + unsigned int timeout = 1000; /* loop for 1s */ + + do { + val = readl(base + MII_CTRL_OFFSET); + if ((val & BIT(MII_CTRL_BUSY_SHIFT)) == 0) + return 0; + + usleep_range(1000, 2000); + } while (timeout--); + + return -ETIMEDOUT; +} + +static inline void iproc_mdio_config_clk(void __iomem *base) +{ + u32 val; + + val = (IPROC_GPHY_MDCDIV << MII_CTRL_DIV_SHIFT) | + BIT(MII_CTRL_PRE_SHIFT); + writel(val, base + MII_CTRL_OFFSET); +} + +static int iproc_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct iproc_mdio_priv *priv = bus->priv; + u32 cmd; + int rc; + + rc = iproc_mdio_wait_for_idle(priv->base); + if (rc) + return rc; + + /* Prepare the read operation */ + cmd = (MII_DATA_TA_VAL << MII_DATA_TA_SHIFT) | + (reg << MII_DATA_RA_SHIFT) | + (phy_id << MII_DATA_PA_SHIFT) | + BIT(MII_DATA_SB_SHIFT) | + (MII_DATA_OP_READ << MII_DATA_OP_SHIFT); + + writel(cmd, priv->base + MII_DATA_OFFSET); + + rc = iproc_mdio_wait_for_idle(priv->base); + if (rc) + return rc; + + cmd = readl(priv->base + MII_DATA_OFFSET) & MII_DATA_MASK; + + return cmd; +} + +static int iproc_mdio_write(struct mii_bus *bus, int phy_id, + int reg, u16 val) +{ + struct iproc_mdio_priv *priv = bus->priv; + u32 cmd; + int rc; + + rc = iproc_mdio_wait_for_idle(priv->base); + if (rc) + return rc; + + /* Prepare the write operation */ + cmd = (MII_DATA_TA_VAL << MII_DATA_TA_SHIFT) | + (reg << MII_DATA_RA_SHIFT) | + (phy_id << MII_DATA_PA_SHIFT) | + BIT(MII_DATA_SB_SHIFT) | + (MII_DATA_OP_WRITE << MII_DATA_OP_SHIFT) | + ((u32)(val) & MII_DATA_MASK); + + writel(cmd, priv->base + MII_DATA_OFFSET); + + rc = iproc_mdio_wait_for_idle(priv->base); + if (rc) + return rc; + + return 0; +} + +static int iproc_mdio_probe(struct platform_device *pdev) +{ + struct iproc_mdio_priv *priv; + struct mii_bus *bus; + int rc; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) { + dev_err(&pdev->dev, "failed to ioremap register\n"); + return PTR_ERR(priv->base); + } + + priv->mii_bus = mdiobus_alloc(); + if (!priv->mii_bus) { + dev_err(&pdev->dev, "MDIO bus alloc failed\n"); + return -ENOMEM; + } + + bus = priv->mii_bus; + bus->priv = priv; + bus->name = "iProc MDIO bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); + bus->parent = &pdev->dev; + bus->read = iproc_mdio_read; + bus->write = iproc_mdio_write; + + iproc_mdio_config_clk(priv->base); + + rc = of_mdiobus_register(bus, pdev->dev.of_node); + if (rc) { + dev_err(&pdev->dev, "MDIO bus registration failed\n"); + goto err_iproc_mdio; + } + + platform_set_drvdata(pdev, priv); + + dev_info(&pdev->dev, "Broadcom iProc MDIO bus registered\n"); + + return 0; + +err_iproc_mdio: + mdiobus_free(bus); + return rc; +} + +static int iproc_mdio_remove(struct platform_device *pdev) +{ + struct iproc_mdio_priv *priv = platform_get_drvdata(pdev); + + mdiobus_unregister(priv->mii_bus); + mdiobus_free(priv->mii_bus); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int iproc_mdio_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iproc_mdio_priv *priv = platform_get_drvdata(pdev); + + /* restore the mii clock configuration */ + iproc_mdio_config_clk(priv->base); + + return 0; +} + +static const struct dev_pm_ops iproc_mdio_pm_ops = { + .resume = iproc_mdio_resume +}; +#endif /* CONFIG_PM_SLEEP */ + +static const struct of_device_id iproc_mdio_of_match[] = { + { .compatible = "brcm,iproc-mdio", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, iproc_mdio_of_match); + +static struct platform_driver iproc_mdio_driver = { + .driver = { + .name = "iproc-mdio", + .of_match_table = iproc_mdio_of_match, +#ifdef CONFIG_PM_SLEEP + .pm = &iproc_mdio_pm_ops, +#endif + }, + .probe = iproc_mdio_probe, + .remove = iproc_mdio_remove, +}; + +module_platform_driver(iproc_mdio_driver); + +MODULE_AUTHOR("Broadcom Corporation"); +MODULE_DESCRIPTION("Broadcom iProc MDIO bus controller"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iproc-mdio"); diff --git a/drivers/net/mdio/mdio-bcm-unimac.c b/drivers/net/mdio/mdio-bcm-unimac.c new file mode 100644 index 000000000000..fbd36891ee64 --- /dev/null +++ b/drivers/net/mdio/mdio-bcm-unimac.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Broadcom UniMAC MDIO bus controller driver + * + * Copyright (C) 2014-2017 Broadcom + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define MDIO_CMD 0x00 +#define MDIO_START_BUSY (1 << 29) +#define MDIO_READ_FAIL (1 << 28) +#define MDIO_RD (2 << 26) +#define MDIO_WR (1 << 26) +#define MDIO_PMD_SHIFT 21 +#define MDIO_PMD_MASK 0x1F +#define MDIO_REG_SHIFT 16 +#define MDIO_REG_MASK 0x1F + +#define MDIO_CFG 0x04 +#define MDIO_C22 (1 << 0) +#define MDIO_C45 0 +#define MDIO_CLK_DIV_SHIFT 4 +#define MDIO_CLK_DIV_MASK 0x3F +#define MDIO_SUPP_PREAMBLE (1 << 12) + +struct unimac_mdio_priv { + struct mii_bus *mii_bus; + void __iomem *base; + int (*wait_func) (void *wait_func_data); + void *wait_func_data; + struct clk *clk; + u32 clk_freq; +}; + +static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset) +{ + /* MIPS chips strapped for BE will automagically configure the + * peripheral registers for CPU-native byte order. + */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + return __raw_readl(priv->base + offset); + else + return readl_relaxed(priv->base + offset); +} + +static inline void unimac_mdio_writel(struct unimac_mdio_priv *priv, u32 val, + u32 offset) +{ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + __raw_writel(val, priv->base + offset); + else + writel_relaxed(val, priv->base + offset); +} + +static inline void unimac_mdio_start(struct unimac_mdio_priv *priv) +{ + u32 reg; + + reg = unimac_mdio_readl(priv, MDIO_CMD); + reg |= MDIO_START_BUSY; + unimac_mdio_writel(priv, reg, MDIO_CMD); +} + +static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv) +{ + return unimac_mdio_readl(priv, MDIO_CMD) & MDIO_START_BUSY; +} + +static int unimac_mdio_poll(void *wait_func_data) +{ + struct unimac_mdio_priv *priv = wait_func_data; + unsigned int timeout = 1000; + + do { + if (!unimac_mdio_busy(priv)) + return 0; + + usleep_range(1000, 2000); + } while (--timeout); + + return -ETIMEDOUT; +} + +static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct unimac_mdio_priv *priv = bus->priv; + int ret; + u32 cmd; + + /* Prepare the read operation */ + cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT); + unimac_mdio_writel(priv, cmd, MDIO_CMD); + + /* Start MDIO transaction */ + unimac_mdio_start(priv); + + ret = priv->wait_func(priv->wait_func_data); + if (ret) + return ret; + + cmd = unimac_mdio_readl(priv, MDIO_CMD); + + /* Some broken devices are known not to release the line during + * turn-around, e.g: Broadcom BCM53125 external switches, so check for + * that condition here and ignore the MDIO controller read failure + * indication. + */ + if (!(bus->phy_ignore_ta_mask & 1 << phy_id) && (cmd & MDIO_READ_FAIL)) + return -EIO; + + return cmd & 0xffff; +} + +static int unimac_mdio_write(struct mii_bus *bus, int phy_id, + int reg, u16 val) +{ + struct unimac_mdio_priv *priv = bus->priv; + u32 cmd; + + /* Prepare the write operation */ + cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) | + (reg << MDIO_REG_SHIFT) | (0xffff & val); + unimac_mdio_writel(priv, cmd, MDIO_CMD); + + unimac_mdio_start(priv); + + return priv->wait_func(priv->wait_func_data); +} + +/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with + * their internal MDIO management controller making them fail to successfully + * be read from or written to for the first transaction. We insert a dummy + * BMSR read here to make sure that phy_get_device() and get_phy_id() can + * correctly read the PHY MII_PHYSID1/2 registers and successfully register a + * PHY device for this peripheral. + * + * Once the PHY driver is registered, we can workaround subsequent reads from + * there (e.g: during system-wide power management). + * + * bus->reset is invoked before mdiobus_scan during mdiobus_register and is + * therefore the right location to stick that workaround. Since we do not want + * to read from non-existing PHYs, we either use bus->phy_mask or do a manual + * Device Tree scan to limit the search area. + */ +static int unimac_mdio_reset(struct mii_bus *bus) +{ + struct device_node *np = bus->dev.of_node; + struct device_node *child; + u32 read_mask = 0; + int addr; + + if (!np) { + read_mask = ~bus->phy_mask; + } else { + for_each_available_child_of_node(np, child) { + addr = of_mdio_parse_addr(&bus->dev, child); + if (addr < 0) + continue; + + read_mask |= 1 << addr; + } + } + + for (addr = 0; addr < PHY_MAX_ADDR; addr++) { + if (read_mask & 1 << addr) { + dev_dbg(&bus->dev, "Workaround for PHY @ %d\n", addr); + mdiobus_read(bus, addr, MII_BMSR); + } + } + + return 0; +} + +static void unimac_mdio_clk_set(struct unimac_mdio_priv *priv) +{ + unsigned long rate; + u32 reg, div; + + /* Keep the hardware default values */ + if (!priv->clk_freq) + return; + + if (!priv->clk) + rate = 250000000; + else + rate = clk_get_rate(priv->clk); + + div = (rate / (2 * priv->clk_freq)) - 1; + if (div & ~MDIO_CLK_DIV_MASK) { + pr_warn("Incorrect MDIO clock frequency, ignoring\n"); + return; + } + + /* The MDIO clock is the reference clock (typicaly 250Mhz) divided by + * 2 x (MDIO_CLK_DIV + 1) + */ + reg = unimac_mdio_readl(priv, MDIO_CFG); + reg &= ~(MDIO_CLK_DIV_MASK << MDIO_CLK_DIV_SHIFT); + reg |= div << MDIO_CLK_DIV_SHIFT; + unimac_mdio_writel(priv, reg, MDIO_CFG); +} + +static int unimac_mdio_probe(struct platform_device *pdev) +{ + struct unimac_mdio_pdata *pdata = pdev->dev.platform_data; + struct unimac_mdio_priv *priv; + struct device_node *np; + struct mii_bus *bus; + struct resource *r; + int ret; + + np = pdev->dev.of_node; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EINVAL; + + /* Just ioremap, as this MDIO block is usually integrated into an + * Ethernet MAC controller register range + */ + priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!priv->base) { + dev_err(&pdev->dev, "failed to remap register\n"); + return -ENOMEM; + } + + priv->clk = devm_clk_get_optional(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + if (of_property_read_u32(np, "clock-frequency", &priv->clk_freq)) + priv->clk_freq = 0; + + unimac_mdio_clk_set(priv); + + priv->mii_bus = mdiobus_alloc(); + if (!priv->mii_bus) { + ret = -ENOMEM; + goto out_clk_disable; + } + + bus = priv->mii_bus; + bus->priv = priv; + if (pdata) { + bus->name = pdata->bus_name; + priv->wait_func = pdata->wait_func; + priv->wait_func_data = pdata->wait_func_data; + bus->phy_mask = ~pdata->phy_mask; + } else { + bus->name = "unimac MII bus"; + priv->wait_func_data = priv; + priv->wait_func = unimac_mdio_poll; + } + bus->parent = &pdev->dev; + bus->read = unimac_mdio_read; + bus->write = unimac_mdio_write; + bus->reset = unimac_mdio_reset; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); + + ret = of_mdiobus_register(bus, np); + if (ret) { + dev_err(&pdev->dev, "MDIO bus registration failed\n"); + goto out_mdio_free; + } + + platform_set_drvdata(pdev, priv); + + dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus\n"); + + return 0; + +out_mdio_free: + mdiobus_free(bus); +out_clk_disable: + clk_disable_unprepare(priv->clk); + return ret; +} + +static int unimac_mdio_remove(struct platform_device *pdev) +{ + struct unimac_mdio_priv *priv = platform_get_drvdata(pdev); + + mdiobus_unregister(priv->mii_bus); + mdiobus_free(priv->mii_bus); + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int __maybe_unused unimac_mdio_suspend(struct device *d) +{ + struct unimac_mdio_priv *priv = dev_get_drvdata(d); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int __maybe_unused unimac_mdio_resume(struct device *d) +{ + struct unimac_mdio_priv *priv = dev_get_drvdata(d); + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + unimac_mdio_clk_set(priv); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(unimac_mdio_pm_ops, + unimac_mdio_suspend, unimac_mdio_resume); + +static const struct of_device_id unimac_mdio_ids[] = { + { .compatible = "brcm,genet-mdio-v5", }, + { .compatible = "brcm,genet-mdio-v4", }, + { .compatible = "brcm,genet-mdio-v3", }, + { .compatible = "brcm,genet-mdio-v2", }, + { .compatible = "brcm,genet-mdio-v1", }, + { .compatible = "brcm,unimac-mdio", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, unimac_mdio_ids); + +static struct platform_driver unimac_mdio_driver = { + .driver = { + .name = UNIMAC_MDIO_DRV_NAME, + .of_match_table = unimac_mdio_ids, + .pm = &unimac_mdio_pm_ops, + }, + .probe = unimac_mdio_probe, + .remove = unimac_mdio_remove, +}; +module_platform_driver(unimac_mdio_driver); + +MODULE_AUTHOR("Broadcom Corporation"); +MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" UNIMAC_MDIO_DRV_NAME); diff --git a/drivers/net/mdio/mdio-bitbang.c b/drivers/net/mdio/mdio-bitbang.c new file mode 100644 index 000000000000..5136275c8e73 --- /dev/null +++ b/drivers/net/mdio/mdio-bitbang.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Bitbanged MDIO support. + * + * Author: Scott Wood + * Copyright (c) 2007 Freescale Semiconductor + * + * Based on CPM2 MDIO code which is: + * + * Copyright (c) 2003 Intracom S.A. + * by Pantelis Antoniou + * + * 2005 (c) MontaVista Software, Inc. + * Vitaly Bordug + */ + +#include +#include +#include +#include + +#define MDIO_READ 2 +#define MDIO_WRITE 1 + +#define MDIO_C45 (1<<15) +#define MDIO_C45_ADDR (MDIO_C45 | 0) +#define MDIO_C45_READ (MDIO_C45 | 3) +#define MDIO_C45_WRITE (MDIO_C45 | 1) + +#define MDIO_SETUP_TIME 10 +#define MDIO_HOLD_TIME 10 + +/* Minimum MDC period is 400 ns, plus some margin for error. MDIO_DELAY + * is done twice per period. + */ +#define MDIO_DELAY 250 + +/* The PHY may take up to 300 ns to produce data, plus some margin + * for error. + */ +#define MDIO_READ_DELAY 350 + +/* MDIO must already be configured as output. */ +static void mdiobb_send_bit(struct mdiobb_ctrl *ctrl, int val) +{ + const struct mdiobb_ops *ops = ctrl->ops; + + ops->set_mdio_data(ctrl, val); + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 1); + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 0); +} + +/* MDIO must already be configured as input. */ +static int mdiobb_get_bit(struct mdiobb_ctrl *ctrl) +{ + const struct mdiobb_ops *ops = ctrl->ops; + + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 1); + ndelay(MDIO_READ_DELAY); + ops->set_mdc(ctrl, 0); + + return ops->get_mdio_data(ctrl); +} + +/* MDIO must already be configured as output. */ +static void mdiobb_send_num(struct mdiobb_ctrl *ctrl, u16 val, int bits) +{ + int i; + + for (i = bits - 1; i >= 0; i--) + mdiobb_send_bit(ctrl, (val >> i) & 1); +} + +/* MDIO must already be configured as input. */ +static u16 mdiobb_get_num(struct mdiobb_ctrl *ctrl, int bits) +{ + int i; + u16 ret = 0; + + for (i = bits - 1; i >= 0; i--) { + ret <<= 1; + ret |= mdiobb_get_bit(ctrl); + } + + return ret; +} + +/* Utility to send the preamble, address, and + * register (common to read and write). + */ +static void mdiobb_cmd(struct mdiobb_ctrl *ctrl, int op, u8 phy, u8 reg) +{ + const struct mdiobb_ops *ops = ctrl->ops; + int i; + + ops->set_mdio_dir(ctrl, 1); + + /* + * Send a 32 bit preamble ('1's) with an extra '1' bit for good + * measure. The IEEE spec says this is a PHY optional + * requirement. The AMD 79C874 requires one after power up and + * one after a MII communications error. This means that we are + * doing more preambles than we need, but it is safer and will be + * much more robust. + */ + + for (i = 0; i < 32; i++) + mdiobb_send_bit(ctrl, 1); + + /* send the start bit (01) and the read opcode (10) or write (01). + Clause 45 operation uses 00 for the start and 11, 10 for + read/write */ + mdiobb_send_bit(ctrl, 0); + if (op & MDIO_C45) + mdiobb_send_bit(ctrl, 0); + else + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, (op >> 1) & 1); + mdiobb_send_bit(ctrl, (op >> 0) & 1); + + mdiobb_send_num(ctrl, phy, 5); + mdiobb_send_num(ctrl, reg, 5); +} + +/* In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the + lower 16 bits of the 21 bit address. This transfer is done identically to a + MDIO_WRITE except for a different code. To enable clause 45 mode or + MII_ADDR_C45 into the address. Theoretically clause 45 and normal devices + can exist on the same bus. Normal devices should ignore the MDIO_ADDR + phase. */ +static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr) +{ + unsigned int dev_addr = (addr >> 16) & 0x1F; + unsigned int reg = addr & 0xFFFF; + mdiobb_cmd(ctrl, MDIO_C45_ADDR, phy, dev_addr); + + /* send the turnaround (10) */ + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, 0); + + mdiobb_send_num(ctrl, reg, 16); + + ctrl->ops->set_mdio_dir(ctrl, 0); + mdiobb_get_bit(ctrl); + + return dev_addr; +} + +static int mdiobb_read(struct mii_bus *bus, int phy, int reg) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + int ret, i; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); + mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg); + } else + mdiobb_cmd(ctrl, MDIO_READ, phy, reg); + + ctrl->ops->set_mdio_dir(ctrl, 0); + + /* check the turnaround bit: the PHY should be driving it to zero, if this + * PHY is listed in phy_ignore_ta_mask as having broken TA, skip that + */ + if (mdiobb_get_bit(ctrl) != 0 && + !(bus->phy_ignore_ta_mask & (1 << phy))) { + /* PHY didn't drive TA low -- flush any bits it + * may be trying to send. + */ + for (i = 0; i < 32; i++) + mdiobb_get_bit(ctrl); + + return 0xffff; + } + + ret = mdiobb_get_num(ctrl, 16); + mdiobb_get_bit(ctrl); + return ret; +} + +static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); + mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg); + } else + mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg); + + /* send the turnaround (10) */ + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, 0); + + mdiobb_send_num(ctrl, val, 16); + + ctrl->ops->set_mdio_dir(ctrl, 0); + mdiobb_get_bit(ctrl); + return 0; +} + +struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl) +{ + struct mii_bus *bus; + + bus = mdiobus_alloc(); + if (!bus) + return NULL; + + __module_get(ctrl->ops->owner); + + bus->read = mdiobb_read; + bus->write = mdiobb_write; + bus->priv = ctrl; + + return bus; +} +EXPORT_SYMBOL(alloc_mdio_bitbang); + +void free_mdio_bitbang(struct mii_bus *bus) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + module_put(ctrl->ops->owner); + mdiobus_free(bus); +} +EXPORT_SYMBOL(free_mdio_bitbang); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-cavium.c b/drivers/net/mdio/mdio-cavium.c new file mode 100644 index 000000000000..1afd6fc1a351 --- /dev/null +++ b/drivers/net/mdio/mdio-cavium.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2009-2016 Cavium, Inc. + */ + +#include +#include +#include +#include + +#include "mdio-cavium.h" + +static void cavium_mdiobus_set_mode(struct cavium_mdiobus *p, + enum cavium_mdiobus_mode m) +{ + union cvmx_smix_clk smi_clk; + + if (m == p->mode) + return; + + smi_clk.u64 = oct_mdio_readq(p->register_base + SMI_CLK); + smi_clk.s.mode = (m == C45) ? 1 : 0; + smi_clk.s.preamble = 1; + oct_mdio_writeq(smi_clk.u64, p->register_base + SMI_CLK); + p->mode = m; +} + +static int cavium_mdiobus_c45_addr(struct cavium_mdiobus *p, + int phy_id, int regnum) +{ + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_wr_dat smi_wr; + int timeout = 1000; + + cavium_mdiobus_set_mode(p, C45); + + smi_wr.u64 = 0; + smi_wr.s.dat = regnum & 0xffff; + oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); + + 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; + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); + } while (smi_wr.s.pending && --timeout); + + if (timeout <= 0) + return -EIO; + return 0; +} + +int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) +{ + struct cavium_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 = cavium_mdiobus_c45_addr(p, phy_id, regnum); + + if (r < 0) + return r; + + regnum = (regnum >> 16) & 0x1f; + op = 3; /* MDIO_CLAUSE_45_READ */ + } else { + cavium_mdiobus_set_mode(p, C22); + } + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = op; + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_rd.u64 = oct_mdio_readq(p->register_base + SMI_RD_DAT); + } while (smi_rd.s.pending && --timeout); + + if (smi_rd.s.val) + return smi_rd.s.dat; + else + return -EIO; +} +EXPORT_SYMBOL(cavium_mdiobus_read); + +int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val) +{ + struct cavium_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 = cavium_mdiobus_c45_addr(p, phy_id, regnum); + + if (r < 0) + return r; + + regnum = (regnum >> 16) & 0x1f; + op = 1; /* MDIO_CLAUSE_45_WRITE */ + } else { + cavium_mdiobus_set_mode(p, C22); + } + + smi_wr.u64 = 0; + smi_wr.s.dat = val; + oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = op; + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = regnum; + oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); + + do { + /* Wait 1000 clocks so we don't saturate the RSL bus + * doing reads. + */ + __delay(1000); + smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); + } while (smi_wr.s.pending && --timeout); + + if (timeout <= 0) + return -EIO; + + return 0; +} +EXPORT_SYMBOL(cavium_mdiobus_write); + +MODULE_DESCRIPTION("Common code for OCTEON and Thunder MDIO bus drivers"); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-cavium.h b/drivers/net/mdio/mdio-cavium.h new file mode 100644 index 000000000000..a2245d436f5d --- /dev/null +++ b/drivers/net/mdio/mdio-cavium.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2009-2016 Cavium, Inc. + */ + +enum cavium_mdiobus_mode { + UNINIT = 0, + C22, + C45 +}; + +#define SMI_CMD 0x0 +#define SMI_WR_DAT 0x8 +#define SMI_RD_DAT 0x10 +#define SMI_CLK 0x18 +#define SMI_EN 0x20 + +#ifdef __BIG_ENDIAN_BITFIELD +#define OCT_MDIO_BITFIELD_FIELD(field, more) \ + field; \ + more + +#else +#define OCT_MDIO_BITFIELD_FIELD(field, more) \ + more \ + field; + +#endif + +union cvmx_smix_clk { + u64 u64; + struct cvmx_smix_clk_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_25_63:39, + OCT_MDIO_BITFIELD_FIELD(u64 mode:1, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_21_23:3, + OCT_MDIO_BITFIELD_FIELD(u64 sample_hi:5, + OCT_MDIO_BITFIELD_FIELD(u64 sample_mode:1, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_14_14:1, + OCT_MDIO_BITFIELD_FIELD(u64 clk_idle:1, + OCT_MDIO_BITFIELD_FIELD(u64 preamble:1, + OCT_MDIO_BITFIELD_FIELD(u64 sample:4, + OCT_MDIO_BITFIELD_FIELD(u64 phase:8, + ;)))))))))) + } s; +}; + +union cvmx_smix_cmd { + u64 u64; + struct cvmx_smix_cmd_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 phy_op:2, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_13_15:3, + OCT_MDIO_BITFIELD_FIELD(u64 phy_adr:5, + OCT_MDIO_BITFIELD_FIELD(u64 reserved_5_7:3, + OCT_MDIO_BITFIELD_FIELD(u64 reg_adr:5, + ;)))))) + } s; +}; + +union cvmx_smix_en { + u64 u64; + struct cvmx_smix_en_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_1_63:63, + OCT_MDIO_BITFIELD_FIELD(u64 en:1, + ;)) + } s; +}; + +union cvmx_smix_rd_dat { + u64 u64; + struct cvmx_smix_rd_dat_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 pending:1, + OCT_MDIO_BITFIELD_FIELD(u64 val:1, + OCT_MDIO_BITFIELD_FIELD(u64 dat:16, + ;)))) + } s; +}; + +union cvmx_smix_wr_dat { + u64 u64; + struct cvmx_smix_wr_dat_s { + OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, + OCT_MDIO_BITFIELD_FIELD(u64 pending:1, + OCT_MDIO_BITFIELD_FIELD(u64 val:1, + OCT_MDIO_BITFIELD_FIELD(u64 dat:16, + ;)))) + } s; +}; + +struct cavium_mdiobus { + struct mii_bus *mii_bus; + void __iomem *register_base; + enum cavium_mdiobus_mode mode; +}; + +#ifdef CONFIG_CAVIUM_OCTEON_SOC + +#include + +static inline void oct_mdio_writeq(u64 val, void __iomem *addr) +{ + cvmx_write_csr((u64 __force)addr, val); +} + +static inline u64 oct_mdio_readq(void __iomem *addr) +{ + return cvmx_read_csr((u64 __force)addr); +} +#else +#include + +#define oct_mdio_writeq(val, addr) writeq(val, addr) +#define oct_mdio_readq(addr) readq(addr) +#endif + +int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum); +int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val); diff --git a/drivers/net/mdio/mdio-gpio.c b/drivers/net/mdio/mdio-gpio.c new file mode 100644 index 000000000000..1b00235d7dc5 --- /dev/null +++ b/drivers/net/mdio/mdio-gpio.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * GPIO based MDIO bitbang driver. + * Supports OpenFirmware. + * + * Copyright (c) 2008 CSE Semaphore Belgium. + * by Laurent Pinchart + * + * Copyright (C) 2008, Paulius Zaleckas + * + * Based on earlier work by + * + * Copyright (c) 2003 Intracom S.A. + * by Pantelis Antoniou + * + * 2005 (c) MontaVista Software, Inc. + * Vitaly Bordug + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mdio_gpio_info { + struct mdiobb_ctrl ctrl; + struct gpio_desc *mdc, *mdio, *mdo; +}; + +static int mdio_gpio_get_data(struct device *dev, + struct mdio_gpio_info *bitbang) +{ + bitbang->mdc = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDC, + GPIOD_OUT_LOW); + if (IS_ERR(bitbang->mdc)) + return PTR_ERR(bitbang->mdc); + + bitbang->mdio = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDIO, + GPIOD_IN); + if (IS_ERR(bitbang->mdio)) + return PTR_ERR(bitbang->mdio); + + bitbang->mdo = devm_gpiod_get_index_optional(dev, NULL, MDIO_GPIO_MDO, + GPIOD_OUT_LOW); + return PTR_ERR_OR_ZERO(bitbang->mdo); +} + +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. + */ + gpiod_set_value_cansleep(bitbang->mdo, 1); + return; + } + + if (dir) + gpiod_direction_output(bitbang->mdio, 1); + else + gpiod_direction_input(bitbang->mdio); +} + +static int mdio_get(struct mdiobb_ctrl *ctrl) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + return gpiod_get_value_cansleep(bitbang->mdio); +} + +static void mdio_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + if (bitbang->mdo) + gpiod_set_value_cansleep(bitbang->mdo, what); + else + gpiod_set_value_cansleep(bitbang->mdio, what); +} + +static void mdc_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + gpiod_set_value_cansleep(bitbang->mdc, what); +} + +static const struct mdiobb_ops mdio_gpio_ops = { + .owner = THIS_MODULE, + .set_mdc = mdc_set, + .set_mdio_dir = mdio_dir, + .set_mdio_data = mdio_set, + .get_mdio_data = mdio_get, +}; + +static struct mii_bus *mdio_gpio_bus_init(struct device *dev, + struct mdio_gpio_info *bitbang, + int bus_id) +{ + struct mdio_gpio_platform_data *pdata = dev_get_platdata(dev); + struct mii_bus *new_bus; + + bitbang->ctrl.ops = &mdio_gpio_ops; + + new_bus = alloc_mdio_bitbang(&bitbang->ctrl); + if (!new_bus) + return NULL; + + new_bus->name = "GPIO Bitbanged MDIO"; + new_bus->parent = dev; + + if (bus_id != -1) + snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id); + else + strncpy(new_bus->id, "gpio", MII_BUS_ID_SIZE); + + if (pdata) { + new_bus->phy_mask = pdata->phy_mask; + new_bus->phy_ignore_ta_mask = pdata->phy_ignore_ta_mask; + } + + dev_set_drvdata(dev, new_bus); + + return new_bus; +} + +static void mdio_gpio_bus_deinit(struct device *dev) +{ + struct mii_bus *bus = dev_get_drvdata(dev); + + free_mdio_bitbang(bus); +} + +static void mdio_gpio_bus_destroy(struct device *dev) +{ + struct mii_bus *bus = dev_get_drvdata(dev); + + mdiobus_unregister(bus); + mdio_gpio_bus_deinit(dev); +} + +static int mdio_gpio_probe(struct platform_device *pdev) +{ + struct mdio_gpio_info *bitbang; + struct mii_bus *new_bus; + int ret, bus_id; + + bitbang = devm_kzalloc(&pdev->dev, sizeof(*bitbang), GFP_KERNEL); + if (!bitbang) + return -ENOMEM; + + ret = mdio_gpio_get_data(&pdev->dev, bitbang); + if (ret) + return ret; + + if (pdev->dev.of_node) { + 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 { + bus_id = pdev->id; + } + + new_bus = mdio_gpio_bus_init(&pdev->dev, bitbang, bus_id); + if (!new_bus) + return -ENODEV; + + ret = of_mdiobus_register(new_bus, pdev->dev.of_node); + if (ret) + mdio_gpio_bus_deinit(&pdev->dev); + + return ret; +} + +static int mdio_gpio_remove(struct platform_device *pdev) +{ + mdio_gpio_bus_destroy(&pdev->dev); + + return 0; +} + +static const struct of_device_id mdio_gpio_of_match[] = { + { .compatible = "virtual,mdio-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mdio_gpio_of_match); + +static struct platform_driver mdio_gpio_driver = { + .probe = mdio_gpio_probe, + .remove = mdio_gpio_remove, + .driver = { + .name = "mdio-gpio", + .of_match_table = mdio_gpio_of_match, + }, +}; + +module_platform_driver(mdio_gpio_driver); + +MODULE_ALIAS("platform:mdio-gpio"); +MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Generic driver for MDIO bus emulation using GPIO"); diff --git a/drivers/net/mdio/mdio-hisi-femac.c b/drivers/net/mdio/mdio-hisi-femac.c new file mode 100644 index 000000000000..f231c2fbb1de --- /dev/null +++ b/drivers/net/mdio/mdio-hisi-femac.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hisilicon Fast Ethernet MDIO Bus Driver + * + * Copyright (c) 2016 HiSilicon Technologies Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MDIO_RWCTRL 0x00 +#define MDIO_RO_DATA 0x04 +#define MDIO_WRITE BIT(13) +#define MDIO_RW_FINISH BIT(15) +#define BIT_PHY_ADDR_OFFSET 8 +#define BIT_WR_DATA_OFFSET 16 + +struct hisi_femac_mdio_data { + struct clk *clk; + void __iomem *membase; +}; + +static int hisi_femac_mdio_wait_ready(struct hisi_femac_mdio_data *data) +{ + u32 val; + + return readl_poll_timeout(data->membase + MDIO_RWCTRL, + val, val & MDIO_RW_FINISH, 20, 10000); +} + +static int hisi_femac_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct hisi_femac_mdio_data *data = bus->priv; + int ret; + + ret = hisi_femac_mdio_wait_ready(data); + if (ret) + return ret; + + writel((mii_id << BIT_PHY_ADDR_OFFSET) | regnum, + data->membase + MDIO_RWCTRL); + + ret = hisi_femac_mdio_wait_ready(data); + if (ret) + return ret; + + return readl(data->membase + MDIO_RO_DATA) & 0xFFFF; +} + +static int hisi_femac_mdio_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct hisi_femac_mdio_data *data = bus->priv; + int ret; + + ret = hisi_femac_mdio_wait_ready(data); + if (ret) + return ret; + + writel(MDIO_WRITE | (value << BIT_WR_DATA_OFFSET) | + (mii_id << BIT_PHY_ADDR_OFFSET) | regnum, + data->membase + MDIO_RWCTRL); + + return hisi_femac_mdio_wait_ready(data); +} + +static int hisi_femac_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mii_bus *bus; + struct hisi_femac_mdio_data *data; + int ret; + + bus = mdiobus_alloc_size(sizeof(*data)); + if (!bus) + return -ENOMEM; + + bus->name = "hisi_femac_mii_bus"; + bus->read = &hisi_femac_mdio_read; + bus->write = &hisi_femac_mdio_write; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); + bus->parent = &pdev->dev; + + data = bus->priv; + data->membase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->membase)) { + ret = PTR_ERR(data->membase); + goto err_out_free_mdiobus; + } + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + ret = PTR_ERR(data->clk); + goto err_out_free_mdiobus; + } + + ret = clk_prepare_enable(data->clk); + if (ret) + goto err_out_free_mdiobus; + + ret = of_mdiobus_register(bus, np); + if (ret) + goto err_out_disable_clk; + + platform_set_drvdata(pdev, bus); + + return 0; + +err_out_disable_clk: + clk_disable_unprepare(data->clk); +err_out_free_mdiobus: + mdiobus_free(bus); + return ret; +} + +static int hisi_femac_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + struct hisi_femac_mdio_data *data = bus->priv; + + mdiobus_unregister(bus); + clk_disable_unprepare(data->clk); + mdiobus_free(bus); + + return 0; +} + +static const struct of_device_id hisi_femac_mdio_dt_ids[] = { + { .compatible = "hisilicon,hisi-femac-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, hisi_femac_mdio_dt_ids); + +static struct platform_driver hisi_femac_mdio_driver = { + .probe = hisi_femac_mdio_probe, + .remove = hisi_femac_mdio_remove, + .driver = { + .name = "hisi-femac-mdio", + .of_match_table = hisi_femac_mdio_dt_ids, + }, +}; + +module_platform_driver(hisi_femac_mdio_driver); + +MODULE_DESCRIPTION("Hisilicon Fast Ethernet MAC MDIO interface driver"); +MODULE_AUTHOR("Dongpo Li "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-i2c.c b/drivers/net/mdio/mdio-i2c.c new file mode 100644 index 000000000000..09200a70b315 --- /dev/null +++ b/drivers/net/mdio/mdio-i2c.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MDIO I2C bridge + * + * Copyright (C) 2015-2016 Russell King + * + * Network PHYs can appear on I2C buses when they are part of SFP module. + * This driver exposes these PHYs to the networking PHY code, allowing + * our PHY drivers access to these PHYs, and so allowing configuration + * of their settings. + */ +#include +#include +#include + +/* + * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is + * specified to be present in SFP modules. These correspond with PHY + * addresses 16 and 17. Disallow access to these "phy" addresses. + */ +static bool i2c_mii_valid_phy_id(int phy_id) +{ + return phy_id != 0x10 && phy_id != 0x11; +} + +static unsigned int i2c_mii_phy_addr(int phy_id) +{ + return phy_id + 0x40; +} + +static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msgs[2]; + u8 addr[3], data[2], *p; + int bus_addr, ret; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0xffff; + + p = addr; + if (reg & MII_ADDR_C45) { + *p++ = 0x20 | ((reg >> 16) & 31); + *p++ = reg >> 8; + } + *p++ = reg; + + bus_addr = i2c_mii_phy_addr(phy_id); + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = p - addr; + msgs[0].buf = addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(data); + msgs[1].buf = data; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return 0xffff; + + return data[0] << 8 | data[1]; +} + +static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msg; + int ret; + u8 data[5], *p; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0; + + p = data; + if (reg & MII_ADDR_C45) { + *p++ = (reg >> 16) & 31; + *p++ = reg >> 8; + } + *p++ = reg; + *p++ = val >> 8; + *p++ = val; + + msg.addr = i2c_mii_phy_addr(phy_id); + msg.flags = 0; + msg.len = p - data; + msg.buf = data; + + ret = i2c_transfer(i2c, &msg, 1); + + return ret < 0 ? ret : 0; +} + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c) +{ + struct mii_bus *mii; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return ERR_PTR(-EINVAL); + + mii = mdiobus_alloc(); + if (!mii) + return ERR_PTR(-ENOMEM); + + snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent)); + mii->parent = parent; + mii->read = i2c_mii_read; + mii->write = i2c_mii_write; + mii->priv = i2c; + + return mii; +} +EXPORT_SYMBOL_GPL(mdio_i2c_alloc); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("MDIO I2C bridge library"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-ipq4019.c b/drivers/net/mdio/mdio-ipq4019.c new file mode 100644 index 000000000000..1ce81ff2f41d --- /dev/null +++ b/drivers/net/mdio/mdio-ipq4019.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2020 Sartura Ltd. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MDIO_ADDR_REG 0x44 +#define MDIO_DATA_WRITE_REG 0x48 +#define MDIO_DATA_READ_REG 0x4c +#define MDIO_CMD_REG 0x50 +#define MDIO_CMD_ACCESS_BUSY BIT(16) +#define MDIO_CMD_ACCESS_START BIT(8) +#define MDIO_CMD_ACCESS_CODE_READ 0 +#define MDIO_CMD_ACCESS_CODE_WRITE 1 + +#define ipq4019_MDIO_TIMEOUT 10000 +#define ipq4019_MDIO_SLEEP 10 + +struct ipq4019_mdio_data { + void __iomem *membase; +}; + +static int ipq4019_mdio_wait_busy(struct mii_bus *bus) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int busy; + + return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, + (busy & MDIO_CMD_ACCESS_BUSY) == 0, + ipq4019_MDIO_SLEEP, ipq4019_MDIO_TIMEOUT); +} + +static int ipq4019_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int cmd; + + /* Reject clause 45 */ + if (regnum & MII_ADDR_C45) + return -EOPNOTSUPP; + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + /* issue the phy address and reg */ + writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); + + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; + + /* issue read command */ + writel(cmd, priv->membase + MDIO_CMD_REG); + + /* Wait read complete */ + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + /* Read and return data */ + return readl(priv->membase + MDIO_DATA_READ_REG); +} + +static int ipq4019_mdio_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int cmd; + + /* Reject clause 45 */ + if (regnum & MII_ADDR_C45) + return -EOPNOTSUPP; + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + /* issue the phy address and reg */ + writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); + + /* issue write data */ + writel(value, priv->membase + MDIO_DATA_WRITE_REG); + + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; + /* issue write command */ + writel(cmd, priv->membase + MDIO_CMD_REG); + + /* Wait write complete */ + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + return 0; +} + +static int ipq4019_mdio_probe(struct platform_device *pdev) +{ + struct ipq4019_mdio_data *priv; + struct mii_bus *bus; + int ret; + + bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); + if (!bus) + return -ENOMEM; + + priv = bus->priv; + + priv->membase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->membase)) + return PTR_ERR(priv->membase); + + bus->name = "ipq4019_mdio"; + bus->read = ipq4019_mdio_read; + bus->write = ipq4019_mdio_write; + bus->parent = &pdev->dev; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); + + ret = of_mdiobus_register(bus, pdev->dev.of_node); + if (ret) { + dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); + return ret; + } + + platform_set_drvdata(pdev, bus); + + return 0; +} + +static int ipq4019_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + + return 0; +} + +static const struct of_device_id ipq4019_mdio_dt_ids[] = { + { .compatible = "qcom,ipq4019-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); + +static struct platform_driver ipq4019_mdio_driver = { + .probe = ipq4019_mdio_probe, + .remove = ipq4019_mdio_remove, + .driver = { + .name = "ipq4019-mdio", + .of_match_table = ipq4019_mdio_dt_ids, + }, +}; + +module_platform_driver(ipq4019_mdio_driver); + +MODULE_DESCRIPTION("ipq4019 MDIO interface driver"); +MODULE_AUTHOR("Qualcomm Atheros"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/mdio/mdio-ipq8064.c b/drivers/net/mdio/mdio-ipq8064.c new file mode 100644 index 000000000000..1bd18857e1c5 --- /dev/null +++ b/drivers/net/mdio/mdio-ipq8064.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Qualcomm IPQ8064 MDIO interface driver + * + * Copyright (C) 2019 Christian Lamparter + * Copyright (C) 2020 Ansuel Smith + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* MII address register definitions */ +#define MII_ADDR_REG_ADDR 0x10 +#define MII_BUSY BIT(0) +#define MII_WRITE BIT(1) +#define MII_CLKRANGE_60_100M (0 << 2) +#define MII_CLKRANGE_100_150M (1 << 2) +#define MII_CLKRANGE_20_35M (2 << 2) +#define MII_CLKRANGE_35_60M (3 << 2) +#define MII_CLKRANGE_150_250M (4 << 2) +#define MII_CLKRANGE_250_300M (5 << 2) +#define MII_CLKRANGE_MASK GENMASK(4, 2) +#define MII_REG_SHIFT 6 +#define MII_REG_MASK GENMASK(10, 6) +#define MII_ADDR_SHIFT 11 +#define MII_ADDR_MASK GENMASK(15, 11) + +#define MII_DATA_REG_ADDR 0x14 + +#define MII_MDIO_DELAY_USEC (1000) +#define MII_MDIO_RETRY_MSEC (10) + +struct ipq8064_mdio { + struct regmap *base; /* NSS_GMAC0_BASE */ +}; + +static int +ipq8064_mdio_wait_busy(struct ipq8064_mdio *priv) +{ + u32 busy; + + return regmap_read_poll_timeout(priv->base, MII_ADDR_REG_ADDR, busy, + !(busy & MII_BUSY), MII_MDIO_DELAY_USEC, + MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); +} + +static int +ipq8064_mdio_read(struct mii_bus *bus, int phy_addr, int reg_offset) +{ + u32 miiaddr = MII_BUSY | MII_CLKRANGE_250_300M; + struct ipq8064_mdio *priv = bus->priv; + u32 ret_val; + int err; + + /* Reject clause 45 */ + if (reg_offset & MII_ADDR_C45) + return -EOPNOTSUPP; + + miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) | + ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK); + + regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr); + usleep_range(8, 10); + + err = ipq8064_mdio_wait_busy(priv); + if (err) + return err; + + regmap_read(priv->base, MII_DATA_REG_ADDR, &ret_val); + return (int)ret_val; +} + +static int +ipq8064_mdio_write(struct mii_bus *bus, int phy_addr, int reg_offset, u16 data) +{ + u32 miiaddr = MII_WRITE | MII_BUSY | MII_CLKRANGE_250_300M; + struct ipq8064_mdio *priv = bus->priv; + + /* Reject clause 45 */ + if (reg_offset & MII_ADDR_C45) + return -EOPNOTSUPP; + + regmap_write(priv->base, MII_DATA_REG_ADDR, data); + + miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) | + ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK); + + regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr); + usleep_range(8, 10); + + return ipq8064_mdio_wait_busy(priv); +} + +static int +ipq8064_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct ipq8064_mdio *priv; + struct mii_bus *bus; + int ret; + + bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); + if (!bus) + return -ENOMEM; + + bus->name = "ipq8064_mdio_bus"; + bus->read = ipq8064_mdio_read; + bus->write = ipq8064_mdio_write; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); + bus->parent = &pdev->dev; + + priv = bus->priv; + priv->base = device_node_to_regmap(np); + if (IS_ERR(priv->base)) { + if (priv->base == ERR_PTR(-EPROBE_DEFER)) + return -EPROBE_DEFER; + + dev_err(&pdev->dev, "error getting device regmap, error=%pe\n", + priv->base); + return PTR_ERR(priv->base); + } + + ret = of_mdiobus_register(bus, np); + if (ret) + return ret; + + platform_set_drvdata(pdev, bus); + return 0; +} + +static int +ipq8064_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + + return 0; +} + +static const struct of_device_id ipq8064_mdio_dt_ids[] = { + { .compatible = "qcom,ipq8064-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, ipq8064_mdio_dt_ids); + +static struct platform_driver ipq8064_mdio_driver = { + .probe = ipq8064_mdio_probe, + .remove = ipq8064_mdio_remove, + .driver = { + .name = "ipq8064-mdio", + .of_match_table = ipq8064_mdio_dt_ids, + }, +}; + +module_platform_driver(ipq8064_mdio_driver); + +MODULE_DESCRIPTION("Qualcomm IPQ8064 MDIO interface driver"); +MODULE_AUTHOR("Christian Lamparter "); +MODULE_AUTHOR("Ansuel Smith "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-moxart.c b/drivers/net/mdio/mdio-moxart.c new file mode 100644 index 000000000000..b72c6d185175 --- /dev/null +++ b/drivers/net/mdio/mdio-moxart.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver + * + * Copyright (C) 2013 Jonas Jensen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + 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; + + /* 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; + data->base = devm_platform_ioremap_resource(pdev, 0); + 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 "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-mscc-miim.c b/drivers/net/mdio/mdio-mscc-miim.c new file mode 100644 index 000000000000..11f583fd4611 --- /dev/null +++ b/drivers/net/mdio/mdio-mscc-miim.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Driver for the MDIO interface of Microsemi network switches. + * + * Author: Alexandre Belloni + * Copyright (c) 2017 Microsemi Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSCC_MIIM_REG_STATUS 0x0 +#define MSCC_MIIM_STATUS_STAT_PENDING BIT(2) +#define MSCC_MIIM_STATUS_STAT_BUSY BIT(3) +#define MSCC_MIIM_REG_CMD 0x8 +#define MSCC_MIIM_CMD_OPR_WRITE BIT(1) +#define MSCC_MIIM_CMD_OPR_READ BIT(2) +#define MSCC_MIIM_CMD_WRDATA_SHIFT 4 +#define MSCC_MIIM_CMD_REGAD_SHIFT 20 +#define MSCC_MIIM_CMD_PHYAD_SHIFT 25 +#define MSCC_MIIM_CMD_VLD BIT(31) +#define MSCC_MIIM_REG_DATA 0xC +#define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17)) + +#define MSCC_PHY_REG_PHY_CFG 0x0 +#define PHY_CFG_PHY_ENA (BIT(0) | BIT(1) | BIT(2) | BIT(3)) +#define PHY_CFG_PHY_COMMON_RESET BIT(4) +#define PHY_CFG_PHY_RESET (BIT(5) | BIT(6) | BIT(7) | BIT(8)) +#define MSCC_PHY_REG_PHY_STATUS 0x4 + +struct mscc_miim_dev { + void __iomem *regs; + void __iomem *phy_regs; +}; + +/* When high resolution timers aren't built-in: we can't use usleep_range() as + * we would sleep way too long. Use udelay() instead. + */ +#define mscc_readl_poll_timeout(addr, val, cond, delay_us, timeout_us) \ +({ \ + if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS)) \ + readl_poll_timeout_atomic(addr, val, cond, delay_us, \ + timeout_us); \ + readl_poll_timeout(addr, val, cond, delay_us, timeout_us); \ +}) + +static int mscc_miim_wait_ready(struct mii_bus *bus) +{ + struct mscc_miim_dev *miim = bus->priv; + u32 val; + + return mscc_readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val, + !(val & MSCC_MIIM_STATUS_STAT_BUSY), 50, + 10000); +} + +static int mscc_miim_wait_pending(struct mii_bus *bus) +{ + struct mscc_miim_dev *miim = bus->priv; + u32 val; + + return mscc_readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val, + !(val & MSCC_MIIM_STATUS_STAT_PENDING), + 50, 10000); +} + +static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct mscc_miim_dev *miim = bus->priv; + u32 val; + int ret; + + ret = mscc_miim_wait_pending(bus); + if (ret) + goto out; + + writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | + (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ, + miim->regs + MSCC_MIIM_REG_CMD); + + ret = mscc_miim_wait_ready(bus); + if (ret) + goto out; + + val = readl(miim->regs + MSCC_MIIM_REG_DATA); + if (val & MSCC_MIIM_DATA_ERROR) { + ret = -EIO; + goto out; + } + + ret = val & 0xFFFF; +out: + return ret; +} + +static int mscc_miim_write(struct mii_bus *bus, int mii_id, + int regnum, u16 value) +{ + struct mscc_miim_dev *miim = bus->priv; + int ret; + + ret = mscc_miim_wait_pending(bus); + if (ret < 0) + goto out; + + writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | + (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | + (value << MSCC_MIIM_CMD_WRDATA_SHIFT) | + MSCC_MIIM_CMD_OPR_WRITE, + miim->regs + MSCC_MIIM_REG_CMD); + +out: + return ret; +} + +static int mscc_miim_reset(struct mii_bus *bus) +{ + struct mscc_miim_dev *miim = bus->priv; + + if (miim->phy_regs) { + writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG); + writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG); + mdelay(500); + } + + return 0; +} + +static int mscc_miim_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mii_bus *bus; + struct mscc_miim_dev *dev; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev)); + if (!bus) + return -ENOMEM; + + bus->name = "mscc_miim"; + bus->read = mscc_miim_read; + bus->write = mscc_miim_write; + bus->reset = mscc_miim_reset; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); + bus->parent = &pdev->dev; + + dev = bus->priv; + dev->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->regs)) { + dev_err(&pdev->dev, "Unable to map MIIM registers\n"); + return PTR_ERR(dev->regs); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + dev->phy_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->phy_regs)) { + dev_err(&pdev->dev, "Unable to map internal phy registers\n"); + return PTR_ERR(dev->phy_regs); + } + } + + ret = of_mdiobus_register(bus, pdev->dev.of_node); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); + return ret; + } + + platform_set_drvdata(pdev, bus); + + return 0; +} + +static int mscc_miim_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + + return 0; +} + +static const struct of_device_id mscc_miim_match[] = { + { .compatible = "mscc,ocelot-miim" }, + { } +}; +MODULE_DEVICE_TABLE(of, mscc_miim_match); + +static struct platform_driver mscc_miim_driver = { + .probe = mscc_miim_probe, + .remove = mscc_miim_remove, + .driver = { + .name = "mscc-miim", + .of_match_table = mscc_miim_match, + }, +}; + +module_platform_driver(mscc_miim_driver); + +MODULE_DESCRIPTION("Microsemi MIIM driver"); +MODULE_AUTHOR("Alexandre Belloni "); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/net/mdio/mdio-mux-bcm-iproc.c b/drivers/net/mdio/mdio-mux-bcm-iproc.c new file mode 100644 index 000000000000..42fb5f166136 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-bcm-iproc.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2016 Broadcom + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MDIO_RATE_ADJ_EXT_OFFSET 0x000 +#define MDIO_RATE_ADJ_INT_OFFSET 0x004 +#define MDIO_RATE_ADJ_DIVIDENT_SHIFT 16 + +#define MDIO_SCAN_CTRL_OFFSET 0x008 +#define MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR 28 + +#define MDIO_PARAM_OFFSET 0x23c +#define MDIO_PARAM_MIIM_CYCLE 29 +#define MDIO_PARAM_INTERNAL_SEL 25 +#define MDIO_PARAM_BUS_ID 22 +#define MDIO_PARAM_C45_SEL 21 +#define MDIO_PARAM_PHY_ID 16 +#define MDIO_PARAM_PHY_DATA 0 + +#define MDIO_READ_OFFSET 0x240 +#define MDIO_READ_DATA_MASK 0xffff +#define MDIO_ADDR_OFFSET 0x244 + +#define MDIO_CTRL_OFFSET 0x248 +#define MDIO_CTRL_WRITE_OP 0x1 +#define MDIO_CTRL_READ_OP 0x2 + +#define MDIO_STAT_OFFSET 0x24c +#define MDIO_STAT_DONE 1 + +#define BUS_MAX_ADDR 32 +#define EXT_BUS_START_ADDR 16 + +#define MDIO_REG_ADDR_SPACE_SIZE 0x250 + +#define MDIO_OPERATING_FREQUENCY 11000000 +#define MDIO_RATE_ADJ_DIVIDENT 1 + +struct iproc_mdiomux_desc { + void *mux_handle; + void __iomem *base; + struct device *dev; + struct mii_bus *mii_bus; + struct clk *core_clk; +}; + +static void mdio_mux_iproc_config(struct iproc_mdiomux_desc *md) +{ + u32 divisor; + u32 val; + + /* Disable external mdio master access */ + val = readl(md->base + MDIO_SCAN_CTRL_OFFSET); + val |= BIT(MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR); + writel(val, md->base + MDIO_SCAN_CTRL_OFFSET); + + if (md->core_clk) { + /* use rate adjust regs to derrive the mdio's operating + * frequency from the specified core clock + */ + divisor = clk_get_rate(md->core_clk) / MDIO_OPERATING_FREQUENCY; + divisor = divisor / (MDIO_RATE_ADJ_DIVIDENT + 1); + val = divisor; + val |= MDIO_RATE_ADJ_DIVIDENT << MDIO_RATE_ADJ_DIVIDENT_SHIFT; + writel(val, md->base + MDIO_RATE_ADJ_EXT_OFFSET); + writel(val, md->base + MDIO_RATE_ADJ_INT_OFFSET); + } +} + +static int iproc_mdio_wait_for_idle(void __iomem *base, bool result) +{ + u32 val; + + return readl_poll_timeout(base + MDIO_STAT_OFFSET, val, + (val & MDIO_STAT_DONE) == result, + 2000, 1000000); +} + +/* start_miim_ops- Program and start MDIO transaction over mdio bus. + * @base: Base address + * @phyid: phyid of the selected bus. + * @reg: register offset to be read/written. + * @val :0 if read op else value to be written in @reg; + * @op: Operation that need to be carried out. + * MDIO_CTRL_READ_OP: Read transaction. + * MDIO_CTRL_WRITE_OP: Write transaction. + * + * Return value: Successful Read operation returns read reg values and write + * operation returns 0. Failure operation returns negative error code. + */ +static int start_miim_ops(void __iomem *base, + u16 phyid, u32 reg, u16 val, u32 op) +{ + u32 param; + int ret; + + writel(0, base + MDIO_CTRL_OFFSET); + ret = iproc_mdio_wait_for_idle(base, 0); + if (ret) + goto err; + + param = readl(base + MDIO_PARAM_OFFSET); + param |= phyid << MDIO_PARAM_PHY_ID; + param |= val << MDIO_PARAM_PHY_DATA; + if (reg & MII_ADDR_C45) + param |= BIT(MDIO_PARAM_C45_SEL); + + writel(param, base + MDIO_PARAM_OFFSET); + + writel(reg, base + MDIO_ADDR_OFFSET); + + writel(op, base + MDIO_CTRL_OFFSET); + + ret = iproc_mdio_wait_for_idle(base, 1); + if (ret) + goto err; + + if (op == MDIO_CTRL_READ_OP) + ret = readl(base + MDIO_READ_OFFSET) & MDIO_READ_DATA_MASK; +err: + return ret; +} + +static int iproc_mdiomux_read(struct mii_bus *bus, int phyid, int reg) +{ + struct iproc_mdiomux_desc *md = bus->priv; + int ret; + + ret = start_miim_ops(md->base, phyid, reg, 0, MDIO_CTRL_READ_OP); + if (ret < 0) + dev_err(&bus->dev, "mdiomux read operation failed!!!"); + + return ret; +} + +static int iproc_mdiomux_write(struct mii_bus *bus, + int phyid, int reg, u16 val) +{ + struct iproc_mdiomux_desc *md = bus->priv; + int ret; + + /* Write val at reg offset */ + ret = start_miim_ops(md->base, phyid, reg, val, MDIO_CTRL_WRITE_OP); + if (ret < 0) + dev_err(&bus->dev, "mdiomux write operation failed!!!"); + + return ret; +} + +static int mdio_mux_iproc_switch_fn(int current_child, int desired_child, + void *data) +{ + struct iproc_mdiomux_desc *md = data; + u32 param, bus_id; + bool bus_dir; + + /* select bus and its properties */ + bus_dir = (desired_child < EXT_BUS_START_ADDR); + bus_id = bus_dir ? desired_child : (desired_child - EXT_BUS_START_ADDR); + + param = (bus_dir ? 1 : 0) << MDIO_PARAM_INTERNAL_SEL; + param |= (bus_id << MDIO_PARAM_BUS_ID); + + writel(param, md->base + MDIO_PARAM_OFFSET); + return 0; +} + +static int mdio_mux_iproc_probe(struct platform_device *pdev) +{ + struct iproc_mdiomux_desc *md; + struct mii_bus *bus; + struct resource *res; + int rc; + + md = devm_kzalloc(&pdev->dev, sizeof(*md), GFP_KERNEL); + if (!md) + return -ENOMEM; + md->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res->start & 0xfff) { + /* For backward compatibility in case the + * base address is specified with an offset. + */ + dev_info(&pdev->dev, "fix base address in dt-blob\n"); + res->start &= ~0xfff; + res->end = res->start + MDIO_REG_ADDR_SPACE_SIZE - 1; + } + md->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(md->base)) { + dev_err(&pdev->dev, "failed to ioremap register\n"); + return PTR_ERR(md->base); + } + + md->mii_bus = devm_mdiobus_alloc(&pdev->dev); + if (!md->mii_bus) { + dev_err(&pdev->dev, "mdiomux bus alloc failed\n"); + return -ENOMEM; + } + + md->core_clk = devm_clk_get(&pdev->dev, NULL); + if (md->core_clk == ERR_PTR(-ENOENT) || + md->core_clk == ERR_PTR(-EINVAL)) + md->core_clk = NULL; + else if (IS_ERR(md->core_clk)) + return PTR_ERR(md->core_clk); + + rc = clk_prepare_enable(md->core_clk); + if (rc) { + dev_err(&pdev->dev, "failed to enable core clk\n"); + return rc; + } + + bus = md->mii_bus; + bus->priv = md; + bus->name = "iProc MDIO mux bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); + bus->parent = &pdev->dev; + bus->read = iproc_mdiomux_read; + bus->write = iproc_mdiomux_write; + + bus->phy_mask = ~0; + bus->dev.of_node = pdev->dev.of_node; + rc = mdiobus_register(bus); + if (rc) { + dev_err(&pdev->dev, "mdiomux registration failed\n"); + goto out_clk; + } + + platform_set_drvdata(pdev, md); + + rc = mdio_mux_init(md->dev, md->dev->of_node, mdio_mux_iproc_switch_fn, + &md->mux_handle, md, md->mii_bus); + if (rc) { + dev_info(md->dev, "mdiomux initialization failed\n"); + goto out_register; + } + + mdio_mux_iproc_config(md); + + dev_info(md->dev, "iProc mdiomux registered\n"); + return 0; + +out_register: + mdiobus_unregister(bus); +out_clk: + clk_disable_unprepare(md->core_clk); + return rc; +} + +static int mdio_mux_iproc_remove(struct platform_device *pdev) +{ + struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev); + + mdio_mux_uninit(md->mux_handle); + mdiobus_unregister(md->mii_bus); + clk_disable_unprepare(md->core_clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mdio_mux_iproc_suspend(struct device *dev) +{ + struct iproc_mdiomux_desc *md = dev_get_drvdata(dev); + + clk_disable_unprepare(md->core_clk); + + return 0; +} + +static int mdio_mux_iproc_resume(struct device *dev) +{ + struct iproc_mdiomux_desc *md = dev_get_drvdata(dev); + int rc; + + rc = clk_prepare_enable(md->core_clk); + if (rc) { + dev_err(md->dev, "failed to enable core clk\n"); + return rc; + } + mdio_mux_iproc_config(md); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mdio_mux_iproc_pm_ops, + mdio_mux_iproc_suspend, mdio_mux_iproc_resume); + +static const struct of_device_id mdio_mux_iproc_match[] = { + { + .compatible = "brcm,mdio-mux-iproc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mdio_mux_iproc_match); + +static struct platform_driver mdiomux_iproc_driver = { + .driver = { + .name = "mdio-mux-iproc", + .of_match_table = mdio_mux_iproc_match, + .pm = &mdio_mux_iproc_pm_ops, + }, + .probe = mdio_mux_iproc_probe, + .remove = mdio_mux_iproc_remove, +}; + +module_platform_driver(mdiomux_iproc_driver); + +MODULE_DESCRIPTION("iProc MDIO Mux Bus Driver"); +MODULE_AUTHOR("Pramod Kumar "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-mux-gpio.c b/drivers/net/mdio/mdio-mux-gpio.c new file mode 100644 index 000000000000..10a758fdc9e6 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-gpio.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011, 2012 Cavium, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_VERSION "1.1" +#define DRV_DESCRIPTION "GPIO controlled MDIO bus multiplexer driver" + +struct mdio_mux_gpio_state { + struct gpio_descs *gpios; + void *mux_handle; +}; + +static int mdio_mux_gpio_switch_fn(int current_child, int desired_child, + void *data) +{ + struct mdio_mux_gpio_state *s = data; + DECLARE_BITMAP(values, BITS_PER_TYPE(desired_child)); + + if (current_child == desired_child) + return 0; + + values[0] = desired_child; + + gpiod_set_array_value_cansleep(s->gpios->ndescs, s->gpios->desc, + s->gpios->info, values); + + return 0; +} + +static int mdio_mux_gpio_probe(struct platform_device *pdev) +{ + struct mdio_mux_gpio_state *s; + struct gpio_descs *gpios; + int r; + + gpios = devm_gpiod_get_array(&pdev->dev, NULL, GPIOD_OUT_LOW); + if (IS_ERR(gpios)) + return PTR_ERR(gpios); + + s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + s->gpios = gpios; + + r = mdio_mux_init(&pdev->dev, pdev->dev.of_node, + mdio_mux_gpio_switch_fn, &s->mux_handle, s, NULL); + + if (r != 0) + return r; + + pdev->dev.platform_data = s; + return 0; +} + +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 const 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", + .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 v2"); diff --git a/drivers/net/mdio/mdio-mux-meson-g12a.c b/drivers/net/mdio/mdio-mux-meson-g12a.c new file mode 100644 index 000000000000..bf86c9c7a288 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-meson-g12a.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2019 Baylibre, SAS. + * Author: Jerome Brunet + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ETH_PLL_STS 0x40 +#define ETH_PLL_CTL0 0x44 +#define PLL_CTL0_LOCK_DIG BIT(30) +#define PLL_CTL0_RST BIT(29) +#define PLL_CTL0_EN BIT(28) +#define PLL_CTL0_SEL BIT(23) +#define PLL_CTL0_N GENMASK(14, 10) +#define PLL_CTL0_M GENMASK(8, 0) +#define PLL_LOCK_TIMEOUT 1000000 +#define PLL_MUX_NUM_PARENT 2 +#define ETH_PLL_CTL1 0x48 +#define ETH_PLL_CTL2 0x4c +#define ETH_PLL_CTL3 0x50 +#define ETH_PLL_CTL4 0x54 +#define ETH_PLL_CTL5 0x58 +#define ETH_PLL_CTL6 0x5c +#define ETH_PLL_CTL7 0x60 + +#define ETH_PHY_CNTL0 0x80 +#define EPHY_G12A_ID 0x33010180 +#define ETH_PHY_CNTL1 0x84 +#define PHY_CNTL1_ST_MODE GENMASK(2, 0) +#define PHY_CNTL1_ST_PHYADD GENMASK(7, 3) +#define EPHY_DFLT_ADD 8 +#define PHY_CNTL1_MII_MODE GENMASK(15, 14) +#define EPHY_MODE_RMII 0x1 +#define PHY_CNTL1_CLK_EN BIT(16) +#define PHY_CNTL1_CLKFREQ BIT(17) +#define PHY_CNTL1_PHY_ENB BIT(18) +#define ETH_PHY_CNTL2 0x88 +#define PHY_CNTL2_USE_INTERNAL BIT(5) +#define PHY_CNTL2_SMI_SRC_MAC BIT(6) +#define PHY_CNTL2_RX_CLK_EPHY BIT(9) + +#define MESON_G12A_MDIO_EXTERNAL_ID 0 +#define MESON_G12A_MDIO_INTERNAL_ID 1 + +struct g12a_mdio_mux { + bool pll_is_enabled; + void __iomem *regs; + void *mux_handle; + struct clk *pclk; + struct clk *pll; +}; + +struct g12a_ephy_pll { + void __iomem *base; + struct clk_hw hw; +}; + +#define g12a_ephy_pll_to_dev(_hw) \ + container_of(_hw, struct g12a_ephy_pll, hw) + +static unsigned long g12a_ephy_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); + u32 val, m, n; + + val = readl(pll->base + ETH_PLL_CTL0); + m = FIELD_GET(PLL_CTL0_M, val); + n = FIELD_GET(PLL_CTL0_N, val); + + return parent_rate * m / n; +} + +static int g12a_ephy_pll_enable(struct clk_hw *hw) +{ + struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); + u32 val = readl(pll->base + ETH_PLL_CTL0); + + /* Apply both enable an reset */ + val |= PLL_CTL0_RST | PLL_CTL0_EN; + writel(val, pll->base + ETH_PLL_CTL0); + + /* Clear the reset to let PLL lock */ + val &= ~PLL_CTL0_RST; + writel(val, pll->base + ETH_PLL_CTL0); + + /* Poll on the digital lock instead of the usual analog lock + * This is done because bit 31 is unreliable on some SoC. Bit + * 31 may indicate that the PLL is not lock eventhough the clock + * is actually running + */ + return readl_poll_timeout(pll->base + ETH_PLL_CTL0, val, + val & PLL_CTL0_LOCK_DIG, 0, PLL_LOCK_TIMEOUT); +} + +static void g12a_ephy_pll_disable(struct clk_hw *hw) +{ + struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); + u32 val; + + val = readl(pll->base + ETH_PLL_CTL0); + val &= ~PLL_CTL0_EN; + val |= PLL_CTL0_RST; + writel(val, pll->base + ETH_PLL_CTL0); +} + +static int g12a_ephy_pll_is_enabled(struct clk_hw *hw) +{ + struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); + unsigned int val; + + val = readl(pll->base + ETH_PLL_CTL0); + + return (val & PLL_CTL0_LOCK_DIG) ? 1 : 0; +} + +static int g12a_ephy_pll_init(struct clk_hw *hw) +{ + struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); + + /* Apply PLL HW settings */ + writel(0x29c0040a, pll->base + ETH_PLL_CTL0); + writel(0x927e0000, pll->base + ETH_PLL_CTL1); + writel(0xac5f49e5, pll->base + ETH_PLL_CTL2); + writel(0x00000000, pll->base + ETH_PLL_CTL3); + writel(0x00000000, pll->base + ETH_PLL_CTL4); + writel(0x20200000, pll->base + ETH_PLL_CTL5); + writel(0x0000c002, pll->base + ETH_PLL_CTL6); + writel(0x00000023, pll->base + ETH_PLL_CTL7); + + return 0; +} + +static const struct clk_ops g12a_ephy_pll_ops = { + .recalc_rate = g12a_ephy_pll_recalc_rate, + .is_enabled = g12a_ephy_pll_is_enabled, + .enable = g12a_ephy_pll_enable, + .disable = g12a_ephy_pll_disable, + .init = g12a_ephy_pll_init, +}; + +static int g12a_enable_internal_mdio(struct g12a_mdio_mux *priv) +{ + int ret; + + /* Enable the phy clock */ + if (!priv->pll_is_enabled) { + ret = clk_prepare_enable(priv->pll); + if (ret) + return ret; + } + + priv->pll_is_enabled = true; + + /* Initialize ephy control */ + writel(EPHY_G12A_ID, priv->regs + ETH_PHY_CNTL0); + writel(FIELD_PREP(PHY_CNTL1_ST_MODE, 3) | + FIELD_PREP(PHY_CNTL1_ST_PHYADD, EPHY_DFLT_ADD) | + FIELD_PREP(PHY_CNTL1_MII_MODE, EPHY_MODE_RMII) | + PHY_CNTL1_CLK_EN | + PHY_CNTL1_CLKFREQ | + PHY_CNTL1_PHY_ENB, + priv->regs + ETH_PHY_CNTL1); + writel(PHY_CNTL2_USE_INTERNAL | + PHY_CNTL2_SMI_SRC_MAC | + PHY_CNTL2_RX_CLK_EPHY, + priv->regs + ETH_PHY_CNTL2); + + return 0; +} + +static int g12a_enable_external_mdio(struct g12a_mdio_mux *priv) +{ + /* Reset the mdio bus mux */ + writel_relaxed(0x0, priv->regs + ETH_PHY_CNTL2); + + /* Disable the phy clock if enabled */ + if (priv->pll_is_enabled) { + clk_disable_unprepare(priv->pll); + priv->pll_is_enabled = false; + } + + return 0; +} + +static int g12a_mdio_switch_fn(int current_child, int desired_child, + void *data) +{ + struct g12a_mdio_mux *priv = dev_get_drvdata(data); + + if (current_child == desired_child) + return 0; + + switch (desired_child) { + case MESON_G12A_MDIO_EXTERNAL_ID: + return g12a_enable_external_mdio(priv); + case MESON_G12A_MDIO_INTERNAL_ID: + return g12a_enable_internal_mdio(priv); + default: + return -EINVAL; + } +} + +static const struct of_device_id g12a_mdio_mux_match[] = { + { .compatible = "amlogic,g12a-mdio-mux", }, + {}, +}; +MODULE_DEVICE_TABLE(of, g12a_mdio_mux_match); + +static int g12a_ephy_glue_clk_register(struct device *dev) +{ + struct g12a_mdio_mux *priv = dev_get_drvdata(dev); + const char *parent_names[PLL_MUX_NUM_PARENT]; + struct clk_init_data init; + struct g12a_ephy_pll *pll; + struct clk_mux *mux; + struct clk *clk; + char *name; + int i; + + /* get the mux parents */ + for (i = 0; i < PLL_MUX_NUM_PARENT; i++) { + char in_name[8]; + + snprintf(in_name, sizeof(in_name), "clkin%d", i); + clk = devm_clk_get(dev, in_name); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) != -EPROBE_DEFER) + dev_err(dev, "Missing clock %s\n", in_name); + return PTR_ERR(clk); + } + + parent_names[i] = __clk_get_name(clk); + } + + /* create the input mux */ + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + name = kasprintf(GFP_KERNEL, "%s#mux", dev_name(dev)); + if (!name) + return -ENOMEM; + + init.name = name; + init.ops = &clk_mux_ro_ops; + init.flags = 0; + init.parent_names = parent_names; + init.num_parents = PLL_MUX_NUM_PARENT; + + mux->reg = priv->regs + ETH_PLL_CTL0; + mux->shift = __ffs(PLL_CTL0_SEL); + mux->mask = PLL_CTL0_SEL >> mux->shift; + mux->hw.init = &init; + + clk = devm_clk_register(dev, &mux->hw); + kfree(name); + if (IS_ERR(clk)) { + dev_err(dev, "failed to register input mux\n"); + return PTR_ERR(clk); + } + + /* create the pll */ + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return -ENOMEM; + + name = kasprintf(GFP_KERNEL, "%s#pll", dev_name(dev)); + if (!name) + return -ENOMEM; + + init.name = name; + init.ops = &g12a_ephy_pll_ops; + init.flags = 0; + parent_names[0] = __clk_get_name(clk); + init.parent_names = parent_names; + init.num_parents = 1; + + pll->base = priv->regs; + pll->hw.init = &init; + + clk = devm_clk_register(dev, &pll->hw); + kfree(name); + if (IS_ERR(clk)) { + dev_err(dev, "failed to register input mux\n"); + return PTR_ERR(clk); + } + + priv->pll = clk; + + return 0; +} + +static int g12a_mdio_mux_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct g12a_mdio_mux *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + priv->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(priv->pclk)) { + ret = PTR_ERR(priv->pclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get peripheral clock\n"); + return ret; + } + + /* Make sure the device registers are clocked */ + ret = clk_prepare_enable(priv->pclk); + if (ret) { + dev_err(dev, "failed to enable peripheral clock"); + return ret; + } + + /* Register PLL in CCF */ + ret = g12a_ephy_glue_clk_register(dev); + if (ret) + goto err; + + ret = mdio_mux_init(dev, dev->of_node, g12a_mdio_switch_fn, + &priv->mux_handle, dev, NULL); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "mdio multiplexer init failed: %d", ret); + goto err; + } + + return 0; + +err: + clk_disable_unprepare(priv->pclk); + return ret; +} + +static int g12a_mdio_mux_remove(struct platform_device *pdev) +{ + struct g12a_mdio_mux *priv = platform_get_drvdata(pdev); + + mdio_mux_uninit(priv->mux_handle); + + if (priv->pll_is_enabled) + clk_disable_unprepare(priv->pll); + + clk_disable_unprepare(priv->pclk); + + return 0; +} + +static struct platform_driver g12a_mdio_mux_driver = { + .probe = g12a_mdio_mux_probe, + .remove = g12a_mdio_mux_remove, + .driver = { + .name = "g12a-mdio_mux", + .of_match_table = g12a_mdio_mux_match, + }, +}; +module_platform_driver(g12a_mdio_mux_driver); + +MODULE_DESCRIPTION("Amlogic G12a MDIO multiplexer driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-mux-mmioreg.c b/drivers/net/mdio/mdio-mux-mmioreg.c new file mode 100644 index 000000000000..d1a8780e24d8 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-mmioreg.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Simple memory-mapped device MDIO MUX driver + * + * Author: Timur Tabi + * + * Copyright 2012 Freescale Semiconductor, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct mdio_mux_mmioreg_state { + void *mux_handle; + phys_addr_t phys; + unsigned int iosize; + unsigned int 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, s->iosize); + if (!p) + return -ENOMEM; + + switch (s->iosize) { + case sizeof(uint8_t): { + uint8_t x, y; + + 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); + } + + break; + } + case sizeof(uint16_t): { + uint16_t x, y; + + x = ioread16(p); + y = (x & ~s->mask) | desired_child; + if (x != y) { + iowrite16((x & ~s->mask) | desired_child, p); + pr_debug("%s: %04x -> %04x\n", __func__, x, y); + } + + break; + } + case sizeof(uint32_t): { + uint32_t x, y; + + x = ioread32(p); + y = (x & ~s->mask) | desired_child; + if (x != y) { + iowrite32((x & ~s->mask) | desired_child, p); + pr_debug("%s: %08x -> %08x\n", __func__, x, y); + } + + break; + } + } + + 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 %pOF\n", np); + + 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 %pOF\n", + np); + return ret; + } + s->phys = res.start; + + s->iosize = resource_size(&res); + if (s->iosize != sizeof(uint8_t) && + s->iosize != sizeof(uint16_t) && + s->iosize != sizeof(uint32_t)) { + dev_err(&pdev->dev, "only 8/16/32-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) >= BIT(s->iosize * 8)) { + dev_err(&pdev->dev, "only 8/16/32-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 %pOF is " + "missing a 'reg' property\n", np2); + of_node_put(np2); + return -ENODEV; + } + if (be32_to_cpup(iprop) & ~s->mask) { + dev_err(&pdev->dev, "mdio-mux child node %pOF has " + "a 'reg' value with unmasked bits\n", + np2); + of_node_put(np2); + return -ENODEV; + } + } + + ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, + mdio_mux_mmioreg_switch_fn, + &s->mux_handle, s, NULL); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "failed to register mdio-mux bus %pOF\n", np); + 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 const 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", + .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 "); +MODULE_DESCRIPTION("Memory-mapped device MDIO MUX driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-mux-multiplexer.c b/drivers/net/mdio/mdio-mux-multiplexer.c new file mode 100644 index 000000000000..d6564381aa3e --- /dev/null +++ b/drivers/net/mdio/mdio-mux-multiplexer.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* MDIO bus multiplexer using kernel multiplexer subsystem + * + * Copyright 2019 NXP + */ + +#include +#include +#include +#include + +struct mdio_mux_multiplexer_state { + struct mux_control *muxc; + bool do_deselect; + void *mux_handle; +}; + +/** + * mdio_mux_multiplexer_switch_fn - This function is called by the mdio-mux + * layer when it thinks the mdio bus + * multiplexer needs to switch. + * @current_child: current value of the mux register. + * @desired_child: value of the 'reg' property of the target child MDIO node. + * @data: Private data used by this switch_fn passed to mdio_mux_init function + * via mdio_mux_init(.., .., .., .., data, ..). + * + * 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_multiplexer_switch_fn(int current_child, int desired_child, + void *data) +{ + struct platform_device *pdev; + struct mdio_mux_multiplexer_state *s; + int ret = 0; + + pdev = (struct platform_device *)data; + s = platform_get_drvdata(pdev); + + if (!(current_child ^ desired_child)) + return 0; + + if (s->do_deselect) + ret = mux_control_deselect(s->muxc); + if (ret) { + dev_err(&pdev->dev, "mux_control_deselect failed in %s: %d\n", + __func__, ret); + return ret; + } + + ret = mux_control_select(s->muxc, desired_child); + if (!ret) { + dev_dbg(&pdev->dev, "%s %d -> %d\n", __func__, current_child, + desired_child); + s->do_deselect = true; + } else { + s->do_deselect = false; + } + + return ret; +} + +static int mdio_mux_multiplexer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mdio_mux_multiplexer_state *s; + int ret = 0; + + s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + s->muxc = devm_mux_control_get(dev, NULL); + if (IS_ERR(s->muxc)) { + ret = PTR_ERR(s->muxc); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get mux: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, s); + + ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, + mdio_mux_multiplexer_switch_fn, &s->mux_handle, + pdev, NULL); + + return ret; +} + +static int mdio_mux_multiplexer_remove(struct platform_device *pdev) +{ + struct mdio_mux_multiplexer_state *s = platform_get_drvdata(pdev); + + mdio_mux_uninit(s->mux_handle); + + if (s->do_deselect) + mux_control_deselect(s->muxc); + + return 0; +} + +static const struct of_device_id mdio_mux_multiplexer_match[] = { + { .compatible = "mdio-mux-multiplexer", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mdio_mux_multiplexer_match); + +static struct platform_driver mdio_mux_multiplexer_driver = { + .driver = { + .name = "mdio-mux-multiplexer", + .of_match_table = mdio_mux_multiplexer_match, + }, + .probe = mdio_mux_multiplexer_probe, + .remove = mdio_mux_multiplexer_remove, +}; + +module_platform_driver(mdio_mux_multiplexer_driver); + +MODULE_DESCRIPTION("MDIO bus multiplexer using kernel multiplexer subsystem"); +MODULE_AUTHOR("Pankaj Bansal "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-mux.c b/drivers/net/mdio/mdio-mux.c new file mode 100644 index 000000000000..6a1d3540210b --- /dev/null +++ b/drivers/net/mdio/mdio-mux.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011, 2012 Cavium, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#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; +}; + +/* + * 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; + + mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX); + 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, MDIO_MUTEX_MUX); + 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, + struct device_node *mux_node, + int (*switch_fn)(int cur, int desired, void *data), + void **mux_handle, + void *data, + struct mii_bus *mux_bus) +{ + 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 (!mux_node) + return -ENODEV; + + if (!mux_bus) { + parent_bus_node = of_parse_phandle(mux_node, + "mdio-parent-bus", 0); + + if (!parent_bus_node) + return -ENODEV; + + parent_bus = of_mdio_find_bus(parent_bus_node); + if (!parent_bus) { + ret_val = -EPROBE_DEFER; + goto err_parent_bus; + } + } else { + parent_bus_node = NULL; + parent_bus = mux_bus; + get_device(&parent_bus->dev); + } + + pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL); + if (!pb) { + ret_val = -ENOMEM; + goto err_pb_kz; + } + + 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(mux_node, child_bus_node) { + int v; + + r = of_property_read_u32(child_bus_node, "reg", &v); + if (r) { + dev_err(dev, + "Error: Failed to find reg for child %pOF\n", + child_bus_node); + continue; + } + + cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL); + if (!cb) { + ret_val = -ENOMEM; + continue; + } + cb->bus_number = v; + cb->parent = pb; + + cb->mii_bus = mdiobus_alloc(); + if (!cb->mii_bus) { + ret_val = -ENOMEM; + devm_kfree(dev, cb); + continue; + } + cb->mii_bus->priv = cb; + + 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) { + dev_err(dev, + "Error: Failed to register MDIO bus for child %pOF\n", + child_bus_node); + mdiobus_free(cb->mii_bus); + devm_kfree(dev, cb); + } else { + cb->next = pb->children; + pb->children = cb; + } + } + if (pb->children) { + *mux_handle = pb; + return 0; + } + + dev_err(dev, "Error: No acceptable child buses found\n"); + devm_kfree(dev, pb); +err_pb_kz: + put_device(&parent_bus->dev); +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; + } + + put_device(&pb->mii_bus->dev); +} +EXPORT_SYMBOL_GPL(mdio_mux_uninit); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-mvusb.c b/drivers/net/mdio/mdio-mvusb.c new file mode 100644 index 000000000000..d5eabddfdf51 --- /dev/null +++ b/drivers/net/mdio/mdio-mvusb.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +#define USB_MARVELL_VID 0x1286 + +static const struct usb_device_id mvusb_mdio_table[] = { + { USB_DEVICE(USB_MARVELL_VID, 0x1fa4) }, + + {} +}; +MODULE_DEVICE_TABLE(usb, mvusb_mdio_table); + +enum { + MVUSB_CMD_PREAMBLE0, + MVUSB_CMD_PREAMBLE1, + MVUSB_CMD_ADDR, + MVUSB_CMD_VAL, +}; + +struct mvusb_mdio { + struct usb_device *udev; + struct mii_bus *mdio; + + __le16 buf[4]; +}; + +static int mvusb_mdio_read(struct mii_bus *mdio, int dev, int reg) +{ + struct mvusb_mdio *mvusb = mdio->priv; + int err, alen; + + if (dev & MII_ADDR_C45) + return -EOPNOTSUPP; + + mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0xa400 | (dev << 5) | reg); + + err = usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2), + mvusb->buf, 6, &alen, 100); + if (err) + return err; + + err = usb_bulk_msg(mvusb->udev, usb_rcvbulkpipe(mvusb->udev, 6), + &mvusb->buf[MVUSB_CMD_VAL], 2, &alen, 100); + if (err) + return err; + + return le16_to_cpu(mvusb->buf[MVUSB_CMD_VAL]); +} + +static int mvusb_mdio_write(struct mii_bus *mdio, int dev, int reg, u16 val) +{ + struct mvusb_mdio *mvusb = mdio->priv; + int alen; + + if (dev & MII_ADDR_C45) + return -EOPNOTSUPP; + + mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0x8000 | (dev << 5) | reg); + mvusb->buf[MVUSB_CMD_VAL] = cpu_to_le16(val); + + return usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2), + mvusb->buf, 8, &alen, 100); +} + +static int mvusb_mdio_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct device *dev = &interface->dev; + struct mvusb_mdio *mvusb; + struct mii_bus *mdio; + + mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb)); + if (!mdio) + return -ENOMEM; + + mvusb = mdio->priv; + mvusb->mdio = mdio; + mvusb->udev = usb_get_dev(interface_to_usbdev(interface)); + + /* Reversed from USB PCAPs, no idea what these mean. */ + mvusb->buf[MVUSB_CMD_PREAMBLE0] = cpu_to_le16(0xe800); + mvusb->buf[MVUSB_CMD_PREAMBLE1] = cpu_to_le16(0x0001); + + snprintf(mdio->id, MII_BUS_ID_SIZE, "mvusb-%s", dev_name(dev)); + mdio->name = mdio->id; + mdio->parent = dev; + mdio->read = mvusb_mdio_read; + mdio->write = mvusb_mdio_write; + + usb_set_intfdata(interface, mvusb); + return of_mdiobus_register(mdio, dev->of_node); +} + +static void mvusb_mdio_disconnect(struct usb_interface *interface) +{ + struct mvusb_mdio *mvusb = usb_get_intfdata(interface); + struct usb_device *udev = mvusb->udev; + + mdiobus_unregister(mvusb->mdio); + usb_set_intfdata(interface, NULL); + usb_put_dev(udev); +} + +static struct usb_driver mvusb_mdio_driver = { + .name = "mvusb_mdio", + .id_table = mvusb_mdio_table, + .probe = mvusb_mdio_probe, + .disconnect = mvusb_mdio_disconnect, +}; + +module_usb_driver(mvusb_mdio_driver); + +MODULE_AUTHOR("Tobias Waldekranz "); +MODULE_DESCRIPTION("Marvell USB MDIO Adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-octeon.c b/drivers/net/mdio/mdio-octeon.c new file mode 100644 index 000000000000..d1e1009d51af --- /dev/null +++ b/drivers/net/mdio/mdio-octeon.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2009-2015 Cavium, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdio-cavium.h" + +static int octeon_mdiobus_probe(struct platform_device *pdev) +{ + struct cavium_mdiobus *bus; + struct mii_bus *mii_bus; + struct resource *res_mem; + resource_size_t mdio_phys; + resource_size_t regsize; + union cvmx_smix_en smi_en; + int err = -ENOENT; + + mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus)); + if (!mii_bus) + return -ENOMEM; + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res_mem == NULL) { + dev_err(&pdev->dev, "found no memory resource\n"); + return -ENXIO; + } + + bus = mii_bus->priv; + bus->mii_bus = mii_bus; + mdio_phys = res_mem->start; + regsize = resource_size(res_mem); + + if (!devm_request_mem_region(&pdev->dev, mdio_phys, regsize, + res_mem->name)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + return -ENXIO; + } + + bus->register_base = devm_ioremap(&pdev->dev, mdio_phys, regsize); + if (!bus->register_base) { + dev_err(&pdev->dev, "dev_ioremap failed\n"); + return -ENOMEM; + } + + smi_en.u64 = 0; + smi_en.s.en = 1; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); + + bus->mii_bus->name = KBUILD_MODNAME; + snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%px", bus->register_base); + bus->mii_bus->parent = &pdev->dev; + + bus->mii_bus->read = cavium_mdiobus_read; + bus->mii_bus->write = cavium_mdiobus_write; + + platform_set_drvdata(pdev, bus); + + err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node); + if (err) + goto fail_register; + + dev_info(&pdev->dev, "Probed\n"); + + return 0; +fail_register: + mdiobus_free(bus->mii_bus); + smi_en.u64 = 0; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); + return err; +} + +static int octeon_mdiobus_remove(struct platform_device *pdev) +{ + struct cavium_mdiobus *bus; + union cvmx_smix_en smi_en; + + bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus->mii_bus); + mdiobus_free(bus->mii_bus); + smi_en.u64 = 0; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); + return 0; +} + +static const 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 = KBUILD_MODNAME, + .of_match_table = octeon_mdiobus_match, + }, + .probe = octeon_mdiobus_probe, + .remove = octeon_mdiobus_remove, +}; + +module_platform_driver(octeon_mdiobus_driver); + +MODULE_DESCRIPTION("Cavium OCTEON MDIO bus driver"); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-sun4i.c b/drivers/net/mdio/mdio-sun4i.c new file mode 100644 index 000000000000..f798de3276dc --- /dev/null +++ b/drivers/net/mdio/mdio-sun4i.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Allwinner EMAC MDIO interface driver + * + * Copyright 2012-2013 Stefan Roese + * Copyright 2013 Maxime Ripard + * + * Based on the Linux driver provided by Allwinner: + * Copyright (C) 1997 Sten Wang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + int ret; + + 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; + + data = bus->priv; + data->membase = devm_platform_ioremap_resource(pdev, 0); + 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) { + ret = -EPROBE_DEFER; + goto err_out_free_mdiobus; + } + + dev_info(&pdev->dev, "no regulator found\n"); + data->regulator = NULL; + } 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: + if (data->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); + struct sun4i_mdio_data *data = bus->priv; + + mdiobus_unregister(bus); + if (data->regulator) + regulator_disable(data->regulator); + 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 "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-thunder.c b/drivers/net/mdio/mdio-thunder.c new file mode 100644 index 000000000000..3d7eda99d34e --- /dev/null +++ b/drivers/net/mdio/mdio-thunder.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2009-2016 Cavium, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdio-cavium.h" + +struct thunder_mdiobus_nexus { + void __iomem *bar0; + struct cavium_mdiobus *buses[4]; +}; + +static int thunder_mdiobus_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct device_node *node; + struct fwnode_handle *fwn; + struct thunder_mdiobus_nexus *nexus; + int err; + int i; + + nexus = devm_kzalloc(&pdev->dev, sizeof(*nexus), GFP_KERNEL); + if (!nexus) + return -ENOMEM; + + pci_set_drvdata(pdev, nexus); + + err = pcim_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Failed to enable PCI device\n"); + pci_set_drvdata(pdev, NULL); + return err; + } + + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + dev_err(&pdev->dev, "pci_request_regions failed\n"); + goto err_disable_device; + } + + nexus->bar0 = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); + if (!nexus->bar0) { + err = -ENOMEM; + goto err_release_regions; + } + + i = 0; + device_for_each_child_node(&pdev->dev, fwn) { + struct resource r; + struct mii_bus *mii_bus; + struct cavium_mdiobus *bus; + union cvmx_smix_en smi_en; + + /* If it is not an OF node we cannot handle it yet, so + * exit the loop. + */ + node = to_of_node(fwn); + if (!node) + break; + + err = of_address_to_resource(node, 0, &r); + if (err) { + dev_err(&pdev->dev, + "Couldn't translate address for \"%pOFn\"\n", + node); + break; + } + + mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus)); + if (!mii_bus) + break; + bus = mii_bus->priv; + bus->mii_bus = mii_bus; + + nexus->buses[i] = bus; + i++; + + bus->register_base = nexus->bar0 + + r.start - pci_resource_start(pdev, 0); + + smi_en.u64 = 0; + smi_en.s.en = 1; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); + bus->mii_bus->name = KBUILD_MODNAME; + snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", r.start); + bus->mii_bus->parent = &pdev->dev; + bus->mii_bus->read = cavium_mdiobus_read; + bus->mii_bus->write = cavium_mdiobus_write; + + err = of_mdiobus_register(bus->mii_bus, node); + if (err) + dev_err(&pdev->dev, "of_mdiobus_register failed\n"); + + dev_info(&pdev->dev, "Added bus at %llx\n", r.start); + if (i >= ARRAY_SIZE(nexus->buses)) + break; + } + return 0; + +err_release_regions: + pci_release_regions(pdev); + +err_disable_device: + pci_set_drvdata(pdev, NULL); + return err; +} + +static void thunder_mdiobus_pci_remove(struct pci_dev *pdev) +{ + int i; + struct thunder_mdiobus_nexus *nexus = pci_get_drvdata(pdev); + + for (i = 0; i < ARRAY_SIZE(nexus->buses); i++) { + struct cavium_mdiobus *bus = nexus->buses[i]; + + if (!bus) + continue; + + mdiobus_unregister(bus->mii_bus); + mdiobus_free(bus->mii_bus); + oct_mdio_writeq(0, bus->register_base + SMI_EN); + } + pci_release_regions(pdev); + pci_set_drvdata(pdev, NULL); +} + +static const struct pci_device_id thunder_mdiobus_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa02b) }, + { 0, } /* End of table. */ +}; +MODULE_DEVICE_TABLE(pci, thunder_mdiobus_id_table); + +static struct pci_driver thunder_mdiobus_driver = { + .name = KBUILD_MODNAME, + .id_table = thunder_mdiobus_id_table, + .probe = thunder_mdiobus_pci_probe, + .remove = thunder_mdiobus_pci_remove, +}; + +module_pci_driver(thunder_mdiobus_driver); + +MODULE_DESCRIPTION("Cavium ThunderX MDIO bus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/mdio/mdio-xgene.c b/drivers/net/mdio/mdio-xgene.c new file mode 100644 index 000000000000..461207cdf5d6 --- /dev/null +++ b/drivers/net/mdio/mdio-xgene.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Applied Micro X-Gene SoC MDIO Driver + * + * Copyright (c) 2016, Applied Micro Circuits Corporation + * Author: Iyappan Subramanian + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool xgene_mdio_status; + +u32 xgene_mdio_rd_mac(struct xgene_mdio_pdata *pdata, u32 rd_addr) +{ + void __iomem *addr, *rd, *cmd, *cmd_done; + u32 done, rd_data = BUSY_MASK; + u8 wait = 10; + + addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET; + rd = pdata->mac_csr_addr + MAC_READ_REG_OFFSET; + cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET; + cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET; + + spin_lock(&pdata->mac_lock); + iowrite32(rd_addr, addr); + iowrite32(XGENE_ENET_RD_CMD, cmd); + + while (!(done = ioread32(cmd_done)) && wait--) + udelay(1); + + if (done) + rd_data = ioread32(rd); + + iowrite32(0, cmd); + spin_unlock(&pdata->mac_lock); + + return rd_data; +} +EXPORT_SYMBOL(xgene_mdio_rd_mac); + +void xgene_mdio_wr_mac(struct xgene_mdio_pdata *pdata, u32 wr_addr, u32 data) +{ + void __iomem *addr, *wr, *cmd, *cmd_done; + u8 wait = 10; + u32 done; + + addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET; + wr = pdata->mac_csr_addr + MAC_WRITE_REG_OFFSET; + cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET; + cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET; + + spin_lock(&pdata->mac_lock); + iowrite32(wr_addr, addr); + iowrite32(data, wr); + iowrite32(XGENE_ENET_WR_CMD, cmd); + + while (!(done = ioread32(cmd_done)) && wait--) + udelay(1); + + if (!done) + pr_err("MCX mac write failed, addr: 0x%04x\n", wr_addr); + + iowrite32(0, cmd); + spin_unlock(&pdata->mac_lock); +} +EXPORT_SYMBOL(xgene_mdio_wr_mac); + +int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv; + u32 data, done; + u8 wait = 10; + + data = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); + xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, data); + xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, READ_CYCLE_MASK); + do { + usleep_range(5, 10); + done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); + } while ((done & BUSY_MASK) && wait--); + + if (done & BUSY_MASK) { + dev_err(&bus->dev, "MII_MGMT read failed\n"); + return -EBUSY; + } + + data = xgene_mdio_rd_mac(pdata, MII_MGMT_STATUS_ADDR); + xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, 0); + + return data; +} +EXPORT_SYMBOL(xgene_mdio_rgmii_read); + +int xgene_mdio_rgmii_write(struct mii_bus *bus, int phy_id, int reg, u16 data) +{ + struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv; + u32 val, done; + u8 wait = 10; + + val = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); + xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, val); + + xgene_mdio_wr_mac(pdata, MII_MGMT_CONTROL_ADDR, data); + do { + usleep_range(5, 10); + done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); + } while ((done & BUSY_MASK) && wait--); + + if (done & BUSY_MASK) { + dev_err(&bus->dev, "MII_MGMT write failed\n"); + return -EBUSY; + } + + return 0; +} +EXPORT_SYMBOL(xgene_mdio_rgmii_write); + +static u32 xgene_menet_rd_diag_csr(struct xgene_mdio_pdata *pdata, u32 offset) +{ + return ioread32(pdata->diag_csr_addr + offset); +} + +static void xgene_menet_wr_diag_csr(struct xgene_mdio_pdata *pdata, + u32 offset, u32 val) +{ + iowrite32(val, pdata->diag_csr_addr + offset); +} + +static int xgene_enet_ecc_init(struct xgene_mdio_pdata *pdata) +{ + u32 data; + u8 wait = 10; + + xgene_menet_wr_diag_csr(pdata, MENET_CFG_MEM_RAM_SHUTDOWN_ADDR, 0x0); + do { + usleep_range(100, 110); + data = xgene_menet_rd_diag_csr(pdata, MENET_BLOCK_MEM_RDY_ADDR); + } while ((data != 0xffffffff) && wait--); + + if (data != 0xffffffff) { + dev_err(pdata->dev, "Failed to release memory from shutdown\n"); + return -ENODEV; + } + + return 0; +} + +static void xgene_gmac_reset(struct xgene_mdio_pdata *pdata) +{ + xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, SOFT_RESET); + xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, 0); +} + +static int xgene_mdio_reset(struct xgene_mdio_pdata *pdata) +{ + int ret; + + if (pdata->dev->of_node) { + clk_prepare_enable(pdata->clk); + udelay(5); + clk_disable_unprepare(pdata->clk); + udelay(5); + clk_prepare_enable(pdata->clk); + udelay(5); + } else { +#ifdef CONFIG_ACPI + acpi_evaluate_object(ACPI_HANDLE(pdata->dev), + "_RST", NULL, NULL); +#endif + } + + ret = xgene_enet_ecc_init(pdata); + if (ret) { + if (pdata->dev->of_node) + clk_disable_unprepare(pdata->clk); + return ret; + } + xgene_gmac_reset(pdata); + + return 0; +} + +static void xgene_enet_rd_mdio_csr(void __iomem *base_addr, + u32 offset, u32 *val) +{ + void __iomem *addr = base_addr + offset; + + *val = ioread32(addr); +} + +static void xgene_enet_wr_mdio_csr(void __iomem *base_addr, + u32 offset, u32 val) +{ + void __iomem *addr = base_addr + offset; + + iowrite32(val, addr); +} + +static int xgene_xfi_mdio_write(struct mii_bus *bus, int phy_id, + int reg, u16 data) +{ + void __iomem *addr = (void __iomem *)bus->priv; + int timeout = 100; + u32 status, val; + + val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg) | + SET_VAL(HSTMIIMWRDAT, data); + xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val); + + val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_WRITE); + xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val); + + do { + usleep_range(5, 10); + xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status); + } while ((status & BUSY_MASK) && timeout--); + + xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0); + + return 0; +} + +static int xgene_xfi_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + void __iomem *addr = (void __iomem *)bus->priv; + u32 data, status, val; + int timeout = 100; + + val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg); + xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val); + + val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_READ); + xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val); + + do { + usleep_range(5, 10); + xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status); + } while ((status & BUSY_MASK) && timeout--); + + if (status & BUSY_MASK) { + pr_err("XGENET_MII_MGMT write failed\n"); + return -EBUSY; + } + + xgene_enet_rd_mdio_csr(addr, MIIMRD_FIELD_ADDR, &data); + xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0); + + return data; +} + +struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr) +{ + struct phy_device *phy_dev; + + phy_dev = get_phy_device(bus, phy_addr, false); + if (!phy_dev || IS_ERR(phy_dev)) + return NULL; + + if (phy_device_register(phy_dev)) + phy_device_free(phy_dev); + + return phy_dev; +} +EXPORT_SYMBOL(xgene_enet_phy_register); + +#ifdef CONFIG_ACPI +static acpi_status acpi_register_phy(acpi_handle handle, u32 lvl, + void *context, void **ret) +{ + struct mii_bus *mdio = context; + struct acpi_device *adev; + struct phy_device *phy_dev; + const union acpi_object *obj; + u32 phy_addr; + + if (acpi_bus_get_device(handle, &adev)) + return AE_OK; + + if (acpi_dev_get_property(adev, "phy-channel", ACPI_TYPE_INTEGER, &obj)) + return AE_OK; + phy_addr = obj->integer.value; + + phy_dev = xgene_enet_phy_register(mdio, phy_addr); + adev->driver_data = phy_dev; + + return AE_OK; +} +#endif + +static const struct of_device_id xgene_mdio_of_match[] = { + { + .compatible = "apm,xgene-mdio-rgmii", + .data = (void *)XGENE_MDIO_RGMII + }, + { + .compatible = "apm,xgene-mdio-xfi", + .data = (void *)XGENE_MDIO_XFI + }, + {}, +}; +MODULE_DEVICE_TABLE(of, xgene_mdio_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id xgene_mdio_acpi_match[] = { + { "APMC0D65", XGENE_MDIO_RGMII }, + { "APMC0D66", XGENE_MDIO_XFI }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, xgene_mdio_acpi_match); +#endif + + +static int xgene_mdio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mii_bus *mdio_bus; + const struct of_device_id *of_id; + struct xgene_mdio_pdata *pdata; + void __iomem *csr_base; + int mdio_id = 0, ret = 0; + + of_id = of_match_device(xgene_mdio_of_match, &pdev->dev); + if (of_id) { + mdio_id = (enum xgene_mdio_id)of_id->data; + } else { +#ifdef CONFIG_ACPI + const struct acpi_device_id *acpi_id; + + acpi_id = acpi_match_device(xgene_mdio_acpi_match, &pdev->dev); + if (acpi_id) + mdio_id = (enum xgene_mdio_id)acpi_id->driver_data; +#endif + } + + if (!mdio_id) + return -ENODEV; + + pdata = devm_kzalloc(dev, sizeof(struct xgene_mdio_pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + pdata->mdio_id = mdio_id; + pdata->dev = dev; + + csr_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csr_base)) + return PTR_ERR(csr_base); + pdata->mac_csr_addr = csr_base; + pdata->mdio_csr_addr = csr_base + BLOCK_XG_MDIO_CSR_OFFSET; + pdata->diag_csr_addr = csr_base + BLOCK_DIAG_CSR_OFFSET; + + if (mdio_id == XGENE_MDIO_RGMII) + spin_lock_init(&pdata->mac_lock); + + if (dev->of_node) { + pdata->clk = devm_clk_get(dev, NULL); + if (IS_ERR(pdata->clk)) { + dev_err(dev, "Unable to retrieve clk\n"); + return PTR_ERR(pdata->clk); + } + } + + ret = xgene_mdio_reset(pdata); + if (ret) + return ret; + + mdio_bus = mdiobus_alloc(); + if (!mdio_bus) { + ret = -ENOMEM; + goto out_clk; + } + + mdio_bus->name = "APM X-Gene MDIO bus"; + + if (mdio_id == XGENE_MDIO_RGMII) { + mdio_bus->read = xgene_mdio_rgmii_read; + mdio_bus->write = xgene_mdio_rgmii_write; + mdio_bus->priv = (void __force *)pdata; + snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", + "xgene-mii-rgmii"); + } else { + mdio_bus->read = xgene_xfi_mdio_read; + mdio_bus->write = xgene_xfi_mdio_write; + mdio_bus->priv = (void __force *)pdata->mdio_csr_addr; + snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", + "xgene-mii-xfi"); + } + + mdio_bus->parent = dev; + platform_set_drvdata(pdev, pdata); + + if (dev->of_node) { + ret = of_mdiobus_register(mdio_bus, dev->of_node); + } else { +#ifdef CONFIG_ACPI + /* Mask out all PHYs from auto probing. */ + mdio_bus->phy_mask = ~0; + ret = mdiobus_register(mdio_bus); + if (ret) + goto out_mdiobus; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(dev), 1, + acpi_register_phy, NULL, mdio_bus, NULL); +#endif + } + + if (ret) + goto out_mdiobus; + + pdata->mdio_bus = mdio_bus; + xgene_mdio_status = true; + + return 0; + +out_mdiobus: + mdiobus_free(mdio_bus); + +out_clk: + if (dev->of_node) + clk_disable_unprepare(pdata->clk); + + return ret; +} + +static int xgene_mdio_remove(struct platform_device *pdev) +{ + struct xgene_mdio_pdata *pdata = platform_get_drvdata(pdev); + struct mii_bus *mdio_bus = pdata->mdio_bus; + struct device *dev = &pdev->dev; + + mdiobus_unregister(mdio_bus); + mdiobus_free(mdio_bus); + + if (dev->of_node) + clk_disable_unprepare(pdata->clk); + + return 0; +} + +static struct platform_driver xgene_mdio_driver = { + .driver = { + .name = "xgene-mdio", + .of_match_table = of_match_ptr(xgene_mdio_of_match), + .acpi_match_table = ACPI_PTR(xgene_mdio_acpi_match), + }, + .probe = xgene_mdio_probe, + .remove = xgene_mdio_remove, +}; + +module_platform_driver(xgene_mdio_driver); + +MODULE_DESCRIPTION("APM X-Gene SoC MDIO driver"); +MODULE_AUTHOR("Iyappan Subramanian "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index c69cc806f064..20252d7487db 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -3,240 +3,6 @@ # PHY Layer Configuration # -menuconfig MDIO_DEVICE - tristate "MDIO bus device drivers" - help - MDIO devices and driver infrastructure code. - -if MDIO_DEVICE - -config MDIO_BUS - tristate - default m if PHYLIB=m - default MDIO_DEVICE - help - This internal symbol is used for link time dependencies and it - reflects whether the mdio_bus/mdio_device code is built as a - loadable module or built-in. - -if MDIO_BUS - -config MDIO_DEVRES - tristate - -config MDIO_ASPEED - tristate "ASPEED MDIO bus controller" - depends on ARCH_ASPEED || COMPILE_TEST - depends on OF_MDIO && HAS_IOMEM - help - This module provides a driver for the independent MDIO bus - controllers found in the ASPEED AST2600 SoC. This is a driver for the - third revision of the ASPEED MDIO register interface - the first two - revisions are the "old" and "new" interfaces found in the AST2400 and - AST2500, embedded in the MAC. For legacy reasons, FTGMAC100 driver - continues to drive the embedded MDIO controller for the AST2400 and - AST2500 SoCs, so say N if AST2600 support is not required. - -config MDIO_BCM_IPROC - tristate "Broadcom iProc MDIO bus controller" - depends on ARCH_BCM_IPROC || COMPILE_TEST - depends on HAS_IOMEM && OF_MDIO - default ARCH_BCM_IPROC - help - This module provides a driver for the MDIO busses found in the - Broadcom iProc SoC's. - -config MDIO_BCM_UNIMAC - tristate "Broadcom UniMAC MDIO bus controller" - depends on HAS_IOMEM - help - This module provides a driver for the Broadcom UniMAC MDIO busses. - This hardware can be found in the Broadcom GENET Ethernet MAC - controllers as well as some Broadcom Ethernet switches such as the - Starfighter 2 switches. - -config MDIO_BITBANG - tristate "Bitbanged MDIO buses" - help - This module implements the MDIO bus protocol in software, - for use by low level drivers that export the ability to - drive the relevant pins. - - If in doubt, say N. - -config MDIO_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_BCM_IPROC - tristate "Broadcom iProc based MDIO bus multiplexers" - depends on OF && OF_MDIO && (ARCH_BCM_IPROC || COMPILE_TEST) - select MDIO_BUS_MUX - default ARCH_BCM_IPROC - help - This module provides a driver for MDIO bus multiplexers found in - iProc based Broadcom SoCs. This multiplexer connects one of several - child MDIO bus to a parent bus. Buses could be internal as well as - external and selection logic lies inside the same multiplexer. - -config MDIO_BUS_MUX_GPIO - tristate "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_MESON_G12A - tristate "Amlogic G12a based MDIO bus multiplexer" - depends on ARCH_MESON || COMPILE_TEST - depends on OF_MDIO && HAS_IOMEM && COMMON_CLK - select MDIO_BUS_MUX - default m if ARCH_MESON - help - This module provides a driver for the MDIO multiplexer/glue of - the amlogic g12a SoC. The multiplexers connects either the external - or the internal MDIO bus to the parent bus. - -config MDIO_BUS_MUX_MMIOREG - tristate "MMIO device-controlled MDIO bus multiplexers" - depends on OF_MDIO && HAS_IOMEM - 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/16/32 bits registers are supported. - -config MDIO_BUS_MUX_MULTIPLEXER - tristate "MDIO bus multiplexer using kernel multiplexer subsystem" - depends on OF_MDIO - select MULTIPLEXER - select MDIO_BUS_MUX - help - This module provides a driver for MDIO bus multiplexer - that is controlled via the kernel multiplexer subsystem. The - bus multiplexer connects one of several child MDIO busses to - a parent bus. Child bus selection is under the control of - the kernel multiplexer subsystem. - -config MDIO_CAVIUM - tristate - -config MDIO_GPIO - tristate "GPIO lib-based bitbanged MDIO buses" - depends on MDIO_BITBANG - depends on GPIOLIB || COMPILE_TEST - help - Supports GPIO lib-based MDIO busses. - - To compile this driver as a module, choose M here: the module - will be called mdio-gpio. - -config MDIO_HISI_FEMAC - tristate "Hisilicon FEMAC MDIO bus controller" - depends on HAS_IOMEM && OF_MDIO - help - This module provides a driver for the MDIO busses found in the - Hisilicon SoC that have an Fast Ethernet MAC. - -config MDIO_I2C - tristate - depends on I2C - help - Support I2C based PHYs. This provides a MDIO bus bridged - to I2C to allow PHYs connected in I2C mode to be accessed - using the existing infrastructure. - - This is library mode. - -config MDIO_IPQ4019 - tristate "Qualcomm IPQ4019 MDIO interface support" - depends on HAS_IOMEM && OF_MDIO - help - This driver supports the MDIO interface found in Qualcomm - IPQ40xx series Soc-s. - -config MDIO_IPQ8064 - tristate "Qualcomm IPQ8064 MDIO interface support" - depends on HAS_IOMEM && OF_MDIO - depends on MFD_SYSCON - help - This driver supports the MDIO interface found in the network - interface units of the IPQ8064 SoC - -config MDIO_MOXART - tristate "MOXA ART MDIO interface support" - depends on ARCH_MOXART || COMPILE_TEST - help - This driver supports the MDIO interface found in the network - interface units of the MOXA ART SoC - -config MDIO_MSCC_MIIM - tristate "Microsemi MIIM interface support" - depends on HAS_IOMEM - select MDIO_DEVRES - help - This driver supports the MIIM (MDIO) interface found in the network - switches of the Microsemi SoCs; it is recommended to switch on - CONFIG_HIGH_RES_TIMERS - -config MDIO_MVUSB - tristate "Marvell USB to MDIO Adapter" - depends on USB - select MDIO_DEVRES - help - A USB to MDIO converter present on development boards for - Marvell's Link Street family of Ethernet switches. - -config MDIO_OCTEON - tristate "Octeon and some ThunderX SOCs MDIO buses" - depends on (64BIT && OF_MDIO) || COMPILE_TEST - depends on HAS_IOMEM - select MDIO_CAVIUM - help - This module provides a driver for the Octeon and ThunderX MDIO - buses. It is required by the Octeon and ThunderX ethernet device - drivers on some systems. - -config MDIO_SUN4I - tristate "Allwinner sun4i MDIO interface support" - depends on ARCH_SUNXI || COMPILE_TEST - 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_THUNDER - tristate "ThunderX SOCs MDIO buses" - depends on 64BIT - depends on PCI - select MDIO_CAVIUM - help - This driver supports the MDIO interfaces found on Cavium - ThunderX SoCs when the MDIO bus device appears as a PCI - device. - -config MDIO_XGENE - tristate "APM X-Gene SoC MDIO bus controller" - depends on ARCH_XGENE || COMPILE_TEST - help - This module provides a driver for the MDIO busses found in the - APM X-Gene SoC's. - -endif -endif - config PHYLINK tristate depends on NETDEVICES diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 7cd8a0d1c0d0..3d83b648e3f0 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -# Makefile for Linux PHY drivers and MDIO bus drivers +# Makefile for Linux PHY drivers libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \ linkmode.o @@ -24,30 +24,6 @@ libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o obj-$(CONFIG_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o -obj-$(CONFIG_MDIO_ASPEED) += mdio-aspeed.o -obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o -obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o -obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o -obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o -obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC) += mdio-mux-bcm-iproc.o -obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o -obj-$(CONFIG_MDIO_BUS_MUX_MESON_G12A) += mdio-mux-meson-g12a.o -obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o -obj-$(CONFIG_MDIO_BUS_MUX_MULTIPLEXER) += mdio-mux-multiplexer.o -obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o -obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o -obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o -obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o -obj-$(CONFIG_MDIO_IPQ4019) += mdio-ipq4019.o -obj-$(CONFIG_MDIO_IPQ8064) += mdio-ipq8064.o -obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o -obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o -obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o -obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o -obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o -obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o -obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o - obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o obj-$(CONFIG_SFP) += sfp.o diff --git a/drivers/net/phy/mdio-aspeed.c b/drivers/net/phy/mdio-aspeed.c deleted file mode 100644 index cad820568f75..000000000000 --- a/drivers/net/phy/mdio-aspeed.c +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* Copyright (C) 2019 IBM Corp. */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRV_NAME "mdio-aspeed" - -#define ASPEED_MDIO_CTRL 0x0 -#define ASPEED_MDIO_CTRL_FIRE BIT(31) -#define ASPEED_MDIO_CTRL_ST BIT(28) -#define ASPEED_MDIO_CTRL_ST_C45 0 -#define ASPEED_MDIO_CTRL_ST_C22 1 -#define ASPEED_MDIO_CTRL_OP GENMASK(27, 26) -#define MDIO_C22_OP_WRITE 0b01 -#define MDIO_C22_OP_READ 0b10 -#define ASPEED_MDIO_CTRL_PHYAD GENMASK(25, 21) -#define ASPEED_MDIO_CTRL_REGAD GENMASK(20, 16) -#define ASPEED_MDIO_CTRL_MIIWDATA GENMASK(15, 0) - -#define ASPEED_MDIO_DATA 0x4 -#define ASPEED_MDIO_DATA_MDC_THRES GENMASK(31, 24) -#define ASPEED_MDIO_DATA_MDIO_EDGE BIT(23) -#define ASPEED_MDIO_DATA_MDIO_LATCH GENMASK(22, 20) -#define ASPEED_MDIO_DATA_IDLE BIT(16) -#define ASPEED_MDIO_DATA_MIIRDATA GENMASK(15, 0) - -#define ASPEED_MDIO_INTERVAL_US 100 -#define ASPEED_MDIO_TIMEOUT_US (ASPEED_MDIO_INTERVAL_US * 10) - -struct aspeed_mdio { - void __iomem *base; -}; - -static int aspeed_mdio_read(struct mii_bus *bus, int addr, int regnum) -{ - struct aspeed_mdio *ctx = bus->priv; - u32 ctrl; - u32 data; - int rc; - - dev_dbg(&bus->dev, "%s: addr: %d, regnum: %d\n", __func__, addr, - regnum); - - /* Just clause 22 for the moment */ - if (regnum & MII_ADDR_C45) - return -EOPNOTSUPP; - - ctrl = ASPEED_MDIO_CTRL_FIRE - | FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22) - | FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_READ) - | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr) - | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regnum); - - iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); - - rc = readl_poll_timeout(ctx->base + ASPEED_MDIO_DATA, data, - data & ASPEED_MDIO_DATA_IDLE, - ASPEED_MDIO_INTERVAL_US, - ASPEED_MDIO_TIMEOUT_US); - if (rc < 0) - return rc; - - return FIELD_GET(ASPEED_MDIO_DATA_MIIRDATA, data); -} - -static int aspeed_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val) -{ - struct aspeed_mdio *ctx = bus->priv; - u32 ctrl; - - dev_dbg(&bus->dev, "%s: addr: %d, regnum: %d, val: 0x%x\n", - __func__, addr, regnum, val); - - /* Just clause 22 for the moment */ - if (regnum & MII_ADDR_C45) - return -EOPNOTSUPP; - - ctrl = ASPEED_MDIO_CTRL_FIRE - | FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22) - | FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_WRITE) - | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr) - | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regnum) - | FIELD_PREP(ASPEED_MDIO_CTRL_MIIWDATA, val); - - iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); - - return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl, - !(ctrl & ASPEED_MDIO_CTRL_FIRE), - ASPEED_MDIO_INTERVAL_US, - ASPEED_MDIO_TIMEOUT_US); -} - -static int aspeed_mdio_probe(struct platform_device *pdev) -{ - struct aspeed_mdio *ctx; - struct mii_bus *bus; - int rc; - - bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*ctx)); - if (!bus) - return -ENOMEM; - - ctx = bus->priv; - ctx->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(ctx->base)) - return PTR_ERR(ctx->base); - - bus->name = DRV_NAME; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); - bus->parent = &pdev->dev; - bus->read = aspeed_mdio_read; - bus->write = aspeed_mdio_write; - - rc = of_mdiobus_register(bus, pdev->dev.of_node); - if (rc) { - dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); - return rc; - } - - platform_set_drvdata(pdev, bus); - - return 0; -} - -static int aspeed_mdio_remove(struct platform_device *pdev) -{ - mdiobus_unregister(platform_get_drvdata(pdev)); - - return 0; -} - -static const struct of_device_id aspeed_mdio_of_match[] = { - { .compatible = "aspeed,ast2600-mdio", }, - { }, -}; - -static struct platform_driver aspeed_mdio_driver = { - .driver = { - .name = DRV_NAME, - .of_match_table = aspeed_mdio_of_match, - }, - .probe = aspeed_mdio_probe, - .remove = aspeed_mdio_remove, -}; - -module_platform_driver(aspeed_mdio_driver); - -MODULE_AUTHOR("Andrew Jeffery "); -MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-bcm-iproc.c b/drivers/net/phy/mdio-bcm-iproc.c deleted file mode 100644 index 77fc970cdfde..000000000000 --- a/drivers/net/phy/mdio-bcm-iproc.c +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2015 Broadcom Corporation - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define IPROC_GPHY_MDCDIV 0x1a - -#define MII_CTRL_OFFSET 0x000 - -#define MII_CTRL_DIV_SHIFT 0 -#define MII_CTRL_PRE_SHIFT 7 -#define MII_CTRL_BUSY_SHIFT 8 - -#define MII_DATA_OFFSET 0x004 -#define MII_DATA_MASK 0xffff -#define MII_DATA_TA_SHIFT 16 -#define MII_DATA_TA_VAL 2 -#define MII_DATA_RA_SHIFT 18 -#define MII_DATA_PA_SHIFT 23 -#define MII_DATA_OP_SHIFT 28 -#define MII_DATA_OP_WRITE 1 -#define MII_DATA_OP_READ 2 -#define MII_DATA_SB_SHIFT 30 - -struct iproc_mdio_priv { - struct mii_bus *mii_bus; - void __iomem *base; -}; - -static inline int iproc_mdio_wait_for_idle(void __iomem *base) -{ - u32 val; - unsigned int timeout = 1000; /* loop for 1s */ - - do { - val = readl(base + MII_CTRL_OFFSET); - if ((val & BIT(MII_CTRL_BUSY_SHIFT)) == 0) - return 0; - - usleep_range(1000, 2000); - } while (timeout--); - - return -ETIMEDOUT; -} - -static inline void iproc_mdio_config_clk(void __iomem *base) -{ - u32 val; - - val = (IPROC_GPHY_MDCDIV << MII_CTRL_DIV_SHIFT) | - BIT(MII_CTRL_PRE_SHIFT); - writel(val, base + MII_CTRL_OFFSET); -} - -static int iproc_mdio_read(struct mii_bus *bus, int phy_id, int reg) -{ - struct iproc_mdio_priv *priv = bus->priv; - u32 cmd; - int rc; - - rc = iproc_mdio_wait_for_idle(priv->base); - if (rc) - return rc; - - /* Prepare the read operation */ - cmd = (MII_DATA_TA_VAL << MII_DATA_TA_SHIFT) | - (reg << MII_DATA_RA_SHIFT) | - (phy_id << MII_DATA_PA_SHIFT) | - BIT(MII_DATA_SB_SHIFT) | - (MII_DATA_OP_READ << MII_DATA_OP_SHIFT); - - writel(cmd, priv->base + MII_DATA_OFFSET); - - rc = iproc_mdio_wait_for_idle(priv->base); - if (rc) - return rc; - - cmd = readl(priv->base + MII_DATA_OFFSET) & MII_DATA_MASK; - - return cmd; -} - -static int iproc_mdio_write(struct mii_bus *bus, int phy_id, - int reg, u16 val) -{ - struct iproc_mdio_priv *priv = bus->priv; - u32 cmd; - int rc; - - rc = iproc_mdio_wait_for_idle(priv->base); - if (rc) - return rc; - - /* Prepare the write operation */ - cmd = (MII_DATA_TA_VAL << MII_DATA_TA_SHIFT) | - (reg << MII_DATA_RA_SHIFT) | - (phy_id << MII_DATA_PA_SHIFT) | - BIT(MII_DATA_SB_SHIFT) | - (MII_DATA_OP_WRITE << MII_DATA_OP_SHIFT) | - ((u32)(val) & MII_DATA_MASK); - - writel(cmd, priv->base + MII_DATA_OFFSET); - - rc = iproc_mdio_wait_for_idle(priv->base); - if (rc) - return rc; - - return 0; -} - -static int iproc_mdio_probe(struct platform_device *pdev) -{ - struct iproc_mdio_priv *priv; - struct mii_bus *bus; - int rc; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->base)) { - dev_err(&pdev->dev, "failed to ioremap register\n"); - return PTR_ERR(priv->base); - } - - priv->mii_bus = mdiobus_alloc(); - if (!priv->mii_bus) { - dev_err(&pdev->dev, "MDIO bus alloc failed\n"); - return -ENOMEM; - } - - bus = priv->mii_bus; - bus->priv = priv; - bus->name = "iProc MDIO bus"; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); - bus->parent = &pdev->dev; - bus->read = iproc_mdio_read; - bus->write = iproc_mdio_write; - - iproc_mdio_config_clk(priv->base); - - rc = of_mdiobus_register(bus, pdev->dev.of_node); - if (rc) { - dev_err(&pdev->dev, "MDIO bus registration failed\n"); - goto err_iproc_mdio; - } - - platform_set_drvdata(pdev, priv); - - dev_info(&pdev->dev, "Broadcom iProc MDIO bus registered\n"); - - return 0; - -err_iproc_mdio: - mdiobus_free(bus); - return rc; -} - -static int iproc_mdio_remove(struct platform_device *pdev) -{ - struct iproc_mdio_priv *priv = platform_get_drvdata(pdev); - - mdiobus_unregister(priv->mii_bus); - mdiobus_free(priv->mii_bus); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int iproc_mdio_resume(struct device *dev) -{ - struct platform_device *pdev = to_platform_device(dev); - struct iproc_mdio_priv *priv = platform_get_drvdata(pdev); - - /* restore the mii clock configuration */ - iproc_mdio_config_clk(priv->base); - - return 0; -} - -static const struct dev_pm_ops iproc_mdio_pm_ops = { - .resume = iproc_mdio_resume -}; -#endif /* CONFIG_PM_SLEEP */ - -static const struct of_device_id iproc_mdio_of_match[] = { - { .compatible = "brcm,iproc-mdio", }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(of, iproc_mdio_of_match); - -static struct platform_driver iproc_mdio_driver = { - .driver = { - .name = "iproc-mdio", - .of_match_table = iproc_mdio_of_match, -#ifdef CONFIG_PM_SLEEP - .pm = &iproc_mdio_pm_ops, -#endif - }, - .probe = iproc_mdio_probe, - .remove = iproc_mdio_remove, -}; - -module_platform_driver(iproc_mdio_driver); - -MODULE_AUTHOR("Broadcom Corporation"); -MODULE_DESCRIPTION("Broadcom iProc MDIO bus controller"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:iproc-mdio"); diff --git a/drivers/net/phy/mdio-bcm-unimac.c b/drivers/net/phy/mdio-bcm-unimac.c deleted file mode 100644 index fbd36891ee64..000000000000 --- a/drivers/net/phy/mdio-bcm-unimac.c +++ /dev/null @@ -1,363 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Broadcom UniMAC MDIO bus controller driver - * - * Copyright (C) 2014-2017 Broadcom - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#define MDIO_CMD 0x00 -#define MDIO_START_BUSY (1 << 29) -#define MDIO_READ_FAIL (1 << 28) -#define MDIO_RD (2 << 26) -#define MDIO_WR (1 << 26) -#define MDIO_PMD_SHIFT 21 -#define MDIO_PMD_MASK 0x1F -#define MDIO_REG_SHIFT 16 -#define MDIO_REG_MASK 0x1F - -#define MDIO_CFG 0x04 -#define MDIO_C22 (1 << 0) -#define MDIO_C45 0 -#define MDIO_CLK_DIV_SHIFT 4 -#define MDIO_CLK_DIV_MASK 0x3F -#define MDIO_SUPP_PREAMBLE (1 << 12) - -struct unimac_mdio_priv { - struct mii_bus *mii_bus; - void __iomem *base; - int (*wait_func) (void *wait_func_data); - void *wait_func_data; - struct clk *clk; - u32 clk_freq; -}; - -static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset) -{ - /* MIPS chips strapped for BE will automagically configure the - * peripheral registers for CPU-native byte order. - */ - if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) - return __raw_readl(priv->base + offset); - else - return readl_relaxed(priv->base + offset); -} - -static inline void unimac_mdio_writel(struct unimac_mdio_priv *priv, u32 val, - u32 offset) -{ - if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) - __raw_writel(val, priv->base + offset); - else - writel_relaxed(val, priv->base + offset); -} - -static inline void unimac_mdio_start(struct unimac_mdio_priv *priv) -{ - u32 reg; - - reg = unimac_mdio_readl(priv, MDIO_CMD); - reg |= MDIO_START_BUSY; - unimac_mdio_writel(priv, reg, MDIO_CMD); -} - -static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv) -{ - return unimac_mdio_readl(priv, MDIO_CMD) & MDIO_START_BUSY; -} - -static int unimac_mdio_poll(void *wait_func_data) -{ - struct unimac_mdio_priv *priv = wait_func_data; - unsigned int timeout = 1000; - - do { - if (!unimac_mdio_busy(priv)) - return 0; - - usleep_range(1000, 2000); - } while (--timeout); - - return -ETIMEDOUT; -} - -static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) -{ - struct unimac_mdio_priv *priv = bus->priv; - int ret; - u32 cmd; - - /* Prepare the read operation */ - cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT); - unimac_mdio_writel(priv, cmd, MDIO_CMD); - - /* Start MDIO transaction */ - unimac_mdio_start(priv); - - ret = priv->wait_func(priv->wait_func_data); - if (ret) - return ret; - - cmd = unimac_mdio_readl(priv, MDIO_CMD); - - /* Some broken devices are known not to release the line during - * turn-around, e.g: Broadcom BCM53125 external switches, so check for - * that condition here and ignore the MDIO controller read failure - * indication. - */ - if (!(bus->phy_ignore_ta_mask & 1 << phy_id) && (cmd & MDIO_READ_FAIL)) - return -EIO; - - return cmd & 0xffff; -} - -static int unimac_mdio_write(struct mii_bus *bus, int phy_id, - int reg, u16 val) -{ - struct unimac_mdio_priv *priv = bus->priv; - u32 cmd; - - /* Prepare the write operation */ - cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) | - (reg << MDIO_REG_SHIFT) | (0xffff & val); - unimac_mdio_writel(priv, cmd, MDIO_CMD); - - unimac_mdio_start(priv); - - return priv->wait_func(priv->wait_func_data); -} - -/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with - * their internal MDIO management controller making them fail to successfully - * be read from or written to for the first transaction. We insert a dummy - * BMSR read here to make sure that phy_get_device() and get_phy_id() can - * correctly read the PHY MII_PHYSID1/2 registers and successfully register a - * PHY device for this peripheral. - * - * Once the PHY driver is registered, we can workaround subsequent reads from - * there (e.g: during system-wide power management). - * - * bus->reset is invoked before mdiobus_scan during mdiobus_register and is - * therefore the right location to stick that workaround. Since we do not want - * to read from non-existing PHYs, we either use bus->phy_mask or do a manual - * Device Tree scan to limit the search area. - */ -static int unimac_mdio_reset(struct mii_bus *bus) -{ - struct device_node *np = bus->dev.of_node; - struct device_node *child; - u32 read_mask = 0; - int addr; - - if (!np) { - read_mask = ~bus->phy_mask; - } else { - for_each_available_child_of_node(np, child) { - addr = of_mdio_parse_addr(&bus->dev, child); - if (addr < 0) - continue; - - read_mask |= 1 << addr; - } - } - - for (addr = 0; addr < PHY_MAX_ADDR; addr++) { - if (read_mask & 1 << addr) { - dev_dbg(&bus->dev, "Workaround for PHY @ %d\n", addr); - mdiobus_read(bus, addr, MII_BMSR); - } - } - - return 0; -} - -static void unimac_mdio_clk_set(struct unimac_mdio_priv *priv) -{ - unsigned long rate; - u32 reg, div; - - /* Keep the hardware default values */ - if (!priv->clk_freq) - return; - - if (!priv->clk) - rate = 250000000; - else - rate = clk_get_rate(priv->clk); - - div = (rate / (2 * priv->clk_freq)) - 1; - if (div & ~MDIO_CLK_DIV_MASK) { - pr_warn("Incorrect MDIO clock frequency, ignoring\n"); - return; - } - - /* The MDIO clock is the reference clock (typicaly 250Mhz) divided by - * 2 x (MDIO_CLK_DIV + 1) - */ - reg = unimac_mdio_readl(priv, MDIO_CFG); - reg &= ~(MDIO_CLK_DIV_MASK << MDIO_CLK_DIV_SHIFT); - reg |= div << MDIO_CLK_DIV_SHIFT; - unimac_mdio_writel(priv, reg, MDIO_CFG); -} - -static int unimac_mdio_probe(struct platform_device *pdev) -{ - struct unimac_mdio_pdata *pdata = pdev->dev.platform_data; - struct unimac_mdio_priv *priv; - struct device_node *np; - struct mii_bus *bus; - struct resource *r; - int ret; - - np = pdev->dev.of_node; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r) - return -EINVAL; - - /* Just ioremap, as this MDIO block is usually integrated into an - * Ethernet MAC controller register range - */ - priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (!priv->base) { - dev_err(&pdev->dev, "failed to remap register\n"); - return -ENOMEM; - } - - priv->clk = devm_clk_get_optional(&pdev->dev, NULL); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); - - ret = clk_prepare_enable(priv->clk); - if (ret) - return ret; - - if (of_property_read_u32(np, "clock-frequency", &priv->clk_freq)) - priv->clk_freq = 0; - - unimac_mdio_clk_set(priv); - - priv->mii_bus = mdiobus_alloc(); - if (!priv->mii_bus) { - ret = -ENOMEM; - goto out_clk_disable; - } - - bus = priv->mii_bus; - bus->priv = priv; - if (pdata) { - bus->name = pdata->bus_name; - priv->wait_func = pdata->wait_func; - priv->wait_func_data = pdata->wait_func_data; - bus->phy_mask = ~pdata->phy_mask; - } else { - bus->name = "unimac MII bus"; - priv->wait_func_data = priv; - priv->wait_func = unimac_mdio_poll; - } - bus->parent = &pdev->dev; - bus->read = unimac_mdio_read; - bus->write = unimac_mdio_write; - bus->reset = unimac_mdio_reset; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); - - ret = of_mdiobus_register(bus, np); - if (ret) { - dev_err(&pdev->dev, "MDIO bus registration failed\n"); - goto out_mdio_free; - } - - platform_set_drvdata(pdev, priv); - - dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus\n"); - - return 0; - -out_mdio_free: - mdiobus_free(bus); -out_clk_disable: - clk_disable_unprepare(priv->clk); - return ret; -} - -static int unimac_mdio_remove(struct platform_device *pdev) -{ - struct unimac_mdio_priv *priv = platform_get_drvdata(pdev); - - mdiobus_unregister(priv->mii_bus); - mdiobus_free(priv->mii_bus); - clk_disable_unprepare(priv->clk); - - return 0; -} - -static int __maybe_unused unimac_mdio_suspend(struct device *d) -{ - struct unimac_mdio_priv *priv = dev_get_drvdata(d); - - clk_disable_unprepare(priv->clk); - - return 0; -} - -static int __maybe_unused unimac_mdio_resume(struct device *d) -{ - struct unimac_mdio_priv *priv = dev_get_drvdata(d); - int ret; - - ret = clk_prepare_enable(priv->clk); - if (ret) - return ret; - - unimac_mdio_clk_set(priv); - - return 0; -} - -static SIMPLE_DEV_PM_OPS(unimac_mdio_pm_ops, - unimac_mdio_suspend, unimac_mdio_resume); - -static const struct of_device_id unimac_mdio_ids[] = { - { .compatible = "brcm,genet-mdio-v5", }, - { .compatible = "brcm,genet-mdio-v4", }, - { .compatible = "brcm,genet-mdio-v3", }, - { .compatible = "brcm,genet-mdio-v2", }, - { .compatible = "brcm,genet-mdio-v1", }, - { .compatible = "brcm,unimac-mdio", }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(of, unimac_mdio_ids); - -static struct platform_driver unimac_mdio_driver = { - .driver = { - .name = UNIMAC_MDIO_DRV_NAME, - .of_match_table = unimac_mdio_ids, - .pm = &unimac_mdio_pm_ops, - }, - .probe = unimac_mdio_probe, - .remove = unimac_mdio_remove, -}; -module_platform_driver(unimac_mdio_driver); - -MODULE_AUTHOR("Broadcom Corporation"); -MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" UNIMAC_MDIO_DRV_NAME); diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c deleted file mode 100644 index 5136275c8e73..000000000000 --- a/drivers/net/phy/mdio-bitbang.c +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Bitbanged MDIO support. - * - * Author: Scott Wood - * Copyright (c) 2007 Freescale Semiconductor - * - * Based on CPM2 MDIO code which is: - * - * Copyright (c) 2003 Intracom S.A. - * by Pantelis Antoniou - * - * 2005 (c) MontaVista Software, Inc. - * Vitaly Bordug - */ - -#include -#include -#include -#include - -#define MDIO_READ 2 -#define MDIO_WRITE 1 - -#define MDIO_C45 (1<<15) -#define MDIO_C45_ADDR (MDIO_C45 | 0) -#define MDIO_C45_READ (MDIO_C45 | 3) -#define MDIO_C45_WRITE (MDIO_C45 | 1) - -#define MDIO_SETUP_TIME 10 -#define MDIO_HOLD_TIME 10 - -/* Minimum MDC period is 400 ns, plus some margin for error. MDIO_DELAY - * is done twice per period. - */ -#define MDIO_DELAY 250 - -/* The PHY may take up to 300 ns to produce data, plus some margin - * for error. - */ -#define MDIO_READ_DELAY 350 - -/* MDIO must already be configured as output. */ -static void mdiobb_send_bit(struct mdiobb_ctrl *ctrl, int val) -{ - const struct mdiobb_ops *ops = ctrl->ops; - - ops->set_mdio_data(ctrl, val); - ndelay(MDIO_DELAY); - ops->set_mdc(ctrl, 1); - ndelay(MDIO_DELAY); - ops->set_mdc(ctrl, 0); -} - -/* MDIO must already be configured as input. */ -static int mdiobb_get_bit(struct mdiobb_ctrl *ctrl) -{ - const struct mdiobb_ops *ops = ctrl->ops; - - ndelay(MDIO_DELAY); - ops->set_mdc(ctrl, 1); - ndelay(MDIO_READ_DELAY); - ops->set_mdc(ctrl, 0); - - return ops->get_mdio_data(ctrl); -} - -/* MDIO must already be configured as output. */ -static void mdiobb_send_num(struct mdiobb_ctrl *ctrl, u16 val, int bits) -{ - int i; - - for (i = bits - 1; i >= 0; i--) - mdiobb_send_bit(ctrl, (val >> i) & 1); -} - -/* MDIO must already be configured as input. */ -static u16 mdiobb_get_num(struct mdiobb_ctrl *ctrl, int bits) -{ - int i; - u16 ret = 0; - - for (i = bits - 1; i >= 0; i--) { - ret <<= 1; - ret |= mdiobb_get_bit(ctrl); - } - - return ret; -} - -/* Utility to send the preamble, address, and - * register (common to read and write). - */ -static void mdiobb_cmd(struct mdiobb_ctrl *ctrl, int op, u8 phy, u8 reg) -{ - const struct mdiobb_ops *ops = ctrl->ops; - int i; - - ops->set_mdio_dir(ctrl, 1); - - /* - * Send a 32 bit preamble ('1's) with an extra '1' bit for good - * measure. The IEEE spec says this is a PHY optional - * requirement. The AMD 79C874 requires one after power up and - * one after a MII communications error. This means that we are - * doing more preambles than we need, but it is safer and will be - * much more robust. - */ - - for (i = 0; i < 32; i++) - mdiobb_send_bit(ctrl, 1); - - /* send the start bit (01) and the read opcode (10) or write (01). - Clause 45 operation uses 00 for the start and 11, 10 for - read/write */ - mdiobb_send_bit(ctrl, 0); - if (op & MDIO_C45) - mdiobb_send_bit(ctrl, 0); - else - mdiobb_send_bit(ctrl, 1); - mdiobb_send_bit(ctrl, (op >> 1) & 1); - mdiobb_send_bit(ctrl, (op >> 0) & 1); - - mdiobb_send_num(ctrl, phy, 5); - mdiobb_send_num(ctrl, reg, 5); -} - -/* In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the - lower 16 bits of the 21 bit address. This transfer is done identically to a - MDIO_WRITE except for a different code. To enable clause 45 mode or - MII_ADDR_C45 into the address. Theoretically clause 45 and normal devices - can exist on the same bus. Normal devices should ignore the MDIO_ADDR - phase. */ -static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr) -{ - unsigned int dev_addr = (addr >> 16) & 0x1F; - unsigned int reg = addr & 0xFFFF; - mdiobb_cmd(ctrl, MDIO_C45_ADDR, phy, dev_addr); - - /* send the turnaround (10) */ - mdiobb_send_bit(ctrl, 1); - mdiobb_send_bit(ctrl, 0); - - mdiobb_send_num(ctrl, reg, 16); - - ctrl->ops->set_mdio_dir(ctrl, 0); - mdiobb_get_bit(ctrl); - - return dev_addr; -} - -static int mdiobb_read(struct mii_bus *bus, int phy, int reg) -{ - struct mdiobb_ctrl *ctrl = bus->priv; - int ret, i; - - if (reg & MII_ADDR_C45) { - reg = mdiobb_cmd_addr(ctrl, phy, reg); - mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg); - } else - mdiobb_cmd(ctrl, MDIO_READ, phy, reg); - - ctrl->ops->set_mdio_dir(ctrl, 0); - - /* check the turnaround bit: the PHY should be driving it to zero, if this - * PHY is listed in phy_ignore_ta_mask as having broken TA, skip that - */ - if (mdiobb_get_bit(ctrl) != 0 && - !(bus->phy_ignore_ta_mask & (1 << phy))) { - /* PHY didn't drive TA low -- flush any bits it - * may be trying to send. - */ - for (i = 0; i < 32; i++) - mdiobb_get_bit(ctrl); - - return 0xffff; - } - - ret = mdiobb_get_num(ctrl, 16); - mdiobb_get_bit(ctrl); - return ret; -} - -static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val) -{ - struct mdiobb_ctrl *ctrl = bus->priv; - - if (reg & MII_ADDR_C45) { - reg = mdiobb_cmd_addr(ctrl, phy, reg); - mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg); - } else - mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg); - - /* send the turnaround (10) */ - mdiobb_send_bit(ctrl, 1); - mdiobb_send_bit(ctrl, 0); - - mdiobb_send_num(ctrl, val, 16); - - ctrl->ops->set_mdio_dir(ctrl, 0); - mdiobb_get_bit(ctrl); - return 0; -} - -struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl) -{ - struct mii_bus *bus; - - bus = mdiobus_alloc(); - if (!bus) - return NULL; - - __module_get(ctrl->ops->owner); - - bus->read = mdiobb_read; - bus->write = mdiobb_write; - bus->priv = ctrl; - - return bus; -} -EXPORT_SYMBOL(alloc_mdio_bitbang); - -void free_mdio_bitbang(struct mii_bus *bus) -{ - struct mdiobb_ctrl *ctrl = bus->priv; - - module_put(ctrl->ops->owner); - mdiobus_free(bus); -} -EXPORT_SYMBOL(free_mdio_bitbang); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-cavium.c b/drivers/net/phy/mdio-cavium.c deleted file mode 100644 index 1afd6fc1a351..000000000000 --- a/drivers/net/phy/mdio-cavium.c +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2009-2016 Cavium, Inc. - */ - -#include -#include -#include -#include - -#include "mdio-cavium.h" - -static void cavium_mdiobus_set_mode(struct cavium_mdiobus *p, - enum cavium_mdiobus_mode m) -{ - union cvmx_smix_clk smi_clk; - - if (m == p->mode) - return; - - smi_clk.u64 = oct_mdio_readq(p->register_base + SMI_CLK); - smi_clk.s.mode = (m == C45) ? 1 : 0; - smi_clk.s.preamble = 1; - oct_mdio_writeq(smi_clk.u64, p->register_base + SMI_CLK); - p->mode = m; -} - -static int cavium_mdiobus_c45_addr(struct cavium_mdiobus *p, - int phy_id, int regnum) -{ - union cvmx_smix_cmd smi_cmd; - union cvmx_smix_wr_dat smi_wr; - int timeout = 1000; - - cavium_mdiobus_set_mode(p, C45); - - smi_wr.u64 = 0; - smi_wr.s.dat = regnum & 0xffff; - oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); - - 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; - oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); - - do { - /* Wait 1000 clocks so we don't saturate the RSL bus - * doing reads. - */ - __delay(1000); - smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); - } while (smi_wr.s.pending && --timeout); - - if (timeout <= 0) - return -EIO; - return 0; -} - -int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) -{ - struct cavium_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 = cavium_mdiobus_c45_addr(p, phy_id, regnum); - - if (r < 0) - return r; - - regnum = (regnum >> 16) & 0x1f; - op = 3; /* MDIO_CLAUSE_45_READ */ - } else { - cavium_mdiobus_set_mode(p, C22); - } - - smi_cmd.u64 = 0; - smi_cmd.s.phy_op = op; - smi_cmd.s.phy_adr = phy_id; - smi_cmd.s.reg_adr = regnum; - oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); - - do { - /* Wait 1000 clocks so we don't saturate the RSL bus - * doing reads. - */ - __delay(1000); - smi_rd.u64 = oct_mdio_readq(p->register_base + SMI_RD_DAT); - } while (smi_rd.s.pending && --timeout); - - if (smi_rd.s.val) - return smi_rd.s.dat; - else - return -EIO; -} -EXPORT_SYMBOL(cavium_mdiobus_read); - -int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val) -{ - struct cavium_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 = cavium_mdiobus_c45_addr(p, phy_id, regnum); - - if (r < 0) - return r; - - regnum = (regnum >> 16) & 0x1f; - op = 1; /* MDIO_CLAUSE_45_WRITE */ - } else { - cavium_mdiobus_set_mode(p, C22); - } - - smi_wr.u64 = 0; - smi_wr.s.dat = val; - oct_mdio_writeq(smi_wr.u64, p->register_base + SMI_WR_DAT); - - smi_cmd.u64 = 0; - smi_cmd.s.phy_op = op; - smi_cmd.s.phy_adr = phy_id; - smi_cmd.s.reg_adr = regnum; - oct_mdio_writeq(smi_cmd.u64, p->register_base + SMI_CMD); - - do { - /* Wait 1000 clocks so we don't saturate the RSL bus - * doing reads. - */ - __delay(1000); - smi_wr.u64 = oct_mdio_readq(p->register_base + SMI_WR_DAT); - } while (smi_wr.s.pending && --timeout); - - if (timeout <= 0) - return -EIO; - - return 0; -} -EXPORT_SYMBOL(cavium_mdiobus_write); - -MODULE_DESCRIPTION("Common code for OCTEON and Thunder MDIO bus drivers"); -MODULE_AUTHOR("David Daney"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-cavium.h b/drivers/net/phy/mdio-cavium.h deleted file mode 100644 index a2245d436f5d..000000000000 --- a/drivers/net/phy/mdio-cavium.h +++ /dev/null @@ -1,118 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (C) 2009-2016 Cavium, Inc. - */ - -enum cavium_mdiobus_mode { - UNINIT = 0, - C22, - C45 -}; - -#define SMI_CMD 0x0 -#define SMI_WR_DAT 0x8 -#define SMI_RD_DAT 0x10 -#define SMI_CLK 0x18 -#define SMI_EN 0x20 - -#ifdef __BIG_ENDIAN_BITFIELD -#define OCT_MDIO_BITFIELD_FIELD(field, more) \ - field; \ - more - -#else -#define OCT_MDIO_BITFIELD_FIELD(field, more) \ - more \ - field; - -#endif - -union cvmx_smix_clk { - u64 u64; - struct cvmx_smix_clk_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_25_63:39, - OCT_MDIO_BITFIELD_FIELD(u64 mode:1, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_21_23:3, - OCT_MDIO_BITFIELD_FIELD(u64 sample_hi:5, - OCT_MDIO_BITFIELD_FIELD(u64 sample_mode:1, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_14_14:1, - OCT_MDIO_BITFIELD_FIELD(u64 clk_idle:1, - OCT_MDIO_BITFIELD_FIELD(u64 preamble:1, - OCT_MDIO_BITFIELD_FIELD(u64 sample:4, - OCT_MDIO_BITFIELD_FIELD(u64 phase:8, - ;)))))))))) - } s; -}; - -union cvmx_smix_cmd { - u64 u64; - struct cvmx_smix_cmd_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, - OCT_MDIO_BITFIELD_FIELD(u64 phy_op:2, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_13_15:3, - OCT_MDIO_BITFIELD_FIELD(u64 phy_adr:5, - OCT_MDIO_BITFIELD_FIELD(u64 reserved_5_7:3, - OCT_MDIO_BITFIELD_FIELD(u64 reg_adr:5, - ;)))))) - } s; -}; - -union cvmx_smix_en { - u64 u64; - struct cvmx_smix_en_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_1_63:63, - OCT_MDIO_BITFIELD_FIELD(u64 en:1, - ;)) - } s; -}; - -union cvmx_smix_rd_dat { - u64 u64; - struct cvmx_smix_rd_dat_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, - OCT_MDIO_BITFIELD_FIELD(u64 pending:1, - OCT_MDIO_BITFIELD_FIELD(u64 val:1, - OCT_MDIO_BITFIELD_FIELD(u64 dat:16, - ;)))) - } s; -}; - -union cvmx_smix_wr_dat { - u64 u64; - struct cvmx_smix_wr_dat_s { - OCT_MDIO_BITFIELD_FIELD(u64 reserved_18_63:46, - OCT_MDIO_BITFIELD_FIELD(u64 pending:1, - OCT_MDIO_BITFIELD_FIELD(u64 val:1, - OCT_MDIO_BITFIELD_FIELD(u64 dat:16, - ;)))) - } s; -}; - -struct cavium_mdiobus { - struct mii_bus *mii_bus; - void __iomem *register_base; - enum cavium_mdiobus_mode mode; -}; - -#ifdef CONFIG_CAVIUM_OCTEON_SOC - -#include - -static inline void oct_mdio_writeq(u64 val, void __iomem *addr) -{ - cvmx_write_csr((u64 __force)addr, val); -} - -static inline u64 oct_mdio_readq(void __iomem *addr) -{ - return cvmx_read_csr((u64 __force)addr); -} -#else -#include - -#define oct_mdio_writeq(val, addr) writeq(val, addr) -#define oct_mdio_readq(addr) readq(addr) -#endif - -int cavium_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum); -int cavium_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val); diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c deleted file mode 100644 index 1b00235d7dc5..000000000000 --- a/drivers/net/phy/mdio-gpio.c +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * GPIO based MDIO bitbang driver. - * Supports OpenFirmware. - * - * Copyright (c) 2008 CSE Semaphore Belgium. - * by Laurent Pinchart - * - * Copyright (C) 2008, Paulius Zaleckas - * - * Based on earlier work by - * - * Copyright (c) 2003 Intracom S.A. - * by Pantelis Antoniou - * - * 2005 (c) MontaVista Software, Inc. - * Vitaly Bordug - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct mdio_gpio_info { - struct mdiobb_ctrl ctrl; - struct gpio_desc *mdc, *mdio, *mdo; -}; - -static int mdio_gpio_get_data(struct device *dev, - struct mdio_gpio_info *bitbang) -{ - bitbang->mdc = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDC, - GPIOD_OUT_LOW); - if (IS_ERR(bitbang->mdc)) - return PTR_ERR(bitbang->mdc); - - bitbang->mdio = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDIO, - GPIOD_IN); - if (IS_ERR(bitbang->mdio)) - return PTR_ERR(bitbang->mdio); - - bitbang->mdo = devm_gpiod_get_index_optional(dev, NULL, MDIO_GPIO_MDO, - GPIOD_OUT_LOW); - return PTR_ERR_OR_ZERO(bitbang->mdo); -} - -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. - */ - gpiod_set_value_cansleep(bitbang->mdo, 1); - return; - } - - if (dir) - gpiod_direction_output(bitbang->mdio, 1); - else - gpiod_direction_input(bitbang->mdio); -} - -static int mdio_get(struct mdiobb_ctrl *ctrl) -{ - struct mdio_gpio_info *bitbang = - container_of(ctrl, struct mdio_gpio_info, ctrl); - - return gpiod_get_value_cansleep(bitbang->mdio); -} - -static void mdio_set(struct mdiobb_ctrl *ctrl, int what) -{ - struct mdio_gpio_info *bitbang = - container_of(ctrl, struct mdio_gpio_info, ctrl); - - if (bitbang->mdo) - gpiod_set_value_cansleep(bitbang->mdo, what); - else - gpiod_set_value_cansleep(bitbang->mdio, what); -} - -static void mdc_set(struct mdiobb_ctrl *ctrl, int what) -{ - struct mdio_gpio_info *bitbang = - container_of(ctrl, struct mdio_gpio_info, ctrl); - - gpiod_set_value_cansleep(bitbang->mdc, what); -} - -static const struct mdiobb_ops mdio_gpio_ops = { - .owner = THIS_MODULE, - .set_mdc = mdc_set, - .set_mdio_dir = mdio_dir, - .set_mdio_data = mdio_set, - .get_mdio_data = mdio_get, -}; - -static struct mii_bus *mdio_gpio_bus_init(struct device *dev, - struct mdio_gpio_info *bitbang, - int bus_id) -{ - struct mdio_gpio_platform_data *pdata = dev_get_platdata(dev); - struct mii_bus *new_bus; - - bitbang->ctrl.ops = &mdio_gpio_ops; - - new_bus = alloc_mdio_bitbang(&bitbang->ctrl); - if (!new_bus) - return NULL; - - new_bus->name = "GPIO Bitbanged MDIO"; - new_bus->parent = dev; - - if (bus_id != -1) - snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id); - else - strncpy(new_bus->id, "gpio", MII_BUS_ID_SIZE); - - if (pdata) { - new_bus->phy_mask = pdata->phy_mask; - new_bus->phy_ignore_ta_mask = pdata->phy_ignore_ta_mask; - } - - dev_set_drvdata(dev, new_bus); - - return new_bus; -} - -static void mdio_gpio_bus_deinit(struct device *dev) -{ - struct mii_bus *bus = dev_get_drvdata(dev); - - free_mdio_bitbang(bus); -} - -static void mdio_gpio_bus_destroy(struct device *dev) -{ - struct mii_bus *bus = dev_get_drvdata(dev); - - mdiobus_unregister(bus); - mdio_gpio_bus_deinit(dev); -} - -static int mdio_gpio_probe(struct platform_device *pdev) -{ - struct mdio_gpio_info *bitbang; - struct mii_bus *new_bus; - int ret, bus_id; - - bitbang = devm_kzalloc(&pdev->dev, sizeof(*bitbang), GFP_KERNEL); - if (!bitbang) - return -ENOMEM; - - ret = mdio_gpio_get_data(&pdev->dev, bitbang); - if (ret) - return ret; - - if (pdev->dev.of_node) { - 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 { - bus_id = pdev->id; - } - - new_bus = mdio_gpio_bus_init(&pdev->dev, bitbang, bus_id); - if (!new_bus) - return -ENODEV; - - ret = of_mdiobus_register(new_bus, pdev->dev.of_node); - if (ret) - mdio_gpio_bus_deinit(&pdev->dev); - - return ret; -} - -static int mdio_gpio_remove(struct platform_device *pdev) -{ - mdio_gpio_bus_destroy(&pdev->dev); - - return 0; -} - -static const struct of_device_id mdio_gpio_of_match[] = { - { .compatible = "virtual,mdio-gpio", }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, mdio_gpio_of_match); - -static struct platform_driver mdio_gpio_driver = { - .probe = mdio_gpio_probe, - .remove = mdio_gpio_remove, - .driver = { - .name = "mdio-gpio", - .of_match_table = mdio_gpio_of_match, - }, -}; - -module_platform_driver(mdio_gpio_driver); - -MODULE_ALIAS("platform:mdio-gpio"); -MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Generic driver for MDIO bus emulation using GPIO"); diff --git a/drivers/net/phy/mdio-hisi-femac.c b/drivers/net/phy/mdio-hisi-femac.c deleted file mode 100644 index f231c2fbb1de..000000000000 --- a/drivers/net/phy/mdio-hisi-femac.c +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Hisilicon Fast Ethernet MDIO Bus Driver - * - * Copyright (c) 2016 HiSilicon Technologies Co., Ltd. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define MDIO_RWCTRL 0x00 -#define MDIO_RO_DATA 0x04 -#define MDIO_WRITE BIT(13) -#define MDIO_RW_FINISH BIT(15) -#define BIT_PHY_ADDR_OFFSET 8 -#define BIT_WR_DATA_OFFSET 16 - -struct hisi_femac_mdio_data { - struct clk *clk; - void __iomem *membase; -}; - -static int hisi_femac_mdio_wait_ready(struct hisi_femac_mdio_data *data) -{ - u32 val; - - return readl_poll_timeout(data->membase + MDIO_RWCTRL, - val, val & MDIO_RW_FINISH, 20, 10000); -} - -static int hisi_femac_mdio_read(struct mii_bus *bus, int mii_id, int regnum) -{ - struct hisi_femac_mdio_data *data = bus->priv; - int ret; - - ret = hisi_femac_mdio_wait_ready(data); - if (ret) - return ret; - - writel((mii_id << BIT_PHY_ADDR_OFFSET) | regnum, - data->membase + MDIO_RWCTRL); - - ret = hisi_femac_mdio_wait_ready(data); - if (ret) - return ret; - - return readl(data->membase + MDIO_RO_DATA) & 0xFFFF; -} - -static int hisi_femac_mdio_write(struct mii_bus *bus, int mii_id, int regnum, - u16 value) -{ - struct hisi_femac_mdio_data *data = bus->priv; - int ret; - - ret = hisi_femac_mdio_wait_ready(data); - if (ret) - return ret; - - writel(MDIO_WRITE | (value << BIT_WR_DATA_OFFSET) | - (mii_id << BIT_PHY_ADDR_OFFSET) | regnum, - data->membase + MDIO_RWCTRL); - - return hisi_femac_mdio_wait_ready(data); -} - -static int hisi_femac_mdio_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct mii_bus *bus; - struct hisi_femac_mdio_data *data; - int ret; - - bus = mdiobus_alloc_size(sizeof(*data)); - if (!bus) - return -ENOMEM; - - bus->name = "hisi_femac_mii_bus"; - bus->read = &hisi_femac_mdio_read; - bus->write = &hisi_femac_mdio_write; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); - bus->parent = &pdev->dev; - - data = bus->priv; - data->membase = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(data->membase)) { - ret = PTR_ERR(data->membase); - goto err_out_free_mdiobus; - } - - data->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(data->clk)) { - ret = PTR_ERR(data->clk); - goto err_out_free_mdiobus; - } - - ret = clk_prepare_enable(data->clk); - if (ret) - goto err_out_free_mdiobus; - - ret = of_mdiobus_register(bus, np); - if (ret) - goto err_out_disable_clk; - - platform_set_drvdata(pdev, bus); - - return 0; - -err_out_disable_clk: - clk_disable_unprepare(data->clk); -err_out_free_mdiobus: - mdiobus_free(bus); - return ret; -} - -static int hisi_femac_mdio_remove(struct platform_device *pdev) -{ - struct mii_bus *bus = platform_get_drvdata(pdev); - struct hisi_femac_mdio_data *data = bus->priv; - - mdiobus_unregister(bus); - clk_disable_unprepare(data->clk); - mdiobus_free(bus); - - return 0; -} - -static const struct of_device_id hisi_femac_mdio_dt_ids[] = { - { .compatible = "hisilicon,hisi-femac-mdio" }, - { } -}; -MODULE_DEVICE_TABLE(of, hisi_femac_mdio_dt_ids); - -static struct platform_driver hisi_femac_mdio_driver = { - .probe = hisi_femac_mdio_probe, - .remove = hisi_femac_mdio_remove, - .driver = { - .name = "hisi-femac-mdio", - .of_match_table = hisi_femac_mdio_dt_ids, - }, -}; - -module_platform_driver(hisi_femac_mdio_driver); - -MODULE_DESCRIPTION("Hisilicon Fast Ethernet MAC MDIO interface driver"); -MODULE_AUTHOR("Dongpo Li "); -MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c deleted file mode 100644 index 09200a70b315..000000000000 --- a/drivers/net/phy/mdio-i2c.c +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * MDIO I2C bridge - * - * Copyright (C) 2015-2016 Russell King - * - * Network PHYs can appear on I2C buses when they are part of SFP module. - * This driver exposes these PHYs to the networking PHY code, allowing - * our PHY drivers access to these PHYs, and so allowing configuration - * of their settings. - */ -#include -#include -#include - -/* - * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is - * specified to be present in SFP modules. These correspond with PHY - * addresses 16 and 17. Disallow access to these "phy" addresses. - */ -static bool i2c_mii_valid_phy_id(int phy_id) -{ - return phy_id != 0x10 && phy_id != 0x11; -} - -static unsigned int i2c_mii_phy_addr(int phy_id) -{ - return phy_id + 0x40; -} - -static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg) -{ - struct i2c_adapter *i2c = bus->priv; - struct i2c_msg msgs[2]; - u8 addr[3], data[2], *p; - int bus_addr, ret; - - if (!i2c_mii_valid_phy_id(phy_id)) - return 0xffff; - - p = addr; - if (reg & MII_ADDR_C45) { - *p++ = 0x20 | ((reg >> 16) & 31); - *p++ = reg >> 8; - } - *p++ = reg; - - bus_addr = i2c_mii_phy_addr(phy_id); - msgs[0].addr = bus_addr; - msgs[0].flags = 0; - msgs[0].len = p - addr; - msgs[0].buf = addr; - msgs[1].addr = bus_addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = sizeof(data); - msgs[1].buf = data; - - ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); - if (ret != ARRAY_SIZE(msgs)) - return 0xffff; - - return data[0] << 8 | data[1]; -} - -static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val) -{ - struct i2c_adapter *i2c = bus->priv; - struct i2c_msg msg; - int ret; - u8 data[5], *p; - - if (!i2c_mii_valid_phy_id(phy_id)) - return 0; - - p = data; - if (reg & MII_ADDR_C45) { - *p++ = (reg >> 16) & 31; - *p++ = reg >> 8; - } - *p++ = reg; - *p++ = val >> 8; - *p++ = val; - - msg.addr = i2c_mii_phy_addr(phy_id); - msg.flags = 0; - msg.len = p - data; - msg.buf = data; - - ret = i2c_transfer(i2c, &msg, 1); - - return ret < 0 ? ret : 0; -} - -struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c) -{ - struct mii_bus *mii; - - if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) - return ERR_PTR(-EINVAL); - - mii = mdiobus_alloc(); - if (!mii) - return ERR_PTR(-ENOMEM); - - snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent)); - mii->parent = parent; - mii->read = i2c_mii_read; - mii->write = i2c_mii_write; - mii->priv = i2c; - - return mii; -} -EXPORT_SYMBOL_GPL(mdio_i2c_alloc); - -MODULE_AUTHOR("Russell King"); -MODULE_DESCRIPTION("MDIO I2C bridge library"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-ipq4019.c b/drivers/net/phy/mdio-ipq4019.c deleted file mode 100644 index 1ce81ff2f41d..000000000000 --- a/drivers/net/phy/mdio-ipq4019.c +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause -/* Copyright (c) 2015, The Linux Foundation. All rights reserved. */ -/* Copyright (c) 2020 Sartura Ltd. */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MDIO_ADDR_REG 0x44 -#define MDIO_DATA_WRITE_REG 0x48 -#define MDIO_DATA_READ_REG 0x4c -#define MDIO_CMD_REG 0x50 -#define MDIO_CMD_ACCESS_BUSY BIT(16) -#define MDIO_CMD_ACCESS_START BIT(8) -#define MDIO_CMD_ACCESS_CODE_READ 0 -#define MDIO_CMD_ACCESS_CODE_WRITE 1 - -#define ipq4019_MDIO_TIMEOUT 10000 -#define ipq4019_MDIO_SLEEP 10 - -struct ipq4019_mdio_data { - void __iomem *membase; -}; - -static int ipq4019_mdio_wait_busy(struct mii_bus *bus) -{ - struct ipq4019_mdio_data *priv = bus->priv; - unsigned int busy; - - return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, - (busy & MDIO_CMD_ACCESS_BUSY) == 0, - ipq4019_MDIO_SLEEP, ipq4019_MDIO_TIMEOUT); -} - -static int ipq4019_mdio_read(struct mii_bus *bus, int mii_id, int regnum) -{ - struct ipq4019_mdio_data *priv = bus->priv; - unsigned int cmd; - - /* Reject clause 45 */ - if (regnum & MII_ADDR_C45) - return -EOPNOTSUPP; - - if (ipq4019_mdio_wait_busy(bus)) - return -ETIMEDOUT; - - /* issue the phy address and reg */ - writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); - - cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; - - /* issue read command */ - writel(cmd, priv->membase + MDIO_CMD_REG); - - /* Wait read complete */ - if (ipq4019_mdio_wait_busy(bus)) - return -ETIMEDOUT; - - /* Read and return data */ - return readl(priv->membase + MDIO_DATA_READ_REG); -} - -static int ipq4019_mdio_write(struct mii_bus *bus, int mii_id, int regnum, - u16 value) -{ - struct ipq4019_mdio_data *priv = bus->priv; - unsigned int cmd; - - /* Reject clause 45 */ - if (regnum & MII_ADDR_C45) - return -EOPNOTSUPP; - - if (ipq4019_mdio_wait_busy(bus)) - return -ETIMEDOUT; - - /* issue the phy address and reg */ - writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); - - /* issue write data */ - writel(value, priv->membase + MDIO_DATA_WRITE_REG); - - cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; - /* issue write command */ - writel(cmd, priv->membase + MDIO_CMD_REG); - - /* Wait write complete */ - if (ipq4019_mdio_wait_busy(bus)) - return -ETIMEDOUT; - - return 0; -} - -static int ipq4019_mdio_probe(struct platform_device *pdev) -{ - struct ipq4019_mdio_data *priv; - struct mii_bus *bus; - int ret; - - bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); - if (!bus) - return -ENOMEM; - - priv = bus->priv; - - priv->membase = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->membase)) - return PTR_ERR(priv->membase); - - bus->name = "ipq4019_mdio"; - bus->read = ipq4019_mdio_read; - bus->write = ipq4019_mdio_write; - bus->parent = &pdev->dev; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); - - ret = of_mdiobus_register(bus, pdev->dev.of_node); - if (ret) { - dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); - return ret; - } - - platform_set_drvdata(pdev, bus); - - return 0; -} - -static int ipq4019_mdio_remove(struct platform_device *pdev) -{ - struct mii_bus *bus = platform_get_drvdata(pdev); - - mdiobus_unregister(bus); - - return 0; -} - -static const struct of_device_id ipq4019_mdio_dt_ids[] = { - { .compatible = "qcom,ipq4019-mdio" }, - { } -}; -MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); - -static struct platform_driver ipq4019_mdio_driver = { - .probe = ipq4019_mdio_probe, - .remove = ipq4019_mdio_remove, - .driver = { - .name = "ipq4019-mdio", - .of_match_table = ipq4019_mdio_dt_ids, - }, -}; - -module_platform_driver(ipq4019_mdio_driver); - -MODULE_DESCRIPTION("ipq4019 MDIO interface driver"); -MODULE_AUTHOR("Qualcomm Atheros"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/phy/mdio-ipq8064.c b/drivers/net/phy/mdio-ipq8064.c deleted file mode 100644 index 1bd18857e1c5..000000000000 --- a/drivers/net/phy/mdio-ipq8064.c +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* Qualcomm IPQ8064 MDIO interface driver - * - * Copyright (C) 2019 Christian Lamparter - * Copyright (C) 2020 Ansuel Smith - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* MII address register definitions */ -#define MII_ADDR_REG_ADDR 0x10 -#define MII_BUSY BIT(0) -#define MII_WRITE BIT(1) -#define MII_CLKRANGE_60_100M (0 << 2) -#define MII_CLKRANGE_100_150M (1 << 2) -#define MII_CLKRANGE_20_35M (2 << 2) -#define MII_CLKRANGE_35_60M (3 << 2) -#define MII_CLKRANGE_150_250M (4 << 2) -#define MII_CLKRANGE_250_300M (5 << 2) -#define MII_CLKRANGE_MASK GENMASK(4, 2) -#define MII_REG_SHIFT 6 -#define MII_REG_MASK GENMASK(10, 6) -#define MII_ADDR_SHIFT 11 -#define MII_ADDR_MASK GENMASK(15, 11) - -#define MII_DATA_REG_ADDR 0x14 - -#define MII_MDIO_DELAY_USEC (1000) -#define MII_MDIO_RETRY_MSEC (10) - -struct ipq8064_mdio { - struct regmap *base; /* NSS_GMAC0_BASE */ -}; - -static int -ipq8064_mdio_wait_busy(struct ipq8064_mdio *priv) -{ - u32 busy; - - return regmap_read_poll_timeout(priv->base, MII_ADDR_REG_ADDR, busy, - !(busy & MII_BUSY), MII_MDIO_DELAY_USEC, - MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); -} - -static int -ipq8064_mdio_read(struct mii_bus *bus, int phy_addr, int reg_offset) -{ - u32 miiaddr = MII_BUSY | MII_CLKRANGE_250_300M; - struct ipq8064_mdio *priv = bus->priv; - u32 ret_val; - int err; - - /* Reject clause 45 */ - if (reg_offset & MII_ADDR_C45) - return -EOPNOTSUPP; - - miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) | - ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK); - - regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr); - usleep_range(8, 10); - - err = ipq8064_mdio_wait_busy(priv); - if (err) - return err; - - regmap_read(priv->base, MII_DATA_REG_ADDR, &ret_val); - return (int)ret_val; -} - -static int -ipq8064_mdio_write(struct mii_bus *bus, int phy_addr, int reg_offset, u16 data) -{ - u32 miiaddr = MII_WRITE | MII_BUSY | MII_CLKRANGE_250_300M; - struct ipq8064_mdio *priv = bus->priv; - - /* Reject clause 45 */ - if (reg_offset & MII_ADDR_C45) - return -EOPNOTSUPP; - - regmap_write(priv->base, MII_DATA_REG_ADDR, data); - - miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) | - ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK); - - regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr); - usleep_range(8, 10); - - return ipq8064_mdio_wait_busy(priv); -} - -static int -ipq8064_mdio_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - struct ipq8064_mdio *priv; - struct mii_bus *bus; - int ret; - - bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); - if (!bus) - return -ENOMEM; - - bus->name = "ipq8064_mdio_bus"; - bus->read = ipq8064_mdio_read; - bus->write = ipq8064_mdio_write; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); - bus->parent = &pdev->dev; - - priv = bus->priv; - priv->base = device_node_to_regmap(np); - if (IS_ERR(priv->base)) { - if (priv->base == ERR_PTR(-EPROBE_DEFER)) - return -EPROBE_DEFER; - - dev_err(&pdev->dev, "error getting device regmap, error=%pe\n", - priv->base); - return PTR_ERR(priv->base); - } - - ret = of_mdiobus_register(bus, np); - if (ret) - return ret; - - platform_set_drvdata(pdev, bus); - return 0; -} - -static int -ipq8064_mdio_remove(struct platform_device *pdev) -{ - struct mii_bus *bus = platform_get_drvdata(pdev); - - mdiobus_unregister(bus); - - return 0; -} - -static const struct of_device_id ipq8064_mdio_dt_ids[] = { - { .compatible = "qcom,ipq8064-mdio" }, - { } -}; -MODULE_DEVICE_TABLE(of, ipq8064_mdio_dt_ids); - -static struct platform_driver ipq8064_mdio_driver = { - .probe = ipq8064_mdio_probe, - .remove = ipq8064_mdio_remove, - .driver = { - .name = "ipq8064-mdio", - .of_match_table = ipq8064_mdio_dt_ids, - }, -}; - -module_platform_driver(ipq8064_mdio_driver); - -MODULE_DESCRIPTION("Qualcomm IPQ8064 MDIO interface driver"); -MODULE_AUTHOR("Christian Lamparter "); -MODULE_AUTHOR("Ansuel Smith "); -MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-moxart.c b/drivers/net/phy/mdio-moxart.c deleted file mode 100644 index b72c6d185175..000000000000 --- a/drivers/net/phy/mdio-moxart.c +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver - * - * Copyright (C) 2013 Jonas Jensen - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#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; - 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; - - /* 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; - data->base = devm_platform_ioremap_resource(pdev, 0); - 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 "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-mscc-miim.c b/drivers/net/phy/mdio-mscc-miim.c deleted file mode 100644 index 11f583fd4611..000000000000 --- a/drivers/net/phy/mdio-mscc-miim.c +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0 OR MIT) -/* - * Driver for the MDIO interface of Microsemi network switches. - * - * Author: Alexandre Belloni - * Copyright (c) 2017 Microsemi Corporation - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define MSCC_MIIM_REG_STATUS 0x0 -#define MSCC_MIIM_STATUS_STAT_PENDING BIT(2) -#define MSCC_MIIM_STATUS_STAT_BUSY BIT(3) -#define MSCC_MIIM_REG_CMD 0x8 -#define MSCC_MIIM_CMD_OPR_WRITE BIT(1) -#define MSCC_MIIM_CMD_OPR_READ BIT(2) -#define MSCC_MIIM_CMD_WRDATA_SHIFT 4 -#define MSCC_MIIM_CMD_REGAD_SHIFT 20 -#define MSCC_MIIM_CMD_PHYAD_SHIFT 25 -#define MSCC_MIIM_CMD_VLD BIT(31) -#define MSCC_MIIM_REG_DATA 0xC -#define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17)) - -#define MSCC_PHY_REG_PHY_CFG 0x0 -#define PHY_CFG_PHY_ENA (BIT(0) | BIT(1) | BIT(2) | BIT(3)) -#define PHY_CFG_PHY_COMMON_RESET BIT(4) -#define PHY_CFG_PHY_RESET (BIT(5) | BIT(6) | BIT(7) | BIT(8)) -#define MSCC_PHY_REG_PHY_STATUS 0x4 - -struct mscc_miim_dev { - void __iomem *regs; - void __iomem *phy_regs; -}; - -/* When high resolution timers aren't built-in: we can't use usleep_range() as - * we would sleep way too long. Use udelay() instead. - */ -#define mscc_readl_poll_timeout(addr, val, cond, delay_us, timeout_us) \ -({ \ - if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS)) \ - readl_poll_timeout_atomic(addr, val, cond, delay_us, \ - timeout_us); \ - readl_poll_timeout(addr, val, cond, delay_us, timeout_us); \ -}) - -static int mscc_miim_wait_ready(struct mii_bus *bus) -{ - struct mscc_miim_dev *miim = bus->priv; - u32 val; - - return mscc_readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val, - !(val & MSCC_MIIM_STATUS_STAT_BUSY), 50, - 10000); -} - -static int mscc_miim_wait_pending(struct mii_bus *bus) -{ - struct mscc_miim_dev *miim = bus->priv; - u32 val; - - return mscc_readl_poll_timeout(miim->regs + MSCC_MIIM_REG_STATUS, val, - !(val & MSCC_MIIM_STATUS_STAT_PENDING), - 50, 10000); -} - -static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum) -{ - struct mscc_miim_dev *miim = bus->priv; - u32 val; - int ret; - - ret = mscc_miim_wait_pending(bus); - if (ret) - goto out; - - writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | - (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | MSCC_MIIM_CMD_OPR_READ, - miim->regs + MSCC_MIIM_REG_CMD); - - ret = mscc_miim_wait_ready(bus); - if (ret) - goto out; - - val = readl(miim->regs + MSCC_MIIM_REG_DATA); - if (val & MSCC_MIIM_DATA_ERROR) { - ret = -EIO; - goto out; - } - - ret = val & 0xFFFF; -out: - return ret; -} - -static int mscc_miim_write(struct mii_bus *bus, int mii_id, - int regnum, u16 value) -{ - struct mscc_miim_dev *miim = bus->priv; - int ret; - - ret = mscc_miim_wait_pending(bus); - if (ret < 0) - goto out; - - writel(MSCC_MIIM_CMD_VLD | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | - (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | - (value << MSCC_MIIM_CMD_WRDATA_SHIFT) | - MSCC_MIIM_CMD_OPR_WRITE, - miim->regs + MSCC_MIIM_REG_CMD); - -out: - return ret; -} - -static int mscc_miim_reset(struct mii_bus *bus) -{ - struct mscc_miim_dev *miim = bus->priv; - - if (miim->phy_regs) { - writel(0, miim->phy_regs + MSCC_PHY_REG_PHY_CFG); - writel(0x1ff, miim->phy_regs + MSCC_PHY_REG_PHY_CFG); - mdelay(500); - } - - return 0; -} - -static int mscc_miim_probe(struct platform_device *pdev) -{ - struct resource *res; - struct mii_bus *bus; - struct mscc_miim_dev *dev; - int ret; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENODEV; - - bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*dev)); - if (!bus) - return -ENOMEM; - - bus->name = "mscc_miim"; - bus->read = mscc_miim_read; - bus->write = mscc_miim_write; - bus->reset = mscc_miim_reset; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); - bus->parent = &pdev->dev; - - dev = bus->priv; - dev->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(dev->regs)) { - dev_err(&pdev->dev, "Unable to map MIIM registers\n"); - return PTR_ERR(dev->regs); - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res) { - dev->phy_regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(dev->phy_regs)) { - dev_err(&pdev->dev, "Unable to map internal phy registers\n"); - return PTR_ERR(dev->phy_regs); - } - } - - ret = of_mdiobus_register(bus, pdev->dev.of_node); - if (ret < 0) { - dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); - return ret; - } - - platform_set_drvdata(pdev, bus); - - return 0; -} - -static int mscc_miim_remove(struct platform_device *pdev) -{ - struct mii_bus *bus = platform_get_drvdata(pdev); - - mdiobus_unregister(bus); - - return 0; -} - -static const struct of_device_id mscc_miim_match[] = { - { .compatible = "mscc,ocelot-miim" }, - { } -}; -MODULE_DEVICE_TABLE(of, mscc_miim_match); - -static struct platform_driver mscc_miim_driver = { - .probe = mscc_miim_probe, - .remove = mscc_miim_remove, - .driver = { - .name = "mscc-miim", - .of_match_table = mscc_miim_match, - }, -}; - -module_platform_driver(mscc_miim_driver); - -MODULE_DESCRIPTION("Microsemi MIIM driver"); -MODULE_AUTHOR("Alexandre Belloni "); -MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/net/phy/mdio-mux-bcm-iproc.c b/drivers/net/phy/mdio-mux-bcm-iproc.c deleted file mode 100644 index 42fb5f166136..000000000000 --- a/drivers/net/phy/mdio-mux-bcm-iproc.c +++ /dev/null @@ -1,323 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2016 Broadcom - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MDIO_RATE_ADJ_EXT_OFFSET 0x000 -#define MDIO_RATE_ADJ_INT_OFFSET 0x004 -#define MDIO_RATE_ADJ_DIVIDENT_SHIFT 16 - -#define MDIO_SCAN_CTRL_OFFSET 0x008 -#define MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR 28 - -#define MDIO_PARAM_OFFSET 0x23c -#define MDIO_PARAM_MIIM_CYCLE 29 -#define MDIO_PARAM_INTERNAL_SEL 25 -#define MDIO_PARAM_BUS_ID 22 -#define MDIO_PARAM_C45_SEL 21 -#define MDIO_PARAM_PHY_ID 16 -#define MDIO_PARAM_PHY_DATA 0 - -#define MDIO_READ_OFFSET 0x240 -#define MDIO_READ_DATA_MASK 0xffff -#define MDIO_ADDR_OFFSET 0x244 - -#define MDIO_CTRL_OFFSET 0x248 -#define MDIO_CTRL_WRITE_OP 0x1 -#define MDIO_CTRL_READ_OP 0x2 - -#define MDIO_STAT_OFFSET 0x24c -#define MDIO_STAT_DONE 1 - -#define BUS_MAX_ADDR 32 -#define EXT_BUS_START_ADDR 16 - -#define MDIO_REG_ADDR_SPACE_SIZE 0x250 - -#define MDIO_OPERATING_FREQUENCY 11000000 -#define MDIO_RATE_ADJ_DIVIDENT 1 - -struct iproc_mdiomux_desc { - void *mux_handle; - void __iomem *base; - struct device *dev; - struct mii_bus *mii_bus; - struct clk *core_clk; -}; - -static void mdio_mux_iproc_config(struct iproc_mdiomux_desc *md) -{ - u32 divisor; - u32 val; - - /* Disable external mdio master access */ - val = readl(md->base + MDIO_SCAN_CTRL_OFFSET); - val |= BIT(MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR); - writel(val, md->base + MDIO_SCAN_CTRL_OFFSET); - - if (md->core_clk) { - /* use rate adjust regs to derrive the mdio's operating - * frequency from the specified core clock - */ - divisor = clk_get_rate(md->core_clk) / MDIO_OPERATING_FREQUENCY; - divisor = divisor / (MDIO_RATE_ADJ_DIVIDENT + 1); - val = divisor; - val |= MDIO_RATE_ADJ_DIVIDENT << MDIO_RATE_ADJ_DIVIDENT_SHIFT; - writel(val, md->base + MDIO_RATE_ADJ_EXT_OFFSET); - writel(val, md->base + MDIO_RATE_ADJ_INT_OFFSET); - } -} - -static int iproc_mdio_wait_for_idle(void __iomem *base, bool result) -{ - u32 val; - - return readl_poll_timeout(base + MDIO_STAT_OFFSET, val, - (val & MDIO_STAT_DONE) == result, - 2000, 1000000); -} - -/* start_miim_ops- Program and start MDIO transaction over mdio bus. - * @base: Base address - * @phyid: phyid of the selected bus. - * @reg: register offset to be read/written. - * @val :0 if read op else value to be written in @reg; - * @op: Operation that need to be carried out. - * MDIO_CTRL_READ_OP: Read transaction. - * MDIO_CTRL_WRITE_OP: Write transaction. - * - * Return value: Successful Read operation returns read reg values and write - * operation returns 0. Failure operation returns negative error code. - */ -static int start_miim_ops(void __iomem *base, - u16 phyid, u32 reg, u16 val, u32 op) -{ - u32 param; - int ret; - - writel(0, base + MDIO_CTRL_OFFSET); - ret = iproc_mdio_wait_for_idle(base, 0); - if (ret) - goto err; - - param = readl(base + MDIO_PARAM_OFFSET); - param |= phyid << MDIO_PARAM_PHY_ID; - param |= val << MDIO_PARAM_PHY_DATA; - if (reg & MII_ADDR_C45) - param |= BIT(MDIO_PARAM_C45_SEL); - - writel(param, base + MDIO_PARAM_OFFSET); - - writel(reg, base + MDIO_ADDR_OFFSET); - - writel(op, base + MDIO_CTRL_OFFSET); - - ret = iproc_mdio_wait_for_idle(base, 1); - if (ret) - goto err; - - if (op == MDIO_CTRL_READ_OP) - ret = readl(base + MDIO_READ_OFFSET) & MDIO_READ_DATA_MASK; -err: - return ret; -} - -static int iproc_mdiomux_read(struct mii_bus *bus, int phyid, int reg) -{ - struct iproc_mdiomux_desc *md = bus->priv; - int ret; - - ret = start_miim_ops(md->base, phyid, reg, 0, MDIO_CTRL_READ_OP); - if (ret < 0) - dev_err(&bus->dev, "mdiomux read operation failed!!!"); - - return ret; -} - -static int iproc_mdiomux_write(struct mii_bus *bus, - int phyid, int reg, u16 val) -{ - struct iproc_mdiomux_desc *md = bus->priv; - int ret; - - /* Write val at reg offset */ - ret = start_miim_ops(md->base, phyid, reg, val, MDIO_CTRL_WRITE_OP); - if (ret < 0) - dev_err(&bus->dev, "mdiomux write operation failed!!!"); - - return ret; -} - -static int mdio_mux_iproc_switch_fn(int current_child, int desired_child, - void *data) -{ - struct iproc_mdiomux_desc *md = data; - u32 param, bus_id; - bool bus_dir; - - /* select bus and its properties */ - bus_dir = (desired_child < EXT_BUS_START_ADDR); - bus_id = bus_dir ? desired_child : (desired_child - EXT_BUS_START_ADDR); - - param = (bus_dir ? 1 : 0) << MDIO_PARAM_INTERNAL_SEL; - param |= (bus_id << MDIO_PARAM_BUS_ID); - - writel(param, md->base + MDIO_PARAM_OFFSET); - return 0; -} - -static int mdio_mux_iproc_probe(struct platform_device *pdev) -{ - struct iproc_mdiomux_desc *md; - struct mii_bus *bus; - struct resource *res; - int rc; - - md = devm_kzalloc(&pdev->dev, sizeof(*md), GFP_KERNEL); - if (!md) - return -ENOMEM; - md->dev = &pdev->dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res->start & 0xfff) { - /* For backward compatibility in case the - * base address is specified with an offset. - */ - dev_info(&pdev->dev, "fix base address in dt-blob\n"); - res->start &= ~0xfff; - res->end = res->start + MDIO_REG_ADDR_SPACE_SIZE - 1; - } - md->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(md->base)) { - dev_err(&pdev->dev, "failed to ioremap register\n"); - return PTR_ERR(md->base); - } - - md->mii_bus = devm_mdiobus_alloc(&pdev->dev); - if (!md->mii_bus) { - dev_err(&pdev->dev, "mdiomux bus alloc failed\n"); - return -ENOMEM; - } - - md->core_clk = devm_clk_get(&pdev->dev, NULL); - if (md->core_clk == ERR_PTR(-ENOENT) || - md->core_clk == ERR_PTR(-EINVAL)) - md->core_clk = NULL; - else if (IS_ERR(md->core_clk)) - return PTR_ERR(md->core_clk); - - rc = clk_prepare_enable(md->core_clk); - if (rc) { - dev_err(&pdev->dev, "failed to enable core clk\n"); - return rc; - } - - bus = md->mii_bus; - bus->priv = md; - bus->name = "iProc MDIO mux bus"; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); - bus->parent = &pdev->dev; - bus->read = iproc_mdiomux_read; - bus->write = iproc_mdiomux_write; - - bus->phy_mask = ~0; - bus->dev.of_node = pdev->dev.of_node; - rc = mdiobus_register(bus); - if (rc) { - dev_err(&pdev->dev, "mdiomux registration failed\n"); - goto out_clk; - } - - platform_set_drvdata(pdev, md); - - rc = mdio_mux_init(md->dev, md->dev->of_node, mdio_mux_iproc_switch_fn, - &md->mux_handle, md, md->mii_bus); - if (rc) { - dev_info(md->dev, "mdiomux initialization failed\n"); - goto out_register; - } - - mdio_mux_iproc_config(md); - - dev_info(md->dev, "iProc mdiomux registered\n"); - return 0; - -out_register: - mdiobus_unregister(bus); -out_clk: - clk_disable_unprepare(md->core_clk); - return rc; -} - -static int mdio_mux_iproc_remove(struct platform_device *pdev) -{ - struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev); - - mdio_mux_uninit(md->mux_handle); - mdiobus_unregister(md->mii_bus); - clk_disable_unprepare(md->core_clk); - - return 0; -} - -#ifdef CONFIG_PM_SLEEP -static int mdio_mux_iproc_suspend(struct device *dev) -{ - struct iproc_mdiomux_desc *md = dev_get_drvdata(dev); - - clk_disable_unprepare(md->core_clk); - - return 0; -} - -static int mdio_mux_iproc_resume(struct device *dev) -{ - struct iproc_mdiomux_desc *md = dev_get_drvdata(dev); - int rc; - - rc = clk_prepare_enable(md->core_clk); - if (rc) { - dev_err(md->dev, "failed to enable core clk\n"); - return rc; - } - mdio_mux_iproc_config(md); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(mdio_mux_iproc_pm_ops, - mdio_mux_iproc_suspend, mdio_mux_iproc_resume); - -static const struct of_device_id mdio_mux_iproc_match[] = { - { - .compatible = "brcm,mdio-mux-iproc", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, mdio_mux_iproc_match); - -static struct platform_driver mdiomux_iproc_driver = { - .driver = { - .name = "mdio-mux-iproc", - .of_match_table = mdio_mux_iproc_match, - .pm = &mdio_mux_iproc_pm_ops, - }, - .probe = mdio_mux_iproc_probe, - .remove = mdio_mux_iproc_remove, -}; - -module_platform_driver(mdiomux_iproc_driver); - -MODULE_DESCRIPTION("iProc MDIO Mux Bus Driver"); -MODULE_AUTHOR("Pramod Kumar "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-mux-gpio.c b/drivers/net/phy/mdio-mux-gpio.c deleted file mode 100644 index 10a758fdc9e6..000000000000 --- a/drivers/net/phy/mdio-mux-gpio.c +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2011, 2012 Cavium, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define DRV_VERSION "1.1" -#define DRV_DESCRIPTION "GPIO controlled MDIO bus multiplexer driver" - -struct mdio_mux_gpio_state { - struct gpio_descs *gpios; - void *mux_handle; -}; - -static int mdio_mux_gpio_switch_fn(int current_child, int desired_child, - void *data) -{ - struct mdio_mux_gpio_state *s = data; - DECLARE_BITMAP(values, BITS_PER_TYPE(desired_child)); - - if (current_child == desired_child) - return 0; - - values[0] = desired_child; - - gpiod_set_array_value_cansleep(s->gpios->ndescs, s->gpios->desc, - s->gpios->info, values); - - return 0; -} - -static int mdio_mux_gpio_probe(struct platform_device *pdev) -{ - struct mdio_mux_gpio_state *s; - struct gpio_descs *gpios; - int r; - - gpios = devm_gpiod_get_array(&pdev->dev, NULL, GPIOD_OUT_LOW); - if (IS_ERR(gpios)) - return PTR_ERR(gpios); - - s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); - if (!s) - return -ENOMEM; - - s->gpios = gpios; - - r = mdio_mux_init(&pdev->dev, pdev->dev.of_node, - mdio_mux_gpio_switch_fn, &s->mux_handle, s, NULL); - - if (r != 0) - return r; - - pdev->dev.platform_data = s; - return 0; -} - -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 const 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", - .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 v2"); diff --git a/drivers/net/phy/mdio-mux-meson-g12a.c b/drivers/net/phy/mdio-mux-meson-g12a.c deleted file mode 100644 index bf86c9c7a288..000000000000 --- a/drivers/net/phy/mdio-mux-meson-g12a.c +++ /dev/null @@ -1,380 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* Copyright (c) 2019 Baylibre, SAS. - * Author: Jerome Brunet - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ETH_PLL_STS 0x40 -#define ETH_PLL_CTL0 0x44 -#define PLL_CTL0_LOCK_DIG BIT(30) -#define PLL_CTL0_RST BIT(29) -#define PLL_CTL0_EN BIT(28) -#define PLL_CTL0_SEL BIT(23) -#define PLL_CTL0_N GENMASK(14, 10) -#define PLL_CTL0_M GENMASK(8, 0) -#define PLL_LOCK_TIMEOUT 1000000 -#define PLL_MUX_NUM_PARENT 2 -#define ETH_PLL_CTL1 0x48 -#define ETH_PLL_CTL2 0x4c -#define ETH_PLL_CTL3 0x50 -#define ETH_PLL_CTL4 0x54 -#define ETH_PLL_CTL5 0x58 -#define ETH_PLL_CTL6 0x5c -#define ETH_PLL_CTL7 0x60 - -#define ETH_PHY_CNTL0 0x80 -#define EPHY_G12A_ID 0x33010180 -#define ETH_PHY_CNTL1 0x84 -#define PHY_CNTL1_ST_MODE GENMASK(2, 0) -#define PHY_CNTL1_ST_PHYADD GENMASK(7, 3) -#define EPHY_DFLT_ADD 8 -#define PHY_CNTL1_MII_MODE GENMASK(15, 14) -#define EPHY_MODE_RMII 0x1 -#define PHY_CNTL1_CLK_EN BIT(16) -#define PHY_CNTL1_CLKFREQ BIT(17) -#define PHY_CNTL1_PHY_ENB BIT(18) -#define ETH_PHY_CNTL2 0x88 -#define PHY_CNTL2_USE_INTERNAL BIT(5) -#define PHY_CNTL2_SMI_SRC_MAC BIT(6) -#define PHY_CNTL2_RX_CLK_EPHY BIT(9) - -#define MESON_G12A_MDIO_EXTERNAL_ID 0 -#define MESON_G12A_MDIO_INTERNAL_ID 1 - -struct g12a_mdio_mux { - bool pll_is_enabled; - void __iomem *regs; - void *mux_handle; - struct clk *pclk; - struct clk *pll; -}; - -struct g12a_ephy_pll { - void __iomem *base; - struct clk_hw hw; -}; - -#define g12a_ephy_pll_to_dev(_hw) \ - container_of(_hw, struct g12a_ephy_pll, hw) - -static unsigned long g12a_ephy_pll_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); - u32 val, m, n; - - val = readl(pll->base + ETH_PLL_CTL0); - m = FIELD_GET(PLL_CTL0_M, val); - n = FIELD_GET(PLL_CTL0_N, val); - - return parent_rate * m / n; -} - -static int g12a_ephy_pll_enable(struct clk_hw *hw) -{ - struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); - u32 val = readl(pll->base + ETH_PLL_CTL0); - - /* Apply both enable an reset */ - val |= PLL_CTL0_RST | PLL_CTL0_EN; - writel(val, pll->base + ETH_PLL_CTL0); - - /* Clear the reset to let PLL lock */ - val &= ~PLL_CTL0_RST; - writel(val, pll->base + ETH_PLL_CTL0); - - /* Poll on the digital lock instead of the usual analog lock - * This is done because bit 31 is unreliable on some SoC. Bit - * 31 may indicate that the PLL is not lock eventhough the clock - * is actually running - */ - return readl_poll_timeout(pll->base + ETH_PLL_CTL0, val, - val & PLL_CTL0_LOCK_DIG, 0, PLL_LOCK_TIMEOUT); -} - -static void g12a_ephy_pll_disable(struct clk_hw *hw) -{ - struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); - u32 val; - - val = readl(pll->base + ETH_PLL_CTL0); - val &= ~PLL_CTL0_EN; - val |= PLL_CTL0_RST; - writel(val, pll->base + ETH_PLL_CTL0); -} - -static int g12a_ephy_pll_is_enabled(struct clk_hw *hw) -{ - struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); - unsigned int val; - - val = readl(pll->base + ETH_PLL_CTL0); - - return (val & PLL_CTL0_LOCK_DIG) ? 1 : 0; -} - -static int g12a_ephy_pll_init(struct clk_hw *hw) -{ - struct g12a_ephy_pll *pll = g12a_ephy_pll_to_dev(hw); - - /* Apply PLL HW settings */ - writel(0x29c0040a, pll->base + ETH_PLL_CTL0); - writel(0x927e0000, pll->base + ETH_PLL_CTL1); - writel(0xac5f49e5, pll->base + ETH_PLL_CTL2); - writel(0x00000000, pll->base + ETH_PLL_CTL3); - writel(0x00000000, pll->base + ETH_PLL_CTL4); - writel(0x20200000, pll->base + ETH_PLL_CTL5); - writel(0x0000c002, pll->base + ETH_PLL_CTL6); - writel(0x00000023, pll->base + ETH_PLL_CTL7); - - return 0; -} - -static const struct clk_ops g12a_ephy_pll_ops = { - .recalc_rate = g12a_ephy_pll_recalc_rate, - .is_enabled = g12a_ephy_pll_is_enabled, - .enable = g12a_ephy_pll_enable, - .disable = g12a_ephy_pll_disable, - .init = g12a_ephy_pll_init, -}; - -static int g12a_enable_internal_mdio(struct g12a_mdio_mux *priv) -{ - int ret; - - /* Enable the phy clock */ - if (!priv->pll_is_enabled) { - ret = clk_prepare_enable(priv->pll); - if (ret) - return ret; - } - - priv->pll_is_enabled = true; - - /* Initialize ephy control */ - writel(EPHY_G12A_ID, priv->regs + ETH_PHY_CNTL0); - writel(FIELD_PREP(PHY_CNTL1_ST_MODE, 3) | - FIELD_PREP(PHY_CNTL1_ST_PHYADD, EPHY_DFLT_ADD) | - FIELD_PREP(PHY_CNTL1_MII_MODE, EPHY_MODE_RMII) | - PHY_CNTL1_CLK_EN | - PHY_CNTL1_CLKFREQ | - PHY_CNTL1_PHY_ENB, - priv->regs + ETH_PHY_CNTL1); - writel(PHY_CNTL2_USE_INTERNAL | - PHY_CNTL2_SMI_SRC_MAC | - PHY_CNTL2_RX_CLK_EPHY, - priv->regs + ETH_PHY_CNTL2); - - return 0; -} - -static int g12a_enable_external_mdio(struct g12a_mdio_mux *priv) -{ - /* Reset the mdio bus mux */ - writel_relaxed(0x0, priv->regs + ETH_PHY_CNTL2); - - /* Disable the phy clock if enabled */ - if (priv->pll_is_enabled) { - clk_disable_unprepare(priv->pll); - priv->pll_is_enabled = false; - } - - return 0; -} - -static int g12a_mdio_switch_fn(int current_child, int desired_child, - void *data) -{ - struct g12a_mdio_mux *priv = dev_get_drvdata(data); - - if (current_child == desired_child) - return 0; - - switch (desired_child) { - case MESON_G12A_MDIO_EXTERNAL_ID: - return g12a_enable_external_mdio(priv); - case MESON_G12A_MDIO_INTERNAL_ID: - return g12a_enable_internal_mdio(priv); - default: - return -EINVAL; - } -} - -static const struct of_device_id g12a_mdio_mux_match[] = { - { .compatible = "amlogic,g12a-mdio-mux", }, - {}, -}; -MODULE_DEVICE_TABLE(of, g12a_mdio_mux_match); - -static int g12a_ephy_glue_clk_register(struct device *dev) -{ - struct g12a_mdio_mux *priv = dev_get_drvdata(dev); - const char *parent_names[PLL_MUX_NUM_PARENT]; - struct clk_init_data init; - struct g12a_ephy_pll *pll; - struct clk_mux *mux; - struct clk *clk; - char *name; - int i; - - /* get the mux parents */ - for (i = 0; i < PLL_MUX_NUM_PARENT; i++) { - char in_name[8]; - - snprintf(in_name, sizeof(in_name), "clkin%d", i); - clk = devm_clk_get(dev, in_name); - if (IS_ERR(clk)) { - if (PTR_ERR(clk) != -EPROBE_DEFER) - dev_err(dev, "Missing clock %s\n", in_name); - return PTR_ERR(clk); - } - - parent_names[i] = __clk_get_name(clk); - } - - /* create the input mux */ - mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); - if (!mux) - return -ENOMEM; - - name = kasprintf(GFP_KERNEL, "%s#mux", dev_name(dev)); - if (!name) - return -ENOMEM; - - init.name = name; - init.ops = &clk_mux_ro_ops; - init.flags = 0; - init.parent_names = parent_names; - init.num_parents = PLL_MUX_NUM_PARENT; - - mux->reg = priv->regs + ETH_PLL_CTL0; - mux->shift = __ffs(PLL_CTL0_SEL); - mux->mask = PLL_CTL0_SEL >> mux->shift; - mux->hw.init = &init; - - clk = devm_clk_register(dev, &mux->hw); - kfree(name); - if (IS_ERR(clk)) { - dev_err(dev, "failed to register input mux\n"); - return PTR_ERR(clk); - } - - /* create the pll */ - pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); - if (!pll) - return -ENOMEM; - - name = kasprintf(GFP_KERNEL, "%s#pll", dev_name(dev)); - if (!name) - return -ENOMEM; - - init.name = name; - init.ops = &g12a_ephy_pll_ops; - init.flags = 0; - parent_names[0] = __clk_get_name(clk); - init.parent_names = parent_names; - init.num_parents = 1; - - pll->base = priv->regs; - pll->hw.init = &init; - - clk = devm_clk_register(dev, &pll->hw); - kfree(name); - if (IS_ERR(clk)) { - dev_err(dev, "failed to register input mux\n"); - return PTR_ERR(clk); - } - - priv->pll = clk; - - return 0; -} - -static int g12a_mdio_mux_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct g12a_mdio_mux *priv; - int ret; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - platform_set_drvdata(pdev, priv); - - priv->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->regs)) - return PTR_ERR(priv->regs); - - priv->pclk = devm_clk_get(dev, "pclk"); - if (IS_ERR(priv->pclk)) { - ret = PTR_ERR(priv->pclk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get peripheral clock\n"); - return ret; - } - - /* Make sure the device registers are clocked */ - ret = clk_prepare_enable(priv->pclk); - if (ret) { - dev_err(dev, "failed to enable peripheral clock"); - return ret; - } - - /* Register PLL in CCF */ - ret = g12a_ephy_glue_clk_register(dev); - if (ret) - goto err; - - ret = mdio_mux_init(dev, dev->of_node, g12a_mdio_switch_fn, - &priv->mux_handle, dev, NULL); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "mdio multiplexer init failed: %d", ret); - goto err; - } - - return 0; - -err: - clk_disable_unprepare(priv->pclk); - return ret; -} - -static int g12a_mdio_mux_remove(struct platform_device *pdev) -{ - struct g12a_mdio_mux *priv = platform_get_drvdata(pdev); - - mdio_mux_uninit(priv->mux_handle); - - if (priv->pll_is_enabled) - clk_disable_unprepare(priv->pll); - - clk_disable_unprepare(priv->pclk); - - return 0; -} - -static struct platform_driver g12a_mdio_mux_driver = { - .probe = g12a_mdio_mux_probe, - .remove = g12a_mdio_mux_remove, - .driver = { - .name = "g12a-mdio_mux", - .of_match_table = g12a_mdio_mux_match, - }, -}; -module_platform_driver(g12a_mdio_mux_driver); - -MODULE_DESCRIPTION("Amlogic G12a MDIO multiplexer driver"); -MODULE_AUTHOR("Jerome Brunet "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-mux-mmioreg.c b/drivers/net/phy/mdio-mux-mmioreg.c deleted file mode 100644 index d1a8780e24d8..000000000000 --- a/drivers/net/phy/mdio-mux-mmioreg.c +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Simple memory-mapped device MDIO MUX driver - * - * Author: Timur Tabi - * - * Copyright 2012 Freescale Semiconductor, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include - -struct mdio_mux_mmioreg_state { - void *mux_handle; - phys_addr_t phys; - unsigned int iosize; - unsigned int 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, s->iosize); - if (!p) - return -ENOMEM; - - switch (s->iosize) { - case sizeof(uint8_t): { - uint8_t x, y; - - 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); - } - - break; - } - case sizeof(uint16_t): { - uint16_t x, y; - - x = ioread16(p); - y = (x & ~s->mask) | desired_child; - if (x != y) { - iowrite16((x & ~s->mask) | desired_child, p); - pr_debug("%s: %04x -> %04x\n", __func__, x, y); - } - - break; - } - case sizeof(uint32_t): { - uint32_t x, y; - - x = ioread32(p); - y = (x & ~s->mask) | desired_child; - if (x != y) { - iowrite32((x & ~s->mask) | desired_child, p); - pr_debug("%s: %08x -> %08x\n", __func__, x, y); - } - - break; - } - } - - 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 %pOF\n", np); - - 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 %pOF\n", - np); - return ret; - } - s->phys = res.start; - - s->iosize = resource_size(&res); - if (s->iosize != sizeof(uint8_t) && - s->iosize != sizeof(uint16_t) && - s->iosize != sizeof(uint32_t)) { - dev_err(&pdev->dev, "only 8/16/32-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) >= BIT(s->iosize * 8)) { - dev_err(&pdev->dev, "only 8/16/32-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 %pOF is " - "missing a 'reg' property\n", np2); - of_node_put(np2); - return -ENODEV; - } - if (be32_to_cpup(iprop) & ~s->mask) { - dev_err(&pdev->dev, "mdio-mux child node %pOF has " - "a 'reg' value with unmasked bits\n", - np2); - of_node_put(np2); - return -ENODEV; - } - } - - ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, - mdio_mux_mmioreg_switch_fn, - &s->mux_handle, s, NULL); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, - "failed to register mdio-mux bus %pOF\n", np); - 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 const 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", - .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 "); -MODULE_DESCRIPTION("Memory-mapped device MDIO MUX driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-mux-multiplexer.c b/drivers/net/phy/mdio-mux-multiplexer.c deleted file mode 100644 index d6564381aa3e..000000000000 --- a/drivers/net/phy/mdio-mux-multiplexer.c +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* MDIO bus multiplexer using kernel multiplexer subsystem - * - * Copyright 2019 NXP - */ - -#include -#include -#include -#include - -struct mdio_mux_multiplexer_state { - struct mux_control *muxc; - bool do_deselect; - void *mux_handle; -}; - -/** - * mdio_mux_multiplexer_switch_fn - This function is called by the mdio-mux - * layer when it thinks the mdio bus - * multiplexer needs to switch. - * @current_child: current value of the mux register. - * @desired_child: value of the 'reg' property of the target child MDIO node. - * @data: Private data used by this switch_fn passed to mdio_mux_init function - * via mdio_mux_init(.., .., .., .., data, ..). - * - * 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_multiplexer_switch_fn(int current_child, int desired_child, - void *data) -{ - struct platform_device *pdev; - struct mdio_mux_multiplexer_state *s; - int ret = 0; - - pdev = (struct platform_device *)data; - s = platform_get_drvdata(pdev); - - if (!(current_child ^ desired_child)) - return 0; - - if (s->do_deselect) - ret = mux_control_deselect(s->muxc); - if (ret) { - dev_err(&pdev->dev, "mux_control_deselect failed in %s: %d\n", - __func__, ret); - return ret; - } - - ret = mux_control_select(s->muxc, desired_child); - if (!ret) { - dev_dbg(&pdev->dev, "%s %d -> %d\n", __func__, current_child, - desired_child); - s->do_deselect = true; - } else { - s->do_deselect = false; - } - - return ret; -} - -static int mdio_mux_multiplexer_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct mdio_mux_multiplexer_state *s; - int ret = 0; - - s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); - if (!s) - return -ENOMEM; - - s->muxc = devm_mux_control_get(dev, NULL); - if (IS_ERR(s->muxc)) { - ret = PTR_ERR(s->muxc); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, "Failed to get mux: %d\n", ret); - return ret; - } - - platform_set_drvdata(pdev, s); - - ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, - mdio_mux_multiplexer_switch_fn, &s->mux_handle, - pdev, NULL); - - return ret; -} - -static int mdio_mux_multiplexer_remove(struct platform_device *pdev) -{ - struct mdio_mux_multiplexer_state *s = platform_get_drvdata(pdev); - - mdio_mux_uninit(s->mux_handle); - - if (s->do_deselect) - mux_control_deselect(s->muxc); - - return 0; -} - -static const struct of_device_id mdio_mux_multiplexer_match[] = { - { .compatible = "mdio-mux-multiplexer", }, - {}, -}; -MODULE_DEVICE_TABLE(of, mdio_mux_multiplexer_match); - -static struct platform_driver mdio_mux_multiplexer_driver = { - .driver = { - .name = "mdio-mux-multiplexer", - .of_match_table = mdio_mux_multiplexer_match, - }, - .probe = mdio_mux_multiplexer_probe, - .remove = mdio_mux_multiplexer_remove, -}; - -module_platform_driver(mdio_mux_multiplexer_driver); - -MODULE_DESCRIPTION("MDIO bus multiplexer using kernel multiplexer subsystem"); -MODULE_AUTHOR("Pankaj Bansal "); -MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-mux.c b/drivers/net/phy/mdio-mux.c deleted file mode 100644 index 6a1d3540210b..000000000000 --- a/drivers/net/phy/mdio-mux.c +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2011, 2012 Cavium, Inc. - */ - -#include -#include -#include -#include -#include -#include - -#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; -}; - -/* - * 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; - - mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX); - 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, MDIO_MUTEX_MUX); - 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, - struct device_node *mux_node, - int (*switch_fn)(int cur, int desired, void *data), - void **mux_handle, - void *data, - struct mii_bus *mux_bus) -{ - 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 (!mux_node) - return -ENODEV; - - if (!mux_bus) { - parent_bus_node = of_parse_phandle(mux_node, - "mdio-parent-bus", 0); - - if (!parent_bus_node) - return -ENODEV; - - parent_bus = of_mdio_find_bus(parent_bus_node); - if (!parent_bus) { - ret_val = -EPROBE_DEFER; - goto err_parent_bus; - } - } else { - parent_bus_node = NULL; - parent_bus = mux_bus; - get_device(&parent_bus->dev); - } - - pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL); - if (!pb) { - ret_val = -ENOMEM; - goto err_pb_kz; - } - - 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(mux_node, child_bus_node) { - int v; - - r = of_property_read_u32(child_bus_node, "reg", &v); - if (r) { - dev_err(dev, - "Error: Failed to find reg for child %pOF\n", - child_bus_node); - continue; - } - - cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL); - if (!cb) { - ret_val = -ENOMEM; - continue; - } - cb->bus_number = v; - cb->parent = pb; - - cb->mii_bus = mdiobus_alloc(); - if (!cb->mii_bus) { - ret_val = -ENOMEM; - devm_kfree(dev, cb); - continue; - } - cb->mii_bus->priv = cb; - - 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) { - dev_err(dev, - "Error: Failed to register MDIO bus for child %pOF\n", - child_bus_node); - mdiobus_free(cb->mii_bus); - devm_kfree(dev, cb); - } else { - cb->next = pb->children; - pb->children = cb; - } - } - if (pb->children) { - *mux_handle = pb; - return 0; - } - - dev_err(dev, "Error: No acceptable child buses found\n"); - devm_kfree(dev, pb); -err_pb_kz: - put_device(&parent_bus->dev); -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; - } - - put_device(&pb->mii_bus->dev); -} -EXPORT_SYMBOL_GPL(mdio_mux_uninit); - -MODULE_DESCRIPTION(DRV_DESCRIPTION); -MODULE_AUTHOR("David Daney"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-mvusb.c b/drivers/net/phy/mdio-mvusb.c deleted file mode 100644 index d5eabddfdf51..000000000000 --- a/drivers/net/phy/mdio-mvusb.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include -#include -#include -#include - -#define USB_MARVELL_VID 0x1286 - -static const struct usb_device_id mvusb_mdio_table[] = { - { USB_DEVICE(USB_MARVELL_VID, 0x1fa4) }, - - {} -}; -MODULE_DEVICE_TABLE(usb, mvusb_mdio_table); - -enum { - MVUSB_CMD_PREAMBLE0, - MVUSB_CMD_PREAMBLE1, - MVUSB_CMD_ADDR, - MVUSB_CMD_VAL, -}; - -struct mvusb_mdio { - struct usb_device *udev; - struct mii_bus *mdio; - - __le16 buf[4]; -}; - -static int mvusb_mdio_read(struct mii_bus *mdio, int dev, int reg) -{ - struct mvusb_mdio *mvusb = mdio->priv; - int err, alen; - - if (dev & MII_ADDR_C45) - return -EOPNOTSUPP; - - mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0xa400 | (dev << 5) | reg); - - err = usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2), - mvusb->buf, 6, &alen, 100); - if (err) - return err; - - err = usb_bulk_msg(mvusb->udev, usb_rcvbulkpipe(mvusb->udev, 6), - &mvusb->buf[MVUSB_CMD_VAL], 2, &alen, 100); - if (err) - return err; - - return le16_to_cpu(mvusb->buf[MVUSB_CMD_VAL]); -} - -static int mvusb_mdio_write(struct mii_bus *mdio, int dev, int reg, u16 val) -{ - struct mvusb_mdio *mvusb = mdio->priv; - int alen; - - if (dev & MII_ADDR_C45) - return -EOPNOTSUPP; - - mvusb->buf[MVUSB_CMD_ADDR] = cpu_to_le16(0x8000 | (dev << 5) | reg); - mvusb->buf[MVUSB_CMD_VAL] = cpu_to_le16(val); - - return usb_bulk_msg(mvusb->udev, usb_sndbulkpipe(mvusb->udev, 2), - mvusb->buf, 8, &alen, 100); -} - -static int mvusb_mdio_probe(struct usb_interface *interface, - const struct usb_device_id *id) -{ - struct device *dev = &interface->dev; - struct mvusb_mdio *mvusb; - struct mii_bus *mdio; - - mdio = devm_mdiobus_alloc_size(dev, sizeof(*mvusb)); - if (!mdio) - return -ENOMEM; - - mvusb = mdio->priv; - mvusb->mdio = mdio; - mvusb->udev = usb_get_dev(interface_to_usbdev(interface)); - - /* Reversed from USB PCAPs, no idea what these mean. */ - mvusb->buf[MVUSB_CMD_PREAMBLE0] = cpu_to_le16(0xe800); - mvusb->buf[MVUSB_CMD_PREAMBLE1] = cpu_to_le16(0x0001); - - snprintf(mdio->id, MII_BUS_ID_SIZE, "mvusb-%s", dev_name(dev)); - mdio->name = mdio->id; - mdio->parent = dev; - mdio->read = mvusb_mdio_read; - mdio->write = mvusb_mdio_write; - - usb_set_intfdata(interface, mvusb); - return of_mdiobus_register(mdio, dev->of_node); -} - -static void mvusb_mdio_disconnect(struct usb_interface *interface) -{ - struct mvusb_mdio *mvusb = usb_get_intfdata(interface); - struct usb_device *udev = mvusb->udev; - - mdiobus_unregister(mvusb->mdio); - usb_set_intfdata(interface, NULL); - usb_put_dev(udev); -} - -static struct usb_driver mvusb_mdio_driver = { - .name = "mvusb_mdio", - .id_table = mvusb_mdio_table, - .probe = mvusb_mdio_probe, - .disconnect = mvusb_mdio_disconnect, -}; - -module_usb_driver(mvusb_mdio_driver); - -MODULE_AUTHOR("Tobias Waldekranz "); -MODULE_DESCRIPTION("Marvell USB MDIO Adapter"); -MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio-octeon.c b/drivers/net/phy/mdio-octeon.c deleted file mode 100644 index d1e1009d51af..000000000000 --- a/drivers/net/phy/mdio-octeon.c +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2009-2015 Cavium, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mdio-cavium.h" - -static int octeon_mdiobus_probe(struct platform_device *pdev) -{ - struct cavium_mdiobus *bus; - struct mii_bus *mii_bus; - struct resource *res_mem; - resource_size_t mdio_phys; - resource_size_t regsize; - union cvmx_smix_en smi_en; - int err = -ENOENT; - - mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus)); - if (!mii_bus) - return -ENOMEM; - - res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res_mem == NULL) { - dev_err(&pdev->dev, "found no memory resource\n"); - return -ENXIO; - } - - bus = mii_bus->priv; - bus->mii_bus = mii_bus; - mdio_phys = res_mem->start; - regsize = resource_size(res_mem); - - if (!devm_request_mem_region(&pdev->dev, mdio_phys, regsize, - res_mem->name)) { - dev_err(&pdev->dev, "request_mem_region failed\n"); - return -ENXIO; - } - - bus->register_base = devm_ioremap(&pdev->dev, mdio_phys, regsize); - if (!bus->register_base) { - dev_err(&pdev->dev, "dev_ioremap failed\n"); - return -ENOMEM; - } - - smi_en.u64 = 0; - smi_en.s.en = 1; - oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); - - bus->mii_bus->name = KBUILD_MODNAME; - snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%px", bus->register_base); - bus->mii_bus->parent = &pdev->dev; - - bus->mii_bus->read = cavium_mdiobus_read; - bus->mii_bus->write = cavium_mdiobus_write; - - platform_set_drvdata(pdev, bus); - - err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node); - if (err) - goto fail_register; - - dev_info(&pdev->dev, "Probed\n"); - - return 0; -fail_register: - mdiobus_free(bus->mii_bus); - smi_en.u64 = 0; - oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); - return err; -} - -static int octeon_mdiobus_remove(struct platform_device *pdev) -{ - struct cavium_mdiobus *bus; - union cvmx_smix_en smi_en; - - bus = platform_get_drvdata(pdev); - - mdiobus_unregister(bus->mii_bus); - mdiobus_free(bus->mii_bus); - smi_en.u64 = 0; - oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); - return 0; -} - -static const 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 = KBUILD_MODNAME, - .of_match_table = octeon_mdiobus_match, - }, - .probe = octeon_mdiobus_probe, - .remove = octeon_mdiobus_remove, -}; - -module_platform_driver(octeon_mdiobus_driver); - -MODULE_DESCRIPTION("Cavium OCTEON MDIO bus driver"); -MODULE_AUTHOR("David Daney"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-sun4i.c b/drivers/net/phy/mdio-sun4i.c deleted file mode 100644 index f798de3276dc..000000000000 --- a/drivers/net/phy/mdio-sun4i.c +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Allwinner EMAC MDIO interface driver - * - * Copyright 2012-2013 Stefan Roese - * Copyright 2013 Maxime Ripard - * - * Based on the Linux driver provided by Allwinner: - * Copyright (C) 1997 Sten Wang - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; - int ret; - - 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; - - data = bus->priv; - data->membase = devm_platform_ioremap_resource(pdev, 0); - 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) { - ret = -EPROBE_DEFER; - goto err_out_free_mdiobus; - } - - dev_info(&pdev->dev, "no regulator found\n"); - data->regulator = NULL; - } 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: - if (data->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); - struct sun4i_mdio_data *data = bus->priv; - - mdiobus_unregister(bus); - if (data->regulator) - regulator_disable(data->regulator); - 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 "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-thunder.c b/drivers/net/phy/mdio-thunder.c deleted file mode 100644 index 3d7eda99d34e..000000000000 --- a/drivers/net/phy/mdio-thunder.c +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2009-2016 Cavium, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mdio-cavium.h" - -struct thunder_mdiobus_nexus { - void __iomem *bar0; - struct cavium_mdiobus *buses[4]; -}; - -static int thunder_mdiobus_pci_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) -{ - struct device_node *node; - struct fwnode_handle *fwn; - struct thunder_mdiobus_nexus *nexus; - int err; - int i; - - nexus = devm_kzalloc(&pdev->dev, sizeof(*nexus), GFP_KERNEL); - if (!nexus) - return -ENOMEM; - - pci_set_drvdata(pdev, nexus); - - err = pcim_enable_device(pdev); - if (err) { - dev_err(&pdev->dev, "Failed to enable PCI device\n"); - pci_set_drvdata(pdev, NULL); - return err; - } - - err = pci_request_regions(pdev, KBUILD_MODNAME); - if (err) { - dev_err(&pdev->dev, "pci_request_regions failed\n"); - goto err_disable_device; - } - - nexus->bar0 = pcim_iomap(pdev, 0, pci_resource_len(pdev, 0)); - if (!nexus->bar0) { - err = -ENOMEM; - goto err_release_regions; - } - - i = 0; - device_for_each_child_node(&pdev->dev, fwn) { - struct resource r; - struct mii_bus *mii_bus; - struct cavium_mdiobus *bus; - union cvmx_smix_en smi_en; - - /* If it is not an OF node we cannot handle it yet, so - * exit the loop. - */ - node = to_of_node(fwn); - if (!node) - break; - - err = of_address_to_resource(node, 0, &r); - if (err) { - dev_err(&pdev->dev, - "Couldn't translate address for \"%pOFn\"\n", - node); - break; - } - - mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus)); - if (!mii_bus) - break; - bus = mii_bus->priv; - bus->mii_bus = mii_bus; - - nexus->buses[i] = bus; - i++; - - bus->register_base = nexus->bar0 + - r.start - pci_resource_start(pdev, 0); - - smi_en.u64 = 0; - smi_en.s.en = 1; - oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); - bus->mii_bus->name = KBUILD_MODNAME; - snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", r.start); - bus->mii_bus->parent = &pdev->dev; - bus->mii_bus->read = cavium_mdiobus_read; - bus->mii_bus->write = cavium_mdiobus_write; - - err = of_mdiobus_register(bus->mii_bus, node); - if (err) - dev_err(&pdev->dev, "of_mdiobus_register failed\n"); - - dev_info(&pdev->dev, "Added bus at %llx\n", r.start); - if (i >= ARRAY_SIZE(nexus->buses)) - break; - } - return 0; - -err_release_regions: - pci_release_regions(pdev); - -err_disable_device: - pci_set_drvdata(pdev, NULL); - return err; -} - -static void thunder_mdiobus_pci_remove(struct pci_dev *pdev) -{ - int i; - struct thunder_mdiobus_nexus *nexus = pci_get_drvdata(pdev); - - for (i = 0; i < ARRAY_SIZE(nexus->buses); i++) { - struct cavium_mdiobus *bus = nexus->buses[i]; - - if (!bus) - continue; - - mdiobus_unregister(bus->mii_bus); - mdiobus_free(bus->mii_bus); - oct_mdio_writeq(0, bus->register_base + SMI_EN); - } - pci_release_regions(pdev); - pci_set_drvdata(pdev, NULL); -} - -static const struct pci_device_id thunder_mdiobus_id_table[] = { - { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xa02b) }, - { 0, } /* End of table. */ -}; -MODULE_DEVICE_TABLE(pci, thunder_mdiobus_id_table); - -static struct pci_driver thunder_mdiobus_driver = { - .name = KBUILD_MODNAME, - .id_table = thunder_mdiobus_id_table, - .probe = thunder_mdiobus_pci_probe, - .remove = thunder_mdiobus_pci_remove, -}; - -module_pci_driver(thunder_mdiobus_driver); - -MODULE_DESCRIPTION("Cavium ThunderX MDIO bus driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-xgene.c b/drivers/net/phy/mdio-xgene.c deleted file mode 100644 index 461207cdf5d6..000000000000 --- a/drivers/net/phy/mdio-xgene.c +++ /dev/null @@ -1,466 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* Applied Micro X-Gene SoC MDIO Driver - * - * Copyright (c) 2016, Applied Micro Circuits Corporation - * Author: Iyappan Subramanian - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static bool xgene_mdio_status; - -u32 xgene_mdio_rd_mac(struct xgene_mdio_pdata *pdata, u32 rd_addr) -{ - void __iomem *addr, *rd, *cmd, *cmd_done; - u32 done, rd_data = BUSY_MASK; - u8 wait = 10; - - addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET; - rd = pdata->mac_csr_addr + MAC_READ_REG_OFFSET; - cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET; - cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET; - - spin_lock(&pdata->mac_lock); - iowrite32(rd_addr, addr); - iowrite32(XGENE_ENET_RD_CMD, cmd); - - while (!(done = ioread32(cmd_done)) && wait--) - udelay(1); - - if (done) - rd_data = ioread32(rd); - - iowrite32(0, cmd); - spin_unlock(&pdata->mac_lock); - - return rd_data; -} -EXPORT_SYMBOL(xgene_mdio_rd_mac); - -void xgene_mdio_wr_mac(struct xgene_mdio_pdata *pdata, u32 wr_addr, u32 data) -{ - void __iomem *addr, *wr, *cmd, *cmd_done; - u8 wait = 10; - u32 done; - - addr = pdata->mac_csr_addr + MAC_ADDR_REG_OFFSET; - wr = pdata->mac_csr_addr + MAC_WRITE_REG_OFFSET; - cmd = pdata->mac_csr_addr + MAC_COMMAND_REG_OFFSET; - cmd_done = pdata->mac_csr_addr + MAC_COMMAND_DONE_REG_OFFSET; - - spin_lock(&pdata->mac_lock); - iowrite32(wr_addr, addr); - iowrite32(data, wr); - iowrite32(XGENE_ENET_WR_CMD, cmd); - - while (!(done = ioread32(cmd_done)) && wait--) - udelay(1); - - if (!done) - pr_err("MCX mac write failed, addr: 0x%04x\n", wr_addr); - - iowrite32(0, cmd); - spin_unlock(&pdata->mac_lock); -} -EXPORT_SYMBOL(xgene_mdio_wr_mac); - -int xgene_mdio_rgmii_read(struct mii_bus *bus, int phy_id, int reg) -{ - struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv; - u32 data, done; - u8 wait = 10; - - data = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); - xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, data); - xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, READ_CYCLE_MASK); - do { - usleep_range(5, 10); - done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); - } while ((done & BUSY_MASK) && wait--); - - if (done & BUSY_MASK) { - dev_err(&bus->dev, "MII_MGMT read failed\n"); - return -EBUSY; - } - - data = xgene_mdio_rd_mac(pdata, MII_MGMT_STATUS_ADDR); - xgene_mdio_wr_mac(pdata, MII_MGMT_COMMAND_ADDR, 0); - - return data; -} -EXPORT_SYMBOL(xgene_mdio_rgmii_read); - -int xgene_mdio_rgmii_write(struct mii_bus *bus, int phy_id, int reg, u16 data) -{ - struct xgene_mdio_pdata *pdata = (struct xgene_mdio_pdata *)bus->priv; - u32 val, done; - u8 wait = 10; - - val = SET_VAL(PHY_ADDR, phy_id) | SET_VAL(REG_ADDR, reg); - xgene_mdio_wr_mac(pdata, MII_MGMT_ADDRESS_ADDR, val); - - xgene_mdio_wr_mac(pdata, MII_MGMT_CONTROL_ADDR, data); - do { - usleep_range(5, 10); - done = xgene_mdio_rd_mac(pdata, MII_MGMT_INDICATORS_ADDR); - } while ((done & BUSY_MASK) && wait--); - - if (done & BUSY_MASK) { - dev_err(&bus->dev, "MII_MGMT write failed\n"); - return -EBUSY; - } - - return 0; -} -EXPORT_SYMBOL(xgene_mdio_rgmii_write); - -static u32 xgene_menet_rd_diag_csr(struct xgene_mdio_pdata *pdata, u32 offset) -{ - return ioread32(pdata->diag_csr_addr + offset); -} - -static void xgene_menet_wr_diag_csr(struct xgene_mdio_pdata *pdata, - u32 offset, u32 val) -{ - iowrite32(val, pdata->diag_csr_addr + offset); -} - -static int xgene_enet_ecc_init(struct xgene_mdio_pdata *pdata) -{ - u32 data; - u8 wait = 10; - - xgene_menet_wr_diag_csr(pdata, MENET_CFG_MEM_RAM_SHUTDOWN_ADDR, 0x0); - do { - usleep_range(100, 110); - data = xgene_menet_rd_diag_csr(pdata, MENET_BLOCK_MEM_RDY_ADDR); - } while ((data != 0xffffffff) && wait--); - - if (data != 0xffffffff) { - dev_err(pdata->dev, "Failed to release memory from shutdown\n"); - return -ENODEV; - } - - return 0; -} - -static void xgene_gmac_reset(struct xgene_mdio_pdata *pdata) -{ - xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, SOFT_RESET); - xgene_mdio_wr_mac(pdata, MAC_CONFIG_1_ADDR, 0); -} - -static int xgene_mdio_reset(struct xgene_mdio_pdata *pdata) -{ - int ret; - - if (pdata->dev->of_node) { - clk_prepare_enable(pdata->clk); - udelay(5); - clk_disable_unprepare(pdata->clk); - udelay(5); - clk_prepare_enable(pdata->clk); - udelay(5); - } else { -#ifdef CONFIG_ACPI - acpi_evaluate_object(ACPI_HANDLE(pdata->dev), - "_RST", NULL, NULL); -#endif - } - - ret = xgene_enet_ecc_init(pdata); - if (ret) { - if (pdata->dev->of_node) - clk_disable_unprepare(pdata->clk); - return ret; - } - xgene_gmac_reset(pdata); - - return 0; -} - -static void xgene_enet_rd_mdio_csr(void __iomem *base_addr, - u32 offset, u32 *val) -{ - void __iomem *addr = base_addr + offset; - - *val = ioread32(addr); -} - -static void xgene_enet_wr_mdio_csr(void __iomem *base_addr, - u32 offset, u32 val) -{ - void __iomem *addr = base_addr + offset; - - iowrite32(val, addr); -} - -static int xgene_xfi_mdio_write(struct mii_bus *bus, int phy_id, - int reg, u16 data) -{ - void __iomem *addr = (void __iomem *)bus->priv; - int timeout = 100; - u32 status, val; - - val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg) | - SET_VAL(HSTMIIMWRDAT, data); - xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val); - - val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_WRITE); - xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val); - - do { - usleep_range(5, 10); - xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status); - } while ((status & BUSY_MASK) && timeout--); - - xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0); - - return 0; -} - -static int xgene_xfi_mdio_read(struct mii_bus *bus, int phy_id, int reg) -{ - void __iomem *addr = (void __iomem *)bus->priv; - u32 data, status, val; - int timeout = 100; - - val = SET_VAL(HSTPHYADX, phy_id) | SET_VAL(HSTREGADX, reg); - xgene_enet_wr_mdio_csr(addr, MIIM_FIELD_ADDR, val); - - val = HSTLDCMD | SET_VAL(HSTMIIMCMD, MIIM_CMD_LEGACY_READ); - xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, val); - - do { - usleep_range(5, 10); - xgene_enet_rd_mdio_csr(addr, MIIM_INDICATOR_ADDR, &status); - } while ((status & BUSY_MASK) && timeout--); - - if (status & BUSY_MASK) { - pr_err("XGENET_MII_MGMT write failed\n"); - return -EBUSY; - } - - xgene_enet_rd_mdio_csr(addr, MIIMRD_FIELD_ADDR, &data); - xgene_enet_wr_mdio_csr(addr, MIIM_COMMAND_ADDR, 0); - - return data; -} - -struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr) -{ - struct phy_device *phy_dev; - - phy_dev = get_phy_device(bus, phy_addr, false); - if (!phy_dev || IS_ERR(phy_dev)) - return NULL; - - if (phy_device_register(phy_dev)) - phy_device_free(phy_dev); - - return phy_dev; -} -EXPORT_SYMBOL(xgene_enet_phy_register); - -#ifdef CONFIG_ACPI -static acpi_status acpi_register_phy(acpi_handle handle, u32 lvl, - void *context, void **ret) -{ - struct mii_bus *mdio = context; - struct acpi_device *adev; - struct phy_device *phy_dev; - const union acpi_object *obj; - u32 phy_addr; - - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - - if (acpi_dev_get_property(adev, "phy-channel", ACPI_TYPE_INTEGER, &obj)) - return AE_OK; - phy_addr = obj->integer.value; - - phy_dev = xgene_enet_phy_register(mdio, phy_addr); - adev->driver_data = phy_dev; - - return AE_OK; -} -#endif - -static const struct of_device_id xgene_mdio_of_match[] = { - { - .compatible = "apm,xgene-mdio-rgmii", - .data = (void *)XGENE_MDIO_RGMII - }, - { - .compatible = "apm,xgene-mdio-xfi", - .data = (void *)XGENE_MDIO_XFI - }, - {}, -}; -MODULE_DEVICE_TABLE(of, xgene_mdio_of_match); - -#ifdef CONFIG_ACPI -static const struct acpi_device_id xgene_mdio_acpi_match[] = { - { "APMC0D65", XGENE_MDIO_RGMII }, - { "APMC0D66", XGENE_MDIO_XFI }, - { } -}; - -MODULE_DEVICE_TABLE(acpi, xgene_mdio_acpi_match); -#endif - - -static int xgene_mdio_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct mii_bus *mdio_bus; - const struct of_device_id *of_id; - struct xgene_mdio_pdata *pdata; - void __iomem *csr_base; - int mdio_id = 0, ret = 0; - - of_id = of_match_device(xgene_mdio_of_match, &pdev->dev); - if (of_id) { - mdio_id = (enum xgene_mdio_id)of_id->data; - } else { -#ifdef CONFIG_ACPI - const struct acpi_device_id *acpi_id; - - acpi_id = acpi_match_device(xgene_mdio_acpi_match, &pdev->dev); - if (acpi_id) - mdio_id = (enum xgene_mdio_id)acpi_id->driver_data; -#endif - } - - if (!mdio_id) - return -ENODEV; - - pdata = devm_kzalloc(dev, sizeof(struct xgene_mdio_pdata), GFP_KERNEL); - if (!pdata) - return -ENOMEM; - pdata->mdio_id = mdio_id; - pdata->dev = dev; - - csr_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(csr_base)) - return PTR_ERR(csr_base); - pdata->mac_csr_addr = csr_base; - pdata->mdio_csr_addr = csr_base + BLOCK_XG_MDIO_CSR_OFFSET; - pdata->diag_csr_addr = csr_base + BLOCK_DIAG_CSR_OFFSET; - - if (mdio_id == XGENE_MDIO_RGMII) - spin_lock_init(&pdata->mac_lock); - - if (dev->of_node) { - pdata->clk = devm_clk_get(dev, NULL); - if (IS_ERR(pdata->clk)) { - dev_err(dev, "Unable to retrieve clk\n"); - return PTR_ERR(pdata->clk); - } - } - - ret = xgene_mdio_reset(pdata); - if (ret) - return ret; - - mdio_bus = mdiobus_alloc(); - if (!mdio_bus) { - ret = -ENOMEM; - goto out_clk; - } - - mdio_bus->name = "APM X-Gene MDIO bus"; - - if (mdio_id == XGENE_MDIO_RGMII) { - mdio_bus->read = xgene_mdio_rgmii_read; - mdio_bus->write = xgene_mdio_rgmii_write; - mdio_bus->priv = (void __force *)pdata; - snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", - "xgene-mii-rgmii"); - } else { - mdio_bus->read = xgene_xfi_mdio_read; - mdio_bus->write = xgene_xfi_mdio_write; - mdio_bus->priv = (void __force *)pdata->mdio_csr_addr; - snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s", - "xgene-mii-xfi"); - } - - mdio_bus->parent = dev; - platform_set_drvdata(pdev, pdata); - - if (dev->of_node) { - ret = of_mdiobus_register(mdio_bus, dev->of_node); - } else { -#ifdef CONFIG_ACPI - /* Mask out all PHYs from auto probing. */ - mdio_bus->phy_mask = ~0; - ret = mdiobus_register(mdio_bus); - if (ret) - goto out_mdiobus; - - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(dev), 1, - acpi_register_phy, NULL, mdio_bus, NULL); -#endif - } - - if (ret) - goto out_mdiobus; - - pdata->mdio_bus = mdio_bus; - xgene_mdio_status = true; - - return 0; - -out_mdiobus: - mdiobus_free(mdio_bus); - -out_clk: - if (dev->of_node) - clk_disable_unprepare(pdata->clk); - - return ret; -} - -static int xgene_mdio_remove(struct platform_device *pdev) -{ - struct xgene_mdio_pdata *pdata = platform_get_drvdata(pdev); - struct mii_bus *mdio_bus = pdata->mdio_bus; - struct device *dev = &pdev->dev; - - mdiobus_unregister(mdio_bus); - mdiobus_free(mdio_bus); - - if (dev->of_node) - clk_disable_unprepare(pdata->clk); - - return 0; -} - -static struct platform_driver xgene_mdio_driver = { - .driver = { - .name = "xgene-mdio", - .of_match_table = of_match_ptr(xgene_mdio_of_match), - .acpi_match_table = ACPI_PTR(xgene_mdio_acpi_match), - }, - .probe = xgene_mdio_probe, - .remove = xgene_mdio_remove, -}; - -module_platform_driver(xgene_mdio_driver); - -MODULE_DESCRIPTION("APM X-Gene SoC MDIO driver"); -MODULE_AUTHOR("Iyappan Subramanian "); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 0da4c3d393e40e41e3c6b9f1cebaa498512c2abb Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Sun, 30 Aug 2020 11:34:01 +0300 Subject: net: phy: add Lynx PCS module Add a Lynx PCS module which exposes the necessary operations to drive the PCS using phylink. The majority of the code is extracted from the Felix DSA driver, which will be also modified in a later patch, and exposed as a separate module for code reusability purposes. As such, this aims at feature and bug parity with the existing Felix DSA driver, and thus USXGMII, SGMII, QSGMII and 2500Base-X (only w/o in-band AN) are supported by the Lynx PCS module since these were also supported by Felix. The module can only be enabled by the drivers in need and not user selectable. Signed-off-by: Ioana Ciornei Signed-off-by: David S. Miller --- MAINTAINERS | 7 + drivers/net/pcs/Kconfig | 6 + drivers/net/pcs/Makefile | 1 + drivers/net/pcs/pcs-lynx.c | 312 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/pcs-lynx.h | 21 +++ 5 files changed, 347 insertions(+) create mode 100644 drivers/net/pcs/pcs-lynx.c create mode 100644 include/linux/pcs-lynx.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index b0e909937499..a1c15b6714a0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10293,6 +10293,13 @@ S: Maintained W: http://linux-test-project.github.io/ T: git git://github.com/linux-test-project/ltp.git +LYNX PCS MODULE +M: Ioana Ciornei +L: netdev@vger.kernel.org +S: Supported +F: drivers/net/phy/pcs-lynx.c +F: include/linux/pcs-lynx.h + M68K ARCHITECTURE M: Geert Uytterhoeven L: linux-m68k@lists.linux-m68k.org diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig index 9d6e2be32060..074fb3f5db18 100644 --- a/drivers/net/pcs/Kconfig +++ b/drivers/net/pcs/Kconfig @@ -13,4 +13,10 @@ config PCS_XPCS This module provides helper functions for Synopsys DesignWare XPCS controllers. +config PCS_LYNX + tristate + help + This module provides helpers to phylink for managing the Lynx PCS + which is part of the Layerscape and QorIQ Ethernet SERDES. + endmenu diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile index f0480afc7157..c23146755972 100644 --- a/drivers/net/pcs/Makefile +++ b/drivers/net/pcs/Makefile @@ -2,3 +2,4 @@ # Makefile for Linux PCS drivers obj-$(CONFIG_PCS_XPCS) += pcs-xpcs.o +obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o diff --git a/drivers/net/pcs/pcs-lynx.c b/drivers/net/pcs/pcs-lynx.c new file mode 100644 index 000000000000..c43d97682083 --- /dev/null +++ b/drivers/net/pcs/pcs-lynx.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* Copyright 2020 NXP + * Lynx PCS MDIO helpers + */ + +#include +#include +#include + +#define SGMII_CLOCK_PERIOD_NS 8 /* PCS is clocked at 125 MHz */ +#define LINK_TIMER_VAL(ns) ((u32)((ns) / SGMII_CLOCK_PERIOD_NS)) + +#define SGMII_AN_LINK_TIMER_NS 1600000 /* defined by SGMII spec */ + +#define LINK_TIMER_LO 0x12 +#define LINK_TIMER_HI 0x13 +#define IF_MODE 0x14 +#define IF_MODE_SGMII_EN BIT(0) +#define IF_MODE_USE_SGMII_AN BIT(1) +#define IF_MODE_SPEED(x) (((x) << 2) & GENMASK(3, 2)) +#define IF_MODE_SPEED_MSK GENMASK(3, 2) +#define IF_MODE_HALF_DUPLEX BIT(4) + +enum sgmii_speed { + SGMII_SPEED_10 = 0, + SGMII_SPEED_100 = 1, + SGMII_SPEED_1000 = 2, + SGMII_SPEED_2500 = 2, +}; + +#define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs) + +static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs, + struct phylink_link_state *state) +{ + struct mii_bus *bus = pcs->bus; + int addr = pcs->addr; + int status, lpa; + + status = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_BMSR); + if (status < 0) + return; + + state->link = !!(status & MDIO_STAT1_LSTATUS); + state->an_complete = !!(status & MDIO_AN_STAT1_COMPLETE); + if (!state->link || !state->an_complete) + return; + + lpa = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_LPA); + if (lpa < 0) + return; + + phylink_decode_usxgmii_word(state, lpa); +} + +static void lynx_pcs_get_state_2500basex(struct mdio_device *pcs, + struct phylink_link_state *state) +{ + struct mii_bus *bus = pcs->bus; + int addr = pcs->addr; + int bmsr, lpa; + + bmsr = mdiobus_read(bus, addr, MII_BMSR); + lpa = mdiobus_read(bus, addr, MII_LPA); + if (bmsr < 0 || lpa < 0) { + state->link = false; + return; + } + + state->link = !!(bmsr & BMSR_LSTATUS); + state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE); + if (!state->link) + return; + + state->speed = SPEED_2500; + state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX; + state->duplex = DUPLEX_FULL; +} + +static void lynx_pcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + + switch (state->interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + phylink_mii_c22_pcs_get_state(lynx->mdio, state); + break; + case PHY_INTERFACE_MODE_2500BASEX: + lynx_pcs_get_state_2500basex(lynx->mdio, state); + break; + case PHY_INTERFACE_MODE_USXGMII: + lynx_pcs_get_state_usxgmii(lynx->mdio, state); + break; + default: + break; + } + + dev_dbg(&lynx->mdio->dev, + "mode=%s/%s/%s link=%u an_enabled=%u an_complete=%u\n", + phy_modes(state->interface), + phy_speed_to_str(state->speed), + phy_duplex_to_str(state->duplex), + state->link, state->an_enabled, state->an_complete); +} + +static int lynx_pcs_config_sgmii(struct mdio_device *pcs, unsigned int mode, + const unsigned long *advertising) +{ + struct mii_bus *bus = pcs->bus; + int addr = pcs->addr; + u16 if_mode; + int err; + + if_mode = IF_MODE_SGMII_EN; + if (mode == MLO_AN_INBAND) { + u32 link_timer; + + if_mode |= IF_MODE_USE_SGMII_AN; + + /* Adjust link timer for SGMII */ + link_timer = LINK_TIMER_VAL(SGMII_AN_LINK_TIMER_NS); + mdiobus_write(bus, addr, LINK_TIMER_LO, link_timer & 0xffff); + mdiobus_write(bus, addr, LINK_TIMER_HI, link_timer >> 16); + } + err = mdiobus_modify(bus, addr, IF_MODE, + IF_MODE_SGMII_EN | IF_MODE_USE_SGMII_AN, + if_mode); + if (err) + return err; + + return phylink_mii_c22_pcs_config(pcs, mode, PHY_INTERFACE_MODE_SGMII, + advertising); +} + +static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, unsigned int mode, + const unsigned long *advertising) +{ + struct mii_bus *bus = pcs->bus; + int addr = pcs->addr; + + if (!phylink_autoneg_inband(mode)) { + dev_err(&pcs->dev, "USXGMII only supports in-band AN for now\n"); + return -EOPNOTSUPP; + } + + /* Configure device ability for the USXGMII Replicator */ + return mdiobus_c45_write(bus, addr, MDIO_MMD_VEND2, MII_ADVERTISE, + MDIO_USXGMII_10G | MDIO_USXGMII_LINK | + MDIO_USXGMII_FULL_DUPLEX | + ADVERTISE_SGMII | ADVERTISE_LPACK); +} + +static int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t ifmode, + const unsigned long *advertising, + bool permit) +{ + struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + + switch (ifmode) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + return lynx_pcs_config_sgmii(lynx->mdio, mode, advertising); + case PHY_INTERFACE_MODE_2500BASEX: + if (phylink_autoneg_inband(mode)) { + dev_err(&lynx->mdio->dev, + "AN not supported on 3.125GHz SerDes lane\n"); + return -EOPNOTSUPP; + } + break; + case PHY_INTERFACE_MODE_USXGMII: + return lynx_pcs_config_usxgmii(lynx->mdio, mode, advertising); + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static void lynx_pcs_link_up_sgmii(struct mdio_device *pcs, unsigned int mode, + int speed, int duplex) +{ + struct mii_bus *bus = pcs->bus; + u16 if_mode = 0, sgmii_speed; + int addr = pcs->addr; + + /* The PCS needs to be configured manually only + * when not operating on in-band mode + */ + if (mode == MLO_AN_INBAND) + return; + + if (duplex == DUPLEX_HALF) + if_mode |= IF_MODE_HALF_DUPLEX; + + switch (speed) { + case SPEED_1000: + sgmii_speed = SGMII_SPEED_1000; + break; + case SPEED_100: + sgmii_speed = SGMII_SPEED_100; + break; + case SPEED_10: + sgmii_speed = SGMII_SPEED_10; + break; + case SPEED_UNKNOWN: + /* Silently don't do anything */ + return; + default: + dev_err(&pcs->dev, "Invalid PCS speed %d\n", speed); + return; + } + if_mode |= IF_MODE_SPEED(sgmii_speed); + + mdiobus_modify(bus, addr, IF_MODE, + IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, + if_mode); +} + +/* 2500Base-X is SerDes protocol 7 on Felix and 6 on ENETC. It is a SerDes lane + * clocked at 3.125 GHz which encodes symbols with 8b/10b and does not have + * auto-negotiation of any link parameters. Electrically it is compatible with + * a single lane of XAUI. + * The hardware reference manual wants to call this mode SGMII, but it isn't + * really, since the fundamental features of SGMII: + * - Downgrading the link speed by duplicating symbols + * - Auto-negotiation + * are not there. + * The speed is configured at 1000 in the IF_MODE because the clock frequency + * is actually given by a PLL configured in the Reset Configuration Word (RCW). + * Since there is no difference between fixed speed SGMII w/o AN and 802.3z w/o + * AN, we call this PHY interface type 2500Base-X. In case a PHY negotiates a + * lower link speed on line side, the system-side interface remains fixed at + * 2500 Mbps and we do rate adaptation through pause frames. + */ +static void lynx_pcs_link_up_2500basex(struct mdio_device *pcs, + unsigned int mode, + int speed, int duplex) +{ + struct mii_bus *bus = pcs->bus; + int addr = pcs->addr; + u16 if_mode = 0; + + if (mode == MLO_AN_INBAND) { + dev_err(&pcs->dev, "AN not supported for 2500BaseX\n"); + return; + } + + if (duplex == DUPLEX_HALF) + if_mode |= IF_MODE_HALF_DUPLEX; + if_mode |= IF_MODE_SPEED(SGMII_SPEED_2500); + + mdiobus_modify(bus, addr, IF_MODE, + IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, + if_mode); +} + +static void lynx_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + int speed, int duplex) +{ + struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + lynx_pcs_link_up_sgmii(lynx->mdio, mode, speed, duplex); + break; + case PHY_INTERFACE_MODE_2500BASEX: + lynx_pcs_link_up_2500basex(lynx->mdio, mode, speed, duplex); + break; + case PHY_INTERFACE_MODE_USXGMII: + /* At the moment, only in-band AN is supported for USXGMII + * so nothing to do in link_up + */ + break; + default: + break; + } +} + +static const struct phylink_pcs_ops lynx_pcs_phylink_ops = { + .pcs_get_state = lynx_pcs_get_state, + .pcs_config = lynx_pcs_config, + .pcs_link_up = lynx_pcs_link_up, +}; + +struct lynx_pcs *lynx_pcs_create(struct mdio_device *mdio) +{ + struct lynx_pcs *lynx_pcs; + + lynx_pcs = kzalloc(sizeof(*lynx_pcs), GFP_KERNEL); + if (!lynx_pcs) + return NULL; + + lynx_pcs->mdio = mdio; + lynx_pcs->pcs.ops = &lynx_pcs_phylink_ops; + lynx_pcs->pcs.poll = true; + + return lynx_pcs; +} +EXPORT_SYMBOL(lynx_pcs_create); + +void lynx_pcs_destroy(struct lynx_pcs *pcs) +{ + kfree(pcs); +} +EXPORT_SYMBOL(lynx_pcs_destroy); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/linux/pcs-lynx.h b/include/linux/pcs-lynx.h new file mode 100644 index 000000000000..a6440d6ebe95 --- /dev/null +++ b/include/linux/pcs-lynx.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* Copyright 2020 NXP + * Lynx PCS helpers + */ + +#ifndef __LINUX_PCS_LYNX_H +#define __LINUX_PCS_LYNX_H + +#include +#include + +struct lynx_pcs { + struct phylink_pcs pcs; + struct mdio_device *mdio; +}; + +struct lynx_pcs *lynx_pcs_create(struct mdio_device *mdio); + +void lynx_pcs_destroy(struct lynx_pcs *pcs); + +#endif /* __LINUX_PCS_LYNX_H */ -- cgit v1.2.3 From e799151814d5ecde28d1ea450bea6543c0363c02 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Sat, 5 Sep 2020 12:37:00 +0200 Subject: MAINTAINERS: repair reference in LYNX PCS MODULE Commit 0da4c3d393e4 ("net: phy: add Lynx PCS module") added the files in ./drivers/net/pcs/, but the new LYNX PCS MODULE section refers to ./drivers/net/phy/. Hence, ./scripts/get_maintainer.pl --self-test=patterns complains: warning: no file matches F: drivers/net/phy/pcs-lynx.c Repair the LYNX PCS MODULE section by referring to the right location. Signed-off-by: Lukas Bulwahn Reviewed-by: Andrew Lunn Acked-by: Ioana Ciornei Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index cd4ce7977b6c..9a545a631f0d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10314,7 +10314,7 @@ LYNX PCS MODULE M: Ioana Ciornei L: netdev@vger.kernel.org S: Supported -F: drivers/net/phy/pcs-lynx.c +F: drivers/net/pcs/pcs-lynx.c F: include/linux/pcs-lynx.h M68K ARCHITECTURE -- cgit v1.2.3 From 07d20a643084f5cc96370c2490a07a517877dc0a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 10 Sep 2020 18:12:12 +0200 Subject: dt-bindings: net: nfc: s3fwrn5: Convert to dtschema Convert the Samsung S3FWRN5 NCI NFC controller bindings to dtschema. This is conversion only so it includes properties with invalid prefixes (s3fwrn5,en-gpios) which should be addressed later. Signed-off-by: Krzysztof Kozlowski Signed-off-by: David S. Miller --- .../devicetree/bindings/net/nfc/s3fwrn5.txt | 25 --------- .../bindings/net/nfc/samsung,s3fwrn5.yaml | 61 ++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 62 insertions(+), 25 deletions(-) delete mode 100644 Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt create mode 100644 Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt b/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt deleted file mode 100644 index f02f6fb7f81c..000000000000 --- a/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt +++ /dev/null @@ -1,25 +0,0 @@ -* Samsung S3FWRN5 NCI NFC Controller - -Required properties: -- compatible: Should be "samsung,s3fwrn5-i2c". -- reg: address on the bus -- interrupts: GPIO interrupt to which the chip is connected -- s3fwrn5,en-gpios: Output GPIO pin used for enabling/disabling the chip -- s3fwrn5,fw-gpios: Output GPIO pin used to enter firmware mode and - sleep/wakeup control - -Example: - -&hsi2c_4 { - s3fwrn5@27 { - compatible = "samsung,s3fwrn5-i2c"; - - reg = <0x27>; - - interrupt-parent = <&gpa1>; - interrupts = <3 0 0>; - - s3fwrn5,en-gpios = <&gpf1 4 0>; - s3fwrn5,fw-gpios = <&gpj0 2 0>; - }; -}; diff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml new file mode 100644 index 000000000000..f43d31a2d94b --- /dev/null +++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/nfc/samsung,s3fwrn5.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S3FWRN5 NCI NFC Controller + +maintainers: + - Krzysztof Kozlowski + - Krzysztof Opasiak + +properties: + compatible: + const: samsung,s3fwrn5-i2c + + interrupts: + maxItems: 1 + + reg: + maxItems: 1 + + s3fwrn5,en-gpios: + maxItems: 1 + description: + Output GPIO pin used for enabling/disabling the chip + + s3fwrn5,fw-gpios: + maxItems: 1 + description: + Output GPIO pin used to enter firmware mode and sleep/wakeup control + +additionalProperties: false + +required: + - compatible + - interrupts + - reg + - s3fwrn5,en-gpios + - s3fwrn5,fw-gpios + +examples: + - | + #include + #include + + i2c4 { + #address-cells = <1>; + #size-cells = <0>; + + s3fwrn5@27 { + compatible = "samsung,s3fwrn5-i2c"; + reg = <0x27>; + + interrupt-parent = <&gpa1>; + interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; + + s3fwrn5,en-gpios = <&gpf1 4 GPIO_ACTIVE_HIGH>; + s3fwrn5,fw-gpios = <&gpj0 2 GPIO_ACTIVE_HIGH>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 9a545a631f0d..8a21d28cb878 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15296,6 +15296,7 @@ M: Robert Baldyga M: Krzysztof Opasiak L: linux-nfc@lists.01.org (moderated for non-subscribers) S: Supported +F: Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml F: drivers/nfc/s3fwrn5 SAMSUNG S5C73M3 CAMERA DRIVER -- cgit v1.2.3 From 46c9efa47fe004e461f7e2dd1f9fae4596254a69 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 10 Sep 2020 18:12:18 +0200 Subject: MAINTAINERS: Add Krzysztof Kozlowski to Samsung S3FWRN5 and remove Robert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Robert Bałdyga's email does not work (bounces) since 2016 so remove it. Additionally there are no review/ack/tested tags from Krzysztof Opasiak so it looks like the driver is not supported. As a maintainer of Samsung ARM/ARM64 SoC, I can take care about this driver and provide some review. However clearly driver is not in supported mode as I do not work in Samsung anymore. Signed-off-by: Krzysztof Kozlowski Signed-off-by: David S. Miller --- CREDITS | 4 ++++ MAINTAINERS | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/CREDITS b/CREDITS index 32ee70a7562e..1df63cdf71df 100644 --- a/CREDITS +++ b/CREDITS @@ -191,6 +191,10 @@ N: Krishna Balasubramanian E: balasub@cis.ohio-state.edu D: Wrote SYS V IPC (part of standard kernel since 0.99.10) +B: Robert Baldyga +E: r.baldyga@hackerion.com +D: Samsung S3FWRN5 NCI NFC Controller + N: Chris Ball E: chris@printf.net D: Former maintainer of the MMC/SD/SDIO subsystem. diff --git a/MAINTAINERS b/MAINTAINERS index 8a21d28cb878..c99577961cc4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15292,10 +15292,10 @@ F: drivers/media/platform/s3c-camif/ F: include/media/drv-intf/s3c_camif.h SAMSUNG S3FWRN5 NFC DRIVER -M: Robert Baldyga +M: Krzysztof Kozlowski M: Krzysztof Opasiak L: linux-nfc@lists.01.org (moderated for non-subscribers) -S: Supported +S: Maintained F: Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml F: drivers/nfc/s3fwrn5 -- cgit v1.2.3 From 27cf93863cbcd08e49136d548ee44e8a7525a1c3 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 18 Sep 2020 19:25:35 +0200 Subject: MAINTAINERS: Add entry for Microchip MCP25XXFD SPI-CAN network driver Add MAINTAINERS entry for Microchip MCP25XXFD SPI-CAN network driver. Signed-off-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20200910133806.25077-7-manivannan.sadhasivam@linaro.org Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c99577961cc4..fcb63f0c9635 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10671,6 +10671,14 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/hid/hid-mcp2221.c +MCP25XXFD SPI-CAN NETWORK DRIVER +M: Marc Kleine-Budde +M: Manivannan Sadhasivam +L: linux-can@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.yaml +F: drivers/net/can/spi/mcp25xxfd/ + MCP4018 AND MCP4531 MICROCHIP DIGITAL POTENTIOMETER DRIVERS M: Peter Rosin L: linux-iio@vger.kernel.org -- cgit v1.2.3 From 64fb587cfdc325e60903be85353c8a42219757b7 Mon Sep 17 00:00:00 2001 From: Thomas Kopp Date: Fri, 18 Sep 2020 19:25:36 +0200 Subject: MAINTAINERS: Add reviewer entry for microchip mcp25xxfd SPI-CAN network driver This patch adds Thomas Kopp as a reviewer for the mcp25xxfd CAN driver. Signed-off-by: Thomas Kopp Link: https://lore.kernel.org/r/20200916101334.1277-1-thomas.kopp@microchip.com Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index fcb63f0c9635..e3c1c70057e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10674,6 +10674,7 @@ F: drivers/hid/hid-mcp2221.c MCP25XXFD SPI-CAN NETWORK DRIVER M: Marc Kleine-Budde M: Manivannan Sadhasivam +R: Thomas Kopp L: linux-can@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.yaml -- cgit v1.2.3 From bc2652b7ae1e1b85b5fbd3621c98a9c743ed89d6 Mon Sep 17 00:00:00 2001 From: Dmitry Safonov Date: Mon, 21 Sep 2020 15:36:57 +0100 Subject: selftest/net/xfrm: Add test for ipsec tunnel It's an exhaustive testing for ipsec: covering all encryption/ authentication/compression algorithms. The tests are run in two network namespaces, connected by veth interfaces. To make exhaustive testing less time-consuming, the tests are run in parallel tasks, specified by parameter to the selftest. As the patches set adds support for xfrm in compatible tasks, there are tests to check structures that differ in size between 64-bit and 32-bit applications. The selftest doesn't use libnl so that it can be easily compiled as compatible application and don't require compatible .so. Here is a diagram of the selftest: --------------- | selftest | | (parent) | --------------- | | | (pipe) | ---------- / | | \ /------------- / \ -------------\ | /----- -----\ | ---------|----------|----------------|----------|--------- | --------- --------- --------- --------- | | | child | | child | NS A | child | | child | | | --------- --------- --------- --------- | -------|------------|----------------|-------------|------ veth0 veth1 veth2 vethN ---------|------------|----------------|-------------|---------- | ------------ ------------ ------------ ------------ | | | gr.child | | gr.child | NS B | gr.child | | gr.child | | | ------------ ------------ ------------ ------------ | ---------------------------------------------------------------- The parent sends the description of a test (xfrm parameters) to the child, the child and grand child setup a tunnel over veth interface and test it by sending udp packets. Cc: Shuah Khan Cc: linux-kselftest@vger.kernel.org Signed-off-by: Dmitry Safonov Signed-off-by: Steffen Klassert --- MAINTAINERS | 1 + tools/testing/selftests/net/.gitignore | 1 + tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/ipsec.c | 2195 ++++++++++++++++++++++++++++++++ 4 files changed, 2198 insertions(+) create mode 100644 tools/testing/selftests/net/ipsec.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 9a545a631f0d..9f098c7d64fb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12145,6 +12145,7 @@ F: net/ipv6/ipcomp6.c F: net/ipv6/xfrm* F: net/key/ F: net/xfrm/ +F: tools/testing/selftests/net/ipsec.c NETWORKING [IPv4/IPv6] M: "David S. Miller" diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index 742c499328b2..61ae899cfc17 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +ipsec msg_zerocopy socket psock_fanout diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 9491bbaa0831..edd4ac632dc8 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -29,6 +29,7 @@ TEST_GEN_FILES += tcp_fastopen_backup_key TEST_GEN_FILES += fin_ack_lat TEST_GEN_FILES += reuseaddr_ports_exhausted TEST_GEN_FILES += hwtstamp_config rxtimestamp timestamping txtimestamp +TEST_GEN_FILES += ipsec TEST_GEN_PROGS = reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa TEST_GEN_PROGS += reuseport_dualstack reuseaddr_conflict tls diff --git a/tools/testing/selftests/net/ipsec.c b/tools/testing/selftests/net/ipsec.c new file mode 100644 index 000000000000..17ced7d6ce25 --- /dev/null +++ b/tools/testing/selftests/net/ipsec.c @@ -0,0 +1,2195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ipsec.c - Check xfrm on veth inside a net-ns. + * Copyright (c) 2018 Dmitry Safonov + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest.h" + +#define printk(fmt, ...) \ + ksft_print_msg("%d[%u] " fmt "\n", getpid(), __LINE__, ##__VA_ARGS__) + +#define pr_err(fmt, ...) printk(fmt ": %m", ##__VA_ARGS__) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) + +#define IPV4_STR_SZ 16 /* xxx.xxx.xxx.xxx is longest + \0 */ +#define MAX_PAYLOAD 2048 +#define XFRM_ALGO_KEY_BUF_SIZE 512 +#define MAX_PROCESSES (1 << 14) /* /16 mask divided by /30 subnets */ +#define INADDR_A ((in_addr_t) 0x0a000000) /* 10.0.0.0 */ +#define INADDR_B ((in_addr_t) 0xc0a80000) /* 192.168.0.0 */ + +/* /30 mask for one veth connection */ +#define PREFIX_LEN 30 +#define child_ip(nr) (4*nr + 1) +#define grchild_ip(nr) (4*nr + 2) + +#define VETH_FMT "ktst-%d" +#define VETH_LEN 12 + +static int nsfd_parent = -1; +static int nsfd_childa = -1; +static int nsfd_childb = -1; +static long page_size; + +/* + * ksft_cnt is static in kselftest, so isn't shared with children. + * We have to send a test result back to parent and count there. + * results_fd is a pipe with test feedback from children. + */ +static int results_fd[2]; + +const unsigned int ping_delay_nsec = 50 * 1000 * 1000; +const unsigned int ping_timeout = 300; +const unsigned int ping_count = 100; +const unsigned int ping_success = 80; + +static void randomize_buffer(void *buf, size_t buflen) +{ + int *p = (int *)buf; + size_t words = buflen / sizeof(int); + size_t leftover = buflen % sizeof(int); + + if (!buflen) + return; + + while (words--) + *p++ = rand(); + + if (leftover) { + int tmp = rand(); + + memcpy(buf + buflen - leftover, &tmp, leftover); + } + + return; +} + +static int unshare_open(void) +{ + const char *netns_path = "/proc/self/ns/net"; + int fd; + + if (unshare(CLONE_NEWNET) != 0) { + pr_err("unshare()"); + return -1; + } + + fd = open(netns_path, O_RDONLY); + if (fd <= 0) { + pr_err("open(%s)", netns_path); + return -1; + } + + return fd; +} + +static int switch_ns(int fd) +{ + if (setns(fd, CLONE_NEWNET)) { + pr_err("setns()"); + return -1; + } + return 0; +} + +/* + * Running the test inside a new parent net namespace to bother less + * about cleanup on error-path. + */ +static int init_namespaces(void) +{ + nsfd_parent = unshare_open(); + if (nsfd_parent <= 0) + return -1; + + nsfd_childa = unshare_open(); + if (nsfd_childa <= 0) + return -1; + + if (switch_ns(nsfd_parent)) + return -1; + + nsfd_childb = unshare_open(); + if (nsfd_childb <= 0) + return -1; + + if (switch_ns(nsfd_parent)) + return -1; + return 0; +} + +static int netlink_sock(int *sock, uint32_t *seq_nr, int proto) +{ + if (*sock > 0) { + seq_nr++; + return 0; + } + + *sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, proto); + if (*sock <= 0) { + pr_err("socket(AF_NETLINK)"); + return -1; + } + + randomize_buffer(seq_nr, sizeof(*seq_nr)); + + return 0; +} + +static inline struct rtattr *rtattr_hdr(struct nlmsghdr *nh) +{ + return (struct rtattr *)((char *)(nh) + RTA_ALIGN((nh)->nlmsg_len)); +} + +static int rtattr_pack(struct nlmsghdr *nh, size_t req_sz, + unsigned short rta_type, const void *payload, size_t size) +{ + /* NLMSG_ALIGNTO == RTA_ALIGNTO, nlmsg_len already aligned */ + struct rtattr *attr = rtattr_hdr(nh); + size_t nl_size = RTA_ALIGN(nh->nlmsg_len) + RTA_LENGTH(size); + + if (req_sz < nl_size) { + printk("req buf is too small: %zu < %zu", req_sz, nl_size); + return -1; + } + nh->nlmsg_len = nl_size; + + attr->rta_len = RTA_LENGTH(size); + attr->rta_type = rta_type; + memcpy(RTA_DATA(attr), payload, size); + + return 0; +} + +static struct rtattr *_rtattr_begin(struct nlmsghdr *nh, size_t req_sz, + unsigned short rta_type, const void *payload, size_t size) +{ + struct rtattr *ret = rtattr_hdr(nh); + + if (rtattr_pack(nh, req_sz, rta_type, payload, size)) + return 0; + + return ret; +} + +static inline struct rtattr *rtattr_begin(struct nlmsghdr *nh, size_t req_sz, + unsigned short rta_type) +{ + return _rtattr_begin(nh, req_sz, rta_type, 0, 0); +} + +static inline void rtattr_end(struct nlmsghdr *nh, struct rtattr *attr) +{ + char *nlmsg_end = (char *)nh + nh->nlmsg_len; + + attr->rta_len = nlmsg_end - (char *)attr; +} + +static int veth_pack_peerb(struct nlmsghdr *nh, size_t req_sz, + const char *peer, int ns) +{ + struct ifinfomsg pi; + struct rtattr *peer_attr; + + memset(&pi, 0, sizeof(pi)); + pi.ifi_family = AF_UNSPEC; + pi.ifi_change = 0xFFFFFFFF; + + peer_attr = _rtattr_begin(nh, req_sz, VETH_INFO_PEER, &pi, sizeof(pi)); + if (!peer_attr) + return -1; + + if (rtattr_pack(nh, req_sz, IFLA_IFNAME, peer, strlen(peer))) + return -1; + + if (rtattr_pack(nh, req_sz, IFLA_NET_NS_FD, &ns, sizeof(ns))) + return -1; + + rtattr_end(nh, peer_attr); + + return 0; +} + +static int netlink_check_answer(int sock) +{ + struct nlmsgerror { + struct nlmsghdr hdr; + int error; + struct nlmsghdr orig_msg; + } answer; + + if (recv(sock, &answer, sizeof(answer), 0) < 0) { + pr_err("recv()"); + return -1; + } else if (answer.hdr.nlmsg_type != NLMSG_ERROR) { + printk("expected NLMSG_ERROR, got %d", (int)answer.hdr.nlmsg_type); + return -1; + } else if (answer.error) { + printk("NLMSG_ERROR: %d: %s", + answer.error, strerror(-answer.error)); + return answer.error; + } + + return 0; +} + +static int veth_add(int sock, uint32_t seq, const char *peera, int ns_a, + const char *peerb, int ns_b) +{ + uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; + struct { + struct nlmsghdr nh; + struct ifinfomsg info; + char attrbuf[MAX_PAYLOAD]; + } req; + const char veth_type[] = "veth"; + struct rtattr *link_info, *info_data; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.info)); + req.nh.nlmsg_type = RTM_NEWLINK; + req.nh.nlmsg_flags = flags; + req.nh.nlmsg_seq = seq; + req.info.ifi_family = AF_UNSPEC; + req.info.ifi_change = 0xFFFFFFFF; + + if (rtattr_pack(&req.nh, sizeof(req), IFLA_IFNAME, peera, strlen(peera))) + return -1; + + if (rtattr_pack(&req.nh, sizeof(req), IFLA_NET_NS_FD, &ns_a, sizeof(ns_a))) + return -1; + + link_info = rtattr_begin(&req.nh, sizeof(req), IFLA_LINKINFO); + if (!link_info) + return -1; + + if (rtattr_pack(&req.nh, sizeof(req), IFLA_INFO_KIND, veth_type, sizeof(veth_type))) + return -1; + + info_data = rtattr_begin(&req.nh, sizeof(req), IFLA_INFO_DATA); + if (!info_data) + return -1; + + if (veth_pack_peerb(&req.nh, sizeof(req), peerb, ns_b)) + return -1; + + rtattr_end(&req.nh, info_data); + rtattr_end(&req.nh, link_info); + + if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + return netlink_check_answer(sock); +} + +static int ip4_addr_set(int sock, uint32_t seq, const char *intf, + struct in_addr addr, uint8_t prefix) +{ + uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; + struct { + struct nlmsghdr nh; + struct ifaddrmsg info; + char attrbuf[MAX_PAYLOAD]; + } req; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.info)); + req.nh.nlmsg_type = RTM_NEWADDR; + req.nh.nlmsg_flags = flags; + req.nh.nlmsg_seq = seq; + req.info.ifa_family = AF_INET; + req.info.ifa_prefixlen = prefix; + req.info.ifa_index = if_nametoindex(intf); + +#ifdef DEBUG + { + char addr_str[IPV4_STR_SZ] = {}; + + strncpy(addr_str, inet_ntoa(addr), IPV4_STR_SZ - 1); + + printk("ip addr set %s", addr_str); + } +#endif + + if (rtattr_pack(&req.nh, sizeof(req), IFA_LOCAL, &addr, sizeof(addr))) + return -1; + + if (rtattr_pack(&req.nh, sizeof(req), IFA_ADDRESS, &addr, sizeof(addr))) + return -1; + + if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + return netlink_check_answer(sock); +} + +static int link_set_up(int sock, uint32_t seq, const char *intf) +{ + struct { + struct nlmsghdr nh; + struct ifinfomsg info; + char attrbuf[MAX_PAYLOAD]; + } req; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.info)); + req.nh.nlmsg_type = RTM_NEWLINK; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = seq; + req.info.ifi_family = AF_UNSPEC; + req.info.ifi_change = 0xFFFFFFFF; + req.info.ifi_index = if_nametoindex(intf); + req.info.ifi_flags = IFF_UP; + req.info.ifi_change = IFF_UP; + + if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + return netlink_check_answer(sock); +} + +static int ip4_route_set(int sock, uint32_t seq, const char *intf, + struct in_addr src, struct in_addr dst) +{ + struct { + struct nlmsghdr nh; + struct rtmsg rt; + char attrbuf[MAX_PAYLOAD]; + } req; + unsigned int index = if_nametoindex(intf); + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)); + req.nh.nlmsg_type = RTM_NEWROUTE; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + req.nh.nlmsg_seq = seq; + req.rt.rtm_family = AF_INET; + req.rt.rtm_dst_len = 32; + req.rt.rtm_table = RT_TABLE_MAIN; + req.rt.rtm_protocol = RTPROT_BOOT; + req.rt.rtm_scope = RT_SCOPE_LINK; + req.rt.rtm_type = RTN_UNICAST; + + if (rtattr_pack(&req.nh, sizeof(req), RTA_DST, &dst, sizeof(dst))) + return -1; + + if (rtattr_pack(&req.nh, sizeof(req), RTA_PREFSRC, &src, sizeof(src))) + return -1; + + if (rtattr_pack(&req.nh, sizeof(req), RTA_OIF, &index, sizeof(index))) + return -1; + + if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + + return netlink_check_answer(sock); +} + +static int tunnel_set_route(int route_sock, uint32_t *route_seq, char *veth, + struct in_addr tunsrc, struct in_addr tundst) +{ + if (ip4_addr_set(route_sock, (*route_seq)++, "lo", + tunsrc, PREFIX_LEN)) { + printk("Failed to set ipv4 addr"); + return -1; + } + + if (ip4_route_set(route_sock, (*route_seq)++, veth, tunsrc, tundst)) { + printk("Failed to set ipv4 route"); + return -1; + } + + return 0; +} + +static int init_child(int nsfd, char *veth, unsigned int src, unsigned int dst) +{ + struct in_addr intsrc = inet_makeaddr(INADDR_B, src); + struct in_addr tunsrc = inet_makeaddr(INADDR_A, src); + struct in_addr tundst = inet_makeaddr(INADDR_A, dst); + int route_sock = -1, ret = -1; + uint32_t route_seq; + + if (switch_ns(nsfd)) + return -1; + + if (netlink_sock(&route_sock, &route_seq, NETLINK_ROUTE)) { + printk("Failed to open netlink route socket in child"); + return -1; + } + + if (ip4_addr_set(route_sock, route_seq++, veth, intsrc, PREFIX_LEN)) { + printk("Failed to set ipv4 addr"); + goto err; + } + + if (link_set_up(route_sock, route_seq++, veth)) { + printk("Failed to bring up %s", veth); + goto err; + } + + if (tunnel_set_route(route_sock, &route_seq, veth, tunsrc, tundst)) { + printk("Failed to add tunnel route on %s", veth); + goto err; + } + ret = 0; + +err: + close(route_sock); + return ret; +} + +#define ALGO_LEN 64 +enum desc_type { + CREATE_TUNNEL = 0, + ALLOCATE_SPI, + MONITOR_ACQUIRE, + EXPIRE_STATE, + EXPIRE_POLICY, +}; +const char *desc_name[] = { + "create tunnel", + "alloc spi", + "monitor acquire", + "expire state", + "expire policy" +}; +struct xfrm_desc { + enum desc_type type; + uint8_t proto; + char a_algo[ALGO_LEN]; + char e_algo[ALGO_LEN]; + char c_algo[ALGO_LEN]; + char ae_algo[ALGO_LEN]; + unsigned int icv_len; + /* unsigned key_len; */ +}; + +enum msg_type { + MSG_ACK = 0, + MSG_EXIT, + MSG_PING, + MSG_XFRM_PREPARE, + MSG_XFRM_ADD, + MSG_XFRM_DEL, + MSG_XFRM_CLEANUP, +}; + +struct test_desc { + enum msg_type type; + union { + struct { + in_addr_t reply_ip; + unsigned int port; + } ping; + struct xfrm_desc xfrm_desc; + } body; +}; + +struct test_result { + struct xfrm_desc desc; + unsigned int res; +}; + +static void write_test_result(unsigned int res, struct xfrm_desc *d) +{ + struct test_result tr = {}; + ssize_t ret; + + tr.desc = *d; + tr.res = res; + + ret = write(results_fd[1], &tr, sizeof(tr)); + if (ret != sizeof(tr)) + pr_err("Failed to write the result in pipe %zd", ret); +} + +static void write_msg(int fd, struct test_desc *msg, bool exit_of_fail) +{ + ssize_t bytes = write(fd, msg, sizeof(*msg)); + + /* Make sure that write/read is atomic to a pipe */ + BUILD_BUG_ON(sizeof(struct test_desc) > PIPE_BUF); + + if (bytes < 0) { + pr_err("write()"); + if (exit_of_fail) + exit(KSFT_FAIL); + } + if (bytes != sizeof(*msg)) { + pr_err("sent part of the message %zd/%zu", bytes, sizeof(*msg)); + if (exit_of_fail) + exit(KSFT_FAIL); + } +} + +static void read_msg(int fd, struct test_desc *msg, bool exit_of_fail) +{ + ssize_t bytes = read(fd, msg, sizeof(*msg)); + + if (bytes < 0) { + pr_err("read()"); + if (exit_of_fail) + exit(KSFT_FAIL); + } + if (bytes != sizeof(*msg)) { + pr_err("got incomplete message %zd/%zu", bytes, sizeof(*msg)); + if (exit_of_fail) + exit(KSFT_FAIL); + } +} + +static int udp_ping_init(struct in_addr listen_ip, unsigned int u_timeout, + unsigned int *server_port, int sock[2]) +{ + struct sockaddr_in server; + struct timeval t = { .tv_sec = 0, .tv_usec = u_timeout }; + socklen_t s_len = sizeof(server); + + sock[0] = socket(AF_INET, SOCK_DGRAM, 0); + if (sock[0] < 0) { + pr_err("socket()"); + return -1; + } + + server.sin_family = AF_INET; + server.sin_port = 0; + memcpy(&server.sin_addr.s_addr, &listen_ip, sizeof(struct in_addr)); + + if (bind(sock[0], (struct sockaddr *)&server, s_len)) { + pr_err("bind()"); + goto err_close_server; + } + + if (getsockname(sock[0], (struct sockaddr *)&server, &s_len)) { + pr_err("getsockname()"); + goto err_close_server; + } + + *server_port = ntohs(server.sin_port); + + if (setsockopt(sock[0], SOL_SOCKET, SO_RCVTIMEO, (const char *)&t, sizeof t)) { + pr_err("setsockopt()"); + goto err_close_server; + } + + sock[1] = socket(AF_INET, SOCK_DGRAM, 0); + if (sock[1] < 0) { + pr_err("socket()"); + goto err_close_server; + } + + return 0; + +err_close_server: + close(sock[0]); + return -1; +} + +static int udp_ping_send(int sock[2], in_addr_t dest_ip, unsigned int port, + char *buf, size_t buf_len) +{ + struct sockaddr_in server; + const struct sockaddr *dest_addr = (struct sockaddr *)&server; + char *sock_buf[buf_len]; + ssize_t r_bytes, s_bytes; + + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = dest_ip; + + s_bytes = sendto(sock[1], buf, buf_len, 0, dest_addr, sizeof(server)); + if (s_bytes < 0) { + pr_err("sendto()"); + return -1; + } else if (s_bytes != buf_len) { + printk("send part of the message: %zd/%zu", s_bytes, sizeof(server)); + return -1; + } + + r_bytes = recv(sock[0], sock_buf, buf_len, 0); + if (r_bytes < 0) { + if (errno != EAGAIN) + pr_err("recv()"); + return -1; + } else if (r_bytes == 0) { /* EOF */ + printk("EOF on reply to ping"); + return -1; + } else if (r_bytes != buf_len || memcmp(buf, sock_buf, buf_len)) { + printk("ping reply packet is corrupted %zd/%zu", r_bytes, buf_len); + return -1; + } + + return 0; +} + +static int udp_ping_reply(int sock[2], in_addr_t dest_ip, unsigned int port, + char *buf, size_t buf_len) +{ + struct sockaddr_in server; + const struct sockaddr *dest_addr = (struct sockaddr *)&server; + char *sock_buf[buf_len]; + ssize_t r_bytes, s_bytes; + + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = dest_ip; + + r_bytes = recv(sock[0], sock_buf, buf_len, 0); + if (r_bytes < 0) { + if (errno != EAGAIN) + pr_err("recv()"); + return -1; + } + if (r_bytes == 0) { /* EOF */ + printk("EOF on reply to ping"); + return -1; + } + if (r_bytes != buf_len || memcmp(buf, sock_buf, buf_len)) { + printk("ping reply packet is corrupted %zd/%zu", r_bytes, buf_len); + return -1; + } + + s_bytes = sendto(sock[1], buf, buf_len, 0, dest_addr, sizeof(server)); + if (s_bytes < 0) { + pr_err("sendto()"); + return -1; + } else if (s_bytes != buf_len) { + printk("send part of the message: %zd/%zu", s_bytes, sizeof(server)); + return -1; + } + + return 0; +} + +typedef int (*ping_f)(int sock[2], in_addr_t dest_ip, unsigned int port, + char *buf, size_t buf_len); +static int do_ping(int cmd_fd, char *buf, size_t buf_len, struct in_addr from, + bool init_side, int d_port, in_addr_t to, ping_f func) +{ + struct test_desc msg; + unsigned int s_port, i, ping_succeeded = 0; + int ping_sock[2]; + char to_str[IPV4_STR_SZ] = {}, from_str[IPV4_STR_SZ] = {}; + + if (udp_ping_init(from, ping_timeout, &s_port, ping_sock)) { + printk("Failed to init ping"); + return -1; + } + + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_PING; + msg.body.ping.port = s_port; + memcpy(&msg.body.ping.reply_ip, &from, sizeof(from)); + + write_msg(cmd_fd, &msg, 0); + if (init_side) { + /* The other end sends ip to ping */ + read_msg(cmd_fd, &msg, 0); + if (msg.type != MSG_PING) + return -1; + to = msg.body.ping.reply_ip; + d_port = msg.body.ping.port; + } + + for (i = 0; i < ping_count ; i++) { + struct timespec sleep_time = { + .tv_sec = 0, + .tv_nsec = ping_delay_nsec, + }; + + ping_succeeded += !func(ping_sock, to, d_port, buf, page_size); + nanosleep(&sleep_time, 0); + } + + close(ping_sock[0]); + close(ping_sock[1]); + + strncpy(to_str, inet_ntoa(*(struct in_addr *)&to), IPV4_STR_SZ - 1); + strncpy(from_str, inet_ntoa(from), IPV4_STR_SZ - 1); + + if (ping_succeeded < ping_success) { + printk("ping (%s) %s->%s failed %u/%u times", + init_side ? "send" : "reply", from_str, to_str, + ping_count - ping_succeeded, ping_count); + return -1; + } + +#ifdef DEBUG + printk("ping (%s) %s->%s succeeded %u/%u times", + init_side ? "send" : "reply", from_str, to_str, + ping_succeeded, ping_count); +#endif + + return 0; +} + +static int xfrm_fill_key(char *name, char *buf, + size_t buf_len, unsigned int *key_len) +{ + /* TODO: use set/map instead */ + if (strncmp(name, "digest_null", ALGO_LEN) == 0) + *key_len = 0; + else if (strncmp(name, "ecb(cipher_null)", ALGO_LEN) == 0) + *key_len = 0; + else if (strncmp(name, "cbc(des)", ALGO_LEN) == 0) + *key_len = 64; + else if (strncmp(name, "hmac(md5)", ALGO_LEN) == 0) + *key_len = 128; + else if (strncmp(name, "cmac(aes)", ALGO_LEN) == 0) + *key_len = 128; + else if (strncmp(name, "xcbc(aes)", ALGO_LEN) == 0) + *key_len = 128; + else if (strncmp(name, "cbc(cast5)", ALGO_LEN) == 0) + *key_len = 128; + else if (strncmp(name, "cbc(serpent)", ALGO_LEN) == 0) + *key_len = 128; + else if (strncmp(name, "hmac(sha1)", ALGO_LEN) == 0) + *key_len = 160; + else if (strncmp(name, "hmac(rmd160)", ALGO_LEN) == 0) + *key_len = 160; + else if (strncmp(name, "cbc(des3_ede)", ALGO_LEN) == 0) + *key_len = 192; + else if (strncmp(name, "hmac(sha256)", ALGO_LEN) == 0) + *key_len = 256; + else if (strncmp(name, "cbc(aes)", ALGO_LEN) == 0) + *key_len = 256; + else if (strncmp(name, "cbc(camellia)", ALGO_LEN) == 0) + *key_len = 256; + else if (strncmp(name, "cbc(twofish)", ALGO_LEN) == 0) + *key_len = 256; + else if (strncmp(name, "rfc3686(ctr(aes))", ALGO_LEN) == 0) + *key_len = 288; + else if (strncmp(name, "hmac(sha384)", ALGO_LEN) == 0) + *key_len = 384; + else if (strncmp(name, "cbc(blowfish)", ALGO_LEN) == 0) + *key_len = 448; + else if (strncmp(name, "hmac(sha512)", ALGO_LEN) == 0) + *key_len = 512; + else if (strncmp(name, "rfc4106(gcm(aes))-128", ALGO_LEN) == 0) + *key_len = 160; + else if (strncmp(name, "rfc4543(gcm(aes))-128", ALGO_LEN) == 0) + *key_len = 160; + else if (strncmp(name, "rfc4309(ccm(aes))-128", ALGO_LEN) == 0) + *key_len = 152; + else if (strncmp(name, "rfc4106(gcm(aes))-192", ALGO_LEN) == 0) + *key_len = 224; + else if (strncmp(name, "rfc4543(gcm(aes))-192", ALGO_LEN) == 0) + *key_len = 224; + else if (strncmp(name, "rfc4309(ccm(aes))-192", ALGO_LEN) == 0) + *key_len = 216; + else if (strncmp(name, "rfc4106(gcm(aes))-256", ALGO_LEN) == 0) + *key_len = 288; + else if (strncmp(name, "rfc4543(gcm(aes))-256", ALGO_LEN) == 0) + *key_len = 288; + else if (strncmp(name, "rfc4309(ccm(aes))-256", ALGO_LEN) == 0) + *key_len = 280; + else if (strncmp(name, "rfc7539(chacha20,poly1305)-128", ALGO_LEN) == 0) + *key_len = 0; + + if (*key_len > buf_len) { + printk("Can't pack a key - too big for buffer"); + return -1; + } + + randomize_buffer(buf, *key_len); + + return 0; +} + +static int xfrm_state_pack_algo(struct nlmsghdr *nh, size_t req_sz, + struct xfrm_desc *desc) +{ + struct { + union { + struct xfrm_algo alg; + struct xfrm_algo_aead aead; + struct xfrm_algo_auth auth; + } u; + char buf[XFRM_ALGO_KEY_BUF_SIZE]; + } alg = {}; + size_t alen, elen, clen, aelen; + unsigned short type; + + alen = strlen(desc->a_algo); + elen = strlen(desc->e_algo); + clen = strlen(desc->c_algo); + aelen = strlen(desc->ae_algo); + + /* Verify desc */ + switch (desc->proto) { + case IPPROTO_AH: + if (!alen || elen || clen || aelen) { + printk("BUG: buggy ah desc"); + return -1; + } + strncpy(alg.u.alg.alg_name, desc->a_algo, ALGO_LEN - 1); + if (xfrm_fill_key(desc->a_algo, alg.u.alg.alg_key, + sizeof(alg.buf), &alg.u.alg.alg_key_len)) + return -1; + type = XFRMA_ALG_AUTH; + break; + case IPPROTO_COMP: + if (!clen || elen || alen || aelen) { + printk("BUG: buggy comp desc"); + return -1; + } + strncpy(alg.u.alg.alg_name, desc->c_algo, ALGO_LEN - 1); + if (xfrm_fill_key(desc->c_algo, alg.u.alg.alg_key, + sizeof(alg.buf), &alg.u.alg.alg_key_len)) + return -1; + type = XFRMA_ALG_COMP; + break; + case IPPROTO_ESP: + if (!((alen && elen) ^ aelen) || clen) { + printk("BUG: buggy esp desc"); + return -1; + } + if (aelen) { + alg.u.aead.alg_icv_len = desc->icv_len; + strncpy(alg.u.aead.alg_name, desc->ae_algo, ALGO_LEN - 1); + if (xfrm_fill_key(desc->ae_algo, alg.u.aead.alg_key, + sizeof(alg.buf), &alg.u.aead.alg_key_len)) + return -1; + type = XFRMA_ALG_AEAD; + } else { + + strncpy(alg.u.alg.alg_name, desc->e_algo, ALGO_LEN - 1); + type = XFRMA_ALG_CRYPT; + if (xfrm_fill_key(desc->e_algo, alg.u.alg.alg_key, + sizeof(alg.buf), &alg.u.alg.alg_key_len)) + return -1; + if (rtattr_pack(nh, req_sz, type, &alg, sizeof(alg))) + return -1; + + strncpy(alg.u.alg.alg_name, desc->a_algo, ALGO_LEN); + type = XFRMA_ALG_AUTH; + if (xfrm_fill_key(desc->a_algo, alg.u.alg.alg_key, + sizeof(alg.buf), &alg.u.alg.alg_key_len)) + return -1; + } + break; + default: + printk("BUG: unknown proto in desc"); + return -1; + } + + if (rtattr_pack(nh, req_sz, type, &alg, sizeof(alg))) + return -1; + + return 0; +} + +static inline uint32_t gen_spi(struct in_addr src) +{ + return htonl(inet_lnaof(src)); +} + +static int xfrm_state_add(int xfrm_sock, uint32_t seq, uint32_t spi, + struct in_addr src, struct in_addr dst, + struct xfrm_desc *desc) +{ + struct { + struct nlmsghdr nh; + struct xfrm_usersa_info info; + char attrbuf[MAX_PAYLOAD]; + } req; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.info)); + req.nh.nlmsg_type = XFRM_MSG_NEWSA; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = seq; + + /* Fill selector. */ + memcpy(&req.info.sel.daddr, &dst, sizeof(dst)); + memcpy(&req.info.sel.saddr, &src, sizeof(src)); + req.info.sel.family = AF_INET; + req.info.sel.prefixlen_d = PREFIX_LEN; + req.info.sel.prefixlen_s = PREFIX_LEN; + + /* Fill id */ + memcpy(&req.info.id.daddr, &dst, sizeof(dst)); + /* Note: zero-spi cannot be deleted */ + req.info.id.spi = spi; + req.info.id.proto = desc->proto; + + memcpy(&req.info.saddr, &src, sizeof(src)); + + /* Fill lifteme_cfg */ + req.info.lft.soft_byte_limit = XFRM_INF; + req.info.lft.hard_byte_limit = XFRM_INF; + req.info.lft.soft_packet_limit = XFRM_INF; + req.info.lft.hard_packet_limit = XFRM_INF; + + req.info.family = AF_INET; + req.info.mode = XFRM_MODE_TUNNEL; + + if (xfrm_state_pack_algo(&req.nh, sizeof(req), desc)) + return -1; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + + return netlink_check_answer(xfrm_sock); +} + +static bool xfrm_usersa_found(struct xfrm_usersa_info *info, uint32_t spi, + struct in_addr src, struct in_addr dst, + struct xfrm_desc *desc) +{ + if (memcmp(&info->sel.daddr, &dst, sizeof(dst))) + return false; + + if (memcmp(&info->sel.saddr, &src, sizeof(src))) + return false; + + if (info->sel.family != AF_INET || + info->sel.prefixlen_d != PREFIX_LEN || + info->sel.prefixlen_s != PREFIX_LEN) + return false; + + if (info->id.spi != spi || info->id.proto != desc->proto) + return false; + + if (memcmp(&info->id.daddr, &dst, sizeof(dst))) + return false; + + if (memcmp(&info->saddr, &src, sizeof(src))) + return false; + + if (info->lft.soft_byte_limit != XFRM_INF || + info->lft.hard_byte_limit != XFRM_INF || + info->lft.soft_packet_limit != XFRM_INF || + info->lft.hard_packet_limit != XFRM_INF) + return false; + + if (info->family != AF_INET || info->mode != XFRM_MODE_TUNNEL) + return false; + + /* XXX: check xfrm algo, see xfrm_state_pack_algo(). */ + + return true; +} + +static int xfrm_state_check(int xfrm_sock, uint32_t seq, uint32_t spi, + struct in_addr src, struct in_addr dst, + struct xfrm_desc *desc) +{ + struct { + struct nlmsghdr nh; + char attrbuf[MAX_PAYLOAD]; + } req; + struct { + struct nlmsghdr nh; + union { + struct xfrm_usersa_info info; + int error; + }; + char attrbuf[MAX_PAYLOAD]; + } answer; + struct xfrm_address_filter filter = {}; + bool found = false; + + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(0); + req.nh.nlmsg_type = XFRM_MSG_GETSA; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.nh.nlmsg_seq = seq; + + /* + * Add dump filter by source address as there may be other tunnels + * in this netns (if tests run in parallel). + */ + filter.family = AF_INET; + filter.splen = 0x1f; /* 0xffffffff mask see addr_match() */ + memcpy(&filter.saddr, &src, sizeof(src)); + if (rtattr_pack(&req.nh, sizeof(req), XFRMA_ADDRESS_FILTER, + &filter, sizeof(filter))) + return -1; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + + while (1) { + if (recv(xfrm_sock, &answer, sizeof(answer), 0) < 0) { + pr_err("recv()"); + return -1; + } + if (answer.nh.nlmsg_type == NLMSG_ERROR) { + printk("NLMSG_ERROR: %d: %s", + answer.error, strerror(-answer.error)); + return -1; + } else if (answer.nh.nlmsg_type == NLMSG_DONE) { + if (found) + return 0; + printk("didn't find allocated xfrm state in dump"); + return -1; + } else if (answer.nh.nlmsg_type == XFRM_MSG_NEWSA) { + if (xfrm_usersa_found(&answer.info, spi, src, dst, desc)) + found = true; + } + } +} + +static int xfrm_set(int xfrm_sock, uint32_t *seq, + struct in_addr src, struct in_addr dst, + struct in_addr tunsrc, struct in_addr tundst, + struct xfrm_desc *desc) +{ + int err; + + err = xfrm_state_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, desc); + if (err) { + printk("Failed to add xfrm state"); + return -1; + } + + err = xfrm_state_add(xfrm_sock, (*seq)++, gen_spi(src), dst, src, desc); + if (err) { + printk("Failed to add xfrm state"); + return -1; + } + + /* Check dumps for XFRM_MSG_GETSA */ + err = xfrm_state_check(xfrm_sock, (*seq)++, gen_spi(src), src, dst, desc); + err |= xfrm_state_check(xfrm_sock, (*seq)++, gen_spi(src), dst, src, desc); + if (err) { + printk("Failed to check xfrm state"); + return -1; + } + + return 0; +} + +static int xfrm_policy_add(int xfrm_sock, uint32_t seq, uint32_t spi, + struct in_addr src, struct in_addr dst, uint8_t dir, + struct in_addr tunsrc, struct in_addr tundst, uint8_t proto) +{ + struct { + struct nlmsghdr nh; + struct xfrm_userpolicy_info info; + char attrbuf[MAX_PAYLOAD]; + } req; + struct xfrm_user_tmpl tmpl; + + memset(&req, 0, sizeof(req)); + memset(&tmpl, 0, sizeof(tmpl)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.info)); + req.nh.nlmsg_type = XFRM_MSG_NEWPOLICY; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = seq; + + /* Fill selector. */ + memcpy(&req.info.sel.daddr, &dst, sizeof(tundst)); + memcpy(&req.info.sel.saddr, &src, sizeof(tunsrc)); + req.info.sel.family = AF_INET; + req.info.sel.prefixlen_d = PREFIX_LEN; + req.info.sel.prefixlen_s = PREFIX_LEN; + + /* Fill lifteme_cfg */ + req.info.lft.soft_byte_limit = XFRM_INF; + req.info.lft.hard_byte_limit = XFRM_INF; + req.info.lft.soft_packet_limit = XFRM_INF; + req.info.lft.hard_packet_limit = XFRM_INF; + + req.info.dir = dir; + + /* Fill tmpl */ + memcpy(&tmpl.id.daddr, &dst, sizeof(dst)); + /* Note: zero-spi cannot be deleted */ + tmpl.id.spi = spi; + tmpl.id.proto = proto; + tmpl.family = AF_INET; + memcpy(&tmpl.saddr, &src, sizeof(src)); + tmpl.mode = XFRM_MODE_TUNNEL; + tmpl.aalgos = (~(uint32_t)0); + tmpl.ealgos = (~(uint32_t)0); + tmpl.calgos = (~(uint32_t)0); + + if (rtattr_pack(&req.nh, sizeof(req), XFRMA_TMPL, &tmpl, sizeof(tmpl))) + return -1; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + + return netlink_check_answer(xfrm_sock); +} + +static int xfrm_prepare(int xfrm_sock, uint32_t *seq, + struct in_addr src, struct in_addr dst, + struct in_addr tunsrc, struct in_addr tundst, uint8_t proto) +{ + if (xfrm_policy_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, + XFRM_POLICY_OUT, tunsrc, tundst, proto)) { + printk("Failed to add xfrm policy"); + return -1; + } + + if (xfrm_policy_add(xfrm_sock, (*seq)++, gen_spi(src), dst, src, + XFRM_POLICY_IN, tunsrc, tundst, proto)) { + printk("Failed to add xfrm policy"); + return -1; + } + + return 0; +} + +static int xfrm_policy_del(int xfrm_sock, uint32_t seq, + struct in_addr src, struct in_addr dst, uint8_t dir, + struct in_addr tunsrc, struct in_addr tundst) +{ + struct { + struct nlmsghdr nh; + struct xfrm_userpolicy_id id; + char attrbuf[MAX_PAYLOAD]; + } req; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.id)); + req.nh.nlmsg_type = XFRM_MSG_DELPOLICY; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = seq; + + /* Fill id */ + memcpy(&req.id.sel.daddr, &dst, sizeof(tundst)); + memcpy(&req.id.sel.saddr, &src, sizeof(tunsrc)); + req.id.sel.family = AF_INET; + req.id.sel.prefixlen_d = PREFIX_LEN; + req.id.sel.prefixlen_s = PREFIX_LEN; + req.id.dir = dir; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + + return netlink_check_answer(xfrm_sock); +} + +static int xfrm_cleanup(int xfrm_sock, uint32_t *seq, + struct in_addr src, struct in_addr dst, + struct in_addr tunsrc, struct in_addr tundst) +{ + if (xfrm_policy_del(xfrm_sock, (*seq)++, src, dst, + XFRM_POLICY_OUT, tunsrc, tundst)) { + printk("Failed to add xfrm policy"); + return -1; + } + + if (xfrm_policy_del(xfrm_sock, (*seq)++, dst, src, + XFRM_POLICY_IN, tunsrc, tundst)) { + printk("Failed to add xfrm policy"); + return -1; + } + + return 0; +} + +static int xfrm_state_del(int xfrm_sock, uint32_t seq, uint32_t spi, + struct in_addr src, struct in_addr dst, uint8_t proto) +{ + struct { + struct nlmsghdr nh; + struct xfrm_usersa_id id; + char attrbuf[MAX_PAYLOAD]; + } req; + xfrm_address_t saddr = {}; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.id)); + req.nh.nlmsg_type = XFRM_MSG_DELSA; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = seq; + + memcpy(&req.id.daddr, &dst, sizeof(dst)); + req.id.family = AF_INET; + req.id.proto = proto; + /* Note: zero-spi cannot be deleted */ + req.id.spi = spi; + + memcpy(&saddr, &src, sizeof(src)); + if (rtattr_pack(&req.nh, sizeof(req), XFRMA_SRCADDR, &saddr, sizeof(saddr))) + return -1; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return -1; + } + + return netlink_check_answer(xfrm_sock); +} + +static int xfrm_delete(int xfrm_sock, uint32_t *seq, + struct in_addr src, struct in_addr dst, + struct in_addr tunsrc, struct in_addr tundst, uint8_t proto) +{ + if (xfrm_state_del(xfrm_sock, (*seq)++, gen_spi(src), src, dst, proto)) { + printk("Failed to remove xfrm state"); + return -1; + } + + if (xfrm_state_del(xfrm_sock, (*seq)++, gen_spi(src), dst, src, proto)) { + printk("Failed to remove xfrm state"); + return -1; + } + + return 0; +} + +static int xfrm_state_allocspi(int xfrm_sock, uint32_t *seq, + uint32_t spi, uint8_t proto) +{ + struct { + struct nlmsghdr nh; + struct xfrm_userspi_info spi; + } req; + struct { + struct nlmsghdr nh; + union { + struct xfrm_usersa_info info; + int error; + }; + } answer; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.spi)); + req.nh.nlmsg_type = XFRM_MSG_ALLOCSPI; + req.nh.nlmsg_flags = NLM_F_REQUEST; + req.nh.nlmsg_seq = (*seq)++; + + req.spi.info.family = AF_INET; + req.spi.min = spi; + req.spi.max = spi; + req.spi.info.id.proto = proto; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + return KSFT_FAIL; + } + + if (recv(xfrm_sock, &answer, sizeof(answer), 0) < 0) { + pr_err("recv()"); + return KSFT_FAIL; + } else if (answer.nh.nlmsg_type == XFRM_MSG_NEWSA) { + uint32_t new_spi = htonl(answer.info.id.spi); + + if (new_spi != spi) { + printk("allocated spi is different from requested: %#x != %#x", + new_spi, spi); + return KSFT_FAIL; + } + return KSFT_PASS; + } else if (answer.nh.nlmsg_type != NLMSG_ERROR) { + printk("expected NLMSG_ERROR, got %d", (int)answer.nh.nlmsg_type); + return KSFT_FAIL; + } + + printk("NLMSG_ERROR: %d: %s", answer.error, strerror(-answer.error)); + return (answer.error) ? KSFT_FAIL : KSFT_PASS; +} + +static int netlink_sock_bind(int *sock, uint32_t *seq, int proto, uint32_t groups) +{ + struct sockaddr_nl snl = {}; + socklen_t addr_len; + int ret = -1; + + snl.nl_family = AF_NETLINK; + snl.nl_groups = groups; + + if (netlink_sock(sock, seq, proto)) { + printk("Failed to open xfrm netlink socket"); + return -1; + } + + if (bind(*sock, (struct sockaddr *)&snl, sizeof(snl)) < 0) { + pr_err("bind()"); + goto out_close; + } + + addr_len = sizeof(snl); + if (getsockname(*sock, (struct sockaddr *)&snl, &addr_len) < 0) { + pr_err("getsockname()"); + goto out_close; + } + if (addr_len != sizeof(snl)) { + printk("Wrong address length %d", addr_len); + goto out_close; + } + if (snl.nl_family != AF_NETLINK) { + printk("Wrong address family %d", snl.nl_family); + goto out_close; + } + return 0; + +out_close: + close(*sock); + return ret; +} + +static int xfrm_monitor_acquire(int xfrm_sock, uint32_t *seq, unsigned int nr) +{ + struct { + struct nlmsghdr nh; + union { + struct xfrm_user_acquire acq; + int error; + }; + char attrbuf[MAX_PAYLOAD]; + } req; + struct xfrm_user_tmpl xfrm_tmpl = {}; + int xfrm_listen = -1, ret = KSFT_FAIL; + uint32_t seq_listen; + + if (netlink_sock_bind(&xfrm_listen, &seq_listen, NETLINK_XFRM, XFRMNLGRP_ACQUIRE)) + return KSFT_FAIL; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.acq)); + req.nh.nlmsg_type = XFRM_MSG_ACQUIRE; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = (*seq)++; + + req.acq.policy.sel.family = AF_INET; + req.acq.aalgos = 0xfeed; + req.acq.ealgos = 0xbaad; + req.acq.calgos = 0xbabe; + + xfrm_tmpl.family = AF_INET; + xfrm_tmpl.id.proto = IPPROTO_ESP; + if (rtattr_pack(&req.nh, sizeof(req), XFRMA_TMPL, &xfrm_tmpl, sizeof(xfrm_tmpl))) + goto out_close; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + goto out_close; + } + + if (recv(xfrm_sock, &req, sizeof(req), 0) < 0) { + pr_err("recv()"); + goto out_close; + } else if (req.nh.nlmsg_type != NLMSG_ERROR) { + printk("expected NLMSG_ERROR, got %d", (int)req.nh.nlmsg_type); + goto out_close; + } + + if (req.error) { + printk("NLMSG_ERROR: %d: %s", req.error, strerror(-req.error)); + ret = req.error; + goto out_close; + } + + if (recv(xfrm_listen, &req, sizeof(req), 0) < 0) { + pr_err("recv()"); + goto out_close; + } + + if (req.acq.aalgos != 0xfeed || req.acq.ealgos != 0xbaad + || req.acq.calgos != 0xbabe) { + printk("xfrm_user_acquire has changed %x %x %x", + req.acq.aalgos, req.acq.ealgos, req.acq.calgos); + goto out_close; + } + + ret = KSFT_PASS; +out_close: + close(xfrm_listen); + return ret; +} + +static int xfrm_expire_state(int xfrm_sock, uint32_t *seq, + unsigned int nr, struct xfrm_desc *desc) +{ + struct { + struct nlmsghdr nh; + union { + struct xfrm_user_expire expire; + int error; + }; + } req; + struct in_addr src, dst; + int xfrm_listen = -1, ret = KSFT_FAIL; + uint32_t seq_listen; + + src = inet_makeaddr(INADDR_B, child_ip(nr)); + dst = inet_makeaddr(INADDR_B, grchild_ip(nr)); + + if (xfrm_state_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, desc)) { + printk("Failed to add xfrm state"); + return KSFT_FAIL; + } + + if (netlink_sock_bind(&xfrm_listen, &seq_listen, NETLINK_XFRM, XFRMNLGRP_EXPIRE)) + return KSFT_FAIL; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.expire)); + req.nh.nlmsg_type = XFRM_MSG_EXPIRE; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = (*seq)++; + + memcpy(&req.expire.state.id.daddr, &dst, sizeof(dst)); + req.expire.state.id.spi = gen_spi(src); + req.expire.state.id.proto = desc->proto; + req.expire.state.family = AF_INET; + req.expire.hard = 0xff; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + goto out_close; + } + + if (recv(xfrm_sock, &req, sizeof(req), 0) < 0) { + pr_err("recv()"); + goto out_close; + } else if (req.nh.nlmsg_type != NLMSG_ERROR) { + printk("expected NLMSG_ERROR, got %d", (int)req.nh.nlmsg_type); + goto out_close; + } + + if (req.error) { + printk("NLMSG_ERROR: %d: %s", req.error, strerror(-req.error)); + ret = req.error; + goto out_close; + } + + if (recv(xfrm_listen, &req, sizeof(req), 0) < 0) { + pr_err("recv()"); + goto out_close; + } + + if (req.expire.hard != 0x1) { + printk("expire.hard is not set: %x", req.expire.hard); + goto out_close; + } + + ret = KSFT_PASS; +out_close: + close(xfrm_listen); + return ret; +} + +static int xfrm_expire_policy(int xfrm_sock, uint32_t *seq, + unsigned int nr, struct xfrm_desc *desc) +{ + struct { + struct nlmsghdr nh; + union { + struct xfrm_user_polexpire expire; + int error; + }; + } req; + struct in_addr src, dst, tunsrc, tundst; + int xfrm_listen = -1, ret = KSFT_FAIL; + uint32_t seq_listen; + + src = inet_makeaddr(INADDR_B, child_ip(nr)); + dst = inet_makeaddr(INADDR_B, grchild_ip(nr)); + tunsrc = inet_makeaddr(INADDR_A, child_ip(nr)); + tundst = inet_makeaddr(INADDR_A, grchild_ip(nr)); + + if (xfrm_policy_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, + XFRM_POLICY_OUT, tunsrc, tundst, desc->proto)) { + printk("Failed to add xfrm policy"); + return KSFT_FAIL; + } + + if (netlink_sock_bind(&xfrm_listen, &seq_listen, NETLINK_XFRM, XFRMNLGRP_EXPIRE)) + return KSFT_FAIL; + + memset(&req, 0, sizeof(req)); + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.expire)); + req.nh.nlmsg_type = XFRM_MSG_POLEXPIRE; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nh.nlmsg_seq = (*seq)++; + + /* Fill selector. */ + memcpy(&req.expire.pol.sel.daddr, &dst, sizeof(tundst)); + memcpy(&req.expire.pol.sel.saddr, &src, sizeof(tunsrc)); + req.expire.pol.sel.family = AF_INET; + req.expire.pol.sel.prefixlen_d = PREFIX_LEN; + req.expire.pol.sel.prefixlen_s = PREFIX_LEN; + req.expire.pol.dir = XFRM_POLICY_OUT; + req.expire.hard = 0xff; + + if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { + pr_err("send()"); + goto out_close; + } + + if (recv(xfrm_sock, &req, sizeof(req), 0) < 0) { + pr_err("recv()"); + goto out_close; + } else if (req.nh.nlmsg_type != NLMSG_ERROR) { + printk("expected NLMSG_ERROR, got %d", (int)req.nh.nlmsg_type); + goto out_close; + } + + if (req.error) { + printk("NLMSG_ERROR: %d: %s", req.error, strerror(-req.error)); + ret = req.error; + goto out_close; + } + + if (recv(xfrm_listen, &req, sizeof(req), 0) < 0) { + pr_err("recv()"); + goto out_close; + } + + if (req.expire.hard != 0x1) { + printk("expire.hard is not set: %x", req.expire.hard); + goto out_close; + } + + ret = KSFT_PASS; +out_close: + close(xfrm_listen); + return ret; +} + +static int child_serv(int xfrm_sock, uint32_t *seq, + unsigned int nr, int cmd_fd, void *buf, struct xfrm_desc *desc) +{ + struct in_addr src, dst, tunsrc, tundst; + struct test_desc msg; + int ret = KSFT_FAIL; + + src = inet_makeaddr(INADDR_B, child_ip(nr)); + dst = inet_makeaddr(INADDR_B, grchild_ip(nr)); + tunsrc = inet_makeaddr(INADDR_A, child_ip(nr)); + tundst = inet_makeaddr(INADDR_A, grchild_ip(nr)); + + /* UDP pinging without xfrm */ + if (do_ping(cmd_fd, buf, page_size, src, true, 0, 0, udp_ping_send)) { + printk("ping failed before setting xfrm"); + return KSFT_FAIL; + } + + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_XFRM_PREPARE; + memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); + write_msg(cmd_fd, &msg, 1); + + if (xfrm_prepare(xfrm_sock, seq, src, dst, tunsrc, tundst, desc->proto)) { + printk("failed to prepare xfrm"); + goto cleanup; + } + + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_XFRM_ADD; + memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); + write_msg(cmd_fd, &msg, 1); + if (xfrm_set(xfrm_sock, seq, src, dst, tunsrc, tundst, desc)) { + printk("failed to set xfrm"); + goto delete; + } + + /* UDP pinging with xfrm tunnel */ + if (do_ping(cmd_fd, buf, page_size, tunsrc, + true, 0, 0, udp_ping_send)) { + printk("ping failed for xfrm"); + goto delete; + } + + ret = KSFT_PASS; +delete: + /* xfrm delete */ + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_XFRM_DEL; + memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); + write_msg(cmd_fd, &msg, 1); + + if (xfrm_delete(xfrm_sock, seq, src, dst, tunsrc, tundst, desc->proto)) { + printk("failed ping to remove xfrm"); + ret = KSFT_FAIL; + } + +cleanup: + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_XFRM_CLEANUP; + memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); + write_msg(cmd_fd, &msg, 1); + if (xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst)) { + printk("failed ping to cleanup xfrm"); + ret = KSFT_FAIL; + } + return ret; +} + +static int child_f(unsigned int nr, int test_desc_fd, int cmd_fd, void *buf) +{ + struct xfrm_desc desc; + struct test_desc msg; + int xfrm_sock = -1; + uint32_t seq; + + if (switch_ns(nsfd_childa)) + exit(KSFT_FAIL); + + if (netlink_sock(&xfrm_sock, &seq, NETLINK_XFRM)) { + printk("Failed to open xfrm netlink socket"); + exit(KSFT_FAIL); + } + + /* Check that seq sock is ready, just for sure. */ + memset(&msg, 0, sizeof(msg)); + msg.type = MSG_ACK; + write_msg(cmd_fd, &msg, 1); + read_msg(cmd_fd, &msg, 1); + if (msg.type != MSG_ACK) { + printk("Ack failed"); + exit(KSFT_FAIL); + } + + for (;;) { + ssize_t received = read(test_desc_fd, &desc, sizeof(desc)); + int ret; + + if (received == 0) /* EOF */ + break; + + if (received != sizeof(desc)) { + pr_err("read() returned %zd", received); + exit(KSFT_FAIL); + } + + switch (desc.type) { + case CREATE_TUNNEL: + ret = child_serv(xfrm_sock, &seq, nr, + cmd_fd, buf, &desc); + break; + case ALLOCATE_SPI: + ret = xfrm_state_allocspi(xfrm_sock, &seq, + -1, desc.proto); + break; + case MONITOR_ACQUIRE: + ret = xfrm_monitor_acquire(xfrm_sock, &seq, nr); + break; + case EXPIRE_STATE: + ret = xfrm_expire_state(xfrm_sock, &seq, nr, &desc); + break; + case EXPIRE_POLICY: + ret = xfrm_expire_policy(xfrm_sock, &seq, nr, &desc); + break; + default: + printk("Unknown desc type %d", desc.type); + exit(KSFT_FAIL); + } + write_test_result(ret, &desc); + } + + close(xfrm_sock); + + msg.type = MSG_EXIT; + write_msg(cmd_fd, &msg, 1); + exit(KSFT_PASS); +} + +static void grand_child_serv(unsigned int nr, int cmd_fd, void *buf, + struct test_desc *msg, int xfrm_sock, uint32_t *seq) +{ + struct in_addr src, dst, tunsrc, tundst; + bool tun_reply; + struct xfrm_desc *desc = &msg->body.xfrm_desc; + + src = inet_makeaddr(INADDR_B, grchild_ip(nr)); + dst = inet_makeaddr(INADDR_B, child_ip(nr)); + tunsrc = inet_makeaddr(INADDR_A, grchild_ip(nr)); + tundst = inet_makeaddr(INADDR_A, child_ip(nr)); + + switch (msg->type) { + case MSG_EXIT: + exit(KSFT_PASS); + case MSG_ACK: + write_msg(cmd_fd, msg, 1); + break; + case MSG_PING: + tun_reply = memcmp(&dst, &msg->body.ping.reply_ip, sizeof(in_addr_t)); + /* UDP pinging without xfrm */ + if (do_ping(cmd_fd, buf, page_size, tun_reply ? tunsrc : src, + false, msg->body.ping.port, + msg->body.ping.reply_ip, udp_ping_reply)) { + printk("ping failed before setting xfrm"); + } + break; + case MSG_XFRM_PREPARE: + if (xfrm_prepare(xfrm_sock, seq, src, dst, tunsrc, tundst, + desc->proto)) { + xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst); + printk("failed to prepare xfrm"); + } + break; + case MSG_XFRM_ADD: + if (xfrm_set(xfrm_sock, seq, src, dst, tunsrc, tundst, desc)) { + xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst); + printk("failed to set xfrm"); + } + break; + case MSG_XFRM_DEL: + if (xfrm_delete(xfrm_sock, seq, src, dst, tunsrc, tundst, + desc->proto)) { + xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst); + printk("failed to remove xfrm"); + } + break; + case MSG_XFRM_CLEANUP: + if (xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst)) { + printk("failed to cleanup xfrm"); + } + break; + default: + printk("got unknown msg type %d", msg->type); + }; +} + +static int grand_child_f(unsigned int nr, int cmd_fd, void *buf) +{ + struct test_desc msg; + int xfrm_sock = -1; + uint32_t seq; + + if (switch_ns(nsfd_childb)) + exit(KSFT_FAIL); + + if (netlink_sock(&xfrm_sock, &seq, NETLINK_XFRM)) { + printk("Failed to open xfrm netlink socket"); + exit(KSFT_FAIL); + } + + do { + read_msg(cmd_fd, &msg, 1); + grand_child_serv(nr, cmd_fd, buf, &msg, xfrm_sock, &seq); + } while (1); + + close(xfrm_sock); + exit(KSFT_FAIL); +} + +static int start_child(unsigned int nr, char *veth, int test_desc_fd[2]) +{ + int cmd_sock[2]; + void *data_map; + pid_t child; + + if (init_child(nsfd_childa, veth, child_ip(nr), grchild_ip(nr))) + return -1; + + if (init_child(nsfd_childb, veth, grchild_ip(nr), child_ip(nr))) + return -1; + + child = fork(); + if (child < 0) { + pr_err("fork()"); + return -1; + } else if (child) { + /* in parent - selftest */ + return switch_ns(nsfd_parent); + } + + if (close(test_desc_fd[1])) { + pr_err("close()"); + return -1; + } + + /* child */ + data_map = mmap(0, page_size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (data_map == MAP_FAILED) { + pr_err("mmap()"); + return -1; + } + + randomize_buffer(data_map, page_size); + + if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, cmd_sock)) { + pr_err("socketpair()"); + return -1; + } + + child = fork(); + if (child < 0) { + pr_err("fork()"); + return -1; + } else if (child) { + if (close(cmd_sock[0])) { + pr_err("close()"); + return -1; + } + return child_f(nr, test_desc_fd[0], cmd_sock[1], data_map); + } + if (close(cmd_sock[1])) { + pr_err("close()"); + return -1; + } + return grand_child_f(nr, cmd_sock[0], data_map); +} + +static void exit_usage(char **argv) +{ + printk("Usage: %s [nr_process]", argv[0]); + exit(KSFT_FAIL); +} + +static int __write_desc(int test_desc_fd, struct xfrm_desc *desc) +{ + ssize_t ret; + + ret = write(test_desc_fd, desc, sizeof(*desc)); + + if (ret == sizeof(*desc)) + return 0; + + pr_err("Writing test's desc failed %ld", ret); + + return -1; +} + +static int write_desc(int proto, int test_desc_fd, + char *a, char *e, char *c, char *ae) +{ + struct xfrm_desc desc = {}; + + desc.type = CREATE_TUNNEL; + desc.proto = proto; + + if (a) + strncpy(desc.a_algo, a, ALGO_LEN - 1); + if (e) + strncpy(desc.e_algo, e, ALGO_LEN - 1); + if (c) + strncpy(desc.c_algo, c, ALGO_LEN - 1); + if (ae) + strncpy(desc.ae_algo, ae, ALGO_LEN - 1); + + return __write_desc(test_desc_fd, &desc); +} + +int proto_list[] = { IPPROTO_AH, IPPROTO_COMP, IPPROTO_ESP }; +char *ah_list[] = { + "digest_null", "hmac(md5)", "hmac(sha1)", "hmac(sha256)", + "hmac(sha384)", "hmac(sha512)", "hmac(rmd160)", + "xcbc(aes)", "cmac(aes)" +}; +char *comp_list[] = { + "deflate", +#if 0 + /* No compression backend realization */ + "lzs", "lzjh" +#endif +}; +char *e_list[] = { + "ecb(cipher_null)", "cbc(des)", "cbc(des3_ede)", "cbc(cast5)", + "cbc(blowfish)", "cbc(aes)", "cbc(serpent)", "cbc(camellia)", + "cbc(twofish)", "rfc3686(ctr(aes))" +}; +char *ae_list[] = { +#if 0 + /* not implemented */ + "rfc4106(gcm(aes))", "rfc4309(ccm(aes))", "rfc4543(gcm(aes))", + "rfc7539esp(chacha20,poly1305)" +#endif +}; + +const unsigned int proto_plan = ARRAY_SIZE(ah_list) + ARRAY_SIZE(comp_list) \ + + (ARRAY_SIZE(ah_list) * ARRAY_SIZE(e_list)) \ + + ARRAY_SIZE(ae_list); + +static int write_proto_plan(int fd, int proto) +{ + unsigned int i; + + switch (proto) { + case IPPROTO_AH: + for (i = 0; i < ARRAY_SIZE(ah_list); i++) { + if (write_desc(proto, fd, ah_list[i], 0, 0, 0)) + return -1; + } + break; + case IPPROTO_COMP: + for (i = 0; i < ARRAY_SIZE(comp_list); i++) { + if (write_desc(proto, fd, 0, 0, comp_list[i], 0)) + return -1; + } + break; + case IPPROTO_ESP: + for (i = 0; i < ARRAY_SIZE(ah_list); i++) { + int j; + + for (j = 0; j < ARRAY_SIZE(e_list); j++) { + if (write_desc(proto, fd, ah_list[i], + e_list[j], 0, 0)) + return -1; + } + } + for (i = 0; i < ARRAY_SIZE(ae_list); i++) { + if (write_desc(proto, fd, 0, 0, 0, ae_list[i])) + return -1; + } + break; + default: + printk("BUG: Specified unknown proto %d", proto); + return -1; + } + + return 0; +} + +/* + * Some structures in xfrm uapi header differ in size between + * 64-bit and 32-bit ABI: + * + * 32-bit UABI | 64-bit UABI + * -------------------------------------|------------------------------------- + * sizeof(xfrm_usersa_info) = 220 | sizeof(xfrm_usersa_info) = 224 + * sizeof(xfrm_userpolicy_info) = 164 | sizeof(xfrm_userpolicy_info) = 168 + * sizeof(xfrm_userspi_info) = 228 | sizeof(xfrm_userspi_info) = 232 + * sizeof(xfrm_user_acquire) = 276 | sizeof(xfrm_user_acquire) = 280 + * sizeof(xfrm_user_expire) = 224 | sizeof(xfrm_user_expire) = 232 + * sizeof(xfrm_user_polexpire) = 168 | sizeof(xfrm_user_polexpire) = 176 + * + * Check the affected by the UABI difference structures. + */ +const unsigned int compat_plan = 4; +static int write_compat_struct_tests(int test_desc_fd) +{ + struct xfrm_desc desc = {}; + + desc.type = ALLOCATE_SPI; + desc.proto = IPPROTO_AH; + strncpy(desc.a_algo, ah_list[0], ALGO_LEN - 1); + + if (__write_desc(test_desc_fd, &desc)) + return -1; + + desc.type = MONITOR_ACQUIRE; + if (__write_desc(test_desc_fd, &desc)) + return -1; + + desc.type = EXPIRE_STATE; + if (__write_desc(test_desc_fd, &desc)) + return -1; + + desc.type = EXPIRE_POLICY; + if (__write_desc(test_desc_fd, &desc)) + return -1; + + return 0; +} + +static int write_test_plan(int test_desc_fd) +{ + unsigned int i; + pid_t child; + + child = fork(); + if (child < 0) { + pr_err("fork()"); + return -1; + } + if (child) { + if (close(test_desc_fd)) + printk("close(): %m"); + return 0; + } + + if (write_compat_struct_tests(test_desc_fd)) + exit(KSFT_FAIL); + + for (i = 0; i < ARRAY_SIZE(proto_list); i++) { + if (write_proto_plan(test_desc_fd, proto_list[i])) + exit(KSFT_FAIL); + } + + exit(KSFT_PASS); +} + +static int children_cleanup(void) +{ + unsigned ret = KSFT_PASS; + + while (1) { + int status; + pid_t p = wait(&status); + + if ((p < 0) && errno == ECHILD) + break; + + if (p < 0) { + pr_err("wait()"); + return KSFT_FAIL; + } + + if (!WIFEXITED(status)) { + ret = KSFT_FAIL; + continue; + } + + if (WEXITSTATUS(status) == KSFT_FAIL) + ret = KSFT_FAIL; + } + + return ret; +} + +typedef void (*print_res)(const char *, ...); + +static int check_results(void) +{ + struct test_result tr = {}; + struct xfrm_desc *d = &tr.desc; + int ret = KSFT_PASS; + + while (1) { + ssize_t received = read(results_fd[0], &tr, sizeof(tr)); + print_res result; + + if (received == 0) /* EOF */ + break; + + if (received != sizeof(tr)) { + pr_err("read() returned %zd", received); + return KSFT_FAIL; + } + + switch (tr.res) { + case KSFT_PASS: + result = ksft_test_result_pass; + break; + case KSFT_FAIL: + default: + result = ksft_test_result_fail; + ret = KSFT_FAIL; + } + + result(" %s: [%u, '%s', '%s', '%s', '%s', %u]\n", + desc_name[d->type], (unsigned int)d->proto, d->a_algo, + d->e_algo, d->c_algo, d->ae_algo, d->icv_len); + } + + return ret; +} + +int main(int argc, char **argv) +{ + unsigned int nr_process = 1; + int route_sock = -1, ret = KSFT_SKIP; + int test_desc_fd[2]; + uint32_t route_seq; + unsigned int i; + + if (argc > 2) + exit_usage(argv); + + if (argc > 1) { + char *endptr; + + errno = 0; + nr_process = strtol(argv[1], &endptr, 10); + if ((errno == ERANGE && (nr_process == LONG_MAX || nr_process == LONG_MIN)) + || (errno != 0 && nr_process == 0) + || (endptr == argv[1]) || (*endptr != '\0')) { + printk("Failed to parse [nr_process]"); + exit_usage(argv); + } + + if (nr_process > MAX_PROCESSES || !nr_process) { + printk("nr_process should be between [1; %u]", + MAX_PROCESSES); + exit_usage(argv); + } + } + + srand(time(NULL)); + page_size = sysconf(_SC_PAGESIZE); + if (page_size < 1) + ksft_exit_skip("sysconf(): %m\n"); + + if (pipe2(test_desc_fd, O_DIRECT) < 0) + ksft_exit_skip("pipe(): %m\n"); + + if (pipe2(results_fd, O_DIRECT) < 0) + ksft_exit_skip("pipe(): %m\n"); + + if (init_namespaces()) + ksft_exit_skip("Failed to create namespaces\n"); + + if (netlink_sock(&route_sock, &route_seq, NETLINK_ROUTE)) + ksft_exit_skip("Failed to open netlink route socket\n"); + + for (i = 0; i < nr_process; i++) { + char veth[VETH_LEN]; + + snprintf(veth, VETH_LEN, VETH_FMT, i); + + if (veth_add(route_sock, route_seq++, veth, nsfd_childa, veth, nsfd_childb)) { + close(route_sock); + ksft_exit_fail_msg("Failed to create veth device"); + } + + if (start_child(i, veth, test_desc_fd)) { + close(route_sock); + ksft_exit_fail_msg("Child %u failed to start", i); + } + } + + if (close(route_sock) || close(test_desc_fd[0]) || close(results_fd[1])) + ksft_exit_fail_msg("close(): %m"); + + ksft_set_plan(proto_plan + compat_plan); + + if (write_test_plan(test_desc_fd[1])) + ksft_exit_fail_msg("Failed to write test plan to pipe"); + + ret = check_results(); + + if (children_cleanup() == KSFT_FAIL) + exit(KSFT_FAIL); + + exit(ret); +} -- cgit v1.2.3 From 8ee2267ad33e0ba021e9dd9b437f773906cd99d6 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 29 Sep 2020 11:15:52 +0300 Subject: drop_monitor: Convert to using devlink tracepoint Convert drop monitor to use the recently introduced 'devlink_trap_report' tracepoint instead of having devlink call into drop monitor. This is both consistent with software originated drops ('kfree_skb' tracepoint) and also allows drop monitor to be built as a module and still report hardware originated drops. Signed-off-by: Ido Schimmel Reviewed-by: Jiri Pirko Signed-off-by: David S. Miller --- MAINTAINERS | 1 - include/net/drop_monitor.h | 36 ----------------------------- net/Kconfig | 1 - net/core/devlink.c | 24 -------------------- net/core/drop_monitor.c | 56 +++++++++++++++++++++++++++++++++------------- 5 files changed, 40 insertions(+), 78 deletions(-) delete mode 100644 include/net/drop_monitor.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 42c69d2eeece..c1e946606dce 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12065,7 +12065,6 @@ M: Neil Horman L: netdev@vger.kernel.org S: Maintained W: https://fedorahosted.org/dropwatch/ -F: include/net/drop_monitor.h F: include/uapi/linux/net_dropmon.h F: net/core/drop_monitor.c diff --git a/include/net/drop_monitor.h b/include/net/drop_monitor.h deleted file mode 100644 index 3f5b6ddb3179..000000000000 --- a/include/net/drop_monitor.h +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ - -#ifndef _NET_DROP_MONITOR_H_ -#define _NET_DROP_MONITOR_H_ - -#include -#include -#include -#include - -/** - * struct net_dm_hw_metadata - Hardware-supplied packet metadata. - * @trap_group_name: Hardware trap group name. - * @trap_name: Hardware trap name. - * @input_dev: Input netdevice. - * @fa_cookie: Flow action user cookie. - */ -struct net_dm_hw_metadata { - const char *trap_group_name; - const char *trap_name; - struct net_device *input_dev; - const struct flow_action_cookie *fa_cookie; -}; - -#if IS_REACHABLE(CONFIG_NET_DROP_MONITOR) -void net_dm_hw_report(struct sk_buff *skb, - const struct net_dm_hw_metadata *hw_metadata); -#else -static inline void -net_dm_hw_report(struct sk_buff *skb, - const struct net_dm_hw_metadata *hw_metadata) -{ -} -#endif - -#endif /* _NET_DROP_MONITOR_H_ */ diff --git a/net/Kconfig b/net/Kconfig index 3831206977a1..d6567162c1cf 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -434,7 +434,6 @@ config NET_SOCK_MSG config NET_DEVLINK bool default n - imply NET_DROP_MONITOR config PAGE_POOL bool diff --git a/net/core/devlink.c b/net/core/devlink.c index c0f300507c37..2ea9fdc0df2d 100644 --- a/net/core/devlink.c +++ b/net/core/devlink.c @@ -27,7 +27,6 @@ #include #include #include -#include #define CREATE_TRACE_POINTS #include @@ -9261,24 +9260,6 @@ devlink_trap_stats_update(struct devlink_stats __percpu *trap_stats, u64_stats_update_end(&stats->syncp); } -static void -devlink_trap_report_metadata_fill(struct net_dm_hw_metadata *hw_metadata, - const struct devlink_trap_item *trap_item, - struct devlink_port *in_devlink_port, - const struct flow_action_cookie *fa_cookie) -{ - struct devlink_trap_group_item *group_item = trap_item->group_item; - - hw_metadata->trap_group_name = group_item->group->name; - hw_metadata->trap_name = trap_item->trap->name; - hw_metadata->fa_cookie = fa_cookie; - - spin_lock(&in_devlink_port->type_lock); - if (in_devlink_port->type == DEVLINK_PORT_TYPE_ETH) - hw_metadata->input_dev = in_devlink_port->type_dev; - spin_unlock(&in_devlink_port->type_lock); -} - static void devlink_trap_report_metadata_set(struct devlink_trap_metadata *metadata, const struct devlink_trap_item *trap_item, @@ -9309,7 +9290,6 @@ void devlink_trap_report(struct devlink *devlink, struct sk_buff *skb, { struct devlink_trap_item *trap_item = trap_ctx; - struct net_dm_hw_metadata hw_metadata = {}; devlink_trap_stats_update(trap_item->stats, skb->len); devlink_trap_stats_update(trap_item->group_item->stats, skb->len); @@ -9321,10 +9301,6 @@ void devlink_trap_report(struct devlink *devlink, struct sk_buff *skb, if (trap_item->trap->type == DEVLINK_TRAP_TYPE_CONTROL) return; - devlink_trap_report_metadata_fill(&hw_metadata, trap_item, - in_devlink_port, fa_cookie); - net_dm_hw_report(skb, &hw_metadata); - if (trace_devlink_trap_report_enabled()) { struct devlink_trap_metadata metadata = {}; diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 03aba582c0b9..c14278fd6405 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -34,6 +33,7 @@ #include #include +#include #include @@ -108,6 +108,13 @@ static enum net_dm_alert_mode net_dm_alert_mode = NET_DM_ALERT_MODE_SUMMARY; static u32 net_dm_trunc_len; static u32 net_dm_queue_len = 1000; +struct net_dm_hw_metadata { + const char *trap_group_name; + const char *trap_name; + struct net_device *input_dev; + const struct flow_action_cookie *fa_cookie; +}; + struct net_dm_alert_ops { void (*kfree_skb_probe)(void *ignore, struct sk_buff *skb, void *location); @@ -1129,25 +1136,32 @@ static const struct net_dm_alert_ops *net_dm_alert_ops_arr[] = { [NET_DM_ALERT_MODE_PACKET] = &net_dm_alert_packet_ops, }; -void net_dm_hw_report(struct sk_buff *skb, - const struct net_dm_hw_metadata *hw_metadata) +#if IS_ENABLED(CONFIG_NET_DEVLINK) +static int net_dm_hw_probe_register(const struct net_dm_alert_ops *ops) { - rcu_read_lock(); - - if (!monitor_hw) - goto out; + return register_trace_devlink_trap_report(ops->hw_trap_probe, NULL); +} - net_dm_alert_ops_arr[net_dm_alert_mode]->hw_probe(skb, hw_metadata); +static void net_dm_hw_probe_unregister(const struct net_dm_alert_ops *ops) +{ + unregister_trace_devlink_trap_report(ops->hw_trap_probe, NULL); + tracepoint_synchronize_unregister(); +} +#else +static int net_dm_hw_probe_register(const struct net_dm_alert_ops *ops) +{ + return -EOPNOTSUPP; +} -out: - rcu_read_unlock(); +static void net_dm_hw_probe_unregister(const struct net_dm_alert_ops *ops) +{ } -EXPORT_SYMBOL_GPL(net_dm_hw_report); +#endif static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) { const struct net_dm_alert_ops *ops; - int cpu; + int cpu, rc; if (monitor_hw) { NL_SET_ERR_MSG_MOD(extack, "Hardware monitoring already enabled"); @@ -1171,13 +1185,24 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) kfree(hw_entries); } + rc = net_dm_hw_probe_register(ops); + if (rc) { + NL_SET_ERR_MSG_MOD(extack, "Failed to connect probe to devlink_trap_probe() tracepoint"); + goto err_module_put; + } + monitor_hw = true; return 0; + +err_module_put: + module_put(THIS_MODULE); + return rc; } static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) { + const struct net_dm_alert_ops *ops; int cpu; if (!monitor_hw) { @@ -1185,12 +1210,11 @@ static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) return; } + ops = net_dm_alert_ops_arr[net_dm_alert_mode]; + monitor_hw = false; - /* After this call returns we are guaranteed that no CPU is processing - * any hardware drops. - */ - synchronize_rcu(); + net_dm_hw_probe_unregister(ops); for_each_possible_cpu(cpu) { struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); -- cgit v1.2.3 From 8cd6b020b644d1f1fc2a8768032b8cb8472ab352 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Fri, 2 Oct 2020 15:02:28 +0300 Subject: selftests: ocelot: add some example VCAP IS1, IS2 and ES0 tc offloads Provide an example script which can be used as a skeleton for offloading TCAM rules in the Ocelot switches. Not all actions are demoed, mostly because of difficulty to automate this from a single board. For example, policing. We can set up an iperf3 UDP server and client and measure throughput at destination. But at least with DSA setups, network namespacing the individual ports is not possible because all switch ports are handled by the same DSA master. And we cannot assume that the target platform (an embedded board) has 2 other non-switch generator ports, we need to work with the generator ports as switch ports (this is the reason why mausezahn is used, and not IP traffic like ping). When somebody has an idea how to test policing, that can be added to this test. Signed-off-by: Vladimir Oltean Signed-off-by: David S. Miller --- MAINTAINERS | 1 + .../drivers/net/ocelot/tc_flower_chains.sh | 273 +++++++++++++++++++++ tools/testing/selftests/net/forwarding/lib.sh | 43 ++++ 3 files changed, 317 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/ocelot/tc_flower_chains.sh (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 8f1c9eb6a869..c2038d47e47f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12542,6 +12542,7 @@ F: drivers/net/dsa/ocelot/* F: drivers/net/ethernet/mscc/ F: include/soc/mscc/ocelot* F: net/dsa/tag_ocelot.c +F: tools/testing/selftests/drivers/net/ocelot/* OCXL (Open Coherent Accelerator Processor Interface OpenCAPI) DRIVER M: Frederic Barrat diff --git a/tools/testing/selftests/drivers/net/ocelot/tc_flower_chains.sh b/tools/testing/selftests/drivers/net/ocelot/tc_flower_chains.sh new file mode 100755 index 000000000000..71a538add08a --- /dev/null +++ b/tools/testing/selftests/drivers/net/ocelot/tc_flower_chains.sh @@ -0,0 +1,273 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2020 NXP Semiconductors + +WAIT_TIME=1 +NUM_NETIFS=4 +lib_dir=$(dirname $0)/../../../net/forwarding +source $lib_dir/tc_common.sh +source $lib_dir/lib.sh + +require_command tcpdump + +# +# +---------------------------------------------+ +# | DUT ports Generator ports | +# | +--------+ +--------+ +--------+ +--------+ | +# | | | | | | | | | | +# | | eth0 | | eth1 | | eth2 | | eth3 | | +# | | | | | | | | | | +# +-+--------+-+--------+-+--------+-+--------+-+ +# | | | | +# | | | | +# | +-----------+ | +# | | +# +--------------------------------+ + +eth0=${NETIFS[p1]} +eth1=${NETIFS[p2]} +eth2=${NETIFS[p3]} +eth3=${NETIFS[p4]} + +eth0_mac="de:ad:be:ef:00:00" +eth1_mac="de:ad:be:ef:00:01" +eth2_mac="de:ad:be:ef:00:02" +eth3_mac="de:ad:be:ef:00:03" + +# Helpers to map a VCAP IS1 and VCAP IS2 lookup and policy to a chain number +# used by the kernel driver. The numbers are: +# VCAP IS1 lookup 0: 10000 +# VCAP IS1 lookup 1: 11000 +# VCAP IS1 lookup 2: 12000 +# VCAP IS2 lookup 0 policy 0: 20000 +# VCAP IS2 lookup 0 policy 1: 20001 +# VCAP IS2 lookup 0 policy 255: 20255 +# VCAP IS2 lookup 1 policy 0: 21000 +# VCAP IS2 lookup 1 policy 1: 21001 +# VCAP IS2 lookup 1 policy 255: 21255 +IS1() +{ + local lookup=$1 + + echo $((10000 + 1000 * lookup)) +} + +IS2() +{ + local lookup=$1 + local pag=$2 + + echo $((20000 + 1000 * lookup + pag)) +} + +ES0() +{ + echo 0 +} + +# The Ocelot switches have a fixed ingress pipeline composed of: +# +# +----------------------------------------------+ +-----------------------------------------+ +# | VCAP IS1 | | VCAP IS2 | +# | | | | +# | +----------+ +----------+ +----------+ | | +----------+ +----------+ | +# | | Lookup 0 | | Lookup 1 | | Lookup 2 | | --+------> PAG 0: | Lookup 0 | -> | Lookup 1 | | +# | +----------+ -> +----------+ -> +----------+ | | | +----------+ +----------+ | +# | |key&action| |key&action| |key&action| | | | |key&action| |key&action| | +# | |key&action| |key&action| |key&action| | | | | .. | | .. | | +# | | .. | | .. | | .. | | | | +----------+ +----------+ | +# | +----------+ +----------+ +----------+ | | | | +# | selects PAG | | | +----------+ +----------+ | +# +----------------------------------------------+ +------> PAG 1: | Lookup 0 | -> | Lookup 1 | | +# | | +----------+ +----------+ | +# | | |key&action| |key&action| | +# | | | .. | | .. | | +# | | +----------+ +----------+ | +# | | ... | +# | | | +# | | +----------+ +----------+ | +# +----> PAG 254: | Lookup 0 | -> | Lookup 1 | | +# | | +----------+ +----------+ | +# | | |key&action| |key&action| | +# | | | .. | | .. | | +# | | +----------+ +----------+ | +# | | | +# | | +----------+ +----------+ | +# +----> PAG 255: | Lookup 0 | -> | Lookup 1 | | +# | +----------+ +----------+ | +# | |key&action| |key&action| | +# | | .. | | .. | | +# | +----------+ +----------+ | +# +-----------------------------------------+ +# +# Both the VCAP IS1 (Ingress Stage 1) and IS2 (Ingress Stage 2) are indexed +# (looked up) multiple times: IS1 3 times, and IS2 2 times. Each filter +# (key and action pair) can be configured to only match during the first, or +# second, etc, lookup. +# +# During one TCAM lookup, the filter processing stops at the first entry that +# matches, then the pipeline jumps to the next lookup. +# The driver maps each individual lookup of each individual ingress TCAM to a +# separate chain number. For correct rule offloading, it is mandatory that each +# filter installed in one TCAM is terminated by a non-optional GOTO action to +# the next lookup from the fixed pipeline. +# +# A chain can only be used if there is a GOTO action correctly set up from the +# prior lookup in the processing pipeline. Setting up all chains is not +# mandatory. + +# NOTE: VCAP IS1 currently uses only S1_NORMAL half keys and VCAP IS2 +# dynamically chooses between MAC_ETYPE, ARP, IP4_TCP_UDP, IP4_OTHER, which are +# all half keys as well. + +create_tcam_skeleton() +{ + local eth=$1 + + tc qdisc add dev $eth clsact + + # VCAP IS1 is the Ingress Classification TCAM and can offload the + # following actions: + # - skbedit priority + # - vlan pop + # - vlan modify + # - goto (only in lookup 2, the last IS1 lookup) + tc filter add dev $eth ingress chain 0 pref 49152 flower \ + skip_sw action goto chain $(IS1 0) + tc filter add dev $eth ingress chain $(IS1 0) pref 49152 \ + flower skip_sw action goto chain $(IS1 1) + tc filter add dev $eth ingress chain $(IS1 1) pref 49152 \ + flower skip_sw action goto chain $(IS1 2) + tc filter add dev $eth ingress chain $(IS1 2) pref 49152 \ + flower skip_sw action goto chain $(IS2 0 0) + + # VCAP IS2 is the Security Enforcement ingress TCAM and can offload the + # following actions: + # - trap + # - drop + # - police + # The two VCAP IS2 lookups can be segmented into up to 256 groups of + # rules, called Policies. A Policy is selected through the Policy + # Association Group (PAG) action of VCAP IS1 (which is the + # GOTO offload). + tc filter add dev $eth ingress chain $(IS2 0 0) pref 49152 \ + flower skip_sw action goto chain $(IS2 1 0) +} + +setup_prepare() +{ + create_tcam_skeleton $eth0 + + ip link add br0 type bridge + ip link set $eth0 master br0 + ip link set $eth1 master br0 + ip link set br0 up + + ip link add link $eth3 name $eth3.100 type vlan id 100 + ip link set $eth3.100 up + + tc filter add dev $eth0 ingress chain $(IS1 1) pref 1 \ + protocol 802.1Q flower skip_sw vlan_id 100 \ + action vlan pop \ + action goto chain $(IS1 2) + + tc filter add dev $eth0 egress chain $(ES0) pref 1 \ + flower skip_sw indev $eth1 \ + action vlan push protocol 802.1Q id 100 + + tc filter add dev $eth0 ingress chain $(IS1 0) \ + protocol ipv4 flower skip_sw src_ip 10.1.1.2 \ + action skbedit priority 7 \ + action goto chain $(IS1 1) + + tc filter add dev $eth0 ingress chain $(IS2 0 0) \ + protocol ipv4 flower skip_sw ip_proto udp dst_port 5201 \ + action police rate 50mbit burst 64k \ + action goto chain $(IS2 1 0) +} + +cleanup() +{ + ip link del $eth3.100 + tc qdisc del dev $eth0 clsact + ip link del br0 +} + +test_vlan_pop() +{ + printf "Testing VLAN pop.. " + + tcpdump_start $eth2 + + # Work around Mausezahn VLAN builder bug + # (https://github.com/netsniff-ng/netsniff-ng/issues/225) by using + # an 8021q upper + $MZ $eth3.100 -q -c 1 -p 64 -a $eth3_mac -b $eth2_mac -t ip + + sleep 1 + + tcpdump_stop + + if tcpdump_show | grep -q "$eth3_mac > $eth2_mac, ethertype IPv4"; then + echo "OK" + else + echo "FAIL" + fi + + tcpdump_cleanup +} + +test_vlan_push() +{ + printf "Testing VLAN push.. " + + tcpdump_start $eth3.100 + + $MZ $eth2 -q -c 1 -p 64 -a $eth2_mac -b $eth3_mac -t ip + + sleep 1 + + tcpdump_stop + + if tcpdump_show | grep -q "$eth2_mac > $eth3_mac"; then + echo "OK" + else + echo "FAIL" + fi + + tcpdump_cleanup +} + +test_skbedit_priority() +{ + local num_pkts=100 + + printf "Testing frame prioritization.. " + + before=$(ethtool_stats_get $eth0 'rx_green_prio_7') + + $MZ $eth3 -q -c $num_pkts -p 64 -a $eth3_mac -b $eth2_mac -t ip -A 10.1.1.2 + + after=$(ethtool_stats_get $eth0 'rx_green_prio_7') + + if [ $((after - before)) = $num_pkts ]; then + echo "OK" + else + echo "FAIL" + fi +} + +trap cleanup EXIT + +ALL_TESTS=" + test_vlan_pop + test_vlan_push + test_skbedit_priority +" + +setup_prepare +setup_wait + +tests_run + +exit $EXIT_STATUS diff --git a/tools/testing/selftests/net/forwarding/lib.sh b/tools/testing/selftests/net/forwarding/lib.sh index 977fc2b326a2..927f9ba49e08 100644 --- a/tools/testing/selftests/net/forwarding/lib.sh +++ b/tools/testing/selftests/net/forwarding/lib.sh @@ -1227,3 +1227,46 @@ stop_traffic() # Suppress noise from killing mausezahn. { kill %% && wait %%; } 2>/dev/null } + +tcpdump_start() +{ + local if_name=$1; shift + local ns=$1; shift + + capfile=$(mktemp) + capout=$(mktemp) + + if [ -z $ns ]; then + ns_cmd="" + else + ns_cmd="ip netns exec ${ns}" + fi + + if [ -z $SUDO_USER ] ; then + capuser="" + else + capuser="-Z $SUDO_USER" + fi + + $ns_cmd tcpdump -e -n -Q in -i $if_name \ + -s 65535 -B 32768 $capuser -w $capfile > "$capout" 2>&1 & + cappid=$! + + sleep 1 +} + +tcpdump_stop() +{ + $ns_cmd kill $cappid + sleep 1 +} + +tcpdump_cleanup() +{ + rm $capfile $capout +} + +tcpdump_show() +{ + tcpdump -e -n -r $capfile 2>&1 +} -- cgit v1.2.3 From dca4121cdc48027320c95cbce8f5e81160aa4afe Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Mon, 5 Oct 2020 15:36:48 -0700 Subject: bpf, doc: Update Andrii's email in MAINTAINERS Update Andrii Nakryiko's reviewer email to kernel.org account. This optimizes email logistics on my side and makes it less likely for me to miss important patches. Signed-off-by: Andrii Nakryiko Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/bpf/20201005223648.2437130-1-andrii@kernel.org --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c1e946606dce..fd5d5507d229 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3258,7 +3258,7 @@ M: Daniel Borkmann R: Martin KaFai Lau R: Song Liu R: Yonghong Song -R: Andrii Nakryiko +R: Andrii Nakryiko R: John Fastabend R: KP Singh L: netdev@vger.kernel.org -- cgit v1.2.3 From d61469dc87ad928b16b1edd90fb59e401c3f4565 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Sat, 3 Oct 2020 09:55:00 +0200 Subject: MAINTAINERS: adjust to mcp251xfd file renaming Commit 27cf93863cbc ("MAINTAINERS: Add entry for Microchip MCP25XXFD SPI-CAN network driver"), added the MCP25XXFD SPI-CAN NETWORK DRIVER section with the following two file entries: F: Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.yaml F: drivers/net/can/spi/mcp25xxfd/ Commit 1f0e21a0c065 ("can: mcp251xfd: rename driver files and subdir to mcp251xfd") renamed the files from mcp25xxfd to mcp251xfd, but missed to adjust the MAINTAINERS section. Hence, ./scripts/get_maintainer.pl --self-test=patterns complains: warning: no file matches F: \ Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.yaml warning: no file matches F: drivers/net/can/spi/mcp25xxfd/ Adjust the MCP251XFD SPI-CAN NETWORK DRIVER section to this driver file renaming. Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20201003075500.12477-1-lukas.bulwahn@gmail.com Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 734d4dc61084..d651a0934be7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10675,14 +10675,14 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/hid/hid-mcp2221.c -MCP25XXFD SPI-CAN NETWORK DRIVER +MCP251XFD SPI-CAN NETWORK DRIVER M: Marc Kleine-Budde M: Manivannan Sadhasivam R: Thomas Kopp L: linux-can@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.yaml -F: drivers/net/can/spi/mcp25xxfd/ +F: Documentation/devicetree/bindings/net/can/microchip,mcp251xfd.yaml +F: drivers/net/can/spi/mcp251xfd/ MCP4018 AND MCP4531 MICROCHIP DIGITAL POTENTIOMETER DRIVERS M: Peter Rosin -- cgit v1.2.3 From e057dd3fc20ffb3d7f150af46542a51b59b90127 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Mon, 28 Sep 2020 22:04:04 +0200 Subject: can: add ISO 15765-2:2016 transport protocol CAN Transport Protocols offer support for segmented Point-to-Point communication between CAN nodes via two defined CAN Identifiers. As CAN frames can only transport a small amount of data bytes (max. 8 bytes for 'classic' CAN and max. 64 bytes for CAN FD) this segmentation is needed to transport longer PDUs as needed e.g. for vehicle diagnosis (UDS, ISO 14229) or IP-over-CAN traffic. This protocol driver implements data transfers according to ISO 15765-2:2016 for 'classic' CAN and CAN FD frame types. Signed-off-by: Oliver Hartkopp Link: https://lore.kernel.org/r/20200928200404.82229-1-socketcan@hartkopp.net [mkl: Removed "WITH Linux-syscall-note" from isotp.c. Fixed indention, a checkpatch warning and typos. Replaced __u{8,32} by u{8,32}. Removed always false (optlen < 0) check in isotp_setsockopt().] Signed-off-by: Marc Kleine-Budde --- MAINTAINERS | 1 + include/uapi/linux/can/isotp.h | 166 +++++ net/can/Kconfig | 13 + net/can/Makefile | 3 + net/can/isotp.c | 1426 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1609 insertions(+) create mode 100644 include/uapi/linux/can/isotp.h create mode 100644 net/can/isotp.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index d651a0934be7..7a8a53adba91 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3912,6 +3912,7 @@ F: include/net/netns/can.h F: include/uapi/linux/can.h F: include/uapi/linux/can/bcm.h F: include/uapi/linux/can/gw.h +F: include/uapi/linux/can/isotp.h F: include/uapi/linux/can/raw.h F: net/can/ diff --git a/include/uapi/linux/can/isotp.h b/include/uapi/linux/can/isotp.h new file mode 100644 index 000000000000..553006509f4e --- /dev/null +++ b/include/uapi/linux/can/isotp.h @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* + * linux/can/isotp.h + * + * Definitions for isotp CAN sockets (ISO 15765-2:2016) + * + * Copyright (c) 2020 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + +#ifndef _UAPI_CAN_ISOTP_H +#define _UAPI_CAN_ISOTP_H + +#include +#include + +#define SOL_CAN_ISOTP (SOL_CAN_BASE + CAN_ISOTP) + +/* for socket options affecting the socket (not the global system) */ + +#define CAN_ISOTP_OPTS 1 /* pass struct can_isotp_options */ + +#define CAN_ISOTP_RECV_FC 2 /* pass struct can_isotp_fc_options */ + +/* sockopts to force stmin timer values for protocol regression tests */ + +#define CAN_ISOTP_TX_STMIN 3 /* pass __u32 value in nano secs */ + /* use this time instead of value */ + /* provided in FC from the receiver */ + +#define CAN_ISOTP_RX_STMIN 4 /* pass __u32 value in nano secs */ + /* ignore received CF frames which */ + /* timestamps differ less than val */ + +#define CAN_ISOTP_LL_OPTS 5 /* pass struct can_isotp_ll_options */ + +struct can_isotp_options { + + __u32 flags; /* set flags for isotp behaviour. */ + /* __u32 value : flags see below */ + + __u32 frame_txtime; /* frame transmission time (N_As/N_Ar) */ + /* __u32 value : time in nano secs */ + + __u8 ext_address; /* set address for extended addressing */ + /* __u8 value : extended address */ + + __u8 txpad_content; /* set content of padding byte (tx) */ + /* __u8 value : content on tx path */ + + __u8 rxpad_content; /* set content of padding byte (rx) */ + /* __u8 value : content on rx path */ + + __u8 rx_ext_address; /* set address for extended addressing */ + /* __u8 value : extended address (rx) */ +}; + +struct can_isotp_fc_options { + + __u8 bs; /* blocksize provided in FC frame */ + /* __u8 value : blocksize. 0 = off */ + + __u8 stmin; /* separation time provided in FC frame */ + /* __u8 value : */ + /* 0x00 - 0x7F : 0 - 127 ms */ + /* 0x80 - 0xF0 : reserved */ + /* 0xF1 - 0xF9 : 100 us - 900 us */ + /* 0xFA - 0xFF : reserved */ + + __u8 wftmax; /* max. number of wait frame transmiss. */ + /* __u8 value : 0 = omit FC N_PDU WT */ +}; + +struct can_isotp_ll_options { + + __u8 mtu; /* generated & accepted CAN frame type */ + /* __u8 value : */ + /* CAN_MTU (16) -> standard CAN 2.0 */ + /* CANFD_MTU (72) -> CAN FD frame */ + + __u8 tx_dl; /* tx link layer data length in bytes */ + /* (configured maximum payload length) */ + /* __u8 value : 8,12,16,20,24,32,48,64 */ + /* => rx path supports all LL_DL values */ + + __u8 tx_flags; /* set into struct canfd_frame.flags */ + /* at frame creation: e.g. CANFD_BRS */ + /* Obsolete when the BRS flag is fixed */ + /* by the CAN netdriver configuration */ +}; + +/* flags for isotp behaviour */ + +#define CAN_ISOTP_LISTEN_MODE 0x001 /* listen only (do not send FC) */ +#define CAN_ISOTP_EXTEND_ADDR 0x002 /* enable extended addressing */ +#define CAN_ISOTP_TX_PADDING 0x004 /* enable CAN frame padding tx path */ +#define CAN_ISOTP_RX_PADDING 0x008 /* enable CAN frame padding rx path */ +#define CAN_ISOTP_CHK_PAD_LEN 0x010 /* check received CAN frame padding */ +#define CAN_ISOTP_CHK_PAD_DATA 0x020 /* check received CAN frame padding */ +#define CAN_ISOTP_HALF_DUPLEX 0x040 /* half duplex error state handling */ +#define CAN_ISOTP_FORCE_TXSTMIN 0x080 /* ignore stmin from received FC */ +#define CAN_ISOTP_FORCE_RXSTMIN 0x100 /* ignore CFs depending on rx stmin */ +#define CAN_ISOTP_RX_EXT_ADDR 0x200 /* different rx extended addressing */ +#define CAN_ISOTP_WAIT_TX_DONE 0x400 /* wait for tx completion */ + + +/* default values */ + +#define CAN_ISOTP_DEFAULT_FLAGS 0 +#define CAN_ISOTP_DEFAULT_EXT_ADDRESS 0x00 +#define CAN_ISOTP_DEFAULT_PAD_CONTENT 0xCC /* prevent bit-stuffing */ +#define CAN_ISOTP_DEFAULT_FRAME_TXTIME 0 +#define CAN_ISOTP_DEFAULT_RECV_BS 0 +#define CAN_ISOTP_DEFAULT_RECV_STMIN 0x00 +#define CAN_ISOTP_DEFAULT_RECV_WFTMAX 0 + +#define CAN_ISOTP_DEFAULT_LL_MTU CAN_MTU +#define CAN_ISOTP_DEFAULT_LL_TX_DL CAN_MAX_DLEN +#define CAN_ISOTP_DEFAULT_LL_TX_FLAGS 0 + +/* + * Remark on CAN_ISOTP_DEFAULT_RECV_* values: + * + * We can strongly assume, that the Linux Kernel implementation of + * CAN_ISOTP is capable to run with BS=0, STmin=0 and WFTmax=0. + * But as we like to be able to behave as a commonly available ECU, + * these default settings can be changed via sockopts. + * For that reason the STmin value is intentionally _not_ checked for + * consistency and copied directly into the flow control (FC) frame. + * + */ + +#endif /* !_UAPI_CAN_ISOTP_H */ diff --git a/net/can/Kconfig b/net/can/Kconfig index 25436a715db3..021fe03a8ed6 100644 --- a/net/can/Kconfig +++ b/net/can/Kconfig @@ -55,6 +55,19 @@ config CAN_GW source "net/can/j1939/Kconfig" +config CAN_ISOTP + tristate "ISO 15765-2:2016 CAN transport protocol" + default y + help + CAN Transport Protocols offer support for segmented Point-to-Point + communication between CAN nodes via two defined CAN Identifiers. + As CAN frames can only transport a small amount of data bytes + (max. 8 bytes for 'classic' CAN and max. 64 bytes for CAN FD) this + segmentation is needed to transport longer PDUs as needed e.g. for + vehicle diagnosis (UDS, ISO 14229) or IP-over-CAN traffic. + This protocol driver implements data transfers according to + ISO 15765-2:2016 for 'classic' CAN and CAN FD frame types. + source "drivers/net/can/Kconfig" endif diff --git a/net/can/Makefile b/net/can/Makefile index 08bd217fc051..58f2c31c1ef3 100644 --- a/net/can/Makefile +++ b/net/can/Makefile @@ -17,3 +17,6 @@ obj-$(CONFIG_CAN_GW) += can-gw.o can-gw-y := gw.o obj-$(CONFIG_CAN_J1939) += j1939/ + +obj-$(CONFIG_CAN_ISOTP) += can-isotp.o +can-isotp-y := isotp.o diff --git a/net/can/isotp.c b/net/can/isotp.c new file mode 100644 index 000000000000..e6ff032b5426 --- /dev/null +++ b/net/can/isotp.c @@ -0,0 +1,1426 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +/* isotp.c - ISO 15765-2 CAN transport protocol for protocol family CAN + * + * This implementation does not provide ISO-TP specific return values to the + * userspace. + * + * - RX path timeout of data reception leads to -ETIMEDOUT + * - RX path SN mismatch leads to -EILSEQ + * - RX path data reception with wrong padding leads to -EBADMSG + * - TX path flowcontrol reception timeout leads to -ECOMM + * - TX path flowcontrol reception overflow leads to -EMSGSIZE + * - TX path flowcontrol reception with wrong layout/padding leads to -EBADMSG + * - when a transfer (tx) is on the run the next write() blocks until it's done + * - use CAN_ISOTP_WAIT_TX_DONE flag to block the caller until the PDU is sent + * - as we have static buffers the check whether the PDU fits into the buffer + * is done at FF reception time (no support for sending 'wait frames') + * - take care of the tx-queue-len as traffic shaping is still on the TODO list + * + * Copyright (c) 2020 Volkswagen Group Electronic Research + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Volkswagen nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAN_ISOTP_VERSION "20200928" + +MODULE_DESCRIPTION("PF_CAN isotp 15765-2:2016 protocol"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Oliver Hartkopp "); +MODULE_ALIAS("can-proto-6"); + +#define SINGLE_MASK(id) (((id) & CAN_EFF_FLAG) ? \ + (CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG) : \ + (CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG)) + +/* ISO 15765-2:2016 supports more than 4095 byte per ISO PDU as the FF_DL can + * take full 32 bit values (4 Gbyte). We would need some good concept to handle + * this between user space and kernel space. For now increase the static buffer + * to something about 8 kbyte to be able to test this new functionality. + */ +#define MAX_MSG_LENGTH 8200 + +/* N_PCI type values in bits 7-4 of N_PCI bytes */ +#define N_PCI_SF 0x00 /* single frame */ +#define N_PCI_FF 0x10 /* first frame */ +#define N_PCI_CF 0x20 /* consecutive frame */ +#define N_PCI_FC 0x30 /* flow control */ + +#define N_PCI_SZ 1 /* size of the PCI byte #1 */ +#define SF_PCI_SZ4 1 /* size of SingleFrame PCI including 4 bit SF_DL */ +#define SF_PCI_SZ8 2 /* size of SingleFrame PCI including 8 bit SF_DL */ +#define FF_PCI_SZ12 2 /* size of FirstFrame PCI including 12 bit FF_DL */ +#define FF_PCI_SZ32 6 /* size of FirstFrame PCI including 32 bit FF_DL */ +#define FC_CONTENT_SZ 3 /* flow control content size in byte (FS/BS/STmin) */ + +#define ISOTP_CHECK_PADDING (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA) + +/* Flow Status given in FC frame */ +#define ISOTP_FC_CTS 0 /* clear to send */ +#define ISOTP_FC_WT 1 /* wait */ +#define ISOTP_FC_OVFLW 2 /* overflow */ + +enum { + ISOTP_IDLE = 0, + ISOTP_WAIT_FIRST_FC, + ISOTP_WAIT_FC, + ISOTP_WAIT_DATA, + ISOTP_SENDING +}; + +struct tpcon { + int idx; + int len; + u8 state; + u8 bs; + u8 sn; + u8 ll_dl; + u8 buf[MAX_MSG_LENGTH + 1]; +}; + +struct isotp_sock { + struct sock sk; + int bound; + int ifindex; + canid_t txid; + canid_t rxid; + ktime_t tx_gap; + ktime_t lastrxcf_tstamp; + struct hrtimer rxtimer, txtimer; + struct can_isotp_options opt; + struct can_isotp_fc_options rxfc, txfc; + struct can_isotp_ll_options ll; + u32 force_tx_stmin; + u32 force_rx_stmin; + struct tpcon rx, tx; + struct notifier_block notifier; + wait_queue_head_t wait; +}; + +static inline struct isotp_sock *isotp_sk(const struct sock *sk) +{ + return (struct isotp_sock *)sk; +} + +static enum hrtimer_restart isotp_rx_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + rxtimer); + struct sock *sk = &so->sk; + + if (so->rx.state == ISOTP_WAIT_DATA) { + /* we did not get new data frames in time */ + + /* report 'connection timed out' */ + sk->sk_err = ETIMEDOUT; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + + /* reset rx state */ + so->rx.state = ISOTP_IDLE; + } + + return HRTIMER_NORESTART; +} + +static int isotp_send_fc(struct sock *sk, int ae, u8 flowstatus) +{ + struct net_device *dev; + struct sk_buff *nskb; + struct canfd_frame *ncf; + struct isotp_sock *so = isotp_sk(sk); + int can_send_ret; + + nskb = alloc_skb(so->ll.mtu + sizeof(struct can_skb_priv), gfp_any()); + if (!nskb) + return 1; + + dev = dev_get_by_index(sock_net(sk), so->ifindex); + if (!dev) { + kfree_skb(nskb); + return 1; + } + + can_skb_reserve(nskb); + can_skb_prv(nskb)->ifindex = dev->ifindex; + can_skb_prv(nskb)->skbcnt = 0; + + nskb->dev = dev; + can_skb_set_owner(nskb, sk); + ncf = (struct canfd_frame *)nskb->data; + skb_put(nskb, so->ll.mtu); + + /* create & send flow control reply */ + ncf->can_id = so->txid; + + if (so->opt.flags & CAN_ISOTP_TX_PADDING) { + memset(ncf->data, so->opt.txpad_content, CAN_MAX_DLEN); + ncf->len = CAN_MAX_DLEN; + } else { + ncf->len = ae + FC_CONTENT_SZ; + } + + ncf->data[ae] = N_PCI_FC | flowstatus; + ncf->data[ae + 1] = so->rxfc.bs; + ncf->data[ae + 2] = so->rxfc.stmin; + + if (ae) + ncf->data[0] = so->opt.ext_address; + + if (so->ll.mtu == CANFD_MTU) + ncf->flags = so->ll.tx_flags; + + can_send_ret = can_send(nskb, 1); + if (can_send_ret) + printk_once(KERN_NOTICE "can-isotp: %s: can_send_ret %d\n", + __func__, can_send_ret); + + dev_put(dev); + + /* reset blocksize counter */ + so->rx.bs = 0; + + /* reset last CF frame rx timestamp for rx stmin enforcement */ + so->lastrxcf_tstamp = ktime_set(0, 0); + + /* start rx timeout watchdog */ + hrtimer_start(&so->rxtimer, ktime_set(1, 0), HRTIMER_MODE_REL_SOFT); + return 0; +} + +static void isotp_rcv_skb(struct sk_buff *skb, struct sock *sk) +{ + struct sockaddr_can *addr = (struct sockaddr_can *)skb->cb; + + BUILD_BUG_ON(sizeof(skb->cb) < sizeof(struct sockaddr_can)); + + memset(addr, 0, sizeof(*addr)); + addr->can_family = AF_CAN; + addr->can_ifindex = skb->dev->ifindex; + + if (sock_queue_rcv_skb(sk, skb) < 0) + kfree_skb(skb); +} + +static u8 padlen(u8 datalen) +{ + const u8 plen[] = {8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0 - 8 */ + 12, 12, 12, 12, /* 9 - 12 */ + 16, 16, 16, 16, /* 13 - 16 */ + 20, 20, 20, 20, /* 17 - 20 */ + 24, 24, 24, 24, /* 21 - 24 */ + 32, 32, 32, 32, 32, 32, 32, 32, /* 25 - 32 */ + 48, 48, 48, 48, 48, 48, 48, 48, /* 33 - 40 */ + 48, 48, 48, 48, 48, 48, 48, 48}; /* 41 - 48 */ + + if (datalen > 48) + return 64; + + return plen[datalen]; +} + +/* check for length optimization and return 1/true when the check fails */ +static int check_optimized(struct canfd_frame *cf, int start_index) +{ + /* for CAN_DL <= 8 the start_index is equal to the CAN_DL as the + * padding would start at this point. E.g. if the padding would + * start at cf.data[7] cf->len has to be 7 to be optimal. + * Note: The data[] index starts with zero. + */ + if (cf->len <= CAN_MAX_DLEN) + return (cf->len != start_index); + + /* This relation is also valid in the non-linear DLC range, where + * we need to take care of the minimal next possible CAN_DL. + * The correct check would be (padlen(cf->len) != padlen(start_index)). + * But as cf->len can only take discrete values from 12, .., 64 at this + * point the padlen(cf->len) is always equal to cf->len. + */ + return (cf->len != padlen(start_index)); +} + +/* check padding and return 1/true when the check fails */ +static int check_pad(struct isotp_sock *so, struct canfd_frame *cf, + int start_index, u8 content) +{ + int i; + + /* no RX_PADDING value => check length of optimized frame length */ + if (!(so->opt.flags & CAN_ISOTP_RX_PADDING)) { + if (so->opt.flags & CAN_ISOTP_CHK_PAD_LEN) + return check_optimized(cf, start_index); + + /* no valid test against empty value => ignore frame */ + return 1; + } + + /* check datalength of correctly padded CAN frame */ + if ((so->opt.flags & CAN_ISOTP_CHK_PAD_LEN) && + cf->len != padlen(cf->len)) + return 1; + + /* check padding content */ + if (so->opt.flags & CAN_ISOTP_CHK_PAD_DATA) { + for (i = start_index; i < cf->len; i++) + if (cf->data[i] != content) + return 1; + } + return 0; +} + +static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) +{ + struct sock *sk = &so->sk; + + if (so->tx.state != ISOTP_WAIT_FC && + so->tx.state != ISOTP_WAIT_FIRST_FC) + return 0; + + hrtimer_cancel(&so->txtimer); + + if ((cf->len < ae + FC_CONTENT_SZ) || + ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { + /* malformed PDU - report 'not a data message' */ + sk->sk_err = EBADMSG; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + return 1; + } + + /* get communication parameters only from the first FC frame */ + if (so->tx.state == ISOTP_WAIT_FIRST_FC) { + so->txfc.bs = cf->data[ae + 1]; + so->txfc.stmin = cf->data[ae + 2]; + + /* fix wrong STmin values according spec */ + if (so->txfc.stmin > 0x7F && + (so->txfc.stmin < 0xF1 || so->txfc.stmin > 0xF9)) + so->txfc.stmin = 0x7F; + + so->tx_gap = ktime_set(0, 0); + /* add transmission time for CAN frame N_As */ + so->tx_gap = ktime_add_ns(so->tx_gap, so->opt.frame_txtime); + /* add waiting time for consecutive frames N_Cs */ + if (so->opt.flags & CAN_ISOTP_FORCE_TXSTMIN) + so->tx_gap = ktime_add_ns(so->tx_gap, + so->force_tx_stmin); + else if (so->txfc.stmin < 0x80) + so->tx_gap = ktime_add_ns(so->tx_gap, + so->txfc.stmin * 1000000); + else + so->tx_gap = ktime_add_ns(so->tx_gap, + (so->txfc.stmin - 0xF0) + * 100000); + so->tx.state = ISOTP_WAIT_FC; + } + + switch (cf->data[ae] & 0x0F) { + case ISOTP_FC_CTS: + so->tx.bs = 0; + so->tx.state = ISOTP_SENDING; + /* start cyclic timer for sending CF frame */ + hrtimer_start(&so->txtimer, so->tx_gap, + HRTIMER_MODE_REL_SOFT); + break; + + case ISOTP_FC_WT: + /* start timer to wait for next FC frame */ + hrtimer_start(&so->txtimer, ktime_set(1, 0), + HRTIMER_MODE_REL_SOFT); + break; + + case ISOTP_FC_OVFLW: + /* overflow on receiver side - report 'message too long' */ + sk->sk_err = EMSGSIZE; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + fallthrough; + + default: + /* stop this tx job */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + } + return 0; +} + +static int isotp_rcv_sf(struct sock *sk, struct canfd_frame *cf, int pcilen, + struct sk_buff *skb, int len) +{ + struct isotp_sock *so = isotp_sk(sk); + struct sk_buff *nskb; + + hrtimer_cancel(&so->rxtimer); + so->rx.state = ISOTP_IDLE; + + if (!len || len > cf->len - pcilen) + return 1; + + if ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, pcilen + len, so->opt.rxpad_content)) { + /* malformed PDU - report 'not a data message' */ + sk->sk_err = EBADMSG; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + return 1; + } + + nskb = alloc_skb(len, gfp_any()); + if (!nskb) + return 1; + + memcpy(skb_put(nskb, len), &cf->data[pcilen], len); + + nskb->tstamp = skb->tstamp; + nskb->dev = skb->dev; + isotp_rcv_skb(nskb, sk); + return 0; +} + +static int isotp_rcv_ff(struct sock *sk, struct canfd_frame *cf, int ae) +{ + struct isotp_sock *so = isotp_sk(sk); + int i; + int off; + int ff_pci_sz; + + hrtimer_cancel(&so->rxtimer); + so->rx.state = ISOTP_IDLE; + + /* get the used sender LL_DL from the (first) CAN frame data length */ + so->rx.ll_dl = padlen(cf->len); + + /* the first frame has to use the entire frame up to LL_DL length */ + if (cf->len != so->rx.ll_dl) + return 1; + + /* get the FF_DL */ + so->rx.len = (cf->data[ae] & 0x0F) << 8; + so->rx.len += cf->data[ae + 1]; + + /* Check for FF_DL escape sequence supporting 32 bit PDU length */ + if (so->rx.len) { + ff_pci_sz = FF_PCI_SZ12; + } else { + /* FF_DL = 0 => get real length from next 4 bytes */ + so->rx.len = cf->data[ae + 2] << 24; + so->rx.len += cf->data[ae + 3] << 16; + so->rx.len += cf->data[ae + 4] << 8; + so->rx.len += cf->data[ae + 5]; + ff_pci_sz = FF_PCI_SZ32; + } + + /* take care of a potential SF_DL ESC offset for TX_DL > 8 */ + off = (so->rx.ll_dl > CAN_MAX_DLEN) ? 1 : 0; + + if (so->rx.len + ae + off + ff_pci_sz < so->rx.ll_dl) + return 1; + + if (so->rx.len > MAX_MSG_LENGTH) { + /* send FC frame with overflow status */ + isotp_send_fc(sk, ae, ISOTP_FC_OVFLW); + return 1; + } + + /* copy the first received data bytes */ + so->rx.idx = 0; + for (i = ae + ff_pci_sz; i < so->rx.ll_dl; i++) + so->rx.buf[so->rx.idx++] = cf->data[i]; + + /* initial setup for this pdu reception */ + so->rx.sn = 1; + so->rx.state = ISOTP_WAIT_DATA; + + /* no creation of flow control frames */ + if (so->opt.flags & CAN_ISOTP_LISTEN_MODE) + return 0; + + /* send our first FC frame */ + isotp_send_fc(sk, ae, ISOTP_FC_CTS); + return 0; +} + +static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, + struct sk_buff *skb) +{ + struct isotp_sock *so = isotp_sk(sk); + struct sk_buff *nskb; + int i; + + if (so->rx.state != ISOTP_WAIT_DATA) + return 0; + + /* drop if timestamp gap is less than force_rx_stmin nano secs */ + if (so->opt.flags & CAN_ISOTP_FORCE_RXSTMIN) { + if (ktime_to_ns(ktime_sub(skb->tstamp, so->lastrxcf_tstamp)) < + so->force_rx_stmin) + return 0; + + so->lastrxcf_tstamp = skb->tstamp; + } + + hrtimer_cancel(&so->rxtimer); + + /* CFs are never longer than the FF */ + if (cf->len > so->rx.ll_dl) + return 1; + + /* CFs have usually the LL_DL length */ + if (cf->len < so->rx.ll_dl) { + /* this is only allowed for the last CF */ + if (so->rx.len - so->rx.idx > so->rx.ll_dl - ae - N_PCI_SZ) + return 1; + } + + if ((cf->data[ae] & 0x0F) != so->rx.sn) { + /* wrong sn detected - report 'illegal byte sequence' */ + sk->sk_err = EILSEQ; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + + /* reset rx state */ + so->rx.state = ISOTP_IDLE; + return 1; + } + so->rx.sn++; + so->rx.sn %= 16; + + for (i = ae + N_PCI_SZ; i < cf->len; i++) { + so->rx.buf[so->rx.idx++] = cf->data[i]; + if (so->rx.idx >= so->rx.len) + break; + } + + if (so->rx.idx >= so->rx.len) { + /* we are done */ + so->rx.state = ISOTP_IDLE; + + if ((so->opt.flags & ISOTP_CHECK_PADDING) && + check_pad(so, cf, i + 1, so->opt.rxpad_content)) { + /* malformed PDU - report 'not a data message' */ + sk->sk_err = EBADMSG; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + return 1; + } + + nskb = alloc_skb(so->rx.len, gfp_any()); + if (!nskb) + return 1; + + memcpy(skb_put(nskb, so->rx.len), so->rx.buf, + so->rx.len); + + nskb->tstamp = skb->tstamp; + nskb->dev = skb->dev; + isotp_rcv_skb(nskb, sk); + return 0; + } + + /* no creation of flow control frames */ + if (so->opt.flags & CAN_ISOTP_LISTEN_MODE) + return 0; + + /* perform blocksize handling, if enabled */ + if (!so->rxfc.bs || ++so->rx.bs < so->rxfc.bs) { + /* start rx timeout watchdog */ + hrtimer_start(&so->rxtimer, ktime_set(1, 0), + HRTIMER_MODE_REL_SOFT); + return 0; + } + + /* we reached the specified blocksize so->rxfc.bs */ + isotp_send_fc(sk, ae, ISOTP_FC_CTS); + return 0; +} + +static void isotp_rcv(struct sk_buff *skb, void *data) +{ + struct sock *sk = (struct sock *)data; + struct isotp_sock *so = isotp_sk(sk); + struct canfd_frame *cf; + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + u8 n_pci_type, sf_dl; + + /* Strictly receive only frames with the configured MTU size + * => clear separation of CAN2.0 / CAN FD transport channels + */ + if (skb->len != so->ll.mtu) + return; + + cf = (struct canfd_frame *)skb->data; + + /* if enabled: check reception of my configured extended address */ + if (ae && cf->data[0] != so->opt.rx_ext_address) + return; + + n_pci_type = cf->data[ae] & 0xF0; + + if (so->opt.flags & CAN_ISOTP_HALF_DUPLEX) { + /* check rx/tx path half duplex expectations */ + if ((so->tx.state != ISOTP_IDLE && n_pci_type != N_PCI_FC) || + (so->rx.state != ISOTP_IDLE && n_pci_type == N_PCI_FC)) + return; + } + + switch (n_pci_type) { + case N_PCI_FC: + /* tx path: flow control frame containing the FC parameters */ + isotp_rcv_fc(so, cf, ae); + break; + + case N_PCI_SF: + /* rx path: single frame + * + * As we do not have a rx.ll_dl configuration, we can only test + * if the CAN frames payload length matches the LL_DL == 8 + * requirements - no matter if it's CAN 2.0 or CAN FD + */ + + /* get the SF_DL from the N_PCI byte */ + sf_dl = cf->data[ae] & 0x0F; + + if (cf->len <= CAN_MAX_DLEN) { + isotp_rcv_sf(sk, cf, SF_PCI_SZ4 + ae, skb, sf_dl); + } else { + if (skb->len == CANFD_MTU) { + /* We have a CAN FD frame and CAN_DL is greater than 8: + * Only frames with the SF_DL == 0 ESC value are valid. + * + * If so take care of the increased SF PCI size + * (SF_PCI_SZ8) to point to the message content behind + * the extended SF PCI info and get the real SF_DL + * length value from the formerly first data byte. + */ + if (sf_dl == 0) + isotp_rcv_sf(sk, cf, SF_PCI_SZ8 + ae, skb, + cf->data[SF_PCI_SZ4 + ae]); + } + } + break; + + case N_PCI_FF: + /* rx path: first frame */ + isotp_rcv_ff(sk, cf, ae); + break; + + case N_PCI_CF: + /* rx path: consecutive frame */ + isotp_rcv_cf(sk, cf, ae, skb); + break; + } +} + +static void isotp_fill_dataframe(struct canfd_frame *cf, struct isotp_sock *so, + int ae, int off) +{ + int pcilen = N_PCI_SZ + ae + off; + int space = so->tx.ll_dl - pcilen; + int num = min_t(int, so->tx.len - so->tx.idx, space); + int i; + + cf->can_id = so->txid; + cf->len = num + pcilen; + + if (num < space) { + if (so->opt.flags & CAN_ISOTP_TX_PADDING) { + /* user requested padding */ + cf->len = padlen(cf->len); + memset(cf->data, so->opt.txpad_content, cf->len); + } else if (cf->len > CAN_MAX_DLEN) { + /* mandatory padding for CAN FD frames */ + cf->len = padlen(cf->len); + memset(cf->data, CAN_ISOTP_DEFAULT_PAD_CONTENT, + cf->len); + } + } + + for (i = 0; i < num; i++) + cf->data[pcilen + i] = so->tx.buf[so->tx.idx++]; + + if (ae) + cf->data[0] = so->opt.ext_address; +} + +static void isotp_create_fframe(struct canfd_frame *cf, struct isotp_sock *so, + int ae) +{ + int i; + int ff_pci_sz; + + cf->can_id = so->txid; + cf->len = so->tx.ll_dl; + if (ae) + cf->data[0] = so->opt.ext_address; + + /* create N_PCI bytes with 12/32 bit FF_DL data length */ + if (so->tx.len > 4095) { + /* use 32 bit FF_DL notation */ + cf->data[ae] = N_PCI_FF; + cf->data[ae + 1] = 0; + cf->data[ae + 2] = (u8)(so->tx.len >> 24) & 0xFFU; + cf->data[ae + 3] = (u8)(so->tx.len >> 16) & 0xFFU; + cf->data[ae + 4] = (u8)(so->tx.len >> 8) & 0xFFU; + cf->data[ae + 5] = (u8)so->tx.len & 0xFFU; + ff_pci_sz = FF_PCI_SZ32; + } else { + /* use 12 bit FF_DL notation */ + cf->data[ae] = (u8)(so->tx.len >> 8) | N_PCI_FF; + cf->data[ae + 1] = (u8)so->tx.len & 0xFFU; + ff_pci_sz = FF_PCI_SZ12; + } + + /* add first data bytes depending on ae */ + for (i = ae + ff_pci_sz; i < so->tx.ll_dl; i++) + cf->data[i] = so->tx.buf[so->tx.idx++]; + + so->tx.sn = 1; + so->tx.state = ISOTP_WAIT_FIRST_FC; +} + +static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txtimer); + struct sock *sk = &so->sk; + struct sk_buff *skb; + struct net_device *dev; + struct canfd_frame *cf; + enum hrtimer_restart restart = HRTIMER_NORESTART; + int can_send_ret; + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + + switch (so->tx.state) { + case ISOTP_WAIT_FC: + case ISOTP_WAIT_FIRST_FC: + + /* we did not get any flow control frame in time */ + + /* report 'communication error on send' */ + sk->sk_err = ECOMM; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + + /* reset tx state */ + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + break; + + case ISOTP_SENDING: + + /* push out the next segmented pdu */ + dev = dev_get_by_index(sock_net(sk), so->ifindex); + if (!dev) + break; + +isotp_tx_burst: + skb = alloc_skb(so->ll.mtu + sizeof(struct can_skb_priv), + gfp_any()); + if (!skb) { + dev_put(dev); + break; + } + + can_skb_reserve(skb); + can_skb_prv(skb)->ifindex = dev->ifindex; + can_skb_prv(skb)->skbcnt = 0; + + cf = (struct canfd_frame *)skb->data; + skb_put(skb, so->ll.mtu); + + /* create consecutive frame */ + isotp_fill_dataframe(cf, so, ae, 0); + + /* place consecutive frame N_PCI in appropriate index */ + cf->data[ae] = N_PCI_CF | so->tx.sn++; + so->tx.sn %= 16; + so->tx.bs++; + + if (so->ll.mtu == CANFD_MTU) + cf->flags = so->ll.tx_flags; + + skb->dev = dev; + can_skb_set_owner(skb, sk); + + can_send_ret = can_send(skb, 1); + if (can_send_ret) + printk_once(KERN_NOTICE "can-isotp: %s: can_send_ret %d\n", + __func__, can_send_ret); + + if (so->tx.idx >= so->tx.len) { + /* we are done */ + so->tx.state = ISOTP_IDLE; + dev_put(dev); + wake_up_interruptible(&so->wait); + break; + } + + if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { + /* stop and wait for FC */ + so->tx.state = ISOTP_WAIT_FC; + dev_put(dev); + hrtimer_set_expires(&so->txtimer, + ktime_add(ktime_get(), + ktime_set(1, 0))); + restart = HRTIMER_RESTART; + break; + } + + /* no gap between data frames needed => use burst mode */ + if (!so->tx_gap) + goto isotp_tx_burst; + + /* start timer to send next data frame with correct delay */ + dev_put(dev); + hrtimer_set_expires(&so->txtimer, + ktime_add(ktime_get(), so->tx_gap)); + restart = HRTIMER_RESTART; + break; + + default: + WARN_ON_ONCE(1); + } + + return restart; +} + +static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) +{ + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + struct sk_buff *skb; + struct net_device *dev; + struct canfd_frame *cf; + int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; + int off; + int err; + + if (!so->bound) + return -EADDRNOTAVAIL; + + /* we do not support multiple buffers - for now */ + if (so->tx.state != ISOTP_IDLE || wq_has_sleeper(&so->wait)) { + if (msg->msg_flags & MSG_DONTWAIT) + return -EAGAIN; + + /* wait for complete transmission of current pdu */ + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + } + + if (!size || size > MAX_MSG_LENGTH) + return -EINVAL; + + err = memcpy_from_msg(so->tx.buf, msg, size); + if (err < 0) + return err; + + dev = dev_get_by_index(sock_net(sk), so->ifindex); + if (!dev) + return -ENXIO; + + skb = sock_alloc_send_skb(sk, so->ll.mtu + sizeof(struct can_skb_priv), + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) { + dev_put(dev); + return err; + } + + can_skb_reserve(skb); + can_skb_prv(skb)->ifindex = dev->ifindex; + can_skb_prv(skb)->skbcnt = 0; + + so->tx.state = ISOTP_SENDING; + so->tx.len = size; + so->tx.idx = 0; + + cf = (struct canfd_frame *)skb->data; + skb_put(skb, so->ll.mtu); + + /* take care of a potential SF_DL ESC offset for TX_DL > 8 */ + off = (so->tx.ll_dl > CAN_MAX_DLEN) ? 1 : 0; + + /* check for single frame transmission depending on TX_DL */ + if (size <= so->tx.ll_dl - SF_PCI_SZ4 - ae - off) { + /* The message size generally fits into a SingleFrame - good. + * + * SF_DL ESC offset optimization: + * + * When TX_DL is greater 8 but the message would still fit + * into a 8 byte CAN frame, we can omit the offset. + * This prevents a protocol caused length extension from + * CAN_DL = 8 to CAN_DL = 12 due to the SF_SL ESC handling. + */ + if (size <= CAN_MAX_DLEN - SF_PCI_SZ4 - ae) + off = 0; + + isotp_fill_dataframe(cf, so, ae, off); + + /* place single frame N_PCI w/o length in appropriate index */ + cf->data[ae] = N_PCI_SF; + + /* place SF_DL size value depending on the SF_DL ESC offset */ + if (off) + cf->data[SF_PCI_SZ4 + ae] = size; + else + cf->data[ae] |= size; + + so->tx.state = ISOTP_IDLE; + wake_up_interruptible(&so->wait); + + /* don't enable wait queue for a single frame transmission */ + wait_tx_done = 0; + } else { + /* send first frame and wait for FC */ + + isotp_create_fframe(cf, so, ae); + + /* start timeout for FC */ + hrtimer_start(&so->txtimer, ktime_set(1, 0), HRTIMER_MODE_REL_SOFT); + } + + /* send the first or only CAN frame */ + if (so->ll.mtu == CANFD_MTU) + cf->flags = so->ll.tx_flags; + + skb->dev = dev; + skb->sk = sk; + err = can_send(skb, 1); + dev_put(dev); + if (err) { + printk_once(KERN_NOTICE "can-isotp: %s: can_send_ret %d\n", + __func__, err); + return err; + } + + if (wait_tx_done) { + /* wait for complete transmission of current pdu */ + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + } + + return size; +} + +static int isotp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, + int flags) +{ + struct sock *sk = sock->sk; + struct sk_buff *skb; + int err = 0; + int noblock; + + noblock = flags & MSG_DONTWAIT; + flags &= ~MSG_DONTWAIT; + + skb = skb_recv_datagram(sk, flags, noblock, &err); + if (!skb) + return err; + + if (size < skb->len) + msg->msg_flags |= MSG_TRUNC; + else + size = skb->len; + + err = memcpy_to_msg(msg, skb->data, size); + if (err < 0) { + skb_free_datagram(sk, skb); + return err; + } + + sock_recv_timestamp(msg, sk, skb); + + if (msg->msg_name) { + msg->msg_namelen = sizeof(struct sockaddr_can); + memcpy(msg->msg_name, skb->cb, msg->msg_namelen); + } + + skb_free_datagram(sk, skb); + + return size; +} + +static int isotp_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + struct isotp_sock *so; + struct net *net; + + if (!sk) + return 0; + + so = isotp_sk(sk); + net = sock_net(sk); + + /* wait for complete transmission of current pdu */ + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + + unregister_netdevice_notifier(&so->notifier); + + lock_sock(sk); + + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->rxtimer); + + /* remove current filters & unregister */ + if (so->bound) { + if (so->ifindex) { + struct net_device *dev; + + dev = dev_get_by_index(net, so->ifindex); + if (dev) { + can_rx_unregister(net, dev, so->rxid, + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + dev_put(dev); + } + } + } + + so->ifindex = 0; + so->bound = 0; + + sock_orphan(sk); + sock->sk = NULL; + + release_sock(sk); + sock_put(sk); + + return 0; +} + +static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) +{ + struct sockaddr_can *addr = (struct sockaddr_can *)uaddr; + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + struct net *net = sock_net(sk); + int ifindex; + struct net_device *dev; + int err = 0; + int notify_enetdown = 0; + + if (len < CAN_REQUIRED_SIZE(struct sockaddr_can, can_addr.tp)) + return -EINVAL; + + if (addr->can_addr.tp.rx_id == addr->can_addr.tp.tx_id) + return -EADDRNOTAVAIL; + + if ((addr->can_addr.tp.rx_id | addr->can_addr.tp.tx_id) & + (CAN_ERR_FLAG | CAN_RTR_FLAG)) + return -EADDRNOTAVAIL; + + if (!addr->can_ifindex) + return -ENODEV; + + lock_sock(sk); + + if (so->bound && addr->can_ifindex == so->ifindex && + addr->can_addr.tp.rx_id == so->rxid && + addr->can_addr.tp.tx_id == so->txid) + goto out; + + dev = dev_get_by_index(net, addr->can_ifindex); + if (!dev) { + err = -ENODEV; + goto out; + } + if (dev->type != ARPHRD_CAN) { + dev_put(dev); + err = -ENODEV; + goto out; + } + if (dev->mtu < so->ll.mtu) { + dev_put(dev); + err = -EINVAL; + goto out; + } + if (!(dev->flags & IFF_UP)) + notify_enetdown = 1; + + ifindex = dev->ifindex; + + can_rx_register(net, dev, addr->can_addr.tp.rx_id, + SINGLE_MASK(addr->can_addr.tp.rx_id), isotp_rcv, sk, + "isotp", sk); + + dev_put(dev); + + if (so->bound) { + /* unregister old filter */ + if (so->ifindex) { + dev = dev_get_by_index(net, so->ifindex); + if (dev) { + can_rx_unregister(net, dev, so->rxid, + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + dev_put(dev); + } + } + } + + /* switch to new settings */ + so->ifindex = ifindex; + so->rxid = addr->can_addr.tp.rx_id; + so->txid = addr->can_addr.tp.tx_id; + so->bound = 1; + +out: + release_sock(sk); + + if (notify_enetdown) { + sk->sk_err = ENETDOWN; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + } + + return err; +} + +static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, int peer) +{ + struct sockaddr_can *addr = (struct sockaddr_can *)uaddr; + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + + if (peer) + return -EOPNOTSUPP; + + addr->can_family = AF_CAN; + addr->can_ifindex = so->ifindex; + addr->can_addr.tp.rx_id = so->rxid; + addr->can_addr.tp.tx_id = so->txid; + + return sizeof(*addr); +} + +static int isotp_setsockopt(struct socket *sock, int level, int optname, + sockptr_t optval, unsigned int optlen) +{ + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + int ret = 0; + + if (level != SOL_CAN_ISOTP) + return -EINVAL; + + switch (optname) { + case CAN_ISOTP_OPTS: + if (optlen != sizeof(struct can_isotp_options)) + return -EINVAL; + + if (copy_from_sockptr(&so->opt, optval, optlen)) + return -EFAULT; + + /* no separate rx_ext_address is given => use ext_address */ + if (!(so->opt.flags & CAN_ISOTP_RX_EXT_ADDR)) + so->opt.rx_ext_address = so->opt.ext_address; + break; + + case CAN_ISOTP_RECV_FC: + if (optlen != sizeof(struct can_isotp_fc_options)) + return -EINVAL; + + if (copy_from_sockptr(&so->rxfc, optval, optlen)) + return -EFAULT; + break; + + case CAN_ISOTP_TX_STMIN: + if (optlen != sizeof(u32)) + return -EINVAL; + + if (copy_from_sockptr(&so->force_tx_stmin, optval, optlen)) + return -EFAULT; + break; + + case CAN_ISOTP_RX_STMIN: + if (optlen != sizeof(u32)) + return -EINVAL; + + if (copy_from_sockptr(&so->force_rx_stmin, optval, optlen)) + return -EFAULT; + break; + + case CAN_ISOTP_LL_OPTS: + if (optlen == sizeof(struct can_isotp_ll_options)) { + struct can_isotp_ll_options ll; + + if (copy_from_sockptr(&ll, optval, optlen)) + return -EFAULT; + + /* check for correct ISO 11898-1 DLC data length */ + if (ll.tx_dl != padlen(ll.tx_dl)) + return -EINVAL; + + if (ll.mtu != CAN_MTU && ll.mtu != CANFD_MTU) + return -EINVAL; + + if (ll.mtu == CAN_MTU && ll.tx_dl > CAN_MAX_DLEN) + return -EINVAL; + + memcpy(&so->ll, &ll, sizeof(ll)); + + /* set ll_dl for tx path to similar place as for rx */ + so->tx.ll_dl = ll.tx_dl; + } else { + return -EINVAL; + } + break; + + default: + ret = -ENOPROTOOPT; + } + + return ret; +} + +static int isotp_getsockopt(struct socket *sock, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + struct isotp_sock *so = isotp_sk(sk); + int len; + void *val; + + if (level != SOL_CAN_ISOTP) + return -EINVAL; + if (get_user(len, optlen)) + return -EFAULT; + if (len < 0) + return -EINVAL; + + switch (optname) { + case CAN_ISOTP_OPTS: + len = min_t(int, len, sizeof(struct can_isotp_options)); + val = &so->opt; + break; + + case CAN_ISOTP_RECV_FC: + len = min_t(int, len, sizeof(struct can_isotp_fc_options)); + val = &so->rxfc; + break; + + case CAN_ISOTP_TX_STMIN: + len = min_t(int, len, sizeof(u32)); + val = &so->force_tx_stmin; + break; + + case CAN_ISOTP_RX_STMIN: + len = min_t(int, len, sizeof(u32)); + val = &so->force_rx_stmin; + break; + + case CAN_ISOTP_LL_OPTS: + len = min_t(int, len, sizeof(struct can_isotp_ll_options)); + val = &so->ll; + break; + + default: + return -ENOPROTOOPT; + } + + if (put_user(len, optlen)) + return -EFAULT; + if (copy_to_user(optval, val, len)) + return -EFAULT; + return 0; +} + +static int isotp_notifier(struct notifier_block *nb, unsigned long msg, + void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct isotp_sock *so = container_of(nb, struct isotp_sock, notifier); + struct sock *sk = &so->sk; + + if (!net_eq(dev_net(dev), sock_net(sk))) + return NOTIFY_DONE; + + if (dev->type != ARPHRD_CAN) + return NOTIFY_DONE; + + if (so->ifindex != dev->ifindex) + return NOTIFY_DONE; + + switch (msg) { + case NETDEV_UNREGISTER: + lock_sock(sk); + /* remove current filters & unregister */ + if (so->bound) + can_rx_unregister(dev_net(dev), dev, so->rxid, + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + + so->ifindex = 0; + so->bound = 0; + release_sock(sk); + + sk->sk_err = ENODEV; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + break; + + case NETDEV_DOWN: + sk->sk_err = ENETDOWN; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_error_report(sk); + break; + } + + return NOTIFY_DONE; +} + +static int isotp_init(struct sock *sk) +{ + struct isotp_sock *so = isotp_sk(sk); + + so->ifindex = 0; + so->bound = 0; + + so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS; + so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; + so->opt.rx_ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; + so->opt.rxpad_content = CAN_ISOTP_DEFAULT_PAD_CONTENT; + so->opt.txpad_content = CAN_ISOTP_DEFAULT_PAD_CONTENT; + so->opt.frame_txtime = CAN_ISOTP_DEFAULT_FRAME_TXTIME; + so->rxfc.bs = CAN_ISOTP_DEFAULT_RECV_BS; + so->rxfc.stmin = CAN_ISOTP_DEFAULT_RECV_STMIN; + so->rxfc.wftmax = CAN_ISOTP_DEFAULT_RECV_WFTMAX; + so->ll.mtu = CAN_ISOTP_DEFAULT_LL_MTU; + so->ll.tx_dl = CAN_ISOTP_DEFAULT_LL_TX_DL; + so->ll.tx_flags = CAN_ISOTP_DEFAULT_LL_TX_FLAGS; + + /* set ll_dl for tx path to similar place as for rx */ + so->tx.ll_dl = so->ll.tx_dl; + + so->rx.state = ISOTP_IDLE; + so->tx.state = ISOTP_IDLE; + + hrtimer_init(&so->rxtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->rxtimer.function = isotp_rx_timer_handler; + hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + so->txtimer.function = isotp_tx_timer_handler; + + init_waitqueue_head(&so->wait); + + so->notifier.notifier_call = isotp_notifier; + register_netdevice_notifier(&so->notifier); + + return 0; +} + +static int isotp_sock_no_ioctlcmd(struct socket *sock, unsigned int cmd, + unsigned long arg) +{ + /* no ioctls for socket layer -> hand it down to NIC layer */ + return -ENOIOCTLCMD; +} + +static const struct proto_ops isotp_ops = { + .family = PF_CAN, + .release = isotp_release, + .bind = isotp_bind, + .connect = sock_no_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = isotp_getname, + .poll = datagram_poll, + .ioctl = isotp_sock_no_ioctlcmd, + .gettstamp = sock_gettstamp, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = isotp_setsockopt, + .getsockopt = isotp_getsockopt, + .sendmsg = isotp_sendmsg, + .recvmsg = isotp_recvmsg, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; + +static struct proto isotp_proto __read_mostly = { + .name = "CAN_ISOTP", + .owner = THIS_MODULE, + .obj_size = sizeof(struct isotp_sock), + .init = isotp_init, +}; + +static const struct can_proto isotp_can_proto = { + .type = SOCK_DGRAM, + .protocol = CAN_ISOTP, + .ops = &isotp_ops, + .prot = &isotp_proto, +}; + +static __init int isotp_module_init(void) +{ + int err; + + pr_info("can: isotp protocol (rev " CAN_ISOTP_VERSION ")\n"); + + err = can_proto_register(&isotp_can_proto); + if (err < 0) + pr_err("can: registration of isotp protocol failed\n"); + + return err; +} + +static __exit void isotp_module_exit(void) +{ + can_proto_unregister(&isotp_can_proto); +} + +module_init(isotp_module_init); +module_exit(isotp_module_exit); -- cgit v1.2.3 From 14b26b127c098bbab9b364d90a007c478090bf5f Mon Sep 17 00:00:00 2001 From: Calvin Johnson Date: Thu, 8 Oct 2020 20:17:06 +0530 Subject: net: phy: Move of_mdio from drivers/of to drivers/net/mdio Better place for of_mdio.c is drivers/net/mdio. Move of_mdio.c from drivers/of to drivers/net/mdio Signed-off-by: Calvin Johnson Acked-by: Rob Herring Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 +- drivers/net/mdio/Kconfig | 8 + drivers/net/mdio/Makefile | 2 + drivers/net/mdio/of_mdio.c | 592 +++++++++++++++++++++++++++++++++++++++++++++ drivers/of/Kconfig | 7 - drivers/of/Makefile | 1 - drivers/of/of_mdio.c | 592 --------------------------------------------- 7 files changed, 603 insertions(+), 601 deletions(-) create mode 100644 drivers/net/mdio/of_mdio.c delete mode 100644 drivers/of/of_mdio.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 19a7b59ec456..c80f87d7258c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6531,9 +6531,9 @@ F: Documentation/devicetree/bindings/net/mdio* F: Documentation/devicetree/bindings/net/qca,ar803x.yaml F: Documentation/networking/phy.rst F: drivers/net/mdio/ +F: drivers/net/mdio/of_mdio.c F: drivers/net/pcs/ F: drivers/net/phy/ -F: drivers/of/of_mdio.c F: drivers/of/of_net.c F: include/dt-bindings/net/qca-ar803x.h F: include/linux/*mdio*.h diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig index 27a2a4a3d943..a10cc460d7cf 100644 --- a/drivers/net/mdio/Kconfig +++ b/drivers/net/mdio/Kconfig @@ -19,6 +19,14 @@ config MDIO_BUS reflects whether the mdio_bus/mdio_device code is built as a loadable module or built-in. +config OF_MDIO + def_tristate PHYLIB + depends on OF + depends on PHYLIB + select FIXED_PHY + help + OpenFirmware MDIO bus (Ethernet PHY) accessors + if MDIO_BUS config MDIO_DEVRES diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile index 14d1beb633c9..5c498dde463f 100644 --- a/drivers/net/mdio/Makefile +++ b/drivers/net/mdio/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for Linux MDIO bus drivers +obj-$(CONFIG_OF_MDIO) += of_mdio.o + obj-$(CONFIG_MDIO_ASPEED) += mdio-aspeed.o obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o diff --git a/drivers/net/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c new file mode 100644 index 000000000000..4daf94bb56a5 --- /dev/null +++ b/drivers/net/mdio/of_mdio.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * OF helpers for the MDIO (Ethernet PHY) API + * + * Copyright (c) 2009 Secret Lab Technologies, Ltd. + * + * This file provides helper functions for extracting PHY device information + * out of the OpenFirmware device tree and using it to populate an mii_bus. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */ + +MODULE_AUTHOR("Grant Likely "); +MODULE_LICENSE("GPL"); + +/* Extract the clause 22 phy ID from the compatible string of the form + * ethernet-phy-idAAAA.BBBB */ +static int of_get_phy_id(struct device_node *device, u32 *phy_id) +{ + struct property *prop; + const char *cp; + unsigned int upper, lower; + + of_property_for_each_string(device, "compatible", prop, cp) { + if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) == 2) { + *phy_id = ((upper & 0xFFFF) << 16) | (lower & 0xFFFF); + return 0; + } + } + return -EINVAL; +} + +static struct mii_timestamper *of_find_mii_timestamper(struct device_node *node) +{ + struct of_phandle_args arg; + int err; + + err = of_parse_phandle_with_fixed_args(node, "timestamper", 1, 0, &arg); + + if (err == -ENOENT) + return NULL; + else if (err) + return ERR_PTR(err); + + if (arg.args_count != 1) + return ERR_PTR(-EINVAL); + + return register_mii_timestamper(arg.np, arg.args[0]); +} + +int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy, + struct device_node *child, u32 addr) +{ + int rc; + + rc = of_irq_get(child, 0); + if (rc == -EPROBE_DEFER) + return rc; + + if (rc > 0) { + phy->irq = rc; + mdio->irq[addr] = rc; + } else { + phy->irq = mdio->irq[addr]; + } + + if (of_property_read_bool(child, "broken-turn-around")) + mdio->phy_ignore_ta_mask |= 1 << addr; + + of_property_read_u32(child, "reset-assert-us", + &phy->mdio.reset_assert_delay); + of_property_read_u32(child, "reset-deassert-us", + &phy->mdio.reset_deassert_delay); + + /* Associate the OF node with the device structure so it + * can be looked up later */ + of_node_get(child); + phy->mdio.dev.of_node = child; + phy->mdio.dev.fwnode = of_fwnode_handle(child); + + /* All data is now stored in the phy struct; + * register it */ + rc = phy_device_register(phy); + if (rc) { + of_node_put(child); + return rc; + } + + dev_dbg(&mdio->dev, "registered phy %pOFn at address %i\n", + child, addr); + return 0; +} +EXPORT_SYMBOL(of_mdiobus_phy_device_register); + +static int of_mdiobus_register_phy(struct mii_bus *mdio, + struct device_node *child, u32 addr) +{ + struct mii_timestamper *mii_ts; + struct phy_device *phy; + bool is_c45; + int rc; + u32 phy_id; + + mii_ts = of_find_mii_timestamper(child); + if (IS_ERR(mii_ts)) + return PTR_ERR(mii_ts); + + is_c45 = of_device_is_compatible(child, + "ethernet-phy-ieee802.3-c45"); + + if (!is_c45 && !of_get_phy_id(child, &phy_id)) + phy = phy_device_create(mdio, addr, phy_id, 0, NULL); + else + phy = get_phy_device(mdio, addr, is_c45); + if (IS_ERR(phy)) { + if (mii_ts) + unregister_mii_timestamper(mii_ts); + return PTR_ERR(phy); + } + + rc = of_mdiobus_phy_device_register(mdio, phy, child, addr); + if (rc) { + if (mii_ts) + unregister_mii_timestamper(mii_ts); + phy_device_free(phy); + return rc; + } + + /* phy->mii_ts may already be defined by the PHY driver. A + * mii_timestamper probed via the device tree will still have + * precedence. + */ + if (mii_ts) + phy->mii_ts = mii_ts; + + return 0; +} + +static int of_mdiobus_register_device(struct mii_bus *mdio, + struct device_node *child, u32 addr) +{ + struct mdio_device *mdiodev; + int rc; + + mdiodev = mdio_device_create(mdio, addr); + if (IS_ERR(mdiodev)) + return PTR_ERR(mdiodev); + + /* Associate the OF node with the device structure so it + * can be looked up later. + */ + of_node_get(child); + mdiodev->dev.of_node = child; + mdiodev->dev.fwnode = of_fwnode_handle(child); + + /* All data is now stored in the mdiodev struct; register it. */ + rc = mdio_device_register(mdiodev); + if (rc) { + mdio_device_free(mdiodev); + of_node_put(child); + return rc; + } + + dev_dbg(&mdio->dev, "registered mdio device %pOFn at address %i\n", + child, addr); + return 0; +} + +/* The following is a list of PHY compatible strings which appear in + * some DTBs. The compatible string is never matched against a PHY + * driver, so is pointless. We only expect devices which are not PHYs + * to have a compatible string, so they can be matched to an MDIO + * driver. Encourage users to upgrade their DT blobs to remove these. + */ +static const struct of_device_id whitelist_phys[] = { + { .compatible = "brcm,40nm-ephy" }, + { .compatible = "broadcom,bcm5241" }, + { .compatible = "marvell,88E1111", }, + { .compatible = "marvell,88e1116", }, + { .compatible = "marvell,88e1118", }, + { .compatible = "marvell,88e1145", }, + { .compatible = "marvell,88e1149r", }, + { .compatible = "marvell,88e1310", }, + { .compatible = "marvell,88E1510", }, + { .compatible = "marvell,88E1514", }, + { .compatible = "moxa,moxart-rtl8201cp", }, + {} +}; + +/* + * Return true if the child node is for a phy. It must either: + * o Compatible string of "ethernet-phy-idX.X" + * o Compatible string of "ethernet-phy-ieee802.3-c45" + * o Compatible string of "ethernet-phy-ieee802.3-c22" + * o In the white list above (and issue a warning) + * o No compatibility string + * + * A device which is not a phy is expected to have a compatible string + * indicating what sort of device it is. + */ +bool of_mdiobus_child_is_phy(struct device_node *child) +{ + u32 phy_id; + + if (of_get_phy_id(child, &phy_id) != -EINVAL) + return true; + + if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c45")) + return true; + + if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c22")) + return true; + + if (of_match_node(whitelist_phys, child)) { + pr_warn(FW_WARN + "%pOF: Whitelisted compatible string. Please remove\n", + child); + return true; + } + + if (!of_find_property(child, "compatible", NULL)) + return true; + + return false; +} +EXPORT_SYMBOL(of_mdiobus_child_is_phy); + +/** + * of_mdiobus_register - Register mii_bus and create PHYs from the device tree + * @mdio: pointer to mii_bus structure + * @np: pointer to device_node of MDIO bus. + * + * This function registers the mii_bus structure and registers a phy_device + * for each child node of @np. + */ +int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np) +{ + struct device_node *child; + bool scanphys = false; + int addr, rc; + + if (!np) + return mdiobus_register(mdio); + + /* Do not continue if the node is disabled */ + if (!of_device_is_available(np)) + return -ENODEV; + + /* Mask out all PHYs from auto probing. Instead the PHYs listed in + * the device tree are populated after the bus has been registered */ + mdio->phy_mask = ~0; + + mdio->dev.of_node = np; + mdio->dev.fwnode = of_fwnode_handle(np); + + /* Get bus level PHY reset GPIO details */ + mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY; + of_property_read_u32(np, "reset-delay-us", &mdio->reset_delay_us); + mdio->reset_post_delay_us = 0; + of_property_read_u32(np, "reset-post-delay-us", &mdio->reset_post_delay_us); + + /* Register the MDIO bus */ + rc = mdiobus_register(mdio); + if (rc) + return rc; + + /* Loop over the child nodes and register a phy_device for each phy */ + for_each_available_child_of_node(np, child) { + addr = of_mdio_parse_addr(&mdio->dev, child); + if (addr < 0) { + scanphys = true; + continue; + } + + if (of_mdiobus_child_is_phy(child)) + rc = of_mdiobus_register_phy(mdio, child, addr); + else + rc = of_mdiobus_register_device(mdio, child, addr); + + if (rc == -ENODEV) + dev_err(&mdio->dev, + "MDIO device at address %d is missing.\n", + addr); + else if (rc) + goto unregister; + } + + if (!scanphys) + return 0; + + /* auto scan for PHYs with empty reg property */ + for_each_available_child_of_node(np, child) { + /* Skip PHYs with reg property set */ + if (of_find_property(child, "reg", NULL)) + continue; + + for (addr = 0; addr < PHY_MAX_ADDR; addr++) { + /* skip already registered PHYs */ + if (mdiobus_is_registered_device(mdio, addr)) + continue; + + /* be noisy to encourage people to set reg property */ + dev_info(&mdio->dev, "scan phy %pOFn at address %i\n", + child, addr); + + if (of_mdiobus_child_is_phy(child)) { + /* -ENODEV is the return code that PHYLIB has + * standardized on to indicate that bus + * scanning should continue. + */ + rc = of_mdiobus_register_phy(mdio, child, addr); + if (!rc) + break; + if (rc != -ENODEV) + goto unregister; + } + } + } + + return 0; + +unregister: + mdiobus_unregister(mdio); + return rc; +} +EXPORT_SYMBOL(of_mdiobus_register); + +/** + * of_mdio_find_device - Given a device tree node, find the mdio_device + * @np: pointer to the mdio_device's device tree node + * + * If successful, returns a pointer to the mdio_device with the embedded + * struct device refcount incremented by one, or NULL on failure. + * The caller should call put_device() on the mdio_device after its use + */ +struct mdio_device *of_mdio_find_device(struct device_node *np) +{ + struct device *d; + + if (!np) + return NULL; + + d = bus_find_device_by_of_node(&mdio_bus_type, np); + if (!d) + return NULL; + + return to_mdio_device(d); +} +EXPORT_SYMBOL(of_mdio_find_device); + +/** + * of_phy_find_device - Give a PHY node, find the phy_device + * @phy_np: Pointer to the phy's device tree node + * + * If successful, returns a pointer to the phy_device with the embedded + * struct device refcount incremented by one, or NULL on failure. + */ +struct phy_device *of_phy_find_device(struct device_node *phy_np) +{ + struct mdio_device *mdiodev; + + mdiodev = of_mdio_find_device(phy_np); + if (!mdiodev) + return NULL; + + if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) + return to_phy_device(&mdiodev->dev); + + put_device(&mdiodev->dev); + + return NULL; +} +EXPORT_SYMBOL(of_phy_find_device); + +/** + * of_phy_connect - Connect to the phy described in the device tree + * @dev: pointer to net_device claiming the phy + * @phy_np: Pointer to device tree node for the PHY + * @hndlr: Link state callback for the network device + * @flags: flags to pass to the PHY + * @iface: PHY data interface type + * + * If successful, returns a pointer to the phy_device with the embedded + * struct device refcount incremented by one, or NULL on failure. The + * refcount must be dropped by calling phy_disconnect() or phy_detach(). + */ +struct phy_device *of_phy_connect(struct net_device *dev, + struct device_node *phy_np, + void (*hndlr)(struct net_device *), u32 flags, + phy_interface_t iface) +{ + struct phy_device *phy = of_phy_find_device(phy_np); + int ret; + + if (!phy) + return NULL; + + phy->dev_flags |= flags; + + ret = phy_connect_direct(dev, phy, hndlr, iface); + + /* refcount is held by phy_connect_direct() on success */ + put_device(&phy->mdio.dev); + + return ret ? NULL : phy; +} +EXPORT_SYMBOL(of_phy_connect); + +/** + * of_phy_get_and_connect + * - Get phy node and connect to the phy described in the device tree + * @dev: pointer to net_device claiming the phy + * @np: Pointer to device tree node for the net_device claiming the phy + * @hndlr: Link state callback for the network device + * + * If successful, returns a pointer to the phy_device with the embedded + * struct device refcount incremented by one, or NULL on failure. The + * refcount must be dropped by calling phy_disconnect() or phy_detach(). + */ +struct phy_device *of_phy_get_and_connect(struct net_device *dev, + struct device_node *np, + void (*hndlr)(struct net_device *)) +{ + phy_interface_t iface; + struct device_node *phy_np; + struct phy_device *phy; + int ret; + + ret = of_get_phy_mode(np, &iface); + if (ret) + return NULL; + if (of_phy_is_fixed_link(np)) { + ret = of_phy_register_fixed_link(np); + if (ret < 0) { + netdev_err(dev, "broken fixed-link specification\n"); + return NULL; + } + phy_np = of_node_get(np); + } else { + phy_np = of_parse_phandle(np, "phy-handle", 0); + if (!phy_np) + return NULL; + } + + phy = of_phy_connect(dev, phy_np, hndlr, 0, iface); + + of_node_put(phy_np); + + return phy; +} +EXPORT_SYMBOL(of_phy_get_and_connect); + +/** + * of_phy_attach - Attach to a PHY without starting the state machine + * @dev: pointer to net_device claiming the phy + * @phy_np: Node pointer for the PHY + * @flags: flags to pass to the PHY + * @iface: PHY data interface type + * + * If successful, returns a pointer to the phy_device with the embedded + * struct device refcount incremented by one, or NULL on failure. The + * refcount must be dropped by calling phy_disconnect() or phy_detach(). + */ +struct phy_device *of_phy_attach(struct net_device *dev, + struct device_node *phy_np, u32 flags, + phy_interface_t iface) +{ + struct phy_device *phy = of_phy_find_device(phy_np); + int ret; + + if (!phy) + return NULL; + + ret = phy_attach_direct(dev, phy, flags, iface); + + /* refcount is held by phy_attach_direct() on success */ + put_device(&phy->mdio.dev); + + return ret ? NULL : phy; +} +EXPORT_SYMBOL(of_phy_attach); + +/* + * of_phy_is_fixed_link() and of_phy_register_fixed_link() must + * support two DT bindings: + * - the old DT binding, where 'fixed-link' was a property with 5 + * cells encoding various informations about the fixed PHY + * - the new DT binding, where 'fixed-link' is a sub-node of the + * Ethernet device. + */ +bool of_phy_is_fixed_link(struct device_node *np) +{ + struct device_node *dn; + int len, err; + const char *managed; + + /* New binding */ + dn = of_get_child_by_name(np, "fixed-link"); + if (dn) { + of_node_put(dn); + return true; + } + + err = of_property_read_string(np, "managed", &managed); + if (err == 0 && strcmp(managed, "auto") != 0) + return true; + + /* Old binding */ + if (of_get_property(np, "fixed-link", &len) && + len == (5 * sizeof(__be32))) + return true; + + return false; +} +EXPORT_SYMBOL(of_phy_is_fixed_link); + +int of_phy_register_fixed_link(struct device_node *np) +{ + struct fixed_phy_status status = {}; + struct device_node *fixed_link_node; + u32 fixed_link_prop[5]; + const char *managed; + + if (of_property_read_string(np, "managed", &managed) == 0 && + strcmp(managed, "in-band-status") == 0) { + /* status is zeroed, namely its .link member */ + goto register_phy; + } + + /* New binding */ + fixed_link_node = of_get_child_by_name(np, "fixed-link"); + if (fixed_link_node) { + status.link = 1; + status.duplex = of_property_read_bool(fixed_link_node, + "full-duplex"); + if (of_property_read_u32(fixed_link_node, "speed", + &status.speed)) { + of_node_put(fixed_link_node); + return -EINVAL; + } + status.pause = of_property_read_bool(fixed_link_node, "pause"); + status.asym_pause = of_property_read_bool(fixed_link_node, + "asym-pause"); + of_node_put(fixed_link_node); + + goto register_phy; + } + + /* Old binding */ + if (of_property_read_u32_array(np, "fixed-link", fixed_link_prop, + ARRAY_SIZE(fixed_link_prop)) == 0) { + status.link = 1; + status.duplex = fixed_link_prop[1]; + status.speed = fixed_link_prop[2]; + status.pause = fixed_link_prop[3]; + status.asym_pause = fixed_link_prop[4]; + goto register_phy; + } + + return -ENODEV; + +register_phy: + return PTR_ERR_OR_ZERO(fixed_phy_register(PHY_POLL, &status, np)); +} +EXPORT_SYMBOL(of_phy_register_fixed_link); + +void of_phy_deregister_fixed_link(struct device_node *np) +{ + struct phy_device *phydev; + + phydev = of_phy_find_device(np); + if (!phydev) + return; + + fixed_phy_unregister(phydev); + + put_device(&phydev->mdio.dev); /* of_phy_find_device() */ + phy_device_free(phydev); /* fixed_phy_register() */ +} +EXPORT_SYMBOL(of_phy_deregister_fixed_link); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index d91618641be6..18450437d5d5 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -74,13 +74,6 @@ config OF_NET depends on NETDEVICES def_bool y -config OF_MDIO - def_tristate PHYLIB - depends on PHYLIB - select FIXED_PHY - help - OpenFirmware MDIO bus (Ethernet PHY) accessors - config OF_RESERVED_MEM bool depends on OF_EARLY_FLATTREE diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 663a4af0cccd..6e1e5212f058 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -9,7 +9,6 @@ obj-$(CONFIG_OF_ADDRESS) += address.o obj-$(CONFIG_OF_IRQ) += irq.o obj-$(CONFIG_OF_NET) += of_net.o obj-$(CONFIG_OF_UNITTEST) += unittest.o -obj-$(CONFIG_OF_MDIO) += of_mdio.o obj-$(CONFIG_OF_RESERVED_MEM) += of_reserved_mem.o obj-$(CONFIG_OF_RESOLVE) += resolver.o obj-$(CONFIG_OF_OVERLAY) += overlay.o diff --git a/drivers/of/of_mdio.c b/drivers/of/of_mdio.c deleted file mode 100644 index 4daf94bb56a5..000000000000 --- a/drivers/of/of_mdio.c +++ /dev/null @@ -1,592 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * OF helpers for the MDIO (Ethernet PHY) API - * - * Copyright (c) 2009 Secret Lab Technologies, Ltd. - * - * This file provides helper functions for extracting PHY device information - * out of the OpenFirmware device tree and using it to populate an mii_bus. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */ - -MODULE_AUTHOR("Grant Likely "); -MODULE_LICENSE("GPL"); - -/* Extract the clause 22 phy ID from the compatible string of the form - * ethernet-phy-idAAAA.BBBB */ -static int of_get_phy_id(struct device_node *device, u32 *phy_id) -{ - struct property *prop; - const char *cp; - unsigned int upper, lower; - - of_property_for_each_string(device, "compatible", prop, cp) { - if (sscanf(cp, "ethernet-phy-id%4x.%4x", &upper, &lower) == 2) { - *phy_id = ((upper & 0xFFFF) << 16) | (lower & 0xFFFF); - return 0; - } - } - return -EINVAL; -} - -static struct mii_timestamper *of_find_mii_timestamper(struct device_node *node) -{ - struct of_phandle_args arg; - int err; - - err = of_parse_phandle_with_fixed_args(node, "timestamper", 1, 0, &arg); - - if (err == -ENOENT) - return NULL; - else if (err) - return ERR_PTR(err); - - if (arg.args_count != 1) - return ERR_PTR(-EINVAL); - - return register_mii_timestamper(arg.np, arg.args[0]); -} - -int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy, - struct device_node *child, u32 addr) -{ - int rc; - - rc = of_irq_get(child, 0); - if (rc == -EPROBE_DEFER) - return rc; - - if (rc > 0) { - phy->irq = rc; - mdio->irq[addr] = rc; - } else { - phy->irq = mdio->irq[addr]; - } - - if (of_property_read_bool(child, "broken-turn-around")) - mdio->phy_ignore_ta_mask |= 1 << addr; - - of_property_read_u32(child, "reset-assert-us", - &phy->mdio.reset_assert_delay); - of_property_read_u32(child, "reset-deassert-us", - &phy->mdio.reset_deassert_delay); - - /* Associate the OF node with the device structure so it - * can be looked up later */ - of_node_get(child); - phy->mdio.dev.of_node = child; - phy->mdio.dev.fwnode = of_fwnode_handle(child); - - /* All data is now stored in the phy struct; - * register it */ - rc = phy_device_register(phy); - if (rc) { - of_node_put(child); - return rc; - } - - dev_dbg(&mdio->dev, "registered phy %pOFn at address %i\n", - child, addr); - return 0; -} -EXPORT_SYMBOL(of_mdiobus_phy_device_register); - -static int of_mdiobus_register_phy(struct mii_bus *mdio, - struct device_node *child, u32 addr) -{ - struct mii_timestamper *mii_ts; - struct phy_device *phy; - bool is_c45; - int rc; - u32 phy_id; - - mii_ts = of_find_mii_timestamper(child); - if (IS_ERR(mii_ts)) - return PTR_ERR(mii_ts); - - is_c45 = of_device_is_compatible(child, - "ethernet-phy-ieee802.3-c45"); - - if (!is_c45 && !of_get_phy_id(child, &phy_id)) - phy = phy_device_create(mdio, addr, phy_id, 0, NULL); - else - phy = get_phy_device(mdio, addr, is_c45); - if (IS_ERR(phy)) { - if (mii_ts) - unregister_mii_timestamper(mii_ts); - return PTR_ERR(phy); - } - - rc = of_mdiobus_phy_device_register(mdio, phy, child, addr); - if (rc) { - if (mii_ts) - unregister_mii_timestamper(mii_ts); - phy_device_free(phy); - return rc; - } - - /* phy->mii_ts may already be defined by the PHY driver. A - * mii_timestamper probed via the device tree will still have - * precedence. - */ - if (mii_ts) - phy->mii_ts = mii_ts; - - return 0; -} - -static int of_mdiobus_register_device(struct mii_bus *mdio, - struct device_node *child, u32 addr) -{ - struct mdio_device *mdiodev; - int rc; - - mdiodev = mdio_device_create(mdio, addr); - if (IS_ERR(mdiodev)) - return PTR_ERR(mdiodev); - - /* Associate the OF node with the device structure so it - * can be looked up later. - */ - of_node_get(child); - mdiodev->dev.of_node = child; - mdiodev->dev.fwnode = of_fwnode_handle(child); - - /* All data is now stored in the mdiodev struct; register it. */ - rc = mdio_device_register(mdiodev); - if (rc) { - mdio_device_free(mdiodev); - of_node_put(child); - return rc; - } - - dev_dbg(&mdio->dev, "registered mdio device %pOFn at address %i\n", - child, addr); - return 0; -} - -/* The following is a list of PHY compatible strings which appear in - * some DTBs. The compatible string is never matched against a PHY - * driver, so is pointless. We only expect devices which are not PHYs - * to have a compatible string, so they can be matched to an MDIO - * driver. Encourage users to upgrade their DT blobs to remove these. - */ -static const struct of_device_id whitelist_phys[] = { - { .compatible = "brcm,40nm-ephy" }, - { .compatible = "broadcom,bcm5241" }, - { .compatible = "marvell,88E1111", }, - { .compatible = "marvell,88e1116", }, - { .compatible = "marvell,88e1118", }, - { .compatible = "marvell,88e1145", }, - { .compatible = "marvell,88e1149r", }, - { .compatible = "marvell,88e1310", }, - { .compatible = "marvell,88E1510", }, - { .compatible = "marvell,88E1514", }, - { .compatible = "moxa,moxart-rtl8201cp", }, - {} -}; - -/* - * Return true if the child node is for a phy. It must either: - * o Compatible string of "ethernet-phy-idX.X" - * o Compatible string of "ethernet-phy-ieee802.3-c45" - * o Compatible string of "ethernet-phy-ieee802.3-c22" - * o In the white list above (and issue a warning) - * o No compatibility string - * - * A device which is not a phy is expected to have a compatible string - * indicating what sort of device it is. - */ -bool of_mdiobus_child_is_phy(struct device_node *child) -{ - u32 phy_id; - - if (of_get_phy_id(child, &phy_id) != -EINVAL) - return true; - - if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c45")) - return true; - - if (of_device_is_compatible(child, "ethernet-phy-ieee802.3-c22")) - return true; - - if (of_match_node(whitelist_phys, child)) { - pr_warn(FW_WARN - "%pOF: Whitelisted compatible string. Please remove\n", - child); - return true; - } - - if (!of_find_property(child, "compatible", NULL)) - return true; - - return false; -} -EXPORT_SYMBOL(of_mdiobus_child_is_phy); - -/** - * of_mdiobus_register - Register mii_bus and create PHYs from the device tree - * @mdio: pointer to mii_bus structure - * @np: pointer to device_node of MDIO bus. - * - * This function registers the mii_bus structure and registers a phy_device - * for each child node of @np. - */ -int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np) -{ - struct device_node *child; - bool scanphys = false; - int addr, rc; - - if (!np) - return mdiobus_register(mdio); - - /* Do not continue if the node is disabled */ - if (!of_device_is_available(np)) - return -ENODEV; - - /* Mask out all PHYs from auto probing. Instead the PHYs listed in - * the device tree are populated after the bus has been registered */ - mdio->phy_mask = ~0; - - mdio->dev.of_node = np; - mdio->dev.fwnode = of_fwnode_handle(np); - - /* Get bus level PHY reset GPIO details */ - mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY; - of_property_read_u32(np, "reset-delay-us", &mdio->reset_delay_us); - mdio->reset_post_delay_us = 0; - of_property_read_u32(np, "reset-post-delay-us", &mdio->reset_post_delay_us); - - /* Register the MDIO bus */ - rc = mdiobus_register(mdio); - if (rc) - return rc; - - /* Loop over the child nodes and register a phy_device for each phy */ - for_each_available_child_of_node(np, child) { - addr = of_mdio_parse_addr(&mdio->dev, child); - if (addr < 0) { - scanphys = true; - continue; - } - - if (of_mdiobus_child_is_phy(child)) - rc = of_mdiobus_register_phy(mdio, child, addr); - else - rc = of_mdiobus_register_device(mdio, child, addr); - - if (rc == -ENODEV) - dev_err(&mdio->dev, - "MDIO device at address %d is missing.\n", - addr); - else if (rc) - goto unregister; - } - - if (!scanphys) - return 0; - - /* auto scan for PHYs with empty reg property */ - for_each_available_child_of_node(np, child) { - /* Skip PHYs with reg property set */ - if (of_find_property(child, "reg", NULL)) - continue; - - for (addr = 0; addr < PHY_MAX_ADDR; addr++) { - /* skip already registered PHYs */ - if (mdiobus_is_registered_device(mdio, addr)) - continue; - - /* be noisy to encourage people to set reg property */ - dev_info(&mdio->dev, "scan phy %pOFn at address %i\n", - child, addr); - - if (of_mdiobus_child_is_phy(child)) { - /* -ENODEV is the return code that PHYLIB has - * standardized on to indicate that bus - * scanning should continue. - */ - rc = of_mdiobus_register_phy(mdio, child, addr); - if (!rc) - break; - if (rc != -ENODEV) - goto unregister; - } - } - } - - return 0; - -unregister: - mdiobus_unregister(mdio); - return rc; -} -EXPORT_SYMBOL(of_mdiobus_register); - -/** - * of_mdio_find_device - Given a device tree node, find the mdio_device - * @np: pointer to the mdio_device's device tree node - * - * If successful, returns a pointer to the mdio_device with the embedded - * struct device refcount incremented by one, or NULL on failure. - * The caller should call put_device() on the mdio_device after its use - */ -struct mdio_device *of_mdio_find_device(struct device_node *np) -{ - struct device *d; - - if (!np) - return NULL; - - d = bus_find_device_by_of_node(&mdio_bus_type, np); - if (!d) - return NULL; - - return to_mdio_device(d); -} -EXPORT_SYMBOL(of_mdio_find_device); - -/** - * of_phy_find_device - Give a PHY node, find the phy_device - * @phy_np: Pointer to the phy's device tree node - * - * If successful, returns a pointer to the phy_device with the embedded - * struct device refcount incremented by one, or NULL on failure. - */ -struct phy_device *of_phy_find_device(struct device_node *phy_np) -{ - struct mdio_device *mdiodev; - - mdiodev = of_mdio_find_device(phy_np); - if (!mdiodev) - return NULL; - - if (mdiodev->flags & MDIO_DEVICE_FLAG_PHY) - return to_phy_device(&mdiodev->dev); - - put_device(&mdiodev->dev); - - return NULL; -} -EXPORT_SYMBOL(of_phy_find_device); - -/** - * of_phy_connect - Connect to the phy described in the device tree - * @dev: pointer to net_device claiming the phy - * @phy_np: Pointer to device tree node for the PHY - * @hndlr: Link state callback for the network device - * @flags: flags to pass to the PHY - * @iface: PHY data interface type - * - * If successful, returns a pointer to the phy_device with the embedded - * struct device refcount incremented by one, or NULL on failure. The - * refcount must be dropped by calling phy_disconnect() or phy_detach(). - */ -struct phy_device *of_phy_connect(struct net_device *dev, - struct device_node *phy_np, - void (*hndlr)(struct net_device *), u32 flags, - phy_interface_t iface) -{ - struct phy_device *phy = of_phy_find_device(phy_np); - int ret; - - if (!phy) - return NULL; - - phy->dev_flags |= flags; - - ret = phy_connect_direct(dev, phy, hndlr, iface); - - /* refcount is held by phy_connect_direct() on success */ - put_device(&phy->mdio.dev); - - return ret ? NULL : phy; -} -EXPORT_SYMBOL(of_phy_connect); - -/** - * of_phy_get_and_connect - * - Get phy node and connect to the phy described in the device tree - * @dev: pointer to net_device claiming the phy - * @np: Pointer to device tree node for the net_device claiming the phy - * @hndlr: Link state callback for the network device - * - * If successful, returns a pointer to the phy_device with the embedded - * struct device refcount incremented by one, or NULL on failure. The - * refcount must be dropped by calling phy_disconnect() or phy_detach(). - */ -struct phy_device *of_phy_get_and_connect(struct net_device *dev, - struct device_node *np, - void (*hndlr)(struct net_device *)) -{ - phy_interface_t iface; - struct device_node *phy_np; - struct phy_device *phy; - int ret; - - ret = of_get_phy_mode(np, &iface); - if (ret) - return NULL; - if (of_phy_is_fixed_link(np)) { - ret = of_phy_register_fixed_link(np); - if (ret < 0) { - netdev_err(dev, "broken fixed-link specification\n"); - return NULL; - } - phy_np = of_node_get(np); - } else { - phy_np = of_parse_phandle(np, "phy-handle", 0); - if (!phy_np) - return NULL; - } - - phy = of_phy_connect(dev, phy_np, hndlr, 0, iface); - - of_node_put(phy_np); - - return phy; -} -EXPORT_SYMBOL(of_phy_get_and_connect); - -/** - * of_phy_attach - Attach to a PHY without starting the state machine - * @dev: pointer to net_device claiming the phy - * @phy_np: Node pointer for the PHY - * @flags: flags to pass to the PHY - * @iface: PHY data interface type - * - * If successful, returns a pointer to the phy_device with the embedded - * struct device refcount incremented by one, or NULL on failure. The - * refcount must be dropped by calling phy_disconnect() or phy_detach(). - */ -struct phy_device *of_phy_attach(struct net_device *dev, - struct device_node *phy_np, u32 flags, - phy_interface_t iface) -{ - struct phy_device *phy = of_phy_find_device(phy_np); - int ret; - - if (!phy) - return NULL; - - ret = phy_attach_direct(dev, phy, flags, iface); - - /* refcount is held by phy_attach_direct() on success */ - put_device(&phy->mdio.dev); - - return ret ? NULL : phy; -} -EXPORT_SYMBOL(of_phy_attach); - -/* - * of_phy_is_fixed_link() and of_phy_register_fixed_link() must - * support two DT bindings: - * - the old DT binding, where 'fixed-link' was a property with 5 - * cells encoding various informations about the fixed PHY - * - the new DT binding, where 'fixed-link' is a sub-node of the - * Ethernet device. - */ -bool of_phy_is_fixed_link(struct device_node *np) -{ - struct device_node *dn; - int len, err; - const char *managed; - - /* New binding */ - dn = of_get_child_by_name(np, "fixed-link"); - if (dn) { - of_node_put(dn); - return true; - } - - err = of_property_read_string(np, "managed", &managed); - if (err == 0 && strcmp(managed, "auto") != 0) - return true; - - /* Old binding */ - if (of_get_property(np, "fixed-link", &len) && - len == (5 * sizeof(__be32))) - return true; - - return false; -} -EXPORT_SYMBOL(of_phy_is_fixed_link); - -int of_phy_register_fixed_link(struct device_node *np) -{ - struct fixed_phy_status status = {}; - struct device_node *fixed_link_node; - u32 fixed_link_prop[5]; - const char *managed; - - if (of_property_read_string(np, "managed", &managed) == 0 && - strcmp(managed, "in-band-status") == 0) { - /* status is zeroed, namely its .link member */ - goto register_phy; - } - - /* New binding */ - fixed_link_node = of_get_child_by_name(np, "fixed-link"); - if (fixed_link_node) { - status.link = 1; - status.duplex = of_property_read_bool(fixed_link_node, - "full-duplex"); - if (of_property_read_u32(fixed_link_node, "speed", - &status.speed)) { - of_node_put(fixed_link_node); - return -EINVAL; - } - status.pause = of_property_read_bool(fixed_link_node, "pause"); - status.asym_pause = of_property_read_bool(fixed_link_node, - "asym-pause"); - of_node_put(fixed_link_node); - - goto register_phy; - } - - /* Old binding */ - if (of_property_read_u32_array(np, "fixed-link", fixed_link_prop, - ARRAY_SIZE(fixed_link_prop)) == 0) { - status.link = 1; - status.duplex = fixed_link_prop[1]; - status.speed = fixed_link_prop[2]; - status.pause = fixed_link_prop[3]; - status.asym_pause = fixed_link_prop[4]; - goto register_phy; - } - - return -ENODEV; - -register_phy: - return PTR_ERR_OR_ZERO(fixed_phy_register(PHY_POLL, &status, np)); -} -EXPORT_SYMBOL(of_phy_register_fixed_link); - -void of_phy_deregister_fixed_link(struct device_node *np) -{ - struct phy_device *phydev; - - phydev = of_phy_find_device(np); - if (!phydev) - return; - - fixed_phy_unregister(phydev); - - put_device(&phydev->mdio.dev); /* of_phy_find_device() */ - phy_device_free(phydev); /* fixed_phy_register() */ -} -EXPORT_SYMBOL(of_phy_deregister_fixed_link); -- cgit v1.2.3