qcomspmi.c revision 1.1 1 /* $NetBSD: qcomspmi.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
2 /* $OpenBSD: qcspmi.c,v 1.6 2024/08/14 10:54:58 mglocker Exp $ */
3 /*
4 * Copyright (c) 2022 Patrick Wildt <patrick (at) blueri.se>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/param.h>
20 #include <sys/kmem.h>
21 #include <sys/systm.h>
22 #include <sys/bus.h>
23 #include <sys/device.h>
24
25 #include <dev/acpi/acpivar.h>
26
27 /* SPMI commands */
28 #define SPMI_CMD_EXT_WRITEL 0x30
29 #define SPMI_CMD_EXT_READL 0x38
30
31 /* Core registers. */
32 #define SPMI_VERSION 0x00
33 #define SPMI_VERSION_V2_MIN 0x20010000
34 #define SPMI_VERSION_V3_MIN 0x30000000
35 #define SPMI_VERSION_V5_MIN 0x50000000
36 #define SPMI_VERSION_V7_MIN 0x70000000
37 #define SPMI_ARB_APID_MAP(sc, x) ((sc)->sc_arb_apid_map + (x) * 0x4)
38 #define SPMI_ARB_APID_MAP_PPID_MASK 0xfff
39 #define SPMI_ARB_APID_MAP_PPID_SHIFT 8
40 #define SPMI_ARB_APID_MAP_IRQ_OWNER (1 << 14)
41
42 /* Channel registers. */
43 #define SPMI_CHAN_OFF(sc, x) ((sc)->sc_chan_stride * (x))
44 #define SPMI_OBSV_OFF(sc, x, y) \
45 ((sc)->sc_obsv_ee_stride * (x) + (sc)->sc_obsv_apid_stride * (y))
46 #define SPMI_COMMAND 0x00
47 #define SPMI_COMMAND_OP_EXT_WRITEL (0 << 27)
48 #define SPMI_COMMAND_OP_EXT_READL (1 << 27)
49 #define SPMI_COMMAND_OP_EXT_WRITE (2 << 27)
50 #define SPMI_COMMAND_OP_RESET (3 << 27)
51 #define SPMI_COMMAND_OP_SLEEP (4 << 27)
52 #define SPMI_COMMAND_OP_SHUTDOWN (5 << 27)
53 #define SPMI_COMMAND_OP_WAKEUP (6 << 27)
54 #define SPMI_COMMAND_OP_AUTHENTICATE (7 << 27)
55 #define SPMI_COMMAND_OP_MSTR_READ (8 << 27)
56 #define SPMI_COMMAND_OP_MSTR_WRITE (9 << 27)
57 #define SPMI_COMMAND_OP_EXT_READ (13 << 27)
58 #define SPMI_COMMAND_OP_WRITE (14 << 27)
59 #define SPMI_COMMAND_OP_READ (15 << 27)
60 #define SPMI_COMMAND_OP_ZERO_WRITE (16 << 27)
61 #define SPMI_COMMAND_ADDR(x) (((x) & 0xff) << 4)
62 #define SPMI_COMMAND_LEN(x) (((x) & 0x7) << 0)
63 #define SPMI_CONFIG 0x04
64 #define SPMI_STATUS 0x08
65 #define SPMI_STATUS_DONE (1 << 0)
66 #define SPMI_STATUS_FAILURE (1 << 1)
67 #define SPMI_STATUS_DENIED (1 << 2)
68 #define SPMI_STATUS_DROPPED (1 << 3)
69 #define SPMI_WDATA0 0x10
70 #define SPMI_WDATA1 0x14
71 #define SPMI_RDATA0 0x18
72 #define SPMI_RDATA1 0x1c
73 #define SPMI_ACC_ENABLE 0x100
74 #define SPMI_ACC_ENABLE_BIT (1 << 0)
75 #define SPMI_IRQ_STATUS 0x104
76 #define SPMI_IRQ_CLEAR 0x108
77
78 /* Intr registers */
79 #define SPMI_OWNER_ACC_STATUS(sc, x, y) \
80 ((sc)->sc_chan_stride * (x) + 0x4 * (y))
81
82 /* Config registers */
83 #define SPMI_OWNERSHIP_TABLE(sc, x) ((sc)->sc_ownership_table + (x) * 0x4)
84 #define SPMI_OWNERSHIP_TABLE_OWNER(x) ((x) & 0x7)
85
86 /* Misc */
87 #define SPMI_MAX_PERIPH 1024
88 #define SPMI_MAX_PPID 4096
89 #define SPMI_PPID_TO_APID_VALID (1U << 15)
90 #define SPMI_PPID_TO_APID_MASK (0x7fff)
91
92 /* Intr commands */
93 #define INTR_RT_STS 0x10
94 #define INTR_SET_TYPE 0x11
95 #define INTR_POLARITY_HIGH 0x12
96 #define INTR_POLARITY_LOW 0x13
97 #define INTR_LATCHED_CLR 0x14
98 #define INTR_EN_SET 0x15
99 #define INTR_EN_CLR 0x16
100 #define INTR_LATCHED_STS 0x18
101
102 #define HREAD4(sc, obj, reg) \
103 bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, \
104 (sc)->sc_data->regs[obj] + (reg))
105 #define HWRITE4(sc, obj, reg, val) \
106 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, \
107 (sc)->sc_data->regs[obj] + (reg), (val))
108
109 #define QCSPMI_REG_CORE 0
110 #define QCSPMI_REG_CHNLS 1
111 #define QCSPMI_REG_OBSRVR 2
112 #define QCSPMI_REG_INTR 3
113 #define QCSPMI_REG_CNFG 4
114 #define QCSPMI_REG_MAX 5
115
116 struct qcspmi_apid {
117 uint16_t ppid;
118 uint8_t write_ee;
119 uint8_t irq_ee;
120 };
121
122 struct qcspmi_data {
123 bus_size_t regs[QCSPMI_REG_MAX];
124 int ee;
125 };
126
127 struct qcspmi_softc {
128 device_t sc_dev;
129
130 bus_space_tag_t sc_iot;
131 bus_space_handle_t sc_ioh;
132
133 const struct qcspmi_data *sc_data;
134
135 int sc_ee;
136
137 struct qcspmi_apid sc_apid[SPMI_MAX_PERIPH];
138 uint16_t sc_ppid_to_apid[SPMI_MAX_PPID];
139 uint16_t sc_max_periph;
140 bus_size_t sc_chan_stride;
141 bus_size_t sc_obsv_ee_stride;
142 bus_size_t sc_obsv_apid_stride;
143 bus_size_t sc_arb_apid_map;
144 bus_size_t sc_ownership_table;
145 };
146
147 static int qcspmi_match(device_t, cfdata_t, void *);
148 static void qcspmi_attach(device_t, device_t, void *);
149
150 int qcspmi_cmd_read(struct qcspmi_softc *, uint8_t, uint8_t,
151 uint16_t, void *, size_t);
152 int qcspmi_cmd_write(struct qcspmi_softc *, uint8_t, uint8_t, uint16_t,
153 const void *, size_t);
154
155 CFATTACH_DECL_NEW(qcomspmi, sizeof(struct qcspmi_softc),
156 qcspmi_match, qcspmi_attach, NULL, NULL);
157
158 static const struct qcspmi_data qcspmi_x1e_data = {
159 .ee = 0,
160 .regs = {
161 [QCSPMI_REG_CORE] = 0x0,
162 [QCSPMI_REG_CHNLS] = 0x100000,
163 [QCSPMI_REG_OBSRVR] = 0x40000,
164 [QCSPMI_REG_INTR] = 0xc0000,
165 [QCSPMI_REG_CNFG] = 0x2d000,
166 },
167 };
168
169 static const struct device_compatible_entry compat_data[] = {
170 { .compat = "QCOM0C0B", .data = &qcspmi_x1e_data },
171 DEVICE_COMPAT_EOL
172 };
173
174 static int
175 qcspmi_match(device_t parent, cfdata_t match, void *aux)
176 {
177 struct acpi_attach_args *aa = aux;
178
179 return acpi_compatible_match(aa, compat_data);
180 }
181
182 void
183 qcspmi_attach(device_t parent, device_t self, void *aux)
184 {
185 struct acpi_attach_args *aa = aux;
186 struct qcspmi_softc *sc = device_private(self);
187 struct qcspmi_apid *apid, *last_apid;
188 struct acpi_resources res;
189 struct acpi_mem *mem;
190 uint32_t val, ppid, irq_own;
191 ACPI_STATUS rv;
192 int error, i;
193
194 rv = acpi_resource_parse(sc->sc_dev, aa->aa_node->ad_handle, "_CRS",
195 &res, &acpi_resource_parse_ops_default);
196 if (ACPI_FAILURE(rv)) {
197 return;
198 }
199
200 mem = acpi_res_mem(&res, 0);
201 if (mem == NULL) {
202 aprint_error_dev(self, "couldn't find mem resource\n");
203 goto done;
204 }
205
206 sc->sc_dev = self;
207 sc->sc_data = acpi_compatible_lookup(aa, compat_data)->data;
208 sc->sc_iot = aa->aa_memt;
209 error = bus_space_map(sc->sc_iot, mem->ar_base, mem->ar_length, 0,
210 &sc->sc_ioh);
211 if (error != 0) {
212 aprint_error_dev(self, "couldn't map registers\n");
213 goto done;
214 }
215
216 /* Support only version 5 and 7 for now */
217 val = HREAD4(sc, QCSPMI_REG_CORE, SPMI_VERSION);
218 if (val < SPMI_VERSION_V5_MIN) {
219 printf(": unsupported version 0x%08x\n", val);
220 return;
221 }
222
223 if (val < SPMI_VERSION_V7_MIN) {
224 sc->sc_max_periph = 512;
225 sc->sc_chan_stride = 0x10000;
226 sc->sc_obsv_ee_stride = 0x10000;
227 sc->sc_obsv_apid_stride = 0x00080;
228 sc->sc_arb_apid_map = 0x00900;
229 sc->sc_ownership_table = 0x00700;
230 } else {
231 sc->sc_max_periph = 1024;
232 sc->sc_chan_stride = 0x01000;
233 sc->sc_obsv_ee_stride = 0x08000;
234 sc->sc_obsv_apid_stride = 0x00020;
235 sc->sc_arb_apid_map = 0x02000;
236 sc->sc_ownership_table = 0x00000;
237 }
238
239 KASSERT(sc->sc_max_periph <= SPMI_MAX_PERIPH);
240
241 sc->sc_ee = sc->sc_data->ee;
242
243 for (i = 0; i < sc->sc_max_periph; i++) {
244 val = HREAD4(sc, QCSPMI_REG_CORE, SPMI_ARB_APID_MAP(sc, i));
245 if (!val)
246 continue;
247 ppid = (val >> SPMI_ARB_APID_MAP_PPID_SHIFT) &
248 SPMI_ARB_APID_MAP_PPID_MASK;
249 irq_own = val & SPMI_ARB_APID_MAP_IRQ_OWNER;
250 val = HREAD4(sc, QCSPMI_REG_CNFG, SPMI_OWNERSHIP_TABLE(sc, i));
251 apid = &sc->sc_apid[i];
252 apid->write_ee = SPMI_OWNERSHIP_TABLE_OWNER(val);
253 apid->irq_ee = 0xff;
254 if (irq_own)
255 apid->irq_ee = apid->write_ee;
256 last_apid = &sc->sc_apid[sc->sc_ppid_to_apid[ppid] &
257 SPMI_PPID_TO_APID_MASK];
258 if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID) ||
259 apid->write_ee == sc->sc_ee) {
260 sc->sc_ppid_to_apid[ppid] = SPMI_PPID_TO_APID_VALID | i;
261 } else if ((sc->sc_ppid_to_apid[ppid] &
262 SPMI_PPID_TO_APID_VALID) && irq_own &&
263 last_apid->write_ee == sc->sc_ee) {
264 last_apid->irq_ee = apid->irq_ee;
265 }
266 }
267
268 done:
269 acpi_resource_cleanup(&res);
270 }
271
272 int
273 qcspmi_cmd_read(struct qcspmi_softc *sc, uint8_t sid, uint8_t cmd,
274 uint16_t addr, void *buf, size_t len)
275 {
276 uint8_t *cbuf = buf;
277 uint32_t reg;
278 uint16_t apid, ppid;
279 int bc = len - 1;
280 int i;
281
282 if (len == 0 || len > 8)
283 return EINVAL;
284
285 /* TODO: support more types */
286 if (cmd != SPMI_CMD_EXT_READL)
287 return EINVAL;
288
289 ppid = (sid << 8) | (addr >> 8);
290 if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID))
291 return ENXIO;
292 apid = sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_MASK;
293
294 HWRITE4(sc, QCSPMI_REG_OBSRVR,
295 SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_COMMAND,
296 SPMI_COMMAND_OP_EXT_READL | SPMI_COMMAND_ADDR(addr) |
297 SPMI_COMMAND_LEN(bc));
298
299 for (i = 1000; i > 0; i--) {
300 reg = HREAD4(sc, QCSPMI_REG_OBSRVR,
301 SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_STATUS);
302 if (reg & SPMI_STATUS_DONE)
303 break;
304 if (reg & SPMI_STATUS_FAILURE) {
305 printf(": transaction failed\n");
306 return EIO;
307 }
308 if (reg & SPMI_STATUS_DENIED) {
309 printf(": transaction denied\n");
310 return EIO;
311 }
312 if (reg & SPMI_STATUS_DROPPED) {
313 printf(": transaction dropped\n");
314 return EIO;
315 }
316 }
317 if (i == 0) {
318 printf("\n");
319 return ETIMEDOUT;
320 }
321
322 if (len > 0) {
323 reg = HREAD4(sc, QCSPMI_REG_OBSRVR,
324 SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_RDATA0);
325 memcpy(cbuf, ®, MIN(len, 4));
326 cbuf += MIN(len, 4);
327 len -= MIN(len, 4);
328 }
329 if (len > 0) {
330 reg = HREAD4(sc, QCSPMI_REG_OBSRVR,
331 SPMI_OBSV_OFF(sc, sc->sc_ee, apid) + SPMI_RDATA1);
332 memcpy(cbuf, ®, MIN(len, 4));
333 cbuf += MIN(len, 4);
334 len -= MIN(len, 4);
335 }
336
337 return 0;
338 }
339
340 int
341 qcspmi_cmd_write(struct qcspmi_softc *sc, uint8_t sid, uint8_t cmd,
342 uint16_t addr, const void *buf, size_t len)
343 {
344 const uint8_t *cbuf = buf;
345 uint32_t reg;
346 uint16_t apid, ppid;
347 int bc = len - 1;
348 int i;
349
350 if (len == 0 || len > 8)
351 return EINVAL;
352
353 /* TODO: support more types */
354 if (cmd != SPMI_CMD_EXT_WRITEL)
355 return EINVAL;
356
357 ppid = (sid << 8) | (addr >> 8);
358 if (!(sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_VALID))
359 return ENXIO;
360 apid = sc->sc_ppid_to_apid[ppid] & SPMI_PPID_TO_APID_MASK;
361
362 if (sc->sc_apid[apid].write_ee != sc->sc_ee)
363 return EPERM;
364
365 if (len > 0) {
366 memcpy(®, cbuf, MIN(len, 4));
367 HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) +
368 SPMI_WDATA0, reg);
369 cbuf += MIN(len, 4);
370 len -= MIN(len, 4);
371 }
372 if (len > 0) {
373 memcpy(®, cbuf, MIN(len, 4));
374 HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) +
375 SPMI_WDATA1, reg);
376 cbuf += MIN(len, 4);
377 len -= MIN(len, 4);
378 }
379
380 HWRITE4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) + SPMI_COMMAND,
381 SPMI_COMMAND_OP_EXT_WRITEL | SPMI_COMMAND_ADDR(addr) |
382 SPMI_COMMAND_LEN(bc));
383
384 for (i = 1000; i > 0; i--) {
385 reg = HREAD4(sc, QCSPMI_REG_CHNLS, SPMI_CHAN_OFF(sc, apid) +
386 SPMI_STATUS);
387 if (reg & SPMI_STATUS_DONE)
388 break;
389 }
390 if (i == 0)
391 return ETIMEDOUT;
392
393 if (reg & SPMI_STATUS_FAILURE ||
394 reg & SPMI_STATUS_DENIED ||
395 reg & SPMI_STATUS_DROPPED)
396 return EIO;
397
398 return 0;
399 }
400