* [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support
@ 2026-05-13 14:56 Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 1/2] dpll: add DPLL_PIN_TYPE_INT_NCO pin type Ivan Vecera
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Ivan Vecera @ 2026-05-13 14:56 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
Add a new DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing
the NCO mode of a DPLL and implement support for it in the zl3073x driver.
Patch 1 adds the new pin type to the DPLL netlink spec and UAPI header.
Patch 2 adds a virtual NCO input pin to the zl3073x driver that allows
userspace to switch a DPLL channel into NCO mode. The pin reports
connected/active state when the channel is in NCO mode and handles
the hardware-specific details of mode transitions including automatic
df_offset capture and 1PPS phase preservation.
Ivan Vecera (2):
dpll: add DPLL_PIN_TYPE_INT_NCO pin type
dpll: zl3073x: add NCO virtual input pin
Documentation/netlink/specs/dpll.yaml | 12 ++
drivers/dpll/dpll_nl.c | 2 +-
drivers/dpll/zl3073x/chan.c | 14 +-
drivers/dpll/zl3073x/chan.h | 24 +++
drivers/dpll/zl3073x/core.c | 7 +
drivers/dpll/zl3073x/dpll.c | 259 ++++++++++++++++++++++----
drivers/dpll/zl3073x/dpll.h | 2 +
drivers/dpll/zl3073x/regs.h | 7 +
include/uapi/linux/dpll.h | 4 +
9 files changed, 291 insertions(+), 40 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net-next 1/2] dpll: add DPLL_PIN_TYPE_INT_NCO pin type
2026-05-13 14:56 [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
@ 2026-05-13 14:56 ` Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 2/2] dpll: zl3073x: add NCO virtual input pin Ivan Vecera
2026-05-15 20:56 ` [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
2 siblings, 0 replies; 4+ messages in thread
From: Ivan Vecera @ 2026-05-13 14:56 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
Add DPLL_PIN_TYPE_INT_NCO pin type for device internal numerically
controlled oscillators. When connected as a DPLL input, the DPLL
operates in NCO mode where the output frequency is controlled by
the host.
Update the fractional-frequency-offset-ppt attribute documentation
to note that for INT_NCO pins this attribute represents the DPLL's
delta frequency offset.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Documentation/netlink/specs/dpll.yaml | 12 ++++++++++++
drivers/dpll/dpll_nl.c | 2 +-
include/uapi/linux/dpll.h | 4 ++++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index 91a172617b3a9..159de8ad8b00b 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -162,6 +162,12 @@ definitions:
-
name: gnss
doc: GNSS recovered clock
+ -
+ name: int-nco
+ doc: |
+ Device internal numerically controlled oscillator.
+ When connected as a DPLL input, the DPLL operates in NCO mode
+ where output frequency is controlled by the host.
render-max: true
-
type: enum
@@ -453,6 +459,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
+ For pins of type PIN_TYPE_INT_NCO this attribute represents
+ the DPLL's current delta frequency offset when the NCO pin
+ is connected.
Value is in PPM (parts per million).
This is a lower-precision version of
fractional-frequency-offset-ppt.
@@ -499,6 +508,9 @@ attribute-sets:
offset on the media associated with the pin. Inside
the pin-parent-device nest it represents the frequency
offset between the pin and its parent DPLL device.
+ For pins of type PIN_TYPE_INT_NCO this attribute represents
+ the DPLL's current delta frequency offset when the NCO pin
+ is connected.
Value is in PPT (parts per trillion, 10^-12).
This is a higher-precision version of
fractional-frequency-offset.
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index b1d9182c7802f..2dab99202764f 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] =
[DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, },
[DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, },
- [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5),
+ [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6),
};
/* DPLL_CMD_PIN_GET - do */
diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
index cb363cccf2e2a..dab7ffbde11b7 100644
--- a/include/uapi/linux/dpll.h
+++ b/include/uapi/linux/dpll.h
@@ -127,6 +127,9 @@ enum dpll_type {
* @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock
* @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator
* @DPLL_PIN_TYPE_GNSS: GNSS recovered clock
+ * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator.
+ * When connected as a DPLL input, the DPLL operates in NCO mode where output
+ * frequency is controlled by the host.
*/
enum dpll_pin_type {
DPLL_PIN_TYPE_MUX = 1,
@@ -134,6 +137,7 @@ enum dpll_pin_type {
DPLL_PIN_TYPE_SYNCE_ETH_PORT,
DPLL_PIN_TYPE_INT_OSCILLATOR,
DPLL_PIN_TYPE_GNSS,
+ DPLL_PIN_TYPE_INT_NCO,
/* private: */
__DPLL_PIN_TYPE_MAX,
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net-next 2/2] dpll: zl3073x: add NCO virtual input pin
2026-05-13 14:56 [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 1/2] dpll: add DPLL_PIN_TYPE_INT_NCO pin type Ivan Vecera
@ 2026-05-13 14:56 ` Ivan Vecera
2026-05-15 20:56 ` [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
2 siblings, 0 replies; 4+ messages in thread
From: Ivan Vecera @ 2026-05-13 14:56 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
Add a virtual NCO (Numerically Controlled Oscillator) input pin that
lets userspace switch a DPLL channel into NCO mode. The NCO pin is
registered with the new DPLL_PIN_TYPE_INT_NCO type and reports
DPLL_PIN_STATE_CONNECTED / ACTIVE when the channel is in NCO mode.
When entering NCO mode the nco_auto_read bit in dpll_ctrl_x causes the
hardware to automatically capture the current tracking offset into
df_offset. This is refreshed immediately after the mode switch so
userspace sees an up-to-date FFO value.
When leaving NCO mode set tod_step_reset and tie_clear in dpll_ctrl_x
to preserve the output 1PPS phase. PPS DPLLs set tie_clear=1 to
re-align to the new reference; EEC DPLLs set tie_clear=0 to prevent
an unwanted TIE write.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/chan.c | 14 +-
drivers/dpll/zl3073x/chan.h | 24 ++++
drivers/dpll/zl3073x/core.c | 7 +
drivers/dpll/zl3073x/dpll.c | 259 ++++++++++++++++++++++++++++++------
drivers/dpll/zl3073x/dpll.h | 2 +
drivers/dpll/zl3073x/regs.h | 7 +
6 files changed, 274 insertions(+), 39 deletions(-)
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2fe3c3da84bb5..70db3dbfc4b11 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -71,6 +71,10 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
struct zl3073x_chan *chan = &zldev->chan[index];
int rc, i;
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl);
+ if (rc)
+ return rc;
+
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
&chan->mode_refsel);
if (rc)
@@ -145,7 +149,15 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
- /* Direct register write for mode_refsel */
+ /* Direct register writes for ctrl and mode_refsel */
+ if (dchan->ctrl != chan->ctrl) {
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index),
+ chan->ctrl);
+ if (rc)
+ return rc;
+ dchan->ctrl = chan->ctrl;
+ }
+
if (dchan->mode_refsel != chan->mode_refsel) {
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
chan->mode_refsel);
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 4353809c69122..aa89728433710 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -13,6 +13,7 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
+ * @ctrl: DPLL control register value
* @mode_refsel: mode and reference selection register value
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
@@ -21,6 +22,7 @@ struct zl3073x_dev;
*/
struct zl3073x_chan {
struct_group(cfg,
+ u8 ctrl;
u8 mode_refsel;
u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
@@ -152,6 +154,28 @@ static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status);
}
+/**
+ * zl3073x_chan_mode_is_auto - check if channel is in automatic mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in automatic mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_auto(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO;
+}
+
+/**
+ * zl3073x_chan_mode_is_nco - check if channel is in NCO mode
+ * @chan: pointer to channel state
+ *
+ * Return: true if channel is in NCO mode, false otherwise
+ */
+static inline bool zl3073x_chan_mode_is_nco(const struct zl3073x_chan *chan)
+{
+ return zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO;
+}
+
/**
* zl3073x_chan_is_ho_ready - check if holdover is ready
* @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index b3345060490db..1326b431c7bb4 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -572,6 +572,13 @@ zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev)
int i, rc;
for (i = 0; i < zldev->info->num_channels; i++) {
+ const struct zl3073x_chan *chan;
+
+ /* Skip channels in NCO mode - no reference tracking */
+ chan = zl3073x_chan_state_get(zldev, i);
+ if (zl3073x_chan_mode_is_nco(chan))
+ continue;
+
rc = zl3073x_chan_state_update(zldev, i);
if (rc)
dev_warn(zldev->dev,
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index cff85cdb9d0e5..0407c072b604c 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -81,6 +81,18 @@ zl3073x_dpll_is_input_pin(struct zl3073x_dpll_pin *pin)
return pin->dir == DPLL_PIN_DIRECTION_INPUT;
}
+/**
+ * zl3073x_dpll_is_nco_pin - check if the pin is a virtual NCO pin
+ * @pin: pin to check
+ *
+ * Return: true if pin is a virtual NCO pin, false otherwise.
+ */
+static bool
+zl3073x_dpll_is_nco_pin(struct zl3073x_dpll_pin *pin)
+{
+ return pin->id == ZL3073X_NCO_PIN_ID;
+}
+
/**
* zl3073x_dpll_is_p_pin - check if the pin is P-pin
* @pin: pin to check
@@ -634,6 +646,18 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
goto invalid_state;
}
break;
+ case ZL_DPLL_MODE_REFSEL_MODE_NCO:
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ /* Preserve output phase when leaving NCO:
+ * PPS needs tie_clear=1 to re-align to the new ref,
+ * EEC needs tie_clear=0 to prevent an unwanted TIE
+ * write.
+ */
+ FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1);
+ FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl,
+ zldpll->type == DPLL_TYPE_PPS ? 1 : 0);
+ }
+ fallthrough;
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
if (state == DPLL_PIN_STATE_CONNECTED) {
@@ -989,6 +1013,129 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
return 0;
}
+static int
+zl3073x_dpll_nco_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_operstate *operstate,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (zl3073x_chan_mode_is_nco(chan))
+ *operstate = DPLL_PIN_OPERSTATE_ACTIVE;
+ else
+ *operstate = DPLL_PIN_OPERSTATE_STANDBY;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_nco_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (zl3073x_chan_mode_is_nco(chan))
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_nco_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_chan chan;
+ int rc;
+
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+
+ switch (state) {
+ case DPLL_PIN_STATE_CONNECTED:
+ /* Already in NCO mode, nothing to do */
+ if (zl3073x_chan_mode_is_nco(&chan))
+ return 0;
+ /* NCO is only allowed in manual mode */
+ if (zl3073x_chan_mode_is_auto(&chan)) {
+ NL_SET_ERR_MSG(extack,
+ "NCO pin cannot be connected in automatic mode");
+ return -EINVAL;
+ }
+ /* Switch to NCO mode */
+ zl3073x_chan_mode_set(&chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
+ break;
+ case DPLL_PIN_STATE_DISCONNECTED:
+ /* Not in NCO mode, nothing to do */
+ if (!zl3073x_chan_mode_is_nco(&chan))
+ return 0;
+ /* Preserve output phase when leaving NCO:
+ * PPS needs tie_clear=1 to re-align to the new ref,
+ * EEC needs tie_clear=0 to prevent an unwanted TIE write.
+ */
+ FIELD_MODIFY(ZL_DPLL_CTRL_TOD_STEP_RST, &chan.ctrl, 1);
+ FIELD_MODIFY(ZL_DPLL_CTRL_TIE_CLEAR, &chan.ctrl,
+ zldpll->type == DPLL_TYPE_PPS ? 1 : 0);
+ /* Leave NCO, switch to freerun */
+ zl3073x_chan_mode_set(&chan, ZL_DPLL_MODE_REFSEL_MODE_FREERUN);
+ break;
+ default:
+ NL_SET_ERR_MSG(extack, "invalid pin state for NCO pin");
+ return -EINVAL;
+ }
+
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
+ if (rc)
+ return rc;
+
+ /* Refresh cached df_offset after entering NCO - nco_auto_read
+ * causes HW to capture the tracking offset.
+ */
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ rc = zl3073x_chan_state_update(zldpll->dev, zldpll->id);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_nco_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ if (!zl3073x_chan_mode_is_nco(chan))
+ return -ENODATA;
+
+ /* Convert df_offset (2^-48) to PPT: ppt = -df * 5^12 / 2^36 */
+ ffo->ffo = -mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
+ 244140625, 36);
+
+ return 0;
+}
+
static int
zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
s32 *temp, struct netlink_ext_ack *extack)
@@ -1057,19 +1204,7 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
void *dpll_priv, unsigned long *modes,
struct netlink_ext_ack *extack)
{
- struct zl3073x_dpll *zldpll = dpll_priv;
- const struct zl3073x_chan *chan;
-
- chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
-
- /* We support switching between automatic and manual mode, except in
- * a case where the DPLL channel is configured to run in NCO mode.
- * In this case, report only the manual mode to which the NCO is mapped
- * as the only supported one.
- */
- if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO)
- __set_bit(DPLL_MODE_AUTOMATIC, modes);
-
+ __set_bit(DPLL_MODE_AUTOMATIC, modes);
__set_bit(DPLL_MODE_MANUAL, modes);
return 0;
@@ -1310,6 +1445,15 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
.state_on_dpll_get = zl3073x_dpll_output_pin_state_on_dpll_get,
};
+static const struct dpll_pin_ops zl3073x_dpll_nco_pin_ops = {
+ .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
+ .direction_get = zl3073x_dpll_pin_direction_get,
+ .ffo_get = zl3073x_dpll_nco_pin_ffo_get,
+ .operstate_on_dpll_get = zl3073x_dpll_nco_pin_operstate_on_dpll_get,
+ .state_on_dpll_get = zl3073x_dpll_nco_pin_state_on_dpll_get,
+ .state_on_dpll_set = zl3073x_dpll_nco_pin_state_on_dpll_set,
+};
+
static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.lock_status_get = zl3073x_dpll_lock_status_get,
.mode_get = zl3073x_dpll_mode_get,
@@ -1457,7 +1601,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
WARN(!pin->dpll_pin, "DPLL pin is not registered\n");
- if (zl3073x_dpll_is_input_pin(pin))
+ if (zl3073x_dpll_is_nco_pin(pin))
+ ops = &zl3073x_dpll_nco_pin_ops;
+ else if (zl3073x_dpll_is_input_pin(pin))
ops = &zl3073x_dpll_input_pin_ops;
else
ops = &zl3073x_dpll_output_pin_ops;
@@ -1510,20 +1656,13 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
enum dpll_pin_direction dir, u8 index)
{
struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_chan *chan;
bool is_diff, is_enabled;
const char *name;
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
-
if (dir == DPLL_PIN_DIRECTION_INPUT) {
u8 ref_id = zl3073x_input_pin_ref_get(index);
const struct zl3073x_ref *ref;
- /* Skip the pin if the DPLL is running in NCO mode */
- if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO)
- return false;
-
name = "REF";
ref = zl3073x_ref_state_get(zldev, ref_id);
is_diff = zl3073x_ref_is_diff(ref);
@@ -1564,6 +1703,57 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
return true;
}
+static const struct dpll_pin_properties zl3073x_dpll_nco_pin_props = {
+ .type = DPLL_PIN_TYPE_INT_NCO,
+ .package_label = "NCO",
+ .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
+};
+
+static int
+zl3073x_dpll_nco_pin_register(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dpll_pin *pin;
+ struct zl3073x_chan chan;
+ int rc;
+
+ /* Enable automatic df_offset capture on NCO entry */
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ FIELD_MODIFY(ZL_DPLL_CTRL_NCO_AUTO_READ, &chan.ctrl, 1);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
+ if (rc)
+ return rc;
+
+ pin = zl3073x_dpll_pin_alloc(zldpll, DPLL_PIN_DIRECTION_INPUT,
+ ZL3073X_NCO_PIN_ID);
+ if (IS_ERR(pin))
+ return PTR_ERR(pin);
+
+ pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, ZL3073X_NCO_PIN_ID,
+ THIS_MODULE, &zl3073x_dpll_nco_pin_props,
+ &pin->tracker);
+ if (IS_ERR(pin->dpll_pin)) {
+ rc = PTR_ERR(pin->dpll_pin);
+ goto err_pin_get;
+ }
+
+ rc = dpll_pin_register(zldpll->dpll_dev, pin->dpll_pin,
+ &zl3073x_dpll_nco_pin_ops, pin);
+ if (rc)
+ goto err_register;
+
+ list_add(&pin->list, &zldpll->pins);
+
+ return 0;
+
+err_register:
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
+err_pin_get:
+ pin->dpll_pin = NULL;
+ kfree(pin);
+
+ return rc;
+}
+
/**
* zl3073x_dpll_pins_register - register all registerable DPLL pins
* @zldpll: pointer to zl3073x_dpll structure
@@ -1609,6 +1799,11 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll)
list_add(&pin->list, &zldpll->pins);
}
+ /* Register NCO virtual input pin */
+ rc = zl3073x_dpll_nco_pin_register(zldpll);
+ if (rc)
+ goto error;
+
return 0;
error:
@@ -1644,8 +1839,8 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
return rc;
}
- rc = dpll_device_register(zldpll->dpll_dev,
- zl3073x_prop_dpll_type_get(zldev, zldpll->id),
+ zldpll->type = zl3073x_prop_dpll_type_get(zldev, zldpll->id);
+ rc = dpll_device_register(zldpll->dpll_dev, zldpll->type,
&zldpll->ops, zldpll);
if (rc) {
dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
@@ -1815,10 +2010,8 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
struct zl3073x_dev *zldev = zldpll->dev;
enum dpll_lock_status lock_status;
struct device *dev = zldev->dev;
- const struct zl3073x_chan *chan;
struct zl3073x_dpll_pin *pin;
int rc;
- u8 mode;
zldpll->check_count++;
@@ -1837,15 +2030,6 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
dpll_device_change_ntf(zldpll->dpll_dev);
}
- /* Input pin monitoring does make sense only in automatic
- * or forced reference modes.
- */
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
- mode = zl3073x_chan_mode_get(chan);
- if (mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
- mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
- return;
-
/* Update phase offset latch registers for this DPLL if the phase
* offset monitor feature is enabled.
*/
@@ -1863,10 +2047,9 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
enum dpll_pin_operstate operstate;
bool pin_changed = false;
- /* Output pins change checks are not necessary because output
- * states are constant.
- */
- if (!zl3073x_dpll_is_input_pin(pin))
+ /* Only physical input pins need monitoring */
+ if (!zl3073x_dpll_is_input_pin(pin) ||
+ zl3073x_dpll_is_nco_pin(pin))
continue;
rc = zl3073x_dpll_ref_operstate_get(pin, &operstate);
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 434c32a7db123..0920e4d31ab35 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -19,6 +19,7 @@
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
+ * @type: DPLL type (PPS or EEC)
* @lock_status: last saved DPLL lock status
* @pins: list of pins
* @change_work: device change notification work
@@ -33,6 +34,7 @@ struct zl3073x_dpll {
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
+ enum dpll_type type;
enum dpll_lock_status lock_status;
struct list_head pins;
struct work_struct change_work;
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 9578f00095282..8f1dd3a2ac214 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -17,6 +17,7 @@
#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
ZL3073X_NUM_OUTPUT_PINS)
+#define ZL3073X_NCO_PIN_ID ZL3073X_NUM_PINS
/*
* Register address structure:
@@ -164,6 +165,12 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+#define ZL_REG_DPLL_CTRL(_idx) \
+ ZL_REG_IDX(_idx, 5, 0x05, 1, ZL3073X_MAX_CHANNELS, 4)
+#define ZL_DPLL_CTRL_TIE_CLEAR BIT(0)
+#define ZL_DPLL_CTRL_TOD_STEP_RST BIT(2)
+#define ZL_DPLL_CTRL_NCO_AUTO_READ BIT(7)
+
#define ZL_REG_DPLL_DF_READ(_idx) \
ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
#define ZL_DPLL_DF_READ_SEM BIT(4)
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support
2026-05-13 14:56 [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 1/2] dpll: add DPLL_PIN_TYPE_INT_NCO pin type Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 2/2] dpll: zl3073x: add NCO virtual input pin Ivan Vecera
@ 2026-05-15 20:56 ` Ivan Vecera
2 siblings, 0 replies; 4+ messages in thread
From: Ivan Vecera @ 2026-05-15 20:56 UTC (permalink / raw)
To: netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Petr Oros, Prathosh Satish,
Simon Horman, Vadim Fedorenko, linux-kernel
On 5/13/26 4:56 PM, Ivan Vecera wrote:
> Add a new DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing
> the NCO mode of a DPLL and implement support for it in the zl3073x driver.
>
> Patch 1 adds the new pin type to the DPLL netlink spec and UAPI header.
>
> Patch 2 adds a virtual NCO input pin to the zl3073x driver that allows
> userspace to switch a DPLL channel into NCO mode. The pin reports
> connected/active state when the channel is in NCO mode and handles
> the hardware-specific details of mode transitions including automatic
> df_offset capture and 1PPS phase preservation.
>
> Ivan Vecera (2):
> dpll: add DPLL_PIN_TYPE_INT_NCO pin type
> dpll: zl3073x: add NCO virtual input pin
>
> Documentation/netlink/specs/dpll.yaml | 12 ++
> drivers/dpll/dpll_nl.c | 2 +-
> drivers/dpll/zl3073x/chan.c | 14 +-
> drivers/dpll/zl3073x/chan.h | 24 +++
> drivers/dpll/zl3073x/core.c | 7 +
> drivers/dpll/zl3073x/dpll.c | 259 ++++++++++++++++++++++----
> drivers/dpll/zl3073x/dpll.h | 2 +
> drivers/dpll/zl3073x/regs.h | 7 +
> include/uapi/linux/dpll.h | 4 +
> 9 files changed, 291 insertions(+), 40 deletions(-)
>
Sashiko found some issues... will address them in v2.
Ivan
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-05-15 20:56 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-13 14:56 [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 1/2] dpll: add DPLL_PIN_TYPE_INT_NCO pin type Ivan Vecera
2026-05-13 14:56 ` [PATCH net-next 2/2] dpll: zl3073x: add NCO virtual input pin Ivan Vecera
2026-05-15 20:56 ` [PATCH net-next 0/2] dpll: add NCO pin type and zl3073x support Ivan Vecera
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.