mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-06 03:06:50 -04:00
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: David S. Miller <davem@davemloft.net>
This commit is contained in:
@@ -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 *
|
||||
|
||||
267
drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c
Normal file
267
drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c
Normal file
@@ -0,0 +1,267 @@
|
||||
// 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);
|
||||
}
|
||||
44
drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.h
Normal file
44
drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* 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
|
||||
*
|
||||
|
||||
166
tools/testing/selftests/ptp/phc.sh
Executable file
166
tools/testing/selftests/ptp/phc.sh
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user