vserver 1.9.5.x5
[linux-2.6.git] / arch / arm / mach-integrator / time.c
index 3960609..249581f 100644 (file)
 /*
  *  linux/arch/arm/mach-integrator/time.c
  *
- *  Copyright (C) 2000-2001 Deep Blue Solutions
+ *  Copyright (C) 2000-2001 Deep Blue Solutions Ltd.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
+#include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/time.h>
+#include <linux/mc146818rtc.h>
+#include <linux/interrupt.h>
 #include <linux/init.h>
+#include <linux/device.h>
 
+#include <asm/hardware/amba.h>
 #include <asm/hardware.h>
 #include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/rtc.h>
 
-#define RTC_DR         (IO_ADDRESS(INTEGRATOR_RTC_BASE) + 0)
-#define RTC_MR         (IO_ADDRESS(INTEGRATOR_RTC_BASE) + 4)
-#define RTC_STAT       (IO_ADDRESS(INTEGRATOR_RTC_BASE) + 8)
-#define RTC_EOI                (IO_ADDRESS(INTEGRATOR_RTC_BASE) + 8)
-#define RTC_LR         (IO_ADDRESS(INTEGRATOR_RTC_BASE) + 12)
-#define RTC_CR         (IO_ADDRESS(INTEGRATOR_RTC_BASE) + 16)
+#include <asm/mach/time.h>
 
-#define RTC_CR_MIE     0x00000001
+#define RTC_DR         (0)
+#define RTC_MR         (4)
+#define RTC_STAT       (8)
+#define RTC_EOI                (8)
+#define RTC_LR         (12)
+#define RTC_CR         (16)
+#define RTC_CR_MIE     (1 << 0)
 
 extern int (*set_rtc)(void);
+static void __iomem *rtc_base;
 
 static int integrator_set_rtc(void)
 {
-       __raw_writel(xtime.tv_sec, RTC_LR);
+       __raw_writel(xtime.tv_sec, rtc_base + RTC_LR);
        return 1;
 }
 
-static int integrator_rtc_init(void)
+static void rtc_read_alarm(struct rtc_wkalrm *alrm)
 {
-       __raw_writel(0, RTC_CR);
-       __raw_writel(0, RTC_EOI);
+       rtc_time_to_tm(readl(rtc_base + RTC_MR), &alrm->time);
+}
+
+static int rtc_set_alarm(struct rtc_wkalrm *alrm)
+{
+       unsigned long time;
+       int ret;
+
+       ret = rtc_tm_to_time(&alrm->time, &time);
+       if (ret == 0)
+               writel(time, rtc_base + RTC_MR);
+       return ret;
+}
+
+static void rtc_read_time(struct rtc_time *tm)
+{
+       rtc_time_to_tm(readl(rtc_base + RTC_DR), tm);
+}
 
-       xtime.tv_sec = __raw_readl(RTC_DR);
+/*
+ * Set the RTC time.  Unfortunately, we can't accurately set
+ * the point at which the counter updates.
+ *
+ * Also, since RTC_LR is transferred to RTC_CR on next rising
+ * edge of the 1Hz clock, we must write the time one second
+ * in advance.
+ */
+static int rtc_set_time(struct rtc_time *tm)
+{
+       unsigned long time;
+       int ret;
+
+       ret = rtc_tm_to_time(tm, &time);
+       if (ret == 0)
+               writel(time + 1, rtc_base + RTC_LR);
+
+       return ret;
+}
+
+static struct rtc_ops rtc_ops = {
+       .owner          = THIS_MODULE,
+       .read_time      = rtc_read_time,
+       .set_time       = rtc_set_time,
+       .read_alarm     = rtc_read_alarm,
+       .set_alarm      = rtc_set_alarm,
+};
+
+static irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+       writel(0, rtc_base + RTC_EOI);
+       return IRQ_HANDLED;
+}
+
+static int rtc_probe(struct amba_device *dev, void *id)
+{
+       int ret;
+
+       if (rtc_base)
+               return -EBUSY;
+
+       ret = amba_request_regions(dev, NULL);
+       if (ret)
+               goto out;
+
+       rtc_base = ioremap(dev->res.start, SZ_4K);
+       if (!rtc_base) {
+               ret = -ENOMEM;
+               goto res_out;
+       }
+
+       __raw_writel(0, rtc_base + RTC_CR);
+       __raw_writel(0, rtc_base + RTC_EOI);
+
+       xtime.tv_sec = __raw_readl(rtc_base + RTC_DR);
+
+       ret = request_irq(dev->irq[0], rtc_interrupt, SA_INTERRUPT,
+                         "rtc-pl030", dev);
+       if (ret)
+               goto map_out;
+
+       ret = register_rtc(&rtc_ops);
+       if (ret)
+               goto irq_out;
 
        set_rtc = integrator_set_rtc;
+       return 0;
+
+ irq_out:
+       free_irq(dev->irq[0], dev);
+ map_out:
+       iounmap(rtc_base);
+       rtc_base = NULL;
+ res_out:
+       amba_release_regions(dev);
+ out:
+       return ret;
+}
+
+static int rtc_remove(struct amba_device *dev)
+{
+       set_rtc = NULL;
+
+       writel(0, rtc_base + RTC_CR);
+
+       free_irq(dev->irq[0], dev);
+       unregister_rtc(&rtc_ops);
+
+       iounmap(rtc_base);
+       rtc_base = NULL;
+       amba_release_regions(dev);
+
+       return 0;
+}
+
+static struct timespec rtc_delta;
+
+static int rtc_suspend(struct amba_device *dev, u32 state)
+{
+       struct timespec rtc;
+
+       rtc.tv_sec = readl(rtc_base + RTC_DR);
+       rtc.tv_nsec = 0;
+       save_time_delta(&rtc_delta, &rtc);
 
        return 0;
 }
 
-__initcall(integrator_rtc_init);
+static int rtc_resume(struct amba_device *dev)
+{
+       struct timespec rtc;
+
+       rtc.tv_sec = readl(rtc_base + RTC_DR);
+       rtc.tv_nsec = 0;
+       restore_time_delta(&rtc_delta, &rtc);
+
+       return 0;
+}
+
+static struct amba_id rtc_ids[] = {
+       {
+               .id     = 0x00041030,
+               .mask   = 0x000fffff,
+       },
+       { 0, 0 },
+};
+
+static struct amba_driver rtc_driver = {
+       .drv            = {
+               .name   = "rtc-pl030",
+       },
+       .probe          = rtc_probe,
+       .remove         = rtc_remove,
+       .suspend        = rtc_suspend,
+       .resume         = rtc_resume,
+       .id_table       = rtc_ids,
+};
+
+static int __init integrator_rtc_init(void)
+{
+       return amba_driver_register(&rtc_driver);
+}
+
+static void __exit integrator_rtc_exit(void)
+{
+       amba_driver_unregister(&rtc_driver);
+}
+
+module_init(integrator_rtc_init);
+module_exit(integrator_rtc_exit);