Subject: MI Z8530 driver, take two
To: None <gwr@mc.com>
From: Ken Hornstein <kenh@cmf.nrl.navy.mil>
List: tech-kern
Date: 05/27/1997 19:03:09
Attached are the changes that make it possible to use the nutty wiring
on the pmax/alpha with the MI Z8530 driver. They are pretty minimal,
and with only a few changes in the port-specific drivers it shouldn't
be a problem to make them DTRT.
Also attached is the current incarnation of the Alpha zs driver (which
can't be used as a console, but that's my next task).
Comments/feedback welcome.
--Ken
--- z8530sc.c.orig Tue May 27 13:59:19 1997
+++ z8530sc.c Tue May 27 14:24:53 1997
@@ -133,6 +133,10 @@
bcopy((caddr_t)cs->cs_preg, (caddr_t)cs->cs_creg, 16);
reg = cs->cs_creg; /* current regs */
+ if (cs->cs_ctl_chan && cs != cs->cs_ctl_chan)
+ bcopy((caddr_t)&cs->cs_ctl_chan->cs_preg[5],
+ (caddr_t)&cs->cs_ctl_chan->cs_creg[5], 1);
+
zs_write_csr(cs, ZSM_RESET_ERR); /* XXX: reset error condition */
#if 1
@@ -207,6 +211,13 @@
/* char size, enable (RX/TX)*/
zs_write_reg(cs, 3, reg[3]);
zs_write_reg(cs, 5, reg[5]);
+
+ /*
+ * Write the status bits on the alternate channel as well
+ */
+
+ if (cs->cs_ctl_chan && cs != cs->cs_ctl_chan)
+ zs_write_reg(cs->cs_ctl_chan, 5, cs->cs_ctl_chan->cs_creg[5]);
/* interrupt enables: RX, TX, STATUS */
zs_write_reg(cs, 1, reg[1]);
--- z8530sc.h.orig Tue May 27 13:53:43 1997
+++ z8530sc.h Tue May 27 13:57:17 1997
@@ -108,6 +108,15 @@
char cs_softreq; /* need soft interrupt call */
char cs_pad[1];
+
+ /*
+ * For strange systems that have oddly wired serial ports, we
+ * provide a pointer to the channel state of the port that has
+ * our status lines on it
+ */
+
+ struct zs_chanstate *cs_ctl_chan;
+
/* MD code might define a larger variant of this. */
};
--- z8530tty.c.orig Tue May 27 13:57:32 1997
+++ z8530tty.c Tue May 27 14:39:47 1997
@@ -740,7 +740,7 @@
struct zstty_softc *zst;
struct zs_chanstate *cs;
int s, bps, cflag, error;
- u_char tmp3, tmp4, tmp5;
+ u_char tmp3, tmp4, tmp5, ctl_tmp5;
zst = zstty_cd.cd_devs[minor(tp->t_dev)];
cs = zst->zst_cs;
@@ -810,17 +810,21 @@
tmp5 |= ZSWR5_TX_8;
break;
}
+
+ cs->cs_preg[3] = tmp3;
+ cs->cs_preg[5] = tmp5;
+
/* Raise or lower DTR and RTS as appropriate. */
+ ctl_tmp5 = cs->cs_ctl_chan->cs_preg[5];
if (bps) {
/* Raise DTR and RTS */
- tmp5 |= cs->cs_wr5_dtr;
+ ctl_tmp5 |= cs->cs_wr5_dtr;
} else {
/* Drop DTR and RTS */
/* XXX: Should SOFTCAR prevent this? */
- tmp5 &= ~(cs->cs_wr5_dtr);
+ ctl_tmp5 &= ~(cs->cs_wr5_dtr);
}
- cs->cs_preg[3] = tmp3;
- cs->cs_preg[5] = tmp5;
+ cs->cs_ctl_chan->cs_preg[5] = ctl_tmp5;
/*
* Recompute the stop bits and parity bits. Note that
@@ -901,16 +905,16 @@
}
s = splzs();
- cs->cs_preg[5] &= ~clr;
- cs->cs_preg[5] |= set;
+ cs->cs_ctl_chan->cs_preg[5] &= ~clr;
+ cs->cs_ctl_chan->cs_preg[5] |= set;
if (cs->cs_heldchange == 0) {
if (zst->zst_tx_busy) {
zst->zst_heldtbc = zst->zst_tbc;
zst->zst_tbc = 0;
cs->cs_heldchange = (1<<5);
} else {
- cs->cs_creg[5] = cs->cs_preg[5];
- zs_write_reg(cs, 5, cs->cs_creg[5]);
+ cs->cs_ctl_chan->cs_creg[5] = cs->cs_ctl_chan->cs_preg[5];
+ zs_write_reg(cs->cs_ctl_chan, 5, cs->cs_ctl_chan->cs_creg[5]);
}
}
splx(s);
@@ -987,16 +991,16 @@
set = cs->cs_wr5_rts;
}
- cs->cs_preg[5] &= ~clr;
- cs->cs_preg[5] |= set;
+ cs->cs_ctl_chan->cs_preg[5] &= ~clr;
+ cs->cs_ctl_chan->cs_preg[5] |= set;
if (cs->cs_heldchange == 0) {
if (zst->zst_tx_busy) {
zst->zst_heldtbc = zst->zst_tbc;
zst->zst_tbc = 0;
cs->cs_heldchange = (1<<5);
} else {
- cs->cs_creg[5] = cs->cs_preg[5];
- zs_write_reg(cs, 5, cs->cs_creg[5]);
+ cs->cs_ctl_chan->cs_creg[5] = cs->cs_ctl_chan->cs_preg[5];
+ zs_write_reg(cs->cs_ctl_chan, 5, cs->cs_ctl_chan->cs_creg[5]);
}
}
}
@@ -1106,6 +1110,10 @@
/* Avoid whacking the chip... */
cs->cs_creg[5] = cs->cs_preg[5];
zs_write_reg(cs, 5, cs->cs_creg[5]);
+ if (cs->cs_ctl_chan && cs != cs->cs_ctl_chan) {
+ cs->cs_ctl_chan->cs_creg[5] = cs->cs_ctl_chan->cs_preg[5];
+ zs_write_reg(cs->cs_ctl_chan, 5, cs->cs_ctl_chan->cs_creg[5]);
+ }
} else
zs_loadchannelregs(cs);
cs->cs_heldchange = 0;
/*
* Machine-dependant part of the Z8530 driver.
*
* This driver handles Z8530 chips attached to the Alpha IOASIC
*/
#include <machine/options.h> /* Config option headers */
#include <sys/cdefs.h> /* RCS macro defines */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/syslog.h>
#include <dev/cons.h>
#include <dev/ic/z8530reg.h>
#include <machine/z8530var.h>
#include <machine/autoconf.h>
#include <dev/tc/tcvar.h>
#include <alpha/tc/ioasicreg.h>
#include <dev/tc/ioasicvar.h>
/*
* The Alpha provides a 7.372 MHz clock to the UART
*/
#define PCLK (9600 * 768)
/*
* Hardware layout of the Z8530 registers
*/
struct zschan {
volatile u_int zc_csr; /* control, status, and indirect reg */
#ifdef SPARSE
u_int zc_pad0;
#endif
volatile u_int zc_data; /* data */
#ifdef SPARSE
u_int zc_pad1;
#endif
};
struct zsdevice {
/* Sigh. This is supposted to be backwards, apparantly */
struct zschan zs_chan_b;
struct zschan zs_chan_a;
};
static struct zschan * zs_get_chan_addr __P((int, tc_addr_t));
static int zscintr __P((void *));
static void zscsoft __P((void *));
static volatile int zsc_soft_scheduled;
/*
* Get a zs channel address given an address on the ioasic
*/
static struct zschan *
zs_get_chan_addr(channel, zsaddr)
int channel;
tc_addr_t zsaddr;
{
struct zsdevice *addr;
struct zschan *zc;
addr = (struct zsdevice *) zsaddr;
#ifdef SPARSE
addr = (struct zsdevice *) TC_DENSE_TO_SPARSE((tc_addr_t) addr);
#endif
if (channel == 0) {
zc = &addr->zs_chan_a;
} else {
zc = &addr->zs_chan_b;
}
return (zc);
}
/*
* Initial register settings
*/
static u_char zs_init_reg[16] = {
0, /* 0: CMD (reset, etc) */
0, /* 1: No interrupts yet */
0xf0, /* Interrupt vector */
ZSWR3_RX_8 | ZSWR3_RX_ENABLE,
ZSWR4_CLK_X16 | ZSWR4_ONESB,
ZSWR5_TX_8 | ZSWR5_TX_ENABLE,
0, /* 6: TXSYNC/SYNCLO */
0, /* 7: RXSYNC/SYNCHI */
0, /* 8: alias for data port */
ZSWR9_MASTER_IE | ZSWR9_VECTOR_INCL_STAT,
0, /* Misc TX/RX control bits */
ZSWR11_TXCLK_BAUD | ZSWR11_RXCLK_BAUD |
ZSWR11_TRXC_OUT_ENA | ZSWR11_TRXC_BAUD,
22, /* BAUDLO (default = 9600) */
0, /* BAUDHI (default = 9600) */
ZSWR14_BAUD_ENA | ZSWR14_BAUD_FROM_PCLK,
ZSWR15_BREAK_IE | ZSWR15_DCD_IE,
};
/*
* Autoconfiguration support
*/
/* Driver definitions for autoconfig */
static int zsc_match __P((struct device *, struct cfdata *, void *));
static void zsc_attach __P((struct device *, struct device *, void *));
static int zsc_print __P((void *, const char *name));
struct cfattach zsc_ca = {
sizeof(struct zsc_softc), zsc_match, zsc_attach
};
struct cfdriver zsc_cd = {
NULL, "zsc", DV_DULL
};
/*
* Warts needed by zs8530tty.c. zs_major must match the major number in
* the cdev switch table.
*/
int zs_major = 38;
int zs_def_cflag = (CREAD | CS8 | HUPCL);
/*
* Test to see if the device is present.
* Return true if we find it.
*/
int
zsc_match(parent, cf, aux)
struct device *parent;
struct cfdata *cf;
void *aux;
{
struct ioasicdev_attach_args *d = aux;
void *zsc_addr;
if (parent->dv_cfdata->cf_driver != &ioasic_cd) {
#ifdef DIAGNOSTIC
printf("Cannot attach zsc on %s\n", parent->dv_xname);
#endif
return (0);
}
/*
* Make sure that we're looking for the right kind of device
*/
if ((strncmp(d->iada_modname, "z8530 ", TC_ROM_LLEN) != 0) &&
(strncmp(d->iada_modname, "scc", TC_ROM_LLEN) != 0))
return (0);
/*
* Find out the device address, and check it for validity
*/
zsc_addr = (void *) d->iada_addr;
#ifdef SPARSE
zsc_addr = (void *) TC_DENSE_TO_SPARSE((tc_addr_t) zsc_addr);
#endif
if (badaddr(zsc_addr, 2))
return (0);
return(1);
}
/*
* Attach a found zs device.
*
* Match slave number to zs unit numbers, so that if we mis-configure the
* devices we won't set up the keyboard as a tty, etc etc.
*/
static void
zsc_attach(parent, self, aux)
struct device *parent;
struct device *self;
void *aux;
{
struct zsc_softc *zsc = (void *) self;
struct zsc_attach_args zsc_args;
struct zs_chanstate *cs;
struct ioasicdev_attach_args *d = aux;
volatile struct zschan *zc;
int s, channel;
printf("\n");
/*
* Initialize the software state for each channel
*/
for (channel = 0; channel < 2; channel++) {
zsc_args.channel = channel;
zsc_args.hwflags = 0;
cs = &zsc->zsc_cs_store[channel];
zsc->zsc_cs[channel] = cs;
cs->cs_channel = channel;
cs->cs_private = NULL;
cs->cs_ops = &zsops_null;
cs->cs_brg_clk = PCLK / 16;
zc = zs_get_chan_addr(channel, d->iada_addr);
cs->cs_reg_csr = (volatile u_char *) &zc->zc_csr;
cs->cs_reg_data = (volatile u_char *) &zc->zc_data;
bcopy(zs_init_reg, cs->cs_creg, 16);
bcopy(zs_init_reg, cs->cs_preg, 16);
cs->cs_defspeed = 9600; /* XXX */
cs->cs_defcflag = zs_def_cflag;
/*
* Match these to cs_defcflag
*/
cs->cs_rr0_dcd = ZSRR0_DCD;
cs->cs_rr0_cts = 0;
cs->cs_wr5_dtr = ZSWR5_DTR | ZSWR5_RTS;
cs->cs_wr5_rts = 0;
/*
* Clear the master interrupt enable.
* Common to both channels, so just do it on the A channel
*/
if (channel == 0) {
zs_write_reg(cs, 9, 0);
}
/*
* Set up the channel status register pointer to handle
* the weird wiring on the Alpha/pmax.
*/
if (channel == 1)
cs->cs_ctl_chan = zsc->zsc_cs[0];
else
cs->cs_ctl_chan = NULL;
if (!config_found(self, (void *) &zsc_args, zsc_print)) {
/*
* No sub-driver. Just reset it.
*/
u_char reset = (channel == 0) ?
ZSWR9_A_RESET : ZSWR9_B_RESET;
s = splhigh();
zs_write_reg(cs, 9, reset);
splx(s);
}
}
/*
* Setup the ioasic interrupt handler
*/
ioasic_intr_establish(parent, d->iada_cookie, TC_IPL_TTY,
zscintr, (void *) zsc);
/*
* Set up master interrupt enable and interrupt vectors.
*/
s = splhigh();
/*
* Interrupt vectors. The sun3 driver only does this for the A
* channel, but the alpha SCC driver does it for both, so let's
* play it safe.
*/
zs_write_reg(zsc->zsc_cs[0], 2, zs_init_reg[2]);
zs_write_reg(zsc->zsc_cs[1], 2, zs_init_reg[2]);
/*
* Master interrupt control (enable it)
*/
zs_write_reg(zsc->zsc_cs[0], 9, zs_init_reg[9]);
splx(s);
}
static int
zsc_print(aux, name)
void *aux;
const char *name;
{
struct zsc_attach_args *args = aux;
if (name != NULL)
printf("%s: ", name);
if (args->channel != -1)
printf(" channel %d", args->channel);
return UNCONF;
}
/*
* Our hardware interrupt handler
*/
static int
zscintr(arg)
void *arg;
{
struct zsc_softc *zsc = arg;
int softreq = 0;
/*
* Call the upper-level MI hardware interrupt handler
*/
zsc_intr_hard(zsc);
/*
* Check to see if we need to schedule any software-level
* processing interrupts.
*/
softreq |= zsc->zsc_cs[0]->cs_softreq;
softreq |= zsc->zsc_cs[1]->cs_softreq;
if (softreq && (zsc_soft_scheduled == 0)) {
zsc_soft_scheduled = 1;
timeout(zscsoft, (void *) zsc, 1);
}
return 0;
}
/*
* Software-level interrupt (character processing, lower priority)
*/
static void
zscsoft(arg)
void *arg;
{
struct zsc_softc *zsc = arg;
int s;
s = spltty();
zsc_soft_scheduled = 0;
(void) zsc_intr_soft(zsc);
splx(s);
}
/*
* Read/write chip registers (dummy functions for now)
*/
u_char
zs_read_reg(cs, reg)
struct zs_chanstate *cs;
u_char reg;
{
u_char val;
*((volatile unsigned int *) cs->cs_reg_csr) =
((volatile unsigned int) reg) << 8;
tc_mb();
DELAY(5);
val = ((*(volatile unsigned int *) cs->cs_reg_csr) >> 8) & 0xff;
tc_mb();
DELAY(5);
return val;
}
void
zs_write_reg(cs, reg, val)
struct zs_chanstate *cs;
u_char reg, val;
{
*((volatile unsigned int *) cs->cs_reg_csr) =
((volatile unsigned int) reg) << 8;
tc_mb();
DELAY(5);
*((volatile unsigned int *) cs->cs_reg_csr) =
((volatile unsigned int) val) << 8;
tc_mb();
DELAY(5);
}
u_char
zs_read_csr(cs)
struct zs_chanstate *cs;
{
u_char val;
val = (*((volatile unsigned int *) cs->cs_reg_csr) >> 8) & 0xff;
tc_mb();
DELAY(5);
return val;
}
void
zs_write_csr(cs, val)
struct zs_chanstate *cs;
u_char val;
{
*((volatile unsigned int *) cs->cs_reg_csr) =
((volatile unsigned int) val) << 8;
tc_mb();
DELAY(5);
}
u_char
zs_read_data(cs)
struct zs_chanstate *cs;
{
u_char val;
val = (*((volatile unsigned int *) cs->cs_reg_data) >> 8) & 0xff;
tc_mb();
DELAY(5);
return val;
}
void
zs_write_data(cs, val)
struct zs_chanstate *cs;
u_char val;
{
*((volatile unsigned int *) cs->cs_reg_data) =
((volatile unsigned int) val) << 8;
tc_mb();
DELAY(5);
}
int
zs_set_speed(cs, bps)
struct zs_chanstate *cs;
int bps; /* bits per second */
{
int tconst, real_bps;
if (bps == 0)
return (0);
tconst = BPS_TO_TCONST(cs->cs_brg_clk, bps);
if (tconst < 0)
return (EINVAL);
/* Convert back the other way to make sure we can do it */
real_bps = TCONST_TO_BPS(cs->cs_brg_clk, tconst);
if (real_bps != bps)
return (EINVAL);
cs->cs_preg[12] = tconst;
cs->cs_preg[13] = tconst >> 8;
/*
* The caller will do the actual register stuffing, so we just
* return at this point.
*/
return (0);
}
int
zs_set_modes(cs, cflag)
struct zs_chanstate *cs;
int cflag;
{
int s;
s = splzs();
if (cflag & CRTSCTS) {
cs->cs_wr5_dtr = ZSWR5_DTR;
cs->cs_wr5_rts = ZSWR5_RTS;
cs->cs_rr0_cts = ZSRR0_CTS;
cs->cs_preg[15] |= ZSWR15_CTS_IE;
} else {
cs->cs_wr5_dtr = ZSWR5_DTR | ZSWR5_RTS;
cs->cs_wr5_rts = 0;
cs->cs_wr5_rts = 0;
cs->cs_preg[15] &= ~ZSWR15_CTS_IE;
}
splx(s);
return (0);
}
void
zs_abort(cs)
struct zs_chanstate *cs;
{
printf("zs_abort called \n");
}