Skip to content

Commit e8c35bf

Browse files
triha2workPaolo Abeni
authored andcommitted
net: dsa: microchip: Add SGMII port support to KSZ9477 switch
The KSZ9477 switch driver uses the XPCS driver to operate its SGMII port. However there are some hardware bugs in the KSZ9477 SGMII module so workarounds are needed. There was a proposal to update the XPCS driver to accommodate KSZ9477, but the new code is not generic enough to be used by other vendors. It is better to do all these workarounds inside the KSZ9477 driver instead of modifying the XPCS driver. There are 3 hardware issues. The first is the MII_ADVERTISE register needs to be write once after reset for the correct code word to be sent. The XPCS driver disables auto-negotiation first before configuring the SGMII/1000BASE-X mode and then enables it back. The KSZ9477 driver then writes the MII_ADVERTISE register before enabling auto-negotiation. In 1000BASE-X mode the MII_ADVERTISE register will be set, so KSZ9477 driver does not need to write it. The second issue is the MII_BMCR register needs to set the exact speed and duplex mode when running in SGMII mode. During link polling the KSZ9477 will check the speed and duplex mode are different from previous ones and update the MII_BMCR register accordingly. The last issue is 1000BASE-X mode does not work with auto-negotiation on. The cause is the local port hardware does not know the link is up and so network traffic is not forwarded. The workaround is to write 2 additional bits when 1000BASE-X mode is configured. Note the SGMII interrupt in the port cannot be masked. As that interrupt is not handled in the KSZ9477 driver the SGMII interrupt bit will not be set even when the XPCS driver sets it. Signed-off-by: Tristram Ha <[email protected]> Reviewed-by: Maxime Chevallier <[email protected]> Tested-by: Maxime Chevallier <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Paolo Abeni <[email protected]>
1 parent 405b0d6 commit e8c35bf

File tree

5 files changed

+252
-7
lines changed

5 files changed

+252
-7
lines changed

drivers/net/dsa/microchip/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
66
select NET_DSA_TAG_NONE
77
select NET_IEEE8021Q_HELPERS
88
select DCB
9+
select PCS_XPCS
910
help
1011
This driver adds support for Microchip KSZ8, KSZ9 and
1112
LAN937X series switch chips, being KSZ8863/8873,

drivers/net/dsa/microchip/ksz9477.c

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/*
33
* Microchip KSZ9477 switch driver main logic
44
*
5-
* Copyright (C) 2017-2024 Microchip Technology Inc.
5+
* Copyright (C) 2017-2025 Microchip Technology Inc.
66
*/
77

88
#include <linux/kernel.h>
@@ -161,6 +161,190 @@ static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev)
161161
10, 1000);
162162
}
163163

