/*
 * Copyright (c) 1982, 1986 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 *
 *	derived from: "@(#)sm.c	1.7	ULTRIX	12/16/86";
 */

/************************************************************************
 *									*
 *			Copyright (c) 1985 by				*
 *		Digital Equipment Corporation, Maynard, MA		*
 *			All rights reserved.				*
 *									*
 *   This software is furnished under a license and may be used and	*
 *   copied  only  in accordance with the terms of such license and	*
 *   with the  inclusion  of  the  above  copyright  notice.   This	*
 *   software  or  any  other copies thereof may not be provided or	*
 *   otherwise made available to any other person.  No title to and	*
 *   ownership of the software is hereby transferred.			*
 *									*
 *   This software is  derived  from  software  received  from  the	*
 *   University    of   California,   Berkeley,   and   from   Bell	*
 *   Laboratories.  Use, duplication, or disclosure is  subject  to	*
 *   restrictions  under  license  agreements  with  University  of	*
 *   California and with AT&T.						*
 *									*
 *   The information in this software is subject to change  without	*
 *   notice  and should not be construed as a commitment by Digital	*
 *   Equipment Corporation.						*
 *									*
 *   Digital assumes no responsibility for the use  or  reliability	*
 *   of its software on equipment which is not supplied by Digital.	*
 *									*
 ************************************************************************/


#include <sys/types.h>
#include "smioctl.h"
#include "nbregs.h"

/*
 * VS2000 memory base
 */
#define	VS_PHYSNEXUS	0x20080000
#define	VS_PHYSBITMAP	0x30000000
#define	VS_PHYSCURSOR	0x200c0000

#define VS_CFGTST       0x20020000      /* VAXstar config & test reg (r/o) */
#define VS_MTYPEMASK    0x7             /* Memory option type (mask) */
#define VS_MTYPE_1MB    0x1             /* 1MB memory board */
#define VS_MTYPE_2MB    0x2             /* 2MB memory board */
#define VS_MTYPE_4MB    0x3             /* 4MB memory board */
#define VS_VIDOPT       0x8             /* Video option present (color) */
#define VS_CURTEST      0x10            /* Monochrome video present */
#define VS_L3CON        0x20            /* SLU line 3 is the console */
#define VS_NETOPT       0x40            /* Network option present */
#define VS_MULTU        0x80            /* Set=MICROVAX 1800, clear=VAXstar */

/*
 * Definitions needed to access the VAXstar SLU.
 * Couldn't include sreg.h (too many compiler errors).
 */
#define	sscsr	nb_sercsr
#define	ssrbuf	nb_serrbuf_lpr
#define	sslpr	nb_serrbuf_lpr
#define	sstcr	nb_sertcr.c[0]
#define	sstbuf	nb_sermsr_tdr.c[0]
#define	SS_TRDY		0x8000		/* Transmit ready */
#define	SS_RDONE	0x80		/* Receiver done		*/
#define SS_PE		0x1000		/* Parity error			*/
#define SS_FE		0x2000		/* Framing error		*/
#define SS_DO		0x4000		/* Data overrun error		*/
#define	SINT_ST		0100		/* SLU xmit interrupt bit	*/
#define SS_CLR		0x10		/* Reset ss			*/
#define SS_MSE		0x20		/* Master Scan Enable		*/


#define SMMAXEVQ	64	/* must be power of 2 */

/*
 * Screen parameters for the 19 inch monitors. These determine the max size in
 * pixel and character units for the display and cursor positions.
 * Notice that the mouse defaults to original square algorithm, but X
 * will change to its defaults once implemented.
 */
struct sm_info sm_scn_defaults = {
	{0, 0, 0, 0, 56, 120,1024, 864,1024, 864,
	 0, 0, 0, 0, SMMAXEVQ, 0, 0, 2, 4}
};

struct sm_info  *sm_scn;
	
/*
 * Keyboard state
 */
struct sm_keyboard {
	int shift;			/* state variables	*/
	int cntrl;
	int lock;
	int hold;
	char last;			/* last character	*/
} sm_keyboard;

short sm_divdefaults[15] = { LK_DOWN,	/* 0 doesn't exist */
	LK_AUTODOWN, LK_AUTODOWN, LK_AUTODOWN, LK_DOWN,
	LK_UPDOWN,   LK_UPDOWN,   LK_AUTODOWN, LK_AUTODOWN, 
	LK_AUTODOWN, LK_AUTODOWN, LK_AUTODOWN, LK_AUTODOWN, 
	LK_DOWN, LK_AUTODOWN };

short sm_kbdinitstring[] = {		/* reset any random keyboard stuff */
	LK_AR_ENABLE,			/* we want autorepeat by default */
	LK_CL_ENABLE,			/* keyclick */
	0x84,				/* keyclick volume */
	LK_KBD_ENABLE,			/* the keyboard itself */
	LK_BELL_ENABLE,			/* keyboard bell */
	0x84,				/* bell volume */
	LK_LED_DISABLE,			/* keyboard leds */
	LED_ALL };
