1 /* 2 * Driver for Broadcom 63xx SOCs integrated PHYs 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 7 * 2 of the License, or (at your option) any later version. 8 */ 9 #include <linux/module.h> 10 #include <linux/phy.h> 11 12 #define MII_BCM63XX_IR 0x1a /* interrupt register */ 13 #define MII_BCM63XX_IR_EN 0x4000 /* global interrupt enable */ 14 #define MII_BCM63XX_IR_DUPLEX 0x0800 /* duplex changed */ 15 #define MII_BCM63XX_IR_SPEED 0x0400 /* speed changed */ 16 #define MII_BCM63XX_IR_LINK 0x0200 /* link changed */ 17 #define MII_BCM63XX_IR_GMASK 0x0100 /* global interrupt mask */ 18 19 MODULE_DESCRIPTION("Broadcom 63xx internal PHY driver"); 20 MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>"); 21 MODULE_LICENSE("GPL"); 22 23 static int bcm63xx_config_init(struct phy_device *phydev) 24 { 25 int reg, err; 26 27 reg = phy_read(phydev, MII_BCM63XX_IR); 28 if (reg < 0) 29 return reg; 30 31 /* Mask interrupts globally. */ 32 reg |= MII_BCM63XX_IR_GMASK; 33 err = phy_write(phydev, MII_BCM63XX_IR, reg); 34 if (err < 0) 35 return err; 36 37 /* Unmask events we are interested in */ 38 reg = ~(MII_BCM63XX_IR_DUPLEX | 39 MII_BCM63XX_IR_SPEED | 40 MII_BCM63XX_IR_LINK) | 41 MII_BCM63XX_IR_EN; 42 err = phy_write(phydev, MII_BCM63XX_IR, reg); 43 if (err < 0) 44 return err; 45 return 0; 46 } 47 48 static int bcm63xx_ack_interrupt(struct phy_device *phydev) 49 { 50 int reg; 51 52 /* Clear pending interrupts. */ 53 reg = phy_read(phydev, MII_BCM63XX_IR); 54 if (reg < 0) 55 return reg; 56 57 return 0; 58 } 59 60 static int bcm63xx_config_intr(struct phy_device *phydev) 61 { 62 int reg, err; 63 64 reg = phy_read(phydev, MII_BCM63XX_IR); 65 if (reg < 0) 66 return reg; 67 68 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) 69 reg &= ~MII_BCM63XX_IR_GMASK; 70 else 71 reg |= MII_BCM63XX_IR_GMASK; 72 73 err = phy_write(phydev, MII_BCM63XX_IR, reg); 74 return err; 75 } 76 77 static struct phy_driver bcm63xx_1_driver = { 78 .phy_id = 0x00406000, 79 .phy_id_mask = 0xfffffc00, 80 .name = "Broadcom BCM63XX (1)", 81 /* ASYM_PAUSE bit is marked RO in datasheet, so don't cheat */ 82 .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), 83 .flags = PHY_HAS_INTERRUPT, 84 .config_init = bcm63xx_config_init, 85 .config_aneg = genphy_config_aneg, 86 .read_status = genphy_read_status, 87 .ack_interrupt = bcm63xx_ack_interrupt, 88 .config_intr = bcm63xx_config_intr, 89 .driver = { .owner = THIS_MODULE }, 90 }; 91 92 /* same phy as above, with just a different OUI */ 93 static struct phy_driver bcm63xx_2_driver = { 94 .phy_id = 0x002bdc00, 95 .phy_id_mask = 0xfffffc00, 96 .name = "Broadcom BCM63XX (2)", 97 .features = (PHY_BASIC_FEATURES | SUPPORTED_Pause), 98 .flags = PHY_HAS_INTERRUPT, 99 .config_init = bcm63xx_config_init, 100 .config_aneg = genphy_config_aneg, 101 .read_status = genphy_read_status, 102 .ack_interrupt = bcm63xx_ack_interrupt, 103 .config_intr = bcm63xx_config_intr, 104 .driver = { .owner = THIS_MODULE }, 105 }; 106 107 static int __init bcm63xx_phy_init(void) 108 { 109 int ret; 110 111 ret = phy_driver_register(&bcm63xx_1_driver); 112 if (ret) 113 goto out_63xx_1; 114 ret = phy_driver_register(&bcm63xx_2_driver); 115 if (ret) 116 goto out_63xx_2; 117 return ret; 118 119 out_63xx_2: 120 phy_driver_unregister(&bcm63xx_1_driver); 121 out_63xx_1: 122 return ret; 123 } 124 125 static void __exit bcm63xx_phy_exit(void) 126 { 127 phy_driver_unregister(&bcm63xx_1_driver); 128 phy_driver_unregister(&bcm63xx_2_driver); 129 } 130 131 module_init(bcm63xx_phy_init); 132 module_exit(bcm63xx_phy_exit); 133 134 static struct mdio_device_id __maybe_unused bcm63xx_tbl[] = { 135 { 0x00406000, 0xfffffc00 }, 136 { 0x002bdc00, 0xfffffc00 }, 137 { } 138 }; 139 140 MODULE_DEVICE_TABLE(mdio, bcm63xx_tbl); 141