2017-12-07 14:44:52

by Richard Leitner

[permalink] [raw]
Subject: [PATCH net-next v4 0/4] net: fec: fix refclk enable for SMSC LAN8710/20

From: Richard Leitner <[email protected]>

This patch series fixes the use of the SMSC LAN8710/20 with a Freescale ETH
when the refclk is generated by the FSL.

This patchset depends on the "phylib: Add device reset GPIO support" patch
submitted by Geert Uytterhoeven/Sergei Shtylyov, which was merged to
net-next as commit bafbdd527d56 ("phylib: Add device reset GPIO support").

Changes v4:
- simplify dts parsing
- simplify reset delay evaluation and execution
- fec: ensure to only reset once during fec_enet_open()
- remove dependency notes from commit message
- add reviews and acks

Changes v3:
- use phylib to hard-reset the PHY
- implement reset delays in phylib
- add new phylib API & flag (PHY_RST_AFTER_CLK_EN) to determine if
a PHY is affected

Changes v2:
- simplify and fix fec_reset_phy function to support multiple calls
- include: linux: phy: harmonize phy_id{,_mask} type
- reset the phy instead of not turning the clock on and off
(which would have caused a power consumption regression)

Richard Leitner (4):
phylib: Add device reset delay support
phylib: add reset after clk enable support
net: phy: smsc: LAN8710/20: add PHY_RST_AFTER_CLK_EN flag
net: fec: add phy_reset_after_clk_enable() support

Documentation/devicetree/bindings/net/phy.txt | 10 ++++++++++
drivers/net/ethernet/freescale/fec_main.c | 20 ++++++++++++++++++++
drivers/net/phy/mdio_device.c | 13 +++++++++++--
drivers/net/phy/phy_device.c | 24 ++++++++++++++++++++++++
drivers/net/phy/smsc.c | 2 +-
drivers/of/of_mdio.c | 4 ++++
include/linux/mdio.h | 2 ++
include/linux/phy.h | 2 ++
8 files changed, 74 insertions(+), 3 deletions(-)

--
2.11.0


2017-12-07 14:44:55

by Richard Leitner

[permalink] [raw]
Subject: [PATCH net-next v4 1/4] phylib: Add device reset delay support

From: Richard Leitner <[email protected]>

Some PHYs need a minimum time after the reset gpio was asserted and/or
deasserted. To ensure we meet these timing requirements add two new
optional devicetree parameters for the phy: reset-delay-us and
reset-post-delay-us.

Signed-off-by: Richard Leitner <[email protected]>
Reviewed-by: Geert Uytterhoeven <[email protected]>
---
Documentation/devicetree/bindings/net/phy.txt | 10 ++++++++++
drivers/net/phy/mdio_device.c | 13 +++++++++++--
drivers/of/of_mdio.c | 4 ++++
include/linux/mdio.h | 2 ++
4 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/phy.txt b/Documentation/devicetree/bindings/net/phy.txt
index c05479f5ac7c..72860ce7f610 100644
--- a/Documentation/devicetree/bindings/net/phy.txt
+++ b/Documentation/devicetree/bindings/net/phy.txt
@@ -55,6 +55,12 @@ Optional Properties:

- reset-gpios: The GPIO phandle and specifier for the PHY reset signal.

+- reset-delay-us: Delay after the reset was asserted in microseconds.
+ If this property is missing the delay will be skipped.
+
+- reset-post-delay-us: Delay after the reset was deasserted in microseconds.
+ If this property is missing the delay will be skipped.
+
Example:

