diff options
| author | Richard Purdie <rpurdie@rpsys.net> | 2006-03-27 01:16:46 -0800 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-03-27 08:44:52 -0800 | 
| commit | e842f1c8ff8a88f290e26d1139e89aad02c4e0c3 (patch) | |
| tree | e5a5498d09db91d9508836c6155ea74972008013 | |
| parent | fd507e2ff3a5adaccbefa05f4bc9f58f44e930db (diff) | |
| download | olio-linux-3.10-e842f1c8ff8a88f290e26d1139e89aad02c4e0c3.tar.xz olio-linux-3.10-e842f1c8ff8a88f290e26d1139e89aad02c4e0c3.zip  | |
[PATCH] RTC subsystem: SA1100/PXA2XX driver
Add an RTC subsystem driver for the ARM SA1100/PXA2XX processor RTC.
Signed-off-by: Richard Purdie <rpurdie@rpsys.net>
Signed-off-by: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
| -rw-r--r-- | arch/arm/mach-pxa/generic.c | 6 | ||||
| -rw-r--r-- | arch/arm/mach-sa1100/generic.c | 6 | ||||
| -rw-r--r-- | drivers/rtc/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/rtc/rtc-sa1100.c | 388 | 
5 files changed, 410 insertions, 0 deletions
diff --git a/arch/arm/mach-pxa/generic.c b/arch/arm/mach-pxa/generic.c index 9b48a90aefc..5efa84749f3 100644 --- a/arch/arm/mach-pxa/generic.c +++ b/arch/arm/mach-pxa/generic.c @@ -319,6 +319,11 @@ void __init pxa_set_ficp_info(struct pxaficp_platform_data *info)  	pxaficp_device.dev.platform_data = info;  } +static struct platform_device pxartc_device = { +	.name		= "sa1100-rtc", +	.id		= -1, +}; +  static struct platform_device *devices[] __initdata = {  	&pxamci_device,  	&udc_device, @@ -329,6 +334,7 @@ static struct platform_device *devices[] __initdata = {  	&pxaficp_device,  	&i2c_device,  	&i2s_device, +	&pxartc_device,  };  static int __init pxa_init(void) diff --git a/arch/arm/mach-sa1100/generic.c b/arch/arm/mach-sa1100/generic.c index 2abdc419e98..9ea71551fc0 100644 --- a/arch/arm/mach-sa1100/generic.c +++ b/arch/arm/mach-sa1100/generic.c @@ -324,6 +324,11 @@ void sa11x0_set_irda_data(struct irda_platform_data *irda)  	sa11x0ir_device.dev.platform_data = irda;  } +static struct platform_device sa11x0rtc_device = { +	.name		= "sa1100-rtc", +	.id		= -1, +}; +  static struct platform_device *sa11x0_devices[] __initdata = {  	&sa11x0udc_device,  	&sa11x0uart1_device, @@ -333,6 +338,7 @@ static struct platform_device *sa11x0_devices[] __initdata = {  	&sa11x0pcmcia_device,  	&sa11x0fb_device,  	&sa11x0mtd_device, +	&sa11x0rtc_device,  };  static int __init sa1100_init(void) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index b399259784e..166232a3f56 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -127,6 +127,15 @@ config RTC_DRV_EP93XX  	  This driver can also be built as a module. If so, the module  	  will be called rtc-ep93xx. +config RTC_DRV_SA1100 +	tristate "SA11x0/PXA2xx" +	depends on RTC_CLASS && (ARCH_SA1100 || ARCH_PXA) +	help +	  If you say Y here you will get access to the real time clock +	  built into your SA11x0 or PXA2xx CPU. + +	  To compile this driver as a module, choose M here: the +	  module will be called rtc-sa1100.  config RTC_DRV_TEST  	tristate "Test driver/device" diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 38b4d87939e..56820d18161 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_RTC_DRV_DS1672)	+= rtc-ds1672.o  obj-$(CONFIG_RTC_DRV_PCF8563)	+= rtc-pcf8563.o  obj-$(CONFIG_RTC_DRV_RS5C372)	+= rtc-rs5c372.o  obj-$(CONFIG_RTC_DRV_EP93XX)	+= rtc-ep93xx.o +obj-$(CONFIG_RTC_DRV_SA1100)	+= rtc-sa1100.o diff --git a/drivers/rtc/rtc-sa1100.c b/drivers/rtc/rtc-sa1100.c new file mode 100644 index 00000000000..83b2bb480a1 --- /dev/null +++ b/drivers/rtc/rtc-sa1100.c @@ -0,0 +1,388 @@ +/* + * Real Time Clock interface for StrongARM SA1x00 and XScale PXA2xx + * + * Copyright (c) 2000 Nils Faerber + * + * Based on rtc.c by Paul Gortmaker + * + * Original Driver by Nils Faerber <nils@kernelconcepts.de> + * + * Modifications from: + *   CIH <cih@coventive.com> + *   Nicolas Pitre <nico@cam.org> + *   Andrew Christian <andrew.christian@hp.com> + * + * Converted to the RTC subsystem and Driver Model + *   by Richard Purdie <rpurdie@rpsys.net> + * + * 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/platform_device.h> +#include <linux/module.h> +#include <linux/rtc.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/pm.h> + +#include <asm/bitops.h> +#include <asm/hardware.h> +#include <asm/irq.h> +#include <asm/rtc.h> + +#ifdef CONFIG_ARCH_PXA +#include <asm/arch/pxa-regs.h> +#endif + +#define TIMER_FREQ		CLOCK_TICK_RATE +#define RTC_DEF_DIVIDER		32768 - 1 +#define RTC_DEF_TRIM		0 + +static unsigned long rtc_freq = 1024; +static struct rtc_time rtc_alarm; +static spinlock_t sa1100_rtc_lock = SPIN_LOCK_UNLOCKED; + +static int rtc_update_alarm(struct rtc_time *alrm) +{ +	struct rtc_time alarm_tm, now_tm; +	unsigned long now, time; +	int ret; + +	do { +		now = RCNR; +		rtc_time_to_tm(now, &now_tm); +		rtc_next_alarm_time(&alarm_tm, &now_tm, alrm); +		ret = rtc_tm_to_time(&alarm_tm, &time); +		if (ret != 0) +			break; + +		RTSR = RTSR & (RTSR_HZE|RTSR_ALE|RTSR_AL); +		RTAR = time; +	} while (now != RCNR); + +	return ret; +} + +static irqreturn_t sa1100_rtc_interrupt(int irq, void *dev_id, +		struct pt_regs *regs) +{ +	struct platform_device *pdev = to_platform_device(dev_id); +	struct rtc_device *rtc = platform_get_drvdata(pdev); +	unsigned int rtsr; +	unsigned long events = 0; + +	spin_lock(&sa1100_rtc_lock); + +	rtsr = RTSR; +	/* clear interrupt sources */ +	RTSR = 0; +	RTSR = (RTSR_AL | RTSR_HZ) & (rtsr >> 2); + +	/* clear alarm interrupt if it has occurred */ +	if (rtsr & RTSR_AL) +		rtsr &= ~RTSR_ALE; +	RTSR = rtsr & (RTSR_ALE | RTSR_HZE); + +	/* update irq data & counter */ +	if (rtsr & RTSR_AL) +		events |= RTC_AF | RTC_IRQF; +	if (rtsr & RTSR_HZ) +		events |= RTC_UF | RTC_IRQF; + +	rtc_update_irq(&rtc->class_dev, 1, events); + +	if (rtsr & RTSR_AL && rtc_periodic_alarm(&rtc_alarm)) +		rtc_update_alarm(&rtc_alarm); + +	spin_unlock(&sa1100_rtc_lock); + +	return IRQ_HANDLED; +} + +static int rtc_timer1_count; + +static irqreturn_t timer1_interrupt(int irq, void *dev_id, +		struct pt_regs *regs) +{ +	struct platform_device *pdev = to_platform_device(dev_id); +	struct rtc_device *rtc = platform_get_drvdata(pdev); + +	/* +	 * If we match for the first time, rtc_timer1_count will be 1. +	 * Otherwise, we wrapped around (very unlikely but +	 * still possible) so compute the amount of missed periods. +	 * The match reg is updated only when the data is actually retrieved +	 * to avoid unnecessary interrupts. +	 */ +	OSSR = OSSR_M1;	/* clear match on timer1 */ + +	rtc_update_irq(&rtc->class_dev, rtc_timer1_count, RTC_PF | RTC_IRQF); + +	if (rtc_timer1_count == 1) +		rtc_timer1_count = (rtc_freq * ((1<<30)/(TIMER_FREQ>>2))); + +	return IRQ_HANDLED; +} + +static int sa1100_rtc_read_callback(struct device *dev, int data) +{ +	if (data & RTC_PF) { +		/* interpolate missed periods and set match for the next */ +		unsigned long period = TIMER_FREQ/rtc_freq; +		unsigned long oscr = OSCR; +		unsigned long osmr1 = OSMR1; +		unsigned long missed = (oscr - osmr1)/period; +		data += missed << 8; +		OSSR = OSSR_M1;	/* clear match on timer 1 */ +		OSMR1 = osmr1 + (missed + 1)*period; +		/* Ensure we didn't miss another match in the mean time. +		 * Here we compare (match - OSCR) 8 instead of 0 -- +		 * see comment in pxa_timer_interrupt() for explanation. +		 */ +		while( (signed long)((osmr1 = OSMR1) - OSCR) <= 8 ) { +			data += 0x100; +			OSSR = OSSR_M1;	/* clear match on timer 1 */ +			OSMR1 = osmr1 + period; +		} +	} +	return data; +} + +static int sa1100_rtc_open(struct device *dev) +{ +	int ret; + +	ret = request_irq(IRQ_RTC1Hz, sa1100_rtc_interrupt, SA_INTERRUPT, +				"rtc 1Hz", dev); +	if (ret) { +		printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_RTC1Hz); +		goto fail_ui; +	} +	ret = request_irq(IRQ_RTCAlrm, sa1100_rtc_interrupt, SA_INTERRUPT, +				"rtc Alrm", dev); +	if (ret) { +		printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_RTCAlrm); +		goto fail_ai; +	} +	ret = request_irq(IRQ_OST1, timer1_interrupt, SA_INTERRUPT, +				"rtc timer", dev); +	if (ret) { +		printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_OST1); +		goto fail_pi; +	} +	return 0; + + fail_pi: +	free_irq(IRQ_RTCAlrm, NULL); + fail_ai: +	free_irq(IRQ_RTC1Hz, NULL); + fail_ui: +	return ret; +} + +static void sa1100_rtc_release(struct device *dev) +{ +	spin_lock_irq(&sa1100_rtc_lock); +	RTSR = 0; +	OIER &= ~OIER_E1; +	OSSR = OSSR_M1; +	spin_unlock_irq(&sa1100_rtc_lock); + +	free_irq(IRQ_OST1, dev); +	free_irq(IRQ_RTCAlrm, dev); +	free_irq(IRQ_RTC1Hz, dev); +} + + +static int sa1100_rtc_ioctl(struct device *dev, unsigned int cmd, +		unsigned long arg) +{ +	switch(cmd) { +	case RTC_AIE_OFF: +		spin_lock_irq(&sa1100_rtc_lock); +		RTSR &= ~RTSR_ALE; +		spin_unlock_irq(&sa1100_rtc_lock); +		return 0; +	case RTC_AIE_ON: +		spin_lock_irq(&sa1100_rtc_lock); +		RTSR |= RTSR_ALE; +		spin_unlock_irq(&sa1100_rtc_lock); +		return 0; +	case RTC_UIE_OFF: +		spin_lock_irq(&sa1100_rtc_lock); +		RTSR &= ~RTSR_HZE; +		spin_unlock_irq(&sa1100_rtc_lock); +		return 0; +	case RTC_UIE_ON: +		spin_lock_irq(&sa1100_rtc_lock); +		RTSR |= RTSR_HZE; +		spin_unlock_irq(&sa1100_rtc_lock); +		return 0; +	case RTC_PIE_OFF: +		spin_lock_irq(&sa1100_rtc_lock); +		OIER &= ~OIER_E1; +		spin_unlock_irq(&sa1100_rtc_lock); +		return 0; +	case RTC_PIE_ON: +		if ((rtc_freq > 64) && !capable(CAP_SYS_RESOURCE)) +			return -EACCES; +		spin_lock_irq(&sa1100_rtc_lock); +		OSMR1 = TIMER_FREQ/rtc_freq + OSCR; +		OIER |= OIER_E1; +		rtc_timer1_count = 1; +		spin_unlock_irq(&sa1100_rtc_lock); +		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; +		return 0; +	} +	return -EINVAL; +} + +static int sa1100_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ +	rtc_time_to_tm(RCNR, tm); +	return 0; +} + +static int sa1100_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ +	unsigned long time; +	int ret; + +	ret = rtc_tm_to_time(tm, &time); +	if (ret == 0) +		RCNR = time; +	return ret; +} + +static int sa1100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ +	memcpy(&alrm->time, &rtc_alarm, sizeof(struct rtc_time)); +	alrm->pending = RTSR & RTSR_AL ? 1 : 0; +	return 0; +} + +static int sa1100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ +	int ret; + +	spin_lock_irq(&sa1100_rtc_lock); +	ret = rtc_update_alarm(&alrm->time); +	if (ret == 0) { +		memcpy(&rtc_alarm, &alrm->time, sizeof(struct rtc_time)); + +		if (alrm->enabled) +			enable_irq_wake(IRQ_RTCAlrm); +		else +			disable_irq_wake(IRQ_RTCAlrm); +	} +	spin_unlock_irq(&sa1100_rtc_lock); + +	return ret; +} + +static int sa1100_rtc_proc(struct device *dev, struct seq_file *seq) +{ +	seq_printf(seq, "trim/divider\t: 0x%08x\n", RTTR); +	seq_printf(seq, "alarm_IRQ\t: %s\n", +			(RTSR & RTSR_ALE) ? "yes" : "no" ); +	seq_printf(seq, "update_IRQ\t: %s\n", +			(RTSR & RTSR_HZE) ? "yes" : "no"); +	seq_printf(seq, "periodic_IRQ\t: %s\n", +			(OIER & OIER_E1) ? "yes" : "no"); +	seq_printf(seq, "periodic_freq\t: %ld\n", rtc_freq); + +	return 0; +} + +static struct rtc_class_ops sa1100_rtc_ops = { +	.open = sa1100_rtc_open, +	.read_callback = sa1100_rtc_read_callback, +	.release = sa1100_rtc_release, +	.ioctl = sa1100_rtc_ioctl, +	.read_time = sa1100_rtc_read_time, +	.set_time = sa1100_rtc_set_time, +	.read_alarm = sa1100_rtc_read_alarm, +	.set_alarm = sa1100_rtc_set_alarm, +	.proc = sa1100_rtc_proc, +}; + +static int sa1100_rtc_probe(struct platform_device *pdev) +{ +	struct rtc_device *rtc; + +	/* +	 * 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. +	 */ +	if (RTTR == 0) { +		RTTR = RTC_DEF_DIVIDER + (RTC_DEF_TRIM << 16); +		printk(KERN_WARNING "rtc: warning: initializing default clock divider/trim value\n"); +		/* The current RTC value probably doesn't make sense either */ +		RCNR = 0; +	} + +	rtc = rtc_device_register(pdev->name, &pdev->dev, &sa1100_rtc_ops, +				THIS_MODULE); + +	if (IS_ERR(rtc)) { +		dev_err(&pdev->dev, "Unable to register the RTC device\n"); +		return PTR_ERR(rtc); +	} + +	platform_set_drvdata(pdev, rtc); + +	dev_info(&pdev->dev, "SA11xx/PXA2xx RTC Registered\n"); + +	return 0; +} + +static int sa1100_rtc_remove(struct platform_device *pdev) +{ +	struct rtc_device *rtc = platform_get_drvdata(pdev); + + 	if (rtc) +		rtc_device_unregister(rtc); + +	return 0; +} + +static struct platform_driver sa1100_rtc_driver = { +	.probe		= sa1100_rtc_probe, +	.remove		= sa1100_rtc_remove, +	.driver		= { +		.name		= "sa1100-rtc", +	}, +}; + +static int __init sa1100_rtc_init(void) +{ +	return platform_driver_register(&sa1100_rtc_driver); +} + +static void __exit sa1100_rtc_exit(void) +{ +	platform_driver_unregister(&sa1100_rtc_driver); +} + +module_init(sa1100_rtc_init); +module_exit(sa1100_rtc_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("SA11x0/PXA2xx Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL");  |