This commit add support for capturing a timestamp in which the PTP_PULSE pin, received a signal. This feature is needed in order to synchronize multiple clocks in the same board, using tools like ts2phc from the linuxptp project. On the Armada 8040, this is the only way to do so as a result of multiple erattas with the PTP_PULSE_IN interface that was designed to synchronize the TAI on an external PPS signal (the errattas are FE-6856276, FE-7382160 from document MV-S501388-00). This patch introduces a pinctrl configuration "extts" that will be selected once the user had enabled extts, and then will be returned back to the "default" pinctrl config once it has been disabled. Additionally these configurations will be also used in any case that the user asks us to perform any action that involves "triggerering" the TAI subsystem, in order to avoid a case where the external trigger would trigger with the wrong action. This pinctrl mess is needed due to the fact that there is no way for us to distinguish between an external trigger (e.g. from the PTP_PULSE_IN pin) or an internal one, triggered by the registers. The polling rate is once in 95ms, the value was borrowed from ptp_clockmatrix which performs a similar task. This feature has been tested on an Aramda 8040 based board, with linuxptp 3.1.1's ts2phc. Reviewed-by: Horatiu Vultur <horatiu.vultur@xxxxxxxxxxxxx> Signed-off-by: Shmuel Hazan <shmuel.h@xxxxxxxxx> --- v1 -> v2: minor commit message and code comment changes. v2 -> v3: same as above. v3 -> v4: replace mvpp22_tai_{start,stop}_unlocked with mvpp22_tai_{start,stop}. --- .../net/ethernet/marvell/mvpp2/mvpp2_tai.c | 304 ++++++++++++++++-- 1 file changed, 273 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c index d8ce8bdae046..97b34c876653 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c @@ -3,8 +3,11 @@ * Marvell PP2.2 TAI support * * Note: - * Do NOT use the event capture support. - * Do Not even set the MPP muxes to allow PTP_EVENT_REQ to be used. + * In order to use the event capture support, please see the example + * in marvell,pp2.yaml. + * Do not manually (e.g. without pinctrl-1, as described in + * marvell,pp2.yaml) set the MPP muxes to allow PTP_EVENT_REQ to be + * used. * It will disrupt the operation of this driver, and there is nothing * that this driver can do to prevent that. Even using PTP_EVENT_REQ * as an output will be seen as a trigger input, which can't be masked. @@ -34,6 +37,8 @@ */ #include "linux/spinlock.h" #include <linux/io.h> +#include <linux/pinctrl/consumer.h> +#include <linux/ptp_clock.h> #include <linux/ptp_clock_kernel.h> #include <linux/slab.h> @@ -54,6 +59,10 @@ #define TCSR_CAPTURE_1_VALID BIT(1) #define TCSR_CAPTURE_0_VALID BIT(0) +#define MVPP2_PINCTRL_EXTTS_STATE "extts" +#define MAX_PINS 1 +#define EXTTS_PERIOD_MS 95 + struct mvpp2_tai { struct ptp_clock_info caps; struct ptp_clock *ptp_clock; @@ -62,8 +71,13 @@ struct mvpp2_tai { u64 period; // nanosecond period in 32.32 fixed point /* This timestamp is updated every two seconds */ struct timespec64 stamp; + struct pinctrl *extts_pinctrl; + struct pinctrl_state *default_pinctrl_state; + struct pinctrl_state *extts_pinctrl_state; + struct ptp_pin_desc pin_config[MAX_PINS]; spinlock_t refcount_lock; /* Protects the poll_worker_refcount variable */ u16 poll_worker_refcount; + bool extts_enabled:1; }; static void mvpp2_tai_modify(void __iomem *reg, u32 mask, u32 set) @@ -75,6 +89,38 @@ static void mvpp2_tai_modify(void __iomem *reg, u32 mask, u32 set) writel(val, reg); } +/* mvpp2_tai_{pause,resume}_external_trigger are used as guards + * to mask external triggers where it is undesirable. For example, + * in case that the action is "increment", we may want to perform it + * once; however, we may trigger it once internally and once from + * an external pulse, which will cause an issue. + * In order to work around this issue, we need perform the following sequence: + * 1. call mvpp2_tai_pause_external_trigger + * 2. save the current trigger operation. + * 3. update the trigger operation. + * 4. perform an internal trigger. + * 5. restore the previous trigger operation. + * 6. call mvpp2_tai_restore_external_trigger. + */ +static int mvpp2_tai_pause_external_trigger(struct mvpp2_tai *tai) +{ + if (tai->extts_enabled && tai->extts_pinctrl && + tai->extts_pinctrl_state) + return pinctrl_select_state(tai->extts_pinctrl, + tai->default_pinctrl_state); + + return 0; +} + +static int mvpp2_tai_resume_external_trigger(struct mvpp2_tai *tai) +{ + if (tai->extts_enabled && tai->extts_pinctrl && + tai->extts_pinctrl_state) + return pinctrl_select_state(tai->extts_pinctrl, + tai->extts_pinctrl_state); + return 0; +} + static void mvpp2_tai_write(u32 val, void __iomem *reg) { writel_relaxed(val & 0xffff, reg); @@ -104,6 +150,28 @@ static void mvpp22_tai_read_ts(struct timespec64 *ts, void __iomem *base) readl_relaxed(base + 24); } +static int mvpp22_tai_try_read_ts(struct timespec64 *ts, void __iomem *base) +{ + long tcsr = readl(base + MVPP22_TAI_TCSR); + /* If both captures are not valid, return EBUSY */ + int ret = -EBUSY; + + if (tcsr & TCSR_CAPTURE_1_VALID) { + mvpp22_tai_read_ts(ts, base + MVPP22_TAI_TCV1_SEC_HIGH); + ret = 0; + } + + /* If both capture 1 and capture 0 are valid, use capture 0 + * but also read and clear capture 1. + */ + if (tcsr & TCSR_CAPTURE_0_VALID) { + mvpp22_tai_read_ts(ts, base + MVPP22_TAI_TCV0_SEC_HIGH); + ret = 0; + } + + return ret; +} + static void mvpp2_tai_write_tlv(const struct timespec64 *ts, u32 frac, void __iomem *base) { @@ -116,16 +184,30 @@ static void mvpp2_tai_write_tlv(const struct timespec64 *ts, u32 frac, mvpp2_tai_write(frac, base + MVPP22_TAI_TLV_FRAC_LOW); } -static void mvpp2_tai_op(u32 op, void __iomem *base) +static int mvpp2_tai_op(u32 op, void __iomem *base, struct mvpp2_tai *tai) { + u32 reg_val; + int ret; + + reg_val = mvpp2_tai_read(base + MVPP22_TAI_TCFCR0); /* Trigger the operation. Note that an external unmaskable * event on PTP_EVENT_REQ will also trigger this action. + * therefore, pause possible (known) external triggers. */ + ret = mvpp2_tai_pause_external_trigger(tai); + if (ret) + goto out; + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, op | TCFCR0_TCF_TRIGGER); - mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, - TCFCR0_TCF_NOP); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, + TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, reg_val); + + mvpp2_tai_resume_external_trigger(tai); + +out: + return ret; } /* The adjustment has a range of +0.5ns to -0.5ns in 2^32 steps, so has units @@ -172,6 +254,7 @@ static int mvpp22_tai_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) bool neg_adj; s32 frac; u64 val; + int ret; neg_adj = scaled_ppm < 0; if (neg_adj) @@ -199,10 +282,10 @@ static int mvpp22_tai_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) spin_lock_irqsave(&tai->lock, flags); mvpp2_tai_write(frac >> 16, base + MVPP22_TAI_TLV_FRAC_HIGH); mvpp2_tai_write(frac, base + MVPP22_TAI_TLV_FRAC_LOW); - mvpp2_tai_op(TCFCR0_TCF_FREQUPDATE, base); + ret = mvpp2_tai_op(TCFCR0_TCF_FREQUPDATE, base, tai); spin_unlock_irqrestore(&tai->lock, flags); - return 0; + return ret; } static int mvpp22_tai_adjtime(struct ptp_clock_info *ptp, s64 delta) @@ -212,6 +295,7 @@ static int mvpp22_tai_adjtime(struct ptp_clock_info *ptp, s64 delta) unsigned long flags; void __iomem *base; u32 tcf; + int ret; /* We can't deal with S64_MIN */ if (delta == S64_MIN) @@ -229,10 +313,10 @@ static int mvpp22_tai_adjtime(struct ptp_clock_info *ptp, s64 delta) base = tai->base; spin_lock_irqsave(&tai->lock, flags); mvpp2_tai_write_tlv(&ts, 0, base); - mvpp2_tai_op(tcf, base); + ret = mvpp2_tai_op(tcf, base, tai); spin_unlock_irqrestore(&tai->lock, flags); - return 0; + return ret; } static int mvpp22_tai_gettimex64(struct ptp_clock_info *ptp, @@ -242,35 +326,34 @@ static int mvpp22_tai_gettimex64(struct ptp_clock_info *ptp, struct mvpp2_tai *tai = ptp_to_tai(ptp); unsigned long flags; void __iomem *base; - u32 tcsr; + u32 reg_val; int ret; base = tai->base; spin_lock_irqsave(&tai->lock, flags); /* XXX: the only way to read the PTP time is for the CPU to trigger * an event. However, there is no way to distinguish between the CPU - * triggered event, and an external event on PTP_EVENT_REQ. So this - * is incompatible with external use of PTP_EVENT_REQ. + * triggered event, and an external event on PTP_EVENT_REQ. As a result + * we are pausing here external triggers by switching the pinctrl state + * to the default state (if applicable). */ + ret = mvpp2_tai_pause_external_trigger(tai); + if (ret) + goto unlock_out; + + reg_val = mvpp2_tai_read(base + MVPP22_TAI_TCFCR0); ptp_read_system_prets(sts); mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, TCFCR0_TCF_CAPTURE | TCFCR0_TCF_TRIGGER); ptp_read_system_postts(sts); - mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, - TCFCR0_TCF_NOP); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, + TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, reg_val); - tcsr = readl(base + MVPP22_TAI_TCSR); - if (tcsr & TCSR_CAPTURE_1_VALID) { - mvpp22_tai_read_ts(ts, base + MVPP22_TAI_TCV1_SEC_HIGH); - ret = 0; - } else if (tcsr & TCSR_CAPTURE_0_VALID) { - mvpp22_tai_read_ts(ts, base + MVPP22_TAI_TCV0_SEC_HIGH); - ret = 0; - } else { - /* We don't seem to have a reading... */ - ret = -EBUSY; - } + ret = mvpp22_tai_try_read_ts(ts, base); + mvpp2_tai_resume_external_trigger(tai); + +unlock_out: spin_unlock_irqrestore(&tai->lock, flags); return ret; @@ -282,32 +365,71 @@ static int mvpp22_tai_settime64(struct ptp_clock_info *ptp, struct mvpp2_tai *tai = ptp_to_tai(ptp); unsigned long flags; void __iomem *base; + u32 reg_val; + int ret; base = tai->base; spin_lock_irqsave(&tai->lock, flags); mvpp2_tai_write_tlv(ts, 0, base); + ret = mvpp2_tai_pause_external_trigger(tai); + if (ret) + goto unlock_out; + /* Trigger an update to load the value from the TLV registers * into the TOD counter. Note that an external unmaskable event on * PTP_EVENT_REQ will also trigger this action. */ + reg_val = mvpp2_tai_read(base + MVPP22_TAI_TCFCR0); mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, - TCFCR0_PHASE_UPDATE_ENABLE | - TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, + TCFCR0_PHASE_UPDATE_ENABLE | TCFCR0_TCF_MASK | + TCFCR0_TCF_TRIGGER, TCFCR0_TCF_UPDATE | TCFCR0_TCF_TRIGGER); - mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, - TCFCR0_TCF_NOP); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, + TCFCR0_PHASE_UPDATE_ENABLE | TCFCR0_TCF_MASK | + TCFCR0_TCF_TRIGGER, + reg_val); + + mvpp2_tai_resume_external_trigger(tai); + +unlock_out: spin_unlock_irqrestore(&tai->lock, flags); - return 0; + return ret; +} + +static void do_aux_work_extts(struct mvpp2_tai *tai) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&tai->lock, flags); + + ret = mvpp22_tai_try_read_ts(&tai->stamp, tai->base); + /* We are not managed to read a TS, try again later */ + if (!ret) { + struct ptp_clock_event event; + + /* Triggered - save timestamp */ + event.type = PTP_CLOCK_EXTTS; + event.index = 0; /* We only have one channel */ + event.timestamp = timespec64_to_ns(&tai->stamp); + ptp_clock_event(tai->ptp_clock, &event); + } + + spin_unlock_irqrestore(&tai->lock, flags); } static long mvpp22_tai_aux_work(struct ptp_clock_info *ptp) { struct mvpp2_tai *tai = ptp_to_tai(ptp); - mvpp22_tai_gettimex64(ptp, &tai->stamp, NULL); + if (tai->extts_enabled) { + do_aux_work_extts(tai); + return msecs_to_jiffies(EXTTS_PERIOD_MS); + } + mvpp22_tai_gettimex64(ptp, &tai->stamp, NULL); return msecs_to_jiffies(2000); } @@ -403,6 +525,94 @@ void mvpp22_tai_stop(struct mvpp2_tai *tai) spin_unlock_irqrestore(&tai->refcount_lock, flags); } +static void mvpp22_tai_capture_enable(struct mvpp2_tai *tai, bool enable) +{ + mvpp2_tai_modify(tai->base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, + enable ? TCFCR0_TCF_CAPTURE : TCFCR0_TCF_NOP); +} + +static int mvpp22_tai_req_extts_enable(struct mvpp2_tai *tai, + struct ptp_clock_request *rq, int on) +{ + u8 index = rq->extts.index; + int ret = 0; + + if (!tai->extts_pinctrl) + return -EINVAL; + + /* Reject requests with unsupported flags */ + if (rq->extts.flags & ~(PTP_ENABLE_FEATURE | PTP_RISING_EDGE | + PTP_FALLING_EDGE | PTP_STRICT_FLAGS)) + return -EOPNOTSUPP; + + /* Reject requests to enable time stamping on falling edge */ + if ((rq->extts.flags & PTP_ENABLE_FEATURE) && + (rq->extts.flags & PTP_FALLING_EDGE)) + return -EOPNOTSUPP; + + if (index >= MAX_PINS) + return -EINVAL; + + if (on) + ret = pinctrl_select_state(tai->extts_pinctrl, + tai->extts_pinctrl_state); + else + ret = pinctrl_select_state(tai->extts_pinctrl, + tai->default_pinctrl_state); + if (ret) + goto out; + + tai->extts_enabled = on != 0; + mvpp22_tai_capture_enable(tai, tai->extts_enabled); + + /* We need to enable the poll worker in order for events to be polled */ + if (on) + mvpp22_tai_start(tai); + else + mvpp22_tai_stop(tai); + +out: + return ret; +} + +static int mvpp22_tai_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct mvpp2_tai *tai = ptp_to_tai(ptp); + int err = -EOPNOTSUPP; + unsigned long flags; + + spin_lock_irqsave(&tai->lock, flags); + + switch (rq->type) { + case PTP_CLK_REQ_EXTTS: + err = mvpp22_tai_req_extts_enable(tai, rq, on); + break; + default: + break; + } + + spin_unlock_irqrestore(&tai->lock, flags); + return err; +} + +static int mvpp22_tai_verify_pin(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + if (chan != 0) + return -1; + + switch (func) { + case PTP_PF_NONE: + case PTP_PF_EXTTS: + break; + case PTP_PF_PEROUT: + case PTP_PF_PHYSYNC: + return -1; + } + return 0; +} + static void mvpp22_tai_remove(void *priv) { struct mvpp2_tai *tai = priv; @@ -423,6 +633,24 @@ int mvpp22_tai_probe(struct device *dev, struct mvpp2 *priv) spin_lock_init(&tai->lock); spin_lock_init(&tai->refcount_lock); + tai->extts_pinctrl = devm_pinctrl_get_select_default(dev); + if (!IS_ERR(tai->extts_pinctrl)) { + tai->default_pinctrl_state = pinctrl_lookup_state + (tai->extts_pinctrl, PINCTRL_STATE_DEFAULT); + tai->extts_pinctrl_state = pinctrl_lookup_state + (tai->extts_pinctrl, MVPP2_PINCTRL_EXTTS_STATE); + + if (IS_ERR(tai->default_pinctrl_state) || + IS_ERR(tai->extts_pinctrl_state)) { + pinctrl_put(tai->extts_pinctrl); + tai->extts_pinctrl = NULL; + tai->default_pinctrl_state = NULL; + tai->extts_pinctrl_state = NULL; + } + } else { + tai->extts_pinctrl = NULL; + } + tai->base = priv->iface_base; /* The step size consists of three registers - a 16-bit nanosecond step @@ -458,12 +686,26 @@ int mvpp22_tai_probe(struct device *dev, struct mvpp2 *priv) tai->caps.owner = THIS_MODULE; strscpy(tai->caps.name, "Marvell PP2.2", sizeof(tai->caps.name)); + tai->caps.n_ext_ts = MAX_PINS; + tai->caps.n_pins = MAX_PINS; tai->caps.max_adj = mvpp22_calc_max_adj(tai); tai->caps.adjfine = mvpp22_tai_adjfine; tai->caps.adjtime = mvpp22_tai_adjtime; tai->caps.gettimex64 = mvpp22_tai_gettimex64; tai->caps.settime64 = mvpp22_tai_settime64; tai->caps.do_aux_work = mvpp22_tai_aux_work; + tai->caps.enable = mvpp22_tai_enable; + tai->caps.verify = mvpp22_tai_verify_pin; + tai->caps.pin_config = tai->pin_config; + + for (int i = 0; i < tai->caps.n_pins; ++i) { + struct ptp_pin_desc *ppd = &tai->caps.pin_config[i]; + + snprintf(ppd->name, sizeof(ppd->name), "PTP_PULSE_IN%d", i); + ppd->index = i; + ppd->func = PTP_PF_NONE; + ppd->chan = 0; + } ret = devm_add_action(dev, mvpp22_tai_remove, tai); if (ret) -- 2.40.1