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 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 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 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 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