/*
 *	Real Time Clock interface for Linux on StrongARM SA1100
 *
 *	Copyright (c) 2000 Nils Faerber
 *
 *	Based on rtc.c by Paul Gortmaker
 *	Date/time conversion routines taken from arch/arm/kernel/time.c
 *			by Linus Torvalds and Russel King
 *		and the GNU C Library
 *	( ... I love the GPL ... just take what you need! ;)
 *
 *	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.
 *
 *  0.03    CIH <cih@coventive.com>
 *      03-07-2001  Modify the bug setups RTC clock.
 *	0.02	Nils Faerber <nils@@kernelconcepts.de>
 *		02272001 removed mktime(), added alarm irq clear
 *	0.01	Nils Faerber <nils@@kernelconcepts.de>
 *		10012000 initial release
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/delay.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <asm/hardware.h>
#include <asm/arch/irqs.h>
#include <linux/rtc.h>

#define	DRIVER_VERSION	"0.02"
#define	DEFAULT_DIVIDER	0x8000
#define DEFAULT_TRIM	0x0000

static int rtc_usage = 0;
static int rtc_irq_data = 0;

static struct fasync_struct *rtc_async_queue;

static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);

extern spinlock_t rtc_lock;

/* static struct timer_list rtc_irq_timer; */

static unsigned long epoch = 1900;      /* year corresponding to 0x00   */

static const unsigned char days_in_mo[] = 
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/*
 * Convert seconds to time, from the GNU C Library
 * (this code is ugly ;)
 */
void timemk(unsigned long t, struct rtc_time *tval)
{
long int days, rem, y;
const unsigned short int *ip;
unsigned int wday;
unsigned int yday;
const unsigned short int __mon_yday[2][13] =
	{ /* Normal years.  */
	  { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
	  /* Leap years.  */
	  { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
	};

#define __isleap(year) \
 	((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))

	days = t / 86400;
	rem = t % 86400;
	while (rem < 0)
	{
		rem += 86400;
		--days;
	}
	while (rem >= 86400)
	{
		rem -= 86400;
		++days;
	}
	tval->tm_hour = rem / 3600;
	rem %= 3600;
	tval->tm_min = rem / 60;
	tval->tm_sec = rem % 60;
	wday = (4 + days) % 7;
	if (wday < 0)   
		wday += 7;
	y = 1970;
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
	while (days < 0 || days >= (__isleap (y) ? 366 : 365))
	{
		long int yg = y + days / 365 - (days % 365 < 0);
		days -= ((yg - y) * 365
			+ LEAPS_THRU_END_OF (yg - 1)
			- LEAPS_THRU_END_OF (y - 1));
		y = yg;
	}
	tval->tm_year = y - 1900;
	yday = days;
	ip = __mon_yday[__isleap(y)];
	for (y = 11; days < (long int) ip[y]; --y)
		continue;
	days -= ip[y];   
	tval->tm_mon = y;
	tval->tm_mday = days + 1;
}

static void rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned int rtsr_shadow;
unsigned int rcnr_shadow;

	spin_lock (&rtc_lock);
	/* Read the clock */
	rtsr_shadow = RTSR;
	/* If Alarm detected, disable the Alarm IRQ */
	if (RTSR & 0x0001)
		RTSR=(RTSR & 0x0008);
	RTSR |= 0x0003; /* clear the interrupt status bits */
	rcnr_shadow = RCNR;
	rtc_irq_data = rtsr_shadow & 0x0003;
	spin_unlock (&rtc_lock);
	wake_up_interruptible(&rtc_wait);

	kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
}

int rtc_open(struct inode *minode, struct file *mfile)
{
	if (rtc_usage != 0)
		return -EBUSY;

	MOD_INC_USE_COUNT;
	rtc_usage = 1;

return 0;
}

int rtc_release(struct inode *minode, struct file *mfile)
{
	MOD_DEC_USE_COUNT;
	rtc_usage = 0;

return 0;
}

static int rtc_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &rtc_async_queue);
}

static loff_t rtc_llseek(struct file *file, loff_t offset, int origin)
{
return -ESPIPE;
}

ssize_t rtc_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
DECLARE_WAITQUEUE(wait, current);
unsigned long data;
ssize_t retval;

	if (count < sizeof(unsigned long))
		return -EINVAL;

	add_wait_queue(&rtc_wait, &wait);

	current->state = TASK_INTERRUPTIBLE;

	do {
		spin_lock_irq (&rtc_lock);
		data = rtc_irq_data;
		spin_unlock_irq (&rtc_lock);

		if (data != 0) {
			rtc_irq_data=0;
			break;
		}

		if (file->f_flags & O_NONBLOCK) {
			retval = -EAGAIN;
			goto out;
		}
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			goto out;
		}
		schedule();
	} while (1);

	retval = put_user(data, (unsigned long *)buf);
	if (!retval)
		retval = sizeof(unsigned long);
  out:
	current->state = TASK_RUNNING;
	remove_wait_queue(&rtc_wait, &wait);

