/* $Id: syncser.c,v 1.2 2000/10/17 13:18:50 henriken Exp $
 * 
 * Copyright (c) Bjorn Wesen
 * select() and non blocking support + shadow register handling
 * by Johan Adolfsson, Axis Communications AB
 */

#include <linux/config.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <asm/irq.h>

#include <linux/malloc.h>
#include <linux/interrupt.h>

#include <asm/segment.h>
#include <asm/system.h>
#include <asm/svinto.h>
#include <asm/io.h>

#include <linux/etraxsyncser.h>

#define D(x)

/* #define SYNCSER_USES_INTERRUPT */

#define SYNCSER_STATISTICS 0


#define SYNCSER_MAJOR 63

#define BUFSIZE 256000/*65536*/
#define LOWWATERMARK (BUFSIZE*3 / 4)
#define HIGHWATERMARK (BUFSIZE - 6000)

static int ocount = 0;
static struct wait_queue *ss_wq;

static unsigned char ss_buf[BUFSIZE];  /* ring buffer */
static long int wrpos = 0, rdpos = 0, sbufsize = 0;

/* bit 0 is CLOCK
 * bit 1 is DATA
 */

/* PA0 is DREQ
 * PA1 is DATA
 * PA2 is CLOCK
 * the MP3 chip latches on falling clock
 */

// on NAB, PA4 is SDI, PA5 is SCKR, PA3 is DREQ

#define DATA_OUT(x)  REG_SHADOW_SET(R_PORT_PA_DATA, port_pa_data_shadow, 4, x)
#define CLOCK_OUT(x) REG_SHADOW_SET(R_PORT_PA_DATA, port_pa_data_shadow, 5, x)
#define DREQ_IN()    (*R_PORT_PA_READ & (1 << 3))

static struct tq_struct syncser_task;

#if SYNCSER_STATISTICS
#define MAX_STAT 300
static unsigned long stat_write[MAX_STAT];
static int stat_count = 0;
#endif

#if 0
static void
dodelay(int i)
{
	while(i--) /* nothing */;
}
#endif

static void
send_8(unsigned char v)
{
	int i;
	unsigned char pa;
	/* output 8 bits in MSB ordering */
	pa =  port_pa_data_shadow;
	for(i = 0; i < 8; i++) {
		if(v & 0x80) {
			/* output a 1  (C D: 1 1 then 0 1)*/
			*R_PORT_PA_DATA = pa |= 0x30;
			//0x30; //0x6;

			*R_PORT_PA_DATA = pa &= ~0x20;
			//0x10; //0x2;
		} else {
			/* output a 0 ( C D: 1 0 then 0 0*/
			pa &= ~0x30;
			*R_PORT_PA_DATA = pa |= 0x20;
			//0x20; //0x4;
			*R_PORT_PA_DATA =  pa &= ~0x30;
			//0x00; //0x0;
		}
		v <<= 1;
		/* Clock period min 50ns.
		 * each low and high: min 10 ns
		 * At 100Mhz (MIPS) each instruction is typically 10 ns
		 * so we shouldn't be needing any delay.
		 */
		/*dodelay(5); */		
	}
	port_pa_data_shadow = pa;
}


/* every time we get the timer irq, we send data until we get a busy signal */

static void 
syncser_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
#ifndef SYNCSER_USES_INTERRUPT
	/* Schedule bottom half routine in tq_scheduler */
	/* Should we check that we are not schedule already? */
	if (sbufsize /* && DREQ_IN() */){
		queue_task_irq_off(&syncser_task, &tq_immediate);
		mark_bh(IMMEDIATE_BH);
	}
#else
	
#if SYNCSER_STATISTICS
#define HISTORY_MAX 4
	static unsigned long write_history[HISTORY_MAX] = {0,0,0,0};
	static unsigned long history_sum = 0;
	static int history_index = 0;
	unsigned long write_count = 0;
	static unsigned long write_max = 200;
