/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software 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 3 of the License, or
 * (at your option) any later version.
 *
 * This software  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 software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */

#include "parlink.h"








/* GLOBAL values. */
unsigned long par_exchanged_bytes_count = 0;
void par_reset_exchanged_bytes(void)
{
    par_exchanged_bytes_count = 0;
}
unsigned long par_get_exchanged_bytes(void)
{
    return par_exchanged_bytes_count;
}



/* GLOBAL setting values. */
unsigned long _port_init     = 0;
unsigned long _port_value    = 0x378;

unsigned long _link_type = LINK_DEVICE_FREEWING;

unsigned long _loop_max = 40;

unsigned long _checksum_retry = 5;

unsigned long _rcv_packet_size = 32;
unsigned long _snd_packet_size = 32;

unsigned long _rcv_recovery = 0;
unsigned long _snd_recovery = 0;

unsigned long _busyon_loop_max = 5;

unsigned long _use_xcomms = 1;
unsigned long _use_compression = 1;





/**
 *  DLL startup : deisplay help screen and enable ports access.
**/
char SC_parlink::sc_start(void)
{
    /* Display help screen. */
    scl_log(SCLT_DLLPG, 2, "Parallel port-based link interface.");
    scl_log(SCLT_DLLPG, 2, "Available parameters :");
    scl_log(SCLT_DLLPG, 2, " commslink       <1    > : Use Comms Link transmission device.");
    scl_log(SCLT_DLLPG, 2, " freewing        <1    > : Use Freewing transmission device.");
    scl_log(SCLT_DLLPG, 2, " exstand         <1    > : Use eXstand transmission device (not implemented !).");
    scl_log(SCLT_DLLPG, 2, " extended        <0/1  > : Use eXtended (or Action Replay legacy) transmission protocol.");
    scl_log(SCLT_DLLPG, 2, " port            <value> : Use specified port value (ex: freewing/exstand: 0x378, commslink: 0x320).");
    scl_log(SCLT_DLLPG, 2, " loop_max        <value> : Max loop count when polling data.");
    scl_log(SCLT_DLLPG, 2, " checksum_retry  <value> : Max retry count when getting a packet.");
    scl_log(SCLT_DLLPG, 2, " rcv_packet_size <value> : Packet size in receive process.");
    scl_log(SCLT_DLLPG, 2, " snd_packet_size <value> : Packet size in send process.");
    scl_log(SCLT_DLLPG, 2, " rcv_recovery    <value> : Recovery type in receive process.");
    scl_log(SCLT_DLLPG, 2, "                         : (0->resend, 1->Reed-Solomon).");
    scl_log(SCLT_DLLPG, 2, " snd_recovery    <value> : Recovery type in send process.");
    scl_log(SCLT_DLLPG, 2, "                         : (0->resend, 1->Reed-Solomon, 2->simple-but-fast).");
    scl_log(SCLT_DLLPG, 2, " busyon_loop_max <value> : Busy signal rising edge maximum polling count.");
    scl_log(SCLT_DLLPG, 2, " use_compression <0/1  > : Use/not use compression when sending/receiving data.");
    scl_log(SCLT_DLLPG, 2, " display         <1    > : Display the parameters current values.");

    /* If needed, enable ports access. */
    if(_port_init == 0)
    {
        if(port_init() != 0) return SC_ERROR_PORT;
        _port_init = 1;
    }

    if(_port_init)
    {
        par_reset_signals();
    }

    return SC_NOERROR;
}

