/* $NetBSD: nextdisplay.c,v 1.31 2024/02/02 15:59:30 tsutsui Exp $ */

/*
 * Copyright (c) 1998 Matt DeBergalis
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt DeBergalis
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: nextdisplay.c,v 1.31 2024/02/02 15:59:30 tsutsui Exp $");

#include <sys/cdefs.h>			/* RCS ID & Copyright macro defns */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/kmem.h>

#include <machine/bus.h>
#include <machine/cpu.h>

#include <next68k/next68k/nextrom.h>
#include <next68k/next68k/isr.h>

#include <next68k/dev/intiovar.h>
#include <next68k/dev/nextdisplayvar.h>
#include <dev/wscons/wsconsio.h>

#include <dev/rcons/raster.h>
#include <dev/wscons/wscons_raster.h>
#include <dev/wscons/wsdisplayvar.h>

extern int turbo;

int nextdisplay_match(device_t, cfdata_t, void *);
void nextdisplay_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(nextdisplay, sizeof(struct nextdisplay_softc),
    nextdisplay_match, nextdisplay_attach, NULL, NULL);

const struct wsdisplay_emulops nextdisplay_mono_emulops = {
	rcons_cursor,
	rcons_mapchar,
	rcons_putchar,
	rcons_copycols,
	rcons_erasecols,
	rcons_copyrows,
	rcons_eraserows,
	rcons_allocattr
};

struct wsscreen_descr nextdisplay_mono = {
	"mono",
	0, 0, /* will be filled in -- XXX shouldn't, it's global */
	&nextdisplay_mono_emulops,
	0, 0
};

struct wsscreen_descr nextdisplay_color = {
        "color",
        0, 0, /* again, filled in */
        &nextdisplay_mono_emulops,
        0, 0
};

const struct wsscreen_descr *_nextdisplay_scrlist_mono[] = {
	&nextdisplay_mono,
};

const struct wsscreen_descr *_nextdisplay_scrlist_color[] = {
        &nextdisplay_color,
};

const struct wsscreen_list nextdisplay_screenlist_mono = {
	sizeof(_nextdisplay_scrlist_mono) / sizeof(struct wsscreen_descr *),
	_nextdisplay_scrlist_mono
};

const struct wsscreen_list nextdisplay_screenlist_color = {
	sizeof(_nextdisplay_scrlist_color) / sizeof(struct wsscreen_descr *),
	_nextdisplay_scrlist_color
};

static int	nextdisplay_ioctl(void *, void *, u_long, void *, int,
		    struct lwp *);
static paddr_t	nextdisplay_mmap(void *, void *, off_t, int);
static int	nextdisplay_alloc_screen(void *, const struct wsscreen_descr *,
		void **, int *, int *, long *);
static void	nextdisplay_free_screen(void *, void *);
static int	nextdisplay_show_screen(void *, void *, int,
					void (*) (void *, int, int), void *);
static int	nextdisplay_load_font(void *, void *, struct wsdisplay_font *);

const struct wsdisplay_accessops nextdisplay_accessops = {
	nextdisplay_ioctl,
	nextdisplay_mmap,
	nextdisplay_alloc_screen,
	nextdisplay_free_screen,
	nextdisplay_show_screen,
	nextdisplay_load_font
};

void nextdisplay_init(struct nextdisplay_config *, int);
int nextdisplay_intr(void *);

vaddr_t nextdisplay_consaddr;
static int nextdisplay_is_console(vaddr_t);

static struct nextdisplay_config nextdisplay_console_dc;

static int
nextdisplay_is_console(vaddr_t addr)
{
	return nextdisplay_console_dc.isconsole &&
	    addr == nextdisplay_consaddr;
}

int
nextdisplay_match(device_t parent, cfdata_t match, void *aux)
{
	if (rom_machine_type == NeXT_WARP9 ||
	    rom_machine_type == NeXT_X15 ||
	    rom_machine_type == NeXT_WARP9C ||
	    rom_machine_type == NeXT_TURBO_MONO ||
	    rom_machine_type == NeXT_TURBO_COLOR ||
	    rom_machine_type == NeXT_CUBE_TURBO)
		return 1;
	else
		return 0;
}

void
nextdisplay_init(struct nextdisplay_config *dc, int color)
{
	struct raster *rap;
	struct rcons *rcp;
	int i;

	/* printf("in nextdisplay_init\n"); */

	dc->dc_vaddr = fbbase;
	dc->dc_paddr = fbbasepa;
	dc->dc_size = color ? NEXT_P_C16_VIDEOSIZE : NEXT_P_VIDEOSIZE;

	dc->dc_wid = 1120;
	dc->dc_ht = 832;
	dc->dc_depth = color ? 16 : 2;
	dc->dc_cmsize = color ? 256 : 4;
	dc->dc_rowbytes = (turbo ? 1120 : 1152) * dc->dc_depth / 8;

	dc->dc_videobase = dc->dc_vaddr;

#if 0
	printf("intiobase at: %08x\n", intiobase);
	printf("intiolimit at: %08x\n", intiolimit);
	printf("videobase at: %08x\n", fbbase);
	printf("videolimit at: %08x\n", fblimit);

	printf("virtual fb at: %08x\n", dc->dc_vaddr);
	printf("physical fb at: %08x\n", dc->dc_paddr);
	printf("fb size: %08x\n", dc->dc_size);

	printf("dc_wid: %08x\n", dc->dc_wid);
	printf("dc_ht: %08x\n", dc->dc_ht);
	printf("dc_depth: %08x\n", dc->dc_depth);
	printf("dc_rowbytes: %08x\n", dc->dc_rowbytes);
	printf("dc_videobase: %08x\n", dc->dc_videobase);
#endif

	/* clear the screen */
	for (i = 0; i < dc->dc_ht * dc->dc_rowbytes; i += sizeof(uint32_t))
		*(uint32_t *)(dc->dc_videobase + i) =
		    color ? 0x0 : 0xffffffff;

	rap = &dc->dc_raster;
	rap->width = dc->dc_wid;
	rap->height = dc->dc_ht;
	rap->depth = color ? 16 : 2;
	rap->linelongs = dc->dc_rowbytes / sizeof(uint32_t);
	rap->pixels = (uint32_t *)dc->dc_videobase;

	/* initialize the raster console blitter */
	rcp = &dc->dc_rcons;
	rcp->rc_sp = rap;
	rcp->rc_crow = rcp->rc_ccol = -1;
	rcp->rc_crowp = &rcp->rc_crow;
	rcp->rc_ccolp = &rcp->rc_ccol;
	rcons_init(rcp, 34, 80);

	if (color) {
		nextdisplay_color.nrows = dc->dc_rcons.rc_maxrow;
		nextdisplay_color.ncols = dc->dc_rcons.rc_maxcol;
	} else {
		nextdisplay_mono.nrows = dc->dc_rcons.rc_maxrow;
		nextdisplay_mono.ncols = dc->dc_rcons.rc_maxcol;
	}
}

void
nextdisplay_attach(device_t parent, device_t self, void *aux)
{
	struct nextdisplay_softc *sc = device_private(self);
	struct wsemuldisplaydev_attach_args waa;
	int isconsole;
	vaddr_t addr;

	sc->sc_dev = self;

	addr = fbbase;

	isconsole = nextdisplay_is_console(addr);

	if (isconsole) {
		sc->sc_dc = &nextdisplay_console_dc;
		sc->nscreens = 1;
	} else {
		sc->sc_dc = kmem_alloc(sizeof(struct nextdisplay_config),
		    KM_SLEEP);
		nextdisplay_init(sc->sc_dc, iscolor);
	}

	printf(": %d x %d, %dbpp\n", sc->sc_dc->dc_wid, sc->sc_dc->dc_ht,
	    sc->sc_dc->dc_depth);

	if (iscolor && !turbo) {
#if 0
		uint8_t x;

		x = *(volatile uint8_t *)IIOV(NEXT_P_C16_CMD_REG);
		aprint_debug_dev(sc->sc_dev, "cmd=%02x\n", x);
#endif
		*(volatile uint8_t *)IIOV(NEXT_P_C16_CMD_REG) = 0x05;
		isrlink_autovec(nextdisplay_intr, sc,
		    NEXT_I_IPL(NEXT_I_C16_VIDEO), 1, NULL);
		INTR_ENABLE(NEXT_I_C16_VIDEO);
	}

	sc->sc_mode = WSDISPLAYIO_MODE_EMUL;

	/* initialize the raster */
	waa.console = isconsole;
	waa.scrdata = iscolor ?
	    &nextdisplay_screenlist_color : &nextdisplay_screenlist_mono;
	waa.accessops = &nextdisplay_accessops;
	waa.accesscookie = sc;
#if 0
	printf("nextdisplay: access cookie is %p\n", sc);
#endif
	config_found(self, &waa, wsemuldisplaydevprint, CFARGS_NONE);
}

int
nextdisplay_intr(void *arg)
{
#if 0
	uint8_t x;
#endif

	if (!INTR_OCCURRED(NEXT_I_C16_VIDEO))
		return 0;
#if 0
	x = *(volatile uint8_t *)IIOV(NEXT_P_C16_CMD_REG);
	printf("I%02x", x);
#endif
	*(volatile uint8_t *)IIOV(NEXT_P_C16_CMD_REG) = 0x05;
	return 1;
}

