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
32extern void (*cpuresetfn)(void);
33extern 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
56struct 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
63struct 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
72struct aplpmu_softc *aplpmu_sc;
73
74int	aplpmu_match(struct device *, void *, void *);
75void	aplpmu_attach(struct device *, struct device *, void *);
76
77const struct cfattach	aplpmu_ca = {
78	sizeof (struct aplpmu_softc), aplpmu_match, aplpmu_attach
79};
80
81struct cfdriver aplpmu_cd = {
82	NULL, "aplpmu", DV_DULL
83};
84
85int	aplpmu_gettime(struct todr_chip_handle *, struct timeval *);
86int	aplpmu_settime(struct todr_chip_handle *, struct timeval *);
87void	aplpmu_powerdown(void);
88int	aplpmu_nvmem_read(void *, bus_addr_t, void *, bus_size_t);
89int	aplpmu_nvmem_write(void *, bus_addr_t, const void *, bus_size_t);
90
91int
92aplpmu_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
100void
101aplpmu_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
156int
157aplpmu_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
175int
176aplpmu_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
197void
198aplpmu_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
209int
210aplpmu_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
222int
223aplpmu_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