/*
 * pphw.c
 *
 * Hardware level routines used by the PIC84PGM project.  Information
 * about the parallel port was taken from Zhahai Stewart's PC printer
 * port FAQ.
 *
 * Revision history:
 *
 * 29-Aug-1996: V-0.0; created (partly based on pp.c V-0.3).
 * 31-Aug-1996: V-0.1; added debug mode
 * 03-Sep-1996: V-0.2; changed to use timer.c
 *
 * Copyright (C) 1996 David Tait.  All rights reserved.
 * Permission is granted to use, modify, or redistribute this software
 * so long as it is not sold or exploited for profit.
 *
 * THIS SOFTWARE IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND,
 * EITHER EXPRESSED OR IMPLIED.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#ifndef AMIGA
#include <conio.h>
#endif
#include "pphw.h"
#include "hex.h"

static char *version = "pphw.c V-0.2  Copyright (C) 1996 David Tait";  

PPREG_T d_reg;
PPREG_T s_reg;
PPREG_T c_reg;
int d_bits;
int tdly = TDLY;
int dumpfmt = INHX8M;

int hwid = 0;

unsigned (*inbit)(void);                /* vectors to hardware functions */
void (*vppon)(void);
void (*vppoff)(void);
void (*vddon)(void);
void (*vddoff)(void);
void (*clkhi)(void);
void (*clklo)(void);
void (*outlo)(void);
void (*outhi)(void);


unsigned tbit(void)                     /* real hardware functions */
{
    return inportb(s_reg)&IN;
}

unsigned ibit(void)
{
    return ~inportb(s_reg)&IN;
}

void svpp(void)
{
    d_bits |= VPP;
}

void rvpp(void)
{
    d_bits &= ~VPP;
}

void svdd(void)
{
    d_bits |= VDD;
}

void rvdd(void)
{
    d_bits &= ~VDD;
}

void sclk(void)
{
    d_bits |= CLK;
}

void rclk(void)
{
    d_bits &= ~CLK;
}

void sout(void)
{
    d_bits |= OUT;
}

void rout(void)
{
    d_bits &= ~OUT;
}

void assert(void)
{
    outportb(d_reg,d_bits);
}

struct hwfun {
   unsigned (* f0)(void);
   void (* f1)(void);
   void (* f2)(void);
   void (* f3)(void);
   void (* f4)(void);
   void (* f5)(void);
   void (* f6)(void);
   void (* f7)(void);
   void (* f8)(void);
} hwfun[4] = {
    { ibit, rvpp, svpp, rvdd, svdd, sclk, rclk, sout, rout }, /* 7406/4066 */
    { tbit, svpp, rvpp, svdd, rvdd, rclk, sclk, rout, sout }, /* 7407/4066 */
    { ibit, svpp, rvpp, svdd, rvdd, sclk, rclk, sout, rout }, /* 7406/PNP */
    { tbit, rvpp, svpp, rvdd, svdd, rclk, sclk, rout, sout }  /* 7407/PNP */
};

char *hw_str[4] = {
    "7406/4066", "7407/4066", "7406/PNP", "7407/PNP"
    };


void ppdelay(char *s)           /* get delay from environment */
{
    if ( s )
	if ( (tdly = atoi(s)) < 0 || tdly > 127 )
	    tdly = TDLY;
}


void ppsetup(char *s)           /* get hardware setup from environment */
{
    struct hwfun *hwp;

    if ( s )
	if ( (hwid = atoi(s)) < 0 || hwid > 3 )
	    hwid = 0;

    hwp = &hwfun[hwid];

    inbit  = hwp->f0;           /* use appropriate functions */
    vppon  = hwp->f1;
    vppoff = hwp->f2;
    vddon  = hwp->f3;
    vddoff = hwp->f4;
    clklo  = hwp->f5;
    clkhi  = hwp->f6;
    outlo  = hwp->f7;
    outhi  = hwp->f8;
}


