/*
*
* Driver for the Compaq iPAQ Mercury Backpaq FPGA programming interface
*
* Copyright 2001 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.
*
* 
* This character driver provides a programming interface to the Xilinx FPGA
* on the Mercury Backpaq.  To program the device, it suffices to execute:
*
*    cat fpga_file_name.bin > /dev/backpaq
*
* To check on the status of the FPGA, call:
*
*    cat /proc/backpaq
*
* 
* ToDo:
* 
*     1. Use the hardware_version bit to determine how many bytes should
*        be sent to the backpaq and return an appropriate error code.
*                   
* Author: Andrew Christian
*         <andyc@handhelds.org>
*/

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

#include <linux/init.h>
#include <asm/uaccess.h>         /* get_user,copy_to_user*/
#include <linux/vmalloc.h>

#include <linux/config.h>
#include <linux/proc_fs.h> 
#include <linux/pm.h>
#include <asm/arch/backpaq.h>
#include <linux/delay.h>
#include <linux/devfs_fs_kernel.h>

//#define FPGA_FILESIZE  97652

int valid_fpga_sizes[] = {
	97652,
	107980,
	0
};

struct h3600_backpaq_fpga_dev_struct {
	unsigned int  usage_count;     /* Number of people currently using this device */
	unsigned long busy_count;      /* Number of times we've had to wait for EMPTY bit */
	unsigned long bytes_written;   /* Bytes written in the most recent open/close */
};

#define MODULE_NAME      "h3600_backpaq_fpga"

#define FPGA_MINOR        0
#define FPGA_DEVICE_NAME "backpaq/fpga"

#define FPGA_PROC_DIR  "backpaq"
#define FPGA_PROC_NAME "backpaq/fpga"

/* Local variables */

static struct proc_dir_entry   *proc_backpaq_fpga;
static devfs_handle_t           devfs_fpga;

static struct h3600_backpaq_fpga_dev_struct  h3600_backpaq_fpga_data;
static int    h3600_backpaq_fpga_major_num = 0;

/**************************************************************/

enum {
	FPGA_READY      = 0,
	FPGA_NO_BACKPAQ,
	FPGA_CRC_ERROR,
	FPGA_NOT_DONE_PROGRAMMING,
	FPGA_NOT_PROGRAMMED,
	FPGA_BAD_DATA_FILE_SIZE
};

static char * h3600_backpaq_fpga_strings[] = {
	"Okay",
	"No backpaq present",
	"CRC error",
	"Not done programming",
	"Not programmed",
	"Bad data file size",
};

int h3600_backpaq_valid_fpga_size( int len )
{
	int i;
	for ( i = 0 ; valid_fpga_sizes[i] != 0 ; i++ )
		if ( len == valid_fpga_sizes[i] )
			return len;
	return 0;
}
	
int h3600_backpaq_fpga_state( void  )
{
	if ( !h3600_backpaq_present() )
		return FPGA_NO_BACKPAQ;
	     
	if ( !(BackpaqSysctlFPGAStatus & BACKPAQ_FPGASTATUS_INITL) )
		return FPGA_CRC_ERROR;

	if ( !(BackpaqSysctlFPGAStatus & BACKPAQ_FPGASTATUS_DONE))
		return FPGA_NOT_DONE_PROGRAMMING;

	if ( h3600_backpaq_fpga_data.bytes_written == 0 )
		return FPGA_NOT_PROGRAMMED;

	if ( !h3600_backpaq_valid_fpga_size(h3600_backpaq_fpga_data.bytes_written) ) 
		return FPGA_BAD_DATA_FILE_SIZE;

	return FPGA_READY;
}

int h3600_backpaq_fpga_status( void )
{
	int result = -ENODEV;

	switch (h3600_backpaq_fpga_state()) {
	case FPGA_READY:
		return 0;
	case FPGA_NO_BACKPAQ:
		return -ENODEV;
	}
	return result;
}

EXPORT_SYMBOL(h3600_backpaq_fpga_status);

/**************************************************************/

