summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManivannan Sadhasivam <manivannan.sadhasivam@linaro.org>2023-10-12 22:51:27 +0530
committerMartin K. Petersen <martin.petersen@oracle.com>2023-10-16 20:59:21 -0400
commit72208ebe181e38678dce753354233acf0cc5422b (patch)
tree470d5fcab61dd251057c39fa67d9a5a64efe7426
parent930bd77ebe3dc23b18aa49e55e6a515d5663d67a (diff)
scsi: ufs: core: Add support for parsing OPP
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> Link: https://lore.kernel.org/r/20231012172129.65172-4-manivannan.sadhasivam@linaro.org Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
-rw-r--r--drivers/ufs/core/ufshcd.c36
-rw-r--r--drivers/ufs/host/ufshcd-pltfrm.c78
-rw-r--r--include/ufs/ufshcd.h3
3 files changed, 117 insertions, 0 deletions
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 04087ddfcaa9..5c4f2643dde6 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -1063,6 +1063,42 @@ out:
return ret;
}
+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;
+}
+EXPORT_SYMBOL_GPL(ufshcd_opp_config_clks);
+
static int ufshcd_opp_set_rate(struct ufs_hba *hba, unsigned long freq)
{
struct dev_pm_opp *opp;
diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
index 61cf8b957da4..da2558e274b4 100644
--- a/drivers/ufs/host/ufshcd-pltfrm.c
+++ b/drivers/ufs/host/ufshcd-pltfrm.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
@@ -212,6 +213,77 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
}
}
+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
@@ -378,6 +450,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_probe(dev, err, "Initialization failed with error %d\n",
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index fc0d6d37319a..7f0b2c5599cd 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -1254,6 +1254,9 @@ void ufshcd_mcq_make_queues_operational(struct ufs_hba *hba);
void ufshcd_mcq_enable_esi(struct ufs_hba *hba);
void ufshcd_mcq_config_esi(struct ufs_hba *hba, struct msi_msg *msg);
+int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table,
+ struct dev_pm_opp *opp, void *data,
+ bool scaling_down);
/**
* ufshcd_set_variant - set variant specific data to the hba
* @hba: per adapter instance