From 034dd340b08be1f2f0477ad16131d609f9dbd53c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 09:22:00 -0700 Subject: Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure --- drivers/net/mdio/Kconfig | 293 ++++++++++++ drivers/net/mdio/Makefile | 37 ++ drivers/net/mdio/acpi_mdio.c | 61 +++ drivers/net/mdio/fwnode_mdio.c | 189 ++++++++ drivers/net/mdio/mdio-airoha.c | 278 ++++++++++++ drivers/net/mdio/mdio-aspeed.c | 215 +++++++++ drivers/net/mdio/mdio-bcm-iproc.c | 219 +++++++++ drivers/net/mdio/mdio-bcm-unimac.c | 366 +++++++++++++++ drivers/net/mdio/mdio-bitbang.c | 266 +++++++++++ drivers/net/mdio/mdio-cavium.c | 199 +++++++++ drivers/net/mdio/mdio-cavium.h | 123 +++++ drivers/net/mdio/mdio-gpio.c | 218 +++++++++ drivers/net/mdio/mdio-hisi-femac.c | 150 +++++++ drivers/net/mdio/mdio-i2c.c | 514 +++++++++++++++++++++ drivers/net/mdio/mdio-ipq4019.c | 408 +++++++++++++++++ drivers/net/mdio/mdio-ipq8064.c | 177 ++++++++ drivers/net/mdio/mdio-moxart.c | 185 ++++++++ drivers/net/mdio/mdio-mscc-miim.c | 391 ++++++++++++++++ drivers/net/mdio/mdio-mux-bcm-iproc.c | 352 +++++++++++++++ drivers/net/mdio/mdio-mux-bcm6368.c | 182 ++++++++ drivers/net/mdio/mdio-mux-gpio.c | 96 ++++ drivers/net/mdio/mdio-mux-meson-g12a.c | 361 +++++++++++++++ drivers/net/mdio/mdio-mux-meson-gxl.c | 163 +++++++ drivers/net/mdio/mdio-mux-mmioreg.c | 190 ++++++++ drivers/net/mdio/mdio-mux-multiplexer.c | 117 +++++ drivers/net/mdio/mdio-mux.c | 270 +++++++++++ drivers/net/mdio/mdio-mvusb.c | 113 +++++ drivers/net/mdio/mdio-octeon.c | 97 ++++ drivers/net/mdio/mdio-pic64hpsc.c | 190 ++++++++ drivers/net/mdio/mdio-realtek-rtl9300.c | 764 ++++++++++++++++++++++++++++++++ drivers/net/mdio/mdio-regmap.c | 93 ++++ drivers/net/mdio/mdio-sun4i.c | 178 ++++++++ drivers/net/mdio/mdio-thunder.c | 148 +++++++ drivers/net/mdio/mdio-xgene.c | 451 +++++++++++++++++++ drivers/net/mdio/of_mdio.c | 476 ++++++++++++++++++++ 35 files changed, 8530 insertions(+) create mode 100644 drivers/net/mdio/Kconfig create mode 100644 drivers/net/mdio/Makefile create mode 100644 drivers/net/mdio/acpi_mdio.c create mode 100644 drivers/net/mdio/fwnode_mdio.c create mode 100644 drivers/net/mdio/mdio-airoha.c 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-bcm6368.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-meson-gxl.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-pic64hpsc.c create mode 100644 drivers/net/mdio/mdio-realtek-rtl9300.c create mode 100644 drivers/net/mdio/mdio-regmap.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 create mode 100644 drivers/net/mdio/of_mdio.c (limited to 'drivers/net/mdio') diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig new file mode 100644 index 000000000..a05229838 --- /dev/null +++ b/drivers/net/mdio/Kconfig @@ -0,0 +1,293 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# MDIO Layer Configuration +# + +menu "MDIO controller drivers" + depends on PHYLIB + +config FWNODE_MDIO + def_tristate (ACPI || OF) || COMPILE_TEST + help + FWNODE MDIO bus (Ethernet PHY) accessors + +config OF_MDIO + def_tristate OF + select FIXED_PHY + help + OpenFirmware MDIO bus (Ethernet PHY) accessors + +config ACPI_MDIO + def_tristate ACPI + help + ACPI MDIO bus (Ethernet PHY) accessors + +config MDIO_AIROHA + tristate "Airoha AN7583 MDIO bus controller" + depends on ARCH_AIROHA || COMPILE_TEST + help + This module provides a driver for the MDIO busses found in the + Airoha AN7583 SoC's. + +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 + 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 REGMAP_MMIO + 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_PIC64HPSC + tristate "PIC64-HPSC/HX MDIO interface support" + depends on ARCH_MICROCHIP || COMPILE_TEST + depends on HAS_IOMEM && OF_MDIO + help + This driver supports the MDIO interface found on the PIC64-HPSC/HX + SoCs. + +config MDIO_IPQ4019 + tristate "Qualcomm IPQ4019 MDIO interface support" + depends on HAS_IOMEM && OF_MDIO + depends on COMMON_CLK + help + This driver supports the MDIO interface found in Qualcomm + IPQ40xx, IPQ60xx, IPQ807x and IPQ50xx 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_REALTEK_RTL9300 + tristate "Realtek RTL9300 MDIO interface support" + depends on MACH_REALTEK_RTL || COMPILE_TEST + help + This driver supports the MDIO interface found in the Realtek + RTL9300 family of Ethernet switches with integrated SoC. + +config MDIO_REGMAP + tristate + help + This driver allows using MDIO devices that are not sitting on a + regular MDIO bus, but still exposes the standard 802.3 register + layout. It's regmap-based so that it can be used on integrated, + memory-mapped PHYs, SPI PHYs and so on. A new virtual MDIO bus is + created, and its read/write operations are mapped to the underlying + regmap. Users willing to use this driver must explicitly select + REGMAP. + +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. + +menu "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_MESON_GXL + tristate "Amlogic GXL 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 GXL SoC. The multiplexer connects either the external + or the internal MDIO bus to the parent bus. + +config MDIO_BUS_MUX_BCM6368 + tristate "Broadcom BCM6368 MDIO bus multiplexers" + depends on OF && OF_MDIO && (BMIPS_GENERIC || COMPILE_TEST) + select MDIO_BUS_MUX + default BMIPS_GENERIC + help + This module provides a driver for MDIO bus multiplexers found in + BCM6368 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_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_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. + +endmenu +endmenu diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile new file mode 100644 index 000000000..048586746 --- /dev/null +++ b/drivers/net/mdio/Makefile @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Linux MDIO bus drivers + +obj-$(CONFIG_ACPI_MDIO) += acpi_mdio.o +obj-$(CONFIG_FWNODE_MDIO) += fwnode_mdio.o +obj-$(CONFIG_OF_MDIO) += of_mdio.o + +obj-$(CONFIG_MDIO_AIROHA) += mdio-airoha.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_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_PIC64HPSC) += mdio-pic64hpsc.o +obj-$(CONFIG_MDIO_REALTEK_RTL9300) += mdio-realtek-rtl9300.o +obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.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_BCM6368) += mdio-mux-bcm6368.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_MESON_GXL) += mdio-mux-meson-gxl.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/acpi_mdio.c b/drivers/net/mdio/acpi_mdio.c new file mode 100644 index 000000000..5d0f11f28 --- /dev/null +++ b/drivers/net/mdio/acpi_mdio.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ACPI helpers for the MDIO (Ethernet PHY) API + * + * This file provides helper functions for extracting PHY device information + * out of the ACPI ASL and using it to populate an mii_bus. + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Calvin Johnson "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ACPI MDIO bus (Ethernet PHY) accessors"); + +/** + * __acpi_mdiobus_register - Register mii_bus and create PHYs from the ACPI ASL. + * @mdio: pointer to mii_bus structure + * @fwnode: pointer to fwnode of MDIO bus. This fwnode is expected to represent + * @owner: module owning this @mdio object. + * an ACPI device object corresponding to the MDIO bus and its children are + * expected to correspond to the PHY devices on that bus. + * + * This function registers the mii_bus structure and registers a phy_device + * for each child node of @fwnode. + */ +int __acpi_mdiobus_register(struct mii_bus *mdio, struct fwnode_handle *fwnode, + struct module *owner) +{ + struct fwnode_handle *child; + u32 addr; + int ret; + + /* Mask out all PHYs from auto probing. */ + mdio->phy_mask = GENMASK(31, 0); + ret = __mdiobus_register(mdio, owner); + if (ret) + return ret; + + ACPI_COMPANION_SET(&mdio->dev, to_acpi_device_node(fwnode)); + + /* Loop over the child nodes and register a phy_device for each PHY */ + fwnode_for_each_child_node(fwnode, child) { + ret = acpi_get_local_address(ACPI_HANDLE_FWNODE(child), &addr); + if (ret || addr >= PHY_MAX_ADDR) + continue; + + ret = fwnode_mdiobus_register_phy(mdio, child, addr); + if (ret == -ENODEV) + dev_err(&mdio->dev, + "MDIO device at address %d is missing.\n", + addr); + } + return 0; +} +EXPORT_SYMBOL(__acpi_mdiobus_register); diff --git a/drivers/net/mdio/fwnode_mdio.c b/drivers/net/mdio/fwnode_mdio.c new file mode 100644 index 000000000..ba7091518 --- /dev/null +++ b/drivers/net/mdio/fwnode_mdio.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fwnode helpers for the MDIO (Ethernet PHY) API + * + * This file provides helper functions for extracting PHY device information + * out of the fwnode and using it to populate an mii_bus. + */ + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Calvin Johnson "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors"); + +static struct pse_control * +fwnode_find_pse_control(struct fwnode_handle *fwnode, + struct phy_device *phydev) +{ + struct pse_control *psec; + struct device_node *np; + + if (!IS_ENABLED(CONFIG_PSE_CONTROLLER)) + return NULL; + + np = to_of_node(fwnode); + if (!np) + return NULL; + + psec = of_pse_control_get(np, phydev); + if (PTR_ERR(psec) == -ENOENT) + return NULL; + + return psec; +} + +static struct mii_timestamper * +fwnode_find_mii_timestamper(struct fwnode_handle *fwnode) +{ + struct mii_timestamper *mii_ts; + struct of_phandle_args arg; + int err; + + if (is_acpi_node(fwnode)) + return NULL; + + err = of_parse_phandle_with_fixed_args(to_of_node(fwnode), + "timestamper", 1, 0, &arg); + if (err == -ENOENT) + return NULL; + else if (err) + return ERR_PTR(err); + + if (arg.args_count != 1) { + mii_ts = ERR_PTR(-EINVAL); + goto put_node; + } + + mii_ts = register_mii_timestamper(arg.np, arg.args[0]); + +put_node: + of_node_put(arg.np); + return mii_ts; +} + +int fwnode_mdiobus_phy_device_register(struct mii_bus *mdio, + struct phy_device *phy, + struct fwnode_handle *child, u32 addr) +{ + int rc; + + rc = fwnode_irq_get(child, 0); + /* Don't wait forever if the IRQ provider doesn't become available, + * just fall back to poll mode + */ + if (rc == -EPROBE_DEFER) + rc = driver_deferred_probe_check_state(&phy->mdio.dev); + if (rc == -EPROBE_DEFER) + return rc; + + if (rc > 0) { + phy->irq = rc; + mdio->irq[addr] = rc; + } else { + phy->irq = mdio->irq[addr]; + } + + if (fwnode_property_read_bool(child, "broken-turn-around")) + mdio->phy_ignore_ta_mask |= 1 << addr; + + /* Associate the fwnode with the device structure so it + * can be looked up later + */ + fwnode_handle_get(child); + device_set_node(&phy->mdio.dev, child); + + /* All data is now stored in the phy struct; + * register it + */ + rc = phy_device_register(phy); + if (rc) { + device_set_node(&phy->mdio.dev, NULL); + fwnode_handle_put(child); + return rc; + } + + dev_dbg(&mdio->dev, "registered phy fwnode %pfw at address %i\n", + child, addr); + return 0; +} +EXPORT_SYMBOL(fwnode_mdiobus_phy_device_register); + +int fwnode_mdiobus_register_phy(struct mii_bus *bus, + struct fwnode_handle *child, u32 addr) +{ + struct mii_timestamper *mii_ts = NULL; + struct pse_control *psec = NULL; + struct phy_device *phy; + bool is_c45; + u32 phy_id; + int rc; + + mii_ts = fwnode_find_mii_timestamper(child); + if (IS_ERR(mii_ts)) + return PTR_ERR(mii_ts); + + is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"); + if (is_c45 || fwnode_get_phy_id(child, &phy_id)) + phy = get_phy_device(bus, addr, is_c45); + else + phy = phy_device_create(bus, addr, phy_id, 0, NULL); + if (IS_ERR(phy)) { + rc = PTR_ERR(phy); + goto clean_mii_ts; + } + + if (is_acpi_node(child)) { + phy->irq = bus->irq[addr]; + + /* Associate the fwnode with the device structure so it + * can be looked up later. + */ + phy->mdio.dev.fwnode = fwnode_handle_get(child); + + /* All data is now stored in the phy struct, so register it */ + rc = phy_device_register(phy); + if (rc) { + phy->mdio.dev.fwnode = NULL; + fwnode_handle_put(child); + goto clean_phy; + } + } else if (is_of_node(child)) { + rc = fwnode_mdiobus_phy_device_register(bus, phy, child, addr); + if (rc) + goto clean_phy; + } + + psec = fwnode_find_pse_control(child, phy); + if (IS_ERR(psec)) { + rc = PTR_ERR(psec); + goto unregister_phy; + } + + phy->psec = psec; + + /* 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; + +unregister_phy: + if (is_acpi_node(child) || is_of_node(child)) + phy_device_remove(phy); +clean_phy: + phy_device_free(phy); +clean_mii_ts: + unregister_mii_timestamper(mii_ts); + + return rc; +} +EXPORT_SYMBOL(fwnode_mdiobus_register_phy); diff --git a/drivers/net/mdio/mdio-airoha.c b/drivers/net/mdio/mdio-airoha.c new file mode 100644 index 000000000..52e747512 --- /dev/null +++ b/drivers/net/mdio/mdio-airoha.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Airoha AN7583 MDIO interface driver + * + * Copyright (C) 2025 Christian Marangi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* MII address register definitions */ +#define AN7583_MII_BUSY BIT(31) +#define AN7583_MII_RDY BIT(30) /* RO signal BUS is ready */ +#define AN7583_MII_CL22_REG_ADDR GENMASK(29, 25) +#define AN7583_MII_CL45_DEV_ADDR AN7583_MII_CL22_REG_ADDR +#define AN7583_MII_PHY_ADDR GENMASK(24, 20) +#define AN7583_MII_CMD GENMASK(19, 18) +#define AN7583_MII_CMD_CL22_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) +#define AN7583_MII_CMD_CL22_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) +#define AN7583_MII_CMD_CL45_ADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x0) +#define AN7583_MII_CMD_CL45_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) +#define AN7583_MII_CMD_CL45_POSTREAD_INCADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) +#define AN7583_MII_CMD_CL45_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x3) +#define AN7583_MII_ST GENMASK(17, 16) +#define AN7583_MII_ST_CL45 FIELD_PREP_CONST(AN7583_MII_ST, 0x0) +#define AN7583_MII_ST_CL22 FIELD_PREP_CONST(AN7583_MII_ST, 0x1) +#define AN7583_MII_RWDATA GENMASK(15, 0) +#define AN7583_MII_CL45_REG_ADDR AN7583_MII_RWDATA + +#define AN7583_MII_MDIO_DELAY_USEC 100 +#define AN7583_MII_MDIO_RETRY_MSEC 100 + +struct airoha_mdio_data { + u32 base_addr; + struct regmap *regmap; + struct clk *clk; + struct reset_control *reset; +}; + +static int airoha_mdio_wait_busy(struct airoha_mdio_data *priv) +{ + u32 busy; + + return regmap_read_poll_timeout(priv->regmap, priv->base_addr, busy, + !(busy & AN7583_MII_BUSY), + AN7583_MII_MDIO_DELAY_USEC, + AN7583_MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); +} + +static void airoha_mdio_reset(struct airoha_mdio_data *priv) +{ + /* There seems to be Hardware bug where AN7583_MII_RWDATA + * is not wiped in the context of unconnected PHY and the + * previous read value is returned. + * + * Example: (only one PHY on the BUS at 0x1f) + * - read at 0x1f report at 0x2 0x7500 + * - read at 0x0 report 0x7500 on every address + * + * To workaround this, we reset the Mdio BUS at every read + * to have consistent values on read operation. + */ + reset_control_assert(priv->reset); + reset_control_deassert(priv->reset); +} + +static int airoha_mdio_read(struct mii_bus *bus, int addr, int regnum) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + airoha_mdio_reset(priv); + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | + AN7583_MII_CMD_CL22_READ; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, priv->base_addr, &val); + if (ret) + return ret; + + return FIELD_GET(AN7583_MII_RWDATA, val); +} + +static int airoha_mdio_write(struct mii_bus *bus, int addr, int regnum, + u16 value) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | + AN7583_MII_CMD_CL22_WRITE; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); + val |= FIELD_PREP(AN7583_MII_RWDATA, value); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + + return ret; +} + +static int airoha_mdio_cl45_read(struct mii_bus *bus, int addr, int devnum, + int regnum) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + airoha_mdio_reset(priv); + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_ADDR; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_READ; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, priv->base_addr, &val); + if (ret) + return ret; + + return FIELD_GET(AN7583_MII_RWDATA, val); +} + +static int airoha_mdio_cl45_write(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 value) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_ADDR; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_WRITE; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + val |= FIELD_PREP(AN7583_MII_RWDATA, value); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + + return ret; +} + +static int airoha_mdio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct airoha_mdio_data *priv; + struct mii_bus *bus; + u32 addr, freq; + int ret; + + ret = of_property_read_u32(dev->of_node, "reg", &addr); + if (ret) + return ret; + + bus = devm_mdiobus_alloc_size(dev, sizeof(*priv)); + if (!bus) + return -ENOMEM; + + priv = bus->priv; + priv->base_addr = addr; + priv->regmap = device_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + reset_control_deassert(priv->reset); + + bus->name = "airoha_mdio_bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); + bus->parent = dev; + bus->read = airoha_mdio_read; + bus->write = airoha_mdio_write; + bus->read_c45 = airoha_mdio_cl45_read; + bus->write_c45 = airoha_mdio_cl45_write; + + /* Check if a custom frequency is defined in DT or default to 2.5 MHz */ + if (of_property_read_u32(dev->of_node, "clock-frequency", &freq)) + freq = 2500000; + + ret = clk_set_rate(priv->clk, freq); + if (ret) + return ret; + + ret = devm_of_mdiobus_register(dev, bus, dev->of_node); + if (ret) { + reset_control_assert(priv->reset); + return ret; + } + + return 0; +} + +static const struct of_device_id airoha_mdio_dt_ids[] = { + { .compatible = "airoha,an7583-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, airoha_mdio_dt_ids); + +static struct platform_driver airoha_mdio_driver = { + .probe = airoha_mdio_probe, + .driver = { + .name = "airoha-mdio", + .of_match_table = airoha_mdio_dt_ids, + }, +}; + +module_platform_driver(airoha_mdio_driver); + +MODULE_DESCRIPTION("Airoha AN7583 MDIO interface driver"); +MODULE_AUTHOR("Christian Marangi "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-aspeed.c b/drivers/net/mdio/mdio-aspeed.c new file mode 100644 index 000000000..d6b9004c6 --- /dev/null +++ b/drivers/net/mdio/mdio-aspeed.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2019 IBM Corp. */ + +#include +#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 MDIO_C45_OP_ADDR 0b00 +#define MDIO_C45_OP_WRITE 0b01 +#define MDIO_C45_OP_PREAD 0b10 +#define MDIO_C45_OP_READ 0b11 +#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; + struct reset_control *reset; +}; + +static int aspeed_mdio_op(struct mii_bus *bus, u8 st, u8 op, u8 phyad, u8 regad, + u16 data) +{ + struct aspeed_mdio *ctx = bus->priv; + u32 ctrl; + + dev_dbg(&bus->dev, "%s: st: %u op: %u, phyad: %u, regad: %u, data: %u\n", + __func__, st, op, phyad, regad, data); + + ctrl = ASPEED_MDIO_CTRL_FIRE + | FIELD_PREP(ASPEED_MDIO_CTRL_ST, st) + | FIELD_PREP(ASPEED_MDIO_CTRL_OP, op) + | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, phyad) + | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regad) + | FIELD_PREP(ASPEED_MDIO_DATA_MIIRDATA, data); + + iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); + + /* Workaround for read-after-write issue. + * The controller may return stale data if a read follows immediately + * after a write. A dummy read forces the hardware to update its + * internal state, ensuring that the next real read returns correct data. + */ + ioread32(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_get_data(struct mii_bus *bus) +{ + struct aspeed_mdio *ctx = bus->priv; + u32 data; + int rc; + + 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_read_c22(struct mii_bus *bus, int addr, int regnum) +{ + int rc; + + rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C22, MDIO_C22_OP_READ, + addr, regnum, 0); + if (rc < 0) + return rc; + + return aspeed_mdio_get_data(bus); +} + +static int aspeed_mdio_write_c22(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C22, MDIO_C22_OP_WRITE, + addr, regnum, val); +} + +static int aspeed_mdio_read_c45(struct mii_bus *bus, int addr, int devad, + int regnum) +{ + int rc; + + rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR, + addr, devad, regnum); + if (rc < 0) + return rc; + + rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_READ, + addr, devad, 0); + if (rc < 0) + return rc; + + return aspeed_mdio_get_data(bus); +} + +static int aspeed_mdio_write_c45(struct mii_bus *bus, int addr, int devad, + int regnum, u16 val) +{ + int rc; + + rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR, + addr, devad, regnum); + if (rc < 0) + return rc; + + return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_WRITE, + addr, devad, val); +} + +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); + + ctx->reset = devm_reset_control_get_optional_shared(&pdev->dev, NULL); + if (IS_ERR(ctx->reset)) + return PTR_ERR(ctx->reset); + + reset_control_deassert(ctx->reset); + + 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_c22; + bus->write = aspeed_mdio_write_c22; + bus->read_c45 = aspeed_mdio_read_c45; + bus->write_c45 = aspeed_mdio_write_c45; + + rc = of_mdiobus_register(bus, pdev->dev.of_node); + if (rc) { + dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); + reset_control_assert(ctx->reset); + return rc; + } + + platform_set_drvdata(pdev, bus); + + return 0; +} + +static void aspeed_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = (struct mii_bus *)platform_get_drvdata(pdev); + struct aspeed_mdio *ctx = bus->priv; + + reset_control_assert(ctx->reset); + mdiobus_unregister(bus); +} + +static const struct of_device_id aspeed_mdio_of_match[] = { + { .compatible = "aspeed,ast2600-mdio", }, + { }, +}; +MODULE_DEVICE_TABLE(of, aspeed_mdio_of_match); + +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"); +MODULE_DESCRIPTION("ASPEED MDIO bus controller"); diff --git a/drivers/net/mdio/mdio-bcm-iproc.c b/drivers/net/mdio/mdio-bcm-iproc.c new file mode 100644 index 000000000..91690b496 --- /dev/null +++ b/drivers/net/mdio/mdio-bcm-iproc.c @@ -0,0 +1,219 @@ +// 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 void 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); +} + +#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 000000000..37e35f282 --- /dev/null +++ b/drivers/net/mdio/mdio-bcm-unimac.c @@ -0,0 +1,366 @@ +// 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 int unimac_mdio_poll(void *wait_func_data) +{ + struct unimac_mdio_priv *priv = wait_func_data; + u32 val; + + /* + * C22 transactions should take ~25 usec, will need to adjust + * if C45 support is added. + */ + udelay(30); + + return read_poll_timeout(unimac_mdio_readl, val, !(val & MDIO_START_BUSY), + 2000, 100000, false, priv, MDIO_CMD); +} + +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; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + /* 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) + goto out; + + 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)) { + ret = -EIO; + goto out; + } + + ret = cmd & 0xffff; +out: + clk_disable_unprepare(priv->clk); + return ret; +} + +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; + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + /* 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); + + ret = priv->wait_func(priv->wait_func_data); + clk_disable_unprepare(priv->clk); + + return ret; +} + +/* 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 int unimac_mdio_clk_set(struct unimac_mdio_priv *priv) +{ + unsigned long rate; + u32 reg, div; + int ret; + + /* Keep the hardware default values */ + if (!priv->clk_freq) + return 0; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + rate = clk_get_rate(priv->clk); + if (!rate) + rate = 250000000; + + div = (rate / (2 * priv->clk_freq)) - 1; + if (div & ~MDIO_CLK_DIV_MASK) { + dev_warn(priv->mii_bus->parent, + "Ignoring MDIO clock frequency request: %d vs. rate: %ld\n", + priv->clk_freq, rate); + ret = 0; + goto out; + } + + /* The MDIO clock is the reference clock (typically 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); +out: + clk_disable_unprepare(priv->clk); + return ret; +} + +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; + } + + if (of_property_read_u32(np, "clock-frequency", &priv->clk_freq)) + priv->clk_freq = 0; + + priv->mii_bus = mdiobus_alloc(); + if (!priv->mii_bus) + return -ENOMEM; + + 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; + priv->clk = pdata->clk; + } else { + bus->name = "unimac MII bus"; + priv->wait_func_data = priv; + priv->wait_func = unimac_mdio_poll; + priv->clk = devm_clk_get_optional(&pdev->dev, NULL); + } + + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + goto out_mdio_free; + } + + 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 = unimac_mdio_clk_set(priv); + if (ret) + goto out_mdio_free; + + 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); + return ret; +} + +static void 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); +} + +static int __maybe_unused unimac_mdio_resume(struct device *d) +{ + struct unimac_mdio_priv *priv = dev_get_drvdata(d); + + return unimac_mdio_clk_set(priv); +} + +static SIMPLE_DEV_PM_OPS(unimac_mdio_pm_ops, + NULL, unimac_mdio_resume); + +static const struct of_device_id unimac_mdio_ids[] = { + { .compatible = "brcm,asp-v3.0-mdio", }, + { .compatible = "brcm,asp-v2.2-mdio", }, + { .compatible = "brcm,asp-v2.1-mdio", }, + { .compatible = "brcm,bcm6846-mdio", }, + { .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 000000000..f88639297 --- /dev/null +++ b/drivers/net/mdio/mdio-bitbang.c @@ -0,0 +1,266 @@ +// 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. Theoretically clause 45 and normal + devices can exist on the same bus. Normal devices should ignore the MDIO_ADDR + phase. */ +static void mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, int dev_addr, + int reg) +{ + 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); +} + +static int mdiobb_read_common(struct mii_bus *bus, int phy) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + int ret, i; + + 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; +} + +int mdiobb_read_c22(struct mii_bus *bus, int phy, int reg) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + mdiobb_cmd(ctrl, ctrl->op_c22_read, phy, reg); + + return mdiobb_read_common(bus, phy); +} +EXPORT_SYMBOL(mdiobb_read_c22); + +int mdiobb_read_c45(struct mii_bus *bus, int phy, int devad, int reg) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + mdiobb_cmd_addr(ctrl, phy, devad, reg); + mdiobb_cmd(ctrl, MDIO_C45_READ, phy, devad); + + return mdiobb_read_common(bus, phy); +} +EXPORT_SYMBOL(mdiobb_read_c45); + +static int mdiobb_write_common(struct mii_bus *bus, u16 val) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + /* 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; +} + +int mdiobb_write_c22(struct mii_bus *bus, int phy, int reg, u16 val) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + mdiobb_cmd(ctrl, ctrl->op_c22_write, phy, reg); + + return mdiobb_write_common(bus, val); +} +EXPORT_SYMBOL(mdiobb_write_c22); + +int mdiobb_write_c45(struct mii_bus *bus, int phy, int devad, int reg, u16 val) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + mdiobb_cmd_addr(ctrl, phy, devad, reg); + mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, devad); + + return mdiobb_write_common(bus, val); +} +EXPORT_SYMBOL(mdiobb_write_c45); + +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_c22; + bus->write = mdiobb_write_c22; + bus->read_c45 = mdiobb_read_c45; + bus->write_c45 = mdiobb_write_c45; + + bus->priv = ctrl; + if (!ctrl->override_op_c22) { + ctrl->op_c22_read = MDIO_READ; + ctrl->op_c22_write = MDIO_WRITE; + } + + 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"); +MODULE_DESCRIPTION("Bitbanged MDIO buses"); diff --git a/drivers/net/mdio/mdio-cavium.c b/drivers/net/mdio/mdio-cavium.c new file mode 100644 index 000000000..100e46a70 --- /dev/null +++ b/drivers/net/mdio/mdio-cavium.c @@ -0,0 +1,199 @@ +// 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 devad, 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); + + 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 = devad; + 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_c22(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; + int timeout = 1000; + + cavium_mdiobus_set_mode(p, C22); + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = 1; /* MDIO_CLAUSE_22_READ */ + 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_c22); + +int cavium_mdiobus_read_c45(struct mii_bus *bus, int phy_id, int devad, + int regnum) +{ + struct cavium_mdiobus *p = bus->priv; + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_rd_dat smi_rd; + int timeout = 1000; + int r; + + r = cavium_mdiobus_c45_addr(p, phy_id, devad, regnum); + if (r < 0) + return r; + + smi_cmd.u64 = 0; + smi_cmd.s.phy_op = 3; /* MDIO_CLAUSE_45_READ */ + 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_c45); + +int cavium_mdiobus_write_c22(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; + int timeout = 1000; + + 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 = 0; /* MDIO_CLAUSE_22_WRITE */ + 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_c22); + +int cavium_mdiobus_write_c45(struct mii_bus *bus, int phy_id, int devad, + int regnum, u16 val) +{ + struct cavium_mdiobus *p = bus->priv; + union cvmx_smix_cmd smi_cmd; + union cvmx_smix_wr_dat smi_wr; + int timeout = 1000; + int r; + + r = cavium_mdiobus_c45_addr(p, phy_id, devad, regnum); + if (r < 0) + return r; + + 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 = 1; /* MDIO_CLAUSE_45_WRITE */ + smi_cmd.s.phy_adr = phy_id; + smi_cmd.s.reg_adr = devad; + 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_c45); + +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 000000000..71b8e20cd --- /dev/null +++ b/drivers/net/mdio/mdio-cavium.h @@ -0,0 +1,123 @@ +/* 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_c22(struct mii_bus *bus, int phy_id, int regnum); +int cavium_mdiobus_write_c22(struct mii_bus *bus, int phy_id, int regnum, + u16 val); +int cavium_mdiobus_read_c45(struct mii_bus *bus, int phy_id, int devad, + int regnum); +int cavium_mdiobus_write_c45(struct mii_bus *bus, int phy_id, int devad, + 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 000000000..958d1c660 --- /dev/null +++ b/drivers/net/mdio/mdio-gpio.c @@ -0,0 +1,218 @@ +// 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 + +#define MDIO_GPIO_MDC 0 +#define MDIO_GPIO_MDIO 1 +#define MDIO_GPIO_MDO 2 + +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 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, sizeof(new_bus->id), "gpio-%x", bus_id); + else + strscpy(new_bus->id, "gpio", sizeof(new_bus->id)); + + if (device_is_compatible(dev, "microchip,mdio-smi0")) { + bitbang->ctrl.op_c22_read = 0; + bitbang->ctrl.op_c22_write = 0; + bitbang->ctrl.override_op_c22 = 1; + } + + 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 void mdio_gpio_remove(struct platform_device *pdev) +{ + mdio_gpio_bus_destroy(&pdev->dev); +} + +static const struct of_device_id mdio_gpio_of_match[] = { + { .compatible = "virtual,mdio-gpio", }, + { .compatible = "microchip,mdio-smi0" }, + { /* 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 000000000..d78a1dc36 --- /dev/null +++ b/drivers/net/mdio/mdio-hisi-femac.c @@ -0,0 +1,150 @@ +// 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 void 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); +} + +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 000000000..ed20352a5 --- /dev/null +++ b/drivers/net/mdio/mdio-i2c.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MDIO I2C bridge + * + * Copyright (C) 2015-2016 Russell King + * Copyright (C) 2021 Marek Behun + * + * 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 +#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_default_c45(struct mii_bus *bus, int phy_id, int devad, + 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 (devad >= 0) { + *p++ = 0x20 | devad; + *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_default_c45(struct mii_bus *bus, int phy_id, + int devad, 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 (devad >= 0) { + *p++ = devad; + *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; +} + +static int i2c_mii_read_default_c22(struct mii_bus *bus, int phy_id, int reg) +{ + return i2c_mii_read_default_c45(bus, phy_id, -1, reg); +} + +static int i2c_mii_write_default_c22(struct mii_bus *bus, int phy_id, int reg, + u16 val) +{ + return i2c_mii_write_default_c45(bus, phy_id, -1, reg, val); +} + +static int smbus_byte_mii_read_default_c22(struct mii_bus *bus, int phy_id, + int reg) +{ + struct i2c_adapter *i2c = bus->priv; + union i2c_smbus_data smbus_data; + int val = 0, ret; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0; + + i2c_lock_bus(i2c, I2C_LOCK_SEGMENT); + + ret = __i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, + I2C_SMBUS_READ, reg, + I2C_SMBUS_BYTE_DATA, &smbus_data); + if (ret < 0) + goto unlock; + + val = (smbus_data.byte & 0xff) << 8; + + ret = __i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, + I2C_SMBUS_READ, reg, + I2C_SMBUS_BYTE_DATA, &smbus_data); + +unlock: + i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); + + if (ret < 0) + return ret; + + val |= smbus_data.byte & 0xff; + + return val; +} + +static int smbus_byte_mii_write_default_c22(struct mii_bus *bus, int phy_id, + int reg, u16 val) +{ + struct i2c_adapter *i2c = bus->priv; + union i2c_smbus_data smbus_data; + int ret; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0; + + smbus_data.byte = (val & 0xff00) >> 8; + + i2c_lock_bus(i2c, I2C_LOCK_SEGMENT); + + ret = __i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, + I2C_SMBUS_WRITE, reg, + I2C_SMBUS_BYTE_DATA, &smbus_data); + if (ret < 0) + goto unlock; + + smbus_data.byte = val & 0xff; + + ret = __i2c_smbus_xfer(i2c, i2c_mii_phy_addr(phy_id), 0, + I2C_SMBUS_WRITE, reg, + I2C_SMBUS_BYTE_DATA, &smbus_data); + +unlock: + i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); + + return ret < 0 ? ret : 0; +} + +/* RollBall SFPs do not access internal PHY via I2C address 0x56, but + * instead via address 0x51, when SFP page is set to 0x03 and password to + * 0xffffffff. + * + * address size contents description + * ------- ---- -------- ----------- + * 0x80 1 CMD 0x01/0x02/0x04 for write/read/done + * 0x81 1 DEV Clause 45 device + * 0x82 2 REG Clause 45 register + * 0x84 2 VAL Register value + */ +#define ROLLBALL_PHY_I2C_ADDR 0x51 + +#define ROLLBALL_PASSWORD (SFP_VSL + 3) + +#define ROLLBALL_CMD_ADDR 0x80 +#define ROLLBALL_DATA_ADDR 0x81 + +#define ROLLBALL_CMD_WRITE 0x01 +#define ROLLBALL_CMD_READ 0x02 +#define ROLLBALL_CMD_DONE 0x04 + +#define SFP_PAGE_ROLLBALL_MDIO 3 + +static int __i2c_transfer_err(struct i2c_adapter *i2c, struct i2c_msg *msgs, + int num) +{ + int ret; + + ret = __i2c_transfer(i2c, msgs, num); + if (ret < 0) + return ret; + else if (ret != num) + return -EIO; + else + return 0; +} + +static int __i2c_rollball_get_page(struct i2c_adapter *i2c, int bus_addr, + u8 *page) +{ + struct i2c_msg msgs[2]; + u8 addr = SFP_PAGE; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &addr; + + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = page; + + return __i2c_transfer_err(i2c, msgs, 2); +} + +static int __i2c_rollball_set_page(struct i2c_adapter *i2c, int bus_addr, + u8 page) +{ + struct i2c_msg msg; + u8 buf[2]; + + buf[0] = SFP_PAGE; + buf[1] = page; + + msg.addr = bus_addr; + msg.flags = 0; + msg.len = 2; + msg.buf = buf; + + return __i2c_transfer_err(i2c, &msg, 1); +} + +/* In order to not interfere with other SFP code (which possibly may manipulate + * SFP_PAGE), for every transfer we do this: + * 1. lock the bus + * 2. save content of SFP_PAGE + * 3. set SFP_PAGE to 3 + * 4. do the transfer + * 5. restore original SFP_PAGE + * 6. unlock the bus + * Note that one might think that steps 2 to 5 could be theoretically done all + * in one call to i2c_transfer (by constructing msgs array in such a way), but + * unfortunately tests show that this does not work :-( Changed SFP_PAGE does + * not take into account until i2c_transfer() is done. + */ +static int i2c_transfer_rollball(struct i2c_adapter *i2c, + struct i2c_msg *msgs, int num) +{ + int ret, main_err = 0; + u8 saved_page; + + i2c_lock_bus(i2c, I2C_LOCK_SEGMENT); + + /* save original page */ + ret = __i2c_rollball_get_page(i2c, msgs->addr, &saved_page); + if (ret) + goto unlock; + + /* change to RollBall MDIO page */ + ret = __i2c_rollball_set_page(i2c, msgs->addr, SFP_PAGE_ROLLBALL_MDIO); + if (ret) + goto unlock; + + /* do the transfer; we try to restore original page if this fails */ + ret = __i2c_transfer_err(i2c, msgs, num); + if (ret) + main_err = ret; + + /* restore original page */ + ret = __i2c_rollball_set_page(i2c, msgs->addr, saved_page); + +unlock: + i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT); + + return main_err ? : ret; +} + +static int i2c_rollball_mii_poll(struct mii_bus *bus, int bus_addr, u8 *buf, + size_t len) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msgs[2]; + u8 cmd_addr, tmp, *res; + int i, ret; + + cmd_addr = ROLLBALL_CMD_ADDR; + + res = buf ? buf : &tmp; + len = buf ? len : 1; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &cmd_addr; + + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = res; + + /* By experiment it takes up to 70 ms to access a register for these + * SFPs. Sleep 20ms between iterations and try 10 times. + */ + i = 10; + do { + msleep(20); + + ret = i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret) + return ret; + + if (*res == ROLLBALL_CMD_DONE) + return 0; + } while (i-- > 0); + + dev_dbg(&bus->dev, "poll timed out\n"); + + return -ETIMEDOUT; +} + +static int i2c_rollball_mii_cmd(struct mii_bus *bus, int bus_addr, u8 cmd, + u8 *data, size_t len) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msgs[2]; + u8 cmdbuf[2]; + + cmdbuf[0] = ROLLBALL_CMD_ADDR; + cmdbuf[1] = cmd; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = len; + msgs[0].buf = data; + + msgs[1].addr = bus_addr; + msgs[1].flags = 0; + msgs[1].len = sizeof(cmdbuf); + msgs[1].buf = cmdbuf; + + return i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs)); +} + +static int i2c_mii_read_rollball(struct mii_bus *bus, int phy_id, int devad, + int reg) +{ + u8 buf[4], res[6]; + int bus_addr, ret; + u16 val; + + bus_addr = i2c_mii_phy_addr(phy_id); + if (bus_addr != ROLLBALL_PHY_I2C_ADDR) + return 0xffff; + + buf[0] = ROLLBALL_DATA_ADDR; + buf[1] = devad; + buf[2] = (reg >> 8) & 0xff; + buf[3] = reg & 0xff; + + ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_READ, buf, + sizeof(buf)); + if (ret < 0) + return ret; + + ret = i2c_rollball_mii_poll(bus, bus_addr, res, sizeof(res)); + if (ret == -ETIMEDOUT) + return 0xffff; + else if (ret < 0) + return ret; + + val = res[4] << 8 | res[5]; + + return val; +} + +static int i2c_mii_write_rollball(struct mii_bus *bus, int phy_id, int devad, + int reg, u16 val) +{ + int bus_addr, ret; + u8 buf[6]; + + bus_addr = i2c_mii_phy_addr(phy_id); + if (bus_addr != ROLLBALL_PHY_I2C_ADDR) + return 0; + + buf[0] = ROLLBALL_DATA_ADDR; + buf[1] = devad; + buf[2] = (reg >> 8) & 0xff; + buf[3] = reg & 0xff; + buf[4] = val >> 8; + buf[5] = val & 0xff; + + ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_WRITE, buf, + sizeof(buf)); + if (ret < 0) + return ret; + + ret = i2c_rollball_mii_poll(bus, bus_addr, NULL, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int i2c_mii_init_rollball(struct i2c_adapter *i2c) +{ + struct i2c_msg msg; + u8 pw[5]; + int ret; + + pw[0] = ROLLBALL_PASSWORD; + pw[1] = 0xff; + pw[2] = 0xff; + pw[3] = 0xff; + pw[4] = 0xff; + + msg.addr = ROLLBALL_PHY_I2C_ADDR; + msg.flags = 0; + msg.len = sizeof(pw); + msg.buf = pw; + + ret = i2c_transfer(i2c, &msg, 1); + if (ret < 0) + return ret; + else if (ret != 1) + return -EIO; + else + return 0; +} + +static bool mdio_i2c_check_functionality(struct i2c_adapter *i2c, + enum mdio_i2c_proto protocol) +{ + if (i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return true; + + if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA) && + protocol == MDIO_I2C_MARVELL_C22) + return true; + + return false; +} + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c, + enum mdio_i2c_proto protocol) +{ + struct mii_bus *mii; + int ret; + + if (!mdio_i2c_check_functionality(i2c, protocol)) + 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->priv = i2c; + + /* Only use SMBus if we have no other choice */ + if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA) && + !i2c_check_functionality(i2c, I2C_FUNC_I2C)) { + mii->read = smbus_byte_mii_read_default_c22; + mii->write = smbus_byte_mii_write_default_c22; + return mii; + } + + switch (protocol) { + case MDIO_I2C_ROLLBALL: + ret = i2c_mii_init_rollball(i2c); + if (ret < 0) { + dev_err(parent, + "Cannot initialize RollBall MDIO I2C protocol: %d\n", + ret); + mdiobus_free(mii); + return ERR_PTR(ret); + } + + mii->read_c45 = i2c_mii_read_rollball; + mii->write_c45 = i2c_mii_write_rollball; + break; + default: + mii->read = i2c_mii_read_default_c22; + mii->write = i2c_mii_write_default_c22; + mii->read_c45 = i2c_mii_read_default_c45; + mii->write_c45 = i2c_mii_write_default_c45; + break; + } + + 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 000000000..d9a94df48 --- /dev/null +++ b/drivers/net/mdio/mdio-ipq4019.c @@ -0,0 +1,408 @@ +// 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 +#include + +#define MDIO_MODE_REG 0x40 +#define MDIO_MODE_MDC_MODE BIT(12) +/* 0 = Clause 22, 1 = Clause 45 */ +#define MDIO_MODE_C45 BIT(8) +#define MDIO_MODE_DIV_MASK GENMASK(7, 0) +#define MDIO_MODE_DIV(x) FIELD_PREP(MDIO_MODE_DIV_MASK, (x) - 1) +#define MDIO_MODE_DIV_1 0x0 +#define MDIO_MODE_DIV_2 0x1 +#define MDIO_MODE_DIV_4 0x3 +#define MDIO_MODE_DIV_8 0x7 +#define MDIO_MODE_DIV_16 0xf +#define MDIO_MODE_DIV_32 0x1f +#define MDIO_MODE_DIV_64 0x3f +#define MDIO_MODE_DIV_128 0x7f +#define MDIO_MODE_DIV_256 0xff +#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 MDIO_CMD_ACCESS_CODE_C45_ADDR 0 +#define MDIO_CMD_ACCESS_CODE_C45_WRITE 1 +#define MDIO_CMD_ACCESS_CODE_C45_READ 2 + +#define IPQ4019_MDIO_TIMEOUT 10000 +#define IPQ4019_MDIO_SLEEP 10 + +/* MDIO clock source frequency is fixed to 100M */ +#define IPQ_MDIO_CLK_RATE 100000000 + +#define IPQ_PHY_SET_DELAY_US 100000 + +struct ipq4019_mdio_data { + void __iomem *membase; + void __iomem *eth_ldo_rdy; + struct clk *mdio_clk; + unsigned int mdc_rate; +}; + +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_c45(struct mii_bus *bus, int mii_id, int mmd, + int reg) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int data; + unsigned int cmd; + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + data = readl(priv->membase + MDIO_MODE_REG); + + data |= MDIO_MODE_C45; + + writel(data, priv->membase + MDIO_MODE_REG); + + /* issue the phy address and mmd */ + writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); + + /* issue reg */ + writel(reg, priv->membase + MDIO_DATA_WRITE_REG); + + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; + + /* issue read command */ + writel(cmd, priv->membase + MDIO_CMD_REG); + + /* Wait read complete */ + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_READ; + + writel(cmd, priv->membase + MDIO_CMD_REG); + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + /* Read and return data */ + return readl(priv->membase + MDIO_DATA_READ_REG); +} + +static int ipq4019_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int data; + unsigned int cmd; + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + data = readl(priv->membase + MDIO_MODE_REG); + + data &= ~MDIO_MODE_C45; + + writel(data, priv->membase + MDIO_MODE_REG); + + /* 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_c45(struct mii_bus *bus, int mii_id, int mmd, + int reg, u16 value) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int data; + unsigned int cmd; + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + data = readl(priv->membase + MDIO_MODE_REG); + + data |= MDIO_MODE_C45; + + writel(data, priv->membase + MDIO_MODE_REG); + + /* issue the phy address and mmd */ + writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); + + /* issue reg */ + writel(reg, priv->membase + MDIO_DATA_WRITE_REG); + + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; + + writel(cmd, priv->membase + MDIO_CMD_REG); + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + /* issue write data */ + writel(value, priv->membase + MDIO_DATA_WRITE_REG); + + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_WRITE; + writel(cmd, priv->membase + MDIO_CMD_REG); + + /* Wait write complete */ + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + return 0; +} + +static int ipq4019_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct ipq4019_mdio_data *priv = bus->priv; + unsigned int data; + unsigned int cmd; + + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + /* Enter Clause 22 mode */ + data = readl(priv->membase + MDIO_MODE_REG); + + data &= ~MDIO_MODE_C45; + + writel(data, priv->membase + MDIO_MODE_REG); + + /* 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); + + /* issue write command */ + cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; + + writel(cmd, priv->membase + MDIO_CMD_REG); + + /* Wait write complete */ + if (ipq4019_mdio_wait_busy(bus)) + return -ETIMEDOUT; + + return 0; +} + +static int ipq4019_mdio_set_div(struct ipq4019_mdio_data *priv) +{ + unsigned long ahb_rate; + int div; + u32 val; + + /* If we don't have a clock for AHB use the fixed value */ + ahb_rate = IPQ_MDIO_CLK_RATE; + if (priv->mdio_clk) + ahb_rate = clk_get_rate(priv->mdio_clk); + + /* MDC rate is ahb_rate/(MDIO_MODE_DIV + 1) + * While supported, internal documentation doesn't + * assure correct functionality of the MDIO bus + * with divider of 1, 2 or 4. + */ + for (div = 8; div <= 256; div *= 2) { + /* The requested rate is supported by the div */ + if (priv->mdc_rate == DIV_ROUND_UP(ahb_rate, div)) { + val = readl(priv->membase + MDIO_MODE_REG); + val &= ~MDIO_MODE_DIV_MASK; + val |= MDIO_MODE_DIV(div); + writel(val, priv->membase + MDIO_MODE_REG); + + return 0; + } + } + + /* The requested rate is not supported */ + return -EINVAL; +} + +static int ipq_mdio_reset(struct mii_bus *bus) +{ + struct ipq4019_mdio_data *priv = bus->priv; + u32 val; + int ret; + + /* To indicate CMN_PLL that ethernet_ldo has been ready if platform resource 1 + * is specified in the device tree. + */ + if (priv->eth_ldo_rdy) { + val = readl(priv->eth_ldo_rdy); + val |= BIT(0); + writel(val, priv->eth_ldo_rdy); + fsleep(IPQ_PHY_SET_DELAY_US); + } + + /* Configure MDIO clock source frequency if clock is specified in the device tree */ + ret = clk_set_rate(priv->mdio_clk, IPQ_MDIO_CLK_RATE); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->mdio_clk); + if (ret) + return ret; + + mdelay(10); + + /* Restore MDC rate */ + return ipq4019_mdio_set_div(priv); +} + +static void ipq4019_mdio_select_mdc_rate(struct platform_device *pdev, + struct ipq4019_mdio_data *priv) +{ + unsigned long ahb_rate; + int div; + u32 val; + + /* MDC rate defined in DT, we don't have to decide a default value */ + if (!of_property_read_u32(pdev->dev.of_node, "clock-frequency", + &priv->mdc_rate)) + return; + + /* If we don't have a clock for AHB use the fixed value */ + ahb_rate = IPQ_MDIO_CLK_RATE; + if (priv->mdio_clk) + ahb_rate = clk_get_rate(priv->mdio_clk); + + /* Check what is the current div set */ + val = readl(priv->membase + MDIO_MODE_REG); + div = FIELD_GET(MDIO_MODE_DIV_MASK, val); + + /* div is not set to the default value of /256 + * Probably someone changed that (bootloader, other drivers) + * Keep this and don't overwrite it. + */ + if (div != MDIO_MODE_DIV_256) { + priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div + 1); + return; + } + + /* If div is /256 assume nobody have set this value and + * try to find one MDC rate that is close the 802.3 spec of + * 2.5MHz + */ + for (div = 256; div >= 8; div /= 2) { + /* Stop as soon as we found a divider that + * reached the closest value to 2.5MHz + */ + if (DIV_ROUND_UP(ahb_rate, div) > 2500000) + break; + + priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div); + } +} + +static int ipq4019_mdio_probe(struct platform_device *pdev) +{ + struct ipq4019_mdio_data *priv; + struct mii_bus *bus; + struct resource *res; + 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); + + priv->mdio_clk = devm_clk_get_optional(&pdev->dev, "gcc_mdio_ahb_clk"); + if (IS_ERR(priv->mdio_clk)) + return PTR_ERR(priv->mdio_clk); + + ipq4019_mdio_select_mdc_rate(pdev, priv); + ret = ipq4019_mdio_set_div(priv); + if (ret) + return ret; + + /* The platform resource is provided on the chipset IPQ5018 */ + /* This resource is optional */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + priv->eth_ldo_rdy = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->eth_ldo_rdy)) + return PTR_ERR(priv->eth_ldo_rdy); + } + + bus->name = "ipq4019_mdio"; + bus->read = ipq4019_mdio_read_c22; + bus->write = ipq4019_mdio_write_c22; + bus->read_c45 = ipq4019_mdio_read_c45; + bus->write_c45 = ipq4019_mdio_write_c45; + bus->reset = ipq_mdio_reset; + 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 void ipq4019_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); +} + +static const struct of_device_id ipq4019_mdio_dt_ids[] = { + { .compatible = "qcom,ipq4019-mdio" }, + { .compatible = "qcom,ipq5018-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 000000000..6253a9ab8 --- /dev/null +++ b/drivers/net/mdio/mdio-ipq8064.c @@ -0,0 +1,177 @@ +// 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 + +/* MII address register definitions */ +#define MII_ADDR_REG_ADDR 0x10 +#define MII_BUSY BIT(0) +#define MII_WRITE BIT(1) +#define MII_CLKRANGE(x) ((x) << 2) +#define MII_CLKRANGE_60_100M MII_CLKRANGE(0) +#define MII_CLKRANGE_100_150M MII_CLKRANGE(1) +#define MII_CLKRANGE_20_35M MII_CLKRANGE(2) +#define MII_CLKRANGE_35_60M MII_CLKRANGE(3) +#define MII_CLKRANGE_150_250M MII_CLKRANGE(4) +#define MII_CLKRANGE_250_300M MII_CLKRANGE(5) +#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; + + 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(10, 13); + + 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; + + 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); + + /* For the specific reg 31 extra time is needed or the next + * read will produce garbage data. + */ + if (reg_offset == 31) + usleep_range(30, 43); + else + usleep_range(10, 13); + + return ipq8064_mdio_wait_busy(priv); +} + +static const struct regmap_config ipq8064_mdio_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .can_multi_write = false, + /* the mdio lock is used by any user of this mdio driver */ + .disable_locking = true, + + .cache_type = REGCACHE_NONE, +}; + +static int +ipq8064_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct ipq8064_mdio *priv; + struct resource res; + struct mii_bus *bus; + void __iomem *base; + int ret; + + if (of_address_to_resource(np, 0, &res)) + return -ENOMEM; + + base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); + if (!base) + return -ENOMEM; + + 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 = devm_regmap_init_mmio(&pdev->dev, base, + &ipq8064_mdio_regmap_config); + if (IS_ERR(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 void ipq8064_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); +} + +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 000000000..9853be6f0 --- /dev/null +++ b/drivers/net/mdio/mdio-moxart.c @@ -0,0 +1,185 @@ +// 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_MAC_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_MAC_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 void moxart_mdio_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + + mdiobus_unregister(bus); + mdiobus_free(bus); +} + +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 000000000..259944d37 --- /dev/null +++ b/drivers/net/mdio/mdio-mscc-miim.c @@ -0,0 +1,391 @@ +// 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 +#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_MIIM_REG_CFG 0x10 +#define MSCC_MIIM_CFG_PRESCALE_MASK GENMASK(7, 0) + +#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 + +#define LAN966X_CUPHY_COMMON_CFG 0x0 +#define CUPHY_COMMON_CFG_RESET_N BIT(0) + +struct mscc_miim_info { + unsigned int phy_reset_offset; + unsigned int phy_reset_bits; +}; + +struct mscc_miim_dev { + struct regmap *regs; + int mii_status_offset; + bool ignore_read_errors; + struct regmap *phy_regs; + const struct mscc_miim_info *info; + struct clk *clk; + u32 bus_freq; +}; + +/* 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_readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us)\ +({ \ + if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS)) \ + readx_poll_timeout_atomic(op, addr, val, cond, delay_us, \ + timeout_us); \ + readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us); \ +}) + +static int mscc_miim_status(struct mii_bus *bus) +{ + struct mscc_miim_dev *miim = bus->priv; + int val, ret; + + ret = regmap_read(miim->regs, + MSCC_MIIM_REG_STATUS + miim->mii_status_offset, &val); + if (ret < 0) { + WARN_ONCE(1, "mscc miim status read error %d\n", ret); + return ret; + } + + return val; +} + +static int mscc_miim_wait_ready(struct mii_bus *bus) +{ + u32 val; + + return mscc_readx_poll_timeout(mscc_miim_status, bus, val, + !(val & MSCC_MIIM_STATUS_STAT_BUSY), 50, + 10000); +} + +static int mscc_miim_wait_pending(struct mii_bus *bus) +{ + u32 val; + + return mscc_readx_poll_timeout(mscc_miim_status, bus, 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; + + ret = regmap_write(miim->regs, + MSCC_MIIM_REG_CMD + miim->mii_status_offset, + MSCC_MIIM_CMD_VLD | + (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | + (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | + MSCC_MIIM_CMD_OPR_READ); + + if (ret < 0) { + WARN_ONCE(1, "mscc miim write cmd reg error %d\n", ret); + goto out; + } + + ret = mscc_miim_wait_ready(bus); + if (ret) + goto out; + + ret = regmap_read(miim->regs, + MSCC_MIIM_REG_DATA + miim->mii_status_offset, &val); + if (ret < 0) { + WARN_ONCE(1, "mscc miim read data reg error %d\n", ret); + goto out; + } + + if (!miim->ignore_read_errors && !!(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; + + ret = regmap_write(miim->regs, + MSCC_MIIM_REG_CMD + miim->mii_status_offset, + 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); + + if (ret < 0) + WARN_ONCE(1, "mscc miim write error %d\n", ret); +out: + return ret; +} + +static int mscc_miim_reset(struct mii_bus *bus) +{ + struct mscc_miim_dev *miim = bus->priv; + unsigned int offset, bits; + int ret; + + if (!miim->phy_regs) + return 0; + + offset = miim->info->phy_reset_offset; + bits = miim->info->phy_reset_bits; + + ret = regmap_update_bits(miim->phy_regs, offset, bits, 0); + if (ret < 0) { + WARN_ONCE(1, "mscc reset set error %d\n", ret); + return ret; + } + + ret = regmap_update_bits(miim->phy_regs, offset, bits, bits); + if (ret < 0) { + WARN_ONCE(1, "mscc reset clear error %d\n", ret); + return ret; + } + + mdelay(500); + + return 0; +} + +static const struct regmap_config mscc_miim_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static const struct regmap_config mscc_miim_phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .name = "phy", +}; + +int mscc_miim_setup(struct device *dev, struct mii_bus **pbus, const char *name, + struct regmap *mii_regmap, int status_offset, + bool ignore_read_errors) +{ + struct mscc_miim_dev *miim; + struct mii_bus *bus; + + bus = devm_mdiobus_alloc_size(dev, sizeof(*miim)); + if (!bus) + return -ENOMEM; + + bus->name = name; + 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(dev)); + bus->parent = dev; + + miim = bus->priv; + + *pbus = bus; + + miim->regs = mii_regmap; + miim->mii_status_offset = status_offset; + miim->ignore_read_errors = ignore_read_errors; + + *pbus = bus; + + return 0; +} +EXPORT_SYMBOL(mscc_miim_setup); + +static int mscc_miim_clk_set(struct mii_bus *bus) +{ + struct mscc_miim_dev *miim = bus->priv; + unsigned long rate; + u32 div; + + /* Keep the current settings */ + if (!miim->bus_freq) + return 0; + + rate = clk_get_rate(miim->clk); + + div = DIV_ROUND_UP(rate, 2 * miim->bus_freq) - 1; + if (div == 0 || div & ~MSCC_MIIM_CFG_PRESCALE_MASK) { + dev_err(&bus->dev, "Incorrect MDIO clock frequency\n"); + return -EINVAL; + } + + return regmap_update_bits(miim->regs, MSCC_MIIM_REG_CFG, + MSCC_MIIM_CFG_PRESCALE_MASK, div); +} + +static int mscc_miim_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct regmap *mii_regmap, *phy_regmap; + struct device *dev = &pdev->dev; + struct reset_control *reset; + struct mscc_miim_dev *miim; + struct mii_bus *bus; + int ret; + + reset = devm_reset_control_get_optional_shared(dev, "switch"); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "Failed to get reset\n"); + + reset_control_reset(reset); + + mii_regmap = ocelot_regmap_from_resource(pdev, 0, + &mscc_miim_regmap_config); + if (IS_ERR(mii_regmap)) + return dev_err_probe(dev, PTR_ERR(mii_regmap), + "Unable to create MIIM regmap\n"); + + /* This resource is optional */ + phy_regmap = ocelot_regmap_from_resource_optional(pdev, 1, + &mscc_miim_phy_regmap_config); + if (IS_ERR(phy_regmap)) + return dev_err_probe(dev, PTR_ERR(phy_regmap), + "Unable to create phy register regmap\n"); + + ret = mscc_miim_setup(dev, &bus, "mscc_miim", mii_regmap, 0, false); + if (ret < 0) { + dev_err(dev, "Unable to setup the MDIO bus\n"); + return ret; + } + + miim = bus->priv; + miim->phy_regs = phy_regmap; + + miim->info = device_get_match_data(dev); + if (!miim->info) + return -EINVAL; + + miim->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(miim->clk)) + return PTR_ERR(miim->clk); + + of_property_read_u32(np, "clock-frequency", &miim->bus_freq); + + if (miim->bus_freq && !miim->clk) { + dev_err(dev, "cannot use clock-frequency without a clock\n"); + return -EINVAL; + } + + ret = clk_prepare_enable(miim->clk); + if (ret) + return ret; + + ret = mscc_miim_clk_set(bus); + if (ret) + goto out_disable_clk; + + ret = of_mdiobus_register(bus, np); + if (ret < 0) { + dev_err_probe(dev, ret, "Cannot register MDIO bus\n"); + goto out_disable_clk; + } + + platform_set_drvdata(pdev, bus); + + return 0; + +out_disable_clk: + clk_disable_unprepare(miim->clk); + return ret; +} + +static void mscc_miim_remove(struct platform_device *pdev) +{ + struct mii_bus *bus = platform_get_drvdata(pdev); + struct mscc_miim_dev *miim = bus->priv; + + clk_disable_unprepare(miim->clk); + mdiobus_unregister(bus); +} + +static const struct mscc_miim_info mscc_ocelot_miim_info = { + .phy_reset_offset = MSCC_PHY_REG_PHY_CFG, + .phy_reset_bits = PHY_CFG_PHY_ENA | PHY_CFG_PHY_COMMON_RESET | + PHY_CFG_PHY_RESET, +}; + +static const struct mscc_miim_info microchip_lan966x_miim_info = { + .phy_reset_offset = LAN966X_CUPHY_COMMON_CFG, + .phy_reset_bits = CUPHY_COMMON_CFG_RESET_N, +}; + +static const struct of_device_id mscc_miim_match[] = { + { + .compatible = "mscc,ocelot-miim", + .data = &mscc_ocelot_miim_info + }, { + .compatible = "microchip,lan966x-miim", + .data = µchip_lan966x_miim_info + }, + { } +}; +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 000000000..8ba0917a9 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-bcm-iproc.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2016 Broadcom + */ +#include +#include +#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 derive 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, bool c45, + 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 (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_c22(struct mii_bus *bus, int phyid, int reg) +{ + struct iproc_mdiomux_desc *md = bus->priv; + int ret; + + ret = start_miim_ops(md->base, false, phyid, reg, 0, MDIO_CTRL_READ_OP); + if (ret < 0) + dev_err(&bus->dev, "mdiomux c22 read operation failed!!!"); + + return ret; +} + +static int iproc_mdiomux_read_c45(struct mii_bus *bus, int phyid, int devad, + int reg) +{ + struct iproc_mdiomux_desc *md = bus->priv; + int ret; + + ret = start_miim_ops(md->base, true, phyid, reg | devad << 16, 0, + MDIO_CTRL_READ_OP); + if (ret < 0) + dev_err(&bus->dev, "mdiomux read c45 operation failed!!!"); + + return ret; +} + +static int iproc_mdiomux_write_c22(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, false, phyid, reg, val, + MDIO_CTRL_WRITE_OP); + if (ret < 0) + dev_err(&bus->dev, "mdiomux write c22 operation failed!!!"); + + return ret; +} + +static int iproc_mdiomux_write_c45(struct mii_bus *bus, + int phyid, int devad, int reg, u16 val) +{ + struct iproc_mdiomux_desc *md = bus->priv; + int ret; + + /* Write val at reg offset */ + ret = start_miim_ops(md->base, true, phyid, reg | devad << 16, val, + MDIO_CTRL_WRITE_OP); + if (ret < 0) + dev_err(&bus->dev, "mdiomux write c45 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; + + md->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(md->base)) + return PTR_ERR(md->base); + if (!IS_ALIGNED(res->start, SZ_4K)) { + /* 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 = ALIGN_DOWN(res->start, SZ_4K); + res->end = res->start + MDIO_REG_ADDR_SPACE_SIZE - 1; + } + + 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_c22; + bus->write = iproc_mdiomux_write_c22; + bus->read_c45 = iproc_mdiomux_read_c45; + bus->write_c45 = iproc_mdiomux_write_c45; + + 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 void 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); +} + +#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-bcm6368.c b/drivers/net/mdio/mdio-mux-bcm6368.c new file mode 100644 index 000000000..476f8b72d --- /dev/null +++ b/drivers/net/mdio/mdio-mux-bcm6368.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Broadcom BCM6368 mdiomux bus controller driver + * + * Copyright (C) 2021 Álvaro Fernández Rojas + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MDIOC_REG 0x0 +#define MDIOC_EXT_MASK BIT(16) +#define MDIOC_REG_SHIFT 20 +#define MDIOC_PHYID_SHIFT 25 +#define MDIOC_RD_MASK BIT(30) +#define MDIOC_WR_MASK BIT(31) + +#define MDIOD_REG 0x4 + +struct bcm6368_mdiomux_desc { + void *mux_handle; + void __iomem *base; + struct device *dev; + struct mii_bus *mii_bus; + int ext_phy; +}; + +static int bcm6368_mdiomux_read(struct mii_bus *bus, int phy_id, int loc) +{ + struct bcm6368_mdiomux_desc *md = bus->priv; + uint32_t reg; + int ret; + + __raw_writel(0, md->base + MDIOC_REG); + + reg = MDIOC_RD_MASK | + (phy_id << MDIOC_PHYID_SHIFT) | + (loc << MDIOC_REG_SHIFT); + if (md->ext_phy) + reg |= MDIOC_EXT_MASK; + + __raw_writel(reg, md->base + MDIOC_REG); + udelay(50); + ret = __raw_readw(md->base + MDIOD_REG); + + return ret; +} + +static int bcm6368_mdiomux_write(struct mii_bus *bus, int phy_id, int loc, + uint16_t val) +{ + struct bcm6368_mdiomux_desc *md = bus->priv; + uint32_t reg; + + __raw_writel(0, md->base + MDIOC_REG); + + reg = MDIOC_WR_MASK | + (phy_id << MDIOC_PHYID_SHIFT) | + (loc << MDIOC_REG_SHIFT); + if (md->ext_phy) + reg |= MDIOC_EXT_MASK; + reg |= val; + + __raw_writel(reg, md->base + MDIOC_REG); + udelay(50); + + return 0; +} + +static int bcm6368_mdiomux_switch_fn(int current_child, int desired_child, + void *data) +{ + struct bcm6368_mdiomux_desc *md = data; + + md->ext_phy = desired_child; + + return 0; +} + +static int bcm6368_mdiomux_probe(struct platform_device *pdev) +{ + struct bcm6368_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) + return -EINVAL; + + /* + * Just ioremap, as this MDIO block is usually integrated into an + * Ethernet MAC controller register range + */ + md->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!md->base) { + dev_err(&pdev->dev, "failed to ioremap register\n"); + return -ENOMEM; + } + + md->mii_bus = devm_mdiobus_alloc(&pdev->dev); + if (!md->mii_bus) { + dev_err(&pdev->dev, "mdiomux bus alloc failed\n"); + return -ENOMEM; + } + + bus = md->mii_bus; + bus->priv = md; + bus->name = "BCM6368 MDIO mux bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); + bus->parent = &pdev->dev; + bus->read = bcm6368_mdiomux_read; + bus->write = bcm6368_mdiomux_write; + bus->phy_mask = 0x3f; + bus->dev.of_node = pdev->dev.of_node; + + rc = mdiobus_register(bus); + if (rc) { + dev_err(&pdev->dev, "mdiomux registration failed\n"); + return rc; + } + + platform_set_drvdata(pdev, md); + + rc = mdio_mux_init(md->dev, md->dev->of_node, + bcm6368_mdiomux_switch_fn, &md->mux_handle, md, + md->mii_bus); + if (rc) { + dev_info(md->dev, "mdiomux initialization failed\n"); + goto out_register; + } + + dev_info(&pdev->dev, "Broadcom BCM6368 MDIO mux bus\n"); + + return 0; + +out_register: + mdiobus_unregister(bus); + return rc; +} + +static void bcm6368_mdiomux_remove(struct platform_device *pdev) +{ + struct bcm6368_mdiomux_desc *md = platform_get_drvdata(pdev); + + mdio_mux_uninit(md->mux_handle); + mdiobus_unregister(md->mii_bus); +} + +static const struct of_device_id bcm6368_mdiomux_ids[] = { + { .compatible = "brcm,bcm6368-mdio-mux", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, bcm6368_mdiomux_ids); + +static struct platform_driver bcm6368_mdiomux_driver = { + .driver = { + .name = "bcm6368-mdio-mux", + .of_match_table = bcm6368_mdiomux_ids, + }, + .probe = bcm6368_mdiomux_probe, + .remove = bcm6368_mdiomux_remove, +}; +module_platform_driver(bcm6368_mdiomux_driver); + +MODULE_AUTHOR("Álvaro Fernández Rojas "); +MODULE_DESCRIPTION("BCM6368 mdiomux bus controller driver"); +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 000000000..fefa40ea5 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-gpio.c @@ -0,0 +1,96 @@ +// 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_multi_set_value_cansleep(s->gpios, 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 void 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); +} + +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 000000000..08d6a6c93 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-meson-g12a.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2019 Baylibre, SAS. + * Author: Jerome Brunet + */ + +#include +#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 { + void __iomem *regs; + void *mux_handle; + 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 even though 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) +{ + u32 value; + int ret; + + /* Enable the phy clock */ + if (!__clk_is_enabled(priv->pll)) { + ret = clk_prepare_enable(priv->pll); + if (ret) + return ret; + } + + /* Initialize ephy control */ + writel(EPHY_G12A_ID, priv->regs + ETH_PHY_CNTL0); + + /* Make sure we get a 0 -> 1 transition on the enable bit */ + value = 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; + writel(value, 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); + + value |= PHY_CNTL1_PHY_ENB; + writel(value, priv->regs + ETH_PHY_CNTL1); + + /* The phy needs a bit of time to power up */ + mdelay(10); + + 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 (__clk_is_enabled(priv->pll)) + clk_disable_unprepare(priv->pll); + + 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)) + return dev_err_probe(dev, PTR_ERR(clk), + "Missing clock %s\n", in_name); + + 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; + struct clk *pclk; + 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); + + pclk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(pclk)) + return dev_err_probe(dev, PTR_ERR(pclk), + "failed to get peripheral clock\n"); + + /* Register PLL in CCF */ + ret = g12a_ephy_glue_clk_register(dev); + if (ret) + return ret; + + ret = mdio_mux_init(dev, dev->of_node, g12a_mdio_switch_fn, + &priv->mux_handle, dev, NULL); + if (ret) + dev_err_probe(dev, ret, "mdio multiplexer init failed\n"); + + return ret; +} + +static void g12a_mdio_mux_remove(struct platform_device *pdev) +{ + struct g12a_mdio_mux *priv = platform_get_drvdata(pdev); + + mdio_mux_uninit(priv->mux_handle); + + if (__clk_is_enabled(priv->pll)) + clk_disable_unprepare(priv->pll); +} + +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-meson-gxl.c b/drivers/net/mdio/mdio-mux-meson-gxl.c new file mode 100644 index 000000000..3dd12a8c8 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-meson-gxl.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Baylibre, SAS. + * Author: Jerome Brunet + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ETH_REG2 0x0 +#define REG2_PHYID GENMASK(21, 0) +#define EPHY_GXL_ID 0x110181 +#define REG2_LEDACT GENMASK(23, 22) +#define REG2_LEDLINK GENMASK(25, 24) +#define REG2_DIV4SEL BIT(27) +#define REG2_REVERSED BIT(28) +#define REG2_ADCBYPASS BIT(30) +#define REG2_CLKINSEL BIT(31) +#define ETH_REG3 0x4 +#define REG3_ENH BIT(3) +#define REG3_CFGMODE GENMASK(6, 4) +#define REG3_AUTOMDIX BIT(7) +#define REG3_PHYADDR GENMASK(12, 8) +#define REG3_PWRUPRST BIT(21) +#define REG3_PWRDOWN BIT(22) +#define REG3_LEDPOL BIT(23) +#define REG3_PHYMDI BIT(26) +#define REG3_CLKINEN BIT(29) +#define REG3_PHYIP BIT(30) +#define REG3_PHYEN BIT(31) +#define ETH_REG4 0x8 +#define REG4_PWRUPRSTSIG BIT(0) + +#define MESON_GXL_MDIO_EXTERNAL_ID 0 +#define MESON_GXL_MDIO_INTERNAL_ID 1 + +struct gxl_mdio_mux { + void __iomem *regs; + void *mux_handle; +}; + +static void gxl_enable_internal_mdio(struct gxl_mdio_mux *priv) +{ + u32 val; + + /* Setup the internal phy */ + val = (REG3_ENH | + FIELD_PREP(REG3_CFGMODE, 0x7) | + REG3_AUTOMDIX | + FIELD_PREP(REG3_PHYADDR, 8) | + REG3_LEDPOL | + REG3_PHYMDI | + REG3_CLKINEN | + REG3_PHYIP); + + writel(REG4_PWRUPRSTSIG, priv->regs + ETH_REG4); + writel(val, priv->regs + ETH_REG3); + mdelay(10); + + /* NOTE: The HW kept the phy id configurable at runtime. + * The id below is arbitrary. It is the one used in the vendor code. + * The only constraint is that it must match the one in + * drivers/net/phy/meson-gxl.c to properly match the PHY. + */ + writel(REG2_REVERSED | FIELD_PREP(REG2_PHYID, EPHY_GXL_ID), + priv->regs + ETH_REG2); + + /* Enable the internal phy */ + val |= REG3_PHYEN; + writel(val, priv->regs + ETH_REG3); + writel(0, priv->regs + ETH_REG4); + + /* The phy needs a bit of time to power up */ + mdelay(10); +} + +static void gxl_enable_external_mdio(struct gxl_mdio_mux *priv) +{ + /* Reset the mdio bus mux to the external phy */ + writel(0, priv->regs + ETH_REG3); +} + +static int gxl_mdio_switch_fn(int current_child, int desired_child, + void *data) +{ + struct gxl_mdio_mux *priv = dev_get_drvdata(data); + + if (current_child == desired_child) + return 0; + + switch (desired_child) { + case MESON_GXL_MDIO_EXTERNAL_ID: + gxl_enable_external_mdio(priv); + break; + case MESON_GXL_MDIO_INTERNAL_ID: + gxl_enable_internal_mdio(priv); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct of_device_id gxl_mdio_mux_match[] = { + { .compatible = "amlogic,gxl-mdio-mux", }, + {}, +}; +MODULE_DEVICE_TABLE(of, gxl_mdio_mux_match); + +static int gxl_mdio_mux_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gxl_mdio_mux *priv; + struct clk *rclk; + 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); + + rclk = devm_clk_get_enabled(dev, "ref"); + if (IS_ERR(rclk)) + return dev_err_probe(dev, PTR_ERR(rclk), + "failed to get reference clock\n"); + + ret = mdio_mux_init(dev, dev->of_node, gxl_mdio_switch_fn, + &priv->mux_handle, dev, NULL); + if (ret) + dev_err_probe(dev, ret, "mdio multiplexer init failed\n"); + + return ret; +} + +static void gxl_mdio_mux_remove(struct platform_device *pdev) +{ + struct gxl_mdio_mux *priv = platform_get_drvdata(pdev); + + mdio_mux_uninit(priv->mux_handle); +} + +static struct platform_driver gxl_mdio_mux_driver = { + .probe = gxl_mdio_mux_probe, + .remove = gxl_mdio_mux_remove, + .driver = { + .name = "gxl-mdio-mux", + .of_match_table = gxl_mdio_mux_match, + }, +}; +module_platform_driver(gxl_mdio_mux_driver); + +MODULE_DESCRIPTION("Amlogic GXL MDIO multiplexer driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-mux-mmioreg.c b/drivers/net/mdio/mdio-mux-mmioreg.c new file mode 100644 index 000000000..9c4b1efd0 --- /dev/null +++ b/drivers/net/mdio/mdio-mux-mmioreg.c @@ -0,0 +1,190 @@ +// 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 *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) + return dev_err_probe(&pdev->dev, ret, + "could not obtain memory map for node %pOF\n", np); + 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)) + return dev_err_probe(&pdev->dev, -EINVAL, + "only 8/16/32-bit registers are supported\n"); + + iprop = of_get_property(np, "mux-mask", &len); + if (!iprop || len != sizeof(uint32_t)) + return dev_err_probe(&pdev->dev, -ENODEV, + "missing or invalid mux-mask property\n"); + if (be32_to_cpup(iprop) >= BIT(s->iosize * 8)) + return dev_err_probe(&pdev->dev, -EINVAL, + "only 8/16/32-bit registers are supported\n"); + 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_scoped(np, np2) { + u64 reg; + + if (of_property_read_reg(np2, 0, ®, NULL)) + return dev_err_probe(&pdev->dev, -ENODEV, + "mdio-mux child node %pOF is missing a 'reg' property\n", + np2); + if ((u32)reg & ~s->mask) + return dev_err_probe(&pdev->dev, -ENODEV, + "mdio-mux child node %pOF has a 'reg' value with unmasked bits\n", + np2); + } + + ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, + mdio_mux_mmioreg_switch_fn, + &s->mux_handle, s, NULL); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register mdio-mux bus %pOF\n", np); + + pdev->dev.platform_data = s; + + return 0; +} + +static void 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); +} + +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 000000000..8e11960fc --- /dev/null +++ b/drivers/net/mdio/mdio-mux-multiplexer.c @@ -0,0 +1,117 @@ +// 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)) + return dev_err_probe(&pdev->dev, PTR_ERR(s->muxc), + "Failed to get mux\n"); + + 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 void 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); +} + +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 000000000..fe0e46bd7 --- /dev/null +++ b/drivers/net/mdio/mdio-mux.c @@ -0,0 +1,270 @@ +// 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; +} + +static int mdio_mux_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, + 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_c45(pb->mii_bus, phy_id, dev_addr, 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 mdio_mux_write_c45(struct mii_bus *bus, int phy_id, int dev_addr, + 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_c45(pb->mii_bus, phy_id, dev_addr, regnum, val); +out: + mutex_unlock(&pb->mii_bus->mdio_lock); + + return r; +} + +static int parent_count; + +static void mdio_mux_uninit_children(struct mdio_mux_parent_bus *pb) +{ + struct mdio_mux_child_bus *cb = pb->children; + + while (cb) { + mdiobus_unregister(cb->mii_bus); + mdiobus_free(cb->mii_bus); + cb = cb->next; + } +} + +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: %pe\n", + child_bus_node, ERR_PTR(r)); + continue; + } + + cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL); + if (!cb) { + ret_val = -ENOMEM; + goto err_loop; + } + cb->bus_number = v; + cb->parent = pb; + + cb->mii_bus = mdiobus_alloc(); + if (!cb->mii_bus) { + ret_val = -ENOMEM; + goto err_loop; + } + cb->mii_bus->priv = cb; + + cb->mii_bus->name = "mdio_mux"; + snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x.%x", + cb->mii_bus->name, pb->parent_id, v); + cb->mii_bus->parent = dev; + if (parent_bus->read) + cb->mii_bus->read = mdio_mux_read; + if (parent_bus->write) + cb->mii_bus->write = mdio_mux_write; + if (parent_bus->read_c45) + cb->mii_bus->read_c45 = mdio_mux_read_c45; + if (parent_bus->write_c45) + cb->mii_bus->write_c45 = mdio_mux_write_c45; + r = of_mdiobus_register(cb->mii_bus, child_bus_node); + if (r) { + mdiobus_free(cb->mii_bus); + if (r == -EPROBE_DEFER) { + ret_val = r; + goto err_loop; + } + devm_kfree(dev, cb); + dev_err(dev, + "Error: Failed to register MDIO bus for child %pOF: %pe\n", + child_bus_node, ERR_PTR(r)); + } 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"); + +err_loop: + mdio_mux_uninit_children(pb); + of_node_put(child_bus_node); +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; + + mdio_mux_uninit_children(pb); + 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 000000000..86786ecaa --- /dev/null +++ b/drivers/net/mdio/mdio-mvusb.c @@ -0,0 +1,113 @@ +// 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; + + 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; + + 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 = 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); + + mdiobus_unregister(mvusb->mdio); + usb_set_intfdata(interface, NULL); +} + +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 000000000..cb53dccbd --- /dev/null +++ b/drivers/net/mdio/mdio-octeon.c @@ -0,0 +1,97 @@ +// 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; + union cvmx_smix_en smi_en; + int err; + + mii_bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*bus)); + if (!mii_bus) + return -ENOMEM; + + bus = mii_bus->priv; + bus->mii_bus = mii_bus; + + bus->register_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bus->register_base)) { + dev_err(&pdev->dev, "dev_ioremap failed\n"); + return PTR_ERR(bus->register_base); + } + + 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_c22; + bus->mii_bus->write = cavium_mdiobus_write_c22; + bus->mii_bus->read_c45 = cavium_mdiobus_read_c45; + bus->mii_bus->write_c45 = cavium_mdiobus_write_c45; + + 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: + smi_en.u64 = 0; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); + return err; +} + +static void 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); + smi_en.u64 = 0; + oct_mdio_writeq(smi_en.u64, bus->register_base + SMI_EN); +} + +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-pic64hpsc.c b/drivers/net/mdio/mdio-pic64hpsc.c new file mode 100644 index 000000000..28be77ad6 --- /dev/null +++ b/drivers/net/mdio/mdio-pic64hpsc.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Microchip PIC64-HPSC/HX MDIO controller driver + * + * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MDIO_REG_PRESCALER 0x20 +#define MDIO_CFG_PRESCALE_MASK GENMASK(7, 0) + +#define MDIO_REG_FRAME_CFG_1 0x24 +#define MDIO_WDATA_MASK GENMASK(15, 0) + +#define MDIO_REG_FRAME_CFG_2 0x28 +#define MDIO_TRIGGER_BIT BIT(31) +#define MDIO_REG_DEV_ADDR_MASK GENMASK(20, 16) +#define MDIO_PHY_PRT_ADDR_MASK GENMASK(8, 4) +#define MDIO_OPERATION_MASK GENMASK(3, 2) +#define MDIO_START_OF_FRAME_MASK GENMASK(1, 0) + +/* Possible value of MDIO_OPERATION_MASK */ +#define MDIO_OPERATION_WRITE BIT(0) +#define MDIO_OPERATION_READ BIT(1) + +#define MDIO_REG_FRAME_STATUS 0x2C +#define MDIO_READOK_BIT BIT(24) +#define MDIO_RDATA_MASK GENMASK(15, 0) + +struct pic64hpsc_mdio_dev { + void __iomem *regs; +}; + +static int pic64hpsc_mdio_wait_trigger(struct mii_bus *bus) +{ + struct pic64hpsc_mdio_dev *priv = bus->priv; + u32 val; + int ret; + + /* The MDIO_TRIGGER bit returns 0 when a transaction has completed. */ + ret = readl_poll_timeout(priv->regs + MDIO_REG_FRAME_CFG_2, val, + !(val & MDIO_TRIGGER_BIT), 50, 10000); + + if (ret < 0) + dev_dbg(&bus->dev, "TRIGGER bit timeout: %x\n", val); + + return ret; +} + +static int pic64hpsc_mdio_c22_read(struct mii_bus *bus, int mii_id, int regnum) +{ + struct pic64hpsc_mdio_dev *priv = bus->priv; + u32 val; + int ret; + + ret = pic64hpsc_mdio_wait_trigger(bus); + if (ret) + return ret; + + writel(MDIO_TRIGGER_BIT | FIELD_PREP(MDIO_REG_DEV_ADDR_MASK, regnum) | + FIELD_PREP(MDIO_PHY_PRT_ADDR_MASK, mii_id) | + FIELD_PREP(MDIO_OPERATION_MASK, MDIO_OPERATION_READ) | + FIELD_PREP(MDIO_START_OF_FRAME_MASK, 1), + priv->regs + MDIO_REG_FRAME_CFG_2); + + ret = pic64hpsc_mdio_wait_trigger(bus); + if (ret) + return ret; + + val = readl(priv->regs + MDIO_REG_FRAME_STATUS); + + /* The MDIO_READOK is a 1-bit value reflecting the inverse of the MDIO + * bus value captured during the 2nd TA cycle. A PHY/Port should drive + * the MDIO bus with a logic 0 on the 2nd TA cycle, however, the + * PHY/Port could optionally drive a logic 1, to communicate a read + * failure. This feature is optional, not defined by the 802.3 standard + * and not supported in standard external PHYs. + */ + if (!(bus->phy_ignore_ta_mask & 1 << mii_id) && + !FIELD_GET(MDIO_READOK_BIT, val)) { + dev_dbg(&bus->dev, "READOK bit cleared\n"); + return -EIO; + } + + return FIELD_GET(MDIO_RDATA_MASK, val); +} + +static int pic64hpsc_mdio_c22_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) +{ + struct pic64hpsc_mdio_dev *priv = bus->priv; + int ret; + + ret = pic64hpsc_mdio_wait_trigger(bus); + if (ret < 0) + return ret; + + writel(FIELD_PREP(MDIO_WDATA_MASK, value), + priv->regs + MDIO_REG_FRAME_CFG_1); + + writel(MDIO_TRIGGER_BIT | FIELD_PREP(MDIO_REG_DEV_ADDR_MASK, regnum) | + FIELD_PREP(MDIO_PHY_PRT_ADDR_MASK, mii_id) | + FIELD_PREP(MDIO_OPERATION_MASK, MDIO_OPERATION_WRITE) | + FIELD_PREP(MDIO_START_OF_FRAME_MASK, 1), + priv->regs + MDIO_REG_FRAME_CFG_2); + + return 0; +} + +static int pic64hpsc_mdio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct pic64hpsc_mdio_dev *priv; + struct mii_bus *bus; + unsigned long rate; + struct clk *clk; + u32 bus_freq; + u32 div; + int ret; + + bus = devm_mdiobus_alloc_size(dev, sizeof(*priv)); + if (!bus) + return -ENOMEM; + + priv = bus->priv; + + priv->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + bus->name = KBUILD_MODNAME; + bus->read = pic64hpsc_mdio_c22_read; + bus->write = pic64hpsc_mdio_c22_write; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); + bus->parent = dev; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + if (of_property_read_u32(np, "clock-frequency", &bus_freq)) + bus_freq = 2500000; + + rate = clk_get_rate(clk); + + div = DIV_ROUND_UP(rate, 2 * bus_freq) - 1; + if (div == 0 || div & ~MDIO_CFG_PRESCALE_MASK) { + dev_err(dev, "MDIO clock-frequency out of range\n"); + return -EINVAL; + } + + dev_dbg(dev, "rate=%lu bus_freq=%u real_bus_freq=%lu div=%u\n", rate, + bus_freq, rate / (2 * (1 + div)), div); + writel(div, priv->regs + MDIO_REG_PRESCALER); + + ret = devm_of_mdiobus_register(dev, bus, np); + if (ret) { + dev_err(dev, "Cannot register MDIO bus (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id pic64hpsc_mdio_match[] = { + { .compatible = "microchip,pic64hpsc-mdio" }, + {} +}; +MODULE_DEVICE_TABLE(of, pic64hpsc_mdio_match); + +static struct platform_driver pic64hpsc_mdio_driver = { + .probe = pic64hpsc_mdio_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = pic64hpsc_mdio_match, + }, +}; +module_platform_driver(pic64hpsc_mdio_driver); + +MODULE_AUTHOR("Charles Perry "); +MODULE_DESCRIPTION("Microchip PIC64-HPSC/HX MDIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c new file mode 100644 index 000000000..afd52a1cd --- /dev/null +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Realtek switches of the Otto series (RTL838x, RTL839x, RTL930x and RTL931x SoCs) have multiple + * integrated MDIO controllers. This driver targets the ethernet MDIO controller. It serves only + * 1G/2.5G/10G ethernet PHYs attached to up to 4 individual buses. + * + * The controller is programmed through MMIO. The MDIO communication is abstracted by the hardware + * and uses the switch port number for its addressing. For this to work, mapping registers need to + * be setup in advance. With that the controller translates each port based I/O operation into the + * physical bus and address. This gives the following end-to-end communication + * + * +----------+ +----------+ +----------+ +----------+ + * | phydev | ... | phydev | | phydev | ... | phydev | + * +----------+ +----------+ +----------+ +----------+ + * | | | | + * mii_bus 0 +------------------+ +------------------+ mii_bus 1 + * | | + * +-----------------------------------------------------+ + * | MDIO driver | + * | translate bus/address -> port | + * +-----------------------------------------------------+ + * | Software + * - - - - - - - - - - - - - - - - - - - - - - - - - + * | Hardware + * +-----------------------------------------------------+ + * | MDIO controller | + * | translate port -> bus/address | + * +-----------------------------------------------------+ + * | | + * bus 0 +------------------+ +------------------+ bus 1 + * | | | | + * +----------+ +----------+ +----------+ +----------+ + * | PHY 0/1 | ... | PHY 0/31 | | PHY 1/1 | ... | PHY 1/31 | + * +----------+ +----------+ +----------+ +----------+ + * + * The driver works out the mapping based on the MDIO bus described in device tree and phandles on + * the ethernet-ports property. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RTL9300_NUM_BUSES 4 +#define RTL9300_NUM_PAGES 4096 +#define RTL9300_NUM_PORTS 28 +#define RTL9300_SMI_GLB_CTRL 0xca00 +#define RTL9300_GLB_CTRL_INTF_SEL(intf) BIT(16 + (intf)) +#define RTL9300_SMI_PORT0_15_POLLING_SEL 0xca08 +#define RTL9300_SMI_ACCESS_PHY_CTRL_0 0xcb70 +#define RTL9300_SMI_ACCESS_PHY_CTRL_1 0xcb74 +#define RTL9300_PHY_CTRL_REG_ADDR GENMASK(24, 20) +#define RTL9300_PHY_CTRL_PARK_PAGE GENMASK(19, 15) +#define RTL9300_PHY_CTRL_MAIN_PAGE GENMASK(14, 3) +#define RTL9300_PHY_CTRL_WRITE BIT(2) +#define RTL9300_PHY_CTRL_READ 0 +#define RTL9300_PHY_CTRL_TYPE_C45 BIT(1) +#define RTL9300_PHY_CTRL_TYPE_C22 0 +#define RTL9300_PHY_CTRL_FAIL BIT(25) +#define RTL9300_SMI_ACCESS_PHY_CTRL_2 0xcb78 +#define RTL9300_PHY_CTRL_INDATA GENMASK(31, 16) +#define RTL9300_PHY_CTRL_DATA GENMASK(15, 0) +#define RTL9300_SMI_ACCESS_PHY_CTRL_3 0xcb7c +#define RTL9300_SMI_PORT0_5_ADDR_CTRL 0xcb80 + +#define RTL9310_NUM_BUSES 4 +#define RTL9310_NUM_PAGES 8192 +#define RTL9310_NUM_PORTS 56 +#define RTL9310_SMI_GLB_CTRL1 0x0cbc +#define RTL9310_SMI_GLB_FMT_SEL_C45(intf) BIT((intf) * 2 + 1) +#define RTL9310_SMI_INDRT_ACCESS_CTRL_0 0x0c00 +#define RTL9310_PHY_CTRL_REG_ADDR GENMASK(10, 6) +#define RTL9310_PHY_CTRL_MAIN_PAGE GENMASK(23, 11) +#define RTL9310_PHY_CTRL_READ 0 +#define RTL9310_PHY_CTRL_WRITE BIT(4) +#define RTL9310_PHY_CTRL_TYPE_C45 BIT(3) +#define RTL9310_PHY_CTRL_TYPE_C22 0 +#define RTL9310_PHY_CTRL_FAIL BIT(1) +#define RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL 0x0c14 +#define RTL9310_BC_PORT_ID GENMASK(10, 5) +#define RTL9310_SMI_INDRT_ACCESS_CTRL_1 0x0c04 +#define RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW 0x0c08 +#define RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH 0x0c0c +#define RTL9310_SMI_INDRT_ACCESS_CTRL_3 0x0c10 /* I/O fields flipped */ +#define RTL9310_PHY_CTRL_DATA GENMASK(31, 16) +#define RTL9310_PHY_CTRL_INDATA GENMASK(15, 0) +#define RTL9310_SMI_INDRT_ACCESS_MMD_CTRL 0x0c18 +#define RTL9310_SMI_PORT_ADDR_CTRL 0x0c74 +#define RTL9310_SMI_PORT_POLLING_SEL 0x0c9c + +#define PHY_CTRL_CMD BIT(0) +#define PHY_CTRL_MMD_DEVAD GENMASK(20, 16) +#define PHY_CTRL_MMD_REG GENMASK(15, 0) + +#define MAP_ADDRS_PER_REG 6 +#define MAP_BITS_PER_ADDR 5 +#define MAP_BITS_PER_BUS 2 +#define MAP_BUSES_PER_REG 16 +#define MAX_PORTS 56 +#define MAX_SMI_BUSSES 4 +#define RAW_PAGE(priv) ((priv)->info->num_pages - 1) + + +struct otto_emdio_cmd_regs { + u32 c22_data; + u32 c45_data; + u32 io_data; + u32 port_mask_low; + /* additional registers for high port count models RTL839x/RTL931x */ + u32 port_mask_high; + u32 broadcast; + u32 ext_page; +}; + +struct otto_emdio_priv { + const struct otto_emdio_info *info; + struct regmap *regmap; + struct mutex lock; /* protect HW access */ + DECLARE_BITMAP(valid_ports, MAX_PORTS); + u8 smi_bus[MAX_PORTS]; + u8 smi_addr[MAX_PORTS]; + bool smi_bus_is_c45[MAX_SMI_BUSSES]; + struct mii_bus *bus[MAX_SMI_BUSSES]; +}; + +struct otto_emdio_info { + u32 addr_map_base; + u32 bus_map_base; + u32 cmd_fail; + u32 cmd_read; + u32 cmd_write; + struct otto_emdio_cmd_regs cmd_regs; + u8 num_buses; + u8 num_ports; + u16 num_pages; + int (*setup_controller)(struct otto_emdio_priv *priv); + int (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value); + int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value); + int (*write_c22)(struct mii_bus *bus, int port, int regnum, u16 value); + int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value); +}; + +struct otto_emdio_chan { + struct otto_emdio_priv *priv; + u8 mdio_bus; +}; + +static int otto_emdio_phy_to_port(struct mii_bus *bus, int phy_id) +{ + struct otto_emdio_chan *chan = bus->priv; + struct otto_emdio_priv *priv; + int i; + + priv = chan->priv; + + for_each_set_bit(i, priv->valid_ports, priv->info->num_ports) + if (priv->smi_bus[i] == chan->mdio_bus && + priv->smi_addr[i] == phy_id) + return i; + + return -ENOENT; +} + +static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus) +{ + struct otto_emdio_chan *chan = bus->priv; + + return chan->priv; +} + +static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd, + struct otto_emdio_cmd_regs *cmd_data) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + const struct otto_emdio_info *info = priv->info; + u32 cmdstate; + int ret; + + /* Defensive pre check just in case something goes horrible wrong */ + ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data, + cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000); + if (ret) + return ret; + + /* Fill all registers. Hardware will read only the needed bits depending on command */ + if (info->cmd_regs.port_mask_high) { + /* Fill extra registers for high port count models */ + ret = regmap_write(priv->regmap, info->cmd_regs.broadcast, cmd_data->broadcast); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, info->cmd_regs.ext_page, cmd_data->ext_page); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, + info->cmd_regs.port_mask_high, cmd_data->port_mask_high); + if (ret) + return ret; + } + + ret = regmap_write(priv->regmap, info->cmd_regs.port_mask_low, cmd_data->port_mask_low); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, info->cmd_regs.io_data, cmd_data->io_data); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, info->cmd_regs.c45_data, cmd_data->c45_data); + if (ret) + return ret; + + /* C22 data and command bits share the same register. */ + ret = regmap_write(priv->regmap, info->cmd_regs.c22_data, + cmd_data->c22_data | cmd | PHY_CTRL_CMD); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data, + cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000); + if (ret) + return ret; + + return cmdstate & info->cmd_fail ? -ENXIO : 0; +} + +static int otto_emdio_read_cmd(struct mii_bus *bus, u32 cmd, + struct otto_emdio_cmd_regs *cmd_data, u32 mask, u32 *value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + int ret; + + lockdep_assert_held(&priv->lock); + ret = otto_emdio_run_cmd(bus, cmd | priv->info->cmd_read, cmd_data); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, priv->info->cmd_regs.io_data, value); + if (ret) + return ret; + + *value = field_get(mask, *value); + + return 0; +} + +static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd, + struct otto_emdio_cmd_regs *cmd_data) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + + lockdep_assert_held(&priv->lock); + + return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data); +} + +static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + struct otto_emdio_cmd_regs cmd_data = { + .c22_data = FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) | + FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) | + FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port), + }; + + return otto_emdio_read_cmd(bus, RTL9300_PHY_CTRL_TYPE_C22, &cmd_data, + RTL9300_PHY_CTRL_DATA, value); +} + +static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum, u16 value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + struct otto_emdio_cmd_regs cmd_data = { + .c22_data = FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) | + FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) | + FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value), + .port_mask_low = BIT(port), + }; + + return otto_emdio_write_cmd(bus, RTL9300_PHY_CTRL_TYPE_C22, &cmd_data); +} + +static int otto_emdio_9300_read_c45(struct mii_bus *bus, int port, + int dev_addr, int regnum, u32 *value) +{ + struct otto_emdio_cmd_regs cmd_data = { + .c45_data = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) | + FIELD_PREP(PHY_CTRL_MMD_REG, regnum), + .io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port), + }; + + return otto_emdio_read_cmd(bus, RTL9300_PHY_CTRL_TYPE_C45, &cmd_data, + RTL9300_PHY_CTRL_DATA, value); +} + +static int otto_emdio_9300_write_c45(struct mii_bus *bus, int port, + int dev_addr, int regnum, u16 value) +{ + struct otto_emdio_cmd_regs cmd_data = { + .c45_data = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) | + FIELD_PREP(PHY_CTRL_MMD_REG, regnum), + .io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value), + .port_mask_low = BIT(port), + }; + + return otto_emdio_write_cmd(bus, RTL9300_PHY_CTRL_TYPE_C45, &cmd_data); +} + +static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + struct otto_emdio_cmd_regs cmd_data = { + .broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port), + .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | + FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + }; + + return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data, + RTL9310_PHY_CTRL_DATA, value); +} + +static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum, u16 value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + struct otto_emdio_cmd_regs cmd_data = { + .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | + FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value), + .port_mask_high = (u32)(BIT_ULL(port) >> 32), + .port_mask_low = (u32)(BIT_ULL(port)), + }; + + return otto_emdio_write_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data); +} + +static int otto_emdio_9310_read_c45(struct mii_bus *bus, int port, + int dev_addr, int regnum, u32 *value) +{ + struct otto_emdio_cmd_regs cmd_data = { + .broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port), + .c45_data = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) | + FIELD_PREP(PHY_CTRL_MMD_REG, regnum), + }; + + return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C45, &cmd_data, + RTL9310_PHY_CTRL_DATA, value); +} + +static int otto_emdio_9310_write_c45(struct mii_bus *bus, int port, + int dev_addr, int regnum, u16 value) +{ + struct otto_emdio_cmd_regs cmd_data = { + .c45_data = FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) | + FIELD_PREP(PHY_CTRL_MMD_REG, regnum), + .io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value), + .port_mask_high = (u32)(BIT_ULL(port) >> 32), + .port_mask_low = (u32)(BIT_ULL(port)), + }; + + return otto_emdio_write_cmd(bus, RTL9310_PHY_CTRL_TYPE_C45, &cmd_data); +} + +static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + int ret, port; + u32 value; + + port = otto_emdio_phy_to_port(bus, phy_id); + if (port < 0) + return port; + + scoped_guard(mutex, &priv->lock) + ret = priv->info->read_c22(bus, port, regnum, &value); + + return ret ? ret : value; +} + +static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + int ret, port; + + port = otto_emdio_phy_to_port(bus, phy_id); + if (port < 0) + return port; + + scoped_guard(mutex, &priv->lock) + ret = priv->info->write_c22(bus, port, regnum, value); + + return ret; +} + +static int otto_emdio_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + int ret, port; + u32 value; + + port = otto_emdio_phy_to_port(bus, phy_id); + if (port < 0) + return port; + + scoped_guard(mutex, &priv->lock) + ret = priv->info->read_c45(bus, port, dev_addr, regnum, &value); + + return ret ? ret : value; +} + +static int otto_emdio_write_c45(struct mii_bus *bus, int phy_id, + int dev_addr, int regnum, u16 value) +{ + struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus); + int ret, port; + + port = otto_emdio_phy_to_port(bus, phy_id); + if (port < 0) + return port; + + scoped_guard(mutex, &priv->lock) + ret = priv->info->write_c45(bus, port, dev_addr, regnum, value); + + return ret; +} + +static int otto_emdio_write_mapping(struct otto_emdio_priv *priv, u32 base, u32 port, + u32 vals_per_reg, u32 bits_per_val, u32 value) +{ + u32 shift = (port % vals_per_reg) * bits_per_val; + u32 reg = base + (port / vals_per_reg) * 4; + u32 mask = GENMASK(bits_per_val - 1, 0); + + return regmap_update_bits(priv->regmap, reg, mask << shift, value << shift); +} + +static int otto_emdio_setup_topology(struct otto_emdio_priv *priv) +{ + const struct otto_emdio_info *info = priv->info; + u32 port; + int ret; + + for_each_set_bit(port, priv->valid_ports, info->num_ports) { + if (info->bus_map_base) { + ret = otto_emdio_write_mapping(priv, info->bus_map_base, port, + MAP_BUSES_PER_REG, MAP_BITS_PER_BUS, + priv->smi_bus[port]); + if (ret) + return ret; + } + if (info->addr_map_base) { + ret = otto_emdio_write_mapping(priv, info->addr_map_base, port, + MAP_ADDRS_PER_REG, MAP_BITS_PER_ADDR, + priv->smi_addr[port]); + if (ret) + return ret; + } + } + + return 0; +} + +static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv) +{ + u32 glb_ctrl_mask = 0, glb_ctrl_val = 0; + struct regmap *regmap = priv->regmap; + int i, err; + + /* Put the interfaces into C45 mode if required */ + glb_ctrl_mask = GENMASK(19, 16); + for (i = 0; i < priv->info->num_buses; i++) + if (priv->smi_bus_is_c45[i]) + glb_ctrl_val |= RTL9300_GLB_CTRL_INTF_SEL(i); + + err = regmap_update_bits(regmap, RTL9300_SMI_GLB_CTRL, + glb_ctrl_mask, glb_ctrl_val); + if (err) + return err; + + return 0; +} + +static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv) +{ + int i, err; + + /* Put the interfaces into C45 mode if required */ + for (i = 0; i < priv->info->num_buses; i++) { + err = regmap_assign_bits(priv->regmap, RTL9310_SMI_GLB_CTRL1, + RTL9310_SMI_GLB_FMT_SEL_C45(i), + priv->smi_bus_is_c45[i]); + if (err) + return err; + } + + return 0; +} + +static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv, + struct fwnode_handle *node) +{ + struct otto_emdio_chan *chan; + struct mii_bus *bus; + u32 mdio_bus; + int err; + + err = fwnode_property_read_u32(node, "reg", &mdio_bus); + if (err) + return dev_err_probe(dev, err, "undefined smi bus number\n"); + + if (mdio_bus >= priv->info->num_buses) + return dev_err_probe(dev, -EINVAL, + "illegal (dangling) smi bus number %d\n", mdio_bus); + + bus = devm_mdiobus_alloc_size(dev, sizeof(*chan)); + if (!bus) + return -ENOMEM; + + bus->name = "Realtek Switch MDIO Bus"; + if (priv->smi_bus_is_c45[mdio_bus]) { + bus->read_c45 = otto_emdio_read_c45; + bus->write_c45 = otto_emdio_write_c45; + } else { + bus->read = otto_emdio_read_c22; + bus->write = otto_emdio_write_c22; + } + bus->parent = dev; + chan = bus->priv; + chan->mdio_bus = mdio_bus; + chan->priv = priv; + + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", dev_name(dev), mdio_bus); + + err = devm_of_mdiobus_register(dev, bus, to_of_node(node)); + if (err) + return dev_err_probe(dev, err, "cannot register MDIO bus\n"); + + return 0; +} + +static struct device_node *otto_emdio_get_bus_node(struct device_node *dn) +{ + struct device_node *parent = of_get_parent(dn); + struct device_node *grandparent; + + if (parent && of_node_name_eq(parent, "ethernet-phy-package")) { + grandparent = of_get_parent(parent); + of_node_put(parent); + + return grandparent; + } + + return parent; +} + +/* The mdio-controller is part of a switch block so we parse the sibling + * ethernet-ports node and build a mapping of the switch port to MDIO bus/addr + * based on the phy-handle. + */ +static int otto_emdio_map_ports(struct device *dev) +{ + struct device_node *ports_dn, *phy_dn, *bus_dn, *ctrl_dn; + struct otto_emdio_priv *priv = dev_get_drvdata(dev); + struct device *parent = dev->parent; + int addr, err = 0; + u32 bus, pn; + + ports_dn = of_get_child_by_name(parent->of_node, "ethernet-ports"); + if (!ports_dn) + return dev_err_probe(dev, -EINVAL, "%pfwP missing ethernet-ports\n", + dev_fwnode(parent)); + + for_each_available_child_of_node_scoped(ports_dn, port_dn) { + ctrl_dn = NULL; + bus_dn = NULL; + phy_dn = of_parse_phandle(port_dn, "phy-handle", 0); + /* skip ports without phys */ + if (!phy_dn) + continue; + + bus_dn = otto_emdio_get_bus_node(phy_dn); + if (!bus_dn) + goto put_nodes; + + ctrl_dn = of_get_parent(bus_dn); + /* only map ports that are connected to this mdio-controller */ + if (ctrl_dn != dev->of_node) + goto put_nodes; + + err = of_property_read_u32(port_dn, "reg", &pn); + if (err) + goto put_nodes; + + if (pn >= priv->info->num_ports) { + err = dev_err_probe(dev, -EINVAL, "illegal port number %d\n", pn); + goto put_nodes; + } + + if (test_bit(pn, priv->valid_ports)) { + err = dev_err_probe(dev, -EINVAL, "duplicated port number %d\n", pn); + goto put_nodes; + } + + err = of_property_read_u32(bus_dn, "reg", &bus); + if (err) + goto put_nodes; + + if (bus >= priv->info->num_buses) { + err = dev_err_probe(dev, -EINVAL, "illegal smi bus number %d\n", bus); + goto put_nodes; + } + + addr = of_mdio_parse_addr(dev, phy_dn); + if (addr < 0) { + err = addr; + goto put_nodes; + } + + /* + * The MDIO accesses from the kernel work with the PHY polling unit in the + * switch. The PPU either operates in GPHY (i.e. clause 22) or 10GPHY mode + * (i.e. clause 45). Select 10GPHY mode if there is at least one PHY that + * declares compatible = "ethernet-phy-ieee802.3-c45". + */ + if (of_device_is_compatible(phy_dn, "ethernet-phy-ieee802.3-c45")) + priv->smi_bus_is_c45[bus] = true; + + __set_bit(pn, priv->valid_ports); + priv->smi_bus[pn] = bus; + priv->smi_addr[pn] = addr; +put_nodes: + of_node_put(bus_dn); + of_node_put(phy_dn); + of_node_put(ctrl_dn); + if (err) + break; + } + + of_node_put(ports_dn); + + return err; +} + +static int otto_emdio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct otto_emdio_priv *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + err = devm_mutex_init(dev, &priv->lock); + if (err) + return err; + + priv->info = device_get_match_data(dev); + priv->regmap = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + platform_set_drvdata(pdev, priv); + + err = otto_emdio_map_ports(dev); + if (err) + return err; + + err = otto_emdio_setup_topology(priv); + if (err) + return err; + + if (priv->info->setup_controller) { + err = priv->info->setup_controller(priv); + if (err) + return dev_err_probe(dev, err, "failed to setup MDIO bus controller\n"); + } + + device_for_each_child_node_scoped(dev, child) { + err = otto_emdio_probe_one(dev, priv, child); + if (err) + return err; + } + + return 0; +} + +static const struct otto_emdio_info otto_emdio_9300_info = { + .addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL, + .bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL, + .cmd_fail = RTL9300_PHY_CTRL_FAIL, + .cmd_read = RTL9300_PHY_CTRL_READ, + .cmd_write = RTL9300_PHY_CTRL_WRITE, + .cmd_regs = { + .c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1, + .c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3, + .io_data = RTL9300_SMI_ACCESS_PHY_CTRL_2, + .port_mask_low = RTL9300_SMI_ACCESS_PHY_CTRL_0, + }, + .num_buses = RTL9300_NUM_BUSES, + .num_ports = RTL9300_NUM_PORTS, + .num_pages = RTL9300_NUM_PAGES, + .setup_controller = otto_emdio_9300_setup_controller, + .read_c22 = otto_emdio_9300_read_c22, + .read_c45 = otto_emdio_9300_read_c45, + .write_c22 = otto_emdio_9300_write_c22, + .write_c45 = otto_emdio_9300_write_c45, +}; + +static const struct otto_emdio_info otto_emdio_9310_info = { + .addr_map_base = RTL9310_SMI_PORT_ADDR_CTRL, + .bus_map_base = RTL9310_SMI_PORT_POLLING_SEL, + .cmd_fail = RTL9310_PHY_CTRL_FAIL, + .cmd_read = RTL9310_PHY_CTRL_READ, + .cmd_write = RTL9310_PHY_CTRL_WRITE, + .cmd_regs = { + .broadcast = RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL, + .c22_data = RTL9310_SMI_INDRT_ACCESS_CTRL_0, + .c45_data = RTL9310_SMI_INDRT_ACCESS_MMD_CTRL, + .ext_page = RTL9310_SMI_INDRT_ACCESS_CTRL_1, + .io_data = RTL9310_SMI_INDRT_ACCESS_CTRL_3, + .port_mask_low = RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW, + .port_mask_high = RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH, + }, + .num_buses = RTL9310_NUM_BUSES, + .num_pages = RTL9310_NUM_PAGES, + .num_ports = RTL9310_NUM_PORTS, + .setup_controller = otto_emdio_9310_setup_controller, + .read_c22 = otto_emdio_9310_read_c22, + .read_c45 = otto_emdio_9310_read_c45, + .write_c22 = otto_emdio_9310_write_c22, + .write_c45 = otto_emdio_9310_write_c45, +}; + +static const struct of_device_id otto_emdio_ids[] = { + { .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info }, + { .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info }, + {} +}; +MODULE_DEVICE_TABLE(of, otto_emdio_ids); + +static struct platform_driver otto_emdio_driver = { + .probe = otto_emdio_probe, + .driver = { + .name = "mdio-rtl9300", + .of_match_table = otto_emdio_ids, + }, +}; + +module_platform_driver(otto_emdio_driver); + +MODULE_DESCRIPTION("RTL9300 MDIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-regmap.c b/drivers/net/mdio/mdio-regmap.c new file mode 100644 index 000000000..8a742a8d6 --- /dev/null +++ b/drivers/net/mdio/mdio-regmap.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Driver for MMIO-Mapped MDIO devices. Some IPs expose internal PHYs or PCS + * within the MMIO-mapped area + * + * Copyright (C) 2023 Maxime Chevallier + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "mdio-regmap" + +struct mdio_regmap_priv { + struct regmap *regmap; + u8 valid_addr; +}; + +static int mdio_regmap_read_c22(struct mii_bus *bus, int addr, int regnum) +{ + struct mdio_regmap_priv *ctx = bus->priv; + unsigned int val; + int ret; + + if (ctx->valid_addr != addr) + return -ENODEV; + + ret = regmap_read(ctx->regmap, regnum, &val); + if (ret < 0) + return ret; + + return val; +} + +static int mdio_regmap_write_c22(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + struct mdio_regmap_priv *ctx = bus->priv; + + if (ctx->valid_addr != addr) + return -ENODEV; + + return regmap_write(ctx->regmap, regnum, val); +} + +struct mii_bus *devm_mdio_regmap_register(struct device *dev, + const struct mdio_regmap_config *config) +{ + struct mdio_regmap_priv *mr; + struct mii_bus *mii; + int rc; + + if (!config->parent) + return ERR_PTR(-EINVAL); + + mii = devm_mdiobus_alloc_size(config->parent, sizeof(*mr)); + if (!mii) + return ERR_PTR(-ENOMEM); + + mr = mii->priv; + mr->regmap = config->regmap; + mr->valid_addr = config->valid_addr; + + mii->name = DRV_NAME; + strscpy(mii->id, config->name, MII_BUS_ID_SIZE); + mii->parent = config->parent; + mii->read = mdio_regmap_read_c22; + mii->write = mdio_regmap_write_c22; + + if (config->autoscan) + mii->phy_mask = ~BIT(config->valid_addr); + else + mii->phy_mask = ~0; + + rc = devm_mdiobus_register(dev, mii); + if (rc) { + dev_err(config->parent, "Cannot register MDIO bus![%s] (%d)\n", mii->id, rc); + return ERR_PTR(rc); + } + + return mii; +} +EXPORT_SYMBOL_GPL(devm_mdio_regmap_register); + +MODULE_DESCRIPTION("MDIO API over regmap"); +MODULE_AUTHOR("Maxime Chevallier "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-sun4i.c b/drivers/net/mdio/mdio-sun4i.c new file mode 100644 index 000000000..ad1edadc5 --- /dev/null +++ b/drivers/net/mdio/mdio-sun4i.c @@ -0,0 +1,178 @@ +// 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 void 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); +} + +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 000000000..a3047f725 --- /dev/null +++ b/drivers/net/mdio/mdio-thunder.c @@ -0,0 +1,148 @@ +// 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 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 = pcim_request_all_regions(pdev, KBUILD_MODNAME); + if (err) { + dev_err(&pdev->dev, "pcim_request_all_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_disable_device; + } + + i = 0; + device_for_each_child_node_scoped(&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_c22; + bus->mii_bus->write = cavium_mdiobus_write_c22; + bus->mii_bus->read_c45 = cavium_mdiobus_read_c45; + bus->mii_bus->write_c45 = cavium_mdiobus_write_c45; + + 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_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); + oct_mdio_writeq(0, bus->register_base + SMI_EN); + } + 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 000000000..ede6b9ddc --- /dev/null +++ b/drivers/net/mdio/mdio-xgene.c @@ -0,0 +1,451 @@ +// 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 +#include +#include + +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 = 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 = 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 read 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; + + adev = acpi_fetch_acpi_dev(handle); + if (!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; + struct xgene_mdio_pdata *pdata; + void __iomem *csr_base; + int mdio_id = 0, ret = 0; + + mdio_id = (uintptr_t)device_get_match_data(&pdev->dev); + 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; + + return 0; + +out_mdiobus: + mdiobus_free(mdio_bus); + +out_clk: + if (dev->of_node) + clk_disable_unprepare(pdata->clk); + + return ret; +} + +static void 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); +} + +static struct platform_driver xgene_mdio_driver = { + .driver = { + .name = "xgene-mdio", + .of_match_table = 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/mdio/of_mdio.c b/drivers/net/mdio/of_mdio.c new file mode 100644 index 000000000..b8d298c04 --- /dev/null +++ b/drivers/net/mdio/of_mdio.c @@ -0,0 +1,476 @@ +// 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 +#include + +#define DEFAULT_GPIO_RESET_DELAY 10 /* in microseconds */ + +MODULE_AUTHOR("Grant Likely "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("OpenFirmware MDIO bus (Ethernet PHY) accessors"); + +/* 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) +{ + return fwnode_get_phy_id(of_fwnode_handle(device), phy_id); +} + +int of_mdiobus_phy_device_register(struct mii_bus *mdio, struct phy_device *phy, + struct device_node *child, u32 addr) +{ + return fwnode_mdiobus_phy_device_register(mdio, phy, + of_fwnode_handle(child), + addr); +} +EXPORT_SYMBOL(of_mdiobus_phy_device_register); + +static int of_mdiobus_register_phy(struct mii_bus *mdio, + struct device_node *child, u32 addr) +{ + return fwnode_mdiobus_register_phy(mdio, of_fwnode_handle(child), addr); +} + +static int of_mdiobus_register_device(struct mii_bus *mdio, + struct device_node *child, u32 addr) +{ + struct fwnode_handle *fwnode = of_fwnode_handle(child); + 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. + */ + device_set_node(&mdiodev->dev, fwnode_handle_get(fwnode)); + + /* All data is now stored in the mdiodev struct; register it. */ + rc = mdio_device_register(mdiodev); + if (rc) { + mdio_device_free(mdiodev); + 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_property_present(child, "compatible")) + return true; + + return false; +} +EXPORT_SYMBOL(of_mdiobus_child_is_phy); + +static int __of_mdiobus_parse_phys(struct mii_bus *mdio, struct device_node *np, + bool *scanphys) +{ + struct device_node *child; + int addr, rc = 0; + + /* Loop over the child nodes and register a phy_device for each phy */ + for_each_available_child_of_node(np, child) { + if (of_node_name_eq(child, "ethernet-phy-package")) { + /* Ignore invalid ethernet-phy-package node */ + if (!of_property_present(child, "reg")) + continue; + + rc = __of_mdiobus_parse_phys(mdio, child, NULL); + if (rc && rc != -ENODEV) + goto exit; + + continue; + } + + addr = of_mdio_parse_addr(&mdio->dev, child); + if (addr < 0) { + /* Skip scanning for invalid ethernet-phy-package node */ + if (scanphys) + *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 exit; + } + + return 0; +exit: + of_node_put(child); + return rc; +} + +/** + * __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. + * @owner: module owning the @mdio object. + * + * 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 module *owner) +{ + struct device_node *child; + bool scanphys = false; + int addr, rc; + + if (!np) + return __mdiobus_register(mdio, owner); + + /* 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; + + device_set_node(&mdio->dev, 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, owner); + if (rc) + return rc; + + /* Loop over the child nodes and register a phy_device for each phy */ + rc = __of_mdiobus_parse_phys(mdio, np, &scanphys); + 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 or ethernet-phy-package node */ + if (of_property_present(child, "reg") || + of_node_name_eq(child, "ethernet-phy-package")) + 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 put_unregister; + } + } + } + + return 0; + +put_unregister: + of_node_put(child); +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) +{ + return fwnode_mdio_find_device(of_fwnode_handle(np)); +} +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) +{ + return fwnode_phy_find_device(of_fwnode_handle(phy_np)); +} +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_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 information 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 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_property_count_u32_elems(np, "fixed-link") == 5) + 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) { + pr_warn_once("%pOF uses deprecated array-style fixed-link binding!\n", + np); + 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(&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() */ +} +EXPORT_SYMBOL(of_phy_deregister_fixed_link); -- cgit v1.3.1