805 lines
23 KiB
C
805 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
||
/* Copyright(c) 2022 - 2024 Mucse Corporation. */
|
||
|
||
#include <linux/netdevice.h>
|
||
#include <linux/ptp_classify.h>
|
||
#include <linux/io.h>
|
||
//#include <linux/iopoll.h>
|
||
#include <linux/delay.h>
|
||
#include <linux/clk.h>
|
||
|
||
#include "rnpm.h"
|
||
#include "rnpm_regs.h"
|
||
#include "rnpm_ptp.h"
|
||
|
||
/* PTP and HW Timer ops */
|
||
static void config_hw_tstamping(void __iomem *ioaddr, u8 port, u32 data)
|
||
{
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_TS_CTRL(port), data);
|
||
}
|
||
|
||
static void config_sub_second_increment(void __iomem *ioaddr, u8 port,
|
||
u32 ptp_clock, u32 *ssinc)
|
||
{
|
||
u32 value = rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port));
|
||
unsigned long data;
|
||
u32 reg_value;
|
||
|
||
/* in "fine adjustement mode" set sub-second
|
||
* increment to twice the number of nanoseconds of a clock cycle.
|
||
* The calculation of the default_addend value by the caller will set it
|
||
* to mid-range = 2^31 when the remainder of this division is zero,
|
||
* which will make the accumulator overflow once every 2 ptp_clock
|
||
* cycles, adding twice the number of nanoseconds of a clock cycle :
|
||
* 2000000000ULL / ptp_clock.
|
||
*/
|
||
if (ptp_clock == 0) {
|
||
ptp_dbg("%s:%dthis is a bug that the syskernel clock is zero\n",
|
||
__func__, __LINE__);
|
||
return;
|
||
}
|
||
if (value & RNPM_PTP_TCR_TSCFUPDT)
|
||
data = (2000000000ULL / ptp_clock);
|
||
else
|
||
data = (1000000000ULL / ptp_clock);
|
||
|
||
/* 0.465ns accuracy */
|
||
if (!(value & RNPM_PTP_TCR_TSCTRLSSR))
|
||
data = (data * 1000) / 465;
|
||
|
||
data &= RNPM_PTP_SSIR_SSINC_MASK;
|
||
|
||
reg_value = data;
|
||
|
||
reg_value <<= RNPM_PTP_SSIR_SSINC_SHIFT;
|
||
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_SUB_SECOND_INCREMENT(port), reg_value);
|
||
|
||
if (ssinc)
|
||
*ssinc = data;
|
||
}
|
||
|
||
static int config_addend(void __iomem *ioaddr, u8 port, u32 addend)
|
||
{
|
||
u32 value;
|
||
int limit;
|
||
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_TS_ADDEND(port), addend);
|
||
/* issue command to update the addend value */
|
||
value = rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port));
|
||
value |= RNPM_PTP_TCR_TSADDREG;
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_TS_CTRL(port), value);
|
||
|
||
/* wait for present addend update to complete */
|
||
limit = 10;
|
||
while (limit--) {
|
||
if (!(rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port)) &
|
||
RNPM_PTP_TCR_TSADDREG))
|
||
break;
|
||
mdelay(10);
|
||
}
|
||
if (limit < 0)
|
||
return -EBUSY;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int init_systime(void __iomem *ioaddr, u8 port, u32 sec, u32 nsec)
|
||
{
|
||
u32 value;
|
||
u32 target;
|
||
int timeout = 0;
|
||
|
||
//printk("init systime: %x\n", sec);
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_SYS_TIME_SEC_UPDATE(port), sec);
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_SYS_TIME_NANOSEC_UPDATE(port), nsec);
|
||
/* issue command to initialize the system time value */
|
||
value = rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port));
|
||
value |= RNPM_PTP_TCR_TSINIT;
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_TS_CTRL(port), value);
|
||
|
||
while (timeout <= 20) {
|
||
target = rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port));
|
||
if (!(value & RNPM_PTP_TCR_TSINIT))
|
||
break;
|
||
usleep_range(5000, 10000);
|
||
timeout++;
|
||
}
|
||
|
||
if (timeout < 20)
|
||
return 0;
|
||
else
|
||
return -1;
|
||
|
||
/* wait for present system time initialize to complete */
|
||
//return readl_poll_timeout(ioaddr + RNPM_MAC_TS_CTRL(port), value,
|
||
// !(value & RNPM_PTP_TCR_TSINIT),
|
||
// 10000, 100000);
|
||
}
|
||
|
||
static void get_systime(void __iomem *ioaddr, u8 port, u64 *systime)
|
||
{
|
||
u64 ns;
|
||
|
||
/* Get the TSSS value */
|
||
ns = rnpm_rd_reg(ioaddr + RNPM_MAC_SYS_TIME_NANOSEC_CFG(port));
|
||
/* Get the TSS and convert sec time value to nanosecond */
|
||
ns += rnpm_rd_reg(ioaddr + RNPM_MAC_SYS_TIME_SEC_CFG(port)) *
|
||
1000000000ULL;
|
||
|
||
if (systime)
|
||
*systime = ns;
|
||
}
|
||
|
||
static void config_mac_interrupt_enable(void __iomem *ioaddr, u8 port, bool on)
|
||
{
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_INTERRUPT_ENABLE(port), on);
|
||
}
|
||
|
||
struct rnpm_hwtimestamp {
|
||
void (*config_hw_tstamping)(void __iomem *ioaddr, u8 port, u32 data);
|
||
void (*config_sub_second_increment)(void __iomem *ioaddr, u8 port,
|
||
u32 ptp_clock, u32 *ssinc);
|
||
void (*config_mac_irq_enable)(void __iomem *ioaddr, u8 port, bool on);
|
||
int (*init_systime)(void __iomem *ioaddr, u8 port, u32 sec, u32 nsec);
|
||
int (*config_addend)(void __iomem *ioaddr, u8 port, u32 addend);
|
||
int (*adjust_systime)(void __iomem *ioaddr, u8 port, u32 sec, u32 nsec,
|
||
int add_sub);
|
||
void (*get_systime)(void __iomem *ioaddr, u8 port, u64 *systime);
|
||
};
|
||
|
||
static int adjust_systime(void __iomem *ioaddr, u8 port, u32 sec, u32 nsec,
|
||
int add_sub)
|
||
{
|
||
u32 value;
|
||
int limit;
|
||
|
||
if (add_sub) {
|
||
/* If the new sec value needs to be subtracted with
|
||
* the system time, then RNPM_MAC_STSUR reg should be
|
||
* programmed with (2^32 – <new_sec_value>)
|
||
*/
|
||
sec = -sec;
|
||
|
||
value = rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port));
|
||
if (value & RNPM_PTP_TCR_TSCTRLSSR)
|
||
nsec = (PTP_DIGITAL_ROLLOVER_MODE - nsec);
|
||
else
|
||
nsec = (PTP_BINARY_ROLLOVER_MODE - nsec);
|
||
}
|
||
|
||
//printk("adjust %x\n", sec);
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_SYS_TIME_SEC_UPDATE(port), sec);
|
||
value = (add_sub << PTP_STNSUR_ADDSUB_SHIFT) | nsec;
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_SYS_TIME_NANOSEC_UPDATE(port), value);
|
||
|
||
/* issue command to initialize the system time value */
|
||
value = rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port));
|
||
value |= RNPM_PTP_TCR_TSUPDT;
|
||
rnpm_wr_reg(ioaddr + RNPM_MAC_TS_CTRL(port), value);
|
||
|
||
/* wait for present system time adjust/update to complete */
|
||
limit = 10;
|
||
while (limit--) {
|
||
if (!(rnpm_rd_reg(ioaddr + RNPM_MAC_TS_CTRL(port)) &
|
||
RNPM_PTP_TCR_TSUPDT))
|
||
break;
|
||
mdelay(10);
|
||
}
|
||
if (limit < 0)
|
||
return -EBUSY;
|
||
return 0;
|
||
}
|
||
|
||
const struct rnpm_hwtimestamp mac_ptp = {
|
||
.config_hw_tstamping = config_hw_tstamping,
|
||
.config_mac_irq_enable = config_mac_interrupt_enable,
|
||
.init_systime = init_systime,
|
||
.config_sub_second_increment = config_sub_second_increment,
|
||
.config_addend = config_addend,
|
||
.adjust_systime = adjust_systime,
|
||
.get_systime = get_systime,
|
||
};
|
||
|
||
static int rnpm_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
|
||
{
|
||
struct rnpm_adapter *pf =
|
||
container_of(ptp, struct rnpm_adapter, ptp_clock_ops);
|
||
unsigned long flags;
|
||
u32 diff, addend;
|
||
int neg_adj = 0;
|
||
u64 adj;
|
||
u8 port = pf->port;
|
||
|
||
if (pf == NULL) {
|
||
ptp_dbg("adapter_of contail is null\n");
|
||
return 0;
|
||
}
|
||
|
||
if (scaled_ppm < 0) {
|
||
neg_adj = 1;
|
||
scaled_ppm = -scaled_ppm;
|
||
}
|
||
addend = pf->default_addend;
|
||
adj = addend;
|
||
|
||
adj *= (u64)scaled_ppm;
|
||
diff = div64_u64(adj, 1000000ULL << 16);
|
||
addend = neg_adj ? (addend - diff) : (addend + diff);
|
||
spin_lock_irqsave(&pf->ptp_lock, flags);
|
||
|
||
pf->hwts_ops->config_addend(pf->hw.hw_addr, port, addend);
|
||
spin_unlock_irqrestore(&pf->ptp_lock, flags);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int rnpm_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
||
{
|
||
struct rnpm_adapter *pf =
|
||
container_of(ptp, struct rnpm_adapter, ptp_clock_ops);
|
||
unsigned long flags;
|
||
u32 sec, nsec;
|
||
u32 quotient, reminder;
|
||
int neg_adj = 0;
|
||
u8 port = pf->port;
|
||
|
||
if (delta < 0) {
|
||
neg_adj = 1;
|
||
delta = -delta;
|
||
}
|
||
|
||
if (delta == 0)
|
||
return 0;
|
||
|
||
quotient = div_u64_rem(delta, 1000000000ULL, &reminder);
|
||
sec = quotient;
|
||
nsec = reminder;
|
||
|
||
spin_lock_irqsave(&pf->ptp_lock, flags);
|
||
pf->hwts_ops->adjust_systime(pf->hw.hw_addr, port, sec, nsec, neg_adj);
|
||
spin_unlock_irqrestore(&pf->ptp_lock, flags);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int rnpm_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
|
||
{
|
||
struct rnpm_adapter *pf =
|
||
container_of(ptp, struct rnpm_adapter, ptp_clock_ops);
|
||
unsigned long flags;
|
||
u64 ns = 0;
|
||
u8 port = pf->port;
|
||
|
||
spin_lock_irqsave(&pf->ptp_lock, flags);
|
||
|
||
pf->hwts_ops->get_systime(pf->hw.hw_addr, port, &ns);
|
||
|
||
spin_unlock_irqrestore(&pf->ptp_lock, flags);
|
||
|
||
*ts = ns_to_timespec64(ns);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int rnpm_ptp_settime(struct ptp_clock_info *ptp,
|
||
const struct timespec64 *ts)
|
||
{
|
||
struct rnpm_adapter *pf =
|
||
container_of(ptp, struct rnpm_adapter, ptp_clock_ops);
|
||
unsigned long flags;
|
||
u8 port = pf->port;
|
||
|
||
spin_lock_irqsave(&pf->ptp_lock, flags);
|
||
pf->hwts_ops->init_systime(pf->hw.hw_addr, port, ts->tv_sec,
|
||
ts->tv_nsec);
|
||
spin_unlock_irqrestore(&pf->ptp_lock, flags);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int rnpm_ptp_feature_enable(struct ptp_clock_info *ptp,
|
||
struct ptp_clock_request *rq, int on)
|
||
{
|
||
/*TODO add support for enable the option 1588 feature PPS Auxiliary */
|
||
return -EOPNOTSUPP;
|
||
}
|
||
|
||
int rnpm_ptp_get_ts_config(struct rnpm_adapter *pf, struct ifreq *ifr)
|
||
{
|
||
struct hwtstamp_config *config = &pf->tstamp_config;
|
||
|
||
return copy_to_user(ifr->ifr_data, config, sizeof(*config)) ? -EFAULT :
|
||
0;
|
||
}
|
||
|
||
int rnpm_ptp_setup_ptp(struct rnpm_adapter *pf, u32 value)
|
||
{
|
||
u32 sec_inc = 0;
|
||
u64 temp = 0;
|
||
struct timespec64 now;
|
||
u8 port = pf->port;
|
||
|
||
/*For now just use extrnal clock(the kernel-system clock)*/
|
||
//value |= RNPM_PTP_TCR_ESTI;
|
||
/* 1.Mask the Timestamp Trigger interrupt */
|
||
pf->hwts_ops->config_mac_irq_enable(pf->hw.hw_addr, port, false);
|
||
/* 2.enable time stamping */
|
||
/* 2.1 clear all bytes about time ctrl reg*/
|
||
pf->hwts_ops->config_hw_tstamping(pf->hw.hw_addr, port, 0);
|
||
|
||
pf->hwts_ops->config_hw_tstamping(pf->hw.hw_addr, port, value);
|
||
/* 3.Program the PTPclock frequency */
|
||
/* program Sub Second Increment reg
|
||
* we use kernel-system clock
|
||
*/
|
||
pf->hwts_ops->config_sub_second_increment(pf->hw.hw_addr, port,
|
||
pf->clk_ptp_rate, &sec_inc);
|
||
/* 4.If use fine correction approash then,
|
||
* Program MAC_Timestamp_Addend register
|
||
*/
|
||
if (sec_inc == 0) {
|
||
ptp_dbg("%s:%d the sec_inc is zero this is a bug\n", __func__,
|
||
__LINE__);
|
||
return -EFAULT;
|
||
}
|
||
temp = div_u64(1000000000ULL, sec_inc);
|
||
/* Store sub second increment and flags for later use */
|
||
pf->sub_second_inc = sec_inc;
|
||
pf->systime_flags = value;
|
||
/* calculate default added value:
|
||
* formula is :
|
||
* addend = (2^32)/freq_div_ratio;
|
||
* where, freq_div_ratio = 1e9ns/sec_inc
|
||
*/
|
||
temp = (u64)(temp << 32);
|
||
|
||
if (pf->clk_ptp_rate == 0) {
|
||
pf->clk_ptp_rate = 1000;
|
||
ptp_dbg("%s:%d clk_ptp_rate is zero\n", __func__, __LINE__);
|
||
}
|
||
|
||
pf->default_addend = div_u64(temp, pf->clk_ptp_rate);
|
||
|
||
pf->hwts_ops->config_addend(pf->hw.hw_addr, port, pf->default_addend);
|
||
/* 5.Poll wait for the TCR Update Addend Register*/
|
||
/* 6.enabled Fine Update method */
|
||
/* 7.program the second and nanosecond register*/
|
||
/*TODO If we need to enable one-step timestamp */
|
||
|
||
/* initialize system time */
|
||
ktime_get_real_ts64(&now);
|
||
|
||
/* lower 32 bits of tv_sec are safe until y2106 */
|
||
pf->hwts_ops->init_systime(pf->hw.hw_addr, port, (u32)now.tv_sec,
|
||
now.tv_nsec);
|
||
|
||
pf->hwts_ops->config_mac_irq_enable(pf->hw.hw_addr, port, true);
|
||
|
||
return 0;
|
||
}
|
||
|
||
int rnpm_ptp_set_ts_config(struct rnpm_adapter *pf, struct ifreq *ifr)
|
||
{
|
||
struct hwtstamp_config config;
|
||
u32 ptp_v2 = 0;
|
||
u32 tstamp_all = 0;
|
||
u32 ptp_over_ipv4_udp = 0;
|
||
u32 ptp_over_ipv6_udp = 0;
|
||
u32 ptp_over_ethernet = 0;
|
||
u32 snap_type_sel = 0;
|
||
u32 ts_master_en = 0;
|
||
u32 ts_event_en = 0;
|
||
u32 value = 0;
|
||
s32 ret = -1;
|
||
u8 port = pf->port;
|
||
|
||
if (!(pf->flags2 & RNPM_FLAG2_PTP_ENABLED)) {
|
||
pci_alert(pf->pdev, "No support for HW time stamping\n");
|
||
pf->ptp_tx_en = 0;
|
||
pf->ptp_tx_en = 0;
|
||
|
||
return -EOPNOTSUPP;
|
||
}
|
||
|
||
if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
|
||
return -EFAULT;
|
||
|
||
netdev_info(pf->netdev,
|
||
"%s config flags:0x%x, tx_type:0x%x, rx_filter:0x%x\n",
|
||
__func__, config.flags, config.tx_type, config.rx_filter);
|
||
/* reserved for future extensions */
|
||
if (config.flags)
|
||
return -EINVAL;
|
||
|
||
if (config.tx_type != HWTSTAMP_TX_OFF &&
|
||
config.tx_type != HWTSTAMP_TX_ON)
|
||
return -ERANGE;
|
||
|
||
switch (config.rx_filter) {
|
||
case HWTSTAMP_FILTER_NONE:
|
||
/* time stamp no incoming packet at all */
|
||
config.rx_filter = HWTSTAMP_FILTER_NONE;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
|
||
/* PTP v1, UDP, any kind of event packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
|
||
/* 'mac' hardware can support Sync, Pdelay_Req and
|
||
* Pdelay_resp by setting bit14 and bits17/16 to 01
|
||
* This leaves Delay_Req timestamps out.
|
||
* Enable all events *and* general purpose message
|
||
* timestamping
|
||
*/
|
||
snap_type_sel = RNPM_PTP_TCR_SNAPTYPSEL_1;
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
|
||
/* PTP v1, UDP, Sync packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_SYNC;
|
||
/* take time stamp for SYNC messages only */
|
||
ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
|
||
/* PTP v1, UDP, Delay_req packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ;
|
||
/* take time stamp for Delay_Req messages only */
|
||
ts_master_en = RNPM_PTP_TCR_TSMSTRENA;
|
||
ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
|
||
/* PTP v2, UDP, any kind of event packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
|
||
ptp_v2 = RNPM_PTP_TCR_TSVER2ENA;
|
||
|
||
/* take time stamp for all event messages */
|
||
snap_type_sel = RNPM_PTP_TCR_SNAPTYPSEL_1;
|
||
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
|
||
/* PTP v2, UDP, Sync packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_SYNC;
|
||
ptp_v2 = RNPM_PTP_TCR_TSVER2ENA;
|
||
/* take time stamp for SYNC messages only */
|
||
ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
|
||
/* PTP v2, UDP, Delay_req packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ;
|
||
ptp_v2 = RNPM_PTP_TCR_TSVER2ENA;
|
||
/* take time stamp for Delay_Req messages only */
|
||
ts_master_en = RNPM_PTP_TCR_TSMSTRENA;
|
||
ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V2_EVENT:
|
||
/* PTP v2/802.AS1 any layer, any kind of event packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
|
||
ptp_v2 = RNPM_PTP_TCR_TSVER2ENA;
|
||
snap_type_sel = RNPM_PTP_TCR_SNAPTYPSEL_1;
|
||
// ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
ptp_over_ethernet = RNPM_PTP_TCR_TSIPENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V2_SYNC:
|
||
/* PTP v2/802.AS1, any layer, Sync packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V2_SYNC;
|
||
ptp_v2 = RNPM_PTP_TCR_TSVER2ENA;
|
||
/* take time stamp for SYNC messages only */
|
||
ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
ptp_over_ethernet = RNPM_PTP_TCR_TSIPENA;
|
||
break;
|
||
|
||
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
|
||
/* PTP v2/802.AS1, any layer, Delay_req packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_PTP_V2_DELAY_REQ;
|
||
ptp_v2 = RNPM_PTP_TCR_TSVER2ENA;
|
||
/* take time stamp for Delay_Req messages only */
|
||
ts_master_en = RNPM_PTP_TCR_TSMSTRENA;
|
||
ts_event_en = RNPM_PTP_TCR_TSEVNTENA;
|
||
|
||
ptp_over_ipv4_udp = RNPM_PTP_TCR_TSIPV4ENA;
|
||
ptp_over_ipv6_udp = RNPM_PTP_TCR_TSIPV6ENA;
|
||
ptp_over_ethernet = RNPM_PTP_TCR_TSIPENA;
|
||
break;
|
||
|
||
#ifdef HWTSTAMP_FILTER_NTP_ALL
|
||
case HWTSTAMP_FILTER_NTP_ALL:
|
||
#endif
|
||
case HWTSTAMP_FILTER_ALL:
|
||
/* time stamp any incoming packet */
|
||
config.rx_filter = HWTSTAMP_FILTER_ALL;
|
||
tstamp_all = RNPM_PTP_TCR_TSENALL;
|
||
break;
|
||
|
||
default:
|
||
return -ERANGE;
|
||
}
|
||
|
||
pf->ptp_rx_en = ((config.rx_filter == HWTSTAMP_FILTER_NONE) ? 0 : 1);
|
||
pf->ptp_tx_en = config.tx_type == HWTSTAMP_TX_ON;
|
||
|
||
netdev_info(
|
||
pf->netdev,
|
||
"ptp config rx filter 0x%.2x tx_type 0x%.2x rx_en[%d] tx_en[%d]\n",
|
||
config.rx_filter, config.tx_type, pf->ptp_rx_en, pf->ptp_tx_en);
|
||
if (!pf->ptp_rx_en && !pf->ptp_tx_en)
|
||
/*rx and tx is not use hardware ts so clear the ptp register */
|
||
pf->hwts_ops->config_hw_tstamping(pf->hw.hw_addr, port, 0);
|
||
else {
|
||
value = (RNPM_PTP_TCR_TSENA | RNPM_PTP_TCR_TSCFUPDT |
|
||
RNPM_PTP_TCR_TSCTRLSSR | tstamp_all | ptp_v2 |
|
||
ptp_over_ethernet | ptp_over_ipv6_udp |
|
||
ptp_over_ipv4_udp | ts_event_en | ts_master_en |
|
||
snap_type_sel);
|
||
ret = rnpm_ptp_setup_ptp(pf, value);
|
||
if (ret < 0)
|
||
return ret;
|
||
}
|
||
pf->ptp_config_value = value;
|
||
memcpy(&pf->tstamp_config, &config, sizeof(config));
|
||
|
||
return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? -EFAULT :
|
||
0;
|
||
}
|
||
|
||
/* structure describing a PTP hardware clock */
|
||
static struct ptp_clock_info rnpm_ptp_clock_ops = {
|
||
.owner = THIS_MODULE,
|
||
.name = "rnpm ptp",
|
||
.max_adj = 500000000,
|
||
.n_alarm = 0,
|
||
.n_ext_ts = 0,
|
||
.n_per_out = 0, /* will be overwritten in stmmac_ptp_register */
|
||
.n_pins = 0, /*should be 0 if not set*/
|
||
.adjfine = rnpm_ptp_adjfine,
|
||
.adjtime = rnpm_ptp_adjtime,
|
||
.gettime64 = rnpm_ptp_gettime,
|
||
.settime64 = rnpm_ptp_settime,
|
||
.enable = rnpm_ptp_feature_enable,
|
||
};
|
||
|
||
int rnpm_ptp_register(struct rnpm_adapter *pf)
|
||
{
|
||
pf->hwts_ops = &mac_ptp;
|
||
|
||
pf->ptp_tx_en = 0;
|
||
pf->ptp_rx_en = 0;
|
||
|
||
spin_lock_init(&pf->ptp_lock);
|
||
pf->flags2 |= RNPM_FLAG2_PTP_ENABLED;
|
||
pf->ptp_clock_ops = rnpm_ptp_clock_ops;
|
||
|
||
/*default mac clock rate is 50Mhz */
|
||
pf->clk_ptp_rate = 50000000; //50Mhz
|
||
if (pf->pdev == NULL)
|
||
ptp_dbg("pdev dev is null\n");
|
||
|
||
pf->ptp_clock = ptp_clock_register(&pf->ptp_clock_ops, &pf->pdev->dev);
|
||
if (pf->ptp_clock == NULL)
|
||
pci_err(pf->pdev, "ptp clock register failed\n");
|
||
|
||
if (IS_ERR(pf->ptp_clock)) {
|
||
pci_err(pf->pdev, "ptp_clock_register failed\n");
|
||
pf->ptp_clock = NULL;
|
||
} else {
|
||
pci_info(pf->pdev, "registered PTP clock\n");
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void rnpm_ptp_unregister(struct rnpm_adapter *pf)
|
||
{
|
||
/*1. stop the ptp module*/
|
||
if (pf->ptp_clock) {
|
||
ptp_clock_unregister(pf->ptp_clock);
|
||
pf->ptp_clock = NULL;
|
||
pr_debug("Removed PTP HW clock successfully on %s\n",
|
||
"rnpm_ptp");
|
||
}
|
||
}
|
||
|
||
#if defined(DEBUG_PTP_HARD_SOFTWAY_RX) || defined(DEBUG_PTP_HARD_SOFTWAY_TX)
|
||
static u64 rnpm_get_software_ts(void)
|
||
{
|
||
struct timespec64 ts;
|
||
|
||
ktime_get_real_ts64(&ts);
|
||
return (ts.tv_nsec + ts.tv_sec * 1000000000ULL);
|
||
}
|
||
#endif
|
||
|
||
#if defined(DEBUG_PTP_TX_TIMESTAMP) || defined(DEBUG_PTP_RX_TIMESTAMP)
|
||
#define TIME_ZONE_CHINA (8)
|
||
char *asctime(const struct tm *timeptr)
|
||
{
|
||
static const char wday_name[][4] = { "Sun", "Mon", "Tue", "Wed",
|
||
"Thu", "Fri", "Sat" };
|
||
static const char mon_name[][4] = { "Jan", "Feb", "Mar", "Apr",
|
||
"May", "Jun", "Jul", "Aug",
|
||
"Sep", "Oct", "Nov", "Dec" };
|
||
static char result[26];
|
||
|
||
sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %ld\n",
|
||
wday_name[timeptr->tm_wday], mon_name[timeptr->tm_mon],
|
||
timeptr->tm_mday, timeptr->tm_hour + TIME_ZONE_CHINA,
|
||
timeptr->tm_min, timeptr->tm_sec, 1900 + timeptr->tm_year);
|
||
return result;
|
||
}
|
||
|
||
static void rnpm_print_human_timestamp(uint64_t ns, uint8_t *direct)
|
||
{
|
||
struct timespec64 ts;
|
||
struct tm tms;
|
||
ktime_t ktm = ns_to_ktime(ns);
|
||
|
||
ts = ktime_to_timespec64(ktm);
|
||
//time64_to_tm(ts.tv_sec, ts.tv_nsec / 1000000000ULL, &tms);
|
||
//ptp_dbg("[%s] %s ------\n", direct, asctime(&tms));
|
||
ptp_dbg("[%s] %s ------\n", direct, ns);
|
||
}
|
||
#endif
|
||
|
||
void rnpm_tx_hwtstamp_work(struct work_struct *work)
|
||
{
|
||
struct rnpm_adapter *adapter =
|
||
container_of(work, struct rnpm_adapter, tx_hwtstamp_work);
|
||
void __iomem *ioaddr = adapter->hw.hw_addr;
|
||
|
||
/* 1. read port belone timestatmp status reg */
|
||
/* 2. status enabled read nsec and sec reg*/
|
||
/* 3. */
|
||
u64 nanosec = 0, sec = 0;
|
||
u8 port = adapter->port;
|
||
|
||
if (!adapter->ptp_tx_skb)
|
||
return;
|
||
|
||
if (rnpm_rd_reg(ioaddr + RNPM_ETH_PTP_TX_TSVALUE_STATUS(port)) & 0x01) {
|
||
struct sk_buff *skb = adapter->ptp_tx_skb;
|
||
struct skb_shared_hwtstamps shhwtstamps;
|
||
u64 txstmp = 0;
|
||
/* read and add nsec, sec turn to nsec*/
|
||
|
||
nanosec = rnpm_rd_reg(ioaddr + RNPM_ETH_PTP_TX_LTIMES(port));
|
||
sec = rnpm_rd_reg(ioaddr + RNPM_ETH_PTP_TX_HTIMES(port));
|
||
/* when we read the timestamp finish need to notice the hardware
|
||
* that the timestamp need to update via set tx_hwts_clear-reg
|
||
* from high to low
|
||
*/
|
||
//printk("port %d call clean ptp %llx %llx\n", port, nanosec, sec);
|
||
|
||
rnpm_wr_reg(ioaddr + RNPM_ETH_PTP_TX_CLEAR(port),
|
||
PTP_GET_TX_HWTS_FINISH);
|
||
rnpm_wr_reg(ioaddr + RNPM_ETH_PTP_TX_CLEAR(port),
|
||
PTP_GET_TX_HWTS_UPDATE);
|
||
txstmp = nanosec & PTP_HWTX_TIME_VALUE_MASK;
|
||
txstmp += (sec & PTP_HWTX_TIME_VALUE_MASK) * 1000000000ULL;
|
||
|
||
/* Clear the global tx_hwtstamp_skb pointer and force writes
|
||
* prior to notifying the stack of a Tx timestamp.
|
||
*/
|
||
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
|
||
shhwtstamps.hwtstamp = ns_to_ktime(txstmp);
|
||
adapter->ptp_tx_skb = NULL;
|
||
clear_bit_unlock(__RNPM_PTP_TX_IN_PROGRESS, &adapter->state);
|
||
#ifdef DEBUG_PTP_TX_TIMESTAMP
|
||
rnpm_print_human_timestamp(txstmp, "TX");
|
||
#endif
|
||
/* Force memory writes to complete before letting h/w
|
||
* know there are new descriptors to fetch. (Only
|
||
* applicable for weak-ordered memory model archs,
|
||
* such as IA-64).
|
||
*/
|
||
wmb();
|
||
/* force write prior to skb_tstamp_tx
|
||
* because the xmit will re used the point to store ptp skb
|
||
*/
|
||
skb_tstamp_tx(skb, &shhwtstamps);
|
||
dev_consume_skb_any(skb);
|
||
} else if (time_after(jiffies,
|
||
adapter->tx_hwtstamp_start +
|
||
adapter->tx_timeout_factor * HZ)) {
|
||
/* this function will mark the skb drop*/
|
||
if (adapter->ptp_tx_skb)
|
||
dev_kfree_skb_any(adapter->ptp_tx_skb);
|
||
adapter->ptp_tx_skb = NULL;
|
||
adapter->tx_hwtstamp_timeouts++;
|
||
clear_bit_unlock(__RNPM_PTP_TX_IN_PROGRESS, &adapter->state);
|
||
netdev_warn(adapter->netdev, "clearing Tx timestamp hang\n");
|
||
} else {
|
||
/* reschedule to check later */
|
||
#ifdef DEBUG_PTP_HARD_SOFTWAY_TX
|
||
struct skb_shared_hwtstamps shhwtstamp;
|
||
u64 ns = 0;
|
||
|
||
ns = rnpm_get_software_ts();
|
||
shhwtstamp.hwtstamp = ns_to_ktime(ns);
|
||
if (adapter->ptp_tx_skb) {
|
||
skb_tstamp_tx(adapter->ptp_tx_skb, &shhwtstamp);
|
||
dev_consume_skb_any(adapter->ptp_tx_skb);
|
||
adapter->ptp_tx_skb = NULL;
|
||
}
|
||
#else
|
||
schedule_work(&adapter->tx_hwtstamp_work);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
void rnpm_ptp_get_rx_hwstamp(struct rnpm_adapter *adapter,
|
||
union rnpm_rx_desc *desc, struct sk_buff *skb)
|
||
{
|
||
u64 ns = 0;
|
||
u64 tsvalueh = 0, tsvaluel = 0;
|
||
struct skb_shared_hwtstamps *hwtstamps = NULL;
|
||
|
||
if (!skb || !adapter->ptp_rx_en) {
|
||
netdev_dbg(adapter->netdev,
|
||
"hwstamp skb is null or rx_en is zero %u\n",
|
||
adapter->ptp_rx_en);
|
||
return;
|
||
}
|
||
|
||
#ifdef DEBUG_PTP_HARD_SOFTWAY_RX
|
||
ns = rnpm_get_software_ts();
|
||
#else
|
||
if (likely(!((desc->wb.cmd) & RNPM_RXD_STAT_PTP)))
|
||
return;
|
||
hwtstamps = skb_hwtstamps(skb);
|
||
/* because of rx hwstamp store before the mac head
|
||
* skb->head and skb->data is point to same location when call alloc_skb
|
||
* so we must move 16 bytes the skb->data to the mac head location
|
||
* but for the head point if we need move the skb->head need to be diss
|
||
*/
|
||
/* low8bytes is null high8bytes is timestamp
|
||
* high32bit is seconds low32bits is nanoseconds
|
||
*/
|
||
skb_copy_from_linear_data_offset(skb, RNPM_RX_TIME_RESERVE, &tsvalueh,
|
||
RNPM_RX_SEC_SIZE);
|
||
skb_copy_from_linear_data_offset(
|
||
skb, RNPM_RX_TIME_RESERVE + RNPM_RX_SEC_SIZE, &tsvaluel,
|
||
RNPM_RX_NANOSEC_SIZE);
|
||
skb_pull(skb, RNPM_RX_HWTS_OFFSET);
|
||
tsvalueh = ntohl(tsvalueh);
|
||
tsvaluel = ntohl(tsvaluel);
|
||
|
||
ns = tsvaluel & RNPM_RX_NSEC_MASK;
|
||
ns += ((tsvalueh & RNPM_RX_SEC_MASK) * 1000000000ULL);
|
||
netdev_dbg(adapter->netdev,
|
||
"ptp get hardware ts-sec %llu ts-nanosec %llu\n", tsvalueh,
|
||
tsvaluel);
|
||
hwtstamps->hwtstamp = ns_to_ktime(ns);
|
||
#endif
|
||
#ifdef DEBUG_PTP_RX_TIMESTAMP
|
||
rnpm_print_human_timestamp(ns, "RX");
|
||
#endif
|
||
}
|
||
|
||
void rnpm_ptp_reset(struct rnpm_adapter *adapter)
|
||
{
|
||
rnpm_ptp_setup_ptp(adapter, adapter->ptp_config_value);
|
||
}
|