/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution. 
\*************************************************************************/
/* devMz8310.c.c */
/* base/src/dev $Id$ */

/* Device Support Routines for MZ8310 */
/*
 *      Original Author: Marty Kraimer and Bob Daly
 *      Date:            6-19-91
 */


/* In order to understand the code in this file you must first	*/
/* understand the MIZAR 8310 Users Manual.			*/
/* You must also understand Chapter 1 of the 			*/
/* the Advanced Micro Devices Am9513 Technical Manual		*/

#include <vxWorks.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sysLib.h>
#include <rebootLib.h>
#include <intLib.h>
#include <vme.h>
#include <iv.h>

#include "alarm.h"
#include "dbDefs.h"
#include "dbAccess.h"
#include "recGbl.h"
#include "dbCommon.h"
#include "fast_lock.h"
#include "recSup.h"
#include "devSup.h"
#include "dbScan.h"
#include "special.h"
#include "module_types.h"
#include "eventRecord.h"
#include "pulseCounterRecord.h"
#include "pulseDelayRecord.h"
#include "pulseTrainRecord.h"
#include "epicsPrint.h"

/* Create the dsets for devMz8310 */
static long report();
static long init();
static long init_pc();
static long init_pd();
static long init_pt();
static long get_ioint_info();
static long cmd_pc();
static long write_pd();
static long write_pt();
static long cmd_pc();
typedef struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	write;} MZDSET;
/*It doesnt matter who honors report or init thus let 1st devSup do it*/
MZDSET devEventMz8310=	{ 5, report, init,    NULL, get_ioint_info, NULL};
MZDSET devPcMz8310=	{ 5,   NULL, NULL, init_pc,            NULL, cmd_pc};
MZDSET devPdMz8310=	{ 5,   NULL, NULL, init_pd,            NULL, write_pd};
MZDSET devPtMz8310=	{ 5,   NULL, NULL, init_pt,            NULL, write_pt};

/*forward references			*/
static void mz8310_int_service(IOSCANPVT);

volatile int mz8310Debug=0;
int mz8310NoShutdown=0;

#if (CPU_FAMILY==PPC)
static volatile unsigned short *mz8310_known_cardreg=0;
#endif

static unsigned short *shortaddr;
/* definitions related to fields of records*/
/* defs for gtyp and ctyp fields */

#define INTERNAL 0
#define HARDWARE 0
#define SOFTWARE 1
#define EXTERNAL 1
#define INACTIVE 0
#define ACTIVE   1
#define DISABLE  0
#define ENABLE   1
#define RISING_EDGE   0
#define FALLING_EDGE  1
#define DELAY_LOW_EDGE   0 /* valid hts values */
#define DELAY_HIGH_EDGE  1 /* valid hts values */
#define NORMAL_LOW   0
#define NORMAL_HIGH  1

#define DLY_FIELD       0x0001
#define WIDE_FIELD      0x0002
#define STV_FIELD       0x0004
#define GATE_FIELD      0x0008
#define HTS_FIELD	0x0010

/* defs for counter commands */
#define CTR_READ  pulseCounterCMD_Read
#define CTR_CLEAR pulseCounterCMD_Clear
#define CTR_START pulseCounterCMD_Start
#define CTR_STOP  pulseCounterCMD_Stop
#define CTR_SETUP pulseCounterCMD_Setup

#define SIZE_16 pulseCounterCSIZ_16_bit
#define SIZE_32 pulseCounterCSIZ_32_bit

/* defines specific to mz8310*/

static unsigned short BASE;
#define MAXCARDS	4
#define CARDSIZE	0x00000100
#define NUMINTVEC	4
#define CHIPSIZE	0x20
#define CHANONCHIP	5
#define NCHIP		2

static struct {
    unsigned char irq_lev;	/*interrupt request level  */
    unsigned char vec_addr;	/*offset from base register*/
}mz8310_strap_info[NUMINTVEC] = {
    {1,0x41},{3,0x61},{5,0x81},{6,0xA1}};

/* time unit conversion constants - want in seconds
   seconds,milliseconds,microseconds,nanoseconds,picoseconds */
 
static double cons[] = { 1,1e-3,1e-6,1e-9,1,1e-12 };

/*keep information needed for reporting*/
static struct mz8310_info {
    short present;
    FAST_LOCK lock;
    struct {
	short connected;
	short nrec_using;
	IOSCANPVT ioscanpvt;
    } int_info[NUMINTVEC];
    struct {
	short nrec_using;
    } chan_info[NCHIP][CHANONCHIP];
}mz8310_info[MAXCARDS];


#define PCMDREG(CARD,CHIP) \
( (unsigned char *)\
 ((unsigned char*)shortaddr + BASE + (CARD)*CARDSIZE + (CHIP)*CHIPSIZE + 3))
#define PDATAREG(CARD,CHIP) \
( (unsigned short *)\
 ((unsigned char*)shortaddr + BASE + (CARD)*CARDSIZE + (CHIP)*CHIPSIZE))
#define MZ8310INTVEC(CARD,INTVEC) \
( (unsigned char) \
 (MZ8310_INT_VEC_BASE + (CARD)*NUMINTVEC + (INTVEC)))
#define PVECREG(CARD,INTVEC) \
( (unsigned char *)\
 ((unsigned char*)shortaddr + BASE + (CARD)*CARDSIZE + mz8310_strap_info[(INTVEC)].vec_addr))

/*definitions for am9513 stc chip */

/* Internal clock rate and rate divisor */
#define INT_CLOCK_RATE	4e6
#define CLOCK_RATE_DIV	16e0
#define MAX_CLOCK_RATE 7e6

/* desired master mode 					*/
/* Binary Division, Enable Inc, 16-bit bus, FOUT on,	*/
/* FOUT divide by 1, Source F1, TOD Disabled		*/
#define MASTERMODE	0x2100

/* define command codes */
#define LDPCOUNTER	0x00
#define LDPMASTER	0x17
#define LDPSTATUS	0x1f
#define ARM		0x20
#define LOAD		0x40
#define LOADARM		0x60
#define DISARMSAVE	0x80
#define SAVE		0xa0
#define DISARM		0xc0
#define SETTOGOUT	0xe8
#define CLRTOGOUT	0xe0
#define STEP		0xf0
#define DISDPS		0xe8
#define GATEOFFFOUT	0xee
#define BUS16		0xef
#define ENADPS		0xe0
#define GATEONFOUT	0xe6
#define BUS8		0xe7
#define ENAPRE		0xf8
#define DISPRE		0xf9
#define RESET		0xff

/* Count Source Selection */
#define SRC1	0x0100
#define SRC2	0x0200
#define SRC3	0x0300
#define SRC4	0x0400
#define SRC5	0x0500
/*static unsigned short externalCountSource[] = {SRC1,SRC2,SRC3,SRC4,SRC5};*/
#define F1	0x0b00
#define F2	0x0c00
#define F3	0x0d00
#define F4	0x0e00
#define F5	0x0f00
static unsigned short internalCountSource[] = {F1,F2,F3,F4,F5};

/*The following are used to communicate with the mz8310.		*/

/*The mz8310 can not keep up when commands are sent as rapidly as possible.*/

#if (CPU_FAMILY==PPC)
int devMz8310wasteTimeCount = 75;	/* 75 works for a 233MHz mv2700 */
#else
int devMz8310wasteTimeCount = 0;	/* mv167 is slow enough already */
#endif

static void wasteTime()
{
    volatile int counter;
    int i;
    for(i=0; i<devMz8310wasteTimeCount; i++) counter++;
}

void putCmd(volatile unsigned char *preg,unsigned char cmd)
{
    wasteTime();
    *preg = cmd;
    if(mz8310Debug) printf("mz8310:putCmd:  pcmdreg=%p cmd=%x %u\n",
	preg,cmd,cmd);
}

void putData(volatile unsigned short *preg,unsigned short data)
{
    wasteTime();
    *preg = data;
    if(mz8310Debug) printf("mz8310:putData: preg=%p data=%x %u\n",
	preg,data,data);
}

