[PATCH 12/12] ptp: Added a clock driver for the National Semiconductor PHYTER.

Richard Cochran richardcochran at gmail.com
Wed Jun 16 02:10:45 EST 2010


This patch adds support for the PTP clock found on the DP83640. Only the
basic clock operations have been implemented.

Signed-off-by: Richard Cochran <richard.cochran at omicron.at>
---
 drivers/net/phy/Kconfig   |   11 +++
 drivers/net/phy/dp83640.c |  158 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 168 insertions(+), 1 deletions(-)

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 430cab1..507c68a 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -79,9 +79,20 @@ config NATIONAL_PHY
 
 config DP83640_PHY
 	tristate "Driver for the National Semiconductor DP83640 PHYTER"
+	depends on PTP_1588_CLOCK
+	depends on NETWORK_PHY_TIMESTAMPING
 	---help---
 	  Supports the DP83640 PHYTER with IEEE 1588 features.
 
+	  This driver adds support for using the DP83640 as a PTP
+	  clock. This clock is only useful if your PTP programs are
+	  getting hardware time stamps on the PTP Ethernet packets
+	  using the SO_TIMESTAMPING API.
+
+	  In order for this to work, your MAC driver must also
+	  implement the the skb_tx_timetamp() and skb_rx_timetamp()
+	  functions.
+
 config STE10XP
 	depends on PHYLIB
 	tristate "Driver for STMicroelectronics STe10Xp PHYs"
diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
index a3217ea..21eadc3 100644
--- a/drivers/net/phy/dp83640.c
+++ b/drivers/net/phy/dp83640.c
@@ -26,6 +26,7 @@
 #include <linux/netdevice.h>
 #include <linux/phy.h>
 #include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
 
 #include "dp83640_reg.h"
 
