1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * dwc3-exynos.c - Samsung Exynos DWC3 Specific Glue layer |
4 | * |
5 | * Copyright (c) 2012 Samsung Electronics Co., Ltd. |
6 | * http://www.samsung.com |
7 | * |
8 | * Author: Anton Tikhomirov <av.tikhomirov@samsung.com> |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/clk.h> |
16 | #include <linux/of.h> |
17 | #include <linux/of_platform.h> |
18 | #include <linux/regulator/consumer.h> |
19 | |
20 | #define DWC3_EXYNOS_MAX_CLOCKS 4 |
21 | |
22 | struct dwc3_exynos_driverdata { |
23 | const char *clk_names[DWC3_EXYNOS_MAX_CLOCKS]; |
24 | int num_clks; |
25 | int suspend_clk_idx; |
26 | }; |
27 | |
28 | struct dwc3_exynos { |
29 | struct device *dev; |
30 | |
31 | const char **clk_names; |
32 | struct clk *clks[DWC3_EXYNOS_MAX_CLOCKS]; |
33 | int num_clks; |
34 | int suspend_clk_idx; |
35 | |
36 | struct regulator *vdd33; |
37 | struct regulator *vdd10; |
38 | }; |
39 | |
40 | static int dwc3_exynos_probe(struct platform_device *pdev) |
41 | { |
42 | struct dwc3_exynos *exynos; |
43 | struct device *dev = &pdev->dev; |
44 | struct device_node *node = dev->of_node; |
45 | const struct dwc3_exynos_driverdata *driver_data; |
46 | int i, ret; |
47 | |
48 | exynos = devm_kzalloc(dev, size: sizeof(*exynos), GFP_KERNEL); |
49 | if (!exynos) |
50 | return -ENOMEM; |
51 | |
52 | driver_data = of_device_get_match_data(dev); |
53 | exynos->dev = dev; |
54 | exynos->num_clks = driver_data->num_clks; |
55 | exynos->clk_names = (const char **)driver_data->clk_names; |
56 | exynos->suspend_clk_idx = driver_data->suspend_clk_idx; |
57 | |
58 | platform_set_drvdata(pdev, data: exynos); |
59 | |
60 | for (i = 0; i < exynos->num_clks; i++) { |
61 | exynos->clks[i] = devm_clk_get(dev, id: exynos->clk_names[i]); |
62 | if (IS_ERR(ptr: exynos->clks[i])) { |
63 | dev_err(dev, "failed to get clock: %s\n" , |
64 | exynos->clk_names[i]); |
65 | return PTR_ERR(ptr: exynos->clks[i]); |
66 | } |
67 | } |
68 | |
69 | for (i = 0; i < exynos->num_clks; i++) { |
70 | ret = clk_prepare_enable(clk: exynos->clks[i]); |
71 | if (ret) { |
72 | while (i-- > 0) |
73 | clk_disable_unprepare(clk: exynos->clks[i]); |
74 | return ret; |
75 | } |
76 | } |
77 | |
78 | if (exynos->suspend_clk_idx >= 0) |
79 | clk_prepare_enable(clk: exynos->clks[exynos->suspend_clk_idx]); |
80 | |
81 | exynos->vdd33 = devm_regulator_get(dev, id: "vdd33" ); |
82 | if (IS_ERR(ptr: exynos->vdd33)) { |
83 | ret = PTR_ERR(ptr: exynos->vdd33); |
84 | goto vdd33_err; |
85 | } |
86 | ret = regulator_enable(regulator: exynos->vdd33); |
87 | if (ret) { |
88 | dev_err(dev, "Failed to enable VDD33 supply\n" ); |
89 | goto vdd33_err; |
90 | } |
91 | |
92 | exynos->vdd10 = devm_regulator_get(dev, id: "vdd10" ); |
93 | if (IS_ERR(ptr: exynos->vdd10)) { |
94 | ret = PTR_ERR(ptr: exynos->vdd10); |
95 | goto vdd10_err; |
96 | } |
97 | ret = regulator_enable(regulator: exynos->vdd10); |
98 | if (ret) { |
99 | dev_err(dev, "Failed to enable VDD10 supply\n" ); |
100 | goto vdd10_err; |
101 | } |
102 | |
103 | if (node) { |
104 | ret = of_platform_populate(root: node, NULL, NULL, parent: dev); |
105 | if (ret) { |
106 | dev_err(dev, "failed to add dwc3 core\n" ); |
107 | goto populate_err; |
108 | } |
109 | } else { |
110 | dev_err(dev, "no device node, failed to add dwc3 core\n" ); |
111 | ret = -ENODEV; |
112 | goto populate_err; |
113 | } |
114 | |
115 | return 0; |
116 | |
117 | populate_err: |
118 | regulator_disable(regulator: exynos->vdd10); |
119 | vdd10_err: |
120 | regulator_disable(regulator: exynos->vdd33); |
121 | vdd33_err: |
122 | for (i = exynos->num_clks - 1; i >= 0; i--) |
123 | clk_disable_unprepare(clk: exynos->clks[i]); |
124 | |
125 | if (exynos->suspend_clk_idx >= 0) |
126 | clk_disable_unprepare(clk: exynos->clks[exynos->suspend_clk_idx]); |
127 | |
128 | return ret; |
129 | } |
130 | |
131 | static void dwc3_exynos_remove(struct platform_device *pdev) |
132 | { |
133 | struct dwc3_exynos *exynos = platform_get_drvdata(pdev); |
134 | int i; |
135 | |
136 | of_platform_depopulate(parent: &pdev->dev); |
137 | |
138 | for (i = exynos->num_clks - 1; i >= 0; i--) |
139 | clk_disable_unprepare(clk: exynos->clks[i]); |
140 | |
141 | if (exynos->suspend_clk_idx >= 0) |
142 | clk_disable_unprepare(clk: exynos->clks[exynos->suspend_clk_idx]); |
143 | |
144 | regulator_disable(regulator: exynos->vdd33); |
145 | regulator_disable(regulator: exynos->vdd10); |
146 | } |
147 | |
148 | static const struct dwc3_exynos_driverdata exynos5250_drvdata = { |
149 | .clk_names = { "usbdrd30" }, |
150 | .num_clks = 1, |
151 | .suspend_clk_idx = -1, |
152 | }; |
153 | |
154 | static const struct dwc3_exynos_driverdata exynos5433_drvdata = { |
155 | .clk_names = { "aclk" , "susp_clk" , "pipe_pclk" , "phyclk" }, |
156 | .num_clks = 4, |
157 | .suspend_clk_idx = 1, |
158 | }; |
159 | |
160 | static const struct dwc3_exynos_driverdata exynos7_drvdata = { |
161 | .clk_names = { "usbdrd30" , "usbdrd30_susp_clk" , "usbdrd30_axius_clk" }, |
162 | .num_clks = 3, |
163 | .suspend_clk_idx = 1, |
164 | }; |
165 | |
166 | static const struct dwc3_exynos_driverdata exynos850_drvdata = { |
167 | .clk_names = { "bus_early" , "ref" }, |
168 | .num_clks = 2, |
169 | .suspend_clk_idx = -1, |
170 | }; |
171 | |
172 | static const struct of_device_id exynos_dwc3_match[] = { |
173 | { |
174 | .compatible = "samsung,exynos5250-dwusb3" , |
175 | .data = &exynos5250_drvdata, |
176 | }, { |
177 | .compatible = "samsung,exynos5433-dwusb3" , |
178 | .data = &exynos5433_drvdata, |
179 | }, { |
180 | .compatible = "samsung,exynos7-dwusb3" , |
181 | .data = &exynos7_drvdata, |
182 | }, { |
183 | .compatible = "samsung,exynos850-dwusb3" , |
184 | .data = &exynos850_drvdata, |
185 | }, { |
186 | } |
187 | }; |
188 | MODULE_DEVICE_TABLE(of, exynos_dwc3_match); |
189 | |
190 | #ifdef CONFIG_PM_SLEEP |
191 | static int dwc3_exynos_suspend(struct device *dev) |
192 | { |
193 | struct dwc3_exynos *exynos = dev_get_drvdata(dev); |
194 | int i; |
195 | |
196 | for (i = exynos->num_clks - 1; i >= 0; i--) |
197 | clk_disable_unprepare(clk: exynos->clks[i]); |
198 | |
199 | regulator_disable(regulator: exynos->vdd33); |
200 | regulator_disable(regulator: exynos->vdd10); |
201 | |
202 | return 0; |
203 | } |
204 | |
205 | static int dwc3_exynos_resume(struct device *dev) |
206 | { |
207 | struct dwc3_exynos *exynos = dev_get_drvdata(dev); |
208 | int i, ret; |
209 | |
210 | ret = regulator_enable(regulator: exynos->vdd33); |
211 | if (ret) { |
212 | dev_err(dev, "Failed to enable VDD33 supply\n" ); |
213 | return ret; |
214 | } |
215 | ret = regulator_enable(regulator: exynos->vdd10); |
216 | if (ret) { |
217 | dev_err(dev, "Failed to enable VDD10 supply\n" ); |
218 | return ret; |
219 | } |
220 | |
221 | for (i = 0; i < exynos->num_clks; i++) { |
222 | ret = clk_prepare_enable(clk: exynos->clks[i]); |
223 | if (ret) { |
224 | while (i-- > 0) |
225 | clk_disable_unprepare(clk: exynos->clks[i]); |
226 | return ret; |
227 | } |
228 | } |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = { |
234 | SET_SYSTEM_SLEEP_PM_OPS(dwc3_exynos_suspend, dwc3_exynos_resume) |
235 | }; |
236 | |
237 | #define DEV_PM_OPS (&dwc3_exynos_dev_pm_ops) |
238 | #else |
239 | #define DEV_PM_OPS NULL |
240 | #endif /* CONFIG_PM_SLEEP */ |
241 | |
242 | static struct platform_driver dwc3_exynos_driver = { |
243 | .probe = dwc3_exynos_probe, |
244 | .remove_new = dwc3_exynos_remove, |
245 | .driver = { |
246 | .name = "exynos-dwc3" , |
247 | .of_match_table = exynos_dwc3_match, |
248 | .pm = DEV_PM_OPS, |
249 | }, |
250 | }; |
251 | |
252 | module_platform_driver(dwc3_exynos_driver); |
253 | |
254 | MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@samsung.com>" ); |
255 | MODULE_LICENSE("GPL v2" ); |
256 | MODULE_DESCRIPTION("DesignWare USB3 Exynos Glue Layer" ); |
257 | |