Skip to content

Commit 350749b

Browse files
Wei Fangdavem330
authored andcommitted
net: fec: Add support for periodic output signal of PPS
This patch adds the support for configuring periodic output signal of PPS. So the PPS can be output at a specified time and period. For developers or testers, they can use the command "echo <channel> <start.sec> <start.nsec> <period.sec> <period. nsec> > /sys/class/ptp/ptp0/period" to specify time and period to output PPS signal. Notice that, the channel can only be set to 0. In addtion, the start time must larger than the current PTP clock time. So users can use the command "phc_ctl /dev/ptp0 -- get" to get the current PTP clock time before. Signed-off-by: Wei Fang <[email protected]> Acked-by: Richard Cochran <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 0cafd77 commit 350749b

File tree

2 files changed

+164
-2
lines changed

2 files changed

+164
-2
lines changed

drivers/net/ethernet/freescale/fec.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,8 @@ struct fec_enet_private {
658658
unsigned int reload_period;
659659
int pps_enable;
660660
unsigned int next_counter;
661+
struct hrtimer perout_timer;
662+
u64 perout_stime;
661663

662664
struct imx_sc_ipc *ipc_handle;
663665

drivers/net/ethernet/freescale/fec_ptp.c

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
#define FEC_CHANNLE_0 0
8989
#define DEFAULT_PPS_CHANNEL FEC_CHANNLE_0
9090

91+
#define FEC_PTP_MAX_NSEC_PERIOD 4000000000ULL
92+
#define FEC_PTP_MAX_NSEC_COUNTER 0x80000000ULL
93+
9194
/**
9295
* fec_ptp_enable_pps
9396
* @fep: the fec_enet_private structure handle
@@ -198,6 +201,78 @@ static int fec_ptp_enable_pps(struct fec_enet_private *fep, uint enable)
198201
return 0;
199202
}
200203

204+
static int fec_ptp_pps_perout(struct fec_enet_private *fep)
205+
{
206+
u32 compare_val, ptp_hc, temp_val;
207+
u64 curr_time;
208+
unsigned long flags;
209+
210+
spin_lock_irqsave(&fep->tmreg_lock, flags);
211+
212+
/* Update time counter */
213+
timecounter_read(&fep->tc);
214+
215+
/* Get the current ptp hardware time counter */
216+
temp_val = readl(fep->hwp + FEC_ATIME_CTRL);
217+
temp_val |= FEC_T_CTRL_CAPTURE;
218+
writel(temp_val, fep->hwp + FEC_ATIME_CTRL);
219+
if (fep->quirks & FEC_QUIRK_BUG_CAPTURE)
220+
udelay(1);
221+
222+
ptp_hc = readl(fep->hwp + FEC_ATIME);
223+
224+
/* Convert the ptp local counter to 1588 timestamp */
225+
curr_time = timecounter_cyc2time(&fep->tc, ptp_hc);
226+
227+
/* If the pps start time less than current time add 100ms, just return.
228+
* Because the software might not able to set the comparison time into
229+
* the FEC_TCCR register in time and missed the start time.
230+
*/
231+
if (fep->perout_stime < curr_time + 100 * NSEC_PER_MSEC) {
232+
dev_err(&fep->pdev->dev, "Current time is too close to the start time!\n");
233+
spin_unlock_irqrestore(&fep->tmreg_lock, flags);
234+
return -1;
235+
}
236+
237+
compare_val = fep->perout_stime - curr_time + ptp_hc;
238+
compare_val &= fep->cc.mask;
239+
240+
writel(compare_val, fep->hwp + FEC_TCCR(fep->pps_channel));
241+
fep->next_counter = (compare_val + fep->reload_period) & fep->cc.mask;
242+
243+
/* Enable compare event when overflow */
244+
temp_val = readl(fep->hwp + FEC_ATIME_CTRL);
245+
temp_val |= FEC_T_CTRL_PINPER;
246+
writel(temp_val, fep->hwp + FEC_ATIME_CTRL);
247+
248+
/* Compare channel setting. */
249+
temp_val = readl(fep->hwp + FEC_TCSR(fep->pps_channel));
250+
temp_val |= (1 << FEC_T_TF_OFFSET | 1 << FEC_T_TIE_OFFSET);
251+
temp_val &= ~(1 << FEC_T_TDRE_OFFSET);
252+
temp_val &= ~(FEC_T_TMODE_MASK);
253+
temp_val |= (FEC_TMODE_TOGGLE << FEC_T_TMODE_OFFSET);
254+
writel(temp_val, fep->hwp + FEC_TCSR(fep->pps_channel));
255+
256+
/* Write the second compare event timestamp and calculate
257+
* the third timestamp. Refer the TCCR register detail in the spec.
258+
*/
259+
writel(fep->next_counter, fep->hwp + FEC_TCCR(fep->pps_channel));
260+
fep->next_counter = (fep->next_counter + fep->reload_period) & fep->cc.mask;
261+
spin_unlock_irqrestore(&fep->tmreg_lock, flags);
262+
263+
return 0;
264+
}
265+
266+
static enum hrtimer_restart fec_ptp_pps_perout_handler(struct hrtimer *timer)
267+
{
268+
struct fec_enet_private *fep = container_of(timer,
269+
struct fec_enet_private, perout_timer);
270+
271+
fec_ptp_pps_perout(fep);
272+
273+
return HRTIMER_NORESTART;
274+
}
275+
201276
/**
202277
* fec_ptp_read - read raw cycle counter (to be used by time counter)
203278
* @cc: the cyclecounter structure
@@ -425,6 +500,17 @@ static int fec_ptp_settime(struct ptp_clock_info *ptp,
425500
return 0;
426501
}
427502

503+
static int fec_ptp_pps_disable(struct fec_enet_private *fep, uint channel)
504+
{
505+
unsigned long flags;
506+
507+
spin_lock_irqsave(&fep->tmreg_lock, flags);
508+
writel(0, fep->hwp + FEC_TCSR(channel));
509+
spin_unlock_irqrestore(&fep->tmreg_lock, flags);
510+
511+
return 0;
512+
}
513+
428514
/**
429515
* fec_ptp_enable
430516
* @ptp: the ptp clock structure
@@ -437,14 +523,84 @@ static int fec_ptp_enable(struct ptp_clock_info *ptp,
437523
{
438524
struct fec_enet_private *fep =
439525
container_of(ptp, struct fec_enet_private, ptp_caps);
526+
ktime_t timeout;
527+
struct timespec64 start_time, period;
528+
u64 curr_time, delta, period_ns;
529+
unsigned long flags;
440530
int ret = 0;
441531

442532
if (rq->type == PTP_CLK_REQ_PPS) {
443533
ret = fec_ptp_enable_pps(fep, on);
444534

445535
return ret;
536+
} else if (rq->type == PTP_CLK_REQ_PEROUT) {
537+
/* Reject requests with unsupported flags */
538+
if (rq->perout.flags)
539+
return -EOPNOTSUPP;
540+
541+
if (rq->perout.index != DEFAULT_PPS_CHANNEL)
542+
return -EOPNOTSUPP;
543+
544+
fep->pps_channel = DEFAULT_PPS_CHANNEL;
545+
period.tv_sec = rq->perout.period.sec;
546+
period.tv_nsec = rq->perout.period.nsec;
547+
period_ns = timespec64_to_ns(&period);
548+
549+
/* FEC PTP timer only has 31 bits, so if the period exceed
550+
* 4s is not supported.
551+
*/
552+
if (period_ns > FEC_PTP_MAX_NSEC_PERIOD) {
553+
dev_err(&fep->pdev->dev, "The period must equal to or less than 4s!\n");
554+
return -EOPNOTSUPP;
555+
}
556+
557+
fep->reload_period = div_u64(period_ns, 2);
558+
if (on && fep->reload_period) {
559+
/* Convert 1588 timestamp to ns*/
560+
start_time.tv_sec = rq->perout.start.sec;
561+
start_time.tv_nsec = rq->perout.start.nsec;
562+
fep->perout_stime = timespec64_to_ns(&start_time);
563+
564+
mutex_lock(&fep->ptp_clk_mutex);
565+
if (!fep->ptp_clk_on) {
566+
dev_err(&fep->pdev->dev, "Error: PTP clock is closed!\n");
567+
mutex_unlock(&fep->ptp_clk_mutex);
568+
return -EOPNOTSUPP;
569+
}
570+
spin_lock_irqsave(&fep->tmreg_lock, flags);
571+
/* Read current timestamp */
572+
curr_time = timecounter_read(&fep->tc);
573+
spin_unlock_irqrestore(&fep->tmreg_lock, flags);
574+
mutex_unlock(&fep->ptp_clk_mutex);
575+
576+
/* Calculate time difference */
577+
delta = fep->perout_stime - curr_time;
578+
579+
if (fep->perout_stime <= curr_time) {
580+
dev_err(&fep->pdev->dev, "Start time must larger than current time!\n");
581+
return -EINVAL;
582+
}
583+
584+
/* Because the timer counter of FEC only has 31-bits, correspondingly,
585+
* the time comparison register FEC_TCCR also only low 31 bits can be
586+
* set. If the start time of pps signal exceeds current time more than
587+
* 0x80000000 ns, a software timer is used and the timer expires about
588+
* 1 second before the start time to be able to set FEC_TCCR.
589+
*/
590+
if (delta > FEC_PTP_MAX_NSEC_COUNTER) {
591+
timeout = ns_to_ktime(delta - NSEC_PER_SEC);
592+
hrtimer_start(&fep->perout_timer, timeout, HRTIMER_MODE_REL);
593+
} else {
594+
return fec_ptp_pps_perout(fep);
595+
}
596+
} else {
597+
fec_ptp_pps_disable(fep, fep->pps_channel);
598+
}
599+
600+
return 0;
601+
} else {
602+
return -EOPNOTSUPP;
446603
}
447-
return -EOPNOTSUPP;
448604
}
449605

