wiifb.c revision 1.1 1 /* $NetBSD: wiifb.c,v 1.1 2024/01/20 21:36:00 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: wiifb.c,v 1.1 2024/01/20 21:36:00 jmcneill 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
45 #define WIIFB_ERROR_BLINK_INTERVAL 1000000
46
47 struct wiifb_mode {
48 const char * name;
49 u_int width;
50 u_int height;
51 u_int lines;
52 };
53
54 static uint32_t wiifb_devcmap[16] = {
55 0x00800080, /* Black */
56 0x1dff1d6b, /* Blue */
57 0x4b554b4a, /* Green */
58 0x80808080, /* Cyan */
59 0x4c544cff, /* Red */
60 0x3aaa34b5, /* Magenta */
61 0x7140718a, /* Brown */
62 0xff80ff80, /* White */
63 0x80808080, /* Gray */
64 0xc399c36a, /* Bright Blue */
65 0xd076d074, /* Bright Green */
66 0x80808080, /* Bright Cyan */
67 0x4c544cff, /* Bright Red */
68 0x3aaa34b5, /* Bright Magenta */
69 0xe100e194, /* Bright Yellow */
70 0xff80ff80 /* Bright White */
71 };
72
73 #define WIIFB_MODE_INDEX(fmt, interlaced) ((fmt << 1) | interlaced)
74
75 static const struct wiifb_mode wiifb_modes[] = {
76 [WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 0)] = {
77 .name = "NTSC 480p",
78 .width = 640,
79 .height = 480,
80 .lines = 525,
81 },
82 [WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 1)] = {
83 .name = "NTSC 480i",
84 .width = 640,
85 .height = 480,
86 .lines = 525,
87 },
88 };
89 #define WIIFB_NMODES __arraycount(wiifb_modes)
90
91 struct wiifb_softc {
92 struct genfb_softc sc_gen;
93
94 bus_space_tag_t sc_bst;
95 bus_space_handle_t sc_bsh;
96
97 void *sc_bits;
98
99 uint8_t sc_format;
100 bool sc_interlaced;
101
102 const struct wiifb_mode *sc_curmode;
103 };
104
105 #define RD2(sc, reg) \
106 bus_space_read_2((sc)->sc_bst, (sc)->sc_bsh, (reg))
107 #define RD4(sc, reg) \
108 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
109 #define WR2(sc, reg, val) \
110 bus_space_write_2((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
111 #define WR4(sc, reg, val) \
112 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
113
114 static int wiifb_match(device_t, cfdata_t, void *);
115 static void wiifb_attach(device_t, device_t, void *);
116
117 static void wiifb_init(struct wiifb_softc *);
118 static void wiifb_set_mode(struct wiifb_softc *, uint8_t, bool);
119 static void wiifb_set_fb(struct wiifb_softc *);
120
121 static int wiifb_ioctl(void *, void *, u_long, void *, int, lwp_t *);
122 static paddr_t wiifb_mmap(void *, void *, off_t, int);
123
124 static struct genfb_ops wiifb_ops = {
125 .genfb_ioctl = wiifb_ioctl,
126 .genfb_mmap = wiifb_mmap,
127 };
128
129 CFATTACH_DECL_NEW(wiifb, sizeof(struct wiifb_softc),
130 wiifb_match, wiifb_attach, NULL, NULL);
131
132 static int
133 wiifb_match(device_t parent, cfdata_t cf, void *aux)
134 {
135 struct mainbus_attach_args *maa = aux;
136
137 return strcmp(maa->maa_name, "genfb") == 0;
138 }
139
140 static void
141 wiifb_attach(device_t parent, device_t self, void *aux)
142 {
143 struct wiifb_softc *sc = device_private(self);
144 prop_dictionary_t dict = device_properties(self);
145 struct mainbus_attach_args *maa = aux;
146 int error;
147
148 sc->sc_gen.sc_dev = self;
149 sc->sc_bst = maa->maa_bst;
150 error = bus_space_map(sc->sc_bst, maa->maa_addr, VI_SIZE, 0,
151 &sc->sc_bsh);
152 if (error != 0) {
153 panic("couldn't map registers");
154 }
155 sc->sc_bits = mapiodev(XFB_START, XFB_SIZE, true);
156
157 wiifb_init(sc);
158 wiifb_set_mode(sc, sc->sc_format, sc->sc_interlaced);
159
160 prop_dictionary_set_uint32(dict, "width", sc->sc_curmode->width);
161 prop_dictionary_set_uint32(dict, "height", sc->sc_curmode->height);
162 prop_dictionary_set_uint8(dict, "depth", 16);
163 prop_dictionary_set_uint32(dict, "address", XFB_START);
164 prop_dictionary_set_uint32(dict, "virtual_address",
165 (uintptr_t)sc->sc_bits);
166 prop_dictionary_set_uint64(dict, "devcmap", (uintptr_t)wiifb_devcmap);
167
168 genfb_init(&sc->sc_gen);
169
170 aprint_naive("\n");
171 aprint_normal(": %s\n", sc->sc_curmode->name);
172
173 genfb_cnattach();
174 prop_dictionary_set_bool(dict, "is_console", true);
175 genfb_attach(&sc->sc_gen, &wiifb_ops);
176 }
177
178 static void
179 wiifb_init(struct wiifb_softc *sc)
180 {
181 uint16_t dcr;
182
183 #if notyet
184 /* Read current display format and interlaced settings. */
185 dcr = RD2(sc, VI_DCR);
186 sc->sc_format = __SHIFTOUT(dcr, VI_DCR_FMT);
187 sc->sc_interlaced = (dcr & VI_DCR_NIN) == 0;
188
189 /* Reset video interface. */
190 WR2(sc, VI_DCR, dcr | VI_DCR_RST);
191 WR2(sc, VI_DCR, dcr & ~VI_DCR_RST);
192 #else
193 /* Force NTSC 480i and reset video interface. */
194 dcr = RD2(sc, VI_DCR);
195 dcr |= VI_DCR_RST;
196 WR2(sc, VI_DCR, dcr);
197 dcr &= ~VI_DCR_RST;
198 dcr &= ~VI_DCR_FMT;
199 dcr |= __SHIFTIN(VI_DCR_FMT_NTSC, VI_DCR_FMT);
200 dcr &= ~VI_DCR_NIN;
201 WR2(sc, VI_DCR, dcr);
202
203 sc->sc_format = VI_DCR_FMT_NTSC;
204 sc->sc_interlaced = 1;
205 #endif
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 /* 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 {
231 /*
232 * Display mode is not supported. Blink the slot LED to
233 * indicate failure.
234 */
235 wii_slot_led_blink(WIIFB_ERROR_BLINK_INTERVAL);
236 }
237
238 /* Picture configuration */
239 strides = (sc->sc_curmode->width * 2) / (interlaced ? 16 : 32);
240 reads = (sc->sc_curmode->width * 2) / 32;
241 WR2(sc, VI_PICCONF,
242 __SHIFTIN(strides, VI_PICCONF_STRIDES) |
243 __SHIFTIN(reads, VI_PICCONF_READS));
244
245 WR2(sc, VI_HSR, __SHIFTIN(256, VI_HSR_STP));
246
247 /* Video clock configuration */
248 WR2(sc, VI_VICLK,
249 interlaced ? VI_VICLK_SEL_27MHZ : VI_VICLK_SEL_54MHZ);
250
251 /* Horizontal scaling width */
252 WR2(sc, VI_HSCALINGW, sc->sc_curmode->width);
253
254 /* Set framebuffer address */
255 wiifb_set_fb(sc);
256 }
257
258 static void
259 wiifb_set_fb(struct wiifb_softc *sc)
260 {
261 uint32_t taddr = XFB_START;
262 uint32_t baddr = taddr + (sc->sc_interlaced ?
263 sc->sc_curmode->width * 2 : 0);
264
265 WR4(sc, VI_TFBL,
266 VI_TFBL_PGOFF |
267 __SHIFTIN((taddr >> 5), VI_TFBL_FBB) |
268 __SHIFTIN((taddr / 2) & 0xf, VI_TFBL_XOF));
269 WR4(sc, VI_TFBR, 0);
270
271 WR4(sc, VI_BFBL,
272 VI_BFBL_PGOFF |
273 __SHIFTIN((baddr >> 5), VI_BFBL_FBB) |
274 __SHIFTIN((baddr / 2) & 0xf, VI_BFBL_XOF));
275 WR4(sc, VI_BFBR, 0);
276 }
277
278 static int
279 wiifb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l)
280 {
281 struct wiifb_softc *sc = v;
282 struct wsdisplayio_bus_id *busid;
283 struct wsdisplayio_fbinfo *fbi;
284
285 switch (cmd) {
286 case WSDISPLAYIO_GTYPE:
287 *(u_int *)data = WSDISPLAY_TYPE_HOLLYWOOD;
288 return 0;
289 case WSDISPLAYIO_GET_BUSID:
290 busid = data;
291 busid->bus_type = WSDISPLAYIO_BUS_SOC;
292 return 0;
293 case WSDISPLAYIO_GET_FBINFO:
294 fbi = data;
295 /*
296 * rasops info does not match the pixel encoding due to our
297 * devcmap, so fill out fbinfo manually instead of relying
298 * on wsdisplayio_get_fbinfo.
299 */
300 fbi->fbi_fbsize = XFB_SIZE;
301 fbi->fbi_fboffset = 0;
302 fbi->fbi_width = sc->sc_curmode->width;
303 fbi->fbi_height = sc->sc_curmode->height;
304 fbi->fbi_stride = fbi->fbi_width * 2;
305 fbi->fbi_bitsperpixel = 16;
306 fbi->fbi_pixeltype = WSFB_YUY2;
307 fbi->fbi_flags = WSFB_VRAM_IS_RAM;
308 return 0;
309 }
310
311 return EPASSTHROUGH;
312 }
313
314 static paddr_t
315 wiifb_mmap(void *v, void *vs, off_t off, int prot)
316 {
317 struct wiifb_softc *sc = v;
318
319 if (off < 0 || off >= XFB_SIZE) {
320 return -1;
321 }
322
323 return bus_space_mmap(sc->sc_bst, XFB_START, off, prot,
324 BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE);
325 }
326