#include "prtrans.h"

/*
 * Note that for the Amiga only the pramiga cable is supported. This is
 * because nobody would/could use the pc64 or 64net cables, because they
 * are for compatibility with pc software. To make up for this, we can use
 * the same prlink cable to its full potential by using all 8 data lines
 * bidirectionally.
 *
 * To enable this, define PRLINK88 (in the Makefile below), and don't forget
 * to set the cable type to prlink88 in the pet/vic/64 side programs as
 * well.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <exec/exec.h>
#include <resources/misc.h>
#include <hardware/cia.h>
#include <clib/exec_protos.h>
#include <clib/misc_protos.h>

/*
 * The CIAs are volatile, otherwise the while loop waiting for ACK to
 * change is "optimized".
 */
extern volatile struct CIA ciaa;
extern volatile struct CIA ciab;

struct Library *MiscBase;
static unsigned long MiscAllocated;
static unsigned char ack;
int		bounce;
char		myname[] = "pramiga.c";

#define BUSY	(1 << CIAB_PRTRBUSY)	/* == 0x01 */
#define POUT	(1 << CIAB_PRTRPOUT)	/* == 0x02 */
#define NO_ASM	1

int
prinit(void)
{
    atexit(prclose);

    MiscBase = OpenResource("misc.resource");
    if (MiscBase == NULL) {
	printf("Can't open misc.resource\n");
	return -1;
    } {
	const char     *user;

	user = AllocMiscResource(MR_PARALLELPORT, myname);
	if (user != NULL && user != myname) {
	    printf("Printer data lines already in use by %s.\n", user);
	    return -2;
	}
	MiscAllocated = 1 << MR_PARALLELPORT;
#ifndef TRANSNIB
	user = AllocMiscResource(MR_PARALLELBITS, myname);
	if (user != NULL && user != myname) {
	    printf("Printer control lines already in use by %s.\n", user);
	    return -3;
	}
	MiscAllocated |= 1 << MR_PARALLELBITS;
#endif
    }

#ifndef TRANSNIB
    /* turn POUT into output (BUSY is input) */
    ciab.ciaddra = (ciab.ciaddra | POUT) & ~BUSY;
    ciab.ciapra |= POUT;	/* and set it high */
    ack = ciab.ciapra & BUSY;	/* get the current state of the BUSY line */
#endif
    ciaa.ciaddrb = 0xFF;	/* set all lines to output */
    ciaa.ciaprb = 0xFF; 	/* and high */

    return 0;
}

void
prclose(void)
{
    if (bounce != 0) {
	printf("*** bounce, bounce! (%d times)\n", bounce);
	bounce = 0;
    }
#ifndef TRANSNIB
    if (MiscAllocated & (1 << MR_PARALLELBITS)) {
	ciab.ciaddra &= ~POUT;	/* turn POUT back into input */
	FreeMiscResource(MR_PARALLELBITS);
    }
#endif
    if (MiscAllocated & (1 << MR_PARALLELPORT)) {
	ciaa.ciaprb = 0xFF;
	ciaa.ciaddrb = 0xFF;	/* set all data lines to output */
	FreeMiscResource(MR_PARALLELPORT);
    }
    MiscAllocated = 0;
    /* There is no CloseResource() function, really! */
}

void
output(unsigned char byte)
{
    send(&byte, 1);
}

unsigned
input(void)
{
    unsigned char   byte;

    receive(&byte, 1);
    return byte;
}

unsigned
wait_input(void)
{
#ifdef TRANSNIB
    while (ciaa.ciaprb & 0x40)	/* wait for DRDY to go low */
	usleep (SLEEP_TIME);
#else
    ciab.ciapra &= ~POUT;		/* drop the -FLAG to low */
    while (ack == (BUSY & ciab.ciapra)) /* wait for BUSY to change */
	usleep (SLEEP_TIME);
#endif
    return input();
}

#ifndef TRANSNIB
void
send(unsigned char *buffer, unsigned length)
{
    ciaa.ciaddrb = 0xFF;	/* set all lines to output */

#if defined(__GNUC__) && !NO_ASM
/* INDENT OFF */
    asm("
	subql #1,%1
	moveq #-1,d0
	cmpl %1,d0
	jeq .send
.sbyteloop:
	moveb %2@+,%4@
	andb %6,%5@
.sackloop:
	moveb %5@,d0
	andb %7,d0
	cmpb %3,d0
	jeq .sackloop

	orb %8,%5@
	eorb %7,%3
	dbra %1,.sbyteloop
	clrw %1
	subql #1,%1
	jcc .sbyteloop
.send:
    " : "=d" (ack)              /* 0 output */
      : "d" (length),           /* 1 input */
	"a" (buffer),           /* 2 */
	"0" (ack),              /* 3 */
	"a" (&ciaa.ciaprb),     /* 4 */
	"a" (&ciab.ciapra),     /* 5 */
	"g" (~POUT),            /* 6 */
	"g" (BUSY),             /* 7 */
	"g" (POUT)              /* 8 */
      : "d0"                    /* clobbered */
    );
    /* INDENT ON */
#else
    while (length--) {
	ciaa.ciaprb = *buffer++;
	ciab.ciapra &= ~POUT;	/* drop the -FLAG to low */
	while (ack == (BUSY & ciab.ciapra));	/* wait for BUSY to change */
	ciab.ciapra |= POUT;	/* raise -FLAG again */
	ack ^= BUSY;		/* store the new state of -BUSY */
    }
#endif
}

