si.c revision 1.2
1/* $NetBSD: si.c,v 1.2 2025/12/11 01:13:49 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2025 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: si.c,v 1.2 2025/12/11 01:13:49 jmcneill Exp $");
31
32#include <sys/param.h>
33#include <sys/bus.h>
34#include <sys/device.h>
35#include <sys/systm.h>
36#include <sys/bitops.h>
37#include <sys/mutex.h>
38#include <sys/tty.h>
39#include <uvm/uvm_extern.h>
40
41#include <machine/wii.h>
42#include <machine/pio.h>
43
44#include <dev/hid/hidev.h>
45
46#include "locators.h"
47#include "mainbus.h"
48#include "si.h"
49#include "gcpad_rdesc.h"
50
51#define SI_NUM_CHAN		4
52
53#define SICOUTBUF(n)		((n) * 0xc + 0x00)
54#define SICINBUFH(n)		((n) * 0xc + 0x04)
55#define SICINBUFL(n)		((n) * 0xc + 0x08)
56#define SIPOLL			0x30
57#define  SIPOLL_X		__BITS(25, 16)
58#define  SIPOLL_Y		__BITS(15, 8)
59#define  SIPOLL_EN(n)		(__BIT(7 - n))
60#define SICOMCSR		0x34
61#define  SICOMCSR_TCINT		__BIT(31)
62#define  SICOMCSR_TCINTMSK	__BIT(30)
63#define  SICOMCSR_RDSTINT	__BIT(28)
64#define  SICOMCSR_RDSTINTMSK	__BIT(27)
65#define  SICOMCSR_OUTLNGTH	__BITS(22, 16)
66#define  SICOMCSR_INLNGTH	__BITS(14, 8)
67#define  SICOMCSR_TSTART	__BIT(0)
68#define SISR			0x38
69#define  SISR_OFF(n)		((3 - (n)) * 8)
70#define  SISR_WR(n)		__BIT(SISR_OFF(n) + 7)
71#define  SISR_RDST(n)		__BIT(SISR_OFF(n) + 5)
72#define  SISR_WRST(n)		__BIT(SISR_OFF(n) + 4)
73#define  SISR_NOREP(n)		__BIT(SISR_OFF(n) + 3)
74#define  SISR_COLL(n)		__BIT(SISR_OFF(n) + 2)
75#define  SISR_OVRUN(n)		__BIT(SISR_OFF(n) + 1)
76#define  SISR_UNRUN(n)		__BIT(SISR_OFF(n) + 0)
77#define  SISR_ERROR_MASK(n)	(SISR_NOREP(n) | SISR_COLL(n) | \
78				 SISR_OVRUN(n) | SISR_UNRUN(n))
79#define  SISR_ERROR_ACK_ALL	(SISR_ERROR_MASK(0) | SISR_ERROR_MASK(1) | \
80				 SISR_ERROR_MASK(2) | SISR_ERROR_MASK(3))
81#define SIEXILK			0x3c
82#define SIIOBUF			0x80
83
84#define GCPAD_REPORT_SIZE	9
85#define GCPAD_START(_buf)	ISSET((_buf)[0], 0x10)
86#define GCPAD_Y(_buf)		ISSET((_buf)[0], 0x08)
87#define GCPAD_X(_buf)		ISSET((_buf)[0], 0x04)
88#define GCPAD_B(_buf)		ISSET((_buf)[0], 0x02)
89#define GCPAD_A(_buf)		ISSET((_buf)[0], 0x01)
90#define GCPAD_LCLICK(_buf)	ISSET((_buf)[1], 0x40)
91#define GCPAD_RCLICK(_buf)	ISSET((_buf)[1], 0x20)
92#define GCPAD_Z(_buf)		ISSET((_buf)[1], 0x10)
93#define GCPAD_UP(_buf)		ISSET((_buf)[1], 0x08)
94#define GCPAD_DOWN(_buf)	ISSET((_buf)[1], 0x04)
95#define GCPAD_RIGHT(_buf)	ISSET((_buf)[1], 0x02)
96#define GCPAD_LEFT(_buf)	ISSET((_buf)[1], 0x01)
97
98struct si_softc;
99
100struct si_channel {
101	struct si_softc		*ch_sc;
102	device_t		ch_dev;
103	unsigned		ch_index;
104	struct hidev_tag	ch_hidev;
105	kmutex_t		ch_lock;
106	kcondvar_t		ch_cv;
107	uint8_t			ch_state;
108#define SI_STATE_OPEN		__BIT(0)
109#define SI_STATE_STOPPED	__BIT(1)
110	void			(*ch_intr)(void *, void *, u_int);
111	void			*ch_intrarg;
112	uint8_t			ch_buf[GCPAD_REPORT_SIZE];
113	void			*ch_desc;
114	int			ch_descsize;
115	void			*ch_si;
116};
117
118struct si_softc {
119	device_t		sc_dev;
120	bus_space_tag_t		sc_bst;
121	bus_space_handle_t	sc_bsh;
122
123	struct si_channel	sc_chan[SI_NUM_CHAN];
124};
125
126#define RD4(sc, reg)							\
127	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
128#define WR4(sc, reg, val)						\
129	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
130
131static int	si_match(device_t, cfdata_t, void *);
132static void	si_attach(device_t, device_t, void *);
133
134static int	si_intr(void *);
135static void	si_softintr(void *);
136
137static int	si_rescan(device_t, const char *, const int *);
138static int	si_print(void *, const char *);
139
140static void	si_get_report_desc(void *, void **, int *);
141static int	si_open(void *, void (*)(void *, void *, unsigned), void *);
142static void	si_stop(void *);
143static void	si_close(void *);
144static usbd_status si_set_report(void *, int, void *, int);
145static usbd_status si_get_report(void *, int, void *, int);
146static usbd_status si_write(void *, void *, int);
147
148CFATTACH_DECL_NEW(si, sizeof(struct si_softc),
149	si_match, si_attach, NULL, NULL);
150
151static int
152si_match(device_t parent, cfdata_t cf, void *aux)
153{
154	struct mainbus_attach_args *maa = aux;
155
156	return strcmp(maa->maa_name, "si") == 0;
157}
158
159static void
160si_attach(device_t parent, device_t self, void *aux)
161{
162	struct mainbus_attach_args * const maa = aux;
163	struct si_softc * const sc = device_private(self);
164	unsigned chan;
165	void *ih;
166
167	KASSERT(device_unit(self) == 0);
168
169	aprint_naive("\n");
170	aprint_normal(": Serial Interface\n");
171
172	sc->sc_dev = self;
173	sc->sc_bst = maa->maa_bst;
174	if (bus_space_map(sc->sc_bst, maa->maa_addr, SI_SIZE, 0,
175	    &sc->sc_bsh) != 0) {
176		aprint_error_dev(self, "couldn't map registers\n");
177		return;
178	}
179
180	for (chan = 0; chan < SI_NUM_CHAN; chan++) {
181		struct si_channel *ch;
182		struct hidev_tag *t;
183
184		ch = &sc->sc_chan[chan];
185		ch->ch_sc = sc;
186		ch->ch_index = chan;
187		mutex_init(&ch->ch_lock, MUTEX_DEFAULT, IPL_VM);
188		cv_init(&ch->ch_cv, "sich");
189		ch->ch_si = softint_establish(SOFTINT_SERIAL,
190		    si_softintr, ch);
191		KASSERT(ch->ch_si != NULL);
192
193		t = &ch->ch_hidev;
194		t->_cookie = &sc->sc_chan[chan];
195		t->_get_report_desc = si_get_report_desc;
196		t->_open = si_open;
197		t->_stop = si_stop;
198		t->_close = si_close;
199		t->_set_report = si_set_report;
200		t->_get_report = si_get_report;
201		t->_write = si_write;
202	}
203
204	WR4(sc, SIPOLL,
205	    __SHIFTIN(7, SIPOLL_X) |
206	    __SHIFTIN(1, SIPOLL_Y));
207	WR4(sc, SICOMCSR, SICOMCSR_RDSTINT | SICOMCSR_RDSTINTMSK);
208
209	ih = intr_establish_xname(maa->maa_irq, IST_LEVEL, IPL_VM, si_intr, sc,
210	    device_xname(self));
211	KASSERT(ih != NULL);
212
213	si_rescan(self, NULL, NULL);
214}
215
216static int
217si_rescan(device_t self, const char *ifattr, const int *locs)
218{
219	struct si_softc * const sc = device_private(self);
220	struct si_attach_args saa;
221	unsigned chan;
222
223	for (chan = 0; chan < SI_NUM_CHAN; chan++) {
224		struct si_channel *ch = &sc->sc_chan[chan];
225
226		if (ch->ch_dev == NULL) {
227			saa.saa_hidev = &ch->ch_hidev;
228			saa.saa_index = ch->ch_index;
229
230			ch->ch_dev = config_found(self, &saa, si_print,
231			    CFARGS(.submatch = config_stdsubmatch,
232				   .locators = locs));
233		}
234	}
235
236	return 0;
237}
238
239static int
240si_print(void *aux, const char *pnp)
241{
242	struct si_attach_args *saa = aux;
243
244	if (pnp != NULL) {
245		aprint_normal("uhid at %s", pnp);
246	}
247
248	/*
249	 * The Wii Operations Manual for RVL-001 refers to the controller
250	 * ports as "Nintendo GameCube Controller Sockets".
251	 */
252	aprint_normal(" socket %d", saa->saa_index + 1);
253
254	return UNCONF;
255}
256
257static void
258si_make_report(struct si_softc *sc, unsigned chan, void *report, bool with_rid)
259{
260	uint32_t inbuf[2];
261	uint8_t *iptr = (uint8_t *)inbuf;
262	uint8_t *optr = report;
263	unsigned off = 0;
264
265	inbuf[0] = RD4(sc, SICINBUFH(chan));
266	inbuf[1] = RD4(sc, SICINBUFL(chan));
267
268	if (with_rid) {
269		optr[off++] = chan + 1;
270	}
271
272	optr[off] = 0;
273	optr[off] |= GCPAD_X(iptr)	? 0x01 : 0;
274	optr[off] |= GCPAD_A(iptr)	? 0x02 : 0;
275	optr[off] |= GCPAD_B(iptr)	? 0x04 : 0;
276	optr[off] |= GCPAD_Y(iptr)	? 0x08 : 0;
277	optr[off] |= GCPAD_LCLICK(iptr)	? 0x10 : 0;
278	optr[off] |= GCPAD_RCLICK(iptr)	? 0x20 : 0;
279	optr[off] |= GCPAD_Z(iptr)	? 0x80 : 0;
280	off++;
281
282	optr[off] = 0;
283	optr[off] |= GCPAD_START(iptr)	? 0x02 : 0;
284	optr[off] |= GCPAD_UP(iptr)	? 0x10 : 0;
285	optr[off] |= GCPAD_RIGHT(iptr)	? 0x20 : 0;
286	optr[off] |= GCPAD_DOWN(iptr)	? 0x40 : 0;
287	optr[off] |= GCPAD_LEFT(iptr)	? 0x80 : 0;
288	off++;
289
290	memcpy(&optr[off], &iptr[2], 6);
291	off += 6;
292
293	optr[off++] = 0;
294}
295
296static void
297si_softintr(void *priv)
298{
299	struct si_channel *ch = priv;
300
301	if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
302		ch->ch_intr(ch->ch_intrarg, ch->ch_buf, sizeof(ch->ch_buf));
303	}
304}
305
306static int
307si_intr(void *priv)
308{
309	struct si_softc *sc = priv;
310	unsigned chan;
311	uint32_t comcsr, sr;
312	int ret = 0;
313
314	comcsr = RD4(sc, SICOMCSR);
315	sr = RD4(sc, SISR);
316
317	if (ISSET(comcsr, SICOMCSR_TCINT)) {
318		WR4(sc, SICOMCSR, comcsr | SICOMCSR_TCINT);
319	}
320
321	if (ISSET(comcsr, SICOMCSR_RDSTINT)) {
322		for (chan = 0; chan < SI_NUM_CHAN; chan++) {
323			struct si_channel *ch = &sc->sc_chan[chan];
324
325			if (ISSET(sr, SISR_RDST(chan))) {
326				/* Reading INBUF[HL] de-asserts RDSTINT. */
327				si_make_report(sc, chan, ch->ch_buf, false);
328
329				if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
330					softint_schedule(ch->ch_si);
331				}
332			}
333
334			ret = 1;
335		}
336	}
337
338	WR4(sc, SISR, sr & SISR_ERROR_ACK_ALL);
339
340	return ret;
341}
342
343static void
344si_get_report_desc(void *cookie, void **desc, int *size)
345{
346	*desc = gcpad_report_descr;
347	*size = sizeof(gcpad_report_descr);
348}
349
350static int
351si_open(void *cookie, void (*intr)(void *, void *, u_int), void *arg)
352{
353	struct si_channel *ch = cookie;
354	struct si_softc *sc = ch->ch_sc;
355	int error;
356
357	mutex_enter(&ch->ch_lock);
358
359	if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
360		error = EBUSY;
361		goto unlock;
362	}
363
364	ch->ch_intr = intr;
365	ch->ch_intrarg = arg;
366	ch->ch_state |= SI_STATE_OPEN;
367
368	(void)RD4(sc, SICINBUFH(ch->ch_index));
369	(void)RD4(sc, SICINBUFL(ch->ch_index));
370
371	/* Init controller */
372	WR4(sc, SICOUTBUF(ch->ch_index), 0x00400300);
373
374	/* Enable polling */
375	WR4(sc, SIPOLL, RD4(sc, SIPOLL) | SIPOLL_EN(ch->ch_index));
376
377	WR4(sc, SISR, SISR_WR(ch->ch_index));
378	WR4(sc, SICOMCSR, RD4(sc, SICOMCSR) | SICOMCSR_TSTART);
379
380	error = 0;
381
382unlock:
383	mutex_exit(&ch->ch_lock);
384
385	return error;
386}
387
388static void
389si_stop(void *cookie)
390{
391	struct si_channel *ch = cookie;
392
393	mutex_enter(&ch->ch_lock);
394
395	ch->ch_state |= SI_STATE_STOPPED;
396
397	cv_broadcast(&ch->ch_cv);
398	mutex_exit(&ch->ch_lock);
399}
400
401static void
402si_close(void *cookie)
403{
404	struct si_channel *ch = cookie;
405	struct si_softc *sc = ch->ch_sc;
406
407	mutex_enter(&ch->ch_lock);
408
409	/* Diable polling */
410	WR4(sc, SIPOLL, RD4(sc, SIPOLL) & ~SIPOLL_EN(ch->ch_index));
411
412	ch->ch_state &= ~(SI_STATE_OPEN | SI_STATE_STOPPED);
413	ch->ch_intr = NULL;
414	ch->ch_intrarg = NULL;
415
416	cv_broadcast(&ch->ch_cv);
417	mutex_exit(&ch->ch_lock);
418}
419
420static usbd_status
421si_set_report(void *cookie, int type, void *data, int len)
422{
423        return USBD_INVAL;
424}
425
426static usbd_status
427si_get_report(void *cookie, int type, void *data, int len)
428{
429	struct si_channel *ch = cookie;
430	struct si_softc *sc = ch->ch_sc;
431	uint32_t *inbuf = data;
432
433	if (len != GCPAD_REPORT_SIZE + 1) {
434		return USBD_IOERROR;
435	}
436
437	mutex_enter(&ch->ch_lock);
438	si_make_report(sc, ch->ch_index, inbuf, true);
439	mutex_exit(&ch->ch_lock);
440
441	return USBD_NORMAL_COMPLETION;
442}
443
444static usbd_status
445si_write(void *cookie, void *data, int len)
446{
447        return USBD_INVAL;
448}
449