/* Sega Multi-Tap to USB
 * Copyright (C) 2014 Francis-Olivier Gradel, ing.
 *
 * Using V-USB code from OBJECTIVE DEVELOPMENT Software GmbH. http://www.obdev.at/products/vusb/
 * Based on Raphaël Assénat's code. http://www.raphnet.net/
 *
 * 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/pgmspace.h>
#include <avr/interrupt.h>

#include <util/delay.h>
#include <string.h>

#include "usbdrv.h"
#include "usbconfig.h"
#include "gamepad.h"
#include "SegaMtap.h"

#define TIMEOUT 65500

static char SegaMtapInit(void);
static void SegaMtapUpdate(void);

#define REPORT_SIZE 4
static unsigned char last_read_data[4*REPORT_SIZE];
static unsigned char last_reported_data[4*REPORT_SIZE];

#define GET_TL()	(PINB & (1<<PB4)) 

#define TH_HIGH()	PORTB |= (1<<PB5)
#define TH_LOW()	PORTB &= ~(1<<PB5)

#define TR_HIGH()	PORTC |= (1<<PC2)
#define TR_LOW()	PORTC &= ~(1<<PC2)

static char SegaMtapInit(void)
{
	/* PIN1-PB0 = D0 (IN, 1)
	 * PIN2-PB1 = D1 (IN, 1)
	 * PIN3-PB2 = D2 (IN, 1)
	 * PIN4-PB3 = D3 (IN, 1)
	 * PIN5-PC1 = VCC (OUT,1), PC3 = VCC (OUT,1)
	 * PÎN6-PB4 = TL (IN, 0)
	 * PIN7-PB5 = TH (OUT, 0)
	 * PIN8-PD7 = GND (OUT, 0)
	 * PIN9-PC0 = nc (IN,0), PC2 = TR (OUT,0)
	 */
	
	DDRB &= ~((1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3)|(1<<PB4));
	DDRB |= ((1<<PB5));
	PORTB |= ((1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3));
	PORTB &= ~((1<<PB4)|(1<<PB5));

	DDRC &= ~(1<<PC0);
	DDRC |= ((1<<PC1)|(1<<PC3)|(1<<PC2));
	PORTC |= ((1<<PC1)|(1<<PC3));
	PORTC &= ~((1<<PC0)|(1<<PC2));

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

	return 0;
}

/* Each group of 3 nibble (on per index) has the following meaning:
 *
 * Nibble 0:
 *  Bit 3: Left
 *  Bit 2: Right
 *  Bit 1: Down
 *  Bit 0: Up
 *
 * Nibble 1:
 *  Bit 3: Start
 *  Bit 2: A
 *  Bit 1: C
 *  Bit 0: B
 *
 * Nibble 2:
 *  Bit 3: Mode
 *  Bit 2: X
 *  Bit 1: Y
 *  Bit 0: Z
 */

static void SegaMtapUpdate(void)
{
	unsigned char raw_data[18];
	int i, pos, j;
	unsigned int timer;

	TH_LOW();
	_delay_us(100);

	for (i=0; i<18; i++) {

		timer=0;
		if (i%2) {
			TR_HIGH();
			while (!GET_TL() && timer != TIMEOUT) {
				timer++;
			}
		}
		else {
			TR_LOW();
			while (GET_TL() && timer != TIMEOUT) {
				timer++;
			}
		}

		raw_data[i] = ~(PINB&0x0F); // Read data and convert to active-high
		
		_delay_us(100);
	}

	TR_HIGH();
	TH_HIGH();

	/* If the constant bytes are not what we expect, abort. */
	if (raw_data[0] != 0x0F)
		return;
	if (raw_data[1] != 0x0F)
		return;
	
	/* Raw data structure:
	 * 
	 * 0: Fixed value 0xf
	 * 1: Fixed value 0xf
	 * 2: Port A controller Id
	 * 3: Port B controller Id
	 * 4: Port C controller Id
	 * 5: Port D controller Id
	 * 6-18: Controller data
	 *
	 * 6 Button controller data: 3 nibbles
	 * 3 Button controller data: 2 nibbles
	 * Empty port data: 0 nibbles
	 **/

	/* Using the controller ID nibbles, expand the data
	 * to our 12 byte buffer (equivalent to 6btn controllers only). This
	 * simplifies report building */
	for (i=0, pos=6; i<4; i++) {
		int sz = 0;
		switch(raw_data[i+2])
		{
			case 0xe:	sz = 3; break; // 6 btn
			case 0xf:	sz = 2; break; // 3 btn
		}
		for (j=0; j<sz; j++) {
			last_read_data[i*REPORT_SIZE+j] = raw_data[pos]; 
			pos++;
		}
	}
}

