Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'dpll-support-mode-switching'

Ivan Vecera says:

====================
dpll: support mode switching

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.
====================

Link: https://patch.msgid.link/20260114122726.120303-1-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+182 -8
+1
Documentation/netlink/specs/dpll.yaml
··· 550 550 request: 551 551 attributes: 552 552 - id 553 + - mode 553 554 - phase-offset-monitor 554 555 - phase-offset-avg-factor 555 556 -
+63 -8
drivers/dpll/dpll_netlink.c
··· 128 128 struct netlink_ext_ack *extack) 129 129 { 130 130 const struct dpll_device_ops *ops = dpll_device_ops(dpll); 131 + DECLARE_BITMAP(modes, DPLL_MODE_MAX + 1) = { 0 }; 131 132 enum dpll_mode mode; 132 133 int ret; 133 134 134 - /* No mode change is supported now, so the only supported mode is the 135 - * one obtained by mode_get(). 136 - */ 135 + if (ops->supported_modes_get) { 136 + ret = ops->supported_modes_get(dpll, dpll_priv(dpll), modes, 137 + extack); 138 + if (ret) 139 + return ret; 140 + } else { 141 + /* If the supported modes are not reported by the driver, the 142 + * only supported mode is the one obtained by mode_get(). 143 + */ 144 + ret = ops->mode_get(dpll, dpll_priv(dpll), &mode, extack); 145 + if (ret) 146 + return ret; 137 147 138 - ret = ops->mode_get(dpll, dpll_priv(dpll), &mode, extack); 139 - if (ret) 140 - return ret; 141 - if (nla_put_u32(msg, DPLL_A_MODE_SUPPORTED, mode)) 142 - return -EMSGSIZE; 148 + __set_bit(mode, modes); 149 + } 150 + 151 + for_each_set_bit(mode, modes, DPLL_MODE_MAX + 1) 152 + if (nla_put_u32(msg, DPLL_A_MODE_SUPPORTED, mode)) 153 + return -EMSGSIZE; 143 154 144 155 return 0; 145 156 } ··· 852 841 return ret; 853 842 } 854 843 EXPORT_SYMBOL_GPL(dpll_pin_change_ntf); 844 + 845 + static int 846 + dpll_mode_set(struct dpll_device *dpll, struct nlattr *a, 847 + struct netlink_ext_ack *extack) 848 + { 849 + const struct dpll_device_ops *ops = dpll_device_ops(dpll); 850 + DECLARE_BITMAP(modes, DPLL_MODE_MAX + 1) = { 0 }; 851 + enum dpll_mode mode = nla_get_u32(a), old_mode; 852 + int ret; 853 + 854 + if (!(ops->mode_set && ops->supported_modes_get)) { 855 + NL_SET_ERR_MSG_ATTR(extack, a, 856 + "dpll device does not support mode switch"); 857 + return -EOPNOTSUPP; 858 + } 859 + 860 + ret = ops->mode_get(dpll, dpll_priv(dpll), &old_mode, extack); 861 + if (ret) { 862 + NL_SET_ERR_MSG(extack, "unable to get current mode"); 863 + return ret; 864 + } 865 + 866 + if (mode == old_mode) 867 + return 0; 868 + 869 + ret = ops->supported_modes_get(dpll, dpll_priv(dpll), modes, extack); 870 + if (ret) { 871 + NL_SET_ERR_MSG(extack, "unable to get supported modes"); 872 + return ret; 873 + } 874 + 875 + if (!test_bit(mode, modes)) { 876 + NL_SET_ERR_MSG(extack, 877 + "dpll device does not support requested mode"); 878 + return -EINVAL; 879 + } 880 + 881 + return ops->mode_set(dpll, dpll_priv(dpll), mode, extack); 882 + } 855 883 856 884 static int 857 885 dpll_phase_offset_monitor_set(struct dpll_device *dpll, struct nlattr *a, ··· 1847 1797 nla_for_each_attr(a, genlmsg_data(info->genlhdr), 1848 1798 genlmsg_len(info->genlhdr), rem) { 1849 1799 switch (nla_type(a)) { 1800 + case DPLL_A_MODE: 1801 + ret = dpll_mode_set(dpll, a, info->extack); 1802 + if (ret) 1803 + return ret; 1804 + break; 1850 1805 case DPLL_A_PHASE_OFFSET_MONITOR: 1851 1806 ret = dpll_phase_offset_monitor_set(dpll, a, 1852 1807 info->extack);
+1
drivers/dpll/dpll_nl.c
··· 45 45 /* DPLL_CMD_DEVICE_SET - do */ 46 46 static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = { 47 47 [DPLL_A_ID] = { .type = NLA_U32, }, 48 + [DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2), 48 49 [DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1), 49 50 [DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, }, 50 51 };
+112
drivers/dpll/zl3073x/dpll.c
··· 100 100 return 0; 101 101 } 102 102 103 + static struct zl3073x_dpll_pin * 104 + zl3073x_dpll_pin_get_by_ref(struct zl3073x_dpll *zldpll, u8 ref_id) 105 + { 106 + struct zl3073x_dpll_pin *pin; 107 + 108 + list_for_each_entry(pin, &zldpll->pins, list) { 109 + if (zl3073x_dpll_is_input_pin(pin) && 110 + zl3073x_input_pin_ref_get(pin->id) == ref_id) 111 + return pin; 112 + } 113 + 114 + return NULL; 115 + } 116 + 103 117 static int 104 118 zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin, 105 119 void *pin_priv, ··· 1152 1138 } 1153 1139 1154 1140 static int 1141 + zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll, 1142 + void *dpll_priv, unsigned long *modes, 1143 + struct netlink_ext_ack *extack) 1144 + { 1145 + struct zl3073x_dpll *zldpll = dpll_priv; 1146 + 1147 + /* We support switching between automatic and manual mode, except in 1148 + * a case where the DPLL channel is configured to run in NCO mode. 1149 + * In this case, report only the manual mode to which the NCO is mapped 1150 + * as the only supported one. 1151 + */ 1152 + if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_NCO) 1153 + __set_bit(DPLL_MODE_AUTOMATIC, modes); 1154 + 1155 + __set_bit(DPLL_MODE_MANUAL, modes); 1156 + 1157 + return 0; 1158 + } 1159 + 1160 + static int 1155 1161 zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, 1156 1162 enum dpll_mode *mode, struct netlink_ext_ack *extack) 1157 1163 { ··· 1252 1218 } 1253 1219 1254 1220 static int 1221 + zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv, 1222 + enum dpll_mode mode, struct netlink_ext_ack *extack) 1223 + { 1224 + struct zl3073x_dpll *zldpll = dpll_priv; 1225 + u8 hw_mode, mode_refsel, ref; 1226 + int rc; 1227 + 1228 + rc = zl3073x_dpll_selected_ref_get(zldpll, &ref); 1229 + if (rc) { 1230 + NL_SET_ERR_MSG_MOD(extack, "failed to get selected reference"); 1231 + return rc; 1232 + } 1233 + 1234 + if (mode == DPLL_MODE_MANUAL) { 1235 + /* We are switching from automatic to manual mode: 1236 + * - if we have a valid reference selected during auto mode then 1237 + * we will switch to forced reference lock mode and use this 1238 + * reference for selection 1239 + * - if NO valid reference is selected, we will switch to forced 1240 + * holdover mode or freerun mode, depending on the current 1241 + * lock status 1242 + */ 1243 + if (ZL3073X_DPLL_REF_IS_VALID(ref)) 1244 + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK; 1245 + else if (zldpll->lock_status == DPLL_LOCK_STATUS_UNLOCKED) 1246 + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN; 1247 + else 1248 + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER; 1249 + } else { 1250 + /* We are switching from manual to automatic mode: 1251 + * - if there is a valid reference selected then ensure that 1252 + * it is selectable after switch to automatic mode 1253 + * - switch to automatic mode 1254 + */ 1255 + struct zl3073x_dpll_pin *pin; 1256 + 1257 + pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref); 1258 + if (pin && !pin->selectable) { 1259 + /* Restore pin priority in HW */ 1260 + rc = zl3073x_dpll_ref_prio_set(pin, pin->prio); 1261 + if (rc) { 1262 + NL_SET_ERR_MSG_MOD(extack, 1263 + "failed to restore pin priority"); 1264 + return rc; 1265 + } 1266 + 1267 + pin->selectable = true; 1268 + } 1269 + 1270 + hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO; 1271 + } 1272 + 1273 + /* Build mode_refsel value */ 1274 + mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode); 1275 + 1276 + if (ZL3073X_DPLL_REF_IS_VALID(ref)) 1277 + mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref); 1278 + 1279 + /* Update dpll_mode_refsel register */ 1280 + rc = zl3073x_write_u8(zldpll->dev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id), 1281 + mode_refsel); 1282 + if (rc) { 1283 + NL_SET_ERR_MSG_MOD(extack, 1284 + "failed to set reference selection mode"); 1285 + return rc; 1286 + } 1287 + 1288 + zldpll->refsel_mode = hw_mode; 1289 + 1290 + if (ZL3073X_DPLL_REF_IS_VALID(ref)) 1291 + zldpll->forced_ref = ref; 1292 + 1293 + return 0; 1294 + } 1295 + 1296 + static int 1255 1297 zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, 1256 1298 void *dpll_priv, 1257 1299 enum dpll_feature_state *state, ··· 1386 1276 static const struct dpll_device_ops zl3073x_dpll_device_ops = { 1387 1277 .lock_status_get = zl3073x_dpll_lock_status_get, 1388 1278 .mode_get = zl3073x_dpll_mode_get, 1279 + .mode_set = zl3073x_dpll_mode_set, 1389 1280 .phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get, 1390 1281 .phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set, 1391 1282 .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, 1392 1283 .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, 1284 + .supported_modes_get = zl3073x_dpll_supported_modes_get, 1393 1285 }; 1394 1286 1395 1287 /**
+5
include/linux/dpll.h
··· 20 20 struct dpll_device_ops { 21 21 int (*mode_get)(const struct dpll_device *dpll, void *dpll_priv, 22 22 enum dpll_mode *mode, struct netlink_ext_ack *extack); 23 + int (*mode_set)(const struct dpll_device *dpll, void *dpll_priv, 24 + enum dpll_mode mode, struct netlink_ext_ack *extack); 25 + int (*supported_modes_get)(const struct dpll_device *dpll, 26 + void *dpll_priv, unsigned long *modes, 27 + struct netlink_ext_ack *extack); 23 28 int (*lock_status_get)(const struct dpll_device *dpll, void *dpll_priv, 24 29 enum dpll_lock_status *status, 25 30 enum dpll_lock_status_error *status_error,