* [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA
@ 2026-08-31 20:54 Linus Walleij
2026-08-31 20:54 ` [PATCH net-next v8 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Linus Walleij @ 2026-08-31 20:54 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King, Nicolai Buchwitz
Cc: netdev, Woojung Huh, devicetree, Linus Walleij,
Krzysztof Kozlowski
This series breaks with the dated attempt to polish the old
KS8995 driver, and instead implement support for the KS8995XA
in the KSZ driver, and after that delete the old KS8995 driver.
The hardware clearly has the same ancestry, the KSZ8995XA is
just a rebrand of the much older Micrel KX8995XA switch.
The old drivers referce to "KS8995" was actually KS8995XA only,
it never supported the sibling devices KS8995E or KS8995MA.
This is reflected in this patch set.
Add new compatibles, add special code paths for the KSZ8995XA,
including support for its two-level DCB priority model, and add a
new tagger for the special front tag found in the KSZ8995XA.
The patches were tested with the Actiontec MI424WR rev D (which
has the KS8995XA) and OpenWrt as userspace.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Changes in v8:
- Add DCB support for the KSZ8995XA two-level priority model and set its
internal priority map count to two.
- Initialize the KSZ8995XA per-port default tags to VID 0 so the tagger can
distinguish originally untagged packets from VLAN-tagged frames.
- Push non-802.1Q hardware-accelerated VLAN tags into the packet before
adding the KS8995 tag, so they cannot displace the switch tag from its
required offset 12.
- Account for the discrepancy between the documented and observed
KSZ8995XA MTU behavior. Its packet-size control bits use the same
polarity as the other KSZ8 switches, while using the opposite polarity
documented in the manual makes the switch hang on big packets.
- Rebased on v7.3-rc1
- Link to v7: https://patch.msgid.link/20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org
Changes in v7:
- Rebased on v7.2-rc1
- Rebased on top of Bastiens cleanups.
- Rebased and reworked on top of my patch changing the SKB ownership
model in the taggers.
- Drop bogus reset routine, keep per-port reset but use the right
defines.
- Properly implement the 2way queue split support (low/hi prio)
- Drop bogus surplus ops assignment.
- Link to v6: https://patch.msgid.link/20260608-ks8995-to-ksz8-v6-0-d91eb43edcc5@kernel.org
Changes in v6:
- Provide a prompt along with the transition symbol NET_DSA_KS8995
so that oldconfig works.
- Clarify in commit message that the platform data probe path is
unused in the kernel and it is fine to delete this mechanism.
- Link to v5: https://patch.msgid.link/20260604-ks8995-to-ksz8-v5-0-98baeb45e665@kernel.org
Changes in v5:
- Rebase on net-next again to be sure we apply.
- Fix the codepath where DCB was set up per-port, damaging KS8995XA
port registers.
- Fix up the transitional symbol NET_DSA_KS8995 to depend on
SPI and select both NET_DSA_MICROCHIP_KSZ_COMMON and
NET_DSA_MICROCHIP_KSZ_SPI.
- Link to v4: https://patch.msgid.link/20260603-ks8995-to-ksz8-v4-0-e15149ef21e7@kernel.org
Changes in v4:
- Create a stub symbol for NET_DSA_KS8995 to phase over users to
the new driver.
- Link to v3: https://patch.msgid.link/20260526-ks8995-to-ksz8-v3-0-c530f651989f@kernel.org
Changes in v3:
- Rebase on net-next again, moving target!
- Fix a netdev_info() print in the tagger to be netdev_debug()
- Add net/dsa/tag_ks8995.c to MAINTAINERS
- Link to v2: https://patch.msgid.link/20260522-ks8995-to-ksz8-v2-0-5712c0dc9e75@kernel.org
Changes in v2:
- Avoid trying to configure TOS priority settings (DCB) on the KSZ8995XA.
It does have some support for this, but let's add that later.
- Pick up Krzysztof's ACK on the new DT bindings.
- Reset the KSZ8995XA by disabling and enabling the switch like the old
driver does.
- Move mutually exclusive Kconfig over to the patch introducing the
old Micrel compatibles to the Microchip ksz_spi driver.
(this complaint from Sashiko.)
- Use __be16 instead of u16 when casting into the SKB in the tagger
(this complaint from Sashiko.)
- Do not modify the hdr->h_vlan_TCI if no hardware accelerated tag
is detected, it would get nullified. Use whatever the Linux network stack
assigned to TCI there.
(this complaint from Sashiko.)
- Avoid dereference of NULL in debug prints in the tagger.
(this complaint from Sashiko.)
- Ignored comment: Sashiko complains about ilog2(0) being potentially
undefined but that is clearly defined as 0 in the Linux kernel.
- Link to v1: https://patch.msgid.link/20260516-ks8995-to-ksz8-v1-0-70d0ef4aa5f4@kernel.org
---
Linus Walleij (5):
net: dsa: microchip: Add fallback Micrel compatibles
dt-bindings: net: dsa: microchip: Add KSZ8995XA
net: dsa: tag_ks8995: Add the KS8995 tag handling
net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
net: dsa: ks8995: Delete surplus driver
.../devicetree/bindings/net/dsa/microchip,ksz.yaml | 1 +
MAINTAINERS | 1 +
drivers/net/dsa/Kconfig | 9 +-
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 ---------------------
drivers/net/dsa/microchip/Kconfig | 1 +
drivers/net/dsa/microchip/ksz8.c | 205 ++++-
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 +
drivers/net/dsa/microchip/ksz_common.c | 53 +-
drivers/net/dsa/microchip/ksz_common.h | 11 +-
drivers/net/dsa/microchip/ksz_dcb.c | 55 +-
drivers/net/dsa/microchip/ksz_spi.c | 33 +-
include/linux/platform_data/microchip-ksz.h | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 141 ++++
18 files changed, 463 insertions(+), 924 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260509-ks8995-to-ksz8-15f3f9c8271f
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net-next v8 1/5] net: dsa: microchip: Add fallback Micrel compatibles
2026-08-31 20:54 [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
@ 2026-08-31 20:54 ` Linus Walleij
2026-09-02 9:50 ` [net-next,v8,1/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Linus Walleij @ 2026-08-31 20:54 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King, Nicolai Buchwitz
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
Because of forking paths when Micrel was acquired by Microchip,
two devices also exist with the micrel,* prefix bindings.
Add these to the KSZ SPI driver so users can use the more capable
driver.
Make the KS8995 driver mutually exclusive with this driver
to avoid probe races.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 1 +
drivers/net/dsa/microchip/ksz_spi.c | 15 +++++++++++++++
2 files changed, 16 insertions(+)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 676fb7dffe14..68fb175cc36a 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -110,6 +110,7 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
+ depends on !NET_DSA_MICROCHIP_KSZ_SPI
select NET_DSA_TAG_NONE
help
This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 373e9054947c..77aecac32466 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -224,6 +224,21 @@ static void ksz_spi_shutdown(struct spi_device *spi)
}
static const struct of_device_id ksz_dt_ids[] = {
+ /*
+ * Legacy Micrel bindings. In 2015 Microchip acquired
+ * Micrel which is the originator of the KSZ series, and
+ * devices branded for Micrel already existed, as well as
+ * some device tree bindings. These two products are identical
+ * to the same Microchip products.
+ */
+ {
+ .compatible = "micrel,ksz8864",
+ .data = &ksz_switch_chips[KSZ8864]
+ },
+ {
+ .compatible = "micrel,ksz8795",
+ .data = &ksz_switch_chips[KSZ8795]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v8 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA
2026-08-31 20:54 [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
2026-08-31 20:54 ` [PATCH net-next v8 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
@ 2026-08-31 20:54 ` Linus Walleij
2026-09-02 9:50 ` [net-next,v8,2/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
` (2 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Linus Walleij @ 2026-08-31 20:54 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King, Nicolai Buchwitz
Cc: netdev, Woojung Huh, devicetree, Linus Walleij,
Krzysztof Kozlowski
The KSZ8995XA is just like the KSZ8795 and KSZ8864 a Micrel
product. It was renamed from KS8995XA to KSZ8995XA at some point,
but it has the same properties as the KS8995XA.
Be careful to use the full product name in this new compatible:
there is also KSZ8995MA and KSZ8995E which are not compatible
with the KS8995XA.
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
index 8d4a3a9a33fc..4ed13870ed3a 100644
--- a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
@@ -23,6 +23,7 @@ properties:
- microchip,ksz8864 # 4-port version of KSZ8895 family switch
- microchip,ksz8873
- microchip,ksz8895 # 5-port version of KSZ8895 family switch
+ - microchip,ksz8995xa
- microchip,ksz9477
- microchip,ksz9897
- microchip,ksz9896
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v8 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
2026-08-31 20:54 [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
2026-08-31 20:54 ` [PATCH net-next v8 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
2026-08-31 20:54 ` [PATCH net-next v8 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
@ 2026-08-31 20:54 ` Linus Walleij
2026-09-02 9:50 ` [net-next,v8,3/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
2026-08-31 20:54 ` [PATCH net-next v8 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
4 siblings, 1 reply; 11+ messages in thread
From: Linus Walleij @ 2026-08-31 20:54 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King, Nicolai Buchwitz
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
The KS8995 100Mbit switch can do proper DSA per-port tagging
with the proper set-up. This adds the code to handle ingress
and egress KS8995 tags.
The tag is a modified 0x8100 ethertype tag where a bit in the
last nybble is set for each target port.
Push any hardware-accelerated VLAN tag into the packet before adding the
switch tag. This keeps the switch tag at offset 12 for non-802.1Q tags
such as 802.1ad.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
MAINTAINERS | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 151 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a19da74d00c..7fabc2eae9af 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17776,6 +17776,7 @@ F: Documentation/devicetree/bindings/net/dsa/microchip,lan937x.yaml
F: drivers/net/dsa/microchip/*
F: include/linux/dsa/ksz_common.h
F: include/linux/platform_data/microchip-ksz.h
+F: net/dsa/tag_ks8995.c
F: net/dsa/tag_ksz.c
MICROCHIP LAN743X ETHERNET DRIVER
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 7507d632e7c6..5d12191b6f6f 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -61,6 +61,7 @@ struct tc_action;
#define DSA_TAG_PROTO_NETC_VALUE 33
#define DSA_TAG_PROTO_KSZ8463_VALUE 34
#define DSA_TAG_PROTO_MT7628_VALUE 35
+#define DSA_TAG_PROTO_KS8995_VALUE 36
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -99,6 +100,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
DSA_TAG_PROTO_KSZ8463 = DSA_TAG_PROTO_KSZ8463_VALUE,
DSA_TAG_PROTO_MT7628 = DSA_TAG_PROTO_MT7628_VALUE,
+ DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 23b4b74004ed..4f44bf3ede23 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -125,6 +125,12 @@ config NET_DSA_TAG_MXL_GSW1XX
Say Y or M if you want to enable support for tagging frames for
MaxLinear GSW1xx switches.
+config NET_DSA_TAG_KS8995
+ tristate "Tag driver for Micrel KS8995 switch"
+ help
+ Say Y if you want to enable support for tagging frames for the
+ Micrel KS8995 switch.
+
config NET_DSA_TAG_KSZ
tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index d15bcf5c68f0..1f9cc30e9988 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o
obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
+obj-$(CONFIG_NET_DSA_TAG_KS8995) += tag_ks8995.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MT7628) += tag_mt7628.o
diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
new file mode 100644
index 000000000000..6bd5f1015c26
--- /dev/null
+++ b/net/dsa/tag_ks8995.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Linus Walleij <linusw@kernel.org>
+ */
+#include <linux/etherdevice.h>
+#include <linux/log2.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "tag.h"
+
+/* The Micrel KS8995XA / Microchip KSZ8995XA Special Tag Packet ID (STPID)
+ * pushes its tag in a modified VLAN (802.1Q) tag.
+ * -----------------------------------------------------------
+ * | MAC DA | MAC SA | 2 bytes tag | 2 bytes TCI | EtherType |
+ * -----------------------------------------------------------
+ * The tag is: 0x8100 |= BIT(port), ports 0,1,2,3
+ */
+
+#define KS8995_NAME "ks8995"
+
+#define KS8995M_STPID_STD GENMASK(15, 4)
+#define KS8995M_STPID_PORTMASK GENMASK(3, 0)
+#define KS8995M_STPID(portmask) htons(ETH_P_8021Q | FIELD_PREP(KS8995M_STPID_PORTMASK, portmask))
+
+static struct sk_buff *ks8995_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vlan_ethhdr *hdr;
+ u16 portmask;
+
+ /* Prepare the special KS8995 tags */
+ portmask = dsa_xmit_port_mask(skb, dev);
+
+ /* The switch expects the special tag at offset 12. Move any hardware
+ * accelerated VLAN tag into the payload so the conduit cannot insert
+ * it outside the special tag.
+ */
+ if (unlikely(skb_vlan_tag_present(skb))) {
+ skb = __vlan_hwaccel_push_inside(skb);
+ if (!skb)
+ return NULL;
+ }
+
+ hdr = skb_vlan_eth_hdr(skb);
+ if (hdr->h_vlan_proto != htons(ETH_P_8021Q)) {
+ skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), 0);
+ /* vlan_insert_tag() drops the skb on failure */
+ if (!skb)
+ return NULL;
+ hdr = skb_vlan_eth_hdr(skb);
+ netdev_dbg(dev, "%s: inserted VLAN TAG %04x TCI %04x\n",
+ __func__, hdr->h_vlan_proto, hdr->h_vlan_TCI);
+ } else {
+ /* VLAN tag already exists in skb head, modify it in place */
+ hdr = vlan_eth_hdr(skb);
+ hdr->h_vlan_proto = KS8995M_STPID(portmask);
+ netdev_dbg(dev, "%s: modified VLAN TAG %04x\n",
+ __func__, hdr->h_vlan_proto);
+ }
+
+ return skb;
+}
+
+static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+ int portmask;
+ u16 etype;
+
+ /* We are expecting all received packets to have a mangled VLAN
+ * TPID, so drop anything else. Because of the non-standard TPID,
+ * don't even bother looking for a tag in the hwaccel area.
+ *
+ * We have to inspect the ethertype directly because skb->protocol
+ * will contain garbage.
+ */
+ etype = ntohs(*(__be16 *)dsa_etype_header_pos_rx(skb));
+ if ((etype & KS8995M_STPID_STD) != ETH_P_8021Q) {
+ netdev_dbg(dev, "%s: dropped ethertype 0x%04x\n",
+ __func__, etype);
+ kfree_skb(skb);
+ return NULL;
+ }
+ netdev_dbg(dev, "%s: received ethertype %04x\n",
+ __func__, etype);
+
+ /* Move the custom DSA+VLAN tag into the hwaccel area and strip
+ * it from the skb head
+ */
+ skb = skb_vlan_untag(skb);
+ if (!skb) {
+ /* skb_vlan_untag drops the skb on failure */
+ netdev_err(dev, "%s: unable to untag skb\n", __func__);
+ return NULL;
+ }
+
+ portmask = FIELD_GET(KS8995M_STPID_PORTMASK, etype);
+ netdev_dbg(dev, "%s: etype %04x portmask %04x (%d)\n",
+ __func__, etype, portmask, ilog2(portmask));
+ skb->dev = dsa_conduit_find_user(dev, 0, ilog2(portmask));
+ if (!skb->dev) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Preserve the VLAN tag if it contains a non-zero VID which is not
+ * identical to 0x001, or PCP, and restore its TPID to the standard
+ * value.
+ *
+ * If this is just an ordinary inbound package the datasheet claims
+ * it will "replace null VID with ingress port VID", which means
+ * VID set to 1: 0x8101 0001 for port 0 or 0x8102 0001 for port 1.
+ * So in the DSA driver we will set the default port VID to 0 so
+ * we can properly detect non-VLAN frames.
+ */
+ if (!skb->vlan_tci) {
+ netdev_dbg(dev, "%s: clear VLAN tag from frame\n", __func__);
+ __vlan_hwaccel_clear_tag(skb);
+ } else {
+ skb->vlan_proto = htons(ETH_P_8021Q);
+ netdev_dbg(dev, "%s: vlan_tci = 0x%04x VLAN frame\n",
+ __func__, skb->vlan_tci);
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+
+ return skb;
+}
+
+static const struct dsa_device_ops ks8995_netdev_ops = {
+ .name = KS8995_NAME,
+ .proto = DSA_TAG_PROTO_KS8995,
+ .xmit = ks8995_xmit,
+ .rcv = ks8995_rcv,
+ .needed_headroom = VLAN_HLEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Micrel KS8995 family of switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KS8995, KS8995_NAME);
+
+module_dsa_tag_driver(ks8995_netdev_ops);
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v8 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
2026-08-31 20:54 [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
` (2 preceding siblings ...)
2026-08-31 20:54 ` [PATCH net-next v8 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
@ 2026-08-31 20:54 ` Linus Walleij
2026-09-02 9:50 ` [net-next,v8,4/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
4 siblings, 1 reply; 11+ messages in thread
From: Linus Walleij @ 2026-08-31 20:54 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King, Nicolai Buchwitz
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
This adds support for the Microchip KSZ8995XA also known as the
Micrel KS8995XA switch to the KSZ driver.
Notice: there are also KSZ8995E and KSZ8995MA. These are BOTH
different from the KSZ8995XA.
The helper macros are named ksz_is_ksz8995xa() to make it
possible to add E and MA support in the future.
The KSZ8995XA has eight global TOS priority registers with one
bit per DSCP code point, providing two internal priority levels.
The registers are ordered in the opposite direction from the
other KSZ8 switches. Describe the two priorities in the chip
data, then augment the DCB helpers for this.
Practical testing also shows that its packet-size control bits use
the same semantics as the other KSZ8 switches. The polarity in the
manual appears to be wrong and makes the switch hang on big packets.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/microchip/Kconfig | 1 +
drivers/net/dsa/microchip/ksz8.c | 205 +++++++++++++++++++++++-----
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 +
drivers/net/dsa/microchip/ksz_common.c | 53 ++++++-
drivers/net/dsa/microchip/ksz_common.h | 11 +-
drivers/net/dsa/microchip/ksz_dcb.c | 55 +++++---
drivers/net/dsa/microchip/ksz_spi.c | 18 ++-
include/linux/platform_data/microchip-ksz.h | 1 +
9 files changed, 290 insertions(+), 63 deletions(-)
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index c71d3fd5dfeb..75c9b2114afd 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -2,6 +2,7 @@
menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
tristate "Microchip KSZ8XXX/KSZ9XXX/LAN937X series switch support"
depends on NET_DSA
+ select NET_DSA_TAG_KS8995
select NET_DSA_TAG_KSZ
select NET_DSA_TAG_NONE
select NET_IEEE8021Q_HELPERS
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index 941ae9f66f70..f9272d1b7bf4 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:
+ * - KSZ8995XA (the oldest ancestor)
* - KSZ8463
* - KSZ8863, KSZ8873 aka KSZ88X3
* - KSZ8895, KSZ8864 aka KSZ8895 family
@@ -253,7 +254,17 @@ static int ksz8463_reset_switch(struct ksz_device *dev)
static int ksz8_reset_switch(struct ksz_device *dev)
{
- if (ksz_is_ksz88x3(dev)) {
+ if (ksz_is_ksz8995xa(dev)) {
+ unsigned int port;
+
+ /* The KSZ8995XA switch itself cannot be reset by software, but
+ * often boot loaders have fun with ports, so reset them. This
+ * reset bit is self-clearing.
+ */
+ for (port = 0; port < dev->info->port_cnt; port++)
+ ksz_port_cfg(dev, port, REG_PORT_STATUS_3,
+ PORT_PHY_SOFT_RESET, true);
+ } else if (ksz_is_ksz88x3(dev)) {
/* reset switch */
ksz_cfg(dev, KSZ8863_REG_SW_RESET,
KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);
@@ -269,6 +280,31 @@ static int ksz8_reset_switch(struct ksz_device *dev)
return 0;
}
+static int ksz8995xa_change_mtu(struct dsa_switch *ds, int port, int mtu)
+{
+ struct ksz_device *dev = ds->priv;
+ int frame_size;
+ u8 ctrl2 = 0;
+
+ if (!dsa_is_cpu_port(dev->ds, port))
+ return 0;
+
+ frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
+
+ /* The legal packet size bit polarity documented in the KSZ8995XA
+ * manual appears to be wrong. Practical tests show that the same
+ * semantics as the other switches are correct; using the documented
+ * polarity makes the switch hang on big packets.
+ */
+ if (frame_size <= KSZ8995XA_LEGAL_PACKET_SIZE)
+ ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE;
+ else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE)
+ ctrl2 |= KSZ8863_HUGE_PACKET_ENABLE;
+
+ return ksz_rmw8(dev, REG_SW_CTRL_2, KSZ8863_LEGAL_PACKET_ENABLE |
+ KSZ8863_HUGE_PACKET_ENABLE, ctrl2);
+}
+
static int ksz88xx_change_mtu(struct dsa_switch *ds, int port, int mtu)
{
struct ksz_device *dev = ds->priv;
@@ -325,8 +361,8 @@ static int ksz88xx_max_mtu(struct dsa_switch *ds, int port)
static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
{
- u8 mask_4q, mask_2q;
- u8 reg_4q, reg_2q;
+ u8 mask_4q = 0, mask_2q;
+ u8 reg_4q = 0, reg_2q;
u8 data_4q = 0;
u8 data_2q = 0;
int ret;
@@ -352,6 +388,10 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN;
reg_4q = P1CR1;
reg_2q = P1CR1 + 1;
+ } else if (ksz_is_ksz8995xa(dev)) {
+ /* This switch has no 4way split support */
+ mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
+ reg_2q = REG_PORT_CTRL_0;
} else {
mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
@@ -373,9 +413,11 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
else if (queues == 2)
data_2q = mask_2q;
- ret = ksz_prmw8(dev, port, reg_4q, mask_4q, data_4q);
- if (ret)
- return ret;
+ if (mask_4q) {
+ ret = ksz_prmw8(dev, port, reg_4q, mask_4q, data_4q);
+ if (ret)
+ return ret;
+ }
return ksz_prmw8(dev, port, reg_2q, mask_2q, data_2q);
}
@@ -970,6 +1012,10 @@ static int ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val)
if (reg_val & PORT_MDIX_STATUS)
*val |= KSZ886X_CTRL_MDIX_STAT;
+ /* KSZ8995XA has no fancy features in register 0xA */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, ®_val);
if (ret < 0)
return ret;
@@ -1068,8 +1114,10 @@ static int ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val)
if (ctrl & PORT_FORCE_FULL_DUPLEX)
*val |= BMCR_FULLDPLX;
- if (speed & PORT_HP_MDIX)
- *val |= KSZ886X_BMCR_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ if (speed & PORT_HP_MDIX)
+ *val |= KSZ886X_BMCR_HP_MDIX;
+ }
if (restart & PORT_FORCE_MDIX)
*val |= KSZ886X_BMCR_FORCE_MDI;
@@ -1164,6 +1212,9 @@ static int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
data |= LPA_LPACK;
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1);
if (ret)
return ret;
@@ -1307,13 +1358,15 @@ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
if (val & BMCR_RESET)
return 0;
- speed = 0;
- if (val & KSZ886X_BMCR_HP_MDIX)
- speed |= PORT_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ speed = 0;
+ if (val & KSZ886X_BMCR_HP_MDIX)
+ speed |= PORT_HP_MDIX;
- ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
- if (ret)
- return ret;
+ ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
+ if (ret)
+ return ret;
+ }
ctrl = 0;
if (ksz_is_ksz88x3(dev)) {
@@ -1423,11 +1476,17 @@ static int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
}
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
if (val & PHY_START_CABLE_DIAG)
ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true);
break;
case PHY_REG_PHY_CTRL:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz8_w_phy_ctrl(dev, p, val);
if (ret)
return ret;
@@ -1672,7 +1731,7 @@ static int ksz8_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag,
{
struct ksz_device *dev = ds->priv;
- if (ksz_is_ksz88x3(dev))
+ if (ksz_is_ksz88x3(dev) || ksz_is_ksz8995xa(dev))
return -ENOTSUPP;
/* Discard packets with VID not enabled on the switch */
@@ -2043,12 +2102,14 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_port_queue_split(dev, port, dev->info->num_tx_queues);
- /* replace priority */
- 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 (!ksz_is_ksz8995xa(dev)) {
+ /* replace priority */
+ 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)
member = dsa_user_ports(ds);
@@ -2057,6 +2118,21 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_cfg_port_member(dev, port, member);
+ if (ksz_is_ksz8995xa(dev)) {
+ /* Use VID 0 to identify untagged frames in the special tag */
+ ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, 0);
+
+ /* The KSZ8995XA has a special tag format in the front of the frame
+ * that need to be inserted by the CPU and then removed by each
+ * port. PORT_REMOVE_TAG simply means "remove tags coming from the
+ * CPU port" it does not affect ingress packets.
+ */
+ if (cpu_port)
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_INSERT_TAG, true);
+ else
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_REMOVE_TAG, true);
+ }
+
/* Disable all WoL options by default. Otherwise
* ksz_switch_macaddr_get/put logic will not work properly.
* CPU port 4 has no WoL functionality.
@@ -2073,6 +2149,7 @@ static int ksz8_dsa_port_setup(struct dsa_switch *ds, int port)
return 0;
ksz8_port_setup(dev, port, false);
+
return ksz_dcb_init_port(dev, port);
}
@@ -2147,7 +2224,9 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds)
masks = dev->info->masks;
regs = dev->info->regs;
- ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
+ /* KSZ8995XA uses a tag in the header instead of the tail */
+ if (!ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
ksz8_port_setup(dev, dev->cpu_port, true);
@@ -2323,6 +2402,10 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev)
{
struct alu_struct alu;
+ /* KSZ8995XA lacks STP */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
/* Setup STP address for STP operation. */
memset(&alu, 0, sizeof(alu));
ether_addr_copy(alu.mac, eth_stp_addr);
@@ -2650,13 +2733,17 @@ static int ksz8_setup(struct dsa_switch *ds)
struct ksz_port *p;
const u16 *regs;
int i, ret;
+ u8 val;
regs = dev->info->regs;
- dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
- dev->info->num_vlans, GFP_KERNEL);
- if (!dev->vlan_cache)
- return -ENOMEM;
+ /* KSZ8995XA has no SW controlled VLAN handling */
+ if (!ksz_is_ksz8995xa(dev)) {
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->info->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+ }
ret = ksz8_reset_switch(dev);
if (ret) {
@@ -2711,9 +2798,10 @@ static int ksz8_setup(struct dsa_switch *ds)
* Make sure unicast VLAN boundary is set as default and
* enable no excessive collision drop.
*/
- ret = ksz_rmw8(dev, REG_SW_CTRL_2,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP);
+ val = NO_EXC_COLLISION_DROP;
+ if (!ksz_is_ksz8995xa(dev))
+ val |= UNICAST_VLAN_BOUNDARY;
+ ret = ksz_rmw8(dev, REG_SW_CTRL_2, val, val);
if (ret)
return ret;
@@ -2721,11 +2809,15 @@ static 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_ksz8995xa(dev))
+ ksz_cfg(dev, REG_SW_CTRL_9, SW_SPECIAL_TAG, true);
+ else if (!ksz_is_ksz88x3(dev))
ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true);
- for (i = 0; i < (dev->info->num_vlans / 4); i++)
- ksz8_r_vlan_entries(dev, i);
+ if (!ksz_is_ksz8995xa(dev)) {
+ for (i = 0; i < (dev->info->num_vlans / 4); i++)
+ ksz8_r_vlan_entries(dev, i);
+ }
/* Make sure PME (WoL) is not enabled. If requested, it will
* be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs
@@ -2976,6 +3068,13 @@ static int ksz8_switch_init(struct ksz_device *dev)
return 0;
}
+static enum dsa_tag_protocol ksz8995xa_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_KS8995;
+}
+
static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mp)
@@ -3072,6 +3171,16 @@ const struct phylink_mac_ops ksz8_phylink_mac_ops = {
.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
};
+/*
+ * The KS(Z)8995XA has no indirect access, meaning no MIB counters,
+ * no FDB access, and no VLAN handling.
+ */
+const struct ksz_dev_ops ksz8995xa_dev_ops = {
+ .get_port_addr = ksz8_get_port_addr,
+ .cfg_port_member = ksz8_cfg_port_member,
+ .init = ksz8_switch_init,
+};
+
const struct ksz_dev_ops ksz8463_dev_ops = {
.get_port_addr = ksz8463_get_port_addr,
.cfg_port_member = ksz8_cfg_port_member,
@@ -3111,6 +3220,38 @@ const struct ksz_dev_ops ksz88xx_dev_ops = {
.pme_pwrite8 = ksz8_pme_pwrite8,
};
+/*
+ * Restricted operations for KSZ8995XA, so many things are not supported
+ * by this old switch that we need diet DSA operations.
+ */
+const struct dsa_switch_ops ksz8995xa_switch_ops = {
+ .get_tag_protocol = ksz8995xa_get_tag_protocol,
+ .setup = ksz8_setup,
+ .teardown = ksz_teardown,
+ .phy_read = ksz8_phy_read16,
+ .phy_write = ksz8_phy_write16,
+ .phylink_get_caps = ksz8_phylink_get_caps,
+ .port_setup = ksz8_dsa_port_setup,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_set_mac_address = ksz_port_set_mac_address,
+ .port_stp_state_set = ksz_port_stp_state_set,
+ .port_pre_bridge_flags = ksz_port_pre_bridge_flags,
+ .port_bridge_flags = ksz_port_bridge_flags,
+ .port_fast_age = ksz8_flush_dyn_mac_table,
+ .port_mirror_add = ksz8_port_mirror_add,
+ .port_mirror_del = ksz8_port_mirror_del,
+ .port_change_mtu = ksz8995xa_change_mtu,
+ .port_max_mtu = ksz88xx_max_mtu,
+ .port_get_default_prio = ksz_port_get_default_prio,
+ .port_set_default_prio = ksz_port_set_default_prio,
+ .port_get_dscp_prio = ksz_port_get_dscp_prio,
+ .port_add_dscp_prio = ksz_port_add_dscp_prio,
+ .port_del_dscp_prio = ksz_port_del_dscp_prio,
+ .port_get_apptrust = ksz_port_get_apptrust,
+ .port_set_apptrust = ksz_port_set_apptrust,
+};
+
const struct dsa_switch_ops ksz8463_switch_ops = {
.get_tag_protocol = ksz8463_get_tag_protocol,
.connect_tag_protocol = ksz8463_connect_tag_protocol,
diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h
index bc371cc26c6f..9b37c56c9617 100644
--- a/drivers/net/dsa/microchip/ksz8.h
+++ b/drivers/net/dsa/microchip/ksz8.h
@@ -12,11 +12,13 @@
#include <net/dsa.h>
#include "ksz_common.h"
+extern const struct ksz_dev_ops ksz8995xa_dev_ops;
extern const struct ksz_dev_ops ksz8463_dev_ops;
extern const struct ksz_dev_ops ksz87xx_dev_ops;
extern const struct ksz_dev_ops ksz88xx_dev_ops;
extern const struct phylink_mac_ops ksz88x3_phylink_mac_ops;
extern const struct phylink_mac_ops ksz8_phylink_mac_ops;
+extern const struct dsa_switch_ops ksz8995xa_switch_ops;
extern const struct dsa_switch_ops ksz8463_switch_ops;
extern const struct dsa_switch_ops ksz87xx_switch_ops;
extern const struct dsa_switch_ops ksz88xx_switch_ops;
diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h
index 6bc511da1f7d..bd99699aae02 100644
--- a/drivers/net/dsa/microchip/ksz8_reg.h
+++ b/drivers/net/dsa/microchip/ksz8_reg.h
@@ -30,6 +30,11 @@
#define KSZ88X3_REG_FVID_AND_HOST_MODE 0xC6
#define KSZ88X3_PORT3_RMII_CLK_INTERNAL BIT(3)
+#define REG_SW_ID_0 0x00
+#define REG_SW_ID_1 0x01
+
+#define SW_START_SWITCH BIT(0)
+
#define REG_SW_CTRL_0 0x02
#define SW_NEW_BACKOFF BIT(7)
@@ -95,6 +100,8 @@
#define SW_LED_LINK_ACT_DUPLEX 2
#define SW_LED_LINK_DUPLEX 3
+#define SW_SPECIAL_TAG BIT(0) /* KSZ8995XA only */
+
#define REG_SW_CTRL_10 0x0C
#define SW_PASS_PAUSE BIT(0)
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index 1a9d6f83a023..6c15ac97a7e3 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -446,6 +446,20 @@ static const u8 ksz8895_shifts[] = {
[DYNAMIC_MAC_SRC_PORT] = 24,
};
+static const u16 ksz8995xa_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x68,
+ [P_FORCE_CTRL] = 0x0C,
+ [P_LINK_STATUS] = 0x0E,
+ [P_LOCAL_CTRL] = 0x0C,
+ [P_NEG_RESTART_CTRL] = 0x0D,
+ [P_REMOTE_STATUS] = 0x0E,
+ [P_SPEED_STATUS] = 0x09,
+ [P_STP_CTRL] = 0x02,
+ [S_START_CTRL] = 0x01,
+ [S_BROADCAST_CTRL] = 0x06,
+ [S_MULTICAST_CTRL] = 0x04,
+};
+
static const u16 ksz9477_regs[] = {
[REG_SW_MAC_ADDR] = 0x0302,
[P_STP_CTRL] = 0x0B04,
@@ -1377,6 +1391,21 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true, false},
},
+ [KSZ8995XA] = {
+ .chip_id = KSZ8995XA_CHIP_ID, /* Also known as KS8995XA */
+ .dev_name = "KSZ8995XA",
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 5, /* total cpu and user ports */
+ .num_tx_queues = 2, /* low/hi priority queues, no more */
+ .num_ipms = 2,
+ .ops = &ksz8995xa_dev_ops,
+ .switch_ops = &ksz8995xa_switch_ops,
+ .phylink_mac_ops = &ksz88x3_phylink_mac_ops,
+ .regs = ksz8995xa_regs,
+ .supports_mii = {true, true, true, true, true},
+ .internal_phy = {true, true, true, true, false},
+ },
+
[KSZ9477] = {
.chip_id = KSZ9477_CHIP_ID,
.dev_name = "KSZ9477",
@@ -2649,6 +2678,10 @@ void ksz_init_mib_timer(struct ksz_device *dev)
{
int i;
+ /* KSZ8995XA lacks MiB features */
+ if (ksz_is_ksz8995xa(dev))
+ return;
+
INIT_DELAYED_WORK(&dev->mib_read, ksz_mib_read_work);
for (i = 0; i < dev->info->port_cnt; i++) {
@@ -2944,11 +2977,15 @@ static int ksz_switch_detect(struct ksz_device *dev)
return -ENODEV;
break;
case KSZ8895_FAMILY_ID:
- if (id2 == KSZ8895_CHIP_ID_95 ||
- id2 == KSZ8895_CHIP_ID_95R)
+ if (id2 == KSZ8895_CHIP_ID_95XA) {
+ dev->chip_id = KSZ8995XA_CHIP_ID;
+ break;
+ } else if (id2 == KSZ8895_CHIP_ID_95 ||
+ id2 == KSZ8895_CHIP_ID_95R) {
dev->chip_id = KSZ8895_CHIP_ID;
- else
+ } else {
return -ENODEV;
+ }
ret = ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id4);
if (ret)
return ret;
@@ -4029,11 +4066,13 @@ int ksz_switch_register(struct ksz_device *dev)
if (ret)
return ret;
- /* Read MIB counters every 30 seconds to avoid overflow. */
- dev->mib_read_interval = msecs_to_jiffies(5000);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* Read MIB counters every 30 seconds to avoid overflow. */
+ dev->mib_read_interval = msecs_to_jiffies(5000);
- /* Start the MIB timer. */
- schedule_delayed_work(&dev->mib_read, 0);
+ /* Start the MIB timer. */
+ schedule_delayed_work(&dev->mib_read, 0);
+ }
return ret;
}
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index cbe98494578c..93be74f7a265 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -281,6 +281,7 @@ enum ksz_model {
KSZ88X3,
KSZ8864,
KSZ8895,
+ KSZ8995XA,
KSZ9477,
KSZ9896,
KSZ9897,
@@ -789,7 +790,13 @@ static inline bool ksz_is_ksz88x3(struct ksz_device *dev)
static inline bool ksz_is_8895_family(struct ksz_device *dev)
{
return dev->chip_id == KSZ8895_CHIP_ID ||
- dev->chip_id == KSZ8864_CHIP_ID;
+ dev->chip_id == KSZ8864_CHIP_ID ||
+ dev->chip_id == KSZ8995XA_CHIP_ID;
+}
+
+static inline bool ksz_is_ksz8995xa(struct ksz_device *dev)
+{
+ return dev->chip_id == KSZ8995XA_CHIP_ID;
}
static inline bool is_ksz8(struct ksz_device *dev)
@@ -860,6 +867,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ87_CHIP_ID_94 0x6
#define KSZ87_CHIP_ID_95 0x9
#define KSZ88_CHIP_ID_63 0x3
+#define KSZ8895_CHIP_ID_95XA 0x0
#define KSZ8895_CHIP_ID_95 0x4
#define KSZ8895_CHIP_ID_95R 0x6
@@ -923,6 +931,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ8863_HUGE_PACKET_SIZE 1916
#define KSZ8863_NORMAL_PACKET_SIZE 1536
#define KSZ8_LEGAL_PACKET_SIZE 1518
+#define KSZ8995XA_LEGAL_PACKET_SIZE 1522
#define KSZ9477_MAX_FRAME_SIZE 9000
#define KSZ8873_REG_GLOBAL_CTRL_12 0x0e
diff --git a/drivers/net/dsa/microchip/ksz_dcb.c b/drivers/net/dsa/microchip/ksz_dcb.c
index 7131c5caac54..968de88e8ddd 100644
--- a/drivers/net/dsa/microchip/ksz_dcb.c
+++ b/drivers/net/dsa/microchip/ksz_dcb.c
@@ -20,10 +20,12 @@
#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 KSZ8995XA_PORT_BASED_PRIO BIT(4)
#define KSZ8463_REG_TOS_DSCP_CTRL 0x16
#define KSZ88X3_REG_TOS_DSCP_CTRL 0x60
#define KSZ8765_REG_TOS_DSCP_CTRL 0x90
+#define KSZ8995XA_REG_TOS_DSCP_CTRL_7 0x67
#define KSZ9477_REG_SW_MAC_TOS_CTRL 0x033e
#define KSZ9477_SW_TOS_DSCP_REMAP BIT(0)
@@ -98,8 +100,11 @@ static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg,
{
if (is_ksz8(dev)) {
*reg = KSZ8_REG_PORT_1_CTRL_0;
- *mask = KSZ8_PORT_BASED_PRIO_M;
- *shift = __bf_shf(KSZ8_PORT_BASED_PRIO_M);
+ if (ksz_is_ksz8995xa(dev))
+ *mask = KSZ8995XA_PORT_BASED_PRIO;
+ else
+ *mask = KSZ8_PORT_BASED_PRIO_M;
+ *shift = __bf_shf(*mask);
if (ksz_is_ksz8463(dev))
*reg = KSZ8463_REG_PORT_1_CTRL_0;
} else {
@@ -112,31 +117,45 @@ static void ksz_get_default_port_prio_reg(struct ksz_device *dev, int *reg,
/**
* ksz_get_dscp_prio_reg - Retrieves the DSCP-to-priority-mapping register
* @dev: Pointer to the KSZ switch device structure
+ * @dscp: DSCP value for which to retrieve the register
* @reg: Pointer to the register address to be set
- * @per_reg: Pointer to the number of DSCP values per register
* @mask: Pointer to the mask to be set
+ * @shift: Pointer to the bit shift to be set
*
- * This function retrieves the DSCP to priority mapping register, the number of
- * DSCP values per register, and the mask to be set.
+ * This function retrieves the register, mask and shift for a DSCP to priority
+ * mapping entry.
*/
-static void ksz_get_dscp_prio_reg(struct ksz_device *dev, int *reg,
- int *per_reg, u8 *mask)
+static void ksz_get_dscp_prio_reg(struct ksz_device *dev, u8 dscp, int *reg,
+ u8 *mask, int *shift)
{
+ int per_reg;
+
+ if (ksz_is_ksz8995xa(dev)) {
+ /* KSZ8995XA stores DSCP groups in descending register order. */
+ *reg = KSZ8995XA_REG_TOS_DSCP_CTRL_7 - dscp / 8;
+ *mask = BIT(0);
+ *shift = dscp % 8;
+ return;
+ }
+
if (ksz_is_ksz87xx(dev) || ksz_is_8895_family(dev)) {
*reg = KSZ8765_REG_TOS_DSCP_CTRL;
- *per_reg = 4;
+ per_reg = 4;
*mask = GENMASK(1, 0);
} else if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) {
*reg = KSZ88X3_REG_TOS_DSCP_CTRL;
- *per_reg = 4;
+ 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;
+ per_reg = 2;
*mask = GENMASK(2, 0);
}
+
+ *reg += dscp / per_reg;
+ *shift = (dscp % per_reg) * (8 / per_reg);
}
/**
@@ -236,10 +255,10 @@ int ksz_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio)
int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
{
struct ksz_device *dev = ds->priv;
- int reg, per_reg, ret, shift;
+ int reg, ret, shift;
u8 data, mask;
- ksz_get_dscp_prio_reg(dev, ®, &per_reg, &mask);
+ ksz_get_dscp_prio_reg(dev, dscp, ®, &mask, &shift);
/* If DSCP remapping is disabled, DSCP bits 3-5 are used as Internal
* Priority Map (IPM)
@@ -260,13 +279,10 @@ int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
/* In case DSCP remapping is enabled, we need to write the DSCP to
* priority mapping table.
*/
- reg += dscp / per_reg;
ret = ksz_read8(dev, reg, &data);
if (ret)
return ret;
- shift = (dscp % per_reg) * (8 / per_reg);
-
return (data >> shift) & mask;
}
@@ -283,15 +299,12 @@ int ksz_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp)
*/
static int ksz_set_global_dscp_entry(struct ksz_device *dev, u8 dscp, u8 ipm)
{
- int reg, per_reg, shift;
+ int reg, shift;
u8 mask;
- ksz_get_dscp_prio_reg(dev, ®, &per_reg, &mask);
-
- shift = (dscp % per_reg) * (8 / per_reg);
+ ksz_get_dscp_prio_reg(dev, dscp, ®, &mask, &shift);
- return ksz_rmw8(dev, reg + (dscp / per_reg), mask << shift,
- ipm << shift);
+ return ksz_rmw8(dev, reg, mask << shift, ipm << shift);
}
/**
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 77aecac32466..2c55014246e5 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -161,7 +161,8 @@ static int ksz_spi_probe(struct spi_device *spi)
chip->chip_id == KSZ8794_CHIP_ID ||
chip->chip_id == KSZ8765_CHIP_ID)
regmap_config = ksz8795_regmap_config;
- else if (chip->chip_id == KSZ8895_CHIP_ID ||
+ else if (chip->chip_id == KSZ8995XA_CHIP_ID ||
+ chip->chip_id == KSZ8895_CHIP_ID ||
chip->chip_id == KSZ8864_CHIP_ID)
regmap_config = ksz8863_regmap_config;
else
@@ -185,7 +186,10 @@ static int ksz_spi_probe(struct spi_device *spi)
dev->pdata = spi->dev.platform_data;
/* setup spi */
- spi->mode = SPI_MODE_3;
+ if (chip->chip_id == KSZ8995XA_CHIP_ID)
+ spi->mode = SPI_MODE_0;
+ else
+ spi->mode = SPI_MODE_3;
ret = spi_setup(spi);
if (ret)
return ret;
@@ -239,6 +243,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.compatible = "micrel,ksz8795",
.data = &ksz_switch_chips[KSZ8795]
},
+ {
+ .compatible = "micrel,ks8995",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
@@ -271,6 +279,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.compatible = "microchip,ksz8895",
.data = &ksz_switch_chips[KSZ8895]
},
+ {
+ .compatible = "microchip,ksz8995xa",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz9477",
.data = &ksz_switch_chips[KSZ9477]
@@ -332,6 +344,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[] = {
+ { "ks8995" },
{ "ksz8463" },
{ "ksz8765" },
{ "ksz8794" },
@@ -340,6 +353,7 @@ static const struct spi_device_id ksz_spi_ids[] = {
{ "ksz8864" },
{ "ksz8873" },
{ "ksz8895" },
+ { "ksz8995" },
{ "ksz9477" },
{ "ksz9896" },
{ "ksz9897" },
diff --git a/include/linux/platform_data/microchip-ksz.h b/include/linux/platform_data/microchip-ksz.h
index 028781ad4059..d8eddd21c3c7 100644
--- a/include/linux/platform_data/microchip-ksz.h
+++ b/include/linux/platform_data/microchip-ksz.h
@@ -31,6 +31,7 @@ enum ksz_chip_id {
KSZ88X3_CHIP_ID = 0x8830,
KSZ8864_CHIP_ID = 0x8864,
KSZ8895_CHIP_ID = 0x8895,
+ KSZ8995XA_CHIP_ID = 0x8995,
KSZ9477_CHIP_ID = 0x00947700,
KSZ9896_CHIP_ID = 0x00989600,
KSZ9897_CHIP_ID = 0x00989700,
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v8 5/5] net: dsa: ks8995: Delete surplus driver
2026-08-31 20:54 [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
` (3 preceding siblings ...)
2026-08-31 20:54 ` [PATCH net-next v8 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
@ 2026-08-31 20:54 ` Linus Walleij
2026-09-02 9:50 ` [net-next,v8,5/5] " netdev-bot+sashiko
4 siblings, 1 reply; 11+ messages in thread
From: Linus Walleij @ 2026-08-31 20:54 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King, Nicolai Buchwitz
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
The Microchip ksz driver now handles all switches that the
old driver was handling, but better.
Delete the old driver, but leave a stub behind in Kconfig
so new users will be automatically phased over to the new
symbol when migrating their configs.
The old driver supports platform data (board file)
instantiation, but nothing in the kernel tree makes use
of this legacy mechanism so it is fine to delete.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 10 +-
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 -----------------------------------------------
3 files changed, 6 insertions(+), 862 deletions(-)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 68fb175cc36a..55a9714033a6 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -110,11 +110,13 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
- depends on !NET_DSA_MICROCHIP_KSZ_SPI
- select NET_DSA_TAG_NONE
+ select NET_DSA_MICROCHIP_KSZ_COMMON
+ select NET_DSA_MICROCHIP_KSZ_SPI
help
- This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
- switches, managed over SPI.
+ This is a transitional option for users who had NET_DSA_KS8995
+ enabled. It automatically enables the new NET_DSA_MICROCHIP_KSZ_SPI
+ driver which supersedes it. This option will be removed in a future
+ kernel release.
config NET_DSA_SMSC_LAN9303
tristate
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index 6ceb78a755d7..9923137429f8 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -2,7 +2,6 @@
obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm-sf2.o
bcm-sf2-objs := bcm_sf2.o bcm_sf2_cfp.o
obj-$(CONFIG_NET_DSA_LOOP) += dsa_loop.o
-obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
diff --git a/drivers/net/dsa/ks8995.c b/drivers/net/dsa/ks8995.c
deleted file mode 100644
index 77d8b842693c..000000000000
--- a/drivers/net/dsa/ks8995.c
+++ /dev/null
@@ -1,857 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
- *
- * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
- * Copyright (C) 2025 Linus Walleij <linus.walleij@linaro.org>
- *
- * This file was based on: drivers/spi/at25.c
- * Copyright (C) 2006 David Brownell
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/bits.h>
-#include <linux/if_bridge.h>
-#include <linux/if_vlan.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/gpio/consumer.h>
-#include <linux/of.h>
-#include <linux/spi/spi.h>
-#include <net/dsa.h>
-
-#define DRV_VERSION "0.1.1"
-#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver"
-
-/* ------------------------------------------------------------------------ */
-
-#define KS8995_REG_ID0 0x00 /* Chip ID0 */
-#define KS8995_REG_ID1 0x01 /* Chip ID1 */
-
-#define KS8995_REG_GC0 0x02 /* Global Control 0 */
-
-#define KS8995_GC0_P5_PHY BIT(3) /* Port 5 PHY enabled */
-
-#define KS8995_REG_GC1 0x03 /* Global Control 1 */
-#define KS8995_REG_GC2 0x04 /* Global Control 2 */
-
-#define KS8995_GC2_HUGE BIT(2) /* Huge packet support */
-#define KS8995_GC2_LEGAL BIT(1) /* Legal size override */
-
-#define KS8995_REG_GC3 0x05 /* Global Control 3 */
-#define KS8995_REG_GC4 0x06 /* Global Control 4 */
-
-#define KS8995_GC4_10BT BIT(4) /* Force switch to 10Mbit */
-#define KS8995_GC4_MII_FLOW BIT(5) /* MII full-duplex flow control enable */
-#define KS8995_GC4_MII_HD BIT(6) /* MII half-duplex mode enable */
-
-#define KS8995_REG_GC5 0x07 /* Global Control 5 */
-#define KS8995_REG_GC6 0x08 /* Global Control 6 */
-#define KS8995_REG_GC7 0x09 /* Global Control 7 */
-#define KS8995_REG_GC8 0x0a /* Global Control 8 */
-#define KS8995_REG_GC9 0x0b /* Global Control 9 */
-
-#define KS8995_GC9_SPECIAL BIT(0) /* Special tagging mode (DSA) */
-
-/* In DSA the ports 1-4 are numbered 0-3 and the CPU port is port 4 */
-#define KS8995_REG_PC(p, r) (0x10 + (0x10 * (p)) + (r)) /* Port Control */
-#define KS8995_REG_PS(p, r) (0x1e + (0x10 * (p)) + (r)) /* Port Status */
-
-#define KS8995_REG_PC0 0x00 /* Port Control 0 */
-#define KS8995_REG_PC1 0x01 /* Port Control 1 */
-#define KS8995_REG_PC2 0x02 /* Port Control 2 */
-#define KS8995_REG_PC3 0x03 /* Port Control 3 */
-#define KS8995_REG_PC4 0x04 /* Port Control 4 */
-#define KS8995_REG_PC5 0x05 /* Port Control 5 */
-#define KS8995_REG_PC6 0x06 /* Port Control 6 */
-#define KS8995_REG_PC7 0x07 /* Port Control 7 */
-#define KS8995_REG_PC8 0x08 /* Port Control 8 */
-#define KS8995_REG_PC9 0x09 /* Port Control 9 */
-#define KS8995_REG_PC10 0x0a /* Port Control 10 */
-#define KS8995_REG_PC11 0x0b /* Port Control 11 */
-#define KS8995_REG_PC12 0x0c /* Port Control 12 */
-#define KS8995_REG_PC13 0x0d /* Port Control 13 */
-
-#define KS8995_PC0_TAG_INS BIT(2) /* Enable tag insertion on port */
-#define KS8995_PC0_TAG_REM BIT(1) /* Enable tag removal on port */
-#define KS8995_PC0_PRIO_EN BIT(0) /* Enable priority handling */
-
-#define KS8995_PC2_TXEN BIT(2) /* Enable TX on port */
-#define KS8995_PC2_RXEN BIT(1) /* Enable RX on port */
-#define KS8995_PC2_LEARN_DIS BIT(0) /* Disable learning on port */
-
-#define KS8995_PC13_TXDIS BIT(6) /* Disable transmitter */
-#define KS8995_PC13_PWDN BIT(3) /* Power down */
-
-#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */
-#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */
-#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */
-#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */
-#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */
-#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */
-#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */
-#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */
-
-#define KS8995_REG_MAC0 0x68 /* MAC address 0 */
-#define KS8995_REG_MAC1 0x69 /* MAC address 1 */
-#define KS8995_REG_MAC2 0x6a /* MAC address 2 */
-#define KS8995_REG_MAC3 0x6b /* MAC address 3 */
-#define KS8995_REG_MAC4 0x6c /* MAC address 4 */
-#define KS8995_REG_MAC5 0x6d /* MAC address 5 */
-
-#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */
-#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */
-#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */
-#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */
-#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */
-#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */
-#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */
-#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */
-#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */
-#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */
-
-#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */
-
-#define KS8995_REGS_SIZE 0x80
-#define KSZ8864_REGS_SIZE 0x100
-#define KSZ8795_REGS_SIZE 0x100
-
-#define ID1_CHIPID_M 0xf
-#define ID1_CHIPID_S 4
-#define ID1_REVISION_M 0x7
-#define ID1_REVISION_S 1
-#define ID1_START_SW 1 /* start the switch */
-
-#define FAMILY_KS8995 0x95
-#define FAMILY_KSZ8795 0x87
-#define CHIPID_M 0
-#define KS8995_CHIP_ID 0x00
-#define KSZ8864_CHIP_ID 0x01
-#define KSZ8795_CHIP_ID 0x09
-
-#define KS8995_CMD_WRITE 0x02U
-#define KS8995_CMD_READ 0x03U
-
-#define KS8995_CPU_PORT 4
-#define KS8995_NUM_PORTS 5 /* 5 ports including the CPU port */
-#define KS8995_RESET_DELAY 10 /* usec */
-
-enum ks8995_chip_variant {
- ks8995,
- ksz8864,
- ksz8795,
- max_variant
-};
-
-struct ks8995_chip_params {
- char *name;
- int family_id;
- int chip_id;
- int regs_size;
- int addr_width;
- int addr_shift;
-};
-
-static const struct ks8995_chip_params ks8995_chip[] = {
- [ks8995] = {
- .name = "KS8995MA",
- .family_id = FAMILY_KS8995,
- .chip_id = KS8995_CHIP_ID,
- .regs_size = KS8995_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8864] = {
- .name = "KSZ8864RMN",
- .family_id = FAMILY_KS8995,
- .chip_id = KSZ8864_CHIP_ID,
- .regs_size = KSZ8864_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8795] = {
- .name = "KSZ8795CLX",
- .family_id = FAMILY_KSZ8795,
- .chip_id = KSZ8795_CHIP_ID,
- .regs_size = KSZ8795_REGS_SIZE,
- .addr_width = 12,
- .addr_shift = 1,
- },
-};
-
-struct ks8995_switch {
- struct spi_device *spi;
- struct device *dev;
- struct dsa_switch *ds;
- struct mutex lock;
- struct gpio_desc *reset_gpio;
- struct bin_attribute regs_attr;
- const struct ks8995_chip_params *chip;
- int revision_id;
- unsigned int max_mtu[KS8995_NUM_PORTS];
-};
-
-static const struct spi_device_id ks8995_id[] = {
- {"ks8995", ks8995},
- {"ksz8864", ksz8864},
- {"ksz8795", ksz8795},
- { }
-};
-MODULE_DEVICE_TABLE(spi, ks8995_id);
-
-static const struct of_device_id ks8995_spi_of_match[] = {
- { .compatible = "micrel,ks8995" },
- { .compatible = "micrel,ksz8864" },
- { .compatible = "micrel,ksz8795" },
- { },
-};
-MODULE_DEVICE_TABLE(of, ks8995_spi_of_match);
-
-static inline u8 get_chip_id(u8 val)
-{
- return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
-}
-
-static inline u8 get_chip_rev(u8 val)
-{
- return (val >> ID1_REVISION_S) & ID1_REVISION_M;
-}
-
-/* create_spi_cmd - create a chip specific SPI command header
- * @ks: pointer to switch instance
- * @cmd: SPI command for switch
- * @address: register address for command
- *
- * Different chip families use different bit pattern to address the switches
- * registers:
- *
- * KS8995: 8bit command + 8bit address
- * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
- */
-static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
- unsigned address)
-{
- u16 result = cmd;
-
- /* make room for address (incl. address shift) */
- result <<= ks->chip->addr_width + ks->chip->addr_shift;
- /* add address */
- result |= address << ks->chip->addr_shift;
- /* SPI protocol needs big endian */
- return cpu_to_be16(result);
-}
-/* ------------------------------------------------------------------------ */
-static int ks8995_read(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].rx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static int ks8995_write(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].tx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
-{
- return ks8995_read(ks, buf, addr, 1) != 1;
-}
-
-static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
-{
- char buf = val;
-
- return ks8995_write(ks, &buf, addr, 1) != 1;
-}
-
-/* ------------------------------------------------------------------------ */
-
-static int ks8995_stop(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
-}
-
-static int ks8995_start(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
-}
-
-static int ks8995_reset(struct ks8995_switch *ks)
-{
- int err;
-
- err = ks8995_stop(ks);
- if (err)
- return err;
-
- udelay(KS8995_RESET_DELAY);
-
- return ks8995_start(ks);
-}
-
-/* ks8995_get_revision - get chip revision
- * @ks: pointer to switch instance
- *
- * Verify chip family and id and get chip revision.
- */
-static int ks8995_get_revision(struct ks8995_switch *ks)
-{
- int err;
- u8 id0, id1, ksz8864_id;
-
- /* read family id */
- err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify family id */
- if (id0 != ks->chip->family_id) {
- dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
- ks->chip->family_id, id0);
- err = -ENODEV;
- goto err_out;
- }
-
- switch (ks->chip->family_id) {
- case FAMILY_KS8995:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify chip id */
- if ((get_chip_id(id1) == CHIPID_M) &&
- (get_chip_id(id1) == ks->chip->chip_id)) {
- /* KS8995MA */
- ks->revision_id = get_chip_rev(id1);
- } else if (get_chip_id(id1) != CHIPID_M) {
- /* KSZ8864RMN */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if ((ksz8864_id & 0x80) &&
- (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
- ks->revision_id = get_chip_rev(id1);
- }
-
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- case FAMILY_KSZ8795:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if (get_chip_id(id1) == ks->chip->chip_id) {
- ks->revision_id = get_chip_rev(id1);
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- default:
- dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
- err = -ENODEV;
- break;
- }
-err_out:
- return err;
-}
-
-static int ks8995_check_config(struct ks8995_switch *ks)
-{
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC0, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC0\n");
- return ret;
- }
-
- dev_dbg(ks->dev, "port 5 PHY %senabled\n",
- (val & KS8995_GC0_P5_PHY) ? "" : "not ");
-
- val |= KS8995_GC0_P5_PHY;
- ret = ks8995_write_reg(ks, KS8995_REG_GC0, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC0\n");
-
- dev_dbg(ks->dev, "set KS8995_REG_GC0 to 0x%02x\n", val);
-
- return 0;
-}
-
-static void
-ks8995_mac_config(struct phylink_config *config, unsigned int mode,
- const struct phylink_link_state *state)
-{
-}
-
-static void
-ks8995_mac_link_up(struct phylink_config *config, struct phy_device *phydev,
- unsigned int mode, phy_interface_t interface,
- int speed, int duplex, bool tx_pause, bool rx_pause)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
- int ret;
- u8 val;
-
- /* Allow forcing the mode on the fixed CPU port, no autonegotiation.
- * We assume autonegotiation works on the PHY-facing ports.
- */
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link up on CPU port (%d)\n", port);
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC4, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC4\n");
- return;
- }
-
- /* Conjure port config */
- switch (speed) {
- case SPEED_10:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val |= KS8995_GC4_10BT;
- break;
- case SPEED_100:
- default:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val &= ~KS8995_GC4_10BT;
- break;
- }
-
- if (duplex == DUPLEX_HALF) {
- dev_dbg(ks->dev, "set switch MII to half duplex\n");
- val |= KS8995_GC4_MII_HD;
- } else {
- dev_dbg(ks->dev, "set switch MII to full duplex\n");
- val &= ~KS8995_GC4_MII_HD;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_GC4 to %02x\n", val);
-
- /* Enable the CPU port */
- ret = ks8995_write_reg(ks, KS8995_REG_GC4, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC4\n");
-}
-
-static void
-ks8995_mac_link_down(struct phylink_config *config, unsigned int mode,
- phy_interface_t interface)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
-
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link down on CPU port (%d)\n", port);
-
- /* Disable the CPU port */
-}
-
-static const struct phylink_mac_ops ks8995_phylink_mac_ops = {
- .mac_config = ks8995_mac_config,
- .mac_link_up = ks8995_mac_link_up,
- .mac_link_down = ks8995_mac_link_down,
-};
-
-static enum
-dsa_tag_protocol ks8995_get_tag_protocol(struct dsa_switch *ds,
- int port,
- enum dsa_tag_protocol mp)
-{
- /* This switch actually uses the 6 byte KS8995 protocol */
- return DSA_TAG_PROTO_NONE;
-}
-
-static int ks8995_setup(struct dsa_switch *ds)
-{
- return 0;
-}
-
-static int ks8995_port_enable(struct dsa_switch *ds, int port,
- struct phy_device *phy)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "enable port %d\n", port);
-
- return 0;
-}
-
-static void ks8995_port_disable(struct dsa_switch *ds, int port)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "disable port %d\n", port);
-}
-
-static int ks8995_port_pre_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- /* We support enabling/disabling learning */
- if (flags.mask & ~(BR_LEARNING))
- return -EINVAL;
-
- return 0;
-}
-
-static int ks8995_port_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- if (flags.mask & BR_LEARNING) {
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
-
- if (flags.val & BR_LEARNING)
- val &= ~KS8995_PC2_LEARN_DIS;
- else
- val |= KS8995_PC2_LEARN_DIS;
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
- }
-
- return 0;
-}
-
-static void ks8995_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- /* Set the bits for the different STP states in accordance with
- * the datasheet, pages 36-37 "Spanning tree support".
- */
- switch (state) {
- case BR_STATE_DISABLED:
- case BR_STATE_BLOCKING:
- case BR_STATE_LISTENING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val |= KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_LEARNING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_FORWARDING:
- val |= KS8995_PC2_TXEN;
- val |= KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- default:
- dev_err(ks->dev, "unknown bridge state requested\n");
- return;
- }
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_PC2 for port %d to %02x\n", port, val);
-}
-
-static void ks8995_phylink_get_caps(struct dsa_switch *dsa, int port,
- struct phylink_config *config)
-{
- unsigned long *interfaces = config->supported_interfaces;
-
- if (port == KS8995_CPU_PORT)
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
-
- if (port <= 3) {
- /* Internal PHYs */
- __set_bit(PHY_INTERFACE_MODE_INTERNAL, interfaces);
- /* phylib default */
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
- }
-
- config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100;
-}
-
-/* Huge packet support up to 1916 byte packages "inclusive"
- * which means that tags are included. If the bit is not set
- * it is 1536 bytes "inclusive". We present the length without
- * tags or ethernet headers. The setting affects all ports.
- */
-static int ks8995_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-{
- struct ks8995_switch *ks = ds->priv;
- unsigned int max_mtu;
- int ret;
- u8 val;
- int i;
-
- ks->max_mtu[port] = new_mtu;
-
- /* Roof out the MTU for the entire switch to the greatest
- * common denominator: the biggest set for any one port will
- * be the biggest MTU for the switch.
- */
- max_mtu = ETH_DATA_LEN;
- for (i = 0; i < KS8995_NUM_PORTS; i++) {
- if (ks->max_mtu[i] > max_mtu)
- max_mtu = ks->max_mtu[i];
- }
-
- /* Translate to layer 2 size.
- * Add ethernet and (possible) VLAN headers, and checksum to the size.
- * For ETH_DATA_LEN (1500 bytes) this will add up to 1522 bytes.
- */
- max_mtu += VLAN_ETH_HLEN;
- max_mtu += ETH_FCS_LEN;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC2, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC2\n");
- return ret;
- }
-
- if (max_mtu <= 1522) {
- val &= ~KS8995_GC2_HUGE;
- val &= ~KS8995_GC2_LEGAL;
- } else if (max_mtu > 1522 && max_mtu <= 1536) {
- /* This accepts packets up to 1536 bytes */
- val &= ~KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- } else {
- /* This accepts packets up to 1916 bytes */
- val |= KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- }
-
- dev_dbg(ks->dev, "new max MTU %d bytes (inclusive)\n", max_mtu);
-
- ret = ks8995_write_reg(ks, KS8995_REG_GC2, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC2\n");
-
- return ret;
-}
-
-static int ks8995_get_max_mtu(struct dsa_switch *ds, int port)
-{
- return 1916 - ETH_HLEN - ETH_FCS_LEN;
-}
-
-static const struct dsa_switch_ops ks8995_ds_ops = {
- .get_tag_protocol = ks8995_get_tag_protocol,
- .setup = ks8995_setup,
- .port_pre_bridge_flags = ks8995_port_pre_bridge_flags,
- .port_bridge_flags = ks8995_port_bridge_flags,
- .port_enable = ks8995_port_enable,
- .port_disable = ks8995_port_disable,
- .port_stp_state_set = ks8995_port_stp_state_set,
- .port_change_mtu = ks8995_change_mtu,
- .port_max_mtu = ks8995_get_max_mtu,
- .phylink_get_caps = ks8995_phylink_get_caps,
-};
-
-/* ------------------------------------------------------------------------ */
-static int ks8995_probe(struct spi_device *spi)
-{
- struct ks8995_switch *ks;
- int err;
- int variant = spi_get_device_id(spi)->driver_data;
-
- if (variant >= max_variant) {
- dev_err(&spi->dev, "bad chip variant %d\n", variant);
- return -ENODEV;
- }
-
- ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
- if (!ks)
- return -ENOMEM;
-
- mutex_init(&ks->lock);
- ks->spi = spi;
- ks->dev = &spi->dev;
- ks->chip = &ks8995_chip[variant];
-
- ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
- GPIOD_OUT_HIGH);
- err = PTR_ERR_OR_ZERO(ks->reset_gpio);
- if (err) {
- dev_err(&spi->dev,
- "failed to get reset gpio: %d\n", err);
- return err;
- }
-
- err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
- if (err)
- return err;
-
- if (ks->reset_gpio) {
- /*
- * If a reset line was obtained, wait for 100us after
- * de-asserting RESET before accessing any registers, see
- * the KS8995MA datasheet, page 44.
- */
- gpiod_set_value_cansleep(ks->reset_gpio, 0);
- udelay(100);
- }
-
- spi_set_drvdata(spi, ks);
-
- spi->mode = SPI_MODE_0;
- spi->bits_per_word = 8;
- err = spi_setup(spi);
- if (err) {
- dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
- return err;
- }
-
- err = ks8995_get_revision(ks);
- if (err)
- return err;
-
- err = ks8995_reset(ks);
- if (err)
- return err;
-
- dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
- ks->chip->name, ks->chip->chip_id, ks->revision_id);
-
- err = ks8995_check_config(ks);
- if (err)
- return err;
-
- ks->ds = devm_kzalloc(&spi->dev, sizeof(*ks->ds), GFP_KERNEL);
- if (!ks->ds)
- return -ENOMEM;
-
- ks->ds->dev = &spi->dev;
- ks->ds->num_ports = KS8995_NUM_PORTS;
- ks->ds->ops = &ks8995_ds_ops;
- ks->ds->phylink_mac_ops = &ks8995_phylink_mac_ops;
- ks->ds->priv = ks;
-
- err = dsa_register_switch(ks->ds);
- if (err)
- return dev_err_probe(&spi->dev, err,
- "unable to register DSA switch\n");
-
- return 0;
-}
-
-static void ks8995_remove(struct spi_device *spi)
-{
- struct ks8995_switch *ks = spi_get_drvdata(spi);
-
- dsa_unregister_switch(ks->ds);
- /* assert reset */
- gpiod_set_value_cansleep(ks->reset_gpio, 1);
-}
-
-/* ------------------------------------------------------------------------ */
-static struct spi_driver ks8995_driver = {
- .driver = {
- .name = "spi-ks8995",
- .of_match_table = ks8995_spi_of_match,
- },
- .probe = ks8995_probe,
- .remove = ks8995_remove,
- .id_table = ks8995_id,
-};
-
-module_spi_driver(ks8995_driver);
-
-MODULE_DESCRIPTION(DRV_DESC);
-MODULE_VERSION(DRV_VERSION);
-MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
-MODULE_LICENSE("GPL v2");
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [net-next,v8,1/5] net: dsa: microchip: Add fallback Micrel compatibles
2026-08-31 20:54 ` [PATCH net-next v8 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
@ 2026-09-02 9:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 9:50 UTC (permalink / raw)
To: linusw
Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
netdev, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 3 · Low: 1
- [Medium] The mutual exclusion the commit message claims to establish
("Make the KS8995 driver mutually exclusive with this driver to avoid…
- [Medium] At this commit the new `depends on !NET_DSA_MICROCHIP_KSZ_SPI`
disables the whole KS8995 driver whenever NET_DSA_MICROCHIP_KSZ_SPI=y,…
- [Medium] Adding "micrel,ksz8864" to ksz_dt_ids redirects existing
device trees for the KSZ8864RMN to a driver with a different DSA-…
- [Low] The KSZ SPI driver now binds "micrel,ksz8864" and
"micrel,ksz8795" (and, at the series tip, "micrel,ks8995"), but…
--
net: dsa: microchip: Add fallback Micrel compatibles
This adds the legacy "micrel,ksz8864" and "micrel,ksz8795" compatibles to
ksz_dt_ids in the KSZ SPI driver, and makes NET_DSA_KS8995 depend on
!NET_DSA_MICROCHIP_KSZ_SPI so the two drivers are not built together.
> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index 676fb7dffe14f..68fb175cc36ab 100644
> --- a/drivers/net/dsa/Kconfig
> +++ b/drivers/net/dsa/Kconfig
> @@ -110,6 +110,7 @@ config NET_DSA_RZN1_A5PSW
> config NET_DSA_KS8995
> tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
> depends on SPI
> + depends on !NET_DSA_MICROCHIP_KSZ_SPI
[Severity: Medium]
Does this actually make the two drivers mutually exclusive for modular
builds? NET_DSA_MICROCHIP_KSZ_SPI is a tristate, and Kconfig implements
negation as arithmetic on the tristate value:
scripts/kconfig/expr.h:
#define EXPR_NOT(dep) (2-(dep))
so !m evaluates to m, not n. That leaves
NET_DSA_MICROCHIP_KSZ_SPI=m together with NET_DSA_KS8995=m legal, both
modules get built, and both claim the same OF compatibles:
ksz_dt_ids in drivers/net/dsa/microchip/ksz_spi.c:
{ .compatible = "micrel,ksz8864", ... },
{ .compatible = "micrel,ksz8795", ... },
ks8995_spi_of_match in drivers/net/dsa/ks8995.c:
{ .compatible = "micrel,ks8995" },
{ .compatible = "micrel,ksz8864" },
{ .compatible = "micrel,ksz8795" },
The SPI modaliases "ksz8864"/"ksz8795" in ks8995_id overlap too, so which
driver binds a given node depends on module registration order, which is
the probe race the commit message says is being avoided. Would
"depends on NET_DSA_MICROCHIP_KSZ_SPI=n" be the expression you want here,
or should the overlapping IDs simply be removed from ks8995.c?
For the record, this transient state does not survive the series: the later
patch "net: dsa: ks8995: Delete surplus driver" (59dbca612286) drops this
dependency line and deletes drivers/net/dsa/ks8995.c.
[Severity: Medium]
As of this commit, does this dependency leave "micrel,ks8995" without any
driver? With NET_DSA_MICROCHIP_KSZ_SPI=y the whole KS8995 driver is
forced off, but ksz_dt_ids here only picks up ksz8864 and ksz8795 and goes
straight on to "microchip,ksz8463" - there is no "micrel,ks8995" entry and
no KSZ8995XA chip data yet. ks8995_spi_of_match is the only match for
that string, and it is used by two in-tree device trees:
arch/arm/boot/dts/intel/ixp/intel-ixp42x-linksys-wrv54g.dts
arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi
both with:
compatible = "micrel,ks8995";
and documented in
Documentation/devicetree/bindings/net/dsa/micrel,ks8995.yaml. On those
boards the switch would no longer be probed in a KSZ_SPI=y build, and
make olddefconfig silently drops NET_DSA_KS8995.
This is repaired later in the same series: "net: dsa: microchip: Support
Microchip KSZ8995XA / KS8995XA" (65df0673d386) adds the "micrel,ks8995"
match with KSZ8995XA chip data, and 59dbca612286 turns NET_DSA_KS8995 into
a stub selecting the KSZ driver. Would it be worth ordering the series so
this commit does not open a bisect window where KS8995 support disappears?
> select NET_DSA_TAG_NONE
> help
> This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
> diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
> index 373e9054947cb..77aecac324667 100644
> --- a/drivers/net/dsa/microchip/ksz_spi.c
> +++ b/drivers/net/dsa/microchip/ksz_spi.c
> @@ -224,6 +224,21 @@ static void ksz_spi_shutdown(struct spi_device *spi)
> }
>
> static const struct of_device_id ksz_dt_ids[] = {
> + /*
> + * Legacy Micrel bindings. In 2015 Microchip acquired
> + * Micrel which is the originator of the KSZ series, and
> + * devices branded for Micrel already existed, as well as
> + * some device tree bindings. These two products are identical
> + * to the same Microchip products.
> + */
> + {
> + .compatible = "micrel,ksz8864",
> + .data = &ksz_switch_chips[KSZ8864]
> + },
[Severity: Medium]
Does this change the port numbering seen by existing "micrel,ksz8864"
device trees? The two drivers model the same chip differently.
ksz_switch_chips[KSZ8864] in drivers/net/dsa/microchip/ksz_common.c:
/* WARNING
* KSZ8864 is similar to KSZ8895, except the first port
* does not exist.
* external cpu
* KSZ8864 1,2,3 4
*/
.internal_phy = {false, true, true, true, false},
.supports_mii = {false, false, false, false, true},
drivers/net/dsa/ks8995.c, the previous owner of this compatible:
/* In DSA the ports 1-4 are numbered 0-3 and the CPU port is port 4 */
#define KS8995_REG_PC(p, r) (0x10 + (0x10 * (p)) + (r))
and ks8995_phylink_get_caps() marks every port <= 3 as an internal-PHY
user port, including for [ksz8864] = { .name = "KSZ8864RMN" }.
So a device tree written for the old driver describes ethernet-port@0..@3
as user ports. Bound to the KSZ driver, port 0 has internal_phy and all
supports_* false, so ksz_phylink_get_caps() leaves supported_interfaces
empty and phylink_create() rejects it:
drivers/net/phy/phylink.c:
if (phy_interface_empty(config->supported_interfaces)) {
dev_err(config->dev,
"phylink: error: empty supported_interfaces\n");
return ERR_PTR(-EINVAL);
which fails dsa_register_switch(). If port@0 is omitted instead, the
third real user port at register block 0x40 is never described and its PC2
bits stay at reset values.
No in-tree device tree uses micrel,ksz8864, so this only affects
out-of-tree trees written against the ks8995 numbering, and the KSZ model
is the one matching Microchip's documentation. Could the commit message
or the binding mention that the port semantics of this already-documented
compatible change?
> + {
> + .compatible = "micrel,ksz8795",
> + .data = &ksz_switch_chips[KSZ8795]
> + },
[Severity: Low]
Should the DT bindings be updated along with these matches?
Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml, which
describes this driver's contract, has a closed compatible enum that starts
at "microchip,ksz8463" and lists no micrel,* string. The only schema
matching these compatibles is micrel,ks8995.yaml, and it ends with:
unevaluatedProperties: false
allowing nothing beyond reg, reset-gpios, dsa.yaml ethernet-ports and
spi-peripheral-props. A tree that uses the features of the more capable
driver referred to in the commit message - an mdio child node with
ethernet-phy@N, interrupts, wakeup-source, microchip,synclko-125, the
microchip,*-drive-strength-microamp properties - is then rejected by
dt_binding_check/dtbs_check for these compatibles.
At the tip of the series (59dbca612286) neither schema covers this:
microchip,ksz.yaml only gained "microchip,ksz8995xa" and micrel,ks8995.yaml
is untouched. Would listing the micrel,* strings in microchip,ksz.yaml,
including in the existing if/then blocks next to microchip,ksz8795 and
microchip,ksz8864, be the right fix?
> {
> .compatible = "microchip,ksz8463",
> .data = &ksz_switch_chips[KSZ8463]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831-ks8995-to-ksz8-v8-0-fd1dbb2b76e8%40kernel.org
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [net-next,v8,2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA
2026-08-31 20:54 ` [PATCH net-next v8 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
@ 2026-09-02 9:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 9:50 UTC (permalink / raw)
To: linusw
Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
netdev, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] Concern (validated as FALSE POSITIVE by series-end verification):
the newly documented `microchip,ksz8995xa` compatible was reported to…
--
dt-bindings: net: dsa: microchip: Add KSZ8995XA
This adds a new compatible string microchip,ksz8995xa to the Microchip KSZ
switch binding, noting the part was renamed from KS8995XA and that
KSZ8995MA and KSZ8995E are different parts.
> diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
> index 8d4a3a9a33fcc..4ed13870ed3ac 100644
> --- a/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
> +++ b/Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
> @@ -23,6 +23,7 @@ properties:
> - microchip,ksz8864 # 4-port version of KSZ8895 family switch
> - microchip,ksz8873
> - microchip,ksz8895 # 5-port version of KSZ8895 family switch
> + - microchip,ksz8995xa
[Severity: Low]
At this commit, is there anything in tree that matches this compatible?
A device tree node using microchip,ksz8995xa passes dt_binding_check here,
but ksz_dt_ids[] in drivers/net/dsa/microchip/ksz_spi.c has no entry for
it yet, so the node would not probe.
That said, the following patch in the same series ("net: dsa: microchip:
Support Microchip KSZ8995XA / KS8995XA") adds the matching entry:
drivers/net/dsa/microchip/ksz_spi.c:ksz_dt_ids[] {
...
{
.compatible = "microchip,ksz8995xa",
.data = &ksz_switch_chips[KSZ8995XA]
},
...
}
along with ksz_switch_chips[KSZ8995XA] and the { "ksz8995" } spi_device_id
entry, so at the tip of the series the string does resolve to a driver.
Documenting the binding ahead of the driver is the usual ordering, so this
is only a note about the intermediate state rather than something that
breaks a previously working configuration.
> - microchip,ksz9477
> - microchip,ksz9897
> - microchip,ksz9896
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831-ks8995-to-ksz8-v8-0-fd1dbb2b76e8%40kernel.org
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [net-next,v8,3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
2026-08-31 20:54 ` [PATCH net-next v8 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
@ 2026-09-02 9:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 9:50 UTC (permalink / raw)
To: linusw
Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
netdev, devicetree
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider:
Critical: 0 · High: 2 · Medium: 0 · Low: 3
- [High] ks8995_xmit() (net/dsa/tag_ks8995.c:44-59) does not insert a
switch tag when the frame already carries an in-band 802.1Q tag at…
- [High] ks8995_xmit() mixes two different header base pointers: the
branch decision at net/dsa/tag_ks8995.c:44-45 reads the EtherType…
- [Low] DISCARDED (resolved later in the series): the new
DSA_TAG_PROTO_KS8995 tagger has no in-tree consumer and no switch-side…
- [Low] DISCARDED as a functional bug, residual documentation defect
only: the ks8995_rcv() comment at net/dsa/tag_ks8995.c:105-114 states…
- [Low] ks8995_rcv() calls netdev_err() (net/dsa/tag_ks8995.c:92) on
every skb_vlan_untag() failure.
--
net: dsa: tag_ks8995: Add the KS8995 tag handling
This adds a new DSA tagger for the Micrel/Microchip KS8995 family. The tag
is a modified 0x8100 EtherType where the low nybble carries the target port
mask, and the tagger pushes any hardware-accelerated VLAN tag inside the
frame first so the special tag stays at offset 12.
A few questions about the transmit and receive paths below.
> diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
> new file mode 100644
> index 0000000000000..6bd5f1015c265
> --- /dev/null
> +++ b/net/dsa/tag_ks8995.c
> @@ -0,0 +1,141 @@
[ ... ]
> +static struct sk_buff *ks8995_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> + struct vlan_ethhdr *hdr;
> + u16 portmask;
> +
> + /* Prepare the special KS8995 tags */
> + portmask = dsa_xmit_port_mask(skb, dev);
> +
> + /* The switch expects the special tag at offset 12. Move any hardware
> + * accelerated VLAN tag into the payload so the conduit cannot insert
> + * it outside the special tag.
> + */
> + if (unlikely(skb_vlan_tag_present(skb))) {
> + skb = __vlan_hwaccel_push_inside(skb);
> + if (!skb)
> + return NULL;
> + }
> +
> + hdr = skb_vlan_eth_hdr(skb);
> + if (hdr->h_vlan_proto != htons(ETH_P_8021Q)) {
> + skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), 0);
> + /* vlan_insert_tag() drops the skb on failure */
> + if (!skb)
> + return NULL;
> + hdr = skb_vlan_eth_hdr(skb);
> + netdev_dbg(dev, "%s: inserted VLAN TAG %04x TCI %04x\n",
> + __func__, hdr->h_vlan_proto, hdr->h_vlan_TCI);
> + } else {
> + /* VLAN tag already exists in skb head, modify it in place */
> + hdr = vlan_eth_hdr(skb);
> + hdr->h_vlan_proto = KS8995M_STPID(portmask);
[Severity: High]
In the else branch of ks8995_xmit(), no switch tag is inserted; only the
TPID of the frame's existing 802.1Q tag is rewritten. Does that mean the
sender's VLAN tag is consumed as the special tag?
For an 8021q upper on a DSA user port the tag ends up in-band before the
tagger runs, either through the core:
vlan_dev_hard_start_xmit() -> validate_xmit_vlan() ->
__vlan_hwaccel_push_inside()
or through the __vlan_hwaccel_push_inside() call at the top of
ks8995_xmit() itself. The h_vlan_proto == htons(ETH_P_8021Q) test then
selects the in-place rewrite.
The companion patch in this series configures PORT_INSERT_TAG on the CPU
port and PORT_REMOVE_TAG on the user ports, with the comment in
ksz8_port_setup():
/* The KSZ8995XA has a special tag format in the front of the frame
* that need to be inserted by the CPU and then removed by each
* port. PORT_REMOVE_TAG simply means "remove tags coming from the
* CPU port" it does not affect ingress packets.
*/
If the user port removes those four bytes, do VLAN-tagged frames leave the
user port untagged, landing in the peer's native VLAN?
There is a second effect of reusing the existing tag: the special tag's TCI
becomes whatever VID/PCP the local sender chose (an 8021q upper, or a raw
socket writing its own 802.1Q header), whereas the vlan_insert_tag() branch
always produces TCI 0. Is that asymmetry intentional?
The commit message says:
Push any hardware-accelerated VLAN tag into the packet before adding the
switch tag.
For frames that already carry an 802.1Q tag no switch tag is added at all,
so could the merging behaviour be spelled out either in the commit message
or in a comment?
[Severity: High]
The branch decision and the write use two different header bases. The test
reads through skb_vlan_eth_hdr(), which is (struct vlan_ethhdr *)skb->data,
while the store goes through vlan_eth_hdr(), which is
(struct vlan_ethhdr *)skb_mac_header(skb). include/linux/if_vlan.h says of
skb_vlan_eth_hdr(): "Prefer this version in TX path".
The two bases coincide only because __dev_queue_xmit() calls
skb_reset_mac_header(). __dev_direct_xmit() does not, and neither does
validate_xmit_skb() on that path. AF_XDP copy mode takes exactly that
route:
xsk_generic_xmit() -> xsk_build_skb() /* mac_header left unset */
-> __dev_direct_xmit() -> netdev_start_xmit()
-> dsa_user_xmit() -> ks8995_xmit()
xsk_build_skb() allocates with sock_alloc_send_skb() and never sets the MAC
header, so skb->mac_header keeps the unset value (~0U); skb_mac_header()
only has a DEBUG_NET_WARN_ON_ONCE for that case.
For such an skb whose bytes 12-13 happen to be 0x8100 (fully chosen by the
AF_XDP application, which writes raw L2 frames), the test picks the else
branch from the correct offset and then:
hdr = vlan_eth_hdr(skb);
hdr->h_vlan_proto = KS8995M_STPID(portmask);
writes two bytes at skb->head + 0xFFFF + 12. Can this scribble outside the
buffer? Would simply reusing the hdr already obtained from
skb_vlan_eth_hdr() above avoid it? Other taggers that rewrite the header on
transmit use the skb->data based accessor, for example
net/dsa/tag_sja1105.c.
> + netdev_dbg(dev, "%s: modified VLAN TAG %04x\n",
> + __func__, hdr->h_vlan_proto);
> + }
> +
> + return skb;
> +}
> +
> +static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
> +{
[ ... ]
> + /* Move the custom DSA+VLAN tag into the hwaccel area and strip
> + * it from the skb head
> + */
> + skb = skb_vlan_untag(skb);
> + if (!skb) {
> + /* skb_vlan_untag drops the skb on failure */
> + netdev_err(dev, "%s: unable to untag skb\n", __func__);
> + return NULL;
> + }
[Severity: Low]
Could this netdev_err() flood the log? It is an unratelimited KERN_ERR on a
path driven entirely by received frames, and skb_vlan_untag() has two
remote-traffic-dependent failure modes:
net/core/skbuff.c:skb_vlan_untag() {
...
if (unlikely(!pskb_may_pull(skb, VLAN_HLEN + sizeof(unsigned short))))
goto err_free;
...
skb = skb_reorder_vlan_header(skb);
if (unlikely(!skb))
goto err_free;
...
}
The first fires on short frames, the second on the skb_cow() ->
pskb_expand_head() GFP_ATOMIC allocation, which is reachable whenever the
RX skb is cloned (a packet socket tapping the conduit) and memory is tight.
The neighbouring drop reason in this same function uses netdev_dbg(), and
net/dsa/tag_lan9303.c uses dev_warn_ratelimited() on its RX drop path.
Would netdev_dbg() or a ratelimited/once variant be preferable here?
[ ... ]
> + /* Preserve the VLAN tag if it contains a non-zero VID which is not
> + * identical to 0x001, or PCP, and restore its TPID to the standard
> + * value.
> + *
> + * If this is just an ordinary inbound package the datasheet claims
> + * it will "replace null VID with ingress port VID", which means
> + * VID set to 1: 0x8101 0001 for port 0 or 0x8102 0001 for port 1.
> + * So in the DSA driver we will set the default port VID to 0 so
> + * we can properly detect non-VLAN frames.
> + */
> + if (!skb->vlan_tci) {
[Severity: Low]
This looks like a comment inaccuracy rather than a functional problem. The
comment describes preserving the tag only when the VID is "non-zero ... and
not identical to 0x001", but the code just tests !skb->vlan_tci, so there
is no VID == 1 exemption.
The code appears correct as written: the companion patch programs
ksz8_port_setup() with
/* Use VID 0 to identify untagged frames in the special tag */
ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, 0);
so untagged ingress frames arrive with TCI 0 and the test catches them, and
a genuine VLAN 1 frame should not be treated as untagged. Could the comment
be reworded to match what the code does?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831-ks8995-to-ksz8-v8-0-fd1dbb2b76e8%40kernel.org
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [net-next,v8,4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
2026-08-31 20:54 ` [PATCH net-next v8 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
@ 2026-09-02 9:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 9:50 UTC (permalink / raw)
To: linusw
Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
netdev, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 1
- [Medium] KSZ8995XA detection cannot distinguish the XA from the
KS8995MA/KSZ8995E parts the commit message says are incompatible, and…
- [Medium] The new KSZ8995XA branch of ksz8_reset_switch() loops over all
ports (`for (port = 0; port < dev->info->port_cnt; port++)`, i.e.…
- [Low] For KSZ8995XA the special-tag configuration in ksz8_port_setup()
only ever *sets* bits in Port Control 0: PORT_INSERT_TAG (BIT(2)) on…
--
net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
This adds KSZ8995XA (aka Micrel KS8995XA) support to the KSZ DSA driver,
with a reduced set of dev/switch operations, a header special tag instead
of a tail tag, and adjusted DCB helpers for the chip's eight one-bit-per-
DSCP TOS priority registers. It also switches the SPI mode for this chip
and adds new OF and SPI identifiers.
> diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
> index 941ae9f66f70b..f9272d1b7bf47 100644
> --- a/drivers/net/dsa/microchip/ksz8.c
> +++ b/drivers/net/dsa/microchip/ksz8.c
[ ... ]
> @@ -253,7 +254,17 @@ static int ksz8463_reset_switch(struct ksz_device *dev)
>
> static int ksz8_reset_switch(struct ksz_device *dev)
> {
> - if (ksz_is_ksz88x3(dev)) {
> + if (ksz_is_ksz8995xa(dev)) {
> + unsigned int port;
> +
> + /* The KSZ8995XA switch itself cannot be reset by software, but
> + * often boot loaders have fun with ports, so reset them. This
> + * reset bit is self-clearing.
> + */
> + for (port = 0; port < dev->info->port_cnt; port++)
> + ksz_port_cfg(dev, port, REG_PORT_STATUS_3,
> + PORT_PHY_SOFT_RESET, true);
[Severity: Medium]
Should this loop be bounded by dev->phy_port_cnt rather than
dev->info->port_cnt?
The new chip data declares five ports with no internal PHY on the last
one:
drivers/net/dsa/microchip/ksz_common.c, ksz_switch_chips[KSZ8995XA]:
.port_cnt = 5, /* total cpu and user ports */
...
.internal_phy = {true, true, true, true, false},
With PORT_CTRL_ADDR(port, addr) = addr + 0x10 + port * 0x10, the fifth
iteration writes address 0x5F, the MII/CPU port block, while ksz8_reg.h
only defines the STATUS_3 register for ports 1 through 4:
drivers/net/dsa/microchip/ksz8_reg.h:
#define REG_PORT_1_STATUS_3 0x1F
#define REG_PORT_2_STATUS_3 0x2F
#define REG_PORT_3_STATUS_3 0x3F
#define REG_PORT_4_STATUS_3 0x4F
Every other PHY-side loop in ksz8.c uses dev->phy_port_cnt, which is
port_cnt - 1 for this chip.
Does that last write land on a reserved bit, or does it soft-reset the
fifth PHY on MII-P5 which does not belong to the switch fabric? On the
two in-tree boards using this family that PHY is the WAN PHY driven by a
different MAC, so a reset there would be visible as a link drop on an
unrelated netdev. Since the comment says the switch itself has no
software reset, the bit is also never cleared again by the driver.
[ ... ]
> @@ -2057,6 +2118,21 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
>
> ksz8_cfg_port_member(dev, port, member);
>
> + if (ksz_is_ksz8995xa(dev)) {
> + /* Use VID 0 to identify untagged frames in the special tag */
> + ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, 0);
> +
> + /* The KSZ8995XA has a special tag format in the front of the frame
> + * that need to be inserted by the CPU and then removed by each
> + * port. PORT_REMOVE_TAG simply means "remove tags coming from the
> + * CPU port" it does not affect ingress packets.
> + */
> + if (cpu_port)
> + ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_INSERT_TAG, true);
> + else
> + ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_REMOVE_TAG, true);
> + }
[Severity: Low]
Both branches here only ever set a bit, since ksz_port_cfg(..., true)
expands to ksz_rmw8() with mask equal to value. The opposite bit is
never driven to a defined value: PORT_INSERT_TAG is left untouched on
user ports and PORT_REMOVE_TAG is left untouched on the CPU port.
Given that ksz8_reset_switch() states this part cannot be reset by
software, and only pokes PORT_PHY_SOFT_RESET, is Port Control 0 ever
brought to a known state? If a boot loader (the same agent the comment
above blames for having fun with ports) left PORT_INSERT_TAG set on a
user port, the switch would emit the special tag onto the wire there; if
it left PORT_REMOVE_TAG set on the CPU port, frames towards the CPU would
be stripped, and the KS8995 tagger would then disagree with the hardware
frame format.
Would a single read-modify-write covering both bits be clearer, so both
end up defined?
ksz_prmw8(dev, port, REG_PORT_CTRL_0,
PORT_INSERT_TAG | PORT_REMOVE_TAG,
cpu_port ? PORT_INSERT_TAG : PORT_REMOVE_TAG);
[ ... ]
> diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
> index 1a9d6f83a0232..6c15ac97a7e3f 100644
> --- a/drivers/net/dsa/microchip/ksz_common.c
> +++ b/drivers/net/dsa/microchip/ksz_common.c
[ ... ]
> @@ -2944,11 +2977,15 @@ static int ksz_switch_detect(struct ksz_device *dev)
> return -ENODEV;
> break;
> case KSZ8895_FAMILY_ID:
> - if (id2 == KSZ8895_CHIP_ID_95 ||
> - id2 == KSZ8895_CHIP_ID_95R)
> + if (id2 == KSZ8895_CHIP_ID_95XA) {
> + dev->chip_id = KSZ8995XA_CHIP_ID;
> + break;
> + } else if (id2 == KSZ8895_CHIP_ID_95 ||
> + id2 == KSZ8895_CHIP_ID_95R) {
> dev->chip_id = KSZ8895_CHIP_ID;
> - else
> + } else {
> return -ENODEV;
> + }
> ret = ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id4);
> if (ret)
> return ret;
[Severity: Medium]
How does this branch distinguish the XA from the parts the commit message
calls out as different? The commit message says:
Notice: there are also KSZ8995E and KSZ8995MA. These are BOTH
different from the KSZ8995XA.
Here family ID 0x95 with chip-ID nibble 0 is claimed as
KSZ8995XA_CHIP_ID unconditionally, and the early break skips any further
read. The neighbouring KSZ8895/KSZ8864 path does read
REG_KSZ8864_CHIP_ID to narrow things down, and the revision field in
register 0x01 (which the old driver read in get_chip_rev()) is never
consulted, so dev->chip_rev stays 0 and probe prints "found switch:
KSZ8995XA, rev 0" for any family-0x95, chip-0 silicon.
The new define asserts the ID is XA specific:
drivers/net/dsa/microchip/ksz_common.h:
#define KSZ8895_CHIP_ID_95XA 0x0
but drivers/net/dsa/ks8995.c names exactly the same family/chip-ID pair
KS8995MA:
[ks8995] = {
.name = "KS8995MA",
.family_id = FAMILY_KS8995,
.chip_id = KS8995_CHIP_ID,
The same patch also points the family-wide legacy identifiers at the XA
chip data:
drivers/net/dsa/microchip/ksz_spi.c, ksz_dt_ids:
{
.compatible = "micrel,ks8995",
.data = &ksz_switch_chips[KSZ8995XA]
},
plus { "ks8995" } in ksz_spi_ids. micrel,ks8995 is the family-wide
compatible per Documentation/devicetree/bindings/net/dsa/micrel,ks8995.yaml,
and both in-tree users of it, intel-ixp42x-linksys-wrv54g.dts and
intel-ixp42x-actiontec-mi424wr.dtsi, were previously handled by
drivers/net/dsa/ks8995.c. The later commit "net: dsa: ks8995: Delete
surplus driver" removes that driver, so there is no fallback.
If an MA or E part answers with this ID, it gets programmed with the
XA-specific configuration with no warning and no way to opt out:
SW_SPECIAL_TAG in REG_SW_CTRL_9, per-port PORT_INSERT_TAG and
PORT_REMOVE_TAG, DSA_TAG_PROTO_KS8995 instead of the previous
DSA_TAG_PROTO_NONE, descending TOS/DSCP register order, single-bit port
priority, the deliberately inverted legal/huge packet-size polarity, and
VLAN, MIB and FDB support switched off.
Could this branch consult the revision field (or another discriminator),
or at least emit a dev_warn that MA and E share this ID and are treated
as XA? Alternatively, could the legacy family-wide compatible keep
distinct chip data?
The series also reads as internally inconsistent on this point: this
commit message says E and MA support could be added "in the future",
while "net: dsa: ks8995: Delete surplus driver" states the ksz driver
"now handles all switches that the old driver was handling".
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831-ks8995-to-ksz8-v8-0-fd1dbb2b76e8%40kernel.org
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [net-next,v8,5/5] net: dsa: ks8995: Delete surplus driver
2026-08-31 20:54 ` [PATCH net-next v8 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
@ 2026-09-02 9:50 ` netdev-bot+sashiko
0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-02 9:50 UTC (permalink / raw)
To: linusw
Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
netdev, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] After this patch drivers/net/dsa/microchip/ksz_spi.c (ksz_dt_ids,
lines 236-249) is the only implementation of the compatibles…
--
net: dsa: ks8995: Delete surplus driver
This removes drivers/net/dsa/ks8995.c and its Makefile entry, since the
Microchip ksz driver now covers the same switches. NET_DSA_KS8995 is kept
in Kconfig as a transitional symbol that selects the ksz SPI driver.
> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index 68fb175cc36ab..55a9714033a6f 100644
> --- a/drivers/net/dsa/Kconfig
> +++ b/drivers/net/dsa/Kconfig
> @@ -110,11 +110,13 @@ config NET_DSA_RZN1_A5PSW
> config NET_DSA_KS8995
> tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
> depends on SPI
> - depends on !NET_DSA_MICROCHIP_KSZ_SPI
> - select NET_DSA_TAG_NONE
> + select NET_DSA_MICROCHIP_KSZ_COMMON
> + select NET_DSA_MICROCHIP_KSZ_SPI
> help
> - This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
> - switches, managed over SPI.
> + This is a transitional option for users who had NET_DSA_KS8995
> + enabled. It automatically enables the new NET_DSA_MICROCHIP_KSZ_SPI
> + driver which supersedes it. This option will be removed in a future
> + kernel release.
>
> config NET_DSA_SMSC_LAN9303
> tristate
[ ... ]
> diff --git a/drivers/net/dsa/ks8995.c b/drivers/net/dsa/ks8995.c
> deleted file mode 100644
> index 77d8b842693c1..0000000000000
> --- a/drivers/net/dsa/ks8995.c
> +++ /dev/null
[ ... ]
> -static const struct of_device_id ks8995_spi_of_match[] = {
> - { .compatible = "micrel,ks8995" },
> - { .compatible = "micrel,ksz8864" },
> - { .compatible = "micrel,ksz8795" },
> - { },
> -};
> -MODULE_DEVICE_TABLE(of, ks8995_spi_of_match);
[Severity: Low]
Should the devicetree binding be folded over as well when these three
compatibles move to the ksz driver?
After the deletion, ksz_dt_ids in drivers/net/dsa/microchip/ksz_spi.c is the
only implementation of micrel,ks8995, micrel,ksz8795 and micrel,ksz8864:
{
.compatible = "micrel,ksz8864",
.data = &ksz_switch_chips[KSZ8864]
},
...
{
.compatible = "micrel,ks8995",
.data = &ksz_switch_chips[KSZ8995XA]
},
But the only schema selecting those strings is
Documentation/devicetree/bindings/net/dsa/micrel,ks8995.yaml, which was
written for the deleted driver and ends with:
unevaluatedProperties: false
allowing only compatible, reg, reset-gpios, the dsa.yaml ethernet-ports and
spi-peripheral-props.
The surviving driver parses more than that for these chips. For example
ksz_mdio_register() in drivers/net/dsa/microchip/ksz_common.c does:
mdio_np = of_get_child_by_name(dev->dev->of_node, "mdio");
and ksz_switch_register() looks at wakeup-source,
microchip,synclko-125/synclko-disable, microchip,pme-active-high,
micrel,fiber-mode and interrupts. Those properties are described only in
Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml, whose
compatible enum contains no micrel-prefixed string.
So a board that uses compatible = "micrel,ks8995" and adds an mdio child
node binds fine at runtime, but would dt_binding_check/dtbs_check reject it,
with no schema left that can describe it? Would it make sense to add the
micrel aliases to microchip,ksz.yaml (deprecated) or cross-reference it from
micrel,ks8995.yaml?
Both in-tree users, arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi
and arch/arm/boot/dts/intel/ixp/intel-ixp42x-linksys-wrv54g.dts, only use
compatible, reg, spi-max-frequency and ethernet-ports, so nothing fails
today.
Related, is this part of the micrel,ks8995.yaml description still accurate
now that the ksz driver handles the chip?
The chip itself is managed over SPI, but all the PHYs need to be accessed
from an external MDIO channel.
ksz_switch_chips[KSZ8995XA] in ksz_common.c declares
.internal_phy = {true, true, true, true, false},
and the ksz driver can register an internal MDIO bus for those PHYs, so the
sentence reads like a property of the removed driver rather than of the
hardware.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260831-ks8995-to-ksz8-v8-0-fd1dbb2b76e8%40kernel.org
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-09-02 9:50 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 20:54 [PATCH net-next v8 0/5] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
2026-08-31 20:54 ` [PATCH net-next v8 1/5] net: dsa: microchip: Add fallback Micrel compatibles Linus Walleij
2026-09-02 9:50 ` [net-next,v8,1/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 2/5] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
2026-09-02 9:50 ` [net-next,v8,2/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
2026-09-02 9:50 ` [net-next,v8,3/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
2026-09-02 9:50 ` [net-next,v8,4/5] " netdev-bot+sashiko
2026-08-31 20:54 ` [PATCH net-next v8 5/5] net: dsa: ks8995: Delete surplus driver Linus Walleij
2026-09-02 9:50 ` [net-next,v8,5/5] " netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox