(OSD3358) GPIO reset ethernet PHY(s)

Hi,

I have a custom board based upon the Beagle Bone Black revC3. The board occasionally has the problem that one of my two LAN8720 PHY(s) will not come up. The boot loader and kernel cannot find the PHY. The BBB C3 board includes GPIO controlled PHY reset circuit. The GPIO pin, uart0_ctsn.gpio1_8, is used to reset the LAN8710 ethernet PHY. My custom board uses that pin as uart4.rxd for my UART4. Instead, I choose gpmc_ben1.gpio1_28 as my PHY reset pin.

Here is snippet from my device tree.

davinci_mdio_default: davinci_mdio_default {
	pinctrl-single,pins = <
		/* MDIO */
		AM33XX_IOPAD(0x948, PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0)	/* (M17) mdio_data.mdio_data */
		AM33XX_IOPAD(0x94c, PIN_OUTPUT_PULLUP | MUX_MODE0)					/* (M18) mdio_clk.mdio_clk */
		/* Added to support GPIO controlled PHY reset */
		AM33XX_IOPAD(0x878, PIN_OUTPUT_PULLUP | MUX_MODE7)					/* (N14) gpmc_ben1.gpio1_28 */
	>;
};

davinci_mdio_sleep: davinci_mdio_sleep {
	pinctrl-single,pins = <
		/* MDIO reset value */
		AM33XX_IOPAD(0x948, PIN_INPUT_PULLDOWN | MUX_MODE7)	/* (M17) mdio_data.gpio0_0 */
		AM33XX_IOPAD(0x94c, PIN_INPUT_PULLDOWN | MUX_MODE7)	/* (M18) mdio_clk.gpio0_1 */
		/* Added to support GPIO controlled PHY reset */
		AM33XX_IOPAD(0x878, PIN_INPUT_PULLDOWN | MUX_MODE7)	/* (N14) gpmc_ben1.gpio1_28 */
	>;
};
&davinci_mdio {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&davinci_mdio_default>;
	pinctrl-1 = <&davinci_mdio_sleep>;
	status = "okay";
	/* Support GPIO reset */
	reset-gpios = <&gpio1 28 GPIO_ACTIVE_LOW>;
	reset-delay-us = <300>;
	reset-deassert-us = <6500>;
	
	ethphy0: ethernet-phy@0 {
		compatible = "ethernet-phy-ieee802.3-c22";
		reg = <0>;
	};

	ethphy1: ethernet-phy@1 {
		reg = <1>;
	};
};

&aes {
	status = "okay";
};

While viewing gpio1_28 with an o-scope, I am expecting that gpio1_28 should pulse low for 300ms, during kernel boot just before MDIO probes for the PHYs. My problem is the signal stays high and I never see low pulse.

I temporarily removed the GPIO support lines from the DT so that I could try to control the line with sysfs. I used these commands in attempt to control the pin.

cd /sys/class/gpio
# I used 60 because (32x1) + 28 = 60
echo 60 > export
echo out > gpio60/direction
echo 1 > gpio60/value
echo 0 > gpio60/value

The gpio1_28 never changes state. I can control other GPIO pins with sysfs, so I know that sysfs works. I cannot control gpio1_28.

Is that something special about the gpmc_ben1 pin that won’t allow it to be used as a gpio1_28?

Thank you,

Your reset node is one level too far down..

RCN,

Thank you for your guidance. Here is snippet of my change.

&davinci_mdio {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&davinci_mdio_default>;
	pinctrl-1 = <&davinci_mdio_sleep>;
	status = "okay";
	
	ethphy0: ethernet-phy@0 {
		compatible = "ethernet-phy-ieee802.3-c22";
		reg = <0>;
		/* Support GPIO reset */
		reset-gpios = <&gpio1 28 GPIO_ACTIVE_LOW>;
		reset-delay-us = <300>;
		reset-deassert-us = <6500>;
	};

	ethphy1: ethernet-phy@1 {
		reg = <1>;
	};
};

My board has two PHYs. Both of the Phy’s nRST pins are connected to the reset circuit. When the reset is asserted then both PHYs will be reset. I added the support for reset only to PHY0. My thought is that only one of he PHYs needs support for reset and will reset the other.

Since the reset signal is common to both PHYS, does only one PHY need the ‘support for reset’ properties or do they both need the properties?

Thank you,

as long as it’s defined in one, it’ll reset both..

RCN,

