/*  psio.c
 *    by Frank "Shaggy" Barrus          feb6399@ultb.isc.rit.edu
 *    started:      July 29, 1994
 *    last update:  August 10, 1994
 *
 *  This program communicates with Atari SIO peripherals 
 *  (currently just the disk drives)
 *  that are connected through the PC parallel port
 *  it supports single, enhanced, and double density
 *
 *  It is designed to be run under Linux, but could
 *  probably be modified for other operating systems.
 *
 *  The following is the pinout for the cable:
 *  (thanks to Mike Munoz for the idea and the cable pinout,
 *   which has been copied directly from his source code...
 *   the rest of the code is all mine)
 *
 *   Cable specifications:
 *                       
 *   Pinouts:           
 *            IBM Parallel Port         Atari SIO     name	  color 
 *                                                           
 *                 1  ----------------->  7          command/  	  purple 
 *                 14 <----------------   5          dataout      green 
 *                 16 ----------------->  10         vcc/rdy      blue
 *                 17 ----------------->  3          datain       orange
 *                 24 -----------------   4          gnd          black
 *                 25 -----------------   6          gnd          shield
 *                                                      
 *                                                             
 *  Connector Specs:                                              
 *        IBM Parallel Port           DB25M    25 pin male d-shell 
 *   (view as if you were plugging the connector into your face)  
 *             1   2  3  4  5  6  7  8  9  10 11 12 13           
 *               14 15 16 17 18 19 20 21 22 23 24 25            
 *                                                             
 *                                                            
 *                                                           
 *        Atari SIO                   Molex    13 pin      
 *   (view as if you were plugging the connector into your face)  
 *                                                         
 *               1  3  5  7  9  11  13                       
 *                 2  4  6  8  10  12                       
 *                                                         
 *                                                        
 *    Notes:                                             
 *      (1) Keep the wire length less than 3 feet.    
 *
 *    Additional notes: (by Shaggy)
 *
 *    Take a standard Atari SIO cable, and cut it in half, or 
 *    to whatever length you want while still leaving enough
 *    cable on the short end to use it too if you should later
 *    want to.
 *    Then take a DB25 connector, and just attach it to the SIO
 *    cable by the colors I added to the list above.   I'm not
 *    sure if they are really consistent but all the cables
 *    I've cut open were.  So, double check with some sort
 *    of continuity tester.
 *    Note that pin 6 is wired to the sheilding around
 *    the cable--  just twist some of it together, and solder
 *    that to pin 25 on the DB25.
 */


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "memtype.h"
#include "lowlevel.h"
#include "sio2pc.h"
#include "sio.h"
#include "psio.h"
#include "vdisk.h"


extern char errmsg[];


int bps = 19200;	/* 19244 actually */
word bps_tval;		/* timer value for bps */

word parport = DEFAULT_PAR;	/* parallel port to use */

byte pbyte;		/* current parallel output byte */
int cfactor;		/* calibration factor used for timeout loops */


char *devname(byte dev)
{
static char buf[3];
	switch(dev) {
	case D1: case D2: case D3: case D4:
	case D5: case D6: case D7: case D8:
		sprintf(buf, "D%d", (dev-D1)+1);
		break;
	case R1: case R2: case R3: case R4:
		sprintf(buf, "R%d", (dev-R1)+1);
		break;
	case P1: sprintf(buf, "P1"); break;
	case P2: sprintf(buf, "P2"); break;
	default:
		sprintf(buf, "%02x", dev);
	}
	return buf;
}



static inline void psio_send(byte *buf, int len, int dataout)
{
int i, b;
	set_timer(bps_tval);
	start_timer();
	while(len-- > 0) {
		b = (((*buf++)^0xff)<<(dataout+1))|(1<<dataout);
		for(i=0; i<10; i++) {
			timer_sync();
			out_portb(parport, (b&(1<<dataout))|pbyte);
			b >>= 1;
		}
	}	
}



static inline int get_start_bit(word port, int bit, int timeout)
{
	while(!(in_portb(port)&(1<<bit))) 
		if(--timeout < 0)
			return False;
	return True;
}


static inline int psio_recv(byte *buf, int len, int timeout, int datain)
{
byte b;
int i, n;
	n = 0;
	while(len-- > 0) {
		set_timer(bps_tval);
		if(!get_start_bit(parport, datain, timeout*cfactor)) 
			return n;
		start_timer();
		b = 0;
		timer_sync();
		for(i=0; i<8; i++) {
			timer_sync_half();
			if(!(in_portb(parport)&(1<<datain)))
				b |= (1<<i);
		}
		timer_sync();
		timeout = 20*1000/bps;	/* timeout after 20 bit times */
		if(timeout < 1)
			timeout = 1;
		*buf++ = b;
		n++;
	}
	return n; 
}




void psio_set_command(int onoff)
{
	pbyte = READY|(COMMAND*(onoff != 0));
	out_portb(parport, pbyte);
}


int cmd_error(byte *frame, char *str)
{
char buf[80];	
	strcpy(buf, "%02X: cmd %02X %02X %02X %02X: ");
	strcat(buf, str);
	sprintf(errmsg, buf, frame[0], frame[1], frame[2], frame[3],
			frame[4], frame[5]);
	return False;
}