static char SegaMtapChanged(char id)
{
	static int first = 1;
	if (first) { first = 0;  return 1; }

	id--; // first report is 1

	return memcmp(&last_read_data[id*REPORT_SIZE], &last_reported_data[id*REPORT_SIZE], REPORT_SIZE);
}

static char SegaMtapBuildReport(unsigned char *reportBuffer, char id)
{
	unsigned char *first_nibble;
	int x=128, y=128;
	unsigned char btns=0;
	
	if (!id)
		return 0;

	first_nibble = &last_read_data[(id-1) * REPORT_SIZE];

	memcpy(&last_reported_data[(id-1)*REPORT_SIZE], first_nibble, REPORT_SIZE);

	if (first_nibble[0] & 1) { y = 0; } // up
	if (first_nibble[0] & 2) { y = 255; } // down
	if (first_nibble[0] & 4) { x = 0; } // left
	if (first_nibble[0] & 8) { x = 255; } // right

	
	if (first_nibble[1] & 1) { btns |= 0x02; } // B
	if (first_nibble[1] & 2) { btns |= 0x04; } // C
	if (first_nibble[1] & 4) { btns |= 0x01; } // A
	if (first_nibble[1] & 8) { btns |= 0x08; } // Start

	if (first_nibble[2] & 1) { btns |= 0x40; } // Z
	if (first_nibble[2] & 2) { btns |= 0x20; } // Y
	if (first_nibble[2] & 4) { btns |= 0x10; } // X
	if (first_nibble[2] & 8) { btns |= 0x80; } // Mode

	reportBuffer[0] = id;
	reportBuffer[1] = x;
	reportBuffer[2] = y;
	reportBuffer[3] = btns;

	return REPORT_SIZE;
}

const char SegaMtap_usbHidReportDescriptor[] PROGMEM = {

	/* Controller and report_id 1 */
    0x05, 0x01,			// USAGE_PAGE (Generic Desktop)
    0x09, 0x04,			// USAGE (Joystick)
    0xa1, 0x01,			//	COLLECTION (Application)
    0x09, 0x01,			//		USAGE (Pointer)
    0xa1, 0x00,			//		COLLECTION (Physical)
	0x85, 0x01,			//			REPORT_ID (1)
	0x09, 0x30,			//			USAGE (X)
    0x09, 0x31,			//			USAGE (Y)
    0x15, 0x00,			//			LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,	//			LOGICAL_MAXIMUM (255)
    0x75, 0x08,			//			REPORT_SIZE (8)
    0x95, 0x02,			//			REPORT_COUNT (2)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)

    0x05, 0x09,			//			USAGE_PAGE (Button)
    0x19, 1,			//   		USAGE_MINIMUM (Button 1)
    0x29, 8,			//   		USAGE_MAXIMUM (Button 8)
    0x15, 0x00,			//   		LOGICAL_MINIMUM (0)
    0x25, 0x01,			//   		LOGICAL_MAXIMUM (1)
    0x75, 1,			// 			REPORT_SIZE (1)
    0x95, 8,			//			REPORT_COUNT (8)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
	0xc0,				//		END_COLLECTION
    0xc0,				// END_COLLECTION

	/* Controller and report_id 2 */
    0x05, 0x01,			// USAGE_PAGE (Generic Desktop)
    0x09, 0x04,			// USAGE (Joystick)
    0xa1, 0x01,			//	COLLECTION (Application)
    0x09, 0x01,			//		USAGE (Pointer)
    0xa1, 0x00,			//		COLLECTION (Physical)
	0x85, 0x02,			//			REPORT_ID (2)
	0x09, 0x30,			//			USAGE (X)
    0x09, 0x31,			//			USAGE (Y)
    0x15, 0x00,			//			LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,	//			LOGICAL_MAXIMUM (255)
    0x75, 0x08,			//			REPORT_SIZE (8)
    0x95, 0x02,			//			REPORT_COUNT (2)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
    0x05, 0x09,			//			USAGE_PAGE (Button)
    0x19, 1,			//   		USAGE_MINIMUM (Button 1)
    0x29, 8,			//   		USAGE_MAXIMUM (Button 8)
    0x15, 0x00,			//   		LOGICAL_MINIMUM (0)
    0x25, 0x01,			//   		LOGICAL_MAXIMUM (1)
    0x75, 1,			// 			REPORT_SIZE (1)
    0x95, 8,			//			REPORT_COUNT (8)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
	0xc0,				//		END_COLLECTION
    0xc0,				// END_COLLECTION

	/* Controller and report_id 3 */
    0x05, 0x01,			// USAGE_PAGE (Generic Desktop)
    0x09, 0x04,			// USAGE (Joystick)
    0xa1, 0x01,			//	COLLECTION (Application)
    0x09, 0x01,			//		USAGE (Pointer)
    0xa1, 0x00,			//		COLLECTION (Physical)
	0x85, 0x03,			//			REPORT_ID (3)
	0x09, 0x30,			//			USAGE (X)
    0x09, 0x31,			//			USAGE (Y)
    0x15, 0x00,			//			LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,	//			LOGICAL_MAXIMUM (255)
    0x75, 0x08,			//			REPORT_SIZE (8)
    0x95, 0x02,			//			REPORT_COUNT (2)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
    0x05, 0x09,			//			USAGE_PAGE (Button)
    0x19, 1,			//   		USAGE_MINIMUM (Button 1)
    0x29, 8,			//   		USAGE_MAXIMUM (Button 8)
    0x15, 0x00,			//   		LOGICAL_MINIMUM (0)
    0x25, 0x01,			//   		LOGICAL_MAXIMUM (1)
    0x75, 1,			// 			REPORT_SIZE (1)
    0x95, 8,			//			REPORT_COUNT (8)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
	0xc0,				//		END_COLLECTION
    0xc0,				// END_COLLECTION

	/* Controller and report_id 4 */
    0x05, 0x01,			// USAGE_PAGE (Generic Desktop)
    0x09, 0x04,			// USAGE (Joystick)
    0xa1, 0x01,			//	COLLECTION (Application)
    0x09, 0x01,			//		USAGE (Pointer)
    0xa1, 0x00,			//		COLLECTION (Physical)
	0x85, 0x04,			//			REPORT_ID (4)
	0x09, 0x30,			//			USAGE (X)
    0x09, 0x31,			//			USAGE (Y)
    0x15, 0x00,			//			LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,	//			LOGICAL_MAXIMUM (255)
    0x75, 0x08,			//			REPORT_SIZE (8)
    0x95, 0x02,			//			REPORT_COUNT (2)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
    0x05, 0x09,			//			USAGE_PAGE (Button)
    0x19, 1,			//   		USAGE_MINIMUM (Button 1)
    0x29, 8,			//   		USAGE_MAXIMUM (Button 8)
    0x15, 0x00,			//   		LOGICAL_MINIMUM (0)
    0x25, 0x01,			//   		LOGICAL_MAXIMUM (1)
    0x75, 1,			// 			REPORT_SIZE (1)
    0x95, 8,			//			REPORT_COUNT (8)
    0x81, 0x02,			//			INPUT (Data,Var,Abs)
	0xc0,				//		END_COLLECTION
    0xc0,				// END_COLLECTION

};

