/*
 * arch/ppc/5xxx_io/timers.c
 *
 * Driver for the MPC5200 General Purpose Timers
 * 
 * Copyright (C) 2005  Andre Schwarz <andre.schwarz@matrix-vision.de>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */
#include <linux/module.h>
#include <linux/config.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#endif
#include <linux/time.h>

#include <asm/types.h>
#include <asm/delay.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/mpc5xxx.h>
#include <asm/mpc5200_timer.h>


#ifdef CONFIG_PROC_FS
static void __init proc_init(void);
static void __exit proc_exit(void);
#endif

typedef struct _mpc5200_timer {
	int	nr,
		active,
		open_drain_output,
		continuous_mode;

	timer_mode_e 	mode;
	
	__u32	prescale,
		count;

	__u32	width,
		polarity,
		immediate_update;

	__u32	last_jiffy,
		dbg_cnt;

	unsigned oc_pulse_width;
	unsigned oc_type, ic_type;

	wait_queue_head_t   waitq;
	unsigned timeout;
	unsigned capture;

	spinlock_t	slock;		
	unsigned long flags;

	void 	(*callback_fctn)(void *);
	void 	*callback_data;
} mpc5200_timer_t;


#define MPC5200_TIMER_CNT	8

#define MPC5200_CHECK_TIMER(_nr)	do{ if ( !mpc5200_timer_is_active(nr) ) {				\
					        printk( "%s: timer %d not activated !\n", __FUNCTION__, nr );	\
					        return -EINVAL;							\
					    }									\
					} while(0);

/* Enable and Mode Select Register - MBAR + 0x0600 */
#define EMS_MODE_SHIFT		0
#define EMS_GPIO_SHIFT		4
#define EMS_INTEN_SHIFT		8
#define EMS_OPENDRAIN_SHIFT	9
#define EMS_STPCONT_SHIFT	10
#define EMS_CNTEN_SHIFT		12
#define EMS_WDEN_SHIFT		15
#define EMS_ICT_SHIFT		16
#define EMS_OCT_SHIFT		20
#define EMS_OCPW_SHIFT		24

#define	EMS_OCT_FORCE_LOW	0
#define	EMS_OCT_PULSE_HIGH	1
#define	EMS_OCT_PULSE_LOW	2
#define	EMS_OCT_TOGGLE		3

#define EMS_ICT_ANY		0
#define EMS_ICT_RISE		1
#define EMS_ICT_FALL		2
#define EMS_ICT_PULSE		3

#define EMS_GPIO_IN		0
#define EMS_GPIO_OUT_LOW	2
#define EMS_GPIO_OUT_HIGH	3

#define EMS_MODE_OFF		0
#define EMS_MODE_IC		1
#define EMS_MODE_OC		2
#define EMS_MODE_PWM		3
#define EMS_MODE_GPIO		4


static struct mpc5xxx_gpt *pt;
static mpc5200_timer_t t[MPC5200_TIMER_CNT];

static int mpc5200_timer_irqs[MPC5200_TIMER_CNT] = {
	MPC5xxx_GPTIMER0_IRQ, MPC5xxx_GPTIMER1_IRQ, MPC5xxx_GPTIMER2_IRQ, MPC5xxx_GPTIMER3_IRQ,
	MPC5xxx_GPTIMER4_IRQ, MPC5xxx_GPTIMER5_IRQ, MPC5xxx_GPTIMER6_IRQ, MPC5xxx_GPTIMER7_IRQ };

static char *timer_mode_names[MODE_MAX+1] = { "off","IC","OC","PWM","n/a","n/a","n/a","n/a" };

static int initialized = 0;

static unsigned use_timers = 0xff;
static unsigned ipb_clock = 66;
static int isr_debug = 1;

MODULE_PARM(use_timers,"i");
MODULE_PARM_DESC(use_timers, "active timer bitfield : SLT 1..0 + GPT 7..0 (default=0x0ff)");
MODULE_PARM(ipb_clock,"i");
MODULE_PARM_DESC(ipb_clock, "IPB clock [MHz] (default=66)");
MODULE_PARM(isr_debug,"i");
MODULE_PARM_DESC(isr_debug, "enable isr debug messages (default=0)");


