From nobody Sun Oct 5 03:40:34 2025 Received: from mail-pg1-f175.google.com (mail-pg1-f175.google.com [209.85.215.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8065B27F727; Fri, 8 Aug 2025 17:41:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754674897; cv=none; b=PTQrnH6E7OdoJsqSJyNS9LCAhvmNv9rx1j82LjgmoZGHlUaJhok4Sihu6DfMegUTmBmKZpEDiZIZSAAy41FpWwu9at5XwEW7QsfH8kws0yiQIYmlCaBIK82BtdfFiZZYX4Rehj+Mmf3x/4UIoMiq3o4lD+mVp5Z0abBsb9dKaT0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754674897; c=relaxed/simple; bh=Keq+q4SHjEnprIHht1Gkm51TMEsbCSJKSbRviiHVooY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nxPiQrr57+Pup2dv2J2FYpymAOUu14JjeWnqa9pRSrAATI6Q7YPVRFCa/ZjglhnvkSC9ExOmsEMLCH4Ydyzg/QeIs65jhe+nNVRmFX/Yf5Pkza7SwuiEYNkWaNR/GRKooN1XtQbXhJFrQNKMtE08I7JI98Ov+ncBsO50/wsQwwA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=BldNdrya; arc=none smtp.client-ip=209.85.215.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="BldNdrya" Received: by mail-pg1-f175.google.com with SMTP id 41be03b00d2f7-b2c4e46a89fso1936612a12.2; Fri, 08 Aug 2025 10:41:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1754674895; x=1755279695; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=v04PbebFhHBNtpWqDhvlshr0WfLlj+fEY3t+CKSpRrk=; b=BldNdryaeCUP30uTi5MQ56cAjRC5H0zVWGSRkZhnfpCcMICLrLS9IdT4Cb4ZrCNCnv IYFqIWULqmYA89cz+1p6a42ZgU1Hee2k9T4SlRBSxdJAxQEesv/AMxu0QVxcuVY1Cg/o 3q7/dAVT95MB/uVK3uRh0lFutj+3TCnyhdf5+N+r7TQow6leSx2Tc1Vj1EeTN2dv/Rig 9Eoswq5TWuYZiIZA2pGlZWxDdI1wqdvJwUtddHZ/F/FF2UBRq1lVEUVh/75kQnCSHHiR Phy6gbktmDrM7f9hnb53rJE2q3t/cK5eLuAZrqOd7kHm3KaU/phIhy1prku3F3Qe7Y1C BmSw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1754674895; x=1755279695; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=v04PbebFhHBNtpWqDhvlshr0WfLlj+fEY3t+CKSpRrk=; b=UpejoeZjjUJyoJ3659nzlyexnSe2RbfJmJvA/fRpmURsaoAMMBnuDoz4cEThWvTibY JFwJ+37DczmiAgdhPVLHZB0rsFyPTiZdFVYSyXbX4iXuVIo8dyhILv2XPpP1ZL0oMjQE iKIBk56qXro6VzQdzWeYjm4MU1hcw5VUhUie47yrmmdlty6leCW8q4JMw7LJ8XAQLDDN LW9MxjuXklDpuE1GSqntr9KzqmGRnk5E8qcOAvIB4i4Ie+Lq6zHOYKMOEj/Rvl+RPZlI k/dwDfJ1yx3EvpstfljfVGZZQksl8YKPJJm8nmQPY2Xt3DFS/mUbjxy4N7H0Utzkr36d OalA== X-Forwarded-Encrypted: i=1; AJvYcCW3MXrHJoPJ/iW4g7N5UD7N2f6uNTYetKO2ZEOsD/ul0CSZ3MWJiTkB3rbfnm4n74X/lgqKmPR6WPxbnzU=@vger.kernel.org X-Gm-Message-State: AOJu0YwcNy6hV50RwCzjuTyBBUvrQRt7xHY1bKctAzY1N6FbCCDyTQ6I w/opjIZVETOuNOfJh751i9e3DU0+1lNrtkUfcDjngRjSleZgngtMM9hgeU1HL1sND84= X-Gm-Gg: ASbGncupf/Ce87QQDC6+FtL8JbjxufwCJJnEH/q1Bj+jTVfGMEnw3mK3Cl40lAP11nG R+4yG8h2fZyo4AeOr9DX4JmQb/Eep1cG+EAxQMBv2yVojKh1ytoN/ulQsdPUXRyTaq+SsbeaHQG pdQtiHMsiWQs5SJ8pF9iusHXhwZa6YQvL+xDuQ51PWdZsurTCVeocWi8xI8n6oR7aTH6jhu2JYZ K1TDWFL4P+qEI//TLMcbTp++2mxLsz8cZ1A78Fp4KU32qHd3YwAeyj/dqzP7ct6sEmte0/CMHsg wFVs6ZtAPM7Xi+gXgaWuRS3052qXVKHBizfFmpaQ13DhWexkVtO2c7saDOmgBT1O4P8SK6GL0Rh EXWr43Gy+Sg00gn69i/m0O/NA8dnwAY2P5nmirWib X-Google-Smtp-Source: AGHT+IFdmHH+SWPYWVEVCNj9fwPDu+oalvm5Zr0wQ4+JuRnuvSri63Qm+BG8m1r2DMVavbgJ44mruw== X-Received: by 2002:a17:902:ef4d:b0:242:cfc7:1fd6 with SMTP id d9443c01a7336-242cfc72077mr17907315ad.32.1754674894480; Fri, 08 Aug 2025 10:41:34 -0700 (PDT) Received: from d.home.yangfl.dn42 ([104.28.215.164]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-241e8975a66sm214174165ad.95.2025.08.08.10.41.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 08 Aug 2025 10:41:34 -0700 (PDT) From: David Yang To: netdev@vger.kernel.org Cc: David Yang , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Russell King , linux-kernel@vger.kernel.org Subject: [PATCH 1/2] net: dsa: tag_yt921x: add support for Motorcomm YT921x tags Date: Sat, 9 Aug 2025 01:38:02 +0800 Message-ID: <20250808173808.273774-2-mmyangfl@gmail.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250808173808.273774-1-mmyangfl@gmail.com> References: <20250808173808.273774-1-mmyangfl@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add support for Motorcomm YT921x tags, which includes a configurable ethertype field (default to 0x9988). Signed-off-by: David Yang --- include/net/dsa.h | 2 + net/dsa/Kconfig | 6 +++ net/dsa/Makefile | 1 + net/dsa/tag_yt921x.c | 116 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 net/dsa/tag_yt921x.c diff --git a/include/net/dsa.h b/include/net/dsa.h index d73ea0880066..67762fdaf3c7 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -55,6 +55,7 @@ struct tc_action; #define DSA_TAG_PROTO_LAN937X_VALUE 27 #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE 28 #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29 +#define DSA_TAG_PROTO_YT921X_VALUE 30 =20 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE =3D DSA_TAG_PROTO_NONE_VALUE, @@ -87,6 +88,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_RZN1_A5PSW =3D DSA_TAG_PROTO_RZN1_A5PSW_VALUE, DSA_TAG_PROTO_LAN937X =3D DSA_TAG_PROTO_LAN937X_VALUE, DSA_TAG_PROTO_VSC73XX_8021Q =3D DSA_TAG_PROTO_VSC73XX_8021Q_VALUE, + DSA_TAG_PROTO_YT921X =3D DSA_TAG_PROTO_YT921X_VALUE, }; =20 struct dsa_switch; diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index 869cbe57162f..6b94028b1fcc 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -190,4 +190,10 @@ config NET_DSA_TAG_XRS700X Say Y or M if you want to enable support for tagging frames for Arrow SpeedChips XRS700x switches that use a single byte tag trailer. =20 +config NET_DSA_TAG_YT921X + tristate "Tag driver for Motorcomm YT921x switches" + help + Say Y or M if you want to enable support for tagging frames for + Motorcomm YT921x switches. + endif diff --git a/net/dsa/Makefile b/net/dsa/Makefile index 555c07cfeb71..4b011a1d5c87 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_NET_DSA_TAG_SJA1105) +=3D tag_sja1105.o obj-$(CONFIG_NET_DSA_TAG_TRAILER) +=3D tag_trailer.o obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) +=3D tag_vsc73xx_8021q.o obj-$(CONFIG_NET_DSA_TAG_XRS700X) +=3D tag_xrs700x.o +obj-$(CONFIG_NET_DSA_TAG_YT921X) +=3D tag_yt921x.o =20 # for tracing framework to find trace.h CFLAGS_trace.o :=3D -I$(src) diff --git a/net/dsa/tag_yt921x.c b/net/dsa/tag_yt921x.c new file mode 100644 index 000000000000..95354bdb0aff --- /dev/null +++ b/net/dsa/tag_yt921x.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Motorcomm YT921x Switch External CPU tagging + * + * Copyright (c) 2025 David Yang + * + * +----+----+-------+-----+----+--------- + * | DA | SA | TagET | Tag | ET | Payload ... + * +----+----+-------+-----+----+--------- + * 6 6 2 6 2 N + * + * Tag Ethertype: CPU_TAG_TPID_TPIDf (default: 0x9988) + * Tag: + * 2: Service VLAN Tag + * 2: Rx Port + * 15b: Rx Port Valid + * 14b-11b: Rx Port + * 10b-0b: Unknown Value 0x80 + * 2: Tx Port(s) + * 15b: Tx Port(s) Valid + * 10b-0b: Tx Port(s) Mask + */ + +#include +#include +#include + +#include "tag.h" + +#define YT921X_NAME "yt921x" + +#define YT921X_TAG_LEN 8 + +#define ETH_P_YT921X 0x9988 + +#define YT921X_TAG_PORT_ENf BIT(15) +#define YT921X_TAG_RX_PORTf GENMASK(14, 11) +#define YT921X_TAG_TX_PORTf GENMASK(10, 0) +#define YT921X_TAG_TX_PORTnv(port) BIT(port) + +static struct sk_buff * +yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct dsa_port *dp =3D dsa_user_to_port(netdev); + __be16 *tag; + + skb_push(skb, YT921X_TAG_LEN); + dsa_alloc_etype_header(skb, YT921X_TAG_LEN); + + tag =3D (__be16 *)(skb->data + 2 * ETH_ALEN); + + /* Might use yt921x_priv::tag_eth_p, but... */ + tag[0] =3D htons(ETH_P_YT921X); + /* Service VLAN not used here, set to 1 anyway */ + tag[1] =3D htons(1); + tag[2] =3D 0; + tag[3] =3D htons(YT921X_TAG_PORT_ENf | YT921X_TAG_TX_PORTnv(dp->index)); + + /* Now tell the conduit network device about the desired output queue + * as well + */ + skb_set_queue_mapping(skb, dp->index); + + return skb; +} + +static struct sk_buff * +yt921x_tag_rcv(struct sk_buff *skb, struct net_device *netdev) +{ + __be16 *tag; + u16 rx; + int rx_port; + + if (unlikely(!pskb_may_pull(skb, YT921X_TAG_LEN))) + return NULL; + + tag =3D (__be16 *)skb->data; + + /* Locate which port this is coming from */ + rx =3D ntohs(tag[1]); + if (unlikely((rx & YT921X_TAG_PORT_ENf) =3D=3D 0)) { + netdev_err(netdev, "Unexpected rx tag 0x%04x\n", rx); + return NULL; + } + + rx_port =3D FIELD_GET(YT921X_TAG_RX_PORTf, rx); + skb->dev =3D dsa_conduit_find_user(netdev, 0, rx_port); + if (unlikely(!skb->dev)) { + dev_warn_ratelimited(&netdev->dev, + "Couldn't decode source port\n"); + return NULL; + } + + /* Remove YT921x tag and update checksum */ + skb_pull_rcsum(skb, YT921X_TAG_LEN); + + dsa_default_offload_fwd_mark(skb); + + dsa_strip_etype_header(skb, YT921X_TAG_LEN); + + return skb; +} + +static const struct dsa_device_ops yt921x_netdev_ops =3D { + .name =3D YT921X_NAME, + .proto =3D DSA_TAG_PROTO_YT921X, + .xmit =3D yt921x_tag_xmit, + .rcv =3D yt921x_tag_rcv, + .needed_headroom =3D YT921X_TAG_LEN, +}; + +MODULE_DESCRIPTION("DSA tag driver for Motorcomm YT921x switches"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_YT921X, YT921X_NAME); + +module_dsa_tag_driver(yt921x_netdev_ops); --=20 2.47.2 From nobody Sun Oct 5 03:40:34 2025 Received: from mail-pl1-f173.google.com (mail-pl1-f173.google.com [209.85.214.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A09B2285C9B; Fri, 8 Aug 2025 17:41:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754674903; cv=none; b=cpmKXxsGrQQmnRniaiD2uQ7LD1QfKeEgQ9iefpcso2i1suh0JTa8DatNXKEGVIMCWciZsOSKzSSpg/hrykARtmNZrIPdzsbePmN9eQTPKHg4iuWGNYyqLTwbzoGvBZjGOolnddXoGZICI+0193KxAIyDcN3CZt8NquAdT7lCdg8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754674903; c=relaxed/simple; bh=LaXcrbF04uGCzuqvW2rkxrLiksywdTE7gYhSP+NCIrM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FJKUR/qn3DxPo5R2rS2w2nS5SrvxiWzPzu4x19xF390gy9TPrKvNxl0DrRzsn70NY+oFueyytoOht7FggOStYTsVkEBcEO92WYRrBr4ezhKgirrG3NhY8gp5zP65ggbAReYd4lKskLINnFk0/4mYxstow+eVyUc4ovKQSFv24ls= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MyvY083p; arc=none smtp.client-ip=209.85.214.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MyvY083p" Received: by mail-pl1-f173.google.com with SMTP id d9443c01a7336-2403df11a2aso17151945ad.0; Fri, 08 Aug 2025 10:41:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1754674899; x=1755279699; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bgUqdqkH3U2q/yFRaB1F89XXy6BOMuClC52p0B0p+uM=; b=MyvY083pTuNs/IwsPPQRuuepoqz45PLGnamZcUj2oUWZe2lB+Gyz9yS4fhQkRjGe58 5ZeQyOcjAHmm0gcQLw/QQTkWTR/p564mpeWPLCX950pMeGByMEI/LOtajjr9XzqcFiSZ 4UTXz7s5lUw6hhXYZXcWUy0/12PtaKimeXJOUS/SDlUaKDjj4pk9TI2bxe0s2FWzDuV/ tyju/Pc7Ld0HbbY3s0uWmf6orBd2tIIIzplXmJKwu15O3xPdWBKh2QrAJMHBZeFLFBkV bNb/R4ro+glW7V0V5NJolrF3iWYx8Dqkg4tZtBjZwGGTxSoFO0X55I9c8NTjlbetBqMm uEJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1754674899; x=1755279699; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=bgUqdqkH3U2q/yFRaB1F89XXy6BOMuClC52p0B0p+uM=; b=lPy06co4M9c1BR56B7jsfNq1d0KZ1yuDR0FTiYxv+/dTUSPXwcveR3xJuIxHBn6eMr NuegsBigQyIb5ItRhbvj6rekp132VSbs01Nd6xbmOHVIDpSiSsS4nltHQVNtne65CKtT xdjVz84Z+QVMxDFt2/YWh5S1XbYk0LwQdF7vpb62lLrFT0IeunUMKD98ahuElYuwjWw1 JWtIvlaCc049yRXVS7sR54HftTBebOa/oILBqptsMuoHrl8Ezg1wZWVIEArtBuZg2YCz QgGF+h/faJFzEdLKh34kUK0l6QnFed5ME0R1+loMRMDlvusrEp4HViXE0AGyELXKRvdj 474Q== X-Forwarded-Encrypted: i=1; AJvYcCXztBeqSceOFK4mcI1m6DXxXwTRmZLfiWR854N6yURNy3qRHmm4tDmCbUh+ote1dMipZVXCCUSI3n6C8b4=@vger.kernel.org X-Gm-Message-State: AOJu0Yzs6EvzW5ogWdxyvdvAN/CtNlKmUMvz2yJ2YlearCkVbCSLSKle 8M76MlisD/VCs0xIhgGPjrx8XDF3+R6Ld1cEqOY2VZu78WFfVeZU0NUY+CQvcpIa0Y4= X-Gm-Gg: ASbGnctWh39V5/jovQWdG7wt91vcw79eEK0L0hSRXfA7dl7PAwIIf9vZnzWxods4wbv Ccc7DMLprxk7myfbhuJMMCOM1DmIYhPrhMIvXBfyQxCCfMg/NJUmVM1ONiGaCearDxN9NPcJVr+ FsXbBsXuffy7GcBznN2czOaq65k7YtPdZQbjBEOW4uShA4P3Zwd+r1yUucoGWzj9KcbB6FqovVm VHdWmYIt4pZ0Uh8xQe/kQBoha/WyKVMZpDIxjGWdFK0dG+AHpn6/ysgCyy9V0HyB00duv87maPt 2100pPezJjLoHZ9z17DM9t8q4SSzmYRj2Q/nLJmd2J7M6W3vaA/fuxIFCHT6HKeAejyUJ3mtHjv J4Kt9Ixs8wDgglXNbLIsB2KDhkwkudQ== X-Google-Smtp-Source: AGHT+IFJehmHFpOw6swuMkMXfPHN7pWA5cq5MXFfEUW5VV3O1yVcFUtWpUm/jQfXFI17OHyeRkcf8A== X-Received: by 2002:a17:902:ce8c:b0:240:4d5b:29b4 with SMTP id d9443c01a7336-242c19a0913mr59649855ad.0.1754674897890; Fri, 08 Aug 2025 10:41:37 -0700 (PDT) Received: from d.home.yangfl.dn42 ([104.28.215.164]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-241e8975a66sm214174165ad.95.2025.08.08.10.41.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 08 Aug 2025 10:41:37 -0700 (PDT) From: David Yang To: netdev@vger.kernel.org Cc: David Yang , Andrew Lunn , Vladimir Oltean , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Russell King , linux-kernel@vger.kernel.org Subject: [PATCH 2/2] net: dsa: yt921x: Add support for Motorcomm YT921x Date: Sat, 9 Aug 2025 01:38:03 +0800 Message-ID: <20250808173808.273774-3-mmyangfl@gmail.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20250808173808.273774-1-mmyangfl@gmail.com> References: <20250808173808.273774-1-mmyangfl@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Motorcomm YT921x is a series of ethernet switches developed by Shanghai Motorcomm Electronic Technology, including: - YT9215S / YT9215RB / YT9215SC: 5 GbE phys - YT9213NB / YT9214NB: 2 GbE phys - YT9218N / YT9218MB: 8 GbE phys and up to 2 serdes interfaces. This patch adds basic support for a working DSA switch, but not including any possible offloading capabilities. Driver verified on a stock wireless router with IPQ5018 + YT9215S. Signed-off-by: David Yang --- drivers/net/dsa/Kconfig | 7 + drivers/net/dsa/Makefile | 1 + drivers/net/dsa/yt921x.c | 1895 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1903 insertions(+) create mode 100644 drivers/net/dsa/yt921x.c diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index ec759f8cb0e2..1b789daab34c 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -152,4 +152,11 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM This enables support for the Vitesse VSC7385, VSC7388, VSC7395 and VSC7398 SparX integrated ethernet switches, connected over a CPU-attached address bus and work in memory-mapped I/O mode. + +config NET_DSA_YT921X + tristate "Motorcomm YT9215 ethernet switch chip support" + select NET_DSA_TAG_YT921X + help + This enables support for the Motorcomm YT9215 ethernet switch + chip. endmenu diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index cb9a97340e58..dc81769fa92b 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) +=3D lan9303_mdio= .o obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) +=3D vitesse-vsc73xx-core.o obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) +=3D vitesse-vsc73xx-platfo= rm.o obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) +=3D vitesse-vsc73xx-spi.o +obj-$(CONFIG_NET_DSA_YT921X) +=3D yt921x.o obj-y +=3D b53/ obj-y +=3D hirschmann/ obj-y +=3D microchip/ diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c new file mode 100644 index 000000000000..07f66dd9be89 --- /dev/null +++ b/drivers/net/dsa/yt921x.c @@ -0,0 +1,1895 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Motorcomm YT921x Switch + * + * Should work on YT9213/YT9214/YT9215/YT9218, but only tested on YT9215+S= GMII, + * be sure to do your own checks before porting to another chip. + * + * Copyright (c) 2025 David Yang + */ + +#include +#include +#include +#include +#include +#include +#include + +#undef dev_dbg +#define dev_dbg dev_info + +/******** hardware definitions ********/ + +#define YT921X_SMI_SWITCHIDf GENMASK(3, 2) +#define YT921X_SMI_SWITCHIDv(x) FIELD_PREP(YT921X_SMI_SWITCHIDf, (x)) +#define YT921X_SMI_ADf BIT(1) +#define YT921X_SMI_ADDRv 0 +#define YT921X_SMI_DATAv YT921X_SMI_ADf +#define YT921X_SMI_RWf BIT(0) +#define YT921X_SMI_WRITEv 0 +#define YT921X_SMI_READv YT921X_SMI_RWf + +#define YT921X_RESETm 0x80000 +#define YT921X_RESET_HWf BIT(31) +#define YT921X_RESET_SWf BIT(1) +#define YT921X_FUNCm 0x80004 +#define YT921X_FUNC_MIBf BIT(1) +#define YT921X_CHIP_IDm 0x80008 +#define YT921X_CHIP_ID_IDf GENMASK(31, 16) +#define YT921X_EXT_CPU_PORTm 0x8000c +#define YT921X_EXT_CPU_PORT_TAG_ENf BIT(15) +#define YT921X_EXT_CPU_PORT_PORT_ENf BIT(14) +#define YT921X_EXT_CPU_PORT_PORTf GENMASK(3, 0) +#define YT921X_EXT_CPU_PORT_PORTv(x) FIELD_PREP(YT921X_EXT_CPU_PORT_PO= RTf, (x)) +#define YT921X_CPU_TAG_TPIDm 0x80010 +#define YT921X_CPU_TAG_TPID_TPIDf GENMASK(15, 0) +#define YT921X_CPU_TAG_TPID_TPIDv 0x9988 +#define YT921X_EXTIF_SERDESm 0x80028 +#define YT921X_EXTIF_SERDES_ENf BIT(6) +#define YT921X_EXTIF_SERDES_EXTIFnf(port) BIT((port) - 8) +#define YT921X_SGMIInm(port) (0x8008c + 4 * ((port) - 8)) +#define YT921X_SGMII_MODEf GENMASK(9, 7) +#define YT921X_SGMII_MODEv(x) FIELD_PREP(YT921X_SGMII_MODEf, (x)) +#define YT921X_SGMII_MODE_SGMII_MACv YT921X_SGMII_MODEv(0) +#define YT921X_SGMII_MODE_SGMII_PHYv YT921X_SGMII_MODEv(1) +#define YT921X_SGMII_MODE_1000BASEXv YT921X_SGMII_MODEv(2) +#define YT921X_SGMII_MODE_100BASEXv YT921X_SGMII_MODEv(3) +#define YT921X_SGMII_MODE_2500BASEXv YT921X_SGMII_MODEv(4) +#define YT921X_SGMII_MODE_BASEXv YT921X_SGMII_MODEv(5) +#define YT921X_SGMII_MODE_DISABLEv YT921X_SGMII_MODEv(6) +#define YT921X_SGMII_RX_PAUSEf BIT(6) +#define YT921X_SGMII_TX_PAUSEf BIT(5) +#define YT921X_SGMII_LINKf BIT(4) /* force link */ +#define YT921X_SGMII_DUPLEX_FULLf BIT(3) +#define YT921X_SGMII_SPEEDf GENMASK(2, 0) +#define YT921X_SGMII_SPEEDv(x) FIELD_PREP(YT921X_SGMII_SPEEDf, (x)) +#define YT921X_SGMII_SPEED_10v YT921X_SGMII_SPEEDv(0) +#define YT921X_SGMII_SPEED_100v YT921X_SGMII_SPEEDv(1) +#define YT921X_SGMII_SPEED_1000v YT921X_SGMII_SPEEDv(2) +#define YT921X_SGMII_SPEED_2500v YT921X_SGMII_SPEEDv(4) +#define YT921X_PORTn_CTRLm(port) (0x80100 + 4 * (port)) +#define YT921X_PORT_CTRL_FLOW_CONTROL_ANf BIT(10) +#define YT921X_PORTn_STATUSm(port) (0x80200 + 4 * (port)) +#define YT921X_PORT_LINKf BIT(9) /* CTRL: auto negotiation */ +#define YT921X_PORT_HALF_FLOW_CONTROLf BIT(8) /* Half-duplex back press= ure mode */ +#define YT921X_PORT_DUPLEX_FULLf BIT(7) +#define YT921X_PORT_RX_FLOW_CONTROLf BIT(6) +#define YT921X_PORT_TX_FLOW_CONTROLf BIT(5) +#define YT921X_PORT_RX_MAC_ENf BIT(4) +#define YT921X_PORT_TX_MAC_ENf BIT(3) +#define YT921X_PORT_SPEEDf GENMASK(2, 0) +#define YT921X_PORT_SPEEDv(x) FIELD_PREP(YT921X_PORT_SPEEDf, (x)) +#define YT921X_PORT_SPEED_10v YT921X_PORT_SPEEDv(0) +#define YT921X_PORT_SPEED_100v YT921X_PORT_SPEEDv(1) +#define YT921X_PORT_SPEED_1000v YT921X_PORT_SPEEDv(2) +#define YT921X_PORT_SPEED_2500v YT921X_PORT_SPEEDv(4) +#define YT921X_PON_STRAP_FUNCm 0x80320 +#define YT921X_PON_STRAP_VALm 0x80324 +#define YT921X_PON_STRAP_CAPm 0x80328 +#define YT921X_PON_STRAP_EEEf BIT(16) +#define YT921X_PON_STRAP_LOOP_DETECTf BIT(7) +#define YT921X_MDIO_POLLINGnm(port) (0x80364 + 4 * ((port) - 8)) +#define YT921X_MDIO_POLLING_DUPLEX_FULLf BIT(4) +#define YT921X_MDIO_POLLING_LINKf BIT(3) +#define YT921X_MDIO_POLLING_SPEEDf GENMASK(2, 0) +#define YT921X_MDIO_POLLING_SPEEDv(x) FIELD_PREP(YT921X_MDIO_POLLING_S= PEEDf, (x)) +#define YT921X_MDIO_POLLING_SPEED_10v YT921X_MDIO_POLLING_SPEEDv(0) +#define YT921X_MDIO_POLLING_SPEED_100v YT921X_MDIO_POLLING_SPEEDv(1) +#define YT921X_MDIO_POLLING_SPEED_1000v YT921X_MDIO_POLLING_SPEEDv(2) +#define YT921X_MDIO_POLLING_SPEED_2500v YT921X_MDIO_POLLING_SPEEDv(4) +#define YT921X_CHIP_MODEm 0x80388 +#define YT921X_CHIP_MODE_MODEf GENMASK(1, 0) +#define YT921X_EXTIF_SELm 0x80394 +#define YT921X_EXTIF_SEL_EXTIFn_XMIIf(port) BIT(9 - (port)) /* Yes, it's= reversed */ +#define YT921X_EXTIFn_MODEm(port) (0x80400 + 8 * ((port) - 8)) +#define YT921X_EXTIF_MODE_MODEf GENMASK(31, 29) +#define YT921X_EXTIF_MODE_MODEv(x) FIELD_PREP(YT921X_EXTIF_MODE_MODEf,= (x)) +#define YT921X_EXTIF_MODE_MODE_MIIv YT921X_EXTIF_MODE_MODEv(0) +#define YT921X_EXTIF_MODE_MODE_REVMIIv YT921X_EXTIF_MODE_MODEv(1) +#define YT921X_EXTIF_MODE_MODE_RMIIv YT921X_EXTIF_MODE_MODEv(2) +#define YT921X_EXTIF_MODE_MODE_REVRMIIv YT921X_EXTIF_MODE_MODEv(3) +#define YT921X_EXTIF_MODE_MODE_RGMIIv YT921X_EXTIF_MODE_MODEv(4) +#define YT921X_EXTIF_MODE_MODE_DISABLEv YT921X_EXTIF_MODE_MODEv(5) +#define YT921X_EXTIF_MODE_PORT_ENf BIT(18) + +#define YT921X_MACn_JUMBOm(port) (0x81008 + 0x1000 * (port)) +#define YT921X_MAC_FRAME_SIZEf GENMASK(21, 8) +#define YT921X_MAC_FRAME_SIZEv(x) FIELD_PREP(YT921X_MAC_FRAME_SIZEf, (= x)) + +#define YT921X_MACn_EEE_VALm(port) (0xa0000 + 4 * (port)) +#define YT921X_MAC_EEE_VAL_DATAf BIT(1) + +#define YT921X_MAC_EEE_CTRLm 0xb0000 +#define YT921X_MAC_EEE_CTRL_ENnf(port) BIT(port) + +#define YT921X_MIB_CTRLm 0xc0004 +#define YT921X_MIB_CTRL_CLEANf BIT(30) +#define YT921X_MIB_CTRL_PORTf GENMASK(6, 3) +#define YT921X_MIB_CTRL_PORTv(x) FIELD_PREP(YT921X_MIB_CTRL_PORTf, (x)) +#define YT921X_MIB_CTRL_ONE_PORTf BIT(1) +#define YT921X_MIB_CTRL_ALL_PORTf BIT(0) + +#define YT921X_MIBn_DATAnm(port, x) (0xc0100 + 0x100 * (port) + 4 * (x)) + +#define YT921X_EDATA_CTRLm 0xe0000 +#define YT921X_EDATA_CTRL_ADDRf GENMASK(15, 8) +#define YT921X_EDATA_CTRL_ADDRv(x) FIELD_PREP(YT921X_EDATA_CTRL_ADDRf,= (x)) +#define YT921X_EDATA_CTRL_OPf GENMASK(3, 0) +#define YT921X_EDATA_CTRL_READv 5 +#define YT921X_EDATA_DATAm 0xe0004 +#define YT921X_EDATA_DATA_DATAf GENMASK(31, 24) +#define YT921X_EDATA_DATA_STATUSf GENMASK(3, 0) +#define YT921X_EDATA_DATA_IDLEv 3 + +#define YT921X_EXTIF_MDIO_OPm 0x6a000 +#define YT921X_INTIF_MDIO_OPm 0xf0000 +#define YT921X_IF_MDIO_OP_DOf BIT(0) +#define YT921X_EXTIF_MDIO_CTRLm 0x6a004 +#define YT921X_INTIF_MDIO_CTRLm 0xf0004 +#define YT921X_IF_MDIO_CTRL_PORTf GENMASK(25, 21) +#define YT921X_IF_MDIO_CTRL_PORTv(x) FIELD_PREP(YT921X_IF_MDIO_CTRL_PO= RTf, (x)) +#define YT921X_IF_MDIO_CTRL_REGf GENMASK(20, 16) +#define YT921X_IF_MDIO_CTRL_REGv(x) FIELD_PREP(YT921X_IF_MDIO_CTRL_REG= f, (x)) +#define YT921X_IF_MDIO_CTRL_TYPEf GENMASK(11, 8) +#define YT921X_IF_MDIO_CTRL_TYPEv(x) FIELD_PREP(YT921X_IF_MDIO_CTRL_TY= PEf, (x)) +#define YT921X_IF_MDIO_CTRL_TYPE_C22v YT921X_IF_MDIO_CTRL_TYPEv(4) +#define YT921X_IF_MDIO_CTRL_OPf GENMASK(3, 2) +#define YT921X_IF_MDIO_CTRL_OPv(x) FIELD_PREP(YT921X_IF_MDIO_CTRL_OPf,= (x)) +#define YT921X_IF_MDIO_CTRL_WRITEv YT921X_IF_MDIO_CTRL_OPv(1) +#define YT921X_IF_MDIO_CTRL_READv YT921X_IF_MDIO_CTRL_OPv(2) +#define YT921X_EXTIF_MDIO_WRITE_DATAm 0x6a008 +#define YT921X_INTIF_MDIO_WRITE_DATAm 0xf0008 +#define YT921X_EXTIF_MDIO_READ_DATAm 0x6a00c +#define YT921X_INTIF_MDIO_READ_DATAm 0xf000c + +#define YT921X_EDATA_EXTMODEm 0xfb + +#define YT921X_FRAME_SIZE_MAX 0x2400 /* 9216 */ +#define YT921X_TAG_LEN 8 + +struct yt921x_mib_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +#define MIB_DESC(_size, _offset, _name) {_size, _offset, _name} + +static const struct yt921x_mib_desc yt921x_mib_descs[] =3D { + MIB_DESC(1, 0x00, "RxBroadcast"), /* rx broadcast pkts */ + MIB_DESC(1, 0x04, "RxPause"), /* rx pause pkts */ + MIB_DESC(1, 0x08, "RxMulticast"), /* rx multicast pkts, excluding pause a= nd OAM */ + MIB_DESC(1, 0x0c, "RxCrcErr"), /* rx crc err pkts, len >=3D 64B */ + + MIB_DESC(1, 0x10, "RxAlignErr"), /* rx pkts with odd number of bytes */ + MIB_DESC(1, 0x14, "RxUnderSizeErr"), /* rx crc ok pkts, len < 64B */ + MIB_DESC(1, 0x18, "RxFragErr"), /* rx crc err pkts, len < 64B */ + MIB_DESC(1, 0x1c, "RxPktSz64"), /* rx pkts, len =3D=3D 64B */ + + MIB_DESC(1, 0x20, "RxPktSz65To127"), /* rx pkts, len >=3D 65B and <=3D 12= 7B */ + MIB_DESC(1, 0x24, "RxPktSz128To255"), /* rx pkts, len >=3D 128B and <=3D = 255B */ + MIB_DESC(1, 0x28, "RxPktSz256To511"), /* rx pkts, len >=3D 256B and <=3D = 511B */ + MIB_DESC(1, 0x2c, "RxPktSz512To1023"), /* rx pkts, len >=3D 512B and <=3D= 1023B */ + + MIB_DESC(1, 0x30, "RxPktSz1024To1518"), /* rx pkts, len >=3D 1024B and <= =3D 1518B */ + MIB_DESC(1, 0x34, "RxPktSz1519ToMax"), /* rx pkts, len >=3D 1519B */ + MIB_DESC(2, 0x38, "RxGoodBytes"), /* total bytes of rx ok pkts */ + /* 0x3c */ + + MIB_DESC(2, 0x40, "RxBadBytes"), /* total bytes of rx err pkts */ + /* 0x44 */ + MIB_DESC(2, 0x48, "RxOverSzErr"), /* rx pkts, len > mac frame size */ + /* 0x4c */ + + MIB_DESC(1, 0x50, "RxDropped"), /* rx dropped pkts, excluding crc err an= d pause */ + MIB_DESC(1, 0x54, "TxBroadcast"), /* tx broadcast pkts */ + MIB_DESC(1, 0x58, "TxPause"), /* tx pause pkts */ + MIB_DESC(1, 0x5c, "TxMulticast"), /* tx multicast pkts, excluding pause a= nd OAM */ + + MIB_DESC(1, 0x60, "TxUnderSizeErr"), /* tx pkts, len < 64B */ + MIB_DESC(1, 0x64, "TxPktSz64"), /* tx pkts, len =3D=3D 64B */ + MIB_DESC(1, 0x68, "TxPktSz65To127"), /* tx pkts, len >=3D 65B and <=3D 12= 7B */ + MIB_DESC(1, 0x6c, "TxPktSz128To255"), /* tx pkts, len >=3D 128B and <=3D = 255B */ + + MIB_DESC(1, 0x70, "TxPktSz256To511"), /* tx pkts, len >=3D 256B and <=3D = 511B */ + MIB_DESC(1, 0x74, "TxPktSz512To1023"), /* tx pkts, len >=3D 512B and <=3D= 1023B */ + MIB_DESC(1, 0x78, "TxPktSz1024To1518"), /* tx pkts, len >=3D 1024B and <= =3D 1518B */ + MIB_DESC(1, 0x7c, "TxPktSz1519ToMax"), /* tx pkts, len >=3D 1519B */ + + MIB_DESC(2, 0x80, "TxGoodBytes"), /* total bytes of tx ok pkts */ + /* 0x84 */ + MIB_DESC(2, 0x88, "TxCollision"), /* collisions before 64B */ + /* 0x8c */ + + MIB_DESC(1, 0x90, "TxExcessiveCollistion"), /* aborted pkts due to too ma= ny colls */ + MIB_DESC(1, 0x94, "TxMultipleCollision"), /* multiple collision for one m= ac */ + MIB_DESC(1, 0x98, "TxSingleCollision"), /* one collision for one mac */ + MIB_DESC(1, 0x9c, "TxPkt"), /* tx ok pkts */ + + MIB_DESC(1, 0xa0, "TxDeferred"), /* delayed pkts due to defer signal */ + MIB_DESC(1, 0xa4, "TxLateCollision"), /* collisions after 64B */ + MIB_DESC(1, 0xa8, "RxOAM"), /* rx OAM pkts */ + MIB_DESC(1, 0xac, "TxOAM"), /* tx OAM pkts */ +}; + +struct yt921x_mib_raw { + u32 rx_broadcast; + u32 rx_pause; + u32 rx_multicast; + u32 rx_crc_errors; + + u32 rx_frame_errors; + u32 rx_undersize_errors; + u32 rx_fragment_errors; + u32 rx_64byte; + + u32 rx_65_127byte; + u32 rx_128_255byte; + u32 rx_256_511byte; + u32 rx_512_1023byte; + + u32 rx_1024_1518byte; + u32 rx_jumbo; + u32 rx_good_bytes_hi; + u32 rx_good_bytes_lo; + + u32 rx_bad_bytes_hi; + u32 rx_bad_bytes_lo; + u32 rx_over_errors_hi; + u32 rx_over_errors_lo; + + u32 rx_dropped; + u32 tx_broadcast; + u32 tx_pause; + u32 tx_multicast; + + u32 tx_undersize_errors; + u32 tx_64byte; + u32 tx_65_127byte; + u32 tx_128_255byte; + + u32 tx_256_511byte; + u32 tx_512_1023byte; + u32 tx_1024_1518byte; + u32 tx_jumbo; + + u32 tx_good_bytes_hi; + u32 tx_good_bytes_lo; + u32 tx_collisions_hi; + u32 tx_collisions_lo; + + u32 tx_aborted_errors; + u32 tx_multiple_collisions; + u32 tx_single_collisions; + u32 tx_good; + + u32 tx_defer; + u32 tx_window_errors; + u32 rx_oam; + u32 tx_oam; +}; + +#define u64_from_u32(hi, lo) (((u64)(hi) << 32) | (lo)) + +struct yt921x_chip_info { + const char *name; + u16 id; + u8 mode; + u8 extmode; + u16 intif_mask; + u16 extif_mask; +}; + +static const struct yt921x_chip_info yt921x_chip_infos[] =3D { + { "YT9215SC", 0x9002, 1, 0, GENMASK(4, 0), BIT(8) | BIT(9), }, + { "YT9215S", 0x9002, 2, 0, GENMASK(4, 0), BIT(8), }, + { "YT9215RB", 0x9002, 3, 0, GENMASK(4, 0), BIT(8) | BIT(9), }, + { "YT9214NB", 0x9002, 3, 2, BIT(1) | BIT(3), BIT(8) | BIT(9), }, + { "YT9213NB", 0x9002, 3, 3, BIT(1) | BIT(3), BIT(9), }, + { "YT9218N", 0x9001, 0, 0, GENMASK(7, 0), 0, }, + { "YT9218MB", 0x9001, 1, 0, GENMASK(7, 0), BIT(8) | BIT(9), }, + {} +}; + +#define YT921X_INT_PORTS_NUM 8 +#define YT921X_EXT_PORTS_NUM 2 +/* 8 internal + 2 external + 1 mcu */ +#define YT921X_PORT_CTRLS_NUM 11 + +/******** driver definitions ********/ + +#define YT921X_MDIO_SLEEP_US 10000 +#define YT921X_MDIO_TIMEOUT_US 100000 +#define YT921X_RESET_TIMEOUT_US 100000 + +enum yt921x_speed { + YT921X_SPEED_AN =3D 0, + YT921X_SPEED_10, + YT921X_SPEED_100, + YT921X_SPEED_1000, + YT921X_SPEED_2500, +}; + +struct yt921x_smi_ops { + int (*acquire)(void *context); + void (*release)(void *context); + int (*read)(void *context, u32 reg, u32 *valp); + int (*write)(void *context, u32 reg, u32 val); +}; + +struct yt921x_smi_mdio { + struct mii_bus *bus; + int addr; + unsigned char switchid; +}; + +/* TODO: SPI/I2C */ + +struct yt921x_port { + struct yt921x_mib_raw mib; +}; + +struct yt921x_priv { + struct device *dev; + const struct yt921x_chip_info *info; + u32 pon_strap_cap; + u16 tag_eth_p; + + const struct yt921x_smi_ops *smi_ops; + void *smi_ctx; + + /* mdio master */ + struct mii_bus *mbus_int; + struct mii_bus *mbus_ext; + + struct dsa_switch ds; + + struct yt921x_port ports[YT921X_PORT_CTRLS_NUM]; + + u16 eee_ports_mask; +}; + +/******** smi ********/ + +static int yt921x_smi_acquire(struct yt921x_priv *priv) +{ + if (priv->smi_ops->acquire) + return priv->smi_ops->acquire(priv->smi_ctx); + return 0; +} + +static void yt921x_smi_release(struct yt921x_priv *priv) +{ + if (priv->smi_ops->release) + priv->smi_ops->release(priv->smi_ctx); +} + +static int +__yt921x_smi_read(struct yt921x_priv *priv, u32 reg, u32 *valp) +{ + return priv->smi_ops->read(priv->smi_ctx, reg, valp); +} + +static int yt921x_smi_read(struct yt921x_priv *priv, u32 reg, u32 *valp) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_read(priv, reg, valp); + yt921x_smi_release(priv); + + return res; +} + +static int +__yt921x_smi_write(struct yt921x_priv *priv, u32 reg, u32 val) +{ + return priv->smi_ops->write(priv->smi_ctx, reg, val); +} + +static int yt921x_smi_write(struct yt921x_priv *priv, u32 reg, u32 val) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, reg, val); + yt921x_smi_release(priv); + + return res; +} + +static int +__yt921x_smi_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 = val) +{ + u32 v; + int res; + + res =3D __yt921x_smi_read(priv, reg, &v); + if (unlikely(res !=3D 0)) + return res; + v &=3D ~mask; + v |=3D val; + return __yt921x_smi_write(priv, reg, v); +} + +static int +yt921x_smi_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 va= l) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_update_bits(priv, reg, mask, val); + yt921x_smi_release(priv); + + return res; +} + +static int __yt921x_smi_set_bits(struct yt921x_priv *priv, u32 reg, u32 ma= sk) +{ + return __yt921x_smi_update_bits(priv, reg, 0, mask); +} + +static int yt921x_smi_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask) +{ + return yt921x_smi_update_bits(priv, reg, 0, mask); +} + +static int __yt921x_smi_clear_bits(struct yt921x_priv *priv, u32 reg, u32 = mask) +{ + return __yt921x_smi_update_bits(priv, reg, mask, 0); +} + +static int yt921x_smi_clear_bits(struct yt921x_priv *priv, u32 reg, u32 ma= sk) +{ + return yt921x_smi_update_bits(priv, reg, mask, 0); +} + +static int +__yt921x_smi_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool= set) +{ + return __yt921x_smi_update_bits(priv, reg, mask, !set ? 0 : mask); +} + +/******** smi via mdio ********/ + +static int yt921x_smi_mdio_acquire(void *context) +{ + struct yt921x_smi_mdio *mdio =3D context; + struct mii_bus *bus =3D mdio->bus; + + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + return 0; +} + +static void yt921x_smi_mdio_release(void *context) +{ + struct yt921x_smi_mdio *mdio =3D context; + struct mii_bus *bus =3D mdio->bus; + + mutex_unlock(&bus->mdio_lock); +} + +static int yt921x_smi_mdio_read(void *context, u32 reg, u32 *valp) +{ + struct yt921x_smi_mdio *mdio =3D context; + struct mii_bus *bus =3D mdio->bus; + int addr =3D mdio->addr; + u32 reg_addr =3D YT921X_SMI_SWITCHIDv(mdio->switchid) | + YT921X_SMI_ADDRv | YT921X_SMI_READv; + u32 reg_data =3D YT921X_SMI_SWITCHIDv(mdio->switchid) | + YT921X_SMI_DATAv | YT921X_SMI_READv; + u32 val; + int res; + + res =3D __mdiobus_write(bus, addr, reg_addr, (reg >> 16) & 0xffff); + if (unlikely(res !=3D 0)) + return res; + res =3D __mdiobus_write(bus, addr, reg_addr, (reg >> 0) & 0xffff); + if (unlikely(res !=3D 0)) + return res; + + res =3D __mdiobus_read(bus, addr, reg_data); + if (unlikely(res < 0)) + return res; + val =3D res & 0xffff; + res =3D __mdiobus_read(bus, addr, reg_data); + if (unlikely(res < 0)) + return res; + val =3D (val << 16) | (res & 0xffff); + + *valp =3D val; + return 0; +} + +static int yt921x_smi_mdio_write(void *context, u32 reg, u32 val) +{ + struct yt921x_smi_mdio *mdio =3D context; + struct mii_bus *bus =3D mdio->bus; + int addr =3D mdio->addr; + u32 reg_addr =3D YT921X_SMI_SWITCHIDv(mdio->switchid) | + YT921X_SMI_ADDRv | YT921X_SMI_WRITEv; + u32 reg_data =3D YT921X_SMI_SWITCHIDv(mdio->switchid) | + YT921X_SMI_DATAv | YT921X_SMI_WRITEv; + int res; + + res =3D __mdiobus_write(bus, addr, reg_addr, (reg >> 16) & 0xffff); + if (unlikely(res !=3D 0)) + return res; + res =3D __mdiobus_write(bus, addr, reg_addr, (reg >> 0) & 0xffff); + if (unlikely(res !=3D 0)) + return res; + + res =3D __mdiobus_write(bus, addr, reg_data, (val >> 16) & 0xffff); + if (unlikely(res !=3D 0)) + return res; + res =3D __mdiobus_write(bus, addr, reg_data, (val >> 0) & 0xffff); + if (unlikely(res !=3D 0)) + return res; + + return 0; +} + +static const struct yt921x_smi_ops yt921x_smi_ops_mdio =3D { + .acquire =3D yt921x_smi_mdio_acquire, + .release =3D yt921x_smi_mdio_release, + .read =3D yt921x_smi_mdio_read, + .write =3D yt921x_smi_mdio_write, +}; + +/* TODO: SPI/I2C */ + +/******** edata ********/ + +static int __yt921x_edata_out(struct yt921x_priv *priv, u32 *valp) +{ + u32 val; + int res; + + res =3D __yt921x_smi_read(priv, YT921X_EDATA_DATAm, &val); + if (unlikely(res !=3D 0)) + return res; + if ((val & YT921X_EDATA_DATA_STATUSf) !=3D YT921X_EDATA_DATA_IDLEv) { + int res2; + + yt921x_smi_release(priv); + res =3D read_poll_timeout(yt921x_smi_read, res, + (val & YT921X_EDATA_DATA_STATUSf) =3D=3D + YT921X_EDATA_DATA_IDLEv, + YT921X_MDIO_SLEEP_US, + YT921X_MDIO_TIMEOUT_US, + true, priv, YT921X_EDATA_DATAm, &val); + res2 =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + if (unlikely(res2 !=3D 0)) + return res2; + } + + *valp =3D val; + return 0; +} + +static int __yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp) +{ + u32 ctrl =3D YT921X_EDATA_CTRL_ADDRv(addr) | YT921X_EDATA_CTRL_READv; + u32 val; + int res; + + res =3D __yt921x_edata_out(priv, &val); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_EDATA_CTRLm, ctrl); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_edata_out(priv, &val); + if (unlikely(res !=3D 0)) + return res; + + *valp =3D FIELD_GET(YT921X_EDATA_DATA_DATAf, val); + return 0; +} + +/******** internal interface mdio ********/ + +static int __yt921x_intif_wait(struct yt921x_priv *priv) +{ + u32 val; + int res; + + res =3D __yt921x_smi_read(priv, YT921X_INTIF_MDIO_OPm, &val); + if (unlikely(res !=3D 0)) + return res; + if ((val & YT921X_IF_MDIO_OP_DOf) !=3D 0) { + int res2; + + yt921x_smi_release(priv); + res =3D read_poll_timeout(yt921x_smi_read, res, + (val & YT921X_IF_MDIO_OP_DOf) =3D=3D 0, + YT921X_MDIO_SLEEP_US, + YT921X_MDIO_TIMEOUT_US, + true, priv, YT921X_INTIF_MDIO_OPm, + &val); + res2 =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + if (unlikely(res2 !=3D 0)) + return res2; + } + + return 0; +} + +static int +__yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp) +{ + struct device *dev =3D priv->dev; + + u32 mask =3D YT921X_IF_MDIO_CTRL_PORTf | YT921X_IF_MDIO_CTRL_REGf | + YT921X_IF_MDIO_CTRL_OPf; + u32 ctrl =3D YT921X_IF_MDIO_CTRL_PORTv(port) | + YT921X_IF_MDIO_CTRL_REGv(reg) | + YT921X_IF_MDIO_CTRL_READv; + + u32 val; + int res; + + res =3D __yt921x_intif_wait(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_update_bits(priv, YT921X_INTIF_MDIO_CTRLm, mask, + ctrl); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_INTIF_MDIO_OPm, + YT921X_IF_MDIO_OP_DOf); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_intif_wait(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_read(priv, YT921X_INTIF_MDIO_READ_DATAm, &val); + if (unlikely(res !=3D 0)) + return res; + + if (unlikely((u16)val !=3D val)) + dev_err(dev, "%s: Expected u16, got 0x%08x\n", __func__, val); + *valp =3D (u16)val; + return 0; +} + +static int +yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_intif_read(priv, port, reg, valp); + yt921x_smi_release(priv); + return res; +} + +static int +__yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val) +{ + u32 mask =3D YT921X_IF_MDIO_CTRL_PORTf | YT921X_IF_MDIO_CTRL_REGf | + YT921X_IF_MDIO_CTRL_OPf; + u32 ctrl =3D YT921X_IF_MDIO_CTRL_PORTv(port) | + YT921X_IF_MDIO_CTRL_REGv(reg) | + YT921X_IF_MDIO_CTRL_WRITEv; + int res; + + res =3D __yt921x_intif_wait(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_update_bits(priv, YT921X_INTIF_MDIO_CTRLm, mask, + ctrl); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_INTIF_MDIO_WRITE_DATAm, val); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_INTIF_MDIO_OPm, + YT921X_IF_MDIO_OP_DOf); + if (unlikely(res !=3D 0)) + return res; + return __yt921x_intif_wait(priv); +} + +static int +yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_intif_write(priv, port, reg, val); + yt921x_smi_release(priv); + return res; +} + +static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg) +{ + struct yt921x_priv *priv =3D mbus->priv; + u16 val; + int res; + + res =3D yt921x_intif_read(priv, port, reg, &val); + if (unlikely(res !=3D 0)) + return res; + return val; +} + +static int +yt921x_mbus_int_write(struct mii_bus *mbus, int port, int reg, u16 data) +{ + struct yt921x_priv *priv =3D mbus->priv; + + return yt921x_intif_write(priv, port, reg, data); +} + +static int +yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp) +{ + struct device *dev =3D priv->dev; + struct mii_bus *mbus; + int res; + + mbus =3D devm_mdiobus_alloc(dev); + if (unlikely(!mbus)) + return -ENOMEM; + + mbus->name =3D "YT921x internal MDIO bus"; + snprintf(mbus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); + mbus->priv =3D priv; + mbus->read =3D yt921x_mbus_int_read; + mbus->write =3D yt921x_mbus_int_write; + mbus->parent =3D dev; + mbus->phy_mask =3D (u32)~GENMASK(YT921X_PORT_CTRLS_NUM - 1, 0); + + if (!mnp) + res =3D devm_mdiobus_register(dev, mbus); + else + res =3D devm_of_mdiobus_register(dev, mbus, mnp); + if (unlikely(res !=3D 0)) + return res; + + priv->mbus_int =3D mbus; + + return 0; +} + +/******** external interface mdio ********/ + +static int __yt921x_extif_wait(struct yt921x_priv *priv) +{ + u32 val; + int res; + + res =3D __yt921x_smi_read(priv, YT921X_EXTIF_MDIO_OPm, &val); + if (unlikely(res !=3D 0)) + return res; + if ((val & YT921X_IF_MDIO_OP_DOf) !=3D 0) { + int res2; + + yt921x_smi_release(priv); + res =3D read_poll_timeout(yt921x_smi_read, res, + (val & YT921X_IF_MDIO_OP_DOf) =3D=3D 0, + YT921X_MDIO_SLEEP_US, + YT921X_MDIO_TIMEOUT_US, + true, priv, YT921X_EXTIF_MDIO_OPm, + &val); + res2 =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + if (unlikely(res2 !=3D 0)) + return res2; + } + + return 0; +} + +static int +__yt921x_extif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp) +{ + struct device *dev =3D priv->dev; + + u32 mask =3D YT921X_IF_MDIO_CTRL_PORTf | YT921X_IF_MDIO_CTRL_REGf | + YT921X_IF_MDIO_CTRL_TYPEf | YT921X_IF_MDIO_CTRL_OPf; + u32 ctrl =3D YT921X_IF_MDIO_CTRL_PORTv(port) | + YT921X_IF_MDIO_CTRL_REGv(reg) | + YT921X_IF_MDIO_CTRL_TYPE_C22v | YT921X_IF_MDIO_CTRL_READv; + + u32 val; + int res; + + res =3D __yt921x_extif_wait(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_update_bits(priv, YT921X_EXTIF_MDIO_CTRLm, mask, + ctrl); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_EXTIF_MDIO_OPm, + YT921X_IF_MDIO_OP_DOf); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_extif_wait(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_read(priv, YT921X_EXTIF_MDIO_READ_DATAm, &val); + if (unlikely(res !=3D 0)) + return res; + + if (unlikely((u16)val !=3D val)) + dev_err(dev, "%s: Expected u16, got 0x%08x\n", __func__, val); + *valp =3D (u16)val; + return 0; +} + +static int +yt921x_extif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_extif_read(priv, port, reg, valp); + yt921x_smi_release(priv); + return res; +} + +static int +__yt921x_extif_write(struct yt921x_priv *priv, int port, int reg, u16 val) +{ + u32 mask =3D YT921X_IF_MDIO_CTRL_PORTf | YT921X_IF_MDIO_CTRL_REGf | + YT921X_IF_MDIO_CTRL_TYPEf | YT921X_IF_MDIO_CTRL_OPf; + u32 ctrl =3D YT921X_IF_MDIO_CTRL_PORTv(port) | + YT921X_IF_MDIO_CTRL_REGv(reg) | + YT921X_IF_MDIO_CTRL_TYPE_C22v | YT921X_IF_MDIO_CTRL_WRITEv; + int res; + + res =3D __yt921x_extif_wait(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_update_bits(priv, YT921X_EXTIF_MDIO_CTRLm, mask, + ctrl); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_EXTIF_MDIO_WRITE_DATAm, val); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_write(priv, YT921X_EXTIF_MDIO_OPm, + YT921X_IF_MDIO_OP_DOf); + if (unlikely(res !=3D 0)) + return res; + return __yt921x_extif_wait(priv); +} + +static int +yt921x_extif_write(struct yt921x_priv *priv, int port, int reg, u16 val) +{ + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_extif_write(priv, port, reg, val); + yt921x_smi_release(priv); + return res; +} + +static int yt921x_mbus_ext_read(struct mii_bus *mbus, int port, int reg) +{ + struct yt921x_priv *priv =3D mbus->priv; + u16 val; + int res; + + res =3D yt921x_extif_read(priv, port, reg, &val); + if (unlikely(res !=3D 0)) + return res; + return val; +} + +static int +yt921x_mbus_ext_write(struct mii_bus *mbus, int port, int reg, u16 data) +{ + struct yt921x_priv *priv =3D mbus->priv; + + return yt921x_extif_write(priv, port, reg, data); +} + +static int +yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp) +{ + struct device *dev =3D priv->dev; + struct mii_bus *mbus; + int res; + + mbus =3D devm_mdiobus_alloc(dev); + if (unlikely(!mbus)) + return -ENOMEM; + + mbus->name =3D "YT921x external MDIO bus"; + snprintf(mbus->id, MII_BUS_ID_SIZE, "%s-ext", dev_name(dev)); + mbus->priv =3D priv; + mbus->read =3D yt921x_mbus_ext_read; + mbus->write =3D yt921x_mbus_ext_write; + mbus->parent =3D dev; + mbus->phy_mask =3D (u32)~GENMASK(9, 8); + + if (!mnp) + res =3D devm_mdiobus_register(dev, mbus); + else + res =3D devm_of_mdiobus_register(dev, mbus, mnp); + if (unlikely(res !=3D 0)) + return res; + + priv->mbus_ext =3D mbus; + + return 0; +} + +/******** dsa ********/ + +static enum dsa_tag_protocol +yt921x_dsa_get_tag_protocol(struct dsa_switch *ds, int port, + enum dsa_tag_protocol m) +{ + return DSA_TAG_PROTO_YT921X; +} + +static int yt921x_dsa_cpu_port(struct dsa_switch *ds, int *portp) +{ + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + struct dsa_port *cpu_dp; + int port =3D -1; + + dsa_switch_for_each_cpu_port(cpu_dp, ds) { + if (unlikely(port >=3D 0)) { + dev_warn(dev, "More than one CPU port, %d and %d\n", + port, cpu_dp->index); + continue; + } + port =3D cpu_dp->index; + } + + if (unlikely(port < 0)) { + dev_err(dev, "No CPU port\n"); + return -EINVAL; + } + + *portp =3D port; + return 0; +} + +static int yt921x_detect(struct yt921x_priv *priv) +{ + struct device *dev =3D priv->dev; + + const struct yt921x_chip_info *info; + u32 chipid; + u32 id; + u32 mode; + u8 extmode; + + int res; + + res =3D yt921x_smi_read(priv, YT921X_CHIP_IDm, &chipid); + if (unlikely(res !=3D 0)) + return res; + + id =3D FIELD_GET(YT921X_CHIP_ID_IDf, chipid); + + for (info =3D yt921x_chip_infos; info->name; info++) + if (info->id =3D=3D id) + goto found_id; + + dev_err(dev, "Unexpected chipid 0x%x\n", chipid); + return -ENODEV; + +found_id: + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + do { + res =3D __yt921x_smi_read(priv, YT921X_CHIP_MODEm, &mode); + if (unlikely(res !=3D 0)) + break; + res =3D __yt921x_edata_read(priv, YT921X_EDATA_EXTMODEm, + &extmode); + } while (0); + yt921x_smi_release(priv); + if (unlikely(res !=3D 0)) + return res; + + for (; info->name; info++) + if (info->id =3D=3D id && info->mode =3D=3D mode && + info->extmode =3D=3D extmode) + goto found_chip; + + dev_err(dev, "Unexpected chipid 0x%x chipmode 0x%x 0x%x\n", + chipid, mode, extmode); + return -ENODEV; + +found_chip: + dev_info(dev, + "Motorcomm %s switch, chipid: 0x%x, chipmode: 0x%x 0x%x\n", + info->name, chipid, mode, extmode); + + res =3D yt921x_smi_read(priv, YT921X_PON_STRAP_CAPm, + &priv->pon_strap_cap); + if (unlikely(res !=3D 0)) + return res; + + priv->info =3D info; + return 0; +} + +static int yt921x_dsa_setup(struct dsa_switch *ds) +{ + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + struct device_node *np =3D dev->of_node; + + struct device_node *child; + int cpu_port; + u32 val; + int res; + + res =3D yt921x_dsa_cpu_port(ds, &cpu_port); + if (unlikely(res !=3D 0)) + return res; + + res =3D yt921x_detect(priv); + if (unlikely(res !=3D 0)) + return res; + + /* Reset */ + res =3D yt921x_smi_write(priv, YT921X_RESETm, YT921X_RESET_HWf); + if (unlikely(res !=3D 0)) + return res; + + /* YT921X_RESET_HWf is almost same as GPIO hard reset. So we need + * this delay. + */ + usleep_range(10000, 15000); + + res =3D read_poll_timeout(yt921x_smi_read, res, val =3D=3D 0, + YT921X_MDIO_SLEEP_US, YT921X_RESET_TIMEOUT_US, + false, priv, YT921X_RESETm, &val); + if (unlikely(res !=3D 0)) { + dev_err(dev, "Reset timeout\n"); + return res; + } + + /* Always register one mdio bus for the internal/default mdio bus. This + * maybe represented in the device tree, but is optional. + */ + child =3D of_get_child_by_name(np, "mdio"); + res =3D yt921x_mbus_int_init(priv, child); + of_node_put(child); + if (unlikely(res !=3D 0)) + return res; + ds->user_mii_bus =3D priv->mbus_int; + + /* Walk the device tree, and see if there are any other nodes which say + * they are compatible with the external mdio bus. + */ + priv->mbus_ext =3D NULL; + for_each_available_child_of_node(np, child) { + if (!of_device_is_compatible(child, + "motorcomm,yt921x-mdio-external")) + continue; + + res =3D yt921x_mbus_ext_init(priv, child); + if (unlikely(res !=3D 0)) { + of_node_put(child); + return res; + } + break; + } + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + do { + /* Enable DSA */ + val =3D YT921X_EXT_CPU_PORT_TAG_ENf | + YT921X_EXT_CPU_PORT_PORT_ENf | + YT921X_EXT_CPU_PORT_PORTv(cpu_port); + res =3D __yt921x_smi_write(priv, YT921X_EXT_CPU_PORTm, val); + if (unlikely(res !=3D 0)) + break; + + res =3D __yt921x_smi_read(priv, YT921X_CPU_TAG_TPIDm, &val); + if (unlikely(res !=3D 0)) + break; + priv->tag_eth_p =3D FIELD_GET(YT921X_CPU_TAG_TPID_TPIDf, val); + if (priv->tag_eth_p !=3D YT921X_CPU_TAG_TPID_TPIDv) { + dev_warn(dev, "Tag type 0x%x !=3D 0x%x\n", + priv->tag_eth_p, YT921X_CPU_TAG_TPID_TPIDv); + /* Should we pass tag_eth_p to yt921x_xmit()? + * Nah, just enforce a fixed value for now. + */ + res =3D -EINVAL; + break; + } + + /* Enable MIB */ + res =3D __yt921x_smi_set_bits(priv, YT921X_FUNCm, + YT921X_FUNC_MIBf); + if (unlikely(res !=3D 0)) + break; + + val =3D YT921X_MIB_CTRL_CLEANf | YT921X_MIB_CTRL_ALL_PORTf; + res =3D __yt921x_smi_write(priv, YT921X_MIB_CTRLm, val); + } while (0); + yt921x_smi_release(priv); + if (unlikely(res !=3D 0)) + return res; + + return 0; +} + +static int yt921x_dsa_get_eeprom_len(struct dsa_switch *ds) +{ + return 0x100; +} + +static int +__yt921x_dsa_get_eeprom(struct dsa_switch *ds, struct ethtool_eeprom *eepr= om, + u8 *data) +{ + struct yt921x_priv *priv =3D ds->priv; + unsigned int i; + int res =3D 0; + + for (i =3D 0; i < eeprom->len; i++) { + res =3D __yt921x_edata_read(priv, eeprom->offset + i, &data[i]); + if (unlikely(res !=3D 0)) + break; + } + + eeprom->len =3D i; + return res; +} + +static int +yt921x_dsa_get_eeprom(struct dsa_switch *ds, struct ethtool_eeprom *eeprom, + u8 *data) +{ + struct yt921x_priv *priv =3D ds->priv; + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_dsa_get_eeprom(ds, eeprom, data); + yt921x_smi_release(priv); + return res; +} + +static bool yt921x_dsa_support_eee(struct dsa_switch *ds, int port) +{ + struct yt921x_priv *priv =3D ds->priv; + + return (priv->pon_strap_cap & YT921X_PON_STRAP_EEEf) !=3D 0; +} + +static int +__yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_keee *e) +{ + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + bool enable =3D e->eee_enabled; + u16 new_mask; + int res; + + dev_dbg(dev, "%s: port %d, enable %d\n", __func__, port, enable); + + /* Enable / disable global EEE */ + new_mask =3D priv->eee_ports_mask; + new_mask &=3D ~BIT(port); + new_mask |=3D !enable ? 0 : BIT(port); + + if (!!new_mask !=3D !!priv->eee_ports_mask) { + dev_dbg(dev, "%s: toggle %d\n", __func__, !!new_mask); + + res =3D __yt921x_smi_toggle_bits(priv, YT921X_PON_STRAP_FUNCm, + YT921X_PON_STRAP_EEEf, + !!new_mask); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_toggle_bits(priv, YT921X_PON_STRAP_VALm, + YT921X_PON_STRAP_EEEf, + !!new_mask); + if (unlikely(res !=3D 0)) + return res; + } + + priv->eee_ports_mask =3D new_mask; + + /* Enable / disable port EEE */ + res =3D __yt921x_smi_toggle_bits(priv, YT921X_MAC_EEE_CTRLm, + YT921X_MAC_EEE_CTRL_ENnf(port), enable); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_toggle_bits(priv, YT921X_MACn_EEE_VALm(port), + YT921X_MAC_EEE_VAL_DATAf, enable); + if (unlikely(res !=3D 0)) + return res; + + return 0; +} + +static int +yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_kee= e *e) +{ + struct yt921x_priv *priv =3D ds->priv; + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_dsa_set_mac_eee(ds, port, e); + yt921x_smi_release(priv); + + return res; +} + +static int +yt921x_dsa_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) +{ + /* Could be nop at all, since the default frame size is always set to + * maximum after reset, but let's implement it anyway + */ + + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + int frame_size; + + frame_size =3D new_mtu + ETH_HLEN + ETH_FCS_LEN; + if (dsa_is_cpu_port(ds, port)) + frame_size +=3D YT921X_TAG_LEN; + + dev_dbg(dev, "%s: port %d, mtu %d, frame size %d\n", __func__, + port, new_mtu, frame_size); + + return yt921x_smi_update_bits(priv, YT921X_MACn_JUMBOm(port), + YT921X_MAC_FRAME_SIZEf, + YT921X_MAC_FRAME_SIZEv(frame_size)); +} + +static int yt921x_dsa_port_max_mtu(struct dsa_switch *ds, int port) +{ + /* Don't want to brother dsa_is_cpu_port() here, so use a fixed value */ + return YT921X_FRAME_SIZE_MAX - ETH_HLEN - ETH_FCS_LEN - YT921X_TAG_LEN; +} + +static void +yt921x_dsa_get_strings(struct dsa_switch *ds, int port, u32 stringset, + uint8_t *data) +{ + if (stringset !=3D ETH_SS_STATS) + return; + + for (size_t i =3D 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) + ethtool_puts(&data, yt921x_mib_descs[i].name); +} + +static int yt921x_mib_read(struct yt921x_priv *priv, int port, void *data) +{ + struct device *dev =3D priv->dev; + u32 *buf =3D data; + int res; + + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + return res; + for (size_t i =3D 0; i < sizeof(struct yt921x_mib_raw) / sizeof(u32); + i++) { + res =3D __yt921x_smi_read(priv, YT921X_MIBn_DATAnm(port, i), + &buf[i]); + if (unlikely(res !=3D 0)) { + dev_err(dev, "Cannot read MIB %d at 0x%zx: %i\n", + port, sizeof(u32) * i, res); + break; + } + } + yt921x_smi_release(priv); + return res; +} + +static int yt921x_read_mib(struct yt921x_priv *priv, int port) +{ + struct device *dev =3D priv->dev; + int res; + + res =3D yt921x_mib_read(priv, port, &priv->ports[port].mib); + if (unlikely(res !=3D 0)) + dev_err(dev, "Cannot read MIB %d: %i\n", port, res); + + return res; +} + +static void +yt921x_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *da= ta) +{ + struct yt921x_priv *priv =3D ds->priv; + unsigned char *buf =3D (unsigned char *)&priv->ports[port].mib; + + yt921x_read_mib(priv, port); + + for (size_t i =3D 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) { + const struct yt921x_mib_desc *desc =3D &yt921x_mib_descs[i]; + u32 *valp =3D (u32 *)(buf + desc->offset); + + data[i] =3D valp[0]; + if (desc->size > 1) { + data[i] <<=3D 32; + data[i] |=3D valp[1]; + } + } +} + +static int yt921x_dsa_get_sset_count(struct dsa_switch *ds, int port, int = sset) +{ + if (sset !=3D ETH_SS_STATS) + return 0; + + return ARRAY_SIZE(yt921x_mib_descs); +} + +static void +yt921x_dsa_get_stats64(struct dsa_switch *ds, int port, + struct rtnl_link_stats64 *s) +{ + struct yt921x_priv *priv =3D ds->priv; + struct yt921x_mib_raw *mib =3D &priv->ports[port].mib; + + yt921x_read_mib(priv, port); + + s->rx_length_errors =3D (u64)mib->rx_undersize_errors + + mib->rx_fragment_errors; + s->rx_over_errors =3D u64_from_u32(mib->rx_over_errors_hi, + mib->rx_over_errors_lo); + s->rx_crc_errors =3D mib->rx_crc_errors; + s->rx_frame_errors =3D mib->rx_frame_errors; + /* s->rx_fifo_errors */ + /* s->rx_missed_errors */ + + s->tx_aborted_errors =3D mib->tx_aborted_errors; + /* s->tx_carrier_errors */ + s->tx_fifo_errors =3D mib->tx_undersize_errors; + /* s->tx_heartbeat_errors */ + s->tx_window_errors =3D mib->tx_window_errors; + + s->rx_packets =3D (u64)mib->rx_64byte + mib->rx_65_127byte + + mib->rx_128_255byte + mib->rx_256_511byte + + mib->rx_512_1023byte + mib->rx_1024_1518byte + + mib->rx_jumbo; + s->tx_packets =3D (u64)mib->tx_64byte + mib->tx_65_127byte + + mib->tx_128_255byte + mib->tx_256_511byte + + mib->tx_512_1023byte + mib->tx_1024_1518byte + + mib->tx_jumbo; + s->rx_bytes =3D u64_from_u32(mib->rx_good_bytes_hi, mib->rx_good_bytes_lo= ) - + ETH_FCS_LEN * s->rx_packets; + s->tx_bytes =3D u64_from_u32(mib->tx_good_bytes_hi, mib->tx_good_bytes_lo= ) - + ETH_FCS_LEN * s->tx_packets; + s->rx_errors =3D (u64)mib->rx_crc_errors + s->rx_length_errors + + s->rx_over_errors; + s->tx_errors =3D (u64)mib->tx_undersize_errors + mib->tx_aborted_errors + + mib->tx_window_errors; + s->rx_dropped =3D mib->rx_dropped; + /* s->tx_dropped */ + s->multicast =3D mib->rx_multicast; + s->collisions =3D u64_from_u32(mib->tx_collisions_hi, + mib->tx_collisions_lo); +} + +static void +yt921x_dsa_get_pause_stats(struct dsa_switch *ds, int port, + struct ethtool_pause_stats *pause_stats) +{ + struct yt921x_priv *priv =3D ds->priv; + struct yt921x_mib_raw *mib =3D &priv->ports[port].mib; + + yt921x_read_mib(priv, port); + + pause_stats->tx_pause_frames =3D mib->tx_pause; + pause_stats->rx_pause_frames =3D mib->rx_pause; +} + +static void +yt921x_dsa_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + struct yt921x_priv *priv =3D ds->priv; + const struct yt921x_chip_info *info =3D priv->info; + + if ((BIT(port) & info->intif_mask) !=3D 0) { + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + config->mac_capabilities =3D 0; + } else if ((BIT(port) & info->extif_mask) !=3D 0) { + /* SGMII */ + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_100BASEX, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + config->supported_interfaces); + /* XMII */ + __set_bit(PHY_INTERFACE_MODE_MII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_REVMII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_RMII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_REVRMII, + config->supported_interfaces); + phy_interface_set_rgmii(config->supported_interfaces); + config->mac_capabilities =3D MAC_2500FD; + } else { + return; + } + + config->mac_capabilities |=3D MAC_ASYM_PAUSE | MAC_SYM_PAUSE | + MAC_10 | MAC_100 | MAC_1000; +} + +static const struct dsa_switch_ops yt921x_dsa_switch_ops =3D { + .get_tag_protocol =3D yt921x_dsa_get_tag_protocol, + .setup =3D yt921x_dsa_setup, + .get_eeprom_len =3D yt921x_dsa_get_eeprom_len, + .get_eeprom =3D yt921x_dsa_get_eeprom, + .support_eee =3D yt921x_dsa_support_eee, + .set_mac_eee =3D yt921x_dsa_set_mac_eee, + .port_change_mtu =3D yt921x_dsa_port_change_mtu, + .port_max_mtu =3D yt921x_dsa_port_max_mtu, + .get_strings =3D yt921x_dsa_get_strings, + .get_ethtool_stats =3D yt921x_dsa_get_ethtool_stats, + .get_sset_count =3D yt921x_dsa_get_sset_count, + .get_stats64 =3D yt921x_dsa_get_stats64, + .get_pause_stats =3D yt921x_dsa_get_pause_stats, + .phylink_get_caps =3D yt921x_dsa_phylink_get_caps, +}; + +static int +__yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode, + const struct phylink_link_state *state) +{ + const struct yt921x_chip_info *info =3D priv->info; + struct device *dev =3D priv->dev; + enum yt921x_speed speed; + bool duplex_full; + u32 val; + u32 mask; + int res; + + speed =3D YT921X_SPEED_AN; + val =3D YT921X_PORT_LINKf; + if (mode =3D=3D MLO_AN_FIXED) + switch (state->speed) { + case 10: + speed =3D YT921X_SPEED_10; + val =3D YT921X_PORT_SPEED_10v; + break; + case 100: + speed =3D YT921X_SPEED_100; + val =3D YT921X_PORT_SPEED_100v; + break; + case 1000: + speed =3D YT921X_SPEED_1000; + val =3D YT921X_PORT_SPEED_1000v; + break; + case 2500: + speed =3D YT921X_SPEED_2500; + val =3D YT921X_PORT_SPEED_2500v; + break; + default: + if (unlikely(state->speed >=3D 0)) + dev_err(dev, "Unsupported speed %d\n", + state->speed); + break; + } + + if (speed !=3D YT921X_SPEED_AN) { + switch (state->duplex) { + case DUPLEX_FULL: + duplex_full =3D true; + val |=3D YT921X_PORT_DUPLEX_FULLf; + break; + case DUPLEX_HALF: + duplex_full =3D false; + break; + default: + dev_err(dev, + "Unspecified duplex while fixed speed %d\n", + state->speed); + duplex_full =3D true; + break; + } + } + + if ((state->pause & MLO_PAUSE_RX) !=3D 0) + val |=3D YT921X_PORT_RX_FLOW_CONTROLf; + if ((state->pause & MLO_PAUSE_TX) !=3D 0) + val |=3D YT921X_PORT_TX_FLOW_CONTROLf; + if ((state->pause & MLO_PAUSE_AN) !=3D 0) + val |=3D YT921X_PORT_CTRL_FLOW_CONTROL_ANf; + + res =3D __yt921x_smi_write(priv, YT921X_PORTn_CTRLm(port), val); + if (unlikely(res !=3D 0)) + return res; + + switch (state->interface) { + /* SGMII */ + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_100BASEX: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + if (unlikely((BIT(port) & info->extif_mask) =3D=3D 0)) { + dev_err(dev, "Wrong mode %d on port %d\n", + state->interface, port); + return -EINVAL; + } + + res =3D __yt921x_smi_set_bits(priv, YT921X_EXTIF_SERDESm, + YT921X_EXTIF_SERDES_EXTIFnf(port)); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_clear_bits(priv, YT921X_EXTIF_SELm, + YT921X_EXTIF_SEL_EXTIFn_XMIIf(port)); + if (unlikely(res !=3D 0)) + return res; + + switch (state->interface) { + case PHY_INTERFACE_MODE_SGMII: + val =3D YT921X_SGMII_MODE_SGMII_PHYv; + break; + case PHY_INTERFACE_MODE_100BASEX: + val =3D YT921X_SGMII_MODE_100BASEXv; + break; + case PHY_INTERFACE_MODE_1000BASEX: + val =3D YT921X_SGMII_MODE_1000BASEXv; + break; + case PHY_INTERFACE_MODE_2500BASEX: + val =3D YT921X_SGMII_MODE_2500BASEXv; + break; + default: + /* unreachable; suppress compiler warning */ + break; + } + mask =3D YT921X_SGMII_MODEf; + + if (speed !=3D YT921X_SPEED_AN) { + val |=3D YT921X_SGMII_LINKf; + + switch (speed) { + case YT921X_SPEED_10: + val |=3D YT921X_SGMII_SPEED_10v; + break; + case YT921X_SPEED_100: + val |=3D YT921X_SGMII_SPEED_100v; + break; + case YT921X_SPEED_1000: + val |=3D YT921X_SGMII_SPEED_1000v; + break; + case YT921X_SPEED_2500: + val |=3D YT921X_SGMII_SPEED_2500v; + break; + default: + /* unreachable; suppress compiler warning */ + break; + } + + if (duplex_full) + val |=3D YT921X_SGMII_DUPLEX_FULLf; + + mask |=3D YT921X_SGMII_LINKf; + mask |=3D YT921X_SGMII_SPEEDf; + mask |=3D YT921X_PORT_DUPLEX_FULLf; + } + + res =3D __yt921x_smi_update_bits(priv, YT921X_SGMIInm(port), + mask, val); + if (unlikely(res !=3D 0)) + return res; + + break; + /* XMII */ + case PHY_INTERFACE_MODE_MII: + case PHY_INTERFACE_MODE_REVMII: + case PHY_INTERFACE_MODE_RMII: + case PHY_INTERFACE_MODE_REVRMII: + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + if (unlikely((BIT(port) & info->extif_mask) =3D=3D 0)) { + dev_err(dev, "Wrong mode %d on port %d\n", + state->interface, port); + return -EINVAL; + } + + res =3D __yt921x_smi_clear_bits(priv, YT921X_EXTIF_SERDESm, + YT921X_EXTIF_SERDES_EXTIFnf(port)); + if (unlikely(res !=3D 0)) + return res; + res =3D __yt921x_smi_set_bits(priv, YT921X_EXTIF_SELm, + YT921X_EXTIF_SEL_EXTIFn_XMIIf(port)); + if (unlikely(res !=3D 0)) + return res; + + val =3D YT921X_EXTIF_MODE_PORT_ENf; + switch (state->interface) { + case PHY_INTERFACE_MODE_MII: + val |=3D YT921X_EXTIF_MODE_MODE_MIIv; + break; + case PHY_INTERFACE_MODE_REVMII: + val |=3D YT921X_EXTIF_MODE_MODE_REVMIIv; + break; + case PHY_INTERFACE_MODE_RMII: + val |=3D YT921X_EXTIF_MODE_MODE_RMIIv; + break; + case PHY_INTERFACE_MODE_REVRMII: + val |=3D YT921X_EXTIF_MODE_MODE_REVRMIIv; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + val |=3D YT921X_EXTIF_MODE_MODE_RGMIIv; + break; + default: + /* unreachable; suppress compiler warning */ + break; + } + /* TODO: RGMII delay */ + res =3D __yt921x_smi_update_bits(priv, YT921X_EXTIFn_MODEm(port), + YT921X_EXTIF_MODE_PORT_ENf | + YT921X_EXTIF_MODE_MODEf, + val); + if (unlikely(res !=3D 0)) + return res; + + if (speed !=3D YT921X_SPEED_AN) { + val =3D YT921X_MDIO_POLLING_LINKf; + + switch (speed) { + case YT921X_SPEED_10: + val |=3D YT921X_MDIO_POLLING_SPEED_10v; + break; + case YT921X_SPEED_100: + val |=3D YT921X_MDIO_POLLING_SPEED_100v; + break; + case YT921X_SPEED_1000: + val |=3D YT921X_MDIO_POLLING_SPEED_1000v; + break; + case YT921X_SPEED_2500: + val |=3D YT921X_MDIO_POLLING_SPEED_2500v; + break; + default: + /* unreachable; suppress compiler warning */ + break; + } + if (duplex_full) + val |=3D YT921X_MDIO_POLLING_DUPLEX_FULLf; + + res =3D __yt921x_smi_write(priv, + YT921X_MDIO_POLLINGnm(port), + val); + if (unlikely(res !=3D 0)) + return res; + } + + break; + case PHY_INTERFACE_MODE_INTERNAL: + break; + default: + dev_err(dev, "Wrong mode %d on port %d\n", + state->interface, port); + break; + } + + return 0; +} + +static void +yt921x_phylink_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) +{ + struct dsa_port *dp =3D dsa_phylink_to_port(config); + int port =3D dp->index; + struct dsa_switch *ds =3D dp->ds; + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + int res; + + dev_dbg(dev, + "%s: port %d, mode %u, interface %d, speed %d, duplex %d, " + "pause %d, advertising %lx\n", __func__, + port, mode, state->interface, state->speed, state->duplex, + state->pause, *state->advertising); + + do { + res =3D yt921x_smi_acquire(priv); + if (unlikely(res !=3D 0)) + break; + res =3D __yt921x_port_config(priv, port, mode, state); + yt921x_smi_release(priv); + } while (0); + + if (unlikely(res !=3D 0)) + dev_err(dev, "Cannot configure port %d: %i\n", port, res); +} + +static void +yt921x_phylink_mac_link_down(struct phylink_config *config, unsigned int m= ode, + phy_interface_t interface) +{ + struct dsa_port *dp =3D dsa_phylink_to_port(config); + int port =3D dp->index; + struct dsa_switch *ds =3D dp->ds; + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + u32 val; + int res; + + dev_dbg(dev, "%s: port %d\n", __func__, port); + + val =3D YT921X_PORT_RX_MAC_ENf | YT921X_PORT_TX_MAC_ENf; + res =3D yt921x_smi_clear_bits(priv, YT921X_PORTn_CTRLm(port), val); + if (unlikely(res !=3D 0)) + dev_err(dev, "Cannot disable port %d: %i\n", port, res); +} + +static void +yt921x_phylink_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 =3D dsa_phylink_to_port(config); + int port =3D dp->index; + struct dsa_switch *ds =3D dp->ds; + struct yt921x_priv *priv =3D ds->priv; + struct device *dev =3D priv->dev; + u32 val; + int res; + + dev_dbg(dev, + "%s: port %d, mode %u, interface %d, speed %d, duplex %d, " + "tx_pause %d, rx_pause %d\n", __func__, port, mode, interface, + speed, duplex, tx_pause, rx_pause); + + val =3D YT921X_PORT_RX_MAC_ENf | YT921X_PORT_TX_MAC_ENf; + res =3D yt921x_smi_set_bits(priv, YT921X_PORTn_CTRLm(port), val); + if (unlikely(res !=3D 0)) + dev_err(dev, "Cannot enable port %d: %i\n", port, res); +} + +static const struct phylink_mac_ops yt921x_phylink_mac_ops =3D { + .mac_config =3D yt921x_phylink_mac_config, + .mac_link_down =3D yt921x_phylink_mac_link_down, + .mac_link_up =3D yt921x_phylink_mac_link_up, +}; + +/******** device ********/ + +static int yt921x_probe(struct mdio_device *mdiodev) +{ + struct device *dev =3D &mdiodev->dev; + struct device_node *np =3D dev->of_node; + struct yt921x_priv *priv; + struct yt921x_smi_mdio *mdio; + struct dsa_switch *ds; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (unlikely(!priv)) + return -ENOMEM; + + mdio =3D devm_kzalloc(dev, sizeof(*mdio), GFP_KERNEL); + if (unlikely(!mdio)) + return -ENOMEM; + + mdio->bus =3D mdiodev->bus; + mdio->addr =3D mdiodev->addr; + if (of_property_read_u8(np, "switchid", &mdio->switchid) !=3D 0) + mdio->switchid =3D 0; + + priv->dev =3D dev; + priv->smi_ops =3D &yt921x_smi_ops_mdio; + priv->smi_ctx =3D mdio; + + ds =3D &priv->ds; + ds->dev =3D dev; + ds->num_ports =3D YT921X_PORT_CTRLS_NUM; + ds->priv =3D priv; + ds->dev =3D dev; + ds->ops =3D &yt921x_dsa_switch_ops; + ds->phylink_mac_ops =3D &yt921x_phylink_mac_ops; + + dev_set_drvdata(dev, ds); + + return dsa_register_switch(ds); +} + +static void yt921x_remove(struct mdio_device *mdiodev) +{ + struct dsa_switch *ds =3D dev_get_drvdata(&mdiodev->dev); + + if (unlikely(!ds)) + return; + + dsa_unregister_switch(ds); +} + +static void yt921x_shutdown(struct mdio_device *mdiodev) +{ + struct dsa_switch *ds =3D dev_get_drvdata(&mdiodev->dev); + + if (unlikely(!ds)) + return; + + dsa_switch_shutdown(ds); +} + +static const struct of_device_id yt921x_of_match[] =3D { + { .compatible =3D "motorcomm,yt9215" }, + {} +}; +MODULE_DEVICE_TABLE(of, yt921x_of_match); + +static struct mdio_driver yt921x_driver =3D { + .probe =3D yt921x_probe, + .remove =3D yt921x_remove, + .shutdown =3D yt921x_shutdown, + .mdiodrv.driver =3D { + .name =3D "yt921x", + .of_match_table =3D yt921x_of_match, + }, +}; + +mdio_module_driver(yt921x_driver); + +MODULE_AUTHOR("David Yang "); +MODULE_DESCRIPTION("Driver for Motorcomm YT921x Switch"); +MODULE_LICENSE("GPL"); --=20 2.47.2