int
nextdisplay_ioctl(void *v, void *vs, u_long cmd, void *data, int flag,
    struct lwp *l)
{
	struct nextdisplay_softc *sc = v;
	struct nextdisplay_config *dc = sc->sc_dc;
	int new_mode;

	switch (cmd) {
	case WSDISPLAYIO_GTYPE:
		*(int *)data = dc->dc_type;
		return 0;

	case WSDISPLAYIO_SCURSOR:
		printf("nextdisplay_ioctl: wsdisplayio_scursor\n");
		return EPASSTHROUGH;

	case WSDISPLAYIO_SCURPOS:
		printf("nextdisplay_ioctl: wsdisplayio_scurpos\n");
		return EPASSTHROUGH;

	case WSDISPLAYIO_GINFO:
#define wsd_fbip ((struct wsdisplay_fbinfo *)data)
		wsd_fbip->height = dc->dc_ht;
		wsd_fbip->width = dc->dc_wid;
		wsd_fbip->depth = dc->dc_depth;
		wsd_fbip->cmsize = dc->dc_cmsize;
#undef wsd_fbip
                return 0;

	case WSDISPLAYIO_LINEBYTES:
		*(u_int *)data = dc->dc_rowbytes;
		return 0;

	case WSDISPLAYIO_SMODE:
		new_mode = *(int *)data;
		if (new_mode != sc->sc_mode) {
			sc->sc_mode = new_mode;
		}
		return 0;

	case WSDISPLAYIO_GETCMAP:
	case WSDISPLAYIO_PUTCMAP:
	case WSDISPLAYIO_GVIDEO:
	case WSDISPLAYIO_SVIDEO:
	case WSDISPLAYIO_GCURPOS:
	case WSDISPLAYIO_GCURMAX:
	case WSDISPLAYIO_GCURSOR:
		printf("nextdisplay_ioctl: listed but unsupported ioctl\n");
		return EPASSTHROUGH;
	}

	return EPASSTHROUGH;
}

static paddr_t
nextdisplay_mmap(void *v, void *vs, off_t offset, int prot)
{
	struct nextdisplay_softc *sc = v;
	struct nextdisplay_config *dc = sc->sc_dc;
	paddr_t cookie = -1;

	switch (sc->sc_mode) {
	case WSDISPLAYIO_MODE_DUMBFB:
		if (offset >= 0 && offset < dc->dc_size)
			cookie = m68k_btop(dc->dc_paddr + offset);
		break;
	}

	return cookie;
}

int
nextdisplay_alloc_screen(void *v, const struct wsscreen_descr *type,
    void **cookiep, int *curxp, int *curyp, long *defattrp)
{
	struct nextdisplay_softc *sc = v;
	long defattr;

	/* only allow one screen */
	if (sc->nscreens > 0)
		return ENOMEM;

	*cookiep = &sc->sc_dc->dc_rcons; /* one and only for now */
	*curxp = 0;
	*curyp = 0;
	rcons_allocattr(&sc->sc_dc->dc_rcons, 0, 0,
	    (strcmp(type->name, "color") == 0) ? 0 : WSATTR_REVERSE, &defattr);
	*defattrp = defattr;
	sc->nscreens++;
#if 0
	printf("nextdisplay: allocating screen\n");
#endif
	return 0;
}

void
nextdisplay_free_screen(void *v, void *cookie)
{
	struct nextdisplay_softc *sc = v;

	if (sc->sc_dc == &nextdisplay_console_dc)
		panic("nextdisplay_free_screen: console");

	sc->nscreens--;
}

int
nextdisplay_show_screen(void *v, void *cookie, int waitok,
    void (*cb)(void *, int, int), void *cbarg)
{

	return 0;
}

static int
nextdisplay_load_font(void *v, void *cookie, struct wsdisplay_font *font)
{

	return EPASSTHROUGH;
}

int
nextdisplay_cnattach(void)
{
	struct nextdisplay_config *dc = &nextdisplay_console_dc;
	long defattr;

	/* set up the display */
	nextdisplay_init(&nextdisplay_console_dc, iscolor);
	nextdisplay_consaddr = nextdisplay_console_dc.dc_vaddr;

	rcons_allocattr(&dc->dc_rcons, 0, 0,
	    iscolor ? 0 : WSATTR_REVERSE, &defattr);

	wsdisplay_cnattach(iscolor ? &nextdisplay_color : &nextdisplay_mono,
	    &dc->dc_rcons, 0, 0, defattr);

	dc->isconsole = 1;
	return 0;
}
