From: Brandon Streiff <brandon.stre...@ni.com>

This patch adds support for configuring mv88e6xxx GPIO lines as PTP
pins, so that they may be used for time stamping external events or for
periodic output.

Checkpatch and reverse Christmas tree fixes by Andrew Lunn

Periodic output removed by Richard Cochran, until a better abstraction
of a VCO is added to Linux in general.

Signed-off-by: Brandon Streiff <brandon.stre...@ni.com>
Signed-off-by: Andrew Lunn <and...@lunn.ch>
Signed-off-by: Richard Cochran <richardcoch...@gmail.com>
---
 drivers/net/dsa/mv88e6xxx/chip.h |   4 +
 drivers/net/dsa/mv88e6xxx/ptp.c  | 189 ++++++++++++++++++++++++++++++++++++++-
 drivers/net/dsa/mv88e6xxx/ptp.h  |  16 ++++
 3 files changed, 207 insertions(+), 2 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 5bfa1aeb3269..eaf254a0e674 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -232,6 +232,10 @@ struct mv88e6xxx_chip {
 
        struct ptp_clock        *ptp_clock;
        struct ptp_clock_info   ptp_clock_info;
+       struct delayed_work     tai_event_work;
+       struct ptp_pin_desc     pin_config[MV88E6XXX_MAX_GPIO];
+       u16 trig_config;
+       u16 evcap_config;
 };
 
 struct mv88e6xxx_bus_ops {
diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c
index 6bc643cea03d..92885e715e85 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.c
+++ b/drivers/net/dsa/mv88e6xxx/ptp.c
@@ -31,6 +31,8 @@
                                      ptp_clock_info)
 #define dw_overflow_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \
                                             overflow_work)
+#define dw_tai_event_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \
+                                             tai_event_work)
 
 static int mv88e6xxx_tai_read(struct mv88e6xxx_chip *chip, int addr,
                              u16 *data, int len)
@@ -41,6 +43,30 @@ static int mv88e6xxx_tai_read(struct mv88e6xxx_chip *chip, 
int addr,
        return chip->info->ops->avb_ops->tai_read(chip, addr, data, len);
 }
 
+static int mv88e6xxx_tai_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
+{
+       if (!chip->info->ops->avb_ops->tai_write)
+               return -EOPNOTSUPP;
+
+       return chip->info->ops->avb_ops->tai_write(chip, addr, data);
+}
+
+/* TODO: places where this are called should be using pinctrl */
+static int mv88e6xxx_set_gpio_func(struct mv88e6xxx_chip *chip, int pin,
+                                  int func, int input)
+{
+       int err;
+
+       if (!chip->info->ops->gpio_ops)
+               return -EOPNOTSUPP;
+
+       err = chip->info->ops->gpio_ops->set_dir(chip, pin, input);
+       if (err)
+               return err;
+
+       return chip->info->ops->gpio_ops->set_pctl(chip, pin, func);
+}
+
 static u64 mv88e6xxx_ptp_clock_read(const struct cyclecounter *cc)
 {
        struct mv88e6xxx_chip *chip = cc_to_chip(cc);
@@ -55,6 +81,92 @@ static u64 mv88e6xxx_ptp_clock_read(const struct 
cyclecounter *cc)
                return ((u32)phc_time[1] << 16) | phc_time[0];
 }
 
