Skip to content

Commit e3ba9bf

Browse files
committed
Merge branch 'Fix-regression-with-AR8035-speed-downgrade'
Russell King says: ==================== Fix regression with AR8035 speed downgrade The following series attempts to address an issue spotted by tinywrkb with the AR8035 on the Cubox-i2 in a situation where the PHY downgrades the negotiated link. This is version 2, not much has changed other than rebasing on the current net tree. Changes have happend to patch 2 due to conflicts, so I dropped Andrew's reviewed-by. Minor context changes to patch 4 which I don't consider important enough to warrant dropping the reviewed-by. Before commit 5502b21 ("net: phy: use phy_resolve_aneg_linkmode in genphy_read_status"), we would read not only the link partner's advertisement, but also our own advertisement from the PHY registers, and use both to derive the PHYs current link mode. This works when the AR8035 downgrades the speed, because it appears that the AR8035 clears link mode bits in the advertisement registers as part of the downgrade. Commentary: what is not yet known is whether the AR8035 restores the advertisement register when the link goes down to the previous state. However, since the above referenced commit, we no longer use the PHYs advertisement registers, instead converting the link partner's advertisement to the ethtool link mode array, and combine that with phylib's cached version of our advertisement - which is not updated on speed downgrade. This results in phylib disagreeing with the actual operating mode of the PHY. Commentary: I wonder how many more PHY drivers are broken by this commit, but have yet to be discovered. The obvious way to address this would be to disable the downgrade feature, and indeed this does fix the problem in tinywrkb's case - his link partner instead downgrades the speed by reducing its advertisement, resulting in phylib correctly evaluating a slower speed. However, it has a serious drawback - the gigabit control register (MII register 9) appears to become read only. It seems the only way to update the register is to re-enable the downgrade feature, reset the PHY, changing register 9, disable the downgrade feature, and reset the PHY again. This series attempts to address the problem using a different approach, similar to the approach taken with Marvell PHYs. The AR8031, AR8033 and AR8035 have a PHY-Specific Status register which reports the actual operating mode of the PHY - both speed and duplex. This register correctly reports the operating mode irrespective of whether autoneg is enabled or not. We use this register to fill in phylib's speed and duplex parameters. In detail: Patch 1 fixes a bug where writing to register 9 does not update phylib's advertisement mask in the same way that writing register 4 does; this looks like an omission from when gigabit PHY support came into being. Patch 2 seperates the generic phylib code which reads the link partners advertisement from the PHY, so that we can re-use this in the Atheros PHY driver. Patch 3 seperates the generic phylib pause mode; phylib provides no help for MAC drivers to ascertain the negotiated pause mode, it merely copies the link partner's pause mode bits into its own variables. Commentary: Both the aforementioned Atheros PHYs and Marvell PHYs provide the resolved pause modes in terms of whether we should transmit pause frames, or whether we should allow reception of pause frames. Surely the resolution of this should be in phylib? Patch 4 provides the Atheros PHY driver with a private "read_status" implementation that fills in phylib's speed and duplex settings depending on the PHY-Specific status register. This ensures that phylib and the MAC driver match the operating mode that the PHY has decided to use. Since the register also gives us MDIX status, we can trivially fill that status in as well. Note that, although the bits mentioned in this patch for this register match those in th Marvell PHY driver, and it is located at the same address, the meaning of other register bits varies between the PHYs. Therefore, I do not feel that it would be appropriate to make this some kind of generic function. ==================== Signed-off-by: David S. Miller <[email protected]>
2 parents 2d819d2 + 06d5f34 commit e3ba9bf

File tree

6 files changed

+138
-32
lines changed

6 files changed

+138
-32
lines changed

drivers/net/phy/at803x.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
#include <linux/of_gpio.h>
1616
#include <linux/gpio/consumer.h>
1717

18+
#define AT803X_SPECIFIC_STATUS 0x11
19+
#define AT803X_SS_SPEED_MASK (3 << 14)
20+
#define AT803X_SS_SPEED_1000 (2 << 14)
21+
#define AT803X_SS_SPEED_100 (1 << 14)
22+
#define AT803X_SS_SPEED_10 (0 << 14)
23+
#define AT803X_SS_DUPLEX BIT(13)
24+
#define AT803X_SS_SPEED_DUPLEX_RESOLVED BIT(11)
25+
#define AT803X_SS_MDIX BIT(6)
26+
1827
#define AT803X_INTR_ENABLE 0x12
1928
#define AT803X_INTR_ENABLE_AUTONEG_ERR BIT(15)
2029
#define AT803X_INTR_ENABLE_SPEED_CHANGED BIT(14)
@@ -357,6 +366,64 @@ static int at803x_aneg_done(struct phy_device *phydev)
357366
return aneg_done;
358367
}
359368

