Commit 11817aa6 authored by David S. Miller's avatar David S. Miller

Merge branch 'mlxsw-Add-support-for-physical-hardware-clock'

Ido Schimmel says:

====================
mlxsw: Add support for physical hardware clock

Shalom says:

This patchset adds support for physical hardware clock for Spectrum-1
ASIC only.

Patches #1, #2 and #3 add the ability to query the free running clock
PCI address.

Patches #4 and #5 add two new register, the Management UTC Register and
the Management Pulse Per Second Register.

Patch #6 publishes scaled_ppm_to_ppb() to allow drivers to use it.

Patch #7 adds the physical hardware clock operations.

Patch #8 initializes the physical hardware clock.

Patch #9 adds a selftest for testing the PTP physical hardware clock.

v2 (Richard):
* s/ptp_clock_scaled_ppm_to_ppb/scaled_ppm_to_ppb/
* imply PTP_1588_CLOCK in mlxsw Kconfig
* s/mlxsw_sp1_ptp_update_phc_settime/mlxsw_sp1_ptp_phc_settime/
* s/mlxsw_sp1_ptp_update_phc_adjfreq/mlxsw_sp1_ptp_phc_adjfreq/
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 514fcaac 9366211f
......@@ -83,6 +83,7 @@ config MLXSW_SPECTRUM
select PARMAN
select OBJAGG
select MLXFW
imply PTP_1588_CLOCK
default m
---help---
This driver supports Mellanox Technologies Spectrum Ethernet
......
......@@ -31,5 +31,6 @@ mlxsw_spectrum-objs := spectrum.o spectrum_buffers.o \
spectrum_nve.o spectrum_nve_vxlan.o \
spectrum_dpipe.o
mlxsw_spectrum-$(CONFIG_MLXSW_SPECTRUM_DCB) += spectrum_dcb.o
mlxsw_spectrum-$(CONFIG_PTP_1588_CLOCK) += spectrum_ptp.o
obj-$(CONFIG_MLXSW_MINIMAL) += mlxsw_minimal.o
mlxsw_minimal-objs := minimal.o
......@@ -317,6 +317,18 @@ MLXSW_ITEM64(cmd_mbox, query_fw, doorbell_page_offset, 0x40, 0, 64);
*/
MLXSW_ITEM32(cmd_mbox, query_fw, doorbell_page_bar, 0x48, 30, 2);
/* cmd_mbox_query_fw_free_running_clock_offset
* The offset of the free running clock page
*/
MLXSW_ITEM64(cmd_mbox, query_fw, free_running_clock_offset, 0x50, 0, 64);
/* cmd_mbox_query_fw_fr_rn_clk_bar
* PCI base address register (BAR) of the free running clock page
* 0: BAR 0
* 1: 64 bit BAR
*/
MLXSW_ITEM32(cmd_mbox, query_fw, fr_rn_clk_bar, 0x58, 30, 2);
/* QUERY_BOARDINFO - Query Board Information
* -----------------------------------------
* OpMod == 0 (N/A), INMmod == 0 (N/A)
......
......@@ -2026,6 +2026,18 @@ int mlxsw_core_resources_query(struct mlxsw_core *mlxsw_core, char *mbox,
}
EXPORT_SYMBOL(mlxsw_core_resources_query);
u32 mlxsw_core_read_frc_h(struct mlxsw_core *mlxsw_core)
{
return mlxsw_core->bus->read_frc_h(mlxsw_core->bus_priv);
}
EXPORT_SYMBOL(mlxsw_core_read_frc_h);
u32 mlxsw_core_read_frc_l(struct mlxsw_core *mlxsw_core)
{
return mlxsw_core->bus->read_frc_l(mlxsw_core->bus_priv);
}
EXPORT_SYMBOL(mlxsw_core_read_frc_l);
static int __init mlxsw_core_module_init(void)
{
int err;
......
......@@ -309,6 +309,9 @@ int mlxsw_core_kvd_sizes_get(struct mlxsw_core *mlxsw_core,
void mlxsw_core_fw_flash_start(struct mlxsw_core *mlxsw_core);
void mlxsw_core_fw_flash_end(struct mlxsw_core *mlxsw_core);
u32 mlxsw_core_read_frc_h(struct mlxsw_core *mlxsw_core);
u32 mlxsw_core_read_frc_l(struct mlxsw_core *mlxsw_core);
bool mlxsw_core_res_valid(struct mlxsw_core *mlxsw_core,
enum mlxsw_res_id res_id);
......@@ -339,6 +342,8 @@ struct mlxsw_bus {
char *in_mbox, size_t in_mbox_size,
char *out_mbox, size_t out_mbox_size,
u8 *p_status);
u32 (*read_frc_h)(void *bus_priv);
u32 (*read_frc_l)(void *bus_priv);
u8 features;
};
......@@ -356,7 +361,8 @@ struct mlxsw_bus_info {
struct mlxsw_fw_rev fw_rev;
u8 vsd[MLXSW_CMD_BOARDINFO_VSD_LEN];
u8 psid[MLXSW_CMD_BOARDINFO_PSID_LEN];
u8 low_frequency;
u8 low_frequency:1,
read_frc_capable:1;
};
struct mlxsw_hwmon;
......
......@@ -102,6 +102,7 @@ struct mlxsw_pci_queue_type_group {
struct mlxsw_pci {
struct pci_dev *pdev;
u8 __iomem *hw_addr;
u64 free_running_clock_offset;
struct mlxsw_pci_queue_type_group queues[MLXSW_PCI_QUEUE_TYPE_COUNT];
u32 doorbell_offset;
struct mlxsw_core *core;
......@@ -1414,6 +1415,15 @@ static int mlxsw_pci_init(void *bus_priv, struct mlxsw_core *mlxsw_core,
mlxsw_pci->doorbell_offset =
mlxsw_cmd_mbox_query_fw_doorbell_page_offset_get(mbox);
if (mlxsw_cmd_mbox_query_fw_fr_rn_clk_bar_get(mbox) != 0) {
dev_err(&pdev->dev, "Unsupported free running clock BAR queried from hw\n");
err = -EINVAL;
goto err_fr_rn_clk_bar;
}
mlxsw_pci->free_running_clock_offset =
mlxsw_cmd_mbox_query_fw_free_running_clock_offset_get(mbox);
num_pages = mlxsw_cmd_mbox_query_fw_fw_pages_get(mbox);
err = mlxsw_pci_fw_area_init(mlxsw_pci, mbox, num_pages);
if (err)
......@@ -1469,6 +1479,7 @@ static int mlxsw_pci_init(void *bus_priv, struct mlxsw_core *mlxsw_core,
err_boardinfo:
mlxsw_pci_fw_area_fini(mlxsw_pci);
err_fw_area_init:
err_fr_rn_clk_bar:
err_doorbell_page_bar:
err_iface_rev:
err_query_fw:
......@@ -1672,6 +1683,24 @@ static int mlxsw_pci_cmd_exec(void *bus_priv, u16 opcode, u8 opcode_mod,
return err;
}
static u32 mlxsw_pci_read_frc_h(void *bus_priv)
{
struct mlxsw_pci *mlxsw_pci = bus_priv;
u64 frc_offset;
frc_offset = mlxsw_pci->free_running_clock_offset;
return mlxsw_pci_read32(mlxsw_pci, FREE_RUNNING_CLOCK_H(frc_offset));
}
static u32 mlxsw_pci_read_frc_l(void *bus_priv)
{
struct mlxsw_pci *mlxsw_pci = bus_priv;
u64 frc_offset;
frc_offset = mlxsw_pci->free_running_clock_offset;
return mlxsw_pci_read32(mlxsw_pci, FREE_RUNNING_CLOCK_L(frc_offset));
}
static const struct mlxsw_bus mlxsw_pci_bus = {
.kind = "pci",
.init = mlxsw_pci_init,
......@@ -1679,6 +1708,8 @@ static const struct mlxsw_bus mlxsw_pci_bus = {
.skb_transmit_busy = mlxsw_pci_skb_transmit_busy,
.skb_transmit = mlxsw_pci_skb_transmit,
.cmd_exec = mlxsw_pci_cmd_exec,
.read_frc_h = mlxsw_pci_read_frc_h,
.read_frc_l = mlxsw_pci_read_frc_l,
.features = MLXSW_BUS_F_TXRX | MLXSW_BUS_F_RESET,
};
......@@ -1740,6 +1771,7 @@ static int mlxsw_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mlxsw_pci->bus_info.device_kind = driver_name;
mlxsw_pci->bus_info.device_name = pci_name(mlxsw_pci->pdev);
mlxsw_pci->bus_info.dev = &pdev->dev;
mlxsw_pci->bus_info.read_frc_capable = true;
mlxsw_pci->id = id;
err = mlxsw_core_bus_device_register(&mlxsw_pci->bus_info,
......
......@@ -43,6 +43,9 @@
#define MLXSW_PCI_DOORBELL(offset, type_offset, num) \
((offset) + (type_offset) + (num) * 4)
#define MLXSW_PCI_FREE_RUNNING_CLOCK_H(offset) (offset)
#define MLXSW_PCI_FREE_RUNNING_CLOCK_L(offset) ((offset) + 4)
#define MLXSW_PCI_CQS_MAX 96
#define MLXSW_PCI_EQS_COUNT 2
#define MLXSW_PCI_EQ_ASYNC_NUM 0
......
......@@ -8691,6 +8691,107 @@ static inline void mlxsw_reg_mlcr_pack(char *payload, u8 local_port,
MLXSW_REG_MLCR_DURATION_MAX : 0);
}
/* MTPPS - Management Pulse Per Second Register
* --------------------------------------------
* This register provides the device PPS capabilities, configure the PPS in and
* out modules and holds the PPS in time stamp.
*/
#define MLXSW_REG_MTPPS_ID 0x9053
#define MLXSW_REG_MTPPS_LEN 0x3C
MLXSW_REG_DEFINE(mtpps, MLXSW_REG_MTPPS_ID, MLXSW_REG_MTPPS_LEN);
/* reg_mtpps_enable
* Enables the PPS functionality the specific pin.
* A boolean variable.
* Access: RW
*/
MLXSW_ITEM32(reg, mtpps, enable, 0x20, 31, 1);
enum mlxsw_reg_mtpps_pin_mode {
MLXSW_REG_MTPPS_PIN_MODE_VIRTUAL_PIN = 0x2,
};
/* reg_mtpps_pin_mode
* Pin mode to be used. The mode must comply with the supported modes of the
* requested pin.
* Access: RW
*/
MLXSW_ITEM32(reg, mtpps, pin_mode, 0x20, 8, 4);
#define MLXSW_REG_MTPPS_PIN_SP_VIRTUAL_PIN 7
/* reg_mtpps_pin
* Pin to be configured or queried out of the supported pins.
* Access: Index
*/
MLXSW_ITEM32(reg, mtpps, pin, 0x20, 0, 8);
/* reg_mtpps_time_stamp
* When pin_mode = pps_in, the latched device time when it was triggered from
* the external GPIO pin.
* When pin_mode = pps_out or virtual_pin or pps_out_and_virtual_pin, the target
* time to generate next output signal.
* Time is in units of device clock.
* Access: RW
*/
MLXSW_ITEM64(reg, mtpps, time_stamp, 0x28, 0, 64);
static inline void
mlxsw_reg_mtpps_vpin_pack(char *payload, u64 time_stamp)
{
MLXSW_REG_ZERO(mtpps, payload);
mlxsw_reg_mtpps_pin_set(payload, MLXSW_REG_MTPPS_PIN_SP_VIRTUAL_PIN);
mlxsw_reg_mtpps_pin_mode_set(payload,
MLXSW_REG_MTPPS_PIN_MODE_VIRTUAL_PIN);
mlxsw_reg_mtpps_enable_set(payload, true);
mlxsw_reg_mtpps_time_stamp_set(payload, time_stamp);
}
/* MTUTC - Management UTC Register
* -------------------------------
* Configures the HW UTC counter.
*/
#define MLXSW_REG_MTUTC_ID 0x9055
#define MLXSW_REG_MTUTC_LEN 0x1C
MLXSW_REG_DEFINE(mtutc, MLXSW_REG_MTUTC_ID, MLXSW_REG_MTUTC_LEN);
enum mlxsw_reg_mtutc_operation {
MLXSW_REG_MTUTC_OPERATION_SET_TIME_AT_NEXT_SEC = 0,
MLXSW_REG_MTUTC_OPERATION_ADJUST_FREQ = 3,
};
/* reg_mtutc_operation
* Operation.
* Access: OP
*/
MLXSW_ITEM32(reg, mtutc, operation, 0x00, 0, 4);
/* reg_mtutc_freq_adjustment
* Frequency adjustment: Every PPS the HW frequency will be
* adjusted by this value. Units of HW clock, where HW counts
* 10^9 HW clocks for 1 HW second.
* Access: RW
*/
MLXSW_ITEM32(reg, mtutc, freq_adjustment, 0x04, 0, 32);
/* reg_mtutc_utc_sec
* UTC seconds.
* Access: WO
*/
MLXSW_ITEM32(reg, mtutc, utc_sec, 0x10, 0, 32);
static inline void
mlxsw_reg_mtutc_pack(char *payload, enum mlxsw_reg_mtutc_operation oper,
u32 freq_adj, u32 utc_sec)
{
MLXSW_REG_ZERO(mtutc, payload);
mlxsw_reg_mtutc_operation_set(payload, oper);
mlxsw_reg_mtutc_freq_adjustment_set(payload, freq_adj);
mlxsw_reg_mtutc_utc_sec_set(payload, utc_sec);
}
/* MCQI - Management Component Query Information
* ---------------------------------------------
* This register allows querying information about firmware components.
......@@ -10105,6 +10206,8 @@ static const struct mlxsw_reg_info *mlxsw_reg_infos[] = {
MLXSW_REG(mgir),
MLXSW_REG(mrsr),
MLXSW_REG(mlcr),
MLXSW_REG(mtpps),
MLXSW_REG(mtutc),
MLXSW_REG(mpsc),
MLXSW_REG(mcqi),
MLXSW_REG(mcc),
......
......@@ -41,6 +41,7 @@
#include "spectrum_dpipe.h"
#include "spectrum_acl_flex_actions.h"
#include "spectrum_span.h"
#include "spectrum_ptp.h"
#include "../mlxfw/mlxfw.h"
#define MLXSW_SP_FWREV_MINOR_TO_BRANCH(minor) ((minor) / 100)
......@@ -4342,6 +4343,22 @@ static int mlxsw_sp_basic_trap_groups_set(struct mlxsw_core *mlxsw_core)
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(htgt), htgt_pl);
}
struct mlxsw_sp_ptp_ops {
struct mlxsw_sp_ptp_clock *
(*clock_init)(struct mlxsw_sp *mlxsw_sp, struct device *dev);
void (*clock_fini)(struct mlxsw_sp_ptp_clock *clock);
};
static const struct mlxsw_sp_ptp_ops mlxsw_sp1_ptp_ops = {
.clock_init = mlxsw_sp1_ptp_clock_init,
.clock_fini = mlxsw_sp1_ptp_clock_fini,
};
static const struct mlxsw_sp_ptp_ops mlxsw_sp2_ptp_ops = {
.clock_init = mlxsw_sp2_ptp_clock_init,
.clock_fini = mlxsw_sp2_ptp_clock_fini,
};
static int mlxsw_sp_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr);
......@@ -4439,6 +4456,18 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
goto err_router_init;
}
if (mlxsw_sp->bus_info->read_frc_capable) {
/* NULL is a valid return value from clock_init */
mlxsw_sp->clock =
mlxsw_sp->ptp_ops->clock_init(mlxsw_sp,
mlxsw_sp->bus_info->dev);
if (IS_ERR(mlxsw_sp->clock)) {
err = PTR_ERR(mlxsw_sp->clock);
dev_err(mlxsw_sp->bus_info->dev, "Failed to init ptp clock\n");
goto err_ptp_clock_init;
}
}
/* Initialize netdevice notifier after router and SPAN is initialized,
* so that the event handler can use router structures and call SPAN
* respin.
......@@ -4469,6 +4498,9 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
err_dpipe_init:
unregister_netdevice_notifier(&mlxsw_sp->netdevice_nb);
err_netdev_notifier:
if (mlxsw_sp->clock)
mlxsw_sp->ptp_ops->clock_fini(mlxsw_sp->clock);
err_ptp_clock_init:
mlxsw_sp_router_fini(mlxsw_sp);
err_router_init:
mlxsw_sp_acl_fini(mlxsw_sp);
......@@ -4512,6 +4544,7 @@ static int mlxsw_sp1_init(struct mlxsw_core *mlxsw_core,
mlxsw_sp->rif_ops_arr = mlxsw_sp1_rif_ops_arr;
mlxsw_sp->sb_vals = &mlxsw_sp1_sb_vals;
mlxsw_sp->port_type_speed_ops = &mlxsw_sp1_port_type_speed_ops;
mlxsw_sp->ptp_ops = &mlxsw_sp1_ptp_ops;
return mlxsw_sp_init(mlxsw_core, mlxsw_bus_info);
}
......@@ -4531,6 +4564,7 @@ static int mlxsw_sp2_init(struct mlxsw_core *mlxsw_core,
mlxsw_sp->rif_ops_arr = mlxsw_sp2_rif_ops_arr;
mlxsw_sp->sb_vals = &mlxsw_sp2_sb_vals;
mlxsw_sp->port_type_speed_ops = &mlxsw_sp2_port_type_speed_ops;
mlxsw_sp->ptp_ops = &mlxsw_sp2_ptp_ops;
return mlxsw_sp_init(mlxsw_core, mlxsw_bus_info);
}
......@@ -4542,6 +4576,8 @@ static void mlxsw_sp_fini(struct mlxsw_core *mlxsw_core)
mlxsw_sp_ports_remove(mlxsw_sp);
mlxsw_sp_dpipe_fini(mlxsw_sp);
unregister_netdevice_notifier(&mlxsw_sp->netdevice_nb);
if (mlxsw_sp->clock)
mlxsw_sp->ptp_ops->clock_fini(mlxsw_sp->clock);
mlxsw_sp_router_fini(mlxsw_sp);
mlxsw_sp_acl_fini(mlxsw_sp);
mlxsw_sp_nve_fini(mlxsw_sp);
......
......@@ -136,6 +136,7 @@ struct mlxsw_sp_acl_tcam_ops;
struct mlxsw_sp_nve_ops;
struct mlxsw_sp_sb_vals;
struct mlxsw_sp_port_type_speed_ops;
struct mlxsw_sp_ptp_ops;
struct mlxsw_sp {
struct mlxsw_sp_port **ports;
......@@ -155,6 +156,7 @@ struct mlxsw_sp {
struct mlxsw_sp_kvdl *kvdl;
struct mlxsw_sp_nve *nve;
struct notifier_block netdevice_nb;
struct mlxsw_sp_ptp_clock *clock;
struct mlxsw_sp_counter_pool *counter_pool;
struct {
......@@ -172,6 +174,7 @@ struct mlxsw_sp {
const struct mlxsw_sp_rif_ops **rif_ops_arr;
const struct mlxsw_sp_sb_vals *sb_vals;
const struct mlxsw_sp_port_type_speed_ops *port_type_speed_ops;
const struct mlxsw_sp_ptp_ops *ptp_ops;
};
static inline struct mlxsw_sp_upper *
......
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2019 Mellanox Technologies. All rights reserved */
#include <linux/ptp_clock_kernel.h>
#include <linux/clocksource.h>
#include <linux/timecounter.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include "spectrum_ptp.h"
#include "core.h"
#define MLXSW_SP1_PTP_CLOCK_CYCLES_SHIFT 29
#define MLXSW_SP1_PTP_CLOCK_FREQ_KHZ 156257 /* 6.4nSec */
#define MLXSW_SP1_PTP_CLOCK_MASK 64
struct mlxsw_sp_ptp_clock {
struct mlxsw_core *core;
spinlock_t lock; /* protect this structure */
struct cyclecounter cycles;
struct timecounter tc;
u32 nominal_c_mult;
struct ptp_clock *ptp;
struct ptp_clock_info ptp_info;
unsigned long overflow_period;
struct delayed_work overflow_work;
};
static u64 __mlxsw_sp1_ptp_read_frc(struct mlxsw_sp_ptp_clock *clock,
struct ptp_system_timestamp *sts)
{
struct mlxsw_core *mlxsw_core = clock->core;
u32 frc_h1, frc_h2, frc_l;
frc_h1 = mlxsw_core_read_frc_h(mlxsw_core);
ptp_read_system_prets(sts);
frc_l = mlxsw_core_read_frc_l(mlxsw_core);
ptp_read_system_postts(sts);
frc_h2 = mlxsw_core_read_frc_h(mlxsw_core);
if (frc_h1 != frc_h2) {
/* wrap around */
ptp_read_system_prets(sts);
frc_l = mlxsw_core_read_frc_l(mlxsw_core);
ptp_read_system_postts(sts);
}
return (u64) frc_l | (u64) frc_h2 << 32;
}
static u64 mlxsw_sp1_ptp_read_frc(const struct cyclecounter *cc)
{
struct mlxsw_sp_ptp_clock *clock =
container_of(cc, struct mlxsw_sp_ptp_clock, cycles);
return __mlxsw_sp1_ptp_read_frc(clock, NULL) & cc->mask;
}
static int
mlxsw_sp1_ptp_phc_adjfreq(struct mlxsw_sp_ptp_clock *clock, int freq_adj)
{
struct mlxsw_core *mlxsw_core = clock->core;
char mtutc_pl[MLXSW_REG_MTUTC_LEN];
mlxsw_reg_mtutc_pack(mtutc_pl, MLXSW_REG_MTUTC_OPERATION_ADJUST_FREQ,
freq_adj, 0);
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtutc), mtutc_pl);
}
static u64 mlxsw_sp1_ptp_ns2cycles(const struct timecounter *tc, u64 nsec)
{
u64 cycles = (u64) nsec;
cycles <<= tc->cc->shift;
cycles = div_u64(cycles, tc->cc->mult);
return cycles;
}
static int
mlxsw_sp1_ptp_phc_settime(struct mlxsw_sp_ptp_clock *clock, u64 nsec)
{
struct mlxsw_core *mlxsw_core = clock->core;
char mtutc_pl[MLXSW_REG_MTUTC_LEN];
char mtpps_pl[MLXSW_REG_MTPPS_LEN];
u64 next_sec_in_nsec, cycles;
u32 next_sec;
int err;
next_sec = nsec / NSEC_PER_SEC + 1;
next_sec_in_nsec = next_sec * NSEC_PER_SEC;
spin_lock(&clock->lock);
cycles = mlxsw_sp1_ptp_ns2cycles(&clock->tc, next_sec_in_nsec);
spin_unlock(&clock->lock);
mlxsw_reg_mtpps_vpin_pack(mtpps_pl, cycles);
err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtpps), mtpps_pl);
if (err)
return err;
mlxsw_reg_mtutc_pack(mtutc_pl,
MLXSW_REG_MTUTC_OPERATION_SET_TIME_AT_NEXT_SEC,
0, next_sec);
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtutc), mtutc_pl);
}
static int mlxsw_sp1_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct mlxsw_sp_ptp_clock *clock =
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
int neg_adj = 0;
u32 diff;
u64 adj;
s32 ppb;
ppb = scaled_ppm_to_ppb(scaled_ppm);
if (ppb < 0) {
neg_adj = 1;
ppb = -ppb;
}
adj = clock->nominal_c_mult;
adj *= ppb;
diff = div_u64(adj, NSEC_PER_SEC);
spin_lock(&clock->lock);
timecounter_read(&clock->tc);
clock->cycles.mult = neg_adj ? clock->nominal_c_mult - diff :
clock->nominal_c_mult + diff;
spin_unlock(&clock->lock);
return mlxsw_sp1_ptp_phc_adjfreq(clock, neg_adj ? -ppb : ppb);
}
static int mlxsw_sp1_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct mlxsw_sp_ptp_clock *clock =
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
u64 nsec;
spin_lock(&clock->lock);
timecounter_adjtime(&clock->tc, delta);
nsec = timecounter_read(&clock->tc);
spin_unlock(&clock->lock);
return mlxsw_sp1_ptp_phc_settime(clock, nsec);
}
static int mlxsw_sp1_ptp_gettimex(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct mlxsw_sp_ptp_clock *clock =
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
u64 cycles, nsec;
spin_lock(&clock->lock);
cycles = __mlxsw_sp1_ptp_read_frc(clock, sts);
nsec = timecounter_cyc2time(&clock->tc, cycles);
spin_unlock(&clock->lock);
*ts = ns_to_timespec64(nsec);
return 0;
}
static int mlxsw_sp1_ptp_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct mlxsw_sp_ptp_clock *clock =
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
u64 nsec = timespec64_to_ns(ts);
spin_lock(&clock->lock);
timecounter_init(&clock->tc, &clock->cycles, nsec);
nsec = timecounter_read(&clock->tc);
spin_unlock(&clock->lock);
return mlxsw_sp1_ptp_phc_settime(clock, nsec);
}
static const struct ptp_clock_info mlxsw_sp1_ptp_clock_info = {
.owner = THIS_MODULE,
.name = "mlxsw_sp_clock",
.max_adj = 100000000,
.adjfine = mlxsw_sp1_ptp_adjfine,
.adjtime = mlxsw_sp1_ptp_adjtime,
.gettimex64 = mlxsw_sp1_ptp_gettimex,
.settime64 = mlxsw_sp1_ptp_settime,
};
static void mlxsw_sp1_ptp_clock_overflow(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct mlxsw_sp_ptp_clock *clock;
clock = container_of(dwork, struct mlxsw_sp_ptp_clock, overflow_work);
spin_lock(&clock->lock);
timecounter_read(&clock->tc);
spin_unlock(&clock->lock);
mlxsw_core_schedule_dw(&clock->overflow_work, clock->overflow_period);
}
struct mlxsw_sp_ptp_clock *
mlxsw_sp1_ptp_clock_init(struct mlxsw_sp *mlxsw_sp, struct device *dev)
{
u64 overflow_cycles, nsec, frac = 0;
struct mlxsw_sp_ptp_clock *clock;
int err;
clock = kzalloc(sizeof(*clock), GFP_KERNEL);
if (!clock)
return ERR_PTR(-ENOMEM);
spin_lock_init(&clock->lock);
clock->cycles.read = mlxsw_sp1_ptp_read_frc;
clock->cycles.shift = MLXSW_SP1_PTP_CLOCK_CYCLES_SHIFT;
clock->cycles.mult = clocksource_khz2mult(MLXSW_SP1_PTP_CLOCK_FREQ_KHZ,
clock->cycles.shift);
clock->nominal_c_mult = clock->cycles.mult;
clock->cycles.mask = CLOCKSOURCE_MASK(MLXSW_SP1_PTP_CLOCK_MASK);
clock->core = mlxsw_sp->core;
timecounter_init(&clock->tc, &clock->cycles,
ktime_to_ns(ktime_get_real()));
/* Calculate period in seconds to call the overflow watchdog - to make
* sure counter is checked at least twice every wrap around.
* The period is calculated as the minimum between max HW cycles count
* (The clock source mask) and max amount of cycles that can be
* multiplied by clock multiplier where the result doesn't exceed
* 64bits.
*/
overflow_cycles = div64_u64(~0ULL >> 1, clock->cycles.mult);
overflow_cycles = min(overflow_cycles, div_u64(clock->cycles.mask, 3));
nsec = cyclecounter_cyc2ns(&clock->cycles, overflow_cycles, 0, &frac);
clock->overflow_period = nsecs_to_jiffies(nsec);
INIT_DELAYED_WORK(&clock->overflow_work, mlxsw_sp1_ptp_clock_overflow);
mlxsw_core_schedule_dw(&clock->overflow_work, 0);
clock->ptp_info = mlxsw_sp1_ptp_clock_info;
clock->ptp = ptp_clock_register(&clock->ptp_info, dev);
if (IS_ERR(clock->ptp)) {
err = PTR_ERR(clock->ptp);
dev_err(dev, "ptp_clock_register failed %d\n", err);
goto err_ptp_clock_register;
}
return clock;
err_ptp_clock_register:
cancel_delayed_work_sync(&clock->overflow_work);
kfree(clock);
return ERR_PTR(err);
}
void mlxsw_sp1_ptp_clock_fini(struct mlxsw_sp_ptp_clock *clock)
{
ptp_clock_unregister(clock->ptp);
cancel_delayed_work_sync(&clock->overflow_work);
kfree(clock);
}
/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */
/* Copyright (c) 2019 Mellanox Technologies. All rights reserved */
#ifndef _MLXSW_SPECTRUM_PTP_H
#define _MLXSW_SPECTRUM_PTP_H
#include <linux/device.h>
#include "spectrum.h"
struct mlxsw_sp_ptp_clock;
#if IS_REACHABLE(CONFIG_PTP_1588_CLOCK)
struct mlxsw_sp_ptp_clock *
mlxsw_sp1_ptp_clock_init(struct mlxsw_sp *mlxsw_sp, struct device *dev);
void mlxsw_sp1_ptp_clock_fini(struct mlxsw_sp_ptp_clock *clock);
#else
static inline struct mlxsw_sp_ptp_clock *
mlxsw_sp1_ptp_clock_init(struct mlxsw_sp *mlxsw_sp, struct device *dev)
{
return NULL;
}
static inline void mlxsw_sp1_ptp_clock_fini(struct mlxsw_sp_ptp_clock *clock)
{
}
#endif
static inline struct mlxsw_sp_ptp_clock *
mlxsw_sp2_ptp_clock_init(struct mlxsw_sp *mlxsw_sp, struct device *dev)
{
return NULL;
}
static inline void mlxsw_sp2_ptp_clock_fini(struct mlxsw_sp_ptp_clock *clock)
{
}
#endif
......@@ -63,7 +63,7 @@ static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
spin_unlock_irqrestore(&queue->lock, flags);
}
static s32 scaled_ppm_to_ppb(long ppm)
s32 scaled_ppm_to_ppb(long ppm)
{
/*
* The 'freq' field in the 'struct timex' is in parts per
......@@ -82,6 +82,7 @@ static s32 scaled_ppm_to_ppb(long ppm)
ppb >>= 13;
return (s32) ppb;
}
EXPORT_SYMBOL(scaled_ppm_to_ppb);
/* posix clock implementation */
......
......@@ -212,6 +212,14 @@ extern void ptp_clock_event(struct ptp_clock *ptp,
extern int ptp_clock_index(struct ptp_clock *ptp);
/**
* scaled_ppm_to_ppb() - convert scaled ppm to ppb
*
* @ppm: Parts per million, but with a 16 bit binary fractional field
*/
extern s32 scaled_ppm_to_ppb(long ppm);
/**
* ptp_find_pin() - obtain the pin index of a given auxiliary function
*
......
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
ALL_TESTS="
settime
adjtime
adjfreq
"
DEV=$1
##############################################################################
# Sanity checks
if [[ "$(id -u)" -ne 0 ]]; then
echo "SKIP: need root privileges"
exit 0
fi
if [[ "$DEV" == "" ]]; then
echo "SKIP: PTP device not provided"
exit 0
fi
require_command()
{
local cmd=$1; shift
if [[ ! -x "$(command -v "$cmd")" ]]; then
echo "SKIP: $cmd not installed"
exit 1
fi
}
phc_sanity()
{
phc_ctl $DEV get &> /dev/null
if [ $? != 0 ]; then
echo "SKIP: unknown clock $DEV: No such device"
exit 1
fi
}
require_command phc_ctl
phc_sanity
##############################################################################
# Helpers
# Exit status to return at the end. Set in case one of the tests fails.
EXIT_STATUS=0
# Per-test return value. Clear at the beginning of each test.
RET=0
check_err()
{
local err=$1
if [[ $RET -eq 0 && $err -ne 0 ]]; then
RET=$err
fi
}
log_test()
{
local test_name=$1
if [[ $RET -ne 0 ]]; then
EXIT_STATUS=1
printf "TEST: %-60s [FAIL]\n" "$test_name"
return 1
fi
printf "TEST: %-60s [ OK ]\n" "$test_name"
return 0
}
tests_run()
{
local current_test
for current_test in ${TESTS:-$ALL_TESTS}; do
$current_test
done
}
##############################################################################
# Tests
settime_do()
{
local res
res=$(phc_ctl $DEV set 0 wait 120.5 get 2> /dev/null \
| awk '/clock time is/{print $5}' \
| awk -F. '{print $1}')
(( res == 120 ))
}
adjtime_do()
{
local res
res=$(phc_ctl $DEV set 0 adj 10 get 2> /dev/null \
| awk '/clock time is/{print $5}' \
| awk -F. '{print $1}')
(( res == 10 ))
}
adjfreq_do()
{
local res
# Set the clock to be 1% faster
res=$(phc_ctl $DEV freq 10000000 set 0 wait 100.5 get 2> /dev/null \
| awk '/clock time is/{print $5}' \
| awk -F. '{print $1}')
(( res == 101 ))
}
##############################################################################
cleanup()
{
phc_ctl $DEV freq 0.0 &> /dev/null
phc_ctl $DEV set &> /dev/null
}
settime()
{
RET=0
settime_do
check_err $?
log_test "settime"
cleanup
}
adjtime()
{
RET=0
adjtime_do
check_err $?
log_test "adjtime"
cleanup
}
adjfreq()
{
RET=0
adjfreq_do
check_err $?
log_test "adjfreq"
cleanup
}
trap cleanup EXIT
tests_run
exit $EXIT_STATUS
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment