/**
 * Copyright (C) 2009 Ubixum, Inc. 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 **/

//#define DEBUG_SETUPDAT

#ifdef DEBUG_SETUPDAT
#include <stdio.h> // NOTE this needs deleted
#else
#define printf(...)
#define NULL (void*)0;
#endif

#include <fx2regs.h>
#include <fx2macros.h>
#include <eputils.h>
#include <setupdat.h>


extern BOOL handle_get_descriptor();
extern BOOL handle_vendorcommand(BYTE cmd);
extern BOOL handle_set_configuration(BYTE cfg);
extern BOOL handle_get_interface(BYTE ifc, BYTE* alt_ifc);
extern BOOL handle_set_interface(BYTE ifc, BYTE alt_ifc);
extern BYTE handle_get_configuration();
extern void handle_reset_ep(BYTE ep);

/**
 * Forward declaration for handlers
 **/


//  GET_STATUS,
BOOL handle_get_status();
//  CLEAR_FEATURE,
BOOL handle_clear_feature();
  // 0x02 is reserved
//  SET_FEATURE=0x03,
BOOL handle_set_feature();
  // 0x04 is reserved
//  SET_ADDRESS=0x05, // SET_ADDRESS is *always* handled by EZ-USB core.
//  GET_DESCRIPTOR,
void _handle_get_descriptor();
//  SET_DESCRIPTOR,
//  GET_CONFIGURATION, // handled by callback
//  SET_CONFIGURATION, // handled by callback
//  GET_INTERFACE, // handled by callback
//  SET_INTERFACE, // handled by callback
//  SYNC_FRAME // not yet implemented

/*
 TRM 2.2
 Setup Token ->
 data transfer ->
 handshake
*/

void handle_setupdata() {
    printf("Handle setupdat: 0x%02x\n", SETUPDAT[1]);

    switch ( SETUPDAT[1] ) {
        case GET_STATUS:
            if (!handle_get_status())
                STALLEP0();
            break;
        case CLEAR_FEATURE:
            if (!handle_clear_feature()) {
                STALLEP0();
            }
            break;
        case SET_FEATURE:
            if (!handle_set_feature()) {
                STALLEP0();
            }
            break;
        case GET_DESCRIPTOR:
            if (!handle_get_descriptor())
              _handle_get_descriptor();
            break;
        case GET_CONFIGURATION:            
            EP0BUF[0] = handle_get_configuration();
            EP0BCH=0;
            EP0BCL=1;
            break;
        case SET_CONFIGURATION:
            // user callback
            if(!handle_set_configuration(SETUPDAT[2])) {
                STALLEP0();
            }
            break;
        case GET_INTERFACE:
            {
                BYTE alt_ifc;
                if (!handle_get_interface(SETUPDAT[4],&alt_ifc)) {
                    STALLEP0();
                } else {
                    EP0BUF[0] = alt_ifc;
                    EP0BCH=0;
                    EP0BCL=1;
                }
            }
            break;
        case SET_INTERFACE:
            // user callback
            if (!handle_set_interface(SETUPDAT[4],SETUPDAT[2])) {
                STALLEP0();
            }
            break;
        default:
            if (!handle_vendorcommand(SETUPDAT[1])) {
                printf("Unhandled Vendor Command: 0x%02x\n" , SETUPDAT[1]);
                STALLEP0();
            }
    }
    
    // do the handshake
    EP0CS |= bmHSNAK;
}

__xdata BYTE* ep_addr(BYTE ep) {
    // Bit 8 of ep_num is the direction (In or Out) of the endpoint.
    BYTE ep_num = ep & ~bmBIT7;
    switch (ep_num) {
        case 0: return &EP0CS;
        case 1: return (ep&0x80) ? &EP1INCS : &EP1OUTCS;
        case 2: return &EP2CS;
        case 4: return &EP4CS;
        case 6: return &EP6CS;
        case 8: return &EP8CS;
        default: return NULL;
    }
}