return retval;
}

static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
struct rtc_time wtime;

	switch (cmd) {
	case RTC_AIE_OFF:
	{
		spin_lock_irq(&rtc_lock);
		RTSR &= ~0x0004;
		rtc_irq_data = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	}
	case RTC_AIE_ON:
	{
		spin_lock_irq(&rtc_lock);
		RTSR |= 0x0004;
		rtc_irq_data = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	}
	case RTC_PIE_OFF:
	{
		spin_lock_irq(&rtc_lock);
		RTSR &= ~0x0008;
		rtc_irq_data = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	}
	case RTC_PIE_ON:
	{
		spin_lock_irq(&rtc_lock);
		RTSR |= ~0x0008;
		rtc_irq_data = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	}
	case RTC_UIE_OFF:
	{
		spin_lock_irq(&rtc_lock);
		RTSR &= ~0x0008;
		rtc_irq_data = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	}
	case RTC_UIE_ON:
	{
		spin_lock_irq(&rtc_lock);
		RTSR |= 0x0008;
		rtc_irq_data = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	}
	case RTC_ALM_READ:
	{
		unsigned long tmpctr;
		tmpctr=RTAR;
		timemk(tmpctr,&wtime);
		break;
	}
	case RTC_ALM_SET:
	{
		struct rtc_time rtc_tm;
		unsigned char mon, day, hrs, min, sec, leap_yr;
		unsigned int yrs;
		unsigned long tmpctr;

		if (copy_from_user(&rtc_tm, (struct rtc_time*)arg,
				sizeof(struct rtc_time)))
			return -EFAULT;
		yrs = rtc_tm.tm_year + 1900;
		mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
		day = rtc_tm.tm_mday;
		hrs = rtc_tm.tm_hour;
		min = rtc_tm.tm_min;
		sec = rtc_tm.tm_sec;

		if (yrs < 1970)
			return -EINVAL;
		leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
		if ((mon > 12) || (day == 0))
			return -EINVAL;
		if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
			return -EINVAL;
		if ((hrs >= 24) || (min >= 60) || (sec >= 60))
			return -EINVAL;
		if ((yrs -= epoch) > 255)    /* They are unsigned */
			return -EINVAL;
		tmpctr=mktime(yrs+1900,mon,day,hrs,min,sec);
		/* update the alarm register */
		spin_lock_irq(&rtc_lock);
		RTAR=tmpctr;
		spin_unlock_irq(&rtc_lock);

		return 0;
	}
	case RTC_RD_TIME:
	{
		unsigned long tmpctr;
		spin_lock_irq(&rtc_lock);
		tmpctr=RCNR;
		spin_unlock_irq(&rtc_lock);
		timemk(tmpctr,&wtime);
		break;
	}
	case RTC_SET_TIME:
	{
		struct rtc_time rtc_tm;
		unsigned char mon, day, hrs, min, sec, leap_yr;
		unsigned int yrs;
		unsigned long tmpctr;

		if (copy_from_user(&rtc_tm, (struct rtc_time*)arg,
				sizeof(struct rtc_time)))
			return -EFAULT;
		yrs = rtc_tm.tm_year + 1900;
		mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
		day = rtc_tm.tm_mday;
		hrs = rtc_tm.tm_hour;
		min = rtc_tm.tm_min;
		sec = rtc_tm.tm_sec;

		if (yrs < 1970)
			return -EINVAL;
		leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
		if ((mon > 12) || (day == 0))
			return -EINVAL;
		if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
			return -EINVAL;
		if ((hrs >= 24) || (min >= 60) || (sec >= 60))
			return -EINVAL;
		if ((yrs -= epoch) > 255)    /* They are unsigned */
			return -EINVAL;
		tmpctr=mktime(yrs+1900,mon,day,hrs,min,sec);
		/* update the clock counter */
		spin_lock_irq(&rtc_lock);
		RCNR=tmpctr;
		spin_unlock_irq(&rtc_lock);

		return 0;
	}
	case RTC_IRQP_READ:
	{
		return put_user(1, (unsigned long *)arg);
	}
	case RTC_IRQP_SET:
	{
		/* Changing the periodic frequency is not supported */
		return -EACCES;
	}
	case RTC_EPOCH_READ:
	{
		return put_user (1970, (unsigned long *)arg);
	}
	case RTC_EPOCH_SET:
	{
		/* Changing the epoch is not supprted */
		return -EACCES;
	}
	default:
		return -EINVAL;
	}