#define KBD_INIT_LENGTH	sizeof(sm_kbdinitstring)/sizeof(short)

unsigned  short def_cursor[32] = { 
/* plane A */ 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF,
 	      0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF, 0x00FF,
/* plane B */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
              0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
	    };

int smputc(), smgetc(), smgetct();

/*
 * Keyboard translation and font tables
 */
extern  char q_key[],q_shift_key[],*q_special[],q_font[];
extern	short q_cursor[];

extern (*v_putc)(),(*v_getc)(),(*v_getct)();

/*
 * Routine called to init an sm.
 */
sm_init()
{
	register struct nb_regs *smaddr = (struct nb_regs *)VS_PHYSNEXUS;
	register struct nb1_regs *smaddr1 = (struct nb1_regs *)QMEMVAXSTAR;
	register struct nb2_regs *smaddr2 = (struct nb2_regs *)VS_PHYSBITMAP;
	char vs_cfgtst = *(char *)(VS_CFGTST);
	register struct sm_info *qp;
	register int i;
	register char *ptr;
	char	*smmem;

	if (vs_cfgtst&VS_MULTU ||
	    vs_cfgtst&VS_VIDOPT ||
	    vs_cfgtst&VS_CURTEST == 0)
	  return(0);

        if( badaddr( smaddr->nb_vdc_sel, sizeof(char) ) )
	  return(0);

	/*
	 * Initialize the screen
	 */

	sm_scrn_init(smaddr1);
	smaddr1->nb_cur_cmd = (HSHI | ENPA | ENPB);
	smmem = (char *)((u_long)smaddr2);
        sm_scn = (struct sm_info *)((u_int)smmem + 125*1024);
	qp = sm_scn;
        *sm_scn = sm_scn_defaults;
 	qp->bitmap = smmem;
        qp->smaddr = smaddr;
        qp->cursorbits = (short *)((u_int)smmem + 127*1024);

	/*
	 * Clear the bit map
	 */

	for( i=0 , ptr = qp->bitmap ; i<60 ; i++ , ptr += 2048)
	    bzero( ptr, 2048 );

	/*
	 * Clear and Home the cursor
	 */

	for (i = 0; i < 32; i++) {
	    qp->cursorbits[i] = 0;
	}
	qp->row = qp->col = 0;
	sm_load_cursor(def_cursor);

	/*
	 * Set the line parameters on SLU line 0 for
	 * the LK201 keyboard: 4800 BPS, 8-bit char, 1 stop bit, no parity.
	 */
	smaddr->sscsr = SS_CLR;
	for(i=0; i<100000; i++)
	  if((smaddr->sscsr&SS_CLR) == 0) break;
	smaddr->sscsr = SS_MSE;
	smaddr->sslpr = (SER_RXENAB | SER_KBD | SER_SPEED | SER_CHARW);

	sm_key_out(LK_DEFAULTS);
	for (i = 0; i < KBD_INIT_LENGTH; i++)
	    sm_key_out(sm_kbdinitstring[i]);

	/*
	 * Turn it on.
	 */
	v_getc = smgetc;
	v_putc = smputc;
	v_getct = smgetct;

	return 1;
}

/******************************************************************
 **                                                              **
 * Routine to initialize the screen.                             **
 **                                                              **
 ******************************************************************/

sm_scrn_init(smaddr1)
struct nb1_regs *smaddr1;
{

	register int	i;

/*	DELAY(100000);  /* wait  */

	i = FOPB | VBHI;
	smaddr1->nb_cur_cmd = i;
}

sm_load_cursor(cur)
unsigned short cur[32];
{

	register struct nb1_regs *smaddr1 = (struct nb1_regs *)QMEMVAXSTAR;
	register int	i;

	smaddr1->nb_cur_cmd = HSHI | ENPA | ENPB | LODSA;
	for (i = 0; i < 32; ++i)
	    smaddr1->nb_load = cur[i];
	smaddr1->nb_cur_cmd = HSHI | ENPA | ENPB;
}

/*
 * Routine to display a character on the screen.  The model used is a 
 * glass tty.  It is assummed that the user will only use this emulation
 * during system boot and that the screen will be eventually controlled
 * by a window manager.
 */
