diff mbox

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

Message ID 37f5015b186bdd51f8451ade042f90c8b39a5cc8.1276615626.git.richard.cochran@omicron.at
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Richard Cochran June 15, 2010, 4:10 p.m. UTC
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@omicron.at>
---
 drivers/net/phy/Kconfig   |   11 +++
 drivers/net/phy/dp83640.c |  158 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 168 insertions(+), 1 deletions(-)

Comments

Grant Likely June 15, 2010, 6:49 p.m. UTC | #1
On Tue, Jun 15, 2010 at 10:10 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> 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@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

Won't this break things for existing DP83640 users?

>        ---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 */

Why only one?  Is it not possible to have 2 of these PHYs in a system?

> +
>  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,

ditto here, can leave the 0s and nulls out.

> +       .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
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>
Richard Cochran June 16, 2010, 10:05 a.m. UTC | #2
On Tue, Jun 15, 2010 at 12:49:13PM -0600, Grant Likely wrote:
> Won't this break things for existing DP83640 users?

Nope, the driver was only added five patches ago, and it only offers
the timestamping stuff. The standard PHY functions just call the
generic functions, so the PHY works fine even without this driver.

> > +static struct ptp_clock *dp83640_clock;
> > +DEFINE_SPINLOCK(clock_lock); /* protects the one and only dp83640_clock */
> 
> Why only one?  Is it not possible to have 2 of these PHYs in a system?

Yes, you can have multiple PHYs, but only one PTP clock.

If you do use multiple PHYs, then you must wire their clocks together
and adjust the PTP clock on only one of the PHYs.


Thanks for your other comments,

Richard
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Grant Likely June 16, 2010, 3:10 p.m. UTC | #3
On Wed, Jun 16, 2010 at 4:05 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> On Tue, Jun 15, 2010 at 12:49:13PM -0600, Grant Likely wrote:
>> Won't this break things for existing DP83640 users?
>
> Nope, the driver was only added five patches ago, and it only offers
> the timestamping stuff. The standard PHY functions just call the
> generic functions, so the PHY works fine even without this driver.
>
>> > +static struct ptp_clock *dp83640_clock;
>> > +DEFINE_SPINLOCK(clock_lock); /* protects the one and only dp83640_clock */
>>
>> Why only one?  Is it not possible to have 2 of these PHYs in a system?
>
> Yes, you can have multiple PHYs, but only one PTP clock.
>
> If you do use multiple PHYs, then you must wire their clocks together
> and adjust the PTP clock on only one of the PHYs.
>
>
> Thanks for your other comments,

You're welcome.  Make sure to cc: linux-kernel on your next posting.
I commented on what I could, but there is a lot of code outside my
areas of expertise.  In particular the time keeping code needs to be
looked at by the maintainers in that area.

Cheers,
g.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

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);
 }