diff options
Diffstat (limited to 'drivers/soc/imx/gpcv2.c')
-rw-r--r-- | drivers/soc/imx/gpcv2.c | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c new file mode 100644 index 000000000000..3039072911a5 --- /dev/null +++ b/drivers/soc/imx/gpcv2.c | |||
@@ -0,0 +1,363 @@ | |||
1 | /* | ||
2 | * Copyright 2017 Impinj, Inc | ||
3 | * Author: Andrey Smirnov <andrew.smirnov@gmail.com> | ||
4 | * | ||
5 | * Based on the code of analogus driver: | ||
6 | * | ||
7 | * Copyright 2015-2017 Pengutronix, Lucas Stach <kernel@pengutronix.de> | ||
8 | * | ||
9 | * The code contained herein is licensed under the GNU General Public | ||
10 | * License. You may obtain a copy of the GNU General Public License | ||
11 | * Version 2 or later at the following locations: | ||
12 | * | ||
13 | * http://www.opensource.org/licenses/gpl-license.html | ||
14 | * http://www.gnu.org/copyleft/gpl.html | ||
15 | */ | ||
16 | |||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/pm_domain.h> | ||
19 | #include <linux/regmap.h> | ||
20 | #include <linux/regulator/consumer.h> | ||
21 | #include <dt-bindings/power/imx7-power.h> | ||
22 | |||
23 | #define GPC_LPCR_A7_BSC 0x000 | ||
24 | |||
25 | #define GPC_PGC_CPU_MAPPING 0x0ec | ||
26 | #define USB_HSIC_PHY_A7_DOMAIN BIT(6) | ||
27 | #define USB_OTG2_PHY_A7_DOMAIN BIT(5) | ||
28 | #define USB_OTG1_PHY_A7_DOMAIN BIT(4) | ||
29 | #define PCIE_PHY_A7_DOMAIN BIT(3) | ||
30 | #define MIPI_PHY_A7_DOMAIN BIT(2) | ||
31 | |||
32 | #define GPC_PU_PGC_SW_PUP_REQ 0x0f8 | ||
33 | #define GPC_PU_PGC_SW_PDN_REQ 0x104 | ||
34 | #define USB_HSIC_PHY_SW_Pxx_REQ BIT(4) | ||
35 | #define USB_OTG2_PHY_SW_Pxx_REQ BIT(3) | ||
36 | #define USB_OTG1_PHY_SW_Pxx_REQ BIT(2) | ||
37 | #define PCIE_PHY_SW_Pxx_REQ BIT(1) | ||
38 | #define MIPI_PHY_SW_Pxx_REQ BIT(0) | ||
39 | |||
40 | #define GPC_M4_PU_PDN_FLG 0x1bc | ||
41 | |||
42 | |||
43 | #define PGC_MIPI 4 | ||
44 | #define PGC_PCIE 5 | ||
45 | #define PGC_USB_HSIC 8 | ||
46 | #define GPC_PGC_CTRL(n) (0x800 + (n) * 0x40) | ||
47 | #define GPC_PGC_SR(n) (GPC_PGC_CTRL(n) + 0xc) | ||
48 | |||
49 | #define GPC_PGC_CTRL_PCR BIT(0) | ||
50 | |||
51 | struct imx7_pgc_domain { | ||
52 | struct generic_pm_domain genpd; | ||
53 | struct regmap *regmap; | ||
54 | struct regulator *regulator; | ||
55 | |||
56 | unsigned int pgc; | ||
57 | |||
58 | const struct { | ||
59 | u32 pxx; | ||
60 | u32 map; | ||
61 | } bits; | ||
62 | |||
63 | const int voltage; | ||
64 | struct device *dev; | ||
65 | }; | ||
66 | |||
67 | static int imx7_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, | ||
68 | bool on) | ||
69 | { | ||
70 | struct imx7_pgc_domain *domain = container_of(genpd, | ||
71 | struct imx7_pgc_domain, | ||
72 | genpd); | ||
73 | unsigned int offset = on ? | ||
74 | GPC_PU_PGC_SW_PUP_REQ : GPC_PU_PGC_SW_PDN_REQ; | ||
75 | const bool enable_power_control = !on; | ||
76 | const bool has_regulator = !IS_ERR(domain->regulator); | ||
77 | unsigned long deadline; | ||
78 | int ret = 0; | ||
79 | |||
80 | regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING, | ||
81 | domain->bits.map, domain->bits.map); | ||
82 | |||
83 | if (has_regulator && on) { | ||
84 | ret = regulator_enable(domain->regulator); | ||
85 | if (ret) { | ||
86 | dev_err(domain->dev, "failed to enable regulator\n"); | ||
87 | goto unmap; | ||
88 | } | ||
89 | } | ||
90 | |||
91 | if (enable_power_control) | ||
92 | regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), | ||
93 | GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR); | ||
94 | |||
95 | regmap_update_bits(domain->regmap, offset, | ||
96 | domain->bits.pxx, domain->bits.pxx); | ||
97 | |||
98 | /* | ||
99 | * As per "5.5.9.4 Example Code 4" in IMX7DRM.pdf wait | ||
100 | * for PUP_REQ/PDN_REQ bit to be cleared | ||
101 | */ | ||
102 | deadline = jiffies + msecs_to_jiffies(1); | ||
103 | while (true) { | ||
104 | u32 pxx_req; | ||
105 | |||
106 | regmap_read(domain->regmap, offset, &pxx_req); | ||
107 | |||
108 | if (!(pxx_req & domain->bits.pxx)) | ||
109 | break; | ||
110 | |||
111 | if (time_after(jiffies, deadline)) { | ||
112 | dev_err(domain->dev, "falied to command PGC\n"); | ||
113 | ret = -ETIMEDOUT; | ||
114 | /* | ||
115 | * If we were in a process of enabling a | ||
116 | * domain and failed we might as well disable | ||
117 | * the regulator we just enabled. And if it | ||
118 | * was the opposite situation and we failed to | ||
119 | * power down -- keep the regulator on | ||
120 | */ | ||
121 | on = !on; | ||
122 | break; | ||
123 | } | ||
124 | |||
125 | cpu_relax(); | ||
126 | } | ||
127 | |||
128 | if (enable_power_control) | ||
129 | regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), | ||
130 | GPC_PGC_CTRL_PCR, 0); | ||
131 | |||
132 | if (has_regulator && !on) { | ||
133 | int err; | ||
134 | |||
135 | err = regulator_disable(domain->regulator); | ||
136 | if (err) | ||
137 | dev_err(domain->dev, | ||
138 | "failed to disable regulator: %d\n", ret); | ||
139 | /* Preserve earlier error code */ | ||
140 | ret = ret ?: err; | ||
141 | } | ||
142 | unmap: | ||
143 | regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING, | ||
144 | domain->bits.map, 0); | ||
145 | return ret; | ||
146 | } | ||
147 | |||
148 | static int imx7_gpc_pu_pgc_sw_pup_req(struct generic_pm_domain *genpd) | ||
149 | { | ||
150 | return imx7_gpc_pu_pgc_sw_pxx_req(genpd, true); | ||
151 | } | ||
152 | |||
153 | static int imx7_gpc_pu_pgc_sw_pdn_req(struct generic_pm_domain *genpd) | ||
154 | { | ||
155 | return imx7_gpc_pu_pgc_sw_pxx_req(genpd, false); | ||
156 | } | ||
157 | |||
158 | static struct imx7_pgc_domain imx7_pgc_domains[] = { | ||
159 | [IMX7_POWER_DOMAIN_MIPI_PHY] = { | ||
160 | .genpd = { | ||
161 | .name = "mipi-phy", | ||
162 | }, | ||
163 | .bits = { | ||
164 | .pxx = MIPI_PHY_SW_Pxx_REQ, | ||
165 | .map = MIPI_PHY_A7_DOMAIN, | ||
166 | }, | ||
167 | .voltage = 1000000, | ||
168 | .pgc = PGC_MIPI, | ||
169 | }, | ||
170 | |||
171 | [IMX7_POWER_DOMAIN_PCIE_PHY] = { | ||
172 | .genpd = { | ||
173 | .name = "pcie-phy", | ||
174 | }, | ||
175 | .bits = { | ||
176 | .pxx = PCIE_PHY_SW_Pxx_REQ, | ||
177 | .map = PCIE_PHY_A7_DOMAIN, | ||
178 | }, | ||
179 | .voltage = 1000000, | ||
180 | .pgc = PGC_PCIE, | ||
181 | }, | ||
182 | |||
183 | [IMX7_POWER_DOMAIN_USB_HSIC_PHY] = { | ||
184 | .genpd = { | ||
185 | .name = "usb-hsic-phy", | ||
186 | }, | ||
187 | .bits = { | ||
188 | .pxx = USB_HSIC_PHY_SW_Pxx_REQ, | ||
189 | .map = USB_HSIC_PHY_A7_DOMAIN, | ||
190 | }, | ||
191 | .voltage = 1200000, | ||
192 | .pgc = PGC_USB_HSIC, | ||
193 | }, | ||
194 | }; | ||
195 | |||
196 | static int imx7_pgc_domain_probe(struct platform_device *pdev) | ||
197 | { | ||
198 | struct imx7_pgc_domain *domain = pdev->dev.platform_data; | ||
199 | int ret; | ||
200 | |||
201 | domain->dev = &pdev->dev; | ||
202 | |||
203 | ret = pm_genpd_init(&domain->genpd, NULL, true); | ||
204 | if (ret) { | ||
205 | dev_err(domain->dev, "Failed to init power domain\n"); | ||
206 | return ret; | ||
207 | } | ||
208 | |||
209 | domain->regulator = devm_regulator_get_optional(domain->dev, "power"); | ||
210 | if (IS_ERR(domain->regulator)) { | ||
211 | if (PTR_ERR(domain->regulator) != -ENODEV) { | ||
212 | dev_err(domain->dev, "Failed to get domain's regulator\n"); | ||
213 | return PTR_ERR(domain->regulator); | ||
214 | } | ||
215 | } else { | ||
216 | regulator_set_voltage(domain->regulator, | ||
217 | domain->voltage, domain->voltage); | ||
218 | } | ||
219 | |||
220 | ret = of_genpd_add_provider_simple(domain->dev->of_node, | ||
221 | &domain->genpd); | ||
222 | if (ret) { | ||
223 | dev_err(domain->dev, "Failed to add genpd provider\n"); | ||
224 | pm_genpd_remove(&domain->genpd); | ||
225 | } | ||
226 | |||
227 | return ret; | ||
228 | } | ||
229 | |||
230 | static int imx7_pgc_domain_remove(struct platform_device *pdev) | ||
231 | { | ||
232 | struct imx7_pgc_domain *domain = pdev->dev.platform_data; | ||
233 | |||
234 | of_genpd_del_provider(domain->dev->of_node); | ||
235 | pm_genpd_remove(&domain->genpd); | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static const struct platform_device_id imx7_pgc_domain_id[] = { | ||
241 | { "imx7-pgc-domain", }, | ||
242 | { }, | ||
243 | }; | ||
244 | |||
245 | static struct platform_driver imx7_pgc_domain_driver = { | ||
246 | .driver = { | ||
247 | .name = "imx7-pgc", | ||
248 | }, | ||
249 | .probe = imx7_pgc_domain_probe, | ||
250 | .remove = imx7_pgc_domain_remove, | ||
251 | .id_table = imx7_pgc_domain_id, | ||
252 | }; | ||
253 | builtin_platform_driver(imx7_pgc_domain_driver) | ||
254 | |||
255 | static int imx_gpcv2_probe(struct platform_device *pdev) | ||
256 | { | ||
257 | static const struct regmap_range yes_ranges[] = { | ||
258 | regmap_reg_range(GPC_LPCR_A7_BSC, | ||
259 | GPC_M4_PU_PDN_FLG), | ||
260 | regmap_reg_range(GPC_PGC_CTRL(PGC_MIPI), | ||
261 | GPC_PGC_SR(PGC_MIPI)), | ||
262 | regmap_reg_range(GPC_PGC_CTRL(PGC_PCIE), | ||
263 | GPC_PGC_SR(PGC_PCIE)), | ||
264 | regmap_reg_range(GPC_PGC_CTRL(PGC_USB_HSIC), | ||
265 | GPC_PGC_SR(PGC_USB_HSIC)), | ||
266 | }; | ||
267 | static const struct regmap_access_table access_table = { | ||
268 | .yes_ranges = yes_ranges, | ||
269 | .n_yes_ranges = ARRAY_SIZE(yes_ranges), | ||
270 | }; | ||
271 | static const struct regmap_config regmap_config = { | ||
272 | .reg_bits = 32, | ||
273 | .val_bits = 32, | ||
274 | .reg_stride = 4, | ||
275 | .rd_table = &access_table, | ||
276 | .wr_table = &access_table, | ||
277 | .max_register = SZ_4K, | ||
278 | }; | ||
279 | struct device *dev = &pdev->dev; | ||
280 | struct device_node *pgc_np, *np; | ||
281 | struct regmap *regmap; | ||
282 | struct resource *res; | ||
283 | void __iomem *base; | ||
284 | int ret; | ||
285 | |||
286 | pgc_np = of_get_child_by_name(dev->of_node, "pgc"); | ||
287 | if (!pgc_np) { | ||
288 | dev_err(dev, "No power domains specified in DT\n"); | ||
289 | return -EINVAL; | ||
290 | } | ||
291 | |||
292 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
293 | base = devm_ioremap_resource(dev, res); | ||
294 | if (IS_ERR(base)) | ||
295 | return PTR_ERR(base); | ||
296 | |||
297 | regmap = devm_regmap_init_mmio(dev, base, ®map_config); | ||
298 | if (IS_ERR(regmap)) { | ||
299 | ret = PTR_ERR(regmap); | ||
300 | dev_err(dev, "failed to init regmap (%d)\n", ret); | ||
301 | return ret; | ||
302 | } | ||
303 | |||
304 | for_each_child_of_node(pgc_np, np) { | ||
305 | struct platform_device *pd_pdev; | ||
306 | struct imx7_pgc_domain *domain; | ||
307 | u32 domain_index; | ||
308 | |||
309 | ret = of_property_read_u32(np, "reg", &domain_index); | ||
310 | if (ret) { | ||
311 | dev_err(dev, "Failed to read 'reg' property\n"); | ||
312 | of_node_put(np); | ||
313 | return ret; | ||
314 | } | ||
315 | |||
316 | if (domain_index >= ARRAY_SIZE(imx7_pgc_domains)) { | ||
317 | dev_warn(dev, | ||
318 | "Domain index %d is out of bounds\n", | ||
319 | domain_index); | ||
320 | continue; | ||
321 | } | ||
322 | |||
323 | domain = &imx7_pgc_domains[domain_index]; | ||
324 | domain->regmap = regmap; | ||
325 | domain->genpd.power_on = imx7_gpc_pu_pgc_sw_pup_req; | ||
326 | domain->genpd.power_off = imx7_gpc_pu_pgc_sw_pdn_req; | ||
327 | |||
328 | pd_pdev = platform_device_alloc("imx7-pgc-domain", | ||
329 | domain_index); | ||
330 | if (!pd_pdev) { | ||
331 | dev_err(dev, "Failed to allocate platform device\n"); | ||
332 | of_node_put(np); | ||
333 | return -ENOMEM; | ||
334 | } | ||
335 | |||
336 | pd_pdev->dev.platform_data = domain; | ||
337 | pd_pdev->dev.parent = dev; | ||
338 | pd_pdev->dev.of_node = np; | ||
339 | |||
340 | ret = platform_device_add(pd_pdev); | ||
341 | if (ret) { | ||
342 | platform_device_put(pd_pdev); | ||
343 | of_node_put(np); | ||
344 | return ret; | ||
345 | } | ||
346 | } | ||
347 | |||
348 | return 0; | ||
349 | } | ||
350 | |||
351 | static const struct of_device_id imx_gpcv2_dt_ids[] = { | ||
352 | { .compatible = "fsl,imx7d-gpc" }, | ||
353 | { } | ||
354 | }; | ||
355 | |||
356 | static struct platform_driver imx_gpc_driver = { | ||
357 | .driver = { | ||
358 | .name = "imx-gpcv2", | ||
359 | .of_match_table = imx_gpcv2_dt_ids, | ||
360 | }, | ||
361 | .probe = imx_gpcv2_probe, | ||
362 | }; | ||
363 | builtin_platform_driver(imx_gpc_driver) | ||