]>
Commit | Line | Data |
---|---|---|
03b1781a VH |
1 | /* |
2 | * Universal Flash Storage Host controller Platform bus based glue driver | |
3 | * | |
4 | * This code is based on drivers/scsi/ufs/ufshcd-pltfrm.c | |
5 | * Copyright (C) 2011-2013 Samsung India Software Operations | |
6 | * | |
7 | * Authors: | |
8 | * Santosh Yaraganavi <[email protected]> | |
9 | * Vinayak Holikatti <[email protected]> | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or | |
12 | * modify it under the terms of the GNU General Public License | |
13 | * as published by the Free Software Foundation; either version 2 | |
14 | * of the License, or (at your option) any later version. | |
15 | * See the COPYING file in the top-level directory or visit | |
16 | * <http://www.gnu.org/licenses/gpl-2.0.html> | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, | |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | * GNU General Public License for more details. | |
22 | * | |
23 | * This program is provided "AS IS" and "WITH ALL FAULTS" and | |
24 | * without warranty of any kind. You are solely responsible for | |
25 | * determining the appropriateness of using and distributing | |
26 | * the program and assume all risks associated with your exercise | |
27 | * of rights with respect to the program, including but not limited | |
28 | * to infringement of third party rights, the risks and costs of | |
29 | * program errors, damage to or loss of data, programs or equipment, | |
30 | * and unavailability or interruption of operations. Under no | |
31 | * circumstances will the contributor of this Program be liable for | |
32 | * any damages of any kind arising from your use or distribution of | |
33 | * this program. | |
34 | */ | |
35 | ||
03b1781a | 36 | #include <linux/platform_device.h> |
62694735 | 37 | #include <linux/pm_runtime.h> |
5c0c28a8 | 38 | #include <linux/of.h> |
03b1781a | 39 | |
2953f850 SJ |
40 | #include "ufshcd.h" |
41 | ||
5c0c28a8 SRT |
42 | static const struct of_device_id ufs_of_match[]; |
43 | static struct ufs_hba_variant_ops *get_variant_ops(struct device *dev) | |
44 | { | |
45 | if (dev->of_node) { | |
46 | const struct of_device_id *match; | |
47 | ||
48 | match = of_match_node(ufs_of_match, dev->of_node); | |
49 | if (match) | |
50 | return (struct ufs_hba_variant_ops *)match->data; | |
51 | } | |
52 | ||
53 | return NULL; | |
54 | } | |
55 | ||
c6e79dac SRT |
56 | static int ufshcd_parse_clock_info(struct ufs_hba *hba) |
57 | { | |
58 | int ret = 0; | |
59 | int cnt; | |
60 | int i; | |
61 | struct device *dev = hba->dev; | |
62 | struct device_node *np = dev->of_node; | |
63 | char *name; | |
64 | u32 *clkfreq = NULL; | |
65 | struct ufs_clk_info *clki; | |
4cff6d99 ST |
66 | int len = 0; |
67 | size_t sz = 0; | |
c6e79dac SRT |
68 | |
69 | if (!np) | |
70 | goto out; | |
71 | ||
72 | INIT_LIST_HEAD(&hba->clk_list_head); | |
73 | ||
74 | cnt = of_property_count_strings(np, "clock-names"); | |
75 | if (!cnt || (cnt == -EINVAL)) { | |
76 | dev_info(dev, "%s: Unable to find clocks, assuming enabled\n", | |
77 | __func__); | |
78 | } else if (cnt < 0) { | |
79 | dev_err(dev, "%s: count clock strings failed, err %d\n", | |
80 | __func__, cnt); | |
81 | ret = cnt; | |
82 | } | |
83 | ||
84 | if (cnt <= 0) | |
85 | goto out; | |
86 | ||
4cff6d99 ST |
87 | if (!of_get_property(np, "freq-table-hz", &len)) { |
88 | dev_info(dev, "freq-table-hz property not specified\n"); | |
89 | goto out; | |
90 | } | |
91 | ||
92 | if (len <= 0) | |
93 | goto out; | |
94 | ||
95 | sz = len / sizeof(*clkfreq); | |
96 | if (sz != 2 * cnt) { | |
97 | dev_err(dev, "%s len mismatch\n", "freq-table-hz"); | |
98 | ret = -EINVAL; | |
99 | goto out; | |
100 | } | |
101 | ||
102 | clkfreq = devm_kzalloc(dev, sz * sizeof(*clkfreq), | |
103 | GFP_KERNEL); | |
c6e79dac SRT |
104 | if (!clkfreq) { |
105 | ret = -ENOMEM; | |
c6e79dac SRT |
106 | goto out; |
107 | } | |
108 | ||
4cff6d99 ST |
109 | ret = of_property_read_u32_array(np, "freq-table-hz", |
110 | clkfreq, sz); | |
c6e79dac | 111 | if (ret && (ret != -EINVAL)) { |
4cff6d99 ST |
112 | dev_err(dev, "%s: error reading array %d\n", |
113 | "freq-table-hz", ret); | |
e8cb64db | 114 | return ret; |
c6e79dac SRT |
115 | } |
116 | ||
4cff6d99 | 117 | for (i = 0; i < sz; i += 2) { |
c6e79dac | 118 | ret = of_property_read_string_index(np, |
4cff6d99 | 119 | "clock-names", i/2, (const char **)&name); |
c6e79dac | 120 | if (ret) |
e8cb64db | 121 | goto out; |
c6e79dac SRT |
122 | |
123 | clki = devm_kzalloc(dev, sizeof(*clki), GFP_KERNEL); | |
124 | if (!clki) { | |
125 | ret = -ENOMEM; | |
e8cb64db | 126 | goto out; |
c6e79dac SRT |
127 | } |
128 | ||
4cff6d99 ST |
129 | clki->min_freq = clkfreq[i]; |
130 | clki->max_freq = clkfreq[i+1]; | |
c6e79dac | 131 | clki->name = kstrdup(name, GFP_KERNEL); |
4cff6d99 ST |
132 | dev_dbg(dev, "%s: min %u max %u name %s\n", "freq-table-hz", |
133 | clki->min_freq, clki->max_freq, clki->name); | |
c6e79dac SRT |
134 | list_add_tail(&clki->list, &hba->clk_list_head); |
135 | } | |
4cff6d99 | 136 | out: |
c6e79dac SRT |
137 | return ret; |
138 | } | |
139 | ||
aa497613 SRT |
140 | #define MAX_PROP_SIZE 32 |
141 | static int ufshcd_populate_vreg(struct device *dev, const char *name, | |
142 | struct ufs_vreg **out_vreg) | |
143 | { | |
144 | int ret = 0; | |
145 | char prop_name[MAX_PROP_SIZE]; | |
146 | struct ufs_vreg *vreg = NULL; | |
147 | struct device_node *np = dev->of_node; | |
148 | ||
149 | if (!np) { | |
150 | dev_err(dev, "%s: non DT initialization\n", __func__); | |
151 | goto out; | |
152 | } | |
153 | ||
154 | snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", name); | |
155 | if (!of_parse_phandle(np, prop_name, 0)) { | |
156 | dev_info(dev, "%s: Unable to find %s regulator, assuming enabled\n", | |
157 | __func__, prop_name); | |
158 | goto out; | |
159 | } | |
160 | ||
161 | vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); | |
758581b9 DR |
162 | if (!vreg) |
163 | return -ENOMEM; | |
aa497613 SRT |
164 | |
165 | vreg->name = kstrdup(name, GFP_KERNEL); | |
166 | ||
6a771a65 RS |
167 | /* if fixed regulator no need further initialization */ |
168 | snprintf(prop_name, MAX_PROP_SIZE, "%s-fixed-regulator", name); | |
169 | if (of_property_read_bool(np, prop_name)) | |
170 | goto out; | |
171 | ||
aa497613 SRT |
172 | snprintf(prop_name, MAX_PROP_SIZE, "%s-max-microamp", name); |
173 | ret = of_property_read_u32(np, prop_name, &vreg->max_uA); | |
174 | if (ret) { | |
175 | dev_err(dev, "%s: unable to find %s err %d\n", | |
176 | __func__, prop_name, ret); | |
177 | goto out_free; | |
178 | } | |
179 | ||
180 | vreg->min_uA = 0; | |
181 | if (!strcmp(name, "vcc")) { | |
182 | if (of_property_read_bool(np, "vcc-supply-1p8")) { | |
183 | vreg->min_uV = UFS_VREG_VCC_1P8_MIN_UV; | |
184 | vreg->max_uV = UFS_VREG_VCC_1P8_MAX_UV; | |
185 | } else { | |
186 | vreg->min_uV = UFS_VREG_VCC_MIN_UV; | |
187 | vreg->max_uV = UFS_VREG_VCC_MAX_UV; | |
188 | } | |
189 | } else if (!strcmp(name, "vccq")) { | |
190 | vreg->min_uV = UFS_VREG_VCCQ_MIN_UV; | |
191 | vreg->max_uV = UFS_VREG_VCCQ_MAX_UV; | |
192 | } else if (!strcmp(name, "vccq2")) { | |
193 | vreg->min_uV = UFS_VREG_VCCQ2_MIN_UV; | |
194 | vreg->max_uV = UFS_VREG_VCCQ2_MAX_UV; | |
195 | } | |
196 | ||
197 | goto out; | |
198 | ||
199 | out_free: | |
200 | devm_kfree(dev, vreg); | |
201 | vreg = NULL; | |
202 | out: | |
203 | if (!ret) | |
204 | *out_vreg = vreg; | |
205 | return ret; | |
206 | } | |
207 | ||
208 | /** | |
209 | * ufshcd_parse_regulator_info - get regulator info from device tree | |
210 | * @hba: per adapter instance | |
211 | * | |
212 | * Get regulator info from device tree for vcc, vccq, vccq2 power supplies. | |
213 | * If any of the supplies are not defined it is assumed that they are always-on | |
214 | * and hence return zero. If the property is defined but parsing is failed | |
215 | * then return corresponding error. | |
216 | */ | |
217 | static int ufshcd_parse_regulator_info(struct ufs_hba *hba) | |
218 | { | |
219 | int err; | |
220 | struct device *dev = hba->dev; | |
221 | struct ufs_vreg_info *info = &hba->vreg_info; | |
222 | ||
6a771a65 RS |
223 | err = ufshcd_populate_vreg(dev, "vdd-hba", &info->vdd_hba); |
224 | if (err) | |
225 | goto out; | |
226 | ||
aa497613 SRT |
227 | err = ufshcd_populate_vreg(dev, "vcc", &info->vcc); |
228 | if (err) | |
229 | goto out; | |
230 | ||
231 | err = ufshcd_populate_vreg(dev, "vccq", &info->vccq); | |
232 | if (err) | |
233 | goto out; | |
234 | ||
235 | err = ufshcd_populate_vreg(dev, "vccq2", &info->vccq2); | |
236 | out: | |
237 | return err; | |
238 | } | |
239 | ||
03b1781a VH |
240 | #ifdef CONFIG_PM |
241 | /** | |
242 | * ufshcd_pltfrm_suspend - suspend power management function | |
243 | * @dev: pointer to device handle | |
244 | * | |
57d104c1 SJ |
245 | * Returns 0 if successful |
246 | * Returns non-zero otherwise | |
03b1781a VH |
247 | */ |
248 | static int ufshcd_pltfrm_suspend(struct device *dev) | |
249 | { | |
57d104c1 | 250 | return ufshcd_system_suspend(dev_get_drvdata(dev)); |
03b1781a VH |
251 | } |
252 | ||
253 | /** | |
254 | * ufshcd_pltfrm_resume - resume power management function | |
255 | * @dev: pointer to device handle | |
256 | * | |
57d104c1 SJ |
257 | * Returns 0 if successful |
258 | * Returns non-zero otherwise | |
03b1781a VH |
259 | */ |
260 | static int ufshcd_pltfrm_resume(struct device *dev) | |
261 | { | |
57d104c1 | 262 | return ufshcd_system_resume(dev_get_drvdata(dev)); |
03b1781a | 263 | } |
03b1781a | 264 | |
62694735 SRT |
265 | static int ufshcd_pltfrm_runtime_suspend(struct device *dev) |
266 | { | |
57d104c1 | 267 | return ufshcd_runtime_suspend(dev_get_drvdata(dev)); |
62694735 SRT |
268 | } |
269 | static int ufshcd_pltfrm_runtime_resume(struct device *dev) | |
270 | { | |
57d104c1 | 271 | return ufshcd_runtime_resume(dev_get_drvdata(dev)); |
62694735 SRT |
272 | } |
273 | static int ufshcd_pltfrm_runtime_idle(struct device *dev) | |
274 | { | |
57d104c1 | 275 | return ufshcd_runtime_idle(dev_get_drvdata(dev)); |
62694735 | 276 | } |
4f7ad521 RW |
277 | #else /* !CONFIG_PM */ |
278 | #define ufshcd_pltfrm_suspend NULL | |
279 | #define ufshcd_pltfrm_resume NULL | |
62694735 SRT |
280 | #define ufshcd_pltfrm_runtime_suspend NULL |
281 | #define ufshcd_pltfrm_runtime_resume NULL | |
282 | #define ufshcd_pltfrm_runtime_idle NULL | |
4f7ad521 | 283 | #endif /* CONFIG_PM */ |
62694735 | 284 | |
57d104c1 SJ |
285 | static void ufshcd_pltfrm_shutdown(struct platform_device *pdev) |
286 | { | |
287 | ufshcd_shutdown((struct ufs_hba *)platform_get_drvdata(pdev)); | |
288 | } | |
289 | ||
03b1781a VH |
290 | /** |
291 | * ufshcd_pltfrm_probe - probe routine of the driver | |
292 | * @pdev: pointer to Platform device handle | |
293 | * | |
294 | * Returns 0 on success, non-zero value on failure | |
295 | */ | |
296 | static int ufshcd_pltfrm_probe(struct platform_device *pdev) | |
297 | { | |
298 | struct ufs_hba *hba; | |
299 | void __iomem *mmio_base; | |
300 | struct resource *mem_res; | |
2953f850 | 301 | int irq, err; |
03b1781a VH |
302 | struct device *dev = &pdev->dev; |
303 | ||
304 | mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
2953f850 | 305 | mmio_base = devm_ioremap_resource(dev, mem_res); |
5c0c28a8 SRT |
306 | if (IS_ERR(*(void **)&mmio_base)) { |
307 | err = PTR_ERR(*(void **)&mmio_base); | |
2953f850 | 308 | goto out; |
03b1781a VH |
309 | } |
310 | ||
2953f850 SJ |
311 | irq = platform_get_irq(pdev, 0); |
312 | if (irq < 0) { | |
313 | dev_err(dev, "IRQ resource not available\n"); | |
03b1781a | 314 | err = -ENODEV; |
2953f850 | 315 | goto out; |
03b1781a VH |
316 | } |
317 | ||
5c0c28a8 SRT |
318 | err = ufshcd_alloc_host(dev, &hba); |
319 | if (err) { | |
320 | dev_err(&pdev->dev, "Allocation failed\n"); | |
321 | goto out; | |
322 | } | |
323 | ||
324 | hba->vops = get_variant_ops(&pdev->dev); | |
325 | ||
c6e79dac SRT |
326 | err = ufshcd_parse_clock_info(hba); |
327 | if (err) { | |
328 | dev_err(&pdev->dev, "%s: clock parse failed %d\n", | |
329 | __func__, err); | |
330 | goto out; | |
331 | } | |
aa497613 SRT |
332 | err = ufshcd_parse_regulator_info(hba); |
333 | if (err) { | |
334 | dev_err(&pdev->dev, "%s: regulator init failed %d\n", | |
335 | __func__, err); | |
336 | goto out; | |
337 | } | |
338 | ||
62694735 SRT |
339 | pm_runtime_set_active(&pdev->dev); |
340 | pm_runtime_enable(&pdev->dev); | |
341 | ||
5c0c28a8 | 342 | err = ufshcd_init(hba, mmio_base, irq); |
03b1781a | 343 | if (err) { |
2953f850 | 344 | dev_err(dev, "Intialization failed\n"); |
62694735 | 345 | goto out_disable_rpm; |
03b1781a VH |
346 | } |
347 | ||
348 | platform_set_drvdata(pdev, hba); | |
349 | ||
62694735 SRT |
350 | return 0; |
351 | ||
352 | out_disable_rpm: | |
353 | pm_runtime_disable(&pdev->dev); | |
354 | pm_runtime_set_suspended(&pdev->dev); | |
2953f850 | 355 | out: |
03b1781a VH |
356 | return err; |
357 | } | |
358 | ||
359 | /** | |
360 | * ufshcd_pltfrm_remove - remove platform driver routine | |
361 | * @pdev: pointer to platform device handle | |
362 | * | |
363 | * Returns 0 on success, non-zero value on failure | |
364 | */ | |
365 | static int ufshcd_pltfrm_remove(struct platform_device *pdev) | |
366 | { | |
03b1781a VH |
367 | struct ufs_hba *hba = platform_get_drvdata(pdev); |
368 | ||
62694735 | 369 | pm_runtime_get_sync(&(pdev)->dev); |
03b1781a | 370 | ufshcd_remove(hba); |
03b1781a VH |
371 | return 0; |
372 | } | |
373 | ||
374 | static const struct of_device_id ufs_of_match[] = { | |
375 | { .compatible = "jedec,ufs-1.1"}, | |
2e2930a3 | 376 | {}, |
03b1781a VH |
377 | }; |
378 | ||
379 | static const struct dev_pm_ops ufshcd_dev_pm_ops = { | |
380 | .suspend = ufshcd_pltfrm_suspend, | |
381 | .resume = ufshcd_pltfrm_resume, | |
62694735 SRT |
382 | .runtime_suspend = ufshcd_pltfrm_runtime_suspend, |
383 | .runtime_resume = ufshcd_pltfrm_runtime_resume, | |
384 | .runtime_idle = ufshcd_pltfrm_runtime_idle, | |
03b1781a VH |
385 | }; |
386 | ||
387 | static struct platform_driver ufshcd_pltfrm_driver = { | |
388 | .probe = ufshcd_pltfrm_probe, | |
389 | .remove = ufshcd_pltfrm_remove, | |
57d104c1 | 390 | .shutdown = ufshcd_pltfrm_shutdown, |
03b1781a VH |
391 | .driver = { |
392 | .name = "ufshcd", | |
03b1781a VH |
393 | .pm = &ufshcd_dev_pm_ops, |
394 | .of_match_table = ufs_of_match, | |
395 | }, | |
396 | }; | |
397 | ||
398 | module_platform_driver(ufshcd_pltfrm_driver); | |
399 | ||
400 | MODULE_AUTHOR("Santosh Yaragnavi <[email protected]>"); | |
401 | MODULE_AUTHOR("Vinayak Holikatti <[email protected]>"); | |
402 | MODULE_DESCRIPTION("UFS host controller Pltform bus based glue driver"); | |
403 | MODULE_LICENSE("GPL"); | |
404 | MODULE_VERSION(UFSHCD_DRIVER_VERSION); |