[PATCH v14 28/38] phy: core: add notifier infrastructure
Sebastian Reichel <[email protected]>
| Newsgroups | org.infradead.lists.linux-phy,org.infradead.lists.linux-arm-kernel,org.infradead.lists.linux-rockchip,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb |
|---|---|
| Message-ID | <20260813-rockchip-usbdp-cleanup-v14-28-b5ad9c68fa11@collabora.com> |
Some PHY devices with multiple ports (e.g. USB3 and DP) require a reset if the configuration changes or cable orientation changes. This is a problem, as the consumer device will run into undefined behavior. With the new PHY notifier API introduced in this patch, the consumer driver can hook into reset events coming from a PHY device to handle the PHY going down gracefully. Note that this uses -ENOSYS instead of the more sensible -ENOTSUP for the stub functions when GENERIC_PHY is disabled to stay consistent with the existing ones. Signed-off-by: Sebastian Reichel <[email protected]> --- drivers/phy/phy-core.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/phy/phy.h | 40 ++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 21aaf2f76e53..51d261daae7a 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -542,6 +542,70 @@ int phy_notify_state(struct phy *phy, union phy_notify state) } EXPORT_SYMBOL_GPL(phy_notify_state); +/** + * phy_register_notifier() - register a notifier for PHY events + * @phy: the phy returned by phy_get() + * @nb: notifier block to register + * + * Allows PHY consumers to receive notifications about PHY reset events. + * PHY providers can signal these events using phy_notify_reset(). + * + * Returns: %0 if successful, a negative error code otherwise + */ +int phy_register_notifier(struct phy *phy, struct notifier_block *nb) +{ + if (!phy) + return 0; + + return blocking_notifier_chain_register(&phy->notifier, nb); +} +EXPORT_SYMBOL_GPL(phy_register_notifier); + +/** + * phy_unregister_notifier() - unregister a notifier for PHY events + * @phy: the phy returned by phy_get() + * @nb: notifier block to unregister + * + * Returns: %0 if successful, a negative error code otherwise + */ +int phy_unregister_notifier(struct phy *phy, struct notifier_block *nb) +{ + if (!phy) + return 0; + + return blocking_notifier_chain_unregister(&phy->notifier, nb); +} +EXPORT_SYMBOL_GPL(phy_unregister_notifier); + +/** + * phy_notify_reset() - notify consumers of a PHY reset event + * @phy: the phy that is being reset + * @event: the notification event (PRE_RESET or POST_RESET) + * + * Called by PHY providers to notify consumers that the PHY is about to + * be reset or has completed a reset. This allows consumers to quiesce + * hardware before the PHY becomes unavailable. + * + * This may be called from within PHY provider callbacks (e.g. set_mode, + * power_on) where phy->mutex is held. Consumer notification handlers must + * therefore NOT call back into the PHY framework (e.g. phy_power_off, + * phy_exit) on the same PHY, as this would result in a deadlock. + * + * Returns: %0 if successful or no notifiers registered, a negative error + * code if a notifier returns an error (for PRE_RESET only) + */ +int phy_notify_reset(struct phy *phy, enum phy_notification event) +{ + int ret; + + if (!phy) + return 0; + + ret = blocking_notifier_call_chain(&phy->notifier, event, phy); + return notifier_to_errno(ret); +} +EXPORT_SYMBOL_GPL(phy_notify_reset); + /** * phy_configure() - Changes the phy parameters * @phy: the phy returned by phy_get() @@ -1018,6 +1082,7 @@ struct phy *phy_create(struct device *dev, struct device_node *node, device_initialize(&phy->dev); lockdep_register_key(&phy->lockdep_key); mutex_init_with_key(&phy->mutex, &phy->lockdep_key); + BLOCKING_INIT_NOTIFIER_HEAD(&phy->notifier); phy->dev.class = &phy_class; phy->dev.parent = dev; diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index ea47975e288a..3779a4d0a02c 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -11,6 +11,7 @@ #define __DRIVERS_PHY_H #include <linux/err.h> +#include <linux/notifier.h> #include <linux/of.h> #include <linux/device.h> #include <linux/pm_runtime.h> @@ -53,6 +54,16 @@ enum phy_media { PHY_MEDIA_DAC, }; +/** + * enum phy_notification - PHY notification events + * @PHY_NOTIFY_PRE_RESET: PHY is about to be reset, consumers should quiesce + * @PHY_NOTIFY_POST_RESET: PHY reset is complete, consumers may resume + */ +enum phy_notification { + PHY_NOTIFY_PRE_RESET, + PHY_NOTIFY_POST_RESET, +}; + enum phy_ufs_state { PHY_UFS_HIBERN8_ENTER, PHY_UFS_HIBERN8_EXIT, @@ -170,6 +181,7 @@ struct phy_attrs { * @power_count: used to protect when the PHY is used by multiple consumers * @attrs: used to specify PHY specific attributes * @pwr: power regulator associated with the phy + * @notifier: notifier head for PHY reset events * @debugfs: debugfs directory */ struct phy { @@ -182,6 +194,7 @@ struct phy { int power_count; struct phy_attrs attrs; struct regulator *pwr; + struct blocking_notifier_head notifier; struct dentry *debugfs; }; @@ -267,6 +280,9 @@ int phy_calibrate(struct phy *phy); int phy_notify_connect(struct phy *phy, int port); int phy_notify_disconnect(struct phy *phy, int port); int phy_notify_state(struct phy *phy, union phy_notify state); +int phy_register_notifier(struct phy *phy, struct notifier_block *nb); +int phy_unregister_notifier(struct phy *phy, struct notifier_block *nb); +int phy_notify_reset(struct phy *phy, enum phy_notification event); static inline int phy_get_bus_width(struct phy *phy) { return phy->attrs.bus_width; @@ -428,6 +444,30 @@ static inline int phy_notify_state(struct phy *phy, union phy_notify state) return -ENOSYS; } +static inline int phy_register_notifier(struct phy *phy, + struct notifier_block *nb) +{ + if (!phy) + return 0; + return -ENOSYS; +} + +static inline int phy_unregister_notifier(struct phy *phy, + struct notifier_block *nb) +{ + if (!phy) + return 0; + return -ENOSYS; +} + +static inline int phy_notify_reset(struct phy *phy, + enum phy_notification event) +{ + if (!phy) + return 0; + return -ENOSYS; +} + static inline int phy_configure(struct phy *phy, union phy_configure_opts *opts) { -- 2.53.0 -- linux-phy mailing list [email protected] https://lists.infradead.org/mailman/listinfo/linux-phy