164+
static void port_sgmii_s(struct ksz_device *dev, uint port, u16 devid, u16 reg)
165+
{
166+
u32 data;
167+
168+
data = (devid & MII_MMD_CTRL_DEVAD_MASK) << 16;
169+
data |= reg;
170+
ksz_pwrite32(dev, port, REG_PORT_SGMII_ADDR__4, data);
171+
}
172+
173+
static void port_sgmii_r(struct ksz_device *dev, uint port, u16 devid, u16 reg,
174+
u16 *buf)
175+
{
176+
port_sgmii_s(dev, port, devid, reg);
177+
ksz_pread16(dev, port, REG_PORT_SGMII_DATA__4 + 2, buf);
178+
}
179+
180+
static void port_sgmii_w(struct ksz_device *dev, uint port, u16 devid, u16 reg,
181+
u16 buf)
182+
{
183+
port_sgmii_s(dev, port, devid, reg);
184+
ksz_pwrite32(dev, port, REG_PORT_SGMII_DATA__4, buf);
185+
}
186+
187+
static int ksz9477_pcs_read(struct mii_bus *bus, int phy, int mmd, int reg)
188+
{
189+
struct ksz_device *dev = bus->priv;
190+
int port = ksz_get_sgmii_port(dev);
191+
u16 val;
192+
193+
port_sgmii_r(dev, port, mmd, reg, &val);
194+
195+
/* Simulate a value to activate special code in the XPCS driver if
196+
* supported.
197+
*/
198+
if (mmd == MDIO_MMD_PMAPMD) {
199+
if (reg == MDIO_DEVID1)
200+
val = 0x9477;
201+
else if (reg == MDIO_DEVID2)
202+
val = 0x22 << 10;
203+
} else if (mmd == MDIO_MMD_VEND2) {
204+
struct ksz_port *p = &dev->ports[port];
205+
206+
/* Need to update MII_BMCR register with the exact speed and
207+
* duplex mode when running in SGMII mode and this register is
208+
* used to detect connected speed in that mode.
209+
*/
210+
if (reg == MMD_SR_MII_AUTO_NEG_STATUS) {
211+
int duplex, speed;
212+
213+
if (val & SR_MII_STAT_LINK_UP) {
214+
speed = (val >> SR_MII_STAT_S) & SR_MII_STAT_M;
215+
if (speed == SR_MII_STAT_1000_MBPS)
216+
speed = SPEED_1000;
217+
else if (speed == SR_MII_STAT_100_MBPS)
218+
speed = SPEED_100;
219+
else
220+
speed = SPEED_10;
221+
222+
if (val & SR_MII_STAT_FULL_DUPLEX)
223+
duplex = DUPLEX_FULL;
224+
else
225+
duplex = DUPLEX_HALF;
226+
227+
if (!p->phydev.link ||
228+
p->phydev.speed != speed ||
229+
p->phydev.duplex != duplex) {
230+
u16 ctrl;
231+
232+
p->phydev.link = 1;
233+
p->phydev.speed = speed;
234+
p->phydev.duplex = duplex;
235+
port_sgmii_r(dev, port, mmd, MII_BMCR,
236+
&ctrl);
237+
ctrl &= BMCR_ANENABLE;
238+
ctrl |= mii_bmcr_encode_fixed(speed,
239+
duplex);
240+
port_sgmii_w(dev, port, mmd, MII_BMCR,
241+
ctrl);
242+
}
243+
} else {
244+
p->phydev.link = 0;
245+
}
246+
} else if (reg == MII_BMSR) {
247+
p->phydev.link = (val & BMSR_LSTATUS);
248+
}
249+
}
250+
251+
return val;
252+
}
253+
254+
static int ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int reg,
255+
u16 val)
256+
{
257+
struct ksz_device *dev = bus->priv;
258+
int port = ksz_get_sgmii_port(dev);
259+
260+
if (mmd == MDIO_MMD_VEND2) {
261+
struct ksz_port *p = &dev->ports[port];
262+
263+
if (reg == MMD_SR_MII_AUTO_NEG_CTRL) {
264+
u16 sgmii_mode = SR_MII_PCS_SGMII << SR_MII_PCS_MODE_S;
265+
266+
/* Need these bits for 1000BASE-X mode to work with
267+
* AN on.
268+
*/
269+
if (!(val & sgmii_mode))
270+
val |= SR_MII_SGMII_LINK_UP |
271+
SR_MII_TX_CFG_PHY_MASTER;
272+
273+
/* SGMII interrupt in the port cannot be masked, so
274+
* make sure interrupt is not enabled as it is not
275+
* handled.
276+
*/
277+
val &= ~SR_MII_AUTO_NEG_COMPLETE_INTR;
278+
} else if (reg == MII_BMCR) {
279+
/* The MII_ADVERTISE register needs to write once
280+
* before doing auto-negotiation for the correct
281+
* config_word to be sent out after reset.
282+
*/
283+
if ((val & BMCR_ANENABLE) && !p->sgmii_adv_write) {
284+
u16 adv;
285+
286+
/* The SGMII port cannot disable flow control
287+
* so it is better to just advertise symmetric
288+
* pause.
289+
*/
290+
port_sgmii_r(dev, port, mmd, MII_ADVERTISE,
291+
&adv);
292+
adv |= ADVERTISE_1000XPAUSE;
293+
adv &= ~ADVERTISE_1000XPSE_ASYM;
294+
port_sgmii_w(dev, port, mmd, MII_ADVERTISE,
295+
adv);
296+
p->sgmii_adv_write = 1;
297+
} else if (val & BMCR_RESET) {
298+
p->sgmii_adv_write = 0;
299+
}
300+
} else if (reg == MII_ADVERTISE) {
301+
/* XPCS driver writes to this register so there is no
302+
* need to update it for the errata.
303+
*/
304+
p->sgmii_adv_write = 1;
305+
}
306+
}
307+
port_sgmii_w(dev, port, mmd, reg, val);
308+
309+
return 0;
310+
}
311+
312+
int ksz9477_pcs_create(struct ksz_device *dev)
313+
{
314+
/* This chip has a SGMII port. */
315+
if (ksz_has_sgmii_port(dev)) {
316+
int port = ksz_get_sgmii_port(dev);
317+
struct ksz_port *p = &dev->ports[port];
318+
struct phylink_pcs *pcs;
319+
struct mii_bus *bus;
320+
int ret;
321+
322+
bus = devm_mdiobus_alloc(dev->dev);
323+
if (!bus)
324+
return -ENOMEM;
325+
326+
bus->name = "ksz_pcs_mdio_bus";
327+
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
328+
dev_name(dev->dev));
329+
bus->read_c45 = &ksz9477_pcs_read;
330+
bus->write_c45 = &ksz9477_pcs_write;
331+
bus->parent = dev->dev;
332+
bus->phy_mask = ~0;
333+
bus->priv = dev;
334+
335+
ret = devm_mdiobus_register(dev->dev, bus);
336+
if (ret)
337+
return ret;
338+
339+
pcs = xpcs_create_pcs_mdiodev(bus, 0);
340+
if (IS_ERR(pcs))
341+
return PTR_ERR(pcs);
342+
p->pcs = pcs;
343+
}
344+
345+
return 0;
346+
}
347+
164348
int ksz9477_reset_switch(struct ksz_device *dev)
165349
{
166350
u8 data8;
@@ -978,6 +1162,14 @@ void ksz9477_get_caps(struct ksz_device *dev, int port,
9781162

9791163
if (dev->info->gbit_capable[port])
9801164
config->mac_capabilities |= MAC_1000FD;
1165+
1166+
if (ksz_is_sgmii_port(dev, port)) {
1167+
struct ksz_port *p = &dev->ports[port];
1168+
1169+
phy_interface_or(config->supported_interfaces,
1170+
config->supported_interfaces,
1171+
p->pcs->supported_interfaces);
1172+
}
9811173
}
9821174

9831175
int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs)

