net: dsa: mv88e6xxx: expose switch time as a PTP hardware clock
authorBrandon Streiff <brandon.streiff@ni.com>
Wed, 14 Feb 2018 00:07:45 +0000 (01:07 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 14 Feb 2018 19:33:36 +0000 (14:33 -0500)
This patch adds basic support for exposing the 32-bit timestamp counter
inside the mv88e6xxx switch as a ptp_clock.

Adjfine implemented by Richard Cochran.
Andrew Lunn: fix return value of PTP stub function.

Signed-off-by: Brandon Streiff <brandon.streiff@ni.com>
Signed-off-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/mv88e6xxx/Kconfig
drivers/net/dsa/mv88e6xxx/Makefile
drivers/net/dsa/mv88e6xxx/chip.c
drivers/net/dsa/mv88e6xxx/chip.h
drivers/net/dsa/mv88e6xxx/ptp.c [new file with mode: 0644]
drivers/net/dsa/mv88e6xxx/ptp.h [new file with mode: 0644]

index 1aaa7a9..ae9e7f7 100644 (file)
@@ -18,3 +18,13 @@ config NET_DSA_MV88E6XXX_GLOBAL2
 
          It is required on most chips. If the chip you compile the support for
          doesn't have such registers set, say N here. In doubt, say Y.
+
+config NET_DSA_MV88E6XXX_PTP
+       bool "PTP support for Marvell 88E6xxx"
+       default n
+       depends on NET_DSA_MV88E6XXX_GLOBAL2
+       imply NETWORK_PHY_TIMESTAMPING
+       imply PTP_1588_CLOCK
+       help
+         Say Y to enable PTP hardware timestamping on Marvell 88E6xxx switch
+         chips that support it.
index bdbbbf7..40a423c 100644 (file)
@@ -8,4 +8,5 @@ mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2.o
 mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_avb.o
 mv88e6xxx-objs += phy.o
 mv88e6xxx-objs += port.o
+mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
 mv88e6xxx-objs += serdes.o
index 413d50e..a795efd 100644 (file)
@@ -38,6 +38,7 @@
 #include "global2.h"
 #include "phy.h"
 #include "port.h"
+#include "ptp.h"
 #include "serdes.h"
 
 static void assert_reg_lock(struct mv88e6xxx_chip *chip)
@@ -2092,6 +2093,13 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
        if (err)
                goto unlock;
 
+       /* Setup PTP Hardware Clock */
+       if (chip->info->ptp_support) {
+               err = mv88e6xxx_ptp_setup(chip);
+               if (err)
+                       goto unlock;
+       }
+
 unlock:
        mutex_unlock(&chip->reg_lock);
 
@@ -3484,6 +3492,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_DSA,
+               .ptp_support = true,
                .ops = &mv88e6191_ops,
        },
 
@@ -3504,6 +3513,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_EDSA,
+               .ptp_support = true,
                .ops = &mv88e6240_ops,
        },
 
@@ -3524,6 +3534,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_DSA,
+               .ptp_support = true,
                .ops = &mv88e6290_ops,
        },
 
@@ -3543,6 +3554,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_EDSA,
+               .ptp_support = true,
                .ops = &mv88e6320_ops,
        },
 
@@ -3561,6 +3573,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .atu_move_port_mask = 0xf,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_EDSA,
+               .ptp_support = true,
                .ops = &mv88e6321_ops,
        },
 
@@ -3580,6 +3593,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_EDSA,
+               .ptp_support = true,
                .ops = &mv88e6341_ops,
        },
 
@@ -3640,6 +3654,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_EDSA,
+               .ptp_support = true,
                .ops = &mv88e6352_ops,
        },
        [MV88E6390] = {
@@ -3659,6 +3674,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_DSA,
+               .ptp_support = true,
                .ops = &mv88e6390_ops,
        },
        [MV88E6390X] = {
@@ -3678,6 +3694,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
                .pvt = true,
                .multi_chip = true,
                .tag_protocol = DSA_TAG_PROTO_DSA,
+               .ptp_support = true,
                .ops = &mv88e6390x_ops,
        },
 };
