[PATCH] net: phy: broadcom: add BCM5221 phy support

Giulio Benetti giulio.benetti at benettiengineering.com
Sun Aug 13 00:54:25 CEST 2023


Hello Marek,

thanks for reviewing,

On 12/08/23 08:19, Marek Vasut wrote:
> On 8/11/23 23:42, Giulio Benetti wrote:
>> Add BCM5221 phy support.
> 
> Why not port Linux drivers/net/sungem_phy.c instead ?
> That already supports the PHY .

That was my idea too in the beginning, but sungem_phy.c is a hidden
tristate choice in Kconfig that depends on CONFIG_PCI and is not part of 
the Linux standard drivers/net/phy/ folder.
It's only chosen by Toshiba CONFIG_SPIDER_NET and CONFIG_SUNGEM that
depends on PCI that in order depend on NET_VENDOR_SUN, so it looks like
legacy code and indeed while checking with git last commit that added
some support I've found it was back in 2011.

I've sent a patch for adding BCM5221 to Linux too:
https://lore.kernel.org/lkml/20230811215322.8679-1-giulio.benetti@benettiengineering.com/
and nobody pointed me to use sungem_phy.c

>> Sponsored by: Tekvox Inc.
>> Cc: Jim Reinhart <jimr at tekvox.com>
>> Cc: James Autry <jautry at tekvox.com>
>> Cc: Matthew Maron <matthewm at tekvox.com>
>> Signed-off-by: Giulio Benetti <giulio.benetti at benettiengineering.com>
>> ---
>>   drivers/net/phy/broadcom.c | 99 ++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 99 insertions(+)
>>
>> diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
>> index 36c70da181..a1996e6059 100644
>> --- a/drivers/net/phy/broadcom.c
>> +++ b/drivers/net/phy/broadcom.c
>> @@ -34,6 +34,26 @@
>>   #define MIIM_BCM_CHANNEL_WIDTH    0x2000
>> +#define MII_BCM5221_INTREG        0x1a    /* Interrupt register */
>> +#define MII_BCM5221_IR_MASK        0x0100    /* Mask all interrupts */
>> +#define MII_BCM5221_IR_LINK_EN        0x0200    /* Link status change 
>> enable */
>> +#define MII_BCM5221_IR_SPEED_EN    0x0400    /* Link speed change 
>> enable */
>> +#define MII_BCM5221_IR_DUPLEX_EN    0x0800    /* Duplex mode change 
>> enable */
>> +#define MII_BCM5221_IR_ENABLE        0x4000    /* Interrupt enable */
>> +
>> +#define MII_BCM5221_BRCMTEST        0x1f    /* Brcm test register */
>> +#define MII_BCM5221_BT_SRE        0x0080    /* Shadow register enable */
>> +
>> +#define MII_BCM5221_AE_GSR        0x1c    /* BCM5221 Auxiliary Error &
>> +                         * General Status Register
>> +                         */
>> +#define MII_BCM5221_AE_GSR_DIS_MDIX    0x0800    /* BCM5221 Disable 
>> MDIX */
>> +#define MII_BCM5221_SHDW_AM4_FLPM    0x0002    /* BCM5221 Force Low 
>> Power
>> +                         * Mode
>> +                         */
>> +
>> +#define MII_BCM5221_SHDW_AUXMODE4    0x1a    /* Auxiliary mode 4 */
>> +
>>   static void bcm_phy_write_misc(struct phy_device *phydev,
>>                      u16 reg, u16 chl, u16 value)
>>   {
>> @@ -311,6 +331,75 @@ static int bcm5482_startup(struct phy_device 
>> *phydev)
>>       return bcm54xx_parse_status(phydev);
>>   }
>> +static int bcm_bcm5221_config(struct phy_device *phydev)
>> +{
>> +    int reg, err, err2, brcmtest;
>> +
>> +    phy_reset(phydev);
>> +
>> +    /* The datasheet indicates the PHY needs up to 1us to complete a 
>> reset,
>> +     * build some slack here.
>> +     */
>> +    udelay(2000);
> 
> 1 us and 2000 us is a huge difference , why such a long delay ?

I agree with you, only I've found in Linux drivers/net/phy/broadcom.c
usleep_range(1000, 2000) even if the comment above states 1us so for
consistency I've added the worst case 2000us.
But while writing I see that usleep_range(1000, 2000) is an additional
delay added after software reset finished, indeed in Datasheet page 33:
https://docs.broadcom.com/doc/5221-DS07-405-RDS.pdf
they state:
"
...until the reset process is completed, which requires approximately 1 µs.
"
and phy_reset() already waits for RESET bit to be cleared, so that is
really an additional delay. It's not that clear to be honest.

>> +    /* The PHY requires 65 MDC clock cycles to complete a write 
>> operation
>> +     * and turnaround the line properly.
>> +     *
>> +     * We ignore -EIO here as the MDIO controller (e.g.: 
>> mdio-bcm-unimac)
>> +     * may flag the lack of turn-around as a read failure. This is
>> +     * particularly true with this combination since the MDIO controller
>> +     * only used 64 MDC cycles. This is not a critical failure in this
>> +     * specific case and it has no functional impact otherwise, so we 
>> let
>> +     * that one go through. If there is a genuine bus error, the next 
>> read
>> +     * of MII_BCM5221_INTREG will error out.
>> +     */
> 
> Shouldn't this be fixed on the MDIO/MAC driver level?

Yes you're right, but at the moment in Linux they deal with it like
this. I don't have access to such mdio-bcm-unimac so I don't know how
to test, reproduce and fix the problem at MAC level :-/

>> +    err = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
>> +    if (err < 0 && err != -EIO)
>> +        return err;
>> +
>> +    reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BCM5221_INTREG);
>> +    if (reg < 0)
>> +        return reg;
>> +
>> +    /* Mask interrupts globally since we don't use interrupt */
>> +    reg = MII_BCM5221_IR_MASK;
>> +
>> +    err = phy_write(phydev, MDIO_DEVAD_NONE, MII_BCM5221_INTREG, reg);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    /* Enable auto MDIX */
>> +    err = phy_modify(phydev, MDIO_DEVAD_NONE, MII_BCM5221_AE_GSR,
>> +             MII_BCM5221_AE_GSR_DIS_MDIX, 0);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    /* Enable shadow register access */
>> +    brcmtest = phy_read(phydev, MDIO_DEVAD_NONE, MII_BCM5221_BRCMTEST);
>> +    if (brcmtest < 0)
>> +        return brcmtest;
>> +
>> +    reg = brcmtest | MII_BCM5221_BT_SRE;
> 
> I think it would be best to port phy_set_bits() wrapper from Linux and 
> use it here. The mmd one is already ported over.