I have put the ‘support for reset’ properties in the ethphy0 node. Success! Using a o-scope shows a single reset pulse. Viewing the kernel code, mdio_bus.c mdiobus_register_device() gets called for each device in the DT (ethphy0, ethphy1…). However I cannot find how the kernel decides the order which the PHYs are registered. I think the order is important because the reset should be done before any PHY is probed for.

Will ethphy0 always be the first phy registered (probed)?

Thank you,

This depends on the specific kernel version, but most modern Major releases, do this in parallel for most of the supported peripherals.

RCN,

My kernel version is 6.1.134.

My previous snippets of the &davinci_mdio {} node have an error. the ‘reset-delay-us’ property is not correct. Instead, it should be ‘reset-assert-us’. Here is my corrected snippet.

&davinci_mdio {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&davinci_mdio_default>;
	pinctrl-1 = <&davinci_mdio_sleep>;
	status = "okay";
	
	ethphy0: ethernet-phy@0 {
		reg = <0>;
		/* Support GPIO reset */
		reset-gpios = <&gpio1 28 GPIO_ACTIVE_LOW>;
		reset-assert-us = <300>;
		reset-deassert-us = <50000>;
	};

	ethphy1: ethernet-phy@1 {
		reg = <1>;
	};
};

O-scope screen shot of reset signal during kernel boot. Scope probe is connected to BBB ETH_RESETn, pin-4 of the SN74AHC1G09DCKR. I noticed that the 300us value measured appox 0.640us pulse width.

Here are some other values I tried.

assert value = pulse width
10000us = 17.8ms
5000 = 7.8ms
1000 = 2.1ms
500 = 1.0ms
300 = 0.64ms

Thank you,

Here’s the values we eventually settled on with 6.1.x with an smsc phy… Making sure you're not a bot!

&davinci_mdio_sw {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&davinci_mdio_default>;
	pinctrl-1 = <&davinci_mdio_sleep>;

	ethphy0: ethernet-phy@0 {
		reg = <0>;
		/* Support GPIO reset on revision C3 boards */
		reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
		reset-assert-us = <300>;
		reset-deassert-us = <50000>;
	};
};

RCN,

I understand the reset-assert-us value controls how long the reset pulse is held low. My guess is the reset-deassert-us value controls how to wait, after the pulse goes high, before attempting to probe for the PHY(s). It allows time for the PHYs to boot (if boot is the correct word here).

Do I understand correctly?

Thank you,

Hi all,

I still have the phy stability problem even after adding properties to the device tree that enable the GPIO controlled PHY reset. I understand there are two nodes where the GPIO line is controlled. I apologize if my terminology (node, properties, channel…) is not accurate.

  1. At the MDIO node. This node has the properties reset-gpios, reset-delay-us and reset-post-delay-us. The MDIO driver will use these properties to control the GPIO before probing for device on the MDIO channel. The GPIO line will reset all devices on the associated MDIO channel.
  2. At the PHY node. This node has the properties reset-gpios, reset-assert-us and reset-deassert-us. The PHY driver will use these properties to control the GPIO before probing for the associated PHY. If you have multiple PHYs, each with a dedicated GPIO line, you can control the reset of PHY.

My custom board has one GPIO line connected to the nRST of two PHYs. The MDIO channel only has these two PHYs devices. I added the PHY node properties to phy0 (zero). The GPIO line pulsed before probing for phy0 and did not pulse before probing for phy1. I was concerned about the possibility that phy1 could be probed before phy0. I also noticed that the reset-assert-us value entered is not the same as what an oscilloscope pulse width shows. A value of 300 is actually longer. Since both my phy’s nRST are connected to one GPIO, I removed the PHY node properties and added the MDIO node properties. As expected the GPIO line pulsed before the MDIO driver attempted to probe for the devices on the MDIO channel. I again noticed that the gpio-delay-us value did not match what the oscope pulse width showed.

To check if the MDIO node GPIO reset fixed the unstable PHY problem, I setup another GPIO line to control an external delay timer that interrupts the power supplied to the custom board for 10 seconds. I created a script that runs after kernel startup that checks for the existence of devices at /sys/bus/mdio_bus/devices/4a101000.mdio:00 and 4a101000.mdio:01. If they both exist then the script will toggle the GPIO controlling the power supply and reboot the board. If one device (or both) does not exist, the script would not turn the power off so that I can see the failure. After approx 300 reboots, I got a device is missing failure. The test is repeatable. One of the PHYs is broken even with the kernel controlling the GPIO reset line.