@@ -4031,6 +4048,9 @@ static void mv88e6xxx_remove(struct mdio_device *mdiodev)
        struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
        struct mv88e6xxx_chip *chip = ds->priv;
 
+       if (chip->info->ptp_support)
+               mv88e6xxx_ptp_free(chip);
+
        mv88e6xxx_phy_destroy(chip);
        mv88e6xxx_unregister_switch(chip);
        mv88e6xxx_mdios_unregister(chip);
index 5467c86..92e5fd6 100644 (file)
@@ -16,6 +16,8 @@
 #include <linux/irq.h>
 #include <linux/gpio/consumer.h>
 #include <linux/phy.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/timecounter.h>
 #include <net/dsa.h>
 
 #ifndef UINT64_MAX
@@ -126,6 +128,9 @@ struct mv88e6xxx_info {
         */
        u8 atu_move_port_mask;
        const struct mv88e6xxx_ops *ops;
+
+       /* Supports PTP */
+       bool ptp_support;
 };
 
 struct mv88e6xxx_atu_entry {
@@ -210,6 +215,16 @@ struct mv88e6xxx_chip {
        int watchdog_irq;
        int atu_prob_irq;
        int vtu_prob_irq;
+
+       /* This cyclecounter abstracts the switch PTP time.
+        * reg_lock must be held for any operation that read()s.
+        */
+       struct cyclecounter     tstamp_cc;
+       struct timecounter      tstamp_tc;
+       struct delayed_work     overflow_work;
+
+       struct ptp_clock        *ptp_clock;
+       struct ptp_clock_info   ptp_clock_info;
 };
 
 struct mv88e6xxx_bus_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c
new file mode 100644 (file)
index 0000000..6bc643c
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Marvell 88E6xxx Switch PTP support
+ *
+ * Copyright (c) 2008 Marvell Semiconductor
+ *
+ * Copyright (c) 2017 National Instruments
+ *      Erik Hons <erik.hons@ni.com>
+ *      Brandon Streiff <brandon.streiff@ni.com>
+ *      Dane Wagner <dane.wagner@ni.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "chip.h"
+#include "global2.h"
+#include "ptp.h"
+
+/* Raw timestamps are in units of 8-ns clock periods. */
+#define CC_SHIFT       28
+#define CC_MULT                (8 << CC_SHIFT)
+#define CC_MULT_NUM    (1 << 9)
+#define CC_MULT_DEM    15625ULL
+
+#define TAI_EVENT_WORK_INTERVAL msecs_to_jiffies(100)
+
+#define cc_to_chip(cc) container_of(cc, struct mv88e6xxx_chip, tstamp_cc)
+#define ptp_to_chip(ptp) container_of(ptp, struct mv88e6xxx_chip, \
+                                     ptp_clock_info)
+#define dw_overflow_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \
+                                            overflow_work)
+
+static int mv88e6xxx_tai_read(struct mv88e6xxx_chip *chip, int addr,
+                             u16 *data, int len)
+{
+       if (!chip->info->ops->avb_ops->tai_read)
+               return -EOPNOTSUPP;
+
+       return chip->info->ops->avb_ops->tai_read(chip, addr, data, len);
+}
+
+static u64 mv88e6xxx_ptp_clock_read(const struct cyclecounter *cc)
+{
+       struct mv88e6xxx_chip *chip = cc_to_chip(cc);
+       u16 phc_time[2];
+       int err;
+
+       err = mv88e6xxx_tai_read(chip, MV88E6XXX_TAI_TIME_LO, phc_time,
+                                ARRAY_SIZE(phc_time));
+       if (err)
+               return 0;
+       else
+               return ((u32)phc_time[1] << 16) | phc_time[0];
+}
+
+static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+       struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
+       int neg_adj = 0;
+       u32 diff, mult;
+       u64 adj;
+
+       if (scaled_ppm < 0) {
+               neg_adj = 1;
+               scaled_ppm = -scaled_ppm;
+       }
+       mult = CC_MULT;
+       adj = CC_MULT_NUM;
+       adj *= scaled_ppm;
+       diff = div_u64(adj, CC_MULT_DEM);
+
+       mutex_lock(&chip->reg_lock);
+
+       timecounter_read(&chip->tstamp_tc);
+       chip->tstamp_cc.mult = neg_adj ? mult - diff : mult + diff;
+
+       mutex_unlock(&chip->reg_lock);
+
+       return 0;
+}
+
+static int mv88e6xxx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+       struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
+
+       mutex_lock(&chip->reg_lock);
+       timecounter_adjtime(&chip->tstamp_tc, delta);
+       mutex_unlock(&chip->reg_lock);
+
+       return 0;
+}
+
+static int mv88e6xxx_ptp_gettime(struct ptp_clock_info *ptp,
+                                struct timespec64 *ts)
+{
+       struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
+       u64 ns;
+
+       mutex_lock(&chip->reg_lock);
+       ns = timecounter_read(&chip->tstamp_tc);
+       mutex_unlock(&chip->reg_lock);
+
+       *ts = ns_to_timespec64(ns);
+
+       return 0;
+}
+
+static int mv88e6xxx_ptp_settime(struct ptp_clock_info *ptp,
+                                const struct timespec64 *ts)
+{
+       struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
+       u64 ns;
+
+       ns = timespec64_to_ns(ts);
+
+       mutex_lock(&chip->reg_lock);
+       timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc, ns);
+       mutex_unlock(&chip->reg_lock);
+
+       return 0;
+}
+
+static int mv88e6xxx_ptp_enable(struct ptp_clock_info *ptp,
+                               struct ptp_clock_request *rq, int on)
+{
+       return -EOPNOTSUPP;
+}
+
+static int mv88e6xxx_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin,
+                               enum ptp_pin_function func, unsigned int chan)
+{
+       return -EOPNOTSUPP;
+}
+
+/* With a 125MHz input clock, the 32-bit timestamp counter overflows in ~34.3
+ * seconds; this task forces periodic reads so that we don't miss any.
+ */
+#define MV88E6XXX_TAI_OVERFLOW_PERIOD (HZ * 16)
+static void mv88e6xxx_ptp_overflow_check(struct work_struct *work)
+{
+       struct delayed_work *dw = to_delayed_work(work);
+       struct mv88e6xxx_chip *chip = dw_overflow_to_chip(dw);
+       struct timespec64 ts;
+
+       mv88e6xxx_ptp_gettime(&chip->ptp_clock_info, &ts);
+
+       schedule_delayed_work(&chip->overflow_work,
+                             MV88E6XXX_TAI_OVERFLOW_PERIOD);
+}
+
+int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
+{
+       /* Set up the cycle counter */
+       memset(&chip->tstamp_cc, 0, sizeof(chip->tstamp_cc));
+       chip->tstamp_cc.read    = mv88e6xxx_ptp_clock_read;
+       chip->tstamp_cc.mask    = CYCLECOUNTER_MASK(32);
+       chip->tstamp_cc.mult    = CC_MULT;
+       chip->tstamp_cc.shift   = CC_SHIFT;
+
+       timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc,
+                        ktime_to_ns(ktime_get_real()));
+
+       INIT_DELAYED_WORK(&chip->overflow_work, mv88e6xxx_ptp_overflow_check);
+
+       chip->ptp_clock_info.owner = THIS_MODULE;
+       snprintf(chip->ptp_clock_info.name, sizeof(chip->ptp_clock_info.name),
+                dev_name(chip->dev));
+       chip->ptp_clock_info.max_adj    = 1000000;
+
+       chip->ptp_clock_info.adjfine    = mv88e6xxx_ptp_adjfine;
+       chip->ptp_clock_info.adjtime    = mv88e6xxx_ptp_adjtime;
+       chip->ptp_clock_info.gettime64  = mv88e6xxx_ptp_gettime;
+       chip->ptp_clock_info.settime64  = mv88e6xxx_ptp_settime;
+       chip->ptp_clock_info.enable     = mv88e6xxx_ptp_enable;
+       chip->ptp_clock_info.verify     = mv88e6xxx_ptp_verify;
+
+       chip->ptp_clock = ptp_clock_register(&chip->ptp_clock_info, chip->dev);
+       if (IS_ERR(chip->ptp_clock))
+               return PTR_ERR(chip->ptp_clock);
+
+       schedule_delayed_work(&chip->overflow_work,
+                             MV88E6XXX_TAI_OVERFLOW_PERIOD);
+
+       return 0;
+}
+
+void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
+{
+       if (chip->ptp_clock) {
+               cancel_delayed_work_sync(&chip->overflow_work);
+
+               ptp_clock_unregister(chip->ptp_clock);
+               chip->ptp_clock = NULL;
+       }
+}
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.h b/drivers/net/dsa/mv88e6xxx/ptp.h
new file mode 100644 (file)
index 0000000..5713d00
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Marvell 88E6xxx Switch PTP support
+ *
+ * Copyright (c) 2008 Marvell Semiconductor
+ *
+ * Copyright (c) 2017 National Instruments
+ *      Erik Hons <erik.hons@ni.com>
+ *      Brandon Streiff <brandon.streiff@ni.com>
+ *      Dane Wagner <dane.wagner@ni.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _MV88E6XXX_PTP_H
+#define _MV88E6XXX_PTP_H
+
+#include "chip.h"
+
+/* Offset 0x00: TAI Global Config */
+#define MV88E6XXX_TAI_CFG                      0x00
+
+/* Offset 0x01: Timestamp Clock Period (ps) */
+#define MV88E6XXX_TAI_CLOCK_PERIOD             0x01
+
+/* Offset 0x02/0x03: Trigger Generation Amount */
+#define MV88E6XXX_TAI_TRIG_GEN_AMOUNT_LO       0x02
+#define MV88E6XXX_TAI_TRIG_GEN_AMOUNT_HI       0x03
+
+/* Offset 0x04: Clock Compensation */
+#define MV88E6XXX_TAI_TRIG_CLOCK_COMP          0x04
+
+/* Offset 0x05: Trigger Configuration */
+#define MV88E6XXX_TAI_TRIG_CFG                 0x05
+
+/* Offset 0x06: Ingress Rate Limiter Clock Generation Amount */
+#define MV88E6XXX_TAI_IRL_AMOUNT               0x06
+
+/* Offset 0x07: Ingress Rate Limiter Compensation */
+#define MV88E6XXX_TAI_IRL_COMP                 0x07
+
+/* Offset 0x08: Ingress Rate Limiter Compensation */
+#define MV88E6XXX_TAI_IRL_COMP_PS              0x08
+
+/* Offset 0x09: Event Status */
+#define MV88E6XXX_TAI_EVENT_STATUS             0x09
+
+/* Offset 0x0A/0x0B: Event Time */
+#define MV88E6XXX_TAI_EVENT_TIME_LO            0x0a
+#define MV88E6XXX_TAI_EVENT_TYPE_HI            0x0b
+
+/* Offset 0x0E/0x0F: PTP Global Time */
+#define MV88E6XXX_TAI_TIME_LO                  0x0e
+#define MV88E6XXX_TAI_TIME_HI                  0x0f
+
+/* Offset 0x10/0x11: Trig Generation Time */
+#define MV88E6XXX_TAI_TRIG_TIME_LO             0x10
+#define MV88E6XXX_TAI_TRIG_TIME_HI             0x11
+
+/* Offset 0x12: Lock Status */
+#define MV88E6XXX_TAI_LOCK_STATUS              0x12
+
+#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP
+
+int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip);
+void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip);
+
+#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */
+
+static inline int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
+{
+       return 0;
+}
+
+static void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
+{
+}
+
+#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */
+
+#endif /* _MV88E6XXX_PTP_H */