/*
 * $Id: psx.c 206 2005-10-02 18:29:26Z anders $
 * 
 * Copyright (C) 2005 Anders Dubgaard <anders@dubgaard.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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "psx.h"

volatile uint8_t cmdbuf[]  = {
	0x01, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
volatile uint8_t databuf[] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

volatile psx_data *psx = &joydata;

volatile int8_t clk;
volatile int8_t bit_cnt = 0;
volatile int8_t byte_cnt = -1; /* -1 is the state prior to reading any data */
volatile uint8_t mask = 1;     /* Send LSB first */
volatile uint8_t psx_data_ready = false;

uint8_t middle = 0x80 - 1; /* middle is 127 */
uint8_t nullzone = 5;

/* PS joystick interrupt rutine.
 * Run (here) by soft interrupts from timer0 at 32KHz.
 *
 * States:
 *  -1    Gets the attention of the joystick controller
 * 0-8    Read 9 bytes of data, byte 0 is nonthing, byte 2 = 'here's the data'
 *   9    No more data, 
 *        
 */
SIGNAL(SIG_OVERFLOW0){
	if(byte_cnt < 0){  /* -1 or less... */
		att_lo();
		byte_cnt++;
	}
	else if (byte_cnt >= 0 && byte_cnt < 9){
		if(PINC & 1<<CLK) { /* Write bits on falling edge */
			clk_lo();
			if(cmdbuf[byte_cnt] & mask)
				cmd_hi();
			else
				cmd_lo();
			mask <<= 1;
		}
		else { /* READ bits on rising (leading) edge */
			clk_hi();
			if((PINC & 1<<DATA) == 1)
				setbit(databuf[byte_cnt], 7);  /* set MSB in current byte */
			else
				clrbit(databuf[byte_cnt], 7);  /* clear MSB in current byte */
			if(bit_cnt < 7)
				databuf[byte_cnt] >>= 1;    /* as we receive LSB first */
			bit_cnt++;        /* Prepare for next data and cmd bit */
			if(bit_cnt > 7){  /* if this is the 8th bits, prepare for next byte */
				bit_cnt = 0;
				mask = 1;
				byte_cnt++;
			}
		}
	} else {  /* byte_cnt = 9 */
		att_hi();
    cmd_hi();
		byte_cnt = -1;
		if(check_psx_type(databuf[1])) { /* Check joystick type */
      /* copy data buffer to psx struct */
      psx->type = databuf[1];    /* 2nd data byte, psx_arr[0] */
      psx->buttons = databuf[3]; /* 4th data byte, psx_arr[1] */
      psx->buttons2 = databuf[4];/* 5th data byte, psx_arr[2] */
      psx->r_horz = databuf[5];  /* 6th data byte, psx_arr[3] */
      psx->r_vert = databuf[6];  /* 7th data byte, psx_arr[4] */
      psx->l_horz = databuf[7];  /* 8th data byte, psx_arr[5] */
      psx->l_vert = databuf[8];  /* 9th data byte, psx_arr[6] */

			disable_psx_int();
			psx_data_ready = true;
		}
	}
}

/* Initialise the PSX connections; pins and register
 */
void psx_init(void) {
	DDRC = (1<<CMD)|(1<<ATT)|(1<<CLK);  // Pin directions, 1 = out, 0 = in

	att_hi();  /* ATT for PSJoy must start high */
	clk_hi();  /* CLK for PSJoy must start high */

	start_psx_timer();
}

/* Enables the joystick data gathering interrupt (which disables itself)
 * and waits for the data to be ready and returns a pointer to it.
 */
volatile psx_data *get_psx_data(void) {
	psx_data_ready = false;
	enable_psx_int();
	while(!psx_data_ready)
		;
	return psx;
}

void disable_psx_int(void) {
	clrbit(TIMSK, TOIE0);
}

void enable_psx_int(void) {
	setbit(TIMSK, TOIE0);
}

/* Returns 1 if the PS joystick's is OK, otherwise returns 0.
 */
int check_psx_type(int type){
	if(type == DIGIPAD
			|| type == ANALOG_RED) {
		return 1;
	}
	else {
		return 0;
	}
}

/* Wrapper for the PS joystick interface rutine timer start
 */
void start_psx_timer(void){
	start_timer0();
}

/* Simple calculation of an analog joystick's position from
 * the midpoint, 0x80, (when not used) to an end point, 0xFF.
 */
uint8_t psx_pwm_calc(uint8_t axis_pos) {
	uint8_t forward_val = 0;
  /* PWM, from midpoint to end of an axis = 0-0x80
   * - this must be mapped into the output range, 0-0xff
   */
  forward_val = axis_pos;
  if(forward_val > (middle - nullzone))
    forward_val = 0;
  else {
    forward_val = middle - forward_val;
    forward_val = forward_val * 2 + 1;
  }
  return forward_val;
}