void getCmd(volatile unsigned char *preg,unsigned char *pcmd)
{
    wasteTime();
    *pcmd = *preg;
    if(mz8310Debug) printf("mz8310:getCmd:  pcmdreg=%p cmd=%x %u\n",
	preg,*pcmd,*pcmd);
}

void getData(volatile unsigned short *preg,unsigned short *pdata)
{
    wasteTime();
    *pdata = *preg;
    if(mz8310Debug) printf("mz8310:getData: preg=%p data=%x %u\n",
	preg,*pdata,*pdata);
}

static int Mz8310_shutdown()
{
    int card,chip;
    volatile unsigned char  *pcmd;
    int maxCards;

    if(mz8310NoShutdown) return(0);
    maxCards = MAXCARDS;
    if(tm_num_channels[MZ8310] < maxCards) maxCards = tm_num_channels[MZ8310];
    for(card=0;card<maxCards;card++)
    {
	if(mz8310_info[card].present==TRUE)
	{
	    FASTLOCK(&mz8310_info[card].lock);

	    for(chip=0;chip<NCHIP;chip++)
	    {
		pcmd=PCMDREG(card,chip);
		putCmd(pcmd,RESET);
	    }
	    FASTUNLOCK(&mz8310_info[card].lock);
	}
    }
    return(0);
}


static long init(after)
    int after;
{
    int card,chip,intvec;
    volatile unsigned char  *pcmd;
    volatile unsigned short *pdata;
    unsigned short data;
    int dummy;

    if(after)return(0);
    if(sysBusToLocalAdrs(VME_AM_SUP_SHORT_IO, 0, (void *)&shortaddr)) {
	epicsPrintf ("devMz8310: sysBusToLocalAdrs failed\n",0);
	exit(1);
    }
    memset((char *)&mz8310_info[0],0,MAXCARDS*sizeof(struct mz8310_info));
    BASE = tm_addrs[MZ8310];
    for(card=0; card<MAXCARDS; card++) {
	mz8310_info[card].present = FALSE;
	FASTLOCKINIT(&mz8310_info[card].lock);
	FASTLOCK(&mz8310_info[card].lock);
	for(chip=0; chip<NCHIP; chip ++) {
	    pcmd = PCMDREG(card,chip); pdata = PDATAREG(card,chip);
	    if(vxMemProbe((char *)pcmd, READ, sizeof(*pcmd), (char *)&dummy) != OK)continue;
	    if(vxMemProbe((char *)pdata, READ, sizeof(*pdata), (char *)&dummy) != OK)continue;
	    mz8310_info[card].present = TRUE;
	    if(mz8310Debug) printf("mz8310: Found card %d chip %d\n",card,chip);
	    putCmd(pcmd,BUS16);
	    putCmd(pcmd,LDPMASTER);
	    getData(pdata,&data);
	    if(data != MASTERMODE) { /* need to reinitialize */
	        putCmd(pcmd,RESET);
	        putCmd(pcmd,BUS16);
	        putCmd(pcmd,LDPMASTER);
	        putData(pdata,MASTERMODE);
	        putCmd(pcmd,(LOAD | 0x001F)); /* issue load command for all channels*/
		if(mz8310Debug) {
		    printf("reading back master mode register\n");
		    putCmd(pcmd,LDPMASTER);
		    getData(pdata,&data);
		}
	    }
	}
	FASTUNLOCK(&mz8310_info[card].lock);
	/*Initialize I/O scanning for each interrupt vector		*/
	/*Note that interrupt vectors are not initialized until the	*/
	/*first record is attached via get_ioint_info.			*/
	if(mz8310_info[card].present) {
#if (CPU_FAMILY==PPC)
	    mz8310_known_cardreg = PDATAREG(card,0);
#endif
	    for(intvec=0; intvec<NUMINTVEC; intvec++) {
		scanIoInit(&mz8310_info[card].int_info[intvec].ioscanpvt);
	    }
	}
    }

    if(rebootHookAdd(Mz8310_shutdown)<0)
	epicsPrintf ("Mz8310_shutdown: reboot hook add failed\n");

    return(0);
}