smputc( c )
char c;
{

  register char *b_row, *f_row;
  register int i;
  register int ote = 128;
  register struct sm_info *qp =
    		(struct sm_info *)((u_int)VS_PHYSBITMAP + 125*1024);
  static enum {
    NORM = 0, ESC, ESCBRAK,
  } state = NORM;
  static int num_row=0, num_col=0, cur_num=0;
	
  qp->bitmap = (char *)((u_long)VS_PHYSBITMAP);
  c &= 0x7f;
  switch (state) {
  case NORM:
    num_col = num_row = cur_num = 0;
    
    switch ( c ) {
    case '\033':		/* escape */
      state = ESC;
      return(0);
      
    case '\t':			/* tab		*/
      for( i = 8 - (qp->col & 0x7) ; i > 0 ; i-- )
	smputc( ' ' );
      break;

    case '\r':			/* return	*/
      qp->col = 0;
      break;

    case '\b':			/* backspace	*/
      if( --qp->col < 0 )
	qp->col = 0;
      break;

    case '\n':			/* linefeed	*/
      if( qp->row+1 >= qp->max_row )
	smscroll();
      else
	qp->row++;
      break;

    case '\007':		/* bell		*/

      /*
       * We don't do anything to the keyboard until after autoconfigure.
       */

      if( qp->smaddr )
	sm_key_out( LK_RING_BELL );
      return(0);

    default:
      if( c >= ' ' && c <= '~' ) {
	b_row = qp->bitmap+(qp->row*15 & 0x3ff)*128+qp->col;
	i = c - ' ';
	if( i < 0 || i > 95 )
	  i = 0;
	else
	  i *= 15;
	f_row = (char *)((int)q_font + i);

	/* inline expansion for speed */

	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	*b_row = *f_row++; b_row += ote;
	/* the following is so that ansi marginning works... */
	if( ++qp->col >=  qp->max_col ) {
	  qp->col = 0 ;
	  if( qp->row+1 >= qp->max_row )
	    smscroll();
	  else
	    qp->row++;
	}
      }
      break;
    }
    break;
  case ESC:
    switch(c) {
    case '[':
      state = ESCBRAK;
      return(0);

    default:
      state = NORM;
      return(0);
    }
    break;
  case ESCBRAK:
    switch(c) {
    case 'C':			/* nd (cursor right) */
      if(++qp->col >= qp->max_col){
	qp->col = 0;
	if(qp->row+1 >= qp->max_row)
	  smscroll();
	else
	  qp->row++;
      }
      state = NORM;
      break;

    case 'A':			/* up (cursor up) */
      if(qp->row > 0) --qp->row;
      state = NORM;
      break;
      
    case 'K':			/* ce (clear to eol) */
      b_row = qp->bitmap+(qp->row*15 & 0x3ff)*128+qp->col;
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 1 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 2 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 3 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 4 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 5 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 6 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 7 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 8 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 9 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 10 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 11 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 12 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 13 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 14 */
      bzero(b_row, qp->max_col-qp->col); b_row+=ote; /* 15 */
      state = NORM;
      return(0);
      
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      cur_num *= 10;
      cur_num += c-'0';
      return(0);
      
    case ';':
      num_row = cur_num;
      cur_num = 0;
      return(0);

    case 'H':			/* position cursor */
      num_col = cur_num;
      if((num_row-1 <= qp->max_row) && (num_col-1 <= qp->max_col)) {
	qp->row = num_row?(num_row-1):0;
	qp->col = num_col?(num_col-1):0;
      }
      state = NORM;
      break;
      
    case 'J':
      cur_num = 0;
      for( i=0 , b_row = qp->bitmap ; i<60 ; i++ , b_row += 2048)
	bzero( b_row, 2048 );
      state = NORM;
      return(0);
      
    default:
      state = NORM;
      return(0);
    }
    break;
  default:
    state = NORM;
    return(0);
  }
  /*
   * Position the cursor to the next character location.
   */
  sm_pos_cur( qp->col*8, qp->row*15 );
  return(0);
}


/*
 * Position the cursor to a particular spot.
 */
sm_pos_cur( x, y)
register int x,y;
{
	register struct nb1_regs *smaddr1;
	register struct sm_info *qp = sm_scn;
	register index;

	smaddr1 = (struct nb1_regs *)VS_PHYSCURSOR;
	if(qp->smaddr ) {
	    if( y < 0 || y > qp->max_cur_y )
		y = qp->max_cur_y;
	    if( x < 0 || x > qp->max_cur_x )
		x = qp->max_cur_x;
	    smaddr1->nb_cur_xpos = XOFFSET + x;
	    smaddr1->nb_cur_ypos = YOFFSET + y;
	}
}


/*
 * Scroll the bitmap by moving the scanline map words. This could
 * be done by moving the bitmap but it's much too slow for a full screen.
 * The only drawback is that the scanline map must be reset when the user 
 * wants to do graphics.
 */
