#define NETC_TMR_CUR_TIME_H 0x00f4
#define NETC_TMR_REGS_BAR 0
+#define NETC_GLOBAL_OFFSET 0x10000
+#define NETC_GLOBAL_IPBRR0 0xbf8
+#define IPBRR0_IP_REV GENMASK(15, 0)
+#define NETC_REV_4_1 0x0401
#define NETC_TMR_FIPER_NUM 3
+#define NETC_TMR_INVALID_CHANNEL NETC_TMR_FIPER_NUM
#define NETC_TMR_DEFAULT_PRSC 2
#define NETC_TMR_DEFAULT_ALARM GENMASK_ULL(63, 0)
#define NETC_TMR_DEFAULT_FIPER GENMASK(31, 0)
#define NETC_TMR_FIPER_MAX_PW GENMASK(4, 0)
+#define NETC_TMR_ALARM_NUM 2
/* 1588 timer reference clock source select */
#define NETC_TMR_CCM_TIMER1 0 /* enet_timer1_clk_root, from CCM */
#define NETC_TMR_SYSCLK_333M 333333333U
+enum netc_pp_type {
+ NETC_PP_PPS = 1,
+ NETC_PP_PEROUT,
+};
+
+struct netc_pp {
+ enum netc_pp_type type;
+ bool enabled;
+ int alarm_id;
+ u32 period; /* pulse period, ns */
+ u64 stime; /* start time, ns */
+};
+
struct netc_timer {
void __iomem *base;
struct pci_dev *pdev;
int irq;
char irq_name[24];
+ int revision;
u32 tmr_emask;
- bool pps_enabled;
+ u8 pps_channel;
+ u8 fs_alarm_num;
+ u8 fs_alarm_bitmap;
+ struct netc_pp pp[NETC_TMR_FIPER_NUM]; /* periodic pulse */
};
#define netc_timer_rd(p, o) netc_read((p)->base + (o))
static void netc_timer_set_pps_alarm(struct netc_timer *priv, int channel,
u32 integral_period)
{
+ struct netc_pp *pp = &priv->pp[channel];
u64 alarm;
/* Get the alarm value */
alarm = roundup_u64(alarm, NSEC_PER_SEC);
alarm = roundup_u64(alarm, integral_period);
- netc_timer_alarm_write(priv, alarm, 0);
+ netc_timer_alarm_write(priv, alarm, pp->alarm_id);
+}
+
+static void netc_timer_set_perout_alarm(struct netc_timer *priv, int channel,
+ u32 integral_period)
+{
+ u64 cur_time = netc_timer_cur_time_read(priv);
+ struct netc_pp *pp = &priv->pp[channel];
+ u64 alarm, delta, min_time;
+ u32 period = pp->period;
+ u64 stime = pp->stime;
+
+ min_time = cur_time + NSEC_PER_MSEC + period;
+ if (stime < min_time) {
+ delta = min_time - stime;
+ stime += roundup_u64(delta, period);
+ }
+
+ alarm = roundup_u64(stime - period, integral_period);
+ netc_timer_alarm_write(priv, alarm, pp->alarm_id);
+}
+
+static int netc_timer_get_alarm_id(struct netc_timer *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->fs_alarm_num; i++) {
+ if (!(priv->fs_alarm_bitmap & BIT(i))) {
+ priv->fs_alarm_bitmap |= BIT(i);
+ break;
+ }
+ }
+
+ return i;
+}
+
+static u64 netc_timer_get_gclk_period(struct netc_timer *priv)
+{
+ /* TMR_GCLK_freq = (clk_freq / oclk_prsc) Hz.
+ * TMR_GCLK_period = NSEC_PER_SEC / TMR_GCLK_freq.
+ * TMR_GCLK_period = (NSEC_PER_SEC * oclk_prsc) / clk_freq
+ */
+
+ return div_u64(mul_u32_u32(NSEC_PER_SEC, priv->oclk_prsc),
+ priv->clk_freq);
+}
+
+static void netc_timer_enable_periodic_pulse(struct netc_timer *priv,
+ u8 channel)
+{
+ u32 fiper_pw, fiper, fiper_ctrl, integral_period;
+ struct netc_pp *pp = &priv->pp[channel];
+ int alarm_id = pp->alarm_id;
+
+ integral_period = netc_timer_get_integral_period(priv);
+ /* Set to desired FIPER interval in ns - TCLK_PERIOD */
+ fiper = pp->period - integral_period;
+ fiper_pw = netc_timer_calculate_fiper_pw(priv, fiper);
+
+ fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ fiper_ctrl &= ~(FIPER_CTRL_DIS(channel) | FIPER_CTRL_PW(channel) |
+ FIPER_CTRL_FS_ALARM(channel));
+ fiper_ctrl |= FIPER_CTRL_SET_PW(channel, fiper_pw);
+ fiper_ctrl |= alarm_id ? FIPER_CTRL_FS_ALARM(channel) : 0;
+
+ priv->tmr_emask |= TMR_TEVNET_PPEN(channel) |
+ TMR_TEVENT_ALMEN(alarm_id);
+
+ if (pp->type == NETC_PP_PPS)
+ netc_timer_set_pps_alarm(priv, channel, integral_period);
+ else
+ netc_timer_set_perout_alarm(priv, channel, integral_period);
+
+ netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
+ netc_timer_wr(priv, NETC_TMR_FIPER(channel), fiper);
+ netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+}
+
+static void netc_timer_disable_periodic_pulse(struct netc_timer *priv,
+ u8 channel)
+{
+ struct netc_pp *pp = &priv->pp[channel];
+ int alarm_id = pp->alarm_id;
+ u32 fiper_ctrl;
+
+ if (!pp->enabled)
+ return;
+
+ priv->tmr_emask &= ~(TMR_TEVNET_PPEN(channel) |
+ TMR_TEVENT_ALMEN(alarm_id));
+
+ fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ fiper_ctrl |= FIPER_CTRL_DIS(channel);
+
+ netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, alarm_id);
+ netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
+ netc_timer_wr(priv, NETC_TMR_FIPER(channel), NETC_TMR_DEFAULT_FIPER);
+ netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+}
+
+static u8 netc_timer_select_pps_channel(struct netc_timer *priv)
+{
+ int i;
+
+ for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
+ if (!priv->pp[i].enabled)
+ return i;
+ }
+
+ return NETC_TMR_INVALID_CHANNEL;
}
/* Note that users should not use this API to output PPS signal on
static int netc_timer_enable_pps(struct netc_timer *priv,
struct ptp_clock_request *rq, int on)
{
- u32 fiper, fiper_ctrl;
+ struct device *dev = &priv->pdev->dev;
unsigned long flags;
+ struct netc_pp *pp;
+ int err = 0;
spin_lock_irqsave(&priv->lock, flags);
- fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
-
if (on) {
- u32 integral_period, fiper_pw;
+ int alarm_id;
+ u8 channel;
+
+ if (priv->pps_channel < NETC_TMR_FIPER_NUM) {
+ channel = priv->pps_channel;
+ } else {
+ channel = netc_timer_select_pps_channel(priv);
+ if (channel == NETC_TMR_INVALID_CHANNEL) {
+ dev_err(dev, "No available FIPERs\n");
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+ }
- if (priv->pps_enabled)
+ pp = &priv->pp[channel];
+ if (pp->enabled)
goto unlock_spinlock;
- integral_period = netc_timer_get_integral_period(priv);
- fiper = NSEC_PER_SEC - integral_period;
- fiper_pw = netc_timer_calculate_fiper_pw(priv, fiper);
- fiper_ctrl &= ~(FIPER_CTRL_DIS(0) | FIPER_CTRL_PW(0) |
- FIPER_CTRL_FS_ALARM(0));
- fiper_ctrl |= FIPER_CTRL_SET_PW(0, fiper_pw);
- priv->tmr_emask |= TMR_TEVNET_PPEN(0) | TMR_TEVENT_ALMEN(0);
- priv->pps_enabled = true;
- netc_timer_set_pps_alarm(priv, 0, integral_period);
+ alarm_id = netc_timer_get_alarm_id(priv);
+ if (alarm_id == priv->fs_alarm_num) {
+ dev_err(dev, "No available ALARMs\n");
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+
+ pp->enabled = true;
+ pp->type = NETC_PP_PPS;
+ pp->alarm_id = alarm_id;
+ pp->period = NSEC_PER_SEC;
+ priv->pps_channel = channel;
+
+ netc_timer_enable_periodic_pulse(priv, channel);
} else {
- if (!priv->pps_enabled)
+ /* pps_channel is invalid if PPS is not enabled, so no
+ * processing is needed.
+ */
+ if (priv->pps_channel >= NETC_TMR_FIPER_NUM)
goto unlock_spinlock;
- fiper = NETC_TMR_DEFAULT_FIPER;
- priv->tmr_emask &= ~(TMR_TEVNET_PPEN(0) |
- TMR_TEVENT_ALMEN(0));
- fiper_ctrl |= FIPER_CTRL_DIS(0);
- priv->pps_enabled = false;
- netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 0);
+ netc_timer_disable_periodic_pulse(priv, priv->pps_channel);
+ pp = &priv->pp[priv->pps_channel];
+ priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id);
+ memset(pp, 0, sizeof(*pp));
+ priv->pps_channel = NETC_TMR_INVALID_CHANNEL;
}
- netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
- netc_timer_wr(priv, NETC_TMR_FIPER(0), fiper);
- netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+unlock_spinlock:
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return err;
+}
+
+static int net_timer_enable_perout(struct netc_timer *priv,
+ struct ptp_clock_request *rq, int on)
+{
+ struct device *dev = &priv->pdev->dev;
+ u32 channel = rq->perout.index;
+ unsigned long flags;
+ struct netc_pp *pp;
+ int err = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ pp = &priv->pp[channel];
+ if (pp->type == NETC_PP_PPS) {
+ dev_err(dev, "FIPER%u is being used for PPS\n", channel);
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+
+ if (on) {
+ u64 period_ns, gclk_period, max_period, min_period;
+ struct timespec64 period, stime;
+ u32 integral_period;
+ int alarm_id;
+
+ period.tv_sec = rq->perout.period.sec;
+ period.tv_nsec = rq->perout.period.nsec;
+ period_ns = timespec64_to_ns(&period);
+
+ integral_period = netc_timer_get_integral_period(priv);
+ max_period = (u64)NETC_TMR_DEFAULT_FIPER + integral_period;
+ gclk_period = netc_timer_get_gclk_period(priv);
+ min_period = gclk_period * 4 + integral_period;
+ if (period_ns > max_period || period_ns < min_period) {
+ dev_err(dev, "The period range is %llu ~ %llu\n",
+ min_period, max_period);
+ err = -EINVAL;
+ goto unlock_spinlock;
+ }
+
+ if (pp->enabled) {
+ alarm_id = pp->alarm_id;
+ } else {
+ alarm_id = netc_timer_get_alarm_id(priv);
+ if (alarm_id == priv->fs_alarm_num) {
+ dev_err(dev, "No available ALARMs\n");
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+
+ pp->type = NETC_PP_PEROUT;
+ pp->enabled = true;
+ pp->alarm_id = alarm_id;
+ }
+
+ stime.tv_sec = rq->perout.start.sec;
+ stime.tv_nsec = rq->perout.start.nsec;
+ pp->stime = timespec64_to_ns(&stime);
+ pp->period = period_ns;
+
+ netc_timer_enable_periodic_pulse(priv, channel);
+ } else {
+ netc_timer_disable_periodic_pulse(priv, channel);
+ priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id);
+ memset(pp, 0, sizeof(*pp));
+ }
unlock_spinlock:
spin_unlock_irqrestore(&priv->lock, flags);
- return 0;
+ return err;
}
-static void netc_timer_disable_pps_fiper(struct netc_timer *priv)
+static void netc_timer_disable_fiper(struct netc_timer *priv)
{
- u32 fiper_ctrl;
+ u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ int i;
- if (!priv->pps_enabled)
- return;
+ for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
+ if (!priv->pp[i].enabled)
+ continue;
+
+ fiper_ctrl |= FIPER_CTRL_DIS(i);
+ netc_timer_wr(priv, NETC_TMR_FIPER(i), NETC_TMR_DEFAULT_FIPER);
+ }
- fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
- fiper_ctrl |= FIPER_CTRL_DIS(0);
- netc_timer_wr(priv, NETC_TMR_FIPER(0), NETC_TMR_DEFAULT_FIPER);
netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
}
-static void netc_timer_enable_pps_fiper(struct netc_timer *priv)
+static void netc_timer_enable_fiper(struct netc_timer *priv)
{
- u32 fiper_ctrl, integral_period, fiper;
+ u32 integral_period = netc_timer_get_integral_period(priv);
+ u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ int i;
- if (!priv->pps_enabled)
- return;
+ for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
+ struct netc_pp *pp = &priv->pp[i];
+ u32 fiper;
- integral_period = netc_timer_get_integral_period(priv);
- fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
- fiper_ctrl &= ~FIPER_CTRL_DIS(0);
- fiper = NSEC_PER_SEC - integral_period;
+ if (!pp->enabled)
+ continue;
+
+ fiper_ctrl &= ~FIPER_CTRL_DIS(i);
+
+ if (pp->type == NETC_PP_PPS)
+ netc_timer_set_pps_alarm(priv, i, integral_period);
+ else if (pp->type == NETC_PP_PEROUT)
+ netc_timer_set_perout_alarm(priv, i, integral_period);
+
+ fiper = pp->period - integral_period;
+ netc_timer_wr(priv, NETC_TMR_FIPER(i), fiper);
+ }
- netc_timer_set_pps_alarm(priv, 0, integral_period);
- netc_timer_wr(priv, NETC_TMR_FIPER(0), fiper);
netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
}
switch (rq->type) {
case PTP_CLK_REQ_PPS:
return netc_timer_enable_pps(priv, rq, on);
+ case PTP_CLK_REQ_PEROUT:
+ return net_timer_enable_perout(priv, rq, on);
default:
return -EOPNOTSUPP;
}
tmr_ctrl = u32_replace_bits(old_tmr_ctrl, integral_period,
TMR_CTRL_TCLK_PERIOD);
if (tmr_ctrl != old_tmr_ctrl) {
- netc_timer_disable_pps_fiper(priv);
+ netc_timer_disable_fiper(priv);
netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
- netc_timer_enable_pps_fiper(priv);
+ netc_timer_enable_fiper(priv);
}
netc_timer_wr(priv, NETC_TMR_ADD, fractional_period);
spin_lock_irqsave(&priv->lock, flags);
- netc_timer_disable_pps_fiper(priv);
+ netc_timer_disable_fiper(priv);
/* Adjusting TMROFF instead of TMR_CNT is that the timer
* counter keeps increasing during reading and writing
tmr_off += delta;
netc_timer_offset_write(priv, tmr_off);
- netc_timer_enable_pps_fiper(priv);
+ netc_timer_enable_fiper(priv);
spin_unlock_irqrestore(&priv->lock, flags);
spin_lock_irqsave(&priv->lock, flags);
- netc_timer_disable_pps_fiper(priv);
+ netc_timer_disable_fiper(priv);
netc_timer_offset_write(priv, 0);
netc_timer_cnt_write(priv, ns);
- netc_timer_enable_pps_fiper(priv);
+ netc_timer_enable_fiper(priv);
spin_unlock_irqrestore(&priv->lock, flags);
.n_pins = 0,
.n_alarm = 2,
.pps = 1,
+ .n_per_out = 3,
.adjfine = netc_timer_adjfine,
.adjtime = netc_timer_adjtime,
.gettimex64 = netc_timer_gettimex64,
if (tmr_event & TMR_TEVENT_ALMEN(0))
netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 0);
+ if (tmr_event & TMR_TEVENT_ALMEN(1))
+ netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 1);
+
if (tmr_event & TMR_TEVENT_PPEN_ALL) {
event.type = PTP_CLOCK_PPS;
ptp_clock_event(priv->clock, &event);
pci_free_irq_vectors(pdev);
}
+static int netc_timer_get_global_ip_rev(struct netc_timer *priv)
+{
+ u32 val;
+
+ val = netc_timer_rd(priv, NETC_GLOBAL_OFFSET + NETC_GLOBAL_IPBRR0);
+
+ return val & IPBRR0_IP_REV;
+}
+
static int netc_timer_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
return err;
priv = pci_get_drvdata(pdev);
+ priv->revision = netc_timer_get_global_ip_rev(priv);
+ if (priv->revision == NETC_REV_4_1)
+ priv->fs_alarm_num = 1;
+ else
+ priv->fs_alarm_num = NETC_TMR_ALARM_NUM;
+
err = netc_timer_parse_dt(priv);
if (err)
goto timer_pci_remove;
priv->caps = netc_timer_ptp_caps;
priv->oclk_prsc = NETC_TMR_DEFAULT_PRSC;
+ priv->pps_channel = NETC_TMR_INVALID_CHANNEL;
spin_lock_init(&priv->lock);
snprintf(priv->irq_name, sizeof(priv->irq_name), "ptp-netc %s",
pci_name(pdev));