// Get status has three request types
#define GS_DEVICE 0x80
#define GS_INTERFACE 0x81
#define GS_ENDPOINT 0x82


volatile BOOL self_powered=FALSE;
volatile BOOL remote_wakeup_allowed=FALSE;

BOOL handle_get_status() {
    
    switch ( SETUPDAT[0] ) {
//        case 0: // sometimes we get a 0 status too

        case GS_INTERFACE: 
            EP0BUF[0] = 0;
            EP0BUF[1] = 0;
            EP0BCH=0;
            EP0BCL=2;
            break;
        case GS_DEVICE:

            // two byte response
            // byte 0 bit 0 = self powered bit 1 = remote wakeup
            EP0BUF[0] = (remote_wakeup_allowed << 1) | self_powered;
            // byte 1 = 0
            EP0BUF[1] = 0;
            EP0BCH = 0;
            EP0BCL = 2;
            break;
        case GS_ENDPOINT:
            {
                __xdata BYTE* pep=ep_addr(SETUPDAT[4]);
                if ( !pep ) return FALSE;
                // byte 0 bit 0 = stall bit
                EP0BUF[0] = *pep & bmEPSTALL ? 1 : 0;
                EP0BUF[1] = 0;
                EP0BCH=0;
                EP0BCL=2;
            }
            break;
        default:
            printf("Unexpected Get Status: 0x%02x\n", SETUPDAT[0]);
            return FALSE;
    }
    return TRUE;
}


#define GF_DEVICE 0
#define GF_ENDPOINT 2

BOOL handle_clear_feature() {
    printf("Clear Feature 0x%02x\n", SETUPDAT[0]);

    switch ( SETUPDAT[0] ) {
        case GF_DEVICE:
            if (SETUPDAT[2] == 1) {
                remote_wakeup_allowed=FALSE;
                break;
            }

            if (SETUPDAT[2] == 6) // debug feature
                break;

            return FALSE;
        case GF_ENDPOINT:
            if (SETUPDAT[2] == 0) { // ep stall feature
                __xdata BYTE* pep=ep_addr(SETUPDAT[4]);
                printf("unstall endpoint 0x%02x\n" , SETUPDAT[4]);
                *pep &= ~bmEPSTALL;
                RESETTOGGLE(SETUPDAT[4]);
            } else {
                printf("unsupported ep feature 0x%02x\n", SETUPDAT[2]);
                return FALSE;
            }
            break;
        default:
            return handle_vendorcommand(SETUPDAT[1]);
    }
    return TRUE;
}

BOOL handle_set_feature() {
    printf("Set Feature 0x%02x\n", SETUPDAT[0]);

    switch ( SETUPDAT[0] ) {
        case GF_DEVICE:
            // this is TEST_MODE and we simply need to return the handshake
            if (SETUPDAT[2] == 2)
                break;

            if (SETUPDAT[2] == 1) {
                remote_wakeup_allowed=TRUE;
                break;
            }
            // debug feature
            if (SETUPDAT[2] == 6)
                break;

            return FALSE;
        case GF_ENDPOINT:
            if ( SETUPDAT[2] == 0 ) { // ep stall feature
                // set TRM 2.3.2
                // stall and endpoint
                __xdata BYTE* pep = ep_addr(SETUPDAT[4]);
                printf("Stall ep 0x%02x\n", SETUPDAT[4]);
                if (!pep) {
                    return FALSE;
                }
        
                *pep |= bmEPSTALL;
                // should now reset data toggles
                // write ep+dir to TOGCTL
                RESETTOGGLE(SETUPDAT[4]);
                // restore stalled ep to default condition
                // NOTE
                //handle_reset_ep(SETUPDAT[4]);
            } else {
                printf("unsupported ep feature 0x%02x\n", SETUPDAT[2]);
                return FALSE;
            }
            break;
        default:
            return handle_vendorcommand(SETUPDAT[1]);
    }
    return TRUE;
}

