/*
* Driver interface to the ASIC Companion chip on the iPAQ H5400
*
* Copyright 2003 Compaq Computer Corporation.
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
* COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
* AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
* FITNESS FOR ANY PARTICULAR PURPOSE.
*
* Author:  Jamey Hicks <jamey.hicks@hp.com
*          March 2003
*/

#include <linux/module.h>
#include <linux/version.h>
#include <linux/config.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>

#include <asm/arch/hardware.h>
#include <asm/arch-sa1100/h3600_hal.h>
#include <asm/irq.h>

#include <asm/arch-pxa/h5400-gpio.h>
#include <asm/arch-pxa/h5400-asic.h>
#include <asm/arch-pxa/h5400-irqs.h>

#include "h5400_asic_sleeve.h"

#define SPI_READ_CMD 3

static struct semaphore spi_lock;

static void 
spi_set_cs(int cs)
{
	int count = 0;

	while (SSSR & SSSR_BSY) {
		if (count++ > 10000) {
			printk("spi_set_cs: timeout SSSR=%x\n", SSSR);
			break;
		}
	}

	SET_H5400_GPIO (OPT_SPI_CS_N, cs);
}

static int 
spi_transfer_byte(int dataout)
{
	int datain;
	int count = 0;
	/* write to fifo */
	SSDR = dataout;
	while (!(SSSR & SSSR_RNE)) {
		/* wait for data to arrive */
		if (count++ > 10000) {
#ifdef DEBUG_SPI
			printk("spi_transfer_byte: dataout=%x, timed out with SSSR=%x\n", dataout, SSSR);
#endif
			return 0;
		}
	}
	datain = SSDR;
#ifdef DEBUG_SPI
	printk("spi_transfer_byte: dataout=%x datain=%x\n", dataout, datain);
#endif
	return datain;
}

int 
h5400_spi_init(void)
{
	unsigned long flags;

	init_MUTEX(&spi_lock);
  
	h5400_asic_write_register (H5400_ASIC_GPIO_GPA_CON2, 0x5a);
	/* clear OPT_RESET */
	H5400_ASIC_CLEAR_BIT (H5400_ASIC_GPIO_GPA_DAT, ~0xFFFBFFFF);
	/* clear OPT_ON# */
	H5400_ASIC_CLEAR_BIT (H5400_ASIC_GPIO_GPA_DAT, ~0xFFFDFFFF);

#ifdef DEBUG_SPI
	printk("h5400_spi_init: gpa_dat=0x%08x \n", h5400_asic_read_register (H5400_ASIC_GPIO_GPA_DAT));
#endif

	SET_H5400_GPIO(OPT_SPI_CLK, 0);
	SET_H5400_GPIO(OPT_SPI_DOUT, 0);
	SET_H5400_GPIO(OPT_SPI_DIN, 0);
	SET_H5400_GPIO(OPT_SPI_CS_N, 1);

	/* set modes and directions */
	set_GPIO_mode(GPIO23_SCLK_MD);
	set_GPIO_mode(GPIO24_SFRM | GPIO_MD_MASK_DIR | GPIO_ACTIVE_LOW);
	set_GPIO_mode(GPIO25_STXD_MD);
	set_GPIO_mode(GPIO26_SRXD_MD);
	set_GPIO_mode(GPIO27_SEXTCLK);

	local_irq_save (flags);

	/* enable clock to the SSP */ 
	CKEN |= CKEN3_SSP;

	/* set up control regs */
	SSCR0 = 0;
	SSCR1 = 0;
	
	SSCR0 = (SSCR0_DSS_8BIT | SSCR0_FRF_SPI | (255 << SSCR0_SCR_SHIFT));
	SSCR1 = (SSCR1_SPO_RISING | SSCR1_SPH /* early */);
	
	SSCR0 |= SSCR0_SSE_ENABLED;

	/* disable SSP clock until required */
	CKEN &= ~CKEN3_SSP;

	local_irq_restore (flags);

	return 0;
}

