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