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