/*
 * Copyright © 1994-1996 Marko Mäkelä and Olaf Seibert
 * Original Linux and Commodore 64/128/Vic-20 version by Marko Mäkelä
 * Ported to the PET and the Amiga series by Olaf Seibert
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Note that for the Amiga only the pramiga and transnib cables are
 * 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 the "prlink88" cable, 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.
 *
 * To enable the "transnib" cable, define TRANSNIB.
 */

#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>

#include "prtrans.h"

#define DEBOUNCE	1	/* debounce the ack line */
#define DEBOUNCE2	1	/* debounce the ack line more thoroughly */
#define DEBUG		0
#define NO_ASM		(DEBOUNCE || DEBOUNCE2 || DEBUG)

/*
 * 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;
#if DEBOUNCE2
int		bounce;
#endif
char		myname[] = "pramiga.c";

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

#define DATA		ciaa.ciaprb
#define DATA_DDR	ciaa.ciaddrb
#define CTL		ciab.ciapra
#define CTL_DDR 	ciab.ciaddra

#if DEBUG
unsigned char *Goldbuffer;
#endif

int
prinit(void)
{
    if (MiscAllocated == 0) {
	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) {
		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) {
		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) */
    CTL |= POUT;	/* and set it high */
    CTL_DDR = (CTL_DDR | POUT) & ~BUSY;
    ack = CTL & BUSY;	/* get the current state of the BUSY line */
#endif
    DATA = 0xFF;	/* and high */
    DATA_DDR = 0xFF;	/* set all lines to output */

    return 0;
}

void
prclose(void)
{
#if DEBOUNCE2
    if (bounce != 0) {
	printf("*** bounce, bounce! (%d times)\n", bounce);
	bounce = 0;
    }
#endif
#ifndef TRANSNIB
    if (MiscAllocated & (1 << MR_PARALLELBITS)) {
	CTL_DDR &= ~POUT;	/* turn POUT back into input */
	FreeMiscResource(MR_PARALLELBITS);
    }
#endif
    if (MiscAllocated & (1 << MR_PARALLELPORT)) {
	DATA = 0xFF;
	DATA_DDR = 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);
}


void wait_output (unsigned char byte)
{
#ifndef TRANSNIB
    DATA_DDR = 0xFF;	/* set all lines to output */

    DATA = byte;
    CTL &= ~POUT;		/* drop the -FLAG to low */
    while (ack == (BUSY & CTL))
	usleep (SLEEP_TIME); /* wait for BUSY to change */
    CTL |= POUT;		/* raise -FLAG again */
    ack ^= BUSY;		/* store the new state of -BUSY */
#else
    register unsigned char nibble;

    DATA_DDR = 0x8f;	/* set the bidirectional signals to output */
	
    nibble = byte >> 4;

    DATA = nibble;		/* set DRDY low */
    while (DATA & 0x40)		/* wait for DRCV to go low */
	usleep (SLEEP_TIME);

    /* For the rest, use the normal routine */

    send(&byte, 1);
#endif
}