static inline int mpc5200_timer_is_active( int nr )
{
	if ( (nr < MPC5200_TIMER_CNT) && (use_timers & (1<<nr)) )
	    return 1;
	return 0;
}

void mpc5200_timer_isr( int irq, void *timer, struct pt_regs *regs )
{
	mpc5200_timer_t *isr_timer = (mpc5200_timer_t*)timer;

	/* get and ... */
	isr_timer->capture = pt[isr_timer->nr].sr;
	__u32 local_jiffies = jiffies;
	
	/* ... clear irq status flags */
	pt[isr_timer->nr].sr = (isr_timer->capture | 0x0000000f);

	wake_up_interruptible( &isr_timer->waitq );

	if ( isr_timer->callback_fctn )
		isr_timer->callback_fctn( isr_timer->callback_data );	

	if ( isr_debug && (t[isr_timer->nr].dbg_cnt++ < 5) ) {
		struct timespec ts;
		jiffies_to_timespec( local_jiffies-t[isr_timer->nr].last_jiffy, &ts );
		printk("timer-%d : interrupt @ mode=%d (time: %ld us) !\n", 
			isr_timer->nr, isr_timer->mode, ts.tv_nsec/1000 );
		/*printk("timer-%d : status 0x%x / pin %d / ovf %d / capture 0x%04x\n", isr_timer->nr,
			status & 0xf, (status>>8)&1, (status>>12)&7, (status>>16)&0xffff );
		*/
	}
	t[isr_timer->nr].last_jiffy = local_jiffies;
}

static int mpc5200_reset_timer( unsigned nr )
{
	int result=-1;	
	if ( mpc5200_timer_is_active(nr) ){
		spin_lock_irqsave( &(t[nr].slock), t[nr].flags );
		pt[nr].emsr = 0;
		/* prescale == 0 results in an effective prescale of 0xffff (64k).
		   Set prescale to 1 for no IP clock prescaling at all. */	
		pt[nr].cir = 0x00010000;
		pt[nr].cr = 0x0;
		/* clear status bits 28:31 by writing '1' to _all_. No single clearing possible ! */
		pt[nr].sr = 0x0000000f;
		spin_unlock_irqrestore( &(t[nr].slock), t[nr].flags );
	}
	return result;
}

static int mpc5200_timers_init( void )
{
	int result = 0;
	unsigned i;

	if ( initialized )
		return 0;

#ifdef CONFIG_PROC_FS
	proc_init();
#endif
	initialized  = 1;

	pt = (struct mpc5xxx_gpt *)MPC5xxx_GPT;
	memset( t, 0, sizeof(mpc5200_timer_t) * MPC5200_TIMER_CNT );

	printk(KERN_INFO "init mpc5200 timer module : GPS Port Config = 0x%08x\n", *((__u32*)(MPC5xxx_GPIO)) );
	printk(KERN_INFO "init mpc5200 timer module : using timers 0x%x @ 0x%p / IPB clock %d MHz\n", 
		use_timers, pt, ipb_clock );

	for ( i=0; i<MPC5200_TIMER_CNT; i++ ) {
		if ( mpc5200_timer_is_active(i) ) {

			t[i].nr = i;
			t[i].slock = SPIN_LOCK_UNLOCKED;

			t[i].timeout = 500;
			t[i].continuous_mode = 1;

			init_waitqueue_head( &(t[i].waitq) );
			mpc5200_reset_timer( i );

			result = request_irq( mpc5200_timer_irqs[i], mpc5200_timer_isr, 
						SA_SHIRQ | SA_INTERRUPT, "timer", (void *)(&t[i]) );

			if ( result < 0 ) {
				printk("timer-%d : request irq failed (=%d) !\n", i, result );
				use_timers &= ~(1<<i);
				printk( "timer-%d disabled\n", i );
				break;
			}
			printk( "timer-%d enabled\n", i );
		}
	}
	return result;
}

/* I/F Functions */

unsigned mpc5200_timer_get_prescale_us()
{
	if ( ipb_clock )	
		return (ipb_clock);
	else
		return 0;
}


int mpc5200_timer_stop( int nr )
{
	MPC5200_CHECK_TIMER(nr);
	pt[nr].emsr = 0;
	t[nr].dbg_cnt = 0;
	t[nr].active = 0;
	return 0;
}

int mpc5200_timer_start( int nr )
{
	unsigned timer_ms = 0;
	MPC5200_CHECK_TIMER(nr);

	pt[nr].cir = (t[nr].prescale << 16) | (t[nr].count);

    	switch ( t[nr].mode ) {
        	case MODE_IC:
			timer_ms = EMS_MODE_IC | (t[nr].ic_type << EMS_ICT_SHIFT);
			break;
        	case MODE_OC:
			timer_ms = EMS_MODE_OC | (t[nr].oc_pulse_width << EMS_OCPW_SHIFT) | \
				(t[nr].oc_type << EMS_OCT_SHIFT);
			break;
        	case MODE_PWM:
			pt[nr].cr = (t[nr].width << 16) | t[nr].polarity | t[nr].immediate_update;
			timer_ms = EMS_MODE_PWM;
			break;
        	default:
			printk("mode %d not (yet) supported !\n", t[nr].mode );
			return -ENODEV;
	}
	t[nr].last_jiffy = jiffies;
	t[nr].active = 1;

	if ( t[nr].continuous_mode )
		timer_ms |= (1<<EMS_STPCONT_SHIFT);
	//timer_ms |= (1<<EMS_CNTEN_SHIFT); /* enable counter */
	timer_ms |= (1<<EMS_INTEN_SHIFT); /* enable interrupt */
	pt[nr].emsr = timer_ms;
	asm("eieio");
	return 0;
}

int mpc5200_timer_prepare( int nr, int mode, void *callback, void *context )
{
	MPC5200_CHECK_TIMER(nr);
	mpc5200_timer_stop( nr );
	switch ( mode ) {
		case MODE_IC:
		case MODE_OC:
		case MODE_PWM:
			t[nr].mode = mode;
			break;
		default:
			printk("mode %d not (yet) supported !\n", mode );
			return -ENODEV;
	}
	if ( callback )
		t[nr].callback_fctn = callback;
	if ( context )
		t[nr].callback_data = context;
	return 0;
}

int mpc5200_timer_setup_ci( int nr, unsigned prescale, unsigned count )
{
	MPC5200_CHECK_TIMER(nr);
	mpc5200_timer_stop( nr );
	t[nr].prescale = prescale & 0xffff;
	t[nr].count = count & 0xffff;
	printk("timer-%d: prescale 0x%x / count 0x%x\n", nr, t[nr].prescale, t[nr].count );
	return 0;
}

int mpc5200_timer_setup_pwm( int nr, unsigned width, int polarity, int immediate_update )
{
	MPC5200_CHECK_TIMER(nr);
	mpc5200_timer_stop( nr );
	if ( t[nr].mode != MODE_PWM ) {
		printk("timer mode invalid (=%d). use \"timer_prepare\" function !\n", t[nr].mode );
		return-EFAULT;
	}
	t[nr].width = width & 0xffff;
	t[nr].polarity = polarity ? 1<<8 : 0;
	t[nr].immediate_update = immediate_update ? 1 : 0;
	return 0;
}


#ifdef CONFIG_PROC_FS

#define MPC5200_TIMER_PROC_ENTRIES	2
#define MPC5200_TIMER_PROC_NAME_LEN	64
#define MPC5200_TIMER_PROC_BASE_NAME	"cputimers"

static struct proc_dir_entry *proc_timer;

static struct procfsentry {
	char name[MPC5200_TIMER_PROC_NAME_LEN];
	int timer_nr;
	mode_t mode;
	read_proc_t	*read_proc;
	write_proc_t *write_proc;
	struct proc_dir_entry *procent;
} procfsentries[MPC5200_TIMER_CNT*MPC5200_TIMER_PROC_ENTRIES];

#define _pp0(fmt)		do { len += snprintf(page+len,count-len,fmt); 	\
				} while(0);
#define _pp(fmt,args...)	do { len += snprintf(page+len,count-len,fmt,args); 	\
				} while(0);

int mpc5200_timer_read_usage(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int len=0;

	*eof = 1;
	if (off != 0)
	       	return 0;
	_pp0( "timer usage :\n");
	_pp0( "  supported modes = 0 (off) / 1 (Input Capture) / 2 (Output Compare) / 3 (PWM)\n" );
	_pp0( "  read \"settings\" returns current timer settings\n");
	_pp0( "  write \"settings\" : first arg=mode / argc depends on mode ...\n");
	_pp0( "    mode 0: no args\n");
	_pp0( "    mode 1: prescale type\n");
	_pp0( "            types = 0 (any) / 1 (rising edge) / 2 (falling edge) / 3 (pulse)\n");
	_pp0( "    mode 2: prescale count type pulsewidth.\n");
	_pp0( "            types = 0 (force low) / 1 (pulse high) / 2 (pulse low) / 3 (toggle)\n");
	_pp0( "            pulsewidth 0-255\n");
	_pp0( "    mode 3: prescale period width polarity\n");
	_pp0( "    all args are 16-bit decimals 0-65535 !\n");
	_pp0( "  read \"wait_event\" : returns on next timer event\n\n");
	_pp0( "  write \"wait_event\": set unsigned timeout in 10ms steps. 0 disables timeout.\n\n");
	_pp( "  1us prescale value for this machine : %u\n", mpc5200_timer_get_prescale_us() );
    	return len;
}

int mpc5200_timer_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int len=0;
	mpc5200_timer_t *tmr = (mpc5200_timer_t *)data;

	*eof = 1;
	if (off != 0)
	       	return 0;

	_pp( "timer %02d (%s): mode = %d (%s) / timeout %dms\n", tmr->nr, tmr->active ? "activated" : "inactive", 
		tmr->mode, timer_mode_names[tmr->mode], tmr->timeout*10 );
	switch ( tmr->mode  ) {
		case MODE_IC:
			_pp("  prescale: 0x%04x\n", tmr->prescale );
			_pp("  counter : 0x%04x\n", tmr->count );
			_pp("  free run: %s\n", tmr->continuous_mode ? "yes" : "no" );
			_pp("  pin type: %s\n", tmr->open_drain_output ? "open drain" : "hi/low driver" );
			_pp("  ic_type : %d\n", tmr->ic_type );
			break;
		case MODE_OC:
			_pp("  prescale: 0x%04x\n", tmr->prescale );
			_pp("  counter : 0x%04x\n", tmr->count );
			_pp("  free run: %s\n", tmr->continuous_mode ? "yes" : "no" );
			_pp("  pin type: %s\n", tmr->open_drain_output ? "open drain" : "hi/low driver" );
			_pp("  oc_type : %d\n", tmr->oc_type );
			_pp("  pulse width  : %d\n", tmr->oc_pulse_width );
			break;
		case MODE_PWM:
			_pp("  prescale: 0x%04x\n", tmr->prescale );
			_pp("  width   : 0x%04x\n", tmr->width );
			_pp("  polarity: %d\n", tmr->polarity ? 1 : 0 );
			_pp("  free run: %s\n", tmr->continuous_mode ? "yes" : "no" );
			_pp("  pin type: %s\n", tmr->open_drain_output ? "open drain" : "hi/low driver" );
		default:
			break;
	};
    	return len;
}