#endif
	D(static int had_data = 0;
	  
	  if (sbufsize > 0)
	  {
		  had_data = 1;
	  }
		);
	/*
	 * The write pattern typically looks like this:
	 * 0418 0000 0000 0418 0000 0418 0000 0000 0418 0000 
	 * 0000 0418 0000 0418 0000 0000 0418 0000 0418 0000 
	 * 0000 0417 0000 0000 0418 0000 0418 0000 0000 0418 
	 * 0000 0418 0000 0000 0418 0000 0000 0418 0000 0418
	 * (the values depend on the bitrate: 418 for 128kbps, 316 for 96kbps)
	 * In every second or third timer interrupt we write
	 * a certain amount of data.
	 * We can try to even this out to balance the load/latencies:
	 * First time - set a 
	 * Keep track of how much we write: wrote_count
	 * Remember how much we wrote the previous interrupt: prev_count
	 */
	while(sbufsize &&              /* check if there IS anything to send */
#if SYNCSER_STATISTICS
	      write_count < write_max &&
#endif
	      DREQ_IN()) { /* check for DATA_REQ */
		send_8(ss_buf[rdpos]);
		rdpos = (rdpos + 1) % BUFSIZE;
		sbufsize--;
#if SYNCSER_STATISTICS
		write_count++;
#endif
	}
#if SYNCSER_STATISTICS
	if (DREQ_IN()){
		/* Could send more */
		write_max++;
	}
	else
	{
		if (write_count == 0){
			write_max = (write_max*3) / 4;
		}
		if (write_max > 100)
			write_max--;
	}
	
	if (stat_count < MAX_STAT){
		stat_write[stat_count] = write_count;
		stat_count++;
	}
#endif
	
	/* make sure blocked writes wake up to fill up the buffer */
	
	if(sbufsize < LOWWATERMARK)
		wake_up_interruptible(&ss_wq);
	D(
		if (had_data && sbufsize == 0)
		{
			printk("\n*\n");
			had_data = 0;
		}
		);
#endif /* SYNCSER_USES_INTERRUPT */
} /* syncser_interrupt */



/* The task is run as in the tq_timer queue.
 * we send data until we get a busy signal 
 */
static void 
syncser_task_routine(void *data)
{	
	while(sbufsize &&              /* check if there IS anything to send */
	      DREQ_IN()) { /* check for DATA_REQ */
		send_8(ss_buf[rdpos]);
		rdpos = (rdpos + 1) % BUFSIZE;
		sbufsize--;
	}
	
	/* make sure blocked writes wake up to fill up the buffer */
	if(sbufsize < LOWWATERMARK)
		wake_up_interruptible(&ss_wq);
	/* Reschedule is done in timer_int or in write() */
} /* syncser_task_routine */



static int
syncser_write(struct inode *inode, struct file *file,
	      const char *buf, int count)
{
	unsigned long flags;
	long int wrlen;
	long local_sbufsize = sbufsize;  /* Make sure we have a local copy */

	D(printk("syncser_write %d bytes, %d in buf\n", count, sbufsize));
	
	/* wait until we can put stuff into the buffer */
	if (!(file->f_flags & O_NONBLOCK)) {
		while((volatile)sbufsize > HIGHWATERMARK)  /* && !(current->signal & ~current->blocked) */
			interruptible_sleep_on(&ss_wq);
	}
#if SYNCSER_STATISTICS
	if (stat_count == MAX_STAT){
		int i;
		for (i = 0; i < MAX_STAT; i++){
			printk("%4.4d ", stat_write[i]);
			if (i % 10 == 0){
				printk("\n");
			}
		}
		stat_count = 0;
	}	
#endif

	/* dont write more than will fit */
	local_sbufsize = sbufsize;
	count = count > BUFSIZE - local_sbufsize ? 
		BUFSIZE - local_sbufsize : count;
	
	/* split in two writes if the buffer wraps */
	
	wrlen = wrpos + count > BUFSIZE ? BUFSIZE - wrpos : count;
	
	memcpy(ss_buf + wrpos, buf, wrlen);
	wrpos = (wrpos + wrlen) % BUFSIZE;
	
	if(wrlen < count) {
		/* write the rest */
		memcpy(ss_buf, buf + wrlen, count - wrlen);
		wrpos = count - wrlen;
	}
//	D(printk("written %d bytes. Total: %d\n", count, syncser_tot));

	atomic_add(count, (atomic_t*)&sbufsize); /* used by irq handler */
	if (count == 0 && file->f_flags & O_NONBLOCK) {
		return -EAGAIN;
	}
#if 0
	/* Reschedule if we have more to send */
	{
		unsigned long flags;	
		save_flags(flags);
		cli();
		if (sbufsize > 0){
			restore_flags(flags);
			queue_task(&syncser_task, &tq_scheduler);
			mark_bh(IMMEDIATE_BH);
		}else{
			restore_flags(flags);
		}
	}
#endif
	return count;
}

