si.c revision 1.1
1/* $NetBSD: si.c,v 1.1 2025/12/08 23:00:22 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.1 2025/12/08 23:00:22 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};
116
117struct si_softc {
118	device_t		sc_dev;
119	bus_space_tag_t		sc_bst;
120	bus_space_handle_t	sc_bsh;
121
122	struct si_channel	sc_chan[SI_NUM_CHAN];
123};
124
125#define RD4(sc, reg)							\
126	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
127#define WR4(sc, reg, val)						\
128	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
129
130static int	si_match(device_t, cfdata_t, void *);
131static void	si_attach(device_t, device_t, void *);
132
133static int	si_intr(void *);
134
135static int	si_rescan(device_t, const char *, const int *);
136static int	si_print(void *, const char *);
137
138static void	si_get_report_desc(void *, void **, int *);
139static int	si_open(void *, void (*)(void *, void *, unsigned), void *);
140static void	si_stop(void *);
141static void	si_close(void *);
142static usbd_status si_set_report(void *, int, void *, int);
143static usbd_status si_get_report(void *, int, void *, int);
144static usbd_status si_write(void *, void *, int);
145
146CFATTACH_DECL_NEW(si, sizeof(struct si_softc),
147	si_match, si_attach, NULL, NULL);
148
149static int
150si_match(device_t parent, cfdata_t cf, void *aux)
151{
152	struct mainbus_attach_args *maa = aux;
153
154	return strcmp(maa->maa_name, "si") == 0;
155}
156
157static void
158si_attach(device_t parent, device_t self, void *aux)
159{
160	struct mainbus_attach_args * const maa = aux;
161	struct si_softc * const sc = device_private(self);
162	unsigned chan;
163	void *ih;
164
165	KASSERT(device_unit(self) == 0);
166
167	aprint_naive("\n");
168	aprint_normal(": Serial Interface\n");
169
170	sc->sc_dev = self;
171	sc->sc_bst = maa->maa_bst;
172	if (bus_space_map(sc->sc_bst, maa->maa_addr, SI_SIZE, 0,
173	    &sc->sc_bsh) != 0) {
174		aprint_error_dev(self, "couldn't map registers\n");
175		return;
176	}
177
178	for (chan = 0; chan < SI_NUM_CHAN; chan++) {
179		struct si_channel *ch;
180		struct hidev_tag *t;
181
182		ch = &sc->sc_chan[chan];
183		ch->ch_sc = sc;
184		ch->ch_index = chan;
185		mutex_init(&ch->ch_lock, MUTEX_DEFAULT, IPL_VM);
186		cv_init(&ch->ch_cv, "sich");
187
188		t = &ch->ch_hidev;
189		t->_cookie = &sc->sc_chan[chan];
190		t->_get_report_desc = si_get_report_desc;
191		t->_open = si_open;
192		t->_stop = si_stop;
193		t->_close = si_close;
194		t->_set_report = si_set_report;
195		t->_get_report = si_get_report;
196		t->_write = si_write;
197	}
198
199	WR4(sc, SIPOLL,
200	    __SHIFTIN(7, SIPOLL_X) |
201	    __SHIFTIN(1, SIPOLL_Y));
202	WR4(sc, SICOMCSR, SICOMCSR_RDSTINT | SICOMCSR_RDSTINTMSK);
203
204	ih = intr_establish_xname(maa->maa_irq, IST_LEVEL, IPL_VM, si_intr, sc,
205	    device_xname(self));
206	KASSERT(ih != NULL);
207
208	si_rescan(self, NULL, NULL);
209}
210
211static int
212si_rescan(device_t self, const char *ifattr, const int *locs)
213{
214	struct si_softc * const sc = device_private(self);
215	struct si_attach_args saa;
216	unsigned chan;
217
218	for (chan = 0; chan < SI_NUM_CHAN; chan++) {
219		struct si_channel *ch = &sc->sc_chan[chan];
220
221		if (ch->ch_dev == NULL) {
222			saa.saa_hidev = &ch->ch_hidev;
223			saa.saa_index = ch->ch_index;
224
225			ch->ch_dev = config_found(self, &saa, si_print,
226			    CFARGS(.submatch = config_stdsubmatch,
227				   .locators = locs));
228		}
229	}
230
231	return 0;
232}
233
234static int
235si_print(void *aux, const char *pnp)
236{
237	struct si_attach_args *saa = aux;
238
239	if (pnp != NULL) {
240		aprint_normal("uhid at %s", pnp);
241	}
242
243	/*
244	 * The Wii Operations Manual for RVL-001 refers to the controller
245	 * ports as "Nintendo GameCube Controller Sockets".
246	 */
247	aprint_normal(" socket %d", saa->saa_index + 1);
248
249	return UNCONF;
250}
251
252static void
253si_make_report(struct si_softc *sc, unsigned chan, void *report, bool with_rid)
254{
255	uint32_t inbuf[2];
256	uint8_t *iptr = (uint8_t *)inbuf;
257	uint8_t *optr = report;
258	unsigned off = 0;
259
260	inbuf[0] = RD4(sc, SICINBUFH(chan));
261	inbuf[1] = RD4(sc, SICINBUFL(chan));
262
263	if (with_rid) {
264		optr[off++] = chan + 1;
265	}
266
267	optr[off] = 0;
268	optr[off] |= GCPAD_X(iptr)	? 0x01 : 0;
269	optr[off] |= GCPAD_A(iptr)	? 0x02 : 0;
270	optr[off] |= GCPAD_B(iptr)	? 0x04 : 0;
271	optr[off] |= GCPAD_Y(iptr)	? 0x08 : 0;
272	optr[off] |= GCPAD_LCLICK(iptr)	? 0x10 : 0;
273	optr[off] |= GCPAD_RCLICK(iptr)	? 0x20 : 0;
274	optr[off] |= GCPAD_Z(iptr)	? 0x80 : 0;
275	off++;
276
277	optr[off] = 0;
278	optr[off] |= GCPAD_START(iptr)	? 0x02 : 0;
279	optr[off] |= GCPAD_UP(iptr)	? 0x10 : 0;
280	optr[off] |= GCPAD_RIGHT(iptr)	? 0x20 : 0;
281	optr[off] |= GCPAD_DOWN(iptr)	? 0x40 : 0;
282	optr[off] |= GCPAD_LEFT(iptr)	? 0x80 : 0;
283	off++;
284
285	memcpy(&optr[off], &iptr[2], 6);
286	off += 6;
287
288	optr[off++] = 0;
289}
290
291static int
292si_intr(void *priv)
293{
294	struct si_softc *sc = priv;
295	unsigned chan;
296	uint32_t comcsr, sr;
297	int ret = 0;
298
299	comcsr = RD4(sc, SICOMCSR);
300	sr = RD4(sc, SISR);
301
302	if (ISSET(comcsr, SICOMCSR_TCINT)) {
303		WR4(sc, SICOMCSR, comcsr | SICOMCSR_TCINT);
304	}
305
306	if (ISSET(comcsr, SICOMCSR_RDSTINT)) {
307		for (chan = 0; chan < SI_NUM_CHAN; chan++) {
308			struct si_channel *ch = &sc->sc_chan[chan];
309
310			if (ISSET(sr, SISR_RDST(chan))) {
311				/* Reading INBUF[HL] de-asserts RDSTINT. */
312				si_make_report(sc, chan, ch->ch_buf, false);
313
314				if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
315					ch->ch_intr(ch->ch_intrarg, ch->ch_buf,
316					    sizeof(ch->ch_buf));
317				}
318			}
319
320			ret = 1;
321		}
322	}
323
324	WR4(sc, SISR, sr & SISR_ERROR_ACK_ALL);
325
326	return ret;
327}
328
329static void
330si_get_report_desc(void *cookie, void **desc, int *size)
331{
332	*desc = gcpad_report_descr;
333	*size = sizeof(gcpad_report_descr);
334}
335
336static int
337si_open(void *cookie, void (*intr)(void *, void *, u_int), void *arg)
338{
339	struct si_channel *ch = cookie;
340	struct si_softc *sc = ch->ch_sc;
341	int error;
342
343	mutex_enter(&ch->ch_lock);
344
345	if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
346		error = EBUSY;
347		goto unlock;
348	}
349
350	ch->ch_intr = intr;
351	ch->ch_intrarg = arg;
352	ch->ch_state |= SI_STATE_OPEN;
353
354	(void)RD4(sc, SICINBUFH(ch->ch_index));
355	(void)RD4(sc, SICINBUFL(ch->ch_index));
356
357	/* Init controller */
358	WR4(sc, SICOUTBUF(ch->ch_index), 0x00400300);
359
360	/* Enable polling */
361	WR4(sc, SIPOLL, RD4(sc, SIPOLL) | SIPOLL_EN(ch->ch_index));
362
363	WR4(sc, SISR, SISR_WR(ch->ch_index));
364	WR4(sc, SICOMCSR, RD4(sc, SICOMCSR) | SICOMCSR_TSTART);
365
366	error = 0;
367
368unlock:
369	mutex_exit(&ch->ch_lock);
370
371	return error;
372}
373
374static void
375si_stop(void *cookie)
376{
377	struct si_channel *ch = cookie;
378
379	mutex_enter(&ch->ch_lock);
380
381	ch->ch_state |= SI_STATE_STOPPED;
382
383	cv_broadcast(&ch->ch_cv);
384	mutex_exit(&ch->ch_lock);
385}
386
387static void
388si_close(void *cookie)
389{
390	struct si_channel *ch = cookie;
391	struct si_softc *sc = ch->ch_sc;
392
393	mutex_enter(&ch->ch_lock);
394
395	/* Diable polling */
396	WR4(sc, SIPOLL, RD4(sc, SIPOLL) & ~SIPOLL_EN(ch->ch_index));
397
398	ch->ch_state &= ~(SI_STATE_OPEN | SI_STATE_STOPPED);
399	ch->ch_intr = NULL;
400	ch->ch_intrarg = NULL;
401
402	cv_broadcast(&ch->ch_cv);
403	mutex_exit(&ch->ch_lock);
404}
405
406static usbd_status
407si_set_report(void *cookie, int type, void *data, int len)
408{
409        return USBD_INVAL;
410}
411
412static usbd_status
413si_get_report(void *cookie, int type, void *data, int len)
414{
415	struct si_channel *ch = cookie;
416	struct si_softc *sc = ch->ch_sc;
417	uint32_t *inbuf = data;
418
419	if (len != GCPAD_REPORT_SIZE + 1) {
420		return USBD_IOERROR;
421	}
422
423	mutex_enter(&ch->ch_lock);
424	si_make_report(sc, ch->ch_index, inbuf, true);
425	mutex_exit(&ch->ch_lock);
426
427	return USBD_NORMAL_COMPLETION;
428}
429
430static usbd_status
431si_write(void *cookie, void *data, int len)
432{
433        return USBD_INVAL;
434}
435