jcs's openbsd hax
openbsd
1/* $OpenBSD: tipd.c,v 1.4 2025/07/10 22:46:17 kettenis Exp $ */
2/*
3 * Copyright (c) 2022 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/intr.h>
24#include <machine/fdt.h>
25
26#include <dev/ofw/openfirm.h>
27#include <dev/ofw/ofw_misc.h>
28#include <dev/ofw/fdt.h>
29
30#include <dev/i2c/i2cvar.h>
31
32#define TPS_CMD1 0x08
33#define TPS_DATA1 0x09
34#define TPS_INT_EVENT_1 0x14
35#define TPS_INT_EVENT_2 0x15
36#define TPS_INT_MASK_1 0x16
37#define TPS_INT_MASK_2 0x17
38#define TPS_INT_CLEAR_1 0x18
39#define TPS_INT_CLEAR_2 0x19
40#define TPS_STATUS 0x1a
41#define TPS_STATUS_PLUG_PRESENT (1 << 0)
42#define TPS_SYSTEM_POWER_STATE 0x20
43#define TPS_SYSTEM_POWER_STATE_S0 0
44#define TPS_SYSTEM_POWER_STATE_S5 5
45#define TPS_POWER_STATUS 0x3f
46
47#define TPS_CMD(s) ((s[3] << 24) | (s[2] << 16) | (s[1] << 8) | s[0])
48
49/*
50 * Interrupt bits on the CD321x controllers used by Apple differ from
51 * those used by the standard TPS6598x controllers.
52 */
53#define CD_INT_PLUG_EVENT (1 << 1)
54
55struct tipd_softc {
56 struct device sc_dev;
57 i2c_tag_t sc_tag;
58 i2c_addr_t sc_addr;
59
60 void *sc_ih;
61
62 struct device_ports sc_ports;
63 uint32_t sc_status;
64};
65
66int tipd_match(struct device *, void *, void *);
67void tipd_attach(struct device *, struct device *, void *);
68int tipd_activate(struct device *, int);
69
70const struct cfattach tipd_ca = {
71 sizeof(struct tipd_softc), tipd_match, tipd_attach, NULL,
72 tipd_activate
73};
74
75struct cfdriver tipd_cd = {
76 NULL, "tipd", DV_DULL
77};
78
79int tipd_intr(void *);
80
81int tipd_read_4(struct tipd_softc *, uint8_t, uint32_t *);
82int tipd_read_8(struct tipd_softc *, uint8_t, uint64_t *);
83int tipd_write_4(struct tipd_softc *, uint8_t, uint32_t);
84int tipd_write_8(struct tipd_softc *, uint8_t, uint64_t);
85int tipd_exec(struct tipd_softc *, const char *,
86 const void *, size_t, void *, size_t);
87
88int
89tipd_match(struct device *parent, void *match, void *aux)
90{
91 struct i2c_attach_args *ia = aux;
92
93 return iic_is_compatible(ia, "apple,cd321x");
94}
95
96void
97tipd_attach(struct device *parent, struct device *self, void *aux)
98{
99 struct tipd_softc *sc = (struct tipd_softc *)self;
100 struct i2c_attach_args *ia = aux;
101 int node = *(int *)ia->ia_cookie;
102
103 sc->sc_tag = ia->ia_tag;
104 sc->sc_addr = ia->ia_addr;
105
106 sc->sc_ih = fdt_intr_establish(node, IPL_BIO, tipd_intr,
107 sc, sc->sc_dev.dv_xname);
108 if (sc->sc_ih == NULL) {
109 printf(": can't establish interrupt\n");
110 return;
111 }
112
113 printf("\n");
114
115 tipd_read_4(sc, TPS_STATUS, &sc->sc_status);
116 tipd_write_8(sc, TPS_INT_MASK_1, CD_INT_PLUG_EVENT);
117
118 node = OF_getnodebyname(node, "connector");
119 if (node) {
120 sc->sc_ports.dp_node = node;
121 device_ports_register(&sc->sc_ports, -1);
122 }
123}
124
125int
126tipd_activate(struct device *self, int act)
127{
128 struct tipd_softc *sc = (struct tipd_softc *)self;
129 uint8_t state;
130 int error;
131
132 switch (act) {
133 case DVACT_QUIESCE:
134 tipd_write_8(sc, TPS_INT_MASK_1, 0);
135 break;
136 case DVACT_SUSPEND:
137 state = TPS_SYSTEM_POWER_STATE_S5;
138 error = tipd_exec(sc, "SSPS", &state, sizeof(state), NULL, 0);
139 if (error)
140 printf("%s: powerdown failed\n", sc->sc_dev.dv_xname);
141 break;
142 case DVACT_RESUME:
143 state = TPS_SYSTEM_POWER_STATE_S0;
144 error = tipd_exec(sc, "SSPS", &state, sizeof(state), NULL, 0);
145 if (error)
146 printf("%s: powerup failed\n", sc->sc_dev.dv_xname);
147 break;
148 case DVACT_WAKEUP:
149 tipd_read_4(sc, TPS_STATUS, &sc->sc_status);
150 tipd_write_8(sc, TPS_INT_MASK_1, CD_INT_PLUG_EVENT);
151 break;
152 }
153
154 return 0;
155}
156
157void
158tipd_connect(struct tipd_softc *sc)
159{
160 struct endpoint *ep, *rep;
161 struct usb_controller_port *port;
162
163 ep = endpoint_byreg(&sc->sc_ports, 0, -1);
164 if (ep == NULL)
165 return;
166 rep = endpoint_remote(ep);
167 if (rep == NULL || rep->ep_type != EP_USB_CONTROLLER_PORT)
168 return;
169 port = endpoint_get_cookie(rep);
170 if (port && port->up_connect)
171 port->up_connect(port->up_cookie);
172}
173
174void
175tipd_disconnect(struct tipd_softc *sc)
176{
177 struct endpoint *ep, *rep;
178 struct usb_controller_port *port;
179
180 ep = endpoint_byreg(&sc->sc_ports, 0, -1);
181 if (ep == NULL)
182 return;
183 rep = endpoint_remote(ep);
184 if (rep == NULL || rep->ep_type != EP_USB_CONTROLLER_PORT)
185 return;
186 port = endpoint_get_cookie(rep);
187 if (port && port->up_disconnect)
188 port->up_disconnect(port->up_cookie);
189}
190
191int
192tipd_intr(void *arg)
193{
194 struct tipd_softc *sc = arg;
195 uint64_t event;
196 uint32_t status;
197 int error;
198
199 error = tipd_read_8(sc, TPS_INT_EVENT_1, &event);
200 if (error)
201 return 0;
202
203 if (event == 0)
204 return 0;
205
206 if (event & CD_INT_PLUG_EVENT) {
207 error = tipd_read_4(sc, TPS_STATUS, &status);
208 if (error)
209 goto fail;
210
211 /*
212 * We may get a spurious plug event upon resume. Make
213 * sure we only signal a new connection when the plug
214 * present state really changed.
215 */
216 if ((status ^ sc->sc_status) & TPS_STATUS_PLUG_PRESENT) {
217 if (status & TPS_STATUS_PLUG_PRESENT)
218 tipd_connect(sc);
219 else
220 tipd_disconnect(sc);
221 sc->sc_status = status;
222 }
223 }
224
225fail:
226 tipd_write_8(sc, TPS_INT_CLEAR_1, event);
227 return 1;
228}
229
230int
231tipd_read_4(struct tipd_softc *sc, uint8_t reg, uint32_t *val)
232{
233 uint8_t buf[5];
234 int error;
235
236 iic_acquire_bus(sc->sc_tag, 0);
237 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
238 sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0);
239 iic_release_bus(sc->sc_tag, 0);
240
241 if (error == 0)
242 *val = lemtoh32(&buf[1]);
243
244 return error;
245}
246
247int
248tipd_read_8(struct tipd_softc *sc, uint8_t reg, uint64_t *val)
249{
250 uint8_t buf[9];
251 int error;
252
253 iic_acquire_bus(sc->sc_tag, 0);
254 error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
255 sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0);
256 iic_release_bus(sc->sc_tag, 0);
257
258 if (error == 0)
259 *val = lemtoh64(&buf[1]);
260
261 return error;
262}
263
264int
265tipd_write_4(struct tipd_softc *sc, uint8_t reg, uint32_t val)
266{
267 uint8_t buf[5];
268 int error;
269
270 buf[0] = 4;
271 htolem32(&buf[1], val);
272
273 iic_acquire_bus(sc->sc_tag, 0);
274 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
275 sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0);
276 iic_release_bus(sc->sc_tag, 0);
277
278 return error;
279}
280
281int
282tipd_write_8(struct tipd_softc *sc, uint8_t reg, uint64_t val)
283{
284 uint8_t buf[9];
285 int error;
286
287 buf[0] = 8;
288 htolem64(&buf[1], val);
289
290 iic_acquire_bus(sc->sc_tag, 0);
291 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
292 sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0);
293 iic_release_bus(sc->sc_tag, 0);
294
295 return error;
296}
297
298int
299tipd_exec(struct tipd_softc *sc, const char *cmd, const void *wbuf,
300 size_t wlen, void *rbuf, size_t rlen)
301{
302 char buf[65];
303 uint32_t val;
304 int timo, error;
305 uint8_t reg = TPS_DATA1;
306 int s;
307
308 if (wlen >= sizeof(buf) - 1)
309 return EINVAL;
310
311 s = splbio();
312
313 error = tipd_read_4(sc, TPS_CMD1, &val);
314 if (error == 0 && val == TPS_CMD("!CMD"))
315 error = EBUSY;
316 if (error)
317 goto fail;
318
319 if (wlen > 0) {
320 buf[0] = wlen;
321 memcpy(&buf[1], wbuf, wlen);
322 iic_acquire_bus(sc->sc_tag, 0);
323 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
324 sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0);
325 iic_release_bus(sc->sc_tag, 0);
326 if (error)
327 goto fail;
328 }
329
330 error = tipd_write_4(sc, TPS_CMD1, TPS_CMD(cmd));
331 if (error)
332 goto fail;
333
334 for (timo = 1000; timo > 0; timo--) {
335 error = tipd_read_4(sc, TPS_CMD1, &val);
336 if (error == 0 && val == TPS_CMD("!CMD"))
337 error = EBUSY;
338 if (error)
339 goto fail;
340 if (val == 0)
341 break;
342 delay(10);
343 }
344
345 if (timo == 0) {
346 error = ETIMEDOUT;
347 goto fail;
348 }
349
350 if (rlen > 0) {
351 memset(buf, 0, sizeof(buf));
352 iic_acquire_bus(sc->sc_tag, 0);
353 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
354 sc->sc_addr, ®, sizeof(reg), buf, sizeof(buf), 0);
355 iic_release_bus(sc->sc_tag, 0);
356 if (error == 0 && buf[0] < rlen)
357 error = EIO;
358 if (error)
359 goto fail;
360 memcpy(rbuf, &buf[1], rlen);
361 }
362
363fail:
364 splx(s);
365 return error;
366}