/*
 *	Real Time Clock interface for Linux on S3CXXX          
 *	
 *	Copyright (c) 2002 SAMSUGN ELECTRONICS SW.LEE <hitchcar@sec.samsung.com>
 *	
 *	based on Strongarm	Copyright (c) 2000 Nils Faerber
 *
 */


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <asm/bitops.h>
#include <asm/hardware.h>
#include <asm/irq.h>
#include <linux/rtc.h>


#if DEBUG
#define PDEBUG(fmt,arg...) \
	printk(KERN_ERR fmt,##arg)
#else
#define PDEBUG(fmt,arg...) \
	do { } while (0)
#endif


#define	DRIVER_VERSION		"1.00"
#define TIMER_FREQ		3686400 /* need to recalculate */

#define RTC_DEF_DIVIDER		32768 - 1
#define RTC_DEF_TRIM		0

static unsigned long rtc_status;
static unsigned long rtc_irq_data;
static unsigned long rtc_freq = 1024;
static unsigned long rtc_has_irq  =1;

static struct fasync_struct *rtc_async_queue;
static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);

extern spinlock_t rtc_lock;

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

#ifndef BCD_TO_BIN
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
#endif

#ifndef BIN_TO_BCD
#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10)
#endif

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

#define YEAR_OFFSET  2000


typedef enum {
	ENCODE_TIME,
	ENCODE_ALARM,
	DECODE_TIME,
	DECODE_ALARM
} CODE_RTC ;


static void encode_s3c_rtc(struct rtc_time tm,CODE_RTC flag)
{
	uint year,mon,day,hour,min,sec;

	if ( flag == ENCODE_ALARM )
		year = tm.tm_year - (YEAR_OFFSET - 1900);
	else 
		year = tm.tm_year;

	mon  = tm.tm_mon + 1;
	day  = tm.tm_mday;
	hour = tm.tm_hour;
	min =  tm.tm_min;
	sec  = tm.tm_sec;

	year = BIN_TO_BCD(year);
	mon =  BIN_TO_BCD(mon);
	day =  BIN_TO_BCD(day);
	hour = BIN_TO_BCD(hour);
	min  = BIN_TO_BCD(min);
	sec  = BIN_TO_BCD(sec);

	if( flag == ENCODE_TIME ) {	
		rBCDYEAR = year;
		rBCDMON =  mon ;
		rBCDDATE =  day;
		rBCDHOUR =  hour;
		rBCDMIN =   min;
		rBCDSEC =  sec; 
	}
	else {
		/* As far as I know, alarm func can be applied to h/m/s */
		/*	
			rALMYEAR = year;
			rALMMON =  mon ;
			rALMDATE =  day;
		 */	
		rALMHOUR =  hour;
		rALMMIN =   min;
		rALMSEC =  sec; 
	}
}


static unsigned long decode_s3c_rtc(CODE_RTC flag)
{
	uint year,mon,day,hour,min,sec;

read_again:

	if ( flag == DECODE_TIME )  {
		year = rBCDYEAR;
		mon  = rBCDMON;
		day  = rBCDDATE;
		hour = rBCDHOUR;
		min  = rBCDMIN;
		sec  = rBCDSEC;

	}
	else  {
		year = rALMYEAR;
		mon  = rALMMON;
		day  = rALMDATE;
		hour = rALMHOUR;
		min  = rALMMIN;
		sec  = rALMSEC;
	}

	if ( sec == 0 ) goto read_again;

	year = BCD_TO_BIN(year);
	mon =  BCD_TO_BIN(mon);
	day =  BCD_TO_BIN(day);
	hour = BCD_TO_BIN(hour);
	min  = BCD_TO_BIN(min);
	sec  = BCD_TO_BIN(sec);

	year += YEAR_OFFSET;

	PDEBUG( "\n year %d mon %d day %d hour %d min %d sec %d \n",
			year,mon,day,hour,min,sec);

	return mktime (year,mon,day,hour,min,sec);
}

	
/*
 * Converts seconds since 1970-01-01 00:00:00 to Gregorian date.
 */

static void decodetime (unsigned long t, struct rtc_time *tval)
{
	long days, month, year, rem;

	days = t / 86400;
	rem = t % 86400;
	tval->tm_hour = rem / 3600;
	rem %= 3600;
	tval->tm_min = rem / 60;
	tval->tm_sec = rem % 60;
	tval->tm_wday = (4 + days) % 7;

#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)

	year = 1970 + days / 365;
	days -= ((year - 1970) * 365
			+ LEAPS_THRU_END_OF (year - 1)
			- LEAPS_THRU_END_OF (1970 - 1));
	if (days < 0) {
		year -= 1;
		days += 365 + is_leap(year);
	}
	tval->tm_year = year - 1900;
	tval->tm_yday = days + 1;

	month = 0;
	if (days >= 31) {
		days -= 31;
		month++;
		if (days >= (28 + is_leap(year))) {
			days -= (28 + is_leap(year));
			month++;
			while (days >= days_in_mo[month]) {
				days -= days_in_mo[month];
				month++;
			}
		}
	}
	tval->tm_mon = month;
	tval->tm_mday = days + 1;
}


typedef enum {
	AIE_ON,
	AIE_OFF,
	ALM_SET,
	ALM_READ
} S3C_ALARM;


static u16 s3c_alarm_conf(S3C_ALARM al,void*  tm)
{
	switch (al ) {
		case AIE_ON:
		/* 0x4f :     1     0   0   0   1   1   1   
		         Global En  YE  ME  DE  HE  ME  SE	
		*/
			rRTCALM = 0x47 ; /* all alarm enable */
			return 0;
			break;
		case AIE_OFF:
			rRTCALM = 0x00;
			return 0;
			break;
		case ALM_SET:

			break;
		case ALM_READ:
			break;
		default:
			break;
	}
	return 0;
}


static u16 get_rtc_status(void)
{
	u16 ret = 0;
/* Those are the bits from a classic RTC we want to mimic */
#define RTC_IRQF		0x80	/* any of the following 3 is active */
#define RTC_PF			0x40
#define RTC_AF			0x20
#define RTC_UF			0x10

	if ( rTICNT&(1<<7)) ret |= RTC_IRQF|RTC_UF;
	
	if ( rRTCALM&(1<<6)){
		
	       	ret |= RTC_IRQF|RTC_AF;
	}
	/* RTC_PF using PWM */
	return ret;
}



static void rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	uint min ;
	min = rBCDMIN;
	PDEBUG( "rtc_interrupt rBCDMIN  %d\n",BCD_TO_BIN(min));
	
	/* update irq data & counter */
	rtc_irq_data += 0x100;
	rtc_irq_data &= ~0xff;
	rtc_irq_data |= get_rtc_status();

	/* wake up waiting process */
	wake_up_interruptible(&rtc_wait);
	kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
}


static void rtc_period_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	PDEBUG("rtc_period_interrupt \n");
	
	if (rtc_irq_data & RTC_PF) {
		/* I am not sure of this computation help me !! */
		rtc_irq_data += (rtc_freq * ((1<<30)/(TIMER_FREQ>>2))) << 8;
	} else {
		rtc_irq_data += (0x100|RTC_PF|RTC_IRQF);
	}

	wake_up_interruptible(&rtc_wait);
	kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
}


static int rtc_open(struct inode *inode, struct file *file)
{
	PDEBUG( "rtc_open \n");
	if (test_and_set_bit (1, &rtc_status))
		return -EBUSY;
	rtc_irq_data = 0;
	return 0;
}

static int rtc_release(struct inode *inode, struct file *file)
{
	spin_lock_irq (&rtc_lock);
	spin_unlock_irq (&rtc_lock);
	rtc_status = 0;
	return 0;
}

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


static unsigned int rtc_poll(struct file *file, poll_table *wait)
{
	poll_wait (file, &rtc_wait, wait);
	if ( rtc_irq_data != 0 ) 
		return POLLIN | POLLRDNORM;
	PDEBUG(" No descitptor are ready \n");
	return 0 ; /* means no descriptors are ready */
}

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


/* if you use rtc_read Without  RTC_IRQ, rtc_read will be blocked.
 * How about using return -EIO if so ?
 *
 */
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 ( rtc_has_irq  == 0 ) return -EIO;
#if 0
	if ( file->f_flags & O_NONBLOCK) 
		PDEBUG( " NONBLOCK reading \n");
	else  
		PDEBUG( " BLOCK read  \n");