+/* mv88e6xxx_config_eventcap - configure TAI event capture
+ * @event: PTP_CLOCK_PPS (internal) or PTP_CLOCK_EXTTS (external)
+ * @rising: zero for falling-edge trigger, else rising-edge trigger
+ *
+ * This will also reset the capture sequence counter.
+ */
+static int mv88e6xxx_config_eventcap(struct mv88e6xxx_chip *chip, int event,
+                                    int rising)
+{
+       u16 global_config;
+       u16 cap_config;
+       int err;
+
+       chip->evcap_config = MV88E6XXX_TAI_CFG_CAP_OVERWRITE |
+                            MV88E6XXX_TAI_CFG_CAP_CTR_START;
+       if (!rising)
+               chip->evcap_config |= MV88E6XXX_TAI_CFG_EVREQ_FALLING;
+
+       global_config = (chip->evcap_config | chip->trig_config);
+       err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_CFG, global_config);
+       if (err)
+               return err;
+
+       if (event == PTP_CLOCK_PPS) {
+               cap_config = MV88E6XXX_TAI_EVENT_STATUS_CAP_TRIG;
+       } else if (event == PTP_CLOCK_EXTTS) {
+               /* if STATUS_CAP_TRIG is unset we capture PTP_EVREQ events */
+               cap_config = 0;
+       } else {
+               return -EINVAL;
+       }
+
+       /* Write the capture config; this also clears the capture counter */
+       err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_EVENT_STATUS,
+                                 cap_config);
+
+       return err;
+}
+
+static void mv88e6xxx_tai_event_work(struct work_struct *ugly)
+{
+       struct delayed_work *dw = to_delayed_work(ugly);
+       struct mv88e6xxx_chip *chip = dw_tai_event_to_chip(dw);
+       struct ptp_clock_event ev;
+       u16 status[4];
+       u32 raw_ts;
+       int err;
+
+       mutex_lock(&chip->reg_lock);
+       err = mv88e6xxx_tai_read(chip, MV88E6XXX_TAI_EVENT_STATUS,
+                                status, ARRAY_SIZE(status));
+       mutex_unlock(&chip->reg_lock);
+
+       if (err) {
+               dev_err(chip->dev, "failed to read TAI status register\n");
+               return;
+       }
+       if (status[0] & MV88E6XXX_TAI_EVENT_STATUS_ERROR) {
+               dev_warn(chip->dev, "missed event capture\n");
+               return;
+       }
+       if (!(status[0] & MV88E6XXX_TAI_EVENT_STATUS_VALID))
+               goto out;
+
+       raw_ts = ((u32)status[2] << 16) | status[1];
+
+       /* Clear the valid bit so the next timestamp can come in */
+       status[0] &= ~MV88E6XXX_TAI_EVENT_STATUS_VALID;
+       mutex_lock(&chip->reg_lock);
+       err = mv88e6xxx_tai_write(chip, MV88E6XXX_TAI_EVENT_STATUS, status[0]);
+       mutex_unlock(&chip->reg_lock);
+
+       /* This is an external timestamp */
+       ev.type = PTP_CLOCK_EXTTS;
+
+       /* We only have one timestamping channel. */
+       ev.index = 0;
+       mutex_lock(&chip->reg_lock);
+       ev.timestamp = timecounter_cyc2time(&chip->tstamp_tc, raw_ts);
+       mutex_unlock(&chip->reg_lock);
+
+       ptp_clock_event(chip->ptp_clock, &ev);
+out:
+       schedule_delayed_work(&chip->tai_event_work, TAI_EVENT_WORK_INTERVAL);
+}
+
 static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
 {
        struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
@@ -122,16 +234,71 @@ static int mv88e6xxx_ptp_settime(struct ptp_clock_info 
*ptp,
        return 0;
 }
 
+static int mv88e6xxx_ptp_enable_extts(struct mv88e6xxx_chip *chip,
+                                     struct ptp_clock_request *rq, int on)
+{
+       int rising = (rq->extts.flags & PTP_RISING_EDGE);
+       int func;
+       int pin;
+       int err;
+
+       pin = ptp_find_pin(chip->ptp_clock, PTP_PF_EXTTS, rq->extts.index);
+
+       if (pin < 0)
+               return -EBUSY;
+
+       mutex_lock(&chip->reg_lock);
+
+       if (on) {
+               func = MV88E6352_G2_SCRATCH_GPIO_PCTL_EVREQ;
+
+               err = mv88e6xxx_set_gpio_func(chip, pin, func, true);
+               if (err)
+                       goto out;
+
+               schedule_delayed_work(&chip->tai_event_work,
+                                     TAI_EVENT_WORK_INTERVAL);
+
+               err = mv88e6xxx_config_eventcap(chip, PTP_CLOCK_EXTTS, rising);
+       } else {
+               func = MV88E6352_G2_SCRATCH_GPIO_PCTL_GPIO;
+
+               err = mv88e6xxx_set_gpio_func(chip, pin, func, true);
+
+               cancel_delayed_work_sync(&chip->tai_event_work);
+       }
+
+out:
+       mutex_unlock(&chip->reg_lock);
+
+       return err;
+}
+
 static int mv88e6xxx_ptp_enable(struct ptp_clock_info *ptp,
                                struct ptp_clock_request *rq, int on)
 {
-       return -EOPNOTSUPP;
+       struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
+
+       switch (rq->type) {
+       case PTP_CLK_REQ_EXTTS:
+               return mv88e6xxx_ptp_enable_extts(chip, rq, on);
+       default:
+               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;
+       switch (func) {
+       case PTP_PF_NONE:
+       case PTP_PF_EXTTS:
+               break;
+       case PTP_PF_PEROUT:
+       case PTP_PF_PHYSYNC:
+               return -EOPNOTSUPP;
+       }
+       return 0;
 }
 
 /* With a 125MHz input clock, the 32-bit timestamp counter overflows in ~34.3
@@ -152,6 +319,8 @@ static void mv88e6xxx_ptp_overflow_check(struct work_struct 
*work)
 
 int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
 {
+       int i;
+
        /* Set up the cycle counter */
        memset(&chip->tstamp_cc, 0, sizeof(chip->tstamp_cc));
        chip->tstamp_cc.read    = mv88e6xxx_ptp_clock_read;
@@ -163,12 +332,27 @@ int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
                         ktime_to_ns(ktime_get_real()));
 
        INIT_DELAYED_WORK(&chip->overflow_work, mv88e6xxx_ptp_overflow_check);
+       INIT_DELAYED_WORK(&chip->tai_event_work, mv88e6xxx_tai_event_work);
 
        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.n_ext_ts   = 1;
+       chip->ptp_clock_info.n_per_out  = 0;
+       chip->ptp_clock_info.n_pins     = mv88e6xxx_num_gpio(chip);
+       chip->ptp_clock_info.pps        = 0;
+
+       for (i = 0; i < chip->ptp_clock_info.n_pins; ++i) {
+               struct ptp_pin_desc *ppd = &chip->pin_config[i];
+
+               snprintf(ppd->name, sizeof(ppd->name), "mv88e6xxx_gpio%d", i);
+               ppd->index = i;
+               ppd->func = PTP_PF_NONE;
+       }
+       chip->ptp_clock_info.pin_config = chip->pin_config;
+
        chip->ptp_clock_info.adjfine    = mv88e6xxx_ptp_adjfine;
        chip->ptp_clock_info.adjtime    = mv88e6xxx_ptp_adjtime;
        chip->ptp_clock_info.gettime64  = mv88e6xxx_ptp_gettime;
@@ -190,6 +374,7 @@ void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
 {
        if (chip->ptp_clock) {
                cancel_delayed_work_sync(&chip->overflow_work);
+               cancel_delayed_work_sync(&chip->tai_event_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
index 5713d00800fc..21c93d278b09 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.h
+++ b/drivers/net/dsa/mv88e6xxx/ptp.h
@@ -21,6 +21,18 @@
 
 /* Offset 0x00: TAI Global Config */
 #define MV88E6XXX_TAI_CFG                      0x00
+#define MV88E6XXX_TAI_CFG_CAP_OVERWRITE                0x8000
+#define MV88E6XXX_TAI_CFG_CAP_CTR_START                0x4000
+#define MV88E6XXX_TAI_CFG_EVREQ_FALLING                0x2000
+#define MV88E6XXX_TAI_CFG_TRIG_ACTIVE_LO       0x1000
+#define MV88E6XXX_TAI_CFG_IRL_ENABLE           0x0400
+#define MV88E6XXX_TAI_CFG_TRIG_IRQ_EN          0x0200
+#define MV88E6XXX_TAI_CFG_EVREQ_IRQ_EN         0x0100
+#define MV88E6XXX_TAI_CFG_TRIG_LOCK            0x0080
+#define MV88E6XXX_TAI_CFG_BLOCK_UPDATE         0x0008
+#define MV88E6XXX_TAI_CFG_MULTI_PTP            0x0004
+#define MV88E6XXX_TAI_CFG_TRIG_MODE_ONESHOT    0x0002
+#define MV88E6XXX_TAI_CFG_TRIG_ENABLE          0x0001
 
 /* Offset 0x01: Timestamp Clock Period (ps) */
 #define MV88E6XXX_TAI_CLOCK_PERIOD             0x01
@@ -46,6 +58,10 @@
 
 /* Offset 0x09: Event Status */
 #define MV88E6XXX_TAI_EVENT_STATUS             0x09
+#define MV88E6XXX_TAI_EVENT_STATUS_CAP_TRIG    0x4000
+#define MV88E6XXX_TAI_EVENT_STATUS_ERROR       0x0200
+#define MV88E6XXX_TAI_EVENT_STATUS_VALID       0x0100
+#define MV88E6XXX_TAI_EVENT_STATUS_CTR_MASK    0x00ff
 
 /* Offset 0x0A/0x0B: Event Time */
 #define MV88E6XXX_TAI_EVENT_TIME_LO            0x0a
-- 
2.15.1

Reply via email to