/* $NetBSD: wiifb.c,v 1.5.2.5 2025/10/26 13:12:29 martin Exp $ */ /*- * Copyright (c) 2024-2025 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: wiifb.c,v 1.5.2.5 2025/10/26 13:12:29 martin Exp $"); #include #include #include #include #include #include #include #include #include #include #include "mainbus.h" #include "vireg.h" #include "gxreg.h" #define WIIFB_RGB_WIDTH 640 #define WIIFB_RGB_HEIGHT 480 #define WIIFB_RGB_BPP 32 #define WIIFB_ERROR_BLINK_INTERVAL 1000000 #define WIIFB_TOP_BOTTOM_BORDER 16 #define WIIFB_EFFECTIVE_START(p, w) \ ((uintptr_t)(p) + WIIFB_TOP_BOTTOM_BORDER * (w) * 2) #define WIIFB_EFFECTIVE_HEIGHT(h) \ ((h) - WIIFB_TOP_BOTTOM_BORDER * 2) #define WIIFB_FIFO_SIZE (256 * 1024) #define IBM750CL_SPR_HID2 920 #define IBM750CL_SPR_HID2_WPE 0x40000000 /* Write pipe enable */ #define IBM750CL_SPR_WPAR 921 struct wiifb_mode { const char * name; u_int width; u_int height; u_int lines; }; static uint32_t wiifb_devcmap[16] = { 0x00800080, /* Black */ 0x1dff1d6b, /* Blue */ 0x4b554b4a, /* Green */ 0x80808080, /* Cyan */ 0x4c544cff, /* Red */ 0x3aaa34b5, /* Magenta */ 0x7140718a, /* Brown */ 0xff80ff80, /* White */ 0x80808080, /* Gray */ 0xc399c36a, /* Bright Blue */ 0xd076d074, /* Bright Green */ 0x80808080, /* Bright Cyan */ 0x4c544cff, /* Bright Red */ 0x3aaa34b5, /* Bright Magenta */ 0xe100e194, /* Bright Yellow */ 0xff80ff80 /* Bright White */ }; #define WIIFB_MODE_INDEX(fmt, interlaced) ((fmt << 1) | interlaced) static const struct wiifb_mode wiifb_modes[] = { [WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 0)] = { .name = "NTSC 480p", .width = 640, .height = 480, .lines = 525, }, [WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 1)] = { .name = "NTSC 480i", .width = 640, .height = 480, .lines = 525, }, [WIIFB_MODE_INDEX(VI_DCR_FMT_PAL, 1)] = { .name = "PAL 576i", .width = 640, .height = 574, .lines = 625, }, }; #define WIIFB_NMODES __arraycount(wiifb_modes) struct wiifb_dma { bus_dmamap_t dma_map; bus_dma_tag_t dma_tag; bus_size_t dma_size; bus_dma_segment_t dma_segs[1]; int dma_nsegs; void *dma_addr; }; struct wiifb_softc { struct genfb_softc sc_gen; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; bus_dma_tag_t sc_dmat; void *sc_bits; uint8_t sc_format; bool sc_interlaced; const struct wiifb_mode *sc_curmode; u_int sc_wsmode; volatile uint32_t *sc_efb; volatile uint16_t *sc_cp; volatile uint16_t *sc_pe; volatile uint32_t *sc_pi; gx_wgpipe_t *sc_wgpipe; struct wiifb_dma sc_rgb; struct wiifb_dma sc_fifo; uint16_t sc_token; }; #define RD2(sc, reg) \ bus_space_read_2((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define RD4(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define WR2(sc, reg, val) \ bus_space_write_2((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) #define WR4(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) #define CP_WRITE(sc, off, val) (sc)->sc_cp[(off)] = (val) #define CP_READ(sc, off) (sc)->sc_cp[(off)] #define PE_WRITE(sc, off, val) (sc)->sc_pe[(off)] = (val) #define PE_READ(sc, off) (sc)->sc_pe[(off)] #define PI_WRITE(sc, off, val) (sc)->sc_pi[(off)] = (val) #define PI_READ(sc, off) (sc)->sc_pi[(off)] static int wiifb_match(device_t, cfdata_t, void *); static void wiifb_attach(device_t, device_t, void *); static void wiifb_accel_init(struct wiifb_softc *); static int wiifb_vi_intr(void *); static void wiifb_vi_refresh(void *); static void wiifb_init(struct wiifb_softc *); static void wiifb_set_mode(struct wiifb_softc *, uint8_t, bool); static void wiifb_set_fb(struct wiifb_softc *); static void wiifb_clear_xfb(struct wiifb_softc *); static int wiifb_ioctl(void *, void *, u_long, void *, int, lwp_t *); static paddr_t wiifb_mmap(void *, void *, off_t, int); static struct genfb_ops wiifb_ops = { .genfb_ioctl = wiifb_ioctl, .genfb_mmap = wiifb_mmap, }; CFATTACH_DECL_NEW(wiifb, sizeof(struct wiifb_softc), wiifb_match, wiifb_attach, NULL, NULL); static int wiifb_match(device_t parent, cfdata_t cf, void *aux) { struct mainbus_attach_args *maa = aux; return strcmp(maa->maa_name, "genfb") == 0; } static void wiifb_attach(device_t parent, device_t self, void *aux) { struct wiifb_softc *sc = device_private(self); prop_dictionary_t dict = device_properties(self); struct mainbus_attach_args *maa = aux; int error; sc->sc_gen.sc_dev = self; sc->sc_bst = maa->maa_bst; error = bus_space_map(sc->sc_bst, maa->maa_addr, VI_SIZE, 0, &sc->sc_bsh); if (error != 0) { panic("couldn't map registers"); } sc->sc_bits = mapiodev(XFB_START, XFB_SIZE, true); sc->sc_wsmode = WSDISPLAYIO_MODE_EMUL; sc->sc_dmat = maa->maa_dmat; wiifb_clear_xfb(sc); wiifb_init(sc); wiifb_set_mode(sc, sc->sc_format, sc->sc_interlaced); prop_dictionary_set_uint32(dict, "width", sc->sc_curmode->width); prop_dictionary_set_uint32(dict, "height", WIIFB_EFFECTIVE_HEIGHT(sc->sc_curmode->height)); prop_dictionary_set_uint8(dict, "depth", 16); prop_dictionary_set_uint32(dict, "address", XFB_START); prop_dictionary_set_uint32(dict, "virtual_address", WIIFB_EFFECTIVE_START(sc->sc_bits, sc->sc_curmode->width)); prop_dictionary_set_uint64(dict, "devcmap", (uintptr_t)wiifb_devcmap); genfb_init(&sc->sc_gen); aprint_naive("\n"); aprint_normal(": %s\n", sc->sc_curmode->name); genfb_cnattach(); prop_dictionary_set_bool(dict, "is_console", true); genfb_attach(&sc->sc_gen, &wiifb_ops); wiifb_accel_init(sc); } static void wiifb_clear_xfb(struct wiifb_softc *sc) { u_int offset; uint32_t *p; /* * Paint the entire XFB black. Use 4-byte accesses as the Wii will * ignore 1- and 2- byte writes to uncached memory. */ for (p = sc->sc_bits, offset = 0; offset < XFB_SIZE; offset += 4, p++) { *p = wiifb_devcmap[0]; } } static int wiifb_vi_init(struct wiifb_softc *sc) { device_t dev = sc->sc_gen.sc_dev; void *ih; WR4(sc, VI_DI0, VI_DI_ENB | __SHIFTIN(1, VI_DI_VCT) | __SHIFTIN(1, VI_DI_HCT)); WR4(sc, VI_DI1, 0); WR4(sc, VI_DI2, 0); WR4(sc, VI_DI3, 0); ih = intr_establish_xname(PI_IRQ_VI, IST_LEVEL, IPL_TTY, wiifb_vi_intr, sc, device_xname(dev)); if (ih == NULL) { aprint_error_dev(dev, "failed to install VI intr handler\n"); return EIO; } return 0; } static int wiifb_dma_alloc(struct wiifb_softc *sc, bus_size_t size, bus_size_t align, int flags, struct wiifb_dma *dma) { bus_dma_tag_t dmat = sc->sc_dmat; // &wii_mem1_bus_dma_tag; int error; error = bus_dmamem_alloc(dmat, size, align, 0, dma->dma_segs, 1, &dma->dma_nsegs, BUS_DMA_WAITOK); if (error) return error; error = bus_dmamem_map(dmat, dma->dma_segs, dma->dma_nsegs, size, &dma->dma_addr, BUS_DMA_WAITOK | flags); if (error) goto free; error = bus_dmamap_create(dmat, size, dma->dma_nsegs, size, 0, BUS_DMA_WAITOK, &dma->dma_map); if (error) goto unmap; error = bus_dmamap_load(dmat, dma->dma_map, dma->dma_addr, size, NULL, BUS_DMA_WAITOK); if (error) goto destroy; dma->dma_size = size; dma->dma_tag = dmat; memset(dma->dma_addr, 0, dma->dma_size); return 0; destroy: bus_dmamap_destroy(dmat, dma->dma_map); unmap: bus_dmamem_unmap(dmat, dma->dma_addr, dma->dma_size); free: bus_dmamem_free(dmat, dma->dma_segs, dma->dma_nsegs); return error; } static void * wiifb_mapreg(struct wiifb_softc *sc, bus_addr_t base, bus_size_t size) { bus_space_handle_t bsh; int error; error = bus_space_map(sc->sc_bst, base, size, BUS_SPACE_MAP_LINEAR, &bsh); if (error != 0) { panic("couldn't map 0x%x", base); } return bus_space_vaddr(sc->sc_bst, bsh); } static void wiifb_set_wgpipe(bus_addr_t base) { uint32_t value; if (base) { mtspr(IBM750CL_SPR_WPAR, base); } value = mfspr(IBM750CL_SPR_HID2); if (base) { value |= IBM750CL_SPR_HID2_WPE; } else { value &= ~IBM750CL_SPR_HID2_WPE; } mtspr(IBM750CL_SPR_HID2, value); } static void wiifb_ppcsync(void) { uint32_t value; value = mfspr(SPR_HID0); mtspr(SPR_HID0, value | 0x8); asm volatile("isync" ::: "memory"); asm volatile("sync" ::: "memory"); mtspr(SPR_HID0, value); } static void wiifb_gx_bp_load(struct wiifb_softc *sc, uint32_t data) { GX_STRICT_ORDER(sc->sc_wgpipe->u8 = 0x61); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = data); } static void wiifb_gx_cp_load(struct wiifb_softc *sc, uint8_t addr, uint32_t data) { GX_STRICT_ORDER(sc->sc_wgpipe->u8 = 0x08); GX_STRICT_ORDER(sc->sc_wgpipe->u8 = addr); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = data); } static void wiifb_gx_xf_load(struct wiifb_softc *sc, uint16_t addr, uint32_t data) { GX_STRICT_ORDER(sc->sc_wgpipe->u8 = 0x10); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = addr); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = data); } static void wiifb_gx_xf_load_multi(struct wiifb_softc *sc, uint16_t addr, uint16_t count, uint32_t *data) { uint16_t n; GX_STRICT_ORDER(sc->sc_wgpipe->u8 = 0x10); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = (((uint32_t)count - 1) << 16) | addr); for (n = 0; n < count; n++) { GX_STRICT_ORDER(sc->sc_wgpipe->u32 = data[n]); } } static void wiifb_gx_set_viewport(struct wiifb_softc *sc, u_int w, u_int h) { uint32_t data[6]; KASSERT(w == 640); KASSERT(h == 480); data[0] = 0x00000140; data[1] = 0xffffff10; data[2] = 0x00ffffff; data[3] = 0x00000296; data[4] = 0x00000246; data[5] = 0x00ffffff; wiifb_gx_xf_load_multi(sc, GX_XF_VIEWPORT_X0, __arraycount(data), data); } static void wiifb_gx_set_scissor(struct wiifb_softc *sc, u_int x, u_int y, u_int w, u_int h) { uint32_t xo = x + 0x156; uint32_t yo = y + 0x156; uint32_t wo = xo + w - 1; uint32_t ho = yo + h - 1; wiifb_gx_bp_load(sc, 0x20000000 | yo | (xo << 12)); wiifb_gx_bp_load(sc, 0x21000000 | ho | (wo << 12)); wiifb_gx_bp_load(sc, 0x59000000 | GX_XY(xo >> 1, yo >> 1)); } static void wiifb_gx_init(struct wiifb_softc *sc) { const uint32_t fifo_start = sc->sc_fifo.dma_segs[0].ds_addr; const uint32_t fifo_end = fifo_start + sc->sc_fifo.dma_size - 4; const uint32_t fifo_hiwat = GX_FIFO_HIWAT(sc->sc_fifo.dma_size); const uint32_t fifo_lowat = GX_FIFO_LOWAT(sc->sc_fifo.dma_size); /* Disable WGPIPE and unlink CP FIFO before changing settings. */ wiifb_set_wgpipe(0); CP_WRITE(sc, CP_CR, 0); CP_WRITE(sc, CP_CLEAR, CP_CLEAR_UNDERFLOW | CP_CLEAR_OVERFLOW); /* Setup GP FIFO */ CP_WRITE(sc, CP_FIFO_BASE_LO, LOWER_16_BITS(fifo_start)); CP_WRITE(sc, CP_FIFO_BASE_HI, UPPER_16_BITS(fifo_start)); CP_WRITE(sc, CP_FIFO_END_LO, LOWER_16_BITS(fifo_end)); CP_WRITE(sc, CP_FIFO_END_HI, UPPER_16_BITS(fifo_end)); CP_WRITE(sc, CP_FIFO_HIWAT_LO, LOWER_16_BITS(fifo_hiwat)); CP_WRITE(sc, CP_FIFO_HIWAT_HI, UPPER_16_BITS(fifo_hiwat)); CP_WRITE(sc, CP_FIFO_LOWAT_LO, LOWER_16_BITS(fifo_lowat)); CP_WRITE(sc, CP_FIFO_LOWAT_HI, UPPER_16_BITS(fifo_lowat)); CP_WRITE(sc, CP_FIFO_RW_DIST_LO, 0); CP_WRITE(sc, CP_FIFO_RW_DIST_HI, 0); CP_WRITE(sc, CP_FIFO_WRITE_PTR_LO, LOWER_16_BITS(fifo_start)); CP_WRITE(sc, CP_FIFO_WRITE_PTR_HI, UPPER_16_BITS(fifo_start)); CP_WRITE(sc, CP_FIFO_READ_PTR_LO, LOWER_16_BITS(fifo_start)); CP_WRITE(sc, CP_FIFO_READ_PTR_HI, UPPER_16_BITS(fifo_start)); wiifb_ppcsync(); /* Setup CPU FIFO */ PI_WRITE(sc, PI_FIFO_BASE_START, fifo_start); PI_WRITE(sc, PI_FIFO_BASE_END, fifo_end); PI_WRITE(sc, PI_FIFO_WRITE_PTR, fifo_start); wiifb_ppcsync(); /* Link CP/PE FIFO and enable GP FIFO */ CP_WRITE(sc, CP_CR, CP_CR_GP_LINK_ENABLE); CP_WRITE(sc, CP_CR, CP_READ(sc, CP_CR) | CP_CR_READ_ENABLE); /* Init pixel engine */ PE_WRITE(sc, PE_ZCONF, PE_ZCONF_UPD_ENABLE | PE_ZCONF_FUNC_ALWAYS | PE_ZCONF_COMP_ENABLE); PE_WRITE(sc, PE_ALPHA_CONF, PE_ALPHA_CONF_OP_SET | PE_ALPHA_CONF_SRC_1 | PE_ALPHA_CONF_DST_0 | PE_ALPHA_CONF_UPD_A | PE_ALPHA_CONF_UPD_C); PE_WRITE(sc, PE_ALPHA_DEST, 0); PE_WRITE(sc, PE_ALPHA_MODE, PE_ALPHA_MODE_ALWAYS); PE_WRITE(sc, PE_ALPHA_READ, PE_ALPHA_READ_UNK | PE_ALPHA_READ_FF); /* Enable WG pipe */ wiifb_set_wgpipe(WGPIPE_BASE); /* Sanitize command processor registers */ for (int n = 0; n < 8; n++) { wiifb_gx_cp_load(sc, 0x80 + n, 0x80000000); } wiifb_gx_cp_load(sc, 0x20, 0); /* Sanitize transform unit registers */ wiifb_gx_xf_load(sc, 0x1000, 0x3f); wiifb_gx_xf_load(sc, 0x1005, 0x01); wiifb_gx_xf_load(sc, 0x1012, 0x01); wiifb_gx_xf_load(sc, 0x1006, 0); /* Initialize blitting processor */ wiifb_gx_bp_load(sc, 0x00000001); wiifb_gx_bp_load(sc, 0x01666666); wiifb_gx_bp_load(sc, 0x02666666); wiifb_gx_bp_load(sc, 0x03666666); wiifb_gx_bp_load(sc, 0x04666666); wiifb_gx_bp_load(sc, 0x22000606); wiifb_gx_bp_load(sc, 0x23000000); wiifb_gx_bp_load(sc, 0x24000000); wiifb_gx_bp_load(sc, 0x42000000); wiifb_gx_bp_load(sc, 0x44000003); wiifb_gx_bp_load(sc, 0x43000000); wiifb_gx_bp_load(sc, 0x53595000); wiifb_gx_bp_load(sc, 0x54000015); wiifb_gx_bp_load(sc, 0x550003ff); wiifb_gx_bp_load(sc, 0x560003ff); wiifb_gx_bp_load(sc, 0x5800000f); wiifb_gx_bp_load(sc, 0x67000000); /* Set viewport and scissor parameters */ wiifb_gx_set_viewport(sc, WIIFB_RGB_WIDTH, WIIFB_RGB_HEIGHT); wiifb_gx_set_scissor(sc, 0, 0, WIIFB_RGB_WIDTH, WIIFB_RGB_HEIGHT); wiifb_gx_bp_load(sc, 0x4000001f); if (sc->sc_curmode->height == 574) { /* Scale 480 lines to PAL display height */ wiifb_gx_bp_load(sc, 0x4e000127); } /* Copy mode parameters */ wiifb_gx_bp_load(sc, 0x410004bc); /* Copy source */ wiifb_gx_bp_load(sc, 0x49000000 | GX_XY(0, 0)); wiifb_gx_bp_load(sc, 0x4a000000 | GX_XY(WIIFB_RGB_WIDTH - 1, WIIFB_RGB_HEIGHT - 1)); /* Copy destination */ wiifb_gx_bp_load(sc, 0x4d000000 | (WIIFB_RGB_WIDTH >> 4)); /* XFB address */ wiifb_gx_bp_load(sc, 0x4b000000 | (XFB_START >> 5)); /* Copy clear settings */ wiifb_gx_bp_load(sc, 0x4f000000); wiifb_gx_bp_load(sc, 0x50000000); wiifb_gx_bp_load(sc, 0x5100ffff); } static void wiifb_gx_flush(struct wiifb_softc *sc) { GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); GX_STRICT_ORDER(sc->sc_wgpipe->u32 = 0); wiifb_ppcsync(); } static void wiifb_accel_init(struct wiifb_softc *sc) { bus_size_t rgb_size; bus_size_t fifo_size; if (wiifb_vi_init(sc) != 0) { panic("couldn't init VI"); } rgb_size = WIIFB_RGB_WIDTH * WIIFB_RGB_HEIGHT; rgb_size = rgb_size * WIIFB_RGB_BPP / NBBY; rgb_size = roundup(rgb_size, PAGE_SIZE); if (wiifb_dma_alloc(sc, rgb_size, PAGE_SIZE, BUS_DMA_PREFETCHABLE, &sc->sc_rgb) != 0) { panic("couldn't alloc rgb fb"); } fifo_size = WIIFB_FIFO_SIZE; if (wiifb_dma_alloc(sc, fifo_size, GX_FIFO_ALIGN, BUS_DMA_NOCACHE, &sc->sc_fifo) != 0) { panic("couldn't alloc gx fifo"); } sc->sc_efb = wiifb_mapreg(sc, EFB_BASE, EFB_SIZE); sc->sc_cp = wiifb_mapreg(sc, CP_BASE, CP_SIZE); sc->sc_pe = wiifb_mapreg(sc, PE_BASE, PE_SIZE); sc->sc_pi = wiifb_mapreg(sc, PI_BASE, PI_SIZE); sc->sc_wgpipe = wiifb_mapreg(sc, WGPIPE_BASE, WGPIPE_SIZE); wiifb_gx_init(sc); }; static void wiifb_rgb_to_efb(struct wiifb_softc *sc) { u_int y; uint32_t *src = sc->sc_rgb.dma_addr; uint32_t *dst = __UNVOLATILE(sc->sc_efb); register_t hid0 = mfspr(SPR_HID0); KASSERT(src != NULL); KASSERT(dst != NULL); /* Disable store gathering while writing to EFB. */ mtspr(SPR_HID0, hid0 & ~HID0_SGE); for (y = 0; y < WIIFB_RGB_HEIGHT; y++) { memcpy(dst, src, WIIFB_RGB_WIDTH * 4); src += WIIFB_RGB_WIDTH; dst += 1024; } /* Re-enable store gathering. */ mtspr(SPR_HID0, hid0); } static void wiifb_efb_to_xfb(struct wiifb_softc *sc) { const uint32_t copy_mask = sc->sc_curmode->height == 574 ? 0x400 : 0x0; /* Execute copy to XFB */ wiifb_gx_bp_load(sc, 0x52004803 | copy_mask); } static void wiifb_gx_draw_done(struct wiifb_softc *sc, uint16_t token) { /* Draw done */ wiifb_gx_bp_load(sc, 0x45000002); /* Write tokens */ wiifb_gx_bp_load(sc, 0x48000000 | token); wiifb_gx_bp_load(sc, 0x47000000 | token); /* Flush WG pipe */ wiifb_gx_flush(sc); } static void wiifb_vi_refresh(void *priv) { struct wiifb_softc *sc = priv; wiifb_rgb_to_efb(sc); wiifb_efb_to_xfb(sc); wiifb_gx_draw_done(sc, sc->sc_token++); } static int wiifb_vi_intr(void *priv) { struct wiifb_softc *sc = priv; uint32_t di0; int ret = 0; di0 = RD4(sc, VI_DI0); WR4(sc, VI_DI0, RD4(sc, VI_DI0) & ~VI_DI_INT); WR4(sc, VI_DI1, RD4(sc, VI_DI1) & ~VI_DI_INT); WR4(sc, VI_DI2, RD4(sc, VI_DI2) & ~VI_DI_INT); WR4(sc, VI_DI3, RD4(sc, VI_DI3) & ~VI_DI_INT); if ((di0 & VI_DI_INT) != 0 && sc->sc_wsmode != WSDISPLAYIO_MODE_EMUL) { wiifb_vi_refresh(sc); ret = 1; } return ret; } static void wiifb_init(struct wiifb_softc *sc) { uint16_t dcr; uint16_t visel; /* Read current display format and interlaced settings. */ dcr = RD2(sc, VI_DCR); if ((dcr & VI_DCR_ENB) != 0) { sc->sc_format = __SHIFTOUT(dcr, VI_DCR_FMT); sc->sc_interlaced = (dcr & VI_DCR_NIN) == 0; } else { visel = RD2(sc, VI_VISEL); sc->sc_format = VI_DCR_FMT_NTSC; sc->sc_interlaced = (visel & VI_VISEL_COMPONENT_CABLE) == 0; } /* Reset video interface. */ WR2(sc, VI_DCR, VI_DCR_RST); delay(1000); /* Initialize video format and interlace selector. */ dcr = __SHIFTIN(sc->sc_format, VI_DCR_FMT) | (sc->sc_interlaced ? 0 : VI_DCR_NIN); WR2(sc, VI_DCR, dcr); } static void wiifb_set_mode(struct wiifb_softc *sc, uint8_t format, bool interlaced) { u_int modeidx; u_int strides, reads; modeidx = WIIFB_MODE_INDEX(format, interlaced); if (modeidx == WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 1)) { /* NTSC 480i Magic numbers from YAGCD. */ WR2(sc, VI_VTR, 0x0f06); WR4(sc, VI_HTR0, 0x476901AD); WR4(sc, VI_HTR1, 0x02EA5140); WR4(sc, VI_VTO, 0x00030018); WR4(sc, VI_VTE, 0x00020019); WR4(sc, VI_BBOI, 0x410C410C); WR4(sc, VI_BBEI, 0x40ED40ED); WR2(sc, VI_DPV, 0x0000); WR2(sc, VI_DPH, 0x0000); } else if (modeidx == WIIFB_MODE_INDEX(VI_DCR_FMT_NTSC, 0)) { /* NTSC 480p */ WR2(sc, VI_VTR, 0x1e0c); WR4(sc, VI_HTR0, 0x476901ad); WR4(sc, VI_HTR1, 0x030a4940); WR4(sc, VI_VTO, 0x00060030); WR4(sc, VI_VTE, 0x00060030); WR4(sc, VI_BBOI, 0x81d881d8); WR4(sc, VI_BBEI, 0x81d881d8); WR2(sc, VI_DPV, 0x0000); WR2(sc, VI_DPH, 0x0000); } else if (modeidx == WIIFB_MODE_INDEX(VI_DCR_FMT_PAL, 1)) { /* PAL 576i */ WR2(sc, VI_VTR, 0x11f5); WR4(sc, VI_HTR0, 0x4b6a01b0); WR4(sc, VI_HTR1, 0x02f85640); WR4(sc, VI_VTO, 0x00010023); WR4(sc, VI_VTE, 0x00000024); WR4(sc, VI_BBOI, 0x4d2b4d6d); WR4(sc, VI_BBEI, 0x4d8a4d4c); WR2(sc, VI_DPV, 0x013c); WR2(sc, VI_DPH, 0x0144); } else { /* * Display mode is not supported. Blink the slot LED to * indicate failure. */ wii_slot_led_blink(WIIFB_ERROR_BLINK_INTERVAL); } if (modeidx >= WIIFB_NMODES || wiifb_modes[modeidx].name == NULL) { panic("Unsupported format (0x%x) / interlaced (%d) settings", sc->sc_format, sc->sc_interlaced); } sc->sc_curmode = &wiifb_modes[modeidx]; /* Filter coefficient table, values from YAGCD. */ WR4(sc, VI_FCT0, 0x1ae771f0); WR4(sc, VI_FCT1, 0x0db4a574); WR4(sc, VI_FCT2, 0x00c1188e); WR4(sc, VI_FCT3, 0xc4c0cbe2); WR4(sc, VI_FCT4, 0xfcecdecf); WR4(sc, VI_FCT5, 0x13130f08); WR4(sc, VI_FCT6, 0x00080C0f); /* Unknown registers. */ WR4(sc, VI_UNKNOWN_68H, 0x00ff0000); WR2(sc, VI_UNKNOWN_76H, 0x00ff); WR4(sc, VI_UNKNOWN_78H, 0x00ff00ff); WR4(sc, VI_UNKNOWN_7CH, 0x00ff00ff); /* Picture configuration */ strides = (sc->sc_curmode->width * 2) / (interlaced ? 16 : 32); reads = (sc->sc_curmode->width * 2) / 32; WR2(sc, VI_PICCONF, __SHIFTIN(strides, VI_PICCONF_STRIDES) | __SHIFTIN(reads, VI_PICCONF_READS)); /* Horizontal scaler configuration */ if (interlaced) { WR2(sc, VI_HSR, __SHIFTIN(256, VI_HSR_STP)); } else { WR2(sc, VI_HSR, __SHIFTIN(244, VI_HSR_STP) | VI_HSR_HS_EN); } /* Video clock configuration */ WR2(sc, VI_VICLK, interlaced ? VI_VICLK_SEL_27MHZ : VI_VICLK_SEL_54MHZ); /* Horizontal scaling width */ WR2(sc, VI_HSCALINGW, sc->sc_curmode->width); /* Set framebuffer address */ wiifb_set_fb(sc); /* Finally, enable the framebuffer */ WR2(sc, VI_DCR, RD2(sc, VI_DCR) | VI_DCR_ENB); } static void wiifb_set_fb(struct wiifb_softc *sc) { uint32_t taddr = XFB_START; uint32_t baddr = taddr + (sc->sc_interlaced ? sc->sc_curmode->width * 2 : 0); WR4(sc, VI_TFBL, VI_TFBL_PGOFF | __SHIFTIN((taddr >> 5), VI_TFBL_FBB) | __SHIFTIN((taddr / 2) & 0xf, VI_TFBL_XOF)); WR4(sc, VI_TFBR, 0); WR4(sc, VI_BFBL, VI_BFBL_PGOFF | __SHIFTIN((baddr >> 5), VI_BFBL_FBB) | __SHIFTIN((baddr / 2) & 0xf, VI_BFBL_XOF)); WR4(sc, VI_BFBR, 0); } static int wiifb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l) { struct wiifb_softc *sc = v; struct wsdisplayio_bus_id *busid; struct wsdisplayio_fbinfo *fbi; u_int video; u_int wsmode; switch (cmd) { case WSDISPLAYIO_GTYPE: *(u_int *)data = WSDISPLAY_TYPE_GENFB; return 0; case WSDISPLAYIO_GET_BUSID: busid = data; busid->bus_type = WSDISPLAYIO_BUS_SOC; return 0; case WSDISPLAYIO_GET_FBINFO: fbi = data; /* * rasops info does not match the pixel encoding due to our * devcmap, so fill out fbinfo manually instead of relying * on wsdisplayio_get_fbinfo. */ fbi->fbi_fboffset = 0; fbi->fbi_width = WIIFB_RGB_WIDTH; fbi->fbi_height = WIIFB_RGB_HEIGHT; fbi->fbi_bitsperpixel = WIIFB_RGB_BPP; fbi->fbi_stride = fbi->fbi_width * fbi->fbi_bitsperpixel / 8; fbi->fbi_fbsize = fbi->fbi_height * fbi->fbi_stride; fbi->fbi_pixeltype = WSFB_RGB; fbi->fbi_flags = WSFB_VRAM_IS_RAM; return 0; case WSDISPLAYIO_SVIDEO: video = *(u_int *)data; switch (video) { case WSDISPLAYIO_VIDEO_OFF: out32(HW_VIDIM, __SHIFTIN(7, VIDIM_Y) | __SHIFTIN(7, VIDIM_C) | VIDIM_E); return 0; case WSDISPLAYIO_VIDEO_ON: out32(HW_VIDIM, 0); return 0; default: return EINVAL; } case WSDISPLAYIO_SMODE: wsmode = *(u_int *)data; if (wsmode != WSDISPLAYIO_MODE_EMUL) { /* Blank the RGB FB when leaving text mode */ memset(sc->sc_rgb.dma_addr, 0, sc->sc_rgb.dma_size); } if (sc->sc_wsmode != wsmode) { sc->sc_wsmode = wsmode; if (wsmode == WSDISPLAYIO_MODE_EMUL) { wiifb_clear_xfb(sc); } } return EPASSTHROUGH; } return EPASSTHROUGH; } static paddr_t wiifb_mmap(void *v, void *vs, off_t off, int prot) { struct wiifb_softc *sc = v; if (sc->sc_wsmode == WSDISPLAYIO_MODE_EMUL || sc->sc_efb == NULL) { return -1; } if (off < 0 || off >= sc->sc_rgb.dma_size) { return -1; } return bus_dmamem_mmap(sc->sc_rgb.dma_tag, sc->sc_rgb.dma_segs, sc->sc_rgb.dma_nsegs, off, prot, BUS_DMA_PREFETCHABLE); }