From e557696bfd7735a4793d70cb18b04318f35b0a91 Mon Sep 17 00:00:00 2001 From: Lakesh Sharma Date: Tue, 9 Dec 2014 10:25:28 -0800 Subject: [PATCH] Additional Broadcom PHY support Original Author: Copyright (C) 2014 David Yen SPDX-License-Identifier: GPL-2.0 --- drivers/net/phy/broadcom.c | 204 ++++++++++++++++++++++++++++++++++++++++++++- include/linux/brcmphy.h | 1 + 2 files changed, 204 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index f8c90ea..612f6c5 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -17,7 +17,7 @@ #include #include #include - +#include #define BRCM_PHY_MODEL(phydev) \ ((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask) @@ -25,6 +25,8 @@ #define BRCM_PHY_REV(phydev) \ ((phydev)->drv->phy_id & ~((phydev)->drv->phy_id_mask)) +#define MII_BCM54XX_CR 0x00 /* BCM54xx control register */ +#define MII_BCM54XX_CR_RESET 0x8000 /* Reset */ #define MII_BCM54XX_ECR 0x10 /* BCM54xx extended control register */ #define MII_BCM54XX_ECR_IM 0x1000 /* Interrupt mask */ @@ -39,6 +41,7 @@ #define MII_BCM54XX_EXP_SEL_ER 0x0f00 /* Expansion register select */ #define MII_BCM54XX_AUX_CTL 0x18 /* Auxiliary control register */ +#define MII_BCM54XX_AUX_CTL_ENCODE(val) (((val & 0x7) << 12)|(val & 0x7)) #define MII_BCM54XX_ISR 0x1a /* BCM54xx interrupt status register */ #define MII_BCM54XX_IMR 0x1b /* BCM54xx interrupt mask register */ #define MII_BCM54XX_INT_CRCERR 0x0001 /* CRC error */ @@ -57,10 +60,21 @@ #define MII_BCM54XX_INT_MDIX 0x2000 /* MDIX status change */ #define MII_BCM54XX_INT_PSERR 0x4000 /* Pair swap error */ +#define MII_BCM54XX_AUX_STATUS 0x19 /* Auxiliary status */ +#define MII_BCM54XX_AUX_STATUS_LINKMODE_MASK 0x0700 +#define MII_BCM54XX_AUX_STATUS_LINKMODE_SHIFT 8 + #define MII_BCM54XX_SHD 0x1c /* 0x1c shadow registers */ #define MII_BCM54XX_SHD_WRITE 0x8000 #define MII_BCM54XX_SHD_VAL(x) ((x & 0x1f) << 10) #define MII_BCM54XX_SHD_DATA(x) ((x & 0x3ff) << 0) +#define MII_BCM54XX_AUX_STATUS 0x19 /* Auxiliary status */ +#define MII_BCM54XX_AUX_STATUS_LINKMODE_MASK 0x0700 +#define MII_BCM54XX_AUX_STATUS_LINKMODE_SHIFT 8 +#define MII_BCM54XX_SHD_WR_ENCODE(val, data) \ + (MII_BCM54XX_SHD_WRITE | MII_BCM54XX_SHD_VAL(val) | \ + MII_BCM54XX_SHD_DATA(data)) + /* * AUXILIARY CONTROL SHADOW ACCESS REGISTERS. (PHY REG 0x18) @@ -418,6 +432,31 @@ static int bcm54xx_config_init(struct phy_device *phydev) return 0; } +static int bcm54616_config_init(struct phy_device *phydev) +{ + int reg; + + /* reset the PHY */ + reg = phy_read(phydev, MII_BCM54XX_CR); + reg |= MII_BCM54XX_CR_RESET; + phy_write(phydev, MII_BCM54XX_CR, reg); + + /* Setup read from auxilary control shadow register 7 */ + phy_write(phydev, MII_BCM54XX_AUX_CTL, + MII_BCM54XX_AUX_CTL_ENCODE(7)); + /* Read Misc Control register */ + reg = (phy_read(phydev, MII_BCM54XX_AUX_CTL) & 0x8FFF) | 0x8010; + phy_write(phydev, MII_BCM54XX_AUX_CTL, reg); + + /* Enable copper/fiber auto-detect */ + phy_write(phydev, MII_BCM54XX_SHD, + MII_BCM54XX_SHD_WR_ENCODE(0x1e, 0x027)); + + genphy_config_aneg(phydev); + + return 0; +} + static int bcm5482_config_init(struct phy_device *phydev) { int err, reg; @@ -508,6 +547,126 @@ static int bcm5482_read_status(struct phy_device *phydev) return err; } +/* + * Find out if PHY is in copper or serdes mode by looking at Shadow Reg + * 0x1F - "Mode Control Register" + */ +static int bcm54616_is_serdes(struct phy_device *phydev) +{ + u16 val; + + phy_write(phydev, MII_BCM54XX_SHD, + MII_BCM54XX_SHD_VAL(0x1F)); + val = phy_read(phydev, MII_BCM54XX_SHD); + return (val & 0x0001); +} + +/* + * Determine SerDes link speed and duplex from Expansion reg 0x42 "Operating + * Mode Status Register" + */ +static u32 bcm54616_parse_serdes_sr(struct phy_device *phydev) +{ + u16 val; + int i = 0; + + /* Wait 1s for link - Clause 37 autonegotiation happens very fast */ + while (1) { + phy_write(phydev, MII_BCM54XX_SHD, + MII_BCM54XX_SHD_VAL(0x15)); + val = phy_read(phydev, MII_BCM54XX_SHD); + + if (val & 0x0200) + break; + + if (i++ > 1000) { + phydev->link = 0; + return 1; + } + + udelay(1000); /* 1 ms */ + } + + phydev->link = 1; + switch ((val >> 6) & 0x3) { + case (0x00): + phydev->speed = 10; + break; + case (0x01): + phydev->speed = 100; + break; + case (0x02): + phydev->speed = 1000; + break; + } + + phydev->duplex = (val & 0x0100) == 0x0100; + + return 0; +} + +static int bcm54xx_parse_status(struct phy_device *phydev) +{ + unsigned int mii_reg; + + mii_reg = phy_read(phydev, MII_BCM54XX_AUX_STATUS); + + switch ((mii_reg & MII_BCM54XX_AUX_STATUS_LINKMODE_MASK) >> + MII_BCM54XX_AUX_STATUS_LINKMODE_SHIFT) { + case 1: + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_10; + break; + case 2: + phydev->duplex = DUPLEX_FULL; + phydev->speed = SPEED_10; + break; + case 3: + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_100; + break; + case 5: + phydev->duplex = DUPLEX_FULL; + phydev->speed = SPEED_100; + break; + case 6: + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_1000; + break; + case 7: + phydev->duplex = DUPLEX_FULL; + phydev->speed = SPEED_1000; + break; + default: + printk("Auto-neg error, defaulting to 1000/HD\n"); + phydev->duplex = DUPLEX_FULL; + phydev->speed = SPEED_1000; + break; + } + + return 0; +} + +/* + * Figure out if BCM54616 is in serdes or copper mode and determine link + * configuration accordingly + */ +static int bcm54616_read_status(struct phy_device *phydev) +{ + if (bcm54616_is_serdes(phydev)) { + bcm54616_parse_serdes_sr(phydev); + /* phydev->port = PORT_FIBRE; */ + } else { + /* Wait for auto-negotiation to complete or fail */ + genphy_update_link(phydev); + /* Parse BCM54xx copper aux status register */ + bcm54xx_parse_status(phydev); + } + + return 0; +} + + static int bcm54xx_ack_interrupt(struct phy_device *phydev) { int reg; @@ -520,6 +679,35 @@ static int bcm54xx_ack_interrupt(struct phy_device *phydev) return 0; } +static int bcm54616_ack_interrupt(struct phy_device *phydev) +{ + int reg; + + /* Clear pending interrupts. */ + reg = phy_read(phydev, MII_BCM54XX_ISR); + if (reg < 0) + return reg; + + return 0; +} + +static int bcm54616_config_intr(struct phy_device *phydev) +{ + int reg, err; + + reg = phy_read(phydev, MII_BCM54XX_ECR); + if (reg < 0) + return reg; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + reg &= ~MII_BCM54XX_ECR_IM; + else + reg |= MII_BCM54XX_ECR_IM; + + err = phy_write(phydev, MII_BCM54XX_ECR, reg); + return err; +} + static int bcm54xx_config_intr(struct phy_device *phydev) { int reg, err; @@ -723,6 +911,19 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm54xx_config_intr, .driver = { .owner = THIS_MODULE }, }, { + .phy_id = PHY_ID_BCM54616, + .phy_id_mask = 0xfffffff0, + .name = "Broadcom BCM54616", + .features = PHY_GBIT_FEATURES | + SUPPORTED_Pause | SUPPORTED_Asym_Pause, + .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT, + .config_init = bcm54616_config_init, + .config_aneg = genphy_config_aneg, + .read_status = bcm54616_read_status, + .ack_interrupt = bcm54616_ack_interrupt, + .config_intr = bcm54616_config_intr, + .driver = { .owner = THIS_MODULE }, +}, { .phy_id = PHY_ID_BCM5464, .phy_id_mask = 0xfffffff0, .name = "Broadcom BCM5464", @@ -847,6 +1048,7 @@ static struct mdio_device_id __maybe_unused broadcom_tbl[] = { { PHY_ID_BCM5411, 0xfffffff0 }, { PHY_ID_BCM5421, 0xfffffff0 }, { PHY_ID_BCM5461, 0xfffffff0 }, + { PHY_ID_BCM54616, 0xfffffff0 }, { PHY_ID_BCM5464, 0xfffffff0 }, { PHY_ID_BCM5482, 0xfffffff0 }, { PHY_ID_BCM5482, 0xfffffff0 }, diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index 677b4f0..d72470b 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -11,6 +11,7 @@ #define PHY_ID_BCM5421 0x002060e0 #define PHY_ID_BCM5464 0x002060b0 #define PHY_ID_BCM5461 0x002060c0 +#define PHY_ID_BCM54616 0x03625d10 #define PHY_ID_BCM57780 0x03625d90 #define PHY_BCM_OUI_MASK 0xfffffc00 -- 1.9.1