// SPDX-License-Identifier: GPL-2.0+
/*
* PTP hardware clock driver for the FemtoClock3 family of timing and
* synchronization devices.
*
* Copyright (C) 2023 Integrated Device Technology, Inc., a Renesas Company.
*/
#include <linux/firmware.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/timekeeping.h>
#include <linux/string.h>
#include <linux/of.h>
#include <linux/bitfield.h>
#include <linux/mfd/rsmu.h>
#include <linux/mfd/idtRC38xxx_reg.h>
#include <asm/unaligned.h>
#include "ptp_private.h"
#include "ptp_fc3.h"
MODULE_DESCRIPTION("Driver for IDT FemtoClock3(TM) family");
MODULE_AUTHOR("IDT support-1588 <IDT-support-1588@lm.renesas.com>");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");
/*
* The name of the firmware file to be loaded
* over-rides any automatic selection
*/
static char *firmware;
module_param(firmware, charp, 0);
static s64 ns2counters(struct idtfc3 *idtfc3, s64 nsec, u32 *sub_ns)
{
s64 sync;
s32 rem;
if (likely(nsec >= 0)) {
sync = div_u64_rem(nsec, idtfc3->ns_per_sync, &rem);
*sub_ns = rem;
} else {
sync = -div_u64_rem(-nsec - 1, idtfc3->ns_per_sync, &rem) - 1;
*sub_ns = idtfc3->ns_per_sync - rem - 1;
}
return sync * idtfc3->ns_per_sync;
}
static s64 tdc_meas2offset(struct idtfc3 *idtfc3, u64 meas_read)
{
s64 coarse, fine;
fine = sign_extend64(FIELD_GET(FINE_MEAS_MASK, meas_read), 12);
coarse = sign_extend64(FIELD_GET(COARSE_MEAS_MASK, meas_read), (39 - 13));
fine = div64_s64(fine * NSEC_PER_SEC, idtfc3->tdc_apll_freq * 62LL);
coarse = div64_s64(coarse * NSEC_PER_SEC, idtfc3->time_ref_freq);
return coarse + fine;
}
static s64 tdc_offset2phase(struct idtfc3 *idtfc3, s64 offset_ns)
{
if (offset_ns > idtfc3->ns_per_sync / 2)
offset_ns -= idtfc3->ns_per_sync;
return offset_ns * idtfc3->tdc_offset_sign;
}
static int idtfc3_set_lpf_mode(struct idtfc3 *idtfc3, u8 mode)
{
int err;
if (mode >= LPF_INVALID)
return -EINVAL;
if (idtfc3->lpf_mode == mode)
return 0;
err = regmap_bulk_write(idtfc3->regmap, LPF_MODE_CNFG, &mode, sizeof(mode));
if (err)
return err;
idtfc3->lpf_mode = mode;
return 0;
}
static int idtfc3_enable_lpf(struct idtfc3 *idtfc3, bool enable)
{
u8 val;
int err;
err = regmap_bulk_read(idtfc3->regmap, LPF_CTRL, &val, sizeof(val));
if (err)
return err;
if (enable == true)
val |= LPF_EN;
else
val &= ~LPF_EN;
return regmap_bulk_write(idtfc3->regmap, LPF_CTRL, &val, sizeof(val));
}
static int idtfc3_get_time_ref_freq(struct idtfc3 *idtfc3)
{
int err;
u8 buf[4];
u8 time_ref_div;
u8 time_clk_div;
err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_MEAS_DIV_CNFG, buf, sizeof(buf));
if (err)
return err;
time_ref_div = FIELD_GET(TIME_REF_DIV_MASK, get_unaligned_le32(buf)) + 1;
err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_COUNT, buf, 1);
if (err)
return err;
time_clk_div = (buf[0] & TIME_CLOCK_COUNT_MASK) + 1;
idtfc3->time_ref_freq = idtfc3->hw_param.time_clk_freq *
time_clk_div / time_ref_div;
return 0;
}
static int idtfc3_get_tdc_offset_sign(struct idtfc3 *idtfc3)
{
int err;
u8 buf[4];
u32 val;
u8 sig1, sig2;
err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_TDC_FANOUT_CNFG, buf, sizeof(buf));
if (err)
return err;
val = get_unaligned_le32(buf);
if ((val & TIME_SYNC_TO_TDC_EN) != TIME_SYNC_TO_TDC_EN) {
dev_err(idtfc3->dev, "TIME_SYNC_TO_TDC_EN is off !!!");
return -EINVAL;
}
sig1 = FIELD_GET(SIG1_MUX_SEL_MASK, val);
sig2 = FIELD_GET(SIG2_MUX_SEL_MASK, val);
if ((sig1 == sig2) || ((sig1 != TIME_SYNC) && (sig2 != TIME_SYNC))) {
dev_err(idtfc3->dev, "Invalid tdc_mux_sel sig1=%d sig2=%d", sig1, sig2);
return -EINVAL;
} else if (sig1 == TIME_SYNC) {
idtfc3->tdc_offset_sign = 1;
} else if (sig2 == TIME_SYNC) {
idtfc3->tdc_offset_sign = -1;
}
return 0;
}
static int idtfc3_lpf_bw(struct idtfc3 *idtfc3, u8 shift, u8 mult)
{
u8 val = FIELD_PREP(LPF_BW_SHIFT, shift) | FIELD_PREP(LPF_BW_MULT, mult);
return regmap_bulk_write(idtfc3->regmap, LPF_BW_CNFG, &val, sizeof(val));
}
static int