-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
David Daney
authored and
David S. Miller
committed
Jun 28, 2012
1 parent
a30e2c1
commit e9976d7
Showing
4 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
Documentation/devicetree/bindings/net/broadcom-bcm87xx.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
The Broadcom BCM87XX devices are a family of 10G Ethernet PHYs. They | ||
have these bindings in addition to the standard PHY bindings. | ||
|
||
Compatible: Should contain "broadcom,bcm8706" or "broadcom,bcm8727" and | ||
"ethernet-phy-ieee802.3-c45" | ||
|
||
Optional Properties: | ||
|
||
- broadcom,c45-reg-init : one of more sets of 4 cells. The first cell | ||
is the MDIO Manageable Device (MMD) address, the second a register | ||
address within the MMD, the third cell contains a mask to be ANDed | ||
with the existing register value, and the fourth cell is ORed with | ||
he result to yield the new register value. If the third cell has a | ||
value of zero, no read of the existing value is performed. | ||
|
||
Example: | ||
|
||
ethernet-phy@5 { | ||
reg = <5>; | ||
compatible = "broadcom,bcm8706", "ethernet-phy-ieee802.3-c45"; | ||
interrupt-parent = <&gpio>; | ||
interrupts = <12 8>; /* Pin 12, active low */ | ||
/* | ||
* Set PMD Digital Control Register for | ||
* GPIO[1] Tx/Rx | ||
* GPIO[0] R64 Sync Acquired | ||
*/ | ||
broadcom,c45-reg-init = <1 0xc808 0xff8f 0x70>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
/* | ||
* This file is subject to the terms and conditions of the GNU General Public | ||
* License. See the file "COPYING" in the main directory of this archive | ||
* for more details. | ||
* | ||
* Copyright (C) 2011 - 2012 Cavium, Inc. | ||
*/ | ||
|
||
#include <linux/module.h> | ||
#include <linux/phy.h> | ||
#include <linux/of.h> | ||
|
||
#define PHY_ID_BCM8706 0x0143bdc1 | ||
#define PHY_ID_BCM8727 0x0143bff0 | ||
|
||
#define BCM87XX_PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x1000a) | ||
#define BCM87XX_10GBASER_PCS_STATUS (MII_ADDR_C45 | 0x30020) | ||
#define BCM87XX_XGXS_LANE_STATUS (MII_ADDR_C45 | 0x40018) | ||
|
||
#define BCM87XX_LASI_CONTROL (MII_ADDR_C45 | 0x39002) | ||
#define BCM87XX_LASI_STATUS (MII_ADDR_C45 | 0x39005) | ||
|
||
#if IS_ENABLED(CONFIG_OF_MDIO) | ||
/* Set and/or override some configuration registers based on the | ||
* marvell,reg-init property stored in the of_node for the phydev. | ||
* | ||
* broadcom,c45-reg-init = <devid reg mask value>,...; | ||
* | ||
* There may be one or more sets of <devid reg mask value>: | ||
* | ||
* devid: which sub-device to use. | ||
* reg: the register. | ||
* mask: if non-zero, ANDed with existing register value. | ||
* value: ORed with the masked value and written to the regiser. | ||
* | ||
*/ | ||
static int bcm87xx_of_reg_init(struct phy_device *phydev) | ||
{ | ||
const __be32 *paddr; | ||
const __be32 *paddr_end; | ||
int len, ret; | ||
|
||
if (!phydev->dev.of_node) | ||
return 0; | ||
|
||
paddr = of_get_property(phydev->dev.of_node, | ||
"broadcom,c45-reg-init", &len); | ||
if (!paddr) | ||
return 0; | ||
|
||
paddr_end = paddr + (len /= sizeof(*paddr)); | ||
|
||
ret = 0; | ||
|
||
while (paddr + 3 < paddr_end) { | ||
u16 devid = be32_to_cpup(paddr++); | ||
u16 reg = be32_to_cpup(paddr++); | ||
u16 mask = be32_to_cpup(paddr++); | ||
u16 val_bits = be32_to_cpup(paddr++); | ||
int val; | ||
u32 regnum = MII_ADDR_C45 | (devid << 16) | reg; | ||
val = 0; | ||
if (mask) { | ||
val = phy_read(phydev, regnum); | ||
if (val < 0) { | ||
ret = val; | ||
goto err; | ||
} | ||
val &= mask; | ||
} | ||
val |= val_bits; | ||
|
||
ret = phy_write(phydev, regnum, val); | ||
if (ret < 0) | ||
goto err; | ||
} | ||
err: | ||
return ret; | ||
} | ||
#else | ||
static int bcm87xx_of_reg_init(struct phy_device *phydev) | ||
{ | ||
return 0; | ||
} | ||
#endif /* CONFIG_OF_MDIO */ | ||
|
||
static int bcm87xx_config_init(struct phy_device *phydev) | ||
{ | ||
phydev->supported = SUPPORTED_10000baseR_FEC; | ||
phydev->advertising = ADVERTISED_10000baseR_FEC; | ||
phydev->state = PHY_NOLINK; | ||
|
||
bcm87xx_of_reg_init(phydev); | ||
|
||
return 0; | ||
} | ||
|
||
static int bcm87xx_config_aneg(struct phy_device *phydev) | ||
{ | ||
return -EINVAL; | ||
} | ||
|
||
static int bcm87xx_read_status(struct phy_device *phydev) | ||
{ | ||
int rx_signal_detect; | ||
int pcs_status; | ||
int xgxs_lane_status; | ||
|
||
rx_signal_detect = phy_read(phydev, BCM87XX_PMD_RX_SIGNAL_DETECT); | ||
if (rx_signal_detect < 0) | ||
return rx_signal_detect; | ||
|
||
if ((rx_signal_detect & 1) == 0) | ||
goto no_link; | ||
|
||
pcs_status = phy_read(phydev, BCM87XX_10GBASER_PCS_STATUS); | ||
if (pcs_status < 0) | ||
return pcs_status; | ||
|
||
if ((pcs_status & 1) == 0) | ||
goto no_link; | ||
|
||
xgxs_lane_status = phy_read(phydev, BCM87XX_XGXS_LANE_STATUS); | ||
if (xgxs_lane_status < 0) | ||
return xgxs_lane_status; | ||
|
||
if ((xgxs_lane_status & 0x1000) == 0) | ||
goto no_link; | ||
|
||
phydev->speed = 10000; | ||
phydev->link = 1; | ||
phydev->duplex = 1; | ||
return 0; | ||
|
||
no_link: | ||
phydev->link = 0; | ||
return 0; | ||
} | ||
|
||
static int bcm87xx_config_intr(struct phy_device *phydev) | ||
{ | ||
int reg, err; | ||
|
||
reg = phy_read(phydev, BCM87XX_LASI_CONTROL); | ||
|
||
if (reg < 0) | ||
return reg; | ||
|
||
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | ||
reg |= 1; | ||
else | ||
reg &= ~1; | ||
|
||
err = phy_write(phydev, BCM87XX_LASI_CONTROL, reg); | ||
return err; | ||
} | ||
|
||
static int bcm87xx_did_interrupt(struct phy_device *phydev) | ||
{ | ||
int reg; | ||
|
||
reg = phy_read(phydev, BCM87XX_LASI_STATUS); | ||
|
||
if (reg < 0) { | ||
dev_err(&phydev->dev, | ||
"Error: Read of BCM87XX_LASI_STATUS failed: %d\n", reg); | ||
return 0; | ||
} | ||
return (reg & 1) != 0; | ||
} | ||
|
||
static int bcm87xx_ack_interrupt(struct phy_device *phydev) | ||
{ | ||
/* Reading the LASI status clears it. */ | ||
bcm87xx_did_interrupt(phydev); | ||
return 0; | ||
} | ||
|
||
static int bcm8706_match_phy_device(struct phy_device *phydev) | ||
{ | ||
return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8706; | ||
} | ||
|
||
static int bcm8727_match_phy_device(struct phy_device *phydev) | ||
{ | ||
return phydev->c45_ids.device_ids[4] == PHY_ID_BCM8727; | ||
} | ||
|
||
static struct phy_driver bcm8706_driver = { | ||
.phy_id = PHY_ID_BCM8706, | ||
.phy_id_mask = 0xffffffff, | ||
.name = "Broadcom BCM8706", | ||
.flags = PHY_HAS_INTERRUPT, | ||
.config_init = bcm87xx_config_init, | ||
.config_aneg = bcm87xx_config_aneg, | ||
.read_status = bcm87xx_read_status, | ||
.ack_interrupt = bcm87xx_ack_interrupt, | ||
.config_intr = bcm87xx_config_intr, | ||
.did_interrupt = bcm87xx_did_interrupt, | ||
.match_phy_device = bcm8706_match_phy_device, | ||
.driver = { .owner = THIS_MODULE }, | ||
}; | ||
|
||
static struct phy_driver bcm8727_driver = { | ||
.phy_id = PHY_ID_BCM8727, | ||
.phy_id_mask = 0xffffffff, | ||
.name = "Broadcom BCM8727", | ||
.flags = PHY_HAS_INTERRUPT, | ||
.config_init = bcm87xx_config_init, | ||
.config_aneg = bcm87xx_config_aneg, | ||
.read_status = bcm87xx_read_status, | ||
.ack_interrupt = bcm87xx_ack_interrupt, | ||
.config_intr = bcm87xx_config_intr, | ||
.did_interrupt = bcm87xx_did_interrupt, | ||
.match_phy_device = bcm8727_match_phy_device, | ||
.driver = { .owner = THIS_MODULE }, | ||
}; | ||
|
||
static int __init bcm87xx_init(void) | ||
{ | ||
int ret; | ||
|
||
ret = phy_driver_register(&bcm8706_driver); | ||
if (ret) | ||
goto err; | ||
|
||
ret = phy_driver_register(&bcm8727_driver); | ||
err: | ||
return ret; | ||
} | ||
module_init(bcm87xx_init); | ||
|
||
static void __exit bcm87xx_exit(void) | ||
{ | ||
phy_driver_unregister(&bcm8706_driver); | ||
phy_driver_unregister(&bcm8727_driver); | ||
} | ||
module_exit(bcm87xx_exit); |