1/*	$OpenBSD: aplspmi.c,v 1.2 2022/04/06 18:59:26 naddy 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
22#include <machine/bus.h>
23#include <machine/fdt.h>
24
25#include <dev/ofw/openfirm.h>
26#include <dev/ofw/fdt.h>
27
28#include <dev/fdt/spmivar.h>
29
30/*
31 * This driver is based on preliminary device tree bindings and will
32 * almost certainly need changes once the official bindings land in
33 * mainline Linux.  Support for these preliminary bindings will be
34 * dropped as soon as official bindings are available.
35 */
36
37#define SPMI_STAT		0x00
38#define  SPMI_STAT_RXEMPTY		(1 << 24)
39#define  SPMI_STAT_TXEMPTY		(1 << 8)
40#define SPMI_CMD		0x04
41#define  SPMI_CMD_ADDR(x)		((x) << 16)
42#define  SPMI_CMD_LAST			(1 << 15)
43#define  SPMI_CMD_SID(x)		((x) << 8)
44#define SPMI_RESP		0x08
45#define SPMI_INTEN(i)		(0x20 + (i) * 4)
46#define SPMI_INTSTAT(i)		(0x60 + (i) * 4)
47
48#define HREAD4(sc, reg)							\
49	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
50#define HWRITE4(sc, reg, val)						\
51	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
52
53struct aplspmi_softc {
54	struct device		sc_dev;
55	bus_space_tag_t		sc_iot;
56	bus_space_handle_t	sc_ioh;
57
58	struct spmi_controller	sc_tag;
59};
60
61int	aplspmi_match(struct device *, void *, void *);
62void	aplspmi_attach(struct device *, struct device *, void *);
63
64const struct cfattach	aplspmi_ca = {
65	sizeof (struct aplspmi_softc), aplspmi_match, aplspmi_attach
66};
67
68struct cfdriver aplspmi_cd = {
69	NULL, "aplspmi", DV_DULL
70};
71
72int	aplspmi_print(void *, const char *);
73int	aplspmi_cmd_read(void *, uint8_t, uint8_t, uint16_t, void *, size_t);
74int	aplspmi_cmd_write(void *, uint8_t, uint8_t, uint16_t,
75	    const void *, size_t);
76
77int
78aplspmi_match(struct device *parent, void *match, void *aux)
79{
80	struct fdt_attach_args *faa = aux;
81
82	return OF_is_compatible(faa->fa_node, "apple,spmi");
83}
84
85void
86aplspmi_attach(struct device *parent, struct device *self, void *aux)
87{
88	struct aplspmi_softc *sc = (struct aplspmi_softc *)self;
89	struct fdt_attach_args *faa = aux;
90	struct spmi_attach_args sa;
91	char name[32];
92	uint32_t reg[2];
93	int node;
94
95	if (faa->fa_nreg < 1) {
96		printf(": no registers\n");
97		return;
98	}
99
100	sc->sc_iot = faa->fa_iot;
101	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
102	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
103		printf(": can't map registers\n");
104		return;
105	}
106
107	printf("\n");
108
109	sc->sc_tag.sc_cookie = sc;
110	sc->sc_tag.sc_cmd_read = aplspmi_cmd_read;
111	sc->sc_tag.sc_cmd_write = aplspmi_cmd_write;
112
113	for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
114		if (OF_getpropintarray(node, "reg", reg,
115		    sizeof(reg)) != sizeof(reg))
116			continue;
117
118		memset(name, 0, sizeof(name));
119		if (OF_getprop(node, "compatible", name, sizeof(name)) == -1)
120			continue;
121		if (name[0] == '\0')
122			continue;
123
124		memset(&sa, 0, sizeof(sa));
125		sa.sa_tag = &sc->sc_tag;
126		sa.sa_sid = reg[0];
127		sa.sa_name = name;
128		sa.sa_node = node;
129		config_found(self, &sa, aplspmi_print);
130	}
131}
132
133int
134aplspmi_print(void *aux, const char *pnp)
135{
136	struct spmi_attach_args *sa = aux;
137
138	if (pnp != NULL)
139		printf("\"%s\" at %s", sa->sa_name, pnp);
140	printf(" sid 0x%x", sa->sa_sid);
141
142	return UNCONF;
143}
144
145int
146aplspmi_read_resp(struct aplspmi_softc *sc, uint32_t *resp)
147{
148	int retry;
149
150	for (retry = 1000; retry > 0; retry--) {
151		if ((HREAD4(sc, SPMI_STAT) & SPMI_STAT_RXEMPTY) == 0)
152			break;
153		delay(1);
154	}
155	if (retry == 0)
156		return ETIMEDOUT;
157
158	*resp = HREAD4(sc, SPMI_RESP);
159	return 0;
160}
161
162int
163aplspmi_cmd_read(void *cookie, uint8_t sid, uint8_t cmd, uint16_t addr,
164    void *buf, size_t len)
165{
166	struct aplspmi_softc *sc = cookie;
167	uint8_t *cbuf = buf;
168	uint32_t resp;
169	int error;
170
171	if (len == 0 || len > 8)
172		return EINVAL;
173
174	HWRITE4(sc, SPMI_CMD, SPMI_CMD_SID(sid) | cmd | SPMI_CMD_ADDR(addr) |
175	    (len - 1) | SPMI_CMD_LAST);
176
177	error = aplspmi_read_resp(sc, &resp);
178	if (error)
179		return error;
180
181	while (len > 0) {
182		error = aplspmi_read_resp(sc, &resp);
183		if (error)
184			return error;
185		memcpy(cbuf, &resp, MIN(len, 4));
186		cbuf += MIN(len, 4);
187		len -= MIN(len, 4);
188	}
189
190	return 0;
191}
192
193int
194aplspmi_cmd_write(void *cookie, uint8_t sid, uint8_t cmd, uint16_t addr,
195    const void *buf, size_t len)
196{
197	struct aplspmi_softc *sc = cookie;
198	const uint8_t *cbuf = buf;
199	uint32_t data, resp;
200
201	if (len == 0 || len > 8)
202		return EINVAL;
203
204	HWRITE4(sc, SPMI_CMD, SPMI_CMD_SID(sid) | cmd | SPMI_CMD_ADDR(addr) |
205	    (len - 1) | SPMI_CMD_LAST);
206
207	while (len > 0) {
208		memcpy(&data, cbuf, MIN(len, 4));
209		HWRITE4(sc, SPMI_CMD, data);
210		cbuf += MIN(len, 4);
211		len -= MIN(len, 4);
212	}
213
214	return aplspmi_read_resp(sc, &resp);
215}
216