/**
 *  Set internal parameter.
**/
char SC_parlink::sc_set(char* parameter, char* value)
{
    /* Parse parameters. */
    //scl_log(SCLT_DLLPG, 9, "param = %s", parameter);
    if(!stricmp(parameter, "commslink"))
    {
        _link_type = LINK_DEVICE_COMMS;
    }
    else if(!stricmp(parameter, "freewing"))
    {
        _link_type = LINK_DEVICE_FREEWING;
    }
    else if(!stricmp(parameter, "exstand"))
    {
        _link_type = LINK_DEVICE_EXSTAND;
    }
    else if(!stricmp(parameter, "port"))
    {
        _port_value = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "loop_max"))
    {
        _loop_max = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "checksum_retry"))
    {
        _checksum_retry = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "rcv_packet_size"))
    {
        _rcv_packet_size = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "rcv_recovery"))
    {
        _rcv_recovery = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "snd_packet_size"))
    {
        _snd_packet_size = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "snd_recovery"))
    {
        _snd_recovery = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "busyon_loop_max"))
    {
        _busyon_loop_max = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "use_compression"))
    {
        _use_compression = strtoul(value, NULL, 0);
    }
    else if(!stricmp(parameter, "extended"))
    {
        _use_xcomms = (strtoul(value, NULL, 0) ? 1 : 0);
    }
    else if(!stricmp(parameter, "display"))
    {
        /* Display parameters contents to log. */
        switch(_link_type)
        {
        case(LINK_DEVICE_COMMS):
            scl_log(SCLT_DLLPG, 1, "Protocol : Comms Link");
            break;
        case(LINK_DEVICE_FREEWING):
            scl_log(SCLT_DLLPG, 1, "Protocol : FreeWing");
            break;
        case(LINK_DEVICE_EXSTAND):
            scl_log(SCLT_DLLPG, 1, "Protocol : eXstand");
            break;
        default:
            scl_log(SCLT_DLLPG, 1, "Unknown protocol !!!");
        }
        scl_log(SCLT_DLLPG, 1, "Port            = 0x%04X", _port_value);
        scl_log(SCLT_DLLPG, 1, "loop_max        = %d"    , _loop_max);
        scl_log(SCLT_DLLPG, 1, "checksum_retry  = %d"    , _checksum_retry);
        scl_log(SCLT_DLLPG, 1, "rcv_packet_size = %d"    , _rcv_packet_size);
        scl_log(SCLT_DLLPG, 1, "rcv_recovery    = %d"    , _rcv_recovery);
        scl_log(SCLT_DLLPG, 1, "snd_packet_size = %d"    , _snd_packet_size);
        scl_log(SCLT_DLLPG, 1, "snd_recovery    = %d"    , _snd_recovery);
        scl_log(SCLT_DLLPG, 1, "busyon_loop_max = %d"    , _busyon_loop_max);
        scl_log(SCLT_DLLPG, 1, "use_compression = %d"    , _use_compression);
        scl_log(SCLT_DLLPG, 1, "extended        = %d"    , _use_xcomms);
    }
    else
    {
        scl_log(SCLT_DLLPG, 0, "ERROR: Unknown parameter : \"%s\"", parameter);
        return SC_ERROR_PARAMETER;
    }

    return SC_NOERROR;
}


/**
 *  If needed, disable ports access.
**/
char SC_parlink::sc_end(void)
{
    if(_port_init == 1)
    {
        par_reset_signals();
        port_close();
    }
    _port_init = 0;

    return SC_NOERROR;
}



/**
 * Return DLL version informations.
**/
char* SC_parlink::sc_version(void)
{
    sprintf(_version, (char*)("Saturn <-> Parallel port interface Ver. 1.0 (commslink/freewing/eXstand)"));
    return _version;
}


//=============================================================================================
//=============================================================================================
//=============================================================================================









/**
 *  Init signal values on Parallel Port.
**/
void par_reset_signals(void)
{
    if(_link_type == LINK_DEVICE_COMMS)
    {
        scl_log(SCLT_DLLPG, 3, "Reset CommsLink signals ...");
        /* Clear Comms Link data out latch. Having too many bits set in the latch
         * will provide enough power to the Saturn to prevent it from booting up. */
        port_write_byte(_port_value, 0x00);
    }
    else if(_link_type == LINK_DEVICE_FREEWING)
    { /* See Freewing initial signal values below. */
        scl_log(SCLT_DLLPG, 3, "Reset Freewing signals ...");
        port_write_byte(_port_value+0, 0x00);
        port_write_byte(_port_value+2, 0x09);
    }
    else if(_link_type == LINK_DEVICE_EXSTAND)
    { /* See Freewing initial signal values below. */
        scl_log(SCLT_DLLPG, 3, "Reset eXstand signals (TODO!) ...");
    }
    else
    {
        scl_log(SCLT_DLLPG, 0, "Reset signals : unknown protocol ?");
    }
}



