Devicetree
 help / color / mirror / Atom feed
* [PATCH net-next v11 0/4] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA
@ 2026-09-09 17:00 Linus Walleij
  2026-09-09 17:00 ` [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-09 17:00 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 driver identified the shared 0x95/0x0 ID as KS8995MA, but
this ID cannot distinguish MA from XA. No KS8995MA users are known,
and KS8995E has no documented SPI management interface. Neither
variant has been verified with the replacement driver.

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, including the per-port PHY reset polling, 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 v11:
- Drop KSZ8995XA from the three-port binding exception so ports 3 and
  4 receive generic DSA port validation.
- Reject invalid KS8995 receive port masks before ilog2() so plain
  802.1Q frames are dropped safely.
- Propagate KSZ8995XA PHY reset assertion errors instead of treating
  a failed write as a completed reset.
- Do not advertise legacy VLAN behavior for KSZ8995XA, preserving genuine
  ingress VLAN tags through the DSA receive path.
- Link to v10: https://patch.msgid.link/20260908-ks8995-to-ksz8-v10-0-7379239eb65f@kernel.org

Changes in v10:
- Wait for the KSZ8995XA per-port PHY reset bits to clear before
  continuing setup; verified to work on Actiontec MI424WR rev D
  hardware.
- Drop the unreachable KSZ8995XA VLAN filtering check because the
  restricted switch operations do not expose that callback.
- Add KSZ8995XA to the binding's Ethernet-port schema exception list.
- Convert big-endian KS8995 transmit tag fields to host byte order
  before logging them.
- Align the KSZ8995XA SPI ID with its DT compatible to restore module
  autoloading and avoid the SPI registration warning.
- Link to v9: https://patch.msgid.link/20260906-ks8995-to-ksz8-v9-0-8d8815a91bd8@kernel.org

Changes in v9:
- Fold the legacy Micrel compatibles into KSZ8995XA support and use an
  exact tristate dependency to avoid probe races and bisect gaps.
- Always prepend a distinct KS8995 switch tag to preserve existing VLAN
  headers and avoid unsafe MAC-header access on direct transmit paths.
- Rate-limit KS8995 receive untagging errors to avoid log flooding.
- Link to v8: https://patch.msgid.link/20260831-ks8995-to-ksz8-v8-0-fd1dbb2b76e8@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 (4):
      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                   | 243 +++++-
 drivers/net/dsa/microchip/ksz8.h                   |   2 +
 drivers/net/dsa/microchip/ksz8_reg.h               |   2 +
 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                               | 145 ++++
 18 files changed, 492 insertions(+), 932 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260509-ks8995-to-ksz8-15f3f9c8271f

Best regards,
--  
Linus Walleij <linusw@kernel.org>


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA
  2026-09-09 17:00 [PATCH net-next v11 0/4] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
@ 2026-09-09 17:00 ` Linus Walleij
  2026-09-10 20:01   ` netdev-bot+sashiko
  2026-09-09 17:00 ` [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 16+ messages in thread
From: Linus Walleij @ 2026-09-09 17:00 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] 16+ messages in thread

* [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling
  2026-09-09 17:00 [PATCH net-next v11 0/4] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
  2026-09-09 17:00 ` [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
@ 2026-09-09 17:00 ` Linus Walleij
  2026-09-10 17:01   ` sashiko-bot
  2026-09-10 20:01   ` netdev-bot+sashiko
  2026-09-09 17:00 ` [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
  2026-09-09 17:00 ` [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver Linus Walleij
  3 siblings, 2 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-09 17:00 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 tagger is used by the KSZ8995XA support added later in this
series.

The tag is a modified 0x8100 ethertype tag where a bit in the
last nybble is set for each target port.

Always insert a distinct outer switch tag. User ports remove this
field on egress, so reusing an existing 802.1Q header would consume
the sender VLAN tag instead of preserving it.

Push any hardware-accelerated VLAN tag into the packet first. This
keeps the switch tag at offset 12 while retaining 802.1Q, 802.1ad and
stacked VLAN headers as inner payload, and avoids relying on a valid
SKB MAC header in direct transmit paths.

Rate-limit receive untagging errors so repeated failures cannot flood
the kernel log.

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 | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 155 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..f6ef1ef1891f
--- /dev/null
+++ b/net/dsa/tag_ks8995.c
@@ -0,0 +1,145 @@
+// 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/net.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;
+	}
+
+	/* Always add a distinct outer tag. The user port removes this field on
+	 * egress, so reusing an existing 802.1Q tag would consume that VLAN tag.
+	 */
+	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__, ntohs(hdr->h_vlan_proto),
+		   ntohs(hdr->h_vlan_TCI));
+
+	return skb;
+}
+
+static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+	int portmask;
+	int port;
+	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;
+	}
+
+	portmask = FIELD_GET(KS8995M_STPID_PORTMASK, etype);
+	if (unlikely(!is_power_of_2(portmask))) {
+		netdev_dbg(dev, "%s: dropped invalid port mask 0x%04x\n",
+			   __func__, portmask);
+		kfree_skb(skb);
+		return NULL;
+	}
+	port = ilog2(portmask);
+
+	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 */
+		if (net_ratelimit())
+			netdev_err(dev, "%s: unable to untag skb\n", __func__);
+		return NULL;
+	}
+
+	netdev_dbg(dev, "%s: etype %04x portmask %04x (%d)\n",
+		   __func__, etype, portmask, port);
+	skb->dev = dsa_conduit_find_user(dev, 0, port);
+	if (!skb->dev) {
+		kfree_skb(skb);
+		return NULL;
+	}
+
+	/* skb_vlan_untag() moved the special tag's TCI into the hardware-
+	 * accelerated VLAN metadata. The switch preserves the TCI of tagged
+	 * frames, but inserts the ingress port's default tag for untagged frames.
+	 * Since the driver programs the default tag to 0, a zero TCI identifies
+	 * an originally untagged frame. Preserve every non-zero TCI as an
+	 * 802.1Q tag, including VID 0 frames with PCP or DEI set.
+	 */
+	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] 16+ messages in thread

