diff options
author | Hans-Christian Egtvedt <hcegtvedt@atmel.com> | 2007-07-17 07:05:00 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-17 13:23:09 -0400 |
commit | fa04e78b2d44cb923177d7e6988ac32639beb2d0 (patch) | |
tree | 12a3f9970ba4839002a1b121f17bc7d6b8966343 | |
parent | ac495bf8971f2dc9d401d892849977e86633ced6 (diff) |
Driver for the Atmel on-chip RTC on AT32AP700x devices
Tested on the AT32AP7000/ATSTK1000. Driver does only suport time, wake up
and a very simple alarm, because of hardware limitations.
Hardware documentation can be found in the AT32AP7000 data sheet, which can
be downloaded from
http://www.atmel.com/dyn/products/datasheets.asp?family_id=682
From: David Brownell <david-b@pacbell.net>
- Strike some alarm setup code that's no longer needed.
(This patch seems to have gotten lost somewhere...)
- Make the driver name (and its module alias) match what
the platform setup code uses, so the driver can bind
and hotplug.
[akpm@linux-foundation.org: fix several checkpatch.pl warnings]
Signed-off-by: Hans-Christian Egtvedt <hcegtvedt@atmel.com>
Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/rtc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-at32ap700x.c | 317 |
3 files changed, 325 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 905d3308253..d2bd8de9403 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -379,6 +379,13 @@ config RTC_DRV_PL031 | |||
379 | To compile this driver as a module, choose M here: the | 379 | To compile this driver as a module, choose M here: the |
380 | module will be called rtc-pl031. | 380 | module will be called rtc-pl031. |
381 | 381 | ||
382 | config RTC_DRV_AT32AP700X | ||
383 | tristate "AT32AP700X series RTC" | ||
384 | depends on RTC_CLASS && PLATFORM_AT32AP | ||
385 | help | ||
386 | Driver for the internal RTC (Realtime Clock) on Atmel AVR32 | ||
387 | AT32AP700x family processors. | ||
388 | |||
382 | config RTC_DRV_AT91RM9200 | 389 | config RTC_DRV_AT91RM9200 |
383 | tristate "AT91RM9200" | 390 | tristate "AT91RM9200" |
384 | depends on RTC_CLASS && ARCH_AT91RM9200 | 391 | depends on RTC_CLASS && ARCH_AT91RM9200 |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index a1afbc23607..5fb1c102f30 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -19,6 +19,7 @@ obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o | |||
19 | obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o | 19 | obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o |
20 | obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o | 20 | obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o |
21 | obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o | 21 | obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o |
22 | obj-$(CONFIG_RTC_DRV_AT32AP700X) += rtc-at32ap700x.o | ||
22 | obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o | 23 | obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o |
23 | obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o | 24 | obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o |
24 | obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o | 25 | obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o |
diff --git a/drivers/rtc/rtc-at32ap700x.c b/drivers/rtc/rtc-at32ap700x.c new file mode 100644 index 00000000000..2999214ca53 --- /dev/null +++ b/drivers/rtc/rtc-at32ap700x.c | |||
@@ -0,0 +1,317 @@ | |||
1 | /* | ||
2 | * An RTC driver for the AVR32 AT32AP700x processor series. | ||
3 | * | ||
4 | * Copyright (C) 2007 Atmel Corporation | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License version 2 as published | ||
8 | * by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/platform_device.h> | ||
14 | #include <linux/rtc.h> | ||
15 | #include <linux/io.h> | ||
16 | |||
17 | /* | ||
18 | * This is a bare-bones RTC. It runs during most system sleep states, but has | ||
19 | * no battery backup and gets reset during system restart. It must be | ||
20 | * initialized from an external clock (network, I2C, etc) before it can be of | ||
21 | * much use. | ||
22 | * | ||
23 | * The alarm functionality is limited by the hardware, not supporting | ||
24 | * periodic interrupts. | ||
25 | */ | ||
26 | |||
27 | #define RTC_CTRL 0x00 | ||
28 | #define RTC_CTRL_EN 0 | ||
29 | #define RTC_CTRL_PCLR 1 | ||
30 | #define RTC_CTRL_TOPEN 2 | ||
31 | #define RTC_CTRL_PSEL 8 | ||
32 | |||
33 | #define RTC_VAL 0x04 | ||
34 | |||
35 | #define RTC_TOP 0x08 | ||
36 | |||
37 | #define RTC_IER 0x10 | ||
38 | #define RTC_IER_TOPI 0 | ||
39 | |||
40 | #define RTC_IDR 0x14 | ||
41 | #define RTC_IDR_TOPI 0 | ||
42 | |||
43 | #define RTC_IMR 0x18 | ||
44 | #define RTC_IMR_TOPI 0 | ||
45 | |||
46 | #define RTC_ISR 0x1c | ||
47 | #define RTC_ISR_TOPI 0 | ||
48 | |||
49 | #define RTC_ICR 0x20 | ||
50 | #define RTC_ICR_TOPI 0 | ||
51 | |||
52 | #define RTC_BIT(name) (1 << RTC_##name) | ||
53 | #define RTC_BF(name, value) ((value) << RTC_##name) | ||
54 | |||
55 | #define rtc_readl(dev, reg) \ | ||
56 | __raw_readl((dev)->regs + RTC_##reg) | ||
57 | #define rtc_writel(dev, reg, value) \ | ||
58 | __raw_writel((value), (dev)->regs + RTC_##reg) | ||
59 | |||
60 | struct rtc_at32ap700x { | ||
61 | struct rtc_device *rtc; | ||
62 | void __iomem *regs; | ||
63 | unsigned long alarm_time; | ||
64 | unsigned long irq; | ||
65 | /* Protect against concurrent register access. */ | ||
66 | spinlock_t lock; | ||
67 | }; | ||
68 | |||
69 | static int at32_rtc_readtime(struct device *dev, struct rtc_time *tm) | ||
70 | { | ||
71 | struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
72 | unsigned long now; | ||
73 | |||
74 | now = rtc_readl(rtc, VAL); | ||
75 | rtc_time_to_tm(now, tm); | ||
76 | |||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | static int at32_rtc_settime(struct device *dev, struct rtc_time *tm) | ||
81 | { | ||
82 | struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
83 | unsigned long now; | ||
84 | int ret; | ||
85 | |||
86 | ret = rtc_tm_to_time(tm, &now); | ||
87 | if (ret == 0) | ||
88 | rtc_writel(rtc, VAL, now); | ||
89 | |||
90 | return ret; | ||
91 | } | ||
92 | |||
93 | static int at32_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
94 | { | ||
95 | struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
96 | |||
97 | rtc_time_to_tm(rtc->alarm_time, &alrm->time); | ||
98 | alrm->pending = rtc_readl(rtc, IMR) & RTC_BIT(IMR_TOPI) ? 1 : 0; | ||
99 | |||
100 | return 0; | ||
101 | } | ||
102 | |||
103 | static int at32_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
104 | { | ||
105 | struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
106 | unsigned long rtc_unix_time; | ||
107 | unsigned long alarm_unix_time; | ||
108 | int ret; | ||
109 | |||
110 | rtc_unix_time = rtc_readl(rtc, VAL); | ||
111 | |||
112 | ret = rtc_tm_to_time(&alrm->time, &alarm_unix_time); | ||
113 | if (ret) | ||
114 | return ret; | ||
115 | |||
116 | if (alarm_unix_time < rtc_unix_time) | ||
117 | return -EINVAL; | ||
118 | |||
119 | spin_lock_irq(&rtc->lock); | ||
120 | rtc->alarm_time = alarm_unix_time; | ||
121 | rtc_writel(rtc, TOP, rtc->alarm_time); | ||
122 | if (alrm->pending) | ||
123 | rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
124 | | RTC_BIT(CTRL_TOPEN)); | ||
125 | else | ||
126 | rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
127 | & ~RTC_BIT(CTRL_TOPEN)); | ||
128 | spin_unlock_irq(&rtc->lock); | ||
129 | |||
130 | return ret; | ||
131 | } | ||
132 | |||
133 | static int at32_rtc_ioctl(struct device *dev, unsigned int cmd, | ||
134 | unsigned long arg) | ||
135 | { | ||
136 | struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
137 | int ret = 0; | ||
138 | |||
139 | spin_lock_irq(&rtc->lock); | ||
140 | |||
141 | switch (cmd) { | ||
142 | case RTC_AIE_ON: | ||
143 | if (rtc_readl(rtc, VAL) > rtc->alarm_time) { | ||
144 | ret = -EINVAL; | ||
145 | break; | ||
146 | } | ||
147 | rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
148 | | RTC_BIT(CTRL_TOPEN)); | ||
149 | rtc_writel(rtc, ICR, RTC_BIT(ICR_TOPI)); | ||
150 | rtc_writel(rtc, IER, RTC_BIT(IER_TOPI)); | ||
151 | break; | ||
152 | case RTC_AIE_OFF: | ||
153 | rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
154 | & ~RTC_BIT(CTRL_TOPEN)); | ||
155 | rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); | ||
156 | rtc_writel(rtc, ICR, RTC_BIT(ICR_TOPI)); | ||
157 | break; | ||
158 | default: | ||
159 | ret = -ENOIOCTLCMD; | ||
160 | break; | ||
161 | } | ||
162 | |||
163 | spin_unlock_irq(&rtc->lock); | ||
164 | |||
165 | return ret; | ||
166 | } | ||
167 | |||
168 | static irqreturn_t at32_rtc_interrupt(int irq, void *dev_id) | ||
169 | { | ||
170 | struct rtc_at32ap700x *rtc = (struct rtc_at32ap700x *)dev_id; | ||
171 | unsigned long isr = rtc_readl(rtc, ISR); | ||
172 | unsigned long events = 0; | ||
173 | int ret = IRQ_NONE; | ||
174 | |||
175 | spin_lock(&rtc->lock); | ||
176 | |||
177 | if (isr & RTC_BIT(ISR_TOPI)) { | ||
178 | rtc_writel(rtc, ICR, RTC_BIT(ICR_TOPI)); | ||
179 | rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); | ||
180 | rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
181 | & ~RTC_BIT(CTRL_TOPEN)); | ||
182 | rtc_writel(rtc, VAL, rtc->alarm_time); | ||
183 | events = RTC_AF | RTC_IRQF; | ||
184 | rtc_update_irq(rtc->rtc, 1, events); | ||
185 | ret = IRQ_HANDLED; | ||
186 | } | ||
187 | |||
188 | spin_unlock(&rtc->lock); | ||
189 | |||
190 | return ret; | ||
191 | } | ||
192 | |||
193 | static struct rtc_class_ops at32_rtc_ops = { | ||
194 | .ioctl = at32_rtc_ioctl, | ||
195 | .read_time = at32_rtc_readtime, | ||
196 | .set_time = at32_rtc_settime, | ||
197 | .read_alarm = at32_rtc_readalarm, | ||
198 | .set_alarm = at32_rtc_setalarm, | ||
199 | }; | ||
200 | |||
201 | static int __init at32_rtc_probe(struct platform_device *pdev) | ||
202 | { | ||
203 | struct resource *regs; | ||
204 | struct rtc_at32ap700x *rtc; | ||
205 | int irq = -1; | ||
206 | int ret; | ||
207 | |||
208 | rtc = kzalloc(sizeof(struct rtc_at32ap700x), GFP_KERNEL); | ||
209 | if (!rtc) { | ||
210 | dev_dbg(&pdev->dev, "out of memory\n"); | ||
211 | return -ENOMEM; | ||
212 | } | ||
213 | |||
214 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
215 | if (!regs) { | ||
216 | dev_dbg(&pdev->dev, "no mmio resource defined\n"); | ||
217 | ret = -ENXIO; | ||
218 | goto out; | ||
219 | } | ||
220 | |||
221 | irq = platform_get_irq(pdev, 0); | ||
222 | if (irq < 0) { | ||
223 | dev_dbg(&pdev->dev, "could not get irq\n"); | ||
224 | ret = -ENXIO; | ||
225 | goto out; | ||
226 | } | ||
227 | |||
228 | ret = request_irq(irq, at32_rtc_interrupt, IRQF_SHARED, "rtc", rtc); | ||
229 | if (ret) { | ||
230 | dev_dbg(&pdev->dev, "could not request irq %d\n", irq); | ||
231 | goto out; | ||
232 | } | ||
233 | |||
234 | rtc->irq = irq; | ||
235 | rtc->regs = ioremap(regs->start, regs->end - regs->start + 1); | ||
236 | if (!rtc->regs) { | ||
237 | ret = -ENOMEM; | ||
238 | dev_dbg(&pdev->dev, "could not map I/O memory\n"); | ||
239 | goto out_free_irq; | ||
240 | } | ||
241 | spin_lock_init(&rtc->lock); | ||
242 | |||
243 | /* | ||
244 | * Maybe init RTC: count from zero at 1 Hz, disable wrap irq. | ||
245 | * | ||
246 | * Do not reset VAL register, as it can hold an old time | ||
247 | * from last JTAG reset. | ||
248 | */ | ||
249 | if (!(rtc_readl(rtc, CTRL) & RTC_BIT(CTRL_EN))) { | ||
250 | rtc_writel(rtc, CTRL, RTC_BIT(CTRL_PCLR)); | ||
251 | rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); | ||
252 | rtc_writel(rtc, CTRL, RTC_BF(CTRL_PSEL, 0xe) | ||
253 | | RTC_BIT(CTRL_EN)); | ||
254 | } | ||
255 | |||
256 | rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, | ||
257 | &at32_rtc_ops, THIS_MODULE); | ||
258 | if (IS_ERR(rtc->rtc)) { | ||
259 | dev_dbg(&pdev->dev, "could not register rtc device\n"); | ||
260 | ret = PTR_ERR(rtc->rtc); | ||
261 | goto out_iounmap; | ||
262 | } | ||
263 | |||
264 | platform_set_drvdata(pdev, rtc); | ||
265 | |||
266 | dev_info(&pdev->dev, "Atmel RTC for AT32AP700x at %08lx irq %ld\n", | ||
267 | (unsigned long)rtc->regs, rtc->irq); | ||
268 | |||
269 | return 0; | ||
270 | |||
271 | out_iounmap: | ||
272 | iounmap(rtc->regs); | ||
273 | out_free_irq: | ||
274 | free_irq(irq, rtc); | ||
275 | out: | ||
276 | kfree(rtc); | ||
277 | return ret; | ||
278 | } | ||
279 | |||
280 | static int __exit at32_rtc_remove(struct platform_device *pdev) | ||
281 | { | ||
282 | struct rtc_at32ap700x *rtc = platform_get_drvdata(pdev); | ||
283 | |||
284 | free_irq(rtc->irq, rtc); | ||
285 | iounmap(rtc->regs); | ||
286 | rtc_device_unregister(rtc->rtc); | ||
287 | kfree(rtc); | ||
288 | platform_set_drvdata(pdev, NULL); | ||
289 | |||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | MODULE_ALIAS("at32ap700x_rtc"); | ||
294 | |||
295 | static struct platform_driver at32_rtc_driver = { | ||
296 | .remove = __exit_p(at32_rtc_remove), | ||
297 | .driver = { | ||
298 | .name = "at32ap700x_rtc", | ||
299 | .owner = THIS_MODULE, | ||
300 | }, | ||
301 | }; | ||
302 | |||
303 | static int __init at32_rtc_init(void) | ||
304 | { | ||
305 | return platform_driver_probe(&at32_rtc_driver, at32_rtc_probe); | ||
306 | } | ||
307 | module_init(at32_rtc_init); | ||
308 | |||
309 | static void __exit at32_rtc_exit(void) | ||
310 | { | ||
311 | platform_driver_unregister(&at32_rtc_driver); | ||
312 | } | ||
313 | module_exit(at32_rtc_exit); | ||
314 | |||
315 | MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); | ||
316 | MODULE_DESCRIPTION("Real time clock for AVR32 AT32AP700x"); | ||
317 | MODULE_LICENSE("GPL"); | ||