**************************************************************
*RTC driver for the at9260
*Michel Benoit
*separated from EMAC SVN by NZG - 09/04/2007
*EMAC.Inc
**************************************************************
diff -uprN linux-2.6.20-at9260-e1.0-x/arch/arm/mach-at91rm9200/at91sam9260_devices.c ../linux-2.6.20.AT91_e1.0-nsv/arch/arm/mach-at91rm9200/at91sam9260_devices.c
--- linux-2.6.20-at9260-e1.0-x/arch/arm/mach-at91rm9200/at91sam9260_devices.c	2007-09-04 12:37:19.000000000 -0400
+++ ../linux-2.6.20.AT91_e1.0-nsv/arch/arm/mach-at91rm9200/at91sam9260_devices.c	2007-08-24 11:03:00.000000000 -0400
@@ -552,6 +552,25 @@ void __init at91_gpio_leds(struct at91_g
 void __init at91_gpio_leds(struct at91_gpio_led *leds, int nr) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  RTC
+ * -------------------------------------------------------------------- */
+
+#if defined(CONFIG_RTC_DRV_AT91SAM926X) 
+static struct platform_device at91rm9200_rtc_device = {
+	.name		= "at91_rtt",
+	.id		= -1,
+	.num_resources	= 0,
+};
+
+static void __init at91_add_device_rtc(void)
+{
+	platform_device_register(&at91rm9200_rtc_device);
+}
+#else
+static void __init at91_add_device_rtc(void) {}
+#endif
+
 
 /* --------------------------------------------------------------------
  *  UART
@@ -891,7 +910,8 @@ void __init at91_add_device_serial(void)
  */
 static int __init at91_add_standard_devices(void)
 {
-	return 0;
+  at91_add_device_rtc();	
+  return 0;
 }
 
 arch_initcall(at91_add_standard_devices);
diff -uprN linux-2.6.20-at9260-e1.0-x/drivers/rtc/Kconfig ../linux-2.6.20.AT91_e1.0-nsv/drivers/rtc/Kconfig
--- linux-2.6.20-at9260-e1.0-x/drivers/rtc/Kconfig	2007-02-04 13:44:54.000000000 -0500
+++ ../linux-2.6.20.AT91_e1.0-nsv/drivers/rtc/Kconfig	2007-08-24 11:03:00.000000000 -0400
@@ -294,6 +294,12 @@ config RTC_DRV_AT91RM9200
 	help
 	  Driver for the Atmel AT91RM9200's internal RTC (Realtime Clock).
 
+config RTC_DRV_AT91SAM926X
+	tristate "AT91SAM926X"
+	depends on RTC_CLASS
+	help
+	  Driver for the Atmel AT91RM926x's internal RTC (Realtime Clock).
+
 config RTC_DRV_TEST
 	tristate "Test driver/device"
 	depends on RTC_CLASS
diff -uprN linux-2.6.20-at9260-e1.0-x/drivers/rtc/rtc-at91sam926x.c ../linux-2.6.20.AT91_e1.0-nsv/drivers/rtc/rtc-at91sam926x.c
--- linux-2.6.20-at9260-e1.0-x/drivers/rtc/rtc-at91sam926x.c	1969-12-31 19:00:00.000000000 -0500
+++ ../linux-2.6.20.AT91_e1.0-nsv/drivers/rtc/rtc-at91sam926x.c	2007-08-24 11:03:00.000000000 -0400
@@ -0,0 +1,512 @@
+/*
+ *	Real Time Clock interface for Linux on Atmel AT91SAM926x family of SoC
+ *
+ *      Uses the RTT peripheral of the AT91SAM926x and a 32 bit word
+ *      of the General Purpose Backup Registers (GPBR). 
+ *
+ *	2007 Michel Benoit
+ *
+ *      Based on rtc-at91rm9200.c by Rick Bronson
+ *
+ *	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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/time.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/interrupt.h>
+#include <linux/ioctl.h>
+#include <linux/completion.h>
+
+#include <asm/uaccess.h>
+#include <asm/rtc.h>
+
+#include <asm/mach/time.h>
+#include <asm/arch/board.h>
+#include <asm/arch/at91_rtt.h>
+#include <asm/arch/at91sam9260.h>
+
+#define AT91_RTC_FREQ		1
+
+static unsigned long gpbr_offset = 0;
+
+/*
+ * Read current time and date in RTC
+ */
+static int at91_rtc_readtime(struct device *dev, struct rtc_time *tm)
+{
+    unsigned long secs1;
+    unsigned long secs2;
+    unsigned long base;
+    
+
+    printk(KERN_INFO "at91_rtc_readtime called\n");
+    
+    /* retrieve time base from battery backup register */ 
+    base = at91_sys_read( AT91_GPBR + gpbr_offset );
+    if( base == 0 ){
+        /* time has not been set */
+        return -1;
+    }
+
+    /* read the second counter twice as it can be about to change */
+    secs1 = at91_sys_read( AT91_RTT_VR ); 
+    secs2 = at91_sys_read( AT91_RTT_VR );
+    if( secs2!=secs1 ){
+        secs2 = at91_sys_read( AT91_RTT_VR );
+    } 
+    
+    rtc_time_to_tm( secs2+base, tm );
+
+    pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+             1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
+             tm->tm_hour, tm->tm_min, tm->tm_sec);
+    
+    return 0;
+}
+
+/*
+ * Set current time and date in RTC
+ */
+static int at91_rtc_set_mmss(struct device *dev, unsigned long secs)
+{
+
+    unsigned long base;
+    unsigned long alarm;
+    unsigned long mr;
+    
+    printk(KERN_INFO "at91_rtc_set_mmss called\n");
+    
+    mr = at91_sys_read( AT91_RTT_MR );
+
+    /* disable interrupts */
+    at91_sys_write( AT91_RTT_MR, 
+                    mr & ~(AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN) );
+
+    /* read old base time */
+    base = at91_sys_read( AT91_GPBR + gpbr_offset);
+
+    /* store the new base time in a battery backup register */
+    secs+=1;
+    at91_sys_write( AT91_GPBR + gpbr_offset, secs );
+
+    /* adjust the alarm time for the new base */
+    alarm = at91_sys_read( AT91_RTT_AR );
+    if( alarm != 0xffffffff ){
+        if( base > secs ){
+            /* time jumped backwards, increase time until alarm */
+            alarm += (base-secs);
+        } else if ((alarm+base) > secs){
+            /* time jumped forwards, decrease time until alarm */
+            alarm -= (secs-base);
+        } else {
+            /* time jumped past the alarm, disable alarm */
+            alarm = 0xffffffff;   
+        }
+        at91_sys_write( AT91_RTT_AR, alarm );
+    }
+
+    /* restart the timer, set the prescaler fo 1Hz and re-enable interrupts */
+    mr |= (0x8000 & AT91_RTT_RTPRES);
+    at91_sys_write( AT91_RTT_MR, mr | AT91_RTT_RTTRST );
+
+    return 0;
+}
+
+static int at91_rtc_settime(struct device *dev, struct rtc_time *tm)
+{
+	
+    int err;
+    unsigned long secs;
+
+    printk(KERN_INFO "at91_rtc_settime called\n");
+
+    pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+             1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
+             tm->tm_hour, tm->tm_min, tm->tm_sec);
+     	
+	err = rtc_tm_to_time(tm, &secs);
+	if (err != 0)
+		return err;
+
+	return at91_rtc_set_mmss(dev, secs);
+
+}
+
+/*
+ * Read alarm time and date in RTC
+ */
+static int at91_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct rtc_time *tm = &alrm->time;
+        unsigned long base;
+        unsigned long alarm;
+    
+    printk(KERN_INFO "at91_rtc_readalarm called\n"); 
+
+       alarm = at91_sys_read( AT91_RTT_AR );
+        
+        if( alarm == 0xffffffff ){
+         
+            memset( tm, 0, sizeof(tm) );
+            
+        } else {
+
+            base = at91_sys_read( AT91_GPBR + gpbr_offset );
+            
+            rtc_time_to_tm( alarm+base, tm );
+            
+            pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+                     1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
+                     tm->tm_hour, tm->tm_min, tm->tm_sec);
+            
+        }
+
+	return 0;
+}
+
+/*
+ * Set alarm time and date in RTC
+ */
+static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+        struct rtc_time *tm = &alrm->time;
+        unsigned long secs;
+        unsigned long base;
+        int err; 
+    
+  printk(KERN_INFO "at91_rtc_setalarm called\n");
+
+            err = rtc_tm_to_time(tm, &secs);
+            if (err != 0)
+		return err;
+            
+            base = at91_sys_read( AT91_GPBR + gpbr_offset );
+            if( base == 0 ){
+                /* time is not set */
+                return -1;
+            } 
+
+            if( secs > base ){
+                at91_sys_write( AT91_RTT_AR, secs-base );
+  
+                pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+                         tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour,
+                         tm->tm_min, tm->tm_sec);           
+                
+            } else {
+                /* alarm is before base time, disable alarm */
+                at91_sys_write( AT91_RTT_AR, 0xffffffff );
+
+                pr_debug("%s(): INVALID %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+                         tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour,
+                         tm->tm_min, tm->tm_sec);
+                
+                return -1;
+            }
+        
+	return 0;
+}
+
+/*
+ * Handle commands from user-space
+ */
+static int at91_rtc_ioctl(struct device *dev, unsigned int cmd,
+			unsigned long arg)
+{
+	int ret = 0;
+        unsigned long mr = at91_sys_read(AT91_RTT_MR);
+    
+printk(KERN_INFO "at91_rtc_ioctl called\n");
+
+	pr_debug("%s(): cmd=%08x, arg=%08lx.\n", __FUNCTION__, cmd, arg);
+
+	switch (cmd) {
+	case RTC_AIE_OFF:	/* alarm off */
+            at91_sys_write(AT91_RTT_MR, mr & ~AT91_RTT_ALMIEN);
+		break;
+	case RTC_AIE_ON:	/* alarm on */
+            at91_sys_write(AT91_RTT_MR, mr | AT91_RTT_ALMIEN);
+		break;
+	case RTC_UIE_OFF:	/* update off */
+	case RTC_PIE_OFF:	/* periodic off */
+            at91_sys_write(AT91_RTT_MR, mr & ~AT91_RTT_RTTINCIEN);
+		break;
+	case RTC_UIE_ON:	/* update on */
+	case RTC_PIE_ON:	/* periodic on */
+            at91_sys_write(AT91_RTT_MR, mr | AT91_RTT_RTTINCIEN);
+		break;
+	case RTC_IRQP_READ:	/* read periodic alarm frequency */
+		ret = put_user(AT91_RTC_FREQ, (unsigned long *) arg);
+		break;
+	case RTC_IRQP_SET:	/* set periodic alarm frequency */
+		if (arg != AT91_RTC_FREQ)
+			ret = -EINVAL;
+		break;
+	default:
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+
+	return ret;
+}
+
+/*
+ * Provide additional RTC information in /proc/driver/rtc
+ */
+static int at91_rtc_proc(struct device *dev, struct seq_file *seq)
+{
+	unsigned long mr = at91_sys_read(AT91_RTT_MR);
+
+     printk(KERN_INFO "at91_rtc_proc called\n");
+
+	seq_printf(seq, "alarm_IRQ\t: %s\n",
+			(mr & AT91_RTT_ALMIEN) ? "yes" : "no");
+	seq_printf(seq, "periodic_IRQ\t: %s\n",
+			(mr & AT91_RTT_RTTINCIEN) ? "yes" : "no");
+	seq_printf(seq, "periodic_freq\t: %ld\n",
+			(unsigned long) AT91_RTC_FREQ);
+        seq_printf(seq, "count\t\t: 0x%08x\n", 
+                   at91_sys_read( AT91_RTT_VR ) );
+        seq_printf(seq, "base\t\t: 0x%08x\n", 
+                   at91_sys_read( AT91_GPBR + gpbr_offset ));
+	return 0;
+}
+
+/*
+ * IRQ handler for the RTC
+ */
+static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
+{
+	struct platform_device *pdev = dev_id;
+	struct rtc_device *rtc = platform_get_drvdata(pdev);
+	unsigned long sr;
+        unsigned long mr;
+	unsigned long events = 0;
+
+        mr = at91_sys_read(AT91_RTT_MR);
+	sr = at91_sys_read(AT91_RTT_SR);
+ 
+        if( (sr & AT91_RTT_ALMS) && (mr & AT91_RTT_ALMIEN) ){
+            events |= (RTC_AF | RTC_IRQF);
+        }
+ 
+        if( (sr & AT91_RTT_RTTINC) && (mr & AT91_RTT_RTTINCIEN) ){
+            events |= (RTC_UF | RTC_IRQF);
+        }
+
+         /* this interrupt is shared */
+        if( events ){	
+
+            rtc_update_irq(&rtc->class_dev, 1, events);
+
+            pr_debug("%s(): num=%ld, events=0x%02lx\n", __FUNCTION__,
+                     events >> 8, events & 0x000000FF);
+            
+            return IRQ_HANDLED;
+	}
+	/* not handled */
+	return IRQ_NONE;	
+}
+
+static const struct rtc_class_ops at91_rtc_ops = {
+	.ioctl		= at91_rtc_ioctl,
+	.read_time	= at91_rtc_readtime,
+        .set_mmss       = at91_rtc_set_mmss,
+	.set_time	= at91_rtc_settime,
+	.read_alarm	= at91_rtc_readalarm,
+	.set_alarm	= at91_rtc_setalarm,
+	.proc		= at91_rtc_proc,
+};
+
+/*
+ * Initialize and install RTC driver
+ */
+static int __init at91_rtc_probe(struct platform_device *pdev)
+{
+	struct rtc_device *rtc;
+	int ret;
+        unsigned long mr;
+        //struct at91_rtt_data *rtt_data = (struct at91_rtt_data*) pdev->dev.platform_data;
+        
+        printk(KERN_INFO "at91_rtc_probe called\n");
+        
+        /* save offset into GPBR to use for the base time */
+        //gpbr_offset = 1;//(pdev->dev.platform_data)->gpbr_offset;
+        //if( gpbr_offset > 3 ){
+        //    gpbr_offset = 0;
+        //}
+
+        printk(KERN_INFO "rtc: 0x%08x AT91_RTT_VR(%x) 0x%08x AT91_GPBR(%lx) \n",
+               at91_sys_read( AT91_RTT_VR ), AT91_RTT_VR,
+               at91_sys_read( AT91_GPBR + gpbr_offset ), AT91_GPBR + gpbr_offset);
+
+        /* set clock to 1Hz and disable interrupts */ 
+        mr = at91_sys_read(AT91_RTT_MR);
+        mr |= (0x8000 & AT91_RTT_RTPRES);
+        at91_sys_write( AT91_RTT_MR, 
+                        mr & ~(AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN) );
+
+	ret = request_irq(AT91_ID_SYS, at91_rtc_interrupt,
+				IRQF_DISABLED | IRQF_SHARED,
+				"at91_rtt", pdev);
+	if (ret) {
+		printk(KERN_ERR "at91_rtt: IRQ %d already in use.\n",
+				AT91_ID_SYS);
+		return ret;
+	}
+
+	rtc = rtc_device_register(pdev->name, &pdev->dev,
+				&at91_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc)) {
+		free_irq(AT91_ID_SYS, pdev);
+                printk(KERN_INFO "rtc_device_register failed (%ld)\n",PTR_ERR(rtc));
+		return PTR_ERR(rtc);
+	}
+	platform_set_drvdata(pdev, rtc);
+	device_init_wakeup(&pdev->dev, 1);
+
+        /* TODO re-enable interrupts that were set? */
+
+	printk(KERN_INFO "AT91 Real Time Clock driver.\n");
+	return 0;
+}
+
+/*
+ * Disable and remove the RTC driver
+ */
+static int __devexit at91_rtc_remove(struct platform_device *pdev)
+{
+	struct rtc_device *rtc = platform_get_drvdata(pdev);
+        unsigned long mr;
+
+	/* disable all interrupts */
+        mr = at91_sys_read(AT91_RTT_MR);
+        at91_sys_write( AT91_RTT_MR, 
+                        mr & ~(AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN) );
+
+	free_irq(AT91_ID_SYS, pdev);
+
+	rtc_device_unregister(rtc);
+	platform_set_drvdata(pdev, NULL);
+	device_init_wakeup(&pdev->dev, 0);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+/* AT91SAM9260 RTC Power management control */
+
+static struct timespec at91_rtc_delta;
+static u32 at91_rtc_imr;
+
+static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct rtc_time tm;
+	struct timespec time;
+        unsigned long mr;
+ 
+	time.tv_nsec = 0;
+
+	/* calculate time delta for suspend */
+	at91_rtc_readtime(&pdev->dev, &tm);
+	rtc_tm_to_time(&tm, &time.tv_sec);
+	save_time_delta(&at91_rtc_delta, &time);
+
+	/* this IRQ is shared with DBGU and other hardware which isn't
+	 * necessarily doing PM like we are...
+	 */
+        mr = at91_sys_read(AT91_RTT_MR);
+	at91_rtc_imr = mr & (AT91_RTT_ALMIEN|AT91_RTT_RTTINCIEN);
+	if (at91_rtc_imr) {
+		if (device_may_wakeup(&pdev->dev))
+                    enable_irq_wake(AT91_ID_SYS);
+		else
+                    at91_sys_write(AT91_RTT_MR, 
+                                   mr & ~at91_rtc_imr );
+	}
+
+	pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+		1900 + tm.tm_year, tm.tm_mon, tm.tm_mday,
+		tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+	return 0;
+}
+
+static int at91_rtc_resume(struct platform_device *pdev)
+{
+	struct rtc_time tm;
+	struct timespec time;
+
+	time.tv_nsec = 0;
+
+	at91_rtc_readtime(&pdev->dev, &tm);
+	rtc_tm_to_time(&tm, &time.tv_sec);
+	restore_time_delta(&at91_rtc_delta, &time);
+
+	if (at91_rtc_imr) {
+		if (device_may_wakeup(&pdev->dev))
+			disable_irq_wake(AT91_ID_SYS);
+		else {
+                    mr = at91_sys_read(AT91_RTT_MR);
+		    at91_sys_write(AT91_RTT_MR, mr | at91_rtc_imr);
+                }
+	}
+
+	pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
+		1900 + tm.tm_year, tm.tm_mon, tm.tm_mday,
+		tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+	return 0;
+}
+#else
+#define at91_rtc_suspend NULL
+#define at91_rtc_resume  NULL
+#endif
+
+static struct platform_driver at91_rtc_driver = {
+	.probe		= at91_rtc_probe,
+	.remove		= at91_rtc_remove,
+	.suspend	= at91_rtc_suspend,
+	.resume		= at91_rtc_resume,
+	.driver		= {
+		.name	= "at91_rtt",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init at91_rtc_init(void)
+{       
+  int retval;
+  
+  printk(KERN_INFO "at91_rtc_init called\n");
+  
+  retval = platform_driver_probe(&at91_rtc_driver, at91_rtc_probe);
+  
+  printk(KERN_INFO "platform_driver_probe return (%d)\n",retval);       
+  
+  return retval;
+}
+
+static void __exit at91_rtc_exit(void)
+{
+  printk(KERN_INFO "at91_rtc_exit called\n");
+
+  platform_driver_unregister(&at91_rtc_driver);
+}
+
+module_init(at91_rtc_init);
+module_exit(at91_rtc_exit);
+
+MODULE_AUTHOR("Michel Benoit");
+MODULE_DESCRIPTION("RTC driver for Atmel AT91SAM926x");
+MODULE_LICENSE("GPL");
+
diff -uprN linux-2.6.20-at9260-e1.0-x/drivers/rtc/Makefile ../linux-2.6.20.AT91_e1.0-nsv/drivers/rtc/Makefile
--- linux-2.6.20-at9260-e1.0-x/drivers/rtc/Makefile	2007-02-04 13:44:54.000000000 -0500
+++ ../linux-2.6.20.AT91_e1.0-nsv/drivers/rtc/Makefile	2007-08-24 11:03:00.000000000 -0400
@@ -36,4 +36,5 @@ obj-$(CONFIG_RTC_DRV_PL031)	+= rtc-pl031
 obj-$(CONFIG_RTC_DRV_MAX6902)	+= rtc-max6902.o
 obj-$(CONFIG_RTC_DRV_V3020)	+= rtc-v3020.o
 obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
+obj-$(CONFIG_RTC_DRV_AT91SAM926X)+= rtc-at91sam926x.o
 obj-$(CONFIG_RTC_DRV_SH)	+= rtc-sh.o

