/* * linux/arch/arm/mach-integrator/time.c * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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_base + RTC_LR); return 1; } static int integrator_rtc_read_alarm(struct rtc_wkalrm *alrm) { rtc_time_to_tm(readl(rtc_base + RTC_MR), &alrm->time); return 0; } static inline int integrator_rtc_set_alarm(struct rtc_wkalrm *alrm) { unsigned long time; int ret; /* * At the moment, we can only deal with non-wildcarded alarm times. */ ret = rtc_valid_tm(&alrm->time); if (ret == 0) ret = rtc_tm_to_time(&alrm->time, &time); if (ret == 0) writel(time, rtc_base + RTC_MR); return ret; } static int integrator_rtc_read_time(struct rtc_time *tm) { rtc_time_to_tm(readl(rtc_base + RTC_DR), tm); return 0; } /* * 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 inline int integrator_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 = integrator_rtc_read_time, .set_time = integrator_rtc_set_time, .read_alarm = integrator_rtc_read_alarm, .set_alarm = integrator_rtc_set_alarm, }; static irqreturn_t arm_rtc_interrupt(int irq, void *dev_id) { 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], arm_rtc_interrupt, IRQF_DISABLED, "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, pm_message_t state) { struct timespec rtc; rtc.tv_sec = readl(rtc_base + RTC_DR); rtc.tv_nsec = 0; save_time_delta(&rtc_delta, &rtc); return 0; } 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);