Ok, I will add it on V2, at this point I will add phy_clear_bits() too
for enabling MDIX above. I don't know why I haven't used it neither on
Linux driver. I think I will modify it according to this then, because
I need to preserve all bits of the register and set bit 7 but it is
exactly what phy_set_bits() does.

> 
>> +    err = phy_write(phydev, MDIO_DEVAD_NONE, MII_BCM5221_BRCMTEST, reg);
>> +    if (err < 0)
>> +        return err;
>> +
>> +    /* Exit low power mode */
>> +    err = phy_modify(phydev, MDIO_DEVAD_NONE, MII_BCM5221_SHDW_AUXMODE4,
>> +             MII_BCM5221_SHDW_AM4_FLPM, 0);
>> +    if (err < 0)
>> +        goto done;

Below maybe you mean I have to return instead of 'goto done', right?
Also because it makes no sense since it will get to done: in any case.

>> +
>> +done:
>> +    /* Disable shadow register access */
>> +    err2 = phy_write(phydev, MDIO_DEVAD_NONE, MII_BCM5221_BRCMTEST, 
>> brcmtest);
>> +    if (!err)
>> +        err = err2;
> 
> Just ignore the return value of this write here, you want to return the 
> original error value anyway, and if the write here fails, it means the 
> hardware just failed badly.

And so yes, I can get rid of err2 and use err again and simply return it.

Does all sound good to you?

Thanks again
Best regards
-- 
Giulio Benetti
CEO&CTO at Benetti Engineering sas


More information about the U-Boot mailing list