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