/* these are defined in dscr.asm
   and need to be customized then
   linked in by the firmware manually */
extern __code WORD dev_dscr;
extern __code WORD dev_qual_dscr;
extern __code WORD highspd_dscr;
extern __code WORD fullspd_dscr;
extern __code WORD dev_strings;

/**
 * \brief Pointer to the active descriptor.
 **/
WORD pDevConfig = (WORD)&fullspd_dscr;
/**
 * \brief Pointer to the inactive descriptor.
 **/
WORD pOtherConfig = (WORD)&highspd_dscr;

/**
 * This function is called inside ISR functions, so must *not* call any
 * non-reentrant functions.
 * (This *includes* things like printf provided by SDCC.)
 **/
void handle_hispeed(BOOL highspeed) __critical {
    if (highspeed) {
        pDevConfig=(WORD)&highspd_dscr;
        pOtherConfig=(WORD)&fullspd_dscr;
    } else {
        pDevConfig=(WORD)&fullspd_dscr;
        pOtherConfig=(WORD)&highspd_dscr;
    }
}

/**
 * Handle:
 *  Device Descriptor
 *  Device Qualifier
 *  Configuration
 *  String
 *  Other-Speed
 **/
void _handle_get_descriptor() {
    printf("Get Descriptor ");

    // Sets to "Setup Data Pointer Auto Mode" see Appendix C - 29 of TRM
    SUDPTRCTL = 0x01;

    switch ( SETUPDAT[3] ) {
        case DSCR_DEVICE_TYPE:
            printf("Device\n");
            SUDPTRH = MSB((WORD)&dev_dscr);
            SUDPTRL = LSB((WORD)&dev_dscr);
            break;
        case DSCR_CONFIG_TYPE:
            // get the config descriptor
            printf("Config\n");
            SUDPTRH = MSB(pDevConfig);
            SUDPTRL = LSB(pDevConfig);
            break;        
        case DSCR_STRING_TYPE:
            printf("String idx: %d\n", SETUPDAT[2]);
            {
                STRING_DSCR* pStr = (STRING_DSCR*)&dev_strings;
                // pStr points to string 0
                BYTE idx = SETUPDAT[2];
                BYTE cur=0; // current check
                do {
                    if (idx==cur++) break;
                    //printf("Length of pStr: %d\n", pStr->dsc_len);
                    //printf("pstr: 0x%04x to ", pStr);
                    pStr = (STRING_DSCR*)((BYTE*)pStr + pStr->dsc_len);
                    //printf("%04x\n", pStr);
                    if (pStr->dsc_type != DSCR_STRING_TYPE) pStr=NULL;
                } while ( pStr && cur<=idx);
                
                if (pStr) {
                    //BYTE i;
                    //printf("found str: '");
                    //for (i=0;i<pStr->dsc_len-2;++i) {
                    //    printf(i%2==0?"%c":"%02x", *((BYTE*)(&pStr->pstr)+i));
                    //} printf("\n");

                    SUDPTRH = MSB((WORD)pStr);
                    SUDPTRL = LSB((WORD)pStr);
                    //SUDPTRH = MSB((WORD)&dev_strings);
                    //SUDPTRL = LSB((WORD)&dev_strings);
                } else {STALLEP0();}
                
            }
            
            break;
        case DSCR_DEVQUAL_TYPE:
            printf("Device Qualifier\n");
            // assumes this is a high speed capable device
            SUDPTRH = MSB((WORD)&dev_qual_dscr);
            SUDPTRL = LSB((WORD)&dev_qual_dscr);
            break;
        case DSCR_OTHERSPD_TYPE:
            printf("Other Speed\n");
            SUDPTRH = MSB(pOtherConfig);
            SUDPTRL = LSB(pOtherConfig);
            break;
        default:
            printf("Unknown: %02x\n", SETUPDAT[3]);
            STALLEP0();
    }
}
