public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next 0/3] dpll: support mode switching
@ 2026-01-12 10:14 Ivan Vecera
  2026-01-12 10:14 ` [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes Ivan Vecera
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Ivan Vecera @ 2026-01-12 10:14 UTC (permalink / raw)
  To: netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Vadim Fedorenko, Arkadiusz Kubalewski,
	Jiri Pirko, Prathosh Satish, Petr Oros, linux-kernel,
	Michal Schmidt

This series adds support for switching the working mode (automatic vs
manual) of a DPLL device via netlink.

Currently, the DPLL subsystem allows userspace to retrieve the current
working mode but lacks the mechanism to configure it. Userspace is also
unaware of which modes a specific device actually supports, as it
currently assumes only the active mode is supported.

The series addresses these limitations by:
1. Introducing .supported_modes_get() callback to allow drivers to report
   all modes capable of running on the device.
2. Introducing .mode_set() callback and updating the netlink policy
   to allow userspace to request a mode change.
3. Implementing these callbacks in the zl3073x driver, enabling dynamic
   switching between automatic and manual modes.

Ivan Vecera (3):
  dpll: add dpll_device op to get supported modes
  dpll: add dpll_device op to set working mode
  dpll: zl3073x: Implement device mode setting support

 Documentation/netlink/specs/dpll.yaml |   1 +
 drivers/dpll/dpll_netlink.c           |  71 +++++++++++++++--
 drivers/dpll/dpll_nl.c                |   1 +
 drivers/dpll/zl3073x/dpll.c           | 106 ++++++++++++++++++++++++++
 include/linux/dpll.h                  |   5 ++
 5 files changed, 176 insertions(+), 8 deletions(-)

-- 
2.52.0


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes
  2026-01-12 10:14 [PATCH net-next 0/3] dpll: support mode switching Ivan Vecera
@ 2026-01-12 10:14 ` Ivan Vecera
  2026-01-12 11:44   ` Vadim Fedorenko
  2026-01-12 10:14 ` [PATCH net-next 2/3] dpll: add dpll_device op to set working mode Ivan Vecera
  2026-01-12 10:14 ` [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support Ivan Vecera
  2 siblings, 1 reply; 9+ messages in thread
From: Ivan Vecera @ 2026-01-12 10:14 UTC (permalink / raw)
  To: netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Vadim Fedorenko, Arkadiusz Kubalewski,
	Jiri Pirko, Prathosh Satish, Petr Oros, linux-kernel,
	Michal Schmidt

Currently, the DPLL subsystem assumes that the only supported mode is
the one currently active on the device. When dpll_msg_add_mode_supported()
is called, it relies on ops->mode_get() and reports that single mode
to userspace. This prevents users from discovering other modes the device
might be capable of.

Add a new callback .supported_modes_get() to struct dpll_device_ops. This
allows drivers to populate a bitmap indicating all modes supported by
the hardware.

Update dpll_msg_add_mode_supported() to utilize this new callback:

* if ops->supported_modes_get is defined, use it to retrieve the full
  bitmap of supported modes.
* if not defined, fall back to the existing behavior: retrieve
  the current mode via ops->mode_get and set the corresponding bit
  in the bitmap.

Finally, iterate over the bitmap and add a DPLL_A_MODE_SUPPORTED netlink
attribute for every set bit, accurately reporting the device's capabilities
to userspace.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/dpll_netlink.c | 27 +++++++++++++++++++--------
 include/linux/dpll.h        |  3 +++
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 64944f601ee5a..d6a0e272d7038 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -128,18 +128,29 @@ dpll_msg_add_mode_supported(struct sk_buff *msg, struct dpll_device *dpll,
 			    struct netlink_ext_ack *extack)
 {
 	const struct dpll_device_ops *ops = dpll_device_ops(dpll);
+	DECLARE_BITMAP(modes, DPLL_MODE_MAX + 1) = { 0 };
 	enum dpll_mode mode;
 	int ret;
 
-	/* No mode change is supported now, so the only supported mode is the
-	 * one obtained by mode_get().
-	 */
+	if (ops->supported_modes_get) {
+		ret = ops->supported_modes_get(dpll, dpll_priv(dpll), modes,
+					       extack);
+		if (ret)
+			return ret;
+	} else {
+		/* If the supported modes are not reported by the driver, the
+		 * only supported mode is the one obtained by mode_get().
+		 */
+		ret = ops->mode_get(dpll, dpll_priv(dpll), &mode, extack);
+		if (ret)
+			return ret;
 
-	ret = ops->mode_get(dpll, dpll_priv(dpll), &mode, extack);
-	if (ret)
-		return ret;
-	if (nla_put_u32(msg, DPLL_A_MODE_SUPPORTED, mode))
-		return -EMSGSIZE;
+		__set_bit(mode, modes);
+	}
+
+	for_each_set_bit(mode, modes, DPLL_MODE_MAX + 1)
+		if (nla_put_u32(msg, DPLL_A_MODE_SUPPORTED, mode))
+			return -EMSGSIZE;
 
 	return 0;
 }
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index 562f520b23c27..912a2ca3e0ee7 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -20,6 +20,9 @@ struct dpll_pin_esync;
 struct dpll_device_ops {
 	int (*mode_get)(const struct dpll_device *dpll, void *dpll_priv,
 			enum dpll_mode *mode, struct netlink_ext_ack *extack);
+	int (*supported_modes_get)(const struct dpll_device *dpll,
+				   void *dpll_priv, unsigned long *modes,
+				   struct netlink_ext_ack *extack);
 	int (*lock_status_get)(const struct dpll_device *dpll, void *dpll_priv,
 			       enum dpll_lock_status *status,
 			       enum dpll_lock_status_error *status_error,
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH net-next 2/3] dpll: add dpll_device op to set working mode
  2026-01-12 10:14 [PATCH net-next 0/3] dpll: support mode switching Ivan Vecera
  2026-01-12 10:14 ` [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes Ivan Vecera
@ 2026-01-12 10:14 ` Ivan Vecera
  2026-01-12 11:35   ` Vadim Fedorenko
  2026-01-12 10:14 ` [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support Ivan Vecera
  2 siblings, 1 reply; 9+ messages in thread
From: Ivan Vecera @ 2026-01-12 10:14 UTC (permalink / raw)
  To: netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Vadim Fedorenko, Arkadiusz Kubalewski,
	Jiri Pirko, Prathosh Satish, Petr Oros, linux-kernel,
	Michal Schmidt

Currently, userspace can retrieve the DPLL working mode but cannot
configure it. This prevents changing the device operation, such as
switching from manual to automatic mode and vice versa.

Add a new callback .mode_set() to struct dpll_device_ops. Extend
the netlink policy and device-set command handling to process
the DPLL_A_MODE attribute.  Update the netlink YAML specification
to include the mode attribute in the device-set operation.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 Documentation/netlink/specs/dpll.yaml |  1 +
 drivers/dpll/dpll_netlink.c           | 44 +++++++++++++++++++++++++++
 drivers/dpll/dpll_nl.c                |  1 +
 include/linux/dpll.h                  |  2 ++
 4 files changed, 48 insertions(+)

diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index 78d0724d7e12c..b55afa77eac4b 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -550,6 +550,7 @@ operations:
         request:
           attributes:
             - id
+            - mode
             - phase-offset-monitor
             - phase-offset-avg-factor
     -
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index d6a0e272d7038..37ca90ab841bd 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -853,6 +853,45 @@ int dpll_pin_change_ntf(struct dpll_pin *pin)
 }
 EXPORT_SYMBOL_GPL(dpll_pin_change_ntf);
 
+static int
+dpll_mode_set(struct dpll_device *dpll, struct nlattr *a,
+	      struct netlink_ext_ack *extack)
+{
+	const struct dpll_device_ops *ops = dpll_device_ops(dpll);
+	enum dpll_mode mode = nla_get_u32(a), old_mode;
+	DECLARE_BITMAP(modes, DPLL_MODE_MAX) = { 0 };
+	int ret;
+
+	if (!(ops->mode_set && ops->supported_modes_get)) {
+		NL_SET_ERR_MSG_ATTR(extack, a,
+				    "dpll device does not support mode switch");
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->mode_get(dpll, dpll_priv(dpll), &old_mode, extack);
+	if (ret) {
+		NL_SET_ERR_MSG(extack, "unable to get current mode");
+		return ret;
+	}
+
+	if (mode == old_mode)
+		return 0;
+
+	ret = ops->supported_modes_get(dpll, dpll_priv(dpll), modes, extack);
+	if (ret) {
+		NL_SET_ERR_MSG(extack, "unable to get supported modes");
+		return ret;
+	}
+
+	if (!test_bit(mode, modes)) {
+		NL_SET_ERR_MSG(extack,
+			       "dpll device does not support requested mode");
+		return -EINVAL;
+	}
+
+	return ops->mode_set(dpll, dpll_priv(dpll), mode, extack);
+}
+
 static int
 dpll_phase_offset_monitor_set(struct dpll_device *dpll, struct nlattr *a,
 			      struct netlink_ext_ack *extack)
@@ -1808,6 +1847,11 @@ dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info)
 	nla_for_each_attr(a, genlmsg_data(info->genlhdr),
 			  genlmsg_len(info->genlhdr), rem) {
 		switch (nla_type(a)) {
+		case DPLL_A_MODE:
+			ret = dpll_mode_set(dpll, a, info->extack);
+			if (ret)
+				return ret;
+			break;
 		case DPLL_A_PHASE_OFFSET_MONITOR:
 			ret = dpll_phase_offset_monitor_set(dpll, a,
 							    info->extack);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index 36d11ff195df4..a2b22d4921142 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -45,6 +45,7 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = {
 /* DPLL_CMD_DEVICE_SET - do */
 static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = {
 	[DPLL_A_ID] = { .type = NLA_U32, },
+	[DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
 	[DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
 	[DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, },
 };
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index 912a2ca3e0ee7..c6d0248fa5273 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -20,6 +20,8 @@ struct dpll_pin_esync;
 struct dpll_device_ops {
 	int (*mode_get)(const struct dpll_device *dpll, void *dpll_priv,
 			enum dpll_mode *mode, struct netlink_ext_ack *extack);
+	int (*mode_set)(const struct dpll_device *dpll, void *dpll_priv,
+			enum dpll_mode mode, struct netlink_ext_ack *extack);
 	int (*supported_modes_get)(const struct dpll_device *dpll,
 				   void *dpll_priv, unsigned long *modes,
 				   struct netlink_ext_ack *extack);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support
  2026-01-12 10:14 [PATCH net-next 0/3] dpll: support mode switching Ivan Vecera
  2026-01-12 10:14 ` [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes Ivan Vecera
  2026-01-12 10:14 ` [PATCH net-next 2/3] dpll: add dpll_device op to set working mode Ivan Vecera
@ 2026-01-12 10:14 ` Ivan Vecera
  2026-01-12 11:37   ` Vadim Fedorenko
  2 siblings, 1 reply; 9+ messages in thread
From: Ivan Vecera @ 2026-01-12 10:14 UTC (permalink / raw)
  To: netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Vadim Fedorenko, Arkadiusz Kubalewski,
	Jiri Pirko, Prathosh Satish, Petr Oros, linux-kernel,
	Michal Schmidt

Add support for .supported_modes_get() and .mode_set() callbacks
to enable switching between manual and automatic modes via netlink.

Implement .supported_modes_get() to report available modes based
on the current hardware configuration:

* manual mode is always supported
* automatic mode is supported unless the dpll channel is configured
  in NCO (Numerically Controlled Oscillator) mode

Implement .mode_set() to handle the specific logic required when
transitioning between modes:

1) Transition to manual:
* If a valid reference is currently active, switch the hardware
  to ref-lock mode (force lock to that reference).
* If no reference is valid and the DPLL is unlocked, switch to freerun.
* Otherwise, switch to Holdover.

2) Transition to automatic:
* If the currently selected reference pin was previously marked
  as non-selectable (likely during a previous manual forcing
  operation), restore its priority and selectability in the hardware.
* Switch the hardware to Automatic selection mode.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 drivers/dpll/zl3073x/dpll.c | 106 ++++++++++++++++++++++++++++++++++++
 1 file changed, 106 insertions(+)

diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 9879d85d29af0..d0a9c361dc1d8 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -100,6 +100,20 @@ zl3073x_dpll_pin_direction_get(const struct dpll_pin *dpll_pin, void *pin_priv,
 	return 0;
 }
 
+static struct zl3073x_dpll_pin *
+zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
+{
+	struct zl3073x_dpll_pin *pin;
+
+	list_for_each_entry(pin, &zldpll->pins, list) {
+		if (zl3073x_dpll_is_input_pin(pin) &&
+		    zl3073x_input_pin_ref_get(pin->id) == ref_id)
+			return pin;
+	}
+
+	return NULL;
+}
+
 static int
 zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
 				 void *pin_priv,
@@ -1137,6 +1151,26 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
 	return 0;
 }
 
+static int
+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;
+
+	/* 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 (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_NCO)
+		__set_bit(DPLL_MODE_AUTOMATIC, modes);
+
+	__set_bit(DPLL_MODE_MANUAL, modes);
+
+	return 0;
+}
+
 static int
 zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
 		      enum dpll_mode *mode, struct netlink_ext_ack *extack)
@@ -1217,6 +1251,76 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
 	return 0;
 }
 
+static int
+zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
+		      enum dpll_mode mode, struct netlink_ext_ack *extack)
+{
+	struct zl3073x_dpll *zldpll = dpll_priv;
+	u8 hw_mode, mode_refsel, ref;
+	int rc;
+
+	rc = zl3073x_dpll_selected_ref_get(zldpll, &ref);
+	if (rc) {
+		NL_SET_ERR_MSG_MOD(extack, "failed to get selected reference");
+		return rc;
+	}
+
+	if (mode == DPLL_MODE_MANUAL) {
+		/* We are switching from automatic to manual mode:
+		 * - if we have a valid reference selected during auto mode then
+		 *   we will switch to forced reference lock mode and use this
+		 *   reference for selection
+		 * - if NO valid reference is selected, we will switch to forced
+		 *   holdover mode or freerun mode, depending on the current
+		 *   lock status
+		 */
+		if (ZL3073X_DPLL_REF_IS_VALID(ref))
+			hw_mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
+		else if (zldpll->lock_status == DPLL_LOCK_STATUS_UNLOCKED)
+			hw_mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;
+		else
+			hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
+	} else {
+		/* We are switching from manual to automatic mode:
+		 * - if there is a valid reference selected then ensure that
+		 *   it is selectable after switch to automatic mode
+		 * - switch to automatic mode
+		 */
+		struct zl3073x_dpll_pin *pin;
+
+		pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
+		if (pin && !pin->selectable) {
+			/* Restore pin priority in HW */
+			rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
+			if (rc)
+				return rc;
+
+			pin->selectable = true;
+		}
+
+		hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO;
+	}
+
+	/* Build mode_refsel value */
+	mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode);
+
+	if (ZL3073X_DPLL_REF_IS_VALID(ref))
+		mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
+
+	/* Update dpll_mode_refsel register */
+	rc = zl3073x_write_u8(zldpll->dev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id),
+			      mode_refsel);
+	if (rc)
+		return rc;
+
+	zldpll->refsel_mode = hw_mode;
+
+	if (ZL3073X_DPLL_REF_IS_VALID(ref))
+		zldpll->forced_ref = ref;
+
+	return 0;
+}
+
 static int
 zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
 				      void *dpll_priv,
@@ -1276,10 +1380,12 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
 static const struct dpll_device_ops zl3073x_dpll_device_ops = {
 	.lock_status_get = zl3073x_dpll_lock_status_get,
 	.mode_get = zl3073x_dpll_mode_get,
+	.mode_set = zl3073x_dpll_mode_set,
 	.phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get,
 	.phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set,
 	.phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
 	.phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
+	.supported_modes_get = zl3073x_dpll_supported_modes_get,
 };
 
 /**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH net-next 2/3] dpll: add dpll_device op to set working mode
  2026-01-12 10:14 ` [PATCH net-next 2/3] dpll: add dpll_device op to set working mode Ivan Vecera
@ 2026-01-12 11:35   ` Vadim Fedorenko
  2026-01-12 13:10     ` Ivan Vecera
  0 siblings, 1 reply; 9+ messages in thread
From: Vadim Fedorenko @ 2026-01-12 11:35 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Arkadiusz Kubalewski, Jiri Pirko,
	Prathosh Satish, Petr Oros, linux-kernel, Michal Schmidt

On 12/01/2026 10:14, Ivan Vecera wrote:
> Currently, userspace can retrieve the DPLL working mode but cannot
> configure it. This prevents changing the device operation, such as
> switching from manual to automatic mode and vice versa.
> 
> Add a new callback .mode_set() to struct dpll_device_ops. Extend
> the netlink policy and device-set command handling to process
> the DPLL_A_MODE attribute.  Update the netlink YAML specification
> to include the mode attribute in the device-set operation.
> 
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
>   Documentation/netlink/specs/dpll.yaml |  1 +
>   drivers/dpll/dpll_netlink.c           | 44 +++++++++++++++++++++++++++
>   drivers/dpll/dpll_nl.c                |  1 +
>   include/linux/dpll.h                  |  2 ++
>   4 files changed, 48 insertions(+)
> 
> diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
> index 78d0724d7e12c..b55afa77eac4b 100644
> --- a/Documentation/netlink/specs/dpll.yaml
> +++ b/Documentation/netlink/specs/dpll.yaml
> @@ -550,6 +550,7 @@ operations:
>           request:
>             attributes:
>               - id
> +            - mode
>               - phase-offset-monitor
>               - phase-offset-avg-factor
>       -
> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
> index d6a0e272d7038..37ca90ab841bd 100644
> --- a/drivers/dpll/dpll_netlink.c
> +++ b/drivers/dpll/dpll_netlink.c
> @@ -853,6 +853,45 @@ int dpll_pin_change_ntf(struct dpll_pin *pin)
>   }
>   EXPORT_SYMBOL_GPL(dpll_pin_change_ntf);
>   
> +static int
> +dpll_mode_set(struct dpll_device *dpll, struct nlattr *a,
> +	      struct netlink_ext_ack *extack)
> +{
> +	const struct dpll_device_ops *ops = dpll_device_ops(dpll);
> +	enum dpll_mode mode = nla_get_u32(a), old_mode;
> +	DECLARE_BITMAP(modes, DPLL_MODE_MAX) = { 0 };

I believe the size of bitmap should be DPLL_MODE_MAX + 1 or
__DPLL_MODE_MAX?

> +	int ret;
> +
> +	if (!(ops->mode_set && ops->supported_modes_get)) {
> +		NL_SET_ERR_MSG_ATTR(extack, a,
> +				    "dpll device does not support mode switch");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	ret = ops->mode_get(dpll, dpll_priv(dpll), &old_mode, extack);
> +	if (ret) {
> +		NL_SET_ERR_MSG(extack, "unable to get current mode");
> +		return ret;
> +	}
> +
> +	if (mode == old_mode)
> +		return 0;
> +
> +	ret = ops->supported_modes_get(dpll, dpll_priv(dpll), modes, extack);
> +	if (ret) {
> +		NL_SET_ERR_MSG(extack, "unable to get supported modes");
> +		return ret;
> +	}
> +
> +	if (!test_bit(mode, modes)) {
> +		NL_SET_ERR_MSG(extack,
> +			       "dpll device does not support requested mode");
> +		return -EINVAL;
> +	}
> +
> +	return ops->mode_set(dpll, dpll_priv(dpll), mode, extack);
> +}
> +
>   static int
>   dpll_phase_offset_monitor_set(struct dpll_device *dpll, struct nlattr *a,
>   			      struct netlink_ext_ack *extack)
> @@ -1808,6 +1847,11 @@ dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info)
>   	nla_for_each_attr(a, genlmsg_data(info->genlhdr),
>   			  genlmsg_len(info->genlhdr), rem) {
>   		switch (nla_type(a)) {
> +		case DPLL_A_MODE:
> +			ret = dpll_mode_set(dpll, a, info->extack);
> +			if (ret)
> +				return ret;
> +			break;
>   		case DPLL_A_PHASE_OFFSET_MONITOR:
>   			ret = dpll_phase_offset_monitor_set(dpll, a,
>   							    info->extack);
> diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
> index 36d11ff195df4..a2b22d4921142 100644
> --- a/drivers/dpll/dpll_nl.c
> +++ b/drivers/dpll/dpll_nl.c
> @@ -45,6 +45,7 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = {
>   /* DPLL_CMD_DEVICE_SET - do */
>   static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = {
>   	[DPLL_A_ID] = { .type = NLA_U32, },
> +	[DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
>   	[DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
>   	[DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, },
>   };
> diff --git a/include/linux/dpll.h b/include/linux/dpll.h
> index 912a2ca3e0ee7..c6d0248fa5273 100644
> --- a/include/linux/dpll.h
> +++ b/include/linux/dpll.h
> @@ -20,6 +20,8 @@ struct dpll_pin_esync;
>   struct dpll_device_ops {
>   	int (*mode_get)(const struct dpll_device *dpll, void *dpll_priv,
>   			enum dpll_mode *mode, struct netlink_ext_ack *extack);
> +	int (*mode_set)(const struct dpll_device *dpll, void *dpll_priv,
> +			enum dpll_mode mode, struct netlink_ext_ack *extack);
>   	int (*supported_modes_get)(const struct dpll_device *dpll,
>   				   void *dpll_priv, unsigned long *modes,
>   				   struct netlink_ext_ack *extack);


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support
  2026-01-12 10:14 ` [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support Ivan Vecera
@ 2026-01-12 11:37   ` Vadim Fedorenko
  2026-01-12 13:27     ` Ivan Vecera
  0 siblings, 1 reply; 9+ messages in thread
From: Vadim Fedorenko @ 2026-01-12 11:37 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Arkadiusz Kubalewski, Jiri Pirko,
	Prathosh Satish, Petr Oros, linux-kernel, Michal Schmidt

On 12/01/2026 10:14, Ivan Vecera wrote:
> Add support for .supported_modes_get() and .mode_set() callbacks
> to enable switching between manual and automatic modes via netlink.
> 
> Implement .supported_modes_get() to report available modes based
> on the current hardware configuration:
> 
> * manual mode is always supported
> * automatic mode is supported unless the dpll channel is configured
>    in NCO (Numerically Controlled Oscillator) mode
> 
> Implement .mode_set() to handle the specific logic required when
> transitioning between modes:
> 
> 1) Transition to manual:
> * If a valid reference is currently active, switch the hardware
>    to ref-lock mode (force lock to that reference).
> * If no reference is valid and the DPLL is unlocked, switch to freerun.
> * Otherwise, switch to Holdover.
> 
> 2) Transition to automatic:
> * If the currently selected reference pin was previously marked
>    as non-selectable (likely during a previous manual forcing
>    operation), restore its priority and selectability in the hardware.
> * Switch the hardware to Automatic selection mode.
> 
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
>   drivers/dpll/zl3073x/dpll.c | 106 ++++++++++++++++++++++++++++++++++++
>   1 file changed, 106 insertions(+)
> 
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 9879d85d29af0..d0a9c361dc1d8 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -100,6 +100,20 @@ zl3073x_dpll_pin_direction_get(const struct dpll_pin *dpll_pin, void *pin_priv,
>   	return 0;
>   }
>   
> +static struct zl3073x_dpll_pin *
> +zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id)
> +{
> +	struct zl3073x_dpll_pin *pin;
> +
> +	list_for_each_entry(pin, &zldpll->pins, list) {
> +		if (zl3073x_dpll_is_input_pin(pin) &&
> +		    zl3073x_input_pin_ref_get(pin->id) == ref_id)
> +			return pin;
> +	}
> +
> +	return NULL;
> +}
> +
>   static int
>   zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
>   				 void *pin_priv,
> @@ -1137,6 +1151,26 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
>   	return 0;
>   }
>   
> +static int
> +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;
> +
> +	/* 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 (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_NCO)
> +		__set_bit(DPLL_MODE_AUTOMATIC, modes);
> +
> +	__set_bit(DPLL_MODE_MANUAL, modes);
> +
> +	return 0;
> +}
> +
>   static int
>   zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
>   		      enum dpll_mode *mode, struct netlink_ext_ack *extack)
> @@ -1217,6 +1251,76 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
>   	return 0;
>   }
>   
> +static int
> +zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
> +		      enum dpll_mode mode, struct netlink_ext_ack *extack)
> +{
> +	struct zl3073x_dpll *zldpll = dpll_priv;
> +	u8 hw_mode, mode_refsel, ref;
> +	int rc;
> +
> +	rc = zl3073x_dpll_selected_ref_get(zldpll, &ref);
> +	if (rc) {
> +		NL_SET_ERR_MSG_MOD(extack, "failed to get selected reference");
> +		return rc;
> +	}
> +
> +	if (mode == DPLL_MODE_MANUAL) {
> +		/* We are switching from automatic to manual mode:
> +		 * - if we have a valid reference selected during auto mode then
> +		 *   we will switch to forced reference lock mode and use this
> +		 *   reference for selection
> +		 * - if NO valid reference is selected, we will switch to forced
> +		 *   holdover mode or freerun mode, depending on the current
> +		 *   lock status
> +		 */
> +		if (ZL3073X_DPLL_REF_IS_VALID(ref))
> +			hw_mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
> +		else if (zldpll->lock_status == DPLL_LOCK_STATUS_UNLOCKED)
> +			hw_mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;
> +		else
> +			hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
> +	} else {
> +		/* We are switching from manual to automatic mode:
> +		 * - if there is a valid reference selected then ensure that
> +		 *   it is selectable after switch to automatic mode
> +		 * - switch to automatic mode
> +		 */
> +		struct zl3073x_dpll_pin *pin;
> +
> +		pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
> +		if (pin && !pin->selectable) {
> +			/* Restore pin priority in HW */
> +			rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
> +			if (rc)
> +				return rc;

I think it's better to fill-up extack here to give at least some info of
what's happened?

> +
> +			pin->selectable = true;
> +		}
> +
> +		hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO;
> +	}
> +
> +	/* Build mode_refsel value */
> +	mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode);
> +
> +	if (ZL3073X_DPLL_REF_IS_VALID(ref))
> +		mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
> +
> +	/* Update dpll_mode_refsel register */
> +	rc = zl3073x_write_u8(zldpll->dev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id),
> +			      mode_refsel);
> +	if (rc)
> +		return rc;

And here as well..

> +
> +	zldpll->refsel_mode = hw_mode;
> +
> +	if (ZL3073X_DPLL_REF_IS_VALID(ref))
> +		zldpll->forced_ref = ref;
> +
> +	return 0;
> +}
> +
>   static int
>   zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
>   				      void *dpll_priv,
> @@ -1276,10 +1380,12 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = {
>   static const struct dpll_device_ops zl3073x_dpll_device_ops = {
>   	.lock_status_get = zl3073x_dpll_lock_status_get,
>   	.mode_get = zl3073x_dpll_mode_get,
> +	.mode_set = zl3073x_dpll_mode_set,
>   	.phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get,
>   	.phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set,
>   	.phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
>   	.phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
> +	.supported_modes_get = zl3073x_dpll_supported_modes_get,
>   };
>   
>   /**


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes
  2026-01-12 10:14 ` [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes Ivan Vecera
@ 2026-01-12 11:44   ` Vadim Fedorenko
  0 siblings, 0 replies; 9+ messages in thread
From: Vadim Fedorenko @ 2026-01-12 11:44 UTC (permalink / raw)
  To: Ivan Vecera, netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Arkadiusz Kubalewski, Jiri Pirko,
	Prathosh Satish, Petr Oros, linux-kernel, Michal Schmidt

On 12/01/2026 10:14, Ivan Vecera wrote:
> Currently, the DPLL subsystem assumes that the only supported mode is
> the one currently active on the device. When dpll_msg_add_mode_supported()
> is called, it relies on ops->mode_get() and reports that single mode
> to userspace. This prevents users from discovering other modes the device
> might be capable of.
> 
> Add a new callback .supported_modes_get() to struct dpll_device_ops. This
> allows drivers to populate a bitmap indicating all modes supported by
> the hardware.
> 
> Update dpll_msg_add_mode_supported() to utilize this new callback:
> 
> * if ops->supported_modes_get is defined, use it to retrieve the full
>    bitmap of supported modes.
> * if not defined, fall back to the existing behavior: retrieve
>    the current mode via ops->mode_get and set the corresponding bit
>    in the bitmap.
> 
> Finally, iterate over the bitmap and add a DPLL_A_MODE_SUPPORTED netlink
> attribute for every set bit, accurately reporting the device's capabilities
> to userspace.
> 
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>

LGTM,
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net-next 2/3] dpll: add dpll_device op to set working mode
  2026-01-12 11:35   ` Vadim Fedorenko
@ 2026-01-12 13:10     ` Ivan Vecera
  0 siblings, 0 replies; 9+ messages in thread
From: Ivan Vecera @ 2026-01-12 13:10 UTC (permalink / raw)
  To: Vadim Fedorenko, netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Arkadiusz Kubalewski, Jiri Pirko,
	Prathosh Satish, Petr Oros, linux-kernel, Michal Schmidt



On 1/12/26 12:35 PM, Vadim Fedorenko wrote:
>> diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/ 
>> netlink/specs/dpll.yaml
>> index 78d0724d7e12c..b55afa77eac4b 100644
>> --- a/Documentation/netlink/specs/dpll.yaml
>> +++ b/Documentation/netlink/specs/dpll.yaml
>> @@ -550,6 +550,7 @@ operations:
>>           request:
>>             attributes:
>>               - id
>> +            - mode
>>               - phase-offset-monitor
>>               - phase-offset-avg-factor
>>       -
>> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
>> index d6a0e272d7038..37ca90ab841bd 100644
>> --- a/drivers/dpll/dpll_netlink.c
>> +++ b/drivers/dpll/dpll_netlink.c
>> @@ -853,6 +853,45 @@ int dpll_pin_change_ntf(struct dpll_pin *pin)
>>   }
>>   EXPORT_SYMBOL_GPL(dpll_pin_change_ntf);
>> +static int
>> +dpll_mode_set(struct dpll_device *dpll, struct nlattr *a,
>> +          struct netlink_ext_ack *extack)
>> +{
>> +    const struct dpll_device_ops *ops = dpll_device_ops(dpll);
>> +    enum dpll_mode mode = nla_get_u32(a), old_mode;
>> +    DECLARE_BITMAP(modes, DPLL_MODE_MAX) = { 0 };
> 
> I believe the size of bitmap should be DPLL_MODE_MAX + 1 or
> __DPLL_MODE_MAX?

Yes, you are right... will fix.

Thanks,
Ivan


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support
  2026-01-12 11:37   ` Vadim Fedorenko
@ 2026-01-12 13:27     ` Ivan Vecera
  0 siblings, 0 replies; 9+ messages in thread
From: Ivan Vecera @ 2026-01-12 13:27 UTC (permalink / raw)
  To: Vadim Fedorenko, netdev
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Paolo Abeni, Simon Horman, Arkadiusz Kubalewski, Jiri Pirko,
	Prathosh Satish, Petr Oros, linux-kernel, Michal Schmidt

On 1/12/26 12:37 PM, Vadim Fedorenko wrote:
>> +    if (mode == DPLL_MODE_MANUAL) {
>> +        /* We are switching from automatic to manual mode:
>> +         * - if we have a valid reference selected during auto mode then
>> +         *   we will switch to forced reference lock mode and use this
>> +         *   reference for selection
>> +         * - if NO valid reference is selected, we will switch to forced
>> +         *   holdover mode or freerun mode, depending on the current
>> +         *   lock status
>> +         */
>> +        if (ZL3073X_DPLL_REF_IS_VALID(ref))
>> +            hw_mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
>> +        else if (zldpll->lock_status == DPLL_LOCK_STATUS_UNLOCKED)
>> +            hw_mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;
>> +        else
>> +            hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
>> +    } else {
>> +        /* We are switching from manual to automatic mode:
>> +         * - if there is a valid reference selected then ensure that
>> +         *   it is selectable after switch to automatic mode
>> +         * - switch to automatic mode
>> +         */
>> +        struct zl3073x_dpll_pin *pin;
>> +
>> +        pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
>> +        if (pin && !pin->selectable) {
>> +            /* Restore pin priority in HW */
>> +            rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
>> +            if (rc)
>> +                return rc;
> 
> I think it's better to fill-up extack here to give at least some info of
> what's happened?

Will add, thanks for pointing this out.

Ivan


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-01-12 13:27 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-12 10:14 [PATCH net-next 0/3] dpll: support mode switching Ivan Vecera
2026-01-12 10:14 ` [PATCH net-next 1/3] dpll: add dpll_device op to get supported modes Ivan Vecera
2026-01-12 11:44   ` Vadim Fedorenko
2026-01-12 10:14 ` [PATCH net-next 2/3] dpll: add dpll_device op to set working mode Ivan Vecera
2026-01-12 11:35   ` Vadim Fedorenko
2026-01-12 13:10     ` Ivan Vecera
2026-01-12 10:14 ` [PATCH net-next 3/3] dpll: zl3073x: Implement device mode setting support Ivan Vecera
2026-01-12 11:37   ` Vadim Fedorenko
2026-01-12 13:27     ` Ivan Vecera

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox