/* $NetBSD: si.c,v 1.1 2025/12/08 23:00:22 jmcneill Exp $ */ /*- * Copyright (c) 2025 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: si.c,v 1.1 2025/12/08 23:00:22 jmcneill Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include "locators.h" #include "mainbus.h" #include "si.h" #include "gcpad_rdesc.h" #define SI_NUM_CHAN 4 #define SICOUTBUF(n) ((n) * 0xc + 0x00) #define SICINBUFH(n) ((n) * 0xc + 0x04) #define SICINBUFL(n) ((n) * 0xc + 0x08) #define SIPOLL 0x30 #define SIPOLL_X __BITS(25, 16) #define SIPOLL_Y __BITS(15, 8) #define SIPOLL_EN(n) (__BIT(7 - n)) #define SICOMCSR 0x34 #define SICOMCSR_TCINT __BIT(31) #define SICOMCSR_TCINTMSK __BIT(30) #define SICOMCSR_RDSTINT __BIT(28) #define SICOMCSR_RDSTINTMSK __BIT(27) #define SICOMCSR_OUTLNGTH __BITS(22, 16) #define SICOMCSR_INLNGTH __BITS(14, 8) #define SICOMCSR_TSTART __BIT(0) #define SISR 0x38 #define SISR_OFF(n) ((3 - (n)) * 8) #define SISR_WR(n) __BIT(SISR_OFF(n) + 7) #define SISR_RDST(n) __BIT(SISR_OFF(n) + 5) #define SISR_WRST(n) __BIT(SISR_OFF(n) + 4) #define SISR_NOREP(n) __BIT(SISR_OFF(n) + 3) #define SISR_COLL(n) __BIT(SISR_OFF(n) + 2) #define SISR_OVRUN(n) __BIT(SISR_OFF(n) + 1) #define SISR_UNRUN(n) __BIT(SISR_OFF(n) + 0) #define SISR_ERROR_MASK(n) (SISR_NOREP(n) | SISR_COLL(n) | \ SISR_OVRUN(n) | SISR_UNRUN(n)) #define SISR_ERROR_ACK_ALL (SISR_ERROR_MASK(0) | SISR_ERROR_MASK(1) | \ SISR_ERROR_MASK(2) | SISR_ERROR_MASK(3)) #define SIEXILK 0x3c #define SIIOBUF 0x80 #define GCPAD_REPORT_SIZE 9 #define GCPAD_START(_buf) ISSET((_buf)[0], 0x10) #define GCPAD_Y(_buf) ISSET((_buf)[0], 0x08) #define GCPAD_X(_buf) ISSET((_buf)[0], 0x04) #define GCPAD_B(_buf) ISSET((_buf)[0], 0x02) #define GCPAD_A(_buf) ISSET((_buf)[0], 0x01) #define GCPAD_LCLICK(_buf) ISSET((_buf)[1], 0x40) #define GCPAD_RCLICK(_buf) ISSET((_buf)[1], 0x20) #define GCPAD_Z(_buf) ISSET((_buf)[1], 0x10) #define GCPAD_UP(_buf) ISSET((_buf)[1], 0x08) #define GCPAD_DOWN(_buf) ISSET((_buf)[1], 0x04) #define GCPAD_RIGHT(_buf) ISSET((_buf)[1], 0x02) #define GCPAD_LEFT(_buf) ISSET((_buf)[1], 0x01) struct si_softc; struct si_channel { struct si_softc *ch_sc; device_t ch_dev; unsigned ch_index; struct hidev_tag ch_hidev; kmutex_t ch_lock; kcondvar_t ch_cv; uint8_t ch_state; #define SI_STATE_OPEN __BIT(0) #define SI_STATE_STOPPED __BIT(1) void (*ch_intr)(void *, void *, u_int); void *ch_intrarg; uint8_t ch_buf[GCPAD_REPORT_SIZE]; void *ch_desc; int ch_descsize; }; struct si_softc { device_t sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; struct si_channel sc_chan[SI_NUM_CHAN]; }; #define RD4(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define WR4(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) static int si_match(device_t, cfdata_t, void *); static void si_attach(device_t, device_t, void *); static int si_intr(void *); static int si_rescan(device_t, const char *, const int *); static int si_print(void *, const char *); static void si_get_report_desc(void *, void **, int *); static int si_open(void *, void (*)(void *, void *, unsigned), void *); static void si_stop(void *); static void si_close(void *); static usbd_status si_set_report(void *, int, void *, int); static usbd_status si_get_report(void *, int, void *, int); static usbd_status si_write(void *, void *, int); CFATTACH_DECL_NEW(si, sizeof(struct si_softc), si_match, si_attach, NULL, NULL); static int si_match(device_t parent, cfdata_t cf, void *aux) { struct mainbus_attach_args *maa = aux; return strcmp(maa->maa_name, "si") == 0; } static void si_attach(device_t parent, device_t self, void *aux) { struct mainbus_attach_args * const maa = aux; struct si_softc * const sc = device_private(self); unsigned chan; void *ih; KASSERT(device_unit(self) == 0); aprint_naive("\n"); aprint_normal(": Serial Interface\n"); sc->sc_dev = self; sc->sc_bst = maa->maa_bst; if (bus_space_map(sc->sc_bst, maa->maa_addr, SI_SIZE, 0, &sc->sc_bsh) != 0) { aprint_error_dev(self, "couldn't map registers\n"); return; } for (chan = 0; chan < SI_NUM_CHAN; chan++) { struct si_channel *ch; struct hidev_tag *t; ch = &sc->sc_chan[chan]; ch->ch_sc = sc; ch->ch_index = chan; mutex_init(&ch->ch_lock, MUTEX_DEFAULT, IPL_VM); cv_init(&ch->ch_cv, "sich"); t = &ch->ch_hidev; t->_cookie = &sc->sc_chan[chan]; t->_get_report_desc = si_get_report_desc; t->_open = si_open; t->_stop = si_stop; t->_close = si_close; t->_set_report = si_set_report; t->_get_report = si_get_report; t->_write = si_write; } WR4(sc, SIPOLL, __SHIFTIN(7, SIPOLL_X) | __SHIFTIN(1, SIPOLL_Y)); WR4(sc, SICOMCSR, SICOMCSR_RDSTINT | SICOMCSR_RDSTINTMSK); ih = intr_establish_xname(maa->maa_irq, IST_LEVEL, IPL_VM, si_intr, sc, device_xname(self)); KASSERT(ih != NULL); si_rescan(self, NULL, NULL); } static int si_rescan(device_t self, const char *ifattr, const int *locs) { struct si_softc * const sc = device_private(self); struct si_attach_args saa; unsigned chan; for (chan = 0; chan < SI_NUM_CHAN; chan++) { struct si_channel *ch = &sc->sc_chan[chan]; if (ch->ch_dev == NULL) { saa.saa_hidev = &ch->ch_hidev; saa.saa_index = ch->ch_index; ch->ch_dev = config_found(self, &saa, si_print, CFARGS(.submatch = config_stdsubmatch, .locators = locs)); } } return 0; } static int si_print(void *aux, const char *pnp) { struct si_attach_args *saa = aux; if (pnp != NULL) { aprint_normal("uhid at %s", pnp); } /* * The Wii Operations Manual for RVL-001 refers to the controller * ports as "Nintendo GameCube Controller Sockets". */ aprint_normal(" socket %d", saa->saa_index + 1); return UNCONF; } static void si_make_report(struct si_softc *sc, unsigned chan, void *report, bool with_rid) { uint32_t inbuf[2]; uint8_t *iptr = (uint8_t *)inbuf; uint8_t *optr = report; unsigned off = 0; inbuf[0] = RD4(sc, SICINBUFH(chan)); inbuf[1] = RD4(sc, SICINBUFL(chan)); if (with_rid) { optr[off++] = chan + 1; } optr[off] = 0; optr[off] |= GCPAD_X(iptr) ? 0x01 : 0; optr[off] |= GCPAD_A(iptr) ? 0x02 : 0; optr[off] |= GCPAD_B(iptr) ? 0x04 : 0; optr[off] |= GCPAD_Y(iptr) ? 0x08 : 0; optr[off] |= GCPAD_LCLICK(iptr) ? 0x10 : 0; optr[off] |= GCPAD_RCLICK(iptr) ? 0x20 : 0; optr[off] |= GCPAD_Z(iptr) ? 0x80 : 0; off++; optr[off] = 0; optr[off] |= GCPAD_START(iptr) ? 0x02 : 0; optr[off] |= GCPAD_UP(iptr) ? 0x10 : 0; optr[off] |= GCPAD_RIGHT(iptr) ? 0x20 : 0; optr[off] |= GCPAD_DOWN(iptr) ? 0x40 : 0; optr[off] |= GCPAD_LEFT(iptr) ? 0x80 : 0; off++; memcpy(&optr[off], &iptr[2], 6); off += 6; optr[off++] = 0; } static int si_intr(void *priv) { struct si_softc *sc = priv; unsigned chan; uint32_t comcsr, sr; int ret = 0; comcsr = RD4(sc, SICOMCSR); sr = RD4(sc, SISR); if (ISSET(comcsr, SICOMCSR_TCINT)) { WR4(sc, SICOMCSR, comcsr | SICOMCSR_TCINT); } if (ISSET(comcsr, SICOMCSR_RDSTINT)) { for (chan = 0; chan < SI_NUM_CHAN; chan++) { struct si_channel *ch = &sc->sc_chan[chan]; if (ISSET(sr, SISR_RDST(chan))) { /* Reading INBUF[HL] de-asserts RDSTINT. */ si_make_report(sc, chan, ch->ch_buf, false); if (ISSET(ch->ch_state, SI_STATE_OPEN)) { ch->ch_intr(ch->ch_intrarg, ch->ch_buf, sizeof(ch->ch_buf)); } } ret = 1; } } WR4(sc, SISR, sr & SISR_ERROR_ACK_ALL); return ret; } static void si_get_report_desc(void *cookie, void **desc, int *size) { *desc = gcpad_report_descr; *size = sizeof(gcpad_report_descr); } static int si_open(void *cookie, void (*intr)(void *, void *, u_int), void *arg) { struct si_channel *ch = cookie; struct si_softc *sc = ch->ch_sc; int error; mutex_enter(&ch->ch_lock); if (ISSET(ch->ch_state, SI_STATE_OPEN)) { error = EBUSY; goto unlock; } ch->ch_intr = intr; ch->ch_intrarg = arg; ch->ch_state |= SI_STATE_OPEN; (void)RD4(sc, SICINBUFH(ch->ch_index)); (void)RD4(sc, SICINBUFL(ch->ch_index)); /* Init controller */ WR4(sc, SICOUTBUF(ch->ch_index), 0x00400300); /* Enable polling */ WR4(sc, SIPOLL, RD4(sc, SIPOLL) | SIPOLL_EN(ch->ch_index)); WR4(sc, SISR, SISR_WR(ch->ch_index)); WR4(sc, SICOMCSR, RD4(sc, SICOMCSR) | SICOMCSR_TSTART); error = 0; unlock: mutex_exit(&ch->ch_lock); return error; } static void si_stop(void *cookie) { struct si_channel *ch = cookie; mutex_enter(&ch->ch_lock); ch->ch_state |= SI_STATE_STOPPED; cv_broadcast(&ch->ch_cv); mutex_exit(&ch->ch_lock); } static void si_close(void *cookie) { struct si_channel *ch = cookie; struct si_softc *sc = ch->ch_sc; mutex_enter(&ch->ch_lock); /* Diable polling */ WR4(sc, SIPOLL, RD4(sc, SIPOLL) & ~SIPOLL_EN(ch->ch_index)); ch->ch_state &= ~(SI_STATE_OPEN | SI_STATE_STOPPED); ch->ch_intr = NULL; ch->ch_intrarg = NULL; cv_broadcast(&ch->ch_cv); mutex_exit(&ch->ch_lock); } static usbd_status si_set_report(void *cookie, int type, void *data, int len) { return USBD_INVAL; } static usbd_status si_get_report(void *cookie, int type, void *data, int len) { struct si_channel *ch = cookie; struct si_softc *sc = ch->ch_sc; uint32_t *inbuf = data; if (len != GCPAD_REPORT_SIZE + 1) { return USBD_IOERROR; } mutex_enter(&ch->ch_lock); si_make_report(sc, ch->ch_index, inbuf, true); mutex_exit(&ch->ch_lock); return USBD_NORMAL_COMPLETION; } static usbd_status si_write(void *cookie, void *data, int len) { return USBD_INVAL; }