diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile index 6edd869c8d6f5..5cd5551461e3c 100644 --- a/drivers/net/dsa/mv88e6xxx/Makefile +++ b/drivers/net/dsa/mv88e6xxx/Makefile @@ -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 diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 41de250dbcc36..c2f38f6770aad 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -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, ®); + 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, diff --git a/drivers/net/dsa/mv88e6xxx/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx/mv88e6xxx.h index 77236cd72df25..9087cb009cc36 100644 --- a/drivers/net/dsa/mv88e6xxx/mv88e6xxx.h +++ b/drivers/net/dsa/mv88e6xxx/mv88e6xxx.h @@ -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 diff --git a/drivers/net/dsa/mv88e6xxx/phy.c b/drivers/net/dsa/mv88e6xxx/phy.c new file mode 100644 index 0000000000000..0d3e8aaedf506 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/phy.c @@ -0,0 +1,243 @@ +/* + * Marvell 88e6xxx Ethernet switch PHY and PPU support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2017 Andrew Lunn + * + * 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 +#include +#include + +#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); +} diff --git a/drivers/net/dsa/mv88e6xxx/phy.h b/drivers/net/dsa/mv88e6xxx/phy.h new file mode 100644 index 0000000000000..0961d781b7266 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/phy.h @@ -0,0 +1,37 @@ +/* + * Marvell 88E6xxx PHY access + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2017 Andrew Lunn + * + * 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 */ diff --git a/drivers/net/dsa/mv88e6xxx/serdes.c b/drivers/net/dsa/mv88e6xxx/serdes.c new file mode 100644 index 0000000000000..53795676bd70b --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/serdes.c @@ -0,0 +1,229 @@ +/* + * Marvell 88E6xxx SERDES manipulation, via SMI bus + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2017 Andrew Lunn + * + * 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 + +#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; +} diff --git a/drivers/net/dsa/mv88e6xxx/serdes.h b/drivers/net/dsa/mv88e6xxx/serdes.h new file mode 100644 index 0000000000000..eb3ceaef790f4 --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/serdes.h @@ -0,0 +1,48 @@ +/* + * Marvell 88E6xxx SERDES manipulation, via SMI bus + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2016 Andrew Lunn + * + * 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