return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
}

static unsigned int rtc_poll(struct file *file, poll_table *wait)
{
unsigned long l;

	poll_wait(file, &rtc_wait, wait);

	spin_lock_irq (&rtc_lock);
	l = rtc_irq_data;
	spin_unlock_irq (&rtc_lock);

	if (l != 0)
		return POLLIN | POLLRDNORM;
return 0;
}

static int rtc_proc_output (char *buf)
{
char *p;
struct rtc_time tm;

	timemk(RCNR,&tm);
	p = buf;
	p += sprintf(p,
		"rtc_time\t: %02d:%02d:%02d\n"
		"rtc_date\t: %04d-%02d-%02d\n"
		"rtc_epoch\t: %04lu\n",
		tm.tm_hour, tm.tm_min, tm.tm_sec,
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, epoch);
	timemk(RTAR,&tm);
	p += sprintf(p,
		"alrm_time\t: %02d:%02d:%02d\n"
		"alrm_date\t: %04d-%02d-%02d\n",
		tm.tm_hour, tm.tm_min, tm.tm_sec,
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
	p += sprintf(p,"trim\t\t: %u\n",RTTR);
/*	p += sprintf(p,"status  = %u\n",RTSR);*/
	p += sprintf(p,"alarm_IRQ\t: %s\n", (RTSR & 0x04) ? "yes":"no" );
	p += sprintf(p,"periodic_IRQ\t: %s\n", (RTSR & 0x08) ? "yes":"no" );
	p += sprintf(p,"periodic_freq\t: 1\n");
	p += sprintf(p,"batt_status\t: okay\n");

return (p - buf);
}

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

	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;

return len;
}


MODULE_AUTHOR("Nils Faerber <nils@@kernelconcepts.de>");
MODULE_DESCRIPTION("SA1100 StrongARM Realtime Clock Driver (RTC)");

static struct file_operations rtc_fops = {
	owner:		THIS_MODULE,
	llseek:		rtc_llseek,
	read:		rtc_read,
	poll:		rtc_poll,
	ioctl:		rtc_ioctl,
	open:		rtc_open,
	release:	rtc_release,
	fasync:		rtc_fasync,
};


static struct miscdevice sa1100rtc_miscdev = {
	RTC_MINOR,
	"rtc",
	&rtc_fops
};

void rtc_exit(void)
{
	spin_lock_irq (&rtc_lock);
	RTSR &= 0xfff0; /* disable interrupts */
	RTSR |= 0x0003; /* clear the iterrupt status */
	spin_unlock_irq (&rtc_lock);
	free_irq(IRQ_RTC1Hz,NULL);
	free_irq(IRQ_RTCAlrm,NULL);
	remove_proc_entry ("driver/rtc", NULL);
	misc_deregister(&sa1100rtc_miscdev);
}

static int __init rtc_init(void)
{
unsigned long tmpctr;

	misc_register(&sa1100rtc_miscdev);
	create_proc_read_entry ("driver/rtc", 0, 0, rtc_read_proc, NULL);
	/* Reset the interrupt flags and disable interrupts */
	tmpctr=RTSR;
	RTSR = (tmpctr | 0x0003) & 0xfff3;
	tmpctr=RTTR;
	/* If the divider is outside a reasonable range to achieve 1Hz */
	/* then reset it to the default value */
	if (tmpctr < 0x7900 || tmpctr > 0x8700) {
		/* Set the divider to 32768 = one Hz */
		RTTR=DEFAULT_DIVIDER -1 + (DEFAULT_TRIM << 15);
	}
        if (request_irq(IRQ_RTC1Hz, rtc_interrupt, SA_INTERRUPT, "rtc1Hz", NULL))
	{
		printk(KERN_ERR "rtc: IRQ %d already in use.\n", IRQ_RTC1Hz);
		return -EIO;
	}
        if (request_irq(IRQ_RTCAlrm, rtc_interrupt, SA_INTERRUPT, "rtcAlrm", NULL))
	{
		printk(KERN_ERR "rtc: IRQ %d already in use.\n", IRQ_RTCAlrm);
		return -EIO;
	}

	printk(KERN_INFO "SA1100 Real Time Clock Driver v" DRIVER_VERSION "\n");

return 0;
}

module_init(rtc_init);
module_exit(rtc_exit);
EXPORT_NO_SYMBOLS;
