Commit 5f9e27e6 authored by David S. Miller's avatar David S. Miller

Merge branch 'mv88e6xxx-SERDES'

Andrew Lunn says:

====================
net: dsa: mv88e6xxx: Add basic SERDES support

Some of the Marvell switches are SERDES interface, which must be
powered up before packets can be passed. This is particularly true on
the 6390, where the SERDES defaults to down, probably to save power.

This series refactors the existing SERDES support for the 6352, and
adds 6390 support.

v2:

Split phy functions out into phy.[ch]
Don't add MV88E6XXX_FLAG_G1_ATU_FID back again
Move the serdes op up in mv88e6xxx_ops
Move some #defines into serdes.h
Add a mv88e6xxx_serdes_power()
Don't keep moving calls to this helper around in the code

v3:

Move more phy functions into phy.[ch]
Make mv88e6xxx_phy_page_get() and mv88e6xxx_phy_page_put static
Use the mv88e6xxx_serdes_power() helper everywhere
dev_err(...) when mv88e6xxx_serdes_power() fails
Add reviewed-by's
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 03d1da3c 04aca993
......@@ -4,4 +4,6 @@ mv88e6xxx-objs += global1.o
mv88e6xxx-objs += global1_atu.o
mv88e6xxx-objs += global1_vtu.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2.o
mv88e6xxx-objs += phy.o
mv88e6xxx-objs += port.o
mv88e6xxx-objs += serdes.o
......@@ -36,7 +36,9 @@
#include "mv88e6xxx.h"
#include "global1.h"
#include "global2.h"
#include "phy.h"
#include "port.h"
#include "serdes.h"
static void assert_reg_lock(struct mv88e6xxx_chip *chip)
{
......@@ -221,21 +223,7 @@ int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val)
return 0;
}
static int mv88e6165_phy_read(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 *val)
{
return mv88e6xxx_read(chip, addr, reg, val);
}
static int mv88e6165_phy_write(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 val)
{
return mv88e6xxx_write(chip, addr, reg, val);
}
static struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip)
struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip)
{
struct mv88e6xxx_mdio_bus *mdio_bus;
......@@ -247,106 +235,6 @@ static struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip)
return mdio_bus->bus;
}
static int mv88e6xxx_phy_read(struct mv88e6xxx_chip *chip, int phy,
int reg, u16 *val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_read)
return -EOPNOTSUPP;
return chip->info->ops->phy_read(chip, bus, addr, reg, val);
}
static int mv88e6xxx_phy_write(struct mv88e6xxx_chip *chip, int phy,
int reg, u16 val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_write)
return -EOPNOTSUPP;
return chip->info->ops->phy_write(chip, bus, addr, reg, val);
}
static int mv88e6xxx_phy_page_get(struct mv88e6xxx_chip *chip, int phy, u8 page)
{
if (!mv88e6xxx_has(chip, MV88E6XXX_FLAG_PHY_PAGE))
return -EOPNOTSUPP;
return mv88e6xxx_phy_write(chip, phy, PHY_PAGE, page);
}
static void mv88e6xxx_phy_page_put(struct mv88e6xxx_chip *chip, int phy)
{
int err;
/* Restore PHY page Copper 0x0 for access via the registered MDIO bus */
err = mv88e6xxx_phy_write(chip, phy, PHY_PAGE, PHY_PAGE_COPPER);
if (unlikely(err)) {
dev_err(chip->dev, "failed to restore PHY %d page Copper (%d)\n",
phy, err);
}
}
static int mv88e6xxx_phy_page_read(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 *val)
{
int err;
/* There is no paging for registers 22 */
if (reg == PHY_PAGE)
return -EINVAL;
err = mv88e6xxx_phy_page_get(chip, phy, page);
if (!err) {
err = mv88e6xxx_phy_read(chip, phy, reg, val);
mv88e6xxx_phy_page_put(chip, phy);
}
return err;
}
static int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 val)
{
int err;
/* There is no paging for registers 22 */
if (reg == PHY_PAGE)
return -EINVAL;
err = mv88e6xxx_phy_page_get(chip, phy, page);
if (!err) {
err = mv88e6xxx_phy_write(chip, phy, PHY_PAGE, page);
mv88e6xxx_phy_page_put(chip, phy);
}
return err;
}
static int mv88e6xxx_serdes_read(struct mv88e6xxx_chip *chip, int reg, u16 *val)
{
return mv88e6xxx_phy_page_read(chip, ADDR_SERDES, SERDES_PAGE_FIBER,
reg, val);
}
static int mv88e6xxx_serdes_write(struct mv88e6xxx_chip *chip, int reg, u16 val)
{
return mv88e6xxx_phy_page_write(chip, ADDR_SERDES, SERDES_PAGE_FIBER,
reg, val);
}
static void mv88e6xxx_g1_irq_mask(struct irq_data *d)
{
struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d);
......@@ -560,122 +448,6 @@ int mv88e6xxx_update(struct mv88e6xxx_chip *chip, int addr, int reg, u16 update)
return mv88e6xxx_write(chip, addr, reg, val);
}
static int mv88e6xxx_ppu_disable(struct mv88e6xxx_chip *chip)
{
if (!chip->info->ops->ppu_disable)
return 0;
return chip->info->ops->ppu_disable(chip);
}
static int mv88e6xxx_ppu_enable(struct mv88e6xxx_chip *chip)
{
if (!chip->info->ops->ppu_enable)
return 0;
return chip->info->ops->ppu_enable(chip);
}
static void mv88e6xxx_ppu_reenable_work(struct work_struct *ugly)
{
struct mv88e6xxx_chip *chip;
chip = container_of(ugly, struct mv88e6xxx_chip, ppu_work);
mutex_lock(&chip->reg_lock);
if (mutex_trylock(&chip->ppu_mutex)) {
if (mv88e6xxx_ppu_enable(chip) == 0)
chip->ppu_disabled = 0;
mutex_unlock(&chip->ppu_mutex);
}
mutex_unlock(&chip->reg_lock);
}
static void mv88e6xxx_ppu_reenable_timer(unsigned long _ps)
{
struct mv88e6xxx_chip *chip = (void *)_ps;
schedule_work(&chip->ppu_work);
}
static int mv88e6xxx_ppu_access_get(struct mv88e6xxx_chip *chip)
{
int ret;
mutex_lock(&chip->ppu_mutex);
/* If the PHY polling unit is enabled, disable it so that
* we can access the PHY registers. If it was already
* disabled, cancel the timer that is going to re-enable
* it.
*/
if (!chip->ppu_disabled) {
ret = mv88e6xxx_ppu_disable(chip);
if (ret < 0) {
mutex_unlock(&chip->ppu_mutex);
return ret;
}
chip->ppu_disabled = 1;
} else {
del_timer(&chip->ppu_timer);
ret = 0;
}
return ret;
}
static void mv88e6xxx_ppu_access_put(struct mv88e6xxx_chip *chip)
{
/* Schedule a timer to re-enable the PHY polling unit. */
mod_timer(&chip->ppu_timer, jiffies + msecs_to_jiffies(10));
mutex_unlock(&chip->ppu_mutex);
}
static void mv88e6xxx_ppu_state_init(struct mv88e6xxx_chip *chip)
{
mutex_init(&chip->ppu_mutex);
INIT_WORK(&chip->ppu_work, mv88e6xxx_ppu_reenable_work);
setup_timer(&chip->ppu_timer, mv88e6xxx_ppu_reenable_timer,
(unsigned long)chip);
}
static void mv88e6xxx_ppu_state_destroy(struct mv88e6xxx_chip *chip)
{
del_timer_sync(&chip->ppu_timer);
}
static int mv88e6xxx_phy_ppu_read(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 *val)
{
int err;
err = mv88e6xxx_ppu_access_get(chip);
if (!err) {
err = mv88e6xxx_read(chip, addr, reg, val);
mv88e6xxx_ppu_access_put(chip);
}
return err;
}
static int mv88e6xxx_phy_ppu_write(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 val)
{
int err;
err = mv88e6xxx_ppu_access_get(chip);
if (!err) {
err = mv88e6xxx_write(chip, addr, reg, val);
mv88e6xxx_ppu_access_put(chip);
}
return err;
}
static int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port,
int link, int speed, int duplex,
phy_interface_t mode)
......@@ -1950,24 +1722,6 @@ static int mv88e6xxx_switch_reset(struct mv88e6xxx_chip *chip)
return mv88e6xxx_software_reset(chip);
}
static int mv88e6xxx_serdes_power_on(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
/* Clear Power Down bit */
err = mv88e6xxx_serdes_read(chip, MII_BMCR, &val);
if (err)
return err;
if (val & BMCR_PDOWN) {
val &= ~BMCR_PDOWN;
err = mv88e6xxx_serdes_write(chip, MII_BMCR, val);
}
return err;
}
static int mv88e6xxx_set_port_mode(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_frame_mode frame, u16 egress,
u16 etype)
......@@ -2049,6 +1803,21 @@ static int mv88e6xxx_setup_egress_floods(struct mv88e6xxx_chip *chip, int port)
return 0;
}
static int mv88e6xxx_serdes_power(struct mv88e6xxx_chip *chip, int port,
bool on)
{
int err = 0;
if (chip->info->ops->serdes_power) {
err = chip->info->ops->serdes_power(chip, port, on);
if (err)
dev_err(chip->dev,
"Failed to change SERDES power: %d\n", err);
}
return err;
}
static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
{
struct dsa_switch *ds = chip->ds;
......@@ -2099,21 +1868,14 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
if (err)
return err;
/* If this port is connected to a SerDes, make sure the SerDes is not
* powered down.
/* Enable the SERDES interface for DSA and CPU ports. Normal
* ports SERDES are enabled when the port is enabled, thus
* saving a bit of power.
*/
if (mv88e6xxx_has(chip, MV88E6XXX_FLAGS_SERDES)) {
err = mv88e6xxx_port_read(chip, port, PORT_STATUS, &reg);
if ((dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))) {
err = mv88e6xxx_serdes_power(chip, port, true);
if (err)
return err;
reg &= PORT_STATUS_CMODE_MASK;
if ((reg == PORT_STATUS_CMODE_100BASE_X) ||
(reg == PORT_STATUS_CMODE_1000BASE_X) ||
(reg == PORT_STATUS_CMODE_SGMII)) {
err = mv88e6xxx_serdes_power_on(chip);
if (err < 0)
return err;
}
}
/* Port Control 2: don't force a good FCS, set the maximum frame size to
......@@ -2216,6 +1978,29 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
return mv88e6xxx_port_write(chip, port, PORT_DEFAULT_VLAN, 0x0000);
}
static int mv88e6xxx_port_enable(struct dsa_switch *ds, int port,
struct phy_device *phydev)
{
struct mv88e6xxx_chip *chip = ds->priv;
int err = 0;
mutex_lock(&chip->reg_lock);
mv88e6xxx_serdes_power(chip, port, true);
mutex_unlock(&chip->reg_lock);
return err;
}
static void mv88e6xxx_port_disable(struct dsa_switch *ds, int port,
struct phy_device *phydev)
{
struct mv88e6xxx_chip *chip = ds->priv;
mutex_lock(&chip->reg_lock);
mv88e6xxx_serdes_power(chip, port, false);
mutex_unlock(&chip->reg_lock);
}
static int mv88e6xxx_g1_set_switch_mac(struct mv88e6xxx_chip *chip, u8 *addr)
{
int err;
......@@ -2879,6 +2664,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.serdes_power = mv88e6352_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6175_ops = {
......@@ -2943,6 +2729,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.serdes_power = mv88e6352_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6185_ops = {
......@@ -3002,6 +2789,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6190x_ops = {
......@@ -3034,6 +2822,7 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6191_ops = {
......@@ -3066,6 +2855,7 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6240_ops = {
......@@ -3099,6 +2889,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.serdes_power = mv88e6352_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6290_ops = {
......@@ -3132,6 +2923,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6320_ops = {
......@@ -3321,6 +3113,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.serdes_power = mv88e6352_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6390_ops = {
......@@ -3356,6 +3149,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
};
static const struct mv88e6xxx_ops mv88e6390x_ops = {
......@@ -3390,6 +3184,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
};
static const struct mv88e6xxx_info mv88e6xxx_table[] = {
......@@ -3914,18 +3709,6 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev)
return chip;
}
static void mv88e6xxx_phy_init(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable)
mv88e6xxx_ppu_state_init(chip);
}
static void mv88e6xxx_phy_destroy(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable)
mv88e6xxx_ppu_state_destroy(chip);
}
static int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip,
struct mii_bus *bus, int sw_addr)
{
......@@ -4058,6 +3841,8 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
.get_strings = mv88e6xxx_get_strings,
.get_ethtool_stats = mv88e6xxx_get_ethtool_stats,
.get_sset_count = mv88e6xxx_get_sset_count,
.port_enable = mv88e6xxx_port_enable,
.port_disable = mv88e6xxx_port_disable,
.set_eee = mv88e6xxx_set_eee,
.get_eee = mv88e6xxx_get_eee,
.get_eeprom_len = mv88e6xxx_get_eeprom_len,
......
......@@ -37,9 +37,6 @@
#define PHY_PAGE 0x16
#define PHY_PAGE_COPPER 0x00
#define ADDR_SERDES 0x0f
#define SERDES_PAGE_FIBER 0x01
#define PORT_STATUS 0x00
#define PORT_STATUS_PAUSE_EN BIT(15)
#define PORT_STATUS_MY_PAUSE BIT(14)
......@@ -511,14 +508,6 @@ enum mv88e6xxx_cap {
MV88E6XXX_CAP_SMI_CMD, /* (0x00) SMI Command */
MV88E6XXX_CAP_SMI_DATA, /* (0x01) SMI Data */
/* PHY Registers.
*/
MV88E6XXX_CAP_PHY_PAGE, /* (0x16) Page Register */
/* Fiber/SERDES Registers (SMI address F).
*/
MV88E6XXX_CAP_SERDES,
/* Switch Global (1) Registers.
*/
MV88E6XXX_CAP_G1_ATU_FID, /* (0x01) ATU FID Register */
......@@ -553,10 +542,6 @@ enum mv88e6xxx_cap {
#define MV88E6XXX_FLAG_SMI_CMD BIT_ULL(MV88E6XXX_CAP_SMI_CMD)
#define MV88E6XXX_FLAG_SMI_DATA BIT_ULL(MV88E6XXX_CAP_SMI_DATA)
#define MV88E6XXX_FLAG_PHY_PAGE BIT_ULL(MV88E6XXX_CAP_PHY_PAGE)
#define MV88E6XXX_FLAG_SERDES BIT_ULL(MV88E6XXX_CAP_SERDES)
#define MV88E6XXX_FLAG_G1_VTU_FID BIT_ULL(MV88E6XXX_CAP_G1_VTU_FID)
#define MV88E6XXX_FLAG_GLOBAL2 BIT_ULL(MV88E6XXX_CAP_GLOBAL2)
......@@ -577,11 +562,6 @@ enum mv88e6xxx_cap {
(MV88E6XXX_FLAG_SMI_CMD | \
MV88E6XXX_FLAG_SMI_DATA)
/* Fiber/SERDES Registers at SMI address F, page 1 */
#define MV88E6XXX_FLAGS_SERDES \
(MV88E6XXX_FLAG_PHY_PAGE | \
MV88E6XXX_FLAG_SERDES)
#define MV88E6XXX_FLAGS_FAMILY_6095 \
(MV88E6XXX_FLAG_GLOBAL2 | \
MV88E6XXX_FLAG_G2_MGMT_EN_0X | \
......@@ -629,8 +609,7 @@ enum mv88e6xxx_cap {
MV88E6XXX_FLAG_G2_INT | \
MV88E6XXX_FLAG_G2_POT | \
MV88E6XXX_FLAGS_IRL | \
MV88E6XXX_FLAGS_MULTI_CHIP | \
MV88E6XXX_FLAGS_SERDES)
MV88E6XXX_FLAGS_MULTI_CHIP)
#define MV88E6XXX_FLAGS_FAMILY_6351 \
(MV88E6XXX_FLAG_G1_VTU_FID | \
......@@ -651,8 +630,7 @@ enum mv88e6xxx_cap {
MV88E6XXX_FLAG_G2_MGMT_EN_0X | \
MV88E6XXX_FLAG_G2_POT | \
MV88E6XXX_FLAGS_IRL | \
MV88E6XXX_FLAGS_MULTI_CHIP | \
MV88E6XXX_FLAGS_SERDES)
MV88E6XXX_FLAGS_MULTI_CHIP)
#define MV88E6XXX_FLAGS_FAMILY_6390 \
(MV88E6XXX_FLAG_EEE | \
......@@ -884,6 +862,9 @@ struct mv88e6xxx_ops {
/* Can be either in g1 or g2, so don't use a prefix */
int (*mgmt_rsvd2cpu)(struct mv88e6xxx_chip *chip);
/* Power on/off a SERDES interface */
int (*serdes_power)(struct mv88e6xxx_chip *chip, int port, bool on);
/* VLAN Translation Unit operations */
int (*vtu_getnext)(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
......@@ -942,5 +923,5 @@ int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val);
int mv88e6xxx_update(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 update);
int mv88e6xxx_wait(struct mv88e6xxx_chip *chip, int addr, int reg, u16 mask);
struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip);
#endif
/*
* Marvell 88e6xxx Ethernet switch PHY and PPU support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/mdio.h>
#include <linux/module.h>
#include <net/dsa.h>
#include "mv88e6xxx.h"
#include "phy.h"
int mv88e6165_phy_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val)
{
return mv88e6xxx_read(chip, addr, reg, val);
}
int mv88e6165_phy_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val)
{
return mv88e6xxx_write(chip, addr, reg, val);
}
int mv88e6xxx_phy_read(struct mv88e6xxx_chip *chip, int phy, int reg, u16 *val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_read)
return -EOPNOTSUPP;
return chip->info->ops->phy_read(chip, bus, addr, reg, val);
}
int mv88e6xxx_phy_write(struct mv88e6xxx_chip *chip, int phy, int reg, u16 val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_write)
return -EOPNOTSUPP;
return chip->info->ops->phy_write(chip, bus, addr, reg, val);
}
static int mv88e6xxx_phy_page_get(struct mv88e6xxx_chip *chip, int phy, u8 page)
{
return mv88e6xxx_phy_write(chip, phy, PHY_PAGE, page);
}
static void mv88e6xxx_phy_page_put(struct mv88e6xxx_chip *chip, int phy)
{
int err;
/* Restore PHY page Copper 0x0 for access via the registered
* MDIO bus
*/
err = mv88e6xxx_phy_write(chip, phy, PHY_PAGE, PHY_PAGE_COPPER);
if (unlikely(err)) {
dev_err(chip->dev,
"failed to restore PHY %d page Copper (%d)\n",
phy, err);
}
}
int mv88e6xxx_phy_page_read(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 *val)
{
int err;
/* There is no paging for registers 22 */
if (reg == PHY_PAGE)
return -EINVAL;
err = mv88e6xxx_phy_page_get(chip, phy, page);
if (!err) {
err = mv88e6xxx_phy_read(chip, phy, reg, val);
mv88e6xxx_phy_page_put(chip, phy);
}
return err;
}
int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 val)
{
int err;
/* There is no paging for registers 22 */
if (reg == PHY_PAGE)
return -EINVAL;
err = mv88e6xxx_phy_page_get(chip, phy, page);
if (!err) {
err = mv88e6xxx_phy_write(chip, phy, PHY_PAGE, page);
mv88e6xxx_phy_page_put(chip, phy);
}
return err;
}
static int mv88e6xxx_ppu_disable(struct mv88e6xxx_chip *chip)
{
if (!chip->info->ops->ppu_disable)
return 0;
return chip->info->ops->ppu_disable(chip);
}
int mv88e6xxx_ppu_enable(struct mv88e6xxx_chip *chip)
{
if (!chip->info->ops->ppu_enable)
return 0;
return chip->info->ops->ppu_enable(chip);
}
static void mv88e6xxx_ppu_reenable_work(struct work_struct *ugly)
{
struct mv88e6xxx_chip *chip;
chip = container_of(ugly, struct mv88e6xxx_chip, ppu_work);
mutex_lock(&chip->reg_lock);
if (mutex_trylock(&chip->ppu_mutex)) {
if (mv88e6xxx_ppu_enable(chip) == 0)
chip->ppu_disabled = 0;
mutex_unlock(&chip->ppu_mutex);
}
mutex_unlock(&chip->reg_lock);
}
static void mv88e6xxx_ppu_reenable_timer(unsigned long _ps)
{
struct mv88e6xxx_chip *chip = (void *)_ps;
schedule_work(&chip->ppu_work);
}
static int mv88e6xxx_ppu_access_get(struct mv88e6xxx_chip *chip)
{
int ret;
mutex_lock(&chip->ppu_mutex);
/* If the PHY polling unit is enabled, disable it so that
* we can access the PHY registers. If it was already
* disabled, cancel the timer that is going to re-enable
* it.
*/
if (!chip->ppu_disabled) {
ret = mv88e6xxx_ppu_disable(chip);
if (ret < 0) {
mutex_unlock(&chip->ppu_mutex);
return ret;
}
chip->ppu_disabled = 1;
} else {
del_timer(&chip->ppu_timer);
ret = 0;
}
return ret;
}
static void mv88e6xxx_ppu_access_put(struct mv88e6xxx_chip *chip)
{
/* Schedule a timer to re-enable the PHY polling unit. */
mod_timer(&chip->ppu_timer, jiffies + msecs_to_jiffies(10));
mutex_unlock(&chip->ppu_mutex);
}
static void mv88e6xxx_ppu_state_init(struct mv88e6xxx_chip *chip)
{
mutex_init(&chip->ppu_mutex);
INIT_WORK(&chip->ppu_work, mv88e6xxx_ppu_reenable_work);
setup_timer(&chip->ppu_timer, mv88e6xxx_ppu_reenable_timer,
(unsigned long)chip);
}
static void mv88e6xxx_ppu_state_destroy(struct mv88e6xxx_chip *chip)
{
del_timer_sync(&chip->ppu_timer);
}
int mv88e6xxx_phy_ppu_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val)
{
int err;
err = mv88e6xxx_ppu_access_get(chip);
if (!err) {
err = mv88e6xxx_read(chip, addr, reg, val);
mv88e6xxx_ppu_access_put(chip);
}
return err;
}
int mv88e6xxx_phy_ppu_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val)
{
int err;
err = mv88e6xxx_ppu_access_get(chip);
if (!err) {
err = mv88e6xxx_write(chip, addr, reg, val);
mv88e6xxx_ppu_access_put(chip);
}
return err;
}
void mv88e6xxx_phy_init(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable)
mv88e6xxx_ppu_state_init(chip);
}
void mv88e6xxx_phy_destroy(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable)
mv88e6xxx_ppu_state_destroy(chip);
}
/*
* Marvell 88E6xxx PHY access
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef _MV88E6XXX_PHY_H
#define _MV88E6XXX_PHY_H
int mv88e6165_phy_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val);
int mv88e6165_phy_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val);
int mv88e6xxx_phy_read(struct mv88e6xxx_chip *chip, int phy,
int reg, u16 *val);
int mv88e6xxx_phy_write(struct mv88e6xxx_chip *chip, int phy,
int reg, u16 val);
int mv88e6xxx_phy_page_read(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 *val);
int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 val);
int mv88e6xxx_phy_ppu_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val);
int mv88e6xxx_phy_ppu_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val);
int mv88e6xxx_ppu_enable(struct mv88e6xxx_chip *chip);
void mv88e6xxx_phy_init(struct mv88e6xxx_chip *chip);
void mv88e6xxx_phy_destroy(struct mv88e6xxx_chip *chip);
#endif /*_MV88E6XXX_PHY_H */
/*
* Marvell 88E6xxx SERDES manipulation, via SMI bus
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/mii.h>
#include "global2.h"
#include "mv88e6xxx.h"
#include "phy.h"
#include "port.h"
#include "serdes.h"
static int mv88e6352_serdes_read(struct mv88e6xxx_chip *chip, int reg,
u16 *val)
{
return mv88e6xxx_phy_page_read(chip, MV88E6352_ADDR_SERDES,
MV88E6352_SERDES_PAGE_FIBER,
reg, val);
}
static int mv88e6352_serdes_write(struct mv88e6xxx_chip *chip, int reg,
u16 val)
{
return mv88e6xxx_phy_page_write(chip, MV88E6352_ADDR_SERDES,
MV88E6352_SERDES_PAGE_FIBER,
reg, val);
}
static int mv88e6352_serdes_power_set(struct mv88e6xxx_chip *chip, bool on)
{
u16 val, new_val;
int err;
err = mv88e6352_serdes_read(chip, MII_BMCR, &val);
if (err)
return err;
if (on)
new_val = val & ~BMCR_PDOWN;
else
new_val = val | BMCR_PDOWN;
if (val != new_val)
err = mv88e6352_serdes_write(chip, MII_BMCR, new_val);
return err;
}
int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
int err;
u8 cmode;
err = mv88e6xxx_port_get_cmode(chip, port, &cmode);
if (err)
return err;
if ((cmode == PORT_STATUS_CMODE_100BASE_X) ||
(cmode == PORT_STATUS_CMODE_1000BASE_X) ||
(cmode == PORT_STATUS_CMODE_SGMII)) {
err = mv88e6352_serdes_power_set(chip, on);
if (err < 0)
return err;
}
return 0;
}
/* Set the power on/off for 10GBASE-R and 10GBASE-X4/X2 */
static int mv88e6390_serdes_10g(struct mv88e6xxx_chip *chip, int addr, bool on)
{
u16 val, new_val;
int reg_c45;
int err;
reg_c45 = MII_ADDR_C45 | MV88E6390_SERDES_DEVICE |
MV88E6390_PCS_CONTROL_1;
err = mv88e6xxx_phy_read(chip, addr, reg_c45, &val);
if (err)
return err;
if (on)
new_val = val & ~(MV88E6390_PCS_CONTROL_1_RESET |
MV88E6390_PCS_CONTROL_1_LOOPBACK |
MV88E6390_PCS_CONTROL_1_PDOWN);
else
new_val = val | MV88E6390_PCS_CONTROL_1_PDOWN;
if (val != new_val)
err = mv88e6xxx_phy_write(chip, addr, reg_c45, new_val);
return err;
}
/* Set the power on/off for 10GBASE-R and 10GBASE-X4/X2 */
static int mv88e6390_serdes_sgmii(struct mv88e6xxx_chip *chip, int addr,
bool on)
{
u16 val, new_val;
int reg_c45;
int err;
reg_c45 = MII_ADDR_C45 | MV88E6390_SERDES_DEVICE |
MV88E6390_SGMII_CONTROL;
err = mv88e6xxx_phy_read(chip, addr, reg_c45, &val);
if (err)
return err;
if (on)
new_val = val & ~(MV88E6390_SGMII_CONTROL_RESET |
MV88E6390_SGMII_CONTROL_LOOPBACK |
MV88E6390_SGMII_CONTROL_PDOWN);
else
new_val = val | MV88E6390_SGMII_CONTROL_PDOWN;
if (val != new_val)
err = mv88e6xxx_phy_write(chip, addr, reg_c45, new_val);
return err;
}
static int mv88e6390_serdes_lower(struct mv88e6xxx_chip *chip, u8 cmode,
int port_donor, int lane, bool rxaui, bool on)
{
int err;
u8 cmode_donor;
err = mv88e6xxx_port_get_cmode(chip, port_donor, &cmode_donor);
if (err)
return err;
switch (cmode_donor) {
case PORT_STATUS_CMODE_RXAUI:
if (!rxaui)
break;
/* Fall through */
case PORT_STATUS_CMODE_1000BASE_X:
case PORT_STATUS_CMODE_SGMII:
case PORT_STATUS_CMODE_2500BASEX:
if (cmode == PORT_STATUS_CMODE_1000BASE_X ||
cmode == PORT_STATUS_CMODE_SGMII)
return mv88e6390_serdes_sgmii(chip, lane, on);
}
return 0;
}
static int mv88e6390_serdes_port9(struct mv88e6xxx_chip *chip, u8 cmode,
bool on)
{
switch (cmode) {
case PORT_STATUS_CMODE_1000BASE_X:
case PORT_STATUS_CMODE_SGMII:
return mv88e6390_serdes_sgmii(chip, MV88E6390_PORT9_LANE0, on);
case PORT_STATUS_CMODE_XAUI:
case PORT_STATUS_CMODE_RXAUI:
case PORT_STATUS_CMODE_2500BASEX:
return mv88e6390_serdes_10g(chip, MV88E6390_PORT9_LANE0, on);
}
return 0;
}
static int mv88e6390_serdes_port10(struct mv88e6xxx_chip *chip, u8 cmode,
bool on)
{
switch (cmode) {
case PORT_STATUS_CMODE_SGMII:
return mv88e6390_serdes_sgmii(chip, MV88E6390_PORT10_LANE0, on);
case PORT_STATUS_CMODE_XAUI:
case PORT_STATUS_CMODE_RXAUI:
case PORT_STATUS_CMODE_1000BASE_X:
case PORT_STATUS_CMODE_2500BASEX:
return mv88e6390_serdes_10g(chip, MV88E6390_PORT10_LANE0, on);
}
return 0;
}
int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
u8 cmode;
int err;
err = mv88e6xxx_port_get_cmode(chip, port, &cmode);
if (err)
return cmode;
switch (port) {
case 2:
return mv88e6390_serdes_lower(chip, cmode, 9,
MV88E6390_PORT9_LANE1,
false, on);
case 3:
return mv88e6390_serdes_lower(chip, cmode, 9,
MV88E6390_PORT9_LANE2,
true, on);
case 4:
return mv88e6390_serdes_lower(chip, cmode, 9,
MV88E6390_PORT9_LANE3,
true, on);
case 5:
return mv88e6390_serdes_lower(chip, cmode, 10,
MV88E6390_PORT10_LANE1,
false, on);
case 6:
return mv88e6390_serdes_lower(chip, cmode, 10,
MV88E6390_PORT10_LANE2,
true, on);
case 7:
return mv88e6390_serdes_lower(chip, cmode, 10,
MV88E6390_PORT10_LANE3,
true, on);
case 9:
return mv88e6390_serdes_port9(chip, cmode, on);
case 10:
return mv88e6390_serdes_port10(chip, cmode, on);
}
return 0;
}
/*
* Marvell 88E6xxx SERDES manipulation, via SMI bus
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef _MV88E6XXX_SERDES_H
#define _MV88E6XXX_SERDES_H
#include "mv88e6xxx.h"
#define MV88E6352_ADDR_SERDES 0x0f
#define MV88E6352_SERDES_PAGE_FIBER 0x01
#define MV88E6390_PORT9_LANE0 0x09
#define MV88E6390_PORT9_LANE1 0x12
#define MV88E6390_PORT9_LANE2 0x13
#define MV88E6390_PORT9_LANE3 0x14
#define MV88E6390_PORT10_LANE0 0x0a
#define MV88E6390_PORT10_LANE1 0x15
#define MV88E6390_PORT10_LANE2 0x16
#define MV88E6390_PORT10_LANE3 0x17
#define MV88E6390_SERDES_DEVICE (4 << 16)
/* 10GBASE-R and 10GBASE-X4/X2 */
#define MV88E6390_PCS_CONTROL_1 0x1000
#define MV88E6390_PCS_CONTROL_1_RESET BIT(15)
#define MV88E6390_PCS_CONTROL_1_LOOPBACK BIT(14)
#define MV88E6390_PCS_CONTROL_1_SPEED BIT(13)
#define MV88E6390_PCS_CONTROL_1_PDOWN BIT(11)
/* 1000BASE-X and SGMII */
#define MV88E6390_SGMII_CONTROL 0x2000
#define MV88E6390_SGMII_CONTROL_RESET BIT(15)
#define MV88E6390_SGMII_CONTROL_LOOPBACK BIT(14)
#define MV88E6390_SGMII_CONTROL_PDOWN BIT(11)
int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
#endif
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