/* $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;
}