#define TIMER_MAX_PROG_ARGS	5
int mpc5200_timer_write_proc(struct file *file, const char *buffer, unsigned long count, void *data)
{
	int 	mode = 0,
		cnt,
		par[TIMER_MAX_PROG_ARGS],
		i;
	char	buf[64];

	memset ( buf, 0, sizeof(buf) );

	if (copy_from_user(buf, buffer, sizeof(buf))) 
        	return -EFAULT;

	mpc5200_timer_t *tmr = (mpc5200_timer_t *)data;

	memset( par, 0, sizeof(par) );
	cnt = sscanf( buf, "%d %d %d %d %d %d",
			&mode, &par[0], &par[1], &par[2], &par[3], &par[4] );

	printk("%s: read %d args !\n", __FUNCTION__, cnt );

	for ( i=0; i<TIMER_MAX_PROG_ARGS; i++ )
		par[i] &= 0xffff;

	if ( cnt <= 0 ) 
		printk("%s: can't convert given string !\n", __FUNCTION__ );
	else {
		mpc5200_timer_stop( tmr->nr );
		switch ( mode ) {
			case MODE_OFF:
				break;
			case MODE_IC:
				if ( cnt != 3 ) 
					return -EINVAL;
				mpc5200_timer_prepare( tmr->nr, MODE_IC, NULL, NULL );
				/* prescale */
				mpc5200_timer_setup_ci( tmr->nr, par[0], 0 ); 
				tmr->ic_type = par[1] & 3;
				mpc5200_timer_start( tmr->nr );
				break;
			case MODE_OC:
				if ( cnt != 5 ) 
					return -EINVAL;
				mpc5200_timer_prepare( tmr->nr, MODE_OC, NULL, NULL );
				/* prescale, count */
				mpc5200_timer_setup_ci( tmr->nr, par[0], par[1] ); 
				tmr->oc_type = par[2] & 3;
				tmr->oc_pulse_width = par[3] & 0xff;
				mpc5200_timer_start( tmr->nr );
				break;
			case MODE_PWM:
				if ( cnt != 5 ) 
					return -EINVAL;
				mpc5200_timer_prepare( tmr->nr, MODE_PWM, NULL, NULL );
				/* prescale, period */
				mpc5200_timer_setup_ci( tmr->nr, par[0], par[1] ); 
				/* width, polarity */
				mpc5200_timer_setup_pwm( tmr->nr, par[2], par[3], 1 );
				mpc5200_timer_start( tmr->nr );
				break;
			default:
				printk("%s: unknown timer mode %d\n", __FUNCTION__, mode );
				break;
		};	
		printk("++%s-%d: %d args -> mode %d / args = ", __FUNCTION__, tmr->nr, cnt, mode );
		for ( i=0; i<cnt-1; i++ )
			printk("%d ", par[i] );
		printk("\n");
	}
	return count;
}

int mpc5200_timer_wait_event(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int len = 0,
	    timeout = 0;
	mpc5200_timer_t *tmr = (mpc5200_timer_t *)data;

	*eof = 1;
	if (off != 0)
	       	return 0;

	if ( tmr->timeout )
		timeout = interruptible_sleep_on_timeout( &tmr->waitq, tmr->timeout*HZ/100 );
	else
		interruptible_sleep_on( &tmr->waitq );

	if ( timeout == 0 ) {
		_pp0("timeout !\n" );
	} else {
		_pp0("timer event !\n" );
		if ( tmr->mode == MODE_IC ) {
			_pp("capture counter = %d\n", tmr->capture );
		}
	}
	return len;
}

int mpc5200_timer_set_timeout(struct file *file, const char *buffer, unsigned long count, void *data)
{
	unsigned timeout = 0, cnt;
	char buf[8];

	if (copy_from_user( buf, buffer, sizeof(buf)) ) 
        	return -EFAULT;

	mpc5200_timer_t *tmr = (mpc5200_timer_t *)data;

	cnt = sscanf( buf, "%d", &timeout );
	if ( cnt <= 0 ) 
		printk("%s: can't convert given string !\n", __FUNCTION__ );
	else {
		if ( cnt == 1 ) {
			tmr->timeout = timeout;
			printk("%s: timer %d timeout = %dms\n", __FUNCTION__, tmr->nr, tmr->timeout*10 );
		}
	}
	return count;
}

