|
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 |
|
@@ -212,6 +213,77 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba) |
212 | 213 | } |
213 | 214 | } |
214 | 215 |
|
| 216 | +static int ufshcd_parse_operating_points(struct ufs_hba *hba) |
| 217 | +{ |
| 218 | + struct device *dev = hba->dev; |
| 219 | + struct device_node *np = dev->of_node; |
| 220 | + struct dev_pm_opp_config config = {}; |
| 221 | + struct ufs_clk_info *clki; |
| 222 | + const char **clk_names; |
| 223 | + int cnt, i, ret; |
| 224 | + |
| 225 | + if (!of_find_property(np, "operating-points-v2", NULL)) |
| 226 | + return 0; |
| 227 | + |
| 228 | + if (of_find_property(np, "freq-table-hz", NULL)) { |
| 229 | + dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n", |
| 230 | + __func__); |
| 231 | + return -EINVAL; |
| 232 | + } |
| 233 | + |
| 234 | + cnt = of_property_count_strings(np, "clock-names"); |
| 235 | + if (cnt <= 0) { |
| 236 | + dev_err(dev, "%s: Missing clock-names\n", __func__); |
| 237 | + return -ENODEV; |
| 238 | + } |
| 239 | + |
| 240 | + /* OPP expects clk_names to be NULL terminated */ |
| 241 | + clk_names = devm_kcalloc(dev, cnt + 1, sizeof(*clk_names), GFP_KERNEL); |
| 242 | + if (!clk_names) |
| 243 | + return -ENOMEM; |
| 244 | + |
| 245 | + /* |
| 246 | + * We still need to get reference to all clocks as the UFS core uses |
| 247 | + * them separately. |
| 248 | + */ |
| 249 | + for (i = 0; i < cnt; i++) { |
| 250 | + ret = of_property_read_string_index(np, "clock-names", i, |
| 251 | + &clk_names[i]); |
| 252 | + if (ret) |
| 253 | + return ret; |
| 254 | + |
| 255 | + clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL); |
| 256 | + if (!clki) |
| 257 | + return -ENOMEM; |
| 258 | + |
| 259 | + clki->name = devm_kstrdup(dev, clk_names[i], GFP_KERNEL); |
| 260 | + if (!clki->name) |
| 261 | + return -ENOMEM; |
| 262 | + |
| 263 | + if (!strcmp(clk_names[i], "ref_clk")) |
| 264 | + clki->keep_link_active = true; |
| 265 | + |
| 266 | + list_add_tail(&clki->list, &hba->clk_list_head); |
| 267 | + } |
| 268 | + |
| 269 | + config.clk_names = clk_names, |
| 270 | + config.config_clks = ufshcd_opp_config_clks; |
| 271 | + |
| 272 | + ret = devm_pm_opp_set_config(dev, &config); |
| 273 | + if (ret) |
| 274 | + return ret; |
| 275 | + |
| 276 | + ret = devm_pm_opp_of_add_table(dev); |
| 277 | + if (ret) { |
| 278 | + dev_err(dev, "Failed to add OPP table: %d\n", ret); |
| 279 | + return ret; |
| 280 | + } |
| 281 | + |
| 282 | + hba->use_pm_opp = true; |
| 283 | + |
| 284 | + return 0; |
| 285 | +} |
| 286 | + |
215 | 287 | /** |
216 | 288 | * ufshcd_get_pwr_dev_param - get finally agreed attributes for |
217 | 289 | * power mode change |
@@ -378,6 +450,12 @@ int ufshcd_pltfrm_init(struct platform_device *pdev, |
378 | 450 |
|
379 | 451 | ufshcd_init_lanes_per_dir(hba); |
380 | 452 |
|
| 453 | + err = ufshcd_parse_operating_points(hba); |
| 454 | + if (err) { |
| 455 | + dev_err(dev, "%s: OPP parse failed %d\n", __func__, err); |
| 456 | + goto dealloc_host; |
| 457 | + } |
| 458 | + |
381 | 459 | err = ufshcd_init(hba, mmio_base, irq); |
382 | 460 | if (err) { |
383 | 461 | dev_err_probe(dev, err, "Initialization failed with error %d\n", |
|
0 commit comments