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