Skip to content

Commit

Permalink
net: dsa: microchip: ptp: add the posix clock support
Browse files Browse the repository at this point in the history
This patch implement routines (adjfine, adjtime, gettime and settime)
for manipulating the chip's PTP clock. It registers the ptp caps
to posix clock register.

Signed-off-by: Christian Eggers <[email protected]>
Co-developed-by: Arun Ramadoss <[email protected]>
Signed-off-by: Arun Ramadoss <[email protected]>
Reviewed-by: Vladimir Oltean <[email protected]> # mostly api
Reviewed-by: Florian Fainelli <[email protected]>
Reviewed-by: Jacob Keller <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
ceggers-arri authored and davem330 committed Jan 13, 2023
1 parent 72863e0 commit eac1ea2
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 1 deletion.
11 changes: 11 additions & 0 deletions drivers/net/dsa/microchip/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,28 @@ menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
config NET_DSA_MICROCHIP_KSZ9477_I2C
tristate "KSZ series I2C connected switch driver"
depends on NET_DSA_MICROCHIP_KSZ_COMMON && I2C
depends on PTP_1588_CLOCK_OPTIONAL
select REGMAP_I2C
help
Select to enable support for registering switches configured through I2C.

config NET_DSA_MICROCHIP_KSZ_SPI
tristate "KSZ series SPI connected switch driver"
depends on NET_DSA_MICROCHIP_KSZ_COMMON && SPI
depends on PTP_1588_CLOCK_OPTIONAL
select REGMAP_SPI
help
Select to enable support for registering switches configured through SPI.

config NET_DSA_MICROCHIP_KSZ_PTP
bool "Support for the PTP clock on the KSZ9563/LAN937x Ethernet Switch"
depends on NET_DSA_MICROCHIP_KSZ_COMMON && PTP_1588_CLOCK
help
Select to enable support for timestamping & PTP clock manipulation in
KSZ8563/KSZ9563/LAN937x series of switches. KSZ9563/KSZ8563 supports
only one step timestamping. LAN937x switch supports both one step and
two step timestamping.

config NET_DSA_MICROCHIP_KSZ8863_SMI
tristate "KSZ series SMI connected switch driver"
depends on NET_DSA_MICROCHIP_KSZ_COMMON
Expand Down
5 changes: 5 additions & 0 deletions drivers/net/dsa/microchip/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ ksz_switch-objs := ksz_common.o
ksz_switch-objs += ksz9477.o
ksz_switch-objs += ksz8795.o
ksz_switch-objs += lan937x_main.o

ifdef CONFIG_NET_DSA_MICROCHIP_KSZ_PTP
ksz_switch-objs += ksz_ptp.o
endif

obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI) += ksz8863_smi.o
13 changes: 12 additions & 1 deletion drivers/net/dsa/microchip/ksz_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <net/switchdev.h>

#include "ksz_common.h"
#include "ksz_ptp.h"
#include "ksz8.h"
#include "ksz9477.h"
#include "lan937x.h"
Expand Down Expand Up @@ -2102,10 +2103,16 @@ static int ksz_setup(struct dsa_switch *ds)
}
}

ret = ksz_ptp_clock_register(ds);
if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n", ret);
goto out_pirq;
}

ret = ksz_mdio_register(dev);
if (ret < 0) {
dev_err(dev->dev, "failed to register the mdio");
goto out_pirq;
goto out_ptp_clock_unregister;
}

/* start switch */
Expand All @@ -2114,6 +2121,8 @@ static int ksz_setup(struct dsa_switch *ds)

return 0;

out_ptp_clock_unregister:
ksz_ptp_clock_unregister(ds);
out_pirq:
if (dev->irq > 0)
dsa_switch_for_each_user_port(dp, dev->ds)
Expand All @@ -2130,6 +2139,8 @@ static void ksz_teardown(struct dsa_switch *ds)
struct ksz_device *dev = ds->priv;
struct dsa_port *dp;

ksz_ptp_clock_unregister(ds);

