diff mbox series

[11/14] scsi: ufs: host: Add support for parsing OPP

Message ID 20230712103213.101770-14-manivannan.sadhasivam@linaro.org
State Superseded
Headers show
Series UFS: Add OPP and interconnect support | expand

Commit Message

Manivannan Sadhasivam July 12, 2023, 10:32 a.m. UTC
OPP framework can be used to scale the clocks along with other entities
such as regulators, performance state etc... So let's add support for
parsing OPP from devicetree. OPP support in devicetree is added through
the "operating-points-v2" property which accepts the OPP table defining
clock frequency, regulator voltage, power domain performance state etc...

Since the UFS controller requires multiple clocks to be controlled for
proper working, devm_pm_opp_set_config() has been used which supports
scaling multiple clocks through custom ufshcd_opp_config_clks() callback.

It should be noted that the OPP support is not compatible with the old
"freq-table-hz" property. So only one can be used at a time even though
the UFS core supports both.

Co-developed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
---
 drivers/ufs/host/ufshcd-pltfrm.c | 116 +++++++++++++++++++++++++++++++
 1 file changed, 116 insertions(+)

Comments

Manivannan Sadhasivam July 12, 2023, 4:34 p.m. UTC | #1
On Wed, Jul 12, 2023 at 04:15:12PM +0300, Dmitry Baryshkov wrote:
> On 12/07/2023 13:32, Manivannan Sadhasivam wrote:
> > OPP framework can be used to scale the clocks along with other entities
> > such as regulators, performance state etc... So let's add support for
> > parsing OPP from devicetree. OPP support in devicetree is added through
> > the "operating-points-v2" property which accepts the OPP table defining
> > clock frequency, regulator voltage, power domain performance state etc...
> > 
> > Since the UFS controller requires multiple clocks to be controlled for
> > proper working, devm_pm_opp_set_config() has been used which supports
> > scaling multiple clocks through custom ufshcd_opp_config_clks() callback.
> > 
> > It should be noted that the OPP support is not compatible with the old
> > "freq-table-hz" property. So only one can be used at a time even though
> > the UFS core supports both.
> > 
> > Co-developed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> > Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> > Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> > ---
> >   drivers/ufs/host/ufshcd-pltfrm.c | 116 +++++++++++++++++++++++++++++++
> >   1 file changed, 116 insertions(+)
> > 
> > diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
> > index 0b7430033047..068c22378c88 100644
> > --- a/drivers/ufs/host/ufshcd-pltfrm.c
> > +++ b/drivers/ufs/host/ufshcd-pltfrm.c
> > @@ -8,8 +8,10 @@
> >    *	Vinayak Holikatti <h.vinayak@samsung.com>
> >    */
> > +#include <linux/clk.h>
> >   #include <linux/module.h>
> >   #include <linux/platform_device.h>
> > +#include <linux/pm_opp.h>
> >   #include <linux/pm_runtime.h>
> >   #include <linux/of.h>
> > @@ -17,6 +19,8 @@
> >   #include "ufshcd-pltfrm.h"
> >   #include <ufs/unipro.h>
> > +#include <trace/events/ufs.h>
> > +
> >   #define UFSHCD_DEFAULT_LANES_PER_DIRECTION		2
> >   static int ufshcd_parse_clock_info(struct ufs_hba *hba)
> > @@ -205,6 +209,112 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
> >   	}
> >   }
> > +static int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
> > +				  struct dev_pm_opp *opp, void *data,
> > +				  bool scaling_down)
> > +{
> > +	struct ufs_hba *hba = dev_get_drvdata(dev);
> > +	struct list_head *head = &hba->clk_list_head;
> > +	struct ufs_clk_info *clki;
> > +	unsigned long freq;
> > +	u8 idx = 0;
> > +	int ret;
> > +
> > +	list_for_each_entry(clki, head, list) {
> > +		if (!IS_ERR_OR_NULL(clki->clk)) {
> > +			freq = dev_pm_opp_get_freq_indexed(opp, idx++);
> > +
> > +			/* Do not set rate for clocks having frequency as 0 */
> > +			if (!freq)
> > +				continue;
> 
> Can we omit these clocks from the opp table? I don't think they serve any
> purpose.
> 

No, we cannot. OPP requires the clocks and opp-hz to be of same length. And we
cannot omit those clocks as well since linux needs to gate control them.

> Maybe it would even make sense to move this function to drivers/opp then, as
> it will be generic enough.
> 

There is already a generic function available in OPP core. But we cannot use it
as we need to skip setting 0 freq and that's not applicable in OPP core as
discussed with Viresh offline.

- Mani

> > +
> > +			ret = clk_set_rate(clki->clk, freq);
> > +			if (ret) {
> > +				dev_err(dev, "%s: %s clk set rate(%ldHz) failed, %d\n",
> > +					__func__, clki->name, freq, ret);
> > +				return ret;
> > +			}
> > +
> > +			trace_ufshcd_clk_scaling(dev_name(dev),
> > +				(scaling_down ? "scaled down" : "scaled up"),
> > +				clki->name, hba->clk_scaling.target_freq, freq);
> > +		}
> > +	}
> > +
> > +	return 0;
> > +} > +
> > +static int ufshcd_parse_operating_points(struct ufs_hba *hba)
> > +{
> > +	struct device *dev = hba->dev;
> > +	struct device_node *np = dev->of_node;
> > +	struct dev_pm_opp_config config = {};
> > +	struct ufs_clk_info *clki;
> > +	const char **clk_names;
> > +	int cnt, i, ret;
> > +
> > +	if (!of_find_property(np, "operating-points-v2", NULL))
> > +		return 0;
> > +
> > +	if (of_find_property(np, "freq-table-hz", NULL)) {
> > +		dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n",
> > +			 __func__);
> > +		return -EINVAL;
> > +	}
> > +
> > +	cnt = of_property_count_strings(np, "clock-names");
> > +	if (cnt <= 0) {
> > +		dev_err(dev, "%s: Missing clock-names\n",  __func__);
> > +		return -ENODEV;
> > +	}
> > +
> > +	/* OPP expects clk_names to be NULL terminated */
> > +	clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL);
> > +	if (!clk_names)
> > +		return -ENOMEM;
> > +
> > +	/*
> > +	 * We still need to get reference to all clocks as the UFS core uses
> > +	 * them separately.
> > +	 */
> > +	for (i = 0; i < cnt; i++) {
> > +		ret = of_property_read_string_index(np, "clock-names", i,
> > +						    &clk_names[i]);
> > +		if (ret)
> > +			return ret;
> > +
> > +		clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
> > +		if (!clki)
> > +			return -ENOMEM;
> > +
> > +		clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL);
> > +		if (!clki->name)
> > +			return -ENOMEM;
> > +
> > +		if (!strcmp(clk_names[i], "ref_clk"))
> > +			clki->keep_link_active = true;
> > +
> > +		list_add_tail(&clki->list, &hba->clk_list_head);
> > +	}
> > +
> > +	config.clk_names = clk_names,
> > +	config.config_clks = ufshcd_opp_config_clks;
> > +
> > +	ret = devm_pm_opp_set_config(dev, &config);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_pm_opp_of_add_table(dev);
> > +	if (ret) {
> > +		dev_err(dev, "Failed to add OPP table: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	hba->use_pm_opp = true;
> > +
> > +	return 0;
> > +}
> > +
> >   /**
> >    * ufshcd_get_pwr_dev_param - get finally agreed attributes for
> >    *                            power mode change
> > @@ -371,6 +481,12 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
> >   	ufshcd_init_lanes_per_dir(hba);
> > +	err = ufshcd_parse_operating_points(hba);
> > +	if (err) {
> > +		dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
> > +		goto dealloc_host;
> > +	}
> > +
> >   	err = ufshcd_init(hba, mmio_base, irq);
> >   	if (err) {
> >   		dev_err(dev, "Initialization failed\n");
> 
> -- 
> With best wishes
> Dmitry
>
Dmitry Baryshkov July 12, 2023, 4:48 p.m. UTC | #2
On Wed, 12 Jul 2023 at 19:34, Manivannan Sadhasivam
<manivannan.sadhasivam@linaro.org> wrote:
>
> On Wed, Jul 12, 2023 at 04:15:12PM +0300, Dmitry Baryshkov wrote:
> > On 12/07/2023 13:32, Manivannan Sadhasivam wrote:
> > > OPP framework can be used to scale the clocks along with other entities
> > > such as regulators, performance state etc... So let's add support for
> > > parsing OPP from devicetree. OPP support in devicetree is added through
> > > the "operating-points-v2" property which accepts the OPP table defining
> > > clock frequency, regulator voltage, power domain performance state etc...
> > >
> > > Since the UFS controller requires multiple clocks to be controlled for
> > > proper working, devm_pm_opp_set_config() has been used which supports
> > > scaling multiple clocks through custom ufshcd_opp_config_clks() callback.
> > >
> > > It should be noted that the OPP support is not compatible with the old
> > > "freq-table-hz" property. So only one can be used at a time even though
> > > the UFS core supports both.
> > >
> > > Co-developed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> > > Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> > > Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
> > > ---
> > >   drivers/ufs/host/ufshcd-pltfrm.c | 116 +++++++++++++++++++++++++++++++
> > >   1 file changed, 116 insertions(+)
> > >
> > > diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
> > > index 0b7430033047..068c22378c88 100644
> > > --- a/drivers/ufs/host/ufshcd-pltfrm.c
> > > +++ b/drivers/ufs/host/ufshcd-pltfrm.c
> > > @@ -8,8 +8,10 @@
> > >    *        Vinayak Holikatti <h.vinayak@samsung.com>
> > >    */
> > > +#include <linux/clk.h>
> > >   #include <linux/module.h>
> > >   #include <linux/platform_device.h>
> > > +#include <linux/pm_opp.h>
> > >   #include <linux/pm_runtime.h>
> > >   #include <linux/of.h>
> > > @@ -17,6 +19,8 @@
> > >   #include "ufshcd-pltfrm.h"
> > >   #include <ufs/unipro.h>
> > > +#include <trace/events/ufs.h>
> > > +
> > >   #define UFSHCD_DEFAULT_LANES_PER_DIRECTION                2
> > >   static int ufshcd_parse_clock_info(struct ufs_hba *hba)
> > > @@ -205,6 +209,112 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
> > >     }
> > >   }
> > > +static int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
> > > +                             struct dev_pm_opp *opp, void *data,
> > > +                             bool scaling_down)
> > > +{
> > > +   struct ufs_hba *hba = dev_get_drvdata(dev);
> > > +   struct list_head *head = &hba->clk_list_head;
> > > +   struct ufs_clk_info *clki;
> > > +   unsigned long freq;
> > > +   u8 idx = 0;
> > > +   int ret;
> > > +
> > > +   list_for_each_entry(clki, head, list) {
> > > +           if (!IS_ERR_OR_NULL(clki->clk)) {
> > > +                   freq = dev_pm_opp_get_freq_indexed(opp, idx++);
> > > +
> > > +                   /* Do not set rate for clocks having frequency as 0 */
> > > +                   if (!freq)
> > > +                           continue;
> >
> > Can we omit these clocks from the opp table? I don't think they serve any
> > purpose.
> >
>
> No, we cannot. OPP requires the clocks and opp-hz to be of same length. And we
> cannot omit those clocks as well since linux needs to gate control them.

Hmm, I thought we push the list of "interesting" clocks through
config->clock_names.

>
> > Maybe it would even make sense to move this function to drivers/opp then, as
> > it will be generic enough.
> >
>
> There is already a generic function available in OPP core. But we cannot use it
> as we need to skip setting 0 freq and that's not applicable in OPP core as
> discussed with Viresh offline.

Ack.

>
> - Mani
>
> > > +
> > > +                   ret = clk_set_rate(clki->clk, freq);
> > > +                   if (ret) {
> > > +                           dev_err(dev, "%s: %s clk set rate(%ldHz) failed, %d\n",
> > > +                                   __func__, clki->name, freq, ret);
> > > +                           return ret;
> > > +                   }
> > > +
> > > +                   trace_ufshcd_clk_scaling(dev_name(dev),
> > > +                           (scaling_down ? "scaled down" : "scaled up"),
> > > +                           clki->name, hba->clk_scaling.target_freq, freq);
> > > +           }
> > > +   }
> > > +
> > > +   return 0;
> > > +} > +
> > > +static int ufshcd_parse_operating_points(struct ufs_hba *hba)
> > > +{
> > > +   struct device *dev = hba->dev;
> > > +   struct device_node *np = dev->of_node;
> > > +   struct dev_pm_opp_config config = {};
> > > +   struct ufs_clk_info *clki;
> > > +   const char **clk_names;
> > > +   int cnt, i, ret;
> > > +
> > > +   if (!of_find_property(np, "operating-points-v2", NULL))
> > > +           return 0;
> > > +
> > > +   if (of_find_property(np, "freq-table-hz", NULL)) {
> > > +           dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n",
> > > +                    __func__);
> > > +           return -EINVAL;
> > > +   }
> > > +
> > > +   cnt = of_property_count_strings(np, "clock-names");
> > > +   if (cnt <= 0) {
> > > +           dev_err(dev, "%s: Missing clock-names\n",  __func__);
> > > +           return -ENODEV;
> > > +   }
> > > +
> > > +   /* OPP expects clk_names to be NULL terminated */
> > > +   clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL);
> > > +   if (!clk_names)
> > > +           return -ENOMEM;
> > > +
> > > +   /*
> > > +    * We still need to get reference to all clocks as the UFS core uses
> > > +    * them separately.
> > > +    */
> > > +   for (i = 0; i < cnt; i++) {
> > > +           ret = of_property_read_string_index(np, "clock-names", i,
> > > +                                               &clk_names[i]);
> > > +           if (ret)
> > > +                   return ret;
> > > +
> > > +           clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
> > > +           if (!clki)
> > > +                   return -ENOMEM;
> > > +
> > > +           clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL);
> > > +           if (!clki->name)
> > > +                   return -ENOMEM;
> > > +
> > > +           if (!strcmp(clk_names[i], "ref_clk"))
> > > +                   clki->keep_link_active = true;
> > > +
> > > +           list_add_tail(&clki->list, &hba->clk_list_head);
> > > +   }
> > > +
> > > +   config.clk_names = clk_names,
> > > +   config.config_clks = ufshcd_opp_config_clks;
> > > +
> > > +   ret = devm_pm_opp_set_config(dev, &config);
> > > +   if (ret)
> > > +           return ret;
> > > +
> > > +   ret = devm_pm_opp_of_add_table(dev);
> > > +   if (ret) {
> > > +           dev_err(dev, "Failed to add OPP table: %d\n", ret);
> > > +           return ret;
> > > +   }
> > > +
> > > +   hba->use_pm_opp = true;
> > > +
> > > +   return 0;
> > > +}
> > > +
> > >   /**
> > >    * ufshcd_get_pwr_dev_param - get finally agreed attributes for
> > >    *                            power mode change
> > > @@ -371,6 +481,12 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
> > >     ufshcd_init_lanes_per_dir(hba);
> > > +   err = ufshcd_parse_operating_points(hba);
> > > +   if (err) {
> > > +           dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
> > > +           goto dealloc_host;
> > > +   }
> > > +
> > >     err = ufshcd_init(hba, mmio_base, irq);
> > >     if (err) {
> > >             dev_err(dev, "Initialization failed\n");
> >
> > --
> > With best wishes
> > Dmitry
> >
>
> --
> மணிவண்ணன் சதாசிவம்
Viresh Kumar July 13, 2023, 4:09 a.m. UTC | #3
On 12-07-23, 19:48, Dmitry Baryshkov wrote:
> On Wed, 12 Jul 2023 at 19:34, Manivannan Sadhasivam
> <manivannan.sadhasivam@linaro.org> wrote:
> > On Wed, Jul 12, 2023 at 04:15:12PM +0300, Dmitry Baryshkov wrote:
> > > On 12/07/2023 13:32, Manivannan Sadhasivam wrote:

> > > > +static int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
> > > > +                             struct dev_pm_opp *opp, void *data,
> > > > +                             bool scaling_down)
> > > > +{
> > > > +   struct ufs_hba *hba = dev_get_drvdata(dev);
> > > > +   struct list_head *head = &hba->clk_list_head;
> > > > +   struct ufs_clk_info *clki;
> > > > +   unsigned long freq;
> > > > +   u8 idx = 0;
> > > > +   int ret;
> > > > +
> > > > +   list_for_each_entry(clki, head, list) {
> > > > +           if (!IS_ERR_OR_NULL(clki->clk)) {
> > > > +                   freq = dev_pm_opp_get_freq_indexed(opp, idx++);
> > > > +
> > > > +                   /* Do not set rate for clocks having frequency as 0 */
> > > > +                   if (!freq)
> > > > +                           continue;
> > >
> > > Can we omit these clocks from the opp table? I don't think they serve any
> > > purpose.
> > >
> >
> > No, we cannot. OPP requires the clocks and opp-hz to be of same length.

