1 /* $OpenBSD: aplpmu.c,v 1.7 2022/12/12 18:45:01 kettenis Exp $ */ 2 /* 3 * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org> 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 #include <sys/malloc.h> 22 23 #include <machine/bus.h> 24 #include <machine/fdt.h> 25 26 #include <dev/clock_subr.h> 27 #include <dev/fdt/spmivar.h> 28 #include <dev/ofw/openfirm.h> 29 #include <dev/ofw/ofw_misc.h> 30 #include <dev/ofw/fdt.h> 31 32 extern void (*cpuresetfn)(void); 33 extern void (*powerdownfn)(void); 34 35 /* 36 * This driver is based on preliminary device tree bindings and will 37 * almost certainly need changes once the official bindings land in 38 * mainline Linux. Support for these preliminary bindings will be 39 * dropped as soon as official bindings are available. 40 */ 41 42 /* 43 * Apple's "sera" PMU contains an RTC that provides time in 32.16 44 * fixed-point format as well as a time offset in 33.15 fixed-point 45 * format. The sum of the two gives us a standard Unix timestamp with 46 * sub-second resolution. The time itself is read-only. To set the 47 * time we need to adjust the time offset. 48 */ 49 #define SERA_TIME 0xd002 50 #define SERA_TIME_OFFSET 0xd100 51 #define SERA_TIME_LEN 6 52 53 #define SERA_POWERDOWN 0x9f0f 54 #define SERA_POWERDOWN_MAGIC 0x08 55 56 struct aplpmu_nvmem { 57 struct aplpmu_softc *an_sc; 58 struct nvmem_device an_nd; 59 bus_addr_t an_base; 60 bus_size_t an_size; 61 }; 62 63 struct aplpmu_softc { 64 struct device sc_dev; 65 spmi_tag_t sc_tag; 66 int8_t sc_sid; 67 68 struct todr_chip_handle sc_todr; 69 uint64_t sc_offset; 70 }; 71 72 struct aplpmu_softc *aplpmu_sc; 73 74 int aplpmu_match(struct device *, void *, void *); 75 void aplpmu_attach(struct device *, struct device *, void *); 76 77 const struct cfattach aplpmu_ca = { 78 sizeof (struct aplpmu_softc), aplpmu_match, aplpmu_attach 79 }; 80 81 struct cfdriver aplpmu_cd = { 82 NULL, "aplpmu", DV_DULL 83 }; 84 85 int aplpmu_gettime(struct todr_chip_handle *, struct timeval *); 86 int aplpmu_settime(struct todr_chip_handle *, struct timeval *); 87 void aplpmu_powerdown(void); 88 int aplpmu_nvmem_read(void *, bus_addr_t, void *, bus_size_t); 89 int aplpmu_nvmem_write(void *, bus_addr_t, const void *, bus_size_t); 90 91 int 92 aplpmu_match(struct device *parent, void *match, void *aux) 93 { 94 struct spmi_attach_args *sa = aux; 95 96 return OF_is_compatible(sa->sa_node, "apple,sera-pmu") || 97 OF_is_compatible(sa->sa_node, "apple,spmi-pmu"); 98 } 99 100 void 101 aplpmu_attach(struct device *parent, struct device *self, void *aux) 102 { 103 struct aplpmu_softc *sc = (struct aplpmu_softc *)self; 104 struct spmi_attach_args *sa = aux; 105 uint8_t data[8] = {}; 106 int error, node; 107 108 sc->sc_tag = sa->sa_tag; 109 sc->sc_sid = sa->sa_sid; 110 111 if (OF_is_compatible(sa->sa_node, "apple,sera-pmu")) { 112 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, 113 SPMI_CMD_EXT_READL, SERA_TIME_OFFSET, 114 &data, SERA_TIME_LEN); 115 if (error) { 116 printf(": can't read offset\n"); 117 return; 118 } 119 sc->sc_offset = lemtoh64(data); 120 121 sc->sc_todr.cookie = sc; 122 sc->sc_todr.todr_gettime = aplpmu_gettime; 123 sc->sc_todr.todr_settime = aplpmu_settime; 124 sc->sc_todr.todr_quality = 0; 125 todr_attach(&sc->sc_todr); 126 127 aplpmu_sc = sc; 128 powerdownfn = aplpmu_powerdown; 129 } 130 131 printf("\n"); 132 133 for (node = OF_child(sa->sa_node); node; node = OF_peer(node)) { 134 struct aplpmu_nvmem *an; 135 uint32_t reg[2]; 136 137 if (!OF_is_compatible(node, "apple,spmi-pmu-nvmem")) 138 continue; 139 140 if (OF_getpropintarray(node, "reg", reg, 141 sizeof(reg)) != sizeof(reg)) 142 continue; 143 144 an = malloc(sizeof(*an), M_DEVBUF, M_WAITOK); 145 an->an_sc = sc; 146 an->an_base = reg[0]; 147 an->an_size = reg[1]; 148 an->an_nd.nd_node = node; 149 an->an_nd.nd_cookie = an; 150 an->an_nd.nd_read = aplpmu_nvmem_read; 151 an->an_nd.nd_write = aplpmu_nvmem_write; 152 nvmem_register(&an->an_nd); 153 } 154 } 155 156 int 157 aplpmu_gettime(struct todr_chip_handle *handle, struct timeval *tv) 158 { 159 struct aplpmu_softc *sc = handle->cookie; 160 uint8_t data[8] = {}; 161 uint64_t time; 162 int error; 163 164 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 165 SERA_TIME, &data, SERA_TIME_LEN); 166 if (error) 167 return error; 168 time = lemtoh64(data) + (sc->sc_offset << 1); 169 170 tv->tv_sec = (time >> 16); 171 tv->tv_usec = (((time & 0xffff) * 1000000) >> 16); 172 return 0; 173 } 174 175 int 176 aplpmu_settime(struct todr_chip_handle *handle, struct timeval *tv) 177 { 178 struct aplpmu_softc *sc = handle->cookie; 179 uint8_t data[8] = {}; 180 uint64_t time; 181 int error; 182 183 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 184 SERA_TIME, &data, SERA_TIME_LEN); 185 if (error) 186 return error; 187 188 time = ((uint64_t)tv->tv_sec << 16); 189 time |= ((uint64_t)tv->tv_usec << 16) / 1000000; 190 sc->sc_offset = ((time - lemtoh64(data)) >> 1); 191 192 htolem64(data, sc->sc_offset); 193 return spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, 194 SERA_TIME_OFFSET, &data, SERA_TIME_LEN); 195 } 196 197 void 198 aplpmu_powerdown(void) 199 { 200 struct aplpmu_softc *sc = aplpmu_sc; 201 uint8_t data = SERA_POWERDOWN_MAGIC; 202 203 spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, 204 SERA_POWERDOWN, &data, sizeof(data)); 205 206 cpuresetfn(); 207 } 208 209 int 210 aplpmu_nvmem_read(void *cookie, bus_addr_t addr, void *data, bus_size_t size) 211 { 212 struct aplpmu_nvmem *an = cookie; 213 struct aplpmu_softc *sc = an->an_sc; 214 215 if (addr >= an->an_size || size > an->an_size - addr) 216 return EINVAL; 217 218 return spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 219 an->an_base + addr, data, size); 220 } 221 222 int 223 aplpmu_nvmem_write(void *cookie, bus_addr_t addr, const void *data, 224 bus_size_t size) 225 { 226 struct aplpmu_nvmem *an = cookie; 227 struct aplpmu_softc *sc = an->an_sc; 228 229 if (addr >= an->an_size || size > an->an_size - addr) 230 return EINVAL; 231 232 return spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL, 233 an->an_base + addr, data, size); 234 } 235