* [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
  2026-09-09 17:00 [PATCH net-next v11 0/4] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
  2026-09-09 17:00 ` [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
  2026-09-09 17:00 ` [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
@ 2026-09-09 17:00 ` Linus Walleij
  2026-09-10 17:01   ` sashiko-bot
  2026-09-10 20:02   ` netdev-bot+sashiko
  2026-09-09 17:00 ` [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver Linus Walleij
  3 siblings, 2 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-09 17:00 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.

The KSZ8995XA shares family ID 0x95 and chip-ID nibble 0x0 with
the KS8995MA, so hardware detection cannot distinguish them. The
legacy "micrel,ks8995" compatible is therefore treated as KSZ8995XA.
No KS8995MA users are known, and KS8995E has no documented SPI
management interface. Neither MA nor E has been verified with this
driver.

The helper macros are named ksz_is_ksz8995xa() to make it
possible to add E and MA support in the future.

Wait for the self-clearing per-port PHY reset bits before continuing
with switch setup. Poll each port for up to 600 ms and allow an
additional millisecond for the PHYs to settle.

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.

The legacy KS8995 driver also matches the Micrel compatibles for
KSZ8864 and KSZ8795. Add these to the KSZ SPI driver here and use
an exact tristate dependency to make the drivers mutually exclusive.
This transfers ownership atomically, avoiding module probe races and
gaps in driver coverage between patches.

Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/net/dsa/Kconfig                     |   1 +
 drivers/net/dsa/microchip/Kconfig           |   1 +
 drivers/net/dsa/microchip/ksz8.c            | 243 +++++++++++++++++++++++-----
 drivers/net/dsa/microchip/ksz8.h            |   2 +
 drivers/net/dsa/microchip/ksz8_reg.h        |   2 +
 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 +
 10 files changed, 331 insertions(+), 71 deletions(-)

diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 676fb7dffe14..e309e4c1ee3a 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 = n
 	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/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..c3871d5d6d36 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
@@ -20,6 +21,7 @@
 #include <linux/export.h>
 #include <linux/gpio/consumer.h>
 #include <linux/if_vlan.h>
+#include <linux/iopoll.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/platform_data/microchip-ksz.h>
@@ -253,7 +255,35 @@ 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, reg, val;
+		int ret;
+
+		/* 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++) {
+			ret = ksz_prmw8(dev, port, REG_PORT_STATUS_3,
+					PORT_PHY_SOFT_RESET,
+					PORT_PHY_SOFT_RESET);
+			if (ret)
+				return ret;
+		}
+
+		/* IEEE 802.3 allows up to 500 ms for a PHY reset. */
+		for (port = 0; port < dev->info->port_cnt; port++) {
+			reg = dev->dev_ops->get_port_addr(port, REG_PORT_STATUS_3);
+			ret = regmap_read_poll_timeout(ksz_regmap_8(dev), reg, val,
+						       !(val & PORT_PHY_SOFT_RESET),
+						       1000, 600000);
+			if (ret)
+				return ret;
+		}
+
+		/* Some PHYs need additional settling time after reset. */
+		usleep_range(1000, 2000);
+	} 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 +299,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 +380,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 +407,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 +432,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 +1031,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, &reg_val);
 	if (ret < 0)
 		return ret;
@@ -1068,8 +1133,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 +1231,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 +1377,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 +1495,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;
@@ -2043,12 +2121,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 +2137,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 +2168,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 +2243,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 +2421,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 +2752,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) {
@@ -2685,19 +2791,22 @@ static int ksz8_setup(struct dsa_switch *ds)
 
 	ksz_init_mib_timer(dev);
 
-	ds->configure_vlan_while_not_filtering = false;
 	ds->dscp_prio_mapping_is_global = true;
 	ds->mtu_enforcement_ingress = true;
 
-	/* We rely on software untagging on the CPU port, so that we
-	 * can support both tagged and untagged VLANs
-	 */
-	ds->untag_bridge_pvid = true;
+	if (!ksz_is_ksz8995xa(dev)) {
+		ds->configure_vlan_while_not_filtering = false;
 
-	/* VLAN filtering is partly controlled by the global VLAN
-	 * Enable flag
-	 */
-	ds->vlan_filtering_is_global = true;
+		/* We rely on software untagging on the CPU port, so that we
+		 * can support both tagged and untagged VLANs
+		 */
+		ds->untag_bridge_pvid = true;
+
+		/* VLAN filtering is partly controlled by the global VLAN
+		 * Enable flag
+		 */
+		ds->vlan_filtering_is_global = true;
+	}
 
 	/* Enable automatic fast aging when link changed detected. */
 	ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true);