//==============================================
unsigned char char2pchar(unsigned char c)
{
    if(c < ' ') return '.';
    if(c > 127) return '?';
    return c;
}

/**
 * About Freewing :
 * WWW : http://www.neko.ne.jp/~freewing/software/freesoft/
 *
 * [Translated from From par_hard.lzh > PARTS.TXT]
 *
 * `port' : parallel port. Typical value = 0x378 (LPT1).
 * port + 0 -> Data    (output)
 * port + 1 -> Status  (input )
 * port + 2 -> Control (output)
 *
 * `Status' pins assignment :
 *   D0 -> unused
 *   D1 -> unused
 *   D2 -> unused
 *   D3 -> /ERROR
 *   D4 -> SCLT
 *   D5 -> PEND
 *   D6 -> /ACK
 *   D7 -> BUSY
 *
 * `Control' pins assignment :
 *   D0 -> /STROBE
 *   D1 -> /AUTO FD
 *   D2 -> Always '0'
 *   D3 -> /SLCT IN
 *   D4 -> Always '0'
 *   D5 -> Always '0'
 *   D6 -> Always '0'
 *   D7 -> Always '0'
 *
 *
 * [Quick and Dirty translation from From par_hard.lzh > PSC_SUPP.TXT]
 * Principle of use
 * 
 * Initial signal values :
 *  Set /STROBE  to 'H'
 *  Set /SLCT IN to 'H'
 *  Set /AUTO FD to 'L'
 *   -> Set port + 2 to 0x09 (0b00001001)
 * 
 * By setting /STROBE to 'L' data are sent from PC.
 * When /STROBE becomes 'H', data from PC are lacted to U004.
 * Then, at the same time, U001 (right) 's Q output become 'H', 
 * which informs PAR that there is some data (because PAR's STB become 'H')
 * 
 * When PAR receives data, PAR's ACK signal becomes 'H', 
 * which informs PC that there is new data (because PC's BUSY signal becomes 'H')
 * 
 * PC set /SLCT IN to 'L' and get data from PAR.
 * At this time, in order to get 1 byte of data, its high/low nibbles
 * are sent by using /AUTO FD signal.
 * By setting /AUTO FD signal to 'L', PC receives data's Low nibble (bit 0~3).
 * By setting /AUTO FD signal to 'H', PC receives data's High nibble (bit 4~7).
 * 
 * When PC finishes data reception, set back the /SLCT IN signal to 'H'.
 *
**/





#define DelayLPT() ;
//void DelayLPT(void)
//{
    //scl_log(SCLT_DLLPG, 5, "  wait STT");
    //int ms = 100; usleep(ms*1000);
//Sleep(msec);
    //scl_log(SCLT_DLLPG, 5, "  wait END");
//    return; /* DUMMY WAIT */
//}

// float DelayLPT2(int count)
// {
    // float a = 5.0;
    // float b = 0.0;
    // float c = 0.0;
    // int i;
    // for(i=0; i<count; i++)
    // {
        // b = pow(a, i);
        // a = 5+cos(b);
        // c += a+b;
    // }
    // return c;
// }