if (dev->irq > 0) {
dsa_switch_for_each_user_port(dp, dev->ds)
ksz_irq_free(&dev->ports[dp->index].pirq);
Expand Down
16 changes: 16 additions & 0 deletions drivers/net/dsa/microchip/ksz_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <net/dsa.h>
#include <linux/irq.h>

#include "ksz_ptp.h"

#define KSZ_MAX_NUM_PORTS 8

struct ksz_device;
Expand Down Expand Up @@ -140,6 +142,7 @@ struct ksz_device {
u16 port_mask;
struct mutex lock_irq; /* IRQ Access */
struct ksz_irq girq;
struct ksz_ptp_data ptp_data;
};

/* List of supported models */
Expand Down Expand Up @@ -443,6 +446,19 @@ static inline int ksz_write32(struct ksz_device *dev, u32 reg, u32 value)
return ret;
}

static inline int ksz_rmw16(struct ksz_device *dev, u32 reg, u16 mask,
u16 value)
{
int ret;

ret = regmap_update_bits(dev->regmap[1], reg, mask, value);
if (ret)
dev_err(dev->dev, "can't rmw 16bit reg 0x%x: %pe\n", reg,
ERR_PTR(ret));

return ret;
}

static inline int ksz_write64(struct ksz_device *dev, u32 reg, u64 value)
{
u32 val[2];
Expand Down
236 changes: 236 additions & 0 deletions drivers/net/dsa/microchip/ksz_ptp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: GPL-2.0
/* Microchip KSZ PTP Implementation
*
* Copyright (C) 2020 ARRI Lighting
* Copyright (C) 2022 Microchip Technology Inc.
*/

#include <linux/kernel.h>
#include <linux/ptp_classify.h>
#include <linux/ptp_clock_kernel.h>

#include "ksz_common.h"
#include "ksz_ptp.h"
#include "ksz_ptp_reg.h"

#define ptp_caps_to_data(d) container_of((d), struct ksz_ptp_data, caps)
#define ptp_data_to_ksz_dev(d) container_of((d), struct ksz_device, ptp_data)

/* Sub-nanoseconds-adj,max * sub-nanoseconds / 40ns * 1ns
* = (2^30-1) * (2 ^ 32) / 40 ns * 1 ns = 6249999
*/
#define KSZ_MAX_DRIFT_CORR 6249999

#define KSZ_PTP_INC_NS 40ULL /* HW clock is incremented every 40 ns (by 40) */
#define KSZ_PTP_SUBNS_BITS 32

static int _ksz_ptp_gettime(struct ksz_device *dev, struct timespec64 *ts)
{
u32 nanoseconds;
u32 seconds;
u8 phase;
int ret;

/* Copy current PTP clock into shadow registers and read */
ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_READ_TIME, PTP_READ_TIME);
if (ret)
return ret;

ret = ksz_read8(dev, REG_PTP_RTC_SUB_NANOSEC__2, &phase);
if (ret)
return ret;

ret = ksz_read32(dev, REG_PTP_RTC_NANOSEC, &nanoseconds);
if (ret)
return ret;

ret = ksz_read32(dev, REG_PTP_RTC_SEC, &seconds);
if (ret)
return ret;

ts->tv_sec = seconds;
ts->tv_nsec = nanoseconds + phase * 8;

return 0;
}

static int ksz_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
int ret;

mutex_lock(&ptp_data->lock);
ret = _ksz_ptp_gettime(dev, ts);
mutex_unlock(&ptp_data->lock);

return ret;
}

static int ksz_ptp_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
int ret;

mutex_lock(&ptp_data->lock);

/* Write to shadow registers and Load PTP clock */
ret = ksz_write16(dev, REG_PTP_RTC_SUB_NANOSEC__2, PTP_RTC_0NS);
if (ret)
goto unlock;

ret = ksz_write32(dev, REG_PTP_RTC_NANOSEC, ts->tv_nsec);
if (ret)
goto unlock;

ret = ksz_write32(dev, REG_PTP_RTC_SEC, ts->tv_sec);
if (ret)
goto unlock;

ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_LOAD_TIME, PTP_LOAD_TIME);

unlock:
mutex_unlock(&ptp_data->lock);

return ret;
}

