/src/barebox/drivers/power/reset/reboot-mode.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd |
4 | | * Copyright (c) 2019, Ahmad Fatoum, Pengutronix |
5 | | */ |
6 | | |
7 | | #include <common.h> |
8 | | #include <driver.h> |
9 | | #include <init.h> |
10 | | #include <of.h> |
11 | | #include <linux/reboot-mode.h> |
12 | | #include <globalvar.h> |
13 | | #include <magicvar.h> |
14 | | |
15 | | static int __priority; |
16 | | static struct reboot_mode_driver *__boot_mode; |
17 | | |
18 | | static int reboot_mode_param_set(struct param_d *p, void *priv) |
19 | 0 | { |
20 | 0 | struct reboot_mode_driver *reboot = priv; |
21 | 0 | size_t i = reboot->reboot_mode_next * reboot->nelems; |
22 | |
|
23 | 0 | return reboot->write(reboot, &reboot->magics[i]); |
24 | 0 | } |
25 | | |
26 | | static int reboot_mode_add_param(struct device *dev, |
27 | | const char *prefix, |
28 | | struct reboot_mode_driver *reboot) |
29 | 52 | { |
30 | 52 | char name[sizeof "system.reboot_mode.when"]; |
31 | 52 | struct param_d *param; |
32 | | |
33 | 52 | scnprintf(name, sizeof(name), "%sprev", prefix); |
34 | | |
35 | 52 | param = dev_add_param_enum_ro(dev, name, |
36 | 52 | &reboot->reboot_mode_prev, reboot->modes, |
37 | 52 | reboot->nmodes); |
38 | 52 | if (IS_ERR(param)) |
39 | 0 | return PTR_ERR(param); |
40 | | |
41 | 52 | scnprintf(name, sizeof(name), "%snext", prefix); |
42 | | |
43 | 52 | param = dev_add_param_enum(dev, name, |
44 | 52 | reboot_mode_param_set, NULL, |
45 | 52 | &reboot->reboot_mode_next, reboot->modes, |
46 | 52 | reboot->nmodes, reboot); |
47 | | |
48 | 52 | return PTR_ERR_OR_ZERO(param); |
49 | 52 | } |
50 | | |
51 | | static int of_reboot_mode_fixup(struct device_node *root, void *ctx) |
52 | 0 | { |
53 | 0 | struct reboot_mode_driver *reboot = ctx; |
54 | 0 | struct device_node *dstnp, *srcnp, *dstparent; |
55 | |
|
56 | 0 | srcnp = reboot->dev->of_node; |
57 | 0 | dstnp = of_get_node_by_reproducible_name(root, srcnp); |
58 | |
|
59 | 0 | if (dstnp) { |
60 | 0 | dstparent = dstnp->parent; |
61 | 0 | of_delete_node(dstnp); |
62 | 0 | } else { |
63 | 0 | dstparent = of_get_node_by_reproducible_name(root, srcnp->parent); |
64 | 0 | } |
65 | |
|
66 | 0 | if (!dstparent) |
67 | 0 | return -EINVAL; |
68 | | |
69 | 0 | of_copy_node(dstparent, srcnp); |
70 | |
|
71 | 0 | return 0; |
72 | 0 | } |
73 | | |
74 | | static __maybe_unused int reboot_mode_add_globalvar(void) |
75 | 26 | { |
76 | 26 | struct reboot_mode_driver *reboot = __boot_mode; |
77 | | |
78 | 26 | if (!reboot) |
79 | 0 | return 0; |
80 | | |
81 | 26 | if (!reboot->no_fixup) |
82 | 0 | of_register_fixup(of_reboot_mode_fixup, reboot); |
83 | | |
84 | 26 | return reboot_mode_add_param(&global_device, "system.reboot_mode.", reboot); |
85 | 26 | } |
86 | | #ifdef CONFIG_GLOBALVAR |
87 | | late_initcall(reboot_mode_add_globalvar); |
88 | | #endif |
89 | | |
90 | | static void reboot_mode_print(struct reboot_mode_driver *reboot, |
91 | | const char *prefix, const u32 *arr) |
92 | 78 | { |
93 | 78 | size_t i; |
94 | 78 | dev_dbg(reboot->dev, "%s: ", prefix); |
95 | 156 | for (i = 0; i < reboot->nelems; i++) |
96 | 78 | __pr_printk(7, "%08x ", arr[i]); |
97 | 78 | __pr_printk(7, "\n"); |
98 | 78 | } |
99 | | |
100 | | static const char *get_mode_name(const struct property *prop) |
101 | 312 | { |
102 | 312 | unsigned prefix_len; |
103 | | |
104 | 312 | prefix_len = str_has_prefix(prop->name, "mode-"); |
105 | 312 | if (!prefix_len) |
106 | 208 | prefix_len = str_has_prefix(prop->name, "barebox,mode-"); |
107 | 312 | if (!prefix_len) |
108 | 208 | return NULL; |
109 | | |
110 | 104 | return prop->name + prefix_len; |
111 | 312 | } |
112 | | |
113 | | /** |
114 | | * reboot_mode_register - register a reboot mode driver |
115 | | * @reboot: reboot mode driver |
116 | | * @reboot_mode: reboot mode read from hardware |
117 | | * |
118 | | * Returns: 0 on success or a negative error code on failure. |
119 | | */ |
120 | | int reboot_mode_register(struct reboot_mode_driver *reboot, |
121 | | const u32 *reboot_mode, size_t nelems) |
122 | 26 | { |
123 | 26 | struct property *prop; |
124 | 26 | struct device_node *np = reboot->dev->of_node; |
125 | 26 | const char *alias; |
126 | 26 | size_t nmodes = 0; |
127 | 26 | int i = 0; |
128 | 26 | int ret, normal = -1; |
129 | | |
130 | 156 | for_each_property_of_node(np, prop) { |
131 | 156 | u32 magic; |
132 | | |
133 | 156 | if (!get_mode_name(prop)) |
134 | 104 | continue; |
135 | 52 | if (of_property_read_u32(np, prop->name, &magic)) |
136 | 0 | continue; |
137 | | |
138 | 52 | nmodes++; |
139 | 52 | } |
140 | | |
141 | 26 | reboot->nmodes = nmodes; |
142 | 26 | reboot->nelems = nelems; |
143 | | |
144 | | /* |
145 | | * Allocate one entry more than necessary, because in the loop below |
146 | | * we use an entry before we realize that the property is not valid. |
147 | | */ |
148 | 26 | reboot->magics = xzalloc((nmodes + 1) * nelems * sizeof(u32)); |
149 | 26 | reboot->modes = xzalloc((nmodes + 1) * sizeof(const char *)); |
150 | | |
151 | 26 | reboot_mode_print(reboot, "registering magic", reboot_mode); |
152 | | |
153 | 156 | for_each_property_of_node(np, prop) { |
154 | 156 | const char **mode; |
155 | 156 | u32 *magic; |
156 | | |
157 | 156 | magic = &reboot->magics[i * nelems]; |
158 | 156 | mode = &reboot->modes[i]; |
159 | | |
160 | 156 | *mode = get_mode_name(prop); |
161 | 156 | if (!*mode) |
162 | 104 | continue; |
163 | 52 | if (*mode[0] == '\0') { |
164 | 0 | ret = -EINVAL; |
165 | 0 | dev_err(reboot->dev, "invalid mode name(%s): too short!\n", |
166 | 0 | prop->name); |
167 | 0 | goto error; |
168 | 0 | } |
169 | | |
170 | 52 | if (!strcmp(*mode, "normal")) |
171 | 26 | normal = i; |
172 | | |
173 | 52 | if (of_property_read_u32_array(np, prop->name, magic, nelems)) { |
174 | 0 | dev_err(reboot->dev, "reboot mode %s without magic number\n", |
175 | 0 | *mode); |
176 | 0 | continue; |
177 | 0 | } |
178 | | |
179 | 52 | reboot_mode_print(reboot, *mode, magic); |
180 | | |
181 | 52 | i++; |
182 | 52 | } |
183 | | |
184 | 26 | for (i = 0; i < reboot->nmodes; i++) { |
185 | 26 | if (memcmp(&reboot->magics[i * nelems], reboot_mode, nelems * sizeof(u32))) |
186 | 0 | continue; |
187 | | |
188 | 26 | reboot->reboot_mode_prev = i; |
189 | 26 | break; |
190 | 26 | } |
191 | | |
192 | 26 | reboot_mode_add_param(reboot->dev, "", reboot); |
193 | | |
194 | | /* clear mode for next reboot */ |
195 | 26 | if (normal >= 0) |
196 | 26 | reboot->write(reboot, &reboot->magics[normal]); |
197 | | |
198 | 26 | if (!reboot->priority) |
199 | 0 | reboot->priority = REBOOT_MODE_DEFAULT_PRIORITY; |
200 | | |
201 | 26 | if (reboot->priority >= __priority) { |
202 | 26 | __priority = reboot->priority; |
203 | 26 | __boot_mode = reboot; |
204 | 26 | } |
205 | | |
206 | | |
207 | 26 | alias = of_alias_get(np); |
208 | 26 | if (alias) |
209 | 26 | dev_set_name(reboot->dev, "%s", alias); |
210 | | |
211 | 26 | return 0; |
212 | | |
213 | 0 | error: |
214 | 0 | free(reboot->magics); |
215 | 0 | free(reboot->modes); |
216 | |
|
217 | 0 | return ret; |
218 | 26 | } |
219 | | EXPORT_SYMBOL_GPL(reboot_mode_register); |
220 | | |
221 | | const char *reboot_mode_get(void) |
222 | 0 | { |
223 | 0 | if (!__boot_mode) |
224 | 0 | return NULL; |
225 | | |
226 | 0 | return __boot_mode->modes[__boot_mode->reboot_mode_prev]; |
227 | 0 | } |
228 | | EXPORT_SYMBOL_GPL(reboot_mode_get); |
229 | | |
230 | | BAREBOX_MAGICVAR(global.system.reboot_mode.prev, |
231 | | "reboot-mode: Mode set previously, before barebox start"); |
232 | | BAREBOX_MAGICVAR(global.system.reboot_mode.next, |
233 | | "reboot-mode: Mode to set next, to be evaluated after reset"); |