static long report(interest)
    int  interest;
{
    int card,chip,channel,intvec;

    for(card=0; card<MAXCARDS; card++) {
	if(!mz8310_info[card].present) continue;
	printf("mz8310: card %d\n",card);
	if(interest==0)continue;
	for(chip=0; chip<NCHIP; chip++) {
	    for(channel=0; channel<CHANONCHIP; channel++) {
		short nrec_using;

		nrec_using = mz8310_info[card].chan_info[chip][channel].nrec_using;
		if(nrec_using==0) continue;
		printf("   chip: %d channel: %d nrec_using: %d\n",
			chip,channel,nrec_using);
	    }
	}
	for(intvec=0; intvec<NUMINTVEC; intvec++) {
	    short nrec_using,connected;

	    nrec_using = mz8310_info[card].int_info[intvec].nrec_using;
	    connected = mz8310_info[card].int_info[intvec].connected;
	    if(nrec_using==0 && !connected) continue;
	    printf("  intvec: %d connected: %d nrec_using: %d  eventRecord\n",
			intvec,connected,nrec_using);
	}
    }
    return(0);
}

static long init_common(pdbCommon,pout,nchan)
    struct dbCommon	*pdbCommon;
    struct link		*pout;
    int			nchan;
{
    struct vmeio *pvmeio = (struct vmeio *)(&pout->value);
    unsigned int card,signal,chip,channel;

    /* out must be an VME_IO */
    switch (pout->type) {
    case (VME_IO) :
	break;
    default :
	recGblRecordError(S_dev_badBus,(void *)pdbCommon,
		"devMz8310 (init_record) Illegal OUT Bus Type");
	return(S_dev_badBus);
    }
    card = pvmeio->card;
    signal = pvmeio->signal;
    if(card>=MAXCARDS){
	recGblRecordError(S_dev_badCard,(void *)pdbCommon,
                "devMz8310 (init_record) exceeded maximum supported cards");
	return(S_dev_badCard);
    }
    if(!mz8310_info[card].present){
	recGblRecordError(S_dev_badCard,(void *)pdbCommon,
		"devMz8310 (init_record) VME card not found");
	return(S_dev_badCard);
    }
    if(signal >= NCHIP*CHANONCHIP
    || (nchan==2 && (signal==4 || signal==9))) {
	recGblRecordError(S_dev_badSignal,(void *)pdbCommon,
		"devMz8310 (init_record) Illegal SIGNAL field");
	return(S_dev_badSignal);
    }
    chip = (signal>=CHANONCHIP ? 1 : 0);
    channel = signal - chip*CHANONCHIP;
    if((mz8310_info[card].chan_info[chip][channel].nrec_using +=1)>1) 
	recGblRecordError(S_dev_Conflict,(void *)pdbCommon,
		"devMz8310 (init_record) signal already used");
    if(nchan==2) {
	if((mz8310_info[card].chan_info[chip][channel+1].nrec_using +=1)>1)
	    recGblRecordError(S_dev_Conflict,(void *)pdbCommon,
		"devMz8310 (init_record) signal already used");
    }
    return(0);
 }

static long init_pc(pr)
    struct pulseCounterRecord	*pr;
{
    long status;
    int num_chan;

    if(pr->csiz==SIZE_16)
        num_chan=1;
    else
        num_chan=2;

    status = init_common((struct dbCommon *)pr,&pr->out,num_chan);

    if(status) return(status);

    /*just use dpvt as flag that initialization succeeded*/
    pr->dpvt = (void *)pr;

    return(0);
}

static long init_pd(pr)
    struct pulseDelayRecord	*pr;
{
    long status;
    status = init_common((struct dbCommon *)pr,&pr->out,1);
    if(status) return(status);
    /*just use dpvt as flag that initialization succeeded*/
    pr->dpvt = (void *)pr;
    return(0);
}
static long init_pt(pr)
    struct pulseTrainRecord	*pr;
{
    long status;
    status = init_common((struct dbCommon *)pr,&pr->out,1);
    if(status) return(status);
    /*just use dpvt as flag that initialization succeeded*/
    pr->dpvt = (void *)pr;
    return(0);
}

