/*
*
* 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/config.h>
#include <linux/proc_fs.h> 
#include <asm/arch/backpaq.h>
#include <linux/delay.h>
#include <linux/devfs_fs_kernel.h>

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 BACKPAQ_DIR_NAME "backpaq"
#define FPGA_MINOR        0
#define FPGA_DEVICE_NAME "fpga"

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


/* Global variables used elsewhere */
struct proc_dir_entry *proc_backpaq_dir;
devfs_handle_t         devfs_backpaq_dir;

/* 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;

static volatile struct mercury_backpaq_sysctl *mercury_backpaq_sysctl 
     = ((struct mercury_backpaq_sysctl *)BACKPAQ_SYSCTL_BASE);
static volatile struct mercury_backpaq_socket *mercury_backpaq_socket 
     = ((struct mercury_backpaq_socket *)BACKPAQ_SOCKET_STATUS_BASE);


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

static int h3600_backpaq_fpga_ioctl(struct inode *inode, struct file * filp,
                    unsigned int cmd, unsigned long arg)
{
  return -ENOIOCTLCMD;
}


ssize_t h3600_backpaq_fpga_read( struct file *filp, char *buf, size_t count,
		 loff_t *f_pos )
{
  return 0;
}

/*
 * 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 ( 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 (!(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_EMPTY))
			h3600_backpaq_fpga_data.busy_count++;
		
		/* Write *pbuf to the FPGA */
		mercury_backpaq_sysctl->fpga_program = *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 )
{
  mercury_backpaq_sysctl->pcmcia_power |= BACKPAQ_PWRCTL_18VEN;

  /* Turn on audio and camera clocks */
  mercury_backpaq_sysctl->gencontrol |= BACKPAQ_REGC_CAM_CLKEN | BACKPAQ_REGC_AUDIO_CLKEN;
  /* Clear the control registers to wipe out memory */
  mercury_backpaq_sysctl->fpga_ctl = BACKPAQ_FPGACTL_M0 | BACKPAQ_FPGACTL_M1 | BACKPAQ_FPGACTL_M2;
  /* Wait 100 ns */
  udelay( 1 );       /* Wait for 1 microsecond */
  /* Put the FPGA into program mode */
  mercury_backpaq_sysctl->fpga_ctl = 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 been programmed, we return an error code.
 */

int shutdown_fpga( void )
{
	/* Make sure we've finished programming */
	while (!(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_EMPTY))
		h3600_backpaq_fpga_data.busy_count++;

	/* Reset the FPGA */
	mercury_backpaq_sysctl->fpga_ctl = BACKPAQ_FPGACTL_M0 | BACKPAQ_FPGACTL_M2 
		| BACKPAQ_FPGACTL_PROGRAM;

	mercury_backpaq_socket->fpga_reset &= ~BACKPAQ_SOCKET_FPGA_RESET;

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

	/* Unreset the FPGA */
	mercury_backpaq_socket->fpga_reset |= BACKPAQ_SOCKET_FPGA_RESET;

	/* Check for illegal states */
	if ( !(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_INITL) )
		return -EIO;        /* CRC error */

	if ( !(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_DONE) )
		return -EIO;        /* Incomplete file */

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

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

	if ( h3600_backpaq_fpga_data.usage_count > 0 )
		return -EBUSY;

	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 %-17s : %04x\n", (unsigned long) &x, s, x)
#define PRINT_SYSCTL_REG(x,s)   PRINT_GENERAL_REG(mercury_backpaq_sysctl->x,s)
#define PRINT_SOCKET_REG(x,s)   PRINT_GENERAL_REG(mercury_backpaq_socket->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;
  
	PRINT_SYSCTL_REG(hardware_version,"Hardware version");
	PRINT_SYSCTL_REG(firmware_version,"Firmwarwe version");
	PRINT_SYSCTL_REG(fpga_ctl,"FPGA control");
	PRINT_SYSCTL_REG(fpga_status,"FPGA status");
	PRINT_SYSCTL_REG(fpga_program,"FPGA program");
	PRINT_SYSCTL_REG(pcmcia_power,"PCMCIA power");
	PRINT_SYSCTL_REG(flash_control,"Flash control");
	PRINT_SYSCTL_REG(gencontrol,"General control");

	PRINT_SOCKET_REG(fpga_firmware_version,"FPGA firmware rev");

	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        : ");
	if ( !(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_INITL) ) {
		p += sprintf(p, "CRC error\n");        
	} else if ( !(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_DONE) ) {
		p += sprintf(p, "Not done programming\n");
	} else if ( h3600_backpaq_fpga_data.bytes_written == 0 ) {
		p += sprintf(p, "No data file loaded\n");
	} else if ( h3600_backpaq_fpga_data.bytes_written != 97652 ) {
		p += sprintf(p, "Wrong data file size\n");
	} else {
		p += sprintf(p, "Okay\n");
	}

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

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

	return len;
}

struct file_operations h3600_backpaq_fpga_fops = {
	read:    h3600_backpaq_fpga_read,
	write:   h3600_backpaq_fpga_write,
	open:    h3600_backpaq_fpga_open,
	release: h3600_backpaq_fpga_release,
	ioctl:   h3600_backpaq_fpga_ioctl,
};

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

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);

	devfs_backpaq_dir = devfs_mk_dir( NULL, BACKPAQ_DIR_NAME, NULL );
	devfs_fpga = devfs_register( devfs_backpaq_dir, FPGA_DEVICE_NAME, DEVFS_FL_DEFAULT,
				       h3600_backpaq_fpga_major_num, FPGA_MINOR,
				       S_IFCHR | S_IRUSR | S_IWUSR, 
				       &h3600_backpaq_fpga_fops, NULL );

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

#ifdef CONFIG_PROC_FS
	/* Set up the PROC file system entry */
	proc_backpaq_dir = NULL;
	proc_backpaq_fpga = NULL;
	if ( (proc_backpaq_dir = proc_mkdir(FPGA_PROC_DIR, 0)) != NULL
	     && (proc_backpaq_fpga = create_proc_entry(FPGA_PROC_NAME, 0, proc_backpaq_dir)) != NULL ) {
		proc_backpaq_fpga->read_proc = proc_h3600_backpaq_read;    
	}
	else {
		printk(KERN_ALERT __FILE__ ": unable to create proc entry %s\n", FPGA_PROC_NAME);
	}
#endif

	return 0;    
} 

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

#ifdef CONFIG_PROC_FS
	if (proc_backpaq_fpga)
		remove_proc_entry(FPGA_PROC_NAME, 0);
	if (proc_backpaq_dir)
		remove_proc_entry(FPGA_PROC_DIR, 0);
#endif

	devfs_unregister( devfs_fpga );
	devfs_unregister( devfs_backpaq_dir );
	devfs_unregister_chrdev( h3600_backpaq_fpga_major_num, MODULE_NAME );
}

module_init(h3600_backpaq_fpga_init_module);
module_exit(h3600_backpaq_fpga_exit_module);