static int ksz_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
u64 base, adj;
bool negative;
u32 data32;
int ret;

mutex_lock(&ptp_data->lock);

if (scaled_ppm) {
base = KSZ_PTP_INC_NS << KSZ_PTP_SUBNS_BITS;
negative = diff_by_scaled_ppm(base, scaled_ppm, &adj);

data32 = (u32)adj;
data32 &= PTP_SUBNANOSEC_M;
if (!negative)
data32 |= PTP_RATE_DIR;

ret = ksz_write32(dev, REG_PTP_SUBNANOSEC_RATE, data32);
if (ret)
goto unlock;

ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_CLK_ADJ_ENABLE,
PTP_CLK_ADJ_ENABLE);
if (ret)
goto unlock;
} else {
ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_CLK_ADJ_ENABLE, 0);
if (ret)
goto unlock;
}

unlock:
mutex_unlock(&ptp_data->lock);
return ret;
}

static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data);
s32 sec, nsec;
u16 data16;
int ret;

mutex_lock(&ptp_data->lock);

/* do not use ns_to_timespec64(),
* both sec and nsec are subtracted by hw
*/
sec = div_s64_rem(delta, NSEC_PER_SEC, &nsec);

ret = ksz_write32(dev, REG_PTP_RTC_NANOSEC, abs(nsec));
if (ret)
goto unlock;

ret = ksz_write32(dev, REG_PTP_RTC_SEC, abs(sec));
if (ret)
goto unlock;

ret = ksz_read16(dev, REG_PTP_CLK_CTRL, &data16);
if (ret)
goto unlock;

data16 |= PTP_STEP_ADJ;

/* PTP_STEP_DIR -- 0: subtract, 1: add */
if (delta < 0)
data16 &= ~PTP_STEP_DIR;
else
data16 |= PTP_STEP_DIR;

ret = ksz_write16(dev, REG_PTP_CLK_CTRL, data16);

unlock:
mutex_unlock(&ptp_data->lock);
return ret;
}

static int ksz_ptp_start_clock(struct ksz_device *dev)
{
return ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_CLK_ENABLE, PTP_CLK_ENABLE);
}

int ksz_ptp_clock_register(struct dsa_switch *ds)
{
struct ksz_device *dev = ds->priv;
struct ksz_ptp_data *ptp_data;
int ret;

ptp_data = &dev->ptp_data;
mutex_init(&ptp_data->lock);

ptp_data->caps.owner = THIS_MODULE;
snprintf(ptp_data->caps.name, 16, "Microchip Clock");
ptp_data->caps.max_adj = KSZ_MAX_DRIFT_CORR;
ptp_data->caps.gettime64 = ksz_ptp_gettime;
ptp_data->caps.settime64 = ksz_ptp_settime;
ptp_data->caps.adjfine = ksz_ptp_adjfine;
ptp_data->caps.adjtime = ksz_ptp_adjtime;

ret = ksz_ptp_start_clock(dev);
if (ret)
return ret;

/* Currently only P2P mode is supported. When 802_1AS bit is set, it
* forwards all PTP packets to host port and none to other ports.
*/
ret = ksz_rmw16(dev, REG_PTP_MSG_CONF1, PTP_TC_P2P | PTP_802_1AS,
PTP_TC_P2P | PTP_802_1AS);
if (ret)
return ret;

ptp_data->clock = ptp_clock_register(&ptp_data->caps, dev->dev);
if (IS_ERR_OR_NULL(ptp_data->clock))
return PTR_ERR(ptp_data->clock);

return 0;
}

void ksz_ptp_clock_unregister(struct dsa_switch *ds)
{
struct ksz_device *dev = ds->priv;
struct ksz_ptp_data *ptp_data;

ptp_data = &dev->ptp_data;

if (ptp_data->clock)
ptp_clock_unregister(ptp_data->clock);
}

MODULE_AUTHOR("Christian Eggers <[email protected]>");
MODULE_AUTHOR("Arun Ramadoss <[email protected]>");
MODULE_DESCRIPTION("PTP support for KSZ switch");
MODULE_LICENSE("GPL");
Loading

0 comments on commit eac1ea2

Please sign in to comment.