[PATCH net-next 5/5] net: mdio: bcm-unimac: implement timestamped MDIO writes
From: James Clark
Date: Fri Oct 09 2026 - 10:41:22 EST
Implement write_sts to provide system timestamp bounds for completion
of an MDIO write. This enables tighter system timestamp bounds in PHY
implementations of gettimex64.
Take system timestamps around the command start, then add the time
from the command start until the MDC edge that clocks the last data
bit, calculated from the reference clock rate and the configured MDC
divider.
On BCM2711 that edge comes 64 MDC periods after the command start, on
average. This was measured on a Raspberry Pi CM4 with its BCM54210PE
PHY. The PHY's PHC was read with PTP_SYS_OFFSET_EXTENDED while the MDC
divider was switched between 9 and 39 through /dev/mem. Any error in
the number of periods would make the PHC offset jump at each switch.
After compensating for drift, the jump with 64 periods was under 10 ns.
The MDC divider appears to run freely: the PHC offsets spread evenly
over one MDC period at each divider. So use 63.5 and 64.5 periods as
the lower and upper bounds of the delay.
Use a 200 MHz reference rate for BCM2711 GENET, whose clock is not
described in DT.
Signed-off-by: James Clark <jjc@xxxxxxxxxx>
Assisted-by: LLM
---
When there is no clock, unimac_mdio_clk_set() assumes a 250 MHz
reference rate, and no in-tree DT gives GENET or UniMAC a clock. For
BCM2711 I have no documentation of the reference rate or of the clock
that supplies it, but MDIO busy times measured at several MDC dividers
fit a rate of 200 MHz. This patch checks the parent's compatible string,
which is unsatisfactory. I would prefer to get the rate from DT, and
would welcome suggestions for the right DT description.
The delay of 63.5 to 64.5 MDC periods has been measured only on
BCM2711, but the code assumes it holds for any UniMAC.
drivers/net/mdio/mdio-bcm-unimac.c | 105 ++++++++++++++++++++++++++++-
1 file changed, 102 insertions(+), 3 deletions(-)
diff --git a/drivers/net/mdio/mdio-bcm-unimac.c b/drivers/net/mdio/mdio-bcm-unimac.c
index 31e396cc9fb..e1ae806d1a8 100644
--- a/drivers/net/mdio/mdio-bcm-unimac.c
+++ b/drivers/net/mdio/mdio-bcm-unimac.c
@@ -16,6 +16,7 @@
#include <linux/phy.h>
#include <linux/platform_data/mdio-bcm-unimac.h>
#include <linux/platform_device.h>
+#include <linux/ptp_clock_kernel.h>
#include <linux/sched.h>
#define MDIO_CMD 0x00
@@ -42,6 +43,7 @@ struct unimac_mdio_priv {
void *wait_func_data;
struct clk *clk;
u32 clk_freq;
+ unsigned long mdio_ref_rate;
};
static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset)
@@ -73,6 +75,27 @@ static inline void unimac_mdio_start(struct unimac_mdio_priv *priv)
unimac_mdio_writel(priv, reg, MDIO_CMD);
}
+static void unimac_mdio_start_sts(struct unimac_mdio_priv *priv,
+ struct ptp_system_timestamp *sts)
+{
+ unsigned long flags;
+ u32 reg;
+
+ reg = unimac_mdio_readl(priv, MDIO_CMD);
+ reg |= MDIO_START_BUSY;
+ local_irq_save(flags);
+ ptp_read_system_prets(sts);
+ /* Order the timestamp before the relaxed command write. */
+ mb();
+ unimac_mdio_writel(priv, reg, MDIO_CMD);
+ /* Flush the posted write before taking the upper bound. */
+ unimac_mdio_readl(priv, MDIO_CMD);
+ /* Order the read-back before the system timestamp. */
+ rmb();
+ ptp_read_system_postts(sts);
+ local_irq_restore(flags);
+}
+
static int unimac_mdio_poll(void *wait_func_data)
{
struct unimac_mdio_priv *priv = wait_func_data;
@@ -127,10 +150,41 @@ static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
return ret;
}
-static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
- int reg, u16 val)
+static int unimac_mdio_sts_delays(struct unimac_mdio_priv *priv,
+ u64 *pre_ns, u64 *post_ns)
+{
+ u32 cmd, config, divisor;
+ int ret;
+
+ /* The delays assume the controller is idle. */
+ ret = read_poll_timeout(unimac_mdio_readl, cmd,
+ !(cmd & MDIO_START_BUSY),
+ 1, 1000, false, priv, MDIO_CMD);
+ if (ret)
+ return ret;
+
+ config = unimac_mdio_readl(priv, MDIO_CFG);
+ if (config & MDIO_SUPP_PREAMBLE)
+ return -EIO;
+ divisor = 2 * (((config >> MDIO_CLK_DIV_SHIFT) & MDIO_CLK_DIV_MASK) + 1);
+ /* On BCM2711 the MDC divider runs freely, so the MDC edge that
+ * clocks the last bit of a write comes 63.5 to 64.5 periods after
+ * the command start.
+ */
+ *pre_ns = div64_ul(127ULL * divisor * NSEC_PER_SEC,
+ 2 * priv->mdio_ref_rate);
+ *post_ns = div64_ul(129ULL * divisor * NSEC_PER_SEC +
+ 2 * priv->mdio_ref_rate - 1,
+ 2 * priv->mdio_ref_rate);
+
+ return 0;
+}
+
+static int unimac_mdio_write_sts(struct mii_bus *bus, int phy_id, int reg,
+ u16 val, struct ptp_system_timestamp *sts)
{
struct unimac_mdio_priv *priv = bus->priv;
+ u64 pre_ns, post_ns;
u32 cmd;
int ret;
@@ -138,19 +192,38 @@ static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
if (ret)
return ret;
+ if (sts) {
+ ret = unimac_mdio_sts_delays(priv, &pre_ns, &post_ns);
+ if (ret)
+ goto out;
+ }
+
/* Prepare the write operation */
cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) |
(reg << MDIO_REG_SHIFT) | (0xffff & val);
unimac_mdio_writel(priv, cmd, MDIO_CMD);
- unimac_mdio_start(priv);
+ if (sts) {
+ unimac_mdio_start_sts(priv, sts);
+ ptp_adjust_system_prets(sts, pre_ns);
+ ptp_adjust_system_postts(sts, post_ns);
+ } else {
+ unimac_mdio_start(priv);
+ }
ret = priv->wait_func(priv->wait_func_data);
+out:
clk_disable_unprepare(priv->clk);
return ret;
}
+static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
+ int reg, u16 val)
+{
+ return unimac_mdio_write_sts(bus, phy_id, reg, val, NULL);
+}
+
/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with
* their internal MDIO management controller making them fail to successfully
* be read from or written to for the first transaction. We insert a dummy
@@ -234,6 +307,30 @@ static int unimac_mdio_clk_set(struct unimac_mdio_priv *priv)
return ret;
}
+static bool unimac_mdio_init_sts(struct unimac_mdio_priv *priv,
+ struct device *dev)
+{
+ u32 config;
+
+ /* The reference rate is fixed, so read it once. */
+ priv->mdio_ref_rate = clk_get_rate(priv->clk);
+ /* BCM2711's 200 MHz GENET reference clock is not described in DT. */
+ if (!priv->mdio_ref_rate && dev->parent &&
+ of_device_is_compatible(dev->parent->of_node,
+ "brcm,bcm2711-genet-v5"))
+ priv->mdio_ref_rate = 200000000;
+
+ if (!priv->mdio_ref_rate)
+ return false;
+
+ if (clk_prepare_enable(priv->clk))
+ return false;
+ config = unimac_mdio_readl(priv, MDIO_CFG);
+ clk_disable_unprepare(priv->clk);
+
+ return !(config & MDIO_SUPP_PREAMBLE);
+}
+
static int unimac_mdio_probe(struct platform_device *pdev)
{
struct unimac_mdio_pdata *pdata = pdev->dev.platform_data;
@@ -292,6 +389,8 @@ static int unimac_mdio_probe(struct platform_device *pdev)
bus->parent = &pdev->dev;
bus->read = unimac_mdio_read;
bus->write = unimac_mdio_write;
+ if (unimac_mdio_init_sts(priv, &pdev->dev))
+ bus->write_sts = unimac_mdio_write_sts;
bus->reset = unimac_mdio_reset;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id);
--
2.56.0