From 88a972d74ea9172a156cdffb6bb30fbb4b1e27c6 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:07 -0600 Subject: net: axienet: fix MDIO bus naming The MDIO bus for this driver was being named using the result of of_address_to_resource on a node which may not have any resource on it, but the return value of that call was not checked so it was using some random value in the bus name. Change to name the MDIO bus based on the resource start of the actual Ethernet register block. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index 704babdbc8a2..665ae1d16f1e 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -127,7 +127,7 @@ int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) int ret; u32 clk_div, host_clock; struct mii_bus *bus; - struct resource res; + struct device_node *mdio_node; struct device_node *np1; /* clk_div can be calculated by deriving it from the equation: @@ -199,10 +199,9 @@ issue: if (!bus) return -ENOMEM; - np1 = of_get_parent(lp->phy_node); - of_address_to_resource(np1, 0, &res); - snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", - (unsigned long long) res.start); + mdio_node = of_get_parent(lp->phy_node); + snprintf(bus->id, MII_BUS_ID_SIZE, "axienet-%.8llx", + (unsigned long long)lp->regs_start); bus->priv = lp; bus->name = "Xilinx Axi Ethernet MDIO"; @@ -211,7 +210,7 @@ issue: bus->parent = lp->dev; lp->mii_bus = bus; - ret = of_mdiobus_register(bus, np1); + ret = of_mdiobus_register(bus, mdio_node); if (ret) { mdiobus_free(bus); lp->mii_bus = NULL; -- cgit v1.2.3 From 09a0354cadec267be7f5249c89eb998b3474263a Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:09 -0600 Subject: net: axienet: Use clock framework to get device clock rate This driver was previously always calculating the MDIO clock divisor (from AXI bus clock to MDIO bus clock) based on the CPU clock frequency, assuming that it is the same as the AXI bus frequency, but that simplistic method only works on the MicroBlaze platform. Add support for specifying the clock used for the device in the device tree using the clock framework. If the clock is specified then it will be used when calculating the clock divisor. The previous CPU clock detection method is left for backward compatibility if no clock is specified. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- .../devicetree/bindings/net/xilinx_axienet.txt | 6 +++ drivers/net/ethernet/xilinx/xilinx_axienet.h | 5 +- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 23 +++++++++- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 53 ++++++++++++---------- 4 files changed, 59 insertions(+), 28 deletions(-) (limited to 'drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c') diff --git a/Documentation/devicetree/bindings/net/xilinx_axienet.txt b/Documentation/devicetree/bindings/net/xilinx_axienet.txt index 38f9ec076743..2dea90332ebb 100644 --- a/Documentation/devicetree/bindings/net/xilinx_axienet.txt +++ b/Documentation/devicetree/bindings/net/xilinx_axienet.txt @@ -31,6 +31,11 @@ Optional properties: 1 to enable partial TX checksum offload, 2 to enable full TX checksum offload - xlnx,rxcsum : Same values as xlnx,txcsum but for RX checksum offload +- clocks : AXI bus clock for the device. Refer to common clock bindings. + Used to calculate MDIO clock divisor. If not specified, it is + auto-detected from the CPU clock (but only on platforms where + this is possible). New device trees should specify this - the + auto detection is only for backward compatibility. Example: axi_ethernet_eth: ethernet@40c00000 { @@ -38,6 +43,7 @@ Example: device_type = "network"; interrupt-parent = <µblaze_0_axi_intc>; interrupts = <2 0>; + clocks = <&axi_clk>; phy-mode = "mii"; reg = <0x40c00000 0x40000>; xlnx,rxcsum = <0x2>; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index f9078bd7f86f..f240ff1b7296 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -418,6 +418,9 @@ struct axienet_local { /* Connection to PHY device */ struct device_node *phy_node; + /* Clock for AXI bus */ + struct clk *clk; + /* MDIO bus data */ struct mii_bus *mii_bus; /* MII bus reference */ @@ -502,7 +505,7 @@ static inline void axienet_iow(struct axienet_local *lp, off_t offset, } /* Function prototypes visible in xilinx_axienet_mdio.c for other files */ -int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np); +int axienet_mdio_setup(struct axienet_local *lp); int axienet_mdio_wait_until_ready(struct axienet_local *lp); void axienet_mdio_teardown(struct axienet_local *lp); diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index ffbd4d762534..42b343cb3e2b 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -21,6 +21,7 @@ * - Add support for extended VLAN support. */ +#include #include #include #include @@ -1611,9 +1612,24 @@ static int axienet_probe(struct platform_device *pdev) lp->phy_node = of_parse_phandle(pdev->dev.of_node, "phy-handle", 0); if (lp->phy_node) { - ret = axienet_mdio_setup(lp, pdev->dev.of_node); + lp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lp->clk)) { + dev_warn(&pdev->dev, "Failed to get clock: %ld\n", + PTR_ERR(lp->clk)); + lp->clk = NULL; + } else { + ret = clk_prepare_enable(lp->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock: %d\n", + ret); + goto free_netdev; + } + } + + ret = axienet_mdio_setup(lp); if (ret) - dev_warn(&pdev->dev, "error registering MDIO bus\n"); + dev_warn(&pdev->dev, + "error registering MDIO bus: %d\n", ret); } ret = register_netdev(lp->ndev); @@ -1638,6 +1654,9 @@ static int axienet_remove(struct platform_device *pdev) axienet_mdio_teardown(lp); unregister_netdev(ndev); + if (lp->clk) + clk_disable_unprepare(lp->clk); + of_node_put(lp->phy_node); lp->phy_node = NULL; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index 665ae1d16f1e..cce0dc42583e 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -8,6 +8,7 @@ * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. */ +#include #include #include #include @@ -16,7 +17,7 @@ #include "xilinx_axienet.h" #define MAX_MDIO_FREQ 2500000 /* 2.5 MHz */ -#define DEFAULT_CLOCK_DIVISOR XAE_MDIO_DIV_DFT +#define DEFAULT_HOST_CLOCK 150000000 /* 150 MHz */ /* Wait till MDIO interface is ready to accept a new transaction.*/ int axienet_mdio_wait_until_ready(struct axienet_local *lp) @@ -114,7 +115,6 @@ static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, /** * axienet_mdio_setup - MDIO setup function * @lp: Pointer to axienet local data structure. - * @np: Pointer to device node * * Return: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when * mdiobus_alloc (to allocate memory for mii bus structure) fails. @@ -122,13 +122,37 @@ static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, * Sets up the MDIO interface by initializing the MDIO clock and enabling the * MDIO interface in hardware. Register the MDIO interface. **/ -int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) +int axienet_mdio_setup(struct axienet_local *lp) { int ret; u32 clk_div, host_clock; struct mii_bus *bus; struct device_node *mdio_node; - struct device_node *np1; + + if (lp->clk) { + host_clock = clk_get_rate(lp->clk); + } else { + struct device_node *np1; + + /* Legacy fallback: detect CPU clock frequency and use as AXI + * bus clock frequency. This only works on certain platforms. + */ + np1 = of_find_node_by_name(NULL, "cpu"); + if (!np1) { + netdev_warn(lp->ndev, "Could not find CPU device node.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } else { + ret = of_property_read_u32(np1, "clock-frequency", + &host_clock); + if (ret) { + netdev_warn(lp->ndev, "CPU clock-frequency property not found.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } + of_node_put(np1); + } + netdev_info(lp->ndev, "Setting assumed host clock to %u\n", + host_clock); + } /* clk_div can be calculated by deriving it from the equation: * fMDIO = fHOST / ((1 + clk_div) * 2) @@ -155,25 +179,6 @@ int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) * "clock-frequency" from the CPU */ - np1 = of_find_node_by_name(NULL, "cpu"); - if (!np1) { - netdev_warn(lp->ndev, "Could not find CPU device node.\n"); - netdev_warn(lp->ndev, - "Setting MDIO clock divisor to default %d\n", - DEFAULT_CLOCK_DIVISOR); - clk_div = DEFAULT_CLOCK_DIVISOR; - goto issue; - } - if (of_property_read_u32(np1, "clock-frequency", &host_clock)) { - netdev_warn(lp->ndev, "clock-frequency property not found.\n"); - netdev_warn(lp->ndev, - "Setting MDIO clock divisor to default %d\n", - DEFAULT_CLOCK_DIVISOR); - clk_div = DEFAULT_CLOCK_DIVISOR; - of_node_put(np1); - goto issue; - } - clk_div = (host_clock / (MAX_MDIO_FREQ * 2)) - 1; /* If there is any remainder from the division of * fHOST / (MAX_MDIO_FREQ * 2), then we need to add @@ -186,8 +191,6 @@ int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) "Setting MDIO clock divisor to %u/%u Hz host clock.\n", clk_div, host_clock); - of_node_put(np1); -issue: axienet_iow(lp, XAE_MDIO_MC_OFFSET, (((u32) clk_div) | XAE_MDIO_MC_MDIOEN_MASK)); -- cgit v1.2.3 From 7789e9ed0591bab4d8a419bf3b9b18327d75128f Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:11 -0600 Subject: net: axienet: Re-initialize MDIO registers properly after reset The MDIO clock divisor register setting was only applied on the initial startup when the driver was loaded. However, this setting is cleared when the device is reset, such as would occur when the interface was taken down and brought up again, and so the MDIO bus would be non-functional afterwards. Split up the MDIO bus setup and enable into separate functions and re-enable the bus after a device reset, to ensure that the MDIO registers are set properly. This also allows us to remove direct access to MDIO registers in xilinx_axienet_main.c and centralize them all in xilinx_axienet_mdio.c. Also, lock the MDIO bus lock around the device reset process, to avoid MDIO accesses from occurring while the MDIO is disabled during the reset. Signed-off-by: Robert Hancock Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 3 +- drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 38 +++++++--------- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 54 +++++++++++++++++------ 3 files changed, 57 insertions(+), 38 deletions(-) (limited to 'drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index f240ff1b7296..4a135ed6e8a3 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -505,8 +505,9 @@ static inline void axienet_iow(struct axienet_local *lp, off_t offset, } /* Function prototypes visible in xilinx_axienet_mdio.c for other files */ +int axienet_mdio_enable(struct axienet_local *lp); +void axienet_mdio_disable(struct axienet_local *lp); int axienet_mdio_setup(struct axienet_local *lp); -int axienet_mdio_wait_until_ready(struct axienet_local *lp); void axienet_mdio_teardown(struct axienet_local *lp); #endif /* XILINX_AXI_ENET_H */ diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 5cb39deca8d2..e735ca7b5df0 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -914,27 +914,23 @@ static void axienet_dma_err_handler(unsigned long data); */ static int axienet_open(struct net_device *ndev) { - int ret, mdio_mcreg; + int ret; struct axienet_local *lp = netdev_priv(ndev); struct phy_device *phydev = NULL; dev_dbg(&ndev->dev, "axienet_open()\n"); - mdio_mcreg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); - ret = axienet_mdio_wait_until_ready(lp); - if (ret < 0) - return ret; /* Disable the MDIO interface till Axi Ethernet Reset is completed. * When we do an Axi Ethernet reset, it resets the complete core - * including the MDIO. If MDIO is not disabled when the reset - * process is started, MDIO will be broken afterwards. + * including the MDIO. MDIO must be disabled before resetting + * and re-enabled afterwards. + * Hold MDIO bus lock to avoid MDIO accesses during the reset. */ - axienet_iow(lp, XAE_MDIO_MC_OFFSET, - (mdio_mcreg & (~XAE_MDIO_MC_MDIOEN_MASK))); + mutex_lock(&lp->mii_bus->mdio_lock); + axienet_mdio_disable(lp); axienet_device_reset(ndev); - /* Enable the MDIO */ - axienet_iow(lp, XAE_MDIO_MC_OFFSET, mdio_mcreg); - ret = axienet_mdio_wait_until_ready(lp); + ret = axienet_mdio_enable(lp); + mutex_unlock(&lp->mii_bus->mdio_lock); if (ret < 0) return ret; @@ -1316,28 +1312,24 @@ static void axienet_dma_err_handler(unsigned long data) { u32 axienet_status; u32 cr, i; - int mdio_mcreg; struct axienet_local *lp = (struct axienet_local *) data; struct net_device *ndev = lp->ndev; struct axidma_bd *cur_p; axienet_setoptions(ndev, lp->options & ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); - mdio_mcreg = axienet_ior(lp, XAE_MDIO_MC_OFFSET); - axienet_mdio_wait_until_ready(lp); /* Disable the MDIO interface till Axi Ethernet Reset is completed. * When we do an Axi Ethernet reset, it resets the complete core - * including the MDIO. So if MDIO is not disabled when the reset - * process is started, MDIO will be broken afterwards. + * including the MDIO. MDIO must be disabled before resetting + * and re-enabled afterwards. + * Hold MDIO bus lock to avoid MDIO accesses during the reset. */ - axienet_iow(lp, XAE_MDIO_MC_OFFSET, (mdio_mcreg & - ~XAE_MDIO_MC_MDIOEN_MASK)); - + mutex_lock(&lp->mii_bus->mdio_lock); + axienet_mdio_disable(lp); __axienet_device_reset(lp, XAXIDMA_TX_CR_OFFSET); __axienet_device_reset(lp, XAXIDMA_RX_CR_OFFSET); - - axienet_iow(lp, XAE_MDIO_MC_OFFSET, mdio_mcreg); - axienet_mdio_wait_until_ready(lp); + axienet_mdio_enable(lp); + mutex_unlock(&lp->mii_bus->mdio_lock); for (i = 0; i < TX_BD_NUM; i++) { cur_p = &lp->tx_bd_v[i]; diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index cce0dc42583e..7106810793d5 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -5,6 +5,7 @@ * Copyright (c) 2009 Secret Lab Technologies, Ltd. * Copyright (c) 2010 - 2011 Michal Simek * Copyright (c) 2010 - 2011 PetaLogix + * Copyright (c) 2019 SED Systems, a division of Calian Ltd. * Copyright (c) 2010 - 2012 Xilinx, Inc. All rights reserved. */ @@ -20,7 +21,7 @@ #define DEFAULT_HOST_CLOCK 150000000 /* 150 MHz */ /* Wait till MDIO interface is ready to accept a new transaction.*/ -int axienet_mdio_wait_until_ready(struct axienet_local *lp) +static int axienet_mdio_wait_until_ready(struct axienet_local *lp) { u32 val; @@ -113,21 +114,17 @@ static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, } /** - * axienet_mdio_setup - MDIO setup function + * axienet_mdio_enable - MDIO hardware setup function * @lp: Pointer to axienet local data structure. * - * Return: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when - * mdiobus_alloc (to allocate memory for mii bus structure) fails. + * Return: 0 on success, -ETIMEDOUT on a timeout. * * Sets up the MDIO interface by initializing the MDIO clock and enabling the - * MDIO interface in hardware. Register the MDIO interface. + * MDIO interface in hardware. **/ -int axienet_mdio_setup(struct axienet_local *lp) +int axienet_mdio_enable(struct axienet_local *lp) { - int ret; u32 clk_div, host_clock; - struct mii_bus *bus; - struct device_node *mdio_node; if (lp->clk) { host_clock = clk_get_rate(lp->clk); @@ -142,8 +139,8 @@ int axienet_mdio_setup(struct axienet_local *lp) netdev_warn(lp->ndev, "Could not find CPU device node.\n"); host_clock = DEFAULT_HOST_CLOCK; } else { - ret = of_property_read_u32(np1, "clock-frequency", - &host_clock); + int ret = of_property_read_u32(np1, "clock-frequency", + &host_clock); if (ret) { netdev_warn(lp->ndev, "CPU clock-frequency property not found.\n"); host_clock = DEFAULT_HOST_CLOCK; @@ -191,10 +188,39 @@ int axienet_mdio_setup(struct axienet_local *lp) "Setting MDIO clock divisor to %u/%u Hz host clock.\n", clk_div, host_clock); - axienet_iow(lp, XAE_MDIO_MC_OFFSET, - (((u32) clk_div) | XAE_MDIO_MC_MDIOEN_MASK)); + axienet_iow(lp, XAE_MDIO_MC_OFFSET, clk_div | XAE_MDIO_MC_MDIOEN_MASK); - ret = axienet_mdio_wait_until_ready(lp); + return axienet_mdio_wait_until_ready(lp); +} + +/** + * axienet_mdio_disable - MDIO hardware disable function + * @lp: Pointer to axienet local data structure. + * + * Disable the MDIO interface in hardware. + **/ +void axienet_mdio_disable(struct axienet_local *lp) +{ + axienet_iow(lp, XAE_MDIO_MC_OFFSET, 0); +} + +/** + * axienet_mdio_setup - MDIO setup function + * @lp: Pointer to axienet local data structure. + * + * Return: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when + * mdiobus_alloc (to allocate memory for mii bus structure) fails. + * + * Sets up the MDIO interface by initializing the MDIO clock and enabling the + * MDIO interface in hardware. Register the MDIO interface. + **/ +int axienet_mdio_setup(struct axienet_local *lp) +{ + struct device_node *mdio_node; + struct mii_bus *bus; + int ret; + + ret = axienet_mdio_enable(lp); if (ret < 0) return ret; -- cgit v1.2.3 From 3b09a3fb35079106fd490241bdc8e0c31434be00 Mon Sep 17 00:00:00 2001 From: Robert Hancock Date: Thu, 6 Jun 2019 16:28:21 -0600 Subject: net: axienet: Fix MDIO bus parent node detection This driver was previously using the parent node of the specified PHY node as the device node to register the MDIO bus on. Andrew Lunn pointed out this is wrong as the PHY node is potentially not even underneath the MDIO bus for the current device instance. Find the MDIO node explicitly by looking it up by name under the controller's device node instead. This could potentially break existing device trees if they don't use "mdio" as the name for the MDIO bus, but I did not find any with various searches and Xilinx's examples all use mdio as the name so it seems like this should be relatively safe. Signed-off-by: Robert Hancock Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c') diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c index 7106810793d5..435ed308d990 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_mdio.c @@ -228,7 +228,6 @@ int axienet_mdio_setup(struct axienet_local *lp) if (!bus) return -ENOMEM; - mdio_node = of_get_parent(lp->phy_node); snprintf(bus->id, MII_BUS_ID_SIZE, "axienet-%.8llx", (unsigned long long)lp->regs_start); @@ -239,7 +238,9 @@ int axienet_mdio_setup(struct axienet_local *lp) bus->parent = lp->dev; lp->mii_bus = bus; + mdio_node = of_get_child_by_name(lp->dev->of_node, "mdio"); ret = of_mdiobus_register(bus, mdio_node); + of_node_put(mdio_node); if (ret) { mdiobus_free(bus); lp->mii_bus = NULL; -- cgit v1.2.3