NetBSD/sys/arch/da30/dev/gsp.c

759 lines
17 KiB
C

/*
* Copyright (c) 1993 Paul Mackerras.
* 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. The name of the author may not be used to endorse or promote products
* derived from this software withough 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.
*
* $Id: gsp.c,v 1.1 1994/02/22 23:50:12 paulus Exp $
*/
#include "gsp.h"
#if NGSP > 0
/*
* Code to drive keyboard & GSP-controlled display for DA30
*/
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/tty.h>
#include <sys/uio.h>
#include <sys/callout.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <machine/cpu.h>
#include <dev/cons.h>
#include <da30/dev/kbd.h>
#include <da30/da30/iio.h>
#define NGSP 1
struct gsp_regs {
u_long adrs; /* address register */
u_short ctrl; /* control/status */
u_short data; /* memory R/W data register */
};
typedef volatile struct gsp_regs *gsp_ptr;
/* Bits in ctrl */
#define LBL 0x2000 /* lower byte last */
#define INCR 0x1000 /* increment address on read */
#define INCW 0x0800 /* increment address on write */
/* Standard addresses in GSP memory */
#define PUT_PTR_ADRS 0x320000 /* put pointer in ring buffer */
#define PUT_HI_PTR_ADRS 0x320010 /* put pointer high word */
#define GET_PTR_ADRS 0x320020 /* get pointer (low word) */
#define GSP_MODE_ADRS 0x320040 /* GSP mode word */
#define KBD_MODE_ADRS 0x320050 /* keyboard mode word */
#define LCL_MODE_ADRS 0x320060 /* GSP local modes */
#define HIST_REQ_ADRS 0x320070 /* history scrollback request */
#define GSP_CMD_ADRS 0x320080 /* GSP command code */
#define MAX_COL_ADRS 0x320140 /* # columns */
#define MAX_ROW_ADRS 0x320150 /* # rows */
#define SCR_WID_ADRS 0x320180 /* screen pixel width */
#define SCR_HT_ADRS 0x320190 /* screen pixel height */
/* Bits in GSP mode word */
#define GMODE_HOLD 1 /* hold screen */
#define GMODE_FLUSH 2 /* flush GSP input queue */
#define GMODE_ALTSCRN 4 /* use alternate screen */
#define GMODE_DISPCTRL 8 /* display control chars */
struct kbd {
short prefix; /* 0 => seen 0x55, 0x100 => 0xAA */
short last; /* last char seen, for autorepeat */
};
/*
* State.
*/
struct tty *gsp_tty[NGSP];
struct gspsoftc {
struct device dev;
gsp_ptr GSP;
struct tty *tty;
int open_as_console;
int open;
int kbd_dev;
struct kbd kbd;
};
/*
* We use an SCC channel connected to the keyboard as the input
* side of this device.
*/
int kbd_zsunit = 3; /* minor no. of keyboard SCC channel */
/* Termios values for keyboard */
struct termios kbdcn_termios = {
IGNBRK, /* iflag */
0, /* oflag */
CREAD | CS8 | CLOCAL, /* cflag */
0, /* lflag */
{0},
B4800, B4800, /* speeds */
};
void gspstart __P((struct tty *));
/*
* Autoconfiguration stuff.
*/
void gspattach __P((struct device *, struct device *, void *));
int gspmatch __P((struct device *, struct cfdata *, void *));
struct cfdriver gspcd = {
NULL, "gsp", gspmatch, gspattach, DV_DULL, sizeof(struct gspsoftc), 0
};
int
gspmatch(parent, cf, args)
struct device *parent;
struct cfdata *cf;
void *args;
{
caddr_t virt;
virt = (caddr_t) IIO_CFLOC_ADDR(cf);
return !badbaddr(virt);
}
void
gspattach(parent, self, args)
struct device *parent, *self;
void *args;
{
struct gspsoftc *dv;
iio_print(self->dv_cfdata);
/* save the address */
dv = (struct gspsoftc *) self;
dv->GSP = (volatile struct gsp_regs *) IIO_CFLOC_ADDR(self->dv_cfdata);
dv->kbd.prefix = -1;
dv->kbd_dev = kbd_zsunit;
}
/*
* Major entry points referenced from cdevsw.
*/
gspopen(dev_t dev, int flag, int mode, struct proc *p)
{
register struct tty *tp;
int error, unit;
struct gspsoftc *dv;
unit = minor(dev);
if( unit >= gspcd.cd_ndevs
|| (dv = (struct gspsoftc *) gspcd.cd_devs[unit]) == NULL )
return ENODEV;
error = zs_kbdopen(dv->kbd_dev, unit, &kbdcn_termios, p);
if( error )
return error;
if( dv->tty == NULL ){
dv->tty = ttymalloc();
if( unit < NGSP )
gsp_tty[unit] = dv->tty;
}
tp = dv->tty;
tp->t_oproc = gspstart;
tp->t_param = NULL;
tp->t_dev = dev;
tp->t_sc = (caddr_t) dv;
if ((tp->t_state & TS_ISOPEN) == 0) {
tp->t_state |= TS_WOPEN;
ttychars(tp);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_cflag = TTYDEF_CFLAG;
tp->t_lflag = TTYDEF_LFLAG;
tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
ttsetwater(tp);
} else if (tp->t_state&TS_XCLUDE && p->p_ucred->cr_uid != 0) {
return (EBUSY);
}
tp->t_state |= TS_CARR_ON;
error = ((*linesw[tp->t_line].l_open)(dev, tp));
if( error ){
zs_kbdclose(dv->kbd_dev);
return error;
}
if( cdevsw[major(dev)].d_open == gspopen )
dv->open = 1;
else
dv->open_as_console = 1;
tp->t_winsize.ws_row = gsp_read_word(dv->GSP, MAX_ROW_ADRS);
tp->t_winsize.ws_col = gsp_read_word(dv->GSP, MAX_COL_ADRS);
return 0;
}
gspclose(dev, flag, mode, p)
dev_t dev;
int flag, mode;
struct proc *p;
{
int unit;
struct gspsoftc *dv;
unit = minor(dev);
dv = (struct gspsoftc *) gspcd.cd_devs[unit];
if( cdevsw[major(dev)].d_open == gspopen )
dv->open = 0;
else
dv->open_as_console = 0;
if( !dv->open && !dv->open_as_console ){
(*linesw[dv->tty->t_line].l_close)(dv->tty, flag);
ttyclose(dv->tty);
}
zs_kbdclose(dv->kbd_dev);
return(0);
}
/*ARGSUSED*/
gspread(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
struct tty *tp = gsp_tty[minor(dev)];
return ((*linesw[tp->t_line].l_read)(tp, uio, flag));
}
/*ARGSUSED*/
gspwrite(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
struct tty *tp = gsp_tty[minor(dev)];
return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
}
gspioctl(dev, cmd, data, flag, p)
dev_t dev;
caddr_t data;
int cmd, flag;
struct proc *p;
{
register struct tty *tp = gsp_tty[minor(dev)];
register error;
error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
if (error >= 0)
return (error);
error = ttioctl(tp, cmd, data, flag, p);
if (error >= 0)
return (error);
return (ENOTTY);
}
void
gspstart(tp)
register struct tty *tp;
{
register int s, nc, ncput;
struct gspsoftc *dv;
unsigned char buf[256];
dv = (struct gspsoftc *) tp->t_sc;
s = spltty();
if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP|TS_BUSY))
goto out;
do {
if (tp->t_outq.c_cc <= tp->t_lowat) {
if (tp->t_state&TS_ASLEEP) {
tp->t_state &= ~TS_ASLEEP;
wakeup((caddr_t)&tp->t_outq);
}
selwakeup(&tp->t_wsel);
}
if (tp->t_outq.c_cc == 0)
goto out;
nc = q_to_b(&tp->t_outq, buf, sizeof(buf));
tp->t_state |= TS_BUSY;
splx(s);
ncput = gsp_write(dv->GSP, buf, (size_t) nc);
s = spltty();
tp->t_state &= ~TS_BUSY;
} while( ncput == nc );
/* some characters remain; start a timeout to push them out later */
tp->t_state |= TS_TIMEOUT;
timeout((timeout_t) ttrstrt, (caddr_t) tp, 1);
out:
splx(s);
}
/*
* Stop output to the screen.
*/
gspstop(tp, flag)
register struct tty *tp;
int flag;
{
register int s;
register struct gspsoftc *dv;
register gsp_ptr gsp;
register int mode;
register u_short oc;
register u_long oa;
dv = (struct gspsoftc *) tp->t_sc;
gsp = dv->GSP;
s = spltty();
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl |= LBL;
gsp->adrs = GSP_MODE_ADRS;
mode = gsp->data;
if ((tp->t_state&TS_TTSTOP)==0) {
/* flush output */
tp->t_state |= TS_FLUSH;
mode |= GMODE_FLUSH;
} else {
/* suspending output */
mode |= GMODE_HOLD;
}
gsp->adrs = GSP_MODE_ADRS;
gsp->data = mode;
gsp->adrs = oa;
gsp->ctrl = oc;
splx(s);
}
int
gspmap(dev_t dev, long off, int prot)
{
struct gspsoftc *dv;
if( off != 0 )
return -1;
dv = (struct gspsoftc *) gspcd.cd_devs[minor(dev)];
return btoc(dv->GSP);
}
int
gsp_write(register gsp_ptr gsp, unsigned char *ptr, size_t nb)
{
size_t n, na;
register int c;
register unsigned short put, oc;
register unsigned long next, put_hi, oa;
if( nb == 0 )
return 0;
n = nb;
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl = (gsp->ctrl | LBL | INCW) & ~INCR;
gsp->adrs = GSP_MODE_ADRS;
gsp->data &= ~GMODE_FLUSH;
gsp->adrs = PUT_HI_PTR_ADRS;
put_hi = gsp->data << 16;
while( n != 0 ){
gsp->adrs = GET_PTR_ADRS;
next = (gsp->data - 8) & 0xFFF0;
gsp->adrs = PUT_PTR_ADRS;
put = gsp->data & ~7;
/* can't put a byte in location `next' */
if( next == put )
break;
if( (put & 8) != 0 ){
/* put a single byte in a word */
gsp->adrs = put | put_hi;
gsp->data = (*ptr++ << 8) | (gsp->data & 0xFF);
--n;
gsp->adrs = PUT_PTR_ADRS;
gsp->data = put + 8;
continue;
}
if( next < put )
next = 0x10000;
na = (next - put) >> 3;
if( na > n )
na = n;
gsp->adrs = put | put_hi;
n -= na;
put += na << 3;
for( ; na > 1; na -= 2 ){
c = *ptr++;
gsp->data = (*ptr++ << 8) | c;
}
if( na != 0 )
gsp->data = *ptr++;
gsp->adrs = PUT_PTR_ADRS;
gsp->data = put;
}
gsp->adrs = oa;
gsp->ctrl = oc;
return nb - n;
}
int
gsp_read_word(register gsp_ptr gsp, unsigned long adrs)
{
register u_short oc, val;
register u_long oa;
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl |= LBL;
gsp->adrs = adrs;
val = gsp->data;
gsp->adrs = oa;
gsp->ctrl = oc;
return val;
}
int
gsp_get_kmode(register gsp_ptr gsp)
{
return gsp_read_word(gsp, KBD_MODE_ADRS);
}
int
gsp_toggle_hold(register gsp_ptr gsp)
{
register int mode;
register u_short oc;
register u_long oa;
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl |= LBL;
gsp->adrs = GSP_MODE_ADRS;
mode = gsp->data ^ GMODE_HOLD;
gsp->adrs = GSP_MODE_ADRS;
gsp->data = mode;
gsp->adrs = oa;
gsp->ctrl = oc;
return mode & GMODE_HOLD;
}
gsp_set_history(register gsp_ptr gsp, int n)
{
register u_short oc;
register u_long oa;
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl |= LBL;
gsp->adrs = HIST_REQ_ADRS;
gsp->data = n;
gsp->adrs = oa;
gsp->ctrl = oc;
}
gsp_incr_history(register gsp_ptr gsp, int n)
{
register u_short oc;
register u_long oa;
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl |= LBL;
gsp->adrs = HIST_REQ_ADRS;
n += gsp->data;
if( n < 0 )
n = 0;
gsp->adrs = HIST_REQ_ADRS;
gsp->data = n;
gsp->adrs = oa;
gsp->ctrl = oc;
}
gsp_issue_cmd(register gsp_ptr gsp, int cmd)
{
register u_short oc;
register u_long oa;
oc = gsp->ctrl;
oa = gsp->adrs;
gsp->ctrl |= LBL;
gsp->adrs = GSP_CMD_ADRS;
gsp->data = cmd;
gsp->adrs = oa;
gsp->ctrl = oc;
}
/*
* Routines for using a keyboard attached to an SCC as the
* input side of /dev/gsp.
*/
int
kbd_newchar(int unit, register int c)
{
int n, kmode;
struct gspsoftc *dv = (struct gspsoftc *) gspcd.cd_devs[unit];
struct tty *tp = dv->tty;
u_char kbstr[5];
if( dv->kbd.prefix < 0 ){
if( c == 0x55 )
dv->kbd.prefix = 0;
else if( c == 0xAA )
dv->kbd.prefix = 0x100;
return;
}
c += dv->kbd.prefix;
dv->kbd.prefix = -1;
kmode = gsp_get_kmode(dv->GSP);
if( c == 0x8F ){
if( (kmode & KBD_AUTO_RPT) == 0 )
return;
c = dv->kbd.last;
} else
dv->kbd.last = c;
/* got a single-character code for the key; take action or
turn it into an escape sequence */
if( c <= 0x80 ){
if( (kmode & KBD_CLICK) != 0 )
zs_kbdput(dv->kbd_dev, KC_CLICK);
(*linesw[tp->t_line].l_rint)(c, tp);
return;
}
if( c < 0x100 ){
/* compose, f1-f5 */
if( c == KEY_SCRL ){
/* Hold screen */
n = gsp_toggle_hold(dv->GSP);
zs_kbdput(dv->kbd_dev, n? 0x38: 0x28);
if( (kmode & KBD_CLICK) != 0 )
zs_kbdput(dv->kbd_dev, KC_CLICK);
} else if( c == KEY_BREAK ){
gsp_issue_cmd(dv->GSP, GSP_CMD_REFRESH);
} else if( c == KEY_PRINT ){
/* print */
/* print_something(); */
#if 0
} else if( c == KEY_C_PRINT ){
vm_check();
#endif
} else if( c == KEY_C_BREAK ){
/* ctrl-break */
asm(" trap #14");
}
return;
}
/* control of scrolling back through history */
switch( c ){
case 0x145: /* shift up-arrow */
gsp_incr_history(dv->GSP, 1);
return;
case 0x146: /* shift down-arrow */
gsp_incr_history(dv->GSP, -1);
return;
case 0x1B5: /* shift prev-screen */
gsp_incr_history(dv->GSP, 24);
return;
case 0x1B6: /* shift next-screen */
gsp_incr_history(dv->GSP, -24);
return;
case 0x1B4: /* shift select */
gsp_set_history(dv->GSP, 0);
return;
}
for( n = 0; n < 5; ++n )
kbstr[n] = 0;
if( c < 0x130 ){
/* top row, f6 - f20, normal, shifted and ctrled */
n = c & 0xF;
if( n >= 12 ) ++n;
if( n >= 10 ) ++n;
if( n >= 6 ) ++n;
if( c >= 0x110 )
n += 20;
if( c >= 0x120 )
n += 20;
n += 16;
kbstr[0] = CSI;
kbstr[1] = n / 10 + '0';
kbstr[2] = n % 10 + '0';
kbstr[3] = '~';
} else if( c <= 0x15F || (kmode & KBD_APPL_KPAD) != 0 && c <= 0x17E ){
/* editing and numeric keypads */
if( c <= 0x139 || (kmode & KBD_APPL_CURS) == 0 && c <= 0x144 )
kbstr[0] = CSI;
else
kbstr[0] = SS3;
kbstr[1] = c;
if( c <= 0x139 )
kbstr[2] = '~';
} else if( c <= 0x17E ){
/* numeric keypad, not in application mode */
kbstr[0] = c - 0x140;
} else if( c <= 0x18A ){
/* delete, backspace, line feed */
kbstr[0] = c & 0x7F;
} else
/* shifted keypad keys */
return;
if( (kmode & KBD_CLICK) != 0 )
zs_kbdput(dv->kbd_dev, KC_CLICK);
for( n = 0; (c = kbstr[n]) != 0; ++n ){
if( c >= 0x80 && (kmode & KBD_7_BIT) != 0 ){
(*linesw[tp->t_line].l_rint)(ESC, tp);
c -= 0x40;
}
(*linesw[tp->t_line].l_rint)(c, tp);
}
}
/*
* Routines to interface to the virtual console code.
*/
int gsp_cons_addr = 0xE70000;
int gsp_cons_kbd = 3;
struct kbd gsp_cons_kstate = {-1, 0};
gsp_ptr gsp_cons_ptr;
gspcnprobe(cp)
struct consdev *cp;
{
int maj;
/* check the keyboard */
if( !zs_kbdcnprobe(cp, gsp_cons_kbd) )
return;
/* locate the major number */
for (maj = 0; maj < nchrdev; maj++)
if (cdevsw[maj].d_open == gspopen)
break;
/* initialize required fields */
cp->cn_dev = makedev(maj, 0);
cp->cn_pri = CN_INTERNAL;
}
/* ARGSUSED */
gspcninit(cp)
struct consdev *cp;
{
gsp_cons_ptr = (gsp_ptr) IIOV(gsp_cons_addr);
zs_cnsetup(gsp_cons_kbd, &kbdcn_termios); /* initialize the keyboard */
gsp_cons_kstate.prefix = -1;
}
/* ARGSUSED */
gspcnputc(dev, c)
dev_t dev;
u_char c;
{
int s;
s = spltty();
while( gsp_write(gsp_cons_ptr, &c, 1) == 0 )
DELAY(100)
splx(s);
return 0;
}
/* ARGSUSED */
gspcngetc(dev)
dev_t dev;
{
register int c;
int kmode;
for(;;){
c = zscngetc(gsp_cons_kbd);
if( gsp_cons_kstate.prefix < 0 ){
if( c == 0x55 )
gsp_cons_kstate.prefix = 0;
else if( c == 0xAA )
gsp_cons_kstate.prefix = 0x100;
continue;
}
c += gsp_cons_kstate.prefix;
gsp_cons_kstate.prefix = -1;
kmode = gsp_get_kmode(gsp_cons_ptr);
if( c == 0x8F ){
if( (kmode & KBD_AUTO_RPT) == 0 )
continue;
c = gsp_cons_kstate.last;
} else
gsp_cons_kstate.last = c;
if( c <= 0x80 ){
/* an ordinary char - that's what we want */
if( (kmode & KBD_CLICK) != 0 )
zscnputc(gsp_cons_kbd, KC_CLICK);
break;
}
/* we take action on some other keys, but we don't return
escape sequences. */
switch( c ){
case KEY_SCRL: /* Hold screen */
c = gsp_toggle_hold(gsp_cons_ptr);
zscnputc(gsp_cons_kbd, c? 0x38: 0x28);
break;
case 0x145: /* shift up-arrow */
gsp_incr_history(gsp_cons_ptr, 1);
break;
case 0x146: /* shift down-arrow */
gsp_incr_history(gsp_cons_ptr, -1);
break;
case 0x1B5: /* shift prev-screen */
gsp_incr_history(gsp_cons_ptr, 24);
break;
case 0x1B6: /* shift next-screen */
gsp_incr_history(gsp_cons_ptr, -24);
break;
case 0x1B4: /* shift select */
gsp_set_history(gsp_cons_ptr, 0);
break;
}
/* ignore function keys, etc. */
}
if (c == '\r')
c = '\n';
return c;
}
#endif /* NGSP > 0 */