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>tirimbino
parent
a30e2c1891
commit
e9976d7c96
@ -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>; |
||||
}; |
@ -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); |
Loading…
Reference in new issue