wiifb.c revision 1.5.2.2 1 /* $NetBSD: wiifb.c,v 1.5.2.2 2024/02/03 11:47:04 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: wiifb.c,v 1.5.2.2 2024/02/03 11:47:04 martin Exp $");
31
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/device.h>
35 #include <sys/systm.h>
36
37 #include <machine/wii.h>
38
39 #include <dev/videomode/videomode.h>
40 #include <dev/wsfb/genfbvar.h>
41
42 #include "mainbus.h"
43 #include "vireg.h"
44 #include "viio.h"
45
46 #define WIIFB_ERROR_BLINK_INTERVAL 1000000
47
48 struct wiifb_mode {
49 const char * name;
50 u_int width;
51 u_int height;
52 u_int lines;
53 };
54
55 static uint32_t wiifb_devcmap[16] = {
56 0x00800080, /* Black */
57 0x1dff1d6b, /* Blue */
58 0x4b554b4a, /* Green */
59 0x80808080, /* Cyan */
60 0x4c544cff, /* Red */
61 0x3aaa34b5, /* Magenta */
62 0x7140718a, /* Brown */
63 0xff80ff80, /* White */
64 0x80808080, /* Gray */
65 0xc399c36a, /* Bright Blue */
66 0xd076d074, /* Bright Green */
67 0x80808080, /* Bright Cyan */
68 0x4c544cff, /* Bright Red */
69 0x3aaa34b5, /* Bright Magenta */
70 0xe100e194, /* Bright Yellow */
71 0xff80ff80 /* Bright White */
72 };
73
74 #define WIIFB_MODE_INDEX(fmt, interlaced) ((fmt << 1) | interlaced)
75
76 static const struct wiifb_mode wiifb_modes[] = {
77 [WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 0)] = {
78 .name = "NTSC 480p",
79 .width = 640,
80 .height = 480,
81 .lines = 525,
82 },
83 [WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 1)] = {
84 .name = "NTSC 480i",
85 .width = 640,
86 .height = 480,
87 .lines = 525,
88 },
89 [WIIFB_MODE_INDEX(VI_DCR_FMT_PAL, 1)] = {
90 .name = "PAL 576i",
91 .width = 640,
92 .height = 574,
93 .lines = 625,
94 },
95
96 };
97 #define WIIFB_NMODES __arraycount(wiifb_modes)
98
99 struct wiifb_softc {
100 struct genfb_softc sc_gen;
101
102 bus_space_tag_t sc_bst;
103 bus_space_handle_t sc_bsh;
104
105 void *sc_bits;
106
107 uint8_t sc_format;
108 bool sc_interlaced;
109
110 const struct wiifb_mode *sc_curmode;
111 };
112
113 #define RD2(sc, reg) \
114 bus_space_read_2((sc)->sc_bst, (sc)->sc_bsh, (reg))
115 #define RD4(sc, reg) \
116 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
117 #define WR2(sc, reg, val) \
118 bus_space_write_2((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
119 #define WR4(sc, reg, val) \
120 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
121
122 static int wiifb_match(device_t, cfdata_t, void *);
123 static void wiifb_attach(device_t, device_t, void *);
124
125 static void wiifb_init(struct wiifb_softc *);
126 static void wiifb_set_mode(struct wiifb_softc *, uint8_t, bool);
127 static void wiifb_set_fb(struct wiifb_softc *);
128
129 static int wiifb_ioctl(void *, void *, u_long, void *, int, lwp_t *);
130 static paddr_t wiifb_mmap(void *, void *, off_t, int);
131
132 static struct genfb_ops wiifb_ops = {
133 .genfb_ioctl = wiifb_ioctl,
134 .genfb_mmap = wiifb_mmap,
135 };
136
137 CFATTACH_DECL_NEW(wiifb, sizeof(struct wiifb_softc),
138 wiifb_match, wiifb_attach, NULL, NULL);
139
140 static int
141 wiifb_match(device_t parent, cfdata_t cf, void *aux)
142 {
143 struct mainbus_attach_args *maa = aux;
144
145 return strcmp(maa->maa_name, "genfb") == 0;
146 }
147
148 static void
149 wiifb_attach(device_t parent, device_t self, void *aux)
150 {
151 struct wiifb_softc *sc = device_private(self);
152 prop_dictionary_t dict = device_properties(self);
153 struct mainbus_attach_args *maa = aux;
154 int error;
155
156 sc->sc_gen.sc_dev = self;
157 sc->sc_bst = maa->maa_bst;
158 error = bus_space_map(sc->sc_bst, maa->maa_addr, VI_SIZE, 0,
159 &sc->sc_bsh);
160 if (error != 0) {
161 panic("couldn't map registers");
162 }
163 sc->sc_bits = mapiodev(XFB_START, XFB_SIZE, true);
164
165 wiifb_init(sc);
166 wiifb_set_mode(sc, sc->sc_format, sc->sc_interlaced);
167
168 prop_dictionary_set_uint32(dict, "width", sc->sc_curmode->width);
169 prop_dictionary_set_uint32(dict, "height", sc->sc_curmode->height);
170 prop_dictionary_set_uint8(dict, "depth", 16);
171 prop_dictionary_set_uint32(dict, "address", XFB_START);
172 prop_dictionary_set_uint32(dict, "virtual_address",
173 (uintptr_t)sc->sc_bits);
174 prop_dictionary_set_uint64(dict, "devcmap", (uintptr_t)wiifb_devcmap);
175
176 genfb_init(&sc->sc_gen);
177
178 aprint_naive("\n");
179 aprint_normal(": %s\n", sc->sc_curmode->name);
180
181 genfb_cnattach();
182 prop_dictionary_set_bool(dict, "is_console", true);
183 genfb_attach(&sc->sc_gen, &wiifb_ops);
184 }
185
186 static void
187 wiifb_init(struct wiifb_softc *sc)
188 {
189 uint16_t dcr;
190 uint16_t visel;
191
192 /* Read current display format and interlaced settings. */
193 dcr = RD2(sc, VI_DCR);
194 if ((dcr & VI_DCR_ENB) != 0) {
195 sc->sc_format = __SHIFTOUT(dcr, VI_DCR_FMT);
196 sc->sc_interlaced = (dcr & VI_DCR_NIN) == 0;
197 } else {
198 visel = RD2(sc, VI_VISEL);
199 sc->sc_format = VI_DCR_FMT_NTSC;
200 sc->sc_interlaced = (visel & VI_VISEL_COMPONENT_CABLE) == 0;
201 }
202
203 /* Reset video interface. */
204 WR2(sc, VI_DCR, dcr | VI_DCR_RST);
205 WR2(sc, VI_DCR, dcr & ~VI_DCR_RST);
206 }
207
208 static void
209 wiifb_set_mode(struct wiifb_softc *sc, uint8_t format, bool interlaced)
210 {
211 u_int modeidx;
212 u_int strides, reads;
213
214 modeidx = WIIFB_MODE_INDEX(format, interlaced);
215 if (modeidx >= WIIFB_NMODES || wiifb_modes[modeidx].name == NULL) {
216 panic("Unsupported format (0x%x) / interlaced (%d) settings",
217 sc->sc_format, sc->sc_interlaced);
218 }
219 sc->sc_curmode = &wiifb_modes[modeidx];
220
221 if (modeidx == WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 1)) {
222 /* NTSC 480i Magic numbers from YAGCD. */
223 WR2(sc, VI_VTR, 0x0f06);
224 WR4(sc, VI_HTR0, 0x476901AD);
225 WR4(sc, VI_HTR1, 0x02EA5140);
226 WR4(sc, VI_VTO, 0x00030018);
227 WR4(sc, VI_VTE, 0x00020019);
228 WR4(sc, VI_BBOI, 0x410C410C);
229 WR4(sc, VI_BBEI, 0x40ED40ED);
230 } else if (modeidx == WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 0)) {
231 /* NTSC 480p */
232 WR2(sc, VI_VTR, 0x1e0c);
233 WR4(sc, VI_HTR0, 0x476901ad);
234 WR4(sc, VI_HTR1, 0x030a4940);
235 WR4(sc, VI_VTO, 0x00060030);
236 WR4(sc, VI_VTE, 0x00060030);
237 WR4(sc, VI_BBOI, 0x81d881d8);
238 WR4(sc, VI_BBEI, 0x81d881d8);
239 } else if (modeidx == WIIFB_MODE_INDEX(VI_DCR_FMT_PAL, 1)) {
240 /* PAL 576i */
241 WR2(sc, VI_VTR, 0x11f5);
242 WR4(sc, VI_HTR0, 0x4b6a01b0);
243 WR4(sc, VI_HTR1, 0x02f85640);
244 WR4(sc, VI_VTO, 0x00010023);
245 WR4(sc, VI_VTE, 0x00000024);
246 WR4(sc, VI_BBOI, 0x4d2b4d6d);
247 WR4(sc, VI_BBEI, 0x4d8a4d4c);
248 } else {
249 /*
250 * Display mode is not supported. Blink the slot LED to
251 * indicate failure.
252 */
253 wii_slot_led_blink(WIIFB_ERROR_BLINK_INTERVAL);
254 }
255
256 /* Picture configuration */
257 strides = (sc->sc_curmode->width * 2) / (interlaced ? 16 : 32);
258 reads = (sc->sc_curmode->width * 2) / 32;
259 WR2(sc, VI_PICCONF,
260 __SHIFTIN(strides, VI_PICCONF_STRIDES) |
261 __SHIFTIN(reads, VI_PICCONF_READS));
262
263 /* Horizontal scaler configuration */
264 if (interlaced) {
265 WR2(sc, VI_HSR, __SHIFTIN(256, VI_HSR_STP));
266 } else {
267 WR2(sc, VI_HSR, __SHIFTIN(244, VI_HSR_STP) | VI_HSR_HS_EN);
268 }
269
270 /* Video clock configuration */
271 WR2(sc, VI_VICLK,
272 interlaced ? VI_VICLK_SEL_27MHZ : VI_VICLK_SEL_54MHZ);
273
274 /* Horizontal scaling width */
275 WR2(sc, VI_HSCALINGW, sc->sc_curmode->width);
276
277 /* Set framebuffer address */
278 wiifb_set_fb(sc);
279 }
280
281 static void
282 wiifb_set_fb(struct wiifb_softc *sc)
283 {
284 uint32_t taddr = XFB_START;
285 uint32_t baddr = taddr + (sc->sc_interlaced ?
286 sc->sc_curmode->width * 2 : 0);
287
288 WR4(sc, VI_TFBL,
289 VI_TFBL_PGOFF |
290 __SHIFTIN((taddr >> 5), VI_TFBL_FBB) |
291 __SHIFTIN((taddr / 2) & 0xf, VI_TFBL_XOF));
292 WR4(sc, VI_TFBR, 0);
293
294 WR4(sc, VI_BFBL,
295 VI_BFBL_PGOFF |
296 __SHIFTIN((baddr >> 5), VI_BFBL_FBB) |
297 __SHIFTIN((baddr / 2) & 0xf, VI_BFBL_XOF));
298 WR4(sc, VI_BFBR, 0);
299 }
300
301 static int
302 wiifb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l)
303 {
304 struct wiifb_softc *sc = v;
305 struct wsdisplayio_bus_id *busid;
306 struct wsdisplayio_fbinfo *fbi;
307 struct vi_regs *vr;
308 u_int video;
309
310 switch (cmd) {
311 case WSDISPLAYIO_GTYPE:
312 *(u_int *)data = WSDISPLAY_TYPE_HOLLYWOOD;
313 return 0;
314 case WSDISPLAYIO_GET_BUSID:
315 busid = data;
316 busid->bus_type = WSDISPLAYIO_BUS_SOC;
317 return 0;
318 case WSDISPLAYIO_GET_FBINFO:
319 fbi = data;
320 /*
321 * rasops info does not match the pixel encoding due to our
322 * devcmap, so fill out fbinfo manually instead of relying
323 * on wsdisplayio_get_fbinfo.
324 */
325 fbi->fbi_fbsize = XFB_SIZE;
326 fbi->fbi_fboffset = 0;
327 fbi->fbi_width = sc->sc_curmode->width;
328 fbi->fbi_height = sc->sc_curmode->height;
329 fbi->fbi_stride = fbi->fbi_width * 2;
330 fbi->fbi_bitsperpixel = 16;
331 fbi->fbi_pixeltype = WSFB_YUY2;
332 fbi->fbi_flags = WSFB_VRAM_IS_RAM;
333 return 0;
334
335 case WSDISPLAYIO_SVIDEO:
336 video = *(u_int *)data;
337 switch (video) {
338 case WSDISPLAYIO_VIDEO_OFF:
339 out32(HW_VIDIM, __SHIFTIN(7, VIDIM_Y) |
340 __SHIFTIN(7, VIDIM_C) |
341 VIDIM_E);
342 return 0;
343 case WSDISPLAYIO_VIDEO_ON:
344 out32(HW_VIDIM, 0);
345 return 0;
346 default:
347 return EINVAL;
348 }
349
350 case VIIO_GETREGS:
351 case VIIO_SETREGS:
352 vr = data;
353 switch (vr->bits) {
354 case 16:
355 if ((vr->reg & 1) != 0) {
356 return EINVAL;
357 }
358 if (cmd == VIIO_GETREGS) {
359 vr->val16 = RD2(sc, vr->reg);
360 } else {
361 WR2(sc, vr->reg, vr->val16);
362 }
363 return 0;
364 case 32:
365 if ((vr->reg & 3) != 0) {
366 return EINVAL;
367 }
368 if (cmd == VIIO_GETREGS) {
369 vr->val32 = RD4(sc, vr->reg);
370 } else {
371 WR4(sc, vr->reg, vr->val32);
372 }
373 return 0;
374 default:
375 return EINVAL;
376 }
377 return 0;
378 }
379
380 return EPASSTHROUGH;
381 }
382
383 static paddr_t
384 wiifb_mmap(void *v, void *vs, off_t off, int prot)
385 {
386 struct wiifb_softc *sc = v;
387
388 if (off < 0 || off >= XFB_SIZE) {
389 return -1;
390 }
391
392 return bus_space_mmap(sc->sc_bst, XFB_START, off, prot,
393 BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE);
394 }
395