Commit a8fbe1ee authored by David S. Miller's avatar David S. Miller

Merge branch 'mv88e6xxx-phylink_pcs'

Russell King says:

====================
Convert mv88e6xxx to phylink_pcs

This series (previously posted with further patches on the 26 June as
RFC) converts mv88e6xxx to phylink_pcs, and thus moves it from being
a pre-March 2020 legacy driver.

The first four patches lay the ground-work for the conversion by
adding four new methods to the phylink_pcs operations structure:

  pcs_enable() - called when the PCS is going to start to be used
  pcs_disable() - called when the PCS is no longer being used

  pcs_pre_config() - called before the MAC configuration method
  pcs_post_config() - called after the MAC configuration method
      Both of these are necessary for some of the mv88e639x
      workarounds.

We also add the ability to inform phylink of a change to the PCS
state without involving the MAC later, by providing
phylink_pcs_change() which takes a phylink_pcs structure rather than
a phylink structure. phylink maintains which instance the PCS is
conencted to, so internally it can do the right thing when the PCS
is in-use.

Then we provide some additional mdiobus and mdiodev accessors that
we will be using in the new PCS drivers.

The changes for mv88e6xxx follow, and the first one needs to be
explicitly pointed out - we (Andrew and myself) have both decided that
all possible approaches to maintaining backwards compatibility with DT
have been exhaused - everyone has some objection to everything that
has been proposed. So, after many years of trying, we have decided
that this is just an impossibility, and with this patch, we are now
intentionally and knowingly breaking any DT that does not specify the
CPU and DSA port fixed-link parameters. Hence why Andrew has recently
been submitting DT update patches. It is regrettable that it has come
to this.

Following this, we start preparing 88e6xxx for phylink_pcs conversion
by padding the mac_select_pcs() DSA method, and the internal hooks to
create and tear-down PCS instances. Rather than bloat the already very
large mv88e6xxx_ops structure, I decided that it would be better that
the new internal chip specific PCS methods are all grouped within their
own structure - and this structure can be declared in the PCS drivers
themselves.

Then we have the actual conversion patches, one for each family of PCS.