@@ -2711,9 +2820,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 +2831,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 +3090,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 +3193,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 +3242,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..f858bce84bfd 100644
--- a/drivers/net/dsa/microchip/ksz8_reg.h
+++ b/drivers/net/dsa/microchip/ksz8_reg.h
@@ -95,6 +95,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, &reg, &per_reg, &mask);
+	ksz_get_dscp_prio_reg(dev, dscp, &reg, &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, &reg, &per_reg, &mask);
-
-	shift = (dscp % per_reg) * (8 / per_reg);
+	ksz_get_dscp_prio_reg(dev, dscp, &reg, &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 373e9054947c..e3bb9e604186 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;
@@ -224,6 +228,25 @@ 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 = "micrel,ks8995",
+		.data = &ksz_switch_chips[KSZ8995XA]
+	},
 	{
 		.compatible = "microchip,ksz8463",
 		.data = &ksz_switch_chips[KSZ8463]
@@ -256,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]
@@ -317,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" },
@@ -325,6 +353,7 @@ static const struct spi_device_id ksz_spi_ids[] = {
 	{ "ksz8864" },
 	{ "ksz8873" },
 	{ "ksz8895" },
+	{ "ksz8995xa" },
 	{ "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] 16+ messages in thread

* [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver
  2026-09-09 17:00 [PATCH net-next v11 0/4] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
                   ` (2 preceding siblings ...)
  2026-09-09 17:00 ` [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
@ 2026-09-09 17:00 ` Linus Walleij
  2026-09-10 20:02   ` netdev-bot+sashiko
  3 siblings, 1 reply; 16+ messages in thread
From: Linus Walleij @ 2026-09-09 17:00 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 the KS(Z)8995XA. The old
driver identified the shared 0x95/0x0 ID as KS8995MA, but this ID
cannot distinguish MA from XA. No KS8995MA users are known, and
KS8995E has no documented SPI management interface. Neither variant
has been verified with the replacement driver.

The two in-tree device trees using the "micrel,ks8995" compatible:

 arch/arm/boot/dts/intel/ixp/intel-ixp42x-linksys-wrv54g.dts
 arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi

both use KS8995XA hardware and continue to be supported by the
replacement driver without regressions.

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 e309e4c1ee3a..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 = n
-	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] 16+ messages in thread

* Re: [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
  2026-09-09 17:00 ` [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
@ 2026-09-10 17:01   ` sashiko-bot
  2026-09-10 19:56     ` Linus Walleij
  2026-09-10 20:02   ` netdev-bot+sashiko
  1 sibling, 1 reply; 16+ messages in thread
From: sashiko-bot @ 2026-09-10 17:01 UTC (permalink / raw)
  To: Linus Walleij; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Hardware Reset Timeout: `ksz8_reset_switch()` erroneously applies a PHY reset to all ports, including the CPU port which lacks an internal PHY.
--

commit 80bf0b7a0236c83f85f86b7cc57ef97388c5c06c
Author: Linus Walleij <linusw@kernel.org>

net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA

This commit adds support for the Microchip KSZ8995XA switch in the KSZ
driver. It implements hardware detection, MAC operations, and specific
PHY reset and initialization sequences required for this legacy device.

> diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
> --- a/drivers/net/dsa/microchip/ksz8.c
> +++ b/drivers/net/dsa/microchip/ksz8.c
> @@ -253,7 +255,35 @@ 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, reg, val;
> +		int ret;
> +
> +		/* 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++) {
> +			ret = ksz_prmw8(dev, port, REG_PORT_STATUS_3,
> +					PORT_PHY_SOFT_RESET,
> +					PORT_PHY_SOFT_RESET);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		/* IEEE 802.3 allows up to 500 ms for a PHY reset. */
> +		for (port = 0; port < dev->info->port_cnt; port++) {
> +			reg = dev->dev_ops->get_port_addr(port, REG_PORT_STATUS_3);
> +			ret = regmap_read_poll_timeout(ksz_regmap_8(dev), reg, val,
> +						       !(val & PORT_PHY_SOFT_RESET),
> +						       1000, 600000);
> +			if (ret)
> +				return ret;
> +		}

[Severity: Medium]
Could this result in a timeout regression that fails switch initialization?

Both loops in ksz8_reset_switch() iterate over all ports up to
dev->info->port_cnt, which is 5 for the KSZ8995XA, thereby including the
CPU port. Since the CPU port lacks an internal PHY, writing to its
REG_PORT_STATUS_3 targets an undefined or reserved register.

If the hardware latches the written bit or does not return 0 when read
back, the regmap_read_poll_timeout() call will hit its 600ms timeout
and return -ETIMEDOUT, entirely aborting the setup sequence.

Should these loops iterate up to dev->phy_port_cnt instead to avoid
resetting non-existent PHYs?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-ks8995-to-ksz8-v11-0-0e04411a559d@kernel.org?part=3

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling
  2026-09-09 17:00 ` [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
@ 2026-09-10 17:01   ` sashiko-bot
  2026-09-10 19:57     ` Linus Walleij
  2026-09-10 20:01   ` netdev-bot+sashiko
  1 sibling, 1 reply; 16+ messages in thread
From: sashiko-bot @ 2026-09-10 17:01 UTC (permalink / raw)
  To: Linus Walleij; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Insufficient `.needed_headroom` causes expensive SKB reallocation on the transmission fast-path for VLAN-tagged packets.
--

commit f2aa1666092956329d185ea98c072903551a8a85
Author: Linus Walleij <linusw@kernel.org>

net: dsa: tag_ks8995: Add the KS8995 tag handling

This commit adds a new DSA tag driver for the Micrel KS8995 switch. It 
pushes a modified 802.1Q tag on transmission and untags it on reception, 
incorporating any hardware-accelerated VLAN tags into the packet payload.

> diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
> new file mode 100644
> index 0000000000000..f6ef1ef1891fd
> --- /dev/null
> +++ b/net/dsa/tag_ks8995.c
[ ... ]
> +static struct sk_buff *ks8995_xmit(struct sk_buff *skb, struct net_device *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;
> +	}
> +
> +	/* Always add a distinct outer tag. The user port removes this field on
> +	 * egress, so reusing an existing 802.1Q tag would consume that VLAN tag.
> +	 */
> +	skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), 0);
[ ... ]
> +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,

[Severity: Medium]
Does this code reserve enough headroom for the worst-case transmission path?

When an SKB is transmitted via ks8995_xmit() with a hardware-accelerated
VLAN tag, the driver first pushes the hardware tag into the payload via
__vlan_hwaccel_push_inside() which consumes 4 bytes of headroom. It then
inserts the custom KS8995 switch tag via vlan_insert_tag(), consuming
another 4 bytes. This requires a total of 8 bytes of headroom.

Because needed_headroom only requests 4 bytes (VLAN_HLEN), if an SKB arrives
with exactly the requested headroom (such as when forwarded from another
interface), will the second tag insertion exhaust the headroom and trigger
an expensive SKB reallocation on the fast-path?

> +};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-ks8995-to-ksz8-v11-0-0e04411a559d@kernel.org?part=2

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
  2026-09-10 17:01   ` sashiko-bot
@ 2026-09-10 19:56     ` Linus Walleij
  0 siblings, 0 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-10 19:56 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: robh, conor+dt, devicetree

On Thu, Sep 10, 2026 at 7:01 PM <sashiko-bot@kernel.org> wrote:

> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Hardware Reset Timeout: `ksz8_reset_switch()` erroneously applies a PHY reset to all ports, including the CPU port which lacks an internal PHY.

Again this is a non-issue.

This is what boot loaders do, it makes a proper reset of the switch
and it is tested on hardware.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling
  2026-09-10 17:01   ` sashiko-bot
@ 2026-09-10 19:57     ` Linus Walleij
  0 siblings, 0 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-10 19:57 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: devicetree, robh, conor+dt

On Thu, Sep 10, 2026 at 7:01 PM <sashiko-bot@kernel.org> wrote:

> > +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,
>
> [Severity: Medium]
> Does this code reserve enough headroom for the worst-case transmission path?
>
> When an SKB is transmitted via ks8995_xmit() with a hardware-accelerated
> VLAN tag, the driver first pushes the hardware tag into the payload via
> __vlan_hwaccel_push_inside() which consumes 4 bytes of headroom. It then
> inserts the custom KS8995 switch tag via vlan_insert_tag(), consuming
> another 4 bytes. This requires a total of 8 bytes of headroom.
>
> Because needed_headroom only requests 4 bytes (VLAN_HLEN), if an SKB arrives
> with exactly the requested headroom (such as when forwarded from another
> interface), will the second tag insertion exhaust the headroom and trigger
> an expensive SKB reallocation on the fast-path?

That's a valid concern. I will increase the headroom for v12.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA
  2026-09-09 17:00 ` [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
@ 2026-09-10 20:01   ` netdev-bot+sashiko
  2026-09-10 20:29     ` Linus Walleij
  0 siblings, 1 reply; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-10 20:01 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] Claim: the newly documented `microchip,ksz8995xa` compatible has
  no in-tree consumer — no entry in ksz_dt_ids[]/ksz_spi_ids[], no chip…

--

dt-bindings: net: dsa: microchip: Add KSZ8995XA

This adds a new compatible string microchip,ksz8995xa to the Microchip
KSZ DSA switch binding. The commit message notes the chip was renamed
from KS8995XA and that KSZ8995MA and KSZ8995E are not compatible.

> 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

[Severity: Low]
This isn't a bug, just a note for the record: at this patch alone there is
no in-tree consumer for the new compatible, so a device tree using
microchip,ksz8995xa passes dt_binding_check but does not bind.

The driver side arrives in the later patch of the same series, "net: dsa:
microchip: Support Microchip KSZ8995XA / KS8995XA", which adds:

drivers/net/dsa/microchip/ksz_spi.c:ksz_dt_ids[] {
	...
	{
		.compatible = "microchip,ksz8995xa",
		.data = &ksz_switch_chips[KSZ8995XA]
	},
	...
}

and the SPI alias in ksz_spi_ids[]:

	{ "ksz8995xa" },

So the skew only exists between patch 1 and patch 3 of the series, which is
the expected ordering for bindings-first submissions. Is the intent that
both patches land together in the same merge window?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-ks8995-to-ksz8-v11-0-0e04411a559d%40kernel.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling
  2026-09-09 17:00 ` [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
  2026-09-10 17:01   ` sashiko-bot
@ 2026-09-10 20:01   ` netdev-bot+sashiko
  1 sibling, 0 replies; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-10 20:01 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: 1 · Low: 0

- [Medium] ks8995_rcv() in net/dsa/tag_ks8995.c treats any non-NULL
  return from skb_vlan_untag() as proof that the 4-byte KS8995 switch…

--

net: dsa: tag_ks8995: Add the KS8995 tag handling

This adds a new DSA tagger for the Micrel KS8995 / Microchip KSZ8995XA
switch, which carries its per-port tag in a modified 802.1Q header where
the low nybble of the 0x8100 TPID holds the port mask.  The xmit path
pushes any hardware-accelerated VLAN tag inside the frame and then
inserts the special tag at offset 12, and the rcv path parses the port
from the mangled TPID and moves the tag into the hwaccel area.

> diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
> new file mode 100644
> index 000000000000..f6ef1ef1891f
> --- /dev/null
> +++ b/net/dsa/tag_ks8995.c
> @@ -0,0 +1,145 @@

[ ... ]

> +static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
> +{
> +	int portmask;
> +	int port;
> +	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;
> +	}

[ ... ]

> +	/* 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 */
> +		if (net_ratelimit())
> +			netdev_err(dev, "%s: unable to untag skb\n", __func__);
> +		return NULL;
> +	}

[Severity: Medium]
Is a non-NULL return from skb_vlan_untag() enough to conclude that the
4-byte switch tag was actually removed from the frame head?

net/core/skbuff.c:skb_vlan_untag() starts with:

	if (unlikely(skb_vlan_tag_present(skb))) {
		/* vlan_tci is already set-up so leave this for another time */
		return skb;
	}

On that path the same pointer comes back untouched: no pskb_may_pull(),
no skb_pull_rcsum(skb, VLAN_HLEN), no skb_reorder_vlan_header() and no
mac_header adjustment.

Can ks8995_rcv() be entered with skb_vlan_tag_present(skb) already true?
net/core/dev.c:__netif_receive_skb_core() deliberately keeps the tag for
DSA conduits:

	if (unlikely(skb_vlan_tag_present(skb)) && !netdev_uses_dsa(skb->dev)) {

and net/dsa/tag.c:dsa_switch_rcv() does not clear it either before
calling cpu_dp->rcv().

If that happens, does the frame handed to the user port end up 4 bytes
too long with the STPID still embedded?  After the tagger returns,
dsa_switch_rcv() does:

	skb_push(skb, ETH_HLEN);
	skb->pkt_type = PACKET_HOST;
	skb->protocol = eth_type_trans(skb, skb->dev);

so eth_type_trans() would re-read the still-present 0x810X at offset 12
as the frame ethertype, and the network/transport offsets would point
into the tag.

Would the DSA idiom used elsewhere in the tree work here, i.e. guard the
call and re-test afterwards the way net/dsa/tag.h:dsa_software_vlan_untag()
does?

	if (!skb_vlan_tag_present(skb) && skb->protocol == htons(proto)) {
		skb = skb_vlan_untag(skb);
		if (!skb)
			return NULL;
	}

	if (!skb_vlan_tag_present(skb))
		return skb;

tag_lan9303.c and tag_8021q.c also handle the "tag is already in the
hwaccel area" case explicitly.

> +	netdev_dbg(dev, "%s: etype %04x portmask %04x (%d)\n",
> +		   __func__, etype, portmask, port);
> +	skb->dev = dsa_conduit_find_user(dev, 0, port);
> +	if (!skb->dev) {
> +		kfree_skb(skb);
> +		return NULL;
> +	}
> +
> +	/* skb_vlan_untag() moved the special tag's TCI into the hardware-
> +	 * accelerated VLAN metadata. The switch preserves the TCI of tagged
> +	 * frames, but inserts the ingress port's default tag for untagged frames.
> +	 * Since the driver programs the default tag to 0, a zero TCI identifies
> +	 * an originally untagged frame. Preserve every non-zero TCI as an
> +	 * 802.1Q tag, including VID 0 frames with PCP or DEI set.
> +	 */
> +	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);
> +	}

