// SPDX-License-Identifier: GPL-2.0-or-later /* * Marvell 88E6185 family SERDES PCS support * * Copyright (c) 2008 Marvell Semiconductor * * Copyright (c) 2017 Andrew Lunn */ #include #include "global2.h" #include "port.h" #include "serdes.h" struct mv88e6185_pcs { struct phylink_pcs phylink_pcs; unsigned int irq; char name[64]; struct mv88e6xxx_chip *chip; int port; }; static struct mv88e6185_pcs *pcs_to_mv88e6185_pcs(struct phylink_pcs *pcs) { return container_of(pcs, struct mv88e6185_pcs, phylink_pcs); } static irqreturn_t mv88e6185_pcs_handle_irq(int irq, void *dev_id) { struct mv88e6185_pcs *mpcs = dev_id; struct mv88e6xxx_chip *chip; irqreturn_t ret = IRQ_NONE; bool link_up; u16 status; int port; int err; chip = mpcs->chip; port = mpcs->port; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status); mv88e6xxx_reg_unlock(chip); if (!err) { link_up = !!(status & MV88E6XXX_PORT_STS_LINK); phylink_pcs_change(&mpcs->phylink_pcs, link_up); ret = IRQ_HANDLED; } return ret; } static void mv88e6185_pcs_get_state(struct phylink_pcs *pcs, struct phylink_link_state *state) { struct mv88e6185_pcs *mpcs = pcs_to_mv88e6185_pcs(pcs); struct mv88e6xxx_chip *chip = mpcs->chip; int port = mpcs->port; u16 status; int err; mv88e6xxx_reg_lock(chip); err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status); mv88e6xxx_reg_unlock(chip); if (err) status = 0; state->link = !!(status & MV88E6XXX_PORT_STS_LINK); if (state->link) { state->duplex = status & MV88E6XXX_PORT_STS_DUPLEX ? DUPLEX_FULL : DUPLEX_HALF; switch (status & MV88E6XXX_PORT_STS_SPEED_MASK) { case MV88E6XXX_PORT_STS_SPEED_1000: state->speed = SPEED_1000; break; case MV88E6XXX_PORT_STS_SPEED_100: state->speed = SPEED_100; break; case MV88E6XXX_PORT_STS_SPEED_10: state->speed = SPEED_10; break; default: state->link = false; break; } } } static int mv88e6185_pcs_config(struct phylink_pcs *pcs, unsigned int mode, phy_interface_t interface, const unsigned long *advertising, bool permit_pause_to_mac) { return 0; } static void mv88e6185_pcs_an_restart(struct phylink_pcs *pcs) { } static const struct phylink_pcs_ops mv88e6185_phylink_pcs_ops = { .pcs_get_state = mv88e6185_pcs_get_state, .pcs_config = mv88e6185_pcs_config, .pcs_an_restart = mv88e6185_pcs_an_restart, }; static int mv88e6185_pcs_init(struct mv88e6xxx_chip *chip, int port) { struct mv88e6185_pcs *mpcs; struct device *dev; unsigned int irq; int err; /* There are no configurable serdes lanes on this switch chip, so * we use the static cmode configuration to determine whether we * have a PCS or not. */ if (chip->ports[port].cmode != MV88E6185_PORT_STS_CMODE_SERDES && chip->ports[port].cmode != MV88E6185_PORT_STS_CMODE_1000BASE_X) return 0; dev = chip->dev; mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL); if (!mpcs) return -ENOMEM; mpcs->chip = chip; mpcs->port = port; mpcs->phylink_pcs.ops = &mv88e6185_phylink_pcs_ops; irq = mv88e6xxx_serdes_irq_mapping(chip, port); if (irq) { snprintf(mpcs->name, sizeof(mpcs->name), "mv88e6xxx-%s-serdes-%d", dev_name(dev), port); err = request_threaded_irq(irq, NULL, mv88e6185_pcs_handle_irq, IRQF_ONESHOT, mpcs->name, mpcs); if (err) { kfree(mpcs); return err; } mpcs->irq = irq; } else { mpcs->phylink_pcs.poll = true; } chip->ports[port].pcs_private = &mpcs->phylink_pcs; return 0; } static void mv88e6185_pcs_teardown(struct mv88e6xxx_chip *chip, int port) { struct mv88e6185_pcs *mpcs; mpcs = chip->ports[port].pcs_private; if (!mpcs) return; if (mpcs->irq) free_irq(mpcs->irq, mpcs); kfree(mpcs); chip->ports[port].pcs_private = NULL; } static struct phylink_pcs *mv88e6185_pcs_select(struct mv88e6xxx_chip *chip, int port, phy_interface_t interface) { return chip->ports[port].pcs_private; } const struct mv88e6xxx_pcs_ops mv88e6185_pcs_ops = { .pcs_init = mv88e6185_pcs_init, .pcs_teardown = mv88e6185_pcs_teardown, .pcs_select = mv88e6185_pcs_select, };