static void mz8310_int_service(IOSCANPVT ioscanpvt)
{
#if (CPU_FAMILY==PPC)
    /* Card bug: Mz8310 waits for IACK* to be negated, which the mv2700
     * only does at its next VME cycle.  Read a register on any Mz8310
     * known card to force a VME cycle and hence clear the interrupt. */
    unsigned short junk;
    getData(mz8310_known_cardreg,&junk);
#endif

    scanIoRequest(ioscanpvt);
}

static long get_ioint_info(
	int			cmd,
	struct eventRecord	*pr,
	IOSCANPVT		*ppvt)
{
	struct vmeio *pvmeio = (struct vmeio *)(&pr->inp.value);
	unsigned int	card,intvec;

	*ppvt = NULL;	/*initialize pvt to null*/
	card = pvmeio->card;
	intvec = pvmeio->signal;
	if(card>=MAXCARDS){
	    recGblRecordError(S_dev_badCard,(void *)pr,
		"devMz8310 (get_ioint_info) exceeded maximum supported cards");
	    return(0);
	}
	if(intvec>=NUMINTVEC) {
	    recGblRecordError(S_dev_badSignal,(void *)pr,
		"devMz8310 (get_ioint_info) Illegal SIGNAL field");
	    return(0);
	}
	if(!mz8310_info[card].present){
	    recGblRecordError(S_dev_badCard,(void *)pr,
		"devMz8310 (get_ioint_info) card not found");
	     return(0);
	}
	*ppvt = mz8310_info[card].int_info[intvec].ioscanpvt;
	if(cmd!=0) {
	    if(cmd==1) mz8310_info[card].int_info[intvec].nrec_using -= 1;
	    return(0);
	}
	mz8310_info[card].int_info[intvec].nrec_using +=1;
	if(!mz8310_info[card].int_info[intvec].connected) {
	    unsigned char vector;
	    unsigned char *pvecreg;

	    vector=MZ8310INTVEC(card,intvec);
	    if(intConnect(INUM_TO_IVEC((int)vector),mz8310_int_service,
	    (int)mz8310_info[card].int_info[intvec].ioscanpvt)!=OK) {
		recGblRecordError(0,(void *)pr,"devMz8310 (get_ioint_info) intConnect failed");
		return(0);
	    }
	    pvecreg = PVECREG(card,intvec);
	    if(vxMemProbe((void *)pvecreg,WRITE,sizeof(char),(void *)&vector)!=OK) {
		recGblRecordError(0,(void *)pr,"devMz8310 (get_ioint_info) vxMemProbe failed");
		return(0);
	    }
	    sysIntEnable(mz8310_strap_info[intvec].irq_lev);
	    mz8310_info[card].int_info[intvec].connected = TRUE;
	}
	return(0);
}

static long cmd_pc(pr)
    struct pulseCounterRecord	*pr;
{
    volatile unsigned char  *pcmd;
    volatile unsigned short *pdata;
    struct vmeio 	*pvmeio;
    int card,chip,channel,signal,set_chan_bits;
    unsigned short  mode;
	
    if(!pr->dpvt) return(S_dev_NoInit);

    pvmeio = (struct vmeio *)&(pr->out.value);

    card = pvmeio->card;
    signal = pvmeio->signal;

    chip = (signal>=CHANONCHIP ? 1 : 0);
    channel = signal - chip*CHANONCHIP;

    pcmd = PCMDREG(card,chip);
    pdata = PDATAREG(card,chip);

    if(pr->csiz==SIZE_32)
	set_chan_bits=3;
    else /* SIZE_16 */
	set_chan_bits=1;

    FASTLOCK(&mz8310_info[card].lock);