369+
static int at803x_read_status(struct phy_device *phydev)
370+
{
371+
int ss, err, old_link = phydev->link;
372+
373+
/* Update the link, but return if there was an error */
374+
err = genphy_update_link(phydev);
375+
if (err)
376+
return err;
377+
378+
/* why bother the PHY if nothing can have changed */
379+
if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
380+
return 0;
381+
382+
phydev->speed = SPEED_UNKNOWN;
383+
phydev->duplex = DUPLEX_UNKNOWN;
384+
phydev->pause = 0;
385+
phydev->asym_pause = 0;
386+
387+
err = genphy_read_lpa(phydev);
388+
if (err < 0)
389+
return err;
390+
391+
/* Read the AT8035 PHY-Specific Status register, which indicates the
392+
* speed and duplex that the PHY is actually using, irrespective of
393+
* whether we are in autoneg mode or not.
394+
*/
395+
ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);
396+
if (ss < 0)
397+
return ss;
398+
399+
if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {
400+
switch (ss & AT803X_SS_SPEED_MASK) {
401+
case AT803X_SS_SPEED_10:
402+
phydev->speed = SPEED_10;
403+
break;
404+
case AT803X_SS_SPEED_100:
405+
phydev->speed = SPEED_100;
406+
break;
407+
case AT803X_SS_SPEED_1000:
408+
phydev->speed = SPEED_1000;
409+
break;
410+
}
411+
if (ss & AT803X_SS_DUPLEX)
412+
phydev->duplex = DUPLEX_FULL;
413+
else
414+
phydev->duplex = DUPLEX_HALF;
415+
if (ss & AT803X_SS_MDIX)
416+
phydev->mdix = ETH_TP_MDI_X;
417+
else
418+
phydev->mdix = ETH_TP_MDI;
419+
}
420+
421+
if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
422+
phy_resolve_aneg_pause(phydev);
423+
424+
return 0;
425+
}
426+
360427
static struct phy_driver at803x_driver[] = {
361428
{
362429
/* ATHEROS 8035 */
@@ -370,6 +437,7 @@ static struct phy_driver at803x_driver[] = {
370437
.suspend = at803x_suspend,
371438
.resume = at803x_resume,
372439
/* PHY_GBIT_FEATURES */
440+
.read_status = at803x_read_status,
373441
.ack_interrupt = at803x_ack_interrupt,
374442
.config_intr = at803x_config_intr,
375443
}, {
@@ -399,6 +467,7 @@ static struct phy_driver at803x_driver[] = {
399467
.suspend = at803x_suspend,
400468
.resume = at803x_resume,
401469
/* PHY_GBIT_FEATURES */
470+
.read_status = at803x_read_status,
402471
.aneg_done = at803x_aneg_done,
403472
.ack_interrupt = &at803x_ack_interrupt,
404473
.config_intr = &at803x_config_intr,

drivers/net/phy/phy-core.c

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,18 @@ void of_set_phy_eee_broken(struct phy_device *phydev)
283283
phydev->eee_broken_modes = broken;
284284
}
285285

286+
void phy_resolve_aneg_pause(struct phy_device *phydev)
287+
{
288+
if (phydev->duplex == DUPLEX_FULL) {
289+
phydev->pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
290+
phydev->lp_advertising);
291+
phydev->asym_pause = linkmode_test_bit(
292+
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
293+
phydev->lp_advertising);
294+
}
295+
}
296+
EXPORT_SYMBOL_GPL(phy_resolve_aneg_pause);
297+
286298
/**
287299
* phy_resolve_aneg_linkmode - resolve the advertisements into phy settings
288300
* @phydev: The phy_device struct
@@ -305,13 +317,7 @@ void phy_resolve_aneg_linkmode(struct phy_device *phydev)
305317
break;
306318
}
307319

308-
if (phydev->duplex == DUPLEX_FULL) {
309-
phydev->pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
310-
phydev->lp_advertising);
311-
phydev->asym_pause = linkmode_test_bit(
312-
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
313-
phydev->lp_advertising);
314-
}
320+
phy_resolve_aneg_pause(phydev);
315321
}
316322
EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);
317323

drivers/net/phy/phy.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,11 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
457457
val);
458458
change_autoneg = true;
459459
break;
460+
case MII_CTRL1000:
461+
mii_ctrl1000_mod_linkmode_adv_t(phydev->advertising,
462+
val);
463+
change_autoneg = true;
464+
break;
460465
default:
461466
/* do nothing */
462467
break;

drivers/net/phy/phy_device.c

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,32 +1783,9 @@ int genphy_update_link(struct phy_device *phydev)
17831783
}
17841784
EXPORT_SYMBOL(genphy_update_link);
17851785

1786-
/**
1787-
* genphy_read_status - check the link status and update current link state
1788-
* @phydev: target phy_device struct
1789-
*
1790-
* Description: Check the link, then figure out the current state
1791-
* by comparing what we advertise with what the link partner
1792-
* advertises. Start by checking the gigabit possibilities,
1793-
* then move on to 10/100.
1794-
*/
1795-
int genphy_read_status(struct phy_device *phydev)
1786+
int genphy_read_lpa(struct phy_device *phydev)
17961787
{
1797-
int lpa, lpagb, err, old_link = phydev->link;
1798-
1799-
/* Update the link, but return if there was an error */
1800-
err = genphy_update_link(phydev);
1801-
if (err)
1802-
return err;
1803-
1804-
/* why bother the PHY if nothing can have changed */
1805-
if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
1806-
return 0;
1807-
1808-
phydev->speed = SPEED_UNKNOWN;
1809-
phydev->duplex = DUPLEX_UNKNOWN;
1810-
phydev->pause = 0;
1811-
phydev->asym_pause = 0;
1788+
int lpa, lpagb;
18121789

18131790
if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
18141791
if (phydev->is_gigabit_capable) {
@@ -1838,6 +1815,44 @@ int genphy_read_status(struct phy_device *phydev)
18381815
return lpa;
18391816

18401817
mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa);
1818+
}
1819+
1820+
return 0;
1821+
}
1822+
EXPORT_SYMBOL(genphy_read_lpa);
1823+
1824+
/**
1825+
* genphy_read_status - check the link status and update current link state
1826+
* @phydev: target phy_device struct
1827+
*
1828+
* Description: Check the link, then figure out the current state
1829+
* by comparing what we advertise with what the link partner
1830+
* advertises. Start by checking the gigabit possibilities,
1831+
* then move on to 10/100.
1832+
*/
1833+
int genphy_read_status(struct phy_device *phydev)
1834+
{
1835+
int err, old_link = phydev->link;
1836+
1837+
/* Update the link, but return if there was an error */
1838+
err = genphy_update_link(phydev);
1839+
if (err)
1840+
return err;
1841+
1842+
/* why bother the PHY if nothing can have changed */
1843+
if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)
1844+
return 0;
1845+
1846+
phydev->speed = SPEED_UNKNOWN;
1847+
phydev->duplex = DUPLEX_UNKNOWN;
1848+
phydev->pause = 0;
1849+
phydev->asym_pause = 0;
1850+
1851+
err = genphy_read_lpa(phydev);
1852+
if (err < 0)
1853+
return err;
1854+
1855+
if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
18411856
phy_resolve_aneg_linkmode(phydev);
18421857
} else if (phydev->autoneg == AUTONEG_DISABLE) {
18431858
int bmcr = phy_read(phydev, MII_BMCR);

include/linux/mii.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,15 @@ static inline void mii_lpa_mod_linkmode_lpa_t(unsigned long *lp_advertising,
455455
lp_advertising, lpa & LPA_LPACK);
456456
}
457457

