Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Manivannan Sadhasivam and committed by
Martin K. Petersen
72208ebe 930bd77e

+117
+36
drivers/ufs/core/ufshcd.c
··· 1063 1063 return ret; 1064 1064 } 1065 1065 1066 + int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table, 1067 + struct dev_pm_opp *opp, void *data, 1068 + bool scaling_down) 1069 + { 1070 + struct ufs_hba *hba = dev_get_drvdata(dev); 1071 + struct list_head *head = &hba->clk_list_head; 1072 + struct ufs_clk_info *clki; 1073 + unsigned long freq; 1074 + u8 idx = 0; 1075 + int ret; 1076 + 1077 + list_for_each_entry(clki, head, list) { 1078 + if (!IS_ERR_OR_NULL(clki->clk)) { 1079 + freq = dev_pm_opp_get_freq_indexed(opp, idx++); 1080 + 1081 + /* Do not set rate for clocks having frequency as 0 */ 1082 + if (!freq) 1083 + continue; 1084 + 1085 + ret = clk_set_rate(clki->clk, freq); 1086 + if (ret) { 1087 + dev_err(dev, "%s: %s clk set rate(%ldHz) failed, %d\n", 1088 + __func__, clki->name, freq, ret); 1089 + return ret; 1090 + } 1091 + 1092 + trace_ufshcd_clk_scaling(dev_name(dev), 1093 + (scaling_down ? "scaled down" : "scaled up"), 1094 + clki->name, hba->clk_scaling.target_freq, freq); 1095 + } 1096 + } 1097 + 1098 + return 0; 1099 + } 1100 + EXPORT_SYMBOL_GPL(ufshcd_opp_config_clks); 1101 + 1066 1102 static int ufshcd_opp_set_rate(struct ufs_hba *hba, unsigned long freq) 1067 1103 { 1068 1104 struct dev_pm_opp *opp;
+78
drivers/ufs/host/ufshcd-pltfrm.c
··· 10 10 11 11 #include <linux/module.h> 12 12 #include <linux/platform_device.h> 13 + #include <linux/pm_opp.h> 13 14 #include <linux/pm_runtime.h> 14 15 #include <linux/of.h> 15 16 ··· 213 212 } 214 213 } 215 214 215 + static int ufshcd_parse_operating_points(struct ufs_hba *hba) 216 + { 217 + struct device *dev = hba->dev; 218 + struct device_node *np = dev->of_node; 219 + struct dev_pm_opp_config config = {}; 220 + struct ufs_clk_info *clki; 221 + const char **clk_names; 222 + int cnt, i, ret; 223 + 224 + if (!of_find_property(np, "operating-points-v2", NULL)) 225 + return 0; 226 + 227 + if (of_find_property(np, "freq-table-hz", NULL)) { 228 + dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n", 229 + __func__); 230 + return -EINVAL; 231 + } 232 + 233 + cnt = of_property_count_strings(np, "clock-names"); 234 + if (cnt <= 0) { 235 + dev_err(dev, "%s: Missing clock-names\n", __func__); 236 + return -ENODEV; 237 + } 238 + 239 + /* OPP expects clk_names to be NULL terminated */ 240 + clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL); 241 + if (!clk_names) 242 + return -ENOMEM; 243 + 244 + /* 245 + * We still need to get reference to all clocks as the UFS core uses 246 + * them separately. 247 + */ 248 + for (i = 0; i < cnt; i++) { 249 + ret = of_property_read_string_index(np, "clock-names", i, 250 + &clk_names[i]); 251 + if (ret) 252 + return ret; 253 + 254 + clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL); 255 + if (!clki) 256 + return -ENOMEM; 257 + 258 + clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL); 259 + if (!clki->name) 260 + return -ENOMEM; 261 + 262 + if (!strcmp(clk_names[i], "ref_clk")) 263 + clki->keep_link_active = true; 264 + 265 + list_add_tail(&clki->list, &hba->clk_list_head); 266 + } 267 + 268 + config.clk_names = clk_names, 269 + config.config_clks = ufshcd_opp_config_clks; 270 + 271 + ret = devm_pm_opp_set_config(dev, &config); 272 + if (ret) 273 + return ret; 274 + 275 + ret = devm_pm_opp_of_add_table(dev); 276 + if (ret) { 277 + dev_err(dev, "Failed to add OPP table: %d\n", ret); 278 + return ret; 279 + } 280 + 281 + hba->use_pm_opp = true; 282 + 283 + return 0; 284 + } 285 + 216 286 /** 217 287 * ufshcd_get_pwr_dev_param - get finally agreed attributes for 218 288 * power mode change ··· 449 377 } 450 378 451 379 ufshcd_init_lanes_per_dir(hba); 380 + 381 + err = ufshcd_parse_operating_points(hba); 382 + if (err) { 383 + dev_err(dev, "%s: OPP parse failed %d\n", __func__, err); 384 + goto dealloc_host; 385 + } 452 386 453 387 err = ufshcd_init(hba, mmio_base, irq); 454 388 if (err) {
+3
include/ufs/ufshcd.h
··· 1254 1254 void ufshcd_mcq_enable_esi(struct ufs_hba *hba); 1255 1255 void ufshcd_mcq_config_esi(struct ufs_hba *hba, struct msi_msg *msg); 1256 1256 1257 + int ufshcd_opp_config_clks(struct device *dev, struct opp_table *opp_table, 1258 + struct dev_pm_opp *opp, void *data, 1259 + bool scaling_down); 1257 1260 /** 1258 1261 * ufshcd_set_variant - set variant specific data to the hba 1259 1262 * @hba: per adapter instance