diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2025-07-30 08:58:55 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2025-07-30 08:58:55 -0700 |
commit | 8be4d31cb8aaeea27bde4b7ddb26e28a89062ebf (patch) | |
tree | fec3039a08284cd87f4ec9c3bea5b5a439f1859f /drivers/net/dsa/microchip | |
parent | 4b290aae788e06561754b28c6842e4080957d3f7 (diff) | |
parent | fa582ca7e187a15e772e6a72fe035f649b387a60 (diff) |
Merge tag 'net-next-6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next
Pull networking updates from Jakub Kicinski:
"Core & protocols:
- Wrap datapath globals into net_aligned_data, to avoid false sharing
- Preserve MSG_ZEROCOPY in forwarding (e.g. out of a container)
- Add SO_INQ and SCM_INQ support to AF_UNIX
- Add SIOCINQ support to AF_VSOCK
- Add TCP_MAXSEG sockopt to MPTCP
- Add IPv6 force_forwarding sysctl to enable forwarding per interface
- Make TCP validation of whether packet fully fits in the receive
window and the rcv_buf more strict. With increased use of HW
aggregation a single "packet" can be multiple 100s of kB
- Add MSG_MORE flag to optimize large TCP transmissions via sockmap,
improves latency up to 33% for sockmap users
- Convert TCP send queue handling from tasklet to BH workque
- Improve BPF iteration over TCP sockets to see each socket exactly
once
- Remove obsolete and unused TCP RFC3517/RFC6675 loss recovery code
- Support enabling kernel threads for NAPI processing on per-NAPI
instance basis rather than a whole device. Fully stop the kernel
NAPI thread when threaded NAPI gets disabled. Previously thread
would stick around until ifdown due to tricky synchronization
- Allow multicast routing to take effect on locally-generated packets
- Add output interface argument for End.X in segment routing
- MCTP: add support for gateway routing, improve bind() handling
- Don't require rtnl_lock when fetching an IPv6 neighbor over Netlink
- Add a new neighbor flag ("extern_valid"), which cedes refresh
responsibilities to userspace. This is needed for EVPN multi-homing
where a neighbor entry for a multi-homed host needs to be synced
across all the VTEPs among which the host is multi-homed
- Support NUD_PERMANENT for proxy neighbor entries
- Add a new queuing discipline for IETF RFC9332 DualQ Coupled AQM
- Add sequence numbers to netconsole messages. Unregister
netconsole's console when all net targets are removed. Code
refactoring. Add a number of selftests
- Align IPSec inbound SA lookup to RFC 4301. Only SPI and protocol
should be used for an inbound SA lookup
- Support inspecting ref_tracker state via DebugFS
- Don't force bonding advertisement frames tx to ~333 ms boundaries.
Add broadcast_neighbor option to send ARP/ND on all bonded links
- Allow providing upcall pid for the 'execute' command in openvswitch
- Remove DCCP support from Netfilter's conntrack
- Disallow multiple packet duplications in the queuing layer
- Prevent use of deprecated iptables code on PREEMPT_RT
Driver API:
- Support RSS and hashing configuration over ethtool Netlink
- Add dedicated ethtool callbacks for getting and setting hashing
fields
- Add support for power budget evaluation strategy in PSE /
Power-over-Ethernet. Generate Netlink events for overcurrent etc
- Support DPLL phase offset monitoring across all device inputs.
Support providing clock reference and SYNC over separate DPLL
inputs
- Support traffic classes in devlink rate API for bandwidth
management
- Remove rtnl_lock dependency from UDP tunnel port configuration
Device drivers:
- Add a new Broadcom driver for 800G Ethernet (bnge)
- Add a standalone driver for Microchip ZL3073x DPLL
- Remove IBM's NETIUCV device driver
- Ethernet high-speed NICs:
- Broadcom (bnxt):
- support zero-copy Tx of DMABUF memory
- take page size into account for page pool recycling rings
- Intel (100G, ice, idpf):
- idpf: XDP and AF_XDP support preparations
- idpf: add flow steering
- add link_down_events statistic
- clean up the TSPLL code
- preparations for live VM migration
- nVidia/Mellanox:
- support zero-copy Rx/Tx interfaces (DMABUF and io_uring)
- optimize context memory usage for matchers
- expose serial numbers in devlink info
- support PCIe congestion metrics
- Meta (fbnic):
- add 25G, 50G, and 100G link modes to phylink
- support dumping FW logs
- Marvell/Cavium:
- support for CN20K generation of the Octeon chips
- Amazon:
- add HW clock (without timestamping, just hypervisor time access)
- Ethernet virtual:
- VirtIO net:
- support segmentation of UDP-tunnel-encapsulated packets
- Google (gve):
- support packet timestamping and clock synchronization
- Microsoft vNIC:
- add handler for device-originated servicing events
- allow dynamic MSI-X vector allocation
- support Tx bandwidth clamping
- Ethernet NICs consumer, and embedded:
- AMD:
- amd-xgbe: hardware timestamping and PTP clock support
- Broadcom integrated MACs (bcmgenet, bcmasp):
- use napi_complete_done() return value to support NAPI polling
- add support for re-starting auto-negotiation
- Broadcom switches (b53):
- support BCM5325 switches
- add bcm63xx EPHY power control
- Synopsys (stmmac):
- lots of code refactoring and cleanups
- TI:
- icssg-prueth: read firmware-names from device tree
- icssg: PRP offload support
- Microchip:
- lan78xx: convert to PHYLINK for improved PHY and MAC management
- ksz: add KSZ8463 switch support
- Intel:
- support similar queue priority scheme in multi-queue and
time-sensitive networking (taprio)
- support packet pre-emption in both
- RealTek (r8169):
- enable EEE at 5Gbps on RTL8126
- Airoha:
- add PPPoE offload support
- MDIO bus controller for Airoha AN7583
- Ethernet PHYs:
- support for the IPQ5018 internal GE PHY
- micrel KSZ9477 switch-integrated PHYs:
- add MDI/MDI-X control support
- add RX error counters
- add cable test support
- add Signal Quality Indicator (SQI) reporting
- dp83tg720: improve reset handling and reduce link recovery time
- support bcm54811 (and its MII-Lite interface type)
- air_en8811h: support resume/suspend
- support PHY counters for QCA807x and QCA808x
- support WoL for QCA807x
- CAN drivers:
- rcar_canfd: support for Transceiver Delay Compensation
- kvaser: report FW versions via devlink dev info
- WiFi:
- extended regulatory info support (6 GHz)
- add statistics and beacon monitor for Multi-Link Operation (MLO)
- support S1G aggregation, improve S1G support
- add Radio Measurement action fields
- support per-radio RTS threshold
- some work around how FIPS affects wifi, which was wrong (RC4 is
used by TKIP, not only WEP)
- improvements for unsolicited probe response handling
- WiFi drivers:
- RealTek (rtw88):
- IBSS mode for SDIO devices
- RealTek (rtw89):
- BT coexistence for MLO/WiFi7
- concurrent station + P2P support
- support for USB devices RTL8851BU/RTL8852BU
- Intel (iwlwifi):
- use embedded PNVM in (to be released) FW images to fix
compatibility issues
- many cleanups (unused FW APIs, PCIe code, WoWLAN)
- some FIPS interoperability
- MediaTek (mt76):
- firmware recovery improvements
- more MLO work
- Qualcomm/Atheros (ath12k):
- fix scan on multi-radio devices
- more EHT/Wi-Fi 7 features
- encapsulation/decapsulation offload
- Broadcom (brcm80211):
- support SDIO 43751 device
- Bluetooth:
- hci_event: add support for handling LE BIG Sync Lost event
- ISO: add socket option to report packet seqnum via CMSG
- ISO: support SCM_TIMESTAMPING for ISO TS
- Bluetooth drivers:
- intel_pcie: support Function Level Reset
- nxpuart: add support for 4M baudrate
- nxpuart: implement powerup sequence, reset, FW dump, and FW loading"
* tag 'net-next-6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1742 commits)
dpll: zl3073x: Fix build failure
selftests: bpf: fix legacy netfilter options
ipv6: annotate data-races around rt->fib6_nsiblings
ipv6: fix possible infinite loop in fib6_info_uses_dev()
ipv6: prevent infinite loop in rt6_nlmsg_size()
ipv6: add a retry logic in net6_rt_notify()
vrf: Drop existing dst reference in vrf_ip6_input_dst
net/sched: taprio: align entry index attr validation with mqprio
net: fsl_pq_mdio: use dev_err_probe
selftests: rtnetlink.sh: remove esp4_offload after test
vsock: remove unnecessary null check in vsock_getname()
igb: xsk: solve negative overflow of nb_pkts in zerocopy mode
stmmac: xsk: fix negative overflow of budget in zerocopy mode
dt-bindings: ieee802154: Convert at86rf230.txt yaml format
net: dsa: microchip: Disable PTP function of KSZ8463
net: dsa: microchip: Setup fiber ports for KSZ8463
net: dsa: microchip: Write switch MAC address differently for KSZ8463
net: dsa: microchip: Use different registers for KSZ8463
net: dsa: microchip: Add KSZ8463 switch support to KSZ DSA driver
dt-bindings: net: dsa: microchip: Add KSZ8463 switch support
...
Diffstat (limited to 'drivers/net/dsa/microchip')
-rw-r--r-- | drivers/net/dsa/microchip/ksz8.c | 191 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz8.h | 4 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz8_reg.h | 53 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_common.c | 163 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_common.h | 37 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_dcb.c | 10 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_ptp.c | 4 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_spi.c | 104 |
8 files changed, 529 insertions, 37 deletions
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c index be433b4e2b1c..76e490070e9c 100644 --- a/drivers/net/dsa/microchip/ksz8.c +++ b/drivers/net/dsa/microchip/ksz8.c @@ -3,6 +3,7 @@ * Microchip KSZ8XXX series switch driver * * It supports the following switches: + * - KSZ8463 * - KSZ8863, KSZ8873 aka KSZ88X3 * - KSZ8895, KSZ8864 aka KSZ8895 family * - KSZ8794, KSZ8795, KSZ8765 aka KSZ87XX @@ -41,7 +42,8 @@ static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, bool set) { - regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset), + regmap_update_bits(ksz_regmap_8(dev), + dev->dev_ops->get_port_addr(port, offset), bits, set ? bits : 0); } @@ -140,6 +142,11 @@ int ksz8_reset_switch(struct ksz_device *dev) KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true); ksz_cfg(dev, KSZ8863_REG_SW_RESET, KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false); + } else if (ksz_is_ksz8463(dev)) { + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET, true); + ksz_cfg(dev, KSZ8463_REG_SW_RESET, + KSZ8463_GLOBAL_SOFTWARE_RESET, false); } else { /* reset switch */ ksz_write8(dev, REG_POWER_MANAGEMENT_1, @@ -194,6 +201,7 @@ int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu) case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return ksz8795_change_mtu(dev, frame_size); + case KSZ8463_CHIP_ID: case KSZ88X3_CHIP_ID: case KSZ8864_CHIP_ID: case KSZ8895_CHIP_ID: @@ -227,6 +235,11 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues) WEIGHTED_FAIR_QUEUE_ENABLE); if (ret) return ret; + } else if (ksz_is_ksz8463(dev)) { + mask_4q = KSZ8873_PORT_4QUEUE_SPLIT_EN; + mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN; + reg_4q = P1CR1; + reg_2q = P1CR1 + 1; } else { mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN; mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN; @@ -371,6 +384,9 @@ static void ksz8863_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, addr -= dev->info->reg_mib_cnt; ctrl_addr = addr ? KSZ8863_MIB_PACKET_DROPPED_TX_0 : KSZ8863_MIB_PACKET_DROPPED_RX_0; + if (ksz_is_8895_family(dev) && + ctrl_addr == KSZ8863_MIB_PACKET_DROPPED_RX_0) + ctrl_addr = KSZ8895_MIB_PACKET_DROPPED_RX_0; ctrl_addr += port; ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ); @@ -1265,12 +1281,15 @@ int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val) void ksz8_cfg_port_member(struct ksz_device *dev, int port, u8 member) { + int offset = P_MIRROR_CTRL; u8 data; - ksz_pread8(dev, port, P_MIRROR_CTRL, &data); - data &= ~PORT_VLAN_MEMBERSHIP; + if (ksz_is_ksz8463(dev)) + offset = P1CR2; + ksz_pread8(dev, port, offset, &data); + data &= ~dev->port_mask; data |= (member & dev->port_mask); - ksz_pwrite8(dev, port, P_MIRROR_CTRL, data); + ksz_pwrite8(dev, port, offset, data); } void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port) @@ -1278,6 +1297,8 @@ void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port) u8 learn[DSA_MAX_PORTS]; int first, index, cnt; const u16 *regs; + int reg = S_FLUSH_TABLE_CTRL; + int mask = SW_FLUSH_DYN_MAC_TABLE; regs = dev->info->regs; @@ -1295,7 +1316,11 @@ void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port) ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index] | PORT_LEARN_DISABLE); } - ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true); + if (ksz_is_ksz8463(dev)) { + reg = KSZ8463_FLUSH_TABLE_CTRL; + mask = KSZ8463_FLUSH_DYN_MAC_TABLE; + } + ksz_cfg(dev, reg, mask, true); for (index = first; index < cnt; index++) { if (!(learn[index] & PORT_LEARN_DISABLE)) ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index]); @@ -1434,7 +1459,7 @@ int ksz8_fdb_del(struct ksz_device *dev, int port, const unsigned char *addr, int ksz8_port_vlan_filtering(struct ksz_device *dev, int port, bool flag, struct netlink_ext_ack *extack) { - if (ksz_is_ksz88x3(dev)) + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return -ENOTSUPP; /* Discard packets with VID not enabled on the switch */ @@ -1450,9 +1475,12 @@ int ksz8_port_vlan_filtering(struct ksz_device *dev, int port, bool flag, static void ksz8_port_enable_pvid(struct ksz_device *dev, int port, bool state) { - if (ksz_is_ksz88x3(dev)) { - ksz_cfg(dev, REG_SW_INSERT_SRC_PVID, - 0x03 << (4 - 2 * port), state); + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) { + int reg = REG_SW_INSERT_SRC_PVID; + + if (ksz_is_ksz8463(dev)) + reg = KSZ8463_REG_SW_CTRL_9; + ksz_cfg(dev, reg, 0x03 << (4 - 2 * port), state); } else { ksz_pwrite8(dev, port, REG_PORT_CTRL_12, state ? 0x0f : 0x00); } @@ -1467,7 +1495,7 @@ int ksz8_port_vlan_add(struct ksz_device *dev, int port, u16 data, new_pvid = 0; u8 fid, member, valid; - if (ksz_is_ksz88x3(dev)) + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return -ENOTSUPP; /* If a VLAN is added with untagged flag different from the @@ -1536,7 +1564,7 @@ int ksz8_port_vlan_del(struct ksz_device *dev, int port, u16 data, pvid; u8 fid, member, valid; - if (ksz_is_ksz88x3(dev)) + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return -ENOTSUPP; ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid); @@ -1566,19 +1594,23 @@ int ksz8_port_mirror_add(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress, struct netlink_ext_ack *extack) { + int offset = P_MIRROR_CTRL; + + if (ksz_is_ksz8463(dev)) + offset = P1CR2; if (ingress) { - ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true); + ksz_port_cfg(dev, port, offset, PORT_MIRROR_RX, true); dev->mirror_rx |= BIT(port); } else { - ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true); + ksz_port_cfg(dev, port, offset, PORT_MIRROR_TX, true); dev->mirror_tx |= BIT(port); } - ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); + ksz_port_cfg(dev, port, offset, PORT_MIRROR_SNIFFER, false); /* configure mirror port */ if (dev->mirror_rx || dev->mirror_tx) - ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, + ksz_port_cfg(dev, mirror->to_local_port, offset, PORT_MIRROR_SNIFFER, true); return 0; @@ -1587,20 +1619,23 @@ int ksz8_port_mirror_add(struct ksz_device *dev, int port, void ksz8_port_mirror_del(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror) { + int offset = P_MIRROR_CTRL; u8 data; + if (ksz_is_ksz8463(dev)) + offset = P1CR2; if (mirror->ingress) { - ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false); + ksz_port_cfg(dev, port, offset, PORT_MIRROR_RX, false); dev->mirror_rx &= ~BIT(port); } else { - ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false); + ksz_port_cfg(dev, port, offset, PORT_MIRROR_TX, false); dev->mirror_tx &= ~BIT(port); } - ksz_pread8(dev, port, P_MIRROR_CTRL, &data); + ksz_pread8(dev, port, offset, &data); if (!dev->mirror_rx && !dev->mirror_tx) - ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, + ksz_port_cfg(dev, mirror->to_local_port, offset, PORT_MIRROR_SNIFFER, false); } @@ -1625,17 +1660,24 @@ void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port) const u16 *regs = dev->info->regs; struct dsa_switch *ds = dev->ds; const u32 *masks; + int offset; u8 member; masks = dev->info->masks; /* enable broadcast storm limit */ - ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); + offset = P_BCAST_STORM_CTRL; + if (ksz_is_ksz8463(dev)) + offset = P1CR1; + ksz_port_cfg(dev, port, offset, PORT_BROADCAST_STORM, true); ksz8_port_queue_split(dev, port, dev->info->num_tx_queues); /* replace priority */ - ksz_port_cfg(dev, port, P_802_1P_CTRL, + offset = P_802_1P_CTRL; + if (ksz_is_ksz8463(dev)) + offset = P1CR2; + ksz_port_cfg(dev, port, offset, masks[PORT_802_1P_REMAPPING], false); if (cpu_port) @@ -1675,6 +1717,7 @@ void ksz8_config_cpu_port(struct dsa_switch *ds) const u32 *masks; const u16 *regs; u8 remote; + u8 fiber_ports = 0; int i; masks = dev->info->masks; @@ -1705,6 +1748,32 @@ void ksz8_config_cpu_port(struct dsa_switch *ds) else ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, false); + if (p->fiber) + fiber_ports |= (1 << i); + } + if (ksz_is_ksz8463(dev)) { + /* Setup fiber ports. */ + if (fiber_ports) { + fiber_ports &= 3; + regmap_update_bits(ksz_regmap_16(dev), + KSZ8463_REG_CFG_CTRL, + fiber_ports << PORT_COPPER_MODE_S, + 0); + regmap_update_bits(ksz_regmap_16(dev), + KSZ8463_REG_DSP_CTRL_6, + COPPER_RECEIVE_ADJUSTMENT, 0); + } + + /* Turn off PTP function as the switch's proprietary way of + * handling timestamp is not supported in current Linux PTP + * stack implementation. + */ + regmap_update_bits(ksz_regmap_16(dev), + KSZ8463_PTP_MSG_CONF1, + PTP_ENABLE, 0); + regmap_update_bits(ksz_regmap_16(dev), + KSZ8463_PTP_CLK_CTRL, + PTP_CLK_ENABLE, 0); } } @@ -1901,7 +1970,7 @@ int ksz8_setup(struct dsa_switch *ds) ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); - if (!ksz_is_ksz88x3(dev)) + if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev)) ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true); for (i = 0; i < (dev->info->num_vlans / 4); i++) @@ -1947,6 +2016,84 @@ u32 ksz8_get_port_addr(int port, int offset) return PORT_CTRL_ADDR(port, offset); } +u32 ksz8463_get_port_addr(int port, int offset) +{ + return offset + 0x18 * port; +} + +static u16 ksz8463_get_phy_addr(u16 phy, u16 reg, u16 offset) +{ + return offset + reg * 2 + phy * (P2MBCR - P1MBCR); +} + +int ksz8463_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val) +{ + u16 sw_reg = 0; + u16 data = 0; + int ret; + + if (phy > 1) + return -ENOSPC; + switch (reg) { + case MII_PHYSID1: + sw_reg = ksz8463_get_phy_addr(phy, 0, PHY1IHR); + break; + case MII_PHYSID2: + sw_reg = ksz8463_get_phy_addr(phy, 0, PHY1ILR); + break; + case MII_BMCR: + case MII_BMSR: + case MII_ADVERTISE: + case MII_LPA: + sw_reg = ksz8463_get_phy_addr(phy, reg, P1MBCR); + break; + case MII_TPISTATUS: + /* This register holds the PHY interrupt status for simulated + * Micrel KSZ PHY. + */ + data = 0x0505; + break; + default: + break; + } + if (sw_reg) { + ret = ksz_read16(dev, sw_reg, &data); + if (ret) + return ret; + } + *val = data; + + return 0; +} + +int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val) +{ + u16 sw_reg = 0; + int ret; + + if (phy > 1) + return -ENOSPC; + + /* No write to fiber port. */ + if (dev->ports[phy].fiber) + return 0; + switch (reg) { + case MII_BMCR: + case MII_ADVERTISE: + sw_reg = ksz8463_get_phy_addr(phy, reg, P1MBCR); + break; + default: + break; + } + if (sw_reg) { + ret = ksz_write16(dev, sw_reg, val); + if (ret) + return ret; + } + + return 0; +} + int ksz8_switch_init(struct ksz_device *dev) { dev->cpu_port = fls(dev->info->cpu_ports) - 1; diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h index e1c79ff97123..0f2cd1474b44 100644 --- a/drivers/net/dsa/microchip/ksz8.h +++ b/drivers/net/dsa/microchip/ksz8.h @@ -63,4 +63,8 @@ void ksz8_phylink_mac_link_up(struct phylink_config *config, bool tx_pause, bool rx_pause); int ksz8_all_queues_split(struct ksz_device *dev, int queues); +u32 ksz8463_get_port_addr(int port, int offset); +int ksz8463_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val); +int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val); + #endif diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h index 329688603a58..332408567b47 100644 --- a/drivers/net/dsa/microchip/ksz8_reg.h +++ b/drivers/net/dsa/microchip/ksz8_reg.h @@ -729,6 +729,55 @@ #define PHY_POWER_SAVING_ENABLE BIT(2) #define PHY_REMOTE_LOOPBACK BIT(1) +/* KSZ8463 specific registers. */ +#define P1MBCR 0x4C +#define P1MBSR 0x4E +#define PHY1ILR 0x50 +#define PHY1IHR 0x52 +#define P1ANAR 0x54 +#define P1ANLPR 0x56 +#define P2MBCR 0x58 +#define P2MBSR 0x5A +#define PHY2ILR 0x5C +#define PHY2IHR 0x5E +#define P2ANAR 0x60 +#define P2ANLPR 0x62 + +#define P1CR1 0x6C +#define P1CR2 0x6E +#define P1CR3 0x72 +#define P1CR4 0x7E +#define P1SR 0x80 + +#define KSZ8463_FLUSH_TABLE_CTRL 0xAD + +#define KSZ8463_FLUSH_DYN_MAC_TABLE BIT(2) +#define KSZ8463_FLUSH_STA_MAC_TABLE BIT(1) + +#define KSZ8463_REG_SW_CTRL_9 0xAE + +#define KSZ8463_REG_CFG_CTRL 0xD8 + +#define PORT_2_COPPER_MODE BIT(7) +#define PORT_1_COPPER_MODE BIT(6) +#define PORT_COPPER_MODE_S 6 + +#define KSZ8463_REG_SW_RESET 0x126 + +#define KSZ8463_GLOBAL_SOFTWARE_RESET BIT(0) + +#define KSZ8463_PTP_CLK_CTRL 0x600 + +#define PTP_CLK_ENABLE BIT(1) + +#define KSZ8463_PTP_MSG_CONF1 0x620 + +#define PTP_ENABLE BIT(6) + +#define KSZ8463_REG_DSP_CTRL_6 0x734 + +#define COPPER_RECEIVE_ADJUSTMENT BIT(13) + /* Chip resource */ #define PRIO_QUEUES 4 @@ -784,7 +833,9 @@ #define KSZ8795_MIB_TOTAL_TX_1 0x105 #define KSZ8863_MIB_PACKET_DROPPED_TX_0 0x100 -#define KSZ8863_MIB_PACKET_DROPPED_RX_0 0x105 +#define KSZ8863_MIB_PACKET_DROPPED_RX_0 0x103 + +#define KSZ8895_MIB_PACKET_DROPPED_RX_0 0x105 #define MIB_PACKET_DROPPED 0x0000FFFF diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 7c142c17b3f6..7292bfe2f7ca 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -331,6 +331,38 @@ static const struct phylink_mac_ops ksz8_phylink_mac_ops = { .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi, }; +static const struct ksz_dev_ops ksz8463_dev_ops = { + .setup = ksz8_setup, + .get_port_addr = ksz8463_get_port_addr, + .cfg_port_member = ksz8_cfg_port_member, + .flush_dyn_mac_table = ksz8_flush_dyn_mac_table, + .port_setup = ksz8_port_setup, + .r_phy = ksz8463_r_phy, + .w_phy = ksz8463_w_phy, + .r_mib_cnt = ksz8_r_mib_cnt, + .r_mib_pkt = ksz8_r_mib_pkt, + .r_mib_stat64 = ksz88xx_r_mib_stats64, + .freeze_mib = ksz8_freeze_mib, + .port_init_cnt = ksz8_port_init_cnt, + .fdb_dump = ksz8_fdb_dump, + .fdb_add = ksz8_fdb_add, + .fdb_del = ksz8_fdb_del, + .mdb_add = ksz8_mdb_add, + .mdb_del = ksz8_mdb_del, + .vlan_filtering = ksz8_port_vlan_filtering, + .vlan_add = ksz8_port_vlan_add, + .vlan_del = ksz8_port_vlan_del, + .mirror_add = ksz8_port_mirror_add, + .mirror_del = ksz8_port_mirror_del, + .get_caps = ksz8_get_caps, + .config_cpu_port = ksz8_config_cpu_port, + .enable_stp_addr = ksz8_enable_stp_addr, + .reset = ksz8_reset_switch, + .init = ksz8_switch_init, + .exit = ksz8_switch_exit, + .change_mtu = ksz8_change_mtu, +}; + static const struct ksz_dev_ops ksz88xx_dev_ops = { .setup = ksz8_setup, .get_port_addr = ksz8_get_port_addr, @@ -517,6 +549,60 @@ static const struct ksz_dev_ops lan937x_dev_ops = { .exit = lan937x_switch_exit, }; +static const u16 ksz8463_regs[] = { + [REG_SW_MAC_ADDR] = 0x10, + [REG_IND_CTRL_0] = 0x30, + [REG_IND_DATA_8] = 0x26, + [REG_IND_DATA_CHECK] = 0x26, + [REG_IND_DATA_HI] = 0x28, + [REG_IND_DATA_LO] = 0x2C, + [REG_IND_MIB_CHECK] = 0x2F, + [P_FORCE_CTRL] = 0x0C, + [P_LINK_STATUS] = 0x0E, + [P_LOCAL_CTRL] = 0x0C, + [P_NEG_RESTART_CTRL] = 0x0D, + [P_REMOTE_STATUS] = 0x0E, + [P_SPEED_STATUS] = 0x0F, + [S_TAIL_TAG_CTRL] = 0xAD, + [P_STP_CTRL] = 0x6F, + [S_START_CTRL] = 0x01, + [S_BROADCAST_CTRL] = 0x06, + [S_MULTICAST_CTRL] = 0x04, +}; + +static const u32 ksz8463_masks[] = { + [PORT_802_1P_REMAPPING] = BIT(3), + [SW_TAIL_TAG_ENABLE] = BIT(0), + [MIB_COUNTER_OVERFLOW] = BIT(7), + [MIB_COUNTER_VALID] = BIT(6), + [VLAN_TABLE_FID] = GENMASK(15, 12), + [VLAN_TABLE_MEMBERSHIP] = GENMASK(18, 16), + [VLAN_TABLE_VALID] = BIT(19), + [STATIC_MAC_TABLE_VALID] = BIT(19), + [STATIC_MAC_TABLE_USE_FID] = BIT(21), + [STATIC_MAC_TABLE_FID] = GENMASK(25, 22), + [STATIC_MAC_TABLE_OVERRIDE] = BIT(20), + [STATIC_MAC_TABLE_FWD_PORTS] = GENMASK(18, 16), + [DYNAMIC_MAC_TABLE_ENTRIES_H] = GENMASK(1, 0), + [DYNAMIC_MAC_TABLE_MAC_EMPTY] = BIT(2), + [DYNAMIC_MAC_TABLE_NOT_READY] = BIT(7), + [DYNAMIC_MAC_TABLE_ENTRIES] = GENMASK(31, 24), + [DYNAMIC_MAC_TABLE_FID] = GENMASK(19, 16), + [DYNAMIC_MAC_TABLE_SRC_PORT] = GENMASK(21, 20), + [DYNAMIC_MAC_TABLE_TIMESTAMP] = GENMASK(23, 22), +}; + +static u8 ksz8463_shifts[] = { + [VLAN_TABLE_MEMBERSHIP_S] = 16, + [STATIC_MAC_FWD_PORTS] = 16, + [STATIC_MAC_FID] = 22, + [DYNAMIC_MAC_ENTRIES_H] = 8, + [DYNAMIC_MAC_ENTRIES] = 24, + [DYNAMIC_MAC_FID] = 16, + [DYNAMIC_MAC_TIMESTAMP] = 22, + [DYNAMIC_MAC_SRC_PORT] = 20, +}; + static const u16 ksz8795_regs[] = { [REG_SW_MAC_ADDR] = 0x68, [REG_IND_CTRL_0] = 0x6E, @@ -1387,6 +1473,29 @@ static const struct regmap_access_table ksz8873_register_set = { }; const struct ksz_chip_data ksz_switch_chips[] = { + [KSZ8463] = { + .chip_id = KSZ8463_CHIP_ID, + .dev_name = "KSZ8463", + .num_vlans = 16, + .num_alus = 0, + .num_statics = 8, + .cpu_ports = 0x4, /* can be configured as cpu port */ + .port_cnt = 3, + .num_tx_queues = 4, + .num_ipms = 4, + .ops = &ksz8463_dev_ops, + .phylink_mac_ops = &ksz88x3_phylink_mac_ops, + .mib_names = ksz88xx_mib_names, + .mib_cnt = ARRAY_SIZE(ksz88xx_mib_names), + .reg_mib_cnt = MIB_COUNTER_NUM, + .regs = ksz8463_regs, + .masks = ksz8463_masks, + .shifts = ksz8463_shifts, + .supports_mii = {false, false, true}, + .supports_rmii = {false, false, true}, + .internal_phy = {true, true, false}, + }, + [KSZ8563] = { .chip_id = KSZ8563_CHIP_ID, .dev_name = "KSZ8563", @@ -2786,8 +2895,7 @@ static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq) kirq->dev = dev; kirq->masked = ~0; - kirq->domain = irq_domain_create_simple(of_fwnode_handle(dev->dev->of_node), - kirq->nirqs, 0, + kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, &ksz_irq_domain_ops, kirq); if (!kirq->domain) return -ENOMEM; @@ -2843,6 +2951,7 @@ static int ksz_parse_drive_strength(struct ksz_device *dev); static int ksz_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; + u16 storm_mask, storm_rate; struct dsa_port *dp; struct ksz_port *p; const u16 *regs; @@ -2872,10 +2981,14 @@ static int ksz_setup(struct dsa_switch *ds) } /* set broadcast storm protection 10% rate */ + storm_mask = BROADCAST_STORM_RATE; + storm_rate = (BROADCAST_STORM_VALUE * BROADCAST_STORM_PROT_RATE) / 100; + if (ksz_is_ksz8463(dev)) { + storm_mask = swab16(storm_mask); + storm_rate = swab16(storm_rate); + } regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL], - BROADCAST_STORM_RATE, - (BROADCAST_STORM_VALUE * - BROADCAST_STORM_PROT_RATE) / 100); + storm_mask, storm_rate); dev->dev_ops->config_cpu_port(ds); @@ -3401,6 +3514,7 @@ static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds, proto = DSA_TAG_PROTO_KSZ8795; if (dev->chip_id == KSZ88X3_CHIP_ID || + dev->chip_id == KSZ8463_CHIP_ID || dev->chip_id == KSZ8563_CHIP_ID || dev->chip_id == KSZ9893_CHIP_ID || dev->chip_id == KSZ9563_CHIP_ID) @@ -3513,6 +3627,7 @@ static int ksz_max_mtu(struct dsa_switch *ds, int port) case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; + case KSZ8463_CHIP_ID: case KSZ88X3_CHIP_ID: case KSZ8864_CHIP_ID: case KSZ8895_CHIP_ID: @@ -3867,6 +3982,9 @@ static int ksz_switch_detect(struct ksz_device *dev) id2 = FIELD_GET(SW_CHIP_ID_M, id16); switch (id1) { + case KSZ84_FAMILY_ID: + dev->chip_id = KSZ8463_CHIP_ID; + break; case KSZ87_FAMILY_ID: if (id2 == KSZ87_CHIP_ID_95) { u8 val; @@ -4108,6 +4226,17 @@ static int ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, return p->bands - 1 - band; } +static u8 ksz8463_tc_ctrl(int port, int queue) +{ + u8 reg; + + reg = 0xC8 + port * 4; + reg += ((3 - queue) / 2) * 2; + reg++; + reg -= (queue & 1); + return reg; +} + /** * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) * for a port on KSZ88x3 switch @@ -4143,6 +4272,8 @@ static int ksz88x3_tc_ets_add(struct ksz_device *dev, int port, * port/queue */ reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); /* Clear WFQ enable bit to select strict priority scheduling */ ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); @@ -4178,6 +4309,8 @@ static int ksz88x3_tc_ets_del(struct ksz_device *dev, int port) * port/queue */ reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); + if (ksz_is_ksz8463(dev)) + reg = ksz8463_tc_ctrl(port, queue); /* Set WFQ enable bit to revert back to default scheduling * mode @@ -4325,7 +4458,7 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct ksz_device *dev = ds->priv; int ret; - if (is_ksz8(dev) && !ksz_is_ksz88x3(dev)) + if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) return -EOPNOTSUPP; if (qopt->parent != TC_H_ROOT) { @@ -4339,13 +4472,13 @@ static int ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, if (ret) return ret; - if (ksz_is_ksz88x3(dev)) + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return ksz88x3_tc_ets_add(dev, port, &qopt->replace_params); else return ksz_tc_ets_add(dev, port, &qopt->replace_params); case TC_ETS_DESTROY: - if (ksz_is_ksz88x3(dev)) + if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return ksz88x3_tc_ets_del(dev, port); else return ksz_tc_ets_del(dev, port); @@ -4688,7 +4821,16 @@ int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, /* Program the switch MAC address to hardware */ for (i = 0; i < ETH_ALEN; i++) { - ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]); + if (ksz_is_ksz8463(dev)) { + u16 addr16 = ((u16)addr[i] << 8) | addr[i + 1]; + + ret = ksz_write16(dev, regs[REG_SW_MAC_ADDR] + i, + addr16); + i++; + } else { + ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, + addr[i]); + } if (ret) goto macaddr_drop; } @@ -5297,6 +5439,9 @@ int ksz_switch_register(struct ksz_device *dev) &dev->ports[port_num].interface); ksz_parse_rgmii_delay(dev, port_num, port); + dev->ports[port_num].fiber = + of_property_read_bool(port, + "micrel,fiber-mode"); } of_node_put(ports); } diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index a08417df2ca4..a1eb39771bb9 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -222,6 +222,7 @@ struct ksz_device { /* List of supported models */ enum ksz_model { + KSZ8463, KSZ8563, KSZ8567, KSZ8795, @@ -484,6 +485,11 @@ static inline struct regmap *ksz_regmap_32(struct ksz_device *dev) return dev->regmap[KSZ_REGMAP_32]; } +static inline bool ksz_is_ksz8463(struct ksz_device *dev) +{ + return dev->chip_id == KSZ8463_CHIP_ID; +} + static inline int ksz_read8(struct ksz_device *dev, u32 reg, u8 *val) { unsigned int value; @@ -709,12 +715,13 @@ static inline bool ksz_is_8895_family(struct ksz_device *dev) static inline bool is_ksz8(struct ksz_device *dev) { return ksz_is_ksz87xx(dev) || ksz_is_ksz88x3(dev) || - ksz_is_8895_family(dev); + ksz_is_8895_family(dev) || ksz_is_ksz8463(dev); } static inline bool is_ksz88xx(struct ksz_device *dev) { - return ksz_is_ksz88x3(dev) || ksz_is_8895_family(dev); + return ksz_is_ksz88x3(dev) || ksz_is_8895_family(dev) || + ksz_is_ksz8463(dev); } static inline bool is_ksz9477(struct ksz_device *dev) @@ -761,6 +768,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) #define REG_CHIP_ID0 0x00 #define SW_FAMILY_ID_M GENMASK(15, 8) +#define KSZ84_FAMILY_ID 0x84 #define KSZ87_FAMILY_ID 0x87 #define KSZ88_FAMILY_ID 0x88 #define KSZ8895_FAMILY_ID 0x95 @@ -939,4 +947,29 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) [KSZ_REGMAP_32] = KSZ_REGMAP_ENTRY(32, swp, (regbits), (regpad), (regalign)), \ } +#define KSZ8463_REGMAP_ENTRY(width, regbits, regpad, regalign) \ + { \ + .name = #width, \ + .val_bits = (width), \ + .reg_stride = (width / 8), \ + .reg_bits = (regbits) + (regalign), \ + .pad_bits = (regpad), \ + .read = ksz8463_spi_read, \ + .write = ksz8463_spi_write, \ + .max_register = BIT(regbits) - 1, \ + .cache_type = REGCACHE_NONE, \ + .zero_flag_mask = 1, \ + .use_single_read = 1, \ + .use_single_write = 1, \ + .lock = ksz_regmap_lock, \ + .unlock = ksz_regmap_unlock, \ + } + +#define KSZ8463_REGMAP_TABLE(ksz, regbits, regpad, regalign) \ + static const struct regmap_config ksz##_regmap_config[] = { \ + [KSZ_REGMAP_8] = KSZ8463_REGMAP_ENTRY(8, (regbits), (regpad), (regalign)), \ + [KSZ_REGMAP_16] = KSZ8463_REGMAP_ENTRY(16, (regbits), (regpad), (regalign)), \ + [KSZ_REGMAP_32] = KSZ8463_REGMAP_ENTRY(32, (regbits), (regpad), (regalign)), \ + } + #endif diff --git a/drivers/net/dsa/microchip/ksz_dcb.c b/drivers/net/dsa/microchip/ksz_dcb.c index c3b501997ac9..7131c5caac54 100644 --- a/drivers/net/dsa/microchip/ksz_dcb.c +++ b/drivers/net/dsa/microchip/ksz_dcb.c @@ -16,10 +16,12 @@ * Therefore, we define the base offset as 0x00 here to align with that logic. */ #define KSZ8_REG_PORT_1_CTRL_0 0x00 +#define KSZ8463_REG_PORT_1_CTRL_0 0x6C #define KSZ8_PORT_DIFFSERV_ENABLE BIT(6) #define KSZ8_PORT_802_1P_ENABLE BIT(5) #define KSZ8_PORT_BASED_PRIO_M GENMASK(4, 3) +#define KSZ8463_REG_TOS_DSCP_CTRL 0x16 #define KSZ88X3_REG_TOS_DSCP_CTRL 0x60 #define KSZ8765_REG_TOS_DSCP_CTRL 0x90 @@ -98,6 +100,8 @@ static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg, *reg = KSZ8_REG_PORT_1_CTRL_0; *mask = KSZ8_PORT_BASED_PRIO_M; *shift = __bf_shf(KSZ8_PORT_BASED_PRIO_M); + if (ksz_is_ksz8463(dev)) + *reg = KSZ8463_REG_PORT_1_CTRL_0; } else { *reg = KSZ9477_REG_PORT_MRI_MAC_CTRL; *mask = KSZ9477_PORT_BASED_PRIO_M; @@ -122,10 +126,12 @@ static void ksz_get_dscp_prio_reg(struct ksz_device *dev, int *reg, *reg = KSZ8765_REG_TOS_DSCP_CTRL; *per_reg = 4; *mask = GENMASK(1, 0); - } else if (ksz_is_ksz88x3(dev)) { + } else if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) { *reg = KSZ88X3_REG_TOS_DSCP_CTRL; *per_reg = 4; *mask = GENMASK(1, 0); + if (ksz_is_ksz8463(dev)) + *reg = KSZ8463_REG_TOS_DSCP_CTRL; } else { *reg = KSZ9477_REG_DIFFSERV_PRIO_MAP; *per_reg = 2; @@ -151,6 +157,8 @@ static void ksz_get_apptrust_map_and_reg(struct ksz_device *dev, *map = ksz8_apptrust_map_to_bit; *reg = KSZ8_REG_PORT_1_CTRL_0; *mask = KSZ8_PORT_DIFFSERV_ENABLE | KSZ8_PORT_802_1P_ENABLE; + if (ksz_is_ksz8463(dev)) + *reg = KSZ8463_REG_PORT_1_CTRL_0; } else { *map = ksz9477_apptrust_map_to_bit; *reg = KSZ9477_REG_PORT_MRI_PRIO_CTRL; diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 8ab664e85f13..35fc21b1ee48 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -1130,8 +1130,8 @@ int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) init_completion(&port->tstamp_msg_comp); - ptpirq->domain = irq_domain_create_linear(of_fwnode_handle(dev->dev->of_node), - ptpirq->nirqs, &ksz_ptp_irq_domain_ops, ptpirq); + ptpirq->domain = irq_domain_create_linear(dev_fwnode(dev->dev), ptpirq->nirqs, + &ksz_ptp_irq_domain_ops, ptpirq); if (!ptpirq->domain) return -ENOMEM; diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c index b633d263098c..d8001734b057 100644 --- a/drivers/net/dsa/microchip/ksz_spi.c +++ b/drivers/net/dsa/microchip/ksz_spi.c @@ -16,6 +16,10 @@ #include "ksz_common.h" +#define KSZ8463_SPI_ADDR_SHIFT 13 +#define KSZ8463_SPI_ADDR_ALIGN 3 +#define KSZ8463_SPI_TURNAROUND_SHIFT 2 + #define KSZ8795_SPI_ADDR_SHIFT 12 #define KSZ8795_SPI_ADDR_ALIGN 3 #define KSZ8795_SPI_TURNAROUND_SHIFT 1 @@ -37,6 +41,99 @@ KSZ_REGMAP_TABLE(ksz8863, 16, KSZ8863_SPI_ADDR_SHIFT, KSZ_REGMAP_TABLE(ksz9477, 32, KSZ9477_SPI_ADDR_SHIFT, KSZ9477_SPI_TURNAROUND_SHIFT, KSZ9477_SPI_ADDR_ALIGN); +static u16 ksz8463_reg(u16 reg, size_t size) +{ + switch (size) { + case 1: + reg = ((reg >> 2) << 4) | (1 << (reg & 3)); + break; + case 2: + reg = ((reg >> 2) << 4) | (reg & 2 ? 0x0c : 0x03); + break; + default: + reg = ((reg >> 2) << 4) | 0xf; + break; + } + reg <<= KSZ8463_SPI_TURNAROUND_SHIFT; + return reg; +} + +static int ksz8463_spi_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + u8 bytes[2]; + u16 cmd; + int rc; + + if (reg_size > 2 || val_size > 4) + return -EINVAL; + memcpy(&cmd, reg, sizeof(u16)); + cmd = ksz8463_reg(cmd, val_size); + /* SPI command uses big-endian format. */ + put_unaligned_be16(cmd, bytes); + rc = spi_write_then_read(spi, bytes, reg_size, val, val_size); +#if defined(__BIG_ENDIAN) + /* Register value uses little-endian format so need to convert when + * running in big-endian system. + */ + if (!rc && val_size > 1) { + if (val_size == 2) { + u16 v = get_unaligned_le16(val); + + memcpy(val, &v, sizeof(v)); + } else if (val_size == 4) { + u32 v = get_unaligned_le32(val); + + memcpy(val, &v, sizeof(v)); + } + } +#endif + return rc; +} + +static int ksz8463_spi_write(void *context, const void *data, size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + size_t val_size = count - 2; + u8 bytes[6]; + u16 cmd; + + if (count <= 2 || count > 6) + return -EINVAL; + memcpy(bytes, data, count); + memcpy(&cmd, data, sizeof(u16)); + cmd = ksz8463_reg(cmd, val_size); + cmd |= (1 << (KSZ8463_SPI_ADDR_SHIFT + KSZ8463_SPI_TURNAROUND_SHIFT)); + /* SPI command uses big-endian format. */ + put_unaligned_be16(cmd, bytes); +#if defined(__BIG_ENDIAN) + /* Register value uses little-endian format so need to convert when + * running in big-endian system. + */ + if (val_size == 2) { + u8 *val = &bytes[2]; + u16 v; + + memcpy(&v, val, sizeof(v)); + put_unaligned_le16(v, val); + } else if (val_size == 4) { + u8 *val = &bytes[2]; + u32 v; + + memcpy(&v, val, sizeof(v)); + put_unaligned_le32(v, val); + } +#endif + return spi_write(spi, bytes, count); +} + +KSZ8463_REGMAP_TABLE(ksz8463, KSZ8463_SPI_ADDR_SHIFT, 0, + KSZ8463_SPI_ADDR_ALIGN); + static int ksz_spi_probe(struct spi_device *spi) { const struct regmap_config *regmap_config; @@ -58,6 +155,8 @@ static int ksz_spi_probe(struct spi_device *spi) dev->chip_id = chip->chip_id; if (chip->chip_id == KSZ88X3_CHIP_ID) regmap_config = ksz8863_regmap_config; + else if (chip->chip_id == KSZ8463_CHIP_ID) + regmap_config = ksz8463_regmap_config; else if (chip->chip_id == KSZ8795_CHIP_ID || chip->chip_id == KSZ8794_CHIP_ID || chip->chip_id == KSZ8765_CHIP_ID) @@ -126,6 +225,10 @@ static void ksz_spi_shutdown(struct spi_device *spi) static const struct of_device_id ksz_dt_ids[] = { { + .compatible = "microchip,ksz8463", + .data = &ksz_switch_chips[KSZ8463] + }, + { .compatible = "microchip,ksz8765", .data = &ksz_switch_chips[KSZ8765] }, @@ -214,6 +317,7 @@ static const struct of_device_id ksz_dt_ids[] = { MODULE_DEVICE_TABLE(of, ksz_dt_ids); static const struct spi_device_id ksz_spi_ids[] = { + { "ksz8463" }, { "ksz8765" }, { "ksz8794" }, { "ksz8795" }, |