////==============================================
//// "previous read error" 10% error rate version (comment out 20100316)
//int ParOutInpDataLPT_PC(unsigned long AnOutData, unsigned long *AnInpData)
//{
//    char logstat[1024]; logstat[0] = '\0';
//    char logtmp[128];
//
//    int    bFlag   = 0;
//    unsigned long nCount  = 30000;
//    unsigned long n       = 0x00100;
//    unsigned char  bDatXor = 0x00b;
//    bDatXor = 0x000;
//
//    port_write_byte(_port_value+2, 0x002 ^ bDatXor);
//    DelayLPT();
//    port_write_byte(_port_value  , AnOutData); /* SET DATA */
//    DelayLPT();
//    port_write_byte(_port_value+2, 0x003 ^ bDatXor); /* STROBE */
//    DelayLPT();
//    port_write_byte(_port_value+2, 0x002 ^ bDatXor);
//    DelayLPT();
//
//
//    while (nCount--)
//    {
//        *AnInpData = port_read_byte(_port_value+1); /* STATUS */
//        //if (GSbParDebugPrintf)
//        //{
//            //if (n != *AnInpData)
//            //{
//                /* Log status. */
//                char st[6];
//                st[0] = 'B'; st[1] = 'a'; st[2] = 'P'; st[3] = 'S'; st[4] = 'e'; st[5] = '\0';
//                if(((*AnInpData) & (1 << 7)) != 0) st[0] = ' ';
//                if(((*AnInpData) & (1 << 6)) == 0) st[1] = ' ';
//                if(((*AnInpData) & (1 << 5)) != 0) st[2] = ' ';
//                if(((*AnInpData) & (1 << 4)) != 0) st[3] = ' ';
//                if(((*AnInpData) & (1 << 3)) == 0) st[4] = ' ';
//                sprintf(logtmp, " %02X [%s],", *AnInpData, st);
//                strncat(logstat, logtmp, sizeof(logstat) - strlen(logstat) - 1);
//                n = *AnInpData;
//            //}
//        //}
//        if ( (*AnInpData & 0x080) )
//        { // ACK ?
//            bFlag = 1;
//            break;
//        }
//    }
//
//    port_write_byte(_port_value+2, 0x00a ^ bDatXor);
//    DelayLPT();
////DelayLPT2(800);
//    n = port_read_byte(_port_value+1);
//    *AnInpData = (n >> 3) & 0x00f; /* GET DATA L */
//    port_write_byte(_port_value+2, 0x008 ^ bDatXor);
//    DelayLPT();
////DelayLPT2(800);
//    n = port_read_byte(_port_value+1);
//    n = (n << 1) & 0x0f0; /* GET DATA H */
//    *AnInpData |= n;
//    port_write_byte(_port_value+2, 0x002 ^ bDatXor);
////DelayLPT2(800);
//
//    /* Log output, input, status. */
//    sprintf(logtmp, " o%02X o%c i%02X i%c, s%s",
//         AnOutData, char2pchar( AnOutData), 
//        *AnInpData, char2pchar(*AnInpData), 
//        logstat);
//    /* Send log to host program. */
//    scl_log(SCLT_DLLPG, 9, "%s", logtmp);
//
//    return (bFlag);
//}



