Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netdev/phy: Add driver for Broadcom BCM87XX 10G Ethernet PHYs

Add a driver for BCM8706 and BCM8727 devices. These are a 10Gig PHYs
which use MII_ADDR_C45 addressing. They are always 10G full duplex, so
there is no autonegotiation. All we do is report link state and send
interrupts when it changes.

If the PHY has a device tree of_node associated with it, the
"broadcom,c45-reg-init" property is used to supply register
initialization values when config_init() is called.

Signed-off-by: David Daney <david.daney@cavium.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

David Daney and committed by
David S. Miller
e9976d7c a30e2c18

+273
+29
Documentation/devicetree/bindings/net/broadcom-bcm87xx.txt
··· 1 + The Broadcom BCM87XX devices are a family of 10G Ethernet PHYs. They 2 + have these bindings in addition to the standard PHY bindings. 3 + 4 + Compatible: Should contain "broadcom,bcm8706" or "broadcom,bcm8727" and 5 + "ethernet-phy-ieee802.3-c45" 6 + 7 + Optional Properties: 8 + 9 + - broadcom,c45-reg-init : one of more sets of 4 cells. The first cell 10 + is the MDIO Manageable Device (MMD) address, the second a register 11 + address within the MMD, the third cell contains a mask to be ANDed 12 + with the existing register value, and the fourth cell is ORed with 13 + he result to yield the new register value. If the third cell has a 14 + value of zero, no read of the existing value is performed. 15 + 16 + Example: 17 + 18 + ethernet-phy@5 { 19 + reg = <5>; 20 + compatible = "broadcom,bcm8706", "ethernet-phy-ieee802.3-c45"; 21 + interrupt-parent = <&gpio>; 22 + interrupts = <12 8>; /* Pin 12, active low */ 23 + /* 24 + * Set PMD Digital Control Register for 25 + * GPIO[1] Tx/Rx 26 + * GPIO[0] R64 Sync Acquired 27 + */ 28 + broadcom,c45-reg-init = <1 0xc808 0xff8f 0x70>; 29 + };
+5
drivers/net/phy/Kconfig
··· 67 67 ---help--- 68 68 Currently supports the 6348 and 6358 PHYs. 69 69 70 + config BCM87XX_PHY 71 + tristate "Driver for Broadcom BCM8706 and BCM8727 PHYs" 72 + help 73 + Currently supports the BCM8706 and BCM8727 10G Ethernet PHYs. 74 + 70 75 config ICPLUS_PHY 71 76 tristate "Drivers for ICPlus PHYs" 72 77 ---help---
+1
drivers/net/phy/Makefile
··· 12 12 obj-$(CONFIG_VITESSE_PHY) += vitesse.o 13 13 obj-$(CONFIG_BROADCOM_PHY) += broadcom.o 14 14 obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o 15 + obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o 15 16 obj-$(CONFIG_ICPLUS_PHY) += icplus.o 16 17 obj-$(CONFIG_REALTEK_PHY) += realtek.o 17 18 obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o
+238
drivers/net/phy/bcm87xx.c
··· 1 + /* 2 + * This file is subject to the terms and conditions of the GNU General Public 3 + * License. See the file "COPYING" in the main directory of this archive 4 + * for more details. 5 + * 6 + * Copyright (C) 2011 - 2012 Cavium, Inc. 7 + */ 8 + 9 + #include <linux/module.h> 10 + #include <linux/phy.h> 11 + #include <linux/of.h> 12 + 13 + #define PHY_ID_BCM8706 0x0143bdc1 14 + #define PHY_ID_BCM8727 0x0143bff0 15 + 16 + #define BCM87XX_PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x1000a) 17 + #define BCM87XX_10GBASER_PCS_STATUS (MII_ADDR_C45 | 0x30020) 18 + #define BCM87XX_XGXS_LANE_STATUS (MII_ADDR_C45 | 0x40018) 19 + 20 + #define BCM87XX_LASI_CONTROL (MII_ADDR_C45 | 0x39002) 21 + #define BCM87XX_LASI_STATUS (MII_ADDR_C45 | 0x39005) 22 + 23 + #if IS_ENABLED(CONFIG_OF_MDIO) 24 + /* Set and/or override some configuration registers based on the 25 + * marvell,reg-init property stored in the of_node for the phydev. 26 + * 27 + * broadcom,c45-reg-init = <devid reg mask value>,...; 28 + * 29 + * There may be one or more sets of <devid reg mask value>: 30 + * 31 + * devid: which sub-device to use. 32 + * reg: the register. 33 + * mask: if non-zero, ANDed with existing register value. 34 + * value: ORed with the masked value and written to the regiser. 35 + * 36 + */ 37 + static int bcm87xx_of_reg_init(struct phy_device *phydev) 38 + { 39 + const __be32 *paddr; 40 + const __be32 *paddr_end; 41 + int len, ret; 42 + 43 + if (!phydev->dev.of_node) 44 + return 0; 45 + 46 + paddr = of_get_property(phydev->dev.of_node, 47 + "broadcom,c45-reg-init", &len); 48 + if (!paddr) 49 + return 0; 50 + 51 + paddr_end = paddr + (len /= sizeof(*paddr)); 52 + 53 + ret = 0; 54 + 55 + while (paddr + 3 < paddr_end) { 56 + u16 devid = be32_to_cpup(paddr++); 57 + u16 reg = be32_to_cpup(paddr++); 58 + u16 mask = be32_to_cpup(paddr++); 59 + u16 val_bits = be32_to_cpup(paddr++); 60 + int val; 61 + u32 regnum = MII_ADDR_C45 | (devid << 16) | reg; 62 + val = 0; 63 + if (mask) { 64 + val = phy_read(phydev, regnum); 65 + if (val < 0) { 66 + ret = val; 67 + goto err; 68 + } 69 + val &= mask; 70 + } 71 + val |= val_bits; 72 + 73 + ret = phy_write(phydev, regnum, val); 74 + if (ret < 0) 75 + goto err; 76 + } 77 + err: 78 + return ret; 79 + } 80 + #else 81 + static int bcm87xx_of_reg_init(struct phy_device *phydev) 82 + { 83 + return 0; 84 + } 85 + #endif /* CONFIG_OF_MDIO */ 86 + 87 + static int bcm87xx_config_init(struct phy_device *phydev) 88 + { 89 + phydev->supported = SUPPORTED_10000baseR_FEC; 90 + phydev->advertising = ADVERTISED_10000baseR_FEC; 91 + phydev->state = PHY_NOLINK; 92 + 93 + bcm87xx_of_reg_init(phydev); 94 + 95 + return 0; 96 + } 97 + 98 + static int bcm87xx_config_aneg(struct phy_device *phydev) 99 + { 100 + return -EINVAL; 101 + } 102 + 103 + static int bcm87xx_read_status(struct phy_device *phydev) 104 + { 105 + int rx_signal_detect; 106 + int pcs_status; 107 + int xgxs_lane_status; 108 + 109 + rx_signal_detect = phy_read(phydev, BCM87XX_PMD_RX_SIGNAL_DETECT); 110 + if (rx_signal_detect < 0) 111 + return rx_signal_detect; 112 + 113 + if ((rx_signal_detect & 1) == 0) 114 + goto no_link; 115 + 116 + pcs_status = phy_read(phydev, BCM87XX_10GBASER_PCS_STATUS); 117 + if (pcs_status < 0) 118 + return pcs_status; 119 + 120 + if ((pcs_status & 1) == 0) 121 + goto no_link; 122 + 123 + xgxs_lane_status = phy_read(phydev, BCM87XX_XGXS_LANE_STATUS); 124 + if (xgxs_lane_status < 0) 125 + return xgxs_lane_status; 126 + 127 + if ((xgxs_lane_status & 0x1000) == 0) 128 + goto no_link; 129 + 130 + phydev->speed = 10000; 131 + phydev->link = 1; 132 + phydev->duplex = 1; 133 + return 0; 134 + 135 + no_link: 136 + phydev->link = 0; 137 + return 0; 138 + } 139 + 140 + static int bcm87xx_config_intr(struct phy_device *phydev) 141 + { 142 + int reg, err; 143 + 144 + reg = phy_read(phydev, BCM87XX_LASI_CONTROL); 145 + 146 + if (reg < 0) 147 + return reg; 148 + 149 + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) 150 + reg |= 1; 151 + else 152 + reg &= ~1; 153 + 154 + err = phy_write(phydev, BCM87XX_LASI_CONTROL, reg); 155 + return err; 156 + } 157 + 158 + static int bcm87xx_did_interrupt(struct phy_device *phydev) 159 + { 160 + int reg; 161 + 162 + reg = phy_read(phydev, BCM87XX_LASI_STATUS); 163 + 164 + if (reg < 0) { 165 + dev_err(&phydev->dev, 166 + "Error: Read of BCM87XX_LASI_STATUS failed: %d\n", reg); 167 + return 0; 168 + } 169 + return (reg & 1) != 0; 170 + } 171 + 172 + static int bcm87xx_ack_interrupt(struct phy_device *phydev) 173 + { 174 + /* Reading the LASI status clears it. */ 175 + bcm87xx_did_interrupt(phydev); 176 + return 0; 177 + } 178 + 179 + static int bcm8706_match_phy_device(struct phy_device *phydev) 180 + { 181 + return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706; 182 + } 183 + 184 + static int bcm8727_match_phy_device(struct phy_device *phydev) 185 + { 186 + return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727; 187 + } 188 + 189 + static struct phy_driver bcm8706_driver = { 190 + .phy_id = PHY_ID_BCM8706, 191 + .phy_id_mask = 0xffffffff, 192 + .name = "Broadcom BCM8706", 193 + .flags = PHY_HAS_INTERRUPT, 194 + .config_init = bcm87xx_config_init, 195 + .config_aneg = bcm87xx_config_aneg, 196 + .read_status = bcm87xx_read_status, 197 + .ack_interrupt = bcm87xx_ack_interrupt, 198 + .config_intr = bcm87xx_config_intr, 199 + .did_interrupt = bcm87xx_did_interrupt, 200 + .match_phy_device = bcm8706_match_phy_device, 201 + .driver = { .owner = THIS_MODULE }, 202 + }; 203 + 204 + static struct phy_driver bcm8727_driver = { 205 + .phy_id = PHY_ID_BCM8727, 206 + .phy_id_mask = 0xffffffff, 207 + .name = "Broadcom BCM8727", 208 + .flags = PHY_HAS_INTERRUPT, 209 + .config_init = bcm87xx_config_init, 210 + .config_aneg = bcm87xx_config_aneg, 211 + .read_status = bcm87xx_read_status, 212 + .ack_interrupt = bcm87xx_ack_interrupt, 213 + .config_intr = bcm87xx_config_intr, 214 + .did_interrupt = bcm87xx_did_interrupt, 215 + .match_phy_device = bcm8727_match_phy_device, 216 + .driver = { .owner = THIS_MODULE }, 217 + }; 218 + 219 + static int __init bcm87xx_init(void) 220 + { 221 + int ret; 222 + 223 + ret = phy_driver_register(&bcm8706_driver); 224 + if (ret) 225 + goto err; 226 + 227 + ret = phy_driver_register(&bcm8727_driver); 228 + err: 229 + return ret; 230 + } 231 + module_init(bcm87xx_init); 232 + 233 + static void __exit bcm87xx_exit(void) 234 + { 235 + phy_driver_unregister(&bcm8706_driver); 236 + phy_driver_unregister(&bcm8727_driver); 237 + } 238 + module_exit(bcm87xx_exit);