static int 
syncser_select(struct inode *inode, struct file *file,
		 int sel_type, select_table * wait)
{
	switch (sel_type) {
	case SEL_IN:
		/* Only out supported! */
/*
		if (random_state.entropy_count >= 8)
			return 1;
		select_wait(&ss_wq, wait);
*/
		return 0;
		break;
	case SEL_OUT:
		if (sbufsize < LOWWATERMARK)
			return 1;
		select_wait(&ss_wq, wait);
		break;
	}
	return 0;
}

static int
syncser_open(struct inode *inode, struct file *file)
{
	/* only allow one open simulatenously */

	if(ocount)
		return -EBUSY;
	
	ocount++;

	D(printk("Opened syncser.\n"));

	return 0;
}

static void
syncser_release(struct inode *inode, struct file *file)
{
	ocount = 0;
}

static int
syncser_ioctl(struct inode *inode, struct file *file,
	      unsigned int cmd, unsigned long arg)
{
	struct etrax_syncser sync_ser;
	unsigned int minor = MINOR(inode->i_rdev);
	int retval = 0;

	D(printk("syncser%d ioctl, cmd: 0x%x, arg: 0x%x\n", minor, cmd, arg));
	
/*	if (minor >= max_syncser)
	return -ENODEV;
 */
	if(_IOC_TYPE(cmd) == ETRAXSYNCSER_IOCTYPE) {
		return -EINVAL;
	}
	
	switch (cmd) {
	case (SYNCSER_SET_CMD):
		retval = verify_area(VERIFY_READ,
				     (struct etrax_syncser*)arg,
				     sizeof(struct etrax_syncser));
		if (retval != 0)
			return retval;
		memcpy_fromfs(&sync_ser, (struct etrax_syncser*)arg, 
			      sizeof(struct etrax_syncser));
		D(printk("SYNCSER_SET_CM\n"));
		switch (sync_ser.command)
		{
		case SYNCSER_FLUSH_BUF:
			D(printk("SYNCSER FLUSH\n"));
			sbufsize = 0;
			rdpos = wrpos;
			break;
		default:
		}
		return 0;
		break;
	default:
		retval = -EINVAL;
	}
	return retval;
}

static struct file_operations sync_fops = {
	NULL,
	NULL,		/* read */
	syncser_write,
	NULL,		/* readdir */
	syncser_select, /* select */
	syncser_ioctl,  /* ioctl */
	NULL,		/* mmap */
	syncser_open,
	syncser_release
};

int
syncser_init(void)
{
	printk("Synchronous serial port driver v1.1\n");

	if (register_chrdev(SYNCSER_MAJOR,"syncser", &sync_fops)) {
		printk("unable to get major %d for syncser port\n", SYNCSER_MAJOR);
		return -EBUSY;
	}

	/* hook onto timer irq */
#if SYNCSER_USES_INTERRUPT
	if(request_irq(2, syncser_interrupt, SA_SHIRQ, "syncser", NULL))
		panic("irq2");
	init_waitqueue(&ss_wq);
#else
	if(request_irq(2, syncser_interrupt, SA_SHIRQ, "syncser", NULL))
		panic("irq2");
	init_waitqueue(&ss_wq);
	syncser_task.routine = syncser_task_routine;
	syncser_task.data = NULL;
	/* Task is scheduled in write() and from timer_int */
	/* queue_task(&syncser_task, &tq_scheduler); */
#endif

	/* setup the correct port PA directions */
	/* PA1 and PA2 are outputs (CLOCK,DATA) */

	//*R_PORT_PA_DIR = port_pa_dir_shadow = 0x06;
}

