1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Universal Flash Storage Host controller Platform bus based glue driver |
4 | * Copyright (C) 2011-2013 Samsung India Software Operations |
5 | * |
6 | * Authors: |
7 | * Santosh Yaraganavi <santosh.sy@samsung.com> |
8 | * Vinayak Holikatti <h.vinayak@samsung.com> |
9 | */ |
10 | |
11 | #include <linux/clk.h> |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/pm_opp.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/of.h> |
17 | |
18 | #include <ufs/ufshcd.h> |
19 | #include "ufshcd-pltfrm.h" |
20 | #include <ufs/unipro.h> |
21 | |
22 | #define UFSHCD_DEFAULT_LANES_PER_DIRECTION 2 |
23 | |
24 | static int ufshcd_parse_clock_info(struct ufs_hba *hba) |
25 | { |
26 | int ret = 0; |
27 | int cnt; |
28 | int i; |
29 | struct device *dev = hba->dev; |
30 | struct device_node *np = dev->of_node; |
31 | const char *name; |
32 | u32 *clkfreq = NULL; |
33 | struct ufs_clk_info *clki; |
34 | int len = 0; |
35 | size_t sz = 0; |
36 | |
37 | if (!np) |
38 | goto out; |
39 | |
40 | cnt = of_property_count_strings(np, propname: "clock-names" ); |
41 | if (!cnt || (cnt == -EINVAL)) { |
42 | dev_info(dev, "%s: Unable to find clocks, assuming enabled\n" , |
43 | __func__); |
44 | } else if (cnt < 0) { |
45 | dev_err(dev, "%s: count clock strings failed, err %d\n" , |
46 | __func__, cnt); |
47 | ret = cnt; |
48 | } |
49 | |
50 | if (cnt <= 0) |
51 | goto out; |
52 | |
53 | if (!of_get_property(node: np, name: "freq-table-hz" , lenp: &len)) { |
54 | dev_info(dev, "freq-table-hz property not specified\n" ); |
55 | goto out; |
56 | } |
57 | |
58 | if (len <= 0) |
59 | goto out; |
60 | |
61 | sz = len / sizeof(*clkfreq); |
62 | if (sz != 2 * cnt) { |
63 | dev_err(dev, "%s len mismatch\n" , "freq-table-hz" ); |
64 | ret = -EINVAL; |
65 | goto out; |
66 | } |
67 | |
68 | clkfreq = devm_kcalloc(dev, n: sz, size: sizeof(*clkfreq), |
69 | GFP_KERNEL); |
70 | if (!clkfreq) { |
71 | ret = -ENOMEM; |
72 | goto out; |
73 | } |
74 | |
75 | ret = of_property_read_u32_array(np, propname: "freq-table-hz" , |
76 | out_values: clkfreq, sz); |
77 | if (ret && (ret != -EINVAL)) { |
78 | dev_err(dev, "%s: error reading array %d\n" , |
79 | "freq-table-hz" , ret); |
80 | return ret; |
81 | } |
82 | |
83 | for (i = 0; i < sz; i += 2) { |
84 | ret = of_property_read_string_index(np, propname: "clock-names" , index: i/2, |
85 | output: &name); |
86 | if (ret) |
87 | goto out; |
88 | |
89 | clki = devm_kzalloc(dev, size: sizeof(*clki), GFP_KERNEL); |
90 | if (!clki) { |
91 | ret = -ENOMEM; |
92 | goto out; |
93 | } |
94 | |
95 | clki->min_freq = clkfreq[i]; |
96 | clki->max_freq = clkfreq[i+1]; |
97 | clki->name = devm_kstrdup(dev, s: name, GFP_KERNEL); |
98 | if (!clki->name) { |
99 | ret = -ENOMEM; |
100 | goto out; |
101 | } |
102 | |
103 | if (!strcmp(name, "ref_clk" )) |
104 | clki->keep_link_active = true; |
105 | dev_dbg(dev, "%s: min %u max %u name %s\n" , "freq-table-hz" , |
106 | clki->min_freq, clki->max_freq, clki->name); |
107 | list_add_tail(new: &clki->list, head: &hba->clk_list_head); |
108 | } |
109 | out: |
110 | return ret; |
111 | } |
112 | |
113 | static bool phandle_exists(const struct device_node *np, |
114 | const char *phandle_name, int index) |
115 | { |
116 | struct device_node *parse_np = of_parse_phandle(np, phandle_name, index); |
117 | |
118 | if (parse_np) |
119 | of_node_put(node: parse_np); |
120 | |
121 | return parse_np != NULL; |
122 | } |
123 | |
124 | #define MAX_PROP_SIZE 32 |
125 | int ufshcd_populate_vreg(struct device *dev, const char *name, |
126 | struct ufs_vreg **out_vreg, bool skip_current) |
127 | { |
128 | char prop_name[MAX_PROP_SIZE]; |
129 | struct ufs_vreg *vreg = NULL; |
130 | struct device_node *np = dev->of_node; |
131 | |
132 | if (!np) { |
133 | dev_err(dev, "%s: non DT initialization\n" , __func__); |
134 | goto out; |
135 | } |
136 | |
137 | snprintf(buf: prop_name, MAX_PROP_SIZE, fmt: "%s-supply" , name); |
138 | if (!phandle_exists(np, phandle_name: prop_name, index: 0)) { |
139 | dev_info(dev, "%s: Unable to find %s regulator, assuming enabled\n" , |
140 | __func__, prop_name); |
141 | goto out; |
142 | } |
143 | |
144 | vreg = devm_kzalloc(dev, size: sizeof(*vreg), GFP_KERNEL); |
145 | if (!vreg) |
146 | return -ENOMEM; |
147 | |
148 | vreg->name = devm_kstrdup(dev, s: name, GFP_KERNEL); |
149 | if (!vreg->name) |
150 | return -ENOMEM; |
151 | |
152 | if (skip_current) { |
153 | vreg->max_uA = 0; |
154 | goto out; |
155 | } |
156 | |
157 | snprintf(buf: prop_name, MAX_PROP_SIZE, fmt: "%s-max-microamp" , name); |
158 | if (of_property_read_u32(np, propname: prop_name, out_value: &vreg->max_uA)) { |
159 | dev_info(dev, "%s: unable to find %s\n" , __func__, prop_name); |
160 | vreg->max_uA = 0; |
161 | } |
162 | out: |
163 | *out_vreg = vreg; |
164 | return 0; |
165 | } |
166 | EXPORT_SYMBOL_GPL(ufshcd_populate_vreg); |
167 | |
168 | /** |
169 | * ufshcd_parse_regulator_info - get regulator info from device tree |
170 | * @hba: per adapter instance |
171 | * |
172 | * Get regulator info from device tree for vcc, vccq, vccq2 power supplies. |
173 | * If any of the supplies are not defined it is assumed that they are always-on |
174 | * and hence return zero. If the property is defined but parsing is failed |
175 | * then return corresponding error. |
176 | * |
177 | * Return: 0 upon success; < 0 upon failure. |
178 | */ |
179 | static int ufshcd_parse_regulator_info(struct ufs_hba *hba) |
180 | { |
181 | int err; |
182 | struct device *dev = hba->dev; |
183 | struct ufs_vreg_info *info = &hba->vreg_info; |
184 | |
185 | err = ufshcd_populate_vreg(dev, "vdd-hba" , &info->vdd_hba, true); |
186 | if (err) |
187 | goto out; |
188 | |
189 | err = ufshcd_populate_vreg(dev, "vcc" , &info->vcc, false); |
190 | if (err) |
191 | goto out; |
192 | |
193 | err = ufshcd_populate_vreg(dev, "vccq" , &info->vccq, false); |
194 | if (err) |
195 | goto out; |
196 | |
197 | err = ufshcd_populate_vreg(dev, "vccq2" , &info->vccq2, false); |
198 | out: |
199 | return err; |
200 | } |
201 | |
202 | static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba) |
203 | { |
204 | struct device *dev = hba->dev; |
205 | int ret; |
206 | |
207 | ret = of_property_read_u32(np: dev->of_node, propname: "lanes-per-direction" , |
208 | out_value: &hba->lanes_per_direction); |
209 | if (ret) { |
210 | dev_dbg(hba->dev, |
211 | "%s: failed to read lanes-per-direction, ret=%d\n" , |
212 | __func__, ret); |
213 | hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; |
214 | } |
215 | } |
216 | |
217 | /** |
218 | * ufshcd_parse_clock_min_max_freq - Parse MIN and MAX clocks freq |
219 | * @hba: per adapter instance |
220 | * |
221 | * This function parses MIN and MAX frequencies of all clocks required |
222 | * by the host drivers. |
223 | * |
224 | * Returns 0 for success and non-zero for failure |
225 | */ |
226 | static int ufshcd_parse_clock_min_max_freq(struct ufs_hba *hba) |
227 | { |
228 | struct list_head *head = &hba->clk_list_head; |
229 | struct ufs_clk_info *clki; |
230 | struct dev_pm_opp *opp; |
231 | unsigned long freq; |
232 | u8 idx = 0; |
233 | |
234 | list_for_each_entry(clki, head, list) { |
235 | if (!clki->name) |
236 | continue; |
237 | |
238 | clki->clk = devm_clk_get(dev: hba->dev, id: clki->name); |
239 | if (IS_ERR(ptr: clki->clk)) |
240 | continue; |
241 | |
242 | /* Find Max Freq */ |
243 | freq = ULONG_MAX; |
244 | opp = dev_pm_opp_find_freq_floor_indexed(dev: hba->dev, freq: &freq, index: idx); |
245 | if (IS_ERR(ptr: opp)) { |
246 | dev_err(hba->dev, "Failed to find OPP for MAX frequency\n" ); |
247 | return PTR_ERR(ptr: opp); |
248 | } |
249 | clki->max_freq = dev_pm_opp_get_freq_indexed(opp, index: idx); |
250 | dev_pm_opp_put(opp); |
251 | |
252 | /* Find Min Freq */ |
253 | freq = 0; |
254 | opp = dev_pm_opp_find_freq_ceil_indexed(dev: hba->dev, freq: &freq, index: idx); |
255 | if (IS_ERR(ptr: opp)) { |
256 | dev_err(hba->dev, "Failed to find OPP for MIN frequency\n" ); |
257 | return PTR_ERR(ptr: opp); |
258 | } |
259 | clki->min_freq = dev_pm_opp_get_freq_indexed(opp, index: idx++); |
260 | dev_pm_opp_put(opp); |
261 | } |
262 | |
263 | return 0; |
264 | } |
265 | |
266 | static int ufshcd_parse_operating_points(struct ufs_hba *hba) |
267 | { |
268 | struct device *dev = hba->dev; |
269 | struct device_node *np = dev->of_node; |
270 | struct dev_pm_opp_config config = {}; |
271 | struct ufs_clk_info *clki; |
272 | const char **clk_names; |
273 | int cnt, i, ret; |
274 | |
275 | if (!of_find_property(np, name: "operating-points-v2" , NULL)) |
276 | return 0; |
277 | |
278 | if (of_find_property(np, name: "freq-table-hz" , NULL)) { |
279 | dev_err(dev, "%s: operating-points and freq-table-hz are incompatible\n" , |
280 | __func__); |
281 | return -EINVAL; |
282 | } |
283 | |
284 | cnt = of_property_count_strings(np, propname: "clock-names" ); |
285 | if (cnt <= 0) { |
286 | dev_err(dev, "%s: Missing clock-names\n" , __func__); |
287 | return -ENODEV; |
288 | } |
289 | |
290 | /* OPP expects clk_names to be NULL terminated */ |
291 | clk_names = devm_kcalloc(dev, n: cnt + 1, size: sizeof(*clk_names), GFP_KERNEL); |
292 | if (!clk_names) |
293 | return -ENOMEM; |
294 | |
295 | /* |
296 | * We still need to get reference to all clocks as the UFS core uses |
297 | * them separately. |
298 | */ |
299 | for (i = 0; i < cnt; i++) { |
300 | ret = of_property_read_string_index(np, propname: "clock-names" , index: i, |
301 | output: &clk_names[i]); |
302 | if (ret) |
303 | return ret; |
304 | |
305 | clki = devm_kzalloc(dev, size: sizeof(*clki), GFP_KERNEL); |
306 | if (!clki) |
307 | return -ENOMEM; |
308 | |
309 | clki->name = devm_kstrdup(dev, s: clk_names[i], GFP_KERNEL); |
310 | if (!clki->name) |
311 | return -ENOMEM; |
312 | |
313 | if (!strcmp(clk_names[i], "ref_clk" )) |
314 | clki->keep_link_active = true; |
315 | |
316 | list_add_tail(new: &clki->list, head: &hba->clk_list_head); |
317 | } |
318 | |
319 | config.clk_names = clk_names, |
320 | config.config_clks = ufshcd_opp_config_clks; |
321 | |
322 | ret = devm_pm_opp_set_config(dev, config: &config); |
323 | if (ret) |
324 | return ret; |
325 | |
326 | ret = devm_pm_opp_of_add_table(dev); |
327 | if (ret) { |
328 | dev_err(dev, "Failed to add OPP table: %d\n" , ret); |
329 | return ret; |
330 | } |
331 | |
332 | ret = ufshcd_parse_clock_min_max_freq(hba); |
333 | if (ret) |
334 | return ret; |
335 | |
336 | hba->use_pm_opp = true; |
337 | |
338 | return 0; |
339 | } |
340 | |
341 | /** |
342 | * ufshcd_negotiate_pwr_params - find power mode settings that are supported by |
343 | * both the controller and the device |
344 | * @host_params: pointer to host parameters |
345 | * @dev_max: pointer to device attributes |
346 | * @agreed_pwr: returned agreed attributes |
347 | * |
348 | * Return: 0 on success, non-zero value on failure. |
349 | */ |
350 | int ufshcd_negotiate_pwr_params(const struct ufs_host_params *host_params, |
351 | const struct ufs_pa_layer_attr *dev_max, |
352 | struct ufs_pa_layer_attr *agreed_pwr) |
353 | { |
354 | int min_host_gear; |
355 | int min_dev_gear; |
356 | bool is_dev_sup_hs = false; |
357 | bool is_host_max_hs = false; |
358 | |
359 | if (dev_max->pwr_rx == FAST_MODE) |
360 | is_dev_sup_hs = true; |
361 | |
362 | if (host_params->desired_working_mode == UFS_HS_MODE) { |
363 | is_host_max_hs = true; |
364 | min_host_gear = min_t(u32, host_params->hs_rx_gear, |
365 | host_params->hs_tx_gear); |
366 | } else { |
367 | min_host_gear = min_t(u32, host_params->pwm_rx_gear, |
368 | host_params->pwm_tx_gear); |
369 | } |
370 | |
371 | /* |
372 | * device doesn't support HS but host_params->desired_working_mode is HS, |
373 | * thus device and host_params don't agree |
374 | */ |
375 | if (!is_dev_sup_hs && is_host_max_hs) { |
376 | pr_info("%s: device doesn't support HS\n" , |
377 | __func__); |
378 | return -ENOTSUPP; |
379 | } else if (is_dev_sup_hs && is_host_max_hs) { |
380 | /* |
381 | * since device supports HS, it supports FAST_MODE. |
382 | * since host_params->desired_working_mode is also HS |
383 | * then final decision (FAST/FASTAUTO) is done according |
384 | * to pltfrm_params as it is the restricting factor |
385 | */ |
386 | agreed_pwr->pwr_rx = host_params->rx_pwr_hs; |
387 | agreed_pwr->pwr_tx = agreed_pwr->pwr_rx; |
388 | } else { |
389 | /* |
390 | * here host_params->desired_working_mode is PWM. |
391 | * it doesn't matter whether device supports HS or PWM, |
392 | * in both cases host_params->desired_working_mode will |
393 | * determine the mode |
394 | */ |
395 | agreed_pwr->pwr_rx = host_params->rx_pwr_pwm; |
396 | agreed_pwr->pwr_tx = agreed_pwr->pwr_rx; |
397 | } |
398 | |
399 | /* |
400 | * we would like tx to work in the minimum number of lanes |
401 | * between device capability and vendor preferences. |
402 | * the same decision will be made for rx |
403 | */ |
404 | agreed_pwr->lane_tx = min_t(u32, dev_max->lane_tx, |
405 | host_params->tx_lanes); |
406 | agreed_pwr->lane_rx = min_t(u32, dev_max->lane_rx, |
407 | host_params->rx_lanes); |
408 | |
409 | /* device maximum gear is the minimum between device rx and tx gears */ |
410 | min_dev_gear = min_t(u32, dev_max->gear_rx, dev_max->gear_tx); |
411 | |
412 | /* |
413 | * if both device capabilities and vendor pre-defined preferences are |
414 | * both HS or both PWM then set the minimum gear to be the chosen |
415 | * working gear. |
416 | * if one is PWM and one is HS then the one that is PWM get to decide |
417 | * what is the gear, as it is the one that also decided previously what |
418 | * pwr the device will be configured to. |
419 | */ |
420 | if ((is_dev_sup_hs && is_host_max_hs) || |
421 | (!is_dev_sup_hs && !is_host_max_hs)) { |
422 | agreed_pwr->gear_rx = |
423 | min_t(u32, min_dev_gear, min_host_gear); |
424 | } else if (!is_dev_sup_hs) { |
425 | agreed_pwr->gear_rx = min_dev_gear; |
426 | } else { |
427 | agreed_pwr->gear_rx = min_host_gear; |
428 | } |
429 | agreed_pwr->gear_tx = agreed_pwr->gear_rx; |
430 | |
431 | agreed_pwr->hs_rate = host_params->hs_rate; |
432 | |
433 | return 0; |
434 | } |
435 | EXPORT_SYMBOL_GPL(ufshcd_negotiate_pwr_params); |
436 | |
437 | void ufshcd_init_host_params(struct ufs_host_params *host_params) |
438 | { |
439 | *host_params = (struct ufs_host_params){ |
440 | .tx_lanes = UFS_LANE_2, |
441 | .rx_lanes = UFS_LANE_2, |
442 | .hs_rx_gear = UFS_HS_G3, |
443 | .hs_tx_gear = UFS_HS_G3, |
444 | .pwm_rx_gear = UFS_PWM_G4, |
445 | .pwm_tx_gear = UFS_PWM_G4, |
446 | .rx_pwr_pwm = SLOW_MODE, |
447 | .tx_pwr_pwm = SLOW_MODE, |
448 | .rx_pwr_hs = FAST_MODE, |
449 | .tx_pwr_hs = FAST_MODE, |
450 | .hs_rate = PA_HS_MODE_B, |
451 | .desired_working_mode = UFS_HS_MODE, |
452 | }; |
453 | } |
454 | EXPORT_SYMBOL_GPL(ufshcd_init_host_params); |
455 | |
456 | /** |
457 | * ufshcd_pltfrm_init - probe routine of the driver |
458 | * @pdev: pointer to Platform device handle |
459 | * @vops: pointer to variant ops |
460 | * |
461 | * Return: 0 on success, non-zero value on failure. |
462 | */ |
463 | int ufshcd_pltfrm_init(struct platform_device *pdev, |
464 | const struct ufs_hba_variant_ops *vops) |
465 | { |
466 | struct ufs_hba *hba; |
467 | void __iomem *mmio_base; |
468 | int irq, err; |
469 | struct device *dev = &pdev->dev; |
470 | |
471 | mmio_base = devm_platform_ioremap_resource(pdev, index: 0); |
472 | if (IS_ERR(ptr: mmio_base)) { |
473 | err = PTR_ERR(ptr: mmio_base); |
474 | goto out; |
475 | } |
476 | |
477 | irq = platform_get_irq(pdev, 0); |
478 | if (irq < 0) { |
479 | err = irq; |
480 | goto out; |
481 | } |
482 | |
483 | err = ufshcd_alloc_host(dev, &hba); |
484 | if (err) { |
485 | dev_err(dev, "Allocation failed\n" ); |
486 | goto out; |
487 | } |
488 | |
489 | hba->vops = vops; |
490 | |
491 | err = ufshcd_parse_clock_info(hba); |
492 | if (err) { |
493 | dev_err(dev, "%s: clock parse failed %d\n" , |
494 | __func__, err); |
495 | goto dealloc_host; |
496 | } |
497 | err = ufshcd_parse_regulator_info(hba); |
498 | if (err) { |
499 | dev_err(dev, "%s: regulator init failed %d\n" , |
500 | __func__, err); |
501 | goto dealloc_host; |
502 | } |
503 | |
504 | ufshcd_init_lanes_per_dir(hba); |
505 | |
506 | err = ufshcd_parse_operating_points(hba); |
507 | if (err) { |
508 | dev_err(dev, "%s: OPP parse failed %d\n" , __func__, err); |
509 | goto dealloc_host; |
510 | } |
511 | |
512 | err = ufshcd_init(hba, mmio_base, irq); |
513 | if (err) { |
514 | dev_err_probe(dev, err, fmt: "Initialization failed with error %d\n" , |
515 | err); |
516 | goto dealloc_host; |
517 | } |
518 | |
519 | pm_runtime_set_active(dev); |
520 | pm_runtime_enable(dev); |
521 | |
522 | return 0; |
523 | |
524 | dealloc_host: |
525 | ufshcd_dealloc_host(hba); |
526 | out: |
527 | return err; |
528 | } |
529 | EXPORT_SYMBOL_GPL(ufshcd_pltfrm_init); |
530 | |
531 | MODULE_AUTHOR("Santosh Yaragnavi <santosh.sy@samsung.com>" ); |
532 | MODULE_AUTHOR("Vinayak Holikatti <h.vinayak@samsung.com>" ); |
533 | MODULE_DESCRIPTION("UFS host controller Platform bus based glue driver" ); |
534 | MODULE_LICENSE("GPL" ); |
535 | |