// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6352 family SERDES PCS support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/mii.h>
#include "chip.h"
#include "global2.h"
#include "phy.h"
#include "port.h"
#include "serdes.h"
struct mv88e639x_pcs {
struct mdio_device mdio;
struct phylink_pcs sgmii_pcs;
struct phylink_pcs xg_pcs;
bool erratum_3_14;
bool supports_5g;
phy_interface_t interface;
unsigned int irq;
char name[64];
irqreturn_t (*handle_irq)(struct mv88e639x_pcs *mpcs);
};
static int mv88e639x_read(struct mv88e639x_pcs *mpcs, u16 regnum, u16 *val)
{
int err;
err = mdiodev_c45_read(&mpcs->mdio, MDIO_MMD_PHYXS, regnum);
if (err < 0)
return err;
*val = err;
return 0;
}
static int mv88e639x_write(struct mv88e639x_pcs *mpcs, u16 regnum, u16 val)
{
return mdiodev_c45_write(&mpcs->mdio, MDIO_MMD_PHYXS, regnum, val);
}
static int mv88e639x_modify(struct mv88e639x_pcs *mpcs, u16 regnum, u16 mask,
u16 val)
{
return mdiodev_c45_modify(&mpcs->mdio, MDIO_MMD_PHYXS, regnum, mask,
val);
}
static int mv88e639x_modify_changed(struct mv88e639x_pcs *mpcs, u16 regnum,
u16 mask, u16 set)
{
return mdiodev_c45_modify_changed(&mpcs->mdio, MDIO_MMD_PHYXS, regnum,
mask, set);
}
static struct mv88e639x_pcs *
mv88e639x_pcs_alloc(struct device *dev, struct mii_bus *bus, unsigned int addr,
int port)
{
struct mv88e639x_pcs *mpcs;
mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL);
if (!mpcs)
return NULL;
mpcs->mdio.dev.parent = dev;
mpcs->mdio.bus = bus;
mpcs->mdio.addr = addr;
snprintf(mpcs->name, sizeof(mpcs->name),
"mv88e6xxx-%s-serdes-%d", dev_name(dev), port);
return mpcs;
}
static irqreturn_t mv88e639x_pcs_handle_irq(int irq, void *dev_id)
{
struct mv88e639x_pcs *mpcs = dev_id;
irqreturn_t (*handler)(struct mv88e639x_pcs *);
handler = READ_ONCE(mpcs->handle_irq);
if (!handler)
return IRQ_NONE;
return handler(mpcs);
}
static int mv88e639x_pcs_setup_irq(struct mv88e639x_pcs *mpcs,
struct mv88e6xxx_chip *chip, int port)
{
unsigned int irq;
irq = mv88e6xxx_serdes_irq_mapping(chip, port);
if (!irq) {
/* Use polling mode */
mpcs->sgmii_pcs.poll = true;
mpcs->xg_pcs.poll = true;
return 0;
}
mpcs->irq = irq;
return request_threaded_irq(irq, NULL, mv88e639x_pcs_handle_irq,
IRQF_ONESHOT, mpcs->name, mpcs);
}
static void mv88e639x_pcs_teardown(struct mv88e6xxx_chip *chip, int port)
{
struct mv88e639x_pcs *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 mv88e639x_pcs *sgmii_pcs_to_mv88e639x_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mv88e639x_pcs, sgmii_pcs);
}
static irqreturn_t mv88e639x_sgmii_handle_irq(struct mv88e639x_pcs *mpcs)
{
u16 int_status;
int err;
err = mv88e639x_read(mpcs, MV88E6390_SGMII_INT_STATUS, &int_status);
if (err)
return IRQ_NONE;
if (int_status & (MV88E6390_SGMII_INT_LINK_DOWN |
MV88E6390_SGMII_INT_LINK_UP)) {
phylink_pcs_change(&mpcs->sgmii_pcs,
int_status & MV88E6390_SGMII_INT_LINK_UP);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mv88e639x_sgmii_pcs_control_irq(struct mv88e639x_pcs *mpcs,
bool enable)
{
u16 val = 0;
if (enable)
val |= MV88E6390_SGMII_INT_LINK_DOWN |
MV88E6390_SGMII_INT_LINK_UP;
return mv88e639x_modify(mpcs, MV88E6390_SGMII_INT_ENABLE,
MV88E6390_SGMII_INT_LINK_DOWN |
MV88E6390_SGMII_INT_LINK_UP, val);
}
static int mv88e639x_sgmii_pcs_control_pwr(struct mv88e639x_pcs *mpcs,
bool enable)
{
u16 mask, val;
if (enable) {
mask = BMCR_RESET | BMCR_LOOPBACK | BMCR_PDOWN;
val = 0;
} else {
mask = val = BMCR_PDOWN;
}
return mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR, mask, val);
}
static int mv88e639x_sgmii_pcs_enable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
/* power enable done in post_config */
mpcs->handle_irq = mv88e639x_sgmii_handle_irq;
return mv88e639x_sgmii_pcs_control_irq(mpcs, !!mpcs->irq);
}
static void mv88e639x_sgmii_pcs_disable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_sgmii_pcs_control_irq(mpcs, false);
mv88e639x_sgmii_pcs_control_pwr(mpcs, false);
}
static void mv88e639x_sgmii_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_sgmii_pcs_control_pwr(mpcs, false);
}
static int mv88e6390_erratum_3_14(struct mv88e639x_pcs *mpcs)
{
static