/*
 * Write function.
 * 
 * To prevent CPU deadlock, we write a limited number of bytes and then
 * return to the caller.
 *
 * The potential difficulty with this approach is that we perform a
 * kernel-to-user mode context switch each time this is called, which
 * is not time-efficient.
 *
 * A potential work around is to allow the function to block until 
 * all data is written, but insert "schedule()" calls periodically 
 * to allow other processes to get time. 
 */

#define MY_MAX_WRITE  512

ssize_t h3600_backpaq_fpga_write( struct file *filp, const char *buf, 
				  size_t count, loff_t *f_pos )
{
	unsigned char mybuf[MY_MAX_WRITE];
	unsigned char *pbuf;
	int bytes_to_write;
	int result;
	int i;

	if ( !h3600_backpaq_present())
		return -ENODEV;

	if ( count <= 0 )
		return 0;  /* Not an error to write nothing? */

	bytes_to_write = ( count < MY_MAX_WRITE ? count : MY_MAX_WRITE );

	/* Copy over from user space */
	result = copy_from_user( mybuf, buf, bytes_to_write );
	if ( result ) 
		return result;

	/* Write out the bytes */
	pbuf = mybuf;
	for ( i = 0 ; i < bytes_to_write ; pbuf++, i++ ) {
		/* Wait for the CPLD to signal it's ready for the next byte */
		while (!(BackpaqSysctlFPGAStatus & BACKPAQ_FPGASTATUS_EMPTY))
			h3600_backpaq_fpga_data.busy_count++;
		
		/* Write *pbuf to the FPGA */
		BackpaqSysctlFPGAProgram = *pbuf;
	}

	h3600_backpaq_fpga_data.bytes_written += i;
	return i;
}

/* 
 * On open we place the FPGA in a known programming state
 */

int setup_fpga( void )
{
	BackpaqSysctlPCMCIAPower |= BACKPAQ_PWRCTL_18VEN;

	/* Turn on audio and camera clocks */
	BackpaqSysctlGenControl |= BACKPAQ_REGC_CAM_CLKEN | BACKPAQ_REGC_AUDIO_CLKEN;
	/* Clear the control registers to wipe out memory */
	BackpaqSysctlFPGAControl = BACKPAQ_FPGACTL_M0 | BACKPAQ_FPGACTL_M1 | BACKPAQ_FPGACTL_M2;
	/* Wait 100 ns */
	udelay( 1 );       /* Wait for 1 microsecond */
	/* Put the FPGA into program mode */
	BackpaqSysctlFPGAControl = BACKPAQ_FPGACTL_M0 | BACKPAQ_FPGACTL_M1 
		| BACKPAQ_FPGACTL_M2 
		| BACKPAQ_FPGACTL_PROGRAM;
	/* We could run a few sanity checks here */
	return 0;
}


/*
 * On close we need to verify that the correct number of bytes were
 * written and that the FPGA has asserted its "I've been programmed"
 * flag. 
 *
 * If the FPGA hasn't beepn programmed, we return an error code.
 */

int shutdown_fpga( void )
{
	if ( !h3600_backpaq_present()) {
		h3600_backpaq_fpga_data.bytes_written = 0;
		return -ENODEV;
	}

	/* Make sure we've finished programming */
	while (!(BackpaqSysctlFPGAStatus & BACKPAQ_FPGASTATUS_EMPTY))
		h3600_backpaq_fpga_data.busy_count++;

	/* Reset the FPGA */
	BackpaqSysctlFPGAControl = BACKPAQ_FPGACTL_M0 | BACKPAQ_FPGACTL_M2 
		| BACKPAQ_FPGACTL_PROGRAM;

	BackpaqSocketFPGAReset &= ~BACKPAQ_SOCKET_FPGA_RESET;

	udelay(2);   /* Wait for 1 microsecond */

	/* Unreset the FPGA */
	BackpaqSocketFPGAReset |= BACKPAQ_SOCKET_FPGA_RESET;

	/* Check for illegal states */
	if ( !(BackpaqSysctlFPGAStatus & BACKPAQ_FPGASTATUS_INITL) ) {
		h3600_backpaq_fpga_data.bytes_written = 0;
		return -EIO;      /* CRC error */
	}

	if ( !(BackpaqSysctlFPGAStatus & BACKPAQ_FPGASTATUS_DONE) ) {
		h3600_backpaq_fpga_data.bytes_written = 0;
		return -EIO;        /* Incomplete file */
	}

	/* This should be updated to reflect the type of FPGA we're programming */
	if ( !h3600_backpaq_valid_fpga_size( h3600_backpaq_fpga_data.bytes_written))
		return -EIO;

	return 0;
}
  