458+
static inline void mii_ctrl1000_mod_linkmode_adv_t(unsigned long *advertising,
459+
u32 ctrl1000)
460+
{
461+
linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, advertising,
462+
ctrl1000 & ADVERTISE_1000HALF);
463+
linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, advertising,
464+
ctrl1000 & ADVERTISE_1000FULL);
465+
}
466+
458467
/**
459468
* linkmode_adv_to_lcl_adv_t
460469
* @advertising:pointer to linkmode advertising

include/linux/phy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ static inline bool phy_is_started(struct phy_device *phydev)
678678
return phydev->state >= PHY_UP;
679679
}
680680

681+
void phy_resolve_aneg_pause(struct phy_device *phydev);
681682
void phy_resolve_aneg_linkmode(struct phy_device *phydev);
682683

683684
/**
@@ -1076,6 +1077,7 @@ int genphy_config_eee_advert(struct phy_device *phydev);
10761077
int __genphy_config_aneg(struct phy_device *phydev, bool changed);
10771078
int genphy_aneg_done(struct phy_device *phydev);
10781079
int genphy_update_link(struct phy_device *phydev);
1080+
int genphy_read_lpa(struct phy_device *phydev);
10791081
int genphy_read_status(struct phy_device *phydev);
10801082
int genphy_suspend(struct phy_device *phydev);
10811083
int genphy_resume(struct phy_device *phydev);

0 commit comments

Comments
 (0)