On the early-return path described above, does this decision act on the
wrong TCI?  The comment assumes skb->vlan_tci came from the special tag,
but if the tag was already in the hwaccel area on entry the TCI is the
conduit-supplied one.

A zero conduit TCI (for example a priority-tagged VID 0 frame) would be
dropped by __vlan_hwaccel_clear_tag(), and a non-zero one gets
relabelled with skb->vlan_proto = htons(ETH_P_8021Q) even if it arrived
as 802.1ad.

For what it is worth, I could confirm the no-op behaviour of
skb_vlan_untag() and that the core stack keeps hwaccel tags alive for
DSA conduits, but I could not demonstrate a plain single-switch setup
that reaches ks8995_rcv() with both a hwaccel tag present and 0x810X
still at offset 12; a normal CTAG-offload NIC will not match TPIDs
0x8101..0x8108.  Cascaded DSA topologies and software injection into the
conduit receive path look like the candidate paths.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-ks8995-to-ksz8-v11-0-0e04411a559d%40kernel.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
  2026-09-09 17:00 ` [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
  2026-09-10 17:01   ` sashiko-bot
@ 2026-09-10 20:02   ` netdev-bot+sashiko
  2026-09-10 20:53     ` Linus Walleij
  1 sibling, 1 reply; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-10 20:02 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 2 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 2 · Low: 0

- [Medium] ksz8_reset_switch() (drivers/net/dsa/microchip/ksz8.c:266 and
  :275) iterates `port < dev->info->port_cnt` (5 for KSZ8995XA) for both…
- [Medium] In ksz8_port_setup()
  (drivers/net/dsa/microchip/ksz8.c:2140-2153) the new KSZ8995XA block…

--

net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA

Adds KSZ8995XA (Micrel KS8995XA) support to the KSZ driver with a reduced
set of dev/switch ops, a per-port PHY reset with poll, header-tag handling
instead of tail tags, and a KSZ8995XA-specific DSCP register layout in the
DCB helpers. The legacy KS8995 driver is made mutually exclusive via
Kconfig and the Micrel compatibles are moved to the KSZ SPI driver.

> diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
> index 941ae9f66f70..c3871d5d6d36 100644
> --- a/drivers/net/dsa/microchip/ksz8.c
> +++ b/drivers/net/dsa/microchip/ksz8.c

[ ... ]

> @@ -253,7 +255,35 @@ 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, reg, val;
> +		int ret;
> +
> +		/* 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++) {
> +			ret = ksz_prmw8(dev, port, REG_PORT_STATUS_3,
> +					PORT_PHY_SOFT_RESET,
> +					PORT_PHY_SOFT_RESET);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		/* IEEE 802.3 allows up to 500 ms for a PHY reset. */
> +		for (port = 0; port < dev->info->port_cnt; port++) {
> +			reg = dev->dev_ops->get_port_addr(port, REG_PORT_STATUS_3);
> +			ret = regmap_read_poll_timeout(ksz_regmap_8(dev), reg, val,
> +						       !(val & PORT_PHY_SOFT_RESET),
> +						       1000, 600000);
> +			if (ret)
> +				return ret;
> +		}