450606
/**
@@ -583,7 +739,7 @@ void fec_ptp_init(struct platform_device *pdev, int irq_idx)
583739
fep->ptp_caps.max_adj = 250000000;
584740
fep->ptp_caps.n_alarm = 0;
585741
fep->ptp_caps.n_ext_ts = 0;
586-
fep->ptp_caps.n_per_out = 0;
742+
fep->ptp_caps.n_per_out = 1;
587743
fep->ptp_caps.n_pins = 0;
588744
fep->ptp_caps.pps = 1;
589745
fep->ptp_caps.adjfreq = fec_ptp_adjfreq;
@@ -605,6 +761,9 @@ void fec_ptp_init(struct platform_device *pdev, int irq_idx)
605761

606762
INIT_DELAYED_WORK(&fep->time_keep, fec_time_keep);
607763

764+
hrtimer_init(&fep->perout_timer, CLOCK_REALTIME, HRTIMER_MODE_REL);
765+
fep->perout_timer.function = fec_ptp_pps_perout_handler;
766+
608767
irq = platform_get_irq_byname_optional(pdev, "pps");
609768
if (irq < 0)
610769
irq = platform_get_irq_optional(pdev, irq_idx);
@@ -634,6 +793,7 @@ void fec_ptp_stop(struct platform_device *pdev)
634793
struct fec_enet_private *fep = netdev_priv(ndev);
635794

636795
cancel_delayed_work_sync(&fep->time_keep);
796+
hrtimer_cancel(&fep->perout_timer);
637797
if (fep->ptp_clock)
638798
ptp_clock_unregister(fep->ptp_clock);
639799
}

0 commit comments

Comments
 (0)