mirror of
https://github.com/PDP-10/klh10.git
synced 2026-02-11 02:31:23 +00:00
331 lines
11 KiB
C
331 lines
11 KiB
C
/* DVLITES.C - Driver for Spare Time Gizmos Panda Display lights
|
||
*/
|
||
|
||
/* Copyright 2005 Mark Crispin
|
||
** All Rights Reserved
|
||
**
|
||
** This file is part of the KLH10 Distribution. Use, modification, and
|
||
** re-distribution is permitted subject to the terms in the file
|
||
** named "LICENSE", which contains the full text of the legal notices
|
||
** and should always accompany this Distribution.
|
||
**
|
||
** This program is provide "AS IS" with NO WARRANTY OF ANY KIND.
|
||
**
|
||
** This notice (including the copyright and warranty disclaimer)
|
||
** must be included in all copies or derivations of this software.
|
||
**
|
||
**
|
||
** This file is derived from PandaDisplayTest, which is
|
||
** Copyright 2004 by Spare Time Gizmos
|
||
** and is used by permission.
|
||
*/
|
||
|
||
#include "klh10.h"
|
||
|
||
#if !KLH10_DEV_LITES && CENV_SYS_DECOSF
|
||
/* Stupid gubbish needed to prevent OSF/1 AXP compiler from
|
||
** halting merely because compiled file is empty!
|
||
*/
|
||
static int decosfcclossage;
|
||
#endif
|
||
|
||
#if KLH10_DEV_LITES /* Moby conditional for entire file */
|
||
|
||
#include <sys/io.h>
|
||
#include "dvlites.h"
|
||
|
||
|
||
/* Internal function prototypes */
|
||
|
||
static void lites_wreg (unsigned char reg,unsigned char data);
|
||
|
||
|
||
#define DISABLE 0 /* convenient names */
|
||
#define ENABLE 1
|
||
|
||
/*
|
||
* DESCRIPTION:
|
||
*
|
||
* To a programmer, the Panda Display appears as a file of eight byte wide
|
||
* registers. The PC can write a register by sending the desired data to the
|
||
* parallel port data bit, selecting the desired register address with the
|
||
* INIT, AUTO LF, and SELECT IN printer status signals, and the clocking the
|
||
* printer STROBE signal. The Panda Display registers are write only - there's
|
||
* no way for the PC to ever read back from the display. In this file, the
|
||
* WriteRegister() method takes care of all the mechanics of writing a byte
|
||
* to an individual register.
|
||
*
|
||
* The first five registers, numbers 0..4, correspond to the forty data LEDs.
|
||
* Register 0 is the left most block of eight LEDs, and register 5 the right
|
||
* most. The MSB of each byte is the left most LED of the block and the LSB
|
||
* the right - pretty much what you'd expect. This isn't rocket science!
|
||
*
|
||
* Register 5 controls the four status LEDs. Bit 7 (the MSB) controls LED1,
|
||
* bit 6 LED2, bit 5 LED3 and finally, bit 4 controls LED4. The lower four
|
||
* bits of this register are currently unused.
|
||
*
|
||
* Register 6 is the display enable register - writing a 1 to the LSB of this
|
||
* register turns on the main forty data LEDs, and writing a 0 turns them off.
|
||
* The powerup state of this register (nor any of the others, for that matter)
|
||
* is not guaranteed, so the software should always initialize this register.
|
||
* Currently only the LSB of register 6 is used - the remaining 7 bits are
|
||
* ignored and should be zero. The display enable flag affects ONLY the main
|
||
* data LEDs - it has no effect on the four status LEDs. Disabling the display
|
||
* has no effect on the data currently stored in registers 0..4 nor does it
|
||
* prevent the software from loading new data in these registers; it simply
|
||
* turns off the LED drivers. Finally, note that this register is NOT affected
|
||
* by the unit selection (see register 7) - any writes to register 6 will
|
||
* change _all_ displays in the system at the same time.
|
||
*
|
||
* It's possible (if you're a really rabid blinking lights kinda person) to
|
||
* have up to four Panda Displays daisy chained on the same parallel port.
|
||
* Each display has a unique two bit address assigned to it by jumpers JP1/2,
|
||
* and register 7 is used to select the active display unit. The software
|
||
* should write a two bit unit select code to bits 6 and 7 (the LSBs) of this
|
||
* register and the display with the matching jumper settings will be selected.
|
||
*
|
||
* JP2 JP1 Unit
|
||
* --------- --------- ----
|
||
* removed removed 0
|
||
* removed installed 1
|
||
* installed removed 2
|
||
* installed installed 3
|
||
*
|
||
* Only the selected Panda Display will respond to writing registers 0..5.
|
||
* All unselected displays will ignore any writes to these registers. Registers
|
||
* 6 and 7 are always enabled regardless of the unit selected and any writes to
|
||
* these registers will affect all displays simultaneously. Finally, remember
|
||
* that this register doesn't have any guaranteed state after power up, so the
|
||
* software should always select a display as the first step.
|
||
*/
|
||
|
||
#define UNIT_PGM 0 /* unit 0: program data lights */
|
||
#define UNIX_xxx 1 /* unit 1: reserved for future assignment */
|
||
#define UNIX_yyy 2 /* unit 2: reserved for future assignment */
|
||
#define UNIX_zzz 3 /* unit 3: reserved for future assignment */
|
||
#define UNIT_MAX 3 /* maximum unit number */
|
||
|
||
|
||
/* Port address offsets */
|
||
|
||
#define PORT_DATA 0 /* offset of the data port from the base adr */
|
||
#define PORT_STATUS 1 /* " " " status " " " " " */
|
||
#define PORT_CONTROL 2 /* " " " control " " " " " */
|
||
#define PORT_MAX 3 /* number of ports used by the lights */
|
||
|
||
/* Control port definitions */
|
||
|
||
/*
|
||
* To send data to the display, the PC should first send the data byte to
|
||
* the DATA port and then generate pulse on the STROBE bit in the CONTROL
|
||
* port. The display latches all data from the PC on the _rising_ edge of
|
||
* the STROBE L signal, however remember that the parallel port hardware
|
||
* inverts this signal so that a rising edge is actually generated by the
|
||
* 1 -> 0 transition of the STROBE bit in the CONTROL register.
|
||
*/
|
||
|
||
#define STROBE 0x01
|
||
|
||
|
||
/*
|
||
* The Panda display has three "address" inputs that are used to select
|
||
* an internal register and/or a special function code. These three address
|
||
* inputs are driven by the parallel port's AUTO LF, INIT and SELECT outputs,
|
||
* which are all controlled by three bits in the CONTROL register. The AUTO
|
||
* LF and SELECT bits are actually inverted by the PC parallel port hardware
|
||
* (but INIT is not!), however the Panda board takes care of this for us and
|
||
* and they all look "normal" to us...
|
||
*/
|
||
|
||
#define RS0 0x02 /* AUTO LF */
|
||
#define RS1 0x04 /* INPUT */
|
||
#define RS2 0x08 /* SELECT (IN) */
|
||
|
||
|
||
/*
|
||
* This table defines the CONTROL port bits corresponding to each of the
|
||
* eight register addresses. It's done in this rather round-about way so that
|
||
* we don't have to depend on the register select bits being consecutive or
|
||
* even in order!
|
||
*/
|
||
|
||
static const unsigned char control[8] = {
|
||
0, RS0, RS1 , RS1|RS0,
|
||
RS2 , RS2 |RS0, RS2|RS1 , RS2|RS1|RS0
|
||
};
|
||
|
||
|
||
/* Mnemonics for the Panda Display register addresses... */
|
||
|
||
#define REG_DATA_MSB 0 /* left most 8 data LEDs */
|
||
#define REG_DATA_1 1 /* second from the left */
|
||
#define REG_DATA_2 2 /* center */
|
||
#define REG_DATA_3 3 /* second from the right */
|
||
#define REG_DATA_LSB 4 /* right most 8 data LEDs */
|
||
#define REG_STATUS 5 /* status LEDs */
|
||
#define REG_ENABLE 6 /* display enable register */
|
||
#define REG_SELECT 7 /* unit select register */
|
||
#define REG_MAX 7 /* maximum register */
|
||
|
||
/* Static data */
|
||
|
||
static int port = 0; /* parallel port, normally LPT1 (0x378) */
|
||
static int unit = -1; /* currently selected unit */
|
||
|
||
|
||
/* One-time initialization
|
||
* Accepts: port base register
|
||
* Returns: T if success, NIL if failure
|
||
*/
|
||
|
||
int lites_init (unsigned int prt)
|
||
{
|
||
int ret;
|
||
unsigned int i;
|
||
/* enable access to the port */
|
||
if (ret = !ioperm (prt,PORT_MAX,ENABLE)) {
|
||
port = prt; /* access granted, note port */
|
||
outb (0,port+PORT_CONTROL); /* initialize the displays */
|
||
for (i = 0; i <= UNIT_MAX; --i) {
|
||
lites_setdisplay (i); /* select unit */
|
||
lites_setenable (ENABLE); /* enable the display */
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
|
||
/* Set a display unit
|
||
* Accepts: unit number (0 - 3)
|
||
*
|
||
* Unfortunately, the Panda display is a write only device, so there's no
|
||
* way to know how many units are attached (or even if there are any
|
||
* attached at all, for that matter!)...
|
||
*/
|
||
|
||
void lites_setdisplay (unsigned int unt)
|
||
{
|
||
if ((unt <= UNIT_MAX) && (unit != unt)) lites_wreg (REG_SELECT,unit = unt);
|
||
}
|
||
|
||
|
||
/* Set the enable for the main data LEDs on the current display
|
||
* Accepts: enable or disable
|
||
*
|
||
* Note that this has no effect on the status LEDs, nor does it change the
|
||
* data actually stored in registers 0..4 - it simply turns off the LED
|
||
* drivers...
|
||
*/
|
||
|
||
void lites_setenable (short enable)
|
||
{
|
||
lites_wreg (REG_ENABLE,enable ? ENABLE : DISABLE);
|
||
}
|
||
|
||
/* Display data lights
|
||
* Accepts: 5 bytes of data
|
||
*
|
||
* Send to the main data LEDs. Remember that register 0 is the left most
|
||
* (the MSBs) and register 4 the right most.
|
||
*/
|
||
|
||
void lites_data (unsigned char data[5])
|
||
{
|
||
lites_wreg (REG_DATA_MSB,data[0]);
|
||
lites_wreg (REG_DATA_1,data[1]);
|
||
lites_wreg (REG_DATA_2,data[2]);
|
||
lites_wreg (REG_DATA_3,data[3]);
|
||
lites_wreg (REG_DATA_LSB,data[4]);
|
||
}
|
||
|
||
|
||
/* Display status lights
|
||
* Accepts: LED status
|
||
*
|
||
* Since these all occupy the same register, there's no way to set them
|
||
* individually...
|
||
*/
|
||
|
||
void lites_status (unsigned char data)
|
||
{
|
||
lites_wreg (REG_STATUS,data & STATUS_LEDS);
|
||
}
|
||
|
||
/* Write byte to the specified register
|
||
* Accepts: register
|
||
* data byte
|
||
*
|
||
* It's not safe to change the register select bits and the STROBE at
|
||
* the same time. That's because there's no guarantee that the bits will
|
||
* all change simultaneously at the other end of the cable (because of
|
||
* variations in propagation delay, slew rate, ringing, etc) and any skew
|
||
* between the strobe and the address bits would cause glitches on the
|
||
* register clocks. The only safe thing is to set the register address
|
||
* and then pulse the STROBE while holding the register constant.
|
||
*/
|
||
|
||
static void lites_wreg (unsigned char reg,unsigned char data)
|
||
{
|
||
/* if port initted and register valid */
|
||
if (port && (reg <= REG_MAX)) {
|
||
/* send the data */
|
||
outb (data,port + PORT_DATA);
|
||
/* select the register */
|
||
outb (control[reg],port + PORT_CONTROL);
|
||
/* pulse strobe */
|
||
outb (control[reg] | STROBE,port + PORT_CONTROL);
|
||
outb (control[reg],port + PORT_CONTROL);
|
||
}
|
||
}
|
||
|
||
/* Routines specific to the primary display lights */
|
||
|
||
static unsigned char byte0 = 0; /* save of aux bits and high 4 pgm lites */
|
||
|
||
|
||
/* Send 36-bit program data to the program data lights
|
||
* Accepts: lh
|
||
* rh
|
||
*/
|
||
|
||
void lights_pgmlites (unsigned long lh,unsigned long rh)
|
||
{
|
||
unsigned char data[5];
|
||
lites_setdisplay (UNIT_PGM); /* select program display lights unit */
|
||
/* calculate MSB with aux bits */
|
||
byte0 = data[0] = ((lh >> 14) & 0xf) | (byte0 & 0xe0);
|
||
data[1] = (lh >> 6) & 0xff; /* calculate other bytes */
|
||
data[2] = ((lh << 2) | (rh >> 16)) & 0xff;
|
||
data[3] = (rh >> 8) & 0xff;
|
||
data[4] = rh & 0xff;
|
||
lites_data (data); /* send data */
|
||
}
|
||
|
||
|
||
/* Send 3-bit auxillary data to the program data lights
|
||
*/
|
||
|
||
void lights_pgmaux (unsigned char aux)
|
||
{
|
||
lites_setdisplay (UNIT_PGM); /* select program display lights unit */
|
||
/* rewrite just the MSB */
|
||
lites_wreg (REG_DATA_MSB,byte0 = (byte0 & 0xf) | ((aux & 0x7) << 5));
|
||
}
|
||
|
||
|
||
/* System heartbeat
|
||
*/
|
||
|
||
void lights_status (char cpu,char disk,char tape,char net,char push)
|
||
{
|
||
static unsigned char state = 0;
|
||
state |= (cpu ? STATUS_LED1 : 0) | (disk ? STATUS_LED2 : 0) |
|
||
(tape ? STATUS_LED3 : 0) | (net ? STATUS_LED4 : 0);
|
||
if (push) { /* push the state? */
|
||
lites_status (state); /* yes, do so */
|
||
state = 0; /* clear state for next time */
|
||
}
|
||
}
|
||
|
||
#endif /* KLH10_DEV_LITES */
|