@@ -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)