void purge_input(void)
{
byte temp[512];
int n;
	while((n=psio_recv(temp, sizeof(temp), 40*1000/bps, DATAIN))) {
		printf("purged %d bytes\n", n);
		disable_interrupts();
	}
}

static int send_command(int dev, byte cmd, byte aux1, byte aux2)
{
byte frame[6];
	psio_set_command(0);
	purge_input();
	frame[0] = dev;
	frame[1] = cmd;
	frame[2] = aux1;
	frame[3] = aux2;
	frame[4] = compute_checksum(frame, 4);
	psio_set_command(1);
	delay_usec(1000);
	psio_send(frame, 5, DATAOUT);
	delay_usec(800);
	psio_set_command(0);
	if(psio_recv(&frame[5], 1, 2000, DATAIN) < 1) 
		return cmd_error(frame, "no response");
	if(frame[5] == Nak) 
		return cmd_error(frame, "NAK");
	if(frame[5] != Ack) 
		return cmd_error(frame, "invalid response: %02X");
	return True;
}


static int frame_error(char *str, int a, int b)
{
	sprintf(errmsg, str, a, b);
	return False;
}

static int frame_recv(byte *data, int size, int timeout)
{
int n, i;
byte buf[512];
byte ck;
	if((n = psio_recv(buf, sizeof(buf), timeout, DATAIN)) < 1)
		return frame_error("timeout", 0, 0);
	for(i=0; i<size; i++)
		data[i] = buf[i+1];
	if(n < size+2) 
		return frame_error("short frame (%d)", n, 0);
	if(n > size+2)
		return frame_error("long frame (%d)", n, 0); 
	switch(buf[0]) {
	case Complete: break;
	case Error: return frame_error("error", 0, 0);
	default: return frame_error("unknown response: %02X", buf[0], 0);
	}
	ck = compute_checksum(&buf[1], size);
	if(ck != buf[n-1]) 
		return frame_error("bad checksum (%02X != %02X)",
					ck, buf[n-1]);
	return True;
}


int psio_read_sec(int drive, int sec, byte *buf)
{
int secsize;
	disable_interrupts();
	if((secsize = vdisk[drive].secsize) == 256 && sec < 4)
		secsize = 128;
	if(!send_command(vdisk[drive].dev, Read, sec&0xff, sec>>8)
	  || !frame_recv(buf, secsize, vdisk[drive].timeout)) {
		enable_interrupts();
		return 0;
	}
	enable_interrupts();
	return secsize;
}


int psio_write_sec(int drive, int sec, byte *buf)
{
	sprintf(errmsg, "D%d: cannot write PSIO disks (yet)\n", drive);
	return False;
}



int psio_get_drive_status(int drive)
{
byte buf[4];
	disable_interrupts();
	if(!send_command(vdisk[drive].dev, Status, 0, 0)
	   || !frame_recv(buf, 4, vdisk[drive].timeout)) {
		enable_interrupts();
		return False;
	}
	vdisk[drive].cstat = buf[0];
	vdisk[drive].hstat = buf[1];
	vdisk[drive].timeout = buf[2]*1000;
	enable_interrupts();
	return True;
}


int psio_mount(int drive, byte dev)
{
	if(drive < 0 || drive > NVDISK) {
		sprintf(errmsg, "%d: invalid drive number", drive);
		return False;
	}
	vdisk_unmount(drive);
	vdisk[drive].type = PSIO;
	vdisk[drive].secsize = 128;
	vdisk[drive].nsec = 720;
	vdisk[drive].elist = NULL;
	vdisk[drive].timeout = DEFAULT_TIMEOUT*1000;
	vdisk[drive].dev = dev;
	if(!psio_get_drive_status(drive))
		return False; 
	if(vdisk[drive].cstat & CSTAT_DOUBLE)
		vdisk[drive].secsize = 256;
	else {
		byte buf[128];
		vdisk[drive].nsec = 1040;
		if(!psio_read_sec(drive, 721, buf))
			vdisk[drive].nsec = 720;
	}
	printf("D%d: %s\n", drive, vdisk_info(drive));
	return True;
}


static void calibrate(void)
{
int u;
int i;
	cfactor = 1000;
	for(i=0; i<10; i++) {
		disable_interrupts();
		out_portb(parport, 0);
		set_timer(0);
		start_timer();
		if(get_start_bit(parport, (1<<DATAIN), cfactor)) {
			printf("PSIO calibration failed\n");
			exit(1);
		}
		u = 65536-read_timer();
		enable_interrupts();
		cfactor = (cfactor*tval_from_freq(1000))/u;
	}
	printf("PSIO calibration factor: %d\n", cfactor);
}


int psio_init(int argc, char **argv)
{
static int init = True;
char **argp = argv;
int narg = 0;
	while(argc--) {
		if(!strcmp(*argv, "-pbps") && argc) {
			bps = atoi(*(++argv)); argc--;
		} else if(!strcmp(*argv, "-par") && argc) {
			parport = strtol(*(++argv), NULL, 16); argc--;
		} else {
			*argp++ = *argv; narg++;
		}
		if(!strcmp(*argv++, "-h")) {
			printf("PSIO options:\n");
			printf("\t-pbps <speed>\t\tset parallel transfer rate\n");
			printf("\t-par <port>\t\tset parallel port\n");
		}
	}
	if(init) {
		bps_tval = tval_from_freq(bps);
		calibrate();
	}
	init = False;
	return narg;
}