    switch (pr->cmd) {
	case CTR_READ:
	    putCmd(pcmd,(SAVE | (set_chan_bits<<channel)));
	    {
		unsigned short low,high;

		putCmd(pcmd,(LDPCOUNTER | 0x10 | (channel+1)));
		getData(pdata,&low);

                if(pr->csiz==SIZE_32)
		{
		   putCmd(pcmd,(LDPCOUNTER | 0x10 | (channel+2)));
		   getData(pdata,&high);
		}
		else /* SIZE_16 */
		   high=0;

		pr->val = high<<16 | low;
	    }
	    break;

	case CTR_CLEAR:
	    putCmd(pcmd,(DISARM | (set_chan_bits<<channel)));
	    putCmd(pcmd,(LOADARM | (set_chan_bits<<channel)));
	    break;

	case CTR_START:
	    putCmd(pcmd,(ARM | (set_chan_bits<<channel)));
	    break;

	case CTR_STOP:
	    putCmd(pcmd,(DISARM | (set_chan_bits<<channel)));
	    break;

	case CTR_SETUP:
	    /* setup counter i */
	    mode = 0x0028; /*Count Repeatedly, Count Up*/

	    if(pr->cnte==FALLING_EDGE)
		mode |= 0x1000; /*count on falling edge*/

	    /* If necessary set hgv control Active High Level Gate N */
	    if(pr->gtyp==INTERNAL && pr->hgv!=0)
	    {
		unsigned short hgv = (unsigned short)pr->hgv;

		if(hgv>5) recGblRecordError(S_db_badField,(void *)pr,
			"devMz8310 : illegal hgv value");
		else mode |= hgv<<13;
	    }

	    /*set count source selection*/
	    if(pr->cnts<0 || pr->cnts>15)
	    {
                recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
		recGblRecordError(S_db_badField,(void *)pr,
		    "devMz8310 : illegal cnts value");
		pr->pact=TRUE;
		break;
	    }

	    mode |= (pr->cnts << 8);

	    putCmd(pcmd,(DISARM | (set_chan_bits<<channel)));
	    putCmd(pcmd,(LDPCOUNTER | (channel+1)));
	    putData(pdata,mode);
	    putData(pdata,0x0000);

	    /* now setup high order portion of counter*/
            if(pr->csiz==SIZE_32)
	    {
	       putCmd(pcmd,(LDPCOUNTER | (channel+2)));
	       putData(pdata,0x0008);
	       putData(pdata,0x0000);
	    }

	    /* Load counter(s) */
	    putCmd(pcmd,(LOAD | (set_chan_bits<<channel)));
	    pr->udf = FALSE;
	    break;

	default:
            recGblSetSevr(pr,WRITE_ALARM,MAJOR_ALARM);
	    recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : illegal command");
	    break;
    }

    FASTUNLOCK(&mz8310_info[card].lock);
    return(0);
}  

static long write_pd(pr)
    struct pulseDelayRecord	*pr;
{
    volatile unsigned char  *pcmd;
    volatile unsigned short *pdata;
    struct vmeio 	*pvmeio;
    int card,chip,channel,signal;
    unsigned short  load,hold,mode;
    double clockRate,holdCount,loadCount;
    int clockDiv=0;
	
    if(!pr->dpvt) return(S_dev_NoInit);

    pvmeio = (struct vmeio *)&(pr->out.value);

    card = pvmeio->card;
    signal = pvmeio->signal;

    chip = (signal>=CHANONCHIP ? 1 : 0);
    channel = signal - chip*CHANONCHIP;

    pcmd = PCMDREG(card,chip);
    pdata = PDATAREG(card,chip);

    if(mz8310Debug)
    {
	printf("pfld=0x%4.4X, odly=%f, dly=%f, gate=%d, stv=%d\n",
	    pr->pfld,pr->odly,pr->dly,pr->gate,pr->stv);
    }

    if(pr->pfld==GATE_FIELD||pr->pfld==STV_FIELD)
    {
	if(pr->odly==pr->dly)
	{
	    if(pr->gate==DISABLE)
	    {
    		putCmd(pcmd,(DISARM | (1<<channel)));
		return(0);
	    }

	    if(pr->stv==ENABLE)
	    {
    		putCmd(pcmd,(ARM | (1<<channel)));
		return(0);
	    }
	}
    }

    /* compute hold count and load count */
    clockRate = (pr->ctyp==INTERNAL ? INT_CLOCK_RATE : pr->ecr);

    if(clockRate<=0 || clockRate>MAX_CLOCK_RATE) 
    {
        recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
	recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : computed illegal clock rate");
	pr->pact=TRUE;
	return(0);
    }

