forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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: 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
1 parent
72863e0
commit eac1ea2
Showing
7 changed files
with
374 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
Oops, something went wrong.