/* "Atari" Video Touch Pad to USB
 * Copyright (C) 2013 Francis-Olivier Gradel, ing.
 *
 * Using V-USB code from OBJECTIVE DEVELOPMENT Software GmbH. http://www.obdev.at/products/vusb/
 * Based on HID-Test demo from Christian Starkjohann.
 * 
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * The author may be contacted at info@retronicdesign.com
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <util/delay.h>

#include "usbdrv.h"
#include "oddebug.h"

#include "fuses.h"
#include "bootloader/bootloader.h"
#include "eeprom.h"
#include "eepvars.h"

int usbDescriptorStringSerialNumber[]  = {
 	USB_STRING_DESCRIPTOR_HEADER(6),
	'0', '0', '0', '0', '0', '0'
};


/* ----------------------- hardware I/O abstraction ------------------------ */

/* Key assignments:

Atari Video Touch Pad

    PIN   5    9    6
          PC3  PC2  PB4
          R0   R1   R2
PB0	1 L0  1    2    3
PB1	2 L1  4    5    6
PB2	3 L2  7    8    9
PB3	4 L3  *    0    #

*/

static void hardwareInit(void)
{
uchar	i, j;

	DDRB &= ~((1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3));   /* PB0-3 input */
    PORTB |= ((1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3));   /* activate pull-ups */
    
	DDRB |= (1<<PB4);
	DDRC |= ((1<<PC2)|(1<<PC3));	/* PB4 & PC2, PC3 output */
	PORTB |= (1<<PB4);
	PORTC |= ((1<<PC2)|(1<<PC3));	/* all output 1 */

	DDRD |= (1<<PD7);	/* GND */
	PORTD &= ~(1<<PD7);

	/* Usb pin are init as outputs */
	DDRD |= ((1<<PD0)|(1<<PD2));

	
	j = 0;
	while(--j){     /* USB Reset by device only required on Watchdog Reset */
		i = 0;
		while(--i); /* delay >10ms for USB reset */
	}
	/* remove USB reset condition */
	DDRD &= ~((1<<PD0)|(1<<PD2));


    /* configure timer 0 for a rate of 12M/(1024 * 256) = 45.78 Hz (~22ms) */
    TCCR0B |= ((1<<CS00)|(1<<CS02));      /* timer 0 prescaler: 1024 */
}

/* ------------------------------------------------------------------------- */

#define NUM_KEYS    13

/* The following function returns an index for the first key pressed. It
 * returns 0 if no key is pressed.
 */
static uchar    keyPressed(void)
{
	int i,j;
	int key=0;

	for(i=0;i<3;i++)
	{
		if(i==0)	// Column 0 selected
		{
			PORTC &= ~(1<<PC3);
			PORTC |= (1<<PC2);
			PORTB |= (1<<PB4);
		}
		else if(i==1) // Column 1 selected
		{
			PORTC |= (1<<PC3);
			PORTC &= ~(1<<PC2);
			PORTB |= (1<<PB4);
		}
		else if(i==2) // Column 2 selected
		{
			PORTC |= (1<<PC3);
			PORTC |= (1<<PC2);
			PORTB &= ~(1<<PB4);
		}
		
		_delay_us(10);

		key = ((~PINB)&(0x0F));

		for(j=0;j<4 && !(key&0x01);j++)
			key=(key>>1);
		
		if((key&0x01))
			return ((j | (i<<2))+1);
		
	}
	return 0;
}

/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */

static uchar    reportBuffer[2];    /* buffer for HID reports */
static uchar    idleRate;           /* in 4 ms units */

const PROGMEM char usbHidReportDescriptor[35] = {   /* USB report descriptor */
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0xc0                           // END_COLLECTION
};
/* We use a simplifed keyboard report descriptor which does not support the
 * boot protocol. We don't allow setting status LEDs and we only allow one
 * simultaneous key press (except modifiers). We can therefore use short
 * 2 byte input reports.
 * The report descriptor has been created with usb.org's "HID Descriptor Tool"
 * which can be downloaded from http://www.usb.org/developers/hidpage/.
 * Redundant entries (such as LOGICAL_MINIMUM and USAGE_PAGE) have been omitted
 * for the second INPUT item.
 */

/* Keyboard usage values, see usb.org's HID-usage-tables document, chapter
 * 10 Keyboard/Keypad Page for more codes.
 */
#define KEY_1       30
#define KEY_2       31
#define KEY_3       32
#define KEY_4       33
#define KEY_5       34
#define KEY_6       35
#define KEY_7       36
#define KEY_8       37
#define KEY_9       38
#define KEY_0       39

#define KEYPAD_STAR		85
#define KEYPAD_SLASH	84

static const uchar  keyReport[NUM_KEYS + 1][2] PROGMEM = {
{0, 0},                     /* no key pressed */
{0, KEY_1},
{0, KEY_4},
{0, KEY_7},
{0, KEYPAD_STAR},
{0, KEY_2},
{0, KEY_5},
{0, KEY_8},
{0, KEY_0},
{0, KEY_3},
{0, KEY_6},
{0, KEY_9},
{0, KEYPAD_SLASH},
};

static void buildReport(uchar key)
{
/* This (not so elegant) cast saves us 10 bytes of program memory */
    *(int *)reportBuffer = pgm_read_word(keyReport[key]);
}

uchar	usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    usbMsgPtr = reportBuffer;
    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* class request type */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* we only have one report type, so don't look at wValue */
            buildReport(keyPressed());
            return sizeof(reportBuffer);
        }else if(rq->bRequest == USBRQ_HID_GET_IDLE){
            usbMsgPtr = &idleRate;
            return 1;
        }else if(rq->bRequest == USBRQ_HID_SET_IDLE){
            idleRate = rq->wValue.bytes[1];
        }
    }else{
        /* no vendor specific requests implemented */
    }
	return 0;
}

/* ------------------------------------------------------------------------- */

int	main(void)
{
uchar   key, lastKey = 0, keyDidChange = 0;
uchar   idleCounter = 0;
int i;

	char serial[6];

	/* Read the serial number from eeprom */
	EEPROM_read((int)&EEPVars.E_serial,6,serial);
	if(serial[0]!=0)
		for(i=0;i<6;i++)
			usbDescriptorStringSerialNumber[i+1]=(int)serial[i];


	wdt_enable(WDTO_2S);
    hardwareInit();
	odDebugInit();
	usbInit();
	sei();
    DBG1(0x00, 0, 0);
	for(;;){	/* main event loop */
		wdt_reset();
		usbPoll();
        key = keyPressed();
        if(lastKey != key){
            lastKey = key;
            keyDidChange = 1;
        }
        if(TIFR0 & (1<<TOV0)){   /* 22 ms timer */
            TIFR0 = 1<<TOV0;
            if(idleRate != 0){
                if(idleCounter > 4){
                    idleCounter -= 5;   /* 22 ms in units of 4 ms */
                }else{
                    idleCounter = idleRate;
                    keyDidChange = 1;
                }
            }
        }
        if(keyDidChange && usbInterruptIsReady()){
            keyDidChange = 0;
            /* use last key and not current key status in order to avoid lost
               changes in key status. */
            buildReport(lastKey);
            usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
        }
	}
	return 0;
}

/* ------------------------------------------------------------------------- */