#define USBDESCR_DEVICE         1

// This is the same descriptor as in devdesc.c, but the product id is 0x0a99 

const char SegaMtap_usbDescrDevice[] PROGMEM = {    /* USB device descriptor */
    18,         /* sizeof(usbDescrDevice): length of descriptor in bytes */
    USBDESCR_DEVICE,    /* descriptor type */
    0x01, 0x01, /* USB version supported */
    USB_CFG_DEVICE_CLASS,
    USB_CFG_DEVICE_SUBCLASS,
    0,          /* protocol */
    8,          /* max packet size */
    USB_CFG_VENDOR_ID,  /* 2 bytes */
    0x99, 0x0a,  /* 2 bytes */
    USB_CFG_DEVICE_VERSION, /* 2 bytes */
    USB_CFG_VENDOR_NAME_LEN != 0 ? 1 : 0,         /* manufacturer string index */
    USB_CFG_DEVICE_NAME_LEN != 0 ? 2 : 0,        /* product string index */
    USB_CFG_SERIAL_NUMBER_LEN != 0 ? 3 : 0,  /* serial number string index */
    1,          /* number of configurations */
};

static Gamepad SegaMtapGamepad = {
	.num_reports			=	4,
	.reportDescriptorSize	=	sizeof(SegaMtap_usbHidReportDescriptor),
	.deviceDescriptorSize	=	sizeof(SegaMtap_usbDescrDevice),	
	.init					=	SegaMtapInit,
	.update					=	SegaMtapUpdate,
	.changed				=	SegaMtapChanged,
	.buildReport			=	SegaMtapBuildReport
};

Gamepad *SegaMtapGetGamepad(void)
{
	SegaMtapGamepad.reportDescriptor = (void*)SegaMtap_usbHidReportDescriptor;
	SegaMtapGamepad.deviceDescriptor = (void*)SegaMtap_usbDescrDevice;

	return &SegaMtapGamepad;
}