[Severity: Medium]

Should these two loops be bounded by dev->phy_port_cnt instead of
dev->info->port_cnt?

port_cnt is 5 for KSZ8995XA, so port index 4 is included, and the chip
data added by this patch declares that port as having no internal PHY:

	[KSZ8995XA] = {
		...
		.port_cnt = 5,		/* total cpu and user ports */
		...
		.internal_phy = {true, true, true, true, false},

ksz8_get_port_addr(4, REG_PORT_STATUS_3) lands on register 0x5F, and
ksz8_reg.h only defines PHY Status 3 for the four PHY ports:

	#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

The first loop therefore does a read-modify-write of bit 4 in the MII/CPU
port bank where no PHY reset is documented.

The second loop has no per-port skip and its only success condition is
!(val & PORT_PHY_SOFT_RESET). If that bit at 0x5F is a plain writable or
reserved bit that never self-clears, does probe fail here?

	ksz_switch_register() -> dsa_register_switch() -> ksz8_setup()
		-> ksz8_reset_switch()

regmap_read_poll_timeout() would return -ETIMEDOUT after 600 ms, and
ksz8_setup() propagates that after printing "failed to reset switch".
Every other per-PHY loop in this file uses dev->phy_port_cnt, for example
ksz8_config_cpu_port():

	for (i = 0; i < dev->phy_port_cnt; i++) {

> +
> +		/* Some PHYs need additional settling time after reset. */
> +		usleep_range(1000, 2000);
> +	} else if (ksz_is_ksz88x3(dev)) {
>  		/* reset switch */
>  		ksz_cfg(dev, KSZ8863_REG_SW_RESET,
>  			KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);

[ ... ]

> @@ -2057,6 +2137,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: Medium]

Can the opposite tag bit stay set here?  Both bits live in
REG_PORT_CTRL_0:

	#define PORT_INSERT_TAG			BIT(2)
	#define PORT_REMOVE_TAG			BIT(1)

and ksz_port_cfg(..., true) can only set bits, never clear them:

	static void ksz_port_cfg(struct ksz_device *dev, int port, int offset,
				 u8 bits, bool set)
	{
		ksz_rmw8(dev, dev->dev_ops->get_port_addr(port, offset), bits,
			 set ? bits : 0);
	}

So a user port that comes up with PORT_INSERT_TAG already set keeps it,
and a CPU port that comes up with PORT_REMOVE_TAG already set keeps that.

For this chip ksz8_reset_switch() intentionally performs no global reset,
and its own comment says the prior state cannot be trusted:

	/* 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.
	 */

No other path clears these bits for KSZ8995XA either: the VLAN writer
ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged) is not
reachable through ksz8995xa_switch_ops, and ksz8_port_queue_split() masks
only KSZ8795_PORT_2QUEUE_SPLIT_EN.