#endif
	if (count < sizeof(unsigned long))
		return -EINVAL;

	add_wait_queue(&rtc_wait, &wait);
	set_current_state(TASK_INTERRUPTIBLE);
	
	for (;;) {
		spin_lock_irq (&rtc_lock);
		data = rtc_irq_data;
		if (data != 0) {
			rtc_irq_data = 0;
			break;
		}
		spin_unlock_irq (&rtc_lock);
		if (file->f_flags & O_NONBLOCK) {
			retval = -EAGAIN;
			goto out;
		}

		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			goto out;
		}

		schedule();
	}
	spin_unlock_irq (&rtc_lock);

	retval = put_user(data, (unsigned long *)buf);
	if (!retval)
		retval = sizeof(unsigned long);

out:
	set_current_state(TASK_RUNNING);
	remove_wait_queue(&rtc_wait, &wait);
	return retval;
}

typedef enum {
	UIE_ON ,
	UIE_OFF,
	UIE_COUNT
} TICK_STATUS;


static void tick_time_conf(TICK_STATUS flag,short count)
{
	if ( count >127 ) printk(KERN_ERR "Wrong Parameter \n");

	switch (flag ) {
		case UIE_ON :   /* TICNT[7] : Tick int enable */
			rTICNT =   ( rTICNT & 0x7f ) | 1<<7 ;
			break;
		case UIE_OFF:
			rTICNT =   ( rTICNT & 0x7f );
			break;
		case UIE_COUNT: /* TICNT[6:0] : Tick Time Count */
			rTICNT =   ( rTICNT & 0x80) | count;
			break;
	}    
	return;
}


