Commit eac1ea20 authored by Christian Eggers's avatar Christian Eggers Committed by David S. Miller
Browse files

net: dsa: microchip: ptp: add the posix clock support



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: default avatarChristian Eggers <ceggers@arri.de>
Co-developed-by: default avatarArun Ramadoss <arun.ramadoss@microchip.com>
Signed-off-by: default avatarArun Ramadoss <arun.ramadoss@microchip.com>
Reviewed-by: Vladimir Oltean <olteanv@gmail.com> # mostly api
Reviewed-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Reviewed-by: default avatarJacob Keller <jacob.e.keller@intel.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 72863e08
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ 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.
@@ -18,10 +19,20 @@ config NET_DSA_MICROCHIP_KSZ9477_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
+5 −0
Original line number Diff line number Diff line
@@ -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
+12 −1
Original line number Diff line number Diff line
@@ -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"
@@ -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 */
@@ -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)
@@ -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);
+16 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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 */
@@ -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];
+236 −0
Original line number Diff line number Diff line
// 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 <ceggers@arri.de>");
MODULE_AUTHOR("Arun Ramadoss <arun.ramadoss@microchip.com>");
MODULE_DESCRIPTION("PTP support for KSZ switch");
MODULE_LICENSE("GPL");
Loading