mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
Add low-level DPLL channel operations for ToD read/write/adjust, output phase step, delta frequency offset write and TIE (Time Interval Error) write. These serve as building blocks for the PTP clock callbacks added in the next patch. ToD operations use a wait-before-write pattern to avoid blocking after each operation. The tod_ready_wait helper selects the poll timeout based on the current ToD command - write operations use a longer timeout (1000 ms) than reads (30 ms). The ToD read captures system timestamps (ptp_system_timestamp) around the HW command and completion poll to support cross-timestamping. The TIE write operation provides sub-picosecond resolution phase adjustment for modes where the DPLL is tracking a reference (AUTO and REFLOCK). Add output step-time mask to struct zl3073x_dev and zl3073x_dev_out_is_stepped() helper to check if an output participates in step-time operations. Reviewed-by: Petr Oros <poros@redhat.com> Tested-by: Chris du Quesnay <Chris.duQuesnay@microchip.com> Signed-off-by: Ivan Vecera <ivecera@redhat.com> Link: https://patch.msgid.link/20260814082656.306534-3-ivecera@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
450 lines
12 KiB
C
450 lines
12 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
|
|
#ifndef _ZL3073X_CORE_H
|
|
#define _ZL3073X_CORE_H
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/time64.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "chan.h"
|
|
#include "out.h"
|
|
#include "ref.h"
|
|
#include "regs.h"
|
|
#include "synth.h"
|
|
|
|
struct device;
|
|
struct regmap;
|
|
struct zl3073x_dpll;
|
|
|
|
/* Per-operation poll timeouts */
|
|
#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC)
|
|
#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC)
|
|
#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC)
|
|
#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC)
|
|
#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC)
|
|
#define ZL_POLL_PHASE_STEP_TIMEOUT_US (3000 * USEC_PER_MSEC)
|
|
#define ZL_POLL_TIE_WR_TIMEOUT_US (1000 * USEC_PER_MSEC)
|
|
#define ZL_POLL_TOD_RD_TIMEOUT_US (30 * USEC_PER_MSEC)
|
|
#define ZL_POLL_TOD_WR_TIMEOUT_US (1000 * USEC_PER_MSEC)
|
|
|
|
enum zl3073x_flags {
|
|
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
|
|
ZL3073X_FLAG_DIE_TEMP_BIT,
|
|
ZL3073X_FLAGS_NBITS /* must be last */
|
|
};
|
|
|
|
#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT)
|
|
#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32)
|
|
#define ZL3073X_FLAG_DIE_TEMP __ZL3073X_FLAG(DIE_TEMP)
|
|
|
|
/**
|
|
* struct zl3073x_chip_info - chip variant identification
|
|
* @id: chip ID
|
|
* @num_channels: number of DPLL channels supported by this variant
|
|
* @flags: chip variant flags
|
|
*/
|
|
struct zl3073x_chip_info {
|
|
u16 id;
|
|
u8 num_channels;
|
|
unsigned long flags;
|
|
};
|
|
|
|
/**
|
|
* struct zl3073x_dev - zl3073x device
|
|
* @dev: pointer to device
|
|
* @regmap: regmap to access device registers
|
|
* @info: detected chip info
|
|
* @multiop_lock: to serialize multiple register operations
|
|
* @tie_lock: to serialize TIE write operations
|
|
* @phase_step_lock: to serialize output phase step operations
|
|
* @ref: array of input references' invariants
|
|
* @out: array of outs' invariants
|
|
* @synth: array of synths' invariants
|
|
* @chan: array of DPLL channels' state
|
|
* @dplls: list of DPLLs
|
|
* @kworker: thread for periodic work
|
|
* @work: periodic work
|
|
* @clock_id: clock id of the device
|
|
* @out_step_time_mask: output step-time mask (device-global)
|
|
* @phase_avg_factor: phase offset measurement averaging factor
|
|
* @freq_monitor: is frequency monitor enabled
|
|
*/
|
|
struct zl3073x_dev {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
const struct zl3073x_chip_info *info;
|
|
struct mutex multiop_lock;
|
|
struct mutex tie_lock;
|
|
struct mutex phase_step_lock;
|
|
|
|
/* Invariants */
|
|
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
|
|
struct zl3073x_out out[ZL3073X_NUM_OUTS];
|
|
struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS];
|
|
struct zl3073x_chan chan[ZL3073X_MAX_CHANNELS];
|
|
|
|
/* DPLL channels */
|
|
struct list_head dplls;
|
|
|
|
/* Monitor */
|
|
struct kthread_worker *kworker;
|
|
struct kthread_delayed_work work;
|
|
|
|
/* Per-chip parameters */
|
|
u64 clock_id;
|
|
u16 out_step_time_mask;
|
|
u8 phase_avg_factor;
|
|
bool freq_monitor;
|
|
};
|
|
|
|
extern const struct regmap_config zl3073x_regmap_config;
|
|
|
|
struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev);
|
|
int zl3073x_dev_probe(struct zl3073x_dev *zldev);
|
|
|
|
int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full);
|
|
void zl3073x_dev_stop(struct zl3073x_dev *zldev);
|
|
|
|
static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
|
|
{
|
|
return READ_ONCE(zldev->phase_avg_factor);
|
|
}
|
|
|
|
int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
|
|
|
|
/**********************
|
|
* Registers operations
|
|
**********************/
|
|
|
|
/**
|
|
* struct zl3073x_hwreg_seq_item - HW register write sequence item
|
|
* @addr: HW register to be written
|
|
* @value: value to be written to HW register
|
|
* @mask: bitmask indicating bits to be updated
|
|
* @wait: number of ms to wait after register write
|
|
*/
|
|
struct zl3073x_hwreg_seq_item {
|
|
u32 addr;
|
|
u32 value;
|
|
u32 mask;
|
|
u32 wait;
|
|
};
|
|
|
|
#define HWREG_SEQ_ITEM(_addr, _value, _mask, _wait) \
|
|
{ \
|
|
.addr = _addr, \
|
|
.value = FIELD_PREP_CONST(_mask, _value), \
|
|
.mask = _mask, \
|
|
.wait = _wait, \
|
|
}
|
|
|
|
int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
|
|
unsigned int mask_reg, u16 mask_val);
|
|
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
|
|
u8 mask, unsigned int timeout_us);
|
|
int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);
|
|
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);
|
|
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);
|
|
int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val);
|
|
int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val);
|
|
int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val);
|
|
int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val);
|
|
int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val);
|
|
int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value);
|
|
int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value);
|
|
int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value,
|
|
u32 mask);
|
|
int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev,
|
|
const struct zl3073x_hwreg_seq_item *seq,
|
|
size_t num_items);
|
|
|
|
/*****************
|
|
* Misc operations
|
|
*****************/
|
|
|
|
int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
|
|
|
|
/**
|
|
* zl3073x_dev_is_ref_phase_comp_32bit - check ref phase comp register size
|
|
* @zldev: pointer to zl3073x device
|
|
*
|
|
* Some chip IDs have a 32-bit wide ref_phase_offset_comp register instead
|
|
* of the default 48-bit.
|
|
*
|
|
* Return: true if the register is 32-bit, false if 48-bit
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev)
|
|
{
|
|
return zldev->info->flags & ZL3073X_FLAG_REF_PHASE_COMP_32;
|
|
}
|
|
|
|
static inline bool
|
|
zl3073x_is_n_pin(u8 id)
|
|
{
|
|
/* P-pins ids are even while N-pins are odd */
|
|
return id & 1;
|
|
}
|
|
|
|
static inline bool
|
|
zl3073x_is_p_pin(u8 id)
|
|
{
|
|
return !zl3073x_is_n_pin(id);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_input_pin_ref_get - get reference for given input pin
|
|
* @id: input pin id
|
|
*
|
|
* Return: reference id for the given input pin
|
|
*/
|
|
static inline u8
|
|
zl3073x_input_pin_ref_get(u8 id)
|
|
{
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_output_pin_out_get - get output for the given output pin
|
|
* @id: output pin id
|
|
*
|
|
* Return: output id for the given output pin
|
|
*/
|
|
static inline u8
|
|
zl3073x_output_pin_out_get(u8 id)
|
|
{
|
|
/* Output pin pair shares the single output */
|
|
return id / 2;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_ref_freq_get - get input reference frequency
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: input reference index
|
|
*
|
|
* Return: frequency of given input reference
|
|
*/
|
|
static inline u32
|
|
zl3073x_dev_ref_freq_get(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index);
|
|
|
|
return zl3073x_ref_freq_get(ref);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_ref_is_diff - check if the given input reference is differential
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: input reference index
|
|
*
|
|
* Return: true if reference is differential, false if reference is single-ended
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_ref_is_diff(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index);
|
|
|
|
return zl3073x_ref_is_diff(ref);
|
|
}
|
|
|
|
/*
|
|
* zl3073x_dev_ref_is_status_ok - check the given input reference status
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: input reference index
|
|
*
|
|
* Return: true if the status is ok, false otherwise
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_ref_is_status_ok(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_ref *ref = zl3073x_ref_state_get(zldev, index);
|
|
|
|
return zl3073x_ref_is_status_ok(ref);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_synth_freq_get - get synth current freq
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: synth index
|
|
*
|
|
* Return: frequency of given synthetizer
|
|
*/
|
|
static inline u32
|
|
zl3073x_dev_synth_freq_get(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_synth *synth;
|
|
|
|
synth = zl3073x_synth_state_get(zldev, index);
|
|
return zl3073x_synth_freq_get(synth);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_out_synth_get - get synth connected to given output
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: output index
|
|
*
|
|
* Return: index of synth connected to given output.
|
|
*/
|
|
static inline u8
|
|
zl3073x_dev_out_synth_get(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
|
|
|
|
return zl3073x_out_synth_get(out);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_out_is_enabled - check if the given output is enabled
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: output index
|
|
*
|
|
* Return: true if the output is enabled, false otherwise
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_out_is_enabled(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
|
|
const struct zl3073x_synth *synth;
|
|
u8 synth_id;
|
|
|
|
/* Output is enabled only if associated synth is enabled */
|
|
synth_id = zl3073x_out_synth_get(out);
|
|
synth = zl3073x_synth_state_get(zldev, synth_id);
|
|
|
|
return zl3073x_synth_is_enabled(synth) && zl3073x_out_is_enabled(out);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_out_is_stepped - check if output is in step-time mask
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: output index
|
|
*
|
|
* Return: true if output is affected by step-time operations
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_out_is_stepped(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
return !!(zldev->out_step_time_mask & BIT(index));
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_out_dpll_get - get DPLL ID the output is driven by
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: output index
|
|
*
|
|
* Return: ID of DPLL the given output is driven by
|
|
*/
|
|
static inline
|
|
u8 zl3073x_dev_out_dpll_get(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
|
|
const struct zl3073x_synth *synth;
|
|
u8 synth_id;
|
|
|
|
/* Get synthesizer connected to given output */
|
|
synth_id = zl3073x_out_synth_get(out);
|
|
synth = zl3073x_synth_state_get(zldev, synth_id);
|
|
|
|
/* Return DPLL that drives the synth */
|
|
return zl3073x_synth_dpll_get(synth);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_output_pin_freq_get - get output pin frequency
|
|
* @zldev: pointer to zl3073x device
|
|
* @id: output pin id
|
|
*
|
|
* Computes the output pin frequency based on the synth frequency, output
|
|
* divisor, and signal format. For N-div formats, N-pin frequency is
|
|
* additionally divided by esync_n_period.
|
|
*
|
|
* Return: frequency of the given output pin in Hz
|
|
*/
|
|
static inline u32
|
|
zl3073x_dev_output_pin_freq_get(struct zl3073x_dev *zldev, u8 id)
|
|
{
|
|
const struct zl3073x_synth *synth;
|
|
const struct zl3073x_out *out;
|
|
u8 out_id;
|
|
u32 freq;
|
|
|
|
out_id = zl3073x_output_pin_out_get(id);
|
|
out = zl3073x_out_state_get(zldev, out_id);
|
|
synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out));
|
|
freq = zl3073x_synth_freq_get(synth) / out->div;
|
|
|
|
if (zl3073x_out_is_ndiv(out) && zl3073x_is_n_pin(id))
|
|
freq /= out->esync_n_period;
|
|
|
|
return freq;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_out_is_diff - check if the given output is differential
|
|
* @zldev: pointer to zl3073x device
|
|
* @index: output index
|
|
*
|
|
* Return: true if output is differential, false if output is single-ended
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_out_is_diff(struct zl3073x_dev *zldev, u8 index)
|
|
{
|
|
const struct zl3073x_out *out = zl3073x_out_state_get(zldev, index);
|
|
|
|
return zl3073x_out_is_diff(out);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_output_pin_is_enabled - check if the given output pin is enabled
|
|
* @zldev: pointer to zl3073x device
|
|
* @id: output pin id
|
|
*
|
|
* Checks if the output of the given output pin is enabled and also that
|
|
* its signal format also enables the given pin.
|
|
*
|
|
* Return: true if output pin is enabled, false if output pin is disabled
|
|
*/
|
|
static inline bool
|
|
zl3073x_dev_output_pin_is_enabled(struct zl3073x_dev *zldev, u8 id)
|
|
{
|
|
u8 out_id = zl3073x_output_pin_out_get(id);
|
|
const struct zl3073x_out *out;
|
|
|
|
out = zl3073x_out_state_get(zldev, out_id);
|
|
|
|
/* Check if the output is enabled - call _dev_ helper that
|
|
* additionally checks for attached synth enablement.
|
|
*/
|
|
if (!zl3073x_dev_out_is_enabled(zldev, out_id))
|
|
return false;
|
|
|
|
/* Check signal format */
|
|
switch (zl3073x_out_signal_format_get(out)) {
|
|
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_DISABLED:
|
|
/* Both output pins are disabled by signal format */
|
|
return false;
|
|
|
|
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1P:
|
|
/* Output is one single ended P-pin output */
|
|
if (zl3073x_is_n_pin(id))
|
|
return false;
|
|
break;
|
|
case ZL_OUTPUT_MODE_SIGNAL_FORMAT_1N:
|
|
/* Output is one single ended N-pin output */
|
|
if (zl3073x_is_p_pin(id))
|
|
return false;
|
|
break;
|
|
default:
|
|
/* For other format both pins are enabled */
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif /* _ZL3073X_CORE_H */
|