Lastly, we clean up the driver after conversion, removing all the now
redundant code.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 6963e463 d20acfdd
......@@ -9,6 +9,9 @@ mv88e6xxx-objs += global2.o
mv88e6xxx-objs += global2_avb.o
mv88e6xxx-objs += global2_scratch.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.o
mv88e6xxx-objs += pcs-6185.o
mv88e6xxx-objs += pcs-6352.o
mv88e6xxx-objs += pcs-639x.o
mv88e6xxx-objs += phy.o
mv88e6xxx-objs += port.o
mv88e6xxx-objs += port_hidden.o
......
This diff is collapsed.
......@@ -205,6 +205,7 @@ struct mv88e6xxx_irq_ops;
struct mv88e6xxx_gpio_ops;
struct mv88e6xxx_avb_ops;
struct mv88e6xxx_ptp_ops;
struct mv88e6xxx_pcs_ops;
struct mv88e6xxx_irq {
u16 masked;
......@@ -285,9 +286,8 @@ struct mv88e6xxx_port {
u8 cmode;
bool mirror_ingress;
bool mirror_egress;
unsigned int serdes_irq;
char serdes_irq_name[64];
struct devlink_region *region;
void *pcs_private;
/* MacAuth Bypass control flag */
bool mab;
......@@ -590,31 +590,12 @@ struct mv88e6xxx_ops {
int (*mgmt_rsvd2cpu)(struct mv88e6xxx_chip *chip);
/* Power on/off a SERDES interface */
int (*serdes_power)(struct mv88e6xxx_chip *chip, int port, int lane,
bool up);
/* SERDES lane mapping */
int (*serdes_get_lane)(struct mv88e6xxx_chip *chip, int port);
int (*serdes_pcs_get_state)(struct mv88e6xxx_chip *chip, int port,
int lane, struct phylink_link_state *state);
int (*serdes_pcs_config)(struct mv88e6xxx_chip *chip, int port,
int lane, unsigned int mode,
phy_interface_t interface,
const unsigned long *advertise);
int (*serdes_pcs_an_restart)(struct mv88e6xxx_chip *chip, int port,
int lane);
int (*serdes_pcs_link_up)(struct mv88e6xxx_chip *chip, int port,
int lane, int speed, int duplex);
/* SERDES interrupt handling */
unsigned int (*serdes_irq_mapping)(struct mv88e6xxx_chip *chip,
int port);
int (*serdes_irq_enable)(struct mv88e6xxx_chip *chip, int port, int lane,
bool enable);
irqreturn_t (*serdes_irq_status)(struct mv88e6xxx_chip *chip, int port,
int lane);
/* Statistics from the SERDES interface */
int (*serdes_get_sset_count)(struct mv88e6xxx_chip *chip, int port);
......@@ -664,6 +645,8 @@ struct mv88e6xxx_ops {
void (*phylink_get_caps)(struct mv88e6xxx_chip *chip, int port,
struct phylink_config *config);
const struct mv88e6xxx_pcs_ops *pcs_ops;
/* Max Frame Size */
int (*set_max_frame_size)(struct mv88e6xxx_chip *chip, int mtu);
};
......@@ -736,6 +719,14 @@ struct mv88e6xxx_ptp_ops {
u32 cc_mult_dem;
};
struct mv88e6xxx_pcs_ops {
int (*pcs_init)(struct mv88e6xxx_chip *chip, int port);
void (*pcs_teardown)(struct mv88e6xxx_chip *chip, int port);
struct phylink_pcs *(*pcs_select)(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
};
#define STATS_TYPE_PORT BIT(0)
#define STATS_TYPE_BANK0 BIT(1)
#define STATS_TYPE_BANK1 BIT(2)
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6185 family SERDES PCS support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/phylink.h>
#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,
};
// 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/phylink.h>
#include "global2.h"
#include "port.h"
#include "serdes.h"
/* Definitions from drivers/net/phy/marvell.c, which would be good to reuse. */
#define MII_M1011_PHY_STATUS 17
#define MII_M1011_IMASK 18
#define MII_M1011_IMASK_LINK_CHANGE BIT(10)
#define MII_M1011_IEVENT 19
#define MII_M1011_IEVENT_LINK_CHANGE BIT(10)
#define MII_MARVELL_PHY_PAGE 22
#define MII_MARVELL_FIBER_PAGE 1
struct marvell_c22_pcs {
struct mdio_device mdio;
struct phylink_pcs phylink_pcs;
unsigned int irq;
char name[64];
bool (*link_check)(struct marvell_c22_pcs *mpcs);
struct mv88e6xxx_port *port;
};
static struct marvell_c22_pcs *pcs_to_marvell_c22_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct marvell_c22_pcs, phylink_pcs);
}
static int marvell_c22_pcs_set_fiber_page(struct marvell_c22_pcs *mpcs)
{
u16 page;
int err;
mutex_lock(&mpcs->mdio.bus->mdio_lock);
err = __mdiodev_read(&mpcs->mdio, MII_MARVELL_PHY_PAGE);
if (err < 0) {
dev_err(mpcs->mdio.dev.parent,
"%s: can't read Serdes page register: %pe\n",
mpcs->name, ERR_PTR(err));
return err;
}
page = err;
err = __mdiodev_write(&mpcs->mdio, MII_MARVELL_PHY_PAGE,
MII_MARVELL_FIBER_PAGE);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"%s: can't set Serdes page register: %pe\n",
mpcs->name, ERR_PTR(err));
return err;
}
return page;
}
static int marvell_c22_pcs_restore_page(struct marvell_c22_pcs *mpcs,
int oldpage, int ret)
{
int err;
if (oldpage >= 0) {
err = __mdiodev_write(&mpcs->mdio, MII_MARVELL_PHY_PAGE,
oldpage);
if (err)
dev_err(mpcs->mdio.dev.parent,
"%s: can't restore Serdes page register: %pe\n",
mpcs->name, ERR_PTR(err));
if (!err || ret < 0)
err = ret;
} else {
err = oldpage;
}
mutex_unlock(&mpcs->mdio.bus->mdio_lock);
return err;
}
static irqreturn_t marvell_c22_pcs_handle_irq(int irq, void *dev_id)
{
struct marvell_c22_pcs *mpcs = dev_id;
irqreturn_t status = IRQ_NONE;
int err, oldpage;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage < 0)
goto fail;
err = __mdiodev_read(&mpcs->mdio, MII_M1011_IEVENT);
if (err >= 0 && err & MII_M1011_IEVENT_LINK_CHANGE) {
phylink_pcs_change(&mpcs->phylink_pcs, true);
status = IRQ_HANDLED;
}
fail:
marvell_c22_pcs_restore_page(mpcs, oldpage, 0);
return status;
}
static int marvell_c22_pcs_modify(struct marvell_c22_pcs *mpcs, u8 reg,
u16 mask, u16 val)
{
int oldpage, err = 0;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage >= 0)
err = __mdiodev_modify(&mpcs->mdio, reg, mask, val);
return marvell_c22_pcs_restore_page(mpcs, oldpage, err);
}
static int marvell_c22_pcs_power(struct marvell_c22_pcs *mpcs,
bool on)
{
u16 val = on ? 0 : BMCR_PDOWN;
return marvell_c22_pcs_modify(mpcs, MII_BMCR, BMCR_PDOWN, val);
}
static int marvell_c22_pcs_control_irq(struct marvell_c22_pcs *mpcs,
bool enable)
{
u16 val = enable ? MII_M1011_IMASK_LINK_CHANGE : 0;
return marvell_c22_pcs_modify(mpcs, MII_M1011_IMASK,
MII_M1011_IMASK_LINK_CHANGE, val);
}
static int marvell_c22_pcs_enable(struct phylink_pcs *pcs)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
int err;
err = marvell_c22_pcs_power(mpcs, true);
if (err)
return err;
return marvell_c22_pcs_control_irq(mpcs, !!mpcs->irq);
}
static void marvell_c22_pcs_disable(struct phylink_pcs *pcs)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
marvell_c22_pcs_control_irq(mpcs, false);
marvell_c22_pcs_power(mpcs, false);
}
static void marvell_c22_pcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
int oldpage, bmsr, lpa, status;
state->link = false;
if (mpcs->link_check && !mpcs->link_check(mpcs))
return;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage >= 0) {
bmsr = __mdiodev_read(&mpcs->mdio, MII_BMSR);
lpa = __mdiodev_read(&mpcs->mdio, MII_LPA);
status = __mdiodev_read(&mpcs->mdio, MII_M1011_PHY_STATUS);
}
if (marvell_c22_pcs_restore_page(mpcs, oldpage, 0) >= 0 &&
bmsr >= 0 && lpa >= 0 && status >= 0)
mv88e6xxx_pcs_decode_state(mpcs->mdio.dev.parent, bmsr, lpa,
status, state);
}
static int marvell_c22_pcs_config(struct phylink_pcs *pcs,
unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
int oldpage, adv, err, ret = 0;
u16 bmcr;
adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
if (adv < 0)
return 0;
bmcr = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED ? BMCR_ANENABLE : 0;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage < 0)
goto restore;
err = __mdiodev_modify_changed(&mpcs->mdio, MII_ADVERTISE, 0xffff, adv);
ret = err;
if (err < 0)
goto restore;
err = __mdiodev_modify_changed(&mpcs->mdio, MII_BMCR, BMCR_ANENABLE,
bmcr);
if (err < 0) {
ret = err;
goto restore;
}
/* If the ANENABLE bit was changed, the PHY will restart negotiation,
* so we don't need to flag a change to trigger its own restart.
*/
if (err)
ret = 0;
restore:
return marvell_c22_pcs_restore_page(mpcs, oldpage, ret);
}
static void marvell_c22_pcs_an_restart(struct phylink_pcs *pcs)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
marvell_c22_pcs_modify(mpcs, MII_BMCR, BMCR_ANRESTART, BMCR_ANRESTART);
}
static void marvell_c22_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
phy_interface_t interface, int speed,
int duplex)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
u16 bmcr;
int err;
if (phylink_autoneg_inband(mode))
return;
bmcr = mii_bmcr_encode_fixed(speed, duplex);
err = marvell_c22_pcs_modify(mpcs, MII_BMCR, BMCR_SPEED100 |
BMCR_FULLDPLX | BMCR_SPEED1000, bmcr);
if (err)
dev_err(mpcs->mdio.dev.parent,
"%s: failed to configure mpcs: %pe\n", mpcs->name,
ERR_PTR(err));
}
static const struct phylink_pcs_ops marvell_c22_pcs_ops = {
.pcs_enable = marvell_c22_pcs_enable,
.pcs_disable = marvell_c22_pcs_disable,
.pcs_get_state = marvell_c22_pcs_get_state,
.pcs_config = marvell_c22_pcs_config,
.pcs_an_restart = marvell_c22_pcs_an_restart,
.pcs_link_up = marvell_c22_pcs_link_up,
};
static struct marvell_c22_pcs *marvell_c22_pcs_alloc(struct device *dev,
struct mii_bus *bus,
unsigned int addr)
{
struct marvell_c22_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;
mpcs->phylink_pcs.ops = &marvell_c22_pcs_ops;
mpcs->phylink_pcs.neg_mode = true;
return mpcs;
}
static int marvell_c22_pcs_setup_irq(struct marvell_c22_pcs *mpcs,
unsigned int irq)
{
int err;
mpcs->phylink_pcs.poll = !irq;
mpcs->irq = irq;
if (irq) {
err = request_threaded_irq(irq, NULL,
marvell_c22_pcs_handle_irq,
IRQF_ONESHOT, mpcs->name, mpcs);
if (err)
return err;
}
return 0;
}
/* mv88e6352 specifics */
static bool mv88e6352_pcs_link_check(struct marvell_c22_pcs *mpcs)
{
struct mv88e6xxx_port *port = mpcs->port;
struct mv88e6xxx_chip *chip = port->chip;
u8 cmode;
/* Port 4 can be in auto-media mode. Check that the port is
* associated with the mpcs.
*/
mv88e6xxx_reg_lock(chip);
chip->info->ops->port_get_cmode(chip, port->port, &cmode);
mv88e6xxx_reg_unlock(chip);
return cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII;
}
static int mv88e6352_pcs_init(struct mv88e6xxx_chip *chip, int port)
{
struct marvell_c22_pcs *mpcs;
struct mii_bus *bus;
struct device *dev;
unsigned int irq;
int err;
mv88e6xxx_reg_lock(chip);
err = mv88e6352_g2_scratch_port_has_serdes(chip, port);
mv88e6xxx_reg_unlock(chip);
if (err <= 0)
return err;
irq = mv88e6xxx_serdes_irq_mapping(chip, port);
bus = mv88e6xxx_default_mdio_bus(chip);
dev = chip->dev;
mpcs = marvell_c22_pcs_alloc(dev, bus, MV88E6352_ADDR_SERDES);
if (!mpcs)
return -ENOMEM;
snprintf(mpcs->name, sizeof(mpcs->name),
"mv88e6xxx-%s-serdes-%d", dev_name(dev), port);
mpcs->link_check = mv88e6352_pcs_link_check;
mpcs->port = &chip->ports[port];
err = marvell_c22_pcs_setup_irq(mpcs, irq);
if (err) {
kfree(mpcs);
return err;
}
chip->ports[port].pcs_private = &mpcs->phylink_pcs;
return 0;
}
static void mv88e6352_pcs_teardown(struct mv88e6xxx_chip *chip, int port)
{
struct marvell_c22_pcs *mpcs;
struct phylink_pcs *pcs;
pcs = chip->ports[port].pcs_private;
if (!pcs)
return;
mpcs = pcs_to_marvell_c22_pcs(pcs);
if (mpcs->irq)
free_irq(mpcs->irq, mpcs);
kfree(mpcs);
chip->ports[port].pcs_private = NULL;
}
static struct phylink_pcs *mv88e6352_pcs_select(struct mv88e6xxx_chip *chip,
int port,
phy_interface_t interface)
{
return chip->ports[port].pcs_private;
}
const struct mv88e6xxx_pcs_ops mv88e6352_pcs_ops = {
.pcs_init = mv88e6352_pcs_init,
.pcs_teardown = mv88e6352_pcs_teardown,
.pcs_select = mv88e6352_pcs_select,
};
This diff is collapsed.
......@@ -524,7 +524,6 @@ static int mv88e6xxx_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode, bool force)
{
u16 cmode;
int lane;
u16 reg;
int err;
......@@ -577,19 +576,6 @@ static int mv88e6xxx_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
if (cmode == chip->ports[port].cmode && !force)
return 0;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane >= 0) {
if (chip->ports[port].serdes_irq) {
err = mv88e6xxx_serdes_irq_disable(chip, port, lane);
if (err)
return err;
}
err = mv88e6xxx_serdes_power_down(chip, port, lane);
if (err)
return err;
}
chip->ports[port].cmode = 0;
if (cmode) {
......@@ -605,22 +591,6 @@ static int mv88e6xxx_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
return err;
chip->ports[port].cmode = cmode;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane == -ENODEV)
return 0;
if (lane < 0)
return lane;
err = mv88e6xxx_serdes_power_up(chip, port, lane);
if (err)
return err;
if (chip->ports[port].serdes_irq) {
err = mv88e6xxx_serdes_irq_enable(chip, port, lane);
if (err)
return err;
}
}
return 0;
......
This diff is collapsed.
......@@ -12,6 +12,8 @@
#include "chip.h"
struct phylink_link_state;
#define MV88E6352_ADDR_SERDES 0x0f
#define MV88E6352_SERDES_PAGE_FIBER 0x01
#define MV88E6352_SERDES_IRQ 0x0b
......@@ -44,6 +46,10 @@
/* 10GBASE-R and 10GBASE-X4/X2 */
#define MV88E6390_10G_CTRL1 (0x1000 + MDIO_CTRL1)
#define MV88E6390_10G_STAT1 (0x1000 + MDIO_STAT1)
#define MV88E6390_10G_INT_ENABLE 0x9001
#define MV88E6390_10G_INT_LINK_DOWN BIT(3)
#define MV88E6390_10G_INT_LINK_UP BIT(2)
#define MV88E6390_10G_INT_STATUS 0x9003
#define MV88E6393X_10G_INT_ENABLE 0x9000
#define MV88E6393X_10G_INT_LINK_CHANGE BIT(2)
#define MV88E6393X_10G_INT_STATUS 0x9001
......@@ -107,65 +113,17 @@
#define MV88E6393X_ERRATA_4_8_REG 0xF074
#define MV88E6393X_ERRATA_4_8_BIT BIT(14)
int mv88e6185_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_pcs_decode_state(struct device *dev, u16 bmsr, u16 lpa,
u16 status, struct phylink_link_state *state);
int mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6393x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
int lane, unsigned int mode,
phy_interface_t interface,
const unsigned long *advertise);
int mv88e6390_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
int lane, unsigned int mode,
phy_interface_t interface,
const unsigned long *advertise);
int mv88e6185_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
int lane, struct phylink_link_state *state);
int mv88e6352_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
int lane, struct phylink_link_state *state);
int mv88e6390_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
int lane, struct phylink_link_state *state);
int mv88e6393x_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
int lane, struct phylink_link_state *state);
int mv88e6352_serdes_pcs_an_restart(struct mv88e6xxx_chip *chip, int port,
int lane);
int mv88e6390_serdes_pcs_an_restart(struct mv88e6xxx_chip *chip, int port,
int lane);
int mv88e6352_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
int lane, int speed, int duplex);
int mv88e6390_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
int lane, int speed, int duplex);
unsigned int mv88e6352_serdes_irq_mapping(struct mv88e6xxx_chip *chip,
int port);
unsigned int mv88e6390_serdes_irq_mapping(struct mv88e6xxx_chip *chip,
int port);
int mv88e6185_serdes_power(struct mv88e6xxx_chip *chip, int port, int lane,
bool up);
int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, int lane,
bool on);
int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, int lane,
bool on);
int mv88e6393x_serdes_power(struct mv88e6xxx_chip *chip, int port, int lane,
bool on);
int mv88e6393x_serdes_setup_errata(struct mv88e6xxx_chip *chip);
int mv88e6097_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, int lane,
bool enable);
int mv88e6352_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, int lane,
bool enable);
int mv88e6390_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port, int lane,
bool enable);
int mv88e6393x_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port,
int lane, bool enable);
irqreturn_t mv88e6097_serdes_irq_status(struct mv88e6xxx_chip *chip, int port,
int lane);
irqreturn_t mv88e6352_serdes_irq_status(struct mv88e6xxx_chip *chip, int port,
int lane);
irqreturn_t mv88e6390_serdes_irq_status(struct mv88e6xxx_chip *chip, int port,
int lane);
irqreturn_t mv88e6393x_serdes_irq_status(struct mv88e6xxx_chip *chip, int port,
int lane);
int mv88e6352_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_get_strings(struct mv88e6xxx_chip *chip,
int port, uint8_t *data);
......@@ -195,24 +153,6 @@ static inline int mv88e6xxx_serdes_get_lane(struct mv88e6xxx_chip *chip,
return chip->info->ops->serdes_get_lane(chip, port);
}
static inline int mv88e6xxx_serdes_power_up(struct mv88e6xxx_chip *chip,
int port, int lane)
{
if (!chip->info->ops->serdes_power)
return -EOPNOTSUPP;
return chip->info->ops->serdes_power(chip, port, lane, true);
}
static inline int mv88e6xxx_serdes_power_down(struct mv88e6xxx_chip *chip,
int port, int lane)
{
if (!chip->info->ops->serdes_power)
return -EOPNOTSUPP;
return chip->info->ops->serdes_power(chip, port, lane, false);
}
static inline unsigned int
mv88e6xxx_serdes_irq_mapping(struct mv88e6xxx_chip *chip, int port)
{
......@@ -222,31 +162,9 @@ mv88e6xxx_serdes_irq_mapping(struct mv88e6xxx_chip *chip, int port)
return chip->info->ops->serdes_irq_mapping(chip, port);
}
static inline int mv88e6xxx_serdes_irq_enable(struct mv88e6xxx_chip *chip,
int port, int lane)
{
if (!chip->info->ops->serdes_irq_enable)
return -EOPNOTSUPP;
return chip->info->ops->serdes_irq_enable(chip, port, lane, true);
}
static inline int mv88e6xxx_serdes_irq_disable(struct mv88e6xxx_chip *chip,
int port, int lane)
{
if (!chip->info->ops->serdes_irq_enable)
return -EOPNOTSUPP;
return chip->info->ops->serdes_irq_enable(chip, port, lane, false);
}
static inline irqreturn_t
mv88e6xxx_serdes_irq_status(struct mv88e6xxx_chip *chip, int port, int lane)
{
if (!chip->info->ops->serdes_irq_status)
return IRQ_NONE;
return chip->info->ops->serdes_irq_status(chip, port, lane);
}
extern const struct mv88e6xxx_pcs_ops mv88e6185_pcs_ops;
extern const struct mv88e6xxx_pcs_ops mv88e6352_pcs_ops;
extern const struct mv88e6xxx_pcs_ops mv88e6390_pcs_ops;
extern const struct mv88e6xxx_pcs_ops mv88e6393x_pcs_ops;
#endif
......@@ -1210,6 +1210,26 @@ int mdiobus_c45_write_nested(struct mii_bus *bus, int addr, int devad,
}
EXPORT_SYMBOL(mdiobus_c45_write_nested);
/*
* __mdiobus_modify - Convenience function for modifying a given mdio device
* register
* @bus: the mii_bus struct
* @addr: the phy address
* @regnum: register number to write
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*/
int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
u16 set)
{
int err;
err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
return err < 0 ? err : 0;
}
EXPORT_SYMBOL_GPL(__mdiobus_modify);
/**
* mdiobus_modify - Convenience function for modifying a given mdio device
* register
......@@ -1224,10 +1244,10 @@ int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set)
int err;
mutex_lock(&bus->mdio_lock);
err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
err = __mdiobus_modify(bus, addr, regnum, mask, set);
mutex_unlock(&bus->mdio_lock);
return err < 0 ? err : 0;
return err;
}
EXPORT_SYMBOL_GPL(mdiobus_modify);
......
......@@ -34,6 +34,10 @@ enum {
PHYLINK_DISABLE_STOPPED,
PHYLINK_DISABLE_LINK,
PHYLINK_DISABLE_MAC_WOL,
PCS_STATE_DOWN = 0,
PCS_STATE_STARTING,
PCS_STATE_STARTED,
};
/**
......@@ -72,6 +76,7 @@ struct phylink {
struct phylink_link_state phy_state;
struct work_struct resolve;
unsigned int pcs_neg_mode;
unsigned int pcs_state;
bool mac_link_dropped;
bool using_mac_select_pcs;
......@@ -993,6 +998,40 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
}
}
static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
if (pcs && pcs->ops->pcs_pre_config)
pcs->ops->pcs_pre_config(pcs, interface);
}
static int phylink_pcs_post_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
int err = 0;
if (pcs && pcs->ops->pcs_post_config)
err = pcs->ops->pcs_post_config(pcs, interface);
return err;
}
static void phylink_pcs_disable(struct phylink_pcs *pcs)
{
if (pcs && pcs->ops->pcs_disable)
pcs->ops->pcs_disable(pcs);
}
static int phylink_pcs_enable(struct phylink_pcs *pcs)
{
int err = 0;
if (pcs && pcs->ops->pcs_enable)
err = pcs->ops->pcs_enable(pcs);
return err;
}
static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
const struct phylink_link_state *state,
bool permit_pause_to_mac)
......@@ -1095,11 +1134,28 @@ static void phylink_major_config(struct phylink *pl, bool restart,
/* If we have a new PCS, switch to the new PCS after preparing the MAC
* for the change.
*/
if (pcs_changed)
if (pcs_changed) {
phylink_pcs_disable(pl->pcs);
if (pl->pcs)
pl->pcs->phylink = NULL;
pcs->phylink = pl;
pl->pcs = pcs;
}
if (pl->pcs)
phylink_pcs_pre_config(pl->pcs, state->interface);
phylink_mac_config(pl, state);
if (pl->pcs)
phylink_pcs_post_config(pl->pcs, state->interface);
if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
phylink_pcs_enable(pl->pcs);
neg_mode = pl->cur_link_an_mode;
if (pl->pcs && pl->pcs->neg_mode)
neg_mode = pl->pcs_neg_mode;
......@@ -1586,6 +1642,7 @@ struct phylink *phylink_create(struct phylink_config *config,
pl->link_config.pause = MLO_PAUSE_AN;
pl->link_config.speed = SPEED_UNKNOWN;
pl->link_config.duplex = DUPLEX_UNKNOWN;
pl->pcs_state = PCS_STATE_DOWN;
pl->mac_ops = mac_ops;
__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
......@@ -1939,6 +1996,14 @@ void phylink_disconnect_phy(struct phylink *pl)
}
EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
static void phylink_link_changed(struct phylink *pl, bool up, const char *what)
{
if (!up)
pl->mac_link_dropped = true;
phylink_run_resolve(pl);
phylink_dbg(pl, "%s link %s\n", what, up ? "up" : "down");
}
/**
* phylink_mac_change() - notify phylink of a change in MAC state
* @pl: a pointer to a &struct phylink returned from phylink_create()
......@@ -1949,13 +2014,30 @@ EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
*/
void phylink_mac_change(struct phylink *pl, bool up)
{
if (!up)
pl->mac_link_dropped = true;
phylink_run_resolve(pl);
phylink_dbg(pl, "mac link %s\n", up ? "up" : "down");
phylink_link_changed(pl, up, "mac");
}
EXPORT_SYMBOL_GPL(phylink_mac_change);
/**
* phylink_pcs_change() - notify phylink of a change to PCS link state
* @pcs: pointer to &struct phylink_pcs
* @up: indicates whether the link is currently up.
*
* The PCS driver should call this when the state of its link changes
* (e.g. link failure, new negotiation results, etc.) Note: it should
* not determine "up" by reading the BMSR. If in doubt about the link
* state at interrupt time, then pass true if pcs_get_state() returns
* the latched link-down state, otherwise pass false.
*/
void phylink_pcs_change(struct phylink_pcs *pcs, bool up)
{
struct phylink *pl = pcs->phylink;
if (pl)
phylink_link_changed(pl, up, "pcs");
}
EXPORT_SYMBOL_GPL(phylink_pcs_change);
static irqreturn_t phylink_link_handler(int irq, void *data)
{
struct phylink *pl = data;
......@@ -1987,6 +2069,8 @@ void phylink_start(struct phylink *pl)
if (pl->netdev)
netif_carrier_off(pl->netdev);
pl->pcs_state = PCS_STATE_STARTING;
/* Apply the link configuration to the MAC when starting. This allows
* a fixed-link to start with the correct parameters, and also
* ensures that we set the appropriate advertisement for Serdes links.
......@@ -1997,6 +2081,8 @@ void phylink_start(struct phylink *pl)
*/
phylink_mac_initial_config(pl, true);
pl->pcs_state = PCS_STATE_STARTED;
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
......@@ -2015,15 +2101,9 @@ void phylink_start(struct phylink *pl)
poll = true;
}
switch (pl->cfg_link_an_mode) {
case MLO_AN_FIXED:
if (pl->cfg_link_an_mode == MLO_AN_FIXED)
poll |= pl->config->poll_fixed_state;
break;
case MLO_AN_INBAND:
if (pl->pcs)
poll |= pl->pcs->poll;
break;
}
if (poll)
mod_timer(&pl->link_poll, jiffies + HZ);
if (pl->phydev)
......@@ -2060,6 +2140,10 @@ void phylink_stop(struct phylink *pl)
}
phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_STOPPED);
pl->pcs_state = PCS_STATE_DOWN;
phylink_pcs_disable(pl->pcs);
}
EXPORT_SYMBOL_GPL(phylink_stop);
......
......@@ -537,6 +537,8 @@ static inline void mii_c73_mod_linkmode(unsigned long *adv, u16 *lpa)
int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum);
int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val);
int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
u16 set);
int __mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
u16 mask, u16 set);
......@@ -564,6 +566,30 @@ int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad,
u32 regnum, u16 mask, u16 set);
static inline int __mdiodev_read(struct mdio_device *mdiodev, u32 regnum)
{
return __mdiobus_read(mdiodev->bus, mdiodev->addr, regnum);
}
static inline int __mdiodev_write(struct mdio_device *mdiodev, u32 regnum,
u16 val)
{
return __mdiobus_write(mdiodev->bus, mdiodev->addr, regnum, val);
}
static inline int __mdiodev_modify(struct mdio_device *mdiodev, u32 regnum,
u16 mask, u16 set)
{
return __mdiobus_modify(mdiodev->bus, mdiodev->addr, regnum, mask, set);
}
static inline int __mdiodev_modify_changed(struct mdio_device *mdiodev,
u32 regnum, u16 mask, u16 set)
{
return __mdiobus_modify_changed(mdiodev->bus, mdiodev->addr, regnum,
mask, set);
}
static inline int mdiodev_read(struct mdio_device *mdiodev, u32 regnum)
{
return mdiobus_read(mdiodev->bus, mdiodev->addr, regnum);
......
......@@ -9,6 +9,7 @@ struct device_node;
struct ethtool_cmd;
struct fwnode_handle;
struct net_device;
struct phylink;
enum {
MLO_PAUSE_NONE,
......@@ -520,14 +521,19 @@ struct phylink_pcs_ops;
/**
* struct phylink_pcs - PHYLINK PCS instance
* @ops: a pointer to the &struct phylink_pcs_ops structure
* @phylink: pointer to &struct phylink_config
* @neg_mode: provide PCS neg mode via "mode" argument
* @poll: poll the PCS for link changes
*
* This structure is designed to be embedded within the PCS private data,
* and will be passed between phylink and the PCS.
*
* The @phylink member is private to phylink and must not be touched by
* the PCS driver.
*/
struct phylink_pcs {
const struct phylink_pcs_ops *ops;
struct phylink *phylink;
bool neg_mode;
bool poll;
};
......@@ -535,6 +541,10 @@ struct phylink_pcs {
/**
* struct phylink_pcs_ops - MAC PCS operations structure.
* @pcs_validate: validate the link configuration.
* @pcs_enable: enable the PCS.
* @pcs_disable: disable the PCS.
* @pcs_pre_config: pre-mac_config method (for errata)
* @pcs_post_config: post-mac_config method (for arrata)
* @pcs_get_state: read the current MAC PCS link state from the hardware.
* @pcs_config: configure the MAC PCS for the selected mode and state.
* @pcs_an_restart: restart 802.3z BaseX autonegotiation.
......@@ -544,6 +554,12 @@ struct phylink_pcs {
struct phylink_pcs_ops {
int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state);
int (*pcs_enable)(struct phylink_pcs *pcs);
void (*pcs_disable)(struct phylink_pcs *pcs);
void (*pcs_pre_config)(struct phylink_pcs *pcs,
phy_interface_t interface);
int (*pcs_post_config)(struct phylink_pcs *pcs,
phy_interface_t interface);
void (*pcs_get_state)(struct phylink_pcs *pcs,
struct phylink_link_state *state);
int (*pcs_config)(struct phylink_pcs *pcs, unsigned int neg_mode,
......@@ -573,6 +589,18 @@ struct phylink_pcs_ops {
int pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state);
/**
* pcs_enable() - enable the PCS.
* @pcs: a pointer to a &struct phylink_pcs.
*/
int pcs_enable(struct phylink_pcs *pcs);
/**
* pcs_disable() - disable the PCS.
* @pcs: a pointer to a &struct phylink_pcs.
*/
void pcs_disable(struct phylink_pcs *pcs);
/**
* pcs_get_state() - Read the current inband link state from the hardware
* @pcs: a pointer to a &struct phylink_pcs.
......@@ -677,6 +705,7 @@ int phylink_fwnode_phy_connect(struct phylink *pl,
void phylink_disconnect_phy(struct phylink *);
void phylink_mac_change(struct phylink *, bool up);
void phylink_pcs_change(struct phylink_pcs *, bool up);
void phylink_start(struct phylink *);
void phylink_stop(struct phylink *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment