/*
**  linux/kernel/chr_drv/console.c
**
**  Copyright (C) 1993 Hamish Macdonald and Greg Harp
**
**  Last Modified 2/22/93 by Greg Harp
**
** This file is subject to the terms and conditions of the GNU General Public
** License.  See the file README.legal in the main directory of this archive
** for more details.
*/

#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>

#include "vt_kern.h"

#include <machine/custom.h>

/*
** Macros
*/

#define COP_LIST_BASE	0x0400
#define BITPL_BASE	0x1000
#define CURSOR_BASE	0xc000

#define HIRES		0x8000
#define ONE_BIT_PLANE	0x1000
#define COMPOSITE	0x0200
#define LACE		0x0004

#define DEFAULT_MODE	(HIRES | ONE_BIT_PLANE | COMPOSITE | LACE)

#define SCR_MAX_WIDTH	704
#define SCR_MAX_HEIGHT	480
#define SCR_WIDTH	640
#define SCR_HEIGHT	400

#define CUSTOM_OFS(fld) ((long)&((struct Custom*)0)->fld)
#define COP_LIST_ENTS	30

#define MEM_REQ 	((SCR_WIDTH/8) * SCR_HEIGHT)
#define COP_MEM_REQ	(COPENTS*4*2)

#define DIWSTRT_V	(0x16 + (SCR_MAX_HEIGHT - SCR_HEIGHT)/4)
#define DIWSTRT_H	(0x67 + (SCR_MAX_WIDTH - SCR_WIDTH)/4)
#define DIWSTOP_V	((0x106 - (SCR_MAX_HEIGHT - SCR_HEIGHT)/4) & 0xff)
#define DIWSTOP_H	(0xc7 - (SCR_MAX_WIDTH - SCR_WIDTH)/4)

#define DIWSTRT 	((DIWSTRT_V << 8) + DIWSTRT_H)
#define DIWSTOP 	((DIWSTOP_V << 8) + DIWSTOP_H)

#define DDFSTRT 	(((ushort)DIWSTRT_H >> 1) - 4)
#define DDFSTOP 	((ushort)DDFSTRT + (4 * (SCR_WIDTH/16 - 2)))

#define FG_COLOR	0x000 /* black */
#define BG_COLOR	0xaaa /* lt. grey */
#define CURSOR_COLOR	0x00a /* med. blue */

/*
** Externals
*/

extern unsigned char
    fontdata[];

extern int
    fontheight,
    fontwidth;

extern char
    fontname[];

extern void register_console (void (*proc)(const char *));

/*
** Global Variables
*/

unsigned long
    video_num_columns,		    /* Number of text columns */
    video_num_lines;		    /* Number of text lines   */

/*
** Local Variables
*/

static struct
    {
    ushort
	scr_height,		    /* screen dimensions */
	scr_width,

	diwstrt,		    /* display window control */
	diwstop,

	ddfstrt,		    /* data fetch control */
	ddfstop,

	mode,			    /* display mode */

	fgcol,			    /* text colors */
	bgcol,
	crsrcol,

	row_max,		    /* highest-numbered row/column */
	col_max;

    ushort
	*bitplane,		    /* pointer to display bitplane */

	*coplist1,		    /* copper list pointers */
	*coplist2;
    } disp;

static ushort
    *cursor,
    *dummy;

static ushort
    cursor_data[] =
	{
	0x2c81,0x2d00,
	0xf000,0x0000,
	0x0000,0x0000
	};

/*
** Prototypes
*/

void
    conputc(int c);

/*
** Functions
*/

void
    initcop(ushort *cop, ushort *othercop, int shf)
    {
    int
	i;

    long
	scrmem = (long)disp.bitplane;

    if (shf)
	scrmem += video_num_columns;

    /* 1-2: Store bitplane address in BPL0PTx */
    *cop++ = CUSTOM_OFS(bplpt[0]);
    *cop++ = (long)scrmem >> 16;
    *cop++ = CUSTOM_OFS(bplpt[0]) + 2;
    *cop++ = (long)scrmem;

    /* 3-4: Modify Copper list pointer to use other list next time */
    *cop++ = CUSTOM_OFS(cop1lc);
    *cop++ = (long)othercop >> 16;
    *cop++ = CUSTOM_OFS(cop1lc) + 2;
    *cop++ = (long)othercop;

    /* 5-6: Point Sprite 0 at cursor sprite */
    *cop++ = CUSTOM_OFS(sprpt[0]);
    *cop++ = (ushort)((long)cursor >> 16);
    *cop++ = CUSTOM_OFS(sprpt[0]) + 2;
    *cop++ = (ushort)((long)cursor & 0x0000ffff);

    /* 7-20: Point Sprites 1-7 at dummy sprite */
    for (i=1; i<8; i++)
	{
	*cop++ = CUSTOM_OFS(sprpt[i]);
	*cop++ = (ushort)((long)dummy >> 16);
	*cop++ = CUSTOM_OFS(sprpt[i]) + 2;
	*cop++ = (ushort)((long)dummy & 0x0000ffff);
	}

    /* 21: End of Copper list */
    *(int*)cop = 0xfffffffe;
    }

