|
2 | 2 | /*
|
3 | 3 | * Microchip KSZ9477 switch driver main logic
|
4 | 4 | *
|
5 |
| - * Copyright (C) 2017-2024 Microchip Technology Inc. |
| 5 | + * Copyright (C) 2017-2025 Microchip Technology Inc. |
6 | 6 | */
|
7 | 7 |
|
8 | 8 | #include <linux/kernel.h>
|
@@ -161,6 +161,190 @@ static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev)
|
161 | 161 | 10, 1000);
|
162 | 162 | }
|
163 | 163 |
|
| 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 | + |
164 | 348 | int ksz9477_reset_switch(struct ksz_device *dev)
|
165 | 349 | {
|
166 | 350 | u8 data8;
|
@@ -978,6 +1162,14 @@ void ksz9477_get_caps(struct ksz_device *dev, int port,
|
978 | 1162 |
|
979 | 1163 | if (dev->info->gbit_capable[port])
|
980 | 1164 | 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 | + } |
981 | 1173 | }
|
982 | 1174 |
|
983 | 1175 | int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs)
|
|
0 commit comments