typedef enum {
        IRQP_SET,
	PIE_OFF,
	PIE_ON 
} IRQP_FLAGS;

	
static int irqp_conf(IRQP_FLAGS flag )
{
	int ret=0;
//	int period  = 0 ;
	uint period ;
//	period = 1/rtc_freq;

	switch(flag) {
		case IRQP_SET:
			PDEBUG(" irqp_conf SET \n");
			rTCFG0  |= SYS_TIMER01_PRESCALER;
			rTCFG1  = (rTCFG1&~(0xf<<0))|(SYS_TIMER0_MUX<<0);

			/* Now period  0xf7cd8 = 1015000 */
			period  =  PCLK / ((SYS_TIMER01_PRESCALER +1)*(SYS_TIMER0_DIVIDER));
			rTCNTB0 =  period / rtc_freq;
//			printk(KERN_ERR " period  0x%08x  rtc_freq 0x%08X TCMTB0 0x%08X \n",
//					period,rtc_freq,period/rtc_freq );
			if ( rTCNTB0 > 65535 ) { printk(KERN_ERR "OVER MAX INTERVAL \n"); return -1 ;}
			PDEBUG(" rTCNTB0 0x%08X  freq 0x%08X \n",rTCNTB0,(uint)rtc_freq);
			rTCMPB0 = 0;
			rTCON   = (rTCON&~(0xf))|(0xA); /* the auto reload ,manual update for Timer 1 */
			rTCON   = (rTCON&~(0xf))|(0); /* Stop and clear manual update  for Timer1  */
			break;
		case PIE_ON:
			PDEBUG(" irqp_conf ON \n");
			rTCON   = (rTCON&~(0xf))|(0x9); /* Auto reload & Start */
			break;
		case PIE_OFF:
			PDEBUG(" irqp_conf OFF \n");
			rTCON   = (rTCON&~(0xf))|(0); /* Stop */
			break;
		default:
			break;
	}
	return ret; /* success */
}


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

	switch (cmd) {
	case RTC_AIE_OFF:
		spin_lock_irq(&rtc_lock);
		rtc_irq_data = 0;
		rtc_has_irq = 0;
		s3c_alarm_conf(AIE_OFF,NULL);		
		spin_unlock_irq(&rtc_lock);
		return 0;
	case RTC_AIE_ON:
		spin_lock_irq(&rtc_lock);
		rtc_irq_data = 0;
		rtc_has_irq = 1;
		s3c_alarm_conf(AIE_ON,NULL);		
		spin_unlock_irq(&rtc_lock);
		return 0;
	case RTC_UIE_OFF:
		spin_lock_irq(&rtc_lock);
		tick_time_conf(UIE_OFF,0);
		rtc_irq_data = 0;
		rtc_has_irq = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	case RTC_UIE_ON:
		PDEBUG( " RTC_UIE_ON \n");
		spin_lock_irq(&rtc_lock);
		tick_time_conf(UIE_COUNT,127); /* 1HZ */
		tick_time_conf(UIE_ON,0); 
		rtc_irq_data = 0;
		rtc_has_irq = 1;
		spin_unlock_irq(&rtc_lock);
		return 0;

	case RTC_PIE_OFF:
		spin_lock_irq(&rtc_lock);
		irqp_conf(PIE_OFF);
		rtc_irq_data = 0;
		rtc_has_irq = 0;
		spin_unlock_irq(&rtc_lock);
		return 0;
	case RTC_PIE_ON:
		if ((rtc_freq > 64) && !capable(CAP_SYS_RESOURCE))
			return -EACCES;
		spin_lock_irq(&rtc_lock);
		irqp_conf(PIE_ON);
		rtc_irq_data = 0;
		rtc_has_irq = 1;
		spin_unlock_irq(&rtc_lock);
		return 0;

	case RTC_ALM_READ:
		decodetime (decode_s3c_rtc(DECODE_ALARM), &tm);
		break;
	case RTC_ALM_SET:
		if (copy_from_user (&tm2, (struct rtc_time*)arg, sizeof (tm2)))
			return -EFAULT;
		decodetime (decode_s3c_rtc(DECODE_TIME), &tm);
		if ((unsigned)tm2.tm_hour < 24)
			tm.tm_hour = tm2.tm_hour;
		if ((unsigned)tm2.tm_min < 60)
			tm.tm_min = tm2.tm_min;
		if ((unsigned)tm2.tm_sec < 60)
			tm.tm_sec = tm2.tm_sec;
		PDEBUG(" RTC_ALM_SET \n");
 		PDEBUG("\ntm.tm_year %d  tm.tm_mon %d tm.tm_mday %d tm.tm_hour %d  tm.tm_min %d  tm.tm_sec %d \n",
		tm.tm_year,tm.tm_mon,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
		encode_s3c_rtc(tm,ENCODE_ALARM);
		return 0;
	
	case RTC_RD_TIME:
		decodetime (decode_s3c_rtc(DECODE_TIME), &tm);
		break;
	case RTC_SET_TIME:
		PDEBUG("RTC_SET_TIME \n");
		if (!capable(CAP_SYS_TIME))
			return -EACCES;
		if (copy_from_user (&tm, (struct rtc_time*)arg, sizeof (tm)))
			return -EFAULT;
		tm.tm_year += 1900;
		if (tm.tm_year < 1970 || (unsigned)tm.tm_mon >= 12 ||
		    tm.tm_mday < 1 || tm.tm_mday > (days_in_mo[tm.tm_mon] +
				(tm.tm_mon == 1 && is_leap(tm.tm_year))) ||
		    (unsigned)tm.tm_hour >= 24 ||
		    (unsigned)tm.tm_min >= 60 ||
		    (unsigned)tm.tm_sec >= 60)
			return -EINVAL;
 		PDEBUG("\ntm.tm_year %d  tm.tm_mon %d tm.tm_mday %d tm.tm_hour %d  tm.tm_min %d  tm.tm_sec %d \n",
		tm.tm_year,tm.tm_mon,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
		encode_s3c_rtc(tm,ENCODE_TIME);	

		return 0;
	case RTC_IRQP_READ:
		return put_user(rtc_freq, (unsigned long *)arg);
	case RTC_IRQP_SET:
		if (arg < 1 || arg > TIMER_FREQ) return -EINVAL;
		if ((arg > 64) && (!capable(CAP_SYS_RESOURCE)))
				return -EACCES;
		rtc_freq = arg;
		PDEBUG("RTC_IRQP_SET \n");
		irqp_conf(IRQP_SET);
		return 0;
	case RTC_EPOCH_READ:
		return put_user (1970, (unsigned long *)arg);
	default:
		return -EINVAL;
	}
	return copy_to_user ((void *)arg, &tm, sizeof (tm)) ? -EFAULT : 0;
}

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 s3c_rtc_miscdev = {
	RTC_MINOR,
	"rtc",
	&rtc_fops
};


#define TESTYEAR  3
#define TESTMONTH 3
#define TESTDATE  13
#define TESTHOUR  9
#define TESTMIN   30
#define TESTSEC   1
 
#define TESTDAY   5


typedef enum {
	RTC_INIT_VALUE,
	RTC_ON
} RTC_CONF;


static void s3c_rtc_conf(RTC_CONF flag)
{
	short year = TESTYEAR,mon = TESTMONTH,date = TESTDATE,day=TESTDAY,
	hour = TESTHOUR,min = TESTMIN, sec = TESTSEC;

	switch (flag ) {
		case RTC_INIT_VALUE :
			rRTCCON	 = 1 << 3; 
			rRTCCON	 = (rRTCCON  & ~(0xf))  | 0x1;	
			//No reset, Merge BCD counters, 1/32768, RTC Control enable

			rBCDYEAR = (rBCDYEAR & ~(0xff)) | BIN_TO_BCD(year);
			rBCDMON	 = (rBCDMON  & ~(0x1f)) | BIN_TO_BCD(mon);
		PDEBUG( " BIN_TO_BCD(data) %d \n",BIN_TO_BCD(date));
			rBCDDATE = (rBCDDATE & ~(0x3f)) | BIN_TO_BCD(date);	      
			rBCDDAY	 = (rBCDDAY  & ~(0x7))  | BIN_TO_BCD(day);	   //SUN:1 MON:2 TUE:3 WED:4 THU:5 FRI:6 SAT:7
			rBCDHOUR = (rBCDHOUR & ~(0x3f)) | BIN_TO_BCD(hour);
			rBCDMIN	 = (rBCDMIN  & ~(0x7f)) | BIN_TO_BCD(min);
			rBCDSEC	 = (rBCDSEC  & ~(0x7f)) | BIN_TO_BCD(sec);
		PDEBUG( "   s3c_rtc_conf rRTCCON 0x%08X \n",rRTCCON);
			
			rRTCCON	 = 0x0;		  
 	 //No reset, Merge BCD counters, 1/32768, RTC Control disable    
			break;
		case RTC_ON:
			break;
	}
}


static int __init rtc_init(void)
{
	int ret;

	printk("Installing S3C2410 RTC \n"); 
	misc_register (&s3c_rtc_miscdev);
	//create_proc_read_entry ("driver/rtc", 0, 0, rtc_read_proc, NULL);
	ret = request_irq (IRQ_TICK, rtc_interrupt, SA_INTERRUPT, "RTC Tick 1Hz", NULL);
	if (ret) {
		printk (KERN_ERR "rtc: IRQ %d already in use.\n", IRQ_TICK);
		goto IRQ_TICK_failed;
	}

	ret = request_irq (IRQ_RTC, rtc_interrupt, SA_INTERRUPT, "RTC ALARM", NULL);
	if (ret) {
		printk (KERN_ERR "rtc: IRQ %d already in use.\n", IRQ_RTC);
		goto IRQ_TICK_failed;
	}

	ret = request_irq(IRQ_TIMER0,rtc_period_interrupt,SA_INTERRUPT,"RTC PERIOD",NULL);
	if (ret) {
		printk (KERN_ERR "rtc: IRQ %d already in use.\n", IRQ_TIMER0);
		goto IRQ_TICK_failed;
	}

	printk (KERN_INFO "S3C Real Time Clock driver v" DRIVER_VERSION "\n");

	/*
	 * According to the manual we should be able to let RTTR be zero
	 * and then a default diviser for a 32.768KHz clock is used.
	 * Apparently this doesn't work, at least for my SA1110 rev 5.
	 * If the clock divider is uninitialized then reset it to the
	 * default value to get the 1Hz clock.
	 */

	s3c_rtc_conf(RTC_INIT_VALUE);
	s3c_rtc_conf(RTC_ON);	/* RTC enable  */
	return 0;

IRQ_TICK_failed:
	free_irq (IRQ_TICK, NULL);
	free_irq (IRQ_RTC, NULL);
	free_irq (IRQ_TIMER0, NULL);
//	remove_proc_entry ("driver/rtc", NULL);
	misc_deregister (&s3c_rtc_miscdev);
	return ret;
}

static void __exit rtc_exit(void)
{
	free_irq (IRQ_TICK, NULL);
	free_irq (IRQ_RTC, NULL);
	free_irq (IRQ_TIMER0, NULL);
	misc_deregister (&s3c_rtc_miscdev);
}


module_init(rtc_init);
module_exit(rtc_exit);

MODULE_AUTHOR("Sangwook Lee <hitchcar@sec.samsung.com>");
MODULE_DESCRIPTION("S3C Realtime Clock Driver (RTC)");
EXPORT_NO_SYMBOLS;



/** 
 
1. Reading from S3C RTC
 	S3C Timer -> seconds time ->  Gregorian date -> struct rtc_time 
   	        decode_s3c_rtc                  decodetime() 
	
2. Writing into S3C RTC.
	User App -> struct rtc_time   -> encode_s3c_rtc 	
   	
*/
