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 reboot_mode_bits {
18 	u32 offset;
19 	u32 mask;
20 	u32 value;
21 	bool valid;
22 };
23 
24 struct reboot_data {
25 	struct reboot_mode_bits mode_bits[REBOOT_SOFT + 1];
26 	struct reboot_mode_bits catchall;
27 };
28 
29 struct syscon_reboot_context {
30 	struct regmap *map;
31 
32 	const struct reboot_data *rd; /* from of match data, if any */
33 	struct reboot_mode_bits catchall; /* from DT */
34 
35 	struct notifier_block restart_handler;
36 };
37 
38 static int syscon_restart_handle(struct notifier_block *this,
39 					unsigned long mode, void *cmd)
40 {
41 	struct syscon_reboot_context *ctx =
42 			container_of(this, struct syscon_reboot_context,
43 					restart_handler);
44 	const struct reboot_mode_bits *mode_bits;
45 
46 	if (ctx->rd) {
47 		if (mode < ARRAY_SIZE(ctx->rd->mode_bits) &&
48 		    ctx->rd->mode_bits[mode].valid)
49 			mode_bits = &ctx->rd->mode_bits[mode];
50 		else
51 			mode_bits = &ctx->rd->catchall;
52 	} else {
53 		mode_bits = &ctx->catchall;
54 	}
55 
56 	/* Issue the reboot */
57 	regmap_update_bits(ctx->map, mode_bits->offset, mode_bits->mask,
58 			   mode_bits->value);
59 
60 	mdelay(1000);
61 
62 	pr_emerg("Unable to restart system\n");
63 	return NOTIFY_DONE;
64 }
65 
66 static int syscon_reboot_probe(struct platform_device *pdev)
67 {
68 	struct syscon_reboot_context *ctx;
69 	struct device *dev = &pdev->dev;
70 	int priority;
71 	int err;
72 
73 	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
74 	if (!ctx)
75 		return -ENOMEM;
76 
77 	ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
78 	if (IS_ERR(ctx->map)) {
79 		ctx->map = syscon_node_to_regmap(dev->parent->of_node);
80 		if (IS_ERR(ctx->map))
81 			return PTR_ERR(ctx->map);
82 	}
83 
84 	if (of_property_read_s32(pdev->dev.of_node, "priority", &priority))
85 		priority = 192;
86 
87 	ctx->rd = of_device_get_match_data(dev);
88 	if (!ctx->rd) {
89 		int mask_err, value_err;
90 
91 		if (of_property_read_u32(pdev->dev.of_node, "offset",
92 					 &ctx->catchall.offset) &&
93 		    of_property_read_u32(pdev->dev.of_node, "reg",
94 					 &ctx->catchall.offset))
95 			return -EINVAL;
96 
97 		value_err = of_property_read_u32(pdev->dev.of_node, "value",
98 						 &ctx->catchall.value);
99 		mask_err = of_property_read_u32(pdev->dev.of_node, "mask",
100 						&ctx->catchall.mask);
101 		if (value_err && mask_err) {
102 			dev_err(dev, "unable to read 'value' and 'mask'");
103 			return -EINVAL;
104 		}
105 
106 		if (value_err) {
107 			/* support old binding */
108 			ctx->catchall.value = ctx->catchall.mask;
109 			ctx->catchall.mask = 0xFFFFFFFF;
110 		} else if (mask_err) {
111 			/* support value without mask */
112 			ctx->catchall.mask = 0xFFFFFFFF;
113 		}
114 	}
115 
116 	ctx->restart_handler.notifier_call = syscon_restart_handle;
117 	ctx->restart_handler.priority = priority;
118 	err = register_restart_handler(&ctx->restart_handler);
119 	if (err)
120 		dev_err(dev, "can't register restart notifier (err=%d)\n", err);
121 
122 	return err;
123 }
124 
125 static const struct reboot_data gs101_reboot_data = {
126 	.mode_bits = {
127 		[REBOOT_WARM] = {
128 			.offset = 0x3a00, /* SYSTEM_CONFIGURATION */
129 			.mask = 0x00000002, /* SWRESET_SYSTEM */
130 			.value = 0x00000002,
131 			.valid = true,
132 		},
133 		[REBOOT_SOFT] = {
134 			.offset = 0x3a00, /* SYSTEM_CONFIGURATION */
135 			.mask = 0x00000002, /* SWRESET_SYSTEM */
136 			.value = 0x00000002,
137 			.valid = true,
138 		},
139 	},
140 	.catchall = {
141 		.offset = 0x3e9c, /* PAD_CTRL_PWR_HOLD */
142 		.mask = 0x00000100,
143 		.value = 0x00000000,
144 	},
145 };
146 
147 static const struct of_device_id syscon_reboot_of_match[] = {
148 	{ .compatible = "google,gs101-reboot", .data = &gs101_reboot_data  },
149 	{ .compatible = "syscon-reboot" },
150 	{}
151 };
152 
153 static struct platform_driver syscon_reboot_driver = {
154 	.probe = syscon_reboot_probe,
155 	.driver = {
156 		.name = "syscon-reboot",
157 		.of_match_table = syscon_reboot_of_match,
158 	},
159 };
160 builtin_platform_driver(syscon_reboot_driver);
161