long
    con_init(long mem_start)
    {
    ushort
	*temp;
    int
	i;
    void
	console_print(const char * b);

    static int
	set_defaults = 0;

    volatile struct Custom
	*custom = (struct Custom *)0xdff000;

    if (!set_defaults)
	{
	/* set up the display defaults */
	disp.scr_height = SCR_HEIGHT;
	disp.scr_width = SCR_WIDTH;

	disp.diwstrt = DIWSTRT;
	disp.diwstop = DIWSTOP;

	disp.ddfstrt = DDFSTRT;
	disp.ddfstop = DDFSTOP;

	disp.mode = DEFAULT_MODE;

	disp.fgcol = FG_COLOR;
	disp.bgcol = BG_COLOR;
	disp.crsrcol = CURSOR_COLOR;

	/* flag that we've already done this */
	set_defaults = 1;
	}

    video_num_columns = SCR_WIDTH / fontwidth;
    video_num_lines = SCR_HEIGHT / fontheight;
    disp.col_max = video_num_columns - 1;
    disp.row_max = video_num_lines - 1;

    /* locate the copper list */
    disp.coplist1 = (ushort *)COP_LIST_BASE;
    disp.coplist2 = (ushort *)(COP_LIST_BASE + COP_LIST_ENTS * 4);

    /* locate the bitplane */
    disp.bitplane = (ushort *)BITPL_BASE;

    /* locate the sprite data */
    cursor = (ushort *)CURSOR_BASE; /* bitplane may be up to 704x480 */
    dummy = (short *)(CURSOR_BASE + 12);

    /* copy the sprite data into Chip mem */
    for (i=0; i<6; i++)
	cursor[i] = cursor_data[i];

    /* set dummy sprite data to a blank sprite */
    for (i=0; i<6; i++)
	dummy[i] = 0x0000;

    /* clear display memory */
    for (temp=disp.bitplane; temp<(disp.bitplane + MEM_REQ/2); temp++)
	*temp = 0x0000;

    /* set the modulo */
    custom->bpl1mod = video_num_columns;

    /* set the display mode */
    custom->bplcon0 = disp.mode;

    /* set DIWSTRT */
    custom->diwstrt = disp.diwstrt;

    /* set DIWSTOP */
    custom->diwstop = disp.diwstop;

    /* set DDFSTRT */
    custom->ddfstrt = disp.ddfstrt;

    /* set DDFSTOP */
    custom->ddfstop = disp.ddfstop;

    /* set background color */
    custom->color[0] = disp.bgcol;

    /* set foreground color */
    custom->color[1] = disp.fgcol;

    /* set sprite 0 color */
    custom->color[17] = disp.crsrcol;

    /* initialize LOF Copper List */
    initcop(disp.coplist1, disp.coplist2, 0);

    /* initialize SHF Copper List */
    initcop(disp.coplist2, disp.coplist1, 1);

    /* turn on DMA for bitplane and sprites */
    custom->dmacon = 0x83a0;

    /* set up copper */
    custom->cop1lc = disp.coplist1;
    custom->copjmp1 = 0;

    /* reset vertical blank interrupt */
    custom->intreq = 0x20;

    /* wait for vertical blank interrupt */
    while ((custom->intreqr & 0x20) != 0x20)
	;

    /* set bitplane pointers based on LOF/SHF bit */
    if (custom->vposr & 0x8000)
	{
	custom->cop1lc = disp.coplist1;
	custom->copjmp1 = 0;
	}
    else
	{
	custom->cop1lc = disp.coplist2;
	custom->copjmp1 = 0;
	}


    register_console(console_print);

    return(mem_start);
    }

int
    scroll(int row)
    {
    int
	i,
	nwords = video_num_columns/2;

    for (i=0; i<row; i++)
	memcpy(&disp.bitplane[i * nwords * fontheight],
	       &disp.bitplane[(i+1) * nwords * fontheight],
	       video_num_columns * fontheight);

    /* clear the new row */
    memset(&disp.bitplane[i * nwords * fontheight], 0,
	   video_num_columns * fontheight);

    return row;
    }

