1*94673892Sjsg /* $OpenBSD: syscon.c,v 1.8 2023/09/22 01:10:44 jsg Exp $ */
24163ec60Skettenis /*
34163ec60Skettenis * Copyright (c) 2017 Mark Kettenis
44163ec60Skettenis *
54163ec60Skettenis * Permission to use, copy, modify, and distribute this software for any
64163ec60Skettenis * purpose with or without fee is hereby granted, provided that the above
74163ec60Skettenis * copyright notice and this permission notice appear in all copies.
84163ec60Skettenis *
94163ec60Skettenis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
104163ec60Skettenis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
114163ec60Skettenis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
124163ec60Skettenis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
134163ec60Skettenis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
144163ec60Skettenis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
154163ec60Skettenis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
164163ec60Skettenis */
174163ec60Skettenis
184163ec60Skettenis #include <sys/param.h>
194163ec60Skettenis #include <sys/systm.h>
204163ec60Skettenis #include <sys/device.h>
214163ec60Skettenis
224163ec60Skettenis #include <machine/bus.h>
234163ec60Skettenis #include <machine/fdt.h>
244163ec60Skettenis
254163ec60Skettenis #include <dev/ofw/openfirm.h>
264163ec60Skettenis #include <dev/ofw/ofw_misc.h>
274163ec60Skettenis #include <dev/ofw/fdt.h>
284163ec60Skettenis
29*94673892Sjsg #include <machine/simplebusvar.h>
30c4620c40Skettenis
314163ec60Skettenis extern void (*cpuresetfn)(void);
324163ec60Skettenis extern void (*powerdownfn)(void);
334163ec60Skettenis
344163ec60Skettenis struct syscon_softc {
35c4620c40Skettenis struct simplebus_softc sc_sbus;
365c4de186Spatrick bus_space_tag_t sc_iot;
375c4de186Spatrick bus_space_handle_t sc_ioh;
384163ec60Skettenis uint32_t sc_regmap;
394163ec60Skettenis bus_size_t sc_offset;
404163ec60Skettenis uint32_t sc_mask;
41c891ae27Skettenis uint32_t sc_value;
424163ec60Skettenis };
434163ec60Skettenis
444163ec60Skettenis struct syscon_softc *syscon_reboot_sc;
454163ec60Skettenis struct syscon_softc *syscon_poweroff_sc;
464163ec60Skettenis
474163ec60Skettenis int syscon_match(struct device *, void *, void *);
484163ec60Skettenis void syscon_attach(struct device *, struct device *, void *);
494163ec60Skettenis
509fdf0c62Smpi const struct cfattach syscon_ca = {
514163ec60Skettenis sizeof(struct syscon_softc), syscon_match, syscon_attach
524163ec60Skettenis };
534163ec60Skettenis
544163ec60Skettenis struct cfdriver syscon_cd = {
554163ec60Skettenis NULL, "syscon", DV_DULL
564163ec60Skettenis };
574163ec60Skettenis
584163ec60Skettenis void syscon_reset(void);
594163ec60Skettenis void syscon_powerdown(void);
604163ec60Skettenis
614163ec60Skettenis int
syscon_match(struct device * parent,void * match,void * aux)624163ec60Skettenis syscon_match(struct device *parent, void *match, void *aux)
634163ec60Skettenis {
644163ec60Skettenis struct fdt_attach_args *faa = aux;
654163ec60Skettenis
665c4de186Spatrick return OF_is_compatible(faa->fa_node, "syscon") ||
675c4de186Spatrick OF_is_compatible(faa->fa_node, "syscon-reboot") ||
685c4de186Spatrick OF_is_compatible(faa->fa_node, "syscon-poweroff");
694163ec60Skettenis }
704163ec60Skettenis
714163ec60Skettenis void
syscon_attach(struct device * parent,struct device * self,void * aux)724163ec60Skettenis syscon_attach(struct device *parent, struct device *self, void *aux)
734163ec60Skettenis {
744163ec60Skettenis struct syscon_softc *sc = (struct syscon_softc *)self;
754163ec60Skettenis struct fdt_attach_args *faa = aux;
76079868a5Skettenis char name[32];
774163ec60Skettenis
78c4620c40Skettenis OF_getprop(faa->fa_node, "name", name, sizeof(name));
79c4620c40Skettenis name[sizeof(name) - 1] = 0;
80c4620c40Skettenis
815c4de186Spatrick if (OF_is_compatible(faa->fa_node, "syscon")) {
825c4de186Spatrick if (faa->fa_nreg < 1) {
835c4de186Spatrick printf(": no registers\n");
845c4de186Spatrick return;
855c4de186Spatrick }
865c4de186Spatrick
875c4de186Spatrick sc->sc_iot = faa->fa_iot;
885c4de186Spatrick
895c4de186Spatrick if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
905c4de186Spatrick faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
915c4de186Spatrick printf(": can't map registers\n");
925c4de186Spatrick return;
935c4de186Spatrick }
945c4de186Spatrick
955c4de186Spatrick regmap_register(faa->fa_node, sc->sc_iot, sc->sc_ioh,
965c4de186Spatrick faa->fa_reg[0].size);
97079868a5Skettenis }
98079868a5Skettenis
99c4620c40Skettenis if (OF_is_compatible(faa->fa_node, "simple-mfd"))
100c4620c40Skettenis simplebus_attach(parent, &sc->sc_sbus.sc_dev, faa);
101c4620c40Skettenis else
102c4620c40Skettenis printf(": \"%s\"\n", name);
1035c4de186Spatrick
1045c4de186Spatrick if (OF_is_compatible(faa->fa_node, "syscon-reboot") ||
1055c4de186Spatrick OF_is_compatible(faa->fa_node, "syscon-poweroff")) {
1064163ec60Skettenis sc->sc_regmap = OF_getpropint(faa->fa_node, "regmap", 0);
1074163ec60Skettenis if (sc->sc_regmap == 0)
1084163ec60Skettenis return;
1094163ec60Skettenis
110c891ae27Skettenis if (OF_getproplen(faa->fa_node, "offset") != sizeof(uint32_t))
111c891ae27Skettenis return;
112c891ae27Skettenis
113c891ae27Skettenis /* At least one of "mask" and "value" should be provided. */
114c891ae27Skettenis if (OF_getproplen(faa->fa_node, "mask") != sizeof(uint32_t) &&
115c891ae27Skettenis OF_getproplen(faa->fa_node, "value") != sizeof(uint32_t))
1164163ec60Skettenis return;
1174163ec60Skettenis
1184163ec60Skettenis sc->sc_offset = OF_getpropint(faa->fa_node, "offset", 0);
119c891ae27Skettenis sc->sc_mask = OF_getpropint(faa->fa_node, "mask", 0xffffffff);
120c891ae27Skettenis sc->sc_value = OF_getpropint(faa->fa_node, "value", 0);
121c891ae27Skettenis
122c891ae27Skettenis /*
123c891ae27Skettenis * Old binding used "mask" as the value to write with
124c891ae27Skettenis * an all-ones mask. This is still supported.
125c891ae27Skettenis */
126c891ae27Skettenis if (OF_getproplen(faa->fa_node, "value") != sizeof(uint32_t)) {
127c891ae27Skettenis sc->sc_value = sc->sc_mask;
128c891ae27Skettenis sc->sc_mask = 0xffffffff;
129c891ae27Skettenis }
1304163ec60Skettenis
1314163ec60Skettenis if (OF_is_compatible(faa->fa_node, "syscon-reboot")) {
1324163ec60Skettenis syscon_reboot_sc = sc;
1334163ec60Skettenis cpuresetfn = syscon_reset;
1345c4de186Spatrick } else if (OF_is_compatible(faa->fa_node, "syscon-poweroff")) {
1354163ec60Skettenis syscon_poweroff_sc = sc;
1364163ec60Skettenis powerdownfn = syscon_powerdown;
1374163ec60Skettenis }
1384163ec60Skettenis }
1395c4de186Spatrick }
1404163ec60Skettenis
1414163ec60Skettenis void
syscon_reset(void)1424163ec60Skettenis syscon_reset(void)
1434163ec60Skettenis {
1444163ec60Skettenis struct syscon_softc *sc = syscon_reboot_sc;
1454163ec60Skettenis struct regmap *rm;
146c891ae27Skettenis uint32_t value;
1474163ec60Skettenis
1484163ec60Skettenis rm = regmap_byphandle(sc->sc_regmap);
1494163ec60Skettenis if (rm == NULL)
1504163ec60Skettenis return;
1514163ec60Skettenis
152c891ae27Skettenis value = regmap_read_4(rm, sc->sc_offset);
153c891ae27Skettenis value &= ~sc->sc_mask;
154c891ae27Skettenis value |= sc->sc_value;
155c891ae27Skettenis regmap_write_4(rm, sc->sc_offset, value);
1564163ec60Skettenis delay(1000000);
1574163ec60Skettenis }
1584163ec60Skettenis
1594163ec60Skettenis void
syscon_powerdown(void)1604163ec60Skettenis syscon_powerdown(void)
1614163ec60Skettenis {
1624163ec60Skettenis struct syscon_softc *sc = syscon_poweroff_sc;
1634163ec60Skettenis struct regmap *rm;
164c891ae27Skettenis uint32_t value;
1654163ec60Skettenis
1664163ec60Skettenis rm = regmap_byphandle(sc->sc_regmap);
1674163ec60Skettenis if (rm == NULL)
1684163ec60Skettenis return;
1694163ec60Skettenis
170c891ae27Skettenis value = regmap_read_4(rm, sc->sc_offset);
171c891ae27Skettenis value &= ~sc->sc_mask;
172c891ae27Skettenis value |= sc->sc_value;
173c891ae27Skettenis regmap_write_4(rm, sc->sc_offset, value);
1744163ec60Skettenis delay(1000000);
1754163ec60Skettenis }
176