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
aplpmu_match(struct device * parent,void * match,void * aux)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
aplpmu_attach(struct device * parent,struct device * self,void * aux)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
aplpmu_gettime(struct todr_chip_handle * handle,struct timeval * tv)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
aplpmu_settime(struct todr_chip_handle * handle,struct timeval * tv)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
aplpmu_powerdown(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
aplpmu_nvmem_read(void * cookie,bus_addr_t addr,void * data,bus_size_t size)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
aplpmu_nvmem_write(void * cookie,bus_addr_t addr,const void * data,bus_size_t size)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