#!/bin/sh

if [ -e /sys/bus/mdio_bus/devices/4a101000.mdio:00 -a -e /sys/bus/mdio_bus/devices/4a101000.mdio:01 ]
then
  # PHY devices exist
  echo "No problem, rebooting..."
  echo 36 > /sys/class/gpio/export
  echo high > /sys/class/gpio/gpio36/direction
  echo 0 > /sys/class/gpio/gpio36/value
else
  # PHY device(s) do not exist, so problem occurred. Don't reboot
  echo "PROBLEM FOUND!"
fi

I thought then to use a script to control the GPIO line instead of the kernel. I removed the MDIO node properties (the PHY node properties were previously removed). I found that I could unbind the MDIO driver from the MDIO device, then use /sys/class/gpio/gpioxx/value to pulse the GPIO PHY reset line and then rebind the MDIO driver. I found that the MDIO driver re-probes for the PHYs. I don’t understand how/why the MDIO unbind/bind causes the driver to re-probe the PHYs, just that it does. The script then checks again for the existence of the devices. I have rebooted 10K+ times. The script has fixed the missing PHY each time. The oscope pulse width matches the usleep value. It may not be an elegant solution but it is working for me.

resetphy script

#!/bin/sh
echo "Begin resetphy script"
logger -t "resetphy" "Attempting a GPIO controlled ethernet PHY reset"
echo "unbind"
echo 4a100000.ethernet > /sys/bus/platform/drivers/cpsw/unbind
usleep 100
echo "GPIO export"
echo 28 > /sys/class/gpio/export
echo "GPIO set direction"
echo high > /sys/class/gpio/gpio28/direction
echo "GPIO assert"
echo 0 > /sys/class/gpio/gpio28/value
usleep 100000
echo "GPIO deassert"
echo 1 > /sys/class/gpio/gpio28/value
usleep 100
echo "bind"
echo 4a100000.ethernet > /sys/bus/platform/drivers/cpsw/bind
usleep 100
echo "run S10 script"
/home/wmi/sysext.d/S10network
echo "Done resetphy script"

checkmdio script

#!/bin/sh
# 20260617 initial release
# phyxx_is_missing: 0=phy is not missing, 1=phy is missing after reboot, 2=phy is still missing after GPIO reset
phy00_is_missing=0
phy01_is_missing=0
if [ ! -e /sys/bus/mdio_bus/devices/4a101000.mdio:00 ]
then
  phy00_is_missing=1
  echo "checkmdio: ethernet PHY:00 device is missing"
  logger -t "checkmdio" "PHY:00 device is missing"
fi
if [ ! -e /sys/bus/mdio_bus/devices/4a101000.mdio:01 ]
then
  phy01_is_missing=1
  echo "checkmdio: ethernet PHY:01 device is missing"
  logger -t "checkmdio" "PHY:01 device is missing"
fi
if [ $phy00_is_missing = 0 -a $phy01_is_missing = 0 ]
then
  echo ""
  echo "checkmdio: ethernet PHY:00 and PHY:01 devices exist, continuing..."
  logger -t  "checkmdio" "ethernet PHY devices exist, continuing..."
else
  # at least one PHY device is missing after reboot. attempt GPIO controlled reset of PHYs
  resetphy
  if [ ! -e /sys/bus/mdio_bus/devices/4a101000.mdio:00 ]
  then
    phy00_is_missing=2
    echo "checkmdio: ethernet PHY:00 device is still missing"
    logger -t "checkmdio" "PHY:00 device is still missing"
  fi
  if [ ! -e /sys/bus/mdio_bus/devices/4a101000.mdio:01 ]
  then
    phy01_is_missing=2
    echo "checkmdio: ethernet PHY:01 device is still missing"
    logger -t "checkmdio" "PHY:01 device is still missing"
  fi
  if [ $phy00_is_missing = 2 -o $phy01_is_missing = 2 ]
  then
    echo "checkmdio: ethernet PHY reset FAILED, continuing..."
    logger -t "checkmdio" "ethernet PHY reset failed!"
  else
    echo "checkmdio: ethernet PHY reset successful, continuing..."
    logger -t "checkmdio" "ethernet PHY reset successful"
  fi
fi

I would like to know command line command to trigger the MDIO driver to re-probe the channel for devices without using unbind/bind.

Thank you,