Home | History | Annotate | Line # | Download | only in dev
      1 /* $NetBSD: si.c,v 1.2 2026/06/11 19:46:18 andvar Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2025 Jared McNeill <jmcneill (at) 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 2026/06/11 19:46:18 andvar 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 
     98 struct si_softc;
     99 
    100 struct 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 
    118 struct 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 
    131 static int	si_match(device_t, cfdata_t, void *);
    132 static void	si_attach(device_t, device_t, void *);
    133 
    134 static int	si_intr(void *);
    135 static void	si_softintr(void *);
    136 
    137 static int	si_rescan(device_t, const char *, const int *);
    138 static int	si_print(void *, const char *);
    139 
    140 static void	si_get_report_desc(void *, void **, int *);
    141 static int	si_open(void *, void (*)(void *, void *, unsigned), void *);
    142 static void	si_stop(void *);
    143 static void	si_close(void *);
    144 static usbd_status si_set_report(void *, int, void *, int);
    145 static usbd_status si_get_report(void *, int, void *, int);
    146 static usbd_status si_write(void *, void *, int);
    147 
    148 CFATTACH_DECL_NEW(si, sizeof(struct si_softc),
    149 	si_match, si_attach, NULL, NULL);
    150 
    151 static int
    152 si_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 
    159 static void
    160 si_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 
    216 static int
    217 si_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 
    239 static int
    240 si_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 
    257 static void
    258 si_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 
    296 static void
    297 si_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 
    306 static int
    307 si_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 
    343 static void
    344 si_get_report_desc(void *cookie, void **desc, int *size)
    345 {
    346 	*desc = gcpad_report_descr;
    347 	*size = sizeof(gcpad_report_descr);
    348 }
    349 
    350 static int
    351 si_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 
    382 unlock:
    383 	mutex_exit(&ch->ch_lock);
    384 
    385 	return error;
    386 }
    387 
    388 static void
    389 si_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 
    401 static void
    402 si_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 	/* Disable 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 
    420 static usbd_status
    421 si_set_report(void *cookie, int type, void *data, int len)
    422 {
    423         return USBD_INVAL;
    424 }
    425 
    426 static usbd_status
    427 si_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 
    444 static usbd_status
    445 si_write(void *cookie, void *data, int len)
    446 {
    447         return USBD_INVAL;
    448 }
    449