int h3600_backpaq_fpga_open( struct inode *inode, struct file *filp )
{
	int result = 0;
/*
	if ( BackpaqSysctl->hardware_version != BACKPAQ_HARDWARE_VERSION_1 )
		return -ENXIO;
*/
	if ( h3600_backpaq_fpga_data.usage_count > 0 )
		return -EBUSY;

	if ( !h3600_backpaq_present() ) {
		h3600_backpaq_fpga_data.bytes_written = 0;
		return -ENODEV;
	}

	if ( (result = setup_fpga()) != 0 )
		return result;

	h3600_backpaq_fpga_data.usage_count++;
	h3600_backpaq_fpga_data.busy_count = 0;
	h3600_backpaq_fpga_data.bytes_written = 0;

	MOD_INC_USE_COUNT;
	return 0;    /* Success */
}

int h3600_backpaq_fpga_release( struct inode *inode, struct file *filp )
{
	int result;

	result = shutdown_fpga();
	h3600_backpaq_fpga_data.usage_count--;

	MOD_DEC_USE_COUNT;
	return result;
}


/***************************************************************
 *  /proc/backpaq
 ***************************************************************/

#define PRINT_GENERAL_REG(x,s) \
	p += sprintf (p, "%lx %-24s : %04x\n", (unsigned long) &x, s, x)
#define PRINT_SYSCTL_REG(x,s)   PRINT_GENERAL_REG(BackpaqSysctl ## x,s)
#define PRINT_SOCKET_REG(x,s)   PRINT_GENERAL_REG(BackpaqSocket ## x,s)

static int proc_h3600_backpaq_read(char *page, char **start, off_t off,
			  int count, int *eof, void *data)
{
	char *p = page;
	int len;

	if (!h3600_backpaq_present()) {
		p += sprintf(p,"No backpaq present\n");
	}
	else {
//		PRINT_SYSCTL_REG(HardwareVersion,"Hardware version");
		PRINT_SYSCTL_REG(FirmwareVersion,"Firmware version");
		PRINT_SYSCTL_REG(FPGAControl,    "FPGA control");
		PRINT_SYSCTL_REG(FPGAStatus,     "FPGA status");
		PRINT_SYSCTL_REG(FPGAProgram,    "FPGA program");
		PRINT_SYSCTL_REG(PCMCIAPower,    "PCMCIA power");
		PRINT_SYSCTL_REG(FlashControl,   "Flash control");
		PRINT_SYSCTL_REG(GenControl,     "General control");

		PRINT_SOCKET_REG(FPGAFirmwareVersion,  "FPGA firmware rev");
		PRINT_SOCKET_REG(CameraLiveMode,       "Camera live mode");
		PRINT_SOCKET_REG(CameraIntegrationTime,"Camera integration time");
		PRINT_SOCKET_REG(CameraClockDivisor,   "Camera clock divisor");
		PRINT_SOCKET_REG(CameraFifoInfo,       "Camera fifo information");

		p += sprintf(p, "         Busy count        : %ld\n", 
			     h3600_backpaq_fpga_data.busy_count );
		p += sprintf(p, "         Bytes written     : %ld\n", 
			     h3600_backpaq_fpga_data.bytes_written );
		p += sprintf(p, "         Usage count       : %d\n", 
			     h3600_backpaq_fpga_data.usage_count );

		p += sprintf(p, "FPGA program status        : ");
		p += sprintf(p, h3600_backpaq_fpga_strings[ h3600_backpaq_fpga_state() ]);
		p += sprintf(p, "\n");
	}

	len = (p - page) - off;
	if (len < 0)
		len = 0;

	*eof = (len <= count) ? 1 : 0;
	*start = page + off;

	return len;
}

/***********************************************************************************/

/* power management skeleton */

#ifdef CONFIG_PM
static struct h3600_backpaq_device *fpga_backpaq_dev;

