xref: /openbsd-src/sys/dev/fdt/syscon.c (revision 24bb5fcea3ed904bc467217bdaadb5dfc618d5bf)
1 /*	$OpenBSD: syscon.c,v 1.6 2021/04/23 12:49:53 jsg Exp $	*/
2 /*
3  * Copyright (c) 2017 Mark Kettenis
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21 
22 #include <machine/bus.h>
23 #include <machine/fdt.h>
24 
25 #include <dev/ofw/openfirm.h>
26 #include <dev/ofw/ofw_misc.h>
27 #include <dev/ofw/fdt.h>
28 
29 #ifdef __armv7__
30 #include <arm/simplebus/simplebusvar.h>
31 #elif defined(__riscv64__)
32 #include <riscv64/dev/simplebusvar.h>
33 #else
34 #include <arm64/dev/simplebusvar.h>
35 #endif
36 
37 extern void (*cpuresetfn)(void);
38 extern void (*powerdownfn)(void);
39 
40 struct syscon_softc {
41 	struct simplebus_softc	sc_sbus;
42 	bus_space_tag_t		sc_iot;
43 	bus_space_handle_t	sc_ioh;
44 	uint32_t		sc_regmap;
45 	bus_size_t		sc_offset;
46 	uint32_t		sc_mask;
47 	uint32_t		sc_value;
48 };
49 
50 struct syscon_softc *syscon_reboot_sc;
51 struct syscon_softc *syscon_poweroff_sc;
52 
53 int	syscon_match(struct device *, void *, void *);
54 void	syscon_attach(struct device *, struct device *, void *);
55 
56 struct cfattach syscon_ca = {
57 	sizeof(struct syscon_softc), syscon_match, syscon_attach
58 };
59 
60 struct cfdriver syscon_cd = {
61 	NULL, "syscon", DV_DULL
62 };
63 
64 void	syscon_reset(void);
65 void	syscon_powerdown(void);
66 
67 int
68 syscon_match(struct device *parent, void *match, void *aux)
69 {
70 	struct fdt_attach_args *faa = aux;
71 
72 	return OF_is_compatible(faa->fa_node, "syscon") ||
73 	    OF_is_compatible(faa->fa_node, "syscon-reboot") ||
74 	    OF_is_compatible(faa->fa_node, "syscon-poweroff");
75 }
76 
77 void
78 syscon_attach(struct device *parent, struct device *self, void *aux)
79 {
80 	struct syscon_softc *sc = (struct syscon_softc *)self;
81 	struct fdt_attach_args *faa = aux;
82 	char name[32];
83 
84 	OF_getprop(faa->fa_node, "name", name, sizeof(name));
85 	name[sizeof(name) - 1] = 0;
86 
87 	if (OF_is_compatible(faa->fa_node, "syscon")) {
88 		if (faa->fa_nreg < 1) {
89 			printf(": no registers\n");
90 			return;
91 		}
92 
93 		sc->sc_iot = faa->fa_iot;
94 
95 		if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
96 		    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
97 			printf(": can't map registers\n");
98 			return;
99 		}
100 
101 		regmap_register(faa->fa_node, sc->sc_iot, sc->sc_ioh,
102 		    faa->fa_reg[0].size);
103 	}
104 
105 	if (OF_is_compatible(faa->fa_node, "simple-mfd"))
106 		simplebus_attach(parent, &sc->sc_sbus.sc_dev, faa);
107 	else
108 		printf(": \"%s\"\n", name);
109 
110 	if (OF_is_compatible(faa->fa_node, "syscon-reboot") ||
111 	    OF_is_compatible(faa->fa_node, "syscon-poweroff")) {
112 		sc->sc_regmap = OF_getpropint(faa->fa_node, "regmap", 0);
113 		if (sc->sc_regmap == 0)
114 			return;
115 
116 		if (OF_getproplen(faa->fa_node, "offset") != sizeof(uint32_t))
117 			return;
118 
119 		/* At least one of "mask" and "value" should be provided. */
120 		if (OF_getproplen(faa->fa_node, "mask") != sizeof(uint32_t) &&
121 		    OF_getproplen(faa->fa_node, "value") != sizeof(uint32_t))
122 			return;
123 
124 		sc->sc_offset = OF_getpropint(faa->fa_node, "offset", 0);
125 		sc->sc_mask = OF_getpropint(faa->fa_node, "mask", 0xffffffff);
126 		sc->sc_value = OF_getpropint(faa->fa_node, "value", 0);
127 
128 		/*
129 		 * Old binding used "mask" as the value to write with
130 		 * an all-ones mask.  This is still supported.
131 		 */
132 		if (OF_getproplen(faa->fa_node, "value") != sizeof(uint32_t)) {
133 			sc->sc_value = sc->sc_mask;
134 			sc->sc_mask = 0xffffffff;
135 		}
136 
137 		if (OF_is_compatible(faa->fa_node, "syscon-reboot")) {
138 			syscon_reboot_sc = sc;
139 			cpuresetfn = syscon_reset;
140 		} else if (OF_is_compatible(faa->fa_node, "syscon-poweroff")) {
141 			syscon_poweroff_sc = sc;
142 			powerdownfn = syscon_powerdown;
143 		}
144 	}
145 }
146 
147 void
148 syscon_reset(void)
149 {
150 	struct syscon_softc *sc = syscon_reboot_sc;
151 	struct regmap *rm;
152 	uint32_t value;
153 
154 	rm = regmap_byphandle(sc->sc_regmap);
155 	if (rm == NULL)
156 		return;
157 
158 	value = regmap_read_4(rm, sc->sc_offset);
159 	value &= ~sc->sc_mask;
160 	value |= sc->sc_value;
161 	regmap_write_4(rm, sc->sc_offset, value);
162 	delay(1000000);
163 }
164 
165 void
166 syscon_powerdown(void)
167 {
168 	struct syscon_softc *sc = syscon_poweroff_sc;
169 	struct regmap *rm;
170 	uint32_t value;
171 
172 	rm = regmap_byphandle(sc->sc_regmap);
173 	if (rm == NULL)
174 		return;
175 
176 	value = regmap_read_4(rm, sc->sc_offset);
177 	value &= ~sc->sc_mask;
178 	value |= sc->sc_value;
179 	regmap_write_4(rm, sc->sc_offset, value);
180 	delay(1000000);
181 }
182