Kieran Tyrrell
2016-09-15 12:23:20 UTC
Signed-off-by: Kieran Tyrrell <***@sienda.com>
---
diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h
index 5387b3a..671c579 100644
--- a/drivers/net/ethernet/intel/igb/igb.h
+++ b/drivers/net/ethernet/intel/igb/igb.h
@@ -352,6 +352,7 @@ struct hwmon_buff {
#define IGB_N_EXTTS 2
#define IGB_N_PEROUT 2
+#define IGB_N_ALARM 2
#define IGB_N_SDP 4
#define IGB_RETA_SIZE 128
@@ -456,6 +457,7 @@ struct igb_adapter {
struct {
struct timespec64 start;
struct timespec64 period;
+ bool enabled;
} perout[IGB_N_PEROUT];
char fw_version[32];
@@ -473,6 +475,8 @@ struct igb_adapter {
int copper_tries;
struct e1000_info ei;
u16 eee_advert;
+
+ int timer_channel;
};
/* flags controlling PTP/1588 function */
@@ -558,6 +562,10 @@ void igb_ptp_rx_pktstamp(struct igb_q_vector *q_vector, unsigned char *va,
int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr);
int igb_ptp_get_ts_config(struct net_device *netdev, struct ifreq *ifr);
void igb_set_flag_queue_pairs(struct igb_adapter *, const u32);
+void igb_tt_timer_enable(struct igb_adapter *adapter, bool enable,
+ struct timespec64 *ts);
+void igb_ptp_read_i210(struct igb_adapter *adapter,
+ struct timespec64 *ts);
#ifdef CONFIG_IGB_HWMON
void igb_sysfs_exit(struct igb_adapter *adapter);
int igb_sysfs_init(struct igb_adapter *adapter);
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index 942a89f..aae53e6 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -5638,32 +5638,69 @@ static void igb_tsync_interrupt(struct igb_adapter *adapter)
}
if (tsicr & TSINTR_TT0) {
- spin_lock(&adapter->tmreg_lock);
- ts = timespec64_add(adapter->perout[0].start,
- adapter->perout[0].period);
- /* u32 conversion of tv_sec is safe until y2106 */
- wr32(E1000_TRGTTIML0, ts.tv_nsec);
- wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
- tsauxc = rd32(E1000_TSAUXC);
- tsauxc |= TSAUXC_EN_TT0;
- wr32(E1000_TSAUXC, tsauxc);
- adapter->perout[0].start = ts;
- spin_unlock(&adapter->tmreg_lock);
- ack |= TSINTR_TT0;
+ if (adapter->timer_channel == 0) {
+ /* this is a timer interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ igb_tt_timer_enable(adapter, false, NULL);
+ wr32(E1000_TSICR, TSINTR_TT0);
+ igb_ptp_read_i210(adapter, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ event.type = PTP_CLOCK_ALARM;
+ event.index = 0;
+ /* ptp_clock_event will return the next time to set */
+ ptp_clock_event(adapter->ptp_clock, &event);
+ spin_lock(&adapter->tmreg_lock);
+ if (timespec64_to_ns(&event.alarm_time) != 0)
+ igb_tt_timer_enable(adapter, true, &event.alarm_time);
+
+ spin_unlock(&adapter->tmreg_lock);
+ } else {
+ /* this is a periodic output interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ ts = timespec64_add(adapter->perout[0].start,
+ adapter->perout[0].period);
+ /* u32 conversion of tv_sec is safe until y2106 */
+ wr32(E1000_TRGTTIML0, ts.tv_nsec);
+ wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
+ tsauxc = rd32(E1000_TSAUXC);
+ tsauxc |= TSAUXC_EN_TT0;
+ wr32(E1000_TSAUXC, tsauxc);
+ adapter->perout[0].start = ts;
+ spin_unlock(&adapter->tmreg_lock);
+ ack |= TSINTR_TT0;
+ }
+
}
if (tsicr & TSINTR_TT1) {
- spin_lock(&adapter->tmreg_lock);
- ts = timespec64_add(adapter->perout[1].start,
- adapter->perout[1].period);
- wr32(E1000_TRGTTIML1, ts.tv_nsec);
- wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
- tsauxc = rd32(E1000_TSAUXC);
- tsauxc |= TSAUXC_EN_TT1;
- wr32(E1000_TSAUXC, tsauxc);
- adapter->perout[1].start = ts;
- spin_unlock(&adapter->tmreg_lock);
- ack |= TSINTR_TT1;
+ if (adapter->timer_channel == 1) {
+ /* this is a timer interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ igb_tt_timer_enable(adapter, false, NULL);
+ wr32(E1000_TSICR, TSINTR_TT1);
+ igb_ptp_read_i210(adapter, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ event.type = PTP_CLOCK_ALARM;
+ /* ptp_clock_event will return the next time to set */
+ ptp_clock_event(adapter->ptp_clock, &event);
+ spin_lock(&adapter->tmreg_lock);
+ if (timespec64_to_ns(&event.alarm_time) != 0)
+ igb_tt_timer_enable(adapter, true, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ } else {
+ /* this is a periodic output interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ ts = timespec64_add(adapter->perout[1].start,
+ adapter->perout[1].period);
+ wr32(E1000_TRGTTIML1, ts.tv_nsec);
+ wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
+ tsauxc = rd32(E1000_TSAUXC);
+ tsauxc |= TSAUXC_EN_TT1;
+ wr32(E1000_TSAUXC, tsauxc);
+ adapter->perout[1].start = ts;
+ spin_unlock(&adapter->tmreg_lock);
+ ack |= TSINTR_TT1;
+ }
}
if (tsicr & TSINTR_AUTT0) {
@@ -5687,7 +5724,8 @@ static void igb_tsync_interrupt(struct igb_adapter *adapter)
}
/* acknowledge the interrupts */
- wr32(E1000_TSICR, ack);
+ if (ack)
+ wr32(E1000_TSICR, ack);
}
static irqreturn_t igb_msix_other(int irq, void *data)
diff --git a/drivers/net/ethernet/intel/igb/igb_ptp.c b/drivers/net/ethernet/intel/igb/igb_ptp.c
index 336c103..fffdb46 100644
--- a/drivers/net/ethernet/intel/igb/igb_ptp.c
+++ b/drivers/net/ethernet/intel/igb/igb_ptp.c
@@ -19,6 +19,7 @@
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/ptp_classify.h>
+#include <linux/posix-timers.h>
#include "igb.h"
@@ -116,8 +117,8 @@ static cycle_t igb_ptp_read_82580(const struct cyclecounter *cc)
}
/* SYSTIM read access for I210/I211 */
-static void igb_ptp_read_i210(struct igb_adapter *adapter,
- struct timespec64 *ts)
+void igb_ptp_read_i210(struct igb_adapter *adapter,
+ struct timespec64 *ts)
{
struct e1000_hw *hw = &adapter->hw;
u32 sec, nsec;
@@ -515,6 +516,9 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
return 0;
case PTP_CLK_REQ_PEROUT:
+ /* cannot use perout and timer at the same time */
+ if (igb->timer_channel == rq->perout.index)
+ return -EBUSY;
if (on) {
pin = ptp_find_pin(igb->ptp_clock, PTP_PF_PEROUT,
rq->perout.index);
@@ -578,7 +582,9 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
wr32(freqout, ns);
tsauxc |= tsauxc_mask;
tsim |= tsim_mask;
- }
+ igb->perout[i].enabled = true;
+ } else
+ igb->perout[rq->perout.index].enabled = false;
wr32(E1000_TSAUXC, tsauxc);
wr32(E1000_TSIM, tsim);
spin_unlock_irqrestore(&igb->tmreg_lock, flags);
@@ -594,6 +600,25 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
wr32(E1000_TSIM, tsim);
spin_unlock_irqrestore(&igb->tmreg_lock, flags);
return 0;
+
+ case PTP_CLK_REQ_ALARM:
+ /* cannot use perout and timer at the same time */
+ if (igb->perout[rq->alarm.index].enabled)
+ return -EBUSY;
+ if (on && (igb->timer_channel != -1))
+ return -EINVAL;
+ if (!on && (igb->timer_channel != rq->alarm.index))
+ return -EINVAL;
+ if (on)
+ igb->timer_channel = rq->alarm.index;
+ else {
+ /* disable the timer */
+ spin_lock_irqsave(&igb->tmreg_lock, flags);
+ igb_tt_timer_enable(igb, false, NULL);
+ igb->timer_channel = -1;
+ spin_unlock_irqrestore(&igb->tmreg_lock, flags);
+ }
+ return 0;
}
return -EOPNOTSUPP;
@@ -1063,6 +1088,71 @@ int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr)
-EFAULT : 0;
}
+/* tmreg_lock should be held for this call */
+void igb_tt_timer_enable(struct igb_adapter *adapter, bool enable,
+ struct timespec64 *ts)
+{
+ u32 tsauxc, tsim, ttimlreg, ttimhreg;
+ struct e1000_hw *hw = &adapter->hw;
+ int channel = adapter->timer_channel;
+
+ tsauxc = rd32(E1000_TSAUXC);
+ tsim = rd32(E1000_TSIM);
+
+ if (enable) {
+ if (channel == 1) {
+ tsauxc |= TSAUXC_EN_TT1;
+ tsim |= TSINTR_TT1;
+ ttimlreg = E1000_TRGTTIML1;
+ ttimhreg = E1000_TRGTTIMH1;
+ }
+ else {
+ tsauxc |= TSAUXC_EN_TT0;
+ tsim |= TSINTR_TT0;
+ ttimlreg = E1000_TRGTTIML0;
+ ttimhreg = E1000_TRGTTIMH0;
+ }
+ /* set trigger time and then enable timer */
+ wr32(ttimlreg, ts->tv_nsec);
+ wr32(ttimhreg, (u32)ts->tv_sec);
+ wr32(E1000_TSAUXC, tsauxc);
+ wr32(E1000_TSIM, tsim);
+ } else {
+ if (channel == 1) {
+ tsauxc &= ~TSAUXC_EN_TT1;
+ tsim &= ~TSINTR_TT1;
+ }
+ else {
+ tsauxc &= ~TSAUXC_EN_TT0;
+ tsim &= ~TSINTR_TT0;
+ }
+ wr32(E1000_TSAUXC, tsauxc);
+ wr32(E1000_TSIM, tsim);
+ }
+}
+
+static int igb_ptp_timer_settime_i210(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct igb_adapter *igb =
+ container_of(ptp, struct igb_adapter, ptp_caps);
+ unsigned long irqsaveflags;
+
+ if (igb->timer_channel == -1)
+ return -EBUSY;
+
+ spin_lock_irqsave(&igb->tmreg_lock, irqsaveflags);
+
+ if (timespec64_to_ns(ts) == 0)
+ igb_tt_timer_enable(igb, false, NULL);
+ else
+ igb_tt_timer_enable(igb, true, ts);
+
+ spin_unlock_irqrestore(&igb->tmreg_lock, irqsaveflags);
+
+ return 0;
+}
+
/**
* igb_ptp_init - Initialize PTP functionality
* @adapter: Board private structure
@@ -1125,6 +1215,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
snprintf(adapter->ptp_caps.name, 16, "%pm", netdev->dev_addr);
adapter->ptp_caps.owner = THIS_MODULE;
adapter->ptp_caps.max_adj = 62499999;
+ adapter->ptp_caps.n_alarm = IGB_N_ALARM;
adapter->ptp_caps.n_ext_ts = IGB_N_EXTTS;
adapter->ptp_caps.n_per_out = IGB_N_PEROUT;
adapter->ptp_caps.n_pins = IGB_N_SDP;
@@ -1136,6 +1227,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
adapter->ptp_caps.settime64 = igb_ptp_settime_i210;
adapter->ptp_caps.enable = igb_ptp_feature_enable_i210;
adapter->ptp_caps.verify = igb_ptp_verify_pin;
+ adapter->ptp_caps.timersettime = igb_ptp_timer_settime_i210;
break;
default:
adapter->ptp_clock = NULL;
@@ -1151,6 +1243,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
+ adapter->timer_channel = -1;
igb_ptp_reset(adapter);
diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c
index d637c93..b351c44 100644
--- a/drivers/ptp/ptp_chardev.c
+++ b/drivers/ptp/ptp_chardev.c
@@ -176,6 +176,21 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
err = ops->enable(ops, &req, enable);
break;
+ case PTP_ALARM_REQUEST:
+ if (copy_from_user(&req.alarm, (void __user *)arg,
+ sizeof(req.alarm))) {
+ err = -EFAULT;
+ break;
+ }
+ if (req.alarm.index >= ops->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ req.type = PTP_CLK_REQ_ALARM;
+ enable = req.alarm.flags & PTP_ENABLE_FEATURE ? 1 : 0;
+ err = ops->enable(ops, &req, enable);
+ break;
+
case PTP_ENABLE_PPS:
if (!capable(CAP_SYS_TIME))
return -EPERM;
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index 2e481b9..28573dc 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -36,6 +36,8 @@
#define PTP_PPS_EVENT PPS_CAPTUREASSERT
#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
+#define PTP_TIMER_MINIMUM_INTERVAL_NS 100000
+
/* private globals */
static dev_t ptp_devt;
@@ -43,6 +45,56 @@ static struct class *ptp_class;
static DEFINE_IDA(ptp_clocks_map);
+static void alarm_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
+{
+ struct timerqueue_node *next;
+ struct k_itimer *kit;
+ s64 ns_now;
+ bool signal_failed_to_send;
+ unsigned long tq_lock_flags;
+
+ ns_now = timespec64_to_ns(&event->alarm_time);
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+
+ while (next) {
+ if (next->expires.tv64 > ns_now)
+ break;
+
+ kit = container_of(next, struct k_itimer, it.real.timer.node);
+
+ signal_failed_to_send = posix_timer_event(kit, 0);
+
+ /* update the last one that has fired */
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+ if ((ktime_to_ns(kit->it.real.interval) != 0)
+ && !signal_failed_to_send) {
+ /* this is a periodic timer, set the next fire time */
+ kit->it.real.timer.node.expires =
+ ktime_add(
+ kit->it.real.timer.node.expires,
+ kit->it.real.interval);
+ timerqueue_add(&ptp->timerqueue,
+ &kit->it.real.timer.node);
+ }
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+ }
+
+ /* now set the event time to the next timer fire time */
+ next = timerqueue_getnext(&ptp->timerqueue);
+ if (next) {
+ event->alarm_time = ktime_to_timespec64(next->expires);
+ } else {
+ event->alarm_time.tv_sec = 0;
+ event->alarm_time.tv_nsec = 0;
+ }
+
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+}
+
/* time stamp event queue operations */
static inline int queue_free(struct timestamp_event_queue *q)
@@ -163,12 +215,113 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct timex *tx)
return err;
}
+static int ptp_timer_create(struct posix_clock *pc, struct k_itimer *kit)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ unsigned long tq_lock_flags;
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+ timerqueue_init(&kit->it.real.timer.node);
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ return 0;
+}
+
+static int ptp_timer_delete(struct posix_clock *pc, struct k_itimer *kit)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ unsigned long tq_lock_flags;
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+ if (!RB_EMPTY_NODE(&kit->it.real.timer.node.node))
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ return 0;
+}
+
+static void ptp_timer_gettime(struct posix_clock *pc,
+ struct k_itimer *kit,
+ struct itimerspec *tsp)
+{
+ struct timespec time_now;
+
+ if (ptp_clock_gettime(pc, &time_now) != 0)
+ return;
+
+ tsp->it_interval = ktime_to_timespec(kit->it.real.interval);
+ tsp->it_value = timespec_sub(ktime_to_timespec(
+ kit->it.real.timer.node.expires), time_now);
+}
+
+
+static int ptp_timer_settime(struct posix_clock *pc,
+ struct k_itimer *kit, int flags,
+ struct itimerspec *tsp, struct itimerspec *old)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ int err;
+ unsigned long tq_lock_flags;
+ struct timespec time_now;
+ ktime_t fire_time;
+ struct timerqueue_node *next;
+ struct timespec64 ts;
+
+ if (ptp->info->timersettime == 0)
+ return -EOPNOTSUPP;
+
+ if (old)
+ ptp_timer_gettime(pc, kit, old);
+
+ fire_time = timespec_to_ktime(tsp->it_value);
+
+ if ((fire_time.tv64 != 0) && !(flags & TIMER_ABSTIME)) {
+ err = ptp_clock_gettime(pc, &time_now);
+ if (err)
+ return err;
+ /* convert relative to absolute time */
+ fire_time = ktime_add(fire_time, timespec_to_ktime(time_now));
+ }
+
+ kit->it.real.interval = timespec_to_ktime(tsp->it_interval);
+
+ if ((ktime_to_ns(kit->it.real.interval) != 0)
+ && (ktime_to_ns(kit->it.real.interval) < PTP_TIMER_MINIMUM_INTERVAL_NS))
+ kit->it.real.interval = ns_to_ktime(PTP_TIMER_MINIMUM_INTERVAL_NS);
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+ kit->it.real.timer.node.expires = fire_time;
+
+ if (!RB_EMPTY_NODE(&kit->it.real.timer.node.node))
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+
+ if (fire_time.tv64 != 0)
+ timerqueue_add(&ptp->timerqueue, &kit->it.real.timer.node);
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ if (next)
+ ts = ktime_to_timespec64(next->expires);
+ else {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ return ptp->info->timersettime(ptp->info, &ts);
+}
+
static struct posix_clock_operations ptp_clock_ops = {
.owner = THIS_MODULE,
.clock_adjtime = ptp_clock_adjtime,
.clock_gettime = ptp_clock_gettime,
.clock_getres = ptp_clock_getres,
.clock_settime = ptp_clock_settime,
+ .timer_create = ptp_timer_create,
+ .timer_delete = ptp_timer_delete,
+ .timer_gettime = ptp_timer_gettime,
+ .timer_settime = ptp_timer_settime,
.ioctl = ptp_ioctl,
.open = ptp_open,
.poll = ptp_poll,
@@ -217,6 +370,8 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
mutex_init(&ptp->tsevq_mux);
mutex_init(&ptp->pincfg_mux);
init_waitqueue_head(&ptp->tsev_wq);
+ spin_lock_init(&ptp->tq_lock);
+ timerqueue_init_head(&ptp->timerqueue);
/* Create a new device in our class. */
ptp->dev = device_create(ptp_class, parent, ptp->devid, ptp,
@@ -293,6 +448,7 @@ void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
switch (event->type) {
case PTP_CLOCK_ALARM:
+ alarm_event(ptp, event);
break;
case PTP_CLOCK_EXTTS:
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h
index 9c5d414..d491299 100644
--- a/drivers/ptp/ptp_private.h
+++ b/drivers/ptp/ptp_private.h
@@ -54,6 +54,10 @@ struct ptp_clock {
struct device_attribute *pin_dev_attr;
struct attribute **pin_attr;
struct attribute_group pin_attr_group;
+
+ struct timerqueue_head timerqueue;
+ spinlock_t tq_lock;
+ struct work_struct alarm_work;
};
/*
diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
index 6b15e16..155acc2 100644
--- a/include/linux/ptp_clock_kernel.h
+++ b/include/linux/ptp_clock_kernel.h
@@ -31,10 +31,12 @@ struct ptp_clock_request {
PTP_CLK_REQ_EXTTS,
PTP_CLK_REQ_PEROUT,
PTP_CLK_REQ_PPS,
+ PTP_CLK_REQ_ALARM
} type;
union {
struct ptp_extts_request extts;
struct ptp_perout_request perout;
+ struct ptp_alarm_request alarm;
};
};
@@ -92,6 +94,9 @@ struct system_device_crosststamp;
* parameter func: the desired function to use.
* parameter chan: the function channel index to use.
*
+ * @timersettime: Set the alarm time for the hardware timer.
+ * parameter ts: Time value to set.
+ *
* Drivers should embed their ptp_clock_info within a private
* structure, obtaining a reference to it using container_of().
*
@@ -118,6 +123,7 @@ struct ptp_clock_info {
struct ptp_clock_request *request, int on);
int (*verify)(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan);
+ int (*timersettime)(struct ptp_clock_info *ptp, struct timespec64 *ts);
};
struct ptp_clock;
@@ -149,12 +155,14 @@ enum ptp_clock_events {
};
/**
- * struct ptp_clock_event - decribes a PTP hardware clock event
+ * struct ptp_clock_event - describes a PTP hardware clock event
*
* @type: One of the ptp_clock_events enumeration values.
* @index: Identifies the source of the event.
- * @timestamp: When the event occurred (%PTP_CLOCK_EXTTS only).
- * @pps_times: When the event occurred (%PTP_CLOCK_PPSUSR only).
+ * @timestamp: When the event occurred (%PTP_CLOCK_EXTTS only).
+ * @pps_times: When the event occurred (%PTP_CLOCK_PPSUSR only).
+ * @alarm_time: When the event occurred, and on return the time for
+ * the next event (%PTP_CLOCK_ALARM only).
*/
struct ptp_clock_event {
@@ -163,6 +171,7 @@ struct ptp_clock_event {
union {
u64 timestamp;
struct pps_event_time pps_times;
+ struct timespec64 alarm_time;
};
};
diff --git a/include/uapi/linux/ptp_clock.h b/include/uapi/linux/ptp_clock.h
index ac6dded..066424b 100644
--- a/include/uapi/linux/ptp_clock.h
+++ b/include/uapi/linux/ptp_clock.h
@@ -70,6 +70,12 @@ struct ptp_perout_request {
unsigned int rsv[4]; /* Reserved for future use. */
};
+struct ptp_alarm_request {
+ unsigned int index; /* Which channel to configure. */
+ unsigned int flags; /* Bit field for PTP_xxx flags. */
+ unsigned int rsv[2]; /* Reserved for future use. */
+};
+
#define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */
struct ptp_sys_offset {
@@ -135,6 +141,7 @@ struct ptp_pin_desc {
#define PTP_PIN_SETFUNC _IOW(PTP_CLK_MAGIC, 7, struct ptp_pin_desc)
#define PTP_SYS_OFFSET_PRECISE \
_IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise)
+#define PTP_ALARM_REQUEST _IOW(PTP_CLK_MAGIC, 9, struct ptp_alarm_request)
struct ptp_extts_event {
struct ptp_clock_time t; /* Time event occured. */
------------------------------------------------------------------------------
---
diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h
index 5387b3a..671c579 100644
--- a/drivers/net/ethernet/intel/igb/igb.h
+++ b/drivers/net/ethernet/intel/igb/igb.h
@@ -352,6 +352,7 @@ struct hwmon_buff {
#define IGB_N_EXTTS 2
#define IGB_N_PEROUT 2
+#define IGB_N_ALARM 2
#define IGB_N_SDP 4
#define IGB_RETA_SIZE 128
@@ -456,6 +457,7 @@ struct igb_adapter {
struct {
struct timespec64 start;
struct timespec64 period;
+ bool enabled;
} perout[IGB_N_PEROUT];
char fw_version[32];
@@ -473,6 +475,8 @@ struct igb_adapter {
int copper_tries;
struct e1000_info ei;
u16 eee_advert;
+
+ int timer_channel;
};
/* flags controlling PTP/1588 function */
@@ -558,6 +562,10 @@ void igb_ptp_rx_pktstamp(struct igb_q_vector *q_vector, unsigned char *va,
int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr);
int igb_ptp_get_ts_config(struct net_device *netdev, struct ifreq *ifr);
void igb_set_flag_queue_pairs(struct igb_adapter *, const u32);
+void igb_tt_timer_enable(struct igb_adapter *adapter, bool enable,
+ struct timespec64 *ts);
+void igb_ptp_read_i210(struct igb_adapter *adapter,
+ struct timespec64 *ts);
#ifdef CONFIG_IGB_HWMON
void igb_sysfs_exit(struct igb_adapter *adapter);
int igb_sysfs_init(struct igb_adapter *adapter);
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index 942a89f..aae53e6 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -5638,32 +5638,69 @@ static void igb_tsync_interrupt(struct igb_adapter *adapter)
}
if (tsicr & TSINTR_TT0) {
- spin_lock(&adapter->tmreg_lock);
- ts = timespec64_add(adapter->perout[0].start,
- adapter->perout[0].period);
- /* u32 conversion of tv_sec is safe until y2106 */
- wr32(E1000_TRGTTIML0, ts.tv_nsec);
- wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
- tsauxc = rd32(E1000_TSAUXC);
- tsauxc |= TSAUXC_EN_TT0;
- wr32(E1000_TSAUXC, tsauxc);
- adapter->perout[0].start = ts;
- spin_unlock(&adapter->tmreg_lock);
- ack |= TSINTR_TT0;
+ if (adapter->timer_channel == 0) {
+ /* this is a timer interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ igb_tt_timer_enable(adapter, false, NULL);
+ wr32(E1000_TSICR, TSINTR_TT0);
+ igb_ptp_read_i210(adapter, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ event.type = PTP_CLOCK_ALARM;
+ event.index = 0;
+ /* ptp_clock_event will return the next time to set */
+ ptp_clock_event(adapter->ptp_clock, &event);
+ spin_lock(&adapter->tmreg_lock);
+ if (timespec64_to_ns(&event.alarm_time) != 0)
+ igb_tt_timer_enable(adapter, true, &event.alarm_time);
+
+ spin_unlock(&adapter->tmreg_lock);
+ } else {
+ /* this is a periodic output interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ ts = timespec64_add(adapter->perout[0].start,
+ adapter->perout[0].period);
+ /* u32 conversion of tv_sec is safe until y2106 */
+ wr32(E1000_TRGTTIML0, ts.tv_nsec);
+ wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
+ tsauxc = rd32(E1000_TSAUXC);
+ tsauxc |= TSAUXC_EN_TT0;
+ wr32(E1000_TSAUXC, tsauxc);
+ adapter->perout[0].start = ts;
+ spin_unlock(&adapter->tmreg_lock);
+ ack |= TSINTR_TT0;
+ }
+
}
if (tsicr & TSINTR_TT1) {
- spin_lock(&adapter->tmreg_lock);
- ts = timespec64_add(adapter->perout[1].start,
- adapter->perout[1].period);
- wr32(E1000_TRGTTIML1, ts.tv_nsec);
- wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
- tsauxc = rd32(E1000_TSAUXC);
- tsauxc |= TSAUXC_EN_TT1;
- wr32(E1000_TSAUXC, tsauxc);
- adapter->perout[1].start = ts;
- spin_unlock(&adapter->tmreg_lock);
- ack |= TSINTR_TT1;
+ if (adapter->timer_channel == 1) {
+ /* this is a timer interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ igb_tt_timer_enable(adapter, false, NULL);
+ wr32(E1000_TSICR, TSINTR_TT1);
+ igb_ptp_read_i210(adapter, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ event.type = PTP_CLOCK_ALARM;
+ /* ptp_clock_event will return the next time to set */
+ ptp_clock_event(adapter->ptp_clock, &event);
+ spin_lock(&adapter->tmreg_lock);
+ if (timespec64_to_ns(&event.alarm_time) != 0)
+ igb_tt_timer_enable(adapter, true, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ } else {
+ /* this is a periodic output interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ ts = timespec64_add(adapter->perout[1].start,
+ adapter->perout[1].period);
+ wr32(E1000_TRGTTIML1, ts.tv_nsec);
+ wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
+ tsauxc = rd32(E1000_TSAUXC);
+ tsauxc |= TSAUXC_EN_TT1;
+ wr32(E1000_TSAUXC, tsauxc);
+ adapter->perout[1].start = ts;
+ spin_unlock(&adapter->tmreg_lock);
+ ack |= TSINTR_TT1;
+ }
}
if (tsicr & TSINTR_AUTT0) {
@@ -5687,7 +5724,8 @@ static void igb_tsync_interrupt(struct igb_adapter *adapter)
}
/* acknowledge the interrupts */
- wr32(E1000_TSICR, ack);
+ if (ack)
+ wr32(E1000_TSICR, ack);
}
static irqreturn_t igb_msix_other(int irq, void *data)
diff --git a/drivers/net/ethernet/intel/igb/igb_ptp.c b/drivers/net/ethernet/intel/igb/igb_ptp.c
index 336c103..fffdb46 100644
--- a/drivers/net/ethernet/intel/igb/igb_ptp.c
+++ b/drivers/net/ethernet/intel/igb/igb_ptp.c
@@ -19,6 +19,7 @@
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/ptp_classify.h>
+#include <linux/posix-timers.h>
#include "igb.h"
@@ -116,8 +117,8 @@ static cycle_t igb_ptp_read_82580(const struct cyclecounter *cc)
}
/* SYSTIM read access for I210/I211 */
-static void igb_ptp_read_i210(struct igb_adapter *adapter,
- struct timespec64 *ts)
+void igb_ptp_read_i210(struct igb_adapter *adapter,
+ struct timespec64 *ts)
{
struct e1000_hw *hw = &adapter->hw;
u32 sec, nsec;
@@ -515,6 +516,9 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
return 0;
case PTP_CLK_REQ_PEROUT:
+ /* cannot use perout and timer at the same time */
+ if (igb->timer_channel == rq->perout.index)
+ return -EBUSY;
if (on) {
pin = ptp_find_pin(igb->ptp_clock, PTP_PF_PEROUT,
rq->perout.index);
@@ -578,7 +582,9 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
wr32(freqout, ns);
tsauxc |= tsauxc_mask;
tsim |= tsim_mask;
- }
+ igb->perout[i].enabled = true;
+ } else
+ igb->perout[rq->perout.index].enabled = false;
wr32(E1000_TSAUXC, tsauxc);
wr32(E1000_TSIM, tsim);
spin_unlock_irqrestore(&igb->tmreg_lock, flags);
@@ -594,6 +600,25 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
wr32(E1000_TSIM, tsim);
spin_unlock_irqrestore(&igb->tmreg_lock, flags);
return 0;
+
+ case PTP_CLK_REQ_ALARM:
+ /* cannot use perout and timer at the same time */
+ if (igb->perout[rq->alarm.index].enabled)
+ return -EBUSY;
+ if (on && (igb->timer_channel != -1))
+ return -EINVAL;
+ if (!on && (igb->timer_channel != rq->alarm.index))
+ return -EINVAL;
+ if (on)
+ igb->timer_channel = rq->alarm.index;
+ else {
+ /* disable the timer */
+ spin_lock_irqsave(&igb->tmreg_lock, flags);
+ igb_tt_timer_enable(igb, false, NULL);
+ igb->timer_channel = -1;
+ spin_unlock_irqrestore(&igb->tmreg_lock, flags);
+ }
+ return 0;
}
return -EOPNOTSUPP;
@@ -1063,6 +1088,71 @@ int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr)
-EFAULT : 0;
}
+/* tmreg_lock should be held for this call */
+void igb_tt_timer_enable(struct igb_adapter *adapter, bool enable,
+ struct timespec64 *ts)
+{
+ u32 tsauxc, tsim, ttimlreg, ttimhreg;
+ struct e1000_hw *hw = &adapter->hw;
+ int channel = adapter->timer_channel;
+
+ tsauxc = rd32(E1000_TSAUXC);
+ tsim = rd32(E1000_TSIM);
+
+ if (enable) {
+ if (channel == 1) {
+ tsauxc |= TSAUXC_EN_TT1;
+ tsim |= TSINTR_TT1;
+ ttimlreg = E1000_TRGTTIML1;
+ ttimhreg = E1000_TRGTTIMH1;
+ }
+ else {
+ tsauxc |= TSAUXC_EN_TT0;
+ tsim |= TSINTR_TT0;
+ ttimlreg = E1000_TRGTTIML0;
+ ttimhreg = E1000_TRGTTIMH0;
+ }
+ /* set trigger time and then enable timer */
+ wr32(ttimlreg, ts->tv_nsec);
+ wr32(ttimhreg, (u32)ts->tv_sec);
+ wr32(E1000_TSAUXC, tsauxc);
+ wr32(E1000_TSIM, tsim);
+ } else {
+ if (channel == 1) {
+ tsauxc &= ~TSAUXC_EN_TT1;
+ tsim &= ~TSINTR_TT1;
+ }
+ else {
+ tsauxc &= ~TSAUXC_EN_TT0;
+ tsim &= ~TSINTR_TT0;
+ }
+ wr32(E1000_TSAUXC, tsauxc);
+ wr32(E1000_TSIM, tsim);
+ }
+}
+
+static int igb_ptp_timer_settime_i210(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct igb_adapter *igb =
+ container_of(ptp, struct igb_adapter, ptp_caps);
+ unsigned long irqsaveflags;
+
+ if (igb->timer_channel == -1)
+ return -EBUSY;
+
+ spin_lock_irqsave(&igb->tmreg_lock, irqsaveflags);
+
+ if (timespec64_to_ns(ts) == 0)
+ igb_tt_timer_enable(igb, false, NULL);
+ else
+ igb_tt_timer_enable(igb, true, ts);
+
+ spin_unlock_irqrestore(&igb->tmreg_lock, irqsaveflags);
+
+ return 0;
+}
+
/**
* igb_ptp_init - Initialize PTP functionality
* @adapter: Board private structure
@@ -1125,6 +1215,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
snprintf(adapter->ptp_caps.name, 16, "%pm", netdev->dev_addr);
adapter->ptp_caps.owner = THIS_MODULE;
adapter->ptp_caps.max_adj = 62499999;
+ adapter->ptp_caps.n_alarm = IGB_N_ALARM;
adapter->ptp_caps.n_ext_ts = IGB_N_EXTTS;
adapter->ptp_caps.n_per_out = IGB_N_PEROUT;
adapter->ptp_caps.n_pins = IGB_N_SDP;
@@ -1136,6 +1227,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
adapter->ptp_caps.settime64 = igb_ptp_settime_i210;
adapter->ptp_caps.enable = igb_ptp_feature_enable_i210;
adapter->ptp_caps.verify = igb_ptp_verify_pin;
+ adapter->ptp_caps.timersettime = igb_ptp_timer_settime_i210;
break;
default:
adapter->ptp_clock = NULL;
@@ -1151,6 +1243,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
+ adapter->timer_channel = -1;
igb_ptp_reset(adapter);
diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c
index d637c93..b351c44 100644
--- a/drivers/ptp/ptp_chardev.c
+++ b/drivers/ptp/ptp_chardev.c
@@ -176,6 +176,21 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
err = ops->enable(ops, &req, enable);
break;
+ case PTP_ALARM_REQUEST:
+ if (copy_from_user(&req.alarm, (void __user *)arg,
+ sizeof(req.alarm))) {
+ err = -EFAULT;
+ break;
+ }
+ if (req.alarm.index >= ops->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ req.type = PTP_CLK_REQ_ALARM;
+ enable = req.alarm.flags & PTP_ENABLE_FEATURE ? 1 : 0;
+ err = ops->enable(ops, &req, enable);
+ break;
+
case PTP_ENABLE_PPS:
if (!capable(CAP_SYS_TIME))
return -EPERM;
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index 2e481b9..28573dc 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -36,6 +36,8 @@
#define PTP_PPS_EVENT PPS_CAPTUREASSERT
#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
+#define PTP_TIMER_MINIMUM_INTERVAL_NS 100000
+
/* private globals */
static dev_t ptp_devt;
@@ -43,6 +45,56 @@ static struct class *ptp_class;
static DEFINE_IDA(ptp_clocks_map);
+static void alarm_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
+{
+ struct timerqueue_node *next;
+ struct k_itimer *kit;
+ s64 ns_now;
+ bool signal_failed_to_send;
+ unsigned long tq_lock_flags;
+
+ ns_now = timespec64_to_ns(&event->alarm_time);
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+
+ while (next) {
+ if (next->expires.tv64 > ns_now)
+ break;
+
+ kit = container_of(next, struct k_itimer, it.real.timer.node);
+
+ signal_failed_to_send = posix_timer_event(kit, 0);
+
+ /* update the last one that has fired */
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+ if ((ktime_to_ns(kit->it.real.interval) != 0)
+ && !signal_failed_to_send) {
+ /* this is a periodic timer, set the next fire time */
+ kit->it.real.timer.node.expires =
+ ktime_add(
+ kit->it.real.timer.node.expires,
+ kit->it.real.interval);
+ timerqueue_add(&ptp->timerqueue,
+ &kit->it.real.timer.node);
+ }
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+ }
+
+ /* now set the event time to the next timer fire time */
+ next = timerqueue_getnext(&ptp->timerqueue);
+ if (next) {
+ event->alarm_time = ktime_to_timespec64(next->expires);
+ } else {
+ event->alarm_time.tv_sec = 0;
+ event->alarm_time.tv_nsec = 0;
+ }
+
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+}
+
/* time stamp event queue operations */
static inline int queue_free(struct timestamp_event_queue *q)
@@ -163,12 +215,113 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct timex *tx)
return err;
}
+static int ptp_timer_create(struct posix_clock *pc, struct k_itimer *kit)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ unsigned long tq_lock_flags;
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+ timerqueue_init(&kit->it.real.timer.node);
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ return 0;
+}
+
+static int ptp_timer_delete(struct posix_clock *pc, struct k_itimer *kit)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ unsigned long tq_lock_flags;
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+ if (!RB_EMPTY_NODE(&kit->it.real.timer.node.node))
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ return 0;
+}
+
+static void ptp_timer_gettime(struct posix_clock *pc,
+ struct k_itimer *kit,
+ struct itimerspec *tsp)
+{
+ struct timespec time_now;
+
+ if (ptp_clock_gettime(pc, &time_now) != 0)
+ return;
+
+ tsp->it_interval = ktime_to_timespec(kit->it.real.interval);
+ tsp->it_value = timespec_sub(ktime_to_timespec(
+ kit->it.real.timer.node.expires), time_now);
+}
+
+
+static int ptp_timer_settime(struct posix_clock *pc,
+ struct k_itimer *kit, int flags,
+ struct itimerspec *tsp, struct itimerspec *old)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ int err;
+ unsigned long tq_lock_flags;
+ struct timespec time_now;
+ ktime_t fire_time;
+ struct timerqueue_node *next;
+ struct timespec64 ts;
+
+ if (ptp->info->timersettime == 0)
+ return -EOPNOTSUPP;
+
+ if (old)
+ ptp_timer_gettime(pc, kit, old);
+
+ fire_time = timespec_to_ktime(tsp->it_value);
+
+ if ((fire_time.tv64 != 0) && !(flags & TIMER_ABSTIME)) {
+ err = ptp_clock_gettime(pc, &time_now);
+ if (err)
+ return err;
+ /* convert relative to absolute time */
+ fire_time = ktime_add(fire_time, timespec_to_ktime(time_now));
+ }
+
+ kit->it.real.interval = timespec_to_ktime(tsp->it_interval);
+
+ if ((ktime_to_ns(kit->it.real.interval) != 0)
+ && (ktime_to_ns(kit->it.real.interval) < PTP_TIMER_MINIMUM_INTERVAL_NS))
+ kit->it.real.interval = ns_to_ktime(PTP_TIMER_MINIMUM_INTERVAL_NS);
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+ kit->it.real.timer.node.expires = fire_time;
+
+ if (!RB_EMPTY_NODE(&kit->it.real.timer.node.node))
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+
+ if (fire_time.tv64 != 0)
+ timerqueue_add(&ptp->timerqueue, &kit->it.real.timer.node);
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ if (next)
+ ts = ktime_to_timespec64(next->expires);
+ else {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ return ptp->info->timersettime(ptp->info, &ts);
+}
+
static struct posix_clock_operations ptp_clock_ops = {
.owner = THIS_MODULE,
.clock_adjtime = ptp_clock_adjtime,
.clock_gettime = ptp_clock_gettime,
.clock_getres = ptp_clock_getres,
.clock_settime = ptp_clock_settime,
+ .timer_create = ptp_timer_create,
+ .timer_delete = ptp_timer_delete,
+ .timer_gettime = ptp_timer_gettime,
+ .timer_settime = ptp_timer_settime,
.ioctl = ptp_ioctl,
.open = ptp_open,
.poll = ptp_poll,
@@ -217,6 +370,8 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
mutex_init(&ptp->tsevq_mux);
mutex_init(&ptp->pincfg_mux);
init_waitqueue_head(&ptp->tsev_wq);
+ spin_lock_init(&ptp->tq_lock);
+ timerqueue_init_head(&ptp->timerqueue);
/* Create a new device in our class. */
ptp->dev = device_create(ptp_class, parent, ptp->devid, ptp,
@@ -293,6 +448,7 @@ void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
switch (event->type) {
case PTP_CLOCK_ALARM:
+ alarm_event(ptp, event);
break;
case PTP_CLOCK_EXTTS:
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h
index 9c5d414..d491299 100644
--- a/drivers/ptp/ptp_private.h
+++ b/drivers/ptp/ptp_private.h
@@ -54,6 +54,10 @@ struct ptp_clock {
struct device_attribute *pin_dev_attr;
struct attribute **pin_attr;
struct attribute_group pin_attr_group;
+
+ struct timerqueue_head timerqueue;
+ spinlock_t tq_lock;
+ struct work_struct alarm_work;
};
/*
diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
index 6b15e16..155acc2 100644
--- a/include/linux/ptp_clock_kernel.h
+++ b/include/linux/ptp_clock_kernel.h
@@ -31,10 +31,12 @@ struct ptp_clock_request {
PTP_CLK_REQ_EXTTS,
PTP_CLK_REQ_PEROUT,
PTP_CLK_REQ_PPS,
+ PTP_CLK_REQ_ALARM
} type;
union {
struct ptp_extts_request extts;
struct ptp_perout_request perout;
+ struct ptp_alarm_request alarm;
};
};
@@ -92,6 +94,9 @@ struct system_device_crosststamp;
* parameter func: the desired function to use.
* parameter chan: the function channel index to use.
*
+ * @timersettime: Set the alarm time for the hardware timer.
+ * parameter ts: Time value to set.
+ *
* Drivers should embed their ptp_clock_info within a private
* structure, obtaining a reference to it using container_of().
*
@@ -118,6 +123,7 @@ struct ptp_clock_info {
struct ptp_clock_request *request, int on);
int (*verify)(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan);
+ int (*timersettime)(struct ptp_clock_info *ptp, struct timespec64 *ts);
};
struct ptp_clock;
@@ -149,12 +155,14 @@ enum ptp_clock_events {
};
/**
- * struct ptp_clock_event - decribes a PTP hardware clock event
+ * struct ptp_clock_event - describes a PTP hardware clock event
*
* @type: One of the ptp_clock_events enumeration values.
* @index: Identifies the source of the event.
- * @timestamp: When the event occurred (%PTP_CLOCK_EXTTS only).
- * @pps_times: When the event occurred (%PTP_CLOCK_PPSUSR only).
+ * @timestamp: When the event occurred (%PTP_CLOCK_EXTTS only).
+ * @pps_times: When the event occurred (%PTP_CLOCK_PPSUSR only).
+ * @alarm_time: When the event occurred, and on return the time for
+ * the next event (%PTP_CLOCK_ALARM only).
*/
struct ptp_clock_event {
@@ -163,6 +171,7 @@ struct ptp_clock_event {
union {
u64 timestamp;
struct pps_event_time pps_times;
+ struct timespec64 alarm_time;
};
};
diff --git a/include/uapi/linux/ptp_clock.h b/include/uapi/linux/ptp_clock.h
index ac6dded..066424b 100644
--- a/include/uapi/linux/ptp_clock.h
+++ b/include/uapi/linux/ptp_clock.h
@@ -70,6 +70,12 @@ struct ptp_perout_request {
unsigned int rsv[4]; /* Reserved for future use. */
};
+struct ptp_alarm_request {
+ unsigned int index; /* Which channel to configure. */
+ unsigned int flags; /* Bit field for PTP_xxx flags. */
+ unsigned int rsv[2]; /* Reserved for future use. */
+};
+
#define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */
struct ptp_sys_offset {
@@ -135,6 +141,7 @@ struct ptp_pin_desc {
#define PTP_PIN_SETFUNC _IOW(PTP_CLK_MAGIC, 7, struct ptp_pin_desc)
#define PTP_SYS_OFFSET_PRECISE \
_IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise)
+#define PTP_ALARM_REQUEST _IOW(PTP_CLK_MAGIC, 9, struct ptp_alarm_request)
struct ptp_extts_event {
struct ptp_clock_time t; /* Time event occured. */
------------------------------------------------------------------------------