#ifdef PRLINK88
void
receive(unsigned char *buffer, unsigned length)
{

    ciaa.ciaddrb = 0x00;	/* set the bidirectional signals to input */

#if defined(__GNUC__) && !NO_ASM
/* INDENT OFF */
    asm("
	subql #1,%1
	moveq #-1,d0
	cmpl %1,d0
	jeq .rend

.rbyteloop:
	andb %6,%5@
.rackloop:
	moveb %5@,d0
	andb %7,d0
	cmpb %3,d0
	jeq .rackloop

	orb %8,%5@
	moveb %4@,%2@+

	eorb %7,%3
	dbra %1,.rbyteloop
	clrw %1
	subql #1,%1
	jcc .rbyteloop

.rend:
    " : "=d" (ack)              /* 0 output */
      : "d" (length),           /* 1 input */
	"a" (buffer),           /* 2 */
	"0" (ack),              /* 3 */
	"a" (&ciaa.ciaprb),     /* 4 data */
	"a" (&ciab.ciapra),     /* 5 ctl */
	"g" (~POUT),            /* 6 */
	"g" (BUSY),             /* 7 */
	"g" (POUT)              /* 8 */
      : "d0"                    /* clobbered */
    );
    /* INDENT ON */
#else
    while (length--) {
	ciab.ciapra &= ~POUT;	/* drop the -FLAG to low */
bounced:
	while (ack == (BUSY & ciab.ciapra));	/* wait for BUSY to change */
	*buffer++ = ciaa.ciaprb;/* read the byte */
	ciab.ciapra |= POUT;	/* raise -FLAG again */
	/* debounce */
	{
	    int 	    bouncecount;

	    for (bouncecount = 0; bouncecount < 2; bouncecount++) {
		if (ack == (BUSY & ciab.ciapra) || buffer[-1] != ciaa.ciaprb) {
		    buffer--;
		    bounce++;
		    goto bounced;
		}
	    }
	}
	ack ^= BUSY;		/* store the new state of -BUSY */
    }
#endif
}

#else				/* not PRLINK88 */

void
receive(unsigned char *buffer, unsigned length)
{
    register unsigned char data;

    ciaa.ciaddrb = 0x00;	/* set all signals to input */

    while (length--) {
	ciab.ciapra &= ~POUT;	/* drop the -FLAG to low */
	while (ack == (BUSY & ciab.ciapra));	/* wait for BUSY to change */
	ciab.ciapra |= POUT;	/* raise -FLAG again */
	data = ciaa.ciaprb & 0x0f;	/* read the low nybble */

	ciab.ciapra &= ~POUT;	/* drop the -FLAG to low */
	while (ack != (BUSY & ciab.ciapra));	/* wait for BUSY to change */

	ciab.ciapra |= POUT;	/* raise -FLAG again */
	data |= (ciaa.ciaprb & 0x0f) << 4;	/* read the high nybble */

	*buffer++ = data;
    }
}

#endif				/* PRLINK88 */

#else				/* TRANSNIB */

/*-
 *  TransNib is a cable that has only 6 bits connected:
 *
 *  0-3:    data i/o
 *  6:	    handshake in
 *  7:	    handshake out
 *
 *  Because the cable is straight-through, bits 6 and 7 are swapped
 *  at the PET/C-64/VIC-20 side.
 *
 *  The protocol is provided for people who made a cable for
 *  TransNib V1.00 Devised by Matt Francis (m.p.francis@newcastle.ac.uk).
 *  Because the cable is a subset of the prlink cable it will also work
 *  with that.
 *
 *  Note that this protocol is not compatible with the TransNib programs!
-*/

void
send(unsigned char *buffer, unsigned length)
{
    register unsigned char data;

    /*
     * $40 is DRCV (input), $80 is DRDY (output) Note that the names are
     * swapped from receiving but the data direction isn't!
     */
    ciaa.ciaddrb = 0x8f;	/* set the bidirectional signals to output */

    while (length--) {
	register unsigned char nibble;

	data = *buffer++;
	nibble = data >> 4;

	ciaa.ciaprb = nibble;	/* set DRDY low */
	while (ciaa.ciaprb & 0x40);	/* wait for DRCV to go low */
	ciaa.ciaprb = nibble | 0x80;	/* set DRDY high */
	while (!(ciaa.ciaprb & 0x40));	/* wait for DRCV to go high */

	nibble = data & 0x0F;

	ciaa.ciaprb = nibble;	/* set DRDY low */
	while (ciaa.ciaprb & 0x40);	/* wait for DRCV to go low */
	ciaa.ciaprb = nibble | 0x80;	/* set DRDY high */
	while (!(ciaa.ciaprb & 0x40));	/* wait for DRCV to go high */
    }
}

void
receive(unsigned char *buffer, unsigned length)
{
    register unsigned char data;

    /*
     * $80 is DRCV (output), $40 is DRDY (input)
     */
    ciaa.ciaddrb = 0x80;	/* set the bidirectional signals to input */

    while (length--) {
	while (ciaa.ciaprb & 0x40);	/* wait for DRDY to go low */
	data = (ciaa.ciaprb & 0x0f) << 4;	/* read the high nybble */
	ciaa.ciaprb = data & ~0x80;	/* set DRCV low */
	while (!(ciaa.ciaprb & 0x40));	/* wait for DRDY to go high */
	ciaa.ciaprb = data | 0x80;	/* set DRCV high */

	while (ciaa.ciaprb & 0x40);	/* wait for DRDY to go low */
	data |= ciaa.ciaprb & 0x0f;	/* read the low nybble */
	ciaa.ciaprb = data & ~0x80;	/* set DRCV low */
	while (!(ciaa.ciaprb & 0x40));	/* wait for DRDY to go high */
	ciaa.ciaprb = data | 0x80;	/* set DRCV high */

	*buffer++ = data;
    }
}

#endif				/* TRANSNIB */
