xref: /openbsd-src/sys/arch/arm64/dev/aplpmu.c (revision b8b628d8366fa32425f070b667dbd2f6fcbc1317)
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