1/* $NetBSD: wiiufb.c,v 1.3 2026/01/10 23:55:24 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2025 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: wiiufb.c,v 1.3 2026/01/10 23:55:24 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#include <machine/wiiu.h>
39
40#include <dev/wscons/wsconsio.h>
41#include <dev/wscons/wsdisplayvar.h>
42#include <dev/rasops/rasops.h>
43#include <dev/wsfont/wsfont.h>
44#include <dev/wscons/wsdisplay_vconsvar.h>
45#include <dev/wsfb/genfbvar.h>
46
47#include "mainbus.h"
48
49/* Set to true to output to Gamepad instead of TV */
50static bool	wiiufb_drc;
51
52#define WIIUFB_BASE		(wiiufb_drc ? WIIU_GFX_DRC_BASE : \
53					      WIIU_GFX_TV_BASE)
54#define WIIUFB_WIDTH		(wiiufb_drc ? 854 : 1280)
55#define WIIUFB_HEIGHT		(wiiufb_drc ? 480 : 720)
56#define WIIUFB_BPP		32
57#define WIIUFB_STRIDE		((wiiufb_drc ? 896 : 1280) * WIIUFB_BPP / NBBY)
58#define WIIUFB_SIZE		(WIIUFB_STRIDE * WIIUFB_HEIGHT)
59
60#define D1GRPH_SWAP_CNTL		0x610c
61#define D2GRPH_SWAP_CNTL		0x690c
62#define  DGRPH_SWAP_ENDIAN_SWAP		__BITS(1, 0)
63#define  DGRPH_SWAP_ENDIAN_SWAP_8IN32	__SHIFTIN(2, DGRPH_SWAP_ENDIAN_SWAP)
64#define D1CRTC_BLANK_CONTROL		0x6084
65#define D2CRTC_BLANK_CONTROL		0x6884
66#define  DCRTC_BLANK_DATA_EN		__BIT(8)
67
68struct wiiufb_softc {
69	struct genfb_softc	sc_gen;
70
71	bus_space_tag_t		sc_bst;
72	bus_space_handle_t	sc_bsh;
73
74	uint32_t		sc_blank_ctrl;
75};
76
77static int	wiiufb_match(device_t, cfdata_t, void *);
78static void	wiiufb_attach(device_t, device_t, void *);
79
80static bool	wiiufb_shutdown(device_t, int);
81static int	wiiufb_ioctl(void *, void *, u_long, void *, int, lwp_t *);
82static paddr_t	wiiufb_mmap(void *, void *, off_t, int);
83
84static void	wiiufb_gpu_write(uint16_t, uint32_t);
85
86void		wiiufb_consinit(void);
87
88static struct genfb_ops wiiufb_ops = {
89	.genfb_ioctl = wiiufb_ioctl,
90	.genfb_mmap = wiiufb_mmap,
91};
92
93struct vcons_screen wiiufb_console_screen;
94static struct wsscreen_descr wiiufb_stdscreen = {
95	"std",
96	0, 0,
97	0,
98	0, 0,
99	0,
100	NULL
101};
102
103CFATTACH_DECL_NEW(wiiufb, sizeof(struct wiiufb_softc),
104	wiiufb_match, wiiufb_attach, NULL, NULL);
105
106static int
107wiiufb_match(device_t parent, cfdata_t cf, void *aux)
108{
109	struct mainbus_attach_args *maa = aux;
110
111	return wiiu_native && strcmp(maa->maa_name, "genfb") == 0;
112}
113
114static void
115wiiufb_attach(device_t parent, device_t self, void *aux)
116{
117	struct wiiufb_softc *sc = device_private(self);
118	prop_dictionary_t dict = device_properties(self);
119	struct mainbus_attach_args *maa = aux;
120	void *bits;
121
122	sc->sc_gen.sc_dev = self;
123	sc->sc_bst = maa->maa_bst;
124
125	/*
126	 * powerpc bus_space_map will use the BAT mapping if present,
127	 * ignoring the map flags passed in. Just use mapiodev directly
128	 * to ensure that the FB is mapped by the kernel pmap.
129	 */
130	bits = mapiodev(WIIUFB_BASE, WIIUFB_SIZE, true);
131
132	prop_dictionary_set_uint32(dict, "width", WIIUFB_WIDTH);
133	prop_dictionary_set_uint32(dict, "height", WIIUFB_HEIGHT);
134	prop_dictionary_set_uint8(dict, "depth", WIIUFB_BPP);
135	prop_dictionary_set_uint16(dict, "linebytes", WIIUFB_STRIDE);
136	prop_dictionary_set_uint32(dict, "address", WIIUFB_BASE);
137	prop_dictionary_set_uint32(dict, "virtual_address", (uintptr_t)bits);
138
139	genfb_init(&sc->sc_gen);
140
141	aprint_naive("\n");
142	aprint_normal(": Wii U %s framebuffer (%ux%u %u-bpp @ 0x%08x)\n",
143	    wiiufb_drc ? "DRC" : "TV", WIIUFB_WIDTH, WIIUFB_HEIGHT,
144	    WIIUFB_BPP, WIIUFB_BASE);
145
146	pmf_device_register1(self, NULL, NULL, wiiufb_shutdown);
147
148	genfb_cnattach();
149	prop_dictionary_set_bool(dict, "is_console", true);
150	genfb_attach(&sc->sc_gen, &wiiufb_ops);
151
152	sc->sc_blank_ctrl = wiiufb_drc ?
153	    D2CRTC_BLANK_CONTROL : D1CRTC_BLANK_CONTROL;
154}
155
156static bool
157wiiufb_shutdown(device_t self, int flags)
158{
159	genfb_enable_polling(self);
160	return true;
161}
162
163static int
164wiiufb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, lwp_t *l)
165{
166	struct wiiufb_softc *sc = v;
167	struct wsdisplayio_bus_id *busid;
168	struct wsdisplayio_fbinfo *fbi;
169	struct rasops_info *ri;
170	u_int video;
171	int error;
172
173	switch (cmd) {
174	case WSDISPLAYIO_GTYPE:
175		*(u_int *)data = WSDISPLAY_TYPE_GENFB;
176		return 0;
177	case WSDISPLAYIO_GET_BUSID:
178		busid = data;
179		busid->bus_type = WSDISPLAYIO_BUS_SOC;
180		return 0;
181	case WSDISPLAYIO_GET_FBINFO:
182		fbi = data;
183		ri = &sc->sc_gen.vd.active->scr_ri;
184		error = wsdisplayio_get_fbinfo(ri, fbi);
185		if (error == 0) {
186			fbi->fbi_flags |= WSFB_VRAM_IS_RAM;
187		}
188		return error;
189	case WSDISPLAYIO_SVIDEO:
190		video = *(u_int *)data;
191		switch (video) {
192		case WSDISPLAYIO_VIDEO_OFF:
193			wiiufb_gpu_write(sc->sc_blank_ctrl, DCRTC_BLANK_DATA_EN);
194			break;
195		case WSDISPLAYIO_VIDEO_ON:
196			wiiufb_gpu_write(sc->sc_blank_ctrl, 0);
197			break;
198		default:
199			return EINVAL;
200		}
201		return 0;
202	}
203
204	return EPASSTHROUGH;
205}
206
207static paddr_t
208wiiufb_mmap(void *v, void *vs, off_t off, int prot)
209{
210	struct wiiufb_softc *sc = v;
211
212	if (off < 0 || off >= WIIUFB_SIZE) {
213		return -1;
214	}
215
216	return bus_space_mmap(sc->sc_bst, WIIUFB_BASE, off, prot,
217	    BUS_SPACE_MAP_LINEAR | BUS_DMA_PREFETCHABLE);
218}
219
220static void
221wiiufb_gpu_write(uint16_t reg, uint32_t data)
222{
223	out32(LT_GPUINDADDR, LT_GPUINDADDR_REGSPACE_GPU | reg);
224	out32(LT_GPUINDDATA, data);
225	in32(LT_GPUINDDATA);
226}
227
228void
229wiiufb_consinit(void)
230{
231	extern char wii_cmdline[];
232	struct rasops_info *ri = &wiiufb_console_screen.scr_ri;
233	long defattr;
234	void *bits;
235	const char *cmdline = wii_cmdline;
236
237	memset(&wiiufb_console_screen, 0, sizeof(wiiufb_console_screen));
238
239	while (*cmdline != '\0') {
240		if (strcmp(cmdline, "video=drc") == 0) {
241			/* Output to the gamepad instead of TV. */
242			wiiufb_drc = true;
243			break;
244		}
245		cmdline += strlen(cmdline) + 1;
246	}
247
248	/* Blank the CRTC we are not using. */
249	if (wiiufb_drc) {
250		wiiufb_gpu_write(D1CRTC_BLANK_CONTROL, DCRTC_BLANK_DATA_EN);
251	} else {
252		wiiufb_gpu_write(D2CRTC_BLANK_CONTROL, DCRTC_BLANK_DATA_EN);
253	}
254
255	/* Ensure that the ARGB8888 framebuffer is in a sane state. */
256	wiiufb_gpu_write(D1GRPH_SWAP_CNTL, DGRPH_SWAP_ENDIAN_SWAP_8IN32);
257	wiiufb_gpu_write(D2GRPH_SWAP_CNTL, DGRPH_SWAP_ENDIAN_SWAP_8IN32);
258
259	/*
260	 * Need to use the BAT mapping here as pmap isn't initialized yet.
261	 *
262	 * Unfortunately, we have a single large (256MB) BAT mapping to cover
263	 * both conventional memory and the framebuffer in MEM1, which means
264	 * the early FB is mapped cacheable. Better than nothing (it's
265	 * useful for debugging) and it's only like this until wiiufb is
266	 * attached later on.
267	 *
268	 * This could be enhanced in the future to hook in to rasops and
269	 * insert proper cache maintenance operations. Just don't flush the
270	 * whole framebuffer every time something changes, it will be very
271	 * slow.
272	 */
273	bits = (void *)WIIUFB_BASE;
274
275	wsfont_init();
276
277	ri->ri_width = WIIUFB_WIDTH;
278	ri->ri_height = WIIUFB_HEIGHT;
279	ri->ri_depth = WIIUFB_BPP;
280	ri->ri_stride = WIIUFB_STRIDE;
281	ri->ri_bits = bits;
282	ri->ri_flg = RI_NO_AUTO | RI_CLEAR | RI_FULLCLEAR | RI_CENTER;
283	rasops_init(ri, ri->ri_height / 8, ri->ri_width / 8);
284
285	ri->ri_caps = WSSCREEN_WSCOLORS;
286	rasops_reconfig(ri, ri->ri_height / ri->ri_font->fontheight,
287	    ri->ri_width / ri->ri_font->fontwidth);
288
289	wiiufb_stdscreen.nrows = ri->ri_rows;
290	wiiufb_stdscreen.ncols = ri->ri_cols;
291	wiiufb_stdscreen.textops = &ri->ri_ops;
292	wiiufb_stdscreen.capabilities = ri->ri_caps;
293
294	ri->ri_ops.allocattr(ri, 0, 0, 0, &defattr);
295
296	wsdisplay_preattach(&wiiufb_stdscreen, ri, 0, 0, defattr);
297}
298