1 From 4765a9722e09765866e131ec31f7b9cf4c1f4854 Mon Sep 17 00:00:00 2001
2 From: Daniel Golle <daniel@makrotopia.org>
3 Date: Sun, 19 Mar 2023 12:57:50 +0000
4 Subject: [PATCH] net: pcs: add driver for MediaTek SGMII PCS
6 The SGMII core found in several MediaTek SoCs is identical to what can
7 also be found in MediaTek's MT7531 Ethernet switch IC.
8 As this has not always been clear, both drivers developed different
9 implementations to deal with the PCS.
10 Recently Alexander Couzens pointed out this fact which lead to the
11 development of this shared driver.
13 Add a dedicated driver, mostly by copying the code now found in the
14 Ethernet driver. The now redundant code will be removed by a follow-up
17 Suggested-by: Alexander Couzens <lynxis@fe80.eu>
18 Suggested-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
19 Signed-off-by: Daniel Golle <daniel@makrotopia.org>
20 Tested-by: Frank Wunderlich <frank-w@public-files.de>
21 Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
22 Signed-off-by: Jakub Kicinski <kuba@kernel.org>
25 drivers/net/pcs/Kconfig | 7 +
26 drivers/net/pcs/Makefile | 1 +
27 drivers/net/pcs/pcs-mtk-lynxi.c | 305 ++++++++++++++++++++++++++++++
28 include/linux/pcs/pcs-mtk-lynxi.h | 13 ++
29 5 files changed, 334 insertions(+)
30 create mode 100644 drivers/net/pcs/pcs-mtk-lynxi.c
31 create mode 100644 include/linux/pcs/pcs-mtk-lynxi.h
35 @@ -12926,6 +12926,14 @@ L: netdev@vger.kernel.org
37 F: drivers/net/ethernet/mediatek/
39 +MEDIATEK ETHERNET PCS DRIVER
40 +M: Alexander Couzens <lynxis@fe80.eu>
41 +M: Daniel Golle <daniel@makrotopia.org>
42 +L: netdev@vger.kernel.org
44 +F: drivers/net/pcs/pcs-mtk-lynxi.c
45 +F: include/linux/pcs/pcs-mtk-lynxi.h
47 MEDIATEK I2C CONTROLLER DRIVER
48 M: Qii Wang <qii.wang@mediatek.com>
49 L: linux-i2c@vger.kernel.org
50 --- a/drivers/net/pcs/Kconfig
51 +++ b/drivers/net/pcs/Kconfig
52 @@ -32,4 +32,11 @@ config PCS_ALTERA_TSE
53 This module provides helper functions for the Altera Triple Speed
54 Ethernet SGMII PCS, that can be found on the Intel Socfpga family.
60 + This module provides helpers to phylink for managing the LynxI PCS
61 + which is part of MediaTek's SoC and Ethernet switch ICs.
64 --- a/drivers/net/pcs/Makefile
65 +++ b/drivers/net/pcs/Makefile
66 @@ -7,3 +7,4 @@ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
67 obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
68 obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
69 obj-$(CONFIG_PCS_ALTERA_TSE) += pcs-altera-tse.o
70 +obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
72 +++ b/drivers/net/pcs/pcs-mtk-lynxi.c
74 +// SPDX-License-Identifier: GPL-2.0
75 +// Copyright (c) 2018-2019 MediaTek Inc.
76 +/* A library for MediaTek SGMII circuit
78 + * Author: Sean Wang <sean.wang@mediatek.com>
79 + * Author: Alexander Couzens <lynxis@fe80.eu>
80 + * Author: Daniel Golle <daniel@makrotopia.org>
84 +#include <linux/mdio.h>
85 +#include <linux/of.h>
86 +#include <linux/pcs/pcs-mtk-lynxi.h>
87 +#include <linux/phylink.h>
88 +#include <linux/regmap.h>
90 +/* SGMII subsystem config registers */
91 +/* BMCR (low 16) BMSR (high 16) */
92 +#define SGMSYS_PCS_CONTROL_1 0x0
93 +#define SGMII_BMCR GENMASK(15, 0)
94 +#define SGMII_BMSR GENMASK(31, 16)
96 +#define SGMSYS_PCS_DEVICE_ID 0x4
97 +#define SGMII_LYNXI_DEV_ID 0x4d544950
99 +#define SGMSYS_PCS_ADVERTISE 0x8
100 +#define SGMII_ADVERTISE GENMASK(15, 0)
101 +#define SGMII_LPA GENMASK(31, 16)
103 +#define SGMSYS_PCS_SCRATCH 0x14
104 +#define SGMII_DEV_VERSION GENMASK(31, 16)
106 +/* Register to programmable link timer, the unit in 2 * 8ns */
107 +#define SGMSYS_PCS_LINK_TIMER 0x18
108 +#define SGMII_LINK_TIMER_MASK GENMASK(19, 0)
109 +#define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \
112 +/* Register to control remote fault */
113 +#define SGMSYS_SGMII_MODE 0x20
114 +#define SGMII_IF_MODE_SGMII BIT(0)
115 +#define SGMII_SPEED_DUPLEX_AN BIT(1)
116 +#define SGMII_SPEED_MASK GENMASK(3, 2)
117 +#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
118 +#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1)
119 +#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2)
120 +#define SGMII_DUPLEX_HALF BIT(4)
121 +#define SGMII_REMOTE_FAULT_DIS BIT(8)
123 +/* Register to reset SGMII design */
124 +#define SGMSYS_RESERVED_0 0x34
125 +#define SGMII_SW_RESET BIT(0)
127 +/* Register to set SGMII speed, ANA RG_ Control Signals III */
128 +#define SGMII_PHY_SPEED_MASK GENMASK(3, 2)
129 +#define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0)
130 +#define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1)
132 +/* Register to power up QPHY */
133 +#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8
134 +#define SGMII_PHYA_PWD BIT(4)
136 +/* Register to QPHY wrapper control */
137 +#define SGMSYS_QPHY_WRAP_CTRL 0xec
138 +#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
139 +#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
141 +/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
143 + * @regmap: The register map pointing at the range used to setup
145 + * @dev: Pointer to device owning the PCS
146 + * @ana_rgc3: The offset of register ANA_RGC3 relative to regmap
147 + * @interface: Currently configured interface mode
148 + * @pcs: Phylink PCS structure
149 + * @flags: Flags indicating hardware properties
151 +struct mtk_pcs_lynxi {
152 + struct regmap *regmap;
154 + phy_interface_t interface;
155 + struct phylink_pcs pcs;
159 +static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
161 + return container_of(pcs, struct mtk_pcs_lynxi, pcs);
164 +static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
165 + struct phylink_link_state *state)
167 + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
168 + unsigned int bm, adv;
170 + /* Read the BMSR and LPA */
171 + regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
172 + regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
174 + phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm),
175 + FIELD_GET(SGMII_LPA, adv));
178 +static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int mode,
179 + phy_interface_t interface,
180 + const unsigned long *advertising,
181 + bool permit_pause_to_mac)
183 + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
184 + bool mode_changed = false, changed, use_an;
185 + unsigned int rgc3, sgm_mode, bmcr;
186 + int advertise, link_timer;
188 + advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
193 + /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
194 + * we assume that fixes it's speed at bitrate = line rate (in
195 + * other words, 1000Mbps or 2500Mbps).
197 + if (interface == PHY_INTERFACE_MODE_SGMII) {
198 + sgm_mode = SGMII_IF_MODE_SGMII;
199 + if (phylink_autoneg_inband(mode)) {
200 + sgm_mode |= SGMII_REMOTE_FAULT_DIS |
201 + SGMII_SPEED_DUPLEX_AN;
206 + } else if (phylink_autoneg_inband(mode)) {
207 + /* 1000base-X or 2500base-X autoneg */
208 + sgm_mode = SGMII_REMOTE_FAULT_DIS;
209 + use_an = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
212 + /* 1000base-X or 2500base-X without autoneg */
218 + bmcr = BMCR_ANENABLE;
222 + if (mpcs->interface != interface) {
223 + link_timer = phylink_get_link_timer_ns(interface);
224 + if (link_timer < 0)
227 + /* PHYA power down */
228 + regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
231 + /* Reset SGMII PCS state */
232 + regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
235 + if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
236 + regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
237 + SGMII_PN_SWAP_MASK,
238 + SGMII_PN_SWAP_TX_RX);
240 + if (interface == PHY_INTERFACE_MODE_2500BASEX)
241 + rgc3 = SGMII_PHY_SPEED_3_125G;
243 + rgc3 = SGMII_PHY_SPEED_1_25G;
245 + /* Configure the underlying interface speed */
246 + regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
247 + SGMII_PHY_SPEED_MASK, rgc3);
249 + /* Setup the link timer */
250 + regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
251 + SGMII_LINK_TIMER_VAL(link_timer));
253 + mpcs->interface = interface;
254 + mode_changed = true;
257 + /* Update the advertisement, noting whether it has changed */
258 + regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
259 + SGMII_ADVERTISE, advertise, &changed);
261 + /* Update the sgmsys mode register */
262 + regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
263 + SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
264 + SGMII_IF_MODE_SGMII, sgm_mode);
266 + /* Update the BMCR */
267 + regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
268 + BMCR_ANENABLE, bmcr);
270 + /* Release PHYA power down state
271 + * Only removing bit SGMII_PHYA_PWD isn't enough.
272 + * There are cases when the SGMII_PHYA_PWD register contains 0x9 which
273 + * prevents SGMII from working. The SGMII still shows link but no traffic
274 + * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
275 + * taken from a good working state of the SGMII interface.
276 + * Unknown how much the QPHY needs but it is racy without a sleep.
277 + * Tested on mt7622 & mt7986.
279 + usleep_range(50, 100);
280 + regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
282 + return changed || mode_changed;
285 +static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs)
287 + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
289 + regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
292 +static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, unsigned int mode,
293 + phy_interface_t interface, int speed,
296 + struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
297 + unsigned int sgm_mode;
299 + if (!phylink_autoneg_inband(mode)) {
300 + /* Force the speed and duplex setting */
301 + if (speed == SPEED_10)
302 + sgm_mode = SGMII_SPEED_10;
303 + else if (speed == SPEED_100)
304 + sgm_mode = SGMII_SPEED_100;
306 + sgm_mode = SGMII_SPEED_1000;
308 + if (duplex != DUPLEX_FULL)
309 + sgm_mode |= SGMII_DUPLEX_HALF;
311 + regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
312 + SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
317 +static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
318 + .pcs_get_state = mtk_pcs_lynxi_get_state,
319 + .pcs_config = mtk_pcs_lynxi_config,
320 + .pcs_an_restart = mtk_pcs_lynxi_restart_an,
321 + .pcs_link_up = mtk_pcs_lynxi_link_up,
324 +struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
325 + struct regmap *regmap, u32 ana_rgc3,
328 + struct mtk_pcs_lynxi *mpcs;
332 + ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
336 + if (id != SGMII_LYNXI_DEV_ID) {
337 + dev_err(dev, "unknown PCS device id %08x\n", id);
341 + ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
345 + ver = FIELD_GET(SGMII_DEV_VERSION, ver);
347 + dev_err(dev, "unknown PCS device version %04x\n", ver);
351 + dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
354 + mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
358 + mpcs->ana_rgc3 = ana_rgc3;
359 + mpcs->regmap = regmap;
360 + mpcs->flags = flags;
361 + mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
362 + mpcs->pcs.poll = true;
363 + mpcs->interface = PHY_INTERFACE_MODE_NA;
367 +EXPORT_SYMBOL(mtk_pcs_lynxi_create);
369 +void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
374 + kfree(pcs_to_mtk_pcs_lynxi(pcs));
376 +EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
378 +MODULE_LICENSE("GPL");
380 +++ b/include/linux/pcs/pcs-mtk-lynxi.h
382 +/* SPDX-License-Identifier: GPL-2.0 */
383 +#ifndef __LINUX_PCS_MTK_LYNXI_H
384 +#define __LINUX_PCS_MTK_LYNXI_H
386 +#include <linux/phylink.h>
387 +#include <linux/regmap.h>
389 +#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
390 +struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
391 + struct regmap *regmap,
392 + u32 ana_rgc3, u32 flags);
393 +void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);