1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2014 Linaro Ltd. |
4 | * |
5 | * Author: Linus Walleij <linus.walleij@linaro.org> |
6 | */ |
7 | #include <linux/init.h> |
8 | #include <linux/mfd/syscon.h> |
9 | #include <linux/reboot.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/of.h> |
12 | |
13 | #define INTEGRATOR_HDR_CTRL_OFFSET 0x0C |
14 | #define INTEGRATOR_HDR_LOCK_OFFSET 0x14 |
15 | #define INTEGRATOR_CM_CTRL_RESET (1 << 3) |
16 | |
17 | #define VERSATILE_SYS_LOCK_OFFSET 0x20 |
18 | #define VERSATILE_SYS_RESETCTL_OFFSET 0x40 |
19 | |
20 | /* Magic unlocking token used on all Versatile boards */ |
21 | #define VERSATILE_LOCK_VAL 0xA05F |
22 | |
23 | /* |
24 | * We detect the different syscon types from the compatible strings. |
25 | */ |
26 | enum versatile_reboot { |
27 | INTEGRATOR_REBOOT_CM, |
28 | VERSATILE_REBOOT_CM, |
29 | REALVIEW_REBOOT_EB, |
30 | REALVIEW_REBOOT_PB1176, |
31 | REALVIEW_REBOOT_PB11MP, |
32 | REALVIEW_REBOOT_PBA8, |
33 | REALVIEW_REBOOT_PBX, |
34 | }; |
35 | |
36 | /* Pointer to the system controller */ |
37 | static struct regmap *syscon_regmap; |
38 | static enum versatile_reboot versatile_reboot_type; |
39 | |
40 | static const struct of_device_id versatile_reboot_of_match[] = { |
41 | { |
42 | .compatible = "arm,core-module-integrator" , |
43 | .data = (void *)INTEGRATOR_REBOOT_CM |
44 | }, |
45 | { |
46 | .compatible = "arm,core-module-versatile" , |
47 | .data = (void *)VERSATILE_REBOOT_CM, |
48 | }, |
49 | { |
50 | .compatible = "arm,realview-eb-syscon" , |
51 | .data = (void *)REALVIEW_REBOOT_EB, |
52 | }, |
53 | { |
54 | .compatible = "arm,realview-pb1176-syscon" , |
55 | .data = (void *)REALVIEW_REBOOT_PB1176, |
56 | }, |
57 | { |
58 | .compatible = "arm,realview-pb11mp-syscon" , |
59 | .data = (void *)REALVIEW_REBOOT_PB11MP, |
60 | }, |
61 | { |
62 | .compatible = "arm,realview-pba8-syscon" , |
63 | .data = (void *)REALVIEW_REBOOT_PBA8, |
64 | }, |
65 | { |
66 | .compatible = "arm,realview-pbx-syscon" , |
67 | .data = (void *)REALVIEW_REBOOT_PBX, |
68 | }, |
69 | {}, |
70 | }; |
71 | |
72 | static int versatile_reboot(struct notifier_block *this, unsigned long mode, |
73 | void *cmd) |
74 | { |
75 | /* Unlock the reset register */ |
76 | /* Then hit reset on the different machines */ |
77 | switch (versatile_reboot_type) { |
78 | case INTEGRATOR_REBOOT_CM: |
79 | regmap_write(map: syscon_regmap, INTEGRATOR_HDR_LOCK_OFFSET, |
80 | VERSATILE_LOCK_VAL); |
81 | regmap_update_bits(map: syscon_regmap, |
82 | INTEGRATOR_HDR_CTRL_OFFSET, |
83 | INTEGRATOR_CM_CTRL_RESET, |
84 | INTEGRATOR_CM_CTRL_RESET); |
85 | break; |
86 | case VERSATILE_REBOOT_CM: |
87 | regmap_write(map: syscon_regmap, VERSATILE_SYS_LOCK_OFFSET, |
88 | VERSATILE_LOCK_VAL); |
89 | regmap_update_bits(map: syscon_regmap, |
90 | VERSATILE_SYS_RESETCTL_OFFSET, |
91 | mask: 0x0107, |
92 | val: 0x0105); |
93 | regmap_write(map: syscon_regmap, VERSATILE_SYS_LOCK_OFFSET, |
94 | val: 0); |
95 | break; |
96 | case REALVIEW_REBOOT_EB: |
97 | regmap_write(map: syscon_regmap, VERSATILE_SYS_LOCK_OFFSET, |
98 | VERSATILE_LOCK_VAL); |
99 | regmap_write(map: syscon_regmap, |
100 | VERSATILE_SYS_RESETCTL_OFFSET, val: 0x0008); |
101 | break; |
102 | case REALVIEW_REBOOT_PB1176: |
103 | regmap_write(map: syscon_regmap, VERSATILE_SYS_LOCK_OFFSET, |
104 | VERSATILE_LOCK_VAL); |
105 | regmap_write(map: syscon_regmap, |
106 | VERSATILE_SYS_RESETCTL_OFFSET, val: 0x0100); |
107 | break; |
108 | case REALVIEW_REBOOT_PB11MP: |
109 | case REALVIEW_REBOOT_PBA8: |
110 | regmap_write(map: syscon_regmap, VERSATILE_SYS_LOCK_OFFSET, |
111 | VERSATILE_LOCK_VAL); |
112 | regmap_write(map: syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET, |
113 | val: 0x0000); |
114 | regmap_write(map: syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET, |
115 | val: 0x0004); |
116 | break; |
117 | case REALVIEW_REBOOT_PBX: |
118 | regmap_write(map: syscon_regmap, VERSATILE_SYS_LOCK_OFFSET, |
119 | VERSATILE_LOCK_VAL); |
120 | regmap_write(map: syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET, |
121 | val: 0x00f0); |
122 | regmap_write(map: syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET, |
123 | val: 0x00f4); |
124 | break; |
125 | } |
126 | dsb(); |
127 | |
128 | return NOTIFY_DONE; |
129 | } |
130 | |
131 | static struct notifier_block versatile_reboot_nb = { |
132 | .notifier_call = versatile_reboot, |
133 | .priority = 192, |
134 | }; |
135 | |
136 | static int __init versatile_reboot_probe(void) |
137 | { |
138 | const struct of_device_id *reboot_id; |
139 | struct device_node *np; |
140 | int err; |
141 | |
142 | np = of_find_matching_node_and_match(NULL, matches: versatile_reboot_of_match, |
143 | match: &reboot_id); |
144 | if (!np) |
145 | return -ENODEV; |
146 | versatile_reboot_type = (enum versatile_reboot)reboot_id->data; |
147 | |
148 | syscon_regmap = syscon_node_to_regmap(np); |
149 | of_node_put(node: np); |
150 | if (IS_ERR(ptr: syscon_regmap)) |
151 | return PTR_ERR(ptr: syscon_regmap); |
152 | |
153 | err = register_restart_handler(&versatile_reboot_nb); |
154 | if (err) |
155 | return err; |
156 | |
157 | pr_info("versatile reboot driver registered\n" ); |
158 | return 0; |
159 | } |
160 | device_initcall(versatile_reboot_probe); |
161 | |