1/* $NetBSD: exi.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2024 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: exi.c,v 1.1 2026/01/09 22:54:29 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
58struct 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
66struct 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
75static 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
82static int	exi_match(device_t, cfdata_t, void *);
83static void	exi_attach(device_t, device_t, void *);
84
85static int	exi_rescan(device_t, const char *, const int *);
86static int	exi_print(void *, const char *);
87
88CFATTACH_DECL_NEW(exi, sizeof(struct exi_softc),
89	exi_match, exi_attach, NULL, NULL);
90
91static int
92exi_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
99static void
100exi_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
136static int
137exi_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
149			if (ch->ch_child[dev] != NULL) {
150				continue;
151			}
152
153			exi_select(chan, dev, EXI_FREQ_8MHZ);
154			exi_send_imm(chan, dev, &command, sizeof(command));
155			exi_recv_imm(chan, dev, &id, sizeof(id));
156			exi_unselect(chan);
157
158			if (id == 0xffffffff) {
159				continue;
160			}
161
162			eaa.eaa_id = id;
163			eaa.eaa_chan = chan;
164			eaa.eaa_device = dev;
165
166			ch->ch_child[dev] = config_found(self, &eaa, exi_print,
167			    CFARGS(.submatch = config_stdsubmatch,
168				   .locators = locs));
169		}
170	}
171
172	return 0;
173}
174
175static int
176exi_print(void *aux, const char *pnp)
177{
178	struct exi_attach_args *eaa = aux;
179
180	if (pnp != NULL && eaa->eaa_id == 0) {
181		return QUIET;
182	}
183
184	if (pnp != NULL) {
185		aprint_normal("exi device ID 0x%08x at %s", eaa->eaa_id, pnp);
186	}
187
188	aprint_normal(" channel %u device %u", eaa->eaa_chan, eaa->eaa_device);
189
190	return UNCONF;
191}
192
193void
194exi_select(uint8_t chan, uint8_t dev, exi_freq_t freq)
195{
196	struct exi_channel *ch;
197	uint32_t val;
198
199	ASSERT_CHAN_VALID(chan);
200	ASSERT_DEV_VALID(dev);
201
202	ch = &exi_softc->sc_chan[chan];
203	mutex_enter(&ch->ch_lock);
204
205	val = RD4(exi_softc, EXI_CSR(chan));
206	val &= ~EXI_CSR_CS;
207	val |= __SHIFTIN(__BIT(dev), EXI_CSR_CS);
208	val &= ~EXI_CSR_CLK;
209	val |= __SHIFTIN(freq, EXI_CSR_CLK);
210	WR4(exi_softc, EXI_CSR(chan), val);
211}
212
213void
214exi_unselect(uint8_t chan)
215{
216	struct exi_channel *ch;
217	uint32_t val;
218
219	ASSERT_CHAN_VALID(chan);
220
221	ch = &exi_softc->sc_chan[chan];
222
223	val = RD4(exi_softc, EXI_CSR(chan));
224	val &= ~EXI_CSR_CS;
225	WR4(exi_softc, EXI_CSR(chan), val);
226
227	mutex_exit(&ch->ch_lock);
228}
229
230static void
231exi_wait(uint8_t chan)
232{
233	uint32_t val;
234
235	ASSERT_CHAN_VALID(chan);
236
237	do {
238		val = RD4(exi_softc, EXI_CR(chan));
239	} while ((val & EXI_CR_TSTART) != 0);
240}
241
242void
243exi_send_imm(uint8_t chan, uint8_t dev, const void *data, size_t datalen)
244{
245	struct exi_channel *ch;
246	uint32_t val = 0;
247
248	ASSERT_CHAN_VALID(chan);
249	ASSERT_DEV_VALID(dev);
250	ASSERT_LEN_VALID(datalen);
251
252	ch = &exi_softc->sc_chan[chan];
253	KASSERT(mutex_owned(&ch->ch_lock));
254
255	switch (datalen) {
256	case 1:
257		val = *(const uint8_t *)data << 24;
258		break;
259	case 2:
260		val = *(const uint16_t *)data << 16;
261		break;
262	case 4:
263		val = *(const uint32_t *)data;
264		break;
265	}
266
267	WR4(exi_softc, EXI_DATA(chan), val);
268	WR4(exi_softc, EXI_CR(chan),
269	    EXI_CR_TSTART | EXI_CR_RW_WRITE |
270	    __SHIFTIN(datalen - 1, EXI_CR_TLEN));
271	exi_wait(chan);
272}
273
274void
275exi_recv_imm(uint8_t chan, uint8_t dev, void *data, size_t datalen)
276{
277	struct exi_channel *ch;
278	uint32_t val;
279
280	ASSERT_CHAN_VALID(chan);
281	ASSERT_DEV_VALID(dev);
282	ASSERT_LEN_VALID(datalen);
283
284	ch = &exi_softc->sc_chan[chan];
285	KASSERT(mutex_owned(&ch->ch_lock));
286
287	WR4(exi_softc, EXI_CR(chan),
288	    EXI_CR_TSTART | EXI_CR_RW_READ |
289	    __SHIFTIN(datalen - 1, EXI_CR_TLEN));
290	exi_wait(chan);
291	val = RD4(exi_softc, EXI_DATA(chan));
292
293	switch (datalen) {
294	case 1:
295		*(uint8_t *)data = val >> 24;
296		break;
297	case 2:
298		*(uint16_t *)data = val >> 16;
299		break;
300	case 4:
301		*(uint32_t *)data = val;
302		break;
303	}
304}
305
306void
307exi_sendrecv_imm(uint8_t chan, uint8_t dev, const void *dataout, void *datain,
308    size_t datalen)
309{
310	struct exi_channel *ch;
311	uint32_t val = 0;
312
313	ASSERT_CHAN_VALID(chan);
314	ASSERT_DEV_VALID(dev);
315	ASSERT_LEN_VALID(datalen);
316
317	ch = &exi_softc->sc_chan[chan];
318	KASSERT(mutex_owned(&ch->ch_lock));
319
320	switch (datalen) {
321	case 1:
322		val = *(const uint8_t *)dataout << 24;
323		break;
324	case 2:
325		val = *(const uint16_t *)dataout << 16;
326		break;
327	case 4:
328		val = *(const uint32_t *)dataout;
329		break;
330	}
331
332	WR4(exi_softc, EXI_DATA(chan), val);
333	WR4(exi_softc, EXI_CR(chan),
334	    EXI_CR_TSTART | EXI_CR_RW_READWRITE |
335	    __SHIFTIN(datalen - 1, EXI_CR_TLEN));
336	exi_wait(chan);
337	val = RD4(exi_softc, EXI_DATA(chan));
338
339	switch (datalen) {
340	case 1:
341		*(uint8_t *)datain = val >> 24;
342		break;
343	case 2:
344		*(uint16_t *)datain = val >> 16;
345		break;
346	case 4:
347		*(uint32_t *)datain = val;
348		break;
349	}
350}
351
352
353void
354exi_recv_dma(uint8_t chan, uint8_t dev, void *data, size_t datalen)
355{
356	struct exi_channel *ch;
357	int error;
358
359	ASSERT_CHAN_VALID(chan);
360	ASSERT_DEV_VALID(dev);
361	KASSERT((datalen & 0x1f) == 0);
362
363	ch = &exi_softc->sc_chan[chan];
364	KASSERT(mutex_owned(&ch->ch_lock));
365
366	error = bus_dmamap_load(exi_softc->sc_dmat, ch->ch_dmamap,
367	    data, datalen, NULL, BUS_DMA_WAITOK);
368	if (error != 0) {
369		device_printf(exi_softc->sc_dev, "can't load DMA handle: %d\n",
370		    error);
371		return;
372	}
373
374	KASSERT((ch->ch_dmamap->dm_segs[0].ds_addr & 0x1f) == 0);
375
376	bus_dmamap_sync(exi_softc->sc_dmat, ch->ch_dmamap, 0, datalen,
377	    BUS_DMASYNC_PREREAD);
378
379	WR4(exi_softc, EXI_MAR(chan), ch->ch_dmamap->dm_segs[0].ds_addr);
380	WR4(exi_softc, EXI_LENGTH(chan), datalen);
381	WR4(exi_softc, EXI_CR(chan),
382	    EXI_CR_TSTART | EXI_CR_RW_READ | EXI_CR_DMA);
383	exi_wait(chan);
384
385	bus_dmamap_sync(exi_softc->sc_dmat, ch->ch_dmamap, 0, datalen,
386	    BUS_DMASYNC_POSTREAD);
387
388	bus_dmamap_unload(exi_softc->sc_dmat, ch->ch_dmamap);
389}
390