1*b8b628d8Skettenis /* $OpenBSD: aplpmu.c,v 1.7 2022/12/12 18:45:01 kettenis Exp $ */
25224032cSkettenis /*
35224032cSkettenis * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
45224032cSkettenis *
55224032cSkettenis * Permission to use, copy, modify, and distribute this software for any
65224032cSkettenis * purpose with or without fee is hereby granted, provided that the above
75224032cSkettenis * copyright notice and this permission notice appear in all copies.
85224032cSkettenis *
95224032cSkettenis * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
105224032cSkettenis * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
115224032cSkettenis * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
125224032cSkettenis * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
135224032cSkettenis * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
145224032cSkettenis * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
155224032cSkettenis * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
165224032cSkettenis */
175224032cSkettenis
185224032cSkettenis #include <sys/param.h>
195224032cSkettenis #include <sys/systm.h>
205224032cSkettenis #include <sys/device.h>
21fe3bed6aSkettenis #include <sys/malloc.h>
225224032cSkettenis
235224032cSkettenis #include <machine/bus.h>
245224032cSkettenis #include <machine/fdt.h>
255224032cSkettenis
265224032cSkettenis #include <dev/clock_subr.h>
275224032cSkettenis #include <dev/fdt/spmivar.h>
285224032cSkettenis #include <dev/ofw/openfirm.h>
29fe3bed6aSkettenis #include <dev/ofw/ofw_misc.h>
305224032cSkettenis #include <dev/ofw/fdt.h>
315224032cSkettenis
32a5505455Skettenis extern void (*cpuresetfn)(void);
33a5505455Skettenis extern void (*powerdownfn)(void);
34a5505455Skettenis
355224032cSkettenis /*
365224032cSkettenis * This driver is based on preliminary device tree bindings and will
375224032cSkettenis * almost certainly need changes once the official bindings land in
385224032cSkettenis * mainline Linux. Support for these preliminary bindings will be
395224032cSkettenis * dropped as soon as official bindings are available.
405224032cSkettenis */
415224032cSkettenis
425224032cSkettenis /*
435224032cSkettenis * Apple's "sera" PMU contains an RTC that provides time in 32.16
445224032cSkettenis * fixed-point format as well as a time offset in 33.15 fixed-point
455224032cSkettenis * format. The sum of the two gives us a standard Unix timestamp with
465224032cSkettenis * sub-second resolution. The time itself is read-only. To set the
475224032cSkettenis * time we need to adjust the time offset.
485224032cSkettenis */
495224032cSkettenis #define SERA_TIME 0xd002
505224032cSkettenis #define SERA_TIME_OFFSET 0xd100
515224032cSkettenis #define SERA_TIME_LEN 6
525224032cSkettenis
53a5505455Skettenis #define SERA_POWERDOWN 0x9f0f
54a5505455Skettenis #define SERA_POWERDOWN_MAGIC 0x08
55a5505455Skettenis
56fe3bed6aSkettenis struct aplpmu_nvmem {
57fe3bed6aSkettenis struct aplpmu_softc *an_sc;
58fe3bed6aSkettenis struct nvmem_device an_nd;
59fe3bed6aSkettenis bus_addr_t an_base;
60fe3bed6aSkettenis bus_size_t an_size;
61fe3bed6aSkettenis };
62fe3bed6aSkettenis
635224032cSkettenis struct aplpmu_softc {
645224032cSkettenis struct device sc_dev;
655224032cSkettenis spmi_tag_t sc_tag;
665224032cSkettenis int8_t sc_sid;
675224032cSkettenis
685224032cSkettenis struct todr_chip_handle sc_todr;
695224032cSkettenis uint64_t sc_offset;
705224032cSkettenis };
715224032cSkettenis
72a5505455Skettenis struct aplpmu_softc *aplpmu_sc;
73a5505455Skettenis
745224032cSkettenis int aplpmu_match(struct device *, void *, void *);
755224032cSkettenis void aplpmu_attach(struct device *, struct device *, void *);
765224032cSkettenis
77471aeecfSnaddy const struct cfattach aplpmu_ca = {
785224032cSkettenis sizeof (struct aplpmu_softc), aplpmu_match, aplpmu_attach
795224032cSkettenis };
805224032cSkettenis
815224032cSkettenis struct cfdriver aplpmu_cd = {
825224032cSkettenis NULL, "aplpmu", DV_DULL
835224032cSkettenis };
845224032cSkettenis
855224032cSkettenis int aplpmu_gettime(struct todr_chip_handle *, struct timeval *);
865224032cSkettenis int aplpmu_settime(struct todr_chip_handle *, struct timeval *);
87a5505455Skettenis void aplpmu_powerdown(void);
88fe3bed6aSkettenis int aplpmu_nvmem_read(void *, bus_addr_t, void *, bus_size_t);
89fe3bed6aSkettenis int aplpmu_nvmem_write(void *, bus_addr_t, const void *, bus_size_t);
905224032cSkettenis
915224032cSkettenis int
aplpmu_match(struct device * parent,void * match,void * aux)925224032cSkettenis aplpmu_match(struct device *parent, void *match, void *aux)
935224032cSkettenis {
945224032cSkettenis struct spmi_attach_args *sa = aux;
955224032cSkettenis
96fe3bed6aSkettenis return OF_is_compatible(sa->sa_node, "apple,sera-pmu") ||
97fe3bed6aSkettenis OF_is_compatible(sa->sa_node, "apple,spmi-pmu");
985224032cSkettenis }
995224032cSkettenis
1005224032cSkettenis void
aplpmu_attach(struct device * parent,struct device * self,void * aux)1015224032cSkettenis aplpmu_attach(struct device *parent, struct device *self, void *aux)
1025224032cSkettenis {
1035224032cSkettenis struct aplpmu_softc *sc = (struct aplpmu_softc *)self;
1045224032cSkettenis struct spmi_attach_args *sa = aux;
1055224032cSkettenis uint8_t data[8] = {};
106fe3bed6aSkettenis int error, node;
1075224032cSkettenis
1085224032cSkettenis sc->sc_tag = sa->sa_tag;
1095224032cSkettenis sc->sc_sid = sa->sa_sid;
1105224032cSkettenis
111fe3bed6aSkettenis if (OF_is_compatible(sa->sa_node, "apple,sera-pmu")) {
112fe3bed6aSkettenis error = spmi_cmd_read(sc->sc_tag, sc->sc_sid,
113fe3bed6aSkettenis SPMI_CMD_EXT_READL, SERA_TIME_OFFSET,
114fe3bed6aSkettenis &data, SERA_TIME_LEN);
1155224032cSkettenis if (error) {
1165224032cSkettenis printf(": can't read offset\n");
1175224032cSkettenis return;
1185224032cSkettenis }
1195224032cSkettenis sc->sc_offset = lemtoh64(data);
1205224032cSkettenis
1215224032cSkettenis sc->sc_todr.cookie = sc;
1225224032cSkettenis sc->sc_todr.todr_gettime = aplpmu_gettime;
1235224032cSkettenis sc->sc_todr.todr_settime = aplpmu_settime;
1240701a158Skettenis sc->sc_todr.todr_quality = 0;
125d350188aSkettenis todr_attach(&sc->sc_todr);
126a5505455Skettenis
127a5505455Skettenis aplpmu_sc = sc;
128a5505455Skettenis powerdownfn = aplpmu_powerdown;
1295224032cSkettenis }
1305224032cSkettenis
131fe3bed6aSkettenis printf("\n");
132fe3bed6aSkettenis
133fe3bed6aSkettenis for (node = OF_child(sa->sa_node); node; node = OF_peer(node)) {
134fe3bed6aSkettenis struct aplpmu_nvmem *an;
135fe3bed6aSkettenis uint32_t reg[2];
136fe3bed6aSkettenis
137fe3bed6aSkettenis if (!OF_is_compatible(node, "apple,spmi-pmu-nvmem"))
138fe3bed6aSkettenis continue;
139fe3bed6aSkettenis
140fe3bed6aSkettenis if (OF_getpropintarray(node, "reg", reg,
141fe3bed6aSkettenis sizeof(reg)) != sizeof(reg))
142fe3bed6aSkettenis continue;
143fe3bed6aSkettenis
144fe3bed6aSkettenis an = malloc(sizeof(*an), M_DEVBUF, M_WAITOK);
145fe3bed6aSkettenis an->an_sc = sc;
146fe3bed6aSkettenis an->an_base = reg[0];
147fe3bed6aSkettenis an->an_size = reg[1];
148fe3bed6aSkettenis an->an_nd.nd_node = node;
149fe3bed6aSkettenis an->an_nd.nd_cookie = an;
150fe3bed6aSkettenis an->an_nd.nd_read = aplpmu_nvmem_read;
151fe3bed6aSkettenis an->an_nd.nd_write = aplpmu_nvmem_write;
152fe3bed6aSkettenis nvmem_register(&an->an_nd);
153fe3bed6aSkettenis }
154fe3bed6aSkettenis }
155fe3bed6aSkettenis
1565224032cSkettenis int
aplpmu_gettime(struct todr_chip_handle * handle,struct timeval * tv)1575224032cSkettenis aplpmu_gettime(struct todr_chip_handle *handle, struct timeval *tv)
1585224032cSkettenis {
1595224032cSkettenis struct aplpmu_softc *sc = handle->cookie;
1605224032cSkettenis uint8_t data[8] = {};
1615224032cSkettenis uint64_t time;
1625224032cSkettenis int error;
1635224032cSkettenis
1645224032cSkettenis error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
1655224032cSkettenis SERA_TIME, &data, SERA_TIME_LEN);
1665224032cSkettenis if (error)
1675224032cSkettenis return error;
1685224032cSkettenis time = lemtoh64(data) + (sc->sc_offset << 1);
1695224032cSkettenis
1705224032cSkettenis tv->tv_sec = (time >> 16);
1715224032cSkettenis tv->tv_usec = (((time & 0xffff) * 1000000) >> 16);
1725224032cSkettenis return 0;
1735224032cSkettenis }
1745224032cSkettenis
1755224032cSkettenis int
aplpmu_settime(struct todr_chip_handle * handle,struct timeval * tv)1765224032cSkettenis aplpmu_settime(struct todr_chip_handle *handle, struct timeval *tv)
1775224032cSkettenis {
1785224032cSkettenis struct aplpmu_softc *sc = handle->cookie;
1795224032cSkettenis uint8_t data[8] = {};
1805224032cSkettenis uint64_t time;
1815224032cSkettenis int error;
1825224032cSkettenis
1835224032cSkettenis error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
1845224032cSkettenis SERA_TIME, &data, SERA_TIME_LEN);
1855224032cSkettenis if (error)
1865224032cSkettenis return error;
1875224032cSkettenis
1885224032cSkettenis time = ((uint64_t)tv->tv_sec << 16);
1895224032cSkettenis time |= ((uint64_t)tv->tv_usec << 16) / 1000000;
1905224032cSkettenis sc->sc_offset = ((time - lemtoh64(data)) >> 1);
1915224032cSkettenis
1925224032cSkettenis htolem64(data, sc->sc_offset);
1935224032cSkettenis return spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL,
1945224032cSkettenis SERA_TIME_OFFSET, &data, SERA_TIME_LEN);
1955224032cSkettenis }
196a5505455Skettenis
197a5505455Skettenis void
aplpmu_powerdown(void)198a5505455Skettenis aplpmu_powerdown(void)
199a5505455Skettenis {
200a5505455Skettenis struct aplpmu_softc *sc = aplpmu_sc;
201a5505455Skettenis uint8_t data = SERA_POWERDOWN_MAGIC;
202a5505455Skettenis
203a5505455Skettenis spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL,
204a5505455Skettenis SERA_POWERDOWN, &data, sizeof(data));
205a5505455Skettenis
206a5505455Skettenis cpuresetfn();
207a5505455Skettenis }
208fe3bed6aSkettenis
209fe3bed6aSkettenis int
aplpmu_nvmem_read(void * cookie,bus_addr_t addr,void * data,bus_size_t size)210fe3bed6aSkettenis aplpmu_nvmem_read(void *cookie, bus_addr_t addr, void *data, bus_size_t size)
211fe3bed6aSkettenis {
212fe3bed6aSkettenis struct aplpmu_nvmem *an = cookie;
213fe3bed6aSkettenis struct aplpmu_softc *sc = an->an_sc;
214fe3bed6aSkettenis
215*b8b628d8Skettenis if (addr >= an->an_size || size > an->an_size - addr)
216fe3bed6aSkettenis return EINVAL;
217fe3bed6aSkettenis
218fe3bed6aSkettenis return spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
219fe3bed6aSkettenis an->an_base + addr, data, size);
220fe3bed6aSkettenis }
221fe3bed6aSkettenis
222fe3bed6aSkettenis int
aplpmu_nvmem_write(void * cookie,bus_addr_t addr,const void * data,bus_size_t size)223fe3bed6aSkettenis aplpmu_nvmem_write(void *cookie, bus_addr_t addr, const void *data,
224fe3bed6aSkettenis bus_size_t size)
225fe3bed6aSkettenis {
226fe3bed6aSkettenis struct aplpmu_nvmem *an = cookie;
227fe3bed6aSkettenis struct aplpmu_softc *sc = an->an_sc;
228fe3bed6aSkettenis
229*b8b628d8Skettenis if (addr >= an->an_size || size > an->an_size - addr)
230fe3bed6aSkettenis return EINVAL;
231fe3bed6aSkettenis
232fe3bed6aSkettenis return spmi_cmd_write(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_WRITEL,
233fe3bed6aSkettenis an->an_base + addr, data, size);
234fe3bed6aSkettenis }
235