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:
$ ethtool -T eth0Capabilities: software-transmit software-receive software-system-clockPTP Hardware Clock: none
$ ls /dev/ptp*ls: cannot access '/dev/ptp*': No such file or directoryOn 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”| board | PHY | hardware PTP |
|---|---|---|
| Compute Module 4 | BCM54210PE | yes |
| Compute Module 5 | — | yes, out of the box |
| Pi 4 Model B | BCM54213PE | no |
Here’s the part that makes this worth a page. Both PHYs report an identical ID:
$ cat /sys/class/mdio_bus/unimac-mdio--19/unimac-mdio--19:01/phy_id0x600d84a2That 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:
$ 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 branchAddress 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:
$ grep -E "CONFIG_NETWORK_PHY_TIMESTAMPING|CONFIG_BCM_NET_PHYPTP" .configCONFIG_NETWORK_PHY_TIMESTAMPING=yCONFIG_BCM_NET_PHYPTP=yThe 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.
What to actually do
Section titled “What to actually do”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.