static int initialize_timer_proc( int nr )
{
	int i;
	struct procfsentry *p = procfsentries + (MPC5200_TIMER_PROC_ENTRIES*nr);

	printk("++mpc5200_timers : %s %d\n", __FUNCTION__, nr );
	for( i=0; i<MPC5200_TIMER_PROC_ENTRIES; i++, p++ ) {	
		p->timer_nr = nr;
		switch ( i ) {
			case 0:
				sprintf( p->name, "settings" );	
				p->read_proc  = mpc5200_timer_read_proc;
				p->write_proc = mpc5200_timer_write_proc;
				break;
			case 1:
				sprintf( p->name, "wait_event" );	
				p->read_proc  = mpc5200_timer_wait_event;
				p->write_proc = mpc5200_timer_set_timeout;
				break;
			default:
				return -1;
		}
	}
	printk("--mpc5200_timers : %s\n", __FUNCTION__ );
	return 0;
}

static void __init proc_init(void)
{
	int i,j;
	struct procfsentry *p;
	static struct proc_dir_entry *pdir, *pusage;
	char *proc_timer_nr = "dummy";

	printk("++mpc5200_timers : %s\n", __FUNCTION__ );
	memset( procfsentries, 0, sizeof(struct procfsentry)*MPC5200_TIMER_PROC_ENTRIES*MPC5200_TIMER_CNT ); 		

	proc_timer = proc_mkdir( MPC5200_TIMER_PROC_BASE_NAME, 0 );
	if ( (pusage = create_proc_entry( "usage", S_IFREG|S_IRUGO, proc_timer )) )
		pusage->read_proc = mpc5200_timer_read_usage;
	
	for (i=0; i < MPC5200_TIMER_CNT; i++) {
		if ( mpc5200_timer_is_active(i) ) {
			if ( initialize_timer_proc( i ) ) {
				printk("timer-%d: init_timer_proc failed !\n", i );
				continue;
			} else {
				sprintf( proc_timer_nr, "%d", i );
				pdir = proc_mkdir( proc_timer_nr, proc_timer );
				for (j=0; j<MPC5200_TIMER_PROC_ENTRIES; j++, p++ ) {
						p = procfsentries + (MPC5200_TIMER_PROC_ENTRIES*i) + j;
	    				if ( pdir && (p->procent = create_proc_entry(p->name, S_IFREG|S_IRUGO, pdir)) ) {
						p->procent->data = &t[i];
						if ( p->read_proc )
							p->procent->read_proc = p->read_proc;
						if ( p->write_proc )
							p->procent->write_proc = p->write_proc;
					} else
						printk("timer-%d: create_proc failed !\n", i );
				}
			}
		}
	}
	printk("--mpc5200_timers : %s\n", __FUNCTION__ );
}

static void __exit proc_exit(void)
{
	printk("++mpc5200_timers : %s\n", __FUNCTION__ );
	remove_proc_entry( MPC5200_TIMER_PROC_BASE_NAME, 0);
	printk("--mpc5200_timers : %s\n", __FUNCTION__ );
}

#endif


#ifdef MODULE
static int __init mpc5200_timers_module_init( void )
{
	return mpc5200_timers_init();
}

static void __devexit mpc5200_timers_module_cleanup( void )
{
	int i;
#ifdef CONFIG_PROC_FS
	proc_exit();
#endif
	for ( i=0; i<MPC5200_TIMER_CNT; i++ ) {
		if ( mpc5200_timer_is_active(i) ) {
			mpc5200_timer_stop( i );
			asm("eieio");
			udelay(100);
			free_irq( mpc5200_timer_irqs[i], (void *)(&t[i]) );
		}
	}
	printk(KERN_INFO "mpc5200 timer module removed\n");
}


module_init(mpc5200_timers_module_init)
module_exit(mpc5200_timers_module_cleanup)

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andre Schwarz <andre.schwarz@matrix-vision.de>");
MODULE_DESCRIPTION("mpc5200 general purpose timers");
#endif

EXPORT_SYMBOL(mpc5200_timer_start);
EXPORT_SYMBOL(mpc5200_timer_stop);
EXPORT_SYMBOL(mpc5200_timer_prepare);
EXPORT_SYMBOL(mpc5200_timer_setup_ci);  
EXPORT_SYMBOL(mpc5200_timer_setup_pwm);
EXPORT_SYMBOL(mpc5200_timer_get_prescale_us);

