Skip to content

No PTP on a Pi 4 Model B (but yes on a CM4)

PTP’s entire value is hardware timestamping: the network silicon stamps the packet as it crosses the wire, outside the operating system. That’s what takes scheduling, driver latency and queueing out of the measurement, and it’s why PTP reaches nanoseconds where NTP reaches microseconds.

With no PHC (PTP Hardware Clock), PTP is just a protocol — packets stamped by the kernel, on the CPU, subject to precisely the jitter you were trying to escape. A more complicated NTP with worse tooling.

On our board:

Terminal window
$ ethtool -T eth0
Capabilities:
software-transmit
software-receive
software-system-clock
PTP Hardware Clock: none
$ ls /dev/ptp*
ls: cannot access '/dev/ptp*': No such file or directory

On a CM4 that same command reports PTP Hardware Clock: 0, and /sys/class/ptp/ptp0/clock_name says bcm_phy_ptp. Same family, same era, different answer.

Two different chips — that report the same ID

Section titled “Two different chips — that report the same ID”
boardPHYhardware PTP
Compute Module 4BCM54210PEyes
Compute Module 5yes, out of the box
Pi 4 Model BBCM54213PEno

Here’s the part that makes this worth a page. Both PHYs report an identical ID:

Terminal window
$ cat /sys/class/mdio_bus/unimac-mdio--19/unimac-mdio--19:01/phy_id
0x600d84a2

That value is PHY_ID_BCM54213PE in include/linux/brcmphy.h — and a CM4 reports it too. The silicon genuinely differs; the identifier does not. So the kernel cannot tell these boards apart by asking the chip who it is.

So it tells them apart by where they’re plugged in

Section titled “So it tells them apart by where they’re plugged in”

From the Raspberry Pi kernel, drivers/net/phy/bcm-phy-ptp.c, comments and all:

struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev)
{
switch (BRCM_PHY_MODEL(phydev)) {
case PHY_ID_BCM54210E:
break;
#ifdef PHY_ID_BCM54213PE
case PHY_ID_BCM54213PE:
switch (phydev->mdio.addr) {
case 0: // CM4 - this is a BCM54210PE which supports PTP
break;
case 1: // 4B - this is a BCM54213PE which doesn't
return NULL;
default: // Unknown - assume it's BCM54210PE
break;
}
break;

The disambiguator is the MDIO bus address. A CM4’s PHY sits at address 0; a Pi 4 Model B’s sits at address 1. Ours:

Terminal window
$ ls -d /sys/class/mdio_bus/unimac-mdio--19/unimac-mdio--19:*
/sys/class/mdio_bus/unimac-mdio--19/unimac-mdio--19:01
^^ address 1 — the 4B branch

Address 1 → return NULL → no PHC is ever registered. That is the whole story.

It is not your kernel, and there is no option to flip

Section titled “It is not your kernel, and there is no option to flip”

This is the part worth internalising, because it’s a trap that looks exactly like a misconfiguration. All the support is present:

Terminal window
$ grep -E "CONFIG_NETWORK_PHY_TIMESTAMPING|CONFIG_BCM_NET_PHYPTP" .config
CONFIG_NETWORK_PHY_TIMESTAMPING=y
CONFIG_BCM_NET_PHYPTP=y

The driver is compiled in. The PHY ID is a recognised constant. The code path exists, and it runs. It runs, looks at your board, and declines. No kernel option changes that, no patch, no rpi-update — the refusal is a deliberate and correct statement about the silicon in front of it.

It also means our IRQF_NO_THREAD kernel is not the culprit. That was the obvious suspicion when we re-opened this, so we checked it first. It isn’t.

On a Pi 4 Model B: stop. Spend the effort on the PPS path, which is where the nanoseconds actually live — and it needs the help.

If you want PTP on a Pi: buy a CM4 or CM5, not a 4B. You get /dev/ptp0, ethtool -T reports hardware transmit and receive timestamping, and on the CM4 IO board you can drive a PPS out of pin 9. Geerling’s post is the guide; we’re not going to rewrite it.

Either way, run ethtool -T first. We didn’t, and burned real time on a question that one command answers.