On 8/28/2025 12:20 AM, Jared McArthur wrote:
> A UFS device needs its bRefClkFreq attribute set to the correct value
> before switching to high speed. If bRefClkFreq is set to the wrong
> value, all transactions after the power mode change will fail.
> 
> The bRefClkFreq depends on the host controller and the device.
> Query the device's current bRefClkFreq and compare with the ref_clk
> specified in the device-tree. If the two differ, set the bRefClkFreq
> to the device-tree's ref_clk frequency.
> 
> Taken from Linux kernel v6.17 (drivers/ufs/core/ufshcd.c and
> include/ufs/ufs.h) and ported to U-Boot.
> 
> This patch depends on the previous patch: "ufs: Add support for
> sending UFS attribute requests"
> 
> Signed-off-by: Jared McArthur <[email protected]>
> ---
>  drivers/ufs/ufs.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/ufs/ufs.h | 10 ++++++
>  2 files changed, 99 insertions(+)
> 
> diff --git a/drivers/ufs/ufs.c b/drivers/ufs/ufs.c
> index 9e67dd86232..4bffbf87749 100644
> --- a/drivers/ufs/ufs.c
> +++ b/drivers/ufs/ufs.c
> @@ -10,6 +10,7 @@
>  
>  #include <bouncebuf.h>
>  #include <charset.h>
> +#include <clk.h>
>  #include <dm.h>
>  #include <log.h>
>  #include <dm/device_compat.h>
> @@ -1773,6 +1774,92 @@ out:
>       return err;
>  }
>  
> +struct ufs_ref_clk {
> +     unsigned long freq_hz;
> +     enum ufs_ref_clk_freq val;
> +};
> +
> +static const struct ufs_ref_clk ufs_ref_clk_freqs[] = {
> +     {19200000, REF_CLK_FREQ_19_2_MHZ},
> +     {26000000, REF_CLK_FREQ_26_MHZ},
> +     {38400000, REF_CLK_FREQ_38_4_MHZ},
> +     {52000000, REF_CLK_FREQ_52_MHZ},
> +     {0, REF_CLK_FREQ_INVAL},
> +};
> +
> +static enum ufs_ref_clk_freq
> +ufs_get_bref_clk_from_hz(unsigned long freq)
> +{
> +     int i;
> +
> +     for (i = 0; ufs_ref_clk_freqs[i].freq_hz; i++)
> +             if (ufs_ref_clk_freqs[i].freq_hz == freq)
> +                     return ufs_ref_clk_freqs[i].val;
> +
> +     return REF_CLK_FREQ_INVAL;
> +}
> +
> +void ufshcd_parse_dev_ref_clk_freq(struct ufs_hba *hba, struct clk *refclk)
> +{
> +     unsigned long freq;
> +
> +     freq = clk_get_rate(refclk);
> +
> +     hba->dev_ref_clk_freq =
> +             ufs_get_bref_clk_from_hz(freq);
> +
> +     if (hba->dev_ref_clk_freq == REF_CLK_FREQ_INVAL)
> +             dev_err(hba->dev,
> +                     "invalid ref_clk setting = %ld\n", freq);
> +}
> +
> +static int ufshcd_set_dev_ref_clk(struct ufs_hba *hba)
> +{
> +     int err;
> +     struct clk *ref_clk;
> +     u32 host_ref_clk_freq;
> +     u32 dev_ref_clk_freq;
> +
> +     /* get ref_clk */
> +     ref_clk = devm_clk_get(hba->dev, "ref_clk");
> +     if (IS_ERR((const void *)ref_clk)) {
> +             err = PTR_ERR(ref_clk);
> +             goto out;
> +     }
> +
> +     ufshcd_parse_dev_ref_clk_freq(hba, ref_clk);
> +     host_ref_clk_freq = hba->dev_ref_clk_freq;
> +
> +     if (host_ref_clk_freq == REF_CLK_FREQ_INVAL)
> +             goto out;
> +
> +     err = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
> +                                   QUERY_ATTR_IDN_REF_CLK_FREQ, 0, 0, 
> &dev_ref_clk_freq);
> +
> +     if (err) {
> +             dev_err(hba->dev, "failed reading bRefClkFreq. err = %d\n", 
> err);
> +             goto out;
> +     }
> +
> +     if (dev_ref_clk_freq == host_ref_clk_freq)
> +             goto out; /* nothing to update */
> +
> +     err = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
> +                                   QUERY_ATTR_IDN_REF_CLK_FREQ, 0, 0, 
> &host_ref_clk_freq);
> +
> +     if (err) {
> +             dev_err(hba->dev, "bRefClkFreq setting to %lu Hz failed\n",
> +                     ufs_ref_clk_freqs[host_ref_clk_freq].freq_hz);
> +             goto out;
> +     }
> +
> +     dev_dbg(hba->dev, "bRefClkFreq setting to %lu Hz succeeded\n",
> +             ufs_ref_clk_freqs[host_ref_clk_freq].freq_hz);
> +
> +out:
> +     return err;
> +}
> +
>  /**
>   * ufshcd_get_max_pwr_mode - reads the max power mode negotiated with device
>   */
> @@ -2000,6 +2087,8 @@ static int ufs_start(struct ufs_hba *hba)
>               return ret;
>       }
>  
> +     ufshcd_set_dev_ref_clk(hba);
> +
>       if (ufshcd_get_max_pwr_mode(hba)) {
>               dev_err(hba->dev,
>                       "%s: Failed getting max supported power mode\n",
> diff --git a/drivers/ufs/ufs.h b/drivers/ufs/ufs.h
> index 53137fae3a8..def39d4ad24 100644
> --- a/drivers/ufs/ufs.h
> +++ b/drivers/ufs/ufs.h
> @@ -172,6 +172,15 @@ enum query_opcode {
>       UPIU_QUERY_OPCODE_TOGGLE_FLAG   = 0x8,
>  };
>  
> +/* bRefClkFreq attribute values */
> +enum ufs_ref_clk_freq {
> +     REF_CLK_FREQ_19_2_MHZ   = 0,
> +     REF_CLK_FREQ_26_MHZ     = 1,
> +     REF_CLK_FREQ_38_4_MHZ   = 2,
> +     REF_CLK_FREQ_52_MHZ     = 3,
> +     REF_CLK_FREQ_INVAL      = -1,
> +};
> +
>  /* Query response result code */
>  enum {
>       QUERY_RESULT_SUCCESS                    = 0x00,
> @@ -684,6 +693,7 @@ struct ufs_hba {
>       u32                     capabilities;
>       u32                     version;
>       u32                     intr_mask;
> +     enum ufs_ref_clk_freq dev_ref_clk_freq;
>       enum ufshcd_quirks      quirks;
>  
>       /* Virtual memory reference */

Reviewed-by: Neha Malcom Francis <[email protected]>

-- 
Thanking You
Neha Malcom Francis

Reply via email to