Home | History | Annotate | Line # | Download | only in dev
      1 /* $NetBSD: exi.c,v 1.2 2026/02/03 11:47:18 jmcneill Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2024 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: exi.c,v 1.2 2026/02/03 11:47:18 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 <uvm/uvm_extern.h>
     39 
     40 #include <machine/wii.h>
     41 #include <machine/pio.h>
     42 
     43 #include "locators.h"
     44 #include "mainbus.h"
     45 #include "exi.h"
     46 #include "exireg.h"
     47 
     48 #define	EXI_NUM_CHAN		3
     49 #define	EXI_NUM_DEV		3
     50 
     51 /* This is an arbitrary limit. The real limit is probably much higher. */
     52 #define	EXI_MAX_DMA		4096
     53 
     54 #define	ASSERT_CHAN_VALID(chan)	KASSERT((chan) >= 0 && (chan) < EXI_NUM_CHAN)
     55 #define	ASSERT_DEV_VALID(dev)	KASSERT((dev) >= 0 && (dev) < EXI_NUM_DEV)
     56 #define ASSERT_LEN_VALID(len)	KASSERT((len) == 1 || (len) == 2 || (len) == 4)
     57 
     58 struct exi_channel {
     59 	kmutex_t		ch_lock;
     60 
     61 	bus_dmamap_t		ch_dmamap;
     62 
     63 	device_t		ch_child[EXI_NUM_DEV];
     64 };
     65 
     66 struct exi_softc {
     67 	device_t		sc_dev;
     68 	bus_space_tag_t		sc_bst;
     69 	bus_space_handle_t	sc_bsh;
     70 	bus_dma_tag_t		sc_dmat;
     71 
     72 	struct exi_channel	sc_chan[EXI_NUM_CHAN];
     73 };
     74 
     75 static struct exi_softc *exi_softc;
     76 
     77 #define RD4(sc, reg)							\
     78 	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
     79 #define WR4(sc, reg, val)						\
     80 	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
     81 
     82 static int	exi_match(device_t, cfdata_t, void *);
     83 static void	exi_attach(device_t, device_t, void *);
     84 
     85 static int	exi_rescan(device_t, const char *, const int *);
     86 static int	exi_print(void *, const char *);
     87 
     88 CFATTACH_DECL_NEW(exi, sizeof(struct exi_softc),
     89 	exi_match, exi_attach, NULL, NULL);
     90 
     91 static int
     92 exi_match(device_t parent, cfdata_t cf, void *aux)
     93 {
     94 	struct mainbus_attach_args *maa = aux;
     95 
     96 	return strcmp(maa->maa_name, "exi") == 0;
     97 }
     98 
     99 static void
    100 exi_attach(device_t parent, device_t self, void *aux)
    101 {
    102 	struct mainbus_attach_args * const maa = aux;
    103 	struct exi_softc * const sc = device_private(self);
    104 	uint8_t chan;
    105 	int error;
    106 
    107 	KASSERT(device_unit(self) == 0);
    108 
    109 	aprint_naive("\n");
    110 	aprint_normal(": External Interface\n");
    111 
    112 	exi_softc = sc;
    113 	sc->sc_dev = self;
    114 	sc->sc_bst = maa->maa_bst;
    115 	if (bus_space_map(sc->sc_bst, maa->maa_addr, EXI_SIZE, 0,
    116 	    &sc->sc_bsh) != 0) {
    117 		aprint_error_dev(self, "couldn't map registers\n");
    118 		return;
    119 	}
    120 	sc->sc_dmat = maa->maa_dmat;
    121 	for (chan = 0; chan < EXI_NUM_CHAN; chan++) {
    122 		mutex_init(&sc->sc_chan[chan].ch_lock, MUTEX_DEFAULT, IPL_VM);
    123 		error = bus_dmamap_create(exi_softc->sc_dmat, EXI_MAX_DMA, 1,
    124 		    EXI_MAX_DMA, 0, BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW,
    125 		    &sc->sc_chan[chan].ch_dmamap);
    126 		if (error != 0) {
    127 			aprint_error_dev(self, "couldn't create dmamap: %d\n",
    128 			    error);
    129 			return;
    130 		}
    131 	}
    132 
    133 	exi_rescan(self, NULL, NULL);
    134 }
    135 
    136 static int
    137 exi_rescan(device_t self, const char *ifattr, const int *locs)
    138 {
    139 	struct exi_softc * const sc = device_private(self);
    140 	uint8_t chan, dev;
    141 
    142 	for (chan = 0; chan < EXI_NUM_CHAN; chan++) {
    143 		struct exi_channel *ch = &sc->sc_chan[chan];
    144 		for (dev = 0; dev < EXI_NUM_DEV; dev++) {
    145 			struct exi_attach_args eaa = {};
    146 			uint16_t command = 0x0000; /* ID command */
    147 			uint32_t id = 0;
    148 			bool force;
    149 
    150 			if (ch->ch_child[dev] != NULL) {
    151 				continue;
    152 			}
    153 
    154 			force = chan == 0 && dev == 1;
    155 			if (!force) {
    156 				exi_select(chan, dev, EXI_FREQ_8MHZ);
    157 				exi_send_imm(chan, dev, &command, sizeof(command));
    158 				exi_recv_imm(chan, dev, &id, sizeof(id));
    159 				exi_unselect(chan);
    160 
    161 				if (id == 0xffffffff) {
    162 					continue;
    163 				}
    164 			}
    165 
    166 			eaa.eaa_id = id;
    167 			eaa.eaa_chan = chan;
    168 			eaa.eaa_device = dev;
    169 
    170 			ch->ch_child[dev] = config_found(self, &eaa, exi_print,
    171 			    CFARGS(.submatch = config_stdsubmatch,
    172 				   .locators = locs));
    173 		}
    174 	}
    175 
    176 	return 0;
    177 }
    178 
    179 static int
    180 exi_print(void *aux, const char *pnp)
    181 {
    182 	struct exi_attach_args *eaa = aux;
    183 
    184 	if (pnp != NULL && eaa->eaa_id == 0) {
    185 		return QUIET;
    186 	}
    187 
    188 	if (pnp != NULL) {
    189 		aprint_normal("exi device ID 0x%08x at %s", eaa->eaa_id, pnp);
    190 	}
    191 
    192 	aprint_normal(" channel %u device %u", eaa->eaa_chan, eaa->eaa_device);
    193 
    194 	return UNCONF;
    195 }
    196 
    197 void
    198 exi_select(uint8_t chan, uint8_t dev, exi_freq_t freq)
    199 {
    200 	struct exi_channel *ch;
    201 	uint32_t val;
    202 
    203 	ASSERT_CHAN_VALID(chan);
    204 	ASSERT_DEV_VALID(dev);
    205 
    206 	ch = &exi_softc->sc_chan[chan];
    207 	mutex_enter(&ch->ch_lock);
    208 
    209 	val = RD4(exi_softc, EXI_CSR(chan));
    210 	val &= ~EXI_CSR_CS;
    211 	val |= __SHIFTIN(__BIT(dev), EXI_CSR_CS);
    212 	val &= ~EXI_CSR_CLK;
    213 	val |= __SHIFTIN(freq, EXI_CSR_CLK);
    214 	WR4(exi_softc, EXI_CSR(chan), val);
    215 }
    216 
    217 void
    218 exi_unselect(uint8_t chan)
    219 {
    220 	struct exi_channel *ch;
    221 	uint32_t val;
    222 
    223 	ASSERT_CHAN_VALID(chan);
    224 
    225 	ch = &exi_softc->sc_chan[chan];
    226 
    227 	val = RD4(exi_softc, EXI_CSR(chan));
    228 	val &= ~EXI_CSR_CS;
    229 	WR4(exi_softc, EXI_CSR(chan), val);
    230 
    231 	mutex_exit(&ch->ch_lock);
    232 }
    233 
    234 static void
    235 exi_wait(uint8_t chan)
    236 {
    237 	uint32_t val;
    238 
    239 	ASSERT_CHAN_VALID(chan);
    240 
    241 	do {
    242 		val = RD4(exi_softc, EXI_CR(chan));
    243 	} while ((val & EXI_CR_TSTART) != 0);
    244 }
    245 
    246 void
    247 exi_send_imm(uint8_t chan, uint8_t dev, const void *data, size_t datalen)
    248 {
    249 	struct exi_channel *ch;
    250 	uint32_t val = 0;
    251 
    252 	ASSERT_CHAN_VALID(chan);
    253 	ASSERT_DEV_VALID(dev);
    254 	ASSERT_LEN_VALID(datalen);
    255 
    256 	ch = &exi_softc->sc_chan[chan];
    257 	KASSERT(mutex_owned(&ch->ch_lock));
    258 
    259 	switch (datalen) {
    260 	case 1:
    261 		val = *(const uint8_t *)data << 24;
    262 		break;
    263 	case 2:
    264 		val = *(const uint16_t *)data << 16;
    265 		break;
    266 	case 4:
    267 		val = *(const uint32_t *)data;
    268 		break;
    269 	}
    270 
    271 	WR4(exi_softc, EXI_DATA(chan), val);
    272 	WR4(exi_softc, EXI_CR(chan),
    273 	    EXI_CR_TSTART | EXI_CR_RW_WRITE |
    274 	    __SHIFTIN(datalen - 1, EXI_CR_TLEN));
    275 	exi_wait(chan);
    276 }
    277 
    278 void
    279 exi_recv_imm(uint8_t chan, uint8_t dev, void *data, size_t datalen)
    280 {
    281 	struct exi_channel *ch;
    282 	uint32_t val;
    283 
    284 	ASSERT_CHAN_VALID(chan);
    285 	ASSERT_DEV_VALID(dev);
    286 	ASSERT_LEN_VALID(datalen);
    287 
    288 	ch = &exi_softc->sc_chan[chan];
    289 	KASSERT(mutex_owned(&ch->ch_lock));
    290 
    291 	WR4(exi_softc, EXI_CR(chan),
    292 	    EXI_CR_TSTART | EXI_CR_RW_READ |
    293 	    __SHIFTIN(datalen - 1, EXI_CR_TLEN));
    294 	exi_wait(chan);
    295 	val = RD4(exi_softc, EXI_DATA(chan));
    296 
    297 	switch (datalen) {
    298 	case 1:
    299 		*(uint8_t *)data = val >> 24;
    300 		break;
    301 	case 2:
    302 		*(uint16_t *)data = val >> 16;
    303 		break;
    304 	case 4:
    305 		*(uint32_t *)data = val;
    306 		break;
    307 	}
    308 }
    309 
    310 void
    311 exi_sendrecv_imm(uint8_t chan, uint8_t dev, const void *dataout, void *datain,
    312     size_t datalen)
    313 {
    314 	struct exi_channel *ch;
    315 	uint32_t val = 0;
    316 
    317 	ASSERT_CHAN_VALID(chan);
    318 	ASSERT_DEV_VALID(dev);
    319 	ASSERT_LEN_VALID(datalen);
    320 
    321 	ch = &exi_softc->sc_chan[chan];
    322 	KASSERT(mutex_owned(&ch->ch_lock));
    323 
    324 	switch (datalen) {
    325 	case 1:
    326 		val = *(const uint8_t *)dataout << 24;
    327 		break;
    328 	case 2:
    329 		val = *(const uint16_t *)dataout << 16;
    330 		break;
    331 	case 4:
    332 		val = *(const uint32_t *)dataout;
    333 		break;
    334 	}
    335 
    336 	WR4(exi_softc, EXI_DATA(chan), val);
    337 	WR4(exi_softc, EXI_CR(chan),
    338 	    EXI_CR_TSTART | EXI_CR_RW_READWRITE |
    339 	    __SHIFTIN(datalen - 1, EXI_CR_TLEN));
    340 	exi_wait(chan);
    341 	val = RD4(exi_softc, EXI_DATA(chan));
    342 
    343 	switch (datalen) {
    344 	case 1:
    345 		*(uint8_t *)datain = val >> 24;
    346 		break;
    347 	case 2:
    348 		*(uint16_t *)datain = val >> 16;
    349 		break;
    350 	case 4:
    351 		*(uint32_t *)datain = val;
    352 		break;
    353 	}
    354 }
    355 
    356 
    357 void
    358 exi_recv_dma(uint8_t chan, uint8_t dev, void *data, size_t datalen)
    359 {
    360 	struct exi_channel *ch;
    361 	int error;
    362 
    363 	ASSERT_CHAN_VALID(chan);
    364 	ASSERT_DEV_VALID(dev);
    365 	KASSERT((datalen & 0x1f) == 0);
    366 
    367 	ch = &exi_softc->sc_chan[chan];
    368 	KASSERT(mutex_owned(&ch->ch_lock));
    369 
    370 	error = bus_dmamap_load(exi_softc->sc_dmat, ch->ch_dmamap,
    371 	    data, datalen, NULL, BUS_DMA_WAITOK);
    372 	if (error != 0) {
    373 		device_printf(exi_softc->sc_dev, "can't load DMA handle: %d\n",
    374 		    error);
    375 		return;
    376 	}
    377 
    378 	KASSERT((ch->ch_dmamap->dm_segs[0].ds_addr & 0x1f) == 0);
    379 
    380 	bus_dmamap_sync(exi_softc->sc_dmat, ch->ch_dmamap, 0, datalen,
    381 	    BUS_DMASYNC_PREREAD);
    382 
    383 	WR4(exi_softc, EXI_MAR(chan), ch->ch_dmamap->dm_segs[0].ds_addr);
    384 	WR4(exi_softc, EXI_LENGTH(chan), datalen);
    385 	WR4(exi_softc, EXI_CR(chan),
    386 	    EXI_CR_TSTART | EXI_CR_RW_READ | EXI_CR_DMA);
    387 	exi_wait(chan);
    388 
    389 	bus_dmamap_sync(exi_softc->sc_dmat, ch->ch_dmamap, 0, datalen,
    390 	    BUS_DMASYNC_POSTREAD);
    391 
    392 	bus_dmamap_unload(exi_softc->sc_dmat, ch->ch_dmamap);
    393 }
    394