static int h3600_backpaq_fpga_callback(struct h3600_backpaq_device *device, 
				       h3600_backpaq_request_t req )
{
	printk(__FUNCTION__ ": fpga backpaq callback %d\n", req);
	switch (req) {
	case H3600_BACKPAQ_EJECT:
		h3600_backpaq_fpga_data.bytes_written = 0;
		break;
	case H3600_BACKPAQ_INSERT:
		/* We let the hotplug routine handle this */
		break;
	case H3600_BACKPAQ_SUSPEND: 
		h3600_backpaq_fpga_data.bytes_written = 0;
                break;
	case H3600_BACKPAQ_RESUME:
		break;
        }
        return 0;
}
#endif /* CONFIG_PM */

/***********************************************************************************/


struct file_operations h3600_backpaq_fpga_fops = {
	write:   h3600_backpaq_fpga_write,
	open:    h3600_backpaq_fpga_open,
	release: h3600_backpaq_fpga_release,
};

/***********************************************************************************/

int __init h3600_backpaq_fpga_init_module(void)    
{    
	int result;    
	printk(KERN_ALERT __FILE__ ": registering char device");    

	/* Register my device driver */
	result = devfs_register_chrdev(0,MODULE_NAME, &h3600_backpaq_fpga_fops);    
	if ( result <= 0 ) {
		printk(" can't get major number\n");
		return result;    
	}    
	if ( h3600_backpaq_fpga_major_num == 0 )
		h3600_backpaq_fpga_major_num = result;
	printk(" %d\n", h3600_backpaq_fpga_major_num);

	/* Clear the default structure */
 	memset(&h3600_backpaq_fpga_data, 0, sizeof(struct h3600_backpaq_fpga_dev_struct));

	/* Create a devfs entry */
#ifdef CONFIG_DEVFS_FS
	devfs_fpga = devfs_register( NULL, FPGA_DEVICE_NAME, DEVFS_FL_DEFAULT,
				       h3600_backpaq_fpga_major_num, FPGA_MINOR,
				       S_IFCHR | S_IRUSR | S_IWUSR, 
				       &h3600_backpaq_fpga_fops, NULL );
#endif

#ifdef CONFIG_PROC_FS
	/* Set up the PROC file system entry */
	proc_backpaq_fpga = create_proc_entry(FPGA_PROC_NAME, 0, NULL);
	if ( !proc_backpaq_fpga ) {
		/* We probably need to create the "backpaq" directory first */
		proc_mkdir(FPGA_PROC_DIR,0);
		proc_backpaq_fpga = create_proc_entry(FPGA_PROC_NAME, 0, NULL);
	}
	
	if ( proc_backpaq_fpga )
		proc_backpaq_fpga->read_proc = proc_h3600_backpaq_read;    
	else {
		printk(KERN_ALERT __FILE__ ": unable to create proc entry %s\n", FPGA_PROC_NAME);
		devfs_unregister( devfs_fpga );
		devfs_unregister_chrdev( h3600_backpaq_fpga_major_num, MODULE_NAME );
		return -ENODEV;
	}
#endif

#ifdef CONFIG_PM
	fpga_backpaq_dev = h3600_backpaq_register_device( H3600_BACKPAQ_FPGA_DEV, 0, 
							  h3600_backpaq_fpga_callback );
	printk(KERN_ALERT __FILE__ ": registered backpaq callback=%p\n", h3600_backpaq_fpga_callback);
#endif

	return 0;    
} 

void __exit h3600_backpaq_fpga_exit_module(void)
{
	printk(KERN_ALERT __FILE__ ": exit\n");

#ifdef CONFIG_PM
        h3600_backpaq_unregister_device(fpga_backpaq_dev);
#endif
#ifdef CONFIG_PROC_FS
	if (proc_backpaq_fpga)
		remove_proc_entry(FPGA_PROC_NAME, 0);
#endif
#ifdef CONFIG_DEVFS_FS
	devfs_unregister( devfs_fpga );
#endif
	devfs_unregister_chrdev( h3600_backpaq_fpga_major_num, MODULE_NAME );
}

module_init(h3600_backpaq_fpga_init_module);
module_exit(h3600_backpaq_fpga_exit_module);