    holdCount = clockRate*(cons[pr->unit]*pr->wide);

    if(holdCount<1e0) holdCount = 1e0;

    loadCount = clockRate*(cons[pr->unit]*pr->dly);

    if(pr->ctyp==INTERNAL) 
    {
	clockDiv = 0;
	while(clockDiv<=5 && (loadCount>65536.0 || holdCount>65535.0)) 
	{
	    clockDiv++;
	    clockRate /= CLOCK_RATE_DIV;
    	    holdCount = clockRate*(cons[pr->unit]*pr->wide);

    	    if(holdCount<1e0) holdCount = 1e0;

    	    loadCount = clockRate*(cons[pr->unit]*pr->dly);

    	    if(loadCount<1e0) loadCount = 1e0;
        }
    }

    if(loadCount>65536.0 || holdCount>65535.0) 
    {
        recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
	recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : computed illegal clock rate");
	pr->pact=TRUE;
	return(0);
    }

    load = loadCount + .5;
    hold = holdCount + .5;

    /* compute mode */
    if(pr->ttyp==INTERNAL)
    {
    	mode = 0x0062; /* MODE L Waveform */

    	if(pr->hts==DELAY_HIGH_EDGE)
	   mode |=0xc000;	/*gate on high edge*/
    	else /* DELAY_LOW_EDGE */
	   mode |= 0xe000;	/*gate on low edge*/
    }
    else
    {
	mode = 0x0042; /* MODE G Waveform */
    }

    /* for very small delays, less then two is bad */
    if(load < 2) load=2;

    if(pr->cedg==FALLING_EDGE)
	mode |= 0x1000; /*count on falling edge*/

    /*set count source selection*/

    if(pr->ctyp==INTERNAL)
    {
	mode |= internalCountSource[clockDiv];
    }
    else
    {	/*external clock. Determine source*/
	if(pr->ecs<0 || pr->ecs>15)
	{
            recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
	    recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : illegal ecs value");
	    pr->pact=TRUE;
	    return(0);
	}
	mode |= (pr->ecs << 8);
    }

    /* setup counter */

    FASTLOCK(&mz8310_info[card].lock);

    putCmd(pcmd,(DISARM | (1<<channel)));

    /* Set initial state of output */

    putCmd(pcmd,((pr->llow==NORMAL_LOW ? CLRTOGOUT : SETTOGOUT) | (channel+1)));
    putCmd(pcmd,(LDPCOUNTER | (channel+1)));
    putData(pdata,mode);
    putData(pdata,load);
    putData(pdata,hold);

    if(mz8310Debug)
    {
	putCmd(pcmd,(LDPCOUNTER | (channel+1)));
	printf("reading mode,load,hold\n");
	getData(pdata,&mode);
	getData(pdata,&load);
	getData(pdata,&hold);
    }

    /* Load, Step, and Arm Counter */
    putCmd(pcmd,(LOAD | (1<<channel)));
    putCmd(pcmd,(STEP | (channel+1)));

    if( (pr->ttyp==INTERNAL||pr->stv==ENABLE) && (pr->gate==ENABLE) )
    	putCmd(pcmd,(ARM | (1<<channel)));

    FASTUNLOCK(&mz8310_info[card].lock);

    pr->udf = FALSE;
    return(0);
}

static long write_pt(pr)
    struct pulseTrainRecord	*pr;
{
    volatile unsigned char  *pcmd;
    volatile unsigned short *pdata;
    struct vmeio 	*pvmeio;
    int card,chip,channel,signal;
    unsigned short  load,hold,mode;
    double clockRate,periodInClockUnits,holdCount,loadCount;
    int clockDiv=0;
	
    if(!pr->dpvt) return(S_dev_NoInit);
    pvmeio = (struct vmeio *)&(pr->out.value);
    card = pvmeio->card;
    signal = pvmeio->signal;
    chip = (signal>=CHANONCHIP ? 1 : 0);
    channel = signal - chip*CHANONCHIP;
    pcmd = PCMDREG(card,chip);
    pdata = PDATAREG(card,chip);