Would a single read-modify-write with mask PORT_INSERT_TAG |
PORT_REMOVE_TAG, setting one and clearing the other, be more robust?  As
written, a stale PORT_INSERT_TAG on a user port emits the private tag onto
the wire, and a stale PORT_REMOVE_TAG on the CPU port strips the tag that
tag_ks8995 expects on receive, both without any error being reported.

>  	/* Disable all WoL options by default. Otherwise
>  	 * ksz_switch_macaddr_get/put logic will not work properly.
>  	 * CPU port 4 has no WoL functionality.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-ks8995-to-ksz8-v11-0-0e04411a559d%40kernel.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver
  2026-09-09 17:00 ` [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver Linus Walleij
@ 2026-09-10 20:02   ` netdev-bot+sashiko
  2026-09-10 20:40     ` Linus Walleij
  0 siblings, 1 reply; 16+ messages in thread
From: netdev-bot+sashiko @ 2026-09-10 20:02 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] Incomplete DT-binding transition: after drivers/net/dsa/ks8995.c
  is deleted, the compatibles "micrel,ks8995", "micrel,ksz8795" and…

--

net: dsa: ks8995: Delete surplus driver

The old ks8995 DSA driver is removed because the Microchip KSZ driver now
handles the KS(Z)8995XA, and the NET_DSA_KS8995 Kconfig symbol is turned
into a transitional stub that selects the KSZ SPI driver instead.  The two
in-tree device trees using "micrel,ks8995" are said to keep working with
the replacement driver.

> diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
> index e309e4c1ee3a..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 = n
> -	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.

[ ... ]

> 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 @@

[ ... ]

> -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 device tree bindings move over together with the driver here?

After this deletion, "micrel,ks8995", "micrel,ksz8795" and "micrel,ksz8864"
are documented only in
Documentation/devicetree/bindings/net/dsa/micrel,ks8995.yaml, which was
written for the driver being removed:

  properties:
    compatible:
      enum:
        - micrel,ks8995
        - micrel,ksz8795
        - micrel,ksz8864
    reg: ...
    reset-gpios: ...
  ...
  unevaluatedProperties: false

The driver that now matches those compatibles is
drivers/net/dsa/microchip/ksz_spi.c, whose ksz_dt_ids still carries:

  {
          .compatible = "micrel,ks8995",
          .data = &ksz_switch_chips[KSZ8995XA]
  },

ksz_spi_probe() does dev->irq = spi->irq, and ksz_mdio_register() looks up
an "mdio" child node and an "mdio-parent-bus" phandle.  Those properties are
described in microchip,ksz.yaml but rejected by micrel,ks8995.yaml because of
unevaluatedProperties: false.  Does that mean a node using the legacy
compatible with interrupts or an mdio child node now fails dt_binding_check?

In the other direction, microchip,ksz.yaml gained microchip,ksz8995xa but
lists none of the three micrel,* strings, so the two in-tree nodes named in
the commit message, ethernet-switch@0 in
arch/arm/boot/dts/intel/ixp/intel-ixp42x-linksys-wrv54g.dts and in
arch/arm/boot/dts/intel/ixp/intel-ixp42x-actiontec-mi424wr.dtsi, cannot be
validated against the binding of the driver that handles them.  Would it make
sense to fold the legacy compatibles into microchip,ksz.yaml, marking them
deprecated as appropriate?

One more detail in the surviving schema: the description states

  The chip itself is managed over SPI, but all the
  PHYs need to be accessed from an external MDIO channel.

while the chip data for KSZ8995XA in drivers/net/dsa/microchip/ksz_common.c
declares

  .internal_phy = {true, true, true, true, false},

Is that sentence still accurate for the replacement driver?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909-ks8995-to-ksz8-v11-0-0e04411a559d%40kernel.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA
  2026-09-10 20:01   ` netdev-bot+sashiko
@ 2026-09-10 20:29     ` Linus Walleij
  0 siblings, 0 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-10 20:29 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
	kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
	netdev, devicetree

On Thu, Sep 10, 2026 at 10:02 PM <netdev-bot+sashiko@kernel.org> wrote:

> So the skew only exists between patch 1 and patch 3 of the series, which is
> the expected ordering for bindings-first submissions. Is the intent that
> both patches land together in the same merge window?

Yes.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver
  2026-09-10 20:02   ` netdev-bot+sashiko
@ 2026-09-10 20:40     ` Linus Walleij
  0 siblings, 0 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-10 20:40 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
	kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
	netdev, devicetree

Hi netdev-Sashiko,

On Thu, Sep 10, 2026 at 10:02 PM <netdev-bot+sashiko@kernel.org> wrote:

> After this deletion, "micrel,ks8995", "micrel,ksz8795" and "micrel,ksz8864"
> are documented only in
> Documentation/devicetree/bindings/net/dsa/micrel,ks8995.yaml, which was
> written for the driver being removed:

No.

You don't understand how device tree bindings work.

Device tree bindings are not written "for a driver".

Device tree bindings are written to describe hardware for any
operating system or other consumer.

The Linux driver happens to use two bindings and you are repeatedly
confused by this.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
  2026-09-10 20:02   ` netdev-bot+sashiko
@ 2026-09-10 20:53     ` Linus Walleij
  0 siblings, 0 replies; 16+ messages in thread
From: Linus Walleij @ 2026-09-10 20:53 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: woojung.huh, UNGLinuxDriver, andrew, olteanv, davem, edumazet,
	kuba, pabeni, robh, krzk+dt, conor+dt, marex, horms, linux, nb,
	netdev, devicetree

Hi netdev-Sashiko,

On Thu, Sep 10, 2026 at 10:02 PM <netdev-bot+sashiko@kernel.org> wrote:

> - [Medium] ksz8_reset_switch() (drivers/net/dsa/microchip/ksz8.c:266 and
>   :275) iterates `port < dev->info->port_cnt` (5 for KSZ8995XA) for both…

No.

I already said why but apparently you ignore old replies to the same
patch. Learn to add this to your context, this is parroting.

> - [Medium] In ksz8_port_setup()
>   (drivers/net/dsa/microchip/ksz8.c:2140-2153) the new KSZ8995XA block…

This is a valid fringe thingy allright I'll fix it in v12.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-09-10 20:53 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09 17:00 [PATCH net-next v11 0/4] net: dsa: microchip: Add support for KSZ8995XA/KS8995XA Linus Walleij
2026-09-09 17:00 ` [PATCH net-next v11 1/4] dt-bindings: net: dsa: microchip: Add KSZ8995XA Linus Walleij
2026-09-10 20:01   ` netdev-bot+sashiko
2026-09-10 20:29     ` Linus Walleij
2026-09-09 17:00 ` [PATCH net-next v11 2/4] net: dsa: tag_ks8995: Add the KS8995 tag handling Linus Walleij
2026-09-10 17:01   ` sashiko-bot
2026-09-10 19:57     ` Linus Walleij
2026-09-10 20:01   ` netdev-bot+sashiko
2026-09-09 17:00 ` [PATCH net-next v11 3/4] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA Linus Walleij
2026-09-10 17:01   ` sashiko-bot
2026-09-10 19:56     ` Linus Walleij
2026-09-10 20:02   ` netdev-bot+sashiko
2026-09-10 20:53     ` Linus Walleij
2026-09-09 17:00 ` [PATCH net-next v11 4/4] net: dsa: ks8995: Delete surplus driver Linus Walleij
2026-09-10 20:02   ` netdev-bot+sashiko
2026-09-10 20:40     ` Linus Walleij

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox