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