summaryrefslogtreecommitdiff
path: root/drivers/dpll
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-11 19:31:52 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-11 19:31:52 -0800
commit37a93dd5c49b5fda807fd204edf2547c3493319c (patch)
treece1ef5a642b9ea3d7242156438eb96dc5607a752 /drivers/dpll
parent098b6e44cbaa2d526d06af90c862d13fb414a0ec (diff)
parent83310d613382f74070fc8b402f3f6c2af8439ead (diff)
Merge tag 'net-next-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextipvs/mainipvs/HEADipvs-next/mainipvs-next/HEADdavem/net-next/maindavem/net-next/HEAD
Pull networking updates from Paolo Abeni: "Core & protocols: - A significant effort all around the stack to guide the compiler to make the right choice when inlining code, to avoid unneeded calls for small helper and stack canary overhead in the fast-path. This generates better and faster code with very small or no text size increases, as in many cases the call generated more code than the actual inlined helper. - Extend AccECN implementation so that is now functionally complete, also allow the user-space enabling it on a per network namespace basis. - Add support for memory providers with large (above 4K) rx buffer. Paired with hw-gro, larger rx buffer sizes reduce the number of buffers traversing the stack, dincreasing single stream CPU usage by up to ~30%. - Do not add HBH header to Big TCP GSO packets. This simplifies the RX path, the TX path and the NIC drivers, and is possible because user-space taps can now interpret correctly such packets without the HBH hint. - Allow IPv6 routes to be configured with a gateway address that is resolved out of a different interface than the one specified, aligning IPv6 to IPv4 behavior. - Multi-queue aware sch_cake. This makes it possible to scale the rate shaper of sch_cake across multiple CPUs, while still enforcing a single global rate on the interface. - Add support for the nbcon (new buffer console) infrastructure to netconsole, enabling lock-free, priority-based console operations that are safer in crash scenarios. - Improve the TCP ipv6 output path to cache the flow information, saving cpu cycles, reducing cache line misses and stack use. - Improve netfilter packet tracker to resolve clashes for most protocols, avoiding unneeded drops on rare occasions. - Add IP6IP6 tunneling acceleration to the flowtable infrastructure. - Reduce tcp socket size by one cache line. - Notify neighbour changes atomically, avoiding inconsistencies between the notification sequence and the actual states sequence. - Add vsock namespace support, allowing complete isolation of vsocks across different network namespaces. - Improve xsk generic performances with cache-alignment-oriented optimizations. - Support netconsole automatic target recovery, allowing netconsole to reestablish targets when underlying low-level interface comes back online. Driver API: - Support for switching the working mode (automatic vs manual) of a DPLL device via netlink. - Introduce PHY ports representation to expose multiple front-facing media ports over a single MAC. - Introduce "rx-polarity" and "tx-polarity" device tree properties, to generalize polarity inversion requirements for differential signaling. - Add helper to create, prepare and enable managed clocks. Device drivers: - Add Huawei hinic3 PF etherner driver. - Add DWMAC glue driver for Motorcomm YT6801 PCIe ethernet controller. - Add ethernet driver for MaxLinear MxL862xx switches - Remove parallel-port Ethernet driver. - Convert existing driver timestamp configuration reporting to hwtstamp_get and remove legacy ioctl(). - Convert existing drivers to .get_rx_ring_count(), simplifing the RX ring count retrieval. Also remove the legacy fallback path. - Ethernet high-speed NICs: - Broadcom (bnxt, bng): - bnxt: add FW interface update to support FEC stats histogram and NVRAM defragmentation - bng: add TSO and H/W GRO support - nVidia/Mellanox (mlx5): - improve latency of channel restart operations, reducing the used H/W resources - add TSO support for UDP over GRE over VLAN - add flow counters support for hardware steering (HWS) rules - use a static memory area to store headers for H/W GRO, leading to 12% RX tput improvement - Intel (100G, ice, idpf): - ice: reorganizes layout of Tx and Rx rings for cacheline locality and utilizes __cacheline_group* macros on the new layouts - ice: introduces Synchronous Ethernet (SyncE) support - Meta (fbnic): - adds debugfs for firmware mailbox and tx/rx rings vectors - Ethernet virtual: - geneve: introduce GRO/GSO support for double UDP encapsulation - Ethernet NICs consumer, and embedded: - Synopsys (stmmac): - some code refactoring and cleanups - RealTek (r8169): - add support for RTL8127ATF (10G Fiber SFP) - add dash and LTR support - Airoha: - AN8811HB 2.5 Gbps phy support - Freescale (fec): - add XDP zero-copy support - Thunderbolt: - add get link setting support to allow bonding - Renesas: - add support for RZ/G3L GBETH SoC - Ethernet switches: - Maxlinear: - support R(G)MII slow rate configuration - add support for Intel GSW150 - Motorcomm (yt921x): - add DCB/QoS support - TI: - icssm-prueth: support bridging (STP/RSTP) via the switchdev framework - Ethernet PHYs: - Realtek: - enable SGMII and 2500Base-X in-band auto-negotiation - simplify and reunify C22/C45 drivers - Micrel: convert bindings to DT schema - CAN: - move skb headroom content into skb extensions, making CAN metadata access more robust - CAN drivers: - rcar_canfd: - add support for FD-only mode - add support for the RZ/T2H SoC - sja1000: cleanup the CAN state handling - WiFi: - implement EPPKE/802.1X over auth frames support - split up drop reasons better, removing generic RX_DROP - additional FTM capabilities: 6 GHz support, supported number of spatial streams and supported number of LTF repetitions - better mac80211 iterators to enumerate resources - initial UHR (Wi-Fi 8) support for cfg80211/mac80211 - WiFi drivers: - Qualcomm/Atheros: - ath11k: support for Channel Frequency Response measurement - ath12k: a significant driver refactor to support multi-wiphy devices and and pave the way for future device support in the same driver (rather than splitting to ath13k) - ath12k: support for the QCC2072 chipset - Intel: - iwlwifi: partial Neighbor Awareness Networking (NAN) support - iwlwifi: initial support for U-NII-9 and IEEE 802.11bn - RealTek (rtw89): - preparations for RTL8922DE support - Bluetooth: - implement setsockopt(BT_PHY) to set the connection packet type/PHY - set link_policy on incoming ACL connections - Bluetooth drivers: - btusb: add support for MediaTek7920, Realtek RTL8761BU and 8851BE - btqca: add WCN6855 firmware priority selection feature" * tag 'net-next-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1254 commits) bnge/bng_re: Add a new HSI net: macb: Fix tx/rx malfunction after phy link down and up af_unix: Fix memleak of newsk in unix_stream_connect(). net: ti: icssg-prueth: Add optional dependency on HSR net: dsa: add basic initial driver for MxL862xx switches net: mdio: add unlocked mdiodev C45 bus accessors net: dsa: add tag format for MxL862xx switches dt-bindings: net: dsa: add MaxLinear MxL862xx selftests: drivers: net: hw: Modify toeplitz.c to poll for packets octeontx2-pf: Unregister devlink on probe failure net: renesas: rswitch: fix forwarding offload statemachine ionic: Rate limit unknown xcvr type messages tcp: inet6_csk_xmit() optimization tcp: populate inet->cork.fl.u.ip6 in tcp_v6_syn_recv_sock() tcp: populate inet->cork.fl.u.ip6 in tcp_v6_connect() ipv6: inet6_csk_xmit() and inet6_csk_update_pmtu() use inet->cork.fl.u.ip6 ipv6: use inet->cork.fl.u.ip6 and np->final in ip6_datagram_dst_update() ipv6: use np->final in inet6_sk_rebuild_header() ipv6: add daddr/final storage in struct ipv6_pinfo net: stmmac: qcom-ethqos: fix qcom_ethqos_serdes_powerup() ...
Diffstat (limited to 'drivers/dpll')
-rw-r--r--drivers/dpll/Kconfig15
-rw-r--r--drivers/dpll/dpll_core.c288
-rw-r--r--drivers/dpll/dpll_core.h11
-rw-r--r--drivers/dpll/dpll_netlink.c87
-rw-r--r--drivers/dpll/dpll_nl.c1
-rw-r--r--drivers/dpll/zl3073x/core.c7
-rw-r--r--drivers/dpll/zl3073x/core.h30
-rw-r--r--drivers/dpll/zl3073x/dpll.c178
-rw-r--r--drivers/dpll/zl3073x/dpll.h2
-rw-r--r--drivers/dpll/zl3073x/out.h17
-rw-r--r--drivers/dpll/zl3073x/prop.c34
11 files changed, 567 insertions, 103 deletions
diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig
index ade872c915ac..be98969f040a 100644
--- a/drivers/dpll/Kconfig
+++ b/drivers/dpll/Kconfig
@@ -8,6 +8,21 @@ menu "DPLL device support"
config DPLL
bool
+config DPLL_REFCNT_TRACKER
+ bool "DPLL reference count tracking"
+ depends on DEBUG_KERNEL && STACKTRACE_SUPPORT && DPLL
+ select REF_TRACKER
+ help
+ Enable reference count tracking for DPLL devices and pins.
+ This helps debugging reference leaks and use-after-free bugs
+ by recording stack traces for each get/put operation.
+
+ The tracking information is exposed via debugfs at:
+ /sys/kernel/debug/ref_tracker/dpll_device_*
+ /sys/kernel/debug/ref_tracker/dpll_pin_*
+
+ If unsure, say N.
+
source "drivers/dpll/zl3073x/Kconfig"
endmenu
diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c
index 8879a7235156..627a5b39a0ef 100644
--- a/drivers/dpll/dpll_core.c
+++ b/drivers/dpll/dpll_core.c
@@ -10,6 +10,8 @@
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
@@ -22,6 +24,9 @@ DEFINE_MUTEX(dpll_lock);
DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC);
DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC);
+static RAW_NOTIFIER_HEAD(dpll_notifier_chain);
+static DEFINE_IDA(dpll_pin_idx_ida);
+
static u32 dpll_device_xa_id;
static u32 dpll_pin_xa_id;
@@ -36,6 +41,7 @@ struct dpll_device_registration {
struct list_head list;
const struct dpll_device_ops *ops;
void *priv;
+ dpll_tracker tracker;
};
struct dpll_pin_registration {
@@ -43,8 +49,117 @@ struct dpll_pin_registration {
const struct dpll_pin_ops *ops;
void *priv;
void *cookie;
+ dpll_tracker tracker;
};
+static int call_dpll_notifiers(unsigned long action, void *info)
+{
+ lockdep_assert_held(&dpll_lock);
+ return raw_notifier_call_chain(&dpll_notifier_chain, action, info);
+}
+
+void dpll_device_notify(struct dpll_device *dpll, unsigned long action)
+{
+ struct dpll_device_notifier_info info = {
+ .dpll = dpll,
+ .id = dpll->id,
+ .idx = dpll->device_idx,
+ .clock_id = dpll->clock_id,
+ .type = dpll->type,
+ };
+
+ call_dpll_notifiers(action, &info);
+}
+
+void dpll_pin_notify(struct dpll_pin *pin, unsigned long action)
+{
+ struct dpll_pin_notifier_info info = {
+ .pin = pin,
+ .id = pin->id,
+ .idx = pin->pin_idx,
+ .clock_id = pin->clock_id,
+ .fwnode = pin->fwnode,
+ .prop = &pin->prop,
+ };
+
+ call_dpll_notifiers(action, &info);
+}
+
+static void dpll_device_tracker_alloc(struct dpll_device *dpll,
+ dpll_tracker *tracker)
+{
+#ifdef CONFIG_DPLL_REFCNT_TRACKER
+ ref_tracker_alloc(&dpll->refcnt_tracker, tracker, GFP_KERNEL);
+#endif
+}
+
+static void dpll_device_tracker_free(struct dpll_device *dpll,
+ dpll_tracker *tracker)
+{
+#ifdef CONFIG_DPLL_REFCNT_TRACKER
+ ref_tracker_free(&dpll->refcnt_tracker, tracker);
+#endif
+}
+
+static void __dpll_device_hold(struct dpll_device *dpll, dpll_tracker *tracker)
+{
+ dpll_device_tracker_alloc(dpll, tracker);
+ refcount_inc(&dpll->refcount);
+}
+
+static void __dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker)
+{
+ dpll_device_tracker_free(dpll, tracker);
+ if (refcount_dec_and_test(&dpll->refcount)) {
+ ASSERT_DPLL_NOT_REGISTERED(dpll);
+ WARN_ON_ONCE(!xa_empty(&dpll->pin_refs));
+ xa_destroy(&dpll->pin_refs);
+ xa_erase(&dpll_device_xa, dpll->id);
+ WARN_ON(!list_empty(&dpll->registration_list));
+ ref_tracker_dir_exit(&dpll->refcnt_tracker);
+ kfree(dpll);
+ }
+}
+
+static void dpll_pin_tracker_alloc(struct dpll_pin *pin, dpll_tracker *tracker)
+{
+#ifdef CONFIG_DPLL_REFCNT_TRACKER
+ ref_tracker_alloc(&pin->refcnt_tracker, tracker, GFP_KERNEL);
+#endif
+}
+
+static void dpll_pin_tracker_free(struct dpll_pin *pin, dpll_tracker *tracker)
+{
+#ifdef CONFIG_DPLL_REFCNT_TRACKER
+ ref_tracker_free(&pin->refcnt_tracker, tracker);
+#endif
+}
+
+static void __dpll_pin_hold(struct dpll_pin *pin, dpll_tracker *tracker)
+{
+ dpll_pin_tracker_alloc(pin, tracker);
+ refcount_inc(&pin->refcount);
+}
+
+static void dpll_pin_idx_free(u32 pin_idx);
+static void dpll_pin_prop_free(struct dpll_pin_properties *prop);
+
+static void __dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker)
+{
+ dpll_pin_tracker_free(pin, tracker);
+ if (refcount_dec_and_test(&pin->refcount)) {
+ xa_erase(&dpll_pin_xa, pin->id);
+ xa_destroy(&pin->dpll_refs);
+ xa_destroy(&pin->parent_refs);
+ xa_destroy(&pin->ref_sync_pins);
+ dpll_pin_prop_free(&pin->prop);
+ fwnode_handle_put(pin->fwnode);
+ dpll_pin_idx_free(pin->pin_idx);
+ ref_tracker_dir_exit(&pin->refcnt_tracker);
+ kfree_rcu(pin, rcu);
+ }
+}
+
struct dpll_device *dpll_device_get_by_id(int id)
{
if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED))
@@ -114,6 +229,7 @@ dpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin,
reg->ops = ops;
reg->priv = priv;
reg->cookie = cookie;
+ __dpll_pin_hold(pin, &reg->tracker);
if (ref_exists)
refcount_inc(&ref->refcount);
list_add_tail(&reg->list, &ref->registration_list);
@@ -136,6 +252,7 @@ static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin,
if (WARN_ON(!reg))
return -EINVAL;
list_del(&reg->list);
+ __dpll_pin_put(pin, &reg->tracker);
kfree(reg);
if (refcount_dec_and_test(&ref->refcount)) {
xa_erase(xa_pins, i);
@@ -193,6 +310,7 @@ dpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll,
reg->ops = ops;
reg->priv = priv;
reg->cookie = cookie;
+ __dpll_device_hold(dpll, &reg->tracker);
if (ref_exists)
refcount_inc(&ref->refcount);
list_add_tail(&reg->list, &ref->registration_list);
@@ -215,6 +333,7 @@ dpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll,
if (WARN_ON(!reg))
return;
list_del(&reg->list);
+ __dpll_device_put(dpll, &reg->tracker);
kfree(reg);
if (refcount_dec_and_test(&ref->refcount)) {
xa_erase(xa_dplls, i);
@@ -256,6 +375,7 @@ dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module)
return ERR_PTR(ret);
}
xa_init_flags(&dpll->pin_refs, XA_FLAGS_ALLOC);
+ ref_tracker_dir_init(&dpll->refcnt_tracker, 128, "dpll_device");
return dpll;
}
@@ -265,6 +385,7 @@ dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module)
* @clock_id: clock_id of creator
* @device_idx: idx given by device driver
* @module: reference to registering module
+ * @tracker: tracking object for the acquired reference
*
* Get existing object of a dpll device, unique for given arguments.
* Create new if doesn't exist yet.
@@ -275,7 +396,8 @@ dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module)
* * ERR_PTR(X) - error
*/
struct dpll_device *
-dpll_device_get(u64 clock_id, u32 device_idx, struct module *module)
+dpll_device_get(u64 clock_id, u32 device_idx, struct module *module,
+ dpll_tracker *tracker)
{
struct dpll_device *dpll, *ret = NULL;
unsigned long index;
@@ -285,13 +407,17 @@ dpll_device_get(u64 clock_id, u32 device_idx, struct module *module)
if (dpll->clock_id == clock_id &&
dpll->device_idx == device_idx &&
dpll->module == module) {
+ __dpll_device_hold(dpll, tracker);
ret = dpll;
- refcount_inc(&ret->refcount);
break;
}
}
- if (!ret)
+ if (!ret) {
ret = dpll_device_alloc(clock_id, device_idx, module);
+ if (!IS_ERR(ret))
+ dpll_device_tracker_alloc(ret, tracker);
+ }
+
mutex_unlock(&dpll_lock);
return ret;
@@ -301,22 +427,16 @@ EXPORT_SYMBOL_GPL(dpll_device_get);
/**
* dpll_device_put - decrease the refcount and free memory if possible
* @dpll: dpll_device struct pointer
+ * @tracker: tracking object for the acquired reference
*
* Context: Acquires a lock (dpll_lock)
* Drop reference for a dpll device, if all references are gone, delete
* dpll device object.
*/
-void dpll_device_put(struct dpll_device *dpll)
+void dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker)
{
mutex_lock(&dpll_lock);
- if (refcount_dec_and_test(&dpll->refcount)) {
- ASSERT_DPLL_NOT_REGISTERED(dpll);
- WARN_ON_ONCE(!xa_empty(&dpll->pin_refs));
- xa_destroy(&dpll->pin_refs);
- xa_erase(&dpll_device_xa, dpll->id);
- WARN_ON(!list_empty(&dpll->registration_list));
- kfree(dpll);
- }
+ __dpll_device_put(dpll, tracker);
mutex_unlock(&dpll_lock);
}
EXPORT_SYMBOL_GPL(dpll_device_put);
@@ -378,6 +498,7 @@ int dpll_device_register(struct dpll_device *dpll, enum dpll_type type,
reg->ops = ops;
reg->priv = priv;
dpll->type = type;
+ __dpll_device_hold(dpll, &reg->tracker);
first_registration = list_empty(&dpll->registration_list);
list_add_tail(&reg->list, &dpll->registration_list);
if (!first_registration) {
@@ -417,6 +538,7 @@ void dpll_device_unregister(struct dpll_device *dpll,
return;
}
list_del(&reg->list);
+ __dpll_device_put(dpll, &reg->tracker);
kfree(reg);
if (!list_empty(&dpll->registration_list)) {
@@ -428,6 +550,36 @@ void dpll_device_unregister(struct dpll_device *dpll,
}
EXPORT_SYMBOL_GPL(dpll_device_unregister);
+static int dpll_pin_idx_alloc(u32 *pin_idx)
+{
+ int ret;
+
+ if (!pin_idx)
+ return -EINVAL;
+
+ /* Alloc unique number from IDA. Number belongs to <0, INT_MAX> range */
+ ret = ida_alloc(&dpll_pin_idx_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ /* Map the value to dynamic pin index range <INT_MAX+1, U32_MAX> */
+ *pin_idx = (u32)ret + INT_MAX + 1;
+
+ return 0;
+}
+
+static void dpll_pin_idx_free(u32 pin_idx)
+{
+ if (pin_idx <= INT_MAX)
+ return; /* Not a dynamic pin index */
+
+ /* Map the index value from dynamic pin index range to IDA range and
+ * free it.
+ */
+ pin_idx -= (u32)INT_MAX + 1;
+ ida_free(&dpll_pin_idx_ida, pin_idx);
+}
+
static void dpll_pin_prop_free(struct dpll_pin_properties *prop)
{
kfree(prop->package_label);
@@ -485,9 +637,18 @@ dpll_pin_alloc(u64 clock_id, u32 pin_idx, struct module *module,
struct dpll_pin *pin;
int ret;
+ if (pin_idx == DPLL_PIN_IDX_UNSPEC) {
+ ret = dpll_pin_idx_alloc(&pin_idx);
+ if (ret)
+ return ERR_PTR(ret);
+ } else if (pin_idx > INT_MAX) {
+ return ERR_PTR(-EINVAL);
+ }
pin = kzalloc(sizeof(*pin), GFP_KERNEL);
- if (!pin)
- return ERR_PTR(-ENOMEM);
+ if (!pin) {
+ ret = -ENOMEM;
+ goto err_pin_alloc;
+ }
pin->pin_idx = pin_idx;
pin->clock_id = clock_id;
pin->module = module;
@@ -507,6 +668,7 @@ dpll_pin_alloc(u64 clock_id, u32 pin_idx, struct module *module,
&dpll_pin_xa_id, GFP_KERNEL);
if (ret < 0)
goto err_xa_alloc;
+ ref_tracker_dir_init(&pin->refcnt_tracker, 128, "dpll_pin");
return pin;
err_xa_alloc:
xa_destroy(&pin->dpll_refs);
@@ -515,6 +677,8 @@ err_xa_alloc:
dpll_pin_prop_free(&pin->prop);
err_pin_prop:
kfree(pin);
+err_pin_alloc:
+ dpll_pin_idx_free(pin_idx);
return ERR_PTR(ret);
}
@@ -538,12 +702,35 @@ void dpll_netdev_pin_clear(struct net_device *dev)
}
EXPORT_SYMBOL(dpll_netdev_pin_clear);
+int register_dpll_notifier(struct notifier_block *nb)
+{
+ int ret;
+
+ mutex_lock(&dpll_lock);
+ ret = raw_notifier_chain_register(&dpll_notifier_chain, nb);
+ mutex_unlock(&dpll_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(register_dpll_notifier);
+
+int unregister_dpll_notifier(struct notifier_block *nb)
+{
+ int ret;
+
+ mutex_lock(&dpll_lock);
+ ret = raw_notifier_chain_unregister(&dpll_notifier_chain, nb);
+ mutex_unlock(&dpll_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(unregister_dpll_notifier);
+
/**
* dpll_pin_get - find existing or create new dpll pin
* @clock_id: clock_id of creator
* @pin_idx: idx given by dev driver
* @module: reference to registering module
* @prop: dpll pin properties
+ * @tracker: tracking object for the acquired reference
*
* Get existing object of a pin (unique for given arguments) or create new
* if doesn't exist yet.
@@ -555,7 +742,7 @@ EXPORT_SYMBOL(dpll_netdev_pin_clear);
*/
struct dpll_pin *
dpll_pin_get(u64 clock_id, u32 pin_idx, struct module *module,
- const struct dpll_pin_properties *prop)
+ const struct dpll_pin_properties *prop, dpll_tracker *tracker)
{
struct dpll_pin *pos, *ret = NULL;
unsigned long i;
@@ -565,13 +752,16 @@ dpll_pin_get(u64 clock_id, u32 pin_idx, struct module *module,
if (pos->clock_id == clock_id &&
pos->pin_idx == pin_idx &&
pos->module == module) {
+ __dpll_pin_hold(pos, tracker);
ret = pos;
- refcount_inc(&ret->refcount);
break;
}
}
- if (!ret)
+ if (!ret) {
ret = dpll_pin_alloc(clock_id, pin_idx, module, prop);
+ if (!IS_ERR(ret))
+ dpll_pin_tracker_alloc(ret, tracker);
+ }
mutex_unlock(&dpll_lock);
return ret;
@@ -581,26 +771,69 @@ EXPORT_SYMBOL_GPL(dpll_pin_get);
/**
* dpll_pin_put - decrease the refcount and free memory if possible
* @pin: pointer to a pin to be put
+ * @tracker: tracking object for the acquired reference
*
* Drop reference for a pin, if all references are gone, delete pin object.
*
* Context: Acquires a lock (dpll_lock)
*/
-void dpll_pin_put(struct dpll_pin *pin)
+void dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker)
{
mutex_lock(&dpll_lock);
- if (refcount_dec_and_test(&pin->refcount)) {
- xa_erase(&dpll_pin_xa, pin->id);
- xa_destroy(&pin->dpll_refs);
- xa_destroy(&pin->parent_refs);
- xa_destroy(&pin->ref_sync_pins);
- dpll_pin_prop_free(&pin->prop);
- kfree_rcu(pin, rcu);
- }
+ __dpll_pin_put(pin, tracker);
mutex_unlock(&dpll_lock);
}
EXPORT_SYMBOL_GPL(dpll_pin_put);
+/**
+ * dpll_pin_fwnode_set - set dpll pin firmware node reference
+ * @pin: pointer to a dpll pin
+ * @fwnode: firmware node handle
+ *
+ * Set firmware node handle for the given dpll pin.
+ */
+void dpll_pin_fwnode_set(struct dpll_pin *pin, struct fwnode_handle *fwnode)
+{
+ mutex_lock(&dpll_lock);
+ fwnode_handle_put(pin->fwnode); /* Drop fwnode previously set */
+ pin->fwnode = fwnode_handle_get(fwnode);
+ mutex_unlock(&dpll_lock);
+}
+EXPORT_SYMBOL_GPL(dpll_pin_fwnode_set);
+
+/**
+ * fwnode_dpll_pin_find - find dpll pin by firmware node reference
+ * @fwnode: reference to firmware node
+ * @tracker: tracking object for the acquired reference
+ *
+ * Get existing object of a pin that is associated with given firmware node
+ * reference.
+ *
+ * Context: Acquires a lock (dpll_lock)
+ * Return:
+ * * valid dpll_pin pointer on success
+ * * NULL when no such pin exists
+ */
+struct dpll_pin *fwnode_dpll_pin_find(struct fwnode_handle *fwnode,
+ dpll_tracker *tracker)
+{
+ struct dpll_pin *pin, *ret = NULL;
+ unsigned long index;
+
+ mutex_lock(&dpll_lock);
+ xa_for_each(&dpll_pin_xa, index, pin) {
+ if (pin->fwnode == fwnode) {
+ __dpll_pin_hold(pin, tracker);
+ ret = pin;
+ break;
+ }
+ }
+ mutex_unlock(&dpll_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(fwnode_dpll_pin_find);
+
static int
__dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
const struct dpll_pin_ops *ops, void *priv, void *cookie)
@@ -743,7 +976,6 @@ int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin,
ret = dpll_xa_ref_pin_add(&pin->parent_refs, parent, ops, priv, pin);
if (ret)
goto unlock;
- refcount_inc(&pin->refcount);
xa_for_each(&parent->dpll_refs, i, ref) {
ret = __dpll_pin_register(ref->dpll, pin, ops, priv, parent);
if (ret) {
@@ -763,7 +995,6 @@ dpll_unregister:
parent);
dpll_pin_delete_ntf(pin);
}
- refcount_dec(&pin->refcount);
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin);
unlock:
mutex_unlock(&dpll_lock);
@@ -790,7 +1021,6 @@ void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin,
mutex_lock(&dpll_lock);
dpll_pin_delete_ntf(pin);
dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin);
- refcount_dec(&pin->refcount);
xa_for_each(&pin->dpll_refs, i, ref)
__dpll_pin_unregister(ref->dpll, pin, ops, priv, parent);
mutex_unlock(&dpll_lock);
diff --git a/drivers/dpll/dpll_core.h b/drivers/dpll/dpll_core.h
index 8ce969bbeb64..71ac88ef2017 100644
--- a/drivers/dpll/dpll_core.h
+++ b/drivers/dpll/dpll_core.h
@@ -10,6 +10,7 @@
#include <linux/dpll.h>
#include <linux/list.h>
#include <linux/refcount.h>
+#include <linux/ref_tracker.h>
#include "dpll_nl.h"
#define DPLL_REGISTERED XA_MARK_1
@@ -23,6 +24,7 @@
* @type: type of a dpll
* @pin_refs: stores pins registered within a dpll
* @refcount: refcount
+ * @refcnt_tracker: ref_tracker directory for debugging reference leaks
* @registration_list: list of registered ops and priv data of dpll owners
**/
struct dpll_device {
@@ -33,6 +35,7 @@ struct dpll_device {
enum dpll_type type;
struct xarray pin_refs;
refcount_t refcount;
+ struct ref_tracker_dir refcnt_tracker;
struct list_head registration_list;
};
@@ -42,11 +45,13 @@ struct dpll_device {
* @pin_idx: index of a pin given by dev driver
* @clock_id: clock_id of creator
* @module: module of creator
+ * @fwnode: optional reference to firmware node
* @dpll_refs: hold referencees to dplls pin was registered with
* @parent_refs: hold references to parent pins pin was registered with
* @ref_sync_pins: hold references to pins for Reference SYNC feature
* @prop: pin properties copied from the registerer
* @refcount: refcount
+ * @refcnt_tracker: ref_tracker directory for debugging reference leaks
* @rcu: rcu_head for kfree_rcu()
**/
struct dpll_pin {
@@ -54,11 +59,13 @@ struct dpll_pin {
u32 pin_idx;
u64 clock_id;
struct module *module;
+ struct fwnode_handle *fwnode;
struct xarray dpll_refs;
struct xarray parent_refs;
struct xarray ref_sync_pins;
struct dpll_pin_properties prop;
refcount_t refcount;
+ struct ref_tracker_dir refcnt_tracker;
struct rcu_head rcu;
};
@@ -89,4 +96,8 @@ struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs);
extern struct xarray dpll_device_xa;
extern struct xarray dpll_pin_xa;
extern struct mutex dpll_lock;
+
+void dpll_device_notify(struct dpll_device *dpll, unsigned long action);
+void dpll_pin_notify(struct dpll_pin *pin, unsigned long action);
+
#endif
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 64944f601ee5..83cbd64abf5a 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;
}
@@ -378,7 +389,15 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
return 0;
return ret;
}
- return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, ffo);
+ /* Put the FFO value in PPM to preserve compatibility with older
+ * programs.
+ */
+ ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
+ div_s64(ffo, 1000000));
+ if (ret)
+ return -EMSGSIZE;
+ return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+ ffo);
}
static int
@@ -742,17 +761,20 @@ err_free_msg:
int dpll_device_create_ntf(struct dpll_device *dpll)
{
+ dpll_device_notify(dpll, DPLL_DEVICE_CREATED);
return dpll_device_event_send(DPLL_CMD_DEVICE_CREATE_NTF, dpll);
}
int dpll_device_delete_ntf(struct dpll_device *dpll)
{
+ dpll_device_notify(dpll, DPLL_DEVICE_DELETED);
return dpll_device_event_send(DPLL_CMD_DEVICE_DELETE_NTF, dpll);
}
static int
__dpll_device_change_ntf(struct dpll_device *dpll)
{
+ dpll_device_notify(dpll, DPLL_DEVICE_CHANGED);
return dpll_device_event_send(DPLL_CMD_DEVICE_CHANGE_NTF, dpll);
}
@@ -810,16 +832,19 @@ err_free_msg:
int dpll_pin_create_ntf(struct dpll_pin *pin)
{
+ dpll_pin_notify(pin, DPLL_PIN_CREATED);
return dpll_pin_event_send(DPLL_CMD_PIN_CREATE_NTF, pin);
}
int dpll_pin_delete_ntf(struct dpll_pin *pin)
{
+ dpll_pin_notify(pin, DPLL_PIN_DELETED);
return dpll_pin_event_send(DPLL_CMD_PIN_DELETE_NTF, pin);
}
int __dpll_pin_change_ntf(struct dpll_pin *pin)
{
+ dpll_pin_notify(pin, DPLL_PIN_CHANGED);
return dpll_pin_event_send(DPLL_CMD_PIN_CHANGE_NTF, pin);
}
@@ -843,6 +868,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);
+ DECLARE_BITMAP(modes, DPLL_MODE_MAX + 1) = { 0 };
+ enum dpll_mode mode = nla_get_u32(a), old_mode;
+ 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)
{
@@ -1797,6 +1861,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 36d11ff195df..a2b22d492114 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/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 383e2397dd03..63bd97181b9e 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -710,8 +710,11 @@ zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
if (rc)
return rc;
- /* Convert to ppm -> ffo = (10^6 * value) / 2^32 */
- zldev->ref[i].ffo = mul_s64_u64_shr(value, 1000000, 32);
+ /* Convert to ppt
+ * ffo = (10^12 * value) / 2^32
+ * ffo = ( 5^12 * value) / 2^20
+ */
+ zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
}
return 0;
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 09bca2d0926d..dddfcacea5c0 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -302,6 +302,36 @@ u8 zl3073x_dev_out_dpll_get(struct zl3073x_dev *zldev, u8 index)
}
/**
+ * 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
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 9879d85d29af..78edc36b17fb 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -29,6 +29,7 @@
* @list: this DPLL pin list entry
* @dpll: DPLL the pin is registered to
* @dpll_pin: pointer to registered dpll_pin
+ * @tracker: tracking object for the acquired reference
* @label: package label
* @dir: pin direction
* @id: pin id
@@ -44,6 +45,7 @@ struct zl3073x_dpll_pin {
struct list_head list;
struct zl3073x_dpll *dpll;
struct dpll_pin *dpll_pin;
+ dpll_tracker tracker;
char label[8];
enum dpll_pin_direction dir;
u8 id;
@@ -100,6 +102,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,
@@ -900,46 +916,9 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
- struct zl3073x_dev *zldev = zldpll->dev;
struct zl3073x_dpll_pin *pin = pin_priv;
- const struct zl3073x_synth *synth;
- const struct zl3073x_out *out;
- u32 synth_freq;
- u8 out_id;
- out_id = zl3073x_output_pin_out_get(pin->id);
- out = zl3073x_out_state_get(zldev, out_id);
-
- /* Get attached synth frequency */
- synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out));
- synth_freq = zl3073x_synth_freq_get(synth);
-
- switch (zl3073x_out_signal_format_get(out)) {
- case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
- case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
- /* In case of divided format we have to distiguish between
- * given output pin type.
- *
- * For P-pin the resulting frequency is computed as simple
- * division of synth frequency and output divisor.
- *
- * For N-pin we have to divide additionally by divisor stored
- * in esync_n_period output mailbox register that is used as
- * N-pin divisor for these modes.
- */
- *frequency = synth_freq / out->div;
-
- if (!zl3073x_dpll_is_p_pin(pin))
- *frequency = (u32)*frequency / out->esync_n_period;
-
- break;
- default:
- /* In other modes the resulting frequency is computed as
- * division of synth frequency and output divisor.
- */
- *frequency = synth_freq / out->div;
- break;
- }
+ *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id);
return 0;
}
@@ -1039,10 +1018,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
- /* Convert value to ps and reverse two's complement negation applied
- * during 'set'
- */
- *phase_adjust = -out->phase_comp * pin->phase_gran;
+ /* The value in the register is expressed in half synth clock cycles. */
+ *phase_adjust = out->phase_comp * pin->phase_gran;
return 0;
}
@@ -1064,10 +1041,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
- /* The value in the register is stored as two's complement negation
- * of requested value and expressed in half synth clock cycles.
- */
- out.phase_comp = -phase_adjust / pin->phase_gran;
+ /* The value in the register is expressed in half synth clock cycles. */
+ out.phase_comp = phase_adjust / pin->phase_gran;
/* Update output configuration from mailbox */
return zl3073x_out_state_set(zldev, out_id, &out);
@@ -1138,6 +1113,26 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
}
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)
{
@@ -1218,6 +1213,82 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
}
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) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "failed to restore pin priority");
+ 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) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "failed to set reference selection mode");
+ 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,
enum dpll_feature_state *state,
@@ -1276,10 +1347,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,
};
/**
@@ -1368,11 +1441,12 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
/* Create or get existing DPLL pin */
pin->dpll_pin = dpll_pin_get(zldpll->dev->clock_id, index, THIS_MODULE,
- &props->dpll_props);
+ &props->dpll_props, &pin->tracker);
if (IS_ERR(pin->dpll_pin)) {
rc = PTR_ERR(pin->dpll_pin);
goto err_pin_get;
}
+ dpll_pin_fwnode_set(pin->dpll_pin, props->fwnode);
if (zl3073x_dpll_is_input_pin(pin))
ops = &zl3073x_dpll_input_pin_ops;
@@ -1390,7 +1464,7 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
return 0;
err_register:
- dpll_pin_put(pin->dpll_pin);
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
err_prio_get:
pin->dpll_pin = NULL;
err_pin_get:
@@ -1421,7 +1495,7 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
/* Unregister the pin */
dpll_pin_unregister(zldpll->dpll_dev, pin->dpll_pin, ops, pin);
- dpll_pin_put(pin->dpll_pin);
+ dpll_pin_put(pin->dpll_pin, &pin->tracker);
pin->dpll_pin = NULL;
}
@@ -1595,7 +1669,7 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
dpll_mode_refsel);
zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id,
- THIS_MODULE);
+ THIS_MODULE, &zldpll->tracker);
if (IS_ERR(zldpll->dpll_dev)) {
rc = PTR_ERR(zldpll->dpll_dev);
zldpll->dpll_dev = NULL;
@@ -1607,7 +1681,7 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
zl3073x_prop_dpll_type_get(zldev, zldpll->id),
&zl3073x_dpll_device_ops, zldpll);
if (rc) {
- dpll_device_put(zldpll->dpll_dev);
+ dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
zldpll->dpll_dev = NULL;
}
@@ -1630,7 +1704,7 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops,
zldpll);
- dpll_device_put(zldpll->dpll_dev);
+ dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
zldpll->dpll_dev = NULL;
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index e8c39b44b356..c65c798c3792 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -18,6 +18,7 @@
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
* @dpll_dev: pointer to registered DPLL device
+ * @tracker: tracking object for the acquired reference
* @lock_status: last saved DPLL lock status
* @pins: list of pins
* @change_work: device change notification work
@@ -31,6 +32,7 @@ struct zl3073x_dpll {
u8 check_count;
bool phase_monitor;
struct dpll_device *dpll_dev;
+ dpll_tracker tracker;
enum dpll_lock_status lock_status;
struct list_head pins;
struct work_struct change_work;
diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h
index e8ea7a0e0f07..318f9bb8da3a 100644
--- a/drivers/dpll/zl3073x/out.h
+++ b/drivers/dpll/zl3073x/out.h
@@ -80,6 +80,23 @@ static inline bool zl3073x_out_is_enabled(const struct zl3073x_out *out)
}
/**
+ * zl3073x_out_is_ndiv - check if the given output is in N-div mode
+ * @out: pointer to out state
+ *
+ * Return: true if output is in N-div mode, false otherwise
+ */
+static inline bool zl3073x_out_is_ndiv(const struct zl3073x_out *out)
+{
+ switch (zl3073x_out_signal_format_get(out)) {
+ case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
+ case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
* zl3073x_out_synth_get - get synth connected to given output
* @out: pointer to out state
*
diff --git a/drivers/dpll/zl3073x/prop.c b/drivers/dpll/zl3073x/prop.c
index 4ed153087570..8523dc8c226e 100644
--- a/drivers/dpll/zl3073x/prop.c
+++ b/drivers/dpll/zl3073x/prop.c
@@ -193,9 +193,10 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
{
struct dpll_pin_frequency *ranges;
struct zl3073x_pin_props *props;
- int i, j, num_freqs, rc;
+ int i, j, num_freqs = 0, rc;
+ u64 *freqs = NULL;
const char *type;
- u64 *freqs;
+ u32 curr_freq;
props = kzalloc(sizeof(*props), GFP_KERNEL);
if (!props)
@@ -207,6 +208,7 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
props->dpll_props.capabilities =
DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
+ curr_freq = zl3073x_dev_ref_freq_get(zldev, index);
} else {
u8 out, synth;
u32 f;
@@ -220,6 +222,7 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
synth = zl3073x_dev_out_synth_get(zldev, out);
f = 2 * zl3073x_dev_synth_freq_get(zldev, synth);
props->dpll_props.phase_gran = f ? div_u64(PSEC_PER_SEC, f) : 1;
+ curr_freq = zl3073x_dev_output_pin_freq_get(zldev, index);
}
props->dpll_props.phase_range.min = S32_MIN;
@@ -230,7 +233,7 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
/* Get firmware node for the given pin */
rc = zl3073x_prop_pin_fwnode_get(zldev, props, dir, index);
if (rc)
- return props; /* Return if it does not exist */
+ goto skip_fwnode_props;
/* Look for label property and store the value as board label */
fwnode_property_read_string(props->fwnode, "label",
@@ -249,6 +252,8 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
else if (!strcmp(type, "synce"))
props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;
+ else if (!strcmp(type, "mux"))
+ props->dpll_props.type = DPLL_PIN_TYPE_MUX;
else
dev_warn(zldev->dev,
"Unknown or unsupported pin type '%s'\n",
@@ -262,9 +267,10 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
/* Read supported frequencies property if it is specified */
num_freqs = fwnode_property_count_u64(props->fwnode,
"supported-frequencies-hz");
- if (num_freqs <= 0)
- /* Return if the property does not exist or number is 0 */
- return props;
+ if (num_freqs <= 0) {
+ num_freqs = 0;
+ goto skip_fwnode_props;
+ }
/* The firmware node specifies list of supported frequencies while
* DPLL core pin properties requires list of frequency ranges.
@@ -281,19 +287,25 @@ struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
"supported-frequencies-hz", freqs,
num_freqs);
- /* Allocate frequency ranges list and fill it */
- ranges = kcalloc(num_freqs, sizeof(*ranges), GFP_KERNEL);
+skip_fwnode_props:
+ /* Allocate frequency ranges list - extra slot for current frequency */
+ ranges = kcalloc(num_freqs + 1, sizeof(*ranges), GFP_KERNEL);
if (!ranges) {
rc = -ENOMEM;
goto err_alloc_ranges;
}
- /* Convert list of frequencies to list of frequency ranges but
- * filter-out frequencies that are not representable by device
+ /* Start with current frequency at index 0 */
+ ranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq);
+
+ /* Add frequencies from firmware node, skipping current frequency
+ * and filtering out frequencies not representable by device
*/
- for (i = 0, j = 0; i < num_freqs; i++) {
+ for (i = 0, j = 1; i < num_freqs; i++) {
struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);
+ if (freqs[i] == curr_freq)
+ continue;
if (zl3073x_pin_check_freq(zldev, dir, index, freqs[i])) {
ranges[j] = freq;
j++;