int 
h5400_asic_spi_read( unsigned short address, unsigned char *data, unsigned short len )
{
	int i;
	unsigned long flags;

	if (!data)
		return -EINVAL;

	if (down_interruptible(&spi_lock))
		return -ERESTARTSYS;	

	/* enable clock to the SSP */ 
	local_irq_save (flags);
	CKEN |= CKEN3_SSP;
	local_irq_restore (flags);

#ifdef DEBUG_SPI
	printk("h5400_asic_spi_read address=%#08x len=%d\n", address, len);
#endif
	spi_set_cs(0);
	spi_transfer_byte(SPI_READ_CMD);
	spi_transfer_byte( (address >> 0) & 0xFF);
	for (i = 0; i < len; i++)
		data[i] = spi_transfer_byte( 0 );
	spi_set_cs(1);

	/* disable SSP clock */
	local_irq_save (flags);
	CKEN &= ~CKEN3_SSP;
	local_irq_restore (flags);

	up (&spi_lock);

	return len;
}

/* 
   Read from the PCMCIA option jacket microcontroller
*/

#define SPI_PCMCIA_HEADER          0xA1     /* STX */
#define	SPI_PCMCIA_ID              0x10
#define SPI_PCMCIA_ID_RESULT       0x13     /* 3 bytes : x.xx */
#define SPI_PCMCIA_BATTERY         0x20
#define SPI_PCMCIA_BATTERY_RESULT  0x24     /* 4 bytes : chem percent flag voltage */
#define SPI_PCMCIA_EBAT_ON         0x30     /* No result */

static int 
h5400_asic_spi_pcmcia_read( unsigned char cmd, unsigned char reply, unsigned char *data )
{
	unsigned char checksum;
	int result;
	int i;
	int rc = -EIO;
	unsigned long flags;

	if (down_interruptible (&spi_lock))
		return -ERESTARTSYS;	

	/* enable clock to the SSP */ 
	local_irq_save (flags);
	CKEN |= CKEN3_SSP;
	local_irq_restore (flags);

	spi_set_cs (0);

	/* Pause for a jiffie to give the micro time to respond */
	set_current_state (TASK_INTERRUPTIBLE);
	schedule_timeout (1);
	set_current_state (TASK_RUNNING);

	spi_set_cs (1);

	/* Send message */
	spi_transfer_byte (SPI_PCMCIA_HEADER);
	spi_transfer_byte (cmd);
	spi_transfer_byte (cmd);  /* The checksum */

	if (! data) {
		rc = 0;
		goto ret;
	}
	
	/* Pause for a jiffie to give the micro time to respond */
	set_current_state (TASK_INTERRUPTIBLE);
	schedule_timeout (1);
	set_current_state (TASK_RUNNING);

	/* Read message here */
	result = spi_transfer_byte (0xff);

	if (result != SPI_PCMCIA_HEADER)
		goto ret;

	result = spi_transfer_byte (0xff);
	if (result != reply) {
		printk ("expected SPI reply %x but got %x\n", reply, result);
		goto ret;
	}

	checksum = result;

	for ( i = 0 ; i < ( reply & 0x0f ) ; i++ ) {
		result = spi_transfer_byte (0xff);
		data[i] = result;
		checksum += result;
	}
	
	result = spi_transfer_byte (0xff);

	/* disable SSP clock */
	local_irq_save (flags);
	CKEN &= ~CKEN3_SSP;
	local_irq_restore (flags);

	up (&spi_lock);
	
	if (checksum != result) {
		printk ("spi checksum error: %x != %x\n", checksum, result);
		return -EIO;
	}

	return 0;

 ret:
	/* disable SSP clock */
	local_irq_save (flags);
	CKEN &= ~CKEN3_SSP;
	local_irq_restore (flags);

	up (&spi_lock);
	return rc;
}

int 
h5400_asic_spi_read_pcmcia_battery( unsigned char *chem, unsigned char *percent, unsigned char *flag )
{
	int result;
	unsigned char data[4];

	result = h5400_asic_spi_pcmcia_read (SPI_PCMCIA_BATTERY, SPI_PCMCIA_BATTERY_RESULT, data);

	if (result == 0) {
		*chem    = data[0];
		*percent = data[1];
		*flag    = data[2];
	}

	return result;
}

int 
h5400_asic_spi_write( unsigned short address, unsigned char *data, unsigned short len )
{
	return -EINVAL;
}


int 
h5400_asic_get_option_detect( int *result )
{
	long regval = h5400_asic_read_register (H5400_ASIC_PCMCIA_EPS);
	int odet = 0;

	if ((regval & (H5400_ASIC_PCMCIA_EPS_ODET0_N|H5400_ASIC_PCMCIA_EPS_ODET1_N)) == 0)
		odet = 1;

	if (result)
		*result = odet;
	return 0;
}

