diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-11 19:31:52 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-11 19:31:52 -0800 |
| commit | 37a93dd5c49b5fda807fd204edf2547c3493319c (patch) | |
| tree | ce1ef5a642b9ea3d7242156438eb96dc5607a752 /drivers/net/phy | |
| parent | 098b6e44cbaa2d526d06af90c862d13fb414a0ec (diff) | |
| parent | 83310d613382f74070fc8b402f3f6c2af8439ead (diff) | |
Merge tag 'net-next-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextipvs/mainipvs/HEADipvs-next/mainipvs-next/HEADdavem/net-next/maindavem/net-next/HEAD
Pull networking updates from Paolo Abeni:
"Core & protocols:
- A significant effort all around the stack to guide the compiler to
make the right choice when inlining code, to avoid unneeded calls
for small helper and stack canary overhead in the fast-path.
This generates better and faster code with very small or no text
size increases, as in many cases the call generated more code than
the actual inlined helper.
- Extend AccECN implementation so that is now functionally complete,
also allow the user-space enabling it on a per network namespace
basis.
- Add support for memory providers with large (above 4K) rx buffer.
Paired with hw-gro, larger rx buffer sizes reduce the number of
buffers traversing the stack, dincreasing single stream CPU usage
by up to ~30%.
- Do not add HBH header to Big TCP GSO packets. This simplifies the
RX path, the TX path and the NIC drivers, and is possible because
user-space taps can now interpret correctly such packets without
the HBH hint.
- Allow IPv6 routes to be configured with a gateway address that is
resolved out of a different interface than the one specified,
aligning IPv6 to IPv4 behavior.
- Multi-queue aware sch_cake. This makes it possible to scale the
rate shaper of sch_cake across multiple CPUs, while still enforcing
a single global rate on the interface.
- Add support for the nbcon (new buffer console) infrastructure to
netconsole, enabling lock-free, priority-based console operations
that are safer in crash scenarios.
- Improve the TCP ipv6 output path to cache the flow information,
saving cpu cycles, reducing cache line misses and stack use.
- Improve netfilter packet tracker to resolve clashes for most
protocols, avoiding unneeded drops on rare occasions.
- Add IP6IP6 tunneling acceleration to the flowtable infrastructure.
- Reduce tcp socket size by one cache line.
- Notify neighbour changes atomically, avoiding inconsistencies
between the notification sequence and the actual states sequence.
- Add vsock namespace support, allowing complete isolation of vsocks
across different network namespaces.
- Improve xsk generic performances with cache-alignment-oriented
optimizations.
- Support netconsole automatic target recovery, allowing netconsole
to reestablish targets when underlying low-level interface comes
back online.
Driver API:
- Support for switching the working mode (automatic vs manual) of a
DPLL device via netlink.
- Introduce PHY ports representation to expose multiple front-facing
media ports over a single MAC.
- Introduce "rx-polarity" and "tx-polarity" device tree properties,
to generalize polarity inversion requirements for differential
signaling.
- Add helper to create, prepare and enable managed clocks.
Device drivers:
- Add Huawei hinic3 PF etherner driver.
- Add DWMAC glue driver for Motorcomm YT6801 PCIe ethernet
controller.
- Add ethernet driver for MaxLinear MxL862xx switches
- Remove parallel-port Ethernet driver.
- Convert existing driver timestamp configuration reporting to
hwtstamp_get and remove legacy ioctl().
- Convert existing drivers to .get_rx_ring_count(), simplifing the RX
ring count retrieval. Also remove the legacy fallback path.
- Ethernet high-speed NICs:
- Broadcom (bnxt, bng):
- bnxt: add FW interface update to support FEC stats histogram
and NVRAM defragmentation
- bng: add TSO and H/W GRO support
- nVidia/Mellanox (mlx5):
- improve latency of channel restart operations, reducing the
used H/W resources
- add TSO support for UDP over GRE over VLAN
- add flow counters support for hardware steering (HWS) rules
- use a static memory area to store headers for H/W GRO,
leading to 12% RX tput improvement
- Intel (100G, ice, idpf):
- ice: reorganizes layout of Tx and Rx rings for cacheline
locality and utilizes __cacheline_group* macros on the new
layouts
- ice: introduces Synchronous Ethernet (SyncE) support
- Meta (fbnic):
- adds debugfs for firmware mailbox and tx/rx rings vectors
- Ethernet virtual:
- geneve: introduce GRO/GSO support for double UDP encapsulation
- Ethernet NICs consumer, and embedded:
- Synopsys (stmmac):
- some code refactoring and cleanups
- RealTek (r8169):
- add support for RTL8127ATF (10G Fiber SFP)
- add dash and LTR support
- Airoha:
- AN8811HB 2.5 Gbps phy support
- Freescale (fec):
- add XDP zero-copy support
- Thunderbolt:
- add get link setting support to allow bonding
- Renesas:
- add support for RZ/G3L GBETH SoC
- Ethernet switches:
- Maxlinear:
- support R(G)MII slow rate configuration
- add support for Intel GSW150
- Motorcomm (yt921x):
- add DCB/QoS support
- TI:
- icssm-prueth: support bridging (STP/RSTP) via the switchdev
framework
- Ethernet PHYs:
- Realtek:
- enable SGMII and 2500Base-X in-band auto-negotiation
- simplify and reunify C22/C45 drivers
- Micrel: convert bindings to DT schema
- CAN:
- move skb headroom content into skb extensions, making CAN
metadata access more robust
- CAN drivers:
- rcar_canfd:
- add support for FD-only mode
- add support for the RZ/T2H SoC
- sja1000: cleanup the CAN state handling
- WiFi:
- implement EPPKE/802.1X over auth frames support
- split up drop reasons better, removing generic RX_DROP
- additional FTM capabilities: 6 GHz support, supported number of
spatial streams and supported number of LTF repetitions
- better mac80211 iterators to enumerate resources
- initial UHR (Wi-Fi 8) support for cfg80211/mac80211
- WiFi drivers:
- Qualcomm/Atheros:
- ath11k: support for Channel Frequency Response measurement
- ath12k: a significant driver refactor to support multi-wiphy
devices and and pave the way for future device support in the
same driver (rather than splitting to ath13k)
- ath12k: support for the QCC2072 chipset
- Intel:
- iwlwifi: partial Neighbor Awareness Networking (NAN) support
- iwlwifi: initial support for U-NII-9 and IEEE 802.11bn
- RealTek (rtw89):
- preparations for RTL8922DE support
- Bluetooth:
- implement setsockopt(BT_PHY) to set the connection packet type/PHY
- set link_policy on incoming ACL connections
- Bluetooth drivers:
- btusb: add support for MediaTek7920, Realtek RTL8761BU and 8851BE
- btqca: add WCN6855 firmware priority selection feature"
* tag 'net-next-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1254 commits)
bnge/bng_re: Add a new HSI
net: macb: Fix tx/rx malfunction after phy link down and up
af_unix: Fix memleak of newsk in unix_stream_connect().
net: ti: icssg-prueth: Add optional dependency on HSR
net: dsa: add basic initial driver for MxL862xx switches
net: mdio: add unlocked mdiodev C45 bus accessors
net: dsa: add tag format for MxL862xx switches
dt-bindings: net: dsa: add MaxLinear MxL862xx
selftests: drivers: net: hw: Modify toeplitz.c to poll for packets
octeontx2-pf: Unregister devlink on probe failure
net: renesas: rswitch: fix forwarding offload statemachine
ionic: Rate limit unknown xcvr type messages
tcp: inet6_csk_xmit() optimization
tcp: populate inet->cork.fl.u.ip6 in tcp_v6_syn_recv_sock()
tcp: populate inet->cork.fl.u.ip6 in tcp_v6_connect()
ipv6: inet6_csk_xmit() and inet6_csk_update_pmtu() use inet->cork.fl.u.ip6
ipv6: use inet->cork.fl.u.ip6 and np->final in ip6_datagram_dst_update()
ipv6: use np->final in inet6_sk_rebuild_header()
ipv6: add daddr/final storage in struct ipv6_pinfo
net: stmmac: qcom-ethqos: fix qcom_ethqos_serdes_powerup()
...
Diffstat (limited to 'drivers/net/phy')
30 files changed, 1891 insertions, 666 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index a7ade7b95a2e..7b73332a13d9 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -98,6 +98,7 @@ config AS21XXX_PHY config AIR_EN8811H_PHY tristate "Airoha EN8811H 2.5 Gigabit PHY" + select PHY_COMMON_PROPS help Currently supports the Airoha EN8811H PHY. diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 76e0db40f879..3a34917adea7 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -3,7 +3,7 @@ libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \ linkmode.o phy_link_topology.o \ - phy_caps.o mdio_bus_provider.o + phy_caps.o mdio_bus_provider.o phy_port.o mdio-bus-y += mdio_bus.o mdio_device.o ifdef CONFIG_PHYLIB diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c index 7fa713ca8d45..3a934051b574 100644 --- a/drivers/net/phy/adin.c +++ b/drivers/net/phy/adin.c @@ -89,6 +89,9 @@ #define ADIN1300_CLOCK_STOP_REG 0x9400 #define ADIN1300_LPI_WAKE_ERR_CNT_REG 0xa000 +#define ADIN1300_B_100_ZPTM_DIMRX 0xB685 +#define ADIN1300_B_100_ZPTM_EN_DIMRX BIT(0) + #define ADIN1300_CDIAG_RUN 0xba1b #define ADIN1300_CDIAG_RUN_EN BIT(0) @@ -522,6 +525,19 @@ static int adin_config_clk_out(struct phy_device *phydev) ADIN1300_GE_CLK_CFG_MASK, sel); } +static int adin_config_zptm100(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + + if (!(device_property_read_bool(dev, "adi,low-cmode-impedance"))) + return 0; + + /* clear bit 0 to configure for lowest common-mode impedance */ + return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, + ADIN1300_B_100_ZPTM_DIMRX, + ADIN1300_B_100_ZPTM_EN_DIMRX); +} + static int adin_config_init(struct phy_device *phydev) { int rc; @@ -548,6 +564,10 @@ static int adin_config_init(struct phy_device *phydev) if (rc < 0) return rc; + rc = adin_config_zptm100(phydev); + if (rc < 0) + return rc; + phydev_dbg(phydev, "PHY is using mode '%s'\n", phy_modes(phydev->interface)); diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c index badd65f0ccee..29ae73e65caa 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -1,28 +1,33 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Driver for the Airoha EN8811H 2.5 Gigabit PHY. + * Driver for the Airoha EN8811H and AN8811HB 2.5 Gigabit PHYs. * - * Limitations of the EN8811H: + * Limitations: * - Only full duplex supported * - Forced speed (AN off) is not supported by hardware (100Mbps) * * Source originated from airoha's en8811h.c and en8811h.h v1.2.1 + * with AN8811HB bits from air_an8811hb.c v0.0.4 * - * Copyright (C) 2023 Airoha Technology Corp. + * Copyright (C) 2023, 2026 Airoha Technology Corp. */ #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/phy.h> +#include <linux/phy/phy-common-props.h> #include <linux/firmware.h> #include <linux/property.h> #include <linux/wordpart.h> #include <linux/unaligned.h> #define EN8811H_PHY_ID 0x03a2a411 +#define AN8811HB_PHY_ID 0xc0ff04a0 #define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" #define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" +#define AN8811HB_MD32_DM "airoha/an8811hb/EthMD32_CRC.DM.bin" +#define AN8811HB_MD32_DSP "airoha/an8811hb/EthMD32_CRC.DSP.bin" #define AIR_FW_ADDR_DM 0x00000000 #define AIR_FW_ADDR_DSP 0x00100000 @@ -30,6 +35,7 @@ /* MII Registers */ #define AIR_AUX_CTRL_STATUS 0x1d #define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2) +#define AIR_AUX_CTRL_STATUS_SPEED_10 0x0 #define AIR_AUX_CTRL_STATUS_SPEED_100 0x4 #define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8 #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc @@ -55,6 +61,7 @@ #define EN8811H_PHY_FW_STATUS 0x8009 #define EN8811H_PHY_READY 0x02 +#define AIR_PHY_MCU_CMD_0 0x800b #define AIR_PHY_MCU_CMD_1 0x800c #define AIR_PHY_MCU_CMD_1_MODE1 0x0 #define AIR_PHY_MCU_CMD_2 0x800d @@ -64,6 +71,10 @@ #define AIR_PHY_MCU_CMD_3_DOCMD 0x1100 #define AIR_PHY_MCU_CMD_4 0x800f #define AIR_PHY_MCU_CMD_4_MODE1 0x0002 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_A 0x00d7 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_B 0x00d8 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_C 0x00d9 +#define AIR_PHY_MCU_CMD_4_CABLE_PAIR_D 0x00da #define AIR_PHY_MCU_CMD_4_INTCLR 0x00e4 /* Registers on MDIO_MMD_VEND2 */ @@ -105,6 +116,9 @@ #define AIR_PHY_LED_BLINK_2500RX BIT(11) /* Registers on BUCKPBUS */ +#define AIR_PHY_CONTROL 0x3a9c +#define AIR_PHY_CONTROL_INTERNAL BIT(11) + #define EN8811H_2P5G_LPA 0x3b30 #define EN8811H_2P5G_LPA_2P5G BIT(0) @@ -128,6 +142,34 @@ #define EN8811H_FW_CTRL_2 0x800000 #define EN8811H_FW_CTRL_2_LOADING BIT(11) +#define AN8811HB_CRC_PM_SET1 0xf020c +#define AN8811HB_CRC_PM_MON2 0xf0218 +#define AN8811HB_CRC_PM_MON3 0xf021c +#define AN8811HB_CRC_DM_SET1 0xf0224 +#define AN8811HB_CRC_DM_MON2 0xf0230 +#define AN8811HB_CRC_DM_MON3 0xf0234 +#define AN8811HB_CRC_RD_EN BIT(0) +#define AN8811HB_CRC_ST (BIT(0) | BIT(1)) +#define AN8811HB_CRC_CHECK_PASS BIT(0) + +#define AN8811HB_TX_POLARITY 0x5ce004 +#define AN8811HB_TX_POLARITY_NORMAL BIT(7) +#define AN8811HB_RX_POLARITY 0x5ce61c +#define AN8811HB_RX_POLARITY_NORMAL BIT(7) + +#define AN8811HB_GPIO_OUTPUT 0x5cf8b8 +#define AN8811HB_GPIO_OUTPUT_345 (BIT(3) | BIT(4) | BIT(5)) + +#define AN8811HB_HWTRAP1 0x5cf910 +#define AN8811HB_HWTRAP2 0x5cf914 +#define AN8811HB_HWTRAP2_CKO BIT(28) + +#define AN8811HB_CLK_DRV 0x5cf9e4 +#define AN8811HB_CLK_DRV_CKO_MASK GENMASK(14, 12) +#define AN8811HB_CLK_DRV_CKOPWD BIT(12) +#define AN8811HB_CLK_DRV_CKO_LDPWD BIT(13) +#define AN8811HB_CLK_DRV_CKO_LPPWD BIT(14) + /* Led definitions */ #define EN8811H_LED_COUNT 3 @@ -447,6 +489,11 @@ static int en8811h_wait_mcu_ready(struct phy_device *phydev) { int ret, reg_value; + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); + if (ret) + return ret; + /* Because of mdio-lock, may have to wait for multiple loads */ ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS, reg_value, @@ -460,9 +507,103 @@ static int en8811h_wait_mcu_ready(struct phy_device *phydev) return 0; } -static int en8811h_load_firmware(struct phy_device *phydev) +static int an8811hb_check_crc(struct phy_device *phydev, u32 set1, + u32 mon2, u32 mon3) +{ + u32 pbus_value; + int retry = 25; + int ret; + + /* Configure CRC */ + ret = air_buckpbus_reg_modify(phydev, set1, + AN8811HB_CRC_RD_EN, + AN8811HB_CRC_RD_EN); + if (ret < 0) + return ret; + air_buckpbus_reg_read(phydev, set1, &pbus_value); + + do { + msleep(300); + air_buckpbus_reg_read(phydev, mon2, &pbus_value); + + /* We do not know what errors this check is supposed + * catch or what to do about a failure. So print the + * result and continue like the vendor driver does. + */ + if (pbus_value & AN8811HB_CRC_ST) { + air_buckpbus_reg_read(phydev, mon3, &pbus_value); + phydev_dbg(phydev, "CRC Check %s!\n", + pbus_value & AN8811HB_CRC_CHECK_PASS ? + "PASS" : "FAIL"); + return air_buckpbus_reg_modify(phydev, set1, + AN8811HB_CRC_RD_EN, 0); + } + } while (--retry); + + phydev_err(phydev, "CRC Check is not ready (%u)\n", pbus_value); + return -ENODEV; +} + +static void en8811h_print_fw_version(struct phy_device *phydev) { struct en8811h_priv *priv = phydev->priv; + + air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, + &priv->firmware_version); + phydev_info(phydev, "MD32 firmware version: %08x\n", + priv->firmware_version); +} + +static int an8811hb_load_file(struct phy_device *phydev, const char *name, + u32 address) +{ + struct device *dev = &phydev->mdio.dev; + const struct firmware *fw; + int ret; + + ret = request_firmware_direct(&fw, name, dev); + if (ret < 0) + return ret; + + ret = air_write_buf(phydev, address, fw); + release_firmware(fw); + return ret; +} + +static int an8811hb_load_firmware(struct phy_device *phydev) +{ + int ret; + + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + return ret; + + ret = an8811hb_load_file(phydev, AN8811HB_MD32_DM, AIR_FW_ADDR_DM); + if (ret < 0) + return ret; + + ret = an8811hb_check_crc(phydev, AN8811HB_CRC_DM_SET1, + AN8811HB_CRC_DM_MON2, + AN8811HB_CRC_DM_MON3); + if (ret < 0) + return ret; + + ret = an8811hb_load_file(phydev, AN8811HB_MD32_DSP, AIR_FW_ADDR_DSP); + if (ret < 0) + return ret; + + ret = an8811hb_check_crc(phydev, AN8811HB_CRC_PM_SET1, + AN8811HB_CRC_PM_MON2, + AN8811HB_CRC_PM_MON3); + if (ret < 0) + return ret; + + return en8811h_wait_mcu_ready(phydev); +} + +static int en8811h_load_firmware(struct phy_device *phydev) +{ struct device *dev = &phydev->mdio.dev; const struct firmware *fw1, *fw2; int ret; @@ -499,17 +640,11 @@ static int en8811h_load_firmware(struct phy_device *phydev) if (ret < 0) goto en8811h_load_firmware_out; - ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_FINISH); + ret = en8811h_wait_mcu_ready(phydev); if (ret < 0) goto en8811h_load_firmware_out; - ret = en8811h_wait_mcu_ready(phydev); - - air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, - &priv->firmware_version); - phydev_info(phydev, "MD32 firmware version: %08x\n", - priv->firmware_version); + en8811h_print_fw_version(phydev); en8811h_load_firmware_out: release_firmware(fw2); @@ -532,11 +667,6 @@ static int en8811h_restart_mcu(struct phy_device *phydev) if (ret < 0) return ret; - ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_FINISH); - if (ret < 0) - return ret; - return en8811h_wait_mcu_ready(phydev); } @@ -819,6 +949,105 @@ static int en8811h_led_hw_is_supported(struct phy_device *phydev, u8 index, return 0; }; +static unsigned long an8811hb_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + u32 pbus_value; + int ret; + + ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value); + if (ret < 0) + return ret; + + return (pbus_value & AN8811HB_HWTRAP2_CKO) ? 50000000 : 25000000; +} + +static int an8811hb_clk_enable(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + + return air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV, + AN8811HB_CLK_DRV_CKO_MASK, + AN8811HB_CLK_DRV_CKO_MASK); +} + +static void an8811hb_clk_disable(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + + air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV, + AN8811HB_CLK_DRV_CKO_MASK, 0); +} + +static int an8811hb_clk_is_enabled(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + struct phy_device *phydev = priv->phydev; + u32 pbus_value; + int ret; + + ret = air_buckpbus_reg_read(phydev, AN8811HB_CLK_DRV, &pbus_value); + if (ret < 0) + return ret; + + return (pbus_value & AN8811HB_CLK_DRV_CKO_MASK); +} + +static int an8811hb_clk_save_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + priv->cko_is_enabled = an8811hb_clk_is_enabled(hw); + + return 0; +} + +static void an8811hb_clk_restore_context(struct clk_hw *hw) +{ + struct en8811h_priv *priv = clk_hw_to_en8811h_priv(hw); + + if (!priv->cko_is_enabled) + an8811hb_clk_disable(hw); +} + +static const struct clk_ops an8811hb_clk_ops = { + .recalc_rate = an8811hb_clk_recalc_rate, + .enable = an8811hb_clk_enable, + .disable = an8811hb_clk_disable, + .is_enabled = an8811hb_clk_is_enabled, + .save_context = an8811hb_clk_save_context, + .restore_context = an8811hb_clk_restore_context, +}; + +static int an8811hb_clk_provider_setup(struct device *dev, struct clk_hw *hw) +{ + struct clk_init_data init; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-cko", + fwnode_get_name(dev_fwnode(dev))); + if (!init.name) + return -ENOMEM; + + init.ops = &an8811hb_clk_ops; + init.flags = 0; + init.num_parents = 0; + hw->init = &init; + + ret = devm_clk_hw_register(dev, hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); +} + static unsigned long en8811h_clk_recalc_rate(struct clk_hw *hw, unsigned long parent) { @@ -918,6 +1147,68 @@ static int en8811h_clk_provider_setup(struct device *dev, struct clk_hw *hw) return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); } +static int en8811h_leds_setup(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0; + priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1; + priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2; + + ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, + AIR_LED_MODE_DISABLE); + if (ret < 0) + phydev_err(phydev, "Failed to disable leds: %d\n", ret); + + return ret; +} + +static int an8811hb_probe(struct phy_device *phydev) +{ + struct en8811h_priv *priv; + int ret; + + priv = devm_kzalloc(&phydev->mdio.dev, sizeof(struct en8811h_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + ret = an8811hb_load_firmware(phydev); + if (ret < 0) { + phydev_err(phydev, "Load firmware failed: %d\n", ret); + return ret; + } + + en8811h_print_fw_version(phydev); + + /* mcu has just restarted after firmware load */ + priv->mcu_needs_restart = false; + + /* MDIO_DEVS1/2 empty, so set mmds_present bits here */ + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + + ret = en8811h_leds_setup(phydev); + if (ret < 0) + return ret; + + priv->phydev = phydev; + /* Co-Clock Output */ + ret = an8811hb_clk_provider_setup(&phydev->mdio.dev, &priv->hw); + if (ret) + return ret; + + /* Configure led gpio pins as output */ + ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT, + AN8811HB_GPIO_OUTPUT_345, + AN8811HB_GPIO_OUTPUT_345); + if (ret < 0) + return ret; + + return 0; +} + static int en8811h_probe(struct phy_device *phydev) { struct en8811h_priv *priv; @@ -936,19 +1227,12 @@ static int en8811h_probe(struct phy_device *phydev) /* mcu has just restarted after firmware load */ priv->mcu_needs_restart = false; - priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0; - priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1; - priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2; - /* MDIO_DEVS1/2 empty, so set mmds_present bits here */ phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; - ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, - AIR_LED_MODE_DISABLE); - if (ret < 0) { - phydev_err(phydev, "Failed to disable leds: %d\n", ret); + ret = en8811h_leds_setup(phydev); + if (ret < 0) return ret; - } priv->phydev = phydev; /* Co-Clock Output */ @@ -966,11 +1250,103 @@ static int en8811h_probe(struct phy_device *phydev) return 0; } +static int an8811hb_config_serdes_polarity(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + u32 pbus_value = 0; + unsigned int pol; + int ret; + + ret = phy_get_manual_rx_polarity(dev_fwnode(dev), + phy_modes(phydev->interface), &pol); + if (ret) + return ret; + if (pol == PHY_POL_NORMAL) + pbus_value |= AN8811HB_RX_POLARITY_NORMAL; + ret = air_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY, + AN8811HB_RX_POLARITY_NORMAL, + pbus_value); + if (ret < 0) + return ret; + + ret = phy_get_manual_tx_polarity(dev_fwnode(dev), + phy_modes(phydev->interface), &pol); + if (ret) + return ret; + pbus_value = 0; + if (pol == PHY_POL_NORMAL) + pbus_value |= AN8811HB_TX_POLARITY_NORMAL; + return air_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY, + AN8811HB_TX_POLARITY_NORMAL, + pbus_value); +} + +static int en8811h_config_serdes_polarity(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + unsigned int pol, default_pol; + u32 pbus_value = 0; + int ret; + + default_pol = PHY_POL_NORMAL; + if (device_property_read_bool(dev, "airoha,pnswap-rx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_rx_polarity(dev_fwnode(dev), phy_modes(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_INVERT) + pbus_value |= EN8811H_POLARITY_RX_REVERSE; + + default_pol = PHY_POL_NORMAL; + if (device_property_read_bool(dev, "airoha,pnswap-tx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_tx_polarity(dev_fwnode(dev), phy_modes(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_NORMAL) + pbus_value |= EN8811H_POLARITY_TX_NORMAL; + + return air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, + EN8811H_POLARITY_RX_REVERSE | + EN8811H_POLARITY_TX_NORMAL, pbus_value); +} + +static int an8811hb_config_init(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + int ret; + + /* If restart happened in .probe(), no need to restart now */ + if (priv->mcu_needs_restart) { + ret = en8811h_restart_mcu(phydev); + if (ret < 0) + return ret; + } else { + /* Next calls to .config_init() mcu needs to restart */ + priv->mcu_needs_restart = true; + } + + ret = an8811hb_config_serdes_polarity(phydev); + if (ret < 0) + return ret; + + ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR, + AIR_LED_MODE_USER_DEFINE); + if (ret < 0) + phydev_err(phydev, "Failed to initialize leds: %d\n", ret); + + return ret; +} + static int en8811h_config_init(struct phy_device *phydev) { struct en8811h_priv *priv = phydev->priv; - struct device *dev = &phydev->mdio.dev; - u32 pbus_value; int ret; /* If restart happened in .probe(), no need to restart now */ @@ -1003,19 +1379,7 @@ static int en8811h_config_init(struct phy_device *phydev) if (ret < 0) return ret; - /* Serdes polarity */ - pbus_value = 0; - if (device_property_read_bool(dev, "airoha,pnswap-rx")) - pbus_value |= EN8811H_POLARITY_RX_REVERSE; - else - pbus_value &= ~EN8811H_POLARITY_RX_REVERSE; - if (device_property_read_bool(dev, "airoha,pnswap-tx")) - pbus_value &= ~EN8811H_POLARITY_TX_NORMAL; - else - pbus_value |= EN8811H_POLARITY_TX_NORMAL; - ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, - EN8811H_POLARITY_RX_REVERSE | - EN8811H_POLARITY_TX_NORMAL, pbus_value); + ret = en8811h_config_serdes_polarity(phydev); if (ret < 0) return ret; @@ -1093,13 +1457,23 @@ static int en8811h_read_status(struct phy_device *phydev) if (ret < 0) return ret; - /* Get link partner 2.5GBASE-T ability from vendor register */ - ret = air_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA, &pbus_value); - if (ret < 0) - return ret; - linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, - phydev->lp_advertising, - pbus_value & EN8811H_2P5G_LPA_2P5G); + if (phy_id_compare_model(phydev->phy_id, AN8811HB_PHY_ID)) { + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + val & MDIO_AN_10GBT_STAT_LP2_5G); + } else { + /* Get link partner 2.5GBASE-T ability from vendor register */ + ret = air_buckpbus_reg_read(phydev, EN8811H_2P5G_LPA, + &pbus_value); + if (ret < 0) + return ret; + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + pbus_value & EN8811H_2P5G_LPA_2P5G); + } if (phydev->autoneg_complete) phy_resolve_aneg_pause(phydev); @@ -1121,6 +1495,9 @@ static int en8811h_read_status(struct phy_device *phydev) case AIR_AUX_CTRL_STATUS_SPEED_100: phydev->speed = SPEED_100; break; + case AIR_AUX_CTRL_STATUS_SPEED_10: + phydev->speed = SPEED_10; + break; } /* Firmware before version 24011202 has no vendor register 2P5G_LPA. @@ -1205,20 +1582,44 @@ static struct phy_driver en8811h_driver[] = { .led_brightness_set = air_led_brightness_set, .led_hw_control_set = air_led_hw_control_set, .led_hw_control_get = air_led_hw_control_get, +}, +{ + PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID), + .name = "Airoha AN8811HB", + .probe = an8811hb_probe, + .get_features = en8811h_get_features, + .config_init = an8811hb_config_init, + .get_rate_matching = en8811h_get_rate_matching, + .config_aneg = en8811h_config_aneg, + .read_status = en8811h_read_status, + .resume = en8811h_resume, + .suspend = en8811h_suspend, + .config_intr = en8811h_clear_intr, + .handle_interrupt = en8811h_handle_interrupt, + .led_hw_is_supported = en8811h_led_hw_is_supported, + .read_page = air_phy_read_page, + .write_page = air_phy_write_page, + .led_blink_set = air_led_blink_set, + .led_brightness_set = air_led_brightness_set, + .led_hw_control_set = air_led_hw_control_set, + .led_hw_control_get = air_led_hw_control_get, } }; module_phy_driver(en8811h_driver); static const struct mdio_device_id __maybe_unused en8811h_tbl[] = { { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) }, + { PHY_ID_MATCH_MODEL(AN8811HB_PHY_ID) }, { } }; MODULE_DEVICE_TABLE(mdio, en8811h_tbl); MODULE_FIRMWARE(EN8811H_MD32_DM); MODULE_FIRMWARE(EN8811H_MD32_DSP); +MODULE_FIRMWARE(AN8811HB_MD32_DM); +MODULE_FIRMWARE(AN8811HB_MD32_DSP); -MODULE_DESCRIPTION("Airoha EN8811H PHY drivers"); +MODULE_DESCRIPTION("Airoha EN8811H and AN8811HB PHY drivers"); MODULE_AUTHOR("Airoha"); MODULE_AUTHOR("Eric Woudstra <ericwouds@gmail.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/ax88796b_rust.rs b/drivers/net/phy/ax88796b_rust.rs index bc73ebccc2aa..2d24628a4e58 100644 --- a/drivers/net/phy/ax88796b_rust.rs +++ b/drivers/net/phy/ax88796b_rust.rs @@ -5,7 +5,6 @@ //! //! C version of this driver: [`drivers/net/phy/ax88796b.c`](./ax88796b.c) use kernel::{ - c_str, net::phy::{self, reg::C22, DeviceId, Driver}, prelude::*, uapi, @@ -41,7 +40,7 @@ struct PhyAX88772A; #[vtable] impl Driver for PhyAX88772A { const FLAGS: u32 = phy::flags::IS_INTERNAL; - const NAME: &'static CStr = c_str!("Asix Electronics AX88772A"); + const NAME: &'static CStr = c"Asix Electronics AX88772A"; const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_exact_mask(0x003b1861); // AX88772A is not working properly with some old switches (NETGEAR EN 108TP): @@ -105,7 +104,7 @@ struct PhyAX88772C; #[vtable] impl Driver for PhyAX88772C { const FLAGS: u32 = phy::flags::IS_INTERNAL; - const NAME: &'static CStr = c_str!("Asix Electronics AX88772C"); + const NAME: &'static CStr = c"Asix Electronics AX88772C"; const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_exact_mask(0x003b1881); fn suspend(dev: &mut phy::Device) -> Result { @@ -125,7 +124,7 @@ struct PhyAX88796B; #[vtable] impl Driver for PhyAX88796B { - const NAME: &'static CStr = c_str!("Asix Electronics AX88796B"); + const NAME: &'static CStr = c"Asix Electronics AX88796B"; const PHY_DEVICE_ID: DeviceId = DeviceId::new_with_model_mask(0x003b1841); fn soft_reset(dev: &mut phy::Device) -> Result { diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c index 33db21251f2e..c012dfab3171 100644 --- a/drivers/net/phy/dp83822.c +++ b/drivers/net/phy/dp83822.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/phy.h> +#include <linux/phy_port.h> #include <linux/netdevice.h> #include <linux/bitfield.h> @@ -811,17 +812,6 @@ static int dp83822_of_init(struct phy_device *phydev) int i, ret; u32 val; - /* Signal detection for the PHY is only enabled if the FX_EN and the - * SD_EN pins are strapped. Signal detection can only enabled if FX_EN - * is strapped otherwise signal detection is disabled for the PHY. - */ - if (dp83822->fx_enabled && dp83822->fx_sd_enable) - dp83822->fx_signal_det_low = device_property_present(dev, - "ti,link-loss-low"); - if (!dp83822->fx_enabled) - dp83822->fx_enabled = device_property_present(dev, - "ti,fiber-mode"); - if (!device_property_read_string(dev, "ti,gpio2-clk-out", &of_val)) { if (strcmp(of_val, "mac-if") == 0) { dp83822->gpio2_clk_out = DP83822_CLK_SRC_MAC_IF; @@ -950,6 +940,48 @@ static int dp83822_read_straps(struct phy_device *phydev) return 0; } +static int dp83822_attach_mdi_port(struct phy_device *phydev, + struct phy_port *port) +{ + struct dp83822_private *dp83822 = phydev->priv; + int ret; + + if (port->mediums) { + if (phy_port_is_fiber(port)) + dp83822->fx_enabled = true; + } else { + ret = dp83822_read_straps(phydev); + if (ret) + return ret; + +#if IS_ENABLED(CONFIG_OF_MDIO) + if (dp83822->fx_enabled && dp83822->fx_sd_enable) + dp83822->fx_signal_det_low = + device_property_present(&phydev->mdio.dev, + "ti,link-loss-low"); + + /* ti,fiber-mode is still used for backwards compatibility, but + * has been replaced with the mdi node definition, see + * ethernet-port.yaml + */ + if (!dp83822->fx_enabled) + dp83822->fx_enabled = + device_property_present(&phydev->mdio.dev, + "ti,fiber-mode"); +#endif /* CONFIG_OF_MDIO */ + + if (dp83822->fx_enabled) { + port->mediums = BIT(ETHTOOL_LINK_MEDIUM_BASEF); + } else { + /* This PHY can only to 100BaseTX max, so on 2 pairs */ + port->pairs = 2; + port->mediums = BIT(ETHTOOL_LINK_MEDIUM_BASET); + } + } + + return 0; +} + static int dp8382x_probe(struct phy_device *phydev) { struct dp83822_private *dp83822; @@ -968,27 +1000,13 @@ static int dp8382x_probe(struct phy_device *phydev) static int dp83822_probe(struct phy_device *phydev) { - struct dp83822_private *dp83822; int ret; ret = dp8382x_probe(phydev); if (ret) return ret; - dp83822 = phydev->priv; - - ret = dp83822_read_straps(phydev); - if (ret) - return ret; - - ret = dp83822_of_init(phydev); - if (ret) - return ret; - - if (dp83822->fx_enabled) - phydev->port = PORT_FIBRE; - - return 0; + return dp83822_of_init(phydev); } static int dp83826_probe(struct phy_device *phydev) @@ -1172,6 +1190,7 @@ static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index, .led_hw_is_supported = dp83822_led_hw_is_supported, \ .led_hw_control_set = dp83822_led_hw_control_set, \ .led_hw_control_get = dp83822_led_hw_control_get, \ + .attach_mdi_port = dp83822_attach_mdi_port \ } #define DP83825_PHY_DRIVER(_id, _name) \ diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 5f5de01c41e1..3fb2293f568f 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -75,6 +75,7 @@ #define MII_DP83867_MICR_JABBER_INT_EN BIT(0) /* RGMIICTL bits */ +#define DP83867_RGMII_EN BIT(7) #define DP83867_RGMII_TX_CLK_DELAY_EN BIT(1) #define DP83867_RGMII_RX_CLK_DELAY_EN BIT(0) @@ -100,7 +101,7 @@ #define DP83867_PHYCR_FIFO_DEPTH_MAX 0x03 #define DP83867_PHYCR_TX_FIFO_DEPTH_MASK GENMASK(15, 14) #define DP83867_PHYCR_RX_FIFO_DEPTH_MASK GENMASK(13, 12) -#define DP83867_PHYCR_RESERVED_MASK BIT(11) +#define DP83867_PHYCR_SGMII_EN BIT(11) #define DP83867_PHYCR_FORCE_LINK_GOOD BIT(10) /* RGMIIDCTL bits */ @@ -744,53 +745,31 @@ static int dp83867_config_init(struct phy_device *phydev) */ phy_disable_eee(phydev); - if (phy_interface_is_rgmii(phydev) || - phydev->interface == PHY_INTERFACE_MODE_SGMII) { - val = phy_read(phydev, MII_DP83867_PHYCTRL); - if (val < 0) - return val; + val = phy_read(phydev, MII_DP83867_PHYCTRL); + if (val < 0) + return val; - val &= ~DP83867_PHYCR_TX_FIFO_DEPTH_MASK; - val |= (dp83867->tx_fifo_depth << - DP83867_PHYCR_TX_FIFO_DEPTH_SHIFT); + val &= ~DP83867_PHYCR_TX_FIFO_DEPTH_MASK; + val |= (dp83867->tx_fifo_depth << + DP83867_PHYCR_TX_FIFO_DEPTH_SHIFT); - if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { - val &= ~DP83867_PHYCR_RX_FIFO_DEPTH_MASK; - val |= (dp83867->rx_fifo_depth << - DP83867_PHYCR_RX_FIFO_DEPTH_SHIFT); - } - - ret = phy_write(phydev, MII_DP83867_PHYCTRL, val); - if (ret) - return ret; + val &= ~DP83867_PHYCR_SGMII_EN; + if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { + val &= ~DP83867_PHYCR_RX_FIFO_DEPTH_MASK; + val |= (dp83867->rx_fifo_depth << + DP83867_PHYCR_RX_FIFO_DEPTH_SHIFT) | + DP83867_PHYCR_SGMII_EN; } - if (phy_interface_is_rgmii(phydev)) { - val = phy_read(phydev, MII_DP83867_PHYCTRL); - if (val < 0) - return val; - - /* The code below checks if "port mirroring" N/A MODE4 has been - * enabled during power on bootstrap. - * - * Such N/A mode enabled by mistake can put PHY IC in some - * internal testing mode and disable RGMII transmission. - * - * In this particular case one needs to check STRAP_STS1 - * register's bit 11 (marked as RESERVED). - */ - - bs = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_STRAP_STS1); - if (bs & DP83867_STRAP_STS1_RESERVED) - val &= ~DP83867_PHYCR_RESERVED_MASK; - - ret = phy_write(phydev, MII_DP83867_PHYCTRL, val); - if (ret) - return ret; + ret = phy_write(phydev, MII_DP83867_PHYCTRL, val); + if (ret) + return ret; + if (phy_interface_is_rgmii(phydev)) { /* Set up RGMII delays */ val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL); + val |= DP83867_RGMII_EN; val &= ~(DP83867_RGMII_TX_CLK_DELAY_EN | DP83867_RGMII_RX_CLK_DELAY_EN); if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) val |= (DP83867_RGMII_TX_CLK_DELAY_EN | DP83867_RGMII_RX_CLK_DELAY_EN); @@ -806,6 +785,10 @@ static int dp83867_config_init(struct phy_device *phydev) phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIIDCTL, dp83867->rx_id_delay | (dp83867->tx_id_delay << DP83867_RGMII_TX_CLK_DELAY_SHIFT)); + } else { + val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL); + val &= ~DP83867_RGMII_EN; + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL, val); } /* If specified, set io impedance */ diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c index 50684271f81a..0b83fb30a548 100644 --- a/drivers/net/phy/fixed_phy.c +++ b/drivers/net/phy/fixed_phy.c @@ -10,39 +10,34 @@ #include <linux/kernel.h> #include <linux/module.h> -#include <linux/list.h> #include <linux/mii.h> #include <linux/phy.h> #include <linux/phy_fixed.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/of.h> -#include <linux/idr.h> #include <linux/netdevice.h> #include "swphy.h" +/* The DSA loop driver may allocate 4 fixed PHY's, and 4 additional + * fixed PHY's for a system should be sufficient. + */ +#define NUM_FP 8 + struct fixed_phy { - int addr; struct phy_device *phydev; struct fixed_phy_status status; int (*link_update)(struct net_device *, struct fixed_phy_status *); - struct list_head node; }; +static DECLARE_BITMAP(fixed_phy_ids, NUM_FP); +static struct fixed_phy fmb_fixed_phys[NUM_FP]; static struct mii_bus *fmb_mii_bus; -static LIST_HEAD(fmb_phys); static struct fixed_phy *fixed_phy_find(int addr) { - struct fixed_phy *fp; - - list_for_each_entry(fp, &fmb_phys, node) { - if (fp->addr == addr) - return fp; - } - - return NULL; + return test_bit(addr, fixed_phy_ids) ? fmb_fixed_phys + addr : NULL; } int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier) @@ -108,42 +103,29 @@ int fixed_phy_set_link_update(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); -static int __fixed_phy_add(int phy_addr, - const struct fixed_phy_status *status) +static void fixed_phy_del(int phy_addr) { struct fixed_phy *fp; - int ret; - ret = swphy_validate_state(status); - if (ret < 0) - return ret; - - fp = kzalloc(sizeof(*fp), GFP_KERNEL); + fp = fixed_phy_find(phy_addr); if (!fp) - return -ENOMEM; - - fp->addr = phy_addr; - fp->status = *status; - fp->status.link = true; - - list_add_tail(&fp->node, &fmb_phys); + return; - return 0; + memset(fp, 0, sizeof(*fp)); + clear_bit(phy_addr, fixed_phy_ids); } -static DEFINE_IDA(phy_fixed_ida); - -static void fixed_phy_del(int phy_addr) +static int fixed_phy_get_free_addr(void) { - struct fixed_phy *fp; + int addr; - fp = fixed_phy_find(phy_addr); - if (!fp) - return; + do { + addr = find_first_zero_bit(fixed_phy_ids, NUM_FP); + if (addr == NUM_FP) + return -ENOSPC; + } while (test_and_set_bit(addr, fixed_phy_ids)); - list_del(&fp->node); - kfree(fp); - ida_free(&phy_fixed_ida, phy_addr); + return addr; } struct phy_device *fixed_phy_register(const struct fixed_phy_status *status, @@ -153,19 +135,20 @@ struct phy_device *fixed_phy_register(const struct fixed_phy_status *status, int phy_addr; int ret; + ret = swphy_validate_state(status); + if (ret < 0) + return ERR_PTR(ret); + if (!fmb_mii_bus || fmb_mii_bus->state != MDIOBUS_REGISTERED) return ERR_PTR(-EPROBE_DEFER); - /* Get the next available PHY address, up to PHY_MAX_ADDR */ - phy_addr = ida_alloc_max(&phy_fixed_ida, PHY_MAX_ADDR - 1, GFP_KERNEL); + /* Get the next available PHY address, up to NUM_FP */ + phy_addr = fixed_phy_get_free_addr(); if (phy_addr < 0) return ERR_PTR(phy_addr); - ret = __fixed_phy_add(phy_addr, status); - if (ret < 0) { - ida_free(&phy_fixed_ida, phy_addr); - return ERR_PTR(ret); - } + fmb_fixed_phys[phy_addr].status = *status; + fmb_fixed_phys[phy_addr].status.link = true; phy = get_phy_device(fmb_mii_bus, phy_addr, false); if (IS_ERR(phy)) { @@ -237,16 +220,8 @@ module_init(fixed_mdio_bus_init); static void __exit fixed_mdio_bus_exit(void) { - struct fixed_phy *fp, *tmp; - mdiobus_unregister(fmb_mii_bus); mdiobus_free(fmb_mii_bus); - - list_for_each_entry_safe(fp, tmp, &fmb_phys, node) { - list_del(&fp->node); - kfree(fp); - } - ida_destroy(&phy_fixed_ida); } module_exit(fixed_mdio_bus_exit); diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 894bcee61e65..ba1bbb6c63d6 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -13,7 +13,7 @@ #include <linux/mdio.h> #include <linux/marvell_phy.h> #include <linux/of.h> -#include <linux/sfp.h> +#include <linux/phy_port.h> #include <linux/netdevice.h> /* Port PCS Configuration */ @@ -473,89 +473,70 @@ static int mv2222_config_init(struct phy_device *phydev) return 0; } -static int mv2222_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +static int mv2222_configure_serdes(struct phy_port *port, bool enable, + phy_interface_t interface) { - struct phy_device *phydev = upstream; - const struct sfp_module_caps *caps; - phy_interface_t sfp_interface; + struct phy_device *phydev = port_phydev(port); struct mv2222_data *priv; - struct device *dev; - int ret; + int ret = 0; priv = phydev->priv; - dev = &phydev->mdio.dev; - - caps = sfp_get_module_caps(phydev->sfp_bus); - - phydev->port = caps->port; - sfp_interface = sfp_select_interface(phydev->sfp_bus, caps->link_modes); - - dev_info(dev, "%s SFP module inserted\n", phy_modes(sfp_interface)); + priv->line_interface = interface; - if (sfp_interface != PHY_INTERFACE_MODE_10GBASER && - sfp_interface != PHY_INTERFACE_MODE_1000BASEX && - sfp_interface != PHY_INTERFACE_MODE_SGMII) { - dev_err(dev, "Incompatible SFP module inserted\n"); + if (enable) { + linkmode_and(priv->supported, phydev->supported, port->supported); - return -EINVAL; - } - - priv->line_interface = sfp_interface; - linkmode_and(priv->supported, phydev->supported, caps->link_modes); + ret = mv2222_config_line(phydev); + if (ret < 0) + return ret; - ret = mv2222_config_line(phydev); - if (ret < 0) - return ret; + if (mutex_trylock(&phydev->lock)) { + ret = mv2222_config_aneg(phydev); + mutex_unlock(&phydev->lock); + } - if (mutex_trylock(&phydev->lock)) { - ret = mv2222_config_aneg(phydev); - mutex_unlock(&phydev->lock); + } else { + linkmode_zero(priv->supported); } return ret; } -static void mv2222_sfp_remove(void *upstream) +static void mv2222_port_link_up(struct phy_port *port) { - struct phy_device *phydev = upstream; - struct mv2222_data *priv; - - priv = phydev->priv; - - priv->line_interface = PHY_INTERFACE_MODE_NA; - linkmode_zero(priv->supported); - phydev->port = PORT_NONE; -} - -static void mv2222_sfp_link_up(void *upstream) -{ - struct phy_device *phydev = upstream; + struct phy_device *phydev = port_phydev(port); struct mv2222_data *priv; priv = phydev->priv; priv->sfp_link = true; } -static void mv2222_sfp_link_down(void *upstream) +static void mv2222_port_link_down(struct phy_port *port) { - struct phy_device *phydev = upstream; + struct phy_device *phydev = port_phydev(port); struct mv2222_data *priv; priv = phydev->priv; priv->sfp_link = false; } -static const struct sfp_upstream_ops sfp_phy_ops = { - .module_insert = mv2222_sfp_insert, - .module_remove = mv2222_sfp_remove, - .link_up = mv2222_sfp_link_up, - .link_down = mv2222_sfp_link_down, - .attach = phy_sfp_attach, - .detach = phy_sfp_detach, - .connect_phy = phy_sfp_connect_phy, - .disconnect_phy = phy_sfp_disconnect_phy, +static const struct phy_port_ops mv2222_port_ops = { + .link_up = mv2222_port_link_up, + .link_down = mv2222_port_link_down, + .configure_mii = mv2222_configure_serdes, }; +static int mv2222_attach_mii_port(struct phy_device *phydev, struct phy_port *port) +{ + port->ops = &mv2222_port_ops; + + __set_bit(PHY_INTERFACE_MODE_10GBASER, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces); + + return 0; +} + static int mv2222_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -591,7 +572,7 @@ static int mv2222_probe(struct phy_device *phydev) priv->line_interface = PHY_INTERFACE_MODE_NA; phydev->priv = priv; - return phy_sfp_probe(phydev, &sfp_phy_ops); + return 0; } static struct phy_driver mv2222_drivers[] = { @@ -608,6 +589,7 @@ static struct phy_driver mv2222_drivers[] = { .suspend = mv2222_suspend, .resume = mv2222_resume, .read_status = mv2222_read_status, + .attach_mii_port = mv2222_attach_mii_port, }, }; module_phy_driver(mv2222_drivers); diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index c248c90510ae..7a578b5aa2ed 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -29,10 +29,10 @@ #include <linux/ethtool.h> #include <linux/ethtool_netlink.h> #include <linux/phy.h> +#include <linux/phy_port.h> #include <linux/marvell_phy.h> #include <linux/bitfield.h> #include <linux/of.h> -#include <linux/sfp.h> #include <linux/io.h> #include <asm/irq.h> @@ -3598,11 +3598,10 @@ static int marvell_probe(struct phy_device *phydev) return marvell_hwmon_probe(phydev); } -static int m88e1510_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +static int m88e1510_port_configure_serdes(struct phy_port *port, bool enable, + phy_interface_t interface) { - struct phy_device *phydev = upstream; - const struct sfp_module_caps *caps; - phy_interface_t interface; + struct phy_device *phydev = port_phydev(port); struct device *dev; int oldpage; int ret = 0; @@ -3610,28 +3609,27 @@ static int m88e1510_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) dev = &phydev->mdio.dev; - caps = sfp_get_module_caps(phydev->sfp_bus); - interface = sfp_select_interface(phydev->sfp_bus, caps->link_modes); + if (enable) { + switch (interface) { + case PHY_INTERFACE_MODE_1000BASEX: + mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X; - dev_info(dev, "%s SFP module inserted\n", phy_modes(interface)); + break; + case PHY_INTERFACE_MODE_100BASEX: + mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX; - switch (interface) { - case PHY_INTERFACE_MODE_1000BASEX: - mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X; + break; + case PHY_INTERFACE_MODE_SGMII: + mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII; - break; - case PHY_INTERFACE_MODE_100BASEX: - mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX; - - break; - case PHY_INTERFACE_MODE_SGMII: - mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII; + break; + default: + dev_err(dev, "Incompatible SFP module inserted\n"); - break; - default: - dev_err(dev, "Incompatible SFP module inserted\n"); - - return -EINVAL; + return -EINVAL; + } + } else { + mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII; } oldpage = phy_select_page(phydev, MII_MARVELL_MODE_PAGE); @@ -3650,47 +3648,20 @@ error: return phy_restore_page(phydev, oldpage, ret); } -static void m88e1510_sfp_remove(void *upstream) -{ - struct phy_device *phydev = upstream; - int oldpage; - int ret = 0; - - oldpage = phy_select_page(phydev, MII_MARVELL_MODE_PAGE); - if (oldpage < 0) - goto error; - - ret = __phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1, - MII_88E1510_GEN_CTRL_REG_1_MODE_MASK, - MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII); - if (ret < 0) - goto error; - - ret = __phy_set_bits(phydev, MII_88E1510_GEN_CTRL_REG_1, - MII_88E1510_GEN_CTRL_REG_1_RESET); - -error: - phy_restore_page(phydev, oldpage, ret); -} - -static const struct sfp_upstream_ops m88e1510_sfp_ops = { - .module_insert = m88e1510_sfp_insert, - .module_remove = m88e1510_sfp_remove, - .attach = phy_sfp_attach, - .detach = phy_sfp_detach, - .connect_phy = phy_sfp_connect_phy, - .disconnect_phy = phy_sfp_disconnect_phy, +static const struct phy_port_ops m88e1510_serdes_port_ops = { + .configure_mii = m88e1510_port_configure_serdes, }; -static int m88e1510_probe(struct phy_device *phydev) +static int m88e1510_attach_mii_port(struct phy_device *phy_device, + struct phy_port *port) { - int err; + port->ops = &m88e1510_serdes_port_ops; - err = marvell_probe(phydev); - if (err) - return err; + __set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_100BASEX, port->interfaces); - return phy_sfp_probe(phydev, &m88e1510_sfp_ops); + return 0; } static struct phy_driver marvell_drivers[] = { @@ -3950,7 +3921,7 @@ static struct phy_driver marvell_drivers[] = { .driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops), .features = PHY_GBIT_FIBRE_FEATURES, .flags = PHY_POLL_CABLE_TEST, - .probe = m88e1510_probe, + .probe = marvell_probe, .config_init = m88e1510_config_init, .config_aneg = m88e1510_config_aneg, .read_status = marvell_read_status, @@ -3976,6 +3947,7 @@ static struct phy_driver marvell_drivers[] = { .led_hw_is_supported = m88e1318_led_hw_is_supported, .led_hw_control_set = m88e1318_led_hw_control_set, .led_hw_control_get = m88e1318_led_hw_control_get, + .attach_mii_port = m88e1510_attach_mii_port, }, { .phy_id = MARVELL_PHY_ID_88E1540, diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index 8fd42131cdbf..b40df82152cd 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -28,7 +28,7 @@ #include <linux/hwmon.h> #include <linux/marvell_phy.h> #include <linux/phy.h> -#include <linux/sfp.h> +#include <linux/phy_port.h> #include <linux/netdevice.h> #define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe @@ -463,30 +463,29 @@ static int mv3310_set_edpd(struct phy_device *phydev, u16 edpd) return err; } -static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +static int mv3310_attach_mii_port(struct phy_device *phydev, + struct phy_port *port) { - struct phy_device *phydev = upstream; - const struct sfp_module_caps *caps; - phy_interface_t iface; + __set_bit(PHY_INTERFACE_MODE_10GBASER, port->interfaces); + return 0; +} - caps = sfp_get_module_caps(phydev->sfp_bus); - iface = sfp_select_interface(phydev->sfp_bus, caps->link_modes); +static int mv3310_attach_mdi_port(struct phy_device *phydev, + struct phy_port *port) +{ + /* This PHY can do combo-ports, i.e. 2 MDI outputs, usually one + * of them going to an SFP and the other one to a RJ45 + * connector. If we don't have any representation for the port + * in DT, and we are dealing with a non-SFP port, then we + * mask the port's capabilities to report BaseT-only modes + */ + if (port->not_described) + return phy_port_restrict_mediums(port, + BIT(ETHTOOL_LINK_MEDIUM_BASET)); - if (iface != PHY_INTERFACE_MODE_10GBASER) { - dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n"); - return -EINVAL; - } return 0; } -static const struct sfp_upstream_ops mv3310_sfp_ops = { - .attach = phy_sfp_attach, - .detach = phy_sfp_detach, - .connect_phy = phy_sfp_connect_phy, - .disconnect_phy = phy_sfp_disconnect_phy, - .module_insert = mv3310_sfp_insert, -}; - static int mv3310_probe(struct phy_device *phydev) { const struct mv3310_chip *chip = to_mv3310_chip(phydev); @@ -544,7 +543,9 @@ static int mv3310_probe(struct phy_device *phydev) chip->init_supported_interfaces(priv->supported_interfaces); - return phy_sfp_probe(phydev, &mv3310_sfp_ops); + phydev->max_n_ports = 2; + + return 0; } static void mv3310_remove(struct phy_device *phydev) @@ -1405,6 +1406,8 @@ static struct phy_driver mv3310_drivers[] = { .set_loopback = genphy_c45_loopback, .get_wol = mv3110_get_wol, .set_wol = mv3110_set_wol, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, }, { .phy_id = MARVELL_PHY_ID_88X3310, @@ -1424,6 +1427,8 @@ static struct phy_driver mv3310_drivers[] = { .set_tunable = mv3310_set_tunable, .remove = mv3310_remove, .set_loopback = genphy_c45_loopback, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, }, { .phy_id = MARVELL_PHY_ID_88E2110, @@ -1444,6 +1449,8 @@ static struct phy_driver mv3310_drivers[] = { .set_loopback = genphy_c45_loopback, .get_wol = mv3110_get_wol, .set_wol = mv3110_set_wol, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, }, { .phy_id = MARVELL_PHY_ID_88E2110, @@ -1462,6 +1469,8 @@ static struct phy_driver mv3310_drivers[] = { .set_tunable = mv3310_set_tunable, .remove = mv3310_remove, .set_loopback = genphy_c45_loopback, + .attach_mii_port = mv3310_attach_mii_port, + .attach_mdi_port = mv3310_attach_mdi_port, }, }; diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c index 6e90ed42cd98..65636070a222 100644 --- a/drivers/net/phy/mdio_device.c +++ b/drivers/net/phy/mdio_device.c @@ -36,18 +36,6 @@ static void mdio_device_release(struct device *dev) kfree(to_mdio_device(dev)); } -static int mdio_device_bus_match(struct device *dev, - const struct device_driver *drv) -{ - struct mdio_device *mdiodev = to_mdio_device(dev); - const struct mdio_driver *mdiodrv = to_mdio_driver(drv); - - if (mdiodrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY) - return 0; - - return strcmp(mdiodev->modalias, drv->name) == 0; -} - struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr) { struct mdio_device *mdiodev; @@ -60,7 +48,6 @@ struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr) mdiodev->dev.release = mdio_device_release; mdiodev->dev.parent = &bus->dev; mdiodev->dev.bus = &mdio_bus_type; - mdiodev->bus_match = mdio_device_bus_match; mdiodev->device_free = mdio_device_free; mdiodev->device_remove = mdio_device_remove; mdiodev->bus = bus; diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c index 2c4bbc236202..9a54949644d5 100644 --- a/drivers/net/phy/mediatek/mtk-ge-soc.c +++ b/drivers/net/phy/mediatek/mtk-ge-soc.c @@ -1508,6 +1508,8 @@ static struct phy_driver mtk_socphy_driver[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581), .name = "Airoha AN7581 PHY", + .config_intr = genphy_no_config_intr, + .handle_interrupt = genphy_handle_interrupt_no_ack, .probe = an7581_phy_probe, .led_blink_set = mt798x_phy_led_blink_set, .led_brightness_set = mt798x_phy_led_brightness_set, diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 8208ecbb575c..663dcdc92204 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -101,6 +101,14 @@ #define LAN8814_CABLE_DIAG_VCT_DATA_MASK GENMASK(7, 0) #define LAN8814_PAIR_BIT_SHIFT 12 +/* KSZ9x31 remote loopback register */ +#define KSZ9x31_REMOTE_LOOPBACK 0x11 +/* This is an undocumented bit of the KSZ9131RNX. + * It was reported by NXP in cooperation with Micrel. + */ +#define KSZ9x31_REMOTE_LOOPBACK_KEEP_PREAMBLE BIT(2) +#define KSZ9x31_REMOTE_LOOPBACK_EN BIT(8) + #define LAN8814_SKUS 0xB #define LAN8814_WIRE_PAIR_MASK 0xF @@ -1500,7 +1508,11 @@ static int ksz9131_config_init(struct phy_device *phydev) if (ret < 0) return ret; - return 0; + if (phydev->dev_flags & PHY_F_KEEP_PREAMBLE_BEFORE_SFD) + ret = phy_modify(phydev, KSZ9x31_REMOTE_LOOPBACK, 0, + KSZ9x31_REMOTE_LOOPBACK_KEEP_PREAMBLE); + + return ret; } #define MII_KSZ9131_AUTO_MDIX 0x1C @@ -3156,6 +3168,18 @@ static void lan8814_flush_fifo(struct phy_device *phydev, bool egress) lanphy_read_page_reg(phydev, LAN8814_PAGE_PORT_REGS, PTP_TSU_INT_STS); } +static int lan8814_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config) +{ + struct kszphy_ptp_priv *ptp_priv = + container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); + + config->tx_type = ptp_priv->hwts_tx_type; + config->rx_filter = ptp_priv->rx_filter; + + return 0; +} + static int lan8814_hwtstamp_set(struct mii_timestamper *mii_ts, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack) @@ -3166,9 +3190,6 @@ static int lan8814_hwtstamp_set(struct mii_timestamper *mii_ts, int txcfg = 0, rxcfg = 0; int pkt_ts_enable; - ptp_priv->hwts_tx_type = config->tx_type; - ptp_priv->rx_filter = config->rx_filter; - switch (config->rx_filter) { case HWTSTAMP_FILTER_NONE: ptp_priv->layer = 0; @@ -3196,6 +3217,18 @@ static int lan8814_hwtstamp_set(struct mii_timestamper *mii_ts, return -ERANGE; } + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + case HWTSTAMP_TX_ON: + case HWTSTAMP_TX_ONESTEP_SYNC: + break; + default: + return -ERANGE; + } + + ptp_priv->hwts_tx_type = config->tx_type; + ptp_priv->rx_filter = config->rx_filter; + if (ptp_priv->layer & PTP_CLASS_L2) { rxcfg = PTP_RX_PARSE_CONFIG_LAYER2_EN_; txcfg = PTP_TX_PARSE_CONFIG_LAYER2_EN_; @@ -4399,6 +4432,7 @@ static void lan8814_ptp_init(struct phy_device *phydev) ptp_priv->mii_ts.rxtstamp = lan8814_rxtstamp; ptp_priv->mii_ts.txtstamp = lan8814_txtstamp; ptp_priv->mii_ts.hwtstamp_set = lan8814_hwtstamp_set; + ptp_priv->mii_ts.hwtstamp_get = lan8814_hwtstamp_get; ptp_priv->mii_ts.ts_info = lan8814_ts_info; phydev->mii_ts = &ptp_priv->mii_ts; @@ -5060,9 +5094,6 @@ static int lan8841_hwtstamp_set(struct mii_timestamper *mii_ts, int txcfg = 0, rxcfg = 0; int pkt_ts_enable; - ptp_priv->hwts_tx_type = config->tx_type; - ptp_priv->rx_filter = config->rx_filter; - switch (config->rx_filter) { case HWTSTAMP_FILTER_NONE: ptp_priv->layer = 0; @@ -5090,6 +5121,18 @@ static int lan8841_hwtstamp_set(struct mii_timestamper *mii_ts, return -ERANGE; } + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + case HWTSTAMP_TX_ON: + case HWTSTAMP_TX_ONESTEP_SYNC: + break; + default: + return -ERANGE; + } + + ptp_priv->hwts_tx_type = config->tx_type; + ptp_priv->rx_filter = config->rx_filter; + /* Setup parsing of the frames and enable the timestamping for ptp * frames */ @@ -5934,6 +5977,7 @@ static int lan8841_probe(struct phy_device *phydev) ptp_priv->mii_ts.rxtstamp = lan8841_rxtstamp; ptp_priv->mii_ts.txtstamp = lan8814_txtstamp; ptp_priv->mii_ts.hwtstamp_set = lan8841_hwtstamp_set; + ptp_priv->mii_ts.hwtstamp_get = lan8814_hwtstamp_get; ptp_priv->mii_ts.ts_info = lan8841_ts_info; phydev->mii_ts = &ptp_priv->mii_ts; diff --git a/drivers/net/phy/microchip_rds_ptp.c b/drivers/net/phy/microchip_rds_ptp.c index 4c6326b0ceaf..f5f2928e705f 100644 --- a/drivers/net/phy/microchip_rds_ptp.c +++ b/drivers/net/phy/microchip_rds_ptp.c @@ -476,6 +476,18 @@ static bool mchp_rds_ptp_rxtstamp(struct mii_timestamper *mii_ts, return true; } +static int mchp_rds_ptp_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *config) +{ + struct mchp_rds_ptp_clock *clock = + container_of(mii_ts, struct mchp_rds_ptp_clock, + mii_ts); + config->tx_type = clock->hwts_tx_type; + config->rx_filter = clock->rx_filter; + + return 0; +} + static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, struct kernel_hwtstamp_config *config, struct netlink_ext_ack *extack) @@ -488,9 +500,6 @@ static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, unsigned long flags; int rc; - clock->hwts_tx_type = config->tx_type; - clock->rx_filter = config->rx_filter; - switch (config->rx_filter) { case HWTSTAMP_FILTER_NONE: clock->layer = 0; @@ -518,6 +527,15 @@ static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, return -ERANGE; } + switch (config->tx_type) { + case HWTSTAMP_TX_ONESTEP_SYNC: + case HWTSTAMP_TX_ON: + case HWTSTAMP_TX_OFF: + break; + default: + return -ERANGE; + } + /* Setup parsing of the frames and enable the timestamping for ptp * frames */ @@ -553,7 +571,7 @@ static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, if (rc < 0) return rc; - if (clock->hwts_tx_type == HWTSTAMP_TX_ONESTEP_SYNC) + if (config->tx_type == HWTSTAMP_TX_ONESTEP_SYNC) /* Enable / disable of the TX timestamp in the SYNC frames */ rc = mchp_rds_phy_modify_mmd(clock, MCHP_RDS_PTP_TX_MOD, MCHP_RDS_PTP_PORT, @@ -587,8 +605,13 @@ static int mchp_rds_ptp_hwtstamp_set(struct mii_timestamper *mii_ts, /* Now enable the timestamping interrupts */ rc = mchp_rds_ptp_config_intr(clock, config->rx_filter != HWTSTAMP_FILTER_NONE); + if (rc < 0) + return rc; + + clock->hwts_tx_type = config->tx_type; + clock->rx_filter = config->rx_filter; - return rc < 0 ? rc : 0; + return 0; } static int mchp_rds_ptp_ts_info(struct mii_timestamper *mii_ts, @@ -1282,6 +1305,7 @@ struct mchp_rds_ptp_clock *mchp_rds_ptp_probe(struct phy_device *phydev, u8 mmd, clock->mii_ts.rxtstamp = mchp_rds_ptp_rxtstamp; clock->mii_ts.txtstamp = mchp_rds_ptp_txtstamp; clock->mii_ts.hwtstamp_set = mchp_rds_ptp_hwtstamp_set; + clock->mii_ts.hwtstamp_get = mchp_rds_ptp_hwtstamp_get; clock->mii_ts.ts_info = mchp_rds_ptp_ts_info; phydev->mii_ts = &clock->mii_ts; diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 42d46b5758fc..4d62f7b36212 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -910,6 +910,10 @@ static int ytphy_rgmii_clk_delay_config(struct phy_device *phydev) val |= FIELD_PREP(YT8521_RC1R_RX_DELAY_MASK, rx_reg) | FIELD_PREP(YT8521_RC1R_GE_TX_DELAY_MASK, tx_reg); break; + case PHY_INTERFACE_MODE_GMII: + if (phydev->drv->phy_id != PHY_ID_YT8531S) + return -EOPNOTSUPP; + return 0; default: /* do not support other modes */ return -EOPNOTSUPP; } diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c index 8e2fd6b942b6..5f99766fb64c 100644 --- a/drivers/net/phy/mxl-gpy.c +++ b/drivers/net/phy/mxl-gpy.c @@ -603,20 +603,6 @@ static int gpy_update_interface(struct phy_device *phydev) case SPEED_100: case SPEED_10: phydev->interface = PHY_INTERFACE_MODE_SGMII; - if (gpy_sgmii_aneg_en(phydev)) - break; - /* Enable and restart SGMII ANEG for 10/100/1000Mbps link speed - * if ANEG is disabled (in 2500-BaseX mode). - */ - ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL, - VSPEC1_SGMII_ANEN_ANRS, - VSPEC1_SGMII_ANEN_ANRS); - if (ret < 0) { - phydev_err(phydev, - "Error: Enable of SGMII ANEG failed: %d\n", - ret); - return ret; - } break; } @@ -1060,6 +1046,27 @@ static int gpy_led_polarity_set(struct phy_device *phydev, int index, return -EINVAL; } +static unsigned int gpy_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + case PHY_INTERFACE_MODE_2500BASEX: + return LINK_INBAND_DISABLE; + default: + return 0; + } +} + +static int gpy_config_inband(struct phy_device *phydev, unsigned int modes) +{ + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL, + VSPEC1_SGMII_ANEN_ANRS, + (modes == LINK_INBAND_DISABLE) ? 0 : + VSPEC1_SGMII_ANEN_ANRS); +} + static struct phy_driver gpy_drivers[] = { { PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx), @@ -1067,6 +1074,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1090,6 +1099,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1112,6 +1123,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1135,6 +1148,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy21x_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1157,6 +1172,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy21x_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1179,6 +1196,8 @@ static struct phy_driver gpy_drivers[] = { .name = "Maxlinear Ethernet GPY212B", .get_features = genphy_c45_pma_read_abilities, .config_init = gpy21x_config_init, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .probe = gpy_probe, .suspend = genphy_suspend, .resume = genphy_resume, @@ -1202,6 +1221,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy21x_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1225,6 +1246,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy21x_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1247,6 +1270,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy21x_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1269,6 +1294,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1286,6 +1313,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1303,6 +1332,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, @@ -1320,6 +1351,8 @@ static struct phy_driver gpy_drivers[] = { .get_features = genphy_c45_pma_read_abilities, .config_init = gpy_config_init, .probe = gpy_probe, + .inband_caps = gpy_inband_caps, + .config_inband = gpy_config_inband, .suspend = genphy_suspend, .resume = genphy_resume, .config_aneg = gpy_config_aneg, diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h index 4951a39f3828..421088e6f6e8 100644 --- a/drivers/net/phy/phy-caps.h +++ b/drivers/net/phy/phy-caps.h @@ -25,6 +25,7 @@ enum { LINK_CAPA_40000FD, LINK_CAPA_50000FD, LINK_CAPA_56000FD, + LINK_CAPA_80000FD, LINK_CAPA_100000FD, LINK_CAPA_200000FD, LINK_CAPA_400000FD, @@ -61,4 +62,9 @@ const struct link_capabilities * phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported, bool exact); +void phy_caps_medium_get_supported(unsigned long *supported, + enum ethtool_link_medium medium, + int lanes); +u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes); + #endif /* __PHY_CAPS_H */ diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 277c034bc32f..d7a4a977fc8a 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -4,6 +4,7 @@ */ #include <linux/export.h> #include <linux/phy.h> +#include <linux/phy_port.h> #include <linux/of.h> #include "phylib.h" @@ -47,6 +48,8 @@ const char *phy_speed_to_str(int speed) return "50Gbps"; case SPEED_56000: return "56Gbps"; + case SPEED_80000: + return "80Gbps"; case SPEED_100000: return "100Gbps"; case SPEED_200000: @@ -208,7 +211,12 @@ EXPORT_SYMBOL_GPL(phy_interface_num_ports); static void __set_phy_supported(struct phy_device *phydev, u32 max_speed) { + struct phy_port *port; + phy_caps_linkmode_max_speed(max_speed, phydev->supported); + + phy_for_each_port(phydev, port) + phy_caps_linkmode_max_speed(max_speed, port->supported); } /** diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 3a05982b39bf..942d43191561 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -21,6 +21,7 @@ static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = { { SPEED_40000, DUPLEX_FULL, {0} }, /* LINK_CAPA_40000FD */ { SPEED_50000, DUPLEX_FULL, {0} }, /* LINK_CAPA_50000FD */ { SPEED_56000, DUPLEX_FULL, {0} }, /* LINK_CAPA_56000FD */ + { SPEED_80000, DUPLEX_FULL, {0} }, /* LINK_CAPA_80000FD */ { SPEED_100000, DUPLEX_FULL, {0} }, /* LINK_CAPA_100000FD */ { SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */ { SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */ @@ -49,6 +50,7 @@ static int speed_duplex_to_capa(int speed, unsigned int duplex) case SPEED_40000: return LINK_CAPA_40000FD; case SPEED_50000: return LINK_CAPA_50000FD; case SPEED_56000: return LINK_CAPA_56000FD; + case SPEED_80000: return LINK_CAPA_80000FD; case SPEED_100000: return LINK_CAPA_100000FD; case SPEED_200000: return LINK_CAPA_200000FD; case SPEED_400000: return LINK_CAPA_400000FD; @@ -80,6 +82,14 @@ int __init phy_caps_init(void) /* Fill the caps array from net/ethtool/common.c */ for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) { linkmode = &link_mode_params[i]; + + /* Sanity check the linkmodes array for number of pairs */ + if (linkmode->pairs < linkmode->min_pairs) { + pr_err("Pairs count must not be under min_pairs for linkmode %d\n", + i); + return -EINVAL; + } + capa = speed_duplex_to_capa(linkmode->speed, linkmode->duplex); if (capa < 0) { @@ -378,3 +388,60 @@ unsigned long phy_caps_from_interface(phy_interface_t interface) return link_caps; } EXPORT_SYMBOL_GPL(phy_caps_from_interface); + +/** + * phy_caps_medium_get_supported() - Returns linkmodes supported on a given medium + * @supported: After this call, contains all possible linkmodes on a given medium, + * and with the given number of pairs, or less. + * @medium: The medium to get the support from + * @pairs: The number of pairs used on the given medium. Only relevant for modes + * that support this notion, such as BaseT. Pass 0 if not applicable. + * + * If no match exists, the supported field is left untouched. + */ +void phy_caps_medium_get_supported(unsigned long *supported, + enum ethtool_link_medium medium, + int pairs) +{ + int i; + + for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) { + /* Special bits such as Autoneg, Pause, Asym_pause, etc. are + * set and will be masked away by the port parent. + */ + if (link_mode_params[i].mediums == BIT(ETHTOOL_LINK_MEDIUM_NONE)) { + linkmode_set_bit(i, supported); + continue; + } + + /* If this medium matches, and had a non-zero min-pairs */ + if (link_mode_params[i].mediums & BIT(medium) && + (!link_mode_params[i].min_pairs || + (link_mode_params[i].min_pairs <= pairs && + link_mode_params[i].pairs >= pairs))) + linkmode_set_bit(i, supported); + } +} +EXPORT_SYMBOL_GPL(phy_caps_medium_get_supported); + +/** + * phy_caps_mediums_from_linkmodes() - Get all mediums from a linkmodes list + * @linkmodes: A bitset of linkmodes to get the mediums from + * + * Returns: A bitset of ETHTOOL_MEDIUM_XXX values corresponding to all medium + * types in the linkmodes list + */ +u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes) +{ + const struct link_mode_info *linkmode; + u32 mediums = 0; + int i; + + for_each_set_bit(i, linkmodes, __ETHTOOL_LINK_MODE_MASK_NBITS) { + linkmode = &link_mode_params[i]; + mediums |= linkmode->mediums; + } + + return mediums; +} +EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 81984d4ebb7c..8a3eb1839a3d 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -30,6 +30,7 @@ #include <linux/phylib_stubs.h> #include <linux/phy_led_triggers.h> #include <linux/phy_link_topology.h> +#include <linux/phy_port.h> #include <linux/pse-pd/pse.h> #include <linux/property.h> #include <linux/ptp_clock_kernel.h> @@ -48,9 +49,6 @@ MODULE_DESCRIPTION("PHY library"); MODULE_AUTHOR("Andy Fleming"); MODULE_LICENSE("GPL"); -#define PHY_ANY_ID "MATCH ANY PHY" -#define PHY_ANY_UID 0xffffffff - struct phy_fixup { struct list_head list; char bus_id[MII_BUS_ID_SIZE + 3]; @@ -431,11 +429,10 @@ static SIMPLE_DEV_PM_OPS(mdio_bus_phy_pm_ops, mdio_bus_phy_suspend, /** * phy_register_fixup - creates a new phy_fixup and adds it to the list - * @bus_id: A string which matches phydev->mdio.dev.bus_id (or PHY_ANY_ID) + * @bus_id: A string which matches phydev->mdio.dev.bus_id (or NULL) * @phy_uid: Used to match against phydev->phy_id (the UID of the PHY) - * It can also be PHY_ANY_UID * @phy_uid_mask: Applied to phydev->phy_id and fixup->phy_uid before - * comparison + * comparison (or 0 to disable id-based matching) * @run: The actual code to be run when a matching PHY is found */ static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, @@ -446,7 +443,8 @@ static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, if (!fixup) return -ENOMEM; - strscpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id)); + if (bus_id) + strscpy(fixup->bus_id, bus_id, sizeof(fixup->bus_id)); fixup->phy_uid = phy_uid; fixup->phy_uid_mask = phy_uid_mask; fixup->run = run; @@ -462,7 +460,7 @@ static int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, int (*run)(struct phy_device *)) { - return phy_register_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask, run); + return phy_register_fixup(NULL, phy_uid, phy_uid_mask, run); } EXPORT_SYMBOL(phy_register_fixup_for_uid); @@ -470,71 +468,20 @@ EXPORT_SYMBOL(phy_register_fixup_for_uid); int phy_register_fixup_for_id(const char *bus_id, int (*run)(struct phy_device *)) { - return phy_register_fixup(bus_id, PHY_ANY_UID, 0xffffffff, run); + return phy_register_fixup(bus_id, 0, 0, run); } EXPORT_SYMBOL(phy_register_fixup_for_id); -/** - * phy_unregister_fixup - remove a phy_fixup from the list - * @bus_id: A string matches fixup->bus_id (or PHY_ANY_ID) in phy_fixup_list - * @phy_uid: A phy id matches fixup->phy_id (or PHY_ANY_UID) in phy_fixup_list - * @phy_uid_mask: Applied to phy_uid and fixup->phy_uid before comparison - */ -int phy_unregister_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask) +static bool phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) { - struct list_head *pos, *n; - struct phy_fixup *fixup; - int ret; - - ret = -ENODEV; - - mutex_lock(&phy_fixup_lock); - list_for_each_safe(pos, n, &phy_fixup_list) { - fixup = list_entry(pos, struct phy_fixup, list); - - if ((!strcmp(fixup->bus_id, bus_id)) && - phy_id_compare(fixup->phy_uid, phy_uid, phy_uid_mask)) { - list_del(&fixup->list); - kfree(fixup); - ret = 0; - break; - } - } - mutex_unlock(&phy_fixup_lock); + if (!strcmp(fixup->bus_id, phydev_name(phydev))) + return true; - return ret; -} -EXPORT_SYMBOL(phy_unregister_fixup); - -/* Unregisters a fixup of any PHY with the UID in phy_uid */ -int phy_unregister_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask) -{ - return phy_unregister_fixup(PHY_ANY_ID, phy_uid, phy_uid_mask); -} -EXPORT_SYMBOL(phy_unregister_fixup_for_uid); + if (fixup->phy_uid_mask && + phy_id_compare(phydev->phy_id, fixup->phy_uid, fixup->phy_uid_mask)) + return true; -/* Unregisters a fixup of the PHY with id string bus_id */ -int phy_unregister_fixup_for_id(const char *bus_id) -{ - return phy_unregister_fixup(bus_id, PHY_ANY_UID, 0xffffffff); -} -EXPORT_SYMBOL(phy_unregister_fixup_for_id); - -/* Returns 1 if fixup matches phydev in bus_id and phy_uid. - * Fixups can be set to match any in one or more fields. - */ -static int phy_needs_fixup(struct phy_device *phydev, struct phy_fixup *fixup) -{ - if (strcmp(fixup->bus_id, phydev_name(phydev)) != 0) - if (strcmp(fixup->bus_id, PHY_ANY_ID) != 0) - return 0; - - if (!phy_id_compare(phydev->phy_id, fixup->phy_uid, - fixup->phy_uid_mask)) - if (fixup->phy_uid != PHY_ANY_UID) - return 0; - - return 1; + return false; } /* Runs any matching fixups for this phydev */ @@ -845,6 +792,13 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id, dev->state = PHY_DOWN; INIT_LIST_HEAD(&dev->leds); + INIT_LIST_HEAD(&dev->ports); + + /* The driver's probe function must change that to the real number + * of ports possible on the PHY. We assume by default we are dealing + * with a single-port PHY + */ + dev->max_n_ports = 1; mutex_init(&dev->lock); INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); @@ -1524,7 +1478,7 @@ static DEVICE_ATTR_RO(phy_standalone); * * Return: 0 on success, otherwise a negative error code. */ -int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) +static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) { struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; @@ -1534,7 +1488,6 @@ int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) return 0; } -EXPORT_SYMBOL(phy_sfp_connect_phy); /** * phy_sfp_disconnect_phy - Disconnect the SFP module's PHY from the upstream PHY @@ -1546,7 +1499,7 @@ EXPORT_SYMBOL(phy_sfp_connect_phy); * will be destroyed, re-inserting the same module will add a new phy with a * new index. */ -void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) +static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) { struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; @@ -1554,7 +1507,6 @@ void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) if (dev) phy_link_topo_del_phy(dev, phy); } -EXPORT_SYMBOL(phy_sfp_disconnect_phy); /** * phy_sfp_attach - attach the SFP bus to the PHY upstream network device @@ -1563,7 +1515,7 @@ EXPORT_SYMBOL(phy_sfp_disconnect_phy); * * This is used to fill in the sfp_upstream_ops .attach member. */ -void phy_sfp_attach(void *upstream, struct sfp_bus *bus) +static void phy_sfp_attach(void *upstream, struct sfp_bus *bus) { struct phy_device *phydev = upstream; @@ -1571,7 +1523,6 @@ void phy_sfp_attach(void *upstream, struct sfp_bus *bus) phydev->attached_dev->sfp_bus = bus; phydev->sfp_bus_attached = true; } -EXPORT_SYMBOL(phy_sfp_attach); /** * phy_sfp_detach - detach the SFP bus from the PHY upstream network device @@ -1580,7 +1531,7 @@ EXPORT_SYMBOL(phy_sfp_attach); * * This is used to fill in the sfp_upstream_ops .detach member. */ -void phy_sfp_detach(void *upstream, struct sfp_bus *bus) +static void phy_sfp_detach(void *upstream, struct sfp_bus *bus) { struct phy_device *phydev = upstream; @@ -1588,15 +1539,164 @@ void phy_sfp_detach(void *upstream, struct sfp_bus *bus) phydev->attached_dev->sfp_bus = NULL; phydev->sfp_bus_attached = false; } -EXPORT_SYMBOL(phy_sfp_detach); + +static int phy_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support); + struct phy_device *phydev = upstream; + const struct sfp_module_caps *caps; + struct phy_port *port; + + phy_interface_t iface; + + linkmode_zero(sfp_support); + + port = phy_get_sfp_port(phydev); + if (!port) + return -EINVAL; + + caps = sfp_get_module_caps(phydev->sfp_bus); + + linkmode_and(sfp_support, port->supported, caps->link_modes); + if (linkmode_empty(sfp_support)) { + dev_err(&phydev->mdio.dev, "incompatible SFP module inserted, no common linkmode\n"); + return -EINVAL; + } + + iface = sfp_select_interface(phydev->sfp_bus, sfp_support); + if (iface == PHY_INTERFACE_MODE_NA) { + dev_err(&phydev->mdio.dev, "PHY %s does not support the SFP module's requested MII interfaces\n", + phydev_name(phydev)); + return -EINVAL; + } + + if (phydev->n_ports == 1) + phydev->port = caps->port; + + if (port->ops && port->ops->configure_mii) + return port->ops->configure_mii(port, true, iface); + + return 0; +} + +static void phy_sfp_module_remove(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->configure_mii) + port->ops->configure_mii(port, false, PHY_INTERFACE_MODE_NA); + + if (phydev->n_ports == 1) + phydev->port = PORT_NONE; +} + +static void phy_sfp_link_up(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->link_up) + port->ops->link_up(port); +} + +static void phy_sfp_link_down(void *upstream) +{ + struct phy_device *phydev = upstream; + struct phy_port *port = phy_get_sfp_port(phydev); + + if (port && port->ops && port->ops->link_down) + port->ops->link_down(port); +} + +static const struct sfp_upstream_ops sfp_phydev_ops = { + .attach = phy_sfp_attach, + .detach = phy_sfp_detach, + .module_insert = phy_sfp_module_insert, + .module_remove = phy_sfp_module_remove, + .link_up = phy_sfp_link_up, + .link_down = phy_sfp_link_down, + .connect_phy = phy_sfp_connect_phy, + .disconnect_phy = phy_sfp_disconnect_phy, +}; + +static int phy_add_port(struct phy_device *phydev, struct phy_port *port) +{ + int ret = 0; + + if (phydev->n_ports == phydev->max_n_ports) + return -EBUSY; + + /* We set all ports as active by default, PHY drivers may deactivate + * them (when unused) + */ + port->active = true; + + if (port->is_mii) { + if (phydev->drv && phydev->drv->attach_mii_port) + ret = phydev->drv->attach_mii_port(phydev, port); + } else { + if (phydev->drv && phydev->drv->attach_mdi_port) + ret = phydev->drv->attach_mdi_port(phydev, port); + } + + if (ret) + return ret; + + /* The PHY driver might have added, removed or set medium/pairs info, + * so update the port supported accordingly. + */ + phy_port_update_supported(port); + + list_add(&port->head, &phydev->ports); + + phydev->n_ports++; + + return 0; +} + +static void phy_del_port(struct phy_device *phydev, struct phy_port *port) +{ + if (!phydev->n_ports) + return; + + list_del(&port->head); + + phydev->n_ports--; +} + +static int phy_setup_sfp_port(struct phy_device *phydev) +{ + struct phy_port *port = phy_port_alloc(); + int ret; + + if (!port) + return -ENOMEM; + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + /* The PHY is a media converter, the port connected to the SFP cage + * is a MII port. + */ + port->is_mii = true; + port->is_sfp = true; + + /* The port->supported and port->interfaces list will be populated + * when attaching the port to the phydev. + */ + ret = phy_add_port(phydev, port); + if (ret) + phy_port_destroy(port); + + return ret; +} /** * phy_sfp_probe - probe for a SFP cage attached to this PHY device * @phydev: Pointer to phy_device - * @ops: SFP's upstream operations */ -int phy_sfp_probe(struct phy_device *phydev, - const struct sfp_upstream_ops *ops) +static int phy_sfp_probe(struct phy_device *phydev) { struct sfp_bus *bus; int ret = 0; @@ -1608,12 +1708,15 @@ int phy_sfp_probe(struct phy_device *phydev, phydev->sfp_bus = bus; - ret = sfp_bus_add_upstream(bus, phydev, ops); + ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops); sfp_bus_put(bus); } + + if (!ret && phydev->sfp_bus) + ret = phy_setup_sfp_port(phydev); + return ret; } -EXPORT_SYMBOL(phy_sfp_probe); static bool phy_drv_supports_irq(const struct phy_driver *phydrv) { @@ -2369,7 +2472,7 @@ int genphy_update_link(struct phy_device *phydev) /* The link state is latched low so that momentary link * drops can be detected. Do not double-read the status * in polling mode to detect such short link drops except - * the link was already down. + * if the link was already down. */ if (!phy_polling_mode(phydev) || !phydev->link) { status = phy_read(phydev, MII_BMSR); @@ -3325,6 +3428,161 @@ exit: return 0; } +static void phy_cleanup_ports(struct phy_device *phydev) +{ + struct phy_port *tmp, *port; + + list_for_each_entry_safe(port, tmp, &phydev->ports, head) { + phy_del_port(phydev, port); + phy_port_destroy(port); + } +} + +static int phy_default_setup_single_port(struct phy_device *phydev) +{ + struct phy_port *port = phy_port_alloc(); + unsigned long mode; + + if (!port) + return -ENOMEM; + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + + /* Let the PHY driver know that this port was never described anywhere. + * This is the usual case, where we assume single-port PHY devices with + * no SFP. In that case, the port supports exactly the same thing as + * the PHY itself. + * + * However, this can also be because we have a combo-port PHY, with + * only one port described in DT, through SFP for example. + * + * In that case, the PHY driver will be in charge of saying what we can + * do on that non-represented port. + */ + port->not_described = true; + linkmode_copy(port->supported, phydev->supported); + port->mediums = phy_caps_mediums_from_linkmodes(port->supported); + + for_each_set_bit(mode, port->supported, __ETHTOOL_LINK_MODE_MASK_NBITS) + port->pairs = max_t(int, port->pairs, + ethtool_linkmode_n_pairs(mode)); + + phy_add_port(phydev, port); + + return 0; +} + +static int of_phy_ports(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct device_node *mdi; + struct phy_port *port; + int err; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return 0; + + if (!node) + return 0; + + mdi = of_get_child_by_name(node, "mdi"); + if (!mdi) + return 0; + + for_each_available_child_of_node_scoped(mdi, port_node) { + port = phy_of_parse_port(port_node); + if (IS_ERR(port)) { + err = PTR_ERR(port); + goto out_err; + } + + port->parent_type = PHY_PORT_PHY; + port->phy = phydev; + err = phy_add_port(phydev, port); + if (err) { + phy_port_destroy(port); + goto out_err; + } + } + of_node_put(mdi); + + return 0; + +out_err: + phy_cleanup_ports(phydev); + of_node_put(mdi); + return err; +} + +static int phy_setup_ports(struct phy_device *phydev) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(ports_supported); + struct phy_port *port; + int ret; + + ret = of_phy_ports(phydev); + if (ret) + return ret; + + ret = phy_sfp_probe(phydev); + if (ret) + goto out; + + if (phydev->n_ports < phydev->max_n_ports) { + ret = phy_default_setup_single_port(phydev); + if (ret) + goto out; + } + + linkmode_zero(ports_supported); + + /* Aggregate the supported modes, which are made-up of : + * - What the PHY itself supports + * - What the sum of all ports support + */ + list_for_each_entry(port, &phydev->ports, head) + if (port->active) + linkmode_or(ports_supported, ports_supported, + port->supported); + + if (!linkmode_empty(ports_supported)) + linkmode_and(phydev->supported, phydev->supported, + ports_supported); + + /* For now, the phy->port field is set as the first active port's type */ + list_for_each_entry(port, &phydev->ports, head) + if (port->active) { + phydev->port = phy_port_get_type(port); + break; + } + + return 0; + +out: + phy_cleanup_ports(phydev); + return ret; +} + +/** + * phy_get_sfp_port() - Returns the first valid SFP port of a PHY + * @phydev: pointer to the PHY device to get the SFP port from + * + * Returns: The first active SFP (serdes) port of a PHY device, NULL if none + * exist. + */ +struct phy_port *phy_get_sfp_port(struct phy_device *phydev) +{ + struct phy_port *port; + + list_for_each_entry(port, &phydev->ports, head) + if (port->active && port->is_sfp) + return port; + + return NULL; +} +EXPORT_SYMBOL_GPL(phy_get_sfp_port); + /** * fwnode_mdio_find_device - Given a fwnode, find the mdio_device * @fwnode: pointer to the mdio_device's fwnode @@ -3462,6 +3720,11 @@ static int phy_probe(struct device *dev) phydev->is_gigabit_capable = 1; of_set_phy_supported(phydev); + + err = phy_setup_ports(phydev); + if (err) + goto out; + phy_advertise_supported(phydev); /* Get PHY default EEE advertising modes and handle them as potentially @@ -3537,6 +3800,8 @@ static int phy_remove(struct device *dev) phydev->state = PHY_DOWN; + phy_cleanup_ports(phydev); + sfp_bus_del_upstream(phydev->sfp_bus); phydev->sfp_bus = NULL; diff --git a/drivers/net/phy/phy_port.c b/drivers/net/phy/phy_port.c new file mode 100644 index 000000000000..ec93c8ca051e --- /dev/null +++ b/drivers/net/phy/phy_port.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Framework to drive Ethernet ports + * + * Copyright (c) 2024 Maxime Chevallier <maxime.chevallier@bootlin.com> + */ + +#include <linux/linkmode.h> +#include <linux/of.h> +#include <linux/phy_port.h> + +#include "phy-caps.h" + +/** + * phy_port_alloc() - Allocate a new phy_port + * + * Returns: a newly allocated struct phy_port, or NULL. + */ +struct phy_port *phy_port_alloc(void) +{ + struct phy_port *port; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return NULL; + + linkmode_zero(port->supported); + INIT_LIST_HEAD(&port->head); + + return port; +} +EXPORT_SYMBOL_GPL(phy_port_alloc); + +/** + * phy_port_destroy() - Free a struct phy_port + * @port: The port to destroy + */ +void phy_port_destroy(struct phy_port *port) +{ + kfree(port); +} +EXPORT_SYMBOL_GPL(phy_port_destroy); + +/** + * phy_of_parse_port() - Create a phy_port from a firmware representation + * @dn: device_node representation of the port, following the + * ethernet-connector.yaml binding + * + * Returns: a newly allocated and initialized phy_port pointer, or an ERR_PTR. + */ +struct phy_port *phy_of_parse_port(struct device_node *dn) +{ + struct fwnode_handle *fwnode = of_fwnode_handle(dn); + enum ethtool_link_medium medium; + struct phy_port *port; + const char *med_str; + u32 pairs = 0, mediums = 0; + int ret; + + ret = fwnode_property_read_string(fwnode, "media", &med_str); + if (ret) + return ERR_PTR(ret); + + medium = ethtool_str_to_medium(med_str); + if (medium == ETHTOOL_LINK_MEDIUM_NONE) + return ERR_PTR(-EINVAL); + + if (medium == ETHTOOL_LINK_MEDIUM_BASET) { + ret = fwnode_property_read_u32(fwnode, "pairs", &pairs); + if (ret) + return ERR_PTR(ret); + + switch (pairs) { + case 1: /* BaseT1 */ + case 2: /* 100BaseTX */ + case 4: + break; + default: + pr_err("%u is not a valid number of pairs\n", pairs); + return ERR_PTR(-EINVAL); + } + } + + if (pairs && medium != ETHTOOL_LINK_MEDIUM_BASET) { + pr_err("pairs property is only compatible with BaseT medium\n"); + return ERR_PTR(-EINVAL); + } + + mediums |= BIT(medium); + + if (!mediums) + return ERR_PTR(-EINVAL); + + port = phy_port_alloc(); + if (!port) + return ERR_PTR(-ENOMEM); + + port->pairs = pairs; + port->mediums = mediums; + + return port; +} +EXPORT_SYMBOL_GPL(phy_of_parse_port); + +/** + * phy_port_update_supported() - Setup the port->supported field + * @port: the port to update + * + * Once the port's medium list and number of pairs has been configured based + * on firmware, straps and vendor-specific properties, this function may be + * called to update the port's supported linkmodes list. + * + * Any mode that was manually set in the port's supported list remains set. + */ +void phy_port_update_supported(struct phy_port *port) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 }; + unsigned long mode; + int i; + + for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) { + linkmode_zero(supported); + phy_caps_medium_get_supported(supported, i, port->pairs); + linkmode_or(port->supported, port->supported, supported); + } + + /* If there's no pairs specified, we grab the default number of + * pairs as the max of the default pairs for each linkmode + */ + if (!port->pairs) + for_each_set_bit(mode, port->supported, + __ETHTOOL_LINK_MODE_MASK_NBITS) + port->pairs = max_t(int, port->pairs, + ethtool_linkmode_n_pairs(mode)); + + /* Serdes ports supported through SFP may not have any medium set, + * as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive + * the supported list based on these interfaces + */ + if (port->is_mii && !port->mediums) { + unsigned long interface, link_caps = 0; + + /* Get each interface's caps */ + for_each_set_bit(interface, port->interfaces, + PHY_INTERFACE_MODE_MAX) + link_caps |= phy_caps_from_interface(interface); + + phy_caps_linkmodes(link_caps, port->supported); + } +} +EXPORT_SYMBOL_GPL(phy_port_update_supported); + +/** + * phy_port_filter_supported() - Make sure that port->supported match port->mediums + * @port: The port to filter + * + * After updating a port's mediums to a more restricted subset, this helper will + * make sure that port->supported only contains linkmodes that are compatible + * with port->mediums. + */ +static void phy_port_filter_supported(struct phy_port *port) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 }; + int i; + + for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) + phy_caps_medium_get_supported(supported, i, port->pairs); + + linkmode_and(port->supported, port->supported, supported); +} + +/** + * phy_port_restrict_mediums - Mask away some of the port's supported mediums + * @port: The port to act upon + * @mediums: A mask of mediums to support on the port + * + * This helper allows removing some mediums from a port's list of supported + * mediums, which occurs once we have enough information about the port to + * know its nature. + * + * Returns: 0 if the change was donne correctly, a negative value otherwise. + */ +int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums) +{ + /* We forbid ending-up with a port with empty mediums */ + if (!(port->mediums & mediums)) + return -EINVAL; + + port->mediums &= mediums; + + phy_port_filter_supported(port); + + return 0; +} +EXPORT_SYMBOL_GPL(phy_port_restrict_mediums); + +/** + * phy_port_get_type() - get the PORT_* attribute for that port. + * @port: The port we want the information from + * + * Returns: A PORT_XXX value. + */ +int phy_port_get_type(struct phy_port *port) +{ + if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET)) + return PORT_TP; + + if (phy_port_is_fiber(port)) + return PORT_FIBRE; + + return PORT_OTHER; +} +EXPORT_SYMBOL_GPL(phy_port_get_type); diff --git a/drivers/net/phy/phylib-internal.h b/drivers/net/phy/phylib-internal.h index ebda74eb60a5..dc9592c6bb8e 100644 --- a/drivers/net/phy/phylib-internal.h +++ b/drivers/net/phy/phylib-internal.h @@ -7,7 +7,6 @@ #define __PHYLIB_INTERNAL_H struct phy_device; -struct mii_bus; /* * phy_supported_speeds - return all speeds currently supported by a PHY device @@ -21,11 +20,6 @@ void of_set_phy_timing_role(struct phy_device *phydev); int phy_speed_down_core(struct phy_device *phydev); void phy_check_downshift(struct phy_device *phydev); -int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, - int devad, u32 regnum); -int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, - int devad, u32 regnum, u16 val); - int genphy_c45_read_eee_adv(struct phy_device *phydev, unsigned long *adv); #endif /* __PHYLIB_INTERNAL_H */ diff --git a/drivers/net/phy/phylib.h b/drivers/net/phy/phylib.h index c15484a805b3..0fba245f9745 100644 --- a/drivers/net/phy/phylib.h +++ b/drivers/net/phy/phylib.h @@ -8,6 +8,7 @@ struct device_node; struct phy_device; +struct mii_bus; struct device_node *phy_package_get_node(struct phy_device *phydev); void *phy_package_get_priv(struct phy_device *phydev); @@ -30,5 +31,9 @@ int devm_phy_package_join(struct device *dev, struct phy_device *phydev, int base_addr, size_t priv_size); int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev, size_t priv_size); +int mmd_phy_read(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum); +int mmd_phy_write(struct mii_bus *bus, int phy_addr, bool is_c45, + int devad, u32 regnum, u16 val); #endif /* __PHYLIB_H */ diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 43d8380aaefb..e1f01d7fc4da 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -28,6 +28,7 @@ enum { PHYLINK_DISABLE_STOPPED, PHYLINK_DISABLE_LINK, PHYLINK_DISABLE_MAC_WOL, + PHYLINK_DISABLE_REPLAY, PCS_STATE_DOWN = 0, PCS_STATE_STARTING, @@ -77,6 +78,7 @@ struct phylink { bool link_failed; bool suspend_link_up; + bool force_major_config; bool major_config_failed; bool mac_supports_eee_ops; bool mac_supports_eee; @@ -311,6 +313,7 @@ static struct { { MAC_400000FD, SPEED_400000, DUPLEX_FULL, BIT(LINK_CAPA_400000FD) }, { MAC_200000FD, SPEED_200000, DUPLEX_FULL, BIT(LINK_CAPA_200000FD) }, { MAC_100000FD, SPEED_100000, DUPLEX_FULL, BIT(LINK_CAPA_100000FD) }, + { MAC_80000FD, SPEED_80000, DUPLEX_FULL, BIT(LINK_CAPA_80000FD) }, { MAC_56000FD, SPEED_56000, DUPLEX_FULL, BIT(LINK_CAPA_56000FD) }, { MAC_50000FD, SPEED_50000, DUPLEX_FULL, BIT(LINK_CAPA_50000FD) }, { MAC_40000FD, SPEED_40000, DUPLEX_FULL, BIT(LINK_CAPA_40000FD) }, @@ -1290,7 +1293,8 @@ static void phylink_major_config(struct phylink *pl, bool restart, if (pl->pcs) pl->pcs->phylink = NULL; - pcs->phylink = pl; + if (pcs) + pcs->phylink = pl; pl->pcs = pcs; } @@ -1683,18 +1687,18 @@ static void phylink_resolve(struct work_struct *w) if (pl->act_link_an_mode != MLO_AN_FIXED) phylink_apply_manual_flow(pl, &link_state); - if (mac_config) { - if (link_state.interface != pl->link_config.interface) { - /* The interface has changed, force the link down and - * then reconfigure. - */ - if (cur_link_state) { - phylink_link_down(pl); - cur_link_state = false; - } - phylink_major_config(pl, false, &link_state); - pl->link_config.interface = link_state.interface; + if ((mac_config && link_state.interface != pl->link_config.interface) || + pl->force_major_config) { + /* The interface has changed or a forced major configuration + * was requested, so force the link down and then reconfigure. + */ + if (cur_link_state) { + phylink_link_down(pl); + cur_link_state = false; } + phylink_major_config(pl, false, &link_state); + pl->link_config.interface = link_state.interface; + pl->force_major_config = false; } /* If configuration of the interface failed, force the link down @@ -4358,6 +4362,57 @@ void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs, } EXPORT_SYMBOL_GPL(phylink_mii_c45_pcs_get_state); +/** + * phylink_replay_link_begin() - begin replay of link callbacks for driver + * which loses state + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Helper for MAC drivers which may perform a destructive reset at runtime. + * Both the own driver's mac_link_down() method is called, as well as the + * pcs_link_down() method of the split PCS (if any). + * + * This is similar to phylink_stop(), except it does not alter the state of + * the phylib PHY (it is assumed that it is not affected by the MAC destructive + * reset). + */ +void phylink_replay_link_begin(struct phylink *pl) +{ + ASSERT_RTNL(); + + phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_REPLAY); +} +EXPORT_SYMBOL_GPL(phylink_replay_link_begin); + +/** + * phylink_replay_link_end() - end replay of link callbacks for driver + * which lost state + * @pl: a pointer to a &struct phylink returned from phylink_create() + * + * Helper for MAC drivers which may perform a destructive reset at runtime. + * Both the own driver's mac_config() and mac_link_up() methods, as well as the + * pcs_config() and pcs_link_up() method of the split PCS (if any), are called. + * + * This is similar to phylink_start(), except it does not alter the state of + * the phylib PHY. + * + * One must call this method only within the same rtnl_lock() critical section + * as a previous phylink_replay_link_start(). + */ +void phylink_replay_link_end(struct phylink *pl) +{ + ASSERT_RTNL(); + + if (WARN(!test_bit(PHYLINK_DISABLE_REPLAY, + &pl->phylink_disable_state), + "phylink_replay_link_end() called without a prior phylink_replay_link_begin()\n")) + return; + + pl->force_major_config = true; + phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_REPLAY); + flush_work(&pl->resolve); +} +EXPORT_SYMBOL_GPL(phylink_replay_link_end); + static int __init phylink_init(void) { for (int i = 0; i < ARRAY_SIZE(phylink_sfp_interface_preference); ++i) diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index 338acd11a9b6..2995b08bac96 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -20,7 +20,7 @@ #include <linux/of.h> #include <linux/phylink.h> #include <linux/reset.h> -#include <linux/sfp.h> +#include <linux/phy_port.h> #include <dt-bindings/net/qca-ar803x.h> #include "qcom.h" @@ -769,57 +769,44 @@ static int at8031_register_regulators(struct phy_device *phydev) return 0; } -static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +static int at803x_configure_mii(struct phy_port *port, bool enable, + phy_interface_t interface) { - __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support); - __ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support); - struct phy_device *phydev = upstream; - const struct sfp_module_caps *caps; - phy_interface_t iface; - - linkmode_zero(phy_support); - phylink_set(phy_support, 1000baseX_Full); - phylink_set(phy_support, 1000baseT_Full); - phylink_set(phy_support, Autoneg); - phylink_set(phy_support, Pause); - phylink_set(phy_support, Asym_Pause); - - caps = sfp_get_module_caps(phydev->sfp_bus); - /* Some modules support 10G modes as well as others we support. - * Mask out non-supported modes so the correct interface is picked. - */ - linkmode_and(sfp_support, phy_support, caps->link_modes); + struct phy_device *phydev = port_phydev(port); - if (linkmode_empty(sfp_support)) { - dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n"); - return -EINVAL; - } + if (interface == PHY_INTERFACE_MODE_SGMII) + dev_warn(&phydev->mdio.dev, + "module may not function if 1000Base-X not supported\n"); + + return 0; +} - iface = sfp_select_interface(phydev->sfp_bus, sfp_support); +static const struct phy_port_ops at803x_port_ops = { + .configure_mii = at803x_configure_mii, +}; - /* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes - * interface for use with SFP modules. - * However, some copper modules detected as having a preferred SGMII - * interface do default to and function in 1000Base-X mode, so just - * print a warning and allow such modules, as they may have some chance - * of working. +static int at8031_attach_mii_port(struct phy_device *phydev, + struct phy_port *port) +{ + linkmode_zero(port->supported); + phylink_set(port->supported, 1000baseX_Full); + phylink_set(port->supported, 1000baseT_Full); + phylink_set(port->supported, Autoneg); + phylink_set(port->supported, Pause); + phylink_set(port->supported, Asym_Pause); + + /* This device doesn't really support SGMII. However, do our best + * to be compatible with copper modules (that usually require SGMII), + * in a degraded mode as we only allow 1000BaseT Full */ - if (iface == PHY_INTERFACE_MODE_SGMII) - dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n"); - else if (iface != PHY_INTERFACE_MODE_1000BASEX) - return -EINVAL; + __set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + + port->ops = &at803x_port_ops; return 0; } -static const struct sfp_upstream_ops at8031_sfp_ops = { - .attach = phy_sfp_attach, - .detach = phy_sfp_detach, - .module_insert = at8031_sfp_insert, - .connect_phy = phy_sfp_connect_phy, - .disconnect_phy = phy_sfp_disconnect_phy, -}; - static int at8031_parse_dt(struct phy_device *phydev) { struct device_node *node = phydev->mdio.dev.of_node; @@ -840,8 +827,7 @@ static int at8031_parse_dt(struct phy_device *phydev) return ret; } - /* Only AR8031/8033 support 1000Base-X for SFP modules */ - return phy_sfp_probe(phydev, &at8031_sfp_ops); + return 0; } static int at8031_probe(struct phy_device *phydev) @@ -1172,6 +1158,7 @@ static struct phy_driver at803x_driver[] = { .set_tunable = at803x_set_tunable, .cable_test_start = at8031_cable_test_start, .cable_test_get_status = at8031_cable_test_get_status, + .attach_mii_port = at8031_attach_mii_port, }, { /* Qualcomm Atheros AR8032 */ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID), diff --git a/drivers/net/phy/qcom/qca807x.c b/drivers/net/phy/qcom/qca807x.c index 1be8295a95cb..d8f1ce5a7128 100644 --- a/drivers/net/phy/qcom/qca807x.c +++ b/drivers/net/phy/qcom/qca807x.c @@ -13,7 +13,7 @@ #include <linux/phy.h> #include <linux/bitfield.h> #include <linux/gpio/driver.h> -#include <linux/sfp.h> +#include <linux/phy_port.h> #include "../phylib.h" #include "qcom.h" @@ -643,67 +643,54 @@ exit: return ret; } -static int qca807x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +static int qca807x_configure_serdes(struct phy_port *port, bool enable, + phy_interface_t interface) { - struct phy_device *phydev = upstream; - const struct sfp_module_caps *caps; - phy_interface_t iface; + struct phy_device *phydev = port_phydev(port); int ret; - caps = sfp_get_module_caps(phydev->sfp_bus); - iface = sfp_select_interface(phydev->sfp_bus, caps->link_modes); + if (!phydev) + return -ENODEV; - dev_info(&phydev->mdio.dev, "%s SFP module inserted\n", phy_modes(iface)); - - switch (iface) { - case PHY_INTERFACE_MODE_1000BASEX: - case PHY_INTERFACE_MODE_100BASEX: + if (enable) { /* Set PHY mode to PSGMII combo (1/4 copper + combo ports) mode */ ret = phy_modify(phydev, QCA807X_CHIP_CONFIGURATION, QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK, QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER); + if (ret) + return ret; /* Enable fiber mode autodection (1000Base-X or 100Base-FX) */ ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION, QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN); - /* Select fiber page */ - ret = phy_clear_bits(phydev, - QCA807X_CHIP_CONFIGURATION, - QCA807X_BT_BX_REG_SEL); - - phydev->port = PORT_FIBRE; - break; - default: - dev_err(&phydev->mdio.dev, "Incompatible SFP module inserted\n"); - return -EINVAL; + if (ret) + return ret; } - return ret; + phydev->port = enable ? PORT_FIBRE : PORT_TP; + + return phy_modify(phydev, QCA807X_CHIP_CONFIGURATION, + QCA807X_BT_BX_REG_SEL, + enable ? 0 : QCA807X_BT_BX_REG_SEL); } -static void qca807x_sfp_remove(void *upstream) +static const struct phy_port_ops qca807x_serdes_port_ops = { + .configure_mii = qca807x_configure_serdes, +}; + +static int qca807x_attach_mii_port(struct phy_device *phydev, + struct phy_port *port) { - struct phy_device *phydev = upstream; + __set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces); + __set_bit(PHY_INTERFACE_MODE_100BASEX, port->interfaces); - /* Select copper page */ - phy_set_bits(phydev, - QCA807X_CHIP_CONFIGURATION, - QCA807X_BT_BX_REG_SEL); + port->ops = &qca807x_serdes_port_ops; - phydev->port = PORT_TP; + return 0; } -static const struct sfp_upstream_ops qca807x_sfp_ops = { - .attach = phy_sfp_attach, - .detach = phy_sfp_detach, - .module_insert = qca807x_sfp_insert, - .module_remove = qca807x_sfp_remove, - .connect_phy = phy_sfp_connect_phy, - .disconnect_phy = phy_sfp_disconnect_phy, -}; - static int qca807x_probe(struct phy_device *phydev) { struct device_node *node = phydev->mdio.dev.of_node; @@ -744,9 +731,8 @@ static int qca807x_probe(struct phy_device *phydev) /* Attach SFP bus on combo port*/ if (phy_read(phydev, QCA807X_CHIP_CONFIGURATION)) { - ret = phy_sfp_probe(phydev, &qca807x_sfp_ops); - if (ret) - return ret; + phydev->max_n_ports = 2; + linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported); linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->advertising); } @@ -824,6 +810,7 @@ static struct phy_driver qca807x_drivers[] = { .get_phy_stats = qca807x_get_phy_stats, .set_wol = at8031_set_wol, .get_wol = at803x_get_wol, + .attach_mii_port = qca807x_attach_mii_port, }, { PHY_ID_MATCH_EXACT(PHY_ID_QCA8075), @@ -851,6 +838,7 @@ static struct phy_driver qca807x_drivers[] = { .get_phy_stats = qca807x_get_phy_stats, .set_wol = at8031_set_wol, .get_wol = at803x_get_wol, + .attach_mii_port = qca807x_attach_mii_port, }, }; module_phy_driver(qca807x_drivers); diff --git a/drivers/net/phy/qt2025.rs b/drivers/net/phy/qt2025.rs index aaaead6512a0..470d89a0ac00 100644 --- a/drivers/net/phy/qt2025.rs +++ b/drivers/net/phy/qt2025.rs @@ -9,7 +9,6 @@ //! //! The QT2025 PHY integrates an Intel 8051 micro-controller. -use kernel::c_str; use kernel::error::code; use kernel::firmware::Firmware; use kernel::io::poll::read_poll_timeout; @@ -38,7 +37,7 @@ struct PhyQT2025; #[vtable] impl Driver for PhyQT2025 { - const NAME: &'static CStr = c_str!("QT2025 10Gpbs SFP+"); + const NAME: &'static CStr = c"QT2025 10Gpbs SFP+"; const PHY_DEVICE_ID: phy::DeviceId = phy::DeviceId::new_with_exact_mask(0x0043a400); fn probe(dev: &mut phy::Device) -> Result<()> { @@ -71,7 +70,7 @@ impl Driver for PhyQT2025 { // The micro-controller will start running from the boot ROM. dev.write(C45::new(Mmd::PCS, 0xe854), 0x00c0)?; - let fw = Firmware::request(c_str!("qt2025-2.0.3.3.fw"), dev.as_ref())?; + let fw = Firmware::request(c"qt2025-2.0.3.3.fw", dev.as_ref())?; if fw.data().len() > SZ_16K + SZ_8K { return Err(code::EFBIG); } diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 6ff0385201a5..75565fbdbf6d 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -17,7 +17,9 @@ #include <linux/delay.h> #include <linux/clk.h> #include <linux/string_choices.h> +#include <net/phy/realtek_phy.h> +#include "../phylib.h" #include "realtek.h" #define RTL8201F_IER 0x13 @@ -66,7 +68,6 @@ #define RTL8211E_DELAY_MASK GENMASK(13, 11) /* RTL8211F PHY configuration */ -#define RTL8211F_PHYCR_PAGE 0xa43 #define RTL8211F_PHYCR1 0x18 #define RTL8211F_ALDPS_PLL_OFF BIT(1) #define RTL8211F_ALDPS_ENABLE BIT(2) @@ -76,7 +77,6 @@ #define RTL8211F_CLKOUT_EN BIT(0) #define RTL8211F_PHYCR2_PHY_EEE_ENABLE BIT(5) -#define RTL8211F_INSR_PAGE 0xa43 #define RTL8211F_INSR 0x1d /* RTL8211F LED configuration */ @@ -131,9 +131,18 @@ #define RTL822X_VND1_SERDES_CTRL3_MODE_SGMII 0x02 #define RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX 0x16 -/* RTL822X_VND2_XXXXX registers are only accessible when phydev->is_c45 - * is set, they cannot be accessed by C45-over-C22. - */ +#define RTL822X_VND1_SERDES_CMD 0x7587 +#define RTL822X_VND1_SERDES_CMD_WRITE BIT(1) +#define RTL822X_VND1_SERDES_CMD_BUSY BIT(0) +#define RTL822X_VND1_SERDES_ADDR 0x7588 +#define RTL822X_VND1_SERDES_ADDR_AUTONEG 0x2 +#define RTL822X_VND1_SERDES_INBAND_DISABLE 0x71d0 +#define RTL822X_VND1_SERDES_INBAND_ENABLE 0x70d0 +#define RTL822X_VND1_SERDES_DATA 0x7589 + +#define RTL822X_VND2_TO_PAGE(reg) ((reg) >> 4) +#define RTL822X_VND2_TO_PAGE_REG(reg) (16 + (((reg) & GENMASK(3, 0)) >> 1)) +#define RTL822X_VND2_TO_C22_REG(reg) (((reg) - 0xa400) / 2) #define RTL822X_VND2_C22_REG(reg) (0xa400 + 2 * (reg)) #define RTL8221B_VND2_INER 0xa4d2 @@ -168,12 +177,12 @@ #define RTL9000A_GINMR 0x14 #define RTL9000A_GINMR_LINK_STATUS BIT(4) -#define RTL_VND2_PHYSR 0xa434 -#define RTL_VND2_PHYSR_DUPLEX BIT(3) -#define RTL_VND2_PHYSR_SPEEDL GENMASK(5, 4) -#define RTL_VND2_PHYSR_SPEEDH GENMASK(10, 9) -#define RTL_VND2_PHYSR_MASTER BIT(11) -#define RTL_VND2_PHYSR_SPEED_MASK (RTL_VND2_PHYSR_SPEEDL | RTL_VND2_PHYSR_SPEEDH) +#define RTL_PHYSR MII_RESV2 +#define RTL_PHYSR_DUPLEX BIT(3) +#define RTL_PHYSR_SPEEDL GENMASK(5, 4) +#define RTL_PHYSR_SPEEDH GENMASK(10, 9) +#define RTL_PHYSR_MASTER BIT(11) +#define RTL_PHYSR_SPEED_MASK (RTL_PHYSR_SPEEDL | RTL_PHYSR_SPEEDH) #define RTL_MDIO_PCS_EEE_ABLE 0xa5c4 #define RTL_MDIO_AN_EEE_ADV 0xa5d0 @@ -322,7 +331,7 @@ static int rtl8211f_ack_interrupt(struct phy_device *phydev) { int err; - err = phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR); + err = phy_read(phydev, RTL8211F_INSR); return (err < 0) ? err : 0; } @@ -468,7 +477,7 @@ static irqreturn_t rtl8211f_handle_interrupt(struct phy_device *phydev) { int irq_status; - irq_status = phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR); + irq_status = phy_read(phydev, RTL8211F_INSR); if (irq_status < 0) { phy_error(phydev); return IRQ_NONE; @@ -659,8 +668,8 @@ static int rtl8211f_config_clk_out(struct phy_device *phydev) RTL8211FVD_CLKOUT_REG, RTL8211FVD_CLKOUT_EN, 0); else - ret = phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, - RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, 0); + ret = phy_modify(phydev, RTL8211F_PHYCR2, RTL8211F_CLKOUT_EN, + 0); if (ret) return ret; @@ -685,15 +694,14 @@ static int rtl8211f_config_aldps(struct phy_device *phydev) if (!priv->enable_aldps) return 0; - return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR1, - mask, mask); + return phy_modify(phydev, RTL8211F_PHYCR1, mask, mask); } static int rtl8211f_config_phy_eee(struct phy_device *phydev) { /* Disable PHY-mode EEE so LPI is passed to the MAC */ - return phy_modify_paged(phydev, RTL8211F_PHYCR_PAGE, RTL8211F_PHYCR2, - RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); + return phy_modify(phydev, RTL8211F_PHYCR2, + RTL8211F_PHYCR2_PHY_EEE_ENABLE, 0); } static int rtl8211f_config_init(struct phy_device *phydev) @@ -759,7 +767,7 @@ static int rtl8211f_suspend(struct phy_device *phydev) goto err; /* Read the INSR to clear any pending interrupt */ - phy_read_paged(phydev, RTL8211F_INSR_PAGE, RTL8211F_INSR); + phy_read(phydev, RTL8211F_INSR); /* Reset the WoL to ensure that an event is picked up. * Unless we do this, even if we receive another packet, @@ -1092,12 +1100,12 @@ static void rtlgen_decode_physr(struct phy_device *phydev, int val) * 0: Half Duplex * 1: Full Duplex */ - if (val & RTL_VND2_PHYSR_DUPLEX) + if (val & RTL_PHYSR_DUPLEX) phydev->duplex = DUPLEX_FULL; else phydev->duplex = DUPLEX_HALF; - switch (val & RTL_VND2_PHYSR_SPEED_MASK) { + switch (val & RTL_PHYSR_SPEED_MASK) { case 0x0000: phydev->speed = SPEED_10; break; @@ -1125,7 +1133,7 @@ static void rtlgen_decode_physr(struct phy_device *phydev, int val) * 1: Master Mode */ if (phydev->speed >= 1000) { - if (val & RTL_VND2_PHYSR_MASTER) + if (val & RTL_PHYSR_MASTER) phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; else phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; @@ -1145,7 +1153,7 @@ static int rtlgen_read_status(struct phy_device *phydev) if (!phydev->link) return 0; - val = phy_read_paged(phydev, 0xa43, 0x12); + val = phy_read(phydev, RTL_PHYSR); if (val < 0) return val; @@ -1238,6 +1246,89 @@ static int rtl822x_probe(struct phy_device *phydev) return 0; } +/* RTL822x cannot access MDIO_MMD_VEND2 via MII_MMD_CTRL/MII_MMD_DATA. + * A mapping to use paged access needs to be used instead. + * All other MMD devices can be accessed as usual. + */ +static int rtl822xb_read_mmd(struct phy_device *phydev, int devnum, u16 reg) +{ + int oldpage, ret, read_ret; + u16 page; + + /* Use default method for all MMDs except MDIO_MMD_VEND2 or in case + * Clause-45 access is available + */ + if (devnum != MDIO_MMD_VEND2 || phydev->is_c45) + return mmd_phy_read(phydev->mdio.bus, phydev->mdio.addr, + phydev->is_c45, devnum, reg); + + /* Simplify access to C22-registers addressed inside MDIO_MMD_VEND2 */ + if (reg >= RTL822X_VND2_C22_REG(0) && + reg <= RTL822X_VND2_C22_REG(30)) + return __phy_read(phydev, RTL822X_VND2_TO_C22_REG(reg)); + + /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */ + page = RTL822X_VND2_TO_PAGE(reg); + oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT); + if (oldpage < 0) + return oldpage; + + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, page); + if (ret < 0) + return ret; + } + + read_ret = __phy_read(phydev, RTL822X_VND2_TO_PAGE_REG(reg)); + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, oldpage); + if (ret < 0) + return ret; + } + + return read_ret; +} + +static int rtl822xb_write_mmd(struct phy_device *phydev, int devnum, u16 reg, + u16 val) +{ + int oldpage, ret, write_ret; + u16 page; + + /* Use default method for all MMDs except MDIO_MMD_VEND2 or in case + * Clause-45 access is available + */ + if (devnum != MDIO_MMD_VEND2 || phydev->is_c45) + return mmd_phy_write(phydev->mdio.bus, phydev->mdio.addr, + phydev->is_c45, devnum, reg, val); + + /* Simplify access to C22-registers addressed inside MDIO_MMD_VEND2 */ + if (reg >= RTL822X_VND2_C22_REG(0) && + reg <= RTL822X_VND2_C22_REG(30)) + return __phy_write(phydev, RTL822X_VND2_TO_C22_REG(reg), val); + + /* Use paged access for MDIO_MMD_VEND2 over Clause-22 */ + page = RTL822X_VND2_TO_PAGE(reg); + oldpage = __phy_read(phydev, RTL821x_PAGE_SELECT); + if (oldpage < 0) + return oldpage; + + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, page); + if (ret < 0) + return ret; + } + + write_ret = __phy_write(phydev, RTL822X_VND2_TO_PAGE_REG(reg), val); + if (oldpage != page) { + ret = __phy_write(phydev, RTL821x_PAGE_SELECT, oldpage); + if (ret < 0) + return ret; + } + + return write_ret; +} + static int rtl822x_set_serdes_option_mode(struct phy_device *phydev, bool gen1) { bool has_2500, has_sgmii; @@ -1308,6 +1399,51 @@ static int rtl822xb_config_init(struct phy_device *phydev) return rtl822x_set_serdes_option_mode(phydev, false); } +static int rtl822x_serdes_write(struct phy_device *phydev, u16 reg, u16 val) +{ + int ret, poll; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_ADDR, reg); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_DATA, val); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CMD, + RTL822X_VND1_SERDES_CMD_WRITE | + RTL822X_VND1_SERDES_CMD_BUSY); + if (ret < 0) + return ret; + + return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, + RTL822X_VND1_SERDES_CMD, poll, + !(poll & RTL822X_VND1_SERDES_CMD_BUSY), + 500, 100000, false); +} + +static int rtl822x_config_inband(struct phy_device *phydev, unsigned int modes) +{ + return rtl822x_serdes_write(phydev, RTL822X_VND1_SERDES_ADDR_AUTONEG, + (modes != LINK_INBAND_DISABLE) ? + RTL822X_VND1_SERDES_INBAND_ENABLE : + RTL822X_VND1_SERDES_INBAND_DISABLE); +} + +static unsigned int rtl822x_inband_caps(struct phy_device *phydev, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_2500BASEX: + return LINK_INBAND_DISABLE; + case PHY_INTERFACE_MODE_SGMII: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + default: + return 0; + } +} + static int rtl822xb_get_rate_matching(struct phy_device *phydev, phy_interface_t iface) { @@ -1484,7 +1620,8 @@ static int rtl822x_c45_read_status(struct phy_device *phydev) } /* Read actual speed from vendor register. */ - val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL_VND2_PHYSR); + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_C22_REG(RTL_PHYSR)); if (val < 0) return val; @@ -1741,28 +1878,18 @@ static int rtl8221b_match_phy_device(struct phy_device *phydev, return phydev->phy_id == RTL_8221B && rtlgen_supports_mmd(phydev); } -static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev, - const struct phy_driver *phydrv) -{ - return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false); -} - -static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev, - const struct phy_driver *phydrv) -{ - return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true); -} - -static int rtl8221b_vm_cg_c22_match_phy_device(struct phy_device *phydev, - const struct phy_driver *phydrv) +static int rtl8221b_vb_cg_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) { - return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, false); + return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true) || + rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false); } -static int rtl8221b_vm_cg_c45_match_phy_device(struct phy_device *phydev, - const struct phy_driver *phydrv) +static int rtl8221b_vm_cg_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) { - return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, true); + return rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, true) || + rtlgen_is_c45_match(phydev, RTL_8221B_VM_CG, false); } static int rtl_internal_nbaset_match_phy_device(struct phy_device *phydev, @@ -1973,10 +2100,49 @@ static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev) return IRQ_HANDLED; } +static int rtlgen_sfp_get_features(struct phy_device *phydev) +{ + linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + phydev->supported); + + /* set default mode */ + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + + phydev->port = PORT_FIBRE; + + return 0; +} + +static int rtlgen_sfp_read_status(struct phy_device *phydev) +{ + int val, err; + + err = genphy_update_link(phydev); + if (err) + return err; + + if (!phydev->link) + return 0; + + val = phy_read(phydev, RTL_PHYSR); + if (val < 0) + return val; + + rtlgen_decode_physr(phydev, val); + + return 0; +} + +static int rtlgen_sfp_config_aneg(struct phy_device *phydev) +{ + return 0; +} + static struct phy_driver realtek_drvs[] = { { PHY_ID_MATCH_EXACT(0x00008201), - .name = "RTL8201CP Ethernet", + .name = "RTL8201CP Ethernet", .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, }, { @@ -2097,126 +2263,140 @@ static struct phy_driver realtek_drvs[] = { .resume = rtlgen_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, }, { .match_phy_device = rtl8221b_match_phy_device, .name = "RTL8226B_RTL8221B 2.5Gbps PHY", .get_features = rtl822x_get_features, .config_aneg = rtl822x_config_aneg, - .config_init = rtl822xb_config_init, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, .get_rate_matching = rtl822xb_get_rate_matching, .read_status = rtl822xb_read_status, .suspend = genphy_suspend, .resume = rtlgen_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, }, { PHY_ID_MATCH_EXACT(0x001cc838), - .name = "RTL8226-CG 2.5Gbps PHY", - .soft_reset = rtl822x_c45_soft_reset, - .get_features = rtl822x_c45_get_features, - .config_aneg = rtl822x_c45_config_aneg, - .config_init = rtl822x_config_init, - .read_status = rtl822xb_c45_read_status, - .suspend = genphy_c45_pma_suspend, - .resume = rtlgen_c45_resume, + .name = "RTL8226-CG 2.5Gbps PHY", + .soft_reset = rtl822x_c45_soft_reset, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .config_init = rtl822x_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, + .read_status = rtl822xb_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, }, { PHY_ID_MATCH_EXACT(0x001cc848), - .name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY", - .get_features = rtl822x_get_features, - .config_aneg = rtl822x_config_aneg, - .config_init = rtl822xb_config_init, - .get_rate_matching = rtl822xb_get_rate_matching, - .read_status = rtl822xb_read_status, - .suspend = genphy_suspend, - .resume = rtlgen_resume, - .read_page = rtl821x_read_page, - .write_page = rtl821x_write_page, - }, { - .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device, - .name = "RTL8221B-VB-CG 2.5Gbps PHY (C22)", - .probe = rtl822x_probe, - .get_features = rtl822x_get_features, - .config_aneg = rtl822x_config_aneg, - .config_init = rtl822xb_config_init, + .name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY", + .get_features = rtl822x_get_features, + .config_aneg = rtl822x_config_aneg, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, .get_rate_matching = rtl822xb_get_rate_matching, - .read_status = rtl822xb_read_status, - .suspend = genphy_suspend, - .resume = rtlgen_resume, - .read_page = rtl821x_read_page, - .write_page = rtl821x_write_page, + .read_status = rtl822xb_read_status, + .suspend = genphy_suspend, + .resume = rtlgen_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, }, { - .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device, - .name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)", + .match_phy_device = rtl8221b_vb_cg_match_phy_device, + .name = "RTL8221B-VB-CG 2.5Gbps PHY", .config_intr = rtl8221b_config_intr, .handle_interrupt = rtl8221b_handle_interrupt, .probe = rtl822x_probe, - .config_init = rtl822xb_config_init, - .get_rate_matching = rtl822xb_get_rate_matching, - .get_features = rtl822x_c45_get_features, - .config_aneg = rtl822x_c45_config_aneg, - .read_status = rtl822xb_c45_read_status, - .suspend = genphy_c45_pma_suspend, - .resume = rtlgen_c45_resume, - }, { - .match_phy_device = rtl8221b_vm_cg_c22_match_phy_device, - .name = "RTL8221B-VM-CG 2.5Gbps PHY (C22)", - .probe = rtl822x_probe, - .get_features = rtl822x_get_features, - .config_aneg = rtl822x_config_aneg, - .config_init = rtl822xb_config_init, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, .get_rate_matching = rtl822xb_get_rate_matching, - .read_status = rtl822xb_read_status, - .suspend = genphy_suspend, - .resume = rtlgen_resume, - .read_page = rtl821x_read_page, - .write_page = rtl821x_write_page, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .read_status = rtl822xb_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, }, { - .match_phy_device = rtl8221b_vm_cg_c45_match_phy_device, - .name = "RTL8221B-VM-CG 2.5Gbps PHY (C45)", + .match_phy_device = rtl8221b_vm_cg_match_phy_device, + .name = "RTL8221B-VM-CG 2.5Gbps PHY", .config_intr = rtl8221b_config_intr, .handle_interrupt = rtl8221b_handle_interrupt, .probe = rtl822x_probe, - .config_init = rtl822xb_config_init, + .config_init = rtl822xb_config_init, + .inband_caps = rtl822x_inband_caps, + .config_inband = rtl822x_config_inband, .get_rate_matching = rtl822xb_get_rate_matching, - .get_features = rtl822x_c45_get_features, - .config_aneg = rtl822x_c45_config_aneg, - .read_status = rtl822xb_c45_read_status, - .suspend = genphy_c45_pma_suspend, - .resume = rtlgen_c45_resume, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .read_status = rtl822xb_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822xb_read_mmd, + .write_mmd = rtl822xb_write_mmd, }, { .match_phy_device = rtl8251b_c45_match_phy_device, - .name = "RTL8251B 5Gbps PHY", + .name = "RTL8251B 5Gbps PHY", .probe = rtl822x_probe, - .get_features = rtl822x_get_features, - .config_aneg = rtl822x_config_aneg, - .read_status = rtl822x_read_status, - .suspend = genphy_suspend, - .resume = rtlgen_resume, - .read_page = rtl821x_read_page, - .write_page = rtl821x_write_page, + .get_features = rtl822x_get_features, + .config_aneg = rtl822x_config_aneg, + .read_status = rtl822x_read_status, + .suspend = genphy_suspend, + .resume = rtlgen_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, }, { .match_phy_device = rtl_internal_nbaset_match_phy_device, - .name = "Realtek Internal NBASE-T PHY", + .name = "Realtek Internal NBASE-T PHY", .flags = PHY_IS_INTERNAL, .probe = rtl822x_probe, - .get_features = rtl822x_get_features, - .config_aneg = rtl822x_config_aneg, - .read_status = rtl822x_read_status, - .suspend = genphy_suspend, - .resume = rtlgen_resume, - .read_page = rtl821x_read_page, - .write_page = rtl821x_write_page, + .get_features = rtl822x_get_features, + .config_aneg = rtl822x_config_aneg, + .read_status = rtl822x_read_status, + .suspend = genphy_suspend, + .resume = rtlgen_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, + .read_mmd = rtl822x_read_mmd, + .write_mmd = rtl822x_write_mmd, + }, { + PHY_ID_MATCH_EXACT(PHY_ID_RTL_DUMMY_SFP), + .name = "Realtek SFP PHY Mode", + .flags = PHY_IS_INTERNAL, + .probe = rtl822x_probe, + .get_features = rtlgen_sfp_get_features, + .config_aneg = rtlgen_sfp_config_aneg, + .read_status = rtlgen_sfp_read_status, + .suspend = genphy_suspend, + .resume = rtlgen_resume, + .read_page = rtl821x_read_page, + .write_page = rtl821x_write_page, .read_mmd = rtl822x_read_mmd, .write_mmd = rtl822x_write_mmd, }, { PHY_ID_MATCH_EXACT(0x001ccad0), .name = "RTL8224 2.5Gbps PHY", .flags = PHY_POLL_CABLE_TEST, - .get_features = rtl822x_c45_get_features, - .config_aneg = rtl822x_c45_config_aneg, - .read_status = rtl822x_c45_read_status, - .suspend = genphy_c45_pma_suspend, - .resume = rtlgen_c45_resume, + .get_features = rtl822x_c45_get_features, + .config_aneg = rtl822x_c45_config_aneg, + .read_status = rtl822x_c45_read_status, + .suspend = genphy_c45_pma_suspend, + .resume = rtlgen_c45_resume, .cable_test_start = rtl8224_cable_test_start, .cable_test_get_status = rtl8224_cable_test_get_status, }, { @@ -2235,7 +2415,7 @@ static struct phy_driver realtek_drvs[] = { }, { PHY_ID_MATCH_EXACT(0x001ccb00), .name = "RTL9000AA_RTL9000AN Ethernet", - .features = PHY_BASIC_T1_FEATURES, + .features = PHY_BASIC_T1_FEATURES, .config_init = rtl9000a_config_init, .config_aneg = rtl9000a_config_aneg, .read_status = rtl9000a_read_status, diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 3e023723887c..43aefdd8b70f 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -532,9 +532,13 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex, sfp_fixup_ignore_tx_fault), - // Lantech 8330-262D-E can operate at 2500base-X, but incorrectly report - // 2500MBd NRZ in their EEPROM + // Lantech 8330-262D-E and 8330-265D can operate at 2500base-X, but + // incorrectly report 2500MBd NRZ in their EEPROM. + // Some 8330-265D modules have inverted LOS, while all of them report + // normal LOS in EEPROM. Therefore we need to ignore LOS entirely. SFP_QUIRK_S("Lantech", "8330-262D-E", sfp_quirk_2500basex), + SFP_QUIRK("Lantech", "8330-265D", sfp_quirk_2500basex, + sfp_fixup_ignore_los), SFP_QUIRK_S("UBNT", "UF-INSTANT", sfp_quirk_ubnt_uf_instant), |
