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(syscon_regmap, INTEGRATOR_HDR_LOCK_OFFSET,
80 			     VERSATILE_LOCK_VAL);
81 		regmap_update_bits(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(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
88 			     VERSATILE_LOCK_VAL);
89 		regmap_update_bits(syscon_regmap,
90 				   VERSATILE_SYS_RESETCTL_OFFSET,
91 				   0x0107,
92 				   0x0105);
93 		regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
94 			     0);
95 		break;
96 	case REALVIEW_REBOOT_EB:
97 		regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
98 			     VERSATILE_LOCK_VAL);
99 		regmap_write(syscon_regmap,
100 			     VERSATILE_SYS_RESETCTL_OFFSET, 0x0008);
101 		break;
102 	case REALVIEW_REBOOT_PB1176:
103 		regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
104 			     VERSATILE_LOCK_VAL);
105 		regmap_write(syscon_regmap,
106 			     VERSATILE_SYS_RESETCTL_OFFSET, 0x0100);
107 		break;
108 	case REALVIEW_REBOOT_PB11MP:
109 	case REALVIEW_REBOOT_PBA8:
110 		regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
111 			     VERSATILE_LOCK_VAL);
112 		regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
113 			     0x0000);
114 		regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
115 			     0x0004);
116 		break;
117 	case REALVIEW_REBOOT_PBX:
118 		regmap_write(syscon_regmap, VERSATILE_SYS_LOCK_OFFSET,
119 			     VERSATILE_LOCK_VAL);
120 		regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
121 			     0x00f0);
122 		regmap_write(syscon_regmap, VERSATILE_SYS_RESETCTL_OFFSET,
123 			     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, versatile_reboot_of_match,
143 						 &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 	if (IS_ERR(syscon_regmap))
150 		return PTR_ERR(syscon_regmap);
151 
152 	err = register_restart_handler(&versatile_reboot_nb);
153 	if (err)
154 		return err;
155 
156 	pr_info("versatile reboot driver registered\n");
157 	return 0;
158 }
159 device_initcall(versatile_reboot_probe);
160