xref: /openbsd/sys/dev/fdt/syscon.c (revision 94673892)
1 /*	$OpenBSD: syscon.c,v 1.8 2023/09/22 01:10:44 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 #include <machine/simplebusvar.h>
30 
31 extern void (*cpuresetfn)(void);
32 extern void (*powerdownfn)(void);
33 
34 struct syscon_softc {
35 	struct simplebus_softc	sc_sbus;
36 	bus_space_tag_t		sc_iot;
37 	bus_space_handle_t	sc_ioh;
38 	uint32_t		sc_regmap;
39 	bus_size_t		sc_offset;
40 	uint32_t		sc_mask;
41 	uint32_t		sc_value;
42 };
43 
44 struct syscon_softc *syscon_reboot_sc;
45 struct syscon_softc *syscon_poweroff_sc;
46 
47 int	syscon_match(struct device *, void *, void *);
48 void	syscon_attach(struct device *, struct device *, void *);
49 
50 const struct cfattach syscon_ca = {
51 	sizeof(struct syscon_softc), syscon_match, syscon_attach
52 };
53 
54 struct cfdriver syscon_cd = {
55 	NULL, "syscon", DV_DULL
56 };
57 
58 void	syscon_reset(void);
59 void	syscon_powerdown(void);
60 
61 int
syscon_match(struct device * parent,void * match,void * aux)62 syscon_match(struct device *parent, void *match, void *aux)
63 {
64 	struct fdt_attach_args *faa = aux;
65 
66 	return OF_is_compatible(faa->fa_node, "syscon") ||
67 	    OF_is_compatible(faa->fa_node, "syscon-reboot") ||
68 	    OF_is_compatible(faa->fa_node, "syscon-poweroff");
69 }
70 
71 void
syscon_attach(struct device * parent,struct device * self,void * aux)72 syscon_attach(struct device *parent, struct device *self, void *aux)
73 {
74 	struct syscon_softc *sc = (struct syscon_softc *)self;
75 	struct fdt_attach_args *faa = aux;
76 	char name[32];
77 
78 	OF_getprop(faa->fa_node, "name", name, sizeof(name));
79 	name[sizeof(name) - 1] = 0;
80 
81 	if (OF_is_compatible(faa->fa_node, "syscon")) {
82 		if (faa->fa_nreg < 1) {
83 			printf(": no registers\n");
84 			return;
85 		}
86 
87 		sc->sc_iot = faa->fa_iot;
88 
89 		if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
90 		    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
91 			printf(": can't map registers\n");
92 			return;
93 		}
94 
95 		regmap_register(faa->fa_node, sc->sc_iot, sc->sc_ioh,
96 		    faa->fa_reg[0].size);
97 	}
98 
99 	if (OF_is_compatible(faa->fa_node, "simple-mfd"))
100 		simplebus_attach(parent, &sc->sc_sbus.sc_dev, faa);
101 	else
102 		printf(": \"%s\"\n", name);
103 
104 	if (OF_is_compatible(faa->fa_node, "syscon-reboot") ||
105 	    OF_is_compatible(faa->fa_node, "syscon-poweroff")) {
106 		sc->sc_regmap = OF_getpropint(faa->fa_node, "regmap", 0);
107 		if (sc->sc_regmap == 0)
108 			return;
109 
110 		if (OF_getproplen(faa->fa_node, "offset") != sizeof(uint32_t))
111 			return;
112 
113 		/* At least one of "mask" and "value" should be provided. */
114 		if (OF_getproplen(faa->fa_node, "mask") != sizeof(uint32_t) &&
115 		    OF_getproplen(faa->fa_node, "value") != sizeof(uint32_t))
116 			return;
117 
118 		sc->sc_offset = OF_getpropint(faa->fa_node, "offset", 0);
119 		sc->sc_mask = OF_getpropint(faa->fa_node, "mask", 0xffffffff);
120 		sc->sc_value = OF_getpropint(faa->fa_node, "value", 0);
121 
122 		/*
123 		 * Old binding used "mask" as the value to write with
124 		 * an all-ones mask.  This is still supported.
125 		 */
126 		if (OF_getproplen(faa->fa_node, "value") != sizeof(uint32_t)) {
127 			sc->sc_value = sc->sc_mask;
128 			sc->sc_mask = 0xffffffff;
129 		}
130 
131 		if (OF_is_compatible(faa->fa_node, "syscon-reboot")) {
132 			syscon_reboot_sc = sc;
133 			cpuresetfn = syscon_reset;
134 		} else if (OF_is_compatible(faa->fa_node, "syscon-poweroff")) {
135 			syscon_poweroff_sc = sc;
136 			powerdownfn = syscon_powerdown;
137 		}
138 	}
139 }
140 
141 void
syscon_reset(void)142 syscon_reset(void)
143 {
144 	struct syscon_softc *sc = syscon_reboot_sc;
145 	struct regmap *rm;
146 	uint32_t value;
147 
148 	rm = regmap_byphandle(sc->sc_regmap);
149 	if (rm == NULL)
150 		return;
151 
152 	value = regmap_read_4(rm, sc->sc_offset);
153 	value &= ~sc->sc_mask;
154 	value |= sc->sc_value;
155 	regmap_write_4(rm, sc->sc_offset, value);
156 	delay(1000000);
157 }
158 
159 void
syscon_powerdown(void)160 syscon_powerdown(void)
161 {
162 	struct syscon_softc *sc = syscon_poweroff_sc;
163 	struct regmap *rm;
164 	uint32_t value;
165 
166 	rm = regmap_byphandle(sc->sc_regmap);
167 	if (rm == NULL)
168 		return;
169 
170 	value = regmap_read_4(rm, sc->sc_offset);
171 	value &= ~sc->sc_mask;
172 	value |= sc->sc_value;
173 	regmap_write_4(rm, sc->sc_offset, value);
174 	delay(1000000);
175 }
176