]>
Commit | Line | Data |
---|---|---|
3e99cb21 TK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * OMAP2+ PRM driver | |
4 | * | |
5 | * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ | |
6 | * Tero Kristo <t-kristo@ti.com> | |
7 | */ | |
8 | ||
9 | #include <linux/kernel.h> | |
10 | #include <linux/device.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/iopoll.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/of_device.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/reset-controller.h> | |
17 | #include <linux/delay.h> | |
18 | ||
d30cd83f TK |
19 | #include <linux/platform_data/ti-prm.h> |
20 | ||
3e99cb21 TK |
21 | struct omap_rst_map { |
22 | s8 rst; | |
23 | s8 st; | |
24 | }; | |
25 | ||
26 | struct omap_prm_data { | |
27 | u32 base; | |
28 | const char *name; | |
d30cd83f | 29 | const char *clkdm_name; |
3e99cb21 TK |
30 | u16 rstctrl; |
31 | u16 rstst; | |
32 | const struct omap_rst_map *rstmap; | |
33 | u8 flags; | |
34 | }; | |
35 | ||
36 | struct omap_prm { | |
37 | const struct omap_prm_data *data; | |
38 | void __iomem *base; | |
39 | }; | |
40 | ||
41 | struct omap_reset_data { | |
42 | struct reset_controller_dev rcdev; | |
43 | struct omap_prm *prm; | |
44 | u32 mask; | |
45 | spinlock_t lock; | |
d30cd83f TK |
46 | struct clockdomain *clkdm; |
47 | struct device *dev; | |
3e99cb21 TK |
48 | }; |
49 | ||
50 | #define to_omap_reset_data(p) container_of((p), struct omap_reset_data, rcdev) | |
51 | ||
52 | #define OMAP_MAX_RESETS 8 | |
53 | #define OMAP_RESET_MAX_WAIT 10000 | |
54 | ||
55 | #define OMAP_PRM_HAS_RSTCTRL BIT(0) | |
56 | #define OMAP_PRM_HAS_RSTST BIT(1) | |
d30cd83f | 57 | #define OMAP_PRM_HAS_NO_CLKDM BIT(2) |
3e99cb21 TK |
58 | |
59 | #define OMAP_PRM_HAS_RESETS (OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_RSTST) | |
60 | ||
8aa35504 TK |
61 | static const struct omap_rst_map rst_map_0[] = { |
62 | { .rst = 0, .st = 0 }, | |
63 | { .rst = -1 }, | |
64 | }; | |
65 | ||
0f0faaf4 TK |
66 | static const struct omap_rst_map rst_map_01[] = { |
67 | { .rst = 0, .st = 0 }, | |
68 | { .rst = 1, .st = 1 }, | |
69 | { .rst = -1 }, | |
70 | }; | |
71 | ||
72 | static const struct omap_rst_map rst_map_012[] = { | |
73 | { .rst = 0, .st = 0 }, | |
74 | { .rst = 1, .st = 1 }, | |
75 | { .rst = 2, .st = 2 }, | |
76 | { .rst = -1 }, | |
77 | }; | |
78 | ||
79 | static const struct omap_prm_data omap4_prm_data[] = { | |
80 | { .name = "tesla", .base = 0x4a306400, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 }, | |
81 | { .name = "core", .base = 0x4a306700, .rstctrl = 0x210, .rstst = 0x214, .clkdm_name = "ducati", .rstmap = rst_map_012 }, | |
82 | { .name = "ivahd", .base = 0x4a306f00, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_012 }, | |
83 | { .name = "device", .base = 0x4a307b00, .rstctrl = 0x0, .rstst = 0x4, .rstmap = rst_map_01, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM }, | |
84 | { }, | |
85 | }; | |
86 | ||
8aa35504 TK |
87 | static const struct omap_rst_map am3_per_rst_map[] = { |
88 | { .rst = 1 }, | |
89 | { .rst = -1 }, | |
90 | }; | |
91 | ||
92 | static const struct omap_rst_map am3_wkup_rst_map[] = { | |
93 | { .rst = 3, .st = 5 }, | |
94 | { .rst = -1 }, | |
95 | }; | |
96 | ||
97 | static const struct omap_prm_data am3_prm_data[] = { | |
98 | { .name = "per", .base = 0x44e00c00, .rstctrl = 0x0, .rstmap = am3_per_rst_map, .flags = OMAP_PRM_HAS_RSTCTRL, .clkdm_name = "pruss_ocp" }, | |
99 | { .name = "wkup", .base = 0x44e00d00, .rstctrl = 0x0, .rstst = 0xc, .rstmap = am3_wkup_rst_map, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM }, | |
100 | { .name = "device", .base = 0x44e00f00, .rstctrl = 0x0, .rstst = 0x8, .rstmap = rst_map_01, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM }, | |
101 | { .name = "gfx", .base = 0x44e01100, .rstctrl = 0x4, .rstst = 0x14, .rstmap = rst_map_0, .clkdm_name = "gfx_l3" }, | |
102 | { }, | |
103 | }; | |
104 | ||
3e99cb21 | 105 | static const struct of_device_id omap_prm_id_table[] = { |
0f0faaf4 | 106 | { .compatible = "ti,omap4-prm-inst", .data = omap4_prm_data }, |
8aa35504 | 107 | { .compatible = "ti,am3-prm-inst", .data = am3_prm_data }, |
3e99cb21 TK |
108 | { }, |
109 | }; | |
110 | ||
111 | static bool _is_valid_reset(struct omap_reset_data *reset, unsigned long id) | |
112 | { | |
113 | if (reset->mask & BIT(id)) | |
114 | return true; | |
115 | ||
116 | return false; | |
117 | } | |
118 | ||
119 | static int omap_reset_get_st_bit(struct omap_reset_data *reset, | |
120 | unsigned long id) | |
121 | { | |
122 | const struct omap_rst_map *map = reset->prm->data->rstmap; | |
123 | ||
124 | while (map->rst >= 0) { | |
125 | if (map->rst == id) | |
126 | return map->st; | |
127 | ||
128 | map++; | |
129 | } | |
130 | ||
131 | return id; | |
132 | } | |
133 | ||
134 | static int omap_reset_status(struct reset_controller_dev *rcdev, | |
135 | unsigned long id) | |
136 | { | |
137 | struct omap_reset_data *reset = to_omap_reset_data(rcdev); | |
138 | u32 v; | |
139 | int st_bit = omap_reset_get_st_bit(reset, id); | |
140 | bool has_rstst = reset->prm->data->rstst || | |
141 | (reset->prm->data->flags & OMAP_PRM_HAS_RSTST); | |
142 | ||
143 | /* Check if we have rstst */ | |
144 | if (!has_rstst) | |
145 | return -ENOTSUPP; | |
146 | ||
147 | /* Check if hw reset line is asserted */ | |
148 | v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl); | |
149 | if (v & BIT(id)) | |
150 | return 1; | |
151 | ||
152 | /* | |
153 | * Check reset status, high value means reset sequence has been | |
154 | * completed successfully so we can return 0 here (reset deasserted) | |
155 | */ | |
156 | v = readl_relaxed(reset->prm->base + reset->prm->data->rstst); | |
157 | v >>= st_bit; | |
158 | v &= 1; | |
159 | ||
160 | return !v; | |
161 | } | |
162 | ||
163 | static int omap_reset_assert(struct reset_controller_dev *rcdev, | |
164 | unsigned long id) | |
165 | { | |
166 | struct omap_reset_data *reset = to_omap_reset_data(rcdev); | |
167 | u32 v; | |
168 | unsigned long flags; | |
169 | ||
170 | /* assert the reset control line */ | |
171 | spin_lock_irqsave(&reset->lock, flags); | |
172 | v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl); | |
173 | v |= 1 << id; | |
174 | writel_relaxed(v, reset->prm->base + reset->prm->data->rstctrl); | |
175 | spin_unlock_irqrestore(&reset->lock, flags); | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
180 | static int omap_reset_deassert(struct reset_controller_dev *rcdev, | |
181 | unsigned long id) | |
182 | { | |
183 | struct omap_reset_data *reset = to_omap_reset_data(rcdev); | |
184 | u32 v; | |
185 | int st_bit; | |
186 | bool has_rstst; | |
187 | unsigned long flags; | |
d30cd83f TK |
188 | struct ti_prm_platform_data *pdata = dev_get_platdata(reset->dev); |
189 | int ret = 0; | |
3e99cb21 TK |
190 | |
191 | has_rstst = reset->prm->data->rstst || | |
192 | (reset->prm->data->flags & OMAP_PRM_HAS_RSTST); | |
193 | ||
194 | if (has_rstst) { | |
195 | st_bit = omap_reset_get_st_bit(reset, id); | |
196 | ||
197 | /* Clear the reset status by writing 1 to the status bit */ | |
198 | v = 1 << st_bit; | |
199 | writel_relaxed(v, reset->prm->base + reset->prm->data->rstst); | |
200 | } | |
201 | ||
d30cd83f TK |
202 | if (reset->clkdm) |
203 | pdata->clkdm_deny_idle(reset->clkdm); | |
204 | ||
3e99cb21 TK |
205 | /* de-assert the reset control line */ |
206 | spin_lock_irqsave(&reset->lock, flags); | |
207 | v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl); | |
208 | v &= ~(1 << id); | |
209 | writel_relaxed(v, reset->prm->base + reset->prm->data->rstctrl); | |
210 | spin_unlock_irqrestore(&reset->lock, flags); | |
211 | ||
c5117a78 | 212 | if (!has_rstst) |
d30cd83f | 213 | goto exit; |
c5117a78 TK |
214 | |
215 | /* wait for the status to be set */ | |
216 | ret = readl_relaxed_poll_timeout(reset->prm->base + | |
217 | reset->prm->data->rstst, | |
218 | v, v & BIT(st_bit), 1, | |
219 | OMAP_RESET_MAX_WAIT); | |
220 | if (ret) | |
221 | pr_err("%s: timedout waiting for %s:%lu\n", __func__, | |
222 | reset->prm->data->name, id); | |
223 | ||
d30cd83f TK |
224 | exit: |
225 | if (reset->clkdm) | |
226 | pdata->clkdm_allow_idle(reset->clkdm); | |
227 | ||
228 | return ret; | |
3e99cb21 TK |
229 | } |
230 | ||
231 | static const struct reset_control_ops omap_reset_ops = { | |
232 | .assert = omap_reset_assert, | |
233 | .deassert = omap_reset_deassert, | |
234 | .status = omap_reset_status, | |
235 | }; | |
236 | ||
237 | static int omap_prm_reset_xlate(struct reset_controller_dev *rcdev, | |
238 | const struct of_phandle_args *reset_spec) | |
239 | { | |
240 | struct omap_reset_data *reset = to_omap_reset_data(rcdev); | |
241 | ||
242 | if (!_is_valid_reset(reset, reset_spec->args[0])) | |
243 | return -EINVAL; | |
244 | ||
245 | return reset_spec->args[0]; | |
246 | } | |
247 | ||
248 | static int omap_prm_reset_init(struct platform_device *pdev, | |
249 | struct omap_prm *prm) | |
250 | { | |
251 | struct omap_reset_data *reset; | |
252 | const struct omap_rst_map *map; | |
d30cd83f TK |
253 | struct ti_prm_platform_data *pdata = dev_get_platdata(&pdev->dev); |
254 | char buf[32]; | |
3e99cb21 TK |
255 | |
256 | /* | |
257 | * Check if we have controllable resets. If either rstctrl is non-zero | |
258 | * or OMAP_PRM_HAS_RSTCTRL flag is set, we have reset control register | |
259 | * for the domain. | |
260 | */ | |
261 | if (!prm->data->rstctrl && !(prm->data->flags & OMAP_PRM_HAS_RSTCTRL)) | |
262 | return 0; | |
263 | ||
d30cd83f TK |
264 | /* Check if we have the pdata callbacks in place */ |
265 | if (!pdata || !pdata->clkdm_lookup || !pdata->clkdm_deny_idle || | |
266 | !pdata->clkdm_allow_idle) | |
267 | return -EINVAL; | |
268 | ||
3e99cb21 TK |
269 | map = prm->data->rstmap; |
270 | if (!map) | |
271 | return -EINVAL; | |
272 | ||
273 | reset = devm_kzalloc(&pdev->dev, sizeof(*reset), GFP_KERNEL); | |
274 | if (!reset) | |
275 | return -ENOMEM; | |
276 | ||
277 | reset->rcdev.owner = THIS_MODULE; | |
278 | reset->rcdev.ops = &omap_reset_ops; | |
279 | reset->rcdev.of_node = pdev->dev.of_node; | |
280 | reset->rcdev.nr_resets = OMAP_MAX_RESETS; | |
281 | reset->rcdev.of_xlate = omap_prm_reset_xlate; | |
282 | reset->rcdev.of_reset_n_cells = 1; | |
d30cd83f | 283 | reset->dev = &pdev->dev; |
3e99cb21 TK |
284 | spin_lock_init(&reset->lock); |
285 | ||
286 | reset->prm = prm; | |
287 | ||
d30cd83f TK |
288 | sprintf(buf, "%s_clkdm", prm->data->clkdm_name ? prm->data->clkdm_name : |
289 | prm->data->name); | |
290 | ||
291 | if (!(prm->data->flags & OMAP_PRM_HAS_NO_CLKDM)) { | |
292 | reset->clkdm = pdata->clkdm_lookup(buf); | |
293 | if (!reset->clkdm) | |
294 | return -EINVAL; | |
295 | } | |
296 | ||
3e99cb21 TK |
297 | while (map->rst >= 0) { |
298 | reset->mask |= BIT(map->rst); | |
299 | map++; | |
300 | } | |
301 | ||
302 | return devm_reset_controller_register(&pdev->dev, &reset->rcdev); | |
303 | } | |
304 | ||
305 | static int omap_prm_probe(struct platform_device *pdev) | |
306 | { | |
307 | struct resource *res; | |
308 | const struct omap_prm_data *data; | |
309 | struct omap_prm *prm; | |
310 | const struct of_device_id *match; | |
311 | ||
312 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
313 | if (!res) | |
314 | return -ENODEV; | |
315 | ||
316 | match = of_match_device(omap_prm_id_table, &pdev->dev); | |
317 | if (!match) | |
318 | return -ENOTSUPP; | |
319 | ||
320 | prm = devm_kzalloc(&pdev->dev, sizeof(*prm), GFP_KERNEL); | |
321 | if (!prm) | |
322 | return -ENOMEM; | |
323 | ||
324 | data = match->data; | |
325 | ||
326 | while (data->base != res->start) { | |
327 | if (!data->base) | |
328 | return -EINVAL; | |
329 | data++; | |
330 | } | |
331 | ||
332 | prm->data = data; | |
333 | ||
334 | prm->base = devm_ioremap_resource(&pdev->dev, res); | |
335 | if (!prm->base) | |
336 | return -ENOMEM; | |
337 | ||
338 | return omap_prm_reset_init(pdev, prm); | |
339 | } | |
340 | ||
341 | static struct platform_driver omap_prm_driver = { | |
342 | .probe = omap_prm_probe, | |
343 | .driver = { | |
344 | .name = KBUILD_MODNAME, | |
345 | .of_match_table = omap_prm_id_table, | |
346 | }, | |
347 | }; | |
348 | builtin_platform_driver(omap_prm_driver); |