smscroll()
{
	register struct sm_info *qp = sm_scn;
	int	thirtylines = 30*1920;


/* First copy 30 lines starting from the second line on the screen to the first
 * line on the screen. (we are moving 30 lines up one line)
 */
	bcopy(qp->bitmap+1920,qp->bitmap,thirtylines);

/* Do the same thing for the rest of the lines on the screen */

	bcopy(qp->bitmap+1920+thirtylines,qp->bitmap+thirtylines,(qp->row - 30)* 1920);

/* Now zero out the last two lines */

	bzero(qp->bitmap+(qp->row*1920),3840);
}


/* getc with timeout, returns -1 on timeout. */

smgetct(timeo)
{
    struct nb_regs *smaddr = (struct nb_regs *)VS_PHYSNEXUS;

    while ((smaddr->sscsr&SS_RDONE) == 0)
      if (timeo-- < 0)
	return(-1);
    return(smgetc());
}


/*
 * Keyboard Input.
 */
smgetc()
{
	struct nb_regs *smaddr = (struct nb_regs *)VS_PHYSNEXUS;
	char vs_cfgtst = *(char *)(VS_CFGTST);
	int c, line;
	u_short	data;
	static int first = 1;

/*
 * Get a character from the keyboard,
 */

loop:
	/*
	 * Line number we expect input from.
	 */
	if(vs_cfgtst&VS_L3CON)
		line = 3;
	else
		line = 0;
	while (1) {
		while ((smaddr->sscsr&SS_RDONE) == 0) ;
		c = smaddr->ssrbuf;
		if(((c >> 8) & 3) != line)	/* wrong line mumber */
			continue;
		if(c&(SS_DO|SS_FE|SS_PE))	/* error */
			continue;
		break;
	}
	data = c & 0xff;

/*
 * Check for various keyboard errors
 */

	if( data == LK_POWER_ERROR || data == LK_KDOWN_ERROR ||
            data == LK_INPUT_ERROR || data == LK_OUTPUT_ERROR) {
		printf("sm0: Keyboard error, code = %x\n",data);
		return(0);
	}
	if( data < LK_LOWEST ) return(0);

/*
 * See if it is a state change key
 */

	switch ( data ) {
	case LOCK:
		sm_keyboard.lock ^= 0xffff;	/* toggle */
		if( sm_keyboard.lock )
			sm_key_out( LK_LED_ENABLE );
		else
			sm_key_out( LK_LED_DISABLE );
		sm_key_out( LED_3 );
		goto loop;

	case SHIFT:
		sm_keyboard.shift ^= 0xffff;
		goto loop;

	case CNTRL:
		sm_keyboard.cntrl ^= 0xffff;
		goto loop;

	case ALLUP:
		sm_keyboard.cntrl = sm_keyboard.shift = 0;
		goto loop;

	case REPEAT:
		c = sm_keyboard.last;
		break;

	default:

/*
 * Test for control characters. If set, see if the character
 * is elligible to become a control character.
 */
		if( sm_keyboard.cntrl ) {
		    c = q_key[ data ];
		    if( c >= ' ' && c <= '~' )
			c &= 0x1f;
		} else if( sm_keyboard.lock || sm_keyboard.shift )
			    c = q_shift_key[ data ];
		       else
			    c = q_key[ data ];
		break;	

	}

	sm_keyboard.last = c;

/*
 * Check for special function keys
 */
	if (first & c == 0) {
	    first = 0;
	    return(smgetc());
	}

	if( c & 0x80 )
	    return (0);
	else
	    return (c);
}


/******************************************************************
 **                                                              **
 ** Routine to output a character to the LK201 keyboard. This	 **
 ** routine polls the tramsmitter on the keyboard to output a    **
 ** code. The timer is to avaoid hanging on a bad device.        **
 **                                                              **
 ******************************************************************/

sm_key_out( c )
char c;
{
	register struct sm_info *qp = sm_scn;
	register struct nb_regs *ssaddr;
	register int timo = 30000;
	int s;
	int tcr, ln;

	if(qp->smaddr == 0)
		return;

	ssaddr = (struct nb_regs *)VS_PHYSNEXUS;

	tcr = 0;
	ssaddr->sstcr |= 0x1;
	while (1) {
		while ((ssaddr->sscsr&SS_TRDY) == 0 && timo--) ;
		ln = (ssaddr->sscsr>>8) & 3;
		if (ln != 0) {
			tcr |= (1 << ln);
			ssaddr->sstcr &= ~(1 << ln);
			continue;
		}
		ssaddr->sstbuf = c&0xff;
		while (1) {
			while ((ssaddr->sscsr&SS_TRDY) == 0) ;
			ln = (ssaddr->sscsr>>8) & 3;
			if (ln != 0) {
				tcr |= (1 << ln);
				ssaddr->sstcr &= ~(1 << ln);
				continue;
			}
			break;
		}
		ssaddr->sstcr &= ~0x1;
		if (tcr == 0)
			ssaddr->nb_int_reqclr = SINT_ST;
		else
			ssaddr->sstcr |= tcr;
		break;
	}
}
