1044 lines
27 KiB
C
1044 lines
27 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
|
|
/*
|
|
* NXP NETC V4 Timer driver
|
|
* Copyright 2025 NXP
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/fsl/netc_global.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/ptp_clock_kernel.h>
|
|
|
|
#define NETC_TMR_PCI_VENDOR_NXP 0x1131
|
|
|
|
#define NETC_TMR_CTRL 0x0080
|
|
#define TMR_CTRL_CK_SEL GENMASK(1, 0)
|
|
#define TMR_CTRL_TE BIT(2)
|
|
#define TMR_ETEP(i) BIT(8 + (i))
|
|
#define TMR_COMP_MODE BIT(15)
|
|
#define TMR_CTRL_TCLK_PERIOD GENMASK(25, 16)
|
|
#define TMR_CTRL_PPL(i) BIT(27 - (i))
|
|
#define TMR_CTRL_FS BIT(28)
|
|
|
|
#define NETC_TMR_TEVENT 0x0084
|
|
#define TMR_TEVNET_PPEN(i) BIT(7 - (i))
|
|
#define TMR_TEVENT_PPEN_ALL GENMASK(7, 5)
|
|
#define TMR_TEVENT_ALMEN(i) BIT(16 + (i))
|
|
#define TMR_TEVENT_ETS_THREN(i) BIT(20 + (i))
|
|
#define TMR_TEVENT_ETSEN(i) BIT(24 + (i))
|
|
#define TMR_TEVENT_ETS_OVEN(i) BIT(28 + (i))
|
|
#define TMR_TEVENT_ETS(i) (TMR_TEVENT_ETS_THREN(i) | \
|
|
TMR_TEVENT_ETSEN(i) | \
|
|
TMR_TEVENT_ETS_OVEN(i))
|
|
|
|
#define NETC_TMR_TEMASK 0x0088
|
|
#define NETC_TMR_STAT 0x0094
|
|
#define TMR_STAT_ETS_VLD(i) BIT(24 + (i))
|
|
|
|
#define NETC_TMR_CNT_L 0x0098
|
|
#define NETC_TMR_CNT_H 0x009c
|
|
#define NETC_TMR_ADD 0x00a0
|
|
#define NETC_TMR_PRSC 0x00a8
|
|
#define NETC_TMR_ECTRL 0x00ac
|
|
#define NETC_TMR_OFF_L 0x00b0
|
|
#define NETC_TMR_OFF_H 0x00b4
|
|
|
|
/* i = 0, 1, i indicates the index of TMR_ALARM */
|
|
#define NETC_TMR_ALARM_L(i) (0x00b8 + (i) * 8)
|
|
#define NETC_TMR_ALARM_H(i) (0x00bc + (i) * 8)
|
|
|
|
/* i = 0, 1, 2. i indicates the index of TMR_FIPER. */
|
|
#define NETC_TMR_FIPER(i) (0x00d0 + (i) * 4)
|
|
|
|
#define NETC_TMR_FIPER_CTRL 0x00dc
|
|
#define FIPER_CTRL_DIS(i) (BIT(7) << (i) * 8)
|
|
#define FIPER_CTRL_PG(i) (BIT(6) << (i) * 8)
|
|
#define FIPER_CTRL_FS_ALARM(i) (BIT(5) << (i) * 8)
|
|
#define FIPER_CTRL_PW(i) (GENMASK(4, 0) << (i) * 8)
|
|
#define FIPER_CTRL_SET_PW(i, v) (((v) & GENMASK(4, 0)) << 8 * (i))
|
|
|
|
/* i = 0, 1, i indicates the index of TMR_ETTS */
|
|
#define NETC_TMR_ETTS_L(i) (0x00e0 + (i) * 8)
|
|
#define NETC_TMR_ETTS_H(i) (0x00e4 + (i) * 8)
|
|
#define NETC_TMR_CUR_TIME_L 0x00f0
|
|
#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
|
|
#define NETC_TMR_DEFAULT_ETTF_THR 7
|
|
|
|
/* 1588 timer reference clock source select */
|
|
#define NETC_TMR_CCM_TIMER1 0 /* enet_timer1_clk_root, from CCM */
|
|
#define NETC_TMR_SYSTEM_CLK 1 /* enet_clk_root/2, from CCM */
|
|
#define NETC_TMR_EXT_OSC 2 /* tmr_1588_clk, from IO pins */
|
|
|
|
#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;
|
|
spinlock_t lock; /* Prevent concurrent access to registers */
|
|
|
|
struct ptp_clock *clock;
|
|
struct ptp_clock_info caps;
|
|
u32 clk_select;
|
|
u32 clk_freq;
|
|
u32 oclk_prsc;
|
|
/* High 32-bit is integer part, low 32-bit is fractional part */
|
|
u64 period;
|
|
|
|
int irq;
|
|
char irq_name[24];
|
|
int revision;
|
|
u32 tmr_emask;
|
|
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))
|
|
#define netc_timer_wr(p, o, v) netc_write((p)->base + (o), v)
|
|
#define ptp_to_netc_timer(ptp) container_of((ptp), struct netc_timer, caps)
|
|
|
|
static const char *const timer_clk_src[] = {
|
|
"ccm",
|
|
"ext"
|
|
};
|
|
|
|
static void netc_timer_cnt_write(struct netc_timer *priv, u64 ns)
|
|
{
|
|
u32 tmr_cnt_h = upper_32_bits(ns);
|
|
u32 tmr_cnt_l = lower_32_bits(ns);
|
|
|
|
/* Writes to the TMR_CNT_L register copies the written value
|
|
* into the shadow TMR_CNT_L register. Writes to the TMR_CNT_H
|
|
* register copies the values written into the shadow TMR_CNT_H
|
|
* register. Contents of the shadow registers are copied into
|
|
* the TMR_CNT_L and TMR_CNT_H registers following a write into
|
|
* the TMR_CNT_H register. So the user must writes to TMR_CNT_L
|
|
* register first. Other H/L registers should have the same
|
|
* behavior.
|
|
*/
|
|
netc_timer_wr(priv, NETC_TMR_CNT_L, tmr_cnt_l);
|
|
netc_timer_wr(priv, NETC_TMR_CNT_H, tmr_cnt_h);
|
|
}
|
|
|
|
static u64 netc_timer_offset_read(struct netc_timer *priv)
|
|
{
|
|
u32 tmr_off_l, tmr_off_h;
|
|
u64 offset;
|
|
|
|
tmr_off_l = netc_timer_rd(priv, NETC_TMR_OFF_L);
|
|
tmr_off_h = netc_timer_rd(priv, NETC_TMR_OFF_H);
|
|
offset = (((u64)tmr_off_h) << 32) | tmr_off_l;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static void netc_timer_offset_write(struct netc_timer *priv, u64 offset)
|
|
{
|
|
u32 tmr_off_h = upper_32_bits(offset);
|
|
u32 tmr_off_l = lower_32_bits(offset);
|
|
|
|
netc_timer_wr(priv, NETC_TMR_OFF_L, tmr_off_l);
|
|
netc_timer_wr(priv, NETC_TMR_OFF_H, tmr_off_h);
|
|
}
|
|
|
|
static u64 netc_timer_cur_time_read(struct netc_timer *priv)
|
|
{
|
|
u32 time_h, time_l;
|
|
u64 ns;
|
|
|
|
/* The user should read NETC_TMR_CUR_TIME_L first to
|
|
* get correct current time.
|
|
*/
|
|
time_l = netc_timer_rd(priv, NETC_TMR_CUR_TIME_L);
|
|
time_h = netc_timer_rd(priv, NETC_TMR_CUR_TIME_H);
|
|
ns = (u64)time_h << 32 | time_l;
|
|
|
|
return ns;
|
|
}
|
|
|
|
static void netc_timer_alarm_write(struct netc_timer *priv,
|
|
u64 alarm, int index)
|
|
{
|
|
u32 alarm_h = upper_32_bits(alarm);
|
|
u32 alarm_l = lower_32_bits(alarm);
|
|
|
|
netc_timer_wr(priv, NETC_TMR_ALARM_L(index), alarm_l);
|
|
netc_timer_wr(priv, NETC_TMR_ALARM_H(index), alarm_h);
|
|
}
|
|
|
|
static u32 netc_timer_get_integral_period(struct netc_timer *priv)
|
|
{
|
|
u32 tmr_ctrl, integral_period;
|
|
|
|
tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
|
|
integral_period = FIELD_GET(TMR_CTRL_TCLK_PERIOD, tmr_ctrl);
|
|
|
|
return integral_period;
|
|
}
|
|
|
|
static u32 netc_timer_calculate_fiper_pw(struct netc_timer *priv,
|
|
u32 fiper)
|
|
{
|
|
u64 divisor, pulse_width;
|
|
|
|
/* Set the FIPER pulse width to half FIPER interval by default.
|
|
* pulse_width = (fiper / 2) / TMR_GCLK_period,
|
|
* TMR_GCLK_period = NSEC_PER_SEC / TMR_GCLK_freq,
|
|
* TMR_GCLK_freq = (clk_freq / oclk_prsc) Hz,
|
|
* so pulse_width = fiper * clk_freq / (2 * NSEC_PER_SEC * oclk_prsc).
|
|
*/
|
|
divisor = mul_u32_u32(2 * NSEC_PER_SEC, priv->oclk_prsc);
|
|
pulse_width = div64_u64(mul_u32_u32(fiper, priv->clk_freq), divisor);
|
|
|
|
/* The FIPER_PW field only has 5 bits, need to update oclk_prsc */
|
|
if (pulse_width > NETC_TMR_FIPER_MAX_PW)
|
|
pulse_width = NETC_TMR_FIPER_MAX_PW;
|
|
|
|
return pulse_width;
|
|
}
|
|
|
|
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 = netc_timer_cur_time_read(priv) + NSEC_PER_MSEC;
|
|
alarm = roundup_u64(alarm, NSEC_PER_SEC);
|
|
alarm = roundup_u64(alarm, integral_period);
|
|
|
|
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_TEVENT_ALMEN(alarm_id);
|
|
|
|
if (pp->type == NETC_PP_PPS) {
|
|
priv->tmr_emask |= TMR_TEVNET_PPEN(channel);
|
|
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
|
|
* external pins, because PTP_CLK_REQ_PPS trigger internal PPS event
|
|
* for input into kernel PPS subsystem. See:
|
|
* https://lore.kernel.org/r/20201117213826.18235-1-a.fatoum@pengutronix.de
|
|
*/
|
|
static int netc_timer_enable_pps(struct netc_timer *priv,
|
|
struct ptp_clock_request *rq, int on)
|
|
{
|
|
struct device *dev = &priv->pdev->dev;
|
|
unsigned long flags;
|
|
struct netc_pp *pp;
|
|
int err = 0;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
if (on) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
pp = &priv->pp[channel];
|
|
if (pp->enabled)
|
|
goto unlock_spinlock;
|
|
|
|
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 {
|
|
/* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
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 err;
|
|
}
|
|
|
|
static void netc_timer_handle_etts_event(struct netc_timer *priv, int index,
|
|
bool update_event)
|
|
{
|
|
struct ptp_clock_event event;
|
|
u32 etts_l = 0, etts_h = 0;
|
|
|
|
while (netc_timer_rd(priv, NETC_TMR_STAT) & TMR_STAT_ETS_VLD(index)) {
|
|
etts_l = netc_timer_rd(priv, NETC_TMR_ETTS_L(index));
|
|
etts_h = netc_timer_rd(priv, NETC_TMR_ETTS_H(index));
|
|
}
|
|
|
|
/* Invalid time stamp */
|
|
if (!etts_l && !etts_h)
|
|
return;
|
|
|
|
if (update_event) {
|
|
event.type = PTP_CLOCK_EXTTS;
|
|
event.index = index;
|
|
event.timestamp = (u64)etts_h << 32;
|
|
event.timestamp |= etts_l;
|
|
ptp_clock_event(priv->clock, &event);
|
|
}
|
|
}
|
|
|
|
static int netc_timer_enable_extts(struct netc_timer *priv,
|
|
struct ptp_clock_request *rq, int on)
|
|
{
|
|
int index = rq->extts.index;
|
|
unsigned long flags;
|
|
u32 tmr_ctrl;
|
|
|
|
/* Reject requests to enable time stamping on both edges */
|
|
if ((rq->extts.flags & PTP_EXTTS_EDGES) == PTP_EXTTS_EDGES)
|
|
return -EOPNOTSUPP;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
netc_timer_handle_etts_event(priv, rq->extts.index, false);
|
|
if (on) {
|
|
tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
|
|
if (rq->extts.flags & PTP_FALLING_EDGE)
|
|
tmr_ctrl |= TMR_ETEP(index);
|
|
else
|
|
tmr_ctrl &= ~TMR_ETEP(index);
|
|
|
|
netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
|
|
priv->tmr_emask |= TMR_TEVENT_ETS(index);
|
|
} else {
|
|
priv->tmr_emask &= ~TMR_TEVENT_ETS(index);
|
|
}
|
|
|
|
netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void netc_timer_disable_fiper(struct netc_timer *priv)
|
|
{
|
|
u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
|
|
int i;
|
|
|
|
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);
|
|
}
|
|
|
|
netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
|
|
}
|
|
|
|
static void netc_timer_enable_fiper(struct netc_timer *priv)
|
|
{
|
|
u32 integral_period = netc_timer_get_integral_period(priv);
|
|
u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
|
|
int i;
|
|
|
|
for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
|
|
struct netc_pp *pp = &priv->pp[i];
|
|
u32 fiper;
|
|
|
|
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_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
|
|
}
|
|
|
|
static int netc_timer_enable(struct ptp_clock_info *ptp,
|
|
struct ptp_clock_request *rq, int on)
|
|
{
|
|
struct netc_timer *priv = ptp_to_netc_timer(ptp);
|
|
|
|
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);
|
|
case PTP_CLK_REQ_EXTTS:
|
|
return netc_timer_enable_extts(priv, rq, on);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static int netc_timer_perout_loopback(struct ptp_clock_info *ptp,
|
|
unsigned int index, int on)
|
|
{
|
|
struct netc_timer *priv = ptp_to_netc_timer(ptp);
|
|
unsigned long flags;
|
|
u32 tmr_ctrl;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
|
|
if (on)
|
|
tmr_ctrl |= TMR_CTRL_PPL(index);
|
|
else
|
|
tmr_ctrl &= ~TMR_CTRL_PPL(index);
|
|
|
|
netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void netc_timer_adjust_period(struct netc_timer *priv, u64 period)
|
|
{
|
|
u32 fractional_period = lower_32_bits(period);
|
|
u32 integral_period = upper_32_bits(period);
|
|
u32 tmr_ctrl, old_tmr_ctrl;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
old_tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
|
|
tmr_ctrl = u32_replace_bits(old_tmr_ctrl, integral_period,
|
|
TMR_CTRL_TCLK_PERIOD);
|
|
if (tmr_ctrl != old_tmr_ctrl) {
|
|
netc_timer_disable_fiper(priv);
|
|
netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
|
|
netc_timer_enable_fiper(priv);
|
|
}
|
|
|
|
netc_timer_wr(priv, NETC_TMR_ADD, fractional_period);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
}
|
|
|
|
static int netc_timer_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
|
|
{
|
|
struct netc_timer *priv = ptp_to_netc_timer(ptp);
|
|
u64 new_period;
|
|
|
|
new_period = adjust_by_scaled_ppm(priv->period, scaled_ppm);
|
|
netc_timer_adjust_period(priv, new_period);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netc_timer_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
{
|
|
struct netc_timer *priv = ptp_to_netc_timer(ptp);
|
|
unsigned long flags;
|
|
s64 tmr_off;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
netc_timer_disable_fiper(priv);
|
|
|
|
/* Adjusting TMROFF instead of TMR_CNT is that the timer
|
|
* counter keeps increasing during reading and writing
|
|
* TMR_CNT, which will cause latency.
|
|
*/
|
|
tmr_off = netc_timer_offset_read(priv);
|
|
tmr_off += delta;
|
|
netc_timer_offset_write(priv, tmr_off);
|
|
|
|
netc_timer_enable_fiper(priv);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netc_timer_gettimex64(struct ptp_clock_info *ptp,
|
|
struct timespec64 *ts,
|
|
struct ptp_system_timestamp *sts)
|
|
{
|
|
struct netc_timer *priv = ptp_to_netc_timer(ptp);
|
|
unsigned long flags;
|
|
u64 ns;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
ptp_read_system_prets(sts);
|
|
ns = netc_timer_cur_time_read(priv);
|
|
ptp_read_system_postts(sts);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
*ts = ns_to_timespec64(ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netc_timer_settime64(struct ptp_clock_info *ptp,
|
|
const struct timespec64 *ts)
|
|
{
|
|
struct netc_timer *priv = ptp_to_netc_timer(ptp);
|
|
u64 ns = timespec64_to_ns(ts);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&priv->lock, flags);
|
|
|
|
netc_timer_disable_fiper(priv);
|
|
netc_timer_offset_write(priv, 0);
|
|
netc_timer_cnt_write(priv, ns);
|
|
netc_timer_enable_fiper(priv);
|
|
|
|
spin_unlock_irqrestore(&priv->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct ptp_clock_info netc_timer_ptp_caps = {
|
|
.owner = THIS_MODULE,
|
|
.name = "NETC Timer PTP clock",
|
|
.max_adj = 500000000,
|
|
.n_pins = 0,
|
|
.n_alarm = 2,
|
|
.pps = 1,
|
|
.n_per_out = 3,
|
|
.n_ext_ts = 2,
|
|
.n_per_lp = 2,
|
|
.supported_extts_flags = PTP_RISING_EDGE | PTP_FALLING_EDGE |
|
|
PTP_STRICT_FLAGS,
|
|
.adjfine = netc_timer_adjfine,
|
|
.adjtime = netc_timer_adjtime,
|
|
.gettimex64 = netc_timer_gettimex64,
|
|
.settime64 = netc_timer_settime64,
|
|
.enable = netc_timer_enable,
|
|
.perout_loopback = netc_timer_perout_loopback,
|
|
};
|
|
|
|
static void netc_timer_init(struct netc_timer *priv)
|
|
{
|
|
u32 fractional_period = lower_32_bits(priv->period);
|
|
u32 integral_period = upper_32_bits(priv->period);
|
|
u32 tmr_ctrl, fiper_ctrl;
|
|
struct timespec64 now;
|
|
u64 ns;
|
|
int i;
|
|
|
|
/* Software must enable timer first and the clock selected must be
|
|
* active, otherwise, the registers which are in the timer clock
|
|
* domain are not accessible.
|
|
*/
|
|
tmr_ctrl = FIELD_PREP(TMR_CTRL_CK_SEL, priv->clk_select) |
|
|
TMR_CTRL_TE | TMR_CTRL_FS;
|
|
netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
|
|
netc_timer_wr(priv, NETC_TMR_PRSC, priv->oclk_prsc);
|
|
|
|
/* Disable FIPER by default */
|
|
fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
|
|
for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
|
|
fiper_ctrl |= FIPER_CTRL_DIS(i);
|
|
fiper_ctrl &= ~FIPER_CTRL_PG(i);
|
|
}
|
|
netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
|
|
netc_timer_wr(priv, NETC_TMR_ECTRL, NETC_TMR_DEFAULT_ETTF_THR);
|
|
|
|
ktime_get_real_ts64(&now);
|
|
ns = timespec64_to_ns(&now);
|
|
netc_timer_cnt_write(priv, ns);
|
|
|
|
/* Allow atomic writes to TCLK_PERIOD and TMR_ADD, An update to
|
|
* TCLK_PERIOD does not take effect until TMR_ADD is written.
|
|
*/
|
|
tmr_ctrl |= FIELD_PREP(TMR_CTRL_TCLK_PERIOD, integral_period) |
|
|
TMR_COMP_MODE;
|
|
netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
|
|
netc_timer_wr(priv, NETC_TMR_ADD, fractional_period);
|
|
}
|
|
|
|
static int netc_timer_pci_probe(struct pci_dev *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct netc_timer *priv;
|
|
int err;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
pcie_flr(pdev);
|
|
err = pci_enable_device_mem(pdev);
|
|
if (err)
|
|
return dev_err_probe(dev, err, "Failed to enable device\n");
|
|
|
|
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
|
|
err = pci_request_mem_regions(pdev, KBUILD_MODNAME);
|
|
if (err) {
|
|
dev_err(dev, "pci_request_regions() failed, err:%pe\n",
|
|
ERR_PTR(err));
|
|
goto disable_dev;
|
|
}
|
|
|
|
pci_set_master(pdev);
|
|
|
|
priv->pdev = pdev;
|
|
priv->base = pci_ioremap_bar(pdev, NETC_TMR_REGS_BAR);
|
|
if (!priv->base) {
|
|
err = -ENOMEM;
|
|
goto release_mem_regions;
|
|
}
|
|
|
|
pci_set_drvdata(pdev, priv);
|
|
|
|
return 0;
|
|
|
|
release_mem_regions:
|
|
pci_release_mem_regions(pdev);
|
|
disable_dev:
|
|
pci_disable_device(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void netc_timer_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct netc_timer *priv = pci_get_drvdata(pdev);
|
|
|
|
iounmap(priv->base);
|
|
pci_release_mem_regions(pdev);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
static int netc_timer_get_reference_clk_source(struct netc_timer *priv)
|
|
{
|
|
struct device *dev = &priv->pdev->dev;
|
|
struct clk *clk;
|
|
int i;
|
|
|
|
/* Select NETC system clock as the reference clock by default */
|
|
priv->clk_select = NETC_TMR_SYSTEM_CLK;
|
|
priv->clk_freq = NETC_TMR_SYSCLK_333M;
|
|
|
|
/* Update the clock source of the reference clock if the clock
|
|
* is specified in DT node.
|
|
*/
|
|
for (i = 0; i < ARRAY_SIZE(timer_clk_src); i++) {
|
|
clk = devm_clk_get_optional_enabled(dev, timer_clk_src[i]);
|
|
if (IS_ERR(clk))
|
|
return dev_err_probe(dev, PTR_ERR(clk),
|
|
"Failed to enable clock\n");
|
|
|
|
if (clk) {
|
|
priv->clk_freq = clk_get_rate(clk);
|
|
priv->clk_select = i ? NETC_TMR_EXT_OSC :
|
|
NETC_TMR_CCM_TIMER1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* The period is a 64-bit number, the high 32-bit is the integer
|
|
* part of the period, the low 32-bit is the fractional part of
|
|
* the period. In order to get the desired 32-bit fixed-point
|
|
* format, multiply the numerator of the fraction by 2^32.
|
|
*/
|
|
priv->period = div_u64((u64)NSEC_PER_SEC << 32, priv->clk_freq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netc_timer_parse_dt(struct netc_timer *priv)
|
|
{
|
|
return netc_timer_get_reference_clk_source(priv);
|
|
}
|
|
|
|
static irqreturn_t netc_timer_isr(int irq, void *data)
|
|
{
|
|
struct netc_timer *priv = data;
|
|
struct ptp_clock_event event;
|
|
u32 tmr_event;
|
|
|
|
spin_lock(&priv->lock);
|
|
|
|
tmr_event = netc_timer_rd(priv, NETC_TMR_TEVENT);
|
|
tmr_event &= priv->tmr_emask;
|
|
/* Clear interrupts status */
|
|
netc_timer_wr(priv, NETC_TMR_TEVENT, tmr_event);
|
|
|
|
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);
|
|
}
|
|
|
|
if (tmr_event & TMR_TEVENT_ETS(0))
|
|
netc_timer_handle_etts_event(priv, 0, true);
|
|
|
|
if (tmr_event & TMR_TEVENT_ETS(1))
|
|
netc_timer_handle_etts_event(priv, 1, true);
|
|
|
|
spin_unlock(&priv->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int netc_timer_init_msix_irq(struct netc_timer *priv)
|
|
{
|
|
struct pci_dev *pdev = priv->pdev;
|
|
int err, n;
|
|
|
|
n = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX);
|
|
if (n != 1) {
|
|
err = (n < 0) ? n : -EPERM;
|
|
dev_err(&pdev->dev, "pci_alloc_irq_vectors() failed\n");
|
|
return err;
|
|
}
|
|
|
|
priv->irq = pci_irq_vector(pdev, 0);
|
|
err = request_irq(priv->irq, netc_timer_isr, 0, priv->irq_name, priv);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "request_irq() failed\n");
|
|
pci_free_irq_vectors(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void netc_timer_free_msix_irq(struct netc_timer *priv)
|
|
{
|
|
struct pci_dev *pdev = priv->pdev;
|
|
|
|
disable_irq(priv->irq);
|
|
free_irq(priv->irq, priv);
|
|
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)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct netc_timer *priv;
|
|
int err;
|
|
|
|
err = netc_timer_pci_probe(pdev);
|
|
if (err)
|
|
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));
|
|
|
|
err = netc_timer_init_msix_irq(priv);
|
|
if (err)
|
|
goto timer_pci_remove;
|
|
|
|
netc_timer_init(priv);
|
|
priv->clock = ptp_clock_register(&priv->caps, dev);
|
|
if (IS_ERR(priv->clock)) {
|
|
err = PTR_ERR(priv->clock);
|
|
goto free_msix_irq;
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_msix_irq:
|
|
netc_timer_free_msix_irq(priv);
|
|
timer_pci_remove:
|
|
netc_timer_pci_remove(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void netc_timer_remove(struct pci_dev *pdev)
|
|
{
|
|
struct netc_timer *priv = pci_get_drvdata(pdev);
|
|
|
|
netc_timer_wr(priv, NETC_TMR_TEMASK, 0);
|
|
netc_timer_wr(priv, NETC_TMR_CTRL, 0);
|
|
ptp_clock_unregister(priv->clock);
|
|
netc_timer_free_msix_irq(priv);
|
|
netc_timer_pci_remove(pdev);
|
|
}
|
|
|
|
static const struct pci_device_id netc_timer_id_table[] = {
|
|
{ PCI_DEVICE(NETC_TMR_PCI_VENDOR_NXP, 0xee02) },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, netc_timer_id_table);
|
|
|
|
static struct pci_driver netc_timer_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = netc_timer_id_table,
|
|
.probe = netc_timer_probe,
|
|
.remove = netc_timer_remove,
|
|
};
|
|
module_pci_driver(netc_timer_driver);
|
|
|
|
MODULE_DESCRIPTION("NXP NETC Timer PTP Driver");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|