#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: si.c,v 1.2 2026/06/11 19:46:18 andvar Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/bitops.h>
#include <sys/mutex.h>
#include <sys/tty.h>
#include <uvm/uvm_extern.h>
#include <machine/wii.h>
#include <machine/pio.h>
#include <dev/hid/hidev.h>
#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;
void *ch_si;
};
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 void si_softintr(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");
ch->ch_si = softint_establish(SOFTINT_SERIAL,
si_softintr, ch);
KASSERT(ch->ch_si != NULL);
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);
}
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 void
si_softintr(void *priv)
{
struct si_channel *ch = priv;
if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
ch->ch_intr(ch->ch_intrarg, ch->ch_buf, sizeof(ch->ch_buf));
}
}
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))) {
si_make_report(sc, chan, ch->ch_buf, false);
if (ISSET(ch->ch_state, SI_STATE_OPEN)) {
softint_schedule(ch->ch_si);
}
}
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));
WR4(sc, SICOUTBUF(ch->ch_index), 0x00400300);
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);
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;
}