diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-05-14 19:42:24 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-05-14 19:42:24 -0700 |
commit | 1b294a1f35616977caddaddf3e9d28e576a1adbc (patch) | |
tree | 723a406740083006b8f8724b5c5e532d4efa431d /drivers/net/phy | |
parent | b850dc206a57ae272c639e31ac202ec0c2f46960 (diff) | |
parent | 654de42f3fc6edc29d743c1dbcd1424f7793f63d (diff) |
Merge tag 'net-next-6.10' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next
Pull networking updates from Jakub Kicinski:
"Core & protocols:
- Complete rework of garbage collection of AF_UNIX sockets.
AF_UNIX is prone to forming reference count cycles due to fd
passing functionality. New method based on Tarjan's Strongly
Connected Components algorithm should be both faster and remove a
lot of workarounds we accumulated over the years.
- Add TCP fraglist GRO support, allowing chaining multiple TCP
packets and forwarding them together. Useful for small switches /
routers which lack basic checksum offload in some scenarios (e.g.
PPPoE).
- Support using SMP threads for handling packet backlog i.e. packet
processing from software interfaces and old drivers which don't use
NAPI. This helps move the processing out of the softirq jumble.
- Continue work of converting from rtnl lock to RCU protection.
Don't require rtnl lock when reading: IPv6 routing FIB, IPv6
address labels, netdev threaded NAPI sysfs files, bonding driver's
sysfs files, MPLS devconf, IPv4 FIB rules, netns IDs, tcp metrics,
TC Qdiscs, neighbor entries, ARP entries via ioctl(SIOCGARP), a lot
of the link information available via rtnetlink.
- Small optimizations from Eric to UDP wake up handling, memory
accounting, RPS/RFS implementation, TCP packet sizing etc.
- Allow direct page recycling in the bulk API used by XDP, for +2%
PPS.
- Support peek with an offset on TCP sockets.
- Add MPTCP APIs for querying last time packets were received/sent/acked
and whether MPTCP "upgrade" succeeded on a TCP socket.
- Add intra-node communication shortcut to improve SMC performance.
- Add IPv6 (and IPv{4,6}-over-IPv{4,6}) support to the GTP protocol
driver.
- Add HSR-SAN (RedBOX) mode of operation to the HSR protocol driver.
- Add reset reasons for tracing what caused a TCP reset to be sent.
- Introduce direction attribute for xfrm (IPSec) states. State can be
used either for input or output packet processing.
Things we sprinkled into general kernel code:
- Add bitmap_{read,write}(), bitmap_size(), expose BYTES_TO_BITS().
This required touch-ups and renaming of a few existing users.
- Add Endian-dependent __counted_by_{le,be} annotations.
- Make building selftests "quieter" by printing summaries like
"CC object.o" rather than full commands with all the arguments.
Netfilter:
- Use GFP_KERNEL to clone elements, to deal better with OOM
situations and avoid failures in the .commit step.
BPF:
- Add eBPF JIT for ARCv2 CPUs.
- Support attaching kprobe BPF programs through kprobe_multi link in
a session mode, meaning, a BPF program is attached to both function
entry and return, the entry program can decide if the return
program gets executed and the entry program can share u64 cookie
value with return program. "Session mode" is a common use-case for
tetragon and bpftrace.
- Add the ability to specify and retrieve BPF cookie for raw
tracepoint programs in order to ease migration from classic to raw
tracepoints.
- Add an internal-only BPF per-CPU instruction for resolving per-CPU
memory addresses and implement support in x86, ARM64 and RISC-V
JITs. This allows inlining functions which need to access per-CPU
state.
- Optimize x86 BPF JIT's emit_mov_imm64, and add support for various
atomics in bpf_arena which can be JITed as a single x86
instruction. Support BPF arena on ARM64.
- Add a new bpf_wq API for deferring events and refactor
process-context bpf_timer code to keep common code where possible.
- Harden the BPF verifier's and/or/xor value tracking.
- Introduce crypto kfuncs to let BPF programs call kernel crypto
APIs.
- Support bpf_tail_call_static() helper for BPF programs with GCC 13.
- Add bpf_preempt_{disable,enable}() kfuncs in order to allow a BPF
program to have code sections where preemption is disabled.
Driver API:
- Skip software TC processing completely if all installed rules are
marked as HW-only, instead of checking the HW-only flag rule by
rule.
- Add support for configuring PoE (Power over Ethernet), similar to
the already existing support for PoDL (Power over Data Line)
config.
- Initial bits of a queue control API, for now allowing a single
queue to be reset without disturbing packet flow to other queues.
- Common (ethtool) statistics for hardware timestamping.
Tests and tooling:
- Remove the need to create a config file to run the net forwarding
tests so that a naive "make run_tests" can exercise them.
- Define a method of writing tests which require an external endpoint
to communicate with (to send/receive data towards the test
machine). Add a few such tests.
- Create a shared code library for writing Python tests. Expose the
YAML Netlink library from tools/ to the tests for easy Netlink
access.
- Move netfilter tests under net/, extend them, separate performance
tests from correctness tests, and iron out issues found by running
them "on every commit".
- Refactor BPF selftests to use common network helpers.
- Further work filling in YAML definitions of Netlink messages for:
nftables, team driver, bonding interfaces, vlan interfaces, VF
info, TC u32 mark, TC police action.
- Teach Python YAML Netlink to decode attribute policies.
- Extend the definition of the "indexed array" construct in the specs
to cover arrays of scalars rather than just nests.
- Add hyperlinks between definitions in generated Netlink docs.
Drivers:
- Make sure unsupported flower control flags are rejected by drivers,
and make more drivers report errors directly to the application
rather than dmesg (large number of driver changes from Asbjørn
Sloth Tønnesen).
- Ethernet high-speed NICs:
- Broadcom (bnxt):
- support multiple RSS contexts and steering traffic to them
- support XDP metadata
- make page pool allocations more NUMA aware
- Intel (100G, ice, idpf):
- extract datapath code common among Intel drivers into a library
- use fewer resources in switchdev by sharing queues with the PF
- add PFCP filter support
- add Ethernet filter support
- use a spinlock instead of HW lock in PTP clock ops
- support 5 layer Tx scheduler topology
- nVidia/Mellanox:
- 800G link modes and 100G SerDes speeds
- per-queue IRQ coalescing configuration
- Marvell Octeon:
- support offloading TC packet mark action
- Ethernet NICs consumer, embedded and virtual:
- stop lying about skb->truesize in USB Ethernet drivers, it
messes up TCP memory calculations
- Google cloud vNIC:
- support changing ring size via ethtool
- support ring reset using the queue control API
- VirtIO net:
- expose flow hash from RSS to XDP
- per-queue statistics
- add selftests
- Synopsys (stmmac):
- support controllers which require an RX clock signal from the
MII bus to perform their hardware initialization
- TI:
- icssg_prueth: support ICSSG-based Ethernet on AM65x SR1.0 devices
- icssg_prueth: add SW TX / RX Coalescing based on hrtimers
- cpsw: minimal XDP support
- Renesas (ravb):
- support describing the MDIO bus
- Realtek (r8169):
- add support for RTL8168M
- Microchip Sparx5:
- matchall and flower actions mirred and redirect
- Ethernet switches:
- nVidia/Mellanox:
- improve events processing performance
- Marvell:
- add support for MV88E6250 family internal PHYs
- Microchip:
- add DCB and DSCP mapping support for KSZ switches
- vsc73xx: convert to PHYLINK
- Realtek:
- rtl8226b/rtl8221b: add C45 instances and SerDes switching
- Many driver changes related to PHYLIB and PHYLINK deprecated API
cleanup
- Ethernet PHYs:
- Add a new driver for Airoha EN8811H 2.5 Gigabit PHY.
- micrel: lan8814: add support for PPS out and external timestamp trigger
- WiFi:
- Disable Wireless Extensions (WEXT) in all Wi-Fi 7 devices
drivers. Modern devices can only be configured using nl80211.
- mac80211/cfg80211
- handle color change per link for WiFi 7 Multi-Link Operation
- Intel (iwlwifi):
- don't support puncturing in 5 GHz
- support monitor mode on passive channels
- BZ-W device support
- P2P with HE/EHT support
- re-add support for firmware API 90
- provide channel survey information for Automatic Channel Selection
- MediaTek (mt76):
- mt7921 LED control
- mt7925 EHT radiotap support
- mt7920e PCI support
- Qualcomm (ath11k):
- P2P support for QCA6390, WCN6855 and QCA2066
- support hibernation
- ieee80211-freq-limit Device Tree property support
- Qualcomm (ath12k):
- refactoring in preparation of multi-link support
- suspend and hibernation support
- ACPI support
- debugfs support, including dfs_simulate_radar support
- RealTek:
- rtw88: RTL8723CS SDIO device support
- rtw89: RTL8922AE Wi-Fi 7 PCI device support
- rtw89: complete features of new WiFi 7 chip 8922AE including
BT-coexistence and Wake-on-WLAN
- rtw89: use BIOS ACPI settings to set TX power and channels
- rtl8xxxu: enable Management Frame Protection (MFP) support
- Bluetooth:
- support for Intel BlazarI and Filmore Peak2 (BE201)
- support for MediaTek MT7921S SDIO
- initial support for Intel PCIe BT driver
- remove HCI_AMP support"
* tag 'net-next-6.10' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1827 commits)
selftests: netfilter: fix packetdrill conntrack testcase
net: gro: fix napi_gro_cb zeroed alignment
Bluetooth: btintel_pcie: Refactor and code cleanup
Bluetooth: btintel_pcie: Fix warning reported by sparse
Bluetooth: hci_core: Fix not handling hdev->le_num_of_adv_sets=1
Bluetooth: btintel: Fix compiler warning for multi_v7_defconfig config
Bluetooth: btintel_pcie: Fix compiler warnings
Bluetooth: btintel_pcie: Add *setup* function to download firmware
Bluetooth: btintel_pcie: Add support for PCIe transport
Bluetooth: btintel: Export few static functions
Bluetooth: HCI: Remove HCI_AMP support
Bluetooth: L2CAP: Fix div-by-zero in l2cap_le_flowctl_init()
Bluetooth: qca: Fix error code in qca_read_fw_build_info()
Bluetooth: hci_conn: Use __counted_by() and avoid -Wfamnae warning
Bluetooth: btintel: Add support for Filmore Peak2 (BE201)
Bluetooth: btintel: Add support for BlazarI
LE Create Connection command timeout increased to 20 secs
dt-bindings: net: bluetooth: Add MediaTek MT7921S SDIO Bluetooth
Bluetooth: compute LE flow credits based on recvbuf space
Bluetooth: hci_sync: Use cmd->num_cis instead of magic number
...
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 5 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/phy/air_en8811h.c | 1090 | ||||
-rw-r--r-- | drivers/net/phy/aquantia/aquantia_main.c | 21 | ||||
-rw-r--r-- | drivers/net/phy/dp83822.c | 37 | ||||
-rw-r--r-- | drivers/net/phy/marvell.c | 397 | ||||
-rw-r--r-- | drivers/net/phy/mediatek-ge.c | 3 | ||||
-rw-r--r-- | drivers/net/phy/micrel.c | 566 | ||||
-rw-r--r-- | drivers/net/phy/phylink.c | 28 | ||||
-rw-r--r-- | drivers/net/phy/qcom/at803x.c | 3 | ||||
-rw-r--r-- | drivers/net/phy/realtek.c | 324 | ||||
-rw-r--r-- | drivers/net/phy/sfp-bus.c | 5 | ||||
-rw-r--r-- | drivers/net/phy/sfp.c | 27 |
13 files changed, 2425 insertions, 82 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 1df0595c5ba9..7fddc8306d82 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -76,6 +76,11 @@ config SFP comment "MII PHY device drivers" +config AIR_EN8811H_PHY + tristate "Airoha EN8811H 2.5 Gigabit PHY" + help + Currently supports the Airoha EN8811H PHY. + config AMD_PHY tristate "AMD and Altima PHYs" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 197acfa0b412..202ed7f450da 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -34,6 +34,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_ADIN1100_PHY) += adin1100.o +obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o obj-$(CONFIG_AMD_PHY) += amd.o obj-$(CONFIG_AQUANTIA_PHY) += aquantia/ ifdef CONFIG_AX88796B_RUST_PHY diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c new file mode 100644 index 000000000000..3cdc8c6b30b6 --- /dev/null +++ b/drivers/net/phy/air_en8811h.c @@ -0,0 +1,1090 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Airoha EN8811H 2.5 Gigabit PHY. + * + * Limitations of the EN8811H: + * - 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 + * + * Copyright (C) 2023 Airoha Technology Corp. + */ + +#include <linux/phy.h> +#include <linux/firmware.h> +#include <linux/property.h> +#include <linux/wordpart.h> +#include <asm/unaligned.h> + +#define EN8811H_PHY_ID 0x03a2a411 + +#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" +#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" + +#define AIR_FW_ADDR_DM 0x00000000 +#define AIR_FW_ADDR_DSP 0x00100000 + +/* MII Registers */ +#define AIR_AUX_CTRL_STATUS 0x1d +#define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2) +#define AIR_AUX_CTRL_STATUS_SPEED_100 0x4 +#define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8 +#define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc + +#define AIR_EXT_PAGE_ACCESS 0x1f +#define AIR_PHY_PAGE_STANDARD 0x0000 +#define AIR_PHY_PAGE_EXTENDED_4 0x0004 + +/* MII Registers Page 4*/ +#define AIR_BPBUS_MODE 0x10 +#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000 +#define AIR_BPBUS_MODE_ADDR_INCR BIT(15) +#define AIR_BPBUS_WR_ADDR_HIGH 0x11 +#define AIR_BPBUS_WR_ADDR_LOW 0x12 +#define AIR_BPBUS_WR_DATA_HIGH 0x13 +#define AIR_BPBUS_WR_DATA_LOW 0x14 +#define AIR_BPBUS_RD_ADDR_HIGH 0x15 +#define AIR_BPBUS_RD_ADDR_LOW 0x16 +#define AIR_BPBUS_RD_DATA_HIGH 0x17 +#define AIR_BPBUS_RD_DATA_LOW 0x18 + +/* Registers on MDIO_MMD_VEND1 */ +#define EN8811H_PHY_FW_STATUS 0x8009 +#define EN8811H_PHY_READY 0x02 + +#define AIR_PHY_MCU_CMD_1 0x800c +#define AIR_PHY_MCU_CMD_1_MODE1 0x0 +#define AIR_PHY_MCU_CMD_2 0x800d +#define AIR_PHY_MCU_CMD_2_MODE1 0x0 +#define AIR_PHY_MCU_CMD_3 0x800e +#define AIR_PHY_MCU_CMD_3_MODE1 0x1101 +#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_INTCLR 0x00e4 + +/* Registers on MDIO_MMD_VEND2 */ +#define AIR_PHY_LED_BCR 0x021 +#define AIR_PHY_LED_BCR_MODE_MASK GENMASK(1, 0) +#define AIR_PHY_LED_BCR_TIME_TEST BIT(2) +#define AIR_PHY_LED_BCR_CLK_EN BIT(3) +#define AIR_PHY_LED_BCR_EXT_CTRL BIT(15) + +#define AIR_PHY_LED_DUR_ON 0x022 + +#define AIR_PHY_LED_DUR_BLINK 0x023 + +#define AIR_PHY_LED_ON(i) (0x024 + ((i) * 2)) +#define AIR_PHY_LED_ON_MASK (GENMASK(6, 0) | BIT(8)) +#define AIR_PHY_LED_ON_LINK1000 BIT(0) +#define AIR_PHY_LED_ON_LINK100 BIT(1) +#define AIR_PHY_LED_ON_LINK10 BIT(2) +#define AIR_PHY_LED_ON_LINKDOWN BIT(3) +#define AIR_PHY_LED_ON_FDX BIT(4) /* Full duplex */ +#define AIR_PHY_LED_ON_HDX BIT(5) /* Half duplex */ +#define AIR_PHY_LED_ON_FORCE_ON BIT(6) +#define AIR_PHY_LED_ON_LINK2500 BIT(8) +#define AIR_PHY_LED_ON_POLARITY BIT(14) +#define AIR_PHY_LED_ON_ENABLE BIT(15) + +#define AIR_PHY_LED_BLINK(i) (0x025 + ((i) * 2)) +#define AIR_PHY_LED_BLINK_1000TX BIT(0) +#define AIR_PHY_LED_BLINK_1000RX BIT(1) +#define AIR_PHY_LED_BLINK_100TX BIT(2) +#define AIR_PHY_LED_BLINK_100RX BIT(3) +#define AIR_PHY_LED_BLINK_10TX BIT(4) +#define AIR_PHY_LED_BLINK_10RX BIT(5) +#define AIR_PHY_LED_BLINK_COLLISION BIT(6) +#define AIR_PHY_LED_BLINK_RX_CRC_ERR BIT(7) +#define AIR_PHY_LED_BLINK_RX_IDLE_ERR BIT(8) +#define AIR_PHY_LED_BLINK_FORCE_BLINK BIT(9) +#define AIR_PHY_LED_BLINK_2500TX BIT(10) +#define AIR_PHY_LED_BLINK_2500RX BIT(11) + +/* Registers on BUCKPBUS */ +#define EN8811H_2P5G_LPA 0x3b30 +#define EN8811H_2P5G_LPA_2P5G BIT(0) + +#define EN8811H_FW_VERSION 0x3b3c + +#define EN8811H_POLARITY 0xca0f8 +#define EN8811H_POLARITY_TX_NORMAL BIT(0) +#define EN8811H_POLARITY_RX_REVERSE BIT(1) + +#define EN8811H_GPIO_OUTPUT 0xcf8b8 +#define EN8811H_GPIO_OUTPUT_345 (BIT(3) | BIT(4) | BIT(5)) + +#define EN8811H_FW_CTRL_1 0x0f0018 +#define EN8811H_FW_CTRL_1_START 0x0 +#define EN8811H_FW_CTRL_1_FINISH 0x1 +#define EN8811H_FW_CTRL_2 0x800000 +#define EN8811H_FW_CTRL_2_LOADING BIT(11) + +/* Led definitions */ +#define EN8811H_LED_COUNT 3 + +/* Default LED setup: + * GPIO5 <-> LED0 On: Link detected, blink Rx/Tx + * GPIO4 <-> LED1 On: Link detected at 2500 or 1000 Mbps + * GPIO3 <-> LED2 On: Link detected at 2500 or 100 Mbps + */ +#define AIR_DEFAULT_TRIGGER_LED0 (BIT(TRIGGER_NETDEV_LINK) | \ + BIT(TRIGGER_NETDEV_RX) | \ + BIT(TRIGGER_NETDEV_TX)) +#define AIR_DEFAULT_TRIGGER_LED1 (BIT(TRIGGER_NETDEV_LINK_2500) | \ + BIT(TRIGGER_NETDEV_LINK_1000)) +#define AIR_DEFAULT_TRIGGER_LED2 (BIT(TRIGGER_NETDEV_LINK_2500) | \ + BIT(TRIGGER_NETDEV_LINK_100)) + +struct led { + unsigned long rules; + unsigned long state; +}; + +struct en8811h_priv { + u32 firmware_version; + bool mcu_needs_restart; + struct led led[EN8811H_LED_COUNT]; +}; + +enum { + AIR_PHY_LED_STATE_FORCE_ON, + AIR_PHY_LED_STATE_FORCE_BLINK, +}; + +enum { + AIR_PHY_LED_DUR_BLINK_32MS, + AIR_PHY_LED_DUR_BLINK_64MS, + AIR_PHY_LED_DUR_BLINK_128MS, + AIR_PHY_LED_DUR_BLINK_256MS, + AIR_PHY_LED_DUR_BLINK_512MS, + AIR_PHY_LED_DUR_BLINK_1024MS, +}; + +enum { + AIR_LED_DISABLE, + AIR_LED_ENABLE, +}; + +enum { + AIR_ACTIVE_LOW, + AIR_ACTIVE_HIGH, +}; + +enum { + AIR_LED_MODE_DISABLE, + AIR_LED_MODE_USER_DEFINE, +}; + +#define AIR_PHY_LED_DUR_UNIT 1024 +#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS) + +static const unsigned long en8811h_led_trig = BIT(TRIGGER_NETDEV_FULL_DUPLEX) | + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + +static int air_phy_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, AIR_EXT_PAGE_ACCESS); +} + +static int air_phy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, AIR_EXT_PAGE_ACCESS, page); +} + +static int __air_buckpbus_reg_write(struct phy_device *phydev, + u32 pbus_address, u32 pbus_data) +{ + int ret; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data)); + if (ret < 0) + return ret; + + return 0; +} + +static int air_buckpbus_reg_write(struct phy_device *phydev, + u32 pbus_address, u32 pbus_data) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_write(phydev, pbus_address, + pbus_data); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +static int __air_buckpbus_reg_read(struct phy_device *phydev, + u32 pbus_address, u32 *pbus_data) +{ + int pbus_data_low, pbus_data_high; + int ret; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) + return pbus_data_high; + + pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) + return pbus_data_low; + + *pbus_data = pbus_data_low | (pbus_data_high << 16); + return 0; +} + +static int air_buckpbus_reg_read(struct phy_device *phydev, + u32 pbus_address, u32 *pbus_data) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +static int __air_buckpbus_reg_modify(struct phy_device *phydev, + u32 pbus_address, u32 mask, u32 set) +{ + int pbus_data_low, pbus_data_high; + u32 pbus_data_old, pbus_data_new; + int ret; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + pbus_data_high = __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) + return pbus_data_high; + + pbus_data_low = __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) + return pbus_data_low; + + pbus_data_old = pbus_data_low | (pbus_data_high << 16); + pbus_data_new = (pbus_data_old & ~mask) | set; + if (pbus_data_new == pbus_data_old) + return 0; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data_new)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data_new)); + if (ret < 0) + return ret; + + return 0; +} + +static int air_buckpbus_reg_modify(struct phy_device *phydev, + u32 pbus_address, u32 mask, u32 set) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask, + set); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +static int __air_write_buf(struct phy_device *phydev, u32 address, + const struct firmware *fw) +{ + unsigned int offset; + int ret; + u16 val; + + ret = __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(address)); + if (ret < 0) + return ret; + + ret = __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(address)); + if (ret < 0) + return ret; + + for (offset = 0; offset < fw->size; offset += 4) { + val = get_unaligned_le16(&fw->data[offset + 2]); + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, val); + if (ret < 0) + return ret; + + val = get_unaligned_le16(&fw->data[offset]); + ret = __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int air_write_buf(struct phy_device *phydev, u32 address, + const struct firmware *fw) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_write_buf(phydev, address, fw); + if (ret < 0) + phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, + address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +static int en8811h_wait_mcu_ready(struct phy_device *phydev) +{ + int ret, reg_value; + + /* 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, + reg_value == EN8811H_PHY_READY, + 20000, 7500000, true); + if (ret) { + phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value); + return -ENODEV; + } + + return 0; +} + +static int en8811h_load_firmware(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + const struct firmware *fw1, *fw2; + int ret; + + ret = request_firmware_direct(&fw1, EN8811H_MD32_DM, dev); + if (ret < 0) + return ret; + + ret = request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev); + if (ret < 0) + goto en8811h_load_firmware_rel1; + + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, + EN8811H_FW_CTRL_2_LOADING); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_write_buf(phydev, AIR_FW_ADDR_DM, fw1); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, fw2); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, 0); + if (ret < 0) + goto en8811h_load_firmware_out; + + ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); + 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_load_firmware_out: + release_firmware(fw2); + +en8811h_load_firmware_rel1: + release_firmware(fw1); + + if (ret < 0) + phydev_err(phydev, "Load firmware failed: %d\n", ret); + + return ret; +} + +static int en8811h_restart_mcu(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 = 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); +} + +static int air_hw_led_on_set(struct phy_device *phydev, u8 index, bool on) +{ + struct en8811h_priv *priv = phydev->priv; + bool changed; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (on) + changed = !test_and_set_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + else + changed = !!test_and_clear_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + + changed |= (priv->led[index].rules != 0); + + /* clear netdev trigger rules in case LED_OFF has been set */ + if (!on) + priv->led[index].rules = 0; + + if (changed) + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, + AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_MASK, + on ? AIR_PHY_LED_ON_FORCE_ON : 0); + + return 0; +} + +static int air_hw_led_blink_set(struct phy_device *phydev, u8 index, + bool blinking) +{ + struct en8811h_priv *priv = phydev->priv; + bool changed; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (blinking) + changed = !test_and_set_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + else + changed = !!test_and_clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + + changed |= (priv->led[index].rules != 0); + + if (changed) + return phy_write_mmd(phydev, MDIO_MMD_VEND2, + AIR_PHY_LED_BLINK(index), + blinking ? + AIR_PHY_LED_BLINK_FORCE_BLINK : 0); + else + return 0; +} + +static int air_led_blink_set(struct phy_device *phydev, u8 index, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct en8811h_priv *priv = phydev->priv; + bool blinking = false; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (delay_on && delay_off && (*delay_on > 0) && (*delay_off > 0)) { + blinking = true; + *delay_on = 50; + *delay_off = 50; + } + + err = air_hw_led_blink_set(phydev, index, blinking); + if (err) + return err; + + /* led-blink set, so switch led-on off */ + err = air_hw_led_on_set(phydev, index, false); + if (err) + return err; + + /* hw-control is off*/ + if (!!test_bit(AIR_PHY_LED_STATE_FORCE_BLINK, &priv->led[index].state)) + priv->led[index].rules = 0; + + return 0; +} + +static int air_led_brightness_set(struct phy_device *phydev, u8 index, + enum led_brightness value) +{ + struct en8811h_priv *priv = phydev->priv; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + /* led-on set, so switch led-blink off */ + err = air_hw_led_blink_set(phydev, index, false); + if (err) + return err; + + err = air_hw_led_on_set(phydev, index, (value != LED_OFF)); + if (err) + return err; + + /* hw-control is off */ + if (!!test_bit(AIR_PHY_LED_STATE_FORCE_ON, &priv->led[index].state)) + priv->led[index].rules = 0; + + return 0; +} + +static int air_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + struct en8811h_priv *priv = phydev->priv; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + *rules = priv->led[index].rules; + + return 0; +}; + +static int air_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + struct en8811h_priv *priv = phydev->priv; + u16 on = 0, blink = 0; + int ret; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + priv->led[index].rules = rules; + + if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX)) + on |= AIR_PHY_LED_ON_FDX; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK10; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK100; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK1000; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) + on |= AIR_PHY_LED_ON_LINK2500; + + if (rules & BIT(TRIGGER_NETDEV_RX)) { + blink |= AIR_PHY_LED_BLINK_10RX | + AIR_PHY_LED_BLINK_100RX | + AIR_PHY_LED_BLINK_1000RX | + AIR_PHY_LED_BLINK_2500RX; + } + + if (rules & BIT(TRIGGER_NETDEV_TX)) { + blink |= AIR_PHY_LED_BLINK_10TX | + AIR_PHY_LED_BLINK_100TX | + AIR_PHY_LED_BLINK_1000TX | + AIR_PHY_LED_BLINK_2500TX; + } + + if (blink || on) { + /* switch hw-control on, so led-on and led-blink are off */ + clear_bit(AIR_PHY_LED_STATE_FORCE_ON, + &priv->led[index].state); + clear_bit(AIR_PHY_LED_STATE_FORCE_BLINK, + &priv->led[index].state); + } else { + priv->led[index].rules = 0; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_MASK, on); + + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index), + blink); +}; + +static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol) +{ + int val = 0; + int err; + + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + if (state == AIR_LED_ENABLE) + val |= AIR_PHY_LED_ON_ENABLE; + else + val &= ~AIR_PHY_LED_ON_ENABLE; + + if (pol == AIR_ACTIVE_HIGH) + val |= AIR_PHY_LED_ON_POLARITY; + else + val &= ~AIR_PHY_LED_ON_POLARITY; + + err = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index), + AIR_PHY_LED_ON_ENABLE | + AIR_PHY_LED_ON_POLARITY, val); + + if (err < 0) + return err; + + return 0; +} + +static int air_leds_init(struct phy_device *phydev, int num, int dur, int mode) +{ + struct en8811h_priv *priv = phydev->priv; + int ret, i; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_BLINK, + dur); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_DUR_ON, + dur >> 1); + if (ret < 0) + return ret; + + switch (mode) { + case AIR_LED_MODE_DISABLE: + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_MODE_MASK, 0); + if (ret < 0) + return ret; + break; + case AIR_LED_MODE_USER_DEFINE: + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_CLK_EN, + AIR_PHY_LED_BCR_EXT_CTRL | + AIR_PHY_LED_BCR_CLK_EN); + if (ret < 0) + return ret; + break; + default: + phydev_err(phydev, "LED mode %d is not supported\n", mode); + return -EINVAL; + } + + for (i = 0; i < num; ++i) { + ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH); + if (ret < 0) { + phydev_err(phydev, "LED%d init failed: %d\n", i, ret); + return ret; + } + air_led_hw_control_set(phydev, i, priv->led[i].rules); + } + + return 0; +} + +static int en8811h_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= EN8811H_LED_COUNT) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~en8811h_led_trig) + return -EOPNOTSUPP; + + return 0; +}; + +static int en8811h_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 = en8811h_load_firmware(phydev); + if (ret < 0) + return ret; + + /* 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); + return ret; + } + + /* Configure led gpio pins as output */ + ret = air_buckpbus_reg_modify(phydev, EN8811H_GPIO_OUTPUT, + EN8811H_GPIO_OUTPUT_345, + EN8811H_GPIO_OUTPUT_345); + if (ret < 0) + return ret; + + return 0; +} + +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 */ + 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; + } + + /* Select mode 1, the only mode supported. + * Configures the SerDes for 2500Base-X with rate adaptation + */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_1, + AIR_PHY_MCU_CMD_1_MODE1); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_2, + AIR_PHY_MCU_CMD_2_MODE1); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3, + AIR_PHY_MCU_CMD_3_MODE1); + if (ret < 0) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4, + AIR_PHY_MCU_CMD_4_MODE1); + 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); + 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; + } + + return 0; +} + +static int en8811h_get_features(struct phy_device *phydev) +{ + linkmode_set_bit_array(phy_basic_ports_array, + ARRAY_SIZE(phy_basic_ports_array), + phydev->supported); + + return genphy_c45_pma_read_abilities(phydev); +} + +static int en8811h_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + return RATE_MATCH_PAUSE; +} + +static int en8811h_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + int ret; + u32 adv; + + if (phydev->autoneg == AUTONEG_DISABLE) { + phydev_warn(phydev, "Disabling autoneg is not supported\n"); + return -EINVAL; + } + + adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising); + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_ADV2_5G, adv); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return __genphy_config_aneg(phydev, changed); +} + +static int en8811h_read_status(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; + u32 pbus_value; + int ret, val; + + ret = genphy_update_link(phydev); + if (ret) + return ret; + + phydev->master_slave_get = MASTER_SLAVE_CFG_UNSUPPORTED; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED; + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + phydev->rate_matching = RATE_MATCH_PAUSE; + + ret = genphy_read_master_slave(phydev); + if (ret < 0) + return ret; + + ret = genphy_read_lpa(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 (phydev->autoneg_complete) + phy_resolve_aneg_pause(phydev); + + if (!phydev->link) + return 0; + + /* Get real speed from vendor register */ + val = phy_read(phydev, AIR_AUX_CTRL_STATUS); + if (val < 0) + return val; + switch (val & AIR_AUX_CTRL_STATUS_SPEED_MASK) { + case AIR_AUX_CTRL_STATUS_SPEED_2500: + phydev->speed = SPEED_2500; + break; + case AIR_AUX_CTRL_STATUS_SPEED_1000: + phydev->speed = SPEED_1000; + break; + case AIR_AUX_CTRL_STATUS_SPEED_100: + phydev->speed = SPEED_100; + break; + } + + /* Firmware before version 24011202 has no vendor register 2P5G_LPA. + * Assume link partner advertised it if connected at 2500Mbps. + */ + if (priv->firmware_version < 0x24011202) { + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + phydev->speed == SPEED_2500); + } + + /* Only supports full duplex */ + phydev->duplex = DUPLEX_FULL; + + return 0; +} + +static int en8811h_clear_intr(struct phy_device *phydev) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_3, + AIR_PHY_MCU_CMD_3_DOCMD); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AIR_PHY_MCU_CMD_4, + AIR_PHY_MCU_CMD_4_INTCLR); + if (ret < 0) + return ret; + + return 0; +} + +static irqreturn_t en8811h_handle_interrupt(struct phy_device *phydev) +{ + int ret; + + ret = en8811h_clear_intr(phydev); + if (ret < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static struct phy_driver en8811h_driver[] = { +{ + PHY_ID_MATCH_MODEL(EN8811H_PHY_ID), + .name = "Airoha EN8811H", + .probe = en8811h_probe, + .get_features = en8811h_get_features, + .config_init = en8811h_config_init, + .get_rate_matching = en8811h_get_rate_matching, + .config_aneg = en8811h_config_aneg, + .read_status = en8811h_read_status, + .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 struct mdio_device_id __maybe_unused en8811h_tbl[] = { + { PHY_ID_MATCH_MODEL(EN8811H_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, en8811h_tbl); +MODULE_FIRMWARE(EN8811H_MD32_DM); +MODULE_FIRMWARE(EN8811H_MD32_DSP); + +MODULE_DESCRIPTION("Airoha EN8811H PHY drivers"); +MODULE_AUTHOR("Airoha"); +MODULE_AUTHOR("Eric Woudstra <ericwouds@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/aquantia/aquantia_main.c b/drivers/net/phy/aquantia/aquantia_main.c index 71bfddb8f453..d34cdec47636 100644 --- a/drivers/net/phy/aquantia/aquantia_main.c +++ b/drivers/net/phy/aquantia/aquantia_main.c @@ -28,6 +28,7 @@ #define PHY_ID_AQR412 0x03a1b712 #define PHY_ID_AQR113 0x31c31c40 #define PHY_ID_AQR113C 0x31c31c12 +#define PHY_ID_AQR114C 0x31c31c22 #define PHY_ID_AQR813 0x31c31cb2 #define MDIO_PHYXS_VEND_IF_STATUS 0xe812 @@ -963,6 +964,25 @@ static struct phy_driver aqr_driver[] = { .link_change_notify = aqr107_link_change_notify, }, { + PHY_ID_MATCH_MODEL(PHY_ID_AQR114C), + .name = "Aquantia AQR114C", + .probe = aqr107_probe, + .get_rate_matching = aqr107_get_rate_matching, + .config_init = aqr111_config_init, + .config_aneg = aqr_config_aneg, + .config_intr = aqr_config_intr, + .handle_interrupt = aqr_handle_interrupt, + .read_status = aqr107_read_status, + .get_tunable = aqr107_get_tunable, + .set_tunable = aqr107_set_tunable, + .suspend = aqr107_suspend, + .resume = aqr107_resume, + .get_sset_count = aqr107_get_sset_count, + .get_strings = aqr107_get_strings, + .get_stats = aqr107_get_stats, + .link_change_notify = aqr107_link_change_notify, +}, +{ PHY_ID_MATCH_MODEL(PHY_ID_AQR813), .name = "Aquantia AQR813", .probe = aqr107_probe, @@ -999,6 +1019,7 @@ static struct mdio_device_id __maybe_unused aqr_tbl[] = { { PHY_ID_MATCH_MODEL(PHY_ID_AQR412) }, { PHY_ID_MATCH_MODEL(PHY_ID_AQR113) }, { PHY_ID_MATCH_MODEL(PHY_ID_AQR113C) }, + { PHY_ID_MATCH_MODEL(PHY_ID_AQR114C) }, { PHY_ID_MATCH_MODEL(PHY_ID_AQR813) }, { } }; diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c index c3426a17e6d0..efeb643c1373 100644 --- a/drivers/net/phy/dp83822.c +++ b/drivers/net/phy/dp83822.c @@ -140,10 +140,11 @@ struct dp83822_private { u16 fx_sd_enable; u8 cfg_dac_minus; u8 cfg_dac_plus; + struct ethtool_wolinfo wol; }; -static int dp83822_set_wol(struct phy_device *phydev, - struct ethtool_wolinfo *wol) +static int dp83822_config_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) { struct net_device *ndev = phydev->attached_dev; u16 value; @@ -197,10 +198,25 @@ static int dp83822_set_wol(struct phy_device *phydev, MII_DP83822_WOL_CFG, value); } else { return phy_clear_bits_mmd(phydev, DP83822_DEVADDR, - MII_DP83822_WOL_CFG, DP83822_WOL_EN); + MII_DP83822_WOL_CFG, + DP83822_WOL_EN | + DP83822_WOL_MAGIC_EN | + DP83822_WOL_SECURE_ON); } } +static int dp83822_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + struct dp83822_private *dp83822 = phydev->priv; + int ret; + + ret = dp83822_config_wol(phydev, wol); + if (!ret) + memcpy(&dp83822->wol, wol, sizeof(*wol)); + return ret; +} + static void dp83822_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { @@ -346,13 +362,6 @@ static irqreturn_t dp83822_handle_interrupt(struct phy_device *phydev) return IRQ_HANDLED; } -static int dp8382x_disable_wol(struct phy_device *phydev) -{ - return phy_clear_bits_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, - DP83822_WOL_EN | DP83822_WOL_MAGIC_EN | - DP83822_WOL_SECURE_ON); -} - static int dp83822_read_status(struct phy_device *phydev) { struct dp83822_private *dp83822 = phydev->priv; @@ -496,7 +505,7 @@ static int dp83822_config_init(struct phy_device *phydev) return err; } } - return dp8382x_disable_wol(phydev); + return dp83822_config_wol(phydev, &dp83822->wol); } static int dp83826_config_rmii_mode(struct phy_device *phydev) @@ -575,12 +584,14 @@ static int dp83826_config_init(struct phy_device *phydev) return ret; } - return dp8382x_disable_wol(phydev); + return dp83822_config_wol(phydev, &dp83822->wol); } static int dp8382x_config_init(struct phy_device *phydev) { - return dp8382x_disable_wol(phydev); + struct dp83822_private *dp83822 = phydev->priv; + + return dp83822_config_wol(phydev, &dp83822->wol); } static int dp83822_phy_reset(struct phy_device *phydev) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index 42ed013385bf..b89fbffa6a93 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -279,10 +279,29 @@ #define MII_VCT7_CTRL_METERS BIT(10) #define MII_VCT7_CTRL_CENTIMETERS 0 +#define MII_VCT_TXPINS 0x1A +#define MII_VCT_RXPINS 0x1B +#define MII_VCT_SR 0x1C +#define MII_VCT_TXPINS_ENVCT BIT(15) +#define MII_VCT_TXRXPINS_VCTTST GENMASK(14, 13) +#define MII_VCT_TXRXPINS_VCTTST_SHIFT 13 +#define MII_VCT_TXRXPINS_VCTTST_OK 0 +#define MII_VCT_TXRXPINS_VCTTST_SHORT 1 +#define MII_VCT_TXRXPINS_VCTTST_OPEN 2 +#define MII_VCT_TXRXPINS_VCTTST_FAIL 3 +#define MII_VCT_TXRXPINS_AMPRFLN GENMASK(12, 8) +#define MII_VCT_TXRXPINS_AMPRFLN_SHIFT 8 +#define MII_VCT_TXRXPINS_DISTRFLN GENMASK(7, 0) +#define MII_VCT_TXRXPINS_DISTRFLN_MAX 0xff + +#define M88E3082_PAIR_A BIT(0) +#define M88E3082_PAIR_B BIT(1) + #define LPA_PAUSE_FIBER 0x180 #define LPA_PAUSE_ASYM_FIBER 0x100 #define NB_FIBER_STATS 1 +#define NB_STAT_MAX 3 MODULE_DESCRIPTION("Marvell PHY driver"); MODULE_AUTHOR("Andy Fleming"); @@ -295,14 +314,37 @@ struct marvell_hw_stat { u8 bits; }; -static struct marvell_hw_stat marvell_hw_stats[] = { +static const struct marvell_hw_stat marvell_hw_stats[] = { { "phy_receive_errors_copper", 0, 21, 16}, { "phy_idle_errors", 0, 10, 8 }, { "phy_receive_errors_fiber", 1, 21, 16}, }; +static_assert(ARRAY_SIZE(marvell_hw_stats) <= NB_STAT_MAX); + +/* "simple" stat list + corresponding marvell_get_*_simple functions are used + * on PHYs without a page register + */ +struct marvell_hw_stat_simple { + const char *string; + u8 reg; + u8 bits; +}; + +static const struct marvell_hw_stat_simple marvell_hw_stats_simple[] = { + { "phy_receive_errors", 21, 16}, +}; + +static_assert(ARRAY_SIZE(marvell_hw_stats_simple) <= NB_STAT_MAX); + +enum { + M88E3082_VCT_OFF, + M88E3082_VCT_PHASE1, + M88E3082_VCT_PHASE2, +}; + struct marvell_priv { - u64 stats[ARRAY_SIZE(marvell_hw_stats)]; + u64 stats[NB_STAT_MAX]; char *hwmon_name; struct device *hwmon_dev; bool cable_test_tdr; @@ -310,6 +352,7 @@ struct marvell_priv { u32 last; u32 step; s8 pair; + u8 vct_phase; }; static int marvell_read_page(struct phy_device *phydev) @@ -1953,6 +1996,11 @@ static int marvell_get_sset_count(struct phy_device *phydev) return ARRAY_SIZE(marvell_hw_stats) - NB_FIBER_STATS; } +static int marvell_get_sset_count_simple(struct phy_device *phydev) +{ + return ARRAY_SIZE(marvell_hw_stats_simple); +} + static void marvell_get_strings(struct phy_device *phydev, u8 *data) { int count = marvell_get_sset_count(phydev); @@ -1964,6 +2012,17 @@ static void marvell_get_strings(struct phy_device *phydev, u8 *data) } } +static void marvell_get_strings_simple(struct phy_device *phydev, u8 *data) +{ + int count = marvell_get_sset_count_simple(phydev); + int i; + + for (i = 0; i < count; i++) { + strscpy(data + i * ETH_GSTRING_LEN, + marvell_hw_stats_simple[i].string, ETH_GSTRING_LEN); + } +} + static u64 marvell_get_stat(struct phy_device *phydev, int i) { struct marvell_hw_stat stat = marvell_hw_stats[i]; @@ -1983,6 +2042,25 @@ static u64 marvell_get_stat(struct phy_device *phydev, int i) return ret; } +static u64 marvell_get_stat_simple(struct phy_device *phydev, int i) +{ + struct marvell_hw_stat_simple stat = marvell_hw_stats_simple[i]; + struct marvell_priv *priv = phydev->priv; + int val; + u64 ret; + + val = phy_read(phydev, stat.reg); + if (val < 0) { + ret = U64_MAX; + } else { + val = val & ((1 << stat.bits) - 1); + priv->stats[i] += val; + ret = priv->stats[i]; + } + + return ret; +} + static void marvell_get_stats(struct phy_device *phydev, struct ethtool_stats *stats, u64 *data) { @@ -1993,6 +2071,16 @@ static void marvell_get_stats(struct phy_device *phydev, data[i] = marvell_get_stat(phydev, i); } +static void marvell_get_stats_simple(struct phy_device *phydev, + struct ethtool_stats *stats, u64 *data) +{ + int count = marvell_get_sset_count_simple(phydev); + int i; + + for (i = 0; i < count; i++) + data[i] = marvell_get_stat_simple(phydev, i); +} + static int m88e1510_loopback(struct phy_device *phydev, bool enable) { int err; @@ -2417,6 +2505,274 @@ static int marvell_vct7_cable_test_get_status(struct phy_device *phydev, return 0; } +static int m88e3082_vct_cable_test_start(struct phy_device *phydev) +{ + struct marvell_priv *priv = phydev->priv; + int ret; + + /* It needs some magic workarounds described in VCT manual for this PHY. + */ + ret = phy_write(phydev, 29, 0x0003); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x6440); + if (ret < 0) + return ret; + + if (priv->vct_phase == M88E3082_VCT_PHASE1) { + ret = phy_write(phydev, 29, 0x000a); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0002); + if (ret < 0) + return ret; + } + + ret = phy_write(phydev, MII_BMCR, + BMCR_RESET | BMCR_SPEED100 | BMCR_FULLDPLX); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MII_VCT_TXPINS, MII_VCT_TXPINS_ENVCT); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 29, 0x0003); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0); + if (ret < 0) + return ret; + + if (priv->vct_phase == M88E3082_VCT_OFF) { + priv->vct_phase = M88E3082_VCT_PHASE1; + priv->pair = 0; + + return 0; + } + + ret = phy_write(phydev, 29, 0x000a); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0); + if (ret < 0) + return ret; + + priv->vct_phase = M88E3082_VCT_PHASE2; + + return 0; +} + +static int m88e3082_vct_cable_test_report_trans(int result, u8 distance) +{ + switch (result) { + case MII_VCT_TXRXPINS_VCTTST_OK: + if (distance == MII_VCT_TXRXPINS_DISTRFLN_MAX) + return ETHTOOL_A_CABLE_RESULT_CODE_OK; + return ETHTOOL_A_CABLE_RESULT_CODE_IMPEDANCE_MISMATCH; + case MII_VCT_TXRXPINS_VCTTST_SHORT: + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; + case MII_VCT_TXRXPINS_VCTTST_OPEN: + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; + default: + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; + } +} + +static u32 m88e3082_vct_distrfln_2_cm(u8 distrfln) +{ + if (distrfln < 24) + return 0; + + /* Original function for meters: y = 0.7861x - 18.862 */ + return (7861 * distrfln - 188620) / 100; +} + +static int m88e3082_vct_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + u8 tx_vcttst_res, rx_vcttst_res, tx_distrfln, rx_distrfln; + struct marvell_priv *priv = phydev->priv; + int ret, tx_result, rx_result; + bool done_phase = true; + + *finished = false; + + ret = phy_read(phydev, MII_VCT_TXPINS); + if (ret < 0) + return ret; + else if (ret & MII_VCT_TXPINS_ENVCT) + return 0; + + tx_distrfln = ret & MII_VCT_TXRXPINS_DISTRFLN; + tx_vcttst_res = (ret & MII_VCT_TXRXPINS_VCTTST) >> + MII_VCT_TXRXPINS_VCTTST_SHIFT; + + ret = phy_read(phydev, MII_VCT_RXPINS); + if (ret < 0) + return ret; + + rx_distrfln = ret & MII_VCT_TXRXPINS_DISTRFLN; + rx_vcttst_res = (ret & MII_VCT_TXRXPINS_VCTTST) >> + MII_VCT_TXRXPINS_VCTTST_SHIFT; + + *finished = true; + + switch (priv->vct_phase) { + case M88E3082_VCT_PHASE1: + tx_result = m88e3082_vct_cable_test_report_trans(tx_vcttst_res, + tx_distrfln); + rx_result = m88e3082_vct_cable_test_report_trans(rx_vcttst_res, + rx_distrfln); + + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, + tx_result); + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B, + rx_result); + + if (tx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN) { + done_phase = false; + priv->pair |= M88E3082_PAIR_A; + } else if (tx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_A; + u32 cm = m88e3082_vct_distrfln_2_cm(tx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + + if (rx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN) { + done_phase = false; + priv->pair |= M88E3082_PAIR_B; + } else if (rx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_B; + u32 cm = m88e3082_vct_distrfln_2_cm(rx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + + break; + case M88E3082_VCT_PHASE2: + if (priv->pair & M88E3082_PAIR_A && + tx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN && + tx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_A; + u32 cm = m88e3082_vct_distrfln_2_cm(tx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + if (priv->pair & M88E3082_PAIR_B && + rx_vcttst_res == MII_VCT_TXRXPINS_VCTTST_OPEN && + rx_distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u8 pair = ETHTOOL_A_CABLE_PAIR_B; + u32 cm = m88e3082_vct_distrfln_2_cm(rx_distrfln); + + ethnl_cable_test_fault_length(phydev, pair, cm); + } + + break; + default: + return -EINVAL; + } + + if (!done_phase) { + *finished = false; + return m88e3082_vct_cable_test_start(phydev); + } + if (*finished) + priv->vct_phase = M88E3082_VCT_OFF; + return 0; +} + +static int m88e1111_vct_cable_test_start(struct phy_device *phydev) +{ + int ret; + + ret = marvell_cable_test_start_common(phydev); + if (ret) + return ret; + + /* It needs some magic workarounds described in VCT manual for this PHY. + */ + ret = phy_write(phydev, 29, 0x0018); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x00c2); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x00ca); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x00c2); + if (ret < 0) + return ret; + + ret = phy_write_paged(phydev, MII_MARVELL_COPPER_PAGE, MII_VCT_SR, + MII_VCT_TXPINS_ENVCT); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 29, 0x0018); + if (ret < 0) + return ret; + + ret = phy_write(phydev, 30, 0x0042); + if (ret < 0) + return ret; + + return 0; +} + +static u32 m88e1111_vct_distrfln_2_cm(u8 distrfln) +{ + if (distrfln < 36) + return 0; + + /* Original function for meters: y = 0.8018x - 28.751 */ + return (8018 * distrfln - 287510) / 100; +} + +static int m88e1111_vct_cable_test_get_status(struct phy_device *phydev, + bool *finished) +{ + u8 vcttst_res, distrfln; + int ret, result; + + *finished = false; + + /* Each pair use one page: A-0, B-1, C-2, D-3 */ + for (u8 i = 0; i < 4; i++) { + ret = phy_read_paged(phydev, i, MII_VCT_SR); + if (ret < 0) + return ret; + else if (i == 0 && ret & MII_VCT_TXPINS_ENVCT) + return 0; + + distrfln = ret & MII_VCT_TXRXPINS_DISTRFLN; + vcttst_res = (ret & MII_VCT_TXRXPINS_VCTTST) >> + MII_VCT_TXRXPINS_VCTTST_SHIFT; + + result = m88e3082_vct_cable_test_report_trans(vcttst_res, + distrfln); + ethnl_cable_test_result(phydev, i, result); + + if (distrfln < MII_VCT_TXRXPINS_DISTRFLN_MAX) { + u32 cm = m88e1111_vct_distrfln_2_cm(distrfln); + + ethnl_cable_test_fault_length(phydev, i, cm); + } + } + + *finished = true; + return 0; +} + #ifdef CONFIG_HWMON struct marvell_hwmon_ops { int (*config)(struct phy_device *phydev); @@ -3290,6 +3646,20 @@ static struct phy_driver marvell_drivers[] = { .get_stats = marvell_get_stats, }, { + .phy_id = MARVELL_PHY_ID_88E3082, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E308X/88E609X Family", + /* PHY_BASIC_FEATURES */ + .probe = marvell_probe, + .config_init = marvell_config_init, + .aneg_done = marvell_aneg_done, + .read_status = marvell_read_status, + .resume = genphy_resume, + .suspend = genphy_suspend, + .cable_test_start = m88e3082_vct_cable_test_start, + .cable_test_get_status = m88e3082_vct_cable_test_get_status, + }, + { .phy_id = MARVELL_PHY_ID_88E1112, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1112", @@ -3314,6 +3684,7 @@ static struct phy_driver marvell_drivers[] = { .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1111", /* PHY_GBIT_FEATURES */ + .flags = PHY_POLL_CABLE_TEST, .probe = marvell_probe, .config_init = m88e1111gbe_config_init, .config_aneg = m88e1111_config_aneg, @@ -3329,6 +3700,8 @@ static struct phy_driver marvell_drivers[] = { .get_stats = marvell_get_stats, .get_tunable = m88e1111_get_tunable, .set_tunable = m88e1111_set_tunable, + .cable_test_start = m88e1111_vct_cable_test_start, + .cable_test_get_status = m88e1111_vct_cable_test_get_status, }, { .phy_id = MARVELL_PHY_ID_88E1111_FINISAR, @@ -3422,6 +3795,7 @@ static struct phy_driver marvell_drivers[] = { .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1145", /* PHY_GBIT_FEATURES */ + .flags = PHY_POLL_CABLE_TEST, .probe = marvell_probe, .config_init = m88e1145_config_init, .config_aneg = m88e1101_config_aneg, @@ -3436,6 +3810,8 @@ static struct phy_driver marvell_drivers[] = { .get_stats = marvell_get_stats, .get_tunable = m88e1111_get_tunable, .set_tunable = m88e1111_set_tunable, + .cable_test_start = m88e1111_vct_cable_test_start, + .cable_test_get_status = m88e1111_vct_cable_test_get_status, }, { .phy_id = MARVELL_PHY_ID_88E1149R, @@ -3610,6 +3986,21 @@ static struct phy_driver marvell_drivers[] = { .get_stats = marvell_get_stats, }, { + .phy_id = MARVELL_PHY_ID_88E6250_FAMILY, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E6250 Family", + /* PHY_BASIC_FEATURES */ + .probe = marvell_probe, + .aneg_done = marvell_aneg_done, + .config_intr = marvell_config_intr, + .handle_interrupt = marvell_handle_interrupt, + .resume = genphy_resume, + .suspend = genphy_suspend, + .get_sset_count = marvell_get_sset_count_simple, + .get_strings = marvell_get_strings_simple, + .get_stats = marvell_get_stats_simple, + }, + { .phy_id = MARVELL_PHY_ID_88E6341_FAMILY, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E6341 Family", @@ -3742,6 +4133,7 @@ module_phy_driver(marvell_drivers); static struct mdio_device_id __maybe_unused marvell_tbl[] = { { MARVELL_PHY_ID_88E1101, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E3082, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E1112, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E1111, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E1111_FINISAR, MARVELL_PHY_ID_MASK }, @@ -3756,6 +4148,7 @@ static struct mdio_device_id __maybe_unused marvell_tbl[] = { { MARVELL_PHY_ID_88E1540, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E1545, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E3016, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88E6250_FAMILY, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E6341_FAMILY, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E6390_FAMILY, MARVELL_PHY_ID_MASK }, { MARVELL_PHY_ID_88E6393_FAMILY, MARVELL_PHY_ID_MASK }, diff --git a/drivers/net/phy/mediatek-ge.c b/drivers/net/phy/mediatek-ge.c index a493ae01b267..54ea64a37ab3 100644 --- a/drivers/net/phy/mediatek-ge.c +++ b/drivers/net/phy/mediatek-ge.c @@ -23,9 +23,6 @@ static int mtk_gephy_write_page(struct phy_device *phydev, int page) static void mtk_gephy_config_init(struct phy_device *phydev) { - /* Disable EEE */ - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); - /* Enable HW auto downshift */ phy_modify_paged(phydev, MTK_PHY_PAGE_EXTENDED, 0x14, 0, BIT(4)); diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index ddb50a0e2bc8..13e30ea7eec5 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -167,6 +167,9 @@ #define PTP_CMD_CTL_PTP_LTC_STEP_SEC_ BIT(5) #define PTP_CMD_CTL_PTP_LTC_STEP_NSEC_ BIT(6) +#define PTP_COMMON_INT_ENA 0x0204 +#define PTP_COMMON_INT_ENA_GPIO_CAP_EN BIT(2) + #define PTP_CLOCK_SET_SEC_HI 0x0205 #define PTP_CLOCK_SET_SEC_MID 0x0206 #define PTP_CLOCK_SET_SEC_LO 0x0207 @@ -179,6 +182,27 @@ #define PTP_CLOCK_READ_NS_HI 0x022C #define PTP_CLOCK_READ_NS_LO 0x022D +#define PTP_GPIO_SEL 0x0230 +#define PTP_GPIO_SEL_GPIO_SEL(pin) ((pin) << 8) +#define PTP_GPIO_CAP_MAP_LO 0x0232 + +#define PTP_GPIO_CAP_EN 0x0233 +#define PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(gpio) BIT(gpio) +#define PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(gpio) (BIT(gpio) << 8) + +#define PTP_GPIO_RE_LTC_SEC_HI_CAP 0x0235 +#define PTP_GPIO_RE_LTC_SEC_LO_CAP 0x0236 +#define PTP_GPIO_RE_LTC_NS_HI_CAP 0x0237 +#define PTP_GPIO_RE_LTC_NS_LO_CAP 0x0238 +#define PTP_GPIO_FE_LTC_SEC_HI_CAP 0x0239 +#define PTP_GPIO_FE_LTC_SEC_LO_CAP 0x023A +#define PTP_GPIO_FE_LTC_NS_HI_CAP 0x023B +#define PTP_GPIO_FE_LTC_NS_LO_CAP 0x023C + +#define PTP_GPIO_CAP_STS 0x023D +#define PTP_GPIO_CAP_STS_PTP_GPIO_RE_STS(gpio) BIT(gpio) +#define PTP_GPIO_CAP_STS_PTP_GPIO_FE_STS(gpio) (BIT(gpio) << 8) + #define PTP_OPERATING_MODE 0x0241 #define PTP_OPERATING_MODE_STANDALONE_ BIT(0) @@ -272,6 +296,67 @@ #define PS_TO_REG 200 #define FIFO_SIZE 8 +#define LAN8814_PTP_GPIO_NUM 24 +#define LAN8814_PTP_PEROUT_NUM 2 +#define LAN8814_PTP_EXTTS_NUM 3 + +#define LAN8814_BUFFER_TIME 2 + +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_200MS 13 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100MS 12 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50MS 11 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10MS 10 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5MS 9 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1MS 8 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500US 7 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100US 6 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50US 5 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10US 4 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5US 3 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1US 2 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500NS 1 +#define LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100NS 0 + +#define LAN8814_GPIO_EN1 0x20 +#define LAN8814_GPIO_EN2 0x21 +#define LAN8814_GPIO_DIR1 0x22 +#define LAN8814_GPIO_DIR2 0x23 +#define LAN8814_GPIO_BUF1 0x24 +#define LAN8814_GPIO_BUF2 0x25 + +#define LAN8814_GPIO_EN_ADDR(pin) \ + ((pin) > 15 ? LAN8814_GPIO_EN1 : LAN8814_GPIO_EN2) +#define LAN8814_GPIO_EN_BIT(pin) BIT(pin) +#define LAN8814_GPIO_DIR_ADDR(pin) \ + ((pin) > 15 ? LAN8814_GPIO_DIR1 : LAN8814_GPIO_DIR2) +#define LAN8814_GPIO_DIR_BIT(pin) BIT(pin) +#define LAN8814_GPIO_BUF_ADDR(pin) \ + ((pin) > 15 ? LAN8814_GPIO_BUF1 : LAN8814_GPIO_BUF2) +#define LAN8814_GPIO_BUF_BIT(pin) BIT(pin) + +#define LAN8814_EVENT_A 0 +#define LAN8814_EVENT_B 1 + +#define LAN8814_PTP_GENERAL_CONFIG 0x0201 +#define LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_MASK(event) \ + ((event) ? GENMASK(11, 8) : GENMASK(7, 4)) +#define LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_SET(event, value) \ + (((value) & GENMASK(3, 0)) << (4 + ((event) << 2))) +#define LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event) \ + ((event) ? BIT(2) : BIT(0)) +#define LAN8814_PTP_GENERAL_CONFIG_POLARITY_X(event) \ + ((event) ? BIT(3) : BIT(1)) + +#define LAN8814_PTP_CLOCK_TARGET_SEC_HI(event) ((event) ? 0x21F : 0x215) +#define LAN8814_PTP_CLOCK_TARGET_SEC_LO(event) ((event) ? 0x220 : 0x216) +#define LAN8814_PTP_CLOCK_TARGET_NS_HI(event) ((event) ? 0x221 : 0x217) +#define LAN8814_PTP_CLOCK_TARGET_NS_LO(event) ((event) ? 0x222 : 0x218) + +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_HI(event) ((event) ? 0x223 : 0x219) +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_LO(event) ((event) ? 0x224 : 0x21A) +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_HI(event) ((event) ? 0x225 : 0x21B) +#define LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_LO(event) ((event) ? 0x226 : 0x21C) + /* Delay used to get the second part from the LTC */ #define LAN8841_GET_SEC_LTC_DELAY (500 * NSEC_PER_MSEC) @@ -304,13 +389,9 @@ struct lan8814_shared_priv { struct phy_device *phydev; struct ptp_clock *ptp_clock; struct ptp_clock_info ptp_clock_info; + struct ptp_pin_desc *pin_config; - /* Reference counter to how many ports in the package are enabling the - * timestamping - */ - u8 ref; - - /* Lock for ptp_clock and ref */ + /* Lock for ptp_clock */ struct mutex shared_lock; }; @@ -2426,8 +2507,6 @@ static int lan8814_hwtstamp(struct mii_timestamper *mii_ts, { struct kszphy_ptp_priv *ptp_priv = container_of(mii_ts, struct kszphy_ptp_priv, mii_ts); - struct phy_device *phydev = ptp_priv->phydev; - struct lan8814_shared_priv *shared = phydev->shared->priv; struct lan8814_ptp_rx_ts *rx_ts, *tmp; int txcfg = 0, rxcfg = 0; int pkt_ts_enable; @@ -2492,20 +2571,6 @@ static int lan8814_hwtstamp(struct mii_timestamper *mii_ts, else lan8814_config_ts_intr(ptp_priv->phydev, false); - mutex_lock(&shared->shared_lock); - if (config->rx_filter != HWTSTAMP_FILTER_NONE) - shared->ref++; - else - shared->ref--; - - if (shared->ref) - lanphy_write_page_reg(ptp_priv->phydev, 4, PTP_CMD_CTL, - PTP_CMD_CTL_PTP_ENABLE_); - else - lanphy_write_page_reg(ptp_priv->phydev, 4, PTP_CMD_CTL, - PTP_CMD_CTL_PTP_DISABLE_); - mutex_unlock(&shared->shared_lock); - /* In case of multiple starts and stops, these needs to be cleared */ list_for_each_entry_safe(rx_ts, tmp, &ptp_priv->rx_ts_list, list) { list_del(&rx_ts->list); @@ -2677,6 +2742,29 @@ static int lan8814_ptpci_settime64(struct ptp_clock_info *ptpci, return 0; } +static void lan8814_ptp_set_target(struct phy_device *phydev, int event, + s64 start_sec, u32 start_nsec) +{ + /* Set the start time */ + lanphy_write_page_reg(phydev, 4, LAN8814_PTP_CLOCK_TARGET_SEC_LO(event), + lower_16_bits(start_sec)); + lanphy_write_page_reg(phydev, 4, LAN8814_PTP_CLOCK_TARGET_SEC_HI(event), + upper_16_bits(start_sec)); + + lanphy_write_page_reg(phydev, 4, LAN8814_PTP_CLOCK_TARGET_NS_LO(event), + lower_16_bits(start_nsec)); + lanphy_write_page_reg(phydev, 4, LAN8814_PTP_CLOCK_TARGET_NS_HI(event), + upper_16_bits(start_nsec) & 0x3fff); +} + +static void lan8814_ptp_update_target(struct phy_device *phydev, time64_t sec) +{ + lan8814_ptp_set_target(phydev, LAN8814_EVENT_A, + sec + LAN8814_BUFFER_TIME, 0); + lan8814_ptp_set_target(phydev, LAN8814_EVENT_B, + sec + LAN8814_BUFFER_TIME, 0); +} + static void lan8814_ptp_clock_step(struct phy_device *phydev, s64 time_step_ns) { @@ -2698,6 +2786,7 @@ static void lan8814_ptp_clock_step(struct phy_device *phydev, nano_seconds -= 1000000000; } lan8814_ptp_clock_set(phydev, set_seconds, nano_seconds); + lan8814_ptp_update_target(phydev, set_seconds); return; } else if (time_step_ns < -15000000000LL) { /* convert to clock set */ @@ -2713,6 +2802,7 @@ static void lan8814_ptp_clock_step(struct phy_device *phydev, } nano_seconds -= nano_seconds_step; lan8814_ptp_clock_set(phydev, set_seconds, nano_seconds); + lan8814_ptp_update_target(phydev, set_seconds); return; } @@ -2749,6 +2839,8 @@ static void lan8814_ptp_clock_step(struct phy_device *phydev, } while (seconds) { + u32 nsec; + if (seconds > 0) { u32 adjustment_value = (u32)seconds; u16 adjustment_value_lo, adjustment_value_hi; @@ -2765,6 +2857,10 @@ static void lan8814_ptp_clock_step(struct phy_device *phydev, PTP_LTC_STEP_ADJ_DIR_ | adjustment_value_hi); seconds -= ((s32)adjustment_value); + + lan8814_ptp_clock_get(phydev, &set_seconds, &nsec); + set_seconds -= adjustment_value; + lan8814_ptp_update_target(phydev, set_seconds); } else { u32 adjustment_value = (u32)(-seconds); u16 adjustment_value_lo, adjustment_value_hi; @@ -2780,6 +2876,10 @@ static void lan8814_ptp_clock_step(struct phy_device *phydev, lanphy_write_page_reg(phydev, 4, PTP_LTC_STEP_ADJ_HI, adjustment_value_hi); seconds += ((s32)adjustment_value); + + lan8814_ptp_clock_get(phydev, &set_seconds, &nsec); + set_seconds += adjustment_value; + lan8814_ptp_update_target(phydev, set_seconds); } lanphy_write_page_reg(phydev, 4, PTP_CMD_CTL, PTP_CMD_CTL_PTP_LTC_STEP_SEC_); @@ -2845,6 +2945,335 @@ static int lan8814_ptpci_adjfine(struct ptp_clock_info *ptpci, long scaled_ppm) return 0; } +static void lan8814_ptp_set_reload(struct phy_device *phydev, int event, + s64 period_sec, u32 period_nsec) +{ + lanphy_write_page_reg(phydev, 4, + LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_LO(event), + lower_16_bits(period_sec)); + lanphy_write_page_reg(phydev, 4, + LAN8814_PTP_CLOCK_TARGET_RELOAD_SEC_HI(event), + upper_16_bits(period_sec)); + + lanphy_write_page_reg(phydev, 4, + LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_LO(event), + lower_16_bits(period_nsec)); + lanphy_write_page_reg(phydev, 4, + LAN8814_PTP_CLOCK_TARGET_RELOAD_NS_HI(event), + upper_16_bits(period_nsec) & 0x3fff); +} + +static void lan8814_ptp_enable_event(struct phy_device *phydev, int event, + int pulse_width) +{ + u16 val; + + val = lanphy_read_page_reg(phydev, 4, LAN8814_PTP_GENERAL_CONFIG); + /* Set the pulse width of the event */ + val &= ~(LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_MASK(event)); + /* Make sure that the target clock will be incremented each time when + * local time reaches or pass it + */ + val |= LAN8814_PTP_GENERAL_CONFIG_LTC_EVENT_SET(event, pulse_width); + val &= ~(LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event)); + /* Set the polarity high */ + val |= LAN8814_PTP_GENERAL_CONFIG_POLARITY_X(event); + lanphy_write_page_reg(phydev, 4, LAN8814_PTP_GENERAL_CONFIG, val); +} + +static void lan8814_ptp_disable_event(struct phy_device *phydev, int event) +{ + u16 val; + + /* Set target to too far in the future, effectively disabling it */ + lan8814_ptp_set_target(phydev, event, 0xFFFFFFFF, 0); + + /* And then reload once it recheas the target */ + val = lanphy_read_page_reg(phydev, 4, LAN8814_PTP_GENERAL_CONFIG); + val |= LAN8814_PTP_GENERAL_CONFIG_RELOAD_ADD_X(event); + lanphy_write_page_reg(phydev, 4, LAN8814_PTP_GENERAL_CONFIG, val); +} + +static void lan8814_ptp_perout_off(struct phy_device *phydev, int pin) +{ + u16 val; + + /* Disable gpio alternate function, + * 1: select as gpio, + * 0: select alt func + */ + val = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_EN_ADDR(pin)); + val |= LAN8814_GPIO_EN_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_EN_ADDR(pin), val); + + val = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin)); + val &= ~LAN8814_GPIO_DIR_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin), val); + + val = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_BUF_ADDR(pin)); + val &= ~LAN8814_GPIO_BUF_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_BUF_ADDR(pin), val); +} + +static void lan8814_ptp_perout_on(struct phy_device *phydev, int pin) +{ + int val; + + /* Set as gpio output */ + val = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin)); + val |= LAN8814_GPIO_DIR_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin), val); + + /* Enable gpio 0:for alternate function, 1:gpio */ + val = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_EN_ADDR(pin)); + val &= ~LAN8814_GPIO_EN_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_EN_ADDR(pin), val); + + /* Set buffer type to push pull */ + val = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_BUF_ADDR(pin)); + val |= LAN8814_GPIO_BUF_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_BUF_ADDR(pin), val); +} + +static int lan8814_ptp_perout(struct ptp_clock_info *ptpci, + struct ptp_clock_request *rq, int on) +{ + struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv, + ptp_clock_info); + struct phy_device *phydev = shared->phydev; + struct timespec64 ts_on, ts_period; + s64 on_nsec, period_nsec; + int pulse_width; + int pin, event; + + /* Reject requests with unsupported flags */ + if (rq->perout.flags & ~PTP_PEROUT_DUTY_CYCLE) + return -EOPNOTSUPP; + + mutex_lock(&shared->shared_lock); + event = rq->perout.index; + pin = ptp_find_pin(shared->ptp_clock, PTP_PF_PEROUT, event); + if (pin < 0 || pin >= LAN8814_PTP_PEROUT_NUM) { + mutex_unlock(&shared->shared_lock); + return -EBUSY; + } + + if (!on) { + lan8814_ptp_perout_off(phydev, pin); + lan8814_ptp_disable_event(phydev, event); + mutex_unlock(&shared->shared_lock); + return 0; + } + + ts_on.tv_sec = rq->perout.on.sec; + ts_on.tv_nsec = rq->perout.on.nsec; + on_nsec = timespec64_to_ns(&ts_on); + + ts_period.tv_sec = rq->perout.period.sec; + ts_period.tv_nsec = rq->perout.period.nsec; + period_nsec = timespec64_to_ns(&ts_period); + + if (period_nsec < 200) { + pr_warn_ratelimited("%s: perout period too small, minimum is 200 nsec\n", + phydev_name(phydev)); + mutex_unlock(&shared->shared_lock); + return -EOPNOTSUPP; + } + + if (on_nsec >= period_nsec) { + pr_warn_ratelimited("%s: pulse width must be smaller than period\n", + phydev_name(phydev)); + mutex_unlock(&shared->shared_lock); + return -EINVAL; + } + + switch (on_nsec) { + case 200000000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_200MS; + break; + case 100000000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100MS; + break; + case 50000000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50MS; + break; + case 10000000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10MS; + break; + case 5000000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5MS; + break; + case 1000000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1MS; + break; + case 500000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500US; + break; + case 100000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100US; + break; + case 50000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_50US; + break; + case 10000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_10US; + break; + case 5000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_5US; + break; + case 1000: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_1US; + break; + case 500: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_500NS; + break; + case 100: + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100NS; + break; + default: + pr_warn_ratelimited("%s: Use default duty cycle of 100ns\n", + phydev_name(phydev)); + pulse_width = LAN8841_PTP_GENERAL_CONFIG_LTC_EVENT_100NS; + break; + } + + /* Configure to pulse every period */ + lan8814_ptp_enable_event(phydev, event, pulse_width); + lan8814_ptp_set_target(phydev, event, rq->perout.start.sec, + rq->perout.start.nsec); + lan8814_ptp_set_reload(phydev, event, rq->perout.period.sec, + rq->perout.period.nsec); + lan8814_ptp_perout_on(phydev, pin); + mutex_unlock(&shared->shared_lock); + + return 0; +} + +static void lan8814_ptp_extts_on(struct phy_device *phydev, int pin, u32 flags) +{ + u16 tmp; + + /* Set as gpio input */ + tmp = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin)); + tmp &= ~LAN8814_GPIO_DIR_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin), tmp); + + /* Map the pin to ltc pin 0 of the capture map registers */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_GPIO_CAP_MAP_LO); + tmp |= pin; + lanphy_write_page_reg(phydev, 4, PTP_GPIO_CAP_MAP_LO, tmp); + + /* Enable capture on the edges of the ltc pin */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_GPIO_CAP_EN); + if (flags & PTP_RISING_EDGE) + tmp |= PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(0); + if (flags & PTP_FALLING_EDGE) + tmp |= PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(0); + lanphy_write_page_reg(phydev, 4, PTP_GPIO_CAP_EN, tmp); + + /* Enable interrupt top interrupt */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_COMMON_INT_ENA); + tmp |= PTP_COMMON_INT_ENA_GPIO_CAP_EN; + lanphy_write_page_reg(phydev, 4, PTP_COMMON_INT_ENA, tmp); +} + +static void lan8814_ptp_extts_off(struct phy_device *phydev, int pin) +{ + u16 tmp; + + /* Set as gpio out */ + tmp = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin)); + tmp |= LAN8814_GPIO_DIR_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_DIR_ADDR(pin), tmp); + + /* Enable alternate, 0:for alternate function, 1:gpio */ + tmp = lanphy_read_page_reg(phydev, 4, LAN8814_GPIO_EN_ADDR(pin)); + tmp &= ~LAN8814_GPIO_EN_BIT(pin); + lanphy_write_page_reg(phydev, 4, LAN8814_GPIO_EN_ADDR(pin), tmp); + + /* Clear the mapping of pin to registers 0 of the capture registers */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_GPIO_CAP_MAP_LO); + tmp &= ~GENMASK(3, 0); + lanphy_write_page_reg(phydev, 4, PTP_GPIO_CAP_MAP_LO, tmp); + + /* Disable capture on both of the edges */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_GPIO_CAP_EN); + tmp &= ~PTP_GPIO_CAP_EN_GPIO_RE_CAPTURE_ENABLE(pin); + tmp &= ~PTP_GPIO_CAP_EN_GPIO_FE_CAPTURE_ENABLE(pin); + lanphy_write_page_reg(phydev, 4, PTP_GPIO_CAP_EN, tmp); + + /* Disable interrupt top interrupt */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_COMMON_INT_ENA); + tmp &= ~PTP_COMMON_INT_ENA_GPIO_CAP_EN; + lanphy_write_page_reg(phydev, 4, PTP_COMMON_INT_ENA, tmp); +} + +static int lan8814_ptp_extts(struct ptp_clock_info *ptpci, + struct ptp_clock_request *rq, int on) +{ + struct lan8814_shared_priv *shared = container_of(ptpci, struct lan8814_shared_priv, + ptp_clock_info); + struct phy_device *phydev = shared->phydev; + int pin; + + if (rq->extts.flags & ~(PTP_ENABLE_FEATURE | + PTP_EXTTS_EDGES | + PTP_STRICT_FLAGS)) + return -EOPNOTSUPP; + + pin = ptp_find_pin(shared->ptp_clock, PTP_PF_EXTTS, + rq->extts.index); + if (pin == -1 || pin != LAN8814_PTP_EXTTS_NUM) + return -EINVAL; + + mutex_lock(&shared->shared_lock); + if (on) + lan8814_ptp_extts_on(phydev, pin, rq->extts.flags); + else + lan8814_ptp_extts_off(phydev, pin); + + mutex_unlock(&shared->shared_lock); + + return 0; +} + +static int lan8814_ptpci_enable(struct ptp_clock_info *ptpci, + struct ptp_clock_request *rq, int on) +{ + switch (rq->type) { + case PTP_CLK_REQ_PEROUT: + return lan8814_ptp_perout(ptpci, rq, on); + case PTP_CLK_REQ_EXTTS: + return lan8814_ptp_extts(ptpci, rq, on); + default: + return -EINVAL; + } +} + +static int lan8814_ptpci_verify(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + switch (func) { + case PTP_PF_NONE: + case PTP_PF_PEROUT: + /* Only pins 0 and 1 can generate perout signals. And for pin 0 + * there is only chan 0 (event A) and for pin 1 there is only + * chan 1 (event B) + */ + if (pin >= LAN8814_PTP_PEROUT_NUM || pin != chan) + return -1; + break; + case PTP_PF_EXTTS: + if (pin != LAN8814_PTP_EXTTS_NUM) + return -1; + break; + default: + return -1; + } + + return 0; +} + static bool lan8814_get_sig_tx(struct sk_buff *skb, u16 *sig) { struct ptp_header *ptp_header; @@ -3010,6 +3439,64 @@ static void lan8814_handle_ptp_interrupt(struct phy_device *phydev, u16 status) } } +static int lan8814_gpio_process_cap(struct lan8814_shared_priv *shared) +{ + struct phy_device *phydev = shared->phydev; + struct ptp_clock_event ptp_event = {0}; + unsigned long nsec; + s64 sec; + u16 tmp; + + /* This is 0 because whatever was the input pin it was mapped it to + * ltc gpio pin 0 + */ + tmp = lanphy_read_page_reg(phydev, 4, PTP_GPIO_SEL); + tmp |= PTP_GPIO_SEL_GPIO_SEL(0); + lanphy_write_page_reg(phydev, 4, PTP_GPIO_SEL, tmp); + + tmp = lanphy_read_page_reg(phydev, 4, PTP_GPIO_CAP_STS); + if (!(tmp & PTP_GPIO_CAP_STS_PTP_GPIO_RE_STS(0)) && + !(tmp & PTP_GPIO_CAP_STS_PTP_GPIO_FE_STS(0))) + return -1; + + if (tmp & BIT(0)) { + sec = lanphy_read_page_reg(phydev, 4, PTP_GPIO_RE_LTC_SEC_HI_CAP); + sec <<= 16; + sec |= lanphy_read_page_reg(phydev, 4, PTP_GPIO_RE_LTC_SEC_LO_CAP); + + nsec = lanphy_read_page_reg(phydev, 4, PTP_GPIO_RE_LTC_NS_HI_CAP) & 0x3fff; + nsec <<= 16; + nsec |= lanphy_read_page_reg(phydev, 4, PTP_GPIO_RE_LTC_NS_LO_CAP); + } else { + sec = lanphy_read_page_reg(phydev, 4, PTP_GPIO_FE_LTC_SEC_HI_CAP); + sec <<= 16; + sec |= lanphy_read_page_reg(phydev, 4, PTP_GPIO_FE_LTC_SEC_LO_CAP); + + nsec = lanphy_read_page_reg(phydev, 4, PTP_GPIO_FE_LTC_NS_HI_CAP) & 0x3fff; + nsec <<= 16; + nsec |= lanphy_read_page_reg(phydev, 4, PTP_GPIO_RE_LTC_NS_LO_CAP); + } + + ptp_event.index = 0; + ptp_event.timestamp = ktime_set(sec, nsec); + ptp_event.type = PTP_CLOCK_EXTTS; + ptp_clock_event(shared->ptp_clock, &ptp_event); + + return 0; +} + +static int lan8814_handle_gpio_interrupt(struct phy_device *phydev, u16 status) +{ + struct lan8814_shared_priv *shared = phydev->shared->priv; + int ret; + + mutex_lock(&shared->shared_lock); + ret = lan8814_gpio_process_cap(shared); + mutex_unlock(&shared->shared_lock); + + return ret; +} + static int lan8804_config_init(struct phy_device *phydev) { int val; @@ -3114,6 +3601,9 @@ static irqreturn_t lan8814_handle_interrupt(struct phy_device *phydev) ret = IRQ_HANDLED; } + if (!lan8814_handle_gpio_interrupt(phydev, irq_status)) + ret = IRQ_HANDLED; + return ret; } @@ -3210,19 +3700,39 @@ static int lan8814_ptp_probe_once(struct phy_device *phydev) /* Initialise shared lock for clock*/ mutex_init(&shared->shared_lock); + shared->pin_config = devm_kmalloc_array(&phydev->mdio.dev, + LAN8814_PTP_GPIO_NUM, + sizeof(*shared->pin_config), + GFP_KERNEL); + if (!shared->pin_config) + return -ENOMEM; + + for (int i = 0; i < LAN8814_PTP_GPIO_NUM; i++) { + struct ptp_pin_desc *ptp_pin = &shared->pin_config[i]; + + memset(ptp_pin, 0, sizeof(*ptp_pin)); + snprintf(ptp_pin->name, + sizeof(ptp_pin->name), "lan8814_ptp_pin_%02d", i); + ptp_pin->index = i; + ptp_pin->func = PTP_PF_NONE; + } + shared->ptp_clock_info.owner = THIS_MODULE; snprintf(shared->ptp_clock_info.name, 30, "%s", phydev->drv->name); shared->ptp_clock_info.max_adj = 31249999; shared->ptp_clock_info.n_alarm = 0; - shared->ptp_clock_info.n_ext_ts = 0; - shared->ptp_clock_info.n_pins = 0; + shared->ptp_clock_info.n_ext_ts = LAN8814_PTP_EXTTS_NUM; + shared->ptp_clock_info.n_pins = LAN8814_PTP_GPIO_NUM; shared->ptp_clock_info.pps = 0; - shared->ptp_clock_info.pin_config = NULL; + shared->ptp_clock_info.pin_config = shared->pin_config; + shared->ptp_clock_info.n_per_out = LAN8814_PTP_PEROUT_NUM; shared->ptp_clock_info.adjfine = lan8814_ptpci_adjfine; shared->ptp_clock_info.adjtime = lan8814_ptpci_adjtime; shared->ptp_clock_info.gettime64 = lan8814_ptpci_gettime64; shared->ptp_clock_info.settime64 = lan8814_ptpci_settime64; shared->ptp_clock_info.getcrosststamp = NULL; + shared->ptp_clock_info.enable = lan8814_ptpci_enable; + shared->ptp_clock_info.verify = lan8814_ptpci_verify; shared->ptp_clock = ptp_clock_register(&shared->ptp_clock_info, &phydev->mdio.dev); @@ -3247,6 +3757,9 @@ static int lan8814_ptp_probe_once(struct phy_device *phydev) lanphy_write_page_reg(phydev, 4, PTP_OPERATING_MODE, PTP_OPERATING_MODE_STANDALONE_); + /* Enable ptp to run LTC clock for ptp and gpio 1PPS operation */ + lanphy_write_page_reg(phydev, 4, PTP_CMD_CTL, PTP_CMD_CTL_PTP_ENABLE_); + return 0; } @@ -4676,7 +5189,8 @@ static int lan8841_suspend(struct phy_device *phydev) struct kszphy_priv *priv = phydev->priv; struct kszphy_ptp_priv *ptp_priv = &priv->ptp_priv; - ptp_cancel_worker_sync(ptp_priv->ptp_clock); + if (ptp_priv->ptp_clock) + ptp_cancel_worker_sync(ptp_priv->ptp_clock); return genphy_suspend(phydev); } diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 503fd7c40523..994471fad833 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1042,6 +1042,21 @@ static void phylink_pcs_poll_start(struct phylink *pl) mod_timer(&pl->link_poll, jiffies + HZ); } +int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs) +{ + int ret = 0; + + /* Signal to PCS driver that MAC requires RX clock for init */ + if (pl->config->mac_requires_rxc) + pcs->rxc_always_on = true; + + if (pcs->ops->pcs_pre_init) + ret = pcs->ops->pcs_pre_init(pcs); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_pcs_pre_init); + static void phylink_mac_config(struct phylink *pl, const struct phylink_link_state *state) { @@ -1823,6 +1838,9 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy, interfaces); } + phylink_dbg(pl, "PHY %s doesn't supply possible interfaces\n", + phydev_name(phy)); + /* Check whether we would use rate matching for the proposed interface * mode. */ @@ -1923,6 +1941,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, phy_interface_t interface) { + u32 flags = 0; + if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED || (pl->cfg_link_an_mode == MLO_AN_INBAND && phy_interface_mode_is_8023z(interface) && !pl->sfp_bus))) @@ -1931,7 +1951,10 @@ static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, if (pl->phydev) return -EBUSY; - return phy_attach_direct(pl->netdev, phy, 0, interface); + if (pl->config->mac_requires_rxc) + flags |= PHY_F_RXC_ALWAYS_ON; + + return phy_attach_direct(pl->netdev, phy, flags, interface); } /** @@ -2034,6 +2057,9 @@ int phylink_fwnode_phy_connect(struct phylink *pl, pl->link_config.interface = pl->link_interface; } + if (pl->config->mac_requires_rxc) + flags |= PHY_F_RXC_ALWAYS_ON; + ret = phy_attach_direct(pl->netdev, phy_dev, flags, pl->link_interface); phy_device_free(phy_dev); diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index e79657f76bea..c8f83e5f78ab 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -426,7 +426,8 @@ static int at803x_hibernation_mode_config(struct phy_device *phydev) /* The default after hardware reset is hibernation mode enabled. After * software reset, the value is retained. */ - if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE)) + if (!(priv->flags & AT803X_DISABLE_HIBERNATION_MODE) && + !(phydev->dev_flags & PHY_F_RXC_ALWAYS_ON)) return 0; return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL, diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c index 1fa70427b2a2..7ab41f95dae5 100644 --- a/drivers/net/phy/realtek.c +++ b/drivers/net/phy/realtek.c @@ -54,6 +54,25 @@ RTL8201F_ISR_LINK) #define RTL8201F_IER 0x13 +#define RTL822X_VND1_SERDES_OPTION 0x697a +#define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0) +#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0 +#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX 2 + +#define RTL822X_VND1_SERDES_CTRL3 0x7580 +#define RTL822X_VND1_SERDES_CTRL3_MODE_MASK GENMASK(5, 0) +#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_VND2_GBCR 0xa412 + +#define RTL822X_VND2_GANLPAR 0xa414 + +#define RTL822X_VND2_PHYSR 0xa434 + #define RTL8366RB_POWER_SAVE 0x15 #define RTL8366RB_POWER_SAVE_ON BIT(12) @@ -64,6 +83,9 @@ #define RTL_GENERIC_PHYID 0x001cc800 #define RTL_8211FVD_PHYID 0x001cc878 +#define RTL_8221B_VB_CG 0x001cc849 +#define RTL_8221B_VN_CG 0x001cc84a +#define RTL_8251B 0x001cc862 MODULE_DESCRIPTION("Realtek PHY driver"); MODULE_AUTHOR("Johnson Leung"); @@ -531,17 +553,8 @@ static int rtl8366rb_config_init(struct phy_device *phydev) } /* get actual speed to cover the downshift case */ -static int rtlgen_get_speed(struct phy_device *phydev) +static void rtlgen_decode_speed(struct phy_device *phydev, int val) { - int val; - - if (!phydev->link) - return 0; - - val = phy_read_paged(phydev, 0xa43, 0x12); - if (val < 0) - return val; - switch (val & RTLGEN_SPEED_MASK) { case 0x0000: phydev->speed = SPEED_10; @@ -564,19 +577,26 @@ static int rtlgen_get_speed(struct phy_device *phydev) default: break; } - - return 0; } static int rtlgen_read_status(struct phy_device *phydev) { - int ret; + int ret, val; ret = genphy_read_status(phydev); if (ret < 0) return ret; - return rtlgen_get_speed(phydev); + if (!phydev->link) + return 0; + + val = phy_read_paged(phydev, 0xa43, 0x12); + if (val < 0) + return val; + + rtlgen_decode_speed(phydev, val); + + return 0; } static int rtlgen_read_mmd(struct phy_device *phydev, int devnum, u16 regnum) @@ -659,6 +679,84 @@ static int rtl822x_write_mmd(struct phy_device *phydev, int devnum, u16 regnum, return ret; } +static int rtl822xb_config_init(struct phy_device *phydev) +{ + bool has_2500, has_sgmii; + u16 mode; + int ret; + + has_2500 = test_bit(PHY_INTERFACE_MODE_2500BASEX, + phydev->host_interfaces) || + phydev->interface == PHY_INTERFACE_MODE_2500BASEX; + + has_sgmii = test_bit(PHY_INTERFACE_MODE_SGMII, + phydev->host_interfaces) || + phydev->interface == PHY_INTERFACE_MODE_SGMII; + + /* fill in possible interfaces */ + __assign_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces, + has_2500); + __assign_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces, + has_sgmii); + + if (!has_2500 && !has_sgmii) + return 0; + + /* determine SerDes option mode */ + if (has_2500 && !has_sgmii) { + mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX; + phydev->rate_matching = RATE_MATCH_PAUSE; + } else { + mode = RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII; + phydev->rate_matching = RATE_MATCH_NONE; + } + + /* the following sequence with magic numbers sets up the SerDes + * option mode + */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x75f3, 0); + if (ret < 0) + return ret; + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND1, + RTL822X_VND1_SERDES_OPTION, + RTL822X_VND1_SERDES_OPTION_MODE_MASK, + mode); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6a04, 0x0503); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f10, 0xd455); + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x6f11, 0x8020); +} + +static int rtl822xb_get_rate_matching(struct phy_device *phydev, + phy_interface_t iface) +{ + int val; + + /* Only rate matching at 2500base-x */ + if (iface != PHY_INTERFACE_MODE_2500BASEX) + return RATE_MATCH_NONE; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_OPTION); + if (val < 0) + return val; + + if ((val & RTL822X_VND1_SERDES_OPTION_MODE_MASK) == + RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX) + return RATE_MATCH_PAUSE; + + /* RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII */ + return RATE_MATCH_NONE; +} + static int rtl822x_get_features(struct phy_device *phydev) { int val; @@ -695,10 +793,30 @@ static int rtl822x_config_aneg(struct phy_device *phydev) return __genphy_config_aneg(phydev, ret); } -static int rtl822x_read_status(struct phy_device *phydev) +static void rtl822xb_update_interface(struct phy_device *phydev) { - int ret; + int val; + + if (!phydev->link) + return; + + /* Change interface according to serdes mode */ + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, RTL822X_VND1_SERDES_CTRL3); + if (val < 0) + return; + switch (val & RTL822X_VND1_SERDES_CTRL3_MODE_MASK) { + case RTL822X_VND1_SERDES_CTRL3_MODE_2500BASEX: + phydev->interface = PHY_INTERFACE_MODE_2500BASEX; + break; + case RTL822X_VND1_SERDES_CTRL3_MODE_SGMII: + phydev->interface = PHY_INTERFACE_MODE_SGMII; + break; + } +} + +static int rtl822x_read_status(struct phy_device *phydev) +{ if (phydev->autoneg == AUTONEG_ENABLE) { int lpadv = phy_read_paged(phydev, 0xa5d, 0x13); @@ -709,11 +827,99 @@ static int rtl822x_read_status(struct phy_device *phydev) lpadv); } - ret = genphy_read_status(phydev); + return rtlgen_read_status(phydev); +} + +static int rtl822xb_read_status(struct phy_device *phydev) +{ + int ret; + + ret = rtl822x_read_status(phydev); + if (ret < 0) + return ret; + + rtl822xb_update_interface(phydev); + + return 0; +} + +static int rtl822x_c45_get_features(struct phy_device *phydev) +{ + linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, + phydev->supported); + + return genphy_c45_pma_read_abilities(phydev); +} + +static int rtl822x_c45_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + int ret, val; + + if (phydev->autoneg == AUTONEG_DISABLE) + return genphy_c45_pma_setup_forced(phydev); + + ret = genphy_c45_an_config_aneg(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + val = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); + + /* Vendor register as C45 has no standardized support for 1000BaseT */ + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2, RTL822X_VND2_GBCR, + ADVERTISE_1000FULL, val); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return genphy_c45_check_and_restart_aneg(phydev, changed); +} + +static int rtl822x_c45_read_status(struct phy_device *phydev) +{ + int ret, val; + + ret = genphy_c45_read_status(phydev); + if (ret < 0) + return ret; + + /* Vendor register as C45 has no standardized support for 1000BaseT */ + if (phydev->autoneg == AUTONEG_ENABLE) { + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, + RTL822X_VND2_GANLPAR); + if (val < 0) + return val; + + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val); + } + + if (!phydev->link) + return 0; + + /* Read actual speed from vendor register. */ + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_PHYSR); + if (val < 0) + return val; + + rtlgen_decode_speed(phydev, val); + + return 0; +} + +static int rtl822xb_c45_read_status(struct phy_device *phydev) +{ + int ret; + + ret = rtl822x_c45_read_status(phydev); if (ret < 0) return ret; - return rtlgen_get_speed(phydev); + rtl822xb_update_interface(phydev); + + return 0; } static bool rtlgen_supports_2_5gbps(struct phy_device *phydev) @@ -739,6 +945,35 @@ static int rtl8226_match_phy_device(struct phy_device *phydev) rtlgen_supports_2_5gbps(phydev); } +static int rtlgen_is_c45_match(struct phy_device *phydev, unsigned int id, + bool is_c45) +{ + if (phydev->is_c45) + return is_c45 && (id == phydev->c45_ids.device_ids[1]); + else + return !is_c45 && (id == phydev->phy_id); +} + +static int rtl8221b_vb_cg_c22_match_phy_device(struct phy_device *phydev) +{ + return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, false); +} + +static int rtl8221b_vb_cg_c45_match_phy_device(struct phy_device *phydev) +{ + return rtlgen_is_c45_match(phydev, RTL_8221B_VB_CG, true); +} + +static int rtl8221b_vn_cg_c22_match_phy_device(struct phy_device *phydev) +{ + return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, false); +} + +static int rtl8221b_vn_cg_c45_match_phy_device(struct phy_device *phydev) +{ + return rtlgen_is_c45_match(phydev, RTL_8221B_VN_CG, true); +} + static int rtlgen_resume(struct phy_device *phydev) { int ret = genphy_resume(phydev); @@ -749,6 +984,15 @@ static int rtlgen_resume(struct phy_device *phydev) return ret; } +static int rtlgen_c45_resume(struct phy_device *phydev) +{ + int ret = genphy_c45_pma_resume(phydev); + + msleep(20); + + return ret; +} + static int rtl9000a_config_init(struct phy_device *phydev) { phydev->autoneg = AUTONEG_DISABLE; @@ -988,7 +1232,9 @@ static struct phy_driver realtek_drvs[] = { .name = "RTL8226B_RTL8221B 2.5Gbps PHY", .get_features = rtl822x_get_features, .config_aneg = rtl822x_config_aneg, - .read_status = rtl822x_read_status, + .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, @@ -1010,32 +1256,58 @@ static struct phy_driver realtek_drvs[] = { .name = "RTL8226B-CG_RTL8221B-CG 2.5Gbps PHY", .get_features = rtl822x_get_features, .config_aneg = rtl822x_config_aneg, - .read_status = rtl822x_read_status, + .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, }, { - PHY_ID_MATCH_EXACT(0x001cc849), - .name = "RTL8221B-VB-CG 2.5Gbps PHY", + .match_phy_device = rtl8221b_vb_cg_c22_match_phy_device, + .name = "RTL8221B-VB-CG 2.5Gbps PHY (C22)", .get_features = rtl822x_get_features, .config_aneg = rtl822x_config_aneg, - .read_status = rtl822x_read_status, + .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, }, { - PHY_ID_MATCH_EXACT(0x001cc84a), - .name = "RTL8221B-VM-CG 2.5Gbps PHY", + .match_phy_device = rtl8221b_vb_cg_c45_match_phy_device, + .name = "RTL8221B-VB-CG 2.5Gbps PHY (C45)", + .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_vn_cg_c22_match_phy_device, + .name = "RTL8221B-VM-CG 2.5Gbps PHY (C22)", .get_features = rtl822x_get_features, .config_aneg = rtl822x_config_aneg, - .read_status = rtl822x_read_status, + .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_vn_cg_c45_match_phy_device, + .name = "RTL8221B-VN-CG 2.5Gbps PHY (C45)", + .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, + }, { PHY_ID_MATCH_EXACT(0x001cc862), .name = "RTL8251B 5Gbps PHY", .get_features = rtl822x_get_features, diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c index db39dec7f247..2f44fc51848f 100644 --- a/drivers/net/phy/sfp-bus.c +++ b/drivers/net/phy/sfp-bus.c @@ -355,7 +355,7 @@ EXPORT_SYMBOL_GPL(sfp_parse_support); * modes mask. */ phy_interface_t sfp_select_interface(struct sfp_bus *bus, - unsigned long *link_modes) + const unsigned long *link_modes) { if (phylink_test(link_modes, 25000baseCR_Full) || phylink_test(link_modes, 25000baseKR_Full) || @@ -373,7 +373,8 @@ phy_interface_t sfp_select_interface(struct sfp_bus *bus, if (phylink_test(link_modes, 5000baseT_Full)) return PHY_INTERFACE_MODE_5GBASER; - if (phylink_test(link_modes, 2500baseX_Full)) + if (phylink_test(link_modes, 2500baseX_Full) || + phylink_test(link_modes, 2500baseT_Full)) return PHY_INTERFACE_MODE_2500BASEX; if (phylink_test(link_modes, 1000baseT_Half) || diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index f75c9eb3958e..3f9cbd797fd6 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -385,18 +385,23 @@ static void sfp_fixup_rollball(struct sfp *sfp) sfp->phy_t_retry = msecs_to_jiffies(1000); } -static void sfp_fixup_fs_10gt(struct sfp *sfp) +static void sfp_fixup_fs_2_5gt(struct sfp *sfp) { - sfp_fixup_10gbaset_30m(sfp); sfp_fixup_rollball(sfp); - /* The RollBall fixup is not enough for FS modules, the AQR chip inside + /* The RollBall fixup is not enough for FS modules, the PHY chip inside * them does not return 0xffff for PHY ID registers in all MMDs for the * while initializing. They need a 4 second wait before accessing PHY. */ sfp->module_t_wait = msecs_to_jiffies(4000); } +static void sfp_fixup_fs_10gt(struct sfp *sfp) +{ + sfp_fixup_10gbaset_30m(sfp); + sfp_fixup_fs_2_5gt(sfp); +} + static void sfp_fixup_halny_gsfp(struct sfp *sfp) { /* Ignore the TX_FAULT and LOS signals on this module. @@ -468,10 +473,15 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex, sfp_fixup_nokia), - // Fiberstore SFP-10G-T doesn't identify as copper, and uses the - // Rollball protocol to talk to the PHY. + // Fiberstore SFP-10G-T doesn't identify as copper, uses the Rollball + // protocol to talk to the PHY and needs 4 sec wait before probing the + // PHY. SFP_QUIRK_F("FS", "SFP-10G-T", sfp_fixup_fs_10gt), + // Fiberstore SFP-2.5G-T uses Rollball protocol to talk to the PHY and + // needs 4 sec wait before probing the PHY. + SFP_QUIRK_F("FS", "SFP-2.5G-T", sfp_fixup_fs_2_5gt), + // Fiberstore GPON-ONU-34-20BI can operate at 2500base-X, but report 1.2GBd // NRZ in their EEPROM SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex, @@ -488,9 +498,6 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex, sfp_fixup_ignore_tx_fault), - // FS 2.5G Base-T - SFP_QUIRK_M("FS", "SFP-2.5G-T", sfp_quirk_oem_2_5g), - // Lantech 8330-262D-E can operate at 2500base-X, but incorrectly report // 2500MBd NRZ in their EEPROM SFP_QUIRK_M("Lantech", "8330-262D-E", sfp_quirk_2500basex), @@ -502,10 +509,14 @@ static const struct sfp_quirk sfp_quirks[] = { SFP_QUIRK_F("Walsun", "HXSX-ATRC-1", sfp_fixup_fs_10gt), SFP_QUIRK_F("Walsun", "HXSX-ATRI-1", sfp_fixup_fs_10gt), + // OEM SFP-GE-T is a 1000Base-T module with broken TX_FAULT indicator + SFP_QUIRK_F("OEM", "SFP-GE-T", sfp_fixup_ignore_tx_fault), + SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc), SFP_QUIRK_M("OEM", "SFP-2.5G-T", sfp_quirk_oem_2_5g), SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc), SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc), + SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball), SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball), SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball), }; |