void
    do_keyboard_interrupt(void)
    {
    TTY_READ_FLUSH(TTY_TABLE(0));
#if 0
    timer_active &= ~(1<<BLANK_TIMER);
    if (vt_cons[fg_console].vc_mode == KD_GRAPHICS)
	return;
    if (console_blanked)
        {
	timer_table[BLANK_TIMER].expires = 0;
	timer_active |= 1<<BLANK_TIMER;
        }
    else if (blankinterval)
        {
	timer_table[BLANK_TIMER].expires = jiffies + blankinterval;
	timer_active |= 1<<BLANK_TIMER;
        }
#endif
    }	

void
    conputc(int c)
    {
    unchar
	*mem;

    ushort
	*temp;

    static int
	row = 0,
	col = 0;

    int
	i,
	tmp,
	hs,
	vs;

    switch(c)
	{
	case '\f':
	    /* clear display memory */
	    for (temp=disp.bitplane; temp<(disp.bitplane + MEM_REQ/2); temp++)
		*temp = 0x0000;

	    row = 0;
	    col = 0;
	    break;

	case '\b':
	    if (col > 0)
		col--;

	    mem = (unchar *)disp.bitplane +
		  (row * video_num_columns * fontheight) + col;
	    for ( i=0; i<fontheight; i++ )
		mem[i * video_num_columns] = 0;
	    break;

	case '\n':
	    /* newline */
	    col = 0;
	    if (++row > disp.row_max)
		scroll(row = disp.row_max);
	    break;

	case '\r':
	    /* carriage return */
	    col = 0;
	    break;

	case 7:
	    /* bell */
	    break;

	case 127:
	    /* delete */
	    break;

	default:
	    if ((c >= 32) && (c < 127))
		/* lower half of charset -- normal ASCII */
		tmp = 32;
	    else if ((c >= 160) && (c < 255))
		/* upper half of charset -- special characters */
		tmp = 64;
	    else
		/* not defined in charset -- non-printable character */
		break;

	    mem = (unchar *)disp.bitplane +
		  (row * video_num_columns * fontheight) + col;
	    for (i=0; i<fontheight; i++)
		mem[i * video_num_columns] = fontdata[((c-tmp)*fontheight)+i];

	    if (col < disp.col_max)
		col++;
	    else
		{
		col = 0;
		if (++row > disp.row_max)
		    {
		    scroll(row = disp.row_max);
		    }
		}

	    break;
	}

    /* move the cursor */
    vs = (disp.diwstrt >> 8) + ((row+1) * fontheight) / 2 - 1;
    hs = (disp.diwstrt & 0x00ff) + (col * fontwidth) / 2 - 1;
    *(long *)cursor = (vs << 24) + ((hs & 0x000001fe) << 15) +
		      (((vs+1) & 0x000000ff) << 8) + (vs & 0x00000100) +
		      (((vs+1) & 0x00000100) >> 1) + (hs & 0x00000001);
    }

void
    console_print(const char *str)
    {
    /* do nothing if console not initialized */
    if (!disp.bitplane)
	return;

    while(*str)
	conputc(*str++);
    }

    void puthex(int c)
    {
    conputc( '0' );
    conputc( 'x' );

    if (((c & 0xf0) >> 4) > 9)
	conputc(((c & 0xf0) >> 4) - 10 + 'a');
    else
	conputc(((c & 0xf0) >> 4) + '0');

    if ((c & 0xf) > 9)
	conputc((c & 0xf) - 10 + 'a');
    else
	conputc((c & 0xf) + '0');
    }

void
    con_write (struct tty_struct *tty)
    {
    int
	c;

    while (!tty->stopped && (c = get_tty_queue(&tty->write_q)) >= 0)
	conputc (c);
    }

void
    blank_screen (void)
    {
    }

void
    unblank_screen (void)
    {
    }

void
    update_screen(int new_console)
    {
    static int lock = 0;

    if (new_console == fg_console || lock)
	return;
    lock = 1;
#if 0
    kbdsave(new_console);
    get_scrmem(fg_console); 
    fg_console = new_console;
    set_scrmem(fg_console); 
    set_origin(fg_console);
    set_cursor(new_console);
    set_leds();
#endif
    lock = 0;
    }

/*
 * All we do is set the write and ioctl subroutines; later on maybe we'll
 * dynamically allocate the console screen memory.
 */
int
    con_open(struct tty_struct *tty, struct file *filp)
    {
    tty->write = con_write;
    tty->ioctl = vt_ioctl;

    if (tty->line > NR_CONSOLES)
	return -ENODEV;

    return 0;
    }
