Merge branch 'bcm7xxx_workaround'
Florian Fainelli says: ==================== net: phy: bcm7xxx initial read/write workaround This patch series fixes occasional BCM7xxx PHY driver binding failure due to a harware bug where the first read or write does not come out of the PHY MDIO management controller. Since we have two different MDIO controllers using this PHY, a similar need to be replicated in GENET and UniMAC MDIO. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
7c80c3af5a
|
@ -594,6 +594,7 @@ struct bcmgenet_priv {
|
||||||
wait_queue_head_t wq;
|
wait_queue_head_t wq;
|
||||||
struct phy_device *phydev;
|
struct phy_device *phydev;
|
||||||
struct device_node *phy_dn;
|
struct device_node *phy_dn;
|
||||||
|
struct device_node *mdio_dn;
|
||||||
struct mii_bus *mii_bus;
|
struct mii_bus *mii_bus;
|
||||||
u16 gphy_rev;
|
u16 gphy_rev;
|
||||||
struct clk *clk_eee;
|
struct clk *clk_eee;
|
||||||
|
|
|
@ -408,6 +408,52 @@ static int bcmgenet_mii_probe(struct net_device *dev)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with
|
||||||
|
* their internal MDIO management controller making them fail to successfully
|
||||||
|
* be read from or written to for the first transaction. We insert a dummy
|
||||||
|
* BMSR read here to make sure that phy_get_device() and get_phy_id() can
|
||||||
|
* correctly read the PHY MII_PHYSID1/2 registers and successfully register a
|
||||||
|
* PHY device for this peripheral.
|
||||||
|
*
|
||||||
|
* Once the PHY driver is registered, we can workaround subsequent reads from
|
||||||
|
* there (e.g: during system-wide power management).
|
||||||
|
*
|
||||||
|
* bus->reset is invoked before mdiobus_scan during mdiobus_register and is
|
||||||
|
* therefore the right location to stick that workaround. Since we do not want
|
||||||
|
* to read from non-existing PHYs, we either use bus->phy_mask or do a manual
|
||||||
|
* Device Tree scan to limit the search area.
|
||||||
|
*/
|
||||||
|
static int bcmgenet_mii_bus_reset(struct mii_bus *bus)
|
||||||
|
{
|
||||||
|
struct net_device *dev = bus->priv;
|
||||||
|
struct bcmgenet_priv *priv = netdev_priv(dev);
|
||||||
|
struct device_node *np = priv->mdio_dn;
|
||||||
|
struct device_node *child = NULL;
|
||||||
|
u32 read_mask = 0;
|
||||||
|
int addr = 0;
|
||||||
|
|
||||||
|
if (!np) {
|
||||||
|
read_mask = 1 << priv->phy_addr;
|
||||||
|
} else {
|
||||||
|
for_each_available_child_of_node(np, child) {
|
||||||
|
addr = of_mdio_parse_addr(&dev->dev, child);
|
||||||
|
if (addr < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
read_mask |= 1 << addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
|
||||||
|
if (read_mask & 1 << addr) {
|
||||||
|
dev_dbg(&dev->dev, "Workaround for PHY @ %d\n", addr);
|
||||||
|
mdiobus_read(bus, addr, MII_BMSR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int bcmgenet_mii_alloc(struct bcmgenet_priv *priv)
|
static int bcmgenet_mii_alloc(struct bcmgenet_priv *priv)
|
||||||
{
|
{
|
||||||
struct mii_bus *bus;
|
struct mii_bus *bus;
|
||||||
|
@ -427,6 +473,7 @@ static int bcmgenet_mii_alloc(struct bcmgenet_priv *priv)
|
||||||
bus->parent = &priv->pdev->dev;
|
bus->parent = &priv->pdev->dev;
|
||||||
bus->read = bcmgenet_mii_read;
|
bus->read = bcmgenet_mii_read;
|
||||||
bus->write = bcmgenet_mii_write;
|
bus->write = bcmgenet_mii_write;
|
||||||
|
bus->reset = bcmgenet_mii_bus_reset;
|
||||||
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d",
|
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d",
|
||||||
priv->pdev->name, priv->pdev->id);
|
priv->pdev->name, priv->pdev->id);
|
||||||
|
|
||||||
|
@ -443,7 +490,6 @@ static int bcmgenet_mii_of_init(struct bcmgenet_priv *priv)
|
||||||
{
|
{
|
||||||
struct device_node *dn = priv->pdev->dev.of_node;
|
struct device_node *dn = priv->pdev->dev.of_node;
|
||||||
struct device *kdev = &priv->pdev->dev;
|
struct device *kdev = &priv->pdev->dev;
|
||||||
struct device_node *mdio_dn;
|
|
||||||
char *compat;
|
char *compat;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -451,14 +497,14 @@ static int bcmgenet_mii_of_init(struct bcmgenet_priv *priv)
|
||||||
if (!compat)
|
if (!compat)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
mdio_dn = of_find_compatible_node(dn, NULL, compat);
|
priv->mdio_dn = of_find_compatible_node(dn, NULL, compat);
|
||||||
kfree(compat);
|
kfree(compat);
|
||||||
if (!mdio_dn) {
|
if (!priv->mdio_dn) {
|
||||||
dev_err(kdev, "unable to find MDIO bus node\n");
|
dev_err(kdev, "unable to find MDIO bus node\n");
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = of_mdiobus_register(priv->mii_bus, mdio_dn);
|
ret = of_mdiobus_register(priv->mii_bus, priv->mdio_dn);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(kdev, "failed to register MDIO bus\n");
|
dev_err(kdev, "failed to register MDIO bus\n");
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -246,6 +246,13 @@ static int bcm7xxx_28nm_config_init(struct phy_device *phydev)
|
||||||
pr_info_once("%s: %s PHY revision: 0x%02x, patch: %d\n",
|
pr_info_once("%s: %s PHY revision: 0x%02x, patch: %d\n",
|
||||||
dev_name(&phydev->dev), phydev->drv->name, rev, patch);
|
dev_name(&phydev->dev), phydev->drv->name, rev, patch);
|
||||||
|
|
||||||
|
/* Dummy read to a register to workaround an issue upon reset where the
|
||||||
|
* internal inverter may not allow the first MDIO transaction to pass
|
||||||
|
* the MDIO management controller and make us return 0xffff for such
|
||||||
|
* reads.
|
||||||
|
*/
|
||||||
|
phy_read(phydev, MII_BMSR);
|
||||||
|
|
||||||
switch (rev) {
|
switch (rev) {
|
||||||
case 0xb0:
|
case 0xb0:
|
||||||
ret = bcm7xxx_28nm_b0_afe_config_init(phydev);
|
ret = bcm7xxx_28nm_b0_afe_config_init(phydev);
|
||||||
|
|
|
@ -120,6 +120,48 @@ static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with
|
||||||
|
* their internal MDIO management controller making them fail to successfully
|
||||||
|
* be read from or written to for the first transaction. We insert a dummy
|
||||||
|
* BMSR read here to make sure that phy_get_device() and get_phy_id() can
|
||||||
|
* correctly read the PHY MII_PHYSID1/2 registers and successfully register a
|
||||||
|
* PHY device for this peripheral.
|
||||||
|
*
|
||||||
|
* Once the PHY driver is registered, we can workaround subsequent reads from
|
||||||
|
* there (e.g: during system-wide power management).
|
||||||
|
*
|
||||||
|
* bus->reset is invoked before mdiobus_scan during mdiobus_register and is
|
||||||
|
* therefore the right location to stick that workaround. Since we do not want
|
||||||
|
* to read from non-existing PHYs, we either use bus->phy_mask or do a manual
|
||||||
|
* Device Tree scan to limit the search area.
|
||||||
|
*/
|
||||||
|
static int unimac_mdio_reset(struct mii_bus *bus)
|
||||||
|
{
|
||||||
|
struct device_node *np = bus->dev.of_node;
|
||||||
|
struct device_node *child;
|
||||||
|
u32 read_mask = 0;
|
||||||
|
int addr;
|
||||||
|
|
||||||
|
if (!np) {
|
||||||
|
read_mask = ~bus->phy_mask;
|
||||||
|
} else {
|
||||||
|
for_each_available_child_of_node(np, child) {
|
||||||
|
addr = of_mdio_parse_addr(&bus->dev, child);
|
||||||
|
if (addr < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
read_mask |= 1 << addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
|
||||||
|
if (read_mask & 1 << addr)
|
||||||
|
mdiobus_read(bus, addr, MII_BMSR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int unimac_mdio_probe(struct platform_device *pdev)
|
static int unimac_mdio_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct unimac_mdio_priv *priv;
|
struct unimac_mdio_priv *priv;
|
||||||
|
@ -155,6 +197,7 @@ static int unimac_mdio_probe(struct platform_device *pdev)
|
||||||
bus->parent = &pdev->dev;
|
bus->parent = &pdev->dev;
|
||||||
bus->read = unimac_mdio_read;
|
bus->read = unimac_mdio_read;
|
||||||
bus->write = unimac_mdio_write;
|
bus->write = unimac_mdio_write;
|
||||||
|
bus->reset = unimac_mdio_reset;
|
||||||
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
|
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
|
||||||
|
|
||||||
bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
|
bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
|
||||||
|
|
Loading…
Reference in New Issue