aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/rtc/rtc-coh901331.c
diff options
context:
space:
mode:
authorLinus Walleij <linus.ml.walleij@gmail.com>2009-09-22 19:46:24 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-23 10:39:45 -0400
commitaa958f571ec9492b8100302ee70ac0ab2598bf19 (patch)
treedd046059120e31fb90527db180fff00b586e673d /drivers/rtc/rtc-coh901331.c
parentd00ed3cf6e54312fb59cd1fd6300d787d22373c7 (diff)
rtc: U300 COH 901 331 RTC driver v3
This adds a driver for the RTC COH 901 331 found in the ST-Ericsson U300 series mobile platforms to the RTC subsystem. It integrates to the ARM kernel support recently added to RMKs ARM tree and will be enabled in the U300 defconfig in due time. Signed-off-by: Linus Walleij <linus.walleij@stericsson.com> Signed-off-by: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc/rtc-coh901331.c')
-rw-r--r--drivers/rtc/rtc-coh901331.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-coh901331.c b/drivers/rtc/rtc-coh901331.c
new file mode 100644
index 000000000000..7fe1fa26c52c
--- /dev/null
+++ b/drivers/rtc/rtc-coh901331.c
@@ -0,0 +1,311 @@
1/*
2 * Copyright (C) 2007-2009 ST-Ericsson AB
3 * License terms: GNU General Public License (GPL) version 2
4 * Real Time Clock interface for ST-Ericsson AB COH 901 331 RTC.
5 * Author: Linus Walleij <linus.walleij@stericsson.com>
6 * Based on rtc-pl031.c by Deepak Saxena <dsaxena@plexity.net>
7 * Copyright 2006 (c) MontaVista Software, Inc.
8 */
9#include <linux/init.h>
10#include <linux/module.h>
11#include <linux/rtc.h>
12#include <linux/clk.h>
13#include <linux/interrupt.h>
14#include <linux/pm.h>
15#include <linux/platform_device.h>
16#include <linux/io.h>
17
18/*
19 * Registers in the COH 901 331
20 */
21/* Alarm value 32bit (R/W) */
22#define COH901331_ALARM 0x00U
23/* Used to set current time 32bit (R/W) */
24#define COH901331_SET_TIME 0x04U
25/* Indication if current time is valid 32bit (R/-) */
26#define COH901331_VALID 0x08U
27/* Read the current time 32bit (R/-) */
28#define COH901331_CUR_TIME 0x0cU
29/* Event register for the "alarm" interrupt */
30#define COH901331_IRQ_EVENT 0x10U
31/* Mask register for the "alarm" interrupt */
32#define COH901331_IRQ_MASK 0x14U
33/* Force register for the "alarm" interrupt */
34#define COH901331_IRQ_FORCE 0x18U
35
36/*
37 * Reference to RTC block clock
38 * Notice that the frequent clk_enable()/clk_disable() on this
39 * clock is mainly to be able to turn on/off other clocks in the
40 * hierarchy as needed, the RTC clock is always on anyway.
41 */
42struct coh901331_port {
43 struct rtc_device *rtc;
44 struct clk *clk;
45 u32 phybase;
46 u32 physize;
47 void __iomem *virtbase;
48 int irq;
49#ifdef CONFIG_PM
50 u32 irqmaskstore;
51#endif
52};
53
54static irqreturn_t coh901331_interrupt(int irq, void *data)
55{
56 struct coh901331_port *rtap = data;
57
58 clk_enable(rtap->clk);
59 /* Ack IRQ */
60 writel(1, rtap->virtbase + COH901331_IRQ_EVENT);
61 clk_disable(rtap->clk);
62 /* Set alarm flag */
63 rtc_update_irq(rtap->rtc, 1, RTC_AF);
64
65 return IRQ_HANDLED;
66}
67
68static int coh901331_read_time(struct device *dev, struct rtc_time *tm)
69{
70 struct coh901331_port *rtap = dev_get_drvdata(dev);
71
72 clk_enable(rtap->clk);
73 /* Check if the time is valid */
74 if (readl(rtap->virtbase + COH901331_VALID)) {
75 rtc_time_to_tm(readl(rtap->virtbase + COH901331_CUR_TIME), tm);
76 clk_disable(rtap->clk);
77 return rtc_valid_tm(tm);
78 }
79 clk_disable(rtap->clk);
80 return -EINVAL;
81}
82
83static int coh901331_set_mmss(struct device *dev, unsigned long secs)
84{
85 struct coh901331_port *rtap = dev_get_drvdata(dev);
86
87 clk_enable(rtap->clk);
88 writel(secs, rtap->virtbase + COH901331_SET_TIME);
89 clk_disable(rtap->clk);
90
91 return 0;
92}
93
94static int coh901331_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
95{
96 struct coh901331_port *rtap = dev_get_drvdata(dev);
97
98 clk_enable(rtap->clk);
99 rtc_time_to_tm(readl(rtap->virtbase + COH901331_ALARM), &alarm->time);
100 alarm->pending = readl(rtap->virtbase + COH901331_IRQ_EVENT) & 1U;
101 alarm->enabled = readl(rtap->virtbase + COH901331_IRQ_MASK) & 1U;
102 clk_disable(rtap->clk);
103
104 return 0;
105}
106
107static int coh901331_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
108{
109 struct coh901331_port *rtap = dev_get_drvdata(dev);
110 unsigned long time;
111
112 rtc_tm_to_time(&alarm->time, &time);
113 clk_enable(rtap->clk);
114 writel(time, rtap->virtbase + COH901331_ALARM);
115 writel(alarm->enabled, rtap->virtbase + COH901331_IRQ_MASK);
116 clk_disable(rtap->clk);
117
118 return 0;
119}
120
121static int coh901331_alarm_irq_enable(struct device *dev, unsigned int enabled)
122{
123 struct coh901331_port *rtap = dev_get_drvdata(dev);
124
125 clk_enable(rtap->clk);
126 if (enabled)
127 writel(1, rtap->virtbase + COH901331_IRQ_MASK);
128 else
129 writel(0, rtap->virtbase + COH901331_IRQ_MASK);
130 clk_disable(rtap->clk);
131}
132
133static struct rtc_class_ops coh901331_ops = {
134 .read_time = coh901331_read_time,
135 .set_mmss = coh901331_set_mmss,
136 .read_alarm = coh901331_read_alarm,
137 .set_alarm = coh901331_set_alarm,
138 .alarm_irq_enable = coh901331_alarm_irq_enable,
139};
140
141static int __exit coh901331_remove(struct platform_device *pdev)
142{
143 struct coh901331_port *rtap = dev_get_drvdata(&pdev->dev);
144
145 if (rtap) {
146 free_irq(rtap->irq, rtap);
147 rtc_device_unregister(rtap->rtc);
148 clk_put(rtap->clk);
149 iounmap(rtap->virtbase);
150 release_mem_region(rtap->phybase, rtap->physize);
151 platform_set_drvdata(pdev, NULL);
152 kfree(rtap);
153 }
154
155 return 0;
156}
157
158
159static int __init coh901331_probe(struct platform_device *pdev)
160{
161 int ret;
162 struct coh901331_port *rtap;
163 struct resource *res;
164
165 rtap = kzalloc(sizeof(struct coh901331_port), GFP_KERNEL);
166 if (!rtap)
167 return -ENOMEM;
168
169 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
170 if (!res) {
171 ret = -ENOENT;
172 goto out_no_resource;
173 }
174 rtap->phybase = res->start;
175 rtap->physize = resource_size(res);
176
177 if (request_mem_region(rtap->phybase, rtap->physize,
178 "rtc-coh901331") == NULL) {
179 ret = -EBUSY;
180 goto out_no_memregion;
181 }
182
183 rtap->virtbase = ioremap(rtap->phybase, rtap->physize);
184 if (!rtap->virtbase) {
185 ret = -ENOMEM;
186 goto out_no_remap;
187 }
188
189 rtap->irq = platform_get_irq(pdev, 0);
190 if (request_irq(rtap->irq, coh901331_interrupt, IRQF_DISABLED,
191 "RTC COH 901 331 Alarm", rtap)) {
192 ret = -EIO;
193 goto out_no_irq;
194 }
195
196 rtap->clk = clk_get(&pdev->dev, NULL);
197 if (IS_ERR(rtap->clk)) {
198 ret = PTR_ERR(rtap->clk);
199 dev_err(&pdev->dev, "could not get clock\n");
200 goto out_no_clk;
201 }
202
203 /* We enable/disable the clock only to assure it works */
204 ret = clk_enable(rtap->clk);
205 if (ret) {
206 dev_err(&pdev->dev, "could not enable clock\n");
207 goto out_no_clk_enable;
208 }
209 clk_disable(rtap->clk);
210
211 rtap->rtc = rtc_device_register("coh901331", &pdev->dev, &coh901331_ops,
212 THIS_MODULE);
213 if (IS_ERR(rtap->rtc)) {
214 ret = PTR_ERR(rtap->rtc);
215 goto out_no_rtc;
216 }
217
218 platform_set_drvdata(pdev, rtap);
219
220 return 0;
221
222 out_no_rtc:
223 out_no_clk_enable:
224 clk_put(rtap->clk);
225 out_no_clk:
226 free_irq(rtap->irq, rtap);
227 out_no_irq:
228 iounmap(rtap->virtbase);
229 out_no_remap:
230 platform_set_drvdata(pdev, NULL);
231 out_no_memregion:
232 release_mem_region(rtap->phybase, SZ_4K);
233 out_no_resource:
234 kfree(rtap);
235 return ret;
236}
237
238#ifdef CONFIG_PM
239static int coh901331_suspend(struct platform_device *pdev, pm_message_t state)
240{
241 struct coh901331_port *rtap = dev_get_drvdata(&pdev->dev);
242
243 /*
244 * If this RTC alarm will be used for waking the system up,
245 * don't disable it of course. Else we just disable the alarm
246 * and await suspension.
247 */
248 if (device_may_wakeup(&pdev->dev)) {
249 enable_irq_wake(rtap->irq);
250 } else {
251 clk_enable(rtap->clk);
252 rtap->irqmaskstore = readl(rtap->virtbase + COH901331_IRQ_MASK);
253 writel(0, rtap->virtbase + COH901331_IRQ_MASK);
254 clk_disable(rtap->clk);
255 }
256 return 0;
257}
258
259static int coh901331_resume(struct platform_device *pdev)
260{
261 struct coh901331_port *rtap = dev_get_drvdata(&pdev->dev);
262
263 if (device_may_wakeup(&pdev->dev))
264 disable_irq_wake(rtap->irq);
265 else
266 clk_enable(rtap->clk);
267 writel(rtap->irqmaskstore, rtap->virtbase + COH901331_IRQ_MASK);
268 clk_disable(rtap->clk);
269 return 0;
270}
271#else
272#define coh901331_suspend NULL
273#define coh901331_resume NULL
274#endif
275
276static void coh901331_shutdown(struct platform_device *pdev)
277{
278 struct coh901331_port *rtap = dev_get_drvdata(&pdev->dev);
279
280 clk_enable(rtap->clk);
281 writel(0, rtap->virtbase + COH901331_IRQ_MASK);
282 clk_disable(rtap->clk);
283}
284
285static struct platform_driver coh901331_driver = {
286 .driver = {
287 .name = "rtc-coh901331",
288 .owner = THIS_MODULE,
289 },
290 .remove = __exit_p(coh901331_remove),
291 .suspend = coh901331_suspend,
292 .resume = coh901331_resume,
293 .shutdown = coh901331_shutdown,
294};
295
296static int __init coh901331_init(void)
297{
298 return platform_driver_probe(&coh901331_driver, coh901331_probe);
299}
300
301static void __exit coh901331_exit(void)
302{
303 platform_driver_unregister(&coh901331_driver);
304}
305
306module_init(coh901331_init);
307module_exit(coh901331_exit);
308
309MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>");
310MODULE_DESCRIPTION("ST-Ericsson AB COH 901 331 RTC Driver");
311MODULE_LICENSE("GPL");