//==============================================
int ParOutInpDataLPT_PC(unsigned long AnOutData, unsigned long *AnInpData)
{
    char logstat[4096]; logstat[0] = '\0';
    char logtmp[8192];

    int    bFlag   = 0;
    unsigned long nCount  = 30000;
    unsigned long i = 0;
    unsigned long n       = 0x00100;
    unsigned char  bDatXor = 0x00b;
    bDatXor = 0x000;

    int trycount = 0;
    for(i=0; i<_busyon_loop_max; i++) //while (nCount--)
    {
        port_write_byte(_port_value+2, 0x002 ^ bDatXor);
        DelayLPT();
        port_write_byte(_port_value  , AnOutData); /* SET DATA */
        DelayLPT();
        port_write_byte(_port_value+2, 0x003 ^ bDatXor); /* STROBE */
        DelayLPT();
        port_write_byte(_port_value+2, 0x002 ^ bDatXor);
        DelayLPT();
        trycount++;


        *AnInpData = port_read_byte(_port_value+1); /* STATUS */
        /* Wait for busy signal rising edge. */
        if ( (*AnInpData & 0x080) == 0 )
        {
            break;
        }
        else
        {
        //break;
            /* Reset signal. */
            port_write_byte(_port_value+2, 0x008 ^ bDatXor);
            port_write_byte(_port_value+2, 0x002 ^ bDatXor);
        }
    }

    for(i=0; i<nCount; i++) //while (nCount--)
    {
        *AnInpData = port_read_byte(_port_value+1); /* STATUS */
        //if (GSbParDebugPrintf)
        //{
            //if (n != *AnInpData)
            //{
                /* Log status. */
                char st[6];
                st[0] = 'B'; st[1] = 'a'; st[2] = 'P'; st[3] = 'S'; st[4] = 'e'; st[5] = '\0';
                if(((*AnInpData) & (1 << 7)) != 0) st[0] = ' ';
                if(((*AnInpData) & (1 << 6)) == 0) st[1] = ' ';
                if(((*AnInpData) & (1 << 5)) != 0) st[2] = ' ';
                if(((*AnInpData) & (1 << 4)) != 0) st[3] = ' ';
                if(((*AnInpData) & (1 << 3)) == 0) st[4] = ' ';
                sprintf(logtmp, " %02X [%s],", *AnInpData, st);
                strncat(logstat, logtmp, sizeof(logstat) - strlen(logstat) - 1);
                n = *AnInpData;
            //}
        //}


        if ( (*AnInpData & 0x080) )
        { // ACK ?
            bFlag = 1;
            break;
        }
    } /* Busy signal polling loop. */

    port_write_byte(_port_value+2, 0x00a ^ bDatXor);
    DelayLPT();
    n = port_read_byte(_port_value+1);
    *AnInpData = (n >> 3) & 0x00f; /* GET DATA L */
    port_write_byte(_port_value+2, 0x008 ^ bDatXor);
    DelayLPT();
    n = port_read_byte(_port_value+1);
    n = (n << 1) & 0x0f0; /* GET DATA H */
    *AnInpData |= n;
    port_write_byte(_port_value+2, 0x002 ^ bDatXor);

    /* Log output, input, status. */
    sprintf(logtmp, "C%02d o%02X o%c i%02X i%c, s%s",
        trycount, 
         AnOutData, char2pchar( AnOutData), 
        *AnInpData, char2pchar(*AnInpData), 
        logstat);
    /* Send log to host program. */
    scl_log(SCLT_DLLPG, 9, "[FEXC]%s", logtmp);

    return (bFlag);
}


/**
 *  Send a byte, then receive a byte from PAR.
**/
unsigned char par_exchange_byte(unsigned char value)
{
    //scl_log(SCLT_DLLPG, 5, "par_exchange_byte");
    par_exchanged_bytes_count++;

    /*--- Comms Link. ---*/
    if(_link_type == LINK_DEVICE_COMMS)
    {
        unsigned char ret = 0;

        port_write_byte(_port_value, value);
     
        int i = 0;
        while (port_read_byte(_port_value + 2) & 1)
        {
            i++;
            if(i > _loop_max) break;
        }
        ret = port_read_byte(_port_value);

        /* Send log to host program. */
        scl_log(SCLT_DLLPG, 1/*9*/, "[CEXC]~~~ SND 0x%02x(%c) WAIT %4d, RCV 0x%02x(%c)%s", 
                    value, char2pchar(value), 
                    i, 
                    ret, char2pchar(ret), 
                    (i > _loop_max ? " (timeout)" : ""));

        return ret;
    }
    /*--- Freewing. ---*/
    else if(_link_type == LINK_DEVICE_FREEWING)
    {
        unsigned long AnOutData = value;
        unsigned long AnInpData;
        ParOutInpDataLPT_PC(AnOutData, &AnInpData);
        return AnInpData;
    }
    /*--- eXstand. ---*/
    else if(_link_type == LINK_DEVICE_EXSTAND)
    {
        return 0x42;
    }

    /* Unknown protocol. */
    return 0;
}


unsigned short par_exchange_word(unsigned short data)
{
    unsigned short ret = 0;
    ret |= par_exchange_byte(data >> 8) << 8;
    ret |= par_exchange_byte(data & 0xFF);
    return ret;
}
unsigned long par_exchange_long(unsigned long data)
{
    unsigned long ret = 0;
    ret |= par_exchange_byte(data >> 24)          << 24;
    ret |= par_exchange_byte((data >> 16) & 0xFF) << 16;
    ret |= par_exchange_byte((data >> 8) & 0xFF)  << 8;
    ret |= par_exchange_byte(data & 0xFF);
    return ret;
}






