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