I am okay with having a patch for the OPP core to modify this
behavior, as I told privately earlier.

> > And we
> > cannot omit those clocks as well since linux needs to gate control them.
> 
> Hmm, I thought we push the list of "interesting" clocks through
> config->clock_names.

Yes, another way to solve this would be keep the interesting clocks in
the beginning in "clock-names" field and let the platform pass only
those to the OPP core.

> >
> > > Maybe it would even make sense to move this function to drivers/opp then, as
> > > it will be generic enough.
> > >
> >
> > There is already a generic function available in OPP core. But we cannot use it
> > as we need to skip setting 0 freq and that's not applicable in OPP core as
> > discussed with Viresh offline.
> 
> Ack.

I am okay with either of the solutions, it is for you guys to decide
what works better for your platform.
Manivannan Sadhasivam July 13, 2023, 5:05 a.m. UTC | #4
On Thu, Jul 13, 2023 at 09:39:18AM +0530, Viresh Kumar wrote:
> On 12-07-23, 19:48, Dmitry Baryshkov wrote:
> > On Wed, 12 Jul 2023 at 19:34, Manivannan Sadhasivam
> > <manivannan.sadhasivam@linaro.org> wrote:
> > > On Wed, Jul 12, 2023 at 04:15:12PM +0300, Dmitry Baryshkov wrote:
> > > > On 12/07/2023 13:32, Manivannan Sadhasivam wrote:
> 
> > > > > +static int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
> > > > > +                             struct dev_pm_opp *opp, void *data,
> > > > > +                             bool scaling_down)
> > > > > +{
> > > > > +   struct ufs_hba *hba = dev_get_drvdata(dev);
> > > > > +   struct list_head *head = &hba->clk_list_head;
> > > > > +   struct ufs_clk_info *clki;
> > > > > +   unsigned long freq;
> > > > > +   u8 idx = 0;
> > > > > +   int ret;
> > > > > +
> > > > > +   list_for_each_entry(clki, head, list) {
> > > > > +           if (!IS_ERR_OR_NULL(clki->clk)) {
> > > > > +                   freq = dev_pm_opp_get_freq_indexed(opp, idx++);
> > > > > +
> > > > > +                   /* Do not set rate for clocks having frequency as 0 */
> > > > > +                   if (!freq)
> > > > > +                           continue;
> > > >
> > > > Can we omit these clocks from the opp table? I don't think they serve any
> > > > purpose.
> > > >
> > >
> > > No, we cannot. OPP requires the clocks and opp-hz to be of same length.
> 
> I am okay with having a patch for the OPP core to modify this
> behavior, as I told privately earlier.
> 
> > > And we
> > > cannot omit those clocks as well since linux needs to gate control them.
> > 
> > Hmm, I thought we push the list of "interesting" clocks through
> > config->clock_names.
> 
> Yes, another way to solve this would be keep the interesting clocks in
> the beginning in "clock-names" field and let the platform pass only
> those to the OPP core.
> 
> > >
> > > > Maybe it would even make sense to move this function to drivers/opp then, as
> > > > it will be generic enough.
> > > >
> > >
> > > There is already a generic function available in OPP core. But we cannot use it
> > > as we need to skip setting 0 freq and that's not applicable in OPP core as
> > > discussed with Viresh offline.
> > 
> > Ack.
> 
> I am okay with either of the solutions, it is for you guys to decide
> what works better for your platform.
> 

We can settle with this custom callback for now. If there are drivers in the
future trying to do the same (skipping 0 freq) then we can generalize.

- Mani

> -- 
> viresh
Viresh Kumar July 13, 2023, 5:12 a.m. UTC | #5
On 13-07-23, 10:35, Manivannan Sadhasivam wrote:
> We can settle with this custom callback for now. If there are drivers in the
> future trying to do the same (skipping 0 freq) then we can generalize.

Just for completeness, there isn't much to generalize here apart from
changing the DT order of clocks. Isn't it ?

The change require for the OPP core makes sense, I will probably just
push it anyway.
Manivannan Sadhasivam July 13, 2023, 5:28 a.m. UTC | #6
On Thu, Jul 13, 2023 at 10:42:35AM +0530, Viresh Kumar wrote:
> On 13-07-23, 10:35, Manivannan Sadhasivam wrote:
> > We can settle with this custom callback for now. If there are drivers in the
> > future trying to do the same (skipping 0 freq) then we can generalize.
> 
> Just for completeness, there isn't much to generalize here apart from
> changing the DT order of clocks. Isn't it ?
> 

Even with changing the order, driver has to know the "interesting" clocks
beforehand. But that varies between platforms (this is a generic driver for
ufshc platforms).

And I do not know if clocks have any dependency between them, atleast not in
Qcom platforms. But not sure about others.

- Mani

> The change require for the OPP core makes sense, I will probably just
> push it anyway.
> 
> -- 
> viresh
Viresh Kumar July 13, 2023, 5:43 a.m. UTC | #7
Okay, sorry about missing one point first. I thought we are adding the
clk config callback (which neglects 0 frequencies) to a Qcom only
driver and so was okay-ish with that. But now that I realize that this
is a generic driver instead (my mistake here), I wonder if it is the
right thing to do anymore.

On 13-07-23, 10:58, Manivannan Sadhasivam wrote:
> On Thu, Jul 13, 2023 at 10:42:35AM +0530, Viresh Kumar wrote:
> > On 13-07-23, 10:35, Manivannan Sadhasivam wrote:
> > > We can settle with this custom callback for now. If there are drivers in the
> > > future trying to do the same (skipping 0 freq) then we can generalize.
> > 
> > Just for completeness, there isn't much to generalize here apart from
> > changing the DT order of clocks. Isn't it ?
> > 
> 
> Even with changing the order, driver has to know the "interesting" clocks
> beforehand. But that varies between platforms (this is a generic driver for
> ufshc platforms).
> 
> And I do not know if clocks have any dependency between them, atleast not in
> Qcom platforms. But not sure about others.

Maybe this requires some sort of callback, per-platform, which gets
you these details or the struct dev_pm_opp_config itself (so platforms
can choose the callback too, in case order is important).

> > The change require for the OPP core makes sense, I will probably just
> > push it anyway.

I tried to look at this code and I think it is doing the right thing
currently, i.e. it matches clk-count with the number of frequencies in
opp-hz, which should turn out to be the same in your case. So nothing
to change here I guess.
diff mbox series

Patch

diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
index 0b7430033047..068c22378c88 100644
--- a/drivers/ufs/host/ufshcd-pltfrm.c
+++ b/drivers/ufs/host/ufshcd-pltfrm.c
@@ -8,8 +8,10 @@ 
  *	Vinayak Holikatti <h.vinayak@samsung.com>
  */
 
+#include <linux/clk.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/pm_opp.h>
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
 
@@ -17,6 +19,8 @@ 
 #include "ufshcd-pltfrm.h"
 #include <ufs/unipro.h>
 
+#include <trace/events/ufs.h>
+
 #define UFSHCD_DEFAULT_LANES_PER_DIRECTION		2
 
 static int ufshcd_parse_clock_info(struct ufs_hba *hba)
@@ -205,6 +209,112 @@  static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
 	}
 }
 
+static int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
+				  struct dev_pm_opp *opp, void *data,
+				  bool scaling_down)
+{
+	struct ufs_hba *hba = dev_get_drvdata(dev);
+	struct list_head *head = &hba->clk_list_head;
+	struct ufs_clk_info *clki;
+	unsigned long freq;
+	u8 idx = 0;
+	int ret;
+
+	list_for_each_entry(clki, head, list) {
+		if (!IS_ERR_OR_NULL(clki->clk)) {
+			freq = dev_pm_opp_get_freq_indexed(opp, idx++);
+
+			/* Do not set rate for clocks having frequency as 0 */
+			if (!freq)
+				continue;
+
+			ret = clk_set_rate(clki->clk, freq);
+			if (ret) {
+				dev_err(dev, "%s: %s clk set rate(%ldHz) failed, %d\n",
+					__func__, clki->name, freq, ret);
+				return ret;
+			}
+
+			trace_ufshcd_clk_scaling(dev_name(dev),
+				(scaling_down ? "scaled down" : "scaled up"),
+				clki->name, hba->clk_scaling.target_freq, freq);
+		}
+	}
+
+	return 0;
+}
+
+static int ufshcd_parse_operating_points(struct ufs_hba *hba)
+{
+	struct device *dev = hba->dev;
+	struct device_node *np = dev->of_node;
+	struct dev_pm_opp_config config = {};
+	struct ufs_clk_info *clki;
+	const char **clk_names;
+	int cnt, i, ret;
+
+	if (!of_find_property(np, "operating-points-v2", NULL))
+		return 0;
+
+	if (of_find_property(np, "freq-table-hz", NULL)) {
+		dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n",
+			 __func__);
+		return -EINVAL;
+	}
+
+	cnt = of_property_count_strings(np, "clock-names");
+	if (cnt <= 0) {
+		dev_err(dev, "%s: Missing clock-names\n",  __func__);
+		return -ENODEV;
+	}
+
+	/* OPP expects clk_names to be NULL terminated */
+	clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL);
+	if (!clk_names)
+		return -ENOMEM;
+
+	/*
+	 * We still need to get reference to all clocks as the UFS core uses
+	 * them separately.
+	 */
+	for (i = 0; i < cnt; i++) {
+		ret = of_property_read_string_index(np, "clock-names", i,
+						    &clk_names[i]);
+		if (ret)
+			return ret;
+
+		clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL);
+		if (!clki)
+			return -ENOMEM;
+
+		clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL);
+		if (!clki->name)
+			return -ENOMEM;
+
+		if (!strcmp(clk_names[i], "ref_clk"))
+			clki->keep_link_active = true;
+
+		list_add_tail(&clki->list, &hba->clk_list_head);
+	}
+
+	config.clk_names = clk_names,
+	config.config_clks = ufshcd_opp_config_clks;
+
+	ret = devm_pm_opp_set_config(dev, &config);
+	if (ret)
+		return ret;
+
+	ret = devm_pm_opp_of_add_table(dev);
+	if (ret) {
+		dev_err(dev, "Failed to add OPP table: %d\n", ret);
+		return ret;
+	}
+
+	hba->use_pm_opp = true;
+
+	return 0;
+}
+
 /**
  * ufshcd_get_pwr_dev_param - get finally agreed attributes for
  *                            power mode change
@@ -371,6 +481,12 @@  int ufshcd_pltfrm_init(struct platform_device *pdev,
 
 	ufshcd_init_lanes_per_dir(hba);
 
+	err = ufshcd_parse_operating_points(hba);
+	if (err) {
+		dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
+		goto dealloc_host;
+	}
+
 	err = ufshcd_init(hba, mmio_base, irq);
 	if (err) {
 		dev_err(dev, "Initialization failed\n");