unsigned
input(void)
{
    unsigned char   byte;

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

unsigned
wait_input(void)
{
    unsigned char   byte;

#ifdef TRANSNIB
    while (DATA & 0x40) /* wait for DRDY to go low */
	usleep (SLEEP_TIME);
#else
    CTL &= ~POUT;		/* drop the -FLAG to low */
    while (ack == (BUSY & CTL)) /* wait for BUSY to change */
	usleep (SLEEP_TIME);
#endif

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

#ifndef TRANSNIB
void
send(unsigned char *buffer, unsigned length)
{
    DATA_DDR = 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" (&DATA),            /* 4 */
	"a" (&CTL),             /* 5 */
	"g" (~POUT),            /* 6 */
	"g" (BUSY),             /* 7 */
	"g" (POUT)              /* 8 */
      : "d0"                    /* clobbered */
    );
    /* INDENT ON */
#else
    while (length--) {
	DATA = *buffer++;
	CTL &= ~POUT;		/* drop the -FLAG to low */
	while (ack == (BUSY & CTL));	/* wait for BUSY to change */
	CTL |= POUT;		/* raise -FLAG again */
	ack ^= BUSY;		/* store the new state of -BUSY */
    }
#endif
}

#ifdef PRLINK88
void
receive(unsigned char *buffer, unsigned length)
{
#if DEBUG
    int oldbounce = bounce;
    Goldbuffer = buffer;
    printf("receive %d bytes: ", length); fflush(stdout);
#endif
    DATA_DDR = 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" (&DATA),            /* 4 data */
	"a" (&CTL),             /* 5 ctl */
	"g" (~POUT),            /* 6 */
	"g" (BUSY),             /* 7 */
	"g" (POUT)              /* 8 */
      : "d0"                    /* clobbered */
    );
    /* INDENT ON */
#else
    while (length--) {
	CTL &= ~POUT;			/* drop the -FLAG to low */
#if DEBOUNCE
bounced:
#endif
	while (ack == (BUSY & CTL));	/* wait for BUSY to change */
#if DEBOUNCE
	/*
	 * With my Amiga and (PET or 64), we need to debounce the
	 * ack line. With the 64 even more seriously.
	 * I'm not sure why this is...
	 */
	if (ack == (BUSY & CTL)) goto bounced;
	if (ack == (BUSY & CTL)) goto bounced;
#if DEBOUNCE > 2
	if (ack == (BUSY & CTL)) goto bounced;
	if (ack == (BUSY & CTL)) goto bounced;
#endif
#endif
	*buffer++ = DATA;		/* read the byte */
#if DEBOUNCE2
	/* debounce */
	{
	    int 	    bouncecount;
	    unsigned char   newbyte;

	    for (bouncecount = 0; bouncecount < 3; bouncecount++) {
		newbyte = DATA;
		if (ack == (BUSY & CTL) || buffer[-1] != newbyte) {
#if DEBUG
		    fprintf(stderr, "@%ld: %02x %02x != %02x (ack = %d)\n",
				     buffer - Goldbuffer - 2,
				     buffer[-2], buffer[-1], newbyte,
				     ack);
#endif
		    buffer--;
		    bounce++;
		    goto bounced;
		}
	    }
	}
#endif
	CTL |= POUT;			/* raise -FLAG again */
	ack ^= BUSY;			/* store the new state of -BUSY */
#if DEBUG
	if ((length & 0xFF) == 0) {
	    putchar('>'); fflush(stdout);
	}
#endif
    }
#endif
#if DEBUG
    printf("%02x", Goldbuffer[0]);
    if (bounce > oldbounce)
	printf(" (%d bounces)", bounce - oldbounce);
    putchar('\n');
#endif
}

#else				/* not PRLINK88 */

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

    DATA_DDR = 0x00;		/* set all signals to input */

    while (length--) {
	CTL &= ~POUT;		/* drop the -FLAG to low */
#if DEBOUNCE
bounced1:
#endif
	while (ack == (BUSY & CTL));	/* wait for BUSY to change */
#if DEBOUNCE
	/*
	 * With my Amiga and (PET or 64), we need to debounce the
	 * ack line. With the 64 even more seriously.
	 * I'm not sure why this is...
	 */
	if (ack == (BUSY & CTL)) goto bounced1;
	if (ack == (BUSY & CTL)) goto bounced1;
#endif
	CTL |= POUT;		/* raise -FLAG again */
	data = DATA & 0x0f;	/* read the low nybble */

	CTL &= ~POUT;		/* drop the -FLAG to low */
#if DEBOUNCE
bounced2:
#endif
	while (ack != (BUSY & CTL));	/* wait for BUSY to change */
#if DEBOUNCE
	if (ack != (BUSY & CTL)) goto bounced2;
	if (ack != (BUSY & CTL)) goto bounced2;
#endif

	CTL |= POUT;		/* raise -FLAG again */
	data |= (DATA & 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!
     */
    DATA_DDR = 0x8f;	/* set the bidirectional signals to output */

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

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

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

	nibble = data & 0x0F;

	DATA = nibble;		/* set DRDY low */
	while (DATA & 0x40);	/* wait for DRCV to go low */
	DATA = nibble | 0x80;	/* set DRDY high */
	while (!(DATA & 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)
     */
    DATA_DDR = 0x80;		/* set the bidirectional signals to input */

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

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

	*buffer++ = data;
    }
}

#endif				/* TRANSNIB */
