simh-testsetgenerator/AltairZ80/s100_hayes.c
2020-08-16 09:31:19 +02:00

716 lines
23 KiB
C

/* s100_hayes: D.C. Hayes 80-103A and Micromodem-100
Copyright (c) 2020, Patrick Linstruth <patrick@deltecent.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Charles E. Owen shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from Charles E. Owen.
This device emulates D.C. Hayes 80-103A and Micromodem 100 communications
adapters.
To provide any useful funcationality, this device need to be attached to
a socket or serial port. Enter "HELP HAYES" at the "simh>" prompt for
additional information.
*/
#include <stdio.h>
#include "altairz80_defs.h"
#include "sim_tmxr.h"
#define HAYES_NAME "HAYES MODEM"
#define HAYES_SNAME "HAYES"
#define HAYES_WAIT 500 /* Service Wait Interval */
#define HAYES_IOBASE 0x80
#define HAYES_IOSIZE 4
#define HAYES_REG0 0 /* Relative Address 0 */
#define HAYES_REG1 1 /* Relative Address 1 */
#define HAYES_REG2 2 /* Relative Address 2 */
#define HAYES_REG3 3 /* Relative Address 3 */
#define HAYES_RRF 0x01 /* Receive Data Register Full */
#define HAYES_TRE 0x02 /* Transmit Data Register Empty */
#define HAYES_PE 0x04 /* Parity Error */
#define HAYES_FE 0x08 /* Framing Error */
#define HAYES_OE 0x10 /* Overrun */
#define HAYES_TMR 0x20 /* Timer Status */
#define HAYES_CD 0x40 /* Overrun */
#define HAYES_RI 0x80 /* Not Ring Indicator */
#define HAYES_BRS 0x01 /* Baud Rate Select */
#define HAYES_TXE 0x02 /* Transmitter Enable */
#define HAYES_ORIG 0x04 /* Mode Select */
#define HAYES_MS 0x04 /* Mode Select */
#define HAYES_BK 0x08 /* Exchange Mark & Space */
#define HAYES_ST 0x10 /* Self Test Mode */
#define HAYES_TIE 0x20 /* Transmit Interrupt Enable */
#define HAYES_OH 0x80 /* Off Hook */
#define HAYES_5BIT 0x00 /* 5 Data Bits */
#define HAYES_6BIT 0x02 /* 6 Data Bits */
#define HAYES_7BIT 0x04 /* 7 Data Bits */
#define HAYES_8BIT 0x06 /* 8 Data Bits */
#define HAYES_BMSK 0x06 /* Data Bits Bit Mask */
#define HAYES_OPAR 0x00 /* Odd Parity */
#define HAYES_EPAR 0x01 /* Odd Parity */
#define HAYES_PI 0x10 /* Parity Inhibit */
#define HAYES_PMSK 0x11 /* Parity Bit Mask */
#define HAYES_1SB 0x00 /* 1 Stop Bit */
#define HAYES_15SB 0x08 /* 1.5 Stop Bits */
#define HAYES_2SB 0x08 /* 2 Stop Bits */
#define HAYES_SMSK 0x08 /* Stop Bits Bit Mask */
#define HAYES_LMSK 0x1F /* Line Bit Bask */
#define HAYES_CLOCK 2500 /* Rate Generator / 100 */
#define HAYES_BAUD 300 /* Default baud rate */
/* Debug flags */
#define STATUS_MSG (1 << 0)
#define ERROR_MSG (1 << 1)
#define VERBOSE_MSG (1 << 2)
#define DEBUG_MSG (1 << 3)
/* IO Read/Write */
#define IO_RD 0x00 /* IO Read */
#define IO_WR 0x01 /* IO Write */
typedef struct {
PNP_INFO pnp; /* Must be first */
TMLN *tmln; /* TMLN pointer */
TMXR *tmxr; /* TMXR pointer */
int32 baud; /* Baud rate */
int32 txp; /* Transmit Pending */
int32 dtr; /* DTR Status */
int32 ireg0; /* In Register 0 */
int32 ireg1; /* In Register 1 */
int32 oreg0; /* Out Register 0 */
int32 oreg1; /* Out Register 1 */
int32 oreg2; /* Out Register 2 */
int32 oreg3; /* Out Register 3 */
int32 intmsk; /* Interrupt Mask */
uint32 timer; /* 50ms Timer */
uint32 flags; /* Original Flags */
} HAYES_CTX;
extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap);
static const char* hayes_description(DEVICE *dptr);
static t_stat hayes_svc(UNIT *uptr);
static t_stat hayes_reset(DEVICE *dptr);
static t_stat hayes_attach(UNIT *uptr, CONST char *cptr);
static t_stat hayes_detach(UNIT *uptr);
static t_stat hayes_config_line(UNIT *uptr);
static t_stat hayes_set_dtr(UNIT *uptr, int32 flag);
static int32 hayes_io(int32 addr, int32 io, int32 data);
static int32 hayes_reg0(int32 io, int32 data);
static int32 hayes_reg1(int32 io, int32 data);
static int32 hayes_reg2(int32 io, int32 data);
static int32 hayes_reg3(int32 io, int32 data);
/* Debug Flags */
static DEBTAB hayes_dt[] = {
{ "STATUS", STATUS_MSG, "Status messages" },
{ "ERROR", ERROR_MSG, "Error messages" },
{ "VERBOSE", VERBOSE_MSG, "Verbose messages" },
{ "DEBUG", DEBUG_MSG, "Debug messages" },
{ NULL, 0 }
};
/* Terminal multiplexer library descriptors */
static TMLN hayes_tmln[] = { /* line descriptors */
{ 0 }
};
static TMXR hayes_tmxr = { /* multiplexer descriptor */
1, /* number of terminal lines */
0, /* listening port (reserved) */
0, /* master socket (reserved) */
hayes_tmln, /* line descriptor array */
NULL, /* line connection order */
NULL /* multiplexer device (derived internally) */
};
static MTAB hayes_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE",
&set_iobase, &show_iobase, NULL, "Sets MITS 2SIO base I/O address" },
{ 0 }
};
static HAYES_CTX hayes_ctx = {{0, 0, HAYES_IOBASE, HAYES_IOSIZE}, hayes_tmln, &hayes_tmxr, HAYES_BAUD, 1};
static UNIT hayes_unit[] = {
{ UDATA (&hayes_svc, UNIT_ATTABLE | UNIT_DISABLE, 0), HAYES_WAIT },
};
static REG hayes_reg[] = {
{ HRDATAD (IREG0, hayes_ctx.ireg0, 8, "HAYES input register 0"), },
{ HRDATAD (IREG1, hayes_ctx.ireg1, 8, "HAYES input register 1"), },
{ HRDATAD (OREG0, hayes_ctx.oreg0, 8, "HAYES output register 0"), },
{ HRDATAD (OREG1, hayes_ctx.oreg1, 8, "HAYES output register 1"), },
{ HRDATAD (OREG2, hayes_ctx.oreg2, 8, "HAYES output register 2"), },
{ HRDATAD (OREG3, hayes_ctx.oreg3, 8, "HAYES output register 3"), },
{ HRDATAD (TXP, hayes_ctx.txp, 8, "HAYES TX data pending"), },
{ HRDATAD (DTR, hayes_ctx.dtr, 8, "HAYES DTR status"), },
{ DRDATAD (BAUD, hayes_ctx.baud, 8, "HAYES baud rate"), },
{ HRDATAD (INTMSK, hayes_ctx.intmsk, 8, "HAYES interrupt mask"), },
{ FLDATAD (RRF, hayes_ctx.ireg1, 0, "HAYES RRF status"), },
{ FLDATAD (TRE, hayes_ctx.ireg1, 1, "HAYES TRE status"), },
{ FLDATAD (PE, hayes_ctx.ireg1, 2, "HAYES PE status"), },
{ FLDATAD (FE, hayes_ctx.ireg1, 3, "HAYES FE status"), },
{ FLDATAD (OE, hayes_ctx.ireg1, 4, "HAYES OE status"), },
{ FLDATAD (TMR, hayes_ctx.ireg1, 5, "HAYES TMR status"), },
{ FLDATAD (CD, hayes_ctx.ireg1, 6, "HAYES CD status"), },
{ FLDATAD (RI, hayes_ctx.ireg1, 7, "HAYES NOT RINGING status"), },
{ FLDATAD (TXE, hayes_ctx.oreg2, 1, "HAYES TXE status"), },
{ FLDATAD (ST, hayes_ctx.oreg2, 4, "HAYES ST status"), },
{ FLDATAD (OH, hayes_ctx.oreg2, 7, "HAYES OH status"), },
{ DRDATAD (TIMER, hayes_ctx.timer, 32, "Hayes timer ms"), },
{ DRDATAD (WAIT, hayes_unit[0].wait, 32, "HAYES wait cycles"), },
{ NULL }
};
DEVICE hayes_dev = {
HAYES_SNAME, /* name */
hayes_unit, /* unit */
hayes_reg, /* registers */
hayes_mod, /* modifiers */
1, /* # units */
10, /* address radix */
31, /* address width */
1, /* address increment */
8, /* data radix */
8, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&hayes_reset, /* reset routine */
NULL, /* boot routine */
&hayes_attach, /* attach routine */
&hayes_detach, /* detach routine */
&hayes_ctx, /* context */
(DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_MUX), /* flags */
0, /* debug control */
hayes_dt, /* debug flags */
NULL, /* mem size routine */
NULL, /* logical name */
NULL, /* help */
NULL, /* attach help */
NULL, /* context for help */
&hayes_description /* description */
};
static const char* hayes_description(DEVICE *dptr)
{
return HAYES_NAME;
}
static t_stat hayes_reset(DEVICE *dptr)
{
HAYES_CTX *xptr;
xptr = dptr->ctxt;
/* Connect/Disconnect I/O Ports at base address */
if(sim_map_resource(xptr->pnp.io_base, xptr->pnp.io_size, RESOURCE_TYPE_IO, &hayes_io, dptr->name, dptr->flags & DEV_DIS) != 0) {
sim_debug(ERROR_MSG, dptr, "error mapping I/O resource at 0x%02x.\n", xptr->pnp.io_base);
return SCPE_ARG;
}
/* Set DEVICE for this UNIT */
dptr->units[0].dptr = dptr;
/* Enable TMXR modem control passthru */
tmxr_set_modem_control_passthru(xptr->tmxr);
/* Reset status registers */
xptr->ireg0 = 0;
xptr->ireg1 = HAYES_RI;
xptr->oreg1 = HAYES_8BIT | HAYES_PI;
xptr->oreg2 = 0;
xptr->oreg3 = 0;
xptr->txp = 0;
xptr->dtr = 0;
xptr->intmsk = 0;
xptr->timer = 0;
xptr->baud = HAYES_BAUD;
if (!(dptr->flags & DEV_DIS)) {
sim_activate(&dptr->units[0], dptr->units[0].wait);
} else {
sim_cancel(&dptr->units[0]);
}
sim_debug(STATUS_MSG, dptr, "reset adapter.\n");
return SCPE_OK;
}
static t_stat hayes_svc(UNIT *uptr)
{
HAYES_CTX *xptr;
int32 c,s,ireg1;
t_stat r;
uint32 ms;
xptr = uptr->dptr->ctxt;
/* Check for new incoming connection */
if (uptr->flags & UNIT_ATT) {
if (tmxr_poll_conn(xptr->tmxr) >= 0) { /* poll connection */
sim_debug(STATUS_MSG, uptr->dptr, "new connection.\n");
}
}
/* Update incoming modem status bits */
if (uptr->flags & UNIT_ATT) {
tmxr_set_get_modem_bits(xptr->tmln, 0, 0, &s);
ireg1 = xptr->ireg1;
xptr->ireg1 &= ~HAYES_RI;
xptr->ireg1 |= (s & TMXR_MDM_RNG) ? 0 : HAYES_RI; /* Active Low */
/* RI status changed */
if ((ireg1 ^ xptr->ireg1) & HAYES_RI) {
sim_debug(STATUS_MSG, uptr->dptr, "RI state changed to %s.\n", (xptr->ireg1 & HAYES_RI) ? "LOW" : "HIGH");
/*
** The Hayes does not have DTR or RTS control signals.
** TMXR will not accept a socket connection unless DTR
** is active and there is no way to tell TMXR to ignore
** them, so we raise DTR here on RI.
*/
if (!(xptr->ireg1 & HAYES_RI)) { /* RI is active low */
hayes_set_dtr(uptr, 1);
}
}
xptr->ireg1 &= ~HAYES_CD;
xptr->ireg1 |= (s & TMXR_MDM_DCD) ? HAYES_CD : 0; /* Active High */
/* CD status changed */
if ((ireg1 ^ xptr->ireg1) & HAYES_CD) {
sim_debug(STATUS_MSG, uptr->dptr, "CD state changed to %s.\n", (xptr->ireg1 & HAYES_CD) ? "HIGH" : "LOW");
/*
** The Hayes does not have DTR or RTS control signals.
** TMXR will not maintain a socket connection unless DTR
** is active and there is no way to tell TMXR to
** ignore them, so we drop DTR here on loss of CD.
*/
if (!(xptr->ireg1 & HAYES_CD)) {
hayes_set_dtr(uptr, 0);
}
}
}
/* TX data */
if (xptr->txp && xptr->oreg2 & HAYES_TXE) {
if (uptr->flags & UNIT_ATT) {
r = tmxr_putc_ln(xptr->tmln, xptr->oreg0);
} else {
r = sim_putchar(xptr->oreg0);
}
xptr->txp = 0; /* Reset TX Pending */
if (r == SCPE_LOST) {
sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n");
}
}
/* Update TRE if not set and no character pending */
if (!xptr->txp && !(xptr->ireg1 & HAYES_TRE)) {
if (uptr->flags & UNIT_ATT) {
tmxr_poll_tx(xptr->tmxr);
xptr->ireg1 |= (tmxr_txdone_ln(xptr->tmln)) ? HAYES_TRE : 0;
} else {
xptr->ireg1 |= HAYES_TRE;
}
}
/* Check for Data if RX buffer empty */
if (!(xptr->ireg1 & HAYES_RRF)) {
if (uptr->flags & UNIT_ATT) {
tmxr_poll_rx(xptr->tmxr);
c = tmxr_getc_ln(xptr->tmln);
} else {
c = sim_poll_kbd();
}
if (c & (TMXR_VALID | SCPE_KFLAG)) {
xptr->ireg0 = c & 0xff;
xptr->ireg1 |= HAYES_RRF;
xptr->ireg1 &= ~(HAYES_FE | HAYES_OE | HAYES_PE);
}
}
/* Timer */
ms = sim_os_msec();
if (xptr->timer && ms > xptr->timer) {
if (!(xptr->ireg1 & HAYES_TMR)) {
sim_debug(VERBOSE_MSG, uptr->dptr, "50ms timer triggered.\n");
}
xptr->ireg1 |= HAYES_TMR;
}
/* Don't let TMXR clobber our wait time */
uptr->wait = HAYES_WAIT;
sim_activate_abs(uptr, uptr->wait);
return SCPE_OK;
}
/* Attach routine */
static t_stat hayes_attach(UNIT *uptr, CONST char *cptr)
{
HAYES_CTX *xptr;
t_stat r;
xptr = uptr->dptr->ctxt;
sim_debug(VERBOSE_MSG, uptr->dptr, "attach (%s).\n", cptr);
if ((r = tmxr_attach(xptr->tmxr, uptr, cptr)) == SCPE_OK) {
xptr->flags = uptr->flags; /* Save Flags */
xptr->tmln->rcve = 1;
hayes_config_line(uptr);
/*
** The Hayes does not have DTR or RTS control signals.
** We raise RTS here for use to provide DCD/RI signals.
** We drop DTR as that is tied to the other functions.
*/
tmxr_set_get_modem_bits(xptr->tmln, TMXR_MDM_RTS, TMXR_MDM_DTR, NULL);
xptr->dtr = 0;
sim_debug(STATUS_MSG, uptr->dptr, "Raising RTS. Dropping DTR.\n");
sim_activate(uptr, uptr->wait);
sim_debug(VERBOSE_MSG, uptr->dptr, "activated service.\n");
}
return r;
}
/* Detach routine */
static t_stat hayes_detach(UNIT *uptr)
{
HAYES_CTX *xptr;
sim_debug(VERBOSE_MSG, uptr->dptr, "detach.\n");
if (uptr->flags & UNIT_ATT) {
xptr = uptr->dptr->ctxt;
uptr->flags = xptr->flags; /* Restore Flags */
sim_cancel(uptr);
return (tmxr_detach(xptr->tmxr, uptr));
}
return SCPE_UNATT;
}
static t_stat hayes_config_line(UNIT *uptr)
{
HAYES_CTX *xptr;
char config[20];
char b,p,s;
t_stat r = SCPE_IERR;
xptr = uptr->dptr->ctxt;
if (xptr != NULL) {
switch (xptr->oreg1 & HAYES_BMSK) {
case HAYES_5BIT:
b = '5';
break;
case HAYES_6BIT:
b = '6';
break;
case HAYES_7BIT:
b = '7';
break;
case HAYES_8BIT:
default:
b = '8';
break;
}
switch (xptr->oreg1 & HAYES_PMSK) {
case HAYES_OPAR:
p = 'O';
break;
case HAYES_EPAR:
p = 'E';
break;
default:
p = 'N';
break;
}
switch (xptr->oreg1 & HAYES_SMSK) {
case HAYES_2SB:
s = '2';
break;
case HAYES_1SB:
default:
s = '1';
break;
}
sprintf(config, "%d-%c%c%c", xptr->baud, b,p,s);
r = tmxr_set_config_line(xptr->tmln, config);
if (r) {
sim_debug(ERROR_MSG, uptr->dptr, "error %d setting port configuration to '%s'.\n", r, config);
} else {
sim_debug(STATUS_MSG, uptr->dptr, "port configuration set to '%s'.\n", config);
}
/*
** AltairZ80 and TMXR refuse to want to play together
** nicely when the CLOCK register is set to anything
** other than 0.
**
** This work-around is for those of us that may wish
** to run irrelevant, old software, that use TMXR and
** rely on some semblance of timing (Remote CP/M, BYE,
** RBBS, PCGET/PUT, Xmodem, MEX, Modem7, or most
** other communications software), on contemprary
** hardware.
**
** Serial ports are self-limiting and sockets will run
** at the clocked CPU speed.
*/
xptr->tmln->txbps = 0; /* Get TMXR's rate-limiting out of our way */
xptr->tmln->rxbps = 0; /* Get TMXR's rate-limiting out of our way */
}
return r;
}
static t_stat hayes_set_dtr(UNIT *uptr, int32 flag)
{
HAYES_CTX *xptr;
t_stat r = SCPE_IERR;
xptr = uptr->dptr->ctxt;
if (xptr != NULL) {
if (xptr->dtr && !flag) {
r = tmxr_set_get_modem_bits(xptr->tmln, 0, TMXR_MDM_DTR, NULL);
sim_debug(STATUS_MSG, uptr->dptr, "Dropping DTR.\n");
} else if (!xptr->dtr && flag) {
r = tmxr_set_get_modem_bits(xptr->tmln, TMXR_MDM_DTR, 0, NULL);
sim_debug(STATUS_MSG, uptr->dptr, "Raising DTR.\n");
}
xptr->dtr = flag;
}
return r;
}
static int32 hayes_io(int32 addr, int32 io, int32 data)
{
int32 r;
addr &= 0xff;
data &= 0xff;
if (io == IO_WR) {
sim_debug(VERBOSE_MSG, &hayes_dev, "OUT %02X,%02X\n", addr , data);
} else {
sim_debug(VERBOSE_MSG, &hayes_dev, "IN %02X\n", addr);
}
switch (addr & 0x03) {
case HAYES_REG0:
r = hayes_reg0(io, data);
break;
case HAYES_REG1:
r = hayes_reg1(io, data);
break;
case HAYES_REG2:
r = hayes_reg2(io, data);
break;
case HAYES_REG3:
r = hayes_reg3(io, data);
break;
}
return(r);
}
/*
** Register 0
**
** Input: Data
** Output: Data
*/
static int32 hayes_reg0(int32 io, int32 data)
{
HAYES_CTX *xptr;
int32 r;
xptr = hayes_dev.ctxt;
if (io == IO_RD) {
r = xptr->ireg0;
xptr->ireg1 &= ~(HAYES_RRF);
} else {
xptr->oreg0 = data;
xptr->ireg1 &= ~(HAYES_TRE);
xptr->txp = 1;
r = 0x00;
}
return(r);
}
/*
** Register 1
**
** Input: RI,CD,X,OE,FE,PE,TRE,RRF
** Output: X,X,X,PI,SBS,LS2,LS1,EPE
*/
static int32 hayes_reg1(int32 io, int32 data)
{
HAYES_CTX *xptr;
int32 r;
xptr = hayes_dev.ctxt;
if (io == IO_RD) {
r = xptr->ireg1;
xptr->ireg1 &= ~(HAYES_FE | HAYES_OE | HAYES_PE);
} else {
xptr->oreg1 = data; /* Set UART configuration */
hayes_config_line(&hayes_dev.units[0]);
r = 0x00;
}
return(r);
}
/*
** Register 2
**
** Input: N/A
** Output: OH,X,TIE,ST,BK,MS,TXE,BRS
*/
static int32 hayes_reg2(int32 io, int32 data)
{
HAYES_CTX *xptr;
int32 oreg2;
xptr = hayes_dev.ctxt;
if (io == IO_WR) {
oreg2 = xptr->oreg2; /* Save previous value */
xptr->oreg2 = data; /* Set new value */
sim_debug(DEBUG_MSG, &hayes_dev, "oreg2 %02X -> %02X\n", oreg2, data);
if (((oreg2 ^ data) & HAYES_OH)) { /* Did OH status change? */
sim_debug(STATUS_MSG, &hayes_dev, "Going %s hook.\n", (data & HAYES_OH) ? "OFF" : "ON");
if (!(data & HAYES_OH)) { /* Drop DTR if going ON HOOK */
hayes_set_dtr(&hayes_dev.units[0], 0);
}
}
/* Raise DTR if ORIGINATE and OFF HOOK */
if (((oreg2 ^ data) & (HAYES_ORIG | HAYES_OH))) { /* Did MODE or OH status change? */
if ((data & HAYES_ORIG) && (data & HAYES_OH)) {
hayes_set_dtr(&hayes_dev.units[0], 1);
}
}
/* Did the line configuration change? */
if ((oreg2 & HAYES_LMSK) != (data & HAYES_LMSK)) {
xptr->baud = (data & HAYES_BRS) ? 300 : 110;
hayes_config_line(&hayes_dev.units[0]);
}
}
return(0x00);
}
/*
** Register 3
**
** Input: N/A
** Output: N/A
*/
static int32 hayes_reg3(int32 io, int32 data)
{
HAYES_CTX *xptr;
xptr = hayes_dev.ctxt;
if (io == IO_WR) {
xptr->timer = sim_os_msec() + 50; /* Set timeout to 50ms */
xptr->ireg1 &= ~(HAYES_TMR); /* Clear timer status */
}
return(0x00);
}