1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Generic Syscon Reboot Driver |
4 | * |
5 | * Copyright (c) 2013, Applied Micro Circuits Corporation |
6 | * Author: Feng Kan <fkan@apm.com> |
7 | */ |
8 | #include <linux/delay.h> |
9 | #include <linux/io.h> |
10 | #include <linux/notifier.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/reboot.h> |
15 | #include <linux/regmap.h> |
16 | |
17 | struct syscon_reboot_context { |
18 | struct regmap *map; |
19 | u32 offset; |
20 | u32 value; |
21 | u32 mask; |
22 | struct notifier_block restart_handler; |
23 | }; |
24 | |
25 | static int syscon_restart_handle(struct notifier_block *this, |
26 | unsigned long mode, void *cmd) |
27 | { |
28 | struct syscon_reboot_context *ctx = |
29 | container_of(this, struct syscon_reboot_context, |
30 | restart_handler); |
31 | |
32 | /* Issue the reboot */ |
33 | regmap_update_bits(map: ctx->map, reg: ctx->offset, mask: ctx->mask, val: ctx->value); |
34 | |
35 | mdelay(1000); |
36 | |
37 | pr_emerg("Unable to restart system\n" ); |
38 | return NOTIFY_DONE; |
39 | } |
40 | |
41 | static int syscon_reboot_probe(struct platform_device *pdev) |
42 | { |
43 | struct syscon_reboot_context *ctx; |
44 | struct device *dev = &pdev->dev; |
45 | int mask_err, value_err; |
46 | int priority; |
47 | int err; |
48 | |
49 | ctx = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ctx), GFP_KERNEL); |
50 | if (!ctx) |
51 | return -ENOMEM; |
52 | |
53 | ctx->map = syscon_regmap_lookup_by_phandle(np: dev->of_node, property: "regmap" ); |
54 | if (IS_ERR(ptr: ctx->map)) { |
55 | ctx->map = syscon_node_to_regmap(np: dev->parent->of_node); |
56 | if (IS_ERR(ptr: ctx->map)) |
57 | return PTR_ERR(ptr: ctx->map); |
58 | } |
59 | |
60 | if (of_property_read_s32(np: pdev->dev.of_node, propname: "priority" , out_value: &priority)) |
61 | priority = 192; |
62 | |
63 | if (of_property_read_u32(np: pdev->dev.of_node, propname: "offset" , out_value: &ctx->offset)) |
64 | return -EINVAL; |
65 | |
66 | value_err = of_property_read_u32(np: pdev->dev.of_node, propname: "value" , out_value: &ctx->value); |
67 | mask_err = of_property_read_u32(np: pdev->dev.of_node, propname: "mask" , out_value: &ctx->mask); |
68 | if (value_err && mask_err) { |
69 | dev_err(dev, "unable to read 'value' and 'mask'" ); |
70 | return -EINVAL; |
71 | } |
72 | |
73 | if (value_err) { |
74 | /* support old binding */ |
75 | ctx->value = ctx->mask; |
76 | ctx->mask = 0xFFFFFFFF; |
77 | } else if (mask_err) { |
78 | /* support value without mask*/ |
79 | ctx->mask = 0xFFFFFFFF; |
80 | } |
81 | |
82 | ctx->restart_handler.notifier_call = syscon_restart_handle; |
83 | ctx->restart_handler.priority = priority; |
84 | err = register_restart_handler(&ctx->restart_handler); |
85 | if (err) |
86 | dev_err(dev, "can't register restart notifier (err=%d)\n" , err); |
87 | |
88 | return err; |
89 | } |
90 | |
91 | static const struct of_device_id syscon_reboot_of_match[] = { |
92 | { .compatible = "syscon-reboot" }, |
93 | {} |
94 | }; |
95 | |
96 | static struct platform_driver syscon_reboot_driver = { |
97 | .probe = syscon_reboot_probe, |
98 | .driver = { |
99 | .name = "syscon-reboot" , |
100 | .of_match_table = syscon_reboot_of_match, |
101 | }, |
102 | }; |
103 | builtin_platform_driver(syscon_reboot_driver); |
104 | |