162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Broadcom 63xx SOCs integrated PHYs 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci#include "bcm-phy-lib.h" 662306a36Sopenharmony_ci#include <linux/module.h> 762306a36Sopenharmony_ci#include <linux/phy.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define MII_BCM63XX_IR 0x1a /* interrupt register */ 1062306a36Sopenharmony_ci#define MII_BCM63XX_IR_EN 0x4000 /* global interrupt enable */ 1162306a36Sopenharmony_ci#define MII_BCM63XX_IR_DUPLEX 0x0800 /* duplex changed */ 1262306a36Sopenharmony_ci#define MII_BCM63XX_IR_SPEED 0x0400 /* speed changed */ 1362306a36Sopenharmony_ci#define MII_BCM63XX_IR_LINK 0x0200 /* link changed */ 1462306a36Sopenharmony_ci#define MII_BCM63XX_IR_GMASK 0x0100 /* global interrupt mask */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ciMODULE_DESCRIPTION("Broadcom 63xx internal PHY driver"); 1762306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>"); 1862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic int bcm63xx_config_intr(struct phy_device *phydev) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci int reg, err; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci reg = phy_read(phydev, MII_BCM63XX_IR); 2562306a36Sopenharmony_ci if (reg < 0) 2662306a36Sopenharmony_ci return reg; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 2962306a36Sopenharmony_ci err = bcm_phy_ack_intr(phydev); 3062306a36Sopenharmony_ci if (err) 3162306a36Sopenharmony_ci return err; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci reg &= ~MII_BCM63XX_IR_GMASK; 3462306a36Sopenharmony_ci err = phy_write(phydev, MII_BCM63XX_IR, reg); 3562306a36Sopenharmony_ci } else { 3662306a36Sopenharmony_ci reg |= MII_BCM63XX_IR_GMASK; 3762306a36Sopenharmony_ci err = phy_write(phydev, MII_BCM63XX_IR, reg); 3862306a36Sopenharmony_ci if (err) 3962306a36Sopenharmony_ci return err; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci err = bcm_phy_ack_intr(phydev); 4262306a36Sopenharmony_ci } 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci return err; 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int bcm63xx_config_init(struct phy_device *phydev) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci int reg, err; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci /* ASYM_PAUSE bit is marked RO in datasheet, so don't cheat */ 5262306a36Sopenharmony_ci linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci reg = phy_read(phydev, MII_BCM63XX_IR); 5562306a36Sopenharmony_ci if (reg < 0) 5662306a36Sopenharmony_ci return reg; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* Mask interrupts globally. */ 5962306a36Sopenharmony_ci reg |= MII_BCM63XX_IR_GMASK; 6062306a36Sopenharmony_ci err = phy_write(phydev, MII_BCM63XX_IR, reg); 6162306a36Sopenharmony_ci if (err < 0) 6262306a36Sopenharmony_ci return err; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci /* Unmask events we are interested in */ 6562306a36Sopenharmony_ci reg = ~(MII_BCM63XX_IR_DUPLEX | 6662306a36Sopenharmony_ci MII_BCM63XX_IR_SPEED | 6762306a36Sopenharmony_ci MII_BCM63XX_IR_LINK) | 6862306a36Sopenharmony_ci MII_BCM63XX_IR_EN; 6962306a36Sopenharmony_ci return phy_write(phydev, MII_BCM63XX_IR, reg); 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic struct phy_driver bcm63xx_driver[] = { 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci .phy_id = 0x00406000, 7562306a36Sopenharmony_ci .phy_id_mask = 0xfffffc00, 7662306a36Sopenharmony_ci .name = "Broadcom BCM63XX (1)", 7762306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 7862306a36Sopenharmony_ci .flags = PHY_IS_INTERNAL, 7962306a36Sopenharmony_ci .config_init = bcm63xx_config_init, 8062306a36Sopenharmony_ci .config_intr = bcm63xx_config_intr, 8162306a36Sopenharmony_ci .handle_interrupt = bcm_phy_handle_interrupt, 8262306a36Sopenharmony_ci}, { 8362306a36Sopenharmony_ci /* same phy as above, with just a different OUI */ 8462306a36Sopenharmony_ci .phy_id = 0x002bdc00, 8562306a36Sopenharmony_ci .phy_id_mask = 0xfffffc00, 8662306a36Sopenharmony_ci .name = "Broadcom BCM63XX (2)", 8762306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 8862306a36Sopenharmony_ci .flags = PHY_IS_INTERNAL, 8962306a36Sopenharmony_ci .config_init = bcm63xx_config_init, 9062306a36Sopenharmony_ci .config_intr = bcm63xx_config_intr, 9162306a36Sopenharmony_ci .handle_interrupt = bcm_phy_handle_interrupt, 9262306a36Sopenharmony_ci} }; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cimodule_phy_driver(bcm63xx_driver); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused bcm63xx_tbl[] = { 9762306a36Sopenharmony_ci { 0x00406000, 0xfffffc00 }, 9862306a36Sopenharmony_ci { 0x002bdc00, 0xfffffc00 }, 9962306a36Sopenharmony_ci { } 10062306a36Sopenharmony_ci}; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, bcm63xx_tbl); 103