@@ -45,10 +46,13 @@ struct rxts {
 };
 
 struct dp83640_private {
+	struct phy_device *phydev;
 	int hwts_tx_en;
 	int hwts_rx_en;
 	int layer;
 	int version;
+	/* protects PTP_TDR register from concurrent access */
+	spinlock_t ptp_tdr_lock;
 	/* protects extended registers from concurrent access */
 	spinlock_t extreg_lock;
 	int page;
@@ -60,6 +64,9 @@ struct dp83640_private {
 
 /* globals */
 
+static struct ptp_clock *dp83640_clock;
+DEFINE_SPINLOCK(clock_lock); /* protects the one and only dp83640_clock */
+
 static struct sock_filter ptp_filter[] = {
 	PTP_FILTER
 };
@@ -99,6 +106,129 @@ static void ext_write(struct phy_device *phydev, int page, u32 regnum, u16 val)
 	spin_unlock(&dp83640->extreg_lock);
 }
 
+static int tdr_write(struct dp83640_private *dp83640,
+		     struct timespec *ts, u16 cmd)
+{
+	struct phy_device *phydev = dp83640->phydev;
+
+	spin_lock(&dp83640->ptp_tdr_lock);
+
+	ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec & 0xffff);/* ns[15:0] */
+	ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec >> 16);   /* ns[31:16] */
+	ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec & 0xffff); /* sec[15:0] */
+	ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec >> 16);    /* sec[31:16] */
+
+	ext_write(phydev, PAGE4, PTP_CTL, cmd);
+
+	spin_unlock(&dp83640->ptp_tdr_lock);
+
+	return 0;
+}
+
+/* ptp clock methods */
+
+static int ptp_dp83640_adjfreq(void *priv, s32 ppb)
+{
+	struct dp83640_private *dp83640 = priv;
+	struct phy_device *phydev = dp83640->phydev;
+	u64 rate;
+	int neg_adj = 0;
+	u16 hi, lo;
+
+	if (!ppb)
+		return 0;
+
+	if (ppb < 0) {
+		neg_adj = 1;
+		ppb = -ppb;
+	}
+	rate = ppb;
+	rate <<= 26;
+	rate = div_u64(rate, 1953125);
+
+	hi = (rate >> 16) & PTP_RATE_HI_MASK;
+	if (neg_adj)
+		hi |= PTP_RATE_DIR;
+
+	lo = rate & 0xffff;
+
+	ext_write(phydev, PAGE4, PTP_RATEH, hi);
+	ext_write(phydev, PAGE4, PTP_RATEL, lo);
+
+	return 0;
+}
+
+static int ptp_dp83640_adjtime(void *priv, struct timespec *ts)
+{
+	return tdr_write(priv, ts, PTP_STEP_CLK);
+}
+
+static int ptp_dp83640_gettime(void *priv, struct timespec *ts)
+{
+	struct dp83640_private *dp83640 = priv;
+	struct phy_device *phydev = dp83640->phydev;
+	unsigned int val[4];
+
+	spin_lock(&dp83640->ptp_tdr_lock);
+
+	ext_write(phydev, PAGE4, PTP_CTL, PTP_RD_CLK);
+
+	val[0] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[15:0] */
+	val[1] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[31:16] */
+	val[2] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[15:0] */
+	val[3] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[31:16] */
+
+	spin_unlock(&dp83640->ptp_tdr_lock);
+
+	ts->tv_nsec = val[0] | (val[1] << 16);
+	ts->tv_sec  = val[2] | (val[3] << 16);
+
+	return 0;
+}
+
+static int ptp_dp83640_settime(void *priv, struct timespec *ts)
+{
+	return tdr_write(priv, ts, PTP_LOAD_CLK);
+}
+
+static int ptp_dp83640_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+	/* We do not (yet) offer any ancillary features. */
+	return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+	/* We do not (yet) offer any ancillary features. */
+	return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+	/* We do not (yet) offer any ancillary features. */
+	return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_dp83640_caps = {
+	.owner		= THIS_MODULE,
+	.name		= "dp83640 timer",
+	.max_adj	= 1953124,
+	.n_alarm	= 0,
+	.n_ext_ts	= 0,
+	.n_per_out	= 0,
+	.pps		= 0,
+	.priv		= NULL,
+	.adjfreq	= ptp_dp83640_adjfreq,
+	.adjtime	= ptp_dp83640_adjtime,
+	.gettime	= ptp_dp83640_gettime,
+	.settime	= ptp_dp83640_settime,
+	.gettimer	= ptp_dp83640_gettimer,
+	.settimer	= ptp_dp83640_settimer,
+	.enable		= ptp_dp83640_enable,
+};
+
+/* time stamping methods */
+
 static int expired(struct rxts *rxts)
 {
 	return time_after(jiffies, rxts->tmo);
@@ -144,6 +274,7 @@ static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
 static int dp83640_probe(struct phy_device *phydev)
 {
 	struct dp83640_private *dp83640;
+	unsigned long flags;
 	int i;
 
 	if (sk_chk_filter(ptp_filter, ARRAY_SIZE(ptp_filter))) {
@@ -155,8 +286,9 @@ static int dp83640_probe(struct phy_device *phydev)
 	if (!dp83640)
 		return -ENOMEM;
 
+	dp83640->phydev = phydev;
+	spin_lock_init(&dp83640->ptp_tdr_lock);
 	spin_lock_init(&dp83640->extreg_lock);
-
 	INIT_LIST_HEAD(&dp83640->rxts);
 	INIT_LIST_HEAD(&dp83640->pool);
 
@@ -165,12 +297,36 @@ static int dp83640_probe(struct phy_device *phydev)
 
 	phydev->priv = dp83640;
 
+	spin_lock_irqsave(&clock_lock, flags);
+
+	if (!dp83640_clock) {
+		ptp_dp83640_caps.priv = dp83640;
+		dp83640_clock = ptp_clock_register(&ptp_dp83640_caps);
+		if (IS_ERR(dp83640_clock)) {
+			spin_unlock_irqrestore(&clock_lock, flags);
+			kfree(dp83640);
+			return PTR_ERR(dp83640_clock);
+		}
+	}
+	spin_unlock_irqrestore(&clock_lock, flags);
+
 	return 0;
 }
 
 static void dp83640_remove(struct phy_device *phydev)
 {
 	struct dp83640_private *dp83640 = phydev->priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&clock_lock, flags);
+
+	if (ptp_dp83640_caps.priv == dp83640) {
+		ptp_clock_unregister(dp83640_clock);
+		dp83640_clock = NULL;
+		ptp_dp83640_caps.priv = NULL;
+	}
+	spin_unlock_irqrestore(&clock_lock, flags);
+
 	kfree(dp83640);
 }
 
-- 
1.6.3.3



More information about the devicetree-discuss mailing list