drivers/net/dsa/microchip/ksz9477.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/*
33
* Microchip KSZ9477 series Header file
44
*
5-
* Copyright (C) 2017-2022 Microchip Technology Inc.
5+
* Copyright (C) 2017-2025 Microchip Technology Inc.
66
*/
77

88
#ifndef __KSZ9477_H
@@ -97,4 +97,6 @@ void ksz9477_acl_match_process_l2(struct ksz_device *dev, int port,
9797
u16 ethtype, u8 *src_mac, u8 *dst_mac,
9898
unsigned long cookie, u32 prio);
9999

100+
int ksz9477_pcs_create(struct ksz_device *dev);
101+
100102
#endif

drivers/net/dsa/microchip/ksz_common.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/*
33
* Microchip switch driver main logic
44
*
5-
* Copyright (C) 2017-2024 Microchip Technology Inc.
5+
* Copyright (C) 2017-2025 Microchip Technology Inc.
66
*/
77

88
#include <linux/delay.h>
@@ -408,12 +408,29 @@ static void ksz9477_phylink_mac_link_up(struct phylink_config *config,
408408
int speed, int duplex, bool tx_pause,
409409
bool rx_pause);
410410

411+
static struct phylink_pcs *
412+
ksz_phylink_mac_select_pcs(struct phylink_config *config,
413+
phy_interface_t interface)
414+
{
415+
struct dsa_port *dp = dsa_phylink_to_port(config);
416+
struct ksz_device *dev = dp->ds->priv;
417+
struct ksz_port *p = &dev->ports[dp->index];
418+
419+
if (ksz_is_sgmii_port(dev, dp->index) &&
420+
(interface == PHY_INTERFACE_MODE_SGMII ||
421+
interface == PHY_INTERFACE_MODE_1000BASEX))
422+
return p->pcs;
423+
424+
return NULL;
425+
}
426+
411427
static const struct phylink_mac_ops ksz9477_phylink_mac_ops = {
412428
.mac_config = ksz_phylink_mac_config,
413429
.mac_link_down = ksz_phylink_mac_link_down,
414430
.mac_link_up = ksz9477_phylink_mac_link_up,
415431
.mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi,
416432
.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
433+
.mac_select_pcs = ksz_phylink_mac_select_pcs,
417434
};
418435