ethernet-phy@0 {
@@ -62,4 +68,8 @@ ethernet-phy@0 {
interrupt-parent = <&PIC>;
interrupts = <35 IRQ_TYPE_EDGE_RISING>;
reg = <0>;
+
+ reset-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;
+ reset-delay-us = <1000>;
+ reset-post-delay-us = <2000>;
};
diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c
index 75d97dd9fb28..0423280c88fe 100644
--- a/drivers/net/phy/mdio_device.c
+++ b/drivers/net/phy/mdio_device.c
@@ -24,6 +24,7 @@
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/unistd.h>
+#include <linux/delay.h>

void mdio_device_free(struct mdio_device *mdiodev)
{
@@ -118,8 +119,16 @@ EXPORT_SYMBOL(mdio_device_remove);

void mdio_device_reset(struct mdio_device *mdiodev, int value)
{
- if (mdiodev->reset)
- gpiod_set_value(mdiodev->reset, value);
+ unsigned int d;
+
+ if (!mdiodev->reset)
+ return;
+
+ gpiod_set_value(mdiodev->reset, value);
+
+ d = value ? mdiodev->reset_delay : mdiodev->reset_post_delay;
+ if (d)
+ usleep_range(d, d + min_t(unsigned int, d / 10, 100));
}
EXPORT_SYMBOL(mdio_device_reset);

diff --git a/drivers/of/of_mdio.c b/drivers/of/of_mdio.c
index 98258583abb0..7c8767176315 100644
--- a/drivers/of/of_mdio.c
+++ b/drivers/of/of_mdio.c
@@ -77,6 +77,10 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio,
if (of_property_read_bool(child, "broken-turn-around"))
mdio->phy_ignore_ta_mask |= 1 << addr;

+ of_property_read_u32(child, "reset-delay-us", &phy->mdio.reset_delay);
+ of_property_read_u32(child, "reset-post-delay-us",
+ &phy->mdio.reset_post_delay);
+
/* Associate the OF node with the device structure so it
* can be looked up later */
of_node_get(child);
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index 92d4e55ffe67..e37c21d8eb19 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h
@@ -41,6 +41,8 @@ struct mdio_device {
int addr;
int flags;
struct gpio_desc *reset;
+ unsigned int reset_delay;
+ unsigned int reset_post_delay;
};
#define to_mdio_device(d) container_of(d, struct mdio_device, dev)

--
2.11.0

2017-12-07 14:44:54

by Richard Leitner

[permalink] [raw]
Subject: [PATCH net-next v4 2/4] phylib: add reset after clk enable support

From: Richard Leitner <[email protected]>

Some PHYs need the refclk to be a continuous clock. Therefore they don't
allow turning it off and on again during operation. Nonetheless such a
clock switching is performed by some ETH drivers (namely FEC [1]) for
power saving reasons. An example for an affected PHY is the
SMSC/Microchip LAN8720 in "REF_CLK In Mode".

In order to provide a uniform method to overcome this problem this patch
adds a new phy_driver flag (PHY_RST_AFTER_CLK_EN) and corresponding
function phy_reset_after_clk_enable() to the phylib. These should be
used to trigger reset of the PHY after the refclk is switched on again.

[1] commit e8fcfcd5684a ("net: fec: optimize the clock management to save power")

Signed-off-by: Richard Leitner <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
---
drivers/net/phy/phy_device.c | 24 ++++++++++++++++++++++++
include/linux/phy.h | 2 ++
2 files changed, 26 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 1de5e242b8b4..462c17ed87b8 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1218,6 +1218,30 @@ int phy_loopback(struct phy_device *phydev, bool enable)
}
EXPORT_SYMBOL(phy_loopback);

+/**
+ * phy_reset_after_clk_enable - perform a PHY reset if needed
+ * @phydev: target phy_device struct
+ *
+ * Description: Some PHYs are known to need a reset after their refclk was
+ * enabled. This function evaluates the flags and perform the reset if it's
+ * needed. Returns < 0 on error, 0 if the phy wasn't reset and 1 if the phy
+ * was reset.
+ */
+int phy_reset_after_clk_enable(struct phy_device *phydev)
+{
+ if (!phydev || !phydev->drv)
+ return -ENODEV;
+
+ if (phydev->drv->flags & PHY_RST_AFTER_CLK_EN) {
+ phy_device_reset(phydev, 1);
+ phy_device_reset(phydev, 0);
+ return 1;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(phy_reset_after_clk_enable);
+
/* Generic PHY support and helper functions */

/**
diff --git a/include/linux/phy.h b/include/linux/phy.h
index d3037e2ffbc4..c4b4715caa21 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -59,6 +59,7 @@

#define PHY_HAS_INTERRUPT 0x00000001
#define PHY_IS_INTERNAL 0x00000002
+#define PHY_RST_AFTER_CLK_EN 0x00000004
#define MDIO_DEVICE_IS_PHY 0x80000000

/* Interface Mode definitions */
@@ -853,6 +854,7 @@ int phy_aneg_done(struct phy_device *phydev);

int phy_stop_interrupts(struct phy_device *phydev);
int phy_restart_aneg(struct phy_device *phydev);
+int phy_reset_after_clk_enable(struct phy_device *phydev);

static inline void phy_device_reset(struct phy_device *phydev, int value)
{
--
2.11.0

2017-12-07 14:46:42

by Richard Leitner

[permalink] [raw]
Subject: [PATCH net-next v4 4/4] net: fec: add phy_reset_after_clk_enable() support

From: Richard Leitner <[email protected]>

Some PHYs (for example the SMSC LAN8710/LAN8720) doesn't allow turning
the refclk on and off again during operation (according to their
datasheet). Nonetheless exactly this behaviour was introduced for power
saving reasons by commit e8fcfcd5684a ("net: fec: optimize the clock management to save power").
Therefore add support for the phy_reset_after_clk_enable function from
phylib to mitigate this issue.

Generally speaking this issue is only relevant if the ref clk for the
PHY is generated by the SoC and therefore the PHY is configured to
"REF_CLK In Mode". In our specific case (PCB) this problem does occur at
about every 10th to 50th POR of an LAN8710 connected to an i.MX6SOLO
SoC. The typical symptom of this problem is a "swinging" ethernet link.
Similar issues were reported by users of the NXP forum:
https://community.nxp.com/thread/389902
https://community.nxp.com/message/309354
With this patch applied the issue didn't occur for at least a few
hundret PORs of our board.

Fixes: e8fcfcd5684a ("net: fec: optimize the clock management to save power")
Signed-off-by: Richard Leitner <[email protected]>
---
drivers/net/ethernet/freescale/fec_main.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)

diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c
index 610573855213..2d1b06579c1a 100644
--- a/drivers/net/ethernet/freescale/fec_main.c
+++ b/drivers/net/ethernet/freescale/fec_main.c
@@ -1862,6 +1862,8 @@ static int fec_enet_clk_enable(struct net_device *ndev, bool enable)
ret = clk_prepare_enable(fep->clk_ref);
if (ret)
goto failed_clk_ref;
+
+ phy_reset_after_clk_enable(ndev->phydev);
} else {
clk_disable_unprepare(fep->clk_ahb);
clk_disable_unprepare(fep->clk_enet_out);
@@ -2834,6 +2836,7 @@ fec_enet_open(struct net_device *ndev)
{
struct fec_enet_private *fep = netdev_priv(ndev);
int ret;
+ bool reset_again;

ret = pm_runtime_get_sync(&fep->pdev->dev);
if (ret < 0)
@@ -2844,6 +2847,17 @@ fec_enet_open(struct net_device *ndev)
if (ret)
goto clk_enable;

+ /* During the first fec_enet_open call the PHY isn't probed at this
+ * point. Therefore the phy_reset_after_clk_enable() call within
+ * fec_enet_clk_enable() fails. As we need this reset in order to be
+ * sure the PHY is working correctly we check if we need to reset again
+ * later when the PHY is probed
+ */
+ if (ndev->phydev && ndev->phydev->drv)
+ reset_again = false;
+ else
+ reset_again = true;
+
/* I should reset the ring buffers here, but I don't yet know
* a simple way to do that.
*/
@@ -2860,6 +2874,12 @@ fec_enet_open(struct net_device *ndev)
if (ret)
goto err_enet_mii_probe;

+ /* Call phy_reset_after_clk_enable() again if it failed during
+ * phy_reset_after_clk_enable() before because the PHY wasn't probed.
+ */
+ if (reset_again)
+ phy_reset_after_clk_enable(ndev->phydev);
+
if (fep->quirks & FEC_QUIRK_ERR006687)
imx6q_cpuidle_fec_irqs_used();

--
2.11.0

2017-12-07 14:47:20

by Richard Leitner

[permalink] [raw]
Subject: [PATCH net-next v4 3/4] net: phy: smsc: LAN8710/20: add PHY_RST_AFTER_CLK_EN flag

From: Richard Leitner <[email protected]>

The Microchip/SMSC LAN8710/LAN8720 PHYs need (according to their
datasheet [1]) a continuous REF_CLK when configured to "REF_CLK In Mode".
Therefore set the PHY_RST_AFTER_CLK_EN flag for those PHYs to let the
ETH driver reset them after the REF_CLK is enabled.

[1] http://ww1.microchip.com/downloads/en/DeviceDoc/00002165B.pdf

Signed-off-by: Richard Leitner <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
---
drivers/net/phy/smsc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c
index a1961ba87e2b..be399d645224 100644
--- a/drivers/net/phy/smsc.c
+++ b/drivers/net/phy/smsc.c
@@ -312,7 +312,7 @@ static struct phy_driver smsc_phy_driver[] = {
.name = "SMSC LAN8710/LAN8720",

.features = PHY_BASIC_FEATURES,
- .flags = PHY_HAS_INTERRUPT,
+ .flags = PHY_HAS_INTERRUPT | PHY_RST_AFTER_CLK_EN,

.probe = smsc_phy_probe,

--
2.11.0

2017-12-07 14:52:22

by Geert Uytterhoeven

[permalink] [raw]
Subject: Re: [PATCH net-next v4 1/4] phylib: Add device reset delay support

Hi Richard,

On Thu, Dec 7, 2017 at 3:43 PM, Richard Leitner <[email protected]> wrote:
> --- a/drivers/net/phy/mdio_device.c
> +++ b/drivers/net/phy/mdio_device.c
> @@ -24,6 +24,7 @@
> #include <linux/slab.h>
> #include <linux/string.h>
> #include <linux/unistd.h>
> +#include <linux/delay.h>
>
> void mdio_device_free(struct mdio_device *mdiodev)
> {
> @@ -118,8 +119,16 @@ EXPORT_SYMBOL(mdio_device_remove);
>
> void mdio_device_reset(struct mdio_device *mdiodev, int value)
> {
> - if (mdiodev->reset)
> - gpiod_set_value(mdiodev->reset, value);
> + unsigned int d;
> +
> + if (!mdiodev->reset)
> + return;
> +
> + gpiod_set_value(mdiodev->reset, value);
> +
> + d = value ? mdiodev->reset_delay : mdiodev->reset_post_delay;
> + if (d)
> + usleep_range(d, d + min_t(unsigned int, d / 10, 100));

Oops, I meant "max_t", not "min_t", else the upper limit can be "d + 0",
which is not what we want.

Sorry, my fault.

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

2017-12-07 15:11:13

by Richard Leitner

[permalink] [raw]
Subject: Re: [PATCH net-next v4 1/4] phylib: Add device reset delay support

Hi Geert,

On 12/07/2017 03:52 PM, Geert Uytterhoeven wrote:
> Hi Richard,
>
> On Thu, Dec 7, 2017 at 3:43 PM, Richard Leitner <[email protected]> wrote:
>> --- a/drivers/net/phy/mdio_device.c
>> +++ b/drivers/net/phy/mdio_device.c
>> @@ -24,6 +24,7 @@
>> #include <linux/slab.h>
>> #include <linux/string.h>
>> #include <linux/unistd.h>
>> +#include <linux/delay.h>
>>
>> void mdio_device_free(struct mdio_device *mdiodev)
>> {
>> @@ -118,8 +119,16 @@ EXPORT_SYMBOL(mdio_device_remove);
>>
>> void mdio_device_reset(struct mdio_device *mdiodev, int value)
>> {
>> - if (mdiodev->reset)
>> - gpiod_set_value(mdiodev->reset, value);
>> + unsigned int d;
>> +
>> + if (!mdiodev->reset)
>> + return;
>> +
>> + gpiod_set_value(mdiodev->reset, value);
>> +
>> + d = value ? mdiodev->reset_delay : mdiodev->reset_post_delay;
>> + if (d)
>> + usleep_range(d, d + min_t(unsigned int, d / 10, 100));
>
> Oops, I meant "max_t", not "min_t", else the upper limit can be "d + 0",
> which is not what we want.

You're right...

> Sorry, my fault.

I just copied it over from you suggestion without thinking about it...
So it's definitely my fault too ;-)

I'll wait for some more comments and send a new version next week.

regards;Richard.L