    /* Should we just set on or off */
    if(pr->dcy<=0e0 || (pr->gtyp==SOFTWARE && pr->hgv!=0)) {
	pr->udf = FALSE;
	putCmd(pcmd,(DISARM | (1<<channel)));
	putCmd(pcmd,(LDPCOUNTER | (channel+1)));
	putData(pdata,(pr->llow==NORMAL_LOW ? 0x0000 : 0x0004));
	return(0);
    }
    if(pr->dcy>=100.0) {
	pr->udf = FALSE;
	putCmd(pcmd,(DISARM | (1<<channel)));
	putCmd(pcmd,(LDPCOUNTER | (channel+1)));
	putData(pdata,(pr->llow==NORMAL_LOW ? 0x0004 : 0x0000));
	return(0);
    }

    /* compute hold count and load count */
    clockRate = (pr->ctyp==INTERNAL ? INT_CLOCK_RATE : pr->ecr);
    periodInClockUnits = (cons[pr->unit]*pr->per) * clockRate;
    if(clockRate<=0 || clockRate>MAX_CLOCK_RATE || periodInClockUnits<1) {
        recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
	recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : computed illegal clock rate");
	pr->pact=TRUE;
	return(0);
    }
    holdCount = periodInClockUnits*pr->dcy/100.0;
    if(holdCount<1e0) holdCount = 1e0;
    loadCount = periodInClockUnits - holdCount;

    if(pr->ctyp==INTERNAL)
    {
	clockDiv = 0;

	while(clockDiv<5 && (loadCount>65536.0 || holdCount>65535.0)) 
	{
	    clockDiv++;
	    periodInClockUnits /= CLOCK_RATE_DIV;
	    holdCount = periodInClockUnits*pr->dcy/100.0;
	    if(holdCount<1e0) holdCount = 1e0;
	    loadCount = periodInClockUnits - holdCount;
        }
    }

    if(loadCount>65536.0 || holdCount>65535.0) 
    {
        recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
	recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : computed illegal clock rate");
	pr->pact=TRUE;
	return(0);
    }

    load = loadCount + .5;
    hold = holdCount + .5;

    if(load < 3) load=3;

    /* compute mode */
    mode = 0x0062; /*MODE J: reload load or hold, count repeatedly, TC toggled*/

    if(pr->cedg==FALLING_EDGE)
	mode |= 0x1000; /*count on falling edge*/

    /* If necessary set hgv control MODE K: Active High Level Gate N */
    if(pr->gtyp==INTERNAL && pr->hgv!=0)
    {
	unsigned short hgv = (unsigned short)pr->hgv;

	if(hgv>5)
	   recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : illegal hgv value");
	else
	   mode |= hgv<<13;
    }

    /*set count source selection*/
    if(pr->ctyp==INTERNAL)
    {
	mode |= internalCountSource[clockDiv];
    }
    else
    {   /*external clock. Determine source*/
	if(pr->ecs<0 || pr->ecs>15) {
            recGblSetSevr(pr,WRITE_ALARM,INVALID_ALARM);
	    recGblRecordError(S_db_badField,(void *)pr,
		"devMz8310 : illegal ecs value");
	    pr->pact=TRUE;
	    return(0);
	}
	mode |= (pr->ecs << 8);
    }

    /* setup counter */
    FASTLOCK(&mz8310_info[card].lock);
    putCmd(pcmd,(DISARM | (1<<channel)));
    /* Set initial state of output */
    putCmd(pcmd,((pr->llow==NORMAL_LOW ? CLRTOGOUT : SETTOGOUT) | (channel+1)));
    putCmd(pcmd,(LDPCOUNTER | (channel+1)));
    putData(pdata,mode);
    putData(pdata,load);
    putData(pdata,hold);
    if(mz8310Debug){
	putCmd(pcmd,(LDPCOUNTER | (channel+1)));
	printf("reading mode,load,hold\n");
	getData(pdata,&mode);
	getData(pdata,&load);
	getData(pdata,&hold);
    }
    /* Load and arm counter*/
    putCmd(pcmd,(LOADARM | (1<<channel)));
    FASTUNLOCK(&mz8310_info[card].lock);
    pr->udf = FALSE;
    return(0);
}
