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");
}