void pphxfmt(char *s)
{
    if ( s )
	if ( atoi(s) == 16 )
	   dumpfmt = INHX16;
}

 
int ppdebug(char *s, int lpt)           /* check hardware */
{
    char *istat;
    int b, hwstat = 0;
    static int debug = 0;

    idle_mode();              /* toggle RB7 to check hardware is present */
    vddon(), assert();
    ms_delay(PWRDLY);
    outhi(), assert();
    us_delay(tdly);
    b = inbit();
    outlo(), assert();
    us_delay(tdly);
    if ( b != IN || inbit() != 0 )
	hwstat = 1;
    idle_mode();

    if ( debug == 0 )
	if ( s )
	    debug = atoi(s);      /* get debug mode from environment */

    if ( debug == 0 ) {
	debug = 1;        /* force debugging on next call */
	return hwstat;
    }

    printf("Hardware setup: %s using LPT%d at %04X (delay = %d)\n",
					   hw_str[hwid],lpt,d_reg,tdly);
    printf("Debug mode entered ... (^C to exit) ");
    if ( getch() == 3 ) {
	printf("\n\n");
	return 0;
    }
    printf("\nRemove PIC ... ");
    getch();
    for(;;) {
	vppoff(), vddoff(), clklo(), outlo(), assert();
	us_delay(tdly);
	istat = (inbit() == 0)? "OK" : "BAD";
	printf("\nVPP off, VDD off, RB6 low, RB7 low (input %s) ... ",istat);
	getch();
	vppon(), assert();
	printf("\nVPP on ... ");
	getch();
	vppoff(), vddon(), assert();
	printf("\nVDD on ... ");
	getch();
	vddoff(), clkhi(), assert();
	printf("\nRB6 high ... ");
	getch();
	clklo(), outhi(), assert();
	us_delay(tdly);
	istat = (inbit() == IN)? "OK" : "BAD";
	printf("\nRB7 high (input %s) ... ",istat);
	getch();
	printf("\nStart over ... (^C to exit) ");
	if (getch() == 3) {
	    printf("\n\n");
	    return 0;
	}
    }
}


void idle_mode(void)
{
    vppoff(), clklo(), outlo(), assert();
    ms_delay(PWRDLY);
    vddoff(), assert();
}


void prog_mode(void)
{
    vppoff(), vddon(), clklo(), outlo(), assert();
    ms_delay(PWRDLY);
    vppon(), assert();
}


static void clock_out(int bit)
{
    bit? outhi(): outlo(); clkhi(), assert();
    us_delay(tdly);
    clklo(), assert();
    us_delay(tdly);
    outlo(), assert();
}


static int clock_in(void)
{
    int b;

    clkhi(), assert();
    us_delay(tdly);
    clklo(), assert();
    b = inbit()? 1: 0;
    us_delay(tdly);

    return b;
}


    /* out_word(w)
     *
     * Write a 14-bit value to the PIC.
     *
     */

void out_word(int w)
{
    int b;

    clock_out(0);
    for ( b=0; b<14; ++b )
	clock_out(w&(1<<b));
    clock_out(0);
}

    /* in_word()
     *
     * Obtain a 14-bit value from the PIC.
     *
     */

int in_word(void)
{
    int b, w;

    (void) clock_in();
    for ( w=0, b=0; b<14; ++b )
	w += clock_in()<<b;
    (void) clock_in();

    return w;
}


    /* command(cmd)
     *
     * Send a 6-bit command to the PIC.
     *
     */

void command(int cmd)
{
    int b;

    outlo(), assert();
    us_delay(tdly);
    for ( b=0; b<6; ++b )
	clock_out(cmd&(1<<b));
    outhi(), assert();
    us_delay(tdly);
}


void cleanup(void)
{
    reset_timer();
}


    /* setup()
     *
     * Reads environment for LPT port and tdly, initialises timer, checks 
     * hardware.  Returns -ve if LPT port address is non-standard or +ve
     * if hardware fails sanity check, 0 for all OK.
     *
     */

int setup(void)
{
    char *s;
    int lpt = 1;

    if ( (s = getenv("PPLPT")) != NULL )
	if ( (lpt = atoi(s)) < 1 || lpt > 3 ) 
	    lpt = 1;

#ifdef AMIGA
    d_reg = (PPREG_T) 0xBFE101;
    c_reg = (PPREG_T) 0xBFE301;
    s_reg = (PPREG_T) 0xBFD000;

    ami_setup();
#else
    d_reg = *(((int far *) 0x408) + lpt-1);  /* base address of LPT port */
    s_reg = d_reg+1;
    c_reg = s_reg+1;

    switch ( d_reg ) {                   /* check port address is valid */
	case 0x3BC:
	case 0x378:
	case 0x278: break;
	   default: return -1;
    }
#endif

    ms_delay(0);                /* dummy call to setup CTC */
    outportb(c_reg,0xF);        /* clear high nibble of LPT control reg */
    ppdelay(getenv("PPDELAY"));
    ppsetup(getenv("PPSETUP"));
    pphxfmt(getenv("PPDUMP"));
    return ppdebug(getenv("PPDEBUG"),lpt);
}
