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
53 struct 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
61 int aplspmi_match(struct device *, void *, void *);
62 void aplspmi_attach(struct device *, struct device *, void *);
63
64 const struct cfattach aplspmi_ca = {
65 sizeof (struct aplspmi_softc), aplspmi_match, aplspmi_attach
66 };
67
68 struct cfdriver aplspmi_cd = {
69 NULL, "aplspmi", DV_DULL
70 };
71
72 int aplspmi_print(void *, const char *);
73 int aplspmi_cmd_read(void *, uint8_t, uint8_t, uint16_t, void *, size_t);
74 int aplspmi_cmd_write(void *, uint8_t, uint8_t, uint16_t,
75 const void *, size_t);
76
77 int
aplspmi_match(struct device * parent,void * match,void * aux)78 aplspmi_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
85 void
aplspmi_attach(struct device * parent,struct device * self,void * aux)86 aplspmi_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
133 int
aplspmi_print(void * aux,const char * pnp)134 aplspmi_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
145 int
aplspmi_read_resp(struct aplspmi_softc * sc,uint32_t * resp)146 aplspmi_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
162 int
aplspmi_cmd_read(void * cookie,uint8_t sid,uint8_t cmd,uint16_t addr,void * buf,size_t len)163 aplspmi_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
193 int
aplspmi_cmd_write(void * cookie,uint8_t sid,uint8_t cmd,uint16_t addr,const void * buf,size_t len)194 aplspmi_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