[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <E1u3Lta-000CP7-7r@rmk-PC.armlinux.org.uk>
Date: Fri, 11 Apr 2025 22:26:42 +0100
From: Russell King <rmk+kernel@...linux.org.uk>
To: Andrew Lunn <andrew@...n.ch>,
Heiner Kallweit <hkallweit1@...il.com>
Cc: "David S. Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>,
Jakub Kicinski <kuba@...nel.org>,
Kory Maincent <kory.maincent@...tlin.com>,
netdev@...r.kernel.org,
Paolo Abeni <pabeni@...hat.com>,
Richard Cochran <richardcochran@...il.com>
Subject: [PATCH RFC net-next 3/5] net: phy: add Marvell PHY PTP support
Add PTP basic support for Marvell 88E151x single port PHYs. These
PHYs support timestamping the egress and ingress of packets, but does
not support any packet modification, nor do we support any filtering
beyond selecting packets that the hardware recognises as PTP/802.1AS.
The PHYs support hardware pins for providing an external clock for the
TAI counter, and a separate pin that can be used for event capture or
generation of a trigger (either a pulse or periodic). Only event
capture is supported.
We currently use a delayed work to poll for the timestamps which is
far from ideal, but we also provide a function that can be called from
an interrupt handler - which would be good to tie into the main Marvell
PHY driver.
The driver takes inspiration from the Marvell 88E6xxx DSA and DP83640
drivers. The hardware is very similar to the implementation found in
the 88E6xxx DSA driver, but the access methods are very different,
although it may be possible to create a library that both can use
along with accessor functions.
Signed-off-by: Russell King <rmk+kernel@...linux.org.uk>
Signed-off-by: Russell King (Oracle) <rmk+kernel@...linux.org.uk>
---
drivers/net/phy/Kconfig | 13 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/marvell.c | 21 ++-
drivers/net/phy/marvell_ptp.c | 307 ++++++++++++++++++++++++++++++++++
drivers/net/phy/marvell_ptp.h | 21 +++
5 files changed, 362 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/phy/marvell_ptp.c
create mode 100644 drivers/net/phy/marvell_ptp.h
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index d29f9f7fd2e1..fb8b326f5c7e 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -240,6 +240,19 @@ config MARVELL_PHY
help
Currently has a driver for the 88E1XXX
+config MARVELL_PHY_PTP
+ bool "Marvell PHY PTP support"
+ depends on NETWORK_PHY_TIMESTAMPING
+ depends on (PTP_1588_CLOCK = y && MARVELL_PHY = y) || \
+ (PTP_1588_CLOCK && MARVELL_PHY = m)
+ select PTP_1588_CLOCK_MARVELL
+ help
+ Support PHY timestamping on Marvell 88E1510, 88E1512, 88E1514
+ and 88E1518 PHYs.
+
+ N.B. In order for this to be fully functional, your MAC driver
+ must call the skb_tx_timestamp() function.
+
config MARVELL_10G_PHY
tristate "Marvell Alaska 10Gbit PHYs"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 23ce205ae91d..9d513a18afb6 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_ICPLUS_PHY) += icplus.o
obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o
obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o
obj-$(CONFIG_LXT_PHY) += lxt.o
+obj-$(CONFIG_MARVELL_PHY_PTP) += marvell_ptp.o
obj-$(CONFIG_MARVELL_10G_PHY) += marvell10g.o
obj-$(CONFIG_MARVELL_PHY) += marvell.o
obj-$(CONFIG_MARVELL_88Q2XXX_PHY) += marvell-88q2xxx.o
diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 623292948fa7..1e9a4b300216 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -38,6 +38,8 @@
#include <asm/irq.h>
#include <linux/uaccess.h>
+#include "marvell_ptp.h"
+
#define MII_MARVELL_PHY_PAGE 22
#define MII_MARVELL_COPPER_PAGE 0x00
#define MII_MARVELL_FIBER_PAGE 0x01
@@ -3647,7 +3649,7 @@ static const struct sfp_upstream_ops m88e1510_sfp_ops = {
.disconnect_phy = phy_sfp_disconnect_phy,
};
-static int m88e1510_probe(struct phy_device *phydev)
+static int m88e15xx_probe(struct phy_device *phydev)
{
int err;
@@ -3658,6 +3660,22 @@ static int m88e1510_probe(struct phy_device *phydev)
return phy_sfp_probe(phydev, &m88e1510_sfp_ops);
}
+static int m88e1510_probe(struct phy_device *phydev)
+{
+ int err;
+
+ err = m88e15xx_probe(phydev);
+ if (err)
+ return err;
+
+ return marvell_phy_ptp_probe(phydev);
+}
+
+static void m88e1510_remove(struct phy_device *phydev)
+{
+ marvell_phy_ptp_remove(phydev);
+}
+
static struct phy_driver marvell_drivers[] = {
{
.phy_id = MARVELL_PHY_ID_88E1101,
@@ -3916,6 +3934,7 @@ static struct phy_driver marvell_drivers[] = {
.features = PHY_GBIT_FIBRE_FEATURES,
.flags = PHY_POLL_CABLE_TEST,
.probe = m88e1510_probe,
+ .remove = m88e1510_remove,
.config_init = m88e1510_config_init,
.config_aneg = m88e1510_config_aneg,
.read_status = marvell_read_status,
diff --git a/drivers/net/phy/marvell_ptp.c b/drivers/net/phy/marvell_ptp.c
new file mode 100644
index 000000000000..3ba71c44ffb0
--- /dev/null
+++ b/drivers/net/phy/marvell_ptp.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Marvell PTP driver for 88E1510, 88E1512, 88E1514 and 88E1518 PHYs
+ *
+ * Ideas taken from 88E6xxx DSA and DP83640 drivers. This file
+ * implements the packet timestamping support only (PTP). TAI
+ * support is separate.
+ */
+#include <linux/marvell_ptp.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+
+#include "marvell_ptp.h"
+
+#define MARVELL_PAGE_MISC 6
+#define GCR 20
+#define GCR_PTP_POWER_DOWN BIT(9)
+#define GCR_PTP_REF_CLOCK_SOURCE BIT(8)
+#define GCR_PTP_INPUT_SOURCE BIT(7)
+#define GCR_PTP_OUTPUT BIT(6)
+
+#define MARVELL_PAGE_PTP_PORT_1 8
+
+#define MARVELL_PAGE_TAI_GLOBAL 12
+#define MARVELL_PAGE_PTP_GLOBAL 14
+#define PTPG_READPLUS_COMMAND 14
+#define PTPG_READPLUS_DATA 15
+
+struct marvell_phy_ptp {
+ struct marvell_ptp ptp;
+ struct mii_timestamper mii_ts;
+};
+
+static struct marvell_phy_ptp *mii_ts_to_phy_ptp(struct mii_timestamper *mii_ts)
+{
+ return container_of(mii_ts, struct marvell_phy_ptp, mii_ts);
+}
+
+static bool marvell_phy_ptp_rxtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct marvell_phy_ptp *phy_ptp = mii_ts_to_phy_ptp(mii_ts);
+
+ return marvell_ptp_rxtstamp(&phy_ptp->ptp, skb, type);
+}
+
+static void marvell_phy_ptp_txtstamp(struct mii_timestamper *mii_ts,
+ struct sk_buff *skb, int type)
+{
+ struct marvell_phy_ptp *phy_ptp = mii_ts_to_phy_ptp(mii_ts);
+
+ return marvell_ptp_txtstamp(&phy_ptp->ptp, skb, type);
+}
+
+static int marvell_phy_ptp_hwtstamp(struct mii_timestamper *mii_ts,
+ struct kernel_hwtstamp_config *kcfg,
+ struct netlink_ext_ack *ack)
+{
+ struct marvell_phy_ptp *phy_ptp = mii_ts_to_phy_ptp(mii_ts);
+
+ return marvell_ptp_hwtstamp(&phy_ptp->ptp, kcfg, ack);
+}
+
+static int marvell_phy_ptp_ts_info(struct mii_timestamper *mii_ts,
+ struct kernel_ethtool_ts_info *ts_info)
+{
+ struct marvell_phy_ptp *phy_ptp = mii_ts_to_phy_ptp(mii_ts);
+
+ return marvell_ptp_ts_info(&phy_ptp->ptp, ts_info);
+}
+
+/* TAI accessor functions */
+static int marvell_phy_tai_enable(struct device *dev)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return phy_modify_paged(phydev, MARVELL_PAGE_MISC, GCR,
+ GCR_PTP_POWER_DOWN, 0);
+}
+
+static u64 marvell_phy_tai_clock_read(struct device *dev,
+ struct ptp_system_timestamp *sts)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ int err, oldpage, lo, hi;
+
+ oldpage = phy_select_page(phydev, MARVELL_PAGE_PTP_GLOBAL);
+ if (oldpage >= 0) {
+ /* 88e151x says to write 0x8e0e */
+ ptp_read_system_prets(sts);
+ err = __phy_write(phydev, PTPG_READPLUS_COMMAND, 0x8e0e);
+ ptp_read_system_postts(sts);
+ lo = __phy_read(phydev, PTPG_READPLUS_DATA);
+ hi = __phy_read(phydev, PTPG_READPLUS_DATA);
+ }
+ err = phy_restore_page(phydev, oldpage, err);
+
+ if (err || lo < 0 || hi < 0)
+ return 0;
+
+ return lo | hi << 16;
+}
+
+static int marvell_phy_tai_write(struct device *dev, u8 reg, u16 val)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return phy_write_paged(phydev, MARVELL_PAGE_TAI_GLOBAL, reg, val);
+}
+
+static int marvell_phy_tai_modify(struct device *dev, u8 reg, u16 mask, u16 val)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return phy_modify_paged(phydev, MARVELL_PAGE_TAI_GLOBAL,
+ reg, mask, val);
+}
+
+static int marvell_phy_ptp_global_write(struct device *dev, u8 reg, u16 val)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return phy_write_paged(phydev, MARVELL_PAGE_PTP_GLOBAL, reg, val);
+}
+
+/* Read the status, timestamp and PTP common header sequence from the PHY.
+ * Apparently, reading these are atomic, but there is no mention how the
+ * PHY treats this access as atomic. So, we set the DisTSOverwrite bit
+ * when configuring the PHY.
+ */
+static int marvell_phy_ptp_port_read_ts(struct device *dev,
+ struct marvell_ts *ts, u8 reg)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ int oldpage, page = MARVELL_PAGE_PTP_PORT_1 + (reg >> 4);
+ int ret;
+
+ reg &= 15;
+
+ /* Read status register */
+ oldpage = phy_select_page(phydev, page);
+ if (oldpage >= 0) {
+ ret = __phy_read(phydev, reg);
+ if (ret < 0)
+ goto restore;
+
+ ts->stat = ret;
+ if (!(ts->stat & MV_STATUS_VALID)) {
+ ret = 0;
+ goto restore;
+ }
+
+ /* Read low timestamp */
+ ret = __phy_read(phydev, reg + 1);
+ if (ret < 0)
+ goto restore;
+
+ ts->time = ret;
+
+ /* Read high timestamp */
+ ret = __phy_read(phydev, reg + 2);
+ if (ret < 0)
+ goto restore;
+
+ ts->time |= ret << 16;
+
+ /* Read sequence */
+ ret = __phy_read(phydev, reg + 3);
+ if (ret < 0)
+ goto restore;
+
+ ts->seq = ret;
+
+ /* Clear valid */
+ __phy_write(phydev, reg, 0);
+
+ ret = 1;
+ }
+restore:
+ return phy_restore_page(phydev, oldpage, ret);
+}
+
+static int marvell_phy_ptp_port_write(struct device *dev, u8 reg, u16 val)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return phy_write_paged(phydev, MARVELL_PAGE_PTP_PORT_1 + (reg >> 4),
+ reg & 15, val);
+}
+
+static int marvell_phy_ptp_port_modify(struct device *dev, u8 reg, u16 mask,
+ u16 val)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+
+ return phy_modify_paged(phydev, MARVELL_PAGE_PTP_PORT_1 + (reg >> 4),
+ reg & 15, mask, val);
+}
+
+static long marvell_phy_ptp_aux_work(struct device *dev)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ struct marvell_phy_ptp *phy_ptp;
+
+ phy_ptp = mii_ts_to_phy_ptp(phydev->mii_ts);
+
+ return marvell_ptp_aux_work(&phy_ptp->ptp);
+}
+
+static const struct marvell_ptp_ops marvell_phy_ptp_ops = {
+ .tai_enable = marvell_phy_tai_enable,
+ .tai_clock_read = marvell_phy_tai_clock_read,
+ .tai_write = marvell_phy_tai_write,
+ .tai_modify = marvell_phy_tai_modify,
+ .ptp_global_write = marvell_phy_ptp_global_write,
+ .ptp_port_read_ts = marvell_phy_ptp_port_read_ts,
+ .ptp_port_write = marvell_phy_ptp_port_write,
+ .ptp_port_modify = marvell_phy_ptp_port_modify,
+ .ptp_aux_work = marvell_phy_ptp_aux_work,
+};
+
+static const struct marvell_tai_param marvell_phy_tai_param = {
+ /* This assumes a 125MHz clock */
+ .cc_mult_num = 1 << 9,
+ .cc_mult_den = 15625U,
+ .cc_mult = 8 << 28,
+ .cc_shift = 28,
+};
+
+/* This function should be called from the PHY threaded interrupt
+ * handler to process any stored timestamps in a timely manner.
+ * The presence of an interrupt has an effect on how quickly a
+ * timestamp requiring received packet will be processed.
+ */
+irqreturn_t marvell_phy_ptp_irq(struct phy_device *phydev)
+{
+ struct marvell_phy_ptp *phy_ptp;
+
+ if (!phydev->mii_ts)
+ return IRQ_NONE;
+
+ phy_ptp = mii_ts_to_phy_ptp(phydev->mii_ts);
+
+ return marvell_ptp_irq(&phy_ptp->ptp);
+}
+EXPORT_SYMBOL_GPL(marvell_phy_ptp_irq);
+
+int marvell_phy_ptp_probe(struct phy_device *phydev)
+{
+ struct marvell_phy_ptp *phy_ptp;
+ struct marvell_tai *tai;
+ struct device *dev;
+ int err;
+
+ dev = &phydev->mdio.dev;
+
+ phy_ptp = devm_kzalloc(dev, sizeof(*phy_ptp), GFP_KERNEL);
+ if (!phy_ptp)
+ return -ENOMEM;
+
+ phy_ptp->mii_ts.rxtstamp = marvell_phy_ptp_rxtstamp;
+ phy_ptp->mii_ts.txtstamp = marvell_phy_ptp_txtstamp;
+ phy_ptp->mii_ts.hwtstamp = marvell_phy_ptp_hwtstamp;
+ phy_ptp->mii_ts.ts_info = marvell_phy_ptp_ts_info;
+
+ /* Get the TAI for this PHY. */
+ err = marvell_tai_probe(&tai, &marvell_phy_ptp_ops,
+ &marvell_phy_tai_param,
+ "Marvell PHY", dev);
+ if (err)
+ return err;
+
+ err = marvell_ptp_probe(&phy_ptp->ptp, dev, tai,
+ &marvell_phy_ptp_ops);
+ if (err) {
+ marvell_tai_remove(tai);
+ return err;
+ }
+
+ phydev->mii_ts = &phy_ptp->mii_ts;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(marvell_phy_ptp_probe);
+
+void marvell_phy_ptp_remove(struct phy_device *phydev)
+{
+ struct marvell_phy_ptp *phy_ptp;
+ struct mii_timestamper *mii_ts;
+
+ /* Disconnect from the net subsystem - we assume there is no
+ * packet activity at this point.
+ */
+ mii_ts = phydev->mii_ts;
+ phydev->mii_ts = NULL;
+
+ if (mii_ts) {
+ phy_ptp = mii_ts_to_phy_ptp(mii_ts);
+ marvell_ptp_remove(&phy_ptp->ptp);
+ marvell_tai_remove(phy_ptp->ptp.tai);
+ }
+}
+EXPORT_SYMBOL_GPL(marvell_phy_ptp_remove);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Marvell PHY PTP library");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/marvell_ptp.h b/drivers/net/phy/marvell_ptp.h
new file mode 100644
index 000000000000..7d009fe4fd23
--- /dev/null
+++ b/drivers/net/phy/marvell_ptp.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef MARVELL_PTP_H
+#define MARVELL_PTP_H
+
+#if IS_ENABLED(CONFIG_MARVELL_PHY_PTP)
+irqreturn_t marvell_phy_ptp_irq(struct phy_device *phydev);
+int marvell_phy_ptp_probe(struct phy_device *phydev);
+void marvell_phy_ptp_remove(struct phy_device *phydev);
+#else
+static inline int marvell_phy_ptp_dummy_probe(void)
+{
+ return 0;
+}
+#define marvell_phy_ptp_probe(x...) marvell_phy_ptp_dummy_probe()
+
+static inline void marvell_phy_ptp_remove(struct phy_device *phydev)
+{
+}
+#endif
+
+#endif
--
2.30.2
Powered by blists - more mailing lists