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