419436
static const struct ksz_dev_ops ksz9477_dev_ops = {
@@ -451,6 +468,7 @@ static const struct ksz_dev_ops ksz9477_dev_ops = {
451468
.reset = ksz9477_reset_switch,
452469
.init = ksz9477_switch_init,
453470
.exit = ksz9477_switch_exit,
471+
.pcs_create = ksz9477_pcs_create,
454472
};
455473

456474
static const struct phylink_mac_ops lan937x_phylink_mac_ops = {
@@ -1093,8 +1111,7 @@ static const struct regmap_range ksz9477_valid_regs[] = {
10931111
regmap_reg_range(0x701b, 0x701b),
10941112
regmap_reg_range(0x701f, 0x7020),
10951113
regmap_reg_range(0x7030, 0x7030),
1096-
regmap_reg_range(0x7200, 0x7203),
1097-
regmap_reg_range(0x7206, 0x7207),
1114+
regmap_reg_range(0x7200, 0x7207),
10981115
regmap_reg_range(0x7300, 0x7301),
10991116
regmap_reg_range(0x7400, 0x7401),
11001117
regmap_reg_range(0x7403, 0x7403),
@@ -1610,6 +1627,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
16101627
true, false, false},
16111628
.gbit_capable = {true, true, true, true, true, true, true},
16121629
.ptp_capable = true,
1630+
.sgmii_port = 7,
16131631
.wr_table = &ksz9477_register_set,
16141632
.rd_table = &ksz9477_register_set,
16151633
},
@@ -2002,6 +2020,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
20022020
.internal_phy = {true, true, true, true,
20032021
true, false, false},
20042022
.gbit_capable = {true, true, true, true, true, true, true},
2023+
.sgmii_port = 7,
20052024
.wr_table = &ksz9477_register_set,
20062025
.rd_table = &ksz9477_register_set,
20072026
},
@@ -2137,7 +2156,7 @@ void ksz_r_mib_stats64(struct ksz_device *dev, int port)
21372156

21382157
spin_unlock(&mib->stats64_lock);
21392158

2140-
if (dev->info->phy_errata_9477) {
2159+
if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) {
21412160
ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col);
21422161
if (ret)
21432162
dev_err(dev->dev, "Failed to monitor transmission halt\n");
@@ -2845,6 +2864,12 @@ static int ksz_setup(struct dsa_switch *ds)
28452864
if (ret)
28462865
return ret;
28472866

2867+
if (ksz_has_sgmii_port(dev) && dev->dev_ops->pcs_create) {
2868+
ret = dev->dev_ops->pcs_create(dev);
2869+
if (ret)
2870+
return ret;
2871+
}
2872+
28482873
/* set broadcast storm protection 10% rate */
28492874
regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL],
28502875
BROADCAST_STORM_RATE,
@@ -3692,6 +3717,10 @@ static void ksz_phylink_mac_config(struct phylink_config *config,
36923717
if (dev->info->internal_phy[port])
36933718
return;
36943719

3720+
/* No need to configure XMII control register when using SGMII. */
3721+
if (ksz_is_sgmii_port(dev, port))
3722+
return;
3723+
36953724
if (phylink_autoneg_inband(mode)) {
36963725
dev_err(dev->dev, "In-band AN not supported!\n");
36973726
return;

0 commit comments

Comments
 (0)