From 4edf654be5471659e3260be0a557eaa2ece668ab Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Wed, 12 Nov 2025 16:27:06 +0000 Subject: [PATCH 1/2] phy: add new phy_notify_state() api Add a new phy_notify_state() api that notifies and configures a phy for a given state transition. This is intended to be used by phy drivers which need to do some runtime configuration of parameters that can't be handled by phy_calibrate() or phy_power_{on|off}(). The first usage of this API is in the Samsung UFS phy that needs to issue some register writes when entering and exiting the hibernate link state. Signed-off-by: Peter Griffin Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251112-phy-notify-pmstate-v5-1-39df622d8fcb@linaro.org Signed-off-by: Vinod Koul --- drivers/phy/phy-core.c | 25 +++++++++++++++++++++++++ include/linux/phy/phy.h | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 04a5a34e7a95..60be8af984bf 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -520,6 +520,31 @@ int phy_notify_disconnect(struct phy *phy, int port) } EXPORT_SYMBOL_GPL(phy_notify_disconnect); +/** + * phy_notify_state() - phy state notification + * @phy: the PHY returned by phy_get() + * @state: the PHY state + * + * Notify the PHY of a state transition. Used to notify and + * configure the PHY accordingly. + * + * Returns: %0 if successful, a negative error code otherwise + */ +int phy_notify_state(struct phy *phy, union phy_notify state) +{ + int ret; + + if (!phy || !phy->ops->notify_phystate) + return 0; + + mutex_lock(&phy->mutex); + ret = phy->ops->notify_phystate(phy, state); + mutex_unlock(&phy->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_notify_state); + /** * phy_configure() - Changes the phy parameters * @phy: the phy returned by phy_get() diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index 13add0c2c407..2af0d01ebb39 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -53,6 +53,15 @@ enum phy_media { PHY_MEDIA_DAC, }; +enum phy_ufs_state { + PHY_UFS_HIBERN8_ENTER, + PHY_UFS_HIBERN8_EXIT, +}; + +union phy_notify { + enum phy_ufs_state ufs_state; +}; + /** * union phy_configure_opts - Opaque generic phy configuration * @@ -83,6 +92,7 @@ union phy_configure_opts { * @set_speed: set the speed of the phy (optional) * @reset: resetting the phy * @calibrate: calibrate the phy + * @notify_phystate: notify and configure the phy for a particular state * @release: ops to be performed while the consumer relinquishes the PHY * @owner: the module owner containing the ops */ @@ -132,6 +142,7 @@ struct phy_ops { int (*connect)(struct phy *phy, int port); int (*disconnect)(struct phy *phy, int port); + int (*notify_phystate)(struct phy *phy, union phy_notify state); void (*release)(struct phy *phy); struct module *owner; }; @@ -255,6 +266,7 @@ int phy_reset(struct phy *phy); 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); static inline int phy_get_bus_width(struct phy *phy) { return phy->attrs.bus_width; @@ -412,6 +424,13 @@ static inline int phy_notify_disconnect(struct phy *phy, int index) return -ENOSYS; } +static inline int phy_notify_state(struct phy *phy, union phy_notify state) +{ + if (!phy) + return 0; + return -ENOSYS; +} + static inline int phy_configure(struct phy *phy, union phy_configure_opts *opts) { From a1af5d2be169ecec92cb4d85c811450d68164aec Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Wed, 12 Nov 2025 16:27:07 +0000 Subject: [PATCH 2/2] phy: samsung: gs101-ufs: Add .notify_phystate() & hibern8 enter/exit values Implement the .notify_phystate() callback and provide the gs101 specific phy values that need to be programmed when entering and exiting the hibern8 state. Signed-off-by: Peter Griffin Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20251112-phy-notify-pmstate-v5-2-39df622d8fcb@linaro.org Signed-off-by: Vinod Koul --- drivers/phy/samsung/phy-gs101-ufs.c | 28 +++++++++++++++++++ drivers/phy/samsung/phy-samsung-ufs.c | 40 +++++++++++++++++++++++++++ drivers/phy/samsung/phy-samsung-ufs.h | 7 +++++ 3 files changed, 75 insertions(+) diff --git a/drivers/phy/samsung/phy-gs101-ufs.c b/drivers/phy/samsung/phy-gs101-ufs.c index 17b798da5b57..a15e1f453f7f 100644 --- a/drivers/phy/samsung/phy-gs101-ufs.c +++ b/drivers/phy/samsung/phy-gs101-ufs.c @@ -108,12 +108,39 @@ static const struct samsung_ufs_phy_cfg tensor_gs101_post_pwr_hs_config[] = { END_UFS_PHY_CFG, }; +static const struct samsung_ufs_phy_cfg tensor_gs101_post_h8_enter[] = { + PHY_TRSV_REG_CFG_GS101(0x262, 0x08, PWR_MODE_ANY), + PHY_TRSV_REG_CFG_GS101(0x265, 0x0A, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x1, 0x8, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x0, 0x86, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x8, 0x60, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG_GS101(0x222, 0x08, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG_GS101(0x246, 0x01, PWR_MODE_HS_ANY), + END_UFS_PHY_CFG, +}; + +static const struct samsung_ufs_phy_cfg tensor_gs101_pre_h8_exit[] = { + PHY_COMN_REG_CFG(0x0, 0xC6, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x1, 0x0C, PWR_MODE_ANY), + PHY_TRSV_REG_CFG_GS101(0x262, 0x00, PWR_MODE_ANY), + PHY_TRSV_REG_CFG_GS101(0x265, 0x00, PWR_MODE_ANY), + PHY_COMN_REG_CFG(0x8, 0xE0, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG_GS101(0x246, 0x03, PWR_MODE_HS_ANY), + PHY_TRSV_REG_CFG_GS101(0x222, 0x18, PWR_MODE_HS_ANY), + END_UFS_PHY_CFG, +}; + static const struct samsung_ufs_phy_cfg *tensor_gs101_ufs_phy_cfgs[CFG_TAG_MAX] = { [CFG_PRE_INIT] = tensor_gs101_pre_init_cfg, [CFG_PRE_PWR_HS] = tensor_gs101_pre_pwr_hs_config, [CFG_POST_PWR_HS] = tensor_gs101_post_pwr_hs_config, }; +static const struct samsung_ufs_phy_cfg *tensor_gs101_hibern8_cfgs[] = { + [CFG_POST_HIBERN8_ENTER] = tensor_gs101_post_h8_enter, + [CFG_PRE_HIBERN8_EXIT] = tensor_gs101_pre_h8_exit, +}; + static const char * const tensor_gs101_ufs_phy_clks[] = { "ref_clk", }; @@ -170,6 +197,7 @@ static int gs101_phy_wait_for_cdr_lock(struct phy *phy, u8 lane) const struct samsung_ufs_phy_drvdata tensor_gs101_ufs_phy = { .cfgs = tensor_gs101_ufs_phy_cfgs, + .cfgs_hibern8 = tensor_gs101_hibern8_cfgs, .isol = { .offset = TENSOR_GS101_PHY_CTRL, .mask = TENSOR_GS101_PHY_CTRL_MASK, diff --git a/drivers/phy/samsung/phy-samsung-ufs.c b/drivers/phy/samsung/phy-samsung-ufs.c index f3cbe6b17b23..ee665f26c236 100644 --- a/drivers/phy/samsung/phy-samsung-ufs.c +++ b/drivers/phy/samsung/phy-samsung-ufs.c @@ -217,6 +217,44 @@ static int samsung_ufs_phy_set_mode(struct phy *generic_phy, return 0; } +static int samsung_ufs_phy_notify_state(struct phy *phy, + union phy_notify state) +{ + struct samsung_ufs_phy *ufs_phy = get_samsung_ufs_phy(phy); + const struct samsung_ufs_phy_cfg *cfg; + int i, err = -EINVAL; + + if (!ufs_phy->cfgs_hibern8) + return 0; + + if (state.ufs_state == PHY_UFS_HIBERN8_ENTER) + cfg = ufs_phy->cfgs_hibern8[CFG_POST_HIBERN8_ENTER]; + else if (state.ufs_state == PHY_UFS_HIBERN8_EXIT) + cfg = ufs_phy->cfgs_hibern8[CFG_PRE_HIBERN8_EXIT]; + else + goto err_out; + + for_each_phy_cfg(cfg) { + for_each_phy_lane(ufs_phy, i) { + samsung_ufs_phy_config(ufs_phy, cfg, i); + } + } + + if (state.ufs_state == PHY_UFS_HIBERN8_EXIT) { + for_each_phy_lane(ufs_phy, i) { + if (ufs_phy->drvdata->wait_for_cdr) { + err = ufs_phy->drvdata->wait_for_cdr(phy, i); + if (err) + goto err_out; + } + } + } + + return 0; +err_out: + return err; +} + static int samsung_ufs_phy_exit(struct phy *phy) { struct samsung_ufs_phy *ss_phy = get_samsung_ufs_phy(phy); @@ -233,6 +271,7 @@ static const struct phy_ops samsung_ufs_phy_ops = { .power_off = samsung_ufs_phy_power_off, .calibrate = samsung_ufs_phy_calibrate, .set_mode = samsung_ufs_phy_set_mode, + .notify_phystate = samsung_ufs_phy_notify_state, .owner = THIS_MODULE, }; @@ -287,6 +326,7 @@ static int samsung_ufs_phy_probe(struct platform_device *pdev) phy->dev = dev; phy->drvdata = drvdata; phy->cfgs = drvdata->cfgs; + phy->cfgs_hibern8 = drvdata->cfgs_hibern8; memcpy(&phy->isol, &drvdata->isol, sizeof(phy->isol)); if (!of_property_read_u32_index(dev->of_node, "samsung,pmu-syscon", 1, diff --git a/drivers/phy/samsung/phy-samsung-ufs.h b/drivers/phy/samsung/phy-samsung-ufs.h index a28f148081d1..f2c2e744e5ba 100644 --- a/drivers/phy/samsung/phy-samsung-ufs.h +++ b/drivers/phy/samsung/phy-samsung-ufs.h @@ -92,6 +92,11 @@ enum { CFG_TAG_MAX, }; +enum { + CFG_POST_HIBERN8_ENTER, + CFG_PRE_HIBERN8_EXIT, +}; + struct samsung_ufs_phy_cfg { u32 off_0; u32 off_1; @@ -108,6 +113,7 @@ struct samsung_ufs_phy_pmu_isol { struct samsung_ufs_phy_drvdata { const struct samsung_ufs_phy_cfg **cfgs; + const struct samsung_ufs_phy_cfg **cfgs_hibern8; struct samsung_ufs_phy_pmu_isol isol; const char * const *clk_list; int num_clks; @@ -124,6 +130,7 @@ struct samsung_ufs_phy { struct clk_bulk_data *clks; const struct samsung_ufs_phy_drvdata *drvdata; const struct samsung_ufs_phy_cfg * const *cfgs; + const struct samsung_ufs_phy_cfg * const *cfgs_hibern8; struct samsung_ufs_phy_pmu_isol isol; u8 lane_cnt; int ufs_phy_state;